github.com/koko1123/flow-go-1@v0.29.6/fvm/environment/contract_updater_test.go (about)

     1  package environment_test
     2  
     3  import (
     4  	"fmt"
     5  	"testing"
     6  
     7  	"github.com/onflow/cadence"
     8  	"github.com/onflow/cadence/runtime"
     9  	"github.com/onflow/cadence/runtime/common"
    10  	"github.com/stretchr/testify/mock"
    11  	"github.com/stretchr/testify/require"
    12  
    13  	"github.com/koko1123/flow-go-1/fvm/blueprints"
    14  	"github.com/koko1123/flow-go-1/fvm/environment"
    15  	envMock "github.com/koko1123/flow-go-1/fvm/environment/mock"
    16  	"github.com/koko1123/flow-go-1/fvm/state"
    17  	"github.com/koko1123/flow-go-1/fvm/utils"
    18  	"github.com/koko1123/flow-go-1/model/flow"
    19  )
    20  
    21  type testContractUpdaterStubs struct {
    22  	deploymentEnabled    bool
    23  	removalEnabled       bool
    24  	deploymentAuthorized []common.Address
    25  	removalAuthorized    []common.Address
    26  
    27  	auditFunc func(address runtime.Address, code []byte) (bool, error)
    28  }
    29  
    30  func (p testContractUpdaterStubs) RestrictedDeploymentEnabled() bool {
    31  	return p.deploymentEnabled
    32  }
    33  
    34  func (p testContractUpdaterStubs) RestrictedRemovalEnabled() bool {
    35  	return p.removalEnabled
    36  }
    37  
    38  func (p testContractUpdaterStubs) GetAuthorizedAccounts(
    39  	path cadence.Path,
    40  ) []common.Address {
    41  	if path == blueprints.ContractDeploymentAuthorizedAddressesPath {
    42  		return p.deploymentAuthorized
    43  	}
    44  	return p.removalAuthorized
    45  }
    46  
    47  func (p testContractUpdaterStubs) UseContractAuditVoucher(
    48  	address runtime.Address,
    49  	code []byte,
    50  ) (
    51  	bool,
    52  	error,
    53  ) {
    54  	return p.auditFunc(address, code)
    55  }
    56  
    57  func TestContract_ChildMergeFunctionality(t *testing.T) {
    58  	txnState := state.NewTransactionState(
    59  		utils.NewSimpleView(),
    60  		state.DefaultParameters())
    61  	accounts := environment.NewAccounts(txnState)
    62  	address := flow.HexToAddress("01")
    63  	rAdd := runtime.Address(address)
    64  	err := accounts.Create(nil, address)
    65  	require.NoError(t, err)
    66  
    67  	contractUpdater := environment.NewContractUpdaterForTesting(
    68  		accounts,
    69  		testContractUpdaterStubs{})
    70  
    71  	// no contract initially
    72  	names, err := accounts.GetContractNames(address)
    73  	require.NoError(t, err)
    74  	require.Equal(t, len(names), 0)
    75  
    76  	// set contract no need for signing accounts
    77  	err = contractUpdater.SetContract(rAdd, "testContract", []byte("ABC"), nil)
    78  	require.NoError(t, err)
    79  	require.True(t, contractUpdater.HasUpdates())
    80  
    81  	// should not be readable from draft
    82  	cont, err := accounts.GetContract("testContract", address)
    83  	require.NoError(t, err)
    84  	require.Equal(t, len(cont), 0)
    85  
    86  	// commit
    87  	_, err = contractUpdater.Commit()
    88  	require.NoError(t, err)
    89  	cont, err = accounts.GetContract("testContract", address)
    90  	require.NoError(t, err)
    91  	require.Equal(t, cont, []byte("ABC"))
    92  
    93  	// rollback
    94  	err = contractUpdater.SetContract(rAdd, "testContract2", []byte("ABC"), nil)
    95  	require.NoError(t, err)
    96  	contractUpdater.Reset()
    97  	require.False(t, contractUpdater.HasUpdates())
    98  	_, err = contractUpdater.Commit()
    99  	require.NoError(t, err)
   100  
   101  	// test contract shouldn't be there
   102  	cont, err = accounts.GetContract("testContract2", address)
   103  	require.NoError(t, err)
   104  	require.Equal(t, len(cont), 0)
   105  
   106  	// test contract should be there
   107  	cont, err = accounts.GetContract("testContract", address)
   108  	require.NoError(t, err)
   109  	require.Equal(t, cont, []byte("ABC"))
   110  
   111  	// remove
   112  	err = contractUpdater.RemoveContract(rAdd, "testContract", nil)
   113  	require.NoError(t, err)
   114  
   115  	// contract still there because no commit yet
   116  	cont, err = accounts.GetContract("testContract", address)
   117  	require.NoError(t, err)
   118  	require.Equal(t, cont, []byte("ABC"))
   119  
   120  	// commit removal
   121  	_, err = contractUpdater.Commit()
   122  	require.NoError(t, err)
   123  
   124  	// contract should no longer be there
   125  	cont, err = accounts.GetContract("testContract", address)
   126  	require.NoError(t, err)
   127  	require.Equal(t, []byte(nil), cont)
   128  }
   129  
   130  func TestContract_AuthorizationFunctionality(t *testing.T) {
   131  	txnState := state.NewTransactionState(
   132  		utils.NewSimpleView(),
   133  		state.DefaultParameters())
   134  	accounts := environment.NewAccounts(txnState)
   135  
   136  	authAdd := flow.HexToAddress("01")
   137  	rAdd := runtime.Address(authAdd)
   138  	err := accounts.Create(nil, authAdd)
   139  	require.NoError(t, err)
   140  
   141  	authRemove := flow.HexToAddress("02")
   142  	rRemove := runtime.Address(authRemove)
   143  	err = accounts.Create(nil, authRemove)
   144  	require.NoError(t, err)
   145  
   146  	authBoth := flow.HexToAddress("03")
   147  	rBoth := runtime.Address(authBoth)
   148  	err = accounts.Create(nil, authBoth)
   149  	require.NoError(t, err)
   150  
   151  	unAuth := flow.HexToAddress("04")
   152  	unAuthR := runtime.Address(unAuth)
   153  	err = accounts.Create(nil, unAuth)
   154  	require.NoError(t, err)
   155  
   156  	makeUpdater := func() *environment.ContractUpdaterImpl {
   157  		return environment.NewContractUpdaterForTesting(
   158  			accounts,
   159  			testContractUpdaterStubs{
   160  				deploymentEnabled:    true,
   161  				removalEnabled:       true,
   162  				deploymentAuthorized: []common.Address{rAdd, rBoth},
   163  				removalAuthorized:    []common.Address{rRemove, rBoth},
   164  				auditFunc:            func(address runtime.Address, code []byte) (bool, error) { return false, nil },
   165  			})
   166  
   167  	}
   168  
   169  	t.Run("try to set contract with unauthorized account", func(t *testing.T) {
   170  		contractUpdater := makeUpdater()
   171  
   172  		err = contractUpdater.SetContract(rAdd, "testContract1", []byte("ABC"), []common.Address{unAuthR})
   173  		require.Error(t, err)
   174  		require.False(t, contractUpdater.HasUpdates())
   175  	})
   176  
   177  	t.Run("try to set contract with account only authorized for removal", func(t *testing.T) {
   178  		contractUpdater := makeUpdater()
   179  
   180  		err = contractUpdater.SetContract(rAdd, "testContract1", []byte("ABC"), []common.Address{rRemove})
   181  		require.Error(t, err)
   182  		require.False(t, contractUpdater.HasUpdates())
   183  	})
   184  
   185  	t.Run("set contract with account authorized for adding", func(t *testing.T) {
   186  		contractUpdater := makeUpdater()
   187  
   188  		err = contractUpdater.SetContract(rAdd, "testContract2", []byte("ABC"), []common.Address{rAdd})
   189  		require.NoError(t, err)
   190  		require.True(t, contractUpdater.HasUpdates())
   191  	})
   192  
   193  	t.Run("set contract with account authorized for adding and removing", func(t *testing.T) {
   194  		contractUpdater := makeUpdater()
   195  
   196  		err = contractUpdater.SetContract(rAdd, "testContract2", []byte("ABC"), []common.Address{rBoth})
   197  		require.NoError(t, err)
   198  		require.True(t, contractUpdater.HasUpdates())
   199  	})
   200  
   201  	t.Run("try to remove contract with unauthorized account", func(t *testing.T) {
   202  		contractUpdater := makeUpdater()
   203  
   204  		err = contractUpdater.SetContract(rAdd, "testContract1", []byte("ABC"), []common.Address{rAdd})
   205  		require.NoError(t, err)
   206  		_, err = contractUpdater.Commit()
   207  		require.NoError(t, err)
   208  
   209  		err = contractUpdater.RemoveContract(unAuthR, "testContract2", []common.Address{unAuthR})
   210  		require.Error(t, err)
   211  		require.False(t, contractUpdater.HasUpdates())
   212  	})
   213  
   214  	t.Run("remove contract account authorized for removal", func(t *testing.T) {
   215  		contractUpdater := makeUpdater()
   216  
   217  		err = contractUpdater.SetContract(rAdd, "testContract1", []byte("ABC"), []common.Address{rAdd})
   218  		require.NoError(t, err)
   219  		_, err = contractUpdater.Commit()
   220  		require.NoError(t, err)
   221  
   222  		err = contractUpdater.RemoveContract(rRemove, "testContract2", []common.Address{rRemove})
   223  		require.NoError(t, err)
   224  		require.True(t, contractUpdater.HasUpdates())
   225  	})
   226  
   227  	t.Run("try to remove contract with account only authorized for adding", func(t *testing.T) {
   228  		contractUpdater := makeUpdater()
   229  
   230  		err = contractUpdater.SetContract(rAdd, "testContract1", []byte("ABC"), []common.Address{rAdd})
   231  		require.NoError(t, err)
   232  		_, err = contractUpdater.Commit()
   233  		require.NoError(t, err)
   234  
   235  		err = contractUpdater.RemoveContract(rAdd, "testContract2", []common.Address{rAdd})
   236  		require.Error(t, err)
   237  		require.False(t, contractUpdater.HasUpdates())
   238  	})
   239  
   240  	t.Run("remove contract with account authorized for adding and removing", func(t *testing.T) {
   241  		contractUpdater := makeUpdater()
   242  
   243  		err = contractUpdater.SetContract(rAdd, "testContract1", []byte("ABC"), []common.Address{rAdd})
   244  		require.NoError(t, err)
   245  		_, err = contractUpdater.Commit()
   246  		require.NoError(t, err)
   247  
   248  		err = contractUpdater.RemoveContract(rBoth, "testContract2", []common.Address{rBoth})
   249  		require.NoError(t, err)
   250  		require.True(t, contractUpdater.HasUpdates())
   251  	})
   252  }
   253  
   254  func TestContract_DeploymentVouchers(t *testing.T) {
   255  
   256  	txnState := state.NewTransactionState(
   257  		utils.NewSimpleView(),
   258  		state.DefaultParameters())
   259  	accounts := environment.NewAccounts(txnState)
   260  
   261  	addressWithVoucher := flow.HexToAddress("01")
   262  	addressWithVoucherRuntime := runtime.Address(addressWithVoucher)
   263  	err := accounts.Create(nil, addressWithVoucher)
   264  	require.NoError(t, err)
   265  
   266  	addressNoVoucher := flow.HexToAddress("02")
   267  	addressNoVoucherRuntime := runtime.Address(addressNoVoucher)
   268  	err = accounts.Create(nil, addressNoVoucher)
   269  	require.NoError(t, err)
   270  
   271  	contractUpdater := environment.NewContractUpdaterForTesting(
   272  		accounts,
   273  		testContractUpdaterStubs{
   274  			deploymentEnabled: true,
   275  			removalEnabled:    true,
   276  			auditFunc: func(address runtime.Address, code []byte) (bool, error) {
   277  				if address.String() == addressWithVoucher.String() {
   278  					return true, nil
   279  				}
   280  				return false, nil
   281  			},
   282  		})
   283  
   284  	// set contract without voucher
   285  	err = contractUpdater.SetContract(
   286  		addressNoVoucherRuntime,
   287  		"TestContract1",
   288  		[]byte("pub contract TestContract1 {}"),
   289  		[]common.Address{
   290  			addressNoVoucherRuntime,
   291  		},
   292  	)
   293  	require.Error(t, err)
   294  	require.False(t, contractUpdater.HasUpdates())
   295  
   296  	// try to set contract with voucher
   297  	err = contractUpdater.SetContract(
   298  		addressWithVoucherRuntime,
   299  		"TestContract2",
   300  		[]byte("pub contract TestContract2 {}"),
   301  		[]common.Address{
   302  			addressWithVoucherRuntime,
   303  		},
   304  	)
   305  	require.NoError(t, err)
   306  	require.True(t, contractUpdater.HasUpdates())
   307  }
   308  
   309  func TestContract_ContractUpdate(t *testing.T) {
   310  
   311  	txnState := state.NewTransactionState(
   312  		utils.NewSimpleView(),
   313  		state.DefaultParameters())
   314  	accounts := environment.NewAccounts(txnState)
   315  
   316  	flowAddress := flow.HexToAddress("01")
   317  	flowCommonAddress := common.MustBytesToAddress(flowAddress.Bytes())
   318  	runtimeAddress := runtime.Address(flowAddress)
   319  	err := accounts.Create(nil, flowAddress)
   320  	require.NoError(t, err)
   321  
   322  	var authorizationChecked bool
   323  
   324  	contractUpdater := environment.NewContractUpdaterForTesting(
   325  		accounts,
   326  		testContractUpdaterStubs{
   327  			deploymentEnabled: true,
   328  			removalEnabled:    true,
   329  			auditFunc: func(address runtime.Address, code []byte) (bool, error) {
   330  				// Ensure the voucher check is only called once,
   331  				// for the initial contract deployment,
   332  				// and not for the subsequent update
   333  				require.False(t, authorizationChecked)
   334  				authorizationChecked = true
   335  				return true, nil
   336  			},
   337  		})
   338  
   339  	// deploy contract with voucher
   340  	err = contractUpdater.SetContract(
   341  		runtimeAddress,
   342  		"TestContract",
   343  		[]byte("pub contract TestContract {}"),
   344  		[]common.Address{
   345  			runtimeAddress,
   346  		},
   347  	)
   348  	require.NoError(t, err)
   349  	require.True(t, contractUpdater.HasUpdates())
   350  
   351  	contractUpdateKeys, err := contractUpdater.Commit()
   352  	require.NoError(t, err)
   353  	require.Equal(
   354  		t,
   355  		[]environment.ContractUpdateKey{
   356  			{
   357  				Address: flowCommonAddress,
   358  				Name:    "TestContract",
   359  			},
   360  		},
   361  		contractUpdateKeys,
   362  	)
   363  
   364  	// try to update contract without voucher
   365  	err = contractUpdater.SetContract(
   366  		runtimeAddress,
   367  		"TestContract",
   368  		[]byte("pub contract TestContract {}"),
   369  		[]common.Address{
   370  			runtimeAddress,
   371  		},
   372  	)
   373  	require.NoError(t, err)
   374  	require.True(t, contractUpdater.HasUpdates())
   375  }
   376  
   377  func TestContract_DeterministicErrorOnCommit(t *testing.T) {
   378  	mockAccounts := &envMock.Accounts{}
   379  
   380  	mockAccounts.On("ContractExists", mock.Anything, mock.Anything).
   381  		Return(false, nil)
   382  
   383  	mockAccounts.On("SetContract", mock.Anything, mock.Anything, mock.Anything).
   384  		Return(func(contractName string, address flow.Address, contract []byte) error {
   385  			return fmt.Errorf("%s %s", contractName, address.Hex())
   386  		})
   387  
   388  	contractUpdater := environment.NewContractUpdaterForTesting(
   389  		mockAccounts,
   390  		testContractUpdaterStubs{})
   391  
   392  	address1 := runtime.Address(flow.HexToAddress("0000000000000001"))
   393  	address2 := runtime.Address(flow.HexToAddress("0000000000000002"))
   394  
   395  	err := contractUpdater.SetContract(address2, "A", []byte("ABC"), nil)
   396  	require.NoError(t, err)
   397  
   398  	err = contractUpdater.SetContract(address1, "B", []byte("ABC"), nil)
   399  	require.NoError(t, err)
   400  
   401  	err = contractUpdater.SetContract(address1, "A", []byte("ABC"), nil)
   402  	require.NoError(t, err)
   403  
   404  	_, err = contractUpdater.Commit()
   405  	require.EqualError(t, err, "A 0000000000000001")
   406  }
   407  
   408  func TestContract_ContractRemoval(t *testing.T) {
   409  
   410  	txnState := state.NewTransactionState(
   411  		utils.NewSimpleView(),
   412  		state.DefaultParameters())
   413  	accounts := environment.NewAccounts(txnState)
   414  
   415  	flowAddress := flow.HexToAddress("01")
   416  	flowCommonAddress := common.MustBytesToAddress(flowAddress.Bytes())
   417  	runtimeAddress := runtime.Address(flowAddress)
   418  	err := accounts.Create(nil, flowAddress)
   419  	require.NoError(t, err)
   420  
   421  	t.Run("contract removal with restriction", func(t *testing.T) {
   422  		var authorizationChecked bool
   423  
   424  		contractUpdater := environment.NewContractUpdaterForTesting(
   425  			accounts,
   426  			testContractUpdaterStubs{
   427  				removalEnabled: true,
   428  				auditFunc: func(address runtime.Address, code []byte) (bool, error) {
   429  					// Ensure the voucher check is only called once,
   430  					// for the initial contract deployment,
   431  					// and not for the subsequent update
   432  					require.False(t, authorizationChecked)
   433  					authorizationChecked = true
   434  					return true, nil
   435  				},
   436  			})
   437  
   438  		// deploy contract with voucher
   439  		err = contractUpdater.SetContract(
   440  			runtimeAddress,
   441  			"TestContract",
   442  			[]byte("pub contract TestContract {}"),
   443  			[]common.Address{
   444  				runtimeAddress,
   445  			},
   446  		)
   447  		require.NoError(t, err)
   448  		require.True(t, contractUpdater.HasUpdates())
   449  
   450  		contractUpdateKeys, err := contractUpdater.Commit()
   451  		require.NoError(t, err)
   452  		require.Equal(
   453  			t,
   454  			[]environment.ContractUpdateKey{
   455  				{
   456  					Address: flowCommonAddress,
   457  					Name:    "TestContract",
   458  				},
   459  			},
   460  			contractUpdateKeys,
   461  		)
   462  
   463  		// update should work
   464  		err = contractUpdater.SetContract(
   465  			runtimeAddress,
   466  			"TestContract",
   467  			[]byte("pub contract TestContract {}"),
   468  			[]common.Address{
   469  				runtimeAddress,
   470  			},
   471  		)
   472  		require.NoError(t, err)
   473  		require.True(t, contractUpdater.HasUpdates())
   474  
   475  		// try remove contract should fail
   476  		err = contractUpdater.RemoveContract(
   477  			runtimeAddress,
   478  			"TestContract",
   479  			[]common.Address{
   480  				runtimeAddress,
   481  			},
   482  		)
   483  		require.Error(t, err)
   484  	})
   485  
   486  	t.Run("contract removal without restriction", func(t *testing.T) {
   487  		var authorizationChecked bool
   488  
   489  		contractUpdater := environment.NewContractUpdaterForTesting(
   490  			accounts,
   491  			testContractUpdaterStubs{
   492  				auditFunc: func(address runtime.Address, code []byte) (bool, error) {
   493  					// Ensure the voucher check is only called once,
   494  					// for the initial contract deployment,
   495  					// and not for the subsequent update
   496  					require.False(t, authorizationChecked)
   497  					authorizationChecked = true
   498  					return true, nil
   499  				},
   500  			})
   501  
   502  		// deploy contract with voucher
   503  		err = contractUpdater.SetContract(
   504  			runtimeAddress,
   505  			"TestContract",
   506  			[]byte("pub contract TestContract {}"),
   507  			[]common.Address{
   508  				runtimeAddress,
   509  			},
   510  		)
   511  		require.NoError(t, err)
   512  		require.True(t, contractUpdater.HasUpdates())
   513  
   514  		contractUpdateKeys, err := contractUpdater.Commit()
   515  		require.NoError(t, err)
   516  		require.Equal(
   517  			t,
   518  			[]environment.ContractUpdateKey{
   519  				{
   520  					Address: flowCommonAddress,
   521  					Name:    "TestContract",
   522  				},
   523  			},
   524  			contractUpdateKeys,
   525  		)
   526  
   527  		// update should work
   528  		err = contractUpdater.SetContract(
   529  			runtimeAddress,
   530  			"TestContract",
   531  			[]byte("pub contract TestContract {}"),
   532  			[]common.Address{
   533  				runtimeAddress,
   534  			},
   535  		)
   536  		require.NoError(t, err)
   537  		require.True(t, contractUpdater.HasUpdates())
   538  
   539  		// try remove contract should fail
   540  		err = contractUpdater.RemoveContract(
   541  			runtimeAddress,
   542  			"TestContract",
   543  			[]common.Address{
   544  				runtimeAddress,
   545  			},
   546  		)
   547  		require.NoError(t, err)
   548  		require.True(t, contractUpdater.HasUpdates())
   549  
   550  	})
   551  }