1
0
mirror of https://github.com/danog/toncontest.git synced 2024-11-30 04:29:14 +01:00
toncontest/wallet-dict/wallet-code.fc
2019-10-07 17:08:09 +02:00

250 lines
7.7 KiB
Plaintext

;; Multisig wallet smart contract
cell preload_refidx(slice s, int index) asm "PLDREFVAR";
;; Tuple manipulation primitives for integers
tuple tuple_set(tuple t, int index, int value) asm(t value index) "SETINDEXVARQ";
(tuple, ()) ~tuple_set(tuple t, int index, int value) asm(t value index) "SETINDEXVARQ";
;; Tuple manipulation primitives for cells
tuple tuple_setcell(tuple t, int index, cell value) asm(t value index) "SETINDEXVARQ";
(tuple, ()) ~tuple_setcell(tuple t, int index, cell value) asm(t value index) "SETINDEXVARQ";
int tuple_len(tuple t) asm "TLEN";
;; Cleanup expired partial orders
;; messages
(cell, ()) ~collect_garbage(cell messages) {
var hash = -1;
do {
(hash, var cs, var ok) = messages.udict_get_next?(256, hash);
if (ok) {
;; modeMessage$_ mode:uint8 body:^(Message X) = ModeMessage X;
;; wrappedMessage$_ expires_at:uint32 seqno:uint32 body:(ModeMessage X) = WrappedMessage X;
;; multiSigWrapperStorage$_ signatures:(HashmapE 4 ^Signature) message:(WrappedMessage X) = MultiSigWrapperStorage X;
;;
;; Skip signatures, check expiry
;;
;; expiry <= now
if (cs.skip_dict().preload_uint(32) <= now()) {
messages~udict_delete?(256, hash);
}
}
} until (~ ok);
return (messages, ());
}
;; messages
(slice, (tuple, tuple)) ~load_keys(slice messages) {
int length = messages~load_uint(4);
int index = 0;
var keys = nil;
var keys_cells = nil;
var keys_cell = messages~load_ref();
keys_cells~tuple_setcell(index / 3, keys_cell);
var keys_slice = keys_cell.begin_parse();
do {
if ((index % 3 == 0)) {
if ((index < 9) & (index > 0)) {
keys_slice.end_parse();
var keys_cell = messages~load_ref();
keys_cells~tuple_setcell(index / 3, keys_cell);
var keys_slice = keys_cell.begin_parse();
} else {
var newkeys_slice = keys_slice.preload_ref().begin_parse();
keys_slice.end_parse();
keys_slice = newkeys_slice;
}
}
int key = keys_slice.preload_uint(256);
keys~tuple_set(index, key);
index += 1;
} until (index == length);
keys_slice.end_parse();
return (messages, (keys, keys_cells));
}
;; messages
tuple preload_keys(slice messages) {
int length = messages~load_uint(4);
int index = 0;
var keys = nil;
var keys_slice = messages~load_ref().begin_parse();
do {
if ((index % 3 == 0)) {
if ((index < 9) & (index > 0)) {
var keys_slice = messages~load_ref().begin_parse();
} else {
var keys_slice = keys_slice.preload_ref().begin_parse();
}
}
int key = keys_slice~load_uint(256);
keys~tuple_set(index, key);
index += 1;
} until (index == length);
return keys;
}
slice skip_keys(slice messages) {
int length = messages~load_uint(4);
length = min(length / 3, 3);
int index = 0;
do {
messages~load_ref();
} until (index == length);
return messages;
}
builder store_keys(builder b, int length, var keys) {
b~store_uint(4, length);
length = keys.tuple_len();
int index = 0;
do {
b.store_ref(keys.cell_at(index));
index += 1;
} until (index == length);
return b;
}
() store_db(int seqno, cell keys, cell messages) {
set_data(begin_cell().store_uint(seqno, 32).store_dict(keys).store_dict(messages).end_cell());
}
() recv_internal(slice in_msg) impure {
;; do nothing for internal messages
}
;; multiSigWrapper$0 keys_signatures:(HashmapE 4 ^Signature) message:(WrappedMessage X) = MultiSigWrapper X;
() recv_external(slice in_msg) impure {
;; Check if multiSigWrapper$0 or future unsupported protocol
throw_unless(32, in_msg~load_uint(1));
;; Check if is hme_empty$0 or hme_root$1
;; Throw if empty signature list
throw_unless(33, in_msg.preload_uint(1));
var signatures = in_msg~load_dict();
slice message_data = in_msg;
;; wrappedMessage$_ expires_at:uint32 seqno:uint32 body:^(Message X) = WrappedMessage X;
(int expires_at, int msg_seqno) = (message_data~load_uint(32), message_data~load_uint(32));
;; Message expired
throw_if(34, expires_at <= now());
;; We will need the hash anyway
int hash = slice_hash(in_msg);
;; storage$_ seqno:uint32 minSigs:(## 4) keys:(HashmapE 4 ^PubKey) messages:(HashmapE 256 ^(StoredMessage X)) = Storage X;
slice stored_data = get_data().begin_parse();
(int stored_seqno, int min_sigs, (var keys, var kcells), var messages) = (stored_data~load_uint(32), stored_data~load_uint(4), stored_data~load_keys(), stored_data~load_dict());
stored_data.end_parse();
;; This is a new message, so there will be no stored messages
var storedMessageSignatures = new_dict();
;; If new message, increase seqno
if (stored_seqno == msg_seqno) {
stored_seqno += 1;
;; If old message
} else {
var (storedMessage, ok) = messages.udict_get?(256, hash);
;; Throw if old message and doesn't exist in db
throw_unless(35, ok);
;; multiSigWrapperStorage$_ signatures:(HashmapE 4 ^Signature) message:(WrappedMessage X) = MultiSigWrapperStorage X;
;;
;; Load signatures
var storedMessageSignatures = storedMessage~load_ref().begin_parse().preload_dict();
storedMessage.end_parse();
}
accept_message();
message_data~touch();
var idx = -1;
do {
(idx, var signature, var ok) = signatures.udict_get_next?(4, idx);
if (ok) {
var public_key = keys.int_at(idx);
;;throw_unless(at(at(36, kok);
var signature_cell = signature~load_ref();
var signature_slice = signature_cell.begin_parse();
var slice_copy = signature_slice;
signature_slice.end_parse();
throw_unless(37, check_signature(hash, slice_copy, public_key));
storedMessageSignatures~udict_set_ref(4, idx, signature_cell);
}
} until (~ ok);
var (mode, message) = (message_data~load_uint(8), message_data~load_ref());
message_data.end_parse();
var count = 0;
var sent = 0;
var idx = -1;
do {
(idx, var signature, var ok) = storedMessageSignatures.udict_get_next?(4, idx);
if (ok) {
count += 1;
if (count >= min_sigs) {
send_raw_message(message, mode);
messages~udict_delete?(256, hash);
ok = 0;
}
}
} until (~ ok);
;; modeMessage$_ mode:uint8 body:^(Message X) = ModeMessage X;
;; wrappedMessage$_ expires_at:uint32 seqno:uint32 body:(ModeMessage X) = WrappedMessage X;
;; multiSigWrapperStorage$_ signatures:(HashmapE 4 ^Signature) message:(WrappedMessage X) = MultiSigWrapperStorage X;
;;
if (count < min_sigs) {
messages~udict_set_ref(256, hash, begin_cell().store_dict(storedMessageSignatures).store_uint(expires_at, 32).store_uint(msg_seqno, 32).store_uint(mode, 8).store_ref(message).end_cell());
}
messages~collect_garbage();
;; storage$_ seqno:uint32 minSigs:(## 4) keys:(HashmapE 4 ^PubKey) messages:(HashmapE 256 ^(Message X)) = Storage X;
set_data(begin_cell().store_uint(stored_seqno + 1, 32).store_uint(min_sigs, 4).store_keys(keys.tuple_len(), kcells).store_dict(messages).end_cell());
}
;; Get methods
int seqno() method_id {
return get_data().begin_parse().preload_uint(32);
}
(int, int) getKey(int id) method_id {
var res = get_data().begin_parse().skip_bits(36).preload_keys().int_at(id);
if (res.null?()) {
return (0, 0);
}
return (-1, res);
}
(int, int) getId(int key) method_id {
var keys = get_data().begin_parse().skip_bits(36).preload_keys();
int index = 0;
int length = keys.tuple_len();
do {
if (keys.int_at(index) == key) {
return (-1, index);
}
} until (index == length);
return (0, 0);
}
(cell) getPartials() method_id {
return get_data().begin_parse().skip_bits(36).skip_keys().preload_dict();
}
(cell) getPartialsById(int id) method_id {
cell messages = get_data().begin_parse().skip_bits(36).skip_keys().preload_dict();
cell messages_found = new_dict();
return messages_found;
}