diff --git a/Cargo.lock b/Cargo.lock index 0d717e1be5..7663984520 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -6179,6 +6179,13 @@ dependencies = [ "test-case-core", ] +[[package]] +name = "test_auth" +version = "25.2.0" +dependencies = [ + "soroban-sdk", +] + [[package]] name = "test_constructor" version = "26.0.0" diff --git a/FULL_HELP_DOCS.md b/FULL_HELP_DOCS.md index 8f23f3b0f2..f2d00e147c 100644 --- a/FULL_HELP_DOCS.md +++ b/FULL_HELP_DOCS.md @@ -162,6 +162,7 @@ Deploy builtin Soroban Asset Contract - `--hd-path ` — If using a seed phrase to sign, sets which hierarchical deterministic path to use, e.g. `m/44'/148'/{hd_path}`. Example: `--hd-path 1`. Default: `0` - `--sign-with-lab` — Sign with https://lab.stellar.org - `--sign-with-ledger` — Sign with a ledger wallet +- `--force` — Skip confirmation prompts when signing ###### **Transaction Options:** @@ -434,6 +435,7 @@ If no keys are specified the contract itself is extended. - `--hd-path ` — If using a seed phrase to sign, sets which hierarchical deterministic path to use, e.g. `m/44'/148'/{hd_path}`. Example: `--hd-path 1`. Default: `0` - `--sign-with-lab` — Sign with https://lab.stellar.org - `--sign-with-ledger` — Sign with a ledger wallet +- `--force` — Skip confirmation prompts when signing ###### **Transaction Options:** @@ -493,6 +495,7 @@ Deploy a wasm contract - `--hd-path ` — If using a seed phrase to sign, sets which hierarchical deterministic path to use, e.g. `m/44'/148'/{hd_path}`. Example: `--hd-path 1`. Default: `0` - `--sign-with-lab` — Sign with https://lab.stellar.org - `--sign-with-ledger` — Sign with a ledger wallet +- `--force` — Skip confirmation prompts when signing ###### **Transaction Options:** @@ -583,6 +586,7 @@ Deploy normal Wasm Contract - `--hd-path ` — If using a seed phrase to sign, sets which hierarchical deterministic path to use, e.g. `m/44'/148'/{hd_path}`. Example: `--hd-path 1`. Default: `0` - `--sign-with-lab` — Sign with https://lab.stellar.org - `--sign-with-ledger` — Sign with a ledger wallet +- `--force` — Skip confirmation prompts when signing ###### **Transaction Options:** @@ -828,6 +832,7 @@ Install a WASM file to the ledger without creating a contract instance - `--hd-path ` — If using a seed phrase to sign, sets which hierarchical deterministic path to use, e.g. `m/44'/148'/{hd_path}`. Example: `--hd-path 1`. Default: `0` - `--sign-with-lab` — Sign with https://lab.stellar.org - `--sign-with-ledger` — Sign with a ledger wallet +- `--force` — Skip confirmation prompts when signing ###### **Transaction Options:** @@ -880,6 +885,7 @@ Install a WASM file to the ledger without creating a contract instance - `--hd-path ` — If using a seed phrase to sign, sets which hierarchical deterministic path to use, e.g. `m/44'/148'/{hd_path}`. Example: `--hd-path 1`. Default: `0` - `--sign-with-lab` — Sign with https://lab.stellar.org - `--sign-with-ledger` — Sign with a ledger wallet +- `--force` — Skip confirmation prompts when signing ###### **Transaction Options:** @@ -936,6 +942,7 @@ stellar contract invoke ... -- --help - `--hd-path ` — If using a seed phrase to sign, sets which hierarchical deterministic path to use, e.g. `m/44'/148'/{hd_path}`. Example: `--hd-path 1`. Default: `0` - `--sign-with-lab` — Sign with https://lab.stellar.org - `--sign-with-ledger` — Sign with a ledger wallet +- `--force` — Skip confirmation prompts when signing ###### **Transaction Options:** @@ -1043,6 +1050,7 @@ If no keys are specificed the contract itself is restored. - `--hd-path ` — If using a seed phrase to sign, sets which hierarchical deterministic path to use, e.g. `m/44'/148'/{hd_path}`. Example: `--hd-path 1`. Default: `0` - `--sign-with-lab` — Sign with https://lab.stellar.org - `--sign-with-ledger` — Sign with a ledger wallet +- `--force` — Skip confirmation prompts when signing ###### **Transaction Options:** @@ -1906,6 +1914,7 @@ Transfer XLM balance to another account and remove source account - `--hd-path ` — If using a seed phrase to sign, sets which hierarchical deterministic path to use, e.g. `m/44'/148'/{hd_path}`. Example: `--hd-path 1`. Default: `0` - `--sign-with-lab` — Sign with https://lab.stellar.org - `--sign-with-ledger` — Sign with a ledger wallet +- `--force` — Skip confirmation prompts when signing ###### **Transaction Options:** @@ -1941,6 +1950,7 @@ Begin sponsoring future reserves for another account - `--hd-path ` — If using a seed phrase to sign, sets which hierarchical deterministic path to use, e.g. `m/44'/148'/{hd_path}`. Example: `--hd-path 1`. Default: `0` - `--sign-with-lab` — Sign with https://lab.stellar.org - `--sign-with-ledger` — Sign with a ledger wallet +- `--force` — Skip confirmation prompts when signing ###### **Transaction Options:** @@ -1976,6 +1986,7 @@ Bump sequence number to invalidate older transactions - `--hd-path ` — If using a seed phrase to sign, sets which hierarchical deterministic path to use, e.g. `m/44'/148'/{hd_path}`. Example: `--hd-path 1`. Default: `0` - `--sign-with-lab` — Sign with https://lab.stellar.org - `--sign-with-ledger` — Sign with a ledger wallet +- `--force` — Skip confirmation prompts when signing ###### **Transaction Options:** @@ -2014,6 +2025,7 @@ Create, update, or delete a trustline - `--hd-path ` — If using a seed phrase to sign, sets which hierarchical deterministic path to use, e.g. `m/44'/148'/{hd_path}`. Example: `--hd-path 1`. Default: `0` - `--sign-with-lab` — Sign with https://lab.stellar.org - `--sign-with-ledger` — Sign with a ledger wallet +- `--force` — Skip confirmation prompts when signing ###### **Transaction Options:** @@ -2049,6 +2061,7 @@ Claim a claimable balance by its balance ID - `--hd-path ` — If using a seed phrase to sign, sets which hierarchical deterministic path to use, e.g. `m/44'/148'/{hd_path}`. Example: `--hd-path 1`. Default: `0` - `--sign-with-lab` — Sign with https://lab.stellar.org - `--sign-with-ledger` — Sign with a ledger wallet +- `--force` — Skip confirmation prompts when signing ###### **Transaction Options:** @@ -2086,6 +2099,7 @@ Clawback an asset from an account - `--hd-path ` — If using a seed phrase to sign, sets which hierarchical deterministic path to use, e.g. `m/44'/148'/{hd_path}`. Example: `--hd-path 1`. Default: `0` - `--sign-with-lab` — Sign with https://lab.stellar.org - `--sign-with-ledger` — Sign with a ledger wallet +- `--force` — Skip confirmation prompts when signing ###### **Transaction Options:** @@ -2121,6 +2135,7 @@ Clawback a claimable balance by its balance ID - `--hd-path ` — If using a seed phrase to sign, sets which hierarchical deterministic path to use, e.g. `m/44'/148'/{hd_path}`. Example: `--hd-path 1`. Default: `0` - `--sign-with-lab` — Sign with https://lab.stellar.org - `--sign-with-ledger` — Sign with a ledger wallet +- `--force` — Skip confirmation prompts when signing ###### **Transaction Options:** @@ -2159,6 +2174,7 @@ Create and fund a new account - `--hd-path ` — If using a seed phrase to sign, sets which hierarchical deterministic path to use, e.g. `m/44'/148'/{hd_path}`. Example: `--hd-path 1`. Default: `0` - `--sign-with-lab` — Sign with https://lab.stellar.org - `--sign-with-ledger` — Sign with a ledger wallet +- `--force` — Skip confirmation prompts when signing ###### **Transaction Options:** @@ -2202,6 +2218,7 @@ Create a claimable balance that can be claimed by specified accounts - `--hd-path ` — If using a seed phrase to sign, sets which hierarchical deterministic path to use, e.g. `m/44'/148'/{hd_path}`. Example: `--hd-path 1`. Default: `0` - `--sign-with-lab` — Sign with https://lab.stellar.org - `--sign-with-ledger` — Sign with a ledger wallet +- `--force` — Skip confirmation prompts when signing ###### **Transaction Options:** @@ -2240,6 +2257,7 @@ Create a passive sell offer on the Stellar DEX - `--hd-path ` — If using a seed phrase to sign, sets which hierarchical deterministic path to use, e.g. `m/44'/148'/{hd_path}`. Example: `--hd-path 1`. Default: `0` - `--sign-with-lab` — Sign with https://lab.stellar.org - `--sign-with-ledger` — Sign with a ledger wallet +- `--force` — Skip confirmation prompts when signing ###### **Transaction Options:** @@ -2271,6 +2289,7 @@ End sponsoring future reserves - `--hd-path ` — If using a seed phrase to sign, sets which hierarchical deterministic path to use, e.g. `m/44'/148'/{hd_path}`. Example: `--hd-path 1`. Default: `0` - `--sign-with-lab` — Sign with https://lab.stellar.org - `--sign-with-ledger` — Sign with a ledger wallet +- `--force` — Skip confirmation prompts when signing ###### **Transaction Options:** @@ -2315,6 +2334,7 @@ Deposit assets into a liquidity pool - `--hd-path ` — If using a seed phrase to sign, sets which hierarchical deterministic path to use, e.g. `m/44'/148'/{hd_path}`. Example: `--hd-path 1`. Default: `0` - `--sign-with-lab` — Sign with https://lab.stellar.org - `--sign-with-ledger` — Sign with a ledger wallet +- `--force` — Skip confirmation prompts when signing ###### **Transaction Options:** @@ -2353,6 +2373,7 @@ Withdraw assets from a liquidity pool - `--hd-path ` — If using a seed phrase to sign, sets which hierarchical deterministic path to use, e.g. `m/44'/148'/{hd_path}`. Example: `--hd-path 1`. Default: `0` - `--sign-with-lab` — Sign with https://lab.stellar.org - `--sign-with-ledger` — Sign with a ledger wallet +- `--force` — Skip confirmation prompts when signing ###### **Transaction Options:** @@ -2394,6 +2415,7 @@ Create, update, or delete a buy offer - `--hd-path ` — If using a seed phrase to sign, sets which hierarchical deterministic path to use, e.g. `m/44'/148'/{hd_path}`. Example: `--hd-path 1`. Default: `0` - `--sign-with-lab` — Sign with https://lab.stellar.org - `--sign-with-ledger` — Sign with a ledger wallet +- `--force` — Skip confirmation prompts when signing ###### **Transaction Options:** @@ -2430,6 +2452,7 @@ Set, modify, or delete account data entries - `--hd-path ` — If using a seed phrase to sign, sets which hierarchical deterministic path to use, e.g. `m/44'/148'/{hd_path}`. Example: `--hd-path 1`. Default: `0` - `--sign-with-lab` — Sign with https://lab.stellar.org - `--sign-with-ledger` — Sign with a ledger wallet +- `--force` — Skip confirmation prompts when signing ###### **Transaction Options:** @@ -2471,6 +2494,7 @@ Create, update, or delete a sell offer - `--hd-path ` — If using a seed phrase to sign, sets which hierarchical deterministic path to use, e.g. `m/44'/148'/{hd_path}`. Example: `--hd-path 1`. Default: `0` - `--sign-with-lab` — Sign with https://lab.stellar.org - `--sign-with-ledger` — Sign with a ledger wallet +- `--force` — Skip confirmation prompts when signing ###### **Transaction Options:** @@ -2511,6 +2535,7 @@ Send a payment with a different asset using path finding, specifying the send am - `--hd-path ` — If using a seed phrase to sign, sets which hierarchical deterministic path to use, e.g. `m/44'/148'/{hd_path}`. Example: `--hd-path 1`. Default: `0` - `--sign-with-lab` — Sign with https://lab.stellar.org - `--sign-with-ledger` — Sign with a ledger wallet +- `--force` — Skip confirmation prompts when signing ###### **Transaction Options:** @@ -2551,6 +2576,7 @@ Send a payment with a different asset using path finding, specifying the receive - `--hd-path ` — If using a seed phrase to sign, sets which hierarchical deterministic path to use, e.g. `m/44'/148'/{hd_path}`. Example: `--hd-path 1`. Default: `0` - `--sign-with-lab` — Sign with https://lab.stellar.org - `--sign-with-ledger` — Sign with a ledger wallet +- `--force` — Skip confirmation prompts when signing ###### **Transaction Options:** @@ -2591,6 +2617,7 @@ Send asset to destination account - `--hd-path ` — If using a seed phrase to sign, sets which hierarchical deterministic path to use, e.g. `m/44'/148'/{hd_path}`. Example: `--hd-path 1`. Default: `0` - `--sign-with-lab` — Sign with https://lab.stellar.org - `--sign-with-ledger` — Sign with a ledger wallet +- `--force` — Skip confirmation prompts when signing ###### **Transaction Options:** @@ -2632,6 +2659,7 @@ Revoke sponsorship of a ledger entry or signer - `--hd-path ` — If using a seed phrase to sign, sets which hierarchical deterministic path to use, e.g. `m/44'/148'/{hd_path}`. Example: `--hd-path 1`. Default: `0` - `--sign-with-lab` — Sign with https://lab.stellar.org - `--sign-with-ledger` — Sign with a ledger wallet +- `--force` — Skip confirmation prompts when signing ###### **Transaction Options:** @@ -2682,6 +2710,7 @@ Set account options like flags, signers, and home domain - `--hd-path ` — If using a seed phrase to sign, sets which hierarchical deterministic path to use, e.g. `m/44'/148'/{hd_path}`. Example: `--hd-path 1`. Default: `0` - `--sign-with-lab` — Sign with https://lab.stellar.org - `--sign-with-ledger` — Sign with a ledger wallet +- `--force` — Skip confirmation prompts when signing ###### **Transaction Options:** @@ -2724,6 +2753,7 @@ Configure authorization and trustline flags for an asset - `--hd-path ` — If using a seed phrase to sign, sets which hierarchical deterministic path to use, e.g. `m/44'/148'/{hd_path}`. Example: `--hd-path 1`. Default: `0` - `--sign-with-lab` — Sign with https://lab.stellar.org - `--sign-with-ledger` — Sign with a ledger wallet +- `--force` — Skip confirmation prompts when signing ###### **Transaction Options:** @@ -2807,6 +2837,7 @@ Transfer XLM balance to another account and remove source account - `--hd-path ` — If using a seed phrase to sign, sets which hierarchical deterministic path to use, e.g. `m/44'/148'/{hd_path}`. Example: `--hd-path 1`. Default: `0` - `--sign-with-lab` — Sign with https://lab.stellar.org - `--sign-with-ledger` — Sign with a ledger wallet +- `--force` — Skip confirmation prompts when signing ###### **Transaction Options:** @@ -2847,6 +2878,7 @@ Begin sponsoring future reserves for another account - `--hd-path ` — If using a seed phrase to sign, sets which hierarchical deterministic path to use, e.g. `m/44'/148'/{hd_path}`. Example: `--hd-path 1`. Default: `0` - `--sign-with-lab` — Sign with https://lab.stellar.org - `--sign-with-ledger` — Sign with a ledger wallet +- `--force` — Skip confirmation prompts when signing ###### **Transaction Options:** @@ -2887,6 +2919,7 @@ Bump sequence number to invalidate older transactions - `--hd-path ` — If using a seed phrase to sign, sets which hierarchical deterministic path to use, e.g. `m/44'/148'/{hd_path}`. Example: `--hd-path 1`. Default: `0` - `--sign-with-lab` — Sign with https://lab.stellar.org - `--sign-with-ledger` — Sign with a ledger wallet +- `--force` — Skip confirmation prompts when signing ###### **Transaction Options:** @@ -2930,6 +2963,7 @@ Create, update, or delete a trustline - `--hd-path ` — If using a seed phrase to sign, sets which hierarchical deterministic path to use, e.g. `m/44'/148'/{hd_path}`. Example: `--hd-path 1`. Default: `0` - `--sign-with-lab` — Sign with https://lab.stellar.org - `--sign-with-ledger` — Sign with a ledger wallet +- `--force` — Skip confirmation prompts when signing ###### **Transaction Options:** @@ -2970,6 +3004,7 @@ Claim a claimable balance by its balance ID - `--hd-path ` — If using a seed phrase to sign, sets which hierarchical deterministic path to use, e.g. `m/44'/148'/{hd_path}`. Example: `--hd-path 1`. Default: `0` - `--sign-with-lab` — Sign with https://lab.stellar.org - `--sign-with-ledger` — Sign with a ledger wallet +- `--force` — Skip confirmation prompts when signing ###### **Transaction Options:** @@ -3012,6 +3047,7 @@ Clawback an asset from an account - `--hd-path ` — If using a seed phrase to sign, sets which hierarchical deterministic path to use, e.g. `m/44'/148'/{hd_path}`. Example: `--hd-path 1`. Default: `0` - `--sign-with-lab` — Sign with https://lab.stellar.org - `--sign-with-ledger` — Sign with a ledger wallet +- `--force` — Skip confirmation prompts when signing ###### **Transaction Options:** @@ -3052,6 +3088,7 @@ Clawback a claimable balance by its balance ID - `--hd-path ` — If using a seed phrase to sign, sets which hierarchical deterministic path to use, e.g. `m/44'/148'/{hd_path}`. Example: `--hd-path 1`. Default: `0` - `--sign-with-lab` — Sign with https://lab.stellar.org - `--sign-with-ledger` — Sign with a ledger wallet +- `--force` — Skip confirmation prompts when signing ###### **Transaction Options:** @@ -3095,6 +3132,7 @@ Create and fund a new account - `--hd-path ` — If using a seed phrase to sign, sets which hierarchical deterministic path to use, e.g. `m/44'/148'/{hd_path}`. Example: `--hd-path 1`. Default: `0` - `--sign-with-lab` — Sign with https://lab.stellar.org - `--sign-with-ledger` — Sign with a ledger wallet +- `--force` — Skip confirmation prompts when signing ###### **Transaction Options:** @@ -3143,6 +3181,7 @@ Create a claimable balance that can be claimed by specified accounts - `--hd-path ` — If using a seed phrase to sign, sets which hierarchical deterministic path to use, e.g. `m/44'/148'/{hd_path}`. Example: `--hd-path 1`. Default: `0` - `--sign-with-lab` — Sign with https://lab.stellar.org - `--sign-with-ledger` — Sign with a ledger wallet +- `--force` — Skip confirmation prompts when signing ###### **Transaction Options:** @@ -3186,6 +3225,7 @@ Create a passive sell offer on the Stellar DEX - `--hd-path ` — If using a seed phrase to sign, sets which hierarchical deterministic path to use, e.g. `m/44'/148'/{hd_path}`. Example: `--hd-path 1`. Default: `0` - `--sign-with-lab` — Sign with https://lab.stellar.org - `--sign-with-ledger` — Sign with a ledger wallet +- `--force` — Skip confirmation prompts when signing ###### **Transaction Options:** @@ -3225,6 +3265,7 @@ End sponsoring future reserves - `--hd-path ` — If using a seed phrase to sign, sets which hierarchical deterministic path to use, e.g. `m/44'/148'/{hd_path}`. Example: `--hd-path 1`. Default: `0` - `--sign-with-lab` — Sign with https://lab.stellar.org - `--sign-with-ledger` — Sign with a ledger wallet +- `--force` — Skip confirmation prompts when signing ###### **Transaction Options:** @@ -3274,6 +3315,7 @@ Deposit assets into a liquidity pool - `--hd-path ` — If using a seed phrase to sign, sets which hierarchical deterministic path to use, e.g. `m/44'/148'/{hd_path}`. Example: `--hd-path 1`. Default: `0` - `--sign-with-lab` — Sign with https://lab.stellar.org - `--sign-with-ledger` — Sign with a ledger wallet +- `--force` — Skip confirmation prompts when signing ###### **Transaction Options:** @@ -3317,6 +3359,7 @@ Withdraw assets from a liquidity pool - `--hd-path ` — If using a seed phrase to sign, sets which hierarchical deterministic path to use, e.g. `m/44'/148'/{hd_path}`. Example: `--hd-path 1`. Default: `0` - `--sign-with-lab` — Sign with https://lab.stellar.org - `--sign-with-ledger` — Sign with a ledger wallet +- `--force` — Skip confirmation prompts when signing ###### **Transaction Options:** @@ -3363,6 +3406,7 @@ Create, update, or delete a buy offer - `--hd-path ` — If using a seed phrase to sign, sets which hierarchical deterministic path to use, e.g. `m/44'/148'/{hd_path}`. Example: `--hd-path 1`. Default: `0` - `--sign-with-lab` — Sign with https://lab.stellar.org - `--sign-with-ledger` — Sign with a ledger wallet +- `--force` — Skip confirmation prompts when signing ###### **Transaction Options:** @@ -3404,6 +3448,7 @@ Set, modify, or delete account data entries - `--hd-path ` — If using a seed phrase to sign, sets which hierarchical deterministic path to use, e.g. `m/44'/148'/{hd_path}`. Example: `--hd-path 1`. Default: `0` - `--sign-with-lab` — Sign with https://lab.stellar.org - `--sign-with-ledger` — Sign with a ledger wallet +- `--force` — Skip confirmation prompts when signing ###### **Transaction Options:** @@ -3450,6 +3495,7 @@ Create, update, or delete a sell offer - `--hd-path ` — If using a seed phrase to sign, sets which hierarchical deterministic path to use, e.g. `m/44'/148'/{hd_path}`. Example: `--hd-path 1`. Default: `0` - `--sign-with-lab` — Sign with https://lab.stellar.org - `--sign-with-ledger` — Sign with a ledger wallet +- `--force` — Skip confirmation prompts when signing ###### **Transaction Options:** @@ -3495,6 +3541,7 @@ Send a payment with a different asset using path finding, specifying the receive - `--hd-path ` — If using a seed phrase to sign, sets which hierarchical deterministic path to use, e.g. `m/44'/148'/{hd_path}`. Example: `--hd-path 1`. Default: `0` - `--sign-with-lab` — Sign with https://lab.stellar.org - `--sign-with-ledger` — Sign with a ledger wallet +- `--force` — Skip confirmation prompts when signing ###### **Transaction Options:** @@ -3540,6 +3587,7 @@ Send a payment with a different asset using path finding, specifying the send am - `--hd-path ` — If using a seed phrase to sign, sets which hierarchical deterministic path to use, e.g. `m/44'/148'/{hd_path}`. Example: `--hd-path 1`. Default: `0` - `--sign-with-lab` — Sign with https://lab.stellar.org - `--sign-with-ledger` — Sign with a ledger wallet +- `--force` — Skip confirmation prompts when signing ###### **Transaction Options:** @@ -3585,6 +3633,7 @@ Send asset to destination account - `--hd-path ` — If using a seed phrase to sign, sets which hierarchical deterministic path to use, e.g. `m/44'/148'/{hd_path}`. Example: `--hd-path 1`. Default: `0` - `--sign-with-lab` — Sign with https://lab.stellar.org - `--sign-with-ledger` — Sign with a ledger wallet +- `--force` — Skip confirmation prompts when signing ###### **Transaction Options:** @@ -3631,6 +3680,7 @@ Revoke sponsorship of a ledger entry or signer - `--hd-path ` — If using a seed phrase to sign, sets which hierarchical deterministic path to use, e.g. `m/44'/148'/{hd_path}`. Example: `--hd-path 1`. Default: `0` - `--sign-with-lab` — Sign with https://lab.stellar.org - `--sign-with-ledger` — Sign with a ledger wallet +- `--force` — Skip confirmation prompts when signing ###### **Transaction Options:** @@ -3686,6 +3736,7 @@ Set account options like flags, signers, and home domain - `--hd-path ` — If using a seed phrase to sign, sets which hierarchical deterministic path to use, e.g. `m/44'/148'/{hd_path}`. Example: `--hd-path 1`. Default: `0` - `--sign-with-lab` — Sign with https://lab.stellar.org - `--sign-with-ledger` — Sign with a ledger wallet +- `--force` — Skip confirmation prompts when signing ###### **Transaction Options:** @@ -3733,6 +3784,7 @@ Configure authorization and trustline flags for an asset - `--hd-path ` — If using a seed phrase to sign, sets which hierarchical deterministic path to use, e.g. `m/44'/148'/{hd_path}`. Example: `--hd-path 1`. Default: `0` - `--sign-with-lab` — Sign with https://lab.stellar.org - `--sign-with-ledger` — Sign with a ledger wallet +- `--force` — Skip confirmation prompts when signing ###### **Transaction Options:** @@ -3789,6 +3841,7 @@ Sign a transaction envelope appending the signature to the envelope - `--hd-path ` — If using a seed phrase to sign, sets which hierarchical deterministic path to use, e.g. `m/44'/148'/{hd_path}`. Example: `--hd-path 1`. Default: `0` - `--sign-with-lab` — Sign with https://lab.stellar.org - `--sign-with-ledger` — Sign with a ledger wallet +- `--force` — Skip confirmation prompts when signing ## `stellar tx simulate` @@ -3821,6 +3874,7 @@ Simulate a transaction envelope from stdin - `--hd-path ` — If using a seed phrase to sign, sets which hierarchical deterministic path to use, e.g. `m/44'/148'/{hd_path}`. Example: `--hd-path 1`. Default: `0` - `--sign-with-lab` — Sign with https://lab.stellar.org - `--sign-with-ledger` — Sign with a ledger wallet +- `--force` — Skip confirmation prompts when signing ###### **Transaction Options:** diff --git a/cmd/crates/soroban-test/src/lib.rs b/cmd/crates/soroban-test/src/lib.rs index 893b4c27cb..3d50412448 100644 --- a/cmd/crates/soroban-test/src/lib.rs +++ b/cmd/crates/soroban-test/src/lib.rs @@ -278,6 +278,7 @@ impl TestEnv { hd_path: None, sign_with_lab: false, sign_with_ledger: false, + force: false, }, fee: None, inclusion_fee: None, diff --git a/cmd/crates/soroban-test/tests/fixtures/test-wasms/auth/Cargo.toml b/cmd/crates/soroban-test/tests/fixtures/test-wasms/auth/Cargo.toml new file mode 100644 index 0000000000..aabcbab5d0 --- /dev/null +++ b/cmd/crates/soroban-test/tests/fixtures/test-wasms/auth/Cargo.toml @@ -0,0 +1,17 @@ +[package] +name = "test_auth" +version = "25.2.0" +authors = ["Stellar Development Foundation "] +license = "Apache-2.0" +edition = "2021" +publish = false + +[lib] +crate-type = ["cdylib", "rlib"] +doctest = false + +[dependencies] +soroban-sdk = { workspace = true } + +[dev-dependencies] +soroban-sdk = { workspace = true, features = ["testutils"]} diff --git a/cmd/crates/soroban-test/tests/fixtures/test-wasms/auth/src/lib.rs b/cmd/crates/soroban-test/tests/fixtures/test-wasms/auth/src/lib.rs new file mode 100644 index 0000000000..4d4f46b64a --- /dev/null +++ b/cmd/crates/soroban-test/tests/fixtures/test-wasms/auth/src/lib.rs @@ -0,0 +1,386 @@ +#![no_std] +use soroban_sdk::{contract, contractimpl, vec, Address, Env, IntoVal, Symbol}; + +#[contract] +pub struct AuthContract; + +#[contractimpl] +impl AuthContract { + /// Constructor with auth + pub fn __constructor(_env: Env, addr: Address) { + addr.require_auth(); + } + + /// require_auth on addr + /// + /// Used by other functions to emulate different nested auth options + pub fn do_auth(_e: Env, addr: Address, val: Symbol) -> Symbol { + addr.require_auth(); + val + } + + /// require_auth on `addr` + /// -> `subcall` does require_auth on `addr` + /// + /// Used by other functions to emulate different nested auth options + pub fn auth_sub_auth(e: Env, addr: Address, val: Symbol, subcall: Address) -> Symbol { + addr.require_auth(); + + let fn_symbol = Symbol::new(&e, "do_auth"); + e.invoke_contract::( + &subcall, + &fn_symbol, + vec![&e, addr.into_val(&e), val.into_val(&e)], + ) + } + + /// require_auth on `addr` + /// -> `subcall` does require_auth on `addr` + /// -> `subcall2` does require_auth on `addr` + pub fn auth_sub_nested_auth( + e: Env, + addr: Address, + val: Symbol, + subcall: Address, + subcall2: Address, + ) -> Symbol { + addr.require_auth(); + + let fn_symbol = Symbol::new(&e, "auth_sub_auth"); + e.invoke_contract::( + &subcall, + &fn_symbol, + vec![ + &e, + addr.into_val(&e), + val.into_val(&e), + subcall2.into_val(&e), + ], + ) + } + + /// require_auth_for_args(val) on `addr` + /// -> `subcall` does require_auth on `addr` + pub fn partial_auth_sub_auth(e: Env, addr: Address, val: Symbol, subcall: Address) -> Symbol { + addr.require_auth_for_args(vec![&e, addr.into_val(&e), val.into_val(&e)]); + + let fn_symbol = Symbol::new(&e, "do_auth"); + e.invoke_contract::( + &subcall, + &fn_symbol, + vec![&e, addr.into_val(&e), val.into_val(&e)], + ) + } + + /// require_auth_for_args(1i128, 2i128) on `addr` + /// -> `subcall` does require_auth on `addr` + pub fn diff_auth_sub_auth(e: Env, addr: Address, val: Symbol, subcall: Address) -> Symbol { + addr.require_auth_for_args(vec![&e, 1i128.into_val(&e), 2i128.into_val(&e)]); + + let fn_symbol = Symbol::new(&e, "do_auth"); + e.invoke_contract::( + &subcall, + &fn_symbol, + vec![&e, addr.into_val(&e), val.into_val(&e)], + ) + } + + /// no auth + /// -> `subcall` does require_auth on `addr` + pub fn no_auth_sub_auth(e: Env, addr: Address, val: Symbol, subcall: Address) -> Symbol { + let fn_symbol = Symbol::new(&e, "do_auth"); + e.invoke_contract::( + &subcall, + &fn_symbol, + vec![&e, addr.into_val(&e), val.into_val(&e)], + ) + } + + /// no auth + /// -> `subcall` does require_auth on `addr` + /// -> `subcall2` does require_auth on `addr` + pub fn no_auth_sub_nested_auth( + e: Env, + addr: Address, + val: Symbol, + subcall: Address, + subcall2: Address, + ) -> Symbol { + let fn_symbol = Symbol::new(&e, "auth_sub_auth"); + e.invoke_contract::( + &subcall, + &fn_symbol, + vec![ + &e, + addr.into_val(&e), + val.into_val(&e), + subcall2.into_val(&e), + ], + ) + } +} + +#[cfg(test)] +mod test { + extern crate std; + + use soroban_sdk::{ + testutils::{Address as _, AuthorizedFunction, AuthorizedInvocation}, + Address, Env, IntoVal, Symbol, + }; + + use crate::{AuthContract, AuthContractClient}; + + #[test] + fn test_do_auth_creates_expected_auth() { + let env = Env::default(); + env.mock_all_auths(); + + let user = Address::generate(&env); + let val = Symbol::new(&env, "test_auth"); + + let contract_id = env.register(AuthContract, (user.clone(),)); + let client = AuthContractClient::new(&env, &contract_id); + + let res = client.do_auth(&user, &val); + assert_eq!( + env.auths(), + std::vec![( + user.clone(), + AuthorizedInvocation { + function: AuthorizedFunction::Contract(( + contract_id.clone(), + Symbol::new(&env, "do_auth"), + (&user, &val).into_val(&env), + )), + sub_invocations: std::vec![], + }, + )] + ); + assert_eq!(res, val); + } + + #[test] + fn test_auth_sub_auth_creates_expected_auth() { + let env = Env::default(); + env.mock_all_auths(); + + let user = Address::generate(&env); + let val = Symbol::new(&env, "test_auth"); + + let contract_id_1 = env.register(AuthContract, (user.clone(),)); + let client_1 = AuthContractClient::new(&env, &contract_id_1); + let contract_id_2 = env.register(AuthContract, (user.clone(),)); + + let res = client_1.auth_sub_auth(&user, &val, &contract_id_2); + assert_eq!( + env.auths(), + std::vec![( + user.clone(), + AuthorizedInvocation { + function: AuthorizedFunction::Contract(( + contract_id_1.clone(), + Symbol::new(&env, "auth_sub_auth"), + (&user, &val, &contract_id_2).into_val(&env), + )), + sub_invocations: std::vec![AuthorizedInvocation { + function: AuthorizedFunction::Contract(( + contract_id_2.clone(), + Symbol::new(&env, "do_auth"), + (&user, &val).into_val(&env), + )), + sub_invocations: std::vec![], + }], + }, + )] + ); + assert_eq!(res, val); + } + + #[test] + fn test_auth_sub_nested_auth_creates_expected_auth() { + let env = Env::default(); + env.mock_all_auths(); + + let user = Address::generate(&env); + let val = Symbol::new(&env, "test_auth"); + + let contract_id_1 = env.register(AuthContract, (user.clone(),)); + let client_1 = AuthContractClient::new(&env, &contract_id_1); + let contract_id_2 = env.register(AuthContract, (user.clone(),)); + let contract_id_3 = env.register(AuthContract, (user.clone(),)); + + let res = client_1.auth_sub_nested_auth(&user, &val, &contract_id_2, &contract_id_3); + assert_eq!( + env.auths(), + std::vec![( + user.clone(), + AuthorizedInvocation { + function: AuthorizedFunction::Contract(( + contract_id_1.clone(), + Symbol::new(&env, "auth_sub_nested_auth"), + (&user, &val, &contract_id_2, &contract_id_3).into_val(&env), + )), + sub_invocations: std::vec![AuthorizedInvocation { + function: AuthorizedFunction::Contract(( + contract_id_2.clone(), + Symbol::new(&env, "auth_sub_auth"), + (&user, &val, &contract_id_3).into_val(&env), + )), + sub_invocations: std::vec![AuthorizedInvocation { + function: AuthorizedFunction::Contract(( + contract_id_3.clone(), + Symbol::new(&env, "do_auth"), + (&user, &val).into_val(&env), + )), + sub_invocations: std::vec![], + }], + }], + }, + )] + ); + assert_eq!(res, val); + } + + #[test] + fn test_partial_auth_sub_auth_creates_expected_auth() { + let env = Env::default(); + env.mock_all_auths(); + + let user = Address::generate(&env); + let val = Symbol::new(&env, "test_auth"); + + let contract_id_1 = env.register(AuthContract, (user.clone(),)); + let client_1 = AuthContractClient::new(&env, &contract_id_1); + let contract_id_2 = env.register(AuthContract, (user.clone(),)); + + let res = client_1.partial_auth_sub_auth(&user, &val, &contract_id_2); + assert_eq!( + env.auths(), + std::vec![( + user.clone(), + AuthorizedInvocation { + function: AuthorizedFunction::Contract(( + contract_id_1.clone(), + Symbol::new(&env, "partial_auth_sub_auth"), + (&user, &val).into_val(&env), + )), + sub_invocations: std::vec![AuthorizedInvocation { + function: AuthorizedFunction::Contract(( + contract_id_2.clone(), + Symbol::new(&env, "do_auth"), + (&user, &val).into_val(&env), + )), + sub_invocations: std::vec![], + }], + }, + )] + ); + assert_eq!(res, val); + } + + #[test] + fn test_diff_auth_sub_auth_creates_expected_auth() { + let env = Env::default(); + env.mock_all_auths(); + + let user = Address::generate(&env); + let val = Symbol::new(&env, "test_auth"); + + let contract_id_1 = env.register(AuthContract, (user.clone(),)); + let client_1 = AuthContractClient::new(&env, &contract_id_1); + let contract_id_2 = env.register(AuthContract, (user.clone(),)); + + let res = client_1.diff_auth_sub_auth(&user, &val, &contract_id_2); + assert_eq!( + env.auths(), + std::vec![( + user.clone(), + AuthorizedInvocation { + function: AuthorizedFunction::Contract(( + contract_id_1.clone(), + Symbol::new(&env, "diff_auth_sub_auth"), + (&1i128, &2i128).into_val(&env), + )), + sub_invocations: std::vec![AuthorizedInvocation { + function: AuthorizedFunction::Contract(( + contract_id_2.clone(), + Symbol::new(&env, "do_auth"), + (&user, &val).into_val(&env), + )), + sub_invocations: std::vec![], + }], + }, + )] + ); + assert_eq!(res, val); + } + + #[test] + fn test_no_auth_sub_auth_creates_expected_auth() { + let env = Env::default(); + env.mock_all_auths_allowing_non_root_auth(); + + let user = Address::generate(&env); + let val = Symbol::new(&env, "test_auth"); + + let contract_id_1 = env.register(AuthContract, (user.clone(),)); + let client_1 = AuthContractClient::new(&env, &contract_id_1); + let contract_id_2 = env.register(AuthContract, (user.clone(),)); + + let res = client_1.no_auth_sub_auth(&user, &val, &contract_id_2); + assert_eq!( + env.auths(), + std::vec![( + user.clone(), + AuthorizedInvocation { + function: AuthorizedFunction::Contract(( + contract_id_2.clone(), + Symbol::new(&env, "do_auth"), + (&user, &val).into_val(&env), + )), + sub_invocations: std::vec![], + }, + )] + ); + assert_eq!(res, val); + } + + #[test] + fn test_no_auth_sub_nested_auth_creates_expected_auth() { + let env = Env::default(); + env.mock_all_auths_allowing_non_root_auth(); + + let user = Address::generate(&env); + let val = Symbol::new(&env, "test_auth"); + + let contract_id_1 = env.register(AuthContract, (user.clone(),)); + let client_1 = AuthContractClient::new(&env, &contract_id_1); + let contract_id_2 = env.register(AuthContract, (user.clone(),)); + let contract_id_3 = env.register(AuthContract, (user.clone(),)); + + let res = client_1.no_auth_sub_nested_auth(&user, &val, &contract_id_2, &contract_id_3); + assert_eq!( + env.auths(), + std::vec![( + user.clone(), + AuthorizedInvocation { + function: AuthorizedFunction::Contract(( + contract_id_2.clone(), + Symbol::new(&env, "auth_sub_auth"), + (&user, &val, &contract_id_3).into_val(&env), + )), + sub_invocations: std::vec![AuthorizedInvocation { + function: AuthorizedFunction::Contract(( + contract_id_3.clone(), + Symbol::new(&env, "do_auth"), + (&user, &val).into_val(&env), + )), + sub_invocations: std::vec![], + }], + }, + )] + ); + assert_eq!(res, val); + } +} diff --git a/cmd/crates/soroban-test/tests/it/integration.rs b/cmd/crates/soroban-test/tests/it/integration.rs index 7f749a7674..9fa5a46ad9 100644 --- a/cmd/crates/soroban-test/tests/it/integration.rs +++ b/cmd/crates/soroban-test/tests/it/integration.rs @@ -1,3 +1,4 @@ +mod auth; mod auto_build; mod bindings; mod constructor; diff --git a/cmd/crates/soroban-test/tests/it/integration/auth.rs b/cmd/crates/soroban-test/tests/it/integration/auth.rs new file mode 100644 index 0000000000..cd32b690c9 --- /dev/null +++ b/cmd/crates/soroban-test/tests/it/integration/auth.rs @@ -0,0 +1,188 @@ +use assert_cmd::Command; +use soroban_test::{AssertExt, TestEnv}; + +use super::util::{extend_contract, new_account, AUTH}; + +fn constructor_cmd(sandbox: &TestEnv, addr: &str) -> Command { + let mut cmd = sandbox.new_assert_cmd("contract"); + cmd.arg("deploy") + .arg("--source=test") + .arg("--wasm") + .arg(AUTH.path()); + cmd.arg("--").arg("--addr").arg(addr); + cmd +} + +/// Helper to deploy two instances of the auth contract and extend them. +/// Returns (contract_id_1, contract_id_2). +async fn deploy_auth_contracts(sandbox: &TestEnv) -> (String, String) { + let id1 = constructor_cmd(sandbox, "test") + .assert() + .success() + .stdout_as_str(); + extend_contract(sandbox, &id1).await; + + let id2 = constructor_cmd(sandbox, "test") + .assert() + .success() + .stdout_as_str(); + extend_contract(sandbox, &id2).await; + + (id1, id2) +} + +#[tokio::test] +async fn standard_auth_with_separate_signer() { + let sandbox = &TestEnv::new(); + new_account(sandbox, "signer"); + + let (id, _) = deploy_auth_contracts(sandbox).await; + + sandbox + .new_assert_cmd("contract") + .arg("invoke") + .arg("--source=test") + .arg("--id") + .arg(&id) + .arg("--") + .arg("do-auth") + .arg("--addr=signer") + .arg("--val=hello") + .assert() + .success() + .stdout("\"hello\"\n"); +} + +#[tokio::test] +async fn root_auth_with_authorized_subcall() { + let sandbox = &TestEnv::new(); + new_account(sandbox, "signer"); + + let (id1, id2) = deploy_auth_contracts(sandbox).await; + + sandbox + .new_assert_cmd("contract") + .arg("invoke") + .arg("--source=test") + .arg("--id") + .arg(&id1) + .arg("--") + .arg("auth-sub-auth") + .arg("--addr=signer") + .arg("--val=hello") + .arg(&format!("--subcall={id2}")) + .assert() + .success() + .stdout("\"hello\"\n"); +} + +#[tokio::test] +async fn non_root_auth_with_authorized_subcall() { + let sandbox = &TestEnv::new(); + new_account(sandbox, "signer"); + + let (id1, id2) = deploy_auth_contracts(sandbox).await; + + // with non-source signer - expect failure + sandbox + .new_assert_cmd("contract") + .arg("invoke") + .arg("--source=test") + .arg("--id") + .arg(&id1) + .arg("--") + .arg("no-auth-sub-auth") + .arg("--addr=signer") + .arg("--val=hello") + .arg(&format!("--subcall={id2}")) + .assert() + .failure() + .stderr(predicates::str::contains("Auth, InvalidAction")); + + // with source signer - expect failure + // TODO: this should pass once CLI supports non-root auth + sandbox + .new_assert_cmd("contract") + .arg("invoke") + .arg("--source=test") + .arg("--id") + .arg(&id1) + .arg("--") + .arg("no-auth-sub-auth") + .arg("--addr=test") + .arg("--val=hello") + .arg(&format!("--subcall={id2}")) + .assert() + .failure() + .stderr(predicates::str::contains("Auth, InvalidAction")); +} + +#[tokio::test] +async fn partial_auth_with_authorized_subcall() { + let sandbox = &TestEnv::new(); + new_account(sandbox, "signer"); + + let (id1, id2) = deploy_auth_contracts(sandbox).await; + + // with non-source signer - expect failure + sandbox + .new_assert_cmd("contract") + .arg("invoke") + .arg("--source=test") + .arg("--id") + .arg(&id1) + .arg("--") + .arg("partial_auth_sub_auth") + .arg("--addr=signer") + .arg("--val=hello") + .arg(&format!("--subcall={id2}")) + .assert() + .failure() + .stderr(predicates::str::contains( + "An authorization entry requires confirmation", + )); + + // with non-source signer and --force - expect success + sandbox + .new_assert_cmd("contract") + .arg("invoke") + .arg("--source=test") + .arg("--id") + .arg(&id1) + .arg("--force") + .arg("--") + .arg("partial_auth_sub_auth") + .arg("--addr=signer") + .arg("--val=hello") + .arg(&format!("--subcall={id2}")) + .assert() + .success() + .stdout("\"hello\"\n"); + + // with source signer - expect success + sandbox + .new_assert_cmd("contract") + .arg("invoke") + .arg("--source=test") + .arg("--id") + .arg(&id1) + .arg("--") + .arg("partial_auth_sub_auth") + .arg("--addr=test") + .arg("--val=hello") + .arg(&format!("--subcall={id2}")) + .assert() + .success() + .stdout("\"hello\"\n"); +} + +#[tokio::test] +async fn constructor_auth_with_non_source_signer() { + let sandbox = &TestEnv::new(); + new_account(sandbox, "signer"); + + constructor_cmd(sandbox, "signer") + .assert() + .failure() + .stderr(predicates::str::contains("Auth, InvalidAction")); +} diff --git a/cmd/crates/soroban-test/tests/it/integration/util.rs b/cmd/crates/soroban-test/tests/it/integration/util.rs index 21eb71874d..826c985e47 100644 --- a/cmd/crates/soroban-test/tests/it/integration/util.rs +++ b/cmd/crates/soroban-test/tests/it/integration/util.rs @@ -5,6 +5,7 @@ use soroban_cli::{ use soroban_test::{AssertExt, TestEnv, Wasm}; use std::fmt::Display; +pub const AUTH: &Wasm = &Wasm::Custom("test-wasms", "test_auth"); pub const HELLO_WORLD: &Wasm = &Wasm::Custom("test-wasms", "test_hello_world"); pub const CONSTRUCTOR: &Wasm = &Wasm::Custom("test-wasms", "test_constructor"); pub const CUSTOM_TYPES: &Wasm = &Wasm::Custom("test-wasms", "test_custom_types"); diff --git a/cmd/soroban-cli/src/commands/contract/extend.rs b/cmd/soroban-cli/src/commands/contract/extend.rs index 17373f96ca..3d0cff87cd 100644 --- a/cmd/soroban-cli/src/commands/contract/extend.rs +++ b/cmd/soroban-cli/src/commands/contract/extend.rs @@ -197,6 +197,9 @@ impl Cmd { tracing::trace!(?network); let keys = self.key.parse_keys(&config.locator, &network)?; let client = network.rpc_client()?; + client + .verify_network_passphrase(Some(&network.network_passphrase)) + .await?; let source_account = config.source_account()?; let extend_to = self.ledgers_to_extend(&client).await?; diff --git a/cmd/soroban-cli/src/commands/contract/restore.rs b/cmd/soroban-cli/src/commands/contract/restore.rs index 7f75462a68..2b71b56ba1 100644 --- a/cmd/soroban-cli/src/commands/contract/restore.rs +++ b/cmd/soroban-cli/src/commands/contract/restore.rs @@ -166,6 +166,9 @@ impl Cmd { tracing::trace!(?network); let entry_keys = self.key.parse_keys(&config.locator, &network)?; let client = network.rpc_client()?; + client + .verify_network_passphrase(Some(&network.network_passphrase)) + .await?; let source_account = config.source_account()?; // Get the account sequence number diff --git a/cmd/soroban-cli/src/config/mod.rs b/cmd/soroban-cli/src/config/mod.rs index b980b447bb..df7a176759 100644 --- a/cmd/soroban-cli/src/config/mod.rs +++ b/cmd/soroban-cli/src/config/mod.rs @@ -154,10 +154,14 @@ impl Args { let client = network.rpc_client()?; let latest_ledger = client.get_latest_ledger().await?.sequence; let seq_num = latest_ledger + 60; // ~ 5 min - Ok( - signer::sign_soroban_authorizations(tx, signers, seq_num, &network.network_passphrase) - .await?, + Ok(signer::sign_soroban_authorizations( + tx, + signers, + seq_num, + &network.network_passphrase, + self.sign_with.force, ) + .await?) } pub fn get_network(&self) -> Result { diff --git a/cmd/soroban-cli/src/config/sign_with.rs b/cmd/soroban-cli/src/config/sign_with.rs index b4a43d0626..85ae8c79d0 100644 --- a/cmd/soroban-cli/src/config/sign_with.rs +++ b/cmd/soroban-cli/src/config/sign_with.rs @@ -69,6 +69,10 @@ pub struct Args { help_heading = HEADING_SIGNING )] pub sign_with_ledger: bool, + + /// Skip confirmation prompts when signing + #[arg(long, help_heading = HEADING_SIGNING)] + pub force: bool, } impl Args { diff --git a/cmd/soroban-cli/src/log/auth.rs b/cmd/soroban-cli/src/log/auth.rs index 4a6b4bea84..315d9a5d8b 100644 --- a/cmd/soroban-cli/src/log/auth.rs +++ b/cmd/soroban-cli/src/log/auth.rs @@ -1,7 +1,87 @@ -use crate::xdr::{SorobanAuthorizationEntry, VecM}; +use std::fmt::Write; -pub fn auth(auth: &[VecM]) { - if !auth.is_empty() { - tracing::debug!("{auth:#?}"); +use crate::xdr::{ + AccountId, InvokeContractArgs, PublicKey, ScAddress, SorobanAuthorizationEntry, + SorobanAuthorizedFunction, SorobanAuthorizedInvocation, SorobanCredentials, Uint256, +}; + +/// Format a single auth entry for display. +pub fn format_auth_entry(entry: &SorobanAuthorizationEntry) -> String { + let mut result = String::from(" Auth Entry:\n"); + + match &entry.credentials { + SorobanCredentials::Address(creds) => { + let _ = writeln!(result, " Signer: {}", format_address(&creds.address)); + } + SorobanCredentials::SourceAccount => { + result.push_str(" Signer: \n"); + } + } + + format_invocation(&entry.root_invocation, 2, "Invocation:", &mut result); + + result +} + +/// Recursively format a `SorobanAuthorizedInvocation` tree. `label` is the +/// header line printed for this node — `"Invocation:"` for the root and +/// `"Sub-invocation #N:"` for each child. +fn format_invocation( + invocation: &SorobanAuthorizedInvocation, + indent: usize, + label: &str, + result: &mut String, +) { + let prefix = " ".repeat(indent); + let _ = writeln!(result, "{prefix}{label}"); + + match &invocation.function { + SorobanAuthorizedFunction::ContractFn(InvokeContractArgs { + contract_address, + function_name, + args, + }) => { + let fn_name = std::str::from_utf8(function_name.as_ref()).unwrap_or(""); + let _ = writeln!( + result, + "{prefix} Contract: {}", + format_address(contract_address) + ); + let _ = writeln!(result, "{prefix} Fn: {fn_name}"); + if !args.is_empty() { + let _ = writeln!(result, "{prefix} Args:"); + for arg in args.iter() { + let _ = writeln!( + result, + "{prefix} {}", + soroban_spec_tools::to_string(arg) + .unwrap_or(String::from("")) + ); + } + } + } + SorobanAuthorizedFunction::CreateContractHostFn(_) + | SorobanAuthorizedFunction::CreateContractV2HostFn(_) => { + let _ = writeln!(result, "{prefix} CreateContract"); + } + } + + for (i, sub) in invocation.sub_invocations.iter().enumerate() { + let sub_label = format!("Sub-invocation #{i}:"); + format_invocation(sub, indent + 1, &sub_label, result); + } +} + +/// Format an ScAddress as a strkey string for display. +fn format_address(address: &ScAddress) -> String { + match address { + ScAddress::Account(AccountId(PublicKey::PublicKeyTypeEd25519(Uint256(bytes)))) => { + stellar_strkey::Strkey::PublicKeyEd25519(stellar_strkey::ed25519::PublicKey(*bytes)) + .to_string() + } + ScAddress::Contract(stellar_xdr::curr::ContractId(stellar_xdr::curr::Hash(bytes))) => { + stellar_strkey::Strkey::Contract(stellar_strkey::Contract(*bytes)).to_string() + } + _ => format!("{address:?}"), } } diff --git a/cmd/soroban-cli/src/signer/mod.rs b/cmd/soroban-cli/src/signer/mod.rs index 87b93bab2d..de958b048b 100644 --- a/cmd/soroban-cli/src/signer/mod.rs +++ b/cmd/soroban-cli/src/signer/mod.rs @@ -1,20 +1,23 @@ use crate::{ + log::format_auth_entry, signer::ledger::LedgerEntry, utils::fee_bump_transaction_hash, xdr::{ self, AccountId, DecoratedSignature, FeeBumpTransactionEnvelope, Hash, HashIdPreimage, - HashIdPreimageSorobanAuthorization, Limits, Operation, OperationBody, PublicKey, ScAddress, - ScMap, ScSymbol, ScVal, Signature, SignatureHint, SorobanAddressCredentials, - SorobanAuthorizationEntry, SorobanCredentials, Transaction, TransactionEnvelope, - TransactionV1Envelope, Uint256, VecM, WriteXdr, + HashIdPreimageSorobanAuthorization, Limits, MuxedAccount, Operation, OperationBody, + PublicKey, ScAddress, ScMap, ScSymbol, ScVal, Signature, SignatureHint, + SorobanAddressCredentials, SorobanAuthorizationEntry, SorobanCredentials, Transaction, + TransactionEnvelope, TransactionV1Envelope, Uint256, VecM, WriteXdr, }, }; use ed25519_dalek::{ed25519::signature::Signer as _, Signature as Ed25519Signature}; use sha2::{Digest, Sha256}; use crate::{config::network::Network, print::Print, utils::transaction_hash}; +use std::io::{self, BufRead, IsTerminal}; pub mod ledger; +pub mod validation; #[cfg(feature = "additional-libs")] mod keyring; @@ -30,8 +33,15 @@ pub enum Error { MissingSignerForAddress { address: String }, #[error(transparent)] TryFromSlice(#[from] std::array::TryFromSliceError), - #[error("User cancelled signing, perhaps need to add -y")] - UserCancelledSigning, + #[error("Invalid Soroban authorization entry - {reason}:\n{auth_entry_str}")] + InvalidAuthEntry { + reason: String, + auth_entry_str: String, + }, + #[error("An authorization entry requires confirmation, but stdin is not interactive. Rerun with --force to sign anyway.")] + AuthEntryRequiresConfirmation, + #[error("signing cancelled by user")] + AuthRejected, #[error(transparent)] Xdr(#[from] xdr::Error), #[error("Transaction envelope type not supported")] @@ -62,6 +72,7 @@ pub async fn sign_soroban_authorizations( signers: &[Signer], signature_expiration_ledger: u32, network_passphrase: &str, + skip_approval: bool, ) -> Result, Error> { // Check if we have exactly one operation and it's InvokeHostFunction let [op @ Operation { @@ -73,25 +84,41 @@ pub async fn sign_soroban_authorizations( }; let network_id = Hash(Sha256::digest(network_passphrase.as_bytes()).into()); + let source_bytes = muxed_account_bytes(&raw.source_account); let mut auths_modified = false; let mut signed_auths = Vec::with_capacity(body.auth.len()); for raw_auth in body.auth.as_slice() { - let mut auth = raw_auth.clone(); let SorobanAuthorizationEntry { - credentials: SorobanCredentials::Address(ref mut credentials), + credentials: SorobanCredentials::Address(credentials), .. - } = auth + } = raw_auth else { // Doesn't need special signing - signed_auths.push(auth); + signed_auths.push(raw_auth.clone()); continue; }; - let SorobanAddressCredentials { ref address, .. } = credentials; + let SorobanAddressCredentials { address, .. } = credentials; + + // Before we attempt to sign, validate the auth entry is strict + match validation::classify_auth_invocation(&body.host_function, &raw_auth.root_invocation) { + validation::AuthStyle::Strict => {} + validation::AuthStyle::NonStrict => { + if !skip_approval { + confirm_non_strict_authorization(raw_auth)?; + } + } + validation::AuthStyle::Invalid => { + return Err(Error::InvalidAuthEntry { + reason: "authorization entry is not expected for the transaction".to_string(), + auth_entry_str: format_auth_entry(raw_auth), + }); + } + } // See if we have a signer for this authorizationEntry // If not, then we Error - let needle: &[u8; 32] = match address { + let auth_address_bytes: &[u8; 32] = match address { ScAddress::MuxedAccount(_) => todo!("muxed accounts are not supported"), ScAddress::ClaimableBalance(_) => todo!("claimable balance not supported"), ScAddress::LiquidityPool(_) => todo!("liquidity pool not supported"), @@ -106,9 +133,17 @@ pub async fn sign_soroban_authorizations( } }; + // Auth entries should not request a signature from the tx source account via the `Address` credential type + if auth_address_bytes == source_bytes { + return Err(Error::InvalidAuthEntry { + reason: "transaction source account is used as credentials".to_string(), + auth_entry_str: format_auth_entry(raw_auth), + }); + } + let mut signer: Option<&Signer> = None; for s in signers { - if needle == &s.get_public_key()?.0 { + if auth_address_bytes == &s.get_public_key()?.0 { signer = Some(s); } } @@ -128,7 +163,7 @@ pub async fn sign_soroban_authorizations( None => { return Err(Error::MissingSignerForAddress { address: stellar_strkey::Strkey::PublicKeyEd25519( - stellar_strkey::ed25519::PublicKey(*needle), + stellar_strkey::ed25519::PublicKey(*auth_address_bytes), ) .to_string(), }); @@ -153,6 +188,28 @@ pub async fn sign_soroban_authorizations( Ok(Some(tx)) } +fn confirm_non_strict_authorization(auth: &SorobanAuthorizationEntry) -> Result<(), Error> { + let print = Print::new(false); + print.warnln( + "Authorization entry does not match the current contract call, and needs approval:", + ); + print.println(format_auth_entry(auth)); + + let stdin = io::stdin(); + if !stdin.is_terminal() { + return Err(Error::AuthEntryRequiresConfirmation); + } + + print.warnln("Sign this authorization entry? (y/N)"); + let mut response = String::new(); + stdin.lock().read_line(&mut response)?; + if response.trim().eq_ignore_ascii_case("y") { + Ok(()) + } else { + Err(Error::AuthRejected) + } +} + async fn sign_soroban_authorization_entry( raw: &SorobanAuthorizationEntry, signer: &Signer, @@ -379,15 +436,235 @@ impl SecureStoreEntry { } } +/// Extract the Ed25519 public key bytes from a MuxedAccount +fn muxed_account_bytes(source: &MuxedAccount) -> &[u8; 32] { + match source { + MuxedAccount::Ed25519(Uint256(bytes)) => bytes, + MuxedAccount::MuxedEd25519(muxed) => &muxed.ed25519.0, + } +} + #[cfg(test)] mod tests { use super::*; use crate::signer::ledger::LedgerEntry; + use crate::xdr::{ + BytesM, HostFunction, InvokeContractArgs, InvokeHostFunctionOp, Memo, Preconditions, + SequenceNumber, SorobanAuthorizedFunction, SorobanAuthorizedInvocation, TransactionExt, + }; + + const NETWORK: &str = "Test SDF Network ; September 2015"; + const EXPIRATION_LEDGER: u32 = 100; - const TEST_PUBLIC_KEY: &str = "GAREAZZQWHOCBJS236KIE3AWYBVFLSBK7E5UW3ICI3TCRWQKT5LNLCEZ"; + fn local_signer(seed: [u8; 32]) -> Signer { + Signer { + kind: SignerKind::Local(LocalKey { + key: ed25519_dalek::SigningKey::from_bytes(&seed), + }), + print: Print::new(true), + } + } + + fn signer_pubkey(signer: &Signer) -> [u8; 32] { + signer.get_public_key().unwrap().0 + } + + fn ed25519_address(bytes: [u8; 32]) -> ScAddress { + ScAddress::Account(AccountId(PublicKey::PublicKeyTypeEd25519(Uint256(bytes)))) + } + + fn invoke_args(contract: [u8; 32], fn_name: &str) -> InvokeContractArgs { + InvokeContractArgs { + contract_address: ScAddress::Contract(stellar_xdr::curr::ContractId(Hash(contract))), + function_name: ScSymbol(fn_name.try_into().unwrap()), + args: VecM::default(), + } + } + + fn invocation(contract: [u8; 32], fn_name: &str) -> SorobanAuthorizedInvocation { + SorobanAuthorizedInvocation { + function: SorobanAuthorizedFunction::ContractFn(invoke_args(contract, fn_name)), + sub_invocations: VecM::default(), + } + } + + fn address_auth( + address: ScAddress, + invocation: SorobanAuthorizedInvocation, + ) -> SorobanAuthorizationEntry { + SorobanAuthorizationEntry { + credentials: SorobanCredentials::Address(SorobanAddressCredentials { + address, + nonce: 0, + signature_expiration_ledger: 0, + signature: ScVal::Void, + }), + root_invocation: invocation, + } + } + + fn build_tx( + source: MuxedAccount, + host_function: HostFunction, + auth: Vec, + ) -> Transaction { + Transaction { + source_account: source, + fee: 100, + seq_num: SequenceNumber(1), + cond: Preconditions::None, + memo: Memo::None, + operations: vec![Operation { + source_account: None, + body: OperationBody::InvokeHostFunction(InvokeHostFunctionOp { + host_function, + auth: auth.try_into().unwrap(), + }), + }] + .try_into() + .unwrap(), + ext: TransactionExt::V0, + } + } + + /// Pull the embedded public_key bytes out of a signed Address-cred entry. + fn extract_signed_pubkey(creds: &SorobanAddressCredentials) -> [u8; 32] { + let ScVal::Vec(Some(outer)) = &creds.signature else { + panic!("expected ScVal::Vec signature"); + }; + let Some(ScVal::Map(Some(map))) = outer.first() else { + panic!("expected ScVal::Map inside signature vec"); + }; + map.iter() + .find_map(|e| match (&e.key, &e.val) { + (ScVal::Symbol(s), ScVal::Bytes(b)) if s.0.as_slice() == b"public_key" => { + Some(b.as_slice().try_into().unwrap()) + } + _ => None, + }) + .expect("public_key entry") + } + + #[tokio::test] + async fn test_signs_address_auth_entry_with_matching_signer() { + let signer = local_signer([1u8; 32]); + let signer_unused = local_signer([2u8; 32]); + let signer_pk = signer_pubkey(&signer); + let source = MuxedAccount::Ed25519(Uint256([9u8; 32])); + let contract = [42u8; 32]; + + let entry = address_auth(ed25519_address(signer_pk), invocation(contract, "hello")); + let host_fn = HostFunction::InvokeContract(invoke_args(contract, "hello")); + let tx = build_tx(source, host_fn, vec![entry]); + + let signed_auth_tx = sign_soroban_authorizations( + &tx, + &[signer_unused, signer], + EXPIRATION_LEDGER, + NETWORK, + false, + ) + .await + .unwrap() + .expect("signing modifies the transaction"); + + let OperationBody::InvokeHostFunction(body) = &signed_auth_tx.operations[0].body else { + panic!("expected InvokeHostFunction"); + }; + let SorobanCredentials::Address(creds) = &body.auth[0].credentials else { + panic!("expected Address credentials"); + }; + assert!( + !matches!(creds.signature, ScVal::Void), + "signature should be filled in" + ); + assert_eq!(creds.signature_expiration_ledger, EXPIRATION_LEDGER); + assert_eq!( + extract_signed_pubkey(creds), + signer_pk, + "embedded public_key should match the signer" + ); + } + + #[tokio::test] + async fn test_non_strict_auth_signs_when_allowed() { + let signer = local_signer([1u8; 32]); + let signer_pk = signer_pubkey(&signer); + let source = MuxedAccount::Ed25519(Uint256([9u8; 32])); + let contract = [42u8; 32]; + let other_contract = [99u8; 32]; + + let entry = address_auth( + ed25519_address(signer_pk), + invocation(other_contract, "hello"), + ); + let host_fn = HostFunction::InvokeContract(invoke_args(contract, "hello")); + let tx = build_tx(source, host_fn, vec![entry]); + + let signed_auth_tx = + sign_soroban_authorizations(&tx, &[signer], EXPIRATION_LEDGER, NETWORK, true) + .await + .unwrap() + .expect("signing modifies the transaction"); + + let OperationBody::InvokeHostFunction(body) = &signed_auth_tx.operations[0].body else { + panic!("expected InvokeHostFunction"); + }; + let SorobanCredentials::Address(creds) = &body.auth[0].credentials else { + panic!("expected Address credentials"); + }; + assert!(!matches!(creds.signature, ScVal::Void)); + } + + #[tokio::test] + async fn test_upload_wasm_with_auth_returns_invalid() { + let signer = local_signer([1u8; 32]); + let signer_pk = signer_pubkey(&signer); + let source = MuxedAccount::Ed25519(Uint256([9u8; 32])); + let wasm: BytesM = [0u8; 32].try_into().unwrap(); + + let entry = address_auth(ed25519_address(signer_pk), invocation([42u8; 32], "hello")); + let host_fn = HostFunction::UploadContractWasm(wasm); + let tx = build_tx(source, host_fn, vec![entry]); + + let result = + sign_soroban_authorizations(&tx, &[signer], EXPIRATION_LEDGER, NETWORK, false).await; + assert!(matches!(result, Err(Error::InvalidAuthEntry { .. }))); + } + + #[tokio::test] + async fn test_source_account_as_address_returns_invalid() { + let signer = local_signer([1u8; 32]); + let signer_pk = signer_pubkey(&signer); + let source = MuxedAccount::Ed25519(Uint256(signer_pk)); + let contract = [42u8; 32]; + + let entry = address_auth(ed25519_address(signer_pk), invocation(contract, "hello")); + let host_fn = HostFunction::InvokeContract(invoke_args(contract, "hello")); + let tx = build_tx(source, host_fn, vec![entry]); + + let result = + sign_soroban_authorizations(&tx, &[signer], EXPIRATION_LEDGER, NETWORK, false).await; + assert!(matches!(result, Err(Error::InvalidAuthEntry { .. }))); + } + + #[tokio::test] + async fn test_missing_signer_returns_error() { + let source = MuxedAccount::Ed25519(Uint256([9u8; 32])); + let contract = [42u8; 32]; + let unknown = [77u8; 32]; + + let entry = address_auth(ed25519_address(unknown), invocation(contract, "hello")); + let host_fn = HostFunction::InvokeContract(invoke_args(contract, "hello")); + let tx = build_tx(source, host_fn, vec![entry]); + + let result = sign_soroban_authorizations(&tx, &[], EXPIRATION_LEDGER, NETWORK, false).await; + assert!(matches!(result, Err(Error::MissingSignerForAddress { .. }))); + } #[test] fn ledger_signer_get_public_key_returns_cached_without_device() { + const TEST_PUBLIC_KEY: &str = "GAREAZZQWHOCBJS236KIE3AWYBVFLSBK7E5UW3ICI3TCRWQKT5LNLCEZ"; let pk = stellar_strkey::ed25519::PublicKey::from_string(TEST_PUBLIC_KEY).unwrap(); let signer = Signer { kind: SignerKind::Ledger(LedgerEntry { diff --git a/cmd/soroban-cli/src/signer/validation.rs b/cmd/soroban-cli/src/signer/validation.rs new file mode 100644 index 0000000000..db9363416c --- /dev/null +++ b/cmd/soroban-cli/src/signer/validation.rs @@ -0,0 +1,221 @@ +use crate::xdr::{HostFunction, SorobanAuthorizedFunction, SorobanAuthorizedInvocation}; + +/// Classification of an `Address`-credential auth entry's relationship to the +/// transaction's host function. +/// +/// `SourceAccount` credential entries are out of scope here — they are signed +/// implicitly via the transaction envelope and never reach this classifier. +#[derive(Debug, PartialEq, Eq)] +pub enum AuthStyle { + /// `root_invocation` matches the host function exactly. Safe to sign: + /// the entry is bound to the host function. + Strict, + /// `root_invocation` does not match the host function exactly. Any transaction + /// whose auth tree contains this entry could consume the resulting signature. + NonStrict, + /// `root_invocation` is not expected for the host function + Invalid, +} + +/// Classify an auth invocation against the transaction's host function. +/// +/// ### Arguments +/// * `source_host_fn`- The transaction's host function +/// * `auth_invocation` - The auth entry's root invocation +pub fn classify_auth_invocation( + source_host_fn: &HostFunction, + auth_invocation: &SorobanAuthorizedInvocation, +) -> AuthStyle { + // No auth entries are valid for `UploadContractWasm`. + if matches!(source_host_fn, HostFunction::UploadContractWasm(_)) { + return AuthStyle::Invalid; + } + + // Check if the auth entry's root invocation matches the host function exactly. + // This is different than just a `root_auth` check, as contracts that authorize with + // `require_auth_for_args` at the root are not considered strict auth. This tradeoff is + // made to ensure that even a tampered auth entry can be flagged as non-strict. + let is_strict = match (source_host_fn, &auth_invocation.function) { + (HostFunction::InvokeContract(op), SorobanAuthorizedFunction::ContractFn(args)) => { + args == op + } + ( + HostFunction::CreateContract(op), + SorobanAuthorizedFunction::CreateContractHostFn(args), + ) => args == op, + ( + HostFunction::CreateContractV2(op), + SorobanAuthorizedFunction::CreateContractV2HostFn(args), + ) => args == op, + _ => false, + }; + + if is_strict { + AuthStyle::Strict + } else { + AuthStyle::NonStrict + } +} + +#[cfg(test)] +mod tests { + use super::*; + use crate::xdr::{ + AccountId, BytesM, ContractExecutable, ContractIdPreimage, ContractIdPreimageFromAddress, + CreateContractArgsV2, Hash, InvokeContractArgs, PublicKey, ScAddress, ScSymbol, ScVal, + Uint256, VecM, + }; + use stellar_strkey::ed25519; + + const SOURCE_ACCOUNT: &str = "GBZXN7PIRZGNMHGA7MUUUF4GWPY5AYPV6LY4UV2GL6VJGIQRXFDNMADI"; + + fn source_bytes() -> [u8; 32] { + ed25519::PublicKey::from_string(SOURCE_ACCOUNT).unwrap().0 + } + + fn ed25519_address(bytes: [u8; 32]) -> ScAddress { + ScAddress::Account(AccountId(PublicKey::PublicKeyTypeEd25519(Uint256(bytes)))) + } + + fn host_fn_invoke(contract: [u8; 32], fn_name: &str, args: &[ScVal]) -> HostFunction { + HostFunction::InvokeContract(InvokeContractArgs { + contract_address: ScAddress::Contract(stellar_xdr::curr::ContractId(Hash(contract))), + function_name: ScSymbol(fn_name.try_into().unwrap()), + args: args.try_into().unwrap(), + }) + } + + fn host_fn_create(wasm_hash: [u8; 32], args: &[ScVal]) -> HostFunction { + HostFunction::CreateContractV2(CreateContractArgsV2 { + contract_id_preimage: ContractIdPreimage::Address(ContractIdPreimageFromAddress { + address: ed25519_address(source_bytes()), + salt: Uint256([0u8; 32]), + }), + executable: ContractExecutable::Wasm(wasm_hash.into()), + constructor_args: args.try_into().unwrap(), + }) + } + + fn invocation_contract( + contract: [u8; 32], + fn_name: &str, + args: &[ScVal], + ) -> SorobanAuthorizedInvocation { + SorobanAuthorizedInvocation { + function: SorobanAuthorizedFunction::ContractFn(InvokeContractArgs { + contract_address: ScAddress::Contract(stellar_xdr::curr::ContractId(Hash( + contract, + ))), + function_name: ScSymbol(fn_name.try_into().unwrap()), + args: args.to_vec().try_into().unwrap(), + }), + sub_invocations: VecM::default(), + } + } + + fn invocation_create(wasm_hash: [u8; 32], args: &[ScVal]) -> SorobanAuthorizedInvocation { + SorobanAuthorizedInvocation { + function: SorobanAuthorizedFunction::CreateContractV2HostFn(CreateContractArgsV2 { + contract_id_preimage: ContractIdPreimage::Address(ContractIdPreimageFromAddress { + address: ed25519_address(source_bytes()), + salt: Uint256([0u8; 32]), + }), + executable: ContractExecutable::Wasm(wasm_hash.into()), + constructor_args: args.try_into().unwrap(), + }), + sub_invocations: VecM::default(), + } + } + + #[test] + fn test_matching_root_invocation_is_strict() { + let contract = [1u8; 32]; + let args = &[ScVal::U32(42), ScVal::Symbol("hello".try_into().unwrap())]; + + let host_fn = host_fn_invoke(contract, "hello", args); + let invocation = invocation_contract(contract, "hello", args); + + let style = classify_auth_invocation(&host_fn, &invocation); + assert_eq!(style, AuthStyle::Strict); + } + + #[test] + fn test_subinvocations_dont_affect_root_match() { + let contract = [1u8; 32]; + let other = [99u8; 32]; + let args = &[ScVal::U32(42), ScVal::Symbol("hello".try_into().unwrap())]; + + let host_fn = host_fn_invoke(contract, "hello", args); + let mut invocation = invocation_contract(contract, "hello", args); + invocation.sub_invocations = [invocation_contract(other, "other", &[])] + .try_into() + .unwrap(); + + let style = classify_auth_invocation(&host_fn, &invocation); + assert_eq!(style, AuthStyle::Strict); + } + + #[test] + fn test_different_root_contract_is_non_strict() { + let contract = [1u8; 32]; + let other = [99u8; 32]; + + let host_fn = host_fn_invoke(contract, "hello", &[]); + let invocation = invocation_contract(other, "hello", &[]); + + let style = classify_auth_invocation(&host_fn, &invocation); + assert_eq!(style, AuthStyle::NonStrict); + } + + #[test] + fn test_different_function_same_contract_is_non_strict() { + let contract = [1u8; 32]; + + let host_fn = host_fn_invoke(contract, "hello", &[]); + let invocation = invocation_contract(contract, "transfer", &[]); + + let style = classify_auth_invocation(&host_fn, &invocation); + assert_eq!(style, AuthStyle::NonStrict); + } + + #[test] + fn test_different_args_is_non_strict() { + let contract = [1u8; 32]; + let args = &[ScVal::U32(42), ScVal::Symbol("hello".try_into().unwrap())]; + let wrong = &[ScVal::U32(43), ScVal::Symbol("hello".try_into().unwrap())]; + + let host_fn = host_fn_invoke(contract, "hello", args); + let invocation = invocation_contract(contract, "hello", wrong); + + let style = classify_auth_invocation(&host_fn, &invocation); + assert_eq!(style, AuthStyle::NonStrict); + } + + #[test] + fn test_upload_wasm_with_auth_entry_is_invalid() { + let contract = [1u8; 32]; + let wasm_hash: BytesM = [42u8; 32].try_into().unwrap(); + + let host_fn = HostFunction::UploadContractWasm(wasm_hash); + let invocation = invocation_contract(contract, "hello", &[]); + + let style = classify_auth_invocation(&host_fn, &invocation); + assert_eq!(style, AuthStyle::Invalid); + } + + #[test] + fn test_matching_create_contract_root_is_strict() { + let contract = [1u8; 32]; + let wasm_hash = [42u8; 32]; + let args = &[ScVal::U32(42), ScVal::Symbol("hello".try_into().unwrap())]; + + let host_fn = host_fn_create(wasm_hash, args); + let mut invocation = invocation_create(wasm_hash, args); + invocation.sub_invocations = [invocation_contract(contract, "__constructor", args)] + .try_into() + .unwrap(); + + let style = classify_auth_invocation(&host_fn, &invocation); + assert_eq!(style, AuthStyle::Strict); + } +} diff --git a/cmd/soroban-cli/src/tx.rs b/cmd/soroban-cli/src/tx.rs index 8aa3a9c373..4aebfb4a7c 100644 --- a/cmd/soroban-cli/src/tx.rs +++ b/cmd/soroban-cli/src/tx.rs @@ -65,7 +65,8 @@ where data::write(sim_res.clone().into(), &network.rpc_uri()?)?; } - // Need to sign all auth entries + // Sign all auth entries. Each entry is validated against the transaction's + // host function inside `sign_soroban_authorizations` before being signed. if let Some(tx) = config .sign_soroban_authorizations(&txn, auth_signers) .await?