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 token contract #166

Merged
merged 30 commits into from
Oct 3, 2022
Merged
Show file tree
Hide file tree
Changes from 15 commits
Commits
Show all changes
30 commits
Select commit Hold shift + click to select a range
e2c6d7a
Extract soroban_rpc crate
Sep 30, 2022
0f93dfe
Add skeleton of asset command
Sep 28, 2022
00d412d
Create asset wrapper in sandbox
Sep 28, 2022
90d0d2a
asset -> token
Sep 28, 2022
5400cd7
Actually deploy contract wrapper in sandbox
Sep 28, 2022
0200b9a
Allow custom salt
Sep 28, 2022
6a24051
First pass at token wrap subcommand
Sep 29, 2022
4d64df2
appease clippy
Sep 29, 2022
8f6e327
Add TODO reminder
Sep 30, 2022
eec6b76
Add 'run_in_sandbox' to token subcommands
Sep 30, 2022
e14b00e
Add token create support for remote server
Sep 30, 2022
88770d4
Add token wrap support for remote server
Sep 30, 2022
cd8a9cf
Update src/token/create.rs
Sep 30, 2022
5bb75a8
Update src/token/wrap.rs
Sep 30, 2022
72767e1
WIP -- invoke init_token when creating
Sep 30, 2022
52462ae
Merge commit 'e2c6d7a' into refac
leighmcculloch Sep 30, 2022
f8aa8fc
remove soroban prefix on things
leighmcculloch Sep 30, 2022
1c4b21a
Client
leighmcculloch Sep 30, 2022
38bf961
files
leighmcculloch Sep 30, 2022
9810a4c
Merge branch 'refac' into 81/deploy-token-contract
leighmcculloch Sep 30, 2022
91ef92c
Merge branch 'main' into 81/deploy-token-contract
leighmcculloch Sep 30, 2022
cf1caca
bump sdk and env revisions
Sep 30, 2022
141ca9f
try re-ordering fields?
Sep 30, 2022
92b1e96
Figure out token create init footprint
Sep 30, 2022
2539351
rustfmt
Sep 30, 2022
256d9ef
use new ScVal::AccountId
Sep 30, 2022
c5fca4a
rustfmt
Sep 30, 2022
88aed3a
Add footprint for token wrap txn
Oct 3, 2022
94a3f90
need to increment the account sequence
Oct 3, 2022
ab0d7d5
Merge branch 'main' into 81/deploy-token-contract
Oct 3, 2022
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
27 changes: 27 additions & 0 deletions Cargo.lock

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

1 change: 1 addition & 0 deletions Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -34,6 +34,7 @@ csv = "1.1.6"
ed25519-dalek = "1.0.1"
jsonrpsee-http-client = "0.15.1"
jsonrpsee-core = "0.15.1"
regex = "1.6.0"

[patch.crates-io]
soroban-spec = { git = "https://github.com/stellar/rs-soroban-sdk", rev = "bc3ff723" }
Expand Down
143 changes: 17 additions & 126 deletions src/deploy.rs
Original file line number Diff line number Diff line change
@@ -1,33 +1,25 @@
use std::array::TryFromSliceError;
use std::num::ParseIntError;
use std::thread::sleep;
use std::time::{Duration, Instant};
use std::{fmt::Debug, fs, io};

use clap::Parser;
use ed25519_dalek::Signer;
use hex::FromHexError;
use jsonrpsee_core::{client::ClientT, rpc_params};
use jsonrpsee_http_client::{HeaderMap, HttpClientBuilder};
use rand::Rng;
use sha2::{Digest, Sha256};
use soroban_env_host::xdr::{
DecoratedSignature, Error as XdrError, Hash, HashIdPreimage, HashIdPreimageEd25519ContractId,
HostFunction, InvokeHostFunctionOp, LedgerFootprint, LedgerKey::ContractData,
LedgerKeyContractData, Memo, MuxedAccount, Operation, OperationBody, Preconditions, ScObject,
ScStatic::LedgerKeyContractCode, ScVal, SequenceNumber, Signature, SignatureHint, Transaction,
TransactionEnvelope, TransactionExt, TransactionSignaturePayload,
TransactionSignaturePayloadTaggedTransaction, TransactionV1Envelope, Uint256, VecM, WriteXdr,
TransactionEnvelope, TransactionExt, TransactionV1Envelope, Uint256, VecM, WriteXdr,
};
use soroban_env_host::HostError;
use stellar_strkey::StrkeyPrivateKeyEd25519;

use crate::snapshot::{self, get_default_ledger_info};
use crate::soroban_rpc::{Error as SorobanRpcError, SorobanRpc};
use crate::utils;

