General: Contracts: Contract Reads - FlipsideCrypto/fsc-evm GitHub Wiki
Contract Reads Pipeline
Summary:
This guide focuses on an essential aspect of blockchain data analysis: modeling function calls that read data from smart contracts. Smart contracts often contain valuable information that isn't directly visible in transaction data. These 'read' functions - such as querying a token's symbol, decimals, or name - provide crucial context that enrich event logs or traces with contract metadata.
Best Practices, Tips & Tricks:
Implementation Steps, Variables & Notes:
Examples, References & Sources:
Read Functions for Curated Models
Summary:
When building curated models for DeFi primitives such as Liquidity Pools, DEX Swaps, Bridging or even NFTs, it's common that contract metadata can only be accessed via a function call on the contract itself, rather than parsed through event logs or traces. And while we have a pipeline for reading token symbols, names and decimals, there's often other data points or metadata that is necessary to collect, such as the token addresses associated with liquidity pools.
Best Practices, Tips & Tricks:
- Before building a reads model to call functions on a contract, verify that the metadata you are looking for is not already present in the Contracts pipeline, or emitted in events or traces.
- Contract reads can be resource intensive. It's crucial to test thoroughly to gauge the required frequency of calls and ensure proper incremental logic is in place to prevent re-reading the contract for metadata that's already been collected, or doesn't exist at all.
- There are various resources for converting function signatures to text and vice versa. Examples include;
- Flipside's EVM Signature Search: Tool to search for EVM Function and Event signatures.
UDF_KECCAK256(STRING)
: Custom Snowflake function designed to convert the function name (string) to the hexadecimal representation that can be called on the contract.- Alternatively, there are legacy UDFs such as
UDFS.STREAMLINE.UDF_HEX_ENCODE_FUNCTION(STRING)
that may be used, but please use caution as these are no longer maintained and may be deprecated in the future.
- Alternatively, there are legacy UDFs such as
Implementation Steps, Variables & Notes:
-
Determine the Contract Address that the function should be called on. This can be done in a number of ways, but the most straightforward is to research the Contract on a block explorer such as Etherscan. View the
Contract
tab, then selectCode
to view the function code orRead Contract
for a structured view of available function calls and outputs.- Note on Proxy Contracts: Often times a custom proxy implementation contract is deployed for the parent contract that requires reads. In this case, select the
Read as Proxy
tab, where the implementation contract address is present. Flipside'ssilver.proxies
table is another great resource for determining parent and proxy addresses.
- Note on Proxy Contracts: Often times a custom proxy implementation contract is deployed for the parent contract that requires reads. In this case, select the
-
Determine the Block to call the function against. It is recommended to use the
MAX(block_number)
associated with the contract rather than theMIN(block_number)
, as often times metadata is not complete at the moment the contract is deployed.- Ensure proper incremental logic is added to limit the amount of data being processed each time the model runs.
-
Create the Function Signatures CTE with two columns,
function_sig
andfunction_name
and define the values. This CTE will be referenced in the function call.- Utilize the Custom Snowflake UDF
UTILS.UDF_KECCAK256
or Flipside's EVM Signature Search to convert the function name into the hexadecimal representation.- It is not recommended to place this function directly into the DBT model because it can be resource intensive to call the UDF every time the models runs. The most efficient route is to run this query in Snowflake and hardcode the values in the model, unless they are expected to change frequently (not common).
- The first 10 characters (including 0x) of the string represent the function_signature.
SELECT UTILS.UDF_KECCAK256('l1CanonicalToken()') AS function_name, LEFT(function_name,10) AS function_signature;
- Utilize the Custom Snowflake UDF
-
Create the Inputs CTE by joining the
contract_address
andblock_number
with the function_sigs CTE, producing 1 row for every function_signature intended to be called.- Produce the
DATA
portion of the call by concatenating the function_sig with the padded function_input. This is typically set to 64 digits in length.
- Produce the
-
Prep the Contract Read
rpc_request
withUTILS.UDF_JSON_RPC_CALL
. Please see eth_call RPC Method for more information on how to structure the request.utils.udf_json_rpc_call( 'eth_call', [ { 'to': contract_address, 'from': NULL, 'data': DATA }, utils.udf_int_to_hex(block_number) ] ) AS rpc_request,
-
Execute the call with
live.udf_api
. Please see LiveQuery Documentation for more information.live.udf_api( 'POST', CONCAT( '{service}', '/', '{Authentication}' ),{}, rpc_request, 'Vault/prod/base/quicknode/mainnet' ) AS read_output,
-
The
result
data within theread_output
object will contain the output of the call. E.g.read_output :data :result :: STRING AS read_result
. This is typically in hexadecimal format and must be transformed according to the expected result. Here is one example of how it might be transformed to an address format.CONCAT('0x', SUBSTR(read_result, 27, 40)) AS address