rosetta_tx_polkadot/
lib.rs

1use anyhow::{bail, Context, Result};
2use parity_scale_codec::{Compact, Decode, Encode};
3use rosetta_config_polkadot::{PolkadotMetadata, PolkadotMetadataParams};
4use rosetta_core::crypto::address::Address;
5use rosetta_core::crypto::SecretKey;
6use rosetta_core::{BlockchainConfig, TransactionBuilder};
7
8#[derive(Debug, Decode, Encode)]
9struct AccountId32([u8; 32]);
10
11#[derive(Debug, Decode, Encode)]
12enum MultiAddress {
13    Id(AccountId32),
14}
15
16#[derive(Encode)]
17enum MultiSignature {
18    #[allow(unused)]
19    Ed25519([u8; 64]),
20    Sr25519([u8; 64]),
21}
22
23#[derive(Encode)]
24enum Era {
25    Immortal,
26}
27
28fn parse_address(address: &Address) -> Result<AccountId32> {
29    const CHECKSUM_LEN: usize = 2;
30    let body_len = 32;
31
32    let data = bs58::decode(address.address()).into_vec()?;
33    if data.len() < 2 {
34        anyhow::bail!("ss58: bad length");
35    }
36    let (prefix_len, _ident) = match data[0] {
37        0..=63 => (1, data[0] as u16),
38        64..=127 => {
39            // weird bit manipulation owing to the combination of LE encoding and missing two
40            // bits from the left.
41            // d[0] d[1] are: 01aaaaaa bbcccccc
42            // they make the LE-encoded 16-bit value: aaaaaabb 00cccccc
43            // so the lower byte is formed of aaaaaabb and the higher byte is 00cccccc
44            let lower = (data[0] << 2) | (data[1] >> 6);
45            let upper = data[1] & 0b00111111;
46            (2, (lower as u16) | ((upper as u16) << 8))
47        }
48        _ => anyhow::bail!("ss58: invalid prefix"),
49    };
50    if data.len() != prefix_len + body_len + CHECKSUM_LEN {
51        anyhow::bail!("ss58: bad length");
52    }
53    //let format = ident.into();
54    //if !Self::format_is_allowed(format) {
55    //    anyhow::bail!("ss58: format not allowed");
56    //}
57
58    let hash = ss58hash(&data[0..body_len + prefix_len]);
59    let checksum = &hash.as_bytes()[0..CHECKSUM_LEN];
60    if data[body_len + prefix_len..body_len + prefix_len + CHECKSUM_LEN] != *checksum {
61        // Invalid checksum.
62        anyhow::bail!("invalid checksum");
63    }
64
65    let result = data[prefix_len..body_len + prefix_len]
66        .try_into()
67        .context("ss58: bad length")?;
68    Ok(AccountId32(result))
69}
70
71fn ss58hash(data: &[u8]) -> blake2_rfc::blake2b::Blake2bResult {
72    let mut context = blake2_rfc::blake2b::Blake2b::new(64);
73    context.update(b"SS58PRE");
74    context.update(data);
75    context.finalize()
76}
77
78#[derive(Default)]
79pub struct PolkadotTransactionBuilder;
80
81impl TransactionBuilder for PolkadotTransactionBuilder {
82    type MetadataParams = PolkadotMetadataParams;
83    type Metadata = PolkadotMetadata;
84
85    fn transfer(&self, address: &Address, amount: u128) -> Result<Self::MetadataParams> {
86        let address: AccountId32 = parse_address(address)?;
87        let dest = MultiAddress::Id(address);
88        #[derive(Debug, Decode, Encode)]
89        struct Transfer {
90            pub dest: MultiAddress,
91            #[codec(compact)]
92            pub amount: u128,
93        }
94        Ok(PolkadotMetadataParams {
95            pallet_name: "Balances".into(),
96            call_name: "transfer".into(),
97            call_args: Transfer { dest, amount }.encode(),
98        })
99    }
100
101    fn method_call(
102        &self,
103        _module: &str,
104        _method: &str,
105        _params: &[String],
106        _amount: u128,
107    ) -> Result<Self::MetadataParams> {
108        bail!("Not Implemented")
109    }
110
111    fn create_and_sign(
112        &self,
113        _config: &BlockchainConfig,
114        metadata_params: &Self::MetadataParams,
115        metadata: &Self::Metadata,
116        secret_key: &SecretKey,
117    ) -> Vec<u8> {
118        let address = AccountId32(secret_key.public_key().to_bytes().try_into().unwrap());
119        let address = MultiAddress::Id(address);
120        let extra_parameters = (
121            Era::Immortal,
122            Compact(metadata.nonce as u64),
123            // plain tip
124            Compact(0u128),
125        );
126        let additional_parameters = (
127            metadata.spec_version,
128            metadata.transaction_version,
129            metadata.genesis_hash,
130            metadata.genesis_hash,
131        );
132
133        // construct payload
134        let mut payload = vec![];
135        metadata.pallet_index.encode_to(&mut payload);
136        metadata.call_index.encode_to(&mut payload);
137        payload.extend(&metadata_params.call_args);
138        extra_parameters.encode_to(&mut payload);
139        additional_parameters.encode_to(&mut payload);
140
141        // sign payload
142        let signature = if payload.len() > 256 {
143            let hash = blake2_rfc::blake2b::blake2b(64, &[], &payload);
144            secret_key.sign(hash.as_bytes(), "substrate")
145        } else {
146            secret_key.sign(&payload, "substrate")
147        };
148        let signature =
149            MultiSignature::Sr25519(signature.to_bytes().as_slice().try_into().unwrap());
150
151        // encode transaction
152        let mut encoded = vec![];
153        // "is signed" + transaction protocol version (4)
154        (0b10000000 + 4u8).encode_to(&mut encoded);
155        // from address for signature
156        address.encode_to(&mut encoded);
157        // signature encode pending to vector
158        signature.encode_to(&mut encoded);
159        // attach custom extra params
160        extra_parameters.encode_to(&mut encoded);
161        // and now, call data
162        metadata.pallet_index.encode_to(&mut encoded);
163        metadata.call_index.encode_to(&mut encoded);
164        encoded.extend(&metadata_params.call_args);
165
166        // now, prefix byte length:
167        let len = Compact(encoded.len() as u32);
168        let mut transaction = vec![];
169        len.encode_to(&mut transaction);
170        transaction.extend(encoded);
171        transaction
172    }
173
174    fn deploy_contract(&self, _contract_binary: Vec<u8>) -> Result<Self::MetadataParams> {
175        bail!("Not Implemented")
176    }
177}