Skip to content

Commit

Permalink
Update auth testing utility interface. (#859)
Browse files Browse the repository at this point in the history
### What

Update auth testing utility interface to return all the authorizations
instead of checking the result inclusion.

### Why

Improve testing UX.

### Known limitations

There is no utility to check the whole tree yet - we should add it as a
followup.
  • Loading branch information
dmkozh authored Feb 7, 2023
1 parent f2f4bf0 commit e2b645c
Show file tree
Hide file tree
Showing 4 changed files with 93 additions and 64 deletions.
10 changes: 5 additions & 5 deletions Cargo.lock

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

6 changes: 3 additions & 3 deletions Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -37,17 +37,17 @@ soroban-token-spec = { version = "0.4.3", path = "soroban-token-spec" }
[workspace.dependencies.soroban-env-common]
version = "0.0.12"
git = "https://github.com/stellar/rs-soroban-env"
rev = "2431bd416010215cc9759b435662a7274206a4c0"
rev = "9fe84ac7d0a52b45eed56c74e1261c7112314ef7"

[workspace.dependencies.soroban-env-guest]
version = "0.0.12"
git = "https://github.com/stellar/rs-soroban-env"
rev = "2431bd416010215cc9759b435662a7274206a4c0"
rev = "9fe84ac7d0a52b45eed56c74e1261c7112314ef7"

[workspace.dependencies.soroban-env-host]
version = "0.0.12"
git = "https://github.com/stellar/rs-soroban-env"
rev = "2431bd416010215cc9759b435662a7274206a4c0"
rev = "9fe84ac7d0a52b45eed56c74e1261c7112314ef7"

[workspace.dependencies.stellar-strkey]
version = "0.0.7"
Expand Down
108 changes: 73 additions & 35 deletions soroban-sdk/src/env.rs
Original file line number Diff line number Diff line change
Expand Up @@ -621,13 +621,21 @@ impl Env {
contract_id
}

/// Checks if a top-level `require_auth` or `require_auth_for_args` call has
/// happened during the last contract invocation for the given `address` and
/// contract invocation.
/// Returns the recorded top-level `require_auth` or `require_auth_for_args`
/// calls that have happened during the last contract invocation.
///
/// This also removes the record about the authorization, so calling this
/// for the second time with the same arguments will usually return `false`
/// (unless the identical authorization happened multiple times).
/// Use this in tests to verify that the expected authorizations with the
/// expected arguments are required.
///
/// The return value is a vector of authorizations represented by tuples of
/// `(address, contract_id, function_name, args)` corresponding to the calls
/// of `require_auth_for_args(address, args)` from the contract function
/// `(contract_id, function_name)` (or `require_auth` with all the arguments
/// of the function invocation).
///
/// The order of the returned vector is defined by the order of
/// `require_auth` calls. It is recommended though to do unordered
/// comparison in case if multiple entries are returned.
///
/// 'Top-level call' here means that this is the first call of
/// `require_auth` for a given address in the call stack; it doesn't have
Expand All @@ -637,15 +645,22 @@ impl Env {
/// `true` when verifying the contract B's `require_auth` call, but it will
/// return `false` if contract A makes a `require_auth` call.
///
/// It is possible for a single address to be present multiple times in the
/// output, as long as there are multiple disjoint call trees for that
/// address.
///
/// ### Examples
/// ```
/// use soroban_sdk::{contractimpl, testutils::Address as _, Address, Env, IntoVal};
/// use soroban_sdk::{contractimpl, testutils::Address as _, Address, Env, IntoVal, symbol};
///
/// pub struct Contract;
///
/// #[contractimpl]
/// impl Contract {
/// pub fn transfer(env: Env, address: Address, amount: i128) {
/// address.require_auth();
/// }
/// pub fn transfer2(env: Env, address: Address, amount: i128) {
/// address.require_auth_for_args((amount / 2,).into_val(&env));
/// }
/// }
Expand All @@ -655,44 +670,67 @@ impl Env {
/// # }
/// # #[cfg(feature = "testutils")]
/// # fn main() {
/// extern crate std;
/// let env = Env::default();
/// let contract_id = env.register_contract(None, Contract);
/// let client = ContractClient::new(&env, &contract_id);
/// let address = Address::random(&env);
/// client.transfer(&address, &1000_i128);
/// // `transfer` requires auth for (amount / 2) == (1000 / 2) == 500.
/// assert!(env.verify_top_authorization(
/// &address,
/// &contract_id,
/// "transfer",
/// (500_i128,).into_val(&env)
/// ));
/// // The duplicate verification won't succeed.
/// assert!(!env.verify_top_authorization(
/// &address,
/// &contract_id,
/// "transfer",
/// (500_i128,).into_val(&env)
/// ));
/// assert_eq!(
/// env.recorded_top_authorizations(),
/// std::vec![(
/// address.clone(),
/// client.contract_id.clone(),
/// symbol!("transfer"),
/// (&address, 1000_i128,).into_val(&env)
/// )]
/// );
///
/// client.transfer2(&address, &1000_i128);
/// assert_eq!(
/// env.recorded_top_authorizations(),
/// std::vec![(
/// address.clone(),
/// client.contract_id.clone(),
/// symbol!("transfer2"),
/// // `transfer2` requires auth for (amount / 2) == (1000 / 2) == 500.
/// (500_i128,).into_val(&env)
/// )]
/// );
/// }
/// # #[cfg(not(feature = "testutils"))]
/// # fn main() { }
/// ```
pub fn verify_top_authorization(
pub fn recorded_top_authorizations(
&self,
address: &Address,
contract_id: &BytesN<32>,
function_name: &str,
args: Vec<RawVal>,
) -> bool {
self.env_impl
.verify_top_authorization(
address.to_object(),
xdr::Hash(contract_id.to_array()),
Symbol::try_from_str(function_name).unwrap(),
args.to_object(),
)
.unwrap()
) -> std::vec::Vec<(Address, BytesN<32>, Symbol, Vec<RawVal>)> {
use xdr::{ScObject, ScVal};
let authorizations = self.env_impl.get_recorded_top_authorizations().unwrap();
authorizations
.iter()
.map(|a| {
let mut args = Vec::new(self);
for v in a.3.iter() {
args.push_back(RawVal::try_from_val(self, v).unwrap());
}
(
Address::try_from_val(
self,
&ScVal::Object(Some(ScObject::Address(a.0.clone()))),
)
.unwrap(),
BytesN::<32>::try_from_val(
self,
&ScVal::Object(Some(ScObject::Bytes(
a.1.as_slice().to_vec().try_into().unwrap(),
))),
)
.unwrap(),
a.2.clone(),
args,
)
})
.collect()
}

fn register_contract_with_contract_id_and_source(
Expand Down
33 changes: 12 additions & 21 deletions soroban-token-spec/src/tests/use_token_contract.rs
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
use soroban_sdk::{
contractimpl, contracttype, testutils::Address as _, Address, BytesN, Env, IntoVal,
contractimpl, contracttype, symbol, testutils::Address as _, Address, BytesN, Env, IntoVal,
};

mod token_contract {
Expand Down Expand Up @@ -43,6 +43,8 @@ impl TestContract {

#[test]
fn test() {
extern crate std;

let env = Env::default();
let admin = Address::random(&env);
let token_contract_id = env.register_stellar_asset_contract(admin);
Expand All @@ -57,26 +59,15 @@ fn test() {
let spender = Address::random(&env);
client.incr_allow(&from, &spender, &20);

// Smoke test check that authorization with wrong args didn't happen.
assert!(!env.verify_top_authorization(
&from,
&token_client.contract_id,
"incr_allow",
(&from, &spender, 19_i128).into_val(&env),
));
assert!(env.verify_top_authorization(
&from,
&token_client.contract_id,
"incr_allow",
(&from, &spender, 20_i128).into_val(&env),
));
// Smoke test check that double authorization didn't happen.
assert!(!env.verify_top_authorization(
&from,
&token_client.contract_id,
"incr_allow",
(&from, &spender, 20_i128).into_val(&env),
));
assert_eq!(
env.recorded_top_authorizations(),
std::vec![(
from.clone(),
token_client.contract_id.clone(),
symbol!("incr_allow"),
(&from, &spender, 20_i128).into_val(&env)
)]
);

assert_eq!(client.allowance(&from, &spender), 20);
}

0 comments on commit e2b645c

Please sign in to comment.