Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Pack resolc.js and resolc.wasm into single file #173

Open
wants to merge 9 commits into
base: main
Choose a base branch
from
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
1 change: 1 addition & 0 deletions .github/workflows/build-revive-wasm.yml
Original file line number Diff line number Diff line change
Expand Up @@ -87,6 +87,7 @@ jobs:
path: |
${{ env.REVIVE_WASM_INSTALL_DIR }}/resolc.js
${{ env.REVIVE_WASM_INSTALL_DIR }}/resolc.wasm
${{ env.REVIVE_WASM_INSTALL_DIR }}/resolc_packed.js
retention-days: 1

test-revive-wasm:
Expand Down
1 change: 1 addition & 0 deletions Makefile
Original file line number Diff line number Diff line change
Expand Up @@ -30,6 +30,7 @@ install-npm:

install-wasm: install-npm
cargo build --target wasm32-unknown-emscripten -p revive-solidity --release --no-default-features
npm run build:package

install-llvm-builder:
cargo install --path crates/llvm-builder
Expand Down
63 changes: 63 additions & 0 deletions js/build.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,63 @@
const fs = require("fs");
const path = require("path");
const { execSync } = require("child_process");
const { minify } = require("terser");

const RESOLC_WASM_TARGET_DIR = path.join(
__dirname,
"../target/wasm32-unknown-emscripten/release",
);
const RESOLC_WASM = path.join(RESOLC_WASM_TARGET_DIR, "resolc.wasm");
const RESOLC_JS = path.join(RESOLC_WASM_TARGET_DIR, "resolc.js");
const RESOLC_JS_PACKED = path.join(RESOLC_WASM_TARGET_DIR, "resolc_packed.js");

const execShellCommand = (cmd) => {
return execSync(cmd, {
encoding: "utf-8",
maxBuffer: 1024 * 1024 * 100,
}).trim();
};

const wasmBase64 = execShellCommand(
`lz4c --no-frame-crc --best --favor-decSpeed "${RESOLC_WASM}" - | tail -c +8 | base64 -w 0`,
);

const wasmSize = fs.statSync(RESOLC_WASM).size;

const miniLz4 = fs.readFileSync(
path.join(__dirname, "utils/mini-lz4.js"),
"utf-8",
);
const base64DecToArr = fs.readFileSync(
path.join(__dirname, "utils/base64DecToArr.js"),
"utf-8",
);
const resolcJs = fs.readFileSync(RESOLC_JS, "utf-8");

const packedJsContent = `
let moduleArgs = { wasmBinary: (function(source, uncompressedSize) {
${miniLz4}
${base64DecToArr}
return uncompress(base64DecToArr(source), uncompressedSize);
})("${wasmBase64}", ${wasmSize}),
};

${resolcJs}

createRevive = createRevive.bind(null, moduleArgs);
`;

minify(packedJsContent)
.then((minifiedJs) => {
if (minifiedJs.error) {
console.error("Error during minification:", minifiedJs.error);
process.exit(1);
}

fs.writeFileSync(RESOLC_JS_PACKED, minifiedJs.code, "utf-8");
console.log(`Combined script written to ${RESOLC_JS_PACKED}`);
})
.catch((err) => {
console.error("Minification failed:", err);
process.exit(1);
});
90 changes: 54 additions & 36 deletions js/e2e/web.test.js
Original file line number Diff line number Diff line change
@@ -1,16 +1,16 @@
const { test, expect } = require('@playwright/test');
const fs = require('fs');
const path = require('path');
const { test, expect } = require("@playwright/test");
const fs = require("fs");
const path = require("path");

function loadFixture(fixture) {
const fixturePath = path.resolve(__dirname, `../fixtures/${fixture}`);
return JSON.parse(fs.readFileSync(fixturePath, 'utf-8'));
return JSON.parse(fs.readFileSync(fixturePath, "utf-8"));
}

