Merge pull request #17 from vacp2p/upstream-fixes-foundry

Modernize repo, use foundry, deploy to sepolia
This commit is contained in:
Aaryamann Challani 2023-03-29 17:58:50 +05:30 committed by GitHub
commit ca05aee912
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
32 changed files with 12046 additions and 37965 deletions

View File

@ -1,3 +1,3 @@
ETHERSCAN_API_KEY=ABC123ABC123ABC123ABC123ABC123ABC1
ROPSTEN_URL=https://eth-ropsten.alchemyapi.io/v2/<YOUR ALCHEMY KEY>
SEPOLIA_URL=https://eth-sepolia.alchemyapi.io/v2/<YOUR ALCHEMY KEY>
PRIVATE_KEY=0xabc123abc123abc123abc123abc123abc123abc123abc123abc123abc123abc1

60
.github/workflows/ci.yml vendored Normal file
View File

@ -0,0 +1,60 @@
name: CI
on:
push:
branches: [main]
paths:
- "**.sol"
- "scripts/**.ts"
- "test/**.ts"
- "hardhat.config.ts"
- "package.json"
- ".github/workflows/ci.yml"
pull_request:
branches: [main]
paths:
- "**.sol"
- "scripts/**.ts"
- "test/**.ts"
- "hardhat.config.ts"
- "package.json"
- ".github/workflows/ci.yml"
jobs:
test:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v3
with:
submodules: recursive
- name: Install Foundry
uses: foundry-rs/foundry-toolchain@v1
with:
version: nightly
- uses: actions/setup-node@v3
with:
node-version: 16
cache: "yarn"
- id: dependencies
run: yarn install
- id: lint
run: yarn lint
- id: test
run: yarn test:verbose
- id: coverage
run: yarn coverage
- id: upload-coverage
# run only in pull requests
if: github.event_name == 'pull_request'
uses: zgosalvez/github-actions-report-lcov@v1
with:
github-token: ${{ secrets.GITHUB_TOKEN }}
coverage-files: lcov.info
artifact-name: code-coverage-report

5
.gitignore vendored
View File

@ -7,3 +7,8 @@ typechain
#Hardhat files
cache
artifacts
#Foundry files
cache_forge
out
lcov.info

4
.gitmodules vendored Normal file
View File

@ -0,0 +1,4 @@
[submodule "lib/forge-std"]
path = lib/forge-std
url = https://github.com/foundry-rs/forge-std
branch = v1.5.2

4
.husky/pre-commit Executable file
View File

@ -0,0 +1,4 @@
#!/usr/bin/env sh
. "$(dirname -- "$0")/_/husky.sh"
npx lint-staged

4
.solcover.js Normal file
View File

@ -0,0 +1,4 @@
module.exports = {
istanbulReporter: ["lcov"],
istanbulFolder: "coverage",
};

203
LICENSE-APACHE Normal file
View File

