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 }