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