@ -0,0 +1,203 @@
Copyright (c) 2023 Vac Research
Apache License
Version 2.0, January 2004
http://www.apache.org/licenses/
TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION
1. Definitions.
"License" shall mean the terms and conditions for use, reproduction,
and distribution as defined by Sections 1 through 9 of this document.
"Licensor" shall mean the copyright owner or entity authorized by
the copyright owner that is granting the License.
"Legal Entity" shall mean the union of the acting entity and all
other entities that control, are controlled by, or are under common
control with that entity. For the purposes of this definition,
"control" means (i) the power, direct or indirect, to cause the
direction or management of such entity, whether by contract or
otherwise, or (ii) ownership of fifty percent (50%) or more of the
outstanding shares, or (iii) beneficial ownership of such entity.
"You" (or "Your") shall mean an individual or Legal Entity
exercising permissions granted by this License.
"Source" form shall mean the preferred form for making modifications,
including but not limited to software source code, documentation
source, and configuration files.
"Object" form shall mean any form resulting from mechanical
transformation or translation of a Source form, including but
not limited to compiled object code, generated documentation,
and conversions to other media types.
"Work" shall mean the work of authorship, whether in Source or
Object form, made available under the License, as indicated by a
copyright notice that is included in or attached to the work
(an example is provided in the Appendix below).
"Derivative Works" shall mean any work, whether in Source or Object
form, that is based on (or derived from) the Work and for which the
editorial revisions, annotations, elaborations, or other modifications
represent, as a whole, an original work of authorship. For the purposes
of this License, Derivative Works shall not include works that remain
separable from, or merely link (or bind by name) to the interfaces of,
the Work and Derivative Works thereof.
"Contribution" shall mean any work of authorship, including
the original version of the Work and any modifications or additions
to that Work or Derivative Works thereof, that is intentionally
submitted to Licensor for inclusion in the Work by the copyright owner
or by an individual or Legal Entity authorized to submit on behalf of
the copyright owner. For the purposes of this definition, "submitted"
means any form of electronic, verbal, or written communication sent
to the Licensor or its representatives, including but not limited to
communication on electronic mailing lists, source code control systems,
and issue tracking systems that are managed by, or on behalf of, the
Licensor for the purpose of discussing and improving the Work, but
excluding communication that is conspicuously marked or otherwise
designated in writing by the copyright owner as "Not a Contribution."
"Contributor" shall mean Licensor and any individual or Legal Entity
on behalf of whom a Contribution has been received by Licensor and
subsequently incorporated within the Work.
2. Grant of Copyright License. Subject to the terms and conditions of
this License, each Contributor hereby grants to You a perpetual,
worldwide, non-exclusive, no-charge, royalty-free, irrevocable
copyright license to reproduce, prepare Derivative Works of,
publicly display, publicly perform, sublicense, and distribute the
Work and such Derivative Works in Source or Object form.
3. Grant of Patent License. Subject to the terms and conditions of
this License, each Contributor hereby grants to You a perpetual,
worldwide, non-exclusive, no-charge, royalty-free, irrevocable
(except as stated in this section) patent license to make, have made,
use, offer to sell, sell, import, and otherwise transfer the Work,
where such license applies only to those patent claims licensable
by such Contributor that are necessarily infringed by their
Contribution(s) alone or by combination of their Contribution(s)
with the Work to which such Contribution(s) was submitted. If You
institute patent litigation against any entity (including a
cross-claim or counterclaim in a lawsuit) alleging that the Work
or a Contribution incorporated within the Work constitutes direct
or contributory patent infringement, then any patent licenses
granted to You under this License for that Work shall terminate
as of the date such litigation is filed.
4. Redistribution. You may reproduce and distribute copies of the
Work or Derivative Works thereof in any medium, with or without
modifications, and in Source or Object form, provided that You
meet the following conditions:
(a) You must give any other recipients of the Work or
Derivative Works a copy of this License; and
(b) You must cause any modified files to carry prominent notices
stating that You changed the files; and
(c) You must retain, in the Source form of any Derivative Works
that You distribute, all copyright, patent, trademark, and
attribution notices from the Source form of the Work,
excluding those notices that do not pertain to any part of
the Derivative Works; and
(d) If the Work includes a "NOTICE" text file as part of its
distribution, then any Derivative Works that You distribute must
include a readable copy of the attribution notices contained
within such NOTICE file, excluding those notices that do not
pertain to any part of the Derivative Works, in at least one
of the following places: within a NOTICE text file distributed
as part of the Derivative Works; within the Source form or
documentation, if provided along with the Derivative Works; or,
within a display generated by the Derivative Works, if and
wherever such third-party notices normally appear. The contents
of the NOTICE file are for informational purposes only and
do not modify the License. You may add Your own attribution
notices within Derivative Works that You distribute, alongside
or as an addendum to the NOTICE text from the Work, provided
that such additional attribution notices cannot be construed
as modifying the License.
You may add Your own copyright statement to Your modifications and
may provide additional or different license terms and conditions
for use, reproduction, or distribution of Your modifications, or
for any such Derivative Works as a whole, provided Your use,
reproduction, and distribution of the Work otherwise complies with
the conditions stated in this License.
5. Submission of Contributions. Unless You explicitly state otherwise,
any Contribution intentionally submitted for inclusion in the Work
by You to the Licensor shall be under the terms and conditions of
this License, without any additional terms or conditions.
Notwithstanding the above, nothing herein shall supersede or modify
the terms of any separate license agreement you may have executed
with Licensor regarding such Contributions.
6. Trademarks. This License does not grant permission to use the trade
names, trademarks, service marks, or product names of the Licensor,
except as required for reasonable and customary use in describing the
origin of the Work and reproducing the content of the NOTICE file.
7. Disclaimer of Warranty. Unless required by applicable law or
agreed to in writing, Licensor provides the Work (and each
Contributor provides its Contributions) on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
implied, including, without limitation, any warranties or conditions
of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A
PARTICULAR PURPOSE. You are solely responsible for determining the
appropriateness of using or redistributing the Work and assume any
risks associated with Your exercise of permissions under this License.
8. Limitation of Liability. In no event and under no legal theory,
whether in tort (including negligence), contract, or otherwise,
unless required by applicable law (such as deliberate and grossly
negligent acts) or agreed to in writing, shall any Contributor be
liable to You for damages, including any direct, indirect, special,
incidental, or consequential damages of any character arising as a
result of this License or out of the use or inability to use the
Work (including but not limited to damages for loss of goodwill,
work stoppage, computer failure or malfunction, or any and all
other commercial damages or losses), even if such Contributor
has been advised of the possibility of such damages.
9. Accepting Warranty or Additional Liability. While redistributing
the Work or Derivative Works thereof, You may choose to offer,
and charge a fee for, acceptance of support, warranty, indemnity,
or other liability obligations and/or rights consistent with this
License. However, in accepting such obligations, You may act only
on Your own behalf and on Your sole responsibility, not on behalf
of any other Contributor, and only if You agree to indemnify,
defend, and hold each Contributor harmless for any liability
incurred by, or claims asserted against, such Contributor by reason
of your accepting any such warranty or additional liability.
END OF TERMS AND CONDITIONS
APPENDIX: How to apply the Apache License to your work.
To apply the Apache License to your work, attach the following
boilerplate notice, with the fields enclosed by brackets "[]"
replaced with your own identifying information. (Don't include
the brackets!) The text should be enclosed in the appropriate
comment syntax for the file format. We also recommend that a
file or class name and description of purpose be included on the
same "printed page" as the copyright notice for easier
identification within third-party archives.
Copyright [yyyy] [name of copyright owner]
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.

25
LICENSE-MIT Normal file
View File

@ -0,0 +1,25 @@
Copyright (c) 2023 Vac Research
Permission is hereby granted, free of charge, to any
person obtaining a copy of this software and associated
documentation files (the "Software"), to deal in the
Software without restriction, including without
limitation the rights to use, copy, modify, merge,
publish, distribute, sublicense, and/or sell copies of
the Software, and to permit persons to whom the Software
is furnished to do so, subject to the following
conditions:
The above copyright notice and this permission notice
shall be included in all copies or substantial portions
of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF
ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED
TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A
PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT
SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY
CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR
IN CONNECTION WITH THE SOFTWARE O THE USE OR OTHER
DEALINGS IN THE SOFTWARE.

View File

