From fb47691dc1de5efd7e08358cf2239d058673bee1 Mon Sep 17 00:00:00 2001
From: Aztec Bot <49558828+AztecBot@users.noreply.github.com>
Date: Tue, 12 Mar 2024 09:46:08 -0400
Subject: [PATCH 1/5] chore(master): Release 0.27.0 (#5100)
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit
:robot: I have created a release *beep* *boop*
---
aztec-package: 0.27.0
##
[0.27.0](https://github.com/AztecProtocol/aztec-packages/compare/aztec-package-v0.26.6...aztec-package-v0.27.0)
(2024-03-12)
### Features
* Add api for inclusion proof of outgoing message in block
[#4562](https://github.com/AztecProtocol/aztec-packages/issues/4562)
([#4899](https://github.com/AztecProtocol/aztec-packages/issues/4899))
([26d2643](https://github.com/AztecProtocol/aztec-packages/commit/26d26437022567e2d54052f21b1c937259f26c94))
### Miscellaneous
* Pin foundry
([#5151](https://github.com/AztecProtocol/aztec-packages/issues/5151))
([69bd7dd](https://github.com/AztecProtocol/aztec-packages/commit/69bd7dd45af6b197b23c25dc883a1a5485955203))
* Remove old contract deployment flow
([#4970](https://github.com/AztecProtocol/aztec-packages/issues/4970))
([6d15947](https://github.com/AztecProtocol/aztec-packages/commit/6d1594736e96cd744ea691a239fcd3a46bdade60))
barretenberg.js: 0.27.0
##
[0.27.0](https://github.com/AztecProtocol/aztec-packages/compare/barretenberg.js-v0.26.6...barretenberg.js-v0.27.0)
(2024-03-12)
### Miscellaneous
* Move alpine containers to ubuntu
([#5026](https://github.com/AztecProtocol/aztec-packages/issues/5026))
([d483e67](https://github.com/AztecProtocol/aztec-packages/commit/d483e678e4b2558f74c3b79083cf2257d6eafe0c)),
closes
[#4708](https://github.com/AztecProtocol/aztec-packages/issues/4708)
aztec-cli: 0.27.0
##
[0.27.0](https://github.com/AztecProtocol/aztec-packages/compare/aztec-cli-v0.26.6...aztec-cli-v0.27.0)
(2024-03-12)
### Miscellaneous
* Remove old contract deployment flow
([#4970](https://github.com/AztecProtocol/aztec-packages/issues/4970))
([6d15947](https://github.com/AztecProtocol/aztec-packages/commit/6d1594736e96cd744ea691a239fcd3a46bdade60))
aztec-packages: 0.27.0
##
[0.27.0](https://github.com/AztecProtocol/aztec-packages/compare/aztec-packages-v0.26.6...aztec-packages-v0.27.0)
(2024-03-12)
### ⚠ BREAKING CHANGES
* Remove open keyword from Noir
([#4967](https://github.com/AztecProtocol/aztec-packages/issues/4967))
### Features
* Add api for inclusion proof of outgoing message in block
[#4562](https://github.com/AztecProtocol/aztec-packages/issues/4562)
([#4899](https://github.com/AztecProtocol/aztec-packages/issues/4899))
([26d2643](https://github.com/AztecProtocol/aztec-packages/commit/26d26437022567e2d54052f21b1c937259f26c94))
* **avm-simulator:** External calls + integration
([#5051](https://github.com/AztecProtocol/aztec-packages/issues/5051))
([dde33f4](https://github.com/AztecProtocol/aztec-packages/commit/dde33f498b0432e5c4adce84191d3517176077dd))
* **avm-simulator:** External static calls + integration
([#5089](https://github.com/AztecProtocol/aztec-packages/issues/5089))
([428d950](https://github.com/AztecProtocol/aztec-packages/commit/428d950ec1f2dc7b129b61380d7d1426a7b7441d))
* **avm:** Equivalence check between Main trace and Mem trace
([#5032](https://github.com/AztecProtocol/aztec-packages/issues/5032))
([7f216eb](https://github.com/AztecProtocol/aztec-packages/commit/7f216eb064fc95791de1286c7695e89575e02b40)),
closes
[#4955](https://github.com/AztecProtocol/aztec-packages/issues/4955)
* **avm:** Fix some Brillig problems
([#5091](https://github.com/AztecProtocol/aztec-packages/issues/5091))
([07dd821](https://github.com/AztecProtocol/aztec-packages/commit/07dd8215dffd2c3c6d22e0f430f5072b4ff7c763))
* Initial integration avm prover
([#4878](https://github.com/AztecProtocol/aztec-packages/issues/4878))
([2e2554e](https://github.com/AztecProtocol/aztec-packages/commit/2e2554e6a055ff7124e18d1566371d5d108c5d5d))
* Noir pull action
([#5062](https://github.com/AztecProtocol/aztec-packages/issues/5062))
([b2d7d14](https://github.com/AztecProtocol/aztec-packages/commit/b2d7d14996722c50c769dfcd9f7b0c324b2e3a7e))
* Restore contract inclusion proofs
([#5141](https://github.com/AztecProtocol/aztec-packages/issues/5141))
([a39cd61](https://github.com/AztecProtocol/aztec-packages/commit/a39cd6192022cd14b824d159b4262c10669b7de3))
* Update the core of SMT Circuit class
([#5096](https://github.com/AztecProtocol/aztec-packages/issues/5096))
([1519d3b](https://github.com/AztecProtocol/aztec-packages/commit/1519d3b07664f471a43d3f6bbb3dbe2d387289fc))
* Updating archiver with new inbox
([#5025](https://github.com/AztecProtocol/aztec-packages/issues/5025))
([f6d17c9](https://github.com/AztecProtocol/aztec-packages/commit/f6d17c972d2cf9c5aa468c8cf954431b42240f87)),
closes
[#4828](https://github.com/AztecProtocol/aztec-packages/issues/4828)
### Bug Fixes
* Duplicate factory code temporarily to unblock
([#5099](https://github.com/AztecProtocol/aztec-packages/issues/5099))
([8b10600](https://github.com/AztecProtocol/aztec-packages/commit/8b1060013e35a3b4e73d75b18bb2a8c16985e662))
* Remove hard coded canonical gas address
([#5106](https://github.com/AztecProtocol/aztec-packages/issues/5106))
([dc2fd9e](https://github.com/AztecProtocol/aztec-packages/commit/dc2fd9e584d987bdc5d2d7a117b76cb50a20b969))
### Miscellaneous
* **avm-simulator:** Enable compressed strings unencrypted log test
([#5083](https://github.com/AztecProtocol/aztec-packages/issues/5083))
([8f7519b](https://github.com/AztecProtocol/aztec-packages/commit/8f7519bdacd3c8b3a91d4361e4648688ec5d47bc))
* **avm-simulator:** Formatting and fixes
([#5092](https://github.com/AztecProtocol/aztec-packages/issues/5092))
([b3fa084](https://github.com/AztecProtocol/aztec-packages/commit/b3fa08469658bd7220863e514d8e4b069d40a00f))
* **AVM:** Negative unit tests for inter table relations
([#5143](https://github.com/AztecProtocol/aztec-packages/issues/5143))
([a74dccb](https://github.com/AztecProtocol/aztec-packages/commit/a74dccbdef0939b77978ddec3875b1afc2d0b530)),
closes
[#5033](https://github.com/AztecProtocol/aztec-packages/issues/5033)
* Aztec-macros refactor
([#5127](https://github.com/AztecProtocol/aztec-packages/issues/5127))
([2195441](https://github.com/AztecProtocol/aztec-packages/commit/2195441afde4d6e78ad0c6027d0a7dbc8671817d))
* **ci:** Fail on clippy warnings in noir
([#5101](https://github.com/AztecProtocol/aztec-packages/issues/5101))
([54af648](https://github.com/AztecProtocol/aztec-packages/commit/54af648b5928b200cd40c8d90a21c155bc2e43bd))
* Extract bb binary in bs fast
([#5128](https://github.com/AztecProtocol/aztec-packages/issues/5128))
([9ca41ef](https://github.com/AztecProtocol/aztec-packages/commit/9ca41ef6951566622ab9e68924958dbb66b160df))
* Increase bytecode size limit
([#5098](https://github.com/AztecProtocol/aztec-packages/issues/5098))
([53b2381](https://github.com/AztecProtocol/aztec-packages/commit/53b238190a9d123c292c3079bb23ed2ecff824c8))
* Increase permitted bytecode size
([#5136](https://github.com/AztecProtocol/aztec-packages/issues/5136))
([6865c34](https://github.com/AztecProtocol/aztec-packages/commit/6865c34fccfd74f83525c8d47b5c516d1696c432))
* Join-split example Part 2
([#5016](https://github.com/AztecProtocol/aztec-packages/issues/5016))
([0718320](https://github.com/AztecProtocol/aztec-packages/commit/07183200b136ec39087c2b35e5799686319d561b))
* Move alpine containers to ubuntu
([#5026](https://github.com/AztecProtocol/aztec-packages/issues/5026))
([d483e67](https://github.com/AztecProtocol/aztec-packages/commit/d483e678e4b2558f74c3b79083cf2257d6eafe0c)),
closes
[#4708](https://github.com/AztecProtocol/aztec-packages/issues/4708)
* Nicer snapshots
([#5133](https://github.com/AztecProtocol/aztec-packages/issues/5133))
([9a737eb](https://github.com/AztecProtocol/aztec-packages/commit/9a737eb9674a757ca3ac9c7a6607ed0f39304d52))
* Pin foundry
([#5151](https://github.com/AztecProtocol/aztec-packages/issues/5151))
([69bd7dd](https://github.com/AztecProtocol/aztec-packages/commit/69bd7dd45af6b197b23c25dc883a1a5485955203))
* Remove old contract deployment flow
([#4970](https://github.com/AztecProtocol/aztec-packages/issues/4970))
([6d15947](https://github.com/AztecProtocol/aztec-packages/commit/6d1594736e96cd744ea691a239fcd3a46bdade60))
* Remove open keyword from Noir
([#4967](https://github.com/AztecProtocol/aztec-packages/issues/4967))
([401557e](https://github.com/AztecProtocol/aztec-packages/commit/401557e1119c1dc4968c16f51381f3306ed8e876))
* Run nargo fmt on each nargo project
([#5102](https://github.com/AztecProtocol/aztec-packages/issues/5102))
([b327254](https://github.com/AztecProtocol/aztec-packages/commit/b32725421171f39d510619c8f78a39c182738725))
* Use context interface in mark-as-initialized
([#5142](https://github.com/AztecProtocol/aztec-packages/issues/5142))
([932c1d5](https://github.com/AztecProtocol/aztec-packages/commit/932c1d5006ad793ee05ed7cdbae05d59c04334d8))
barretenberg: 0.27.0
##
[0.27.0](https://github.com/AztecProtocol/aztec-packages/compare/barretenberg-v0.26.6...barretenberg-v0.27.0)
(2024-03-12)
### Features
* **avm:** Equivalence check between Main trace and Mem trace
([#5032](https://github.com/AztecProtocol/aztec-packages/issues/5032))
([7f216eb](https://github.com/AztecProtocol/aztec-packages/commit/7f216eb064fc95791de1286c7695e89575e02b40)),
closes
[#4955](https://github.com/AztecProtocol/aztec-packages/issues/4955)
* Initial integration avm prover
([#4878](https://github.com/AztecProtocol/aztec-packages/issues/4878))
([2e2554e](https://github.com/AztecProtocol/aztec-packages/commit/2e2554e6a055ff7124e18d1566371d5d108c5d5d))
* Update the core of SMT Circuit class
([#5096](https://github.com/AztecProtocol/aztec-packages/issues/5096))
([1519d3b](https://github.com/AztecProtocol/aztec-packages/commit/1519d3b07664f471a43d3f6bbb3dbe2d387289fc))
### Miscellaneous
* **AVM:** Negative unit tests for inter table relations
([#5143](https://github.com/AztecProtocol/aztec-packages/issues/5143))
([a74dccb](https://github.com/AztecProtocol/aztec-packages/commit/a74dccbdef0939b77978ddec3875b1afc2d0b530)),
closes
[#5033](https://github.com/AztecProtocol/aztec-packages/issues/5033)
* Extract bb binary in bs fast
([#5128](https://github.com/AztecProtocol/aztec-packages/issues/5128))
([9ca41ef](https://github.com/AztecProtocol/aztec-packages/commit/9ca41ef6951566622ab9e68924958dbb66b160df))
* Join-split example Part 2
([#5016](https://github.com/AztecProtocol/aztec-packages/issues/5016))
([0718320](https://github.com/AztecProtocol/aztec-packages/commit/07183200b136ec39087c2b35e5799686319d561b))
* Move alpine containers to ubuntu
([#5026](https://github.com/AztecProtocol/aztec-packages/issues/5026))
([d483e67](https://github.com/AztecProtocol/aztec-packages/commit/d483e678e4b2558f74c3b79083cf2257d6eafe0c)),
closes
[#4708](https://github.com/AztecProtocol/aztec-packages/issues/4708)
* Pin foundry
([#5151](https://github.com/AztecProtocol/aztec-packages/issues/5151))
([69bd7dd](https://github.com/AztecProtocol/aztec-packages/commit/69bd7dd45af6b197b23c25dc883a1a5485955203))
---
This PR was generated with [Release
Please](https://github.com/googleapis/release-please). See
[documentation](https://github.com/googleapis/release-please#release-please).
---
.release-please-manifest.json | 10 +++----
CHANGELOG.md | 46 +++++++++++++++++++++++++++++++++
barretenberg/CHANGELOG.md | 18 +++++++++++++
barretenberg/cpp/CMakeLists.txt | 2 +-
barretenberg/ts/CHANGELOG.md | 7 +++++
barretenberg/ts/package.json | 2 +-
yarn-project/aztec/CHANGELOG.md | 13 ++++++++++
yarn-project/aztec/package.json | 2 +-
yarn-project/cli/CHANGELOG.md | 7 +++++
yarn-project/cli/package.json | 2 +-
10 files changed, 100 insertions(+), 9 deletions(-)
diff --git a/.release-please-manifest.json b/.release-please-manifest.json
index be5c3462ef5..730ce3899bb 100644
--- a/.release-please-manifest.json
+++ b/.release-please-manifest.json
@@ -1,7 +1,7 @@
{
- ".": "0.26.6",
- "yarn-project/cli": "0.26.6",
- "yarn-project/aztec": "0.26.6",
- "barretenberg": "0.26.6",
- "barretenberg/ts": "0.26.6"
+ ".": "0.27.0",
+ "yarn-project/cli": "0.27.0",
+ "yarn-project/aztec": "0.27.0",
+ "barretenberg": "0.27.0",
+ "barretenberg/ts": "0.27.0"
}
diff --git a/CHANGELOG.md b/CHANGELOG.md
index e8f45aaea9e..e3b71031ea3 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -1,5 +1,51 @@
# Changelog
+## [0.27.0](https://github.com/AztecProtocol/aztec-packages/compare/aztec-packages-v0.26.6...aztec-packages-v0.27.0) (2024-03-12)
+
+
+### ⚠ BREAKING CHANGES
+
+* Remove open keyword from Noir ([#4967](https://github.com/AztecProtocol/aztec-packages/issues/4967))
+
+### Features
+
+* Add api for inclusion proof of outgoing message in block [#4562](https://github.com/AztecProtocol/aztec-packages/issues/4562) ([#4899](https://github.com/AztecProtocol/aztec-packages/issues/4899)) ([26d2643](https://github.com/AztecProtocol/aztec-packages/commit/26d26437022567e2d54052f21b1c937259f26c94))
+* **avm-simulator:** External calls + integration ([#5051](https://github.com/AztecProtocol/aztec-packages/issues/5051)) ([dde33f4](https://github.com/AztecProtocol/aztec-packages/commit/dde33f498b0432e5c4adce84191d3517176077dd))
+* **avm-simulator:** External static calls + integration ([#5089](https://github.com/AztecProtocol/aztec-packages/issues/5089)) ([428d950](https://github.com/AztecProtocol/aztec-packages/commit/428d950ec1f2dc7b129b61380d7d1426a7b7441d))
+* **avm:** Equivalence check between Main trace and Mem trace ([#5032](https://github.com/AztecProtocol/aztec-packages/issues/5032)) ([7f216eb](https://github.com/AztecProtocol/aztec-packages/commit/7f216eb064fc95791de1286c7695e89575e02b40)), closes [#4955](https://github.com/AztecProtocol/aztec-packages/issues/4955)
+* **avm:** Fix some Brillig problems ([#5091](https://github.com/AztecProtocol/aztec-packages/issues/5091)) ([07dd821](https://github.com/AztecProtocol/aztec-packages/commit/07dd8215dffd2c3c6d22e0f430f5072b4ff7c763))
+* Initial integration avm prover ([#4878](https://github.com/AztecProtocol/aztec-packages/issues/4878)) ([2e2554e](https://github.com/AztecProtocol/aztec-packages/commit/2e2554e6a055ff7124e18d1566371d5d108c5d5d))
+* Noir pull action ([#5062](https://github.com/AztecProtocol/aztec-packages/issues/5062)) ([b2d7d14](https://github.com/AztecProtocol/aztec-packages/commit/b2d7d14996722c50c769dfcd9f7b0c324b2e3a7e))
+* Restore contract inclusion proofs ([#5141](https://github.com/AztecProtocol/aztec-packages/issues/5141)) ([a39cd61](https://github.com/AztecProtocol/aztec-packages/commit/a39cd6192022cd14b824d159b4262c10669b7de3))
+* Update the core of SMT Circuit class ([#5096](https://github.com/AztecProtocol/aztec-packages/issues/5096)) ([1519d3b](https://github.com/AztecProtocol/aztec-packages/commit/1519d3b07664f471a43d3f6bbb3dbe2d387289fc))
+* Updating archiver with new inbox ([#5025](https://github.com/AztecProtocol/aztec-packages/issues/5025)) ([f6d17c9](https://github.com/AztecProtocol/aztec-packages/commit/f6d17c972d2cf9c5aa468c8cf954431b42240f87)), closes [#4828](https://github.com/AztecProtocol/aztec-packages/issues/4828)
+
+
+### Bug Fixes
+
+* Duplicate factory code temporarily to unblock ([#5099](https://github.com/AztecProtocol/aztec-packages/issues/5099)) ([8b10600](https://github.com/AztecProtocol/aztec-packages/commit/8b1060013e35a3b4e73d75b18bb2a8c16985e662))
+* Remove hard coded canonical gas address ([#5106](https://github.com/AztecProtocol/aztec-packages/issues/5106)) ([dc2fd9e](https://github.com/AztecProtocol/aztec-packages/commit/dc2fd9e584d987bdc5d2d7a117b76cb50a20b969))
+
+
+### Miscellaneous
+
+* **avm-simulator:** Enable compressed strings unencrypted log test ([#5083](https://github.com/AztecProtocol/aztec-packages/issues/5083)) ([8f7519b](https://github.com/AztecProtocol/aztec-packages/commit/8f7519bdacd3c8b3a91d4361e4648688ec5d47bc))
+* **avm-simulator:** Formatting and fixes ([#5092](https://github.com/AztecProtocol/aztec-packages/issues/5092)) ([b3fa084](https://github.com/AztecProtocol/aztec-packages/commit/b3fa08469658bd7220863e514d8e4b069d40a00f))
+* **AVM:** Negative unit tests for inter table relations ([#5143](https://github.com/AztecProtocol/aztec-packages/issues/5143)) ([a74dccb](https://github.com/AztecProtocol/aztec-packages/commit/a74dccbdef0939b77978ddec3875b1afc2d0b530)), closes [#5033](https://github.com/AztecProtocol/aztec-packages/issues/5033)
+* Aztec-macros refactor ([#5127](https://github.com/AztecProtocol/aztec-packages/issues/5127)) ([2195441](https://github.com/AztecProtocol/aztec-packages/commit/2195441afde4d6e78ad0c6027d0a7dbc8671817d))
+* **ci:** Fail on clippy warnings in noir ([#5101](https://github.com/AztecProtocol/aztec-packages/issues/5101)) ([54af648](https://github.com/AztecProtocol/aztec-packages/commit/54af648b5928b200cd40c8d90a21c155bc2e43bd))
+* Extract bb binary in bs fast ([#5128](https://github.com/AztecProtocol/aztec-packages/issues/5128)) ([9ca41ef](https://github.com/AztecProtocol/aztec-packages/commit/9ca41ef6951566622ab9e68924958dbb66b160df))
+* Increase bytecode size limit ([#5098](https://github.com/AztecProtocol/aztec-packages/issues/5098)) ([53b2381](https://github.com/AztecProtocol/aztec-packages/commit/53b238190a9d123c292c3079bb23ed2ecff824c8))
+* Increase permitted bytecode size ([#5136](https://github.com/AztecProtocol/aztec-packages/issues/5136)) ([6865c34](https://github.com/AztecProtocol/aztec-packages/commit/6865c34fccfd74f83525c8d47b5c516d1696c432))
+* Join-split example Part 2 ([#5016](https://github.com/AztecProtocol/aztec-packages/issues/5016)) ([0718320](https://github.com/AztecProtocol/aztec-packages/commit/07183200b136ec39087c2b35e5799686319d561b))
+* Move alpine containers to ubuntu ([#5026](https://github.com/AztecProtocol/aztec-packages/issues/5026)) ([d483e67](https://github.com/AztecProtocol/aztec-packages/commit/d483e678e4b2558f74c3b79083cf2257d6eafe0c)), closes [#4708](https://github.com/AztecProtocol/aztec-packages/issues/4708)
+* Nicer snapshots ([#5133](https://github.com/AztecProtocol/aztec-packages/issues/5133)) ([9a737eb](https://github.com/AztecProtocol/aztec-packages/commit/9a737eb9674a757ca3ac9c7a6607ed0f39304d52))
+* Pin foundry ([#5151](https://github.com/AztecProtocol/aztec-packages/issues/5151)) ([69bd7dd](https://github.com/AztecProtocol/aztec-packages/commit/69bd7dd45af6b197b23c25dc883a1a5485955203))
+* Remove old contract deployment flow ([#4970](https://github.com/AztecProtocol/aztec-packages/issues/4970)) ([6d15947](https://github.com/AztecProtocol/aztec-packages/commit/6d1594736e96cd744ea691a239fcd3a46bdade60))
+* Remove open keyword from Noir ([#4967](https://github.com/AztecProtocol/aztec-packages/issues/4967)) ([401557e](https://github.com/AztecProtocol/aztec-packages/commit/401557e1119c1dc4968c16f51381f3306ed8e876))
+* Run nargo fmt on each nargo project ([#5102](https://github.com/AztecProtocol/aztec-packages/issues/5102)) ([b327254](https://github.com/AztecProtocol/aztec-packages/commit/b32725421171f39d510619c8f78a39c182738725))
+* Use context interface in mark-as-initialized ([#5142](https://github.com/AztecProtocol/aztec-packages/issues/5142)) ([932c1d5](https://github.com/AztecProtocol/aztec-packages/commit/932c1d5006ad793ee05ed7cdbae05d59c04334d8))
+
## [0.26.6](https://github.com/AztecProtocol/aztec-packages/compare/aztec-packages-v0.26.5...aztec-packages-v0.26.6) (2024-03-08)
diff --git a/barretenberg/CHANGELOG.md b/barretenberg/CHANGELOG.md
index d6f4bfe1fe5..b473c000d3d 100644
--- a/barretenberg/CHANGELOG.md
+++ b/barretenberg/CHANGELOG.md
@@ -1,5 +1,23 @@
# Changelog
+## [0.27.0](https://github.com/AztecProtocol/aztec-packages/compare/barretenberg-v0.26.6...barretenberg-v0.27.0) (2024-03-12)
+
+
+### Features
+
+* **avm:** Equivalence check between Main trace and Mem trace ([#5032](https://github.com/AztecProtocol/aztec-packages/issues/5032)) ([7f216eb](https://github.com/AztecProtocol/aztec-packages/commit/7f216eb064fc95791de1286c7695e89575e02b40)), closes [#4955](https://github.com/AztecProtocol/aztec-packages/issues/4955)
+* Initial integration avm prover ([#4878](https://github.com/AztecProtocol/aztec-packages/issues/4878)) ([2e2554e](https://github.com/AztecProtocol/aztec-packages/commit/2e2554e6a055ff7124e18d1566371d5d108c5d5d))
+* Update the core of SMT Circuit class ([#5096](https://github.com/AztecProtocol/aztec-packages/issues/5096)) ([1519d3b](https://github.com/AztecProtocol/aztec-packages/commit/1519d3b07664f471a43d3f6bbb3dbe2d387289fc))
+
+
+### Miscellaneous
+
+* **AVM:** Negative unit tests for inter table relations ([#5143](https://github.com/AztecProtocol/aztec-packages/issues/5143)) ([a74dccb](https://github.com/AztecProtocol/aztec-packages/commit/a74dccbdef0939b77978ddec3875b1afc2d0b530)), closes [#5033](https://github.com/AztecProtocol/aztec-packages/issues/5033)
+* Extract bb binary in bs fast ([#5128](https://github.com/AztecProtocol/aztec-packages/issues/5128)) ([9ca41ef](https://github.com/AztecProtocol/aztec-packages/commit/9ca41ef6951566622ab9e68924958dbb66b160df))
+* Join-split example Part 2 ([#5016](https://github.com/AztecProtocol/aztec-packages/issues/5016)) ([0718320](https://github.com/AztecProtocol/aztec-packages/commit/07183200b136ec39087c2b35e5799686319d561b))
+* Move alpine containers to ubuntu ([#5026](https://github.com/AztecProtocol/aztec-packages/issues/5026)) ([d483e67](https://github.com/AztecProtocol/aztec-packages/commit/d483e678e4b2558f74c3b79083cf2257d6eafe0c)), closes [#4708](https://github.com/AztecProtocol/aztec-packages/issues/4708)
+* Pin foundry ([#5151](https://github.com/AztecProtocol/aztec-packages/issues/5151)) ([69bd7dd](https://github.com/AztecProtocol/aztec-packages/commit/69bd7dd45af6b197b23c25dc883a1a5485955203))
+
## [0.26.6](https://github.com/AztecProtocol/aztec-packages/compare/barretenberg-v0.26.5...barretenberg-v0.26.6) (2024-03-08)
diff --git a/barretenberg/cpp/CMakeLists.txt b/barretenberg/cpp/CMakeLists.txt
index 8a7cd0012d8..15a4003fb68 100644
--- a/barretenberg/cpp/CMakeLists.txt
+++ b/barretenberg/cpp/CMakeLists.txt
@@ -6,7 +6,7 @@ cmake_minimum_required(VERSION 3.24 FATAL_ERROR)
project(
Barretenberg
DESCRIPTION "BN254 elliptic curve library, and PLONK SNARK prover"
- VERSION 0.26.6 # x-release-please-version
+ VERSION 0.27.0 # x-release-please-version
LANGUAGES CXX C
)
# Insert version into `bb` config file
diff --git a/barretenberg/ts/CHANGELOG.md b/barretenberg/ts/CHANGELOG.md
index 71c48c7cd02..05ca4120681 100644
--- a/barretenberg/ts/CHANGELOG.md
+++ b/barretenberg/ts/CHANGELOG.md
@@ -1,5 +1,12 @@
# Changelog
+## [0.27.0](https://github.com/AztecProtocol/aztec-packages/compare/barretenberg.js-v0.26.6...barretenberg.js-v0.27.0) (2024-03-12)
+
+
+### Miscellaneous
+
+* Move alpine containers to ubuntu ([#5026](https://github.com/AztecProtocol/aztec-packages/issues/5026)) ([d483e67](https://github.com/AztecProtocol/aztec-packages/commit/d483e678e4b2558f74c3b79083cf2257d6eafe0c)), closes [#4708](https://github.com/AztecProtocol/aztec-packages/issues/4708)
+
## [0.26.6](https://github.com/AztecProtocol/aztec-packages/compare/barretenberg.js-v0.26.5...barretenberg.js-v0.26.6) (2024-03-08)
diff --git a/barretenberg/ts/package.json b/barretenberg/ts/package.json
index 33295f00aff..57454a8df1d 100644
--- a/barretenberg/ts/package.json
+++ b/barretenberg/ts/package.json
@@ -1,6 +1,6 @@
{
"name": "@aztec/bb.js",
- "version": "0.26.6",
+ "version": "0.27.0",
"homepage": "https://github.com/AztecProtocol/aztec-packages/tree/master/barretenberg/ts",
"license": "MIT",
"type": "module",
diff --git a/yarn-project/aztec/CHANGELOG.md b/yarn-project/aztec/CHANGELOG.md
index 9f62a9ce34f..b1309ecb8f8 100644
--- a/yarn-project/aztec/CHANGELOG.md
+++ b/yarn-project/aztec/CHANGELOG.md
@@ -1,5 +1,18 @@
# Changelog
+## [0.27.0](https://github.com/AztecProtocol/aztec-packages/compare/aztec-package-v0.26.6...aztec-package-v0.27.0) (2024-03-12)
+
+
+### Features
+
+* Add api for inclusion proof of outgoing message in block [#4562](https://github.com/AztecProtocol/aztec-packages/issues/4562) ([#4899](https://github.com/AztecProtocol/aztec-packages/issues/4899)) ([26d2643](https://github.com/AztecProtocol/aztec-packages/commit/26d26437022567e2d54052f21b1c937259f26c94))
+
+
+### Miscellaneous
+
+* Pin foundry ([#5151](https://github.com/AztecProtocol/aztec-packages/issues/5151)) ([69bd7dd](https://github.com/AztecProtocol/aztec-packages/commit/69bd7dd45af6b197b23c25dc883a1a5485955203))
+* Remove old contract deployment flow ([#4970](https://github.com/AztecProtocol/aztec-packages/issues/4970)) ([6d15947](https://github.com/AztecProtocol/aztec-packages/commit/6d1594736e96cd744ea691a239fcd3a46bdade60))
+
## [0.26.6](https://github.com/AztecProtocol/aztec-packages/compare/aztec-package-v0.26.5...aztec-package-v0.26.6) (2024-03-08)
diff --git a/yarn-project/aztec/package.json b/yarn-project/aztec/package.json
index bc5e0f419b4..9ecb167db71 100644
--- a/yarn-project/aztec/package.json
+++ b/yarn-project/aztec/package.json
@@ -1,6 +1,6 @@
{
"name": "@aztec/aztec",
- "version": "0.26.6",
+ "version": "0.27.0",
"type": "module",
"exports": {
".": "./dest/index.js"
diff --git a/yarn-project/cli/CHANGELOG.md b/yarn-project/cli/CHANGELOG.md
index 21a4fae14ff..3a14034116f 100644
--- a/yarn-project/cli/CHANGELOG.md
+++ b/yarn-project/cli/CHANGELOG.md
@@ -1,5 +1,12 @@
# Changelog
+## [0.27.0](https://github.com/AztecProtocol/aztec-packages/compare/aztec-cli-v0.26.6...aztec-cli-v0.27.0) (2024-03-12)
+
+
+### Miscellaneous
+
+* Remove old contract deployment flow ([#4970](https://github.com/AztecProtocol/aztec-packages/issues/4970)) ([6d15947](https://github.com/AztecProtocol/aztec-packages/commit/6d1594736e96cd744ea691a239fcd3a46bdade60))
+
## [0.26.6](https://github.com/AztecProtocol/aztec-packages/compare/aztec-cli-v0.26.5...aztec-cli-v0.26.6) (2024-03-08)
diff --git a/yarn-project/cli/package.json b/yarn-project/cli/package.json
index f786232ddb8..2e2f46b5124 100644
--- a/yarn-project/cli/package.json
+++ b/yarn-project/cli/package.json
@@ -1,6 +1,6 @@
{
"name": "@aztec/cli",
- "version": "0.26.6",
+ "version": "0.27.0",
"type": "module",
"main": "./dest/index.js",
"bin": {
From 5457edb3ddd29df96906f98fb05469a26a644654 Mon Sep 17 00:00:00 2001
From: Cody Gunton
Date: Tue, 12 Mar 2024 10:35:58 -0400
Subject: [PATCH 2/5] fix: Move timers for ClientIVC breakdown (#5145)
https://github.com/AztecProtocol/aztec-packages/pull/4841 improved the
witness construction in the client IVC benchmarks to make use of
straightforward parallelism. This PR updates the manual % breakdown
script to reflect the new structure. It also fixes benchmark.sh so that
it can be used inside of benchmark_client_ivc.sh in the case where the
benchmarking instance is unavailable.
Result:
```
--------------------------------------------------------------------------------
Benchmark Time CPU Iterations UserCounters...
--------------------------------------------------------------------------------
ClientIVCBench/Full/6 29212 ms 24574 ms 1 Decider::construct_proof=1 Decider::construct_proof(t)=755.662M ECCVMComposer::create_prover=1 ECCVMComposer::create_prover(t)=3.41214G ECCVMProver::construct_proof=1 ECCVMProver::construct_proof(t)=1.77115G Goblin::merge=11 Goblin::merge(t)=128.511M GoblinTranslatorCircuitBuilder::constructor=1 GoblinTranslatorCircuitBuilder::constructor(t)=56.2447M GoblinTranslatorComposer::create_prover=1 GoblinTranslatorComposer::create_prover(t)=121.099M GoblinTranslatorProver::construct_proof=1 GoblinTranslatorProver::construct_proof(t)=920.195M ProtogalaxyProver::fold_instances=10 ProtogalaxyProver::fold_instances(t)=15.8816G ProverInstance(Circuit&)=11 ProverInstance(Circuit&)(t)=1.94818G batch_mul_with_endomorphism=30 batch_mul_with_endomorphism(t)=564.471M commit=425 commit(t)=3.96399G compute_combiner=10 compute_combiner(t)=7.19205G compute_perturbator=9 compute_perturbator(t)=1.3842G construct_circuits=6 construct_circuits(t)=4.19883G
Benchmarking lock deleted.
client_ivc_bench.json 100% 2904 89.0KB/s 00:00
function ms % sum
construct_circuits(t) 4199 14.41%
ProverInstance(Circuit&)(t) 1948 6.69%
ProtogalaxyProver::fold_instances(t) 15882 54.51%
Decider::construct_proof(t) 756 2.59%
ECCVMComposer::create_prover(t) 3412 11.71%
GoblinTranslatorComposer::create_prover(t) 121 0.42%
ECCVMProver::construct_proof(t) 1771 6.08%
GoblinTranslatorProver::construct_proof(t) 920 3.16%
Goblin::merge(t) 129 0.44%
Total time accounted for: 29137ms/29212ms = 99.74%
```
---
.../cpp/scripts/analyze_client_ivc_bench.py | 9 ++-
barretenberg/cpp/scripts/benchmark.sh | 11 ++--
.../cpp/scripts/benchmark_client_ivc.sh | 2 +-
.../client_ivc_bench/client_ivc.bench.cpp | 61 +++++++++++--------
.../src/barretenberg/eccvm/eccvm_composer.cpp | 4 ++
.../src/barretenberg/eccvm/eccvm_composer.hpp | 1 +
.../src/barretenberg/goblin/mock_circuits.hpp | 2 -
7 files changed, 57 insertions(+), 33 deletions(-)
diff --git a/barretenberg/cpp/scripts/analyze_client_ivc_bench.py b/barretenberg/cpp/scripts/analyze_client_ivc_bench.py
index d9b5e047b1f..0e95053b443 100644
--- a/barretenberg/cpp/scripts/analyze_client_ivc_bench.py
+++ b/barretenberg/cpp/scripts/analyze_client_ivc_bench.py
@@ -7,8 +7,7 @@
# Single out an independent set of functions accounting for most of BENCHMARK's real_time
to_keep = [
- "construct_mock_function_circuit(t)",
- "construct_mock_folding_kernel(t)",
+ "construct_circuits(t)",
"ProverInstance(Circuit&)(t)",
"ProtogalaxyProver::fold_instances(t)",
"Decider::construct_proof(t)",
@@ -42,3 +41,9 @@
totals = totals.format(
sum_of_kept_times_ms, total_time_ms, sum_of_kept_times_ms/total_time_ms)
print(totals)
+
+print('\nBreakdown of ECCVMProver::create_prover:')
+for key in ["ECCVMComposer::compute_witness(t)", "ECCVMComposer::create_proving_key(t)"]:
+ time_ms = bench[key]/1e6
+ total_time_ms = bench["ECCVMComposer::create_prover(t)"]/1e6
+ print(f"{key:<{MAX_LABEL_LENGTH}}{time_ms:>8.0f} {time_ms/total_time_ms:>8.2%}")
diff --git a/barretenberg/cpp/scripts/benchmark.sh b/barretenberg/cpp/scripts/benchmark.sh
index 93b96377173..6b6758700f4 100755
--- a/barretenberg/cpp/scripts/benchmark.sh
+++ b/barretenberg/cpp/scripts/benchmark.sh
@@ -2,16 +2,19 @@
set -eu
BENCHMARK=${1:-goblin_bench}
-COMMAND=${2:-./bin/$BENCHMARK}
+COMMAND=${2:-./$BENCHMARK}
+PRESET=${3:-clang16}
+BUILD_DIR=${4:-build}
+
# Move above script dir.
cd $(dirname $0)/..
# Configure and build.
-cmake --preset clang16
-cmake --build --preset clang16 --target $BENCHMARK
+cmake --preset $PRESET
+cmake --build --preset $PRESET --target $BENCHMARK
-cd build
+cd $BUILD_DIR
# Consistency with _wasm.sh targets / shorter $COMMAND.
cp ./bin/$BENCHMARK .
$COMMAND
\ No newline at end of file
diff --git a/barretenberg/cpp/scripts/benchmark_client_ivc.sh b/barretenberg/cpp/scripts/benchmark_client_ivc.sh
index 08e43f012ca..17a193c6d82 100755
--- a/barretenberg/cpp/scripts/benchmark_client_ivc.sh
+++ b/barretenberg/cpp/scripts/benchmark_client_ivc.sh
@@ -21,5 +21,5 @@ cd $BUILD_DIR
scp $BB_SSH_KEY $BB_SSH_INSTANCE:$BB_SSH_CPP_PATH/build/$TARGET.json .
# Analyze the results
-cd $(dirname $0)/..
+cd ../
python3 ./scripts/analyze_client_ivc_bench.py
diff --git a/barretenberg/cpp/src/barretenberg/benchmark/client_ivc_bench/client_ivc.bench.cpp b/barretenberg/cpp/src/barretenberg/benchmark/client_ivc_bench/client_ivc.bench.cpp
index f65385da92e..2ace58eb6e6 100644
--- a/barretenberg/cpp/src/barretenberg/benchmark/client_ivc_bench/client_ivc.bench.cpp
+++ b/barretenberg/cpp/src/barretenberg/benchmark/client_ivc_bench/client_ivc.bench.cpp
@@ -2,6 +2,7 @@
#include
#include "barretenberg/client_ivc/client_ivc.hpp"
+#include "barretenberg/common/op_count.hpp"
#include "barretenberg/common/op_count_google_bench.hpp"
#include "barretenberg/goblin/mock_circuits.hpp"
#include "barretenberg/proof_system/circuit_builder/ultra_circuit_builder.hpp"
@@ -46,9 +47,12 @@ class ClientIVCBench : public benchmark::Fixture {
std::vector initial_function_circuits(2);
// Construct 2 starting function circuits in parallel
- parallel_for(2, [&](size_t circuit_index) {
- GoblinMockCircuits::construct_mock_function_circuit(initial_function_circuits[circuit_index]);
- });
+ {
+ BB_OP_COUNT_TIME_NAME("construct_circuits");
+ parallel_for(2, [&](size_t circuit_index) {
+ GoblinMockCircuits::construct_mock_function_circuit(initial_function_circuits[circuit_index]);
+ });
+ };
// Prepend queue to the first circuit
initial_function_circuits[0].op_queue->prepend_previous_queue(*ivc.goblin.op_queue);
@@ -81,25 +85,28 @@ class ClientIVCBench : public benchmark::Fixture {
Builder kernel_circuit{ size_hint, ivc.goblin.op_queue };
Builder function_circuit{ size_hint };
// Construct function and kernel circuits in parallel
- parallel_for(2, [&](size_t workload_idx) {
- // workload index is 0 for kernel and 1 for function
- if (workload_idx == 0) {
- if (circuit_idx == 0) {
-
- // Create the first folding kernel which only verifies the accumulation of a
- // function circuit
- kernel_verifier_accumulator = GoblinMockCircuits::construct_mock_folding_kernel(
- kernel_circuit, function_fold_output, {}, kernel_verifier_accumulator);
+ {
+ BB_OP_COUNT_TIME_NAME("construct_circuits");
+ parallel_for(2, [&](size_t workload_idx) {
+ // workload index is 0 for kernel and 1 for function
+ if (workload_idx == 0) {
+ if (circuit_idx == 0) {
+
+ // Create the first folding kernel which only verifies the accumulation of a
+ // function circuit
+ kernel_verifier_accumulator = GoblinMockCircuits::construct_mock_folding_kernel(
+ kernel_circuit, function_fold_output, {}, kernel_verifier_accumulator);
+ } else {
+ // Create kernel circuit containing the recursive folding verification of a function circuit
+ // and a kernel circuit
+ kernel_verifier_accumulator = GoblinMockCircuits::construct_mock_folding_kernel(
+ kernel_circuit, function_fold_output, kernel_fold_output, kernel_verifier_accumulator);
+ }
} else {
- // Create kernel circuit containing the recursive folding verification of a function circuit and
- // a kernel circuit
- kernel_verifier_accumulator = GoblinMockCircuits::construct_mock_folding_kernel(
- kernel_circuit, function_fold_output, kernel_fold_output, kernel_verifier_accumulator);
+ GoblinMockCircuits::construct_mock_function_circuit(function_circuit);
}
- } else {
- GoblinMockCircuits::construct_mock_function_circuit(function_circuit);
- }
- });
+ });
+ };
// No need to prepend queue, it's the same after last swap
// Accumulate kernel circuit
@@ -127,14 +134,20 @@ class ClientIVCBench : public benchmark::Fixture {
// Create and accumulate the first folding kernel which only verifies the accumulation of a function circuit
Builder kernel_circuit{ size_hint, ivc.goblin.op_queue };
auto kernel_verifier_accumulator = std::make_shared(ivc.vks.first_func_vk);
- kernel_verifier_accumulator = GoblinMockCircuits::construct_mock_folding_kernel(
- kernel_circuit, function_fold_output, {}, kernel_verifier_accumulator);
+ {
+ BB_OP_COUNT_TIME_NAME("construct_circuits");
+ kernel_verifier_accumulator = GoblinMockCircuits::construct_mock_folding_kernel(
+ kernel_circuit, function_fold_output, {}, kernel_verifier_accumulator);
+ }
auto kernel_fold_proof = ivc.accumulate(kernel_circuit);
kernel_fold_output = { kernel_fold_proof, ivc.vks.first_kernel_vk };
} else {
Builder kernel_circuit{ size_hint, ivc.goblin.op_queue };
- kernel_verifier_accumulator = GoblinMockCircuits::construct_mock_folding_kernel(
- kernel_circuit, function_fold_output, kernel_fold_output, kernel_verifier_accumulator);
+ {
+ BB_OP_COUNT_TIME_NAME("construct_circuits");
+ kernel_verifier_accumulator = GoblinMockCircuits::construct_mock_folding_kernel(
+ kernel_circuit, function_fold_output, kernel_fold_output, kernel_verifier_accumulator);
+ }
auto kernel_fold_proof = ivc.accumulate(kernel_circuit);
kernel_fold_output = { kernel_fold_proof, ivc.vks.kernel_vk };
diff --git a/barretenberg/cpp/src/barretenberg/eccvm/eccvm_composer.cpp b/barretenberg/cpp/src/barretenberg/eccvm/eccvm_composer.cpp
index 3f8d20b019f..bf5b4c7316c 100644
--- a/barretenberg/cpp/src/barretenberg/eccvm/eccvm_composer.cpp
+++ b/barretenberg/cpp/src/barretenberg/eccvm/eccvm_composer.cpp
@@ -10,6 +10,8 @@ namespace bb {
*/
template void ECCVMComposer_::compute_witness(CircuitConstructor& circuit_constructor)
{
+ BB_OP_COUNT_TIME_NAME("ECCVMComposer::compute_witness");
+
if (computed_witness) {
return;
}
@@ -67,6 +69,8 @@ template
std::shared_ptr ECCVMComposer_::compute_proving_key(
CircuitConstructor& circuit_constructor)
{
+ BB_OP_COUNT_TIME_NAME("ECCVMComposer::create_proving_key");
+
if (proving_key) {
return proving_key;
}
diff --git a/barretenberg/cpp/src/barretenberg/eccvm/eccvm_composer.hpp b/barretenberg/cpp/src/barretenberg/eccvm/eccvm_composer.hpp
index 03630f5b239..a33446e6caa 100644
--- a/barretenberg/cpp/src/barretenberg/eccvm/eccvm_composer.hpp
+++ b/barretenberg/cpp/src/barretenberg/eccvm/eccvm_composer.hpp
@@ -70,6 +70,7 @@ template class ECCVMComposer_ {
void compute_commitment_key(size_t circuit_size)
{
+ BB_OP_COUNT_TIME_NAME("ECCVMComposer::compute_commitment_key");
commitment_key = std::make_shared(circuit_size);
};
};
diff --git a/barretenberg/cpp/src/barretenberg/goblin/mock_circuits.hpp b/barretenberg/cpp/src/barretenberg/goblin/mock_circuits.hpp
index 884e5c27ab8..58cc8fc2d2c 100644
--- a/barretenberg/cpp/src/barretenberg/goblin/mock_circuits.hpp
+++ b/barretenberg/cpp/src/barretenberg/goblin/mock_circuits.hpp
@@ -109,7 +109,6 @@ class GoblinMockCircuits {
*/
static void construct_mock_function_circuit(GoblinUltraBuilder& builder, bool large = false)
{
- BB_OP_COUNT_TIME();
// Determine number of times to execute the below operations that constitute the mock circuit logic. Note that
// the circuit size does not scale linearly with number of iterations due to e.g. amortization of lookup costs
const size_t NUM_ITERATIONS_LARGE = 13; // results in circuit size 2^19 (521327 gates)
@@ -233,7 +232,6 @@ class GoblinMockCircuits {
const VerifierFoldData& kernel,
std::shared_ptr& prev_kernel_accum)
{
- BB_OP_COUNT_TIME();
using GURecursiveFlavor = GoblinUltraRecursiveFlavor_;
using RecursiveVerifierInstances =
bb::stdlib::recursion::honk::RecursiveVerifierInstances_;
From 419958c7c9abc40a5d83739a74e1ea0797b4f474 Mon Sep 17 00:00:00 2001
From: PhilWindle <60546371+PhilWindle@users.noreply.github.com>
Date: Tue, 12 Mar 2024 16:10:34 +0000
Subject: [PATCH 3/5] fix: Increase the json limit for RPC requests (#5161)
This PR increases the json rpc payload size limit.
---
yarn-project/foundation/src/json-rpc/server/json_rpc_server.ts | 2 +-
1 file changed, 1 insertion(+), 1 deletion(-)
diff --git a/yarn-project/foundation/src/json-rpc/server/json_rpc_server.ts b/yarn-project/foundation/src/json-rpc/server/json_rpc_server.ts
index 08e0533aff4..bd2d537bc54 100644
--- a/yarn-project/foundation/src/json-rpc/server/json_rpc_server.ts
+++ b/yarn-project/foundation/src/json-rpc/server/json_rpc_server.ts
@@ -73,7 +73,7 @@ export class JsonRpcServer {
app.use(compress({ br: false } as any));
app.use(
bodyParser({
- jsonLimit: '10mb',
+ jsonLimit: '50mb',
enableTypes: ['json'],
detectJSON: () => true,
}),
From ef10d6576aa9e89eece5a40669c425ae7987ee8a Mon Sep 17 00:00:00 2001
From: Lucas Xia
Date: Tue, 12 Mar 2024 10:18:45 -0600
Subject: [PATCH 4/5] refactor: share code between provers (#4655)
A lot of code is repeated between the Decider prover, folding prover,
and ultra honk prover. This PR aims to reduce duplication by creating a
OinkProver, which supports the 5 round functions before sumcheck.
The OinkProver is used in the folding and ultra honk provers.
It does not address the shared code between verifiers or the shared code
between prover and verifier. It also is an initial step at a round
abstraction, where each round is implemented as a separate class and the
data being used/modified in each round is clearly defined.
Resolves https://github.com/AztecProtocol/barretenberg/issues/795.
---
.../ultra_bench/ultra_honk_rounds.bench.cpp | 13 +-
.../protogalaxy/protogalaxy_prover.cpp | 90 ++---------
.../protogalaxy/protogalaxy_verifier.cpp | 7 +-
.../protogalaxy_recursive_verifier.cpp | 11 +-
.../barretenberg/ultra_honk/oink_prover.cpp | 133 +++++++++++++++++
.../barretenberg/ultra_honk/oink_prover.hpp | 49 ++++++
.../barretenberg/ultra_honk/ultra_prover.cpp | 141 ++----------------
.../barretenberg/ultra_honk/ultra_prover.hpp | 32 ++--
8 files changed, 236 insertions(+), 240 deletions(-)
create mode 100644 barretenberg/cpp/src/barretenberg/ultra_honk/oink_prover.cpp
create mode 100644 barretenberg/cpp/src/barretenberg/ultra_honk/oink_prover.hpp
diff --git a/barretenberg/cpp/src/barretenberg/benchmark/ultra_bench/ultra_honk_rounds.bench.cpp b/barretenberg/cpp/src/barretenberg/benchmark/ultra_bench/ultra_honk_rounds.bench.cpp
index 11ad5e6e15f..aebc60b1912 100644
--- a/barretenberg/cpp/src/barretenberg/benchmark/ultra_bench/ultra_honk_rounds.bench.cpp
+++ b/barretenberg/cpp/src/barretenberg/benchmark/ultra_bench/ultra_honk_rounds.bench.cpp
@@ -45,11 +45,11 @@ BB_PROFILE static void test_round_inner(State& state, GoblinUltraProver& prover,
}
};
- time_if_index(PREAMBLE, [&] { prover.execute_preamble_round(); });
- time_if_index(WIRE_COMMITMENTS, [&] { prover.execute_wire_commitments_round(); });
- time_if_index(SORTED_LIST_ACCUMULATOR, [&] { prover.execute_sorted_list_accumulator_round(); });
- time_if_index(LOG_DERIVATIVE_INVERSE, [&] { prover.execute_log_derivative_inverse_round(); });
- time_if_index(GRAND_PRODUCT_COMPUTATION, [&] { prover.execute_grand_product_computation_round(); });
+ time_if_index(PREAMBLE, [&] { prover.oink_prover.execute_preamble_round(); });
+ time_if_index(WIRE_COMMITMENTS, [&] { prover.oink_prover.execute_wire_commitments_round(); });
+ time_if_index(SORTED_LIST_ACCUMULATOR, [&] { prover.oink_prover.execute_sorted_list_accumulator_round(); });
+ time_if_index(LOG_DERIVATIVE_INVERSE, [&] { prover.oink_prover.execute_log_derivative_inverse_round(); });
+ time_if_index(GRAND_PRODUCT_COMPUTATION, [&] { prover.oink_prover.execute_grand_product_computation_round(); });
time_if_index(RELATION_CHECK, [&] { prover.execute_relation_check_rounds(); });
time_if_index(ZEROMORPH, [&] { prover.execute_zeromorph_rounds(); });
}
@@ -62,7 +62,10 @@ BB_PROFILE static void test_round(State& state, size_t index) noexcept
auto prover = bb::mock_proofs::get_prover(
&bb::mock_proofs::generate_basic_arithmetic_circuit, log2_num_gates);
for (auto _ : state) {
+ state.PauseTiming();
test_round_inner(state, prover, index);
+ state.ResumeTiming();
+ // NOTE: google bench is very finnicky, must end in ResumeTiming() for correctness
}
}
#define ROUND_BENCHMARK(round) \
diff --git a/barretenberg/cpp/src/barretenberg/protogalaxy/protogalaxy_prover.cpp b/barretenberg/cpp/src/barretenberg/protogalaxy/protogalaxy_prover.cpp
index 65df1840efa..131f52d4c8b 100644
--- a/barretenberg/cpp/src/barretenberg/protogalaxy/protogalaxy_prover.cpp
+++ b/barretenberg/cpp/src/barretenberg/protogalaxy/protogalaxy_prover.cpp
@@ -1,93 +1,29 @@
#include "protogalaxy_prover.hpp"
#include "barretenberg/flavor/flavor.hpp"
+#include "barretenberg/ultra_honk/oink_prover.hpp"
namespace bb {
template
void ProtoGalaxyProver_::finalise_and_send_instance(std::shared_ptr instance,
const std::string& domain_separator)
{
- instance->initialize_prover_polynomials();
+ OinkProver oink_prover(instance, commitment_key, transcript, domain_separator + '_');
- const auto instance_size = static_cast(instance->proving_key->circuit_size);
- const auto num_public_inputs = static_cast(instance->proving_key->num_public_inputs);
- transcript->send_to_verifier(domain_separator + "_instance_size", instance_size);
- transcript->send_to_verifier(domain_separator + "_public_input_size", num_public_inputs);
+ // Add circuit size public input size and public inputs to transcript
+ oink_prover.execute_preamble_round();
- for (size_t i = 0; i < instance->proving_key->public_inputs.size(); ++i) {
- auto public_input_i = instance->proving_key->public_inputs[i];
- transcript->send_to_verifier(domain_separator + "_public_input_" + std::to_string(i), public_input_i);
- }
- transcript->send_to_verifier(domain_separator + "_pub_inputs_offset",
- static_cast(instance->proving_key->pub_inputs_offset));
-
- auto& witness_commitments = instance->witness_commitments;
-
- // Commit to the first three wire polynomials of the instance
- // We only commit to the fourth wire polynomial after adding memory recordss
- witness_commitments.w_l = commitment_key->commit(instance->proving_key->w_l);
- witness_commitments.w_r = commitment_key->commit(instance->proving_key->w_r);
- witness_commitments.w_o = commitment_key->commit(instance->proving_key->w_o);
-
- auto wire_comms = witness_commitments.get_wires();
- auto commitment_labels = instance->commitment_labels;
- auto wire_labels = commitment_labels.get_wires();
- for (size_t idx = 0; idx < 3; ++idx) {
- transcript->send_to_verifier(domain_separator + "_" + wire_labels[idx], wire_comms[idx]);
- }
-
- if constexpr (IsGoblinFlavor) {
- // Commit to Goblin ECC op wires
- witness_commitments.ecc_op_wire_1 = commitment_key->commit(instance->proving_key->ecc_op_wire_1);
- witness_commitments.ecc_op_wire_2 = commitment_key->commit(instance->proving_key->ecc_op_wire_2);
- witness_commitments.ecc_op_wire_3 = commitment_key->commit(instance->proving_key->ecc_op_wire_3);
- witness_commitments.ecc_op_wire_4 = commitment_key->commit(instance->proving_key->ecc_op_wire_4);
-
- auto op_wire_comms = instance->witness_commitments.get_ecc_op_wires();
- auto labels = commitment_labels.get_ecc_op_wires();
- for (size_t idx = 0; idx < Flavor::NUM_WIRES; ++idx) {
- transcript->send_to_verifier(domain_separator + "_" + labels[idx], op_wire_comms[idx]);
- }
- // Commit to DataBus columns
- witness_commitments.calldata = commitment_key->commit(instance->proving_key->calldata);
- witness_commitments.calldata_read_counts = commitment_key->commit(instance->proving_key->calldata_read_counts);
- transcript->send_to_verifier(domain_separator + "_" + commitment_labels.calldata,
- instance->witness_commitments.calldata);
- transcript->send_to_verifier(domain_separator + "_" + commitment_labels.calldata_read_counts,
- instance->witness_commitments.calldata_read_counts);
- }
-
- auto eta = transcript->template get_challenge(domain_separator + "_eta");
- instance->compute_sorted_accumulator_polynomials(eta);
-
- // Commit to the sorted witness-table accumulator and the finalized (i.e. with memory records) fourth wire
- // polynomial
- witness_commitments.sorted_accum = commitment_key->commit(instance->prover_polynomials.sorted_accum);
- witness_commitments.w_4 = commitment_key->commit(instance->prover_polynomials.w_4);
+ // Compute first three wire commitments
+ oink_prover.execute_wire_commitments_round();
- transcript->send_to_verifier(domain_separator + "_" + commitment_labels.sorted_accum,
- witness_commitments.sorted_accum);
- transcript->send_to_verifier(domain_separator + "_" + commitment_labels.w_4, witness_commitments.w_4);
-
- auto [beta, gamma] =
- transcript->template get_challenges(domain_separator + "_beta", domain_separator + "_gamma");
-
- if constexpr (IsGoblinFlavor) {
- // Compute and commit to the logderivative inverse used in DataBus
- instance->compute_logderivative_inverse(beta, gamma);
- instance->witness_commitments.lookup_inverses =
- commitment_key->commit(instance->prover_polynomials.lookup_inverses);
- transcript->send_to_verifier(domain_separator + "_" + commitment_labels.lookup_inverses,
- instance->witness_commitments.lookup_inverses);
- }
+ // Compute sorted list accumulator and commitment
+ oink_prover.execute_sorted_list_accumulator_round();
- instance->compute_grand_product_polynomials(beta, gamma);
+ // Fiat-Shamir: beta & gamma
+ oink_prover.execute_log_derivative_inverse_round();
- witness_commitments.z_perm = commitment_key->commit(instance->prover_polynomials.z_perm);
- witness_commitments.z_lookup = commitment_key->commit(instance->prover_polynomials.z_lookup);
+ // Compute grand product(s) and commitments.
+ oink_prover.execute_grand_product_computation_round();
- transcript->send_to_verifier(domain_separator + "_" + commitment_labels.z_perm,
- instance->witness_commitments.z_perm);
- transcript->send_to_verifier(domain_separator + "_" + commitment_labels.z_lookup,
- instance->witness_commitments.z_lookup);
+ // Generate relation separators alphas for sumcheck
for (size_t idx = 0; idx < NUM_SUBRELATIONS - 1; idx++) {
instance->alphas[idx] =
transcript->template get_challenge(domain_separator + "_alpha_" + std::to_string(idx));
diff --git a/barretenberg/cpp/src/barretenberg/protogalaxy/protogalaxy_verifier.cpp b/barretenberg/cpp/src/barretenberg/protogalaxy/protogalaxy_verifier.cpp
index 667460b5b20..cf08f67d30a 100644
--- a/barretenberg/cpp/src/barretenberg/protogalaxy/protogalaxy_verifier.cpp
+++ b/barretenberg/cpp/src/barretenberg/protogalaxy/protogalaxy_verifier.cpp
@@ -8,11 +8,13 @@ void ProtoGalaxyVerifier_::receive_and_finalise_instance(cons
{
// Get circuit parameters and the public inputs
inst->verification_key->circuit_size =
- transcript->template receive_from_prover(domain_separator + "_instance_size");
+ transcript->template receive_from_prover(domain_separator + "_circuit_size");
inst->verification_key->log_circuit_size =
static_cast(numeric::get_msb(inst->verification_key->circuit_size));
inst->verification_key->num_public_inputs =
transcript->template receive_from_prover(domain_separator + "_public_input_size");
+ inst->verification_key->pub_inputs_offset =
+ transcript->template receive_from_prover(domain_separator + "_pub_inputs_offset");
inst->verification_key->public_inputs.clear();
for (size_t i = 0; i < inst->verification_key->num_public_inputs; ++i) {
auto public_input_i =
@@ -20,9 +22,6 @@ void ProtoGalaxyVerifier_::receive_and_finalise_instance(cons
inst->verification_key->public_inputs.emplace_back(public_input_i);
}
- inst->verification_key->pub_inputs_offset =
- transcript->template receive_from_prover(domain_separator + "_pub_inputs_offset");
-
// Get commitments to first three wire polynomials
auto labels = inst->commitment_labels;
auto& witness_commitments = inst->witness_commitments;
diff --git a/barretenberg/cpp/src/barretenberg/stdlib/recursion/honk/verifier/protogalaxy_recursive_verifier.cpp b/barretenberg/cpp/src/barretenberg/stdlib/recursion/honk/verifier/protogalaxy_recursive_verifier.cpp
index b460fe76b5b..2eb8fd4f209 100644
--- a/barretenberg/cpp/src/barretenberg/stdlib/recursion/honk/verifier/protogalaxy_recursive_verifier.cpp
+++ b/barretenberg/cpp/src/barretenberg/stdlib/recursion/honk/verifier/protogalaxy_recursive_verifier.cpp
@@ -9,13 +9,17 @@ void ProtoGalaxyRecursiveVerifier_::receive_and_finalise_inst
const std::shared_ptr& inst, const std::string& domain_separator)
{
// Get circuit parameters and the public inputs
- const auto instance_size = transcript->template receive_from_prover(domain_separator + "_instance_size");
+ const auto instance_size = transcript->template receive_from_prover(domain_separator + "_circuit_size");
const auto public_input_size =
transcript->template receive_from_prover(domain_separator + "_public_input_size");
inst->verification_key->circuit_size = uint32_t(instance_size.get_value());
inst->verification_key->log_circuit_size =
static_cast(numeric::get_msb(inst->verification_key->circuit_size));
inst->verification_key->num_public_inputs = uint32_t(public_input_size.get_value());
+ const auto pub_inputs_offset =
+ transcript->template receive_from_prover(domain_separator + "_pub_inputs_offset");
+ inst->verification_key->pub_inputs_offset = uint32_t(pub_inputs_offset.get_value());
+
inst->verification_key->public_inputs.clear();
for (size_t i = 0; i < inst->verification_key->num_public_inputs; ++i) {
auto public_input_i =
@@ -23,11 +27,6 @@ void ProtoGalaxyRecursiveVerifier_::receive_and_finalise_inst
inst->verification_key->public_inputs.emplace_back(public_input_i);
}
- const auto pub_inputs_offset =
- transcript->template receive_from_prover(domain_separator + "_pub_inputs_offset");
-
- inst->verification_key->pub_inputs_offset = uint32_t(pub_inputs_offset.get_value());
-
// Get commitments to first three wire polynomials
auto labels = inst->commitment_labels;
auto& witness_commitments = inst->witness_commitments;
diff --git a/barretenberg/cpp/src/barretenberg/ultra_honk/oink_prover.cpp b/barretenberg/cpp/src/barretenberg/ultra_honk/oink_prover.cpp
new file mode 100644
index 00000000000..bfb1d0ad97f
--- /dev/null
+++ b/barretenberg/cpp/src/barretenberg/ultra_honk/oink_prover.cpp
@@ -0,0 +1,133 @@
+#include "barretenberg/ultra_honk/oink_prover.hpp"
+
+namespace bb {
+
+/**
+ * @brief Add circuit size, public input size, and public inputs to transcript
+ *
+ */
+template void OinkProver::execute_preamble_round()
+{
+ const auto circuit_size = static_cast(instance->proving_key->circuit_size);
+ const auto num_public_inputs = static_cast(instance->proving_key->num_public_inputs);
+ transcript->send_to_verifier(domain_separator + "circuit_size", circuit_size);
+ transcript->send_to_verifier(domain_separator + "public_input_size", num_public_inputs);
+ transcript->send_to_verifier(domain_separator + "pub_inputs_offset",
+ static_cast(instance->proving_key->pub_inputs_offset));
+
+ ASSERT(instance->proving_key->num_public_inputs == instance->proving_key->public_inputs.size());
+
+ for (size_t i = 0; i < instance->proving_key->num_public_inputs; ++i) {
+ auto public_input_i = instance->proving_key->public_inputs[i];
+ transcript->send_to_verifier(domain_separator + "public_input_" + std::to_string(i), public_input_i);
+ }
+}
+
+/**
+ * @brief Commit to the wire polynomials (part of the witness), with the exception of the fourth wire, which is
+ * only commited to after adding memory records. In the Goblin Flavor, we also commit to the ECC OP wires and the
+ * DataBus columns.
+ */
+template void OinkProver::execute_wire_commitments_round()
+{
+ auto& witness_commitments = instance->witness_commitments;
+
+ // Commit to the first three wire polynomials of the instance
+ // We only commit to the fourth wire polynomial after adding memory recordss
+ witness_commitments.w_l = commitment_key->commit(instance->proving_key->w_l);
+ witness_commitments.w_r = commitment_key->commit(instance->proving_key->w_r);
+ witness_commitments.w_o = commitment_key->commit(instance->proving_key->w_o);
+
+ auto wire_comms = witness_commitments.get_wires();
+ auto& commitment_labels = instance->commitment_labels;
+ auto wire_labels = commitment_labels.get_wires();
+ for (size_t idx = 0; idx < 3; ++idx) {
+ transcript->send_to_verifier(domain_separator + wire_labels[idx], wire_comms[idx]);
+ }
+
+ if constexpr (IsGoblinFlavor) {
+ // Commit to Goblin ECC op wires
+ witness_commitments.ecc_op_wire_1 = commitment_key->commit(instance->proving_key->ecc_op_wire_1);
+ witness_commitments.ecc_op_wire_2 = commitment_key->commit(instance->proving_key->ecc_op_wire_2);
+ witness_commitments.ecc_op_wire_3 = commitment_key->commit(instance->proving_key->ecc_op_wire_3);
+ witness_commitments.ecc_op_wire_4 = commitment_key->commit(instance->proving_key->ecc_op_wire_4);
+
+ auto op_wire_comms = witness_commitments.get_ecc_op_wires();
+ auto labels = commitment_labels.get_ecc_op_wires();
+ for (size_t idx = 0; idx < Flavor::NUM_WIRES; ++idx) {
+ transcript->send_to_verifier(domain_separator + labels[idx], op_wire_comms[idx]);
+ }
+ // Commit to DataBus columns
+ witness_commitments.calldata = commitment_key->commit(instance->proving_key->calldata);
+ witness_commitments.calldata_read_counts = commitment_key->commit(instance->proving_key->calldata_read_counts);
+ transcript->send_to_verifier(domain_separator + commitment_labels.calldata, witness_commitments.calldata);
+ transcript->send_to_verifier(domain_separator + commitment_labels.calldata_read_counts,
+ witness_commitments.calldata_read_counts);
+ }
+}
+
+/**
+ * @brief Compute sorted witness-table accumulator and commit to the resulting polynomials.
+ *
+ */
+template void OinkProver::execute_sorted_list_accumulator_round()
+{
+ auto& witness_commitments = instance->witness_commitments;
+ const auto& commitment_labels = instance->commitment_labels;
+
+ auto eta = transcript->template get_challenge(domain_separator + "eta");
+ instance->compute_sorted_accumulator_polynomials(eta);
+
+ // Commit to the sorted witness-table accumulator and the finalized (i.e. with memory records) fourth wire
+ // polynomial
+ witness_commitments.sorted_accum = commitment_key->commit(instance->prover_polynomials.sorted_accum);
+ witness_commitments.w_4 = commitment_key->commit(instance->prover_polynomials.w_4);
+
+ transcript->send_to_verifier(domain_separator + commitment_labels.sorted_accum, witness_commitments.sorted_accum);
+ transcript->send_to_verifier(domain_separator + commitment_labels.w_4, witness_commitments.w_4);
+}
+
+/**
+ * @brief Compute log derivative inverse polynomial and its commitment, if required
+ *
+ */
+template void OinkProver::execute_log_derivative_inverse_round()
+{
+ auto& witness_commitments = instance->witness_commitments;
+ const auto& commitment_labels = instance->commitment_labels;
+
+ auto [beta, gamma] = transcript->template get_challenges(domain_separator + "beta", domain_separator + "gamma");
+ instance->relation_parameters.beta = beta;
+ instance->relation_parameters.gamma = gamma;
+ if constexpr (IsGoblinFlavor) {
+ // Compute and commit to the logderivative inverse used in DataBus
+ instance->compute_logderivative_inverse(beta, gamma);
+ witness_commitments.lookup_inverses = commitment_key->commit(instance->prover_polynomials.lookup_inverses);
+ transcript->send_to_verifier(domain_separator + commitment_labels.lookup_inverses,
+ witness_commitments.lookup_inverses);
+ }
+}
+
+/**
+ * @brief Compute permutation and lookup grand product polynomials and their commitments
+ *
+ */
+template void OinkProver::execute_grand_product_computation_round()
+{
+ auto& witness_commitments = instance->witness_commitments;
+ const auto& commitment_labels = instance->commitment_labels;
+
+ instance->compute_grand_product_polynomials(instance->relation_parameters.beta,
+ instance->relation_parameters.gamma);
+
+ witness_commitments.z_perm = commitment_key->commit(instance->prover_polynomials.z_perm);
+ witness_commitments.z_lookup = commitment_key->commit(instance->prover_polynomials.z_lookup);
+
+ transcript->send_to_verifier(domain_separator + commitment_labels.z_perm, witness_commitments.z_perm);
+ transcript->send_to_verifier(domain_separator + commitment_labels.z_lookup, witness_commitments.z_lookup);
+}
+
+template class OinkProver;
+template class OinkProver;
+
+} // namespace bb
\ No newline at end of file
diff --git a/barretenberg/cpp/src/barretenberg/ultra_honk/oink_prover.hpp b/barretenberg/cpp/src/barretenberg/ultra_honk/oink_prover.hpp
new file mode 100644
index 00000000000..470794d8237
--- /dev/null
+++ b/barretenberg/cpp/src/barretenberg/ultra_honk/oink_prover.hpp
@@ -0,0 +1,49 @@
+#pragma once
+#include
+
+#include "barretenberg/flavor/goblin_ultra.hpp"
+#include "barretenberg/flavor/ultra.hpp"
+#include "barretenberg/sumcheck/instance/prover_instance.hpp"
+#include "barretenberg/transcript/transcript.hpp"
+
+namespace bb {
+
+/**
+ * @brief Class for all the oink rounds, which are shared between the folding prover and ultra prover.
+ * @details This class contains execute_preamble_round(), execute_wire_commitments_round(),
+ * execute_sorted_list_accumulator_round(), execute_log_derivative_inverse_round(), and
+ * execute_grand_product_computation_round().
+ *
+ * @tparam Flavor
+ */
+template class OinkProver {
+ using CommitmentKey = typename Flavor::CommitmentKey;
+ using Instance = ProverInstance_;
+ using Transcript = typename Flavor::Transcript;
+ using FF = typename Flavor::FF;
+
+ public:
+ std::shared_ptr instance;
+ std::shared_ptr transcript;
+ std::shared_ptr commitment_key;
+ std::string domain_separator;
+
+ OinkProver(const std::shared_ptr>& inst,
+ const std::shared_ptr& commitment_key,
+ const std::shared_ptr& transcript,
+ std::string domain_separator = "")
+ : instance(inst)
+ , transcript(transcript)
+ , commitment_key(commitment_key)
+ , domain_separator(std::move(domain_separator))
+ {
+ instance->initialize_prover_polynomials();
+ }
+
+ void execute_preamble_round();
+ void execute_wire_commitments_round();
+ void execute_sorted_list_accumulator_round();
+ void execute_log_derivative_inverse_round();
+ void execute_grand_product_computation_round();
+};
+} // namespace bb
\ No newline at end of file
diff --git a/barretenberg/cpp/src/barretenberg/ultra_honk/ultra_prover.cpp b/barretenberg/cpp/src/barretenberg/ultra_honk/ultra_prover.cpp
index f6d800558b5..fe78a0d7d88 100644
--- a/barretenberg/cpp/src/barretenberg/ultra_honk/ultra_prover.cpp
+++ b/barretenberg/cpp/src/barretenberg/ultra_honk/ultra_prover.cpp
@@ -15,9 +15,8 @@ UltraProver_::UltraProver_(const std::shared_ptr& inst, const
: instance(std::move(inst))
, transcript(transcript)
, commitment_key(instance->proving_key->commitment_key)
-{
- instance->initialize_prover_polynomials();
-}
+ , oink_prover(inst, commitment_key, transcript, "")
+{}
/**
* Create UltraProver_ from a circuit.
@@ -31,130 +30,8 @@ UltraProver_::UltraProver_(Builder& circuit)
: instance(std::make_shared(circuit))
, transcript(std::make_shared())
, commitment_key(instance->proving_key->commitment_key)
-{
- instance->initialize_prover_polynomials();
-}
-
-/**
- * @brief Add circuit size, public input size, and public inputs to transcript
- *
- */
-template void UltraProver_::execute_preamble_round()
-{
- auto proving_key = instance->proving_key;
- const auto circuit_size = static_cast(proving_key->circuit_size);
- const auto num_public_inputs = static_cast(proving_key->num_public_inputs);
-
- transcript->send_to_verifier("circuit_size", circuit_size);
- transcript->send_to_verifier("public_input_size", num_public_inputs);
- transcript->send_to_verifier("pub_inputs_offset", static_cast(proving_key->pub_inputs_offset));
-
- for (size_t i = 0; i < proving_key->num_public_inputs; ++i) {
- auto public_input_i = proving_key->public_inputs[i];
- transcript->send_to_verifier("public_input_" + std::to_string(i), public_input_i);
- }
-}
-
-/**
- * @brief Commit to the wire polynomials (part of the witness), with the exception of the fourth wire, which is
- * only commited to after adding memory records. In the Goblin Flavor, we also commit to the ECC OP wires and the
- * DataBus columns.
- */
-template void UltraProver_::execute_wire_commitments_round()
-{
- auto& witness_commitments = instance->witness_commitments;
- auto& proving_key = instance->proving_key;
-
- // Commit to the first three wire polynomials
- // We only commit to the fourth wire polynomial after adding memory recordss
- witness_commitments.w_l = commitment_key->commit(proving_key->w_l);
- witness_commitments.w_r = commitment_key->commit(proving_key->w_r);
- witness_commitments.w_o = commitment_key->commit(proving_key->w_o);
-
- auto wire_comms = witness_commitments.get_wires();
- auto labels = commitment_labels.get_wires();
- for (size_t idx = 0; idx < 3; ++idx) {
- transcript->send_to_verifier(labels[idx], wire_comms[idx]);
- }
-
- if constexpr (IsGoblinFlavor) {
- // Commit to Goblin ECC op wires
- witness_commitments.ecc_op_wire_1 = commitment_key->commit(proving_key->ecc_op_wire_1);
- witness_commitments.ecc_op_wire_2 = commitment_key->commit(proving_key->ecc_op_wire_2);
- witness_commitments.ecc_op_wire_3 = commitment_key->commit(proving_key->ecc_op_wire_3);
- witness_commitments.ecc_op_wire_4 = commitment_key->commit(proving_key->ecc_op_wire_4);
-
- auto op_wire_comms = instance->witness_commitments.get_ecc_op_wires();
- auto labels = commitment_labels.get_ecc_op_wires();
- for (size_t idx = 0; idx < Flavor::NUM_WIRES; ++idx) {
- transcript->send_to_verifier(labels[idx], op_wire_comms[idx]);
- }
-
- // Commit to DataBus columns
- witness_commitments.calldata = commitment_key->commit(proving_key->calldata);
- witness_commitments.calldata_read_counts = commitment_key->commit(proving_key->calldata_read_counts);
- transcript->send_to_verifier(commitment_labels.calldata, instance->witness_commitments.calldata);
- transcript->send_to_verifier(commitment_labels.calldata_read_counts,
- instance->witness_commitments.calldata_read_counts);
- }
-}
-
-/**
- * @brief Compute sorted witness-table accumulator and commit to the resulting polynomials.
- *
- */
-template void UltraProver_::execute_sorted_list_accumulator_round()
-{
- FF eta = transcript->template get_challenge("eta");
-
- instance->compute_sorted_accumulator_polynomials(eta);
-
- auto& witness_commitments = instance->witness_commitments;
- // Commit to the sorted witness-table accumulator and the finalized (i.e. with memory records) fourth wire
- // polynomial
- witness_commitments.sorted_accum = commitment_key->commit(instance->prover_polynomials.sorted_accum);
- witness_commitments.w_4 = commitment_key->commit(instance->prover_polynomials.w_4);
-
- transcript->send_to_verifier(commitment_labels.sorted_accum, instance->witness_commitments.sorted_accum);
- transcript->send_to_verifier(commitment_labels.w_4, instance->witness_commitments.w_4);
-}
-
-/**
- * @brief Compute log derivative inverse polynomial and its commitment, if required
- *
- */
-template void UltraProver_::execute_log_derivative_inverse_round()
-{
- auto& proving_key = instance->proving_key;
-
- // Compute and store challenges beta and gamma
- auto [beta, gamma] = transcript->template get_challenges("beta", "gamma");
- relation_parameters.beta = beta;
- relation_parameters.gamma = gamma;
-
- if constexpr (IsGoblinFlavor) {
- instance->compute_logderivative_inverse(beta, gamma);
- instance->witness_commitments.lookup_inverses = commitment_key->commit(proving_key->lookup_inverses);
- transcript->send_to_verifier(commitment_labels.lookup_inverses, instance->witness_commitments.lookup_inverses);
- }
-}
-
-/**
- * @brief Compute permutation and lookup grand product polynomials and their commitments
- *
- */
-template void UltraProver_::execute_grand_product_computation_round()
-{
- auto& proving_key = instance->proving_key;
-
- instance->compute_grand_product_polynomials(relation_parameters.beta, relation_parameters.gamma);
-
- auto& witness_commitments = instance->witness_commitments;
- witness_commitments.z_perm = commitment_key->commit(proving_key->z_perm);
- witness_commitments.z_lookup = commitment_key->commit(proving_key->z_lookup);
- transcript->send_to_verifier(commitment_labels.z_perm, instance->witness_commitments.z_perm);
- transcript->send_to_verifier(commitment_labels.z_lookup, instance->witness_commitments.z_lookup);
-}
+ , oink_prover(instance, commitment_key, transcript, "")
+{}
/**
* @brief Run Sumcheck resulting in u = (u_1,...,u_d) challenges and all evaluations at u being calculated.
@@ -203,19 +80,19 @@ template HonkProof& UltraProver_::export_proof()
template HonkProof& UltraProver_::construct_proof()
{
// Add circuit size public input size and public inputs to transcript->
- execute_preamble_round();
+ oink_prover.execute_preamble_round();
// Compute first three wire commitments
- execute_wire_commitments_round();
+ oink_prover.execute_wire_commitments_round();
// Compute sorted list accumulator and commitment
- execute_sorted_list_accumulator_round();
+ oink_prover.execute_sorted_list_accumulator_round();
// Fiat-Shamir: beta & gamma
- execute_log_derivative_inverse_round();
+ oink_prover.execute_log_derivative_inverse_round();
// Compute grand product(s) and commitments.
- execute_grand_product_computation_round();
+ oink_prover.execute_grand_product_computation_round();
// Fiat-Shamir: alpha
// Run sumcheck subprotocol.
diff --git a/barretenberg/cpp/src/barretenberg/ultra_honk/ultra_prover.hpp b/barretenberg/cpp/src/barretenberg/ultra_honk/ultra_prover.hpp
index dd822986f11..46aa631f6cb 100644
--- a/barretenberg/cpp/src/barretenberg/ultra_honk/ultra_prover.hpp
+++ b/barretenberg/cpp/src/barretenberg/ultra_honk/ultra_prover.hpp
@@ -7,6 +7,7 @@
#include "barretenberg/sumcheck/instance/prover_instance.hpp"
#include "barretenberg/sumcheck/sumcheck_output.hpp"
#include "barretenberg/transcript/transcript.hpp"
+#include "barretenberg/ultra_honk/oink_prover.hpp"
namespace bb {
@@ -25,6 +26,21 @@ template class UltraProver_ {
using Instance = ProverInstance;
using Transcript = typename Flavor::Transcript;
using RelationSeparator = typename Flavor::RelationSeparator;
+ using ZeroMorph = ZeroMorphProver_;
+
+ std::shared_ptr instance;
+
+ std::shared_ptr transcript;
+
+ bb::RelationParameters relation_parameters;
+
+ Polynomial quotient_W;
+
+ SumcheckOutput sumcheck_output;
+
+ std::shared_ptr commitment_key;
+
+ OinkProver oink_prover;
explicit UltraProver_(const std::shared_ptr&,
const std::shared_ptr& transcript = std::make_shared());
@@ -42,22 +58,6 @@ template class UltraProver_ {
HonkProof& export_proof();
HonkProof& construct_proof();
- std::shared_ptr instance;
-
- std::shared_ptr transcript;
-
- bb::RelationParameters relation_parameters;
-
- CommitmentLabels commitment_labels;
-
- Polynomial quotient_W;
-
- SumcheckOutput sumcheck_output;
-
- std::shared_ptr commitment_key;
-
- using ZeroMorph = ZeroMorphProver_;
-
private:
HonkProof proof;
};
From 426bd6d2490d59126a31de91cd783a76b0dfdc84 Mon Sep 17 00:00:00 2001
From: Leila Wang
Date: Tue, 12 Mar 2024 16:19:26 +0000
Subject: [PATCH 5/5] feat: Nullifier non membership (#5152)
Allow public functions to push non-existent read requests for
nullifiers. The kernel will check that the values being read are not in
the tree and not in the pending set.
Note that the read requests are only propagated to non-revertible data.
We will move all types of requests to `validation_requests` in function
circuit's public inputs. And they should always be verified whether the
tx reverts or not.
---
l1-contracts/slither_output.md | 8 +-
.../src/core/libraries/ConstantsGen.sol | 4 +-
.../aztec/src/context/private_context.nr | 3 +-
.../aztec/src/context/public_context.nr | 12 +-
.../crates/private-kernel-lib/src/common.nr | 13 +-
.../src/private_kernel_tail.nr | 25 +-
.../crates/public-kernel-lib/src/common.nr | 27 +-
.../src/public_kernel_app_logic.nr | 30 +-
.../src/public_kernel_setup.nr | 29 +-
.../src/public_kernel_tail.nr | 175 +++++++--
.../crates/reset-kernel-lib/src/lib.nr | 10 +-
.../src/non_existent_read_request_reset.nr | 354 ++++++++++++++++++
...llifier_non_existent_read_request_reset.nr | 28 ++
.../src/nullifier_read_request_reset.nr | 40 +-
.../src/read_request_reset.nr | 21 +-
.../crates/reset-kernel-lib/src/tests.nr | 2 +
...non_existent_read_request_hints_builder.nr | 80 ++++
.../nullifier_read_request_hints_builder.nr | 32 ++
.../rollup-lib/src/base/base_rollup_inputs.nr | 42 +--
.../rollup-lib/src/root/root_rollup_inputs.nr | 4 +-
...accumulated_non_revertible_data_builder.nr | 5 +-
.../public_accumulated_non_revertible_data.nr | 4 +-
.../public_accumulated_revertible_data.nr | 1 +
.../types/src/abis/membership_witness.nr | 5 -
.../types/src/abis/nullifier_leaf_preimage.nr | 16 +-
.../types/src/abis/public_call_stack_item.nr | 4 +-
.../src/abis/public_circuit_public_inputs.nr | 15 +-
.../crates/types/src/constants.nr | 4 +-
.../crates/types/src/hash.nr | 35 +-
.../crates/types/src/merkle_tree.nr | 118 +-----
.../types/src/merkle_tree/append_only_tree.nr | 2 +-
.../types/src/merkle_tree/indexed_tree.nr | 73 +---
.../types/src/merkle_tree/leaf_preimage.nr | 6 +
.../types/src/merkle_tree/membership.nr | 343 +++++++++++++++++
.../types/src/merkle_tree/merkle_tree.nr | 31 ++
.../crates/types/src/merkle_tree/root.nr | 102 +++++
.../crates/types/src/tests.nr | 1 +
.../types/src/tests/kernel_data_builder.nr | 43 +--
.../public_circuit_public_inputs_builder.nr | 8 +-
.../crates/types/src/tests/sort.nr | 87 +++++
.../crates/types/src/utils/arrays.nr | 153 ++++++++
yarn-project/circuits.js/src/constants.gen.ts | 4 +-
.../__snapshots__/contract_class.test.ts.snap | 10 +-
.../circuits.js/src/hints/build_hints.test.ts | 159 +++++++-
.../circuits.js/src/hints/build_hints.ts | 82 +++-
.../public_call_stack_item.test.ts.snap | 6 +-
.../public_circuit_public_inputs.test.ts.snap | 4 +-
yarn-project/circuits.js/src/structs/index.ts | 3 +-
.../kernel/combined_accumulated_data.ts | 12 +
...vate_kernel_tail_circuit_private_inputs.ts | 11 +-
...blic_kernel_tail_circuit_private_inputs.ts | 15 +-
.../non_existent_read_request_hints.ts | 152 ++++++++
.../structs/public_circuit_public_inputs.ts | 13 +
...t_reset_hints.ts => read_request_hints.ts} | 47 ++-
.../circuits.js/src/tests/factories.ts | 4 +
yarn-project/foundation/src/trees/index.ts | 24 +-
.../src/type_conversion.ts | 51 ++-
.../__snapshots__/index.test.ts.snap | 10 +-
.../pxe/src/kernel_prover/hints_builder.ts | 6 +-
.../pxe/src/kernel_prover/kernel_prover.ts | 4 +-
.../src/sequencer/abstract_phase_manager.ts | 19 +-
.../src/sequencer/hints_builder.ts | 48 ++-
.../src/sequencer/public_processor.test.ts | 1 +
.../src/avm/temporary_executor_migration.ts | 2 +
.../simulator/src/public/execution.ts | 2 +
yarn-project/simulator/src/public/executor.ts | 4 +
66 files changed, 2198 insertions(+), 490 deletions(-)
create mode 100644 noir-projects/noir-protocol-circuits/crates/reset-kernel-lib/src/non_existent_read_request_reset.nr
create mode 100644 noir-projects/noir-protocol-circuits/crates/reset-kernel-lib/src/nullifier_non_existent_read_request_reset.nr
create mode 100644 noir-projects/noir-protocol-circuits/crates/reset-kernel-lib/src/tests.nr
create mode 100644 noir-projects/noir-protocol-circuits/crates/reset-kernel-lib/src/tests/nullifier_non_existent_read_request_hints_builder.nr
create mode 100644 noir-projects/noir-protocol-circuits/crates/reset-kernel-lib/src/tests/nullifier_read_request_hints_builder.nr
create mode 100644 noir-projects/noir-protocol-circuits/crates/types/src/merkle_tree/membership.nr
create mode 100644 noir-projects/noir-protocol-circuits/crates/types/src/merkle_tree/merkle_tree.nr
create mode 100644 noir-projects/noir-protocol-circuits/crates/types/src/merkle_tree/root.nr
create mode 100644 noir-projects/noir-protocol-circuits/crates/types/src/tests/sort.nr
create mode 100644 yarn-project/circuits.js/src/structs/non_existent_read_request_hints.ts
rename yarn-project/circuits.js/src/structs/{read_request_reset_hints.ts => read_request_hints.ts} (82%)
diff --git a/l1-contracts/slither_output.md b/l1-contracts/slither_output.md
index a0a7f05ae3c..1d5dc10c830 100644
--- a/l1-contracts/slither_output.md
+++ b/l1-contracts/slither_output.md
@@ -321,15 +321,15 @@ src/core/messagebridge/Inbox.sol#L148-L153
Impact: Informational
Confidence: Medium
- [ ] ID-35
-Variable [Constants.LOGS_HASHES_NUM_BYTES_PER_BASE_ROLLUP](src/core/libraries/ConstantsGen.sol#L129) is too similar to [Constants.NOTE_HASHES_NUM_BYTES_PER_BASE_ROLLUP](src/core/libraries/ConstantsGen.sol#L122)
+Variable [Constants.LOGS_HASHES_NUM_BYTES_PER_BASE_ROLLUP](src/core/libraries/ConstantsGen.sol#L131) is too similar to [Constants.NOTE_HASHES_NUM_BYTES_PER_BASE_ROLLUP](src/core/libraries/ConstantsGen.sol#L124)
-src/core/libraries/ConstantsGen.sol#L129
+src/core/libraries/ConstantsGen.sol#L131
- [ ] ID-36
-Variable [Constants.L1_TO_L2_MESSAGE_LENGTH](src/core/libraries/ConstantsGen.sol#L109) is too similar to [Constants.L2_TO_L1_MESSAGE_LENGTH](src/core/libraries/ConstantsGen.sol#L110)
+Variable [Constants.L1_TO_L2_MESSAGE_LENGTH](src/core/libraries/ConstantsGen.sol#L111) is too similar to [Constants.L2_TO_L1_MESSAGE_LENGTH](src/core/libraries/ConstantsGen.sol#L112)
-src/core/libraries/ConstantsGen.sol#L109
+src/core/libraries/ConstantsGen.sol#L111
- [ ] ID-37
diff --git a/l1-contracts/src/core/libraries/ConstantsGen.sol b/l1-contracts/src/core/libraries/ConstantsGen.sol
index 6410f22ac56..c953f7d54ea 100644
--- a/l1-contracts/src/core/libraries/ConstantsGen.sol
+++ b/l1-contracts/src/core/libraries/ConstantsGen.sol
@@ -25,6 +25,7 @@ library Constants {
uint256 internal constant MAX_PUBLIC_DATA_READS_PER_CALL = 16;
uint256 internal constant MAX_NOTE_HASH_READ_REQUESTS_PER_CALL = 32;
uint256 internal constant MAX_NULLIFIER_READ_REQUESTS_PER_CALL = 2;
+ uint256 internal constant MAX_NULLIFIER_NON_EXISTENT_READ_REQUESTS_PER_CALL = 2;
uint256 internal constant MAX_NULLIFIER_KEY_VALIDATION_REQUESTS_PER_CALL = 1;
uint256 internal constant MAX_NEW_NOTE_HASHES_PER_TX = 64;
uint256 internal constant MAX_NON_REVERTIBLE_NOTE_HASHES_PER_TX = 8;
@@ -45,6 +46,7 @@ library Constants {
uint256 internal constant MAX_NEW_L2_TO_L1_MSGS_PER_TX = 2;
uint256 internal constant MAX_NOTE_HASH_READ_REQUESTS_PER_TX = 128;
uint256 internal constant MAX_NULLIFIER_READ_REQUESTS_PER_TX = 8;
+ uint256 internal constant MAX_NULLIFIER_NON_EXISTENT_READ_REQUESTS_PER_TX = 8;
uint256 internal constant MAX_NULLIFIER_KEY_VALIDATION_REQUESTS_PER_TX = 4;
uint256 internal constant NUM_ENCRYPTED_LOGS_HASHES_PER_TX = 1;
uint256 internal constant NUM_UNENCRYPTED_LOGS_HASHES_PER_TX = 1;
@@ -113,7 +115,7 @@ library Constants {
uint256 internal constant PARTIAL_STATE_REFERENCE_LENGTH = 6;
uint256 internal constant PRIVATE_CALL_STACK_ITEM_LENGTH = 214;
uint256 internal constant PRIVATE_CIRCUIT_PUBLIC_INPUTS_LENGTH = 209;
- uint256 internal constant PUBLIC_CIRCUIT_PUBLIC_INPUTS_LENGTH = 196;
+ uint256 internal constant PUBLIC_CIRCUIT_PUBLIC_INPUTS_LENGTH = 200;
uint256 internal constant STATE_REFERENCE_LENGTH = 8;
uint256 internal constant TX_CONTEXT_DATA_LENGTH = 4;
uint256 internal constant TX_REQUEST_LENGTH = 10;
diff --git a/noir-projects/aztec-nr/aztec/src/context/private_context.nr b/noir-projects/aztec-nr/aztec/src/context/private_context.nr
index ac987de4251..4bc91a32835 100644
--- a/noir-projects/aztec-nr/aztec/src/context/private_context.nr
+++ b/noir-projects/aztec-nr/aztec/src/context/private_context.nr
@@ -23,7 +23,7 @@ use dep::protocol_types::{
MAX_NEW_NOTE_HASHES_PER_CALL, MAX_NEW_L2_TO_L1_MSGS_PER_CALL, MAX_NEW_NULLIFIERS_PER_CALL,
MAX_PRIVATE_CALL_STACK_LENGTH_PER_CALL, MAX_PUBLIC_CALL_STACK_LENGTH_PER_CALL,
MAX_PUBLIC_DATA_READS_PER_CALL, MAX_PUBLIC_DATA_UPDATE_REQUESTS_PER_CALL,
- MAX_NOTE_HASH_READ_REQUESTS_PER_CALL, MAX_NULLIFIER_READ_REQUESTS_PER_CALL,
+ MAX_NOTE_HASH_READ_REQUESTS_PER_CALL, MAX_NULLIFIER_READ_REQUESTS_PER_CALL, MAX_NULLIFIER_NON_EXISTENT_READ_REQUESTS_PER_CALL,
MAX_NULLIFIER_KEY_VALIDATION_REQUESTS_PER_CALL, NUM_FIELDS_PER_SHA256, RETURN_VALUES_LENGTH
},
contrakt::{storage_read::StorageRead, storage_update_request::StorageUpdateRequest},
@@ -451,6 +451,7 @@ impl PrivateContext {
args_hash: reader.read(),
return_values: [0; RETURN_VALUES_LENGTH],
nullifier_read_requests: [ReadRequest::empty(); MAX_NULLIFIER_READ_REQUESTS_PER_CALL],
+ nullifier_non_existent_read_requests: [ReadRequest::empty(); MAX_NULLIFIER_NON_EXISTENT_READ_REQUESTS_PER_CALL],
contract_storage_update_requests: [StorageUpdateRequest::empty(); MAX_PUBLIC_DATA_UPDATE_REQUESTS_PER_CALL],
contract_storage_reads: [StorageRead::empty(); MAX_PUBLIC_DATA_READS_PER_CALL],
public_call_stack_hashes: [0; MAX_PUBLIC_CALL_STACK_LENGTH_PER_CALL],
diff --git a/noir-projects/aztec-nr/aztec/src/context/public_context.nr b/noir-projects/aztec-nr/aztec/src/context/public_context.nr
index 510df6b0d0b..352e8f03f38 100644
--- a/noir-projects/aztec-nr/aztec/src/context/public_context.nr
+++ b/noir-projects/aztec-nr/aztec/src/context/public_context.nr
@@ -15,7 +15,8 @@ use dep::protocol_types::{
MAX_NEW_NOTE_HASHES_PER_CALL, MAX_NEW_L2_TO_L1_MSGS_PER_CALL, MAX_NEW_NULLIFIERS_PER_CALL,
MAX_PUBLIC_CALL_STACK_LENGTH_PER_CALL, MAX_PUBLIC_DATA_READS_PER_CALL,
MAX_PUBLIC_DATA_UPDATE_REQUESTS_PER_CALL, MAX_NOTE_HASH_READ_REQUESTS_PER_CALL,
- MAX_NULLIFIER_READ_REQUESTS_PER_CALL, NUM_FIELDS_PER_SHA256, RETURN_VALUES_LENGTH
+ MAX_NULLIFIER_READ_REQUESTS_PER_CALL, MAX_NULLIFIER_NON_EXISTENT_READ_REQUESTS_PER_CALL,
+ NUM_FIELDS_PER_SHA256, RETURN_VALUES_LENGTH
},
contrakt::{storage_read::StorageRead, storage_update_request::StorageUpdateRequest},
hash::hash_args, header::Header, messaging::l2_to_l1_message::L2ToL1Message, utils::reader::Reader
@@ -29,6 +30,7 @@ struct PublicContext {
return_values : BoundedVec,
nullifier_read_requests: BoundedVec,
+ nullifier_non_existent_read_requests: BoundedVec,
contract_storage_update_requests: BoundedVec,
contract_storage_reads: BoundedVec,
public_call_stack_hashes: BoundedVec,
@@ -102,6 +104,7 @@ impl PublicContext {
args_hash,
return_values: BoundedVec::new(),
nullifier_read_requests: BoundedVec::new(),
+ nullifier_non_existent_read_requests: BoundedVec::new(),
contract_storage_update_requests: BoundedVec::new(),
contract_storage_reads: BoundedVec::new(),
public_call_stack_hashes: BoundedVec::new(),
@@ -143,6 +146,7 @@ impl PublicContext {
call_context: self.inputs.call_context, // Done
args_hash: self.args_hash, // Done
nullifier_read_requests: self.nullifier_read_requests.storage,
+ nullifier_non_existent_read_requests: self.nullifier_non_existent_read_requests.storage,
contract_storage_update_requests: self.contract_storage_update_requests.storage,
contract_storage_reads: self.contract_storage_reads.storage,
return_values: self.return_values.storage,
@@ -165,6 +169,12 @@ impl PublicContext {
self.side_effect_counter = self.side_effect_counter + 1;
}
+ pub fn push_nullifier_non_existent_read_request(&mut self, nullifier: Field) {
+ let request = ReadRequest { value: nullifier, counter: self.side_effect_counter };
+ self.nullifier_non_existent_read_requests.push(request);
+ self.side_effect_counter = self.side_effect_counter + 1;
+ }
+
pub fn message_portal(&mut self, recipient: EthAddress, content: Field) {
let message = L2ToL1Message { recipient, content };
self.new_l2_to_l1_msgs.push(message);
diff --git a/noir-projects/noir-protocol-circuits/crates/private-kernel-lib/src/common.nr b/noir-projects/noir-protocol-circuits/crates/private-kernel-lib/src/common.nr
index 4313375bed7..43c012dc9ba 100644
--- a/noir-projects/noir-protocol-circuits/crates/private-kernel-lib/src/common.nr
+++ b/noir-projects/noir-protocol-circuits/crates/private-kernel-lib/src/common.nr
@@ -20,9 +20,10 @@ use dep::types::{
hash::{
compute_constructor_hash, compute_l2_to_l1_hash, compute_logs_hash,
compute_new_contract_address_hash, function_tree_root_from_siblings, pedersen_hash,
- private_functions_root_from_siblings, root_from_sibling_path, silo_note_hash, silo_nullifier,
+ private_functions_root_from_siblings, silo_note_hash, silo_nullifier,
stdlib_recursion_verification_key_compress_native_vk
},
+ merkle_tree::check_membership,
utils::{arrays::{array_length, array_to_bounded_vec, validate_array}},
traits::{is_empty, is_empty_array}
};
@@ -72,8 +73,14 @@ pub fn validate_note_hash_read_requests(
// but we use the leaf index as a placeholder to detect a 'pending note read'.
if (read_request != 0) & (witness.is_transient == false) {
- let root_for_read_request = root_from_sibling_path(read_request, witness.leaf_index, witness.sibling_path);
- assert(root_for_read_request == historical_note_hash_tree_root, "note hash tree root mismatch");
+ assert(
+ check_membership(
+ read_request,
+ witness.leaf_index,
+ witness.sibling_path,
+ historical_note_hash_tree_root
+ ), "note hash tree root mismatch"
+ );
// TODO(https://github.com/AztecProtocol/aztec-packages/issues/1354): do we need to enforce
// that a non-transient read_request was derived from the proper/current contract address?
}
diff --git a/noir-projects/noir-protocol-circuits/crates/private-kernel-lib/src/private_kernel_tail.nr b/noir-projects/noir-protocol-circuits/crates/private-kernel-lib/src/private_kernel_tail.nr
index b6d6de9fab3..46f9cda62a5 100644
--- a/noir-projects/noir-protocol-circuits/crates/private-kernel-lib/src/private_kernel_tail.nr
+++ b/noir-projects/noir-protocol-circuits/crates/private-kernel-lib/src/private_kernel_tail.nr
@@ -1,12 +1,11 @@
use crate::common;
use dep::std::{cmp::Eq, option::Option, unsafe};
-use dep::reset_kernel_lib::{NullifierReadRequestResetHints, reset_read_requests};
+use dep::reset_kernel_lib::{NullifierReadRequestHints, reset_read_requests};
use dep::types::{
abis::{
call_request::CallRequest, nullifier_key_validation_request::NullifierKeyValidationRequestContext,
kernel_data::{PrivateKernelInnerData, PrivateKernelTailData},
kernel_circuit_public_inputs::{PrivateKernelCircuitPublicInputsBuilder, PrivateKernelTailCircuitPublicInputs},
- membership_witness::{MembershipWitness, NullifierMembershipWitness},
side_effect::{SideEffect, SideEffectLinkedToNoteHash, Ordered}
},
constants::{
@@ -26,7 +25,7 @@ struct PrivateKernelTailCircuitPrivateInputs {
read_commitment_hints: [u64; MAX_NOTE_HASH_READ_REQUESTS_PER_TX],
sorted_new_nullifiers: [SideEffectLinkedToNoteHash; MAX_NEW_NULLIFIERS_PER_TX],
sorted_new_nullifiers_indexes: [u64; MAX_NEW_NULLIFIERS_PER_TX],
- nullifier_read_request_reset_hints: NullifierReadRequestResetHints,
+ nullifier_read_request_hints: NullifierReadRequestHints,
nullifier_commitment_hints: [u64; MAX_NEW_NULLIFIERS_PER_TX],
master_nullifier_secret_keys: [GrumpkinPrivateKey; MAX_NULLIFIER_KEY_VALIDATION_REQUESTS_PER_TX],
}
@@ -43,7 +42,7 @@ impl PrivateKernelTailCircuitPrivateInputs {
let pending_nullifiers = self.previous_kernel.public_inputs.end.new_nullifiers;
- let hints = self.nullifier_read_request_reset_hints;
+ let hints = self.nullifier_read_request_hints;
let nullifier_tree_root = public_inputs.constants.historical_header.state.partial.nullifier_tree.root;
@@ -256,7 +255,7 @@ mod tests {
use dep::std::{cmp::Eq, unsafe};
use crate::{private_kernel_tail::PrivateKernelTailCircuitPrivateInputs};
use dep::reset_kernel_lib::{
- NullifierReadRequestResetHintsBuilder,
+ tests::nullifier_read_request_hints_builder::NullifierReadRequestHintsBuilder,
read_request_reset::{PendingReadHint, ReadRequestState, ReadRequestStatus}
};
use dep::types::constants::{
@@ -277,7 +276,7 @@ mod tests {
previous_kernel: PreviousKernelDataBuilder,
read_commitment_hints: [u64; MAX_NOTE_HASH_READ_REQUESTS_PER_TX],
nullifier_commitment_hints: [u64; MAX_NEW_NULLIFIERS_PER_TX],
- nullifier_read_request_reset_hints_builder: NullifierReadRequestResetHintsBuilder,
+ nullifier_read_request_hints_builder: NullifierReadRequestHintsBuilder,
}
impl PrivateKernelTailInputsBuilder {
@@ -286,7 +285,7 @@ mod tests {
previous_kernel: PreviousKernelDataBuilder::new(false),
read_commitment_hints: [0; MAX_NOTE_HASH_READ_REQUESTS_PER_TX],
nullifier_commitment_hints: [0; MAX_NEW_NULLIFIERS_PER_TX],
- nullifier_read_request_reset_hints_builder: NullifierReadRequestResetHintsBuilder::new(MAX_NULLIFIER_READ_REQUESTS_PER_TX)
+ nullifier_read_request_hints_builder: NullifierReadRequestHintsBuilder::new(MAX_NULLIFIER_READ_REQUESTS_PER_TX)
}
}
@@ -326,10 +325,10 @@ mod tests {
pub fn add_nullifier_pending_read(&mut self, nullifier_index_offset_one: u64) {
let nullifier_index = nullifier_index_offset_one + 1; // + 1 is for the first nullifier
let read_request_index = self.previous_kernel.add_read_request_for_pending_nullifier(nullifier_index);
- let hint_index = self.nullifier_read_request_reset_hints_builder.pending_read_hints.len();
+ let hint_index = self.nullifier_read_request_hints_builder.pending_read_hints.len();
let hint = PendingReadHint { read_request_index, pending_value_index: nullifier_index };
- self.nullifier_read_request_reset_hints_builder.pending_read_hints.push(hint);
- self.nullifier_read_request_reset_hints_builder.read_request_statuses[read_request_index] = ReadRequestStatus { state: ReadRequestState.PENDING, hint_index };
+ self.nullifier_read_request_hints_builder.pending_read_hints.push(hint);
+ self.nullifier_read_request_hints_builder.read_request_statuses[read_request_index] = ReadRequestStatus { state: ReadRequestState.PENDING, hint_index };
}
pub fn nullify_transient_commitment(&mut self, nullifier_index: Field, commitment_index: u64) {
@@ -383,7 +382,7 @@ mod tests {
read_commitment_hints: sorted_read_commitment_hints,
sorted_new_nullifiers,
sorted_new_nullifiers_indexes,
- nullifier_read_request_reset_hints: self.nullifier_read_request_reset_hints_builder.to_hints(),
+ nullifier_read_request_hints: self.nullifier_read_request_hints_builder.to_hints(),
nullifier_commitment_hints: sorted_nullifier_commitment_hints,
master_nullifier_secret_keys: unsafe::zeroed()
};
@@ -480,10 +479,10 @@ mod tests {
builder.append_nullifiers(3);
builder.add_nullifier_pending_read(1);
- let mut hint = builder.nullifier_read_request_reset_hints_builder.pending_read_hints.pop();
+ let mut hint = builder.nullifier_read_request_hints_builder.pending_read_hints.pop();
assert(hint.pending_value_index == 2);
hint.pending_value_index = 1;
- builder.nullifier_read_request_reset_hints_builder.pending_read_hints.push(hint);
+ builder.nullifier_read_request_hints_builder.pending_read_hints.push(hint);
builder.failed();
}
diff --git a/noir-projects/noir-protocol-circuits/crates/public-kernel-lib/src/common.nr b/noir-projects/noir-protocol-circuits/crates/public-kernel-lib/src/common.nr
index 7df1fac5ffa..550649723f8 100644
--- a/noir-projects/noir-protocol-circuits/crates/public-kernel-lib/src/common.nr
+++ b/noir-projects/noir-protocol-circuits/crates/public-kernel-lib/src/common.nr
@@ -10,9 +10,10 @@ use dep::types::{
contrakt::{storage_read::StorageRead, storage_update_request::StorageUpdateRequest},
constants::{
MAX_NEW_L2_TO_L1_MSGS_PER_CALL, MAX_NEW_NOTE_HASHES_PER_CALL, MAX_NEW_NULLIFIERS_PER_CALL,
- MAX_NULLIFIER_READ_REQUESTS_PER_CALL, MAX_PUBLIC_CALL_STACK_LENGTH_PER_CALL,
- MAX_PUBLIC_DATA_UPDATE_REQUESTS_PER_TX, MAX_PUBLIC_DATA_READS_PER_CALL, NUM_FIELDS_PER_SHA256,
- MAX_REVERTIBLE_PUBLIC_DATA_READS_PER_TX, MAX_REVERTIBLE_PUBLIC_DATA_UPDATE_REQUESTS_PER_TX,
+ MAX_NULLIFIER_READ_REQUESTS_PER_CALL, MAX_NULLIFIER_NON_EXISTENT_READ_REQUESTS_PER_CALL,
+ MAX_PUBLIC_CALL_STACK_LENGTH_PER_CALL, MAX_PUBLIC_DATA_UPDATE_REQUESTS_PER_TX,
+ MAX_PUBLIC_DATA_READS_PER_CALL, NUM_FIELDS_PER_SHA256, MAX_REVERTIBLE_PUBLIC_DATA_READS_PER_TX,
+ MAX_REVERTIBLE_PUBLIC_DATA_UPDATE_REQUESTS_PER_TX,
MAX_NON_REVERTIBLE_PUBLIC_DATA_UPDATE_REQUESTS_PER_TX, MAX_NON_REVERTIBLE_PUBLIC_DATA_READS_PER_TX
},
hash::{silo_note_hash, silo_nullifier, compute_l2_to_l1_hash, accumulate_sha256},
@@ -89,6 +90,7 @@ pub fn initialize_end_values(
let start_non_revertible = previous_kernel.public_inputs.end_non_revertible;
circuit_outputs.end_non_revertible.public_call_stack = array_to_bounded_vec(start_non_revertible.public_call_stack);
circuit_outputs.end_non_revertible.nullifier_read_requests = array_to_bounded_vec(start_non_revertible.nullifier_read_requests);
+ circuit_outputs.end_non_revertible.nullifier_non_existent_read_requests = array_to_bounded_vec(start_non_revertible.nullifier_non_existent_read_requests);
}
fn perform_static_call_checks(public_call: PublicCallData) {
@@ -161,6 +163,7 @@ pub fn update_public_end_non_revertible_values(
circuit_outputs.end_non_revertible.public_call_stack.extend_from_bounded_vec(public_call_requests);
propagate_nullifier_read_requests_non_revertible(public_call, circuit_outputs);
+ propagate_nullifier_non_existent_read_requests_non_revertible(public_call, circuit_outputs);
propagate_new_nullifiers_non_revertible(public_call, circuit_outputs);
propagate_new_note_hashes_non_revertible(public_call, circuit_outputs);
propagate_valid_non_revertible_public_data_update_requests(public_call, circuit_outputs);
@@ -182,6 +185,8 @@ pub fn update_public_end_values(public_call: PublicCallData, circuit_outputs: &m
circuit_outputs.end.public_call_stack.extend_from_bounded_vec(public_call_requests);
propagate_nullifier_read_requests_revertible(public_call, circuit_outputs);
+ propagate_nullifier_non_existent_read_requests_non_revertible(public_call, circuit_outputs); // TODO - Requests are not revertible and should be propagated to "validation_requests".
+
propagate_new_nullifiers(public_call, circuit_outputs);
propagate_new_note_hashes(public_call, circuit_outputs);
@@ -224,6 +229,22 @@ fn propagate_nullifier_read_requests_revertible(
}
}
+fn propagate_nullifier_non_existent_read_requests_non_revertible(
+ public_call: PublicCallData,
+ circuit_outputs: &mut PublicKernelCircuitPublicInputsBuilder
+) {
+ let public_call_public_inputs = public_call.call_stack_item.public_inputs;
+ let nullifier_non_existent_read_requests = public_call_public_inputs.nullifier_non_existent_read_requests;
+ let storage_contract_address = public_call_public_inputs.call_context.storage_contract_address;
+
+ for i in 0..MAX_NULLIFIER_NON_EXISTENT_READ_REQUESTS_PER_CALL {
+ let request = nullifier_non_existent_read_requests[i];
+ if !is_empty(request) {
+ circuit_outputs.end_non_revertible.nullifier_non_existent_read_requests.push(request.to_context(storage_contract_address));
+ }
+ }
+}
+
fn propagate_valid_public_data_update_requests(
public_call: PublicCallData,
circuit_outputs: &mut PublicKernelCircuitPublicInputsBuilder
diff --git a/noir-projects/noir-protocol-circuits/crates/public-kernel-lib/src/public_kernel_app_logic.nr b/noir-projects/noir-protocol-circuits/crates/public-kernel-lib/src/public_kernel_app_logic.nr
index 546e026f156..defa25ed06e 100644
--- a/noir-projects/noir-protocol-circuits/crates/public-kernel-lib/src/public_kernel_app_logic.nr
+++ b/noir-projects/noir-protocol-circuits/crates/public-kernel-lib/src/public_kernel_app_logic.nr
@@ -75,14 +75,14 @@ mod tests {
use dep::types::{
abis::{
kernel_circuit_public_inputs::PublicKernelCircuitPublicInputs, public_data_read::PublicDataRead,
- public_data_update_request::PublicDataUpdateRequest,
+ public_data_update_request::PublicDataUpdateRequest, read_request::ReadRequest,
side_effect::{SideEffect, SideEffectLinkedToNoteHash}
},
address::{AztecAddress, EthAddress}, contract_class_id::ContractClassId,
hash::{compute_l2_to_l1_hash, compute_logs_hash, silo_note_hash, silo_nullifier},
messaging::l2_to_l1_message::L2ToL1Message,
tests::{kernel_data_builder::PreviousKernelDataBuilder, public_call_data_builder::PublicCallDataBuilder},
- utils::{arrays::array_eq}
+ utils::arrays::{array_eq, array_length}
};
use dep::types::constants::{MAX_PUBLIC_DATA_READS_PER_CALL, MAX_PUBLIC_DATA_UPDATE_REQUESTS_PER_CALL};
@@ -419,4 +419,30 @@ mod tests {
);
assert_eq_public_data_reads(public_inputs.end.public_data_reads, read_requests);
}
+
+ #[test]
+ fn propagate_nullifier_non_existent_read_requests() {
+ let mut builder = PublicKernelAppLogicCircuitPrivateInputsBuilder::new();
+ let storage_contract_address = builder.public_call.public_inputs.call_context.storage_contract_address;
+
+ let request_0 = ReadRequest { value: 123, counter: 4567 };
+ builder.public_call.public_inputs.nullifier_non_existent_read_requests.push(request_0);
+ let request_1 = ReadRequest { value: 777888, counter: 90 };
+ builder.public_call.public_inputs.nullifier_non_existent_read_requests.push(request_1);
+
+ let public_inputs = builder.execute();
+
+ let end_requests = public_inputs.end_non_revertible.nullifier_non_existent_read_requests;
+ assert_eq(array_length(end_requests), 2);
+
+ let request_context = end_requests[0];
+ assert_eq(request_context.value, request_0.value);
+ assert_eq(request_context.counter, request_0.counter);
+ assert_eq(request_context.contract_address, storage_contract_address);
+
+ let request_context = end_requests[1];
+ assert_eq(request_context.value, request_1.value);
+ assert_eq(request_context.counter, request_1.counter);
+ assert_eq(request_context.contract_address, storage_contract_address);
+ }
}
diff --git a/noir-projects/noir-protocol-circuits/crates/public-kernel-lib/src/public_kernel_setup.nr b/noir-projects/noir-protocol-circuits/crates/public-kernel-lib/src/public_kernel_setup.nr
index a08b158dd3f..560b2b848e6 100644
--- a/noir-projects/noir-protocol-circuits/crates/public-kernel-lib/src/public_kernel_setup.nr
+++ b/noir-projects/noir-protocol-circuits/crates/public-kernel-lib/src/public_kernel_setup.nr
@@ -78,7 +78,8 @@ mod tests {
abis::{
call_request::CallRequest, function_selector::FunctionSelector,
kernel_circuit_public_inputs::PublicKernelCircuitPublicInputs, public_data_read::PublicDataRead,
- public_data_update_request::PublicDataUpdateRequest, public_call_data::PublicCallData
+ public_data_update_request::PublicDataUpdateRequest, public_call_data::PublicCallData,
+ read_request::ReadRequest
},
address::{AztecAddress, EthAddress}, contract_class_id::ContractClassId,
contrakt::storage_read::StorageRead, hash::compute_logs_hash,
@@ -496,4 +497,30 @@ mod tests {
let expected_unencrypted_logs_hash = compute_logs_hash(prev_unencrypted_logs_hash, unencrypted_logs_hash);
assert_eq(public_inputs.end.unencrypted_logs_hash, expected_unencrypted_logs_hash);
}
+
+ #[test]
+ fn propagate_nullifier_non_existent_read_requests() {
+ let mut builder = PublicKernelSetupCircuitPrivateInputsBuilder::new();
+ let storage_contract_address = builder.public_call.public_inputs.call_context.storage_contract_address;
+
+ let request_0 = ReadRequest { value: 123, counter: 4567 };
+ builder.public_call.public_inputs.nullifier_non_existent_read_requests.push(request_0);
+ let request_1 = ReadRequest { value: 777888, counter: 90 };
+ builder.public_call.public_inputs.nullifier_non_existent_read_requests.push(request_1);
+
+ let public_inputs = builder.execute();
+
+ let end_requests = public_inputs.end_non_revertible.nullifier_non_existent_read_requests;
+ assert_eq(array_length(end_requests), 2);
+
+ let request_context = end_requests[0];
+ assert_eq(request_context.value, request_0.value);
+ assert_eq(request_context.counter, request_0.counter);
+ assert_eq(request_context.contract_address, storage_contract_address);
+
+ let request_context = end_requests[1];
+ assert_eq(request_context.value, request_1.value);
+ assert_eq(request_context.counter, request_1.counter);
+ assert_eq(request_context.contract_address, storage_contract_address);
+ }
}
diff --git a/noir-projects/noir-protocol-circuits/crates/public-kernel-lib/src/public_kernel_tail.nr b/noir-projects/noir-protocol-circuits/crates/public-kernel-lib/src/public_kernel_tail.nr
index d6242fe3292..f0ed14abc3c 100644
--- a/noir-projects/noir-protocol-circuits/crates/public-kernel-lib/src/public_kernel_tail.nr
+++ b/noir-projects/noir-protocol-circuits/crates/public-kernel-lib/src/public_kernel_tail.nr
@@ -1,17 +1,23 @@
use crate::common;
-use dep::reset_kernel_lib::{NullifierReadRequestResetHints, reset_read_requests};
+use dep::reset_kernel_lib::{
+ NullifierReadRequestHints, NullifierNonExistentReadRequestHints, reset_non_existent_read_requests,
+ reset_read_requests
+};
use dep::types::{
abis::{
kernel_circuit_public_inputs::{PublicKernelCircuitPublicInputs, PublicKernelCircuitPublicInputsBuilder},
kernel_data::PublicKernelData, side_effect::SideEffectLinkedToNoteHash
},
- constants::MAX_NEW_NULLIFIERS_PER_TX, utils::{arrays::{array_length, array_merge, array_concat}}
+ constants::MAX_NEW_NULLIFIERS_PER_TX,
+ utils::{arrays::{array_length, array_merge, array_concat, array_to_bounded_vec, assert_sorted_array}},
+ hash::silo_nullifier, traits::is_empty
};
use dep::std::unsafe;
struct PublicKernelTailCircuitPrivateInputs {
previous_kernel: PublicKernelData,
- nullifier_read_request_reset_hints: NullifierReadRequestResetHints,
+ nullifier_read_request_hints: NullifierReadRequestHints,
+ nullifier_non_existent_read_request_hints: NullifierNonExistentReadRequestHints,
}
impl PublicKernelTailCircuitPrivateInputs {
@@ -44,7 +50,7 @@ impl PublicKernelTailCircuitPrivateInputs {
let pending_nullifiers: [SideEffectLinkedToNoteHash; MAX_NEW_NULLIFIERS_PER_TX] = array_concat(end_non_revertible.new_nullifiers, end.new_nullifiers);
- let hints = self.nullifier_read_request_reset_hints;
+ let hints = self.nullifier_read_request_hints;
let nullifier_tree_root = public_inputs.constants.historical_header.state.partial.nullifier_tree.root;
@@ -62,6 +68,42 @@ impl PublicKernelTailCircuitPrivateInputs {
);
}
+ fn validate_nullifier_non_existent_read_requests(self, public_inputs: &mut PublicKernelCircuitPublicInputsBuilder) {
+ let end_non_revertible = self.previous_kernel.public_inputs.end_non_revertible;
+ let end = self.previous_kernel.public_inputs.end;
+
+ // The values of the read requests here need to be siloed.
+ // Notice that it's not the case for regular read requests, which can be run between two kernel iterations, and will to be verified against unsiloed pending values.
+ let mut read_requests = end_non_revertible.nullifier_non_existent_read_requests;
+ for i in 0..read_requests.len() {
+ let read_request = read_requests[i];
+ if !is_empty(read_request) {
+ read_requests[i].value = silo_nullifier(read_request.contract_address, read_request.value);
+ }
+ }
+
+ let nullifier_tree_root = public_inputs.constants.historical_header.state.partial.nullifier_tree.root;
+
+ let hints = self.nullifier_non_existent_read_request_hints;
+
+ let pending_nullifiers: [SideEffectLinkedToNoteHash; MAX_NEW_NULLIFIERS_PER_TX] = array_concat(end_non_revertible.new_nullifiers, end.new_nullifiers);
+ assert_sorted_array(
+ pending_nullifiers,
+ hints.sorted_pending_values,
+ hints.sorted_pending_value_index_hints,
+ |a: SideEffectLinkedToNoteHash, b: SideEffectLinkedToNoteHash| a.value.lt(b.value)
+ );
+ let sorted_pending_nullifiers = array_to_bounded_vec(hints.sorted_pending_values);
+
+ reset_non_existent_read_requests(
+ read_requests,
+ hints.non_membership_hints,
+ nullifier_tree_root,
+ sorted_pending_nullifiers,
+ hints.next_pending_value_indices
+ );
+ }
+
pub fn public_kernel_tail(self) -> PublicKernelCircuitPublicInputs {
let mut public_inputs: PublicKernelCircuitPublicInputsBuilder = unsafe::zeroed();
@@ -73,6 +115,8 @@ impl PublicKernelTailCircuitPrivateInputs {
self.validate_nullifier_read_requests(&mut public_inputs);
+ self.validate_nullifier_non_existent_read_requests(&mut public_inputs);
+
public_inputs.to_inner()
}
}
@@ -80,57 +124,114 @@ impl PublicKernelTailCircuitPrivateInputs {
mod tests {
use crate::{public_kernel_tail::PublicKernelTailCircuitPrivateInputs};
use dep::reset_kernel_lib::{
- NullifierReadRequestResetHintsBuilder,
+ tests::{
+ nullifier_non_existent_read_request_hints_builder::NullifierNonExistentReadRequestHintsBuilder,
+ nullifier_read_request_hints_builder::NullifierReadRequestHintsBuilder
+ },
read_request_reset::{PendingReadHint, ReadRequestState, ReadRequestStatus}
};
use dep::types::{
abis::{
kernel_circuit_public_inputs::{PublicKernelCircuitPublicInputs, PublicKernelCircuitPublicInputsBuilder},
- kernel_data::PublicKernelData
+ kernel_data::PublicKernelData, nullifier_leaf_preimage::NullifierLeafPreimage
},
- constants::MAX_NULLIFIER_READ_REQUESTS_PER_TX,
- tests::{kernel_data_builder::PreviousKernelDataBuilder}
+ constants::{
+ MAX_NEW_NULLIFIERS_PER_TX, MAX_NULLIFIER_READ_REQUESTS_PER_TX, NULLIFIER_TREE_HEIGHT,
+ NULLIFIER_SUBTREE_SIBLING_PATH_LENGTH, NULLIFIER_SUBTREE_HEIGHT
+ },
+ hash::silo_nullifier,
+ tests::{kernel_data_builder::PreviousKernelDataBuilder, merkle_tree_utils::NonEmptyMerkleTree},
+ utils::arrays::array_concat
};
+ fn build_nullifier_tree() -> NonEmptyMerkleTree {
+ let mut pre_existing_nullifiers = [NullifierLeafPreimage::empty(); MAX_NEW_NULLIFIERS_PER_TX];
+ pre_existing_nullifiers[0] = NullifierLeafPreimage { nullifier: 0, next_nullifier: 100, next_index: 1 };
+ pre_existing_nullifiers[1] = NullifierLeafPreimage { nullifier: 100, next_nullifier: 0, next_index: 0 };
+ NonEmptyMerkleTree::new(
+ pre_existing_nullifiers.map(|preimage: NullifierLeafPreimage| preimage.hash()),
+ [0; NULLIFIER_TREE_HEIGHT],
+ [0; NULLIFIER_TREE_HEIGHT - NULLIFIER_SUBTREE_HEIGHT],
+ [0; NULLIFIER_SUBTREE_HEIGHT]
+ )
+ }
+
struct PublicKernelTailCircuitPrivateInputsBuilder {
previous_kernel: PreviousKernelDataBuilder,
- nullifier_read_request_reset_hints_builder: NullifierReadRequestResetHintsBuilder,
+ nullifier_read_request_hints_builder: NullifierReadRequestHintsBuilder,
+ nullifier_non_existent_read_request_hints_builder: NullifierNonExistentReadRequestHintsBuilder,
}
impl PublicKernelTailCircuitPrivateInputsBuilder {
pub fn new() -> Self {
let previous_kernel = PreviousKernelDataBuilder::new(true);
+ let mut nullifier_non_existent_read_request_hints_builder = NullifierNonExistentReadRequestHintsBuilder::new();
- PublicKernelTailCircuitPrivateInputsBuilder {
+ let mut builder = PublicKernelTailCircuitPrivateInputsBuilder {
previous_kernel,
- nullifier_read_request_reset_hints_builder: NullifierReadRequestResetHintsBuilder::new(MAX_NULLIFIER_READ_REQUESTS_PER_TX)
- }
+ nullifier_read_request_hints_builder: NullifierReadRequestHintsBuilder::new(MAX_NULLIFIER_READ_REQUESTS_PER_TX),
+ nullifier_non_existent_read_request_hints_builder
+ };
+ builder.set_nullifiers_for_non_existent_read_request_hints();
+ builder
+ }
+
+ pub fn with_nullifier_tree(&mut self) -> Self {
+ let nullifier_tree = build_nullifier_tree();
+ self.previous_kernel.historical_header.state.partial.nullifier_tree.root = nullifier_tree.get_root();
+ self.nullifier_non_existent_read_request_hints_builder.set_nullifier_tree(nullifier_tree);
+ *self
+ }
+
+ pub fn add_nullifier(&mut self, unsiloed_nullifier: Field) {
+ self.previous_kernel.add_nullifier(unsiloed_nullifier);
+ self.set_nullifiers_for_non_existent_read_request_hints();
}
pub fn append_nullifiers(&mut self, num_nullifiers: u64) {
self.previous_kernel.append_new_nullifiers_from_public(num_nullifiers);
+ self.set_nullifiers_for_non_existent_read_request_hints();
}
pub fn append_nullifiers_non_revertible(&mut self, num_nullifiers: u64) {
self.previous_kernel.append_new_nullifiers_non_revertible_from_public(num_nullifiers);
+ self.set_nullifiers_for_non_existent_read_request_hints();
+ }
+
+ fn set_nullifiers_for_non_existent_read_request_hints(&mut self) {
+ let previous_kernel_public_inputs = self.previous_kernel.to_public_kernel_data().public_inputs;
+ let nullifiers = array_concat(
+ previous_kernel_public_inputs.end_non_revertible.new_nullifiers,
+ previous_kernel_public_inputs.end.new_nullifiers
+ );
+ self.nullifier_non_existent_read_request_hints_builder.set_nullifiers(nullifiers);
}
pub fn add_nullifier_pending_read(&mut self, nullifier_index: u64) {
let read_request_index = self.previous_kernel.add_read_request_for_pending_nullifier(nullifier_index);
- let hint_index = self.nullifier_read_request_reset_hints_builder.pending_read_hints.len();
+ let hint_index = self.nullifier_read_request_hints_builder.pending_read_hints.len();
let pending_value_index = nullifier_index + self.previous_kernel.end_non_revertible.new_nullifiers.len();
let hint = PendingReadHint { read_request_index, pending_value_index };
- self.nullifier_read_request_reset_hints_builder.pending_read_hints.push(hint);
- self.nullifier_read_request_reset_hints_builder.read_request_statuses[read_request_index] = ReadRequestStatus { state: ReadRequestState.PENDING, hint_index };
+ self.nullifier_read_request_hints_builder.pending_read_hints.push(hint);
+ self.nullifier_read_request_hints_builder.read_request_statuses[read_request_index] = ReadRequestStatus { state: ReadRequestState.PENDING, hint_index };
}
pub fn add_nullifier_pending_read_non_revertible(&mut self, nullifier_index_offset_one: u64) {
let nullifier_index = nullifier_index_offset_one + 1; // + 1 is for the first nullifier
let read_request_index = self.previous_kernel.add_read_request_for_pending_nullifier_non_revertible(nullifier_index);
- let hint_index = self.nullifier_read_request_reset_hints_builder.pending_read_hints.len();
+ let hint_index = self.nullifier_read_request_hints_builder.pending_read_hints.len();
let hint = PendingReadHint { read_request_index, pending_value_index: nullifier_index };
- self.nullifier_read_request_reset_hints_builder.pending_read_hints.push(hint);
- self.nullifier_read_request_reset_hints_builder.read_request_statuses[read_request_index] = ReadRequestStatus { state: ReadRequestState.PENDING, hint_index };
+ self.nullifier_read_request_hints_builder.pending_read_hints.push(hint);
+ self.nullifier_read_request_hints_builder.read_request_statuses[read_request_index] = ReadRequestStatus { state: ReadRequestState.PENDING, hint_index };
+ }
+
+ pub fn read_non_existent_nullifier(&mut self, unsiloed_nullifier: Field) {
+ self.previous_kernel.add_non_existent_read_request_for_nullifier(unsiloed_nullifier);
+ let siloed_nullifier = silo_nullifier(
+ self.previous_kernel.storage_contract_address,
+ unsiloed_nullifier
+ );
+ self.nullifier_non_existent_read_request_hints_builder.add_value_read(siloed_nullifier);
}
pub fn execute(&mut self) -> PublicKernelCircuitPublicInputs {
@@ -138,7 +239,8 @@ mod tests {
let kernel = PublicKernelTailCircuitPrivateInputs {
previous_kernel,
- nullifier_read_request_reset_hints: self.nullifier_read_request_reset_hints_builder.to_hints()
+ nullifier_read_request_hints: self.nullifier_read_request_hints_builder.to_hints(),
+ nullifier_non_existent_read_request_hints: self.nullifier_non_existent_read_request_hints_builder.to_hints()
};
kernel.public_kernel_tail()
@@ -154,9 +256,10 @@ mod tests {
}
#[test]
- fn public_kernel_circuit_tail_succeeds() {
+ unconstrained fn public_kernel_circuit_tail_succeeds() {
let mut builder = PublicKernelTailCircuitPrivateInputsBuilder::new();
builder.succeeded();
+ // TODO: Check the values in public inputs.
}
#[test]
@@ -194,9 +297,9 @@ mod tests {
builder.append_nullifiers(3);
builder.add_nullifier_pending_read(1);
- let mut hint = builder.nullifier_read_request_reset_hints_builder.pending_read_hints.pop();
+ let mut hint = builder.nullifier_read_request_hints_builder.pending_read_hints.pop();
hint.pending_value_index -= 1;
- builder.nullifier_read_request_reset_hints_builder.pending_read_hints.push(hint);
+ builder.nullifier_read_request_hints_builder.pending_read_hints.push(hint);
builder.failed();
}
@@ -214,4 +317,32 @@ mod tests {
builder.failed();
}
+
+ // TODO: Add tests for reading (non-existent) settled values.
+
+ #[test]
+ unconstrained fn nullifier_non_existent_read_request() {
+ let mut builder = PublicKernelTailCircuitPrivateInputsBuilder::new().with_nullifier_tree();
+
+ builder.add_nullifier(3);
+ builder.add_nullifier(1);
+ builder.add_nullifier(9);
+
+ builder.read_non_existent_nullifier(8);
+
+ builder.succeeded();
+ }
+
+ #[test(should_fail_with="Value exists in pending set")]
+ unconstrained fn nullifier_non_existent_read_request_failed_read_exist() {
+ let mut builder = PublicKernelTailCircuitPrivateInputsBuilder::new().with_nullifier_tree();
+
+ builder.add_nullifier(3);
+ builder.add_nullifier(1);
+ builder.add_nullifier(9);
+
+ builder.read_non_existent_nullifier(1);
+
+ builder.succeeded();
+ }
}
diff --git a/noir-projects/noir-protocol-circuits/crates/reset-kernel-lib/src/lib.nr b/noir-projects/noir-protocol-circuits/crates/reset-kernel-lib/src/lib.nr
index 8e41dc16d8d..07a22b9eb44 100644
--- a/noir-projects/noir-protocol-circuits/crates/reset-kernel-lib/src/lib.nr
+++ b/noir-projects/noir-protocol-circuits/crates/reset-kernel-lib/src/lib.nr
@@ -1,6 +1,10 @@
+use non_existent_read_request_reset::reset_non_existent_read_requests;
+use nullifier_non_existent_read_request_reset::NullifierNonExistentReadRequestHints;
+use nullifier_read_request_reset::NullifierReadRequestHints;
use read_request_reset::reset_read_requests;
-use nullifier_read_request_reset::NullifierReadRequestResetHints;
-use nullifier_read_request_reset::NullifierReadRequestResetHintsBuilder;
-mod read_request_reset;
+mod non_existent_read_request_reset;
+mod nullifier_non_existent_read_request_reset;
mod nullifier_read_request_reset;
+mod read_request_reset;
+mod tests;
diff --git a/noir-projects/noir-protocol-circuits/crates/reset-kernel-lib/src/non_existent_read_request_reset.nr b/noir-projects/noir-protocol-circuits/crates/reset-kernel-lib/src/non_existent_read_request_reset.nr
new file mode 100644
index 00000000000..4beeee665ae
--- /dev/null
+++ b/noir-projects/noir-protocol-circuits/crates/reset-kernel-lib/src/non_existent_read_request_reset.nr
@@ -0,0 +1,354 @@
+use dep::types::{
+ abis::{side_effect::OrderedValue, read_request::ReadRequestContext},
+ merkle_tree::{assert_check_non_membership, IndexedTreeLeafPreimage, MembershipWitness},
+ traits::{Empty, is_empty}
+};
+
+trait NonMembershipHint where LEAF_PREIMAGE: IndexedTreeLeafPreimage {
+ fn low_leaf_preimage(self) -> LEAF_PREIMAGE;
+ fn membership_witness(self) -> MembershipWitness;
+}
+
+fn check_no_matching_pending_value(
+ read_request: ReadRequestContext,
+ sorted_pending_values: BoundedVec,
+ next_value_index: u64
+) -> bool where T: OrderedValue {
+ if next_value_index == sorted_pending_values.len() {
+ let highest_value = sorted_pending_values.get_unchecked(sorted_pending_values.len() - 1).value();
+ highest_value.lt(read_request.value)
+ } else {
+ let next_value = sorted_pending_values.get_unchecked(next_value_index).value();
+ let is_less_than_next = read_request.value.lt(next_value);
+ let is_greater_than_prev = if next_value_index == 0 {
+ true
+ } else {
+ let prev_value = sorted_pending_values.get_unchecked(next_value_index - 1).value();
+ prev_value.lt(read_request.value)
+ };
+ is_less_than_next & is_greater_than_prev
+ }
+}
+
+fn check_is_read_before_pending_value(
+ read_request: ReadRequestContext,
+ sorted_pending_values: BoundedVec,
+ next_value_index: u64
+) -> bool where T: OrderedValue {
+ if next_value_index == sorted_pending_values.len() {
+ false
+ } else {
+ let pending = sorted_pending_values.get_unchecked(next_value_index);
+ if pending.value() == read_request.value {
+ assert(read_request.counter < pending.counter(), "Value exists in pending set");
+ true
+ } else {
+ false
+ }
+ }
+}
+
+// Unlike regular read requests, which can be reset at any time between two function executions.
+// Non existent read requests can only be verified at the end, after all pending values are present.
+// The values in read_requests and in sorted_pending_values should've been siloed before calling this.
+pub fn reset_non_existent_read_requests(
+ siloed_read_requests: [ReadRequestContext; N],
+ non_membership_hints: [NON_MEMBERSHIP_HINT; N],
+ tree_root: Field,
+ sorted_pending_values: BoundedVec,
+ next_pending_value_indices: [u64; N]
+) where
+ T: OrderedValue,
+ NON_MEMBERSHIP_HINT: NonMembershipHint,
+ LEAF_PREIMAGE: IndexedTreeLeafPreimage {
+ for i in 0..siloed_read_requests.len() {
+ let read_request = siloed_read_requests[i];
+ if !is_empty(read_request) {
+ // Verify that it's not in the tree.
+ let hint = non_membership_hints[i];
+ assert_check_non_membership(
+ read_request.value,
+ hint.low_leaf_preimage(),
+ hint.membership_witness(),
+ tree_root
+ );
+
+ // Verify that its value is either not in the pending set, or is created after the read.
+ let next_value_index = next_pending_value_indices[i];
+ assert(
+ next_value_index <= sorted_pending_values.len(), "Next pending value index out of bounds"
+ );
+ let no_matching_value = check_no_matching_pending_value(read_request, sorted_pending_values, next_value_index);
+ let is_read_before_value = check_is_read_before_pending_value(read_request, sorted_pending_values, next_value_index);
+ assert(no_matching_value | is_read_before_value, "Invalid next pending value index");
+ }
+ }
+}
+
+mod tests {
+ use crate::non_existent_read_request_reset::{NonMembershipHint, reset_non_existent_read_requests};
+
+ use dep::types::{
+ address::AztecAddress, abis::{read_request::ReadRequestContext, side_effect::SideEffect},
+ merkle_tree::{leaf_preimage::IndexedTreeLeafPreimage, membership::MembershipWitness},
+ hash::silo_nullifier, tests::merkle_tree_utils::NonEmptyMerkleTree
+ };
+
+ struct TestLeafPreimage {
+ value: Field,
+ next_value: Field,
+ }
+
+ impl IndexedTreeLeafPreimage for TestLeafPreimage {
+ fn get_key(self) -> Field {
+ self.value
+ }
+
+ fn get_next_key(self) -> Field {
+ self.next_value
+ }
+
+ fn as_leaf(self) -> Field {
+ self.value * 100
+ }
+ }
+
+ struct TestNonMembershipHint {
+ low_leaf_preimage: TestLeafPreimage,
+ membership_witness: MembershipWitness<3>,
+ }
+
+ impl NonMembershipHint<3, TestLeafPreimage> for TestNonMembershipHint {
+ fn low_leaf_preimage(self) -> TestLeafPreimage {
+ self.low_leaf_preimage
+ }
+
+ fn membership_witness(self) -> MembershipWitness<3> {
+ self.membership_witness
+ }
+ }
+
+ global sorted_pending_values = BoundedVec {
+ storage: [
+ SideEffect { value: 5, counter: 17 },
+ SideEffect { value: 15, counter: 8 },
+ SideEffect { value: 25, counter: 11 },
+ SideEffect::empty(),
+ SideEffect::empty(),
+ ],
+ len: 3,
+ };
+
+ global leaf_preimages = [
+ TestLeafPreimage { value: 0, next_value: 10 },
+ TestLeafPreimage { value: 20, next_value: 30 },
+ TestLeafPreimage { value: 30, next_value: 0 },
+ TestLeafPreimage { value: 10, next_value: 20 },
+ ];
+
+ fn build_tree() -> NonEmptyMerkleTree<4, 3, 1, 2> {
+ NonEmptyMerkleTree::new(
+ leaf_preimages.map(|leaf_preimage: TestLeafPreimage| leaf_preimage.as_leaf()),
+ [0; 3],
+ [0; 1],
+ [0; 2]
+ )
+ }
+
+ fn get_non_membership_hints(leaf_indices: [Field; N]) -> ([TestNonMembershipHint; N], Field) {
+ let tree = build_tree();
+ let hints = leaf_indices.map(
+ |leaf_index| TestNonMembershipHint {
+ low_leaf_preimage: leaf_preimages[leaf_index],
+ membership_witness: MembershipWitness { leaf_index, sibling_path: tree.get_sibling_path(leaf_index as u64) }
+ }
+ );
+ let tree_root = tree.get_root();
+ (hints, tree_root)
+ }
+
+ #[test]
+ fn test_reset_non_existent_read_requests_in_range() {
+ let read_requests = [
+ ReadRequestContext { value: 11, counter: 50, contract_address: AztecAddress::zero() },
+ ReadRequestContext { value: 22, counter: 51, contract_address: AztecAddress::zero() },
+ ReadRequestContext { value: 6, counter: 52, contract_address: AztecAddress::zero() }
+ ];
+ let (non_membership_hints, root) = get_non_membership_hints([3, 1, 0]);
+ let next_pending_value_indices = [1, 2, 1];
+ reset_non_existent_read_requests(
+ read_requests,
+ non_membership_hints,
+ root,
+ sorted_pending_values,
+ next_pending_value_indices
+ );
+ }
+
+ #[test]
+ fn test_reset_non_existent_read_requests_less_than_min() {
+ let read_requests = [
+ ReadRequestContext { value: 3, counter: 50, contract_address: AztecAddress::zero() },
+ ReadRequestContext { value: 2, counter: 51, contract_address: AztecAddress::zero() }
+ ];
+ let (non_membership_hints, root) = get_non_membership_hints([0, 0]);
+ let next_pending_value_indices = [0, 0];
+ reset_non_existent_read_requests(
+ read_requests,
+ non_membership_hints,
+ root,
+ sorted_pending_values,
+ next_pending_value_indices
+ );
+ }
+
+ #[test]
+ fn test_reset_non_existent_read_requests_greater_than_max() {
+ let read_requests = [
+ ReadRequestContext { value: 35, counter: 50, contract_address: AztecAddress::zero() },
+ ReadRequestContext { value: 31, counter: 51, contract_address: AztecAddress::zero() }
+ ];
+ let (non_membership_hints, root) = get_non_membership_hints([2, 2]);
+ let next_pending_value_indices = [3, 3];
+ reset_non_existent_read_requests(
+ read_requests,
+ non_membership_hints,
+ root,
+ sorted_pending_values,
+ next_pending_value_indices
+ );
+ }
+
+ #[test]
+ fn test_reset_non_existent_read_requests_read_before_pending_emitted() {
+ let read_requests = [
+ ReadRequestContext { value: 25, counter: 10, contract_address: AztecAddress::zero() },
+ ReadRequestContext { value: 5, counter: 11, contract_address: AztecAddress::zero() }
+ ];
+ let (non_membership_hints, root) = get_non_membership_hints([1, 0]);
+ let next_pending_value_indices = [2, 0];
+ reset_non_existent_read_requests(
+ read_requests,
+ non_membership_hints,
+ root,
+ sorted_pending_values,
+ next_pending_value_indices
+ );
+ }
+
+ #[test(should_fail_with="Cannot check non membership against empty leaf")]
+ fn test_reset_non_existent_read_requests_empty_leaf_failed() {
+ let read_requests = [ReadRequestContext { value: 10, counter: 50, contract_address: AztecAddress::zero() }];
+ let (non_membership_hints, root) = get_non_membership_hints([0]);
+ let mut hint = non_membership_hints[0];
+ hint.low_leaf_preimage = TestLeafPreimage { value: 0, next_value: 0 };
+ let next_pending_value_indices = [1];
+ reset_non_existent_read_requests(
+ read_requests,
+ [hint],
+ root,
+ sorted_pending_values,
+ next_pending_value_indices
+ );
+ }
+
+ #[test(should_fail_with="Low leaf does not exist")]
+ fn test_reset_non_existent_read_requests_invalid_preimage_failed() {
+ let read_requests = [ReadRequestContext { value: 10, counter: 50, contract_address: AztecAddress::zero() }];
+ let (non_membership_hints, root) = get_non_membership_hints([3]);
+ let mut hint = non_membership_hints[0];
+ hint.low_leaf_preimage = TestLeafPreimage { value: 9, next_value: 20 };
+ let next_pending_value_indices = [1];
+ reset_non_existent_read_requests(
+ read_requests,
+ [hint],
+ root,
+ sorted_pending_values,
+ next_pending_value_indices
+ );
+ }
+
+ #[test(should_fail_with="Key is not greater than the low leaf")]
+ fn test_reset_non_existent_read_requests_read_settled_failed() {
+ let read_requests = [ReadRequestContext { value: 10, counter: 50, contract_address: AztecAddress::zero() }];
+ let (non_membership_hints, root) = get_non_membership_hints([3]);
+ let next_pending_value_indices = [1];
+ reset_non_existent_read_requests(
+ read_requests,
+ non_membership_hints,
+ root,
+ sorted_pending_values,
+ next_pending_value_indices
+ );
+ }
+
+ #[test(should_fail_with="Key is not less than the next leaf")]
+ fn test_reset_non_existent_read_requests_invalid_non_membership_hint_failed() {
+ let read_requests = [ReadRequestContext { value: 10, counter: 50, contract_address: AztecAddress::zero() }];
+ let (non_membership_hints, root) = get_non_membership_hints([0]);
+ let next_pending_value_indices = [1];
+ reset_non_existent_read_requests(
+ read_requests,
+ non_membership_hints,
+ root,
+ sorted_pending_values,
+ next_pending_value_indices
+ );
+ }
+
+ #[test(should_fail_with="Value exists in pending set")]
+ fn test_reset_non_existent_read_requests_read_pending_value_failed() {
+ let read_requests = [ReadRequestContext { value: 25, counter: 50, contract_address: AztecAddress::zero() }];
+ let (non_membership_hints, root) = get_non_membership_hints([1]);
+ let next_pending_value_indices = [2];
+ reset_non_existent_read_requests(
+ read_requests,
+ non_membership_hints,
+ root,
+ sorted_pending_values,
+ next_pending_value_indices
+ );
+ }
+
+ #[test(should_fail_with="Invalid next pending value index")]
+ fn test_reset_non_existent_read_requests_wrong_next_pending_index_failed() {
+ let read_requests = [ReadRequestContext { value: 21, counter: 50, contract_address: AztecAddress::zero() }];
+ let (non_membership_hints, root) = get_non_membership_hints([1]);
+ let next_pending_value_indices = [1];
+ reset_non_existent_read_requests(
+ read_requests,
+ non_membership_hints,
+ root,
+ sorted_pending_values,
+ next_pending_value_indices
+ );
+ }
+
+ #[test(should_fail_with="Invalid next pending value index")]
+ fn test_reset_non_existent_read_requests_wrong_max_next_pending_index_failed() {
+ let read_requests = [ReadRequestContext { value: 21, counter: 50, contract_address: AztecAddress::zero() }];
+ let (non_membership_hints, root) = get_non_membership_hints([1]);
+ let next_pending_value_indices = [3];
+ reset_non_existent_read_requests(
+ read_requests,
+ non_membership_hints,
+ root,
+ sorted_pending_values,
+ next_pending_value_indices
+ );
+ }
+
+ #[test(should_fail_with="Next pending value index out of bounds")]
+ fn test_reset_non_existent_read_requests_overflown_index_failed() {
+ let read_requests = [ReadRequestContext { value: 21, counter: 50, contract_address: AztecAddress::zero() }];
+ let (non_membership_hints, root) = get_non_membership_hints([1]);
+ let next_pending_value_indices = [4];
+ reset_non_existent_read_requests(
+ read_requests,
+ non_membership_hints,
+ root,
+ sorted_pending_values,
+ next_pending_value_indices
+ );
+ }
+}
diff --git a/noir-projects/noir-protocol-circuits/crates/reset-kernel-lib/src/nullifier_non_existent_read_request_reset.nr b/noir-projects/noir-protocol-circuits/crates/reset-kernel-lib/src/nullifier_non_existent_read_request_reset.nr
new file mode 100644
index 00000000000..cc76145b18e
--- /dev/null
+++ b/noir-projects/noir-protocol-circuits/crates/reset-kernel-lib/src/nullifier_non_existent_read_request_reset.nr
@@ -0,0 +1,28 @@
+use crate::non_existent_read_request_reset::{NonMembershipHint};
+use dep::types::{
+ abis::{nullifier_leaf_preimage::NullifierLeafPreimage, side_effect::SideEffectLinkedToNoteHash},
+ merkle_tree::{MembershipWitness},
+ constants::{MAX_NEW_NULLIFIERS_PER_TX, MAX_NULLIFIER_NON_EXISTENT_READ_REQUESTS_PER_TX, NULLIFIER_TREE_HEIGHT}
+};
+
+struct NullifierNonMembershipHint {
+ low_leaf_preimage: NullifierLeafPreimage,
+ membership_witness: MembershipWitness,
+}
+
+impl NonMembershipHint for NullifierNonMembershipHint {
+ fn low_leaf_preimage(self) -> NullifierLeafPreimage {
+ self.low_leaf_preimage
+ }
+
+ fn membership_witness(self) -> MembershipWitness {
+ self.membership_witness
+ }
+}
+
+struct NullifierNonExistentReadRequestHints {
+ non_membership_hints: [NullifierNonMembershipHint; MAX_NULLIFIER_NON_EXISTENT_READ_REQUESTS_PER_TX],
+ sorted_pending_values: [SideEffectLinkedToNoteHash; MAX_NEW_NULLIFIERS_PER_TX],
+ sorted_pending_value_index_hints: [u64; MAX_NEW_NULLIFIERS_PER_TX],
+ next_pending_value_indices: [u64; MAX_NULLIFIER_NON_EXISTENT_READ_REQUESTS_PER_TX],
+}
diff --git a/noir-projects/noir-protocol-circuits/crates/reset-kernel-lib/src/nullifier_read_request_reset.nr b/noir-projects/noir-protocol-circuits/crates/reset-kernel-lib/src/nullifier_read_request_reset.nr
index 6524089d74a..6a122e57a2d 100644
--- a/noir-projects/noir-protocol-circuits/crates/reset-kernel-lib/src/nullifier_read_request_reset.nr
+++ b/noir-projects/noir-protocol-circuits/crates/reset-kernel-lib/src/nullifier_read_request_reset.nr
@@ -2,8 +2,9 @@
use crate::read_request_reset::{PendingReadHint, ReadRequestStatus, ReadValueHint, SettledReadHint};
use dep::std::unsafe;
use dep::types::{
- abis::{membership_witness::MembershipWitness, nullifier_leaf_preimage::NullifierLeafPreimage},
- constants::{MAX_NULLIFIER_READ_REQUESTS_PER_TX, NULLIFIER_TREE_HEIGHT}
+ abis::{nullifier_leaf_preimage::NullifierLeafPreimage},
+ constants::{MAX_NULLIFIER_READ_REQUESTS_PER_TX, NULLIFIER_TREE_HEIGHT},
+ merkle_tree::MembershipWitness
};
struct NullifierSettledReadHint {
@@ -36,49 +37,22 @@ impl SettledReadHint for Nullifier
}
}
-struct NullifierReadRequestResetHints {
+struct NullifierReadRequestHints {
read_request_statuses: [ReadRequestStatus; MAX_NULLIFIER_READ_REQUESTS_PER_TX],
pending_read_hints: [PendingReadHint; MAX_NULLIFIER_READ_REQUESTS_PER_TX],
settled_read_hints: [NullifierSettledReadHint; MAX_NULLIFIER_READ_REQUESTS_PER_TX],
}
-struct NullifierReadRequestResetHintsBuilder {
- read_request_statuses: [ReadRequestStatus; MAX_NULLIFIER_READ_REQUESTS_PER_TX],
- pending_read_hints: BoundedVec,
- settled_read_hints: BoundedVec,
-}
-
-impl NullifierReadRequestResetHintsBuilder {
- pub fn new(read_request_len: u64) -> Self {
- NullifierReadRequestResetHintsBuilder {
- read_request_statuses: [ReadRequestStatus::empty(); MAX_NULLIFIER_READ_REQUESTS_PER_TX],
- pending_read_hints: BoundedVec { storage: [PendingReadHint::nada(read_request_len); MAX_NULLIFIER_READ_REQUESTS_PER_TX], len: 0 },
- settled_read_hints: BoundedVec {
- storage: [NullifierSettledReadHint::nada(read_request_len); MAX_NULLIFIER_READ_REQUESTS_PER_TX],
- len: 0
- }
- }
- }
-
- pub fn to_hints(self) -> NullifierReadRequestResetHints {
- NullifierReadRequestResetHints {
- read_request_statuses: self.read_request_statuses,
- pending_read_hints: self.pending_read_hints.storage,
- settled_read_hints: self.settled_read_hints.storage
- }
- }
-}
-
mod tests {
use crate::nullifier_read_request_reset::NullifierSettledReadHint;
use crate::read_request_reset::{PendingReadHint, ReadRequestState, ReadRequestStatus, reset_read_requests};
use dep::types::{
address::AztecAddress,
abis::{
- membership_witness::MembershipWitness, nullifier_leaf_preimage::NullifierLeafPreimage,
- read_request::ReadRequestContext, side_effect::SideEffect
+ nullifier_leaf_preimage::NullifierLeafPreimage, read_request::ReadRequestContext,
+ side_effect::SideEffect
},
- constants::NULLIFIER_TREE_HEIGHT, hash::silo_nullifier,
+ constants::NULLIFIER_TREE_HEIGHT, hash::silo_nullifier, merkle_tree::MembershipWitness,
tests::merkle_tree_utils::NonEmptyMerkleTree
};
diff --git a/noir-projects/noir-protocol-circuits/crates/reset-kernel-lib/src/read_request_reset.nr b/noir-projects/noir-protocol-circuits/crates/reset-kernel-lib/src/read_request_reset.nr
index 2c50bcd46d7..1432df9ef18 100644
--- a/noir-projects/noir-protocol-circuits/crates/reset-kernel-lib/src/read_request_reset.nr
+++ b/noir-projects/noir-protocol-circuits/crates/reset-kernel-lib/src/read_request_reset.nr
@@ -1,8 +1,7 @@
// This will be moved to a separate Read Request Reset Circuit.
use dep::types::{
- abis::{membership_witness::MembershipWitness, read_request::ReadRequestContext, side_effect::OrderedValue},
- hash::{silo_nullifier, root_from_sibling_path}, merkle_tree::leaf_preimage::LeafPreimage,
- traits::{Empty, is_empty}
+ abis::{read_request::ReadRequestContext, side_effect::OrderedValue}, hash::{silo_nullifier},
+ merkle_tree::{assert_check_membership, LeafPreimage, MembershipWitness}, traits::{Empty, is_empty}
};
struct ReadRequestStateEnum {
@@ -84,7 +83,7 @@ fn validate_pending_read_requests(
read_requests: [ReadRequestContext; READ_REQUEST_LEN],
hints: [H; NUM_SETTLED_READS],
- historical_tree_root: Field
+ tree_root: Field
) where
H: SettledReadHint + ReadValueHint,
LEAF_PREIMAGE: LeafPreimage {
@@ -99,8 +98,7 @@ fn validate_settled_read_requests BoundedVec where
P: OrderedValue,
H: SettledReadHint + ReadValueHint,
LEAF_PREIMAGE: LeafPreimage {
validate_pending_read_requests(read_requests, pending_values, pending_read_hints);
- validate_settled_read_requests(read_requests, settled_read_hints, historical_tree_root);
+ validate_settled_read_requests(read_requests, settled_read_hints, tree_root);
propagate_unverified_read_requests(
read_requests,
@@ -163,9 +161,8 @@ mod tests {
};
use dep::std::{hash::pedersen_hash, unsafe};
use dep::types::{
- address::AztecAddress,
- abis::{membership_witness::MembershipWitness, read_request::ReadRequestContext, side_effect::SideEffect},
- merkle_tree::leaf_preimage::LeafPreimage, hash::silo_nullifier,
+ address::AztecAddress, abis::{read_request::ReadRequestContext, side_effect::SideEffect},
+ merkle_tree::{LeafPreimage, MembershipWitness}, hash::silo_nullifier,
tests::merkle_tree_utils::NonEmptyMerkleTree
};
@@ -309,7 +306,7 @@ mod tests {
validate_settled_read_requests(read_requests, hints, tree_root);
}
- #[test(should_fail_with="Tree root mismatch for read request")]
+ #[test(should_fail_with="membership check failed")]
fn test_validate_settled_read_requests_wrong_witness_fails() {
let (settled_hints, tree_root) = get_settled_read_hints();
let mut hint = settled_hints[0];
diff --git a/noir-projects/noir-protocol-circuits/crates/reset-kernel-lib/src/tests.nr b/noir-projects/noir-protocol-circuits/crates/reset-kernel-lib/src/tests.nr
new file mode 100644
index 00000000000..9ecd400c180
--- /dev/null
+++ b/noir-projects/noir-protocol-circuits/crates/reset-kernel-lib/src/tests.nr
@@ -0,0 +1,2 @@
+mod nullifier_non_existent_read_request_hints_builder;
+mod nullifier_read_request_hints_builder;
diff --git a/noir-projects/noir-protocol-circuits/crates/reset-kernel-lib/src/tests/nullifier_non_existent_read_request_hints_builder.nr b/noir-projects/noir-protocol-circuits/crates/reset-kernel-lib/src/tests/nullifier_non_existent_read_request_hints_builder.nr
new file mode 100644
index 00000000000..d4de21463a7
--- /dev/null
+++ b/noir-projects/noir-protocol-circuits/crates/reset-kernel-lib/src/tests/nullifier_non_existent_read_request_hints_builder.nr
@@ -0,0 +1,80 @@
+use crate::nullifier_non_existent_read_request_reset::{NullifierNonMembershipHint, NullifierNonExistentReadRequestHints};
+use dep::types::{
+ abis::{nullifier_leaf_preimage::NullifierLeafPreimage, side_effect::SideEffectLinkedToNoteHash},
+ constants::{
+ MAX_NEW_NULLIFIERS_PER_TX, MAX_NULLIFIER_NON_EXISTENT_READ_REQUESTS_PER_TX, NULLIFIER_TREE_HEIGHT,
+ NULLIFIER_SUBTREE_SIBLING_PATH_LENGTH, NULLIFIER_SUBTREE_HEIGHT
+},
+ merkle_tree::MembershipWitness,
+ tests::{merkle_tree_utils::NonEmptyMerkleTree, sort::sort_get_sorted_hints},
+ utils::{arrays::find_index, field::full_field_greater_than}
+};
+use dep::std::unsafe;
+
+struct NullifierNonExistentReadRequestHintsBuilder {
+ nullifier_tree: NonEmptyMerkleTree,
+ non_membership_hints: BoundedVec,
+ read_values: BoundedVec,
+ pending_nullifiers: [SideEffectLinkedToNoteHash; MAX_NEW_NULLIFIERS_PER_TX],
+}
+
+impl NullifierNonExistentReadRequestHintsBuilder {
+ pub fn new() -> Self {
+ NullifierNonExistentReadRequestHintsBuilder {
+ nullifier_tree: unsafe::zeroed(),
+ non_membership_hints: BoundedVec::new(),
+ read_values: BoundedVec::new(),
+ pending_nullifiers: [SideEffectLinkedToNoteHash::empty(); MAX_NEW_NULLIFIERS_PER_TX]
+ }
+ }
+
+ pub fn set_nullifier_tree(
+ &mut self,
+ tree: NonEmptyMerkleTree
+ ) {
+ self.nullifier_tree = tree;
+ }
+
+ pub fn set_nullifiers(
+ &mut self,
+ nullifiers: [SideEffectLinkedToNoteHash; MAX_NEW_NULLIFIERS_PER_TX]
+ ) {
+ self.pending_nullifiers = nullifiers;
+ }
+
+ pub fn add_value_read(&mut self, siloed_value: Field) {
+ self.read_values.push(siloed_value);
+
+ // There are only two pre-existing nullifiers in the tree: [0, 100], generated in public_kernel_tail::tests.
+ // Assuming the siloed_value is always greater than 100.
+ let hint = NullifierNonMembershipHint {
+ low_leaf_preimage: NullifierLeafPreimage { nullifier: 100, next_nullifier: 0, next_index: 0 },
+ membership_witness: MembershipWitness { leaf_index: 1, sibling_path: self.nullifier_tree.get_sibling_path(1) }
+ };
+ self.non_membership_hints.push(hint);
+ }
+
+ pub fn to_hints(self) -> NullifierNonExistentReadRequestHints {
+ let sorted_result = sort_get_sorted_hints(
+ self.pending_nullifiers,
+ |a: SideEffectLinkedToNoteHash, b: SideEffectLinkedToNoteHash| a.value.lt(b.value)
+ );
+ let sorted_pending_values = sorted_result.sorted_array;
+ let sorted_pending_value_index_hints = sorted_result.sorted_index_hints;
+
+ let mut next_pending_value_indices = [0; MAX_NULLIFIER_NON_EXISTENT_READ_REQUESTS_PER_TX];
+ for i in 0..MAX_NULLIFIER_NON_EXISTENT_READ_REQUESTS_PER_TX {
+ if i < self.read_values.len() {
+ let value = self.read_values.get_unchecked(i);
+ next_pending_value_indices[i] = find_index(sorted_pending_values, |v: SideEffectLinkedToNoteHash| !v.value.lt(value));
+ }
+ }
+
+ NullifierNonExistentReadRequestHints {
+ non_membership_hints: self.non_membership_hints.storage,
+ sorted_pending_values,
+ sorted_pending_value_index_hints,
+ next_pending_value_indices
+ }
+ }
+}
diff --git a/noir-projects/noir-protocol-circuits/crates/reset-kernel-lib/src/tests/nullifier_read_request_hints_builder.nr b/noir-projects/noir-protocol-circuits/crates/reset-kernel-lib/src/tests/nullifier_read_request_hints_builder.nr
new file mode 100644
index 00000000000..355106e7978
--- /dev/null
+++ b/noir-projects/noir-protocol-circuits/crates/reset-kernel-lib/src/tests/nullifier_read_request_hints_builder.nr
@@ -0,0 +1,32 @@
+use crate::{
+ nullifier_read_request_reset::{NullifierSettledReadHint, NullifierReadRequestHints},
+ read_request_reset::{PendingReadHint, ReadRequestStatus}
+};
+use dep::types::constants::MAX_NULLIFIER_READ_REQUESTS_PER_TX;
+
+struct NullifierReadRequestHintsBuilder {
+ read_request_statuses: [ReadRequestStatus; MAX_NULLIFIER_READ_REQUESTS_PER_TX],
+ pending_read_hints: BoundedVec,
+ settled_read_hints: BoundedVec,
+}
+
+impl NullifierReadRequestHintsBuilder {
+ pub fn new(read_request_len: u64) -> Self {
+ NullifierReadRequestHintsBuilder {
+ read_request_statuses: [ReadRequestStatus::empty(); MAX_NULLIFIER_READ_REQUESTS_PER_TX],
+ pending_read_hints: BoundedVec { storage: [PendingReadHint::nada(read_request_len); MAX_NULLIFIER_READ_REQUESTS_PER_TX], len: 0 },
+ settled_read_hints: BoundedVec {
+ storage: [NullifierSettledReadHint::nada(read_request_len); MAX_NULLIFIER_READ_REQUESTS_PER_TX],
+ len: 0
+ }
+ }
+ }
+
+ pub fn to_hints(self) -> NullifierReadRequestHints {
+ NullifierReadRequestHints {
+ read_request_statuses: self.read_request_statuses,
+ pending_read_hints: self.pending_read_hints.storage,
+ settled_read_hints: self.settled_read_hints.storage
+ }
+ }
+}
diff --git a/noir-projects/noir-protocol-circuits/crates/rollup-lib/src/base/base_rollup_inputs.nr b/noir-projects/noir-protocol-circuits/crates/rollup-lib/src/base/base_rollup_inputs.nr
index b554d16bc9e..b06afdce361 100644
--- a/noir-projects/noir-protocol-circuits/crates/rollup-lib/src/base/base_rollup_inputs.nr
+++ b/noir-projects/noir-protocol-circuits/crates/rollup-lib/src/base/base_rollup_inputs.nr
@@ -9,10 +9,7 @@ use crate::{
use dep::types::{
abis::{
append_only_tree_snapshot::AppendOnlyTreeSnapshot,
- membership_witness::{
- ArchiveRootMembershipWitness, MembershipWitness, NullifierMembershipWitness,
- PublicDataMembershipWitness
-},
+ membership_witness::{ArchiveRootMembershipWitness, NullifierMembershipWitness, PublicDataMembershipWitness},
nullifier_leaf_preimage::NullifierLeafPreimage, public_data_update_request::PublicDataUpdateRequest,
public_data_read::PublicDataRead, kernel_data::RollupKernelData,
side_effect::{SideEffect, SideEffectLinkedToNoteHash}, accumulated_data::CombinedAccumulatedData
@@ -29,8 +26,10 @@ use dep::types::{
MAX_NON_REVERTIBLE_NOTE_HASHES_PER_TX, MAX_REVERTIBLE_NULLIFIERS_PER_TX,
MAX_NON_REVERTIBLE_NULLIFIERS_PER_TX
},
- hash::assert_check_membership,
- merkle_tree::{append_only_tree, calculate_empty_tree_root, calculate_subtree, indexed_tree},
+ merkle_tree::{
+ append_only_tree, assert_check_membership, calculate_empty_tree_root, calculate_subtree_root,
+ indexed_tree, MembershipWitness
+},
mocked::{AggregationObject, Proof}, partial_state_reference::PartialStateReference,
public_data_tree_leaf::PublicDataTreeLeaf,
public_data_tree_leaf_preimage::PublicDataTreeLeafPreimage,
@@ -118,7 +117,7 @@ impl BaseRollupInputs {
// TODO(Kev): This should say calculate_commitments_subtree_root
// Cpp code says calculate_commitments_subtree, so I'm leaving it as is for now
fn calculate_commitments_subtree(self) -> Field {
- calculate_subtree(self.kernel_data.public_inputs.end.new_note_hashes.map(|c: SideEffect| c.value))
+ calculate_subtree_root(self.kernel_data.public_inputs.end.new_note_hashes.map(|c: SideEffect| c.value))
}
fn check_nullifier_tree_non_membership_and_insert_to_tree(self) -> AppendOnlyTreeSnapshot {
@@ -137,9 +136,6 @@ impl BaseRollupInputs {
}
}
),
- |a: Field, b: Field| {a == b}, // Nullifier equals
- |nullifier: Field| {nullifier == 0}, // Nullifier is zero
- |leaf: NullifierLeafPreimage| {leaf.hash()}, // Hash leaf
|low_leaf: NullifierLeafPreimage, nullifier: Field| { // Is valid low leaf
let is_less_than_nullifier = full_field_less_than(low_leaf.nullifier, nullifier);
let is_next_greater_than = full_field_less_than(nullifier, low_leaf.next_nullifier);
@@ -169,7 +165,7 @@ impl BaseRollupInputs {
}
fn create_nullifier_subtree(leaves: [NullifierLeafPreimage; N]) -> Field {
- calculate_subtree(leaves.map(|leaf:NullifierLeafPreimage| leaf.hash()))
+ calculate_subtree_root(leaves.map(|leaf:NullifierLeafPreimage| leaf.hash()))
}
fn validate_and_process_public_state(self) -> AppendOnlyTreeSnapshot {
@@ -265,9 +261,6 @@ fn insert_public_data_update_requests(
}
}
),
- |a: PublicDataTreeLeaf, b: PublicDataTreeLeaf| a.eq(b), // PublicDataTreeLeaf equals
- |write: PublicDataTreeLeaf| write.is_empty(), // PublicDataTreeLeaf is_empty
- |preimage: PublicDataTreeLeafPreimage| preimage.hash(), // Hash preimage
|low_preimage: PublicDataTreeLeafPreimage, write: PublicDataTreeLeaf| { // Is valid low preimage
let is_update = low_preimage.slot == write.slot;
let is_low_empty = low_preimage.is_empty();
@@ -406,12 +399,11 @@ mod tests {
MAX_NEW_L2_TO_L1_MSGS_PER_TX
},
contract_class_id::ContractClassId, partial_state_reference::PartialStateReference,
- hash::assert_check_membership, merkle_tree::{calculate_empty_tree_root, calculate_subtree},
public_data_tree_leaf::PublicDataTreeLeaf,
public_data_tree_leaf_preimage::PublicDataTreeLeafPreimage,
tests::{
kernel_data_builder::PreviousKernelDataBuilder,
- merkle_tree_utils::{NonEmptyMerkleTree, compute_zero_hashes}
+ merkle_tree_utils::{NonEmptyMerkleTree, compute_zero_hashes}, sort::sort_high_to_low
},
utils::{field::full_field_less_than, uint256::U256}
};
@@ -421,28 +413,10 @@ mod tests {
value: Field,
}
- struct SortedTuple {
- value: T,
- original_index: u64,
- }
-
global MAX_NEW_NULLIFIERS_PER_TEST = 4;
global MAX_PUBLIC_DATA_WRITES_PER_TEST = 2;
global MAX_PUBLIC_DATA_READS_PER_TEST = 2;
- fn sort_high_to_low(values: [T; N], is_less_than: fn(T, T) -> bool) -> [SortedTuple; N] where T: Eq {
- let mut sorted_tuples = [SortedTuple { value: values[0], original_index: 0 }; N];
-
- for i in 0..N {
- sorted_tuples[i] = SortedTuple {
- value: values[i],
- original_index: i,
- };
- }
-
- sorted_tuples.sort_via(|a: SortedTuple, b: SortedTuple| (b.value == a.value) | is_less_than(b.value, a.value))
- }
-
fn update_public_data_tree(
public_data_tree: &mut NonEmptyMerkleTree,
kernel_data: &mut RollupKernelData,
diff --git a/noir-projects/noir-protocol-circuits/crates/rollup-lib/src/root/root_rollup_inputs.nr b/noir-projects/noir-protocol-circuits/crates/rollup-lib/src/root/root_rollup_inputs.nr
index 623014f668c..508e4d9ed44 100644
--- a/noir-projects/noir-protocol-circuits/crates/rollup-lib/src/root/root_rollup_inputs.nr
+++ b/noir-projects/noir-protocol-circuits/crates/rollup-lib/src/root/root_rollup_inputs.nr
@@ -9,7 +9,7 @@ use dep::types::{
L1_TO_L2_MSG_SUBTREE_SIBLING_PATH_LENGTH, ARCHIVE_HEIGHT
},
header::Header, content_commitment::ContentCommitment,
- merkle_tree::{append_only_tree, calculate_subtree, calculate_empty_tree_root},
+ merkle_tree::{append_only_tree, calculate_subtree_root, calculate_empty_tree_root},
state_reference::StateReference
};
@@ -41,7 +41,7 @@ impl RootRollupInputs {
// Check correct l1 to l2 tree given
// Compute subtree inserting l1 to l2 messages
- let l1_to_l2_subtree_root = calculate_subtree(self.new_l1_to_l2_messages);
+ let l1_to_l2_subtree_root = calculate_subtree_root(self.new_l1_to_l2_messages);
// Insert subtree into the l1 to l2 data tree
let empty_l1_to_l2_subtree_root = calculate_empty_tree_root(L1_TO_L2_MSG_SUBTREE_HEIGHT);
diff --git a/noir-projects/noir-protocol-circuits/crates/types/src/abis/accumulated_data/accumulated_non_revertible_data_builder.nr b/noir-projects/noir-protocol-circuits/crates/types/src/abis/accumulated_data/accumulated_non_revertible_data_builder.nr
index f138d7e1f26..3499620d06c 100644
--- a/noir-projects/noir-protocol-circuits/crates/types/src/abis/accumulated_data/accumulated_non_revertible_data_builder.nr
+++ b/noir-projects/noir-protocol-circuits/crates/types/src/abis/accumulated_data/accumulated_non_revertible_data_builder.nr
@@ -12,7 +12,8 @@ use crate::{
use crate::constants::{
MAX_NON_REVERTIBLE_NOTE_HASHES_PER_TX, MAX_NON_REVERTIBLE_NULLIFIERS_PER_TX,
MAX_NON_REVERTIBLE_PUBLIC_CALL_STACK_LENGTH_PER_TX, MAX_NON_REVERTIBLE_PUBLIC_DATA_READS_PER_TX,
- MAX_NON_REVERTIBLE_PUBLIC_DATA_UPDATE_REQUESTS_PER_TX, MAX_NULLIFIER_READ_REQUESTS_PER_TX
+ MAX_NON_REVERTIBLE_PUBLIC_DATA_UPDATE_REQUESTS_PER_TX, MAX_NULLIFIER_READ_REQUESTS_PER_TX,
+ MAX_NULLIFIER_NON_EXISTENT_READ_REQUESTS_PER_TX
};
struct AccumulatedNonRevertibleDataBuilder {
@@ -20,6 +21,7 @@ struct AccumulatedNonRevertibleDataBuilder {
new_nullifiers: BoundedVec,
public_call_stack: BoundedVec,
nullifier_read_requests: BoundedVec,
+ nullifier_non_existent_read_requests: BoundedVec,
public_data_update_requests: BoundedVec,
public_data_reads: BoundedVec,
}
@@ -38,6 +40,7 @@ impl AccumulatedNonRevertibleDataBuilder {
new_nullifiers: self.new_nullifiers.storage,
public_call_stack: self.public_call_stack.storage,
nullifier_read_requests: self.nullifier_read_requests.storage,
+ nullifier_non_existent_read_requests: self.nullifier_non_existent_read_requests.storage,
public_data_update_requests: self.public_data_update_requests.storage,
public_data_reads: self.public_data_reads.storage
}
diff --git a/noir-projects/noir-protocol-circuits/crates/types/src/abis/accumulated_data/public_accumulated_non_revertible_data.nr b/noir-projects/noir-protocol-circuits/crates/types/src/abis/accumulated_data/public_accumulated_non_revertible_data.nr
index 697c307e656..c8175fbd7c0 100644
--- a/noir-projects/noir-protocol-circuits/crates/types/src/abis/accumulated_data/public_accumulated_non_revertible_data.nr
+++ b/noir-projects/noir-protocol-circuits/crates/types/src/abis/accumulated_data/public_accumulated_non_revertible_data.nr
@@ -8,7 +8,8 @@ use crate::{
use crate::constants::{
MAX_NON_REVERTIBLE_NOTE_HASHES_PER_TX, MAX_NON_REVERTIBLE_NULLIFIERS_PER_TX,
MAX_NON_REVERTIBLE_PUBLIC_CALL_STACK_LENGTH_PER_TX, MAX_NON_REVERTIBLE_PUBLIC_DATA_READS_PER_TX,
- MAX_NON_REVERTIBLE_PUBLIC_DATA_UPDATE_REQUESTS_PER_TX, MAX_NULLIFIER_READ_REQUESTS_PER_TX
+ MAX_NON_REVERTIBLE_PUBLIC_DATA_UPDATE_REQUESTS_PER_TX, MAX_NULLIFIER_READ_REQUESTS_PER_TX,
+ MAX_NULLIFIER_NON_EXISTENT_READ_REQUESTS_PER_TX
};
use dep::std::unsafe;
@@ -21,6 +22,7 @@ struct PublicAccumulatedNonRevertibleData {
new_nullifiers: [SideEffectLinkedToNoteHash; MAX_NON_REVERTIBLE_NULLIFIERS_PER_TX],
public_call_stack: [CallRequest; MAX_NON_REVERTIBLE_PUBLIC_CALL_STACK_LENGTH_PER_TX],
nullifier_read_requests: [ReadRequestContext; MAX_NULLIFIER_READ_REQUESTS_PER_TX],
+ nullifier_non_existent_read_requests: [ReadRequestContext; MAX_NULLIFIER_NON_EXISTENT_READ_REQUESTS_PER_TX],
public_data_update_requests: [PublicDataUpdateRequest; MAX_NON_REVERTIBLE_PUBLIC_DATA_UPDATE_REQUESTS_PER_TX],
public_data_reads: [PublicDataRead; MAX_NON_REVERTIBLE_PUBLIC_DATA_READS_PER_TX],
}
diff --git a/noir-projects/noir-protocol-circuits/crates/types/src/abis/accumulated_data/public_accumulated_revertible_data.nr b/noir-projects/noir-protocol-circuits/crates/types/src/abis/accumulated_data/public_accumulated_revertible_data.nr
index 954cb1b69a0..2b310a2ae87 100644
--- a/noir-projects/noir-protocol-circuits/crates/types/src/abis/accumulated_data/public_accumulated_revertible_data.nr
+++ b/noir-projects/noir-protocol-circuits/crates/types/src/abis/accumulated_data/public_accumulated_revertible_data.nr
@@ -13,6 +13,7 @@ use crate::constants::{
MAX_REVERTIBLE_PUBLIC_DATA_UPDATE_REQUESTS_PER_TX, MAX_REVERTIBLE_PUBLIC_DATA_READS_PER_TX
};
+// TODO - Requests for checking data should not be revertible.
struct PublicAccumulatedRevertibleData {
note_hash_read_requests: [SideEffect; MAX_NOTE_HASH_READ_REQUESTS_PER_TX],
nullifier_read_requests: [ReadRequestContext; MAX_NULLIFIER_READ_REQUESTS_PER_TX],
diff --git a/noir-projects/noir-protocol-circuits/crates/types/src/abis/membership_witness.nr b/noir-projects/noir-protocol-circuits/crates/types/src/abis/membership_witness.nr
index 488272527c3..d23cfb0f19a 100644
--- a/noir-projects/noir-protocol-circuits/crates/types/src/abis/membership_witness.nr
+++ b/noir-projects/noir-protocol-circuits/crates/types/src/abis/membership_witness.nr
@@ -3,11 +3,6 @@ use crate::constants::{
ARCHIVE_HEIGHT, PUBLIC_DATA_TREE_HEIGHT
};
-struct MembershipWitness {
- leaf_index: Field,
- sibling_path: [Field; N]
-}
-
// TODO(Kev): Instead of doing `MembershipWitness` we are forced
// to do this new struct because the typescript bindings generator
// does not have logic to monomorphize these properly. See the file named
diff --git a/noir-projects/noir-protocol-circuits/crates/types/src/abis/nullifier_leaf_preimage.nr b/noir-projects/noir-protocol-circuits/crates/types/src/abis/nullifier_leaf_preimage.nr
index 6eb484e5d66..895196567a0 100644
--- a/noir-projects/noir-protocol-circuits/crates/types/src/abis/nullifier_leaf_preimage.nr
+++ b/noir-projects/noir-protocol-circuits/crates/types/src/abis/nullifier_leaf_preimage.nr
@@ -1,6 +1,6 @@
global NULLIFIER_LEAF_PREIMAGE_LENGTH: u64 = 3;
-use crate::{merkle_tree::leaf_preimage::LeafPreimage, traits::{Empty, Hash}};
+use crate::{merkle_tree::leaf_preimage::{LeafPreimage, IndexedTreeLeafPreimage}, traits::{Empty, Hash}};
struct NullifierLeafPreimage {
nullifier : Field,
@@ -38,6 +38,20 @@ impl LeafPreimage for NullifierLeafPreimage {
}
}
+impl IndexedTreeLeafPreimage for NullifierLeafPreimage {
+ fn get_key(self) -> Field {
+ self.nullifier
+ }
+
+ fn get_next_key(self) -> Field {
+ self.next_nullifier
+ }
+
+ fn as_leaf(self) -> Field {
+ self.hash()
+ }
+}
+
impl NullifierLeafPreimage {
pub fn is_empty(self) -> bool {
(self.nullifier == 0) & (self.next_nullifier == 0) & (self.next_index == 0)
diff --git a/noir-projects/noir-protocol-circuits/crates/types/src/abis/public_call_stack_item.nr b/noir-projects/noir-protocol-circuits/crates/types/src/abis/public_call_stack_item.nr
index 0b9dabb2db7..3a9181be985 100644
--- a/noir-projects/noir-protocol-circuits/crates/types/src/abis/public_call_stack_item.nr
+++ b/noir-projects/noir-protocol-circuits/crates/types/src/abis/public_call_stack_item.nr
@@ -69,7 +69,7 @@ mod tests {
let call_stack_item = PublicCallStackItem { contract_address, public_inputs, is_execution_request: true, function_data };
// Value from public_call_stack_item.test.ts "Computes a callstack item request hash" test
- let test_data_call_stack_item_request_hash = 0x09cb16dc10b48bb544bd5f4293cfd2dee539bd281aa468c0c69a9352df17a307;
+ let test_data_call_stack_item_request_hash = 0x1a1194c14f229b72d31669b06e3984d6f0f5edd4d5204ceda0ff30f25e910e83;
assert_eq(call_stack_item.hash(), test_data_call_stack_item_request_hash);
}
@@ -87,7 +87,7 @@ mod tests {
let call_stack_item = PublicCallStackItem { contract_address, public_inputs, is_execution_request: false, function_data };
// Value from public_call_stack_item.test.ts "Computes a callstack item hash" test
- let test_data_call_stack_item_hash = 0x086b4890110c751f01df5eb163b250f10c90a4f38e73e07e3b5a58685456eaa9;
+ let test_data_call_stack_item_hash = 0x187836686ed01f12180ef08c419e4ac8514d9c60e6a38b4a56d893fa90c83a5d;
assert_eq(call_stack_item.hash(), test_data_call_stack_item_hash);
}
}
diff --git a/noir-projects/noir-protocol-circuits/crates/types/src/abis/public_circuit_public_inputs.nr b/noir-projects/noir-protocol-circuits/crates/types/src/abis/public_circuit_public_inputs.nr
index b8f44a158ad..e364c3b49d8 100644
--- a/noir-projects/noir-protocol-circuits/crates/types/src/abis/public_circuit_public_inputs.nr
+++ b/noir-projects/noir-protocol-circuits/crates/types/src/abis/public_circuit_public_inputs.nr
@@ -6,10 +6,10 @@ use crate::{
address::AztecAddress,
constants::{
MAX_NEW_L2_TO_L1_MSGS_PER_CALL, MAX_NEW_NULLIFIERS_PER_CALL, MAX_NEW_NOTE_HASHES_PER_CALL,
- MAX_NULLIFIER_READ_REQUESTS_PER_CALL, MAX_PUBLIC_CALL_STACK_LENGTH_PER_CALL,
- MAX_PUBLIC_DATA_READS_PER_CALL, MAX_PUBLIC_DATA_UPDATE_REQUESTS_PER_CALL, NUM_FIELDS_PER_SHA256,
- RETURN_VALUES_LENGTH, GENERATOR_INDEX__PUBLIC_CIRCUIT_PUBLIC_INPUTS,
- PUBLIC_CIRCUIT_PUBLIC_INPUTS_LENGTH
+ MAX_NULLIFIER_READ_REQUESTS_PER_CALL, MAX_NULLIFIER_NON_EXISTENT_READ_REQUESTS_PER_CALL,
+ MAX_PUBLIC_CALL_STACK_LENGTH_PER_CALL, MAX_PUBLIC_DATA_READS_PER_CALL,
+ MAX_PUBLIC_DATA_UPDATE_REQUESTS_PER_CALL, NUM_FIELDS_PER_SHA256, RETURN_VALUES_LENGTH,
+ GENERATOR_INDEX__PUBLIC_CIRCUIT_PUBLIC_INPUTS, PUBLIC_CIRCUIT_PUBLIC_INPUTS_LENGTH
},
contrakt::{storage_read::StorageRead, storage_update_request::StorageUpdateRequest},
hash::pedersen_hash, header::Header, messaging::l2_to_l1_message::L2ToL1Message,
@@ -23,6 +23,7 @@ struct PublicCircuitPublicInputs{
return_values: [Field; RETURN_VALUES_LENGTH],
nullifier_read_requests: [ReadRequest; MAX_NULLIFIER_READ_REQUESTS_PER_CALL],
+ nullifier_non_existent_read_requests: [ReadRequest; MAX_NULLIFIER_NON_EXISTENT_READ_REQUESTS_PER_CALL],
contract_storage_update_requests: [StorageUpdateRequest; MAX_PUBLIC_DATA_UPDATE_REQUESTS_PER_CALL],
contract_storage_reads: [StorageRead; MAX_PUBLIC_DATA_READS_PER_CALL],
@@ -62,6 +63,9 @@ impl Serialize for PublicCircuitPublicInput
for i in 0..MAX_NULLIFIER_READ_REQUESTS_PER_CALL {
fields.extend_from_array(self.nullifier_read_requests[i].serialize());
}
+ for i in 0..MAX_NULLIFIER_NON_EXISTENT_READ_REQUESTS_PER_CALL {
+ fields.extend_from_array(self.nullifier_non_existent_read_requests[i].serialize());
+ }
for i in 0..MAX_PUBLIC_DATA_UPDATE_REQUESTS_PER_CALL {
fields.extend_from_array(self.contract_storage_update_requests[i].serialize());
}
@@ -97,6 +101,7 @@ impl Deserialize for PublicCircuitPublicInp
args_hash: reader.read(),
return_values: reader.read_array([0; RETURN_VALUES_LENGTH]),
nullifier_read_requests: reader.read_struct_array(ReadRequest::deserialize, [ReadRequest::empty(); MAX_NULLIFIER_READ_REQUESTS_PER_CALL]),
+ nullifier_non_existent_read_requests: reader.read_struct_array(ReadRequest::deserialize, [ReadRequest::empty(); MAX_NULLIFIER_NON_EXISTENT_READ_REQUESTS_PER_CALL]),
contract_storage_update_requests: reader.read_struct_array(StorageUpdateRequest::deserialize, [StorageUpdateRequest::empty(); MAX_PUBLIC_DATA_UPDATE_REQUESTS_PER_CALL]),
contract_storage_reads: reader.read_struct_array(StorageRead::deserialize, [StorageRead::empty(); MAX_PUBLIC_DATA_READS_PER_CALL]),
public_call_stack_hashes: reader.read_array([0; MAX_PUBLIC_CALL_STACK_LENGTH_PER_CALL]),
@@ -135,6 +140,6 @@ fn empty_hash() {
let hash = inputs.hash();
// Value from public_circuit_public_inputs.test.ts "computes empty item hash" test
- let test_data_empty_hash = 0x153eea640dd0a53eaa029301381962507fb89e348d42d6f3335107644c6541b9;
+ let test_data_empty_hash = 0x1c9942cee14a4f84b3e606f553b2ab3151c395822ee7ffd51759d5822375d6c9;
assert_eq(hash, test_data_empty_hash);
}
diff --git a/noir-projects/noir-protocol-circuits/crates/types/src/constants.nr b/noir-projects/noir-protocol-circuits/crates/types/src/constants.nr
index 12f6c0000f6..1c765a0bc7e 100644
--- a/noir-projects/noir-protocol-circuits/crates/types/src/constants.nr
+++ b/noir-projects/noir-protocol-circuits/crates/types/src/constants.nr
@@ -32,6 +32,7 @@ global MAX_PUBLIC_DATA_UPDATE_REQUESTS_PER_CALL: u64 = 16;
global MAX_PUBLIC_DATA_READS_PER_CALL: u64 = 16;
global MAX_NOTE_HASH_READ_REQUESTS_PER_CALL: u64 = 32;
global MAX_NULLIFIER_READ_REQUESTS_PER_CALL: u64 = 2; // Change it to a larger value when there's a seperate reset circuit.
+global MAX_NULLIFIER_NON_EXISTENT_READ_REQUESTS_PER_CALL: u64 = 2;
global MAX_NULLIFIER_KEY_VALIDATION_REQUESTS_PER_CALL: u64 = 1;
// "PER TRANSACTION" CONSTANTS
@@ -60,6 +61,7 @@ global MAX_REVERTIBLE_PUBLIC_DATA_READS_PER_TX: u64 = 16;
global MAX_NEW_L2_TO_L1_MSGS_PER_TX: u64 = 2;
global MAX_NOTE_HASH_READ_REQUESTS_PER_TX: u64 = 128;
global MAX_NULLIFIER_READ_REQUESTS_PER_TX: u64 = 8; // Change it to a larger value when there's a seperate reset circuit.
+global MAX_NULLIFIER_NON_EXISTENT_READ_REQUESTS_PER_TX: u64 = 8;
global MAX_NULLIFIER_KEY_VALIDATION_REQUESTS_PER_TX: u64 = 4;
global NUM_ENCRYPTED_LOGS_HASHES_PER_TX: u64 = 1;
global NUM_UNENCRYPTED_LOGS_HASHES_PER_TX: u64 = 1;
@@ -167,7 +169,7 @@ global PRIVATE_CALL_STACK_ITEM_LENGTH: u64 = 214;
// constant as well PRIVATE_CALL_STACK_ITEM_LENGTH
global PRIVATE_CIRCUIT_PUBLIC_INPUTS_LENGTH: u64 = 209;
// Change this ONLY if you have changed the PublicCircuitPublicInputs structure.
-global PUBLIC_CIRCUIT_PUBLIC_INPUTS_LENGTH: u64 = 196;
+global PUBLIC_CIRCUIT_PUBLIC_INPUTS_LENGTH: u64 = 200;
global STATE_REFERENCE_LENGTH: u64 = 8; // 2 for snap + 8 for partial
global TX_CONTEXT_DATA_LENGTH: u64 = 4;
global TX_REQUEST_LENGTH: u64 = 10;
diff --git a/noir-projects/noir-protocol-circuits/crates/types/src/hash.nr b/noir-projects/noir-protocol-circuits/crates/types/src/hash.nr
index 0afb6ea78a9..7de5fa7d536 100644
--- a/noir-projects/noir-protocol-circuits/crates/types/src/hash.nr
+++ b/noir-projects/noir-protocol-circuits/crates/types/src/hash.nr
@@ -14,6 +14,7 @@ use crate::constants::{
GENERATOR_INDEX__NOTE_HASH_NONCE, GENERATOR_INDEX__UNIQUE_NOTE_HASH, GENERATOR_INDEX__FUNCTION_ARGS
};
use crate::messaging::l2_to_l1_message::L2ToL1Message;
+use crate::merkle_tree::root::root_from_sibling_path;
use dep::std::hash::{pedersen_hash_with_separator, sha256};
@@ -61,38 +62,6 @@ pub fn hash_args(args: [Field; N]) -> Field {
}
}
-// Checks that `value` is a member of a merkle tree with root `root` at position `index`
-// The witness being the `sibling_path`
-pub fn assert_check_membership(value: Field, index: Field, sibling_path: [Field; N], root: Field) {
- let calculated_root = root_from_sibling_path(value, index, sibling_path);
- assert(calculated_root == root, "membership check failed");
-}
-
-// Calculate the Merkle tree root from the sibling path and leaf.
-//
-// The leaf is hashed with its sibling, and then the result is hashed
-// with the next sibling etc in the path. The last hash is the root.
-//
-// TODO(David/Someone): The cpp code is using a uint256, whereas its
-// TODO a bit simpler in Noir to just have a bit array.
-// TODO: I'd generally like to avoid u256 for algorithms like
-// this because it means we never even need to consider cases where
-// the index is greater than p.
-pub fn root_from_sibling_path(leaf: Field, leaf_index: Field, sibling_path: [Field; N]) -> Field {
- let mut node = leaf;
- let indices = leaf_index.to_le_bits(N);
-
- for i in 0..N {
- let (hash_left, hash_right) = if indices[i] == 1 {
- (sibling_path[i], node)
- } else {
- (node, sibling_path[i])
- };
- node = merkle_hash(hash_left, hash_right);
- }
- node
-}
-
// Calculate the function tree root from the sibling path and leaf preimage.
//
// TODO: The cpp code passes in components of the FunctionLeafPreimage and then
@@ -148,7 +117,7 @@ pub fn silo_nullifier(address: AztecAddress, nullifier: Field) -> Field {
)
}
-fn merkle_hash(left: Field, right: Field) -> Field {
+pub fn merkle_hash(left: Field, right: Field) -> Field {
pedersen_hash([left, right], 0)
}
diff --git a/noir-projects/noir-protocol-circuits/crates/types/src/merkle_tree.nr b/noir-projects/noir-protocol-circuits/crates/types/src/merkle_tree.nr
index ecd76abb5ff..67b23d3f449 100644
--- a/noir-projects/noir-protocol-circuits/crates/types/src/merkle_tree.nr
+++ b/noir-projects/noir-protocol-circuits/crates/types/src/merkle_tree.nr
@@ -1,110 +1,14 @@
mod append_only_tree;
mod indexed_tree;
mod leaf_preimage;
-
-struct MerkleTree {
- leaves: [Field; N],
- nodes: [Field; N],
-}
-
-impl MerkleTree {
- pub fn new(leaves: [Field; N]) -> Self {
- let mut nodes = [0; N];
-
- // We need one less node than leaves, but we cannot have computed array lengths
- let total_nodes = N - 1;
- let half_size = N / 2;
-
- // hash base layer
- for i in 0..half_size {
- nodes[i] = dep::std::hash::pedersen_hash([leaves[2*i], leaves[2*i+1]]);
- }
-
- // hash the other layers
- for i in 0..(total_nodes - half_size) {
- nodes[half_size+i] = dep::std::hash::pedersen_hash([nodes[2*i], nodes[2*i+1]]);
- }
-
- MerkleTree { leaves, nodes }
- }
-
- fn get_root(self) -> Field {
- self.nodes[N - 2]
- }
-}
-
-pub fn calculate_subtree(leaves: [Field; N]) -> Field {
- MerkleTree::new(leaves).get_root()
-}
-
-// These values are precomputed and we run tests to ensure that they
-// are correct. The values themselves were computed from the cpp code.
-//
-// Would be good if we could use width since the compute_subtree
-// algorithm uses depth.
-pub fn calculate_empty_tree_root(depth: u64) -> Field {
- if depth == 0 {
- 0
- } else if depth == 1 {
- 0x27b1d0839a5b23baf12a8d195b18ac288fcf401afb2f70b8a4b529ede5fa9fed
- } else if depth == 2 {
- 0x21dbfd1d029bf447152fcf89e355c334610d1632436ba170f738107266a71550
- } else if depth == 3 {
- 0x0bcd1f91cf7bdd471d0a30c58c4706f3fdab3807a954b8f5b5e3bfec87d001bb
- } else if depth == 4 {
- 0x06e62084ee7b602fe9abc15632dda3269f56fb0c6e12519a2eb2ec897091919d
- } else if depth == 5 {
- 0x03c9e2e67178ac638746f068907e6677b4cc7a9592ef234ab6ab518f17efffa0
- } else if depth == 6 {
- 0x15d28cad4c0736decea8997cb324cf0a0e0602f4d74472cd977bce2c8dd9923f
- } else if depth == 7 {
- 0x268ed1e1c94c3a45a14db4108bc306613a1c23fab68e0466a002dfb0a3f8d2ab
- } else if depth == 8 {
- 0x0cd8d5695bc2dde99dd531671f76f1482f14ddba8eeca7cb9686d4a62359c257
- } else if depth == 9 {
- 0x047fbb7eb974155702149e58ea6ad91f4c6e953e693db35e953e250d8ceac9a9
- } else if depth == 10 {
- 0x00c5ae2526e665e2c7c698c11a06098b7159f720606d50e7660deb55758b0b02
- } else {
- assert(false, "depth should be between 0 and 10");
- 0
- }
-}
-
-#[test]
-fn test_merkle_root_interop_test() {
- // This is a test to ensure that we match the cpp implementation.
- // You can grep for `TEST_F(root_rollup_tests, noir_interop_test)`
- // to find the test that matches this.
- let root = calculate_subtree([1, 2, 3, 4, 1, 2, 3, 4, 1, 2, 3, 4, 1, 2, 3, 4]);
- assert(0x17e8bb70a11d0c946345950879484d2f4f9fef397ff6adbfdec3baab2d41faab == root);
-
- let empty_root = calculate_subtree([0; 16]);
- assert(0x06e62084ee7b602fe9abc15632dda3269f56fb0c6e12519a2eb2ec897091919d == empty_root);
-}
-
-#[test]
-fn test_empty_subroot() {
- assert(calculate_empty_tree_root(0) == 0);
-
- let expected_empty_root_2 = calculate_subtree([0; 2]);
- assert(calculate_empty_tree_root(1) == expected_empty_root_2);
-
- let expected_empty_root_4 = calculate_subtree([0; 4]);
- assert(calculate_empty_tree_root(2) == expected_empty_root_4);
-
- let expected_empty_root_8 = calculate_subtree([0; 8]);
- assert(calculate_empty_tree_root(3) == expected_empty_root_8);
-
- let expected_empty_root_16 = calculate_subtree([0; 16]);
- assert(calculate_empty_tree_root(4) == expected_empty_root_16);
-
- let expected_empty_root_32 = calculate_subtree([0; 32]);
- assert(calculate_empty_tree_root(5) == expected_empty_root_32);
-
- let expected_empty_root_64 = calculate_subtree([0; 64]);
- assert(calculate_empty_tree_root(6) == expected_empty_root_64);
-
- let expected_empty_root_128 = calculate_subtree([0; 128]);
- assert(calculate_empty_tree_root(7) == expected_empty_root_128);
-}
+mod membership;
+mod merkle_tree;
+mod root;
+
+use leaf_preimage::{IndexedTreeLeafPreimage, LeafPreimage};
+use membership::{
+ assert_check_membership, assert_check_non_membership, check_membership, check_non_membership,
+ MembershipWitness
+};
+use merkle_tree::MerkleTree;
+use root::{calculate_empty_tree_root, calculate_subtree_root, root_from_sibling_path};
diff --git a/noir-projects/noir-protocol-circuits/crates/types/src/merkle_tree/append_only_tree.nr b/noir-projects/noir-protocol-circuits/crates/types/src/merkle_tree/append_only_tree.nr
index 18145d3d233..b1faf2c06c7 100644
--- a/noir-projects/noir-protocol-circuits/crates/types/src/merkle_tree/append_only_tree.nr
+++ b/noir-projects/noir-protocol-circuits/crates/types/src/merkle_tree/append_only_tree.nr
@@ -1,6 +1,6 @@
use crate::{
abis::{append_only_tree_snapshot::AppendOnlyTreeSnapshot},
- hash::{assert_check_membership, root_from_sibling_path}
+ merkle_tree::{membership::assert_check_membership, root::root_from_sibling_path}
};
pub fn insert_subtree_to_snapshot_tree(
diff --git a/noir-projects/noir-protocol-circuits/crates/types/src/merkle_tree/indexed_tree.nr b/noir-projects/noir-protocol-circuits/crates/types/src/merkle_tree/indexed_tree.nr
index 9c291900061..6cfc75baaf6 100644
--- a/noir-projects/noir-protocol-circuits/crates/types/src/merkle_tree/indexed_tree.nr
+++ b/noir-projects/noir-protocol-circuits/crates/types/src/merkle_tree/indexed_tree.nr
@@ -1,53 +1,12 @@
use crate::{
- abis::{append_only_tree_snapshot::AppendOnlyTreeSnapshot, membership_witness::MembershipWitness},
- hash::{assert_check_membership, root_from_sibling_path},
- merkle_tree::{calculate_subtree, calculate_empty_tree_root}
+ abis::{append_only_tree_snapshot::AppendOnlyTreeSnapshot},
+ merkle_tree::{
+ membership::{assert_check_membership, MembershipWitness},
+ root::{calculate_subtree_root, calculate_empty_tree_root, root_from_sibling_path}
+},
+ traits::{Empty, Hash, is_empty}, utils::arrays::check_permutation
};
-fn check_permutation(
- original_array: [T; N],
- sorted_array: [T; N],
- indexes: [u64; N],
- is_equal: fn(T, T) -> bool
-) {
- let mut seen_value = [false; N];
- for i in 0..N {
- let index = indexes[i];
- let sorted_value = sorted_array[i];
- let original_value = original_array[index];
- assert(is_equal(sorted_value, original_value), "Invalid index");
- assert(!seen_value[index], "Duplicated index");
- seen_value[index] = true;
- }
-}
-
-#[test]
-fn check_permutation_basic_test() {
- let original_array = [1, 2, 3];
- let sorted_array = [3, 1, 2];
- let indexes = [2, 0, 1];
- let is_equal = |a: Field, b: Field| a == b;
- check_permutation(original_array, sorted_array, indexes, is_equal);
-}
-
-#[test(should_fail_with = "Duplicated index")]
-fn check_permutation_duplicated_index() {
- let original_array = [0, 1, 0];
- let sorted_array = [1, 0, 0];
- let indexes = [1, 0, 0];
- let is_equal = |a: Field, b: Field| a == b;
- check_permutation(original_array, sorted_array, indexes, is_equal);
-}
-
-#[test(should_fail_with = "Invalid index")]
-fn check_permutation_invalid_index() {
- let original_array = [0, 1, 2];
- let sorted_array = [1, 0, 0];
- let indexes = [1, 0, 2];
- let is_equal = |a: Field, b: Field| a == b;
- check_permutation(original_array, sorted_array, indexes, is_equal);
-}
-
pub fn batch_insert(
start_snapshot: AppendOnlyTreeSnapshot,
values_to_insert: [Value; SubtreeWidth],
@@ -56,22 +15,14 @@ pub fn batch_insert; SubtreeWidth],
- is_equal: fn(Value, Value) -> bool,
- is_empty_value: fn(Value) -> bool,
- hash_leaf: fn(Leaf) -> Field,
is_valid_low_leaf: fn(Leaf, Value) -> bool,
update_low_leaf: fn(Leaf, Value, u64) -> Leaf,
build_insertion_leaf: fn(Value, Leaf) -> Leaf,
_subtree_height: [Field; SubtreeHeight],
_tree_height: [Field; TreeHeight]
-) -> AppendOnlyTreeSnapshot {
+) -> AppendOnlyTreeSnapshot where Value: Eq + Empty, Leaf: Hash {
// A permutation to the values is provided to make the insertion use only one insertion strategy
- check_permutation(
- values_to_insert,
- sorted_values,
- sorted_values_indexes,
- is_equal
- );
+ check_permutation(values_to_insert, sorted_values, sorted_values_indexes);
// Now, update the existing leaves with the new leaves
let mut current_tree_root = start_snapshot.root;
@@ -80,7 +31,7 @@ pub fn batch_insert Field;
fn as_leaf(self) -> Field;
}
+
+trait IndexedTreeLeafPreimage {
+ fn get_key(self) -> Field;
+ fn get_next_key(self) -> Field;
+ fn as_leaf(self) -> Field;
+}
diff --git a/noir-projects/noir-protocol-circuits/crates/types/src/merkle_tree/membership.nr b/noir-projects/noir-protocol-circuits/crates/types/src/merkle_tree/membership.nr
new file mode 100644
index 00000000000..6fc8d91d13b
--- /dev/null
+++ b/noir-projects/noir-protocol-circuits/crates/types/src/merkle_tree/membership.nr
@@ -0,0 +1,343 @@
+use crate::{merkle_tree::{leaf_preimage::IndexedTreeLeafPreimage, root::root_from_sibling_path}};
+
+struct MembershipWitness {
+ leaf_index: Field,
+ sibling_path: [Field; N]
+}
+
+pub fn check_membership(leaf: Field, index: Field, sibling_path: [Field; N], root: Field) -> bool {
+ let calculated_root = root_from_sibling_path(leaf, index, sibling_path);
+ calculated_root == root
+}
+
+pub fn assert_check_membership(leaf: Field, index: Field, sibling_path: [Field; N], root: Field) {
+ assert(check_membership(leaf, index, sibling_path, root), "membership check failed");
+}
+
+struct NonMembershipCheckErrorCodeEnum {
+ NADA: u64,
+ IS_EMPTY: u64,
+ NOT_EXISTS: u64,
+ NOT_GREATER_THAN_LOW: u64,
+ NOT_LESS_THAN_NEXT: u64,
+}
+
+global NonMembershipCheckErrorCode = NonMembershipCheckErrorCodeEnum {
+ NADA: 0,
+ IS_EMPTY: 1,
+ NOT_EXISTS: 2,
+ NOT_GREATER_THAN_LOW: 3,
+ NOT_LESS_THAN_NEXT: 4,
+};
+
+fn check_non_membership_internal(
+ key: Field,
+ low_leaf_preimage: LEAF_PREIMAGE,
+ low_leaf_membership_witness: MembershipWitness,
+ tree_root: Field
+) -> u64 where
+ LEAF_PREIMAGE: IndexedTreeLeafPreimage {
+ let low_key = low_leaf_preimage.get_key();
+ let next_key = low_leaf_preimage.get_next_key();
+ let is_empty_leaf = (low_key == 0) & (next_key == 0);
+
+ let low_leaf_exists = check_membership(
+ low_leaf_preimage.as_leaf(),
+ low_leaf_membership_witness.leaf_index,
+ low_leaf_membership_witness.sibling_path,
+ tree_root
+ );
+
+ if is_empty_leaf {
+ NonMembershipCheckErrorCode.IS_EMPTY
+ } else if !low_leaf_exists {
+ NonMembershipCheckErrorCode.NOT_EXISTS
+ } else if !low_key.lt(key) {
+ NonMembershipCheckErrorCode.NOT_GREATER_THAN_LOW
+ } else if !key.lt(next_key) & (next_key != 0) {
+ NonMembershipCheckErrorCode.NOT_LESS_THAN_NEXT
+ } else {
+ NonMembershipCheckErrorCode.NADA
+ }
+}
+
+pub fn check_non_membership(
+ key: Field,
+ low_leaf_preimage: LEAF_PREIMAGE,
+ low_leaf_membership_witness: MembershipWitness,
+ tree_root: Field
+) -> bool where
+ LEAF_PREIMAGE: IndexedTreeLeafPreimage {
+ let error = check_non_membership_internal(key, low_leaf_preimage, low_leaf_membership_witness, tree_root);
+ error == NonMembershipCheckErrorCode.NADA
+}
+
+pub fn assert_check_non_membership(
+ key: Field,
+ low_leaf_preimage: LEAF_PREIMAGE,
+ low_leaf_membership_witness: MembershipWitness,
+ tree_root: Field
+) where
+ LEAF_PREIMAGE: IndexedTreeLeafPreimage {
+ let error = check_non_membership_internal(key, low_leaf_preimage, low_leaf_membership_witness, tree_root);
+ if error != NonMembershipCheckErrorCode.NADA {
+ assert(
+ error != NonMembershipCheckErrorCode.IS_EMPTY, "Cannot check non membership against empty leaf"
+ );
+ assert(error != NonMembershipCheckErrorCode.NOT_EXISTS, "Low leaf does not exist");
+ assert(
+ error != NonMembershipCheckErrorCode.NOT_GREATER_THAN_LOW, "Key is not greater than the low leaf"
+ );
+ assert(
+ error != NonMembershipCheckErrorCode.NOT_LESS_THAN_NEXT, "Key is not less than the next leaf"
+ );
+ assert(false, "Unknown error");
+ }
+}
+
+mod tests {
+ use crate::{
+ merkle_tree::{
+ leaf_preimage::{IndexedTreeLeafPreimage, LeafPreimage},
+ membership::{
+ assert_check_membership, assert_check_non_membership, check_membership, check_non_membership,
+ MembershipWitness
+ }
+ },
+ tests::merkle_tree_utils::NonEmptyMerkleTree
+ };
+ use dep::std::hash::pedersen_hash;
+
+ struct TestLeafPreimage {
+ value: Field,
+ next_value: Field,
+ }
+
+ impl LeafPreimage for TestLeafPreimage {
+ fn get_key(self) -> Field {
+ self.value
+ }
+
+ fn as_leaf(self) -> Field {
+ pedersen_hash([self.value])
+ }
+ }
+
+ impl IndexedTreeLeafPreimage for TestLeafPreimage {
+ fn get_key(self) -> Field {
+ self.value
+ }
+
+ fn get_next_key(self) -> Field {
+ self.next_value
+ }
+
+ fn as_leaf(self) -> Field {
+ pedersen_hash([self.value])
+ }
+ }
+
+ global leaf_preimages = [
+ TestLeafPreimage { value: 20, next_value: 30 },
+ TestLeafPreimage { value: 40, next_value: 0 },
+ TestLeafPreimage { value: 10, next_value: 20 },
+ TestLeafPreimage { value: 30, next_value: 40 },
+ ];
+
+ fn build_tree() -> NonEmptyMerkleTree<4, 3, 1, 2> {
+ NonEmptyMerkleTree::new(
+ leaf_preimages.map(|leaf_preimage: TestLeafPreimage| leaf_preimage.as_leaf()),
+ [0; 3],
+ [0; 1],
+ [0; 2]
+ )
+ }
+
+ fn check_membership_at_index(leaf_index: Field, leaf: Field) -> bool {
+ let tree = build_tree();
+ let tree_root = tree.get_root();
+
+ check_membership(
+ leaf,
+ leaf_index,
+ tree.get_sibling_path(leaf_index as u64),
+ tree_root
+ )
+ }
+
+ fn assert_check_membership_at_index(leaf_index: Field, leaf: Field) {
+ let tree = build_tree();
+ let tree_root = tree.get_root();
+
+ assert_check_membership(
+ leaf,
+ leaf_index,
+ tree.get_sibling_path(leaf_index as u64),
+ tree_root
+ );
+ }
+
+ fn check_non_membership_at_index(low_leaf_index: u64, leaf: Field) -> bool {
+ let tree = build_tree();
+ let tree_root = tree.get_root();
+ let leaf_preimage = if low_leaf_index < leaf_preimages.len() {
+ leaf_preimages[low_leaf_index]
+ } else {
+ TestLeafPreimage { value: 0, next_value: 0 }
+ };
+
+ check_non_membership(
+ leaf,
+ leaf_preimage,
+ MembershipWitness { leaf_index: low_leaf_index as Field, sibling_path: tree.get_sibling_path(low_leaf_index) } ,
+ tree_root
+ )
+ }
+
+ fn assert_check_non_membership_at_index(low_leaf_index: u64, leaf: Field) {
+ let tree = build_tree();
+ let tree_root = tree.get_root();
+ let leaf_preimage = if low_leaf_index < leaf_preimages.len() {
+ leaf_preimages[low_leaf_index]
+ } else {
+ TestLeafPreimage { value: 0, next_value: 0 }
+ };
+
+ assert_check_non_membership(
+ leaf,
+ leaf_preimage,
+ MembershipWitness { leaf_index: low_leaf_index as Field, sibling_path: tree.get_sibling_path(low_leaf_index) } ,
+ tree_root
+ );
+ }
+
+ #[test]
+ fn test_check_membership() {
+ assert_eq(check_membership_at_index(0, leaf_preimages[0].as_leaf()), true);
+ assert_eq(check_membership_at_index(2, leaf_preimages[2].as_leaf()), true);
+ }
+
+ #[test]
+ fn test_assert_check_membership() {
+ assert_check_membership_at_index(0, leaf_preimages[0].as_leaf());
+ assert_check_membership_at_index(2, leaf_preimages[2].as_leaf());
+ }
+
+ #[test]
+ fn test_check_membership_false_wrong_leaf() {
+ assert_eq(check_membership_at_index(0, leaf_preimages[1].as_leaf()), false);
+ assert_eq(check_membership_at_index(2, leaf_preimages[0].as_leaf()), false);
+ }
+
+ #[test(should_fail_with="membership check failed")]
+ fn test_assert_check_membership_failed_wrong_leaf() {
+ assert_check_membership_at_index(0, leaf_preimages[1].as_leaf());
+ }
+
+ #[test]
+ fn test_check_membership_false_wrong_root() {
+ let tree = build_tree();
+ let tree_root = 56;
+
+ let res = check_membership(
+ leaf_preimages[0].as_leaf(),
+ 0,
+ tree.get_sibling_path(0),
+ tree_root
+ );
+ assert_eq(res, false);
+ }
+
+ #[test(should_fail_with="membership check failed")]
+ fn test_assert_check_membership_false_wrong_root() {
+ let tree = build_tree();
+ let tree_root = 56;
+
+ assert_check_membership(
+ leaf_preimages[0].as_leaf(),
+ 0,
+ tree.get_sibling_path(0),
+ tree_root
+ );
+ }
+
+ #[test]
+ fn test_check_non_membership() {
+ assert_eq(check_non_membership_at_index(0, 25), true);
+ }
+
+ #[test]
+ fn test_assert_check_non_membership() {
+ assert_check_non_membership_at_index(0, 25);
+ }
+
+ #[test]
+ fn test_check_non_membership_greater_than_max() {
+ assert_eq(check_non_membership_at_index(1, 45), true);
+ }
+
+ #[test]
+ fn test_assert_check_non_membership_greater_than_max() {
+ assert_check_non_membership_at_index(1, 45);
+ }
+
+ #[test]
+ fn test_check_non_membership_false_empty_leaf() {
+ assert_eq(check_non_membership_at_index(4, 25), false);
+ }
+
+ #[test(should_fail_with="Cannot check non membership against empty leaf")]
+ fn test_assert_check_non_membership_failed_empty_leaf() {
+ assert_check_non_membership_at_index(4, 25);
+ }
+
+ #[test]
+ fn test_check_non_membership_false_wrong_low_leaf() {
+ assert_eq(check_non_membership_at_index(3, 25), false);
+ }
+
+ #[test(should_fail_with="Key is not greater than the low leaf")]
+ fn test_assert_check_non_membership_failed_wrong_low_leaf() {
+ assert_check_non_membership_at_index(3, 25);
+ }
+
+ #[test]
+ fn test_check_non_membership_false_wrong_next_key() {
+ assert_eq(check_non_membership_at_index(2, 25), false);
+ }
+
+ #[test(should_fail_with="Key is not less than the next leaf")]
+ fn test_assert_check_non_membership_failed_wrong_next_key() {
+ assert_check_non_membership_at_index(2, 25);
+ }
+
+ #[test]
+ fn test_check_non_membership_false_invalid_leaf() {
+ let tree = build_tree();
+ let tree_root = tree.get_root();
+
+ let fake_leaf = TestLeafPreimage { value: 50, next_value: 60 };
+ assert_eq(
+ check_non_membership(
+ 55,
+ fake_leaf,
+ MembershipWitness { leaf_index: 1, sibling_path: tree.get_sibling_path(1) } ,
+ tree_root
+ ), false
+ );
+ }
+
+ #[test(should_fail_with="Low leaf does not exist")]
+ fn test_assert_check_non_membership_failed_invalid_leaf() {
+ let tree = build_tree();
+ let tree_root = tree.get_root();
+
+ let fake_leaf = TestLeafPreimage { value: 50, next_value: 60 };
+ assert_check_non_membership(
+ 55,
+ fake_leaf,
+ MembershipWitness { leaf_index: 1, sibling_path: tree.get_sibling_path(1) } ,
+ tree_root
+ );
+ }
+}
diff --git a/noir-projects/noir-protocol-circuits/crates/types/src/merkle_tree/merkle_tree.nr b/noir-projects/noir-protocol-circuits/crates/types/src/merkle_tree/merkle_tree.nr
new file mode 100644
index 00000000000..f1cacb956ac
--- /dev/null
+++ b/noir-projects/noir-protocol-circuits/crates/types/src/merkle_tree/merkle_tree.nr
@@ -0,0 +1,31 @@
+struct MerkleTree {
+ leaves: [Field; N],
+ nodes: [Field; N],
+}
+
+impl MerkleTree {
+ pub fn new(leaves: [Field; N]) -> Self {
+ let mut nodes = [0; N];
+
+ // We need one less node than leaves, but we cannot have computed array lengths
+ let total_nodes = N - 1;
+ let half_size = N / 2;
+
+ // hash base layer
+ for i in 0..half_size {
+ nodes[i] = dep::std::hash::pedersen_hash([leaves[2*i], leaves[2*i+1]]);
+ }
+
+ // hash the other layers
+ for i in 0..(total_nodes - half_size) {
+ nodes[half_size+i] = dep::std::hash::pedersen_hash([nodes[2*i], nodes[2*i+1]]);
+ }
+
+ MerkleTree { leaves, nodes }
+ }
+
+ fn get_root(self) -> Field {
+ self.nodes[N - 2]
+ }
+}
+
diff --git a/noir-projects/noir-protocol-circuits/crates/types/src/merkle_tree/root.nr b/noir-projects/noir-protocol-circuits/crates/types/src/merkle_tree/root.nr
new file mode 100644
index 00000000000..e659261fbdd
--- /dev/null
+++ b/noir-projects/noir-protocol-circuits/crates/types/src/merkle_tree/root.nr
@@ -0,0 +1,102 @@
+use crate::{hash::merkle_hash, merkle_tree::merkle_tree::MerkleTree};
+
+// Calculate the Merkle tree root from the sibling path and leaf.
+//
+// The leaf is hashed with its sibling, and then the result is hashed
+// with the next sibling etc in the path. The last hash is the root.
+//
+// TODO(David/Someone): The cpp code is using a uint256, whereas its
+// TODO a bit simpler in Noir to just have a bit array.
+// TODO: I'd generally like to avoid u256 for algorithms like
+// this because it means we never even need to consider cases where
+// the index is greater than p.
+pub fn root_from_sibling_path(leaf: Field, leaf_index: Field, sibling_path: [Field; N]) -> Field {
+ let mut node = leaf;
+ let indices = leaf_index.to_le_bits(N);
+
+ for i in 0..N {
+ let (hash_left, hash_right) = if indices[i] == 1 {
+ (sibling_path[i], node)
+ } else {
+ (node, sibling_path[i])
+ };
+ node = merkle_hash(hash_left, hash_right);
+ }
+ node
+}
+
+pub fn calculate_subtree_root(leaves: [Field; N]) -> Field {
+ MerkleTree::new(leaves).get_root()
+}
+
+// These values are precomputed and we run tests to ensure that they
+// are correct. The values themselves were computed from the cpp code.
+//
+// Would be good if we could use width since the compute_subtree
+// algorithm uses depth.
+pub fn calculate_empty_tree_root(depth: u64) -> Field {
+ if depth == 0 {
+ 0
+ } else if depth == 1 {
+ 0x27b1d0839a5b23baf12a8d195b18ac288fcf401afb2f70b8a4b529ede5fa9fed
+ } else if depth == 2 {
+ 0x21dbfd1d029bf447152fcf89e355c334610d1632436ba170f738107266a71550
+ } else if depth == 3 {
+ 0x0bcd1f91cf7bdd471d0a30c58c4706f3fdab3807a954b8f5b5e3bfec87d001bb
+ } else if depth == 4 {
+ 0x06e62084ee7b602fe9abc15632dda3269f56fb0c6e12519a2eb2ec897091919d
+ } else if depth == 5 {
+ 0x03c9e2e67178ac638746f068907e6677b4cc7a9592ef234ab6ab518f17efffa0
+ } else if depth == 6 {
+ 0x15d28cad4c0736decea8997cb324cf0a0e0602f4d74472cd977bce2c8dd9923f
+ } else if depth == 7 {
+ 0x268ed1e1c94c3a45a14db4108bc306613a1c23fab68e0466a002dfb0a3f8d2ab
+ } else if depth == 8 {
+ 0x0cd8d5695bc2dde99dd531671f76f1482f14ddba8eeca7cb9686d4a62359c257
+ } else if depth == 9 {
+ 0x047fbb7eb974155702149e58ea6ad91f4c6e953e693db35e953e250d8ceac9a9
+ } else if depth == 10 {
+ 0x00c5ae2526e665e2c7c698c11a06098b7159f720606d50e7660deb55758b0b02
+ } else {
+ assert(false, "depth should be between 0 and 10");
+ 0
+ }
+}
+
+#[test]
+fn test_merkle_root_interop_test() {
+ // This is a test to ensure that we match the cpp implementation.
+ // You can grep for `TEST_F(root_rollup_tests, noir_interop_test)`
+ // to find the test that matches this.
+ let root = calculate_subtree_root([1, 2, 3, 4, 1, 2, 3, 4, 1, 2, 3, 4, 1, 2, 3, 4]);
+ assert(0x17e8bb70a11d0c946345950879484d2f4f9fef397ff6adbfdec3baab2d41faab == root);
+
+ let empty_root = calculate_subtree_root([0; 16]);
+ assert(0x06e62084ee7b602fe9abc15632dda3269f56fb0c6e12519a2eb2ec897091919d == empty_root);
+}
+
+#[test]
+fn test_empty_subroot() {
+ assert(calculate_empty_tree_root(0) == 0);
+
+ let expected_empty_root_2 = calculate_subtree_root([0; 2]);
+ assert(calculate_empty_tree_root(1) == expected_empty_root_2);
+
+ let expected_empty_root_4 = calculate_subtree_root([0; 4]);
+ assert(calculate_empty_tree_root(2) == expected_empty_root_4);
+
+ let expected_empty_root_8 = calculate_subtree_root([0; 8]);
+ assert(calculate_empty_tree_root(3) == expected_empty_root_8);
+
+ let expected_empty_root_16 = calculate_subtree_root([0; 16]);
+ assert(calculate_empty_tree_root(4) == expected_empty_root_16);
+
+ let expected_empty_root_32 = calculate_subtree_root([0; 32]);
+ assert(calculate_empty_tree_root(5) == expected_empty_root_32);
+
+ let expected_empty_root_64 = calculate_subtree_root([0; 64]);
+ assert(calculate_empty_tree_root(6) == expected_empty_root_64);
+
+ let expected_empty_root_128 = calculate_subtree_root([0; 128]);
+ assert(calculate_empty_tree_root(7) == expected_empty_root_128);
+}
diff --git a/noir-projects/noir-protocol-circuits/crates/types/src/tests.nr b/noir-projects/noir-protocol-circuits/crates/types/src/tests.nr
index c02eefc2353..77f7f32bc6d 100644
--- a/noir-projects/noir-protocol-circuits/crates/types/src/tests.nr
+++ b/noir-projects/noir-protocol-circuits/crates/types/src/tests.nr
@@ -6,3 +6,4 @@ mod private_call_data_builder;
mod private_circuit_public_inputs_builder;
mod public_call_data_builder;
mod public_circuit_public_inputs_builder;
+mod sort;
diff --git a/noir-projects/noir-protocol-circuits/crates/types/src/tests/kernel_data_builder.nr b/noir-projects/noir-protocol-circuits/crates/types/src/tests/kernel_data_builder.nr
index 6ab1f79d6f1..50769bbfbb4 100644
--- a/noir-projects/noir-protocol-circuits/crates/types/src/tests/kernel_data_builder.nr
+++ b/noir-projects/noir-protocol-circuits/crates/types/src/tests/kernel_data_builder.nr
@@ -146,6 +146,16 @@ impl PreviousKernelDataBuilder {
value_offset + nullifier_index as Field
}
+ pub fn add_nullifier(&mut self, unsiloed_nullifier: Field) {
+ let value = silo_nullifier(self.storage_contract_address, unsiloed_nullifier);
+ self.end.new_nullifiers.push(SideEffectLinkedToNoteHash { value, note_hash: 0, counter: self.next_sideffect_counter() });
+ }
+
+ pub fn add_nullifier_non_revertible(&mut self, unsiloed_nullifier: Field) {
+ let value = silo_nullifier(self.storage_contract_address, unsiloed_nullifier);
+ self.end_non_revertible.new_nullifiers.push(SideEffectLinkedToNoteHash { value, note_hash: 0, counter: self.next_sideffect_counter() });
+ }
+
pub fn append_new_nullifiers_from_private(&mut self, num_extra_nullifier: u64) {
// in private kernel, the nullifiers have not yet been partitioned
// (that is part of the job of the private kernel tail)
@@ -154,13 +164,7 @@ impl PreviousKernelDataBuilder {
for i in 0..MAX_NEW_NULLIFIERS_PER_TX {
if i < num_extra_nullifier {
let mock_value = self.get_mock_nullifier_value(index_offset + i);
- self.end.new_nullifiers.push(
- SideEffectLinkedToNoteHash {
- value: silo_nullifier(self.storage_contract_address, mock_value),
- note_hash: 0,
- counter: self.next_sideffect_counter()
- }
- );
+ self.add_nullifier(mock_value);
}
}
}
@@ -170,13 +174,7 @@ impl PreviousKernelDataBuilder {
for i in 0..MAX_NEW_NULLIFIERS_PER_TX {
if i < num_extra_nullifier {
let mock_value = self.get_mock_nullifier_value(index_offset + i);
- self.end.new_nullifiers.push(
- SideEffectLinkedToNoteHash {
- value: silo_nullifier(self.storage_contract_address, mock_value),
- note_hash: 0,
- counter: self.next_sideffect_counter()
- }
- );
+ self.add_nullifier(mock_value);
}
}
}
@@ -186,13 +184,7 @@ impl PreviousKernelDataBuilder {
for i in 0..MAX_NON_REVERTIBLE_NULLIFIERS_PER_TX {
if i < num_extra_nullifier {
let mock_value = self.get_mock_nullifier_value_non_revertible(index_offset + i);
- self.end_non_revertible.new_nullifiers.push(
- SideEffectLinkedToNoteHash {
- value: silo_nullifier(self.storage_contract_address, mock_value),
- note_hash: 0,
- counter: self.next_sideffect_counter()
- }
- );
+ self.add_nullifier_non_revertible(mock_value);
}
}
}
@@ -221,6 +213,15 @@ impl PreviousKernelDataBuilder {
read_request_index
}
+ pub fn add_non_existent_read_request_for_nullifier(&mut self, unsiloed_nullifier: Field) {
+ let read_request = ReadRequestContext {
+ value: unsiloed_nullifier,
+ counter: self.next_sideffect_counter(),
+ contract_address: self.storage_contract_address
+ };
+ self.end_non_revertible.nullifier_non_existent_read_requests.push(read_request);
+ }
+
// snapshot the side effects
// this is useful in the private tail circuit to test side effect splitting
pub fn capture_min_revertible_side_effect_counter(&mut self) {
diff --git a/noir-projects/noir-protocol-circuits/crates/types/src/tests/public_circuit_public_inputs_builder.nr b/noir-projects/noir-protocol-circuits/crates/types/src/tests/public_circuit_public_inputs_builder.nr
index ae74ec6bb73..d4bf5c3d294 100644
--- a/noir-projects/noir-protocol-circuits/crates/types/src/tests/public_circuit_public_inputs_builder.nr
+++ b/noir-projects/noir-protocol-circuits/crates/types/src/tests/public_circuit_public_inputs_builder.nr
@@ -9,9 +9,9 @@ use crate::{
};
use crate::constants::{
MAX_NEW_NOTE_HASHES_PER_CALL, MAX_NEW_L2_TO_L1_MSGS_PER_CALL, MAX_NEW_NULLIFIERS_PER_CALL,
- MAX_NULLIFIER_READ_REQUESTS_PER_CALL, MAX_PUBLIC_CALL_STACK_LENGTH_PER_CALL,
- MAX_PUBLIC_DATA_READS_PER_CALL, MAX_PUBLIC_DATA_UPDATE_REQUESTS_PER_CALL, NUM_FIELDS_PER_SHA256,
- RETURN_VALUES_LENGTH
+ MAX_NULLIFIER_READ_REQUESTS_PER_CALL, MAX_NULLIFIER_NON_EXISTENT_READ_REQUESTS_PER_CALL,
+ MAX_PUBLIC_CALL_STACK_LENGTH_PER_CALL, MAX_PUBLIC_DATA_READS_PER_CALL,
+ MAX_PUBLIC_DATA_UPDATE_REQUESTS_PER_CALL, NUM_FIELDS_PER_SHA256, RETURN_VALUES_LENGTH
};
struct PublicCircuitPublicInputsBuilder {
@@ -19,6 +19,7 @@ struct PublicCircuitPublicInputsBuilder {
args_hash: Field,
return_values: BoundedVec,
nullifier_read_requests: BoundedVec,
+ nullifier_non_existent_read_requests: BoundedVec,
contract_storage_update_requests: BoundedVec,
contract_storage_reads: BoundedVec,
public_call_stack_hashes: BoundedVec,
@@ -46,6 +47,7 @@ impl PublicCircuitPublicInputsBuilder {
args_hash: self.args_hash,
return_values: self.return_values.storage,
nullifier_read_requests: self.nullifier_read_requests.storage,
+ nullifier_non_existent_read_requests: self.nullifier_non_existent_read_requests.storage,
contract_storage_update_requests: self.contract_storage_update_requests.storage,
contract_storage_reads: self.contract_storage_reads.storage,
public_call_stack_hashes: self.public_call_stack_hashes.storage,
diff --git a/noir-projects/noir-protocol-circuits/crates/types/src/tests/sort.nr b/noir-projects/noir-protocol-circuits/crates/types/src/tests/sort.nr
new file mode 100644
index 00000000000..d067eb1a2d9
--- /dev/null
+++ b/noir-projects/noir-protocol-circuits/crates/types/src/tests/sort.nr
@@ -0,0 +1,87 @@
+use crate::traits::{Empty, is_empty};
+
+struct SortedTuple {
+ value: T,
+ original_index: u64,
+}
+
+pub fn sort_high_to_low(
+ values: [T; N],
+ is_less_than: fn(T, T) -> bool
+) -> [SortedTuple; N] where T: Eq {
+ let mut sorted_tuples = [SortedTuple { value: values[0], original_index: 0 }; N];
+
+ for i in 0..N {
+ sorted_tuples[i] = SortedTuple {
+ value: values[i],
+ original_index: i,
+ };
+ }
+
+ sorted_tuples.sort_via(|a: SortedTuple, b: SortedTuple| (b.value == a.value) | is_less_than(b.value, a.value))
+}
+
+struct SortedResult {
+ sorted_array: [T; N],
+ sorted_index_hints: [u64; N],
+}
+
+pub fn sort_get_sorted_hints(
+ values: [T; N],
+ ordering: fn(T, T) -> bool
+) -> SortedResult where T: Eq + Empty {
+ let mut tuples = [SortedTuple { value: values[0], original_index: 0 }; N];
+ for i in 0..N {
+ tuples[i] = SortedTuple {
+ value: values[i],
+ original_index: i,
+ };
+ }
+
+ let sorted_tuples = tuples.sort_via(
+ |a: SortedTuple, b: SortedTuple| is_empty(b.value) | (!is_empty(a.value) & !is_empty(b.value) & ordering(a.value, b.value))
+ );
+
+ let sorted_array = sorted_tuples.map(|t: SortedTuple| t.value);
+ let mut sorted_index_hints = [0; N];
+ for i in 0..N {
+ if !is_empty(sorted_tuples[i].value) {
+ let original_index = sorted_tuples[i].original_index;
+ sorted_index_hints[original_index] = i;
+ }
+ }
+
+ SortedResult { sorted_array, sorted_index_hints }
+}
+
+#[test]
+fn sort_get_sorted_hints_asc_non_padded() {
+ let values = [40, 60, 20, 50];
+ let res = sort_get_sorted_hints(values, |a: Field, b: Field| a.lt(b));
+ assert_eq(res.sorted_array, [20, 40, 50, 60]);
+ assert_eq(res.sorted_index_hints, [1, 3, 0, 2]);
+}
+
+#[test]
+fn sort_get_sorted_hints_desc_non_padded() {
+ let values = [40, 20, 60, 50];
+ let res = sort_get_sorted_hints(values, |a: Field, b: Field| b.lt(a));
+ assert_eq(res.sorted_array, [60, 50, 40, 20]);
+ assert_eq(res.sorted_index_hints, [2, 3, 0, 1]);
+}
+
+#[test]
+fn sort_get_sorted_hints_asc_padded() {
+ let values = [40, 60, 20, 50, 0, 0];
+ let res = sort_get_sorted_hints(values, |a: Field, b: Field| a.lt(b));
+ assert_eq(res.sorted_array, [20, 40, 50, 60, 0, 0]);
+ assert_eq(res.sorted_index_hints, [1, 3, 0, 2, 0, 0]);
+}
+
+#[test]
+fn sort_get_sorted_hints_desc_padded() {
+ let values = [40, 20, 60, 50, 0, 0];
+ let res = sort_get_sorted_hints(values, |a: Field, b: Field| b.lt(a));
+ assert_eq(res.sorted_array, [60, 50, 40, 20, 0, 0]);
+ assert_eq(res.sorted_index_hints, [2, 3, 0, 1, 0, 0]);
+}
diff --git a/noir-projects/noir-protocol-circuits/crates/types/src/utils/arrays.nr b/noir-projects/noir-protocol-circuits/crates/types/src/utils/arrays.nr
index ec5e186d412..73af12d2969 100644
--- a/noir-projects/noir-protocol-circuits/crates/types/src/utils/arrays.nr
+++ b/noir-projects/noir-protocol-circuits/crates/types/src/utils/arrays.nr
@@ -57,6 +57,16 @@ pub fn array_eq(array: [T; N], expected: [T; S]) -> bool where T: Empty
eq
}
+pub fn find_index(array: [T; N], find: fn[Env](T) -> bool) -> u64 {
+ let mut index = N;
+ for i in 0..N {
+ if (index == N) & find(array[i]) {
+ index = i;
+ }
+ }
+ index
+}
+
pub fn array_cp(array: [T; N]) -> [T; S] where T: Empty {
let mut result: [T; S] = [T::empty(); S];
for i in 0..S {
@@ -102,6 +112,45 @@ pub fn array_merge(array1: [T; N], array2: [T; N]) -> [T; N] where T: Empt
result
}
+pub fn check_permutation(
+ original_array: [T; N],
+ permuted_array: [T; N],
+ original_indexes: [u64; N]
+) where T: Eq + Empty {
+ let mut seen_value = [false; N];
+ for i in 0..N {
+ let index = original_indexes[i];
+ let original_value = original_array[index];
+ assert(permuted_array[i].eq(original_value), "Invalid index");
+ assert(!seen_value[index], "Duplicated index");
+ seen_value[index] = true;
+ }
+}
+
+pub fn assert_sorted_array(
+ original_array: [T; N],
+ sorted_array: [T; N],
+ sorted_indexes: [u64; N],
+ ordering: fn[Env](T, T) -> bool
+) where T: Eq + Empty {
+ let mut seen_empty = false;
+ for i in 0..N {
+ let original_value = original_array[i];
+ if is_empty(original_value) {
+ seen_empty = true;
+ assert(is_empty(sorted_array[i]), "Empty values must not be mixed with sorted values");
+ } else {
+ assert(!seen_empty, "Empty values must be padded to the right");
+
+ let index = sorted_indexes[i];
+ assert(sorted_array[index].eq(original_value), "Invalid index");
+ if i != 0 {
+ assert(ordering(sorted_array[i - 1], sorted_array[i]), "Values not sorted");
+ }
+ }
+ }
+}
+
#[test]
fn smoke_validate_array() {
let valid_array = [];
@@ -147,3 +196,107 @@ fn test_array_length() {
assert_eq(array_length([123, 0, 456]), 1);
assert_eq(array_length([0, 123, 0, 456]), 0);
}
+
+#[test]
+fn find_index_greater_than_min() {
+ let values = [10, 20, 30, 40];
+ let min = 22;
+ let index = find_index(values, |v: Field| min.lt(v));
+ assert_eq(index, 2);
+}
+
+#[test]
+fn find_index_not_found() {
+ let values = [10, 20, 30, 40];
+ let min = 100;
+ let index = find_index(values, |v: Field| min.lt(v));
+ assert_eq(index, 4);
+}
+
+#[test]
+fn check_permutation_basic_test() {
+ let original_array = [1, 2, 3];
+ let permuted_array = [3, 1, 2];
+ let indexes = [2, 0, 1];
+ check_permutation(original_array, permuted_array, indexes);
+}
+
+#[test(should_fail_with = "Duplicated index")]
+fn check_permutation_duplicated_index() {
+ let original_array = [0, 1, 0];
+ let permuted_array = [1, 0, 0];
+ let indexes = [1, 0, 0];
+ check_permutation(original_array, permuted_array, indexes);
+}
+
+#[test(should_fail_with = "Invalid index")]
+fn check_permutation_invalid_index() {
+ let original_array = [0, 1, 2];
+ let permuted_array = [1, 0, 0];
+ let indexes = [1, 0, 2];
+ check_permutation(original_array, permuted_array, indexes);
+}
+
+#[test]
+fn assert_sorted_array_asc() {
+ let original = [30, 20, 90, 50, 0, 0];
+ let sorted = [20, 30, 50, 90, 0, 0];
+ let indexes = [1, 0, 3, 2, 0, 0];
+ assert_sorted_array(original, sorted, indexes, |a: Field, b: Field| a.lt(b));
+}
+
+#[test]
+fn assert_sorted_array_desc() {
+ let original = [30, 20, 90, 50, 0, 0];
+ let sorted = [90, 50, 30, 20, 0, 0];
+ let indexes = [2, 3, 0, 1, 0, 0];
+ assert_sorted_array(original, sorted, indexes, |a: Field, b: Field| b.lt(a));
+}
+
+#[test]
+fn assert_sorted_array_all_empty() {
+ let original = [0, 0, 0, 0, 0, 0];
+ let sorted = [0, 0, 0, 0, 0, 0];
+ let indexes = [0, 0, 0, 0, 0, 0];
+ assert_sorted_array(original, sorted, indexes, |a: Field, b: Field| a.lt(b));
+}
+
+#[test(should_fail_with="Values not sorted")]
+fn assert_sorted_array_failed_ordering() {
+ let original = [30, 20, 90, 50, 0, 0];
+ let sorted = [20, 30, 90, 50, 0, 0];
+ let indexes = [1, 0, 2, 3, 0, 0];
+ assert_sorted_array(original, sorted, indexes, |a: Field, b: Field| a.lt(b));
+}
+
+#[test(should_fail_with="Values not sorted")]
+fn assert_sorted_array_failed_misplaced_sorted() {
+ let original = [30, 20, 90, 50, 0, 0];
+ let sorted = [20, 30, 50, 0, 0, 90];
+ let indexes = [1, 0, 5, 2, 0, 0];
+ assert_sorted_array(original, sorted, indexes, |a: Field, b: Field| a.lt(b));
+}
+
+#[test(should_fail_with="Invalid index")]
+fn assert_sorted_array_failed_wrong_index() {
+ let original = [30, 20, 90, 50, 0, 0];
+ let sorted = [20, 30, 50, 90, 0, 0];
+ let indexes = [1, 1, 2, 3, 0, 0];
+ assert_sorted_array(original, sorted, indexes, |a: Field, b: Field| a.lt(b));
+}
+
+#[test(should_fail_with="Empty values must be padded to the right")]
+fn assert_sorted_array_failed_not_padded() {
+ let original = [30, 20, 90, 0, 50, 0];
+ let sorted = [20, 30, 90, 0, 0, 0];
+ let indexes = [1, 0, 2, 0, 0, 0];
+ assert_sorted_array(original, sorted, indexes, |a: Field, b: Field| a.lt(b));
+}
+
+#[test(should_fail_with="Empty values must not be mixed with sorted values")]
+fn assert_sorted_array_failed_mixed_empty() {
+ let original = [30, 20, 90, 0, 0, 0];
+ let sorted = [20, 30, 90, 0, 0, 10];
+ let indexes = [1, 0, 2, 0, 0, 0];
+ assert_sorted_array(original, sorted, indexes, |a: Field, b: Field| a.lt(b));
+}
diff --git a/yarn-project/circuits.js/src/constants.gen.ts b/yarn-project/circuits.js/src/constants.gen.ts
index dbbc59cf255..a0af43b2763 100644
--- a/yarn-project/circuits.js/src/constants.gen.ts
+++ b/yarn-project/circuits.js/src/constants.gen.ts
@@ -11,6 +11,7 @@ export const MAX_PUBLIC_DATA_UPDATE_REQUESTS_PER_CALL = 16;
export const MAX_PUBLIC_DATA_READS_PER_CALL = 16;
export const MAX_NOTE_HASH_READ_REQUESTS_PER_CALL = 32;
export const MAX_NULLIFIER_READ_REQUESTS_PER_CALL = 2;
+export const MAX_NULLIFIER_NON_EXISTENT_READ_REQUESTS_PER_CALL = 2;
export const MAX_NULLIFIER_KEY_VALIDATION_REQUESTS_PER_CALL = 1;
export const MAX_NEW_NOTE_HASHES_PER_TX = 64;
export const MAX_NON_REVERTIBLE_NOTE_HASHES_PER_TX = 8;
@@ -31,6 +32,7 @@ export const MAX_REVERTIBLE_PUBLIC_DATA_READS_PER_TX = 16;
export const MAX_NEW_L2_TO_L1_MSGS_PER_TX = 2;
export const MAX_NOTE_HASH_READ_REQUESTS_PER_TX = 128;
export const MAX_NULLIFIER_READ_REQUESTS_PER_TX = 8;
+export const MAX_NULLIFIER_NON_EXISTENT_READ_REQUESTS_PER_TX = 8;
export const MAX_NULLIFIER_KEY_VALIDATION_REQUESTS_PER_TX = 4;
export const NUM_ENCRYPTED_LOGS_HASHES_PER_TX = 1;
export const NUM_UNENCRYPTED_LOGS_HASHES_PER_TX = 1;
@@ -98,7 +100,7 @@ export const NULLIFIER_KEY_VALIDATION_REQUEST_CONTEXT_LENGTH = 5;
export const PARTIAL_STATE_REFERENCE_LENGTH = 6;
export const PRIVATE_CALL_STACK_ITEM_LENGTH = 214;
export const PRIVATE_CIRCUIT_PUBLIC_INPUTS_LENGTH = 209;
-export const PUBLIC_CIRCUIT_PUBLIC_INPUTS_LENGTH = 196;
+export const PUBLIC_CIRCUIT_PUBLIC_INPUTS_LENGTH = 200;
export const STATE_REFERENCE_LENGTH = 8;
export const TX_CONTEXT_DATA_LENGTH = 4;
export const TX_REQUEST_LENGTH = 10;
diff --git a/yarn-project/circuits.js/src/contract/__snapshots__/contract_class.test.ts.snap b/yarn-project/circuits.js/src/contract/__snapshots__/contract_class.test.ts.snap
index 5dc051a1f5f..b400f769558 100644
--- a/yarn-project/circuits.js/src/contract/__snapshots__/contract_class.test.ts.snap
+++ b/yarn-project/circuits.js/src/contract/__snapshots__/contract_class.test.ts.snap
@@ -9,18 +9,18 @@ exports[`ContractClass creates a contract class from a contract compilation arti
"selector": {
"value": 2381782501
},
- "bytecode": "0x1f8b08000000000000ffed9d079454c799ef6fcf0c493dcd30420284080392c8a1a72730e426470990002123230d0c20860c4316306491a364d972daf57a83d7de607bd76b6ff0aec306e7b45edb72ce3ee7f99df7ce79efed396f7775b6aabb3ecf9f9abaedeee156eb6bf8ee39df74dd6fead6f7fbbe5bb76ef5adea5bbf0a82201664b772258f049d37fa7fda7c26ef6cab8db0aca44fce5889709695086779897056940867b712e1ec5e229c3d4a84b3678970f68a9053b39505b76f51f3dee721ae5133c64b2ca6952510d34489c5b47709c4b42a288d36aa4f8970569708e7fd25c2d9b744381f2811ce074b84b35f8970f62f11ce0125c2f95089700e2c11ce874b84735089700e2e11ce2125c239b444386b4a84735889700e2f11ce474a84f3d112e17c2c42ce51c039c27c8e349ff4bfd1e6738cf91c6b3ec799cff1c6c70ab33f41c944cda6a4d6fa5f4a499d927a250de67f35e67f8d4a262969523259c9142553954c53325dc90c25338defb394cc563247c95c25f394cc57b240c942258b942c56b244c952258f2b7942c93225cb95ac50f2a492a794ac54b2ca6259ade469256b943ca3e42d4ad62a7956c95b95ac53f29c92e795342b59af64839216251b956c52b259c90b4ab6286955b255c93625db95ec50b253c92e25bb95ec51b257499b927d56ccf62b39a0e4a0924316e761254794bca8e4a892634a8e2b69577242c94925a7949c567246c95925e794bca4e4bc920b4a2e2ab9a4e4b2922b4aae2ab9a6e4ba921b4a6e2ab9a5e46525af28799b925795bcddb050657f8792d794bc53c9bb94bc5bc97b94bc57c9ef28f95d25ef53f27b4adeafe4f795fc81923f54f2474a3ea0e48f957c50c98794fc89923f55f2674afe5cc987957c44c94795fc8592bf54f231257fa5e4e34a3ea1e4af95fc8d92bf55f2774a3ea9e4ef95fc83924f29f9b492cf28f9ac927f54f24f4afe59c9bf5831ff9c92cf2bf982922f9affd1b3ae2f29f9b2497fc57c7ed57c7ecd7c7edd3ae61b4afed5d27d53c9bf59ba6f29f9b6497fc77cbe6e3ebf6b3ebf673ebf6f3e7f603e7f683e7f643e7f6c3e7f623e7f6a3e7f663e7f6e3e7f613e7f693e7fa5e4fd03b2e99e41c7960e226a77ea3736eb71120af688e0f64dc7a2dcfc8f3e6b8cbec2ecd327c5ae9bd9ef66e9bb9bfdee56393dcd7e4f4b5f6df6ab2d7d5fb3dfd7d23f68f61fb4f4fdcd7e7fd0c703785e6af45a576e5431d0513d2c035db7e0f698685d772a0e743d82db63a175741ebb83ae97d1f500dd7d46d7137471a3eb453153526974e920aa3a916cd6e526a22ed78c21f58e9e77832eb7ca136f9fe87937ea72ab3df0eafa71bf29ab0fd49bbe46570dba078cee7ed03d68747d41d7cfe81e005d7fa37b1074a6990afa81ee21a3eb0fba81463700740f1bdd43a01b6474034137d8e81e06dd10a31b04baa1463718743546370474c38c6e28e8861b5d0de8687eca30d03d6a74c341f798d13d023a6a531f051df5eb1e333add4efc7b00c7187d19e846523b0cba51d406836e34b5bfa01b436d2fe8c6826dd28d83768574e38d8eda28fdbf46934e07515d13a9cc353129ea7255c9badcc9d1979b19739b1274c4350d762641aca69a7484f37a6ad176cc08d9217d05a417425eca47f1a0fb0cb1ebfb4993494fcd715ca3755c02f23439fc4f07d1fa3fd9e2996c31ebfa3f1d38a2afb37529a9b3796f05d7d9d590d7ae7bd4e7b91bebec12e0f050671bfdd4d95452ea6cf6994310b8eb1ef57befc63afb0c70445f671ba4cee6bf155c67f7405ebbeed1779fbbb1ce6e068ee8ebeca446e91be4bd155c67db21af5df7e8fbefdd5867f70187873adb2ced6cde5bc175f60ae4b5eb1e3d8bb91bebec29e088bece4ef65467eba4ce06d9f1a32070d73d7a2e7837d6d9ebc0117d9dddd02c7d83bcb782ebec0720af5df7e819f5dd5867df031c1eeaacafe7b329a9b3d971f32070d73d1a2fb91bebec874c5a8f337cc58c330c02dd578d6e30e8be66744340f775a31b0a7e457f0db4d4cb3590f756f035f039c86bd7e51a93be1baf814f0287873adb287536efade03afb2dc86bd7bde1267d37d6d92f0287873adb247536efade03afb73c86bd73d9ad37037d6d9d74d5af717be63cd77d3bad78d6e24e8be6b74a340f73da31b0dbaef1bdd18d0fdc0e8c682ee8746370e743f32baf1a0fbb1d14d00dd4f8c6e22e87e6a7449d0fdcce86a41f773a34b81ee17465707ba5f1a5d3de87e65740d46a7c704687eca678cae27d84b07d19ddb38f8465bccda4f43bad62f4f32013c68ab2e7a5b75daf75490bfef75c053efc1f738d8c887a71e781aa2e7a9f572ef4966cf71ca8a691c6ca5c02f0ff7a84c7bd618dc1e53da277b09d0e1b53ac9c1d8143d632a06b6a86cda6f0246d2350023b5a574fde8b6b92ad6c1ebe15acadc9fd15e1a38c85e05e49939a0236f5fc35609ffa736a0128ec7b695535dd18c54471a8bcf98ca97b1c162f4747d6762d660f1b86c4ff164db8e05d5a92945b0dd64d9aeb76c631b425bae361efb761efad5b59efa8c99f6609a298bfaeb6407ef5bd3210651f984b6a9bf4e76485f01e954ac232fe5a37850db43ecfa3aa27389ecf67193ace31290678ac3ff7410adfff6f787a916b36e7747c23dc1c3f570db77362a9bf6eb2176534362370562477946828eee118da0a3b696cac0fe2db6c33efa4bb1e0f6be741af61b1cdc4dc0d8e060f4d0c74ce56a9beb819174938127e5296661df2d5362db473dcd5c976483fa6a740d91bd0ac833b8bc23ef6268337cd44fbc1668cbf73b50f4e72995f94e565f000f9e3b0ffdec5a4ff53189dfe7df08a2ad6b769b586fc52aec3b7fd243fcb0ee53d9b44ff684599885599885599885599885599885599885599885599885599885599885993f338e65e13c1ecad7c084d19e0fe5eb397fe6fd55a62c1c037addebfca054661c80e24ff301465b3e57409e5fc73ad87e00f383e8ff38dfc9752e7dcc95cc752ec99e6bbe92cff1e63a8ba7ce110b0eb66b22b39ddae0e7fca692fafd31fa1d6cf5d639b5c7e633ef76b374ba9ed69477f8ed63ecafd0b1486c4b281de5d85e0278d0968f7383d75e59707bfb81f7195ff358a8ada6f1f22996ed0ac8f31f309f682afc3f1d746e2f300f954dfba3e1d8a956d955fefccd3937017f0761ff1e837c6b04eedfccff29ebf0f17d26ede91e972a741e35b6d3d1f701b273085205f02481c7c77dcc535f2789f531ea3904f61c31571fcafe4d8ecfdfc3d8fd60dacff51b1e6116666116666116666116666116e6e49d6dc22cccc22cccc22cccc22cccc22cccc21c31b3e671bd8382f2d531612cd2bc8bcc7806bd8709c7c5ae9775d8f53d0648634e632c9ff177ab5f2beb607bd9a42b83ce732dc2cea5afb1b5b07349f65cef41f135a61c035b5476ad23161c6cd744663b3b8720faf3db318720659dd33aeb7ce2d8395e43ef837a6acfa788079de72478bac6739e1bb287d712ce7fa134fe36db47acf15e62cf29c2f7f2519e0f99d8d2d879f46d752ae9b3dda0f795d03c8194c357caf36168fb3e6ad2387fa40ecafab4e3ffb4e51aa7a6f8699f6744ef73e6fcce3465d1f99de1b09d06d6886cd7a2ed985536e92b20fda9b2ce0c140f8a35b1eb6b04dffb12765cbd755c02f24c77f89f8ed8ff19160f9e63bde9baf331a8679f86fbbfaf36697a488c46438c288fe7f7073ae703da731ab11ded61e5a163f1bd689f87362a6ceeaaeb1e30cd937f61f700b297083adf17f299637aafcf117b1dda8ba9f0ff7470e773c45e873a84f3b402abfc31503e71f508c2ef2d94e707d67dd4c31caebcde8fe3fa3e40bc38078df2fc14daaa7e66ae6621df07deacef76aeef03785c98efd82e447d6fc4fa882cf87e35caf33fadfa383584bbc971ecff0e39966265bf2bac32e81c3f3fef59cbb637332c5fe89a9a0ebe509e7fb7ae9be8fb4cd9fea7af77ca515f87daa05a87af94e73fe15a7b03fa97749ef0be9028effc7fda72f53f297edae759d1fb9c39bfb34d59747e67396ccf01d6886cd7a26dea7f921dd25740bab2bc232fe5a37850ac89bd123891dd3e6e8a755c02f2a41dfea723f67f96c533cb62ceb43df06eb204ccbff7d556a703778cc6408c7e630f74741fc1f780ba9e75f8ba9786f5e3f0775ba4c3b6bd1fc4b458bfa9b19fe9b9fa27632c7eec9f3c6c98ab4c9cedbcf6b34a3a2eca39cbf81b11ec17e26f447c7d474a04b7c7336171f8b45d65d9ae2aa2ed6acb7675116d4bcc25e69c62ce69ad081c9b2a03461ff73abcafe6c3e8baff9503a3afdff1a50a60ac0346bcdf11a387f7b0d676752d0aeceb7403461fbf412df47935be83197f8b4c8c3ede855fe8fb6cf15dd6745c0f60f4b1c601ae55900fa36b8d8c9ef0e9613d8cdaaebe371fd7c8e8058c3edea51f0f6e7fffff6f63c4b59ce8b8fb80d1c738523cb8fdb9da6f63c477b0d37171cf8cb9eeed9ee783a40a7d0681f32ff01914c5c9358fc5d7b37afbd918ede3b37ae40dfcc531671f69aadf58a430eeb4e53a87aef8f4369f71f8bfdfe789b78ff152d9b43f1d184957e5378e39fb9ad3fcc6228571a72dd73974c5a78ff98c079dd7fff03466ed1ca3a47d1cc7255db5679eb8c5f3dbe2e862bc1f18677a629c5100e34c60a4e3fa0263da13e3cc0218d3c048c73d008c1e9e576718d30530e2735dd23f088cb33d31ce2a80713630d271fd80d1c7b3e738d8cd87710e30d271fd8171ae27c6390530ce05463a6e0030cef3c438b700c679c048c73d048cf33d31ce2b80713e30d2710381718127c6f905302e00463aee61605ce8897141018c0b81918e1b048c8b3c312e2c80711130d271838171b127c64505302e06463a6e08302ef1c4b8b800c625c048c70d05c6a59e189714c0b81418e9b89a12601c56028cc34b80f19112607cb404181f2b01c69e25c03816181f8f9e31f3fd7a69018c8f03cfb2e879eae360231f9e65c0f344f43cb59efccccccf5a6eca8afa1d692bac583d6ec52a01799643fc5678885f0cec52d9b44ff6845998c398350fb54fc41a877c4b993092ee09cf3c718b476fb9dac7157e799209473cb4ad27a3b7952ad4f727816765f43c997bd59305f0ac049ea7a2e7a9f5e467e69eb2caf2e949cba704e4c1766395073f636097caa6fd550edb3541b4b1589d472c563b785617391664af50e6e525c8cc21ced816126b1cf22d63c248baa73cf3c42d1ebde56a1f5d8c2bfc32a6bacaa879d644ce937d47f4ea0278d600cfd391f364ef29d1fb996d479fb17c5a6df994803cd8263de3c1cf18d8a5b269ff19380fc22cccc22cccc22cccc22cccc22cccc22cccc22cccc22cccc22cccc22cccc2cc9b19c71888350ef956326124ddd39e79e2168fde728d3bb81857f8654c759551f3ac8d9c273b56f34c013c6b81e72d91f364c76aa2f7333b56f3ace5d333964f09c883d7f7b31efc8c815d2a9bf69f85f320ccc22cccc22cccc22cccc22cccc22cccc22cccc22cccc22cccc22cccc22cccbc99718c8158e3906f0d1346d2bdc5334fdce2d15bae710717e30abf8ca9ae326a9e75d1f3647eabf96c013ceb80e7add1f3d47af2333356f39ce5d3b3964f09c883d7f7731efc8c815d2a9bf69f83f320ccc2ec62c6368b58e3906f2d1346d2bdd5334fdce2d15bae76ccc5b8c22f63aaab8c9aa739729eec38fd7305f03403cff391f364dbfee8fdccb6fdeb2d9f9eb37c4a401ebcbed77bf0330676a96cda5f0fe7a110e65525c82c71963887314b9c25ce61cc1267897318b3c459e21cc62c71963887314b9c25ce61cc1267897318b3c459e21cc62c71963887314b9cef9d38e31803b1c621df3a268ca47bde334fdce2d15bccda4f43dac5b8c22f63aaab8c9aa725729ebacc58cdfa02785a806743e43cd9b19ae8fdcc8ed56cb47c5a6ff994803cd8266df4e0670cec52d9b4bf11cec3ddcebcaa0499a56e148759ea863087314bdd10e63066a91bc21cc62c754398c398a56e087318b3d40d610e6396ba21cc61cc523784398c59ea863087314bdd10e63066a91bc21cc62c754398c398a56e087318b3d40d610e63e65037708e0eb1c6215f331346d26df0cc13b778f4966bde8e8b71855fc654571935cfe6e87932ef24d95800cf66e0d9143d4fad273f33739d5eb07cda68f994803cd826bde0c1cf18d8a5b269ff05380fc22ccc2e666cb388350ef95a9830926e93679eb8c5a3b75ced988b71855fc6545719354fab279e170ae069059e2dd1f3d47af233d3f66fb57c7ac1f2290179f0fadeeac1cf18d8a5b2697f2b9c875263c66b8958e3906f331346d26d011e0ff5aee036a9d511c71525c0b8bc04187b9500e37d25c01807460ffdf4cc359cb078687fb3dff8a4a2884f25c4896bbb97001e5fe7b0cae2a972c482eb39ec6d3e399fc32ae0f1750eab2d9e6a472cb89ec33ee693f339acf61bc748dafbfb4b80b16f09303e50028c0f960063bf1260ec5f028c034a80f1a112601c58028c0f9700e3a012601c5c028c434a8071680930fa7ea691eb7b6e6b116c877d3f2b86edb0ef15c5b02d3197984bcc25e6127389f99dda96984bcc25e66f7ecc7d3ccbc5e7c6b4e5fa0ee17abeeb7b4c5e18f930ae62ca883cb1e87892e83bdadac6c077622835c65525c0b8bc0418258ed931d4ae306a9e1d9e78b615c0b30378b647cf53ebc9cfcc5cc39d964fdb2c9f129007ebc14e0f7ec6c02e954dfb3be13c941a335e4bc41a877cad4c1849b71d787c5d5f855cef787dedf2c4b3a3009e5dc0e3e37c79f23373bdefb67cda61f994803c78edecf6e0670cec52d9b4bf1bce43a931e3f54eac71c8d7ca8491743b81c7d7f555c8f58ed7d71e4f3cbb0ae0d9033c3ece97273f33d7fb5ecba75d964f09c883d7ce5e0f7ec6c02e954dfb7be13c941a335eefc41a877cad4c1849b71b783cd4bb82bf73ec71c471790930ae2a014689a3c49113a3c4f1de89a3300aa3300ae39bc1580a6db8dc670a7f3680dfc5dba2e7a9c7ef6df9f0b4018f8fef769efccc3c1bd867f9b4c7f2290179b01eecf3e0670cec52d9b4bf0fce83300bb38b19db2c628d43be56268ca4db0b3c1eaeef82dbfe36471c57f8654c759551f3ec8f9ca73e89f5251f9efdc0e3a34ef9f133dbf61fb07c6ab37c4a401ebcbe0f78f0330676a96cda3f00e7a110e65525c82c71963887314b9c25ce61cc1267897318b3c459e21cc62c71963887314b9c25ce61cc1267897318b3c459e21cc62c71963887314b9c25ce61cc1267897318b3c459e21cc62c71963887314b9c25ce61cc1267897318b3c459e21cc62c71963887314b9c25ce61cc1267897318b3c459e21cc62c71ee1a33ce7127d638e46b65c248ba7dc0b3df034fa1f3f0f73be2b8c22f63aaab8c9ae760f43cf5585ff2e139083c3eea94273f33bf153864f9b4dff2290179b04d3ae4c1cf18d8a5b269ff109c874298579520b3c4b96bccd866116b1cf2b5326124dd01e0f1707d17dcf61f74c471855fc654571935cfe1e879eab1bee4c37318787cd4294f7e66dafe23964f072d9f129007dba4231efc8c815d2a9bf68fc0791066617631639b45ac71c8d7ca8491748780c7c3f55d70db7fd811c7157e19535d65d43c2f46ce934a627dc987e745e0f151a7fcf8996dfb8f5a3e1db67c4a401ebcbe8f7af0330676a96cda3f0ae7a110e65525c82c71963887314b9c25ce61cc1267897318b3c459e21cc62c71963887314b9c25ce61cc1267897318b3c459e21cc62c71963887314b9cef9d38e31803b1c6215f2b1346d21d011e0fcfe30b1eab79d111c7157e19535d65d43cc722e7a94b627dc987e718f0f8a8537efccc8ed51cb77c7ad1f2290179b04d3aeec1cf18d8a5b269ff389c87bb9d795509324bdd280eb3d40d610e6396ba21cc61cc523784398c59ea863087314bdd10e63066a91bc21cc62c754398c398a56e087318b3d40d610e6396ba21cc61cc523784398c59ea863087314bdd10e63066a91bc21cc6cca16ee01c1d628d43be56268ca43b0a3cc73cf0143a8fe898238e2bfc32a6bacaa879daa3e7a9c7fa920f4f3bf0f8a8539efccccc753a61f974ccf2290179b04d3ae1c1cf18d8a5b2699fec09b3308731639b45ac71c8d7ca849174c781a7dd034fa1ed6a3b30bada7e0f8ca9ae326a9e93d1f3d4637dc987e724f0f8a8539efcccb4fda72c9fda2d9f129007afef531efc8c815d2a9bf64fc1791066617631639bd56e3ee390af950923e94e008f87ebbbe0b6ffa4238e2bfc32a6bacaa8794e47cf538ff5251f9ed3c0e3a34e79f233d3f69fb17c3a69f994803c787d9ff1e0670cec52d9b47f06ce83300bb38b19db2c628d43be56268ca43b053c1eaeef82dbfed38e38ae2801c6e525c0b8aa04183dc731d55546cd73d613cfe90278ce028f8ff6c3939f99fbfc39cba7d3964f09c883f5e09c073f636097caa6fd73701e4a8d19af25628d43be56268ca43b033cbeaeaf42ae77bcbe5ef2c473b6009e9780c7c7f9f2e467e67a3f6ff974d6f2290179f0da39efc1cf18d8a5b269ff3c9c875263c6eb9d58e390af950923e9ce018fafebab90eb1dafaf0b9e785e2a80e702f0f8385f9efccc5cef172d9f5eb27c4a401ebc762e7af0330676a96cdabf08e7a1d498f17a27d638e46b65c248baf3c0e3a1de15fc9de382238ecb4b80715509304a1c258e9c18258ef74e1c85511885b130c6ad25c028e75a18b932b67a608c197bc843fbad45b05d65d9ae2aa2ed6acb7675116d4bcc25e6127389b9c45c627ea7b625e6127389b9c45c622e31bf53db127389b9c45c622e319798dfa96d89b9c45c622e3197984bccefd4b6c45c626edbf630dfb4e0f9dc1781e782875878f233a9cbbd64ca7a23c2f8e9585db662d56ac52a01792e41fc2e7b889f6bce35ed93bd4299473060f6643bd55b95d10bfc271babac7868fb573cf91ed6e65d2982edb036af18b6c3dabc62d896984bcc25e6f76ecc315d1174fe0d922ee3aa497733fb947f2b1c47793e5199fdac0ae47cfab02dd790c45c622e317f33628e71995f049ec0e20972f0a499f14c61c653cf8c6702339e31cc789a99f1ac61c6b394194f39339e05cc786631e329c6f3ac4278a632e36960c6b39019cf6c663cd398f13432e319c58c27c98c6704339e16663c6b99f12c63c6b38819cf1c663cd399f16c66c63389194f2d339e91cc782a99f12498f12c66c6339719cf0c663c1399f13431e34931e359c78c6725339edecc78aa98f1cc63c6338e19cf4c663c9399f1d431e3e9c38ca79a19cf68663c4b98f18c67c61363c0130f3acf018fc3ff2f82aecc3ab6879293033afe7fcde8cbe098eb265dee28fb1ae8686ed575c7b118a76be04bdaa49377b665e284b6d2b04ff62a81e33a139ef1cc789630e319cd8ca79a194f1f663c75cc782633e399c98c671c339e79cc78aa98f1f466c6b39219cf3a663c29663c4dcc782632e399c18c672e339ec5cc7812cc782a99f18c64c653cb8c6712339ecdcc78a633e399c38c6711339e65cc78d632e36961c63382194f9219cf28663c8dcc78a631e399cd8c6721339e06663c5399f15c64c6338b19cf02663ce5cc789632e359c38ca79919cf18663c1398f1d433e399c28c27cd8c67be83c7d7bac334de4e65d3fe4526b63d9c87cc7be16e78f2e9a629abbb2997f8c95e05e499600662f4f8101e4b5cf6fc08ecdbdc84185df5e44bd8bb21ae16c176d8bb218a61bbdab25d5d44db12f3f098df8cde760ae71cd116b3f6d390c6ebcec75c2d4f7eded6e645fd2ecc5b56acae5ab14a409e1b10bf5b1ee2e76a47699fec15ca3c820133d68b9a20da7af172f43efde61d9b14d797adf8a25faf788a69585bfa4a116c87b5a5c5b01dd69616c3b6c45c622e31bfb763fe36938eb09f91441bfa3b14dd3fde0676df6ed2b108edeab25e3565d13b4189e3edc04379b6c2773aa97f72cddf0b31b7d3f48c027fc7efeb1952d83929c6f3abb073520cdb61e7a418b625e6e1317f8707dbf1e0f6b512f496eb19c53b80e7550f3c9efcccdc6b5fb37cba68f994803cd8f6bde6c1cf18d8a5b269ff35e0a10ddf13eba31ee473ce91e716339e34339e29cc78ea99f14c60c63386194f33339e35cc789632e32967c6b38019cf2c663c5399f13430e359c88c6736339e69cc781a99f18c62c69364c63382194f0b339eb5cc789631e3798519cf22663c7398f14c67c6b39919cf24663cb5cc782a99f12498f12c66c6339719cf0c663c1399f13431e34931e359c78c6725339edecc78aa98f1cc63c6338e19cf4c663c9399f1d431e3e9c38ca79a19cf68663c4b98f18c67c61363c013f69e58faff2dd0d11839be3bf69d26fd2ae8ca1c3668ace635d055983495a1df3b7b7940e7b2314ebee605a0ad34ec933d7c4fec3b99f08c67c6b38419cf68663cd5cc78fa30e3a963c6339919cf4c663ce398f1cc63c653c58ca737339e95cc78d631e34931e36962c6339119cf0c663c7399f12c66c69360c653c98ca79619cf24663c9b99f14c67c6338719cf22663caf30e359c68c672d339e16663c2398f12499f18c62c6d3c88c671a339ed9cc781632e36960c6339519cf2c663c0b98f19433e359ca8c670d339e66663c6398f14c60c653cf8c670a339e34339e5bcc78e63b787cbd9f2eec9d0fb78a603bec9d0fc5b01df6ce8762d8969887c7dcc3bb6e32ef02c5f742eacd9eeb9286b4e7dff7d4c783dbdfabf3db78f0bd1f3eda254f7e66de41f92e53964ea7232a57c7eadd41c7a6cba578e2fc2aca830cef866322e2c9f93e11b227ccc21cc68cefa42556d7dcbb379b9174af018f8f7643fb3ece9445e5eb77f425fa74d8f5f09ef77a1d07fb3defc4e17acffbe6fe1d6cd586ad32e8fc2e5c5ceb1ecfa5efb508a86cd75a04e40fae45e0ebbe7fc3e2b9e188c59b69fbba07db85f639ae3b78a27ccf26b63168cbc77bdfb5efd782fc7d77bd873e42df6b13c083b63cbce73dd376d17c5c2a5fb70f23fb788d793d5ef7d4768db77cae803c73a0ed1a9ba3ed2a0b3aae197c4f7959d0f9ddef6f18bd5d464d20dfa77cd896efb03c63eea16f52f077585c8f21fa7e462ad3cedd2880e71af0f8b8df7aea4f253ddd9332df61af58b1ba61c52a01793cdfb73275fb9ac543fb644f98855998855998855998855998855998855998855998855998855998855998f933e37830b1e2b8ed2d268ca4c3316b1fcff9b5ef134c5954be1e1bfd429f0ebbd18f5ba492384781c66d27583e57409e3efd3ad8be02e3b6f6bc86b073e96b9df7b07349f62a83cef3507c8ed385cd8129c6186121b66b22b39ddae0e7fca692bd83ec9adf37ac737acb713e7db415387f8ab65c638d5780c7c3586cadafb923daa7cb964fd72d9f129007d7b1b8ecc14fd77d8df62f030f6db84e9eaf7b4660f1048ef8d056c68c27cd8ca727339e29cc78ea99f14c60c6f328339e31cc789a99f10c61c6b38619cf52663c0398f1dccf8ca717339e72663c0b98f1cc62c63395194f03339ec798f10c65c6f310339eb1cc78fa32e3b98f19cf42663c15cc786633e399c68ca79119cf28663c49663c2398f1b430e3a961c6b39619cf40663ccb98f13cc08c27ce8c6711339e6ecc78e630e399ce8c6733339e49cc786a99f18c64c6338c19cfc3cc787c8fa317caf320339e4a663c09663c8b99f17467c6339719cf0c663c4dcc782632e34931e319ce8c671d339e41cc785632e3e9c78ca737339e2a663c3d98f1cc63c6338e19cf4c663c9399f1d431e3798419cf60663c3799f1f467c6b38419cf68663c7d98f15433e319cf8c27c680271e74fead471cfe7f1d74974d1a7fcf56e6288fe6e9517eddaf7a7940e7b2cb1c655f7130609c2e812f69934eded976dbef3062a65cda277b95c0718509cf78663cd5cc78fa30e319cd8c6709339efecc786e32e319cc8ce711663c75cc782633e399c98c671c339e79cc787a30e3a962c6d39b194f3f663c2b99f10c62c6b38e19cf70663c29663c1399f13431e399c18c672e339eeecc781633e34930e3a964c6f320339e6bcc781e66c6338c19cf48663cb5cc782631e3d9cc8c673a339e39cc78ba31e359c48c27ce8ce701663ccb98f10c64c6b396194f0d339e16663c2398f12499f18c62c6d3c88c671a339ed9cc782a98f12c64c6731f339ebecc78c632e3798819cf50663c8f31e36960c6339519cf2c663c0b98f19433e3e9c58ce77e663c0398f12c65c6b38619cf10663ccdcc78c630e3799419cf04663cf5cc78a630e3e9c98c27cd8ca78c19cf7c8b07ffafbfdbd3f8ea65d0d1ffffafe91c54193f2e5bf622f02319b6f683af98e19676c4246ced070e3c69663c3d99f14c61c653cf8c6702339e4799f18c61c6d3cc8c6708339e35cc789632e319c08ce77e663cbd98f19433e359c08c6716339ea9cc781a98f13cc68c6728339e8798f18c65c6d39719cf7dcc781632e3a960c6339b19cf34663c8dcc784631e34932e36961c653c38c672d339e81cc789631e37980194f9c19cf22663cdd98f1cc61c6339d19cf66663c9398f1d432e319c98c6718339e8799f15c63c6f320339e4a663c09663c8b99f17467c6339719cf0c663c4dcc782632e34931e319ce8c671d339e41cc785632e3e9c78ca737339e2a663c3d98f1cc63c6338e19cf4c663c9399f1d431e3798419cf60663c3799f1f467c6b38419cf68663c7d98f15433e319cf8c27c680276cad05fa7f39e82e9af44dd05d30e96ba03b6fd29741f7924357e660217b174147f3142e808ec636ce838e9e87902ddd9f7b6d4067d632874fe50ed60b0e9f2e3a8ec5f348c7a48368cf23da4ac33ed9ab048e8b4c78c633e3a966c6d38719cf68663c4b98f1f467c6739319cf60663c8f30e3a963c6339919cf4c663ce398f1cc63c6d383194f15339edecc78fa31e359c98c6710339e75cc788633e34931e399c88ca78919cf0c663c7399f17467c6b398194f82194f25339e0799f15c63c6f330339e61cc784632e3a965c6338919cf66663cd399f1cc61c6d38d19cf22663c71663c0f30e359c68c6720339eb5cc786a98f1b430e34932e319c58ca79119cf34663cb399f15430e359c88ce73e663c7d99f18c65c6f310339ea1cc781e63c6d3c08c672a339e59cc781630e32967c6d38b19cffdcc780630e359ca8c670d339e21cc789a99f18c61c6f328339e09cc78ea99f14c61c6d393194f9a194f19339ef9160f8e61264147e95ad0513a053a4ad7818ed2f5a0a37403e828dd083a4a4f021da59b4047e9c9a0a3345d23f1a0c3f762bc879f6c51d9b47f0918693eb3eb9a9e0adc972c9de63ee789fb92c54dfbe780d1e50bb14d03ee73964e739ff5c47dcee2a6fdb3c0e8f285d8a6838ed2334047e999a09b09f64847e959a0a3f46cd0517a0ee8283d1774949e073a4acf071da517808ed20b4147e945a0a3f462d0517a09e828bdd47cea737cd6d2e9737cc6a4d341b4e7986c51d9b47f06185de79dd89601f7194ba7b94f7be23e6371d3fe696074f9426c2b81fbb4a5d3dca73c719fb6b869ff1430ba7c21b635c07dcad269ee939eb84f59dcb47f12185dbe10db5ae03e69e934f7094fdc272d6eda3f018c2e5f886d1d709fb0749abbdd13f7098b9bf6db81d1e50bb1350377bba5d3dcc73d71b75bdcb47f1c185dbe105b0b701fb7749afb9827eee31637ed1f03c676872fc486ef5ddcec89f1a6c578b388b6c3fa5ec5b01dd67f2a86edb03e50316c87dd9b8b613becfe5a0cdb61f7c862d80ebbcf15c376d8bdaa18b6c3ee37c5b0dd6ed96e2fa26db9c68a7f8dbd996deabd7a8dbd99ed5abb65bbbd88b6a5cf247da662d9963e93f4998a65bbddb2dd5e44dbd2a686b7a91e9e4fa4e26083b698b59f86f431e0f1f19cc7939f495dee5153d61b1196ab63f5a215abcd56ac1290e728c4ef450ff18b815d2a9bf6c95e293263bd8845673b19071bf88eb823543ee80e9b742de80e99740a74074dba0e74074cba1e74fb4dba0174fb4c7a31e8da4c7a09e8f69af439d0ed31691cefd96dd26741b7cba4717c65a7499f01dd0e93c6f18ced267d1a74db4c1ac70fb69af429d0b59a343eafdf62d22741f78249e3f3f14d267d02741b4dba19741b4cba1d74eb4d7a33e89e37e9e3a07bcea48f82eead26dd08ba674d7a12e8de62d24da07bc6a42783ee6993c67714ae36691cfb5e65d21740f79449e358f393267d09742b4c1ac776979bf474d03d61d23340f7b849cf04ddcb269d06dd2b263d0b746f33e9d9a07bd5a4e780eeed263d1774ef30e979a07bcda4e783ee9d26bd0074ef32e985a07bb7492f02dd7b4c1ac701de6bd2c7405766d22f828ee69e1e011dfd9ee230e8e83794874047ef4d3808ba1e267d0074341f673fe8688ee93ed0dd67d26da08b9bf45ed0559af41ed0d13b0a76838ede03b40b74f4aea29da0a3f701ee001dbdb3703be8681eea36d0d16f19b6828e7ebfd80a3a7a47c016d0d17b815e001dbdeb6e13e868bee946d0d16f0436808e7e17b81e74f4dbfbe74147efdb790e74f40eb9b7828ee64d3e0b3afa2dc05b405763d2cf806e98493f0dbae126bd1a74f4aeb855a0a3f97f4f818ee6fc3f09ba1126bd0274234d7a39e84699f413a0a3772e3e0e3a9a07f932e8c69af42ba01b67d26f031dbd73f355d0d1bcc1b7838ede55f30ed0d1bdf835d0d1bdf89da0a37bf1bb4047f7e277838eeec5ef011ddd8bdf6b3ef5f5a7afcbeb663f1d44d7efd1f66e04b76fb9fadec4803c51f66513c083b6ae46ee7b2ad36fa6fe56992997eac155b07d3972dbd93efb1553563753ee65cb7605e419d2afe3dc5c82ffa7c1073a0ef350d9b43f018ebd64955d65fcbde2c9dfcb1613715f0126ca33bc5f47decf9a744f382642b6ccf74faa6b01c410b734a4718db4e86395ca7c1fb85600cf15e089fe3ac97e1ff65127f0da8afafbb0fd2cc3ae6b09c87319e2e76b6ee7158b87f6c99e300bb3300bb3300bb3300bb3300bb3300bb3300bb3300bb3300bb3300bb330f367d63c349e80f37928df75268ca4c3b12e1fcff9712c15c7bc3e02e33abec7f7ba9b72275a3e57409eff03634e1f33e94af83f9db7b073e9619c30e7b9247b95e00f8e05f9182f8e812d2afbaa23161c6cd744663bb5c1cff94d257b07d9f1fd6bd639bd6e9d4f1c17c76be8b3504fe9985be6330ef1b905f1f1708de73c37640fafa5aba0a3f40d60f4116bbc97507b30c1ec933d1c17ffb2352e1e7d5b9d4afa6c372e9ab2ecf176f495f27c03dabe6f9a34ce0db90e65fdccf17fda728d5353fcb4cfe7a3f739737e5f3265d1f93defb07d0e5823b25d8bb66346c80ee92b20fd5398cf61ff9e88624decfa1aa13998c86e1f77c33a2e01792e38fc4f07d1fa7fdee2396f31ebbaf36da8673f83fbbfaf36e942488c26408c280ff6833ccc3371b691c441f6741e3aff3dac3cd867a13cbf86364afb42ed3cf989735bf01e70d1937fd72dff689fec61dff80a30da3eeafaf1853e1dbcf7fafcaffff238ffebbfa00ee13cadc02a7f22944f5c3d82f07b0be589f5ef28dfe7fcb26b96cfc47205987e93a77f47de69265d485fffcdfade16d6d7f7f14ec07870fb776fbde5babfe33dc647fbe2c9cfa4ebde75c9f229017946809f1efa31397f07781e6cfb7a0f24d9a03ed4652b161590a73f5cdbb9e288df55af14c59794b33f38d1e10be5196cb5532f7960f279deb09fa5cbbdeaf095f20c87f6ef51938ec379c2b6b4cef17fda72b507143fedb387f7fb65ce2ffd068cceef1987ed53c01a91eddb7e934efd7db243fa0a48a7fa77e4b57f4f4eb1c6f7f8d16fe590dd3eeeb2755c02f29c75f89f0ea2f53fec7d00644fd79d9150cfa81ef96c37cf86c46822c488f2e0b315bab753feb0fbbe8ff9d5b9eefb5781d16e37b1efe293ed9ac5663f4375f507290f1d8bfdc159d0cec61d79fd3f1bcef66d0a99f3eff919797dcc8a671aec8c073dd54f8a33fd0ffbb8946789753ff3d567bd6c71931f388673d1e2be64c5159f452d076ed7b5e97ad6e9a9cf598f7d31bb3f7411d829cf6aab3f64f7e36e3a7cb1efd36541e7ef826f5879f19acc759c9dbe6e1d83cf0c5ccf64f1b72e1780256dd2c93bd96a9349642a73c4221174fe4ee67ace40d701d6392acbbe56e8d80ac8b3d16a93ecbc997b599f8ef8d079c467edae3ae9eb3952d87316b2a7195dbfa38bbeedba377fafd433b27227af2fc531ee9e563a1adba916bc57e48ac545078faf679a61b1b8e8b01d5d2c1a37b8ee1fae585c70f0f8fa8e19168b0b0edb11c66293ebd9822b16e71d3cbebe6b84c5e2bcc37674b19874db738d5cb178c9c113fd338ddcb1c031b142982f3060ee69a5a3b15dbfde3576e78ac539078fafb1bbb0589c73d88e2e16b58daeefecae589c75f0f85a17242c16671db6a38b45d364d73315572cce38787cad9f11168b330edb11d68b8df87c2d572c4e3b787cadc911168bd30edb11f60f1b733d33c4589c72f0f87af617168b530edb11c6a259db3e99472c4e3a784e163916271db6a38bc5fa066dfb441eb138e1e03951e4589c70d88e2e16cd4dda767b1eb16877f0b4173916ed0edb117e87cad48be379c4e2b883c7d7ba2961b138eeb01d5d2c5a327dad6379c4e29883c7d75a2c61b138e6b01d5d2c92997bead13c6271d4c173b4c8b138eab01d61bdc87c9f7c318f5814f3fda361b178d1613bc2fb48a65e1cc92316471c3c478a1c8b230edbd1c56253e6f9d3e13c6271d8c173b8c8b138ecb01de133974cbd3894472c0e39780e153916871cb6a38b455de69e7a308f581c74f01c2c722c0e3a6c47178b8d9931b10379c4e28083e740916371c0613bc27e67a6bdd89f472cf63b78f6173916fb1db623ec77669e5feccb2316fb1c3cfb8a1c8b7d0edb11b69d997e675b1eb16873f0b41539166d0edb11f63b33b1d89b472cf63a78f61639167b1db623ec7766ee237bf288c51e07cf9e22c7628fc37684f522d376eece2316bb1d3cbb8b1c8bdd0edb113ed7cab49dbbf288c52e07cfae22c76297c37684df4732cff876e6118b9d0e9e9d458ec54e87ed08c78a327df01d79c462878367479163b1036cfb986782b1a0b958e3ac5854409e07cccbd8692e56581ca90c9c5786be6c8fdc97ecbcb26d21be6c075f28cf40f0a527e8a364f2e46ba6ce6c3565d1dcf41b0e5f29cfd0011d798799741ccec94d282be9f83f6db9e62051fcb4cf5ba2f73953575f3065d1f9dde2b0bd095823b25d8bb6696e3ad9217d05a4270ee8c84bf9281e146b62d7d708ad9580ecf6713bace31290a7d5e17f3a88d6ff2d16cf168b39f3bb07a867548ffcb45d59a6d690188d8318511e9cb377c3138f3d879038c89ece43e7bf879507e750529e2668a3705e29f9190f3acf9bd4fe6df5e45fd83a6e642f01ba2bc068fba8eb4702e67ed61263d0a1a3b51d700da6464ba77d9de4c957b24565d3fe2460a4b5261a8bcf98ca97b1c162d43c933dc42c0e7669cb75bf980c3c4d1e783cf999b90f4db17c9a64f994803cf8dbc6291efc8c815d2a9bf6a7806d1fe71c6341f7e451562c2a20cf6aabff1816472a43d7df46872fbee2d860f134386c4ff71c472a9bdac4e945b03dd5b25d6fd9d6d736d631bde5bab6a702f3340fccbadc19d1979bb9b6698d34aacf64a71e7c4a430ca2f2096dc7acb2495f01e9ed033a33503ce8de49ecfa3ac2ba1376dc64ebb804e499eef03f1db1ff332c1e3cc77ad3fd86f5d0aff4703d64eac0748b83f6eb2176334262371d6247791a41477d9c29a0a3be02ae2948ffc77e4443f4fe3adb1eda6f0046d2e13a850d0ec6fae81953b9dae67a6024dd34e099ea2966d32c9e51567cf0bedcc3ca43c756409e76b837c61d7975ddaf8a75f8456b1746b8ce4fa65dedee215eb8ae6200f109ac18d2460c3d838eb517a3e4b92f80b515db76ee69debcf1c98dd9a14742abb030f133e670a30c74982e77e882e0f625242b40474b4876035d9915165cba92f2d392763ec285f1a0b22b2cce9ec012a56d5c7e93b65c55a707f0f8a8cabaea549ab24cd5797acf96b68d583fba599c5da93bfa7fe539f2859545f5a0c283efc84465d33ed9d3f14998f4aee60d5b67edd9bc6ffbc61d6d7b11d6beb8301db382607fba8ec14a82171395d3cd0a4eafe883538f17afcd1780bd005802c3d3337a9e4c45a5355b37346fdbb67cdffa6d5b36ccdfb76343db969d3b30a23dacc88545dbbee4f5e66aea30af9dbfbb43676fb8426e0fd0d10ab93d4147f67b818e38ee83ffd967c2cb353102caa76aacff57611cee6e1ca22a48b721dd9ee86ba67790bdfdeb2763d541f6f4e92576f592ba7a095dfd0607fdab6add53d04be0ea256ff512b77a495bbd84ad5eb2562f515b136497a01d1e6497987d34e85842566f9f015ebd64acee6ae82561f512b06383ece3baf141f6d566fa5512bacbaabf8eea6e9efe6aa1bb5cbaaba5bbb5fa2bbb7e7ca3bb31ba8ba8bb3fbacba2bbc1bafb3bd3c47a56905d425a2f193d37c82e093d3fc82ef9bc30c82ee9bc38c82e87be34c82e4dfb44905dce7c79905de6562f7fab97c5d54b97eb2573f552ba7a895dbd44b95e7e572fcbab9721d74bf6eaa57cf552e37a99dfe783ec72e2eb83ec92c12d41763961fde85a2f1fae1fdbebc7d9fa51b17e94a98713f4308a7eccaa87fff470a81e1ed6c3e57afa809e4ea1a797e8e9367afa919e8ea5a7a7e9e97a7afaa29ecea9a7b7eae9be7afa737b909d1e7f32c8fe7c42ff9c44ffbc46ffdc48fffceaa520fb9342fd133dfd9353fd13dccb41f611aafe39b71e56d38f4ef56364fd78553fead5cbf4ea6579f532bc7ad95dbdccae5e56572fa3ab97cdd5cbe4ea6571f532b8ef55f23b4a7e57c9fb94fc9e92f72bf97d257fa0e40f95fc91920f28f963251f54f221257fa2e44f95fc99923f57f261251f51f251257fa1e42f957c4cc95f29f9b8924f28f96b257fa3e46f95fc9d924f2af97b25ffa0e4534a3e1d64ebdf6795fca3927f52f2cf4afe45c9e7947c5ec917947c51c997947c59c957947c55c9d7947c5dc93794fcab926f2af93725df52f26d25df51f2ba92ef2af99e92ef2bf981921f2af991921f2bf989929f2af999929f2bf985925f2af955d0b10c343616ffdfec0c33fbcd6d6d1bb7ef6aab69db59b37ddfb6b62dbbb61daa39b0a5ed859a9dfb37eed9b46de7013cf883a679a2f5aa67edd9d37ca866cb8e968d076b76ee6babd9b9a966fdce7d3b5a6ebb397ece1c34a8b3c5e696967063df29bb03d2ef77d1e8afcd71b412f8a2dcbefdafae04e4ff75e5a081e55d73e8e3a673475fbb9ecaf6f16af66edbd95693acd9a1feaa9be9ce031b5b26d4e0fff6aa20ef6dabd9dbd6bca7ad66d39e9ddb6b6a2760b9e37b77c189d6de7e60069b770175a9aa7ca95f173cf91ffdba763afef34e48693df9428df6ebdf050f0775e5a074170917f70f0dcbde7debdbf6346f680b3f78d99d1cbcaa2b6eb674d1cdbe03ba60eca1ae1c346940d7085775c5d8f1028c05ff0d3975c98424930400",
+ "bytecode": "0x1f8b08000000000000ffed9d67941cc775ef7b76177176b058820824181624080220c2ec6c405884418e14b0244082020572810540805880041639e79cb34831283948b22d4759b26c59b26c39c8966c45cb564e94c40fef9df74defd0ae9aa9abfda3503d9a59740def00b7cfb93bd577abebfeeeedeaea50d55d6f0741100bb24bb99287839b17fa7fdafc266f6da98db0aca44fce58897096950867798970569408679712e1ec5a229cdd4a84b37b8970f6889053b39505372e51f3f6f410d7a819e32516d3ca128869a2c462daab04625a1594461bd5bb4438ab4b84f3ae12e1ec53229c77970867df12e1ec57229cfd4b8473408970de53229cf79608e7c012e1bcaf4438ef2f11ce074a84f3c112e1ac2911ce4125c2f95089703e5c229c834b84f39108398701e710f3fba8f91d6a7e29cf70f3fb98f91d617e471a1f2bccfa2825a3359b925aeb7f2925754aea953458ff6b543246c95825e3ccff6accffc62b69523241c9442593944c367198a264aa92694aa62b99a164a692594a662b99a364ae92794ae62b795cc97b942c50b25049b39227943ca9649192c54a9e52f2b492254a9eb158deab64a9926795bc4fc93225cf29795e498b92e54a56286955b252c92a25ab95bca0648d92b54a5e54b24e499b92f54a36287949c9cb4a362ad9a4a45dc966255b946c55b24dc9762b663b94ec54b24bc96e8b738f92bd4af629d9afe48092834a0e2939ace48892a34a8e2939aee48492934a4e2939ade48c92b34ace2939afe482928b4a2e29b9ace48a92ab4aae29b9aee4fd4a5e51f2aa920f18163a105e53f2ba923794bca9e4834a3ea4e4c34a3ea2e4a34a7e47c9ef2af93d25bfafe4634a3eaee4134afe40c91f2af923259f54f2c74afe44c99f2af933257faee42f947c4ac95f2af9b492cf28f92b259f55f2d74afe46c9e794fcad92cf2bf98292bf53f245257fafe41f947c49c93f2af92725fface45f947cd98af9bf2af937255f51f255f33f7a0ef6ef4afec3a4bf667ebf6e7ebf617ebf696df32d25dfb674ffa9e43b96eebf94fcb7497fd7fc7ecffc7edffcfec0fcfed0fcfec8fcfed8fcfec4fcfed4fcfeccfcfedcfcbe657e7f617e7f697e7f657edf56f2eb01d974f7a063490711b549f52b57ea3e140af690e0c645c7a2dcfc8f7e6b8cbec2acd32fc5ae8b59ef62e9bb9af5ae5639ddcd7a774b5f6dd6ab2d7d1fb3dec7d2f735eb7d2d7d7fb3de1ff4f1009ea51abdd6951b550c74540fcb40d725b831265ad7958a035db7e0c658681dedc7aea0eb6174dd40d7d3e8ba832e6e743d28664a2a8d2e1d445527922dbadc44d4e59afea55ed1f3aed0e55679e2ed1d3def4a5d6eb5075e5d3fee3265f5867ad3c7e8aa4177b7d1dd05babe46d70774fd8cee6ed0f537babea033cd54d00f74f7185d7fd0dd6b74034037d0e8ee01dd7d46772fe8ee37ba81a07bc0e8ee03dd8346773fe86a8cee01d00d32ba0741f790d1d5808ec6ae0c02dd60a37b08748f18ddc3a0a3367530e8e89aef11a3d3ed4497186c63f4d44665b6a176187443a90d06dd306a7f41379cda5ed03d06b6493702da15d28d343a6aa3f4ffc69a743a88ea9848658e89715197ab4ad6e536455f6ea63f6e42d011d734d81907b19a68d2118ef9a945db31236487f415909e0d79291fc583ce33c4aecf27e34d7a628eedc65adb2520cf7887ffe9205aff9b2c9e268bb90ba4fdd4d9ba94d4d9bc9782ebec5390d7ae7b74cd733bd6d979c0e1a1ce364a9dcd7b29b8ceb6425ebbeed175efed58679f010e0f75b6c54f9d4d25a5ce669f7f0581bbeed1bdcfed5867570347f475b641ea6cfe4bc175761fe4b5eb1eddffde8e75763370445f67c7b4c8b541de4bc175f60ce4b5eb1e3d8bb91debec41e0f05067574a3b9bf752709d7d15f2da758f9e0bde8e75f63c70445f67c779aab3755267836c5f6610b8eb1e3da3be1debecebc0117d9d5d21cf67f35f0aaeb39f82bc76dda3fe92dbb1ce7e0238a2afb3adbe9ecfa6a4ce66c7700481bbee51dfdded58673f63d2ba6fec6ba66fec01d07ddde81e04dd3760ec01e9be697483c02f0fc7c0583906f25e0a3e06be0579edbafc9049df8ec7c09781c3439d6d913a9bf752709dfd29e4b5eb1e8d69b81debec7780c3439d5d217536efa5e03afbff20af5df7687ccded5867697ca8be5ef8aeb95e180abaef19dd30d07ddfe88683ee0746f718e87e68742340f723a31b09ba1f1bdd28d0fdc4e84683eea7469704ddcf8cae16743f37ba14e8de32ba3ad0fdc2e8ea41f74ba36b00ddaf8cae11746f1bdd18a3d3fd5834a6ea4b46d71db8d24174fb360e31a02566ada7215deb972799001eb45517bdad3aed7b2ac8dff73ae0a9f7e07b1c6ce4c3530f3c0dd1f364c69336465f6e661fa7ac98c6c1560afc1ae3c1af18d8a2b2699dec254087c7f4180763e4e767751e8d812d2a9bd6c70223e9b08da136978e1fdd36df1febe0f5702c65cecf682f0d1c64af02f25c1ed091779061ab84ff531b5009db631bcca9ae6846aa238dc5674ce5cbd860317a3abe33316bb0785cb6c77bb26dc782ead4f822d81e6bd9aeb76c631b424bae367e2c307bb85eaf7d37aed7f1bcc5e17a3d1debc86b5f7753db53e8f5fa186b3b8ed7eb0d704ef0703c64eac0788b83d6eb21764d21b11b0fb1a33c8f828ece118da0a3b696cac0eb5b6c877d5c2fc5821bafa5d3b0dee0e01e0b8c0d0e460fd798a95c6d733d30920eefad539e6236cee219e788c59d6adb433dcd1c976483aed5e818227b1590e7b1f28ebc4ba0cdf0513ff158a025df7ba0e8f7532a734f565f000fee3b0fd7d9b59eea6312efe7df09a2ad6b769b586fc52aec9e3fe9217e58f7a96c5a277bc22cccc22cccc22cccc22cccc22cccc22cccc22cccc22cccc22cccc22cccc2cc9f19fbb2701c0fe56b60c2688f87f2f59c3ff3cd355316f601bde5757c502ad30f40f1a7f100c32c9f2b20cfaf631d6c6fc3f820fa3f8e7772ed4b1f632573ed4bb2e71aafe4b3bfb9cee2a973c48283ed9ac86ca756f8d9bfa9a4fee691fe6e60bdb54fedbef9ccf7082d9daea7a3ca3bfcf6d1f757685f24b625948eb26f2f013c68cbc7bec163af2cb8b1fdc0f38caf712cd456537ff978cb7605e4e95116fc66df34c1ffd3c1cded05e6a1b2697d186cdb64955de5cfdf9c6313c60337a5bb5abe350237e5e955d6e1e3274ddad3392e55e8386a6ca7a3bf06c88e214815c093041e1fe7314fd73a49ac8f518f21b0c788b9aea1280f8eaff330b631e7382fb227ccc22cccc22cccc22cccc22cccc22cccc22cccc22cccc22cccc22cccc22cccfc99358feb1b1494af8e096391c65d64fa33e87b4dd82ff67a59875ddf7d80d4e734dcf219df5bfd7e5907db874cba32b879ac45d8bef4d5b716b62fc99eeb3b28befa9463608bcaae75c48283ed9ac86c67c71044bf7f3bc610a4ac7d5a67ed4fec3bc763e893504fedf114f1e0e631099e8ef19cfb86ece1b184e35f288def66fb88359e4bec3145640ffbce3f63624b7de7d1b7d5a9a4cf7683be5742e304520e5f29cfe7a0edfbbc49e3f8913a28ebab8effd392ab9f9ae2a77d9e14bdcf99fd3bd99445fb7792c3761a5823b25d8bb66356d9a4af80f457ca6e66a07850ac895d1f23f8dd97b0edeaaded129067a2c3ff74c4fe4fb278701feb45d79d2f423dfb2a9cff7db549134362340c6244793c7f3fd0391ed01ed388ed68372b0f6d8bdf45fb36b4516163575de780df7ccb2828ce3980ec25829bcf0bf98c31bdd3c788bd05ed4513fc3f1ddcfa18b1b7a00ee138adc02a7f38944f5cdd82f0730be579db3a8f7a18c395d7f7715cf703c48b63d028cfff85b66ab019ab59c8fdc0bb756fe7ba1fc0edc27cc77621ea7323d64764c1efab519eff6fd5c7a610eeb18e6dff27645b8a95fdadb0cae0e6f8f9f9ce5ab6bd9964f942c7d444f085f27429eff0c5cf3553f6fad3d737e5e85a87daa05a87af94a7277c33aad2a4e3b09ff0bc30d0f17f5a725d7f52fcb4cf53a2f739b37fa79ab268ff4e71d89e06ac11d9ae45db74fd4976485f01e97bcb3bf2523e8a07c59ad82b8113d9ededc65bdb25204fdae17f3a62ffa7583c532c665d77aaa09e0d84f1f7bedaea74e08ed17088d16fec818ece23f81d50d7b30e5fe7d2b0eb387c6f8b74d8b60f869816eb9d1afb999eebfa64b8c58fd727c3a09d8d3bf2dacf2a69bb28c72ce33b22785d88ef88f8ba474a0437c6336171f8b45d65d9ae2aa2ed6acb7675116d4bcc25e69c62ce69ae08ec9b2a03461fe73a3cafe6c3e83aff9503a3aff7f8520530d601239eef88d1c377586b3b3b17055eeb7401461fefa016fabc1abfc18cef2213a38f6fe117fa3d5bfc96356dd70d187dcc71807315e4c3e89a23a33bfc7a980fa3b6b3dfcdc739327a00a38f6fe9c7831bbfffffdb18c701236dd713187df423c5831b9fabfd3646fc063b6d17f7cc98ebdcee793c48aad0671038fe029f41519c5ce3587c3dabb79f8dd13a3eab47dec05f1c735e2335f98d450ae34e4bae7de88a4f2ff31b87fffb7d9e78631f2f954deb13819174557ee398f35a7382df58a430eeb4e4da87aef8f436bff1e0e6f93f3cf5593bfb28691dfb714957ed99276ef1fcb638ba18ef02c6c99e182715c038191869bb3ec098f6c438b900c63430d2767703a387e7d519c674018cf85c97f47d8171aa27c62905304e0546daae1f30fa78f61c07bbf9304e0346daae3f304ef7c438ad00c6e9c048db0d00c6199e18a717c038031869bb7b8071a627c6190530ce0446daee5e609ce5897166018cb38091b61b088cb33d31ce2a80713630d276f701e31c4f8cb30b609c038cb4ddfdc038d713e39c0218e702236df70030cef3c438b700c679c048db3d088cf33d31ce2b80713e30d2763525c038a804181f2a01c6874b80717009303e52028cdd4b80f131607c3c7ac6ccfdf5fc02181f079e05d1f3d4c7c1463e3c0b80e73dd1f3d47af233333e6ba1292bea6fa4355bb17adc8a5502f22c84f8357b885f0cec52d9b44ef6845998c398350fb54fc41a877cf3993092ee3d9e79e2168f5e72b58fcd7e799209473cb4ad27a2b7952ad4f727806751f43c9973d51305f02c029e27a3e7a9f5e467e69cb2d8f2e909cba704e4c17663b1073f636097caa6f5c50edb3541b4b1782a8f583ce5e079aac8b1207b85322f2c41660e71c6b69058e3906f011346d23de999276ef1e82557fbe8626cf6cb98ea2ca3e659123d4fe69cf254013c4b80e7e9e8796a3df99969479fb17c7acaf2290179b04d7ac6839f31b04b65d3fa33b01f0a615e5c82cc12e7ce31639b45ac71c8b7880923e99ef6cc13b778f492ab1d733136fb654c759651f32c8d9c273b3fc03305f02c059ef746ce936dfba3f733dbf63f6bf9f48ce55302f2609bf4ac073f636097caa6f567613f08b3300bb3300bb3300bb3300bb3300bb3300bb3300bb3300bb3300bb3300b336f66ec6320d638e45bc2849174eff5cc13b778f492abdfc1c5d8ec9731d55946cdb32c729e6c5fcdb305f02c039ef745ce93edab89decf6c5fcd73964fcf5a3e25200f1edfcf79f0330676a96c5a7f0ef683300bb3300bb3300bb3300bb3300bb3300bb3300bb3300bb3300bb3300bb330f366c63e06628d43bea54c1849f73ecf3c718b472fb9fa1d5c8ccd7e19539d65d43c2dd1f364dea97cae009e16e0793e7a9e5a4f7e66fa6a965b3e3d67f994803c787c2ff7e0670cec52d9b4be1cf683300bb38b19db2c628d43be654c1849f7bc679eb8c5a3975ced988bb1d92f63aab38c9aa735729e6c3ffdf202785a816745e43cd9b63f7a3fb36dff4acba7e5964f09c883c7f74a0f7ec6c02e954deb2b613f14c2bcb8049925ce12e7306689b3c4398c59e22c710e6396384b9cc39825ce12e7306689b3c4398c59e22c710e6396384b9cc39825ce12e7306689f39d1367ec6320d638e46b61c248ba159e79e2168f5e72f53bb8189bfd32a63acba8795647ce5397e9ab595900cf6ae05915394fb6af267a3fb37d352f583eadb47c4a401e6c935ef0e0670cec52d9b4fe02ec87db9d797109324bdd280eb3d40d610e6396ba21cc61cc523784398c59ea863087314bdd10e63066a91bc21cc62c754398c398a56e087318b3d40d610e6396ba21cc61cc523784398c59ea863087314bdd10e63066a91bc21cc6cca16ee0181d628d43be56268ca45be599276ef1e825d7b81d1763b35fc654671935cfdae87932df2479a1009eb5c0b3267a9e5a4f7e66c63abd68f9f482e55302f2609bf4a2073f636097caa6f517613f08b330bb98b1cd22d638e45bcd8491746b3cf3c42d1ebde46ac75c8ccd7e19539d65d43c6d9e785e2c80a70d78d645cf53ebc9cf4cdbbfdef2e945cba704e4c1e37bbd073f636097caa6f5f5b01f4a8d198f25628d43beb54c1849b70e783cd4bb82dba436471c9b4b8071610930f62801c69e25c01807460fd7e999633861f1d0fa5abff14945119f4a8813d7762f013cbef66195c553e58805d77dd8cbfc72de8755c0e36b1f565b3cd58e5870dd87bdcd2fe77d58ed378e91b4f7779500639f1260bcbb0418fb960063bf1260ec5f028c034a80f19e1260bcb70418079600e37d25c0787f09303e50028c0f9600a3ef671ab9ee73db8a603becfeac18b6c3ee2b8a615b622e3197984bcc25e612f35bb52d3197984bccdffd98fb78968bcf8d69c9750fe17abeebbb4f5e18f9302e66ca883cb1e87892e83bdadac0c077622835c6c525c0b8b00418258ed93ed4ce306a9e973df16c2880e765e079297a9e5a4f7e66c61a6eb47cda60f994803c580f367af0330676a96c5adf08fba1d498f15822d638e46b63c248ba9780c7d7f155c8f18ec7d7264f3c2f17c0b309787cec2f4f7e668ef776cba7972d9f1290078f9d760f7ec6c02e954debedb01f4a8d198f77628d43be36268ca4db083cbe8eaf428e773cbe367be2d95400cf66e0f1b1bf3cf99939deb7583e6db27c4a401e3c76b678f0330676a96c5adf02fba1d498f17827d638e46b63c248ba76e0f150ef0abee7d8ec88e3c212605c5c028c12478923274689e39d13476114466114c67783b114da7039cf14fe6c00efc5b746cf538ff76df9f06c051e1ff7769efccc3c1bd866f9b4d9f2290179b01e6cf3e0670cec52d9b4be0df683300bb38b19db2c628d43be36268ca4db023c1e8eef82dbfead8e3836fb654c759651f36c8f9ca73e89f5251f9eedc0e3a34ef9f133dbf6efb07cda6af994803c787ceff0e0670cec52d9b4be03f64321cc8b4b9059e22c710e6396384b9cc39825ce12e7306689b3c4398c59e22c710e6396384b9cc39825ce12e7306689b3c4398c59e22c710e6396384b9cc39825ce12e7306689b3c4398c59e22c710e6396384b9cc39825ce12e7306689b3c4398c59e22c710e6396384b9cc39825ce12e7306689b3c4398c59e2dc39661ce34eac71c8d7c6849174db8067bb079e42c7e16f77c4b1d92f63aab38c9a6767f43cf5585ff2e1d9093c3eea94273f33ef0aecb27cda6ef994803cd826edf2e0670cec52d9b4be0bf64321cc8b4b9059e2dc39666cb388350ef9da9830926e07f07838be0b6efb773ae2d8ec9731d55946cdb33b7a9e7aac2ff9f0ec061e1f75ca939f99b67f8fe5d34ecba704e4c136698f073f636097caa6f53db01f8459985dccd866116b1cf2b5316124dd2ee0f1707c17dcf6ef76c4b1d92f63aab38c9a676fe43ca924d6977c78f6028f8f3ae5c7cf6cdbbfcff269b7e55302f2e0f1bdcf839f31b04b65d3fa3ed80ffb0a605e5c82cc1267897318b3c459e21cc62c71963887314b9c25ce61cc1267897318b3c459e21cc62c71963887314b9c25ce61cc1267897318b3c4f9ce8933f631106b1cf2b5316124dd1ee0f1f03cbee0be9abd8e3836fb654c759651f3ec8f9ca72e89f5251f9efdc0b32f729e6c5f4df47e66fb6a0e583eedb57c4a401e6c930e78f0330676a96c5a3f00fbe176675e5c82cc52378ac32c754398c398a56e087318b3d40d610e6396ba21cc61cc52376e5a84d9a4a56e087318b3d40d610e6396ba21cc61cc523784398c59ea863087314bdd10e63066a91bc21cc62c754398c398a56e0873183387ba81637488350ef9da9830926e1ff0ecf7c053e838a2fd8e3836fb654c759651f31c8c9ea71eeb4b3e3c0781c7479df2e46766acd321cba7fd964f09c8836dd2210f7ec6c02e954deb87603f08b330bb98b1cd22d638e46b63c248ba03c0e3e1f82eb8ed3fe88863b35fc654671935cfe1e879eab1bee4c37318787cd4294f7e66dafe23964f072d9f1290078fef231efc8c815d2a9bd68fc07e1066617631639b45ac71c8d7c68491748780c7c3f15d70db7fd811c766bf8ca9ce326a9ea3d1f3d4637dc987e728f0f8a8539efcccb4fdc72c9f0e5b3e25200f1edfc73cf81903bb5436ad1f83fd20ccc2ec62c6368b58e390af8d0923e98e008f87e3bbe0b6ffa8238ecd25c0b8b00418179700a3e738a63acba8798e7be2395a00cf71e0f1d17e78f233739e3f61f974d4f2290179b01e9cf0e0670cec52d9b47e02f643a931e3b144ac71c8d7c6849174c780c7d7f155c8f18ec7d7494f3cc70be039093c3ef697273f33c7fb29cba7e3964f09c883c7ce290f7ec6c02e954deba7603f941a331eefc41a877c6d4c18497702787c1d5f851cef787c9df6c473b2009ed3c0e3637f79f23373bc9fb17c3a69f994803c78ec9cf1e0670cec52d9b47e06f643a931e3f14eac71c8d7c6849174a780c743bd2bf89ee3b4238e0b4b80717109304a1c258e9c18258e774e1c85511885b130c6f525c028fb5a18b932b679608c197bc843eb6d45b05d65d9ae2aa2ed6acb7675116d4bcc25e6127389b9c45c627eabb625e6127389b9c45c622e31bf55db127389b9c45c622e319798dfaa6d89b9c45c622e3197984bcc6fd5b6c45c626edbf630deb4e0f1dc6780e7b4875878f233a9cb3d6bca7a27c2f8e9589db362d566c52a0179ce42fcce79889f6bcc35ad93bd4299873060f6643bd54b95d103fc271b8bad7868fbe73df91ed6e69d2f82edb036af18b6c3dabc62d896984bcc25e6776ecc315d11dcfc0e922ee382497731eb947f3d6c4779deaaccfe5605b23f7dd8966348622e319798bf1b31c7b88c2f024f60f10439786632e3a967c6d3c28c6709339ee1cc784633e399cf8c6716339e29cc78ca99f13431e36960c6338219cf6c663c5399f14c60c6d3c88c27c98c6728339e91cc785a99f10c61c6b39419cf02663cc5e81f2a84670e339e51cc782632e399c68c670c339e5a663c8f32e399cb8ca792194f8219cf74663c9398f18c65c69362c6b39a19cf32663c8b98f1f462c653c58c6706339ec9cc78d632e319c78ca78e19cf3c663cbd99f15433e319c68c27cd8c27c680271edc3c263d0eff3f03ba326bdb6e4abe35a0e3ff178dbe0cb6b964d2e58eb22f828ec67a5d726c8b71ba08bea44d3a796b4b264e682b0deb64af12382e31e14933e319c68ca79a194f6f663cf398f1d431e319c78c672d339ec9cc786630e3a962c6d38b19cf22663ccb98f1ac66c69362c6339619cf24663cd399f12498f15432e399cb8ce751663cb5cc78c630e399c68c6722339e51cc78e630e339c38c6701339ea5cc788630e36965c6339219cf50663c49663c8dcc782630e399ca8c6736339e11cc781a98f13431e32967c6338519cf2c663cf399f18c66c6339c19cf12663c2dcc78ea99f1cc64c633dec1e36b5e66eaffa7b269fd0c13db1ef643e6bb79973df974c594d5d5944bfc64af02f2ac301d43babf0ab7252e7bbc063e2bb80231bae0c997b06f675c2882edb06f6714c376d8b7338a615b621e1ef32bd1db4ee118285a62d67a1ad278dcf9183be6c9cf1bdabca8bf157ad58ad5052b5609c87319e277d543fc5ced28ad93bd4299873060c67a5113445b2fae45efd36fbe414a71bd66c517fdbaee29a6616de9f522d80e6b4b8b613bac2d2d866d89b9c45c627e67c7fcfd261de17546126de87b283a7fbc1fecbe6ad2b108edeab25e3165d1375389e355e0a13c6fc03d9dd43f39e6ef8498db697a4681df15f0f50c296c9f14e3f955d83e2986edb07d520cdb12f3f0987fc083ed7870e35c127ac9f58ce203c0f38a071e4f7e66ceb5af593e9db17c4a401e6cfb5ef3e0670cec52d9b4fe1af0d082dfd1f5510ff2d9e761dfd1e5c053cf8ca78519cf12663cc399f18c66c6339f19cf2c663c5398f19433e36962c6d3c08c6704339eabcc786633e399ca8c6702339e46663c49663c4399f18c64c6d3ca8c6708339ea5cc781630e399c38c6714339e89cc78a631e319c38ca79619cf5c663c95cc7812cc78a633e399c48c672c339e14339ed5cc789631e359c48ce73a339e5ecc78aa98f1cc60c6339919cf5a663ce398f1d431e399c78ca737339e6a663cc398f1a499f1c418f0847d4797fe7f1574d4678fdfd67ddda45f015d99c306f51dbd06ba0a93a632f477797f34e0e6b2314ebec629a0ad34ac933dfc8eeeeb4c78d2cc788631e3a966c6d39b19cf3c663c75cc78c631e359cb8c6732339e19cc78aa98f1f462c6739d19cf22663ccb98f1ac66c69362c6339619cf24663cd399f12498f15432e399cb8ca79619cf18663cd398f14c64c6338a19cf1c663c0b98f12c65c63384194f2b339e91cc788632e34932e36964c6338119cf54663cb399f15c65c63382194f03339e26663ce5cc78a630e399c58c673e339ed1cc788633e359c28ca785194f3d339e99cc78c63b78e4db30d1da96eff1f08cb987ef5466be958adff9d18b3df6260d69cfefcbd6c7831bbfe3f9db78f0bb283eda254f7e66bed1f906b04755ae8ed59b56acae5ab14a401e6478d343fc6241f8f756c89e300b7318b3e6a1f609c7fd51beeb4c1849f71af0f86837b4eff44c81cad7df309cddbbc3ae87f344bd8e037def97be833fc2da2f1590e7b3fd3bd8e61bb64af83fed37edcf154ba77df0fd2d7f2a9bd6c91e3e43c2ef82fb3aefdbdfacbeea88c5bb69dbc377cb6b0bbde6b8e2e079273a9e24b63168eb9227df2f07f9fb7ec9c113a1efb509e0f13c574aa6eda2fe182a5fb70fcb7b7b8d793d1ef7d476d9f319e21c1ed7a1ed5a95a3ed2a0b3a8e19fa5f8dd1dbe7ac778cde2ea32690fb291fb6e51e56ee61038b8716bc878dfe3a23952cf41e16e7cff071bef5743d95f4744ecadcc35eb46265cf3592803cc598e3ebb2c543eb644f98855998855998855998855998855998855998855998855998855998855998f933878d39a17cd79930920efbac7d3ce7d7bed37baa54beee1bed56dd6137fa7e8b5412c72850bfed28cb679c377455bf0eb6b861c3f11c38e6c4b52f7df4f7e7da9764af32b8791c8acf7ebab03130c5e8232cc4764d64b6532bfcecdf5492e644bf6aedd3eb8efde9a3adc0f153b4e4ea6bbc083c3ee6bdf7357644fb74c1f2e98ae55302f2e03c16173cf8e93aafd1fa05e0a105e711f475ce082c9ec0111f5aca98f1d433e3e9ce8ca78519cf12663c8399f1cc67c6f300339e01cc784633e399c58ce72e663c5398f1f460c653ce8ca789194f03339ee1cc784630e3b9c68ce711663c0f32e3b98719cf63cc786633e3e9c38c672a339e9ecc782630e3a960c6d3c88c27c98c6728339e91cc785a99f10c61c6b38019cf52663c35cc78ee65c6338719cfddcc78a631e319c58c6722339e38339e31cc78ba30e3a965c6f328339e41cc780632e399cb8ca72f339ee9cc782a99f12498f14c62c6d39519cf58663c29663cab99f12c63c6b38819cf43cc78ee63c6d38f19cf0c663cbd98f15431e399cc8c672d339e71cc78ba31e3a963c6e37b1c73a13c0f33e399c78ce77e663cfd99f1cc64c6338c194f9a194f6f663cd5cc78620c78e2c1cdef9ec4e1ff574047ef48e0fb75658ef2681c23e5d7e7e95f0eb8b9ec3247d9171d0c18a7f3e04bdaa493b7b6dcf05e48cc944beb64af12382e32e1a966c6d39b194f9a19cf30663c3399f1f467c6733f339e79cc781e66c67399194f1d339e6ecc78c631e359cb8c6732339e2a663cbd98f1cc60c6d38f19cf7dcc781e62c6b38819cf32663cab99f1a498f18c65c6d39519cf24663c09663c95cc78a633e3e9cb8c672e339e81cc780631e37994194f2d339e2ecc78c630e38933e399c88c6714339e69cc78ee66c6338719cfbdcc786a98f12c65c6b38019cf10663cadcc784632e319ca8c27c98ca791194f05339e09cc787a32e399ca8ca70f339ed9cc781e63c6730f339e0799f13cc28ce71a339e11cc788633e36960c6d3c48ca79c194f0f663c5398f1dcc58c6716339ed1cc780630e3798019cf7c663c8399f12c61c6d3c28ca73b339e7a663c65cc78c65b3cf87ffdec83c6b75e001dfdbfb14ff6b7caf871c1b217811fc9b0b9317cc50c97b4232661736370e0a967c6d39d194f0b339e25cc780633e399cf8ce701663c0398f18c66c6338b19cf5dcc78a630e3e9c18ca79c194f13339e06663cc399f18c60c6738d19cf23cc781e64c6730f339ec798f1cc66c6d38719cf54663c3d99f14c60c653c18ca791194f9219cf50663c2399f1b432e359c08c6729339e1a663cf732e399c38ce76e663cd398f18c62c63391194f9c19cf18663c5d98f1d432e3799419cf20663c0399f1cc65c6d39719cf74663c95cc7812cc782631e3e9ca8c672c339e14339ed5cc789631e359c48ce721663cf731e3e9c78c6706339e5ecc78aa98f14c66c6b39619cf38663cdd98f1d431e3b9cc8ce761663cf398f1dccf8ca73f339e99cc788631e34933e3e9cd8ca79a194f8c014fd85c14f4ff72d09d33e96ba03b6bd2974177c6a42f80eeb44357e660217be74047e302ce828e9e4d9f011d3d7f205bfafae0ff0cb899b5cce153b983f5acc3a7738e6d713fd236e920dafd88b6d2b04ef670ae8c734c78aa99f1f466c69366c6338c19cf4c663cfd99f1dccf8c671e339e8799f15c66c653c78ca71b339e71cc78d632e399cc8ca78a194f2f663c3398f1f463c6731f339e8798f12c62c6b38c19cf6a663c29663c6399f17465c63389194f82194f25339ee9cc78fa32e399cb8c6720339e41cc781e65c653cb8ca70b339e31cc78e2cc782632e319c58c671a339ebb99f1cc61c6732f339e1a663c4b99f12c60c6d3ca8c6724339ea1cc7892cc781a99f15430e399c08ca727339ea9cc78fa30e399cd8ce731663cf730e3799019cf23cc78ae31e319c18c6738339e06663c4dcc78ca99f1f460c6338519cf5dcc786631e319cd8c6700339e0798f1cc67c6339819cf12663c2dcc78ba33e3a967c653c68c67bcc583634893a0a3742de8289d021da5eb4047e97ad051ba0174946e041da5c7808ed2634147e971a0a334f988ef6714639e0bb24565d3fa7960a4fb17d73e6902eef3964e739ff2c47ddee2a6f553c0e8f285d82600f7294ba7b94f7ae23e6571d3fa496074f9426c134147e949a0a3f464d04d067ba4a3f414d0517a2ae8283d0d74949e0e3a4acf001da5e9fd057c976116e8283d1b74949e033a4acf051da5e7818ed2f3cdafdec7272d9ddec7274c3a1d44bb8fc916954deb2780d1b5df896d01709fb0749afbb827ee131637ad1f0746972fc4b608b88f5b3acd7dcc13f7718b9bd68f01a3cb17625b02dcc72c9de63eea89fb98c54deb4781d1e50bb12d05eea3964e731ff1c47dd4e2a6f523c0e8f285d89601f7114ba7b90f7be23e6271d3fa616074f9426c2dc07dd8d269ee439eb80f5bdcb47e08185dbe105b2b701fb2749afba027ee431637ad1f0446972fc4b61ab80f5a3acd7dc013f7418b9bd60f00a3cb1762c3ef95acf5c478cd62bc5644db61d78cc5b01d76dd570cdb61d76ec5b01d764d510cdb61d705c5b01d766e2f86edb0f373316c879d638b613bec3c590cdb61e7ba62d80e3b5fc9f12dc777d4b6dfcd73c99d7a7cbf9b6deabbd9b6c8b5a25c2b16cbb69c4be45ab158b6efd46b4569cfc3db730fcf845271b0414bcc5a4f43fa00f0f878b6e6c9cfa42e77bf29eb9d08cbd5b1da67c56aad15ab04e4d90ff1dbe7217e31b04b65d33ad92b4566ac17b1e86c27e36003bf6fb897ca07dd1e93ae05dd6e934e816e9749d7816ea749d7836e874937806ebb49cf05dd36939e07baad267d0a745b4c1afb06379bf449d0b59b34f6c56d32e913a0db68d2d8f7f5b2491f07dd4b268d7d4d1b4cfa18e8d69b34f6edb499f451d0ad3369ec4b79d1a48f806e8d49b780ee05933e0cba55268d7d052b4dfa10e85698f45ad02d37e983a07bdea4f783ee39936e04ddfb4c7a0ce89e35e9b1a07baf498f03dd33268ddfe17cdaa4713cc553267d16748b4d1ac72f3c69d2f81dce274c1ac70b349bf444d02d34e949a07b8f494f06dde3269d06ddfb4d7a0ae85e31e9a9a07bd5a4a781ee03263d1d74af99f40cd0bd6ed23341f78649cf02dd9b263d1b741f34e939a0fb9049631fcd874dfa00e8ca4c7a1fe868fcf05ed0d13b4c7b4047ef99ef061d7dab6417e8e87b693b414763bc76808ec6096f075d4f93de06bab8496f055da5496f011d7d176433e8e85b57eda0ab32e94da0eb6dd21b4147df097d19743476f725d0d1fb3a1b4047ef0caf071d7d07a30d74f4ada975a0a3ef39be083a1a83ba0674f4dec90ba0a3777157818ebe2fb11274f44da915a0a3ef362e071d8d357d1e74f47ec973a0ab31e9f7816e90493f0bba874cfabda0a3ef213e033a1ac3f934e8e83d92a74037c4a417838ebe17f124e8869af413a0a3ef8a36836eb8492f04dd6326fd1ed08d30e9c74147ef8dbe1f74f46efd2ba0a3f1cdaf828eced91f001d9db35f031d9db35f071d9db3df001d9db3df041d9db33f083a6afb3f043a6afba9fdd0c7a93e7eaf98f57410dd7594b67735b871c9752d4f0cc813e5b5710278d0d6a5c87d4f65aec3e9faadcc944bf5e512d8be10b9edec3dc04553561753ee05cb7605e469efd7b16fcec3ffd3e0036d8779a86c5a1f05db9eb7caae32fe5ef4e4ef058b89b82f0213e5d9d6af23ef40d32077876d2264cbdccf525d0b2086b8a4218df367441fab54e6fee272013c178127fae3247b7feda34ee0b115f5fdb5fd6cc4ae6b09c87301e2e76b5cf1458b87d6c99e300bb3300bb3300bb3300bb3300bb3300bb3300bb3300bb3300bb3300bb330f367d63cd49f80e38328df15268ca4c3be2e1fcff9b1cf15fbbc7a40bf8eeffebdaea6dcd196cf1590676eff0eb68449e3b75568bf85ed4b0ffd8439f725d9ab047fb02fc8477f710c6c51d9971cb1e060bb2632dba9157ef66f2aa9c7c7e8fefdcbd63ebd62ed4fec17c7636820d453dae6baf98d437cae437c3c1ce339f70dd9c363e912e8287d15187dc41acf25d41e507f38d9c37ef1474c6ca95f3cfab63a95f4d96ed0f7a1ecfe76f495f20c87b66f8449e3d8902b505693e3ffb4e4eaa7a6f8699fcf44ef7366ff9e3665d1fe3de3b07d0a5823b25d8bb66346c80ee92b203dbe7f475efbdd288a35b1e3b79b90dddeeeaab55d02f29c75f89f0ea2f5ff8cc573c662d6756734d4b32638fffb6a93ce86c46814c488f2e07590877126ce369238c89ece43fbbf9b9507af5928cf3468a3b42fd4ce939f38b605cf01e73cf977c5f28fd6c91e5e1b5f0446db475d3fba5577f0dee9e3bf9e84f6e23cfc3f1ddcfaf8af27a10ee138adc02a7f34944f5cdd82f0730be579da3a8ffab8c7c07d69c713c797519ea5d00e5de8c4b5febb75df1676adefe37b94f1e0c67b6fbde43abfe339c647fbe2c9cfa4ebdc75def229017986809f1eae6372be5778066cfbfa0629d9a06ba80b562c2a204f9bd57684c511ef552f16c59794f37a70b4c317cab3c96aa74e7b60f2b9dff03a4b977bc9e12be5d906eddf0eb89ea7fd846de971c7ff69c9d51e50fcb4cf27a2f739b37fe99d32dabf271cb68f016b44b66f78bf9eaef7c90ee92b207d0ccedf948fe241b1c66f489ea4ed82f0ed2e58db2520cf4987ffe9205affc3be6d40f674ddd90df5ec385ceffb6a374f86c46834c488f2e0b3153ab753feb0f3be8ff1d5b9cefb9780d16e37f1dac527db658bcd7e86eaba1ea43cb42d5e0f5e857636eec8ebffd970f6daa69031ff9e9f91d7c7ac78a6c1ce48d053fda438d3fff01a97f2bce9fdf955b67e5cb0b8c90fecc33967719fb7e28acfa23e6addd3dac7a6eb59a7a76bce7abc16b3af87ce013be5f9b8753d645fc75d73f8629fa7cb829bef05dfb1f2e231996b3b3b7dc5da069f19b89ec9e2bb2e6781256dd2c95b596a9349642a73c42211dc7c4fe67ace40c701d6392acb3e5668db0ac8f319ab4db2f3ea366979ef8ef8d07ec467edae3ae9eb3952d87316b2a7195defd145df76dd99ef2b758facdc71cb4bb18fbbbb958ec676aa15cf15b96271cec1e3eb9966582cce396c47178bc615aef3872b16671d3cbeee31c36271d6613bc258ac723d5b70c5e28c83c7d7bd46582cce386c47178b31373cd7c8158bd30e9ee89f69e48e05f68915c27c960173772b1d8dedfae5aebe3b572c4e39787cf5dd85c5e294c37674b1a86d74ddb3bb6271d2c173b2c8b138e9b01d5d2cc68e733d5371c5e28483c7c3f3b59cb138e1b01d61bd5889cfd772c5e2b883c7d77c3061b138eeb01de1f56163ae6786188b630e1e5fcffec26271cc613bc258b468db47f388c551078faf796bc26271d4613bba582c6fd0b68fe4118b230e1e5f73e184c5e288c37674b16819ab6d1fce2316871d3cbee6d7098bc56187ed08efa132f5e2501eb138e4e0f135674f582c0e396c47178bd6ccb5d6c13c6271d0c1e36b1ea0b0581c74d88e2e16c9cc39f5401eb138e0e03950e4581c70d88eb05e64ee27f7e7118bfd0e9efd458ec57e87ed08cf23997ab12f8f58ec73f0ec2b722cf6396c47178b5599e74f7bf388c55e07cfde22c762afc37684cf5c32f5624f1eb1d8e3e0d953e458ec71d88e2e16759973eaee3c62b1dbc1b3bbc8b1d8edb01d5d2c5666fac476e5118b5d0e9e5d458ec52e87ed08af3b33edc5ce3c62b1d3c1b3b3c8b1d8e9b01de17567e6f9c58e3c62b1c3c1b3a3c8b1d8e1b01d61db99b9eedc9e472cb63b78b6173916db1db623bceeccc4625b1eb1d8e6e0d956e4586c73d88ef0ba33731ed99a472cb63a78b61639165b1db623ac1799b6734b1eb1d8e2e0d952e4586c71d88ef0b956a6eddc9c472c363b78361739169b1db623bc1fc93ce36bcf2316ed0e9ef622c7a2dd613bc2bea2cc35f8a63c62b1c9c1b3a9c8b1d804b67d8c33c158d058ac11562c2a20cf5af3d1761a8b1516472a03c795a12f1b23f7253baeece5105f36822f94e725f0a53be8a364f2e46ba6ced0f7fa696cfa5587af9467f3808ebc5b4d3a0efbe41a9475c4f17f5a728d41a2f8699fd747ef73a6aeb699b268ffae77d85e07ac11d9ae45db34369dec90be02d2870774e4a57c140f8a35b1eb6364834923bbbddd266bbb04e4d9e0f03f1d44ebff7a8b67bdc59c79ef01ea19d5233f6d57966943488c46408c280f8ed9bbea89c71e43481c644fe7a1fddfcdca83632829cf1968a3705c29f9190f6e1e37a9fd7bc9937f61f3c291bd04e82e02a3eda3ae1fb361ec672d31061dba3a934e41398d964efb3ac693af648bcaa6f531c0586fd28dc5674ce5cbd860316a9e711e6286f36cd092eb7c310e78c67ae0f1e467e63c34def2698ce55302f2e0bb8de33df81903bb5436ad8f07db3ef639c682cec943ad5854409e8f5bd78f6171a43274fd6d74f8e22b8e0d164f83c3f644cf71a4b2a94d9c5804db4d96ed7acbb63eb6b18ee925d7b1dd04cc133c30eb7227455f6ee6d8a6b9d4a83e939d7af0290d3188ca27b41db3ca267d05a4bf30e066068a079d3b895d1f475877c2b61b676d97803c131dfea723f67f92c583fb582ffabae153705de9e178c8d481891607add743ec2685c46e22c48ef234828eae71c6838eae1570ee41fa3f5e473444efafb3eda1f50660241dce67d8e060ac8f9e3195ab6dae0746d24d009e264f319b60f10cb5e283e7e56e561edab602f27c03ce8d71475e5df7ef8f75f845731c4638cf4fa65dedea215e38ff6200f109ac18d2420cdd838e391aa3e4e91974ccc1b8a97dc3c696d52b9f5899ed7a24b40a0b137f630e37ca4087e972872e086e9c6ab2027434d56417d0955961c1292e293f4d69e7235c180f2abbc2e2ec0e2c51dac6693a69c95575ba018f8faaacab0e4dd569aaced31bd7b4afc4fad1c5e2ec4cddd1ff2bcf912fac2caa07151e7c47262a9bd6c99e8e4fc2a45f6a59f1e2948dab37b7ad5cdfbe0961ed830bd3312b08f6af6b1bac24783051395dace0f4883e38f578f0da7c01d80b8025303cdda3e7c954d4bb4c592b5ad6ad5bb879f9ba352b666e5ebfa27dcd86f518d16e56e4c2a26d1ff27a71357598d7cedfd5a1b3179c49b71be86826ddeea023fb3d40471c3de17ff69ef0724c0c81f2a91aebff551887bb1a87a80ad26948b727fa98d19f2ed5a77ffd644c7fa94defbe3e4176eaddbe41766a5dfd56b5be52d053e5eaa971f554b87aea5b3dd5ad9eda564f655b1364a7aa7d28c84e453b38e8986a562f5f025e3db5acbed4d053c7eaa9621f0bb28feb4606d94f9be94f49e84b567d3baa2ff3f4ad85bee4d2975afab256dfb2ebc737fa32465f22eacb1f7dc9a22f83f5e5ef6413eb294176aa693db5f4f4203b75b49e2a5a4f0d3d3bc84efdaca75b9f1764a74e7f3cc84e4fbb20c84e5ddb1c64a7ba7d32c84e85be38c84e9bfb74909df25c4fb5aba7e0d5d39aebe979f5b4bd7aea723da5eff341767af2e541767ae0d6203b75b09e52584f2ffd42909d86584f39fe62907da4ad1fe5eb47dcfaf1b17ebca9bb1874d78a7ef4aabb047517a9ee32d65de87a48811e62a1879ce821387a48921ea2a587ace9217cfb82ec10cf03417608b01e12ad8788eb21f3fa15826341f61513fdca8d7e0549bf92753ac8be66a85fdbd3afa1ead772f56bcafab1aa7ec55b77b5e9c7a9fad1b27ee4aa1fffea697af5b4bc7a1a5e3dedae9e66574fabfb46909d36f78341765adc0f2bf988928f2af91d25bfabe4f794fcbe928f29f9b8924f28f903257fa8e48f947c52c91f2bf913257faae4cf94fcb992bf50f229257fa9e4d34a3ea3e4af947c56c95f2bf91b259f53f2b74a3eafe40b4afe4ec91795fcbd927f08b2f5f11f95fc93927f56f22f4abeace45f95fc9b92af28f9aa927f57f21f4abea6e4eb4abea1e49b4abea5e4db4afe53c97794fc9792ff56f25d25df53f27d253f50f243253f52f263253f51f253253f53f273256f29f985925f2af99592b7838ee9a3b1f1e86e5a9c4166bda5bd7d65db4bed35ed1b6ada36af6b5ff3d2baed355bd7b4bf50b361cbca8dabd66dd88a1b7fda6c4cf35c4fd9b8b1657bcd9af5ad2bb7d56cd8dc5eb36155cdf20d9bd7b7de70b2fc96d9e8be9b2db6b4b6861bfbf9ad90feaa93467f6db6eb63d6e7e4f6ed9dce04a4a2bc131b0d2def6414cdc51edd863d99bde6abd9b46e437b4db266bdfaab4eae1bb6ae6c1d5583ffdba482bca9bd66537bcbc6f69a551b37b4d5d48ec27297f7ea8413aff7f203b3a95ff6b753556570ff4e7832b57fe776c713fd6f81f4a94e1a5dd7190f377666a32b9d247c233c2c9b362f6fdfd8b2a23d7ce38fdccac61feb8c9b9feea49b6b0674c2d886ce6c747a40e7083fd619635f2fc058f0bf713e55c3fca20400",
"isInternal": false
},
{
"selector": {
"value": 2603445359
},
- "bytecode": "0x1f8b08000000000000ffed9d79741cc5b5c67b6459963d1acbf2be5bac5e4648a319c9b6b08d65b3181bb3790163360b5b3606db32b60c983d648310080961c90664634908d94312b2ef2464812c2404b213c8bf39efe49d774e4edebb3553f7e853b96bdeb4dc35ae96ef9c7335d557357d7ff7ebdb353dd5dd336f0441900a4a8f1164c706873ef8ffddfa3977788ff618d79573c9994a08674d4238472484b336219c2313c2599710ce5109e1ac4f08e7e81839155b4d30f81137ef1807bac6cd984e98a60d09d03493304dc72640d3c6201963d4b88470362584737c42382724847362423827258473724238a72484736a4238a72584737a42386724847366423867258473764238e72484b339219cc72484f3d884701e9710cee363e49c079c27e8e713f5f35cfdcc7de6ebe705fa39ab9f5b748eb57af924b256b236c567fc4f9d68c89315c83af4ff9af5ff3ac916922d225b4cd6457632d912b2a564cbc84e215baef35f41b692ec54b2d3c84e273b836c15d99964abc9d6909d45b696ec6cb273c8ce253b8fec7cb27564ebc936182c1bc92e20bb906c13d945649bc92e26bb84ec52b2cbc82e27db42d6437605d956b26d64bd64dbc976905d49b693ec2ab2abc97691ed26db43d647b697ec1ab27d64fbc9fa0dcd0e905d4b761dd9f506e741b21bc86e24bb89ec66b25bc86e25bb8dec4d64b793bd99ec2d646f257b1bd9dbc9ee20bb93ec1d647791bd93ec6eb27bc8de45762fd9bbc9de43761fd97bc9ee277b80ec41b287340b17fbfbc8de4ff601b20f927d88ec61b247c81e25fb30d947c83e4af631b28f933d46f638d913644f927d82ec93644f917d8aec69b24f937d86ecb3649f23fb3cd917c8be48f625b267c8be4cf615b2af923d4bf635b2af937d83ec9b64df22fb36d977c8be4bf63db2ef93fd80ec87643f32347f8eecc7643f217b5eff8fe7907e4af633ddfeb97efe857e7e413fbf68bce69764bf327cbf26fb8de17b89ecb7bafd3bfdfcb27efebd7e7e453fbfaa9fffa09fffa89fffa49fffac9fffa29fffaa9fffa69f5fd3cf7fd7cfafebe737c80ae34bedfa60e0d11dc434ee74f476a9f30f2cf609c1e087d26284fe1f3f376b7fad5ee667d66ea45e1e69f8ebf4729db19e7abd5c6ff89bf47293e19fa0972718fe497a7992e19fa297a7803f1dc03ca4f62bdf08ed4a818febb0067c2383c19a285f1daf0e7ca382c15a281f6fc73af08dd6be51e01ba37df5e04b6bdf68d68cac41fbba83b86a22d7a3d69b897bbdfadcccd8f879b7aaf5363ae21d173f6faf5a6f93035e551f7ab808c641dd4cd0be26f04dd4bef1e09ba47d13c03759fb26826f8af64d02df54ed9b0cbe69da37057cd3b56f2af86668df34f0cdd4bee9e09ba57d33c0375bfb66826f8ef6cd025fb3f6cd06df31da37077cc76a5f33f88ed3be63c077bcf61d0b3e1e3f8f031f1fc31daf7d6a4cf85700afd1fe1af09dc8632ef8e6f2780bbe793cd6826f3e8fb3e05b00b1d9978531847d2ddac7e391fadf42ddee0ee2aafffc36b5de4571af97d6acd6db15ff7a8be7ad4e0e0674ed86388b40ab25ba1de3b531ed183ba58de3b0bf16daaba02ff7633df83d85d9d57bc762dd5e52e6750b8dd765a0cfe290fcbb8378f3ef3278ba0c6655ff4b8123fe9a2db44bcd56fc885cb31ba1af597b7c7c331c6b760d7038a8d94e37359bcf49cd96e6178220bcf6f8187738d6ec26e088bf663ba5662b7f44aed96ba0af597bfc396738d6ec76e088bf661775cab141c58fc8357b2bf4356b8f3feb0ec79aed070e0735db25e36cc58fc8357b37f4356b8fe75d8663cdde0e1cf1d76c97a39a2d48cd06a5734541105e7b3c07381c6bf65ee088bf66b776c9b141c58fc835fb24f4356b8fe7a38763cd3e0c1c0e6ad6d5fc6c5e6ab6748e3c08c26b8fcf8d0cc79a7d4ab7d579869febf30c33c1f70bed9b05be17b46f36f85ed4be399057fcfbc0b682ec03153f22ef03cf415fb3969b757b38ee03df000e0735db29355bf12372cdbe047dcdda3b56b78763cd3e0f1c0e6a7691d46cc58fc835fb1af4356b8faf5f188e35fbb26eabe385dfe9e38513c1f7b2f6cd05dfefb56f1ef85ed1bef9e07b55fb1680ef0fda9705df1fb5af057c7fd2be93c0f767ed6b05df5fb4af0d7c7fd5be1cf8fea67dede07b4dfbf2e0fbbbf615c0f7baf67580ef0dedebd43e754e80af4ff99ef6d5434edd417cdbb6784d4a30f8913296bba1dde296279709065f57cdb15ae38f5550b99f14549e7b2bf0b439c83d0d312ae169039e5cfc3cc56b2fdae35f6f711b9f64689a865827415e790779a52016af9b97395e067c381ee443180bf133e653108bd7cdcb0560641f8e4f3cbef2fea3c6e6b1a9015e07fb52f1fd19e3750307c7ab853e2f340df41dafd91ae0ff3c0634401bc7ef9ce17354abed787d3faf9b97db819173cc559f315f29639bc1580f6c31f214356b3378da42b4385a633b18ab8bfb1fc7e0fd8ff7258e570b7dfea766a0ef8929a76cf9a8efb3589ff18ffff91cee9b95f0e481c7c518e3e87d2e87ef4dff09e2adb50e43ab9ca15506fa1440bf0e07fa957bafe478c22cccc22cccc22cccc22cccc22cccc22cccc22cccc22cccc22cccc22cccc2ec3f339e3fc6f36d61e7668f2423fbda81c7c53c7ff13b49f4ba78fdeabcce33705e27fef316f91c9e77e46bb2e61939d7429fe752036ccf869cf3c5f3bbad86cfd13517c56d89d75c74c332c7c373d0780d88ab73885983271ba2850fb19b638b9ddfea66fbe673ea3b01d4f7eab418db34ecfa9305864fd5e9bf6b06f27671ee2feab9481c4bb81de7b9bd4c70e8b5036eae71298d1f3c5ed50483c70f7c9fe98c3df6e073987cbebcd3885d0b7d5e4a0d6c9b85f0ffeee0d0eb72b00faf9b97e7c16b171aeb6e74976fd96b133a819bdb75466e2dc0cd7d5e81f1fc56d84f1cbcc7e5f1fab000780323277ee039fbf88f014ad710e423f074008f83ebc5da1d1debe4b01ee3be8660a1a155d83114f7e904fd62bf1e5eef1fe671302f733c61166661166661166661166661166661166661166661166661166661166661f69f19efef66d634f46bf784b14ad75d14cf67f0f76de079b1dd3503715d9f03e4734ef38d9c6ba1cfd335036cd7e8764370e8b516b66de9eadc9a6d5b72bc06c807cf05b9ba67bd60f01442b4f02176736cb14bd710c4bf7d07ae21c81bdbb4ddd89e78ee1cf7a15ba14ecdefb848833ed5b86ec9b66d381eee4b05f0711befcd76a135be9798d714713c3c777ea7d696cf9dc73f56e7732ec70dfeee2abe4e201f922bf7b907c6be7b751baf1f6987753d12f27f7e943b4fcdfa1d89eff1c2d83e7c8fd7c335037dcdefe362ada37e8f57ce789d8fdfe3751fd4d923f0feef6a4c5a1c846b340f34e23e781ce4e03a93d031d2bcb652f5e1ed3fcae883c72cdce77118a36cd7ae867df79183efe72bfbdd471c0f8f8d3b80d1ccd1bcc6f468bf46ec19182f16c2ffbb83c3bf46ec19a821bc4e2b30d63f1fd6cf5ca302fb7b0bf779d6781f75b56fd9ae41c37dcbfc3cc0bc780d1af7f9168c55ff84e32cf3d81fafd7aac6673bdbf5e11c0f8fa9a2e48ee342dcef8d588fc8c2f1b01e7f6cd4e3420b776bc86b7f6a792d6bc5d71ce36727533fa5c362784d772c3a94c69b2e2317dea716432edce797c67e13ff3153e9f833fe5c071f0ff1185408c995fbfc16f6b597e1f892b7138e95ff08f93f3fca1d7fb27e2ae7a5f1e75cdcbecbf4ba78fb2e0d897d0ab0c614bb1d63f3f127c7617f2db4df80f713eec77ab0d6ccaef6113e864376f3759dc6eb32d0674948fedd41bcf92f3578961accaa765e853afb071c7fba1aab9758349a0f1a711fbc278adf47b8bfed3dc6c5f5bee5de63c2be1f12c7f67f3a9dd30b6733e7f4c28e4fb80fbf168f4ffe1bc6d974485f73ae92df43e2bc6619ef11c942dc7f87c4ed8e59cf4c3058cf8cc1e13276a311bbb18ab19b8cd84d558c2d9a8be63e69eed3f77fe3f774d72480714402186b13c03832018c7509601c9500c6fa04308e4e00e3980430a681f148beb73bd0271f873e0da0534b08b7abfbf15b0c46f3bb1232062ff31cc963245fb7e158fdecf3366c049e6176ac19cb361ca79f7dde864d6e758c65bc1f9f00c60909609c9800c64909609c9c00c62909609c9a00c66909609c9e00c61909609c9900c65909609c9d00c63909606c4e00e33109603c36018cc72580f1f804309e9000c624cc552e70cb38e4cfd78ac7c16f4276e067f44a785c7fb7a6a33c8bd767f1758c717f479af9bb8e2d865678cf89ebdf834d05f6ef4ae578c22ccc36661c0bf1f7b9b95fd613c6b0ef1e76306e447e4f6975cb33e8b7b75bdd6e8bc8ef556e7f13b4f45e7534fc2668d8fd2d61dff7ce7d70dc3852d73162ece6205e2d3a2ad0a24af7603bf9befeb60432fbf21b033c3e85fdc640d613c62afdc640e4f7aa7c888ead6e19f3436574752f33d64b253cf8bd182e6aca4d9ee5bfcb99e3e1f77fe098e4ea9a7ef3fe385eaee43b4b84599885599885599885599885599885599885599885599885599885599885d90f663cc780dfe5ccfdb29e309adf93e4683e3ef2b99ace101d5bdd32e687ca88df7f161f4fe95c4d94efa95a043c2e6aca4d9ea57335e6f76d761a39e1f76de2feedea3bc816193cbcbc18b683300bb3300bb3300bb3300bb3300bb3300bb3300bb3300bb3300bb3300bb330fbcd8ce71898350dfdb29e30b26f21f0b8f83da6a8e7411685e8d8ea96313f5446c5e3e037e43ab05e2ae1c1dff57051538ef21cf45b79bcee45464e19e883fbf7c90ef24c05f6df8fc3dfd51366610e63c6318b59d3d02feb0923fb16038f83fd3bf2d8df15a263ab5bc6fc5019154ffcbf61593a4fdf1581077fd3c8454db9c9b334f69bbf9564fe566706fae0feede077b3427f27899797c27688c25c4820b3e82c3adb984567d1d9c62c3a8bce3666d15974b6318bcea2b38d5974169d6dcca2b3e86c63169d45671bb3e82c3adb9845e7a347673cc7c0ac69e897f584917d27038f83f9f8c8e76a9684e8d8ea96313f5446c5b32c769e420eeba5129e65c0e3a2a6dce4593a57738a91d31223a70cf4c131e9140779a6202eaf9b974f81ed30dc990b096496daa80eb3d48630db98a53684d9c62cb521cc3666a90d61b6314b6d08b38d596a43986dcc521bc26c6396da10661bb3d48630db98a53684d9c62cb521cc3666a90d61b6314b6d08b38d596a43986dcc3ed4065ea3c3ac69e897f584917d4b816799039ea8d7112d0bd1b1d52d637ea88c8a6779fc3c1d582f95f02c071e1735e528cfe2b54edd464ecb8c9c32d007c7a46e0779a6202eaf1bf30ef476e81666610e61c6318b59d3d02feb0923fb4e011e07fb77e4b17f79888ead6e19f34365543c2b1cf12c8fc0b30278bae3e76977946771ec5f69e4b4dcc829037d70ff5ee920cf14c4e575f3f24ad80e4963c67d8959d3d02feb0923d618f338a8bbc863d28a101d5b13c0d89600c6d109601c9300c63430661d30a6743ce4e1e5ac5b7df271e8d3003af93aee6580c7d5366c34781a43b4f0751b8ed5cf3e6fc346e071b50d9b0c9ea6102d7cdd86e3f4b3cfdbb0c9ad8eb18cf7e313c03821018c1313c03829018c9313c03825018c5313c0382d018cd313c03823018c3313c0382b018cb313c03827018c38a771243fe71ec9cf6747f27385682e9ae70eef219a8be6a2b9682e9a07a279eef01ea2b9c79a27e13384300e6fc682a78cc8938a8f2787b963ac951ee4ce0c49632c2480d1f57546a2632c8cf9a1322a9ed31cf1ac8cc0731af09c1a3f4fbba33c8bd71a9e6ee4b4d2c829037db00e4e7790670ae2f2ba79f974d80e4963c67d8959d3d02feb0923fb4e051e57fb5794fd1df7af331cf19c1681e70ce071b1bd1ce559dcdf5719399d66e494813eb8efac7290670ae2f2ba7979156c87a431e3feceac69e897f584917da7038fabfd2bcafe8efbd7998e78ce88c07326f0b8d85e8ef22ceeefab8d9cce3072ca401fdc77563bc833057179ddbcbc1ab643d298717f67d634f4cb7ac2c8be55c0e3a0ee227fe6383344c7b604301612c0283a8a8e3e318a8e478f8ec2288cc2288c4782310963b8bccf449f1bc0cfe26be2e7e9c0cf6d95f0ac011e179fed1ce5599c1b38cbc8e94c23a70cf4c13a38cb419e2988cbebe6e5b3603b08b3308731e398c5ac69e897f584917dab81c7c1fe1d79ec5f13a263ab5bc6fc501915cfdad8793a72582f95f0ac051e1735e526cfd2d87fb691d31a23a70cf4c1fdfb6c0779a6202eaf9b97cf86ed1085b9904066d15974b6318bcea2b38d5974169d6dcca2b3e86c63169d45671bb3e82c3adb984567d1d9c62c3a8bce3666d15974b6318bcea2b38d5974169d6dcca2b3e86c63169d45671bb3e82c3adb984567d1d9c62c3a8bce3666d15974b6318bcea2b38d5974169d6dcca2b3e86c63169d87c6ac78f8ba73664d43bfac278cec3b0b78d63ae0897a1dfeda101d5bdd32e687caa878ce899fa703eba5129e7380c7454d39cab378afc0b9464e6b8d9c32d007c7a4731de49982b8bc6e5e3e17b64314e642029945e7a131e398c5ac69e897f584917d67038f83fd3bf2d87f4e888ead6e19f34365543ce7c5cfd381f55209cf79c0e3a2a61ce5591cfbcf37723ac7c829037d704c3adf419e2988cbebe6e5f3613b08b3308731e398c5ac69e897f584917de7028f83fd3bf2d87f5e888ead6e19f34365543ceb62e7c9e7b05e2ae159073c2e6aca4d9ea5b17fbd91d379464e19e883fbf77a0779a6202eaf9b97d7c37688c25c4820b3e82c3adb984567d1d9c62c3a8bce3666d15974b6318bcea2b38d5974169d6dcca2b3e86c63169d45671bb3e82c3adb9845e7a347673cc7c0ac69e897f584917de7038f83f9f8c8e76ad685e8d8ea96313f5446c5b321769e420eeba5129e0dc0e3a2a6dce4593a57b3d1c8699d915306fae098b4d1419e2988cbebe6e58db01d863b732181cc521bd56196da10661bb3d48630db98a53684d9c62cb521cc3666a90d61b6314b6d08b38d596a43986dcc521bc26c6396da10661bb3d48630db98a53684d9c62cb521cc3666a90d61b6314b6d08b38dd987dac06b7498350dfdb29e30b26f3df06c70c013f53aa20d213ab6ba65cc0f9551f15c103f4f07d64b253c17008f8b9a729467f15aa70b8d9c36183965a00f8e49173ac833057179ddbc7c216c076116e630661cb398350dfdb29e30b26f23f038d8bf238ffd1784e8d8ea96313f5446c5b3297e9e0eac974a7836018f8b9a72946771ecbfc8c8e90223a70cf4c1fdfb220779a6202eaf9b972f82ed20ccc21cc68c6316b3a6a15fd61346f65d083c0ef6efc863ffa6101d5bdd32e687caa87836c7cfd381f55209cf66e07151538ef22c8efd171b396d3272ca401fdcbf2f7690670ae2f2ba79f962d80ec22ccc61cc3866316b1afa653d6164df45c0e360ff8e3cf66f0ed1b135018c6d09602c2480d1b18ef9a1322a9e4b1cf16c8ec07309f0b8183f1ce5597c9fbfd4c869b3915306fa601d5cea20cf14c4e575f3f2a5b01d92c68cfb12b3a6a15fd61346f65d0c3caef6af28fb3bee5f9739e2b92402cf65c0e3627b39cab3b8bf5f6ee47489915306fae0be73b9833c531097d7cdcb97c376481a33eeefcc9a867e594f18d97729f0b8dabfa2ecefb87f6d71c47359049e2dc0e3627b39cab3b8bff718395d66e494813eb8eff438c833057179ddbcccf192c88cfb3bb3a6a15fd61346f65d0e3c5b1cf044dddfb70063d8670e5f190b0960141d45479f1845c7a34747611446618cc6b822018cb2ad85d157c6ac03c6948e873cbc9cad42ec4623766315633719b19baa185b3417cd4573d15c3417cd0f37b6682e9a8be6a2b9682e9a1f6e6cd15c3417cd4573d15c343fdcd8a2b9682e9a8be6a2b9687eb8b14573d1dc8cdd137fecc8d773f700cf16075a38ca33a7d67b855ed77f62d44f69b5d5d02a6b6895813e57807e5b1de81776cd352f73bca8cc277ac0ec28767e2cad6334e4cf310a861e2afe3647b9dbc6bc6d55886d1bf3aa11db36e65523b6682e9a8be647afe6d8ae0d0ebd0749ada357b747ea65eebf025ec77d1e68283d3706b23d5dc4967d483417cd45f323a139ea5253059ec0e009caf0ccf68c67aa673ce33de319ed19cf08cf78e67ac633c7339e699ef14cf08c678c673cb59ef14cf78c67a2673c69cf78467ac633cf339e199ef1ccf78c6792673c0d9ef1643ce3a9f38c67a6673c933de319eb194fa3673c0b3ce319e5194f8f673cb33ce399e219cf38cf789a3ce3a9f78c27e5014f3a38f49a9a34fcbf077c35c66bd5783571fcc0ffb76b7f0dbc66876e8f0859f776f0f1b9aa1d21af459db6432eddba9d3bbc4751278cd50dcb1caf01387678c253ef194f93673ce33ce399e219cf2ccf787a3ce319e519cf02cf781a3de319eb19cf64cf78667ac653e7194fc6339e06cf782679c633df339e199ef1ccf38c67a4673c69cf78267ac633dd339e5acf78c678c633c1339e699ef1ccf18c67ae673c233ce319ed19cf78cf78a67ac633db339e9a109e1e473c3c7fc9ebe6e51e4f623bd80ec5fb16af7494d34ebdae3abd5ee6e778b5d0e721fdc14dcd47e06b99cb9c6fc6b9a69da051afa35c6cd72ef75621b6eddae56ac4b65dbb5c8dd8a2b9682e9a8be6ea11e33df59d72bf45e53c72bf45791eb9dfa23ccf5ccf78e47e8bf23c72bf45791eb9dfa23c8fdc6f519e47eeb728cfd3e3198fdcff519e47eeff28cf23f77f94e791fb3fcaf3c8fd1fe579e4fe8ff23c72bf45791eb9dfa23c8fdc6f7128cfff77bf05de27c1e7a67ac177a56ef780af262406af6727f86a759bd7a1c69b39e30f65a881d75c15c27565483c8e7355c86baba13bc6ea86658e87f76f5ce5094fbd673c4d9ef18cf38c678a673cb33ce319e519cf02cf781a3de319eb19cf64cf78667ac653e7194fc6339e06cf782679c633df339e199ef1f478c633cf339e919ef1a43de399e819cf74cf786a3de319e319cf04cf78a679c633c7339eb99ef18cf08c67b4673ce33de399ea19cf6ccf786a42787a1cf1d8aea3eda9426cdb75b4d5886dbb8eb61ab14573d15c343fba35bf3afed81de960f06fb6a947ca58ee86f6d5c0e3e2fdce519ec5fbef76e975c5788f434e69b5dbd0aac7d02a037d76817ebb1de89782b8bc6e5ee678c22ccc3666157b4ffcb18be30cc6667d0283871f7b1c6be128cfe278d017846bccf132d007b7799f833c531097d7cdcb7d21b19b8378b5d85b81167b4378f656590b8e1795795702997dd059c5be26f6d8f95cda88cdfa04060f3fae71ac859b3c4bfbd6be205c638e97813e58a7fb1ce49982b8bc6e5ede07db4198855998855998855998855998855998855998855998855998855998855998fd6656b1f7c71ebb347f8fb1599fc0e0e1c77ec75ab8c9b3347fdf1f846bccf132d007b779bf833c531097d7cdcbfdb01d84599885599885599885599885599885599885599885599885599885599885d96f6615fb40fcb18bf7f9606cd6273078f871c0b1168ef22ccedf5f1b846bccf132d007b7f9b50ef24c415c5e372f5f0bdb419885398c59c5be2ef6d8a5f379189bf5090c1e7e5ce7580b377996c683eb83708d395e06fae036bfde419e2988cbebe6e5eb613b4461ee4b20b3e82c3adb984567d1d9c62c3a8bce3666d15974b6318bcea2b38d5974169d6dcca2b3e86c63169d45671bb3e82c3adb9845e7a3476715fb60ecb10bc5f97b8ccdfa04060f3f0e3ad6c24d9ea5f9fb1b82708d395e06fa609ddee020cf14c4e575f3f20db01d863b735f0299a536aac32cb521cc3666a90d61b6314b6d08b38d596a43986dcc521bc26c6396da10661bb3d48630db98a53684d9c62cb521cc3666a90d61b6314b6d08b38d596a43986dcc521bc26c6396da10661bb30fb5a162df187fece2fdec189bf5090c1e7edce8580b477916af7fb92908d798e365a00fd6e94d0ef24c415c5e372fdf04db419885398c59c5be39fed8f9b4119bf5090c1e7edcec580b477916c7835b82708d395e06fae036bfc5419e2988cbebe6e55b603b248d19b75f2abed8c5eb3639468d7e56be5b757b04f86ed3ed5af0bd49b74782ef76ddae03df9b757b14f8de02b9b1efadbabd007c6fd3ed1ef0bd5db7b781ef0eddbe1a7c77eaf66ef0bd43b7f780ef2eddde0bbe77eaf635e0bb5bb7f781ef1eddde0fbe77e9763ff8eed5ed03e07bb76e5f0bbef7e8f675e0bb4fb7af07df7b75fb20f8eed7ed1bc0f7806edf08be0775fb26f03da4dbbbc0f73edd1e0dbef7ebf618f07d00dafcfc41dd6e00df87743b03be87757b2cf81ed1ed46f03daadbe3c0f761dd6e02df47747b3cf83eaadb13c0f731dd9e08be8febf624f03da6db93c1f7b86e4f01df13ba3d157c4feaf634f07d42b7a783ef93ba3d037c4fe9f64cf07d4ab76781ef69dd9e0dbe4febf61cf07d46b771fb7e56b76f061f8f03b7808fc7815bc1c7e3c06de0e371e04de0e371e076f0f138f066f0f138f016f071ddbd157c5c776f031fd7dddbc1c7757707f8b8eeee041fd7dd3bc0c7757717f8b8eede093eaebbbbc1c775770ff8b8eede053eaebb7bc1c775f76ef071ddbd077c5c77f7818febeebde0e3babb1f7c5c770f808febee41f071dd3d043eaebbf7818febeefde0e3bac371a159b73f08be6374fb43e03b56b71f06df71bafd08f88ed7ed47c177826e7f187c27eaf647c03757b73f0abe79bafd31f0cdd7ed8f838fdf9b1e035f56b71f075f8b6e3f01be9374fb49f0b5eaf627c0d7a6db9f045f4eb79f025fbb6e7f0a7c79dd7e1a7c05ddfe34f83a74fb33e0ebd46d1e17d4fed700b9709e0dc07d52080ffbea81a73b88f7988963f1ba79390f8cac637bf519f39532e60c46c5d3e14033ac0d7e94fb4cd2013c05073c8ef22c7e26e93472ca1b3965a0cf899067a7833c531097d7cdcb9d10dbc536472deaf47ae71a5ad4429f37f41b9a7aff2ba723af43d56f7b482eae74cc193cb990d88b1debc8ebe631717115622f3462b719b1713ce747b97d7b21302f72c0acd6db15ff7a8bfbf6c97a5d5ccf1ca70d725a021ac49513c64e69e338ecaf85f6ff360df4e57eac07bf7732bbda8f785b22bbf9ba0ee37519e8b33824ffee20defcbb0c9e2e83591dffff57d3008783fda158038b0d0e5e6e03edba2cda2d06edb84f3bf8f818a7137c7cacc0ebc063253c8e68893fdfd0b187975b80917d0b81b12584b13d7ec6e2b14e8bc1d812a22dfb1601cf42479a2d3278e61afae0fbf228a30fbfb616fa8cd71fec1a83c19f21b8afaafdb1a981bcf833f37f8278c7d53a077ae1e7f900f4090c0df9c10cf5c1c067fe3879c604039fe9f7f7f7edebd9d1bbaeb7675b0ad06a0d4c7c4e85a451033e6c8f08f105c1e0a90b9cf2e4a90b9cf2ac3164c12913eeaf3e4aa9b4787aa077f7cefe8d7b7af76cdd77706f7fefb6b57d3b907aa4418fa4b60c90147dfca80f062659ba83784f76d419b1ca154f3d3c8f8a9fa7dd519ec5379ed1464e75464e19e83312fe37da419e2988cbebe6e5d121b1631c888a5a8ca9408b31213c63aaac054e54b30ff754fe3f9e9ca83172c13d1a7332eb3cd68438e009b0fe948653ff533bfb489dcca8606063f3e8a98e2ad54650339cea5d4bcd60aa83333504a919493503a9661cd50ca39a515433886ac650cd10aa19413503a866fcd40c9f9ad16b0e4a33766a864ecdc81d0f5cdf0356f5895abd3baa19353583a666ccd451957af7574722eae8571da9a94f6feae8407daa54330cea9d561dc5a87768f5aeaa8ed4d4119a3aa25647984bc996919d42b65c6bbd826c25d9a964a7919d4e7606d92ab233c95693ad213b8b6c2dd9d964e7909d4b761ed9f964ebc8d6936d20db487601d985649bc82e22db4c7631d9256497925d467639d996a07456ea0ab2ad41e96c542fd976b21d645792ed24bb2a289d955233efea4c943af3a4ae8651679bd4d9257536499d3d52678bd4d9217536489dfd51677bd4d91d7536479dbd51676bd44cbd9a995733f1b705a5997635b3ae66d2d5ccb99a295733e36a26fc8ea034d3ad66b6ef0a4a33d76aa65acd4cab996835f3ac669ad5ccb29a495633c76aa658cd0cab996035f3ab667ad5ccae9ac95533b76aa656cdccaa99d84783d24cab9a595533a96ae654cd94aa99513513fa44509ae954339b6a2653cd5caa994a3533a96622d5cca39a69fc1cd9e7c9be40f645b22f913d43f665b2af907d95ec59b2af917d9dec1b64df24fb16d9b7c9be43f6dda05483df27fb01d90fc97e44f61cd98fc97e42f63cd94fc97e46f673b25f90bd40f622d92fc97e45f66bb2df90bd44f65bb2df91bd4cf67bb257c85e25fb03d91fc9fe44f667b2bf90fd95ec6f64af91fd9dec75b2378281330e3850fc462ff0ec774f7f7fefeebdfdcdfd7dcdbb0feceadfb977d7c1e6eb76f65fd9dc776defbeedbbfaaec317dfa187269eda5fb16f5fcfc1e69d7bb6f55edfdc77a0bfb96f7bf3157d07f66cdb8f2f7a4cbf68e6a1117bb66db307fb52cd61907e7588419fd3afe39326abcbe7f6fc50047971282ffad71013ba5f9f8a5aaa97d7978e689bf7efeaeb6fce35efa1bf3dbbe835bddb5a9bf17ffb49e4fdfdcdfbfb7bf6f5376fdfd7b7bbb9bd15d7fb60660849bcde348417358daf3cf3e0ff00fdc58975355f0300",
+ "bytecode": "0x1f8b08000000000000ffed9d77741cd5f5c767654996bd5acb927b17d5658db45a49b6856d9069a677530dd896646370015bf44e1a0921210d924008e924815448ef0909a47712d220014280ff7ee7fcf21fe777dfeebb475f3fcfec6f479ebb7e23df3de76adf5cbd9dfbb9dfb9f376f6cdccee6b41106482f2631cd9c1c1de0ffe7fbf7d2eecdba32bc1751524393329e1ac4b09e7b89470d6a784b321259c8d29e11c9f12cea694704e4890d3b0d5057b3e92e69d28a06bd28cd99469da9c024d7329d374520a346d09d231464d4e09676b4a38db52c23925259c5353c2392d259cd353c23923259c3353c2392b259cb353c23927259c7353c2392f259cf353c2b920259ced29e13c28259c07a784f39094701e9a20e762e03ccc3e1f6e9f17dae745f699fb2eb1cf79fbbcd4e6586f978f20eb20eb347cceffcc8986225937598ff3bf5eb26564cbc956d8ffb5dbfff5911d49b6926c15d96ab2a3c88eb65aac213b86ec58b2e3c88e273b816c2dd9896427919d4c760ad9a964a7919d4e7606d9996467919d4d760ed9b964ebc8ce233b9fec0287e542b28bc82e265b4f7609d9a56497916d20db48b6896c806c906c886c33d916b2cbc9b6925d417625d936b2ed643bc876925d457635d92eb2dd64c364d7905d4b761dd9f58e663790dd487613d9cd0ee72d64b792dd46763bd91d647792bd81ec8d646f227b33d95bc8ee227b2bd9dbc8ee267b3bd93d64ef207b27d9bd64ef227b37d97bc8de4bf63eb2fbc8ee277b3fd907c83e48f600d983968577840f913d44f661b287c93e42f651b28f917d9cec13649f24fb14d923649f26fb0cd967c91e257b8cec73649f27fb02d917c9be44f665b2c7c99e20fb0ad957c9be46f675b26f907d93ec5b64df26fb0ed977c9be47f67db21f90fd90ec47644f92fd98ec27644f913d4df653b29f91fddcd1fc1764bf24fb15d9afedff787ee93764bfb5eddfd9e7dfdbe73fd8e73f3aaf7986ec4f8eefcf64cf3abebf90fdd5b6ff669fff6e9fff619f9fb3cfcfdbe77fdae77fd9e717ecf38bf6f925fbfc6ffbfcb27dfe8f7d7ec53ebf6a9f5f23bba9addc6e0a461efd41426352cfd0a03937c1621f16ecf9305a8cb3ffe3e776ebafb7cbfcccda35d8e506c7df68971b9df534d9e526c7df6a975b1dff14bb3cc5f14fb3cbd31cff0cbb3c03fcd900e628addff8c65957067c5c8775e06b08f6d4c4f81a7975e01b1feca985f1f1766c04df04eb1b0fbe89d6d704beacf54d60cdc89aadaf3f48aa260a1bcd7a7349afd79eb799943cef80596f8b10efe4e47987cc7a5b05784d7dd8e122980c7533c5fa5ac137d5fadac037cdfaa6806fbaf54d05df0ceb9b06be99d6371d7cb3ac6f06f8665bdf4cf0cdb1be59e09b6b7db3c137cffae6806fbef5cd05df02eb9b07be76eb9b0fbe83ac6f01f80eb6be76f01d627d0781ef50eb3b187c3c7e1e023e3ebe3bd4facc98509f81d7583f8f47a5d7f0980bbe853cde826f118fb5e05bcce32cf896406cf6e5610c61df52ebe3f1c8fc6f856df70749d57f71d0acb72fe9f5d29acd7a5726bfded239ad55c188aefd10a70fb45a6ddb095e37d385b133d6380efbeba1bd16fa723fd683df5398ddbc771c69dbab2bbc6e85f3ba1cf4393224fffe20d9fc573a3c2b1de606c85fa666bbbbb466ab7ec4aed975d0d7ad3d3ebe198b357b327008d46cafd66cd58fd8353b007ddddae363dcb158b317008740cdf6c9d46cb1a0355b9eeb0a82f0dae3cf3963b166370347f235dbab355bfd2376cdde067ddddae3cfba63b166878123f99a5ddea7c706553f62d7ec3dd0d7ad3d9e77198b357b277008d4eca08eb3553f62d7ec03d0d7ad3d9e031c8b357b2f70245fb37d4235dbad351b94cf5b064178edf17cf458acd9878023f99a1dd0f9d9ea1fb16bf6abd0d7ad3d3e3732166bf651e048be6607a5e6678b5ab3e5eb358220bcf6f83cdd58acd96fd8b63937f63b7b6e6c3ef87e6f7d0bc0f707b8ce807d7fb4be83202f817d60b9ee03553f62ef03cf405fb7960fb6edb1b80ffc1c38046ab64f6bb6ea47ec9a7d11fabab5c7d72f8cc59a7d1638046a7693d66cd58fd835fb3fd0d7ad3dbe96662cd6eccbb66d8e17fe668f171681efefd6b7187cffb0be25e07bcefaf2e07bdefa9682ef9fd67704f8fe657d1de07bc1fa3ac1f7a2f515c0f792f57581efdfd65704dfcbd6d70dbeff585f0ff85eb1be5ef0bd6a7dcbc0f79af52db73e731e8bafa97acafa9a20f7fe20b96d5bba8e2ad8f3917196fba1bd5496a7900bf6bc4f806375241fabdbe47e44507dee1dc0d329907b166254c3d3093c85e4794a9f47bb925f6f691b1fe1689a855847405e4581bc32108bd7cdcb1c2f073e1c378a218cddc9331633108bd7cdcbddc0c83e1cc7f83e1cde7fccd83c3733c22bb02f95de9f315e3f7070bc7ae833ab6da46fbb656b86fff318d00c6d1ce70b8e4fa8564bb5823767f5c3721730728e85da3316ab65ec74189b802d419e92669d0e4f678816076a6c81b1bab4ff710cdeff785fe278f5d067fcb891be3d1951b662dcf759accfe4c7ff6201f7cd6a788ac02331c608bdcf15f0bde9f520d95aeb71b42a385ae5a04f37e8d723a05fa5f74a8ea7cccaaccccaaccccaaccccaaccccaaccccaaccccaaccccaaccccaaccccaacccfe33e3f9633cdf16766e767f32b2af0b7824e6f94bdfa363d7c5eb37e7759e84f33ac99fb72816f0bc235f93b5c8c9b91efa3c9319617b3ae49c2f9edfed707c42d75c94b6255e73d10fcb1c0fcf41e3352052e710f30e4f3e440b1f62b72716bb3820b37d8b05f33d16e6bba0963adb34ecfa93258ecfd4e9c47123794b9cfb8b7b2e12c7126e27796e2f17ec7ded80cc352ee5f183c7abba60cff103df677a138fbde7394c3e5fdeebc4ae873e2f6646b6cd32f87f7fb0f77539d887d7cdcb8be0b5cb9c75b7c8e55bf1da845ee0e676a393db52e0e63eafc0787e4f5db92df41e57c4ebc302e00d9c9cf881e7ec933f06285f43508cc1d3033c02d78b75091deb14b01e93be866099a355d83114f7e905fd9609e817761ccccb1c4f99955999955999955999955999955999955999955999955999955999955999fd67c6fbbb99350bfdba3c61acd17517a5f319fcbd1c785eeca6ba91b8d2e700f99cd36227e77ae8f3adba11b6db6cbb39d8fb5a8ba86d29756e2d6a5b72bc66c807cf0549ddb3deedf0748768e143ecf6c46297af21487efb8e5c435074b66997b33df1dc39ee43f7409dbadf7191057d6a71dd52d4b6e178b82f75838fdb786fb684d6f85ee25e53c4f1f0dcf9fd565b3e779efc585d2c488e1bfc9d3f7c9d40312457eef3208c7d0fd9365e3fd205eb7a2ce4fffca8749e9af513fa7eb0d2f6e5efabe2eddb17127b25b02614bb0b6367ac711cf6d743fbd1ba91beeef75fb1d6cc6ef611f73bbac25e57705e97833e2b42f2ef0f92cdbfcfe1e973984ded7c04eaec3178ff971a935604e11a2d028db80f1e07095c67123a46bad7569a3ebcfdc73b7df09885fb3c016354d4b5ab61df7db45c28bfa8ef3ee278786cdc038c6e8eee35a607fa35624fc278b10cfedf1fecfb35624f420de1755a81b3fec5b07ee61a1f44bfb7709fa79df751a97d2bea1a34dcb7dccf03cc8bd7a0719f5fc258c53f36da1cec7dec8fd76bd5e2b35dd4f5e11c0f8fa9e2e48ee342d2ef8d588fc8c2f1b01effe4d4e3b208ee8e90d7fe25e2b5ac155f738c9f9d5cfd8c0e897f2faa1d6ffa9c5c789f5a01b9709fe79dfd46eab75d92cf75cfe3211e83ba4372e53e2fc1bef6321c5ff276c2b1f2bf21ffe747a5e34ffc2ed755c9e75cdabefc9da5bc7d5785c43e0a58138add85b1f9f893e3b0bf1edaff0bef27dc8ff560ad99ddec237c0c87eceeeb7a9dd7e5a0cfca90fcfb8364f35fe5f0ac72984dedbc0a75f65f38fe941aab574668b41834e23e784f14bf8fe0f7bb86bdc7485cef5be93d26ecfb21716c0fc6d59ecd9dd30b3b3ee13efc5a3c3e69b0cc669ccd86f475e72af93d24c96b96f11e913cc4c57b44f2427ae6823df5cc391c92b15b9cd82d358cddeac46ead616cd55c35f749739fbeff1bbfa7bb2e058ce352c0589f02c686143036a680717c0a189b52c03821058c1353c09805c6fdf9de2ea04f31097d9a41a7a521dc52f7e32f7518ddef4ac839bcccb33f8f917cdd8693ecb3cfdbb00578c6d8b16622db70b27df6791bb6caea98c878df9602c62929609c9a02c66929609c9e02c61929609c9902c65929609c9d02c63929609c9b02c67929609c9f02c60529606c4f01e34129603c38058c87a480f1d014301e9602c634cc552e91651cf5e76bc323f09b903df819bd1a1ee9efd614cab3747d165fc798f477a4b9bfebb8d4d10aef3991fe3dd84c10fd5da91c4f9995398a19c742fc1d6fee97f78431ecbb8705c68dd8ef291db23c7bfcf67687ecb688fd5e25fb9ba0e5f7aa03e13741c3ee6f09fbbe77ee83e3c6feba8e1163b707c96ad153851635ba075be4fbfa3b53c8eccb6f0cf0f814f61b03794f186bf41b03b1dfab8a213a76c8321647cb2874ff580fd64b353cf8bd18123525799f5cd4f77f703cfcfe0f1c93a4aee977ef8fe3e56abeb3248cb93b85ccaaf3e89871ccc2ef86e17e794f18ddfbae84f6efd8637f6f888e1db28cc5d132cadc675dfe7d8038f7bd2d071e899a92c9b33cf6bbf7eff73a39e1fdfb382649ddd3b8dce1e1e515b01d94599995599995599995599995599995599995599995599995599995599995d96f663cc780dfc3c5fdf29e30b20fbfbf50e2fb5de39e07591ea263872c6371b48c8647e2fb15b15eaae1c1ef0994a829993cf7fcee6d5ef77227a71cf4c1fdfb48813c3341f4f751e3f7742bb3322bb3322bb3322bb3322bb3322bb3322bb3322bb3322bb3322bb3322bb3dfcc788e8159b3d02fef0923fb56008fc07c7cec73357d213a76c8321647cb6878047ec3b207eba51a1efc4d23899a12cab374aec6fdad24f7b73a73d007f76f81dfcd0afd9d245e5e05db419995398c19c72c66cd42bfbc278ceeef010beddfb1c7fe95213a76c8321647cb68785627ce533e4fbf3206cf6ae091a829993ccb63bffbbb7d2b9d9c72d007f76fa9dfed5bedf0f072a5df1aacc4dc9d4266d559758e62569d55e72866d559758e62569d55e72866d559758e62569d55e72866d559758e62569d55e72866d559758e62569d0f1c9df11c03b366a15fde1346f6ad021e81f9f8d8e76a5687e8d821cb581c2da3e1393a719eee02d64b353c47038f444dc9e4593e57d3efe4b4dac929077d704cea17c833037179dd987760b743ff1867ee4e21b3d6466d98b53694398a596b4399a398b53694398a596b4399a398b53694398a596b4399a398b53694398a596b4399a398b53694398a596b4399a398b53694398a596b4399a398b53694398a596b4399a3987da80dbc468759b3d02fef0923fb8e029ea30578e25e477474888e1db28cc5d1321a9e35c9f3f460bd54c3b30678fa93e7e912cab374add3314e4e473b39e5a00f8e49c708e49981b8bc6e5e3e06b683322b7318338e59cc9a857e794f18715f661e81fd3bf6d8bf2644c70e59c6e268190dcfb1423c6b62f01c0b3c123525946769ec3fcec9698d93530efae0fe7d9c409e1988cbebe6e5e3603ba48d19f72566cd42bfbc278cec3b067804ea2ef698746c888e1d2960ec4c01e38414304e4c01631618f3028c191b0f7978392fab4f31097d9a41275fc7bd1cf0486dc31687a725440b5fb7e124fbecf3366c011ea96dd8eaf0b48668e1eb369c6c9f7dde86adb23a2632deb7a580714a0a18a7a680715a0a18a7a78071460a1867a68071560a1867a780714e0a18e7a680715e0a18e7a78071410a18714e637f7ecedd9f9fcff6e7e70ad55c352fecdb433557cd5573d55c350f54f3c2be3d54738f354fc36708651cdb8cdd9e32224f26399e02e68eb124ceabc4cd3dec7a94343076a78051fa3a23d53111c6e268190dcf09423cc7c5e03901788e4f9ea74b28cfd2b5866b9d9c8e7372ca411fac83b5027966202eaf9b97d7c276481b33ee4bcc9a857e794f18d9773cf048ed5f71f677dcbf4e14e2392106cf89c023b1bd84f22cedef2739399de0e494833eb8ef9c24906706e2f2ba79f924d80e6963c6fd9d59b3d02fef0923fbd6028fd4fe15677fc7fdeb64219e1363f09c0c3c12db4b28cfd2fe7e8a93d3894e4e39e883fbce29027966202eaf9b974f81ed903666dcdf99350bfdf29e30b2ef24e011a8bbd89f394e0ed1b133058cdd2960541d55479f1855c70347476554466554c6fdc19886315cdf67e2cf0de067f15393e7e9c1cf6dd5f09c0a3ca724cfd3259467696ee03427a7939d9c72d007ebe034813c331097d7cdcba7c1765066650e63c6318b59b3d02fef0923fb4e011e81fd3bf6d87f6a888e1db28cc5d1321a9ed313e7e92960bd54c3733af048d4944c9ee5b1ff0c27a7539d9c72d007f7ef3304f2cc405c5e372f9f01db210e73770a995567d5398a5975569da3985567d5398a5975569da3985567d5398a5975569da3985567d5398a5975569da3985567d5398a5975569da3985567d5398a5975569da3985567d5398a5975569da3985567d5398a5975569da3985567d5398a5975569da3985567d5398a5975569da39855e7d131e335eecc9a857e794f18d9771af09c2ec013f73afcd34374ec90652c8e96d1f09c993c4f0fd64b353c67028f444d09e559ba57e02c27a7d39d9c72d007c7a4b304f2cc405c5e372f9f05db210e73770a9955e7d131e398c5ac59e897f784917d67008fc0fe1d7bec3f3344c70e59c6e268190dcfd9c9f3f460bd54c37336f048d494509ea5b1ff1c27a7339d9c72d007c7a47304f2cc405c5e372f9f03db419995398c19c72c66cd42bfbc278cec3b0b7804f6efd863ffd9213a76c8321647cb6878ce4d9ca758c07aa986e75ce091a829993ccb63ff3a27a7b39d9c72d007f7ef75027966202eaf9b97d7c17688c3dc9d4266d559758e62569d55e72866d559758e62569d55e72866d559758e62569d55e72866d559758e62569d55e72866d559758e62569d0f1c9df11c03b366a15fde1346f69d033c02f3f1b1cfd59c1ba263872c6371b48c86e7bcc479ba0b582fd5f09c073c1235259367f95ccdf94e4ee73a39e5a00f8e49e70be49981b8bc6e5e3e1fb6c35867ee4e21b3d6466d98b53694398a596b4399a398b53694398a596b4399a398b53694398a596b4399a398b53694398a596b63af8732dbb6d6863247316b6d287314b3d6863247316b6d287314b3d6863247316b6d287314b30fb581d7e8306b16fae53d6164df3ae0394f8027ee7544e785e8d821cb581c2da3e1b920799e1eac976a782e001e899a12cab374add3854e4ee73939e5a00f8e49170ae49981b8bc6e5ebe10b683322b7318338e59cc9a857e794f18d9773ef008ecdfb1c7fe0b4274ec90652c8e96d1f05c943c4f0fd64b353c17018f444d09e5591afb2f7672bac0c929077d70ffbe5820cf0cc4e575f3f2c5b01d945999c39871cc62d62cf4cb7bc2c8be0b814760ff8e3df65f14a263872c6371b48c86677df23c3d582fd5f0ac071e899a12cab334f65fe2e4749193530efae0fe7d89409e1988cbebe6e54b603b28b3328731e398c5ac59e897f784917d17038fc0fe1d7bec5f1fa263470a183b53c0d89d0246611d8ba365343c970af1ac8fc17329f0488c1f427996dee72f73725aefe494833e58079709e49981b8bc6e5ebe0cb643da98715f62d62cf4cb7bc2c8be4b80476aff8ab3bfe3feb54188e7d2183c1b8047627b09e559dadf373a395deae494833eb8ef6c14c833037179ddbcccf1d2c88cfb3bb366a15fde1346f65d063c1b92e789bdbf6f009e4d423c1b62f06c021e89ed259467697f1f7072dae0e494833eb8ef0c08e49981b8bc6e5e1e80ed903666dcdf37d8e72cf4cb7bc2c8be8dc0235077b13f736c0ad1b133058cdd2960541d55479f1855c7034747655446658cc7786c0a18755b2ba3af8c7901c68c8d873cbc9caf41ec1627764b0d63b73ab15b6b185b3557cd5573d55c3557cdf735b66aae9aabe6aab96aae9aef6b6cd55c3557cd5573d55c35dfd7d8aab96aae9aabe6aab96abeafb15573d5dc8d2d70bd69ecebb9078067938016427916cc7a07edba5e4f503fa3d590a355ded12a077d0641bf2101fdc2aeb9e6658e1797f9700f9885621727d13a2640fe1ca3dbd1c3c4df2c947bd498b7b906b1a3c6bc5ac48e1af36a115b3557cd55f30357736cd7077bdf8364d6b1c5b61bec32f73f165ec77d9e6c2e3fb704ba3d2562eb3ea49aabe6aaf9fed01c75a9ab014fe0f00415786af1f9240ecf7ccf78667ac6d3e619cf04cf78c679c6b3d0339e059ef1ccf28c678a673c133de3a9f78c67b6673c533de3c97ac6d3e019cf22cf78e678c6b3d8339e699ef1347bc693f38ca7d1339eb99ef14cf78c6792673c2d9ef12cf18c67bc673cf33ce399e119cf64cf785a3de369f28c27e3014f36d8fb9a9a2cfc7f007c75ce6bcdf8b0be6de4ff975b7f1dbc66ab6d8f0b59f7e5e0e373555b435e8b3a5d0eb9f4db7661df1e259d30563f2c73bc66e0d8ea094f93673cad9ef14cf68c6786673cf33ce319ef19cf12cf785a3ce399e419cf74cf78e67ac6d3e8194fce339e66cf78a679c6b3d8339e399ef12cf28ca7c1339eac673c533de399ed194fbd673c133de399e219cf2ccf781678c6b3d0339e719ef14cf08ca7cd339e999ef1ccf78c67c0339eba101ea9dfc5e0f94b5e372f0f78125b603b94ee5bbc4228a72bedba1aed7a999fe3d5439f9fd80f92667e045fcb5cee7c33d6ea95a0d116a15ca2ae5dde5283d851d72ed72276d4b5cbb588ad9aabe6aab96a6e1e09de53dfebf3fd167a7f43651ebdbfa1328fdedf509947ef6fa8cca3f73754e6d1fb1b2af3e8fd0d9579f4fe86ca3c7a7f43651ebdbfa1328fdedf5099c7b7fbb9f57e8bca3c7abf45651ebddfa2328fde6f519947efb7a8ccd3e4194fc6039effef7e0bbc4f82cf4d6d011f9fffaa745f4616d67325f8f8f327afc38c375bdaf666a883d76c0be1ba22241ec7d916f2da5ae88eb1fa6199e3e1fd1bdb3ce169f28ca7d5339ec99ef1ccf08c679e673ce33de359e2194f8b673c933ce399ee19cf5ccf78063ce369f48c27e7194fb3673cd33ce359ec19cf1ccf781679c6d3e0194fd6339ea99ef1ccf68ca7de339e899ef14cf18c6796673c0b3ce359e819cf38cf782678c6d3e619cf4ccf78e67bc65317c223750f45d475b4b5b87f23ea3ada5ac48eba8eb616b15573d55c353fb035df9e7cec9e6cb0e76fb69947a57373db8147e2fd4e28cfd2fd773becba12bcc7a160b4dae96835e06895833e3b40bf9d02fa65202eaf9b97399e322b7314b3897d55f2b14be30cc6667d0287871f57096b219467693cb83a08d798e3e5a00f6ef3ab05f2cc405c5e372f5f1d12bb3d48568b5d5568b12b8467578db5e078719977a490d9079d4decddc9c72e8d33189bf5091c1e7eec16d64228cfd2be351c846bccf172d007eb745820cf0cc4e575f3f2306c8738cc57a79059751e1db3897d4de2b18b85ac139bf5091c1e7e5c23ac854c9ee5f1e0da205c638e97833e58a7d70ae49981b8bc6e5ebe16b683322bb3322bb3322bb3322bb3322bb3322bb3322bb3322bb3322bb3322bb332fbcd6c625f9778ecf2fc3dc6667d0287871fd7096b21936779fefefa205c638e97833eb8cdaf17c833037179ddbc7c3d6c0765566665566665566665566665566665566665566665566665566665566665f69bd9c4be21f9d8a5fb713036eb13383cfcb841580ba13c4bf3f73706e11a73bc1cf4c16d7ea3409e1988cbebe6e51b613b28b33287319bd837251ebb7c3e0f63b33e81c3c38f9b84b590c9b33c1edc1c846bccf172d007b7f9cd027966202eaf9b976f86ed1087f9ea1432abceaa7314b3eaac3a4731abceaa7314b3eaac3a4731abceaa7314b3eaac3a4731abceaa7314b3eaac3a4731abceaa7314b3ea7ce0e86c62df9278eceed2fc3dc6667d0287871fb7086b21936779fefed6205c638e97833e58a7b70ae49981b8bc6e5ebe15b6c35867be3a85cc5a1bb561d6da50e62866ad0d658e62d6da50e62866ad0d658e62d6da50e62866ad0d658e62d6da50e62866ad0d658e62d6da50e62866ad0d658e62d6da50e62866ad0d658e62d6da50e62866ad0d658e62f6a1364cecdb928f5dba9f1d63b33e81c3c38fdb84b510cab374fdcbed41b8c61c2f077db04e6f17c833037179ddbc7c3b6c87db95599943984dec3b928f5dcc3ab1599fc0e1e1c71dc25a08e5591a0fee0cc235e67839e883dbfc4e813c331097d7cdcb77c276481b336ebf4c72b14bd76d728c3afb6c7c6fb0ed71e07ba36dd783ef4db6dd00be37db7623f8de62dbe3c17717e4c6beb7daf612f0bdcdb607c077b76d6f06dfdb6d7b3bf8eeb1ed9de07b876d5f05be77daf62ef0dd6bdbbbc1f72edb1e06dfbb6dfb1af0bdc7b6af05df7b6dfb3af0bdcfb6af07df7db67d03f8eeb7ed1bc1f77edbbe097c1fb0ed9bc1f741dbbe057c0fd8f6ade07bd0b66f03df876cfb76f03d64db3bc0f761db9e00be876d7b22f83e026d7efea86d3783ef63b69d03dfc76d7b12f83e61db2de0fba46d4f06dfa76cbb157c8fd8761bf83e6ddb53c0f719db9e0abecfdaf634f03d6adbd3c1f7986dcf00dfe76c7b26f83e6fdbb3c0f705db9e0dbe2fdaf61cf07dc9b6e782efcbb63d0f7c8fdbf67cf03d61db0bc0f715dbc6edfb55dbbe037c3c5edc093e1e2fde003e1e2fde083e1e2fde043e1e2fde0c3e1e2fde023e1e2fee021fd7dd5bc1c775f736f071dddd0d3eaebbb7838febee1ef071ddbd037c5c77ef041fd7ddbde0e3ba7b17f8b8eede0d3eaebbf7808febeebde0e3ba7b1ff8b8eeee031fd7ddfde0e3ba7b3ff8b8ee3e003eaebb0f828febee01f071dd3d083eaebb0f818febee21f071dd7d187cedb6fd30f80eb26d1c3f0eb6ed8f82ef10dbfe18f80eb5ed8f83ef30dbfe04f80eb7ed4f826fa16d7f0a7c8b6cfb11f02db6ed4f838fdfc33e03bebc6d7f167c4b6dfb51f01d61db8f81afc3b63f07be4edbfe3cf80ab6fd05f075d9f617c157b4ed2f81afdbb6bf0cbe1edb7e1c7cbdb6fd04f896d9f657c0b7dcb679fc30fba9d9df3867d6a319f23b22849b7d4dc0dd1f247b0cc6b178ddbc5c0446d6bbabf68cc56a190b0ea3e1e911d00c6b881f953ee3f4004fb7008f509ea5cf38bd4e4e4527a71cf4391cf2ec15c833037179ddbcdc0bb125b6396ad168d7bbd0d1a21efa14ec9b9c799faca423afc3d46f57482e523a161c9e4248ec15c23af2ba794c5c5183d8cb9cd89d4e6c1cf7f95169df5e06cccb0598cd7afb925f6f69df3ed2ae8beb99e374424e2b4183a472c2d8196b1c87fdf5d05edb36d297fbb11efcdec9ec663fe26d89eceeeb7a9cd7e5a0cf8a90fcfb8364f3ef7378fa1c66f3396155db0887c0fe50aa81150e072f7782767d11daad00edb84f17f8f818a7177c7cacc0ebc0632a3c8ee84a3edfd0b1a737849b7dcb8031ec58a7903c63c5639d0230b26f39f02c13d26cb9c3b3d0d107df97c73b7df8b5f5d0e722786fcc86f435b53f373392177fb67e3d48765c6d14d00b3ff707a04fe068c80f66680a46e60692e499188c7cf6df3dbc73d7c62d43670f6d1ccc005abd8389cf999034eac087ed7121be20d8738a03a750798a03a750eb1c59706a85fb9b8f52262d9e4618dabe7578dd8ea11d03bb6eb86a7868f0d49d5b90bac1a147d2a80c90147dfc680a462663fa83644f9e343ab12a154f133c8f4f9ea74b28cfd21bcf0427a74627a71cf46980ff4d10c833037179ddbc3c212476820351498b89556831318467628db5c0096df6e19ecaffc7931d754e2eb847634e6e9d279a10073c0cd69fb170e67f66676fb0c98c0f4636368f9ee6a8d26c0433136adeb5cc4ca799d934439099b93433956666d2cc449a994733d3686616cd4ca299393433856666d0cc049a99bff6a03cb36766f2ccccdda1c0f514b09a4fd4e6ddd1ccbc99993633b3668eaacc27267324628e7ecd919a3932304704e653a5996130efb4e628c6bc439b775573a4668ed0cc11b539c25c45b69aec28b2a3add66bc88e213b96ec38b2e3c94e205b4b7622d9496427939d42762ad96964a7939d417626d9596467939d43762ed93ab2f3c8ce27bb80ec42b28bc82e265b4f7609d9a56497916d20db48b629289fed1a241b0aca67b9b6905d4eb695ec0ab22bc9b605e5b35d66a6de9ce13267b4cc5536e62c96396b65ce5299b352e62c9439eb64ce3299b34ae62c92396b64ce1299b342e62c9039eb737b509ee13733fa6606dfccd89b197a33236f66e0ef0aca33ec6646fdeea03c636e66c8cd8cb899013733de6686dbcc689b196c33636d66a8cd8cb499813633ce6686d9cc289b196433636c6688cd8cb099017e3828cff09a195d33836b666ccd0cad99913533b08f04e5195633a36a6650cd8ca999213533a26606d4cc789a194e33a3696630cd8ca599a13433926606f26b645f27fb06d937c9be45f66db2ef907d97ec7b64df27fb01d90fc97e44f624d98fc97e12946bf269b29f92fd8cece764bf20fb25d9afc87e4df61bb2df92fd8eecf7647f20fb23d933647f22fb33d9b3647f21fb2bd9dfc8fe4ef60fb2e7c89e27fb27d9bfc85e207b91ec25b27f93bd4cf61fb257c85e257b2d1839538103c70b768167cd370e0f0f6dbf6ab87d7867fbf66bb60d6fbd6adb0dedd76d1dbebc7de7b543bb366fdb791dbef83e3b54f1298135bb766dbca17deb8ec1a1ebdb775e33dcbe7373fba69dd7ec18dc8d2f7adcbe68eede11370e0e4607fb51dd3e903e35caa0cfd8d7f1c996932ae7f6ec6804796e342faa1f374a15ed29ac5576f99cf2116efbee6d3b87db0bed3be8efc66df49aa1c18e76fcdf6e1279f770fbeee18dbb86db37efdab9bdbdab03d7fbe3dc2892e86c1bc58b2e6cab3ef3e0ff009826ecb5716c0300",
"isInternal": false
}
],
- "packedBytecode": "0x000000028df71de500000036421f8b08000000000000ffed9d079454c799ef6fcf0c493dcd30420284080392c8a1a72730e426470990002123230d0c20860c4316306491a364d972daf57a83d7de607bd76b6ff0aec306e7b45edb72ce3ee7f99df7ce79efed396f7775b6aabb3ecf9f9abaedeee156eb6bf8ee39df74dd6fead6f7fbbe5bb76ef5adea5bbf0a82201664b772258f049d37fa7fda7c26ef6cab8db0aca44fce5889709695086779897056940867b712e1ec5e229c3d4a84b3678970f68a9053b39505b76f51f3dee721ae5133c64b2ca6952510d34489c5b47709c4b42a288d36aa4f8970569708e7fd25c2d9b744381f2811ce074b84b35f8970f62f11ce0125c2f95089700e2c11ce874b84735089700e2e11ce2125c239b444386b4a84735889700e2f11ce474a84f3d112e17c2c42ce51c039c27c8e349ff4bfd1e6738cf91c6b3ec799cff1c6c70ab33f41c944cda6a4d6fa5f4a499d927a250de67f35e67f8d4a262969523259c9142553954c53325dc90c25338defb394cc563247c95c25f394cc57b240c942258b942c56b244c952258f2b7942c93225cb95ac50f2a492a794ac54b2ca6259ade469256b943ca3e42d4ad62a7956c95b95ac53f29c92e795342b59af64839216251b956c52b259c90b4ab6286955b255c93625db95ec50b253c92e25bb95ec51b257499b927d56ccf62b39a0e4a0924316e761254794bca8e4a892634a8e2b69577242c94925a7949c567246c95925e794bca4e4bc920b4a2e2ab9a4e4b2922b4aae2ab9a6e4ba921b4a6e2ab9a5e46525af28799b925795bcddb050657f8792d794bc53c9bb94bc5bc97b94bc57c9ef28f95d25ef53f27b4adeafe4f795fc81923f54f2474a3ea0e48f957c50c98794fc89923f55f2674afe5cc987957c44c94795fc8592bf54f231257fa5e4e34a3ea1e4af95fc8d92bf55f2774a3ea9e4ef95fc83924f29f9b492cf28f9ac927f54f24f4afe59c9bf5831ff9c92cf2bf982922f9affd1b3ae2f29f9b2497fc57c7ed57c7ecd7c7edd3ae61b4afed5d27d53c9bf59ba6f29f9b6497fc77cbe6e3ebf6b3ebf673ebf6f3e7f603e7f683e7f643e7f6c3e7f623e7f6a3e7f663e7f6e3e7f613e7f693e7fa5e4fd03b2e99e41c7960e226a77ea3736eb71120af688e0f64dc7a2dcfc8f3e6b8cbec2ecd327c5ae9bd9ef66e9bb9bfdee56393dcd7e4f4b5f6df6ab2d7d5fb3dfd7d23f68f61fb4f4fdcd7e7fd0c703785e6af45a576e5431d0513d2c035db7e0f698685d772a0e743d82db63a175741ebb83ae97d1f500dd7d46d7137471a3eb453153526974e920aa3a916cd6e526a22ed78c21f58e9e77832eb7ca136f9fe87937ea72ab3df0eafa71bf29ab0fd49bbe46570dba078cee7ed03d68747d41d7cfe81e005d7fa37b1074a6990afa81ee21a3eb0fba81463700740f1bdd43a01b6474034137d8e81e06dd10a31b04baa1463718743546370474c38c6e28e8861b5d0de8687eca30d03d6a74c341f798d13d023a6a531f051df5eb1e333add4efc7b00c7187d19e846523b0cba51d406836e34b5bfa01b436d2fe8c6826dd28d83768574e38d8eda28fdbf46934e07515d13a9cc353129ea7255c9badcc9d1979b19739b1274c4350d762641aca69a7484f37a6ad176cc08d9217d05a417425eca47f1a0fb0cb1ebfb4993494fcd715ca3755c02f23439fc4f07d1fa3fd9e2996c31ebfa3f1d38a2afb37529a9b3796f05d7d9d590d7ae7bd4e7b91bebec12e0f050671bfdd4d95452ea6cf6994310b8eb1ef57befc63afb0c70445f671ba4cee6bf155c67f7405ebbeed1779fbbb1ce6e068ee8ebeca446e91be4bd155c67db21af5df7e8fbefdd5867f70187873adb2ced6cde5bc175f60ae4b5eb1e3d8bb91bebec29e088bece4ef65467eba4ce06d9f1a32070d73d7a2e7837d6d9ebc0117d9dddd02c7d83bcb782ebec0720af5df7e819f5dd5867df031c1eeaacafe7b329a9b3d971f32070d73d1a2fb91bebec874c5a8f337cc58c330c02dd578d6e30e8be66744340f775a31b0a7e457f0db4d4cb3590f756f035f039c86bd7e51a93be1baf814f0287873adb287536efade03afb2dc86bd7bde1267d37d6d92f0287873adb247536efade03afb73c86bd73d9ad37037d6d9d74d5af717be63cd77d3bad78d6e24e8be6b74a340f73da31b0dbaef1bdd18d0fdc0e8c682ee8746370e743f32baf1a0fbb1d14d00dd4f8c6e22e87e6a7449d0fdcce86a41f773a34b81ee17465707ba5f1a5d3de87e65740d46a7c704687eca678cae27d84b07d19ddb38f8465bccda4f43bad62f4f32013c68ab2e7a5b75daf75490bfef75c053efc1f738d8c887a71e781aa2e7a9f572ef4966cf71ca8a691c6ca5c02f0ff7a84c7bd618dc1e53da277b09d0e1b53ac9c1d8143d632a06b6a86cda6f0246d2350023b5a574fde8b6b92ad6c1ebe15acadc9fd15e1a38c85e05e49939a0236f5fc35609ffa736a0128ec7b695535dd18c54471a8bcf98ca97b1c162f4747d6762d660f1b86c4ff164db8e05d5a92945b0dd64d9aeb76c631b425bae361efb761efad5b59efa8c99f6609a298bfaeb6407ef5bd3210651f984b6a9bf4e76485f01e954ac232fe5a37850db43ecfa3aa27389ecf67193ace31290678ac3ff7410adfff6f787a916b36e7747c23dc1c3f570db77362a9bf6eb2176534362370562477946828eee118da0a3b696cac0fe2db6c33efa4bb1e0f6be741af61b1cdc4dc0d8e060f4d0c74ce56a9beb819174938127e5296661df2d5362db473dcd5c976483fa6a740d91bd0ac833b8bc23ef6268337cd44fbc1668cbf73b50f4e72995f94e565f000f9e3b0ffdec5a4ff53189dfe7df08a2ad6b769b586fc52aec3b7fd243fcb0ee53d9b44ff684599885599885599885599885599885599885599885599885599885599885993f338e65e13c1ecad7c084d19e0fe5eb397fe6fd55a62c1c037addebfca054661c80e24ff301465b3e57409e5fc73ad87e00f383e8ff38dfc9752e7dcc95cc752ec99e6bbe92cff1e63a8ba7ce110b0eb66b22b39ddae0e7fca692fafd31fa1d6cf5d639b5c7e633ef76b374ba9ed69477f8ed63ecafd0b1486c4b281de5d85e0278d0968f7383d75e59707bfb81f7195ff358a8ada6f1f22996ed0ac8f31f309f682afc3f1d746e2f300f954dfba3e1d8a956d955fefccd3937017f0761ff1e837c6b04eedfccff29ebf0f17d26ede91e972a741e35b6d3d1f701b273085205f02481c7c77dcc535f2789f531ea3904f61c31571fcafe4d8ecfdfc3d8fd60dacff51b1e6116666116666116666116666116e6e49d6dc22cccc22cccc22cccc22cccc22cccc21c31b3e671bd8382f2d531612cd2bc8bcc7806bd8709c7c5ae9775d8f53d0648634e632c9ff177ab5f2beb607bd9a42b83ce732dc2cea5afb1b5b07349f65cef41f135a61c035b5476ad23161c6cd744663b3b8720faf3db318720659dd33aeb7ce2d8395e43ef837a6acfa788079de72478bac6739e1bb287d712ce7fa134fe36db47acf15e62cf29c2f7f2519e0f99d8d2d879f46d752ae9b3dda0f795d03c8194c357caf36168fb3e6ad2387fa40ecafab4e3ffb4e51aa7a6f8699f6744ef73e6fcce3465d1f99de1b09d06d6886cd7a2ed985536e92b20fda9b2ce0c140f8a35b1eb6b04dffb12765cbd755c02f24c77f89f8ed8ff19160f9e63bde9baf331a8679f86fbbfaf36697a488c46438c288fe7f7073ae703da731ab11ded61e5a163f1bd689f87362a6ceeaaeb1e30cd937f61f700b297083adf17f299637aafcf117b1dda8ba9f0ff7470e773c45e873a84f3b402abfc31503e71f508c2ef2d94e707d67dd4c31caebcde8fe3fa3e40bc38078df2fc14daaa7e66ae6621df07deacef76aeef03785c98efd82e447d6fc4fa882cf87e35caf33fadfa383584bbc971ecff0e39966265bf2bac32e81c3f3fef59cbb637332c5fe89a9a0ebe509e7fb7ae9be8fb4cd9fea7af77ca515f87daa05a87af94e73fe15a7b03fa97749ef0be9028effc7fda72f53f297edae759d1fb9c39bfb34d59747e67396ccf01d6886cd7a26dea7f921dd25740bab2bc232fe5a37850ac89bd123891dd3e6e8a755c02f2a41dfea723f67f96c533cb62ceb43df06eb204ccbff7d556a703778cc6408c7e630f74741fc1f780ba9e75f8ba9786f5e3f0775ba4c3b6bd1fc4b458bfa9b19fe9b9fa27632c7eec9f3c6c98ab4c9cedbcf6b34a3a2eca39cbf81b11ec17e26f447c7d474a04b7c7336171f8b45d65d9ae2aa2ed6acb7675116d4bcc25e69c62ce69ad081c9b2a03461ff73abcafe6c3e8baff9503a3afdff1a50a60ac0346bcdf11a387f7b0d676752d0aeceb7403461fbf412df47935be83197f8b4c8c3ede855fe8fb6cf15dd6745c0f60f4b1c601ae55900fa36b8d8c9ef0e9613d8cdaaebe371fd7c8e8058c3edea51f0f6e7fffff6f63c4b59ce8b8fb80d1c738523cb8fdb9da6f63c477b0d37171cf8cb9eeed9ee783a40a7d0681f32ff01914c5c9358fc5d7b37afbd918ede3b37ae40dfcc531671f69aadf58a430eeb4e53a87aef8f4369f71f8bfdfe789b78ff152d9b43f1d184957e5378e39fb9ad3fcc6228571a72dd73974c5a78ff98c079dd7fff03466ed1ca3a47d1cc7255db5679eb8c5f3dbe2e862bc1f18677a629c5100e34c60a4e3fa0263da13e3cc0218d3c048c73d008c1e9e576718d30530e2735dd23f088cb33d31ce2a80713630d271fd80d1c7b3e738d8cd87710e30d271fd8171ae27c6390530ce05463a6e0030cef3c438b700c679c048c73d048cf33d31ce2b80713e30d2710381718127c6f905302e00463aee61605ce8897141018c0b81918e1b048c8b3c312e2c80711130d271838171b127c64505302e06463a6e08302ef1c4b8b800c625c048c70d05c6a59e189714c0b81418e9b89a12601c56028cc34b80f19112607cb404181f2b01c69e25c03816181f8f9e31f3fd7a69018c8f03cfb2e879eae360231f9e65c0f344f43cb59efccccccf5a6eca8afa1d692bac583d6ec52a01799643fc5678885f0cec52d9b44ff6845998c398350fb54fc41a877c4b993092ee09cf3c718b476fb9dac7157e799209473cb4ad27a3b7952ad4f727816765f43c997bd59305f0ac049ea7a2e7a9f5e467e69eb2caf2e949cba704e4c1766395073f636097caa6fd550edb3541b4b1589d472c563b785617391664af50e6e525c8cc21ced816126b1cf22d63c248baa73cf3c42d1ebde56a1f5d8c2bfc32a6bacaa879d644ce937d47f4ea0278d600cfd391f364ef29d1fb996d479fb17c5a6df994803cd8263de3c1cf18d8a5b269ff19380fc22cccc22cccc22cccc22cccc22cccc22cccc22cccc22cccc22cccc22cccc2cc9b19c71888350ef956326124ddd39e79e2168fde728d3bb81857f8654c759551f3ac8d9c273b56f34c013c6b81e72d91f364c76aa2f7333b56f3ace5d333964f09c883d7f7b31efc8c815d2a9bf69f85f320ccc22cccc22cccc22cccc22cccc22cccc22cccc22cccc22cccc22cccc22cccbc99718c8158e3906f0d1346d2bdc5334fdce2d15bae710717e30abf8ca9ae326a9e75d1f3647eabf96c013ceb80e7add1f3d47af2333356f39ce5d3b3964f09c883d7f7731efc8c815d2a9bf69f83f320ccc2ec62c6368b58e3906f2d1346d2bdd5334fdce2d15bae76ccc5b8c22f63aaab8c9aa739729eec38fd7305f03403cff391f364dbfee8fdccb6fdeb2d9f9eb37c4a401ebcbed77bf0330676a96cda5f0fe7a110e65525c82c71963887314b9c25ce61cc1267897318b3c459e21cc62c71963887314b9c25ce61cc1267897318b3c459e21cc62c71963887314b9cef9d38e31803b1c621df3a268ca47bde334fdce2d15bccda4f43dac5b8c22f63aaab8c9aa725729ebacc58cdfa02785a806743e43cd9b19ae8fdcc8ed56cb47c5a6ff994803cd8266df4e0670cec52d9b4bf11cec3ddcebcaa0499a56e148759ea863087314bdd10e63066a91bc21cc62c754398c398a56e087318b3d40d610e6396ba21cc61cc523784398c59ea863087314bdd10e63066a91bc21cc62c754398c398a56e087318b3d40d610e63e65037708e0eb1c6215f331346d26df0cc13b778f4966bde8e8b71855fc654571935cfe6e87932ef24d95800cf66e0d9143d4fad273f33739d5eb07cda68f994803cd826bde0c1cf18d8a5b269ff05380fc22ccc2e666cb388350ef95a9830926e93679eb8c5a3b75ced988b71855fc6545719354fab279e170ae069059e2dd1f3d47af233d3f66fb57c7ac1f2290179f0fadeeac1cf18d8a5b2697f2b9c875263c66b8958e3906f331346d26d011e0ff5aee036a9d511c71525c0b8bc04187b9500e37d25c01807460ffdf4cc359cb078687fb3dff8a4a2884f25c4896bbb97001e5fe7b0cae2a972c482eb39ec6d3e399fc32ae0f1750eab2d9e6a472cb89ec33ee693f339acf61bc748dafbfb4b80b16f09303e50028c0f960063bf1260ec5f028c034a80f1a112601c58028c0f9700e3a012601c5c028c434a8071680930fa7ea691eb7b6e6b116c877d3f2b86edb0ef15c5b02d3197984bcc25e6127389f99dda96984bcc25e66f7ecc7d3ccbc5e7c6b4e5fa0ee17abeeb7b4c5e18f930ae62ca883cb1e87892e83bdadac6c077622835c65525c0b8bc0418258ed931d4ae306a9e1d9e78b615c0b30378b647cf53ebc9cfcc5cc39d964fdb2c9f129007ebc14e0f7ec6c02e954dfb3be13c941a335e4bc41a877cad4c1849b71d787c5d5f855cef787dedf2c4b3a3009e5dc0e3e37c79f23373bdefb67cda61f994803c78edecf6e0670cec52d9b4bf1bce43a931e3f54eac71c8d7ca8491743b81c7d7f555c8f58ed7d71e4f3cbb0ae0d9033c3ece97273f33d7fb5ecba75d964f09c883d7ce5e0f7ec6c02e954dfb7be13c941a335eefc41a877cad4c1849b71b783cd4bb82bf73ec71c471790930ae2a014689a3c49113a3c4f1de89a3300aa3300ae39bc1580a6db8dc670a7f3680dfc5dba2e7a9c7ef6df9f0b4018f8fef769efccc3c1bd867f9b4c7f2290179b01eecf3e0670cec52d9b4bf0fce83300bb38b19db2c628d43be56268ca4db0b3c1eaeef82dbfe36471c57f8654c759551f3ec8f9ca73e89f5251f9efdc0e3a34ef9f133dbf61fb07c6ab37c4a401ebcbe0f78f0330676a96cda3f00e7a110e65525c82c71963887314b9c25ce61cc1267897318b3c459e21cc62c71963887314b9c25ce61cc1267897318b3c459e21cc62c71963887314b9c25ce61cc1267897318b3c459e21cc62c71963887314b9c25ce61cc1267897318b3c459e21cc62c71963887314b9c25ce61cc1267897318b3c459e21cc62c71ee1a33ce7127d638e46b65c248ba7dc0b3df034fa1f3f0f73be2b8c22f63aaab8c9ae760f43cf5585ff2e139083c3eea94273f33bf153864f9b4dff2290179b04d3ae4c1cf18d8a5b269ff109c874298579520b3c4b96bccd866116b1cf2b5326124dd01e0f1707d17dcf61f74c471855fc654571935cfe1e879eab1bee4c37318787cd4294f7e66dafe23964f072d9f129007dba4231efc8c815d2a9bf68fc0791066617631639b45ac71c8d7ca8491748780c7c3f55d70db7fd811c7157e19535d65d43c2f46ce934a627dc987e745e0f151a7fcf8996dfb8f5a3e1db67c4a401ebcbe8f7af0330676a96cda3f0ae7a110e65525c82c71963887314b9c25ce61cc1267897318b3c459e21cc62c71963887314b9c25ce61cc1267897318b3c459e21cc62c71963887314b9cef9d38e31803b1c6215f2b1346d21d011e0fcfe30b1eab79d111c7157e19535d65d43cc722e7a94b627dc987e718f0f8a8537efccc8ed51cb77c7ad1f2290179b04d3aeec1cf18d8a5b269ff389c87bb9d795509324bdd280eb3d40d610e6396ba21cc61cc523784398c59ea863087314bdd10e63066a91bc21cc62c754398c398a56e087318b3d40d610e6396ba21cc61cc523784398c59ea863087314bdd10e63066a91bc21cc6cca16ee01c1d628d43be56268ca43b0a3cc73cf0143a8fe898238e2bfc32a6bacaa879daa3e7a9c7fa920f4f3bf0f8a8539efccccc753a61f974ccf2290179b04d3ae1c1cf18d8a5b2699fec09b3308731639b45ac71c8d7ca849174c781a7dd034fa1ed6a3b30bada7e0f8ca9ae326a9e93d1f3d4637dc987e724f0f8a8539efcccb4fda72c9fda2d9f129007afef531efc8c815d2a9bf64fc1791066617631639bd56e3ee390af950923e94e008f87ebbbe0b6ffa4238e2bfc32a6bacaa8794e47cf538ff5251f9ed3c0e3a34e79f233d3f69fb17c3a69f994803c787d9ff1e0670cec52d9b47f06ce83300bb38b19db2c628d43be56268ca43b053c1eaeef82dbfed38e38ae2801c6e525c0b8aa04183dc731d55546cd73d613cfe90278ce028f8ff6c3939f99fbfc39cba7d3964f09c883f5e09c073f636097caa6fd73701e4a8d19af25628d43be56268ca43b033cbeaeaf42ae77bcbe5ef2c473b6009e9780c7c7f9f2e467e67a3f6ff974d6f2290179f0da39efc1cf18d8a5b269ff3c9c875263c6eb9d58e390af950923e9ce018fafebab90eb1dafaf0b9e785e2a80e702f0f8385f9efccc5cef172d9f5eb27c4a401ebc762e7af0330676a96cdabf08e7a1d498f17a27d638e46b65c248baf3c0e3a1de15fc9de382238ecb4b80715509304a1c258e9c18258ef74e1c85511885b130c6ad25c028e75a18b932b67a608c197bc843fbad45b05d65d9ae2aa2ed6acb7675116d4bcc25e6127389b9c45c627ea7b625e6127389b9c45c622e31bf53db127389b9c45c622e319798dfa96d89b9c45c622e3197984bccefd4b6c45c626edbf630dfb4e0f9dc1781e782875878f233a9cbbd64ca7a23c2f8e9585db662d56ac52a01792e41fc2e7b889f6bce35ed93bd4299473060f6643bd55b95d10bfc271babac7868fb573cf91ed6e65d2982edb036af18b6c3dabc62d896984bcc25e6f76ecc315d1174fe0d922ee3aa497733fb947f2b1c47793e5199fdac0ae47cfab02dd790c45c622e317f33628e71995f049ec0e20972f0a499f14c61c653cf8c6702339e31cc789a99f1ac61c6b394194f39339e05cc786631e329c6f3ac4278a632e36960c6b39019cf6c663cd398f13432e319c58c27c98c6704339e16663c6b99f12c63c6b38819cf1c663cd399f16c66c63389194f2d339e91cc782a99f12498f12c66c6339719cf0c663c1399f13431e34931e359c78c6725339edecc78aa98f1cc63c6338e19cf4c663c9399f1d431e3e9c38ca79a19cf68663c4b98f18c67c61363c0130f3acf018fc3ff2f82aecc3ab6879293033afe7fcde8cbe098eb265dee28fb1ae8686ed575c7b118a76be04bdaa49377b665e284b6d2b04ff62a81e33a139ef1cc789630e319cd8ca79a194f1f663c75cc782633e399c98c671c339e79cc78aa98f1f466c6b39219cf3a663c29663c4dcc782632e399c18c672e339ec5cc7812cc782a99f18c64c653cb8c6712339ecdcc78a633e399c38c6711339e65cc78d632e36961c63382194f9219cf28663c8dcc78a631e399cd8c6721339e06663c5399f15c64c6338b19cf02663ce5cc789632e359c38ca79919cf18663c1398f1d433e399c28c27cd8c67be83c7d7bac334de4e65d3fe4526b63d9c87cc7be16e78f2e9a629abbb2997f8c95e05e499600662f4f8101e4b5cf6fc08ecdbdc84185df5e44bd8bb21ae16c176d8bb218a61bbdab25d5d44db12f3f098df8cde760ae71cd116b3f6d390c6ebcec75c2d4f7eded6e645fd2ecc5b56acae5ab14a409e1b10bf5b1ee2e76a47699fec15ca3c820133d68b9a20da7af172f43efde61d9b14d797adf8a25faf788a69585bfa4a116c87b5a5c5b01dd69616c3b6c45c622e31bfb763fe36938eb09f91441bfa3b14dd3fde0676df6ed2b108edeab25e3565d13b4189e3edc04379b6c2773aa97f72cddf0b31b7d3f48c027fc7efeb1952d83929c6f3abb073520cdb61e7a418b625e6e1317f8707dbf1e0f6b512f496eb19c53b80e7550f3c9efcccdc6b5fb37cba68f994803cd8f6bde6c1cf18d8a5b269ff35e0a10ddf13eba31ee473ce91e716339e34339e29cc78ea99f14c60c63386194f33339e35cc789632e32967c6b38019cf2c663c5399f13430e359c88c6736339e69cc781a99f18c62c69364c63382194f0b339eb5cc789631e3798519cf22663c7398f14c67c6b39919cf24663cb5cc782a99f12498f12c66c6339719cf0c663c1399f13431e34931e359c78c6725339edecc78aa98f1cc63c6338e19cf4c663c9399f1d431e3e9c38ca79a19cf68663c4b98f18c67c61363c013f69e58faff2dd0d11839be3bf69d26fd2ae8ca1c3668ace635d055983495a1df3b7b7940e7b2314ebee605a0ad34ec933d7c4fec3b99f08c67c6b38419cf68663cd5cc78fa30e3a963c6339919cf4c663ce398f1cc63c653c58ca737339e95cc78d631e34931e36962c6339119cf0c663c7399f12c66c69360c653c98ca79619cf24663c9b99f14c67c6338719cf22663caf30e359c68c672d339e16663c2398f12499f18c62c6d3c88c671a339ed9cc781632e36960c6339519cf2c663c0b98f19433e359ca8c670d339e66663c6398f14c60c653cf8c670a339e34339e5bcc78e63b787cbd9f2eec9d0fb78a603bec9d0fc5b01df6ce8762d8969887c7dcc3bb6e32ef02c5f742eacd9eeb9286b4e7dff7d4c783dbdfabf3db78f0bd1f3eda254f7e66de41f92e53964ea7232a57c7eadd41c7a6cba578e2fc2aca830cef866322e2c9f93e11b227ccc21cc68cefa42556d7dcbb379b9174af018f8f7643fb3ece9445e5eb77f425fa74d8f5f09ef77a1d07fb3defc4e17acffbe6fe1d6cd586ad32e8fc2e5c5ceb1ecfa5efb508a86cd75a04e40fae45e0ebbe7fc3e2b9e188c59b69fbba07db85f639ae3b78a27ccf26b63168cbc77bdfb5efd782fc7d77bd873e42df6b13c083b63cbce73dd376d17c5c2a5fb70f23fb788d793d5ef7d4768db77cae803c73a0ed1a9ba3ed2a0b3aae197c4f7959d0f9ddef6f18bd5d464d20dfa77cd896efb03c63eea16f52f077585c8f21fa7e462ad3cedd2880e71af0f8b8df7aea4f253ddd9332df61af58b1ba61c52a01793cdfb73275fb9ac543fb644f98855998855998855998855998855998855998855998855998855998855998f933e37830b1e2b8ed2d268ca4c3316b1fcff9b5ef134c5954be1e1bfd429f0ebbd18f5ba492384781c66d27583e57409e3efd3ad8be02e3b6f6bc86b073e96b9df7b07349f62a83cef3507c8ed385cd8129c6186121b66b22b39ddae0e7fca692bd83ec9adf37ac737acb713e7db415387f8ab65c638d5780c7c3586cadafb923daa7cb964fd72d9f129007d7b1b8ecc14fd77d8df62f030f6db84e9eaf7b4660f1048ef8d056c68c27cd8ca727339e29cc78ea99f14c60c6f328339e31cc789a99f10c61c6b38619cf52663c0398f1dccf8ca717339e72663c0b98f1cc62c63395194f03339ec798f10c65c6f310339eb1cc78fa32e3b98f19cf42663c15cc786633e399c68ca79119cf28663c49663c2398f1b430e3a961c6b39619cf40663ccb98f13cc08c27ce8c6711339e6ecc78e630e399ce8c6733339e49cc786a99f18c64c6338c19cfc3cc787c8fa317caf320339e4a663c09663c8b99f17467c6339719cf0c663c4dcc782632e34931e319ce8c671d339e41cc785632e3e9c78ca737339e2a663c3d98f1cc63c6338e19cf4c663c9399f1d431e3798419cf60663c3799f1f467c6b38419cf68663c7d98f15433e319cf8c27c680271e74fead471cfe7f1d74974d1a7fcf56e6288fe6e9517eddaf7a7940e7b2cb1c655f7130609c2e812f69934eded976dbef3062a65cda277b95c0718509cf78663cd5cc78fa30e319cd8c6709339efecc786e32e319cc8ce711663c75cc782633e399c98c671c339e79cc787a30e3a962c6d39b194f3f663c2b99f10c62c6b38e19cf70663c29663c1399f13431e399c18c672e339eeecc781633e34930e3a964c6f320339e6bcc781e66c6338c19cf48663cb5cc782631e3d9cc8c673a339e39cc78ba31e359c48c27ce8ce701663ccb98f10c64c6b396194f0d339e16663c2398f12499f18c62c6d3c88c671a339ed9cc782a98f12c64c6731f339ebecc78c632e3798819cf50663c8f31e36960c6339519cf2c663c0b98f19433e3e9c58ce77e663c0398f12c65c6b38619cf10663ccdcc78c630e3799419cf04663cf5cc78a630e3e9c98c27cd8ca78c19cf7c8b07ffafbfdbd3f8ea65d0d1ffffafe91c54193f2e5bf622f02319b6f683af98e19676c4246ced070e3c69663c3d99f14c61c653cf8c6702339e4799f18c61c6d3cc8c6708339e35cc789632e319c08ce77e663cbd98f19433e359c08c6716339ea9cc781a98f13cc68c6728339e8798f18c65c6d39719cf7dcc781632e3a960c6339b19cf34663c8dcc784631e34932e36961c653c38c672d339e81cc789631e37980194f9c19cf22663cdd98f1cc61c6339d19cf66663c9398f1d432e319c98c6718339e8799f15c63c6f320339e4a663c09663c8b99f17467c6339719cf0c663c4dcc782632e34931e319ce8c671d339e41cc785632e3e9c78ca737339e2a663c3d98f1cc63c6338e19cf4c663c9399f1d431e3798419cf60663c3799f1f467c6b38419cf68663c7d98f15433e319cf8c27c680276cad05fa7f39e82e9af44dd05d30e96ba03b6fd29741f7924357e660217b174147f3142e808ec636ce838e9e87902ddd9f7b6d4067d632874fe50ed60b0e9f2e3a8ec5f348c7a48368cf23da4ac33ed9ab048e8b4c78c633e3a966c6d38719cf68663c4b98f1f467c6739319cf60663c8f30e3a963c6339919cf4c663ce398f1cc63c6d383194f15339edecc78fa31e359c98c6710339e75cc788633e34931e399c88ca78919cf0c663c7399f17467c6b398194f82194f25339e0799f15c63c6f330339e61cc784632e3a965c6338919cf66663cd399f1cc61c6d38d19cf22663c71663c0f30e359c68c6720339eb5cc786a98f1b430e34932e319c58ca79119cf34663cb399f15430e359c88ce73e663c7d99f18c65c6f310339ea1cc781e63c6d3c08c672a339e59cc781630e32967c6d38b19cffdcc780630e359ca8c670d339e21cc789a99f18c61c6f328339e09cc78ea99f14c61c6d393194f9a194f19339ef9160f8e61264147e95ad0513a053a4ad7818ed2f5a0a37403e828dd083a4a4f021da59b4047e9c9a0a3345d23f1a0c3f762bc879f6c51d9b47f0918693eb3eb9a9e0adc972c9de63ee789fb92c54dfbe780d1e50bb14d03ee73964e739ff5c47dcee2a6fdb3c0e8f285d8a6838ed2334047e999a09b09f64847e959a0a3f46cd0517a0ee8283d1774949e073a4acf071da517808ed20b4147e945a0a3f462d0517a09e828bdd47cea737cd6d2e9737cc6a4d341b4e7986c51d9b47f06185de79dd89601f7194ba7b94f7be23e6371d3fe696074f9426c2b81fbb4a5d3dca73c719fb6b869ff1430ba7c21b635c07dcad269ee939eb84f59dcb47f12185dbe10db5ae03e69e934f7094fdc272d6eda3f018c2e5f886d1d709fb0749abbdd13f7098b9bf6db81d1e50bb1350377bba5d3dcc73d71b75bdcb47f1c185dbe105b0b701fb7749afb9827eee31637ed1f03c676872fc486ef5ddcec89f1a6c578b388b6c3fa5ec5b01dd67f2a86edb03e50316c87dd9b8b613becfe5a0cdb61f7c862d80ebbcf15c376d8bdaa18b6c3ee37c5b0dd6ed96e2fa26db9c68a7f8dbd996deabd7a8dbd99ed5abb65bbbd88b6a5cf247da662d9963e93f4998a65bbddb2dd5e44dbd2a686b7a91e9e4fa4e26083b698b59f86f431e0f1f19cc7939f495dee5153d61b1196ab63f5a215abcd56ac1290e728c4ef450ff18b815d2a9bf6c95e293263bd8845673b19071bf88eb823543ee80e9b742de80e99740a74074dba0e74074cba1e74fb4dba0174fb4c7a31e8da4c7a09e8f69af439d0ed31691cefd96dd26741b7cba4717c65a7499f01dd0e93c6f18ced267d1a74db4c1ac70fb69af429d0b59a343eafdf62d22741f78249e3f3f14d267d02741b4dba19741b4cba1d74eb4d7a33e89e37e9e3a07bcea48f82eead26dd08ba674d7a12e8de62d24da07bc6a42783ee6993c67714ae36691cfb5e65d21740f79449e358f393267d09742b4c1ac776979bf474d03d61d23340f7b849cf04ddcb269d06dd2b263d0b746f33e9d9a07bd5a4e780eeed263d1774ef30e979a07bcda4e783ee9d26bd0074ef32e985a07bb7492f02dd7b4c1ac701de6bd2c7405766d22f828ee69e1e011dfd9ee230e8e83794874047ef4d3808ba1e267d0074341f673fe8688ee93ed0dd67d26da08b9bf45ed0559af41ed0d13b0a76838ede03b40b74f4aea29da0a3f701ee001dbdb3703be8681eea36d0d16f19b6828e7ebfd80a3a7a47c016d0d17b815e001dbdeb6e13e868bee946d0d16f0436808e7e17b81e74f4dbfbe74147efdb790e74f40eb9b7828ee64d3e0b3afa2dc05b405763d2cf806e98493f0dbae126bd1a74f4aeb855a0a3f97f4f818ee6fc3f09ba1126bd0274234d7a39e84699f413a0a3772e3e0e3a9a07f932e8c69af42ba01b67d26f031dbd73f355d0d1bcc1b7838ede55f30ed0d1bdf835d0d1bdf89da0a37bf1bb4047f7e277838eeec5ef011ddd8bdf6b3ef5f5a7afcbeb663f1d44d7efd1f66e04b76fb9fadec4803c51f66513c083b6ae46ee7b2ad36fa6fe56992997eac155b07d3972dbd93efb1553563753ee65cb7605e419d2afe3dc5c82ffa7c1073a0ef350d9b43f018ebd64955d65fcbde2c9dfcb1613715f0126ca33bc5f47decf9a744f382642b6ccf74faa6b01c410b734a4718db4e86395ca7c1fb85600cf15e089fe3ac97e1ff65127f0da8afafbb0fd2cc3ae6b09c87319e2e76b6ee7158b87f6c99e300bb3300bb3300bb3300bb3300bb3300bb3300bb3300bb3300bb3300bb330f367d63c349e80f37928df75268ca4c3b12e1fcff9712c15c7bc3e02e33abec7f7ba9b72275a3e57409eff03634e1f33e94af83f9db7b073e9619c30e7b9247b95e00f8e05f9182f8e812d2afbaa23161c6cd744663bb5c1cff94d257b07d9f1fd6bd639bd6e9d4f1c17c76be8b3504fe9985be6330ef1b905f1f1708de73c37640fafa5aba0a3f40d60f4116bbc97507b30c1ec933d1c17ffb2352e1e7d5b9d4afa6c372e9ab2ecf176f495f27c03dabe6f9a34ce0db90e65fdccf17fda728d5353fcb4cfe7a3f739737e5f3265d1f93defb07d0e5823b25d8bb66346c80ee92b20fd5398cf61ff9e88624decfa1aa13998c86e1f77c33a2e01792e38fc4f07d1fa7fdee2396f31ebbaf36da8673f83fbbfaf36e942488c26408c280ff6833ccc3371b691c441f6741e3aff3dac3cd867a13cbf86364afb42ed3cf989735bf01e70d1937fd72dff689fec61dff80a30da3eeafaf1853e1dbcf7fafcaffff238ffebbfa00ee13cadc02a7f22944f5c3d82f07b0be589f5ef28dfe7fcb26b96cfc47205987e93a77f47de69265d485fffcdfade16d6d7f7f14ec07870fb776fbde5babfe33dc647fbe2c9cfa4ebde75c9f229017946809f1efa31397f07781e6cfb7a0f24d9a03ed4652b161590a73f5cdbb9e288df55af14c59794b33f38d1e10be5196cb5532f7960f279deb09fa5cbbdeaf095f20c87f6ef51938ec379c2b6b4cef17fda72b507143fedb387f7fb65ce2ffd068cceef1987ed53c01a91eddb7e934efd7db243fa0a48a7fa77e4b57f4f4eb1c6f7f8d16fe590dd3eeeb2755c02f29c75f89f0ea2f53fec7d00644fd79d9150cfa81ef96c37cf86c46822c488f2e0b315bab753feb0fbbe8ff9d5b9eefb5781d16e37b1efe293ed9ac5663f4375f507290f1d8bfdc159d0cec61d79fd3f1bcef66d0a99f3eff919797dcc8a671aec8c073dd54f8a33fd0ffbb8946789753ff3d567bd6c71931f388673d1e2be64c5159f452d076ed7b5e97ad6e9a9cf598f7d31bb3f7411d829cf6aab3f64f7e36e3a7cb1efd36541e7ef826f5879f19acc759c9dbe6e1d83cf0c5ccf64f1b72e1780256dd2c93bd96a9349642a73c4221174fe4ee67ace40d701d6392acbbe56e8d80ac8b3d16a93ecbc997b599f8ef8d079c467edae3ae9eb3952d87316b2a7195dbfa38bbeedba377fafd433b27227af2fc531ee9e563a1adba916bc57e48ac545078faf679a61b1b8e8b01d5d2c1a37b8ee1fae585c70f0f8fa8e19168b0b0edb11c66293ebd9822b16e71d3cbebe6b84c5e2bcc37674b19874db738d5cb178c9c113fd338ddcb1c031b142982f3060ee69a5a3b15dbfde3576e78ac539078fafb1bbb0589c73d88e2e16b58daeefecae589c75f0f85a17242c16671db6a38b45d364d73315572cce38787cad9f11168b330edb11d68b8df87c2d572c4e3b787cadc911168bd30edb11f60f1b733d33c4589c72f0f87af617168b530edb11c6a259db3e99472c4e3a784e163916271db6a38bc5fa066dfb441eb138e1e03951e4589c70d88e2e16cd4dda767b1eb16877f0b4173916ed0edb117e87cad48be379c4e2b883c7d7ba2961b138eeb01d5d2c5a327dad6379c4e29883c7d75a2c61b138e6b01d5d2c92997bead13c6271d4c173b4c8b138eab01d61bdc87c9f7c318f5814f3fda361b178d1613bc2fb48a65e1cc92316471c3c478a1c8b230edbd1c56253e6f9d3e13c6271d8c173b8c8b138ecb01de133974cbd3894472c0e39780e153916871cb6a38b455de69e7a308f581c74f01c2c722c0e3a6c47178b8d9931b10379c4e28083e740916371c0613bc27e67a6bdd89f472cf63b78f6173916fb1db623ec77669e5feccb2316fb1c3cfb8a1c8b7d0edb11b69d997e675b1eb16873f0b41539166d0edb11f63b33b1d89b472cf63a78f61639167b1db623ec7766ee237bf288c51e07cf9e22c7628fc37684f522d376eece2316bb1d3cbb8b1c8bdd0edb113ed7cab49dbbf288c52e07cfae22c76297c37684df4732cff876e6118b9d0e9e9d458ec54e87ed08c78a327df01d79c462878367479163b1036cfb986782b1a0b958e3ac5854409e07cccbd8692e56581ca90c9c5786be6c8fdc97ecbcb26d21be6c075f28cf40f0a527e8a364f2e46ba6ce6c3565d1dcf41b0e5f29cfd0011d798799741ccec94d282be9f83f6db9e62051fcb4cf5ba2f73953575f3065d1f9dde2b0bd095823b25d8bb6696e3ad9217d05a4270ee8c84bf9281e146b62d7d708ad9580ecf6713bace31290a7d5e17f3a88d6ff2d16cf168b39f3bb07a867548ffcb45d59a6d690188d8318511e9cb377c3138f3d879038c89ece43e7bf879507e750529e2668a3705e29f9190f3acf9bd4fe6df5e45fd83a6e642f01ba2bc068fba8eb4702e67ed61263d0a1a3b51d700da6464ba77d9de4c957b24565d3fe2460a4b5261a8bcf98ca97b1c162d43c933dc42c0e7669cb75bf980c3c4d1e783cf999b90f4db17c9a64f994803cf8dbc6291efc8c815d2a9bf6a7806d1fe71c6341f7e451562c2a20cf6aabff1816472a43d7df46872fbee2d860f134386c4ff71c472a9bdac4e945b03dd5b25d6fd9d6d736d631bde5bab6a702f3340fccbadc19d1979bb9b6698d34aacf64a71e7c4a430ca2f2096dc7acb2495f01e9ed033a33503ce8de49ecfa3ac2ba1376dc64ebb804e499eef03f1db1ff332c1e3cc77ad3fd86f5d0aff4703d64eac0748b83f6eb2176334262371d6247791a41477d9c29a0a3be02ae2948ffc77e4443f4fe3adb1eda6f0046d2e13a850d0ec6fae81953b9dae67a6024dd34e099ea2966d32c9e51567cf0bedcc3ca43c756409e76b837c61d7975ddaf8a75f8456b1746b8ce4fa65dedee215eb8ae6200f109ac18d2460c3d838eb517a3e4b92f80b515db76ee69debcf1c98dd9a14742abb030f133e670a30c74982e77e882e0f625242b40474b4876035d9915165cba92f2d392763ec285f1a0b22b2cce9ec012a56d5c7e93b65c55a707f0f8a8cabaea549ab24cd5797acf96b68d583fba599c5da93bfa7fe539f2859545f5a0c283efc84465d33ed9d3f14998f4aee60d5b67edd9bc6ffbc61d6d7b11d6beb8301db382607fba8ec14a82171395d3cd0a4eafe883538f17afcd1780bd005802c3d3337a9e4c45a5355b37346fdbb67cdffa6d5b36ccdfb76343db969d3b30a23dacc88545dbbee4f5e66aea30af9dbfbb43676fb8426e0fd0d10ab93d4147f67b818e38ee83ffd967c2cb353102caa76aacff57611cee6e1ca22a48b721dd9ee86ba67790bdfdeb2763d541f6f4e92576f592ba7a095dfd0607fdab6add53d04be0ea256ff512b77a495bbd84ad5eb2562f515b136497a01d1e6497987d34e85842566f9f015ebd64acee6ae82561f512b06383ece3baf141f6d566fa5512bacbaabf8eea6e9efe6aa1bb5cbaaba5bbb5fa2bbb7e7ca3bb31ba8ba8bb3fbacba2bbc1bafb3bd3c47a56905d425a2f193d37c82e093d3fc82ef9bc30c82ee9bc38c82e87be34c82e4dfb44905dce7c79905de6562f7fab97c5d54b97eb2573f552ba7a895dbd44b95e7e572fcbab9721d74bf6eaa57cf552e37a99dfe783ec72e2eb83ec92c12d41763961fde85a2f1fae1fdbebc7d9fa51b17e94a98713f4308a7eccaa87fff470a81e1ed6c3e57afa809e4ea1a797e8e9367afa919e8ea5a7a7e9e97a7afaa29ecea9a7b7eae9be7afa737b909d1e7f32c8fe7c42ff9c44ffbc46ffdc48fffceaa520fb9342fd133dfd9353fd13dccb41f611aafe39b71e56d38f4ef56364fd78553fead5cbf4ea6579f532bc7ad95dbdccae5e56572fa3ab97cdd5cbe4ea6571f532b8ef55f23b4a7e57c9fb94fc9e92f72bf97d257fa0e40f95fc91920f28f963251f54f221257fa2e44f95fc99923f57f261251f51f251257fa1e42f957c4cc95f29f9b8924f28f96b257fa3e46f95fc9d924f2af97b25ffa0e4534a3e1d64ebdf6795fca3927f52f2cf4afe45c9e7947c5ec917947c51c997947c59c957947c55c9d7947c5dc93794fcab926f2af93725df52f26d25df51f2ba92ef2af99e92ef2bf981921f2af991921f2bf989929f2af999929f2bf985925f2af955d0b10c343616ffdfec0c33fbcd6d6d1bb7ef6aab69db59b37ddfb6b62dbbb61daa39b0a5ed859a9dfb37eed9b46de7013cf883a679a2f5aa67edd9d37ca866cb8e968d076b76ee6babd9b9a966fdce7d3b5a6ebb397ece1c34a8b3c5e696967063df29bb03d2ef77d1e8afcd71b412f8a2dcbefdafae04e4ff75e5a081e55d73e8e3a673475fbb9ecaf6f16af66edbd95693acd9a1feaa9be9ce031b5b26d4e0fff6aa20ef6dabd9dbd6bca7ad66d39e9ddb6b6a2760b9e37b77c189d6de7e60069b770175a9aa7ca95f173cf91ffdba763afef34e48693df9428df6ebdf050f0775e5a074170917f70f0dcbde7debdbf6346f680b3f78d99d1cbcaa2b6eb674d1cdbe03ba60eca1ae1c346940d7085775c5d8f1028c05ff0d3975c984249304009b2d6c6f00000023561f8b08000000000000ffed9d79741cc5b5c67b6459963d1acbf2be5bac5e4648a319c9b6b08d65b3181bb3790163360b5b3606db32b60c983d648310080961c90664634908d94312b2ef2464812c2404b213c8bf39efe49d774e4edebb3553f7e853b96bdeb4dc35ae96ef9c7335d557357d7ff7ebdb353dd5dd336f0441900a4a8f1164c706873ef8ffddfa3977788ff618d79573c9994a08674d4238472484b336219c2313c2599710ce5109e1ac4f08e7e81839155b4d30f81137ef1807bac6cd984e98a60d09d03493304dc72640d3c6201963d4b88470362584737c42382724847362423827258473724238a72484736a4238a72584737a42386724847366423867258473764238e72484b339219cc72484f3d884701e9710cee363e49c079c27e8e713f5f35cfdcc7de6ebe705fa39ab9f5b748eb57af924b256b236c567fc4f9d68c89315c83af4ff9af5ff3ac916922d225b4cd6457632d912b2a564cbc84e215baef35f41b692ec54b2d3c84e273b836c15d99964abc9d6909d45b696ec6cb273c8ce253b8fec7cb27564ebc936182c1bc92e20bb906c13d945649bc92e26bb84ec52b2cbc82e27db42d6437605d956b26d64bd64dbc976905d49b693ec2ab2abc97691ed26db43d647b697ec1ab27d64fbc9fa0dcd0e905d4b761dd9f506e741b21bc86e24bb89ec66b25bc86e25bb8dec4d64b793bd99ec2d646f257b1bd9dbc9ee20bb93ec1d647791bd93ec6eb27bc8de45762fd9bbc9de43761fd97bc9ee277b80ec41b287340b17fbfbc8de4ff601b20f927d88ec61b247c81e25fb30d947c83e4af631b28f933d46f638d913644f927d82ec93644f917d8aec69b24f937d86ecb3649f23fb3cd917c8be48f625b267c8be4cf615b2af923d4bf635b2af937d83ec9b64df22fb36d977c8be4bf63db2ef93fd80ec87643f32347f8eecc7643f217b5eff8fe7907e4af633ddfeb97efe857e7e413fbf68bce69764bf327cbf26fb8de17b89ecb7bafd3bfdfcb27efebd7e7e453fbfaa9fffa09fffa89fffa49fffac9fffa29fffaa9fffa69f5fd3cf7fd7cfafebe737c80ae34bedfa60e0d11dc434ee74f476a9f30f2cf609c1e087d26284fe1f3f376b7fad5ee667d66ea45e1e69f8ebf4729db19e7abd5c6ff89bf47293e19fa0972718fe497a7992e19fa297a7803f1dc03ca4f62bdf08ed4a818febb0067c2383c19a285f1daf0e7ca382c15a281f6fc73af08dd6be51e01ba37df5e04b6bdf68d68cac41fbba83b86a22d7a3d69b897bbdfadcccd8f879b7aaf5363ae21d173f6faf5a6f93035e551f7ab808c641dd4cd0be26f04dd4bef1e09ba47d13c03759fb26826f8af64d02df54ed9b0cbe69da37057cd3b56f2af86668df34f0cdd4bee9e09ba57d33c0375bfb66826f8ef6cd025fb3f6cd06df31da37077cc76a5f33f88ed3be63c077bcf61d0b3e1e3f8f031f1fc31daf7d6a4cf85700afd1fe1af09dc8632ef8e6f2780bbe793cd6826f3e8fb3e05b00b1d9978531847d2ddac7e391fadf42ddee0ee2aafffc36b5de4571af97d6acd6db15ff7a8be7ad4e0e0674ed86388b40ab25ba1de3b531ed183ba58de3b0bf16daaba02ff7633df83d85d9d57bc762dd5e52e6750b8dd765a0cfe290fcbb8378f3ef3278ba0c6655ff4b8123fe9a2db44bcd56fc885cb31ba1af597b7c7c331c6b760d7038a8d94e37359bcf49cd96e6178220bcf6f8187738d6ec26e088bf663ba5662b7f44aed96ba0af597bfc396738d6ec76e088bf661775cab141c58fc8357b2bf4356b8f3feb0ec79aed070e0735db25e36cc58fc8357b37f4356b8fe75d8663cdde0e1cf1d76c97a39a2d48cd06a5734541105e7b3c07381c6bf65ee088bf66b776c9b141c58fc835fb24f4356b8fe7a38763cd3e0c1c0e6ad6d5fc6c5e6ab6748e3c08c26b8fcf8d0cc79a7d4ab7d579869febf30c33c1f70bed9b05be17b46f36f85ed4be399057fcfbc0b682ec03153f22ef03cf415fb3969b757b38ee03df000e0735db29355bf12372cdbe047dcdda3b56b78763cd3e0f1c0e6a7691d46cc58fc835fb1af4356b8faf5f188e35fbb26eabe385dfe9e38513c1f7b2f6cd05dfefb56f1ef85ed1bef9e07b55fb1680ef0fda9705df1fb5af057c7fd2be93c0f767ed6b05df5fb4af0d7c7fd5be1cf8fea67dede07b4dfbf2e0fbbbf615c0f7baf67580ef0dedebd43e754e80af4ff99ef6d5434edd417cdbb6784d4a30f8913296bba1dde296279709065f57cdb15ae38f5550b99f14549e7b2bf0b439c83d0d312ae169039e5cfc3cc56b2fdae35f6f711b9f64689a865827415e790779a52016af9b97395e067c381ee443180bf133e653108bd7cdcb0560641f8e4f3cbef2fea3c6e6b1a9015e07fb52f1fd19e3750307c7ab853e2f340df41dafd91ae0ff3c0634401bc7ef9ce17354abed787d3faf9b97db819173cc559f315f29639bc1580f6c31f214356b3378da42b4385a633b18ab8bfb1fc7e0fd8ff7258e570b7dfea766a0ef8929a76cf9a8efb3589ff18ffff91cee9b95f0e481c7c518e3e87d2e87ef4dff09e2adb50e43ab9ca15506fa1440bf0e07fa957bafe478c22cccc22cccc22cccc22cccc22cccc22cccc22cccc22cccc22cccc22cccc2ec3f339e3fc6f36d61e7668f2423fbda81c7c53c7ff13b49f4ba78fdeabcce33705e27fef316f91c9e77e46bb2e61939d7429fe752036ccf869cf3c5f3bbad86cfd13517c56d89d75c74c332c7c373d0780d88ab73885983271ba2850fb19b638b9ddfea66fbe673ea3b01d4f7eab418db34ecfa9305864fd5e9bf6b06f27671ee2feab9481c4bb81de7b9bd4c70e8b5036eae71298d1f3c5ed50483c70f7c9fe98c3df6e073987cbebcd3885d0b7d5e4a0d6c9b85f0ffeee0d0eb72b00faf9b97e7c16b171aeb6e74976fd96b133a819bdb75466e2dc0cd7d5e81f1fc56d84f1cbcc7e5f1fab000780323277ee039fbf88f014ad710e423f074008f83ebc5da1d1debe4b01ee3be8660a1a155d83114f7e904fd62bf1e5eef1fe671302f733c61166661166661166661166661166661166661166661166661166661166661f69f19efef66d634f46bf784b14ad75d14cf67f0f76de079b1dd3503715d9f03e4734ef38d9c6ba1cfd335036cd7e8764370e8b516b66de9eadc9a6d5b72bc06c807cf05b9ba67bd60f01442b4f02176736cb14bd710c4bf7d07ae21c81bdbb4ddd89e78ee1cf7a15ba14ecdefb848833ed5b86ec9b66d381eee4b05f0711befcd76a135be9798d714713c3c777ea7d696cf9dc73f56e7732ec70dfeee2abe4e201f922bf7b907c6be7b751baf1f6987753d12f27f7e943b4fcdfa1d89eff1c2d83e7c8fd7c335037dcdefe362ada37e8f57ce789d8fdfe3751fd4d923f0feef6a4c5a1c846b340f34e23e781ce4e03a93d031d2bcb652f5e1ed3fcae883c72cdce77118a36cd7ae867df79183efe72bfbdd471c0f8f8d3b80d1ccd1bcc6f468bf46ec19182f16c2ffbb83c3bf46ec19a821bc4e2b30d63f1fd6cf5ca302fb7b0bf779d6781f75b56fd9ae41c37dcbfc3cc0bc780d1af7f9168c55ff84e32cf3d81fafd7aac6673bdbf5e11c0f8fa9a2e48ee342dcef8d588fc8c2f1b01e7f6cd4e3420b776bc86b7f6a792d6bc5d71ce36727533fa5c362784d772c3a94c69b2e2317dea716432edce797c67e13ff3153e9f833fe5c071f0ff1185408c995fbfc16f6b597e1f892b7138e95ff08f93f3fca1d7fb27e2ae7a5f1e75cdcbecbf4ba78fb2e0d897d0ab0c614bb1d63f3f127c7617f2db4df80f713eec77ab0d6ccaef6113e864376f3759dc6eb32d0674948fedd41bcf92f3578961accaa765e853afb071c7fba1aab9758349a0f1a711fbc278adf47b8bfed3dc6c5f5bee5de63c2be1f12c7f67f3a9dd30b6733e7f4c28e4fb80fbf168f4ffe1bc6d974485f73ae92df43e2bc6619ef11c942dc7f87c4ed8e59cf4c3058cf8cc1e13276a311bbb18ab19b8cd84d558c2d9a8be63e69eed3f77fe3f774d72480714402186b13c03832018c7509601c9500c6fa04308e4e00e3980430a681f148beb73bd0271f873e0da0534b08b7abfbf15b0c46f3bb1232062ff31cc963245fb7e158fdecf3366c049e6176ac19cb361ca79f7dde864d6e758c65bc1f9f00c60909609c9800c64909609c9c00c62909609c9a00c66909609c9e00c61909609c9900c65909609c9d00c63909606c4e00e33109603c36018cc72580f1f804309e9000c624cc552e70cb38e4cfd78ac7c16f4276e067f44a785c7fb7a6a33c8bd767f1758c717f479af9bb8e2d865678cf89ebdf834d05f6ef4ae578c22ccc36661c0bf1f7b9b95fd613c6b0ef1e76306e447e4f6975cb33e8b7b75bdd6e8bc8ef556e7f13b4f45e7534fc2668d8fd2d61dff7ce7d70dc3852d73162ece6205e2d3a2ad0a24af7603bf9befeb60432fbf21b033c3e85fdc640d613c62afdc640e4f7aa7c888ead6e19f3436574752f33d64b253cf8bd182e6aca4d9ee5bfcb99e3e1f77fe098e4ea9a7ef3fe385eaee43b4b84599885599885599885599885599885599885599885599885599885599885d90f663cc780dfe5ccfdb29e309adf93e4683e3ef2b99ace101d5bdd32e687ca88df7f161f4fe95c4d94efa95a043c2e6aca4d9ea57335e6f76d761a39e1f76de2feedea3bc816193cbcbc18b683300bb3300bb3300bb3300bb3300bb3300bb3300bb3300bb3300bb3300bb330fbcd8ce71898350dfdb29e30b26f21f0b8f83da6a8e7411685e8d8ea96313f5446c5e3e037e43ab05e2ae1c1dff57051538ef21cf45b79bcee45464e19e883fbf7c90ef24c05f6df8fc3dfd51366610e63c6318b59d3d02feb0923fb16038f83fd3bf2d8df15a263ab5bc6fc5019154ffcbf61593a4fdf1581077fd3c8454db9c9b334f69bbf9564fe566706fae0feede077b3427f27899797c27688c25c4820b3e82c3adb984567d1d9c62c3a8bce3666d15974b6318bcea2b38d5974169d6dcca2b3e86c63169d45671bb3e82c3adb9845e7a347673cc7c0ac69e897f584917d27038f83f9f8c8e76a9684e8d8ea96313f5446c5b32c769e420eeba5129e65c0e3a2a6dce4593a57738a91d31223a70cf4c131e9140779a6202eaf9b974f81ed30dc990b096496daa80eb3d48630db98a53684d9c62cb521cc3666a90d61b6314b6d08b38d596a43986dcc521bc26c6396da10661bb3d48630db98a53684d9c62cb521cc3666a90d61b6314b6d08b38d596a43986dcc3ed4065ea3c3ac69e897f584917d4b816799039ea8d7112d0bd1b1d52d637ea88c8a6779fc3c1d582f95f02c071e1735e528cfe2b54edd464ecb8c9c32d007c7a46e0779a6202eaf1bf30ef476e81666610e61c6318b59d3d02feb0923fb4e011e07fb77e4b17f79888ead6e19f34365543c2b1cf12c8fc0b30278bae3e76977946771ec5f69e4b4dcc829037d70ff5ee920cf14c4e575f3f24ad80e4963c67d8959d3d02feb0923d618f338a8bbc863d28a101d5b13c0d89600c6d109601c9300c63430661d30a6743ce4e1e5ac5b7df271e8d3003af93aee6580c7d5366c34781a43b4f0751b8ed5cf3e6fc346e071b50d9b0c9ea6102d7cdd86e3f4b3cfdbb0c9ad8eb18cf7e313c03821018c1313c03829018c9313c03825018c5313c0382d018cd313c03823018c3313c0382b018cb313c03827018c38a771243fe71ec9cf6747f27385682e9ae70eef219a8be6a2b9682e9a07a279eef01ea2b9c79a27e13384300e6fc682a78cc8938a8f2787b963ac951ee4ce0c49632c2480d1f57546a2632c8cf9a1322a9ed31cf1ac8cc0731af09c1a3f4fbba33c8bd71a9e6ee4b4d2c829037db00e4e7790670ae2f2ba79f974d80e4963c67d8959d3d02feb0923fb4e051e57fb5794fd1df7af331cf19c1681e70ce071b1bd1ce559dcdf5719399d66e494813eb8efac7290670ae2f2ba7979156c87a431e3feceac69e897f584917da7038fabfd2bcafe8efbd7998e78ce88c07326f0b8d85e8ef22ceeefab8d9cce3072ca401fdc77563bc833057179ddbcbc1ab643d298717f67d634f4cb7ac2c8be55c0e3a0ee227fe6383344c7b604301612c0283a8a8e3e318a8e478f8ec2288cc2288c4782310963b8bccf449f1bc0cfe26be2e7e9c0cf6d95f0ac011e179fed1ce5599c1b38cbc8e94c23a70cf4c13a38cb419e2988cbebe6e5b3603b08b3308731e398c5ac69e897f584917dab81c7c1fe1d79ec5f13a263ab5bc6fc501915cfdad8793a72582f95f0ac051e1735e526cfd2d87fb691d31a23a70cf4c1fdfb6c0779a6202eaf9b97cf86ed1085b9904066d15974b6318bcea2b38d5974169d6dcca2b3e86c63169d45671bb3e82c3adb984567d1d9c62c3a8bce3666d15974b6318bcea2b38d5974169d6dcca2b3e86c63169d45671bb3e82c3adb984567d1d9c62c3a8bce3666d15974b6318bcea2b38d5974169d6dcca2b3e86c63169d87c6ac78f8ba73664d43bfac278cec3b0b78d63ae0897a1dfeda101d5bdd32e687caa878ce899fa703eba5129e7380c7454d39cab378afc0b9464e6b8d9c32d007c7a4731de49982b8bc6e5e3e17b64314e642029945e7a131e398c5ac69e897f584917d67038f83fd3bf2d87f4e888ead6e19f34365543ce7c5cfd381f55209cf79c0e3a2a61ce5591cfbcf37723ac7c829037d704c3adf419e2988cbebe6e5f3613b08b3308731e398c5ac69e897f584917de7028f83fd3bf2d87f5e888ead6e19f34365543ceb62e7c9e7b05e2ae159073c2e6aca4d9ea5b17fbd91d379464e19e883fbf77a0779a6202eaf9b97d7c37688c25c4820b3e82c3adb984567d1d9c62c3a8bce3666d15974b6318bcea2b38d5974169d6dcca2b3e86c63169d45671bb3e82c3adb9845e7a347673cc7c0ac69e897f584917de7038f83f9f8c8e76ad685e8d8ea96313f5446c5b321769e420eeba5129e0dc0e3a2a6dce4593a57b3d1c8699d915306fae098b4d1419e2988cbebe6e58db01d863b732181cc521bd56196da10661bb3d48630db98a53684d9c62cb521cc3666a90d61b6314b6d08b38d596a43986dcc521bc26c6396da10661bb3d48630db98a53684d9c62cb521cc3666a90d61b6314b6d08b38dd987dac06b7498350dfdb29e30b26f3df06c70c013f53aa20d213ab6ba65cc0f9551f15c103f4f07d64b253c17008f8b9a729467f15aa70b8d9c36183965a00f8e49173ac833057179ddbc7c216c076116e630661cb398350dfdb29e30b26f23f038d8bf238ffd1784e8d8ea96313f5446c5b3297e9e0eac974a7836018f8b9a72946771ecbfc8c8e90223a70cf4c1fdfb220779a6202eaf9b972f82ed20ccc21cc68c6316b3a6a15fd61346f65d083c0ef6efc863ffa6101d5bdd32e687caa87836c7cfd381f55209cf66e07151538ef22c8efd171b396d3272ca401fdcbf2f7690670ae2f2ba79f962d80ec22ccc61cc3866316b1afa653d6164df45c0e360ff8e3cf66f0ed1b135018c6d09602c2480d1b18ef9a1322a9e4b1cf16c8ec07309f0b8183f1ce5597c9fbfd4c869b3915306fa601d5cea20cf14c4e575f3f2a5b01d92c68cfb12b3a6a15fd61346f65d0c3caef6af28fb3bee5f9739e2b92402cf65c0e3627b39cab3b8bf5f6ee47489915306fae0be73b9833c531097d7cdcb97c376481a33eeefcc9a867e594f18d97729f0b8dabfa2ecefb87f6d71c47359049e2dc0e3627b39cab3b8bff718395d66e494813eb8eff438c833057179ddbcccf192c88cfb3bb3a6a15fd61346f65d0e3c5b1cf044dddfb70063d8670e5f190b0960141d45479f1845c7a34747611446618cc6b822018cb2ad85d157c6ac03c6948e873cbc9cad42ec4623766315633719b19baa185b3417cd4573d15c3417cd0f37b6682e9a8be6a2b9682e9a1f6e6cd15c3417cd4573d15c343fdcd8a2b9682e9a8be6a2b9687eb8b14573d1dc8cdd137fecc8d773f700cf16075a38ca33a7d67b855ed77f62d44f69b5d5d02a6b6895813e57807e5b1de81776cd352f73bca8cc277ac0ec28767e2cad6334e4cf310a861e2afe3647b9dbc6bc6d55886d1bf3aa11db36e65523b6682e9a8be647afe6d8ae0d0ebd0749ada357b747ea65eebf025ec77d1e68283d3706b23d5dc4967d483417cd45f323a139ea5253059ec0e009caf0ccf68c67aa673ce33de319ed19cf08cf78e67ac633c7339e699ef14cf08c678c673cb59ef14cf78c67a2673c69cf78467ac633cf339e199ef1ccf78c6792673c0d9ef1643ce3a9f38c67a6673c933de319eb194fa3673c0b3ce319e5194f8f673cb33ce399e219cf38cf789a3ce3a9f78c27e5014f3a38f49a9a34fcbf077c35c66bd5783571fcc0ffb76b7f0dbc66876e8f0859f776f0f1b9aa1d21af459db6432eddba9d3bbc4751278cd50dcb1caf01387678c253ef194f93673ce33ce399e219cf2ccf787a3ce319e519cf02cf781a3de319eb19cf64cf78667ac653e7194fc6339e06cf782679c633df339e199ef1ccf38c67a4673c69cf78267ac633dd339e5acf78c678c633c1339e699ef1ccf18c67ae673c233ce319ed19cf78cf78a67ac633db339e9a109e1e473c3c7fc9ebe6e51e4f623bd80ec5fb16af7494d34ebdae3abd5ee6e778b5d0e721fdc14dcd47e06b99cb9c6fc6b9a69da051afa35c6cd72ef75621b6eddae56ac4b65dbb5c8dd8a2b9682e9a8be6ea11e33df59d72bf45e53c72bf45791eb9dfa23ccf5ccf78e47e8bf23c72bf45791eb9dfa23c8fdc6f519e47eeb728cfd3e3198fdcff519e47eeff28cf23f77f94e791fb3fcaf3c8fd1fe579e4fe8ff23c72bf45791eb9dfa23c8fdc6f7128cfff77bf05de27c1e7a67ac177a56ef780af262406af6727f86a759bd7a1c69b39e30f65a881d75c15c27565483c8e7355c86baba13bc6ea86658e87f76f5ce5094fbd673c4d9ef18cf38c678a673cb33ce319e519cf02cf781a3de319eb19cf64cf78667ac653e7194fc6339e06cf782679c633df339e199ef1f478c633cf339e919ef1a43de399e819cf74cf786a3de319e319cf04cf78a679c633c7339eb99ef18cf08c67b4673ce33de399ea19cf6ccf786a42787a1cf1d8aea3eda9426cdb75b4d5886dbb8eb61ab14573d15c343fba35bf3afed81de960f06fb6a947ca58ee86f6d5c0e3e2fdce519ec5fbef76e975c5788f434e69b5dbd0aac7d02a037d76817ebb1de89782b8bc6e5ee678c22ccc3666157b4ffcb18be30cc6667d0283871f7b1c6be128cfe278d017846bccf132d007b7799f833c531097d7cdcb7d21b19b8378b5d85b81167b4378f656590b8e1795795702997dd059c5be26f6d8f95cda88cdfa04060f3fae71ac859b3c4bfbd6be205c638e97813e58a7fb1ce49982b8bc6e5ede07db4198855998855998855998855998855998855998855998855998855998855998fd6656b1f7c71ebb347f8fb1599fc0e0e1c77ec75ab8c9b3347fdf1f846bccf132d007b779bf833c531097d7cdcbfdb01d84599885599885599885599885599885599885599885599885599885599885d96f6615fb40fcb18bf7f9606cd6273078f871c0b1168ef22ccedf5f1b846bccf132d007b7f9b50ef24c415c5e372f5f0bdb419885398c59c5be2ef6d8a5f379189bf5090c1e7e5ce7580b377996c683eb83708d395e06fae036bfde419e2988cbebe6e5eb613b4461ee4b20b3e82c3adb984567d1d9c62c3a8bce3666d15974b6318bcea2b38d5974169d6dcca2b3e86c63169d45671bb3e82c3adb9845e7a3476715fb60ecb10bc5f97b8ccdfa04060f3f0e3ad6c24d9ea5f9fb1b82708d395e06fa609ddee020cf14c4e575f3f20db01d863b735f0299a536aac32cb521cc3666a90d61b6314b6d08b38d596a43986dcc521bc26c6396da10661bb3d48630db98a53684d9c62cb521cc3666a90d61b6314b6d08b38d596a43986dcc521bc26c6396da10661bb30fb5a162df187fece2fdec189bf5090c1e7edce8580b477916af7fb92908d798e365a00fd6e94d0ef24c415c5e372fdf04db419885398c59c5be39fed8f9b4119bf5090c1e7edcec580b477916c7835b82708d395e06fae036bfc5419e2988cbebe6e55b603b248d19b75f2abed8c5eb3639468d7e56be5b757b04f86ed3ed5af0bd49b74782ef76ddae03df9b757b14f8de02b9b1efadbabd007c6fd3ed1ef0bd5db7b781ef0eddbe1a7c77eaf66ef0bd43b7f780ef2eddde0bbe77eaf635e0bb5bb7f781ef1eddde0fbe77e9763ff8eed5ed03e07bb76e5f0bbef7e8f675e0bb4fb7af07df7b75fb20f8eed7ed1bc0f7806edf08be0775fb26f03da4dbbbc0f73edd1e0dbef7ebf618f07d00dafcfc41dd6e00df87743b03be87757b2cf81ed1ed46f03daadbe3c0f761dd6e02df47747b3cf83eaadb13c0f731dd9e08be8febf624f03da6db93c1f7b86e4f01df13ba3d157c4feaf634f07d42b7a783ef93ba3d037c4fe9f64cf07d4ab76781ef69dd9e0dbe4febf61cf07d46b771fb7e56b76f061f8f03b7808fc7815bc1c7e3c06de0e371e04de0e371e076f0f138f066f0f138f016f071ddbd157c5c776f031fd7dddbc1c7757707f8b8eeee041fd7dd3bc0c7757717f8b8eede093eaebbbbc1c775770ff8b8eede053eaebb7bc1c775f76ef071ddbd077c5c77f7818febeebde0e3babb1f7c5c770f808febee41f071dd3d043eaebbf7818febeefde0e3bac371a159b73f08be6374fb43e03b56b71f06df71bafd08f88ed7ed47c177826e7f187c27eaf647c03757b73f0abe79bafd31f0cdd7ed8f838fdf9b1e035f56b71f075f8b6e3f01be9374fb49f0b5eaf627c0d7a6db9f045f4eb79f025fbb6e7f0a7c79dd7e1a7c05ddfe34f83a74fb33e0ebd46d1e17d4fed700b9709e0dc07d52080ffbea81a73b88f7988963f1ba79390f8cac637bf519f39532e60c46c5d3e14033ac0d7e94fb4cd2013c05073c8ef22c7e26e93472ca1b3965a0cf899067a7833c531097d7cdcb9d10dbc536472deaf47ae71a5ad4429f37f41b9a7aff2ba723af43d56f7b482eae74cc193cb990d88b1debc8ebe631717115622f3462b719b1713ce747b97d7b21302f72c0acd6db15ff7a8bfbf6c97a5d5ccf1ca70d725a021ac49513c64e69e338ecaf85f6ff360df4e57eac07bf7732bbda8f785b22bbf9ba0ee37519e8b33824ffee20defcbb0c9e2e83591dffff57d3008783fda158038b0d0e5e6e03edba2cda2d06edb84f3bf8f818a7137c7cacc0ebc063253c8e68893fdfd0b187975b80917d0b81b12584b13d7ec6e2b14e8bc1d812a22dfb1601cf42479a2d3278e61afae0fbf228a30fbfb616fa8cd71fec1a83c19f21b8afaafdb1a981bcf833f37f8278c7d53a077ae1e7f900f4090c0df9c10cf5c1c067fe3879c604039fe9f7f7f7edebd9d1bbaeb7675b0ad06a0d4c7c4e85a451033e6c8f08f105c1e0a90b9cf2e4a90b9cf2ac3164c12913eeaf3e4aa9b4787aa077f7cefe8d7b7af76cdd77706f7fefb6b57d3b907aa4418fa4b60c90147dfca80f062659ba83784f76d419b1ca154f3d3c8f8a9fa7dd519ec5379ed1464e75464e19e83312fe37da419e2988cbebe6e5d121b1631c888a5a8ca9408b31213c63aaac054e54b30ff754fe3f9e9ca83172c13d1a7332eb3cd68438e009b0fe948653ff533bfb489dcca8606063f3e8a98e2ad54650339cea5d4bcd60aa83333504a919493503a9661cd50ca39a515433886ac650cd10aa19413503a866fcd40c9f9ad16b0e4a33766a864ecdc81d0f5cdf0356f5895abd3baa19353583a666ccd451957af7574722eae8571da9a94f6feae8407daa54330cea9d561dc5a87768f5aeaa8ed4d4119a3aa25647984bc996919d42b65c6bbd826c25d9a964a7919d4e7606d92ab233c95693ad213b8b6c2dd9d964e7909d4b761ed9f964ebc8d6936d20db487601d985649bc82e22db4c7631d9256497925d467639d996a07456ea0ab2ad41e96c542fd976b21d645792ed24bb2a289d955233efea4c943af3a4ae8651679bd4d9257536499d3d52678bd4d9217536489dfd51677bd4d91d7536479dbd51676bd44cbd9a995733f1b705a5997635b3ae66d2d5ccb99a295733e36a26fc8ea034d3ad66b6ef0a4a33d76aa65acd4cab996835f3ac669ad5ccb29a495633c76aa658cd0cab996035f3ab667ad5ccae9ac95533b76aa656cdccaa99d84783d24cab9a595533a96ae654cd94aa99513513fa44509ae954339b6a2653cd5caa994a3533a96622d5cca39a69fc1cd9e7c9be40f645b22f913d43f665b2af907d95ec59b2af917d9dec1b64df24fb16d9b7c9be43f6dda05483df27fb01d90fc97e44f61cd98fc97e42f63cd94fc97e46f673b25f90bd40f622d92fc97e45f66bb2df90bd44f65bb2df91bd4cf67bb257c85e25fb03d91fc9fe44f667b2bf90fd95ec6f64af91fd9dec75b2378281330e3850fc462ff0ec774f7f7fefeebdfdcdfd7dcdbb0feceadfb977d7c1e6eb76f65fd9dc776defbeedbbfaaec317dfa187269eda5fb16f5fcfc1e69d7bb6f55edfdc77a0bfb96f7bf3157d07f66cdb8f2f7a4cbf68e6a1117bb66db307fb52cd61907e7588419fd3afe39326abcbe7f6fc50047971282ffad71013ba5f9f8a5aaa97d7978e689bf7efeaeb6fce35efa1bf3dbbe835bddb5a9bf17ffb49e4fdfdcdfbfb7bf6f5376fdfd7b7bbb9bd15d7fb60660849bcde348417358daf3cf3e0ff00fdc58975355f0300",
+ "packedBytecode": "0x000000028df71de500000036cb1f8b08000000000000ffed9d67941cc775ef7b76177176b058820824181624080220c2ec6c405884418e14b0244082020572810540805880041639e79cb34831283948b22d4759b26c59b26c39c8966c45cb564e94c40fef9df74defd0ae9aa9abfda3503d9a59740def00b7cfb93bd577abebfeeeedeaea50d55d6f0741100bb24bb99287839b17fa7fdafc266f6da98db0aca44fce58897096950867798970569408679712e1ec5a229cdd4a84b37b8970f6889053b39505372e51f3f6f410d7a819e32516d3ca128869a2c462daab04625a1594461bd5bb4438ab4b84f3ae12e1ec53229c77970867df12e1ec57229cfd4b8473408970de53229cf79608e7c012e1bcaf4438ef2f11ce074a84f3c112e1ac2911ce4125c2f95089703e5c229c834b84f39108398701e710f3fba8f91d6a7e29cf70f3fb98f91d617e471a1f2bccfa2825a3359b925aeb7f2925754aea953458ff6b543246c95825e3ccff6accffc62b69523241c9442593944c367198a264aa92694aa62b99a164a692594a662b99a364ae92794ae62b795cc97b942c50b25049b39227943ca9649192c54a9e52f2b492254a9eb158deab64a9926795bc4fc93225cf29795e498b92e54a56286955b252c92a25ab95bca0648d92b54a5e54b24e499b92f54a36287949c9cb4a362ad9a4a45dc966255b946c55b24dc9762b663b94ec54b24bc96e8b738f92bd4af629d9afe48092834a0e2939ace48892a34a8e2939aee48492934a4e2939ade48c92b34ace2939afe482928b4a2e29b9ace48a92ab4aae29b9aee4fd4a5e51f2aa920f18163a105e53f2ba923794bca9e4834a3ea4e4c34a3ea2e4a34a7e47c9ef2af93d25bfafe4634a3eaee4134afe40c91f2af923259f54f2c74afe44c99f2af933257faee42f947c4ac95f2af9b492cf28f92b259f55f2d74afe46c9e794fcad92cf2bf98292bf53f245257fafe41f947c49c93f2af92725fface45f947cd98af9bf2af937255f51f255f33f7a0ef6ef4afec3a4bf667ebf6e7ebf617ebf696df32d25dfb674ffa9e43b96eebf94fcb7497fd7fc7ecffc7edffcfec0fcfed0fcfec8fcfed8fcfec4fcfed4fcfeccfcfedcfcbe657e7f617e7f697e7f657edf56f2eb01d974f7a063490711b549f52b57ea3e140af690e0c645c7a2dcfc8f7e6b8cbec2acd32fc5ae8b59ef62e9bb9af5ae5639ddcd7a774b5f6dd6ab2d7d1fb3dec7d2f735eb7d2d7d7fb3de1ff4f1009ea51abdd6951b550c74540fcb40d725b831265ad7958a035db7e0c658681dedc7aea0eb6174dd40d7d3e8ba832e6e743d28664a2a8d2e1d445527922dbadc44d4e59afea55ed1f3aed0e55679e2ed1d3def4a5d6eb5075e5d3fee3265f5867ad3c7e8aa4177b7d1dd05babe46d70774fd8cee6ed0f537babea033cd54d00f74f7185d7fd0dd6b74034037d0e8ee01dd7d46772fe8ee37ba81a07bc0e8ee03dd8346773fe86a8cee01d00d32ba0741f790d1d5808ec6ae0c02dd60a37b08748f18ddc3a0a3367530e8e89aef11a3d3ed4497186c63f4d44665b6a176187443a90d06dd306a7f41379cda5ed03d06b6493702da15d28d343a6aa3f4ffc69a743a88ea9848658e89715197ab4ad6e536455f6ea63f6e42d011d734d81907b19a68d2118ef9a945db31236487f415909e0d79291fc583ce33c4aecf27e34d7a628eedc65adb2520cf7887ffe9205aff9b2c9e268bb90ba4fdd4d9ba94d4d9bc9782ebec5390d7ae7b74cd733bd6d979c0e1a1ce364a9dcd7b29b8ceb6425ebbeed175efed58679f010e0f75b6c54f9d4d25a5ce669f7f0581bbeed1bdcfed5867570347f475b641ea6cfe4bc175761fe4b5eb1eddffde8e75763370445f67c7b4c8b541de4bc175f60ce4b5eb1e3d8bb91debec41e0f05067574a3b9bf752709d7d15f2da758f9e0bde8e75f63c70445f67c779aab3755267836c5f6610b8eb1e3da3be1debecebc0117d9d5d21cf67f35f0aaeb39f82bc76dda3fe92dbb1ce7e0238a2afb3adbe9ecfa6a4ce66c7700481bbee51dfdded58673f63d2ba6fec6ba66fec01d07ddde81e04dd3760ec01e9be697483c02f0fc7c0583906f25e0a3e06be0579edbafc9049df8ec7c09781c3439d6d913a9bf752709dfd29e4b5eb1e8d69b81debec7780c3439d5d217536efa5e03afbff20af5df7687ccded5867697ca8be5ef8aeb95e180abaef19dd30d07ddfe88683ee0746f718e87e68742340f723a31b09ba1f1bdd28d0fdc4e84683eea7469704ddcf8cae16743f37ba14e8de32ba3ad0fdc2e8ea41f74ba36b00ddaf8cae11746f1bdd18a3d3fd5834a6ea4b46d71db8d24174fb360e31a02566ada7215deb972799001eb45517bdad3aed7b2ac8dff73ae0a9f7e07b1c6ce4c3530f3c0dd1f364c69336465f6e661fa7ac98c6c1560afc1ae3c1af18d8a2b2699dec254087c7f4180763e4e767751e8d812d2a9bd6c70223e9b08da136978e1fdd36df1febe0f5702c65cecf682f0d1c64af02f25c1ed091779061ab84ff531b5009db631bcca9ae6846aa238dc5674ce5cbd860317a3abe33316bb0785cb6c77bb26dc782ead4f822d81e6bd9aeb76c631b424bae367e2c307bb85eaf7d37aed7f1bcc5e17a3d1debc86b5f7753db53e8f5fa186b3b8ed7eb0d704ef0703c64eac0788b83d6eb21764d21b11b0fb1a33c8f828ece118da0a3b696cac0eb5b6c877d5c2fc5821bafa5d3b0dee0e01e0b8c0d0e460fd798a95c6d733d30920eefad539e6236cee219e788c59d6adb433dcd1c976483aed5e818227b1590e7b1f28ebc4ba0cdf0513ff158a025df7ba0e8f7532a734f565f000fee3b0fd7d9b59eea6312efe7df09a2ad6b769b586fc52aec9e3fe9217e58f7a96c5a277bc22cccc22cccc22cccc22cccc22cccc22cccc22cccc22cccc22cccc22cccc2cc9f19fbb2701c0fe56b60c2688f87f2f59c3ff3cd355316f601bde5757c502ad30f40f1a7f100c32c9f2b20cfaf631d6c6fc3f820fa3f8e7772ed4b1f632573ed4bb2e71aafe4b3bfb9cee2a973c48283ed9ac86ca756f8d9bfa9a4fee691fe6e60bdb54fedbef9ccf7082d9daea7a3ca3bfcf6d1f757685f24b625948eb26f2f013c68cbc7bec163af2cb8b1fdc0f38caf712cd456537ff978cb7605e4e95116fc66df34c1ffd3c1cded05e6a1b2697d186cdb64955de5cfdf9c6313c60337a5bb5abe350237e5e955d6e1e3274ddad3392e55e8386a6ca7a3bf06c88e214815c093041e1fe7314fd73a49ac8f518f21b0c788b9aea1280f8eaff330b631e7382fb227ccc22cccc22cccc22cccc22cccc22cccc22cccc22cccc22cccc22cccc22cccfc99358feb1b1494af8e096391c65d64fa33e87b4dd82ff67a59875ddf7d80d4e734dcf219df5bfd7e5907db874cba32b879ac45d8bef4d5b716b62fc99eeb3b28befa9463608bcaae75c48283ed9ac86c67c71044bf7f3bc610a4ac7d5a67ed4fec3bc763e893504fedf114f1e0e631099e8ef19cfb86ece1b184e35f288def66fb88359e4bec3145640ffbce3f63624b7de7d1b7d5a9a4cf7683be5742e304520e5f29cfe7a0edfbbc49e3f8913a28ebab8effd392ab9f9ae2a77d9e14bdcf99fd3bd99445fb7792c3761a5823b25d8bb66356d9a4af80f457ca6e66a07850ac895d1f23f8dd97b0edeaaded129067a2c3ff74c4fe4fb278701feb45d79d2f423dfb2a9cff7db549134362340c6244793c7f3fd0391ed01ed388ed68372b0f6d8bdf45fb36b4516163575de780df7ccb2828ce3980ec25829bcf0bf98c31bdd3c788bd05ed4513fc3f1ddcfa18b1b7a00ee138adc02a7f38944f5cdd82f0730be579db3a8f7a18c395d7f7715cf703c48b63d028cfff85b66ab019ab59c8fdc0bb756fe7ba1fc0edc27cc77621ea7323d64764c1efab519eff6fd5c7a610eeb18e6dff27645b8a95fdadb0cae0e6f8f9f9ce5ab6bd9964f942c7d444f085f27429eff0c5cf3553f6fad3d737e5e85a87daa05a87af94a7277c33aad2a4e3b09ff0bc30d0f17f5a725d7f52fcb4cf53a2f739b37fa79ab268ff4e71d89e06ac11d9ae45db74fd4976485f01e97bcb3bf2523e8a07c59ad82b8113d9ededc65bdb25204fdae17f3a62ffa7583c532c665d77aaa09e0d84f1f7bedaea74e08ed17088d16fec818ece23f81d50d7b30e5fe7d2b0eb387c6f8b74d8b60f869816eb9d1afb999eebfa64b8c58fd727c3a09d8d3bf2dacf2a69bb28c72ce33b22785d88ef88f8ba474a0437c6336171f8b45d65d9ae2aa2ed6acb7675116d4bcc25e69c62ce69ae08ec9b2a03461fe73a3cafe6c3e83aff9503a3aff7f8520530d601239eef88d1c377586b3b3b17055eeb7401461fefa016fabc1abfc18cef2213a38f6fe117fa3d5bfc96356dd70d187dcc71807315e4c3e89a23a33bfc7a980fa3b6b3dfcdc739327a00a38f6fe9c7831bbfffffdb18c701236dd713187df423c5831b9fabfd3646fc063b6d17f7cc98ebdcee793c48aad0671038fe029f41519c5ce3587c3dabb79f8dd13a3eab47dec05f1c735e2335f98d450ae34e4bae7de88a4f2ff31b87fffb7d9e78631f2f954deb13819174557ee398f35a7382df58a430eeb4e4da87aef8f436bff1e0e6f93f3cf5593bfb28691dfb714957ed99276ef1fcb638ba18ef02c6c99e182715c038191869bb3ec098f6c438b900c63430d2767703a387e7d519c674018cf85c97f47d8171aa27c62905304e0546daae1f30fa78f61c07bbf9304e0346daae3f304ef7c438ad00c6e9c048db0d00c6199e18a717c038031869bb7b8071a627c6190530ce0446daee5e609ce5897166018cb38091b61b088cb33d31ce2a80713630d276f701e31c4f8cb30b609c038cb4ddfdc038d713e39c0218e702236df70030cef3c438b700c679c048db3d088cf33d31ce2b80713e30d2763525c038a804181f2a01c6874b80717009303e52028cdd4b80f131607c3c7ac6ccfdf5fc02181f079e05d1f3d4c7c1463e3c0b80e73dd1f3d47af233333e6ba1292bea6fa4355bb17adc8a5502f22c84f8357b885f0cec52d9b44ef6845998c398350fb54fc41a877cf3993092ee3d9e79e2168f5e72b58fcd7e799209473cb4ad27a2b7952ad4f727806751f43c9973d51305f02c029e27a3e7a9f5e467e69cb2d8f2e909cba704e4c17663b1073f636097caa6f5c50edb3541b4b1782a8f583ce5e079aac8b1207b85322f2c41660e71c6b69058e3906f011346d23de999276ef1e82557fbe8626cf6cb98ea2ca3e659123d4fe69cf254013c4b80e7e9e8796a3df99969479fb17c7acaf2290179b04d7ac6839f31b04b65d3fa33b01f0a615e5c82cc12e7ce31639b45ac71c8b7880923e99ef6cc13b778f492ab1d733136fb654c759651f32c8d9c273b3fc03305f02c059ef746ce936dfba3f733dbf63f6bf9f48ce55302f2609bf4ac073f636097caa6f567613f08b3300bb3300bb3300bb3300bb3300bb3300bb3300bb3300bb3300bb3300b336f66ec6320d638e45bc2849174eff5cc13b778f492abdfc1c5d8ec9731d55946cdb32c729e6c5fcdb305f02c039ef745ce93edab89decf6c5fcd73964fcf5a3e25200f1edfcf79f0330676a96c5a7f0ef683300bb3300bb3300bb3300bb3300bb3300bb3300bb3300bb3300bb3300bb330f366c63e06628d43bea54c1849f73ecf3c718b472fb9fa1d5c8ccd7e19539d65d43c2dd1f364dea97cae009e16e0793e7a9e5a4f7e66fa6a965b3e3d67f994803c787c2ff7e0670cec52d9b4be1cf683300bb38b19db2c628d43be654c1849f7bc679eb8c5a3975ced988bb1d92f63aab38c9aa735729e6c3ffdf202785a816745e43cd9b63f7a3fb36dff4acba7e5964f09c883c7f74a0f7ec6c02e954deb2b613f14c2bcb8049925ce12e7306689b3c4398c59e22c710e6396384b9cc39825ce12e7306689b3c4398c59e22c710e6396384b9cc39825ce12e7306689f39d1367ec6320d638e46b61c248ba159e79e2168f5e72f53bb8189bfd32a63acba8795647ce5397e9ab595900cf6ae05915394fb6af267a3fb37d352f583eadb47c4a401e6c935ef0e0670cec52d9b4fe02ec87db9d797109324bdd280eb3d40d610e6396ba21cc61cc523784398c59ea863087314bdd10e63066a91bc21cc62c754398c398a56e087318b3d40d610e6396ba21cc61cc523784398c59ea863087314bdd10e63066a91bc21cc6cca16ee0181d628d43be56268ca45be599276ef1e825d7b81d1763b35fc654671935cfdae87932df2479a1009eb5c0b3267a9e5a4f7e66c63abd68f9f482e55302f2609bf4a2073f636097caa6f517613f08b330bb98b1cd22d638e45bcd8491746b3cf3c42d1ebde46ac75c8ccd7e19539d65d43c6d9e785e2c80a70d78d645cf53ebc9cf4cdbbfdef2e945cba704e4c1e37bbd073f636097caa6f5f5b01f4a8d198f25628d43beb54c1849b70e783cd4bb82dba436471c9b4b8071610930f62801c69e25c01807460fd7e999633861f1d0fa5abff14945119f4a8813d7762f013cbef66195c553e58805d77dd8cbfc72de8755c0e36b1f565b3cd58e5870dd87bdcd2fe77d58ed378e91b4f7779500639f1260bcbb0418fb960063bf1260ec5f028c034a80f19e1260bcb70418079600e37d25c0787f09303e50028c0f9600a3ef671ab9ee73db8a603becfeac18b6c3ee2b8a615b622e3197984bcc25e612f35bb52d3197984bccdffd98fb78968bcf8d69c9750fe17abeebbb4f5e18f9302e66ca883cb1e87892e83bdadac0c077622835c6c525c0b8b00418258ed93ed4ce306a9e973df16c2880e765e079297a9e5a4f7e66c61a6eb47cda60f994803c580f367af0330676a96c5adf08fba1d498f15822d638e46b63c248ba9780c7d7f155c8f18ec7d7264f3c2f17c0b309787cec2f4f7e668ef776cba7972d9f1290078f9d760f7ec6c02e954debedb01f4a8d198f77628d43be36268ca4db083cbe8eaf428e773cbe367be2d95400cf66e0f1b1bf3cf99939deb7583e6db27c4a401e3c76b678f0330676a96c5adf02fba1d498f17827d638e46b63c248ba76e0f150ef0abee7d8ec88e3c212605c5c028c12478923274689e39d13476114466114c67783b114da7039cf14fe6c00efc5b746cf538ff76df9f06c051e1ff7769efccc3c1bd866f9b4d9f2290179b01e6cf3e0670cec52d9b4be0df683300bb38b19db2c628d43be36268ca4db023c1e8eef82dbfead8e3836fb654c759651f36c8f9ca73e89f5251f9eedc0e3a34ef9f133dbf6efb07cda6af994803c787ceff0e0670cec52d9b4be03f64321cc8b4b9059e22c710e6396384b9cc39825ce12e7306689b3c4398c59e22c710e6396384b9cc39825ce12e7306689b3c4398c59e22c710e6396384b9cc39825ce12e7306689b3c4398c59e22c710e6396384b9cc39825ce12e7306689b3c4398c59e22c710e6396384b9cc39825ce12e7306689b3c4398c59e2dc39661ce34eac71c8d7c6849174db8067bb079e42c7e16f77c4b1d92f63aab38c9a6767f43cf5585ff2e1d9093c3eea94273f33ef0aecb27cda6ef994803cd826edf2e0670cec52d9b4be0bf64321cc8b4b9059e2dc39666cb388350ef9da9830926e07f07838be0b6efb773ae2d8ec9731d55946cdb33b7a9e7aac2ff9f0ec061e1f75ca939f99b67f8fe5d34ecba704e4c136698f073f636097caa6f53db01f8459985dccd866116b1cf2b5316124dd2ee0f1707c17dcf6ef76c4b1d92f63aab38c9a676fe43ca924d6977c78f6028f8f3ae5c7cf6cdbbfcff269b7e55302f2e0f1bdcf839f31b04b65d3fa3ed80ffb0a605e5c82cc1267897318b3c459e21cc62c71963887314b9c25ce61cc1267897318b3c459e21cc62c71963887314b9c25ce61cc1267897318b3c4f9ce8933f631106b1cf2b5316124dd1ee0f1f03cbee0be9abd8e3836fb654c759651f3ec8f9ca72e89f5251f9efdc0b32f729e6c5f4df47e66fb6a0e583eedb57c4a401e6c930e78f0330676a96c5a3f00fbe176675e5c82cc52378ac32c754398c398a56e087318b3d40d610e6396ba21cc61cc52376e5a84d9a4a56e087318b3d40d610e6396ba21cc61cc523784398c59ea863087314bdd10e63066a91bc21cc62c754398c398a56e0873183387ba81637488350ef9da9830926e1ff0ecf7c053e838a2fd8e3836fb654c759651f31c8c9ea71eeb4b3e3c0781c7479df2e46766acd321cba7fd964f09c8836dd2210f7ec6c02e954deb87603f08b330bb98b1cd22d638e46b63c248ba03c0e3e1f82eb8ed3fe88863b35fc654671935cfe1e879eab1bee4c37318787cd4294f7e66dafe23964f072d9f1290078fef231efc8c815d2a9bd68fc07e1066617631639b45ac71c8d7c68491748780c7c3f15d70db7fd811c766bf8ca9ce326a9ea3d1f3d4637dc987e728f0f8a8539efcccb4fdc72c9f0e5b3e25200f1edfc73cf81903bb5436ad1f83fd20ccc2ec62c6368b58e390af8d0923e98e008f87e3bbe0b6ffa8238ecd25c0b8b00418179700a3e738a63acba8798e7be2395a00cf71e0f1d17e78f233739e3f61f974d4f2290179b01e9cf0e0670cec52d9b47e02f643a931e3b144ac71c8d7c6849174c780c7d7f155c8f18ec7d7494f3cc70be039093c3ef697273f33c7fb29cba7e3964f09c883c7ce290f7ec6c02e954deba7603f941a331eefc41a877c6d4c18497702787c1d5f851cef787c9df6c473b2009ed3c0e3637f79f23373bc9fb17c3a69f994803c78ec9cf1e0670cec52d9b47e06f643a931e3f14eac71c8d7c6849174a780c743bd2bf89ee3b4238e0b4b80717109304a1c258e9c18258e774e1c85511885b130c6f525c028fb5a18b932b679608c197bc843eb6d45b05d65d9ae2aa2ed6acb7675116d4bcc25e6127389b9c45c627eabb625e6127389b9c45c622e31bf55db127389b9c45c622e319798dfaa6d89b9c45c622e3197984bcc6fd5b6c45c626edbf630deb4e0f1dc6780e7b4875878f233a9cb3d6bca7a27c2f8e9589db362d566c52a0179ce42fcce79889f6bcc35ad93bd4299873060f6643bd54b95d103fc271b8bad7868fbe73df91ed6e69d2f82edb036af18b6c3dabc62d896984bcc25e6776ecc315d11dcfc0e922ee382497731eb947f3d6c4779deaaccfe5605b23f7dd8966348622e319798bf1b31c7b88c2f024f60f10439786632e3a967c6d3c28c6709339ee1cc784633e399cf8c6716339e29cc78ca99f13431e36960c6338219cf6c663c5399f14c60c6d3c88c27c98c6728339e91cc785a99f10c61c6b39419cf02663cc5e81f2a84670e339e51cc782632e399c68c670c339e5a663c8f32e399cb8ca792194f8219cf74663c9398f18c65c69362c6b39a19cf32663c8b98f1f462c653c58c6706339ec9cc78d632e319c78ca78e19cf3c663cbd99f15433e319c68c27cd8c27c680271edc3c263d0eff3f03ba326bdb6e4abe35a0e3ff178dbe0cb6b964d2e58eb22f828ec67a5d726c8b71ba08bea44d3a796b4b264e682b0deb64af12382e31e14933e319c68ca79a194f6f663cf398f1d431e319c78c672d339ec9cc786630e3a962c6d38b19cf22663ccb98f1ac66c69362c6339619cf24663cd399f12498f15432e399cb8ce751663cb5cc78c630e399c68c6722339e51cc78e630e339c38c6701339ea5cc788630e36965c6339219cf50663c49663c8dcc782630e399ca8c6736339e11cc781a98f13431e32967c6338519cf2c663cf399f18c66c6339c19cf12663c2dcc78ea99f1cc64c633dec1e36b5e66eaffa7b269fd0c13db1ef643e6bb79973df974c594d5d5944bfc64af02f2ac301d43babf0ab7252e7bbc063e2bb80231bae0c997b06f675c2882edb06f6714c376d8b7338a615b621e1ef32bd1db4ee118285a62d67a1ad278dcf9183be6c9cf1bdabca8bf157ad58ad5052b5609c87319e277d543fc5ced28ad93bd4299873060c67a5113445b2fae45efd36fbe414a71bd66c517fdbaee29a6616de9f522d80e6b4b8b613bac2d2d866d89b9c45c627e67c7fcfd261de17546126de87b283a7fbc1fecbe6ad2b108edeab25e3165d1375389e355e0a13c6fc03d9dd43f39e6ef8498db697a4681df15f0f50c296c9f14e3f955d83e2986edb07d520cdb12f3f0987fc083ed7870e35c127ac9f58ce203c0f38a071e4f7e66ceb5af593e9db17c4a401e6cfb5ef3e0670cec52d9b4fe1af0d082dfd1f5510ff2d9e761dfd1e5c053cf8ca78519cf12663cc399f18c66c6339f19cf2c663c5398f19433e36962c6d3c08c6704339eabcc786633e399ca8c6702339e46663c49663c4399f18c64c6d3ca8c6708339ea5cc781630e399c38c6714339e89cc78a631e319c38ca79619cf5c663c95cc7812cc78a633e399c48c672c339e14339ed5cc789631e359c48ce73a339e5ecc78aa98f1cc60c6339919cf5a663ce398f1d431e399c78ca737339e6a663cc398f1a499f1c418f0847d4797fe7f1574d4678fdfd67ddda45f015d99c306f51dbd06ba0a93a632f477797f34e0e6b2314ebec629a0ad34ac933dfc8eeeeb4c78d2cc788631e3a966c6d39b19cf3c663c75cc78c631e359cb8c6732339e19cc78aa98f1f462c6739d19cf22663ccb98f1ac66c69362c6339619cf24663cd399f12498f15432e399cb8ca79619cf18663cd398f14c64c6338a19cf1c663c0b98f12c65c63384194f2b339e91cc788632e34932e36964c6338119cf54663cb399f15c65c63382194f03339e26663ce5cc78a630e399c58c673e339ed1cc788633e359c28ca785194f3d339e99cc78c63b78e4db30d1da96eff1f08cb987ef5466be958adff9d18b3df6260d69cfefcbd6c7831bbfe3f9db78f0bb283eda254f7e66bed1f906b04755ae8ed59b56acae5ab14a401e6478d343fc6241f8f756c89e300b7318b3e6a1f609c7fd51beeb4c1849f71af0f86837b4eff44c81cad7df309cddbbc3ae87f344bd8e037def97be833fc2da2f1590e7b3fd3bd8e61bb64af83fed37edcf154ba77df0fd2d7f2a9bd6c91e3e43c2ef82fb3aefdbdfacbeea88c5bb69dbc377cb6b0bbde6b8e2e079273a9e24b63168eb9227df2f07f9fb7ec9c113a1efb509e0f13c574aa6eda2fe182a5fb70fcb7b7b8d793d1ef7d476d9f319e21c1ed7a1ed5a95a3ed2a0b3a8e19fa5f8dd1dbe7ac778cde2ea32690fb291fb6e51e56ee61038b8716bc878dfe3a23952cf41e16e7cff071bef5743d95f4744ecadcc35eb46265cf3592803cc598e3ebb2c543eb644f98855998855998855998855998855998855998855998855998855998855998f933878d39a17cd79930920efbac7d3ce7d7bed37baa54beee1bed56dd6137fa7e8b5412c72850bfed28cb679c377455bf0eb6b861c3f11c38e6c4b52f7df4f7e7da9764af32b8791c8acf7ebab03130c5e8232cc4764d64b6532bfcecdf5492e644bf6aedd3eb8efde9a3adc0f153b4e4ea6bbc083c3ee6bdf7357644fb74c1f2e98ae55302f2e03c16173cf8e93aafd1fa05e0a105e711f475ce082c9ec0111f5aca98f1d433e3e9ce8ca78519cf12663c8399f1cc67c6f300339e01cc784633e399c58ce72e663c5398f1f460c653ce8ca789194f03339ee1cc784630e3b9c68ce711663c0f32e3b98719cf63cc786633e3e9c38c672a339e9ecc782630e3a960c6d3c88c27c98c6728339e91cc785a99f10c61c6b38019cf52663c35cc78ee65c6338719cfddcc78a631e319c58c6722339e38339e31cc78ba30e3a965c6f328339e41cc780632e399cb8ca72f339ee9cc782a99f12498f14c62c6d39519cf58663c29663cab99f12c63c6b38819cf43cc78ee63c6d38f19cf0c663cbd98f15431e399cc8c672d339e71cc78ba31e3a963c6e37b1c73a13c0f33e399c78ce77e663cfd99f1cc64c6338c194f9a194f6f663cd5cc78620c78e2c1cdef9ec4e1ff574047ef48e0fb75658ef2681c23e5d7e7e95f0eb8b9ec3247d9171d0c18a7f3e04bdaa493b7b6dcf05e48cc944beb64af12382e32e1a966c6d39b194f9a19cf30663c3399f1f467c6733f339e79cc781e66c67399194f1d339e6ecc78c631e359cb8c6732339e2a663cbd98f1cc60c6d38f19cf7dcc781e62c6b38819cf32663cab99f1a498f18c65c6d39519cf24663c09663c95cc78a633e3e9cb8c672e339e81cc780631e37994194f2d339e2ecc78c630e38933e399c88c6714339e69cc78ee66c6338719cfbdcc786a98f12c65c6b38019cf10663cadcc784632e319ca8c27c98ca791194f05339e09cc787a32e399ca8ca70f339ed9cc781e63c6730f339e0799f13cc28ce71a339e11cc788633e36960c6d3c48ca79c194f0f663c5398f1dcc58c6716339ed1cc780630e3798019cf7c663c8399f12c61c6d3c28ca73b339e7a663c65cc78c65b3cf87ffdec83c6b75e001dfdbfb14ff6b7caf871c1b217811fc9b0b9317cc50c97b4232661736370e0a967c6d39d194f0b339e25cc780633e399cf8ce701663c0398f18c66c6338b19cf5dcc78a630e3e9c18ca79c194f13339e06663cc399f18c60c6738d19cf23cc781e64c6730f339ec798f1cc66c6d38719cf54663c3d99f14c60c653c18ca791194f9219cf50663c2399f1b432e359c08c6729339e1a663cf732e399c38ce76e663cd398f18c62c63391194f9c19cf18663c5d98f1d432e3799419cf20663c0399f1cc65c6d39719cf74663c95cc7812cc782631e3e9ca8c672c339e14339ed5cc789631e359c48ce721663cf731e3e9c78c6706339e5ecc78aa98f14c66c6b39619cf38663cdd98f1d431e3b9cc8ce761663cf398f1dccf8ca73f339e99cc788631e34933e3e9cd8ca79a194f8c014fd85c14f4ff72d09d33e96ba03b6bd2974177c6a42f80eeb44357e660217be74047e302ce828e9e4d9f011d3d7f205bfafae0ff0cb899b5cce153b983f5acc3a7738e6d713fd236e920dafd88b6d2b04ef670ae8c734c78aa99f1f466c69366c6338c19cf4c663cfd99f1dccf8c671e339e8799f15c66c653c78ca71b339e71cc78d632e399cc8ca78a194f2f663c3398f1f463c6731f339e8798f12c62c6b38c19cf6a663c29663c6399f17465c63389194f82194f25339ee9cc78fa32e399cb8c6720339e41cc781e65c653cb8ca70b339e31cc78e2cc782632e319c58c671a339ebb99f1cc61c6732f339e1a663c4b99f12c60c6d3ca8c6724339ea1cc7892cc781a99f15430e399c08ca727339ea9cc78fa30e399cd8ce731663cf730e3799019cf23cc78ae31e319c18c6738339e06663c4dcc78ca99f1f460c6338519cf5dcc786631e319cd8c6700339e0798f1cc67c6339819cf12663c2dcc78ba33e3a967c653c68c67bcc583634893a0a3742de8289d021da5eb4047e97ad051ba0174946e041da5c7808ed2634147e971a0a334f988ef6714639e0bb24565d3fa7960a4fb17d73e6902eef3964e739ff2c47ddee2a6f553c0e8f285d82600f7294ba7b94f7ae23e6571d3fa496074f9426c134147e949a0a3f464d04d067ba4a3f414d0517a2ae8283d0d74949e0e3a4acf001da5e9fd057c976116e8283d1b74949e033a4acf051da5e7818ed2f3cdafdec7272d9ddec7274c3a1d44bb8fc916954deb2780d1b5df896d01709fb0749afbb827ee131637ad1f0746972fc4b608b88f5b3acd7dcc13f7718b9bd68f01a3cb17625b02dcc72c9de63eea89fb98c54deb4781d1e50bb12d05eea3964e731ff1c47dd4e2a6f523c0e8f285d89601f7114ba7b90f7be23e6271d3fa616074f9426c2dc07dd8d269ee439eb80f5bdcb47e08185dbe105b2b701fb2749afba027ee431637ad1f0446972fc4b61ab80f5a3acd7dc013f7418b9bd60f00a3cb1762c3ef95acf5c478cd62bc5644db61d78cc5b01d76dd570cdb61d76ec5b01d764d510cdb61d705c5b01d766e2f86edb0f373316c879d638b613bec3c590cdb61e7ba62d80e3b5fc9f12dc777d4b6dfcd73c99d7a7cbf9b6deabbd9b6c8b5a25c2b16cbb69c4be45ab158b6efd46b4569cfc3db730fcf845271b0414bcc5a4f43fa00f0f878b6e6c9cfa42e77bf29eb9d08cbd5b1da67c56aad15ab04e4d90ff1dbe7217e31b04b65d33ad92b4566ac17b1e86c27e36003bf6fb897ca07dd1e93ae05dd6e934e816e9749d7816ea749d7836e874937806ebb49cf05dd36939e07baad267d0a745b4c1afb06379bf449d0b59b34f6c56d32e913a0db68d2d8f7f5b2491f07dd4b268d7d4d1b4cfa18e8d69b34f6edb499f451d0ad3369ec4b79d1a48f806e8d49b780ee05933e0cba55268d7d052b4dfa10e85698f45ad02d37e983a07bdea4f783ee39936e04ddfb4c7a0ce89e35e9b1a07baf498f03dd33268ddfe17cdaa4713cc553267d16748b4d1ac72f3c69d2f81dce274c1ac70b349bf444d02d34e949a07b8f494f06dde3269d06ddfb4d7a0ae85e31e9a9a07bd5a4a781ee03263d1d74af99f40cd0bd6ed23341f78649cf02dd9b263d1b741f34e939a0fb9049631fcd874dfa00e8ca4c7a1fe868fcf05ed0d13b4c7b4047ef99ef061d7dab6417e8e87b693b414763bc76808ec6096f075d4f93de06bab8496f055da5496f011d7d176433e8e85b57eda0ab32e94da0eb6dd21b4147df097d19743476f725d0d1fb3a1b4047ef0caf071d7d07a30d74f4ada975a0a3ef39be083a1a83ba0674f4dec90ba0a3777157818ebe2fb11274f44da915a0a3ef362e071d8d357d1e74f47ec973a0ab31e9f7816e90493f0bba874cfabda0a3ef213e033a1ac3f934e8e83d92a74037c4a417838ebe17f124e8869af413a0a3ef8a36836eb8492f04dd6326fd1ed08d30e9c74147ef8dbe1f74f46efd2ba0a3f1cdaf828eced91f001d9db35f031d9db35f071d9db3df001d9db3df041d9db33f083a6afb3f043a6afba9fdd0c7a93e7eaf98f57410dd7594b67735b871c9752d4f0cc813e5b5710278d0d6a5c87d4f65aec3e9faadcc944bf5e512d8be10b9edec3dc04553561753ee05cb7605e469efd7b16fcec3ffd3e0036d8779a86c5a1f05db9eb7caae32fe5ef4e4ef058b89b82f0213e5d9d6af23ef40d32077876d2264cbdccf525d0b2086b8a4218df367441fab54e6fee272013c178127fae3247b7feda34ee0b115f5fdb5fd6cc4ae6b09c87301e2e76b5cf1458b87d6c99e300bb3300bb3300bb3300bb3300bb3300bb3300bb3300bb3300bb3300bb330f367d63cd49f80e38328df15268ca4c3be2e1fcff9b1cf15fbbc7a40bf8eeffebdaea6dcd196cf1590676eff0eb68449e3b75568bf85ed4b0ffd8439f725d9ab047fb02fc8477f710c6c51d9971cb1e060bb2632dba9157ef66f2aa9c7c7e8fefdcbd63ebd62ed4fec17c7636820d453dae6baf98d437cae437c3c1ce339f70dd9c363e912e8287d15187dc41acf25d41e507f38d9c37ef1474c6ca95f3cfab63a95f4d96ed0f7a1ecfe76f495f20c87b66f8449e3d8902b505693e3ffb4e4eaa7a6f8699fcf44ef7366ff9e3665d1fe3de3b07d0a5823b25d8bb66346c80ee92b203dbe7f475efbdd288a35b1e3b79b90dddeeeaab55d02f29c75f89f0ea2f5ff8cc573c662d6756734d4b32638fffb6a93ce86c46814c488f2e07590877126ce369238c89ece43fbbf9b9507af5928cf3468a3b42fd4ce939f38b605cf01e73cf977c5f28fd6c91e5e1b5f0446db475d3fba5577f0dee9e3bf9e84f6e23cfc3f1ddcfaf8af27a10ee138adc02a7f34944f5cdd82f0730be579da3a8ffab8c7c07d69c713c797519ea5d00e5de8c4b5febb75df1676adefe37b94f1e0c67b6fbde43abfe339c647fbe2c9cfa4ebdc75def229017986809f1eae6372be5778066cfbfa0629d9a06ba80b562c2a204f9bd57684c511ef552f16c59794f37a70b4c317cab3c96aa74e7b60f2b9dff03a4b977bc9e12be5d906eddf0eb89ea7fd846de971c7ff69c9d51e50fcb4cf27a2f739b37fe99d32dabf271cb68f016b44b66f78bf9eaef7c90ee92b207d0ccedf948fe241b1c66f489ea4ed82f0ed2e58db2520cf4987ffe9205affc3be6d40f674ddd90df5ec385ceffb6a374f86c46834c488f2e0b3153ab753feb0f3be8ff1d5b9cefb9780d16e37f1dac527db658bcd7e86eaba1ea43cb42d5e0f5e857636eec8ebffd970f6daa69031ff9e9f91d7c7ac78a6c1ce48d053fda438d3fff01a97f2bce9fdf955b67e5cb0b8c90fecc33967719fb7e28acfa23e6addd3dac7a6eb59a7a76bce7abc16b3af87ce013be5f9b8753d645fc75d73f8629fa7cb829bef05dfb1f2e231996b3b3b7dc5da069f19b89ec9e2bb2e6781256dd2c95b596a9349642a73c42211dc7c4fe67ace40c701d6392acb3e5668db0ac8f319ab4db2f3ea366979ef8ef8d07ec467edae3ae9eb3952d87316b2a7195defd145df76dd99ef2b758facdc71cb4bb18fbbbb958ec676aa15cf15b96271cec1e3eb9966582cce396c47178bc615aef3872b16671d3cbeee31c36271d6613bc258ac723d5b70c5e28c83c7d7bd46582cce386c47178b31373cd7c8158bd30e9ee89f69e48e05f68915c27c960173772b1d8dedfae5aebe3b572c4e39787cf5dd85c5e294c37674b1a86d74ddb3bb6271d2c173b2c8b138e9b01d5d2cc68e733d5371c5e28483c7c3f3b59cb138e1b01d61bd5889cfd772c5e2b883c7d77c3061b138eeb01de1f56163ae6786188b630e1e5fcffec26271cc613bc258b468db47f388c551078faf796bc26271d4613bba582c6fd0b68fe4118b230e1e5f73e184c5e288c37674b16819ab6d1fce2316871d3cbee6d7098bc56187ed08efa132f5e2501eb138e4e0f135674f582c0e396c47178bd6ccb5d6c13c6271d0c1e36b1ea0b0581c74d88e2e16c9cc39f5401eb138e0e03950e4581c70d88eb05e64ee27f7e7118bfd0e9efd458ec57e87ed08cf23997ab12f8f58ec73f0ec2b722cf6396c47178b5599e74f7bf388c55e07cfde22c762afc37684cf5c32f5624f1eb1d8e3e0d953e458ec71d88e2e16759973eaee3c62b1dbc1b3bbc8b1d8edb01d5d2c5666fac476e5118b5d0e9e5d458ec52e87ed08af3b33edc5ce3c62b1d3c1b3b3c8b1d8e9b01de17567e6f9c58e3c62b1c3c1b3a3c8b1d8e1b01d61db99b9eedc9e472cb63b78b6173916db1db623bceeccc4625b1eb1d8e6e0d956e4586c73d88ef0ba33731ed99a472cb63a78b61639165b1db623ac1799b6734b1eb1d8e2e0d952e4586c71d88ef0b956a6eddc9c472c363b78361739169b1db623bc1fc93ce36bcf2316ed0e9ef622c7a2dd613bc2bea2cc35f8a63c62b1c9c1b3a9c8b1d804b67d8c33c158d058ac11562c2a20cf5af3d1761a8b1516472a03c795a12f1b23f7253baeece5105f36822f94e725f0a53be8a364f2e46ba6ced0f7fa696cfa5587af9467f3808ebc5b4d3a0efbe41a9475c4f17f5a728d41a2f8699fd747ef73a6aeb699b268ffae77d85e07ac11d9ae45db34369dec90be02d2870774e4a57c140f8a35b1eb6364834923bbbddd266bbb04e4d9e0f03f1d44ebff7a8b67bdc59c79ef01ea19d5233f6d57966943488c46408c280f8ed9bbea89c71e43481c644fe7a1fddfcdca83632829cf1968a3705c29f9190f6e1e37a9fd7bc9937f61f3c291bd04e82e02a3eda3ae1fb361ec672d31061dba3a934e41398d964efb3ac693af648bcaa6f531c0586fd28dc5674ce5cbd860316a9e711e6286f36cd092eb7c310e78c67ae0f1e467e63c34def2698ce55302f2e0bb8de33df81903bb5436ad8f07db3ef639c682cec943ad5854409e8f5bd78f6171a43274fd6d74f8e22b8e0d164f83c3f644cf71a4b2a94d9c5804db4d96ed7acbb63eb6b18ee925d7b1dd04cc133c30eb7227455f6ee6d8a6b9d4a83e939d7af0290d3188ca27b41db3ca267d05a4bf30e066068a079d3b895d1f475877c2b61b676d97803c131dfea723f67f92c583fb582ffabae153705de9e178c8d481891607add743ec2685c46e22c48ef234828eae71c6838eae1570ee41fa3f5e473444efafb3eda1f50660241dce67d8e060ac8f9e3195ab6dae0746d24d009e264f319b60f10cb5e283e7e56e561edab602f27c03ce8d71475e5df7ef8f75f845731c4638cf4fa65dedea215e38ff6200f109ac18d2420cdd838e391aa3e4e91974ccc1b8a97dc3c696d52b9f5899ed7a24b40a0b137f630e37ca4087e972872e086e9c6ab2027434d56417d0955961c1292e293f4d69e7235c180f2abbc2e2ec0e2c51dac6693a69c95575ba018f8faaacab0e4dd569aaced31bd7b4afc4fad1c5e2ec4cddd1ff2bcf912fac2caa07151e7c47262a9bd6c99e8e4fc2a45f6a59f1e2948dab37b7ad5cdfbe0961ed830bd3312b08f6af6b1bac24783051395dace0f4883e38f578f0da7c01d80b8025303cdda3e7c954d4bb4c592b5ad6ad5bb879f9ba352b666e5ebfa27dcd86f518d16e56e4c2a26d1ff27a71357598d7cedfd5a1b3179c49b71be86826ddeea023fb3d40471c3de17ff69ef0724c0c81f2a91aebff551887bb1a87a80ad26948b727fa98d19f2ed5a77ffd644c7fa94defbe3e4176eaddbe41766a5dfd56b5be52d053e5eaa971f554b87aea5b3dd5ad9eda564f655b1364a7aa7d28c84e453b38e8986a562f5f025e3db5acbed4d053c7eaa9621f0bb28feb4606d94f9be94f49e84b567d3baa2ff3f4ad85bee4d2975afab256dfb2ebc737fa32465f22eacb1f7dc9a22f83f5e5ef6413eb294176aa693db5f4f4203b75b49e2a5a4f0d3d3bc84efdaca75b9f1764a74e7f3cc84e4fbb20c84e5ddb1c64a7ba7d32c84e85be38c84e9bfb74909df25c4fb5aba7e0d5d39aebe979f5b4bd7aea723da5eff341767af2e541767ae0d6203b75b09e52584f2ffd42909d86584f39fe62907da4ad1fe5eb47dcfaf1b17ebca9bb1874d78a7ef4aabb047517a9ee32d65de87a48811e62a1879ce821387a48921ea2a587ace9217cfb82ec10cf03417608b01e12ad8788eb21f3fa15826341f61513fdca8d7e0549bf92753ac8be66a85fdbd3afa1ead772f56bcafab1aa7ec55b77b5e9c7a9fad1b27ee4aa1fffea697af5b4bc7a1a5e3dedae9e66574fabfb46909d36f78341765adc0f2bf988928f2af91d25bfabe4f794fcbe928f29f9b8924f28f903257fa8e48f947c52c91f2bf913257faae4cf94fcb992bf50f229257fa9e4d34a3ea3e4af947c56c95f2bf91b259f53f2b74a3eafe40b4afe4ec91795fcbd927f08b2f5f11f95fc93927f56f22f4abeace45f95fc9b92af28f9aa927f57f21f4abea6e4eb4abea1e49b4abea5e4db4afe53c97794fc9792ff56f25d25df53f27d253f50f243253f52f263253f51f253253f53f273256f29f985925f2af99592b7838ee9a3b1f1e86e5a9c4166bda5bd7d65db4bed35ed1b6ada36af6b5ff3d2baed355bd7b4bf50b361cbca8dabd66dd88a1b7fda6c4cf35c4fd9b8b1657bcd9af5ad2bb7d56cd8dc5eb36155cdf20d9bd7b7de70b2fc96d9e8be9b2db6b4b6861bfbf9ad90feaa93467f6db6eb63d6e7e4f6ed9dce04a4a2bc131b0d2def6414cdc51edd863d99bde6abd9b46e437b4db266bdfaab4eae1bb6ae6c1d5583ffdba482bca9bd66537bcbc6f69a551b37b4d5d48ec27297f7ea8413aff7f203b3a95ff6b753556570ff4e7832b57fe776c713fd6f81f4a94e1a5dd7190f377666a32b9d247c233c2c9b362f6fdfd8b2a23d7ce38fdccac61feb8c9b9feea49b6b0674c2d886ce6c747a40e7083fd619635f2fc058f0bf713e55c3fca204009b2d6c6f000000243c1f8b08000000000000ffed9d77741cd5f5c767654996bd5acb927b17d5658db45a49b6856d9069a677530dd896646370015bf44e1a0921210d924008e924815448ef0909a47712d220014280ff7ee7fcf21fe777dfeebb475f3fcfec6f479ebb7e23df3de76adf5cbd9dfbb9dfb9f376f6cdccee6b41106482f2631cd9c1c1de0ffe7fbf7d2eecdba32bc1751524393329e1ac4b09e7b89470d6a784b321259c8d29e11c9f12cea694704e4890d3b0d5057b3e92e69d28a06bd28cd99469da9c024d7329d374520a346d09d231464d4e09676b4a38db52c23925259c5353c2392d259cd353c23923259c3353c2392b259cb353c23927259c7353c2392f259cf353c2b920259ced29e13c28259c07a784f39094701e9a20e762e03ccc3e1f6e9f17dae745f699fb2eb1cf79fbbcd4e6586f978f20eb20eb347cceffcc8986225937598ff3bf5eb26564cbc956d8ffb5dbfff5911d49b6926c15d96ab2a3c88eb65aac213b86ec58b2e3c88e273b816c2dd9896427919d4c760ad9a964a7919d4e7606d9996467919d4d760ed9b964ebc8ce233b9fec0287e542b28bc82e265b4f7609d9a56497916d20db48b6896c806c906c886c33d916b2cbc9b6925d417625d936b2ed643bc876925d457635d92eb2dd64c364d7905d4b761dd9f58e663790dd487613d9cd0ee72d64b792dd46763bd91d647792bd81ec8d646f227b33d95bc8ee227b2bd9dbc8ee267b3bd93d64ef207b27d9bd64ef227b37d97bc8de4bf63eb2fbc8ee277b3fd907c83e48f600d983968577840f913d44f661b287c93e42f651b28f917d9cec13649f24fb14d923649f26fb0cd967c91e257b8cec73649f27fb02d917c9be44f665b2c7c99e20fb0ad957c9be46f675b26f907d93ec5b64df26fb0ed977c9be47f67db21f90fd90ec47644f92fd98ec27644f913d4df653b29f91fddcd1fc1764bf24fb15d9afedff787ee93764bfb5eddfd9e7dfdbe73fd8e73f3aaf7986ec4f8eefcf64cf3abebf90fdd5b6ff669fff6e9fff619f9fb3cfcfdbe77fdae77fd9e717ecf38bf6f925fbfc6ffbfcb27dfe8f7d7ec53ebf6a9f5f23bba9addc6e0a461efd41426352cfd0a03937c1621f16ecf9305a8cb3ffe3e776ebafb7cbfcccda35d8e506c7df68971b9df534d9e526c7df6a975b1dff14bb3cc5f14fb3cbd31cff0cbb3c03fcd900e628addff8c65957067c5c8775e06b08f6d4c4f81a7975e01b1feca985f1f1766c04df04eb1b0fbe89d6d704beacf54d60cdc89aadaf3f48aa260a1bcd7a7349afd79eb799943cef80596f8b10efe4e47987cc7a5b05784d7dd8e122980c7533c5fa5ac137d5fadac037cdfaa6806fbaf54d05df0ceb9b06be99d6371d7cb3ac6f06f8665bdf4cf0cdb1be59e09b6b7db3c137cffae6806fbef5cd05df02eb9b07be76eb9b0fbe83ac6f01f80eb6be76f01d627d0781ef50eb3b187c3c7e1e023e3ebe3bd4facc98509f81d7583f8f47a5d7f0980bbe853cde826f118fb5e05bcce32cf896406cf6e5610c61df52ebe3f1c8fc6f856df70749d57f71d0acb72fe9f5d29acd7a5726bfded239ad55c188aefd10a70fb45a6ddb095e37d385b133d6380efbeba1bd16fa723fd683df5398ddbc771c69dbab2bbc6e85f3ba1cf4393224fffe20d9fc573a3c2b1de606c85fa666bbbbb466ab7ec4aed975d0d7ad3d3ebe198b357b327008d46cafd66cd58fd8353b007ddddae363dcb158b317008740cdf6c9d46cb1a0355b9eeb0a82f0dae3cf3963b166370347f235dbab355bfd2376cdde067ddddae3cfba63b166878123f99a5ddea7c706553f62d7ec3dd0d7ad3d9e77198b357b277008d4eca08eb3553f62d7ec03d0d7ad3d9e031c8b357b2f70245fb37d4235dbad351b94cf5b064178edf17cf458acd9878023f99a1dd0f9d9ea1fb16bf6abd0d7ad3d3e3732166bf651e048be6607a5e6678b5ab3e5eb358220bcf6f83cdd58acd96fd8b63937f63b7b6e6c3ef87e6f7d0bc0f707b8ce807d7fb4be83202f817d60b9ee03553f62ef03cf405fb7960fb6edb1b80ffc1c38046ab64f6bb6ea47ec9a7d11fabab5c7d72f8cc59a7d1638046a7693d66cd58fd835fb3fd0d7ad3dbe96662cd6eccbb66d8e17fe668f171681efefd6b7187cffb0be25e07bcefaf2e07bdefa9682ef9fd67704f8fe657d1de07bc1fa3ac1f7a2f515c0f792f57581efdfd65704dfcbd6d70dbeff585f0ff85eb1be5ef0bd6a7dcbc0f79af52db73e731e8bafa97acafa9a20f7fe20b96d5bba8e2ad8f3917196fba1bd5496a7900bf6bc4f806375241fabdbe47e44507dee1dc0d329907b166254c3d3093c85e4794a9f47bb925f6f691b1fe1689a855847405e4581bc32108bd7cdcb1c2f073e1c378a218cddc9331633108bd7cdcbddc0c83e1cc7f83e1cde7fccd83c3733c22bb02f95de9f315e3f7070bc7ae833ab6da46fbb656b86fff318d00c6d1ce70b8e4fa8564bb5823767f5c3721730728e85da3316ab65ec74189b802d419e92669d0e4f678816076a6c81b1bab4ff710cdeff785fe278f5d067fcb891be3d1951b662dcf759accfe4c7ff6201f7cd6a788ac02331c608bdcf15f0bde9f520d95aeb71b42a385ae5a04f37e8d723a05fa5f74a8ea7cccaaccccaaccccaaccccaaccccaaccccaaccccaaccccaaccccaaccccaacccfe33e3f9633cdf16766e767f32b2af0b7824e6f94bdfa363d7c5eb37e7759e84f33ac99fb72816f0bc235f93b5c8c9b91efa3c9319617b3ae49c2f9edfed707c42d75c94b6255e73d10fcb1c0fcf41e3352052e710f30e4f3e440b1f62b72716bb3820b37d8b05f33d16e6bba0963adb34ecfa93258ecfd4e9c47123794b9cfb8b7b2e12c7126e27796e2f17ec7ded80cc352ee5f183c7abba60cff103df677a138fbde7394c3e5fdeebc4ae873e2f6646b6cd32f87f7fb0f77539d887d7cdcb8be0b5cb9c75b7c8e55bf1da845ee0e676a393db52e0e63eafc0787e4f5db92df41e57c4ebc302e00d9c9cf881e7ec933f06285f43508cc1d3033c02d78b75091deb14b01e93be866099a355d83114f7e905fd9609e817761ccccb1c4f99955999955999955999955999955999955999955999955999955999955999fd67c6fbbb99350bfdba3c61acd17517a5f319fcbd1c785eeca6ba91b8d2e700f99cd36227e77ae8f3adba11b6db6cbb39d8fb5a8ba86d29756e2d6a5b72bc66c807cf0549ddb3deedf0748768e143ecf6c46297af21487efb8e5c435074b66997b33df1dc39ee43f7409dbadf7191057d6a71dd52d4b6e178b82f75838fdb786fb684d6f85ee25e53c4f1f0dcf9fd565b3e779efc585d2c488e1bfc9d3f7c9d40312457eef3208c7d0fd9365e3fd205eb7a2ce4fffca8749e9af513fa7eb0d2f6e5efabe2eddb17127b25b02614bb0b6367ac711cf6d743fbd1ba91beeef75fb1d6cc6ef611f73bbac25e57705e97833e2b42f2ef0f92cdbfcfe1e973984ded7c04eaec3178ff971a935604e11a2d028db80f1e07095c67123a46bad7569a3ebcfdc73b7df09885fb3c016354d4b5ab61df7db45c28bfa8ef3ee278786cdc038c6e8eee35a607fa35624fc278b10cfedf1fecfb35624f420de1755a81b3fec5b07ee61a1f44bfb7709fa79df751a97d2bea1a34dcb7dccf03cc8bd7a0719f5fc258c53f36da1cec7dec8fd76bd5e2b35dd4f5e11c0f8fa9e2e48ee342d2ef8d588fc8c2f1b01effe4d4e3b208ee8e90d7fe25e2b5ac155f738c9f9d5cfd8c0e897f2faa1d6ffa9c5c789f5a01b9709fe79dfd46eab75d92cf75cfe3211e83ba4372e53e2fc1bef6321c5ff276c2b1f2bf21ffe747a5e34ffc2ed755c9e75cdabefc9da5bc7d5785c43e0a58138add85b1f9f893e3b0bf1edaff0bef27dc8ff560ad99ddec237c0c87eceeeb7a9dd7e5a0cfca90fcfb8364f35fe5f0ac72984dedbc0a75f65f38fe941aab574668b41834e23e784f14bf8fe0f7bb86bdc7485cef5be93d26ecfb21716c0fc6d59ecd9dd30b3b3ee13efc5a3c3e69b0cc669ccd86f475e72af93d24c96b96f11e913cc4c57b44f2427ae6823df5cc391c92b15b9cd82d358cddeac46ead616cd55c35f749739fbeff1bbfa7bb2e058ce352c0589f02c686143036a680717c0a189b52c03821058c1353c09805c6fdf9de2ea04f31097d9a41a7a521dc52f7e32f7518ddef4ac839bcccb33f8f917cdd8693ecb3cfdbb00578c6d8b16622db70b27df6791bb6caea98c878df9602c62929609c9a02c66929609c9e02c61929609c9902c65929609c9d02c63929609c9b02c67929609c9f02c60529606c4f01e34129603c38058c87a480f1d014301e9602c634cc552e91651cf5e76bc323f09b903df819bd1a1ee9efd614cab3747d165fc798f477a4b9bfebb8d4d10aef3991fe3dd84c10fd5da91c4f9995398a19c742fc1d6fee97f78431ecbb8705c68dd8ef291db23c7bfcf67687ecb688fd5e25fb9ba0e5f7aa03e13741c3ee6f09fbbe77ee83e3c6feba8e1163b707c96ad153851635ba075be4fbfa3b53c8eccb6f0cf0f814f61b03794f186bf41b03b1dfab8a213a76c8321647cb2874ff580fd64b353cf8bd18123525799f5cd4f77f703cfcfe0f1c93a4aee977ef8fe3e56abeb3248cb93b85ccaaf3e89871ccc2ef86e17e794f18ddfbae84f6efd8637f6f888e1db28cc5d132cadc675dfe7d8038f7bd2d071e899a92c9b33cf6bbf7eff73a39e1fdfb382649ddd3b8dce1e1e515b01d94599995599995599995599995599995599995599995599995599995599995d96f663cc780dfc3c5fdf29e30b20fbfbf50e2fb5de39e07591ea263872c6371b48c8647e2fb15b15eaae1c1ef0994a829993cf7fcee6d5ef77227a71cf4c1fdfb48813c3341f4f751e3f7742bb3322bb3322bb3322bb3322bb3322bb3322bb3322bb3322bb3322bb3322bb3dfcc788e8159b3d02fef0923fb56008fc07c7cec73357d213a76c8321647cb6878047ec3b207eba51a1efc4d23899a12cab374aec6fdad24f7b73a73d007f76f81dfcd0afd9d245e5e05db419995398c19c72c66cd42bfbc278ceeef010beddfb1c7fe95213a76c8321647cb68785627ce533e4fbf3206cf6ae091a829993ccb63bffbbb7d2b9d9c72d007f76fa9dfed5bedf0f072a5df1aacc4dc9d4266d559758e62569d55e72866d559758e62569d55e72866d559758e62569d55e72866d559758e62569d55e72866d559758e62569d0f1c9df11c03b366a15fde1346f6ad021e81f9f8d8e76a5687e8d821cb581c2da3e1393a719eee02d64b353c47038f444dc9e4593e57d3efe4b4dac929077d704cea17c833037179dd987760b743ff1867ee4e21b3d6466d98b53694398a596b4399a398b53694398a596b4399a398b53694398a596b4399a398b53694398a596b4399a398b53694398a596b4399a398b53694398a596b4399a398b53694398a596b4399a3987da80dbc468759b3d02fef0923fb8e029ea30578e25e477474888e1db28cc5d1321a9e35c9f3f460bd54c3b30678fa93e7e912cab374add3314e4e473b39e5a00f8e49c708e49981b8bc6e5e3e06b683322b7318338e59cc9a857e794f18715f661e81fd3bf6d8bf2644c70e59c6e268190dcfb1423c6b62f01c0b3c123525946769ec3fcec9698d93530efae0fe7d9c409e1988cbebe6e5e3603ba48d19f72566cd42bfbc278cec3b067804ea2ef698746c888e1d2960ec4c01e38414304e4c01631618f3028c191b0f7978392fab4f31097d9a41275fc7bd1cf0486dc31687a725440b5fb7e124fbecf3366c011ea96dd8eaf0b48668e1eb369c6c9f7dde86adb23a2632deb7a580714a0a18a7a680715a0a18a7a78071460a1867a68071560a1867a780714e0a18e7a680715e0a18e7a78071410a18714e637f7ecedd9f9fcff6e7e70ad55c352fecdb433557cd5573d55c350f54f3c2be3d54738f354fc36708651cdb8cdd9e32224f26399e02e68eb124ceabc4cd3dec7a94343076a78051fa3a23d53111c6e268190dcf09423cc7c5e03901788e4f9ea74b28cfd2b5866b9d9c8e7372ca411fac83b5027966202eaf9b97d7c276481b33ee4bcc9a857e794f18d9773cf048ed5f71f677dcbf4e14e2392106cf89c023b1bd84f22cedef2739399de0e494833eb8ef9c24906706e2f2ba79f924d80e6963c6fd9d59b3d02fef0923fbd6028fd4fe15677fc7fdeb64219e1363f09c0c3c12db4b28cfd2fe7e8a93d3894e4e39e883fbce29027966202eaf9b974f81ed903666dcdf99350bfdf29e30b2ef24e011a8bbd89f394e0ed1b133058cdd2960541d55479f1855c70347476554466554c6fdc19886315cdf67e2cf0de067f15393e7e9c1cf6dd5f09c0a3ca724cfd3259467696ee03427a7939d9c72d007ebe034813c331097d7cdcba7c1765066650e63c6318b59b3d02fef0923fb4e011e81fd3bf6d87f6a888e1db28cc5d1321a9ed313e7e92960bd54c3733af048d4944c9ee5b1ff0c27a7539d9c72d007f7ef3304f2cc405c5e372f9f01db210e73770a995567d5398a5975569da3985567d5398a5975569da3985567d5398a5975569da3985567d5398a5975569da3985567d5398a5975569da3985567d5398a5975569da3985567d5398a5975569da3985567d5398a5975569da3985567d5398a5975569da3985567d5398a5975569da39855e7d131e335eecc9a857e794f18d9771af09c2ec013f73afcd34374ec90652c8e96d1f09c993c4f0fd64b353c67028f444d09e559ba57e02c27a7d39d9c72d007c7a4b304f2cc405c5e372f9f05db210e73770a9955e7d131e398c5ac59e897f784917d67008fc0fe1d7bec3f3344c70e59c6e268190dcfd9c9f3f460bd54c37336f048d494509ea5b1ff1c27a7339d9c72d007c7a47304f2cc405c5e372f9f03db419995398c19c72c66cd42bfbc278cec3b0b7804f6efd863ffd9213a76c8321647cb6878ce4d9ca758c07aa986e75ce091a829993ccb63ff3a27a7b39d9c72d007f7ef75027966202eaf9b97d7c17688c3dc9d4266d559758e62569d55e72866d559758e62569d55e72866d559758e62569d55e72866d559758e62569d55e72866d559758e62569d0f1c9df11c03b366a15fde1346f69d033c02f3f1b1cfd59c1ba263872c6371b48c86e7bcc479ba0b582fd5f09c073c1235259367f95ccdf94e4ee73a39e5a00f8e49e70be49981b8bc6e5e3e1fb6c35867ee4e21b3d6466d98b53694398a596b4399a398b53694398a596b4399a398b53694398a596b4399a398b53694398a596b63af8732dbb6d6863247316b6d287314b3d6863247316b6d287314b3d6863247316b6d287314b30fb581d7e8306b16fae53d6164df3ae0394f8027ee7544e785e8d821cb581c2da3e1b920799e1eac976a782e001e899a12cab374add3854e4ee73939e5a00f8e49170ae49981b8bc6e5ebe10b683322b7318338e59cc9a857e794f18d9773ef008ecdfb1c7fe0b4274ec90652c8e96d1f05c943c4f0fd64b353c17018f444d09e5591afb2f7672bac0c929077d70ffbe5820cf0cc4e575f3f2c5b01d945999c39871cc62d62cf4cb7bc2c8be0b814760ff8e3df65f14a263872c6371b48c86677df23c3d582fd5f0ac071e899a12cab334f65fe2e4749193530efae0fe7d89409e1988cbebe6e54b603b28b3328731e398c5ac59e897f784917d17038fc0fe1d7bec5f1fa263470a183b53c0d89d0246611d8ba365343c970af1ac8fc17329f0488c1f427996dee72f73725aefe494833e58079709e49981b8bc6e5ebe0cb643da98715f62d62cf4cb7bc2c8be4b80476aff8ab3bfe3feb54188e7d2183c1b8047627b09e559dadf373a395deae494833eb8ef6c14c833037179ddbcccf1d2c88cfb3bb366a15fde1346f65d063c1b92e789bdbf6f009e4d423c1b62f06c021e89ed259467697f1f7072dae0e494833eb8ef0c08e49981b8bc6e5e1e80ed903666dcdf37d8e72cf4cb7bc2c8be8dc0235077b13f736c0ad1b133058cdd2960541d55479f1855c7034747655446658cc7786c0a18755b2ba3af8c7901c68c8d873cbc9caf41ec1627764b0d63b73ab15b6b185b3557cd5573d55c3557cdf735b66aae9aabe6aab96aae9aef6b6cd55c3557cd5573d55c35dfd7d8aab96aae9aabe6aab96abeafb15573d5dc8d2d70bd69ecebb9078067938016427916cc7a07edba5e4f503fa3d590a355ded12a077d0641bf2101fdc2aeb9e6658e1797f9700f9885621727d13a2640fe1ca3dbd1c3c4df2c947bd498b7b906b1a3c6bc5ac48e1af36a115b3557cd55f30357736cd7077bdf8364d6b1c5b61bec32f73f165ec77d9e6c2e3fb704ba3d2562eb3ea49aabe6aaf9fed01c75a9ab014fe0f00415786af1f9240ecf7ccf78667ac6d3e619cf04cf78c679c6b3d0339e059ef1ccf28c678a673c133de3a9f78c67b6673c533de3c97ac6d3e019cf22cf78e678c6b3d8339e699ef1347bc693f38ca7d1339eb99ef14cf78c6792673c2d9ef12cf18c67bc673cf33ce399e119cf64cf785a3de369f28c27e3014f36d8fb9a9a2cfc7f007c75ce6bcdf8b0be6de4ff975b7f1dbc66ab6d8f0b59f7e5e0e373555b435e8b3a5d0eb9f4db7661df1e259d30563f2c73bc66e0d8ea094f93673cad9ef14cf68c6786673cf33ce319ef19cf12cf785a3ce399e419cf74cf78e67ac6d3e8194fce339e66cf78a679c6b3d8339e399ef12cf28ca7c1339eac673c533de399ed194fbd673c133de399e219cf2ccf781678c6b3d0339e719ef14cf08ca7cd339e999ef1ccf78c67c0339eba101ea9dfc5e0f94b5e372f0f78125b603b94ee5bbc4228a72bedba1aed7a999fe3d5439f9fd80f92667e045fcb5cee7c33d6ea95a0d116a15ca2ae5dde5283d851d72ed72276d4b5cbb588ad9aabe6aab96a6e1e09de53dfebf3fd167a7f43651ebdbfa1328fdedf509947ef6fa8cca3f73754e6d1fb1b2af3e8fd0d9579f4fe86ca3c7a7f43651ebdbfa1328fdedf5099c7b7fbb9f57e8bca3c7abf45651ebddfa2328fde6f519947efb7a8ccd3e4194fc6039effef7e0bbc4f82cf4d6d011f9fffaa745f4616d67325f8f8f327afc38c375bdaf666a883d76c0be1ba22241ec7d916f2da5ae88eb1fa6199e3e1fd1bdb3ce169f28ca7d5339ec99ef1ccf08c679e673ce33de359e2194f8b673c933ce399ee19cf5ccf78063ce369f48c27e7194fb3673cd33ce359ec19cf1ccf781679c6d3e0194fd6339ea99ef1ccf68ca7de339e899ef14cf18c6796673c0b3ce359e819cf38cf782678c6d3e619cf4ccf78e67bc65317c223750f45d475b4b5b87f23ea3ada5ac48eba8eb616b15573d55c353fb035df9e7cec9e6cb0e76fb69947a57373db8147e2fd4e28cfd2fd773becba12bcc7a160b4dae96835e06895833e3b40bf9d02fa65202eaf9b97399e322b7314b3897d55f2b14be30cc6667d0287871f57096b219467693cb83a08d798e3e5a00f6ef3ab05f2cc405c5e372f5f1d12bb3d48568b5d5568b12b8467578db5e078719977a490d9079d4decddc9c72e8d33189bf5091c1e7eec16d64228cfd2be351c846bccf172d007eb745820cf0cc4e575f3f2306c8738cc57a79059751e1db3897d4de2b18b85ac139bf5091c1e7e5c23ac854c9ee5f1e0da205c638e97833e58a7d70ae49981b8bc6e5ebe16b683322bb3322bb3322bb3322bb3322bb3322bb3322bb3322bb3322bb3322bb332fbcd6c625f9778ecf2fc3dc6667d0287871fd7096b21936779fefefa205c638e97833eb8cdaf17c833037179ddbc7c3d6c0765566665566665566665566665566665566665566665566665566665566665f69bd9c4be21f9d8a5fb713036eb13383cfcb841580ba13c4bf3f73706e11a73bc1cf4c16d7ea3409e1988cbebe6e51b613b28b33287319bd837251ebb7c3e0f63b33e81c3c38f9b84b590c9b33c1edc1c846bccf172d007b7f9cd027966202eaf9b976f86ed1087f9ea1432abceaa7314b3eaac3a4731abceaa7314b3eaac3a4731abceaa7314b3eaac3a4731abceaa7314b3eaac3a4731abceaa7314b3ea7ce0e86c62df9278eceed2fc3dc6667d0287871fb7086b21936779fefed6205c638e97833e58a7b70ae49981b8bc6e5ebe15b6c35867be3a85cc5a1bb561d6da50e62866ad0d658e62d6da50e62866ad0d658e62d6da50e62866ad0d658e62d6da50e62866ad0d658e62d6da50e62866ad0d658e62d6da50e62866ad0d658e62d6da50e62866ad0d658e62f6a1364cecdb928f5dba9f1d63b33e81c3c38fdb84b510cab374fdcbed41b8c61c2f077db04e6f17c833037179ddbc7c3b6c87db95599943984dec3b928f5dcc3ab1599fc0e1e1c71dc25a08e5591a0fee0cc235e67839e883dbfc4e813c331097d7cdcb77c276481b336ebf4c72b14bd76d728c3afb6c7c6fb0ed71e07ba36dd783ef4db6dd00be37db7623f8de62dbe3c17717e4c6beb7daf612f0bdcdb607c077b76d6f06dfdb6d7b3bf8eeb1ed9de07b876d5f05be77daf62ef0dd6bdbbbc1f72edb1e06dfbb6dfb1af0bdc7b6af05df7b6dfb3af0bdcfb6af07df7db67d03f8eeb7ed1bc1f77edbbe097c1fb0ed9bc1f741dbbe057c0fd8f6ade07bd0b66f03df876cfb76f03d64db3bc0f761db9e00be876d7b22f83e026d7efea86d3783ef63b69d03dfc76d7b12f83e61db2de0fba46d4f06dfa76cbb157c8fd8761bf83e6ddb53c0f719db9e0abecfdaf634f03d6adbd3c1f7986dcf00dfe76c7b26f83e6fdbb3c0f705db9e0dbe2fdaf61cf07dc9b6e782efcbb63d0f7c8fdbf67cf03d61db0bc0f715dbc6edfb55dbbe037c3c5edc093e1e2fde003e1e2fde083e1e2fde043e1e2fde0c3e1e2fde023e1e2fee021fd7dd5bc1c775f736f071dddd0d3eaebbb7838febee1ef071ddbd037c5c77ef041fd7ddbde0e3ba7b17f8b8eede0d3eaebbf7808febeebde0e3ba7b1ff8b8eeee031fd7ddfde0e3ba7b3ff8b8ee3e003eaebb0f828febee01f071dd3d083eaebb0f818febee21f071dd7d187cedb6fd30f80eb26d1c3f0eb6ed8f82ef10dbfe18f80eb5ed8f83ef30dbfe04f80eb7ed4f826fa16d7f0a7c8b6cfb11f02db6ed4f838fdfc33e03bebc6d7f167c4b6dfb51f01d61db8f81afc3b63f07be4edbfe3cf80ab6fd05f075d9f617c157b4ed2f81afdbb6bf0cbe1edb7e1c7cbdb6fd04f896d9f657c0b7dcb679fc30fba9d9df3867d6a319f23b22849b7d4dc0dd1f247b0cc6b178ddbc5c0446d6bbabf68cc56a190b0ea3e1e911d00c6b881f953ee3f4004fb7008f509ea5cf38bd4e4e4527a71cf4391cf2ec15c833037179ddbcdc0bb125b6396ad168d7bbd0d1a21efa14ec9b9c799faca423afc3d46f57482e523a161c9e4248ec15c23af2ba794c5c5183d8cb9cd89d4e6c1cf7f95169df5e06cccb0598cd7afb925f6f69df3ed2ae8beb99e374424e2b4183a472c2d8196b1c87fdf5d05edb36d297fbb11efcdec9ec663fe26d89eceeeb7a9cd7e5a0cf8a90fcfb8364f3ef7378fa1c66f3396155db0887c0fe50aa81150e072f7782767d11daad00edb84f17f8f818a7177c7cacc0ebc0632a3c8ee84a3edfd0b1a737849b7dcb8031ec58a7903c63c5639d0230b26f39f02c13d26cb9c3b3d0d107df97c73b7df8b5f5d0e722786fcc86f435b53f373392177fb67e3d48765c6d14d00b3ff707a04fe068c80f66680a46e60692e499188c7cf6df3dbc73d7c62d43670f6d1ccc005abd8389cf999034eac087ed7121be20d8738a03a750798a03a750eb1c59706a85fb9b8f52262d9e4618dabe7578dd8ea11d03bb6eb86a7868f0d49d5b90bac1a147d2a80c90147dfc680a462663fa83644f9e343ab12a154f133c8f4f9ea74b28cfd21bcf0427a74627a71cf46980ff4d10c833037179ddbc3c212476820351498b89556831318467628db5c0096df6e19ecaffc7931d754e2eb847634e6e9d279a10073c0cd69fb170e67f66676fb0c98c0f4636368f9ee6a8d26c0433136adeb5cc4ca799d934439099b93433956666d2cc449a994733d3686616cd4ca299393433856666d0cc049a99bff6a03cb36766f2ccccdda1c0f514b09a4fd4e6ddd1ccbc99993633b3668eaacc27267324628e7ecd919a3932304704e653a5996130efb4e628c6bc439b775573a4668ed0cc11b539c25c45b69aec28b2a3add66bc88e213b96ec38b2e3c94e205b4b7622d9496427939d42762ad96964a7939d417626d9596467939d43762ed93ab2f3c8ce27bb80ec42b28bc82e265b4f7609d9a56497916d20db48b629289fed1a241b0aca67b9b6905d4eb695ec0ab22bc9b605e5b35d66a6de9ce13267b4cc5536e62c96396b65ce5299b352e62c9439eb64ce3299b34ae62c92396b64ce1299b342e62c9039eb737b509ee13733fa6606dfccd89b197a33236f66e0ef0aca33ec6646fdeea03c636e66c8cd8cb899013733de6686dbcc689b196c33636d66a8cd8cb499813633ce6686d9cc289b196433636c6688cd8cb099017e3828cff09a195d33836b666ccd0cad99913533b08f04e5195633a36a6650cd8ca999213533a26606d4cc789a194e33a3696630cd8ca599a13433926606f26b645f27fb06d937c9be45f66db2ef907d97ec7b64df27fb01d90fc97e44f624d98fc97e12946bf269b29f92fd8cece764bf20fb25d9afc87e4df61bb2df92fd8eecf7647f20fb23d933647f22fb33d9b3647f21fb2bd9dfc8fe4ef60fb2e7c89e27fb27d9bfc85e207b91ec25b27f93bd4cf61fb257c85e257b2d1839538103c70b768167cd370e0f0f6dbf6ab87d7867fbf66bb60d6fbd6adb0dedd76d1dbebc7de7b543bb366fdb791dbef83e3b54f1298135bb766dbca17deb8ec1a1ebdb775e33dcbe7373fba69dd7ec18dc8d2f7adcbe68eede11370e0e4607fb51dd3e903e35caa0cfd8d7f1c996932ae7f6ec6804796e342faa1f374a15ed29ac5576f99cf2116efbee6d3b87db0bed3be8efc66df49aa1c18e76fcdf6e1279f770fbeee18dbb86db37efdab9bdbdab03d7fbe3dc2892e86c1bc58b2e6cab3ef3e0ff009826ecb5716c0300",
"privateFunctions": [
{
"selector": {
@@ -44,8 +44,8 @@ exports[`ContractClass creates a contract class from a contract compilation arti
"isInternal": false
}
],
- "id": "0x14984eeda340fba89c656a26cd74e08be8fc849619982aa26f94077a897f56a6",
+ "id": "0x055aa066c8ac7456e491c363b2817c2e06d092d4c08d1f4d3c55f252980d0720",
"privateFunctionsRoot": "0x05fa82a96814b6294d557d507151f7ccc12f70522ec4d9d0395a90e87e8087c6",
- "publicBytecodeCommitment": "0x0bf394d6e8a18153fea19434af4dba812bd80872d27c85d18c5e4ec7046172f6"
+ "publicBytecodeCommitment": "0x1ad0c37ac70788b5d3cdad82d06469037ee016b5fe662ce3d39bdca4e4ff9cbc"
}"
`;
diff --git a/yarn-project/circuits.js/src/hints/build_hints.test.ts b/yarn-project/circuits.js/src/hints/build_hints.test.ts
index 6cf3572bbf7..9db861afe55 100644
--- a/yarn-project/circuits.js/src/hints/build_hints.test.ts
+++ b/yarn-project/circuits.js/src/hints/build_hints.test.ts
@@ -1,34 +1,35 @@
+import { makeTuple } from '@aztec/foundation/array';
+import { AztecAddress } from '@aztec/foundation/aztec-address';
+import { padArrayEnd } from '@aztec/foundation/collection';
+import { Fr } from '@aztec/foundation/fields';
+import { Tuple } from '@aztec/foundation/serialize';
+
+import { MAX_NEW_NULLIFIERS_PER_TX, MAX_NULLIFIER_READ_REQUESTS_PER_TX } from '../constants.gen.js';
+import { siloNullifier } from '../hash/index.js';
import {
- AztecAddress,
- Fr,
- MAX_NEW_NULLIFIERS_PER_TX,
- MAX_NULLIFIER_READ_REQUESTS_PER_TX,
- NullifierReadRequestResetHints,
- NullifierReadRequestResetHintsBuilder,
+ NullifierNonExistentReadRequestHintsBuilder,
+ NullifierReadRequestHints,
+ NullifierReadRequestHintsBuilder,
PendingReadHint,
ReadRequestContext,
ReadRequestState,
ReadRequestStatus,
SettledReadHint,
SideEffectLinkedToNoteHash,
-} from '@aztec/circuits.js';
-import { siloNullifier } from '@aztec/circuits.js/hash';
-import { makeTuple } from '@aztec/foundation/array';
-import { Tuple } from '@aztec/foundation/serialize';
-
-import { HintsBuildingDataOracle, buildNullifierReadRequestResetHints } from './build_hints.js';
+} from '../structs/index.js';
+import { buildNullifierNonExistentReadRequestHints, buildNullifierReadRequestHints } from './build_hints.js';
-describe('buildNullifierReadRequestResetHints', () => {
+describe('buildNullifierReadRequestHints', () => {
const contractAddress = AztecAddress.random();
const settledNullifierInnerValue = 99999;
const settledNullifierValue = makeNullifier(settledNullifierInnerValue).value;
- const oracle: HintsBuildingDataOracle = {
- getNullifierMembershipWitness: value =>
+ const oracle = {
+ getNullifierMembershipWitness: (value: Fr) =>
value.equals(settledNullifierValue) ? ({ membershipWitness: {}, leafPreimage: {} } as any) : undefined,
};
let nullifierReadRequests: Tuple;
let nullifiers: Tuple;
- let expectedHints: NullifierReadRequestResetHints;
+ let expectedHints: NullifierReadRequestHints;
let numReadRequests = 0;
let numPendingReads = 0;
let numSettledReads = 0;
@@ -73,12 +74,12 @@ describe('buildNullifierReadRequestResetHints', () => {
numSettledReads++;
};
- const buildHints = () => buildNullifierReadRequestResetHints(oracle, nullifierReadRequests, nullifiers);
+ const buildHints = () => buildNullifierReadRequestHints(oracle, nullifierReadRequests, nullifiers);
beforeEach(() => {
nullifierReadRequests = makeTuple(MAX_NULLIFIER_READ_REQUESTS_PER_TX, ReadRequestContext.empty);
nullifiers = makeTuple(MAX_NEW_NULLIFIERS_PER_TX, i => makeNullifier(innerNullifier(i)));
- expectedHints = NullifierReadRequestResetHintsBuilder.empty();
+ expectedHints = NullifierReadRequestHintsBuilder.empty();
numReadRequests = 0;
numPendingReads = 0;
numSettledReads = 0;
@@ -118,3 +119,125 @@ describe('buildNullifierReadRequestResetHints', () => {
await expect(buildHints()).rejects.toThrow('Read request is reading an unknown nullifier value.');
});
});
+
+describe('buildNullifierNonExistentReadRequestHints', () => {
+ const contractAddress = AztecAddress.random();
+ const oracle = {
+ getLowNullifierMembershipWitness: () => ({ membershipWitness: {}, leafPreimage: {} } as any),
+ };
+ const nonExistentReadRequests = makeTuple(MAX_NULLIFIER_READ_REQUESTS_PER_TX, ReadRequestContext.empty);
+ let nullifiers = makeTuple(MAX_NEW_NULLIFIERS_PER_TX, SideEffectLinkedToNoteHash.empty);
+
+ const innerNullifier = (index: number) => index + 1;
+
+ const makeReadRequest = (value: number, counter = 2) =>
+ new ReadRequestContext(new Fr(value), counter, contractAddress);
+
+ const makeNullifier = (value: number, counter = 1) => {
+ const siloedValue = siloNullifier(contractAddress, new Fr(value));
+ return new SideEffectLinkedToNoteHash(siloedValue, new Fr(0), new Fr(counter));
+ };
+
+ interface TestNullifier {
+ value: number;
+ siloedValue: Fr;
+ }
+
+ const populateNullifiers = (numNullifiers = MAX_NEW_NULLIFIERS_PER_TX) => {
+ nullifiers = makeTuple(MAX_NEW_NULLIFIERS_PER_TX, i =>
+ i < numNullifiers ? makeNullifier(innerNullifier(i)) : SideEffectLinkedToNoteHash.empty(),
+ );
+ };
+
+ const generateSortedNullifiers = (numNullifiers: number) => {
+ const nullifiers: TestNullifier[] = [];
+ for (let i = 0; i < numNullifiers; ++i) {
+ const value = i;
+ nullifiers.push({
+ value,
+ siloedValue: siloNullifier(contractAddress, new Fr(value)),
+ });
+ }
+ return nullifiers.sort((a, b) => (b.siloedValue.lt(a.siloedValue) ? 1 : -1));
+ };
+
+ const buildHints = () => buildNullifierNonExistentReadRequestHints(oracle, nonExistentReadRequests, nullifiers);
+
+ it('builds empty hints', async () => {
+ const hints = await buildHints();
+ const emptyHints = NullifierNonExistentReadRequestHintsBuilder.empty();
+ expect(hints).toEqual(emptyHints);
+ });
+
+ it('builds hints for full sorted nullifiers', async () => {
+ populateNullifiers();
+
+ const hints = await buildHints();
+ const { sortedPendingValues, sortedPendingValueHints } = hints;
+ for (let i = 0; i < sortedPendingValues.length - 1; ++i) {
+ expect(sortedPendingValues[i].value.lt(sortedPendingValues[i + 1].value)).toBe(true);
+ }
+ for (let i = 0; i < nullifiers.length; ++i) {
+ const index = sortedPendingValueHints[i];
+ expect(nullifiers[i].value.equals(sortedPendingValues[index].value)).toBe(true);
+ }
+ });
+
+ it('builds hints for half-full sorted nullifiers', async () => {
+ const numNonEmptyNullifiers = MAX_NEW_NULLIFIERS_PER_TX / 2;
+ populateNullifiers(numNonEmptyNullifiers);
+
+ const hints = await buildHints();
+ const { sortedPendingValues, sortedPendingValueHints } = hints;
+
+ // The first half contains sorted values.
+ for (let i = 0; i < numNonEmptyNullifiers - 1; ++i) {
+ expect(sortedPendingValues[i]).not.toEqual(SideEffectLinkedToNoteHash.empty());
+ expect(sortedPendingValues[i].value.lt(sortedPendingValues[i + 1].value)).toBe(true);
+ }
+ for (let i = 0; i < numNonEmptyNullifiers; ++i) {
+ const index = sortedPendingValueHints[i];
+ expect(nullifiers[i].value.equals(sortedPendingValues[index].value)).toBe(true);
+ }
+
+ // The second half is empty.
+ for (let i = numNonEmptyNullifiers; i < sortedPendingValues.length; ++i) {
+ expect(sortedPendingValues[i]).toEqual(SideEffectLinkedToNoteHash.empty());
+ }
+ for (let i = numNonEmptyNullifiers; i < sortedPendingValueHints.length; ++i) {
+ expect(sortedPendingValueHints[i]).toBe(0);
+ }
+ });
+
+ it('builds hints for read requests', async () => {
+ const numNonEmptyNullifiers = MAX_NEW_NULLIFIERS_PER_TX / 2;
+ expect(numNonEmptyNullifiers > 1).toBe(true); // Need at least 2 nullifiers to test a value in the middle.
+
+ const sortedNullifiers = generateSortedNullifiers(numNonEmptyNullifiers + 3);
+ const minNullifier = sortedNullifiers.splice(0, 1)[0];
+ const maxNullifier = sortedNullifiers.pop()!;
+ const midIndex = Math.floor(numNonEmptyNullifiers / 2);
+ const midNullifier = sortedNullifiers.splice(midIndex, 1)[0];
+
+ nonExistentReadRequests[0] = makeReadRequest(midNullifier.value);
+ nonExistentReadRequests[1] = makeReadRequest(maxNullifier.value);
+ nonExistentReadRequests[2] = makeReadRequest(minNullifier.value);
+ nullifiers = padArrayEnd(
+ sortedNullifiers.map(n => makeNullifier(n.value)),
+ SideEffectLinkedToNoteHash.empty(),
+ MAX_NEW_NULLIFIERS_PER_TX,
+ );
+
+ const hints = await buildHints();
+ const { nextPendingValueIndices } = hints;
+ expect(nextPendingValueIndices.slice(0, 3)).toEqual([midIndex, numNonEmptyNullifiers, 0]);
+ });
+
+ it('throws if reading existing value', async () => {
+ populateNullifiers();
+
+ nonExistentReadRequests[0] = makeReadRequest(innerNullifier(2));
+
+ await expect(() => buildHints()).rejects.toThrow('Nullifier exists in the pending set.');
+ });
+});
diff --git a/yarn-project/circuits.js/src/hints/build_hints.ts b/yarn-project/circuits.js/src/hints/build_hints.ts
index d80f4bd5c37..c22df8b15fe 100644
--- a/yarn-project/circuits.js/src/hints/build_hints.ts
+++ b/yarn-project/circuits.js/src/hints/build_hints.ts
@@ -1,34 +1,35 @@
+import { padArrayEnd } from '@aztec/foundation/collection';
import { Fr } from '@aztec/foundation/fields';
import { Tuple } from '@aztec/foundation/serialize';
+import { IndexedTreeLeafPreimage } from '@aztec/foundation/trees';
import {
MAX_NEW_NULLIFIERS_PER_TX,
+ MAX_NULLIFIER_NON_EXISTENT_READ_REQUESTS_PER_TX,
MAX_NULLIFIER_READ_REQUESTS_PER_TX,
NULLIFIER_TREE_HEIGHT,
} from '../constants.gen.js';
import { siloNullifier } from '../hash/index.js';
import { MembershipWitness } from '../structs/membership_witness.js';
+import { NullifierNonExistentReadRequestHintsBuilder } from '../structs/non_existent_read_request_hints.js';
import { ReadRequestContext } from '../structs/read_request.js';
-import { NullifierReadRequestResetHintsBuilder } from '../structs/read_request_reset_hints.js';
-import { NullifierLeafPreimage } from '../structs/rollup/nullifier_leaf/index.js';
+import { NullifierReadRequestHintsBuilder } from '../structs/read_request_hints.js';
import { SideEffectLinkedToNoteHash } from '../structs/side_effects.js';
import { countAccumulatedItems } from './utils.js';
export interface NullifierMembershipWitnessWithPreimage {
membershipWitness: MembershipWitness;
- leafPreimage: NullifierLeafPreimage;
+ leafPreimage: IndexedTreeLeafPreimage;
}
-export interface HintsBuildingDataOracle {
- getNullifierMembershipWitness(nullifier: Fr): Promise;
-}
-
-export async function buildNullifierReadRequestResetHints(
- oracle: HintsBuildingDataOracle,
+export async function buildNullifierReadRequestHints(
+ oracle: {
+ getNullifierMembershipWitness(nullifier: Fr): Promise;
+ },
nullifierReadRequests: Tuple,
nullifiers: Tuple,
) {
- const builder = new NullifierReadRequestResetHintsBuilder();
+ const builder = new NullifierReadRequestHintsBuilder();
const numReadRequests = countAccumulatedItems(nullifierReadRequests);
@@ -58,3 +59,64 @@ export async function buildNullifierReadRequestResetHints(
}
return builder.toHints();
}
+
+interface SortedResult {
+ sortedValues: Tuple;
+ sortedIndexHints: Tuple;
+}
+
+function sortNullifiersByValues(
+ nullifiers: Tuple,
+): SortedResult {
+ const numNullifiers = countAccumulatedItems(nullifiers);
+ const sorted = nullifiers
+ .slice(0, numNullifiers)
+ .map((nullifier, originalIndex) => ({ nullifier, originalIndex }))
+ .sort((a, b) => (b.nullifier.value.lt(a.nullifier.value) ? 1 : -1));
+
+ const sortedIndexHints: number[] = [];
+ for (let i = 0; i < numNullifiers; ++i) {
+ sortedIndexHints[sorted[i].originalIndex] = i;
+ }
+
+ return {
+ sortedValues: padArrayEnd(
+ sorted.map(s => s.nullifier),
+ SideEffectLinkedToNoteHash.empty(),
+ MAX_NEW_NULLIFIERS_PER_TX,
+ ),
+ sortedIndexHints: padArrayEnd(sortedIndexHints, 0, MAX_NEW_NULLIFIERS_PER_TX),
+ };
+}
+
+export async function buildNullifierNonExistentReadRequestHints(
+ oracle: {
+ getLowNullifierMembershipWitness(nullifier: Fr): Promise;
+ },
+ nullifierNonExistentReadRequests: Tuple,
+ pendingNullifiers: Tuple,
+) {
+ const { sortedValues, sortedIndexHints } = sortNullifiersByValues(pendingNullifiers);
+
+ const builder = new NullifierNonExistentReadRequestHintsBuilder(sortedValues, sortedIndexHints);
+
+ const numPendingNullifiers = countAccumulatedItems(pendingNullifiers);
+ const numReadRequests = countAccumulatedItems(nullifierNonExistentReadRequests);
+ for (let i = 0; i < numReadRequests; ++i) {
+ const readRequest = nullifierNonExistentReadRequests[i];
+ const siloedValue = siloNullifier(readRequest.contractAddress, readRequest.value);
+
+ const { membershipWitness, leafPreimage } = await oracle.getLowNullifierMembershipWitness(siloedValue);
+
+ let nextPendingValueIndex = sortedValues.findIndex(v => !v.value.lt(siloedValue));
+ if (nextPendingValueIndex == -1) {
+ nextPendingValueIndex = numPendingNullifiers;
+ } else if (sortedValues[nextPendingValueIndex].value.equals(siloedValue)) {
+ throw new Error('Nullifier exists in the pending set.');
+ }
+
+ builder.addHint(membershipWitness, leafPreimage, nextPendingValueIndex);
+ }
+
+ return builder.toHints();
+}
diff --git a/yarn-project/circuits.js/src/structs/__snapshots__/public_call_stack_item.test.ts.snap b/yarn-project/circuits.js/src/structs/__snapshots__/public_call_stack_item.test.ts.snap
index 4f72eacc283..f48aceba2c8 100644
--- a/yarn-project/circuits.js/src/structs/__snapshots__/public_call_stack_item.test.ts.snap
+++ b/yarn-project/circuits.js/src/structs/__snapshots__/public_call_stack_item.test.ts.snap
@@ -1,7 +1,7 @@
// Jest Snapshot v1, https://goo.gl/fbAQLP
-exports[`PublicCallStackItem Computes a callstack item hash 1`] = `"0x086b4890110c751f01df5eb163b250f10c90a4f38e73e07e3b5a58685456eaa9"`;
+exports[`PublicCallStackItem Computes a callstack item hash 1`] = `"0x187836686ed01f12180ef08c419e4ac8514d9c60e6a38b4a56d893fa90c83a5d"`;
-exports[`PublicCallStackItem Computes a callstack item request hash 1`] = `"0x09cb16dc10b48bb544bd5f4293cfd2dee539bd281aa468c0c69a9352df17a307"`;
+exports[`PublicCallStackItem Computes a callstack item request hash 1`] = `"0x1a1194c14f229b72d31669b06e3984d6f0f5edd4d5204ceda0ff30f25e910e83"`;
-exports[`PublicCallStackItem computes hash 1`] = `Fr<0x198bebc3ae39ac7041b6f6cf91cf2055e577494f8f2145d81601b192f71e762a>`;
+exports[`PublicCallStackItem computes hash 1`] = `Fr<0x0ef0cbf32ad96d5f6c7577b023a3b4f9a9cd5d53a8c9eb268324183aaa1437ff>`;
diff --git a/yarn-project/circuits.js/src/structs/__snapshots__/public_circuit_public_inputs.test.ts.snap b/yarn-project/circuits.js/src/structs/__snapshots__/public_circuit_public_inputs.test.ts.snap
index f42e5c120b2..201d5c667eb 100644
--- a/yarn-project/circuits.js/src/structs/__snapshots__/public_circuit_public_inputs.test.ts.snap
+++ b/yarn-project/circuits.js/src/structs/__snapshots__/public_circuit_public_inputs.test.ts.snap
@@ -1,5 +1,5 @@
// Jest Snapshot v1, https://goo.gl/fbAQLP
-exports[`PublicCircuitPublicInputs computes empty item hash 1`] = `Fr<0x153eea640dd0a53eaa029301381962507fb89e348d42d6f3335107644c6541b9>`;
+exports[`PublicCircuitPublicInputs computes empty item hash 1`] = `Fr<0x1c9942cee14a4f84b3e606f553b2ab3151c395822ee7ffd51759d5822375d6c9>`;
-exports[`PublicCircuitPublicInputs hash matches snapshot 1`] = `Fr<0x2ae2a860d511acb274dca33de7a64693fe2948275ed149e2db832dd6ce21fc36>`;
+exports[`PublicCircuitPublicInputs hash matches snapshot 1`] = `Fr<0x1135d901dacdffe956b9cd85c976a2c5fe311018164a3ec612ff8ed89f8d56cb>`;
diff --git a/yarn-project/circuits.js/src/structs/index.ts b/yarn-project/circuits.js/src/structs/index.ts
index f18f723e1e0..2a1b8079c16 100644
--- a/yarn-project/circuits.js/src/structs/index.ts
+++ b/yarn-project/circuits.js/src/structs/index.ts
@@ -39,7 +39,8 @@ export * from './public_call_stack_item.js';
export * from './public_circuit_public_inputs.js';
export * from './read_request.js';
export * from './note_hash_read_request_membership_witness.js';
-export * from './read_request_reset_hints.js';
+export * from './read_request_hints.js';
+export * from './non_existent_read_request_hints.js';
export * from './rollup/append_only_tree_snapshot.js';
export * from './rollup/base_or_merge_rollup_public_inputs.js';
export * from './rollup/base_rollup.js';
diff --git a/yarn-project/circuits.js/src/structs/kernel/combined_accumulated_data.ts b/yarn-project/circuits.js/src/structs/kernel/combined_accumulated_data.ts
index 1e05e125508..cc281831036 100644
--- a/yarn-project/circuits.js/src/structs/kernel/combined_accumulated_data.ts
+++ b/yarn-project/circuits.js/src/structs/kernel/combined_accumulated_data.ts
@@ -18,6 +18,7 @@ import {
MAX_NON_REVERTIBLE_PUBLIC_DATA_UPDATE_REQUESTS_PER_TX,
MAX_NOTE_HASH_READ_REQUESTS_PER_TX,
MAX_NULLIFIER_KEY_VALIDATION_REQUESTS_PER_TX,
+ MAX_NULLIFIER_NON_EXISTENT_READ_REQUESTS_PER_TX,
MAX_NULLIFIER_READ_REQUESTS_PER_TX,
MAX_PRIVATE_CALL_STACK_LENGTH_PER_TX,
MAX_PUBLIC_CALL_STACK_LENGTH_PER_TX,
@@ -743,6 +744,13 @@ export class PublicAccumulatedNonRevertibleData {
* The nullifier read requests made in this transaction.
*/
public nullifierReadRequests: Tuple,
+ /**
+ * The nullifier read requests made in this transaction.
+ */
+ public nullifierNonExistentReadRequests: Tuple<
+ ReadRequestContext,
+ typeof MAX_NULLIFIER_NON_EXISTENT_READ_REQUESTS_PER_TX
+ >,
/**
* The new non-revertible commitments made in this transaction.
*/
@@ -771,6 +779,7 @@ export class PublicAccumulatedNonRevertibleData {
toBuffer() {
return serializeToBuffer(
this.nullifierReadRequests,
+ this.nullifierNonExistentReadRequests,
this.newNoteHashes,
this.newNullifiers,
this.publicCallStack,
@@ -783,6 +792,7 @@ export class PublicAccumulatedNonRevertibleData {
const reader = BufferReader.asReader(buffer);
return new this(
reader.readArray(MAX_NULLIFIER_READ_REQUESTS_PER_TX, ReadRequestContext),
+ reader.readArray(MAX_NULLIFIER_NON_EXISTENT_READ_REQUESTS_PER_TX, ReadRequestContext),
reader.readArray(MAX_NON_REVERTIBLE_NOTE_HASHES_PER_TX, SideEffect),
reader.readArray(MAX_NON_REVERTIBLE_NULLIFIERS_PER_TX, SideEffectLinkedToNoteHash),
reader.readArray(MAX_NON_REVERTIBLE_PUBLIC_CALL_STACK_LENGTH_PER_TX, CallRequest),
@@ -802,6 +812,7 @@ export class PublicAccumulatedNonRevertibleData {
static empty() {
return new this(
makeTuple(MAX_NULLIFIER_READ_REQUESTS_PER_TX, ReadRequestContext.empty),
+ makeTuple(MAX_NULLIFIER_NON_EXISTENT_READ_REQUESTS_PER_TX, ReadRequestContext.empty),
makeTuple(MAX_NON_REVERTIBLE_NOTE_HASHES_PER_TX, SideEffect.empty),
makeTuple(MAX_NON_REVERTIBLE_NULLIFIERS_PER_TX, SideEffectLinkedToNoteHash.empty),
makeTuple(MAX_NON_REVERTIBLE_PUBLIC_CALL_STACK_LENGTH_PER_TX, CallRequest.empty),
@@ -813,6 +824,7 @@ export class PublicAccumulatedNonRevertibleData {
static fromPrivateAccumulatedNonRevertibleData(data: PrivateAccumulatedNonRevertibleData) {
return new this(
makeTuple(MAX_NULLIFIER_READ_REQUESTS_PER_TX, ReadRequestContext.empty),
+ makeTuple(MAX_NULLIFIER_NON_EXISTENT_READ_REQUESTS_PER_TX, ReadRequestContext.empty),
data.newNoteHashes,
data.newNullifiers,
data.publicCallStack,
diff --git a/yarn-project/circuits.js/src/structs/kernel/private_kernel_tail_circuit_private_inputs.ts b/yarn-project/circuits.js/src/structs/kernel/private_kernel_tail_circuit_private_inputs.ts
index da7f5cd14e1..2f90462d3bd 100644
--- a/yarn-project/circuits.js/src/structs/kernel/private_kernel_tail_circuit_private_inputs.ts
+++ b/yarn-project/circuits.js/src/structs/kernel/private_kernel_tail_circuit_private_inputs.ts
@@ -8,10 +8,7 @@ import {
} from '../../constants.gen.js';
import { GrumpkinPrivateKey } from '../../index.js';
import { Fr, GrumpkinScalar } from '../index.js';
-import {
- NullifierReadRequestResetHints,
- nullifierReadRequestResetHintsFromBuffer,
-} from '../read_request_reset_hints.js';
+import { NullifierReadRequestHints, nullifierReadRequestHintsFromBuffer } from '../read_request_hints.js';
import { SideEffect, SideEffectLinkedToNoteHash } from '../side_effects.js';
import { PrivateKernelInnerData } from './private_kernel_inner_data.js';
@@ -47,7 +44,7 @@ export class PrivateKernelTailCircuitPrivateInputs {
/**
* Contains hints for the nullifier read requests to locate corresponding pending or settled nullifiers.
*/
- public nullifierReadRequestResetHints: NullifierReadRequestResetHints,
+ public nullifierReadRequestHints: NullifierReadRequestHints,
/**
* Contains hints for the transient nullifiers to localize corresponding commitments.
*/
@@ -70,7 +67,7 @@ export class PrivateKernelTailCircuitPrivateInputs {
this.readCommitmentHints,
this.sortedNewNullifiers,
this.sortedNewNullifiersIndexes,
- this.nullifierReadRequestResetHints,
+ this.nullifierReadRequestHints,
this.nullifierCommitmentHints,
this.masterNullifierSecretKeys,
);
@@ -90,7 +87,7 @@ export class PrivateKernelTailCircuitPrivateInputs {
reader.readArray(MAX_NOTE_HASH_READ_REQUESTS_PER_TX, Fr),
reader.readArray(MAX_NEW_NULLIFIERS_PER_TX, SideEffectLinkedToNoteHash),
reader.readNumbers(MAX_NEW_NULLIFIERS_PER_TX),
- reader.readObject({ fromBuffer: nullifierReadRequestResetHintsFromBuffer }),
+ reader.readObject({ fromBuffer: nullifierReadRequestHintsFromBuffer }),
reader.readArray(MAX_NEW_NULLIFIERS_PER_TX, Fr),
reader.readArray(MAX_NULLIFIER_KEY_VALIDATION_REQUESTS_PER_TX, GrumpkinScalar),
);
diff --git a/yarn-project/circuits.js/src/structs/kernel/public_kernel_tail_circuit_private_inputs.ts b/yarn-project/circuits.js/src/structs/kernel/public_kernel_tail_circuit_private_inputs.ts
index 37d79805d11..77f3bc4cf11 100644
--- a/yarn-project/circuits.js/src/structs/kernel/public_kernel_tail_circuit_private_inputs.ts
+++ b/yarn-project/circuits.js/src/structs/kernel/public_kernel_tail_circuit_private_inputs.ts
@@ -1,6 +1,7 @@
import { serializeToBuffer } from '@aztec/foundation/serialize';
-import { NullifierReadRequestResetHints } from '../read_request_reset_hints.js';
+import { NullifierNonExistentReadRequestHints } from '../non_existent_read_request_hints.js';
+import { NullifierReadRequestHints } from '../read_request_hints.js';
import { PublicKernelData } from './public_kernel_data.js';
/**
@@ -15,10 +16,18 @@ export class PublicKernelTailCircuitPrivateInputs {
/**
* Contains hints for the nullifier read requests to locate corresponding pending or settled nullifiers.
*/
- public nullifierReadRequestResetHints: NullifierReadRequestResetHints,
+ public readonly nullifierReadRequestHints: NullifierReadRequestHints,
+ /**
+ * Contains hints for the nullifier non existent read requests.
+ */
+ public readonly nullifierNonExistentReadRequestHints: NullifierNonExistentReadRequestHints,
) {}
toBuffer() {
- return serializeToBuffer(this.previousKernel, this.nullifierReadRequestResetHints);
+ return serializeToBuffer(
+ this.previousKernel,
+ this.nullifierReadRequestHints,
+ this.nullifierNonExistentReadRequestHints,
+ );
}
}
diff --git a/yarn-project/circuits.js/src/structs/non_existent_read_request_hints.ts b/yarn-project/circuits.js/src/structs/non_existent_read_request_hints.ts
new file mode 100644
index 00000000000..9cd254bc0b9
--- /dev/null
+++ b/yarn-project/circuits.js/src/structs/non_existent_read_request_hints.ts
@@ -0,0 +1,152 @@
+import { makeTuple } from '@aztec/foundation/array';
+import { BufferReader, Tuple, serializeToBuffer } from '@aztec/foundation/serialize';
+import { IndexedTreeLeafPreimage } from '@aztec/foundation/trees';
+
+import {
+ MAX_NEW_NULLIFIERS_PER_TX,
+ MAX_NULLIFIER_NON_EXISTENT_READ_REQUESTS_PER_TX,
+ NULLIFIER_TREE_HEIGHT,
+} from '../constants.gen.js';
+import { MembershipWitness } from './membership_witness.js';
+import { NullifierLeafPreimage } from './rollup/nullifier_leaf/index.js';
+import { SideEffectLinkedToNoteHash, SideEffectType } from './side_effects.js';
+
+export class NonMembershipHint {
+ constructor(public membershipWitness: MembershipWitness, public leafPreimage: LEAF_PREIMAGE) {}
+
+ static empty(
+ treeHeight: TREE_HEIGHT,
+ makeEmptyLeafPreimage: () => LEAF_PREIMAGE,
+ ) {
+ return new NonMembershipHint(MembershipWitness.empty(treeHeight, 0n), makeEmptyLeafPreimage());
+ }
+
+ static fromBuffer(
+ buffer: Buffer | BufferReader,
+ treeHeight: TREE_HEIGHT,
+ leafPreimageFromBuffer: { fromBuffer: (buffer: BufferReader) => LEAF_PREIMAGE },
+ ): NonMembershipHint {
+ const reader = BufferReader.asReader(buffer);
+ return new NonMembershipHint(
+ MembershipWitness.fromBuffer(reader, treeHeight),
+ reader.readObject(leafPreimageFromBuffer),
+ );
+ }
+
+ toBuffer() {
+ return serializeToBuffer(this.membershipWitness, this.leafPreimage);
+ }
+}
+
+export class NonExistentReadRequestHints<
+ READ_REQUEST_LEN extends number,
+ TREE_HEIGHT extends number,
+ LEAF_PREIMAGE extends IndexedTreeLeafPreimage,
+ PENDING_VALUE_LEN extends number,
+ PENDING_VALUE extends SideEffectType,
+> {
+ constructor(
+ /**
+ * The hints for the low leaves of the read requests.
+ */
+ public nonMembershipHints: Tuple, READ_REQUEST_LEN>,
+ /**
+ * Indices of the smallest pending values greater than the read requests.
+ */
+ public nextPendingValueIndices: Tuple,
+ public sortedPendingValues: Tuple,
+ public sortedPendingValueHints: Tuple,
+ ) {}
+
+ static fromBuffer<
+ READ_REQUEST_LEN extends number,
+ TREE_HEIGHT extends number,
+ LEAF_PREIMAGE extends IndexedTreeLeafPreimage,
+ PENDING_VALUE_LEN extends number,
+ PENDING_VALUE extends SideEffectType,
+ >(
+ buffer: Buffer | BufferReader,
+ readRequestLen: READ_REQUEST_LEN,
+ treeHeight: TREE_HEIGHT,
+ leafPreimageFromBuffer: { fromBuffer: (buffer: BufferReader) => LEAF_PREIMAGE },
+ pendingValueLen: PENDING_VALUE_LEN,
+ orderedValueFromBuffer: { fromBuffer: (buffer: BufferReader) => PENDING_VALUE },
+ ): NonExistentReadRequestHints