Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
21 changes: 19 additions & 2 deletions Dockerfile
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
# Start from a rust base image
FROM rust:1.76.0 as base
FROM rust:1.86.0 as base

# Set the current directory
WORKDIR /app
Expand All @@ -20,13 +20,16 @@ RUN cargo install cargo-make
RUN apt-get --yes update
RUN apt-get --yes upgrade
ENV NVM_DIR /usr/local/nvm
ENV NODE_VERSION v18.16.1
ENV NODE_VERSION v22.14.0
RUN mkdir -p /usr/local/nvm && apt-get update && echo "y" | apt-get install curl
RUN curl -o- https://raw.githubusercontent.com/nvm-sh/nvm/v0.39.7/install.sh | bash
RUN /bin/bash -c "source $NVM_DIR/nvm.sh && nvm install $NODE_VERSION && nvm use --delete-prefix $NODE_VERSION"
ENV NODE_PATH $NVM_DIR/versions/node/$NODE_VERSION/bin
ENV PATH $NODE_PATH:$PATH

# Create public directory if it doesn't exist
RUN mkdir -p /app/packages/frontend/public

# Install dependencies
RUN cargo make deps-wasm
RUN cargo make deps-npm
Expand All @@ -35,6 +38,7 @@ RUN cargo make deps-npm
RUN cargo make build-server
RUN cargo make build-bindings
RUN cargo make build-app
RUN cargo make build-frontend
RUN cargo make build-backend


Expand All @@ -45,8 +49,21 @@ FROM nestybox/ubuntu-jammy-systemd-docker:latest

# Copy the built files
COPY --from=builder /app/packages/app/dist /app/packages/app/dist
COPY --from=builder /app/packages/frontend/.next /app/packages/frontend/.next
COPY --from=builder /app/packages/frontend/public /app/packages/frontend/public
COPY --from=builder /app/packages/frontend/package.json /app/packages/frontend/package.json
COPY --from=builder /app/target/release/backend /app/target/release/backend

# Install Node.js in the final image for running Next.js
RUN apt-get update && apt-get install -y curl
ENV NVM_DIR /usr/local/nvm
ENV NODE_VERSION v22.14.0
RUN mkdir -p /usr/local/nvm
RUN curl -o- https://raw.githubusercontent.com/nvm-sh/nvm/v0.40.2/install.sh | bash
RUN /bin/bash -c "source $NVM_DIR/nvm.sh && nvm install $NODE_VERSION && nvm use --delete-prefix $NODE_VERSION"
ENV NODE_PATH $NVM_DIR/versions/node/$NODE_VERSION/bin
ENV PATH $NODE_PATH:$PATH

# Startup scripts
COPY sysbox/on-start.sh /usr/bin
RUN chmod +x /usr/bin/on-start.sh
Expand Down
4 changes: 2 additions & 2 deletions Makefile.toml
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,7 @@ npm install

[tasks.deps-docker]
script = '''
docker pull ghcr.io/hyperledger/solang@sha256:8776a9bd756664f7bf8414710d1a799799bf6fedc1c8f9f0bda17e76749dea7a
docker pull ghcr.io/hyperledger/solang@sha256:e6f687910df5dd9d4f5285aed105ae0e6bcae912db43e8955ed4d8344d49785d
'''

