-
Notifications
You must be signed in to change notification settings - Fork 314
/
Copy pathsimple-subscription-plugin.fc
179 lines (157 loc) · 7.17 KB
/
simple-subscription-plugin.fc
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
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
#pragma version =0.2.0;
;; Simple subscription plugin for wallet-v4
;; anyone can ask to send a subscription payment
(int) slice_data_equal?(slice s1, slice s2) asm "SDEQ";
int op:destruct() asm "0x64737472 PUSHINT";
int op:payment_request() asm "0x706c7567 PUSHINT";
int op:fallback() asm "0x756e6b77 PUSHINT";
int op:subscription() asm "0x73756273 PUSHINT";
int max_failed_attempts() asm "2 PUSHINT";
int max_reserved_funds() asm "67108864 PUSHINT"; ;; 0.0671 TON
int short_msg_fwd_fee(int workchain) inline {
int config_index = 25 + workchain;
int lump_price = config_param(config_index).begin_parse().skip_bits(8).preload_uint(64);
return lump_price;
}
int gas_to_coins(int workchain, int gas) inline_ref {
int config_index = 21 + workchain;
var cs = config_param(config_index).begin_parse();
if (cs.preload_uint(8) == 0xd1) { ;; gas_flat_pfx
cs~skip_bits(8 + 64 + 64);
}
int tag = cs~load_uint(8);
throw_unless(71, (tag == 0xdd) | (tag == 0xde)); ;; gas_prices or gas_prices_ext
int gas_price = cs~load_uint(64);
return (gas * gas_price) >> 16;
}
;; storage$_ wallet:MsgAddressInt
;; beneficiary:MsgAddressInt
;; amount:Grams
;; period:uint32 start_time:uint32 timeout:uint32
;; last_payment_time:uint32
;; last_request_time:uint32
;; failed_attempts:uint8
;; subscription_id:uint32 = Storage;
(slice, slice, int, int, int, int, int, int, int, int) load_storage() impure inline_ref {
var ds = get_data().begin_parse();
return (ds~load_msg_addr(), ds~load_msg_addr(), ds~load_grams(),
ds~load_uint(32), ds~load_uint(32), ds~load_uint(32),
ds~load_uint(32), ds~load_uint(32), ds~load_uint(8), ds~load_uint(32));
}
() save_storage(slice wallet, slice beneficiary, int amount,
int period, int start_time, int timeout, int last_payment_time,
int last_request_time, int failed_attempts, int subscription_id) impure inline_ref {
set_data(begin_cell()
.store_slice(wallet)
.store_slice(beneficiary)
.store_grams(amount)
.store_uint(period, 32)
.store_uint(start_time, 32)
.store_uint(timeout, 32)
.store_uint(last_payment_time, 32)
.store_uint(last_request_time, 32)
.store_uint(failed_attempts, 8)
.store_uint(subscription_id, 32) ;; to differ subscriptions to the same beneficiary (acts as a nonce)
.end_cell());
}
() forward_funds(slice beneficiary, int self_destruct?, int op) impure inline_ref {
if ~(self_destruct?) {
raw_reserve(max_reserved_funds(), 2); ;; reserve at most `max_reserved_funds` nanocoins
}
var msg = begin_cell()
.store_uint(0x10, 6) ;; non-bounce message
.store_slice(beneficiary)
.store_grams(0)
.store_dict(pair_second(get_balance()))
.store_uint(0, 4 + 4 + 64 + 32 + 1 + 1)
.store_uint(op, 32);
int mode = 128; ;; carry all the remaining balance of the current smart contract
if (self_destruct?) {
mode += 32; ;; must be destroyed if its resulting balance is zero
}
send_raw_message(msg.end_cell(), mode);
}
() request_payment(slice wallet, int requested_amount) impure inline_ref {
(int wc, _) = wallet.parse_std_addr();
int amount = gas_to_coins(wc, 15000) + short_msg_fwd_fee(wc);
var msg = begin_cell().store_uint(0x18, 6)
.store_slice(wallet)
.store_grams(amount)
.store_uint(0, 1 + 4 + 4 + 64 + 32 + 1 + 1)
.store_uint(op:payment_request(), 32) ;; request op
.store_uint(cur_lt(), 64) ;; query_id
.store_grams(requested_amount)
.store_uint(0, 1); ;; empty extra
send_raw_message(msg.end_cell(), 3);
}
() self_destruct(slice wallet, slice beneficiary) impure {
;; send event to wallet
(int wc, _) = wallet.parse_std_addr();
int amount = gas_to_coins(wc, 10000);
var msg = begin_cell().store_uint(0x10, 6) ;; non-bounce - we dont need answer from wallet
.store_slice(wallet)
.store_grams(amount)
.store_uint(0, 1 + 4 + 4 + 64 + 32 + 1 + 1)
.store_uint(op:destruct(), 32) ;; request op
.store_uint(cur_lt(), 64); ;; query_id
send_raw_message(msg.end_cell(), 3);
;; forward all the remaining funds to the beneficiary & destroy
forward_funds(beneficiary, true, op:destruct());
}
() recv_internal(int msg_value, cell in_msg_cell, slice in_msg) impure {
var (wallet, beneficiary, amount, period, start_time, timeout, last_payment_time, last_request_time, failed_attempts, subscription_id) = load_storage();
var cs = in_msg_cell.begin_parse();
var flags = cs~load_uint(4);
slice s_addr = cs~load_msg_addr();
if (slice_data_equal?(s_addr, beneficiary)) {
int op = in_msg~load_uint(32);
if (op == op:destruct()) {
;; end subscription
return self_destruct(wallet, beneficiary);
}
return forward_funds(beneficiary, false, op:fallback());
}
if (~ slice_data_equal?(s_addr, wallet)) {
return forward_funds(beneficiary, false, op:fallback());
}
if (in_msg.slice_bits() < 32) {
return forward_funds(beneficiary, false, op:fallback());
}
int op = in_msg~load_uint(32);
if (op == (op:payment_request() | 0x80000000)) {
int last_timeslot = (last_payment_time - start_time) / period;
int cur_timeslot = (now() - start_time) / period;
throw_if(49, last_timeslot >= cur_timeslot);
(int from_wc, _) = s_addr.parse_std_addr();
if (msg_value >= amount - short_msg_fwd_fee(from_wc) ) {
last_payment_time = now();
failed_attempts = 0;
forward_funds(beneficiary, false, op:subscription());
}
return save_storage(wallet, beneficiary, amount, period, start_time, timeout, last_payment_time, last_request_time, failed_attempts, subscription_id);
}
if (op == op:destruct()) { ;; self-destruct
;; forward all the remaining funds to the beneficiary & destroy
return forward_funds(beneficiary, true, op:destruct());
}
}
() recv_external(slice in_msg) impure {
var (wallet, beneficiary, amount, period, start_time, timeout, last_payment_time, last_request_time, failed_attempts, subscription_id) = load_storage();
int last_timeslot = (last_payment_time - start_time) / period;
int cur_timeslot = (now() - start_time) / period;
throw_unless(30, (cur_timeslot > last_timeslot) & (last_request_time + timeout < now())); ;; too early request
accept_message();
if (failed_attempts >= max_failed_attempts()) {
self_destruct(wallet, beneficiary);
} else {
request_payment(wallet, amount);
failed_attempts += 1;
}
save_storage(wallet, beneficiary, amount, period, start_time, timeout, last_payment_time, now(), failed_attempts, subscription_id);
}
;; Get methods
([int, int], [int, int], int, int, int, int, int, int, int, int) get_subscription_data() method_id {
var (wallet, beneficiary, amount, period, start_time, timeout, last_payment_time, last_request_time, failed_attempts, subscription_id) = load_storage();
return (pair(parse_std_addr(wallet)), pair(parse_std_addr(beneficiary)),
amount, period, start_time, timeout, last_payment_time, last_request_time, failed_attempts, subscription_id);
}