sails-sol-gen 2.0.0

Solidity contract generator for the Sails framework
Documentation
use insta::assert_snapshot;
use sails_sol_gen::{SolidityFile, generate_solidity_contract};

const SIMPLE_IDL: &str = r#"
program TestProgram {
    constructors {
        CreatePrg();
    }
    services {
        Svc1: Svc1
    }
}

service Svc1 {
    functions {
        DoThis(p1: u32, p2: String) -> u32;
    }
}
"#;

const IDL_W_EVENTS: &str = r#"
program TestProgram {
    constructors {
        Create();
    }
    services {
        Svc1: Svc1
    }
}

service Svc1 {
    functions {
        DoThis(p1: u32, p2: String) -> u32;
    }
    events {
        DoThisEvent { p1: u32, p2: String },
        DoThisEvent2(u32, String),
    }
}
"#;

#[test]
fn test_generate_simple_contract() {
    let contract = generate_solidity_contract("TestContract", SIMPLE_IDL, SolidityFile::SingleFile);

    assert!(
        contract.is_ok(),
        "Failed to generate contract: {:?}",
        contract.err()
    );
    assert_snapshot!(String::from_utf8(contract.unwrap()).unwrap());
}

#[test]
fn test_generate_contract_w_events() {
    let contract =
        generate_solidity_contract("TestContract", IDL_W_EVENTS, SolidityFile::SingleFile);

    assert!(
        contract.is_ok(),
        "Failed to generate contract: {:?}",
        contract.err()
    );
    assert_snapshot!(String::from_utf8(contract.unwrap()).unwrap());
}

const IDL_MIXED_INDEXED: &str = r#"
program TestProgram {
    constructors {
        Create();
    }
    services {
        Svc: Svc
    }
}

service Svc {
    events {
        MixedEvent {
            @indexed
            f1: u32,
            f2: String,
            @indexed
            f3: u128,
            f4: u128
        }
    }
}
"#;

#[test]
fn test_generate_contract_w_mixed_indexed_events() {
    let contract =
        generate_solidity_contract("TestContract", IDL_MIXED_INDEXED, SolidityFile::SingleFile);

    assert!(
        contract.is_ok(),
        "Failed to generate contract: {:?}",
        contract.err()
    );
    assert_snapshot!(String::from_utf8(contract.unwrap()).unwrap());
}

const PAYABLE_IDL: &str = r#"
program TestProgram {
    constructors {
        @payable
        Create();
    }
    services {
        MyService
    }
}

service MyService {
    functions {
        @payable
        Deposit() -> ();

        @returns_value
        Withdraw() -> u128;

        @payable
        @returns_value
        SwapAndRefund() -> u128;

        @query
        RegularCall() -> bool;
    }
}
"#;

#[test]
fn test_generate_payable_contract() {
    let contract =
        generate_solidity_contract("PayableContract", PAYABLE_IDL, SolidityFile::SingleFile);

    assert!(
        contract.is_ok(),
        "Failed to generate contract: {:?}",
        contract.err()
    );
    assert_snapshot!(String::from_utf8(contract.unwrap()).unwrap());
}

const IDL_W_ADDRESS: &str = r#"
program TokenProgram {
    constructors {
        New();
    }
    services {
        TokenSvc: TokenSvc
    }
}

service TokenSvc {
    functions {
        Transfer(from: Address, to: Address, amount: u128) -> bool;
        BalanceOf(owner: Address) -> u128;
        Batch(recipients: [Address]) -> [Address];
        Fixed(addrs: [Address; 3]) -> [Address; 3];
    }

    types {
        @sol_type: address
        struct Address(H160);
    }
}
"#;

#[test]
fn test_generate_contract_w_address_type() {
    let contract =
        generate_solidity_contract("TokenContract", IDL_W_ADDRESS, SolidityFile::SingleFile);

    assert!(
        contract.is_ok(),
        "Failed to generate contract: {:?}",
        contract.err()
    );
    assert_snapshot!(String::from_utf8(contract.unwrap()).unwrap());
}

#[test]
fn codec_selection() {
    let idl = r#"
program CodecProgram {
    services {
        CodecTest
    }
}

service CodecTest {
    functions {
        /// Both codecs
        @entry_id: 0
        BothMethod(p1: u32) -> string;
        /// SCALE only - should be excluded from Solidity
        @entry_id: 1
        @codec: scale
        ScaleOnly(p1: u32) -> u32;
        /// Ethabi only
        @entry_id: 2
        @codec: ethabi
        EthabiOnly(p1: u32) -> u32;
        /// Payable ethabi
        @entry_id: 3
        @codec: ethabi
        @payable
        PayableEthabi(p1: u32) -> u32;
    }
    events {
        /// Both codecs
        @entry_id: 0
        BothEvent(u32),
        /// SCALE only - should be excluded from Solidity
        @entry_id: 1
        @codec: scale
        ScaleOnlyEvent(u32),
        /// Ethabi only
        @entry_id: 2
        @codec: ethabi
        EthabiOnlyEvent(u32),
    }
}
"#;

    let data = generate_solidity_contract("CodecTest", idl, SolidityFile::SingleFile)
        .expect("generate solidity contract");
    let generated = String::from_utf8(data).expect("utf8 contract");

    assert!(
        generated.contains("function codecTestBothMethod"),
        "expected codecTestBothMethod to be present"
    );
    assert!(
        generated.contains("function codecTestEthabiOnly"),
        "expected codecTestEthabiOnly to be present"
    );
    assert!(
        generated.contains("function codecTestPayableEthabi"),
        "expected codecTestPayableEthabi to be present"
    );
    assert!(
        !generated.contains("function codecTestScaleOnly"),
        "expected codecTestScaleOnly to be filtered out, got:\n{generated}"
    );
    assert!(
        generated.contains("event BothEvent"),
        "expected BothEvent to be present"
    );
    assert!(
        generated.contains("event EthabiOnlyEvent"),
        "expected EthabiOnlyEvent to be present"
    );
    assert!(
        !generated.contains("event ScaleOnlyEvent"),
        "expected ScaleOnlyEvent to be filtered out, got:\n{generated}"
    );

    assert_snapshot!(generated);
}