Skip to content

Commit

Permalink
Add a very simple account contract example. (#227)
Browse files Browse the repository at this point in the history
* Add a very simple account contract example.

This is close to minimal possible valid account contract that is aimed at demonstrating the most basic concepts.
  • Loading branch information
dmkozh authored Apr 11, 2023
1 parent 22e1827 commit 04a4a6a
Show file tree
Hide file tree
Showing 6 changed files with 163 additions and 1 deletion.
10 changes: 10 additions & 0 deletions Cargo.lock

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

1 change: 1 addition & 0 deletions Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,7 @@ members = [
"atomic_multiswap",
"account",
"alloc",
"simple_account",
]

[profile.release-with-logs]
Expand Down
2 changes: 1 addition & 1 deletion account/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -66,7 +66,7 @@ impl AccountContract {

// This is the 'entry point' of the account contract and every account
// contract has to implement it. `require_auth` calls for the Address of
// this contract will result in calling this `check_auth` function with
// this contract will result in calling this `__check_auth` function with
// the appropriate arguments.
//
// This should return `()` if authentication and authorization checks have
Expand Down
21 changes: 21 additions & 0 deletions simple_account/Cargo.toml
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
[package]
name = "soroban-simple-account-contract"
version = "0.0.0"
authors = ["Stellar Development Foundation <[email protected]>"]
license = "Apache-2.0"
edition = "2021"
publish = false

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

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

[dev_dependencies]
soroban-sdk = { workspace = true, features = ["testutils"] }
soroban-auth = { workspace = true, features = ["testutils"] }
ed25519-dalek = { version = "1.0.1" }
rand = { version = "0.7.3" }
67 changes: 67 additions & 0 deletions simple_account/src/lib.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,67 @@
//! This a minimal exapmle of an account contract.
//!
//! The account is owned by a single ed25519 public key that is also used for
//! authentication.
//!
//! For a more advanced example that demonstrates all the capabilities of the
//! Soroban account contracts see `src/account` example.
#![no_std]

struct SimpleAccount;

use soroban_auth::AuthorizationContext;
use soroban_sdk::{contractimpl, contracttype, BytesN, Env, Vec};

#[derive(Clone)]
#[contracttype]
pub enum DataKey {
Owner,
}

#[contractimpl]
impl SimpleAccount {
// Initialize the contract with an owner's ed25519 public key.
pub fn init(env: Env, public_key: BytesN<32>) {
if env.storage().has(&DataKey::Owner) {
panic!("owner is already set");
}
env.storage().set(&DataKey::Owner, &public_key);
}

// This is the 'entry point' of the account contract and every account
// contract has to implement it. `require_auth` calls for the Address of
// this contract will result in calling this `__check_auth` function with
// the appropriate arguments.
//
// This should return `()` if authentication and authorization checks have
// been passed and return an error (or panic) otherwise.
//
// `__check_auth` takes the payload that needed to be signed, arbitrarily
// typed signatures (`BytesN<64>` type here) and authorization
// context that contains all the invocations that this call tries to verify
// (not used in this example).
//
// In this example `__check_auth` only verifies the signature.
//
// Note, that `__check_auth` function shouldn't call `require_auth` on the
// contract's own address in order to avoid infinite recursion.
#[allow(non_snake_case)]
pub fn __check_auth(
env: Env,
signature_payload: BytesN<32>,
signature_args: Vec<BytesN<64>>,
_auth_context: Vec<AuthorizationContext>,
) {
if signature_args.len() != 1 {
panic!("incorrect number of signature args");
}
let public_key: BytesN<32> = env.storage().get(&DataKey::Owner).unwrap().unwrap();
env.crypto().ed25519_verify(
&public_key,
&signature_payload.into(),
&signature_args.get(0).unwrap().unwrap(),
);
}
}

mod test;
63 changes: 63 additions & 0 deletions simple_account/src/test.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,63 @@
#![cfg(test)]
extern crate std;

use ed25519_dalek::Keypair;
use ed25519_dalek::Signer;
use rand::thread_rng;
use soroban_auth::testutils::EnvAuthUtils;
use soroban_sdk::RawVal;
use soroban_sdk::Status;
use soroban_sdk::{testutils::BytesN as _, vec, BytesN, Env, IntoVal};

use crate::SimpleAccount;
use crate::SimpleAccountClient;

fn generate_keypair() -> Keypair {
Keypair::generate(&mut thread_rng())
}

fn create_account_contract(e: &Env) -> SimpleAccountClient {
SimpleAccountClient::new(e, &e.register_contract(None, SimpleAccount {}))
}

fn sign(e: &Env, signer: &Keypair, payload: &BytesN<32>) -> RawVal {
let signature: BytesN<64> = signer
.sign(payload.to_array().as_slice())
.to_bytes()
.into_val(e);
signature.into_val(e)
}

#[test]
fn test_account() {
let env: Env = Default::default();

let account_contract = create_account_contract(&env);

let signer = generate_keypair();
account_contract.init(&signer.public.to_bytes().into_val(&env));

let payload = BytesN::random(&env);
// `__check_auth` can't be called directly, hence we need to use
// `invoke_account_contract_check_auth` testing utility that emulates being
// called by the Soroban host during a `require_auth` call.
env.invoke_account_contract_check_auth::<Status>(
&account_contract.contract_id,
&payload,
&vec![&env, sign(&env, &signer, &payload)],
&vec![&env],
)
// Unwrap the result to make sure there is no error.
.unwrap();

// Now pass a random bytes array instead of the signature - this should
// result in an error as this is not a valid signature.
assert!(env
.invoke_account_contract_check_auth::<Status>(
&account_contract.contract_id,
&payload,
&vec![&env, BytesN::<64>::random(&env).into()],
&vec![&env],
)
.is_err());
}

0 comments on commit 04a4a6a

Please sign in to comment.