Skip to content

compound-finance/sleuth

Repository files navigation

Sleuth


Sleuth is an easy way to pull data from an EVM-compatible blockchain, allowing for complex queries, similar to an ethers-multicall. Sleuth works by deploying a smart contract and then invoking it in an eth_call. This allows you to use complex logic to pull data from many contracts or other items such as eth_chainId or eth_blockNumber, which you can use for data analysis or in your Web3 front-end. For example:

MyQuery.sol [Note: this is not deployed, and is never deployed]

// SPDX-License-Identifier: UNLICENSED
pragma solidity ^0.8.16;

contract BlockNumber {
    function query() external view returns (uint256) {
        return block.number;
    }
}

MyView.ts

import { Sleuth } from '@compound-finance/sleuth';

let blockNumberQuery = await Sleuth.querySol(fs.readFileSync('./MyQuery.sol', 'utf8'));
let sleuth = new Sleuth(provider);
let blockNumber = await sleuth.fetch(blockNumberQuery);

You can also use pre-compiled contracts (e.g. if you check in the compilation artifacts from solc).

MyView.ts

import { Sleuth } from '@compound-finance/sleuth';

let blockNumberQuery = await Sleuth.querySol(fs.readFileSync('./out/MyQuery.json', 'utf8'));
let sleuth = new Sleuth(provider);
let blockNumber = await sleuth.fetch(blockNumberQuery);

Sleuth Query Language [Experimental]

Sleuth also comes with a full query language, similar to SQL. You can specify contracts and load data from them. This is a WIP and subject to change.

import { Sleuth } from '@compound-finance/sleuth';

let sleuth = new Sleuth(provider);

// Add a source so the query language knows the shape of the contracts you'll be querying.
sleuth.addSource("comet", "0xc3d688B66703497DAA19211EEdff47f25384cdc3", ["function totalSupply() returns (uint256)"]);

// Build a query
let q = sleuth.query<[ BigNumber ]>("SELECT comet.totalSupply FROM comet;");

// Fetch the data
let [ totalSupply ] = await sleuth.fetch(q);

or all in one:

import { Sleuth } from '@compound-finance/sleuth';

let sleuth = new Sleuth(provider);

console.log(await sleuth.fetchSql(`
  REGISTER CONTRACT comet AT 0xc3d688B66703497DAA19211EEdff47f25384cdc3 WITH INTERFACE ["function totalSupply() returns (uint256)"];
  SELECT comet.totalSupply FROM comet;
`));

There's a lot more work in Sleuth Query Language to do, mostly around allowing you to pull in multiple "rows" since that's a core aspect of SQL, but for one-off queries, it's quite fun!

Getting Started

Install Sleuth:

yarn add @compound-finance/sleuth

# npm install --save @compound-finance/sleuth

Next, simply build a Solidity file and build Sleuth, as above, to execute the query. E.g.

import { Sleuth } from '@compound-finance/sleuth';

let sleuth = new Sleuth(provider);

let [name, age] = await sleuth.query(`
// SPDX-License-Identifier: UNLICENSED
pragma solidity ^0.8.16;

contract SimpleQuery {
    function query() external pure returns (uint256, string memory) {
        return (55, "Bob Jones");
    }
}
`);

Future Considerations

Instead of having users build solidity files, it might be nice to build a proper query language. This could be SQL-like or ORM-style or anything that compiles to say Yul (the intermediate representation used by Solidity). We could then abstract the interface to something interesting, such as:

await sleuth.query("SELECT comet.name FROM comet(0xc3...) WHERE comet.id = 5");

There's so much we could do here and it sounds really fun!

Parser

There's an early version up and running, which you can use with Sleuth. See /parser for more information.

License

MIT

Copyright 2022, Compound Labs, Inc. Geoffrey Hayes.