// TODO: put this in a common place
const VERSION: Option<&str> = option_env!("CARGO_PKG_VERSION");

#[derive(Parser, Debug)]
pub struct Cmd {
/// WASM file to deploy
Expand Down Expand Up @@ -71,30 +63,6 @@ pub struct Cmd {
network_passphrase: Option<String>,
}

// TODO: this should also be used by serve
#[derive(serde::Deserialize, serde::Serialize, Debug)]
struct GetAccountResponse {
id: String,
sequence: String,
// TODO: add balances
}

// TODO: this should also be used by serve
#[derive(serde::Deserialize, serde::Serialize, Debug)]
struct SendTransactionResponse {
id: String,
status: String,
// TODO: add results
}

// TODO: this should also be used by serve
#[derive(serde::Deserialize, serde::Serialize, Debug)]
struct TransactionStatusResponse {
id: String,
status: String,
// TODO: add results
}

#[derive(thiserror::Error, Debug)]
pub enum Error {
#[error(transparent)]
Expand Down Expand Up @@ -129,12 +97,8 @@ pub enum Error {
},
#[error("cannot parse private key")]
CannotParsePrivateKey,
#[error("trasaction submission failed")]
TransactionSubmissionFailed,
#[error("expected transaction status: {0}")]
UnexpectedTransactionStatus(String),
#[error("transaction submission timeout")]
TransactionSubmissionTimeout,
#[error(transparent)]
SorobanRpc(#[from] SorobanRpcError),
}

impl Cmd {
Expand Down Expand Up @@ -175,66 +139,29 @@ impl Cmd {
}

async fn run_against_rpc_server(&self, contract: Vec<u8>) -> Result<(), Error> {
// TODO: we should factor out the client creation when we start to use it in invoke and friends
let base_url = self.rpc_server_url.as_ref().unwrap().clone() + "/api/v1/jsonrpc";
let mut headers = HeaderMap::new();
headers.insert("X-Client-Name", "soroban-cli".parse().unwrap());
let version = VERSION.unwrap_or("devel");
headers.insert("X-Client-Version", version.parse().unwrap());
// TODO: We should consider migrating the server subcommand to jsonrpsee
let client = HttpClientBuilder::default()
.set_headers(headers)
.build(base_url)?;
let key = parse_private_key(self.private_strkey.as_ref().unwrap())?;
let client = SorobanRpc::new(self.rpc_server_url.as_ref().unwrap());
let key = utils::parse_private_key(self.private_strkey.as_ref().unwrap())
.map_err(|_| Error::CannotParsePrivateKey)?;

// Get the account sequence number
let public_strkey =
stellar_strkey::StrkeyPublicKeyEd25519(key.public.to_bytes()).to_string();
// TODO: use symbols for the method names (both here and in serve)
let account_details: GetAccountResponse = client
.request("getAccount", rpc_params![public_strkey])
.await?;
let account_details = client.get_account(&public_strkey).await?;
// 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, tx_hash) = build_create_contract_tx(
let tx = build_create_contract_tx(
contract,
sequence,
paulbellamy marked this conversation as resolved.
Show resolved Hide resolved
fee,
self.network_passphrase.as_ref().unwrap(),
&key,
)?;
client
.request("sendTransaction", rpc_params![tx.to_xdr_base64()?])
.await?;

// Poll the transaction status
let start = Instant::now();
loop {
let response: TransactionStatusResponse = client
.request("transactionStatus", rpc_params![hex::encode(tx_hash.0)])
.await?;
match response.status.as_str() {
"success" => {
println!("{}", response.status);
return Ok(());
}
"error" => {
// TODO: provide a more elaborate error
return Err(Error::TransactionSubmissionFailed);
}
"pending" => (),
_ => {
return Err(Error::UnexpectedTransactionStatus(response.status));
}
};
let duration = start.elapsed();
// TODO: parameterize the timeout instead of using a magic constant
if duration.as_secs() > 10 {
return Err(Error::TransactionSubmissionTimeout);
}
sleep(Duration::from_secs(1));
}
client.send_transaction(&tx).await?;

Ok(())
}
}

Expand All @@ -244,7 +171,7 @@ fn build_create_contract_tx(
fee: u32,
network_passphrase: &str,
key: &ed25519_dalek::Keypair,
) -> Result<(TransactionEnvelope, Hash), Error> {
) -> Result<TransactionEnvelope, Error> {
let salt = rand::thread_rng().gen::<[u8; 32]>();

let separator =
Expand Down Expand Up @@ -307,12 +234,7 @@ fn build_create_contract_tx(
};

// sign the transaction
let passphrase_hash = Sha256::digest(network_passphrase);
let signature_payload = TransactionSignaturePayload {
network_id: Hash(passphrase_hash.into()),
tagged_transaction: TransactionSignaturePayloadTaggedTransaction::Tx(tx.clone()),
};
let tx_hash = Sha256::digest(signature_payload.to_xdr()?);
let tx_hash = utils::transaction_hash(&tx, network_passphrase)?;
let tx_signature = key.sign(&tx_hash);

let decorated_signature = DecoratedSignature {
Expand All @@ -325,53 +247,22 @@ fn build_create_contract_tx(
signatures: vec![decorated_signature].try_into()?,
});

Ok((envelope, Hash(tx_hash.into())))
}

fn parse_private_key(strkey: &str) -> Result<ed25519_dalek::Keypair, Error> {
let seed =
StrkeyPrivateKeyEd25519::from_string(strkey).map_err(|_| Error::CannotParsePrivateKey)?;
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,
})
Ok(envelope)
}

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

