1use std::{
2 collections::{BTreeMap, BTreeSet, HashMap, HashSet},
3 sync::Arc,
4 time::Instant,
5};
6
7use async_trait::async_trait;
8use ave_actors::{
9 Actor, ActorContext, ActorError, ActorPath, Handler, Message,
10 NotPersistentActor, Response,
11};
12use ave_common::{
13 Namespace, SchemaType, ValueWrapper, identity::PublicKey,
14 schematype::ReservedWords,
15};
16use borsh::{BorshDeserialize, to_vec};
17use serde::{Deserialize, Serialize};
18use serde_json::json;
19use tokio::sync::RwLock;
20use tracing::{Span, debug, error, info_span};
21use types::{ContractResult, RunnerResult};
22use wasmtime::{Module, Store};
23
24use crate::{
25 evaluation::runner::{error::RunnerError, types::EvaluateInfo},
26 governance::{
27 data::GovernanceData,
28 events::{
29 GovernanceEvent, MemberEvent, PoliciesEvent, RolesEvent,
30 SchemasEvent, gov_policie_change_is_empty,
31 gov_role_event_check_data, gov_role_event_is_empty,
32 governance_event_is_empty, member_event_is_empty,
33 policies_event_is_empty, roles_event_is_empty,
34 schema_change_is_empty, schema_id_policie_is_empty,
35 schema_id_role_check_data, schema_id_role_is_empty,
36 schema_policie_change_is_empty, schemas_event_is_empty,
37 tracker_schemas_role_event_check_data,
38 tracker_schemas_role_event_is_empty,
39 },
40 model::{CreatorWitness, RoleCreator, Schema},
41 },
42 metrics::try_core_metrics,
43 model::common::contract::{
44 MAX_FUEL, MemoryManager, WasmLimits, WasmRuntime, generate_linker,
45 },
46};
47
48type AddRemoveChangeSchema = (
49 HashSet<SchemaType>,
50 HashSet<SchemaType>,
51 HashSet<SchemaType>,
52);
53
54pub mod error;
55pub mod types;
56
57#[derive(Clone, Debug, Serialize, Deserialize)]
58pub struct Runner;
59
60impl Runner {
61 fn adapt_creator_witnesses_to_schema_viewpoints(
62 governance: &mut GovernanceData,
63 schema_id: &SchemaType,
64 schema_viewpoints: &BTreeSet<String>,
65 ) {
66 let Some(roles_schema) = governance.roles_schema.get_mut(schema_id)
67 else {
68 return;
69 };
70
71 roles_schema.creator = roles_schema
72 .creator
73 .iter()
74 .map(|creator| {
75 let mut creator = creator.clone();
76 creator.witnesses = creator
77 .witnesses
78 .iter()
79 .map(|witness| {
80 if witness.viewpoints.is_empty()
81 || witness.viewpoints.contains(
82 &ReservedWords::AllViewpoints.to_string(),
83 )
84 {
85 witness.clone()
86 } else {
87 CreatorWitness {
88 name: witness.name.clone(),
89 viewpoints: witness
90 .viewpoints
91 .intersection(schema_viewpoints)
92 .cloned()
93 .collect(),
94 }
95 }
96 })
97 .collect();
98 creator
99 })
100 .collect::<BTreeSet<RoleCreator>>();
101 }
102
103 async fn execute_contract(
104 ctx: &ActorContext<Self>,
105 data: &EvaluateInfo,
106 is_owner: bool,
107 ) -> Result<(RunnerResult, Vec<SchemaType>), RunnerError> {
108 match data {
109 EvaluateInfo::GovFact { payload, state } => {
110 Self::execute_fact_gov(state.clone(), payload).await
111 }
112 EvaluateInfo::GovTransfer { new_owner, state } => {
113 Self::execute_transfer_gov(state, new_owner)
114 }
115 EvaluateInfo::GovConfirm {
116 new_owner,
117 old_owner_name,
118 state,
119 } => Self::execute_confirm_gov(
120 state.clone(),
121 old_owner_name.clone(),
122 new_owner,
123 ),
124 EvaluateInfo::TrackerSchemasFact {
125 contract,
126 init_state,
127 state,
128 payload,
129 } => {
130 Self::execute_fact_not_gov(
131 ctx, state, init_state, payload, contract, is_owner,
132 )
133 .await
134 }
135 EvaluateInfo::TrackerSchemasTransfer {
136 new_owner,
137 old_owner,
138 namespace,
139 schema_id,
140 members,
141 creators,
142 } => Self::execute_transfer_not_gov(
143 members,
144 creators,
145 new_owner,
146 old_owner,
147 namespace.clone(),
148 schema_id,
149 ),
150 }
151 }
152
153 fn execute_transfer_not_gov(
154 members: &BTreeSet<PublicKey>,
155 creators: &BTreeMap<PublicKey, BTreeSet<Namespace>>,
156 new_owner: &PublicKey,
157 old_owner: &PublicKey,
158 namespace: Namespace,
159 schema_id: &SchemaType,
160 ) -> Result<(RunnerResult, Vec<SchemaType>), RunnerError> {
161 if new_owner.is_empty() {
162 return Err(RunnerError::InvalidEvent {
163 location: "execute_transfer_not_gov",
164 kind: error::InvalidEventKind::Empty {
165 what: "new owner PublicKey".to_owned(),
166 },
167 });
168 }
169
170 if new_owner == old_owner {
171 return Err(RunnerError::InvalidEvent {
172 location: "execute_transfer_not_gov",
173 kind: error::InvalidEventKind::SameValue {
174 what: "new owner (same as current owner)".to_owned(),
175 },
176 });
177 }
178
179 if !members.contains(new_owner) {
180 return Err(RunnerError::InvalidEvent {
181 location: "execute_transfer_not_gov",
182 kind: error::InvalidEventKind::NotMember {
183 who: format!("new owner {}", new_owner),
184 },
185 });
186 }
187
188 if !creators.get(new_owner).is_some_and(|namespaces| {
189 namespaces.iter().any(|creator_namespace| {
190 creator_namespace.is_ancestor_or_equal_of(&namespace)
191 })
192 }) {
193 return Err(RunnerError::InvalidEvent {
194 location: "execute_transfer_not_gov",
195 kind: error::InvalidEventKind::MissingRole {
196 who: new_owner.to_string(),
197 role: "Creator".to_owned(),
198 context: format!(
199 "schema {} with namespace {}",
200 schema_id, namespace
201 ),
202 },
203 });
204 }
205
206 Ok((
207 RunnerResult {
208 approval_required: false,
209 final_state: ValueWrapper(json!([])),
210 },
211 vec![],
212 ))
213 }
214
215 fn execute_transfer_gov(
216 governance: &GovernanceData,
217 new_owner: &PublicKey,
218 ) -> Result<(RunnerResult, Vec<SchemaType>), RunnerError> {
219 if new_owner.is_empty() {
220 return Err(RunnerError::InvalidEvent {
221 location: "execute_transfer_gov",
222 kind: error::InvalidEventKind::Empty {
223 what: "new owner PublicKey".to_owned(),
224 },
225 });
226 }
227
228 let Some(owner_key) =
229 governance.members.get(&ReservedWords::Owner.to_string())
230 else {
231 return Err(RunnerError::InvalidEvent {
232 location: "execute_transfer_gov",
233 kind: error::InvalidEventKind::NotFound {
234 what: "member".to_owned(),
235 id: ReservedWords::Owner.to_string(),
236 },
237 });
238 };
239
240 if owner_key == new_owner {
241 return Err(RunnerError::InvalidEvent {
242 location: "execute_transfer_gov",
243 kind: error::InvalidEventKind::SameValue {
244 what: "new owner (same as current owner)".to_owned(),
245 },
246 });
247 }
248
249 if !governance.is_member(new_owner) {
250 return Err(RunnerError::InvalidEvent {
251 location: "execute_transfer_gov",
252 kind: error::InvalidEventKind::NotMember {
253 who: format!("new owner {}", new_owner),
254 },
255 });
256 }
257
258 Ok((
259 RunnerResult {
260 approval_required: false,
261 final_state: ValueWrapper(json!([])),
262 },
263 vec![],
264 ))
265 }
266
267 fn execute_confirm_gov(
268 mut governance: GovernanceData,
269 old_owner_name: Option<String>,
270 new_owner: &PublicKey,
271 ) -> Result<(RunnerResult, Vec<SchemaType>), RunnerError> {
272 if new_owner.is_empty() {
273 return Err(RunnerError::InvalidEvent {
274 location: "execute_confirm_gov",
275 kind: error::InvalidEventKind::Empty {
276 what: "new owner PublicKey".to_owned(),
277 },
278 });
279 }
280
281 let Some(old_owner_key) = governance
282 .members
283 .get(&ReservedWords::Owner.to_string())
284 .cloned()
285 else {
286 return Err(RunnerError::InvalidEvent {
287 location: "execute_confirm_gov",
288 kind: error::InvalidEventKind::NotFound {
289 what: "member".to_owned(),
290 id: ReservedWords::Owner.to_string(),
291 },
292 });
293 };
294
295 let Some(new_owner_member) = governance
296 .members
297 .iter()
298 .find(|x| x.1 == new_owner)
299 .map(|x| x.0)
300 .cloned()
301 else {
302 return Err(RunnerError::InvalidEvent {
303 location: "execute_confirm_gov",
304 kind: error::InvalidEventKind::NotMember {
305 who: format!("new owner {}", new_owner),
306 },
307 });
308 };
309
310 governance
311 .members
312 .insert(ReservedWords::Owner.to_string(), new_owner.clone());
313 governance.members.remove(&new_owner_member);
314
315 governance.update_name_role(new_owner_member);
316
317 if let Some(old_owner_name) = old_owner_name {
318 if old_owner_name != old_owner_name.trim() {
319 return Err(RunnerError::InvalidEvent {
320 location: "execute_confirm_gov",
321 kind: error::InvalidEventKind::InvalidValue {
322 field: "new name for old owner".to_owned(),
323 reason: "cannot have leading or trailing whitespace"
324 .to_owned(),
325 },
326 });
327 }
328
329 if old_owner_name.is_empty() {
330 return Err(RunnerError::InvalidEvent {
331 location: "execute_confirm_gov",
332 kind: error::InvalidEventKind::Empty {
333 what: "new name for old owner".to_owned(),
334 },
335 });
336 }
337
338 if old_owner_name.len() > 100 {
339 return Err(RunnerError::InvalidEvent {
340 location: "execute_confirm_gov",
341 kind: error::InvalidEventKind::InvalidSize {
342 field: "old owner new name".to_owned(),
343 actual: old_owner_name.len(),
344 max: 100,
345 },
346 });
347 }
348
349 if old_owner_name == ReservedWords::Any.to_string()
350 || old_owner_name == ReservedWords::Witnesses.to_string()
351 || old_owner_name == ReservedWords::Owner.to_string()
352 {
353 return Err(RunnerError::InvalidEvent {
354 location: "execute_confirm_gov",
355 kind: error::InvalidEventKind::ReservedWord {
356 field: "old owner new name".to_owned(),
357 value: old_owner_name,
358 },
359 });
360 }
361
362 if governance
363 .members
364 .insert(old_owner_name.clone(), old_owner_key)
365 .is_some()
366 {
367 return Err(RunnerError::InvalidEvent {
368 location: "execute_confirm_gov",
369 kind: error::InvalidEventKind::AlreadyExists {
370 what: "member".to_owned(),
371 id: old_owner_name,
372 },
373 });
374 }
375
376 governance.roles_gov.witness.insert(old_owner_name);
377 }
378
379 let mod_governance = governance.to_value_wrapper();
380
381 Ok((
382 RunnerResult {
383 final_state: mod_governance,
384 approval_required: false,
385 },
386 vec![],
387 ))
388 }
389
390 async fn execute_fact_not_gov(
391 ctx: &ActorContext<Self>,
392 state: &ValueWrapper,
393 init_state: &ValueWrapper,
394 payload: &ValueWrapper,
395 contract_name: &str,
396 is_owner: bool,
397 ) -> Result<(RunnerResult, Vec<SchemaType>), RunnerError> {
398 let Some(wasm_runtime) = ctx
399 .system()
400 .get_helper::<Arc<WasmRuntime>>("wasm_runtime")
401 .await
402 else {
403 return Err(RunnerError::MissingHelper {
404 name: "wasm_runtime",
405 });
406 };
407
408 let Some(contracts) = ctx
409 .system()
410 .get_helper::<Arc<RwLock<HashMap<String, Arc<Module>>>>>(
411 "contracts",
412 )
413 .await
414 else {
415 return Err(RunnerError::MissingHelper { name: "contracts" });
416 };
417
418 let module = {
419 let contracts = contracts.read().await;
420 let Some(module) = contracts.get(contract_name) else {
421 return Err(RunnerError::ContractNotFound {
422 name: contract_name.to_owned(),
423 });
424 };
425 let result = module.clone();
426 drop(contracts);
427 result
428 };
429
430 let (context, state_ptr, init_state_ptr, event_ptr) =
431 Self::generate_context(
432 state,
433 init_state,
434 payload,
435 &wasm_runtime.limits,
436 )?;
437
438 let mut store = Store::new(&wasm_runtime.engine, context);
439
440 store.limiter(|data| &mut data.store_limits);
442
443 store
444 .set_fuel(MAX_FUEL)
445 .map_err(|e| RunnerError::WasmError {
446 operation: "set fuel",
447 details: e.to_string(),
448 })?;
449
450 let linker = generate_linker(&wasm_runtime.engine)?;
451
452 let instance =
453 linker.instantiate(&mut store, &module).map_err(|e| {
454 RunnerError::WasmError {
455 operation: "instantiate",
456 details: e.to_string(),
457 }
458 })?;
459
460 let contract_entrypoint = instance
461 .get_typed_func::<(u32, u32, u32, u32), u32>(
462 &mut store,
463 "main_function",
464 )
465 .map_err(|e| RunnerError::WasmError {
466 operation: "get entrypoint main_function",
467 details: e.to_string(),
468 })?;
469
470 let result_ptr = contract_entrypoint
471 .call(
472 &mut store,
473 (
474 state_ptr,
475 init_state_ptr,
476 event_ptr,
477 if is_owner { 1 } else { 0 },
478 ),
479 )
480 .map_err(|e| RunnerError::WasmError {
481 operation: "call entrypoint",
482 details: e.to_string(),
483 })?;
484
485 let result = Self::get_result(&store, result_ptr)?;
486 Ok((
487 RunnerResult {
488 approval_required: false,
489 final_state: result.final_state,
490 },
491 vec![],
492 ))
493 }
494
495 async fn execute_fact_gov(
496 mut governance: GovernanceData,
497 event: &ValueWrapper,
498 ) -> Result<(RunnerResult, Vec<SchemaType>), RunnerError> {
499 let event: GovernanceEvent = serde_json::from_value(event.0.clone())
500 .map_err(|e| RunnerError::InvalidEvent {
501 location: "execute_fact_gov",
502 kind: error::InvalidEventKind::Other {
503 msg: format!(
504 "failed to deserialize GovernanceEvent: {}",
505 e
506 ),
507 },
508 })?;
509
510 if governance_event_is_empty(&event) {
511 return Err(RunnerError::InvalidEvent {
512 location: "execute_fact_gov",
513 kind: error::InvalidEventKind::Empty {
514 what: "GovernanceEvent".to_owned(),
515 },
516 });
517 }
518
519 if let Some(member_event) = event.members {
520 let remove = Self::check_members(&member_event, &mut governance)?;
521 if !remove.is_empty() {
522 governance.remove_member_role(&remove);
523 }
524 }
525
526 let add_change_schemas = if let Some(schema_event) = event.schemas {
527 let (add_schemas, remove_schemas, change_schemas) =
528 Self::apply_schemas(&schema_event, &mut governance)?;
529 governance.remove_schema(remove_schemas);
530 governance.add_schema(add_schemas.clone());
531
532 add_schemas
533 .union(&change_schemas)
534 .cloned()
535 .collect::<Vec<SchemaType>>()
536 } else {
537 vec![]
538 };
539
540 if let Some(roles_event) = event.roles {
541 Self::check_roles(roles_event, &mut governance)?;
542 }
543
544 if let Some(policies_event) = event.policies {
545 Self::check_policies(policies_event, &mut governance)?;
546 }
547
548 if !governance.check_basic_gov() {
549 return Err(RunnerError::InvalidEvent {
550 location: "execute_fact_gov",
551 kind: error::InvalidEventKind::CannotModify {
552 what: "governance owner basic roles".to_owned(),
553 reason: "these roles are protected".to_owned(),
554 },
555 });
556 }
557
558 let mod_governance = governance.to_value_wrapper();
559
560 Ok((
561 RunnerResult {
562 final_state: mod_governance,
563 approval_required: true,
564 },
565 add_change_schemas,
566 ))
567 }
568
569 fn check_policies(
570 policies_event: PoliciesEvent,
571 governance: &mut GovernanceData,
572 ) -> Result<(), RunnerError> {
573 if policies_event_is_empty(&policies_event) {
574 return Err(RunnerError::InvalidEvent {
575 location: "check_policies",
576 kind: error::InvalidEventKind::Empty {
577 what: "PoliciesEvent".to_owned(),
578 },
579 });
580 }
581
582 if let Some(gov) = policies_event.governance {
583 if gov_policie_change_is_empty(&gov.change) {
584 return Err(RunnerError::InvalidEvent {
585 location: "check_policies",
586 kind: error::InvalidEventKind::Empty {
587 what: "GovPolicieEvent change".to_owned(),
588 },
589 });
590 }
591
592 let mut new_policies = governance.policies_gov.clone();
593
594 if let Some(approve) = gov.change.approve {
595 approve.check_values().map_err(|e| {
596 RunnerError::InvalidEvent {
597 location: "check_policies",
598 kind: error::InvalidEventKind::InvalidQuorum {
599 context: "governance approve policy".to_owned(),
600 details: e,
601 },
602 }
603 })?;
604 if approve == new_policies.approve {
605 return Err(RunnerError::InvalidEvent {
606 location: "check_policies",
607 kind: error::InvalidEventKind::SameValue {
608 what: "governance approve policy".to_owned(),
609 },
610 });
611 }
612 new_policies.approve = approve;
613 }
614
615 if let Some(evaluate) = gov.change.evaluate {
616 evaluate.check_values().map_err(|e| {
617 RunnerError::InvalidEvent {
618 location: "check_policies",
619 kind: error::InvalidEventKind::InvalidQuorum {
620 context: "governance evaluate policy".to_owned(),
621 details: e,
622 },
623 }
624 })?;
625 if evaluate == new_policies.evaluate {
626 return Err(RunnerError::InvalidEvent {
627 location: "check_policies",
628 kind: error::InvalidEventKind::SameValue {
629 what: "governance evaluate policy".to_owned(),
630 },
631 });
632 }
633 new_policies.evaluate = evaluate;
634 }
635
636 if let Some(validate) = gov.change.validate {
637 validate.check_values().map_err(|e| {
638 RunnerError::InvalidEvent {
639 location: "check_policies",
640 kind: error::InvalidEventKind::InvalidQuorum {
641 context: "governance validate policy".to_owned(),
642 details: e,
643 },
644 }
645 })?;
646 if validate == new_policies.validate {
647 return Err(RunnerError::InvalidEvent {
648 location: "check_policies",
649 kind: error::InvalidEventKind::SameValue {
650 what: "governance validate policy".to_owned(),
651 },
652 });
653 }
654 new_policies.validate = validate;
655 }
656
657 governance.policies_gov = new_policies;
658 }
659
660 if let Some(schemas) = policies_event.schema {
661 if schemas.is_empty() {
662 return Err(RunnerError::InvalidEvent {
663 location: "check_policies",
664 kind: error::InvalidEventKind::Empty {
665 what: "SchemaIdPolicie vec".to_owned(),
666 },
667 });
668 }
669
670 let mut new_policies = governance.policies_schema.clone();
671 let mut seen_schema_ids: HashSet<SchemaType> = HashSet::new();
672
673 for schema in schemas {
674 if !seen_schema_ids.insert(schema.schema_id.clone()) {
675 return Err(RunnerError::InvalidEvent {
676 location: "check_policies",
677 kind: error::InvalidEventKind::Duplicate {
678 what: "schema".to_owned(),
679 id: schema.schema_id.to_string(),
680 },
681 });
682 }
683
684 if schema_id_policie_is_empty(&schema) {
685 return Err(RunnerError::InvalidEvent {
686 location: "check_policies",
687 kind: error::InvalidEventKind::Empty {
688 what: "SchemaIdPolicie".to_owned(),
689 },
690 });
691 }
692
693 let Some(policies_schema) =
694 new_policies.get_mut(&schema.schema_id)
695 else {
696 return Err(RunnerError::InvalidEvent {
697 location: "check_policies",
698 kind: error::InvalidEventKind::NotSchema {
699 id: schema.schema_id.to_string(),
700 },
701 });
702 };
703
704 if schema_policie_change_is_empty(&schema.change) {
705 return Err(RunnerError::InvalidEvent {
706 location: "check_policies",
707 kind: error::InvalidEventKind::Empty {
708 what: "SchemaIdPolicie change".to_owned(),
709 },
710 });
711 }
712
713 if let Some(evaluate) = schema.change.evaluate {
714 evaluate.check_values().map_err(|e| {
715 RunnerError::InvalidEvent {
716 location: "check_policies",
717 kind: error::InvalidEventKind::InvalidQuorum {
718 context: format!(
719 "schema {} evaluate policy",
720 schema.schema_id
721 ),
722 details: e,
723 },
724 }
725 })?;
726 if evaluate == policies_schema.evaluate {
727 return Err(RunnerError::InvalidEvent {
728 location: "check_policies",
729 kind: error::InvalidEventKind::SameValue {
730 what: format!(
731 "schema {} evaluate policy",
732 schema.schema_id
733 ),
734 },
735 });
736 }
737 policies_schema.evaluate = evaluate;
738 }
739
740 if let Some(validate) = schema.change.validate {
741 validate.check_values().map_err(|e| {
742 RunnerError::InvalidEvent {
743 location: "check_policies",
744 kind: error::InvalidEventKind::InvalidQuorum {
745 context: format!(
746 "schema {} validate policy",
747 schema.schema_id
748 ),
749 details: e,
750 },
751 }
752 })?;
753 if validate == policies_schema.validate {
754 return Err(RunnerError::InvalidEvent {
755 location: "check_policies",
756 kind: error::InvalidEventKind::SameValue {
757 what: format!(
758 "schema {} validate policy",
759 schema.schema_id
760 ),
761 },
762 });
763 }
764 policies_schema.validate = validate;
765 }
766 }
767
768 governance.policies_schema = new_policies;
769 }
770
771 Ok(())
772 }
773
774 fn check_roles(
775 roles_event: RolesEvent,
776 governance: &mut GovernanceData,
777 ) -> Result<(), RunnerError> {
778 if roles_event_is_empty(&roles_event) {
779 return Err(RunnerError::InvalidEvent {
780 location: "check_roles",
781 kind: error::InvalidEventKind::Empty {
782 what: "RolesEvent".to_owned(),
783 },
784 });
785 }
786
787 if let Some(gov) = roles_event.governance {
788 if gov_role_event_is_empty(&gov) {
789 return Err(RunnerError::InvalidEvent {
790 location: "check_roles",
791 kind: error::InvalidEventKind::Empty {
792 what: "GovRoleEvent".to_owned(),
793 },
794 });
795 }
796
797 let mut new_roles = governance.roles_gov.clone();
798
799 gov_role_event_check_data(&gov, governance, &mut new_roles)?;
800
801 governance.roles_gov = new_roles;
802 }
803
804 if let Some(schemas) = roles_event.schema {
805 if schemas.is_empty() {
806 return Err(RunnerError::InvalidEvent {
807 location: "check_roles",
808 kind: error::InvalidEventKind::Empty {
809 what: "SchemaIdRole vec".to_owned(),
810 },
811 });
812 }
813
814 let mut new_roles = governance.roles_schema.clone();
815 let mut seen_schema_ids: HashSet<SchemaType> = HashSet::new();
816
817 for schema in schemas {
818 if !seen_schema_ids.insert(schema.schema_id.clone()) {
819 return Err(RunnerError::InvalidEvent {
820 location: "check_roles",
821 kind: error::InvalidEventKind::Duplicate {
822 what: "schema".to_owned(),
823 id: schema.schema_id.to_string(),
824 },
825 });
826 }
827
828 if schema_id_role_is_empty(&schema) {
829 return Err(RunnerError::InvalidEvent {
830 location: "check_roles",
831 kind: error::InvalidEventKind::Empty {
832 what: "SchemaIdRole".to_owned(),
833 },
834 });
835 }
836
837 let Some(roles_schema) = new_roles.get_mut(&schema.schema_id)
838 else {
839 return Err(RunnerError::InvalidEvent {
840 location: "check_roles",
841 kind: error::InvalidEventKind::NotSchema {
842 id: schema.schema_id.to_string(),
843 },
844 });
845 };
846
847 schema_id_role_check_data(
848 &schema,
849 governance,
850 roles_schema,
851 &schema.schema_id,
852 )?;
853 }
854
855 governance.roles_schema = new_roles;
856 }
857
858 if let Some(tracker_schemas) = roles_event.tracker_schemas {
859 if tracker_schemas_role_event_is_empty(&tracker_schemas) {
860 return Err(RunnerError::InvalidEvent {
861 location: "check_roles",
862 kind: error::InvalidEventKind::Empty {
863 what: "TrackerSchemasRoleEvent".to_owned(),
864 },
865 });
866 }
867
868 let new_roles = governance.roles_tracker_schemas.clone();
869
870 let new_roles = tracker_schemas_role_event_check_data(
871 &tracker_schemas,
872 governance,
873 new_roles,
874 &SchemaType::TrackerSchemas,
875 )?;
876
877 governance.roles_tracker_schemas = new_roles;
878 }
879
880 Ok(())
881 }
882
883 fn apply_schemas(
884 schema_event: &SchemasEvent,
885 governance: &mut GovernanceData,
886 ) -> Result<AddRemoveChangeSchema, RunnerError> {
887 fn validate_viewpoints(
888 schema_id: &SchemaType,
889 viewpoints: &[String],
890 field: &str,
891 ) -> Result<BTreeSet<String>, RunnerError> {
892 let mut unique = BTreeSet::new();
893
894 for viewpoint in viewpoints {
895 if viewpoint != viewpoint.trim() {
896 return Err(RunnerError::InvalidEvent {
897 location: "check_schemas",
898 kind: error::InvalidEventKind::InvalidValue {
899 field: format!("{field} for schema {}", schema_id),
900 reason:
901 "cannot have leading or trailing whitespace"
902 .to_owned(),
903 },
904 });
905 }
906
907 if viewpoint.is_empty() {
908 return Err(RunnerError::InvalidEvent {
909 location: "check_schemas",
910 kind: error::InvalidEventKind::Empty {
911 what: format!("{field} for schema {}", schema_id),
912 },
913 });
914 }
915
916 if viewpoint == &ReservedWords::AllViewpoints.to_string() {
917 return Err(RunnerError::InvalidEvent {
918 location: "check_schemas",
919 kind: error::InvalidEventKind::ReservedWord {
920 field: format!("{field} for schema {}", schema_id),
921 value: ReservedWords::AllViewpoints.to_string(),
922 },
923 });
924 }
925
926 if viewpoint.len() > 100 {
927 return Err(RunnerError::InvalidEvent {
928 location: "check_schemas",
929 kind: error::InvalidEventKind::InvalidSize {
930 field: format!("{field} for schema {}", schema_id),
931 actual: viewpoint.len(),
932 max: 100,
933 },
934 });
935 }
936
937 if !unique.insert(viewpoint.clone()) {
938 return Err(RunnerError::InvalidEvent {
939 location: "check_schemas",
940 kind: error::InvalidEventKind::Duplicate {
941 what: field.to_owned(),
942 id: viewpoint.clone(),
943 },
944 });
945 }
946 }
947
948 Ok(unique)
949 }
950
951 if schemas_event_is_empty(schema_event) {
952 return Err(RunnerError::InvalidEvent {
953 location: "check_schemas",
954 kind: error::InvalidEventKind::Empty {
955 what: "SchemasEvent".to_owned(),
956 },
957 });
958 }
959
960 if let (Some(add), Some(remove)) =
961 (&schema_event.add, &schema_event.remove)
962 {
963 let add_ids: HashSet<&SchemaType> =
964 add.iter().map(|s| &s.id).collect();
965 for r in remove {
966 if add_ids.contains(r) {
967 return Err(RunnerError::InvalidEvent {
968 location: "check_schemas",
969 kind: error::InvalidEventKind::CannotModify {
970 what: r.to_string(),
971 reason: "cannot add and remove the same schema in the same event"
972 .to_owned(),
973 },
974 });
975 }
976 }
977 }
978
979 if let (Some(add), Some(change)) =
980 (&schema_event.add, &schema_event.change)
981 {
982 let add_ids: HashSet<&SchemaType> =
983 add.iter().map(|s| &s.id).collect();
984 for c in change {
985 if add_ids.contains(&c.actual_id) {
986 return Err(RunnerError::InvalidEvent {
987 location: "check_schemas",
988 kind: error::InvalidEventKind::CannotModify {
989 what: c.actual_id.to_string(),
990 reason: "cannot add and change the same schema in the same event"
991 .to_owned(),
992 },
993 });
994 }
995 }
996 }
997
998 let mut remove_schemas = HashSet::new();
999 let mut add_schemas = HashSet::new();
1000 let mut change_schemas = HashSet::new();
1001
1002 let mut new_schemas = governance.schemas.clone();
1003
1004 if let Some(add) = schema_event.add.clone() {
1005 if add.is_empty() {
1006 return Err(RunnerError::InvalidEvent {
1007 location: "check_schemas",
1008 kind: error::InvalidEventKind::Empty {
1009 what: "SchemaAdd vec".to_owned(),
1010 },
1011 });
1012 }
1013
1014 for new_schema in add {
1015 if !new_schema.id.is_valid() {
1016 return Err(RunnerError::InvalidEvent {
1017 location: "check_schemas",
1018 kind: error::InvalidEventKind::Empty {
1019 what: "schema id".to_owned(),
1020 },
1021 });
1022 }
1023
1024 let id_str = new_schema.id.to_string();
1025 if id_str != id_str.trim() {
1026 return Err(RunnerError::InvalidEvent {
1027 location: "check_schemas",
1028 kind: error::InvalidEventKind::InvalidValue {
1029 field: "schema id".to_owned(),
1030 reason:
1031 "cannot have leading or trailing whitespace"
1032 .to_owned(),
1033 },
1034 });
1035 }
1036
1037 if new_schema.id.len() > 100 {
1038 return Err(RunnerError::InvalidEvent {
1039 location: "check_schemas",
1040 kind: error::InvalidEventKind::InvalidSize {
1041 field: "schema id".to_owned(),
1042 actual: new_schema.id.len(),
1043 max: 100,
1044 },
1045 });
1046 }
1047
1048 if new_schema.id == SchemaType::TrackerSchemas {
1049 return Err(RunnerError::InvalidEvent {
1050 location: "check_schemas",
1051 kind: error::InvalidEventKind::ReservedWord {
1052 field: "schema id".to_owned(),
1053 value: ReservedWords::TrackerSchemas.to_string(),
1054 },
1055 });
1056 }
1057
1058 if new_schema.id == SchemaType::Governance {
1059 return Err(RunnerError::InvalidEvent {
1060 location: "check_schemas",
1061 kind: error::InvalidEventKind::ReservedWord {
1062 field: "schema id".to_owned(),
1063 value: ReservedWords::Governance.to_string(),
1064 },
1065 });
1066 }
1067
1068 if new_schema.contract.is_empty() {
1069 return Err(RunnerError::InvalidEvent {
1070 location: "check_schemas",
1071 kind: error::InvalidEventKind::Empty {
1072 what: format!(
1073 "contract for schema {}",
1074 new_schema.id
1075 ),
1076 },
1077 });
1078 }
1079
1080 if new_schema.contract.contains(' ') {
1081 return Err(RunnerError::InvalidEvent {
1082 location: "check_schemas",
1083 kind: error::InvalidEventKind::InvalidValue {
1084 field: format!(
1085 "contract for schema {}",
1086 new_schema.id
1087 ),
1088 reason: "cannot contain blank spaces".to_owned(),
1089 },
1090 });
1091 }
1092
1093 if new_schemas
1094 .insert(
1095 new_schema.id.clone(),
1096 Schema {
1097 initial_value: ValueWrapper(
1098 new_schema.initial_value,
1099 ),
1100 contract: new_schema.contract,
1101 viewpoints: validate_viewpoints(
1102 &new_schema.id,
1103 &new_schema.viewpoints,
1104 "viewpoints",
1105 )?,
1106 },
1107 )
1108 .is_some()
1109 {
1110 return Err(RunnerError::InvalidEvent {
1111 location: "check_schemas",
1112 kind: error::InvalidEventKind::AlreadyExists {
1113 what: "schema".to_owned(),
1114 id: new_schema.id.to_string(),
1115 },
1116 });
1117 }
1118
1119 add_schemas.insert(new_schema.id);
1120 }
1121 }
1122
1123 if let Some(remove) = schema_event.remove.clone() {
1124 if remove.is_empty() {
1125 return Err(RunnerError::InvalidEvent {
1126 location: "check_schemas",
1127 kind: error::InvalidEventKind::Empty {
1128 what: "remove schema vec".to_owned(),
1129 },
1130 });
1131 }
1132
1133 for remove_schema in &remove {
1134 if !remove_schema.is_valid() {
1135 return Err(RunnerError::InvalidEvent {
1136 location: "check_schemas",
1137 kind: error::InvalidEventKind::Empty {
1138 what: "schema id to remove".to_owned(),
1139 },
1140 });
1141 }
1142
1143 if new_schemas.remove(remove_schema).is_none() {
1144 return Err(RunnerError::InvalidEvent {
1145 location: "check_schemas",
1146 kind: error::InvalidEventKind::CannotRemove {
1147 what: remove_schema.to_string(),
1148 reason: "not a schema".to_owned(),
1149 },
1150 });
1151 }
1152 }
1153
1154 remove_schemas = remove;
1155 }
1156
1157 if let Some(change) = schema_event.change.clone() {
1158 if change.is_empty() {
1159 return Err(RunnerError::InvalidEvent {
1160 location: "check_schemas",
1161 kind: error::InvalidEventKind::Empty {
1162 what: "SchemaChange vec".to_owned(),
1163 },
1164 });
1165 }
1166
1167 let mut seen_change_ids: HashSet<SchemaType> = HashSet::new();
1168
1169 for change_schema in change {
1170 if !seen_change_ids.insert(change_schema.actual_id.clone()) {
1171 return Err(RunnerError::InvalidEvent {
1172 location: "check_schemas",
1173 kind: error::InvalidEventKind::Duplicate {
1174 what: "schema in change".to_owned(),
1175 id: change_schema.actual_id.to_string(),
1176 },
1177 });
1178 }
1179
1180 if schema_change_is_empty(&change_schema) {
1181 return Err(RunnerError::InvalidEvent {
1182 location: "check_schemas",
1183 kind: error::InvalidEventKind::Empty {
1184 what: format!(
1185 "change data for schema {}",
1186 change_schema.actual_id
1187 ),
1188 },
1189 });
1190 }
1191
1192 let Some(schema_data) =
1193 new_schemas.get_mut(&change_schema.actual_id)
1194 else {
1195 return Err(RunnerError::InvalidEvent {
1196 location: "check_schemas",
1197 kind: error::InvalidEventKind::NotSchema {
1198 id: change_schema.actual_id.to_string(),
1199 },
1200 });
1201 };
1202
1203 if let Some(new_contract) = change_schema.new_contract {
1204 if new_contract.is_empty() {
1205 return Err(RunnerError::InvalidEvent {
1206 location: "check_schemas",
1207 kind: error::InvalidEventKind::Empty {
1208 what: format!(
1209 "new contract for schema {}",
1210 change_schema.actual_id
1211 ),
1212 },
1213 });
1214 }
1215
1216 if new_contract.contains(' ') {
1217 return Err(RunnerError::InvalidEvent {
1218 location: "check_schemas",
1219 kind: error::InvalidEventKind::InvalidValue {
1220 field: format!(
1221 "new contract for schema {}",
1222 change_schema.actual_id
1223 ),
1224 reason: "cannot contain blank spaces"
1225 .to_owned(),
1226 },
1227 });
1228 }
1229
1230 if new_contract == schema_data.contract {
1231 return Err(RunnerError::InvalidEvent {
1232 location: "check_schemas",
1233 kind: error::InvalidEventKind::SameValue {
1234 what: format!(
1235 "contract for schema {}",
1236 change_schema.actual_id
1237 ),
1238 },
1239 });
1240 }
1241
1242 schema_data.contract = new_contract;
1243 }
1244
1245 if let Some(init_value) = change_schema.new_initial_value {
1246 if init_value == schema_data.initial_value.0 {
1247 return Err(RunnerError::InvalidEvent {
1248 location: "check_schemas",
1249 kind: error::InvalidEventKind::SameValue {
1250 what: format!(
1251 "initial_value for schema {}",
1252 change_schema.actual_id
1253 ),
1254 },
1255 });
1256 }
1257
1258 schema_data.initial_value = ValueWrapper(init_value);
1259 }
1260
1261 if let Some(new_viewpoints) = change_schema.new_viewpoints {
1262 let new_viewpoints = validate_viewpoints(
1263 &change_schema.actual_id,
1264 &new_viewpoints,
1265 "new viewpoints",
1266 )?;
1267
1268 if new_viewpoints == schema_data.viewpoints {
1269 return Err(RunnerError::InvalidEvent {
1270 location: "check_schemas",
1271 kind: error::InvalidEventKind::SameValue {
1272 what: format!(
1273 "viewpoints for schema {}",
1274 change_schema.actual_id
1275 ),
1276 },
1277 });
1278 }
1279
1280 schema_data.viewpoints = new_viewpoints;
1281 Self::adapt_creator_witnesses_to_schema_viewpoints(
1282 governance,
1283 &change_schema.actual_id,
1284 &schema_data.viewpoints,
1285 );
1286 }
1287
1288 change_schemas.insert(change_schema.actual_id);
1289 }
1290 }
1291
1292 governance.schemas = new_schemas;
1293 Ok((add_schemas, remove_schemas, change_schemas))
1294 }
1295
1296 fn check_members(
1297 member_event: &MemberEvent,
1298 governance: &mut GovernanceData,
1299 ) -> Result<Vec<String>, RunnerError> {
1300 if member_event_is_empty(member_event) {
1301 return Err(RunnerError::InvalidEvent {
1302 location: "check_members",
1303 kind: error::InvalidEventKind::Empty {
1304 what: "MemberEvent".to_owned(),
1305 },
1306 });
1307 }
1308
1309 if let (Some(add), Some(remove)) =
1310 (&member_event.add, &member_event.remove)
1311 {
1312 let add_names: HashSet<String> =
1313 add.iter().map(|m| m.name.trim().to_owned()).collect();
1314 for r in remove {
1315 if add_names.contains(r.as_str()) {
1316 return Err(RunnerError::InvalidEvent {
1317 location: "check_members",
1318 kind: error::InvalidEventKind::CannotModify {
1319 what: r.clone(),
1320 reason: "cannot add and remove the same member in the same event".to_owned(),
1321 },
1322 });
1323 }
1324 }
1325 }
1326
1327 let mut new_members = governance.members.clone();
1328
1329 if let Some(add) = member_event.add.clone() {
1330 if add.is_empty() {
1331 return Err(RunnerError::InvalidEvent {
1332 location: "check_members",
1333 kind: error::InvalidEventKind::Empty {
1334 what: "NewMember vec".to_owned(),
1335 },
1336 });
1337 }
1338
1339 for new_member in add {
1340 if new_member.name != new_member.name.trim() {
1341 return Err(RunnerError::InvalidEvent {
1342 location: "check_members",
1343 kind: error::InvalidEventKind::InvalidValue {
1344 field: "member name".to_owned(),
1345 reason:
1346 "cannot have leading or trailing whitespace"
1347 .to_owned(),
1348 },
1349 });
1350 }
1351
1352 if new_member.name.is_empty() {
1353 return Err(RunnerError::InvalidEvent {
1354 location: "check_members",
1355 kind: error::InvalidEventKind::Empty {
1356 what: "member name".to_owned(),
1357 },
1358 });
1359 }
1360
1361 if new_member.name.len() > 100 {
1362 return Err(RunnerError::InvalidEvent {
1363 location: "check_members",
1364 kind: error::InvalidEventKind::InvalidSize {
1365 field: "member name".to_owned(),
1366 actual: new_member.name.len(),
1367 max: 100,
1368 },
1369 });
1370 }
1371
1372 if new_member.name == ReservedWords::Any.to_string() {
1373 return Err(RunnerError::InvalidEvent {
1374 location: "check_members",
1375 kind: error::InvalidEventKind::ReservedWord {
1376 field: "member name".to_owned(),
1377 value: ReservedWords::Any.to_string(),
1378 },
1379 });
1380 }
1381
1382 if new_member.name == ReservedWords::Witnesses.to_string() {
1383 return Err(RunnerError::InvalidEvent {
1384 location: "check_members",
1385 kind: error::InvalidEventKind::ReservedWord {
1386 field: "member name".to_owned(),
1387 value: ReservedWords::Witnesses.to_string(),
1388 },
1389 });
1390 }
1391
1392 if new_member.name == ReservedWords::Owner.to_string() {
1393 return Err(RunnerError::InvalidEvent {
1394 location: "check_members",
1395 kind: error::InvalidEventKind::ReservedWord {
1396 field: "member name".to_owned(),
1397 value: ReservedWords::Owner.to_string(),
1398 },
1399 });
1400 }
1401
1402 if new_member.key.is_empty() {
1403 return Err(RunnerError::InvalidEvent {
1404 location: "check_members",
1405 kind: error::InvalidEventKind::Empty {
1406 what: format!("key for member {}", new_member.name),
1407 },
1408 });
1409 }
1410
1411 if new_members
1412 .insert(new_member.name.clone(), new_member.key)
1413 .is_some()
1414 {
1415 return Err(RunnerError::InvalidEvent {
1416 location: "check_members",
1417 kind: error::InvalidEventKind::AlreadyExists {
1418 what: "member".to_owned(),
1419 id: new_member.name,
1420 },
1421 });
1422 }
1423 }
1424 }
1425
1426 let remove_members = if let Some(remove) = member_event.remove.clone() {
1427 if remove.is_empty() {
1428 return Err(RunnerError::InvalidEvent {
1429 location: "check_members",
1430 kind: error::InvalidEventKind::Empty {
1431 what: "remove member vec".to_owned(),
1432 },
1433 });
1434 }
1435
1436 for remove_member in remove.clone() {
1437 if remove_member != remove_member.trim() {
1438 return Err(RunnerError::InvalidEvent {
1439 location: "check_members",
1440 kind: error::InvalidEventKind::InvalidValue {
1441 field: "member name to remove".to_owned(),
1442 reason:
1443 "cannot have leading or trailing whitespace"
1444 .to_owned(),
1445 },
1446 });
1447 }
1448
1449 if remove_member.is_empty() {
1450 return Err(RunnerError::InvalidEvent {
1451 location: "check_members",
1452 kind: error::InvalidEventKind::Empty {
1453 what: "member name to remove".to_owned(),
1454 },
1455 });
1456 }
1457
1458 if remove_member == ReservedWords::Owner.to_string() {
1459 return Err(RunnerError::InvalidEvent {
1460 location: "check_members",
1461 kind: error::InvalidEventKind::CannotRemove {
1462 what: ReservedWords::Owner.to_string(),
1463 reason: "governance owner cannot be removed"
1464 .to_owned(),
1465 },
1466 });
1467 }
1468
1469 if new_members.remove(&remove_member).is_none() {
1470 return Err(RunnerError::InvalidEvent {
1471 location: "check_members",
1472 kind: error::InvalidEventKind::CannotRemove {
1473 what: remove_member,
1474 reason: "not a member".to_owned(),
1475 },
1476 });
1477 }
1478 }
1479
1480 remove.iter().cloned().collect::<Vec<String>>()
1481 } else {
1482 vec![]
1483 };
1484
1485 let members_name: HashSet<String> =
1486 new_members.keys().cloned().collect();
1487 let members_value: HashSet<PublicKey> =
1488 new_members.values().cloned().collect();
1489
1490 if new_members.contains_key(&ReservedWords::Any.to_string()) {
1491 return Err(RunnerError::InvalidEvent {
1492 location: "check_members",
1493 kind: error::InvalidEventKind::ReservedWord {
1494 field: "member name".to_owned(),
1495 value: ReservedWords::Any.to_string(),
1496 },
1497 });
1498 }
1499
1500 if new_members.contains_key(&ReservedWords::Witnesses.to_string()) {
1501 return Err(RunnerError::InvalidEvent {
1502 location: "check_members",
1503 kind: error::InvalidEventKind::ReservedWord {
1504 field: "member name".to_owned(),
1505 value: ReservedWords::Witnesses.to_string(),
1506 },
1507 });
1508 }
1509
1510 if members_name.len() != members_value.len() {
1511 return Err(RunnerError::InvalidEvent {
1512 location: "check_members",
1513 kind: error::InvalidEventKind::Duplicate {
1514 what: "member key".to_owned(),
1515 id: "multiple members share the same key".to_owned(),
1516 },
1517 });
1518 }
1519
1520 governance.members = new_members;
1521
1522 Ok(remove_members)
1523 }
1524
1525 fn generate_context(
1526 state: &ValueWrapper,
1527 init_state: &ValueWrapper,
1528 event: &ValueWrapper,
1529 limits: &WasmLimits,
1530 ) -> Result<(MemoryManager, u32, u32, u32), RunnerError> {
1531 let mut context = MemoryManager::from_limits(limits);
1532
1533 let state_bytes =
1534 to_vec(&state).map_err(|e| RunnerError::SerializationError {
1535 context: "serialize state",
1536 details: e.to_string(),
1537 })?;
1538 let state_ptr = context.add_data_raw(&state_bytes)?;
1539
1540 let init_state_bytes = to_vec(&init_state).map_err(|e| {
1541 RunnerError::SerializationError {
1542 context: "serialize init_state",
1543 details: e.to_string(),
1544 }
1545 })?;
1546 let init_state_ptr = context.add_data_raw(&init_state_bytes)?;
1547
1548 let event_bytes =
1549 to_vec(&event).map_err(|e| RunnerError::SerializationError {
1550 context: "serialize event",
1551 details: e.to_string(),
1552 })?;
1553 let event_ptr = context.add_data_raw(&event_bytes)?;
1554
1555 Ok((
1556 context,
1557 state_ptr as u32,
1558 init_state_ptr as u32,
1559 event_ptr as u32,
1560 ))
1561 }
1562
1563 fn get_result(
1564 store: &Store<MemoryManager>,
1565 pointer: u32,
1566 ) -> Result<ContractResult, RunnerError> {
1567 let bytes = store.data().read_data(pointer as usize)?;
1568
1569 let contract_result: ContractResult =
1570 BorshDeserialize::try_from_slice(bytes).map_err(|e| {
1571 RunnerError::SerializationError {
1572 context: "deserialize ContractResult",
1573 details: e.to_string(),
1574 }
1575 })?;
1576
1577 if contract_result.success {
1578 Ok(contract_result)
1579 } else {
1580 Err(RunnerError::ContractFailed {
1581 details: format!(
1582 "Contract execution in running was not successful: {}",
1583 contract_result.error
1584 ),
1585 })
1586 }
1587 }
1588}
1589
1590#[derive(Debug, Clone)]
1591pub struct RunnerMessage {
1592 pub data: EvaluateInfo,
1593 pub is_owner: bool,
1594}
1595
1596impl Message for RunnerMessage {}
1597
1598#[derive(Debug, Clone)]
1599pub enum RunnerResponse {
1600 Ok {
1601 result: RunnerResult,
1602 compilations: Vec<SchemaType>,
1603 },
1604 Error(RunnerError),
1605}
1606
1607impl Response for RunnerResponse {}
1608
1609#[async_trait]
1610impl Actor for Runner {
1611 type Event = ();
1612 type Message = RunnerMessage;
1613 type Response = RunnerResponse;
1614
1615 fn get_span(_id: &str, parent_span: Option<Span>) -> tracing::Span {
1616 parent_span.map_or_else(
1617 || info_span!("Runner"),
1618 |parent_span| info_span!(parent: parent_span, "Runner"),
1619 )
1620 }
1621}
1622
1623impl NotPersistentActor for Runner {}
1624
1625#[async_trait]
1626impl Handler<Self> for Runner {
1627 async fn handle_message(
1628 &mut self,
1629 _sender: ActorPath,
1630 msg: RunnerMessage,
1631 ctx: &mut ActorContext<Self>,
1632 ) -> Result<RunnerResponse, ActorError> {
1633 let started_at = Instant::now();
1634 match Self::execute_contract(ctx, &msg.data, msg.is_owner).await {
1635 Ok((result, compilations)) => {
1636 if let Some(metrics) = try_core_metrics() {
1637 metrics.observe_contract_execution(
1638 "success",
1639 started_at.elapsed(),
1640 );
1641 }
1642 debug!(
1643 msg_type = "Execute",
1644 approval_required = result.approval_required,
1645 compilations_count = compilations.len(),
1646 is_owner = msg.is_owner,
1647 "Contract executed successfully"
1648 );
1649 Ok(RunnerResponse::Ok {
1650 result,
1651 compilations,
1652 })
1653 }
1654 Err(e) => {
1655 if let Some(metrics) = try_core_metrics() {
1656 metrics.observe_contract_execution(
1657 "error",
1658 started_at.elapsed(),
1659 );
1660 }
1661 error!(
1662 msg_type = "Execute",
1663 error = %e,
1664 is_owner = msg.is_owner,
1665 "Contract execution failed"
1666 );
1667 Ok(RunnerResponse::Error(e))
1668 }
1669 }
1670 }
1671}