Skip to content

Commit 7ba11e4

Browse files
authored
Merge branch 'main' into sr/better-error
2 parents c6186a9 + 6ad8a54 commit 7ba11e4

27 files changed

+3913
-52
lines changed

README.md

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -12,13 +12,13 @@
1212
## Requirements
1313

1414
- [Rust](https://rustup.rs)
15-
- [Swift](https://www.swift.org/getting-started/)
16-
- [Task](https://taskfile.dev)
15+
- [protoc](https://grpc.io/docs/protoc-installation)
1716

1817
Execute command in root directory:
1918
```bash
20-
task build
19+
cargo check
2120
```
21+
2222
## API Docs
2323

2424
Check out the Hedera Rust SDK API reference docs [here](http://docs.rs/hedera/latest/hedera/index.html).

Taskfile.yml

Lines changed: 0 additions & 31 deletions
This file was deleted.

src/account/account_id.rs

Lines changed: 134 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -271,6 +271,7 @@ impl From<EntityId> for AccountId {
271271
mod tests {
272272
use std::str::FromStr;
273273

274+
use assert_matches::assert_matches;
274275
use hex_literal::hex;
275276

276277
use crate::ethereum::EvmAddress;
@@ -375,4 +376,137 @@ mod tests {
375376
"0.0.123-esxsf"
376377
);
377378
}
379+
380+
#[tokio::test]
381+
async fn bad_checksum_on_previewnet() {
382+
let client = Client::for_previewnet();
383+
let id = AccountId::from_str("0.0.123-ntjli").unwrap();
384+
385+
assert_matches!(
386+
id.validate_checksum(&client),
387+
Err(crate::Error::BadEntityId {
388+
shard: 0,
389+
realm: 0,
390+
num: 123,
391+
present_checksum: _,
392+
expected_checksum: _
393+
})
394+
);
395+
}
396+
397+
#[test]
398+
fn malformed_id_fails() {
399+
assert_matches!(AccountId::from_str("0.0."), Err(crate::Error::BasicParse(_)));
400+
}
401+
402+
#[test]
403+
fn malformed_checksum() {
404+
assert_matches!(AccountId::from_str("0.0.123-ntjl"), Err(crate::Error::BasicParse(_)));
405+
}
406+
407+
#[test]
408+
fn malformed_checksum_2() {
409+
assert_matches!(AccountId::from_str("0.0.123-ntjl1"), Err(crate::Error::BasicParse(_)));
410+
}
411+
412+
#[test]
413+
fn malformed_alias() {
414+
assert_matches!(AccountId::from_str("0.0.302a300506032b6570032100114e6abc371b82dab5c15ea149f02d34a012087b163516dd70f44acafabf777"), Err(crate::Error::KeyParse(_)));
415+
}
416+
#[test]
417+
fn malformed_alias_2() {
418+
assert_matches!(AccountId::from_str("0.0.302a300506032b6570032100114e6abc371b82dab5c15ea149f02d34a012087b163516dd70f44acafabf777g"), Err(crate::Error::KeyParse(_)));
419+
}
420+
#[test]
421+
fn malformed_alias_key_3() {
422+
assert_matches!(AccountId::from_str("0.0.303a300506032b6570032100114e6abc371b82dab5c15ea149f02d34a012087b163516dd70f44acafabf7777"), Err(crate::Error::KeyParse(_)));
423+
}
424+
425+
#[test]
426+
fn from_string_alias_key() {
427+
expect_test::expect!["0.0.302a300506032b6570032100114e6abc371b82dab5c15ea149f02d34a012087b163516dd70f44acafabf7777"]
428+
.assert_eq(&AccountId::from_str("0.0.302a300506032b6570032100114e6abc371b82dab5c15ea149f02d34a012087b163516dd70f44acafabf7777").unwrap().to_string())
429+
}
430+
431+
#[test]
432+
fn from_string_evm_address() {
433+
expect_test::expect!["0x302a300506032b6570032100114e6abc371b82da"].assert_eq(
434+
&AccountId::from_str("0x302a300506032b6570032100114e6abc371b82da").unwrap().to_string(),
435+
);
436+
}
437+
438+
#[test]
439+
fn from_solidity_address() {
440+
expect_test::expect!["0.0.5005"].assert_eq(
441+
&AccountId::from_solidity_address("000000000000000000000000000000000000138D")
442+
.unwrap()
443+
.to_string(),
444+
);
445+
}
446+
447+
#[test]
448+
fn from_solidity_address_0x() {
449+
expect_test::expect!["0.0.5005"].assert_eq(
450+
&AccountId::from_solidity_address("0x000000000000000000000000000000000000138D")
451+
.unwrap()
452+
.to_string(),
453+
);
454+
}
455+
456+
#[test]
457+
fn from_bytes() {
458+
let bytes = AccountId {
459+
shard: 0,
460+
realm: 0,
461+
num: 5005,
462+
alias: None,
463+
evm_address: None,
464+
checksum: None,
465+
}
466+
.to_bytes();
467+
468+
expect_test::expect!["0.0.5005"]
469+
.assert_eq(&AccountId::from_bytes(&bytes).unwrap().to_string());
470+
}
471+
472+
#[test]
473+
fn from_bytes_alias() {
474+
let bytes = AccountId::from_str("0.0.302a300506032b6570032100114e6abc371b82dab5c15ea149f02d34a012087b163516dd70f44acafabf7777").unwrap().to_bytes();
475+
476+
expect_test::expect!["0.0.302a300506032b6570032100114e6abc371b82dab5c15ea149f02d34a012087b163516dd70f44acafabf7777"].assert_eq(&AccountId::from_bytes(&bytes).unwrap().to_string());
477+
}
478+
479+
#[test]
480+
fn from_bytes_evm_address() {
481+
let bytes =
482+
AccountId::from_str("0x302a300506032b6570032100114e6abc371b82da").unwrap().to_bytes();
483+
expect_test::expect!["0.0.0"]
484+
.assert_eq(&AccountId::from_bytes(&bytes).unwrap().to_string());
485+
}
486+
487+
#[test]
488+
fn to_solidity_address() {
489+
let id = AccountId {
490+
shard: 0,
491+
realm: 0,
492+
num: 5005,
493+
alias: None,
494+
evm_address: None,
495+
checksum: None,
496+
};
497+
498+
expect_test::expect!["000000000000000000000000000000000000138d"]
499+
.assert_eq(&id.to_solidity_address().unwrap());
500+
}
501+
502+
#[test]
503+
fn from_evm_address() {
504+
let evm_address =
505+
EvmAddress::from_str("0x302a300506032b6570032100114e6abc371b82da").unwrap();
506+
507+
let id = AccountId::from_evm_address(&evm_address);
508+
509+
expect_test::expect!["0x302a300506032b6570032100114e6abc371b82da"]
510+
.assert_eq(&id.to_string());
511+
}
378512
}

src/contract/contract_id.rs

Lines changed: 86 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -269,3 +269,89 @@ impl From<EntityId> for ContractId {
269269
Self { shard, realm, num, evm_address: None, checksum }
270270
}
271271
}
272+
273+
#[cfg(test)]
274+
mod tests {
275+
use std::str::FromStr;
276+
277+
use crate::ContractId;
278+
279+
#[test]
280+
fn parse() {
281+
expect_test::expect!["0.0.5005"]
282+
.assert_eq(&ContractId::from_str("0.0.5005").unwrap().to_string());
283+
}
284+
285+
#[test]
286+
fn from_solidity_address() {
287+
expect_test::expect!["0.0.5005"].assert_eq(
288+
&ContractId::from_solidity_address("000000000000000000000000000000000000138D")
289+
.unwrap()
290+
.to_string(),
291+
);
292+
}
293+
294+
#[test]
295+
fn from_solidity_address_0x() {
296+
expect_test::expect!["0.0.5005"].assert_eq(
297+
&ContractId::from_solidity_address("0x000000000000000000000000000000000000138D")
298+
.unwrap()
299+
.to_string(),
300+
);
301+
}
302+
303+
#[test]
304+
fn from_evm_address() {
305+
expect_test::expect!["1.2.98329e006610472e6b372c080833f6d79ed833cf"].assert_eq(
306+
&ContractId::from_evm_address(1, 2, "98329e006610472e6B372C080833f6D79ED833cf")
307+
.unwrap()
308+
.to_string(),
309+
)
310+
}
311+
312+
#[test]
313+
fn from_evm_address_0x() {
314+
expect_test::expect!["1.2.98329e006610472e6b372c080833f6d79ed833cf"].assert_eq(
315+
&ContractId::from_evm_address(1, 2, "0x98329e006610472e6B372C080833f6D79ED833cf")
316+
.unwrap()
317+
.to_string(),
318+
)
319+
}
320+
321+
#[test]
322+
fn parse_evm_address() {
323+
expect_test::expect!["1.2.98329e006610472e6b372c080833f6d79ed833cf"].assert_eq(
324+
&ContractId::from_str("1.2.0x98329e006610472e6B372C080833f6D79ED833cf")
325+
.unwrap()
326+
.to_string(),
327+
)
328+
}
329+
330+
#[test]
331+
fn to_from_bytes() {
332+
let a = ContractId::from_str("1.2.3").unwrap();
333+
assert_eq!(ContractId::from_bytes(&a.to_bytes()).unwrap(), a);
334+
let b = ContractId::from_evm_address(1, 2, "0x98329e006610472e6B372C080833f6D79ED833cf")
335+
.unwrap();
336+
assert_eq!(ContractId::from_bytes(&b.to_bytes()).unwrap(), b);
337+
}
338+
339+
#[test]
340+
fn to_solidity_address() {
341+
expect_test::expect!["000000000000000000000000000000000000138d"].assert_eq(
342+
&ContractId { shard: 0, realm: 0, num: 5005, checksum: None, evm_address: None }
343+
.to_solidity_address()
344+
.unwrap(),
345+
)
346+
}
347+
348+
#[test]
349+
fn to_solidity_address_2() {
350+
expect_test::expect!["98329e006610472e6b372c080833f6d79ed833cf"].assert_eq(
351+
&ContractId::from_evm_address(1, 2, "0x98329e006610472e6B372C080833f6D79ED833cf")
352+
.unwrap()
353+
.to_solidity_address()
354+
.unwrap(),
355+
)
356+
}
357+
}

src/entity_id.rs

Lines changed: 13 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -48,9 +48,15 @@ impl FromStr for Checksum {
4848
type Err = Error;
4949

5050
fn from_str(s: &str) -> Result<Self, Self::Err> {
51-
s.parse()
52-
.map(Checksum)
53-
.map_err(|_| Error::basic_parse("Expected checksum to be exactly 5 characters"))
51+
let ascii_str: TinyAsciiStr<5> = s
52+
.parse()
53+
.map_err(|e| Error::basic_parse(format!("Expected checksum to be valid ascii: {e}")))?;
54+
55+
if ascii_str.len() != 5 || !ascii_str.is_ascii_alphabetic_lowercase() {
56+
return Err(Error::basic_parse("Expected checksum to be exactly 5 lowercase letters"));
57+
}
58+
59+
Ok(Self(ascii_str))
5460
}
5561
}
5662

@@ -130,6 +136,10 @@ impl<'a> PartialEntityId<'a> {
130136
Some((shard, rest)) => {
131137
let (realm, last) = rest.split_once('.').ok_or_else(expecting)?;
132138

139+
if last.is_empty() {
140+
return Err(expecting());
141+
}
142+
133143
let shard = shard.parse().map_err(|_| expecting())?;
134144
let realm = realm.parse().map_err(|_| expecting())?;
135145

src/token/custom_fees/mod.rs

Lines changed: 13 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -63,14 +63,22 @@ pub struct CustomFee<Fee> {
6363
/// The account to receive the custom fee.
6464
pub fee_collector_account_id: Option<AccountId>,
6565

66+
/// If true, fee fcollectors are not charged this fee for transfers.
6667
pub all_collectors_are_exempt: bool,
6768
}
6869

6970
impl AnyCustomFee {
71+
/// Create `AnyCustomFee` from protobuf-encoded `bytes`.
72+
///
73+
/// # Errors
74+
/// - [`Error::FromProtobuf`](crate::Error::FromProtobuf) if decoding the bytes fails to produce a valid protobuf.
75+
/// - [`Error::FromProtobuf`](crate::Error::FromProtobuf) if decoding the protobuf fails.
7076
pub fn from_bytes(bytes: &[u8]) -> crate::Result<Self> {
7177
FromProtobuf::from_bytes(bytes)
7278
}
7379

80+
/// Convert `self` to a protobuf-encoded [`Vec<u8>`].
81+
#[must_use]
7482
pub fn to_bytes(&self) -> Vec<u8> {
7583
ToProtobuf::to_bytes(self)
7684
}
@@ -206,30 +214,26 @@ pub struct FixedFeeData {
206214

207215
/// The denomination of the fee; taken as hbar if left unset and, in a TokenCreate, taken as the id
208216
/// of the newly created token if set to the sentinel value of 0.0.0
209-
pub denominating_token_id: TokenId,
217+
pub denominating_token_id: Option<TokenId>,
210218
}
211219

212220
impl FixedFeeData {
213221
#[must_use]
214222
pub fn from_hbar(amount: Hbar) -> Self {
215-
Self {
216-
amount: amount.to_tinybars(),
217-
denominating_token_id: TokenId { shard: 0, realm: 0, num: 0, checksum: None },
218-
}
223+
Self { amount: amount.to_tinybars(), denominating_token_id: None }
219224
}
220225

221226
#[must_use]
222227
pub fn get_hbar(&self) -> Option<Hbar> {
223-
(self.denominating_token_id == TokenId { shard: 0, realm: 0, num: 0, checksum: None })
224-
.then(|| Hbar::from_tinybars(self.amount))
228+
self.denominating_token_id.is_none().then(|| Hbar::from_tinybars(self.amount))
225229
}
226230
}
227231

228232
impl FromProtobuf<services::FixedFee> for FixedFeeData {
229233
fn from_protobuf(pb: services::FixedFee) -> crate::Result<Self> {
230234
Ok(Self {
231235
amount: pb.amount,
232-
denominating_token_id: TokenId::from_protobuf(pb_getf!(pb, denominating_token_id)?)?,
236+
denominating_token_id: Option::from_protobuf(pb.denominating_token_id)?,
233237
})
234238
}
235239
}
@@ -240,7 +244,7 @@ impl ToProtobuf for FixedFeeData {
240244
fn to_protobuf(&self) -> Self::Protobuf {
241245
Self::Protobuf {
242246
amount: self.amount,
243-
denominating_token_id: Some(self.denominating_token_id.to_protobuf()),
247+
denominating_token_id: self.denominating_token_id.to_protobuf(),
244248
}
245249
}
246250
}

0 commit comments

Comments
 (0)