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
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
511
512
513
514
515
516
517
518
519
520
521
522
523
524
525
526
527
528
#![cfg_attr(not(feature = "std"), no_std)]
#![cfg_attr(not(feature = "std"), feature(alloc_error_handler))]
#![doc = include_str!("../README.md")]

#[macro_use]
extern crate alloc;

use alloc::string::String;
use alloc::vec::Vec;

use ink::env::{emit_event, topics::state::HasRemainingTopics, Environment, Topics};

use ink::EnvAccess;
use scale::{Decode, Encode};

pub use pink_macro::{contract, driver};
pub use pink_types as types;

pub mod chain_extension;
pub use chain_extension::pink_extension_instance as ext;
pub mod logger;
pub mod system;

#[cfg(all(not(feature = "std"), feature = "dlmalloc"))]
mod allocator_dlmalloc;

pub use logger::ResultExt;
use serde::{Deserialize, Serialize};

const PINK_EVENT_TOPIC: &[u8] = b"phala.pink.event";

pub type WorkerId = [u8; 32];
pub type EcdhPublicKey = [u8; 32];
pub type Hash = [u8; 32];
pub type EcdsaPublicKey = [u8; 33];
pub type EcdsaSignature = [u8; 65];
pub type AccountId = <PinkEnvironment as Environment>::AccountId;
pub type Balance = <PinkEnvironment as Environment>::Balance;
pub type BlockNumber = <PinkEnvironment as Environment>::BlockNumber;

pub trait ConvertTo<To> {
    fn convert_to(&self) -> To;
}

impl<F, T> ConvertTo<T> for F
where
    F: AsRef<[u8; 32]>,
    T: From<[u8; 32]>,
{
    fn convert_to(&self) -> T {
        (*self.as_ref()).into()
    }
}

/// Hook points defined in the runtime.
#[derive(Encode, Decode, Debug, Clone)]
#[cfg_attr(feature = "std", derive(scale_info::TypeInfo))]
pub enum HookPoint {
    /// When all events in a block are processed.
    OnBlockEnd,
}

/// System Event used to communicate between the contract and the runtime.
#[derive(Encode, Decode, Debug, Clone)]
#[cfg_attr(feature = "std", derive(scale_info::TypeInfo))]
pub enum PinkEvent {
    /// Set contract hook
    ///
    /// Please do not use this event directly, use [`set_hook()`] instead.
    ///
    /// # Availability
    /// System contract
    #[codec(index = 2)]
    SetHook {
        /// The event to hook
        hook: HookPoint,
        /// The target contract address
        contract: AccountId,
        /// The selector to invoke on hooked event fired.
        selector: u32,
        /// The gas limit when calling the selector
        gas_limit: u64,
    },
    /// Deploy a sidevm instance to given contract instance
    ///
    /// Please do not use this event directly, use [`deploy_sidevm_to()`] instead.
    ///
    /// # Availability
    /// System contract
    #[codec(index = 3)]
    DeploySidevmTo {
        /// The target contract address
        contract: AccountId,
        /// The hash of the sidevm code.
        code_hash: Hash,
    },
    /// Push a message to the associated sidevm instance.
    ///
    /// Please do not use this event directly, use [`push_sidevm_message()`] instead.
    ///
    /// # Availability
    /// Any contract
    #[codec(index = 4)]
    SidevmMessage(Vec<u8>),
    /// Instructions to manipulate the cache. Including set, remove and set expiration.
    ///
    /// # Availability
    /// Any contract
    #[codec(index = 5)]
    CacheOp(CacheOp),
    /// Stop the side VM instance associated with the caller contract if it is running.
    ///
    /// Please do not use this event directly, use [`force_stop_sidevm()`] instead.
    ///
    /// # Availability
    /// Any contract
    #[codec(index = 6)]
    StopSidevm,
    /// Force stop the side VM instance associated with the given contract if it is running.
    ///
    /// Please do not use this event directly, use [`stop_sidevm_at()`] instead.
    ///
    /// # Availability
    /// System contract
    #[codec(index = 7)]
    ForceStopSidevm {
        /// The target contract address
        contract: AccountId,
    },
    /// Set the log handler contract for current cluster.
    ///
    /// Please do not use this event directly, use [`set_log_handler()`] instead.
    ///
    /// # Availability
    /// System contract
    #[codec(index = 8)]
    SetLogHandler(AccountId),
    /// Set the weight of contract used to schedule queries and sidevm virtual runtime
    ///
    /// Please do not use this event directly, use [`set_contract_weight()`] instead.
    ///
    /// # Availability
    /// System contract
    #[codec(index = 9)]
    SetContractWeight { contract: AccountId, weight: u32 },
    /// Upgrade the runtime to given version
    ///
    /// Please do not use this event directly, use [`upgrade_runtime()`] instead.
    ///
    /// # Availability
    /// System contract
    #[codec(index = 10)]
    UpgradeRuntimeTo { version: (u32, u32) },
    /// Deploy a sidevm instance to given contract instance on given workers.
    ///
    /// # Availability
    /// System contract
    #[codec(index = 11)]
    SidevmOperation(SidevmOperation),
    /// Set the Sidevm program that used to evaluate js code.
    ///
    /// Please do not use this event directly, use [`set_js_runtime()`] instead.
    ///
    /// # Availability
    /// System contract
    #[codec(index = 12)]
    SetJsRuntime(Hash),
}

