CodeValidateSubCommand.java
/*
* Copyright ConsenSys AG.
*
* Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with
* the License. You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on
* an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the
* specific language governing permissions and limitations under the License.
*
* SPDX-License-Identifier: Apache-2.0
*/
package org.hyperledger.besu.evmtool;
import static java.nio.charset.StandardCharsets.UTF_8;
import static org.hyperledger.besu.evmtool.CodeValidateSubCommand.COMMAND_NAME;
import org.hyperledger.besu.evm.code.CodeFactory;
import org.hyperledger.besu.evm.code.CodeInvalid;
import org.hyperledger.besu.evm.code.EOFLayout;
import org.hyperledger.besu.util.LogConfigurator;
import java.io.BufferedReader;
import java.io.File;
import java.io.FileReader;
import java.io.IOException;
import java.io.InputStream;
import java.io.InputStreamReader;
import java.io.PrintStream;
import java.util.ArrayList;
import java.util.List;
import java.util.stream.Collectors;
import java.util.stream.IntStream;
import org.apache.tuweni.bytes.Bytes;
import picocli.CommandLine;
@CommandLine.Command(
name = COMMAND_NAME,
description = "Execute an Ethereum State Test.",
mixinStandardHelpOptions = true,
versionProvider = VersionProvider.class)
public class CodeValidateSubCommand implements Runnable {
public static final String COMMAND_NAME = "code-validate";
private final InputStream input;
private final PrintStream output;
@CommandLine.Option(
names = {"--file"},
description = "A file containing a set of inputs")
private final File codeFile = null;
@SuppressWarnings("MismatchedQueryAndUpdateOfCollection") // picocli does it magically
@CommandLine.Parameters
private final List<String> cliCode = new ArrayList<>();
@SuppressWarnings("unused")
public CodeValidateSubCommand() {
// PicoCLI requires this
this(System.in, System.out);
}
CodeValidateSubCommand(final InputStream input, final PrintStream output) {
this.input = input;
this.output = output;
}
@Override
public void run() {
LogConfigurator.setLevel("", "OFF");
if (cliCode.isEmpty() && codeFile == null) {
try (BufferedReader in = new BufferedReader(new InputStreamReader(input, UTF_8))) {
checkCodeFromBufferedReader(in);
} catch (IOException e) {
throw new RuntimeException(e);
}
} else {
if (codeFile != null) {
try (BufferedReader in = new BufferedReader(new FileReader(codeFile, UTF_8))) {
checkCodeFromBufferedReader(in);
} catch (IOException e) {
throw new RuntimeException(e);
}
}
for (String code : cliCode) {
output.print(considerCode(code));
}
}
}
private void checkCodeFromBufferedReader(final BufferedReader in) {
try {
for (String code = in.readLine(); code != null; code = in.readLine()) {
output.print(considerCode(code));
}
} catch (IOException e) {
throw new RuntimeException(e);
}
}
public String considerCode(final String hexCode) {
Bytes codeBytes;
try {
codeBytes =
Bytes.fromHexString(
hexCode.replaceAll("(^|\n)#[^\n]*($|\n)", "").replaceAll("[^0-9A-Za-z]", ""));
} catch (RuntimeException re) {
return "err: hex string -" + re + "\n";
}
if (codeBytes.size() == 0) {
return "";
}
var layout = EOFLayout.parseEOF(codeBytes);
if (!layout.isValid()) {
return "err: layout - " + layout.getInvalidReason() + "\n";
}
var code = CodeFactory.createCode(codeBytes, 1, true);
if (!code.isValid()) {
return "err: " + ((CodeInvalid) code).getInvalidReason() + "\n";
}
return "OK "
+ IntStream.range(0, code.getCodeSectionCount())
.mapToObj(code::getCodeSection)
.map(cs -> layout.getContainer().slice(cs.getEntryPoint(), cs.getLength()))
.map(Bytes::toUnprefixedHexString)
.collect(Collectors.joining(","))
+ "\n";
}
}