Skip to content

Commit

Permalink
Store Testing Framework Solidity and Yul (#1052)
Browse files Browse the repository at this point in the history
* Store Testing Framework Solidity and Yul

* update sample addresses for better replacement

* web3j plugin to compile sol files and generate java api

* Add example test

* spotless

* cleanup

* Add test using framework entrypoint

* Parse besu transaction logs

* Comments

---------

Co-authored-by: Olivier Bégassat <38285177+OlivierBBB@users.noreply.github.com>
Co-authored-by: Gaurav Ahuja <gauravahuja9@gmail.com>
  • Loading branch information
3 people authored Oct 4, 2024
1 parent 16548e9 commit b0b3c04
Show file tree
Hide file tree
Showing 15 changed files with 953 additions and 0 deletions.
Original file line number Diff line number Diff line change
@@ -0,0 +1,227 @@
/*
* Copyright Consensys Software Inc.
*
* 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 net.consensys.linea.zktracer;

import static org.junit.jupiter.api.Assertions.assertEquals;
import static org.junit.jupiter.api.Assertions.assertTrue;
import static org.junit.jupiter.api.Assertions.fail;

import java.math.BigInteger;
import java.util.Collections;
import java.util.List;
import java.util.function.Consumer;

import net.consensys.linea.testing.SolidityUtils;
import net.consensys.linea.testing.ToyAccount;
import net.consensys.linea.testing.ToyExecutionEnvironmentV2;
import net.consensys.linea.testing.ToyTransaction;
import net.consensys.linea.testing.generated.FrameworkEntrypoint;
import net.consensys.linea.testing.generated.TestSnippet_Events;
import net.consensys.linea.testing.generated.TestStorage;
import org.apache.tuweni.bytes.Bytes;
import org.hyperledger.besu.crypto.KeyPair;
import org.hyperledger.besu.crypto.SECP256K1;
import org.hyperledger.besu.datatypes.Address;
import org.hyperledger.besu.datatypes.Hash;
import org.hyperledger.besu.datatypes.Wei;
import org.hyperledger.besu.ethereum.core.Transaction;
import org.hyperledger.besu.ethereum.processing.TransactionProcessingResult;
import org.hyperledger.besu.evm.log.Log;
import org.junit.jupiter.api.Test;
import org.web3j.abi.EventEncoder;
import org.web3j.abi.FunctionEncoder;
import org.web3j.abi.datatypes.DynamicArray;
import org.web3j.abi.datatypes.Function;
import org.web3j.abi.datatypes.generated.Uint256;

public class ExampleSolidityTest {

@Test
void testWithFrameworkEntrypoint() {
KeyPair keyPair = new SECP256K1().generateKeyPair();
Address senderAddress = Address.extract(Hash.hash(keyPair.getPublicKey().getEncodedBytes()));

ToyAccount senderAccount =
ToyAccount.builder().balance(Wei.fromEth(1)).nonce(5).address(senderAddress).build();

ToyAccount frameworkEntrypointAccount =
ToyAccount.builder()
.address(Address.fromHexString("0x22222"))
.balance(Wei.ONE)
.nonce(5)
.code(SolidityUtils.getContractByteCode(FrameworkEntrypoint.class))
.build();

ToyAccount snippetAccount =
ToyAccount.builder()
.address(Address.fromHexString("0x11111"))
.balance(Wei.ONE)
.nonce(6)
.code(SolidityUtils.getContractByteCode(TestSnippet_Events.class))
.build();

Function snippetFunction =
new Function(
TestSnippet_Events.FUNC_EMITDATANOINDEXES,
List.of(new Uint256(BigInteger.valueOf(123456))),
Collections.emptyList());

FrameworkEntrypoint.ContractCall snippetContractCall =
new FrameworkEntrypoint.ContractCall(
/*Address*/ snippetAccount.getAddress().toHexString(),
/*calldata*/ Bytes.fromHexStringLenient(FunctionEncoder.encode(snippetFunction))
.toArray(),
/*gasLimit*/ BigInteger.ZERO,
/*value*/ BigInteger.ZERO,
/*callType*/ BigInteger.ZERO);

List<FrameworkEntrypoint.ContractCall> contractCalls = List.of(snippetContractCall);

