Decode LogOperate Event

LogOperate Event Documentation

Event Name

LogOperate

Description

This event is essential for anyone looking to monitor or analyze the protocol's activities, such as tracking total supplies, borrows, borrow rate, fee, utilization, exchange prices and supply and borrow liquidity ratios. It provides a real-time overview of operations, enhancing operational transparency and facilitating detailed analysis. Essentially, it acts as the backbone for understanding and engaging with the dynamic environment of Fluid, making every operation traceable and transparent.

The LogOperate event is designed to emit detailed information following the execution of an operate() function in Liquidity Protocol. This function can perform various operations such as deposits, supplies, withdrawals, and borrows.

Parameters

  • user (address indexed): The protocol or user address that initiated the operation, possibly through an fToken, Vault protocol, or other mechanisms.
  • token (address indexed): The token address for which the operation was executed, indicating the specific asset being manipulated.
  • supplyAmount (int256): Indicates the operation's supply amount. Positive for deposit, negative for withdrawal, and zero indicates no supply operation.
  • borrowAmount (int256): Indicates the operation's borrow amount. Positive for borrow, negative for repayment, and zero indicates no borrow operation.
  • withdrawTo (address): The recipient address for funds withdrawn, applicable if supplyAmount is negative.
  • borrowTo (address): The recipient address for borrowed funds, applicable if borrowAmount is positive.
  • totalAmounts (uint256): A packed uint256 representing updated total amounts post-operation, using the BigMath number format. It includes:
    • First 64 bits (0-63): Total supply with interest in raw format (totalSupplyRaw * supplyExchangePrice). BigMath format: 56 | 8.
    • Next 64 bits (64-127): Total interest-free supply in normal token amount. BigMath format: 56 | 8.
    • Next 64 bits (128-191): Total borrow with interest in raw format (totalBorrowRaw * borrowExchangePrice). BigMath format: 56 | 8.
    • Next 64 bits (192-255): Total interest-free borrow in normal token amount. BigMath format: 56 | 8.
  • exchangePricesAndConfig (uint256): A packed uint256 containing updated exchange prices and configuration settings, detailed as follows:
    • First 16 bits (0-15): Borrow rate in 1e2 format (100% = 10,000).
    • Next 14 bits (16-29): Fee on interest from borrowers to lenders, configurable.
    • Next 14 bits (30-43): Last stored utilization rate.
    • Next 14 bits (44-57): Update on storage threshold, configurable.
    • Next 33 bits (58-90): Last update timestamp.
    • Next 64 bits (91-154): Supply exchange price in 1e12 format.
    • Next 64 bits (155-218): Borrow exchange price in 1e12 format.
    • One bit (219): Indicates the ratio direction for supply.
    • Next 14 bits (220-233): SupplyRatio, indicating the supplyInterestFree / supplyWithInterest ratio.
    • One bit (234): Indicates the ratio direction for borrow.
    • Next 14 bits (235-248): BorrowRatio, indicating the borrowInterestFree / borrowWithInterest ratio.

Detailed Breakdown

totalAmounts

Encapsulates the state of total supplies and borrows within the protocol, including interest-bearing and interest-free amounts. This aids in understanding the protocol's liquidity and debts post-operation.

exchangePricesAndConfig

Provides a comprehensive view of economic factors influencing the operation, such as interest rates, fees, market utilization, and supply/borrow exchange prices. It also includes configuration settings impacting operations' execution and pricing.

This enhanced documentation offers a thorough understanding of the LogOperate event, enabling developers and users to track, analyze, and integrate with the DeFi protocol effectively.

Extracting Fields from Compact Storage in Solidity

This part outlines the process for extracting field values from totalAmounts and exchangePricesAndConfig within a Solidity contract, employing bit manipulation techniques and the BigMathMinified library for BigNumber operations.

Having helper variables:

    uint256 internal constant X8 = 0xff;
    uint256 internal constant X14 = 0x3fff;
    uint256 internal constant X15 = 0x7fff;
    uint256 internal constant X16 = 0xffff;
    uint256 internal constant X18 = 0x3ffff;
    uint256 internal constant X24 = 0xffffff;
    uint256 internal constant X33 = 0x1ffffffff;
    uint256 internal constant X64 = 0xffffffffffffffff;

    uint256 internal constant DEFAULT_COEFFICIENT_SIZE = 56;
    uint256 internal constant DEFAULT_EXPONENT_SIZE = 8;

