stackrup 0.1.5

Stackrup into the world of micro-rollups using Stackr CLI
use std::env;
use std::fs::{self, File};
use std::io::{self, ErrorKind};
use std::path::PathBuf;

pub fn build_index_content() -> &'static str {
  r#"
  // Execution step
  // Read inputs from stdin
  const stfInputs = readInput();
  var newState = run(stfInputs.currentState, stfInputs.actions);
  // Write the result to stdout
  writeOutput(newState);
  
  // Read input from stdin
  function readInput() {
    const chunkSize = 1024;
    const inputChunks = [];
    let totalBytes = 0;
  
    // Read all the available bytes
    while (1) {
      const buffer = new Uint8Array(chunkSize);
      // Stdin file descriptor
      const fd = 0;
      const bytesRead = Javy.IO.readSync(fd, buffer);
  
      totalBytes += bytesRead;
      if (bytesRead === 0) {
        break;
      }
      inputChunks.push(buffer.subarray(0, bytesRead));
    }
  
    // Assemble input into a single Uint8Array
    const { finalBuffer } = inputChunks.reduce(
      (context, chunk) => {
        context.finalBuffer.set(chunk, context.bufferOffset);
        context.bufferOffset += chunk.length;
        return context;
      },
      { bufferOffset: 0, finalBuffer: new Uint8Array(totalBytes) },
    );
  
    return JSON.parse(new TextDecoder().decode(finalBuffer));
  }
  
  function writeOutput(output) {
    const encodedOutput = new TextEncoder().encode(JSON.stringify(output));
    const buffer = new Uint8Array(encodedOutput);
    // Stdout file descriptor
    const fd = 1;
    Javy.IO.writeSync(fd, buffer);
  }"#
}

pub fn index_content() -> &'static str {
    r#"import {
        ActionSchema,
        ExecutorEvents,
        FIFOStrategy,
        MicroRollup,
        StateMachine,
        actionEventsEmitter,
        builderEventsEmitter,
        executorEventsEmitter,
        ActionEvents
      } from "@stackr/stackr-js";
      import bodyParser from "body-parser";
      import express, { Request, Response } from "express";
      import { mockStackrConfig } from "../stackr.config";
      import { CounterActionInput, CounterRollup, counterSTF } from "./state";
      import { ZeroAddress } from "ethers";
      
      const rollup = async () => {
        const counterRollup = new CounterRollup(0);
        const counterFsm = new StateMachine({
          state: counterRollup,
          stf: counterSTF,
        });
        const actionInput = new ActionSchema<CounterActionInput>("update-counter");
        const buildStrategy = new FIFOStrategy();
      
        const { state, actions } = await MicroRollup({
          config: mockStackrConfig,
          useState: counterFsm,
          useAction: actionInput,
          useBuilder: { strategy: buildStrategy, autorun: true },
        });
      
        return { state, actions };
      };
      
      const app = express();
      app.use(bodyParser.json());
      const { actions, state } = await rollup();
      
      app.get("/", (req: Request, res: Response) => {
        res.send({ currentCount: state.get().state.getState() });
      });
      
      app.post("/", async (req: Request, res: Response) => {
        const schema = actions.getSchema("update-counter");
      
        if (!schema) {
          res.status(400).send({ message: "error" });
          return;
        }
      
        const newAction = schema.newAction(req.body);
        const ack = await actions.submit(newAction);
        res.status(201).send({ ack });
      });
      
      app.listen(3000, () => {
        console.log("listening on port 3000");
      });
      
      actionEventsEmitter.on(ActionEvents.SUBMIT_ACTION, (data) => {
        console.log("submit_action - Event triggered : ", data.payload);
      });
      
      executorEventsEmitter.on(ExecutorEvents.EXECUTE_SINGLE, (data) => {
        console.log("execute_single - Event triggered : ", data);
      });
      "#
}

