Skip to main content

o2_tools/
signature_ext.rs

1use fuels::{
2    core::{
3        codec::calldata,
4        traits::Tokenizable,
5    },
6    crypto::Message,
7    types::{
8        Bits256,
9        ChainId,
10        Identity,
11    },
12};
13
14use crate::{
15    trade_account::{
16        generate_session_signing_payload,
17        generate_signing_payload,
18    },
19    trade_account_deploy::{
20        CallContractArg,
21        CallContractArgs as TradeAccountCallContractArgs,
22        CallParams,
23        Domain,
24        EIP712Domain,
25        Encoding,
26        MultiCallContractArgs,
27        RevokeArgs,
28        SRC16Domain,
29        Secp256k1,
30        SessionArgs,
31        SetProxyTargetArgs,
32        Signature,
33        WithdrawArgs,
34    },
35};
36use ethers_core::{
37    types::{
38        H160,
39        RecoveryMessage,
40        Signature as EthSig,
41        U256,
42        U256 as Ethers256,
43        transaction::eip712::EIP712Domain as EVMDomain,
44    },
45    utils::keccak256,
46};
47
48const EVM_ADDRESS_PADDING_LENGTH: usize = 12;
49
50/// Takes the rightmost 20 BYTES of a 32-byte Fuel contract ID.
51/// Interprets "rightmost" as the last bytes in the array / hex string.
52fn fuel_contract_id_to_evm_address(id: [u8; 32]) -> [u8; 20] {
53    let mut addr = [0u8; 20];
54    // last 20 bytes: indices 12..32
55    addr.copy_from_slice(&id[12..32]);
56    addr
57}
58
59pub trait DomainExt {
60    fn separator(&self) -> [u8; 32];
61    fn encode<T>(&self, struct_type: T) -> [u8; 32]
62    where
63        T: SRC16Encode;
64}
65
66impl DomainExt for Domain {
67    fn separator(&self) -> [u8; 32] {
68        match self {
69            Domain::EIP712Domain(d) => EVMDomain::from(d.clone()).separator(),
70            Domain::SRC16Domain(d) => d.separator(),
71        }
72    }
73
74    fn encode<T>(&self, struct_type: T) -> [u8; 32]
75    where
76        T: SRC16Encode,
77    {
78        let ds = self.separator();
79        let encoding = match self {
80            Domain::SRC16Domain(_) => Encoding::SRC16,
81            Domain::EIP712Domain(_) => Encoding::EIP712,
82        };
83        let sh = struct_type.struct_hash(encoding);
84
85        let mut preimage = Vec::with_capacity(2 + 32 + 32);
86        preimage.extend_from_slice(&[0x19, 0x01]);
87        preimage.extend_from_slice(ds.as_slice());
88        preimage.extend_from_slice(sh.as_slice());
89
90        keccak256(preimage)
91    }
92}
93
94impl SRC16Domain {
95    // Compute the domain separator;
96    pub fn separator(&self) -> [u8; 32] {
97        let mut tokens: Vec<u8> = Vec::new();
98
99        tokens.extend_from_slice(src16_domain_type_hash(self).as_ref());
100
101        if self.name.is_some() {
102            tokens.extend_from_slice(keccak256(self.name.clone().unwrap()).as_slice());
103        }
104        if self.version.is_some() {
105            tokens.extend_from_slice(keccak256(self.version.clone().unwrap()).as_slice());
106        }
107
108        if let Some(chain_id) = self.chain_id {
109            tokens.extend(chain_id.0.iter().flat_map(|x| x.to_be_bytes()));
110        }
111
112        if let Some(verifying_contract) = self.verifying_contract {
113            tokens.extend_from_slice(verifying_contract.as_slice());
114        }
115
116        if let Some(salt) = self.salt {
117            tokens.extend_from_slice(salt.0.as_slice());
118        }
119
120        keccak256(tokens)
121    }
122}
123
124fn src16_domain_type_hash(domain: &SRC16Domain) -> [u8; 32] {
125    let mut fields = Vec::new();
126
127    // IMPORTANT: fixed order
128    if domain.name.is_some() {
129        fields.push("string name");
130    }
131    if domain.version.is_some() {
132        fields.push("string version");
133    }
134    if domain.chain_id.is_some() {
135        fields.push("u256 chain_id");
136    }
137    if domain.verifying_contract.is_some() {
138        fields.push("contractId verifying_contract");
139    }
140    if domain.salt.is_some() {
141        fields.push("b256 salt");
142    }
143
144    let type_string = format!("SRC16Domain({})", fields.join(","));
145
146    keccak256(type_string.as_bytes())
147}
148
149impl From<EIP712Domain> for EVMDomain {
150    fn from(domain: EIP712Domain) -> Self {
151        let chain_id = domain.chain_id.map(|id| Ethers256(id.0));
152        let verifying_contract = domain
153            .verifying_contract
154            .map(|contract| H160(fuel_contract_id_to_evm_address(contract.0)));
155        let salt = domain.salt.map(|s| s.0);
156
157        EVMDomain {
158            name: domain.name,
159            version: domain.version,
160            chain_id,
161            verifying_contract,
162            salt,
163        }
164    }
165}
166
167pub trait SRC16Encode {
168    #[allow(dead_code)]
169    fn type_hash(encoding: Encoding) -> [u8; 32];
170    fn struct_hash(self, encoding: Encoding) -> [u8; 32];
171}
172
173fn from_compact_or_standard(bytes: &[u8]) -> anyhow::Result<EthSig> {
174    match bytes.len() {
175        65 => Ok(EthSig::try_from(bytes)?),
176        64 => {
177            let r = U256::from_big_endian(&bytes[..32]);
178            let mut s_bytes = [0u8; 32];
179            s_bytes.copy_from_slice(&bytes[32..64]);
180
181            let y_parity = (s_bytes[0] >> 7) & 1;
182            // In compact ECDSA signatures, the y-parity is encoded in the most significant bit
183            // of the first byte of the s value. This bit must be cleared to obtain the actual s value.
184            s_bytes[0] &= 0x7F;
185
186            let s = U256::from_big_endian(&s_bytes);
187            // Ethereum uses v = 27 or 28 for legacy signatures; here, we add 27 to the parity bit.
188            let v = 27 + y_parity as u64;
189
190            Ok(EthSig { r, s, v })
191        }
192        n => anyhow::bail!("invalid length {n}, expected 64 or 65"),
193    }
194}
195
196pub trait SignatureCurve {
197    type Address;
198
199    /// Verify that the owner signed the payload
200    fn verify_owner<T>(
201        &self,
202        nonce: u64,
203        chain_id: ChainId,
204        f_name: String,
205        args: Option<T>,
206        address: &Self::Address,
207    ) -> anyhow::Result<()>
208    where
209        T: Tokenizable;
210
211    /// Verify that the session key signed the payload
212    fn verify_session<T>(
213        &self,
214        nonce: u64,
215        args: T,
216        address: &Self::Address,
217    ) -> anyhow::Result<()>
218    where
219        T: Tokenizable;
220
221    fn verify_typed<T>(
222        &self,
223        args: T,
224        domain: &Domain,
225        address: &Self::Address,
226    ) -> anyhow::Result<()>
227    where
228        T: SRC16Encode;
229}
230
231pub struct Secp256k1Address(pub fuels::types::Address);
232
233impl SignatureCurve for Secp256k1 {
234    type Address = Secp256k1Address;
235
236    fn verify_owner<T>(
237        &self,
238        nonce: u64,
239        chain_id: ChainId,
240        f_name: String,
241        args: Option<T>,
242        address: &Self::Address,
243    ) -> anyhow::Result<()>
244    where
245        T: Tokenizable,
246    {
247        let address = &address.0;
248
249        // if the first 12 bytes of the address are all zeros, then we assume it
250        // is an EVM (Ethereum) address and signature
251        if address.starts_with([0u8; EVM_ADDRESS_PADDING_LENGTH].as_ref()) {
252            let evm_address = &address[EVM_ADDRESS_PADDING_LENGTH..];
253            let evm_message = if let Some(args_some) = args {
254                calldata!((nonce, *chain_id, f_name, args_some))?
255            } else {
256                calldata!((nonce, *chain_id, f_name))?
257            };
258            let evm_sig = from_compact_or_standard(&self.bits)?;
259            let recovered_address = evm_sig.recover(&evm_message[..])?;
260
261            if recovered_address.as_bytes() != evm_address {
262                anyhow::bail!("invalid evm address provided");
263            }
264
265            evm_sig.verify(evm_message, recovered_address)?;
266        } else {
267            let fuel_message = generate_signing_payload(nonce, *chain_id, f_name, args);
268            let fuels_sig = fuels::crypto::Signature::from_bytes(self.bits);
269            let recovered_pub_key = fuels_sig.recover(&fuel_message)?;
270
271            if *recovered_pub_key.hash() != address.as_ref() {
272                anyhow::bail!("invalid fuel address provided");
273            }
274
275            fuels_sig.verify(&recovered_pub_key, &fuel_message)?;
276        }
277
278        Ok(())
279    }
280
281    fn verify_session<T>(
282        &self,
283        nonce: u64,
284        args: T,
285        address: &Self::Address,
286    ) -> anyhow::Result<()>
287    where
288        T: Tokenizable,
289    {
290        let address = &address.0;
291        let fuel_message = generate_session_signing_payload(nonce, args);
292
293        let fuels_sig = fuels::crypto::Signature::from_bytes(self.bits);
294        let recovered_pub_key = fuels_sig.recover(&fuel_message)?;
295
296        if *recovered_pub_key.hash() != address.as_ref() {
297            anyhow::bail!("invalid fuel address provided");
298        }
299
300        fuels_sig.verify(&recovered_pub_key, &fuel_message)?;
301
302        Ok(())
303    }
304
305    fn verify_typed<T>(
306        &self,
307        args: T,
308        domain: &Domain,
309        address: &Self::Address,
310    ) -> anyhow::Result<()>
311    where
312        T: SRC16Encode,
313    {
314        let address = &address.0;
315
316        // Encode the typed data
317        let message: Message = Message::from_bytes(domain.encode(args));
318
319        // Check if this is an EVM address or Fuel address based on padding
320        if address.starts_with([0u8; EVM_ADDRESS_PADDING_LENGTH].as_ref()) {
321            // EVM (Ethereum) address verification
322            let evm_address = &address[EVM_ADDRESS_PADDING_LENGTH..];
323            let evm_sig = from_compact_or_standard(&self.bits)?;
324            let recovered_address =
325                evm_sig.recover(RecoveryMessage::Hash((*message).into()))?;
326
327            if recovered_address.as_bytes() != evm_address {
328                anyhow::bail!("invalid evm address provided");
329            }
330
331            evm_sig
332                .verify(RecoveryMessage::Hash((*message).into()), recovered_address)?;
333        } else {
334            // Fuel address verification
335            let fuels_sig = fuels::crypto::Signature::from_bytes(self.bits);
336            let recovered_pub_key = fuels_sig.recover(&message)?;
337
338            if *recovered_pub_key.hash() != address.as_ref() {
339                anyhow::bail!("invalid fuel address provided");
340            }
341
342            fuels_sig.verify(&recovered_pub_key, &message)?;
343        }
344
345        Ok(())
346    }
347}
348
349pub enum Address {
350    Secp256k1(Secp256k1Address),
351    Secp256r1,
352    Ed25519,
353}
354
355impl From<fuels::types::Address> for Address {
356    fn from(addr: fuels::types::Address) -> Self {
357        Address::Secp256k1(Secp256k1Address(addr))
358    }
359}
360
361impl Signature {
362    pub fn verify_owner<T>(
363        &self,
364        nonce: u64,
365        chain_id: ChainId,
366        f_name: String,
367        args: Option<T>,
368        address: &Address,
369    ) -> anyhow::Result<()>
370    where
371        T: Tokenizable,
372    {
373        match (self, address) {
374            (Signature::Secp256k1(sig), Address::Secp256k1(addr)) => {
375                sig.verify_owner(nonce, chain_id, f_name, args, addr)
376            }
377            (Signature::Secp256r1(_), Address::Secp256r1) => {
378                anyhow::bail!(
379                    "owner signature verification not supported for p256 signatures"
380                )
381            }
382            (Signature::Ed25519(_), Address::Ed25519) => {
383                anyhow::bail!(
384                    "owner signature verification not supported for ed25519 signatures"
385                )
386            }
387            _ => anyhow::bail!("signature and address type mismatch"),
388        }
389    }
390
391    pub fn verify_session<T>(
392        &self,
393        nonce: u64,
394        args: T,
395        address: &Address,
396    ) -> anyhow::Result<()>
397    where
398        T: Tokenizable,
399    {
400        match (self, address) {
401            (Signature::Secp256k1(sig), Address::Secp256k1(addr)) => {
402                sig.verify_session(nonce, args, addr)
403            }
404            (Signature::Secp256r1(_), Address::Secp256r1) => {
405                anyhow::bail!(
406                    "session signature verification not supported for p256 signatures"
407                )
408            }
409            (Signature::Ed25519(_), Address::Ed25519) => {
410                anyhow::bail!(
411                    "session signature verification not supported for ed25519 signatures"
412                )
413            }
414            _ => anyhow::bail!("signature and address type mismatch"),
415        }
416    }
417
418    pub fn verify_typed<T>(
419        &self,
420        args: T,
421        domain: &Domain,
422        address: &Address,
423    ) -> anyhow::Result<()>
424    where
425        T: SRC16Encode,
426    {
427        match (self, address) {
428            (Signature::Secp256k1(sig), Address::Secp256k1(addr)) => {
429                sig.verify_typed(args, domain, addr)
430            }
431            (Signature::Secp256r1(_), Address::Secp256r1) => {
432                anyhow::bail!(
433                    "typed signature verification not supported for p256 signatures"
434                )
435            }
436            (Signature::Ed25519(_), Address::Ed25519) => {
437                anyhow::bail!(
438                    "typed signature verification not supported for ed25519 signatures"
439                )
440            }
441            _ => anyhow::bail!("signature and address type mismatch"),
442        }
443    }
444}
445
446/// The Keccak256 hash of the type SessionArgs as UTF8 encoded bytes.
447///
448/// "SessionArgs(u64 nonce,b256 session_id,u64 expiry,contractId[] contract_ids)"
449pub const SRC_16_SIGNED_SESSION_TYPE_HASH: &str =
450    "0x2eb25d47a049c7084035785d680582656395f354f9484e7e3bbb2993368e46b7";
451
452/// The Keccak256 hash of the type SessionArgs as UTF8 encoded bytes.
453///
454/// "SessionArgs(uint64 nonce,bytes32 session_id,uint64 expiry,bytes32[] contract_ids)"
455pub const EIP_712_SIGNED_SESSION_TYPE_HASH: &str =
456    "0xf91528e233badfd402ad99c4d1dd9d109ec218093c9dcf7432699f5bb94721e2";
457
458impl SRC16Encode for SessionArgs {
459    fn type_hash(encoding: Encoding) -> [u8; 32] {
460        match encoding {
461            Encoding::SRC16 => {
462                Bits256::from_hex_str(SRC_16_SIGNED_SESSION_TYPE_HASH)
463                    .unwrap()
464                    .0
465            }
466            Encoding::EIP712 => {
467                Bits256::from_hex_str(EIP_712_SIGNED_SESSION_TYPE_HASH)
468                    .unwrap()
469                    .0
470            }
471        }
472    }
473    fn struct_hash(self, encoding: Encoding) -> [u8; 32] {
474        let mut tokens: Vec<u8> = Vec::new();
475        match encoding {
476            Encoding::SRC16 => tokens.extend_from_slice(
477                Bits256::from_hex_str(SRC_16_SIGNED_SESSION_TYPE_HASH)
478                    .unwrap()
479                    .0
480                    .as_slice(),
481            ),
482            Encoding::EIP712 => tokens.extend_from_slice(
483                Bits256::from_hex_str(EIP_712_SIGNED_SESSION_TYPE_HASH)
484                    .unwrap()
485                    .0
486                    .as_slice(),
487            ),
488        }
489
490        // nonce u64
491        tokens.extend_from_slice(0u64.to_be_bytes().as_slice());
492        tokens.extend_from_slice(0u64.to_be_bytes().as_slice());
493        tokens.extend_from_slice(0u64.to_be_bytes().as_slice());
494        tokens.extend_from_slice(self.nonce.to_be_bytes().as_slice());
495        // session id b256
496        match self.session_id {
497            Identity::Address(bits) => tokens.extend_from_slice(bits.as_slice()),
498            Identity::ContractId(bits) => tokens.extend_from_slice(bits.as_slice()),
499        }
500        // expiry u64
501        tokens.extend_from_slice(0u64.to_be_bytes().as_slice());
502        tokens.extend_from_slice(0u64.to_be_bytes().as_slice());
503        tokens.extend_from_slice(0u64.to_be_bytes().as_slice());
504        tokens.extend_from_slice(self.expiry.unix.to_be_bytes().as_slice());
505
506        // contract ids b256[]
507        let contract_id_bytes: Vec<u8> = self
508            .contract_ids
509            .iter()
510            .flat_map(|id| id.as_ref())
511            .copied()
512            .collect();
513
514        tokens.extend_from_slice(keccak256(contract_id_bytes).as_slice());
515
516        keccak256(tokens)
517    }
518}
519
520/// The Keccak256 hash of the type WithdrawArgs as UTF8 encoded bytes.
521///
522/// "WithdrawArgs(u64 nonce,identity to,u64 amount,assetId asset_id)"
523pub const SRC_16_SIGNED_WITHDRAW_TYPE_HASH: &str =
524    "0x9732cd3b83bcf964465644c9c8546dff5d9f2e5c16c077dba9a2a2bc2b9f7188";
525
526/// The Keccak256 hash of the type WithdrawArgs as UTF8 encoded bytes.
527///
528/// "WithdrawArgs(uint64 nonce,bytes32 to,uint64 amount,bytes32 asset_id)"
529pub const EIP_712_SIGNED_WITHDRAW_TYPE_HASH: &str =
530    "0x138ab031e51a15ab8c1d21d9a203f6cc5c134d7dbb44a9fd3f6acc3231af1c32";
531
532impl SRC16Encode for &WithdrawArgs {
533    fn type_hash(encoding: Encoding) -> [u8; 32] {
534        match encoding {
535            Encoding::SRC16 => {
536                Bits256::from_hex_str(SRC_16_SIGNED_WITHDRAW_TYPE_HASH)
537                    .unwrap()
538                    .0
539            }
540            Encoding::EIP712 => {
541                Bits256::from_hex_str(EIP_712_SIGNED_WITHDRAW_TYPE_HASH)
542                    .unwrap()
543                    .0
544            }
545        }
546    }
547    fn struct_hash(self, encoding: Encoding) -> [u8; 32] {
548        let mut tokens: Vec<u8> = Vec::new();
549        match encoding {
550            Encoding::SRC16 => tokens.extend_from_slice(
551                Bits256::from_hex_str(SRC_16_SIGNED_WITHDRAW_TYPE_HASH)
552                    .unwrap()
553                    .0
554                    .as_slice(),
555            ),
556            Encoding::EIP712 => tokens.extend_from_slice(
557                Bits256::from_hex_str(EIP_712_SIGNED_WITHDRAW_TYPE_HASH)
558                    .unwrap()
559                    .0
560                    .as_slice(),
561            ),
562        }
563
564        tokens.extend_from_slice(0u64.to_be_bytes().as_slice());
565        tokens.extend_from_slice(0u64.to_be_bytes().as_slice());
566        tokens.extend_from_slice(0u64.to_be_bytes().as_slice());
567        tokens.extend_from_slice(self.nonce.to_be_bytes().as_slice());
568        match self.to {
569            Identity::Address(bits) => tokens.extend_from_slice(bits.as_slice()),
570            Identity::ContractId(bits) => tokens.extend_from_slice(bits.as_slice()),
571        }
572        tokens.extend_from_slice(0u64.to_be_bytes().as_slice());
573        tokens.extend_from_slice(0u64.to_be_bytes().as_slice());
574        tokens.extend_from_slice(0u64.to_be_bytes().as_slice());
575        tokens.extend_from_slice(self.amount.to_be_bytes().as_slice());
576        tokens.extend_from_slice(self.asset_id.as_slice());
577
578        keccak256(tokens)
579    }
580}
581
582/// The Keccak256 hash of the type CallParams as UTF8 encoded bytes.
583///
584/// "CallParams(u64 coins,assetId asset_id,u64 gas)"
585pub const SRC_16_SIGNED_CALL_PARAMS_TYPE_HASH: &str =
586    "0x05816efb1220d5dd49f0abf9882712729285e7080da3dc2ba051a6ab701cf3b8";
587
588/// The Keccak256 hash of the type CallParams as UTF8 encoded bytes.
589///
590/// "CallParams(uint64 coins,bytes32 asset_id,uint64 gas)"
591pub const EIP_712_SIGNED_CALL_PARAMS_TYPE_HASH: &str =
592    "0x322b030cfd61eddd3d0acc5c37358539477197bfc2599dc09a9c75ad47f6e5b8";
593
594impl SRC16Encode for CallParams {
595    fn type_hash(encoding: Encoding) -> [u8; 32] {
596        match encoding {
597            Encoding::SRC16 => {
598                Bits256::from_hex_str(SRC_16_SIGNED_CALL_PARAMS_TYPE_HASH)
599                    .unwrap()
600                    .0
601            }
602            Encoding::EIP712 => {
603                Bits256::from_hex_str(EIP_712_SIGNED_CALL_PARAMS_TYPE_HASH)
604                    .unwrap()
605                    .0
606            }
607        }
608    }
609    fn struct_hash(self, encoding: Encoding) -> [u8; 32] {
610        let mut tokens: Vec<u8> = Vec::new();
611        match encoding {
612            Encoding::SRC16 => tokens.extend_from_slice(
613                Bits256::from_hex_str(SRC_16_SIGNED_CALL_PARAMS_TYPE_HASH)
614                    .unwrap()
615                    .0
616                    .as_slice(),
617            ),
618            Encoding::EIP712 => tokens.extend_from_slice(
619                Bits256::from_hex_str(EIP_712_SIGNED_CALL_PARAMS_TYPE_HASH)
620                    .unwrap()
621                    .0
622                    .as_slice(),
623            ),
624        }
625
626        // Call Params
627        tokens.extend_from_slice(0u64.to_be_bytes().as_slice());
628        tokens.extend_from_slice(0u64.to_be_bytes().as_slice());
629        tokens.extend_from_slice(0u64.to_be_bytes().as_slice());
630        tokens.extend_from_slice(self.coins.to_be_bytes().as_slice());
631        tokens.extend_from_slice(self.asset_id.as_slice());
632        tokens.extend_from_slice(0u64.to_be_bytes().as_slice());
633        tokens.extend_from_slice(0u64.to_be_bytes().as_slice());
634        tokens.extend_from_slice(0u64.to_be_bytes().as_slice());
635        tokens.extend_from_slice(self.gas.to_be_bytes().as_slice());
636
637        keccak256(tokens)
638    }
639}
640
641/// The Keccak256 hash of the type CallContractArg as UTF8 encoded bytes.
642///
643/// "CallContractArg(contractId contract_id,bytes function_selector,CallParams call_params,bytes call_data)CallParams(u64 coins,assetId asset_id,u64 gas)"
644pub const SRC_16_SIGNED_CALL_CONTRACT_ARG_TYPE_HASH: &str =
645    "0xc8ac163d1f87886f48788901b8e5eb4eebb3edad4ae111859b2a051059920048";
646
647/// The Keccak256 hash of the type CallContractArg as UTF8 encoded bytes.
648///
649/// "CallContractArg(bytes32 contract_id,bytes function_selector,CallParams call_params,bytes call_data)CallParams(uint64 coins,bytes32 asset_id,uint64 gas)"
650pub const EIP_712_SIGNED_CALL_CONTRACT_ARG_TYPE_HASH: &str =
651    "0x325b44fd679189ccd8f708a440a27448cbfd6f47f90205e8ace3f1efc27c92d6";
652
653impl SRC16Encode for CallContractArg {
654    fn type_hash(encoding: Encoding) -> [u8; 32] {
655        match encoding {
656            Encoding::SRC16 => {
657                Bits256::from_hex_str(SRC_16_SIGNED_CALL_CONTRACT_ARG_TYPE_HASH)
658                    .unwrap()
659                    .0
660            }
661            Encoding::EIP712 => {
662                Bits256::from_hex_str(EIP_712_SIGNED_CALL_CONTRACT_ARG_TYPE_HASH)
663                    .unwrap()
664                    .0
665            }
666        }
667    }
668    fn struct_hash(self, encoding: Encoding) -> [u8; 32] {
669        let mut tokens: Vec<u8> = Vec::new();
670        match encoding {
671            Encoding::SRC16 => tokens.extend_from_slice(
672                Bits256::from_hex_str(SRC_16_SIGNED_CALL_CONTRACT_ARG_TYPE_HASH)
673                    .unwrap()
674                    .0
675                    .as_slice(),
676            ),
677            Encoding::EIP712 => tokens.extend_from_slice(
678                Bits256::from_hex_str(EIP_712_SIGNED_CALL_CONTRACT_ARG_TYPE_HASH)
679                    .unwrap()
680                    .0
681                    .as_slice(),
682            ),
683        }
684
685        // Contract Id
686        tokens.extend_from_slice(self.contract_id.as_slice());
687
688        // Function selector
689        tokens.extend_from_slice(keccak256(self.function_selector.0).as_slice());
690
691        // Call Params
692        tokens.extend_from_slice(self.call_params.struct_hash(encoding).as_slice());
693
694        // Call data
695        match self.call_data {
696            Some(data) => tokens.extend_from_slice(keccak256(data.0).as_slice()),
697            None => tokens.extend_from_slice(keccak256(Vec::new()).as_slice()),
698        }
699
700        keccak256(tokens)
701    }
702}
703
704/// The Keccak256 hash of the type CallContractArgs as UTF8 encoded bytes.
705///
706/// "CallContractArgs(u64 nonce,CallContractArg call_contract_args)CallContractArg(contractId contract_id,bytes function_selector,CallParams call_params,bytes call_data)CallParams(u64 coins,assetId asset_id,u64 gas)"
707pub const SRC_16_SIGNED_CALL_CONTRACT_TYPE_HASH: &str =
708    "0x3f9265dacb0b84ea31e56285f5757a2200e743f27f0b27c3d58db09c9f62cb46";
709
710/// The Keccak256 hash of the type CallContractArgs as UTF8 encoded bytes.
711///
712/// "CallContractArgs(uint64 nonce,CallContractArg call_contract_args)CallContractArg(bytes32 contract_id,bytes function_selector,CallParams call_params,bytes call_data)CallParams(uint64 coins,bytes32 asset_id,uint64 gas)"
713pub const EIP_712_SIGNED_CALL_CONTRACT_TYPE_HASH: &str =
714    "0x26ade4c76b3df03eed7d7a2d1387896e8717f8a862414199904b8a4455b25699";
715
716impl SRC16Encode for TradeAccountCallContractArgs {
717    fn type_hash(encoding: Encoding) -> [u8; 32] {
718        match encoding {
719            Encoding::SRC16 => {
720                Bits256::from_hex_str(SRC_16_SIGNED_CALL_CONTRACT_TYPE_HASH)
721                    .unwrap()
722                    .0
723            }
724            Encoding::EIP712 => {
725                Bits256::from_hex_str(EIP_712_SIGNED_CALL_CONTRACT_TYPE_HASH)
726                    .unwrap()
727                    .0
728            }
729        }
730    }
731    fn struct_hash(self, encoding: Encoding) -> [u8; 32] {
732        let mut tokens: Vec<u8> = Vec::new();
733        match encoding {
734            Encoding::SRC16 => tokens.extend_from_slice(
735                Bits256::from_hex_str(SRC_16_SIGNED_CALL_CONTRACT_TYPE_HASH)
736                    .unwrap()
737                    .0
738                    .as_slice(),
739            ),
740            Encoding::EIP712 => tokens.extend_from_slice(
741                Bits256::from_hex_str(EIP_712_SIGNED_CALL_CONTRACT_TYPE_HASH)
742                    .unwrap()
743                    .0
744                    .as_slice(),
745            ),
746        }
747
748        // Nonce
749        tokens.extend_from_slice(0u64.to_be_bytes().as_slice());
750        tokens.extend_from_slice(0u64.to_be_bytes().as_slice());
751        tokens.extend_from_slice(0u64.to_be_bytes().as_slice());
752        tokens.extend_from_slice(self.nonce.to_be_bytes().as_slice());
753
754        // Call Contract args
755        tokens
756            .extend_from_slice(self.call_contract_args.struct_hash(encoding).as_slice());
757
758        keccak256(tokens)
759    }
760}
761
762/// The Keccak256 hash of the type MultiCallContractArgs as UTF8 encoded bytes.
763///
764/// "MultiCallContractArgs(u64 nonce,CallContractArg[] call_contract_args)CallContractArg(contractId contract_id,bytes function_selector,CallParams call_params,bytes call_data)CallParams(u64 coins,assetId asset_id,u64 gas)"
765pub const SRC_16_SIGNED_MULTI_CALL_CONTRACT_TYPE_HASH: &str =
766    "0x44c30cb1e12dbc44e06a72b087359f9af5caae554d43dada454d5f6619eabf5f";
767
768/// The Keccak256 hash of the type MultiCallContractArgs as UTF8 encoded bytes.
769///
770/// "MultiCallContractArgs(uint64 nonce,CallContractArg[] call_contract_args)CallContractArg(bytes32 contract_id,bytes function_selector,CallParams call_params,bytes call_data)CallParams(uint64 coins,bytes32 asset_id,uint64 gas)"
771pub const EIP_712_SIGNED_MULTI_CALL_CONTRACT_TYPE_HASH: &str =
772    "0x292b2913cef817f041ea01e3b02c4dd2b7184e6490f21a84ece8443e406640a1";
773
774impl SRC16Encode for MultiCallContractArgs {
775    fn type_hash(encoding: Encoding) -> [u8; 32] {
776        match encoding {
777            Encoding::SRC16 => {
778                Bits256::from_hex_str(SRC_16_SIGNED_MULTI_CALL_CONTRACT_TYPE_HASH)
779                    .unwrap()
780                    .0
781            }
782            Encoding::EIP712 => {
783                Bits256::from_hex_str(EIP_712_SIGNED_MULTI_CALL_CONTRACT_TYPE_HASH)
784                    .unwrap()
785                    .0
786            }
787        }
788    }
789    fn struct_hash(self, encoding: Encoding) -> [u8; 32] {
790        let mut tokens: Vec<u8> = Vec::new();
791        match encoding {
792            Encoding::SRC16 => tokens.extend_from_slice(
793                Bits256::from_hex_str(SRC_16_SIGNED_MULTI_CALL_CONTRACT_TYPE_HASH)
794                    .unwrap()
795                    .0
796                    .as_slice(),
797            ),
798            Encoding::EIP712 => tokens.extend_from_slice(
799                Bits256::from_hex_str(EIP_712_SIGNED_MULTI_CALL_CONTRACT_TYPE_HASH)
800                    .unwrap()
801                    .0
802                    .as_slice(),
803            ),
804        }
805
806        // Nonce
807        tokens.extend_from_slice(0u64.to_be_bytes().as_slice());
808        tokens.extend_from_slice(0u64.to_be_bytes().as_slice());
809        tokens.extend_from_slice(0u64.to_be_bytes().as_slice());
810        tokens.extend_from_slice(self.nonce.to_be_bytes().as_slice());
811
812        // Contract call params
813        let mut call_buffer: Vec<u8> = Vec::new();
814        for call in self.call_contract_args {
815            let h = call.struct_hash(encoding.clone());
816            call_buffer.extend_from_slice(h.as_slice());
817        }
818        tokens.extend_from_slice(keccak256(call_buffer.as_slice()).as_slice());
819
820        keccak256(tokens)
821    }
822}
823
824/// The Keccak256 hash of the type RevokeArgs as UTF8 encoded bytes.
825///
826/// "RevokeArgs(u64 nonce)"
827pub const SRC_16_REVOKE_TYPE_HASH: &str =
828    "0x97c9c74c84314e9fcc4ba97ad28e9cde1eda9b79b0229b54219de0e79560ac86";
829
830/// The Keccak256 hash of the type RevokeArgs as UTF8 encoded bytes.
831///
832/// "RevokeArgs(uint64 nonce)"
833pub const EIP_712_REVOKE_TYPE_HASH: &str =
834    "0xf863f4b733d291f6bd744df3ba070a394dd83fced6f038a22195c1b34009dabb";
835
836impl SRC16Encode for RevokeArgs {
837    fn type_hash(encoding: Encoding) -> [u8; 32] {
838        match encoding {
839            Encoding::SRC16 => Bits256::from_hex_str(SRC_16_REVOKE_TYPE_HASH).unwrap().0,
840            Encoding::EIP712 => {
841                Bits256::from_hex_str(EIP_712_REVOKE_TYPE_HASH).unwrap().0
842            }
843        }
844    }
845    fn struct_hash(self, encoding: Encoding) -> [u8; 32] {
846        let mut tokens: Vec<u8> = Vec::new();
847        match encoding {
848            Encoding::SRC16 => tokens.extend_from_slice(
849                Bits256::from_hex_str(SRC_16_REVOKE_TYPE_HASH)
850                    .unwrap()
851                    .0
852                    .as_slice(),
853            ),
854            Encoding::EIP712 => tokens.extend_from_slice(
855                Bits256::from_hex_str(EIP_712_REVOKE_TYPE_HASH)
856                    .unwrap()
857                    .0
858                    .as_slice(),
859            ),
860        }
861
862        tokens.extend_from_slice(0u64.to_be_bytes().as_slice());
863        tokens.extend_from_slice(0u64.to_be_bytes().as_slice());
864        tokens.extend_from_slice(0u64.to_be_bytes().as_slice());
865        tokens.extend_from_slice(self.nonce.to_be_bytes().as_slice());
866
867        keccak256(tokens)
868    }
869}
870
871/// The Keccak256 hash of the type SetProxyTargetArgs as UTF8 encoded bytes.
872///
873/// "SetProxyTargetArgs(u64 nonce)"
874pub const SRC_16_PROXY_TARGET_TYPE_HASH: &str =
875    "0x523db1f70cbdfc0982627ed4e52dc429384c32e94e16ddc5fa5ab921035d8d6d";
876
877/// The Keccak256 hash of the type RevokeArgs as UTF8 encoded bytes.
878///
879/// "SetProxyTargetArgs(uint64 nonce)"
880pub const EIP_712_PROXY_TARGET_TYPE_HASH: &str =
881    "0xf12c7a18e931f36d11f09bd52f37183c4993a3a509ac0c2c6cec459cee7a004d";
882
883impl SRC16Encode for SetProxyTargetArgs {
884    fn type_hash(encoding: Encoding) -> [u8; 32] {
885        match encoding {
886            Encoding::SRC16 => {
887                Bits256::from_hex_str(SRC_16_PROXY_TARGET_TYPE_HASH)
888                    .unwrap()
889                    .0
890            }
891            Encoding::EIP712 => {
892                Bits256::from_hex_str(EIP_712_PROXY_TARGET_TYPE_HASH)
893                    .unwrap()
894                    .0
895            }
896        }
897    }
898    fn struct_hash(self, encoding: Encoding) -> [u8; 32] {
899        let mut tokens: Vec<u8> = Vec::new();
900        match encoding {
901            Encoding::SRC16 => tokens.extend_from_slice(
902                Bits256::from_hex_str(SRC_16_PROXY_TARGET_TYPE_HASH)
903                    .unwrap()
904                    .0
905                    .as_slice(),
906            ),
907            Encoding::EIP712 => tokens.extend_from_slice(
908                Bits256::from_hex_str(EIP_712_PROXY_TARGET_TYPE_HASH)
909                    .unwrap()
910                    .0
911                    .as_slice(),
912            ),
913        }
914
915        tokens.extend_from_slice(0u64.to_be_bytes().as_slice());
916        tokens.extend_from_slice(0u64.to_be_bytes().as_slice());
917        tokens.extend_from_slice(0u64.to_be_bytes().as_slice());
918        tokens.extend_from_slice(self.nonce.to_be_bytes().as_slice());
919
920        keccak256(tokens)
921    }
922}