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