@@ -6,19 +6,21 @@ use anyhow::Ok;
6
6
use common:: access_controllable_contract:: AccessControllableContract ;
7
7
use common:: upgradable_contract:: UpgradableContract ;
8
8
use common:: utils:: {
9
- assert_failure_with, assert_insufficient_acl_permissions, assert_success_with ,
10
- assert_success_with_unit_return , fast_forward_beyond , get_transaction_block ,
11
- sdk_duration_from_secs,
9
+ assert_failure_with, assert_insufficient_acl_permissions, assert_method_not_found_failure ,
10
+ assert_success_with , assert_success_with_unit_return , fast_forward_beyond ,
11
+ get_transaction_block , sdk_duration_from_secs,
12
12
} ;
13
+ use near_plugins:: upgradable:: FunctionCallArgs ;
13
14
use near_sdk:: serde_json:: json;
14
- use near_sdk:: { CryptoHash , Duration , Timestamp } ;
15
+ use near_sdk:: { CryptoHash , Duration , Gas , Timestamp } ;
15
16
use std:: path:: Path ;
16
17
use workspaces:: network:: Sandbox ;
17
18
use workspaces:: result:: ExecutionFinalResult ;
18
19
use workspaces:: { Account , AccountId , Contract , Worker } ;
19
20
20
21
const PROJECT_PATH : & str = "./tests/contracts/upgradable" ;
21
22
const PROJECT_PATH_2 : & str = "./tests/contracts/upgradable_2" ;
23
+ const PROJECT_PATH_STATE_MIGRATION : & str = "./tests/contracts/upgradable_state_migration" ;
22
24
23
25
const ERR_MSG_NO_STAGING_TS : & str = "Upgradable: staging timestamp isn't set" ;
24
26
const ERR_MSG_DEPLOY_CODE_TOO_EARLY : & str = "Upgradable: Deploy code too early: staging ends on" ;
@@ -169,6 +171,18 @@ impl Setup {
169
171
. await
170
172
}
171
173
174
+ async fn call_is_migrated ( & self , caller : & Account ) -> workspaces:: Result < ExecutionFinalResult > {
175
+ // `is_migrated` could be called via `view`, however here it is called via `transact` so we
176
+ // get an `ExecutionFinalResult` that can be passed to `assert_*` methods from
177
+ // `common::utils`. It is acceptable since all we care about is whether the method exists
178
+ // and can be called successfully.
179
+ caller
180
+ . call ( self . contract . id ( ) , "is_migrated" )
181
+ . max_gas ( )
182
+ . transact ( )
183
+ . await
184
+ }
185
+
172
186
/// Calls the contract's `is_set_up` method and asserts it returns `true`. Panics on failure.
173
187
async fn assert_is_set_up ( & self , caller : & Account ) {
174
188
let res = caller
@@ -419,14 +433,15 @@ async fn test_deploy_code_without_delay() -> anyhow::Result<()> {
419
433
setup. assert_staged_code ( Some ( code) ) . await ;
420
434
421
435
// Deploy staged code.
422
- let res = setup. upgradable_contract . up_deploy_code ( & dao) . await ?;
436
+ let res = setup. upgradable_contract . up_deploy_code ( & dao, None ) . await ?;
423
437
assert_success_with_unit_return ( res) ;
424
438
425
439
Ok ( ( ) )
426
440
}
427
441
428
442
/// Verifies the upgrade was successful by calling a method that's available only on the upgraded
429
- /// contract. Ensures the new contract can be deployed and state migration succeeds.
443
+ /// contract. Ensures the new contract can be deployed and state remains valid without
444
+ /// explicit state migration.
430
445
#[ tokio:: test]
431
446
async fn test_deploy_code_and_call_method ( ) -> anyhow:: Result < ( ) > {
432
447
let worker = workspaces:: sandbox ( ) . await ?;
@@ -435,7 +450,7 @@ async fn test_deploy_code_and_call_method() -> anyhow::Result<()> {
435
450
436
451
// Verify function `is_upgraded` is not defined in the initial contract.
437
452
let res = setup. call_is_upgraded ( & setup. unauth_account ) . await ?;
438
- assert_failure_with ( res, "Action #0: MethodResolveError(MethodNotFound)" ) ;
453
+ assert_method_not_found_failure ( res) ;
439
454
440
455
// Compile the other version of the contract and stage its code.
441
456
let code = common:: repo:: compile_project ( Path :: new ( PROJECT_PATH_2 ) , "upgradable_2" ) . await ?;
@@ -447,7 +462,7 @@ async fn test_deploy_code_and_call_method() -> anyhow::Result<()> {
447
462
setup. assert_staged_code ( Some ( code) ) . await ;
448
463
449
464
// Deploy staged code.
450
- let res = setup. upgradable_contract . up_deploy_code ( & dao) . await ?;
465
+ let res = setup. upgradable_contract . up_deploy_code ( & dao, None ) . await ?;
451
466
assert_success_with_unit_return ( res) ;
452
467
453
468
// The newly deployed contract defines the function `is_upgraded`. Calling it successfully
@@ -458,6 +473,94 @@ async fn test_deploy_code_and_call_method() -> anyhow::Result<()> {
458
473
Ok ( ( ) )
459
474
}
460
475
476
+ /// Deploys a new version of the contract that requires state migration and verifies the migration
477
+ /// succeeded.
478
+ #[ tokio:: test]
479
+ async fn test_deploy_code_with_migration ( ) -> anyhow:: Result < ( ) > {
480
+ let worker = workspaces:: sandbox ( ) . await ?;
481
+ let dao = worker. dev_create_account ( ) . await ?;
482
+ let setup = Setup :: new ( worker. clone ( ) , Some ( dao. id ( ) . clone ( ) ) , None ) . await ?;
483
+
484
+ // Verify function `is_migrated` is not defined in the initial contract.
485
+ let res = setup. call_is_migrated ( & setup. unauth_account ) . await ?;
486
+ assert_method_not_found_failure ( res) ;
487
+
488
+ // Compile the other version of the contract and stage its code.
489
+ let code = common:: repo:: compile_project (
490
+ Path :: new ( PROJECT_PATH_STATE_MIGRATION ) ,
491
+ "upgradable_state_migration" ,
492
+ )
493
+ . await ?;
494
+ let res = setup
495
+ . upgradable_contract
496
+ . up_stage_code ( & dao, code. clone ( ) )
497
+ . await ?;
498
+ assert_success_with_unit_return ( res) ;
499
+ setup. assert_staged_code ( Some ( code) ) . await ;
500
+
501
+ // Deploy staged code and call the new contract's `migrate` method.
502
+ let function_call_args = FunctionCallArgs {
503
+ function_name : "migrate" . to_string ( ) ,
504
+ arguments : Vec :: new ( ) ,
505
+ amount : 0 ,
506
+ gas : Gas :: ONE_TERA ,
507
+ } ;
508
+ let res = setup
509
+ . upgradable_contract
510
+ . up_deploy_code ( & dao, Some ( function_call_args) )
511
+ . await ?;
512
+ assert_success_with_unit_return ( res) ;
513
+
514
+ // The newly deployed contract defines the function `is_migrated`. Calling it successfully
515
+ // verifies the staged contract is deployed and state migration succeeded.
516
+ let res = setup. call_is_migrated ( & setup. unauth_account ) . await ?;
517
+ assert_success_with ( res, true ) ;
518
+
519
+ Ok ( ( ) )
520
+ }
521
+
522
+ /// Deploys a new version of the contract and, batched with the `DeployContractAction`, calls a
523
+ /// migration method that fails. Verifies the failure rolls back the deployment, i.e. the initial
524
+ /// code remains active.
525
+ #[ tokio:: test]
526
+ async fn test_deploy_code_with_migration_failure_rollback ( ) -> anyhow:: Result < ( ) > {
527
+ let worker = workspaces:: sandbox ( ) . await ?;
528
+ let dao = worker. dev_create_account ( ) . await ?;
529
+ let setup = Setup :: new ( worker. clone ( ) , Some ( dao. id ( ) . clone ( ) ) , None ) . await ?;
530
+
531
+ // Compile the other version of the contract and stage its code.
532
+ let code = common:: repo:: compile_project (
533
+ Path :: new ( PROJECT_PATH_STATE_MIGRATION ) ,
534
+ "upgradable_state_migration" ,
535
+ )
536
+ . await ?;
537
+ let res = setup
538
+ . upgradable_contract
539
+ . up_stage_code ( & dao, code. clone ( ) )
540
+ . await ?;
541
+ assert_success_with_unit_return ( res) ;
542
+ setup. assert_staged_code ( Some ( code) ) . await ;
543
+
544
+ // Deploy staged code and call the new contract's `migrate_with_failure` method.
545
+ let function_call_args = FunctionCallArgs {
546
+ function_name : "migrate_with_failure" . to_string ( ) ,
547
+ arguments : Vec :: new ( ) ,
548
+ amount : 0 ,
549
+ gas : Gas :: ONE_TERA ,
550
+ } ;
551
+ let res = setup
552
+ . upgradable_contract
553
+ . up_deploy_code ( & dao, Some ( function_call_args) )
554
+ . await ?;
555
+ assert_failure_with ( res, "Failing migration on purpose" ) ;
556
+
557
+ // Verify `code` wasn't deployed by calling a function that is defined only in the initial
558
+ // contract but not in the contract contract corresponding to `code`.
559
+ setup. assert_is_set_up ( & setup. unauth_account ) . await ;
560
+
561
+ Ok ( ( ) )
562
+ }
563
+
461
564
#[ tokio:: test]
462
565
async fn test_deploy_code_with_delay ( ) -> anyhow:: Result < ( ) > {
463
566
let worker = workspaces:: sandbox ( ) . await ?;
@@ -483,7 +586,7 @@ async fn test_deploy_code_with_delay() -> anyhow::Result<()> {
483
586
fast_forward_beyond ( & worker, staging_duration) . await ;
484
587
485
588
// Deploy staged code.
486
- let res = setup. upgradable_contract . up_deploy_code ( & dao) . await ?;
589
+ let res = setup. upgradable_contract . up_deploy_code ( & dao, None ) . await ?;
487
590
assert_success_with_unit_return ( res) ;
488
591
489
592
Ok ( ( ) )
@@ -513,9 +616,13 @@ async fn test_deploy_code_with_delay_failure_too_early() -> anyhow::Result<()> {
513
616
fast_forward_beyond ( & worker, sdk_duration_from_secs ( 1 ) ) . await ;
514
617
515
618
// Verify trying to deploy staged code fails.
516
- let res = setup. upgradable_contract . up_deploy_code ( & dao) . await ?;
619
+ let res = setup. upgradable_contract . up_deploy_code ( & dao, None ) . await ?;
517
620
assert_failure_with ( res, ERR_MSG_DEPLOY_CODE_TOO_EARLY ) ;
518
621
622
+ // Verify `code` wasn't deployed by calling a function that is defined only in the initial
623
+ // contract but not in the contract contract corresponding to `code`.
624
+ setup. assert_is_set_up ( & setup. unauth_account ) . await ;
625
+
519
626
Ok ( ( ) )
520
627
}
521
628
@@ -538,7 +645,7 @@ async fn test_deploy_code_permission_failure() -> anyhow::Result<()> {
538
645
// call this method.
539
646
let res = setup
540
647
. upgradable_contract
541
- . up_deploy_code ( & setup. unauth_account )
648
+ . up_deploy_code ( & setup. unauth_account , None )
542
649
. await ?;
543
650
assert_insufficient_acl_permissions (
544
651
res,
@@ -577,7 +684,7 @@ async fn test_deploy_code_empty_failure() -> anyhow::Result<()> {
577
684
// The staging timestamp is set when staging code and removed when unstaging code. So when there
578
685
// is no code staged, there is no staging timestamp. Hence the error message regarding a missing
579
686
// staging timestamp is expected.
580
- let res = setup. upgradable_contract . up_deploy_code ( & dao) . await ?;
687
+ let res = setup. upgradable_contract . up_deploy_code ( & dao, None ) . await ?;
581
688
assert_failure_with ( res, ERR_MSG_NO_STAGING_TS ) ;
582
689
583
690
Ok ( ( ) )
@@ -825,7 +932,7 @@ async fn test_acl_permission_scope() -> anyhow::Result<()> {
825
932
// deploy code.
826
933
let res = setup
827
934
. upgradable_contract
828
- . up_deploy_code ( & setup. unauth_account )
935
+ . up_deploy_code ( & setup. unauth_account , None )
829
936
. await ?;
830
937
assert_insufficient_acl_permissions (
831
938
res,
0 commit comments