Skip to content
This repository has been archived by the owner on Mar 25, 2024. It is now read-only.

Commit

Permalink
Merge pull request #58 from matter-labs/sb-enhance-keccak-tests
Browse files Browse the repository at this point in the history
Better tests for keccak precompile
  • Loading branch information
StanislavBreadless committed Nov 21, 2023
2 parents 7b26445 + 54c4a77 commit 123bb68
Show file tree
Hide file tree
Showing 10 changed files with 316 additions and 23 deletions.
2 changes: 1 addition & 1 deletion .github/workflows/ci.yaml
Expand Up @@ -22,7 +22,7 @@ jobs:
run: yarn build

- name: Build yul artifacts
run: yarn preprocess && yarn compile-yul
run: yarn build-yul

- name: Create cache
uses: actions/cache/save@v3
Expand Down
4 changes: 1 addition & 3 deletions README.md
Expand Up @@ -34,9 +34,7 @@ This repository is used as a submodule of the [zksync-2-dev](https://github.com/

Compile the solidity contracts: `yarn build`

Run the bootloader preprocessor: `yarn preprocess`

Compile the yul contracts: `yarn hardhat run ./scripts/compile-yul.ts`
Compile the yul contracts: `yarn build-yul`

## Update Process

Expand Down
2 changes: 1 addition & 1 deletion bootloader/test_infra/README.md
Expand Up @@ -5,7 +5,7 @@ This crate allows you to run the unittests against the bootloader code.
You should put your tests in ../tests/bootloader/bootloader_test.yul, then compile the yul with:

```shell
yarn build && yarn preprocess && yarn compile-yul
yarn build && yarn build-yul
```

And afterwards run the testing infrastructure:
Expand Down
2 changes: 1 addition & 1 deletion bootloader/tests/README.md
Expand Up @@ -11,7 +11,7 @@ Please put bootloader unittests in `bootloader/bootloader_test.yul` file, and an
To execute tests, you should first run yarn to prepare the source code:

```shell
yarn preprocess && yarn compile-yul
yarn build-yul
```

And then run the test framework:
Expand Down
99 changes: 99 additions & 0 deletions contracts/precompiles/test-contracts/Keccak256Mock.yul
@@ -0,0 +1,99 @@
/**
* @author Matter Labs
* @notice The contract used to emulate EVM's keccak256 opcode.
* @dev It accepts the data to be hashed in the calldata, propagate it to the zkEVM built-in circuit precompile via `precompileCall` and burn .
*/
object "Keccak256" {
code { }
object "Keccak256_deployed" {
code {
////////////////////////////////////////////////////////////////
// CONSTANTS
////////////////////////////////////////////////////////////////

/// @dev The size of the processing keccak256 block in bytes.
function BLOCK_SIZE() -> ret {
ret := 136
}

/// @dev The gas cost of processing one keccak256 round.
function KECCAK_ROUND_GAS_COST() -> ret {
ret := 40
}

/// @dev Returns a 32-bit mask value
function UINT32_BIT_MASK() -> ret {
ret := 0xffffffff
}

////////////////////////////////////////////////////////////////
// HELPER FUNCTIONS
////////////////////////////////////////////////////////////////

/// @dev Load raw calldata fat pointer
function getCalldataPtr() -> calldataPtr {
calldataPtr := verbatim_0i_1o("get_global::ptr_calldata")
}

/// @dev Packs precompile parameters into one word.
/// Note: functions expect to work with 32/64 bits unsigned integers.
/// Caller should ensure the type matching before!
function unsafePackPrecompileParams(
uint32_inputOffsetInBytes,
uint32_inputLengthInBytes,
uint32_outputOffsetInWords,
uint32_outputLengthInWords,
uint32_memoryPageToRead,
uint32_memoryPageToWrite,
uint64_perPrecompileInterpreted
) -> rawParams {
rawParams := uint32_inputOffsetInBytes
rawParams := or(rawParams, shl(32, uint32_inputLengthInBytes))
rawParams := or(rawParams, shl(64, uint32_outputOffsetInWords))
rawParams := or(rawParams, shl(96, uint32_outputLengthInWords))
rawParams := or(rawParams, shl(128, uint32_memoryPageToRead))
rawParams := or(rawParams, shl(160, uint32_memoryPageToWrite))
rawParams := or(rawParams, shl(192, uint64_perPrecompileInterpreted))
}

/// @dev Executes the `precompileCall` opcode.
function precompileCall(precompileParams, gasToBurn) -> ret {
// Compiler simulation for calling `precompileCall` opcode
ret := verbatim_2i_1o("precompile", precompileParams, gasToBurn)
}

////////////////////////////////////////////////////////////////
// FALLBACK
////////////////////////////////////////////////////////////////

// 1. Load raw calldata fat pointer
let calldataFatPtr := getCalldataPtr()

// 2. Parse calldata fat pointer
let ptrMemoryPage := and(shr(32, calldataFatPtr), UINT32_BIT_MASK())
let ptrStart := and(shr(64, calldataFatPtr), UINT32_BIT_MASK())
let ptrLength := and(shr(96, calldataFatPtr), UINT32_BIT_MASK())

// 3. Pack precompile parameters
let precompileParams := unsafePackPrecompileParams(
ptrStart, // input offset in bytes
ptrLength, // input length in bytes (safe to pass, never exceed `type(uint32).max`)
0, // output offset in words
1, // output length in words (NOTE: VM doesn't check this value for now, but this could change in future)
ptrMemoryPage, // memory page to read from
0, // memory page to write to (0 means write to heap)
0 // per precompile interpreted value (0 since circuit doesn't react on this value anyway)
)
// 4. Calculate number of required hash rounds per calldata
let numRounds := div(add(ptrLength, sub(BLOCK_SIZE(), 1)), BLOCK_SIZE())
let gasToPay := 0

// 5. Call precompile
let success := precompileCall(precompileParams, gasToPay)
if iszero(success) {
revert(0, 0)
}
return(0, 32)
}
}
}
67 changes: 59 additions & 8 deletions contracts/test-contracts/KeccakTest.sol
Expand Up @@ -11,15 +11,13 @@ contract KeccakTest {
bytes32 constant EMPTY_STRING_KECCAK = 0xc5d2460186f7233c927e7db2dcc703c0e500b653ca82273b7bfad8045d85a470;

// Just some computation-heavy function, it will be used to test out of gas
function infiniteFuction(uint256 n) pure public returns (uint256 sumOfSquares) {
for(uint i = 0; i < n; i++) {
function infiniteFuction(uint256 n) public pure returns (uint256 sumOfSquares) {
for (uint i = 0; i < n; i++) {
sumOfSquares += i * i;
}
}

function _loadFarCallABIIntoActivePtr(
uint256 _gas
) private view {
function _loadFarCallABIIntoActivePtr(uint256 _gas) private view {
uint256 farCallAbi = SystemContractsCaller.getFarCallABIWithEmptyFatPointer(
uint32(_gas),
// Only rollup is supported for now
Expand Down Expand Up @@ -78,7 +76,7 @@ contract KeccakTest {
}

function keccakUpgradeTest(
bytes calldata eraseCallData,
bytes calldata eraseCallData,
bytes calldata upgradeCalldata
) external returns (bytes32 hash) {
// Firstly, we reset keccak256 bytecode to be some random bytecode
Expand Down Expand Up @@ -111,9 +109,62 @@ contract KeccakTest {
// Now it should work again
hash = this.callKeccak(msg.data[0:0]);
require(hash == EMPTY_STRING_KECCAK, "Keccak should start working again");
}

}

function keccakPerformUpgrade(
bytes calldata upgradeCalldata
) external {
EfficientCall.mimicCall(
gasleft(),
address(DEPLOYER_SYSTEM_CONTRACT),
upgradeCalldata,
FORCE_DEPLOYER,
false,
false
);
}

function callKeccak(bytes calldata _data) external pure returns (bytes32 hash) {
hash = keccak256(_data);
}

function keccakValidationTest(
bytes calldata upgradeCalldata,
bytes calldata resetCalldata,
bytes[] calldata testInputs,
bytes32[] calldata expectedOutputs
) external {
require(testInputs.length == expectedOutputs.length, "mismatch between number of inputs and outputs");

// Firstly, we upgrade keccak256 bytecode to the correct version.
EfficientCall.mimicCall(
gasleft(),
address(DEPLOYER_SYSTEM_CONTRACT),
upgradeCalldata,
FORCE_DEPLOYER,
false,
false
);

bytes32[] memory result = new bytes32[](testInputs.length);

for (uint256 i = 0; i < testInputs.length; i++) {
bytes32 res = this.callKeccak(testInputs[i]);
result[i] = res;
}

for (uint256 i = 0; i < result.length; i++) {
require(result[i] == expectedOutputs[i], "hash was not calculated correctly");
}

// Upgrading it back to the original version:
EfficientCall.mimicCall(
gasleft(),
address(DEPLOYER_SYSTEM_CONTRACT),
resetCalldata,
FORCE_DEPLOYER,
false,
false
);
}
}
4 changes: 3 additions & 1 deletion package.json
Expand Up @@ -48,6 +48,8 @@
"fmt": "prettier --config prettier.js --write contracts/*.sol contracts/**/*.sol",
"preprocess": "rm -rf ./bootloader/build && yarn ts-node scripts/process.ts",
"deploy-preimages": "ts-node scripts/deploy-preimages.ts",
"compile-yul": "ts-node scripts/compile-yul.ts"
"compile-precompiles": "ts-node scripts/compile-yul.ts compile-precompiles",
"compile-bootloader": "ts-node scripts/compile-yul.ts compile-bootloader",
"build-yul": "yarn compile-precompiles && yarn preprocess && yarn compile-bootloader"
}
}
26 changes: 22 additions & 4 deletions scripts/compile-yul.ts
@@ -1,5 +1,6 @@
import * as hre from 'hardhat';
import * as fs from 'fs';
import { Command } from 'commander';
import { exec as _exec, spawn as _spawn } from 'child_process';

import { getZksolcUrl, saltFromUrl } from '@matterlabs/hardhat-zksync-solc';
Expand Down Expand Up @@ -88,10 +89,27 @@ class CompilerPaths {


async function main() {
await compileYulFolder('contracts');
await compileYulFolder('contracts/precompiles');
await compileYulFolder('bootloader/build');
await compileYulFolder('bootloader/tests');

const program = new Command();

program.version('0.1.0').name('compile yul').description('publish preimages for the L2 contracts');

program
.command('compile-bootloader')
.action(async () => {
await compileYulFolder('bootloader/build');
await compileYulFolder('bootloader/tests');
});

program
.command('compile-precompiles')
.action(async () => {
await compileYulFolder('contracts');
await compileYulFolder('contracts/precompiles');
await compileYulFolder('contracts/precompiles/test-contracts');
});

await program.parseAsync(process.argv);
}

main()
Expand Down
2 changes: 1 addition & 1 deletion scripts/quick-setup.sh
Expand Up @@ -10,7 +10,7 @@ cargo +nightly install --git https://github.com/matter-labs/era-test-node.git --

yarn
yarn build
yarn preprocess && yarn compile-yul
yarn build-yul
era_test_node run > /dev/null 2>&1 & export TEST_NODE_PID=$!
yarn test
kill $TEST_NODE_PID

0 comments on commit 123bb68

Please sign in to comment.