#[derive(Encode, Decode, Debug, Clone)]
#[cfg_attr(feature = "std", derive(scale_info::TypeInfo))]
pub enum SidevmOperation {
    Start {
        /// The target contract address
        contract: AccountId,
        /// The hash of the sidevm code.
        code_hash: Hash,
        /// The workers to deploy the sidevm instance.
        workers: Workers,
        config: SidevmConfig,
    },
    SetDeadline {
        /// The target contract address
        contract: AccountId,
        /// The block number that the SideVM instance is allowed to run until.
        deadline: u32,
    },
}

#[derive(Encode, Decode, Serialize, Deserialize, Debug, Clone)]
#[cfg_attr(feature = "std", derive(scale_info::TypeInfo))]
pub struct SidevmConfig {
    /// The maximum size of the code that can be uploaded to the SideVM instance.
    pub max_code_size: u32,
    /// The maximum number of memory pages(64KB per page) that can be allocated by the SideVM instance.
    pub max_memory_pages: u32,
    /// The max gas between two host function calls for the SideVM instance.
    pub vital_capacity: u64,
    /// The block number that the SideVM instance is allowed to run until.
    pub deadline: u32,
}

impl Default for SidevmConfig {
    fn default() -> Self {
        Self {
            max_code_size: 1024 * 1024 * 10,
            // 64MB
            max_memory_pages: 1024,
            // about 20 ms
            vital_capacity: 50_000_000_000_u64,
            deadline: u32::MAX,
        }
    }
}

#[derive(Encode, Decode, Debug, Clone)]
#[cfg_attr(feature = "std", derive(scale_info::TypeInfo))]
pub enum Workers {
    All,
    List(Vec<WorkerId>),
}

impl PinkEvent {
    pub fn allowed_in_query(&self) -> bool {
        match self {
            PinkEvent::SetHook { .. } => false,
            PinkEvent::DeploySidevmTo { .. } => true,
            PinkEvent::SidevmMessage(_) => true,
            PinkEvent::CacheOp(_) => true,
            PinkEvent::StopSidevm => true,
            PinkEvent::ForceStopSidevm { .. } => true,
            PinkEvent::SetLogHandler(_) => false,
            PinkEvent::SetContractWeight { .. } => false,
            PinkEvent::UpgradeRuntimeTo { .. } => false,
            PinkEvent::SidevmOperation(_) => true,
            PinkEvent::SetJsRuntime(_) => false,
        }
    }

    pub fn name(&self) -> &'static str {
        match self {
            PinkEvent::SetHook { .. } => "SetHook",
            PinkEvent::DeploySidevmTo { .. } => "DeploySidevmTo",
            PinkEvent::SidevmMessage(_) => "SidevmMessage",
            PinkEvent::CacheOp(_) => "CacheOp",
            PinkEvent::StopSidevm => "StopSidevm",
            PinkEvent::ForceStopSidevm { .. } => "ForceStopSidevm",
            PinkEvent::SetLogHandler(_) => "SetLogHandler",
            PinkEvent::SetContractWeight { .. } => "SetContractWeight",
            PinkEvent::UpgradeRuntimeTo { .. } => "UpgradeRuntimeTo",
            PinkEvent::SidevmOperation(_) => "SidevmOperation",
            PinkEvent::SetJsRuntime(_) => "SetJsRuntime",
        }
    }

    pub fn is_private(&self) -> bool {
        match self {
            PinkEvent::SetHook { .. } => false,
            PinkEvent::DeploySidevmTo { .. } => false,
            PinkEvent::SidevmMessage(_) => true,
            PinkEvent::CacheOp(_) => true,
            PinkEvent::StopSidevm => false,
            PinkEvent::ForceStopSidevm { .. } => false,
            PinkEvent::SetLogHandler(_) => false,
            PinkEvent::SetContractWeight { .. } => false,
            PinkEvent::UpgradeRuntimeTo { .. } => false,
            PinkEvent::SidevmOperation(_) => false,
            PinkEvent::SetJsRuntime(_) => false,
        }
    }
}

