-
Notifications
You must be signed in to change notification settings - Fork 27
/
Copy pathAccount.move
1228 lines (1059 loc) · 50.5 KB
/
Account.move
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
511
512
513
514
515
516
517
518
519
520
521
522
523
524
525
526
527
528
529
530
531
532
533
534
535
536
537
538
539
540
541
542
543
544
545
546
547
548
549
550
551
552
553
554
555
556
557
558
559
560
561
562
563
564
565
566
567
568
569
570
571
572
573
574
575
576
577
578
579
580
581
582
583
584
585
586
587
588
589
590
591
592
593
594
595
596
597
598
599
600
601
602
603
604
605
606
607
608
609
610
611
612
613
614
615
616
617
618
619
620
621
622
623
624
625
626
627
628
629
630
631
632
633
634
635
636
637
638
639
640
641
642
643
644
645
646
647
648
649
650
651
652
653
654
655
656
657
658
659
660
661
662
663
664
665
666
667
668
669
670
671
672
673
674
675
676
677
678
679
680
681
682
683
684
685
686
687
688
689
690
691
692
693
694
695
696
697
698
699
700
701
702
703
704
705
706
707
708
709
710
711
712
713
714
715
716
717
718
719
720
721
722
723
724
725
726
727
728
729
730
731
732
733
734
735
736
737
738
739
740
741
742
743
744
745
746
747
748
749
750
751
752
753
754
755
756
757
758
759
760
761
762
763
764
765
766
767
768
769
770
771
772
773
774
775
776
777
778
779
780
781
782
783
784
785
786
787
788
789
790
791
792
793
794
795
796
797
798
799
800
801
802
803
804
805
806
807
808
809
810
811
812
813
814
815
816
817
818
819
820
821
822
823
824
825
826
827
828
829
830
831
832
833
834
835
836
837
838
839
840
841
842
843
844
845
846
847
848
849
850
851
852
853
854
855
856
857
858
859
860
861
862
863
864
865
866
867
868
869
870
871
872
873
874
875
876
877
878
879
880
881
882
883
884
885
886
887
888
889
890
891
892
893
894
895
896
897
898
899
900
901
902
903
904
905
906
907
908
909
910
911
912
913
914
915
916
917
918
919
920
921
922
923
924
925
926
927
928
929
930
931
932
933
934
935
936
937
938
939
940
941
942
943
944
945
946
947
948
949
950
951
952
953
954
955
956
957
958
959
960
961
962
963
964
965
966
967
968
969
970
971
972
973
974
975
976
977
978
979
980
981
982
983
984
985
986
987
988
989
990
991
992
993
994
995
996
997
998
999
1000
address StarcoinFramework {
/// The module for the account resource that governs every account
module Account {
use StarcoinFramework::Authenticator;
use StarcoinFramework::Event;
use StarcoinFramework::Hash;
use StarcoinFramework::Token::{Self, Token};
use StarcoinFramework::Vector;
use StarcoinFramework::Signer;
use StarcoinFramework::Timestamp;
use StarcoinFramework::Option::{Self, Option};
use StarcoinFramework::TransactionFee;
use StarcoinFramework::CoreAddresses;
use StarcoinFramework::Errors;
use StarcoinFramework::STC::{Self, STC, is_stc};
use StarcoinFramework::BCS;
use StarcoinFramework::Math;
friend StarcoinFramework::TransactionManager;
spec module {
pragma verify = false;
pragma aborts_if_is_strict = true;
}
/// Every account has a Account::Account resource
struct Account has key {
/// The current authentication key.
/// This can be different than the key used to create the account
authentication_key: vector<u8>,
/// A `withdrawal_capability` allows whoever holds this capability
/// to withdraw from the account. At the time of account creation
/// this capability is stored in this option. It can later be
/// "extracted" from this field via `extract_withdraw_capability`,
/// and can also be restored via `restore_withdraw_capability`.
withdrawal_capability: Option<WithdrawCapability>,
/// A `key_rotation_capability` allows whoever holds this capability
/// the ability to rotate the authentication key for the account. At
/// the time of account creation this capability is stored in this
/// option. It can later be "extracted" from this field via
/// `extract_key_rotation_capability`, and can also be restored via
/// `restore_key_rotation_capability`.
key_rotation_capability: Option<KeyRotationCapability>,
/// event handle for account balance withdraw event
withdraw_events: Event::EventHandle<WithdrawEvent>,
/// event handle for account balance deposit event
deposit_events: Event::EventHandle<DepositEvent>,
/// Event handle for accept_token event
accept_token_events: Event::EventHandle<AcceptTokenEvent>,
/// The current sequence number.
/// Incremented by one each time a transaction is submitted
sequence_number: u64,
}
/// A resource that holds the tokens stored in this account
struct Balance<phantom TokenType> has key {
token: Token<TokenType>,
}
/// The holder of WithdrawCapability for account_address can withdraw Token from
/// account_address/Account::Account/balance.
/// There is at most one WithdrawCapability in existence for a given address.
struct WithdrawCapability has store {
account_address: address,
}
/// The holder of KeyRotationCapability for account_address can rotate the authentication key for
/// account_address (i.e., write to account_address/Account::Account/authentication_key).
/// There is at most one KeyRotationCapability in existence for a given address.
struct KeyRotationCapability has store {
account_address: address,
}
/// Message for balance withdraw event.
struct WithdrawEvent has drop, store {
/// The amount of Token<TokenType> sent
amount: u128,
/// The code symbol for the token that was sent
token_code: Token::TokenCode,
/// Metadata associated with the withdraw
metadata: vector<u8>,
}
/// Message for balance deposit event.
struct DepositEvent has drop, store {
/// The amount of Token<TokenType> sent
amount: u128,
/// The code symbol for the token that was sent
token_code: Token::TokenCode,
/// Metadata associated with the deposit
metadata: vector<u8>,
}
/// Message for accept token events
struct AcceptTokenEvent has drop, store {
token_code: Token::TokenCode,
}
// SignerDelegated can only be stored under address, not in other structs.
struct SignerDelegated has key {}
// SignerCapability can only be stored in other structs, not under address.
// So that the capability is always controlled by contracts, not by some EOA.
struct SignerCapability has store { addr: address }
// Resource marking whether the account enable auto-accept-token feature.
struct AutoAcceptToken has key { enable: bool }
/// Message for rotate_authentication_key events
struct RotateAuthKeyEvent has drop, store {
account_address: address,
new_auth_key: vector<u8>,
}
/// Message for extract_withdraw_capability events
struct ExtractWithdrawCapEvent has drop, store {
account_address: address,
}
/// Message for SignerDelegate events
struct SignerDelegateEvent has drop, store {
account_address: address
}
struct EventStore has key {
/// Event handle for rotate_authentication_key event
rotate_auth_key_events: Event::EventHandle<RotateAuthKeyEvent>,
/// Event handle for extract_withdraw_capability event
extract_withdraw_cap_events: Event::EventHandle<ExtractWithdrawCapEvent>,
/// Event handle for signer delegated event
signer_delegate_events: Event::EventHandle<SignerDelegateEvent>,
}
const MAX_U64: u128 = 18446744073709551615;
const EPROLOGUE_ACCOUNT_DOES_NOT_EXIST: u64 = 0;
const EPROLOGUE_INVALID_ACCOUNT_AUTH_KEY: u64 = 1;
const EPROLOGUE_SEQUENCE_NUMBER_TOO_OLD: u64 = 2;
const EPROLOGUE_SEQUENCE_NUMBER_TOO_NEW: u64 = 3;
const EPROLOGUE_CANT_PAY_GAS_DEPOSIT: u64 = 4;
const EPROLOGUE_SEQUENCE_NUMBER_TOO_BIG: u64 = 9;
const EINSUFFICIENT_BALANCE: u64 = 10;
const ECOIN_DEPOSIT_IS_ZERO: u64 = 15;
const EBAD_TRANSACTION_FEE_TOKEN: u64 = 18;
const EDEPRECATED_FUNCTION: u64 = 19;
const EWITHDRAWAL_CAPABILITY_ALREADY_EXTRACTED: u64 = 101;
const EMALFORMED_AUTHENTICATION_KEY: u64 = 102;
const EKEY_ROTATION_CAPABILITY_ALREADY_EXTRACTED: u64 = 103;
const EADDRESS_PUBLIC_KEY_INCONSISTENT: u64 = 104;
const EADDRESS_AND_AUTH_KEY_MISMATCH: u64 = 105;
const ERR_TOKEN_NOT_ACCEPT: u64 = 106;
const ERR_SIGNER_ALREADY_DELEGATED: u64 = 107;
const EPROLOGUE_SIGNER_ALREADY_DELEGATED: u64 = 200;
const DUMMY_AUTH_KEY:vector<u8> = x"0000000000000000000000000000000000000000000000000000000000000000";
// cannot be dummy key, or empty key
const CONTRACT_ACCOUNT_AUTH_KEY_PLACEHOLDER:vector<u8> = x"0000000000000000000000000000000000000000000000000000000000000001";
/// The address bytes length
const ADDRESS_LENGTH: u64 = 16;
/// A one-way action, once SignerCapability is removed from signer, the address cannot send txns anymore.
/// This function can only called once by signer.
public fun remove_signer_capability(signer: &signer): SignerCapability
acquires Account, EventStore {
let signer_addr = Signer::address_of(signer);
assert!(!is_signer_delegated(signer_addr), Errors::invalid_state(ERR_SIGNER_ALREADY_DELEGATED));
// set to account auth key to noop.
{
let key_rotation_capability = extract_key_rotation_capability(signer);
rotate_authentication_key_with_capability(&key_rotation_capability, CONTRACT_ACCOUNT_AUTH_KEY_PLACEHOLDER);
destroy_key_rotation_capability(key_rotation_capability);
move_to(signer, SignerDelegated {});
make_event_store_if_not_exist(signer);
let event_store = borrow_global_mut<EventStore>(signer_addr);
Event::emit_event<SignerDelegateEvent>(
&mut event_store.signer_delegate_events,
SignerDelegateEvent {
account_address: signer_addr
}
);
};
let signer_cap = SignerCapability {addr: signer_addr };
signer_cap
}
public fun create_signer_with_cap(cap: &SignerCapability): signer {
create_signer(cap.addr)
}
public fun destroy_signer_cap(cap: SignerCapability) {
let SignerCapability {addr: _} = cap;
}
public fun signer_address(cap: &SignerCapability): address {
cap.addr
}
public fun is_signer_delegated(addr: address): bool {
exists<SignerDelegated>(addr)
}
/// Create an genesis account at `new_account_address` and return signer.
/// Genesis authentication_key is zero bytes.
public fun create_genesis_account(
new_account_address: address,
) :signer {
Timestamp::assert_genesis();
let new_account = create_signer(new_account_address);
make_account(&new_account, DUMMY_AUTH_KEY);
new_account
}
spec create_genesis_account {
aborts_if !Timestamp::is_genesis();
aborts_if len(DUMMY_AUTH_KEY) != 32;
aborts_if exists<Account>(new_account_address);
}
/// Release genesis account signer
public fun release_genesis_signer(_genesis_account: signer){
}
spec release_genesis_signer {
aborts_if false;
}
/// Deprecated since @v5
public fun create_account<TokenType: store>(_authentication_key: vector<u8>): address {
abort Errors::deprecated(EDEPRECATED_FUNCTION)
}
spec create_account {
aborts_if true;
}
/// Creates a new account at `fresh_address` with a balance of zero and empty auth key, the address as init auth key for check transaction.
/// Creating an account at address StarcoinFramework will cause runtime failure as it is a
/// reserved address for the MoveVM.
public fun create_account_with_address<TokenType: store>(fresh_address: address) acquires Account {
let new_account = create_signer(fresh_address);
make_account(&new_account, DUMMY_AUTH_KEY);
// Make sure all account accept STC.
if (!STC::is_stc<TokenType>()){
do_accept_token<STC>(&new_account);
};
do_accept_token<TokenType>(&new_account);
}
spec create_account_with_address {
//abort condition for make_account
aborts_if exists<Account>(fresh_address);
//abort condition for do_accept_token<STC>
aborts_if Token::spec_token_code<TokenType>() != Token::spec_token_code<STC>() && exists<Balance<STC>>(fresh_address);
//abort condition for do_accept_token<TokenType>
aborts_if exists<Balance<TokenType>>(fresh_address);
ensures exists_at(fresh_address);
ensures exists<Balance<TokenType>>(fresh_address);
}
fun make_account(
new_account: &signer,
authentication_key: vector<u8>,
) {
assert!(Vector::length(&authentication_key) == 32, Errors::invalid_argument(EMALFORMED_AUTHENTICATION_KEY));
let new_account_addr = Signer::address_of(new_account);
Event::publish_generator(new_account);
move_to(new_account, Account {
authentication_key,
withdrawal_capability: Option::some(
WithdrawCapability {
account_address: new_account_addr
}),
key_rotation_capability: Option::some(
KeyRotationCapability {
account_address: new_account_addr
}),
withdraw_events: Event::new_event_handle<WithdrawEvent>(new_account),
deposit_events: Event::new_event_handle<DepositEvent>(new_account),
accept_token_events: Event::new_event_handle<AcceptTokenEvent>(new_account),
sequence_number: 0,
});
move_to(new_account, AutoAcceptToken{enable: true});
move_to(new_account, EventStore {
rotate_auth_key_events: Event::new_event_handle<RotateAuthKeyEvent>(new_account),
extract_withdraw_cap_events: Event::new_event_handle<ExtractWithdrawCapEvent>(new_account),
signer_delegate_events: Event::new_event_handle<SignerDelegateEvent>(new_account),
});
}
spec make_account {
aborts_if len(authentication_key) != 32;
aborts_if exists<Account>(Signer::address_of(new_account));
aborts_if exists<AutoAcceptToken>(Signer::address_of(new_account));
ensures exists_at(Signer::address_of(new_account));
}
native fun create_signer(addr: address): signer;
public entry fun create_account_with_initial_amount<TokenType: store>(account: signer, fresh_address: address, _auth_key: vector<u8>, initial_amount: u128)
acquires Account, Balance, AutoAcceptToken {
create_account_with_initial_amount_entry<TokenType>(account, fresh_address, initial_amount);
}
public entry fun create_account_with_initial_amount_v2<TokenType: store>(account: signer, fresh_address: address, initial_amount: u128)
acquires Account, Balance, AutoAcceptToken {
create_account_with_initial_amount_entry<TokenType>(account, fresh_address, initial_amount);
}
public entry fun create_account_with_initial_amount_entry<TokenType: store>(account: signer, fresh_address: address, initial_amount: u128)
acquires Account, Balance, AutoAcceptToken {
create_account_with_address<TokenType>(fresh_address);
if (initial_amount > 0) {
pay_from<TokenType>(&account, fresh_address, initial_amount);
};
}
spec create_account_with_initial_amount {
pragma verify = false;
}
spec create_account_with_initial_amount_v2 {
pragma verify = false;
}
/// Generate an new address and create a new account, then delegate the account and return the new account address and `SignerCapability`
public fun create_delegate_account(sender: &signer) : (address, SignerCapability) acquires Balance, Account, EventStore {
let sender_address = Signer::address_of(sender);
let sequence_number = Self::sequence_number(sender_address);
// use stc balance as part of seed, just for new address more random.
let stc_balance = Self::balance<STC>(sender_address);
let seed_bytes = BCS::to_bytes(&sender_address);
Vector::append(&mut seed_bytes, BCS::to_bytes(&sequence_number));
Vector::append(&mut seed_bytes, BCS::to_bytes(&stc_balance));
let seed_hash = Hash::sha3_256(seed_bytes);
let i = 0;
let address_bytes = Vector::empty();
while (i < ADDRESS_LENGTH) {
Vector::push_back(&mut address_bytes, *Vector::borrow(&seed_hash,i));
i = i + 1;
};
let new_address = BCS::to_address(address_bytes);
Self::create_account_with_address<STC>(new_address);
let new_signer = Self::create_signer(new_address);
(new_address, Self::remove_signer_capability(&new_signer))
}
spec create_delegate_account {
pragma verify = false;
//TODO write spec
}
/// Deposits the `to_deposit` token into the self's account balance
public fun deposit_to_self<TokenType: store>(account: &signer, to_deposit: Token<TokenType>)
acquires Account, Balance, AutoAcceptToken {
let account_address = Signer::address_of(account);
if (!is_accepts_token<TokenType>(account_address)){
do_accept_token<TokenType>(account);
};
deposit(account_address, to_deposit);
}
spec deposit_to_self {
aborts_if to_deposit.value == 0;
let is_accepts_token = exists<Balance<TokenType>>(Signer::address_of(account));
aborts_if is_accepts_token && global<Balance<TokenType>>(Signer::address_of(account)).token.value + to_deposit.value > max_u128();
aborts_if !exists<Account>(Signer::address_of(account));
ensures exists<Balance<TokenType>>(Signer::address_of(account));
}
/// Deposits the `to_deposit` token into the `receiver`'s account balance with the no metadata
/// It's a reverse operation of `withdraw`.
public fun deposit<TokenType: store>(
receiver: address,
to_deposit: Token<TokenType>,
) acquires Account, Balance, AutoAcceptToken {
deposit_with_metadata<TokenType>(receiver, to_deposit, x"")
}
spec deposit {
include DepositWithMetadataAbortsIf<TokenType>;
}
/// Deposits the `to_deposit` token into the `receiver`'s account balance with the attached `metadata`
/// It's a reverse operation of `withdraw_with_metadata`.
public fun deposit_with_metadata<TokenType: store>(
receiver: address,
to_deposit: Token<TokenType>,
metadata: vector<u8>,
) acquires Account, Balance, AutoAcceptToken {
if (!exists_at(receiver)) {
create_account_with_address<TokenType>(receiver);
};
try_accept_token<TokenType>(receiver);
let deposit_value = Token::value(&to_deposit);
if (deposit_value > 0u128) {
// Deposit the `to_deposit` token
deposit_to_balance<TokenType>(borrow_global_mut<Balance<TokenType>>(receiver), to_deposit);
// emit deposit event
emit_account_deposit_event<TokenType>(receiver, deposit_value, metadata);
} else {
Token::destroy_zero(to_deposit);
};
}
spec deposit_with_metadata {
include DepositWithMetadataAbortsIf<TokenType>;
ensures exists<Balance<TokenType>>(receiver);
ensures old(global<Balance<TokenType>>(receiver)).token.value + to_deposit.value == global<Balance<TokenType>>(receiver).token.value;
}
spec schema DepositWithMetadataAbortsIf<TokenType> {
receiver: address;
to_deposit: Token<TokenType>;
aborts_if to_deposit.value == 0;
aborts_if !exists<Account>(receiver);
aborts_if !exists<Balance<TokenType>>(receiver);
aborts_if global<Balance<TokenType>>(receiver).token.value + to_deposit.value > max_u128();
}
/// Helper to deposit `amount` to the given account balance
fun deposit_to_balance<TokenType: store>(balance: &mut Balance<TokenType>, token: Token::Token<TokenType>) {
Token::deposit(&mut balance.token, token)
}
spec deposit_to_balance {
aborts_if balance.token.value + token.value > MAX_U128;
}
public(friend) fun withdraw_from_balance_v2<TokenType: store>(sender:address, amount: u128): Token<TokenType> acquires Balance {
let balance = borrow_global_mut<Balance<TokenType>>(sender);
Token::withdraw(&mut balance.token, amount)
}
public (friend) fun set_sequence_number(sender: address, sequence_number: u64) acquires Account {
let account = borrow_global_mut<Account>(sender);
account.sequence_number = sequence_number;
}
public (friend) fun set_authentication_key(sender:address,auth_key:vector<u8>) acquires Account{
let account = borrow_global_mut<Account>(sender);
account.authentication_key = auth_key;
}
/// Helper to withdraw `amount` from the given account balance and return the withdrawn Token<TokenType>
public fun withdraw_from_balance<TokenType: store>(balance: &mut Balance<TokenType>, amount: u128): Token<TokenType>{
Token::withdraw(&mut balance.token, amount)
}
spec withdraw_from_balance {
aborts_if balance.token.value < amount;
}
/// Withdraw `amount` Token<TokenType> from the account balance
public fun withdraw<TokenType: store>(account: &signer, amount: u128): Token<TokenType>
acquires Account, Balance {
withdraw_with_metadata<TokenType>(account, amount, x"")
}
spec withdraw {
aborts_if !exists<Balance<TokenType>>(Signer::address_of(account));
aborts_if !exists<Account>(Signer::address_of(account));
aborts_if global<Balance<TokenType>>(Signer::address_of(account)).token.value < amount;
aborts_if Option::is_none(global<Account>(Signer::address_of(account)).withdrawal_capability);
}
/// Withdraw `amount` tokens from `signer` with given `metadata`.
public fun withdraw_with_metadata<TokenType: store>(account: &signer, amount: u128, metadata: vector<u8>): Token<TokenType>
acquires Account, Balance {
let sender_addr = Signer::address_of(account);
let sender_balance = borrow_global_mut<Balance<TokenType>>(sender_addr);
// The sender_addr has delegated the privilege to withdraw from her account elsewhere--abort.
assert!(!delegated_withdraw_capability(sender_addr), Errors::invalid_state(EWITHDRAWAL_CAPABILITY_ALREADY_EXTRACTED));
if (amount == 0){
return Token::zero()
};
emit_account_withdraw_event<TokenType>(sender_addr, amount, metadata);
// The sender_addr has retained her withdrawal privileges--proceed.
withdraw_from_balance<TokenType>(sender_balance, amount)
}
spec withdraw_with_metadata {
aborts_if !exists<Balance<TokenType>>(Signer::address_of(account));
aborts_if !exists<Account>(Signer::address_of(account));
aborts_if global<Balance<TokenType>>(Signer::address_of(account)).token.value < amount;
aborts_if Option::is_none(global<Account>(Signer::address_of(account)).withdrawal_capability);
}
spec fun spec_withdraw<TokenType>(account: signer, amount: u128): Token<TokenType> {
Token<TokenType> { value: amount }
}
/// Withdraw `amount` Token<TokenType> from the account under cap.account_address with no metadata
public fun withdraw_with_capability<TokenType: store>(
cap: &WithdrawCapability, amount: u128
): Token<TokenType> acquires Balance, Account {
withdraw_with_capability_and_metadata<TokenType>(cap, amount, x"")
}
spec withdraw_with_capability {
aborts_if !exists<Balance<TokenType>>(cap.account_address);
aborts_if !exists<Account>(cap.account_address);
aborts_if global<Balance<TokenType>>(cap.account_address).token.value < amount;
}
/// Withdraw `amount` Token<TokenType> from the account under cap.account_address with metadata
public fun withdraw_with_capability_and_metadata<TokenType: store>(
cap: &WithdrawCapability, amount: u128, metadata: vector<u8>
): Token<TokenType> acquires Balance, Account {
let balance = borrow_global_mut<Balance<TokenType>>(cap.account_address);
emit_account_withdraw_event<TokenType>(cap.account_address, amount, metadata);
withdraw_from_balance<TokenType>(balance , amount)
}
spec withdraw_with_capability_and_metadata {
aborts_if !exists<Balance<TokenType>>(cap.account_address);
aborts_if !exists<Account>(cap.account_address);
aborts_if global<Balance<TokenType>>(cap.account_address).token.value < amount;
}
/// Return a unique capability granting permission to withdraw from the sender's account balance.
public fun extract_withdraw_capability(
sender: &signer
): WithdrawCapability acquires Account, EventStore {
let sender_addr = Signer::address_of(sender);
// Abort if we already extracted the unique withdraw capability for this account.
assert!(!delegated_withdraw_capability(sender_addr), Errors::invalid_state(EWITHDRAWAL_CAPABILITY_ALREADY_EXTRACTED));
make_event_store_if_not_exist(sender);
let event_store = borrow_global_mut<EventStore>(sender_addr);
Event::emit_event<ExtractWithdrawCapEvent>(
&mut event_store.extract_withdraw_cap_events,
ExtractWithdrawCapEvent {
account_address: sender_addr,
}
);
let account = borrow_global_mut<Account>(sender_addr);
Option::extract(&mut account.withdrawal_capability)
}
spec extract_withdraw_capability {
aborts_if !exists<Account>(Signer::address_of(sender));
aborts_if Option::is_none(global<Account>( Signer::address_of(sender)).withdrawal_capability);
}
/// Return the withdraw capability to the account it originally came from
public fun restore_withdraw_capability(cap: WithdrawCapability)
acquires Account {
let account = borrow_global_mut<Account>(cap.account_address);
Option::fill(&mut account.withdrawal_capability, cap)
}
spec restore_withdraw_capability {
aborts_if Option::is_some(global<Account>(cap.account_address).withdrawal_capability);
aborts_if !exists<Account>(cap.account_address);
}
fun emit_account_withdraw_event<TokenType: store>(account: address, amount: u128, metadata: vector<u8>)
acquires Account {
// emit withdraw event
let account = borrow_global_mut<Account>(account);
Event::emit_event<WithdrawEvent>(&mut account.withdraw_events, WithdrawEvent {
amount,
token_code: Token::token_code<TokenType>(),
metadata,
});
}
spec emit_account_withdraw_event {
aborts_if !exists<Account>(account);
}
fun emit_account_deposit_event<TokenType: store>(account: address, amount: u128, metadata: vector<u8>)
acquires Account {
// emit withdraw event
let account = borrow_global_mut<Account>(account);
Event::emit_event<DepositEvent>(&mut account.deposit_events, DepositEvent {
amount,
token_code: Token::token_code<TokenType>(),
metadata,
});
}
spec emit_account_deposit_event {
aborts_if !exists<Account>(account);
}
/// Withdraws `amount` Token<TokenType> using the passed in WithdrawCapability, and deposits it
/// into the `payee`'s account balance. Creates the `payee` account if it doesn't exist.
public fun pay_from_capability<TokenType: store>(
cap: &WithdrawCapability,
payee: address,
amount: u128,
metadata: vector<u8>,
) acquires Account, Balance, AutoAcceptToken {
let tokens = withdraw_with_capability_and_metadata<TokenType>(cap, amount, *&metadata);
deposit_with_metadata<TokenType>(
payee,
tokens,
metadata,
);
}
spec pay_from_capability {
// condition for withdraw_with_capability_and_metadata()
aborts_if !exists<Balance<TokenType>>(cap.account_address);
aborts_if !exists<Account>(cap.account_address);
aborts_if global<Balance<TokenType>>(cap.account_address).token.value < amount;
// condition for deposit_with_metadata()
aborts_if amount == 0;
aborts_if !exists<Account>(payee);
aborts_if !exists<Balance<TokenType>>(payee);
aborts_if cap.account_address != payee && global<Balance<TokenType>>(payee).token.value + amount > MAX_U128;
}
/// Withdraw `amount` Token<TokenType> from the transaction sender's
/// account balance and send the token to the `payee` address with the
/// attached `metadata` Creates the `payee` account if it does not exist
public fun pay_from_with_metadata<TokenType: store>(
account: &signer,
payee: address,
amount: u128,
metadata: vector<u8>,
) acquires Account, Balance, AutoAcceptToken {
let tokens = withdraw_with_metadata<TokenType>(account, amount, *&metadata);
deposit_with_metadata<TokenType>(
payee,
tokens,
metadata,
);
}
spec pay_from_with_metadata {
// condition for withdraw_with_metadata()
aborts_if !exists<Balance<TokenType>>(Signer::address_of(account));
aborts_if !exists<Account>(Signer::address_of(account));
aborts_if global<Balance<TokenType>>(Signer::address_of(account)).token.value < amount;
aborts_if Option::is_none(global<Account>(Signer::address_of(account)).withdrawal_capability);
// condition for deposit_with_metadata()
aborts_if amount == 0;
aborts_if !exists<Account>(payee);
aborts_if !exists<Balance<TokenType>>(payee);
aborts_if Signer::address_of(account) != payee && global<Balance<TokenType>>(payee).token.value + amount > max_u128();
}
spec schema DepositWithPayerAndMetadataAbortsIf<TokenType> {
payer: address;
payee: address;
to_deposit: Token<TokenType>;
aborts_if to_deposit.value == 0;
aborts_if !exists<Account>(payer);
aborts_if !exists<Account>(payee);
aborts_if !exists<Balance<TokenType>>(payee);
aborts_if global<Balance<TokenType>>(payee).token.value + to_deposit.value > max_u128();
}
/// Withdraw `amount` Token<TokenType> from the transaction sender's
/// account balance and send the token to the `payee` address
/// Creates the `payee` account if it does not exist
public fun pay_from<TokenType: store>(
account: &signer,
payee: address,
amount: u128
) acquires Account, Balance, AutoAcceptToken {
pay_from_with_metadata<TokenType>(account, payee, amount, x"");
}
spec pay_from {
// condition for withdraw_with_metadata()
aborts_if !exists<Balance<TokenType>>(Signer::address_of(account));
aborts_if !exists<Account>(Signer::address_of(account));
aborts_if global<Balance<TokenType>>(Signer::address_of(account)).token.value < amount;
aborts_if Option::is_none(global<Account>(Signer::address_of(account)).withdrawal_capability);
// condition for deposit_with_metadata()
aborts_if amount == 0;
aborts_if !exists<Account>(payee);
aborts_if !exists<Balance<TokenType>>(payee);
aborts_if Signer::address_of(account) != payee && global<Balance<TokenType>>(payee).token.value + amount > max_u128();
}
/// Rotate the authentication key for the account under cap.account_address
public fun rotate_authentication_key_with_capability(
cap: &KeyRotationCapability,
new_authentication_key: vector<u8>,
) acquires Account {
let sender_account_resource = borrow_global_mut<Account>(cap.account_address);
// Don't allow rotating to clearly invalid key
assert!(Vector::length(&new_authentication_key) == 32, Errors::invalid_argument(EMALFORMED_AUTHENTICATION_KEY));
sender_account_resource.authentication_key = new_authentication_key;
}
spec rotate_authentication_key_with_capability {
aborts_if !exists<Account>(cap.account_address);
aborts_if len(new_authentication_key) != 32;
ensures global<Account>(cap.account_address).authentication_key == new_authentication_key;
}
spec fun spec_rotate_authentication_key_with_capability(addr: address, new_authentication_key: vector<u8>): bool {
global<Account>(addr).authentication_key == new_authentication_key
}
/// Return a unique capability granting permission to rotate the sender's authentication key
public fun extract_key_rotation_capability(account: &signer): KeyRotationCapability
acquires Account {
let account_address = Signer::address_of(account);
// Abort if we already extracted the unique key rotation capability for this account.
assert!(!delegated_key_rotation_capability(account_address), Errors::invalid_state(EKEY_ROTATION_CAPABILITY_ALREADY_EXTRACTED));
let account = borrow_global_mut<Account>(account_address);
Option::extract(&mut account.key_rotation_capability)
}
spec extract_key_rotation_capability {
aborts_if !exists<Account>(Signer::address_of(account));
aborts_if Option::is_none(global<Account>(Signer::address_of(account)).key_rotation_capability);
}
/// Return the key rotation capability to the account it originally came from
public fun restore_key_rotation_capability(cap: KeyRotationCapability)
acquires Account {
let account = borrow_global_mut<Account>(cap.account_address);
Option::fill(&mut account.key_rotation_capability, cap)
}
public fun destroy_key_rotation_capability(cap: KeyRotationCapability) {
let KeyRotationCapability {account_address: _} = cap;
}
spec restore_key_rotation_capability {
aborts_if Option::is_some(global<Account>(cap.account_address).key_rotation_capability);
aborts_if !exists<Account>(cap.account_address);
}
public entry fun rotate_authentication_key(account: signer, new_key: vector<u8>) acquires Account, EventStore {
rotate_authentication_key_entry(account, new_key);
}
public entry fun rotate_authentication_key_entry(account: signer, new_key: vector<u8>) acquires Account, EventStore {
do_rotate_authentication_key(&account, new_key);
}
public fun do_rotate_authentication_key(account: &signer, new_key: vector<u8>) acquires Account, EventStore {
let key_rotation_capability = extract_key_rotation_capability(account);
rotate_authentication_key_with_capability(&key_rotation_capability, copy new_key);
restore_key_rotation_capability(key_rotation_capability);
make_event_store_if_not_exist(account);
let signer_addr = Signer::address_of(account);
let event_store = borrow_global_mut<EventStore>(signer_addr);
Event::emit_event<RotateAuthKeyEvent>(
&mut event_store.rotate_auth_key_events,
RotateAuthKeyEvent {
account_address: signer_addr,
new_auth_key: new_key,
}
);
}
spec rotate_authentication_key {
pragma verify = false;
}
/// Helper to return the u128 value of the `balance` for `account`
fun balance_for<TokenType: store>(balance: &Balance<TokenType>): u128 {
Token::value<TokenType>(&balance.token)
}
spec balance_for {
aborts_if false;
}
/// Return the current TokenType balance of the account at `addr`.
public fun balance<TokenType: store>(addr: address): u128 acquires Balance {
if (exists<Balance<TokenType>>(addr)) {
balance_for(borrow_global<Balance<TokenType>>(addr))
} else {
0u128
}
}
/// Add a balance of `Token` type to the sending account.
public fun do_accept_token<TokenType: store>(account: &signer) acquires Account {
move_to(account, Balance<TokenType>{ token: Token::zero<TokenType>() });
let token_code = Token::token_code<TokenType>();
// Load the sender's account
let sender_account_ref = borrow_global_mut<Account>(Signer::address_of(account));
// Log a sent event
Event::emit_event<AcceptTokenEvent>(
&mut sender_account_ref.accept_token_events,
AcceptTokenEvent {
token_code: token_code,
},
);
}
spec do_accept_token {
aborts_if exists<Balance<TokenType>>(Signer::address_of(account));
aborts_if !exists<Account>(Signer::address_of(account));
}
public entry fun accept_token<TokenType: store>(account: signer) acquires Account {
accept_token_entry<TokenType>(account);
}
public entry fun accept_token_entry<TokenType: store>(account: signer) acquires Account {
do_accept_token<TokenType>(&account);
}
spec accept_token {
pragma verify = false;
}
/// This is a alias of is_accept_token
public fun is_accepts_token<TokenType: store>(addr: address): bool acquires AutoAcceptToken {
Self::is_accept_token<TokenType>(addr)
}
spec is_accepts_token {
aborts_if false;
}
/// Return whether the account at `addr` accept `Token` type tokens
public fun is_accept_token<TokenType: store>(addr: address): bool acquires AutoAcceptToken {
if (can_auto_accept_token(addr)) {
true
} else {
exists<Balance<TokenType>>(addr)
}
}
spec is_accept_token {
aborts_if false;
}
/// Check whether the address can auto accept token.
public fun can_auto_accept_token(addr: address): bool acquires AutoAcceptToken {
if (exists<AutoAcceptToken>(addr)) {
borrow_global<AutoAcceptToken>(addr).enable
} else {
false
}
}
public entry fun set_auto_accept_token_entry(account: signer, enable: bool) acquires AutoAcceptToken {
set_auto_accept_token(&account, enable);
}
/// Configure whether auto-accept tokens.
public fun set_auto_accept_token(account: &signer, enable: bool) acquires AutoAcceptToken {
let addr = Signer::address_of(account);
if (exists<AutoAcceptToken>(addr)) {
let config = borrow_global_mut<AutoAcceptToken>(addr);
config.enable = enable;
} else {
move_to(account, AutoAcceptToken{enable});
};
}
spec set_auto_accept_token {
aborts_if false;
}
/// try to accept token for `addr`.
fun try_accept_token<TokenType: store>(addr: address) acquires AutoAcceptToken, Account {
if (!exists<Balance<TokenType>>(addr)) {
if (can_auto_accept_token(addr)) {
let signer = create_signer(addr);
do_accept_token<TokenType>(&signer);
}else{
abort Errors::not_published(ERR_TOKEN_NOT_ACCEPT)
}
};
}
spec try_accept_token {
aborts_if false;
}
/// Helper to return the sequence number field for given `account`
fun sequence_number_for_account(account: &Account): u64 {
account.sequence_number
}
spec is_accepts_token {
aborts_if false;
}
/// Return the current sequence number at `addr`
public fun sequence_number(addr: address): u64 acquires Account {
sequence_number_for_account(borrow_global<Account>(addr))
}
spec sequence_number {
aborts_if !exists<Account>(addr);
}
/// Return the authentication key for this account
public fun authentication_key(addr: address): vector<u8> acquires Account {
*&borrow_global<Account>(addr).authentication_key
}
spec authentication_key {
aborts_if !exists<Account>(addr);
}
/// Return true if the account at `addr` has delegated its key rotation capability
public fun delegated_key_rotation_capability(addr: address): bool
acquires Account {
Option::is_none(&borrow_global<Account>(addr).key_rotation_capability)
}
spec delegated_key_rotation_capability {
aborts_if !exists<Account>(addr);
}
/// Return true if the account at `addr` has delegated its withdraw capability
public fun delegated_withdraw_capability(addr: address): bool
acquires Account {
Option::is_none(&borrow_global<Account>(addr).withdrawal_capability)
}
spec delegated_withdraw_capability {
aborts_if !exists<Account>(addr);
}
/// Return a reference to the address associated with the given withdraw capability
public fun withdraw_capability_address(cap: &WithdrawCapability): &address {
&cap.account_address
}
spec withdraw_capability_address {
aborts_if false;
}
/// Return a reference to the address associated with the given key rotation capability
public fun key_rotation_capability_address(cap: &KeyRotationCapability): &address {
&cap.account_address
}
spec key_rotation_capability_address {
aborts_if false;
}
/// Checks if an account exists at `check_addr`
public fun exists_at(check_addr: address): bool {
exists<Account>(check_addr)
}
spec exists_at {
aborts_if false;
}
fun is_dummy_auth_key(account: &Account): bool {
*&account.authentication_key == DUMMY_AUTH_KEY
}
public fun is_dummy_auth_key_v2(account: address): bool acquires Account {
let account = borrow_global_mut<Account>(account);
account.authentication_key == DUMMY_AUTH_KEY
}
/// The prologue is invoked at the beginning of every transaction
/// It verifies:
/// - The account's auth key matches the transaction's public key
/// - That the account has enough balance to pay for all of the gas
/// - That the sequence number matches the transaction's sequence key
public fun txn_prologue<TokenType: store>(
account: &signer,
txn_sender: address,
txn_sequence_number: u64,
txn_authentication_key_preimage: vector<u8>,
txn_gas_price: u64,
txn_max_gas_units: u64,
) acquires Account, Balance {
txn_prologue_v2<TokenType>(
account,
txn_sender,
txn_sequence_number,
txn_authentication_key_preimage,
txn_gas_price,
txn_max_gas_units,