mirror of
https://github.com/0xbow-io/privacy-pools-core.git
synced 2026-01-09 09:27:58 -05:00
init repository
This commit is contained in:
11
.gitignore
vendored
Normal file
11
.gitignore
vendored
Normal file
@@ -0,0 +1,11 @@
|
||||
# General
|
||||
yarn-error.log
|
||||
node_modules
|
||||
.DS_STORE
|
||||
.vscode
|
||||
|
||||
# Config files
|
||||
.env
|
||||
|
||||
# Avoid ignoring gitkeep
|
||||
!/**/.gitkeep
|
||||
13
package.json
Normal file
13
package.json
Normal file
@@ -0,0 +1,13 @@
|
||||
{
|
||||
"name": "privacy-pool-core",
|
||||
"version": "1.0.0",
|
||||
"description": "Core repository for the Privacy Pool protocol circuits and smart contracts",
|
||||
"repository": "https://github.com/defi-wonderland/privacy-pool-core",
|
||||
"author": "Defi Wonderland",
|
||||
"license": "MIT",
|
||||
"private": true,
|
||||
"workspaces": [
|
||||
"packages/circuits",
|
||||
"packages/contracts"
|
||||
]
|
||||
}
|
||||
38
packages/circuits/.github/workflows/tests.yml
vendored
Normal file
38
packages/circuits/.github/workflows/tests.yml
vendored
Normal file
@@ -0,0 +1,38 @@
|
||||
name: tests
|
||||
|
||||
on:
|
||||
push:
|
||||
branches:
|
||||
- main
|
||||
|
||||
jobs:
|
||||
test:
|
||||
runs-on: ubuntu-latest
|
||||
|
||||
steps:
|
||||
- uses: actions/checkout@v4
|
||||
|
||||
- name: Install dependencies
|
||||
run: |
|
||||
sudo apt-get update
|
||||
sudo apt-get install --yes \
|
||||
build-essential \
|
||||
libgmp-dev \
|
||||
libsodium-dev \
|
||||
nasm \
|
||||
nlohmann-json3-dev
|
||||
|
||||
- name: Download Circom Binary v2.1.5
|
||||
run: |
|
||||
wget -qO /home/runner/work/circom https://github.com/iden3/circom/releases/download/v2.1.5/circom-linux-amd64
|
||||
chmod +x /home/runner/work/circom
|
||||
sudo mv /home/runner/work/circom /bin/circom
|
||||
|
||||
- name: Print Circom version
|
||||
run: circom --version
|
||||
|
||||
- name: Install dependencies
|
||||
run: yarn
|
||||
|
||||
- name: Run tests
|
||||
run: yarn test
|
||||
122
packages/circuits/.gitignore
vendored
Normal file
122
packages/circuits/.gitignore
vendored
Normal file
@@ -0,0 +1,122 @@
|
||||
# Logs
|
||||
logs
|
||||
*.log
|
||||
npm-debug.log*
|
||||
yarn-debug.log*
|
||||
yarn-error.log*
|
||||
lerna-debug.log*
|
||||
|
||||
# Diagnostic reports (https://nodejs.org/api/report.html)
|
||||
report.[0-9]*.[0-9]*.[0-9]*.[0-9]*.json
|
||||
|
||||
# Runtime data
|
||||
pids
|
||||
*.pid
|
||||
*.seed
|
||||
*.pid.lock
|
||||
|
||||
# Directory for instrumented libs generated by jscoverage/JSCover
|
||||
lib-cov
|
||||
|
||||
# Coverage directory used by tools like istanbul
|
||||
coverage
|
||||
*.lcov
|
||||
|
||||
# nyc test coverage
|
||||
.nyc_output
|
||||
|
||||
# Grunt intermediate storage (https://gruntjs.com/creating-plugins#storing-task-files)
|
||||
.grunt
|
||||
|
||||
# Bower dependency directory (https://bower.io/)
|
||||
bower_components
|
||||
|
||||
# node-waf configuration
|
||||
.lock-wscript
|
||||
|
||||
# Compiled binary addons (https://nodejs.org/api/addons.html)
|
||||
build/Release
|
||||
|
||||
# Dependency directories
|
||||
node_modules/
|
||||
jspm_packages/
|
||||
|
||||
# TypeScript v1 declaration files
|
||||
typings/
|
||||
|
||||
# TypeScript cache
|
||||
*.tsbuildinfo
|
||||
|
||||
# Optional npm cache directory
|
||||
.npm
|
||||
|
||||
# Optional eslint cache
|
||||
.eslintcache
|
||||
|
||||
# Microbundle cache
|
||||
.rpt2_cache/
|
||||
.rts2_cache_cjs/
|
||||
.rts2_cache_es/
|
||||
.rts2_cache_umd/
|
||||
|
||||
# Optional REPL history
|
||||
.node_repl_history
|
||||
|
||||
# Output of 'npm pack'
|
||||
*.tgz
|
||||
|
||||
# Yarn Integrity file
|
||||
.yarn-integrity
|
||||
|
||||
# dotenv environment variables file
|
||||
.env
|
||||
.env.test
|
||||
|
||||
# parcel-bundler cache (https://parceljs.org/)
|
||||
.cache
|
||||
|
||||
# Next.js build output
|
||||
.next
|
||||
|
||||
# Nuxt.js build / generate output
|
||||
.nuxt
|
||||
dist
|
||||
|
||||
# Gatsby files
|
||||
.cache/
|
||||
# Comment in the public line in if your project uses Gatsby and *not* Next.js
|
||||
# https://nextjs.org/blog/next-9-1#public-directory-support
|
||||
# public
|
||||
|
||||
# vuepress build output
|
||||
.vuepress/dist
|
||||
|
||||
# Serverless directories
|
||||
.serverless/
|
||||
|
||||
# FuseBox cache
|
||||
.fusebox/
|
||||
|
||||
# DynamoDB Local files
|
||||
.dynamodb/
|
||||
|
||||
# TernJS port file
|
||||
.tern-port
|
||||
|
||||
# builds
|
||||
build
|
||||
dist
|
||||
|
||||
# circuit-specific powers of tau are ignored
|
||||
*.ptau
|
||||
# universal ptaus not ignored
|
||||
!ptau/*
|
||||
# temporary ptaus are ignored
|
||||
tmp.ptau
|
||||
|
||||
# is this still a thing lol
|
||||
.DS_Store
|
||||
|
||||
# ignore auto generated test circuits
|
||||
circuits/test
|
||||
ptau
|
||||
7
packages/circuits/.mocharc.json
Normal file
7
packages/circuits/.mocharc.json
Normal file
@@ -0,0 +1,7 @@
|
||||
{
|
||||
"extension": ["ts"],
|
||||
"require": "ts-node/register",
|
||||
"spec": "tests/**/*.test.ts",
|
||||
"timeout": 100000,
|
||||
"exit": true
|
||||
}
|
||||
3
packages/circuits/.prettierrc.js
Normal file
3
packages/circuits/.prettierrc.js
Normal file
@@ -0,0 +1,3 @@
|
||||
module.exports = {
|
||||
printWidth: 120,
|
||||
};
|
||||
138
packages/circuits/README.md
Normal file
138
packages/circuits/README.md
Normal file
@@ -0,0 +1,138 @@
|
||||
# Circomkit Examples
|
||||
|
||||
In this repository, we are using [Circomkit](https://github.com/erhant/circomkit) to test some example circuits using Mocha. The circuits and the statements that they prove are as follows:
|
||||
|
||||
- **Multiplier**: "I know `n` factors that make up some number".
|
||||
- **Fibonacci**: "I know the `n`'th Fibonacci number".
|
||||
- **SHA256**: "I know the `n`-byte preimage of some SHA256 digest".
|
||||
- **Sudoku**: "I know the solution to some `(n^2)x(n^2)` Sudoku puzzle".
|
||||
- **Floating-Point Addition**: "I know two floating-point numbers that make up some number with `e` exponent and `m` mantissa bits." (adapted from [Berkeley ZKP MOOC 2023 - Lab](https://github.com/rdi-berkeley/zkp-mooc-lab)).
|
||||
|
||||
## CLI Usage
|
||||
|
||||
To use Circomkit CLI with a circuit, let's say for Sudoku 9x9, we follow the steps below:
|
||||
|
||||
1. We write a circuit config in `circuits.json` with the desired parameters. In this case, we are working with the 9x9 Sudoku solution circuit, and the board size is calculated by the square of our template parameter so we should give 3. Furthermore, `puzzle` is a public input so we should specify that too.
|
||||
|
||||
```json
|
||||
{
|
||||
"sudoku_9x9": {
|
||||
"file": "sudoku",
|
||||
"template": "Sudoku",
|
||||
"pubs": ["puzzle"],
|
||||
"params": [3]
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
2. Compile the circuit with Circomkit, providing the same circuit name as in `circuits.json`:
|
||||
|
||||
```sh
|
||||
npx circomkit compile sudoku_9x9
|
||||
|
||||
# print circuit info if you want to
|
||||
npx circomkit info sudoku_9x9
|
||||
```
|
||||
|
||||
3. Commence circuit-specific setup. Normally, this requires us to download a Phase-1 PTAU file and provide it's path; however, Circomkit can determine the required PTAU and download it automatically when using `bn128` curve, thanks to [Perpetual Powers of Tau](https://github.com/privacy-scaling-explorations/perpetualpowersoftau). In this case, `sudoku_9x9` circuit has 4617 constraints, so Circomkit will download `powersOfTau28_hez_final_13.ptau` (see [here](https://github.com/iden3/snarkjs#7-prepare-phase-2)).
|
||||
|
||||
```sh
|
||||
npx circomkit setup sudoku_9x9
|
||||
|
||||
# alternative: provide the PTAU yourself
|
||||
npx circomkit setup sudoku_9x9 <path-to-ptau>
|
||||
```
|
||||
|
||||
4. Prepare your input file under `./inputs/sudoku_9x9/default.json`.
|
||||
|
||||
```json
|
||||
{
|
||||
"solution": [
|
||||
[1, 9, 4, 8, 6, 5, 2, 3, 7],
|
||||
[7, 3, 5, 4, 1, 2, 9, 6, 8],
|
||||
[8, 6, 2, 3, 9, 7, 1, 4, 5],
|
||||
[9, 2, 1, 7, 4, 8, 3, 5, 6],
|
||||
[6, 7, 8, 5, 3, 1, 4, 2, 9],
|
||||
[4, 5, 3, 9, 2, 6, 8, 7, 1],
|
||||
[3, 8, 9, 6, 5, 4, 7, 1, 2],
|
||||
[2, 4, 6, 1, 7, 9, 5, 8, 3],
|
||||
[5, 1, 7, 2, 8, 3, 6, 9, 4]
|
||||
],
|
||||
"puzzle": [
|
||||
[0, 0, 0, 8, 6, 0, 2, 3, 0],
|
||||
[7, 0, 5, 0, 0, 0, 9, 0, 8],
|
||||
[0, 6, 0, 3, 0, 7, 0, 4, 0],
|
||||
[0, 2, 0, 7, 0, 8, 0, 5, 0],
|
||||
[0, 7, 8, 5, 0, 0, 0, 0, 0],
|
||||
[4, 0, 0, 9, 0, 6, 0, 7, 0],
|
||||
[3, 0, 9, 0, 5, 0, 7, 0, 2],
|
||||
[0, 4, 0, 1, 0, 9, 0, 8, 0],
|
||||
[5, 0, 7, 0, 8, 0, 0, 9, 4]
|
||||
]
|
||||
}
|
||||
```
|
||||
|
||||
5. We are ready to create a proof!
|
||||
|
||||
```sh
|
||||
npx circomkit prove sudoku_9x9 default
|
||||
```
|
||||
|
||||
6. We can then verify our proof. You can try and modify the public input at `./build/sudoku_9x9/default/public.json` and see if the proof verifies or not!
|
||||
|
||||
```sh
|
||||
npx circomkit verify sudoku_9x9 default
|
||||
```
|
||||
|
||||
## In-Code Usage
|
||||
|
||||
If you would like to use Circomkit within the code itself, rather than the CLI, you can see the example at `src/index.ts`. You can `yarn start` to see it in action.
|
||||
|
||||
```ts
|
||||
// create circomkit
|
||||
const circomkit = new Circomkit({
|
||||
protocol: "groth16",
|
||||
});
|
||||
|
||||
// artifacts output at `build/multiplier_3` directory
|
||||
await circomkit.compile("multiplier_3", {
|
||||
file: "multiplier",
|
||||
template: "Multiplier",
|
||||
params: [3],
|
||||
});
|
||||
|
||||
// proof & public signals at `build/multiplier_3/my_input` directory
|
||||
await circomkit.prove("multiplier_3", "my_input", { in: [3, 5, 7] });
|
||||
|
||||
// verify with proof & public signals at `build/multiplier_3/my_input`
|
||||
const ok = await circomkit.verify("multiplier_3", "my_input");
|
||||
if (ok) {
|
||||
circomkit.log("Proof verified!", "success");
|
||||
} else {
|
||||
circomkit.log("Verification failed.", "error");
|
||||
}
|
||||
```
|
||||
|
||||
## Configuration
|
||||
|
||||
Circomkit checks for `circomkit.json` to override it's default configurations. We could for example change the target version, prime field and the proof system by setting `circomkit.json` to be:
|
||||
|
||||
```json
|
||||
{
|
||||
"version": "2.1.2",
|
||||
"protocol": "plonk",
|
||||
"prime": "bls12381"
|
||||
}
|
||||
```
|
||||
|
||||
## Testing
|
||||
|
||||
You can use the following commands to test the circuits:
|
||||
|
||||
```sh
|
||||
# test everything
|
||||
yarn test
|
||||
|
||||
# test a specific circuit
|
||||
yarn test -g <circuit-name>
|
||||
```
|
||||
5
packages/circuits/circomkit.json
Normal file
5
packages/circuits/circomkit.json
Normal file
@@ -0,0 +1,5 @@
|
||||
{
|
||||
"version": "2.1.2",
|
||||
"proofSystem": "plonk",
|
||||
"curve": "bn128"
|
||||
}
|
||||
39
packages/circuits/circuits.json
Normal file
39
packages/circuits/circuits.json
Normal file
@@ -0,0 +1,39 @@
|
||||
{
|
||||
"multiplier_3": {
|
||||
"file": "multiplier",
|
||||
"template": "Multiplier",
|
||||
"params": [3]
|
||||
},
|
||||
"sha256_32": {
|
||||
"file": "sha256",
|
||||
"template": "Sha256Bytes",
|
||||
"params": [32]
|
||||
},
|
||||
"sudoku_9x9": {
|
||||
"file": "sudoku",
|
||||
"template": "Sudoku",
|
||||
"pubs": ["puzzle"],
|
||||
"params": [3]
|
||||
},
|
||||
"sudoku_4x4": {
|
||||
"file": "sudoku",
|
||||
"template": "Sudoku",
|
||||
"pubs": ["puzzle"],
|
||||
"params": [2]
|
||||
},
|
||||
"fp64": {
|
||||
"file": "float_add",
|
||||
"template": "FloatAdd",
|
||||
"params": [11, 52]
|
||||
},
|
||||
"fp32": {
|
||||
"file": "float_add",
|
||||
"template": "FloatAdd",
|
||||
"params": [8, 23]
|
||||
},
|
||||
"fibonacci_11": {
|
||||
"file": "fibonacci",
|
||||
"template": "Fibonacci",
|
||||
"params": [11]
|
||||
}
|
||||
}
|
||||
33
packages/circuits/circuits/fibonacci.circom
Normal file
33
packages/circuits/circuits/fibonacci.circom
Normal file
@@ -0,0 +1,33 @@
|
||||
pragma circom 2.0.0;
|
||||
|
||||
// Fibonacci with custom starting numbers
|
||||
template Fibonacci(n) {
|
||||
assert(n >= 2);
|
||||
signal input in[2];
|
||||
signal output out;
|
||||
|
||||
signal fib[n+1];
|
||||
fib[0] <== in[0];
|
||||
fib[1] <== in[1];
|
||||
for (var i = 2; i <= n; i++) {
|
||||
fib[i] <== fib[i-2] + fib[i-1];
|
||||
}
|
||||
|
||||
out <== fib[n];
|
||||
}
|
||||
|
||||
// Fibonacci with custom starting numbers, recursive & inefficient
|
||||
template FibonacciRecursive(n) {
|
||||
signal input in[2];
|
||||
signal output out;
|
||||
component f1, f2;
|
||||
if (n <= 1) {
|
||||
out <== in[n];
|
||||
} else {
|
||||
f1 = FibonacciRecursive(n-1);
|
||||
f1.in <== in;
|
||||
f2 = FibonacciRecursive(n-2);
|
||||
f2.in <== in;
|
||||
out <== f1.out + f2.out;
|
||||
}
|
||||
}
|
||||
417
packages/circuits/circuits/float_add.circom
Normal file
417
packages/circuits/circuits/float_add.circom
Normal file
@@ -0,0 +1,417 @@
|
||||
pragma circom 2.0.0;
|
||||
|
||||
// circuits adapted from https://github.com/rdi-berkeley/zkp-mooc-lab
|
||||
|
||||
include "circomlib/circuits/comparators.circom";
|
||||
include "circomlib/circuits/switcher.circom";
|
||||
include "circomlib/circuits/gates.circom";
|
||||
include "circomlib/circuits/bitify.circom";
|
||||
|
||||
/*
|
||||
* Finds Math.floor(log2(n))
|
||||
*/
|
||||
function log2(n) {
|
||||
var tmp = 1, ans = 1;
|
||||
while (tmp < n) {
|
||||
ans++;
|
||||
tmp *= 2;
|
||||
}
|
||||
return ans;
|
||||
}
|
||||
|
||||
/*
|
||||
* Basically `out = cond ? ifTrue : ifFalse`
|
||||
*/
|
||||
template IfElse() {
|
||||
signal input cond;
|
||||
signal input ifTrue;
|
||||
signal input ifFalse;
|
||||
signal output out;
|
||||
|
||||
// cond * T - cond * F + F
|
||||
// 0 * T - 0 * F + F = 0 - 0 + F = F
|
||||
// 1 * T - 1 * F + F = T - F + F = T
|
||||
out <== cond * (ifTrue - ifFalse) + ifFalse;
|
||||
}
|
||||
|
||||
/*
|
||||
* Outputs `out` = 1 if `in` is at most `b` bits long, and 0 otherwise.
|
||||
*/
|
||||
template CheckBitLength(b) {
|
||||
assert(b < 254);
|
||||
signal input in;
|
||||
signal output out;
|
||||
|
||||
// compute b-bit representation of the number
|
||||
signal bits[b];
|
||||
var sum_of_bits = 0;
|
||||
for (var i = 0; i < b; i++) {
|
||||
bits[i] <-- (in >> i) & 1;
|
||||
bits[i] * (1 - bits[i]) === 0;
|
||||
sum_of_bits += (2 ** i) * bits[i];
|
||||
}
|
||||
|
||||
// check if sum is equal to number itself
|
||||
component eq = IsEqual();
|
||||
eq.in[0] <== sum_of_bits;
|
||||
eq.in[1] <== in;
|
||||
out <== eq.out;
|
||||
}
|
||||
|
||||
/*
|
||||
* Enforces the well-formedness of an exponent-mantissa pair (e, m), which is defined as follows:
|
||||
* if `e` is zero, then `m` must be zero
|
||||
* else, `e` must be at most `k` bits long, and `m` must be in the range [2^p, 2^p+1)
|
||||
*/
|
||||
template CheckWellFormedness(k, p) {
|
||||
signal input e;
|
||||
signal input m;
|
||||
|
||||
// check if `e` is zero
|
||||
component is_e_zero = IsZero();
|
||||
is_e_zero.in <== e;
|
||||
|
||||
// Case I: `e` is zero
|
||||
//// `m` must be zero
|
||||
component is_m_zero = IsZero();
|
||||
is_m_zero.in <== m;
|
||||
|
||||
// Case II: `e` is nonzero
|
||||
//// `e` is `k` bits
|
||||
component check_e_bits = CheckBitLength(k);
|
||||
check_e_bits.in <== e;
|
||||
//// `m` is `p`+1 bits with the MSB equal to 1
|
||||
//// equivalent to check `m` - 2^`p` is in `p` bits
|
||||
component check_m_bits = CheckBitLength(p);
|
||||
check_m_bits.in <== m - (1 << p);
|
||||
|
||||
// choose the right checks based on `is_e_zero`
|
||||
component if_else = IfElse();
|
||||
if_else.cond <== is_e_zero.out;
|
||||
if_else.ifTrue <== is_m_zero.out;
|
||||
//// check_m_bits.out * check_e_bits.out is equivalent to check_m_bits.out AND check_e_bits.out
|
||||
if_else.ifFalse <== check_m_bits.out * check_e_bits.out;
|
||||
|
||||
// assert that those checks passed
|
||||
if_else.out === 1;
|
||||
}
|
||||
|
||||
/*
|
||||
* Right-shifts `x` by `shift` bits to output `y`, where `shift` is a public circuit parameter.
|
||||
*/
|
||||
template RightShift(b, shift) {
|
||||
assert(shift < b);
|
||||
signal input x;
|
||||
signal output y;
|
||||
|
||||
// convert number to bits
|
||||
component x_bits = Num2Bits(b);
|
||||
x_bits.in <== x;
|
||||
|
||||
// do the shifting
|
||||
signal y_bits[b-shift];
|
||||
for (var i = 0; i < b-shift; i++) {
|
||||
y_bits[i] <== x_bits.out[shift+i];
|
||||
}
|
||||
|
||||
// convert shifted bits to number
|
||||
component y_num = Bits2Num(b-shift);
|
||||
y_num.in <== y_bits;
|
||||
y <== y_num.out;
|
||||
}
|
||||
|
||||
/*
|
||||
* Rounds the input floating-point number and checks to ensure that rounding does not make the mantissa unnormalized.
|
||||
* Rounding is necessary to prevent the bitlength of the mantissa from growing with each successive operation.
|
||||
* The input is a normalized floating-point number (e, m) with precision `P`, where `e` is a `k`-bit exponent and `m` is a `P`+1-bit mantissa.
|
||||
* The output is a normalized floating-point number (e_out, m_out) representing the same value with a lower precision `p`.
|
||||
*/
|
||||
template RoundAndCheck(k, p, P) {
|
||||
signal input e;
|
||||
signal input m;
|
||||
signal output e_out;
|
||||
signal output m_out;
|
||||
assert(P > p);
|
||||
|
||||
// check if no overflow occurs
|
||||
component if_no_overflow = LessThan(P+1);
|
||||
if_no_overflow.in[0] <== m;
|
||||
if_no_overflow.in[1] <== (1 << (P+1)) - (1 << (P-p-1));
|
||||
signal no_overflow <== if_no_overflow.out;
|
||||
|
||||
var round_amt = P-p;
|
||||
// Case I: no overflow
|
||||
// compute (m + 2^{round_amt-1}) >> round_amt
|
||||
var m_prime = m + (1 << (round_amt-1));
|
||||
component right_shift = RightShift(P+2, round_amt);
|
||||
right_shift.x <== m_prime;
|
||||
var m_out_1 = right_shift.y;
|
||||
var e_out_1 = e;
|
||||
|
||||
// Case II: overflow
|
||||
var e_out_2 = e + 1;
|
||||
var m_out_2 = (1 << p);
|
||||
|
||||
// select right output based on no_overflow
|
||||
component if_else[2];
|
||||
for (var i = 0; i < 2; i++) {
|
||||
if_else[i] = IfElse();
|
||||
if_else[i].cond <== no_overflow;
|
||||
}
|
||||
if_else[0].ifTrue <== e_out_1;
|
||||
if_else[0].ifFalse <== e_out_2;
|
||||
if_else[1].ifTrue <== m_out_1;
|
||||
if_else[1].ifFalse <== m_out_2;
|
||||
e_out <== if_else[0].out;
|
||||
m_out <== if_else[1].out;
|
||||
}
|
||||
|
||||
template Num2BitsWithSkipChecks(b) {
|
||||
signal input in;
|
||||
signal input skip_checks;
|
||||
signal output out[b];
|
||||
|
||||
for (var i = 0; i < b; i++) {
|
||||
out[i] <-- (in >> i) & 1;
|
||||
out[i] * (1 - out[i]) === 0;
|
||||
}
|
||||
var sum_of_bits = 0;
|
||||
for (var i = 0; i < b; i++) {
|
||||
sum_of_bits += (2 ** i) * out[i];
|
||||
}
|
||||
|
||||
// is always true if skip_checks is 1
|
||||
(sum_of_bits - in) * (1 - skip_checks) === 0;
|
||||
}
|
||||
|
||||
template LessThanWithSkipChecks(n) {
|
||||
assert(n <= 252);
|
||||
signal input in[2];
|
||||
signal input skip_checks;
|
||||
signal output out;
|
||||
|
||||
component n2b = Num2BitsWithSkipChecks(n+1);
|
||||
n2b.in <== in[0] + (1<<n) - in[1];
|
||||
n2b.skip_checks <== skip_checks;
|
||||
out <== 1-n2b.out[n];
|
||||
}
|
||||
|
||||
/*
|
||||
* Left-shifts `x` by `shift` bits to output `y`.
|
||||
* Enforces 0 <= `shift` < `shift_bound`.
|
||||
* If `skip_checks` = 1, then we don't care about the output
|
||||
* and the `shift_bound` constraint is not enforced.
|
||||
*/
|
||||
template LeftShift(shift_bound) {
|
||||
signal input x;
|
||||
signal input shift;
|
||||
signal input skip_checks;
|
||||
signal output y;
|
||||
|
||||
// find number of bits in shift_bound
|
||||
var n = log2(shift_bound) + 1;
|
||||
|
||||
// convert "shift" to bits
|
||||
component shift_bits = Num2BitsWithSkipChecks(n);
|
||||
shift_bits.in <== shift;
|
||||
shift_bits.skip_checks <== skip_checks;
|
||||
|
||||
// check "shift" < "shift_bound"
|
||||
component less_than = LessThanWithSkipChecks(n);
|
||||
less_than.in[0] <== shift;
|
||||
less_than.in[1] <== shift_bound;
|
||||
less_than.skip_checks <== skip_checks;
|
||||
(less_than.out - 1) * (1 - skip_checks) === 0;
|
||||
|
||||
// compute pow2_shift from bits
|
||||
// represents the shift amount
|
||||
var pow2_shift = 1;
|
||||
component muxes[n];
|
||||
for (var i = 0; i < n; i++) {
|
||||
muxes[i] = IfElse();
|
||||
muxes[i].cond <== shift_bits.out[i];
|
||||
muxes[i].ifTrue <== pow2_shift * (2 ** (2 ** i));
|
||||
muxes[i].ifFalse <== pow2_shift;
|
||||
pow2_shift = muxes[i].out;
|
||||
}
|
||||
|
||||
// if skip checks, set pow2_shift to 0
|
||||
component if_else = IfElse();
|
||||
if_else.cond <== skip_checks;
|
||||
if_else.ifTrue <== 0;
|
||||
if_else.ifFalse <== pow2_shift;
|
||||
pow2_shift = if_else.out; // not <== because it's a variable
|
||||
|
||||
// do the shift
|
||||
y <== x * pow2_shift;
|
||||
|
||||
}
|
||||
|
||||
/*
|
||||
* Find the Most-Significant Non-Zero Bit (MSNZB) of `in`, where `in` is assumed to be non-zero value of `b` bits.
|
||||
* Outputs the MSNZB as a one-hot vector `one_hot` of `b` bits, where `one_hot`[i] = 1 if MSNZB(`in`) = i and 0 otherwise.
|
||||
* The MSNZB is output as a one-hot vector to reduce the number of constraints in the subsequent `Normalize` template.
|
||||
* Enforces that `in` is non-zero as MSNZB(0) is undefined.
|
||||
* If `skip_checks` = 1, then we don't care about the output and the non-zero constraint is not enforced.
|
||||
*/
|
||||
template MSNZB(b) {
|
||||
signal input in;
|
||||
signal input skip_checks;
|
||||
signal output one_hot[b];
|
||||
|
||||
// compute ell, ensuring that it is made of bits too
|
||||
for (var i = 0; i < b; i++) {
|
||||
var temp;
|
||||
if (((1 << i) <= in) && (in < (1 << (i + 1)))) {
|
||||
temp = 1;
|
||||
} else {
|
||||
temp = 0;
|
||||
}
|
||||
one_hot[i] <-- temp;
|
||||
}
|
||||
|
||||
// verify that one_hot only has bits, and has only one set bit
|
||||
var sum_of_bits = 0;
|
||||
for (var i = 0; i < b; i++) {
|
||||
sum_of_bits += one_hot[i];
|
||||
one_hot[i] * (1 - one_hot[i]) === 0; // is bit
|
||||
}
|
||||
(1 - sum_of_bits) * (1 - skip_checks) === 0;
|
||||
|
||||
// verify that the set bit is at correct place
|
||||
var pow2_ell = 0;
|
||||
var pow2_ell_plus1 = 0;
|
||||
for (var i = 0; i < b; i++) {
|
||||
pow2_ell += one_hot[i] * (1 << i);
|
||||
pow2_ell_plus1 += one_hot[i] * (1 << (i + 1));
|
||||
}
|
||||
component lt1 = LessThan(b+1);
|
||||
lt1.in[0] <== in;
|
||||
lt1.in[1] <== pow2_ell_plus1;
|
||||
(lt1.out - 1) * (1 - skip_checks) === 0;
|
||||
component lt2 = LessThan(b);
|
||||
lt2.in[0] <== pow2_ell - 1;
|
||||
lt2.in[1] <== in;
|
||||
(lt2.out - 1) * (1 - skip_checks) === 0;
|
||||
}
|
||||
|
||||
/*
|
||||
* Normalizes the input floating-point number.
|
||||
* The input is a floating-point number with a `k`-bit exponent `e` and a `P`+1-bit *unnormalized* mantissa `m` with precision `p`, where `m` is assumed to be non-zero.
|
||||
* The output is a floating-point number representing the same value with exponent `e_out` and a *normalized* mantissa `m_out` of `P`+1-bits and precision `P`.
|
||||
* Enforces that `m` is non-zero as a zero-value can not be normalized.
|
||||
* If `skip_checks` = 1, then we don't care about the output and the non-zero constraint is not enforced.
|
||||
*/
|
||||
template Normalize(k, p, P) {
|
||||
signal input e;
|
||||
signal input m;
|
||||
signal input skip_checks;
|
||||
signal output e_out;
|
||||
signal output m_out;
|
||||
assert(P > p);
|
||||
|
||||
// compute ell = MSNZB
|
||||
component msnzb = MSNZB(P+1);
|
||||
msnzb.in <== m;
|
||||
msnzb.skip_checks <== skip_checks;
|
||||
|
||||
// compute ell and L = 2 ** (P - ell)
|
||||
var ell, L;
|
||||
for (var i = 0; i < P+1; i++) {
|
||||
ell += msnzb.one_hot[i] * i;
|
||||
L += msnzb.one_hot[i] * (1 << (P - i));
|
||||
}
|
||||
|
||||
// return
|
||||
e_out <== e + ell - p;
|
||||
m_out <== m * L;
|
||||
}
|
||||
|
||||
/*
|
||||
* Adds two floating-point numbers.
|
||||
* The inputs are normalized floating-point numbers with `k`-bit exponents `e` and `p`+1-bit mantissas `m` with scale `p`.
|
||||
* Does not assume that the inputs are well-formed and makes appropriate checks for the same.
|
||||
* The output is a normalized floating-point number with exponent `e_out` and mantissa `m_out` of `p`+1-bits and scale `p`.
|
||||
* Enforces that inputs are well-formed.
|
||||
*/
|
||||
template FloatAdd(k, p) {
|
||||
signal input e[2];
|
||||
signal input m[2];
|
||||
signal output e_out;
|
||||
signal output m_out;
|
||||
|
||||
// check well formedness
|
||||
component well_form[2];
|
||||
for (var i = 0; i < 2; i++) {
|
||||
well_form[i] = CheckWellFormedness(k, p);
|
||||
well_form[i].e <== e[i];
|
||||
well_form[i].m <== m[i];
|
||||
}
|
||||
|
||||
// find the larger magnitude
|
||||
var magn[2];
|
||||
component larger_magn = LessThan(k+p+1);
|
||||
for (var i = 0; i < 2; i++) {
|
||||
magn[i] = (e[i] * (1 << (p+1))) + m[i];
|
||||
larger_magn.in[i] <== magn[i];
|
||||
}
|
||||
signal is_input2_larger <== larger_magn.out;
|
||||
|
||||
// arrange by magnitude
|
||||
var input_1[2] = [e[0], m[0]];
|
||||
var input_2[2] = [e[1], m[1]];
|
||||
component switcher[2];
|
||||
for (var i = 0; i < 2; i++) {
|
||||
switcher[i] = Switcher();
|
||||
switcher[i].sel <== is_input2_larger;
|
||||
switcher[i].L <== input_1[i];
|
||||
switcher[i].R <== input_2[i];
|
||||
}
|
||||
var alpha_e = switcher[0].outL;
|
||||
var alpha_m = switcher[1].outL;
|
||||
var beta_e = switcher[0].outR;
|
||||
var beta_m = switcher[1].outR;
|
||||
|
||||
// if-else part
|
||||
var diff_e = alpha_e - beta_e;
|
||||
component compare_diff_e = LessThan(k);
|
||||
compare_diff_e.in[0] <== p+1;
|
||||
compare_diff_e.in[1] <== diff_e;
|
||||
component is_alpha_e_zero = IsZero();
|
||||
is_alpha_e_zero.in <== alpha_e;
|
||||
|
||||
//// case 1
|
||||
component or = OR();
|
||||
or.a <== compare_diff_e.out;
|
||||
or.b <== is_alpha_e_zero.out;
|
||||
signal is_case_1 <== or.out; // true branch
|
||||
var case_1_output[2] = [alpha_e, alpha_m];
|
||||
|
||||
//// case 2
|
||||
component shl = LeftShift(p+2);
|
||||
shl.x <== alpha_m;
|
||||
shl.shift <== diff_e;
|
||||
shl.skip_checks <== is_case_1; // skip if this isnt the case
|
||||
alpha_m = shl.y;
|
||||
var mantissa = alpha_m + beta_m;
|
||||
var exponent = beta_e;
|
||||
component normalize = Normalize(k, p, 2*p + 1);
|
||||
normalize.m <== mantissa;
|
||||
normalize.e <== exponent;
|
||||
normalize.skip_checks <== is_case_1;
|
||||
component rnc = RoundAndCheck(k, p, 2*p + 1);
|
||||
rnc.m <== normalize.m_out;
|
||||
rnc.e <== normalize.e_out;
|
||||
var case_2_output[2] = [rnc.e_out, rnc.m_out];
|
||||
|
||||
// return
|
||||
component if_else[2];
|
||||
for (var i = 0; i < 2; i++) {
|
||||
if_else[i] = IfElse();
|
||||
if_else[i].cond <== is_case_1;
|
||||
if_else[i].ifTrue <== case_1_output[i];
|
||||
if_else[i].ifFalse <== case_2_output[i];
|
||||
}
|
||||
e_out <== if_else[0].out;
|
||||
m_out <== if_else[1].out;
|
||||
}
|
||||
53
packages/circuits/circuits/multiplier.circom
Normal file
53
packages/circuits/circuits/multiplier.circom
Normal file
@@ -0,0 +1,53 @@
|
||||
pragma circom 2.0.0;
|
||||
|
||||
template MultiplicationGate() {
|
||||
signal input in[2];
|
||||
signal output out <== in[0] * in[1];
|
||||
}
|
||||
|
||||
template Multiplier(N) {
|
||||
assert(N > 1);
|
||||
signal input in[N];
|
||||
signal output out;
|
||||
component gate[N-1];
|
||||
|
||||
// instantiate gates
|
||||
for (var i = 0; i < N-1; i++) {
|
||||
gate[i] = MultiplicationGate();
|
||||
}
|
||||
|
||||
// multiply
|
||||
gate[0].in <== [in[0], in[1]];
|
||||
for (var i = 0; i < N-2; i++) {
|
||||
gate[i+1].in <== [gate[i].out, in[i+2]];
|
||||
}
|
||||
out <== gate[N-2].out;
|
||||
}
|
||||
|
||||
// Alternative way using anonymous components
|
||||
template MultiplierAnonymous(N) {
|
||||
assert(N > 1);
|
||||
signal input in[N];
|
||||
signal output out;
|
||||
|
||||
signal inner[N-1];
|
||||
inner[0] <== MultiplicationGate()([in[0], in[1]]);
|
||||
for(var i = 0; i < N-2; i++) {
|
||||
inner[i+1] <== MultiplicationGate()([inner[i], in[i+2]]);
|
||||
}
|
||||
out <== inner[N-2];
|
||||
}
|
||||
|
||||
// Alternative way without the gate component
|
||||
template MultiplierSimple(N) {
|
||||
assert(N > 1);
|
||||
signal input in[N];
|
||||
signal output out;
|
||||
|
||||
signal inner[N-1];
|
||||
inner[0] <== in[0] * in[1];
|
||||
for(var i = 2; i < N; i++) {
|
||||
inner[i-1] <== inner[i-2] * in[i];
|
||||
}
|
||||
out <== inner[N-2];
|
||||
}
|
||||
42
packages/circuits/circuits/sha256.circom
Normal file
42
packages/circuits/circuits/sha256.circom
Normal file
@@ -0,0 +1,42 @@
|
||||
pragma circom 2.0.0;
|
||||
|
||||
include "circomlib/circuits/sha256/sha256.circom";
|
||||
include "circomlib/circuits/bitify.circom";
|
||||
|
||||
/**
|
||||
* Wrapper around SHA256 to support bytes as input instead of bits
|
||||
* @param N The number of input bytes
|
||||
* @input in The input bytes
|
||||
* @output out The SHA256 output of the n input bytes, in bytes
|
||||
*
|
||||
* SOURCE: https://github.com/celer-network/zk-benchmark/blob/main/circom/circuits/sha256/sha256_bytes.circom
|
||||
*/
|
||||
template Sha256Bytes(N) {
|
||||
signal input in[N];
|
||||
signal output out[32];
|
||||
|
||||
// convert input bytes to bits
|
||||
component byte_to_bits[N];
|
||||
for (var i = 0; i < N; i++) {
|
||||
byte_to_bits[i] = Num2Bits(8);
|
||||
byte_to_bits[i].in <== in[i];
|
||||
}
|
||||
|
||||
// sha256 over bits
|
||||
component sha256 = Sha256(N*8);
|
||||
for (var i = 0; i < N; i++) {
|
||||
for (var j = 0; j < 8; j++) {
|
||||
sha256.in[i*8+j] <== byte_to_bits[i].out[7-j];
|
||||
}
|
||||
}
|
||||
|
||||
// convert output bytes to bits
|
||||
component bits_to_bytes[32];
|
||||
for (var i = 0; i < 32; i++) {
|
||||
bits_to_bytes[i] = Bits2Num(8);
|
||||
for (var j = 0; j < 8; j++) {
|
||||
bits_to_bytes[i].in[7-j] <== sha256.out[i*8+j];
|
||||
}
|
||||
out[i] <== bits_to_bytes[i].out;
|
||||
}
|
||||
}
|
||||
136
packages/circuits/circuits/sudoku.circom
Normal file
136
packages/circuits/circuits/sudoku.circom
Normal file
@@ -0,0 +1,136 @@
|
||||
pragma circom 2.0.0;
|
||||
|
||||
include "circomlib/circuits/bitify.circom";
|
||||
|
||||
// Finds Math.floor(log2(n))
|
||||
function log2(n) {
|
||||
var tmp = 1, ans = 1;
|
||||
while (tmp < n) {
|
||||
ans++;
|
||||
tmp *= 2;
|
||||
}
|
||||
return ans;
|
||||
}
|
||||
|
||||
// Assert that two elements are not equal
|
||||
template NonEqual() {
|
||||
signal input in[2];
|
||||
signal inv;
|
||||
|
||||
// we check if (in[0] - in[1] != 0)
|
||||
// because 1/0 results in 0, so the constraint won't hold
|
||||
inv <-- 1 / (in[1] - in[0]);
|
||||
inv * (in[1] - in[0]) === 1;
|
||||
}
|
||||
|
||||
// Assert that number is representable by b-bits
|
||||
template AssertBitLength(b) {
|
||||
assert(b < 254);
|
||||
signal input in;
|
||||
|
||||
// compute b-bit representation of the number
|
||||
signal bits[b];
|
||||
var sum_of_bits = 0;
|
||||
for (var i = 0; i < b; i++) {
|
||||
bits[i] <-- (in >> i) & 1;
|
||||
bits[i] * (1 - bits[i]) === 0;
|
||||
sum_of_bits += (2 ** i) * bits[i];
|
||||
}
|
||||
|
||||
// check if sum is equal to number itself
|
||||
in === sum_of_bits;
|
||||
}
|
||||
|
||||
// Checks that `in` is in range [MIN, MAX]
|
||||
template InRange(MIN, MAX) {
|
||||
assert(MIN < MAX);
|
||||
signal input in;
|
||||
|
||||
// number of bits to represent MAX
|
||||
var b = log2(MAX) + 1;
|
||||
|
||||
component lowerBound = AssertBitLength(b);
|
||||
component upperBound = AssertBitLength(b);
|
||||
lowerBound.in <== in - MIN; // e.g. 1 - 1 = 0 (for 0 <= in)
|
||||
upperBound.in <== in + (2 ** b) - MAX - 1; // e.g. 9 + (15 - 9) = 15 (for in <= 15)
|
||||
}
|
||||
|
||||
// Assert that all given values are unique
|
||||
template Distinct(n) {
|
||||
signal input in[n];
|
||||
component nonEqual[n][n]; // TODO; has extra comps here
|
||||
for(var i = 0; i < n; i++){
|
||||
for(var j = 0; j < i; j++){
|
||||
nonEqual[i][j] = NonEqual();
|
||||
nonEqual[i][j].in <== [in[i], in[j]];
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
template Sudoku(n_sqrt) {
|
||||
var n = n_sqrt * n_sqrt;
|
||||
signal input solution[n][n]; // solution is a 2D array of numbers
|
||||
signal input puzzle[n][n]; // puzzle is the same, but a zero indicates a blank
|
||||
|
||||
// ensure that solution & puzzle agrees
|
||||
for (var row_i = 0; row_i < n; row_i++) {
|
||||
for (var col_i = 0; col_i < n; col_i++) {
|
||||
// puzzle is either empty (0), or the same as solution
|
||||
puzzle[row_i][col_i] * (puzzle[row_i][col_i] - solution[row_i][col_i]) === 0;
|
||||
}
|
||||
}
|
||||
|
||||
// ensure all values in the solution are in range
|
||||
component inRange[n][n];
|
||||
for (var row_i = 0; row_i < n; row_i++) {
|
||||
for (var col_i = 0; col_i < n; col_i++) {
|
||||
inRange[row_i][col_i] = InRange(1, n);
|
||||
inRange[row_i][col_i].in <== solution[row_i][col_i];
|
||||
}
|
||||
}
|
||||
|
||||
// ensure all values in the solution are distinct
|
||||
component distinctRows[n];
|
||||
for (var row_i = 0; row_i < n; row_i++) {
|
||||
for (var col_i = 0; col_i < n; col_i++) {
|
||||
if (row_i == 0) {
|
||||
distinctRows[col_i] = Distinct(n);
|
||||
}
|
||||
distinctRows[col_i].in[row_i] <== solution[row_i][col_i];
|
||||
}
|
||||
}
|
||||
component distinctCols[n];
|
||||
for (var col_i = 0; col_i < n; col_i++) {
|
||||
for (var row_i = 0; row_i < n; row_i++) {
|
||||
if (col_i == 0) {
|
||||
distinctCols[row_i] = Distinct(n);
|
||||
}
|
||||
distinctCols[row_i].in[col_i] <== solution[row_i][col_i];
|
||||
}
|
||||
}
|
||||
|
||||
// ensure that all values in squares are distinct
|
||||
component distinctSquares[n];
|
||||
var s_i = 0;
|
||||
for (var sr_i = 0; sr_i < n_sqrt; sr_i++) {
|
||||
for (var sc_i = 0; sc_i < n_sqrt; sc_i++) {
|
||||
// square index
|
||||
distinctSquares[s_i] = Distinct(n);
|
||||
|
||||
// (r, c) now marks the start of this square
|
||||
var r = sr_i * n_sqrt;
|
||||
var c = sc_i * n_sqrt;
|
||||
var i = 0;
|
||||
for (var row_i = r; row_i < r + n_sqrt; row_i++) {
|
||||
for (var col_i = c; col_i < c + n_sqrt; col_i++) {
|
||||
distinctSquares[s_i].in[i] <== solution[row_i][col_i];
|
||||
i++;
|
||||
}
|
||||
}
|
||||
|
||||
s_i++;
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
3
packages/circuits/inputs/fibonacci_11/default.json
Normal file
3
packages/circuits/inputs/fibonacci_11/default.json
Normal file
@@ -0,0 +1,3 @@
|
||||
{
|
||||
"in": [1, 1]
|
||||
}
|
||||
4
packages/circuits/inputs/fp64/default.json
Normal file
4
packages/circuits/inputs/fp64/default.json
Normal file
@@ -0,0 +1,4 @@
|
||||
{
|
||||
"e": ["1122", "1024"],
|
||||
"m": ["7807742059002284", "7045130465601185"]
|
||||
}
|
||||
3
packages/circuits/inputs/multiplier_3/default.json
Normal file
3
packages/circuits/inputs/multiplier_3/default.json
Normal file
@@ -0,0 +1,3 @@
|
||||
{
|
||||
"in": [2, 4, 10]
|
||||
}
|
||||
6
packages/circuits/inputs/sha256_32/default.json
Normal file
6
packages/circuits/inputs/sha256_32/default.json
Normal file
@@ -0,0 +1,6 @@
|
||||
{
|
||||
"in": [
|
||||
116, 111, 100, 97, 121, 32, 105, 115, 32, 97, 32, 103, 111, 111, 100, 32, 100, 97, 121, 44, 32, 110, 111, 116, 32,
|
||||
101, 118, 101, 114, 121, 100, 97, 121, 32, 105, 115
|
||||
]
|
||||
}
|
||||
24
packages/circuits/inputs/sudoku_9x9/default.json
Normal file
24
packages/circuits/inputs/sudoku_9x9/default.json
Normal file
@@ -0,0 +1,24 @@
|
||||
{
|
||||
"solution": [
|
||||
[1, 9, 4, 8, 6, 5, 2, 3, 7],
|
||||
[7, 3, 5, 4, 1, 2, 9, 6, 8],
|
||||
[8, 6, 2, 3, 9, 7, 1, 4, 5],
|
||||
[9, 2, 1, 7, 4, 8, 3, 5, 6],
|
||||
[6, 7, 8, 5, 3, 1, 4, 2, 9],
|
||||
[4, 5, 3, 9, 2, 6, 8, 7, 1],
|
||||
[3, 8, 9, 6, 5, 4, 7, 1, 2],
|
||||
[2, 4, 6, 1, 7, 9, 5, 8, 3],
|
||||
[5, 1, 7, 2, 8, 3, 6, 9, 4]
|
||||
],
|
||||
"puzzle": [
|
||||
[0, 0, 0, 8, 6, 0, 2, 3, 0],
|
||||
[7, 0, 5, 0, 0, 0, 9, 0, 8],
|
||||
[0, 6, 0, 3, 0, 7, 0, 4, 0],
|
||||
[0, 2, 0, 7, 0, 8, 0, 5, 0],
|
||||
[0, 7, 8, 5, 0, 0, 0, 0, 0],
|
||||
[4, 0, 0, 9, 0, 6, 0, 7, 0],
|
||||
[3, 0, 9, 0, 5, 0, 7, 0, 2],
|
||||
[0, 4, 0, 1, 0, 9, 0, 8, 0],
|
||||
[5, 0, 7, 0, 8, 0, 0, 9, 4]
|
||||
]
|
||||
}
|
||||
18
packages/circuits/package.json
Normal file
18
packages/circuits/package.json
Normal file
@@ -0,0 +1,18 @@
|
||||
{
|
||||
"description": "Circomkit examples",
|
||||
"scripts": {
|
||||
"start": "npx ts-node ./src/index.ts",
|
||||
"test": "npx mocha"
|
||||
},
|
||||
"dependencies": {
|
||||
"circomkit": "^0.0.22",
|
||||
"circomlib": "^2.0.5"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@types/mocha": "^10.0.1",
|
||||
"@types/node": "^20.3.0",
|
||||
"mocha": "^10.2.0",
|
||||
"ts-node": "^10.9.1",
|
||||
"typescript": "^5.1.3"
|
||||
}
|
||||
}
|
||||
33
packages/circuits/src/index.ts
Normal file
33
packages/circuits/src/index.ts
Normal file
@@ -0,0 +1,33 @@
|
||||
import { Circomkit } from "circomkit";
|
||||
|
||||
async function main() {
|
||||
// create circomkit
|
||||
const circomkit = new Circomkit({
|
||||
protocol: "groth16",
|
||||
});
|
||||
|
||||
// artifacts output at `build/multiplier_3` directory
|
||||
await circomkit.compile("multiplier_3", {
|
||||
file: "multiplier",
|
||||
template: "Multiplier",
|
||||
params: [3],
|
||||
});
|
||||
|
||||
// proof & public signals at `build/multiplier_3/my_input` directory
|
||||
await circomkit.prove("multiplier_3", "my_input", { in: [3, 5, 7] });
|
||||
|
||||
// verify with proof & public signals at `build/multiplier_3/my_input`
|
||||
const ok = await circomkit.verify("multiplier_3", "my_input");
|
||||
if (ok) {
|
||||
circomkit.log("Proof verified!", "success");
|
||||
} else {
|
||||
circomkit.log("Verification failed.", "error");
|
||||
}
|
||||
}
|
||||
|
||||
main()
|
||||
.then(() => process.exit(0))
|
||||
.catch((e) => {
|
||||
console.error(e);
|
||||
process.exit(1);
|
||||
});
|
||||
5
packages/circuits/tests/common/index.ts
Normal file
5
packages/circuits/tests/common/index.ts
Normal file
@@ -0,0 +1,5 @@
|
||||
import { Circomkit } from "circomkit";
|
||||
|
||||
export const circomkit = new Circomkit({
|
||||
verbose: false,
|
||||
});
|
||||
51
packages/circuits/tests/fibonacci.test.ts
Normal file
51
packages/circuits/tests/fibonacci.test.ts
Normal file
@@ -0,0 +1,51 @@
|
||||
import { WitnessTester } from "circomkit";
|
||||
import { circomkit } from "./common";
|
||||
|
||||
describe("fibonacci", () => {
|
||||
const N = 7;
|
||||
let circuit: WitnessTester<["in"], ["out"]>;
|
||||
|
||||
describe("vanilla", () => {
|
||||
before(async () => {
|
||||
circuit = await circomkit.WitnessTester(`fibonacci_${N}`, {
|
||||
file: "fibonacci",
|
||||
template: "Fibonacci",
|
||||
params: [N],
|
||||
});
|
||||
console.log("#constraints:", await circuit.getConstraintCount());
|
||||
});
|
||||
|
||||
it("should compute correctly", async () => {
|
||||
await circuit.expectPass({ in: [1, 1] }, { out: fibonacci([1, 1], N) });
|
||||
});
|
||||
});
|
||||
|
||||
describe("recursive", () => {
|
||||
before(async () => {
|
||||
circuit = await circomkit.WitnessTester(`fibonacci_${N}_recursive`, {
|
||||
file: "fibonacci",
|
||||
template: "FibonacciRecursive",
|
||||
params: [N],
|
||||
});
|
||||
console.log("#constraints:", await circuit.getConstraintCount());
|
||||
});
|
||||
|
||||
it("should compute correctly", async () => {
|
||||
await circuit.expectPass({ in: [1, 1] }, { out: fibonacci([1, 1], N) });
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
// simple fibonacci with 2 variables
|
||||
function fibonacci(init: [number, number], n: number): number {
|
||||
if (n < 0) {
|
||||
throw new Error("N must be positive");
|
||||
}
|
||||
|
||||
let [a, b] = init;
|
||||
for (let i = 2; i <= n; i++) {
|
||||
b = a + b;
|
||||
a = b - a;
|
||||
}
|
||||
return n === 0 ? a : b;
|
||||
}
|
||||
427
packages/circuits/tests/float_add.test.ts
Normal file
427
packages/circuits/tests/float_add.test.ts
Normal file
@@ -0,0 +1,427 @@
|
||||
import { WitnessTester } from "circomkit";
|
||||
import { circomkit } from "./common";
|
||||
|
||||
const expectedConstraints = {
|
||||
fp32: 401,
|
||||
fp64: 819,
|
||||
checkBitLength: (bits: number) => bits + 2,
|
||||
leftShift: (shiftBound: number) => shiftBound + 2,
|
||||
right: (bits: number) => bits + 2,
|
||||
normalize: (P: number) => 3 * P + 1 + 1, // MSNZB + 1
|
||||
msnzb: (bits: number) => 3 * bits + 1,
|
||||
};
|
||||
|
||||
// tests adapted from https://github.com/rdi-berkeley/zkp-mooc-lab
|
||||
describe("float_add 32-bit", () => {
|
||||
let circuit: WitnessTester<["e", "m"], ["e_out", "m_out"]>;
|
||||
|
||||
const k = 8;
|
||||
const p = 23;
|
||||
|
||||
before(async () => {
|
||||
circuit = await circomkit.WitnessTester("fp32", {
|
||||
file: "float_add",
|
||||
template: "FloatAdd",
|
||||
params: [k, p],
|
||||
});
|
||||
});
|
||||
|
||||
it("should have correct number of constraints", async () => {
|
||||
await circuit.expectConstraintCount(expectedConstraints.fp32);
|
||||
});
|
||||
|
||||
it("case I test", async () => {
|
||||
await circuit.expectPass(
|
||||
{
|
||||
e: ["43", "5"],
|
||||
m: ["11672136", "10566265"],
|
||||
},
|
||||
{ e_out: "43", m_out: "11672136" }
|
||||
);
|
||||
});
|
||||
|
||||
it("case II test 1", async () => {
|
||||
await circuit.expectPass(
|
||||
{
|
||||
e: ["104", "106"],
|
||||
m: ["12444445", "14159003"],
|
||||
},
|
||||
{ e_out: "107", m_out: "8635057" }
|
||||
);
|
||||
});
|
||||
|
||||
it("case II test 2", async () => {
|
||||
await circuit.expectPass(
|
||||
{
|
||||
e: ["176", "152"],
|
||||
m: ["16777215", "16777215"],
|
||||
},
|
||||
{ e_out: "177", m_out: "8388608" }
|
||||
);
|
||||
});
|
||||
|
||||
it("case II test 3", async () => {
|
||||
await circuit.expectPass(
|
||||
{
|
||||
e: ["142", "142"],
|
||||
m: ["13291872", "13291872"],
|
||||
},
|
||||
{ e_out: "143", m_out: "13291872" }
|
||||
);
|
||||
});
|
||||
|
||||
it("one input zero test", async () => {
|
||||
await circuit.expectPass(
|
||||
{
|
||||
e: ["0", "43"],
|
||||
m: ["0", "10566265"],
|
||||
},
|
||||
{ e_out: "43", m_out: "10566265" }
|
||||
);
|
||||
});
|
||||
|
||||
it("both inputs zero test", async () => {
|
||||
await circuit.expectPass(
|
||||
{
|
||||
e: ["0", "0"],
|
||||
m: ["0", "0"],
|
||||
},
|
||||
{ e_out: "0", m_out: "0" }
|
||||
);
|
||||
});
|
||||
|
||||
it("should fail - exponent zero but mantissa non-zero", async () => {
|
||||
await circuit.expectFail({
|
||||
e: ["0", "0"],
|
||||
m: ["0", "10566265"],
|
||||
});
|
||||
});
|
||||
|
||||
it("should fail - mantissa ≥ 2^(p+1)", async () => {
|
||||
await circuit.expectFail({
|
||||
e: ["0", "43"],
|
||||
m: ["0", "16777216"],
|
||||
});
|
||||
});
|
||||
|
||||
it("should fail - mantissa < 2^p", async () => {
|
||||
await circuit.expectFail({
|
||||
e: ["0", "43"],
|
||||
m: ["0", "6777216"],
|
||||
});
|
||||
});
|
||||
|
||||
it("should fail - exponent ≥ 2^k", async () => {
|
||||
await circuit.expectFail({
|
||||
e: ["0", "256"],
|
||||
m: ["0", "10566265"],
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
describe("float_add 64-bit", () => {
|
||||
const k = 11;
|
||||
const p = 52;
|
||||
let circuit: WitnessTester<["e", "m"], ["e_out", "m_out"]>;
|
||||
|
||||
before(async () => {
|
||||
circuit = await circomkit.WitnessTester("fp64", {
|
||||
file: "float_add",
|
||||
template: "FloatAdd",
|
||||
params: [k, p],
|
||||
});
|
||||
});
|
||||
|
||||
it("should have correct number of constraints", async () => {
|
||||
await circuit.expectConstraintCount(expectedConstraints.fp64);
|
||||
});
|
||||
|
||||
it("case I test", async () => {
|
||||
await circuit.expectPass(
|
||||
{
|
||||
e: ["1122", "1024"],
|
||||
m: ["7807742059002284", "7045130465601185"],
|
||||
},
|
||||
{ e_out: "1122", m_out: "7807742059002284" }
|
||||
);
|
||||
});
|
||||
|
||||
it("case II test 1", async () => {
|
||||
await circuit.expectPass(
|
||||
{
|
||||
e: ["1056", "1053"],
|
||||
m: ["8879495032259305", "5030141535601637"],
|
||||
},
|
||||
{ e_out: "1057", m_out: "4754131362104755" }
|
||||
);
|
||||
});
|
||||
|
||||
it("case II test 2", async () => {
|
||||
await circuit.expectPass(
|
||||
{
|
||||
e: ["1035", "982"],
|
||||
m: ["4804509148660890", "8505192799372177"],
|
||||
},
|
||||
{ e_out: "1035", m_out: "4804509148660891" }
|
||||
);
|
||||
});
|
||||
|
||||
it("case II test 3", async () => {
|
||||
await circuit.expectPass(
|
||||
{
|
||||
e: ["982", "982"],
|
||||
m: ["8505192799372177", "8505192799372177"],
|
||||
},
|
||||
{ e_out: "983", m_out: "8505192799372177" }
|
||||
);
|
||||
});
|
||||
|
||||
it("one input zero test", async () => {
|
||||
await circuit.expectPass(
|
||||
{
|
||||
e: ["0", "982"],
|
||||
m: ["0", "8505192799372177"],
|
||||
},
|
||||
{ e_out: "982", m_out: "8505192799372177" }
|
||||
);
|
||||
});
|
||||
|
||||
it("both inputs zero test", async () => {
|
||||
await circuit.expectPass(
|
||||
{
|
||||
e: ["0", "0"],
|
||||
m: ["0", "0"],
|
||||
},
|
||||
{ e_out: "0", m_out: "0" }
|
||||
);
|
||||
});
|
||||
|
||||
it("should fail - exponent zero but mantissa non-zero", async () => {
|
||||
await circuit.expectFail({
|
||||
e: ["0", "0"],
|
||||
m: ["0", "8505192799372177"],
|
||||
});
|
||||
});
|
||||
|
||||
it("should fail - mantissa < 2^p", async () => {
|
||||
await circuit.expectFail({
|
||||
e: ["0", "43"],
|
||||
m: ["0", "16777216"],
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
describe("float_add utilities", () => {
|
||||
describe("check bit length", () => {
|
||||
const b = 23; // bit count
|
||||
let circuit: WitnessTester<["in"], ["out"]>;
|
||||
|
||||
before(async () => {
|
||||
circuit = await circomkit.WitnessTester(`cbl_${b}`, {
|
||||
file: "float_add",
|
||||
template: "CheckBitLength",
|
||||
params: [b],
|
||||
dir: "test/float_add",
|
||||
});
|
||||
});
|
||||
|
||||
it("should have correct number of constraints", async () => {
|
||||
await circuit.expectConstraintCount(expectedConstraints.checkBitLength(b));
|
||||
});
|
||||
|
||||
it("should give 1 for in ≤ b", async () => {
|
||||
await circuit.expectPass({ in: "4903265" }, { out: "1" });
|
||||
});
|
||||
|
||||
it("should give 0 for in > b", async () => {
|
||||
await circuit.expectPass({ in: "13291873" }, { out: "0" });
|
||||
});
|
||||
});
|
||||
|
||||
describe("left shift", () => {
|
||||
const shift_bound = 25;
|
||||
let circuit: WitnessTester<["x", "shift", "skip_checks"], ["y"]>;
|
||||
|
||||
before(async () => {
|
||||
circuit = await circomkit.WitnessTester(`shl_${shift_bound}`, {
|
||||
file: "float_add",
|
||||
template: "LeftShift",
|
||||
dir: "test/float_add",
|
||||
params: [shift_bound],
|
||||
});
|
||||
});
|
||||
|
||||
it("should have correct number of constraints", async () => {
|
||||
await circuit.expectConstraintCount(expectedConstraints.leftShift(shift_bound));
|
||||
});
|
||||
|
||||
it("should pass test 1 - don't skip checks", async () => {
|
||||
await circuit.expectPass(
|
||||
{
|
||||
x: "65",
|
||||
shift: "24",
|
||||
skip_checks: "0",
|
||||
},
|
||||
{ y: "1090519040" }
|
||||
);
|
||||
});
|
||||
|
||||
it("should pass test 2 - don't skip checks", async () => {
|
||||
await circuit.expectPass(
|
||||
{
|
||||
x: "65",
|
||||
shift: "0",
|
||||
skip_checks: "0",
|
||||
},
|
||||
{ y: "65" }
|
||||
);
|
||||
});
|
||||
|
||||
it("should fail - don't skip checks", async () => {
|
||||
await circuit.expectFail({
|
||||
x: "65",
|
||||
shift: "25",
|
||||
skip_checks: "0",
|
||||
});
|
||||
});
|
||||
|
||||
it("should pass when skip_checks = 1 and shift is ≥ shift_bound", async () => {
|
||||
await circuit.expectPass({
|
||||
x: "65",
|
||||
shift: "25",
|
||||
skip_checks: "1",
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
describe("right shift", () => {
|
||||
const b = 49;
|
||||
const shift = 24;
|
||||
let circuit: WitnessTester<["x"], ["y"]>;
|
||||
|
||||
before(async () => {
|
||||
circuit = await circomkit.WitnessTester(`shr_${b}`, {
|
||||
file: "float_add",
|
||||
template: "RightShift",
|
||||
dir: "test/float_add",
|
||||
params: [b, shift],
|
||||
});
|
||||
});
|
||||
|
||||
it("should have correct number of constraints", async () => {
|
||||
await circuit.expectConstraintCount(b);
|
||||
});
|
||||
|
||||
it("should pass - small bitwidth", async () => {
|
||||
await circuit.expectPass({ x: "82263136010365" }, { y: "4903265" });
|
||||
});
|
||||
|
||||
it("should fail - large bitwidth", async () => {
|
||||
await circuit.expectFail({ x: "15087340228765024367" });
|
||||
});
|
||||
});
|
||||
|
||||
describe("normalize", () => {
|
||||
const k = 8;
|
||||
const p = 23;
|
||||
const P = 47;
|
||||
let circuit: WitnessTester<["e", "m", "skip_checks"], ["e_out", "m_out"]>;
|
||||
|
||||
before(async () => {
|
||||
circuit = await circomkit.WitnessTester(`normalize_${k}_${p}_${P}`, {
|
||||
file: "float_add",
|
||||
template: "Normalize",
|
||||
params: [k, p, P],
|
||||
dir: "test/float_add",
|
||||
});
|
||||
});
|
||||
|
||||
it("should have correct number of constraints", async () => {
|
||||
await circuit.expectConstraintCount(expectedConstraints.normalize(P));
|
||||
});
|
||||
|
||||
it("should pass - don't skip checks", async () => {
|
||||
await circuit.expectPass(
|
||||
{
|
||||
e: "100",
|
||||
m: "20565784002591",
|
||||
skip_checks: "0",
|
||||
},
|
||||
{ e_out: "121", m_out: "164526272020728" }
|
||||
);
|
||||
});
|
||||
|
||||
it("should pass - already normalized and don't skip checks", async () => {
|
||||
await circuit.expectPass(
|
||||
{
|
||||
e: "100",
|
||||
m: "164526272020728",
|
||||
skip_checks: "0",
|
||||
},
|
||||
{ e_out: "124", m_out: "164526272020728" }
|
||||
);
|
||||
});
|
||||
|
||||
it("should fail when m = 0 - don't skip checks", async () => {
|
||||
await circuit.expectFail({
|
||||
e: "100",
|
||||
m: "0",
|
||||
skip_checks: "0",
|
||||
});
|
||||
});
|
||||
|
||||
it("should pass when skip_checks = 1 and m is 0", async () => {
|
||||
await circuit.expectPass({
|
||||
e: "100",
|
||||
m: "0",
|
||||
skip_checks: "1",
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
describe("msnzb", () => {
|
||||
const b = 48;
|
||||
let circuit: WitnessTester<["in", "skip_checks"], ["one_hot"]>;
|
||||
|
||||
before(async () => {
|
||||
circuit = await circomkit.WitnessTester(`msnzb_${b}`, {
|
||||
file: "float_add",
|
||||
template: "MSNZB",
|
||||
dir: "test/float_add",
|
||||
params: [b],
|
||||
});
|
||||
});
|
||||
|
||||
it("should have correct number of constraints", async () => {
|
||||
await circuit.expectConstraintCount(expectedConstraints.msnzb(b));
|
||||
});
|
||||
|
||||
it("should pass test 1 - don't skip checks", async () => {
|
||||
await circuit.expectPass(
|
||||
{ in: "1", skip_checks: "0" },
|
||||
{
|
||||
// prettier-ignore
|
||||
one_hot: ['1', '0', '0', '0', '0', '0', '0', '0', '0', '0', '0', '0', '0', '0', '0', '0', '0', '0', '0', '0', '0', '0', '0', '0', '0', '0', '0', '0', '0', '0', '0', '0', '0', '0', '0', '0', '0', '0', '0', '0', '0', '0', '0', '0', '0', '0', '0', '0'],
|
||||
}
|
||||
);
|
||||
});
|
||||
|
||||
it("should pass test 2 - don't skip checks", async () => {
|
||||
await circuit.expectPass(
|
||||
{ in: "281474976710655", skip_checks: "0" },
|
||||
{
|
||||
// prettier-ignore
|
||||
one_hot: ['0', '0', '0', '0', '0', '0', '0', '0', '0', '0', '0', '0', '0', '0', '0', '0', '0', '0', '0', '0', '0', '0', '0', '0', '0', '0', '0', '0', '0', '0', '0', '0', '0', '0', '0', '0', '0', '0', '0', '0', '0', '0', '0', '0', '0', '0', '0', '1'],
|
||||
}
|
||||
);
|
||||
});
|
||||
|
||||
it("should fail when in = 0 - don't skip checks", async () => {
|
||||
await circuit.expectFail({ in: "0", skip_checks: "0" });
|
||||
});
|
||||
|
||||
it("should pass when skip_checks = 1 and in is 0", async () => {
|
||||
await circuit.expectPass({ in: "0", skip_checks: "1" });
|
||||
});
|
||||
});
|
||||
});
|
||||
42
packages/circuits/tests/multiplier.test.ts
Normal file
42
packages/circuits/tests/multiplier.test.ts
Normal file
@@ -0,0 +1,42 @@
|
||||
import { WitnessTester } from "circomkit";
|
||||
import { circomkit } from "./common";
|
||||
|
||||
describe("multiplier", () => {
|
||||
const N = 3;
|
||||
let circuit: WitnessTester<["in"], ["out"]>;
|
||||
|
||||
before(async () => {
|
||||
circuit = await circomkit.WitnessTester(`multiplier_${N}`, {
|
||||
file: "multiplier",
|
||||
template: "Multiplier",
|
||||
params: [N],
|
||||
});
|
||||
});
|
||||
|
||||
it("should have correct number of constraints", async () => {
|
||||
await circuit.expectConstraintCount(N - 1);
|
||||
});
|
||||
|
||||
it("should multiply correctly", async () => {
|
||||
const randomNumbers = Array.from({ length: N }, () => Math.floor(Math.random() * 100 * N));
|
||||
await circuit.expectPass({ in: randomNumbers }, { out: randomNumbers.reduce((prev, acc) => acc * prev) });
|
||||
});
|
||||
});
|
||||
|
||||
describe("multiplier utilities", () => {
|
||||
describe("multiplication gate", () => {
|
||||
let circuit: WitnessTester<["in"], ["out"]>;
|
||||
|
||||
before(async () => {
|
||||
circuit = await circomkit.WitnessTester("mulgate", {
|
||||
file: "multiplier",
|
||||
template: "MultiplicationGate",
|
||||
dir: "test/multiplier",
|
||||
});
|
||||
});
|
||||
|
||||
it("should multiply correctly", async () => {
|
||||
await circuit.expectPass({ in: [7, 5] }, { out: 7 * 5 });
|
||||
});
|
||||
});
|
||||
});
|
||||
38
packages/circuits/tests/sha256.test.ts
Normal file
38
packages/circuits/tests/sha256.test.ts
Normal file
@@ -0,0 +1,38 @@
|
||||
import { WitnessTester } from "circomkit";
|
||||
import { createHash } from "crypto";
|
||||
import { circomkit } from "./common";
|
||||
|
||||
describe("sha256", () => {
|
||||
let circuit: WitnessTester<["in"], ["out"]>;
|
||||
|
||||
// number of bytes for the sha256 input
|
||||
const NUM_BYTES = 36;
|
||||
|
||||
// preimage and its byte array
|
||||
const PREIMAGE = Buffer.from("today is a good day, not everyday is");
|
||||
const PREIMAGE_BYTES = PREIMAGE.toJSON().data;
|
||||
|
||||
// digest and its byte array
|
||||
const DIGEST = createHash("sha256").update(PREIMAGE).digest("hex");
|
||||
const DIGEST_BYTES = Buffer.from(DIGEST, "hex").toJSON().data;
|
||||
|
||||
// circuit signals
|
||||
const INPUT = {
|
||||
in: PREIMAGE_BYTES,
|
||||
};
|
||||
const OUTPUT = {
|
||||
out: DIGEST_BYTES,
|
||||
};
|
||||
|
||||
before(async () => {
|
||||
circuit = await circomkit.WitnessTester(`sha256_${NUM_BYTES}`, {
|
||||
file: "sha256",
|
||||
template: "Sha256Bytes",
|
||||
params: [NUM_BYTES],
|
||||
});
|
||||
});
|
||||
|
||||
it("should compute hash correctly", async () => {
|
||||
await circuit.expectPass(INPUT, OUTPUT);
|
||||
});
|
||||
});
|
||||
200
packages/circuits/tests/sudoku.test.ts
Normal file
200
packages/circuits/tests/sudoku.test.ts
Normal file
@@ -0,0 +1,200 @@
|
||||
import { WitnessTester, CircuitSignals } from "circomkit";
|
||||
import { circomkit } from "./common";
|
||||
|
||||
const BOARD_SIZES = [4, 9] as const;
|
||||
const INPUTS: { [N in (typeof BOARD_SIZES)[number]]: CircuitSignals<["solution", "puzzle"]> } = {
|
||||
9: {
|
||||
solution: [
|
||||
[1, 9, 4, 8, 6, 5, 2, 3, 7],
|
||||
[7, 3, 5, 4, 1, 2, 9, 6, 8],
|
||||
[8, 6, 2, 3, 9, 7, 1, 4, 5],
|
||||
[9, 2, 1, 7, 4, 8, 3, 5, 6],
|
||||
[6, 7, 8, 5, 3, 1, 4, 2, 9],
|
||||
[4, 5, 3, 9, 2, 6, 8, 7, 1],
|
||||
[3, 8, 9, 6, 5, 4, 7, 1, 2],
|
||||
[2, 4, 6, 1, 7, 9, 5, 8, 3],
|
||||
[5, 1, 7, 2, 8, 3, 6, 9, 4],
|
||||
],
|
||||
puzzle: [
|
||||
[0, 0, 0, 8, 6, 0, 2, 3, 0],
|
||||
[7, 0, 5, 0, 0, 0, 9, 0, 8],
|
||||
[0, 6, 0, 3, 0, 7, 0, 4, 0],
|
||||
[0, 2, 0, 7, 0, 8, 0, 5, 0],
|
||||
[0, 7, 8, 5, 0, 0, 0, 0, 0],
|
||||
[4, 0, 0, 9, 0, 6, 0, 7, 0],
|
||||
[3, 0, 9, 0, 5, 0, 7, 0, 2],
|
||||
[0, 4, 0, 1, 0, 9, 0, 8, 0],
|
||||
[5, 0, 7, 0, 8, 0, 0, 9, 4],
|
||||
],
|
||||
},
|
||||
4: {
|
||||
solution: [
|
||||
[4, 1, 3, 2],
|
||||
[3, 2, 4, 1],
|
||||
[2, 4, 1, 3],
|
||||
[1, 3, 2, 4],
|
||||
],
|
||||
puzzle: [
|
||||
[0, 1, 0, 2],
|
||||
[3, 2, 0, 0],
|
||||
[0, 0, 1, 0],
|
||||
[1, 0, 0, 0],
|
||||
],
|
||||
},
|
||||
};
|
||||
|
||||
BOARD_SIZES.map((N) =>
|
||||
describe(`sudoku (${N} by ${N})`, () => {
|
||||
const INPUT = INPUTS[N];
|
||||
let circuit: WitnessTester<["solution", "puzzle"]>;
|
||||
|
||||
before(async () => {
|
||||
circuit = await circomkit.WitnessTester(`sudoku_${N}x${N}`, {
|
||||
file: "sudoku",
|
||||
template: "Sudoku",
|
||||
pubs: ["puzzle"],
|
||||
params: [Math.sqrt(N)],
|
||||
});
|
||||
});
|
||||
|
||||
it("should compute correctly", async () => {
|
||||
await circuit.expectPass(INPUT);
|
||||
});
|
||||
|
||||
it("should NOT accept non-distinct rows", async () => {
|
||||
const badInput = JSON.parse(JSON.stringify(INPUT));
|
||||
badInput.solution[0][0] = badInput.solution[0][1];
|
||||
await circuit.expectFail(badInput);
|
||||
});
|
||||
|
||||
it("should NOT accept non-distinct columns", async () => {
|
||||
const badInput = JSON.parse(JSON.stringify(INPUT));
|
||||
badInput.solution[0][0] = badInput.solution[1][0];
|
||||
await circuit.expectFail(badInput);
|
||||
});
|
||||
|
||||
it("should NOT accept non-distinct square", async () => {
|
||||
const badInput = JSON.parse(JSON.stringify(INPUT));
|
||||
badInput.solution[0][0] = badInput.solution[1][1];
|
||||
await circuit.expectFail(badInput);
|
||||
});
|
||||
|
||||
it("should NOT accept empty value in solution", async () => {
|
||||
const badInput = JSON.parse(JSON.stringify(INPUT));
|
||||
badInput.solution[0][0] = 0;
|
||||
await circuit.expectFail(badInput);
|
||||
});
|
||||
|
||||
it("should NOT accept out-of-range values", async () => {
|
||||
const badInput = JSON.parse(JSON.stringify(INPUT));
|
||||
badInput.solution[0][0] = 99999;
|
||||
await circuit.expectFail(badInput);
|
||||
});
|
||||
})
|
||||
);
|
||||
|
||||
describe("sudoku utilities", () => {
|
||||
describe("assert bit length", () => {
|
||||
const b = 3; // bit count
|
||||
|
||||
let circuit: WitnessTester<["in"], []>;
|
||||
|
||||
before(async () => {
|
||||
circuit = await circomkit.WitnessTester(`bitlen_${b}`, {
|
||||
file: "sudoku",
|
||||
template: "AssertBitLength",
|
||||
dir: "test/sudoku",
|
||||
params: [b],
|
||||
});
|
||||
});
|
||||
|
||||
it("should pass for input < 2^b", async () => {
|
||||
await circuit.expectPass({
|
||||
in: 2 ** b - 1,
|
||||
});
|
||||
});
|
||||
|
||||
it("should fail for input ≥ 2^b ", async () => {
|
||||
await circuit.expectFail({
|
||||
in: 2 ** b,
|
||||
});
|
||||
await circuit.expectFail({
|
||||
in: 2 ** b + 1,
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
describe("distinct", () => {
|
||||
const n = 3;
|
||||
let circuit: WitnessTester<["in"], []>;
|
||||
|
||||
before(async () => {
|
||||
circuit = await circomkit.WitnessTester(`distinct_${n}`, {
|
||||
file: "sudoku",
|
||||
template: "Distinct",
|
||||
dir: "test/sudoku",
|
||||
params: [n],
|
||||
});
|
||||
});
|
||||
|
||||
it("should pass if all inputs are unique", async () => {
|
||||
await circuit.expectPass({
|
||||
in: Array(n)
|
||||
.fill(0)
|
||||
.map((v, i) => v + i),
|
||||
});
|
||||
});
|
||||
|
||||
it("should fail if there is a duplicate", async () => {
|
||||
const arr = Array(n)
|
||||
.fill(0)
|
||||
.map((v, i) => v + i);
|
||||
// make a duplicate
|
||||
arr[0] = arr[arr.length - 1];
|
||||
await circuit.expectFail({
|
||||
in: arr,
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
describe("in range", () => {
|
||||
const MIN = 1;
|
||||
const MAX = 9;
|
||||
let circuit: WitnessTester<["in"]>;
|
||||
|
||||
before(async () => {
|
||||
circuit = await circomkit.WitnessTester(`inRange_${MIN}_${MAX}`, {
|
||||
file: "sudoku",
|
||||
template: "InRange",
|
||||
dir: "test/sudoku",
|
||||
params: [MIN, MAX],
|
||||
});
|
||||
});
|
||||
|
||||
it("should pass for in range", async () => {
|
||||
await circuit.expectPass({
|
||||
in: MAX,
|
||||
});
|
||||
await circuit.expectPass({
|
||||
in: MIN,
|
||||
});
|
||||
await circuit.expectPass({
|
||||
in: Math.floor((MIN + MAX) / 2),
|
||||
});
|
||||
});
|
||||
|
||||
it("should FAIL for out of range (upper bound)", async () => {
|
||||
await circuit.expectFail({
|
||||
in: MAX + 1,
|
||||
});
|
||||
});
|
||||
|
||||
it("should FAIL for out of range (lower bound)", async () => {
|
||||
if (MIN > 0) {
|
||||
await circuit.expectFail({
|
||||
in: MIN - 1,
|
||||
});
|
||||
}
|
||||
});
|
||||
});
|
||||
});
|
||||
10
packages/circuits/tsconfig.json
Normal file
10
packages/circuits/tsconfig.json
Normal file
@@ -0,0 +1,10 @@
|
||||
{
|
||||
"compilerOptions": {
|
||||
"target": "ES2020",
|
||||
"module": "commonjs",
|
||||
"types": ["mocha", "node"],
|
||||
"esModuleInterop": true,
|
||||
"forceConsistentCasingInFileNames": true,
|
||||
"strict": true
|
||||
}
|
||||
}
|
||||
1123
packages/circuits/yarn.lock
Normal file
1123
packages/circuits/yarn.lock
Normal file
File diff suppressed because it is too large
Load Diff
7
packages/contracts/.env.example
Normal file
7
packages/contracts/.env.example
Normal file
@@ -0,0 +1,7 @@
|
||||
MAINNET_RPC=
|
||||
MAINNET_DEPLOYER_NAME=
|
||||
|
||||
SEPOLIA_RPC=
|
||||
SEPOLIA_DEPLOYER_NAME=
|
||||
|
||||
ETHERSCAN_API_KEY=
|
||||
1
packages/contracts/.gitattributes
vendored
Normal file
1
packages/contracts/.gitattributes
vendored
Normal file
@@ -0,0 +1 @@
|
||||
*.sol linguist-language=Solidity
|
||||
59
packages/contracts/.github/workflows/canary.yml
vendored
Normal file
59
packages/contracts/.github/workflows/canary.yml
vendored
Normal file
@@ -0,0 +1,59 @@
|
||||
name: Canary Release
|
||||
|
||||
on: workflow_dispatch
|
||||
|
||||
jobs:
|
||||
export:
|
||||
name: Generate Interfaces And Contracts
|
||||
|
||||
# 1) Remove the following line if you wish to export your Solidity contracts and interfaces and publish them to NPM
|
||||
if: false
|
||||
runs-on: ubuntu-latest
|
||||
strategy:
|
||||
matrix:
|
||||
export_type: ['interfaces', 'all']
|
||||
|
||||
env:
|
||||
# 2) Fill the project name to be used in NPM
|
||||
NPM_PACKAGE_NAME: 'my-cool-project'
|
||||
EXPORT_NAME: ${{ matrix.export_type == 'interfaces' && '-interfaces' || '' }}
|
||||
|
||||
steps:
|
||||
- name: Checkout Repo
|
||||
uses: actions/checkout@v4
|
||||
|
||||
- name: Install Foundry
|
||||
uses: foundry-rs/foundry-toolchain@v1
|
||||
with:
|
||||
version: nightly
|
||||
|
||||
- name: Install Node
|
||||
uses: actions/setup-node@v4
|
||||
with:
|
||||
registry-url: 'https://registry.npmjs.org'
|
||||
node-version: 20.x
|
||||
cache: 'yarn'
|
||||
|
||||
- name: Install dependencies
|
||||
run: yarn --frozen-lockfile
|
||||
|
||||
- name: Build project and generate out directory
|
||||
run: yarn build
|
||||
|
||||
- name: Update version
|
||||
run: yarn version --new-version "0.0.0-${GITHUB_SHA::8}" --no-git-tag-version
|
||||
|
||||
- name: Export Solidity - Export Type ${{ matrix.export_type }}
|
||||
uses: defi-wonderland/solidity-exporter-action@v2.1.0
|
||||
with:
|
||||
package_name: ${{ env.NPM_PACKAGE_NAME }}
|
||||
out: 'out'
|
||||
interfaces: 'solidity/interfaces'
|
||||
contracts: 'solidity/contracts'
|
||||
libraries: "solidity/libraries"
|
||||
export_type: '${{ matrix.export_type }}'
|
||||
|
||||
- name: Publish to NPM - Export Type ${{ matrix.export_type }}
|
||||
run: cd export/${{ env.NPM_PACKAGE_NAME }}${{ env.EXPORT_NAME }} && npm publish --access public --tag canary
|
||||
env:
|
||||
NODE_AUTH_TOKEN: ${{ secrets.NPM_TOKEN }}
|
||||
73
packages/contracts/.github/workflows/coverage.yml
vendored
Normal file
73
packages/contracts/.github/workflows/coverage.yml
vendored
Normal file
@@ -0,0 +1,73 @@
|
||||
name: Coverage Check
|
||||
|
||||
on: [push]
|
||||
|
||||
env:
|
||||
COVERAGE_SENSITIVITY_PERCENT: 1
|
||||
|
||||
jobs:
|
||||
upload-coverage:
|
||||
name: Upload Coverage
|
||||
runs-on: ubuntu-latest
|
||||
|
||||
steps:
|
||||
- uses: actions/checkout@v4
|
||||
|
||||
- name: Install Foundry
|
||||
uses: foundry-rs/foundry-toolchain@v1
|
||||
with:
|
||||
version: nightly
|
||||
|
||||
- name: Use Node.js
|
||||
uses: actions/setup-node@v4
|
||||
with:
|
||||
node-version: 20.x
|
||||
cache: 'yarn'
|
||||
|
||||
- name: Install dependencies
|
||||
run: yarn --frozen-lockfile --network-concurrency 1
|
||||
|
||||
- name: Run coverage
|
||||
shell: bash
|
||||
run: yarn coverage
|
||||
|
||||
- name: Setup LCOV
|
||||
uses: hrishikesh-kadam/setup-lcov@v1
|
||||
|
||||
- name: Filter directories
|
||||
run: lcov --remove lcov.info 'test/*' 'script/*' --output-file lcovNew.info --rc lcov_branch_coverage=1 --rc derive_function_end_line=0 --ignore-errors unused
|
||||
|
||||
- name: Capture coverage output
|
||||
id: new-coverage
|
||||
uses: zgosalvez/github-actions-report-lcov@v4
|
||||
with:
|
||||
coverage-files: lcovNew.info
|
||||
|
||||
- name: Retrieve previous coverage
|
||||
uses: actions/download-artifact@v4
|
||||
with:
|
||||
name: coverage.info
|
||||
continue-on-error: true
|
||||
|
||||
- name: Check if a previous coverage exists
|
||||
run: |
|
||||
if [ ! -f coverage.info ]; then
|
||||
echo "Artifact not found. Initializing at 0"
|
||||
echo "0" >> coverage.info
|
||||
fi
|
||||
|
||||
- name: Compare previous coverage
|
||||
run: |
|
||||
old=$(cat coverage.info)
|
||||
new=$(( ${{ steps.new-coverage.outputs.total-coverage }} + ${{ env.COVERAGE_SENSITIVITY_PERCENT }} ))
|
||||
if [ "$new" -lt "$old" ]; then
|
||||
echo "Coverage decreased from $old to $new"
|
||||
exit 1
|
||||
fi
|
||||
mv lcovNew.info coverage.info
|
||||
|
||||
- name: Upload the new coverage
|
||||
uses: actions/upload-artifact@v4
|
||||
with:
|
||||
name: coverage.info
|
||||
path: ./coverage.info
|
||||
58
packages/contracts/.github/workflows/release.yml
vendored
Normal file
58
packages/contracts/.github/workflows/release.yml
vendored
Normal file
@@ -0,0 +1,58 @@
|
||||
name: Production Release
|
||||
|
||||
on:
|
||||
release:
|
||||
types: [published]
|
||||
|
||||
jobs:
|
||||
release:
|
||||
name: Release
|
||||
|
||||
# 1) Remove the following line if you wish to export your Solidity contracts and interfaces and publish them to NPM
|
||||
if: false
|
||||
runs-on: ubuntu-latest
|
||||
strategy:
|
||||
matrix:
|
||||
export_type: ['interfaces', 'all']
|
||||
|
||||
env:
|
||||
# 2) Fill the project name to be used in NPM
|
||||
NPM_PACKAGE_NAME: 'my-cool-project'
|
||||
EXPORT_NAME: ${{ matrix.export_type == 'interfaces' && '-interfaces' || '' }}
|
||||
|
||||
steps:
|
||||
- name: Checkout Repo
|
||||
uses: actions/checkout@v4
|
||||
|
||||
- name: Install Foundry
|
||||
uses: foundry-rs/foundry-toolchain@v1
|
||||
with:
|
||||
version: nightly
|
||||
|
||||
- name: Install Node
|
||||
uses: actions/setup-node@v4
|
||||
with:
|
||||
registry-url: 'https://registry.npmjs.org'
|
||||
node-version: 20.x
|
||||
cache: 'yarn'
|
||||
|
||||
- name: Install dependencies
|
||||
run: yarn --frozen-lockfile
|
||||
|
||||
- name: Build project and generate out directory
|
||||
run: yarn build
|
||||
|
||||
- name: Export Solidity - Export Type ${{ matrix.export_type }}
|
||||
uses: defi-wonderland/solidity-exporter-action@v2.1.0
|
||||
with:
|
||||
package_name: ${{ env.NPM_PACKAGE_NAME }}
|
||||
out: 'out'
|
||||
interfaces: 'solidity/interfaces'
|
||||
contracts: 'solidity/contracts'
|
||||
libraries: "solidity/libraries"
|
||||
export_type: '${{ matrix.export_type }}'
|
||||
|
||||
- name: Publish to NPM - Export Type ${{ matrix.export_type }}
|
||||
run: cd export/${{ env.NPM_PACKAGE_NAME }}${{ env.EXPORT_NAME }} && npm publish --access public --tag latest
|
||||
env:
|
||||
NODE_AUTH_TOKEN: ${{ secrets.NPM_TOKEN }}
|
||||
118
packages/contracts/.github/workflows/tests.yml
vendored
Normal file
118
packages/contracts/.github/workflows/tests.yml
vendored
Normal file
@@ -0,0 +1,118 @@
|
||||
name: CI
|
||||
|
||||
on: [push]
|
||||
|
||||
concurrency:
|
||||
group: ${{github.workflow}}-${{github.ref}}
|
||||
cancel-in-progress: true
|
||||
|
||||
env:
|
||||
MAINNET_RPC: ${{ secrets.MAINNET_RPC }}
|
||||
SEPOLIA_RPC: ${{ secrets.SEPOLIA_RPC }}
|
||||
|
||||
jobs:
|
||||
unit-tests:
|
||||
name: Run Unit Tests
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- uses: actions/checkout@v4
|
||||
|
||||
- name: Install Foundry
|
||||
uses: foundry-rs/foundry-toolchain@v1
|
||||
with:
|
||||
version: nightly
|
||||
|
||||
- name: Use Node.js
|
||||
uses: actions/setup-node@v4
|
||||
with:
|
||||
node-version: 20.x
|
||||
cache: 'yarn'
|
||||
|
||||
- name: Install dependencies
|
||||
run: yarn --frozen-lockfile --network-concurrency 1
|
||||
|
||||
- name: Precompile
|
||||
run: yarn build
|
||||
|
||||
- name: Run tests
|
||||
shell: bash
|
||||
run: yarn test:unit
|
||||
|
||||
integration-tests:
|
||||
name: Run Integration Tests
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- uses: actions/checkout@v4
|
||||
|
||||
- name: Install Foundry
|
||||
uses: foundry-rs/foundry-toolchain@v1
|
||||
with:
|
||||
version: nightly
|
||||
|
||||
- name: Use Node.js
|
||||
uses: actions/setup-node@v4
|
||||
with:
|
||||
node-version: 20.x
|
||||
cache: 'yarn'
|
||||
|
||||
- name: Install dependencies
|
||||
run: yarn --frozen-lockfile --network-concurrency 1
|
||||
|
||||
- name: Precompile
|
||||
run: yarn build
|
||||
|
||||
- name: Run tests
|
||||
run: yarn test:integration
|
||||
|
||||
halmos-tests:
|
||||
name: Run symbolic execution tests
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- uses: actions/checkout@v4
|
||||
|
||||
- name: Install Foundry
|
||||
uses: foundry-rs/foundry-toolchain@v1
|
||||
with:
|
||||
version: nightly
|
||||
|
||||
- name: Use Node.js
|
||||
uses: actions/setup-node@v4
|
||||
with:
|
||||
node-version: 20.x
|
||||
cache: 'yarn'
|
||||
|
||||
- name: Install dependencies
|
||||
run: yarn --frozen-lockfile --network-concurrency 1
|
||||
|
||||
- name: Precompile
|
||||
run: yarn build
|
||||
|
||||
- name: Run tests
|
||||
run: yarn test:integration
|
||||
|
||||
lint:
|
||||
name: Lint Commit Messages
|
||||
runs-on: ubuntu-latest
|
||||
|
||||
steps:
|
||||
- uses: actions/checkout@v4
|
||||
with:
|
||||
fetch-depth: 0
|
||||
|
||||
- uses: wagoid/commitlint-github-action@v5
|
||||
|
||||
- name: Install Foundry
|
||||
uses: foundry-rs/foundry-toolchain@v1
|
||||
with:
|
||||
version: nightly
|
||||
|
||||
- name: Use Node.js
|
||||
uses: actions/setup-node@v4
|
||||
with:
|
||||
node-version: 20.x
|
||||
cache: 'yarn'
|
||||
|
||||
- name: Install dependencies
|
||||
run: yarn --frozen-lockfile --network-concurrency 1
|
||||
|
||||
- run: yarn lint:check
|
||||
25
packages/contracts/.gitignore
vendored
Normal file
25
packages/contracts/.gitignore
vendored
Normal file
@@ -0,0 +1,25 @@
|
||||
# General
|
||||
yarn-error.log
|
||||
node_modules
|
||||
.DS_STORE
|
||||
.vscode
|
||||
|
||||
# Foundry files
|
||||
cache
|
||||
out-via-ir
|
||||
|
||||
# Config files
|
||||
.env
|
||||
|
||||
# Avoid ignoring gitkeep
|
||||
!/**/.gitkeep
|
||||
|
||||
# Keep the latest deployment only
|
||||
broadcast/*/*/*
|
||||
|
||||
# Out dir
|
||||
out
|
||||
crytic-export
|
||||
|
||||
# Echidna corpus
|
||||
test/invariants/fuzz/echidna_coverage
|
||||
1
packages/contracts/.husky/.gitignore
vendored
Normal file
1
packages/contracts/.husky/.gitignore
vendored
Normal file
@@ -0,0 +1 @@
|
||||
_
|
||||
1
packages/contracts/.husky/commit-msg
Executable file
1
packages/contracts/.husky/commit-msg
Executable file
@@ -0,0 +1 @@
|
||||
npx --no-install commitlint --edit $1
|
||||
4
packages/contracts/.husky/pre-commit
Executable file
4
packages/contracts/.husky/pre-commit
Executable file
@@ -0,0 +1,4 @@
|
||||
# 1. Build the contracts
|
||||
# 2. Stage build output
|
||||
# 3. Lint and stage style improvements
|
||||
yarn build && npx lint-staged
|
||||
14
packages/contracts/.solhint.json
Normal file
14
packages/contracts/.solhint.json
Normal file
@@ -0,0 +1,14 @@
|
||||
{
|
||||
"extends": "solhint:recommended",
|
||||
"rules": {
|
||||
"compiler-version": ["warn"],
|
||||
"quotes": "off",
|
||||
"func-visibility": ["warn", { "ignoreConstructors": true }],
|
||||
"no-inline-assembly": "off",
|
||||
"no-empty-blocks": "off",
|
||||
"private-vars-leading-underscore": ["warn", { "strict": false }],
|
||||
"ordering": "warn",
|
||||
"avoid-low-level-calls": "off",
|
||||
"named-parameters-mapping": "warn"
|
||||
}
|
||||
}
|
||||
8
packages/contracts/LICENSE
Normal file
8
packages/contracts/LICENSE
Normal file
@@ -0,0 +1,8 @@
|
||||
The MIT License (MIT)
|
||||
Copyright © 2023 Wonderland
|
||||
|
||||
Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions:
|
||||
|
||||
The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software.
|
||||
|
||||
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
||||
174
packages/contracts/README.md
Normal file
174
packages/contracts/README.md
Normal file
@@ -0,0 +1,174 @@
|
||||
<img src="https://raw.githubusercontent.com/defi-wonderland/brand/v1.0.0/external/solidity-foundry-boilerplate-banner.png" alt="wonderland banner" align="center" />
|
||||
<br />
|
||||
|
||||
<div align="center"><strong>Start your next Solidity project with Foundry in seconds</strong></div>
|
||||
<div align="center">A highly scalable foundation focused on DX and best practices</div>
|
||||
|
||||
<br />
|
||||
|
||||
## Features
|
||||
|
||||
<dl>
|
||||
<dt>Sample contracts</dt>
|
||||
<dd>Basic Greeter contract with an external interface.</dd>
|
||||
|
||||
<dt>Foundry setup</dt>
|
||||
<dd>Foundry configuration with multiple custom profiles and remappings.</dd>
|
||||
|
||||
<dt>Deployment scripts</dt>
|
||||
<dd>Sample scripts to deploy contracts on both mainnet and testnet.</dd>
|
||||
|
||||
<dt>Sample Integration, Unit, Property-based fuzzed and symbolic tests</dt>
|
||||
<dd>Example tests showcasing mocking, assertions and configuration for mainnet forking. As well it includes everything needed in order to check code coverage.</dd>
|
||||
<dd>Unit tests are built based on the <a href="https://twitter.com/PaulRBerg/status/1682346315806539776">Branched-Tree Technique</a>, using <a href="https://github.com/alexfertel/bulloak">Bulloak</a>.
|
||||
<dd>Formal verification and property-based fuzzing are achieved with <a href="https://github.com/a16z/halmos">Halmos</a> and <a href="https://github.com/crytic/medusa">Medusa</a> (resp.).
|
||||
|
||||
<dt>Linter</dt>
|
||||
<dd>Simple and fast solidity linting thanks to forge fmt.</dd>
|
||||
<dd>Find missing natspec automatically.</dd>
|
||||
|
||||
<dt>Github workflows CI</dt>
|
||||
<dd>Run all tests and see the coverage as you push your changes.</dd>
|
||||
<dd>Export your Solidity interfaces and contracts as packages, and publish them to NPM.</dd>
|
||||
</dl>
|
||||
|
||||
## Setup
|
||||
|
||||
1. Install Foundry by following the instructions from [their repository](https://github.com/foundry-rs/foundry#installation).
|
||||
2. Copy the `.env.example` file to `.env` and fill in the variables.
|
||||
3. Install the dependencies by running: `yarn install`. In case there is an error with the commands, run `foundryup` and try them again.
|
||||
|
||||
## Build
|
||||
|
||||
The default way to build the code is suboptimal but fast, you can run it via:
|
||||
|
||||
```bash
|
||||
yarn build
|
||||
```
|
||||
|
||||
In order to build a more optimized code ([via IR](https://docs.soliditylang.org/en/v0.8.15/ir-breaking-changes.html#solidity-ir-based-codegen-changes)), run:
|
||||
|
||||
```bash
|
||||
yarn build:optimized
|
||||
```
|
||||
|
||||
## Running tests
|
||||
|
||||
Unit tests should be isolated from any externalities, while Integration usually run in a fork of the blockchain. In this boilerplate you will find example of both.
|
||||
|
||||
In order to run both unit and integration tests, run:
|
||||
|
||||
```bash
|
||||
yarn test
|
||||
```
|
||||
|
||||
In order to just run unit tests, run:
|
||||
|
||||
```bash
|
||||
yarn test:unit
|
||||
```
|
||||
|
||||
In order to run unit tests and run way more fuzzing than usual (5x), run:
|
||||
|
||||
```bash
|
||||
yarn test:unit:deep
|
||||
```
|
||||
|
||||
In order to just run integration tests, run:
|
||||
|
||||
```bash
|
||||
yarn test:integration
|
||||
```
|
||||
|
||||
In order to start the Medusa fuzzing campaign (requires [Medusa](https://github.com/crytic/medusa/blob/master/docs/src/getting_started/installation.md) installed), run:
|
||||
|
||||
```bash
|
||||
yarn test:fuzz
|
||||
```
|
||||
|
||||
In order to just run the symbolic execution tests (requires [Halmos](https://github.com/a16z/halmos/blob/main/README.md#installation) installed), run:
|
||||
|
||||
```bash
|
||||
yarn test:symbolic
|
||||
```
|
||||
|
||||
In order to check your current code coverage, run:
|
||||
|
||||
```bash
|
||||
yarn coverage
|
||||
```
|
||||
|
||||
<br>
|
||||
|
||||
## Deploy & verify
|
||||
|
||||
### Setup
|
||||
|
||||
Configure the `.env` variables and source them:
|
||||
|
||||
```bash
|
||||
source .env
|
||||
```
|
||||
|
||||
Import your private keys into Foundry's encrypted keystore:
|
||||
|
||||
```bash
|
||||
cast wallet import $MAINNET_DEPLOYER_NAME --interactive
|
||||
```
|
||||
|
||||
```bash
|
||||
cast wallet import $SEPOLIA_DEPLOYER_NAME --interactive
|
||||
```
|
||||
|
||||
### Sepolia
|
||||
|
||||
```bash
|
||||
yarn deploy:sepolia
|
||||
```
|
||||
|
||||
### Mainnet
|
||||
|
||||
```bash
|
||||
yarn deploy:mainnet
|
||||
```
|
||||
|
||||
The deployments are stored in ./broadcast
|
||||
|
||||
See the [Foundry Book for available options](https://book.getfoundry.sh/reference/forge/forge-create.html).
|
||||
|
||||
## Export And Publish
|
||||
|
||||
Export TypeScript interfaces from Solidity contracts and interfaces providing compatibility with TypeChain. Publish the exported packages to NPM.
|
||||
|
||||
To enable this feature, make sure you've set the `NPM_TOKEN` on your org's secrets. Then set the job's conditional to `true`:
|
||||
|
||||
```yaml
|
||||
jobs:
|
||||
export:
|
||||
name: Generate Interfaces And Contracts
|
||||
# Remove the following line if you wish to export your Solidity contracts and interfaces and publish them to NPM
|
||||
if: true
|
||||
...
|
||||
```
|
||||
|
||||
Also, remember to update the `package_name` param to your package name:
|
||||
|
||||
```yaml
|
||||
- name: Export Solidity - ${{ matrix.export_type }}
|
||||
uses: defi-wonderland/solidity-exporter-action@1dbf5371c260add4a354e7a8d3467e5d3b9580b8
|
||||
with:
|
||||
# Update package_name with your package name
|
||||
package_name: "my-cool-project"
|
||||
...
|
||||
|
||||
|
||||
- name: Publish to NPM - ${{ matrix.export_type }}
|
||||
# Update `my-cool-project` with your package name
|
||||
run: cd export/my-cool-project-${{ matrix.export_type }} && npm publish --access public
|
||||
...
|
||||
```
|
||||
|
||||
You can take a look at our [solidity-exporter-action](https://github.com/defi-wonderland/solidity-exporter-action) repository for more information and usage examples.
|
||||
|
||||
## Licensing
|
||||
The primary license for the boilerplate is MIT, see [`LICENSE`](https://github.com/defi-wonderland/solidity-foundry-boilerplate/blob/main/LICENSE)
|
||||
1
packages/contracts/commitlint.config.js
Normal file
1
packages/contracts/commitlint.config.js
Normal file
@@ -0,0 +1 @@
|
||||
module.exports = { extends: ['@commitlint/config-conventional'] };
|
||||
36
packages/contracts/foundry.toml
Normal file
36
packages/contracts/foundry.toml
Normal file
@@ -0,0 +1,36 @@
|
||||
[fmt]
|
||||
line_length = 120
|
||||
tab_width = 2
|
||||
bracket_spacing = false
|
||||
int_types = 'long'
|
||||
quote_style = 'single'
|
||||
number_underscore = 'thousands'
|
||||
multiline_func_header = 'params_first_multi'
|
||||
sort_imports = true
|
||||
|
||||
[profile.default]
|
||||
solc_version = '0.8.23'
|
||||
libs = ['../../node_modules', 'lib']
|
||||
optimizer_runs = 10_000
|
||||
|
||||
[profile.optimized]
|
||||
via_ir = true
|
||||
out = 'out-via-ir'
|
||||
|
||||
[profile.test]
|
||||
via_ir = true
|
||||
out = 'out-via-ir'
|
||||
|
||||
[profile.docs]
|
||||
src = 'src/interfaces/'
|
||||
|
||||
[fuzz]
|
||||
runs = 1000
|
||||
|
||||
[rpc_endpoints]
|
||||
mainnet = "${MAINNET_RPC}"
|
||||
sepolia = "${SEPOLIA_RPC}"
|
||||
|
||||
[etherscan]
|
||||
mainnet = { key = "${ETHERSCAN_API_KEY}" }
|
||||
sepolia = { key = "${ETHERSCAN_API_KEY}" }
|
||||
89
packages/contracts/medusa.json
Normal file
89
packages/contracts/medusa.json
Normal file
@@ -0,0 +1,89 @@
|
||||
{
|
||||
"fuzzing": {
|
||||
"workers": 10,
|
||||
"workerResetLimit": 50,
|
||||
"timeout": 0,
|
||||
"testLimit": 0,
|
||||
"shrinkLimit": 5000,
|
||||
"callSequenceLength": 100,
|
||||
"corpusDirectory": "",
|
||||
"coverageEnabled": true,
|
||||
"coverageFormats": [
|
||||
"html",
|
||||
"lcov"
|
||||
],
|
||||
"targetContracts": ["FuzzTest"],
|
||||
"predeployedContracts": {},
|
||||
"targetContractsBalances": [],
|
||||
"constructorArgs": {},
|
||||
"deployerAddress": "0x30000",
|
||||
"senderAddresses": [
|
||||
"0x10000",
|
||||
"0x20000",
|
||||
"0x30000"
|
||||
],
|
||||
"blockNumberDelayMax": 60480,
|
||||
"blockTimestampDelayMax": 604800,
|
||||
"blockGasLimit": 125000000,
|
||||
"transactionGasLimit": 12500000,
|
||||
"testing": {
|
||||
"stopOnFailedTest": true,
|
||||
"stopOnFailedContractMatching": false,
|
||||
"stopOnNoTests": true,
|
||||
"testAllContracts": false,
|
||||
"traceAll": false,
|
||||
"assertionTesting": {
|
||||
"enabled": true,
|
||||
"testViewMethods": true,
|
||||
"panicCodeConfig": {
|
||||
"failOnCompilerInsertedPanic": false,
|
||||
"failOnAssertion": true,
|
||||
"failOnArithmeticUnderflow": false,
|
||||
"failOnDivideByZero": false,
|
||||
"failOnEnumTypeConversionOutOfBounds": false,
|
||||
"failOnIncorrectStorageAccess": false,
|
||||
"failOnPopEmptyArray": false,
|
||||
"failOnOutOfBoundsArrayAccess": false,
|
||||
"failOnAllocateTooMuchMemory": false,
|
||||
"failOnCallUninitializedVariable": false
|
||||
}
|
||||
},
|
||||
"propertyTesting": {
|
||||
"enabled": false,
|
||||
"testPrefixes": [
|
||||
"property_"
|
||||
]
|
||||
},
|
||||
"optimizationTesting": {
|
||||
"enabled": false,
|
||||
"testPrefixes": [
|
||||
"optimize_"
|
||||
]
|
||||
},
|
||||
"targetFunctionSignatures": [],
|
||||
"excludeFunctionSignatures": []
|
||||
},
|
||||
"chainConfig": {
|
||||
"codeSizeCheckDisabled": true,
|
||||
"cheatCodes": {
|
||||
"cheatCodesEnabled": true,
|
||||
"enableFFI": false
|
||||
},
|
||||
"skipAccountChecks": true
|
||||
}
|
||||
},
|
||||
"compilation": {
|
||||
"platform": "crytic-compile",
|
||||
"platformConfig": {
|
||||
"target": "test/invariants/fuzz/FuzzTest.t.sol",
|
||||
"solcVersion": "",
|
||||
"exportDirectory": "",
|
||||
"args": []
|
||||
}
|
||||
},
|
||||
"logging": {
|
||||
"level": "info",
|
||||
"logDirectory": "",
|
||||
"noColor": false
|
||||
}
|
||||
}
|
||||
9
packages/contracts/natspec-smells.config.js
Normal file
9
packages/contracts/natspec-smells.config.js
Normal file
@@ -0,0 +1,9 @@
|
||||
/**
|
||||
* List of supported options: https://github.com/defi-wonderland/natspec-smells?tab=readme-ov-file#options
|
||||
*/
|
||||
|
||||
/** @type {import('@defi-wonderland/natspec-smells').Config} */
|
||||
module.exports = {
|
||||
include: 'src/**/*.sol',
|
||||
exclude: '(test|scripts)/**/*.sol',
|
||||
};
|
||||
46
packages/contracts/package.json
Normal file
46
packages/contracts/package.json
Normal file
@@ -0,0 +1,46 @@
|
||||
{
|
||||
"name": "solidity-foundry-boilerplate",
|
||||
"version": "1.0.0",
|
||||
"description": "Production ready Solidity boilerplate with Foundry",
|
||||
"homepage": "https://github.com/defi-wonderland/solidity-foundry-boilerplate#readme",
|
||||
"repository": {
|
||||
"type": "git",
|
||||
"url": "git+https://github.com/defi-wonderland/solidity-foundry-boilerplate.git"
|
||||
},
|
||||
"license": "MIT",
|
||||
"author": "Wonderland",
|
||||
"scripts": {
|
||||
"build": "forge build",
|
||||
"build:optimized": "FOUNDRY_PROFILE=optimized forge build",
|
||||
"coverage": "forge coverage --report summary --report lcov --match-path 'test/unit/*'",
|
||||
"deploy:mainnet": "bash -c 'source .env && forge script Deploy --rpc-url $MAINNET_RPC --account $MAINNET_DEPLOYER_NAME --broadcast --verify --chain mainnet -vvvvv'",
|
||||
"deploy:sepolia": "bash -c 'source .env && forge script Deploy --rpc-url $SEPOLIA_RPC --account $SEPOLIA_DEPLOYER_NAME --broadcast --verify --chain sepolia -vvvvv'",
|
||||
"lint:check": "yarn lint:sol && forge fmt --check",
|
||||
"lint:fix": "sort-package-json && forge fmt && yarn lint:sol --fix",
|
||||
"lint:natspec": "npx @defi-wonderland/natspec-smells --config natspec-smells.config.js",
|
||||
"lint:sol": "solhint 'src/**/*.sol' 'script/**/*.sol' 'test/**/*.sol'",
|
||||
"prepare": "husky",
|
||||
"test": "forge test -vvv",
|
||||
"test:fuzz": "medusa fuzz",
|
||||
"test:integration": "forge test --match-contract Integration -vvv",
|
||||
"test:symbolic": "halmos",
|
||||
"test:unit": "forge test --match-contract Unit -vvv",
|
||||
"test:unit:deep": "FOUNDRY_FUZZ_RUNS=5000 yarn test:unit"
|
||||
},
|
||||
"lint-staged": {
|
||||
"*.{js,css,md,ts,sol}": "forge fmt",
|
||||
"(src|test|script)/**/*.sol": "yarn lint:sol",
|
||||
"package.json": "sort-package-json"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@commitlint/cli": "19.3.0",
|
||||
"@commitlint/config-conventional": "19.2.2",
|
||||
"@defi-wonderland/natspec-smells": "1.1.3",
|
||||
"forge-std": "github:foundry-rs/forge-std#1.9.2",
|
||||
"halmos-cheatcodes": "github:a16z/halmos-cheatcodes#c0d8655",
|
||||
"husky": ">=9",
|
||||
"lint-staged": ">=10",
|
||||
"solhint-community": "4.0.0",
|
||||
"sort-package-json": "2.10.0"
|
||||
}
|
||||
}
|
||||
5
packages/contracts/remappings.txt
Normal file
5
packages/contracts/remappings.txt
Normal file
@@ -0,0 +1,5 @@
|
||||
forge-std/=../../node_modules/forge-std/src
|
||||
halmos-cheatcodes=../../node_modules/halmos-cheatcodes
|
||||
|
||||
contracts/=src/contracts
|
||||
interfaces/=src/interfaces
|
||||
7
packages/contracts/script/.solhint.json
Normal file
7
packages/contracts/script/.solhint.json
Normal file
@@ -0,0 +1,7 @@
|
||||
{
|
||||
"rules": {
|
||||
"ordering": "off",
|
||||
"one-contract-per-file": "off",
|
||||
"no-console": "off"
|
||||
}
|
||||
}
|
||||
33
packages/contracts/script/Deploy.sol
Normal file
33
packages/contracts/script/Deploy.sol
Normal file
@@ -0,0 +1,33 @@
|
||||
// SPDX-License-Identifier: MIT
|
||||
pragma solidity 0.8.23;
|
||||
|
||||
import {Greeter} from 'contracts/Greeter.sol';
|
||||
import {Script} from 'forge-std/Script.sol';
|
||||
import {IERC20} from 'forge-std/interfaces/IERC20.sol';
|
||||
|
||||
contract Deploy is Script {
|
||||
struct DeploymentParams {
|
||||
string greeting;
|
||||
IERC20 token;
|
||||
}
|
||||
|
||||
/// @notice Deployment parameters for each chain
|
||||
mapping(uint256 _chainId => DeploymentParams _params) internal _deploymentParams;
|
||||
|
||||
function setUp() public {
|
||||
// Mainnet
|
||||
_deploymentParams[1] = DeploymentParams('Hello, Mainnet!', IERC20(0xC02aaA39b223FE8D0A0e5C4F27eAD9083C756Cc2));
|
||||
|
||||
// Sepolia
|
||||
_deploymentParams[11_155_111] =
|
||||
DeploymentParams('Hello, Sepolia!', IERC20(0xB4FBF271143F4FBf7B91A5ded31805e42b2208d6));
|
||||
}
|
||||
|
||||
function run() public {
|
||||
DeploymentParams memory _params = _deploymentParams[block.chainid];
|
||||
|
||||
vm.startBroadcast();
|
||||
new Greeter(_params.greeting, _params.token);
|
||||
vm.stopBroadcast();
|
||||
}
|
||||
}
|
||||
59
packages/contracts/src/contracts/Greeter.sol
Normal file
59
packages/contracts/src/contracts/Greeter.sol
Normal file
@@ -0,0 +1,59 @@
|
||||
// SPDX-License-Identifier: MIT
|
||||
pragma solidity 0.8.23;
|
||||
|
||||
import {IERC20} from 'forge-std/interfaces/IERC20.sol';
|
||||
import {IGreeter} from 'interfaces/IGreeter.sol';
|
||||
|
||||
contract Greeter is IGreeter {
|
||||
/**
|
||||
* @notice Empty string for revert checks
|
||||
* @dev result of doing keccak256(bytes(''))
|
||||
*/
|
||||
bytes32 internal constant _EMPTY_STRING = 0xc5d2460186f7233c927e7db2dcc703c0e500b653ca82273b7bfad8045d85a470;
|
||||
|
||||
/// @inheritdoc IGreeter
|
||||
address public immutable OWNER;
|
||||
|
||||
/// @inheritdoc IGreeter
|
||||
string public greeting;
|
||||
|
||||
/// @inheritdoc IGreeter
|
||||
IERC20 public token;
|
||||
|
||||
/**
|
||||
* @notice Reverts in case the function was not called by the owner of the contract
|
||||
*/
|
||||
modifier onlyOwner() {
|
||||
if (msg.sender != OWNER) {
|
||||
revert Greeter_OnlyOwner();
|
||||
}
|
||||
_;
|
||||
}
|
||||
|
||||
/**
|
||||
* @notice Defines the owner to the msg.sender and sets the initial greeting
|
||||
* @param _greeting Initial greeting
|
||||
* @param _token Initial token
|
||||
*/
|
||||
constructor(string memory _greeting, IERC20 _token) {
|
||||
OWNER = msg.sender;
|
||||
token = _token;
|
||||
setGreeting(_greeting);
|
||||
}
|
||||
|
||||
/// @inheritdoc IGreeter
|
||||
function greet() external view returns (string memory _greeting, uint256 _balance) {
|
||||
_greeting = greeting;
|
||||
_balance = token.balanceOf(msg.sender);
|
||||
}
|
||||
|
||||
/// @inheritdoc IGreeter
|
||||
function setGreeting(string memory _greeting) public onlyOwner {
|
||||
if (keccak256(bytes(_greeting)) == _EMPTY_STRING) {
|
||||
revert Greeter_InvalidGreeting();
|
||||
}
|
||||
|
||||
greeting = _greeting;
|
||||
emit GreetingSet(_greeting);
|
||||
}
|
||||
}
|
||||
11
packages/contracts/src/interfaces/.solhint.json
Normal file
11
packages/contracts/src/interfaces/.solhint.json
Normal file
@@ -0,0 +1,11 @@
|
||||
{
|
||||
"rules": {
|
||||
"ordering": "warn",
|
||||
"style-guide-casing": [
|
||||
"warn",
|
||||
{
|
||||
"ignoreExternalFunctions": true
|
||||
}
|
||||
]
|
||||
}
|
||||
}
|
||||
74
packages/contracts/src/interfaces/IGreeter.sol
Normal file
74
packages/contracts/src/interfaces/IGreeter.sol
Normal file
@@ -0,0 +1,74 @@
|
||||
// SPDX-License-Identifier: MIT
|
||||
pragma solidity 0.8.23;
|
||||
|
||||
import {IERC20} from 'forge-std/interfaces/IERC20.sol';
|
||||
|
||||
/**
|
||||
* @title Greeter Contract
|
||||
* @author Wonderland
|
||||
* @notice This is a basic contract created in order to portray some
|
||||
* best practices and foundry functionality.
|
||||
*/
|
||||
interface IGreeter {
|
||||
/*///////////////////////////////////////////////////////////////
|
||||
EVENTS
|
||||
//////////////////////////////////////////////////////////////*/
|
||||
/**
|
||||
* @notice Greeting has changed
|
||||
* @param _greeting The new greeting
|
||||
*/
|
||||
event GreetingSet(string _greeting);
|
||||
|
||||
/*///////////////////////////////////////////////////////////////
|
||||
ERRORS
|
||||
//////////////////////////////////////////////////////////////*/
|
||||
/**
|
||||
* @notice Throws if the function was called by someone else than the owner
|
||||
*/
|
||||
error Greeter_OnlyOwner();
|
||||
|
||||
/**
|
||||
* @notice Throws if the greeting set is invalid
|
||||
* @dev Empty string is an invalid greeting
|
||||
*/
|
||||
error Greeter_InvalidGreeting();
|
||||
|
||||
/*///////////////////////////////////////////////////////////////
|
||||
VARIABLES
|
||||
//////////////////////////////////////////////////////////////*/
|
||||
/**
|
||||
* @notice Returns the owner of the contract
|
||||
* @dev The owner will always be the deployer of the contract
|
||||
* @return _owner The owner of the contract
|
||||
*/
|
||||
function OWNER() external view returns (address _owner);
|
||||
|
||||
/**
|
||||
* @notice Returns the previously set greeting
|
||||
* @return _greet The greeting
|
||||
*/
|
||||
function greeting() external view returns (string memory _greet);
|
||||
|
||||
/**
|
||||
* @notice Returns the token used to greet callers
|
||||
* @return _token The address of the token
|
||||
*/
|
||||
function token() external view returns (IERC20 _token);
|
||||
|
||||
/*///////////////////////////////////////////////////////////////
|
||||
LOGIC
|
||||
//////////////////////////////////////////////////////////////*/
|
||||
/**
|
||||
* @notice Sets a new greeting
|
||||
* @dev Only callable by the owner
|
||||
* @param _newGreeting The new greeting to be set
|
||||
*/
|
||||
function setGreeting(string memory _newGreeting) external;
|
||||
|
||||
/**
|
||||
* @notice Greets the caller
|
||||
* @return _greeting The greeting
|
||||
* @return _balance Current token balance of the caller
|
||||
*/
|
||||
function greet() external view returns (string memory _greeting, uint256 _balance);
|
||||
}
|
||||
16
packages/contracts/test/.solhint.json
Normal file
16
packages/contracts/test/.solhint.json
Normal file
@@ -0,0 +1,16 @@
|
||||
{
|
||||
"rules": {
|
||||
"style-guide-casing": [
|
||||
"warn",
|
||||
{
|
||||
"ignorePublicFunctions":true,
|
||||
"ignoreExternalFunctions":true,
|
||||
"ignoreContracts":true
|
||||
}
|
||||
],
|
||||
"no-global-import": "off",
|
||||
"max-states-count": "off",
|
||||
"ordering": "off",
|
||||
"one-contract-per-file": "off"
|
||||
}
|
||||
}
|
||||
16
packages/contracts/test/integration/Greeter.t.sol
Normal file
16
packages/contracts/test/integration/Greeter.t.sol
Normal file
@@ -0,0 +1,16 @@
|
||||
// SPDX-License-Identifier: MIT
|
||||
pragma solidity 0.8.23;
|
||||
|
||||
import {IntegrationBase} from 'test/integration/IntegrationBase.sol';
|
||||
|
||||
contract IntegrationGreeter is IntegrationBase {
|
||||
function test_Greet() public {
|
||||
uint256 _whaleBalance = _dai.balanceOf(_daiWhale);
|
||||
|
||||
vm.prank(_daiWhale);
|
||||
(string memory _greeting, uint256 _balance) = _greeter.greet();
|
||||
|
||||
assertEq(_whaleBalance, _balance);
|
||||
assertEq(_initialGreeting, _greeting);
|
||||
}
|
||||
}
|
||||
23
packages/contracts/test/integration/IntegrationBase.sol
Normal file
23
packages/contracts/test/integration/IntegrationBase.sol
Normal file
@@ -0,0 +1,23 @@
|
||||
// SPDX-License-Identifier: MIT
|
||||
pragma solidity 0.8.23;
|
||||
|
||||
import {Greeter, IGreeter} from 'contracts/Greeter.sol';
|
||||
import {Test} from 'forge-std/Test.sol';
|
||||
import {IERC20} from 'forge-std/interfaces/IERC20.sol';
|
||||
|
||||
contract IntegrationBase is Test {
|
||||
uint256 internal constant _FORK_BLOCK = 18_920_905;
|
||||
|
||||
string internal _initialGreeting = 'hola';
|
||||
address internal _user = makeAddr('user');
|
||||
address internal _owner = makeAddr('owner');
|
||||
address internal _daiWhale = 0x42f8CA49E88A8fd8F0bfA2C739e648468b8f9dec;
|
||||
IERC20 internal _dai = IERC20(0x6B175474E89094C44Da98b954EedeAC495271d0F);
|
||||
IGreeter internal _greeter;
|
||||
|
||||
function setUp() public {
|
||||
vm.createSelectFork(vm.rpcUrl('mainnet'), _FORK_BLOCK);
|
||||
vm.prank(_owner);
|
||||
_greeter = new Greeter(_initialGreeting, _dai);
|
||||
}
|
||||
}
|
||||
4
packages/contracts/test/invariants/PROPERTIES.md
Normal file
4
packages/contracts/test/invariants/PROPERTIES.md
Normal file
@@ -0,0 +1,4 @@
|
||||
| Id | Properties | Type |
|
||||
| --- | --------------------------------------------------- | ------------ |
|
||||
| 1 | Greeting should never be empty | Valid state |
|
||||
| 2 | Only the owner can set the greeting | State transition |
|
||||
8
packages/contracts/test/invariants/fuzz/FuzzTest.t.sol
Normal file
8
packages/contracts/test/invariants/fuzz/FuzzTest.t.sol
Normal file
@@ -0,0 +1,8 @@
|
||||
// SPDX-License-Identifier: UNLICENSED
|
||||
pragma solidity 0.8.23;
|
||||
|
||||
import {GreeterGuidedHandlers} from './handlers/guided/Greeter.t.sol';
|
||||
import {GreeterUnguidedHandlers} from './handlers/unguided/Greeter.t.sol';
|
||||
import {GreeterProperties} from './properties/Greeter.t.sol';
|
||||
|
||||
contract FuzzTest is GreeterGuidedHandlers, GreeterUnguidedHandlers, GreeterProperties {}
|
||||
@@ -0,0 +1,15 @@
|
||||
// SPDX-License-Identifier: UNLICENSED
|
||||
pragma solidity 0.8.23;
|
||||
|
||||
import {GreeterSetup} from '../../setup/Greeter.t.sol';
|
||||
|
||||
contract GreeterGuidedHandlers is GreeterSetup {
|
||||
function handler_setGreeting(string memory _newGreeting) external {
|
||||
// no need to prank since this contract deployed the greeter and is therefore its owner
|
||||
try _targetContract.setGreeting(_newGreeting) {
|
||||
assert(keccak256(bytes(_targetContract.greeting())) == keccak256(bytes(_newGreeting)));
|
||||
} catch {
|
||||
assert(keccak256(bytes(_newGreeting)) == keccak256(''));
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,18 @@
|
||||
// SPDX-License-Identifier: UNLICENSED
|
||||
pragma solidity 0.8.23;
|
||||
|
||||
import {GreeterSetup} from '../../setup/Greeter.t.sol';
|
||||
|
||||
contract GreeterUnguidedHandlers is GreeterSetup {
|
||||
/// @custom:property-id 2
|
||||
/// @custom:property Only the owner can set the greeting
|
||||
function handler_setGreeting(address _caller, string memory _newGreeting) external {
|
||||
vm.prank(_caller);
|
||||
try _targetContract.setGreeting(_newGreeting) {
|
||||
assert(keccak256(bytes(_targetContract.greeting())) == keccak256(bytes(_newGreeting)));
|
||||
assert(_caller == _targetContract.OWNER());
|
||||
} catch {
|
||||
assert(_caller != _targetContract.OWNER() || keccak256(bytes(_newGreeting)) == keccak256(''));
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,12 @@
|
||||
// SPDX-License-Identifier: UNLICENSED
|
||||
pragma solidity 0.8.23;
|
||||
|
||||
import {GreeterSetup} from '../setup/Greeter.t.sol';
|
||||
|
||||
contract GreeterProperties is GreeterSetup {
|
||||
/// @custom:property-id 1
|
||||
/// @custom:property Greeting should never be empty
|
||||
function property_greetingIsNeverEmpty() external view {
|
||||
assert(keccak256(bytes(_targetContract.greeting())) != keccak256(''));
|
||||
}
|
||||
}
|
||||
13
packages/contracts/test/invariants/fuzz/setup/Greeter.t.sol
Normal file
13
packages/contracts/test/invariants/fuzz/setup/Greeter.t.sol
Normal file
@@ -0,0 +1,13 @@
|
||||
// SPDX-License-Identifier: UNLICENSED
|
||||
pragma solidity 0.8.23;
|
||||
|
||||
import {Greeter, IERC20} from 'contracts/Greeter.sol';
|
||||
import {CommonBase} from 'forge-std/Base.sol';
|
||||
|
||||
contract GreeterSetup is CommonBase {
|
||||
Greeter internal _targetContract;
|
||||
|
||||
constructor() {
|
||||
_targetContract = new Greeter('a', IERC20(address(1)));
|
||||
}
|
||||
}
|
||||
58
packages/contracts/test/invariants/symbolic/Greeter.t.sol
Normal file
58
packages/contracts/test/invariants/symbolic/Greeter.t.sol
Normal file
@@ -0,0 +1,58 @@
|
||||
// SPDX-License-Identifier: UNLICENSED
|
||||
pragma solidity 0.8.23;
|
||||
|
||||
import {Greeter, IERC20} from 'contracts/Greeter.sol';
|
||||
|
||||
import {Test} from 'forge-std/Test.sol';
|
||||
import {SymTest} from 'halmos-cheatcodes/src/SymTest.sol'; // See https://github.com/a16z/halmos-cheatcodes?tab=readme-ov-file
|
||||
|
||||
contract SymbolicGreeter is SymTest, Test {
|
||||
Greeter public targetContract;
|
||||
|
||||
function setUp() public {
|
||||
string memory _initialGreeting = svm.createString(64, 'initial greeting');
|
||||
address _token = svm.createAddress('token');
|
||||
|
||||
targetContract = new Greeter(_initialGreeting, IERC20(_token));
|
||||
}
|
||||
|
||||
function check_validState_greeterNeverEmpty(address _caller) public {
|
||||
// Input conditions: any caller
|
||||
vm.prank(_caller);
|
||||
|
||||
// Execution: Halmos cannot use a dynamic-sized array, iterate over multiple string lengths
|
||||
bool _success;
|
||||
for (uint256 i = 1; i < 3; i++) {
|
||||
string memory greeting = svm.createString(i, 'greeting');
|
||||
(_success,) = address(targetContract).call(abi.encodeCall(Greeter.setGreeting, (greeting)));
|
||||
|
||||
// Output condition check
|
||||
vm.assume(_success); // discard failing calls
|
||||
assert(keccak256(bytes(targetContract.greeting())) != keccak256(bytes('')));
|
||||
}
|
||||
|
||||
// Add the empty string (bypass the non-empty check of svm.createString)
|
||||
(_success,) = address(targetContract).call(abi.encodeCall(Greeter.setGreeting, ('')));
|
||||
|
||||
// Output condition check
|
||||
vm.assume(_success); // discard failing calls
|
||||
assert(keccak256(bytes(targetContract.greeting())) != keccak256(bytes('')));
|
||||
}
|
||||
|
||||
function check_setGreeting_onlyOwnerSetsGreeting(address _caller) public {
|
||||
// Input conditions
|
||||
string memory _newGreeting = svm.createString(64, 'new greeting');
|
||||
|
||||
// Execution
|
||||
vm.prank(_caller);
|
||||
(bool _success,) = address(targetContract).call(abi.encodeCall(Greeter.setGreeting, (_newGreeting)));
|
||||
|
||||
// Output condition check
|
||||
if (_success) {
|
||||
assert(_caller == targetContract.OWNER());
|
||||
assert(keccak256(bytes(targetContract.greeting())) == keccak256(bytes(_newGreeting)));
|
||||
} else {
|
||||
assert(_caller != targetContract.OWNER() || keccak256(bytes(_newGreeting)) == keccak256(bytes('')));
|
||||
}
|
||||
}
|
||||
}
|
||||
99
packages/contracts/test/unit/Greeter.t.sol
Normal file
99
packages/contracts/test/unit/Greeter.t.sol
Normal file
@@ -0,0 +1,99 @@
|
||||
// SPDX-License-Identifier: UNLICENSED
|
||||
pragma solidity 0.8.23;
|
||||
|
||||
import {Greeter, IGreeter} from 'contracts/Greeter.sol';
|
||||
import {Test} from 'forge-std/Test.sol';
|
||||
import {IERC20} from 'forge-std/interfaces/IERC20.sol';
|
||||
|
||||
contract UnitGreeter is Test {
|
||||
address internal _owner = makeAddr('owner');
|
||||
IERC20 internal _token = IERC20(makeAddr('token'));
|
||||
uint256 internal _initialBalance = 100;
|
||||
string internal _initialGreeting = 'hola';
|
||||
|
||||
Greeter internal _greeter;
|
||||
|
||||
event GreetingSet(string _greeting);
|
||||
|
||||
function setUp() external {
|
||||
vm.prank(_owner);
|
||||
_greeter = new Greeter(_initialGreeting, _token);
|
||||
|
||||
vm.etch(address(_token), new bytes(0x1));
|
||||
}
|
||||
|
||||
function test_EmptyTestExample() external {
|
||||
// it does nothing
|
||||
vm.skip(true);
|
||||
}
|
||||
|
||||
function test_ConstructorWhenPassingValidGreetingString() external {
|
||||
vm.prank(_owner);
|
||||
|
||||
// it deploys
|
||||
_greeter = new Greeter(_initialGreeting, _token);
|
||||
|
||||
// it sets the greeting string
|
||||
assertEq(_greeter.greeting(), _initialGreeting);
|
||||
|
||||
// it sets the owner as sender
|
||||
assertEq(_greeter.OWNER(), _owner);
|
||||
|
||||
// it sets the token used
|
||||
assertEq(address(_greeter.token()), address(_token));
|
||||
}
|
||||
|
||||
function test_ConstructorWhenPassingAnEmptyGreetingString() external {
|
||||
vm.prank(_owner);
|
||||
|
||||
// it reverts
|
||||
vm.expectRevert(IGreeter.Greeter_InvalidGreeting.selector);
|
||||
_greeter = new Greeter('', _token);
|
||||
}
|
||||
|
||||
function test_GreetWhenCalled() external {
|
||||
vm.mockCall(address(_token), abi.encodeWithSelector(IERC20.balanceOf.selector), abi.encode(_initialBalance));
|
||||
vm.expectCall(address(_token), abi.encodeWithSelector(IERC20.balanceOf.selector));
|
||||
(string memory _greet, uint256 _balance) = _greeter.greet();
|
||||
|
||||
// it returns the greeting string
|
||||
assertEq(_greet, _initialGreeting);
|
||||
|
||||
// it returns the token balance of the contract
|
||||
assertEq(_balance, _initialBalance);
|
||||
}
|
||||
|
||||
modifier whenCalledByTheOwner() {
|
||||
vm.startPrank(_owner);
|
||||
_;
|
||||
vm.stopPrank();
|
||||
}
|
||||
|
||||
function test_SetGreetingWhenPassingAValidGreetingString() external whenCalledByTheOwner {
|
||||
string memory _newGreeting = 'hello';
|
||||
|
||||
// it emit GreetingSet
|
||||
vm.expectEmit(true, true, true, true, address(_greeter));
|
||||
emit GreetingSet(_newGreeting);
|
||||
|
||||
_greeter.setGreeting(_newGreeting);
|
||||
|
||||
// it sets the greeting string
|
||||
assertEq(_greeter.greeting(), _newGreeting);
|
||||
}
|
||||
|
||||
function test_SetGreetingWhenPassingAnEmptyGreetingString() external whenCalledByTheOwner {
|
||||
// it reverts
|
||||
vm.expectRevert(IGreeter.Greeter_InvalidGreeting.selector);
|
||||
_greeter.setGreeting('');
|
||||
}
|
||||
|
||||
function test_SetGreetingWhenCalledByANon_owner(address _caller) external {
|
||||
vm.assume(_caller != _owner);
|
||||
vm.prank(_caller);
|
||||
|
||||
// it reverts
|
||||
vm.expectRevert(IGreeter.Greeter_OnlyOwner.selector);
|
||||
_greeter.setGreeting('new greeting');
|
||||
}
|
||||
}
|
||||
25
packages/contracts/test/unit/Greeter.tree
Normal file
25
packages/contracts/test/unit/Greeter.tree
Normal file
@@ -0,0 +1,25 @@
|
||||
Greeter::constructor
|
||||
├── when passing valid greeting string
|
||||
│ ├── it deploys
|
||||
│ ├── it sets the greeting string
|
||||
│ ├── it sets the owner as sender
|
||||
│ └── it sets the token used
|
||||
└── when passing an empty greeting string
|
||||
└── it reverts
|
||||
|
||||
|
||||
Greeter::greet
|
||||
└── when called
|
||||
├── it returns the greeting string
|
||||
└── it returns the token balance of the contract
|
||||
|
||||
|
||||
Greeter::setGreeting
|
||||
├── when called by the owner
|
||||
│ ├── when passing a valid greeting string
|
||||
│ │ ├── it sets the greeting string
|
||||
│ │ └── it emit GreetingSet
|
||||
│ └── when passing an empty greeting string
|
||||
│ └── it reverts
|
||||
└── when called by a non-owner
|
||||
└── it reverts
|
||||
1991
packages/contracts/yarn.lock
Normal file
1991
packages/contracts/yarn.lock
Normal file
File diff suppressed because it is too large
Load Diff
Reference in New Issue
Block a user