pub fn state_content() -> &'static str {
    r#"import { RollupState, STF } from "@stackr/stackr-js/execution";
    import { BytesLike, solidityPackedKeccak256 } from "ethers";
    import MerkleTree from "merkletreejs";
    
    export type StateVariable = {
      address: string;
      balance: number;
    }[];
    
    export interface TransferInput {
      to: string;
      from: string;
      amount: number;
    }
    
    class BetterMerkleTree {
      public merkleTree: MerkleTree;
      public leaves: any[];
    
      constructor(leaves: any[]) {
        this.merkleTree = this.createTree(leaves);
        this.leaves = leaves;
      }
    
      createTree(leaves: any[]) {
        const hashedLeaves = leaves.map((leaf: any) => {
          return solidityPackedKeccak256(
            ["address", "uint"],
            [leaf.address, leaf.balance],
          );
        });
        return new MerkleTree(hashedLeaves);
      }
    }
    
    export class AccountsRollup extends RollupState<
      StateVariable,
      BetterMerkleTree>
    {
      constructor(leaves: StateVariable) {
        super(leaves);
      }
    
      createTransport(state: StateVariable): BetterMerkleTree {
        const newTree = new BetterMerkleTree(state);
        return newTree;
      }
    
      getState(): StateVariable {
        return this.transport.leaves;
      }
    
      calculateRoot(): BytesLike {
        return this.transport.merkleTree.getHexRoot();
      }
    }
    
    export const transferSTF: STF<AccountsRollup, TransferInput> = {
      identifier: "tokenTransfer",
    
      apply(inputs: TransferInput, state: AccountsRollup): void {
        let newState = state.getState();
    
        const indexTo = newState.findIndex((leaf) => leaf.address === inputs.to);
        const indexFrom = newState.findIndex(
          (leaf) => leaf.address === inputs.from,
        );
    
        newState[indexTo].balance += inputs.amount;
        newState[indexFrom].balance -= inputs.amount;
    
        state.transport.leaves = newState;
        // TODO : consider better approach
      },
    };
    "#
}

pub fn utils_content() -> &'static str {
    r#"import { Domain, EIP712Types } from "@stackr/stackr-js";
    import { ethers } from "ethers";
    
    const getUserInput = async (types: EIP712Types, domain: Domain) => {
      const w = ethers.Wallet.createRandom();
    
      const addr1 = ethers.hexlify(ethers.randomBytes(20));
    
      const payload = { type: "increment" };
      const signature = await w.signTypedData(domain, types, payload);
    
      return {
        data: {
          msgSender: w.address,
          payload,
          signature,
        },
      };
    };
    "#
}

pub fn tsconfig_content() -> &'static str {
    r#"{
        "compilerOptions": {
          "lib": ["ES2020"],
          "module": "ES2020",
          "target": "ES2020",
          "moduleResolution": "bundler",
          "moduleDetection": "force",
          "allowImportingTsExtensions": true,
          "noEmit": true,
          "composite": true,
          "strict": true,
          "downlevelIteration": true,
          "skipLibCheck": true,
          "jsx": "react-jsx",
          "allowSyntheticDefaultImports": true,
          "forceConsistentCasingInFileNames": true,
          "allowJs": true,
          "types": [
            "bun-types" // add Bun global
          ]
        }
      }
      "#
}

pub fn filewriter(project_name: &str) -> io::Result<()> {
    // Get the current directory as a PathBuf
    let current_dir = env::current_dir()?;

    let destination_file_path = current_dir.join(format!("{}/src/state.ts", &project_name));
    let destination_file_path1 = current_dir.join(format!("{}/src/index.ts", &project_name));

    // println!("The destination directory is {}", destination_file_path.display());

    let _ = fs::copy(
        "./src/filehandler/examples/state.ts",
        &destination_file_path,
    )?;
    let _ = fs::copy(
        "./src/filehandler/examples/index.ts",
        &destination_file_path1,
    )?;

    println!("Setting up example project completed");
    Ok(())
}