Skip to content

Commit d1cfd69

Browse files
authored
feat: bundled improvements (#45)
1 parent 4b70178 commit d1cfd69

File tree

11 files changed

+449
-60
lines changed

11 files changed

+449
-60
lines changed

README.md

Lines changed: 2 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -3,11 +3,7 @@
33
Contracts of official protocol contracts deployed by the core ZetaChain team to facilitate cross-chain
44
operations using TON (The Open Network) and other chains.
55

6-
## Supported operations
7-
8-
- `deposit` - deposit TON to the Gateway contract
9-
- `deposit_and_call` - deposit TON to the Gateway contract and call a contract on the EVM side
10-
- `withdraw` - withdraw TON from the Gateway contract
6+
[Contracts Documentation](./docs/gateway.md)
117

128
## ⚠️ Important Notice
139

@@ -76,6 +72,7 @@ Currently, a dedicated authority address is used `state::authority_address`
7672
- `update_tss` - update TSS public key
7773
- `update_code` - upgrade the contract code
7874
- `update_authority` - update the authority TON address
75+
- ...
7976

8077
### Withdrawals
8178

contracts/gateway.fc

Lines changed: 103 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -18,21 +18,29 @@ const size::call_data::max = 2048 * 8; ;; 2 Kilobytes of call data
1818
const op::internal::donate = 100;
1919
const op::internal::deposit = 101;
2020
const op::internal::deposit_and_call = 102;
21+
const op::internal::call = 103;
2122

23+
;; tss ops
2224
const op::external::withdraw = 200;
25+
const op::external::increase_seqno = 205;
2326

27+
;; authority ops
2428
const op::authority::set_deposits_enabled = 201;
2529
const op::authority::update_tss = 202;
2630
const op::authority::update_code = 203;
2731
const op::authority::update_authority = 204;
32+
const op::authority::reset_seqno = 206;
2833

2934
;; GAS FEES =========================================
30-
;; These constants are empirically determined based on tests
31-
;; and slighly bumped to cover all other non-gas fees (e.g. storage, fwd_fee, etc.)
35+
;; IMPORTANT: These constants are empirically determined based on tests
36+
;; and slightly bumped to cover all other non-gas fees (e.g. storage, fwd_fee, etc.)
3237
const gas::deposit = 10000;
3338
const gas::deposit_and_call = 13000;
39+
const gas::call = 10000;
40+
3441
const gas::authority = 20000;
35-
const gas::withdraw = 17500;
42+
43+
const gas::external = 17500;
3644

3745
;; PARSING =========================================
3846

@@ -146,6 +154,30 @@ const gas::withdraw = 17500;
146154
send_log_message(log);
147155
}
148156

157+
;; handles zeta's onCall method by ensuring call_data size and gas costs are covered;
158+
;;
159+
;; NOTE that this operation DOESN'T rebate sent amount if it's bigger than tx fee!
160+
;; We can consider sending surplus amount back in the future improvements.
161+
;; For now, send amount that is equal to calculate_gas_fee(op::call)
162+
() handle_call(int amount, slice in_msg_body) impure inline {
163+
load_state();
164+
guard_deposits();
165+
166+
throw_if(error::invalid_evm_recipient, in_msg_body.slice_bits() < size::evm_address);
167+
in_msg_body~load_uint(size::evm_address);
168+
169+
throw_if(error::invalid_call_data, in_msg_body.slice_refs_empty?());
170+
cell call_data = in_msg_body~load_ref();
171+
guard_cell_size(call_data, size::call_data::max, error::invalid_call_data);
172+
173+
int tx_fee = get_gas_fee_workchain(gas::call);
174+
throw_if(error::insufficient_value, amount < tx_fee);
175+
176+
;; state::total_locked is NOT changed.
177+
178+
mutate_state();
179+
}
180+
149181
;; Enables or disables deposits.
150182
() handle_set_deposits_enabled(slice sender, slice message) impure inline {
151183
load_state();
@@ -184,6 +216,20 @@ const gas::withdraw = 17500;
184216
mutate_state();
185217
}
186218

219+
;; Resets the seqno to the specified value
220+
;; handles reset_seqno (uint32 new_seqno)
221+
() handle_reset_seqno(slice sender, slice message) impure inline {
222+
load_state();
223+
224+
guard_authority_sender(sender);
225+
226+
int new_seqno = message~load_uint(size::seqno);
227+
228+
state::seqno = new_seqno;
229+
230+
mutate_state();
231+
}
232+
187233
() handle_update_authority(slice sender, slice message) impure inline {
188234
load_state();
189235

@@ -235,6 +281,10 @@ const gas::withdraw = 17500;
235281
return handle_deposit_and_call(msg_value, in_msg_body);
236282
}
237283

284+
if (op == op::internal::call) {
285+
return handle_call(msg_value, in_msg_body);
286+
}
287+
238288
int tx_fee_authority = get_gas_fee_workchain(gas::authority);
239289
throw_if(error::insufficient_value, msg_value < tx_fee_authority);
240290

