;; Multisig wallet smart contract (Daniil Gentili @danogentili ) ;; (Now unused) 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"; cell udict_set(cell dict, int key_len, int index, slice value) asm(value index dict key_len) "DICTUSET"; (cell, ()) ~udict_set(cell dict, int key_len, int index, slice value) asm(value index dict key_len) "DICTUSET"; int udict_has?(cell dict, int key_len, int index) asm(index dict key_len) "DICTUGET" "NULLSWAPIFNOT" "NIP"; ;; Cleanup expired partial orders ;; messages (cell, ()) ~collect_garbage(cell messages) { var hash = -1; do { (hash, var cs, int ok) = messages.udict_get_next?(256, hash); if (ok) { ;; modeMessage$0 mode:uint8 body:^(Message X) = ModeMessage X; ;; wrappedMessage$_ expires_at:uint32 seqno:uint32 body:(ModeMessage X) = WrappedMessage X; ;; multiSigWrapperStorage$_ count:(## 4) signatures:(HashmapE 4 Signature) message:(WrappedMessage X) = MultiSigWrapperStorage X; ;; ;; Skip signatures, check expiry ;; ;; expiry <= now if (cs.skip_bits(4).skip_dict().preload_uint(32) <= now()) { messages~udict_delete?(256, hash); } } } until (~ ok); return (messages, ()); } () 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 message) impure { ;; do nothing for internal messages } ;; multiSigWrapper$0 signatures:(HashmapE 4 Signature) message:(WrappedMessage X) = MultiSigWrapper X; () recv_external(slice message) impure { ;; Check if multiSigWrapper$0, multiSigInit$10 or future unsupported protocol int tag = message~load_uint(1); if (tag) { ;; Throw if multiSigFuture$11 throw_if(32, message~load_uint(1)); ;; Return if multiSigInit$10 return (); } ;; Check if is hme_empty$0 or hme_root$1 ;; Throw if empty signature list throw_unless(33, message.preload_uint(1)); var signatures = message~load_dict(); slice message_copy = message; ;; wrappedMessage$_ expires_at:uint32 seqno:uint32 body:(ModeMessage X) = WrappedMessage X; (int expires_at, int msg_seqno) = (message_copy~load_uint(32), message_copy~load_uint(32)); ;; Message expired throw_if(34, expires_at <= now()); ;; We will need the hash anyway int hash = slice_hash(message); ;; storage$_ seqno:uint32 minSigs:(## 4) keys:(HashmapE 4 PubKey) messages:(HashmapE 256 (MultiSigWrapperStorage X)) = Storage X; slice stored_data = get_data().begin_parse(); (int stored_seqno, int min_sigs, var keys, var messages) = (stored_data~load_uint(32), stored_data~load_uint(4), stored_data~load_dict(), stored_data~load_dict()); stored_data.end_parse(); ;; This is a new message, so there will be no stored message var storedSignatureCount = 0; var storedMessageSignatures = new_dict(); ;; If new message, check and increase seqno if (stored_seqno == msg_seqno) { stored_seqno += 1; ;; If old message, seqno WILL be different, check if exists in unsigned messages dict } else { (message, int ok) = messages.udict_get?(256, hash); ;; Throw if old message and doesn't exist in db throw_unless(35, ok); ;; multiSigWrapperStorage$_ count:(## 4) signatures:(HashmapE 4 Signature) message:(WrappedMessage X) = MultiSigWrapperStorage X; ;; ;; Load signatures storedSignatureCount = message~load_uint(4); storedMessageSignatures = message.preload_dict(); } accept_message(); int idx = -1; do { (idx, slice signature, int ok) = signatures.udict_get_next?(4, idx); if (ok) { (var public_key, ok) = keys.udict_get?(4, idx); throw_unless(36, ok); var slice_copy = signature; throw_unless(37, check_signature(hash, slice_copy, public_key.preload_uint(256))); if (~ storedMessageSignatures.udict_has?(4, idx)) { storedMessageSignatures~udict_set(4, idx, signature); storedSignatureCount += 1; } } } until (~ ok); if (storedSignatureCount >= min_sigs) { if (message_copy~load_uint(1)) { ;; Code upgrade ;; codeMessage$1 minSigs:(## 4) keys:(HashmapE 4 PubKey) code:^Cell = ModeMessage X; ;; min_sigs = message_copy~load_uint(4); keys = message_copy~load_dict(); set_code(message_copy~load_ref()); } else { ;; Simple message ;; modeMessage$0 mode:uint8 body:^(Message X) = ModeMessage X; ;; var (mode, message) = (message_copy~load_uint(8), message_copy~load_ref()); send_raw_message(message, mode); } message_copy.end_parse(); messages~udict_delete?(256, hash); } else { ;; modeMessage$0 mode:uint8 body:^(Message X) = ModeMessage X; ;; wrappedMessage$_ expires_at:uint32 seqno:uint32 body:(ModeMessage X) = WrappedMessage X; ;; multiSigWrapperStorage$_ count:(## 4) signatures:(HashmapE 4 Signature) message:(WrappedMessage X) = MultiSigWrapperStorage X; ;; messages~udict_set_builder( 256, hash, begin_cell() .store_uint(storedSignatureCount, 4) .store_dict(storedMessageSignatures) .store_uint(expires_at, 32) .store_uint(msg_seqno, 32) .store_slice(message_copy) ); } messages~collect_garbage(); ;; storage$_ seqno:uint32 minSigs:(## 4) keys:(HashmapE 4 PubKey) messages:(HashmapE 256 (MultiSigWrapperStorage X)) = Storage X; set_data(begin_cell().store_uint(stored_seqno, 32).store_uint(min_sigs, 4).store_dict(keys).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, int ok) = get_data().begin_parse().skip_bits(36).preload_dict().udict_get?(4, id); if (~ ok) { return (ok, 0); } return (-1, res.preload_uint(256)); } (int, int) getId(int key) method_id { var keys = get_data().begin_parse().skip_bits(36).preload_dict(); int idx = -1; int ok = 0; int found = 0; do { (idx, var keyRead, ok) = keys.udict_get_next?(4, idx); if (ok) { ;; I could actually return here, but the funcompiler won't let me found = keyRead.preload_uint(256) == key; } } until ((~ ok) | found); if (found) { return (-1, idx); } return (0, 0); } (cell) getPartials() method_id { return get_data().begin_parse().skip_bits(36).skip_dict().preload_dict(); } (int, cell) getPartialsByKeyId(int id) method_id { cell messages = get_data().begin_parse().skip_bits(36).skip_dict().preload_dict(); cell messages_found = new_dict(); int idx = -1; int ok = 0; int found = 0; do { (idx, slice message, ok) = messages.udict_get_next?(256, idx); if (ok) { if (message.skip_bits(4).preload_dict().udict_has?(4, id)) { found += 1; messages_found~udict_set_builder(256, idx, begin_cell().store_slice(message)); } } } until (~ ok); return (found, messages_found); } (int, cell) getPartialByKey(int key) method_id { (int ok, int id) = getId(key); ifnot (ok) { return (0, new_dict()); } return getPartialsByKeyId(id); } (int, cell) getPartialByHash(int hash) method_id { (slice message, int ok) = get_data().begin_parse().skip_bits(36).skip_dict().preload_dict().udict_get?(256, hash); ifnot (ok) { return (0, begin_cell().end_cell()); } return (ok, begin_cell().store_slice(message).end_cell()); } (int, int) getMagic() method_id { return (420, 69); }