diff --git a/Cargo.lock b/Cargo.lock index 90d47375..0b6427dc 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -921,8 +921,7 @@ dependencies = [ name = "soroban-timelock-contract" version = "0.0.0" dependencies = [ - "ed25519-dalek", - "rand 0.7.3", + "rand 0.8.5", "soroban-auth", "soroban-sdk", ] diff --git a/timelock/Cargo.toml b/timelock/Cargo.toml index 261bb524..4f124ab1 100644 --- a/timelock/Cargo.toml +++ b/timelock/Cargo.toml @@ -11,7 +11,7 @@ crate-type = ["cdylib", "rlib"] doctest = false [features] -testutils = ["soroban-sdk/testutils", "soroban-auth/testutils"] +testutils = ["soroban-sdk/testutils"] [dependencies] soroban-sdk = "0.1.0" @@ -19,6 +19,4 @@ 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" } diff --git a/timelock/src/lib.rs b/timelock/src/lib.rs index 9f57e769..7d1abcee 100644 --- a/timelock/src/lib.rs +++ b/timelock/src/lib.rs @@ -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)] @@ -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, @@ -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 { @@ -107,10 +96,13 @@ 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(); @@ -118,27 +110,21 @@ impl ClaimableBalanceContract { 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); } } @@ -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; diff --git a/timelock/src/test.rs b/timelock/src/test.rs index 389b2e61..440ce75a 100644 --- a/timelock/src/test.rs +++ b/timelock/src/test.rs @@ -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}; @@ -123,19 +122,13 @@ impl ClaimableBalanceTest { claimants: &Vec, 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(); } }