Compare commits

..

17 Commits

Author SHA1 Message Date
xermicus 7233738f45 release resolc v0.1.0-dev.10 (#209)
Signed-off-by: xermicus <cyrill@parity.io>
2025-02-11 15:51:52 +01:00
Sebastian Miasojed 79ec4dd04b Add all resolc dependencies to resolc_web.js file (#176) 2025-02-11 11:55:24 +01:00
xermicus 374563bbe5 integration: add function pointer integration test (#205)
Signed-off-by: Cyrill Leutwiler <bigcyrill@hotmail.com>
2025-02-10 18:50:12 +01:00
xermicus a921e425b4 CI: fix and pin geth (#206) 2025-02-10 17:39:44 +01:00
xermicus 60fc09f787 llvm-context: disable call re-entrancy for send and transfer (#196) 2025-02-06 16:49:50 +01:00
Sebastian Miasojed 10b8ff989c Align emscripten compilation options (#180) 2025-02-06 00:37:10 +01:00
xermicus 157647d5c1 remove superfluous warning and clippy allows (#194)
Signed-off-by: xermicus <cyrill@parity.io>
2025-02-05 12:44:29 +01:00
xermicus 35419e8202 solidity: update custom warnings and version validation (#193) 2025-02-05 11:42:35 +01:00
xermicus de3e7bf253 integration: add create2 test case with duplicate salt (#188)
Signed-off-by: Cyrill Leutwiler <bigcyrill@hotmail.com>
2025-02-04 14:04:14 +01:00
xermicus 9fb24b607d disable werror (#191)
Signed-off-by: Cyrill Leutwiler <bigcyrill@hotmail.com>
2025-02-04 12:43:56 +01:00
xermicus ba7310fdff integration: bugfix blob cache (#192)
Signed-off-by: xermicus <cyrill@parity.io>
2025-02-04 12:11:25 +01:00
dependabot[bot] 4ef495beea Bump openssl from 0.10.68 to 0.10.70 (#190)
Bumps [openssl](https://github.com/sfackler/rust-openssl) from 0.10.68 to 0.10.70.
- [Release notes](https://github.com/sfackler/rust-openssl/releases)
- [Commits](https://github.com/sfackler/rust-openssl/compare/openssl-v0.10.68...openssl-v0.10.70)

---
updated-dependencies:
- dependency-name: openssl
  dependency-type: indirect
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2025-02-04 07:28:29 +01:00
xermicus 8ed689e7ec solidity: various small resolc fixes (#189) 2025-02-03 16:16:54 +01:00
xermicus bfda465c32 remove support for legacy evm assembly (#186) 2025-02-03 14:13:43 +01:00
xermicus ab90af49df Solidity: add --libraries to sources (#187)
* bugfix: add libraries to sources

Signed-off-by: Cyrill Leutwiler <bigcyrill@hotmail.com>
2025-01-31 18:08:11 +01:00
Sebastian Miasojed 8201401fef Fix stack overflow issue (#184) 2025-01-31 14:31:34 +01:00
xermicus 1a8a7926e9 implement the coinbase opcode (#179)
Signed-off-by: xermicus <cyrill@parity.io>
2025-01-29 15:24:45 +01:00
118 changed files with 2192 additions and 7048 deletions
+2 -3
View File
@@ -12,7 +12,6 @@ rustflags = [
"-Clink-arg=-sALLOW_TABLE_GROWTH=1", "-Clink-arg=-sALLOW_TABLE_GROWTH=1",
"-Clink-arg=--js-library=js/embed/soljson_interface.js", "-Clink-arg=--js-library=js/embed/soljson_interface.js",
"-Clink-arg=--pre-js=js/embed/pre.js", "-Clink-arg=--pre-js=js/embed/pre.js",
"-Clink-arg=-sNODEJS_CATCH_EXIT=0", "-Clink-arg=-sSTACK_SIZE=128kb",
"-Clink-arg=-sDISABLE_EXCEPTION_CATCHING=0", "-Clink-arg=-sNODEJS_CATCH_EXIT=0"
"-Copt-level=3"
] ]
+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_web.js
retention-days: 1 retention-days: 1
test-revive-wasm: test-revive-wasm:
+5 -4
View File
@@ -30,11 +30,12 @@ jobs:
tar Jxf llvm.tar.xz -C llvm18/ tar Jxf llvm.tar.xz -C llvm18/
echo "LLVM_SYS_181_PREFIX=$(pwd)/llvm18" >> $GITHUB_ENV echo "LLVM_SYS_181_PREFIX=$(pwd)/llvm18" >> $GITHUB_ENV
- name: Install apt dependencies - name: Install geth
run: | run: |
sudo add-apt-repository -y ppa:ethereum/ethereum git clone https://github.com/xermicus/go-ethereum --branch=cl/fix-runner-state-dump --depth=1
sudo apt update cd go-ethereum
sudo apt install -y ethereum make all
echo "$(pwd)/build/bin/" >> $GITHUB_PATH
- name: Machete - name: Machete
uses: bnjbvr/cargo-machete@main uses: bnjbvr/cargo-machete@main
+30
View File
@@ -2,6 +2,36 @@
## Unreleased ## Unreleased
This is a development pre-release.
Supported `polkadot-sdk` rev: `274a781e8ca1a9432c7ec87593bd93214abbff50`
## v0.1.0-dev.10
This is a development pre-release.
Supported `polkadot-sdk` rev: `274a781e8ca1a9432c7ec87593bd93214abbff50`
### Added
- Support for the `coinbase` opcode.
- The resolc web JS version.
### Changed
- Missing the `--overwrite` flag emits an error instead of a warning.
- The `resolc` executable prints the help by default.
- Removed support for legacy EVM assembly (EVMLA) translation.
- integration: identify cached code blobs on source code to fix potential confusions.
- Setting base, include or allow paths in emscripten is now a hard error.
- Employ a heuristic to detect `address.transfer` and `address.send` calls.
If detected, the re-entrant call flag is not set and 0 deposit limit is endowed.
### Fixed
- Solidity: Add the solc `--libraries` files to sources.
- A data race in tests.
- Fix `broken pipe` errors.
- llvm-builder: Allow warnings.
- solidity: Fix the custom compiler warning messages.
## v0.1.0-dev.9 ## v0.1.0-dev.9
This is a development pre-release. This is a development pre-release.
Generated
+418 -392
View File
File diff suppressed because it is too large Load Diff
+17 -18
View File
@@ -3,7 +3,7 @@ resolver = "2"
members = ["crates/*"] members = ["crates/*"]
[workspace.package] [workspace.package]
version = "0.1.0-dev.9" version = "0.1.0-dev.10"
authors = [ authors = [
"Cyrill Leutwiler <cyrill@parity.io>", "Cyrill Leutwiler <cyrill@parity.io>",
"Parity Technologies <admin@parity.io>", "Parity Technologies <admin@parity.io>",
@@ -14,19 +14,19 @@ repository = "https://github.com/paritytech/revive"
rust-version = "1.81.0" rust-version = "1.81.0"
[workspace.dependencies] [workspace.dependencies]
revive-benchmarks = { version = "0.1.0-dev.9", path = "crates/benchmarks" } revive-benchmarks = { version = "0.1.0-dev.10", path = "crates/benchmarks" }
revive-builtins = { version = "0.1.0-dev.9", path = "crates/builtins" } revive-builtins = { version = "0.1.0-dev.10", path = "crates/builtins" }
revive-common = { version = "0.1.0-dev.9", path = "crates/common" } revive-common = { version = "0.1.0-dev.10", path = "crates/common" }
revive-differential = { version = "0.1.0-dev.9", path = "crates/differential" } revive-differential = { version = "0.1.0-dev.10", path = "crates/differential" }
revive-integration = { version = "0.1.0-dev.9", path = "crates/integration" } revive-integration = { version = "0.1.0-dev.10", path = "crates/integration" }
revive-linker = { version = "0.1.0-dev.9", path = "crates/linker" } revive-linker = { version = "0.1.0-dev.10", path = "crates/linker" }
lld-sys = { version = "0.1.0-dev.9", path = "crates/lld-sys" } lld-sys = { version = "0.1.0-dev.10", path = "crates/lld-sys" }
revive-llvm-context = { version = "0.1.0-dev.9", path = "crates/llvm-context" } revive-llvm-context = { version = "0.1.0-dev.10", path = "crates/llvm-context" }
revive-runtime-api = { version = "0.1.0-dev.9", path = "crates/runtime-api" } revive-runtime-api = { version = "0.1.0-dev.10", path = "crates/runtime-api" }
revive-runner = { version = "0.1.0-dev.9", path = "crates/runner" } revive-runner = { version = "0.1.0-dev.10", path = "crates/runner" }
revive-solidity = { version = "0.1.0-dev.9", path = "crates/solidity" } revive-solidity = { version = "0.1.0-dev.10", path = "crates/solidity" }
revive-stdlib = { version = "0.1.0-dev.9", path = "crates/stdlib" } revive-stdlib = { version = "0.1.0-dev.10", path = "crates/stdlib" }
revive-build-utils = { version = "0.1.0-dev.9", path = "crates/build-utils" } revive-build-utils = { version = "0.1.0-dev.10", path = "crates/build-utils" }
hex = "0.4.3" hex = "0.4.3"
cc = "1.0" cc = "1.0"
@@ -42,7 +42,6 @@ once_cell = "1.19"
num = "0.4.3" num = "0.4.3"
sha1 = "0.10" sha1 = "0.10"
sha3 = "0.10" sha3 = "0.10"
md5 = "0.7.0"
thiserror = "2.0" thiserror = "2.0"
which = "7.0" which = "7.0"
path-slash = "0.2" path-slash = "0.2"
@@ -54,8 +53,8 @@ polkavm-disassembler = "0.19.0"
polkavm = "0.19.0" polkavm = "0.19.0"
alloy-primitives = { version = "0.8.19", features = ["serde"] } alloy-primitives = { version = "0.8.19", features = ["serde"] }
alloy-sol-types = "0.8.19" alloy-sol-types = "0.8.19"
alloy-genesis = "0.9.2" alloy-genesis = "0.11.0"
alloy-serde = "0.9.2" alloy-serde = "0.11.0"
env_logger = { version = "0.11.6", default-features = false } env_logger = { version = "0.11.6", default-features = false }
serde_stacker = "0.1.11" serde_stacker = "0.1.11"
criterion = { version = "0.5.1", features = ["html_reports"] } criterion = { version = "0.5.1", features = ["html_reports"] }
@@ -73,7 +72,7 @@ assert_fs = "1.1.2"
# polkadot-sdk and friends # polkadot-sdk and friends
codec = { version = "3.6.12", default-features = false, package = "parity-scale-codec" } codec = { version = "3.6.12", default-features = false, package = "parity-scale-codec" }
scale-info = { version = "2.11.6", default-features = false } scale-info = { version = "2.11.6", default-features = false }
polkadot-sdk = { git = "https://github.com/paritytech/polkadot-sdk", rev = "4302f74f7874e6a894578731142a7b310a1449b0" } polkadot-sdk = { git = "https://github.com/paritytech/polkadot-sdk", rev = "274a781e8ca1a9432c7ec87593bd93214abbff50" }
# llvm # llvm
[workspace.dependencies.inkwell] [workspace.dependencies.inkwell]
+2 -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
@@ -42,7 +43,7 @@ format:
cargo fmt --all --check cargo fmt --all --check
clippy: clippy:
cargo clippy --all-features --workspace --tests --benches -- --deny warnings --allow dead_code cargo clippy --all-features --workspace --tests --benches -- --deny warnings
machete: machete:
cargo install cargo-machete cargo install cargo-machete
+1 -1
View File
@@ -3,7 +3,7 @@
# revive # revive
YUL and EVM assembly recompiler to LLVM, targetting RISC-V on [PolkaVM](https://github.com/koute/polkavm). YUL recompiler to LLVM, targetting RISC-V on [PolkaVM](https://github.com/koute/polkavm).
Visit [contracts.polkadot.io](https://contracts.polkadot.io) to learn more about contracts on Polkadot! Visit [contracts.polkadot.io](https://contracts.polkadot.io) to learn more about contracts on Polkadot!
+1 -1
View File
@@ -7,5 +7,5 @@ To create a new pre-release:
1. Merge a release PR which updates the `-dev.X` versions in the workspace `Cargo.toml` and updates the `CHANGELOG.md` accordingly 1. Merge a release PR which updates the `-dev.X` versions in the workspace `Cargo.toml` and updates the `CHANGELOG.md` accordingly
2. Push a release tag to `main` 2. Push a release tag to `main`
3. Create a __pre-release__ from the tag and manually upload the `resolc` binary from docker image 3. Create a __pre-release__ from the tag and manually upload the `resolc` binary from docker image
4. Manually upload `resolc.js` and `resolc.wasm` from the `build-revive-wasm` action artifacts. 4. Manually upload `resolc.js`, `resolc-web.js` and `resolc.wasm` from the `build-revive-wasm` action artifacts.
5. Update the [contract-docs](https://github.com/paritytech/contract-docs/) accordingly 5. Update the [contract-docs](https://github.com/paritytech/contract-docs/) accordingly
-6
View File
@@ -12,12 +12,6 @@ pub static EXTENSION_ABI: &str = "abi";
/// The Yul IR file extension. /// The Yul IR file extension.
pub static EXTENSION_YUL: &str = "yul"; pub static EXTENSION_YUL: &str = "yul";
/// The EVM legacy assembly IR file extension.
pub static EXTENSION_EVMLA: &str = "evmla";
/// The Ethereal IR file extension.
pub static EXTENSION_ETHIR: &str = "ethir";
/// The EVM file extension. /// The EVM file extension.
pub static EXTENSION_EVM: &str = "evm"; pub static EXTENSION_EVM: &str = "evm";
+9 -2
View File
@@ -16,9 +16,16 @@
"shanghaiTime": 0, "shanghaiTime": 0,
"cancunTime": 0, "cancunTime": 0,
"terminalTotalDifficulty": 0, "terminalTotalDifficulty": 0,
"terminalTotalDifficultyPassed": true "terminalTotalDifficultyPassed": true,
"blobSchedule": {
"cancun": {
"target": 3,
"max": 6,
"baseFeeUpdateFraction": 3338477
}
}
}, },
"coinbase": "0x0000000000000000000000000000000000000000", "coinbase": "0xffffffffffffffffffffffffffffffffffffffff",
"difficulty": "0x20000", "difficulty": "0x20000",
"extraData": "", "extraData": "",
"gasLimit": "0xffffffff", "gasLimit": "0xffffffff",
+1 -1
View File
@@ -413,7 +413,7 @@ impl Evm {
let stderr = str::from_utf8(output.stderr.as_slice()) let stderr = str::from_utf8(output.stderr.as_slice())
.unwrap_or_else(|err| panic!("{EXECUTABLE_NAME} stderr failed to parse: {err}")); .unwrap_or_else(|err| panic!("{EXECUTABLE_NAME} stderr failed to parse: {err}"));
let mut log: EvmLog = stdout.into(); let mut log: EvmLog = format!("{stdout}{stderr}").as_str().into();
log.stderr = stderr.into(); log.stderr = stderr.into();
if self.bench { if self.bench {
log.parse_gas_used_from_bench(); log.parse_gas_used_from_bench();
+81
View File
@@ -0,0 +1,81 @@
// SPDX-License-Identifier: MIT
pragma solidity ^0.8;
/* runner.json
{
"differential": true,
"actions": [
{
"Upload": {
"code": {
"Solidity": {
"contract": "BalanceReceiver"
}
}
}
},
{
"Instantiate": {
"code": {
"Solidity": {
"contract": "Balance"
}
},
"value": 24
}
},
{
"Call": {
"dest": {
"Instantiated": 0
},
"data": "1c8d16b30000000000000000000000000303030303030303030303030303030303030303000000000000000000000000000000000000000000000000000000000000000a"
}
},
{
"Call": {
"dest": {
"Instantiated": 0
},
"data": "6ada15d90000000000000000000000000303030303030303030303030303030303030303000000000000000000000000000000000000000000000000000000000000000a"
}
}
]
}
*/
contract BalanceReceiver {
constructor() payable {}
fallback() external payable {}
}
contract Balance {
constructor() payable {
// 0 to EOA
transfer_to(payable(address(0xdeadbeef)), 0);
send_to(payable(address(0xdeadbeef)), 0);
// 1 to EOA
transfer_to(payable(address(0xcafebabe)), 1);
send_to(payable(address(0xcafebabe)), 1);
BalanceReceiver balanceReceiver = new BalanceReceiver();
// 0 to contract
transfer_to(payable(address(balanceReceiver)), 0);
send_to(payable(address(balanceReceiver)), 0);
// 1 to contract
transfer_to(payable(address(balanceReceiver)), 1);
send_to(payable(address(balanceReceiver)), 1);
}
function transfer_to(address payable _dest, uint _amount) public payable {
_dest.transfer(_amount);
}
function send_to(address payable _dest, uint _amount) public payable {
require(_dest.send(_amount));
}
}
+27
View File
@@ -0,0 +1,27 @@
// SPDX-License-Identifier: MIT
pragma solidity ^0.8;
/* runner.json
{
"differential": true,
"actions": [
{
"Instantiate": {
"code": {
"Solidity": {
"contract": "Coinbase"
}
}
}
}
]
}
*/
contract Coinbase {
constructor() payable {
address coinbase = address(0xFFfFfFffFFfffFFfFFfFFFFFffFFFffffFfFFFfF);
assert(block.coinbase == coinbase);
}
}
+50
View File
@@ -0,0 +1,50 @@
// SPDX-License-Identifier: MIT
pragma solidity ^0.8;
/* runner.json
{
"differential": true,
"actions": [
{
"Upload": {
"code": {
"Solidity": {
"contract": "CreateA"
}
}
}
},
{
"Instantiate": {
"code": {
"Solidity": {
"contract": "CreateB"
}
},
"value": 100000
}
}
]
}
*/
contract CreateA {
constructor() payable {}
}
contract CreateB {
constructor() payable {
bytes32 salt = hex"ff";
try new CreateA{salt: salt}() returns (CreateA) {} catch {
revert("the first instantiation should succeed");
}
try new CreateA{salt: salt}() returns (CreateA) {} catch {
return;
}
revert("the second instantiation should have failed");
}
}
@@ -0,0 +1,42 @@
// SPDX-License-Identifier: MIT
pragma solidity ^0.8;
/* runner.json
{
"differential": true,
"actions": [
{
"Instantiate": {
"code": {
"Solidity": {
"contract": "FunctionPointer"
}
}
}
},
{
"Call": {
"dest": {
"Instantiated": 0
},
"data": "26121ff0"
}
}
]
}
*/
contract FunctionPointer {
bool public flag = false;
function f0() public {
flag = true;
}
function f() public returns (bool) {
function() internal x = f0;
x();
return flag;
}
}
+75
View File
@@ -0,0 +1,75 @@
// SPDX-License-Identifier: MIT
pragma solidity ^0.8;
/* runner.json
{
"differential": false,
"actions": [
{
"Instantiate": {
"code": {
"Solidity": {
"contract": "Send"
}
},
"value": 211
}
},
{
"Call": {
"dest": {
"Instantiated": 0
},
"data": "1c8d16b30000000000000000000000000303030303030303030303030303030303030303000000000000000000000000000000000000000000000000000000000000000a"
}
},
{
"VerifyCall": {
"success": true
}
},
{
"Call": {
"dest": {
"Instantiated": 0
},
"data": "fb9e8d050000000000000000000000000000000000000000000000000000000000000001"
}
},
{
"VerifyCall": {
"success": false
}
},
{
"Call": {
"dest": {
"Instantiated": 0
},
"data": "fb9e8d050000000000000000000000000000000000000000000000000000000000000000"
}
},
{
"VerifyCall": {
"success": false
}
}
]
}
*/
contract Send {
constructor() payable {}
function transfer_self(uint _amount) public payable {
transfer_to(payable(address(this)), _amount);
}
function transfer_to(address payable _dest, uint _amount) public payable {
if (_dest.send(_amount)) {}
}
fallback() external {}
receive() external payable {}
}
+32 -11
View File
@@ -3,7 +3,7 @@ pragma solidity ^0.8;
/* runner.json /* runner.json
{ {
"differential": true, "differential": false,
"actions": [ "actions": [
{ {
"Instantiate": { "Instantiate": {
@@ -12,7 +12,7 @@ pragma solidity ^0.8;
"contract": "Transfer" "contract": "Transfer"
} }
}, },
"value": 11 "value": 211
} }
}, },
{ {
@@ -23,12 +23,35 @@ pragma solidity ^0.8;
"data": "1c8d16b30000000000000000000000000303030303030303030303030303030303030303000000000000000000000000000000000000000000000000000000000000000a" "data": "1c8d16b30000000000000000000000000303030303030303030303030303030303030303000000000000000000000000000000000000000000000000000000000000000a"
} }
}, },
{
"VerifyCall": {
"success": true
}
},
{ {
"Call": { "Call": {
"dest": { "dest": {
"Instantiated": 0 "Instantiated": 0
}, },
"data": "fb9e8d0500000000000000000000000003030303030303030303030303030303030303030000000000000000000000000000000000000000000000000000000000000001" "data": "fb9e8d050000000000000000000000000000000000000000000000000000000000000001"
}
},
{
"VerifyCall": {
"success": false
}
},
{
"Call": {
"dest": {
"Instantiated": 0
},
"data": "fb9e8d050000000000000000000000000000000000000000000000000000000000000000"
}
},
{
"VerifyCall": {
"success": false
} }
} }
] ]
@@ -36,19 +59,17 @@ pragma solidity ^0.8;
*/ */
contract Transfer { contract Transfer {
constructor() payable { constructor() payable {}
transfer_self(msg.value);
}
function address_self() internal view returns (address payable) {
return payable(address(this));
}
function transfer_self(uint _amount) public payable { function transfer_self(uint _amount) public payable {
transfer_to(address_self(), _amount); transfer_to(payable(address(this)), _amount);
} }
function transfer_to(address payable _dest, uint _amount) public payable { function transfer_to(address payable _dest, uint _amount) public payable {
_dest.transfer(_amount); _dest.transfer(_amount);
} }
fallback() external {}
receive() external payable {}
} }
+14
View File
@@ -156,6 +156,20 @@ case!("DivisionArithmetics.sol", DivisionArithmetics, sdivCall, division_arithme
case!("DivisionArithmetics.sol", DivisionArithmetics, modCall, division_arithmetics_mod, n: U256, d: U256); case!("DivisionArithmetics.sol", DivisionArithmetics, modCall, division_arithmetics_mod, n: U256, d: U256);
case!("DivisionArithmetics.sol", DivisionArithmetics, smodCall, division_arithmetics_smod, n: I256, d: I256); case!("DivisionArithmetics.sol", DivisionArithmetics, smodCall, division_arithmetics_smod, n: I256, d: I256);
sol!(
contract Send {
function transfer_self(uint _amount) public payable;
}
);
case!("Send.sol", Send, transfer_selfCall, send_self, amount: U256);
sol!(
contract Transfer {
function transfer_self(uint _amount) public payable;
}
);
case!("Transfer.sol", Transfer, transfer_selfCall, transfer_self, amount: U256);
sol!( sol!(
contract MStore8 { contract MStore8 {
function mStore8(uint value) public pure returns (uint256 word); function mStore8(uint value) public pure returns (uint256 word);
+47 -30
View File
@@ -37,10 +37,10 @@ test_spec!(events, "Events", "Events.sol");
test_spec!(storage, "Storage", "Storage.sol"); test_spec!(storage, "Storage", "Storage.sol");
test_spec!(mstore8, "MStore8", "MStore8.sol"); test_spec!(mstore8, "MStore8", "MStore8.sol");
test_spec!(address, "Context", "Context.sol"); test_spec!(address, "Context", "Context.sol");
test_spec!(balance, "Value", "Value.sol"); test_spec!(value, "Value", "Value.sol");
test_spec!(create, "CreateB", "Create.sol"); test_spec!(create, "CreateB", "Create.sol");
test_spec!(call, "Caller", "Call.sol"); test_spec!(call, "Caller", "Call.sol");
test_spec!(transfer, "Transfer", "Transfer.sol"); test_spec!(balance, "Balance", "Balance.sol");
test_spec!(return_data_oob, "ReturnDataOob", "ReturnDataOob.sol"); test_spec!(return_data_oob, "ReturnDataOob", "ReturnDataOob.sol");
test_spec!(immutables, "Immutables", "Immutables.sol"); test_spec!(immutables, "Immutables", "Immutables.sol");
test_spec!(transaction, "Transaction", "Transaction.sol"); test_spec!(transaction, "Transaction", "Transaction.sol");
@@ -50,6 +50,11 @@ test_spec!(gas_price, "GasPrice", "GasPrice.sol");
test_spec!(gas_left, "GasLeft", "GasLeft.sol"); test_spec!(gas_left, "GasLeft", "GasLeft.sol");
test_spec!(gas_limit, "GasLimit", "GasLimit.sol"); test_spec!(gas_limit, "GasLimit", "GasLimit.sol");
test_spec!(base_fee, "BaseFee", "BaseFee.sol"); test_spec!(base_fee, "BaseFee", "BaseFee.sol");
test_spec!(coinbase, "Coinbase", "Coinbase.sol");
test_spec!(create2, "CreateB", "Create2.sol");
test_spec!(transfer, "Transfer", "Transfer.sol");
test_spec!(send, "Send", "Send.sol");
test_spec!(function_pointer, "FunctionPointer", "FunctionPointer.sol");
fn instantiate(path: &str, contract: &str) -> Vec<SpecsAction> { fn instantiate(path: &str, contract: &str) -> Vec<SpecsAction> {
vec![Instantiate { vec![Instantiate {
@@ -61,7 +66,6 @@ fn instantiate(path: &str, contract: &str) -> Vec<SpecsAction> {
path: Some(path.into()), path: Some(path.into()),
contract: contract.to_string(), contract: contract.to_string(),
solc_optimizer: None, solc_optimizer: None,
pipeline: None,
}, },
data: vec![], data: vec![],
salt: OptionalHex::default(), salt: OptionalHex::default(),
@@ -354,7 +358,6 @@ fn ext_code_size() {
path: Some("contracts/Baseline.sol".into()), path: Some("contracts/Baseline.sol".into()),
contract: "Baseline".to_string(), contract: "Baseline".to_string(),
solc_optimizer: None, solc_optimizer: None,
pipeline: None,
}, },
data: vec![], data: vec![],
salt: OptionalHex::from([0; 32]), salt: OptionalHex::from([0; 32]),
@@ -435,32 +438,46 @@ fn ext_code_size() {
.run(); .run();
} }
/* #[test]
// These test were implement for the mock-runtime and need to be ported yet. #[should_panic(expected = "ReentranceDenied")]
fn send_denies_reentrancy() {
let value = 1000;
Specs {
actions: vec![
instantiate("contracts/Send.sol", "Send").remove(0),
Call {
origin: TestAddress::Alice,
dest: TestAddress::Instantiated(0),
value,
gas_limit: None,
storage_deposit_limit: None,
data: Contract::send_self(U256::from(value)).calldata,
},
],
differential: false,
..Default::default()
}
.run();
}
#[test] #[test]
fn create2_failure() { #[should_panic(expected = "ReentranceDenied")]
let mut state = State::default(); fn transfer_denies_reentrancy() {
let contract_a = Contract::create_a(); let value = 1000;
state.upload_code(&contract_a.pvm_runtime); Specs {
actions: vec![
let contract = Contract::create_b(); instantiate("contracts/Transfer.sol", "Transfer").remove(0),
let (state, output) = state Call {
.transaction() origin: TestAddress::Alice,
.with_default_account(&contract.pvm_runtime) dest: TestAddress::Instantiated(0),
.calldata(contract.calldata.clone()) value,
.call(); gas_limit: None,
storage_deposit_limit: None,
assert_eq!(output.flags, ReturnFlags::Success); data: Contract::transfer_self(U256::from(value)).calldata,
},
// The address already exists, which should cause the contract to revert ],
differential: false,
let (_, output) = state ..Default::default()
.transaction() }
.with_default_account(&contract.pvm_runtime) .run();
.calldata(contract.calldata)
.call();
assert_eq!(output.flags, ReturnFlags::Revert);
} }
*/
+3 -11
View File
@@ -40,17 +40,9 @@ pub const SHARED_BUILD_OPTS_NOT_MUSL: [&str; 4] = [
/// The shared build options to treat warnings as errors. /// The shared build options to treat warnings as errors.
/// ///
/// Disabled on Windows due to the following upstream issue with MSYS2 with mingw-w64: /// Disabled because it makes the build very brittle.
/// ProgramTest.cpp:23:15: error: '__p__environ' redeclared without 'dllimport' attribute pub fn shared_build_opts_werror(_target_env: TargetEnv) -> Vec<String> {
pub fn shared_build_opts_werror(target_env: TargetEnv) -> Vec<String> { vec!["-DLLVM_ENABLE_WERROR='Off'".to_string()]
vec![format!(
"-DLLVM_ENABLE_WERROR='{}'",
if cfg!(target_os = "windows") || target_env == TargetEnv::Emscripten {
"Off"
} else {
"On"
},
)]
} }
/// The build options to set the default target. /// The build options to set the default target.
@@ -141,7 +141,7 @@ fn build_target(
Command::new("emcmake") Command::new("emcmake")
.env("EMCC_DEBUG", "2") .env("EMCC_DEBUG", "2")
.env("CXXFLAGS", "-Dwait4=__syscall_wait4") .env("CXXFLAGS", "-Dwait4=__syscall_wait4")
.env("LDFLAGS", "-lnodefs.js -s NO_INVOKE_RUN -s EXIT_RUNTIME -s INITIAL_MEMORY=64MB -s ALLOW_MEMORY_GROWTH -s EXPORTED_RUNTIME_METHODS=FS,callMain,NODEFS -s MODULARIZE -s EXPORT_ES6 -s WASM_BIGINT") .env("LDFLAGS", "-lnodefs.js -s NO_INVOKE_RUN=1 -s EXIT_RUNTIME=1 -s ALLOW_MEMORY_GROWTH=1 -s EXPORTED_RUNTIME_METHODS=FS,callMain,NODEFS -s MODULARIZE=1 -s WASM_BIGINT=1 -s ALLOW_TABLE_GROWTH=1 -s NODEJS_CATCH_EXIT=0 -sDYNAMIC_EXECUTION=0")
.arg("cmake") .arg("cmake")
.args([ .args([
"-S", "-S",
-1
View File
@@ -21,7 +21,6 @@ serde = { workspace = true, features = ["derive"] }
num = { workspace = true } num = { workspace = true }
hex = { workspace = true } hex = { workspace = true }
sha3 = { workspace = true } sha3 = { workspace = true }
md5 = { workspace = true }
inkwell = { workspace = true } inkwell = { workspace = true }
polkavm-disassembler = { workspace = true } polkavm-disassembler = { workspace = true }
polkavm-common = { workspace = true } polkavm-common = { workspace = true }
@@ -1,16 +1,11 @@
//! The debug IR type. //! The debug IR type.
/// The debug IR type. /// The debug IR type.
#[allow(non_camel_case_types)]
#[allow(clippy::upper_case_acronyms)] #[allow(clippy::upper_case_acronyms)]
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)] #[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
pub enum IRType { pub enum IRType {
/// Whether to dump the Yul code. /// Whether to dump the Yul code.
Yul, Yul,
/// Whether to dump the EVM legacy assembly code.
EVMLA,
/// Whether to dump the Ethereal IR code.
EthIR,
/// Whether to dump the LLVM IR code. /// Whether to dump the LLVM IR code.
LLVM, LLVM,
/// Whether to dump the assembly code. /// Whether to dump the assembly code.
@@ -27,8 +22,6 @@ impl IRType {
pub fn file_extension(&self) -> &'static str { pub fn file_extension(&self) -> &'static str {
match self { match self {
Self::Yul => revive_common::EXTENSION_YUL, Self::Yul => revive_common::EXTENSION_YUL,
Self::EthIR => revive_common::EXTENSION_ETHIR,
Self::EVMLA => revive_common::EXTENSION_EVMLA,
Self::LLVM => revive_common::EXTENSION_LLVM_SOURCE, Self::LLVM => revive_common::EXTENSION_LLVM_SOURCE,
Self::Assembly => revive_common::EXTENSION_POLKAVM_ASSEMBLY, Self::Assembly => revive_common::EXTENSION_POLKAVM_ASSEMBLY,
#[cfg(debug_assertions)] #[cfg(debug_assertions)]
@@ -39,30 +39,6 @@ impl DebugConfig {
Ok(()) Ok(())
} }
/// Dumps the EVM legacy assembly IR.
pub fn dump_evmla(&self, contract_path: &str, code: &str) -> anyhow::Result<()> {
if let Some(output_directory) = self.output_directory.as_ref() {
let mut file_path = output_directory.to_owned();
let full_file_name = Self::full_file_name(contract_path, None, IRType::EVMLA);
file_path.push(full_file_name);
std::fs::write(file_path, code)?;
}
Ok(())
}
/// Dumps the Ethereal IR.
pub fn dump_ethir(&self, contract_path: &str, code: &str) -> anyhow::Result<()> {
if let Some(output_directory) = self.output_directory.as_ref() {
let mut file_path = output_directory.to_owned();
let full_file_name = Self::full_file_name(contract_path, None, IRType::EthIR);
file_path.push(full_file_name);
std::fs::write(file_path, code)?;
}
Ok(())
}
/// Dumps the unoptimized LLVM IR. /// Dumps the unoptimized LLVM IR.
pub fn dump_llvm_ir_unoptimized( pub fn dump_llvm_ir_unoptimized(
&self, &self,
-5
View File
@@ -17,12 +17,7 @@ pub use self::polkavm::context::attribute::Attribute as PolkaVMAttribute;
pub use self::polkavm::context::build::Build as PolkaVMBuild; pub use self::polkavm::context::build::Build as PolkaVMBuild;
pub use self::polkavm::context::code_type::CodeType as PolkaVMCodeType; pub use self::polkavm::context::code_type::CodeType as PolkaVMCodeType;
pub use self::polkavm::context::debug_info::DebugInfo; pub use self::polkavm::context::debug_info::DebugInfo;
pub use self::polkavm::context::evmla_data::EVMLAData as PolkaVMContextEVMLAData;
pub use self::polkavm::context::function::block::evmla_data::key::Key as PolkaVMFunctionBlockKey;
pub use self::polkavm::context::function::block::evmla_data::EVMLAData as PolkaVMFunctionBlockEVMLAData;
pub use self::polkavm::context::function::block::Block as PolkaVMFunctionBlock;
pub use self::polkavm::context::function::declaration::Declaration as PolkaVMFunctionDeclaration; pub use self::polkavm::context::function::declaration::Declaration as PolkaVMFunctionDeclaration;
pub use self::polkavm::context::function::evmla_data::EVMLAData as PolkaVMFunctionEVMLAData;
pub use self::polkavm::context::function::intrinsics::Intrinsics as PolkaVMIntrinsicFunction; pub use self::polkavm::context::function::intrinsics::Intrinsics as PolkaVMIntrinsicFunction;
pub use self::polkavm::context::function::llvm_runtime::LLVMRuntime as PolkaVMLLVMRuntime; pub use self::polkavm::context::function::llvm_runtime::LLVMRuntime as PolkaVMLLVMRuntime;
pub use self::polkavm::context::function::r#return::Return as PolkaVMFunctionReturn; pub use self::polkavm::context::function::r#return::Return as PolkaVMFunctionReturn;
@@ -1,27 +0,0 @@
//! The LLVM IR generator EVM legacy assembly data.
use crate::polkavm::context::argument::Argument;
/// The LLVM IR generator EVM legacy assembly data.
/// Describes some data that is only relevant to the EVM legacy assembly.
#[derive(Debug, Clone)]
pub struct EVMLAData<'ctx> {
/// The Solidity compiler version.
/// Some instruction behave differenly depending on the version.
pub version: semver::Version,
/// The static stack allocated for the current function.
pub stack: Vec<Argument<'ctx>>,
}
impl EVMLAData<'_> {
/// The default stack size.
pub const DEFAULT_STACK_SIZE: usize = 64;
/// A shortcut constructor.
pub fn new(version: semver::Version) -> Self {
Self {
version,
stack: Vec::with_capacity(Self::DEFAULT_STACK_SIZE),
}
}
}
@@ -1,34 +0,0 @@
//! The LLVM IR generator function block key.
use crate::polkavm::context::code_type::CodeType;
/// The LLVM IR generator function block key.
/// Is only relevant to the EVM legacy assembly.
#[derive(Debug, Clone, PartialEq, Eq, PartialOrd, Ord, Hash)]
pub struct Key {
/// The block code type.
pub code_type: CodeType,
/// The block tag.
pub tag: num::BigUint,
}
impl Key {
/// A shortcut constructor.
pub fn new(code_type: CodeType, tag: num::BigUint) -> Self {
Self { code_type, tag }
}
}
impl std::fmt::Display for Key {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
write!(
f,
"{}_{}",
match self.code_type {
CodeType::Deploy => "dt",
CodeType::Runtime => "rt",
},
self.tag
)
}
}
@@ -1,18 +0,0 @@
//! The LLVM function block EVM legacy assembly data.
pub mod key;
/// The LLVM function block EVM legacy assembly data.
/// Describes some data that is only relevant to the EVM legacy assembly.
#[derive(Debug, Clone)]
pub struct EVMLAData {
/// The initial hashes of the allowed stack states.
pub stack_hashes: Vec<md5::Digest>,
}
impl EVMLAData {
/// A shortcut constructor.
pub fn new(stack_hashes: Vec<md5::Digest>) -> Self {
Self { stack_hashes }
}
}
@@ -1,52 +0,0 @@
//! The LLVM IR generator function block.
pub mod evmla_data;
use self::evmla_data::EVMLAData;
/// The LLVM IR generator function block.
#[derive(Debug, Clone)]
pub struct Block<'ctx> {
/// The inner block.
inner: inkwell::basic_block::BasicBlock<'ctx>,
/// The EVM legacy assembly compiler data.
evmla_data: Option<EVMLAData>,
}
impl<'ctx> Block<'ctx> {
/// A shortcut constructor.
pub fn new(inner: inkwell::basic_block::BasicBlock<'ctx>) -> Self {
Self {
inner,
evmla_data: None,
}
}
/// Sets the EVM legacy assembly data.
pub fn set_evmla_data(&mut self, data: EVMLAData) {
self.evmla_data = Some(data);
}
/// The LLVM object reference.
pub fn inner(&self) -> inkwell::basic_block::BasicBlock<'ctx> {
self.inner
}
/// Returns the EVM data reference.
/// # Panics
/// If the EVM data has not been initialized.
pub fn evm(&self) -> &EVMLAData {
self.evmla_data
.as_ref()
.expect("The EVM data must have been initialized")
}
/// Returns the EVM data mutable reference.
/// # Panics
/// If the EVM data has not been initialized.
pub fn evm_mut(&mut self) -> &mut EVMLAData {
self.evmla_data
.as_mut()
.expect("The EVM data must have been initialized")
}
}
@@ -1,74 +0,0 @@
//! The LLVM function EVM legacy assembly data.
use std::collections::BTreeMap;
use crate::polkavm::context::function::block::evmla_data::key::Key as BlockKey;
use crate::polkavm::context::function::block::Block;
/// The LLVM function EVM legacy assembly data.
/// Describes some data that is only relevant to the EVM legacy assembly.
#[derive(Debug)]
pub struct EVMLAData<'ctx> {
/// The ordinary blocks with numeric tags.
/// Is only used by the Solidity EVM compiler.
pub blocks: BTreeMap<BlockKey, Vec<Block<'ctx>>>,
/// The function stack size.
pub stack_size: usize,
}
impl<'ctx> EVMLAData<'ctx> {
/// A shortcut constructor.
pub fn new(stack_size: usize) -> Self {
Self {
blocks: BTreeMap::new(),
stack_size,
}
}
/// Inserts a function block.
pub fn insert_block(&mut self, key: BlockKey, block: Block<'ctx>) {
if let Some(blocks) = self.blocks.get_mut(&key) {
blocks.push(block);
} else {
self.blocks.insert(key, vec![block]);
}
}
/// Returns the block with the specified tag and initial stack pattern.
/// If there is only one block, it is returned unconditionally.
pub fn find_block(
&self,
key: &BlockKey,
stack_hash: &md5::Digest,
) -> anyhow::Result<Block<'ctx>> {
if self
.blocks
.get(key)
.ok_or_else(|| anyhow::anyhow!("Undeclared function block {}", key))?
.len()
== 1
{
return self
.blocks
.get(key)
.ok_or_else(|| anyhow::anyhow!("Undeclared function block {}", key))?
.first()
.cloned()
.ok_or_else(|| anyhow::anyhow!("Undeclared function block {}", key));
}
self.blocks
.get(key)
.ok_or_else(|| anyhow::anyhow!("Undeclared function block {}", key))?
.iter()
.find(|block| {
block
.evm()
.stack_hashes
.iter()
.any(|hash| hash == stack_hash)
})
.cloned()
.ok_or_else(|| anyhow::anyhow!("Undeclared function block {}", key))
}
}
@@ -1,8 +1,6 @@
//! The LLVM IR generator function. //! The LLVM IR generator function.
pub mod block;
pub mod declaration; pub mod declaration;
pub mod evmla_data;
pub mod intrinsics; pub mod intrinsics;
pub mod llvm_runtime; pub mod llvm_runtime;
pub mod r#return; pub mod r#return;
@@ -19,7 +17,6 @@ use crate::polkavm::context::attribute::Attribute;
use crate::polkavm::context::pointer::Pointer; use crate::polkavm::context::pointer::Pointer;
use self::declaration::Declaration; use self::declaration::Declaration;
use self::evmla_data::EVMLAData;
use self::r#return::Return; use self::r#return::Return;
use self::yul_data::YulData; use self::yul_data::YulData;
@@ -45,8 +42,6 @@ pub struct Function<'ctx> {
/// The Yul compiler data. /// The Yul compiler data.
yul_data: Option<YulData>, yul_data: Option<YulData>,
/// The EVM legacy assembly compiler data.
evmla_data: Option<EVMLAData<'ctx>>,
} }
impl<'ctx> Function<'ctx> { impl<'ctx> Function<'ctx> {
@@ -72,7 +67,6 @@ impl<'ctx> Function<'ctx> {
return_block, return_block,
yul_data: None, yul_data: None,
evmla_data: None,
} }
} }
@@ -300,29 +294,6 @@ impl<'ctx> Function<'ctx> {
self.return_block self.return_block
} }
/// Sets the EVM legacy assembly data.
pub fn set_evmla_data(&mut self, data: EVMLAData<'ctx>) {
self.evmla_data = Some(data);
}
/// Returns the EVM legacy assembly data reference.
/// # Panics
/// If the EVM data has not been initialized.
pub fn evmla(&self) -> &EVMLAData<'ctx> {
self.evmla_data
.as_ref()
.expect("The EVM data must have been initialized")
}
/// Returns the EVM legacy assembly data mutable reference.
/// # Panics
/// If the EVM data has not been initialized.
pub fn evmla_mut(&mut self) -> &mut EVMLAData<'ctx> {
self.evmla_data
.as_mut()
.expect("The EVM data must have been initialized")
}
/// Sets the Yul data. /// Sets the Yul data.
pub fn set_yul_data(&mut self, data: YulData) { pub fn set_yul_data(&mut self, data: YulData) {
self.yul_data = Some(data); self.yul_data = Some(data);
@@ -6,7 +6,6 @@ pub mod attribute;
pub mod build; pub mod build;
pub mod code_type; pub mod code_type;
pub mod debug_info; pub mod debug_info;
pub mod evmla_data;
pub mod function; pub mod function;
pub mod global; pub mod global;
pub mod r#loop; pub mod r#loop;
@@ -38,7 +37,6 @@ use self::attribute::Attribute;
use self::build::Build; use self::build::Build;
use self::code_type::CodeType; use self::code_type::CodeType;
use self::debug_info::DebugInfo; use self::debug_info::DebugInfo;
use self::evmla_data::EVMLAData;
use self::function::declaration::Declaration as FunctionDeclaration; use self::function::declaration::Declaration as FunctionDeclaration;
use self::function::intrinsics::Intrinsics; use self::function::intrinsics::Intrinsics;
use self::function::llvm_runtime::LLVMRuntime; use self::function::llvm_runtime::LLVMRuntime;
@@ -95,8 +93,6 @@ where
solidity_data: Option<SolidityData>, solidity_data: Option<SolidityData>,
/// The Yul data. /// The Yul data.
yul_data: Option<YulData>, yul_data: Option<YulData>,
/// The EVM legacy assembly data.
evmla_data: Option<EVMLAData<'ctx>>,
} }
impl<'ctx, D> Context<'ctx, D> impl<'ctx, D> Context<'ctx, D>
@@ -257,7 +253,6 @@ where
solidity_data: None, solidity_data: None,
yul_data: None, yul_data: None,
evmla_data: None,
} }
} }
@@ -1574,29 +1569,6 @@ where
.expect("The Yul data must have been initialized") .expect("The Yul data must have been initialized")
} }
/// Sets the EVM legacy assembly data.
pub fn set_evmla_data(&mut self, data: EVMLAData<'ctx>) {
self.evmla_data = Some(data);
}
/// Returns the EVM legacy assembly data reference.
/// # Panics
/// If the EVM data has not been initialized.
pub fn evmla(&self) -> &EVMLAData<'ctx> {
self.evmla_data
.as_ref()
.expect("The EVMLA data must have been initialized")
}
/// Returns the EVM legacy assembly data mutable reference.
/// # Panics
/// If the EVM data has not been initialized.
pub fn evmla_mut(&mut self) -> &mut EVMLAData<'ctx> {
self.evmla_data
.as_mut()
.expect("The EVMLA data must have been initialized")
}
/// Returns the current number of immutables values in the contract. /// Returns the current number of immutables values in the contract.
/// If the size is set manually, then it is returned. Otherwise, the number of elements in /// If the size is set manually, then it is returned. Otherwise, the number of elements in
/// the identifier-to-offset mapping tree is returned. /// the identifier-to-offset mapping tree is returned.
+95 -18
View File
@@ -8,6 +8,7 @@ use crate::polkavm::Dependency;
const STATIC_CALL_FLAG: u32 = 0b0001_0000; const STATIC_CALL_FLAG: u32 = 0b0001_0000;
const REENTRANT_CALL_FLAG: u32 = 0b0000_1000; const REENTRANT_CALL_FLAG: u32 = 0b0000_1000;
const SOLIDITY_TRANSFER_GAS_STIPEND_THRESHOLD: u64 = 2300;
/// Translates a contract call. /// Translates a contract call.
#[allow(clippy::too_many_arguments)] #[allow(clippy::too_many_arguments)]
@@ -37,26 +38,24 @@ where
let output_offset = context.safe_truncate_int_to_xlen(output_offset)?; let output_offset = context.safe_truncate_int_to_xlen(output_offset)?;
let output_length = context.safe_truncate_int_to_xlen(output_length)?; let output_length = context.safe_truncate_int_to_xlen(output_length)?;
// TODO: What to supply here? Is there a weight to gas?
let _gas = context
.builder()
.build_int_truncate(gas, context.integer_type(64), "gas")?;
let input_pointer = context.build_heap_gep(input_offset, input_length)?; let input_pointer = context.build_heap_gep(input_offset, input_length)?;
let output_pointer = context.build_heap_gep(output_offset, output_length)?; let output_pointer = context.build_heap_gep(output_offset, output_length)?;
let output_length_pointer = context.build_alloca_at_entry(context.xlen_type(), "output_length"); let output_length_pointer = context.build_alloca_at_entry(context.xlen_type(), "output_length");
context.build_store(output_length_pointer, output_length)?; context.build_store(output_length_pointer, output_length)?;
let deposit_pointer = context.build_alloca_at_entry(context.word_type(), "deposit_pointer"); let (flags, deposit_limit_value) = if static_call {
context.build_store(deposit_pointer, context.word_type().const_all_ones())?; let flags = REENTRANT_CALL_FLAG | STATIC_CALL_FLAG;
(
let flags = if static_call { context.xlen_type().const_int(flags as u64, false),
REENTRANT_CALL_FLAG | STATIC_CALL_FLAG context.word_type().const_zero(),
)
} else { } else {
REENTRANT_CALL_FLAG call_reentrancy_heuristic(context, gas, input_length, output_length)?
}; };
let flags = context.xlen_type().const_int(flags as u64, false);
let deposit_pointer = context.build_alloca_at_entry(context.word_type(), "deposit_pointer");
context.build_store(deposit_pointer, deposit_limit_value)?;
let flags_and_callee = revive_runtime_api::calling_convention::pack_hi_lo_reg( let flags_and_callee = revive_runtime_api::calling_convention::pack_hi_lo_reg(
context.builder(), context.builder(),
@@ -119,7 +118,7 @@ where
#[allow(clippy::too_many_arguments)] #[allow(clippy::too_many_arguments)]
pub fn delegate_call<'ctx, D>( pub fn delegate_call<'ctx, D>(
context: &mut Context<'ctx, D>, context: &mut Context<'ctx, D>,
gas: inkwell::values::IntValue<'ctx>, _gas: inkwell::values::IntValue<'ctx>,
address: inkwell::values::IntValue<'ctx>, address: inkwell::values::IntValue<'ctx>,
input_offset: inkwell::values::IntValue<'ctx>, input_offset: inkwell::values::IntValue<'ctx>,
input_length: inkwell::values::IntValue<'ctx>, input_length: inkwell::values::IntValue<'ctx>,
@@ -137,11 +136,6 @@ where
let output_offset = context.safe_truncate_int_to_xlen(output_offset)?; let output_offset = context.safe_truncate_int_to_xlen(output_offset)?;
let output_length = context.safe_truncate_int_to_xlen(output_length)?; let output_length = context.safe_truncate_int_to_xlen(output_length)?;
// TODO: What to supply here? Is there a weight to gas?
let _gas = context
.builder()
.build_int_truncate(gas, context.integer_type(64), "gas")?;
let input_pointer = context.build_heap_gep(input_offset, input_length)?; let input_pointer = context.build_heap_gep(input_offset, input_length)?;
let output_pointer = context.build_heap_gep(output_offset, output_length)?; let output_pointer = context.build_heap_gep(output_offset, output_length)?;
@@ -221,3 +215,86 @@ where
.resolve_library(path.as_str())? .resolve_library(path.as_str())?
.as_basic_value_enum()) .as_basic_value_enum())
} }
/// The Solidity `address.transfer` and `address.send` call detection heuristic.
///
/// # Why
/// This heuristic is an additional security feature to guard against re-entrancy attacks
/// in case contract authors violate Solidity best practices and use `address.transfer` or
/// `address.send`.
/// While contract authors are supposed to never use `address.transfer` or `address.send`,
/// for a small cost we can be extra defensive about it.
///
/// # How
/// The gas stipend emitted by solc for `transfer` and `send` is not static, thus:
/// - Dynamically allow re-entrancy only for calls considered not transfer or send.
/// - Detected balance transfers will supply 0 deposit limit instead of `u256::MAX`.
///
/// Calls are considered transfer or send if:
/// - (Input length | Output lenght) == 0;
/// - Gas <= 2300;
///
/// # Returns
/// The call flags xlen `IntValue` and the deposit limit word `IntValue`.
fn call_reentrancy_heuristic<'ctx, D>(
context: &mut Context<'ctx, D>,
gas: inkwell::values::IntValue<'ctx>,
input_length: inkwell::values::IntValue<'ctx>,
output_length: inkwell::values::IntValue<'ctx>,
) -> anyhow::Result<(
inkwell::values::IntValue<'ctx>,
inkwell::values::IntValue<'ctx>,
)>
where
D: Dependency + Clone,
{
// Branch-free SSA implementation: First derive the heuristic boolean (int1) value.
let input_length_or_output_length =
context
.builder()
.build_or(input_length, output_length, "input_length_or_output_length")?;
let is_no_input_no_output = context.builder().build_int_compare(
inkwell::IntPredicate::EQ,
context.xlen_type().const_zero(),
input_length_or_output_length,
"is_no_input_no_output",
)?;
let gas_stipend = context
.word_type()
.const_int(SOLIDITY_TRANSFER_GAS_STIPEND_THRESHOLD, false);
let is_gas_stipend_for_transfer_or_send = context.builder().build_int_compare(
inkwell::IntPredicate::ULE,
gas,
gas_stipend,
"is_gas_stipend_for_transfer_or_send",
)?;
let is_balance_transfer = context.builder().build_and(
is_no_input_no_output,
is_gas_stipend_for_transfer_or_send,
"is_balance_transfer",
)?;
let is_regular_call = context
.builder()
.build_not(is_balance_transfer, "is_balance_transfer_inverted")?;
// Call flag: Left shift the heuristic boolean value.
let is_regular_call_xlen = context.builder().build_int_z_extend(
is_regular_call,
context.xlen_type(),
"is_balance_transfer_xlen",
)?;
let call_flags = context.builder().build_left_shift(
is_regular_call_xlen,
context.xlen_type().const_int(3, false),
"flags",
)?;
// Deposit limit value: Sign-extended the heuristic boolean value.
let deposit_limit_value = context.builder().build_int_s_extend(
is_regular_call,
context.word_type(),
"deposit_limit_value",
)?;
Ok((call_flags, deposit_limit_value))
}
+10 -2
View File
@@ -122,12 +122,20 @@ where
/// Translates the `coinbase` instruction. /// Translates the `coinbase` instruction.
pub fn coinbase<'ctx, D>( pub fn coinbase<'ctx, D>(
_context: &mut Context<'ctx, D>, context: &mut Context<'ctx, D>,
) -> anyhow::Result<inkwell::values::BasicValueEnum<'ctx>> ) -> anyhow::Result<inkwell::values::BasicValueEnum<'ctx>>
where where
D: Dependency + Clone, D: Dependency + Clone,
{ {
todo!() let pointer = context.build_alloca_at_entry(
context.integer_type(revive_common::BIT_LENGTH_ETH_ADDRESS),
"coinbase_output",
);
context.build_runtime_call(
revive_runtime_api::polkavm_imports::BLOCK_AUTHOR,
&[pointer.to_int(context).into()],
);
context.build_load_address(pointer)
} }
/// Translates the `basefee` instruction. /// Translates the `basefee` instruction.
-1
View File
@@ -59,7 +59,6 @@ pub fn build_assembly_text(
} }
/// Implemented by items which are translated into LLVM IR. /// Implemented by items which are translated into LLVM IR.
#[allow(clippy::upper_case_acronyms)]
pub trait WriteLLVM<D> pub trait WriteLLVM<D>
where where
D: Dependency + Clone, D: Dependency + Clone,
-3
View File
@@ -239,7 +239,6 @@ pub enum Code {
Solidity { Solidity {
path: Option<std::path::PathBuf>, path: Option<std::path::PathBuf>,
solc_optimizer: Option<bool>, solc_optimizer: Option<bool>,
pipeline: Option<revive_solidity::SolcPipeline>,
contract: String, contract: String,
}, },
/// Read the contract blob from disk /// Read the contract blob from disk
@@ -264,7 +263,6 @@ impl From<Code> for pallet_revive::Code {
path, path,
contract, contract,
solc_optimizer, solc_optimizer,
pipeline,
} => { } => {
let Some(path) = path else { let Some(path) = path else {
panic!("Solidity source of contract '{contract}' missing path"); panic!("Solidity source of contract '{contract}' missing path");
@@ -276,7 +274,6 @@ impl From<Code> for pallet_revive::Code {
&contract, &contract,
&source_code, &source_code,
solc_optimizer.unwrap_or(true), solc_optimizer.unwrap_or(true),
pipeline.unwrap_or(revive_solidity::SolcPipeline::Yul),
)) ))
} }
Code::Path(path) => pallet_revive::Code::Upload(std::fs::read(path).unwrap()), Code::Path(path) => pallet_revive::Code::Upload(std::fs::read(path).unwrap()),
+13 -5
View File
@@ -1,5 +1,4 @@
use frame_support::{runtime, weights::constants::WEIGHT_REF_TIME_PER_SECOND}; use frame_support::{runtime, traits::FindAuthor, weights::constants::WEIGHT_REF_TIME_PER_SECOND};
use pallet_revive::AccountId32Mapper; use pallet_revive::AccountId32Mapper;
use polkadot_sdk::*; use polkadot_sdk::*;
use polkadot_sdk::{ use polkadot_sdk::{
@@ -11,8 +10,6 @@ pub type Balance = u128;
pub type AccountId = pallet_revive::AccountId32Mapper<Runtime>; pub type AccountId = pallet_revive::AccountId32Mapper<Runtime>;
pub type Block = frame_system::mocking::MockBlock<Runtime>; pub type Block = frame_system::mocking::MockBlock<Runtime>;
pub type Hash = <Runtime as frame_system::Config>::Hash; pub type Hash = <Runtime as frame_system::Config>::Hash;
pub type EventRecord =
frame_system::EventRecord<<Runtime as frame_system::Config>::RuntimeEvent, Hash>;
#[runtime] #[runtime]
mod runtime { mod runtime {
@@ -26,7 +23,8 @@ mod runtime {
RuntimeHoldReason, RuntimeHoldReason,
RuntimeSlashReason, RuntimeSlashReason,
RuntimeLockId, RuntimeLockId,
RuntimeTask RuntimeTask,
RuntimeViewFunction
)] )]
pub struct Runtime; pub struct Runtime;
@@ -88,4 +86,14 @@ impl pallet_revive::Config for Runtime {
type InstantiateOrigin = EnsureSigned<AccountId32>; type InstantiateOrigin = EnsureSigned<AccountId32>;
type CodeHashLockupDepositPercent = CodeHashLockupDepositPercent; type CodeHashLockupDepositPercent = CodeHashLockupDepositPercent;
type ChainId = ConstU64<420_420_420>; type ChainId = ConstU64<420_420_420>;
type FindAuthor = Self;
}
impl FindAuthor<<Runtime as frame_system::Config>::AccountId> for Runtime {
fn find_author<'a, I>(_digests: I) -> Option<<Runtime as frame_system::Config>::AccountId>
where
I: 'a + IntoIterator<Item = (frame_support::ConsensusEngineId, &'a [u8])>,
{
Some([0xff; 32].into())
}
} }
-6
View File
@@ -282,17 +282,11 @@ impl Specs {
let Code::Solidity { let Code::Solidity {
path: Some(path), path: Some(path),
solc_optimizer, solc_optimizer,
pipeline,
contract, contract,
} = code } = code
else { else {
panic!("the differential runner requires Code::Solidity source"); panic!("the differential runner requires Code::Solidity source");
}; };
assert_ne!(
pipeline,
Some(revive_solidity::SolcPipeline::EVMLA),
"yul pipeline must be enabled in differential mode"
);
assert!( assert!(
salt.0.is_none(), salt.0.is_none(),
"salt is not supported in differential mode" "salt is not supported in differential mode"
+2
View File
@@ -72,6 +72,8 @@ POLKAVM_IMPORT(void, balance_of, uint32_t, uint32_t)
POLKAVM_IMPORT(void, base_fee, uint32_t) POLKAVM_IMPORT(void, base_fee, uint32_t)
POLKAVM_IMPORT(void, block_author, uint32_t)
POLKAVM_IMPORT(void, block_hash, uint32_t, uint32_t) POLKAVM_IMPORT(void, block_hash, uint32_t, uint32_t)
POLKAVM_IMPORT(void, block_number, uint32_t) POLKAVM_IMPORT(void, block_number, uint32_t)
+4 -1
View File
@@ -22,6 +22,8 @@ pub static BALANCE_OF: &str = "balance_of";
pub static BASE_FEE: &str = "base_fee"; pub static BASE_FEE: &str = "base_fee";
pub static BLOCK_AUTHOR: &str = "block_author";
pub static BLOCK_HASH: &str = "block_hash"; pub static BLOCK_HASH: &str = "block_hash";
pub static BLOCK_NUMBER: &str = "block_number"; pub static BLOCK_NUMBER: &str = "block_number";
@@ -80,13 +82,14 @@ pub static WEIGHT_TO_FEE: &str = "weight_to_fee";
/// All imported runtime API symbols. /// All imported runtime API symbols.
/// Useful for configuring common attributes and linkage. /// Useful for configuring common attributes and linkage.
pub static IMPORTS: [&str; 34] = [ pub static IMPORTS: [&str; 35] = [
SBRK, SBRK,
MEMORY_SIZE, MEMORY_SIZE,
ADDRESS, ADDRESS,
BALANCE, BALANCE,
BALANCE_OF, BALANCE_OF,
BASE_FEE, BASE_FEE,
BLOCK_AUTHOR,
BLOCK_HASH, BLOCK_HASH,
BLOCK_NUMBER, BLOCK_NUMBER,
CALL, CALL,
-1
View File
@@ -33,7 +33,6 @@ regex = { workspace = true }
hex = { workspace = true } hex = { workspace = true }
num = { workspace = true } num = { workspace = true }
sha3 = { workspace = true } sha3 = { workspace = true }
md5 = { workspace = true }
inkwell = { workspace = true } inkwell = { workspace = true }
revive-common = { workspace = true } revive-common = { workspace = true }
+2 -2
View File
@@ -64,7 +64,7 @@ impl Contract {
file_path.push(file_name); file_path.push(file_name);
if file_path.exists() && !overwrite { if file_path.exists() && !overwrite {
eprintln!( anyhow::bail!(
"Refusing to overwrite an existing file {file_path:?} (use --overwrite to force)." "Refusing to overwrite an existing file {file_path:?} (use --overwrite to force)."
); );
} else { } else {
@@ -87,7 +87,7 @@ impl Contract {
file_path.push(file_name); file_path.push(file_name);
if file_path.exists() && !overwrite { if file_path.exists() && !overwrite {
eprintln!( anyhow::bail!(
"Refusing to overwrite an existing file {file_path:?} (use --overwrite to force)." "Refusing to overwrite an existing file {file_path:?} (use --overwrite to force)."
); );
} else { } else {
-2
View File
@@ -1,7 +1,5 @@
//! Solidity to PolkaVM compiler constants. //! Solidity to PolkaVM compiler constants.
#![allow(dead_code)]
/// The default executable name. /// The default executable name.
pub static DEFAULT_EXECUTABLE_NAME: &str = "resolc"; pub static DEFAULT_EXECUTABLE_NAME: &str = "resolc";
@@ -1,67 +0,0 @@
//! The inner JSON legacy assembly code element.
use std::collections::HashSet;
use serde::Deserialize;
use serde::Serialize;
use crate::evmla::assembly::Assembly;
/// The inner JSON legacy assembly code element.
#[derive(Debug, Deserialize, Serialize, Clone)]
#[serde(untagged)]
pub enum Data {
/// The assembly code wrapper.
Assembly(Assembly),
/// The hash.
Hash(String),
/// The full contract path after the factory dependencies replacing pass.
Path(String),
}
impl Data {
/// Returns the inner assembly reference if it is present.
pub fn get_assembly(&self) -> Option<&Assembly> {
match self {
Self::Assembly(ref assembly) => Some(assembly),
Self::Hash(_) => None,
Self::Path(_) => None,
}
}
/// Returns the inner assembly mutable reference if it is present.
pub fn get_assembly_mut(&mut self) -> Option<&mut Assembly> {
match self {
Self::Assembly(ref mut assembly) => Some(assembly),
Self::Hash(_) => None,
Self::Path(_) => None,
}
}
/// Get the list of missing deployable libraries.
pub fn get_missing_libraries(&self) -> HashSet<String> {
match self {
Self::Assembly(assembly) => assembly.get_missing_libraries(),
Self::Hash(_) => HashSet::new(),
Self::Path(_) => HashSet::new(),
}
}
/// Gets the contract `keccak256` hash.
pub fn keccak256(&self) -> String {
match self {
Self::Assembly(assembly) => assembly.keccak256(),
Self::Hash(hash) => panic!("Expected assembly, found hash `{hash}`"),
Self::Path(path) => panic!("Expected assembly, found path `{path}`"),
}
}
}
impl std::fmt::Display for Data {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
match self {
Self::Assembly(inner) => writeln!(f, "{inner}"),
Self::Hash(inner) => writeln!(f, "Hash `{inner}`"),
Self::Path(inner) => writeln!(f, "Path `{inner}`"),
}
}
}
@@ -1,79 +0,0 @@
//! Translates the CODECOPY use cases.
/// Translates the contract hash copying.
pub fn contract_hash<'ctx, D>(
context: &mut revive_llvm_context::PolkaVMContext<'ctx, D>,
offset: inkwell::values::IntValue<'ctx>,
value: inkwell::values::IntValue<'ctx>,
) -> anyhow::Result<()>
where
D: revive_llvm_context::PolkaVMDependency + Clone,
{
let offset = context.builder().build_int_add(
offset,
context
.word_const((revive_common::BYTE_LENGTH_X32 + revive_common::BYTE_LENGTH_WORD) as u64),
"datacopy_contract_hash_offset",
)?;
revive_llvm_context::polkavm_evm_memory::store(context, offset, value)?;
Ok(())
}
/// Translates the library marker copying.
pub fn library_marker<D>(
context: &mut revive_llvm_context::PolkaVMContext<D>,
offset: u64,
value: u64,
) -> anyhow::Result<()>
where
D: revive_llvm_context::PolkaVMDependency + Clone,
{
revive_llvm_context::polkavm_evm_memory::store_byte(
context,
context.word_const(offset),
context.word_const(value),
)?;
Ok(())
}
/// Translates the static data copying.
pub fn static_data<'ctx, D>(
context: &mut revive_llvm_context::PolkaVMContext<'ctx, D>,
destination: inkwell::values::IntValue<'ctx>,
source: &str,
) -> anyhow::Result<()>
where
D: revive_llvm_context::PolkaVMDependency + Clone,
{
let mut offset = 0;
for (index, chunk) in source
.chars()
.collect::<Vec<char>>()
.chunks(revive_common::BYTE_LENGTH_WORD * 2)
.enumerate()
{
let mut value_string = chunk.iter().collect::<String>();
value_string.push_str(
"0".repeat((revive_common::BYTE_LENGTH_WORD * 2) - chunk.len())
.as_str(),
);
let datacopy_destination = context.builder().build_int_add(
destination,
context.word_const(offset as u64),
format!("datacopy_destination_index_{index}").as_str(),
)?;
let datacopy_value = context.word_const_str_hex(value_string.as_str());
revive_llvm_context::polkavm_evm_memory::store(
context,
datacopy_destination,
datacopy_value,
)?;
offset += chunk.len() / 2;
}
Ok(())
}
@@ -1,69 +0,0 @@
//! Translates the jump operations.
/// Translates the unconditional jump.
pub fn unconditional<D>(
context: &mut revive_llvm_context::PolkaVMContext<D>,
destination: num::BigUint,
stack_hash: md5::Digest,
) -> anyhow::Result<()>
where
D: revive_llvm_context::PolkaVMDependency + Clone,
{
let code_type = context
.code_type()
.ok_or_else(|| anyhow::anyhow!("The contract code part type is undefined"))?;
let block_key = revive_llvm_context::PolkaVMFunctionBlockKey::new(code_type, destination);
let block = context
.current_function()
.borrow()
.evmla()
.find_block(&block_key, &stack_hash)?;
context.build_unconditional_branch(block.inner());
Ok(())
}
/// Translates the conditional jump.
pub fn conditional<D>(
context: &mut revive_llvm_context::PolkaVMContext<D>,
destination: num::BigUint,
stack_hash: md5::Digest,
stack_height: usize,
) -> anyhow::Result<()>
where
D: revive_llvm_context::PolkaVMDependency + Clone,
{
let code_type = context
.code_type()
.ok_or_else(|| anyhow::anyhow!("The contract code part type is undefined"))?;
let block_key = revive_llvm_context::PolkaVMFunctionBlockKey::new(code_type, destination);
let condition_pointer = context.evmla().stack[stack_height]
.to_llvm()
.into_pointer_value();
let condition = context.build_load(
revive_llvm_context::PolkaVMPointer::new_stack_field(context, condition_pointer),
format!("conditional_{block_key}_condition").as_str(),
)?;
let condition = context.builder().build_int_compare(
inkwell::IntPredicate::NE,
condition.into_int_value(),
context.word_const(0),
format!("conditional_{block_key}_condition_compared").as_str(),
)?;
let then_block = context
.current_function()
.borrow()
.evmla()
.find_block(&block_key, &stack_hash)?;
let join_block =
context.append_basic_block(format!("conditional_{block_key}_join_block").as_str());
context.build_conditional_branch(condition, then_block.inner(), join_block)?;
context.set_basic_block(join_block);
Ok(())
}
@@ -1,382 +0,0 @@
//! The EVM instruction.
pub mod codecopy;
pub mod jump;
pub mod name;
pub mod stack;
use std::collections::BTreeMap;
use serde::Deserialize;
use serde::Serialize;
use self::name::Name;
/// The EVM instruction.
#[derive(Debug, Deserialize, Serialize, Clone)]
pub struct Instruction {
/// The opcode or tag identifier.
pub name: Name,
/// The optional value argument.
pub value: Option<String>,
/// The source code identifier.
#[serde(skip_serializing_if = "Option::is_none")]
pub source: Option<isize>,
/// The source code location begin.
pub begin: isize,
/// The source code location end.
pub end: isize,
}
impl Instruction {
/// Returns the number of input stack arguments.
pub const fn input_size(&self, version: &semver::Version) -> usize {
match self.name {
Name::POP => 1,
Name::JUMP => 1,
Name::JUMPI => 2,
Name::ADD => 2,
Name::SUB => 2,
Name::MUL => 2,
Name::DIV => 2,
Name::MOD => 2,
Name::SDIV => 2,
Name::SMOD => 2,
Name::LT => 2,
Name::GT => 2,
Name::EQ => 2,
Name::ISZERO => 1,
Name::SLT => 2,
Name::SGT => 2,
Name::OR => 2,
Name::XOR => 2,
Name::NOT => 1,
Name::AND => 2,
Name::SHL => 2,
Name::SHR => 2,
Name::SAR => 2,
Name::BYTE => 2,
Name::ADDMOD => 3,
Name::MULMOD => 3,
Name::EXP => 2,
Name::SIGNEXTEND => 2,
Name::SHA3 => 2,
Name::KECCAK256 => 2,
Name::MLOAD => 1,
Name::MSTORE => 2,
Name::MSTORE8 => 2,
Name::MCOPY => 3,
Name::SLOAD => 1,
Name::SSTORE => 2,
Name::TLOAD => 1,
Name::TSTORE => 2,
Name::PUSHIMMUTABLE => 0,
Name::ASSIGNIMMUTABLE => {
if version.minor >= 8 {
2
} else {
1
}
}
Name::CALLDATALOAD => 1,
Name::CALLDATACOPY => 3,
Name::CODECOPY => 3,
Name::RETURNDATACOPY => 3,
Name::EXTCODESIZE => 1,
Name::EXTCODEHASH => 1,
Name::CALL => 7,
Name::CALLCODE => 7,
Name::STATICCALL => 6,
Name::DELEGATECALL => 6,
Name::RETURN => 2,
Name::REVERT => 2,
Name::SELFDESTRUCT => 1,
Name::LOG0 => 2,
Name::LOG1 => 3,
Name::LOG2 => 4,
Name::LOG3 => 5,
Name::LOG4 => 6,
Name::CREATE => 3,
Name::CREATE2 => 4,
Name::ZK_CREATE => 3,
Name::ZK_CREATE2 => 4,
Name::BALANCE => 1,
Name::BLOCKHASH => 1,
Name::BLOBHASH => 1,
Name::EXTCODECOPY => 4,
Name::RecursiveCall { input_size, .. } => input_size,
Name::RecursiveReturn { input_size } => input_size,
_ => 0,
}
}
/// Returns the number of output stack arguments.
pub const fn output_size(&self) -> usize {
match self.name {
Name::PUSH => 1,
Name::PUSH_Data => 1,
Name::PUSH_Tag => 1,
Name::PUSH_ContractHash => 1,
Name::PUSH_ContractHashSize => 1,
Name::PUSHLIB => 1,
Name::PUSHDEPLOYADDRESS => 1,
Name::PUSH1 => 1,
Name::PUSH2 => 1,
Name::PUSH3 => 1,
Name::PUSH4 => 1,
Name::PUSH5 => 1,
Name::PUSH6 => 1,
Name::PUSH7 => 1,
Name::PUSH8 => 1,
Name::PUSH9 => 1,
Name::PUSH10 => 1,
Name::PUSH11 => 1,
Name::PUSH12 => 1,
Name::PUSH13 => 1,
Name::PUSH14 => 1,
Name::PUSH15 => 1,
Name::PUSH16 => 1,
Name::PUSH17 => 1,
Name::PUSH18 => 1,
Name::PUSH19 => 1,
Name::PUSH20 => 1,
Name::PUSH21 => 1,
Name::PUSH22 => 1,
Name::PUSH23 => 1,
Name::PUSH24 => 1,
Name::PUSH25 => 1,
Name::PUSH26 => 1,
Name::PUSH27 => 1,
Name::PUSH28 => 1,
Name::PUSH29 => 1,
Name::PUSH30 => 1,
Name::PUSH31 => 1,
Name::PUSH32 => 1,
Name::DUP1 => 1,
Name::DUP2 => 1,
Name::DUP3 => 1,
Name::DUP4 => 1,
Name::DUP5 => 1,
Name::DUP6 => 1,
Name::DUP7 => 1,
Name::DUP8 => 1,
Name::DUP9 => 1,
Name::DUP10 => 1,
Name::DUP11 => 1,
Name::DUP12 => 1,
Name::DUP13 => 1,
Name::DUP14 => 1,
Name::DUP15 => 1,
Name::DUP16 => 1,
Name::ADD => 1,
Name::SUB => 1,
Name::MUL => 1,
Name::DIV => 1,
Name::MOD => 1,
Name::SDIV => 1,
Name::SMOD => 1,
Name::LT => 1,
Name::GT => 1,
Name::EQ => 1,
Name::ISZERO => 1,
Name::SLT => 1,
Name::SGT => 1,
Name::OR => 1,
Name::XOR => 1,
Name::NOT => 1,
Name::AND => 1,
Name::SHL => 1,
Name::SHR => 1,
Name::SAR => 1,
Name::BYTE => 1,
Name::ADDMOD => 1,
Name::MULMOD => 1,
Name::EXP => 1,
Name::SIGNEXTEND => 1,
Name::SHA3 => 1,
Name::KECCAK256 => 1,
Name::MLOAD => 1,
Name::SLOAD => 1,
Name::TLOAD => 1,
Name::PUSHIMMUTABLE => 1,
Name::CALLDATALOAD => 1,
Name::CALLDATASIZE => 1,
Name::CODESIZE => 1,
Name::PUSHSIZE => 1,
Name::RETURNDATASIZE => 1,
Name::EXTCODESIZE => 1,
Name::EXTCODEHASH => 1,
Name::CALL => 1,
Name::CALLCODE => 1,
Name::STATICCALL => 1,
Name::DELEGATECALL => 1,
Name::CREATE => 1,
Name::CREATE2 => 1,
Name::ZK_CREATE => 1,
Name::ZK_CREATE2 => 1,
Name::ADDRESS => 1,
Name::CALLER => 1,
Name::TIMESTAMP => 1,
Name::NUMBER => 1,
Name::CALLVALUE => 1,
Name::GAS => 1,
Name::BALANCE => 1,
Name::SELFBALANCE => 1,
Name::GASLIMIT => 1,
Name::GASPRICE => 1,
Name::ORIGIN => 1,
Name::CHAINID => 1,
Name::BLOCKHASH => 1,
Name::BLOBHASH => 1,
Name::DIFFICULTY => 1,
Name::PREVRANDAO => 1,
Name::COINBASE => 1,
Name::MSIZE => 1,
Name::BASEFEE => 1,
Name::BLOBBASEFEE => 1,
Name::PC => 1,
Name::RecursiveCall { output_size, .. } => output_size,
_ => 0,
}
}
/// Replaces the instruction data aliases with the actual data.
pub fn replace_data_aliases(
instructions: &mut [Self],
mapping: &BTreeMap<String, String>,
) -> anyhow::Result<()> {
for instruction in instructions.iter_mut() {
match instruction {
Instruction {
name: Name::PUSH_ContractHash | Name::PUSH_ContractHashSize,
value: Some(value),
..
} => {
*value = mapping.get(value.as_str()).cloned().ok_or_else(|| {
anyhow::anyhow!("Contract alias `{}` data not found", value)
})?;
}
Instruction {
name: Name::PUSH_Data,
value: Some(value),
..
} => {
let mut key_extended =
"0".repeat(revive_common::BYTE_LENGTH_WORD * 2 - value.len());
key_extended.push_str(value.as_str());
*value = mapping.get(key_extended.as_str()).cloned().ok_or_else(|| {
anyhow::anyhow!("Data chunk alias `{}` data not found", key_extended)
})?;
}
_ => {}
}
}
Ok(())
}
/// Initializes an `INVALID` instruction to terminate an invalid unreachable block part.
pub fn invalid(previous: &Self) -> Self {
Self {
name: Name::INVALID,
value: None,
source: previous.source,
begin: previous.begin,
end: previous.end,
}
}
/// Initializes a recursive function `Call` instruction.
pub fn recursive_call(
name: String,
entry_key: revive_llvm_context::PolkaVMFunctionBlockKey,
stack_hash: md5::Digest,
input_size: usize,
output_size: usize,
return_address: revive_llvm_context::PolkaVMFunctionBlockKey,
previous: &Self,
) -> Self {
Self {
name: Name::RecursiveCall {
name,
entry_key,
stack_hash,
input_size,
output_size,
return_address,
},
value: None,
source: previous.source,
begin: previous.begin,
end: previous.end,
}
}
/// Initializes a recursive function `Return` instruction.
pub fn recursive_return(input_size: usize, previous: &Self) -> Self {
Self {
name: Name::RecursiveReturn { input_size },
value: None,
source: previous.source,
begin: previous.begin,
end: previous.end,
}
}
}
impl std::fmt::Display for Instruction {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
let name = self.name.to_string();
match self.name {
Name::Tag => write!(f, "{:4}", name),
_ => write!(f, "{:15}", name),
}?;
match self.value {
Some(ref value) if value.len() <= 64 => write!(f, "{}", value)?,
Some(ref value) => write!(f, "... {}", &value[value.len() - 60..])?,
None => {}
}
Ok(())
}
}
@@ -1,417 +0,0 @@
//! The EVM instruction name.
use serde::Deserialize;
use serde::Serialize;
/// The EVM instruction name.
#[derive(Debug, Serialize, Deserialize, Clone, PartialEq, Eq, Hash)]
#[allow(non_camel_case_types)]
#[allow(clippy::upper_case_acronyms)]
pub enum Name {
/// The eponymous EVM instruction.
PUSH,
/// Pushes a constant tag index.
#[serde(rename = "PUSH [tag]")]
PUSH_Tag,
/// Pushes an unknown `data` value.
#[serde(rename = "PUSH data")]
PUSH_Data,
/// Pushes a contract hash size.
#[serde(rename = "PUSH #[$]")]
PUSH_ContractHashSize,
/// Pushes a contract hash.
#[serde(rename = "PUSH [$]")]
PUSH_ContractHash,
/// The eponymous EVM instruction.
PUSH1,
/// The eponymous EVM instruction.
PUSH2,
/// The eponymous EVM instruction.
PUSH3,
/// The eponymous EVM instruction.
PUSH4,
/// The eponymous EVM instruction.
PUSH5,
/// The eponymous EVM instruction.
PUSH6,
/// The eponymous EVM instruction.
PUSH7,
/// The eponymous EVM instruction.
PUSH8,
/// The eponymous EVM instruction.
PUSH9,
/// The eponymous EVM instruction.
PUSH10,
/// The eponymous EVM instruction.
PUSH11,
/// The eponymous EVM instruction.
PUSH12,
/// The eponymous EVM instruction.
PUSH13,
/// The eponymous EVM instruction.
PUSH14,
/// The eponymous EVM instruction.
PUSH15,
/// The eponymous EVM instruction.
PUSH16,
/// The eponymous EVM instruction.
PUSH17,
/// The eponymous EVM instruction.
PUSH18,
/// The eponymous EVM instruction.
PUSH19,
/// The eponymous EVM instruction.
PUSH20,
/// The eponymous EVM instruction.
PUSH21,
/// The eponymous EVM instruction.
PUSH22,
/// The eponymous EVM instruction.
PUSH23,
/// The eponymous EVM instruction.
PUSH24,
/// The eponymous EVM instruction.
PUSH25,
/// The eponymous EVM instruction.
PUSH26,
/// The eponymous EVM instruction.
PUSH27,
/// The eponymous EVM instruction.
PUSH28,
/// The eponymous EVM instruction.
PUSH29,
/// The eponymous EVM instruction.
PUSH30,
/// The eponymous EVM instruction.
PUSH31,
/// The eponymous EVM instruction.
PUSH32,
/// The eponymous EVM instruction.
DUP1,
/// The eponymous EVM instruction.
DUP2,
/// The eponymous EVM instruction.
DUP3,
/// The eponymous EVM instruction.
DUP4,
/// The eponymous EVM instruction.
DUP5,
/// The eponymous EVM instruction.
DUP6,
/// The eponymous EVM instruction.
DUP7,
/// The eponymous EVM instruction.
DUP8,
/// The eponymous EVM instruction.
DUP9,
/// The eponymous EVM instruction.
DUP10,
/// The eponymous EVM instruction.
DUP11,
/// The eponymous EVM instruction.
DUP12,
/// The eponymous EVM instruction.
DUP13,
/// The eponymous EVM instruction.
DUP14,
/// The eponymous EVM instruction.
DUP15,
/// The eponymous EVM instruction.
DUP16,
/// The eponymous EVM instruction.
SWAP1,
/// The eponymous EVM instruction.
SWAP2,
/// The eponymous EVM instruction.
SWAP3,
/// The eponymous EVM instruction.
SWAP4,
/// The eponymous EVM instruction.
SWAP5,
/// The eponymous EVM instruction.
SWAP6,
/// The eponymous EVM instruction.
SWAP7,
/// The eponymous EVM instruction.
SWAP8,
/// The eponymous EVM instruction.
SWAP9,
/// The eponymous EVM instruction.
SWAP10,
/// The eponymous EVM instruction.
SWAP11,
/// The eponymous EVM instruction.
SWAP12,
/// The eponymous EVM instruction.
SWAP13,
/// The eponymous EVM instruction.
SWAP14,
/// The eponymous EVM instruction.
SWAP15,
/// The eponymous EVM instruction.
SWAP16,
/// The eponymous EVM instruction.
POP,
/// Sets the current basic code block.
#[serde(rename = "tag")]
Tag,
/// The eponymous EVM instruction.
JUMP,
/// The eponymous EVM instruction.
JUMPI,
/// The eponymous EVM instruction.
JUMPDEST,
/// The eponymous EVM instruction.
ADD,
/// The eponymous EVM instruction.
SUB,
/// The eponymous EVM instruction.
MUL,
/// The eponymous EVM instruction.
DIV,
/// The eponymous EVM instruction.
MOD,
/// The eponymous EVM instruction.
SDIV,
/// The eponymous EVM instruction.
SMOD,
/// The eponymous EVM instruction.
LT,
/// The eponymous EVM instruction.
GT,
/// The eponymous EVM instruction.
EQ,
/// The eponymous EVM instruction.
ISZERO,
/// The eponymous EVM instruction.
SLT,
/// The eponymous EVM instruction.
SGT,
/// The eponymous EVM instruction.
OR,
/// The eponymous EVM instruction.
XOR,
/// The eponymous EVM instruction.
NOT,
/// The eponymous EVM instruction.
AND,
/// The eponymous EVM instruction.
SHL,
/// The eponymous EVM instruction.
SHR,
/// The eponymous EVM instruction.
SAR,
/// The eponymous EVM instruction.
BYTE,
/// The eponymous EVM instruction.
ADDMOD,
/// The eponymous EVM instruction.
MULMOD,
/// The eponymous EVM instruction.
EXP,
/// The eponymous EVM instruction.
SIGNEXTEND,
/// The eponymous EVM instruction.
SHA3,
/// The eponymous EVM instruction.
KECCAK256,
/// The eponymous EVM instruction.
MLOAD,
/// The eponymous EVM instruction.
MSTORE,
/// The eponymous EVM instruction.
MSTORE8,
/// The eponymous EVM instruction.
MCOPY,
/// The eponymous EVM instruction.
SLOAD,
/// The eponymous EVM instruction.
SSTORE,
/// The eponymous EVM instruction.
TLOAD,
/// The eponymous EVM instruction.
TSTORE,
/// The eponymous EVM instruction.
PUSHIMMUTABLE,
/// The eponymous EVM instruction.
ASSIGNIMMUTABLE,
/// The eponymous EVM instruction.
CALLDATALOAD,
/// The eponymous EVM instruction.
CALLDATASIZE,
/// The eponymous EVM instruction.
CALLDATACOPY,
/// The eponymous EVM instruction.
CODESIZE,
/// The eponymous EVM instruction.
CODECOPY,
/// The eponymous EVM instruction.
PUSHSIZE,
/// The eponymous EVM instruction.
EXTCODESIZE,
/// The eponymous EVM instruction.
EXTCODEHASH,
/// The eponymous EVM instruction.
RETURNDATASIZE,
/// The eponymous EVM instruction.
RETURNDATACOPY,
/// The eponymous EVM instruction.
RETURN,
/// The eponymous EVM instruction.
REVERT,
/// The eponymous EVM instruction.
STOP,
/// The eponymous EVM instruction.
INVALID,
/// The eponymous EVM instruction.
LOG0,
/// The eponymous EVM instruction.
LOG1,
/// The eponymous EVM instruction.
LOG2,
/// The eponymous EVM instruction.
LOG3,
/// The eponymous EVM instruction.
LOG4,
/// The eponymous EVM instruction.
CALL,
/// The eponymous EVM instruction.
STATICCALL,
/// The eponymous EVM instruction.
DELEGATECALL,
/// The eponymous EVM instruction.
CREATE,
/// The eponymous EVM instruction.
CREATE2,
/// The eponymous PolkaVM instruction.
#[serde(rename = "$ZK_CREATE")]
ZK_CREATE,
/// The eponymous PolkaVM instruction.
#[serde(rename = "$ZK_CREATE2")]
ZK_CREATE2,
/// The eponymous EVM instruction.
ADDRESS,
/// The eponymous EVM instruction.
CALLER,
/// The eponymous EVM instruction.
CALLVALUE,
/// The eponymous EVM instruction.
GAS,
/// The eponymous EVM instruction.
BALANCE,
/// The eponymous EVM instruction.
SELFBALANCE,
/// The eponymous EVM instruction.
PUSHLIB,
/// The eponymous EVM instruction.
PUSHDEPLOYADDRESS,
/// The eponymous EVM instruction.
GASLIMIT,
/// The eponymous EVM instruction.
GASPRICE,
/// The eponymous EVM instruction.
ORIGIN,
/// The eponymous EVM instruction.
CHAINID,
/// The eponymous EVM instruction.
TIMESTAMP,
/// The eponymous EVM instruction.
NUMBER,
/// The eponymous EVM instruction.
BLOCKHASH,
/// The eponymous EVM instruction.
BLOBHASH,
/// The eponymous EVM instruction.
DIFFICULTY,
/// The eponymous EVM instruction.
PREVRANDAO,
/// The eponymous EVM instruction.
COINBASE,
/// The eponymous EVM instruction.
BASEFEE,
/// The eponymous EVM instruction.
BLOBBASEFEE,
/// The eponymous EVM instruction.
MSIZE,
/// The eponymous EVM instruction.
CALLCODE,
/// The eponymous EVM instruction.
PC,
/// The eponymous EVM instruction.
EXTCODECOPY,
/// The eponymous EVM instruction.
SELFDESTRUCT,
/// The recursive function call instruction.
#[serde(skip)]
RecursiveCall {
/// The called function name.
name: String,
/// The called function key.
entry_key: revive_llvm_context::PolkaVMFunctionBlockKey,
/// The stack state hash after return.
stack_hash: md5::Digest,
/// The input size.
input_size: usize,
/// The output size.
output_size: usize,
/// The return address.
return_address: revive_llvm_context::PolkaVMFunctionBlockKey,
},
/// The recursive function return instruction.
#[serde(skip)]
RecursiveReturn {
/// The output size.
input_size: usize,
},
}
impl std::fmt::Display for Name {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
match self {
Self::Tag => write!(f, "Tag"),
Self::RecursiveCall {
name,
entry_key,
input_size,
output_size,
return_address,
..
} => write!(
f,
"RECURSIVE_CALL({}_{}, {}, {}, {})",
name, entry_key, input_size, output_size, return_address
),
Self::RecursiveReturn { input_size } => write!(f, "RECURSIVE_RETURN({})", input_size),
_ => write!(
f,
"{}",
serde_json::to_string(self)
.expect("Always valid")
.trim_matches('\"')
),
}
}
}
@@ -1,106 +0,0 @@
//! Translates the stack memory operations.
use inkwell::values::BasicValue;
/// Translates the ordinar value push.
pub fn push<'ctx, D>(
context: &mut revive_llvm_context::PolkaVMContext<'ctx, D>,
value: String,
) -> anyhow::Result<inkwell::values::BasicValueEnum<'ctx>>
where
D: revive_llvm_context::PolkaVMDependency + Clone,
{
let result = context
.word_type()
.const_int_from_string(
value.to_ascii_uppercase().as_str(),
inkwell::types::StringRadix::Hexadecimal,
)
.expect("Always valid")
.as_basic_value_enum();
Ok(result)
}
/// Translates the block tag label push.
pub fn push_tag<'ctx, D>(
context: &mut revive_llvm_context::PolkaVMContext<'ctx, D>,
value: String,
) -> anyhow::Result<inkwell::values::BasicValueEnum<'ctx>>
where
D: revive_llvm_context::PolkaVMDependency + Clone,
{
let result = context
.word_type()
.const_int_from_string(value.as_str(), inkwell::types::StringRadix::Decimal)
.expect("Always valid");
Ok(result.as_basic_value_enum())
}
/// Translates the stack memory duplicate.
pub fn dup<'ctx, D>(
context: &mut revive_llvm_context::PolkaVMContext<'ctx, D>,
offset: usize,
height: usize,
original: &mut Option<String>,
) -> anyhow::Result<inkwell::values::BasicValueEnum<'ctx>>
where
D: revive_llvm_context::PolkaVMDependency + Clone,
{
let element = &context.evmla().stack[height - offset - 1];
let value = context.build_load(
revive_llvm_context::PolkaVMPointer::new_stack_field(
context,
element.to_llvm().into_pointer_value(),
),
format!("dup{offset}").as_str(),
)?;
element.original.clone_into(original);
Ok(value)
}
/// Translates the stack memory swap.
pub fn swap<D>(
context: &mut revive_llvm_context::PolkaVMContext<D>,
offset: usize,
height: usize,
) -> anyhow::Result<()>
where
D: revive_llvm_context::PolkaVMDependency + Clone,
{
let top_element = context.evmla().stack[height - 1].to_owned();
let top_pointer = revive_llvm_context::PolkaVMPointer::new_stack_field(
context,
top_element.to_llvm().into_pointer_value(),
);
let top_value = context.build_load(top_pointer, format!("swap{offset}_top_value").as_str())?;
let swap_element = context.evmla().stack[height - offset - 1].to_owned();
let swap_pointer = revive_llvm_context::PolkaVMPointer::new_stack_field(
context,
swap_element.to_llvm().into_pointer_value(),
);
let swap_value =
context.build_load(swap_pointer, format!("swap{offset}_swap_value").as_str())?;
swap_element
.original
.clone_into(&mut context.evmla_mut().stack[height - 1].original);
top_element
.original
.clone_into(&mut context.evmla_mut().stack[height - offset - 1].original);
context.build_store(top_pointer, swap_value)?;
context.build_store(swap_pointer, top_value)?;
Ok(())
}
/// Translates the stack memory pop.
pub fn pop<D>(_context: &mut revive_llvm_context::PolkaVMContext<D>) -> anyhow::Result<()>
where
D: revive_llvm_context::PolkaVMDependency + Clone,
{
Ok(())
}
-295
View File
@@ -1,295 +0,0 @@
//! The `solc --asm-json` output.
pub mod data;
pub mod instruction;
use std::collections::BTreeMap;
use std::collections::HashSet;
use serde::Deserialize;
use serde::Serialize;
use sha3::Digest;
use crate::evmla::ethereal_ir::entry_link::EntryLink;
use crate::evmla::ethereal_ir::EtherealIR;
use crate::solc::standard_json::output::contract::evm::extra_metadata::ExtraMetadata;
use self::data::Data;
use self::instruction::name::Name as InstructionName;
use self::instruction::Instruction;
/// The JSON assembly.
#[derive(Debug, Serialize, Deserialize, Clone)]
pub struct Assembly {
/// The metadata string.
#[serde(rename = ".auxdata")]
pub auxdata: Option<String>,
/// The deploy code instructions.
#[serde(rename = ".code")]
pub code: Option<Vec<Instruction>>,
/// The runtime code.
#[serde(rename = ".data")]
pub data: Option<BTreeMap<String, Data>>,
/// The full contract path.
#[serde(skip_serializing_if = "Option::is_none")]
pub full_path: Option<String>,
/// The factory dependency paths.
#[serde(default = "HashSet::new")]
pub factory_dependencies: HashSet<String>,
/// The EVMLA extra metadata.
#[serde(skip_serializing_if = "Option::is_none")]
pub extra_metadata: Option<ExtraMetadata>,
}
impl Assembly {
/// Gets the contract `keccak256` hash.
pub fn keccak256(&self) -> String {
let json = serde_json::to_vec(self).expect("Always valid");
hex::encode(sha3::Keccak256::digest(json.as_slice()))
}
/// Sets the full contract path.
pub fn set_full_path(&mut self, full_path: String) {
self.full_path = Some(full_path);
}
/// Returns the full contract path if it is set, or `<undefined>` otherwise.
/// # Panics
/// If the `full_path` has not been set.
pub fn full_path(&self) -> &str {
self.full_path
.as_deref()
.unwrap_or_else(|| panic!("The full path of some contracts is unset"))
}
/// Get the list of missing deployable libraries.
pub fn get_missing_libraries(&self) -> HashSet<String> {
let mut missing_libraries = HashSet::new();
if let Some(code) = self.code.as_ref() {
for instruction in code.iter() {
if let InstructionName::PUSHLIB = instruction.name {
let library_path = instruction.value.to_owned().expect("Always exists");
missing_libraries.insert(library_path);
}
}
}
if let Some(data) = self.data.as_ref() {
for (_, data) in data.iter() {
missing_libraries.extend(data.get_missing_libraries());
}
}
missing_libraries
}
/// Replaces the deploy code dependencies with full contract path and returns the list.
pub fn deploy_dependencies_pass(
&mut self,
full_path: &str,
hash_data_mapping: &BTreeMap<String, String>,
) -> anyhow::Result<BTreeMap<String, String>> {
let mut index_path_mapping = BTreeMap::new();
let index = "0".repeat(revive_common::BYTE_LENGTH_WORD * 2);
index_path_mapping.insert(index, full_path.to_owned());
let dependencies = match self.data.as_mut() {
Some(dependencies) => dependencies,
None => return Ok(index_path_mapping),
};
for (index, data) in dependencies.iter_mut() {
if index == "0" {
continue;
}
let mut index_extended = "0".repeat(revive_common::BYTE_LENGTH_WORD * 2 - index.len());
index_extended.push_str(index.as_str());
*data = match data {
Data::Assembly(assembly) => {
let hash = assembly.keccak256();
let full_path =
hash_data_mapping
.get(hash.as_str())
.cloned()
.ok_or_else(|| {
anyhow::anyhow!("Contract path not found for hash `{}`", hash)
})?;
self.factory_dependencies.insert(full_path.to_owned());
index_path_mapping.insert(index_extended, full_path.clone());
Data::Path(full_path)
}
Data::Hash(hash) => {
index_path_mapping.insert(index_extended, hash.to_owned());
continue;
}
_ => continue,
};
}
Ok(index_path_mapping)
}
/// Replaces the runtime code dependencies with full contract path and returns the list.
pub fn runtime_dependencies_pass(
&mut self,
full_path: &str,
hash_data_mapping: &BTreeMap<String, String>,
) -> anyhow::Result<BTreeMap<String, String>> {
let mut index_path_mapping = BTreeMap::new();
let index = "0".repeat(revive_common::BYTE_LENGTH_WORD * 2);
index_path_mapping.insert(index, full_path.to_owned());
let dependencies = match self
.data
.as_mut()
.and_then(|data| data.get_mut("0"))
.and_then(|data| data.get_assembly_mut())
.and_then(|assembly| assembly.data.as_mut())
{
Some(dependencies) => dependencies,
None => return Ok(index_path_mapping),
};
for (index, data) in dependencies.iter_mut() {
let mut index_extended = "0".repeat(revive_common::BYTE_LENGTH_WORD * 2 - index.len());
index_extended.push_str(index.as_str());
*data = match data {
Data::Assembly(assembly) => {
let hash = assembly.keccak256();
let full_path =
hash_data_mapping
.get(hash.as_str())
.cloned()
.ok_or_else(|| {
anyhow::anyhow!("Contract path not found for hash `{}`", hash)
})?;
self.factory_dependencies.insert(full_path.to_owned());
index_path_mapping.insert(index_extended, full_path.clone());
Data::Path(full_path)
}
Data::Hash(hash) => {
index_path_mapping.insert(index_extended, hash.to_owned());
continue;
}
_ => continue,
};
}
Ok(index_path_mapping)
}
}
impl<D> revive_llvm_context::PolkaVMWriteLLVM<D> for Assembly
where
D: revive_llvm_context::PolkaVMDependency + Clone,
{
fn declare(
&mut self,
context: &mut revive_llvm_context::PolkaVMContext<D>,
) -> anyhow::Result<()> {
let mut entry = revive_llvm_context::PolkaVMEntryFunction::default();
entry.declare(context)?;
revive_llvm_context::PolkaVMDeployCodeFunction::new(
revive_llvm_context::PolkaVMDummyLLVMWritable::default(),
)
.declare(context)?;
revive_llvm_context::PolkaVMRuntimeCodeFunction::new(
revive_llvm_context::PolkaVMDummyLLVMWritable::default(),
)
.declare(context)?;
revive_llvm_context::PolkaVMImmutableDataLoadFunction.declare(context)?;
entry.into_llvm(context)?;
Ok(())
}
fn into_llvm(
mut self,
context: &mut revive_llvm_context::PolkaVMContext<D>,
) -> anyhow::Result<()> {
let full_path = self.full_path().to_owned();
context
.debug_config()
.dump_evmla(full_path.as_str(), self.to_string().as_str())?;
let deploy_code_blocks = EtherealIR::get_blocks(
context.evmla().version.to_owned(),
revive_llvm_context::PolkaVMCodeType::Deploy,
self.code
.as_deref()
.ok_or_else(|| anyhow::anyhow!("Deploy code instructions not found"))?,
)?;
let data = self
.data
.ok_or_else(|| anyhow::anyhow!("Runtime code data not found"))?
.remove("0")
.expect("Always exists");
context
.debug_config()
.dump_evmla(full_path.as_str(), data.to_string().as_str())?;
let runtime_code_instructions = match data {
Data::Assembly(assembly) => assembly
.code
.ok_or_else(|| anyhow::anyhow!("Runtime code instructions not found"))?,
Data::Hash(hash) => {
anyhow::bail!("Expected runtime code instructions, found hash `{}`", hash)
}
Data::Path(path) => {
anyhow::bail!("Expected runtime code instructions, found path `{}`", path)
}
};
let runtime_code_blocks = EtherealIR::get_blocks(
context.evmla().version.to_owned(),
revive_llvm_context::PolkaVMCodeType::Runtime,
runtime_code_instructions.as_slice(),
)?;
let extra_metadata = self.extra_metadata.take().unwrap_or_default();
let mut blocks = deploy_code_blocks;
blocks.extend(runtime_code_blocks);
let mut ethereal_ir =
EtherealIR::new(context.evmla().version.to_owned(), extra_metadata, blocks)?;
context
.debug_config()
.dump_ethir(full_path.as_str(), ethereal_ir.to_string().as_str())?;
ethereal_ir.declare(context)?;
ethereal_ir.into_llvm(context)?;
revive_llvm_context::PolkaVMDeployCodeFunction::new(EntryLink::new(
revive_llvm_context::PolkaVMCodeType::Deploy,
))
.into_llvm(context)?;
revive_llvm_context::PolkaVMRuntimeCodeFunction::new(EntryLink::new(
revive_llvm_context::PolkaVMCodeType::Runtime,
))
.into_llvm(context)?;
revive_llvm_context::PolkaVMImmutableDataLoadFunction.into_llvm(context)?;
Ok(())
}
}
impl std::fmt::Display for Assembly {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
if let Some(instructions) = self.code.as_ref() {
for (index, instruction) in instructions.iter().enumerate() {
match instruction.name {
InstructionName::Tag => writeln!(f, "{index:03} {instruction}")?,
_ => writeln!(f, "{index:03} {instruction}")?,
}
}
}
Ok(())
}
}
@@ -1,48 +0,0 @@
//! The Ethereal IR entry function link.
use inkwell::values::BasicValue;
use crate::evmla::ethereal_ir::EtherealIR;
/// The Ethereal IR entry function link.
/// The link represents branching between the deploy and runtime code.
#[derive(Debug, Clone)]
pub struct EntryLink {
/// The code part type.
pub code_type: revive_llvm_context::PolkaVMCodeType,
}
impl EntryLink {
/// A shortcut constructor.
pub fn new(code_type: revive_llvm_context::PolkaVMCodeType) -> Self {
Self { code_type }
}
}
impl<D> revive_llvm_context::PolkaVMWriteLLVM<D> for EntryLink
where
D: revive_llvm_context::PolkaVMDependency + Clone,
{
fn into_llvm(self, context: &mut revive_llvm_context::PolkaVMContext<D>) -> anyhow::Result<()> {
let target = context
.get_function(EtherealIR::DEFAULT_ENTRY_FUNCTION_NAME)
.expect("Always exists")
.borrow()
.declaration();
let is_deploy_code = match self.code_type {
revive_llvm_context::PolkaVMCodeType::Deploy => context
.integer_type(revive_common::BIT_LENGTH_BOOLEAN)
.const_int(1, false),
revive_llvm_context::PolkaVMCodeType::Runtime => context
.integer_type(revive_common::BIT_LENGTH_BOOLEAN)
.const_int(0, false),
};
context.build_call(
target,
&[is_deploy_code.as_basic_value_enum()],
format!("call_link_{}", EtherealIR::DEFAULT_ENTRY_FUNCTION_NAME).as_str(),
);
Ok(())
}
}
File diff suppressed because it is too large Load Diff
@@ -1,37 +0,0 @@
//! The Ethereal IR block element stack element.
/// The Ethereal IR block element stack element.
#[derive(Debug, Clone, PartialEq, Eq)]
pub enum Element {
/// The runtime value.
Value(String),
/// The compile-time value.
Constant(num::BigUint),
/// The compile-time destination tag.
Tag(num::BigUint),
/// The compile-time path.
Path(String),
/// The compile-time hexadecimal data chunk.
Data(String),
/// The recursive function return address.
ReturnAddress(usize),
}
impl Element {
pub fn value(identifier: String) -> Self {
Self::Value(identifier)
}
}
impl std::fmt::Display for Element {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
match self {
Self::Value(identifier) => write!(f, "V_{identifier}"),
Self::Constant(value) => write!(f, "{value:X}"),
Self::Tag(tag) => write!(f, "T_{tag}"),
Self::Path(path) => write!(f, "{path}"),
Self::Data(data) => write!(f, "{data}"),
Self::ReturnAddress(_) => write!(f, "RETURN_ADDRESS"),
}
}
}
@@ -1,120 +0,0 @@
//! The Ethereal IR block element stack.
pub mod element;
use self::element::Element;
/// The Ethereal IR block element stack.
#[derive(Debug, Default, Clone)]
pub struct Stack {
/// The stack elements.
pub elements: Vec<Element>,
}
impl Stack {
/// The default stack size.
pub const DEFAULT_STACK_SIZE: usize = 16;
/// A shortcut constructor.
pub fn new() -> Self {
Self {
elements: Vec::with_capacity(Self::DEFAULT_STACK_SIZE),
}
}
/// A shortcut constructor.
pub fn with_capacity(capacity: usize) -> Self {
Self {
elements: Vec::with_capacity(capacity),
}
}
/// A shortcut constructor.
pub fn new_with_elements(elements: Vec<Element>) -> Self {
Self { elements }
}
/// The stack state hash, which acts as a block identifier.
/// Each block clone has its own initial stack state, which uniquely identifies the block.
pub fn hash(&self) -> md5::Digest {
let mut hash_context = md5::Context::new();
for element in self.elements.iter() {
match element {
Element::Tag(tag) => hash_context.consume(tag.to_bytes_be()),
_ => hash_context.consume([0]),
}
}
hash_context.compute()
}
/// Pushes an element onto the stack.
pub fn push(&mut self, element: Element) {
self.elements.push(element);
}
/// Appends another stack on top of this one.
pub fn append(&mut self, other: &mut Self) {
self.elements.append(&mut other.elements);
}
/// Pops a stack element.
pub fn pop(&mut self) -> anyhow::Result<Element> {
self.elements
.pop()
.ok_or_else(|| anyhow::anyhow!("Stack underflow"))
}
/// Pops the tag from the top.
pub fn pop_tag(&mut self) -> anyhow::Result<num::BigUint> {
match self.elements.pop() {
Some(Element::Tag(tag)) => Ok(tag),
Some(element) => anyhow::bail!("Expected tag, found {}", element),
None => anyhow::bail!("Stack underflow"),
}
}
/// Swaps two stack elements.
pub fn swap(&mut self, index: usize) -> anyhow::Result<()> {
if self.elements.len() < index + 1 {
anyhow::bail!("Stack underflow");
}
let length = self.elements.len();
self.elements.swap(length - 1, length - 1 - index);
Ok(())
}
/// Duplicates a stack element.
pub fn dup(&mut self, index: usize) -> anyhow::Result<Element> {
if self.elements.len() < index {
anyhow::bail!("Stack underflow");
}
Ok(self.elements[self.elements.len() - index].to_owned())
}
/// Returns the stack length.
pub fn len(&self) -> usize {
self.elements.len()
}
/// Returns an emptiness flag.
pub fn is_empty(&self) -> bool {
self.elements.len() == 0
}
}
impl std::fmt::Display for Stack {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
write!(
f,
"[ {} ]",
self.elements
.iter()
.map(Element::to_string)
.collect::<Vec<String>>()
.join(" | ")
)
}
}
@@ -1,156 +0,0 @@
//! The Ethereal IR block.
pub mod element;
use std::collections::HashSet;
use num::Zero;
use crate::evmla::assembly::instruction::name::Name as InstructionName;
use crate::evmla::assembly::instruction::Instruction;
use self::element::stack::Stack as ElementStack;
use self::element::Element;
/// The Ethereal IR block.
#[derive(Debug, Clone)]
pub struct Block {
/// The Solidity compiler version.
pub solc_version: semver::Version,
/// The block key.
pub key: revive_llvm_context::PolkaVMFunctionBlockKey,
/// The block instance.
pub instance: Option<usize>,
/// The block elements relevant to the stack consistency.
pub elements: Vec<Element>,
/// The block predecessors.
pub predecessors: HashSet<(revive_llvm_context::PolkaVMFunctionBlockKey, usize)>,
/// The initial stack state.
pub initial_stack: ElementStack,
/// The stack.
pub stack: ElementStack,
/// The extra block hashes for alternative routes.
pub extra_hashes: Vec<md5::Digest>,
}
impl Block {
/// The elements vector initial capacity.
pub const ELEMENTS_VECTOR_DEFAULT_CAPACITY: usize = 64;
/// The predecessors hashset initial capacity.
pub const PREDECESSORS_HASHSET_DEFAULT_CAPACITY: usize = 4;
/// Assembles a block from the sequence of instructions.
pub fn try_from_instructions(
solc_version: semver::Version,
code_type: revive_llvm_context::PolkaVMCodeType,
slice: &[Instruction],
) -> anyhow::Result<(Self, usize)> {
let mut cursor = 0;
let tag: num::BigUint = match slice[cursor].name {
InstructionName::Tag => {
let tag = slice[cursor]
.value
.as_deref()
.expect("Always exists")
.parse()
.expect("Always valid");
cursor += 1;
tag
}
_ => num::BigUint::zero(),
};
let mut block = Self {
solc_version: solc_version.clone(),
key: revive_llvm_context::PolkaVMFunctionBlockKey::new(code_type, tag),
instance: None,
elements: Vec::with_capacity(Self::ELEMENTS_VECTOR_DEFAULT_CAPACITY),
predecessors: HashSet::with_capacity(Self::PREDECESSORS_HASHSET_DEFAULT_CAPACITY),
initial_stack: ElementStack::new(),
stack: ElementStack::new(),
extra_hashes: vec![],
};
let mut dead_code = false;
while cursor < slice.len() {
if !dead_code {
let element: Element = Element::new(solc_version.clone(), slice[cursor].to_owned());
block.elements.push(element);
}
match slice[cursor].name {
InstructionName::RETURN
| InstructionName::REVERT
| InstructionName::STOP
| InstructionName::INVALID => {
cursor += 1;
dead_code = true;
}
InstructionName::JUMP => {
cursor += 1;
dead_code = true;
}
InstructionName::Tag => {
break;
}
_ => {
cursor += 1;
}
}
}
Ok((block, cursor))
}
/// Inserts a predecessor tag.
pub fn insert_predecessor(
&mut self,
key: revive_llvm_context::PolkaVMFunctionBlockKey,
instance: usize,
) {
self.predecessors.insert((key, instance));
}
}
impl<D> revive_llvm_context::PolkaVMWriteLLVM<D> for Block
where
D: revive_llvm_context::PolkaVMDependency + Clone,
{
fn into_llvm(self, context: &mut revive_llvm_context::PolkaVMContext<D>) -> anyhow::Result<()> {
context.set_code_type(self.key.code_type);
for element in self.elements.into_iter() {
element.into_llvm(context)?;
}
Ok(())
}
}
impl std::fmt::Display for Block {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
writeln!(
f,
"block_{}/{}: {}",
self.key,
self.instance.unwrap_or_default(),
if self.predecessors.is_empty() {
"".to_owned()
} else {
format!(
"(predecessors: {})",
self.predecessors
.iter()
.map(|(key, instance)| format!("{}/{}", key, instance))
.collect::<Vec<String>>()
.join(", ")
)
},
)?;
for element in self.elements.iter() {
writeln!(f, " {element}")?;
}
Ok(())
}
}
File diff suppressed because it is too large Load Diff
@@ -1,29 +0,0 @@
//! The Ethereal IR block queue element.
use crate::evmla::ethereal_ir::function::block::element::stack::Stack;
/// The Ethereal IR block queue element.
#[derive(Debug, Clone)]
pub struct QueueElement {
/// The block key.
pub block_key: revive_llvm_context::PolkaVMFunctionBlockKey,
/// The block predecessor.
pub predecessor: Option<(revive_llvm_context::PolkaVMFunctionBlockKey, usize)>,
/// The predecessor's last stack state.
pub stack: Stack,
}
impl QueueElement {
/// A shortcut constructor.
pub fn new(
block_key: revive_llvm_context::PolkaVMFunctionBlockKey,
predecessor: Option<(revive_llvm_context::PolkaVMFunctionBlockKey, usize)>,
stack: Stack,
) -> Self {
Self {
block_key,
predecessor,
stack,
}
}
}
@@ -1,41 +0,0 @@
//! The Ethereal IR function type.
/// The Ethereal IR function type.
#[derive(Debug, Clone)]
pub enum Type {
/// The initial function, combining deploy and runtime code.
Initial,
/// The recursive function with a specific block starting its recursive context.
Recursive {
/// The function name.
name: String,
/// The function initial block key.
block_key: revive_llvm_context::PolkaVMFunctionBlockKey,
/// The size of stack input (in cells or 256-bit words).
input_size: usize,
/// The size of stack output (in cells or 256-bit words).
output_size: usize,
},
}
impl Type {
/// A shortcut constructor.
pub fn new_initial() -> Self {
Self::Initial
}
/// A shortcut constructor.
pub fn new_recursive(
name: String,
block_key: revive_llvm_context::PolkaVMFunctionBlockKey,
input_size: usize,
output_size: usize,
) -> Self {
Self::Recursive {
name,
block_key,
input_size,
output_size,
}
}
}
@@ -1,65 +0,0 @@
//! The Ethereal IR block visited element.
use std::cmp::Ordering;
/// The Ethereal IR block visited element.
#[derive(Debug, Clone, PartialEq, Eq, Hash)]
pub struct VisitedElement {
/// The block key.
pub block_key: revive_llvm_context::PolkaVMFunctionBlockKey,
/// The initial stack state hash.
pub stack_hash: md5::Digest,
}
impl VisitedElement {
/// A shortcut constructor.
pub fn new(
block_key: revive_llvm_context::PolkaVMFunctionBlockKey,
stack_hash: md5::Digest,
) -> Self {
Self {
block_key,
stack_hash,
}
}
}
impl PartialOrd for VisitedElement {
fn partial_cmp(&self, other: &Self) -> Option<Ordering> {
Some(self.cmp(other))
}
}
impl Ord for VisitedElement {
fn cmp(&self, other: &Self) -> Ordering {
match (self.block_key.code_type, other.block_key.code_type) {
(
revive_llvm_context::PolkaVMCodeType::Deploy,
revive_llvm_context::PolkaVMCodeType::Runtime,
) => Ordering::Less,
(
revive_llvm_context::PolkaVMCodeType::Runtime,
revive_llvm_context::PolkaVMCodeType::Deploy,
) => Ordering::Greater,
(
revive_llvm_context::PolkaVMCodeType::Deploy,
revive_llvm_context::PolkaVMCodeType::Deploy,
)
| (
revive_llvm_context::PolkaVMCodeType::Runtime,
revive_llvm_context::PolkaVMCodeType::Runtime,
) => {
let tag_comparison = self.block_key.tag.cmp(&other.block_key.tag);
if tag_comparison == Ordering::Equal {
if self.stack_hash == other.stack_hash {
Ordering::Equal
} else {
Ordering::Less
}
} else {
tag_comparison
}
}
}
}
}
@@ -1,134 +0,0 @@
//! The Ethereal IR of the EVM bytecode.
pub mod entry_link;
pub mod function;
use std::collections::BTreeMap;
use std::collections::BTreeSet;
use std::collections::HashMap;
use crate::evmla::assembly::instruction::Instruction;
use crate::solc::standard_json::output::contract::evm::extra_metadata::ExtraMetadata;
use self::function::block::Block;
use self::function::r#type::Type as FunctionType;
use self::function::Function;
/// The Ethereal IR of the EVM bytecode.
/// The Ethereal IR (EthIR) is a special IR between the EVM legacy assembly and LLVM IR. It is
/// created to facilitate the translation and provide an additional environment for applying some
/// transformations, duplicating parts of the call and control flow graphs, tracking the
/// data flow, and a few more algorithms of static analysis.
/// The most important feature of EthIR is flattening the block tags and duplicating blocks for
/// each of initial states of the stack. The LLVM IR supports only static control flow, so the
/// stack state must be known all the way throughout the program.
#[derive(Debug)]
pub struct EtherealIR {
/// The Solidity compiler version.
pub solc_version: semver::Version,
/// The EVMLA extra metadata.
pub extra_metadata: ExtraMetadata,
/// The all-inlined function.
pub entry_function: Function,
/// The recursive functions.
pub recursive_functions: BTreeMap<revive_llvm_context::PolkaVMFunctionBlockKey, Function>,
}
impl EtherealIR {
/// The default entry function name.
pub const DEFAULT_ENTRY_FUNCTION_NAME: &'static str = "main";
/// The blocks hashmap initial capacity.
pub const BLOCKS_HASHMAP_DEFAULT_CAPACITY: usize = 64;
/// Assembles a sequence of functions from the sequence of instructions.
pub fn new(
solc_version: semver::Version,
extra_metadata: ExtraMetadata,
blocks: HashMap<revive_llvm_context::PolkaVMFunctionBlockKey, Block>,
) -> anyhow::Result<Self> {
let mut entry_function = Function::new(solc_version.clone(), FunctionType::new_initial());
let mut recursive_functions = BTreeMap::new();
let mut visited_functions = BTreeSet::new();
entry_function.traverse(
&blocks,
&mut recursive_functions,
&extra_metadata,
&mut visited_functions,
)?;
Ok(Self {
solc_version,
extra_metadata,
entry_function,
recursive_functions,
})
}
/// Gets blocks for the specified type of the contract code.
pub fn get_blocks(
solc_version: semver::Version,
code_type: revive_llvm_context::PolkaVMCodeType,
instructions: &[Instruction],
) -> anyhow::Result<HashMap<revive_llvm_context::PolkaVMFunctionBlockKey, Block>> {
let mut blocks = HashMap::with_capacity(Self::BLOCKS_HASHMAP_DEFAULT_CAPACITY);
let mut offset = 0;
while offset < instructions.len() {
let (block, size) = Block::try_from_instructions(
solc_version.clone(),
code_type,
&instructions[offset..],
)?;
blocks.insert(
revive_llvm_context::PolkaVMFunctionBlockKey::new(code_type, block.key.tag.clone()),
block,
);
offset += size;
}
Ok(blocks)
}
}
impl<D> revive_llvm_context::PolkaVMWriteLLVM<D> for EtherealIR
where
D: revive_llvm_context::PolkaVMDependency + Clone,
{
fn declare(
&mut self,
context: &mut revive_llvm_context::PolkaVMContext<D>,
) -> anyhow::Result<()> {
self.entry_function.declare(context)?;
for (_key, function) in self.recursive_functions.iter_mut() {
function.declare(context)?;
}
Ok(())
}
fn into_llvm(self, context: &mut revive_llvm_context::PolkaVMContext<D>) -> anyhow::Result<()> {
context.evmla_mut().stack = vec![];
self.entry_function.into_llvm(context)?;
for (_key, function) in self.recursive_functions.into_iter() {
function.into_llvm(context)?;
}
Ok(())
}
}
impl std::fmt::Display for EtherealIR {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
writeln!(f, "{}", self.entry_function)?;
for (_key, function) in self.recursive_functions.iter() {
writeln!(f, "{}", function)?;
}
Ok(())
}
}
-4
View File
@@ -1,4 +0,0 @@
//! The EVM legacy assembly compiling tools.
pub mod assembly;
pub mod ethereal_ir;
+13 -43
View File
@@ -2,7 +2,6 @@
pub(crate) mod build; pub(crate) mod build;
pub(crate) mod r#const; pub(crate) mod r#const;
pub(crate) mod evmla;
pub(crate) mod missing_libraries; pub(crate) mod missing_libraries;
pub(crate) mod process; pub(crate) mod process;
pub(crate) mod project; pub(crate) mod project;
@@ -26,7 +25,6 @@ pub use self::project::Project;
pub use self::r#const::*; pub use self::r#const::*;
pub use self::solc::combined_json::contract::Contract as SolcCombinedJsonContract; pub use self::solc::combined_json::contract::Contract as SolcCombinedJsonContract;
pub use self::solc::combined_json::CombinedJson as SolcCombinedJson; pub use self::solc::combined_json::CombinedJson as SolcCombinedJson;
pub use self::solc::pipeline::Pipeline as SolcPipeline;
#[cfg(not(target_os = "emscripten"))] #[cfg(not(target_os = "emscripten"))]
pub use self::solc::solc_compiler::SolcCompiler; pub use self::solc::solc_compiler::SolcCompiler;
#[cfg(target_os = "emscripten")] #[cfg(target_os = "emscripten")]
@@ -53,6 +51,7 @@ pub mod test_utils;
pub mod tests; pub mod tests;
use std::collections::BTreeSet; use std::collections::BTreeSet;
use std::io::Write;
use std::path::PathBuf; use std::path::PathBuf;
/// Runs the Yul mode. /// Runs the Yul mode.
@@ -119,7 +118,6 @@ pub fn standard_output<T: Compiler>(
evm_version: Option<revive_common::EVMVersion>, evm_version: Option<revive_common::EVMVersion>,
solc_optimizer_enabled: bool, solc_optimizer_enabled: bool,
optimizer_settings: revive_llvm_context::OptimizerSettings, optimizer_settings: revive_llvm_context::OptimizerSettings,
force_evmla: bool,
include_metadata_hash: bool, include_metadata_hash: bool,
base_path: Option<String>, base_path: Option<String>,
include_paths: Vec<String>, include_paths: Vec<String>,
@@ -129,7 +127,6 @@ pub fn standard_output<T: Compiler>(
debug_config: revive_llvm_context::DebugConfig, debug_config: revive_llvm_context::DebugConfig,
) -> anyhow::Result<Build> { ) -> anyhow::Result<Build> {
let solc_version = solc.version()?; let solc_version = solc.version()?;
let solc_pipeline = SolcPipeline::new(&solc_version, force_evmla);
let solc_input = SolcStandardJsonInput::try_from_paths( let solc_input = SolcStandardJsonInput::try_from_paths(
SolcStandardJsonInputLanguage::Solidity, SolcStandardJsonInputLanguage::Solidity,
@@ -137,7 +134,7 @@ pub fn standard_output<T: Compiler>(
input_files, input_files,
libraries, libraries,
remappings, remappings,
SolcStandardJsonInputSettingsSelection::new_required(solc_pipeline), SolcStandardJsonInputSettingsSelection::new_required(),
SolcStandardJsonInputSettingsOptimizer::new( SolcStandardJsonInputSettingsOptimizer::new(
solc_optimizer_enabled, solc_optimizer_enabled,
None, None,
@@ -145,7 +142,6 @@ pub fn standard_output<T: Compiler>(
optimizer_settings.is_fallback_to_size_enabled(), optimizer_settings.is_fallback_to_size_enabled(),
), ),
None, None,
solc_pipeline == SolcPipeline::Yul,
suppressed_warnings, suppressed_warnings,
)?; )?;
@@ -156,13 +152,7 @@ pub fn standard_output<T: Compiler>(
.collect(); .collect();
let libraries = solc_input.settings.libraries.clone().unwrap_or_default(); let libraries = solc_input.settings.libraries.clone().unwrap_or_default();
let mut solc_output = solc.standard_json( let mut solc_output = solc.standard_json(solc_input, base_path, include_paths, allow_paths)?;
solc_input,
solc_pipeline,
base_path,
include_paths,
allow_paths,
)?;
if let Some(errors) = solc_output.errors.as_deref() { if let Some(errors) = solc_output.errors.as_deref() {
let mut has_errors = false; let mut has_errors = false;
@@ -172,7 +162,7 @@ pub fn standard_output<T: Compiler>(
has_errors = true; has_errors = true;
} }
eprintln!("{error}"); writeln!(std::io::stderr(), "{error}")?;
} }
if has_errors { if has_errors {
@@ -180,13 +170,8 @@ pub fn standard_output<T: Compiler>(
} }
} }
let project = solc_output.try_to_project( let project =
source_code_files, solc_output.try_to_project(source_code_files, libraries, &solc_version, &debug_config)?;
libraries,
solc_pipeline,
&solc_version,
&debug_config,
)?;
let build = project.compile(optimizer_settings, include_metadata_hash, debug_config)?; let build = project.compile(optimizer_settings, include_metadata_hash, debug_config)?;
@@ -194,20 +179,17 @@ pub fn standard_output<T: Compiler>(
} }
/// Runs the standard JSON mode. /// Runs the standard JSON mode.
#[allow(clippy::too_many_arguments)]
pub fn standard_json<T: Compiler>( pub fn standard_json<T: Compiler>(
solc: &mut T, solc: &mut T,
detect_missing_libraries: bool, detect_missing_libraries: bool,
force_evmla: bool,
base_path: Option<String>, base_path: Option<String>,
include_paths: Vec<String>, include_paths: Vec<String>,
allow_paths: Option<String>, allow_paths: Option<String>,
debug_config: revive_llvm_context::DebugConfig, debug_config: revive_llvm_context::DebugConfig,
) -> anyhow::Result<()> { ) -> anyhow::Result<()> {
let solc_version = solc.version()?; let solc_version = solc.version()?;
let solc_pipeline = SolcPipeline::new(&solc_version, force_evmla);
let solc_input = SolcStandardJsonInput::try_from_stdin(solc_pipeline)?; let solc_input = SolcStandardJsonInput::try_from_stdin()?;
let source_code_files = solc_input let source_code_files = solc_input
.sources .sources
.iter() .iter()
@@ -225,13 +207,7 @@ pub fn standard_json<T: Compiler>(
}; };
let libraries = solc_input.settings.libraries.clone().unwrap_or_default(); let libraries = solc_input.settings.libraries.clone().unwrap_or_default();
let mut solc_output = solc.standard_json( let mut solc_output = solc.standard_json(solc_input, base_path, include_paths, allow_paths)?;
solc_input,
solc_pipeline,
base_path,
include_paths,
allow_paths,
)?;
if let Some(errors) = solc_output.errors.as_deref() { if let Some(errors) = solc_output.errors.as_deref() {
for error in errors.iter() { for error in errors.iter() {
@@ -242,13 +218,8 @@ pub fn standard_json<T: Compiler>(
} }
} }
let project = solc_output.try_to_project( let project =
source_code_files, solc_output.try_to_project(source_code_files, libraries, &solc_version, &debug_config)?;
libraries,
solc_pipeline,
&solc_version,
&debug_config,
)?;
if detect_missing_libraries { if detect_missing_libraries {
let missing_libraries = project.get_missing_libraries(); let missing_libraries = project.get_missing_libraries();
@@ -271,7 +242,6 @@ pub fn combined_json<T: Compiler>(
evm_version: Option<revive_common::EVMVersion>, evm_version: Option<revive_common::EVMVersion>,
solc_optimizer_enabled: bool, solc_optimizer_enabled: bool,
optimizer_settings: revive_llvm_context::OptimizerSettings, optimizer_settings: revive_llvm_context::OptimizerSettings,
force_evmla: bool,
include_metadata_hash: bool, include_metadata_hash: bool,
base_path: Option<String>, base_path: Option<String>,
include_paths: Vec<String>, include_paths: Vec<String>,
@@ -289,7 +259,6 @@ pub fn combined_json<T: Compiler>(
evm_version, evm_version,
solc_optimizer_enabled, solc_optimizer_enabled,
optimizer_settings, optimizer_settings,
force_evmla,
include_metadata_hash, include_metadata_hash,
base_path, base_path,
include_paths, include_paths,
@@ -309,10 +278,11 @@ pub fn combined_json<T: Compiler>(
combined_json.write_to_directory(output_directory.as_path(), overwrite)?; combined_json.write_to_directory(output_directory.as_path(), overwrite)?;
} }
None => { None => {
println!( writeln!(
std::io::stdout(),
"{}", "{}",
serde_json::to_string(&combined_json).expect("Always valid") serde_json::to_string(&combined_json).expect("Always valid")
); )?;
} }
} }
std::process::exit(0); std::process::exit(0);
@@ -1,47 +0,0 @@
//! The contract EVM legacy assembly source code.
use std::collections::HashSet;
use serde::Deserialize;
use serde::Serialize;
use crate::evmla::assembly::Assembly;
use crate::solc::standard_json::output::contract::evm::extra_metadata::ExtraMetadata;
/// The contract EVM legacy assembly source code.
#[derive(Debug, Serialize, Deserialize, Clone)]
#[allow(non_camel_case_types)]
#[allow(clippy::upper_case_acronyms)]
pub struct EVMLA {
/// The EVM legacy assembly source code.
pub assembly: Assembly,
}
impl EVMLA {
/// A shortcut constructor.
pub fn new(mut assembly: Assembly, extra_metadata: ExtraMetadata) -> Self {
assembly.extra_metadata = Some(extra_metadata);
Self { assembly }
}
/// Get the list of missing deployable libraries.
pub fn get_missing_libraries(&self) -> HashSet<String> {
self.assembly.get_missing_libraries()
}
}
impl<D> revive_llvm_context::PolkaVMWriteLLVM<D> for EVMLA
where
D: revive_llvm_context::PolkaVMDependency + Clone,
{
fn declare(
&mut self,
context: &mut revive_llvm_context::PolkaVMContext<D>,
) -> anyhow::Result<()> {
self.assembly.declare(context)
}
fn into_llvm(self, context: &mut revive_llvm_context::PolkaVMContext<D>) -> anyhow::Result<()> {
self.assembly.into_llvm(context)
}
}
@@ -1,6 +1,5 @@
//! The contract source code. //! The contract source code.
pub mod evmla;
pub mod llvm_ir; pub mod llvm_ir;
pub mod yul; pub mod yul;
@@ -9,24 +8,17 @@ use std::collections::HashSet;
use serde::Deserialize; use serde::Deserialize;
use serde::Serialize; use serde::Serialize;
use crate::evmla::assembly::Assembly;
use crate::solc::standard_json::output::contract::evm::extra_metadata::ExtraMetadata;
use crate::yul::parser::statement::object::Object; use crate::yul::parser::statement::object::Object;
use self::evmla::EVMLA;
use self::llvm_ir::LLVMIR; use self::llvm_ir::LLVMIR;
use self::yul::Yul; use self::yul::Yul;
/// The contract source code. /// The contract source code.
#[derive(Debug, Serialize, Deserialize, Clone)] #[derive(Debug, Serialize, Deserialize, Clone)]
#[allow(non_camel_case_types)]
#[allow(clippy::upper_case_acronyms)] #[allow(clippy::upper_case_acronyms)]
#[allow(clippy::enum_variant_names)]
pub enum IR { pub enum IR {
/// The Yul source code. /// The Yul source code.
Yul(Yul), Yul(Yul),
/// The EVM legacy assembly source code.
EVMLA(EVMLA),
/// The LLVM IR source code. /// The LLVM IR source code.
LLVMIR(LLVMIR), LLVMIR(LLVMIR),
} }
@@ -37,11 +29,6 @@ impl IR {
Self::Yul(Yul::new(source_code, object)) Self::Yul(Yul::new(source_code, object))
} }
/// A shortcut constructor.
pub fn new_evmla(assembly: Assembly, extra_metadata: ExtraMetadata) -> Self {
Self::EVMLA(EVMLA::new(assembly, extra_metadata))
}
/// A shortcut constructor. /// A shortcut constructor.
pub fn new_llvm_ir(path: String, source: String) -> Self { pub fn new_llvm_ir(path: String, source: String) -> Self {
Self::LLVMIR(LLVMIR::new(path, source)) Self::LLVMIR(LLVMIR::new(path, source))
@@ -51,7 +38,6 @@ impl IR {
pub fn get_missing_libraries(&self) -> HashSet<String> { pub fn get_missing_libraries(&self) -> HashSet<String> {
match self { match self {
Self::Yul(inner) => inner.get_missing_libraries(), Self::Yul(inner) => inner.get_missing_libraries(),
Self::EVMLA(inner) => inner.get_missing_libraries(),
Self::LLVMIR(_inner) => HashSet::new(), Self::LLVMIR(_inner) => HashSet::new(),
} }
} }
@@ -67,7 +53,6 @@ where
) -> anyhow::Result<()> { ) -> anyhow::Result<()> {
match self { match self {
Self::Yul(inner) => inner.declare(context), Self::Yul(inner) => inner.declare(context),
Self::EVMLA(inner) => inner.declare(context),
Self::LLVMIR(_inner) => Ok(()), Self::LLVMIR(_inner) => Ok(()),
} }
} }
@@ -75,7 +60,6 @@ where
fn into_llvm(self, context: &mut revive_llvm_context::PolkaVMContext<D>) -> anyhow::Result<()> { fn into_llvm(self, context: &mut revive_llvm_context::PolkaVMContext<D>) -> anyhow::Result<()> {
match self { match self {
Self::Yul(inner) => inner.into_llvm(context), Self::Yul(inner) => inner.into_llvm(context),
Self::EVMLA(inner) => inner.into_llvm(context),
Self::LLVMIR(_inner) => Ok(()), Self::LLVMIR(_inner) => Ok(()),
} }
} }
@@ -54,12 +54,10 @@ impl Contract {
/// Returns the contract identifier, which is: /// Returns the contract identifier, which is:
/// - the Yul object identifier for Yul /// - the Yul object identifier for Yul
/// - the full contract path for EVM legacy assembly
/// - the module name for LLVM IR /// - the module name for LLVM IR
pub fn identifier(&self) -> &str { pub fn identifier(&self) -> &str {
match self.ir { match self.ir {
IR::Yul(ref yul) => yul.object.identifier.as_str(), IR::Yul(ref yul) => yul.object.identifier.as_str(),
IR::EVMLA(ref evm) => evm.assembly.full_path(),
IR::LLVMIR(ref llvm_ir) => llvm_ir.path.as_str(), IR::LLVMIR(ref llvm_ir) => llvm_ir.path.as_str(),
} }
} }
@@ -68,7 +66,6 @@ impl Contract {
pub fn drain_factory_dependencies(&mut self) -> HashSet<String> { pub fn drain_factory_dependencies(&mut self) -> HashSet<String> {
match self.ir { match self.ir {
IR::Yul(ref mut yul) => yul.object.factory_dependencies.drain().collect(), IR::Yul(ref mut yul) => yul.object.factory_dependencies.drain().collect(),
IR::EVMLA(ref mut evm) => evm.assembly.factory_dependencies.drain().collect(),
IR::LLVMIR(_) => HashSet::new(), IR::LLVMIR(_) => HashSet::new(),
} }
} }
@@ -129,10 +126,6 @@ impl Contract {
IR::Yul(_) => { IR::Yul(_) => {
context.set_yul_data(Default::default()); context.set_yul_data(Default::default());
} }
IR::EVMLA(_) => {
let evmla_data = revive_llvm_context::PolkaVMContextEVMLAData::new(version.default);
context.set_evmla_data(evmla_data);
}
IR::LLVMIR(_) => {} IR::LLVMIR(_) => {}
} }
+31 -57
View File
@@ -13,14 +13,14 @@ use path_slash::PathExt;
/// output directory. /// output directory.
/// Example: resolc ERC20.sol -O3 --bin --output-dir './build/' /// Example: resolc ERC20.sol -O3 --bin --output-dir './build/'
#[derive(Debug, Parser)] #[derive(Debug, Parser)]
#[structopt(name = "The PolkaVM Solidity compiler")] #[command(name = "The PolkaVM Solidity compiler", arg_required_else_help = true)]
pub struct Arguments { pub struct Arguments {
/// Print the version and exit. /// Print the version and exit.
#[structopt(long = "version")] #[arg(long = "version")]
pub version: bool, pub version: bool,
/// Print the licence and exit. /// Print the licence and exit.
#[structopt(long = "license")] #[arg(long = "license")]
pub license: bool, pub license: bool,
/// Specify the input paths and remappings. /// Specify the input paths and remappings.
@@ -31,159 +31,141 @@ pub struct Arguments {
/// Set the given path as the root of the source tree instead of the root of the filesystem. /// Set the given path as the root of the source tree instead of the root of the filesystem.
/// Passed to `solc` without changes. /// Passed to `solc` without changes.
#[structopt(long = "base-path")] #[arg(long = "base-path")]
pub base_path: Option<String>, pub base_path: Option<String>,
/// Make an additional source directory available to the default import callback. /// Make an additional source directory available to the default import callback.
/// Can be used multiple times. Can only be used if the base path has a non-empty value. /// Can be used multiple times. Can only be used if the base path has a non-empty value.
/// Passed to `solc` without changes. /// Passed to `solc` without changes.
#[structopt(long = "include-path")] #[arg(long = "include-path")]
pub include_paths: Vec<String>, pub include_paths: Vec<String>,
/// Allow a given path for imports. A list of paths can be supplied by separating them with a comma. /// Allow a given path for imports. A list of paths can be supplied by separating them with a comma.
/// Passed to `solc` without changes. /// Passed to `solc` without changes.
#[structopt(long = "allow-paths")] #[arg(long = "allow-paths")]
pub allow_paths: Option<String>, pub allow_paths: Option<String>,
/// Create one file per component and contract/file at the specified directory, if given. /// Create one file per component and contract/file at the specified directory, if given.
#[structopt(short = 'o', long = "output-dir")] #[arg(short = 'o', long = "output-dir")]
pub output_directory: Option<PathBuf>, pub output_directory: Option<PathBuf>,
/// Overwrite existing files (used together with -o). /// Overwrite existing files (used together with -o).
#[structopt(long = "overwrite")] #[arg(long = "overwrite")]
pub overwrite: bool, pub overwrite: bool,
/// Set the optimization parameter -O[0 | 1 | 2 | 3 | s | z]. /// Set the optimization parameter -O[0 | 1 | 2 | 3 | s | z].
/// Use `3` for best performance and `z` for minimal size. /// Use `3` for best performance and `z` for minimal size.
#[structopt(short = 'O', long = "optimization")] #[arg(short = 'O', long = "optimization")]
pub optimization: Option<char>, pub optimization: Option<char>,
/// Try to recompile with -Oz if the bytecode is too large. /// Try to recompile with -Oz if the bytecode is too large.
#[structopt(long = "fallback-Oz")] #[arg(long = "fallback-Oz")]
pub fallback_to_optimizing_for_size: bool, pub fallback_to_optimizing_for_size: bool,
/// Disable the `solc` optimizer. /// Disable the `solc` optimizer.
/// Use it if your project uses the `MSIZE` instruction, or in other cases. /// Use it if your project uses the `MSIZE` instruction, or in other cases.
/// Beware that it will prevent libraries from being inlined. /// Beware that it will prevent libraries from being inlined.
#[structopt(long = "disable-solc-optimizer")] #[arg(long = "disable-solc-optimizer")]
pub disable_solc_optimizer: bool, pub disable_solc_optimizer: bool,
/// Specify the path to the `solc` executable. By default, the one in `${PATH}` is used. /// Specify the path to the `solc` executable. By default, the one in `${PATH}` is used.
/// Yul mode: `solc` is used for source code validation, as `resolc` itself assumes that the input Yul is valid. /// Yul mode: `solc` is used for source code validation, as `resolc` itself assumes that the input Yul is valid.
/// LLVM IR mode: `solc` is unused. /// LLVM IR mode: `solc` is unused.
#[structopt(long = "solc")] #[arg(long = "solc")]
pub solc: Option<String>, pub solc: Option<String>,
/// The EVM target version to generate IR for. /// The EVM target version to generate IR for.
/// See https://github.com/paritytech/revive/blob/main/crates/common/src/evm_version.rs for reference. /// See https://github.com/paritytech/revive/blob/main/crates/common/src/evm_version.rs for reference.
#[structopt(long = "evm-version")] #[arg(long = "evm-version")]
pub evm_version: Option<String>, pub evm_version: Option<String>,
/// Specify addresses of deployable libraries. Syntax: `<libraryName>=<address> [, or whitespace] ...`. /// Specify addresses of deployable libraries. Syntax: `<libraryName>=<address> [, or whitespace] ...`.
/// Addresses are interpreted as hexadecimal strings prefixed with `0x`. /// Addresses are interpreted as hexadecimal strings prefixed with `0x`.
#[structopt(short = 'l', long = "libraries")] #[arg(short = 'l', long = "libraries")]
pub libraries: Vec<String>, pub libraries: Vec<String>,
/// Output a single JSON document containing the specified information. /// Output a single JSON document containing the specified information.
/// Available arguments: `abi`, `hashes`, `metadata`, `devdoc`, `userdoc`, `storage-layout`, `ast`, `asm`, `bin`, `bin-runtime`. /// Available arguments: `abi`, `hashes`, `metadata`, `devdoc`, `userdoc`, `storage-layout`, `ast`, `asm`, `bin`, `bin-runtime`.
#[structopt(long = "combined-json")] #[arg(long = "combined-json")]
pub combined_json: Option<String>, pub combined_json: Option<String>,
/// Switch to standard JSON input/output mode. Read from stdin, write the result to stdout. /// Switch to standard JSON input/output mode. Read from stdin, write the result to stdout.
/// This is the default used by the Hardhat plugin. /// This is the default used by the Hardhat plugin.
#[structopt(long = "standard-json")] #[arg(long = "standard-json")]
pub standard_json: bool, pub standard_json: bool,
/// Switch to missing deployable libraries detection mode. /// Switch to missing deployable libraries detection mode.
/// Only available for standard JSON input/output mode. /// Only available for standard JSON input/output mode.
/// Contracts are not compiled in this mode, and all compilation artifacts are not included. /// Contracts are not compiled in this mode, and all compilation artifacts are not included.
#[structopt(long = "detect-missing-libraries")] #[arg(long = "detect-missing-libraries")]
pub detect_missing_libraries: bool, pub detect_missing_libraries: bool,
/// Switch to Yul mode. /// Switch to Yul mode.
/// Only one input Yul file is allowed. /// Only one input Yul file is allowed.
/// Cannot be used with combined and standard JSON modes. /// Cannot be used with combined and standard JSON modes.
#[structopt(long = "yul")] #[arg(long = "yul")]
pub yul: bool, pub yul: bool,
/// Switch to LLVM IR mode. /// Switch to LLVM IR mode.
/// Only one input LLVM IR file is allowed. /// Only one input LLVM IR file is allowed.
/// Cannot be used with combined and standard JSON modes. /// Cannot be used with combined and standard JSON modes.
/// Use this mode at your own risk, as LLVM IR input validation is not implemented. /// Use this mode at your own risk, as LLVM IR input validation is not implemented.
#[structopt(long = "llvm-ir")] #[arg(long = "llvm-ir")]
pub llvm_ir: bool, pub llvm_ir: bool,
/// Forcibly switch to EVM legacy assembly pipeline.
/// It is useful for older revisions of `solc` 0.8, where Yul was considered highly experimental
/// and contained more bugs than today.
#[structopt(long = "force-evmla")]
pub force_evmla: bool,
/// Set metadata hash mode. /// Set metadata hash mode.
/// The only supported value is `none` that disables appending the metadata hash. /// The only supported value is `none` that disables appending the metadata hash.
/// Is enabled by default. /// Is enabled by default.
#[structopt(long = "metadata-hash")] #[arg(long = "metadata-hash")]
pub metadata_hash: Option<String>, pub metadata_hash: Option<String>,
/// Output PolkaVM assembly of the contracts. /// Output PolkaVM assembly of the contracts.
#[structopt(long = "asm")] #[arg(long = "asm")]
pub output_assembly: bool, pub output_assembly: bool,
/// Output PolkaVM bytecode of the contracts. /// Output PolkaVM bytecode of the contracts.
#[structopt(long = "bin")] #[arg(long = "bin")]
pub output_binary: bool, pub output_binary: bool,
/// Suppress specified warnings. /// Suppress specified warnings.
/// Available arguments: `ecrecover`, `sendtransfer`, `extcodesize`, `txorigin`, `blocktimestamp`, `blocknumber`, `blockhash`. /// Available arguments: `ecrecover`, `sendtransfer`, `extcodesize`, `txorigin`, `blocktimestamp`, `blocknumber`, `blockhash`.
#[structopt(long = "suppress-warnings")] #[arg(long = "suppress-warnings")]
pub suppress_warnings: Option<Vec<String>>, pub suppress_warnings: Option<Vec<String>>,
/// Generate source based debug information in the output code file. This only has an effect /// Generate source based debug information in the output code file. This only has an effect
/// with the LLVM-IR code generator and is ignored otherwise. /// with the LLVM-IR code generator and is ignored otherwise.
#[structopt(short = 'g')] #[arg(short = 'g')]
pub emit_source_debug_info: bool, pub emit_source_debug_info: bool,
/// Dump all IRs to files in the specified directory. /// Dump all IRs to files in the specified directory.
/// Only for testing and debugging. /// Only for testing and debugging.
#[structopt(long = "debug-output-dir")] #[arg(long = "debug-output-dir")]
pub debug_output_directory: Option<PathBuf>, pub debug_output_directory: Option<PathBuf>,
/// Set the verify-each option in LLVM. /// Set the verify-each option in LLVM.
/// Only for testing and debugging. /// Only for testing and debugging.
#[structopt(long = "llvm-verify-each")] #[arg(long = "llvm-verify-each")]
pub llvm_verify_each: bool, pub llvm_verify_each: bool,
/// Set the debug-logging option in LLVM. /// Set the debug-logging option in LLVM.
/// Only for testing and debugging. /// Only for testing and debugging.
#[structopt(long = "llvm-debug-logging")] #[arg(long = "llvm-debug-logging")]
pub llvm_debug_logging: bool, pub llvm_debug_logging: bool,
/// Run this process recursively and provide JSON input to compile a single contract. /// Run this process recursively and provide JSON input to compile a single contract.
/// Only for usage from within the compiler. /// Only for usage from within the compiler.
#[structopt(long = "recursive-process")] #[arg(long = "recursive-process")]
pub recursive_process: bool, pub recursive_process: bool,
/// Specify the input file to use instead of stdin when --recursive-process is given. /// Specify the input file to use instead of stdin when --recursive-process is given.
/// This is only intended for use when developing the compiler. /// This is only intended for use when developing the compiler.
#[cfg(debug_assertions)] #[cfg(debug_assertions)]
#[structopt(long = "recursive-process-input")] #[arg(long = "recursive-process-input")]
pub recursive_process_input: Option<String>, pub recursive_process_input: Option<String>,
} }
impl Default for Arguments {
fn default() -> Self {
Self::new()
}
}
impl Arguments { impl Arguments {
/// A shortcut constructor.
pub fn new() -> Self {
Self::parse()
}
/// Validates the arguments. /// Validates the arguments.
#[allow(clippy::collapsible_if)]
pub fn validate(&self) -> anyhow::Result<()> { pub fn validate(&self) -> anyhow::Result<()> {
if self.version && std::env::args().count() > 2 { if self.version && std::env::args().count() > 2 {
anyhow::bail!("No other options are allowed while getting the compiler version."); anyhow::bail!("No other options are allowed while getting the compiler version.");
@@ -248,28 +230,20 @@ impl Arguments {
); );
} }
if self.force_evmla {
anyhow::bail!("EVM legacy assembly mode is not supported in Yul, LLVM IR and PolkaVM assembly modes.");
}
if self.disable_solc_optimizer { if self.disable_solc_optimizer {
anyhow::bail!("Disabling the solc optimizer is not supported in Yul, LLVM IR and PolkaVM assembly modes."); anyhow::bail!("Disabling the solc optimizer is not supported in Yul, LLVM IR and PolkaVM assembly modes.");
} }
} }
if self.llvm_ir { if self.llvm_ir && self.solc.is_some() {
if self.solc.is_some() {
anyhow::bail!("`solc` is not used in LLVM IR and PolkaVM assembly modes."); anyhow::bail!("`solc` is not used in LLVM IR and PolkaVM assembly modes.");
} }
}
if self.combined_json.is_some() { if self.combined_json.is_some() && (self.output_assembly || self.output_binary) {
if self.output_assembly || self.output_binary {
anyhow::bail!( anyhow::bail!(
"Cannot output assembly or binary outside of JSON in combined JSON mode." "Cannot output assembly or binary outside of JSON in combined JSON mode."
); );
} }
}
if self.standard_json { if self.standard_json {
if self.output_assembly || self.output_binary { if self.output_assembly || self.output_binary {
+25 -18
View File
@@ -2,6 +2,7 @@
pub mod arguments; pub mod arguments;
use std::io::Write;
use std::str::FromStr; use std::str::FromStr;
use revive_solidity::Process; use revive_solidity::Process;
@@ -16,28 +17,27 @@ const RAYON_WORKER_STACK_SIZE: usize = 16 * 1024 * 1024;
#[global_allocator] #[global_allocator]
static GLOBAL: mimalloc::MiMalloc = mimalloc::MiMalloc; static GLOBAL: mimalloc::MiMalloc = mimalloc::MiMalloc;
/// The application entry point. fn main() -> anyhow::Result<()> {
fn main() {
std::process::exit(match main_inner() { std::process::exit(match main_inner() {
Ok(()) => revive_common::EXIT_CODE_SUCCESS, Ok(()) => revive_common::EXIT_CODE_SUCCESS,
Err(error) => { Err(error) => {
eprintln!("{error}"); writeln!(std::io::stderr(), "{error}")?;
revive_common::EXIT_CODE_FAILURE revive_common::EXIT_CODE_FAILURE
} }
}) })
} }
/// The auxiliary `main` function to facilitate the `?` error conversion operator.
fn main_inner() -> anyhow::Result<()> { fn main_inner() -> anyhow::Result<()> {
let arguments = Arguments::new(); let arguments = <Arguments as clap::Parser>::try_parse()?;
arguments.validate()?; arguments.validate()?;
if arguments.version { if arguments.version {
println!( writeln!(
std::io::stdout(),
"{} version {}", "{} version {}",
env!("CARGO_PKG_DESCRIPTION"), env!("CARGO_PKG_DESCRIPTION"),
revive_solidity::ResolcVersion::default().long revive_solidity::ResolcVersion::default().long
); )?;
return Ok(()); return Ok(());
} }
@@ -45,7 +45,7 @@ fn main_inner() -> anyhow::Result<()> {
let license_mit = include_str!("../../../../LICENSE-MIT"); let license_mit = include_str!("../../../../LICENSE-MIT");
let license_apache = include_str!("../../../../LICENSE-APACHE"); let license_apache = include_str!("../../../../LICENSE-APACHE");
println!("{}\n{}\n", license_mit, license_apache); writeln!(std::io::stdout(), "{}\n{}\n", license_mit, license_apache)?;
return Ok(()); return Ok(());
} }
@@ -103,7 +103,7 @@ fn main_inner() -> anyhow::Result<()> {
let mut solc = { let mut solc = {
#[cfg(target_os = "emscripten")] #[cfg(target_os = "emscripten")]
{ {
revive_solidity::SoljsonCompiler { version: None } revive_solidity::SoljsonCompiler
} }
#[cfg(not(target_os = "emscripten"))] #[cfg(not(target_os = "emscripten"))]
@@ -157,7 +157,6 @@ fn main_inner() -> anyhow::Result<()> {
revive_solidity::standard_json( revive_solidity::standard_json(
&mut solc, &mut solc,
arguments.detect_missing_libraries, arguments.detect_missing_libraries,
arguments.force_evmla,
arguments.base_path, arguments.base_path,
arguments.include_paths, arguments.include_paths,
arguments.allow_paths, arguments.allow_paths,
@@ -173,7 +172,6 @@ fn main_inner() -> anyhow::Result<()> {
evm_version, evm_version,
!arguments.disable_solc_optimizer, !arguments.disable_solc_optimizer,
optimizer_settings, optimizer_settings,
arguments.force_evmla,
include_metadata_hash, include_metadata_hash,
arguments.base_path, arguments.base_path,
arguments.include_paths, arguments.include_paths,
@@ -193,7 +191,6 @@ fn main_inner() -> anyhow::Result<()> {
evm_version, evm_version,
!arguments.disable_solc_optimizer, !arguments.disable_solc_optimizer,
optimizer_settings, optimizer_settings,
arguments.force_evmla,
include_metadata_hash, include_metadata_hash,
arguments.base_path, arguments.base_path,
arguments.include_paths, arguments.include_paths,
@@ -214,26 +211,36 @@ fn main_inner() -> anyhow::Result<()> {
arguments.overwrite, arguments.overwrite,
)?; )?;
eprintln!( writeln!(
std::io::stderr(),
"Compiler run successful. Artifact(s) can be found in directory {output_directory:?}." "Compiler run successful. Artifact(s) can be found in directory {output_directory:?}."
); )?;
} else if arguments.output_assembly || arguments.output_binary { } else if arguments.output_assembly || arguments.output_binary {
for (path, contract) in build.contracts.into_iter() { for (path, contract) in build.contracts.into_iter() {
if arguments.output_assembly { if arguments.output_assembly {
let assembly_text = contract.build.assembly_text; let assembly_text = contract.build.assembly_text;
println!("Contract `{}` assembly:\n\n{}", path, assembly_text); writeln!(
std::io::stdout(),
"Contract `{}` assembly:\n\n{}",
path,
assembly_text
)?;
} }
if arguments.output_binary { if arguments.output_binary {
println!( writeln!(
std::io::stdout(),
"Contract `{}` bytecode: 0x{}", "Contract `{}` bytecode: 0x{}",
path, path,
hex::encode(contract.build.bytecode) hex::encode(contract.build.bytecode)
); )?;
} }
} }
} else { } else {
eprintln!("Compiler run successful. No output requested. Use --asm and --bin flags."); writeln!(
std::io::stderr(),
"Compiler run successful. No output requested. Use --asm and --bin flags."
)?;
} }
Ok(()) Ok(())
@@ -80,10 +80,9 @@ impl CombinedJson {
file_path.push(format!("combined.{}", revive_common::EXTENSION_JSON)); file_path.push(format!("combined.{}", revive_common::EXTENSION_JSON));
if file_path.exists() && !overwrite { if file_path.exists() && !overwrite {
eprintln!( anyhow::bail!(
"Refusing to overwrite an existing file {file_path:?} (use --overwrite to force)." "Refusing to overwrite an existing file {file_path:?} (use --overwrite to force)."
); );
return Ok(());
} }
File::create(&file_path) File::create(&file_path)
+3 -18
View File
@@ -1,7 +1,6 @@
//! The Solidity compiler. //! The Solidity compiler.
pub mod combined_json; pub mod combined_json;
pub mod pipeline;
#[cfg(not(target_os = "emscripten"))] #[cfg(not(target_os = "emscripten"))]
pub mod solc_compiler; pub mod solc_compiler;
#[cfg(target_os = "emscripten")] #[cfg(target_os = "emscripten")]
@@ -9,35 +8,22 @@ pub mod soljson_compiler;
pub mod standard_json; pub mod standard_json;
pub mod version; pub mod version;
use once_cell::sync::Lazy;
use semver::VersionReq;
use std::path::Path; use std::path::Path;
use std::path::PathBuf; use std::path::PathBuf;
use self::combined_json::CombinedJson; use self::combined_json::CombinedJson;
use self::pipeline::Pipeline;
use self::standard_json::input::Input as StandardJsonInput; use self::standard_json::input::Input as StandardJsonInput;
use self::standard_json::output::Output as StandardJsonOutput; use self::standard_json::output::Output as StandardJsonOutput;
use self::version::Version; use self::version::Version;
/// The first version of `solc` with the support of standard JSON interface. /// The first version of `solc` with the support of standard JSON interface.
pub const FIRST_SUPPORTED_VERSION: semver::Version = semver::Version::new(0, 4, 12); pub const FIRST_SUPPORTED_VERSION: semver::Version = semver::Version::new(0, 8, 0);
/// The first version of `solc`, where Yul codegen is considered robust enough.
pub const FIRST_YUL_VERSION: semver::Version = semver::Version::new(0, 8, 0);
/// The first version of `solc`, where `--via-ir` codegen mode is supported.
pub const FIRST_VIA_IR_VERSION: semver::Version = semver::Version::new(0, 8, 13);
/// The last supported version of `solc`. /// The last supported version of `solc`.
pub const LAST_SUPPORTED_VERSION: semver::Version = semver::Version::new(0, 8, 28); pub const LAST_SUPPORTED_VERSION: semver::Version = semver::Version::new(0, 8, 28);
/// `--base-path` was introduced in 0.6.9 <https://github.com/ethereum/solidity/releases/tag/v0.6.9>
pub static FIRST_SUPPORTS_BASE_PATH: Lazy<VersionReq> =
Lazy::new(|| VersionReq::parse(">=0.6.9").unwrap());
/// `--include-path` was introduced in 0.8.8 <https://github.com/ethereum/solidity/releases/tag/v0.8.8> /// `--include-path` was introduced in solc `0.8.8` <https://github.com/ethereum/solidity/releases/tag/v0.8.8>
pub static FIRST_SUPPORTS_INCLUDE_PATH: Lazy<VersionReq> = pub const FIRST_INCLUDE_PATH_VERSION: semver::Version = semver::Version::new(0, 8, 8);
Lazy::new(|| VersionReq::parse(">=0.8.8").unwrap());
/// The Solidity compiler. /// The Solidity compiler.
pub trait Compiler { pub trait Compiler {
@@ -45,7 +31,6 @@ pub trait Compiler {
fn standard_json( fn standard_json(
&mut self, &mut self,
input: StandardJsonInput, input: StandardJsonInput,
pipeline: Pipeline,
base_path: Option<String>, base_path: Option<String>,
include_paths: Vec<String>, include_paths: Vec<String>,
allow_paths: Option<String>, allow_paths: Option<String>,
-27
View File
@@ -1,27 +0,0 @@
//! The Solidity compiler pipeline type.
use serde::{Deserialize, Serialize};
use crate::solc::version::Version as SolcVersion;
/// The Solidity compiler pipeline type.
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, Serialize, Deserialize)]
#[allow(non_camel_case_types)]
#[allow(clippy::upper_case_acronyms)]
pub enum Pipeline {
/// The Yul IR.
Yul,
/// The EVM legacy assembly IR.
EVMLA,
}
impl Pipeline {
/// We always use EVMLA for Solidity <=0.7, or if the user does not want to compile via Yul.
pub fn new(solc_version: &SolcVersion, force_evmla: bool) -> Self {
if solc_version.default < crate::solc::FIRST_YUL_VERSION || force_evmla {
Self::EVMLA
} else {
Self::Yul
}
}
}
+20 -55
View File
@@ -1,24 +1,20 @@
//! The Solidity compiler. //! The Solidity compiler solc interface.
use std::io::Write; use std::io::Write;
use std::path::Path; use std::path::Path;
use std::path::PathBuf; use std::path::PathBuf;
use crate::solc::combined_json::CombinedJson; use crate::solc::combined_json::CombinedJson;
use crate::solc::pipeline::Pipeline;
use crate::solc::standard_json::input::Input as StandardJsonInput; use crate::solc::standard_json::input::Input as StandardJsonInput;
use crate::solc::standard_json::output::Output as StandardJsonOutput; use crate::solc::standard_json::output::Output as StandardJsonOutput;
use crate::solc::version::Version; use crate::solc::version::Version;
use super::Compiler; use super::Compiler;
use crate::solc::{FIRST_SUPPORTS_BASE_PATH, FIRST_SUPPORTS_INCLUDE_PATH};
/// The Solidity compiler. /// The Solidity compiler.
pub struct SolcCompiler { pub struct SolcCompiler {
/// The binary executable name. /// The binary executable name.
pub executable: String, pub executable: String,
/// The lazily-initialized compiler version.
pub version: Option<Version>,
} }
impl SolcCompiler { impl SolcCompiler {
@@ -35,10 +31,7 @@ impl SolcCompiler {
error error
); );
} }
Ok(Self { Ok(Self { executable })
executable,
version: None,
})
} }
} }
@@ -47,45 +40,31 @@ impl Compiler for SolcCompiler {
fn standard_json( fn standard_json(
&mut self, &mut self,
mut input: StandardJsonInput, mut input: StandardJsonInput,
pipeline: Pipeline,
base_path: Option<String>, base_path: Option<String>,
include_paths: Vec<String>, include_paths: Vec<String>,
allow_paths: Option<String>, allow_paths: Option<String>,
) -> anyhow::Result<StandardJsonOutput> { ) -> anyhow::Result<StandardJsonOutput> {
let version = self.version()?; let version = self.version()?.validate(&include_paths)?.default;
let mut command = std::process::Command::new(self.executable.as_str()); let mut command = std::process::Command::new(self.executable.as_str());
command.stdin(std::process::Stdio::piped()); command.stdin(std::process::Stdio::piped());
command.stdout(std::process::Stdio::piped()); command.stdout(std::process::Stdio::piped());
command.arg("--standard-json"); command.arg("--standard-json");
if let Some(base_path) = &base_path {
if !FIRST_SUPPORTS_BASE_PATH.matches(&version.default) {
anyhow::bail!(
"--base-path not supported this version {} of solc",
&version.default
);
}
command.arg("--base-path").arg(base_path);
}
if !include_paths.is_empty() && !FIRST_SUPPORTS_INCLUDE_PATH.matches(&version.default) {
anyhow::bail!(
"--include-path not supported this version {} of solc",
&version.default
);
}
for include_path in include_paths.into_iter() { for include_path in include_paths.into_iter() {
command.arg("--include-path"); command.arg("--include-path");
command.arg(include_path); command.arg(include_path);
} }
if let Some(base_path) = base_path {
command.arg("--base-path");
command.arg(base_path);
}
if let Some(allow_paths) = allow_paths { if let Some(allow_paths) = allow_paths {
command.arg("--allow-paths"); command.arg("--allow-paths");
command.arg(allow_paths); command.arg(allow_paths);
} }
input.normalize(&version.default); input.normalize(&version);
let suppressed_warnings = input.suppressed_warnings.take().unwrap_or_default(); let suppressed_warnings = input.suppressed_warnings.take().unwrap_or_default();
@@ -129,7 +108,7 @@ impl Compiler for SolcCompiler {
), ),
) )
})?; })?;
output.preprocess_ast(&version, pipeline, suppressed_warnings.as_slice())?; output.preprocess_ast(suppressed_warnings.as_slice())?;
Ok(output) Ok(output)
} }
@@ -163,8 +142,16 @@ impl Compiler for SolcCompiler {
anyhow::anyhow!("{} subprocess error: {:?}", self.executable, error) anyhow::anyhow!("{} subprocess error: {:?}", self.executable, error)
})?; })?;
if !output.status.success() { if !output.status.success() {
println!("{}", String::from_utf8_lossy(output.stdout.as_slice())); writeln!(
println!("{}", String::from_utf8_lossy(output.stderr.as_slice())); std::io::stdout(),
"{}",
String::from_utf8_lossy(output.stdout.as_slice())
)?;
writeln!(
std::io::stdout(),
"{}",
String::from_utf8_lossy(output.stderr.as_slice())
)?;
anyhow::bail!( anyhow::bail!(
"{} error: {}", "{} error: {}",
self.executable, self.executable,
@@ -228,10 +215,6 @@ impl Compiler for SolcCompiler {
/// The `solc --version` mini-parser. /// The `solc --version` mini-parser.
fn version(&mut self) -> anyhow::Result<Version> { fn version(&mut self) -> anyhow::Result<Version> {
if let Some(version) = self.version.as_ref() {
return Ok(version.to_owned());
}
let mut command = std::process::Command::new(self.executable.as_str()); let mut command = std::process::Command::new(self.executable.as_str());
command.arg("--version"); command.arg("--version");
let output = command.output().map_err(|error| { let output = command.output().map_err(|error| {
@@ -277,24 +260,6 @@ impl Compiler for SolcCompiler {
.and_then(|line| line.split('-').nth(1)) .and_then(|line| line.split('-').nth(1))
.and_then(|version| version.parse().ok()); .and_then(|version| version.parse().ok());
let version = Version::new(long, default, l2_revision); Ok(Version::new(long, default, l2_revision))
if version.default < super::FIRST_SUPPORTED_VERSION {
anyhow::bail!(
"`solc` versions <{} are not supported, found {}",
super::FIRST_SUPPORTED_VERSION,
version.default
);
}
if version.default > super::LAST_SUPPORTED_VERSION {
anyhow::bail!(
"`solc` versions >{} are not supported, found {}",
super::LAST_SUPPORTED_VERSION,
version.default
);
}
self.version = Some(version.clone());
Ok(version)
} }
} }
+20 -34
View File
@@ -1,10 +1,9 @@
//! The Solidity compiler. //! The Solidity compiler solJson interface.
use std::path::Path; use std::path::Path;
use std::path::PathBuf; use std::path::PathBuf;
use crate::solc::combined_json::CombinedJson; use crate::solc::combined_json::CombinedJson;
use crate::solc::pipeline::Pipeline;
use crate::solc::standard_json::input::Input as StandardJsonInput; use crate::solc::standard_json::input::Input as StandardJsonInput;
use crate::solc::standard_json::output::Output as StandardJsonOutput; use crate::solc::standard_json::output::Output as StandardJsonOutput;
use crate::solc::version::Version; use crate::solc::version::Version;
@@ -19,23 +18,30 @@ extern "C" {
} }
/// The Solidity compiler. /// The Solidity compiler.
pub struct SoljsonCompiler { pub struct SoljsonCompiler;
/// The lazily-initialized compiler version.
pub version: Option<Version>,
}
impl Compiler for SoljsonCompiler { impl Compiler for SoljsonCompiler {
/// Compiles the Solidity `--standard-json` input into Yul IR. /// Compiles the Solidity `--standard-json` input into Yul IR.
fn standard_json( fn standard_json(
&mut self, &mut self,
mut input: StandardJsonInput, mut input: StandardJsonInput,
pipeline: Pipeline, base_path: Option<String>,
_base_path: Option<String>, include_paths: Vec<String>,
_include_paths: Vec<String>, allow_paths: Option<String>,
_allow_paths: Option<String>,
) -> anyhow::Result<StandardJsonOutput> { ) -> anyhow::Result<StandardJsonOutput> {
let version = self.version()?; if !include_paths.is_empty() {
input.normalize(&version.default); anyhow::bail!("configuring include paths is not supported with solJson")
}
if base_path.is_some() {
anyhow::bail!("configuring the base path is not supported with solJson")
}
if allow_paths.is_some() {
anyhow::bail!("configuring allow paths is not supported with solJson")
}
let version = self.version()?.validate(&include_paths)?.default;
input.normalize(&version);
let suppressed_warnings = input.suppressed_warnings.take().unwrap_or_default(); let suppressed_warnings = input.suppressed_warnings.take().unwrap_or_default();
let input_json = serde_json::to_string(&input).expect("Always valid"); let input_json = serde_json::to_string(&input).expect("Always valid");
@@ -50,7 +56,7 @@ impl Compiler for SoljsonCompiler {
.unwrap_or_else(|_| String::from_utf8_lossy(out.as_bytes()).to_string()), .unwrap_or_else(|_| String::from_utf8_lossy(out.as_bytes()).to_string()),
) )
})?; })?;
output.preprocess_ast(&version, pipeline, suppressed_warnings.as_slice())?; output.preprocess_ast(suppressed_warnings.as_slice())?;
Ok(output) Ok(output)
} }
@@ -76,31 +82,11 @@ impl Compiler for SoljsonCompiler {
.ok_or_else(|| anyhow::anyhow!("Soljson version parsing: metadata dropping"))? .ok_or_else(|| anyhow::anyhow!("Soljson version parsing: metadata dropping"))?
.parse() .parse()
.map_err(|error| anyhow::anyhow!("Soljson version parsing: {}", error))?; .map_err(|error| anyhow::anyhow!("Soljson version parsing: {}", error))?;
let l2_revision: Option<semver::Version> = version let l2_revision: Option<semver::Version> = version
.split('-') .split('-')
.nth(1) .nth(1)
.and_then(|version| version.parse().ok()); .and_then(|version| version.parse().ok());
Ok(Version::new(long, default, l2_revision))
let version = Version::new(long, default, l2_revision);
if version.default < super::FIRST_SUPPORTED_VERSION {
anyhow::bail!(
"`Soljson` versions <{} are not supported, found {}",
super::FIRST_SUPPORTED_VERSION,
version.default
);
}
if version.default > super::LAST_SUPPORTED_VERSION {
anyhow::bail!(
"`Soljson` versions >{} are not supported, found {}",
super::LAST_SUPPORTED_VERSION,
version.default
);
}
self.version = Some(version.clone());
Ok(version)
} }
} }
@@ -13,7 +13,6 @@ use rayon::iter::{IntoParallelIterator, ParallelIterator};
use serde::Deserialize; use serde::Deserialize;
use serde::Serialize; use serde::Serialize;
use crate::solc::pipeline::Pipeline as SolcPipeline;
use crate::solc::standard_json::input::settings::metadata::Metadata as SolcStandardJsonInputSettingsMetadata; use crate::solc::standard_json::input::settings::metadata::Metadata as SolcStandardJsonInputSettingsMetadata;
use crate::solc::standard_json::input::settings::optimizer::Optimizer as SolcStandardJsonInputSettingsOptimizer; use crate::solc::standard_json::input::settings::optimizer::Optimizer as SolcStandardJsonInputSettingsOptimizer;
use crate::solc::standard_json::input::settings::selection::Selection as SolcStandardJsonInputSettingsSelection; use crate::solc::standard_json::input::settings::selection::Selection as SolcStandardJsonInputSettingsSelection;
@@ -40,13 +39,13 @@ pub struct Input {
impl Input { impl Input {
/// A shortcut constructor from stdin. /// A shortcut constructor from stdin.
pub fn try_from_stdin(solc_pipeline: SolcPipeline) -> anyhow::Result<Self> { pub fn try_from_stdin() -> anyhow::Result<Self> {
let mut input: Self = serde_json::from_reader(std::io::BufReader::new(std::io::stdin()))?; let mut input: Self = serde_json::from_reader(std::io::BufReader::new(std::io::stdin()))?;
input input
.settings .settings
.output_selection .output_selection
.get_or_insert_with(SolcStandardJsonInputSettingsSelection::default) .get_or_insert_with(SolcStandardJsonInputSettingsSelection::default)
.extend_with_required(solc_pipeline); .extend_with_required();
Ok(input) Ok(input)
} }
@@ -61,15 +60,16 @@ impl Input {
output_selection: SolcStandardJsonInputSettingsSelection, output_selection: SolcStandardJsonInputSettingsSelection,
optimizer: SolcStandardJsonInputSettingsOptimizer, optimizer: SolcStandardJsonInputSettingsOptimizer,
metadata: Option<SolcStandardJsonInputSettingsMetadata>, metadata: Option<SolcStandardJsonInputSettingsMetadata>,
via_ir: bool,
suppressed_warnings: Option<Vec<Warning>>, suppressed_warnings: Option<Vec<Warning>>,
) -> anyhow::Result<Self> { ) -> anyhow::Result<Self> {
#[cfg(feature = "parallel")] let mut paths: BTreeSet<PathBuf> = paths.iter().cloned().collect();
let iter = paths.into_par_iter(); // Parallel iterator let libraries = Settings::parse_libraries(library_map)?;
for library_file in libraries.keys() {
paths.insert(PathBuf::from(library_file));
}
#[cfg(not(feature = "parallel"))] let sources = paths
let iter = paths.iter(); // Sequential iterator .iter()
let sources = iter
.map(|path| { .map(|path| {
let source = Source::try_from(path.as_path()).unwrap_or_else(|error| { let source = Source::try_from(path.as_path()).unwrap_or_else(|error| {
panic!("Source code file {path:?} reading error: {error}") panic!("Source code file {path:?} reading error: {error}")
@@ -78,8 +78,6 @@ impl Input {
}) })
.collect(); .collect();
let libraries = Settings::parse_libraries(library_map)?;
Ok(Self { Ok(Self {
language, language,
sources, sources,
@@ -88,7 +86,6 @@ impl Input {
libraries, libraries,
remappings, remappings,
output_selection, output_selection,
via_ir,
optimizer, optimizer,
metadata, metadata,
), ),
@@ -107,7 +104,6 @@ impl Input {
output_selection: SolcStandardJsonInputSettingsSelection, output_selection: SolcStandardJsonInputSettingsSelection,
optimizer: SolcStandardJsonInputSettingsOptimizer, optimizer: SolcStandardJsonInputSettingsOptimizer,
metadata: Option<SolcStandardJsonInputSettingsMetadata>, metadata: Option<SolcStandardJsonInputSettingsMetadata>,
via_ir: bool,
suppressed_warnings: Option<Vec<Warning>>, suppressed_warnings: Option<Vec<Warning>>,
) -> anyhow::Result<Self> { ) -> anyhow::Result<Self> {
#[cfg(feature = "parallel")] #[cfg(feature = "parallel")]
@@ -127,7 +123,6 @@ impl Input {
libraries, libraries,
remappings, remappings,
output_selection, output_selection,
via_ir,
optimizer, optimizer,
metadata, metadata,
), ),
@@ -51,7 +51,6 @@ impl Settings {
libraries: BTreeMap<String, BTreeMap<String, String>>, libraries: BTreeMap<String, BTreeMap<String, String>>,
remappings: Option<BTreeSet<String>>, remappings: Option<BTreeSet<String>>,
output_selection: Selection, output_selection: Selection,
via_ir: bool,
optimizer: Optimizer, optimizer: Optimizer,
metadata: Option<Metadata>, metadata: Option<Metadata>,
) -> Self { ) -> Self {
@@ -60,9 +59,9 @@ impl Settings {
libraries: Some(libraries), libraries: Some(libraries),
remappings, remappings,
output_selection: Some(output_selection), output_selection: Some(output_selection),
via_ir: if via_ir { Some(true) } else { None },
optimizer, optimizer,
metadata, metadata,
via_ir: Some(true),
} }
} }
@@ -3,12 +3,8 @@
use serde::Deserialize; use serde::Deserialize;
use serde::Serialize; use serde::Serialize;
use crate::solc::pipeline::Pipeline as SolcPipeline;
/// The `solc --standard-json` expected output selection flag. /// The `solc --standard-json` expected output selection flag.
#[derive(Debug, Serialize, Deserialize, Clone, Copy, PartialEq, Eq, Hash)] #[derive(Debug, Serialize, Deserialize, Clone, Copy, PartialEq, Eq, Hash)]
#[allow(non_camel_case_types)]
#[allow(clippy::upper_case_acronyms)]
pub enum Flag { pub enum Flag {
/// The ABI JSON. /// The ABI JSON.
#[serde(rename = "abi")] #[serde(rename = "abi")]
@@ -46,15 +42,6 @@ pub enum Flag {
Assembly, Assembly,
} }
impl From<SolcPipeline> for Flag {
fn from(pipeline: SolcPipeline) -> Self {
match pipeline {
SolcPipeline::Yul => Self::Yul,
SolcPipeline::EVMLA => Self::EVMLA,
}
}
}
impl std::fmt::Display for Flag { impl std::fmt::Display for Flag {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
match self { match self {
@@ -7,8 +7,6 @@ use std::collections::HashSet;
use serde::Deserialize; use serde::Deserialize;
use serde::Serialize; use serde::Serialize;
use crate::solc::pipeline::Pipeline as SolcPipeline;
use self::flag::Flag as SelectionFlag; use self::flag::Flag as SelectionFlag;
/// The `solc --standard-json` output file selection. /// The `solc --standard-json` output file selection.
@@ -24,7 +22,7 @@ pub struct File {
impl File { impl File {
/// Creates the selection required by our compilation process. /// Creates the selection required by our compilation process.
pub fn new_required(pipeline: SolcPipeline) -> Self { pub fn new_required() -> Self {
Self { Self {
per_file: Some(HashSet::from_iter([SelectionFlag::AST])), per_file: Some(HashSet::from_iter([SelectionFlag::AST])),
per_contract: Some(HashSet::from_iter([ per_contract: Some(HashSet::from_iter([
@@ -32,14 +30,14 @@ impl File {
SelectionFlag::EVMDBC, SelectionFlag::EVMDBC,
SelectionFlag::MethodIdentifiers, SelectionFlag::MethodIdentifiers,
SelectionFlag::Metadata, SelectionFlag::Metadata,
SelectionFlag::from(pipeline), SelectionFlag::Yul,
])), ])),
} }
} }
/// Extends the user's output selection with flag required by our compilation process. /// Extends the user's output selection with flag required by our compilation process.
pub fn extend_with_required(&mut self, pipeline: SolcPipeline) -> &mut Self { pub fn extend_with_required(&mut self) -> &mut Self {
let required = Self::new_required(pipeline); let required = Self::new_required();
self.per_file self.per_file
.get_or_insert_with(HashSet::default) .get_or_insert_with(HashSet::default)
@@ -5,8 +5,6 @@ pub mod file;
use serde::Deserialize; use serde::Deserialize;
use serde::Serialize; use serde::Serialize;
use crate::solc::pipeline::Pipeline as SolcPipeline;
use self::file::File as FileSelection; use self::file::File as FileSelection;
/// The `solc --standard-json` output selection. /// The `solc --standard-json` output selection.
@@ -19,17 +17,17 @@ pub struct Selection {
impl Selection { impl Selection {
/// Creates the selection required by our compilation process. /// Creates the selection required by our compilation process.
pub fn new_required(pipeline: SolcPipeline) -> Self { pub fn new_required() -> Self {
Self { Self {
all: Some(FileSelection::new_required(pipeline)), all: Some(FileSelection::new_required()),
} }
} }
/// Extends the user's output selection with flag required by our compilation process. /// Extends the user's output selection with flag required by our compilation process.
pub fn extend_with_required(&mut self, pipeline: SolcPipeline) -> &mut Self { pub fn extend_with_required(&mut self) -> &mut Self {
self.all self.all
.get_or_insert_with(|| FileSelection::new_required(pipeline)) .get_or_insert_with(FileSelection::new_required)
.extend_with_required(pipeline); .extend_with_required();
self self
} }
} }
@@ -1,46 +0,0 @@
//! The `solc --standard-json` output contract EVM extra metadata.
pub mod recursive_function;
use serde::Deserialize;
use serde::Serialize;
use self::recursive_function::RecursiveFunction;
/// The `solc --standard-json` output contract EVM extra metadata.
#[derive(Debug, Default, Serialize, Deserialize, Clone)]
#[serde(rename_all = "camelCase")]
pub struct ExtraMetadata {
/// The list of recursive functions.
#[serde(default = "Vec::new")]
pub recursive_functions: Vec<RecursiveFunction>,
}
impl ExtraMetadata {
/// Returns the recursive function reference for the specified tag.
pub fn get(
&self,
block_key: &revive_llvm_context::PolkaVMFunctionBlockKey,
) -> Option<&RecursiveFunction> {
for function in self.recursive_functions.iter() {
match block_key.code_type {
revive_llvm_context::PolkaVMCodeType::Deploy => {
if let Some(creation_tag) = function.creation_tag {
if num::BigUint::from(creation_tag) == block_key.tag {
return Some(function);
}
}
}
revive_llvm_context::PolkaVMCodeType::Runtime => {
if let Some(runtime_tag) = function.runtime_tag {
if num::BigUint::from(runtime_tag) == block_key.tag {
return Some(function);
}
}
}
}
}
None
}
}
@@ -1,22 +0,0 @@
//! The `solc --standard-json` output contract EVM recursive function.
use serde::Deserialize;
use serde::Serialize;
/// The `solc --standard-json` output contract EVM recursive function.
#[derive(Debug, Serialize, Deserialize, Clone)]
#[serde(rename_all = "camelCase")]
pub struct RecursiveFunction {
/// The function name.
pub name: String,
/// The creation code function block tag.
pub creation_tag: Option<usize>,
/// The runtime code function block tag.
pub runtime_tag: Option<usize>,
/// The number of input arguments.
#[serde(rename = "totalParamSize")]
pub input_size: usize,
/// The number of output arguments.
#[serde(rename = "totalRetParamSize")]
pub output_size: usize,
}
@@ -1,27 +1,20 @@
//! The `solc --standard-json` output contract EVM data. //! The `solc --standard-json` output contract EVM data.
pub mod bytecode; pub mod bytecode;
pub mod extra_metadata;
use std::collections::BTreeMap; use std::collections::BTreeMap;
use serde::Deserialize; use serde::Deserialize;
use serde::Serialize; use serde::Serialize;
use crate::evmla::assembly::Assembly;
use self::bytecode::Bytecode; use self::bytecode::Bytecode;
use self::bytecode::DeployedBytecode; use self::bytecode::DeployedBytecode;
use self::extra_metadata::ExtraMetadata;
/// The `solc --standard-json` output contract EVM data. /// The `solc --standard-json` output contract EVM data.
/// It is replaced by PolkaVM data after compiling. /// It is replaced by PolkaVM data after compiling.
#[derive(Debug, Serialize, Deserialize, Clone)] #[derive(Debug, Serialize, Deserialize, Clone)]
#[serde(rename_all = "camelCase")] #[serde(rename_all = "camelCase")]
pub struct EVM { pub struct EVM {
/// The contract EVM legacy assembly code.
#[serde(rename = "legacyAssembly", skip_serializing_if = "Option::is_none")]
pub assembly: Option<Assembly>,
/// The contract PolkaVM assembly code. /// The contract PolkaVM assembly code.
#[serde(rename = "assembly", skip_serializing_if = "Option::is_none")] #[serde(rename = "assembly", skip_serializing_if = "Option::is_none")]
pub assembly_text: Option<String>, pub assembly_text: Option<String>,
@@ -37,9 +30,6 @@ pub struct EVM {
/// The contract function signatures. /// The contract function signatures.
#[serde(default, skip_serializing_if = "Option::is_none")] #[serde(default, skip_serializing_if = "Option::is_none")]
pub method_identifiers: Option<BTreeMap<String, String>>, pub method_identifiers: Option<BTreeMap<String, String>>,
/// The extra EVMLA metadata.
#[serde(default, skip_serializing_if = "Option::is_none")]
pub extra_metadata: Option<ExtraMetadata>,
} }
impl EVM { impl EVM {
@@ -33,12 +33,11 @@ impl Error {
/// Returns the `ecrecover` function usage warning. /// Returns the `ecrecover` function usage warning.
pub fn message_ecrecover(src: Option<&str>) -> Self { pub fn message_ecrecover(src: Option<&str>) -> Self {
let message = r#" let message = r#"
┌──────────────────────────────────────────────────────────────────────────────────────────────────┐ Warning: It looks like you are using 'ecrecover' to validate a signature of a user account.
│ Warning: It looks like you are using 'ecrecover' to validate a signature of a user account. │ Polkadot comes with native account abstraction support, therefore it is highly recommended NOT
│ Polkadot comes with native account abstraction support, therefore it is highly recommended NOT │ to rely on the fact that the account has an ECDSA private key attached to it since accounts might
│ to rely on the fact that the account has an ECDSA private key attached to it since accounts might│ implement other signature schemes.
│ implement other signature schemes. │ "#
└──────────────────────────────────────────────────────────────────────────────────────────────────┘"#
.to_owned(); .to_owned();
Self { Self {
@@ -55,16 +54,15 @@ impl Error {
/// Returns the `<address payable>`'s `send` and `transfer` methods usage error. /// Returns the `<address payable>`'s `send` and `transfer` methods usage error.
pub fn message_send_and_transfer(src: Option<&str>) -> Self { pub fn message_send_and_transfer(src: Option<&str>) -> Self {
let message = r#" let message = r#"
┌──────────────────────────────────────────────────────────────────────────────────────────────────┐ Warning: It looks like you are using '<address payable>.send/transfer(<X>)'.
│ Warning: It looks like you are using '<address payable>.send/transfer(<X>)' without providing │ Using '<address payable>.send/transfer(<X>)' is deprecated and strongly discouraged!
│ the gas amount. Such calls will fail depending on the pubdata costs. │ The resolc compiler uses a heuristic to detect '<address payable>.send/transfer(<X>)' calls,
│ This might be a false positive if you are using an interface (like IERC20) instead of the │ which disables call re-entrancy and supplies all remaining gas instead of the 2300 gas stipend.
│ native Solidity `send/transfer`. │ However, detection is not guaranteed. You are advised to carefully test this, employ
│ Please use 'payable(<address>).call{value: <X>}("")' instead, but be careful with the reentrancy │ re-entrancy guards or use the withdrawal pattern instead!
│ attack. `send` and `transfer` send limited amount of gas that prevents reentrancy, whereas │ Learn more on https://docs.soliditylang.org/en/latest/security-considerations.html#reentrancy
│ `<address>.call{value: <X>}` sends all gas to the callee. Learn more on │ and https://docs.soliditylang.org/en/latest/common-patterns.html#withdrawal-from-contracts
│ https://docs.soliditylang.org/en/latest/security-considerations.html#reentrancy │ "#
└──────────────────────────────────────────────────────────────────────────────────────────────────┘"#
.to_owned(); .to_owned();
Self { Self {
@@ -81,15 +79,14 @@ impl Error {
/// Returns the `extcodesize` instruction usage warning. /// Returns the `extcodesize` instruction usage warning.
pub fn message_extcodesize(src: Option<&str>) -> Self { pub fn message_extcodesize(src: Option<&str>) -> Self {
let message = r#" let message = r#"
┌──────────────────────────────────────────────────────────────────────────────────────────────────┐ Warning: Your code or one of its dependencies uses the 'extcodesize' instruction, which is
│ Warning: Your code or one of its dependencies uses the 'extcodesize' instruction, which is │ usually needed in the following cases:
│ usually needed in the following cases: │ 1. To detect whether an address belongs to a smart contract.
1. To detect whether an address belongs to a smart contract. │ 2. To detect whether the deploy code execution has finished.
│ 2. To detect whether the deploy code execution has finished. │ Polkadot comes with native account abstraction support (so smart contracts are just accounts
│ Polkadot comes with native account abstraction support (so smart contracts are just accounts │ coverned by code), and you should avoid differentiating between contracts and non-contract
│ coverned by code), and you should avoid differentiating between contracts and non-contract | addresses.
| addresses. │ "#
└──────────────────────────────────────────────────────────────────────────────────────────────────┘"#
.to_owned(); .to_owned();
Self { Self {
@@ -106,12 +103,11 @@ impl Error {
/// Returns the `origin` instruction usage warning. /// Returns the `origin` instruction usage warning.
pub fn message_tx_origin(src: Option<&str>) -> Self { pub fn message_tx_origin(src: Option<&str>) -> Self {
let message = r#" let message = r#"
┌──────────────────────────────────────────────────────────────────────────────────────────────────┐ Warning: You are checking for 'tx.origin' in your code, which might lead to unexpected behavior.
│ Warning: You are checking for 'tx.origin' in your code, which might lead to unexpected behavior. │ Polkadot comes with native account abstraction support, and therefore the initiator of a
│ Polkadot comes with native account abstraction support, and therefore the initiator of a │ transaction might be different from the contract calling your code. It is highly recommended NOT
│ transaction might be different from the contract calling your code. It is highly recommended NOT │ to rely on tx.origin, but use msg.sender instead.
│ to rely on tx.origin, but use msg.sender instead. │ "#
└──────────────────────────────────────────────────────────────────────────────────────────────────┘"#
.to_owned(); .to_owned();
Self { Self {
@@ -125,26 +121,6 @@ impl Error {
} }
} }
/// Returns the internal function pointer usage error.
pub fn message_internal_function_pointer(src: Option<&str>) -> Self {
let message = r#"
┌──────────────────────────────────────────────────────────────────────────────────────────────────┐
│ Error: Internal function pointers are not supported in EVM legacy assembly pipeline. │
│ Please use the Yul IR codegen instead. │
└──────────────────────────────────────────────────────────────────────────────────────────────────┘"#
.to_owned();
Self {
component: "general".to_owned(),
error_code: None,
formatted_message: message.clone(),
message,
severity: "error".to_owned(),
source_location: src.map(SourceLocation::from_str).and_then(Result::ok),
r#type: "Error".to_owned(),
}
}
/// Appends the contract path to the message.. /// Appends the contract path to the message..
pub fn push_contract_path(&mut self, path: &str) { pub fn push_contract_path(&mut self, path: &str) {
self.formatted_message self.formatted_message
@@ -10,12 +10,9 @@ use serde::Deserialize;
use serde::Serialize; use serde::Serialize;
use sha3::Digest; use sha3::Digest;
use crate::evmla::assembly::instruction::Instruction;
use crate::evmla::assembly::Assembly;
use crate::project::contract::ir::IR as ProjectContractIR; use crate::project::contract::ir::IR as ProjectContractIR;
use crate::project::contract::Contract as ProjectContract; use crate::project::contract::Contract as ProjectContract;
use crate::project::Project; use crate::project::Project;
use crate::solc::pipeline::Pipeline as SolcPipeline;
use crate::solc::version::Version as SolcVersion; use crate::solc::version::Version as SolcVersion;
use crate::warning::Warning; use crate::warning::Warning;
use crate::yul::lexer::Lexer; use crate::yul::lexer::Lexer;
@@ -53,14 +50,9 @@ impl Output {
&mut self, &mut self,
source_code_files: BTreeMap<String, String>, source_code_files: BTreeMap<String, String>,
libraries: BTreeMap<String, BTreeMap<String, String>>, libraries: BTreeMap<String, BTreeMap<String, String>>,
pipeline: SolcPipeline,
solc_version: &SolcVersion, solc_version: &SolcVersion,
debug_config: &revive_llvm_context::DebugConfig, debug_config: &revive_llvm_context::DebugConfig,
) -> anyhow::Result<Project> { ) -> anyhow::Result<Project> {
if let SolcPipeline::EVMLA = pipeline {
self.preprocess_dependencies()?;
}
let files = match self.contracts.as_ref() { let files = match self.contracts.as_ref() {
Some(files) => files, Some(files) => files,
None => match &self.errors { None => match &self.errors {
@@ -76,8 +68,6 @@ impl Output {
for (name, contract) in contracts.iter() { for (name, contract) in contracts.iter() {
let full_path = format!("{path}:{name}"); let full_path = format!("{path}:{name}");
let source = match pipeline {
SolcPipeline::Yul => {
let ir_optimized = match contract.ir_optimized.to_owned() { let ir_optimized = match contract.ir_optimized.to_owned() {
Some(ir_optimized) => ir_optimized, Some(ir_optimized) => ir_optimized,
None => continue, None => continue,
@@ -93,21 +83,7 @@ impl Output {
anyhow::anyhow!("Contract `{}` parsing error: {:?}", full_path, error) anyhow::anyhow!("Contract `{}` parsing error: {:?}", full_path, error)
})?; })?;
ProjectContractIR::new_yul(ir_optimized.to_owned(), object) let source = ProjectContractIR::new_yul(ir_optimized.to_owned(), object);
}
SolcPipeline::EVMLA => {
let evm = contract.evm.as_ref();
let assembly = match evm.and_then(|evm| evm.assembly.to_owned()) {
Some(assembly) => assembly.to_owned(),
None => continue,
};
let extra_metadata = evm
.and_then(|evm| evm.extra_metadata.to_owned())
.unwrap_or_default();
ProjectContractIR::new_evmla(assembly, extra_metadata)
}
};
let source_code = source_code_files let source_code = source_code_files
.get(path.as_str()) .get(path.as_str())
@@ -133,12 +109,7 @@ impl Output {
} }
/// Traverses the AST and returns the list of additional errors and warnings. /// Traverses the AST and returns the list of additional errors and warnings.
pub fn preprocess_ast( pub fn preprocess_ast(&mut self, suppressed_warnings: &[Warning]) -> anyhow::Result<()> {
&mut self,
version: &SolcVersion,
pipeline: SolcPipeline,
suppressed_warnings: &[Warning],
) -> anyhow::Result<()> {
let sources = match self.sources.as_ref() { let sources = match self.sources.as_ref() {
Some(sources) => sources, Some(sources) => sources,
None => return Ok(()), None => return Ok(()),
@@ -147,8 +118,7 @@ impl Output {
let mut messages = Vec::new(); let mut messages = Vec::new();
for (path, source) in sources.iter() { for (path, source) in sources.iter() {
if let Some(ast) = source.ast.as_ref() { if let Some(ast) = source.ast.as_ref() {
let mut polkavm_messages = let mut polkavm_messages = Source::get_messages(ast, suppressed_warnings);
Source::get_messages(ast, version, pipeline, suppressed_warnings);
for message in polkavm_messages.iter_mut() { for message in polkavm_messages.iter_mut() {
message.push_contract_path(path.as_str()); message.push_contract_path(path.as_str());
} }
@@ -165,83 +135,4 @@ impl Output {
Ok(()) Ok(())
} }
/// The pass, which replaces with dependency indexes with actual data.
fn preprocess_dependencies(&mut self) -> anyhow::Result<()> {
let files = match self.contracts.as_mut() {
Some(files) => files,
None => return Ok(()),
};
let mut hash_path_mapping = BTreeMap::new();
for (path, contracts) in files.iter() {
for (name, contract) in contracts.iter() {
let full_path = format!("{path}:{name}");
let hash = match contract
.evm
.as_ref()
.and_then(|evm| evm.assembly.as_ref())
.map(|assembly| assembly.keccak256())
{
Some(hash) => hash,
None => continue,
};
hash_path_mapping.insert(hash, full_path);
}
}
for (path, contracts) in files.iter_mut() {
for (name, contract) in contracts.iter_mut() {
let assembly = match contract.evm.as_mut().and_then(|evm| evm.assembly.as_mut()) {
Some(assembly) => assembly,
None => continue,
};
let full_path = format!("{path}:{name}");
Self::preprocess_dependency_level(
full_path.as_str(),
assembly,
&hash_path_mapping,
)?;
}
}
Ok(())
}
/// Preprocesses an assembly JSON structure dependency data map.
fn preprocess_dependency_level(
full_path: &str,
assembly: &mut Assembly,
hash_path_mapping: &BTreeMap<String, String>,
) -> anyhow::Result<()> {
assembly.set_full_path(full_path.to_owned());
let deploy_code_index_path_mapping =
assembly.deploy_dependencies_pass(full_path, hash_path_mapping)?;
if let Some(deploy_code_instructions) = assembly.code.as_deref_mut() {
Instruction::replace_data_aliases(
deploy_code_instructions,
&deploy_code_index_path_mapping,
)?;
};
let runtime_code_index_path_mapping =
assembly.runtime_dependencies_pass(full_path, hash_path_mapping)?;
if let Some(runtime_code_instructions) = assembly
.data
.as_mut()
.and_then(|data_map| data_map.get_mut("0"))
.and_then(|data| data.get_assembly_mut())
.and_then(|assembly| assembly.code.as_deref_mut())
{
Instruction::replace_data_aliases(
runtime_code_instructions,
&runtime_code_index_path_mapping,
)?;
}
Ok(())
}
} }
@@ -3,9 +3,7 @@
use serde::Deserialize; use serde::Deserialize;
use serde::Serialize; use serde::Serialize;
use crate::solc::pipeline::Pipeline as SolcPipeline;
use crate::solc::standard_json::output::error::Error as SolcStandardJsonOutputError; use crate::solc::standard_json::output::error::Error as SolcStandardJsonOutputError;
use crate::solc::version::Version as SolcVersion;
use crate::warning::Warning; use crate::warning::Warning;
/// The `solc --standard-json` output source. /// The `solc --standard-json` output source.
@@ -132,37 +130,9 @@ impl Source {
)) ))
} }
/// Checks the AST node for the internal function pointers value usage.
pub fn check_internal_function_pointer(
ast: &serde_json::Value,
) -> Option<SolcStandardJsonOutputError> {
let ast = ast.as_object()?;
if ast.get("nodeType")?.as_str()? != "VariableDeclaration" {
return None;
}
let type_descriptions = ast.get("typeDescriptions")?.as_object()?;
if !type_descriptions
.get("typeIdentifier")?
.as_str()?
.contains("function_internal")
{
return None;
}
Some(
SolcStandardJsonOutputError::message_internal_function_pointer(
ast.get("src")?.as_str(),
),
)
}
/// Returns the list of messages for some specific parts of the AST. /// Returns the list of messages for some specific parts of the AST.
pub fn get_messages( pub fn get_messages(
ast: &serde_json::Value, ast: &serde_json::Value,
version: &SolcVersion,
pipeline: SolcPipeline,
suppressed_warnings: &[Warning], suppressed_warnings: &[Warning],
) -> Vec<SolcStandardJsonOutputError> { ) -> Vec<SolcStandardJsonOutputError> {
let mut messages = Vec::new(); let mut messages = Vec::new();
@@ -189,31 +159,16 @@ impl Source {
messages.push(message); messages.push(message);
} }
} }
if SolcPipeline::EVMLA == pipeline && version.l2_revision.is_none() {
if let Some(message) = Self::check_internal_function_pointer(ast) {
messages.push(message);
}
}
match ast { match ast {
serde_json::Value::Array(array) => { serde_json::Value::Array(array) => {
for element in array.iter() { for element in array.iter() {
messages.extend(Self::get_messages( messages.extend(Self::get_messages(element, suppressed_warnings));
element,
version,
pipeline,
suppressed_warnings,
));
} }
} }
serde_json::Value::Object(object) => { serde_json::Value::Object(object) => {
for (_key, value) in object.iter() { for (_key, value) in object.iter() {
messages.extend(Self::get_messages( messages.extend(Self::get_messages(value, suppressed_warnings));
value,
version,
pipeline,
suppressed_warnings,
));
} }
} }
_ => {} _ => {}
+22
View File
@@ -36,4 +36,26 @@ impl Version {
l2_revision: None, l2_revision: None,
} }
} }
pub fn validate(self, include_paths: &[String]) -> anyhow::Result<Self> {
if self.default < super::FIRST_SUPPORTED_VERSION {
anyhow::bail!(
"`solc` versions <{} are not supported, found {}",
super::FIRST_SUPPORTED_VERSION,
self.default
);
}
if self.default > super::LAST_SUPPORTED_VERSION {
anyhow::bail!(
"`solc` versions >{} are not supported, found {}",
super::LAST_SUPPORTED_VERSION,
self.default
);
}
if !include_paths.is_empty() && self.default < super::FIRST_INCLUDE_PATH_VERSION {
anyhow::bail!("--include-path is not supported in solc {}", self.default);
}
Ok(self)
}
} }
+15 -38
View File
@@ -8,7 +8,6 @@ use std::sync::Mutex;
use once_cell::sync::Lazy; use once_cell::sync::Lazy;
use crate::project::Project; use crate::project::Project;
use crate::solc::pipeline::Pipeline as SolcPipeline;
use crate::solc::solc_compiler::SolcCompiler; use crate::solc::solc_compiler::SolcCompiler;
use crate::solc::standard_json::input::settings::optimizer::Optimizer as SolcStandardJsonInputSettingsOptimizer; use crate::solc::standard_json::input::settings::optimizer::Optimizer as SolcStandardJsonInputSettingsOptimizer;
use crate::solc::standard_json::input::settings::selection::Selection as SolcStandardJsonInputSettingsSelection; use crate::solc::standard_json::input::settings::selection::Selection as SolcStandardJsonInputSettingsSelection;
@@ -30,8 +29,8 @@ const DEBUG_CONFIG: revive_llvm_context::DebugConfig =
#[derive(Hash, PartialEq, Eq)] #[derive(Hash, PartialEq, Eq)]
struct CachedBlob { struct CachedBlob {
contract_name: String, contract_name: String,
solidity: String,
solc_optimizer_enabled: bool, solc_optimizer_enabled: bool,
pipeline: SolcPipeline,
} }
/// Checks if the required executables are present in `${PATH}`. /// Checks if the required executables are present in `${PATH}`.
@@ -54,17 +53,9 @@ pub fn build_solidity(
sources: BTreeMap<String, String>, sources: BTreeMap<String, String>,
libraries: BTreeMap<String, BTreeMap<String, String>>, libraries: BTreeMap<String, BTreeMap<String, String>>,
remappings: Option<BTreeSet<String>>, remappings: Option<BTreeSet<String>>,
pipeline: SolcPipeline,
optimizer_settings: revive_llvm_context::OptimizerSettings, optimizer_settings: revive_llvm_context::OptimizerSettings,
) -> anyhow::Result<SolcStandardJsonOutput> { ) -> anyhow::Result<SolcStandardJsonOutput> {
build_solidity_with_options( build_solidity_with_options(sources, libraries, remappings, optimizer_settings, true)
sources,
libraries,
remappings,
pipeline,
optimizer_settings,
true,
)
} }
/// Builds the Solidity project and returns the standard JSON output. /// Builds the Solidity project and returns the standard JSON output.
@@ -74,7 +65,6 @@ pub fn build_solidity_with_options(
sources: BTreeMap<String, String>, sources: BTreeMap<String, String>,
libraries: BTreeMap<String, BTreeMap<String, String>>, libraries: BTreeMap<String, BTreeMap<String, String>>,
remappings: Option<BTreeSet<String>>, remappings: Option<BTreeSet<String>>,
pipeline: SolcPipeline,
optimizer_settings: revive_llvm_context::OptimizerSettings, optimizer_settings: revive_llvm_context::OptimizerSettings,
solc_optimizer_enabled: bool, solc_optimizer_enabled: bool,
) -> anyhow::Result<SolcStandardJsonOutput> { ) -> anyhow::Result<SolcStandardJsonOutput> {
@@ -93,7 +83,7 @@ pub fn build_solidity_with_options(
sources.clone(), sources.clone(),
libraries.clone(), libraries.clone(),
remappings, remappings,
SolcStandardJsonInputSettingsSelection::new_required(pipeline), SolcStandardJsonInputSettingsSelection::new_required(),
SolcStandardJsonInputSettingsOptimizer::new( SolcStandardJsonInputSettingsOptimizer::new(
solc_optimizer_enabled, solc_optimizer_enabled,
None, None,
@@ -101,14 +91,12 @@ pub fn build_solidity_with_options(
false, false,
), ),
None, None,
pipeline == SolcPipeline::Yul,
None, None,
)?; )?;
let mut output = solc.standard_json(input, pipeline, None, vec![], None)?; let mut output = solc.standard_json(input, None, vec![], None)?;
let project = let project = output.try_to_project(sources, libraries, &solc_version, &DEBUG_CONFIG)?;
output.try_to_project(sources, libraries, pipeline, &solc_version, &DEBUG_CONFIG)?;
let build: crate::Build = project.compile(optimizer_settings, false, DEBUG_CONFIG)?; let build: crate::Build = project.compile(optimizer_settings, false, DEBUG_CONFIG)?;
build.write_to_standard_json(&mut output, &solc_version)?; build.write_to_standard_json(&mut output, &solc_version)?;
@@ -121,7 +109,6 @@ pub fn build_solidity_with_options_evm(
sources: BTreeMap<String, String>, sources: BTreeMap<String, String>,
libraries: BTreeMap<String, BTreeMap<String, String>>, libraries: BTreeMap<String, BTreeMap<String, String>>,
remappings: Option<BTreeSet<String>>, remappings: Option<BTreeSet<String>>,
pipeline: SolcPipeline,
solc_optimizer_enabled: bool, solc_optimizer_enabled: bool,
) -> anyhow::Result<BTreeMap<String, (Bytecode, DeployedBytecode)>> { ) -> anyhow::Result<BTreeMap<String, (Bytecode, DeployedBytecode)>> {
check_dependencies(); check_dependencies();
@@ -139,7 +126,7 @@ pub fn build_solidity_with_options_evm(
sources.clone(), sources.clone(),
libraries.clone(), libraries.clone(),
remappings, remappings,
SolcStandardJsonInputSettingsSelection::new_required(pipeline), SolcStandardJsonInputSettingsSelection::new_required(),
SolcStandardJsonInputSettingsOptimizer::new( SolcStandardJsonInputSettingsOptimizer::new(
solc_optimizer_enabled, solc_optimizer_enabled,
None, None,
@@ -147,11 +134,10 @@ pub fn build_solidity_with_options_evm(
false, false,
), ),
None, None,
pipeline == SolcPipeline::Yul,
None, None,
)?; )?;
let mut output = solc.standard_json(input, pipeline, None, vec![], None)?; let mut output = solc.standard_json(input, None, vec![], None)?;
let mut contracts = BTreeMap::new(); let mut contracts = BTreeMap::new();
if let Some(files) = output.contracts.as_mut() { if let Some(files) = output.contracts.as_mut() {
@@ -176,7 +162,6 @@ pub fn build_solidity_with_options_evm(
pub fn build_solidity_and_detect_missing_libraries( pub fn build_solidity_and_detect_missing_libraries(
sources: BTreeMap<String, String>, sources: BTreeMap<String, String>,
libraries: BTreeMap<String, BTreeMap<String, String>>, libraries: BTreeMap<String, BTreeMap<String, String>>,
pipeline: SolcPipeline,
) -> anyhow::Result<SolcStandardJsonOutput> { ) -> anyhow::Result<SolcStandardJsonOutput> {
check_dependencies(); check_dependencies();
@@ -193,17 +178,15 @@ pub fn build_solidity_and_detect_missing_libraries(
sources.clone(), sources.clone(),
libraries.clone(), libraries.clone(),
None, None,
SolcStandardJsonInputSettingsSelection::new_required(pipeline), SolcStandardJsonInputSettingsSelection::new_required(),
SolcStandardJsonInputSettingsOptimizer::new(true, None, &solc_version.default, false), SolcStandardJsonInputSettingsOptimizer::new(true, None, &solc_version.default, false),
None, None,
pipeline == SolcPipeline::Yul,
None, None,
)?; )?;
let mut output = solc.standard_json(input, pipeline, None, vec![], None)?; let mut output = solc.standard_json(input, None, vec![], None)?;
let project = let project = output.try_to_project(sources, libraries, &solc_version, &DEBUG_CONFIG)?;
output.try_to_project(sources, libraries, pipeline, &solc_version, &DEBUG_CONFIG)?;
let missing_libraries = project.get_missing_libraries(); let missing_libraries = project.get_missing_libraries();
missing_libraries.write_to_standard_json(&mut output, &solc.version()?)?; missing_libraries.write_to_standard_json(&mut output, &solc.version()?)?;
@@ -234,7 +217,6 @@ pub fn check_solidity_warning(
source_code: &str, source_code: &str,
warning_substring: &str, warning_substring: &str,
libraries: BTreeMap<String, BTreeMap<String, String>>, libraries: BTreeMap<String, BTreeMap<String, String>>,
pipeline: SolcPipeline,
skip_for_revive_edition: bool, skip_for_revive_edition: bool,
suppressed_warnings: Option<Vec<Warning>>, suppressed_warnings: Option<Vec<Warning>>,
) -> anyhow::Result<bool> { ) -> anyhow::Result<bool> {
@@ -253,14 +235,13 @@ pub fn check_solidity_warning(
sources.clone(), sources.clone(),
libraries, libraries,
None, None,
SolcStandardJsonInputSettingsSelection::new_required(pipeline), SolcStandardJsonInputSettingsSelection::new_required(),
SolcStandardJsonInputSettingsOptimizer::new(true, None, &solc_version.default, false), SolcStandardJsonInputSettingsOptimizer::new(true, None, &solc_version.default, false),
None, None,
pipeline == SolcPipeline::Yul,
suppressed_warnings, suppressed_warnings,
)?; )?;
let output = solc.standard_json(input, pipeline, None, vec![], None)?; let output = solc.standard_json(input, None, vec![], None)?;
let contains_warning = output let contains_warning = output
.errors .errors
.ok_or_else(|| anyhow::anyhow!("Solidity compiler messages not found"))? .ok_or_else(|| anyhow::anyhow!("Solidity compiler messages not found"))?
@@ -273,7 +254,7 @@ pub fn check_solidity_warning(
/// Compile the blob of `contract_name` found in given `source_code`. /// Compile the blob of `contract_name` found in given `source_code`.
/// The `solc` optimizer will be enabled /// The `solc` optimizer will be enabled
pub fn compile_blob(contract_name: &str, source_code: &str) -> Vec<u8> { pub fn compile_blob(contract_name: &str, source_code: &str) -> Vec<u8> {
compile_blob_with_options(contract_name, source_code, true, SolcPipeline::Yul) compile_blob_with_options(contract_name, source_code, true)
} }
/// Compile the EVM bin-runtime of `contract_name` found in given `source_code`. /// Compile the EVM bin-runtime of `contract_name` found in given `source_code`.
@@ -298,10 +279,9 @@ fn compile_evm(
solc_optimizer_enabled: bool, solc_optimizer_enabled: bool,
runtime: bool, runtime: bool,
) -> Vec<u8> { ) -> Vec<u8> {
let pipeline = SolcPipeline::Yul;
let id = CachedBlob { let id = CachedBlob {
contract_name: contract_name.to_owned(), contract_name: contract_name.to_owned(),
pipeline, solidity: source_code.to_owned(),
solc_optimizer_enabled, solc_optimizer_enabled,
}; };
@@ -319,7 +299,6 @@ fn compile_evm(
[(file_name.into(), source_code.into())].into(), [(file_name.into(), source_code.into())].into(),
Default::default(), Default::default(),
None, None,
pipeline,
solc_optimizer_enabled, solc_optimizer_enabled,
) )
.expect("source should compile"); .expect("source should compile");
@@ -343,12 +322,11 @@ pub fn compile_blob_with_options(
contract_name: &str, contract_name: &str,
source_code: &str, source_code: &str,
solc_optimizer_enabled: bool, solc_optimizer_enabled: bool,
pipeline: SolcPipeline,
) -> Vec<u8> { ) -> Vec<u8> {
let id = CachedBlob { let id = CachedBlob {
contract_name: contract_name.to_owned(), contract_name: contract_name.to_owned(),
solidity: source_code.to_owned(),
solc_optimizer_enabled, solc_optimizer_enabled,
pipeline,
}; };
if let Some(blob) = PVM_BLOB_CACHE.lock().unwrap().get(&id) { if let Some(blob) = PVM_BLOB_CACHE.lock().unwrap().get(&id) {
@@ -360,7 +338,6 @@ pub fn compile_blob_with_options(
[(file_name.into(), source_code.into())].into(), [(file_name.into(), source_code.into())].into(),
Default::default(), Default::default(),
None, None,
pipeline,
revive_llvm_context::OptimizerSettings::cycles(), revive_llvm_context::OptimizerSettings::cycles(),
solc_optimizer_enabled, solc_optimizer_enabled,
) )
@@ -0,0 +1,66 @@
<?xml version="1.0" encoding="UTF-8"?>
<testsuites name="jest tests" tests="27" failures="0" errors="0" time="2.146">
<testsuite name="Run with --yul by default" errors="0" failures="0" skipped="1" timestamp="2024-10-24T17:08:50" time="1.508" tests="6">
<testcase classname="Run with --yul by default Valid command exit code = 0" name="Run with --yul by default Valid command exit code = 0" time="0.003">
</testcase>
<testcase classname="Run with --yul by default --yul output is presented" name="Run with --yul by default --yul output is presented" time="0">
</testcase>
<testcase classname="Run with --yul by default solc exit code == resolc exit code" name="Run with --yul by default solc exit code == resolc exit code" time="0">
<skipped/>
</testcase>
<testcase classname="Run with --yul by default run invalid: resolc --yul" name="Run with --yul by default run invalid: resolc --yul" time="0.001">
</testcase>
<testcase classname="Run with --yul by default Invalid command exit code = 1" name="Run with --yul by default Invalid command exit code = 1" time="0">
</testcase>
<testcase classname="Run with --yul by default Invalid solc exit code == Invalid resolc exit code" name="Run with --yul by default Invalid solc exit code == Invalid resolc exit code" time="0.041">
</testcase>
</testsuite>
<testsuite name="Run with --asm by default" errors="0" failures="0" skipped="0" timestamp="2024-10-24T17:08:50" time="1.512" tests="6">
<testcase classname="Run with --asm by default Valid command exit code = 0" name="Run with --asm by default Valid command exit code = 0" time="0.002">
</testcase>
<testcase classname="Run with --asm by default --asm output is presented" name="Run with --asm by default --asm output is presented" time="0.001">
</testcase>
<testcase classname="Run with --asm by default solc exit code == resolc exit code" name="Run with --asm by default solc exit code == resolc exit code" time="0.044">
</testcase>
<testcase classname="Run with --asm by default run invalid: resolc --asm" name="Run with --asm by default run invalid: resolc --asm" time="0">
</testcase>
<testcase classname="Run with --asm by default Invalid command exit code = 1" name="Run with --asm by default Invalid command exit code = 1" time="0.001">
</testcase>
<testcase classname="Run with --asm by default Invalid solc exit code == Invalid resolc exit code" name="Run with --asm by default Invalid solc exit code == Invalid resolc exit code" time="0.04">
</testcase>
</testsuite>
<testsuite name="Run resolc without any options" errors="0" failures="0" skipped="2" timestamp="2024-10-24T17:08:50" time="2.016" tests="15">
<testcase classname="Run resolc without any options Info with help is presented" name="Run resolc without any options Info with help is presented" time="0.002">
</testcase>
<testcase classname="Run resolc without any options Exit code = 1" name="Run resolc without any options Exit code = 1" time="0">
</testcase>
<testcase classname="Run resolc without any options solc exit code == resolc exit code" name="Run resolc without any options solc exit code == resolc exit code" time="0.044">
</testcase>
<testcase classname="Default run a command from the help Compiler run successful" name="Default run a command from the help Compiler run successful" time="0">
</testcase>
<testcase classname="Default run a command from the help Exit code = 0" name="Default run a command from the help Exit code = 0" time="0.001">
</testcase>
<testcase classname="Default run a command from the help Output dir is created" name="Default run a command from the help Output dir is created" time="0">
</testcase>
<testcase classname="Default run a command from the help Output file is created" name="Default run a command from the help Output file is created" time="0">
<skipped/>
</testcase>
<testcase classname="Default run a command from the help the output file is not empty" name="Default run a command from the help the output file is not empty" time="0">
</testcase>
<testcase classname="Default run a command from the help No &apos;Error&apos;/&apos;Warning&apos;/&apos;Fail&apos; in the output" name="Default run a command from the help No &apos;Error&apos;/&apos;Warning&apos;/&apos;Fail&apos; in the output" time="0">
</testcase>
<testcase classname="Default run a command from the help Compiler run successful" name="Default run a command from the help Compiler run successful" time="0.001">
</testcase>
<testcase classname="Default run a command from the help Exit code = 0" name="Default run a command from the help Exit code = 0" time="0">
</testcase>
<testcase classname="Default run a command from the help Output dir is created" name="Default run a command from the help Output dir is created" time="0">
</testcase>
<testcase classname="Default run a command from the help Output files are created" name="Default run a command from the help Output files are created" time="0">
<skipped/>
</testcase>
<testcase classname="Default run a command from the help the output files are not empty" name="Default run a command from the help the output files are not empty" time="0.003">
</testcase>
<testcase classname="Default run a command from the help No &apos;Error&apos;/&apos;Warning&apos;/&apos;Fail&apos; in the output" name="Default run a command from the help No &apos;Error&apos;/&apos;Warning&apos;/&apos;Fail&apos; in the output" time="0">
</testcase>
</testsuite>
</testsuites>
@@ -10,7 +10,7 @@ describe("Run resolc without any options", () => {
const result = executeCommand(command); const result = executeCommand(command);
it("Info with help is presented", () => { it("Info with help is presented", () => {
expect(result.output).toMatch(/(No input sources specified|Error(s) found.)/i); expect(result.output).toMatch(/(Usage: resolc)/i);
}); });
it("Exit code = 1", () => { it("Exit code = 1", () => {
@@ -28,7 +28,7 @@ describe("Run resolc without any options", () => {
//#1713 //#1713
describe("Default run a command from the help", () => { describe("Default run a command from the help", () => {
const command = `resolc ${paths.pathToBasicSolContract} -O3 --bin --output-dir "${paths.pathToOutputDir}"`; // potential issue on resolc with full path on Windows cmd const command = `resolc ${paths.pathToBasicSolContract} --overwrite -O3 --bin --output-dir "${paths.pathToOutputDir}"`; // potential issue on resolc with full path on Windows cmd
const result = executeCommand(command); const result = executeCommand(command);
it("Compiler run successful", () => { it("Compiler run successful", () => {
@@ -54,7 +54,7 @@ describe("Default run a command from the help", () => {
//#1818 //#1818
describe("Default run a command from the help", () => { describe("Default run a command from the help", () => {
const command = `resolc ${paths.pathToBasicSolContract} -O3 --bin --asm --output-dir "${paths.pathToOutputDir}"`; // potential issue on resolc with full path on Windows cmd const command = `resolc ${paths.pathToBasicSolContract} --overwrite -O3 --bin --asm --output-dir "${paths.pathToOutputDir}"`; // potential issue on resolc with full path on Windows cmd
const result = executeCommand(command); const result = executeCommand(command);
it("Compiler run successful", () => { it("Compiler run successful", () => {
@@ -81,8 +81,8 @@ describe("Default run a command from the help", () => {
describe("Run resolc with source debug information", () => { describe("Run resolc with source debug information", () => {
const commands = [ const commands = [
`resolc -g ${paths.pathToBasicSolContract} --bin --asm --output-dir "${paths.pathToOutputDir}"`, `resolc -g ${paths.pathToBasicSolContract} --overwrite --bin --asm --output-dir "${paths.pathToOutputDir}"`,
`resolc --disable-solc-optimizer -g ${paths.pathToBasicSolContract} --bin --asm --output-dir "${paths.pathToOutputDir}"` `resolc --disable-solc-optimizer -g ${paths.pathToBasicSolContract} --overwrite --bin --asm --output-dir "${paths.pathToOutputDir}"`
]; // potential issue on resolc with full path on Windows cmd`; ]; // potential issue on resolc with full path on Windows cmd`;
for (var idx in commands) { for (var idx in commands) {
@@ -114,8 +114,8 @@ describe("Run resolc with source debug information", () => {
describe("Run resolc with source debug information, check LLVM debug-info", () => { describe("Run resolc with source debug information, check LLVM debug-info", () => {
const commands = [ const commands = [
`resolc -g ${paths.pathToBasicSolContract} --debug-output-dir="${paths.pathToOutputDir}"`, `resolc -g ${paths.pathToBasicSolContract} --overwrite --debug-output-dir="${paths.pathToOutputDir}"`,
`resolc -g --disable-solc-optimizer ${paths.pathToBasicSolContract} --debug-output-dir="${paths.pathToOutputDir}"` `resolc -g --disable-solc-optimizer ${paths.pathToBasicSolContract} --overwrite --debug-output-dir="${paths.pathToOutputDir}"`
]; // potential issue on resolc with full path on Windows cmd`; ]; // potential issue on resolc with full path on Windows cmd`;
for (var idx in commands) { for (var idx in commands) {
@@ -0,0 +1,153 @@
import { executeCommand } from "../src/helper";
import { paths } from '../src/entities';
describe("Set of --combined-json tests", () => {
const zksolcCommand = 'zksolc';
const solcCommand = 'solc';
const json_args: string[] = [`abi`, `hashes`, `metadata`, `devdoc`, `userdoc`, `storage-layout`, `ast`, `asm`, `bin`, `bin-runtime`];
//id1742:I
describe(`Run ${zksolcCommand} with just --combined-json`, () => {
const args = [`--combined-json`];
const result = executeCommand(zksolcCommand, args);
it("Valid command exit code = 1", () => {
expect(result.exitCode).toBe(1);
});
it("--combined-json error is presented", () => {
expect(result.output).toMatch(/(requires a value but none was supplied)/i);
});
it("solc exit code == zksolc exit code", () => {
const solcResult = executeCommand(solcCommand, args);
expect(solcResult.exitCode).toBe(result.exitCode);
});
});
//id1742:II
describe(`Run ${zksolcCommand} with Sol contract and --combined-json`, () => {
const args = [`${paths.pathToBasicSolContract}`, `--combined-json`];
const result = executeCommand(zksolcCommand, args);
it("Valid command exit code = 1", () => {
expect(result.exitCode).toBe(1);
});
it("--combined-json error is presented", () => {
expect(result.output).toMatch(/(requires a value but none was supplied)/i);
});
it("solc exit code == zksolc exit code", () => {
const solcResult = executeCommand(solcCommand, args);
expect(solcResult.exitCode).toBe(result.exitCode);
});
});
//id1742:III
for (let i = 0; i < json_args.length; i++) {
describe(`Run ${zksolcCommand} with Sol, --combined-json and ARG: ${json_args[i]}`, () => {
const args = [`${paths.pathToBasicSolContract}`, `--combined-json`, `${json_args[i]}`];
const result = executeCommand(zksolcCommand, args);
it("Valid command exit code = 0", () => {
expect(result.exitCode).toBe(0);
});
it("--combined-json error is presented", () => {
expect(result.output).toMatch(/(contracts)/i);
});
it("solc exit code == zksolc exit code", () => {
const solcResult = executeCommand(solcCommand, args);
expect(solcResult.exitCode).toBe(result.exitCode);
});
});
}
//id1829:I
for (let i = 0; i < json_args.length; i++) {
describe(`Run ${zksolcCommand} with Sol, --combined-json and wrong ARG: --${json_args[i]}`, () => {
const args = [`${paths.pathToBasicSolContract}`, `--combined-json`, `--${json_args[i]}`];
const result = executeCommand(zksolcCommand, args);
it("Valid command exit code = 1", () => {
expect(result.exitCode).toBe(1);
});
it("--combined-json error is presented", () => {
expect(result.output).toMatch(/(Invalid option|error)/i);
});
it("solc exit code == zksolc exit code", () => {
const solcResult = executeCommand(solcCommand, args);
expect(solcResult.exitCode).toBe(result.exitCode);
});
});
}
//id1829:II
for (let i = 0; i < json_args.length; i++) {
describe(`Run ${zksolcCommand} with Sol, --combined-json and multiple ARG: ${json_args[i]} ${json_args[i]}`, () => {
const args = [`${paths.pathToBasicSolContract}`, `--combined-json`, `${json_args[i]}`, `${json_args[i]}`];
const result = executeCommand(zksolcCommand, args);
xit("Valid command exit code = 1", () => {
expect(result.exitCode).toBe(1);
});
it("--combined-json error is presented", () => {
expect(result.output).toMatch(/(No such file or directory|cannot find the file specified)/i); // Hopefully we should have more precise message here!
});
xit("solc exit code == zksolc exit code", () => {
const solcResult = executeCommand(solcCommand, args);
expect(solcResult.exitCode).toBe(result.exitCode);
});
});
}
//id1829:III
for (let i = 0; i < json_args.length; i++) {
describe(`Run ${zksolcCommand} with Sol, and multiple (--combined-json ${json_args[i]})`, () => {
const args = [`${paths.pathToBasicSolContract}`, `--combined-json`, `${json_args[i]}`, `--combined-json`, `${json_args[i]}`];
const result = executeCommand(zksolcCommand, args);
it("Valid command exit code = 1", () => {
expect(result.exitCode).toBe(1);
});
it("--combined-json error is presented", () => {
expect(result.output).toMatch(/(cannot be used multiple times)/i);
});
it("solc exit code == zksolc exit code", () => {
const solcResult = executeCommand(solcCommand, args);
expect(solcResult.exitCode).toBe(result.exitCode);
});
});
}
//id1830
for (let i = 0; i < json_args.length; i++) {
describe(`Run ${zksolcCommand} with Yul, and --combined-json ${json_args[i]}`, () => {
const args = [`${paths.pathToBasicYulContract}`, `--combined-json`, `${json_args[i]}`];
const result = executeCommand(zksolcCommand, args);
it("Valid command exit code = 1", () => {
expect(result.exitCode).toBe(1);
});
it("--combined-json error is presented", () => {
expect(result.output).toMatch(/(ParserError: Expected identifier)/i);
});
asd
it("solc exit code == zksolc exit code", () => {
const solcResult = executeCommand(solcCommand, args);
expect(solcResult.exitCode).toBe(result.exitCode);
});
});
}
});
@@ -4,8 +4,6 @@
use std::collections::BTreeMap; use std::collections::BTreeMap;
use crate::solc::pipeline::Pipeline as SolcPipeline;
pub const MAIN_CODE: &str = r#" pub const MAIN_CODE: &str = r#"
// SPDX-License-Identifier: MIT // SPDX-License-Identifier: MIT
@@ -51,7 +49,6 @@ fn default() {
sources, sources,
BTreeMap::new(), BTreeMap::new(),
None, None,
SolcPipeline::Yul,
revive_llvm_context::OptimizerSettings::cycles(), revive_llvm_context::OptimizerSettings::cycles(),
) )
.expect("Build failure"); .expect("Build failure");
-74
View File
@@ -5,8 +5,6 @@
use std::collections::BTreeMap; use std::collections::BTreeMap;
use crate::solc::pipeline::Pipeline as SolcPipeline;
#[test] #[test]
fn yul() { fn yul() {
let source_code = r#" let source_code = r#"
@@ -27,7 +25,6 @@ contract Test {
sources, sources,
BTreeMap::new(), BTreeMap::new(),
None, None,
SolcPipeline::Yul,
revive_llvm_context::OptimizerSettings::cycles(), revive_llvm_context::OptimizerSettings::cycles(),
) )
.expect("Test failure"); .expect("Test failure");
@@ -45,75 +42,4 @@ contract Test {
.is_some(), .is_some(),
"Yul IR is missing" "Yul IR is missing"
); );
assert!(
build
.contracts
.as_ref()
.expect("Always exists")
.get("test.sol")
.expect("Always exists")
.get("Test")
.expect("Always exists")
.evm
.as_ref()
.expect("EVM object is missing")
.assembly
.is_none(),
"EVMLA IR is present although not requested"
);
}
#[test]
fn evmla() {
let source_code = r#"
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.0;
contract Test {
function main() public view returns (uint) {
return 42;
}
}
"#;
let mut sources = BTreeMap::new();
sources.insert("test.sol".to_owned(), source_code.to_owned());
let build = super::build_solidity(
sources,
BTreeMap::new(),
None,
SolcPipeline::EVMLA,
revive_llvm_context::OptimizerSettings::cycles(),
)
.expect("Test failure");
assert!(
build
.contracts
.as_ref()
.expect("Always exists")
.get("test.sol")
.expect("Always exists")
.get("Test")
.expect("Always exists")
.evm
.as_ref()
.expect("EVM object is missing")
.assembly
.is_some(),
"EVMLA IR is missing",
);
assert!(
build
.contracts
.as_ref()
.expect("Always exists")
.get("test.sol")
.expect("Always exists")
.get("Test")
.expect("Always exists")
.ir_optimized
.is_none(),
"Yul IR is present although not requested",
);
} }
+4 -16
View File
@@ -4,8 +4,6 @@
use std::collections::BTreeMap; use std::collections::BTreeMap;
use crate::solc::pipeline::Pipeline as SolcPipeline;
pub const LIBRARY_TEST_SOURCE: &str = r#" pub const LIBRARY_TEST_SOURCE: &str = r#"
// SPDX-License-Identifier: MIT // SPDX-License-Identifier: MIT
pragma solidity ^0.8.0; pragma solidity ^0.8.0;
@@ -38,12 +36,8 @@ fn not_specified() {
let mut sources = BTreeMap::new(); let mut sources = BTreeMap::new();
sources.insert("test.sol".to_owned(), LIBRARY_TEST_SOURCE.to_owned()); sources.insert("test.sol".to_owned(), LIBRARY_TEST_SOURCE.to_owned());
for pipeline in [SolcPipeline::EVMLA, SolcPipeline::Yul] { let output =
let output = super::build_solidity_and_detect_missing_libraries( super::build_solidity_and_detect_missing_libraries(sources.clone(), BTreeMap::new())
sources.clone(),
BTreeMap::new(),
pipeline,
)
.expect("Test failure"); .expect("Test failure");
assert!( assert!(
output output
@@ -61,7 +55,6 @@ fn not_specified() {
"Missing library not detected" "Missing library not detected"
); );
} }
}
#[test] #[test]
fn specified() { fn specified() {
@@ -75,12 +68,8 @@ fn specified() {
.entry("SimpleLibrary".to_string()) .entry("SimpleLibrary".to_string())
.or_insert("0x00000000000000000000000000000000DEADBEEF".to_string()); .or_insert("0x00000000000000000000000000000000DEADBEEF".to_string());
for pipeline in [SolcPipeline::EVMLA, SolcPipeline::Yul] { let output =
let output = super::build_solidity_and_detect_missing_libraries( super::build_solidity_and_detect_missing_libraries(sources.clone(), libraries.clone())
sources.clone(),
libraries.clone(),
pipeline,
)
.expect("Test failure"); .expect("Test failure");
assert!( assert!(
output output
@@ -99,4 +88,3 @@ fn specified() {
"The list of missing libraries must be empty" "The list of missing libraries must be empty"
); );
} }
}
+19 -160
View File
@@ -4,7 +4,6 @@
use std::collections::BTreeMap; use std::collections::BTreeMap;
use crate::solc::pipeline::Pipeline as SolcPipeline;
use crate::warning::Warning; use crate::warning::Warning;
pub const ECRECOVER_TEST_SOURCE: &str = r#" pub const ECRECOVER_TEST_SOURCE: &str = r#"
@@ -30,7 +29,6 @@ fn ecrecover() {
ECRECOVER_TEST_SOURCE, ECRECOVER_TEST_SOURCE,
"Warning: It looks like you are using 'ecrecover' to validate a signature of a user account.", "Warning: It looks like you are using 'ecrecover' to validate a signature of a user account.",
BTreeMap::new(), BTreeMap::new(),
SolcPipeline::Yul,
false, false,
None, None,
).expect("Test failure") ).expect("Test failure")
@@ -44,7 +42,6 @@ fn ecrecover_suppressed() {
ECRECOVER_TEST_SOURCE, ECRECOVER_TEST_SOURCE,
"Warning: It looks like you are using 'ecrecover' to validate a signature of a user account.", "Warning: It looks like you are using 'ecrecover' to validate a signature of a user account.",
BTreeMap::new(), BTreeMap::new(),
SolcPipeline::Yul,
false, false,
Some(vec![Warning::EcRecover]), Some(vec![Warning::EcRecover]),
).expect("Test failure") ).expect("Test failure")
@@ -69,32 +66,31 @@ contract SendExample {
} }
"#; "#;
pub const BALANCE_CALLS_MESSAGE: &str =
"Warning: It looks like you are using '<address payable>.send/transfer(<X>)'";
#[test] #[test]
fn send() { fn send() {
assert!( assert!(super::check_solidity_warning(
super::check_solidity_warning(
SEND_TEST_SOURCE, SEND_TEST_SOURCE,
"Warning: It looks like you are using '<address payable>.send/transfer(<X>)' without providing", BALANCE_CALLS_MESSAGE,
BTreeMap::new(), BTreeMap::new(),
SolcPipeline::Yul,
false, false,
None, None,
).expect("Test failure") )
); .expect("Test failure"));
} }
#[test] #[test]
fn send_suppressed() { fn send_suppressed() {
assert!( assert!(!super::check_solidity_warning(
!super::check_solidity_warning(
SEND_TEST_SOURCE, SEND_TEST_SOURCE,
"Warning: It looks like you are using '<address payable>.send/transfer(<X>)' without providing", BALANCE_CALLS_MESSAGE,
BTreeMap::new(), BTreeMap::new(),
SolcPipeline::Yul,
false, false,
Some(vec![Warning::SendTransfer]), Some(vec![Warning::SendTransfer]),
).expect("Test failure") )
); .expect("Test failure"));
} }
pub const TRANSFER_TEST_SOURCE: &str = r#" pub const TRANSFER_TEST_SOURCE: &str = r#"
@@ -116,30 +112,26 @@ contract TransferExample {
#[test] #[test]
fn transfer() { fn transfer() {
assert!( assert!(super::check_solidity_warning(
super::check_solidity_warning(
TRANSFER_TEST_SOURCE, TRANSFER_TEST_SOURCE,
"Warning: It looks like you are using '<address payable>.send/transfer(<X>)' without providing", BALANCE_CALLS_MESSAGE,
BTreeMap::new(), BTreeMap::new(),
SolcPipeline::Yul,
false, false,
None, None,
).expect("Test failure") )
); .expect("Test failure"));
} }
#[test] #[test]
fn transfer_suppressed() { fn transfer_suppressed() {
assert!( assert!(!super::check_solidity_warning(
!super::check_solidity_warning(
TRANSFER_TEST_SOURCE, TRANSFER_TEST_SOURCE,
"Warning: It looks like you are using '<address payable>.send/transfer(<X>)' without providing", BALANCE_CALLS_MESSAGE,
BTreeMap::new(), BTreeMap::new(),
SolcPipeline::Yul,
false, false,
Some(vec![Warning::SendTransfer]), Some(vec![Warning::SendTransfer]),
).expect("Test failure") )
); .expect("Test failure"));
} }
pub const EXTCODESIZE_TEST_SOURCE: &str = r#" pub const EXTCODESIZE_TEST_SOURCE: &str = r#"
@@ -163,7 +155,6 @@ fn extcodesize() {
EXTCODESIZE_TEST_SOURCE, EXTCODESIZE_TEST_SOURCE,
"Warning: Your code or one of its dependencies uses the 'extcodesize' instruction,", "Warning: Your code or one of its dependencies uses the 'extcodesize' instruction,",
BTreeMap::new(), BTreeMap::new(),
SolcPipeline::Yul,
false, false,
None, None,
) )
@@ -176,7 +167,6 @@ fn extcodesize_suppressed() {
EXTCODESIZE_TEST_SOURCE, EXTCODESIZE_TEST_SOURCE,
"Warning: Your code or one of its dependencies uses the 'extcodesize' instruction,", "Warning: Your code or one of its dependencies uses the 'extcodesize' instruction,",
BTreeMap::new(), BTreeMap::new(),
SolcPipeline::Yul,
false, false,
Some(vec![Warning::ExtCodeSize]), Some(vec![Warning::ExtCodeSize]),
) )
@@ -200,7 +190,6 @@ fn tx_origin() {
TX_ORIGIN_TEST_SOURCE, TX_ORIGIN_TEST_SOURCE,
"Warning: You are checking for 'tx.origin' in your code, which might lead to", "Warning: You are checking for 'tx.origin' in your code, which might lead to",
BTreeMap::new(), BTreeMap::new(),
SolcPipeline::Yul,
false, false,
None, None,
) )
@@ -213,7 +202,6 @@ fn tx_origin_suppressed() {
TX_ORIGIN_TEST_SOURCE, TX_ORIGIN_TEST_SOURCE,
"Warning: You are checking for 'tx.origin' in your code, which might lead to", "Warning: You are checking for 'tx.origin' in your code, which might lead to",
BTreeMap::new(), BTreeMap::new(),
SolcPipeline::Yul,
false, false,
Some(vec![Warning::TxOrigin]), Some(vec![Warning::TxOrigin]),
) )
@@ -244,7 +232,6 @@ fn tx_origin_assembly() {
TX_ORIGIN_ASSEMBLY_TEST_SOURCE, TX_ORIGIN_ASSEMBLY_TEST_SOURCE,
"Warning: You are checking for 'tx.origin' in your code, which might lead to", "Warning: You are checking for 'tx.origin' in your code, which might lead to",
BTreeMap::new(), BTreeMap::new(),
SolcPipeline::Yul,
false, false,
None, None,
) )
@@ -257,136 +244,8 @@ fn tx_origin_assembly_suppressed() {
TX_ORIGIN_ASSEMBLY_TEST_SOURCE, TX_ORIGIN_ASSEMBLY_TEST_SOURCE,
"Warning: You are checking for 'tx.origin' in your code, which might lead to", "Warning: You are checking for 'tx.origin' in your code, which might lead to",
BTreeMap::new(), BTreeMap::new(),
SolcPipeline::Yul,
false, false,
Some(vec![Warning::TxOrigin]), Some(vec![Warning::TxOrigin]),
) )
.expect("Test failure")); .expect("Test failure"));
} }
#[test]
fn internal_function_pointer_argument() {
let source_code = r#"
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.0;
contract InternalFunctionPointerExample {
function add(uint256 a, uint256 b) internal pure returns (uint256) {
return a + b;
}
function sub(uint256 a, uint256 b) internal pure returns (uint256) {
return a - b;
}
function executeOperation(
function (uint256, uint256) internal pure returns (uint256) operation,
uint256 a,
uint256 b
) private pure returns (uint256) {
return operation(a, b);
}
function testAdd(uint256 a, uint256 b) public pure returns (uint256) {
return executeOperation(add, a, b);
}
function testSub(uint256 a, uint256 b) public pure returns (uint256) {
return executeOperation(sub, a, b);
}
}
"#;
assert!(super::check_solidity_warning(
source_code,
"Error: Internal function pointers are not supported in EVM legacy assembly pipeline.",
BTreeMap::new(),
SolcPipeline::EVMLA,
true,
None,
)
.expect("Test failure"));
}
#[test]
fn internal_function_pointer_stack() {
let source_code = r#"
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.0;
contract StackFunctionPointerExample {
function add(uint256 a, uint256 b) internal pure returns (uint256) {
return a + b;
}
function sub(uint256 a, uint256 b) internal pure returns (uint256) {
return a - b;
}
function testAdd(uint256 a, uint256 b) public pure returns (uint256) {
function (uint256, uint256) internal pure returns (uint256) operation = add;
return operation(a, b);
}
function testSub(uint256 a, uint256 b) public pure returns (uint256) {
function (uint256, uint256) internal pure returns (uint256) operation = sub;
return operation(a, b);
}
}
"#;
assert!(super::check_solidity_warning(
source_code,
"Error: Internal function pointers are not supported in EVM legacy assembly pipeline.",
BTreeMap::new(),
SolcPipeline::EVMLA,
true,
None,
)
.expect("Test failure"));
}
#[test]
fn internal_function_pointer_storage() {
let source_code = r#"
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.0;
contract StorageFunctionPointerExample {
function add(uint256 a, uint256 b) internal pure returns (uint256) {
return a + b;
}
function sub(uint256 a, uint256 b) internal pure returns (uint256) {
return a - b;
}
function (uint256, uint256) internal pure returns (uint256) operation;
bool private isOperationSet = false;
function setOperation(bool isAdd) public {
if (isAdd) {
operation = add;
} else {
operation = sub;
}
isOperationSet = true;
}
function executeOperation(uint256 a, uint256 b) public view returns (uint256) {
require(isOperationSet, "Operation not set");
return operation(a, b);
}
}
"#;
assert!(super::check_solidity_warning(
source_code,
"Error: Internal function pointers are not supported in EVM legacy assembly pipeline.",
BTreeMap::new(),
SolcPipeline::EVMLA,
true,
None,
)
.expect("Test failure"));
}
-5
View File
@@ -4,8 +4,6 @@
use std::collections::BTreeMap; use std::collections::BTreeMap;
use crate::solc::pipeline::Pipeline as SolcPipeline;
pub const SOURCE_CODE: &str = r#" pub const SOURCE_CODE: &str = r#"
// SPDX-License-Identifier: MIT // SPDX-License-Identifier: MIT
@@ -54,7 +52,6 @@ fn optimizer() {
sources.clone(), sources.clone(),
BTreeMap::new(), BTreeMap::new(),
None, None,
SolcPipeline::Yul,
revive_llvm_context::OptimizerSettings::none(), revive_llvm_context::OptimizerSettings::none(),
) )
.expect("Build failure"); .expect("Build failure");
@@ -62,7 +59,6 @@ fn optimizer() {
sources.clone(), sources.clone(),
BTreeMap::new(), BTreeMap::new(),
None, None,
SolcPipeline::Yul,
revive_llvm_context::OptimizerSettings::cycles(), revive_llvm_context::OptimizerSettings::cycles(),
) )
.expect("Build failure"); .expect("Build failure");
@@ -70,7 +66,6 @@ fn optimizer() {
sources, sources,
BTreeMap::new(), BTreeMap::new(),
None, None,
SolcPipeline::Yul,
revive_llvm_context::OptimizerSettings::size(), revive_llvm_context::OptimizerSettings::size(),
) )
.expect("Build failure"); .expect("Build failure");
-3
View File
@@ -5,8 +5,6 @@
use std::collections::BTreeMap; use std::collections::BTreeMap;
use std::collections::BTreeSet; use std::collections::BTreeSet;
use crate::solc::pipeline::Pipeline as SolcPipeline;
pub const CALLEE_TEST_SOURCE: &str = r#" pub const CALLEE_TEST_SOURCE: &str = r#"
// SPDX-License-Identifier: MIT // SPDX-License-Identifier: MIT
@@ -46,7 +44,6 @@ fn default() {
sources, sources,
BTreeMap::new(), BTreeMap::new(),
Some(remappings), Some(remappings),
SolcPipeline::Yul,
revive_llvm_context::OptimizerSettings::cycles(), revive_llvm_context::OptimizerSettings::cycles(),
) )
.expect("Test failure"); .expect("Test failure");
@@ -4,8 +4,6 @@
use std::collections::BTreeMap; use std::collections::BTreeMap;
use crate::solc::pipeline::Pipeline as SolcPipeline;
#[test] #[test]
#[should_panic(expected = "runtimeCode is not supported")] #[should_panic(expected = "runtimeCode is not supported")]
fn default() { fn default() {
@@ -29,7 +27,6 @@ contract Test {
sources, sources,
BTreeMap::new(), BTreeMap::new(),
None, None,
SolcPipeline::Yul,
revive_llvm_context::OptimizerSettings::cycles(), revive_llvm_context::OptimizerSettings::cycles(),
) )
.expect("Test failure"); .expect("Test failure");
@@ -4,8 +4,6 @@
use std::collections::BTreeMap; use std::collections::BTreeMap;
use crate::solc::pipeline::Pipeline as SolcPipeline;
#[test] #[test]
#[should_panic(expected = "The `CODECOPY` instruction is not supported")] #[should_panic(expected = "The `CODECOPY` instruction is not supported")]
fn codecopy_yul_runtime() { fn codecopy_yul_runtime() {
@@ -34,7 +32,6 @@ contract FixedCodeCopy {
sources, sources,
BTreeMap::new(), BTreeMap::new(),
None, None,
SolcPipeline::Yul,
revive_llvm_context::OptimizerSettings::cycles(), revive_llvm_context::OptimizerSettings::cycles(),
) )
.expect("Test failure"); .expect("Test failure");
@@ -63,22 +60,6 @@ contract CallcodeTest {
} }
"#; "#;
#[test]
#[should_panic(expected = "The `CALLCODE` instruction is not supported")]
fn callcode_evmla() {
let mut sources = BTreeMap::new();
sources.insert("test.sol".to_owned(), CALLCODE_TEST_SOURCE.to_owned());
super::build_solidity(
sources,
BTreeMap::new(),
None,
SolcPipeline::EVMLA,
revive_llvm_context::OptimizerSettings::cycles(),
)
.expect("Test failure");
}
#[test] #[test]
#[should_panic(expected = "The `CALLCODE` instruction is not supported")] #[should_panic(expected = "The `CALLCODE` instruction is not supported")]
fn callcode_yul() { fn callcode_yul() {
@@ -89,7 +70,6 @@ fn callcode_yul() {
sources, sources,
BTreeMap::new(), BTreeMap::new(),
None, None,
SolcPipeline::Yul,
revive_llvm_context::OptimizerSettings::cycles(), revive_llvm_context::OptimizerSettings::cycles(),
) )
.expect("Test failure"); .expect("Test failure");
@@ -137,22 +117,6 @@ contract ExternalCodeCopy {
} }
"#; "#;
#[test]
#[should_panic(expected = "The `EXTCODECOPY` instruction is not supported")]
fn extcodecopy_evmla() {
let mut sources = BTreeMap::new();
sources.insert("test.sol".to_owned(), EXTCODECOPY_TEST_SOURCE.to_owned());
super::build_solidity(
sources,
BTreeMap::new(),
None,
SolcPipeline::EVMLA,
revive_llvm_context::OptimizerSettings::cycles(),
)
.expect("Test failure");
}
#[test] #[test]
#[should_panic(expected = "The `EXTCODECOPY` instruction is not supported")] #[should_panic(expected = "The `EXTCODECOPY` instruction is not supported")]
fn extcodecopy_yul() { fn extcodecopy_yul() {
@@ -163,7 +127,6 @@ fn extcodecopy_yul() {
sources, sources,
BTreeMap::new(), BTreeMap::new(),
None, None,
SolcPipeline::Yul,
revive_llvm_context::OptimizerSettings::cycles(), revive_llvm_context::OptimizerSettings::cycles(),
) )
.expect("Test failure"); .expect("Test failure");
@@ -187,22 +150,6 @@ contract MinimalDestructible {
} }
"#; "#;
#[test]
#[should_panic(expected = "The `SELFDESTRUCT` instruction is not supported")]
fn selfdestruct_evmla() {
let mut sources = BTreeMap::new();
sources.insert("test.sol".to_owned(), SELFDESTRUCT_TEST_SOURCE.to_owned());
super::build_solidity(
sources,
BTreeMap::new(),
None,
SolcPipeline::EVMLA,
revive_llvm_context::OptimizerSettings::cycles(),
)
.expect("Test failure");
}
#[test] #[test]
#[should_panic(expected = "The `SELFDESTRUCT` instruction is not supported")] #[should_panic(expected = "The `SELFDESTRUCT` instruction is not supported")]
fn selfdestruct_yul() { fn selfdestruct_yul() {
@@ -213,7 +160,6 @@ fn selfdestruct_yul() {
sources, sources,
BTreeMap::new(), BTreeMap::new(),
None, None,
SolcPipeline::Yul,
revive_llvm_context::OptimizerSettings::cycles(), revive_llvm_context::OptimizerSettings::cycles(),
) )
.expect("Test failure"); .expect("Test failure");
-1
View File
@@ -42,7 +42,6 @@ impl Lexer {
} }
/// Advances the lexer, returning the next lexeme. /// Advances the lexer, returning the next lexeme.
#[allow(clippy::should_implement_trait)]
pub fn next(&mut self) -> Result<Token, Error> { pub fn next(&mut self) -> Result<Token, Error> {
if let Some(peeked) = self.peeked.take() { if let Some(peeked) = self.peeked.take() {
return Ok(peeked); return Ok(peeked);
+53
View File
@@ -0,0 +1,53 @@
const fs = require("fs");
const path = require("path");
const { minify } = require("terser");
const SOLJSON_URI =
"https://binaries.soliditylang.org/wasm/soljson-v0.8.28+commit.7893614a.js";
const RESOLC_WASM_URI = "http://127.0.0.1:8080/resolc.wasm";
const RESOLC_WASM_TARGET_DIR = path.join(
__dirname,
"../target/wasm32-unknown-emscripten/release",
);
const RESOLC_JS = path.join(RESOLC_WASM_TARGET_DIR, "resolc.js");
const RESOLC_WEB_JS = path.join(RESOLC_WASM_TARGET_DIR, "resolc_web.js");
const resolcJs = fs.readFileSync(RESOLC_JS, "utf-8");
const packedJsContent = `
if (typeof importScripts === "function") {
importScripts("${SOLJSON_URI}");
var moduleArgs = {
wasmBinary: (function () {
var xhr = new XMLHttpRequest();
xhr.open("GET", "${RESOLC_WASM_URI}", false);
xhr.responseType = "arraybuffer";
xhr.send(null);
return new Uint8Array(xhr.response);
})(),
soljson: Module
};
} else {
console.log("Not a WebWorker, skipping Soljson and WASM loading.");
}
${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_WEB_JS, minifiedJs.code, "utf-8");
console.log(`Combined script written to ${RESOLC_WEB_JS}`);
})
.catch((err) => {
console.error("Minification failed:", err);
process.exit(1);
});
+116 -51
View File
@@ -1,16 +1,23 @@
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 loadTestPage(page) {
await page.goto("http://127.0.0.1:8080");
const outputElement = page.locator("#output");
await outputElement.waitFor({ state: "visible" });
await page.setContent("");
} }
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 +33,120 @@ 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 ({
await page.goto("http://127.0.0.1:8080"); page,
await page.setContent(""); }) => {
const standardInput = loadFixture('storage.json') await loadTestPage(page);
const standardInput = loadFixture("storage.json");
const result = await runWorker(page, standardInput);
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",
);
});
test("should successfully compile large valid Solidity code in browser", async ({
page,
browserName,
}) => {
if (browserName === "firefox") {
// Skipping tests with large contracts on Firefox due to out-of-memory issues.
test.skip();
}
await loadTestPage(page);
const standardInput = loadFixture("token.json");
const result = await runWorker(page, standardInput);
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",
);
});
test("should throw an error for invalid Solidity code in browser", async ({
page,
}) => {
await loadTestPage(page);
const standardInput = loadFixture("invalid_contract_content.json");
const result = await runWorker(page, standardInput);
expect(typeof result).toBe("string");
let output = JSON.parse(result);
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");
});
test("should return not found error for missing imports in browser", async ({
page,
}) => {
await loadTestPage(page);
const standardInput = loadFixture("missing_import.json");
const result = await runWorker(page, standardInput);
expect(typeof result).toBe("string");
let output = JSON.parse(result);
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',
);
});
test('should successfully compile a valid Solidity contract that instantiates another contract in the browser', async ({ page }) => {
await loadTestPage(page);
const standardInput = loadFixture('instantiate.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/instantiate.sol']).toHaveProperty('ChildContract');
expect(output.contracts['fixtures/storage.sol'].Storage).toHaveProperty('abi'); expect(output.contracts['fixtures/instantiate.sol'].ChildContract).toHaveProperty('abi');
expect(output.contracts['fixtures/storage.sol'].Storage).toHaveProperty('evm'); expect(output.contracts['fixtures/instantiate.sol'].ChildContract).toHaveProperty('evm');
expect(output.contracts['fixtures/storage.sol'].Storage.evm).toHaveProperty('bytecode'); expect(output.contracts['fixtures/instantiate.sol'].ChildContract.evm).toHaveProperty('bytecode');
expect(output.contracts['fixtures/instantiate.sol']).toHaveProperty('MainContract');
expect(output.contracts['fixtures/instantiate.sol'].MainContract).toHaveProperty('abi');
expect(output.contracts['fixtures/instantiate.sol'].MainContract).toHaveProperty('evm');
expect(output.contracts['fixtures/instantiate.sol'].MainContract.evm).toHaveProperty('bytecode');
}); });
test('should successfully compile large valid Solidity code in browser', async ({ page }) => { test('should successfully compile a valid Solidity contract that instantiates the token contracts in the browser', async ({
await page.goto("http://127.0.0.1:8080"); page,
await page.setContent(""); browserName,
const standardInput = loadFixture('token.json') }) => {
if (browserName === "firefox") {
// Skipping tests with large contracts on Firefox due to out-of-memory issues.
test.skip();
}
await loadTestPage(page);
const standardInput = loadFixture('instantiate_tokens.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/instantiate_tokens.sol']).toHaveProperty('TokensFactory');
expect(output.contracts['fixtures/token.sol'].MyToken).toHaveProperty('abi'); expect(output.contracts['fixtures/instantiate_tokens.sol'].TokensFactory).toHaveProperty('abi');
expect(output.contracts['fixtures/token.sol'].MyToken).toHaveProperty('evm'); expect(output.contracts['fixtures/instantiate_tokens.sol'].TokensFactory).toHaveProperty('evm');
expect(output.contracts['fixtures/token.sol'].MyToken.evm).toHaveProperty('bytecode'); expect(output.contracts['fixtures/instantiate_tokens.sol'].TokensFactory.evm).toHaveProperty('bytecode');
});
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 result = await runWorker(page, standardInput);
expect(typeof result).toBe('string');
let output = JSON.parse(result);
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');
});
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 result = await runWorker(page, standardInput);
expect(typeof result).toBe('string');
let output = JSON.parse(result);
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');
}); });

Some files were not shown because too many files have changed in this diff Show More