Chainsight is a Platform running on an Internet Computer that allows anyone to build and deploy Modular Oracle. Users can easily build any oracle through the CLI/SDK provided by Chainsight. The CLI allows users to implement their own oracle or calculator with simple operations, but behind this tool hides complex algorithms and advanced techniques. Let us clarify the ingenuity and mechanism behind this.
The prerequisite knowledge is the structure of the Internet Computer.
A advantage of the Internet Computer blockchain is its use of WebAssembly for executing smart contracts and decentralized applications — written in a wide variety of languages that compile to WebAssembly.
Source: https://internetcomputer.org/capabilities/webassembly
Internet Computer Blockchain uses the Web Assembly module as smart contract. The user selects a programming language that can be built into the Web Assembly module to deploy their own contract on the Internet Computer Blockchain, and implements it according to Internet Computer specifications.
Source: https://medium.com/dfinity/webassembly-on-the-internet-computer-a1d0c71c5b94
Web Assembly, promoted by the W3C and Bytecode Alliance, is already widely used and supported by dozens of programming languages. In Internet Computer, this feature has been carried over to enable development in many languages. These efforts have lowered the barrier to entry for users over existing blockchains. However, even if you are familiar with the languages available, understanding the Internet Computer specifications, implementing contract code, and learning the build/deployment process is not an easy task.
The Chainsight CLI/SDK is a tool that assists users throughout the development process, making it easier and faster to create their own smart contracts on the Chainsight Platform. With the Chainsight CLI/SDK, you can quickly create oracle, calculator, and other smart contracts with your own data and metrics.
What Chainsight CLI/SDK can do for you
As explained earlier, to create a contract for Internet Computer, users need to install the necessary libraries in their own language project, coding, and so on. But with Chainsight CLI, the simplest contract can be created with a single YAML file. From the generated code, module generation, its optimization, and metadata assignment are performed in a single command.
Once the Chainsight CLI is installed, the csx
(Chainsight command-line execution environment) command is available. csx new
or csx add
obtains a YAML file that serves as a manifest template, csx generate
generates the contract code, and csx build
generates the module from the code. If necessary, you can change the YAML settings or add custom code. Specific instructions can be found in another article below.
Step-by-step creating an EVM Price Oracle with Chainsight - DEV Community
For example setups, see the past Deep Dive into Showcase.
Behind Chainsight CLI: Generate Command
What is the mechanism to generate code from YAML by csx generate
after the YAML held as a template in Chainsight CLI is placed locally to the user by csx new
or csx add
?
Only this small code is actually generated from YAML.
use chainsight_cdk_macros::def_snapshot_indexer_evm_canister;
def_snapshot_indexer_evm_canister!("{
\"common\":{
\"canister_name\":\"sample_snapshot_indexer_evm\"
},
\"method_identifier\":\"totalSupply():(uint256)\",
\"method_args\":[],
\"abi_file_path\":\"./__interfaces/ERC20.json\"
}");
Chainsight makes full use of rust macro to generate the core code of the contract to reflect the user's unique settings. This proc_macro contains various functions, such as timer logic for periodic processing, structures for external calls, interface generation functions etc., which are also generated by the proc_macro for each function. In other words, the contract code is constructed by nested macros.
In the YAML manifest that serves as the generator, parameters that can be macro variables are converted to json and given as macro arguments to minimize consideration of proprietary settings.
# sample_snapshot_indexer_evm.yaml
...
datasource:
location:
id: 6b175474e89094c44da98b954eedeac495271d0f
args:
network_id: 1
rpc_url: https://mainnet.infura.io/v3/${INFURA_MAINNET_RPC_URL_KEY}
method:
identifier: totalSupply():(uint256)
interface: ERC20.json
args: []
...
↓
use chainsight_cdk_macros::def_snapshot_indexer_evm_canister;
def_snapshot_indexer_evm_canister!("{
\"common\":{
\"canister_name\":\"sample_snapshot_indexer_evm\"
},
\"method_identifier\":\"totalSupply():(uint256)\",
\"method_args\":[],
\"abi_file_path\":\"./__interfaces/ERC20.json\"
}");
To facilitate user customization of the generated code, the area is allocated in a separate package from the proc_macro code itself. The Generate command also provides project generation of templates to facilitate resolving dependencies of this package.
pj_root/src
|- __interface
|- accessors // External communication logic
|- bindings // Types for external communication
|- canisters // Core canister codes
|- logics // User-specific customization
|- Cargo.lock
L Cargo.toml
Let's look a little further behind the scenes at the SDK side. The SDK provides proc_macro entry points that provide contract core logic for each component type. By using these, it is possible to create components that run on the Chainsight Platform even with only the SDK.
#[proc_macro]
pub fn def_snapshot_indexer_evm_canister(input: TokenStream) -> TokenStream {
canisters::snapshot_indexer_evm::def_snapshot_indexer_evm(input)
}
#[proc_macro]
pub fn def_snapshot_indexer_https_canister(input: TokenStream) -> TokenStream {
canisters::snapshot_indexer_https::def_snapshot_indexer_https(input)
}
#[proc_macro]
pub fn def_snapshot_indexer_icp_canister(input: TokenStream) -> TokenStream {
canisters::snapshot_indexer_icp::def_snapshot_indexer_icp(input)
}
#[proc_macro]
pub fn def_event_indexer_canister(input: TokenStream) -> TokenStream {
canisters::event_indexer::def_event_indexer_canister(input)
}
#[proc_macro]
pub fn def_algorithm_indexer_canister(input: TokenStream) -> TokenStream {
canisters::algorithm_indexer::def_algorithm_indexer_canister(input)
}
#[proc_macro]
pub fn def_algorithm_lens_canister(input: TokenStream) -> TokenStream {
canisters::algorithm_lens::def_algorithm_lens_canister(input)
}
#[proc_macro]
pub fn def_relayer_canister(input: TokenStream) -> TokenStream {
canisters::relayer::def_relayer_canister(input)
}
As mentioned earlier, these proc_macro arguments are json data for reflecting per-user settings in the contract code, and their parameters can be easily viewed in the code.
pub fn def_snapshot_indexer_https(input: TokenStream) -> TokenStream {
let input_json_string: String = parse_macro_input!(input as syn::LitStr).value();
let config: SnapshotIndexerHTTPSConfig =
serde_json::from_str(&input_json_string).expect("Failed to parse input_json_string");
snapshot_indexer_https(config).into()
}
...
#[derive(Clone, Debug, Default, serde::Serialize, serde::Deserialize)]
pub struct SnapshotIndexerHTTPSConfig {
pub common: CommonConfig,
pub url: String,
pub headers: BTreeMap<String, String>,
pub queries: SnapshotIndexerHTTPSConfigQueries,
}
#[derive(Clone, Debug, serde::Serialize, serde::Deserialize)]
pub enum SnapshotIndexerHTTPSConfigQueries {
Const(BTreeMap<String, String>),
Func(String),
}
The rust macro is used to generate contract code from the YAML manifest, and the unified code design makes the interface and parameters clear. In addition, by using JSON data as parameters, it is possible to use the SDK independently.
As part of the process of looking behind the scenes of Chainsight up to module generation, we delved into code generation this time. We will look at the process of actually generating modules from this code next time. See you again in the next article.
Follow us on Twitter and Medium for updates! We look forward to seeing you there!
Written by Megared@Chainsight