Extracting Fields from totalAmounts

The totalAmounts is another packed uint256 representing various totals within the protocol. Here is how to access each value:

Total Supply with Interest

uint256 totalSupplyWithInterestBigNumber = totalAmounts_ & X64; //bignumber form
uint256 totalSupplyWithInterest = BigMathMinified.fromBigNumber(
    totalSupplyWithInterestBigNumber,
    DEFAULT_EXPONENT_SIZE,
    DEFAULT_EXPONENT_MASK
);

Total Interest-Free Supply

uint256 totalInterestFreeSupplyBigNumber = (totalAmounts_ >> BITS_TOTAL_AMOUNTS_SUPPLY_INTEREST_FREE) & X64; //bignumber form
uint256 totalInterestFreeSupply = BigMathMinified.fromBigNumber(
    totalInterestFreeSupplyBigNumber,
    DEFAULT_EXPONENT_SIZE,
    DEFAULT_EXPONENT_MASK
);

Total Borrow with Interest

uint256 totalBorrowWithInterestBigNumber = (totalAmounts_ >> BITS_TOTAL_AMOUNTS_BORROW_WITH_INTEREST) & X64; //bignumber form
uint256 totalBorrowWithInterest = BigMathMinified.fromBigNumber(
    totalBorrowWithInterestBigNumber,
    DEFAULT_EXPONENT_SIZE,
    DEFAULT_EXPONENT_MASK
);

Total Interest-Free Borrow

uint256 totalInterestFreeBorrowBigNumber = totalAmounts_ >> BITS_TOTAL_AMOUNTS_BORROW_INTEREST_FREE; //bignumber form
uint256 totalInterestFreeBorrow = BigMathMinified.fromBigNumber(
    totalInterestFreeBorrowBigNumber,
    DEFAULT_EXPONENT_SIZE,
    DEFAULT_EXPONENT_MASK
);

Using BigMathMinified for BigNumber Operations

For handling large numbers accurately, especially when dealing with exchange prices and ratios, the BigMathMinified library is utilized. Here is an example of converting from BigNumber format to uint256:

Converting BigNumber to uint256

uint256 value = BigMathMinified.fromBigNumber(
    (totalAmounts_ >> someBitsPosition) & bitMask,
    DEFAULT_EXPONENT_SIZE,
    DEFAULT_EXPONENT_MASK
);

Extracting Fields from exchangePricesAndConfig

The exchangePricesAndConfig is a packed uint256 value that stores multiple configurations and prices. Below are the methods to extract each value:

Borrow Rate

uint256 borrowRate = exchangePricesAndConfig_ & X16;

Fee

uint256 fee = (exchangePricesAndConfig_ >> BITS_EXCHANGE_PRICES_FEE) & X14;

Utilization

uint256 utilization = (exchangePricesAndConfig_ >> BITS_EXCHANGE_PRICES_UTILIZATION) & X14;

Supply and Borrow Exchange Price

uint256 supplyExchangePrice = (exchangePricesAndConfig_ >> BITS_EXCHANGE_PRICES_SUPPLY_EXCHANGE_PRICE) & X64;
uint256 borrowExchangePrice = (exchangePricesAndConfig_ >> BITS_EXCHANGE_PRICES_BORROW_EXCHANGE_PRICE) & X64;

Ratios

Ratios are encoded with a leading direction bit, followed by the ratio value itself.

bool supplyRatioDirection = ((exchangePricesAndConfig_ >> BITS_EXCHANGE_PRICES_SUPPLY_RATIO) & 1) == 1;
uint256 supplyRatio = (exchangePricesAndConfig_ >> (BITS_EXCHANGE_PRICES_SUPPLY_RATIO + 1)) & X14;

bool borrowRatioDirection = ((exchangePricesAndConfig_ >> BITS_EXCHANGE_PRICES_BORROW_RATIO) & 1) == 1;
uint256 borrowRatio = (exchangePricesAndConfig_ >> (BITS_EXCHANGE_PRICES_BORROW_RATIO + 1)) & X14;

