Compare commits

...

9 Commits

Author SHA1 Message Date
Sebastian Miasojed ff6bb5593d Fix package creation issue 2025-01-24 16:02:01 +01:00
Sebastian Miasojed 3035542a1c Add minification for resolc_packed.js 2025-01-24 15:37:49 +01:00
Sebastian Miasojed e9d3ec2079 Rollback soljson cleaning 2025-01-23 17:20:11 +01:00
Sebastian Miasojed e2ccdaae00 Add Wasm compression 2025-01-23 16:26:20 +01:00
Sebastian Miasojed a4e29b3f3e Apply revive comments 2025-01-23 15:32:54 +01:00
Sebastian Miasojed 66534f4e8c Allow GC to do the cleanup 2025-01-23 14:46:04 +01:00
Sebastian Miasojed 66975af7bc Upload resolc_packed.js from GHA 2025-01-23 12:12:16 +01:00
Sebastian Miasojed 82f83c910a Add again resolc.wasm link 2025-01-23 12:05:56 +01:00
Sebastian Miasojed 8a18f08aff Pack resolc.wasm and resolc.js to resolc_packed.js 2025-01-23 11:59:50 +01:00
20 changed files with 642 additions and 377 deletions
+1
View File
@@ -87,6 +87,7 @@ jobs:
path: | path: |
${{ env.REVIVE_WASM_INSTALL_DIR }}/resolc.js ${{ env.REVIVE_WASM_INSTALL_DIR }}/resolc.js
${{ env.REVIVE_WASM_INSTALL_DIR }}/resolc.wasm ${{ env.REVIVE_WASM_INSTALL_DIR }}/resolc.wasm
${{ env.REVIVE_WASM_INSTALL_DIR }}/resolc_packed.js
retention-days: 1 retention-days: 1
test-revive-wasm: test-revive-wasm:
+1
View File
@@ -30,6 +30,7 @@ install-npm:
install-wasm: install-npm install-wasm: install-npm
cargo build --target wasm32-unknown-emscripten -p revive-solidity --release --no-default-features cargo build --target wasm32-unknown-emscripten -p revive-solidity --release --no-default-features
npm run build:package
install-llvm-builder: install-llvm-builder:
cargo install --path crates/llvm-builder cargo install --path crates/llvm-builder
+63
View File
@@ -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);
});
+51 -33
View File
@@ -1,16 +1,16 @@
const { test, expect } = require('@playwright/test'); const { test, expect } = require("@playwright/test");
const fs = require('fs'); const fs = require("fs");
const path = require('path'); const path = require("path");
function loadFixture(fixture) { function loadFixture(fixture) {
const fixturePath = path.resolve(__dirname, `../fixtures/${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) { async function runWorker(page, input) {
return await page.evaluate((input) => { return await page.evaluate((input) => {
return new Promise((resolve, reject) => { return new Promise((resolve, reject) => {
const worker = new Worker('worker.js'); const worker = new Worker("worker.js");
worker.postMessage(JSON.stringify(input)); worker.postMessage(JSON.stringify(input));
worker.onmessage = (event) => { worker.onmessage = (event) => {
@@ -26,62 +26,80 @@ async function runWorker(page, input) {
}, 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.goto("http://127.0.0.1:8080");
await page.setContent(""); await page.setContent("");
const standardInput = loadFixture('storage.json') const standardInput = loadFixture("storage.json");
const result = await runWorker(page, standardInput); const result = await runWorker(page, standardInput);
expect(typeof result).toBe('string'); expect(typeof result).toBe("string");
let output = JSON.parse(result); let output = JSON.parse(result);
expect(output).toHaveProperty('contracts'); expect(output).toHaveProperty("contracts");
expect(output.contracts['fixtures/storage.sol']).toHaveProperty('Storage'); 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(
expect(output.contracts['fixtures/storage.sol'].Storage).toHaveProperty('evm'); "abi",
expect(output.contracts['fixtures/storage.sol'].Storage.evm).toHaveProperty('bytecode'); );
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.goto("http://127.0.0.1:8080");
await page.setContent(""); await page.setContent("");
const standardInput = loadFixture('token.json') const standardInput = loadFixture("token.json");
const result = await runWorker(page, standardInput); const result = await runWorker(page, standardInput);
expect(typeof result).toBe('string'); expect(typeof result).toBe("string");
let output = JSON.parse(result); let output = JSON.parse(result);
expect(output).toHaveProperty('contracts'); expect(output).toHaveProperty("contracts");
expect(output.contracts['fixtures/token.sol']).toHaveProperty('MyToken'); 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("abi");
expect(output.contracts['fixtures/token.sol'].MyToken).toHaveProperty('evm'); expect(output.contracts["fixtures/token.sol"].MyToken).toHaveProperty("evm");
expect(output.contracts['fixtures/token.sol'].MyToken.evm).toHaveProperty('bytecode'); 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.goto("http://127.0.0.1:8080");
await page.setContent(""); await page.setContent("");
const standardInput = loadFixture('invalid_contract_content.json') const standardInput = loadFixture("invalid_contract_content.json");
const result = await runWorker(page, standardInput); const result = await runWorker(page, standardInput);
expect(typeof result).toBe('string'); expect(typeof result).toBe("string");
let output = JSON.parse(result); 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(Array.isArray(output.errors)).toBeTruthy(); // Check if it's an array
expect(output.errors.length).toBeGreaterThan(0); expect(output.errors.length).toBeGreaterThan(0);
expect(output.errors[0]).toHaveProperty('type'); expect(output.errors[0]).toHaveProperty("type");
expect(output.errors[0].type).toContain('ParserError'); 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.goto("http://127.0.0.1:8080");
await page.setContent(""); await page.setContent("");
const standardInput = loadFixture('missing_import.json') const standardInput = loadFixture("missing_import.json");
const result = await runWorker(page, standardInput); const result = await runWorker(page, standardInput);
expect(typeof result).toBe('string'); expect(typeof result).toBe("string");
let output = JSON.parse(result); 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(Array.isArray(output.errors)).toBeTruthy(); // Check if it's an array
expect(output.errors.length).toBeGreaterThan(0); expect(output.errors.length).toBeGreaterThan(0);
expect(output.errors[0]).toHaveProperty('message'); expect(output.errors[0]).toHaveProperty("message");
expect(output.errors[0].message).toContain('Source "nonexistent/console.sol" not found'); expect(output.errors[0].message).toContain(
'Source "nonexistent/console.sol" not found',
);
}); });
+35 -32
View File
@@ -1,54 +1,57 @@
var Module = { Module.stdinData = null;
stdinData: null, Module.stdinDataPosition = 0;
stdinDataPosition: 0, Module.stdoutData = [];
stdoutData: [], Module.stderrData = [];
stderrData: [],
// Function to read and return all collected stdout data as a string // Method to read all collected stdout data
readFromStdout: function() { Module.readFromStdout = function () {
if (!this.stdoutData.length) return ""; if (!Module.stdoutData.length) return "";
const decoder = new TextDecoder('utf-8'); const decoder = new TextDecoder("utf-8");
const data = decoder.decode(new Uint8Array(this.stdoutData)); const data = decoder.decode(new Uint8Array(Module.stdoutData));
this.stdoutData = []; Module.stdoutData = [];
return data; return data;
}, };
// Function to read and return all collected stderr data as a string // Method to read all collected stderr data
readFromStderr: function() { Module.readFromStderr = function () {
if (!this.stderrData.length) return ""; if (!Module.stderrData.length) return "";
const decoder = new TextDecoder('utf-8'); const decoder = new TextDecoder("utf-8");
const data = decoder.decode(new Uint8Array(this.stderrData)); const data = decoder.decode(new Uint8Array(Module.stderrData));
this.stderrData = []; Module.stderrData = [];
return data; return data;
}, };
// Function to set input data for stdin // Method to write data to stdin
writeToStdin: function(data) { Module.writeToStdin = function (data) {
const encoder = new TextEncoder(); const encoder = new TextEncoder();
this.stdinData = encoder.encode(data); Module.stdinData = encoder.encode(data);
this.stdinDataPosition = 0; Module.stdinDataPosition = 0;
}, };
// `preRun` is called before the program starts running // Override the `preRun` method to customize file system initialization
preRun: function() { Module.preRun = Module.preRun || [];
// Define a custom stdin function Module.preRun.push(function () {
// Custom stdin function
function customStdin() { function customStdin() {
if (!Module.stdinData || Module.stdinDataPosition >= Module.stdinData.length) { if (
!Module.stdinData ||
Module.stdinDataPosition >= Module.stdinData.length
) {
return null; // End of input (EOF) return null; // End of input (EOF)
} }
return Module.stdinData[Module.stdinDataPosition++]; return Module.stdinData[Module.stdinDataPosition++];
} }
// Define a custom stdout function // Custom stdout function
function customStdout(char) { function customStdout(char) {
Module.stdoutData.push(char); Module.stdoutData.push(char);
} }
// Define a custom stderr function // Custom stderr function
function customStderr(char) { function customStderr(char) {
Module.stderrData.push(char); Module.stderrData.push(char);
} }
// Initialize the FS (File System) with custom handlers
FS.init(customStdin, customStdout, customStderr); FS.init(customStdin, customStdout, customStderr);
}, });
};
+9 -4
View File
@@ -1,7 +1,9 @@
mergeInto(LibraryManager.library, { mergeInto(LibraryManager.library, {
soljson_compile: function (inputPtr, inputLen) { soljson_compile: function (inputPtr, inputLen) {
const inputJson = UTF8ToString(inputPtr, inputLen); const inputJson = UTF8ToString(inputPtr, inputLen);
const output = Module.soljson.cwrap('solidity_compile', 'string', ['string'])(inputJson); const output = Module.soljson.cwrap("solidity_compile", "string", [
"string",
])(inputJson);
return stringToNewUTF8(output); return stringToNewUTF8(output);
}, },
soljson_version: function () { soljson_version: function () {
@@ -14,15 +16,18 @@ mergeInto(LibraryManager.library, {
revive.writeToStdin(inputJson); revive.writeToStdin(inputJson);
// Call main on the new instance // Call main on the new instance
const result = revive.callMain(['--recursive-process']); const result = revive.callMain(["--recursive-process"]);
if (result) { if (result) {
const stderrString = revive.readFromStderr(); const stderrString = revive.readFromStderr();
const error = JSON.stringify({ type: 'error', message: stderrString || "Unknown error" }); const error = JSON.stringify({
type: "error",
message: stderrString || "Unknown error",
});
return stringToNewUTF8(error); return stringToNewUTF8(error);
} else { } else {
const stdoutString = revive.readFromStdout(); const stdoutString = revive.readFromStdout();
const json = JSON.stringify({ type: 'success', data: stdoutString }); const json = JSON.stringify({ type: "success", data: stdoutString });
return stringToNewUTF8(json); return stringToNewUTF8(json);
} }
}, },
+4 -4
View File
@@ -1,9 +1,9 @@
const soljson = require('solc/soljson'); const soljson = require("solc/soljson");
const createRevive = require('./resolc.js'); const createRevive = require("./resolc.js");
async function compile(standardJsonInput) { async function compile(standardJsonInput) {
if (!standardJsonInput) { if (!standardJsonInput) {
throw new Error('Input JSON for the Solidity compiler is required.'); throw new Error("Input JSON for the Solidity compiler is required.");
} }
// Initialize the compiler // Initialize the compiler
@@ -14,7 +14,7 @@ async function compile(standardJsonInput) {
compiler.writeToStdin(JSON.stringify(standardJsonInput)); compiler.writeToStdin(JSON.stringify(standardJsonInput));
// Run the compiler // Run the compiler
compiler.callMain(['--standard-json']); compiler.callMain(["--standard-json"]);
// Collect output // Collect output
const stdout = compiler.readFromStdout(); const stdout = compiler.readFromStdout();
+8 -8
View File
@@ -1,9 +1,9 @@
const { compile } = require('./revive.js'); const { compile } = require("./revive.js");
const compilerStandardJsonInput = { const compilerStandardJsonInput = {
language: 'Solidity', language: "Solidity",
sources: { sources: {
'MyContract.sol': { "MyContract.sol": {
content: ` content: `
// SPDX-License-Identifier: UNLICENSED // SPDX-License-Identifier: UNLICENSED
pragma solidity ^0.8.0; pragma solidity ^0.8.0;
@@ -21,18 +21,18 @@ const compilerStandardJsonInput = {
runs: 200, runs: 200,
}, },
outputSelection: { outputSelection: {
'*': { "*": {
'*': ['abi'], "*": ["abi"],
}, },
}, },
}, },
}; };
async function runCompiler() { async function runCompiler() {
let output = await compile(compilerStandardJsonInput) let output = await compile(compilerStandardJsonInput);
console.log("Output: " + output); console.log("Output: " + output);
} }
runCompiler().catch(err => { runCompiler().catch((err) => {
console.error('Error:', err); console.error("Error:", err);
}); });
+17 -15
View File
@@ -1,6 +1,5 @@
<!DOCTYPE html> <!doctype html>
<html> <html>
<head> <head>
<meta charset="utf-8" /> <meta charset="utf-8" />
<title>Web Worker Example</title> <title>Web Worker Example</title>
@@ -19,14 +18,14 @@
<h1>Revive Compilation Output</h1> <h1>Revive Compilation Output</h1>
<pre id="output"></pre> <pre id="output"></pre>
<script> <script>
var outputElement = document.getElementById('output'); var outputElement = document.getElementById("output");
var worker = new Worker('./worker.js'); var worker = new Worker("./worker.js");
const standardJsonInput = { const standardJsonInput = {
language: 'Solidity', language: "Solidity",
sources: { sources: {
contract: { contract: {
content: 'contract MyContract { function f() public { } }', content: "contract MyContract { function f() public { } }",
} },
}, },
settings: { settings: {
optimizer: { optimizer: {
@@ -34,18 +33,21 @@
runs: 200, runs: 200,
}, },
outputSelection: { outputSelection: {
'*': { "*": {
'*': ['abi'], "*": ["abi"],
} },
} },
} },
}; };
worker.addEventListener('message', function (e) { worker.addEventListener(
"message",
function (e) {
outputElement.textContent = e.data.output; outputElement.textContent = e.data.output;
}, false); },
false,
);
worker.postMessage(JSON.stringify(standardJsonInput)); worker.postMessage(JSON.stringify(standardJsonInput));
</script> </script>
</body> </body>
</html> </html>
+3 -5
View File
@@ -1,18 +1,16 @@
importScripts("./soljson.js");
importScripts('./soljson.js'); importScripts("./resolc.js");
importScripts('./resolc.js');
// Handle messages from the main thread // Handle messages from the main thread
onmessage = async function (e) { onmessage = async function (e) {
const m = createRevive(); const m = createRevive();
m.soljson = Module; m.soljson = Module;
// Set input data for stdin // Set input data for stdin
m.writeToStdin(e.data); m.writeToStdin(e.data);
// Compile the Solidity source code // Compile the Solidity source code
m.callMain(['--standard-json']); m.callMain(["--standard-json"]);
postMessage({ output: m.readFromStdout() || m.readFromStderr() }); postMessage({ output: m.readFromStdout() || m.readFromStderr() });
}; };
+1 -4
View File
@@ -12,11 +12,8 @@
}, },
"outputSelection": { "outputSelection": {
"*": { "*": {
"*": [ "*": ["abi"]
"abi"
]
} }
} }
} }
} }
+1 -4
View File
@@ -12,11 +12,8 @@
}, },
"outputSelection": { "outputSelection": {
"*": { "*": {
"*": [ "*": ["abi"]
"abi"
]
} }
} }
} }
} }
+1 -4
View File
@@ -12,11 +12,8 @@
}, },
"outputSelection": { "outputSelection": {
"*": { "*": {
"*": [ "*": ["abi"]
"abi"
]
} }
} }
} }
} }
+1 -4
View File
@@ -72,11 +72,8 @@
}, },
"outputSelection": { "outputSelection": {
"*": { "*": {
"*": [ "*": ["abi"]
"abi"
]
} }
} }
} }
} }
+6 -2
View File
@@ -10,12 +10,16 @@
"example:node": "node ./examples/node/run_revive.js", "example:node": "node ./examples/node/run_revive.js",
"test:node": "mocha --timeout 60000 ./tests", "test:node": "mocha --timeout 60000 ./tests",
"test:bun": "bun test --timeout 60000 node.test", "test:bun": "bun test --timeout 60000 node.test",
"test:all": "npm run test:node && npm run test:bun" "test:all": "npm run test:node && npm run test:bun",
"format": "prettier --write .",
"build:package": "node ./build.js"
}, },
"devDependencies": { "devDependencies": {
"@playwright/test": "^1.49.1", "@playwright/test": "^1.49.1",
"chai": "^5.1.2", "chai": "^5.1.2",
"http-server": "^14.1.1", "http-server": "^14.1.1",
"mocha": "^11.0.1" "mocha": "^11.0.1",
"prettier": "^3.4.2",
"terser": "^5.37.0"
} }
} }
+14 -15
View File
@@ -1,10 +1,10 @@
const { defineConfig, devices } = require('@playwright/test'); const { defineConfig, devices } = require("@playwright/test");
/** /**
* @see https://playwright.dev/docs/test-configuration * @see https://playwright.dev/docs/test-configuration
*/ */
module.exports = defineConfig({ module.exports = defineConfig({
testDir: './e2e', testDir: "./e2e",
/* Run tests in files in parallel */ /* Run tests in files in parallel */
fullyParallel: true, fullyParallel: true,
/* Fail the build on CI if you accidentally left test.only in the source code. */ /* Fail the build on CI if you accidentally left test.only in the source code. */
@@ -14,39 +14,38 @@ module.exports = defineConfig({
/* Opt out of parallel tests on CI. */ /* Opt out of parallel tests on CI. */
workers: process.env.CI ? 1 : undefined, workers: process.env.CI ? 1 : undefined,
/* Reporter to use. See https://playwright.dev/docs/test-reporters */ /* Reporter to use. See https://playwright.dev/docs/test-reporters */
reporter: 'list', reporter: "list",
/* Shared settings for all the projects below. See https://playwright.dev/docs/api/class-testoptions. */ /* Shared settings for all the projects below. See https://playwright.dev/docs/api/class-testoptions. */
use: { use: {
/* Base URL to use in actions like `await page.goto('/')`. */ /* Base URL to use in actions like `await page.goto('/')`. */
baseURL: 'http://127.0.0.1:8080', baseURL: "http://127.0.0.1:8080",
/* Collect trace when retrying the failed test. See https://playwright.dev/docs/trace-viewer */ /* Collect trace when retrying the failed test. See https://playwright.dev/docs/trace-viewer */
trace: 'on-first-retry', trace: "on-first-retry",
}, },
timeout: 480000, timeout: 480000,
/* Configure projects for major browsers */ /* Configure projects for major browsers */
projects: [ projects: [
{ {
name: 'chromium', name: "chromium",
use: { ...devices['Desktop Chrome'] }, use: { ...devices["Desktop Chrome"] },
}, },
{ {
name: 'firefox', name: "firefox",
use: { ...devices['Desktop Firefox'] }, use: { ...devices["Desktop Firefox"] },
}, },
{ {
name: 'webkit', name: "webkit",
use: { ...devices['Desktop Safari'] }, use: { ...devices["Desktop Safari"] },
} },
], ],
/* Run your local dev server before starting the tests */ /* Run your local dev server before starting the tests */
webServer: { webServer: {
command: 'npm run example:web', command: "npm run example:web",
url: 'http://127.0.0.1:8080', url: "http://127.0.0.1:8080",
reuseExistingServer: !process.env.CI, reuseExistingServer: !process.env.CI,
}, },
}); });
+52 -34
View File
@@ -1,70 +1,88 @@
import { expect } from 'chai'; import { expect } from "chai";
import { compile } from '../examples/node/revive.js'; import { compile } from "../examples/node/revive.js";
import { fileURLToPath } from 'url'; import { fileURLToPath } from "url";
import path from 'path'; import path from "path";
import fs from 'fs'; import fs from "fs";
const __filename = fileURLToPath(import.meta.url); const __filename = fileURLToPath(import.meta.url);
const __dirname = path.dirname(__filename); const __dirname = path.dirname(__filename);
function loadFixture(fixture) { function loadFixture(fixture) {
const fixturePath = path.resolve(__dirname, `../fixtures/${fixture}`); const fixturePath = path.resolve(__dirname, `../fixtures/${fixture}`);
return JSON.parse(fs.readFileSync(fixturePath, 'utf-8')); return JSON.parse(fs.readFileSync(fixturePath, "utf-8"));
} }
describe('Compile Function Tests', function () { describe("Compile Function Tests", function () {
it('should successfully compile valid Solidity code', async function () { it("should successfully compile valid Solidity code", async function () {
const standardInput = loadFixture('storage.json') const standardInput = loadFixture("storage.json");
const result = await compile(standardInput); const result = await compile(standardInput);
expect(result).to.be.a('string'); expect(result).to.be.a("string");
const output = JSON.parse(result); const output = JSON.parse(result);
expect(output).to.have.property('contracts'); expect(output).to.have.property("contracts");
expect(output.contracts['fixtures/storage.sol']).to.have.property('Storage'); expect(output.contracts["fixtures/storage.sol"]).to.have.property(
expect(output.contracts['fixtures/storage.sol'].Storage).to.have.property('abi'); "Storage",
expect(output.contracts['fixtures/storage.sol'].Storage).to.have.property('evm'); );
expect(output.contracts['fixtures/storage.sol'].Storage.evm).to.have.property('bytecode'); expect(output.contracts["fixtures/storage.sol"].Storage).to.have.property(
"abi",
);
expect(output.contracts["fixtures/storage.sol"].Storage).to.have.property(
"evm",
);
expect(
output.contracts["fixtures/storage.sol"].Storage.evm,
).to.have.property("bytecode");
}); });
if (typeof globalThis.Bun == 'undefined') { if (typeof globalThis.Bun == "undefined") {
// Running this test with Bun on a Linux host causes: // Running this test with Bun on a Linux host causes:
// RuntimeError: Out of bounds memory access (evaluating 'getWasmTableEntry(index)(a1, a2, a3, a4, a5)') // RuntimeError: Out of bounds memory access (evaluating 'getWasmTableEntry(index)(a1, a2, a3, a4, a5)')
// Once this issue is resolved, the test will be re-enabled. // Once this issue is resolved, the test will be re-enabled.
it('should successfully compile large Solidity code', async function () { it("should successfully compile large Solidity code", async function () {
const standardInput = loadFixture('token.json') const standardInput = loadFixture("token.json");
const result = await compile(standardInput); const result = await compile(standardInput);
expect(result).to.be.a('string'); expect(result).to.be.a("string");
const output = JSON.parse(result); const output = JSON.parse(result);
expect(output).to.have.property('contracts'); expect(output).to.have.property("contracts");
expect(output.contracts['fixtures/token.sol']).to.have.property('MyToken'); expect(output.contracts["fixtures/token.sol"]).to.have.property(
expect(output.contracts['fixtures/token.sol'].MyToken).to.have.property('abi'); "MyToken",
expect(output.contracts['fixtures/token.sol'].MyToken).to.have.property('evm'); );
expect(output.contracts['fixtures/token.sol'].MyToken.evm).to.have.property('bytecode'); expect(output.contracts["fixtures/token.sol"].MyToken).to.have.property(
"abi",
);
expect(output.contracts["fixtures/token.sol"].MyToken).to.have.property(
"evm",
);
expect(
output.contracts["fixtures/token.sol"].MyToken.evm,
).to.have.property("bytecode");
}); });
} }
it('should throw an error for invalid Solidity code', async function () { it("should throw an error for invalid Solidity code", async function () {
const standardInput = loadFixture('invalid_contract_content.json') const standardInput = loadFixture("invalid_contract_content.json");
const result = await compile(standardInput); const result = await compile(standardInput);
expect(result).to.be.a('string'); expect(result).to.be.a("string");
const output = JSON.parse(result); const output = JSON.parse(result);
expect(output).to.have.property('errors'); expect(output).to.have.property("errors");
expect(output.errors).to.be.an('array'); expect(output.errors).to.be.an("array");
expect(output.errors.length).to.be.greaterThan(0); expect(output.errors.length).to.be.greaterThan(0);
expect(output.errors[0].type).to.exist; expect(output.errors[0].type).to.exist;
expect(output.errors[0].type).to.contain("ParserError"); expect(output.errors[0].type).to.contain("ParserError");
}); });
it('should return not found error for missing imports', async function () { it("should return not found error for missing imports", async function () {
const standardInput = loadFixture('missing_import.json') const standardInput = loadFixture("missing_import.json");
const result = await compile(standardInput); const result = await compile(standardInput);
const output = JSON.parse(result); const output = JSON.parse(result);
expect(output).to.have.property('errors'); expect(output).to.have.property("errors");
expect(output.errors).to.be.an('array'); expect(output.errors).to.be.an("array");
expect(output.errors.length).to.be.greaterThan(0); expect(output.errors.length).to.be.greaterThan(0);
expect(output.errors[0].message).to.exist; expect(output.errors[0].message).to.exist;
expect(output.errors[0].message).to.include('Source "nonexistent/console.sol" not found'); expect(output.errors[0].message).to.include(
'Source "nonexistent/console.sol" not found',
);
}); });
}); });
+46
View File
@@ -0,0 +1,46 @@
function base64DecToArr (sBase64) {
/*\
|*|
|*| Base64 / binary data / UTF-8 strings utilities
|*|
|*| https://developer.mozilla.org/en-US/docs/Web/JavaScript/Base64_encoding_and_decoding
|*|
\*/
/* Array of bytes to Base64 string decoding */
function b64ToUint6 (nChr) {
return nChr > 64 && nChr < 91 ?
nChr - 65
: nChr > 96 && nChr < 123 ?
nChr - 71
: nChr > 47 && nChr < 58 ?
nChr + 4
: nChr === 43 ?
62
: nChr === 47 ?
63
:
0;
}
var
nInLen = sBase64.length,
nOutLen = nInLen * 3 + 1 >> 2, taBytes = new Uint8Array(nOutLen);
for (var nMod3, nMod4, nUint24 = 0, nOutIdx = 0, nInIdx = 0; nInIdx < nInLen; nInIdx++) {
nMod4 = nInIdx & 3;
nUint24 |= b64ToUint6(sBase64.charCodeAt(nInIdx)) << 6 * (3 - nMod4);
if (nMod4 === 3 || nInLen - nInIdx === 1) {
for (nMod3 = 0; nMod3 < 3 && nOutIdx < nOutLen; nMod3++, nOutIdx++) {
taBytes[nOutIdx] = nUint24 >>> (16 >>> nMod3 & 24) & 255;
}
nUint24 = 0;
}
}
return taBytes;
}
+118
View File
@@ -0,0 +1,118 @@
function uncompress(source, uncompressedSize) {
/*
Source https://github.com/ethereum/solidity/blob/develop/scripts/ci/mini-lz4.js
====
based off https://github.com/emscripten-core/emscripten/blob/main/third_party/mini-lz4.js
The license only applies to the body of this function (``uncompress``).
====
MiniLZ4: Minimal LZ4 block decoding and encoding.
based off of node-lz4, https://github.com/pierrec/node-lz4
====
Copyright (c) 2012 Pierre Curto
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in
all copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
THE SOFTWARE.
====
changes have the same license
*/
/**
* Decode a block. Assumptions: input contains all sequences of a
* chunk, output is large enough to receive the decoded data.
* If the output buffer is too small, an error will be thrown.
* If the returned value is negative, an error occurred at the returned offset.
*
* @param {ArrayBufferView} input input data
* @param {ArrayBufferView} output output data
* @param {number=} sIdx
* @param {number=} eIdx
* @return {number} number of decoded bytes
* @private
*/
function uncompressBlock (input, output, sIdx, eIdx) {
sIdx = sIdx || 0
eIdx = eIdx || (input.length - sIdx)
// Process each sequence in the incoming data
for (var i = sIdx, n = eIdx, j = 0; i < n;) {
var token = input[i++]
// Literals
var literals_length = (token >> 4)
if (literals_length > 0) {
// length of literals
var l = literals_length + 240
while (l === 255) {
l = input[i++]
literals_length += l
}
// Copy the literals
var end = i + literals_length
while (i < end) output[j++] = input[i++]
// End of buffer?
if (i === n) return j
}
// Match copy
// 2 bytes offset (little endian)
var offset = input[i++] | (input[i++] << 8)
// XXX 0 is an invalid offset value
if (offset === 0) return j
if (offset > j) return -(i-2)
// length of match copy
var match_length = (token & 0xf)
var l = match_length + 240
while (l === 255) {
l = input[i++]
match_length += l
}
// Copy the match
var pos = j - offset // position of the match copy in the current output
var end = j + match_length + 4 // minmatch = 4
while (j < end) output[j++] = output[pos++]
}
return j
}
var result = new ArrayBuffer(uncompressedSize);
var sourceIndex = 0;
var destIndex = 0;
var blockSize;
while((blockSize = (source[sourceIndex] | (source[sourceIndex + 1] << 8) | (source[sourceIndex + 2] << 16) | (source[sourceIndex + 3] << 24))) > 0)
{
sourceIndex += 4;
if (blockSize & 0x80000000)
{
blockSize &= 0x7FFFFFFFF;
for (var i = 0; i < blockSize; i++) {
result[destIndex++] = source[sourceIndex++];
}
}
else
{
destIndex += uncompressBlock(source, new Uint8Array(result, destIndex, uncompressedSize - destIndex), sourceIndex, sourceIndex + blockSize);
sourceIndex += blockSize;
}
}
return new Uint8Array(result, 0, uncompressedSize);
}
+2 -1
View File
@@ -3,7 +3,8 @@
"private": true, "private": true,
"scripts": { "scripts": {
"test:cli": "npm run test -w crates/solidity/src/tests/cli-tests", "test:cli": "npm run test -w crates/solidity/src/tests/cli-tests",
"test:wasm": "npm run test:all -w js" "test:wasm": "npm run test:all -w js",
"build:package": "npm run build:package -w js"
}, },
"workspaces": [ "workspaces": [
"crates/solidity/src/tests/cli-tests", "crates/solidity/src/tests/cli-tests",