Skip to content

SHARD-2738: Fix how token contract data is parsed and stored#94

Open
urnotsam wants to merge 15 commits intodevfrom
SAHRD-2738-tokeninfo
Open

SHARD-2738: Fix how token contract data is parsed and stored#94
urnotsam wants to merge 15 commits intodevfrom
SAHRD-2738-tokeninfo

Conversation

@urnotsam
Copy link

@urnotsam urnotsam commented Aug 27, 2025

PR Type

Bug fix, Enhancement


Description

  • Fix token contract info parsing and storage logic

  • Ensure correct types for decimals and totalSupply fields

  • Fetch and update missing contract info dynamically

  • Improve value extraction for database storage, including booleans


Changes walkthrough 📝

Relevant files
Bug fix
TxDecoder.ts
Correct token contract info type handling in decoder         

src/class/TxDecoder.ts

  • Parse totalSupply as number and decimals as string
  • Improve contract info extraction for ERC-20 tokens
  • +3/-2     
    Enhancement
    account.ts
    Dynamic fetching and updating of token contract info         

    src/storage/account.ts

  • Make queryTokensByAddress async to allow dynamic fetching
  • Fetch and update missing contract info for tokens
  • Store updated contract info in account records
  • Improve error handling and logging for contract info fetch
  • +29/-9   
    sqlite3storage.ts
    Improve value extraction for database storage                       

    src/storage/sqlite3storage.ts

  • Enhance extractValues and extractValuesFromArray to handle booleans as
    1/0
  • Support both string and number return types for extracted values
  • Improve object value stringification for database storage
  • +13/-8   

    Need help?
  • Type /help how to ... in the comments thread for any questions about PR-Agent usage.
  • Check out the documentation for more information.
  • @github-actions
    Copy link

    PR Reviewer Guide 🔍

    Here are some key observations to aid the review process:

    ⏱️ Estimated effort to review: 3 🔵🔵🔵⚪⚪
    🏅 Score: 86
    🧪 No relevant tests
    🔒 No security concerns identified
    ⚡ Recommended focus areas for review

    Async Function Refactor

    The queryTokensByAddress function was changed from sync to async and now fetches contract info dynamically if missing. Ensure all callers of this function handle its Promise return type correctly and that no race conditions or unhandled promise rejections are introduced.

    export async function queryTokensByAddress(address: string, detail = false): Promise<object[]> {
      try {
        const sql = `SELECT * FROM tokens WHERE ethAddress=?`
        const tokens = db.all(sql, [address]) as Token[]
        const filterTokens: object[] = []
        if (detail) {
          for (const { contractAddress, tokenValue } of tokens) {
            const accountExist = queryAccountByAccountId(
              contractAddress.slice(2).toLowerCase() + '0'.repeat(24) //Search by Shardus address
            )
    
            let contractInfo: ContractInfo | null = accountExist?.contractInfo || null
            let contractType: ContractType | null = accountExist?.contractType || null
    
            // If contract info is missing, try to fetch it
            if (!contractInfo) {
              try {
                const fetchedInfo = await getContractInfo(contractAddress)
                contractInfo = fetchedInfo.contractInfo
                contractType = fetchedInfo.contractType
    
                // Update the account record with the newly fetched info if account exists
                if (accountExist) {
                  accountExist.contractInfo = contractInfo
                  accountExist.contractType = contractType
                  insertAccount(accountExist)
                }
              } catch (e) {
                if (config.verbose) console.log(`Failed to fetch contract info for ${contractAddress}:`, e)
              }
            }
    
            filterTokens.push({
              contractAddress: contractAddress,
              contractInfo: contractInfo,
              contractType: contractType,
              balance: tokenValue,
            })
          }
        }
        if (config.verbose) console.log('Tokens of an address', tokens)
        return filterTokens
    Type Consistency

    The types for totalSupply and decimals in contract info are now explicitly cast (number and string). Double-check that this does not cause downstream type mismatches or unexpected behavior, especially if other code expects the previous types.

    const totalSupplyRaw = await Token.methods.totalSupply().call()
    contractInfo.totalSupply = Number(totalSupplyRaw)
    contractInfo.decimals = String(await Token.methods.decimals().call())
    Boolean Value Handling

    The extraction functions now convert booleans to 1/0 for DB storage. Ensure that this is compatible with all DB schemas and that retrieval logic correctly interprets these values as booleans when needed.

    export function extractValues(object: object): (string | number)[] {
      try {
        const inputs: (string | number)[] = []
        for (const [key, value] of Object.entries(object)) {
          if (typeof value === 'object' && value !== null) {
            inputs.push(StringUtils.safeStringify(value))
          } else if (typeof value === 'boolean') {
            inputs.push(value ? 1 : 0)
          } else {
            inputs.push(value)
          }
        }
        return inputs
      } catch (e) {
        console.log(e)
      }
    
      return []
    }
    
    export function extractValuesFromArray(arr: object[]): (string | number)[] {
      try {
        const inputs: (string | number)[] = []
        for (const object of arr) {
          for (let value of Object.values(object)) {
            if (typeof value === 'object') value = StringUtils.safeStringify(value)
            else if (typeof value === 'boolean') value = value ? 1 : 0
            inputs.push(value)
          }
        }
        return inputs

    Comment on lines +267 to +277
    if (!contractInfo) {
    try {
    const fetchedInfo = await getContractInfo(contractAddress)
    contractInfo = fetchedInfo.contractInfo
    contractType = fetchedInfo.contractType

    // Update the account record with the newly fetched info if account exists
    if (accountExist) {
    accountExist.contractInfo = contractInfo
    accountExist.contractType = contractType
    insertAccount(accountExist)

    Choose a reason for hiding this comment

    The reason will be displayed to describe this comment to others. Learn more.

    Suggestion: The call to insertAccount(accountExist) should be awaited since it may be asynchronous. Not awaiting it could lead to race conditions or unhandled promise rejections. [possible issue, importance: 7]

    Suggested change
    if (!contractInfo) {
    try {
    const fetchedInfo = await getContractInfo(contractAddress)
    contractInfo = fetchedInfo.contractInfo
    contractType = fetchedInfo.contractType
    // Update the account record with the newly fetched info if account exists
    if (accountExist) {
    accountExist.contractInfo = contractInfo
    accountExist.contractType = contractType
    insertAccount(accountExist)
    if (!contractInfo) {
    try {
    const fetchedInfo = await getContractInfo(contractAddress)
    contractInfo = fetchedInfo.contractInfo
    contractType = fetchedInfo.contractType
    // Update the account record with the newly fetched info if account exists
    if (accountExist) {
    accountExist.contractInfo = contractInfo
    accountExist.contractType = contractType
    await insertAccount(accountExist)
    }
    } catch (e) {
    if (config.verbose) console.log(`Failed to fetch contract info for ${contractAddress}:`, e)
    }
    }

    Comment on lines +126 to 134
    for (const [key, value] of Object.entries(object)) {
    if (typeof value === 'object' && value !== null) {
    inputs.push(StringUtils.safeStringify(value))
    } else if (typeof value === 'boolean') {
    inputs.push(value ? 1 : 0)
    } else {
    inputs.push(value)
    }
    }

    Choose a reason for hiding this comment

    The reason will be displayed to describe this comment to others. Learn more.

    Suggestion: Serializing all objects, including arrays, may cause issues if arrays are expected to be stored as-is or handled differently. Consider checking for arrays separately and handling them appropriately to avoid data corruption. [general, importance: 6]

    Suggested change
    for (const [key, value] of Object.entries(object)) {
    if (typeof value === 'object' && value !== null) {
    inputs.push(StringUtils.safeStringify(value))
    } else if (typeof value === 'boolean') {
    inputs.push(value ? 1 : 0)
    } else {
    inputs.push(value)
    }
    }
    for (const [key, value] of Object.entries(object)) {
    if (Array.isArray(value)) {
    inputs.push(JSON.stringify(value))
    } else if (typeof value === 'object' && value !== null) {
    inputs.push(StringUtils.safeStringify(value))
    } else if (typeof value === 'boolean') {
    inputs.push(value ? 1 : 0)
    } else {
    inputs.push(value)
    }
    }

    Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

    Projects

    None yet

    Development

    Successfully merging this pull request may close these issues.

    2 participants