o2-tools 0.4.8-rc3

Reusable tooling for trade account and order book contract interactions on Fuel
Documentation
//! Session-bound authorization for `PropMarginPool::close_eval_session_signed`.
//!
//! The configured expiry cosigner evaluates profit and closed positions before
//! signing; any relayer may submit the authorization. `pool` is the deployed
//! pool/proxy ID. Cleanup inputs are unsigned and there is no deadline or nonce.
//! The session must remain active, the signer configured, and its pinned eval
//! policy enabled. A reverted close may be retried with the same authorization.

use fuels::{
    crypto::Message,
    types::ContractId,
};
use sha2::{
    Digest,
    Sha256,
};

const EVAL_CLOSE_COSIGN_TAG: u8 = 0x06;

fn eval_close_preimage(
    chain_id: u64,
    pool: ContractId,
    account: ContractId,
    session_id: u64,
) -> [u8; 81] {
    let mut bytes = [0; 81];
    bytes[0] = EVAL_CLOSE_COSIGN_TAG;
    bytes[1..9].copy_from_slice(&chain_id.to_be_bytes());
    bytes[9..41].copy_from_slice(pool.as_slice());
    bytes[41..73].copy_from_slice(account.as_slice());
    bytes[73..81].copy_from_slice(&session_id.to_be_bytes());
    bytes
}

/// SHA-256 of the 81-byte preimage:
/// `0x06 ++ chain_id_be_u64 ++ pool_raw32 ++ account_raw32 ++ session_id_be_u64`.
/// No length prefix, deadline, evaluation data, policy rate, or cleanup inputs.
pub fn generate_pool_eval_close_digest(
    chain_id: u64,
    pool: ContractId,
    account: ContractId,
    session_id: u64,
) -> [u8; 32] {
    Sha256::digest(eval_close_preimage(chain_id, pool, account, session_id)).into()
}

/// The digest as a signable message, without hashing it a second time.
/// Matches Sway's `Message::from(digest)` and the pool expiry signing convention.
pub fn generate_pool_eval_close_signing_payload(
    chain_id: u64,
    pool: ContractId,
    account: ContractId,
    session_id: u64,
) -> Message {
    Message::from_bytes(generate_pool_eval_close_digest(
        chain_id, pool, account, session_id,
    ))
}

#[cfg(test)]
mod tests {
    use super::*;

    #[test]
    fn encoding_and_signable_message_match_the_sway_vector() {
        let chain = 0x0102_0304_0506_0708;
        let pool = ContractId::new([0x11; 32]);
        let account = ContractId::new([0x22; 32]);
        let session = 0x1122_3344_5566_7788;
        let bytes = eval_close_preimage(chain, pool, account, session);
        assert_eq!(bytes.len(), 81);
        assert_eq!(&bytes[..9], &[6, 1, 2, 3, 4, 5, 6, 7, 8]);
        assert_eq!(&bytes[9..41], &[0x11; 32]);
        assert_eq!(&bytes[41..73], &[0x22; 32]);
        assert_eq!(
            &bytes[73..],
            &[0x11, 0x22, 0x33, 0x44, 0x55, 0x66, 0x77, 0x88]
        );
        // Independently computed SHA-256; also asserted by the Sway unit test.
        let expected = [
            0xa9, 0x02, 0xa5, 0xf7, 0x47, 0xf0, 0x64, 0xdd, 0xcb, 0xef, 0x5e, 0xd3, 0xe7,
            0x5c, 0xb1, 0x3a, 0x95, 0x8a, 0x39, 0x8f, 0x54, 0xc7, 0xb1, 0x95, 0x02, 0x00,
            0xd7, 0xbd, 0x63, 0x02, 0x9f, 0xb8,
        ];
        assert_eq!(
            generate_pool_eval_close_digest(chain, pool, account, session),
            expected
        );
        assert_eq!(
            generate_pool_eval_close_signing_payload(chain, pool, account, session),
            Message::from_bytes(expected)
        );
        assert_ne!(
            expected,
            crate::prop_force_close::generate_pool_force_close_digest(
                chain, pool, account, session,
            )
        );
    }
}