Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Deploy contract to RPC server #152

Merged
merged 12 commits into from
Sep 29, 2022
Merged
Show file tree
Hide file tree
Changes from 2 commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
19 changes: 10 additions & 9 deletions Cargo.lock

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

3 changes: 2 additions & 1 deletion Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -28,7 +28,8 @@ clap_complete = "3.2.3"
prettyplease = "0.1.18"
syn = { version = "1.0.99", features = ["parsing"] }
wasmparser = "0.90.0"
sha2 = "0.10.2"
sha2 = "0.10.6"
ed25519-dalek = "1.0.1"

[patch.crates-io]
soroban-spec = { git = "https://github.com/stellar/rs-soroban-sdk", rev = "49feecab" }
Expand Down
99 changes: 99 additions & 0 deletions src/deploy.rs
Original file line number Diff line number Diff line change
@@ -1,8 +1,20 @@
use std::{fmt::Debug, fs, io};

use clap::Parser;
use ed25519_dalek;
use ed25519_dalek::Signer;
use hex::FromHexError;
use sha2::{Digest, Sha256, Sha512};
use soroban_env_host::xdr::LedgerKey::ContractData;
use soroban_env_host::xdr::ScStatic::LedgerKeyContractCode;
use soroban_env_host::xdr::{
Hash, HashIdPreimageEd25519ContractId, HostFunction, InvokeHostFunctionOp, LedgerFootprint,
LedgerKey, LedgerKeyContractData, Memo, Operation, OperationBody, Preconditions, ScObject,
ScVal, Transaction, TransactionEnvelope, TransactionExt, TransactionV1Envelope, Uint256,
WriteXdr,
};
use soroban_env_host::{xdr::Error as XdrError, HostError};
use stellar_strkey::{DecodeError, StrkeyPrivateKeyEd25519};

use crate::snapshot::{self, get_default_ledger_info};
use crate::utils;
Expand Down Expand Up @@ -46,6 +58,8 @@ pub enum Error {
contract_id: String,
error: FromHexError,
},
#[error("cannot parse private key")]
CannotParsePrivateKey,
}

impl Cmd {
Expand Down Expand Up @@ -77,4 +91,89 @@ impl Cmd {
)?;
Ok(())
}

fn build_create_contract_tx(
contract: Vec<u8>,
key: ed25519_dalek::Keypair,
) -> Result<TransactionEnvelope, Error> {
// TODO: generate the salt
// TODO: should the salt be provided by the end user?
let salt = Sha256::digest(b"a1");
2opremio marked this conversation as resolved.
Show resolved Hide resolved

let separator =
b"create_contract_from_ed25519(contract: Vec<u8>, salt: u256, key: u256, sig: Vec<u8>)";
let mut hasher = Sha256::new();
hasher.update(separator);
hasher.update(salt);
hasher.update(contract);
let hash = hasher.finalize();

let sig = key.sign(&hash);
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Once stellar/rs-soroban-env#473 is merged (should be soon), which adds create_contract_from_source_account, we should remove these lines of code and do the deploy from the source account of the transaction. You won't need to sign the internals, just the outside Stellar tx which is simpler and we only need to handle the simple case from the CLI.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Where is create_contract_from_source_account documented?

What's the advantage of using it?

And finally, will the current approach still work?

Copy link
Member

@leighmcculloch leighmcculloch Sep 27, 2022

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Where is create_contract_from_source_account documented?

All host functions are captured in this file:
https://github.com/stellar/rs-soroban-env/blob/e110dff77241cee23f6b274022b38bf4210f8577/soroban-env-common/src/env.rs#L308

The documentation is sparse on it, so adding some in stellar/rs-soroban-env#488.

What's the advantage of using it?

It requires less signatures as no auth on the actual invocation is required. The Stellar transaction inherently provides the auth. This is also the only way a wallet like Freighter will support signing starting out, so the soroban-cli serve command needs to support deployment via this way, so may as well make it the only way for now to deploy in the CLI.

And finally, will the current approach still work?

Maybe, can you see the link to the issue here: https://stellarfoundation.slack.com/archives/C030Z9EHVQE/p1664297563128759

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@leighmcculloch , which cap/doc has writeup on how the InvokeHostFunction operation/tx is assembled per shown here in rs? I'm transposing this to horizon/go, and wanted to use it as reference if available, thanks!

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

You're looking for CAP-47, but it hasn't been updated to include create_contract_from_source_account yet.


let preimage = HashIdPreimageEd25519ContractId {
ed25519: Uint256(key.secret.as_bytes().clone()),
salt: Uint256(salt.into()),
};
let preimage_xdr = preimage.to_xdr()?;
let contract_id = Sha256::digest(preimage_xdr);

// TODO: clean up duplicated code and check whether the type conversions here make sense
let contract_parameter = ScVal::Object(Some(ScObject::Bytes(contract.try_into()?)));
let salt_parameter = ScVal::Object(Some(ScObject::Bytes(salt.into()?)));
let public_key_parameter =
ScVal::Object(Some(ScObject::Bytes(key.public.as_bytes().into()?)));
let signature_parameter = ScVal::Object(Some(ScObject::Bytes(sig.to_bytes().into()?)));

// TODO: reorder code properly
let lk = LedgerKey::ContractData(LedgerKeyContractData {
contract_id: Hash(contract_id.into()),
key: ScVal::Static(LedgerKeyContractCode),
});

let op = Operation {
source_account: None,
body: OperationBody::InvokeHostFunction(InvokeHostFunctionOp {
function: HostFunction::CreateContract,
parameters:
// TODO: cast to VecM
vec![
contract_parameter,
salt_parameter,
public_key_parameter,
signature_parameter,
],
2opremio marked this conversation as resolved.
Show resolved Hide resolved
footprint: LedgerFootprint {
read_only: Default::default(),
// TODO: how to convert this to VecM?
read_write: vec![lk],
},
}),
};

// TODO: sign transaction
let tx = Transaction {
source_account: Default::default(),
// TODO: should the user supply the fee?
2opremio marked this conversation as resolved.
Show resolved Hide resolved
fee: 0,
// TODO: get sequence number from RPC server
seq_num: SequenceNumber(),
cond: Preconditions::None,
memo: Memo::None,
operations: Default::default(),
ext: TransactionExt::V0,
};
}

fn parse_private_key(strkey: String) -> Result<ed25519_dalek::Keypair, Error> {
let seed = stellar_strkey::StrkeyPrivateKeyEd25519::from_string(&strkey)
.map_err(|_| Error::CannotParsePrivateKey)?;
// TODO: improve error?
2opremio marked this conversation as resolved.
Show resolved Hide resolved
let secret_key = ed25519_dalek::SecretKey::from_bytes(&seed.0)
.map_err(|_| Error::CannotParsePrivateKey)?;
let public_key = (&secret_key).into();
Ok(ed25519_dalek::Keypair {
secret: secret_key,
public: public_key,
})
}
}