@ -1,17 +1,32 @@
The [RLN](contracts/Rln.sol) and [PoseidonHasher](contracts/PoseidonHasher.sol) smart contracts are initially borrowed from the following repository https://github.com/kilic/rlnapp and some modifications are made on top of them.
They may undertake further changes in the future as needed.
# Hardhat Project for rln-contract
## Requirements
The following will need to be installed in order to use this repo. Please follow the links and instructions.
- [Git](https://git-scm.com/book/en/v2/Getting-Started-Installing-Git)
- You'll know you've done it right if you can run `git --version`
- [Foundry / Foundryup](https://github.com/gakonst/foundry)
- This will install `forge`, `cast`, and `anvil`
- You can test you've installed them right by running `forge --version` and get an output like: `forge 0.2.0 (92f8951 2022-08-06T00:09:32.96582Z)`
- To get the latest of each, just run `foundryup`
- [Yarn](https://classic.yarnpkg.com/lang/en/docs/install)
# Hardhat Project for Rln-membership-contract
## Compilation
```shell
npx hardhat compile
yarn compile
```
## Testing
## Testing with Hardhat
```shell
npx hardhat test
yarn test:hardhat
```
## Testing with Foundry
```shell
yarn test:foundry
```
## Deploying
@ -19,15 +34,19 @@ npx hardhat test
- To deploy on local node, first start the local node and then run the deploy script
```shell
npx hardhat node
npx hardhat run --network localhost scripts/deploy.ts
yarn node
npx deploy:localhost
```
- To deploy to an target network (like Goerli), use the name as mentioned in the Hardhat config file.
- To deploy to an target network (like Sepolia), use the name as mentioned in the Hardhat config file.
```shell
npx hardhat run --network <your-network> scripts/deploy.js
yarn deploy:sepolia
```
## References
For more information, see https://hardhat.org/hardhat-runner/docs/guides/project-setup
For more information, see https://hardhat.org/hardhat-runner/docs/guides/project-setup
## License
Dual-licensed under MIT or Apache 2.0, refer to [LICENSE-MIT](LICENSE-MIT) or [LICENSE-APACHE](LICENSE-APACHE) for more information.

File diff suppressed because it is too large Load Diff

View File

@ -1,95 +1,126 @@
// SPDX-License-Identifier: MIT
pragma solidity 0.8.15;
import { IPoseidonHasher } from "./PoseidonHasher.sol";
import {IPoseidonHasher} from "./PoseidonHasher.sol";
contract RLN {
uint256 public immutable MEMBERSHIP_DEPOSIT;
uint256 public immutable DEPTH;
uint256 public immutable SET_SIZE;
uint256 public immutable MEMBERSHIP_DEPOSIT;
uint256 public immutable DEPTH;
uint256 public immutable SET_SIZE;
uint256 public pubkeyIndex = 0;
// This mapping is used to keep track of the public keys that have been registered
// with the stake
mapping(uint256 => uint256) public members;
uint256 public idCommitmentIndex;
mapping(uint256 => uint256) public stakedAmounts;
mapping(uint256 => bool) public members;
IPoseidonHasher public poseidonHasher;
IPoseidonHasher public poseidonHasher;
event MemberRegistered(uint256 pubkey, uint256 index);
event MemberWithdrawn(uint256 pubkey);
event MemberRegistered(uint256 idCommitment, uint256 index);
event MemberWithdrawn(uint256 idCommitment);
constructor(
uint256 membershipDeposit,
uint256 depth,
address _poseidonHasher
) {
MEMBERSHIP_DEPOSIT = membershipDeposit;
DEPTH = depth;
SET_SIZE = 1 << depth;
poseidonHasher = IPoseidonHasher(_poseidonHasher);
}
constructor(
uint256 membershipDeposit,
uint256 depth,
address _poseidonHasher
) {
MEMBERSHIP_DEPOSIT = membershipDeposit;
DEPTH = depth;
SET_SIZE = 1 << depth;
poseidonHasher = IPoseidonHasher(_poseidonHasher);
}
function register(uint256 pubkey) external payable {
require(members[pubkey] == 0, "RLN, register: pubkey already registered");
require(pubkeyIndex < SET_SIZE, "RLN, register: set is full");
require(msg.value == MEMBERSHIP_DEPOSIT, "RLN, register: membership deposit is not satisfied");
_register(pubkey);
}
function register(uint256 idCommitment) external payable {
require(
msg.value == MEMBERSHIP_DEPOSIT,
"RLN, register: membership deposit is not satisfied"
);
_register(idCommitment, msg.value);
}
function registerBatch(uint256[] calldata pubkeys) external payable {
uint256 pubkeylen = pubkeys.length;
require(pubkeyIndex + pubkeylen <= SET_SIZE, "RLN, registerBatch: set is full");
require(msg.value == MEMBERSHIP_DEPOSIT * pubkeylen, "RLN, registerBatch: membership deposit is not satisfied");
for (uint256 i = 0; i < pubkeylen; i++) {
_register(pubkeys[i]);
}
}
function registerBatch(uint256[] calldata idCommitments) external payable {
uint256 idCommitmentlen = idCommitments.length;
require(idCommitmentlen > 0, "RLN, registerBatch: batch size zero");
require(
idCommitmentIndex + idCommitmentlen <= SET_SIZE,
"RLN, registerBatch: set is full"
);
require(
msg.value == MEMBERSHIP_DEPOSIT * idCommitmentlen,
"RLN, registerBatch: membership deposit is not satisfied"
);
for (uint256 i = 0; i < idCommitmentlen; i++) {
_register(idCommitments[i], msg.value / idCommitmentlen);
}
}
function _register(uint256 pubkey) internal {
// Set the pubkey to the value of the tx
members[pubkey] = msg.value;
emit MemberRegistered(pubkey, pubkeyIndex);
pubkeyIndex += 1;
}
function _register(uint256 idCommitment, uint256 stake) internal {
require(
!members[idCommitment],
"RLN, _register: member already registered"
);
require(idCommitmentIndex < SET_SIZE, "RLN, register: set is full");
function withdrawBatch(
uint256[] calldata secrets,
address payable[] calldata receivers
) external {
uint256 batchSize = secrets.length;
require(batchSize != 0, "RLN, withdrawBatch: batch size zero");
require(batchSize == receivers.length, "RLN, withdrawBatch: batch size mismatch receivers");
for (uint256 i = 0; i < batchSize; i++) {
_withdraw(secrets[i], receivers[i]);
}
}
members[idCommitment] = true;
stakedAmounts[idCommitment] = stake;
function withdraw(
uint256 secret,
address payable receiver
) external {
_withdraw(secret, receiver);
}
emit MemberRegistered(idCommitment, idCommitmentIndex);
idCommitmentIndex += 1;
}
function _withdraw(
uint256 secret,
address payable receiver
) internal {
// derive public key
uint256 pubkey = hash(secret);
require(members[pubkey] != 0, "RLN, _withdraw: member doesn't exist");
require(receiver != address(0), "RLN, _withdraw: empty receiver address");
// refund deposit
(bool sent, bytes memory data) = receiver.call{value: members[pubkey]}("");
require(sent, "transfer failed");
function withdrawBatch(
uint256[] calldata secrets,
address payable[] calldata receivers
) external {
uint256 batchSize = secrets.length;
require(batchSize != 0, "RLN, withdrawBatch: batch size zero");
require(
batchSize == receivers.length,
"RLN, withdrawBatch: batch size mismatch receivers"
);
for (uint256 i = 0; i < batchSize; i++) {
_withdraw(secrets[i], receivers[i]);
}
}
// delete member only if refund is successful
members[pubkey] = 0;
function withdraw(uint256 secret, address payable receiver) external {
_withdraw(secret, receiver);
}
emit MemberWithdrawn(pubkey);
}
function _withdraw(uint256 secret, address payable receiver) internal {
require(
receiver != address(0),
"RLN, _withdraw: empty receiver address"
);
function hash(uint256 input) internal view returns (uint256) {
return poseidonHasher.hash(input);
}
}
require(
receiver != address(this),
"RLN, _withdraw: cannot withdraw to RLN"
);
// derive idCommitment
uint256 idCommitment = hash(secret);
// check if member is registered
require(members[idCommitment], "RLN, _withdraw: member not registered");
// check if member has stake
require(
stakedAmounts[idCommitment] != 0,
"RLN, _withdraw: member has no stake"
);
uint256 amountToTransfer = stakedAmounts[idCommitment];
// delete member
members[idCommitment] = false;
stakedAmounts[idCommitment] = 0;
// refund deposit
receiver.transfer(amountToTransfer);
emit MemberWithdrawn(idCommitment);
}
function hash(uint256 input) internal view returns (uint256) {
return poseidonHasher.hash(input);
}
}

View File

@ -0,0 +1,16 @@
import { HardhatRuntimeEnvironment } from "hardhat/types";
import { DeployFunction } from "hardhat-deploy/types";
const func: DeployFunction = async function (hre: HardhatRuntimeEnvironment) {
const { deployments, getUnnamedAccounts } = hre;
const { deploy } = deployments;
const [deployer] = await getUnnamedAccounts();
await deploy("PoseidonHasher", {
from: deployer,
log: true,
});
};
export default func;
func.tags = ["PoseidonHasher"];

21
deploy/002_deploy_rln.ts Normal file
View File

@ -0,0 +1,21 @@
import { HardhatRuntimeEnvironment } from "hardhat/types";
import { DeployFunction } from "hardhat-deploy/types";
const func: DeployFunction = async function (hre: HardhatRuntimeEnvironment) {
const { deployments, getUnnamedAccounts } = hre;
const { deploy } = deployments;
const [deployer] = await getUnnamedAccounts();
const poseidonHasherAddress = (await deployments.get("PoseidonHasher"))
.address;
await deploy("RLN", {
from: deployer,
log: true,
args: [1000000000000000, 20, poseidonHasherAddress],
});
};
export default func;
func.tags = ["RLN"];
func.dependencies = ["PoseidonHasher"];

View File

@ -0,0 +1,283 @@
{
"11155111": [
{
"name": "sepolia",
"chainId": "11155111",
"contracts": {
"PoseidonHasher": {
"address": "0xfa72Ce89D60d085B8FdB445fF6edC47475D58d1E",
"abi": [
{
"inputs": [
{
"internalType": "uint256",
"name": "input",
"type": "uint256"
}
],
"name": "hash",
"outputs": [
{
"internalType": "uint256",
"name": "result",
"type": "uint256"
}
],
"stateMutability": "pure",
"type": "function"
},
{
"inputs": [],
"name": "identity",
"outputs": [
{
"internalType": "uint256",
"name": "",
"type": "uint256"
}
],
"stateMutability": "pure",
"type": "function"
}
]
},
"RLN": {
"address": "0x4B11778822690DefA80934B3203C170ed6B5d317",
"abi": [
{
"inputs": [
{
"internalType": "uint256",
"name": "membershipDeposit",
"type": "uint256"
},
{
"internalType": "uint256",
"name": "depth",
"type": "uint256"
},
{
"internalType": "address",
"name": "_poseidonHasher",
"type": "address"
}
],
"stateMutability": "nonpayable",
"type": "constructor"
},
{
"anonymous": false,
"inputs": [
{
"indexed": false,
"internalType": "uint256",
"name": "idCommitment",
"type": "uint256"
},
{
"indexed": false,
"internalType": "uint256",
"name": "index",
"type": "uint256"
}
],
"name": "MemberRegistered",
"type": "event"
},
{
"anonymous": false,
"inputs": [
{
"indexed": false,
"internalType": "uint256",
"name": "idCommitment",
"type": "uint256"
}
],
"name": "MemberWithdrawn",
"type": "event"
},
{
"inputs": [],
"name": "DEPTH",
"outputs": [
{
"internalType": "uint256",
"name": "",
"type": "uint256"
}
],
"stateMutability": "view",
"type": "function"
},
{
"inputs": [],
"name": "MEMBERSHIP_DEPOSIT",
"outputs": [
{
"internalType": "uint256",
"name": "",
"type": "uint256"
}
],
"stateMutability": "view",
"type": "function"
},
{
"inputs": [],
"name": "SET_SIZE",
"outputs": [
{
"internalType": "uint256",
"name": "",
"type": "uint256"
}
],
"stateMutability": "view",
"type": "function"
},
{
"inputs": [],
"name": "idCommitmentIndex",
"outputs": [
{
"internalType": "uint256",
"name": "",
"type": "uint256"
}
],
"stateMutability": "view",
"type": "function"
},
{
"inputs": [
{
"internalType": "uint256",
"name": "",
"type": "uint256"
}
],
"name": "members",
"outputs": [
{
"internalType": "bool",
"name": "",
"type": "bool"
}
],
"stateMutability": "view",
"type": "function"
},
{
"inputs": [],
"name": "poseidonHasher",
"outputs": [
{
"internalType": "contract IPoseidonHasher",
"name": "",
"type": "address"
}
],
"stateMutability": "view",
"type": "function"
},
{
"inputs": [
{
"internalType": "uint256",
"name": "idCommitment",
"type": "uint256"
}
],
"name": "register",
"outputs": [],
"stateMutability": "payable",
"type": "function"
},
{
"inputs": [
{
"internalType": "uint256[]",
"name": "idCommitments",
"type": "uint256[]"
}
],
"name": "registerBatch",
"outputs": [],
"stateMutability": "payable",
"type": "function"
},
{
"inputs": [
{
"internalType": "uint256",
"name": "",
"type": "uint256"
}
],
"name": "stakedAmounts",
"outputs": [
{
"internalType": "uint256",
"name": "",
"type": "uint256"
}
],
"stateMutability": "view",
"type": "function"
},
{
"inputs": [
{
"internalType": "uint256",
"name": "secret",
"type": "uint256"
},
{
"internalType": "address payable",
"name": "receiver",
"type": "address"
}
],
"name": "withdraw",
"outputs": [],
"stateMutability": "nonpayable",
"type": "function"
},
{
"inputs": [
{
"internalType": "uint256",
"name": "secret",
"type": "uint256"
}
],
"name": "withdraw",
"outputs": [],
"stateMutability": "nonpayable",
"type": "function"
},
{
"inputs": [
{
"internalType": "uint256[]",
"name": "secrets",
"type": "uint256[]"
},
{
"internalType": "address payable[]",
"name": "receivers",
"type": "address[]"
}
],
"name": "withdrawBatch",
"outputs": [],
"stateMutability": "nonpayable",
"type": "function"
}
]
}
}
}
]
}

View File

@ -0,0 +1 @@
11155111

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

6
foundry.toml Normal file
View File

@ -0,0 +1,6 @@
[profile.default]
src = 'contracts'
out = 'out'
libs = ['node_modules', 'lib']
test = 'test'
cache_path = 'cache_forge'

View File

@ -1,35 +1,54 @@
import * as dotenv from "dotenv";
import { HardhatUserConfig, task } from "hardhat/config";
import { HardhatUserConfig } from "hardhat/config";
import { NetworksUserConfig } from "hardhat/types";
import "@nomicfoundation/hardhat-foundry";
import "hardhat-deploy";
import "@nomiclabs/hardhat-etherscan";
import "@nomiclabs/hardhat-ethers";
import "@nomiclabs/hardhat-waffle";
import "hardhat-gas-reporter";
import "solidity-coverage";
dotenv.config();
const {GOERLI_URL,PRIVATE_KEY} = process.env;
const { SEPOLIA_URL, PRIVATE_KEY, ETHERSCAN_API_KEY } = process.env;
// This is a sample Hardhat task. To learn how to create your own go to
// https://hardhat.org/guides/create-task.html
task("accounts", "Prints the list of accounts", async (taskArgs, hre) => {
const accounts = await hre.ethers.getSigners();
for (const account of accounts) {
console.log(account.address);
const getNetworkConfig = (): NetworksUserConfig | undefined => {
if (SEPOLIA_URL && PRIVATE_KEY) {
return {
sepolia: {
url: SEPOLIA_URL,
accounts: [PRIVATE_KEY],
forking: {
url: SEPOLIA_URL,
},
verify: {
etherscan: {
apiKey: ETHERSCAN_API_KEY,
apiUrl: "https://api-sepolia.etherscan.io",
},
},
},
localhost_integration: {
url: "http://localhost:8545",
},
};
}
});
return undefined;
};
// You need to export an object to set up your config
// Go to https://hardhat.org/config/ to learn more
const config: HardhatUserConfig = {
solidity: "0.8.15",
networks: {
goerli: {
url: GOERLI_URL,
accounts: [`${PRIVATE_KEY}`]
}
}
solidity: {
compilers: [
{
version: "0.8.15",
},
],
},
networks: getNetworkConfig(),
};
export default config;
export default config;

1
lib/forge-std Submodule

@ -0,0 +1 @@
Subproject commit 2b58ecbcf3dfde7a75959dc7b4eb3d0670278de6

36830
package-lock.json generated

File diff suppressed because it is too large Load Diff

View File

@ -1,20 +1,49 @@
{
"name": "hardhat-project",
"name": "rln-contract",
"license": "(MIT OR Apache-2.0)",
"scripts": {
"start": "hardhat node --export-all deployments/allDeployments.json",
"compile": "hardhat compile",
"test": "yarn test:foundry && yarn test:hardhat",
"test:verbose": "yarn test:foundry -vvv && yarn test:hardhat --verbose",
"test:hardhat": "hardhat test",
"test:hardhat:localhost": "yarn test:hardhat --network localhost",
"test:hardhat:sepolia": "yarn test:hardhat --network sepolia",
"test:foundry": "forge test",
"deploy": "hardhat deploy --export-all deployments/allDeployments.json --network",
"deploy:sepolia": "yarn deploy sepolia",
"deploy:localhost": "yarn deploy localhost",
"verify:sepolia": "hardhat --network sepolia etherscan-verify",
"coverage": "forge coverage --report lcov",
"fmt": "prettier --write \"**/*.{js,ts}\"",
"lint": "prettier --check \"**/*.{js,ts}\"",
"prepare": "husky install"
},
"devDependencies": {
"@nomiclabs/hardhat-ethers": "^2.0.6",
"@nomiclabs/hardhat-etherscan": "^3.1.0",
"@nomicfoundation/hardhat-foundry": "^1.0.0",
"@nomiclabs/hardhat-ethers": "npm:hardhat-deploy-ethers",
"@nomiclabs/hardhat-etherscan": "^3.1.7",
"@nomiclabs/hardhat-waffle": "^2.0.3",
"@types/chai": "^4.3.4",
"@types/mocha": "^9.1.1",
"@types/node": "^16.11.6",
"chai": "^4.3.6",
"ethereum-waffle": "^3.4.4",
"ethers": "^5.6.9",
"ethers": "^5.7.2",
"hardhat": "^2.9.9",
"hardhat-deploy": "0.11.20",
"hardhat-gas-reporter": "^1.0.8",
"solidity-coverage": "^0.7.21",
"husky": "^8.0.2",
"lint-staged": "^13.0.3",
"ts-node": "^10.8.1",
"typescript": "^4.7.4"
},
"dependencies": {
"dotenv": "^16.0.1"
},
"lint-staged": {
"**/*": [
"prettier --write --ignore-unknown"
]
}
}

View File

@ -1,37 +0,0 @@
// We require the Hardhat Runtime Environment explicitly here. This is optional
// but useful for running the script in a standalone fashion through `node <script>`.
//
// When running the script with `npx hardhat run <script>` you'll find the Hardhat
// Runtime Environment's members available in the global scope.
import { ethers } from "hardhat";
async function main() {
// Hardhat always runs the compile task when running scripts with its command
// line interface.
//
// If this script is run directly using `node` you may want to call compile
// manually to make sure everything is compiled
// await hre.run('compile');
// We get the contract to deploy
const PoseidonHasher = await ethers.getContractFactory("PoseidonHasher");
const poseidonHasher = await PoseidonHasher.deploy();
await poseidonHasher.deployed();
console.log("PoseidonHasher deployed to:", poseidonHasher.address);
const Rln = await ethers.getContractFactory("RLN");
const rln = await Rln.deploy(1000000000000000,20,poseidonHasher.address);
await rln.deployed();
console.log("Rln deployed to:", rln.address);
}
// We recommend this pattern to be able to use async/await everywhere
// and properly handle errors.
main().catch((error) => {
console.error(error);
process.exitCode = 1;
});

28
test/PoseidonHasher.t.sol Normal file
View File

@ -0,0 +1,28 @@
// SPDX-License-Identifier: Unlicense
pragma solidity ^0.8.15;
import "../contracts/PoseidonHasher.sol";
import "forge-std/Test.sol";
contract PoseidonHasherTest is Test {
PoseidonHasher public poseidon;
/// @dev Setup the testing environment.
function setUp() public {
poseidon = new PoseidonHasher();
}
/// @dev Ensure that you can hash a value.
function testHasher(uint256 value) public {
assertEq(poseidon.hash(value), poseidon.hash(value));
}
function testHasher() public {
assertEq(
poseidon.hash(
19014214495641488759237505126948346942972912379615652741039992445865937985820
),
0x0c3ac305f6a4fe9bfeb3eba978bc876e2a99208b8b56c80160cfb54ba8f02368
);
}
}

298
test/RLN.t.sol Normal file
View File

@ -0,0 +1,298 @@
// SPDX-License-Identifier: Unlicense
pragma solidity ^0.8.15;
import "../contracts/PoseidonHasher.sol";
import "../contracts/Rln.sol";
import "forge-std/Test.sol";
import "forge-std/StdCheats.sol";
import "forge-std/console.sol";
contract ArrayUnique {
mapping(uint256 => bool) seen;
constructor(uint256[] memory arr) {
for (uint256 i = 0; i < arr.length; i++) {
require(!seen[arr[i]], "ArrayUnique: duplicate value");
seen[arr[i]] = true;
}
}
// contract in construction goes around the assumePayable() check
receive() external payable {}
}
function repeatElementIntoArray(
uint256 length,
address payable element
) pure returns (address payable[] memory) {
address payable[] memory arr = new address payable[](length);
for (uint256 i = 0; i < length; i++) {
arr[i] = element;
}
return arr;
}
contract RLNTest is Test {
using stdStorage for StdStorage;
RLN public rln;
PoseidonHasher public poseidon;
uint256 public constant MEMBERSHIP_DEPOSIT = 1000000000000000;
uint256 public constant DEPTH = 20;
uint256 public constant SET_SIZE = 1048576;
/// @dev Setup the testing environment.
function setUp() public {
poseidon = new PoseidonHasher();
rln = new RLN(MEMBERSHIP_DEPOSIT, DEPTH, address(poseidon));
}
function isUniqueArray(uint256[] memory arr) internal returns (bool) {
try new ArrayUnique(arr) {
return true;
} catch {
return false;
}
}
/// @dev Ensure that you can hash a value.
function test__Constants() public {
assertEq(rln.MEMBERSHIP_DEPOSIT(), MEMBERSHIP_DEPOSIT);
assertEq(rln.DEPTH(), DEPTH);
assertEq(rln.SET_SIZE(), SET_SIZE);
}
function test__ValidRegistration(uint256 idCommitment) public {
rln.register{value: MEMBERSHIP_DEPOSIT}(idCommitment);
assertEq(rln.stakedAmounts(idCommitment), MEMBERSHIP_DEPOSIT);
assertEq(rln.members(idCommitment), true);
}
function test__InvalidRegistration__DuplicateCommitment(
uint256 idCommitment
) public {
rln.register{value: MEMBERSHIP_DEPOSIT}(idCommitment);
assertEq(rln.stakedAmounts(idCommitment), MEMBERSHIP_DEPOSIT);
assertEq(rln.members(idCommitment), true);
// TODO: use custom errors instead of revert strings
vm.expectRevert(bytes("RLN, _register: member already registered"));
rln.register{value: MEMBERSHIP_DEPOSIT}(idCommitment);
}
function test__InvalidRegistration__InsufficientDeposit(
uint256 idCommitment
) public {
vm.expectRevert(
bytes("RLN, register: membership deposit is not satisfied")
);
rln.register{value: MEMBERSHIP_DEPOSIT - 1}(idCommitment);
}
function test__InvalidRegistration__FullSet(
uint256 idCommitmentSeed
) public {
vm.assume(idCommitmentSeed < 2 ** 255 - SET_SIZE);
RLN tempRln = new RLN(
MEMBERSHIP_DEPOSIT,
2,
address(rln.poseidonHasher())
);
uint256 setSize = tempRln.SET_SIZE();
for (uint256 i = 0; i < setSize; i++) {
tempRln.register{value: MEMBERSHIP_DEPOSIT}(idCommitmentSeed + i);
}
assertEq(tempRln.idCommitmentIndex(), 4);
vm.expectRevert(bytes("RLN, register: set is full"));
tempRln.register{value: MEMBERSHIP_DEPOSIT}(idCommitmentSeed + setSize);
}
function test__ValidBatchRegistration(
uint256[] calldata idCommitments
) public {
// assume that the array is unique, otherwise it triggers
// a revert that has already been tested
vm.assume(isUniqueArray(idCommitments) && idCommitments.length > 0);
uint256 idCommitmentlen = idCommitments.length;
rln.registerBatch{value: MEMBERSHIP_DEPOSIT * idCommitmentlen}(
idCommitments
);
for (uint256 i = 0; i < idCommitmentlen; i++) {
assertEq(rln.stakedAmounts(idCommitments[i]), MEMBERSHIP_DEPOSIT);
assertEq(rln.members(idCommitments[i]), true);
}
}
function test__InvalidBatchRegistration__FullSet(
uint256 idCommitmentSeed
) public {
vm.assume(idCommitmentSeed < 2 ** 255 - SET_SIZE);
RLN tempRln = new RLN(MEMBERSHIP_DEPOSIT, 2, address(poseidon));
uint256 setSize = tempRln.SET_SIZE();
for (uint256 i = 0; i < setSize; i++) {
tempRln.register{value: MEMBERSHIP_DEPOSIT}(idCommitmentSeed + i);
}
assertEq(tempRln.idCommitmentIndex(), 4);
uint256[] memory idCommitments = new uint256[](1);
idCommitments[0] = idCommitmentSeed + setSize;
vm.expectRevert(bytes("RLN, registerBatch: set is full"));
tempRln.registerBatch{value: MEMBERSHIP_DEPOSIT}(idCommitments);
}
function test__InvalidBatchRegistration__EmptyBatch() public {
uint256[] memory idCommitments = new uint256[](0);
vm.expectRevert(bytes("RLN, registerBatch: batch size zero"));
rln.registerBatch{value: MEMBERSHIP_DEPOSIT}(idCommitments);
}
function test__InvalidBatchRegistration__InsufficientDeposit(
uint256[] calldata idCommitments
) public {
vm.assume(isUniqueArray(idCommitments) && idCommitments.length > 0);
uint256 idCommitmentlen = idCommitments.length;
vm.expectRevert(
bytes("RLN, registerBatch: membership deposit is not satisfied")
);
rln.registerBatch{value: MEMBERSHIP_DEPOSIT * idCommitmentlen - 1}(
idCommitments
);
}
function test__ValidWithdraw(
uint256 idSecretHash,
address payable to
) public {
// avoid precompiles, etc
// TODO: wrap both of these in a single function
assumePayable(to);
assumeNoPrecompiles(to);
vm.assume(to != address(0));
uint256 idCommitment = poseidon.hash(idSecretHash);
rln.register{value: MEMBERSHIP_DEPOSIT}(idCommitment);
assertEq(rln.stakedAmounts(idCommitment), MEMBERSHIP_DEPOSIT);
uint256 balanceBefore = to.balance;
rln.withdraw(idSecretHash, to);
assertEq(rln.stakedAmounts(idCommitment), 0);
assertEq(rln.members(idCommitment), false);
assertEq(to.balance, balanceBefore + MEMBERSHIP_DEPOSIT);
}
function test__InvalidWithdraw__ToZeroAddress() public {
uint256 idSecretHash = 19014214495641488759237505126948346942972912379615652741039992445865937985820;
uint256 idCommitment = poseidon.hash(idSecretHash);
rln.register{value: MEMBERSHIP_DEPOSIT}(idCommitment);
assertEq(rln.stakedAmounts(idCommitment), MEMBERSHIP_DEPOSIT);
vm.expectRevert(bytes("RLN, _withdraw: empty receiver address"));
rln.withdraw(idSecretHash, payable(address(0)));
}
function test__InvalidWithdraw__ToRlnAddress() public {
uint256 idSecretHash = 19014214495641488759237505126948346942972912379615652741039992445865937985820;
uint256 idCommitment = poseidon.hash(idSecretHash);
rln.register{value: MEMBERSHIP_DEPOSIT}(idCommitment);
assertEq(rln.stakedAmounts(idCommitment), MEMBERSHIP_DEPOSIT);
vm.expectRevert(bytes("RLN, _withdraw: cannot withdraw to RLN"));
rln.withdraw(idSecretHash, payable(address(rln)));
}
function test__InvalidWithdraw__InvalidIdCommitment(
uint256 idCommitment
) public {
vm.expectRevert(bytes("RLN, _withdraw: member not registered"));
rln.withdraw(idCommitment, payable(address(this)));
}
// this shouldn't be possible, but just in case
function test__InvalidWithdraw__NoStake(
uint256 idSecretHash,
address payable to
) public {
// avoid precompiles, etc
assumePayable(to);
assumeNoPrecompiles(to);
vm.assume(to != address(0));
uint256 idCommitment = poseidon.hash(idSecretHash);
rln.register{value: MEMBERSHIP_DEPOSIT}(idCommitment);
assertEq(rln.stakedAmounts(idCommitment), MEMBERSHIP_DEPOSIT);
rln.withdraw(idSecretHash, to);
assertEq(rln.stakedAmounts(idCommitment), 0);
assertEq(rln.members(idCommitment), false);
// manually set members[idCommitment] to true using vm
stdstore
.target(address(rln))
.sig("members(uint256)")
.with_key(idCommitment)
.depth(0)
.checked_write(true);
vm.expectRevert(bytes("RLN, _withdraw: member has no stake"));
rln.withdraw(idSecretHash, to);
}
function test__ValidBatchWithdraw(
uint256[] calldata idSecretHashes,
address payable to
) public {
// avoid precompiles, etc
assumePayable(to);
assumeNoPrecompiles(to);
vm.assume(isUniqueArray(idSecretHashes) && idSecretHashes.length > 0);
vm.assume(to != address(0));
uint256 idCommitmentlen = idSecretHashes.length;
uint256[] memory idCommitments = new uint256[](idCommitmentlen);
for (uint256 i = 0; i < idCommitmentlen; i++) {
idCommitments[i] = poseidon.hash(idSecretHashes[i]);
}
rln.registerBatch{value: MEMBERSHIP_DEPOSIT * idCommitmentlen}(
idCommitments
);
for (uint256 i = 0; i < idCommitmentlen; i++) {
assertEq(rln.stakedAmounts(idCommitments[i]), MEMBERSHIP_DEPOSIT);
}
uint256 balanceBefore = to.balance;
rln.withdrawBatch(
idSecretHashes,
repeatElementIntoArray(idSecretHashes.length, to)
);
for (uint256 i = 0; i < idCommitmentlen; i++) {
assertEq(rln.stakedAmounts(idCommitments[i]), 0);
assertEq(rln.members(idCommitments[i]), false);
}
assertEq(
to.balance,
balanceBefore + MEMBERSHIP_DEPOSIT * idCommitmentlen
);
}
function test__InvalidBatchWithdraw__EmptyBatch() public {
uint256[] memory idSecretHashes = new uint256[](0);
address payable[] memory to = new address payable[](0);
vm.expectRevert(bytes("RLN, withdrawBatch: batch size zero"));
rln.withdrawBatch(idSecretHashes, to);
}
function test__InvalidBatchWithdraw__MismatchInputSize(
uint256[] calldata idSecretHashes,
address payable to
) public {
assumePayable(to);
assumeNoPrecompiles(to);
vm.assume(isUniqueArray(idSecretHashes) && idSecretHashes.length > 0);
vm.assume(to != address(0));
vm.expectRevert(
bytes("RLN, withdrawBatch: batch size mismatch receivers")
);
rln.withdrawBatch(
idSecretHashes,
repeatElementIntoArray(idSecretHashes.length + 1, to)
);
}
}

View File

@ -1,74 +0,0 @@
import { assert, expect } from "chai";
import { ethers } from "hardhat";
describe("Rln", function () {
it("Deploying", async function () {
const PoseidonHasher = await ethers.getContractFactory("PoseidonHasher");
const poseidonHasher = await PoseidonHasher.deploy();
await poseidonHasher.deployed();
console.log("PoseidonHasher deployed to:", poseidonHasher.address);
const Rln = await ethers.getContractFactory("RLN");
const rln = await Rln.deploy(1000000000000000, 20, poseidonHasher.address);
await rln.deployed();
console.log("Rln deployed to:", rln.address);
const price = await rln.MEMBERSHIP_DEPOSIT();
// A valid pair of (id_secret, id_commitment) generated in rust
const id_secret = "0x2a09a9fd93c590c26b91effbb2499f07e8f7aa12e2b4940a3aed2411cb65e11c"
const id_commitment = "0x0c3ac305f6a4fe9bfeb3eba978bc876e2a99208b8b56c80160cfb54ba8f02368"
const res_register = await rln.register(id_commitment, {value: price});
const txRegisterReceipt = await res_register.wait();
const reg_pubkey = txRegisterReceipt.events[0].args.pubkey;
const reg_tree_index = txRegisterReceipt.events[0].args.index;
// We ensure the registered id_commitment is the one we passed
assert(reg_pubkey.toHexString() === id_commitment, "registered commitment doesn't match passed commitment");
// We withdraw our id_commitment
const receiver_address = "0x000000000000000000000000000000000000dead";
const res_withdraw = await rln.withdraw(id_secret, receiver_address);
const txWithdrawReceipt = await res_withdraw.wait();
const wit_pubkey = txWithdrawReceipt.events[0].args.pubkey;
// We ensure the registered id_commitment is the one we passed and that the index is the same
assert(wit_pubkey.toHexString() === id_commitment, "withdraw commitment doesn't match registered commitment");
const pubkeyIndex = (await rln.pubkeyIndex()).toNumber();
assert(pubkeyIndex === 1, "pubkeyIndex should be 1");
});
it("should not allow dupe registrations", async () => {
const PoseidonHasher = await ethers.getContractFactory("PoseidonHasher");
const poseidonHasher = await PoseidonHasher.deploy();
await poseidonHasher.deployed();
console.log("PoseidonHasher deployed to:", poseidonHasher.address);
const Rln = await ethers.getContractFactory("RLN");
const rln = await Rln.deploy(1000000000000000, 20, poseidonHasher.address);
await rln.deployed();
console.log("Rln deployed to:", rln.address);
const price = await rln.MEMBERSHIP_DEPOSIT();
// A valid id_commitment generated in rust
const id_commitment = "0x0c3ac305f6a4fe9bfeb3eba978bc876e2a99208b8b56c80160cfb54ba8f02368"
await rln.register(id_commitment, {value: price});
expect(rln.register(id_commitment, {value: price})).to.be.revertedWith("RLN, register: pubkey already registered");
});
});

21
test/poseidon.test.ts Normal file
View File

@ -0,0 +1,21 @@
import { expect } from "chai";
import { ethers, deployments } from "hardhat";
describe("PoseidonHasher", () => {
beforeEach(async () => {
await deployments.fixture(["PoseidonHasher"]);
});
it("should hash correctly", async function () {
const poseidonHasher = await ethers.getContract("PoseidonHasher");
// We test hashing for a random number
const hash = await poseidonHasher.hash(
"19014214495641488759237505126948346942972912379615652741039992445865937985820"
);
expect(hash._hex).to.eql(
"0x0c3ac305f6a4fe9bfeb3eba978bc876e2a99208b8b56c80160cfb54ba8f02368"
);
});
});

View File

@ -1,21 +0,0 @@
import { expect } from "chai";
import { ethers } from "hardhat";
describe("Rln", function () {
it("Deploying", async function () {
const PoseidonHasher = await ethers.getContractFactory("PoseidonHasher");
const poseidonHasher = await PoseidonHasher.deploy();
await poseidonHasher.deployed();
console.log("PoseidonHasher deployed to:", poseidonHasher.address);
// We test hashing for a random number
const hash = await poseidonHasher.hash("19014214495641488759237505126948346942972912379615652741039992445865937985820");
console.log("Hash:", hash);
//Expect 0x0c3ac305f6a4fe9bfeb3eba978bc876e2a99208b8b56c80160cfb54ba8f02368
});
});

89
test/rln.test.ts Normal file
View File

@ -0,0 +1,89 @@
import { expect } from "chai";
import { ethers, deployments } from "hardhat";
describe("RLN", () => {
beforeEach(async () => {
await deployments.fixture(["RLN"]);
});
it("should register new memberships", async () => {
const rln = await ethers.getContract("RLN", ethers.provider.getSigner(0));
const price = await rln.MEMBERSHIP_DEPOSIT();
// A valid pair of (id_secret, id_commitment) generated in rust
const idCommitment =
"0x0c3ac305f6a4fe9bfeb3eba978bc876e2a99208b8b56c80160cfb54ba8f02368";
const registerTx = await rln["register(uint256)"](idCommitment, {
value: price,
});
const txRegisterReceipt = await registerTx.wait();
const pubkey = txRegisterReceipt.events[0].args.idCommitment;
// We ensure the registered id_commitment is the one we passed
expect(
pubkey.toHexString() === idCommitment,
"registered commitment doesn't match passed commitment"
);
});
it("should withdraw membership", async () => {
const rln = await ethers.getContract("RLN", ethers.provider.getSigner(0));
const price = await rln.MEMBERSHIP_DEPOSIT();
// A valid pair of (id_secret, id_commitment) generated in rust
const idSecret =
"0x2a09a9fd93c590c26b91effbb2499f07e8f7aa12e2b4940a3aed2411cb65e11c";
const idCommitment =
"0x0c3ac305f6a4fe9bfeb3eba978bc876e2a99208b8b56c80160cfb54ba8f02368";
const registerTx = await rln["register(uint256)"](idCommitment, {
value: price,
});
await registerTx.wait();
// We withdraw our id_commitment
const receiverAddress = "0x000000000000000000000000000000000000dead";
const withdrawTx = await rln["withdraw(uint256,address)"](
idSecret,
receiverAddress
);
const txWithdrawReceipt = await withdrawTx.wait();
const withdrawalPk = txWithdrawReceipt.events[0].args.idCommitment;
// We ensure the registered id_commitment is the one we passed and that the index is the same
expect(
withdrawalPk.toHexString() === idCommitment,
"withdraw commitment doesn't match registered commitment"
);
});
it("should not allow multiple registrations with same pubkey", async () => {
const rln = await ethers.getContract("RLN", ethers.provider.getSigner(0));
const price = await rln.MEMBERSHIP_DEPOSIT();
// A valid pair of (id_secret, id_commitment) generated in rust
const idCommitment =
"0x0c3ac305f6a4fe9bfeb3eba978bc876e2a99208b8b56c80160cfb54ba8f02368";
const registerTx = await rln["register(uint256)"](idCommitment, {
value: price,
});
await registerTx.wait();
// Send the same tx again
const registerTx2 = rln["register(uint256)"](idCommitment, {
value: price,
});
await expect(registerTx2).to.be.revertedWith(
"RLN, _register: member already registered"
);
});
});

View File

@ -7,6 +7,13 @@
"outDir": "dist",
"declaration": true
},
"include": ["./scripts", "./test", "./typechain"],
"files": ["./hardhat.config.ts"]
}
"include": [
"./scripts",
"./test",
"./typechain",
"deploy"
],
"files": [
"./hardhat.config.ts"
]
}

9297
yarn.lock Normal file

File diff suppressed because it is too large Load Diff