add publicDeposit logic

This commit is contained in:
Drygin 2022-06-22 19:49:29 +03:00
parent f9264eeffe
commit 84cf86c4d2
7 changed files with 123 additions and 0 deletions

View File

@ -99,6 +99,7 @@ template Transaction(levels, nIns, nOuts, zeroLeaf) {
outCommitmentHasher[tx].out === outputCommitment[tx];
// Check that amount fits into 248 bits to prevent overflow
// make sure that that limit the same as in TornadoPool.sol:publicDeposit()
outAmountCheck[tx] = Num2Bits(248);
outAmountCheck[tx].in <== outAmount[tx];

View File

@ -17,6 +17,7 @@ module.exports = {
verifier16: '0x743494b60097a2230018079c02fe21a7b687eaa5',
MERKLE_TREE_HEIGHT: 23,
hasher: '0x94c92f096437ab9958fc0a37f09348f30389ae79',
hasher3: '0x0000000000000000000000000000000000000000', // TODO
gcWeth: '0x6a023ccd1ff6f2045c3309768ead9e68f978f6e1',
gcOmniBridge: '0xf6a78083ca3e2a662d6dd1703c939c8ace2e268d',
l1Unwrapper: '0x3F615bA21Bc6Cc5D4a6D798c5950cc5c42937fbd',

View File

@ -17,6 +17,7 @@ import "@openzeppelin/contracts/utils/ReentrancyGuard.sol";
import { IERC20Receiver, IERC6777, IOmniBridge } from "./interfaces/IBridge.sol";
import { CrossChainGuard } from "./bridge/CrossChainGuard.sol";
import { IVerifier } from "./interfaces/IVerifier.sol";
import { IHasher3 } from "./interfaces/IHasher3.sol";
import "./MerkleTreeWithHistory.sol";
/** @dev This contract(pool) allows deposit of an arbitrary amount to it, shielded transfer to another registered user inside the pool
@ -29,6 +30,7 @@ contract TornadoPool is MerkleTreeWithHistory, IERC20Receiver, ReentrancyGuard,
IVerifier public immutable verifier2;
IVerifier public immutable verifier16;
IHasher3 public immutable hasher3;
IERC6777 public immutable token;
address public immutable omniBridge;
address public immutable l1Unwrapper;
@ -79,6 +81,7 @@ contract TornadoPool is MerkleTreeWithHistory, IERC20Receiver, ReentrancyGuard,
@param _verifier16 the address of SNARK verifier for 16 inputs
@param _levels hight of the commitments merkle tree
@param _hasher hasher address for the merkle tree
@param _hasher3 hasher address for the commitment
@param _token token address for the pool
@param _omniBridge omniBridge address for specified token
@param _l1Unwrapper address of the L1Helper
@ -91,6 +94,7 @@ contract TornadoPool is MerkleTreeWithHistory, IERC20Receiver, ReentrancyGuard,
IVerifier _verifier16,
uint32 _levels,
address _hasher,
address _hasher3,
IERC6777 _token,
address _omniBridge,
address _l1Unwrapper,
@ -103,6 +107,7 @@ contract TornadoPool is MerkleTreeWithHistory, IERC20Receiver, ReentrancyGuard,
{
verifier2 = _verifier2;
verifier16 = _verifier16;
hasher3 = IHasher3(_hasher3);
token = _token;
omniBridge = _omniBridge;
l1Unwrapper = _l1Unwrapper;
@ -126,6 +131,30 @@ contract TornadoPool is MerkleTreeWithHistory, IERC20Receiver, ReentrancyGuard,
_transact(_args, _extData);
}
/** @dev Function that allows public deposits without proof verification.
*/
function publicDeposit(bytes32 pubkey, uint256 depositAmount) public payable {
require(depositAmount <= maximumDepositAmount, "amount is larger than maximumDepositAmount");
// make sure that that limit the same as in transaction.circom output check
require(depositAmount < 2**248, "depositAmount should be inside the field");
require(uint256(pubkey) < FIELD_SIZE, "pubkey should be inside the field");
token.transferFrom(msg.sender, address(this), depositAmount);
bytes32[3] memory input;
input[0] = bytes32(depositAmount);
input[1] = pubkey;
input[2] = bytes32(0);
bytes32 commitment = hasher3.poseidon(input);
bytes memory packedOutput = abi.encodePacked("abi", depositAmount, pubkey);
lastBalance = token.balanceOf(address(this));
_insert(commitment, bytes32(ZERO_VALUE)); // use second empty commitment
emit NewCommitment(commitment, nextIndex - 2, packedOutput);
emit NewCommitment(bytes32(ZERO_VALUE), nextIndex - 1, new bytes(0));
}
function register(Account memory _account) public {
require(_account.owner == msg.sender, "only owner can be registered");
_register(_account);

View File

@ -0,0 +1,6 @@
// SPDX-License-Identifier: MIT
pragma solidity ^0.7.0;
interface IHasher3 {
function poseidon(bytes32[3] calldata inputs) external pure returns (bytes32);
}

22
scripts/compileHasher3.js Normal file
View File

@ -0,0 +1,22 @@
// Generates Hasher artifact at compile-time using external compilermechanism
const path = require('path')
const fs = require('fs')
const genContract = require('circomlib/src/poseidon_gencontract.js')
const outputPath = path.join(__dirname, '..', 'artifacts', 'contracts')
const outputFile = path.join(outputPath, 'Hasher3.json')
if (!fs.existsSync(outputPath)) {
fs.mkdirSync(outputPath, { recursive: true })
}
const contract = {
_format: 'hh-sol-artifact-1',
sourceName: 'contracts/Hasher.sol',
linkReferences: {},
deployedLinkReferences: {},
contractName: 'Hasher3',
abi: genContract.generateABI(3),
bytecode: genContract.createCode(3),
}
fs.writeFileSync(outputFile, JSON.stringify(contract, null, 2))

View File

@ -24,6 +24,7 @@ async function generate(config = defaultConfig) {
config.verifier16,
config.MERKLE_TREE_HEIGHT,
config.hasher,
config.hasher3,
config.gcWeth,
config.gcOmniBridge,
config.l1Unwrapper,

View File

@ -3,6 +3,7 @@ const { ethers, waffle } = hre
const { loadFixture } = waffle
const { expect } = require('chai')
const { utils } = ethers
const { BigNumber } = require('@ethersproject/bignumber')
const Utxo = require('../src/utxo')
const { transaction, registerAndTransact, prepareTransaction, buildMerkleTree } = require('../src/index')
@ -27,10 +28,12 @@ describe('TornadoPool', function () {
async function fixture() {
require('../scripts/compileHasher')
require('../scripts/compileHasher3')
const [sender, gov, multisig] = await ethers.getSigners()
const verifier2 = await deploy('Verifier2')
const verifier16 = await deploy('Verifier16')
const hasher = await deploy('Hasher')
const hasher3 = await deploy('Hasher3')
const token = await deploy('PermittableToken', 'Wrapped ETH', 'WETH', 18, l1ChainId)
await token.mint(sender.address, utils.parseEther('10000'))
@ -59,6 +62,7 @@ describe('TornadoPool', function () {
verifier16.address,
MERKLE_TREE_HEIGHT,
hasher.address,
hasher3.address,
token.address,
omniBridge.address,
l1Unwrapper.address,
@ -175,6 +179,65 @@ describe('TornadoPool', function () {
expect(registerEvent.args.key).to.be.equal(aliceDepositUtxo.keypair.address())
})
it('should public deposit', async function () {
let { tornadoPool, token } = await loadFixture(fixture)
const sender = (await ethers.getSigners())[0]
tornadoPool = tornadoPool.connect(sender)
// Alice deposits into tornado pool
const aliceDepositAmount = utils.parseEther('0.1')
const aliceKeypair = new Keypair()
const alicePubkey = aliceKeypair.address().slice(0, 66)
await expect(() => tornadoPool.publicDeposit(alicePubkey, aliceDepositAmount)).to.changeTokenBalances(
token,
[sender, tornadoPool],
[BigNumber.from(0).sub(aliceDepositAmount), aliceDepositAmount],
)
const filter = tornadoPool.filters.NewCommitment()
let fromBlock = await ethers.provider.getBlock()
let events = await tornadoPool.queryFilter(filter, fromBlock.number)
const packedOutput = utils.solidityPack(
['string', 'uint256', 'bytes32'],
['abi', aliceDepositAmount, alicePubkey],
)
expect(events[0].args.encryptedOutput).to.be.equal(packedOutput)
const aliceDepositUtxo = new Utxo({
amount: aliceDepositAmount,
keypair: aliceKeypair,
blinding: 0,
index: 0,
})
// Bob gives Alice address to send some eth inside the shielded pool
const bobKeypair = new Keypair() // contains private and public keys
const bobAddress = bobKeypair.address() // contains only public key
// Alice sends some funds to Bob
const bobSendAmount = utils.parseEther('0.06')
const bobSendUtxo = new Utxo({ amount: bobSendAmount, keypair: Keypair.fromString(bobAddress) })
const aliceChangeUtxo = new Utxo({
amount: aliceDepositAmount.sub(bobSendAmount),
keypair: aliceDepositUtxo.keypair,
})
await transaction({ tornadoPool, inputs: [aliceDepositUtxo], outputs: [bobSendUtxo, aliceChangeUtxo] })
// Bob parses chain to detect incoming funds
fromBlock = await ethers.provider.getBlock()
events = await tornadoPool.queryFilter(filter, fromBlock.number)
let bobReceiveUtxo
try {
bobReceiveUtxo = Utxo.decrypt(bobKeypair, events[0].args.encryptedOutput, events[0].args.index)
} catch (e) {
// we try to decrypt another output here because it shuffles outputs before sending to blockchain
bobReceiveUtxo = Utxo.decrypt(bobKeypair, events[1].args.encryptedOutput, events[1].args.index)
}
expect(bobReceiveUtxo.amount).to.be.equal(bobSendAmount)
})
it('should deposit, transact and withdraw', async function () {
const { tornadoPool, token } = await loadFixture(fixture)