Repository navigation
Expand file tree
/
Copy pathvesting.cell
More file actions
119 lines (105 loc) · 6.36 KB
/
Copy pathvesting.cell
File metadata and controls
119 lines (105 loc) · 6.36 KB
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
module cellscript::vesting
use cellscript::fungible_token::Token
const U64_MAX: u64 = 18446744073709551615
shared VestingConfig has store, create, read_ref {
admin: Address,
token_symbol: [u8; 8],
cliff_period: u64,
total_vesting_period: u64,
revocable: bool,
}
enum VestingState {
Active,
FullyClaimed,
}
receipt VestingGrant has store, create, consume {
state: VestingState,
beneficiary: Address,
total_amount: u64,
claimed_amount: u64,
grant_timepoint: u64,
cliff_timepoint: u64,
end_timepoint: u64,
token_symbol: [u8; 8],
}
flow VestingGrant.state {
initial Active;
terminal FullyClaimed;
Active -> Active;
Active -> FullyClaimed;
}
action create_vesting_config(admin: Address, token_symbol: [u8; 8], cliff_period: u64, total_period: u64, revocable: bool) -> config: VestingConfig {
verification
require cliff_period < total_period, "cliff >= total"
create config = VestingConfig { admin, token_symbol, cliff_period, total_vesting_period: total_period, revocable } with_lock(admin)
}
action grant_vesting(read config: VestingConfig, tokens: Token, beneficiary: Address) -> grant: VestingGrant {
verification
require tokens.symbol == config.token_symbol, "wrong token"
require tokens.amount > 0, "zero grant"
let current_epoch = ckb::header_dep(0).epoch_number
let now = ckb::epoch_number_to_u64(current_epoch)
require config.cliff_period <= U64_MAX - now, "cliff timepoint overflow"
require config.total_vesting_period <= U64_MAX - now, "end timepoint overflow"
let cliff_epoch = ckb::epoch_add(current_epoch, ckb::epoch_duration(config.cliff_period))
let end_epoch = ckb::epoch_add(current_epoch, ckb::epoch_duration(config.total_vesting_period))
consume tokens
create grant = VestingGrant { state: VestingState::Active, beneficiary, total_amount: tokens.amount, claimed_amount: 0, grant_timepoint: now, cliff_timepoint: ckb::epoch_number_to_u64(cliff_epoch), end_timepoint: ckb::epoch_number_to_u64(end_epoch), token_symbol: config.token_symbol } with_lock(beneficiary)
}
action claim_vested(grant: VestingGrant) -> (tokens: Token, updated_grant: VestingGrant) {
verification
let now = ckb::epoch_number_to_u64(ckb::header_dep(0).epoch_number)
require now >= grant.cliff_timepoint, "cliff not reached"
require now < grant.end_timepoint, "use claim_fully_vested after vesting ends"
require grant.state == VestingState::Active, "grant is not active"
let elapsed = now - grant.cliff_timepoint
let vesting_period = grant.end_timepoint - grant.cliff_timepoint
require vesting_period > 0, "invalid vesting period"
let safe_elapsed = if elapsed == 0 { 1 } else { elapsed }
require elapsed == 0 || grant.total_amount <= U64_MAX / safe_elapsed, "vesting calculation overflow"
let vested_total = grant.total_amount * elapsed / vesting_period
require grant.claimed_amount <= vested_total, "claimed amount exceeds vested amount"
let claimable = vested_total - grant.claimed_amount
require claimable > 0, "nothing to claim"
consume grant
create tokens = Token { amount: claimable, symbol: grant.token_symbol } with_lock(grant.beneficiary)
create updated_grant = VestingGrant { state: VestingState::Active, beneficiary: grant.beneficiary, total_amount: grant.total_amount, claimed_amount: grant.claimed_amount + claimable, grant_timepoint: grant.grant_timepoint, cliff_timepoint: grant.cliff_timepoint, end_timepoint: grant.end_timepoint, token_symbol: grant.token_symbol } with_lock(grant.beneficiary)
}
action claim_fully_vested(grant: VestingGrant) -> (tokens: Token, completed_grant: VestingGrant) {
transition grant.state: Active -> completed_grant.state: FullyClaimed
verification
let now = ckb::epoch_number_to_u64(ckb::header_dep(0).epoch_number)
require now >= grant.end_timepoint, "vesting not complete"
require grant.state == VestingState::Active, "grant is not active"
require grant.claimed_amount < grant.total_amount, "nothing to claim"
let claimable = grant.total_amount - grant.claimed_amount
consume grant
create tokens = Token { amount: claimable, symbol: grant.token_symbol } with_lock(grant.beneficiary)
create completed_grant = VestingGrant { state: VestingState::FullyClaimed, beneficiary: grant.beneficiary, total_amount: grant.total_amount, claimed_amount: grant.total_amount, grant_timepoint: grant.grant_timepoint, cliff_timepoint: grant.cliff_timepoint, end_timepoint: grant.end_timepoint, token_symbol: grant.token_symbol } with_lock(grant.beneficiary)
}
action revoke_grant(read config: VestingConfig, grant: VestingGrant, claimed_admin: Address) -> (employee_tokens: Token, admin_tokens: Token) {
verification
require config.revocable, "not revocable"
require is_vesting_admin(config, claimed_admin), "not admin"
require grant.state == VestingState::Active, "grant is not active"
let now = ckb::epoch_number_to_u64(ckb::header_dep(0).epoch_number)
let elapsed = if now >= grant.cliff_timepoint { now - grant.cliff_timepoint } else { 0 }
let vesting_period = grant.end_timepoint - grant.cliff_timepoint
require vesting_period > 0, "invalid vesting period"
let safe_elapsed = if elapsed == 0 { 1 } else { elapsed }
require now >= grant.end_timepoint || elapsed == 0 || grant.total_amount <= U64_MAX / safe_elapsed, "vesting calculation overflow"
let vested = if now >= grant.end_timepoint { grant.total_amount } else { grant.total_amount * elapsed / vesting_period }
require grant.claimed_amount <= vested, "claimed amount exceeds vested amount"
let unclaimed_vested = vested - grant.claimed_amount
let unvested = grant.total_amount - vested
consume grant
create employee_tokens = Token { amount: unclaimed_vested, symbol: grant.token_symbol } with_lock(grant.beneficiary)
create admin_tokens = Token { amount: unvested, symbol: grant.token_symbol } with_lock(claimed_admin)
}
fn is_vesting_admin(config: &VestingConfig, claimed_admin: Address) -> bool {
claimed_admin == config.admin
}
lock vesting_admin(protected config: VestingConfig, witness claimed_admin: Address) -> bool {
verification
require is_vesting_admin(config, claimed_admin)
}