Skip to content

Commit

Permalink
Add some auth test contracts (#938)
Browse files Browse the repository at this point in the history
### What
Add some auth test contracts that exercise different auth scenarios.

### Why
I initially started writing these as a learning experience, but would
like to commit them as a way to capture existing behavior that contract
developers are exposed to and therefore relying on. As auth evolves in
the coming releases, changes to these tests will make it clearer to me
and others working at the application layer on how auth is changing.
  • Loading branch information
leighmcculloch authored Jun 23, 2023
1 parent fa7965d commit c20b456
Show file tree
Hide file tree
Showing 9 changed files with 592 additions and 0 deletions.
1 change: 1 addition & 0 deletions soroban-sdk/src/tests.rs
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
#![cfg(test)]

mod address;
mod auth;
mod budget;
mod contract_add_i32;
mod contract_assert;
Expand Down
7 changes: 7 additions & 0 deletions soroban-sdk/src/tests/auth.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
mod auth_10_one;
mod auth_15_one_repeat;
mod auth_17_no_consume_requirement;
mod auth_20_deep_one_address;
mod auth_30_deep_one_address_repeat;
mod auth_35_deep_one_address_repeat_grouped;
mod auth_40_multi_one_address;
45 changes: 45 additions & 0 deletions soroban-sdk/src/tests/auth/auth_10_one.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,45 @@
//! Demonstrates that a single contract invocation can be authorized.
use crate as soroban_sdk;

use soroban_sdk::{
contract, contractimpl,
testutils::{Address as _, MockAuth, MockAuthInvoke},
Address, Env, IntoVal,
};

#[contract]
pub struct Contract;

#[contractimpl]
impl Contract {
pub fn add(a: Address, x: i32, y: i32) -> i32 {
a.require_auth();
x + y
}
}

#[test]
fn test() {
let e = Env::default();
let contract_id = e.register_contract(None, Contract);
let client = ContractClient::new(&e, &contract_id);

let a = Address::random(&e);

let c = client
.mock_auths(&[MockAuth {
address: &a,
invoke: &MockAuthInvoke {
contract: &contract_id,
fn_name: "add",
args: (&a, 10, 12).into_val(&e),
sub_invokes: &[],
},
}])
.add(&a, &10, &12);

assert_eq!(c, 22);

println!("{:?}", e.auths());
}
60 changes: 60 additions & 0 deletions soroban-sdk/src/tests/auth/auth_15_one_repeat.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,60 @@
//! Demonstrates how an address can be required auth multiple times in a single
//! invocation.
//!
//! The authorizations cannot be grouped into a single authorization.
use crate as soroban_sdk;

use soroban_sdk::{
contract, contractimpl,
testutils::{Address as _, MockAuth, MockAuthInvoke},
Address, Env, IntoVal,
};

#[contract]
pub struct Contract;

#[contractimpl]
impl Contract {
pub fn add(a: Address, x: i32, y: i32) -> i32 {
a.require_auth();
a.require_auth();
x + y
}
}

#[test]
fn test() {
let e = Env::default();
let contract_id = e.register_contract(None, Contract);
let client = ContractClient::new(&e, &contract_id);

let a = Address::random(&e);

let c = client
.mock_auths(&[
MockAuth {
address: &a,
invoke: &MockAuthInvoke {
contract: &contract_id,
fn_name: "add",
args: (&a, 10, 12).into_val(&e),
sub_invokes: &[],
},
},
MockAuth {
address: &a,
invoke: &MockAuthInvoke {
contract: &contract_id,
fn_name: "add",
args: (&a, 10, 12).into_val(&e),
sub_invokes: &[],
},
},
])
.add(&a, &10, &12);

assert_eq!(c, 22);

println!("{:?}", e.auths());
}
65 changes: 65 additions & 0 deletions soroban-sdk/src/tests/auth/auth_17_no_consume_requirement.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,65 @@
//! Demonstrates how an authorization is not required to be consumed by an
//! invocation.
//!
//! It's possible to have dangling authorizations that may be optionally
//! consumed, or never consumed.
//!
//! Because authorizations cannot always be grouped, it's not possible to group
//! potentially related optional auths together to ensure. This means it's
//! possible for an auth to be exposed on chain that could be executed by
//! someone in isolation, even after the transaction succeeds.
use crate as soroban_sdk;

use soroban_sdk::{
contract, contractimpl,
testutils::{Address as _, MockAuth, MockAuthInvoke},
Address, Env, IntoVal,
};

#[contract]
pub struct Contract;

#[contractimpl]
impl Contract {
pub fn add(a: Address, x: i32, y: i32) -> i32 {
a.require_auth();
x + y
}
}

#[test]
fn test() {
let e = Env::default();
let contract_id = e.register_contract(None, Contract);
let client = ContractClient::new(&e, &contract_id);

let a = Address::random(&e);

let c = client
.mock_auths(&[
MockAuth {
address: &a,
invoke: &MockAuthInvoke {
contract: &contract_id,
fn_name: "add",
args: (&a, 10, 12).into_val(&e),
sub_invokes: &[],
},
},
MockAuth {
address: &a,
invoke: &MockAuthInvoke {
contract: &contract_id,
fn_name: "add",
args: (&a, 10, 12).into_val(&e),
sub_invokes: &[],
},
},
])
.add(&a, &10, &12);

assert_eq!(c, 22);

println!("{:?}", e.auths());
}
90 changes: 90 additions & 0 deletions soroban-sdk/src/tests/auth/auth_20_deep_one_address.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,90 @@
use crate as soroban_sdk;

use soroban_sdk::{
contract, contractimpl,
testutils::{Address as _, MockAuth, MockAuthInvoke},
Address, Env, IntoVal,
};

#[contract]
pub struct ContractA;

#[contractimpl]
impl ContractA {
pub fn fna(e: Env, contract: Address, a: Address) -> i32 {
let client = ContractBClient::new(&e, &contract);
client.fnb(&a)
}
}

#[contract]
pub struct ContractB;

#[contractimpl]
impl ContractB {
pub fn fnb(a: Address) -> i32 {
a.require_auth();
1
}
}

#[test]
fn test() {
let e = Env::default();
let contract_a_id = e.register_contract(None, ContractA);
let contract_b_id = e.register_contract(None, ContractB);
let client = ContractAClient::new(&e, &contract_a_id);

let a = Address::random(&e);

let c = client
.mock_auths(&[MockAuth {
address: &a,
invoke: &MockAuthInvoke {
contract: &contract_b_id,
fn_name: "fnb",
args: (&a,).into_val(&e),
sub_invokes: &[],
},
}])
.fna(&contract_b_id, &a);

assert_eq!(c, 1);

println!("{:?}", e.auths());
}

#[test]
// This test panics with not authorized because it does not appear to be
// possible to constrain an auth to a specific call tree, unless the top-level
// of that call tree also calls require_auth with the same address.
#[should_panic = "HostError: Error(Auth, InvalidAction)"]
fn test_auth_tree() {
let e = Env::default();
let contract_a_id = e.register_contract(None, ContractA);
let contract_b_id = e.register_contract(None, ContractB);
let client = ContractAClient::new(&e, &contract_a_id);

let a = Address::random(&e);

let c = client
.mock_auths(&[MockAuth {
address: &a,
invoke: &MockAuthInvoke {
contract: &contract_a_id,
fn_name: "fna",
args: ().into_val(&e), // ???
sub_invokes: &[MockAuthInvoke {
contract: &contract_b_id,
fn_name: "fnb",
args: (&a,).into_val(&e),
sub_invokes: &[],
}],
},
}])
.fna(&contract_b_id, &a);

assert_eq!(c, 1);

println!("{:?}", e.auths());
}
113 changes: 113 additions & 0 deletions soroban-sdk/src/tests/auth/auth_30_deep_one_address_repeat.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,113 @@
use crate as soroban_sdk;

use soroban_sdk::{
contract, contractimpl,
testutils::{Address as _, MockAuth, MockAuthInvoke},
Address, Env, IntoVal,
};

#[contract]
pub struct ContractA;

#[contractimpl]
impl ContractA {
pub fn fna(e: Env, contract: Address, a: Address) -> i32 {
let client = ContractBClient::new(&e, &contract);
client.fnb(&a)
}
}

#[contract]
pub struct ContractB;

#[contractimpl]
impl ContractB {
pub fn fnb(a: Address) -> i32 {
a.require_auth();
a.require_auth();
1
}
}

#[test]
fn test() {
let e = Env::default();
let contract_a_id = e.register_contract(None, ContractA);
let contract_b_id = e.register_contract(None, ContractB);
let client = ContractAClient::new(&e, &contract_a_id);

let a = Address::random(&e);

let c = client
.mock_auths(&[
MockAuth {
address: &a,
invoke: &MockAuthInvoke {
contract: &contract_b_id,
fn_name: "fnb",
args: (&a,).into_val(&e),
sub_invokes: &[],
},
},
MockAuth {
address: &a,
invoke: &MockAuthInvoke {
contract: &contract_b_id,
fn_name: "fnb",
args: (&a,).into_val(&e),
sub_invokes: &[],
},
},
])
.fna(&contract_b_id, &a);

assert_eq!(c, 1);

println!("{:?}", e.auths());
}

#[test]
// This test panics with not authorized because it does not appear to be
// possible to constrain an auth to a specific call tree, unless the top-level
// of that call tree also calls require_auth with the same address.
//
// It also doesn't appear to be possible to group auths that occur at the same
// level, again unless a higher level also require_auth's the same addresses.
#[should_panic = "HostError: Error(Auth, InvalidAction)"]
fn test_auth_tree() {
let e = Env::default();
let contract_a_id = e.register_contract(None, ContractA);
let contract_b_id = e.register_contract(None, ContractB);
let client = ContractAClient::new(&e, &contract_a_id);

let a = Address::random(&e);

let c = client
.mock_auths(&[MockAuth {
address: &a,
invoke: &MockAuthInvoke {
contract: &contract_a_id,
fn_name: "fna",
args: ().into_val(&e), // ???
sub_invokes: &[
MockAuthInvoke {
contract: &contract_b_id,
fn_name: "fnb",
args: (&a,).into_val(&e),
sub_invokes: &[],
},
MockAuthInvoke {
contract: &contract_b_id,
fn_name: "fnb",
args: (&a,).into_val(&e),
sub_invokes: &[],
},
],
},
}])
.fna(&contract_b_id, &a);

assert_eq!(c, 1);

println!("{:?}", e.auths());
}
Loading

0 comments on commit c20b456

Please sign in to comment.