[Announcement] Smart Contract Diagram Generator for NEAR by PrimeLab!
- WillHOP 7
Smart Contract Diagram Generator for NEAR by PrimeLab!**
Cargo utility that allows developers to generate visualizations depicting data flows and methods within their NEAR smart contracts. (Rust)
Contributors:
PrimeLab Blockchain TeamGitHub: GitHub - Web3-Diagram-Generator
Our Smart Contract Diagram Generator enables developers to quickly visualize methods and understand how their contracts works.
Why (is this needed)
- Decreases on-ramping time for new developers.
- Provides developers a visual representation of NEAR’s smart contract structure.
- Helpful for visualizing:
- Contract Data Flows
- Functions and their types
- Visualizing code is a futuristic way of reading code (ex: VS Studio) and can be implemented into VsCode or other IDEs
Who (would this benefit)
- Blockchain/Rust developers
- Consumers (Seeking to understand more about the contract they’re using)
- Contract Owners
- Technical Writers
- Smart Contract Auditors
- Educational Content & Programs
How (does this achieve a solution):
Below the diagram shows the basic user flow of this toolSmart Contract Developers can use this Utility to generate diagrams in a multitude of formats. The resulting diagrams enable anyone to quickly and effectively understand how the Smart Contract is working.
Each function has the corresponding visual shape or connection so that the user can understand the function type(event, pub view function, etc.)
Diagram Legend
To differentiate between various functions, the diagram utility scans the AST tree of the contract source code and collects all the contract information into known Rust structures.
Shape/Color function mappings:
- The circle is a view function inside the contract, and the blue color indicates the modifier of the functioning blue means public
- Hexagon **** is a mutable function, blue is for public functions also
- The rectangle is a process function that is not returning value and they are grayed if private
- Triangle is for events
- Trait impl functions are circles in teal color
- Hexagon tealed as mutable trait functions
- Circles in green for payable functions
- Orange hexagon for initializer functions
- Direct connections are for regular functions just simple fn function inside the smart contract, they are not part of trait or event.
Example
#[near_bindgen] impl Contract { //Direct connection function for Contract pub fn add(&mut self, amount: u64) { if amount == 2 { self.count = SomeStruct::add_two(self.count); } self.add_amount(amount); } }- Trait connections are for trait implementation functions(impl trait) or functions that are out of contract structure scope
Example:
#[near_bindgen] impl Contract { } pub trait SomeTrait { fn view_function(&self) -> u64; } #[near_bindgen] impl SomeTrait for Contract { //Trait connection function fn view_function(&self) -> u64 { self.count * 10 } }- Emission connections are for events, functions that are part of event standards.
Example:
impl NearEvent { //Emittion connection type function pub fn new_171(version: String, event_kind: Nep171EventKind) -> Self { NearEvent::Nep171(Nep171Event { version, event_kind, }) } //Emittion connection type function pub fn new_171_v1(event_kind: Nep171EventKind) -> Self { NearEvent::new_171("1.0.0".to_string(), event_kind) } //Emittion connection type function pub fn nft_burn(data: Vec<NftBurnData>) -> Self { NearEvent::new_171_v1(Nep171EventKind::NftBurn(data)) } }Components:
The diagram utility consists of 3 modules.
Module 1 (scanner_syn) is for scanning the AST tree of the smart contract and differentiating functions
- Scans contracts
- Builds hierarchy trees
Module 2 (markdown_api) is for generating markdown code for diagram js client
- Parses hierarchy trees into a linked list of nodes & connections
- Parses linked list into a structure containing the mermaid markdown compatible string schema
- Allows for additional custom syntaxes to be utilized by inserting a syntax definition that fits the expected trait
Module 3 is a cargo plugin for running utility commands over smart contracts.
Contribution:
Adding custom syntax:
To include your own custom syntax you need to write a definition file that contains a struct that implements the
CoreSyntaxFunctionstrait and pass in the syntax name to the call.Example Syntax Definition:
/// The various different shapes enabled by this API. #[derive(EnumProperty, Debug)] pub enum Shape { #[strum(props(Left = "((", Right = "))"))] Circle, #[strum(props(Left = "{{", Right = "}}"))] Hexagon, #[strum(props(Left = "[", Right = "]"))] Rectangle, #[strum(props(Left = ">", Right = "]"))] Flag, } /// The various different line types enabled by this API. #[derive(EnumProperty, Debug)] pub enum LineType { #[strum(props(Complete = "--", Addition = "-"))] Solid, #[strum(props(Left = "-.", Right = ".-", Addition = "."))] Dashed, } /// The various different arrow types enabled by this API. #[derive(AsRefStr, EnumProperty, Debug, Clone, Copy)] pub enum ArrowType { #[strum(props(Left = "<", Right = ">"))] Standard, #[strum(props(Left = "x", Right = "x"))] X, #[strum(props(Left = "o", Right = "o"))] O, } #[derive(AsRefStr, Debug)] pub enum ArrowDirection { BiDirectional, Left, Right, None, } #[derive(EnumProperty, EnumAsInner, Debug)] pub enum ObjectConfig<'a> { NodeConfig(NodeConfig<'a>), ConnectionConfig(ConnectionConfig), } #[derive(Debug)] pub struct NodeConfig<'a> { pub id: &'a str, pub class: Option<&'a str>, pub shape: Shape, pub inner_text: &'a str, } #[derive(Debug)] pub struct ConnectionConfig { pub line_type: LineType, pub arrow_type: ArrowType, pub arrow_direction: ArrowDirection, pub extra_length_num: Option<u8>, } /// This is the root struct for an individual flow chart. pub struct FlowChart { /// This is the data location of the string data for the markdown data: String, } impl FlowChart { /// Creates a [Mermaid.js Dotted/Dashed Line](https://mermaid-js.github.io/mermaid/#/flowchart?id=dotted-link) with the supplied attributes & appends it to the current data of the flow chart struct (i.e. `self.data`). /// /// # Arguments /// /// * `extra_length_num` - An optional amount of additional flags to increase line length fn add_dashed_line( &mut self, extra_length_num: Option<u8>, ) { // Push the left half of the dashed line flag self.data .push_str(LineType::Dashed.get_str("Left").unwrap()); // Check to see if an additional length was requested if let Some(extra_length_num) = extra_length_num { // Range over `extra_length_num` to add the appropriate number of length additions for _ in 0..extra_length_num { // Add in a `.` self.data .push_str(LineType::Dashed.get_str("Addition").unwrap()); } } // Push the right half of the dashed line flag self.data .push_str(LineType::Dashed.get_str("Right").unwrap()); } /// Creates a [Mermaid.js Solid Line](https://mermaid-js.github.io/mermaid/#/flowchart?id=a-link-with-arrow-head) with the supplied attributes & appends it to the current data of the flow chart struct (i.e. `self.data`). /// /// # Arguments /// /// * `extra_length_num` - An optional amount of additional flags to increase line length fn add_solid_line( &mut self, extra_length_num: Option<u8>, ) { // Push the main portion of the solid line flag self.data.push_str(LineType::Solid.get_str("Main").unwrap()); // Check to see if an additional length was requested if let Some(extra_length_num) = extra_length_num { // Range over `extra_length_num` to add the appropriate number of length additions for _ in 0..extra_length_num { // Add in a `-` self.data .push_str(LineType::Solid.get_str("Addition").unwrap()); } } } /// Creates a [Mermaid.js Connection Line with no arrow](https://mermaid-js.github.io/mermaid/#/flowchart?id=links-between-nodes) with the supplied attributes & appends it to the current data of the flow chart struct (i.e. `self.data`). /// /// # Arguments /// /// * `line_type` - The enum representation of the line type you want /// * `extra_length_num` - An optional amount of additional flags to increase line length fn add_line( &mut self, line_type: LineType, extra_length_num: Option<u8>, ) { match line_type { LineType::Solid => self.add_solid_line(extra_length_num), LineType::Dashed => self.add_dashed_line(extra_length_num), } } /// Creates a [Mermaid.js Arrow](https://mermaid-js.github.io/mermaid/#/flowchart?id=new-arrow-types) with the supplied attributes & appends it to the current data of the flow chart struct (i.e. `self.data`). /// /// # Arguments /// /// * `arrow_type` - The enum representation of the arrow type you want /// * `arrow_direction` - The enum representation of the direction you want the arrow to fn add_arrow( &mut self, arrow_type: ArrowType, arrow_direction: ArrowDirection, ) { // Get the `arrow_direction` as a str to use as the key for the `ArrowType` enum property to then add the correct arrow flag self.data .push_str(arrow_type.get_str(arrow_direction.as_ref()).unwrap()) } fn get_shape_from_node( &self, node: &Node, ) -> Shape { match node.action { ActionType::Mutation => Shape::Hexagon, ActionType::View => Shape::Circle, ActionType::Process => Shape::Rectangle, ActionType::Event => Shape::Flag, } } fn get_line_and_arrow_type_from_connection( &self, connection: &Connection, ) -> (LineType, ArrowType, ArrowDirection) { match connection.connection_type { ConnectionType::DirectConnection => { (LineType::Solid, ArrowType::Standard, ArrowDirection::Right) } ConnectionType::CrossContractConnection => { (LineType::Solid, ArrowType::Standard, ArrowDirection::Right) } ConnectionType::Emission => { (LineType::Solid, ArrowType::Standard, ArrowDirection::Right) } } } } impl CoreSyntaxFunctions for FlowChart { /// Returns a `FlowChart` struct to allow you to build the necessary markdown text. /// /// # Arguments /// /// * `direction` - The enum representation of the flow direction of the diagram /// /// # Examples /// /// ``` /// let mut flow_chart = FlowChart::new(FlowDirection::TD); /// ``` fn new(direction: FlowDirection) -> Self { // Instantiate the starting point for the diagram schema let mut schema_root = "flowchart ".to_string(); // Add in `direction` schema_root.push_str(direction.as_ref()); // Instantiate `FlowChart` let mut result = FlowChart { data: schema_root }; // Add a new line result.add_linebreak(None); result } /// Appends a linebreak & the preceding whitespace to the current data of the flow chart struct (i.e. `self.data`). /// /// # Arguments /// /// * `num_of_indents` - Optional number of indents to insert once the new line is added (default it 1) /// /// # Examples /// /// ``` /// let mut flow_chart = FlowChart::new(FlowDirection::TD); /// /// flow_chart.add_linebreak(None); /// ``` fn add_linebreak( &mut self, num_of_indents: Option<u8>, ) { // Get the number of indents to use let number_of_indents = num_of_indents.unwrap_or(1); // Add the new line self.data += "\n"; // Range over `number_of_indents` to add the appropriate number of tabs for _ in 0..number_of_indents { // Add in a tab self.data += "\t"; } } /// Creates a [Mermaid.js Node](https://mermaid-js.github.io/mermaid/#/flowchart?id=a-node-default) with the supplied attributes & appends it to the current data of the flow chart struct (i.e. `self.data`). /// /// # Arguments /// /// * `id` - The ID that will be assigned to this node /// * `class` - An optional class name to assign to the node /// * `shape` - The shape of the node /// * `inner_text` - The text to be displayed within the node /// /// # Examples /// /// ``` /// let mut flow_chart = FlowChart::new(FlowDirection::TD); /// /// let node_config = SyntaxConfigFile::FlowChart(ConfigFile::NodeConfig(NodeConfig { /// id: "A", /// class: None, /// shape: Shape::Circle, /// inner_text: "inner text", /// })); /// /// flow_chart.add_node(node_config); /// ``` fn add_node( &mut self, node_config: SyntaxConfigFile, ) { let node_config: NodeConfig = node_config .into_flow_chart() .unwrap() .into_node_config() .unwrap(); // Push the ID self.data.push_str(node_config.id); // Push the left shape flag self.data .push_str(node_config.shape.get_str("Left").unwrap()); // Push the inner text self.data.push_str(node_config.inner_text); // Push the left shape flag self.data .push_str(node_config.shape.get_str("Right").unwrap()); // If a class name was passed push it to `self.data` if let Some(class) = node_config.class { self.data.push_str(":::"); self.data.push_str(class); } } fn build_node_config<'a>( &self, node: &'a Node, id: Option<&'a str>, ) -> SyntaxConfigFile<'a> { SyntaxConfigFile::FlowChart(ObjectConfig::NodeConfig(NodeConfig { id: id.unwrap(), class: Some(node.scope.as_ref()), shape: self.get_shape_from_node(&node), inner_text: &node.name, })) } fn build_connection_config<'a>( &self, connection: &'a Connection, extra_length_num: Option<u8>, ) -> SyntaxConfigFile<'a> { let (line_type, arrow_type, arrow_direction) = self.get_line_and_arrow_type_from_connection(connection); SyntaxConfigFile::FlowChart(ObjectConfig::ConnectionConfig(ConnectionConfig { line_type, arrow_type, arrow_direction, extra_length_num, })) } /// Creates a [Mermaid.js Connection](https://mermaid-js.github.io/mermaid/#/flowchart?id=links-between-nodes) with the supplied attributes & appends it to the current data of the flow chart struct (i.e. `self.data`). /// /// # Arguments /// TODO: /// * `line_type` - The enum representation of the type of line you want /// * `arrow_type` - The enum representation of the type of arrow you want /// * `arrow_direction` - The enum representation of the direction you want the arrows to point /// * `extra_length_num` - An optional amount of additional flags to increase line length /// /// # Examples /// /// ``` /// let mut flow_chart = FlowChart::new(FlowDirection::TD); /// /// let node_config = SyntaxConfigFile::FlowChart(ConfigFile::NodeConfig(NodeConfig { /// id: "A", /// class: None, /// shape: Shape::Circle, /// inner_text: "inner text", /// })); /// /// let connection_config = SyntaxConfigFile::FlowChart(ConfigFile::ConnectionConfig(ConnectionConfig { /// line_type: LineType::Dashed, /// arrow_type: ArrowType::Standard, /// arrow_direction: ArrowDirection::Right, /// extra_length_num: None, /// })); /// /// flow_chart.add_node(node_config); /// flow_chart.add_connection(connection_config); /// ``` fn add_connection( &mut self, connection_config: SyntaxConfigFile, ) { let connection_config: ConnectionConfig = connection_config .into_flow_chart() .unwrap() .into_connection_config() .unwrap(); // Push a preceding space self.data.push_str(" "); // Depending on the arrow direction wanted make calls to `self.add_arrow` & `self.add_line` match connection_config.arrow_direction { ArrowDirection::BiDirectional => { self.add_arrow(connection_config.arrow_type, ArrowDirection::Left); self.add_line( connection_config.line_type, connection_config.extra_length_num, ); self.add_arrow(connection_config.arrow_type, ArrowDirection::Right) } ArrowDirection::Left => { self.add_arrow( connection_config.arrow_type, connection_config.arrow_direction, ); self.add_line( connection_config.line_type, connection_config.extra_length_num, ) } ArrowDirection::Right => { self.add_line( connection_config.line_type, connection_config.extra_length_num, ); self.add_arrow( connection_config.arrow_type, connection_config.arrow_direction, ) } ArrowDirection::None => { self.add_line( connection_config.line_type, connection_config.extra_length_num, ); } } // Push a trailing space self.data.push_str(" "); } fn return_schema(self) -> String { self.data } - blockimperiumdao 1
We tried this but have been unable to get it to work due to a signature issue when installing mermaid-cli.
==> Pouring mermaid-cli–8.11.0.arm64_big_sur.bottle.tar.gz
Error: Failed applying an ad-hoc signature to… - WillH
It might be an error after updating to bigsur.
See if this solution works for you @blockimperiumdao
MacBook Air M1 Big Sur 11.2 brew update error · Discussion #673 · Homebrew/discussions · GitHub
