mirror of
https://github.com/danog/toncontest.git
synced 2024-11-29 20:19:11 +01:00
Move
This commit is contained in:
parent
78626adacc
commit
5fe76ed22b
@ -1,7 +0,0 @@
|
||||
|
||||
// Minimum signatures to send order
|
||||
3 : k
|
||||
// Maximum signatures per order
|
||||
10 : n
|
||||
|
||||
|
@ -1,14 +0,0 @@
|
||||
"TonUtil.fif" include
|
||||
|
||||
{ ."usage: " @' $0 type ." <privkey>" cr
|
||||
."Create public key files from private keys; if <privkey> doesn't exist, it will be created." cr cr
|
||||
1 halt
|
||||
} : usage
|
||||
$# 1 < ' usage if
|
||||
|
||||
$1 +".pk" load-generate-keypair drop
|
||||
$1 +".pubkey" B>file
|
||||
|
||||
|
||||
."Wrote private key to " $1 +".pk" type cr
|
||||
."Wrote public key to " $1 +".pubkey" type cr
|
@ -1,163 +0,0 @@
|
||||
<?php
|
||||
|
||||
if (!\file_exists('db.json')) {
|
||||
$db = [
|
||||
'seqno' => 0,
|
||||
'minSig' => 3,
|
||||
'keys' => [
|
||||
'key1'
|
||||
],
|
||||
'messages' => [
|
||||
'hash' => [
|
||||
'expires' => 100,
|
||||
'signatures' => [
|
||||
0 => 'sig' // Generated from key1
|
||||
],
|
||||
'seqno' => 0,
|
||||
'body' => 'body'
|
||||
]
|
||||
]
|
||||
];
|
||||
} else {
|
||||
$db = \json_decode(\file_get_contents('db.json'), true);
|
||||
}
|
||||
|
||||
function storeDb(array $db)
|
||||
{
|
||||
\file_put_contents('db.json', \json_encode($db));
|
||||
}
|
||||
|
||||
function garbageCollect(array $db): array
|
||||
{
|
||||
$changed = false;
|
||||
foreach ($db['messages'] as $hash => $message) {
|
||||
if ($message['expires'] < \time()) {
|
||||
unset($db['messages'][$hash]);
|
||||
$changed = true;
|
||||
}
|
||||
}
|
||||
return [$changed, $db];
|
||||
}
|
||||
|
||||
function hasKey(string $key, array $db)
|
||||
{
|
||||
foreach ($db['keys'] as $id => $curKey) {
|
||||
if ($curKey === $key) {
|
||||
return $id;
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
|
||||
$message = \json_decode(\file_get_contents('php://input'));
|
||||
|
||||
$op = $message['op'];
|
||||
$body = $message['body'];
|
||||
if ($op === 11) {
|
||||
return $db['seqno'];
|
||||
}
|
||||
|
||||
if ($op === 12) {
|
||||
return hasKey($body, $db);
|
||||
}
|
||||
|
||||
if ($op === 0) {
|
||||
//list($shouldUpdate, $db) = garbageCollect($db);
|
||||
|
||||
$signatures = $body['signatures'];
|
||||
if (empty($signatures)) {
|
||||
//if ($shouldUpdate) {
|
||||
// storeDb($db);
|
||||
//}
|
||||
return 'not enough signatures';
|
||||
}
|
||||
|
||||
if ($body['expires'] < \time()) {
|
||||
//if ($shouldUpdate) {
|
||||
// storeDb($db);
|
||||
//}
|
||||
return 'message expired';
|
||||
}
|
||||
$hash = \hash('sha256', \json_encode($body));
|
||||
|
||||
if ($db['seqno'] === $body['seqno']) {
|
||||
$db['seqno']++;
|
||||
$oldSignatures = [];
|
||||
// Assuming there are no signatures stored
|
||||
} elseif (!isset($db['messages'][$hash])) {
|
||||
return 'wrong seqno or no such message';
|
||||
} else {
|
||||
$oldSignatures = $db['messages'][$hash]['signatures'];
|
||||
}
|
||||
|
||||
foreach ($signatures as $idx => $signature) {
|
||||
$key = $db['keys'][$idx];
|
||||
if (!checkSig($signature, $key)) {
|
||||
return 'wrong sig';
|
||||
}
|
||||
|
||||
$oldSignatures[$idx] = $signature;
|
||||
}
|
||||
|
||||
$count = 0;
|
||||
foreach ($oldSignatures as $sig) {
|
||||
if (++$count >= 3) {
|
||||
sendMessage($body);
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
if ($count < 3) {
|
||||
$db['messages'][$hash] = [
|
||||
'expires' => $body['expires'],
|
||||
'signatures' => $oldSignatures,
|
||||
'body' => $body
|
||||
];
|
||||
}
|
||||
|
||||
storeDb(garbageCollect($db));
|
||||
}
|
||||
|
||||
|
||||
$keys = [
|
||||
0,
|
||||
1,
|
||||
2,
|
||||
3,
|
||||
4,
|
||||
5,
|
||||
6,
|
||||
7,
|
||||
8,
|
||||
9,
|
||||
];
|
||||
|
||||
$n = count($keys);
|
||||
$nCells = $n / 3;
|
||||
|
||||
$cells = [];
|
||||
$cell = [];
|
||||
for ($x = 0; $x < n; $x++) {
|
||||
for ($y = 0; $y < 3; $y++) {
|
||||
$z = $x + $y;
|
||||
}
|
||||
}
|
||||
|
||||
foreach ($keys as $k => $key) {
|
||||
if ($k && !($k % 3)) {
|
||||
$cells []= $cell;
|
||||
$cell = [];
|
||||
}
|
||||
$cell []= $key;
|
||||
}
|
||||
if ($cell) {
|
||||
$cells []= $cell;
|
||||
}
|
||||
|
||||
$final = [];
|
||||
for ($x = $nRev = count($cells) - 1; $x >= 0; $x--) {
|
||||
if ($nRev > 2 && $x != $nRev && $x > 1) {
|
||||
|
||||
}
|
||||
}
|
@ -1,198 +0,0 @@
|
||||
// This is just an approximated TL scheme, explaining the message formats used by the multisig wallet.
|
||||
// Multisig wallet constructors
|
||||
|
||||
// 256 bits
|
||||
pubKey$_ k:bits256 = PubKey;
|
||||
|
||||
// 512 bits
|
||||
signature$_ R:bits256 s:bits256 = Signature;
|
||||
|
||||
// Message + send ModeMessage
|
||||
modeMessage$_ mode:uint8 body:^(Message X) = ModeMessage X;
|
||||
|
||||
// Actual multisigned message
|
||||
wrappedMessage$_ expires_at:uint32 seqno:uint32 body:(ModeMessage X) = WrappedMessage X;
|
||||
|
||||
// key ID => signature
|
||||
multiSigWrapper$0 signatures:(HashmapE 4 ^Signature) message:(WrappedMessage X) = MultiSigWrapper X;
|
||||
//multiSigFuture$1 = MultiSigWrapper X;
|
||||
|
||||
// For internal storage, no constructor ID
|
||||
multiSigWrapperStorage$_ signatures:(HashmapE 4 ^Signature) message:(WrappedMessage X) = MultiSigWrapperStorage X;
|
||||
|
||||
|
||||
pubKeys1$_ key1:PubKey = PubKeys;
|
||||
pubKeys2$_ key1:PubKey key2:PubKey = PubKeys;
|
||||
pubKeys3$_ key1:PubKey key2:PubKey key3:PubKey = PubKeys;
|
||||
|
||||
pubKeys3and$_ key1:PubKey key2:PubKey key3:PubKey _:^PubKeys = PubKeys;
|
||||
|
||||
// Not doing explicit versioning here, since the structure of the storage can only change after an update of the onchain wallet code
|
||||
// Max 10 keys (not sure about this, the contest instruction file says 10 in one place and 100 in another, so I chose 10 => 16 => 2^(4) )
|
||||
//
|
||||
// Keys will be deserialized to a simple tuple, using the correct PubKeys constructor depending on the number n of keys left
|
||||
//
|
||||
// The pubKeys3and constructor is used when 10+ keys have to be stored
|
||||
// in which case the last pubKeys3 constructor will contain a references to another PubKeys constructor,
|
||||
// leaving one reference free in the storage cell to store the messages hashmap.
|
||||
//
|
||||
storage$_ seqno:uint32 minSigs:(## 4) n:(## 4) keys:[ ^PubKeys ] messages:(HashmapE 256 ^(MultiSigWrapperStorage X))
|
||||
{ minSigs > 0 } { n >= minSigs } { n <= 10 } { minSigs <= 10 } = Storage X;
|
||||
|
||||
// TON stuff
|
||||
|
||||
unit$_ = Unit;
|
||||
true$_ = True;
|
||||
// EMPTY False;
|
||||
bool_false$0 = Bool;
|
||||
bool_true$1 = Bool;
|
||||
bool_false$0 = BoolFalse;
|
||||
bool_true$1 = BoolTrue;
|
||||
nothing$0 {X:Type} = Maybe X;
|
||||
just$1 {X:Type} value:X = Maybe X;
|
||||
left$0 {X:Type} {Y:Type} value:X = Either X Y;
|
||||
right$1 {X:Type} {Y:Type} value:Y = Either X Y;
|
||||
pair$_ {X:Type} {Y:Type} first:X second:Y = Both X Y;
|
||||
|
||||
bit$_ (## 1) = Bit;
|
||||
/*
|
||||
*
|
||||
* FROM hashmap.tlb
|
||||
*
|
||||
*/
|
||||
// ordinary Hashmap / HashmapE, with fixed length keys
|
||||
//
|
||||
hm_edge#_ {n:#} {X:Type} {l:#} {m:#} label:(HmLabel ~l n)
|
||||
{n = (~m) + l} node:(HashmapNode m X) = Hashmap n X;
|
||||
|
||||
hmn_leaf#_ {X:Type} value:X = HashmapNode 0 X;
|
||||
hmn_fork#_ {n:#} {X:Type} left:^(Hashmap n X)
|
||||
right:^(Hashmap n X) = HashmapNode (n + 1) X;
|
||||
|
||||
hml_short$0 {m:#} {n:#} len:(Unary ~n) {n <= m} s:(n * Bit) = HmLabel ~n m;
|
||||
hml_long$10 {m:#} n:(#<= m) s:(n * Bit) = HmLabel ~n m;
|
||||
hml_same$11 {m:#} v:Bit n:(#<= m) = HmLabel ~n m;
|
||||
|
||||
unary_zero$0 = Unary ~0;
|
||||
unary_succ$1 {n:#} x:(Unary ~n) = Unary ~(n + 1);
|
||||
|
||||
hme_empty$0 {n:#} {X:Type} = HashmapE n X;
|
||||
hme_root$1 {n:#} {X:Type} root:^(Hashmap n X) = HashmapE n X;
|
||||
|
||||
extra_currencies$_ dict:(HashmapE 32 (VarUInteger 32))
|
||||
= ExtraCurrencyCollection;
|
||||
// true#_ = True;
|
||||
_ {n:#} _:(Hashmap n True) = BitstringSet n;
|
||||
|
||||
// HashmapAug, hashmap with an extra value
|
||||
// (augmentation) of type Y at every node
|
||||
//
|
||||
ahm_edge#_ {n:#} {X:Type} {Y:Type} {l:#} {m:#}
|
||||
label:(HmLabel ~l n) {n = (~m) + l}
|
||||
node:(HashmapAugNode m X Y) = HashmapAug n X Y;
|
||||
ahmn_leaf#_ {X:Type} {Y:Type} extra:Y value:X = HashmapAugNode 0 X Y;
|
||||
ahmn_fork#_ {n:#} {X:Type} {Y:Type} left:^(HashmapAug n X Y)
|
||||
right:^(HashmapAug n X Y) extra:Y = HashmapAugNode (n + 1) X Y;
|
||||
|
||||
ahme_empty$0 {n:#} {X:Type} {Y:Type} extra:Y
|
||||
= HashmapAugE n X Y;
|
||||
ahme_root$1 {n:#} {X:Type} {Y:Type} root:^(HashmapAug n X Y)
|
||||
extra:Y = HashmapAugE n X Y;
|
||||
|
||||
// VarHashmap / VarHashmapE, with variable-length keys
|
||||
//
|
||||
vhm_edge#_ {n:#} {X:Type} {l:#} {m:#} label:(HmLabel ~l n)
|
||||
{n = (~m) + l} node:(VarHashmapNode m X)
|
||||
= VarHashmap n X;
|
||||
vhmn_leaf$00 {n:#} {X:Type} value:X = VarHashmapNode n X;
|
||||
vhmn_fork$01 {n:#} {X:Type} left:^(VarHashmap n X)
|
||||
right:^(VarHashmap n X) value:(Maybe X)
|
||||
= VarHashmapNode (n + 1) X;
|
||||
vhmn_cont$1 {n:#} {X:Type} branch:Bit child:^(VarHashmap n X)
|
||||
value:X = VarHashmapNode (n + 1) X;
|
||||
|
||||
// nothing$0 {X:Type} = Maybe X;
|
||||
// just$1 {X:Type} value:X = Maybe X;
|
||||
|
||||
vhme_empty$0 {n:#} {X:Type} = VarHashmapE n X;
|
||||
vhme_root$1 {n:#} {X:Type} root:^(VarHashmap n X)
|
||||
= VarHashmapE n X;
|
||||
|
||||
//
|
||||
// PfxHashmap / PfxHashmapE, with variable-length keys
|
||||
// constituting a prefix code
|
||||
//
|
||||
|
||||
phm_edge#_ {n:#} {X:Type} {l:#} {m:#} label:(HmLabel ~l n)
|
||||
{n = (~m) + l} node:(PfxHashmapNode m X)
|
||||
= PfxHashmap n X;
|
||||
|
||||
phmn_leaf$0 {n:#} {X:Type} value:X = PfxHashmapNode n X;
|
||||
phmn_fork$1 {n:#} {X:Type} left:^(PfxHashmap n X)
|
||||
right:^(PfxHashmap n X) = PfxHashmapNode (n + 1) X;
|
||||
|
||||
phme_empty$0 {n:#} {X:Type} = PfxHashmapE n X;
|
||||
phme_root$1 {n:#} {X:Type} root:^(PfxHashmap n X)
|
||||
= PfxHashmapE n X;
|
||||
/*
|
||||
*
|
||||
* END hashmap.tlb
|
||||
*
|
||||
*/
|
||||
|
||||
// TON messages
|
||||
|
||||
|
||||
addr_none$00 = MsgAddressExt;
|
||||
addr_extern$01 len:(## 9) external_address:(bits len)
|
||||
= MsgAddressExt;
|
||||
anycast_info$_ depth:(#<= 30) { depth >= 1 }
|
||||
rewrite_pfx:(bits depth) = Anycast;
|
||||
addr_std$10 anycast:(Maybe Anycast)
|
||||
workchain_id:int8 address:bits256 = MsgAddressInt;
|
||||
addr_var$11 anycast:(Maybe Anycast) addr_len:(## 9)
|
||||
workchain_id:int32 address:(bits addr_len) = MsgAddressInt;
|
||||
_ _:MsgAddressInt = MsgAddress;
|
||||
_ _:MsgAddressExt = MsgAddress;
|
||||
//
|
||||
var_uint$_ {n:#} len:(#< n) value:(uint (len * 8))
|
||||
= VarUInteger n;
|
||||
var_int$_ {n:#} len:(#< n) value:(int (len * 8))
|
||||
= VarInteger n;
|
||||
nanograms$_ amount:(VarUInteger 16) = Grams;
|
||||
//
|
||||
extra_currencies$_ dict:(HashmapE 32 (VarUInteger 32))
|
||||
= ExtraCurrencyCollection;
|
||||
currencies$_ grams:Grams other:ExtraCurrencyCollection
|
||||
= CurrencyCollection;
|
||||
//
|
||||
int_msg_info$0 ihr_disabled:Bool bounce:Bool bounced:Bool
|
||||
src:MsgAddressInt dest:MsgAddressInt
|
||||
value:CurrencyCollection ihr_fee:Grams fwd_fee:Grams
|
||||
created_lt:uint64 created_at:uint32 = CommonMsgInfo;
|
||||
ext_in_msg_info$10 src:MsgAddressExt dest:MsgAddressInt
|
||||
import_fee:Grams = CommonMsgInfo;
|
||||
ext_out_msg_info$11 src:MsgAddressInt dest:MsgAddressExt
|
||||
created_lt:uint64 created_at:uint32 = CommonMsgInfo;
|
||||
|
||||
|
||||
int_msg_info$0 ihr_disabled:Bool bounce:Bool bounced:Bool
|
||||
src:MsgAddress dest:MsgAddressInt
|
||||
value:CurrencyCollection ihr_fee:Grams fwd_fee:Grams
|
||||
created_lt:uint64 created_at:uint32 = CommonMsgInfoRelaxed;
|
||||
ext_out_msg_info$11 src:MsgAddress dest:MsgAddressExt
|
||||
created_lt:uint64 created_at:uint32 = CommonMsgInfoRelaxed;
|
||||
|
||||
tick_tock$_ tick:Bool tock:Bool = TickTock;
|
||||
|
||||
_ split_depth:(Maybe (## 5)) special:(Maybe TickTock)
|
||||
code:(Maybe ^Cell) data:(Maybe ^Cell)
|
||||
library:(HashmapE 256 SimpleLib) = StateInit;
|
||||
|
||||
simple_lib$_ public:Bool root:^Cell = SimpleLib;
|
||||
|
||||
// create a message
|
||||
message$_ {X:Type} info:CommonMsgInfo
|
||||
init:(Maybe (Either StateInit ^StateInit))
|
||||
body:(Either X ^X) = Message X;
|
||||
|
@ -1,250 +0,0 @@
|
||||
;; 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;
|
||||
}
|
@ -1,416 +0,0 @@
|
||||
"Asm.fif" include
|
||||
// automatically generated from `/home/daniil/repos/contest/lib/crypto/smartcont/stdlib.fc` `wallet-code.fc`
|
||||
PROGRAM{
|
||||
DECLPROC ~collect_garbage
|
||||
DECLPROC ~load_keys
|
||||
DECLPROC preload_keys
|
||||
DECLPROC skip_keys
|
||||
DECLPROC store_keys
|
||||
DECLPROC store_db
|
||||
DECLPROC recv_internal
|
||||
DECLPROC recv_external
|
||||
85143 DECLMETHOD seqno
|
||||
119659 DECLMETHOD getKey
|
||||
66593 DECLMETHOD getId
|
||||
124239 DECLMETHOD getPartials
|
||||
87848 DECLMETHOD getPartialsById
|
||||
~collect_garbage PROC:<{
|
||||
-1 PUSHINT
|
||||
UNTIL:<{
|
||||
OVER
|
||||
8 PUSHPOW2
|
||||
DICTUGETNEXT
|
||||
NULLSWAPIFNOT
|
||||
NULLSWAPIFNOT
|
||||
DUP
|
||||
IF:<{
|
||||
s0 s2 XCHG
|
||||
SKIPDICT
|
||||
32 PLDU
|
||||
NOW
|
||||
LEQ
|
||||
IF:<{
|
||||
8 PUSHPOW2
|
||||
s1 s3 s3 PUXC2
|
||||
DICTUDEL
|
||||
DROP
|
||||
}>ELSE<{
|
||||
s0 s2 XCHG
|
||||
}>
|
||||
}>ELSE<{
|
||||
2SWAP
|
||||
DROP
|
||||
}>
|
||||
SWAP
|
||||
NOT
|
||||
s1 s2 XCHG
|
||||
}>
|
||||
DROP
|
||||
}>
|
||||
~load_keys PROC:<{
|
||||
4 LDU
|
||||
0 PUSHINT
|
||||
PUSHNULL
|
||||
PUSHNULL
|
||||
s0 s3 XCHG
|
||||
LDREF
|
||||
s4 s1 XCPU
|
||||
0 PUSHINT
|
||||
SETINDEXVARQ
|
||||
SWAP
|
||||
CTOS
|
||||
UNTIL:<{
|
||||
s3 PUSH
|
||||
3 PUSHINT
|
||||
MOD
|
||||
0 EQINT
|
||||
IF:<{
|
||||
s3 PUSH
|
||||
9 LESSINT
|
||||
s4 PUSH
|
||||
0 GTINT
|
||||
AND
|
||||
IF:<{
|
||||
DUP
|
||||
ENDS
|
||||
s0 s4 XCHG
|
||||
LDREF
|
||||
s4 PUSH
|
||||
3 PUSHINT
|
||||
DIV
|
||||
s3 s2 s(-1) XCPUXC
|
||||
SETINDEXVARQ
|
||||
NIP
|
||||
}>ELSE<{
|
||||
DUP
|
||||
PLDREF
|
||||
CTOS
|
||||
SWAP
|
||||
ENDS
|
||||
s4 s4 XCHG2
|
||||
}>
|
||||
}>ELSE<{
|
||||
s4 s4 XCHG2
|
||||
}>
|
||||
s4 PUSH
|
||||
256 PLDU
|
||||
s3 s0 s4 XC2PU
|
||||
SETINDEXVARQ
|
||||
s0 s3 XCHG
|
||||
INC
|
||||
s0 s5 PUSH2
|
||||
EQUAL
|
||||
s5 s4 s0 XCHG3
|
||||
s3 s3 s0 XCHG3
|
||||
}>
|
||||
s3 POP
|
||||
s4 POP
|
||||
SWAP
|
||||
ENDS
|
||||
ROT
|
||||
}>
|
||||
preload_keys PROC:<{
|
||||
4 LDU
|
||||
0 PUSHINT
|
||||
PUSHNULL
|
||||
s0 s2 XCHG
|
||||
LDREF
|
||||
DROP
|
||||
CTOS
|
||||
UNTIL:<{
|
||||
OVER
|
||||
3 PUSHINT
|
||||
MOD
|
||||
0 EQINT
|
||||
IF:<{
|
||||
OVER
|
||||
9 LESSINT
|
||||
s2 PUSH
|
||||
0 GTINT
|
||||
AND
|
||||
IF:<{
|
||||
}>ELSE<{
|
||||
}>
|
||||
}>
|
||||
256 LDU
|
||||
s3 s3 s2 XC2PU
|
||||
SETINDEXVARQ
|
||||
SWAP
|
||||
INC
|
||||
s0 s3 PUSH2
|
||||
EQUAL
|
||||
s1 s3 s0 XCHG3
|
||||
}>
|
||||
s2 s3 XCHG
|
||||
3 BLKDROP
|
||||
}>
|
||||
skip_keys PROC:<{
|
||||
4 LDU
|
||||
SWAP
|
||||
3 PUSHINT
|
||||
DIV
|
||||
3 PUSHINT
|
||||
MIN
|
||||
UNTIL:<{
|
||||
SWAP
|
||||
LDREF
|
||||
NIP
|
||||
OVER
|
||||
0 EQINT
|
||||
s1 s2 XCHG
|
||||
}>
|
||||
DROP
|
||||
}>
|
||||
store_keys PROC:<{
|
||||
4 PUSHINT
|
||||
2SWAP
|
||||
STUX
|
||||
OVER
|
||||
TLEN
|
||||
s2 POP
|
||||
0 PUSHINT
|
||||
UNTIL:<{
|
||||
INC
|
||||
s0 s2 PUSH2
|
||||
EQUAL
|
||||
}>
|
||||
DROP
|
||||
NIP
|
||||
}>
|
||||
store_db PROC:<{
|
||||
NEWC
|
||||
s1 s3 XCHG
|
||||
32 STU
|
||||
STDICT
|
||||
STDICT
|
||||
ENDC
|
||||
c4 POP
|
||||
}>
|
||||
recv_internal PROC:<{
|
||||
DROP
|
||||
}>
|
||||
recv_external PROC:<{
|
||||
1 LDU
|
||||
SWAP
|
||||
32 THROWIFNOT
|
||||
DUP
|
||||
1 PLDU
|
||||
33 THROWIFNOT
|
||||
LDDICT
|
||||
DUP
|
||||
32 LDU
|
||||
32 LDU
|
||||
s2 PUSH
|
||||
NOW
|
||||
LEQ
|
||||
34 THROWIF
|
||||
s0 s3 XCHG
|
||||
HASHSU
|
||||
c4 PUSH
|
||||
CTOS
|
||||
32 LDU
|
||||
4 LDU
|
||||
~load_keys CALLDICT
|
||||
s0 s2 XCHG
|
||||
LDDICT
|
||||
ENDS
|
||||
NEWDICT
|
||||
s5 s7 PUSH2
|
||||
EQUAL
|
||||
IF:<{
|
||||
s0 s5 XCHG
|
||||
INC
|
||||
}>ELSE<{
|
||||
s6 s1 PUSH2
|
||||
8 PUSHPOW2
|
||||
DICTUGET
|
||||
NULLSWAPIFNOT
|
||||
35 THROWIFNOT
|
||||
LDREF
|
||||
NIP
|
||||
ENDS
|
||||
s0 s5 XCHG
|
||||
}>
|
||||
ACCEPT
|
||||
s0 s9 XCHG
|
||||
-1 PUSHINT
|
||||
UNTIL:<{
|
||||
s11 PUSH
|
||||
4 PUSHINT
|
||||
DICTUGETNEXT
|
||||
NULLSWAPIFNOT
|
||||
NULLSWAPIFNOT
|
||||
DUP
|
||||
IF:<{
|
||||
s5 s1 PUSH2
|
||||
INDEXVAR
|
||||
s0 s3 XCHG
|
||||
LDREF
|
||||
DROP
|
||||
DUP
|
||||
CTOS
|
||||
DUP
|
||||
ENDS
|
||||
s11 s0 s4 PUXC2
|
||||
CHKSIGNU
|
||||
37 THROWIFNOT
|
||||
s1 s2 XCHG
|
||||
4 PUSHINT
|
||||
s3 s9 s9 PUXC2
|
||||
DICTUSETREF
|
||||
}>ELSE<{
|
||||
s1 s8 s8 XCHG3
|
||||
DROP
|
||||
}>
|
||||
s0 s7 XCHG
|
||||
NOT
|
||||
}>
|
||||
DROP
|
||||
s10 POP
|
||||
s0 s9 XCHG
|
||||
8 LDU
|
||||
LDREF
|
||||
ENDS
|
||||
0 PUSHINT
|
||||
-1 PUSHINT
|
||||
UNTIL:<{
|
||||
s7 PUSH
|
||||
4 PUSHINT
|
||||
DICTUGETNEXT
|
||||
NULLSWAPIFNOT
|
||||
NULLSWAPIFNOT
|
||||
s2 POP
|
||||
OVER
|
||||
IF:<{
|
||||
s0 s2 XCHG
|
||||
INC
|
||||
s0 s7 PUSH2
|
||||
GEQ
|
||||
IF:<{
|
||||
NIP
|
||||
s2 s3 PUSH2
|
||||
SENDRAWMSG
|
||||
8 PUSHPOW2
|
||||
s9 s13 s13 PUXC2
|
||||
DICTUDEL
|
||||
DROP
|
||||
0 PUSHINT
|
||||
}>ELSE<{
|
||||
s13 s13 XCHG2
|
||||
}>
|
||||
}>ELSE<{
|
||||
s0 s13 s13 XCHG3
|
||||
}>
|
||||
NOT
|
||||
s13 s13 s0 XCHG3
|
||||
}>
|
||||
DROP
|
||||
s5 PUSH
|
||||
LESS
|
||||
IF:<{
|
||||
NEWC
|
||||
s1 s6 XCHG
|
||||
STDICT
|
||||
s1 s8 XCHG
|
||||
32 STU
|
||||
s1 s6 XCHG
|
||||
32 STU
|
||||
s1 s6 XCHG
|
||||
8 STU
|
||||
s1 s2 XCHG
|
||||
STREF
|
||||
ENDC
|
||||
s0 s0 s6 XCHG3
|
||||
8 PUSHPOW2
|
||||
DICTUSETREF
|
||||
}>ELSE<{
|
||||
s4 s10 XCHG
|
||||
s3 s8 XCHG
|
||||
s2 s7 XCHG
|
||||
s4 s6 XCHG
|
||||
6 BLKDROP
|
||||
}>
|
||||
~collect_garbage CALLDICT
|
||||
s0 s3 XCHG
|
||||
INC
|
||||
NEWC
|
||||
32 STU
|
||||
s1 s4 XCHG
|
||||
4 STU
|
||||
s0 s3 XCHG
|
||||
TLEN
|
||||
s2 s3 XCHG
|
||||
SWAP
|
||||
store_keys CALLDICT
|
||||
STDICT
|
||||
ENDC
|
||||
c4 POP
|
||||
}>
|
||||
seqno PROC:<{
|
||||
c4 PUSH
|
||||
CTOS
|
||||
32 PLDU
|
||||
}>
|
||||
getKey PROC:<{
|
||||
c4 PUSH
|
||||
CTOS
|
||||
36 PUSHINT
|
||||
SDSKIPFIRST
|
||||
preload_keys CALLDICT
|
||||
SWAP
|
||||
INDEXVAR
|
||||
DUP
|
||||
ISNULL
|
||||
IFJMP:<{
|
||||
DROP
|
||||
0 PUSHINT
|
||||
DUP
|
||||
}>
|
||||
-1 PUSHINT
|
||||
SWAP
|
||||
}>
|
||||
getId PROC:<{
|
||||
c4 PUSH
|
||||
CTOS
|
||||
36 PUSHINT
|
||||
SDSKIPFIRST
|
||||
preload_keys CALLDICT
|
||||
0 PUSHINT
|
||||
OVER
|
||||
TLEN
|
||||
UNTIL:<{
|
||||
s2 PUSH
|
||||
0 INDEX
|
||||
s4 PUSH
|
||||
EQUAL
|
||||
IFJMP:<{
|
||||
s1 s3 XCHG
|
||||
3 BLKDROP
|
||||
-1 PUSHINT
|
||||
SWAP
|
||||
}>
|
||||
2DUP
|
||||
EQUAL
|
||||
}>
|
||||
4 BLKDROP
|
||||
0 PUSHINT
|
||||
DUP
|
||||
}>
|
||||
getPartials PROC:<{
|
||||
c4 PUSH
|
||||
CTOS
|
||||
36 PUSHINT
|
||||
SDSKIPFIRST
|
||||
skip_keys CALLDICT
|
||||
PLDDICT
|
||||
}>
|
||||
getPartialsById PROC:<{
|
||||
DROP
|
||||
c4 PUSH
|
||||
CTOS
|
||||
36 PUSHINT
|
||||
SDSKIPFIRST
|
||||
skip_keys CALLDICT
|
||||
PLDDICT
|
||||
}>
|
||||
}END>c
|
@ -1,159 +0,0 @@
|
||||
"TonUtil.fif" include
|
||||
|
||||
' constant : const
|
||||
|
||||
{ dup ."Loading public key from file " type ."..." cr
|
||||
file>B dup Blen 32 <> abort"Public key must be exactly 32 bytes long"
|
||||
256 B>u@
|
||||
} : load-pubkey
|
||||
{ dup ."Loading order from file " type ."..." cr
|
||||
file>B B>boc
|
||||
} : load-boc
|
||||
|
||||
{ ."usage: " @' $0 type ." <workchain-id> <wallet-name> <n> <k> <privkey1> [<pubkey2> ...] [<boc1> <boc2>]" cr cr
|
||||
."Creates a new multisignature wallet in specified workchain composed of <n> (1-10) keys." cr
|
||||
."The first of the keys must be a private key (pre-existing or not), used to generate the wallet; the rest MUST be public keys." cr
|
||||
."Create or generate public key files from private keys using gen-pub.fif privkey" cr cr
|
||||
."Min <k> (1-10) signatures required to send an order; load <n> pre-existing public keys from files <key1...n>." cr
|
||||
."Optionally load a number of pre-generated partially signed orders <boc1...m> to preload into the contract." cr cr
|
||||
1 halt
|
||||
} : usage
|
||||
$# 5 < ' usage if
|
||||
|
||||
$1 parse-workchain-id =: wc // set workchain id from command line argument
|
||||
$2 constant file-base
|
||||
$3 (number) 1 <> abort"<n> must be a number!" constant n
|
||||
$4 (number) 1 <> abort"<k> must be a number!" constant k
|
||||
$# 4 n + - constant m
|
||||
|
||||
n 1 < n 10 > or abort"<n> must be between 1 and 10"
|
||||
k 1 < k 10 > or abort"<k> must be between 1 and 10"
|
||||
k n <= not abort"<k> must smaller than or equal to <n>"
|
||||
|
||||
$# 4 n + < abort"Not enough keys were provided in args!"
|
||||
|
||||
$5 +".pk" load-generate-keypair const privkey 256 B>u@
|
||||
|
||||
6 { dup $() +".pubkey" load-pubkey swap 1+ } n 1- times drop
|
||||
n tuple constant keys
|
||||
|
||||
5 n + { dup $() +".boc" load-boc swap 1+ } m times drop
|
||||
m tuple constant messages
|
||||
|
||||
cr
|
||||
."Creating new advanced wallet in workchain " wc .
|
||||
."with n=" n .
|
||||
."k=" k .
|
||||
."m=" m . ."..." cr cr
|
||||
|
||||
// idict! (v x s n – s0 −1 or s 0), adds a new value v (represented
|
||||
// by a Slice) with key given by signed big-endian n-bit integer x into
|
||||
// dictionary s with n-bit keys, and returns the new dictionary s0 and −1
|
||||
// on success. Otherwise the unchanged dictionary s and 0 are returned.
|
||||
|
||||
// Create dictionaries with keys and messages
|
||||
|
||||
// Keys will be deserialized to a simple tuple, using the correct PubKeys constructor depending on the number n of keys left
|
||||
//
|
||||
// pubKeys1$_ key1:PubKey = PubKeys;
|
||||
// pubKeys2$_ key1:PubKey key2:PubKey = PubKeys;
|
||||
// pubKeys3$_ key1:PubKey key2:PubKey key3:PubKey = PubKeys;
|
||||
// pubKeys3and$_ key1:PubKey key2:PubKey key3:PubKey _:^PubKeys = PubKeys;
|
||||
//
|
||||
// keys:[ ^PubKeys ]
|
||||
|
||||
// First create builders with groups of (at most) 3 keys each
|
||||
<b
|
||||
0 { dup 1+ swap // Create a counter
|
||||
// Get builder b (or create a new one)
|
||||
// if ($k && !($k % 3)) {
|
||||
dup dup 3 mod 0<> not and { <b } { 2 roll } cond
|
||||
|
||||
keys rot [] // Get n-th value x
|
||||
256 // y
|
||||
|
||||
u, // Write uint
|
||||
swap
|
||||
} n times drop
|
||||
|
||||
// Then convert builders into cells, appropriately inserting references for nested pubkeys
|
||||
n 3 /c const nCeil // Number of builders
|
||||
nCeil 1- const nRev // Steps for reverse counter
|
||||
nCeil 3 > const shouldRef // Whether there are more than 3 builders and references must be created
|
||||
nCeil 1+ const nRoll // Steps for roll
|
||||
|
||||
nRev { dup 1- swap // Create a reverse counter
|
||||
rot // Get current builder
|
||||
swap
|
||||
// if ($x != $nRev && $x > 1 && $nCeil > 3)
|
||||
dup nRev <> swap 1 > shouldRef and and { nCeil pick ref, } if
|
||||
|
||||
b> // Close builder, create cell
|
||||
nCeil -roll // Put cell at the back of the stack
|
||||
|
||||
} nCeil times drop
|
||||
|
||||
// Number of cells to store / Number of already referenced cells to delete
|
||||
nCeil 3 > { 3 nCeil 3 - } { nCeil 0 } cond const nRem const nCells
|
||||
|
||||
// Drop all cells we already referenced
|
||||
' drop nRem times
|
||||
|
||||
// Reverse order of cells
|
||||
nCells 0 reverse
|
||||
|
||||
// Create tuple with cells
|
||||
nCells tuple const keyCells
|
||||
|
||||
// messages:(HashmapE 256 ^(MultiSigWrapperStorage X))
|
||||
dictnew
|
||||
0 { dup 1+ swap // Create a counter
|
||||
messages swap [] // Get n-th value v
|
||||
dup <b swap ref, // ~
|
||||
|
||||
swap hashu // Get x
|
||||
|
||||
3 roll // Get dictionary s
|
||||
256 // Get n
|
||||
b>idict!
|
||||
not abort"Failure storing dictionary value!"
|
||||
|
||||
swap
|
||||
} m times drop const messages-dict
|
||||
|
||||
// code
|
||||
"wallet-code.fif" include
|
||||
// data
|
||||
// storage$_ seqno:uint32 minSigs:(## 4) n:(## 4) keys:[ ^PubKeys ] messages:(HashmapE 256 ^(MultiSigWrapperStorage X))
|
||||
// { minSigs > 0 } { n >= minSigs } { n <= 10 } { minSigs <= 10 } = Storage X;
|
||||
<b 0 32 u,
|
||||
k 4 u,
|
||||
nCells 4 u,
|
||||
keyCells explode roll { swap ref, } nCells times
|
||||
messages-dict dict,
|
||||
b>
|
||||
// no libraries
|
||||
null
|
||||
// create StateInit
|
||||
// _ split_depth:(Maybe (## 5)) special:(Maybe TickTock)
|
||||
// code:(Maybe ^Cell) data:(Maybe ^Cell)
|
||||
// library:(HashmapE 256 SimpleLib) = StateInit;
|
||||
// split_depth 0 special 0 code 1 data 1
|
||||
//
|
||||
<b b{0011} s, 3 roll ref, rot ref, swap dict, b>
|
||||
|
||||
dup ."StateInit: " <s csr. cr
|
||||
dup hash wc swap 2dup 2constant wallet_addr
|
||||
."new wallet address = " 2dup .addr cr
|
||||
2dup file-base +".addr" save-address-verbose
|
||||
."Non-bounceable address (for init): " 2dup 7 .Addr cr
|
||||
."Bounceable address (for later access): " 6 .Addr cr
|
||||
.s
|
||||
<b 0 32 u, -1 32 i, b>
|
||||
dup ."signing message: " <s csr. cr
|
||||
dup hash privkey ed25519_sign_uint rot
|
||||
<b b{1000100} s, wallet_addr addr, b{000010} s, swap <s s, b{0} s, swap B, swap <s s, b>
|
||||
dup ."External message for initialization is " <s csr. cr
|
||||
2 boc+>B dup Bx. cr
|
||||
file-base +"-query.boc" tuck B>file
|
||||
."(Saved wallet creating query to file " type .")" cr
|
@ -1,54 +0,0 @@
|
||||
#!/usr/bin/env -S fift -s
|
||||
"TonUtil.fif" include
|
||||
|
||||
|
||||
{
|
||||
."usage: " @' $0 type ." command [arguments]" cr
|
||||
cr
|
||||
."Available commands: create-wallet, " cr
|
||||
@' $0 type ." create-wallet
|
||||
"<filename-base> <dest-addr> <seqno> <amount> [-B <body-boc>] [<savefile>]" cr
|
||||
."Creates a request to advanced wallet created by new-wallet-v2.fif, with private key loaded from file <filename-base>.pk "
|
||||
."and address from <filename-base>.addr, and saves it into <savefile>.boc ('wallet-query.boc' by default)" cr 1 halt
|
||||
} : usage
|
||||
def? $6 { @' $5 "-B" $= { @' $6 =: body-boc-file [forget] $6 def? $7 { @' $7 =: $5 [forget] $7 } { [forget] $5 } cond
|
||||
@' $# 2- =: $# } if } if
|
||||
$# dup 4 < swap 5 > or ' usage if
|
||||
|
||||
true constant bounce
|
||||
|
||||
$1 =: file-base
|
||||
$2 bounce parse-load-address =: bounce 2=: dest_addr
|
||||
$3 parse-int =: seqno
|
||||
$4 $>GR =: amount
|
||||
def? $5 { @' $5 } { "wallet-query" } cond constant savefile
|
||||
3 constant send-mode // mode for SENDRAWMSG: +1 - sender pays fees, +2 - ignore errors
|
||||
60 constant timeout // external message expires in 60 seconds
|
||||
|
||||
file-base +".addr" load-address
|
||||
2dup 2constant wallet_addr
|
||||
."Source wallet address = " 2dup .addr cr 6 .Addr cr
|
||||
file-base +".pk" load-keypair nip constant wallet_pk
|
||||
|
||||
def? body-boc-file { @' body-boc-file file>B B>boc } { <b 0 32 u, "TESTv2" $, b> } cond
|
||||
constant body-cell
|
||||
|
||||
."Transferring " amount .GR ."to account "
|
||||
dest_addr 2dup bounce 7 + .Addr ." = " .addr
|
||||
."seqno=0x" seqno x. ."bounce=" bounce . cr
|
||||
."Body of transfer message is " body-cell <s csr. cr
|
||||
|
||||
// create a message
|
||||
<b b{01} s, bounce 1 i, b{000100} s, dest_addr addr, amount Gram, 0 9 64 32 + + 1+ u,
|
||||
body-cell <s 2dup s-fits? not rot over 1 i, -rot { drop body-cell ref, } { s, } cond
|
||||
b>
|
||||
<b seqno 32 u, now timeout + 32 u, send-mode 8 u, swap ref, b>
|
||||
dup ."signing message: " <s csr. cr
|
||||
dup hash wallet_pk ed25519_sign_uint
|
||||
<b b{1000100} s, wallet_addr addr, 0 Gram, b{00} s,
|
||||
swap B, swap <s s, b>
|
||||
dup ."resulting external message: " <s csr. cr
|
||||
2 boc+>B dup Bx. cr
|
||||
savefile +".boc" tuck B>file
|
||||
."Query expires in " timeout . ."seconds" cr
|
||||
."(Saved to file " type .")" cr
|
Loading…
Reference in New Issue
Block a user