[tasks.deps]
Expand Down Expand Up @@ -118,7 +118,7 @@ docker build -t solang-playground .
script = '''
docker run \
--runtime=sysbox-runc \
--name playground \
--name playground-makee \
--detach \
--volume /tmp:/tmp \
--publish 9000:9000 \
Expand Down
55 changes: 34 additions & 21 deletions crates/backend/src/services/sandbox.rs
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,8 @@ use tokio::process::Command;
use crate::services::{CompilationRequest, CompilationResult};

const TIMEOUT: Duration = Duration::from_secs(60);
const DOCKER_IMAGE_BASE_NAME: &str = "ghcr.io/hyperledger/solang";
// const DOCKER_IMAGE_BASE_NAME: &str = "ghcr.io/hyperledger-solang/solang@sha256:e6f687910df5dd9d4f5285aed105ae0e6bcae912db43e8955ed4d8344d49785d";
const DOCKER_IMAGE_BASE_NAME: &str = "ghcr.io/hyperledger-solang/solang:latest";
const DOCKER_WORKDIR: &str = "/builds/contract/";
const DOCKER_OUTPUT: &str = "/playground-result";

Expand All @@ -29,11 +30,12 @@ macro_rules! docker_command {

/// Builds the compile command using solang docker image
pub fn build_compile_command(input_file: &Path, output_dir: &Path) -> Command {
println!("ip file: {:?}\nop dir: {:?}", input_file, output_dir);
// Base docker command
let mut cmd = docker_command!(
"run",
"--detach",
"--rm",
// "--rm",
"-it",
"--cap-drop=ALL",
"--cap-add=DAC_OVERRIDE",
Expand Down Expand Up @@ -65,15 +67,12 @@ pub fn build_compile_command(input_file: &Path, output_dir: &Path) -> Command {
cmd.arg("--volume").arg(&mount_output_dir);

// Using the solang image
cmd.arg(format!(
"{}@sha256:8776a9bd756664f7bf8414710d1a799799bf6fedc1c8f9f0bda17e76749dea7a",
DOCKER_IMAGE_BASE_NAME
));
cmd.arg(DOCKER_IMAGE_BASE_NAME);

// Building the compile command
let remove_command = format!("rm -rf {}*.wasm {}*.contract", DOCKER_OUTPUT, DOCKER_OUTPUT);
let compile_command = format!(
"solang compile --target polkadot -o /playground-result {} > /playground-result/stdout.log 2> /playground-result/stderr.log",
"solang compile --target soroban -o /playground-result {} > /playground-result/stdout.log 2> /playground-result/stderr.log",
file_name
);
let sh_command = format!("{} && {}", remove_command, compile_command);
Expand Down Expand Up @@ -102,7 +101,9 @@ impl Sandbox {

fs::set_permissions(&output_dir, PermissionsExt::from_mode(0o777))
.context("failed to set output permissions")?;


File::create(&input_file).context("failed to create input file")?;

Ok(Sandbox {
scratch,
input_file,
Expand All @@ -115,14 +116,15 @@ impl Sandbox {
self.write_source_code(&req.source)?;

let command = build_compile_command(&self.input_file, &self.output_dir);
println!("Executing command: \n{:#?}", command);
// println!("Executing command: \n{:#?}", command);

let output = run_command(command)?;
println!("out: {:?}", output);
let file = fs::read_dir(&self.output_dir)
.context("failed to read output directory")?
.flatten()
.map(|entry| entry.path())
.find(|path| path.extension() == Some(OsStr::new("contract")));
.find(|path| path.extension() == Some(OsStr::new("wasm")));

// The file `stdout.log` is in the same directory as the contract file
let compile_log_stdout_file_path = fs::read_dir(&self.output_dir)
Expand Down Expand Up @@ -187,24 +189,32 @@ impl Sandbox {

/// A helper function to write the source code to the input file
fn write_source_code(&self, code: &str) -> Result<()> {
println!("writing to {:?}", self.input_file);
fs::write(&self.input_file, code).context("failed to write source code")?;
match fs::read_to_string(&self.input_file) {
Ok(content) => println!("Successfully read: {:?}", content),
Err(e) => eprintln!("Error reading file: {}", e),
}
fs::set_permissions(&self.input_file, PermissionsExt::from_mode(0o777))
.context("failed to set source permissions")?;
let s: String = code.chars().take(40).collect();
println!("Code: {:?}", s);
println!("Wrote {} bytes of source to {}", code.len(), self.input_file.display());
Ok(())
}
}

/// Reads a file from the given path
fn read(path: &Path) -> Result<Option<Vec<u8>>> {
println!("reading: {:?}", path);
let f = match File::open(path) {
Ok(f) => f,
Err(ref e) if e.kind() == ErrorKind::NotFound => return Ok(None),
e => e.context("failed to open file")?,
};
let mut f = BufReader::new(f);
let metadata = fs::metadata(path).expect("failed to read metadata");

println!("meta: {:?}", metadata);
let mut buffer = vec![0; metadata.len() as usize];
f.read_exact(&mut buffer).expect("buffer overflow");
Ok(Some(buffer))
Expand All @@ -219,14 +229,15 @@ async fn run_command(mut command: Command) -> Result<std::process::Output> {
use std::os::unix::process::ExitStatusExt;

let timeout = TIMEOUT;
println!("executing command!");
println!("executing command: {:?}", command);
let output = command.output().await.context("failed to start compiler")?;

let stdout = String::from_utf8_lossy(&output.stdout);
let id = stdout.lines().next().context("missing compiler ID")?.trim();
let stderr = &output.stderr;

let mut command = docker_command!("wait", id);
println!("ID: {:?}\nwait: {:?}", id, command);

let timed_out = match tokio::time::timeout(timeout, command.output()).await {
Ok(Ok(o)) => {
Expand All @@ -239,17 +250,19 @@ async fn run_command(mut command: Command) -> Result<std::process::Output> {
};

let mut command = docker_command!("logs", id);
println!("logs: {:?}", command);
let mut output = command.output().await.context("failed to get output from compiler")?;

let mut command = docker_command!(
"rm", // Kills container if still running
"--force", id
);
command.stdout(std::process::Stdio::null());
command.status().await.context("failed to remove compiler")?;
println!("op: {:?}", output);
// let mut command = docker_command!(
// "rm", // Kills container if still running
// "--force", id
// );
// println!("rm: {:?}", command);
// command.stdout(std::process::Stdio::null());
// command.status().await.context("failed to remove compiler")?;

let code = timed_out.context("compiler timed out")?;

println!("timedout: {:?}", code);
output.status = code;
output.stderr = stderr.to_owned();

Expand Down
3 changes: 3 additions & 0 deletions docker-compose.yml
Original file line number Diff line number Diff line change
Expand Up @@ -8,4 +8,7 @@ services:
- /tmp:/tmp
ports:
- "9000:9000"
- "3000:3000" # Expose Next.js port
environment:
- DOCKER_ENV=true
runtime: sysbox-runc
3 changes: 2 additions & 1 deletion packages/app/src/app.ts
Original file line number Diff line number Diff line change
Expand Up @@ -58,7 +58,7 @@ export const downloadBlob = (code: number[]): void => {
const blob = new Blob([new Uint8Array(code).buffer]);

const a = document.createElement('a');
a.download = 'result.contract';
a.download = 'result.wasm';
a.href = URL.createObjectURL(blob);
a.dataset.downloadurl = ['application/json', a.download, a.href].join(':');
a.style.display = 'none';
Expand Down Expand Up @@ -171,6 +171,7 @@ export default class App {
);

console.log("Compilation result: ", result);
client.printToConsole(proto.MessageType.Info, "Compilation result: " + JSON.stringify(result));

// If the compilation was successful, download the wasm blob and print a success message
if (result.type === 'OK') {
Expand Down
9 changes: 7 additions & 2 deletions packages/frontend/next.config.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,14 +3,19 @@ import type { NextConfig } from "next";
const nextConfig: NextConfig = {
/* config options here */
async rewrites() {
// In Docker, use the backend service running on the same container
const backendUrl = process.env.DOCKER_ENV
? "http://localhost:9000"
: "http://localhost:4444";

return [
{
source: "/compile",
destination: "http://localhost:4444/compile",
destination: `${backendUrl}/compile`,
},
{
source: "/health",
destination: "http://localhost:4444/health",
destination: `${backendUrl}/health`,
},
];
},
Expand Down
6 changes: 3 additions & 3 deletions packages/frontend/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@
"private": true,
"scripts": {
"dev": "next dev",
"build": "next build",
"build": "set NODE_ENV=production & next build",
"start": "next start",
"lint": "next lint"
},
Expand Down Expand Up @@ -37,8 +37,8 @@
"next": "15.0.3",
"next-auth": "^5.0.0-beta.25",
"next-themes": "^0.4.4",
"react": "^18.3.1",
"react-dom": "^18.3.1",
"react": "^18",
"react-dom": "^18",
"react-icons": "^5.4.0",
"sonner": "^1.7.4",
"tailwind-merge": "^2.5.5",
Expand Down
3 changes: 2 additions & 1 deletion packages/frontend/src/app/state.ts
Original file line number Diff line number Diff line change
Expand Up @@ -30,7 +30,8 @@ export const useSettingsStore = create(
export enum SidebarView {
FILE_EXPLORER = "FILE-EXPLORER",
SETTINGS = "SETTINGS",
CONTRACT = "CONTRACT",
COMPILE = "COMPILE",
DEPLOY = "DEPLOY",
}

export const useAppStore = create(
Expand Down
85 changes: 85 additions & 0 deletions packages/frontend/src/components/CompileExplorer.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,85 @@
"use client";

import { store } from "@/state";
import { useSelector } from "@xstate/store/react";
import React, { useEffect, useState } from "react";
import Hide from "./Hide";
import { Button } from "./ui/button";
import { useExplorerItem } from "@/state/hooks";
import { FileType } from "@/types/explorer";
import { get } from "lodash";
import useCompile from "@/hooks/useCompile";

function CompileExplorer() {
const { compileFile } = useCompile();

const selected = useSelector(store, (state) => state.context.currentFile);
const obj = useSelector(store, (state) => get(state.context, selected || '')) as FileType;
const [name, setName] = useState<string>('');


const [isSelected, setSelected] = useState<boolean>(!1);

console.log('[tur] selected', selected)

useEffect(() => {
if(selected && selected !== 'home') {
setName(obj.name);
setSelected(!0);
}
}, [selected])

const handleCompile = async () => {
const result = await compileFile();
selected && selected !== 'home' &&
store.send({ type: "addCompiled", path: selected, name });
console.log('[tur] compilation result', result);
}

return (
<div className=" ">
<div className="">
<h2 className="text-base uppercase px-3">Compile Explorer</h2>
</div>
<div className="mt-10 relative z-10 px-3 overflow-x-clip">
<Button
disabled={!isSelected}
onClick={handleCompile}
variant="outline" size="lg"
>
Compile {isSelected ? name : ''}
</Button>
{/* <div className="flex flex-col gap-2">
{
keys.map(k => (
<div key={k} >
<p
style={{cursor: 'pointer'}}
onClick={e => toggleCollapsed(e, k)}
>
{`${k.substring(0, 5)}..${k.substring(50)}`}
</p>
<div style={{display: 'none'}}>
{
deployed[k].map(item => (
<InvokeFunction key={item.name} method={item} />
))
}
</div>
</div>
)
)
}
</div> */}

{/* <Hide open={idl.length === 0}>
<div className="text-center">
<p>No Function or IDL Specified</p>
</div>
</Hide> */}
</div>
</div>
);
}

export default CompileExplorer;
Loading