utility-workspaces 0.12.4

Library for automating workflows and testing Utility smart contracts.
Documentation
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
529
530
531
532
533
534
//! Types used in the workspaces crate. A lot of these are types are copied over from unc_primitives
//! since those APIs are not yet stable. Once they are, we can directly reference them here, so no
//! changes on the library consumer side is needed. Just keep using these types defined here as-is.

pub(crate) mod account;
pub(crate) mod block;
pub(crate) mod chunk;
pub(crate) mod gas_meter;

#[cfg(feature = "interop_sdk")]
mod sdk;

use std::convert::TryFrom;
use std::fmt::{self, Debug, Display};
use std::io;
use std::path::Path;
use std::str::FromStr;

pub use unc_account_id::AccountId;
use unc_primitives::borsh::{BorshDeserialize, BorshSerialize};

use serde::{Deserialize, Serialize};
use sha2::Digest;

use crate::error::{Error, ErrorKind};
use crate::result::Result;

pub use self::account::{AccountDetails, AccountDetailsPatch};
pub use self::chunk::{Chunk, ChunkHeader};
pub use self::gas_meter::GasMeter;

/// Nonce is a unit used to determine the order of transactions in the pool.
pub type Nonce = u64;

/// Gas units used in the execution of transactions. For a more in depth description of
/// how and where it can be used, visit [Gas](https://docs.utility.org/docs/concepts/gas).
pub use unc_gas::UncGas as Gas;

pub use unc_token::UncToken;

/// Height of a specific block
pub type BlockHeight = u64;

/// Shard index, from 0 to NUM_SHARDS - 1.
pub type ShardId = u64;

fn from_base58(s: &str) -> Result<Vec<u8>, Box<dyn std::error::Error + Send + Sync>> {
    bs58::decode(s).into_vec().map_err(|err| err.into())
}

/// Key types supported for either a [`SecretKey`] or [`PublicKey`]
#[derive(Debug, Copy, Clone, Serialize, Deserialize)]
#[non_exhaustive]
pub enum KeyType {
    ED25519 = 0,
    SECP256K1 = 1,
    RSA2048 = 2,
}

impl KeyType {
    const fn into_unc_keytype(self) -> unc_crypto::KeyType {
        match self {
            Self::ED25519 => unc_crypto::KeyType::ED25519,
            Self::SECP256K1 => unc_crypto::KeyType::SECP256K1,
            Self::RSA2048 => unc_crypto::KeyType::RSA2048,
        }
    }

    const fn from_unc_keytype(key_type: unc_crypto::KeyType) -> Self {
        match key_type {
            unc_crypto::KeyType::ED25519 => Self::ED25519,
            unc_crypto::KeyType::SECP256K1 => Self::SECP256K1,
            unc_crypto::KeyType::RSA2048 => Self::RSA2048,
        }
    }

    /// Length of the bytes of the public key associated with this key type.
    pub const fn data_len(&self) -> usize {
        match self {
            Self::ED25519 => 32,
            Self::SECP256K1 => 64,
            Self::RSA2048 => 294,
        }
    }
}

impl Display for KeyType {
    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
        write!(f, "{}", self.into_unc_keytype())
    }
}

impl FromStr for KeyType {
    type Err = Error;

    fn from_str(value: &str) -> Result<Self, Self::Err> {
        let key_type = unc_crypto::KeyType::from_str(value)
            .map_err(|e| ErrorKind::DataConversion.custom(e))?;

        Ok(Self::from_unc_keytype(key_type))
    }
}

impl TryFrom<u8> for KeyType {
    type Error = Error;

    fn try_from(value: u8) -> Result<Self, Self::Error> {
        match value {
            0 => Ok(Self::ED25519),
            1 => Ok(Self::SECP256K1),
            2 => Ok(Self::RSA2048),
            unknown_key_type => Err(ErrorKind::DataConversion
                .custom(format!("Unknown key type provided: {unknown_key_type}"))),
        }
    }
}

impl From<PublicKey> for unc_crypto::PublicKey {
    fn from(pk: PublicKey) -> Self {
        pk.0
    }
}

/// Public key of an account on chain. Usually created along with a [`SecretKey`]
/// to form a keypair associated to the account.
#[derive(Debug, Clone, Hash, Eq, PartialEq, Ord, PartialOrd, Serialize, Deserialize)]
pub struct PublicKey(pub(crate) unc_crypto::PublicKey);

#[allow(clippy::len_without_is_empty)] // PublicKey is guaranteed to never be empty due to KeyType restrictions.
impl PublicKey {
    /// Create an empty `PublicKey` with the given [`KeyType`]. This is a zero-ed out public key with the
    /// length of the bytes determined by the associated key type.
    pub fn empty(key_type: KeyType) -> Self {
        Self(unc_crypto::PublicKey::empty(key_type.into_unc_keytype()))
    }

