Skip to content

Commit

Permalink
feat!: improve deposits (#8)
Browse files Browse the repository at this point in the history
  • Loading branch information
swift1337 authored Aug 29, 2024
1 parent 9f1fe40 commit 8f3a373
Show file tree
Hide file tree
Showing 7 changed files with 258 additions and 93 deletions.
4 changes: 2 additions & 2 deletions contracts/common/crypto.fc
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@
#include "../imports/stdlib.fc";

;; Returs keccak256 hash of the data as uint256
int hash_keccak256(builder b, int n) asm "HASHEXT_KECCAK256";
int hash_keccak256(builder b) asm "1 INT HASHEXT_KECCAK256";

;; Recovers public key from ECDSA signature. Costs 1526 gas.
;;
Expand Down Expand Up @@ -55,7 +55,7 @@ int normalize_ecdsa_recovery_id(int v) inline {
int pub_key_hash = begin_cell()
.store_uint(x1, 256)
.store_uint(x2, 256)
.hash_keccak256(1);
.hash_keccak256();

slice actual_evm_address = begin_cell()
.store_uint(pub_key_hash, 256)
Expand Down
7 changes: 3 additions & 4 deletions contracts/common/errors.fc
Original file line number Diff line number Diff line change
Expand Up @@ -8,11 +8,10 @@ const error::no_intent = 101;
;; Unknown opcode
const error::unknown_op = 102;

;; No memoe provided
const error::memo_is_empty = 103;
;; Invalid EVM recipient (should be 20 bytes slice)
const error::invalid_evm_recipient = 103;

;; Memo is too short (less than 20 bytes)
const error::memo_is_too_short = 104;
const error::invalid_call_data = 104;

;; We only want to support workchain 0 aka "basechain"
const error::wrong_workchain = 105;
Expand Down
4 changes: 2 additions & 2 deletions contracts/common/state.fc
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@

const size::deposits_enabled = 1;
const size::seqno = 32;
const size::tss_address = 160; ;; 20 bytes
const size::evm_address = 160; ;; 20 bytes

;; Boolean flag to enable/disable deposits
global int state::deposits_enabled;
Expand All @@ -28,7 +28,7 @@ global slice state::tss_address;
state::fees = cs~load_coins();

state::seqno = cs~load_uint(size::seqno);
state::tss_address = cs~load_bits(size::tss_address);
state::tss_address = cs~load_bits(size::evm_address);
}

() mutate_state() impure inline_ref {
Expand Down
110 changes: 82 additions & 28 deletions contracts/gateway.fc
Original file line number Diff line number Diff line change
Expand Up @@ -9,13 +9,14 @@
;; Sizes ===========================================
const size::op_code_size = 32;
const size::query_id_size = 64;
const size::memo_min = 20 * 8;
const size::signature_size = 65 * 8;
const size::hash_size = 32 * 8;
const size::call_data::max = 2048 * 8; ;; 2 Kilobytes of call data

;; OP CODES ========================================
const op::internal::deposit = 100;
const op::internal::donate = 101;
const op::internal::donate = 100;
const op::internal::deposit = 101;
const op::internal::deposit_and_call = 102;

const op::external::withdraw = 200;
const op::external::set_deposits_enabled = 201;
Expand All @@ -30,11 +31,11 @@ const deposit_gas_fee = 10000000; ;; 0.01 TON

;; parses the query from the message body
;; https://docs.ton.org/develop/smart-contracts/guidelines/internal-messages#internal-message-body
(int, int) parse_intent(slice message_body) impure inline {
(slice, (int, int)) ~parse_intent(slice message_body) impure inline {
int op = message_body~load_uint(size::op_code_size);
int query_id = message_body~load_uint(size::query_id_size);

return (op, query_id);
return (message_body, (op, query_id));
}

;; parses full message and returns (int flags, slice sender, slice rest_of_the_message)
Expand All @@ -55,36 +56,82 @@ const deposit_gas_fee = 10000000; ;; 0.01 TON

;; INTERNAL MESSAGES ===============================

;; deposit TON to the gateway
() handle_deposit(slice sender, int amount, cell memo_cell) impure inline {
;; Validate memo
slice memo = memo_cell.begin_parse();
;; Checks that deposits are enabled
() guard_deposits() impure inline_ref {
throw_if(error::deposits_disabled, state::deposits_enabled == 0);
}

;; Protects gas usage against huge payloads
() guard_cell_size(cell data, int max_size_bits, int throw_error) impure inline {
int max_size_cells = (max_size_bits / 1023) + 1;

;; note that if count(child cells) > max_size_cells, TVM will exit with code 7;
(_, int data_bits, _) = compute_data_size(data, max_size_cells);

if (data_bits > max_size_bits) {
~strdump("cell size is too big: [got, want]");
data_bits~dump();
max_size_bits~dump();

throw(throw_error);
}
}

;; EVM address is 20 bytes, so the memo should be at least 20 bytes long
throw_if(error::memo_is_too_short, memo.slice_bits() < size::memo_min);
;; todo check if memo is too long. We don't want to have DDOS due to long memos (gas > 0.01 TON)
;; deposit TON to the gateway and specify the EVM recipient on ZetaChain
() handle_deposit(slice sender, int amount, int evm_recipient) impure inline {
load_state();
guard_deposits();

;; Update state
var deposit_amount = amount - deposit_gas_fee;
int deposit_amount = amount - deposit_gas_fee;

state::total_locked += deposit_amount;
state::fees += deposit_gas_fee;

;; Update the state
mutate_state();

;; Logs `$sender deposited $deposit_amount TON with $memo`
;; Logs `$sender deposited $deposit_amount TON to $evm_recipient`
cell log = begin_cell()
.store_uint(op::internal::deposit, size::op_code_size)
.store_uint(0, size::query_id_size)
.store_slice(sender)
.store_coins(deposit_amount)
.store_ref(memo_cell)
.store_uint(evm_recipient, size::evm_address)
.end_cell();

send_log_message(log);
}

() handle_deposit_and_call(slice sender, int amount, int evm_recipient, cell call_data) impure inline {
load_state();
guard_deposits();
guard_cell_size(call_data, size::call_data::max, error::invalid_call_data);

int deposit_amount = amount - deposit_gas_fee;

state::total_locked += deposit_amount;
state::fees += deposit_gas_fee;

mutate_state();

;; todo Should we remove the call_data from the log?
;; todo Removing it reduces gas usage by ~x2.5 (wow!)

;; todo Should we completely remove logs and simply parse `in_msg_body`?

;; Logs `$sender deposited $deposit_amount TON to $evm_recipient with $call_data`
cell log = begin_cell()
.store_uint(op::internal::deposit_and_call, size::op_code_size)
.store_uint(0, size::query_id_size)
.store_slice(sender)
.store_coins(deposit_amount)
.store_uint(evm_recipient, size::evm_address)
.store_ref(call_data)
.end_cell();

send_log_message(log);
}

;; Input for all internal messages
() recv_internal(int my_balance, int msg_value, cell in_msg_full, slice in_msg_body) impure {
(int flags, slice sender, _) = in_msg_full.parse_flags_and_sender();

Expand All @@ -103,34 +150,41 @@ const deposit_gas_fee = 10000000; ;; 0.01 TON
throw_if(error::no_intent, in_msg_body.slice_bits() < (size::op_code_size + size::query_id_size));

;; query_id is not used for now.
(int op, _) = in_msg_body.parse_intent();

load_state();
(int op, _) = in_msg_body~parse_intent();

;; Just accept the incoming value
if (op == op::internal::donate) {
throw_if(error::insufficient_value, msg_value < deposit_gas_fee);

return ();
}

if (op == op::internal::deposit) {
throw_if(error::deposits_disabled, state::deposits_enabled == 0);
throw_if(error::insufficient_value, msg_value < deposit_gas_fee);
throw_if(error::invalid_evm_recipient, in_msg_body.slice_bits() < size::evm_address);

int evm_recipient = in_msg_body~load_uint(size::evm_address);

return handle_deposit(sender, msg_value, evm_recipient);
}

if (op == op::internal::deposit_and_call) {
throw_if(error::insufficient_value, msg_value < deposit_gas_fee);
throw_if(error::invalid_evm_recipient, in_msg_body.slice_bits() < size::evm_address);

int evm_recipient = in_msg_body~load_uint(size::evm_address);

;; expect a ref with a memo
throw_unless(error::memo_is_empty, in_msg_body.slice_refs());
throw_if(error::invalid_call_data, in_msg_body.slice_refs_empty?());

cell memo = in_msg_body~load_ref();
cell call_data = in_msg_body~load_ref();

return handle_deposit(sender, msg_value, memo);
return handle_deposit_and_call(sender, msg_value, evm_recipient, call_data);
}

throw(error::unknown_op);
}

;; EXTERNAL MESSAGES ===============================

;; Check ECDSA (!) signature of the external message
(cell) authenticate_external_message(slice message) impure inline_ref {
;; 1: Parse external message
slice signature = message~load_bits(size::signature_size);
Expand Down Expand Up @@ -214,7 +268,7 @@ const deposit_gas_fee = 10000000; ;; 0.01 TON

slice payload = authenticate_external_message(message).begin_parse();

slice new_tss_address = payload~load_bits(size::tss_address);
slice new_tss_address = payload~load_bits(size::evm_address);
int seqno = payload~load_uint(size::seqno);

throw_if(error::invalid_seqno, seqno != (state::seqno + 1));
Expand Down
Loading

0 comments on commit 8f3a373

Please sign in to comment.