@@ -250,6 +300,10 @@ const gas::withdraw = 17500;
250300
return handle_update_code(sender, in_msg_body);
251301
}
252302

303+
if (op == op::authority::reset_seqno) {
304+
return handle_reset_seqno(sender, in_msg_body);
305+
}
306+
253307
if (op == op::authority::update_authority) {
254308
return handle_update_authority(sender, in_msg_body);
255309
}
@@ -285,7 +339,7 @@ cell auth::ecdsa::external(slice message, slice expected_evm_address) inline {
285339

286340
;; Withdraws assets to the recipient
287341
;;
288-
;; handle_withdrawal (MsgAddr recipient, Coins amount, int seqno)
342+
;; handle_withdrawal (MsgAddr recipient, Coins amount, uint32 seqno)
289343
() handle_withdrawal(slice payload) impure inline {
290344
;; load the body
291345
slice recipient = payload~load_msg_addr();
@@ -300,7 +354,7 @@ cell auth::ecdsa::external(slice message, slice expected_evm_address) inline {
300354
throw_if(error::insufficient_value, amount == 0);
301355
throw_if(error::invalid_seqno, seqno != state::seqno);
302356

303-
int tx_fee = get_gas_fee_workchain(gas::withdraw);
357+
int tx_fee = get_gas_fee_workchain(gas::external);
304358

305359
;; edge-case: make sure gw has enough coins when having low funds
306360
throw_if(error::insufficient_value, state::total_locked < (amount + tx_fee));
@@ -311,14 +365,41 @@ cell auth::ecdsa::external(slice message, slice expected_evm_address) inline {
311365
state::total_locked -= (amount + tx_fee);
312366
state::seqno += 1;
313367

368+
;; Sent TON and mutate the state
314369
mutate_state();
315370
commit();
316371

317-
;; Sent TON and mutate the state
318372
int send_mode = message::flag::pay_fees_separately;
319373
send_simple_message_non_bounceable(recipient_addr, amount, send_mode);
320374
}
321375

376+
;; Increases seqno by 1 without doing any other operations.
377+
;; handle_increase_seqno (uint32 failure_reason, uint32 seqno)
378+
() handle_increase_seqno(slice payload) impure inline {
379+
;; load the body
380+
int increase_reason = payload~load_uint(size::seqno);
381+
382+
;; note that increase_reason is an arbitrary number defined by the protocol
383+
;; and is used to identify the reason for the increase (via parsing input message OR debug logs)
384+
~strdump("increase_reason");
385+
increase_reason~dump();
386+
387+
int seqno = payload~load_uint(size::seqno);
388+
389+
throw_if(error::invalid_seqno, seqno != state::seqno);
390+
391+
int tx_fee = get_gas_fee_workchain(gas::external);
392+
throw_if(error::insufficient_value, state::total_locked < tx_fee);
393+
394+
;; accept the message, the contract agrees to pay gas fees
395+
accept_message();
396+
397+
state::total_locked -= tx_fee;
398+
state::seqno += 1;
399+
400+
mutate_state();
401+
}
402+
322403
;; Entry point for all external messages
323404
() recv_external(slice message) impure {
324405
load_state();
@@ -331,6 +412,10 @@ cell auth::ecdsa::external(slice message, slice expected_evm_address) inline {
331412
return handle_withdrawal(payload);
332413
}
333414

415+
if (op == op::external::increase_seqno) {
416+
return handle_increase_seqno(payload);
417+
}
418+
334419
throw(error::unknown_op);
335420
}
336421

@@ -353,7 +438,7 @@ cell auth::ecdsa::external(slice message, slice expected_evm_address) inline {
353438
);
354439
}
355440

356-
;; get nonce (int32)
441+
;; get seqno [nonce] (int32)
357442
int seqno() method_id {
358443
load_state();
359444

@@ -370,17 +455,25 @@ int calculate_gas_fee(int op) method_id {
370455
return get_gas_fee_workchain(gas::deposit_and_call);
371456
}
372457

458+
if (op == op::internal::call) {
459+
return get_gas_fee_workchain(gas::call);
460+
}
461+
373462
int is_authority_op = (op == op::authority::set_deposits_enabled)
374463
| (op == op::authority::update_tss)
375464
| (op == op::authority::update_code)
376-
| (op == op::authority::update_authority);
465+
| (op == op::authority::update_authority)
466+
| (op == op::authority::reset_seqno);
377467

378468
if (is_authority_op) {
379469
return get_gas_fee_workchain(gas::authority);
380470
}
381471

382-
if (op == op::external::withdraw) {
383-
return get_gas_fee_workchain(gas::withdraw);
472+
int is_external_op = (op == op::external::withdraw)
473+
| (op == op::external::increase_seqno);
474+
475+
if (is_external_op) {
476+
return get_gas_fee_workchain(gas::external);
384477
}
385478

386479
throw(error::unknown_op);

crypto/ecdsa.ts

Lines changed: 22 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,14 +1,33 @@
11
import { beginCell, Cell, Slice } from '@ton/core';
22
import { ethers } from 'ethers';
33

4+
// Arbitrary signer
5+
export type Signer = (hash: Buffer) => { v: number; r: string; s: string };
6+
7+
/**
8+
* Creates a signer from an ethers.js wallet
9+
* @param wallet - ethers.js wallet
10+
* @returns Signer
11+
*/
12+
export function signerFromEthersWallet(wallet: ethers.Wallet): Signer {
13+
return (hash: Buffer) => {
14+
const sig = wallet.signingKey.sign(hash);
15+
16+
return {
17+
v: Number(sig.v),
18+
r: sig.r,
19+
s: sig.s,
20+
};
21+
};
22+
}
23+
424
/**
525
* Signs a cell with secp256k1 signature into a Slice (65 bytes)
626
* @param signer
727
* @param cell
828
*/
9-
export function ecdsaSignCell(signer: ethers.Wallet, cell: Cell): Slice {
10-
const hash = cell.hash();
11-
const sig = signer.signingKey.sign(hash);
29+
export function ecdsaSignCell(signer: Signer, cell: Cell): Slice {
30+
const sig = signer(cell.hash());
1231

1332
// https://docs.ton.org/learn/tvm-instructions/instructions
1433
//

docs/gateway.md

Lines changed: 42 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -4,14 +4,17 @@
44

55
### Constants
66

7-
- **op::internal::donate** = 100
8-
- **op::internal::deposit** = 101
9-
- **op::internal::deposit_and_call** = 102
10-
- **op::external::withdraw** = 200
11-
- **op::authority::set_deposits_enabled** = 201
12-
- **op::authority::update_tss** = 202
13-
- **op::authority::update_code** = 203
14-
- **op::authority::update_authority** = 204
7+
- **op::internal::donate** = 100
8+
- **op::internal::deposit** = 101
9+
- **op::internal::deposit_and_call** = 102
10+
- **op::internal::call** = 103
11+
- **op::external::withdraw** = 200
12+
- **op::external::increase_seqno** = 205
13+
- **op::authority::set_deposits_enabled** = 201
14+
- **op::authority::update_tss** = 202
15+
- **op::authority::update_code** = 203
16+
- **op::authority::update_authority** = 204
17+
- **op::authority::reset_seqno** = 206
1518

1619
### `handle_deposit`
1720

@@ -21,6 +24,18 @@
2124

2225
Deposit TON to the gateway and specify the EVM recipient on ZetaChain
2326

27+
### `handle_call`
28+
29+
```func
30+
() handle_call(int amount, slice in_msg_body) impure inline {
31+
```
32+
33+
Handles zeta's onCall method by ensuring call_data size and gas costs are covered;
34+
35+
NOTE that this operation DOESN'T rebate sent amount if it's bigger than tx fee!
36+
We can consider sending surplus amount back in the future improvements.
37+
For now, send amount that is equal to calculate_gas_fee(op::call)
38+
2439
### `handle_set_deposits_enabled`
2540

2641
```func
@@ -47,6 +62,15 @@ Wrong TSS address leads to loss of funds.
4762
Updated the code of the contract
4863
Handle_code_update (cell new_code)
4964

65+
### `handle_reset_seqno`
66+
67+
```func
68+
() handle_reset_seqno(slice sender, slice message) impure inline {
69+
```
70+
71+
Resets the seqno to the specified value
72+
Handles reset_seqno (uint32 new_seqno)
73+
5074
### `recv_internal`
5175

5276
```func
@@ -63,7 +87,16 @@ Input for all internal messages
6387

6488
Withdraws assets to the recipient
6589

66-
Handle_withdrawal (MsgAddr recipient, Coins amount, int seqno)
90+
Handle_withdrawal (MsgAddr recipient, Coins amount, uint32 seqno)
91+
92+
### `handle_increase_seqno`
93+
94+
```func
95+
() handle_increase_seqno(slice payload) impure inline {
96+
```
97+
98+
Increases seqno by 1 without doing any other operations.
99+
Handle_increase_seqno (uint32 failure_reason, uint32 seqno)
67100

68101
### `recv_external`
69102

package.json

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -32,6 +32,9 @@
3232
"typescript": "^5.5.3"
3333
},
3434
"dependencies": {
35+
"@ton/core": "~0",
36+
"@ton/crypto": "^3.2.0",
37+
"@ton/ton": "^15.1.0",
3538
"ethers": "^6.13.2"
3639
},
3740
"packageManager": "[email protected]+sha1.1959a18351b811cdeedbd484a8f86c3cc3bbaf72"

0 commit comments

Comments
 (0)