-
Notifications
You must be signed in to change notification settings - Fork 78
/
Copy pathlib.rs
127 lines (111 loc) · 3.88 KB
/
lib.rs
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
//! This contract demonstrates 'timelock' concept and implements a
//! greatly simplified Claimable Balance (similar to
//! 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]
use soroban_sdk::{contract, contractimpl, contracttype, token, Address, Env, Vec};
#[derive(Clone)]
#[contracttype]
pub enum DataKey {
Init,
Balance,
}
#[derive(Clone)]
#[contracttype]
pub enum TimeBoundKind {
Before,
After,
}
#[derive(Clone)]
#[contracttype]
pub struct TimeBound {
pub kind: TimeBoundKind,
pub timestamp: u64,
}
#[derive(Clone)]
#[contracttype]
pub struct ClaimableBalance {
pub token: Address,
pub amount: i128,
pub claimants: Vec<Address>,
pub time_bound: TimeBound,
}
#[contract]
pub struct ClaimableBalanceContract;
// The 'timelock' part: check that provided timestamp is before/after
// the current ledger timestamp.
fn check_time_bound(env: &Env, time_bound: &TimeBound) -> bool {
let ledger_timestamp = env.ledger().timestamp();
match time_bound.kind {
TimeBoundKind::Before => ledger_timestamp <= time_bound.timestamp,
TimeBoundKind::After => ledger_timestamp >= time_bound.timestamp,
}
}
#[contractimpl]
impl ClaimableBalanceContract {
pub fn deposit(
env: Env,
from: Address,
token: Address,
amount: i128,
claimants: Vec<Address>,
time_bound: TimeBound,
) {
if claimants.len() > 10 {
panic!("too many claimants");
}
if is_initialized(&env) {
panic!("contract has been already initialized");
}
// Make sure `from` address authorized the deposit call with all the
// arguments.
from.require_auth();
// Transfer token from `from` to this contract address.
token::Client::new(&env, &token).transfer(&from, &env.current_contract_address(), &amount);
// Store all the necessary info to allow one of the claimants to claim it.
env.storage().instance().set(
&DataKey::Balance,
&ClaimableBalance {
token,
amount,
time_bound,
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.storage().instance().set(&DataKey::Init, &());
}
pub fn claim(env: Env, claimant: Address) {
// Make sure claimant has authorized this call, which ensures their
// identity.
claimant.require_auth();
// Just get the balance - if it's been claimed, this will simply panic
// and terminate the contract execution.
let claimable_balance: ClaimableBalance =
env.storage().instance().get(&DataKey::Balance).unwrap();
if !check_time_bound(&env, &claimable_balance.time_bound) {
panic!("time predicate is not fulfilled");
}
let claimants = &claimable_balance.claimants;
if !claimants.contains(&claimant) {
panic!("claimant is not allowed to claim this balance");
}
// Transfer the stored amount of token to claimant after passing
// all the checks.
token::Client::new(&env, &claimable_balance.token).transfer(
&env.current_contract_address(),
&claimant,
&claimable_balance.amount,
);
// Remove the balance entry to prevent any further claims.
env.storage().instance().remove(&DataKey::Balance);
}
}
fn is_initialized(env: &Env) -> bool {
env.storage().instance().has(&DataKey::Init)
}
mod test;