    /// Create a new [`PublicKey`] from the given bytes. This will return an error if the bytes are not in the
    /// correct format. Expected to have key type be the first byte encoded, with the remaining bytes being the
    /// key data.
    pub fn try_from_parts(key_type: KeyType, bytes: &[u8]) -> Result<Self> {
        let mut buf = Vec::new();
        buf.push(key_type as u8);
        buf.extend(bytes);
        Ok(Self(unc_crypto::PublicKey::try_from_slice(&buf).map_err(
            |e| {
                ErrorKind::DataConversion
                    .full(format!("Invalid key data for key type: {key_type}"), e)
            },
        )?))
    }

    #[cfg(feature = "interop_sdk")]
    fn try_from_bytes(bytes: &[u8]) -> Result<Self> {
        let key_type = KeyType::try_from(bytes[0])?;
        Ok(Self(unc_crypto::PublicKey::try_from_slice(bytes).map_err(
            |e| {
                ErrorKind::DataConversion
                    .full(format!("Invalid key data for key type: {key_type}"), e)
            },
        )?))
    }

    /// Get the number of bytes this key uses. This will differ depending on the [`KeyType`]. i.e. for
    /// ED25519 keys, this will return 32 + 1, while for SECP256K1 keys, this will return 64 + 1. The +1
    /// is used to store the key type, and will appear at the start of the serialized key.
    pub fn len(&self) -> usize {
        self.0.len()
    }

    /// Get the [`KeyType`] of the public key.
    pub fn key_type(&self) -> KeyType {
        KeyType::from_unc_keytype(self.0.key_type())
    }

    /// Get the key data of the public key. This is serialized bytes of the public key. This will not
    /// include the key type, and will only contain the raw key data.
    pub fn key_data(&self) -> &[u8] {
        self.0.key_data()
    }
}

impl Display for PublicKey {
    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
        write!(f, "{}", self.0)
    }
}

impl FromStr for PublicKey {
    type Err = Error;

    fn from_str(value: &str) -> Result<Self, Self::Err> {
        let pk = unc_crypto::PublicKey::from_str(value)
            .map_err(|e| ErrorKind::DataConversion.custom(e))?;

        Ok(Self(pk))
    }
}

impl BorshSerialize for PublicKey {
    fn serialize<W: io::Write>(&self, writer: &mut W) -> io::Result<()> {
        // NOTE: sdk::PublicKey requires that we serialize the length of the key first, then the key itself.
        // Casted usize to u32 since the length in WASM is only 4 bytes long.
        BorshSerialize::serialize(&(self.len() as u32), writer)?;
        // Serialize key type and key data:
        BorshSerialize::serialize(&self.0, writer)
    }
}

impl BorshDeserialize for PublicKey {
    fn deserialize_reader<R: io::Read>(reader: &mut R) -> io::Result<Self> {
        let len: u32 = BorshDeserialize::deserialize_reader(reader)?;
        let pk: unc_crypto::PublicKey = BorshDeserialize::deserialize_reader(reader)?;

        // Check that the length of the key matches the length we read from the buffer:
        if pk.len() != len as usize {
            return Err(io::Error::new(
                io::ErrorKind::InvalidData,
                format!(
                    "Key length of {} does not match length of {} read from buffer",
                    pk.len(),
                    len
                ),
            ));
        }
        Ok(Self(pk))
    }
}

/// Secret key of an account on chain. Usually created along with a [`PublicKey`]
/// to form a keypair associated to the account. To generate a new keypair, use
/// one of the creation methods found here, such as [`SecretKey::from_seed`]
#[derive(Debug, Clone, Eq, PartialEq, Serialize, Deserialize)]
pub struct SecretKey(pub(crate) unc_crypto::SecretKey);

impl SecretKey {
    /// Get the [`KeyType`] of the secret key.
    pub fn key_type(&self) -> KeyType {
        KeyType::from_unc_keytype(self.0.key_type())
    }

    /// Get the [`PublicKey`] associated to this secret key.
    pub fn public_key(&self) -> PublicKey {
        PublicKey(self.0.public_key())
    }

    /// Generate a new secret key provided the [`KeyType`] and seed.
    pub fn from_seed(key_type: KeyType, seed: &str) -> Self {
        let key_type = key_type.into_unc_keytype();
        Self(unc_crypto::SecretKey::from_seed(key_type, seed))
    }

