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