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