Merge pull request #10 from tornadocash/optimism

Optimism updates
This commit is contained in:
Alexey Pertsev 2021-09-07 13:03:42 +03:00 committed by GitHub
commit 38fb94b52f
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
16 changed files with 1050 additions and 890 deletions

View File

@ -17,7 +17,7 @@
"rules": { "rules": {
"indent": ["error", 2], "indent": ["error", 2],
"linebreak-style": ["error", "unix"], "linebreak-style": ["error", "unix"],
"quotes": ["error", "single"], "quotes": ["error", "single", { "avoidEscape": true }],
"semi": ["error", "never"], "semi": ["error", "never"],
"object-curly-spacing": ["error", "always"], "object-curly-spacing": ["error", "always"],
"comma-dangle": ["error", "always-multiline"], "comma-dangle": ["error", "always-multiline"],

View File

@ -4,7 +4,6 @@ on:
push: push:
branches: ['*'] branches: ['*']
tags: ['v[0-9]+.[0-9]+.[0-9]+'] tags: ['v[0-9]+.[0-9]+.[0-9]+']
pull_request:
jobs: jobs:
build: build:
@ -15,7 +14,8 @@ jobs:
- uses: actions/setup-node@v1 - uses: actions/setup-node@v1
with: with:
node-version: 14 node-version: 14
- run: yarn install - run: yarn cache clean --all
- run: yarn install --network-concurrency 1
- run: yarn lint - run: yarn lint
- run: yarn download - run: yarn download
- run: yarn build - run: yarn build

2
.gitignore vendored
View File

@ -3,4 +3,6 @@ node_modules
build build
cache cache
artifacts artifacts
artifacts-ovm
cache-ovm
src/types src/types

View File

@ -1,6 +1,8 @@
.vscode .vscode
.idea .idea
artifacts artifacts
artifacts-ovm
cache cache
cache-ovm
contracts/Verifier*.sol contracts/Verifier*.sol
src/types src/types

View File

@ -4,6 +4,7 @@
```shell ```shell
yarn yarn
yarn download
yarn build yarn build
yarn test yarn test
``` ```
@ -11,3 +12,11 @@ yarn test
TODO TODO
1. deposit from mainnet to the pool on optimism in one tx 1. deposit from mainnet to the pool on optimism in one tx
## Useful
How we do transaction inside pool of A amount.
1. sort inputs by amount
2. try to take 1 or 2 smallest inputs to satisfy A amount. Get 16 inputs if it's not possible using the same way
3. Also you can always use transaction to merge your inputs with change (especially in 16 inputs case)

9
TODO
View File

@ -1,9 +0,0 @@
* relayer
* design
* race condition ? (sequencer or something)
* the current trusted setup is not secure ?
How we do transaction inside pool of A amount.
1. sort inputs by amount
2. try to take 1 or 2 smallest inputs to satisfy A amount. Get 16 inputs if it's not possible using the same way
3. Also you can always use transaction to merge your inputs with change (especially in 16 inputs case)

View File

@ -130,6 +130,5 @@ template Transaction(levels, nIns, nOuts, zeroLeaf) {
treeUpdater.pathElements[i] <== outPathElements[i]; treeUpdater.pathElements[i] <== outPathElements[i];
} }
signal extDataSquare; signal extDataSquare <== extDataHash * extDataHash;
extDataSquare <== extDataHash * extDataHash;
} }

View File

@ -0,0 +1,40 @@
// SPDX-License-Identifier: MIT
pragma solidity ^0.7.0;
import "@openzeppelin/contracts/contracts/proxy/TransparentUpgradeableProxy.sol";
// https://github.com/ethereum-optimism/optimism/blob/c7bc85deee999b8edfbe187b302d0ea262638ca9/packages/contracts/contracts/optimistic-ethereum/iOVM/bridge/messaging/iOVM_CrossDomainMessenger.sol
interface iOVM_CrossDomainMessenger {
function xDomainMessageSender() external view returns (address);
}
/**
* @dev TransparentUpgradeableProxy where admin acts from a different chain.
*/
contract CrossChainUpgradeableProxy is TransparentUpgradeableProxy {
// https://github.com/ethereum-optimism/optimism/blob/develop/packages/contracts/deployments/README.md
iOVM_CrossDomainMessenger public immutable messenger;
/**
* @dev Initializes an upgradeable proxy backed by the implementation at `_logic`.
*/
constructor(
address _logic,
address _admin,
bytes memory _data,
iOVM_CrossDomainMessenger _messenger
) TransparentUpgradeableProxy(_logic, _admin, _data) {
messenger = _messenger;
}
/**
* @dev Modifier used internally that will delegate the call to the implementation unless the sender is the cross chain admin.
*/
modifier ifAdmin() override {
if (msg.sender == address(messenger) && messenger.xDomainMessageSender() == _admin()) {
_;
} else {
_fallback();
}
}
}

