Skip to content

Commit

Permalink
Fix: exclude host only functions from client (#1359)
Browse files Browse the repository at this point in the history
* adding test

* fmt

* clippy

* exclude fns starting with __

* revert erroneous change

* Update cmd/crates/soroban-spec-typescript/src/lib.rs

readability

Co-authored-by: Willem Wyndham <[email protected]>

* remove extraneous iterator conversions

* Update cmd/crates/soroban-test/tests/fixtures/test-wasms/custom_account/Cargo.toml

---------

Co-authored-by: Willem Wyndham <[email protected]>
  • Loading branch information
BlaineHeffron and willemneal authored Jun 5, 2024
1 parent 7eec383 commit 610c6f9
Show file tree
Hide file tree
Showing 6 changed files with 204 additions and 0 deletions.
7 changes: 7 additions & 0 deletions Cargo.lock

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

2 changes: 2 additions & 0 deletions cmd/crates/soroban-spec-typescript/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -96,8 +96,10 @@ pub fn generate(spec: &[ScSpecEntry]) -> String {
cases: vec![],
});
}
// Filter out function entries with names that start with "__" and partition the results
let (fns, other): (Vec<_>, Vec<_>) = collected
.into_iter()
.filter(|entry| matches!(entry, Entry::Function { name, .. } if !name.starts_with("__")))
.partition(|entry| matches!(entry, Entry::Function { .. }));
let top = other.iter().map(entry_to_method_type).join("\n");
let bottom = generate_class(&fns, spec);
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
[package]
name = "test_custom_account"
version = "21.0.0-rc.1"
authors = ["Stellar Development Foundation <[email protected]>"]
license = "Apache-2.0"
edition = "2021"
publish = false
rust-version.workspace = true

[lib]
crate-type = ["cdylib"]
doctest = false

[dependencies]
soroban-sdk = { workspace = true }

[dev-dependencies]
soroban-sdk = { workspace = true, features = ["testutils"]}
Original file line number Diff line number Diff line change
@@ -0,0 +1,131 @@
#![no_std]
use soroban_sdk::{
auth::{Context, CustomAccountInterface},
contract, contracterror, contractimpl, contracttype,
crypto::Hash,
symbol_short, vec, Address, Bytes, BytesN, Env, Symbol, Vec,
};

#[contract]
pub struct Contract;

#[contracterror]
#[derive(Copy, Clone, Eq, PartialEq, Debug)]
pub enum Error {
NotFound = 1,
NotPermitted = 2,
ClientDataJsonChallengeIncorrect = 3,
Secp256r1PublicKeyParse = 4,
Secp256r1SignatureParse = 5,
Secp256r1VerifyFailed = 6,
JsonParseError = 7,
InvalidContext = 8,
AlreadyInited = 9,
NotInited = 10,
}

const SIGNERS: Symbol = symbol_short!("sigs");
const FACTORY: Symbol = symbol_short!("factory");
const SUDO_SIGNER: Symbol = symbol_short!("sudo_sig");

#[contractimpl]
impl Contract {
pub fn extend_ttl(env: &Env) {
let max_ttl = env.storage().max_ttl();
let contract_address = env.current_contract_address();

env.storage().instance().extend_ttl(max_ttl, max_ttl);
env.deployer()
.extend_ttl(contract_address.clone(), max_ttl, max_ttl);
env.deployer()
.extend_ttl_for_code(contract_address.clone(), max_ttl, max_ttl);
env.deployer()
.extend_ttl_for_contract_instance(contract_address.clone(), max_ttl, max_ttl);
}
pub fn init(env: Env, id: Bytes, pk: BytesN<65>, factory: Address) -> Result<(), Error> {
if env.storage().instance().has(&SUDO_SIGNER) {
return Err(Error::AlreadyInited);
}

let max_ttl = env.storage().max_ttl();

env.storage().persistent().set(&id, &pk);
env.storage().persistent().extend_ttl(&id, max_ttl, max_ttl);

env.storage().instance().set(&SUDO_SIGNER, &id);
env.storage().instance().set(&FACTORY, &factory);
env.storage().instance().set(&SIGNERS, &vec![&env, id]);

Self::extend_ttl(&env);

Ok(())
}
}

#[contracttype]
pub struct Signature {
pub id: BytesN<32>,
pub authenticator_data: Bytes,
pub client_data_json: Bytes,
pub signature: BytesN<64>,
}

#[derive(Debug)]
struct ClientDataJson<'a> {
challenge: &'a str,
}