Function frameworkEntryPointFunction =
new Function(
FrameworkEntrypoint.FUNC_EXECUTECALLS,
List.of(new DynamicArray<>(FrameworkEntrypoint.ContractCall.class, contractCalls)),
Collections.emptyList());
Bytes txPayload =
Bytes.fromHexStringLenient(FunctionEncoder.encode(frameworkEntryPointFunction));

Transaction tx =
ToyTransaction.builder()
.sender(senderAccount)
.to(frameworkEntrypointAccount)
.payload(txPayload)
.keyPair(keyPair)
.build();

Consumer<TransactionProcessingResult> resultValidator =
(TransactionProcessingResult result) -> {
// One event from the snippet
// One event from the framework entrypoint about contract call
assertEquals(result.getLogs().size(), 2);
for (Log log : result.getLogs()) {
String logTopic = log.getTopics().getFirst().toHexString();
if (EventEncoder.encode(TestSnippet_Events.DATANOINDEXES_EVENT).equals(logTopic)) {
TestSnippet_Events.DataNoIndexesEventResponse response =
TestSnippet_Events.getDataNoIndexesEventFromLog(SolidityUtils.fromBesuLog(log));
assertEquals(response.singleInt, BigInteger.valueOf(123456));
} else if (EventEncoder.encode(FrameworkEntrypoint.CALLEXECUTED_EVENT)
.equals(logTopic)) {
FrameworkEntrypoint.CallExecutedEventResponse response =
FrameworkEntrypoint.getCallExecutedEventFromLog(SolidityUtils.fromBesuLog(log));
assertTrue(response.isSuccess);
assertEquals(response.destination, snippetAccount.getAddress().toHexString());
} else {
fail();
}
}
};

ToyExecutionEnvironmentV2.builder()
.accounts(List.of(senderAccount, frameworkEntrypointAccount, snippetAccount))
.transaction(tx)
.testValidator(resultValidator)
.build()
.run();
}

@Test
void testSnippetIndependently() {
KeyPair keyPair = new SECP256K1().generateKeyPair();
Address senderAddress = Address.extract(Hash.hash(keyPair.getPublicKey().getEncodedBytes()));

ToyAccount senderAccount =
ToyAccount.builder().balance(Wei.fromEth(1)).nonce(5).address(senderAddress).build();

ToyAccount contractAccount =
ToyAccount.builder()
.address(Address.fromHexString("0x11111"))
.balance(Wei.ONE)
.nonce(6)
.code(SolidityUtils.getContractByteCode(TestSnippet_Events.class))
.build();

Function function =
new Function(
TestSnippet_Events.FUNC_EMITDATANOINDEXES,
List.of(new Uint256(BigInteger.valueOf(123456))),
Collections.emptyList());
String encodedFunction = FunctionEncoder.encode(function);
Bytes txPayload = Bytes.fromHexStringLenient(encodedFunction);

Transaction tx =
ToyTransaction.builder()
.sender(senderAccount)
.to(contractAccount)
.payload(txPayload)
.keyPair(keyPair)
.build();

Consumer<TransactionProcessingResult> resultValidator =
(TransactionProcessingResult result) -> {
assertEquals(result.getLogs().size(), 1);
TestSnippet_Events.DataNoIndexesEventResponse response =
TestSnippet_Events.getDataNoIndexesEventFromLog(
SolidityUtils.fromBesuLog(result.getLogs().getFirst()));
assertEquals(response.singleInt, BigInteger.valueOf(123456));
};

ToyExecutionEnvironmentV2.builder()
.accounts(List.of(senderAccount, contractAccount))
.transaction(tx)
.testValidator(resultValidator)
.build()
.run();
}

@Test
void testContractNotRelatedToTestingFramework() {
KeyPair senderkeyPair = new SECP256K1().generateKeyPair();
Address senderAddress =
Address.extract(Hash.hash(senderkeyPair.getPublicKey().getEncodedBytes()));

ToyAccount senderAccount =
ToyAccount.builder().balance(Wei.fromEth(1)).nonce(5).address(senderAddress).build();

ToyAccount contractAccount =
ToyAccount.builder()
.address(Address.fromHexString("0x11111"))
.balance(Wei.ONE)
.nonce(6)
.code(SolidityUtils.getContractByteCode(TestStorage.class))
.build();

Function function =
new Function(
TestStorage.FUNC_STORE,
List.of(new Uint256(BigInteger.valueOf(3))),
Collections.emptyList());
String encodedFunction = FunctionEncoder.encode(function);
Bytes txPayload = Bytes.fromHexStringLenient(encodedFunction);
Transaction tx =
ToyTransaction.builder()
.sender(senderAccount)
.to(contractAccount)
.payload(txPayload)
.keyPair(senderkeyPair)
.build();

ToyExecutionEnvironmentV2.builder()
.accounts(List.of(senderAccount, contractAccount))
.transaction(tx)
.build()
.run();
}
}
20 changes: 20 additions & 0 deletions testing/build.gradle
Original file line number Diff line number Diff line change
@@ -1,5 +1,9 @@
import org.web3j.solidity.gradle.plugin.EVMVersion
import org.web3j.solidity.gradle.plugin.OutputComponent

