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 }