#[test]
fn test_parse_private_key() {
let seed = "SBFGFF27Y64ZUGFAIG5AMJGQODZZKV2YQKAVUUN4HNE24XZXD2OEUVUP";
let keypair = parse_private_key(seed).unwrap();

let expected_public_key: [u8; 32] = [
0x31, 0x40, 0xf1, 0x40, 0x99, 0xa7, 0x4c, 0x90, 0xd4, 0x62, 0x48, 0xec, 0x8d, 0xef,
0xb3, 0x38, 0xc8, 0x2c, 0xe2, 0x42, 0x85, 0xc9, 0xf7, 0xb8, 0x95, 0xce, 0xdd, 0x6f,
0x96, 0x47, 0x82, 0x96,
];
assert_eq!(expected_public_key, keypair.public.to_bytes());

let expected_private_key: [u8; 32] = [
0x4a, 0x62, 0x97, 0x5f, 0xc7, 0xb9, 0x9a, 0x18, 0xa0, 0x41, 0xba, 0x6, 0x24, 0xd0,
0x70, 0xf3, 0x95, 0x57, 0x58, 0x82, 0x81, 0x5a, 0x51, 0xbc, 0x3b, 0x49, 0xae, 0x5f,
0x37, 0x1e, 0x9c, 0x4a,
];
assert_eq!(expected_private_key, keypair.secret.to_bytes());
}

#[test]
fn test_build_create_contract() {
let result = build_create_contract_tx(
b"foo".to_vec(),
300,
1,
"Public Global Stellar Network ; September 2015",
&parse_private_key("SBFGFF27Y64ZUGFAIG5AMJGQODZZKV2YQKAVUUN4HNE24XZXD2OEUVUP").unwrap(),
&utils::parse_private_key("SBFGFF27Y64ZUGFAIG5AMJGQODZZKV2YQKAVUUN4HNE24XZXD2OEUVUP")
.unwrap(),
);

assert!(result.is_ok());
Expand Down
7 changes: 7 additions & 0 deletions src/main.rs
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,9 @@ mod network;
mod read;
mod serve;
mod snapshot;
mod soroban_rpc;
mod strval;
mod token;
mod utils;
mod version;
mod xdr;
Expand Down Expand Up @@ -38,6 +40,8 @@ enum Cmd {
Read(read::Cmd),
/// Run a local webserver for web app development and testing
Serve(serve::Cmd),
/// Wrap, create, and manage token contracts
Token(token::Root),
/// Deploy a WASM file as a contract
Deploy(deploy::Cmd),
/// Generate code client bindings for a contract
Expand Down Expand Up @@ -65,6 +69,8 @@ enum CmdError {
#[error(transparent)]
Serve(#[from] serve::Error),
#[error(transparent)]
Token(#[from] token::Error),
#[error(transparent)]
Gen(#[from] gen::Error),
#[error(transparent)]
Deploy(#[from] deploy::Error),
Expand All @@ -81,6 +87,7 @@ async fn run(cmd: Cmd, matches: &mut clap::ArgMatches) -> Result<(), CmdError> {
}
Cmd::Read(read) => read.run()?,
Cmd::Serve(serve) => serve.run().await?,
Cmd::Token(token) => token.run().await?,
Cmd::Gen(gen) => gen.run()?,
Cmd::Deploy(deploy) => deploy.run().await?,
Cmd::Xdr(xdr) => xdr.run()?,
Expand Down
1 change: 1 addition & 0 deletions src/serve.rs
Original file line number Diff line number Diff line change
Expand Up @@ -250,6 +250,7 @@ fn parse_transaction(
return Err(Error::Xdr(XdrError::Invalid));
};

// TODO: Support creating contracts and token wrappers here as well.
if body.function != HostFunction::InvokeContract {
return Err(Error::Xdr(XdrError::Invalid));
};
Expand Down
Loading