github.com/onflow/flow-go@v0.35.7-crescendo-preview.23-atree-inlining/fvm/environment/contract_updater.go (about)

     1  package environment
     2  
     3  import (
     4  	"bytes"
     5  	"fmt"
     6  	"sort"
     7  
     8  	"github.com/onflow/cadence"
     9  	"github.com/onflow/cadence/runtime/common"
    10  
    11  	"github.com/onflow/flow-go/fvm/blueprints"
    12  	"github.com/onflow/flow-go/fvm/errors"
    13  	"github.com/onflow/flow-go/fvm/storage/state"
    14  	"github.com/onflow/flow-go/fvm/tracing"
    15  	"github.com/onflow/flow-go/model/flow"
    16  	"github.com/onflow/flow-go/module/trace"
    17  )
    18  
    19  type ContractUpdaterParams struct {
    20  	// Depricated: RestrictedDeploymentEnabled is deprecated use
    21  	// SetIsContractDeploymentRestrictedTransaction instead.
    22  	// Can be removed after all networks are migrated to
    23  	// SetIsContractDeploymentRestrictedTransaction
    24  	RestrictContractDeployment bool
    25  	RestrictContractRemoval    bool
    26  }
    27  
    28  func DefaultContractUpdaterParams() ContractUpdaterParams {
    29  	return ContractUpdaterParams{
    30  		RestrictContractDeployment: true,
    31  		RestrictContractRemoval:    true,
    32  	}
    33  }
    34  
    35  type sortableContractUpdates struct {
    36  	keys    []common.AddressLocation
    37  	updates []ContractUpdate
    38  }
    39  
    40  func (lists *sortableContractUpdates) Len() int {
    41  	return len(lists.keys)
    42  }
    43  
    44  func (lists *sortableContractUpdates) Swap(i, j int) {
    45  	lists.keys[i], lists.keys[j] = lists.keys[j], lists.keys[i]
    46  	lists.updates[i], lists.updates[j] = lists.updates[j], lists.updates[i]
    47  }
    48  
    49  func (lists *sortableContractUpdates) Less(i, j int) bool {
    50  	switch bytes.Compare(lists.keys[i].Address[:], lists.keys[j].Address[:]) {
    51  	case -1:
    52  		return true
    53  	case 0:
    54  		return lists.keys[i].Name < lists.keys[j].Name
    55  	default:
    56  		return false
    57  	}
    58  }
    59  
    60  // ContractUpdater handles all smart contracts modification. It captures
    61  // contract updates and defer the updates to the end of the txn execution.
    62  //
    63  // Note that scripts cannot modify smart contracts, but must expose the API in
    64  // compliance with the runtime environment interface.
    65  type ContractUpdater interface {
    66  	// Cadence's runtime API.  Note that the script variant will return
    67  	// OperationNotSupportedError.
    68  	UpdateAccountContractCode(
    69  		location common.AddressLocation,
    70  		code []byte,
    71  	) error
    72  
    73  	// Cadence's runtime API.  Note that the script variant will return
    74  	// OperationNotSupportedError.
    75  	RemoveAccountContractCode(location common.AddressLocation) error
    76  
    77  	Commit() (ContractUpdates, error)
    78  
    79  	Reset()
    80  }
    81  
    82  type ParseRestrictedContractUpdater struct {
    83  	txnState state.NestedTransactionPreparer
    84  	impl     ContractUpdater
    85  }
    86  
    87  func NewParseRestrictedContractUpdater(
    88  	txnState state.NestedTransactionPreparer,
    89  	impl ContractUpdater,
    90  ) ParseRestrictedContractUpdater {
    91  	return ParseRestrictedContractUpdater{
    92  		txnState: txnState,
    93  		impl:     impl,
    94  	}
    95  }
    96  
    97  func (updater ParseRestrictedContractUpdater) UpdateAccountContractCode(
    98  	location common.AddressLocation,
    99  	code []byte,
   100  ) error {
   101  	return parseRestrict2Arg(
   102  		updater.txnState,
   103  		trace.FVMEnvUpdateAccountContractCode,
   104  		updater.impl.UpdateAccountContractCode,
   105  		location,
   106  		code)
   107  }
   108  
   109  func (updater ParseRestrictedContractUpdater) RemoveAccountContractCode(
   110  	location common.AddressLocation,
   111  ) error {
   112  	return parseRestrict1Arg(
   113  		updater.txnState,
   114  		trace.FVMEnvRemoveAccountContractCode,
   115  		updater.impl.RemoveAccountContractCode,
   116  		location)
   117  }
   118  
   119  func (updater ParseRestrictedContractUpdater) Commit() (
   120  	ContractUpdates,
   121  	error,
   122  ) {
   123  	return updater.impl.Commit()
   124  }
   125  
   126  func (updater ParseRestrictedContractUpdater) Reset() {
   127  	updater.impl.Reset()
   128  }
   129  
   130  type NoContractUpdater struct{}
   131  
   132  func (NoContractUpdater) UpdateAccountContractCode(
   133  	_ common.AddressLocation,
   134  	_ []byte,
   135  ) error {
   136  	return errors.NewOperationNotSupportedError("UpdateAccountContractCode")
   137  }
   138  
   139  func (NoContractUpdater) RemoveAccountContractCode(
   140  	_ common.AddressLocation,
   141  ) error {
   142  	return errors.NewOperationNotSupportedError("RemoveAccountContractCode")
   143  }
   144  
   145  func (NoContractUpdater) Commit() (ContractUpdates, error) {
   146  	return ContractUpdates{}, nil
   147  }
   148  
   149  func (NoContractUpdater) Reset() {
   150  }
   151  
   152  // Expose stub interface for testing.
   153  type ContractUpdaterStubs interface {
   154  	RestrictedDeploymentEnabled() bool
   155  	RestrictedRemovalEnabled() bool
   156  
   157  	GetAuthorizedAccounts(path cadence.Path) []flow.Address
   158  }
   159  
   160  type contractUpdaterStubsImpl struct {
   161  	chain flow.Chain
   162  
   163  	ContractUpdaterParams
   164  
   165  	logger          *ProgramLogger
   166  	systemContracts *SystemContracts
   167  	runtime         *Runtime
   168  }
   169  
   170  func (impl *contractUpdaterStubsImpl) RestrictedDeploymentEnabled() bool {
   171  	enabled, defined := impl.getIsContractDeploymentRestricted()
   172  	if !defined {
   173  		// If the contract deployment bool is not set by the state
   174  		// fallback to the default value set by the configuration
   175  		// after the contract deployment bool is set by the state on all
   176  		// chains, this logic can be simplified
   177  		return impl.RestrictContractDeployment
   178  	}
   179  	return enabled
   180  }
   181  
   182  // GetIsContractDeploymentRestricted returns if contract deployment
   183  // restriction is defined in the state and the value of it
   184  func (impl *contractUpdaterStubsImpl) getIsContractDeploymentRestricted() (
   185  	restricted bool,
   186  	defined bool,
   187  ) {
   188  	service := impl.chain.ServiceAddress()
   189  
   190  	runtime := impl.runtime.BorrowCadenceRuntime()
   191  	defer impl.runtime.ReturnCadenceRuntime(runtime)
   192  
   193  	value, err := runtime.ReadStored(
   194  		common.MustBytesToAddress(service.Bytes()),
   195  		blueprints.IsContractDeploymentRestrictedPath)
   196  	if err != nil {
   197  		impl.logger.
   198  			Debug().
   199  			Msg("Failed to read IsContractDeploymentRestricted from the " +
   200  				"service account. Using value from context instead.")
   201  		return false, false
   202  	}
   203  	restrictedCadence, ok := value.(cadence.Bool)
   204  	if !ok {
   205  		impl.logger.
   206  			Debug().
   207  			Msg("Failed to parse IsContractDeploymentRestricted from the " +
   208  				"service account. Using value from context instead.")
   209  		return false, false
   210  	}
   211  	restricted = bool(restrictedCadence)
   212  
   213  	return restricted, true
   214  }
   215  
   216  func (impl *contractUpdaterStubsImpl) RestrictedRemovalEnabled() bool {
   217  	// TODO read this from the chain similar to the contract deployment
   218  	// but for now we would honor the fallback context flag
   219  	return impl.RestrictContractRemoval
   220  }
   221  
   222  // GetAuthorizedAccounts returns a list of addresses authorized by the service
   223  // account. Used to determine which accounts are permitted to deploy, update,
   224  // or remove contracts.
   225  //
   226  // It reads a storage path from service account and parse the addresses. If any
   227  // issue occurs on the process (missing registers, stored value properly not
   228  // set), it gracefully handles it and falls back to default behaviour (only
   229  // service account be authorized).
   230  func (impl *contractUpdaterStubsImpl) GetAuthorizedAccounts(
   231  	path cadence.Path,
   232  ) []flow.Address {
   233  	// set default to service account only
   234  	service := impl.chain.ServiceAddress()
   235  	defaultAccounts := []flow.Address{service}
   236  
   237  	runtime := impl.runtime.BorrowCadenceRuntime()
   238  	defer impl.runtime.ReturnCadenceRuntime(runtime)
   239  
   240  	value, err := runtime.ReadStored(
   241  		common.MustBytesToAddress(service.Bytes()),
   242  		path)
   243  
   244  	const warningMsg = "failed to read contract authorized accounts from " +
   245  		"service account. using default behaviour instead."
   246  
   247  	if err != nil {
   248  		impl.logger.Warn().Msg(warningMsg)
   249  		return defaultAccounts
   250  	}
   251  	addresses, ok := cadenceValueToAddressSlice(value)
   252  	if !ok {
   253  		impl.logger.Warn().Msg(warningMsg)
   254  		return defaultAccounts
   255  	}
   256  	return addresses
   257  }
   258  
   259  type ContractUpdaterImpl struct {
   260  	tracer          tracing.TracerSpan
   261  	meter           Meter
   262  	accounts        Accounts
   263  	signingAccounts []flow.Address
   264  
   265  	draftUpdates map[common.AddressLocation]ContractUpdate
   266  
   267  	ContractUpdaterStubs
   268  }
   269  
   270  var _ ContractUpdater = &ContractUpdaterImpl{}
   271  
   272  func NewContractUpdaterForTesting(
   273  	accounts Accounts,
   274  	stubs ContractUpdaterStubs,
   275  ) *ContractUpdaterImpl {
   276  	updater := NewContractUpdater(
   277  		tracing.NewTracerSpan(),
   278  		nil,
   279  		accounts,
   280  		nil,
   281  		nil,
   282  		DefaultContractUpdaterParams(),
   283  		nil,
   284  		nil,
   285  		nil)
   286  	updater.ContractUpdaterStubs = stubs
   287  	return updater
   288  }
   289  
   290  func NewContractUpdater(
   291  	tracer tracing.TracerSpan,
   292  	meter Meter,
   293  	accounts Accounts,
   294  	signingAccounts []flow.Address,
   295  	chain flow.Chain,
   296  	params ContractUpdaterParams,
   297  	logger *ProgramLogger,
   298  	systemContracts *SystemContracts,
   299  	runtime *Runtime,
   300  ) *ContractUpdaterImpl {
   301  	updater := &ContractUpdaterImpl{
   302  		tracer:          tracer,
   303  		meter:           meter,
   304  		accounts:        accounts,
   305  		signingAccounts: signingAccounts,
   306  		ContractUpdaterStubs: &contractUpdaterStubsImpl{
   307  			logger:                logger,
   308  			chain:                 chain,
   309  			ContractUpdaterParams: params,
   310  			systemContracts:       systemContracts,
   311  			runtime:               runtime,
   312  		},
   313  	}
   314  
   315  	updater.Reset()
   316  	return updater
   317  }
   318  
   319  func (updater *ContractUpdaterImpl) UpdateAccountContractCode(
   320  	location common.AddressLocation,
   321  	code []byte,
   322  ) error {
   323  	defer updater.tracer.StartChildSpan(
   324  		trace.FVMEnvUpdateAccountContractCode).End()
   325  
   326  	err := updater.meter.MeterComputation(
   327  		ComputationKindUpdateAccountContractCode,
   328  		1)
   329  	if err != nil {
   330  		return fmt.Errorf("update account contract code failed: %w", err)
   331  	}
   332  
   333  	err = updater.SetContract(
   334  		location,
   335  		code,
   336  		updater.signingAccounts)
   337  	if err != nil {
   338  		return fmt.Errorf("updating account contract code failed: %w", err)
   339  	}
   340  
   341  	return nil
   342  }
   343  
   344  func (updater *ContractUpdaterImpl) RemoveAccountContractCode(
   345  	location common.AddressLocation,
   346  ) error {
   347  	defer updater.tracer.StartChildSpan(
   348  		trace.FVMEnvRemoveAccountContractCode).End()
   349  
   350  	err := updater.meter.MeterComputation(
   351  		ComputationKindRemoveAccountContractCode,
   352  		1)
   353  	if err != nil {
   354  		return fmt.Errorf("remove account contract code failed: %w", err)
   355  	}
   356  
   357  	err = updater.RemoveContract(
   358  		location,
   359  		updater.signingAccounts)
   360  	if err != nil {
   361  		return fmt.Errorf("remove account contract code failed: %w", err)
   362  	}
   363  
   364  	return nil
   365  }
   366  
   367  func (updater *ContractUpdaterImpl) SetContract(
   368  	location common.AddressLocation,
   369  	code []byte,
   370  	signingAccounts []flow.Address,
   371  ) error {
   372  	// Initial contract deployments must be authorized by signing accounts.
   373  	//
   374  	// Contract updates are always allowed.
   375  	exists, err := updater.accounts.ContractExists(location.Name, flow.ConvertAddress(location.Address))
   376  	if err != nil {
   377  		return err
   378  	}
   379  
   380  	if !exists && !updater.isAuthorizedForDeployment(signingAccounts) {
   381  		return fmt.Errorf(
   382  			"deploying contract failed: %w",
   383  			errors.NewOperationAuthorizationErrorf(
   384  				"SetContract",
   385  				"deploying contracts requires authorization from specific "+
   386  					"accounts"))
   387  
   388  	}
   389  
   390  	updater.draftUpdates[location] = ContractUpdate{
   391  		Location: location,
   392  		Code:     code,
   393  	}
   394  
   395  	return nil
   396  }
   397  
   398  func (updater *ContractUpdaterImpl) RemoveContract(
   399  	location common.AddressLocation,
   400  	signingAccounts []flow.Address,
   401  ) (err error) {
   402  	// check if authorized
   403  	if !updater.isAuthorizedForRemoval(signingAccounts) {
   404  		return fmt.Errorf("removing contract failed: %w",
   405  			errors.NewOperationAuthorizationErrorf(
   406  				"RemoveContract",
   407  				"removing contracts requires authorization from specific "+
   408  					"accounts"))
   409  	}
   410  
   411  	u := ContractUpdate{Location: location}
   412  	updater.draftUpdates[location] = u
   413  
   414  	return nil
   415  }
   416  
   417  func (updater *ContractUpdaterImpl) Commit() (ContractUpdates, error) {
   418  	updateList := updater.updates()
   419  	updater.Reset()
   420  
   421  	contractUpdates := ContractUpdates{
   422  		Updates:   make([]common.AddressLocation, 0, len(updateList)),
   423  		Deploys:   make([]common.AddressLocation, 0, len(updateList)),
   424  		Deletions: make([]common.AddressLocation, 0, len(updateList)),
   425  	}
   426  
   427  	var err error
   428  	for _, v := range updateList {
   429  		var currentlyExists bool
   430  		currentlyExists, err = updater.accounts.ContractExists(v.Location.Name, flow.ConvertAddress(v.Location.Address))
   431  		if err != nil {
   432  			return ContractUpdates{}, err
   433  		}
   434  		shouldDelete := len(v.Code) == 0
   435  
   436  		if shouldDelete {
   437  			// this is a removal
   438  			contractUpdates.Deletions = append(contractUpdates.Deletions, v.Location)
   439  			err = updater.accounts.DeleteContract(v.Location.Name, flow.ConvertAddress(v.Location.Address))
   440  			if err != nil {
   441  				return ContractUpdates{}, err
   442  			}
   443  		} else {
   444  			if !currentlyExists {
   445  				// this is a deployment
   446  				contractUpdates.Deploys = append(contractUpdates.Deploys, v.Location)
   447  			} else {
   448  				// this is an update
   449  				contractUpdates.Updates = append(contractUpdates.Updates, v.Location)
   450  			}
   451  
   452  			err = updater.accounts.SetContract(v.Location.Name, flow.ConvertAddress(v.Location.Address), v.Code)
   453  			if err != nil {
   454  				return ContractUpdates{}, err
   455  			}
   456  		}
   457  	}
   458  
   459  	return contractUpdates, nil
   460  }
   461  
   462  func (updater *ContractUpdaterImpl) Reset() {
   463  	updater.draftUpdates = make(map[common.AddressLocation]ContractUpdate)
   464  }
   465  
   466  func (updater *ContractUpdaterImpl) HasUpdates() bool {
   467  	return len(updater.draftUpdates) > 0
   468  }
   469  
   470  func (updater *ContractUpdaterImpl) updates() []ContractUpdate {
   471  	if len(updater.draftUpdates) == 0 {
   472  		return nil
   473  	}
   474  	keys := make([]common.AddressLocation, 0, len(updater.draftUpdates))
   475  	updates := make([]ContractUpdate, 0, len(updater.draftUpdates))
   476  	for key, update := range updater.draftUpdates {
   477  		keys = append(keys, key)
   478  		updates = append(updates, update)
   479  	}
   480  
   481  	sort.Sort(&sortableContractUpdates{keys: keys, updates: updates})
   482  	return updates
   483  }
   484  
   485  func (updater *ContractUpdaterImpl) isAuthorizedForDeployment(
   486  	signingAccounts []flow.Address,
   487  ) bool {
   488  	if updater.RestrictedDeploymentEnabled() {
   489  		return updater.isAuthorized(
   490  			signingAccounts,
   491  			blueprints.ContractDeploymentAuthorizedAddressesPath)
   492  	}
   493  	return true
   494  }
   495  
   496  func (updater *ContractUpdaterImpl) isAuthorizedForRemoval(
   497  	signingAccounts []flow.Address,
   498  ) bool {
   499  	if updater.RestrictedRemovalEnabled() {
   500  		return updater.isAuthorized(
   501  			signingAccounts,
   502  			blueprints.ContractRemovalAuthorizedAddressesPath)
   503  	}
   504  	return true
   505  }
   506  
   507  func (updater *ContractUpdaterImpl) isAuthorized(
   508  	signingAccounts []flow.Address,
   509  	path cadence.Path,
   510  ) bool {
   511  	accts := updater.GetAuthorizedAccounts(path)
   512  	for _, authorized := range accts {
   513  		for _, signer := range signingAccounts {
   514  			if signer == authorized {
   515  				// a single authorized singer is enough
   516  				return true
   517  			}
   518  		}
   519  	}
   520  	return false
   521  }
   522  
   523  func cadenceValueToAddressSlice(value cadence.Value) (
   524  	[]flow.Address,
   525  	bool,
   526  ) {
   527  	v, ok := value.(cadence.Array)
   528  	if !ok {
   529  		return nil, false
   530  	}
   531  
   532  	addresses := make([]flow.Address, 0, len(v.Values))
   533  	for _, value := range v.Values {
   534  		a, ok := value.(cadence.Address)
   535  		if !ok {
   536  			return nil, false
   537  		}
   538  		addresses = append(addresses, flow.ConvertAddress(a))
   539  	}
   540  	return addresses, true
   541  }