Interacting and decoding data with JavaScript, Ethers.js, and Infura

This section will provide an example of how to fetch and decode the exchangePricesAndConfig and totalAmounts data from the Liquidity Protocol using LiquidityResolver smart contract using ethers.js and infura.

Setup and Initialization

First, ensure ethers.js is included in your project:

npm install ethers@5.6.9

Replace YOUR_INFURA_KEY with your actual Infura project key to connect to the Ethereum network through the JSON RPC provider.

Substitute TOKEN_ADDRESS with the actual Ethereum address of the token you're interested in analyzing, like USDC.

const { ethers } = require("ethers");

const provider = new ethers.providers.JsonRpcProvider("https://mainnet.infura.io/v3/YOUR_INFURA_KEY");
const liquidityResolverContractABI = [
  // Add the ABI definition for the functions you're going to call from liquidity contract
  "function getExchangePricesAndConfig(address) public view returns (uint256)",
  "function getTotalAmounts(address) public view returns (uint256)",
];
const liquidityResolverAddress = "0x741c2Cd25f053a55fd94afF1afAEf146523E1249"; // LiquidityResolver contract on Ethereum mainnet
const liquidityResolver = new ethers.Contract(liquidityResolverAddress, liquidityResolverContractABI, provider);

const tokenAddress = "TOKEN_ADDRESS"; //ex. USDC on Ethereum mainnet - 0xA0b86991c6218b36c1d19D4a2e9Eb0cE3606eB48

const DEFAULT_EXPONENT_SIZE = 8n;
const DEFAULT_EXPONENT_MASK = BigInt("0xFF");
const X64 = BigInt("0xFFFFFFFFFFFFFFFF");

// Helper function to shift and mask according to Solidity's BigMath
function decodeBigMath(value) {
  let memVar = value & X64;
  let coefficient = memVar >> DEFAULT_EXPONENT_SIZE;
  let exponent = memVar & DEFAULT_EXPONENT_MASK;
  return coefficient << exponent; // Simulate BigMath logic
}

async function fetchAndDecode() {
  const totalAmounts = (await liquidityResolver.getTotalAmounts(tokenAddress)).toBigInt();

  // Decode the totalAmounts fields using the decodeBigMath function
  let supplyRawInterest = decodeBigMath(totalAmounts);
  let supplyInterestFree = decodeBigMath((totalAmounts >> 64n) & X64);
  let borrowRawInterest = decodeBigMath((totalAmounts >> 128n) & X64);
  let borrowInterestFree = decodeBigMath(totalAmounts >> 192n); // No need for & X64 due to shifting all the way

  console.log(`Supply Raw Interest: ${supplyRawInterest.toString()}`);
  console.log(`Supply Interest Free: ${supplyInterestFree.toString()}`);
  console.log(`Borrow Raw Interest: ${borrowRawInterest.toString()}`);
  console.log(`Borrow Interest Free: ${borrowInterestFree.toString()}`);

  const exchangePricesAndConfig = (await liquidityResolver.getExchangePricesAndConfig(tokenAddress)).toBigInt();

  // Decode fields from exchangePricesAndConfig
  const borrowRate = exchangePricesAndConfig & BigInt("0xffff");
  const fee = (exchangePricesAndConfig >> 16n) & BigInt("0x3fff");
  const utilization = (exchangePricesAndConfig >> 30n) & BigInt("0x3fff");
  const supplyExchangePrice = (exchangePricesAndConfig >> 91n) & BigInt("0xffffffffffffffff");
  const borrowExchangePrice = (exchangePricesAndConfig >> 155n) & BigInt("0xffffffffffffffff");

  console.log(`Borrow Rate: ${borrowRate.toString()}`);
  console.log(`Fee: ${fee.toString()}`);
  console.log(`Utilization: ${utilization.toString()}`);
  console.log(`Supply Exchange Price: ${supplyExchangePrice.toString()}`);
  console.log(`Borrow Exchange Price: ${borrowExchangePrice.toString()}`);
}

fetchAndDecode().catch(console.error);