View File

@ -0,0 +1,14 @@
// SPDX-License-Identifier: MIT
pragma solidity ^0.7.0;
contract MockOVM_CrossDomainMessenger {
address public xDomainMessageSender;
constructor(address _xDomainMessageSender) {
xDomainMessageSender = _xDomainMessageSender;
}
function execute(address _who, bytes calldata _calldata) external returns (bool success, bytes memory result) {
(success, result) = _who.call(_calldata);
}
}

View File

@ -12,6 +12,7 @@
pragma solidity ^0.7.0; pragma solidity ^0.7.0;
pragma experimental ABIEncoderV2; pragma experimental ABIEncoderV2;
import "@openzeppelin/contracts-upgradeable/proxy/Initializable.sol";
interface IVerifier { interface IVerifier {
function verifyProof(bytes memory _proof, uint256[9] memory _input) external view returns (bool); function verifyProof(bytes memory _proof, uint256[9] memory _input) external view returns (bool);
@ -19,7 +20,11 @@ interface IVerifier {
function verifyProof(bytes memory _proof, uint256[23] memory _input) external view returns (bool); function verifyProof(bytes memory _proof, uint256[23] memory _input) external view returns (bool);
} }
contract TornadoPool { interface ERC20 {
function transfer(address to, uint256 value) external returns (bool);
}
contract TornadoPool is Initializable {
uint256 public constant FIELD_SIZE = 21888242871839275222246405745257275088548364400416034343698204186575808495617; uint256 public constant FIELD_SIZE = 21888242871839275222246405745257275088548364400416034343698204186575808495617;
int256 public constant MAX_EXT_AMOUNT = 2**248; int256 public constant MAX_EXT_AMOUNT = 2**248;
uint256 public constant MAX_FEE = 2**248; uint256 public constant MAX_FEE = 2**248;
@ -65,13 +70,12 @@ contract TornadoPool {
@param _verifier2 the address of SNARK verifier for 2 inputs @param _verifier2 the address of SNARK verifier for 2 inputs
@param _verifier16 the address of SNARK verifier for 16 inputs @param _verifier16 the address of SNARK verifier for 16 inputs
*/ */
constructor( constructor(IVerifier _verifier2, IVerifier _verifier16) {
IVerifier _verifier2,
IVerifier _verifier16,
bytes32 _currentRoot
) {
verifier2 = _verifier2; verifier2 = _verifier2;
verifier16 = _verifier16; verifier16 = _verifier16;
}
function initialize(bytes32 _currentRoot) external initializer {
currentRoot = _currentRoot; currentRoot = _currentRoot;
} }
@ -97,13 +101,13 @@ contract TornadoPool {
} else if (_extData.extAmount < 0) { } else if (_extData.extAmount < 0) {
require(msg.value == 0, "Sent ETH amount should be 0 for withdrawal"); require(msg.value == 0, "Sent ETH amount should be 0 for withdrawal");
require(_extData.recipient != address(0), "Can't withdraw to zero address"); require(_extData.recipient != address(0), "Can't withdraw to zero address");
_extData.recipient.transfer(uint256(-_extData.extAmount)); _transfer(_extData.recipient, uint256(-_extData.extAmount));
} else { } else {
require(msg.value == 0, "Sent ETH amount should be 0 for transaction"); require(msg.value == 0, "Sent ETH amount should be 0 for transaction");
} }
if (_extData.fee > 0) { if (_extData.fee > 0) {
_extData.relayer.transfer(_extData.fee); _transfer(_extData.relayer, _extData.fee);
} }
emit NewCommitment(_args.outputCommitments[0], cachedCommitmentIndex, _extData.encryptedOutput1); emit NewCommitment(_args.outputCommitments[0], cachedCommitmentIndex, _extData.encryptedOutput1);
@ -113,6 +117,18 @@ contract TornadoPool {
} }
} }
function _transfer(address payable _to, uint256 _amount) internal {
uint256 id;
assembly {
id := chainid()
}
if (id == 10) {
ERC20(0x4200000000000000000000000000000000000006).transfer(_to, _amount);
} else {
_to.transfer(_amount);
}
}
function calculatePublicAmount(int256 _extAmount, uint256 _fee) public pure returns (uint256) { function calculatePublicAmount(int256 _extAmount, uint256 _fee) public pure returns (uint256) {
require(_fee < MAX_FEE, "Invalid fee"); require(_fee < MAX_FEE, "Invalid fee");
require(_extAmount > -MAX_EXT_AMOUNT && _extAmount < MAX_EXT_AMOUNT, "Invalid ext amount"); require(_extAmount > -MAX_EXT_AMOUNT && _extAmount < MAX_EXT_AMOUNT, "Invalid ext amount");

View File

@ -1,6 +1,8 @@
/* eslint-disable indent */
require('@typechain/hardhat') require('@typechain/hardhat')
require('@nomiclabs/hardhat-ethers') require('@nomiclabs/hardhat-ethers')
require('@nomiclabs/hardhat-waffle') require('@nomiclabs/hardhat-waffle')
require('@eth-optimism/hardhat-ovm')
require('dotenv').config() require('dotenv').config()
const config = { const config = {
@ -13,6 +15,9 @@ const config = {
}, },
}, },
}, },
ovm: {
solcVersion: '0.7.6+commit.3b061308',
},
networks: { networks: {
// goerli: { // goerli: {
// url: process.env.ETH_RPC, // url: process.env.ETH_RPC,
@ -22,6 +27,19 @@ const config = {
// mnemonic: 'test test test test test test test test test test test junk', // mnemonic: 'test test test test test test test test test test test junk',
// }, // },
// }, // },
optimism: {
url: process.env.ETH_RPC || 'https://mainnet.optimism.io',
accounts: process.env.PRIVATE_KEY
? [process.env.PRIVATE_KEY]
: {
mnemonic: 'test test test test test test test test test test test junk',
},
// This sets the gas price to 0 for all transactions on L2. We do this
// because account balances are not automatically initiated with an ETH
// balance (yet, sorry!).
gasPrice: 15000000,
ovm: true, // This sets the network as using the ovm and ensure contract will be compiled against that.
},
}, },
mocha: { mocha: {
timeout: 600000000, timeout: 600000000,

View File

@ -10,6 +10,7 @@
"circuit": "./scripts/buildCircuit.sh 2 && ./scripts/buildCircuit.sh 16", "circuit": "./scripts/buildCircuit.sh 2 && ./scripts/buildCircuit.sh 16",
"compile": "npx hardhat compile", "compile": "npx hardhat compile",
"build": "npm run circuit && npm run compile", "build": "npm run circuit && npm run compile",
"deploy": "npx hardhat run scripts/deploy.js --network optimism",
"download": "curl -L https://github.com/tornadocash/tornado-pool/releases/download/tmp/ptau15 --create-dirs -o artifacts/circuits/ptau15", "download": "curl -L https://github.com/tornadocash/tornado-pool/releases/download/tmp/ptau15 --create-dirs -o artifacts/circuits/ptau15",
"test": "npx hardhat test", "test": "npx hardhat test",
"eslint": "eslint --ext .js --ignore-path .gitignore .", "eslint": "eslint --ext .js --ignore-path .gitignore .",
@ -23,7 +24,8 @@
"dependencies": { "dependencies": {
"@nomiclabs/hardhat-ethers": "^2.0.2", "@nomiclabs/hardhat-ethers": "^2.0.2",
"@nomiclabs/hardhat-waffle": "^2.0.1", "@nomiclabs/hardhat-waffle": "^2.0.1",
"@openzeppelin/contracts": "^3.4.0", "@openzeppelin/contracts": "git+https://github.com/tornadocash/openzeppelin-contracts.git#6e46aa6946a7f215e7604169ddf46e1aebea850f",
"@openzeppelin/contracts-upgradeable": "3.4.1",
"@typechain/ethers-v5": "^7.0.1", "@typechain/ethers-v5": "^7.0.1",
"@typechain/hardhat": "^2.3.0", "@typechain/hardhat": "^2.3.0",
"bignumber.js": "^9.0.0", "bignumber.js": "^9.0.0",
@ -45,6 +47,7 @@
"typechain": "^5.1.2" "typechain": "^5.1.2"
}, },
"devDependencies": { "devDependencies": {
"@eth-optimism/hardhat-ovm": "^0.2.2",
"babel-eslint": "^10.1.0", "babel-eslint": "^10.1.0",
"eslint": "^7.28.0", "eslint": "^7.28.0",
"eslint-config-prettier": "^8.3.0", "eslint-config-prettier": "^8.3.0",

View File

@ -1,6 +1,6 @@
const { ethers } = require('hardhat') const { ethers } = require('hardhat')
const MERKLE_TREE_HEIGHT = 5 const MERKLE_TREE_HEIGHT = 32
const MerkleTree = require('fixed-merkle-tree') const MerkleTree = require('fixed-merkle-tree')
const { poseidon } = require('circomlib') const { poseidon } = require('circomlib')
const poseidonHash = (items) => ethers.BigNumber.from(poseidon(items).toString()) const poseidonHash = (items) => ethers.BigNumber.from(poseidon(items).toString())

View File

@ -1,3 +1,4 @@
/* global network */
const crypto = require('crypto') const crypto = require('crypto')
const { ethers } = require('hardhat') const { ethers } = require('hardhat')
const BigNumber = ethers.BigNumber const BigNumber = ethers.BigNumber
@ -76,6 +77,15 @@ function shuffle(array) {
return array return array
} }
async function getSignerFromAddress(address) {
await network.provider.request({
method: 'hardhat_impersonateAccount',
params: [address],
})
return await ethers.provider.getSigner(address)
}
module.exports = { module.exports = {
FIELD_SIZE, FIELD_SIZE,
randomBN, randomBN,
@ -85,4 +95,5 @@ module.exports = {
poseidonHash2, poseidonHash2,
getExtDataHash, getExtDataHash,
shuffle, shuffle,
getSignerFromAddress,
} }

View File

@ -14,6 +14,7 @@ const { Keypair } = require('../src/keypair')
describe('TornadoPool', function () { describe('TornadoPool', function () {
this.timeout(20000) this.timeout(20000)
let gov, messenger
async function deploy(contractName, ...args) { async function deploy(contractName, ...args) {
const Factory = await ethers.getContractFactory(contractName) const Factory = await ethers.getContractFactory(contractName)
@ -22,21 +23,57 @@ describe('TornadoPool', function () {
} }
async function fixture() { async function fixture() {
;[, gov] = await ethers.getSigners()
const verifier2 = await deploy('Verifier2') const verifier2 = await deploy('Verifier2')
const verifier16 = await deploy('Verifier16') const verifier16 = await deploy('Verifier16')
const tree = new MerkleTree(MERKLE_TREE_HEIGHT, [], { hashFunction: poseidonHash2 }) const tree = new MerkleTree(MERKLE_TREE_HEIGHT, [], { hashFunction: poseidonHash2 })
/** @type {TornadoPool} */ const root = await tree.root()
const tornadoPool = await deploy(
'TornadoPool',
verifier2.address,
verifier16.address,
toFixedHex(tree.root()),
)
const Pool = await ethers.getContractFactory('TornadoPool')
const tornadoPoolImpl = await Pool.deploy(verifier2.address, verifier16.address)
const OVM_Messenger = await ethers.getContractFactory('MockOVM_CrossDomainMessenger')
messenger = await OVM_Messenger.deploy(gov.address)
await messenger.deployed()
const CrossChainUpgradeableProxy = await ethers.getContractFactory('CrossChainUpgradeableProxy')
const proxy = await CrossChainUpgradeableProxy.deploy(
tornadoPoolImpl.address,
gov.address,
[],
messenger.address,
)
await proxy.deployed()
/** @type {TornadoPool} */
const tornadoPool = Pool.attach(proxy.address)
await tornadoPool.initialize(toFixedHex(root))
return { tornadoPool } return { tornadoPool }
} }
describe('Upgradeability tests', () => {
let tornadoPool, proxy
before(async () => {
;({ tornadoPool } = await loadFixture(fixture))
const CrossChainUpgradeableProxy = await ethers.getContractFactory('CrossChainUpgradeableProxy')
proxy = CrossChainUpgradeableProxy.attach(tornadoPool.address)
})
it('admin should be gov', async () => {
const { data } = await proxy.populateTransaction.admin()
const { result } = await messenger.callStatic.execute(proxy.address, data)
expect('0x' + result.slice(26)).to.be.equal(gov.address.toLowerCase())
})
it('non admin cannot call', async () => {
await expect(proxy.admin()).to.be.revertedWith(
"Transaction reverted: function selector was not recognized and there's no fallback function",
)
})
})
it('encrypt -> decrypt should work', () => { it('encrypt -> decrypt should work', () => {
const data = Buffer.from([0xff, 0xaa, 0x00, 0x01]) const data = Buffer.from([0xff, 0xaa, 0x00, 0x01])
const keypair = new Keypair() const keypair = new Keypair()
@ -57,7 +94,7 @@ describe('TornadoPool', function () {
it('should register and deposit', async function () { it('should register and deposit', async function () {
let { tornadoPool } = await loadFixture(fixture) let { tornadoPool } = await loadFixture(fixture)
const sender = (await ethers.getSigners())[1] const sender = (await ethers.getSigners())[0]
// Alice deposits into tornado pool // Alice deposits into tornado pool
const aliceDepositAmount = 1e7 const aliceDepositAmount = 1e7

1732
yarn.lock

File diff suppressed because it is too large Load Diff