    /// Generate a new secret key provided the [`KeyType`]. This will use OS provided entropy
    /// to generate the key.
    pub fn from_random(key_type: KeyType) -> Self {
        let key_type = key_type.into_unc_keytype();
        Self(unc_crypto::SecretKey::from_random(key_type))
    }
}

impl Display for SecretKey {
    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> Result<(), std::fmt::Error> {
        write!(f, "{}", self.0)
    }
}

impl FromStr for SecretKey {
    type Err = Error;

    fn from_str(value: &str) -> Result<Self, Self::Err> {
        let sk = unc_crypto::SecretKey::from_str(value)
            .map_err(|e| ErrorKind::DataConversion.custom(e))?;

        Ok(Self(sk))
    }
}

#[derive(Clone)]
pub struct InMemorySigner {
    pub(crate) account_id: AccountId,
    pub(crate) secret_key: SecretKey,
}

impl InMemorySigner {
    pub fn from_secret_key(account_id: AccountId, secret_key: SecretKey) -> Self {
        Self {
            account_id,
            secret_key,
        }
    }

    pub fn from_file(path: &Path) -> Result<Self> {
        let signer =
            unc_crypto::InMemorySigner::from_file(path).map_err(|err| ErrorKind::Io.custom(err))?;
        Ok(Self::from_secret_key(
            signer.account_id,
            SecretKey(signer.secret_key),
        ))
    }

    pub(crate) fn inner(&self) -> unc_crypto::InMemorySigner {
        unc_crypto::InMemorySigner::from_secret_key(
            self.account_id.clone(),
            self.secret_key.0.clone(),
        )
    }
}

// type taken from unc_primitives::hash::CryptoHash.
/// CryptoHash is type for storing the hash of a specific block.
#[derive(Copy, Clone, Default, Hash, Eq, PartialEq, Ord, PartialOrd)]
pub struct CryptoHash(pub [u8; 32]);

impl CryptoHash {
    pub(crate) fn hash_bytes(bytes: &[u8]) -> Self {
        let hash = sha2::Sha256::digest(bytes).into();
        Self(hash)
    }
}

impl FromStr for CryptoHash {
    type Err = Error;

    fn from_str(s: &str) -> Result<Self, Self::Err> {
        let bytes = from_base58(s).map_err(|e| ErrorKind::DataConversion.custom(e))?;
        Self::try_from(bytes)
    }
}

impl TryFrom<&[u8]> for CryptoHash {
    type Error = Error;

    fn try_from(bytes: &[u8]) -> Result<Self, Self::Error> {
        if bytes.len() != 32 {
            return Err(Error::message(
                ErrorKind::DataConversion,
                format!(
                    "incorrect hash length (expected 32, but {} was given)",
                    bytes.len()
                ),
            ));
        }
        let mut buf = [0; 32];
        buf.copy_from_slice(bytes);
        Ok(Self(buf))
    }
}

impl TryFrom<Vec<u8>> for CryptoHash {
    type Error = Error;

    fn try_from(v: Vec<u8>) -> Result<Self, Self::Error> {
        <Self as TryFrom<&[u8]>>::try_from(v.as_ref())
    }
}

impl Debug for CryptoHash {
    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
        write!(f, "{}", self)
    }
}

impl Display for CryptoHash {
    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
        Display::fmt(&bs58::encode(self.0).into_string(), f)
    }
}

impl From<unc_primitives::hash::CryptoHash> for CryptoHash {
    fn from(hash: unc_primitives::hash::CryptoHash) -> Self {
        Self(hash.0)
    }
}

/// Access key provides limited access to an account. Each access key belongs to some account and
/// is identified by a unique (within the account) public key. One account may have large number of
/// access keys. Access keys allow to act on behalf of the account by restricting transactions
/// that can be issued.
#[derive(Clone, Debug)]
pub struct AccessKey {
    /// The nonce for this access key.
    /// NOTE: In some cases the access key needs to be recreated. If the new access key reuses the
    /// same public key, the nonce of the new access key should be equal to the nonce of the old
    /// access key. It's required to avoid replaying old transactions again.
    pub nonce: Nonce,

    /// Defines permissions for this access key.
    pub permission: AccessKeyPermission,
}

impl AccessKey {
    pub fn full_access() -> Self {
        Self {
            nonce: 0,
            permission: AccessKeyPermission::FullAccess,
        }
    }

    pub fn function_call_access(
        receiver_id: &AccountId,
        method_names: &[&str],
        allowance: Option<UncToken>,
    ) -> Self {
        Self {
            nonce: 0,
            permission: AccessKeyPermission::FunctionCall(FunctionCallPermission {
                receiver_id: receiver_id.clone().into(),
                method_names: method_names.iter().map(|s| s.to_string()).collect(),
                allowance,
            }),
        }
    }
}

