github.com/onflow/flow-go@v0.33.17/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 = restrictedCadence.ToGoValue().(bool)
   212  	return restricted, true
   213  }
   214  
   215  func (impl *contractUpdaterStubsImpl) RestrictedRemovalEnabled() bool {
   216  	// TODO read this from the chain similar to the contract deployment
   217  	// but for now we would honor the fallback context flag
   218  	return impl.RestrictContractRemoval
   219  }
   220  
   221  // GetAuthorizedAccounts returns a list of addresses authorized by the service
   222  // account. Used to determine which accounts are permitted to deploy, update,
   223  // or remove contracts.
   224  //
   225  // It reads a storage path from service account and parse the addresses. If any
   226  // issue occurs on the process (missing registers, stored value properly not
   227  // set), it gracefully handles it and falls back to default behaviour (only
   228  // service account be authorized).
   229  func (impl *contractUpdaterStubsImpl) GetAuthorizedAccounts(
   230  	path cadence.Path,
   231  ) []flow.Address {
   232  	// set default to service account only
   233  	service := impl.chain.ServiceAddress()
   234  	defaultAccounts := []flow.Address{service}
   235  
   236  	runtime := impl.runtime.BorrowCadenceRuntime()
   237  	defer impl.runtime.ReturnCadenceRuntime(runtime)
   238  
   239  	value, err := runtime.ReadStored(
   240  		common.MustBytesToAddress(service.Bytes()),
   241  		path)
   242  
   243  	const warningMsg = "failed to read contract authorized accounts from " +
   244  		"service account. using default behaviour instead."
   245  
   246  	if err != nil {
   247  		impl.logger.Warn().Msg(warningMsg)
   248  		return defaultAccounts
   249  	}
   250  	addresses, ok := cadenceValueToAddressSlice(value)
   251  	if !ok {
   252  		impl.logger.Warn().Msg(warningMsg)
   253  		return defaultAccounts
   254  	}
   255  	return addresses
   256  }
   257  
   258  type ContractUpdaterImpl struct {
   259  	tracer          tracing.TracerSpan
   260  	meter           Meter
   261  	accounts        Accounts
   262  	signingAccounts []flow.Address
   263  
   264  	draftUpdates map[common.AddressLocation]ContractUpdate
   265  
   266  	ContractUpdaterStubs
   267  }
   268  
   269  var _ ContractUpdater = &ContractUpdaterImpl{}
   270  
   271  func NewContractUpdaterForTesting(
   272  	accounts Accounts,
   273  	stubs ContractUpdaterStubs,
   274  ) *ContractUpdaterImpl {
   275  	updater := NewContractUpdater(
   276  		tracing.NewTracerSpan(),
   277  		nil,
   278  		accounts,
   279  		nil,
   280  		nil,
   281  		DefaultContractUpdaterParams(),
   282  		nil,
   283  		nil,
   284  		nil)
   285  	updater.ContractUpdaterStubs = stubs
   286  	return updater
   287  }
   288  
   289  func NewContractUpdater(
   290  	tracer tracing.TracerSpan,
   291  	meter Meter,
   292  	accounts Accounts,
   293  	signingAccounts []flow.Address,
   294  	chain flow.Chain,
   295  	params ContractUpdaterParams,
   296  	logger *ProgramLogger,
   297  	systemContracts *SystemContracts,
   298  	runtime *Runtime,
   299  ) *ContractUpdaterImpl {
   300  	updater := &ContractUpdaterImpl{
   301  		tracer:          tracer,
   302  		meter:           meter,
   303  		accounts:        accounts,
   304  		signingAccounts: signingAccounts,
   305  		ContractUpdaterStubs: &contractUpdaterStubsImpl{
   306  			logger:                logger,
   307  			chain:                 chain,
   308  			ContractUpdaterParams: params,
   309  			systemContracts:       systemContracts,
   310  			runtime:               runtime,
   311  		},
   312  	}
   313  
   314  	updater.Reset()
   315  	return updater
   316  }
   317  
   318  func (updater *ContractUpdaterImpl) UpdateAccountContractCode(
   319  	location common.AddressLocation,
   320  	code []byte,
   321  ) error {
   322  	defer updater.tracer.StartChildSpan(
   323  		trace.FVMEnvUpdateAccountContractCode).End()
   324  
   325  	err := updater.meter.MeterComputation(
   326  		ComputationKindUpdateAccountContractCode,
   327  		1)
   328  	if err != nil {
   329  		return fmt.Errorf("update account contract code failed: %w", err)
   330  	}
   331  
   332  	err = updater.SetContract(
   333  		location,
   334  		code,
   335  		updater.signingAccounts)
   336  	if err != nil {
   337  		return fmt.Errorf("updating account contract code failed: %w", err)
   338  	}
   339  
   340  	return nil
   341  }
   342  
   343  func (updater *ContractUpdaterImpl) RemoveAccountContractCode(
   344  	location common.AddressLocation,
   345  ) error {
   346  	defer updater.tracer.StartChildSpan(
   347  		trace.FVMEnvRemoveAccountContractCode).End()
   348  
   349  	err := updater.meter.MeterComputation(
   350  		ComputationKindRemoveAccountContractCode,
   351  		1)
   352  	if err != nil {
   353  		return fmt.Errorf("remove account contract code failed: %w", err)
   354  	}
   355  
   356  	err = updater.RemoveContract(
   357  		location,
   358  		updater.signingAccounts)
   359  	if err != nil {
   360  		return fmt.Errorf("remove account contract code failed: %w", err)
   361  	}
   362  
   363  	return nil
   364  }
   365  
   366  func (updater *ContractUpdaterImpl) SetContract(
   367  	location common.AddressLocation,
   368  	code []byte,
   369  	signingAccounts []flow.Address,
   370  ) error {
   371  	// Initial contract deployments must be authorized by signing accounts.
   372  	//
   373  	// Contract updates are always allowed.
   374  	exists, err := updater.accounts.ContractExists(location.Name, flow.ConvertAddress(location.Address))
   375  	if err != nil {
   376  		return err
   377  	}
   378  
   379  	if !exists && !updater.isAuthorizedForDeployment(signingAccounts) {
   380  		return fmt.Errorf(
   381  			"deploying contract failed: %w",
   382  			errors.NewOperationAuthorizationErrorf(
   383  				"SetContract",
   384  				"deploying contracts requires authorization from specific "+
   385  					"accounts"))
   386  
   387  	}
   388  
   389  	updater.draftUpdates[location] = ContractUpdate{
   390  		Location: location,
   391  		Code:     code,
   392  	}
   393  
   394  	return nil
   395  }
   396  
   397  func (updater *ContractUpdaterImpl) RemoveContract(
   398  	location common.AddressLocation,
   399  	signingAccounts []flow.Address,
   400  ) (err error) {
   401  	// check if authorized
   402  	if !updater.isAuthorizedForRemoval(signingAccounts) {
   403  		return fmt.Errorf("removing contract failed: %w",
   404  			errors.NewOperationAuthorizationErrorf(
   405  				"RemoveContract",
   406  				"removing contracts requires authorization from specific "+
   407  					"accounts"))
   408  	}
   409  
   410  	u := ContractUpdate{Location: location}
   411  	updater.draftUpdates[location] = u
   412  
   413  	return nil
   414  }
   415  
   416  func (updater *ContractUpdaterImpl) Commit() (ContractUpdates, error) {
   417  	updateList := updater.updates()
   418  	updater.Reset()
   419  
   420  	contractUpdates := ContractUpdates{
   421  		Updates:   make([]common.AddressLocation, 0, len(updateList)),
   422  		Deploys:   make([]common.AddressLocation, 0, len(updateList)),
   423  		Deletions: make([]common.AddressLocation, 0, len(updateList)),
   424  	}
   425  
   426  	var err error
   427  	for _, v := range updateList {
   428  		var currentlyExists bool
   429  		currentlyExists, err = updater.accounts.ContractExists(v.Location.Name, flow.ConvertAddress(v.Location.Address))
   430  		if err != nil {
   431  			return ContractUpdates{}, err
   432  		}
   433  		shouldDelete := len(v.Code) == 0
   434  
   435  		if shouldDelete {
   436  			// this is a removal
   437  			contractUpdates.Deletions = append(contractUpdates.Deletions, v.Location)
   438  			err = updater.accounts.DeleteContract(v.Location.Name, flow.ConvertAddress(v.Location.Address))
   439  			if err != nil {
   440  				return ContractUpdates{}, err
   441  			}
   442  		} else {
   443  			if !currentlyExists {
   444  				// this is a deployment
   445  				contractUpdates.Deploys = append(contractUpdates.Deploys, v.Location)
   446  			} else {
   447  				// this is an update
   448  				contractUpdates.Updates = append(contractUpdates.Updates, v.Location)
   449  			}
   450  
   451  			err = updater.accounts.SetContract(v.Location.Name, flow.ConvertAddress(v.Location.Address), v.Code)
   452  			if err != nil {
   453  				return ContractUpdates{}, err
   454  			}
   455  		}
   456  	}
   457  
   458  	return contractUpdates, nil
   459  }
   460  
   461  func (updater *ContractUpdaterImpl) Reset() {
   462  	updater.draftUpdates = make(map[common.AddressLocation]ContractUpdate)
   463  }
   464  
   465  func (updater *ContractUpdaterImpl) HasUpdates() bool {
   466  	return len(updater.draftUpdates) > 0
   467  }
   468  
   469  func (updater *ContractUpdaterImpl) updates() []ContractUpdate {
   470  	if len(updater.draftUpdates) == 0 {
   471  		return nil
   472  	}
   473  	keys := make([]common.AddressLocation, 0, len(updater.draftUpdates))
   474  	updates := make([]ContractUpdate, 0, len(updater.draftUpdates))
   475  	for key, update := range updater.draftUpdates {
   476  		keys = append(keys, key)
   477  		updates = append(updates, update)
   478  	}
   479  
   480  	sort.Sort(&sortableContractUpdates{keys: keys, updates: updates})
   481  	return updates
   482  }
   483  
   484  func (updater *ContractUpdaterImpl) isAuthorizedForDeployment(
   485  	signingAccounts []flow.Address,
   486  ) bool {
   487  	if updater.RestrictedDeploymentEnabled() {
   488  		return updater.isAuthorized(
   489  			signingAccounts,
   490  			blueprints.ContractDeploymentAuthorizedAddressesPath)
   491  	}
   492  	return true
   493  }
   494  
   495  func (updater *ContractUpdaterImpl) isAuthorizedForRemoval(
   496  	signingAccounts []flow.Address,
   497  ) bool {
   498  	if updater.RestrictedRemovalEnabled() {
   499  		return updater.isAuthorized(
   500  			signingAccounts,
   501  			blueprints.ContractRemovalAuthorizedAddressesPath)
   502  	}
   503  	return true
   504  }
   505  
   506  func (updater *ContractUpdaterImpl) isAuthorized(
   507  	signingAccounts []flow.Address,
   508  	path cadence.Path,
   509  ) bool {
   510  	accts := updater.GetAuthorizedAccounts(path)
   511  	for _, authorized := range accts {
   512  		for _, signer := range signingAccounts {
   513  			if signer == authorized {
   514  				// a single authorized singer is enough
   515  				return true
   516  			}
   517  		}
   518  	}
   519  	return false
   520  }
   521  
   522  func cadenceValueToAddressSlice(value cadence.Value) (
   523  	[]flow.Address,
   524  	bool,
   525  ) {
   526  	v, ok := value.(cadence.Array)
   527  	if !ok {
   528  		return nil, false
   529  	}
   530  
   531  	addresses := make([]flow.Address, 0, len(v.Values))
   532  	for _, value := range v.Values {
   533  		a, ok := value.(cadence.Address)
   534  		if !ok {
   535  			return nil, false
   536  		}
   537  		addresses = append(addresses, flow.ConvertAddress(a))
   538  	}
   539  	return addresses, true
   540  }