@@ -18,14 +18,19 @@ package ledger
18
18
19
19
import (
20
20
"crypto/rand"
21
+ "encoding/hex"
21
22
"fmt"
22
23
"testing"
23
24
24
25
"github.com/stretchr/testify/require"
25
26
27
+ "github.com/algorand/go-algorand/config"
26
28
"github.com/algorand/go-algorand/crypto"
27
29
"github.com/algorand/go-algorand/data/basics"
28
30
"github.com/algorand/go-algorand/data/transactions"
31
+ "github.com/algorand/go-algorand/data/transactions/logic"
32
+ "github.com/algorand/go-algorand/logging"
33
+ "github.com/algorand/go-algorand/protocol"
29
34
)
30
35
31
36
func getRandomAddress (a * require.Assertions ) basics.Address {
@@ -332,3 +337,233 @@ func TestLogicLedgerDelKey(t *testing.T) {
332
337
err = l .DelLocal (addr1 , "lkey" )
333
338
a .NoError (err )
334
339
}
340
+
341
+ // test ensures that
342
+ // 1) app's GlobalState and local state's KeyValue are stored in the same way
343
+ // before and after application code refactoring
344
+ // 2) writing into empty (opted-in) local state's KeyValue works after reloading
345
+ // Hardcoded values are from commit 9a0b439 (pre app refactor commit)
346
+ func TestAppAccountDataStorage (t * testing.T ) {
347
+ a := require .New (t )
348
+ source := `#pragma version 2
349
+ // do not write local key on opt in or on app create
350
+ txn ApplicationID
351
+ int 0
352
+ ==
353
+ bnz success
354
+ txn OnCompletion
355
+ int NoOp
356
+ ==
357
+ bnz writetostate
358
+ txn OnCompletion
359
+ int OptIn
360
+ ==
361
+ bnz checkargs
362
+ int 0
363
+ return
364
+ checkargs:
365
+ // if no args the success
366
+ // otherwise write data
367
+ txn NumAppArgs
368
+ int 0
369
+ ==
370
+ bnz success
371
+ // write local or global key depending on arg1
372
+ writetostate:
373
+ txna ApplicationArgs 0
374
+ byte "local"
375
+ ==
376
+ bnz writelocal
377
+ txna ApplicationArgs 0
378
+ byte "global"
379
+ ==
380
+ bnz writeglobal
381
+ int 0
382
+ return
383
+ writelocal:
384
+ int 0
385
+ byte "lk"
386
+ byte "local"
387
+ app_local_put
388
+ b success
389
+ writeglobal:
390
+ byte "gk"
391
+ byte "global"
392
+ app_global_put
393
+ success:
394
+ int 1
395
+ return`
396
+
397
+ ops , err := logic .AssembleString (source )
398
+ a .NoError (err )
399
+ a .Greater (len (ops .Program ), 1 )
400
+ program := ops .Program
401
+
402
+ proto := config .Consensus [protocol .ConsensusCurrentVersion ]
403
+ genesisInitState , initKeys := testGenerateInitState (t , protocol .ConsensusCurrentVersion )
404
+
405
+ creator , err := basics .UnmarshalChecksumAddress ("3LN5DBFC2UTPD265LQDP3LMTLGZCQ5M3JV7XTVTGRH5CKSVNQVDFPN6FG4" )
406
+ a .NoError (err )
407
+ userOptin , err := basics .UnmarshalChecksumAddress ("6S6UMUQ4462XRGNON5GKBHW55RUJGJ5INIRDFVFD6KSPHGWGRKPC6RK2O4" )
408
+ a .NoError (err )
409
+ userLocal , err := basics .UnmarshalChecksumAddress ("UL5C6SRVLOROSB5FGAE6TY34VXPXVR7GNIELUB3DD5KTA4VT6JGOZ6WFAY" )
410
+ a .NoError (err )
411
+ userLocal2 , err := basics .UnmarshalChecksumAddress ("XNOGOJECWDOMVENCDJHNMOYVV7PIVIJXRWTSZUA3GSKYTVXH3VVGOXP7CU" )
412
+ a .NoError (err )
413
+
414
+ a .Contains (genesisInitState .Accounts , creator )
415
+ a .Contains (genesisInitState .Accounts , userOptin )
416
+ a .Contains (genesisInitState .Accounts , userLocal )
417
+ a .Contains (genesisInitState .Accounts , userLocal2 )
418
+
419
+ expectedCreator , err := hex .DecodeString ("84a4616c676fce009d2290a461707070810184a6617070726f76c45602200200012604056c6f63616c06676c6f62616c026c6b02676b3118221240003331192212400010311923124000022243311b221240001c361a00281240000a361a0029124000092243222a28664200032b29672343a6636c65617270c40102a46773636881a36e627304a46c73636881a36e627301a36f6e6c01a47473636881a36e627304" )
420
+ a .NoError (err )
421
+ expectedUserOptIn , err := hex .DecodeString ("84a4616c676fce00a02fd0a46170706c810181a46873636881a36e627301a36f6e6c01a47473636881a36e627301" )
422
+ a .NoError (err )
423
+ expectedUserLocal , err := hex .DecodeString ("84a4616c676fce00a33540a46170706c810182a46873636881a36e627301a3746b7681a26c6b82a27462a56c6f63616ca2747401a36f6e6c01a47473636881a36e627301" )
424
+ a .NoError (err )
425
+
426
+ cfg := config .GetDefaultLocal ()
427
+ l , err := OpenLedger (logging .Base (), "TestAppAccountData" , true , genesisInitState , cfg )
428
+ a .NoError (err )
429
+ defer l .Close ()
430
+
431
+ txHeader := transactions.Header {
432
+ Sender : creator ,
433
+ Fee : basics.MicroAlgos {Raw : proto .MinTxnFee * 2 },
434
+ FirstValid : l .Latest () + 1 ,
435
+ LastValid : l .Latest () + 10 ,
436
+ GenesisID : t .Name (),
437
+ GenesisHash : genesisInitState .GenesisHash ,
438
+ }
439
+
440
+ // create application
441
+ approvalProgram := program
442
+ clearStateProgram := []byte ("\x02 " ) // empty
443
+ appCreateFields := transactions.ApplicationCallTxnFields {
444
+ ApprovalProgram : approvalProgram ,
445
+ ClearStateProgram : clearStateProgram ,
446
+ GlobalStateSchema : basics.StateSchema {NumByteSlice : 4 },
447
+ LocalStateSchema : basics.StateSchema {NumByteSlice : 1 },
448
+ }
449
+ appCreate := transactions.Transaction {
450
+ Type : protocol .ApplicationCallTx ,
451
+ Header : txHeader ,
452
+ ApplicationCallTxnFields : appCreateFields ,
453
+ }
454
+ err = l .appendUnvalidatedTx (t , genesisInitState .Accounts , initKeys , appCreate , transactions.ApplyData {})
455
+ a .NoError (err )
456
+
457
+ appIdx := basics .AppIndex (1 ) // first tnx => idx = 1
458
+
459
+ // opt-in, do no write
460
+ txHeader .Sender = userOptin
461
+ appCallFields := transactions.ApplicationCallTxnFields {
462
+ OnCompletion : transactions .OptInOC ,
463
+ ApplicationID : appIdx ,
464
+ }
465
+ appCall := transactions.Transaction {
466
+ Type : protocol .ApplicationCallTx ,
467
+ Header : txHeader ,
468
+ ApplicationCallTxnFields : appCallFields ,
469
+ }
470
+ err = l .appendUnvalidatedTx (t , genesisInitState .Accounts , initKeys , appCall , transactions.ApplyData {})
471
+ a .NoError (err )
472
+
473
+ // opt-in + write
474
+ txHeader .Sender = userLocal
475
+ appCall = transactions.Transaction {
476
+ Type : protocol .ApplicationCallTx ,
477
+ Header : txHeader ,
478
+ ApplicationCallTxnFields : appCallFields ,
479
+ }
480
+ err = l .appendUnvalidatedTx (t , genesisInitState .Accounts , initKeys , appCall , transactions.ApplyData {})
481
+ a .NoError (err )
482
+
483
+ // save data into DB and write into local state
484
+ l .accts .accountsWriting .Add (1 )
485
+ l .accts .commitRound (3 , 0 , 0 )
486
+ l .reloadLedger ()
487
+
488
+ appCallFields = transactions.ApplicationCallTxnFields {
489
+ OnCompletion : 0 ,
490
+ ApplicationID : appIdx ,
491
+ ApplicationArgs : [][]byte {[]byte ("local" )},
492
+ }
493
+ appCall = transactions.Transaction {
494
+ Type : protocol .ApplicationCallTx ,
495
+ Header : txHeader ,
496
+ ApplicationCallTxnFields : appCallFields ,
497
+ }
498
+ err = l .appendUnvalidatedTx (t , genesisInitState .Accounts , initKeys , appCall ,
499
+ transactions.ApplyData {EvalDelta : basics.EvalDelta {
500
+ LocalDeltas : map [uint64 ]basics.StateDelta {0 : {"lk" : basics.ValueDelta {Action : basics .SetBytesAction , Bytes : "local" }}}},
501
+ })
502
+ a .NoError (err )
503
+
504
+ // save data into DB
505
+ l .accts .accountsWriting .Add (1 )
506
+ l .accts .commitRound (1 , 3 , 0 )
507
+ l .reloadLedger ()
508
+
509
+ // dump accounts
510
+ var rowid int64
511
+ var dbRound basics.Round
512
+ var buf []byte
513
+ err = l .accts .accountsq .lookupStmt .QueryRow (creator [:]).Scan (& rowid , & dbRound , & buf )
514
+ a .NoError (err )
515
+ a .Equal (expectedCreator , buf )
516
+
517
+ err = l .accts .accountsq .lookupStmt .QueryRow (userOptin [:]).Scan (& rowid , & dbRound , & buf )
518
+ a .NoError (err )
519
+ a .Equal (expectedUserOptIn , buf )
520
+ pad , err := l .accts .accountsq .lookup (userOptin )
521
+ a .Nil (pad .accountData .AppLocalStates [appIdx ].KeyValue )
522
+ ad , err := l .Lookup (dbRound , userOptin )
523
+ a .Nil (ad .AppLocalStates [appIdx ].KeyValue )
524
+
525
+ err = l .accts .accountsq .lookupStmt .QueryRow (userLocal [:]).Scan (& rowid , & dbRound , & buf )
526
+ a .NoError (err )
527
+ a .Equal (expectedUserLocal , buf )
528
+
529
+ ad , err = l .Lookup (dbRound , userLocal )
530
+ a .NoError (err )
531
+ a .Equal ("local" , ad .AppLocalStates [appIdx ].KeyValue ["lk" ].Bytes )
532
+
533
+ // ensure writing into empty global state works as well
534
+ l .reloadLedger ()
535
+ txHeader .Sender = creator
536
+ appCallFields = transactions.ApplicationCallTxnFields {
537
+ OnCompletion : 0 ,
538
+ ApplicationID : appIdx ,
539
+ ApplicationArgs : [][]byte {[]byte ("global" )},
540
+ }
541
+ appCall = transactions.Transaction {
542
+ Type : protocol .ApplicationCallTx ,
543
+ Header : txHeader ,
544
+ ApplicationCallTxnFields : appCallFields ,
545
+ }
546
+ err = l .appendUnvalidatedTx (t , genesisInitState .Accounts , initKeys , appCall ,
547
+ transactions.ApplyData {EvalDelta : basics.EvalDelta {
548
+ GlobalDelta : basics.StateDelta {"gk" : basics.ValueDelta {Action : basics .SetBytesAction , Bytes : "global" }}},
549
+ })
550
+ a .NoError (err )
551
+
552
+ // opt-in + write by during opt-in
553
+ txHeader .Sender = userLocal2
554
+ appCallFields = transactions.ApplicationCallTxnFields {
555
+ OnCompletion : transactions .OptInOC ,
556
+ ApplicationID : appIdx ,
557
+ ApplicationArgs : [][]byte {[]byte ("local" )},
558
+ }
559
+ appCall = transactions.Transaction {
560
+ Type : protocol .ApplicationCallTx ,
561
+ Header : txHeader ,
562
+ ApplicationCallTxnFields : appCallFields ,
563
+ }
564
+ err = l .appendUnvalidatedTx (t , genesisInitState .Accounts , initKeys , appCall ,
565
+ transactions.ApplyData {EvalDelta : basics.EvalDelta {
566
+ LocalDeltas : map [uint64 ]basics.StateDelta {0 : {"lk" : basics.ValueDelta {Action : basics .SetBytesAction , Bytes : "local" }}}},
567
+ })
568
+ a .NoError (err )
569
+ }
0 commit comments