github.com/onflow/flow-go@v0.33.17/fvm/accounts_test.go (about)

     1  package fvm_test
     2  
     3  import (
     4  	"fmt"
     5  	"strconv"
     6  	"testing"
     7  
     8  	"github.com/onflow/cadence"
     9  	"github.com/onflow/cadence/encoding/ccf"
    10  	jsoncdc "github.com/onflow/cadence/encoding/json"
    11  	"github.com/onflow/cadence/runtime/format"
    12  	"github.com/stretchr/testify/assert"
    13  	"github.com/stretchr/testify/require"
    14  
    15  	"github.com/onflow/flow-go/engine/execution/testutil"
    16  	"github.com/onflow/flow-go/fvm"
    17  	"github.com/onflow/flow-go/fvm/storage/snapshot"
    18  	"github.com/onflow/flow-go/model/flow"
    19  	"github.com/onflow/flow-go/utils/unittest"
    20  )
    21  
    22  type errorOnAddressSnapshotWrapper struct {
    23  	snapshotTree snapshot.SnapshotTree
    24  	owner        flow.Address
    25  }
    26  
    27  func (s errorOnAddressSnapshotWrapper) Get(
    28  	id flow.RegisterID,
    29  ) (
    30  	flow.RegisterValue,
    31  	error,
    32  ) {
    33  	// return error if id.Owner is the same as the owner of the wrapper
    34  	if id.Owner == string(s.owner.Bytes()) {
    35  		return nil, fmt.Errorf("error getting register %s", id)
    36  	}
    37  
    38  	return s.snapshotTree.Get(id)
    39  }
    40  
    41  func createAccount(
    42  	t *testing.T,
    43  	vm fvm.VM,
    44  	chain flow.Chain,
    45  	ctx fvm.Context,
    46  	snapshotTree snapshot.SnapshotTree,
    47  ) (
    48  	snapshot.SnapshotTree,
    49  	flow.Address,
    50  ) {
    51  	ctx = fvm.NewContextFromParent(
    52  		ctx,
    53  		fvm.WithAuthorizationChecksEnabled(false),
    54  		fvm.WithSequenceNumberCheckAndIncrementEnabled(false),
    55  	)
    56  
    57  	txBody := flow.NewTransactionBody().
    58  		SetScript([]byte(createAccountTransaction)).
    59  		AddAuthorizer(chain.ServiceAddress())
    60  
    61  	executionSnapshot, output, err := vm.Run(
    62  		ctx,
    63  		fvm.Transaction(txBody, 0),
    64  		snapshotTree)
    65  	require.NoError(t, err)
    66  	require.NoError(t, output.Err)
    67  
    68  	snapshotTree = snapshotTree.Append(executionSnapshot)
    69  
    70  	accountCreatedEvents := filterAccountCreatedEvents(output.Events)
    71  
    72  	require.Len(t, accountCreatedEvents, 1)
    73  
    74  	data, err := ccf.Decode(nil, accountCreatedEvents[0].Payload)
    75  	require.NoError(t, err)
    76  	address := flow.ConvertAddress(
    77  		data.(cadence.Event).Fields[0].(cadence.Address))
    78  
    79  	return snapshotTree, address
    80  }
    81  
    82  type accountKeyAPIVersion string
    83  
    84  const (
    85  	accountKeyAPIVersionV1 accountKeyAPIVersion = "V1"
    86  	accountKeyAPIVersionV2 accountKeyAPIVersion = "V2"
    87  )
    88  
    89  func addAccountKey(
    90  	t *testing.T,
    91  	vm fvm.VM,
    92  	ctx fvm.Context,
    93  	snapshotTree snapshot.SnapshotTree,
    94  	address flow.Address,
    95  	apiVersion accountKeyAPIVersion,
    96  ) (
    97  	snapshot.SnapshotTree,
    98  	flow.AccountPublicKey,
    99  ) {
   100  
   101  	privateKey, err := unittest.AccountKeyDefaultFixture()
   102  	require.NoError(t, err)
   103  
   104  	publicKeyA, cadencePublicKey := newAccountKey(t, privateKey, apiVersion)
   105  
   106  	var addAccountKeyTx accountKeyAPIVersion
   107  	if apiVersion == accountKeyAPIVersionV1 {
   108  		addAccountKeyTx = addAccountKeyTransaction
   109  	} else {
   110  		addAccountKeyTx = addAccountKeyTransactionV2
   111  	}
   112  
   113  	txBody := flow.NewTransactionBody().
   114  		SetScript([]byte(addAccountKeyTx)).
   115  		AddArgument(cadencePublicKey).
   116  		AddAuthorizer(address)
   117  
   118  	executionSnapshot, output, err := vm.Run(
   119  		ctx,
   120  		fvm.Transaction(txBody, 0),
   121  		snapshotTree)
   122  	require.NoError(t, err)
   123  	require.NoError(t, output.Err)
   124  
   125  	snapshotTree = snapshotTree.Append(executionSnapshot)
   126  
   127  	return snapshotTree, publicKeyA
   128  }
   129  
   130  func addAccountCreator(
   131  	t *testing.T,
   132  	vm fvm.VM,
   133  	chain flow.Chain,
   134  	ctx fvm.Context,
   135  	snapshotTree snapshot.SnapshotTree,
   136  	account flow.Address,
   137  ) snapshot.SnapshotTree {
   138  	script := []byte(
   139  		fmt.Sprintf(addAccountCreatorTransactionTemplate,
   140  			chain.ServiceAddress().String(),
   141  			account.String(),
   142  		),
   143  	)
   144  
   145  	txBody := flow.NewTransactionBody().
   146  		SetScript(script).
   147  		AddAuthorizer(chain.ServiceAddress())
   148  
   149  	executionSnapshot, output, err := vm.Run(
   150  		ctx,
   151  		fvm.Transaction(txBody, 0),
   152  		snapshotTree)
   153  	require.NoError(t, err)
   154  	require.NoError(t, output.Err)
   155  
   156  	return snapshotTree.Append(executionSnapshot)
   157  }
   158  
   159  func removeAccountCreator(
   160  	t *testing.T,
   161  	vm fvm.VM,
   162  	chain flow.Chain,
   163  	ctx fvm.Context,
   164  	snapshotTree snapshot.SnapshotTree,
   165  	account flow.Address,
   166  ) snapshot.SnapshotTree {
   167  	script := []byte(
   168  		fmt.Sprintf(
   169  			removeAccountCreatorTransactionTemplate,
   170  			chain.ServiceAddress(),
   171  			account.String(),
   172  		),
   173  	)
   174  
   175  	txBody := flow.NewTransactionBody().
   176  		SetScript(script).
   177  		AddAuthorizer(chain.ServiceAddress())
   178  
   179  	executionSnapshot, output, err := vm.Run(
   180  		ctx,
   181  		fvm.Transaction(txBody, 0),
   182  		snapshotTree)
   183  	require.NoError(t, err)
   184  	require.NoError(t, output.Err)
   185  
   186  	return snapshotTree.Append(executionSnapshot)
   187  }
   188  
   189  const createAccountTransaction = `
   190  transaction {
   191    prepare(signer: AuthAccount) {
   192      let account = AuthAccount(payer: signer)
   193    }
   194  }
   195  `
   196  
   197  const createMultipleAccountsTransaction = `
   198  transaction {
   199    prepare(signer: AuthAccount) {
   200      let accountA = AuthAccount(payer: signer)
   201      let accountB = AuthAccount(payer: signer)
   202      let accountC = AuthAccount(payer: signer)
   203    }
   204  }
   205  `
   206  
   207  const addAccountKeyTransaction = `
   208  transaction(key: [UInt8]) {
   209    prepare(signer: AuthAccount) {
   210      signer.addPublicKey(key)
   211    }
   212  }
   213  `
   214  const addAccountKeyTransactionV2 = `
   215  transaction(key: [UInt8]) {
   216    prepare(signer: AuthAccount) {
   217      let publicKey = PublicKey(
   218  	  publicKey: key,
   219  	  signatureAlgorithm: SignatureAlgorithm.ECDSA_P256
   220  	)
   221      signer.keys.add(
   222        publicKey: publicKey,
   223        hashAlgorithm: HashAlgorithm.SHA3_256,
   224        weight: 1000.0
   225      )
   226    }
   227  }
   228  `
   229  
   230  const addMultipleAccountKeysTransaction = `
   231  transaction(key1: [UInt8], key2: [UInt8]) {
   232    prepare(signer: AuthAccount) {
   233      signer.addPublicKey(key1)
   234      signer.addPublicKey(key2)
   235    }
   236  }
   237  `
   238  
   239  const addMultipleAccountKeysTransactionV2 = `
   240  transaction(key1: [UInt8], key2: [UInt8]) {
   241    prepare(signer: AuthAccount) {
   242      for key in [key1, key2] {
   243        let publicKey = PublicKey(
   244  	    publicKey: key,
   245  	    signatureAlgorithm: SignatureAlgorithm.ECDSA_P256
   246  	  )
   247        signer.keys.add(
   248          publicKey: publicKey,
   249          hashAlgorithm: HashAlgorithm.SHA3_256,
   250          weight: 1000.0
   251        )
   252      }
   253    }
   254  }
   255  `
   256  
   257  const removeAccountKeyTransaction = `
   258  transaction(key: Int) {
   259    prepare(signer: AuthAccount) {
   260      signer.removePublicKey(key)
   261    }
   262  }
   263  `
   264  
   265  const revokeAccountKeyTransaction = `
   266  transaction(keyIndex: Int) {
   267    prepare(signer: AuthAccount) {
   268      signer.keys.revoke(keyIndex: keyIndex)
   269    }
   270  }
   271  `
   272  
   273  const removeMultipleAccountKeysTransaction = `
   274  transaction(key1: Int, key2: Int) {
   275    prepare(signer: AuthAccount) {
   276      signer.removePublicKey(key1)
   277      signer.removePublicKey(key2)
   278    }
   279  }
   280  `
   281  
   282  const revokeMultipleAccountKeysTransaction = `
   283  transaction(keyIndex1: Int, keyIndex2: Int) {
   284    prepare(signer: AuthAccount) {
   285      for keyIndex in [keyIndex1, keyIndex2] {
   286        signer.keys.revoke(keyIndex: keyIndex)
   287      }
   288    }
   289  }
   290  `
   291  
   292  const removeAccountCreatorTransactionTemplate = `
   293  import FlowServiceAccount from 0x%s
   294  transaction {
   295  	let serviceAccountAdmin: &FlowServiceAccount.Administrator
   296  	prepare(signer: AuthAccount) {
   297  		// Borrow reference to FlowServiceAccount Administrator resource.
   298  		//
   299  		self.serviceAccountAdmin = signer.borrow<&FlowServiceAccount.Administrator>(from: /storage/flowServiceAdmin)
   300  			?? panic("Unable to borrow reference to administrator resource")
   301  	}
   302  	execute {
   303  		// Remove account from account creator allowlist.
   304  		//
   305  		// Will emit AccountCreatorRemoved(accountCreator: accountCreator).
   306  		//
   307  		self.serviceAccountAdmin.removeAccountCreator(0x%s)
   308  	}
   309  }
   310  `
   311  
   312  const addAccountCreatorTransactionTemplate = `
   313  import FlowServiceAccount from 0x%s
   314  transaction {
   315  	let serviceAccountAdmin: &FlowServiceAccount.Administrator
   316  	prepare(signer: AuthAccount) {
   317  		// Borrow reference to FlowServiceAccount Administrator resource.
   318  		//
   319  		self.serviceAccountAdmin = signer.borrow<&FlowServiceAccount.Administrator>(from: /storage/flowServiceAdmin)
   320  			?? panic("Unable to borrow reference to administrator resource")
   321  	}
   322  	execute {
   323  		// Add account to account creator allowlist.
   324  		//
   325  		// Will emit AccountCreatorAdded(accountCreator: accountCreator).
   326  		//
   327  		self.serviceAccountAdmin.addAccountCreator(0x%s)
   328  	}
   329  }
   330  `
   331  
   332  const getAccountKeyTransaction = `
   333  transaction(keyIndex: Int) {
   334    prepare(signer: AuthAccount) {
   335      var key :AccountKey? = signer.keys.get(keyIndex: keyIndex)
   336      log(key)
   337    }
   338  }
   339  `
   340  
   341  const getMultipleAccountKeysTransaction = `
   342  transaction(keyIndex1: Int, keyIndex2: Int) {
   343    prepare(signer: AuthAccount) {
   344      for keyIndex in [keyIndex1, keyIndex2] {
   345        var key :AccountKey? = signer.keys.get(keyIndex: keyIndex)
   346        log(key)
   347      }
   348    }
   349  }
   350  `
   351  
   352  func newAccountKey(
   353  	tb testing.TB,
   354  	privateKey *flow.AccountPrivateKey,
   355  	apiVersion accountKeyAPIVersion,
   356  ) (
   357  	publicKey flow.AccountPublicKey,
   358  	encodedCadencePublicKey []byte,
   359  ) {
   360  	publicKey = privateKey.PublicKey(fvm.AccountKeyWeightThreshold)
   361  
   362  	var publicKeyBytes []byte
   363  	if apiVersion == accountKeyAPIVersionV1 {
   364  		var err error
   365  		publicKeyBytes, err = flow.EncodeRuntimeAccountPublicKey(publicKey)
   366  		require.NoError(tb, err)
   367  	} else {
   368  		publicKeyBytes = publicKey.PublicKey.Encode()
   369  	}
   370  
   371  	cadencePublicKey := testutil.BytesToCadenceArray(publicKeyBytes)
   372  	encodedCadencePublicKey, err := jsoncdc.Encode(cadencePublicKey)
   373  	require.NoError(tb, err)
   374  
   375  	return publicKey, encodedCadencePublicKey
   376  }
   377  
   378  func TestCreateAccount(t *testing.T) {
   379  
   380  	options := []fvm.Option{
   381  		fvm.WithAuthorizationChecksEnabled(false),
   382  		fvm.WithSequenceNumberCheckAndIncrementEnabled(false),
   383  	}
   384  
   385  	t.Run("Single account",
   386  		newVMTest().withContextOptions(options...).
   387  			run(func(t *testing.T, vm fvm.VM, chain flow.Chain, ctx fvm.Context, snapshotTree snapshot.SnapshotTree) {
   388  				snapshotTree, payer := createAccount(
   389  					t,
   390  					vm,
   391  					chain,
   392  					ctx,
   393  					snapshotTree)
   394  
   395  				txBody := flow.NewTransactionBody().
   396  					SetScript([]byte(createAccountTransaction)).
   397  					AddAuthorizer(payer)
   398  
   399  				executionSnapshot, output, err := vm.Run(
   400  					ctx,
   401  					fvm.Transaction(txBody, 0),
   402  					snapshotTree)
   403  				require.NoError(t, err)
   404  				assert.NoError(t, output.Err)
   405  
   406  				snapshotTree = snapshotTree.Append(executionSnapshot)
   407  
   408  				accountCreatedEvents := filterAccountCreatedEvents(output.Events)
   409  				require.Len(t, accountCreatedEvents, 1)
   410  
   411  				data, err := ccf.Decode(nil, accountCreatedEvents[0].Payload)
   412  				require.NoError(t, err)
   413  				address := flow.ConvertAddress(
   414  					data.(cadence.Event).Fields[0].(cadence.Address))
   415  
   416  				account, err := vm.GetAccount(ctx, address, snapshotTree)
   417  				require.NoError(t, err)
   418  				require.NotNil(t, account)
   419  			}),
   420  	)
   421  
   422  	t.Run("Multiple accounts",
   423  		newVMTest().withContextOptions(options...).
   424  			run(func(t *testing.T, vm fvm.VM, chain flow.Chain, ctx fvm.Context, snapshotTree snapshot.SnapshotTree) {
   425  				const count = 3
   426  
   427  				snapshotTree, payer := createAccount(
   428  					t,
   429  					vm,
   430  					chain,
   431  					ctx,
   432  					snapshotTree)
   433  
   434  				txBody := flow.NewTransactionBody().
   435  					SetScript([]byte(createMultipleAccountsTransaction)).
   436  					AddAuthorizer(payer)
   437  
   438  				executionSnapshot, output, err := vm.Run(
   439  					ctx,
   440  					fvm.Transaction(txBody, 0),
   441  					snapshotTree)
   442  				require.NoError(t, err)
   443  				assert.NoError(t, output.Err)
   444  
   445  				snapshotTree = snapshotTree.Append(executionSnapshot)
   446  
   447  				accountCreatedEventCount := 0
   448  				for _, event := range output.Events {
   449  					if event.Type != flow.EventAccountCreated {
   450  						continue
   451  					}
   452  					accountCreatedEventCount += 1
   453  
   454  					data, err := ccf.Decode(nil, event.Payload)
   455  					require.NoError(t, err)
   456  					address := flow.ConvertAddress(
   457  						data.(cadence.Event).Fields[0].(cadence.Address))
   458  
   459  					account, err := vm.GetAccount(ctx, address, snapshotTree)
   460  					require.NoError(t, err)
   461  					require.NotNil(t, account)
   462  				}
   463  				require.Equal(t, count, accountCreatedEventCount)
   464  			}),
   465  	)
   466  }
   467  
   468  func TestCreateAccount_WithRestrictedAccountCreation(t *testing.T) {
   469  
   470  	options := []fvm.Option{
   471  		fvm.WithAuthorizationChecksEnabled(false),
   472  		fvm.WithSequenceNumberCheckAndIncrementEnabled(false),
   473  	}
   474  
   475  	t.Run("Unauthorized account payer",
   476  		newVMTest().
   477  			withContextOptions(options...).
   478  			withBootstrapProcedureOptions(fvm.WithRestrictedAccountCreationEnabled(true)).
   479  			run(func(t *testing.T, vm fvm.VM, chain flow.Chain, ctx fvm.Context, snapshotTree snapshot.SnapshotTree) {
   480  				snapshotTree, payer := createAccount(
   481  					t,
   482  					vm,
   483  					chain,
   484  					ctx,
   485  					snapshotTree)
   486  
   487  				txBody := flow.NewTransactionBody().
   488  					SetScript([]byte(createAccountTransaction)).
   489  					AddAuthorizer(payer)
   490  
   491  				_, output, err := vm.Run(
   492  					ctx,
   493  					fvm.Transaction(txBody, 0),
   494  					snapshotTree)
   495  				require.NoError(t, err)
   496  
   497  				assert.Error(t, output.Err)
   498  			}),
   499  	)
   500  
   501  	t.Run("Authorized account payer",
   502  		newVMTest().withContextOptions(options...).
   503  			withBootstrapProcedureOptions(fvm.WithRestrictedAccountCreationEnabled(true)).
   504  			run(func(t *testing.T, vm fvm.VM, chain flow.Chain, ctx fvm.Context, snapshotTree snapshot.SnapshotTree) {
   505  				txBody := flow.NewTransactionBody().
   506  					SetScript([]byte(createAccountTransaction)).
   507  					AddAuthorizer(chain.ServiceAddress())
   508  
   509  				_, output, err := vm.Run(
   510  					ctx,
   511  					fvm.Transaction(txBody, 0),
   512  					snapshotTree)
   513  				require.NoError(t, err)
   514  
   515  				assert.NoError(t, output.Err)
   516  			}),
   517  	)
   518  
   519  	t.Run("Account payer added to allowlist",
   520  		newVMTest().withContextOptions(options...).
   521  			withBootstrapProcedureOptions(fvm.WithRestrictedAccountCreationEnabled(true)).
   522  			run(func(t *testing.T, vm fvm.VM, chain flow.Chain, ctx fvm.Context, snapshotTree snapshot.SnapshotTree) {
   523  				snapshotTree, payer := createAccount(
   524  					t,
   525  					vm,
   526  					chain,
   527  					ctx,
   528  					snapshotTree)
   529  				snapshotTree = addAccountCreator(
   530  					t,
   531  					vm,
   532  					chain,
   533  					ctx,
   534  					snapshotTree,
   535  					payer)
   536  
   537  				txBody := flow.NewTransactionBody().
   538  					SetScript([]byte(createAccountTransaction)).
   539  					SetPayer(payer).
   540  					AddAuthorizer(payer)
   541  
   542  				_, output, err := vm.Run(
   543  					ctx,
   544  					fvm.Transaction(txBody, 0),
   545  					snapshotTree)
   546  				require.NoError(t, err)
   547  
   548  				assert.NoError(t, output.Err)
   549  			}),
   550  	)
   551  
   552  	t.Run("Account payer removed from allowlist",
   553  		newVMTest().withContextOptions(options...).
   554  			withBootstrapProcedureOptions(fvm.WithRestrictedAccountCreationEnabled(true)).
   555  			run(func(t *testing.T, vm fvm.VM, chain flow.Chain, ctx fvm.Context, snapshotTree snapshot.SnapshotTree) {
   556  				snapshotTree, payer := createAccount(
   557  					t,
   558  					vm,
   559  					chain,
   560  					ctx,
   561  					snapshotTree)
   562  				snapshotTree = addAccountCreator(
   563  					t,
   564  					vm,
   565  					chain,
   566  					ctx,
   567  					snapshotTree,
   568  					payer)
   569  
   570  				txBody := flow.NewTransactionBody().
   571  					SetScript([]byte(createAccountTransaction)).
   572  					AddAuthorizer(payer)
   573  
   574  				executionSnapshot, output, err := vm.Run(
   575  					ctx,
   576  					fvm.Transaction(txBody, 0),
   577  					snapshotTree)
   578  				require.NoError(t, err)
   579  				assert.NoError(t, output.Err)
   580  
   581  				snapshotTree = snapshotTree.Append(executionSnapshot)
   582  
   583  				snapshotTree = removeAccountCreator(
   584  					t,
   585  					vm,
   586  					chain,
   587  					ctx,
   588  					snapshotTree,
   589  					payer)
   590  
   591  				_, output, err = vm.Run(
   592  					ctx,
   593  					fvm.Transaction(txBody, 0),
   594  					snapshotTree)
   595  				require.NoError(t, err)
   596  
   597  				assert.Error(t, output.Err)
   598  			}),
   599  	)
   600  }
   601  
   602  func TestAddAccountKey(t *testing.T) {
   603  
   604  	options := []fvm.Option{
   605  		fvm.WithAuthorizationChecksEnabled(false),
   606  		fvm.WithSequenceNumberCheckAndIncrementEnabled(false),
   607  	}
   608  
   609  	type addKeyTest struct {
   610  		source     string
   611  		apiVersion accountKeyAPIVersion
   612  	}
   613  
   614  	// Add a single key
   615  
   616  	singleKeyTests := []addKeyTest{
   617  		{
   618  			source:     addAccountKeyTransaction,
   619  			apiVersion: accountKeyAPIVersionV1,
   620  		},
   621  		{
   622  			source:     addAccountKeyTransactionV2,
   623  			apiVersion: accountKeyAPIVersionV2,
   624  		},
   625  	}
   626  
   627  	for _, test := range singleKeyTests {
   628  
   629  		t.Run(fmt.Sprintf("Add to empty key list %s", test.apiVersion),
   630  			newVMTest().withContextOptions(options...).
   631  				run(func(t *testing.T, vm fvm.VM, chain flow.Chain, ctx fvm.Context, snapshotTree snapshot.SnapshotTree) {
   632  					snapshotTree, address := createAccount(
   633  						t,
   634  						vm,
   635  						chain,
   636  						ctx,
   637  						snapshotTree)
   638  
   639  					before, err := vm.GetAccount(ctx, address, snapshotTree)
   640  					require.NoError(t, err)
   641  					assert.Empty(t, before.Keys)
   642  
   643  					privateKey, err := unittest.AccountKeyDefaultFixture()
   644  					require.NoError(t, err)
   645  
   646  					publicKeyA, cadencePublicKey := newAccountKey(t, privateKey, test.apiVersion)
   647  
   648  					txBody := flow.NewTransactionBody().
   649  						SetScript([]byte(test.source)).
   650  						AddArgument(cadencePublicKey).
   651  						AddAuthorizer(address)
   652  
   653  					executionSnapshot, output, err := vm.Run(
   654  						ctx,
   655  						fvm.Transaction(txBody, 0),
   656  						snapshotTree)
   657  					require.NoError(t, err)
   658  
   659  					assert.NoError(t, output.Err)
   660  
   661  					snapshotTree = snapshotTree.Append(executionSnapshot)
   662  
   663  					after, err := vm.GetAccount(ctx, address, snapshotTree)
   664  					require.NoError(t, err)
   665  
   666  					require.Len(t, after.Keys, 1)
   667  
   668  					publicKeyB := after.Keys[0]
   669  
   670  					assert.Equal(t, publicKeyA.PublicKey, publicKeyB.PublicKey)
   671  					assert.Equal(t, publicKeyA.SignAlgo, publicKeyB.SignAlgo)
   672  					assert.Equal(t, publicKeyA.HashAlgo, publicKeyB.HashAlgo)
   673  					assert.Equal(t, publicKeyA.Weight, publicKeyB.Weight)
   674  				}),
   675  		)
   676  
   677  		t.Run(fmt.Sprintf("Add to non-empty key list %s", test.apiVersion),
   678  			newVMTest().withContextOptions(options...).
   679  				run(func(t *testing.T, vm fvm.VM, chain flow.Chain, ctx fvm.Context, snapshotTree snapshot.SnapshotTree) {
   680  					snapshotTree, address := createAccount(
   681  						t,
   682  						vm,
   683  						chain,
   684  						ctx,
   685  						snapshotTree)
   686  
   687  					snapshotTree, publicKey1 := addAccountKey(
   688  						t,
   689  						vm,
   690  						ctx,
   691  						snapshotTree,
   692  						address,
   693  						test.apiVersion)
   694  
   695  					before, err := vm.GetAccount(ctx, address, snapshotTree)
   696  					require.NoError(t, err)
   697  					assert.Len(t, before.Keys, 1)
   698  
   699  					privateKey, err := unittest.AccountKeyDefaultFixture()
   700  					require.NoError(t, err)
   701  
   702  					publicKey2, publicKey2Arg := newAccountKey(t, privateKey, test.apiVersion)
   703  
   704  					txBody := flow.NewTransactionBody().
   705  						SetScript([]byte(test.source)).
   706  						AddArgument(publicKey2Arg).
   707  						AddAuthorizer(address)
   708  
   709  					executionSnapshot, output, err := vm.Run(
   710  						ctx,
   711  						fvm.Transaction(txBody, 0),
   712  						snapshotTree)
   713  					require.NoError(t, err)
   714  					assert.NoError(t, output.Err)
   715  
   716  					snapshotTree = snapshotTree.Append(executionSnapshot)
   717  
   718  					after, err := vm.GetAccount(ctx, address, snapshotTree)
   719  					require.NoError(t, err)
   720  
   721  					expectedKeys := []flow.AccountPublicKey{
   722  						publicKey1,
   723  						publicKey2,
   724  					}
   725  
   726  					require.Len(t, after.Keys, len(expectedKeys))
   727  
   728  					for i, expectedKey := range expectedKeys {
   729  						actualKey := after.Keys[i]
   730  						assert.Equal(t, i, actualKey.Index)
   731  						assert.Equal(t, expectedKey.PublicKey, actualKey.PublicKey)
   732  						assert.Equal(t, expectedKey.SignAlgo, actualKey.SignAlgo)
   733  						assert.Equal(t, expectedKey.HashAlgo, actualKey.HashAlgo)
   734  						assert.Equal(t, expectedKey.Weight, actualKey.Weight)
   735  					}
   736  				}),
   737  		)
   738  
   739  		t.Run(fmt.Sprintf("Invalid key %s", test.apiVersion),
   740  			newVMTest().withContextOptions(options...).
   741  				run(func(t *testing.T, vm fvm.VM, chain flow.Chain, ctx fvm.Context, snapshotTree snapshot.SnapshotTree) {
   742  					snapshotTree, address := createAccount(
   743  						t,
   744  						vm,
   745  						chain,
   746  						ctx,
   747  						snapshotTree)
   748  
   749  					invalidPublicKey := testutil.BytesToCadenceArray([]byte{1, 2, 3})
   750  					invalidPublicKeyArg, err := jsoncdc.Encode(invalidPublicKey)
   751  					require.NoError(t, err)
   752  
   753  					txBody := flow.NewTransactionBody().
   754  						SetScript([]byte(test.source)).
   755  						AddArgument(invalidPublicKeyArg).
   756  						AddAuthorizer(address)
   757  
   758  					executionSnapshot, output, err := vm.Run(
   759  						ctx,
   760  						fvm.Transaction(txBody, 0),
   761  						snapshotTree)
   762  					require.NoError(t, err)
   763  					assert.Error(t, output.Err)
   764  
   765  					snapshotTree = snapshotTree.Append(executionSnapshot)
   766  
   767  					after, err := vm.GetAccount(ctx, address, snapshotTree)
   768  					require.NoError(t, err)
   769  
   770  					assert.Empty(t, after.Keys)
   771  				}),
   772  		)
   773  	}
   774  
   775  	// Add multiple keys
   776  
   777  	multipleKeysTests := []addKeyTest{
   778  		{
   779  			source:     addMultipleAccountKeysTransaction,
   780  			apiVersion: accountKeyAPIVersionV1,
   781  		},
   782  		{
   783  			source:     addMultipleAccountKeysTransactionV2,
   784  			apiVersion: accountKeyAPIVersionV2,
   785  		},
   786  	}
   787  
   788  	for _, test := range multipleKeysTests {
   789  		t.Run(fmt.Sprintf("Multiple keys %s", test.apiVersion),
   790  			newVMTest().withContextOptions(options...).
   791  				run(func(t *testing.T, vm fvm.VM, chain flow.Chain, ctx fvm.Context, snapshotTree snapshot.SnapshotTree) {
   792  					snapshotTree, address := createAccount(
   793  						t,
   794  						vm,
   795  						chain,
   796  						ctx,
   797  						snapshotTree)
   798  
   799  					before, err := vm.GetAccount(ctx, address, snapshotTree)
   800  					require.NoError(t, err)
   801  					assert.Empty(t, before.Keys)
   802  
   803  					privateKey1, err := unittest.AccountKeyDefaultFixture()
   804  					require.NoError(t, err)
   805  
   806  					privateKey2, err := unittest.AccountKeyDefaultFixture()
   807  					require.NoError(t, err)
   808  
   809  					publicKey1, publicKey1Arg := newAccountKey(t, privateKey1, test.apiVersion)
   810  					publicKey2, publicKey2Arg := newAccountKey(t, privateKey2, test.apiVersion)
   811  
   812  					txBody := flow.NewTransactionBody().
   813  						SetScript([]byte(test.source)).
   814  						AddArgument(publicKey1Arg).
   815  						AddArgument(publicKey2Arg).
   816  						AddAuthorizer(address)
   817  
   818  					executionSnapshot, output, err := vm.Run(
   819  						ctx,
   820  						fvm.Transaction(txBody, 0),
   821  						snapshotTree)
   822  					require.NoError(t, err)
   823  					assert.NoError(t, output.Err)
   824  
   825  					snapshotTree = snapshotTree.Append(executionSnapshot)
   826  
   827  					after, err := vm.GetAccount(ctx, address, snapshotTree)
   828  					require.NoError(t, err)
   829  
   830  					expectedKeys := []flow.AccountPublicKey{
   831  						publicKey1,
   832  						publicKey2,
   833  					}
   834  
   835  					require.Len(t, after.Keys, len(expectedKeys))
   836  
   837  					for i, expectedKey := range expectedKeys {
   838  						actualKey := after.Keys[i]
   839  						assert.Equal(t, expectedKey.PublicKey, actualKey.PublicKey)
   840  						assert.Equal(t, expectedKey.SignAlgo, actualKey.SignAlgo)
   841  						assert.Equal(t, expectedKey.HashAlgo, actualKey.HashAlgo)
   842  						assert.Equal(t, expectedKey.Weight, actualKey.Weight)
   843  					}
   844  				}),
   845  		)
   846  	}
   847  
   848  	t.Run("Invalid hash algorithms", func(t *testing.T) {
   849  
   850  		for _, hashAlgo := range []string{"SHA2_384", "SHA3_384"} {
   851  
   852  			t.Run(hashAlgo,
   853  				newVMTest().withContextOptions(options...).
   854  					run(func(t *testing.T, vm fvm.VM, chain flow.Chain, ctx fvm.Context, snapshotTree snapshot.SnapshotTree) {
   855  						snapshotTree, address := createAccount(
   856  							t,
   857  							vm,
   858  							chain,
   859  							ctx,
   860  							snapshotTree)
   861  
   862  						privateKey, err := unittest.AccountKeyDefaultFixture()
   863  						require.NoError(t, err)
   864  
   865  						_, publicKeyArg := newAccountKey(t, privateKey, accountKeyAPIVersionV2)
   866  
   867  						txBody := flow.NewTransactionBody().
   868  							SetScript([]byte(fmt.Sprintf(
   869  								`
   870  								transaction(key: [UInt8]) {
   871  								  prepare(signer: AuthAccount) {
   872  								    let publicKey = PublicKey(
   873  									  publicKey: key,
   874  									  signatureAlgorithm: SignatureAlgorithm.ECDSA_P256
   875  									)
   876  								    signer.keys.add(
   877  								      publicKey: publicKey,
   878  								      hashAlgorithm: HashAlgorithm.%s,
   879  								      weight: 1000.0
   880  								    )
   881  								  }
   882  								}
   883  								`,
   884  								hashAlgo,
   885  							))).
   886  							AddArgument(publicKeyArg).
   887  							AddAuthorizer(address)
   888  
   889  						executionSnapshot, output, err := vm.Run(
   890  							ctx,
   891  							fvm.Transaction(txBody, 0),
   892  							snapshotTree)
   893  						require.NoError(t, err)
   894  
   895  						require.Error(t, output.Err)
   896  						assert.ErrorContains(
   897  							t,
   898  							output.Err,
   899  							"hashing algorithm type not supported")
   900  
   901  						snapshotTree = snapshotTree.Append(executionSnapshot)
   902  
   903  						after, err := vm.GetAccount(ctx, address, snapshotTree)
   904  						require.NoError(t, err)
   905  
   906  						assert.Empty(t, after.Keys)
   907  					}),
   908  			)
   909  		}
   910  	})
   911  }
   912  
   913  func TestRemoveAccountKey(t *testing.T) {
   914  
   915  	options := []fvm.Option{
   916  		fvm.WithAuthorizationChecksEnabled(false),
   917  		fvm.WithSequenceNumberCheckAndIncrementEnabled(false),
   918  	}
   919  
   920  	type removeKeyTest struct {
   921  		source      string
   922  		apiVersion  accountKeyAPIVersion
   923  		expectError bool
   924  	}
   925  
   926  	// Remove a single key
   927  
   928  	singleKeyTests := []removeKeyTest{
   929  		{
   930  			source:      removeAccountKeyTransaction,
   931  			apiVersion:  accountKeyAPIVersionV1,
   932  			expectError: true,
   933  		},
   934  		{
   935  			source:      revokeAccountKeyTransaction,
   936  			apiVersion:  accountKeyAPIVersionV2,
   937  			expectError: false,
   938  		},
   939  	}
   940  
   941  	for _, test := range singleKeyTests {
   942  
   943  		t.Run(fmt.Sprintf("Non-existent key %s", test.apiVersion),
   944  			newVMTest().withContextOptions(options...).
   945  				run(func(t *testing.T, vm fvm.VM, chain flow.Chain, ctx fvm.Context, snapshotTree snapshot.SnapshotTree) {
   946  					snapshotTree, address := createAccount(
   947  						t,
   948  						vm,
   949  						chain,
   950  						ctx,
   951  						snapshotTree)
   952  
   953  					const keyCount = 2
   954  
   955  					for i := 0; i < keyCount; i++ {
   956  						snapshotTree, _ = addAccountKey(
   957  							t,
   958  							vm,
   959  							ctx,
   960  							snapshotTree,
   961  							address,
   962  							test.apiVersion)
   963  					}
   964  
   965  					before, err := vm.GetAccount(ctx, address, snapshotTree)
   966  					require.NoError(t, err)
   967  					assert.Len(t, before.Keys, keyCount)
   968  
   969  					for _, keyIndex := range []int{-1, keyCount, keyCount + 1} {
   970  						keyIndexArg, err := jsoncdc.Encode(cadence.NewInt(keyIndex))
   971  						require.NoError(t, err)
   972  
   973  						txBody := flow.NewTransactionBody().
   974  							SetScript([]byte(test.source)).
   975  							AddArgument(keyIndexArg).
   976  							AddAuthorizer(address)
   977  
   978  						executionSnapshot, output, err := vm.Run(
   979  							ctx,
   980  							fvm.Transaction(txBody, 0),
   981  							snapshotTree)
   982  						require.NoError(t, err)
   983  
   984  						if test.expectError {
   985  							assert.Error(t, output.Err)
   986  						} else {
   987  							assert.NoError(t, output.Err)
   988  						}
   989  
   990  						snapshotTree = snapshotTree.Append(executionSnapshot)
   991  					}
   992  
   993  					after, err := vm.GetAccount(ctx, address, snapshotTree)
   994  					require.NoError(t, err)
   995  					assert.Len(t, after.Keys, keyCount)
   996  
   997  					for _, publicKey := range after.Keys {
   998  						assert.False(t, publicKey.Revoked)
   999  					}
  1000  				}),
  1001  		)
  1002  
  1003  		t.Run(fmt.Sprintf("Existing key %s", test.apiVersion),
  1004  			newVMTest().withContextOptions(options...).
  1005  				run(func(t *testing.T, vm fvm.VM, chain flow.Chain, ctx fvm.Context, snapshotTree snapshot.SnapshotTree) {
  1006  					snapshotTree, address := createAccount(
  1007  						t,
  1008  						vm,
  1009  						chain,
  1010  						ctx,
  1011  						snapshotTree)
  1012  
  1013  					const keyCount = 2
  1014  					const keyIndex = keyCount - 1
  1015  
  1016  					for i := 0; i < keyCount; i++ {
  1017  						snapshotTree, _ = addAccountKey(
  1018  							t,
  1019  							vm,
  1020  							ctx,
  1021  							snapshotTree,
  1022  							address,
  1023  							test.apiVersion)
  1024  					}
  1025  
  1026  					before, err := vm.GetAccount(ctx, address, snapshotTree)
  1027  					require.NoError(t, err)
  1028  					assert.Len(t, before.Keys, keyCount)
  1029  
  1030  					keyIndexArg, err := jsoncdc.Encode(cadence.NewInt(keyIndex))
  1031  					require.NoError(t, err)
  1032  
  1033  					txBody := flow.NewTransactionBody().
  1034  						SetScript([]byte(test.source)).
  1035  						AddArgument(keyIndexArg).
  1036  						AddAuthorizer(address)
  1037  
  1038  					executionSnapshot, output, err := vm.Run(
  1039  						ctx,
  1040  						fvm.Transaction(txBody, 0),
  1041  						snapshotTree)
  1042  					require.NoError(t, err)
  1043  					assert.NoError(t, output.Err)
  1044  
  1045  					snapshotTree = snapshotTree.Append(executionSnapshot)
  1046  
  1047  					after, err := vm.GetAccount(ctx, address, snapshotTree)
  1048  					require.NoError(t, err)
  1049  					assert.Len(t, after.Keys, keyCount)
  1050  
  1051  					for _, publicKey := range after.Keys[:len(after.Keys)-1] {
  1052  						assert.False(t, publicKey.Revoked)
  1053  					}
  1054  
  1055  					assert.True(t, after.Keys[keyIndex].Revoked)
  1056  				}),
  1057  		)
  1058  
  1059  		t.Run(fmt.Sprintf("Key added by a different api version %s", test.apiVersion),
  1060  			newVMTest().withContextOptions(options...).
  1061  				run(func(t *testing.T, vm fvm.VM, chain flow.Chain, ctx fvm.Context, snapshotTree snapshot.SnapshotTree) {
  1062  					snapshotTree, address := createAccount(
  1063  						t,
  1064  						vm,
  1065  						chain,
  1066  						ctx,
  1067  						snapshotTree)
  1068  
  1069  					const keyCount = 2
  1070  					const keyIndex = keyCount - 1
  1071  
  1072  					// Use one version of API to add the keys, and a different version of the API to revoke the keys.
  1073  					var apiVersionForAdding accountKeyAPIVersion
  1074  					if test.apiVersion == accountKeyAPIVersionV1 {
  1075  						apiVersionForAdding = accountKeyAPIVersionV2
  1076  					} else {
  1077  						apiVersionForAdding = accountKeyAPIVersionV1
  1078  					}
  1079  
  1080  					for i := 0; i < keyCount; i++ {
  1081  						snapshotTree, _ = addAccountKey(
  1082  							t,
  1083  							vm,
  1084  							ctx,
  1085  							snapshotTree,
  1086  							address,
  1087  							apiVersionForAdding)
  1088  					}
  1089  
  1090  					before, err := vm.GetAccount(ctx, address, snapshotTree)
  1091  					require.NoError(t, err)
  1092  					assert.Len(t, before.Keys, keyCount)
  1093  
  1094  					keyIndexArg, err := jsoncdc.Encode(cadence.NewInt(keyIndex))
  1095  					require.NoError(t, err)
  1096  
  1097  					txBody := flow.NewTransactionBody().
  1098  						SetScript([]byte(test.source)).
  1099  						AddArgument(keyIndexArg).
  1100  						AddAuthorizer(address)
  1101  
  1102  					executionSnapshot, output, err := vm.Run(
  1103  						ctx,
  1104  						fvm.Transaction(txBody, 0),
  1105  						snapshotTree)
  1106  					require.NoError(t, err)
  1107  					assert.NoError(t, output.Err)
  1108  
  1109  					snapshotTree = snapshotTree.Append(executionSnapshot)
  1110  
  1111  					after, err := vm.GetAccount(ctx, address, snapshotTree)
  1112  					require.NoError(t, err)
  1113  					assert.Len(t, after.Keys, keyCount)
  1114  
  1115  					for _, publicKey := range after.Keys[:len(after.Keys)-1] {
  1116  						assert.False(t, publicKey.Revoked)
  1117  					}
  1118  
  1119  					assert.True(t, after.Keys[keyIndex].Revoked)
  1120  				}),
  1121  		)
  1122  	}
  1123  
  1124  	// Remove multiple keys
  1125  
  1126  	multipleKeysTests := []removeKeyTest{
  1127  		{
  1128  			source:     removeMultipleAccountKeysTransaction,
  1129  			apiVersion: accountKeyAPIVersionV1,
  1130  		},
  1131  		{
  1132  			source:     revokeMultipleAccountKeysTransaction,
  1133  			apiVersion: accountKeyAPIVersionV2,
  1134  		},
  1135  	}
  1136  
  1137  	for _, test := range multipleKeysTests {
  1138  		t.Run(fmt.Sprintf("Multiple keys %s", test.apiVersion),
  1139  			newVMTest().withContextOptions(options...).
  1140  				run(func(t *testing.T, vm fvm.VM, chain flow.Chain, ctx fvm.Context, snapshotTree snapshot.SnapshotTree) {
  1141  					snapshotTree, address := createAccount(
  1142  						t,
  1143  						vm,
  1144  						chain,
  1145  						ctx,
  1146  						snapshotTree)
  1147  
  1148  					const keyCount = 2
  1149  
  1150  					for i := 0; i < keyCount; i++ {
  1151  						snapshotTree, _ = addAccountKey(
  1152  							t,
  1153  							vm,
  1154  							ctx,
  1155  							snapshotTree,
  1156  							address,
  1157  							test.apiVersion)
  1158  					}
  1159  
  1160  					before, err := vm.GetAccount(ctx, address, snapshotTree)
  1161  					require.NoError(t, err)
  1162  					assert.Len(t, before.Keys, keyCount)
  1163  
  1164  					txBody := flow.NewTransactionBody().
  1165  						SetScript([]byte(test.source)).
  1166  						AddAuthorizer(address)
  1167  
  1168  					for i := 0; i < keyCount; i++ {
  1169  						keyIndexArg, err := jsoncdc.Encode(cadence.NewInt(i))
  1170  						require.NoError(t, err)
  1171  
  1172  						txBody.AddArgument(keyIndexArg)
  1173  					}
  1174  
  1175  					executionSnapshot, output, err := vm.Run(
  1176  						ctx,
  1177  						fvm.Transaction(txBody, 0),
  1178  						snapshotTree)
  1179  					require.NoError(t, err)
  1180  					assert.NoError(t, output.Err)
  1181  
  1182  					snapshotTree = snapshotTree.Append(executionSnapshot)
  1183  
  1184  					after, err := vm.GetAccount(ctx, address, snapshotTree)
  1185  					require.NoError(t, err)
  1186  					assert.Len(t, after.Keys, keyCount)
  1187  
  1188  					for _, publicKey := range after.Keys {
  1189  						assert.True(t, publicKey.Revoked)
  1190  					}
  1191  				}),
  1192  		)
  1193  	}
  1194  }
  1195  
  1196  func TestGetAccountKey(t *testing.T) {
  1197  
  1198  	options := []fvm.Option{
  1199  		fvm.WithAuthorizationChecksEnabled(false),
  1200  		fvm.WithSequenceNumberCheckAndIncrementEnabled(false),
  1201  		fvm.WithCadenceLogging(true),
  1202  	}
  1203  
  1204  	t.Run("Non-existent key",
  1205  		newVMTest().withContextOptions(options...).
  1206  			run(func(t *testing.T, vm fvm.VM, chain flow.Chain, ctx fvm.Context, snapshotTree snapshot.SnapshotTree) {
  1207  				snapshotTree, address := createAccount(
  1208  					t,
  1209  					vm,
  1210  					chain,
  1211  					ctx,
  1212  					snapshotTree)
  1213  
  1214  				const keyCount = 2
  1215  
  1216  				for i := 0; i < keyCount; i++ {
  1217  					snapshotTree, _ = addAccountKey(
  1218  						t,
  1219  						vm,
  1220  						ctx,
  1221  						snapshotTree,
  1222  						address,
  1223  						accountKeyAPIVersionV2)
  1224  				}
  1225  
  1226  				before, err := vm.GetAccount(ctx, address, snapshotTree)
  1227  				require.NoError(t, err)
  1228  				assert.Len(t, before.Keys, keyCount)
  1229  
  1230  				for _, keyIndex := range []int{-1, keyCount, keyCount + 1} {
  1231  					keyIndexArg, err := jsoncdc.Encode(cadence.NewInt(keyIndex))
  1232  					require.NoError(t, err)
  1233  
  1234  					txBody := flow.NewTransactionBody().
  1235  						SetScript([]byte(getAccountKeyTransaction)).
  1236  						AddArgument(keyIndexArg).
  1237  						AddAuthorizer(address)
  1238  
  1239  					executionSnapshot, output, err := vm.Run(
  1240  						ctx,
  1241  						fvm.Transaction(txBody, 0),
  1242  						snapshotTree)
  1243  					require.NoError(t, err)
  1244  					require.NoError(t, output.Err)
  1245  
  1246  					snapshotTree = snapshotTree.Append(executionSnapshot)
  1247  
  1248  					require.Len(t, output.Logs, 1)
  1249  					assert.Equal(t, "nil", output.Logs[0])
  1250  				}
  1251  			}),
  1252  	)
  1253  
  1254  	t.Run("Existing key",
  1255  		newVMTest().withContextOptions(options...).
  1256  			run(func(t *testing.T, vm fvm.VM, chain flow.Chain, ctx fvm.Context, snapshotTree snapshot.SnapshotTree) {
  1257  				snapshotTree, address := createAccount(
  1258  					t,
  1259  					vm,
  1260  					chain,
  1261  					ctx,
  1262  					snapshotTree)
  1263  
  1264  				const keyCount = 2
  1265  				const keyIndex = keyCount - 1
  1266  
  1267  				keys := make([]flow.AccountPublicKey, keyCount)
  1268  				for i := 0; i < keyCount; i++ {
  1269  					snapshotTree, keys[i] = addAccountKey(
  1270  						t,
  1271  						vm,
  1272  						ctx,
  1273  						snapshotTree,
  1274  						address,
  1275  						accountKeyAPIVersionV2)
  1276  				}
  1277  
  1278  				before, err := vm.GetAccount(ctx, address, snapshotTree)
  1279  				require.NoError(t, err)
  1280  				assert.Len(t, before.Keys, keyCount)
  1281  
  1282  				keyIndexArg, err := jsoncdc.Encode(cadence.NewInt(keyIndex))
  1283  				require.NoError(t, err)
  1284  
  1285  				txBody := flow.NewTransactionBody().
  1286  					SetScript([]byte(getAccountKeyTransaction)).
  1287  					AddArgument(keyIndexArg).
  1288  					AddAuthorizer(address)
  1289  
  1290  				_, output, err := vm.Run(
  1291  					ctx,
  1292  					fvm.Transaction(txBody, 0),
  1293  					snapshotTree)
  1294  				require.NoError(t, err)
  1295  				require.NoError(t, output.Err)
  1296  
  1297  				require.Len(t, output.Logs, 1)
  1298  
  1299  				key := keys[keyIndex]
  1300  
  1301  				expected := fmt.Sprintf(
  1302  					"AccountKey("+
  1303  						"keyIndex: %d, "+
  1304  						"publicKey: PublicKey(publicKey: %s, signatureAlgorithm: SignatureAlgorithm(rawValue: 1)), "+
  1305  						"hashAlgorithm: HashAlgorithm(rawValue: 3), "+
  1306  						"weight: 1000.00000000, "+
  1307  						"isRevoked: false)",
  1308  					keyIndex,
  1309  					byteSliceToCadenceArrayLiteral(key.PublicKey.Encode()),
  1310  				)
  1311  
  1312  				assert.Equal(t, expected, output.Logs[0])
  1313  			}),
  1314  	)
  1315  
  1316  	t.Run("Key added by a different api version",
  1317  		newVMTest().withContextOptions(options...).
  1318  			run(func(t *testing.T, vm fvm.VM, chain flow.Chain, ctx fvm.Context, snapshotTree snapshot.SnapshotTree) {
  1319  				snapshotTree, address := createAccount(
  1320  					t,
  1321  					vm,
  1322  					chain,
  1323  					ctx,
  1324  					snapshotTree)
  1325  
  1326  				const keyCount = 2
  1327  				const keyIndex = keyCount - 1
  1328  
  1329  				keys := make([]flow.AccountPublicKey, keyCount)
  1330  				for i := 0; i < keyCount; i++ {
  1331  
  1332  					// Use the old version of API to add the key
  1333  					snapshotTree, keys[i] = addAccountKey(
  1334  						t,
  1335  						vm,
  1336  						ctx,
  1337  						snapshotTree,
  1338  						address,
  1339  						accountKeyAPIVersionV1)
  1340  				}
  1341  
  1342  				before, err := vm.GetAccount(ctx, address, snapshotTree)
  1343  				require.NoError(t, err)
  1344  				assert.Len(t, before.Keys, keyCount)
  1345  
  1346  				keyIndexArg, err := jsoncdc.Encode(cadence.NewInt(keyIndex))
  1347  				require.NoError(t, err)
  1348  
  1349  				txBody := flow.NewTransactionBody().
  1350  					SetScript([]byte(getAccountKeyTransaction)).
  1351  					AddArgument(keyIndexArg).
  1352  					AddAuthorizer(address)
  1353  
  1354  				_, output, err := vm.Run(
  1355  					ctx,
  1356  					fvm.Transaction(txBody, 0),
  1357  					snapshotTree)
  1358  				require.NoError(t, err)
  1359  				require.NoError(t, output.Err)
  1360  
  1361  				require.Len(t, output.Logs, 1)
  1362  
  1363  				key := keys[keyIndex]
  1364  
  1365  				expected := fmt.Sprintf(
  1366  					"AccountKey("+
  1367  						"keyIndex: %d, "+
  1368  						"publicKey: PublicKey(publicKey: %s, signatureAlgorithm: SignatureAlgorithm(rawValue: 1)), "+
  1369  						"hashAlgorithm: HashAlgorithm(rawValue: 3), "+
  1370  						"weight: 1000.00000000, "+
  1371  						"isRevoked: false)",
  1372  					keyIndex,
  1373  					byteSliceToCadenceArrayLiteral(key.PublicKey.Encode()),
  1374  				)
  1375  
  1376  				assert.Equal(t, expected, output.Logs[0])
  1377  			}),
  1378  	)
  1379  
  1380  	t.Run("Multiple keys",
  1381  		newVMTest().withContextOptions(options...).
  1382  			run(func(t *testing.T, vm fvm.VM, chain flow.Chain, ctx fvm.Context, snapshotTree snapshot.SnapshotTree) {
  1383  				snapshotTree, address := createAccount(
  1384  					t,
  1385  					vm,
  1386  					chain,
  1387  					ctx,
  1388  					snapshotTree)
  1389  
  1390  				const keyCount = 2
  1391  
  1392  				keys := make([]flow.AccountPublicKey, keyCount)
  1393  				for i := 0; i < keyCount; i++ {
  1394  
  1395  					snapshotTree, keys[i] = addAccountKey(
  1396  						t,
  1397  						vm,
  1398  						ctx,
  1399  						snapshotTree,
  1400  						address,
  1401  						accountKeyAPIVersionV2)
  1402  				}
  1403  
  1404  				before, err := vm.GetAccount(ctx, address, snapshotTree)
  1405  				require.NoError(t, err)
  1406  				assert.Len(t, before.Keys, keyCount)
  1407  
  1408  				txBody := flow.NewTransactionBody().
  1409  					SetScript([]byte(getMultipleAccountKeysTransaction)).
  1410  					AddAuthorizer(address)
  1411  
  1412  				for i := 0; i < keyCount; i++ {
  1413  					keyIndexArg, err := jsoncdc.Encode(cadence.NewInt(i))
  1414  					require.NoError(t, err)
  1415  
  1416  					txBody.AddArgument(keyIndexArg)
  1417  				}
  1418  
  1419  				_, output, err := vm.Run(
  1420  					ctx,
  1421  					fvm.Transaction(txBody, 0),
  1422  					snapshotTree)
  1423  				require.NoError(t, err)
  1424  				require.NoError(t, output.Err)
  1425  
  1426  				assert.Len(t, output.Logs, 2)
  1427  
  1428  				for i := 0; i < keyCount; i++ {
  1429  					expected := fmt.Sprintf(
  1430  						"AccountKey("+
  1431  							"keyIndex: %d, "+
  1432  							"publicKey: PublicKey(publicKey: %s, signatureAlgorithm: SignatureAlgorithm(rawValue: 1)), "+
  1433  							"hashAlgorithm: HashAlgorithm(rawValue: 3), "+
  1434  							"weight: 1000.00000000, "+
  1435  							"isRevoked: false)",
  1436  						i,
  1437  						byteSliceToCadenceArrayLiteral(keys[i].PublicKey.Encode()),
  1438  					)
  1439  
  1440  					assert.Equal(t, expected, output.Logs[i])
  1441  				}
  1442  			}),
  1443  	)
  1444  }
  1445  
  1446  func byteSliceToCadenceArrayLiteral(bytes []byte) string {
  1447  	elements := make([]string, 0, len(bytes))
  1448  
  1449  	for _, b := range bytes {
  1450  		elements = append(elements, strconv.Itoa(int(b)))
  1451  	}
  1452  
  1453  	return format.Array(elements)
  1454  }
  1455  
  1456  func TestAccountBalanceFields(t *testing.T) {
  1457  	t.Run("Get balance works",
  1458  		newVMTest().withContextOptions(
  1459  			fvm.WithAuthorizationChecksEnabled(false),
  1460  			fvm.WithSequenceNumberCheckAndIncrementEnabled(false),
  1461  			fvm.WithCadenceLogging(true),
  1462  		).
  1463  			run(func(t *testing.T, vm fvm.VM, chain flow.Chain, ctx fvm.Context, snapshotTree snapshot.SnapshotTree) {
  1464  				snapshotTree, account := createAccount(
  1465  					t,
  1466  					vm,
  1467  					chain,
  1468  					ctx,
  1469  					snapshotTree)
  1470  
  1471  				txBody := transferTokensTx(chain).
  1472  					AddArgument(jsoncdc.MustEncode(cadence.UFix64(100_000_000))).
  1473  					AddArgument(jsoncdc.MustEncode(cadence.Address(account))).
  1474  					AddAuthorizer(chain.ServiceAddress())
  1475  
  1476  				executionSnapshot, output, err := vm.Run(
  1477  					ctx,
  1478  					fvm.Transaction(txBody, 0),
  1479  					snapshotTree)
  1480  				require.NoError(t, err)
  1481  				require.NoError(t, output.Err)
  1482  
  1483  				snapshotTree = snapshotTree.Append(executionSnapshot)
  1484  
  1485  				script := fvm.Script([]byte(fmt.Sprintf(`
  1486  					pub fun main(): UFix64 {
  1487  						let acc = getAccount(0x%s)
  1488  						return acc.balance
  1489  					}
  1490  				`, account.Hex())))
  1491  
  1492  				_, output, err = vm.Run(ctx, script, snapshotTree)
  1493  				require.NoError(t, err)
  1494  				require.NoError(t, output.Err)
  1495  
  1496  				assert.NoError(t, err)
  1497  
  1498  				assert.Equal(t, cadence.UFix64(100_000_000), output.Value)
  1499  			}),
  1500  	)
  1501  
  1502  	// TODO - this is because get account + borrow returns
  1503  	// empty values instead of failing for an account that doesnt exist
  1504  	// this behavior needs to addressed on Cadence side
  1505  	t.Run("Get balance returns 0 for accounts that don't exist",
  1506  		newVMTest().withContextOptions(
  1507  			fvm.WithAuthorizationChecksEnabled(false),
  1508  			fvm.WithSequenceNumberCheckAndIncrementEnabled(false),
  1509  			fvm.WithCadenceLogging(true),
  1510  		).
  1511  			run(func(t *testing.T, vm fvm.VM, chain flow.Chain, ctx fvm.Context, snapshotTree snapshot.SnapshotTree) {
  1512  				nonExistentAddress, err := chain.AddressAtIndex(100)
  1513  				require.NoError(t, err)
  1514  
  1515  				script := fvm.Script([]byte(fmt.Sprintf(`
  1516  					pub fun main(): UFix64 {
  1517  						let acc = getAccount(0x%s)
  1518  						return acc.balance
  1519  					}
  1520  				`, nonExistentAddress)))
  1521  
  1522  				_, output, err := vm.Run(ctx, script, snapshotTree)
  1523  				require.NoError(t, err)
  1524  				require.NoError(t, output.Err)
  1525  
  1526  				require.NoError(t, err)
  1527  				require.NoError(t, output.Err)
  1528  				require.Equal(t, cadence.UFix64(0), output.Value)
  1529  			}),
  1530  	)
  1531  
  1532  	t.Run("Get balance fails if snapshotTree returns an error",
  1533  		newVMTest().withContextOptions(
  1534  			fvm.WithAuthorizationChecksEnabled(false),
  1535  			fvm.WithSequenceNumberCheckAndIncrementEnabled(false),
  1536  			fvm.WithCadenceLogging(true),
  1537  		).
  1538  			run(func(t *testing.T, vm fvm.VM, chain flow.Chain, ctx fvm.Context, snapshotTree snapshot.SnapshotTree) {
  1539  				snapshotTree, address := createAccount(
  1540  					t,
  1541  					vm,
  1542  					chain,
  1543  					ctx,
  1544  					snapshotTree)
  1545  
  1546  				script := fvm.Script([]byte(fmt.Sprintf(`
  1547  					pub fun main(): UFix64 {
  1548  						let acc = getAccount(0x%s)
  1549  						return acc.balance
  1550  					}
  1551  				`, address)))
  1552  
  1553  				snapshot := errorOnAddressSnapshotWrapper{
  1554  					snapshotTree: snapshotTree,
  1555  					owner:        address,
  1556  				}
  1557  
  1558  				_, _, err := vm.Run(ctx, script, snapshot)
  1559  				require.ErrorContains(
  1560  					t,
  1561  					err,
  1562  					fmt.Sprintf(
  1563  						"error getting register %s",
  1564  						address.Hex()))
  1565  			}),
  1566  	)
  1567  
  1568  	t.Run("Get available balance works",
  1569  		newVMTest().withContextOptions(
  1570  			fvm.WithAuthorizationChecksEnabled(false),
  1571  			fvm.WithSequenceNumberCheckAndIncrementEnabled(false),
  1572  			fvm.WithCadenceLogging(true),
  1573  			fvm.WithAccountStorageLimit(false),
  1574  		).withBootstrapProcedureOptions(
  1575  			fvm.WithStorageMBPerFLOW(1000_000_000),
  1576  		).
  1577  			run(func(t *testing.T, vm fvm.VM, chain flow.Chain, ctx fvm.Context, snapshotTree snapshot.SnapshotTree) {
  1578  				snapshotTree, account := createAccount(
  1579  					t,
  1580  					vm,
  1581  					chain,
  1582  					ctx,
  1583  					snapshotTree)
  1584  
  1585  				txBody := transferTokensTx(chain).
  1586  					AddArgument(jsoncdc.MustEncode(cadence.UFix64(100_000_000))).
  1587  					AddArgument(jsoncdc.MustEncode(cadence.Address(account))).
  1588  					AddAuthorizer(chain.ServiceAddress())
  1589  
  1590  				executionSnapshot, output, err := vm.Run(
  1591  					ctx,
  1592  					fvm.Transaction(txBody, 0),
  1593  					snapshotTree)
  1594  				require.NoError(t, err)
  1595  				require.NoError(t, output.Err)
  1596  
  1597  				snapshotTree = snapshotTree.Append(executionSnapshot)
  1598  
  1599  				script := fvm.Script([]byte(fmt.Sprintf(`
  1600  					pub fun main(): UFix64 {
  1601  						let acc = getAccount(0x%s)
  1602  						return acc.availableBalance
  1603  					}
  1604  				`, account.Hex())))
  1605  
  1606  				_, output, err = vm.Run(ctx, script, snapshotTree)
  1607  				assert.NoError(t, err)
  1608  				assert.NoError(t, output.Err)
  1609  				assert.Equal(t, cadence.UFix64(99_993_040), output.Value)
  1610  			}),
  1611  	)
  1612  
  1613  	t.Run("Get available balance fails for accounts that don't exist",
  1614  		newVMTest().withContextOptions(
  1615  			fvm.WithAuthorizationChecksEnabled(false),
  1616  			fvm.WithSequenceNumberCheckAndIncrementEnabled(false),
  1617  			fvm.WithCadenceLogging(true),
  1618  			fvm.WithAccountStorageLimit(false),
  1619  		).withBootstrapProcedureOptions(
  1620  			fvm.WithStorageMBPerFLOW(1_000_000_000),
  1621  		).
  1622  			run(func(t *testing.T, vm fvm.VM, chain flow.Chain, ctx fvm.Context, snapshotTree snapshot.SnapshotTree) {
  1623  				nonExistentAddress, err := chain.AddressAtIndex(100)
  1624  				require.NoError(t, err)
  1625  
  1626  				script := fvm.Script([]byte(fmt.Sprintf(`
  1627  					pub fun main(): UFix64 {
  1628  						let acc = getAccount(0x%s)
  1629  						return acc.availableBalance
  1630  					}
  1631  				`, nonExistentAddress)))
  1632  
  1633  				_, output, err := vm.Run(ctx, script, snapshotTree)
  1634  				assert.NoError(t, err)
  1635  				assert.Error(t, output.Err)
  1636  			}),
  1637  	)
  1638  
  1639  	t.Run("Get available balance works with minimum balance",
  1640  		newVMTest().withContextOptions(
  1641  			fvm.WithAuthorizationChecksEnabled(false),
  1642  			fvm.WithSequenceNumberCheckAndIncrementEnabled(false),
  1643  			fvm.WithCadenceLogging(true),
  1644  			fvm.WithAccountStorageLimit(false),
  1645  		).withBootstrapProcedureOptions(
  1646  			fvm.WithStorageMBPerFLOW(1000_000_000),
  1647  			fvm.WithAccountCreationFee(100_000),
  1648  			fvm.WithMinimumStorageReservation(100_000),
  1649  		).
  1650  			run(func(t *testing.T, vm fvm.VM, chain flow.Chain, ctx fvm.Context, snapshotTree snapshot.SnapshotTree) {
  1651  				snapshotTree, account := createAccount(
  1652  					t,
  1653  					vm,
  1654  					chain,
  1655  					ctx,
  1656  					snapshotTree)
  1657  
  1658  				txBody := transferTokensTx(chain).
  1659  					AddArgument(jsoncdc.MustEncode(cadence.UFix64(100_000_000))).
  1660  					AddArgument(jsoncdc.MustEncode(cadence.Address(account))).
  1661  					AddAuthorizer(chain.ServiceAddress())
  1662  
  1663  				executionSnapshot, output, err := vm.Run(
  1664  					ctx,
  1665  					fvm.Transaction(txBody, 0),
  1666  					snapshotTree)
  1667  				require.NoError(t, err)
  1668  				require.NoError(t, output.Err)
  1669  
  1670  				snapshotTree = snapshotTree.Append(executionSnapshot)
  1671  
  1672  				script := fvm.Script([]byte(fmt.Sprintf(`
  1673  					pub fun main(): UFix64 {
  1674  						let acc = getAccount(0x%s)
  1675  						return acc.availableBalance
  1676  					}
  1677  				`, account.Hex())))
  1678  
  1679  				_, output, err = vm.Run(ctx, script, snapshotTree)
  1680  				assert.NoError(t, err)
  1681  				assert.NoError(t, output.Err)
  1682  
  1683  				// Should be 100_000_000 because 100_000 was given to it during account creation and is now locked up
  1684  				assert.Equal(t, cadence.UFix64(100_000_000), output.Value)
  1685  			}),
  1686  	)
  1687  }
  1688  
  1689  func TestGetStorageCapacity(t *testing.T) {
  1690  	t.Run("Get storage capacity",
  1691  		newVMTest().withContextOptions(
  1692  			fvm.WithAuthorizationChecksEnabled(false),
  1693  			fvm.WithSequenceNumberCheckAndIncrementEnabled(false),
  1694  			fvm.WithCadenceLogging(true),
  1695  			fvm.WithAccountStorageLimit(false),
  1696  		).withBootstrapProcedureOptions(
  1697  			fvm.WithStorageMBPerFLOW(1_000_000_000),
  1698  			fvm.WithAccountCreationFee(100_000),
  1699  			fvm.WithMinimumStorageReservation(100_000),
  1700  		).
  1701  			run(func(t *testing.T, vm fvm.VM, chain flow.Chain, ctx fvm.Context, snapshotTree snapshot.SnapshotTree) {
  1702  				snapshotTree, account := createAccount(
  1703  					t,
  1704  					vm,
  1705  					chain,
  1706  					ctx,
  1707  					snapshotTree)
  1708  
  1709  				txBody := transferTokensTx(chain).
  1710  					AddArgument(jsoncdc.MustEncode(cadence.UFix64(100_000_000))).
  1711  					AddArgument(jsoncdc.MustEncode(cadence.Address(account))).
  1712  					AddAuthorizer(chain.ServiceAddress())
  1713  
  1714  				executionSnapshot, output, err := vm.Run(
  1715  					ctx,
  1716  					fvm.Transaction(txBody, 0),
  1717  					snapshotTree)
  1718  				require.NoError(t, err)
  1719  				require.NoError(t, output.Err)
  1720  
  1721  				snapshotTree = snapshotTree.Append(executionSnapshot)
  1722  
  1723  				script := fvm.Script([]byte(fmt.Sprintf(`
  1724  					pub fun main(): UInt64 {
  1725  						let acc = getAccount(0x%s)
  1726  						return acc.storageCapacity
  1727  					}
  1728  				`, account)))
  1729  
  1730  				_, output, err = vm.Run(ctx, script, snapshotTree)
  1731  				require.NoError(t, err)
  1732  				require.NoError(t, output.Err)
  1733  
  1734  				require.Equal(t, cadence.UInt64(10_010_000), output.Value)
  1735  			}),
  1736  	)
  1737  	t.Run("Get storage capacity returns 0 for accounts that don't exist",
  1738  		newVMTest().withContextOptions(
  1739  			fvm.WithAuthorizationChecksEnabled(false),
  1740  			fvm.WithSequenceNumberCheckAndIncrementEnabled(false),
  1741  			fvm.WithCadenceLogging(true),
  1742  			fvm.WithAccountStorageLimit(false),
  1743  		).withBootstrapProcedureOptions(
  1744  			fvm.WithStorageMBPerFLOW(1_000_000_000),
  1745  			fvm.WithAccountCreationFee(100_000),
  1746  			fvm.WithMinimumStorageReservation(100_000),
  1747  		).
  1748  			run(func(t *testing.T, vm fvm.VM, chain flow.Chain, ctx fvm.Context, snapshotTree snapshot.SnapshotTree) {
  1749  				nonExistentAddress, err := chain.AddressAtIndex(100)
  1750  				require.NoError(t, err)
  1751  
  1752  				script := fvm.Script([]byte(fmt.Sprintf(`
  1753  					pub fun main(): UInt64 {
  1754  						let acc = getAccount(0x%s)
  1755  						return acc.storageCapacity
  1756  					}
  1757  				`, nonExistentAddress)))
  1758  
  1759  				_, output, err := vm.Run(ctx, script, snapshotTree)
  1760  
  1761  				require.NoError(t, err)
  1762  				require.NoError(t, output.Err)
  1763  				require.Equal(t, cadence.UInt64(0), output.Value)
  1764  			}),
  1765  	)
  1766  	t.Run("Get storage capacity fails if snapshotTree returns an error",
  1767  		newVMTest().withContextOptions(
  1768  			fvm.WithAuthorizationChecksEnabled(false),
  1769  			fvm.WithSequenceNumberCheckAndIncrementEnabled(false),
  1770  			fvm.WithCadenceLogging(true),
  1771  			fvm.WithAccountStorageLimit(false),
  1772  		).withBootstrapProcedureOptions(
  1773  			fvm.WithStorageMBPerFLOW(1_000_000_000),
  1774  			fvm.WithAccountCreationFee(100_000),
  1775  			fvm.WithMinimumStorageReservation(100_000),
  1776  		).
  1777  			run(func(t *testing.T, vm fvm.VM, chain flow.Chain, ctx fvm.Context, snapshotTree snapshot.SnapshotTree) {
  1778  				address := chain.ServiceAddress()
  1779  
  1780  				script := fvm.Script([]byte(fmt.Sprintf(`
  1781  					pub fun main(): UInt64 {
  1782  						let acc = getAccount(0x%s)
  1783  						return acc.storageCapacity
  1784  					}
  1785  				`, address)))
  1786  
  1787  				storageSnapshot := errorOnAddressSnapshotWrapper{
  1788  					owner:        address,
  1789  					snapshotTree: snapshotTree,
  1790  				}
  1791  
  1792  				_, _, err := vm.Run(ctx, script, storageSnapshot)
  1793  				require.ErrorContains(
  1794  					t,
  1795  					err,
  1796  					fmt.Sprintf(
  1797  						"error getting register %s",
  1798  						address.Hex()))
  1799  			}),
  1800  	)
  1801  }