forked from Vero-protocol/vero-core-engine
-
Notifications
You must be signed in to change notification settings - Fork 0
Expand file tree
/
Copy pathcontrol_plane.rs
More file actions
296 lines (256 loc) · 10 KB
/
Copy pathcontrol_plane.rs
File metadata and controls
296 lines (256 loc) · 10 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
//! Vero Protocol Control Plane Foundation
//!
//! This module exposes the hardened administrative surface for `engine-core`.
//! Every state-changing control-plane path is authenticated, circuit-breaker
//! aware, reentrancy guarded, and anchored through the ZK-ready audit commitment
//! chain before mutating protocol configuration.
use soroban_sdk::{
contract, contracterror, contractimpl, panic_with_error, symbol_short, Address, Bytes, BytesN,
Env, Map, Symbol,
};
use crate::audit;
use crate::circuit_breaker;
use crate::event_struct::{ACT_EXECUTE, ACT_PROPOSE, MOD_GOV};
use crate::event_utils::publish_event;
use crate::types::{Proposal, StateCommitment};
const KEY_ADMIN: Symbol = symbol_short!("ADMIN");
const KEY_INIT: Symbol = symbol_short!("CP_INIT");
const KEY_PARAM_COUNT: Symbol = symbol_short!("P_COUNT");
const KEY_LAST_PARAM: Symbol = symbol_short!("LASTPAR");
/// Reserved keys that may not be modified through `update_param` to prevent
/// accidental corruption of internal engine state.
const RESERVED_KEYS: &[Symbol] = &[
symbol_short!("ADMIN"),
symbol_short!("SEQ"),
symbol_short!("PREV_H"),
symbol_short!("CB_STATE"),
symbol_short!("CB_GUARD"),
symbol_short!("PROPS"),
symbol_short!("SIGNERS"),
symbol_short!("THRESH"),
symbol_short!("MINSTAKE"),
symbol_short!("STKTOK"),
symbol_short!("ER_ADMINS"),
symbol_short!("ER_THRESH"),
symbol_short!("ER_APPRVS"),
symbol_short!("ER_DEST"),
symbol_short!("ER_TOKEN"),
symbol_short!("ER_AMOUNT"),
symbol_short!("FEE_BPS"),
symbol_short!("FEE_RCP"),
symbol_short!("SNAPC"),
symbol_short!("SNAPL"),
symbol_short!("OUTFLOWS"),
];
#[contracterror]
#[derive(Copy, Clone, Debug, Eq, PartialEq, PartialOrd, Ord)]
#[repr(u32)]
pub enum ControlPlaneError {
AlreadyInitialized = 1,
Unauthorized = 2,
NotInitialized = 3,
InvalidPayload = 4,
ArithmeticOverflow = 5,
ReservedKey = 6,
InvalidAdmin = 7,
}
#[contract]
pub struct ControlPlane;
#[contractimpl]
impl ControlPlane {
/// Initialize the control plane with a master admin.
///
/// The admin must authorize initialization, making deployment races
/// auditable and preventing an arbitrary account from installing itself as
/// administrator without a matching signature.
pub fn initialize(env: Env, admin: Address) {
crate::non_reentrant!(&env);
if env.storage().instance().has(&KEY_INIT) {
panic_with_error!(&env, ControlPlaneError::AlreadyInitialized);
}
if admin == env.current_contract_address() {
panic_with_error!(&env, ControlPlaneError::InvalidAdmin);
}
admin.require_auth();
env.storage().instance().set(&KEY_ADMIN, &admin);
env.storage().instance().set(&KEY_INIT, &true);
env.storage().instance().set(&KEY_PARAM_COUNT, &0u64);
crate::version::init_version(&env);
}
/// Return the deployed contract logic version as (major, minor, patch).
pub fn version(_env: Env) -> (u32, u32, u32) {
crate::version::version()
}
/// Return the on-chain storage schema version.
pub fn contract_version(_env: Env) -> u32 {
crate::version::contract_version()
}
/// Return the configured administrator, or `None` before initialization.
pub fn admin(env: Env) -> Option<Address> {
env.storage().instance().get(&KEY_ADMIN)
}
/// Return a stored protocol parameter value.
pub fn get_param(env: Env, param_key: Symbol) -> Option<u64> {
env.storage().instance().get(¶m_key)
}
/// Return the number of successful parameter updates.
pub fn param_update_count(env: Env) -> u64 {
env.storage().instance().get(&KEY_PARAM_COUNT).unwrap_or(0)
}
/// Return the latest accepted audit commitment sequence.
pub fn last_audit_sequence(env: Env) -> u64 {
audit::get_last_sequence(&env)
}
/// Return the latest accepted state commitment hash.
pub fn state_hash(env: Env) -> BytesN<32> {
audit::get_state_hash(&env)
}
/// Pure preflight integrity check for clients and tests.
pub fn integrity_check(env: Env, commitment: StateCommitment, payload: BytesN<32>) -> bool {
audit::integrity_check(&env, &commitment, &payload.to_array())
}
/// Return the configured admin, or panic if not initialized.
pub fn get_admin(env: Env) -> Address {
env.storage()
.instance()
.get(&KEY_ADMIN)
.unwrap_or_else(|| panic_with_error!(&env, ControlPlaneError::NotInitialized))
}
/// Mutate a protocol parameter securely.
///
/// Security properties:
/// - caller must be the initialized admin and must authorize the invocation;
/// - circuit breaker must be closed;
/// - transition must pass the audit module's chained commitment check;
/// - update counter uses checked arithmetic;
/// - reentrancy guard wraps the full mutation.
pub fn update_param(
env: Env,
caller: Address,
param_key: Symbol,
param_val: u64,
commitment: StateCommitment,
payload: BytesN<32>,
) {
crate::non_reentrant!(&env);
require_admin(&env, &caller);
circuit_breaker::assert_closed(&env);
if is_reserved_key(¶m_key) {
panic_with_error!(&env, ControlPlaneError::ReservedKey);
}
let payload_raw = payload.to_array();
audit::validate_transition_inner(&env, &commitment, &payload_raw);
env.storage().instance().set(¶m_key, ¶m_val);
env.storage().instance().set(&KEY_LAST_PARAM, ¶m_key);
increment_param_count(&env);
}
/// Initialize the shared circuit-breaker guardian set through the
/// control-plane contract surface.
pub fn init_breaker(env: Env, caller: Address, guardians: soroban_sdk::Vec<Address>) {
crate::non_reentrant!(&env);
require_admin(&env, &caller);
circuit_breaker::init(&env, guardians);
}
/// Trip the circuit breaker. Guardian authorization is enforced by the
/// circuit-breaker module itself.
pub fn trip_breaker(env: Env, guardian: Address) {
circuit_breaker::trip(&env, &guardian);
}
/// Reset the circuit breaker. Guardian authorization is enforced by the
/// circuit-breaker module itself.
pub fn reset_breaker(env: Env, guardian: Address) {
circuit_breaker::reset(&env, &guardian);
}
/// Store a governance proposal via the shared governance module.
pub fn propose(env: Env, proposal: Proposal) -> u64 {
crate::governance::propose(&env, proposal)
}
/// Approve a governance proposal via the shared governance module.
pub fn approve(env: Env, signer: Address, proposal_id: u64) {
crate::governance::approve(&env, &signer, proposal_id);
}
/// Execute a governance proposal via the shared governance module.
pub fn execute(env: Env, proposal_id: u64) -> Proposal {
crate::governance::execute(&env, proposal_id)
}
/// Register an off-chain ZK proof attestation for a committed state root.
///
/// This stable hook is admin-gated and only accepts attestations for the
/// current committed state hash, preventing fabricated proofs for unrelated
/// roots from being anchored under the control-plane ABI.
pub fn register_proof(
env: Env,
caller: Address,
state_root: BytesN<32>,
proof_hash: BytesN<32>,
block_seq: u32,
metadata: Map<Symbol, Bytes>,
) {
crate::non_reentrant!(&env);
crate::core::zk_hooks::register_proof(
&env,
&caller,
state_root,
proof_hash,
block_seq,
metadata,
);
}
/// Retrieve a proof attestation by state root.
pub fn get_proof(env: Env, state_root: BytesN<32>) -> Option<BytesN<32>> {
crate::core::zk_hooks::get_proof(&env, state_root)
}
/// Mutate multiple protocol parameters securely in a single batch call.
///
/// Requires administrative authorization, asserts the circuit breaker is closed,
/// and invokes the ZK-ready `validate_transition` hook to ensure state integrity.
pub fn batch_update_param(
env: Env,
caller: Address,
params: soroban_sdk::Vec<(Symbol, u64)>,
commitment: StateCommitment,
payload: BytesN<32>,
) {
crate::non_reentrant!(&env);
require_admin(&env, &caller);
circuit_breaker::assert_closed(&env);
// ZK-ready integrity check (enforces no replays and valid hash)
audit::validate_transition_inner(&env, &commitment, &payload.to_array());
for param in params.iter() {
if is_reserved_key(¶m.0) {
panic_with_error!(&env, ControlPlaneError::ReservedKey);
}
}
for param in params.iter() {
env.storage().instance().set(¶m.0, ¶m.1);
}
increment_param_count(&env);
}
}
fn require_admin(env: &Env, caller: &Address) {
caller.require_auth();
let admin: Address = env
.storage()
.instance()
.get(&KEY_ADMIN)
.unwrap_or_else(|| panic_with_error!(env, ControlPlaneError::NotInitialized));
if caller != &admin {
panic_with_error!(env, ControlPlaneError::Unauthorized);
}
}
fn increment_param_count(env: &Env) {
let count: u64 = env.storage().instance().get(&KEY_PARAM_COUNT).unwrap_or(0);
let next = count
.checked_add(1)
.unwrap_or_else(|| panic_with_error!(env, ControlPlaneError::ArithmeticOverflow));
env.storage().instance().set(&KEY_PARAM_COUNT, &next);
}
/// Emit a compact governance-control event for future control-plane extensions.
#[allow(dead_code)]
fn publish_control_governance_event(env: &Env, proposal_id: u64, executed: bool, hash: BytesN<32>) {
let action = if executed { ACT_EXECUTE } else { ACT_PROPOSE };
publish_event(env, MOD_GOV | action, proposal_id, hash);
}
fn is_reserved_key(key: &Symbol) -> bool {
RESERVED_KEYS.iter().any(|reserved| reserved == key)
}