/// Similar to an [`AccessKey`], but also has the [`PublicKey`] associated with it.
#[derive(Clone, Debug)]
pub struct AccessKeyInfo {
    pub public_key: PublicKey,
    pub access_key: AccessKey,
}

impl From<unc_primitives::views::AccessKeyInfoView> for AccessKeyInfo {
    fn from(view: unc_primitives::views::AccessKeyInfoView) -> Self {
        Self {
            public_key: PublicKey(view.public_key),
            access_key: view.access_key.into(),
        }
    }
}

/// Defines permissions for AccessKey
#[derive(Clone, Debug)]
pub enum AccessKeyPermission {
    FunctionCall(FunctionCallPermission),

    /// Grants full access to the account.
    /// NOTE: It's used to replace account-level public keys.
    FullAccess,
}

/// Grants limited permission to make transactions with FunctionCallActions
/// The permission can limit the allowed balance to be spent on the prepaid gas.
/// It also restrict the account ID of the receiver for this function call.
/// It also can restrict the method name for the allowed function calls.
#[derive(Clone, Debug)]
pub struct FunctionCallPermission {
    /// Allowance is a balance limit to use by this access key to pay for function call gas and
    /// transaction fees. When this access key is used, both account balance and the allowance is
    /// decreased by the same value.
    /// `None` means unlimited allowance.
    /// NOTE: To change or increase the allowance, the old access key needs to be deleted and a new
    /// access key should be created.
    pub allowance: Option<UncToken>,

    // This isn't an AccountId because already existing records in testnet genesis have invalid
    // values for this field (see: https://github.com/utnet-org/unccore/pull/4621#issuecomment-892099860)
    // we accommodate those by using a string, allowing us to read and parse genesis.
    /// The access key only allows transactions with the given receiver's account id.
    pub receiver_id: String,

    /// A list of method names that can be used. The access key only allows transactions with the
    /// function call of one of the given method names.
    /// Empty list means any method name can be used.
    pub method_names: Vec<String>,
}

impl From<AccessKey> for unc_primitives::account::AccessKey {
    fn from(access_key: AccessKey) -> Self {
        Self {
            nonce: access_key.nonce,
            permission: match access_key.permission {
                AccessKeyPermission::FunctionCall(function_call_permission) => {
                    unc_primitives::account::AccessKeyPermission::FunctionCall(
                        unc_primitives::account::FunctionCallPermission {
                            allowance: function_call_permission.allowance.map(|a| a.as_attounc()),
                            receiver_id: function_call_permission.receiver_id,
                            method_names: function_call_permission.method_names,
                        },
                    )
                }
                AccessKeyPermission::FullAccess => {
                    unc_primitives::account::AccessKeyPermission::FullAccess
                }
            },
        }
    }
}

impl From<unc_primitives::views::AccessKeyView> for AccessKey {
    fn from(access_key: unc_primitives::views::AccessKeyView) -> Self {
        Self {
            nonce: access_key.nonce,
            permission: match access_key.permission {
                unc_primitives::views::AccessKeyPermissionView::FunctionCall {
                    allowance,
                    receiver_id,
                    method_names,
                } => AccessKeyPermission::FunctionCall(FunctionCallPermission {
                    allowance: allowance.map(UncToken::from_attounc),
                    receiver_id,
                    method_names,
                }),
                unc_primitives::views::AccessKeyPermissionView::FullAccess => {
                    AccessKeyPermission::FullAccess
                }
            },
        }
    }
}

/// Finality of a transaction or block in which transaction is included in. For more info
/// go to the [Utility finality](https://docs.utility.org/docs/concepts/transaction#finality) docs.
#[derive(Clone, Debug)]
#[non_exhaustive]
pub enum Finality {
    /// Optimistic finality. The latest block recorded on the node that responded to our query
    /// (<1 second delay after the transaction is submitted).
    Optimistic,
    /// Unc-final finality. Similarly to `Final` finality, but delay should be roughly 1 second.
    DoomSlug,
    /// Final finality. The block that has been validated on at least 66% of the nodes in the
    /// network. (At max, should be 2 second delay after the transaction is submitted.)
    Final,
}

impl From<Finality> for unc_primitives::types::BlockReference {
    fn from(value: Finality) -> Self {
        let value = match value {
            Finality::Optimistic => unc_primitives::types::Finality::None,
            Finality::DoomSlug => unc_primitives::types::Finality::DoomSlug,
            Finality::Final => unc_primitives::types::Finality::Final,
        };
        value.into()
    }
}