/// Instructions to manipulate the cache.
#[derive(Encode, Decode, Debug, Clone)]
#[cfg_attr(feature = "std", derive(scale_info::TypeInfo))]
pub enum CacheOp {
    /// Set a key-value pair in the cache.
    Set { key: Vec<u8>, value: Vec<u8> },
    /// Set the expiration of a key-value pair in the cache.
    SetExpiration { key: Vec<u8>, expiration: u64 },
    /// Remove a key-value pair from the cache.
    Remove { key: Vec<u8> },
}

impl Topics for PinkEvent {
    type RemainingTopics = [HasRemainingTopics; 1];

    fn topics<E, B>(
        &self,
        builder: ink::env::topics::TopicsBuilder<ink::env::topics::state::Uninit, E, B>,
    ) -> <B as ink::env::topics::TopicsBuilderBackend<E>>::Output
    where
        E: Environment,
        B: ink::env::topics::TopicsBuilderBackend<E>,
    {
        builder
            .build::<Self>()
            .push_topic(&PINK_EVENT_TOPIC)
            .finish()
    }
}

#[cfg(feature = "runtime_utils")]
impl PinkEvent {
    pub fn event_topic() -> Hash {
        use std::convert::TryFrom;
        let topics = topic::topics_for(Self::StopSidevm);
        let topic: &[u8] = topics[0].as_ref();
        Hash::try_from(topic).expect("Should not failed")
    }
}

/// Sets a hook receiver for a given hook point.
///
/// A hook is a mechanism that allows certain actions to be triggered when a specific situation arises.
/// When the situation corresponding to the hook point occurs, the runtime will call the receiver contract
/// using the specified selector.
///
/// # Supported Hook Points
///  - `OnBlockEnd`: The receiver contract will be invoked once all events in a Phala chain block have been processed.
///
/// # Arguments
///
/// * `hook`: The hook point for which the receiver is set.
/// * `contract`: The AccountId of the contract to be called when the hook is triggered.
/// * `selector`: The function selector to be used when calling the receiver contract.
/// * `gas_limit`: The maximum amount of gas that can be used when calling the receiver contract.
///
/// Note: The cost of the execution would be charged to the contract itself.
///
/// This api is only available for the system contract. User contracts should use `System::set_hook` instead.
pub fn set_hook(hook: HookPoint, contract: AccountId, selector: u32, gas_limit: u64) {
    emit_event::<PinkEnvironment, _>(PinkEvent::SetHook {
        hook,
        contract,
        selector,
        gas_limit,
    })
}

/// Starts a SideVM instance with the provided code hash.
///
/// The calling contract must be authorized by the `SidevmOperation` driver contract.
///
/// If the code corresponding to the provided hash hasn't been uploaded to the cluster storage yet,
/// it will create an empty SideVM instance. This instance will wait for the code to be uploaded
/// via `prpc::UploadSidevmCode`.
///
///# Arguments
///
///* `code_hash`: The hash of the code to be used for starting the SideVM instance.
///
///# Returns
///
/// A `Result` indicating success or failure, specifically a `system::DriverError` in case of failure.
pub fn start_sidevm(code_hash: Hash) -> Result<(), system::DriverError> {
    let driver =
        crate::system::SidevmOperationRef::instance().ok_or(system::Error::DriverNotFound)?;
    driver.deploy(code_hash)
}

/// Deploy a SideVM instance to a given contract. (system only)
pub fn deploy_sidevm_to(contract: AccountId, code_hash: Hash) {
    emit_event::<PinkEnvironment, _>(PinkEvent::DeploySidevmTo {
        contract,
        code_hash,
    });
}

/// Stop a SideVM instance running at given contract address. (system only)
pub fn stop_sidevm_at(contract: AccountId) {
    emit_event::<PinkEnvironment, _>(PinkEvent::ForceStopSidevm { contract });
}

/// Force stop the side VM instance if it is running
///
/// You should avoid to call this function. Instead, prefer let the side program exit gracefully
/// by itself.
pub fn force_stop_sidevm() {
    emit_event::<PinkEnvironment, _>(PinkEvent::StopSidevm)
}

/// Pushes a message to the associated SideVM instance.
///
/// Note: There is no guarantee that the message will be received by the SideVM instance.
/// The message may be dropped due to several reasons:
///
/// - The SideVM instance is not currently running.
/// - The SideVM instance is running, but the message queue is full. This may occur when the SideVM
///   instance is busy processing other messages.
///
///# Arguments
///
///* `message`: The message to be pushed to the SideVM instance.
pub fn push_sidevm_message(message: Vec<u8>) {
    emit_event::<PinkEnvironment, _>(PinkEvent::SidevmMessage(message))
}