async function runWorker(page, input) {
return await page.evaluate((input) => {
return new Promise((resolve, reject) => {
const worker = new Worker('worker.js');
const worker = new Worker("worker.js");
worker.postMessage(JSON.stringify(input));

worker.onmessage = (event) => {
Expand All @@ -26,62 +26,80 @@ async function runWorker(page, input) {
}, input);
}

test('should successfully compile valid Solidity code in browser', async ({ page }) => {
test("should successfully compile valid Solidity code in browser", async ({
page,
}) => {
await page.goto("http://127.0.0.1:8080");
await page.setContent("");
const standardInput = loadFixture('storage.json')
const standardInput = loadFixture("storage.json");
const result = await runWorker(page, standardInput);
expect(typeof result).toBe('string');

expect(typeof result).toBe("string");
let output = JSON.parse(result);
expect(output).toHaveProperty('contracts');
expect(output.contracts['fixtures/storage.sol']).toHaveProperty('Storage');
expect(output.contracts['fixtures/storage.sol'].Storage).toHaveProperty('abi');
expect(output.contracts['fixtures/storage.sol'].Storage).toHaveProperty('evm');
expect(output.contracts['fixtures/storage.sol'].Storage.evm).toHaveProperty('bytecode');
expect(output).toHaveProperty("contracts");
expect(output.contracts["fixtures/storage.sol"]).toHaveProperty("Storage");
expect(output.contracts["fixtures/storage.sol"].Storage).toHaveProperty(
"abi",
);
expect(output.contracts["fixtures/storage.sol"].Storage).toHaveProperty(
"evm",
);
expect(output.contracts["fixtures/storage.sol"].Storage.evm).toHaveProperty(
"bytecode",
);
});

test('should successfully compile large valid Solidity code in browser', async ({ page }) => {
test("should successfully compile large valid Solidity code in browser", async ({
page,
}) => {
await page.goto("http://127.0.0.1:8080");
await page.setContent("");
const standardInput = loadFixture('token.json')
const standardInput = loadFixture("token.json");
const result = await runWorker(page, standardInput);
expect(typeof result).toBe('string');

expect(typeof result).toBe("string");
let output = JSON.parse(result);
expect(output).toHaveProperty('contracts');
expect(output.contracts['fixtures/token.sol']).toHaveProperty('MyToken');
expect(output.contracts['fixtures/token.sol'].MyToken).toHaveProperty('abi');
expect(output.contracts['fixtures/token.sol'].MyToken).toHaveProperty('evm');
expect(output.contracts['fixtures/token.sol'].MyToken.evm).toHaveProperty('bytecode');
expect(output).toHaveProperty("contracts");
expect(output.contracts["fixtures/token.sol"]).toHaveProperty("MyToken");
expect(output.contracts["fixtures/token.sol"].MyToken).toHaveProperty("abi");
expect(output.contracts["fixtures/token.sol"].MyToken).toHaveProperty("evm");
expect(output.contracts["fixtures/token.sol"].MyToken.evm).toHaveProperty(
"bytecode",
);
});

test('should throw an error for invalid Solidity code in browser', async ({ page }) => {
test("should throw an error for invalid Solidity code in browser", async ({
page,
}) => {
await page.goto("http://127.0.0.1:8080");
await page.setContent("");
const standardInput = loadFixture('invalid_contract_content.json')
const standardInput = loadFixture("invalid_contract_content.json");
const result = await runWorker(page, standardInput);

expect(typeof result).toBe('string');
expect(typeof result).toBe("string");
let output = JSON.parse(result);
expect(output).toHaveProperty('errors');
expect(output).toHaveProperty("errors");
expect(Array.isArray(output.errors)).toBeTruthy(); // Check if it's an array
expect(output.errors.length).toBeGreaterThan(0);
expect(output.errors[0]).toHaveProperty('type');
expect(output.errors[0].type).toContain('ParserError');
expect(output.errors[0]).toHaveProperty("type");
expect(output.errors[0].type).toContain("ParserError");
});

test('should return not found error for missing imports in browser', async ({page}) => {
test("should return not found error for missing imports in browser", async ({
page,
}) => {
await page.goto("http://127.0.0.1:8080");
await page.setContent("");
const standardInput = loadFixture('missing_import.json')
const standardInput = loadFixture("missing_import.json");
const result = await runWorker(page, standardInput);
expect(typeof result).toBe('string');

expect(typeof result).toBe("string");
let output = JSON.parse(result);
expect(output).toHaveProperty('errors');
expect(output).toHaveProperty("errors");
expect(Array.isArray(output.errors)).toBeTruthy(); // Check if it's an array
expect(output.errors.length).toBeGreaterThan(0);
expect(output.errors[0]).toHaveProperty('message');
expect(output.errors[0].message).toContain('Source "nonexistent/console.sol" not found');
expect(output.errors[0]).toHaveProperty("message");
expect(output.errors[0].message).toContain(
'Source "nonexistent/console.sol" not found',
);
});
97 changes: 50 additions & 47 deletions js/embed/pre.js
Original file line number Diff line number Diff line change
@@ -1,54 +1,57 @@
var Module = {
stdinData: null,
stdinDataPosition: 0,
stdoutData: [],
stderrData: [],
Module.stdinData = null;
Module.stdinDataPosition = 0;
Module.stdoutData = [];
Module.stderrData = [];

// Function to read and return all collected stdout data as a string
readFromStdout: function() {
if (!this.stdoutData.length) return "";
const decoder = new TextDecoder('utf-8');
const data = decoder.decode(new Uint8Array(this.stdoutData));
this.stdoutData = [];
return data;
},
// Method to read all collected stdout data
Module.readFromStdout = function () {
if (!Module.stdoutData.length) return "";
const decoder = new TextDecoder("utf-8");
const data = decoder.decode(new Uint8Array(Module.stdoutData));
Module.stdoutData = [];
return data;
};

// Function to read and return all collected stderr data as a string
readFromStderr: function() {
if (!this.stderrData.length) return "";
const decoder = new TextDecoder('utf-8');
const data = decoder.decode(new Uint8Array(this.stderrData));
this.stderrData = [];
return data;
},
// Method to read all collected stderr data
Module.readFromStderr = function () {
if (!Module.stderrData.length) return "";
const decoder = new TextDecoder("utf-8");
const data = decoder.decode(new Uint8Array(Module.stderrData));
Module.stderrData = [];
return data;
};

// Function to set input data for stdin
writeToStdin: function(data) {
const encoder = new TextEncoder();
this.stdinData = encoder.encode(data);
this.stdinDataPosition = 0;
},
// Method to write data to stdin
Module.writeToStdin = function (data) {
const encoder = new TextEncoder();
Module.stdinData = encoder.encode(data);
Module.stdinDataPosition = 0;
};

// `preRun` is called before the program starts running
preRun: function() {
// Define a custom stdin function
function customStdin() {
if (!Module.stdinData || Module.stdinDataPosition >= Module.stdinData.length) {
return null; // End of input (EOF)
}
return Module.stdinData[Module.stdinDataPosition++];
}
// Override the `preRun` method to customize file system initialization
Module.preRun = Module.preRun || [];
Module.preRun.push(function () {
// Custom stdin function
function customStdin() {
if (
!Module.stdinData ||
Module.stdinDataPosition >= Module.stdinData.length
) {
return null; // End of input (EOF)
}
return Module.stdinData[Module.stdinDataPosition++];
}

// Define a custom stdout function
function customStdout(char) {
Module.stdoutData.push(char);
}
// Custom stdout function
function customStdout(char) {
Module.stdoutData.push(char);
}

// Define a custom stderr function
function customStderr(char) {
Module.stderrData.push(char);
}
// Custom stderr function
function customStderr(char) {
Module.stderrData.push(char);
}

FS.init(customStdin, customStdout, customStderr);
},
};
// Initialize the FS (File System) with custom handlers
FS.init(customStdin, customStdout, customStderr);
});
55 changes: 30 additions & 25 deletions js/embed/soljson_interface.js
Original file line number Diff line number Diff line change
@@ -1,29 +1,34 @@
mergeInto(LibraryManager.library, {
soljson_compile: function(inputPtr, inputLen) {
const inputJson = UTF8ToString(inputPtr, inputLen);
const output = Module.soljson.cwrap('solidity_compile', 'string', ['string'])(inputJson);
return stringToNewUTF8(output);
},
soljson_version: function() {
const version = Module.soljson.cwrap("solidity_version", "string", [])();
return stringToNewUTF8(version);
},
resolc_compile: function(inputPtr, inputLen) {
const inputJson = UTF8ToString(inputPtr, inputLen);
var revive = createRevive();
revive.writeToStdin(inputJson);
soljson_compile: function (inputPtr, inputLen) {
const inputJson = UTF8ToString(inputPtr, inputLen);
const output = Module.soljson.cwrap("solidity_compile", "string", [
"string",
])(inputJson);
return stringToNewUTF8(output);
},
soljson_version: function () {
const version = Module.soljson.cwrap("solidity_version", "string", [])();
return stringToNewUTF8(version);
},
resolc_compile: function (inputPtr, inputLen) {
const inputJson = UTF8ToString(inputPtr, inputLen);
var revive = createRevive();
revive.writeToStdin(inputJson);

// Call main on the new instance
const result = revive.callMain(['--recursive-process']);
// Call main on the new instance
const result = revive.callMain(["--recursive-process"]);

if (result) {
const stderrString = revive.readFromStderr();
const error = JSON.stringify({ type: 'error', message: stderrString || "Unknown error" });
return stringToNewUTF8(error);
} else {
const stdoutString = revive.readFromStdout();
const json = JSON.stringify({ type: 'success', data: stdoutString });
return stringToNewUTF8(json);
}
},
if (result) {
const stderrString = revive.readFromStderr();
const error = JSON.stringify({
type: "error",
message: stderrString || "Unknown error",
});
return stringToNewUTF8(error);
} else {
const stdoutString = revive.readFromStdout();
const json = JSON.stringify({ type: "success", data: stdoutString });
return stringToNewUTF8(json);
}
},
});
Loading
Loading