#[contractimpl]
impl CustomAccountInterface for Contract {
type Error = Error;
type Signature = Signature;

#[allow(non_snake_case)]
fn __check_auth(
env: Env,
signature_payload: Hash<32>,
signature: Signature,
auth_contexts: Vec<Context>,
) -> Result<(), Error> {
// Only the sudo signer can `add_sig`, `rm_sig` and `resudo`
for context in auth_contexts.iter() {
match context {
Context::Contract(c) => {
if c.contract == env.current_contract_address()
&& (c.fn_name == Symbol::new(&env, "add_sig")
|| c.fn_name == Symbol::new(&env, "rm_sig")
|| c.fn_name == Symbol::new(&env, "resudo"))
&& signature.id
!= env
.storage()
.instance()
.get::<Symbol, BytesN<32>>(&SUDO_SIGNER)
.ok_or(Error::NotFound)?
{
return Err(Error::NotPermitted);
}
}
Context::CreateContractHostFn(_) => return Err(Error::InvalidContext),
};
}

// Dummy public key verification check
env.storage()
.persistent()
.get::<BytesN<32>, Bytes>(&signature.id)
.ok_or(Error::NotFound)?;
if signature_payload.to_bytes().len() != 32 {
return Err(Error::NotPermitted);
}

let client_data = ClientDataJson {
challenge: "dummy_challenge",
};

if client_data.challenge != "dummy_challenge" {
return Err(Error::ClientDataJsonChallengeIncorrect);
}

Ok(())
}
}
41 changes: 41 additions & 0 deletions cmd/crates/soroban-test/tests/it/integration/bindings.rs
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
use super::util::deploy_custom_account;
use super::util::deploy_swap;
use soroban_test::{TestEnv, LOCAL_NETWORK_PASSPHRASE};

Expand Down Expand Up @@ -32,3 +33,43 @@ async fn invoke_test_generate_typescript_bindings() {
"No files generated in the output directory"
);
}

#[tokio::test]
async fn invoke_test_bindings_context_failure() {
let sandbox = &TestEnv::new();
let contract_id = deploy_custom_account(sandbox).await;
let outdir = sandbox.dir().join(OUTPUT_DIR);
let cmd = sandbox.cmd_arr::<soroban_cli::commands::contract::bindings::typescript::Cmd>(&[
"--network-passphrase",
LOCAL_NETWORK_PASSPHRASE,
"--rpc-url",
&sandbox.rpc_url,
"--output-dir",
&outdir.display().to_string(),
"--overwrite",
"--contract-id",
&contract_id.to_string(),
]);

let result = sandbox.run_cmd_with(cmd, "test").await;

assert!(result.is_ok(), "Failed to generate TypeScript bindings");

assert!(outdir.exists(), "Output directory does not exist");

let files = std::fs::read_dir(&outdir).expect("Failed to read output directory");
assert!(
files.count() > 0,
"No files generated in the output directory"
);
// Read the src/index.ts file and check for `__check_auth:`
let index_ts_path = outdir.join("src/index.ts");

assert!(index_ts_path.exists(), "src/index.ts file does not exist");

let content = std::fs::read_to_string(&index_ts_path).expect("Failed to read index.ts file");
assert!(
!content.contains("__check_auth"),
"Test failed: `__check_auth` found in src/index.ts"
);
}
5 changes: 5 additions & 0 deletions cmd/crates/soroban-test/tests/it/integration/util.rs
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@ use std::fmt::Display;

pub const HELLO_WORLD: &Wasm = &Wasm::Custom("test-wasms", "test_hello_world");
pub const CUSTOM_TYPES: &Wasm = &Wasm::Custom("test-wasms", "test_custom_types");
pub const CUSTOM_ACCOUNT: &Wasm = &Wasm::Custom("test-wasms", "test_custom_account");
pub const SWAP: &Wasm = &Wasm::Custom("test-wasms", "test_swap");

pub async fn invoke_with_roundtrip<D>(e: &TestEnv, id: &str, func: &str, data: D)
Expand Down Expand Up @@ -33,6 +34,10 @@ pub async fn deploy_swap(sandbox: &TestEnv) -> String {
deploy_contract(sandbox, SWAP).await
}

pub async fn deploy_custom_account(sandbox: &TestEnv) -> String {
deploy_contract(sandbox, CUSTOM_ACCOUNT).await
}

pub async fn deploy_contract(sandbox: &TestEnv, wasm: &Wasm<'static>) -> String {
let cmd = sandbox.cmd_with_config::<_, commands::contract::deploy::wasm::Cmd>(&[
"--fee",
Expand Down

0 comments on commit 610c6f9

Please sign in to comment.