diff --git a/README.md b/README.md index 653118a..e099d01 100644 --- a/README.md +++ b/README.md @@ -159,6 +159,122 @@ The `fsc_utils` dbt package is a centralized repository consisting of various db ``` +- `utils.udf_encode_contract_call`: Encodes EVM contract function calls into ABI-encoded calldata format for eth_call RPC requests. Handles all Solidity types including tuples and arrays. + + ``` + -- Simple function with no inputs + SELECT utils.udf_encode_contract_call( + PARSE_JSON('{"name": "totalSupply", "inputs": []}'), + ARRAY_CONSTRUCT() + ); + -- Returns: 0x18160ddd + + -- Function with single address parameter + SELECT utils.udf_encode_contract_call( + PARSE_JSON('{ + "name": "balanceOf", + "inputs": [{"name": "account", "type": "address"}] + }'), + ARRAY_CONSTRUCT('0xa0b86991c6218b36c1d19d4a2e9eb0ce3606eb48') + ); + -- Returns: 0x70a08231000000000000000000000000a0b86991c6218b36c1d19d4a2e9eb0ce3606eb48 + + -- Function with multiple parameters + SELECT utils.udf_encode_contract_call( + PARSE_JSON('{ + "name": "transfer", + "inputs": [ + {"name": "to", "type": "address"}, + {"name": "amount", "type": "uint256"} + ] + }'), + ARRAY_CONSTRUCT('0x1234567890123456789012345678901234567890', 1000000) + ); + + -- Complex function with nested tuples + SELECT utils.udf_encode_contract_call( + PARSE_JSON('{ + "name": "swap", + "inputs": [{ + "name": "params", + "type": "tuple", + "components": [ + {"name": "tokenIn", "type": "address"}, + {"name": "tokenOut", "type": "address"}, + {"name": "amountIn", "type": "uint256"} + ] + }] + }'), + ARRAY_CONSTRUCT( + ARRAY_CONSTRUCT( + '0xa0b86991c6218b36c1d19d4a2e9eb0ce3606eb48', + '0xc02aaa39b223fe8d0a0e5c4f27ead9083c756cc2', + 1000000 + ) + ) + ); + ``` + +- `utils.udf_create_eth_call`: Creates an eth_call JSON-RPC request object from contract address and encoded calldata. Supports block parameter as string or number (auto-converts numbers to hex). + + ``` + -- Using default 'latest' block + SELECT utils.udf_create_eth_call( + '0xa0b86991c6218b36c1d19d4a2e9eb0ce3606eb48', + '0x70a08231000000000000000000000000a0b86991c6218b36c1d19d4a2e9eb0ce3606eb48' + ); + + -- Using specific block number (auto-converted to hex) + SELECT utils.udf_create_eth_call( + '0xa0b86991c6218b36c1d19d4a2e9eb0ce3606eb48', + '0x70a08231000000000000000000000000a0b86991c6218b36c1d19d4a2e9eb0ce3606eb48', + 18500000 + ); + ``` + +- `utils.udf_create_eth_call_from_abi`: Convenience function that combines contract call encoding and JSON-RPC request creation in a single call. Recommended for most use cases. + + ``` + -- Simple balanceOf call with default 'latest' block + SELECT utils.udf_create_eth_call_from_abi( + '0xa0b86991c6218b36c1d19d4a2e9eb0ce3606eb48', + PARSE_JSON('{ + "name": "balanceOf", + "inputs": [{"name": "account", "type": "address"}] + }'), + ARRAY_CONSTRUCT('0xbcca60bb61934080951369a648fb03df4f96263c') + ); + + -- Same call but at a specific block number + SELECT utils.udf_create_eth_call_from_abi( + '0xa0b86991c6218b36c1d19d4a2e9eb0ce3606eb48', + PARSE_JSON('{ + "name": "balanceOf", + "inputs": [{"name": "account", "type": "address"}] + }'), + ARRAY_CONSTRUCT('0xbcca60bb61934080951369a648fb03df4f96263c'), + 18500000 + ); + + -- Using ABI from a table + WITH abi_data AS ( + SELECT + abi, + '0xa0b86991c6218b36c1d19d4a2e9eb0ce3606eb48' as contract_address, + '0xbcca60bb61934080951369a648fb03df4f96263c' as user_address + FROM ethereum.silver.flat_function_abis + WHERE contract_address = LOWER('0x43506849d7c04f9138d1a2050bbf3a0c054402dd') + AND function_name = 'balanceOf' + ) + SELECT + utils.udf_create_eth_call_from_abi( + contract_address, + abi, + ARRAY_CONSTRUCT(user_address) + ) as rpc_call + FROM abi_data; + ``` + ## **Streamline V 2.0 Functions** The `Streamline V 2.0` functions are a set of macros and UDFs that are designed to be used with `Streamline V 2.0` deployments. diff --git a/macros/streamline/configs.yaml.sql b/macros/streamline/configs.yaml.sql index d46b6b4..2a1e46b 100644 --- a/macros/streamline/configs.yaml.sql +++ b/macros/streamline/configs.yaml.sql @@ -30,6 +30,18 @@ sql: | {{ fsc_utils.python_udf_hex_to_int_with_encoding() | indent(4) }} + +- name: {{ schema }}.udf_int_to_hex + signature: + - [int, NUMBER] + return_type: VARCHAR(16777216) + options: | + NULL + LANGUAGE SQL + STRICT IMMUTABLE + sql: | + SELECT CONCAT('0x', TRIM(TO_CHAR(int, 'XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX'))) + - name: {{ schema }}.udf_hex_to_string signature: - [hex, STRING] @@ -303,7 +315,7 @@ RUNTIME_VERSION = '3.10' PACKAGES = ('eth-abi') HANDLER = 'encode_call' - COMMENT = '{{ fsc_utils.udf_encode_contract_call_comment() }}' + COMMENT = 'Encodes EVM contract function calls into ABI-encoded calldata format for eth_call RPC requests. Handles all Solidity types including tuples and arrays.' sql: | {{ fsc_utils.create_udf_encode_contract_call() | indent(4) }} @@ -367,7 +379,7 @@ NULL LANGUAGE SQL STRICT IMMUTABLE - COMMENT = '{{ fsc_utils.udf_create_eth_call_from_abi_comment() }}' + COMMENT = 'Convenience function that combines contract call encoding and JSON-RPC request creation for eth_call. Encodes function call from ABI and creates RPC request with default block parameter "latest".' sql: | {{ schema }}.udf_create_eth_call( contract_address, @@ -385,7 +397,7 @@ NULL LANGUAGE SQL STRICT IMMUTABLE - COMMENT = '{{ fsc_utils.udf_create_eth_call_from_abi_comment() }}' + COMMENT = 'Convenience function that combines contract call encoding and JSON-RPC request creation for eth_call. Encodes function call from ABI and creates RPC request with specified block parameter.' sql: | {{ schema }}.udf_create_eth_call( contract_address, diff --git a/macros/streamline/functions.py.sql b/macros/streamline/functions.py.sql index 9843275..ac9b2e0 100644 --- a/macros/streamline/functions.py.sql +++ b/macros/streamline/functions.py.sql @@ -1010,233 +1010,3 @@ def encode_call(function_abi, input_values): }) {% endmacro %} - -{% macro udf_encode_contract_call_comment() %} -Encodes EVM contract function calls into hex calldata format for eth_call RPC requests. - -PURPOSE: - Converts human-readable function parameters into ABI-encoded calldata that can be sent - to Ethereum nodes via JSON-RPC. Handles all Solidity types including complex nested - structures like tuples and arrays. - -PARAMETERS: - function_abi (VARIANT): - - JSON object containing the function ABI definition - - Must include: "name" (string) and "inputs" (array of input definitions) - - Each input needs: "name", "type", and optionally "components" for tuples - - input_values (ARRAY): - - Array of values matching the function inputs in order - - Values should be provided as native Snowflake types: - * addresses: strings (with or without 0x prefix) - * uint/int: numbers - * bool: booleans - * bytes/bytes32: hex strings (with or without 0x prefix) - * arrays: Snowflake arrays - * tuples: Snowflake arrays in component order - -RETURNS: - STRING: Complete calldata as hex string with 0x prefix - - Format: 0x{4-byte selector}{encoded parameters} - - Can be used directly in eth_call RPC requests - - Returns JSON error object if encoding fails - -EXAMPLES: - - -- Simple function with no inputs - SELECT utils.udf_encode_contract_call( - PARSE_JSON(''{"name": "totalSupply", "inputs": []}''), - ARRAY_CONSTRUCT() - ); - -- Returns: 0x18160ddd - - -- Function with single address parameter - SELECT utils.udf_encode_contract_call( - PARSE_JSON(''{ - "name": "balanceOf", - "inputs": [{"name": "account", "type": "address"}] - }''), - ARRAY_CONSTRUCT(''0xa0b86991c6218b36c1d19d4a2e9eb0ce3606eb48'') - ); - -- Returns: 0x70a08231000000000000000000000000a0b86991c6218b36c1d19d4a2e9eb0ce3606eb48 - - -- Function with multiple parameters - SELECT utils.udf_encode_contract_call( - PARSE_JSON(''{ - "name": "transfer", - "inputs": [ - {"name": "to", "type": "address"}, - {"name": "amount", "type": "uint256"} - ] - }''), - ARRAY_CONSTRUCT(''0x1234567890123456789012345678901234567890'', 1000000) - ); - - -- Complex function with nested tuples - SELECT utils.udf_encode_contract_call( - PARSE_JSON(''{ - "name": "swap", - "inputs": [{ - "name": "params", - "type": "tuple", - "components": [ - {"name": "tokenIn", "type": "address"}, - {"name": "tokenOut", "type": "address"}, - {"name": "amountIn", "type": "uint256"} - ] - }] - }''), - ARRAY_CONSTRUCT( - ARRAY_CONSTRUCT( - ''0xa0b86991c6218b36c1d19d4a2e9eb0ce3606eb48'', - ''0xc02aaa39b223fe8d0a0e5c4f27ead9083c756cc2'', - 1000000 - ) - ) - ); - -TYPICAL WORKFLOW: - 1. Get function ABI from crosschain.evm.dim_contract_abis - 2. Prepare input values as Snowflake arrays - 3. Encode using this function - 4. Execute via eth_call RPC (live.udf_api) - 5. Decode response using utils.udf_evm_decode_trace - -SUPPORTED TYPES: - - address: Ethereum addresses - - uint8, uint16, ..., uint256: Unsigned integers - - int8, int16, ..., int256: Signed integers - - bool: Boolean values - - bytes, bytes1, ..., bytes32: Fixed and dynamic byte arrays - - string: Dynamic strings - - Arrays: Any type followed by [] - - Tuples: Nested structures with components - - Nested combinations: tuple[], tuple[][], etc. - -NOTES: - - Function selector is automatically calculated using Keccak256 - - Compatible with existing utils.udf_evm_text_signature and utils.udf_keccak256 - - Handles gas-optimized function names (e.g., selector 0x00000000) - - Tuples must be provided as arrays in component order - - Empty arrays are valid for array-type parameters - -ERROR HANDLING: - - Returns JSON error object on failure - - Check if result starts with "{" to detect errors - - Error object includes: error message, traceback, function name, types - -RELATED FUNCTIONS: - - utils.udf_evm_text_signature: Generate function signature - - utils.udf_keccak256: Calculate function selector - - utils.udf_evm_decode_trace: Decode call results - -{% endmacro %} - -{% macro udf_create_eth_call_from_abi_comment() %} -Convenience function that combines contract call encoding and JSON-RPC request creation for eth_call. - -PURPOSE: - Simplifies the workflow of creating eth_call JSON-RPC requests by combining ABI encoding - and RPC call construction into a single function call. This is the recommended approach for - most use cases where you want to query contract state via eth_call. - -PARAMETERS: - contract_address (STRING): - - Ethereum contract address (with or without 0x prefix) - - Example: '0xa0b86991c6218b36c1d19d4a2e9eb0ce3606eb48' - - function_abi (VARIANT): - - JSON object containing the function ABI definition - - Must include: "name" (string) and "inputs" (array of input definitions) - - Each input needs: "name", "type", and optionally "components" for tuples - - Can be retrieved from tables like crosschain.evm.dim_contract_abis or ethereum.silver.flat_function_abis - - input_values (ARRAY): - - Array of values matching the function inputs in order - - Values should be provided as native Snowflake types: - * addresses: strings (with or without 0x prefix) - * uint/int: numbers - * bool: booleans - * bytes/bytes32: hex strings (with or without 0x prefix) - * arrays: Snowflake arrays - * tuples: Snowflake arrays in component order - - block_parameter (VARIANT, optional): - - Block identifier for the eth_call request - - If NULL or omitted: defaults to 'latest' - - If NUMBER: automatically converted to hex format (e.g., 18500000 -> '0x11a7f80') - - If STRING: used directly (e.g., 'latest', '0x11a7f80', 'pending') - -RETURNS: - OBJECT: Complete JSON-RPC request object ready for eth_call - - Format: {"jsonrpc": "2.0", "method": "eth_call", "params": [...], "id": "..."} - - Can be used directly with RPC execution functions like live.udf_api - -EXAMPLES: - - -- Simple balanceOf call with default 'latest' block - SELECT utils.udf_create_eth_call_from_abi( - '0xa0b86991c6218b36c1d19d4a2e9eb0ce3606eb48', - PARSE_JSON('{ - "name": "balanceOf", - "inputs": [{"name": "account", "type": "address"}] - }'), - ARRAY_CONSTRUCT('0xbcca60bb61934080951369a648fb03df4f96263c') - ); - - -- Same call but at a specific block number - SELECT utils.udf_create_eth_call_from_abi( - '0xa0b86991c6218b36c1d19d4a2e9eb0ce3606eb48', - PARSE_JSON('{ - "name": "balanceOf", - "inputs": [{"name": "account", "type": "address"}] - }'), - ARRAY_CONSTRUCT('0xbcca60bb61934080951369a648fb03df4f96263c'), - 18500000 - ); - - -- Using ABI from a table - WITH abi_data AS ( - SELECT - abi, - '0xa0b86991c6218b36c1d19d4a2e9eb0ce3606eb48' as contract_address, - '0xbcca60bb61934080951369a648fb03df4f96263c' as user_address - FROM ethereum.silver.flat_function_abis - WHERE contract_address = LOWER('0x43506849d7c04f9138d1a2050bbf3a0c054402dd') - AND function_name = 'balanceOf' - ) - SELECT - utils.udf_create_eth_call_from_abi( - contract_address, - abi, - ARRAY_CONSTRUCT(user_address) - ) as rpc_call - FROM abi_data; - -TYPICAL WORKFLOW: - 1. Get function ABI from contract ABI tables (crosschain.evm.dim_contract_abis, etc.) - 2. Prepare input values as Snowflake arrays matching the function signature - 3. Call this function with contract address, ABI, and inputs - 4. Execute the returned RPC call object via live.udf_api or similar - 5. Decode the response using utils.udf_evm_decode_trace or similar decoder - -ADVANTAGES OVER MODULAR APPROACH: - - Single function call instead of two (encode + create) - - Cleaner, more readable SQL - - Better for AI systems (fewer steps to explain) - - Less error-prone (no intermediate variables) - - More intuitive function name - -WHEN TO USE MODULAR FUNCTIONS INSTEAD: - - When you need to reuse encoded calldata for multiple RPC calls - - When you need encoded calldata for transaction construction - - When building complex workflows with intermediate steps - -RELATED FUNCTIONS: - - utils.udf_encode_contract_call: Encode function calls to calldata (used internally) - - utils.udf_create_eth_call: Create RPC call from encoded calldata (used internally) - - utils.udf_evm_text_signature: Generate function signature from ABI - - utils.udf_keccak256: Calculate function selector hash - - utils.udf_evm_decode_trace: Decode eth_call response results - -{% endmacro %} \ No newline at end of file