plugins {
id "common-plugins"
id "org.web3j" version "4.12.2"
}

apply from: rootProject.file("gradle/corset.gradle")
Expand Down Expand Up @@ -34,3 +38,19 @@ dependencies {
implementation 'org.mockito:mockito-junit-jupiter'
implementation 'org.assertj:assertj-core'
}

nodeSetup {
mustRunAfter(spotlessBash, spotlessGroovy, spotlessSol)
}



solidity {
optimize = false
prettyJson = true
evmVersion = EVMVersion.LONDON
}

web3j {
generatedPackageName = "net.consensys.linea.testing.generated"
}
Original file line number Diff line number Diff line change
Expand Up @@ -85,6 +85,11 @@ public class ReplayExecutionEnvironment {
*/
private final boolean txResultChecking;

/**
* Setting this to true will disable the clique consensus protocol for parsing coinbase address
* from block header. This is needed for manual tests like the Multi Block tests which do not have
* encoded coinbase address in the block header.
*/
@Builder.Default private final boolean useCoinbaseAddressFromBlockHeader = false;

private final ZkTracer zkTracer = new ZkTracer();
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,81 @@
/*
* Copyright Consensys Software Inc.
*
* 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 net.consensys.linea.testing;

import java.net.URL;
import java.util.Iterator;
import java.util.Map;

import com.fasterxml.jackson.databind.JsonNode;
import com.fasterxml.jackson.databind.ObjectMapper;
import org.apache.tuweni.bytes.Bytes;
import org.hyperledger.besu.evm.log.LogTopic;
import org.web3j.protocol.core.methods.response.Log;
import org.web3j.tx.Contract;

public class SolidityUtils {

private static final ObjectMapper objectMapper = new ObjectMapper();
private static final ClassLoader classLoader = SolidityUtils.class.getClassLoader();

/**
* The solidity contracts under testing/src/main/solidity are compiled using the solidity plugin
* and then the web3j plugin generates java wrapper classes for each solidity contract. This
* method reads the output of the solidity compiler output file and returns the deployed or
* runtime bytecode for solidity contract.
*
* @param contractClass: The web3j wrapper class that is autogenerated from the solidity contract
* @return The depolyed or runtime bytecode for the solidity contract
*/
public static Bytes getContractByteCode(Class<? extends Contract> contractClass) {
String contractResourcePath = "solidity/%s.json".formatted(contractClass.getSimpleName());
URL contractResourceURL = classLoader.getResource(contractResourcePath);
try {
JsonNode jsonRoot = objectMapper.readTree(contractResourceURL);
Iterator<Map.Entry<String, JsonNode>> contracts = jsonRoot.get("contracts").fields();
while (contracts.hasNext()) {
Map.Entry<String, JsonNode> contract = contracts.next();
if (contract.getKey().contains(contractClass.getSimpleName())) {
return Bytes.fromHexStringLenient(contract.getValue().get("bin-runtime").asText());
}
}
} catch (Exception e) {
throw new RuntimeException(e);
}
throw new RuntimeException("Could not find contract bytecode");
}

/**
* Creates a web3j Log object from the Besu Log object. Web3j Log object can use used which web3j
* APIs to parse events in the log.
*
* @param log The besu log object
* @return The web3j log object
*/
public static Log fromBesuLog(org.hyperledger.besu.evm.log.Log log) {
return new Log(
false,
"",
"",
"",
"",
"",
log.getLogger().toHexString(),
log.getData().toHexString(),
"",
log.getTopics().stream().map(LogTopic::toHexString).toList());
}
}
Loading

0 comments on commit b0b3c04

Please sign in to comment.