/// Set the log handler contract of current cluster. (system only)
pub fn set_log_handler(contract: AccountId) {
    emit_event::<PinkEnvironment, _>(PinkEvent::SetLogHandler(contract))
}

/// Set the SideVM program that used by js_eval code. (system only)
pub fn set_js_runtime(code_hash: Hash) {
    emit_event::<PinkEnvironment, _>(PinkEvent::SetJsRuntime(code_hash))
}

/// Set the weight of contract used to schedule queries and sidevm virtual runtime. (system only)
pub fn set_contract_weight(contract: AccountId, weight: u32) {
    emit_event::<PinkEnvironment, _>(PinkEvent::SetContractWeight { contract, weight });
}

/// Upgrade the pink runtime to given version. (system only)
///
/// Note: pRuntime would exit if the version is not supported.
pub fn upgrade_runtime(version: (u32, u32)) {
    emit_event::<PinkEnvironment, _>(PinkEvent::UpgradeRuntimeTo { version });
}

/// Generate a slice of verifiable random bytes.
///
/// When called in a contract with the same salt, the same random bytes will be generated.
/// Different contracts with the same salt will generate different random bytes.
///
/// # Availability
/// any contract | query | transaction
pub fn vrf(salt: &[u8]) -> Vec<u8> {
    let mut key_salt = b"vrf:".to_vec();
    key_salt.extend_from_slice(salt);
    ext().derive_sr25519_key(key_salt.into())
}

/// Query to a sidevm in current worker.
pub fn query_local_sidevm(address: AccountId, payload: Vec<u8>) -> Result<Vec<u8>, String> {
    let url = format!("sidevm://{}", hex::encode(address));
    let response = http_post!(url, payload);
    if response.status_code != 200 {
        return Err(format!(
            "SideVM query failed: {} {}: {}",
            response.status_code,
            response.reason_phrase,
            String::from_utf8_lossy(&response.body)
        ));
    }
    Ok(response.body)
}

/// Pink defined environment. This environment is used to access the phat contract extended runtime features.
///
/// # Example
/// ```
/// #[ink::contract(env = PinkEnvironment)]
/// mod my_contract {
///     use pink::PinkEnvironment;
///     #[ink(storage)]
///     pub struct MyContract {}
///     impl MyContract {
///         #[ink(constructor)]
///         pub fn new() -> Self {
///             Self {}
///         }
///         #[ink(message)]
///         pub fn my_message(&self) {
///             // Access the pink environment.
///             let _pink_version = self.env().extension().runtime_version();
///         }
///     }
/// }
/// ```
#[derive(Debug, Clone, PartialEq, Eq)]
#[cfg_attr(feature = "std", derive(scale_info::TypeInfo))]
pub enum PinkEnvironment {}

impl Environment for PinkEnvironment {
    const MAX_EVENT_TOPICS: usize = <ink::env::DefaultEnvironment as Environment>::MAX_EVENT_TOPICS;

    type AccountId = <ink::env::DefaultEnvironment as Environment>::AccountId;
    type Balance = <ink::env::DefaultEnvironment as Environment>::Balance;
    type Hash = <ink::env::DefaultEnvironment as Environment>::Hash;
    type BlockNumber = <ink::env::DefaultEnvironment as Environment>::BlockNumber;
    type Timestamp = <ink::env::DefaultEnvironment as Environment>::Timestamp;

    type ChainExtension = chain_extension::PinkExt;
}

/// Returns the PinkEnvironment.
pub fn env() -> EnvAccess<'static, PinkEnvironment> {
    Default::default()
}

#[cfg(feature = "runtime_utils")]
mod topic;

#[cfg(test)]
mod tests {
    #[test]
    fn test_event_topics() {
        insta::assert_debug_snapshot!(super::PinkEvent::event_topic());
    }

    #[crate::contract(env = PinkEnvironment)]
    #[pink(inner = ink::contract)]
    mod test_contract {
        use crate::PinkEnvironment;

        #[ink(storage)]
        pub struct Test {
            flag: String,
        }
        impl Test {
            #[ink(constructor)]
            pub fn new(flag: String) -> Self {
                Self { flag }
            }
            #[ink(message)]
            pub fn flag(&self) -> String {
                self.flag.clone()
            }
        }

        #[test]
        fn it_works() {
            let contract = Test::new("".into());
            assert!(contract.flag().is_empty());
        }
    }
}