Skip to content

Commit

Permalink
Simplified auth in timelock example. (#148)
Browse files Browse the repository at this point in the history
  • Loading branch information
dmkozh authored Oct 11, 2022
1 parent 2499ed4 commit a7bab42
Show file tree
Hide file tree
Showing 4 changed files with 44 additions and 80 deletions.
3 changes: 1 addition & 2 deletions Cargo.lock

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

6 changes: 2 additions & 4 deletions timelock/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -11,14 +11,12 @@ crate-type = ["cdylib", "rlib"]
doctest = false

[features]
testutils = ["soroban-sdk/testutils", "soroban-auth/testutils"]
testutils = ["soroban-sdk/testutils"]

[dependencies]
soroban-sdk = "0.1.0"
soroban-auth = "0.1.0"

[dev_dependencies]
soroban-sdk = { version = "0.1.0", features = ["testutils"] }
soroban-auth = { version = "0.1.0", features = ["testutils"] }
rand = { version = "0.7.3" }
ed25519-dalek = { version = "1.0.1" }
rand = { version = "0.8.5" }
100 changes: 37 additions & 63 deletions timelock/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -3,25 +3,24 @@
//! https://developers.stellar.org/docs/glossary/claimable-balance).
//! The contract allows to deposit some amount of token and allow another
//! account(s) claim it before or after provided time point.
//! For simplicity, the contract only supports invoker-based auth.
#![no_std]
#[cfg(feature = "testutils")]
extern crate std;

use soroban_auth::{
verify, {Identifier, Signature},
};
use soroban_sdk::{contractimpl, contracttype, BigInt, BytesN, Env, Symbol, Vec};
use soroban_sdk::{contractimpl, contracttype, Address, BigInt, BytesN, Env, Vec};

mod token {
soroban_sdk::contractimport!(file = "../soroban_token_spec.wasm");
}

use token::{Identifier, Signature};

#[derive(Clone)]
#[contracttype]
pub enum DataKey {
Init,
Balance,
Nonce(Identifier),
}

#[derive(Clone)]
Expand Down Expand Up @@ -63,16 +62,15 @@ fn check_time_bound(env: &Env, time_bound: &TimeBound) -> bool {
// Contract usage pattern (pseudocode):
// 1. Depositor calls `token.approve(depositor_auth, claimable_balance_contract, 100)`
// to allow contract to withdraw the needed amount of token.
// 2. Depositor calls `deposit(depositor_auth, token_id, 100, claimants, time bound)`. Contract
// 2. Depositor calls `deposit(token_id, 100, claimants, time bound)`. Contract
// withdraws the provided token amount and stores it until one of the claimants
// claims it.
// 3. Claimant calls `claim(claimant_auth)` and if time/auth conditons are passed
// 3. Claimant calls `claim()` and if time/auth conditons are passed
// receives the balance.
#[contractimpl]
impl ClaimableBalanceContract {
pub fn deposit(
env: Env,
from: Signature,
token: BytesN<32>,
amount: BigInt,
claimants: Vec<Identifier>,
Expand All @@ -85,19 +83,10 @@ impl ClaimableBalanceContract {
panic!("contract has been already initialized");
}

verify_and_consume_nonce(&env, &from, &BigInt::zero(&env));

let from_id = from.identifier(&env);

verify(
&env,
&from,
Symbol::from_str("deposit"),
(&from_id, &token, &amount, &claimants, &time_bound),
);
let from_id = address_to_id(env.invoker());
// Transfer token to this contract address.
transfer_from(&env, &token, &from_id, &get_contract_id(&env), &amount);
// Store all the necessary balance to allow one of the claimants to claim it.
transfer_from_account_to_contract(&env, &token, &from_id, &amount);
// Store all the necessary info to allow one of the claimants to claim it.
env.data().set(
DataKey::Balance,
ClaimableBalance {
Expand All @@ -107,38 +96,35 @@ impl ClaimableBalanceContract {
claimants,
},
);
// Mark contract as initialized to prevent double-usage.
// Note, that this is just one way to approach initialization - it may
// be viable to allow one contract to manage several claimable balances.
env.data().set(DataKey::Init, ());
}

pub fn claim(env: Env, claimant: Signature) {
pub fn claim(env: Env) {
let claimable_balance: ClaimableBalance =
env.data().get_unchecked(DataKey::Balance).unwrap();

if !check_time_bound(&env, &claimable_balance.time_bound) {
panic!("time predicate is not fulfilled");
}

let claimant_id = claimant.identifier(&env);
let claimant_id = address_to_id(env.invoker());
let claimants = &claimable_balance.claimants;
if !claimants.contains(&claimant_id) {
panic!("claimant is not allowed to claim this balance");
}

verify_and_consume_nonce(&env, &claimant, &BigInt::zero(&env));

// Authenticate claimant with nonce of zero, so that this may be
// successfully called just once.
// For simplicity, depositor can't be the claimant.
verify(&env, &claimant, Symbol::from_str("claim"), (&claimant_id,));
// Transfer the stored amount of token to claimant after passing
// all the checks.
transfer_to(
transfer_from_contract_to_account(
&env,
&claimable_balance.token,
&claimant_id,
&claimable_balance.amount,
);
// Cleanup unnecessary balance entry.
// Remove the balance entry to prevent any further claims.
env.data().remove(DataKey::Balance);
}
}
Expand All @@ -151,49 +137,37 @@ fn get_contract_id(e: &Env) -> Identifier {
Identifier::Contract(e.get_current_contract().into())
}

fn transfer_from(
fn address_to_id(address: Address) -> Identifier {
match address {
Address::Account(a) => Identifier::Account(a),
Address::Contract(c) => Identifier::Contract(c),
}
}

fn transfer_from_account_to_contract(
e: &Env,
token_id: &BytesN<32>,
from: &Identifier,
to: &Identifier,
amount: &BigInt,
) {
let client = token::Client::new(&e, token_id);
client.xfer_from(&Signature::Invoker, &BigInt::zero(&e), &from, &to, &amount);
client.xfer_from(
&Signature::Invoker,
&BigInt::zero(e),
&from,
&get_contract_id(e),
&amount,
);
}

fn transfer_to(e: &Env, token_id: &BytesN<32>, to: &Identifier, amount: &BigInt) {
fn transfer_from_contract_to_account(
e: &Env,
token_id: &BytesN<32>,
to: &Identifier,
amount: &BigInt,
) {
let client = token::Client::new(&e, token_id);
client.xfer(&Signature::Invoker, &BigInt::zero(&e), to, amount);
}

fn read_nonce(e: &Env, id: &Identifier) -> BigInt {
let key = DataKey::Nonce(id.clone());
e.data()
.get(key)
.unwrap_or_else(|| Ok(BigInt::zero(e)))
.unwrap()
}

fn verify_and_consume_nonce(e: &Env, auth: &Signature, expected_nonce: &BigInt) {
match auth {
Signature::Invoker => {
if BigInt::zero(&e) != expected_nonce {
panic!("nonce should be zero for Invoker")
}
return;
}
_ => {}
}

let id = auth.identifier(&e);
let key = DataKey::Nonce(id.clone());
let nonce = read_nonce(e, &id);

if nonce != expected_nonce {
panic!("incorrect nonce")
}
e.data().set(key, &nonce + 1);
}

mod test;
15 changes: 4 additions & 11 deletions timelock/src/test.rs
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,6 @@

use super::*;
use rand::{thread_rng, RngCore};
use soroban_auth::Identifier;
use soroban_sdk::testutils::{Accounts, Ledger, LedgerInfo};
use soroban_sdk::{vec, AccountId, Env, IntoVal};
use token::{Client as TokenClient, TokenMetadata};
Expand Down Expand Up @@ -123,19 +122,13 @@ impl ClaimableBalanceTest {
claimants: &Vec<Identifier>,
time_bound: &TimeBound,
) {
self.contract.with_source_account(account_id).deposit(
&Signature::Invoker,
token,
amount,
claimants,
time_bound,
);
self.contract
.with_source_account(account_id)
.deposit(token, amount, claimants, time_bound);
}

fn call_claim(&self, account_id: &AccountId) {
self.contract
.with_source_account(account_id)
.claim(&Signature::Invoker);
self.contract.with_source_account(account_id).claim();
}
}

Expand Down

0 comments on commit a7bab42

Please sign in to comment.