Compare commits

...

22 Commits

Author SHA1 Message Date
Alexey Pertsev f9264eeffe
Merge pull request #23 from tornadocash/l1-fee-from-user
L1 fee from user
2022-03-30 10:36:27 +02:00
Drygin b19bac4d92 fix mainnet rpc 2022-02-17 01:06:29 +03:00
Drygin 29a804fb93 lint fix 2022-02-16 23:28:15 +03:00
Drygin 6fa861ce8f encode fix 2022-02-16 23:26:27 +03:00
Drygin 995302bd77 fix ternary opr 2022-02-14 18:02:16 +03:00
Drygin d052e51095 add ternary opr + fix deploy addresses 2022-02-14 16:45:51 +03:00
Drygin 0530f00af5 add L1Receiver logic to unwrapper 2022-02-14 15:24:33 +03:00
Drygin abef8ad760 fix linters 2022-02-14 13:34:03 +03:00
Drygin f4df191ed2 fix readme and tests 2022-02-14 13:27:44 +03:00
Drygin bb2e4186bf fix build 2022-02-12 01:00:42 +03:00
Drygin d6919f2797 add create2 deploy 2022-02-11 23:53:36 +03:00
Drygin b37d6d459f test BSC-GC deploy 2022-02-08 19:54:32 +03:00
Drygin cdde55f564 fix omnibridge version 2022-02-08 19:13:15 +03:00
Drygin e21e1c87e7 fix should insert tree test 2022-01-26 16:58:50 +03:00
Drygin 9cecb7b3ec fix MIN_EXT_AMOUNT_LIMIT check 2022-01-26 15:41:30 +03:00
Drygin 448ec8c1ae add MIN_EXT_AMOUNT_LIMIT 2022-01-24 20:16:55 +03:00
Drygin 391e8c090e fix linter 2022-01-24 18:40:45 +03:00
Drygin b615b9b756 fix tests 2022-01-24 18:25:17 +03:00
Drygin bc2433be08 change configureLimits() access to multisig 2022-01-24 17:56:28 +03:00
Drygin 1f1964417a linter fixes 2022-01-22 03:23:33 +03:00
Drygin bd4500d7ff add L1 fee from user 2022-01-22 03:21:34 +03:00
Alexey 084a68ca56
etherscan verification 2021-12-11 11:40:27 +01:00
23 changed files with 664 additions and 85 deletions

View File

@ -4,3 +4,6 @@ XDAI_RPC=https://
BSC_RPC=https://
MINIMUM_WITHDRAWAL_AMOUNT=0.05
MAXIMUM_DEPOSIT_AMOUNT=1
ALCHEMY_KEY=
INFURA_API_KEY=
ETHERSCAN_KEY=

View File

@ -19,6 +19,8 @@ jobs:
- run: yarn lint
- run: yarn build
- run: yarn test
env:
ALCHEMY_KEY: ${{ secrets.ALCHEMY_KEY }}
- name: Telegram Failure Notification
uses: appleboy/telegram-action@0.0.7
if: failure()

1
.gitignore vendored
View File

@ -4,3 +4,4 @@ build
cache
artifacts
src/types
.vscode

View File

@ -7,7 +7,8 @@
"printWidth": 110
}
],
"quotes": ["error", "double"]
"quotes": ["error", "double"],
"compiler-version": ["error", "^0.7.0"]
},
"plugins": ["prettier"]
}

View File

@ -20,3 +20,31 @@ yarn download
yarn build
yarn test
```
## Deploy
Check config.js for actual values.
With `salt` = `0x0000000000000000000000000000000000000000000000000000000047941987` addresses must be:
1. `L1Unwrapper` - `0x3F615bA21Bc6Cc5D4a6D798c5950cc5c42937fbd`
2. `TornadoPool` - `0x0CDD3705aF7979fBe80A64288Ebf8A9Fe1151cE1`
Check addresses with current config:
```shell
yarn compile
node -e 'require("./src/0_generateAddresses").generateWithLog()'
```
Deploy L1Unwrapper:
```shell
npx hardhat run scripts/deployL1Unwrapper.js --network mainnet
```
Deploy TornadoPool Upgrade:
```shell
npx hardhat run scripts/deployTornadoUpgrade.js --network xdai
```

26
config.js Normal file
View File

@ -0,0 +1,26 @@
module.exports = {
//// L1 -------------------
// ETH
multisig: '0xb04E030140b30C27bcdfaafFFA98C57d80eDa7B4',
omniBridge: '0x88ad09518695c6c3712AC10a214bE5109a655671',
weth: '0xC02aaA39b223FE8D0A0e5C4F27eAD9083C756Cc2',
// BSC
// multisig: '0xBAE5aBfa98466Dbe68836763B087f2d189f4D28f'
// omniBridge: '0xf0b456250dc9990662a6f25808cc74a6d1131ea9'
// weth: '0xbb4CdB9CBd36B01bD1cBaEBF2De08d9173bc095c' // WBNB
singletonFactory: '0xce0042B868300000d44A59004Da54A005ffdcf9f',
salt: '0x0000000000000000000000000000000000000000000000000000000047941987',
//// L2 -------------------
// Gnosis chain
verifier2: '0xdf3a408c53e5078af6e8fb2a85088d46ee09a61b',
verifier16: '0x743494b60097a2230018079c02fe21a7b687eaa5',
MERKLE_TREE_HEIGHT: 23,
hasher: '0x94c92f096437ab9958fc0a37f09348f30389ae79',
gcWeth: '0x6a023ccd1ff6f2045c3309768ead9e68f978f6e1',
gcOmniBridge: '0xf6a78083ca3e2a662d6dd1703c939c8ace2e268d',
l1Unwrapper: '0x3F615bA21Bc6Cc5D4a6D798c5950cc5c42937fbd',
govAddress: '0x5efda50f22d34f262c29268506c5fa42cb56a1ce',
l1ChainId: 1,
gcMultisig: '0x1f727de610030a88863d7da45bdea4eb84655b52',
}

19
contracts/Mocks/WETH.sol Normal file
View File

@ -0,0 +1,19 @@
// SPDX-License-Identifier: MIT
pragma solidity ^0.7.0;
import "@openzeppelin/contracts/token/ERC20/ERC20.sol";
import "@openzeppelin/contracts/access/Ownable.sol";
contract WETH is ERC20 {
constructor(string memory name, string memory ticker) ERC20(name, ticker) {}
function deposit() external payable {
_mint(msg.sender, msg.value);
}
function withdraw(uint256 value) external {
_burn(msg.sender, value);
(bool success, ) = msg.sender.call{ value: value }("");
require(success, "WETH: ETH transfer failed");
}
}

View File

@ -25,6 +25,7 @@ import "./MerkleTreeWithHistory.sol";
contract TornadoPool is MerkleTreeWithHistory, IERC20Receiver, ReentrancyGuard, CrossChainGuard {
int256 public constant MAX_EXT_AMOUNT = 2**248;
uint256 public constant MAX_FEE = 2**248;
uint256 public constant MIN_EXT_AMOUNT_LIMIT = 0.5 ether;
IVerifier public immutable verifier2;
IVerifier public immutable verifier16;
@ -34,7 +35,7 @@ contract TornadoPool is MerkleTreeWithHistory, IERC20Receiver, ReentrancyGuard,
address public immutable multisig;
uint256 public lastBalance;
uint256 public minimalWithdrawalAmount;
uint256 public __gap; // storage padding to prevent storage collision
uint256 public maximumDepositAmount;
mapping(bytes32 => bool) public nullifierHashes;
@ -46,6 +47,7 @@ contract TornadoPool is MerkleTreeWithHistory, IERC20Receiver, ReentrancyGuard,
bytes encryptedOutput1;
bytes encryptedOutput2;
bool isL1Withdrawal;
uint256 l1Fee;
}
struct Proof {
@ -66,11 +68,6 @@ contract TornadoPool is MerkleTreeWithHistory, IERC20Receiver, ReentrancyGuard,
event NewNullifier(bytes32 nullifier);
event PublicKey(address indexed owner, bytes key);
modifier onlyGovernance() {
require(isCalledByOwner(), "only governance");
_;
}
modifier onlyMultisig() {
require(msg.sender == multisig, "only governance");
_;
@ -112,8 +109,8 @@ contract TornadoPool is MerkleTreeWithHistory, IERC20Receiver, ReentrancyGuard,
multisig = _multisig;
}
function initialize(uint256 _minimalWithdrawalAmount, uint256 _maximumDepositAmount) external initializer {
_configureLimits(_minimalWithdrawalAmount, _maximumDepositAmount);
function initialize(uint256 _maximumDepositAmount) external initializer {
_configureLimits(_maximumDepositAmount);
super._initialize();
}
@ -191,8 +188,8 @@ contract TornadoPool is MerkleTreeWithHistory, IERC20Receiver, ReentrancyGuard,
}
}
function configureLimits(uint256 _minimalWithdrawalAmount, uint256 _maximumDepositAmount) public onlyGovernance {
_configureLimits(_minimalWithdrawalAmount, _maximumDepositAmount);
function configureLimits(uint256 _maximumDepositAmount) public onlyMultisig {
_configureLimits(_maximumDepositAmount);
}
function calculatePublicAmount(int256 _extAmount, uint256 _fee) public pure returns (uint256) {
@ -275,11 +272,14 @@ contract TornadoPool is MerkleTreeWithHistory, IERC20Receiver, ReentrancyGuard,
if (_extData.extAmount < 0) {
require(_extData.recipient != address(0), "Can't withdraw to zero address");
if (_extData.isL1Withdrawal) {
token.transferAndCall(omniBridge, uint256(-_extData.extAmount), abi.encodePacked(l1Unwrapper, _extData.recipient));
token.transferAndCall(
omniBridge,
uint256(-_extData.extAmount),
abi.encodePacked(l1Unwrapper, abi.encode(_extData.recipient, _extData.l1Fee))
);
} else {
token.transfer(_extData.recipient, uint256(-_extData.extAmount));
}
require(uint256(-_extData.extAmount) >= minimalWithdrawalAmount, "amount is less than minimalWithdrawalAmount"); // prevents ddos attack to Bridge
}
if (_extData.fee > 0) {
token.transfer(_extData.relayer, _extData.fee);
@ -294,8 +294,7 @@ contract TornadoPool is MerkleTreeWithHistory, IERC20Receiver, ReentrancyGuard,
}
}
function _configureLimits(uint256 _minimalWithdrawalAmount, uint256 _maximumDepositAmount) internal {
minimalWithdrawalAmount = _minimalWithdrawalAmount;
function _configureLimits(uint256 _maximumDepositAmount) internal {
maximumDepositAmount = _maximumDepositAmount;
}
}

View File

@ -14,9 +14,19 @@ pragma solidity ^0.7.0;
pragma abicoder v2;
import "omnibridge/contracts/helpers/WETHOmnibridgeRouter.sol";
import "@openzeppelin/contracts/math/SafeMath.sol";
/// @dev Extension for original WETHOmnibridgeRouter that stores TornadoPool account registrations.
contract L1Helper is WETHOmnibridgeRouter {
contract L1Unwrapper is WETHOmnibridgeRouter {
using SafeMath for uint256;
// If this address sets to not zero it receives L1_fee.
// It can be changed by the multisig.
// And should implement fee sharing logic:
// - some part to tx.origin - based on block base fee and can be subsidized
// - store surplus of ETH for future subsidizions
address payable public l1FeeReceiver;
event PublicKey(address indexed owner, bytes key);
struct Account {
@ -61,4 +71,42 @@ contract L1Helper is WETHOmnibridgeRouter {
function _register(Account memory _account) internal {
emit PublicKey(_account.owner, _account.publicKey);
}
/**
* @dev Bridged callback function used for unwrapping received tokens.
* Can only be called by the associated Omnibridge contract.
* @param _token bridged token contract address, should be WETH.
* @param _value amount of bridged/received tokens.
* @param _data extra data passed alongside with relayTokensAndCall on the other side of the bridge.
* Should contain coins receiver address and L1 executer fee amount.
*/
function onTokenBridged(
address _token,
uint256 _value,
bytes memory _data
) external override {
require(_token == address(WETH), "only WETH token");
require(msg.sender == address(bridge), "only from bridge address");
require(_data.length == 64, "incorrect data length");
WETH.withdraw(_value);
(address payable receipient, uint256 l1Fee) = abi.decode(_data, (address, uint256));
AddressHelper.safeSendValue(receipient, _value.sub(l1Fee));
if (l1Fee > 0) {
address payable l1FeeTo = l1FeeReceiver != payable(address(0)) ? l1FeeReceiver : payable(tx.origin);
AddressHelper.safeSendValue(l1FeeTo, l1Fee);
}
}
/**
* @dev Sets l1FeeReceiver address.
* Only contract owner can call this method.
* @param _receiver address of new L1FeeReceiver, address(0) for native tx.origin receiver.
*/
function setL1FeeReceiver(address payable _receiver) external onlyOwner {
l1FeeReceiver = _receiver;
}
}

View File

@ -0,0 +1,28 @@
// SPDX-License-Identifier: MIT
/**
*Submitted for verification at Etherscan.io on 2020-03-30
*/
pragma solidity 0.6.2;
/**
* @title Singleton Factory (EIP-2470)
* @notice Exposes CREATE2 (EIP-1014) to deploy bytecode on deterministic addresses based on initialization code and salt.
* @author Ricardo Guilherme Schmidt (Status Research & Development GmbH)
*/
contract SingletonFactory {
/**
* @notice Deploys `_initCode` using `_salt` for defining the deterministic address.
* @param _initCode Initialization code.
* @param _salt Arbitrary value to modify resulting address.
* @return createdContract Created contract address.
*/
function deploy(bytes memory _initCode, bytes32 _salt) public returns (address payable createdContract) {
assembly {
createdContract := create2(0, add(_initCode, 0x20), mload(_initCode), _salt)
}
}
}
// IV is a value changed to generate the vanity address.
// IV: 6583047

View File

@ -2,6 +2,7 @@
require('@typechain/hardhat')
require('@nomiclabs/hardhat-ethers')
require('@nomiclabs/hardhat-waffle')
require('@nomiclabs/hardhat-etherscan')
require('dotenv').config()
task('hasher', 'Compile Poseidon hasher', () => {
@ -20,6 +21,15 @@ const config = {
},
},
},
{
version: '0.6.2',
settings: {
optimizer: {
enabled: true,
runs: 200,
},
},
},
{
version: '0.7.5',
settings: {
@ -41,6 +51,25 @@ const config = {
],
},
networks: {
hardhat: {
forking: {
url: `https://eth-mainnet.alchemyapi.io/v2/${process.env.ALCHEMY_KEY}`,
blockNumber: 13685625,
},
chainId: 1,
initialBaseFeePerGas: 5,
loggingEnabled: false,
allowUnlimitedContractSize: false,
blockGasLimit: 50000000,
},
rinkeby: {
url: `https://rinkeby.infura.io/v3/${process.env.INFURA_API_KEY}`,
accounts: process.env.PRIVATE_KEY
? [process.env.PRIVATE_KEY]
: {
mnemonic: 'test test test test test test test test test test test junk',
},
},
xdai: {
url: process.env.ETH_RPC || 'https://rpc.xdaichain.com/',
accounts: process.env.PRIVATE_KEY
@ -59,7 +88,7 @@ const config = {
},
},
mainnet: {
url: process.env.ETH_RPC || '',
url: `https://eth-mainnet.alchemyapi.io/v2/${process.env.ALCHEMY_KEY}`,
accounts: process.env.PRIVATE_KEY
? [process.env.PRIVATE_KEY]
: {
@ -67,6 +96,9 @@ const config = {
},
},
},
etherscan: {
apiKey: process.env.ETHERSCAN_KEY,
},
mocha: {
timeout: 600000000,
},

View File

@ -44,13 +44,14 @@
"fixed-merkle-tree": "^0.5.1",
"hardhat": "^2.3.0",
"mocha": "^9.1.0",
"omnibridge": "git+https://github.com/peppersec/omnibridge.git#aa3a970c29752a4da5f3fc7ccf0733783c1acf0b",
"omnibridge": "git+https://github.com/peppersec/omnibridge.git#30081f7a735eb03c9d6821a9617cc28efe71a682",
"prompt-sync": "^4.2.0",
"snarkjs": "git+https://github.com/tornadocash/snarkjs.git#f37f146948f3b28086493e71512006b030588fc2",
"tmp-promise": "^3.0.2",
"typechain": "^5.1.2"
},
"devDependencies": {
"@nomiclabs/hardhat-etherscan": "^2.1.8",
"babel-eslint": "^10.1.0",
"eslint": "^7.28.0",
"eslint-config-prettier": "^8.3.0",

View File

@ -1,21 +0,0 @@
const { ethers } = require('hardhat')
// This script deploys L1Helper to FOREIGN chain (mainnet)
async function main() {
const owner = '0x03Ebd0748Aa4D1457cF479cce56309641e0a98F5'
const omniBridge = '0xf0b456250dc9990662a6f25808cc74a6d1131ea9'
const token = '0xbb4CdB9CBd36B01bD1cBaEBF2De08d9173bc095c' // WBNB
const Helper = await ethers.getContractFactory('L1Helper')
const helper = await Helper.deploy(omniBridge, token, owner)
await helper.deployed()
console.log(`L1Helper address: ${helper.address}`)
}
main()
.then(() => process.exit(0))
.catch((error) => {
console.error(error)
process.exit(1)
})

View File

@ -0,0 +1,28 @@
const { ethers } = require('hardhat')
const config = require('../config')
const { generate } = require('../src/0_generateAddresses')
// This script deploys L1Helper to FOREIGN chain (mainnet)
async function deploy({ address, bytecode, singletonFactory }) {
const contractCode = await ethers.provider.getCode(address)
if (contractCode !== '0x') {
console.log(`Contract ${address} already deployed. Skipping...`)
return
}
await singletonFactory.deploy(bytecode, config.salt, { gasLimit: 3000000 })
}
async function main() {
const singletonFactory = await ethers.getContractAt('SingletonFactory', config.singletonFactory)
const contracts = await generate()
await deploy({ ...contracts.unwrapperContract, singletonFactory })
console.log(`L1 unwrapper contract have been deployed on ${contracts.unwrapperContract.address} address`)
}
main()
.then(() => process.exit(0))
.catch((error) => {
console.error(error)
process.exit(1)
})

View File

@ -1,18 +1,19 @@
const { ethers } = require('hardhat')
const { utils } = ethers
const prompt = require('prompt-sync')()
// const prompt = require('prompt-sync')()
const MERKLE_TREE_HEIGHT = 23
const { MINIMUM_WITHDRAWAL_AMOUNT, MAXIMUM_DEPOSIT_AMOUNT } = process.env
async function main() {
require('./compileHasher')
const govAddress = '0x03ebd0748aa4d1457cf479cce56309641e0a98f5'
const govAddress = '0xBAE5aBfa98466Dbe68836763B087f2d189f4D28f'
const omniBridge = '0x59447362798334d3485c64D1e4870Fde2DDC0d75'
const amb = '0x162e898bd0aacb578c8d5f8d6ca588c13d2a383f'
const token = '0xCa8d20f3e0144a72C6B5d576e9Bd3Fd8557E2B04' // WBNB
const l1Unwrapper = '0x2353Dcda746fa1AAD17C5650Ddf2A20112862197' // WBNB -> BNB
const l1Unwrapper = '0x8845F740F8B01bC7D9A4C82a6fD4A60320c07AF1' // WBNB -> BNB
const l1ChainId = 56
const multisig = '0xE3611102E23a43136a13993E3a00BAD67da19119'
const Verifier2 = await ethers.getContractFactory('Verifier2')
const verifier2 = await Verifier2.deploy()
@ -41,24 +42,28 @@ async function main() {
l1Unwrapper,
govAddress,
l1ChainId,
multisig,
]).slice(1, -1)}\n`,
)
const tornadoImpl = prompt('Deploy tornado pool implementation and provide address here:\n')
// const tornadoImpl = await Pool.deploy(
// verifier2.address,
// verifier16.address,
// MERKLE_TREE_HEIGHT,
// hasher.address,
// token,
// omniBridge,
// l1Unwrapper,
// govAddress,
// )
// await tornadoImpl.deployed()
// console.log(`TornadoPool implementation address: ${tornadoImpl.address}`)
//const tornadoImpl = prompt('Deploy tornado pool implementation and provide address here:\n')
const tornadoImpl = await Pool.deploy(
verifier2.address,
verifier16.address,
MERKLE_TREE_HEIGHT,
hasher.address,
token,
omniBridge,
l1Unwrapper,
govAddress,
l1ChainId,
multisig,
)
await tornadoImpl.deployed()
console.log(`TornadoPool implementation address: ${tornadoImpl.address}`)
const CrossChainUpgradeableProxy = await ethers.getContractFactory('CrossChainUpgradeableProxy')
const proxy = await CrossChainUpgradeableProxy.deploy(tornadoImpl, govAddress, [], amb, l1ChainId)
const proxy = await CrossChainUpgradeableProxy.deploy(tornadoImpl.address, govAddress, [], amb, l1ChainId)
await proxy.deployed()
console.log(`proxy address: ${proxy.address}`)

View File

@ -0,0 +1,28 @@
const { ethers } = require('hardhat')
const config = require('../config')
const { generate } = require('../src/0_generateAddresses')
// This script deploys Tornado Pool upgrade to L2 (Gnosis Chain)
async function deploy({ address, bytecode, singletonFactory }) {
const contractCode = await ethers.provider.getCode(address)
if (contractCode !== '0x') {
console.log(`Contract ${address} already deployed. Skipping...`)
return
}
await singletonFactory.deploy(bytecode, config.salt, { gasLimit: 5000000 })
}
async function main() {
const singletonFactory = await ethers.getContractAt('SingletonFactory', config.singletonFactory)
const contracts = await generate()
await deploy({ ...contracts.poolContract, singletonFactory })
console.log(`Upgraded pool contract have been deployed on ${contracts.poolContract.address} address`)
}
main()
.then(() => process.exit(0))
.catch((error) => {
console.error(error)
process.exit(1)
})

View File

@ -0,0 +1,68 @@
const { ethers } = require('hardhat')
const defaultConfig = require('../config')
async function generate(config = defaultConfig) {
const singletonFactory = await ethers.getContractAt('SingletonFactory', config.singletonFactory)
const UnwrapperFactory = await ethers.getContractFactory('L1Unwrapper')
const deploymentBytecodeUnwrapper =
UnwrapperFactory.bytecode +
UnwrapperFactory.interface.encodeDeploy([config.omniBridge, config.weth, config.multisig]).slice(2)
const unwrapperAddress = ethers.utils.getCreate2Address(
singletonFactory.address,
config.salt,
ethers.utils.keccak256(deploymentBytecodeUnwrapper),
)
const PoolFactory = await ethers.getContractFactory('TornadoPool')
const deploymentBytecodePool =
PoolFactory.bytecode +
PoolFactory.interface
.encodeDeploy([
config.verifier2,
config.verifier16,
config.MERKLE_TREE_HEIGHT,
config.hasher,
config.gcWeth,
config.gcOmniBridge,
config.l1Unwrapper,
config.govAddress,
config.l1ChainId,
config.gcMultisig,
])
.slice(2)
const poolAddress = ethers.utils.getCreate2Address(
singletonFactory.address,
config.salt,
ethers.utils.keccak256(deploymentBytecodePool),
)
const result = {
unwrapperContract: {
address: unwrapperAddress,
bytecode: deploymentBytecodeUnwrapper,
isProxy: false,
},
poolContract: {
address: poolAddress,
bytecode: deploymentBytecodePool,
isProxy: false,
},
}
return result
}
async function generateWithLog() {
const contracts = await generate()
console.log('L1 unwrapper contract: ', contracts.unwrapperContract.address)
console.log('Upgraded pool contract: ', contracts.poolContract.address)
return contracts
}
module.exports = {
generate,
generateWithLog,
}

View File

@ -16,7 +16,17 @@ async function buildMerkleTree({ tornadoPool }) {
return new MerkleTree(MERKLE_TREE_HEIGHT, leaves, { hashFunction: poseidonHash2 })
}
async function getProof({ inputs, outputs, tree, extAmount, fee, recipient, relayer, isL1Withdrawal }) {
async function getProof({
inputs,
outputs,
tree,
extAmount,
fee,
recipient,
relayer,
isL1Withdrawal,
l1Fee,
}) {
inputs = shuffle(inputs)
outputs = shuffle(outputs)
@ -45,6 +55,7 @@ async function getProof({ inputs, outputs, tree, extAmount, fee, recipient, rela
encryptedOutput1: outputs[0].encrypt(),
encryptedOutput2: outputs[1].encrypt(),
isL1Withdrawal,
l1Fee,
}
const extDataHash = getExtDataHash(extData)
@ -94,6 +105,7 @@ async function prepareTransaction({
recipient = 0,
relayer = 0,
isL1Withdrawal = false,
l1Fee = 0,
}) {
if (inputs.length > 16 || outputs.length > 2) {
throw new Error('Incorrect inputs/outputs count')
@ -118,6 +130,7 @@ async function prepareTransaction({
recipient,
relayer,
isL1Withdrawal,
l1Fee,
})
return {

View File

@ -22,12 +22,13 @@ function getExtDataHash({
encryptedOutput1,
encryptedOutput2,
isL1Withdrawal,
l1Fee,
}) {
const abi = new ethers.utils.AbiCoder()
const encodedData = abi.encode(
[
'tuple(address recipient,int256 extAmount,address relayer,uint256 fee,bytes encryptedOutput1,bytes encryptedOutput2,bool isL1Withdrawal)',
'tuple(address recipient,int256 extAmount,address relayer,uint256 fee,bytes encryptedOutput1,bytes encryptedOutput2,bool isL1Withdrawal,uint256 l1Fee)',
],
[
{
@ -38,6 +39,7 @@ function getExtDataHash({
encryptedOutput1: encryptedOutput1,
encryptedOutput2: encryptedOutput2,
isL1Withdrawal: isL1Withdrawal,
l1Fee: l1Fee,
},
],
)

View File

@ -9,10 +9,11 @@ const { transaction, registerAndTransact, prepareTransaction, buildMerkleTree }
const { toFixedHex, poseidonHash } = require('../src/utils')
const { Keypair } = require('../src/keypair')
const { encodeDataForBridge } = require('./utils')
const config = require('../config')
const { generate } = require('../src/0_generateAddresses')
const MERKLE_TREE_HEIGHT = 5
const l1ChainId = 1
const MINIMUM_WITHDRAWAL_AMOUNT = utils.parseEther(process.env.MINIMUM_WITHDRAWAL_AMOUNT || '0.05')
const MAXIMUM_DEPOSIT_AMOUNT = utils.parseEther(process.env.MAXIMUM_DEPOSIT_AMOUNT || '1')
describe('TornadoPool', function () {
@ -26,7 +27,7 @@ describe('TornadoPool', function () {
async function fixture() {
require('../scripts/compileHasher')
const [sender, gov, l1Unwrapper, multisig] = await ethers.getSigners()
const [sender, gov, multisig] = await ethers.getSigners()
const verifier2 = await deploy('Verifier2')
const verifier16 = await deploy('Verifier16')
const hasher = await deploy('Hasher')
@ -34,9 +35,23 @@ describe('TornadoPool', function () {
const token = await deploy('PermittableToken', 'Wrapped ETH', 'WETH', 18, l1ChainId)
await token.mint(sender.address, utils.parseEther('10000'))
const l1Token = await deploy('WETH', 'Wrapped ETH', 'WETH')
await l1Token.deposit({ value: utils.parseEther('3') })
const amb = await deploy('MockAMB', gov.address, l1ChainId)
const omniBridge = await deploy('MockOmniBridge', amb.address)
// deploy L1Unwrapper with CREATE2
const singletonFactory = await ethers.getContractAt('SingletonFactory', config.singletonFactory)
let customConfig = Object.assign({}, config)
customConfig.omniBridge = omniBridge.address
customConfig.weth = l1Token.address
customConfig.multisig = multisig.address
const contracts = await generate(customConfig)
await singletonFactory.deploy(contracts.unwrapperContract.bytecode, config.salt)
const l1Unwrapper = await ethers.getContractAt('L1Unwrapper', contracts.unwrapperContract.address)
/** @type {TornadoPool} */
const tornadoPoolImpl = await deploy(
'TornadoPool',
@ -52,10 +67,7 @@ describe('TornadoPool', function () {
multisig.address,
)
const { data } = await tornadoPoolImpl.populateTransaction.initialize(
MINIMUM_WITHDRAWAL_AMOUNT,
MAXIMUM_DEPOSIT_AMOUNT,
)
const { data } = await tornadoPoolImpl.populateTransaction.initialize(MAXIMUM_DEPOSIT_AMOUNT)
const proxy = await deploy(
'CrossChainUpgradeableProxy',
tornadoPoolImpl.address,
@ -69,7 +81,7 @@ describe('TornadoPool', function () {
await token.approve(tornadoPool.address, utils.parseEther('10000'))
return { tornadoPool, token, proxy, omniBridge, amb, gov, multisig }
return { tornadoPool, token, proxy, omniBridge, amb, gov, multisig, l1Unwrapper, sender, l1Token }
}
describe('Upgradeability tests', () => {
@ -88,19 +100,12 @@ describe('TornadoPool', function () {
})
it('should configure', async () => {
const { tornadoPool, amb } = await loadFixture(fixture)
const newWithdrawalLimit = utils.parseEther('0.01337')
const { tornadoPool, multisig } = await loadFixture(fixture)
const newDepositLimit = utils.parseEther('1337')
const { data } = await tornadoPool.populateTransaction.configureLimits(
newWithdrawalLimit,
newDepositLimit,
)
await amb.execute([{ who: tornadoPool.address, callData: data }])
await tornadoPool.connect(multisig).configureLimits(newDepositLimit)
expect(await tornadoPool.maximumDepositAmount()).to.be.equal(newDepositLimit)
expect(await tornadoPool.minimalWithdrawalAmount()).to.be.equal(newWithdrawalLimit)
})
})
@ -271,6 +276,201 @@ describe('TornadoPool', function () {
expect(omniBridgeBalance).to.be.equal(aliceWithdrawAmount)
})
it('should withdraw with L1 fee', async function () {
const { tornadoPool, token, omniBridge, l1Unwrapper, sender, l1Token } = await loadFixture(fixture)
const aliceKeypair = new Keypair() // contains private and public keys
// regular L1 deposit -------------------------------------------
const aliceDepositAmount = utils.parseEther('0.07')
const aliceDepositUtxo = new Utxo({ amount: aliceDepositAmount, keypair: aliceKeypair })
const { args, extData } = await prepareTransaction({
tornadoPool,
outputs: [aliceDepositUtxo],
})
let onTokenBridgedData = encodeDataForBridge({
proof: args,
extData,
})
let onTokenBridgedTx = await tornadoPool.populateTransaction.onTokenBridged(
token.address,
aliceDepositUtxo.amount,
onTokenBridgedData,
)
// emulating bridge. first it sends tokens to omnibridge mock then it sends to the pool
await token.transfer(omniBridge.address, aliceDepositAmount)
let transferTx = await token.populateTransaction.transfer(tornadoPool.address, aliceDepositAmount)
await omniBridge.execute([
{ who: token.address, callData: transferTx.data }, // send tokens to pool
{ who: tornadoPool.address, callData: onTokenBridgedTx.data }, // call onTokenBridgedTx
])
// withdrawal with L1 fee ---------------------------------------
// withdraws a part of his funds from the shielded pool
const aliceWithdrawAmount = utils.parseEther('0.06')
const l1Fee = utils.parseEther('0.01')
// sum of desired withdraw amount and L1 fee are stored in extAmount
const extAmount = aliceWithdrawAmount.add(l1Fee)
const recipient = '0xDeaD00000000000000000000000000000000BEEf'
const aliceChangeUtxo = new Utxo({
amount: aliceDepositAmount.sub(extAmount),
keypair: aliceKeypair,
})
await transaction({
tornadoPool,
inputs: [aliceDepositUtxo],
outputs: [aliceChangeUtxo],
recipient: recipient,
isL1Withdrawal: true,
l1Fee: l1Fee,
})
const filter = omniBridge.filters.OnTokenTransfer()
const fromBlock = await ethers.provider.getBlock()
const events = await omniBridge.queryFilter(filter, fromBlock.number)
onTokenBridgedData = events[0].args.data
const hexL1Fee = '0x' + events[0].args.data.toString().slice(66)
expect(ethers.BigNumber.from(hexL1Fee)).to.be.equal(l1Fee)
const recipientBalance = await token.balanceOf(recipient)
expect(recipientBalance).to.be.equal(0)
const omniBridgeBalance = await token.balanceOf(omniBridge.address)
expect(omniBridgeBalance).to.be.equal(extAmount)
// L1 transactions:
onTokenBridgedTx = await l1Unwrapper.populateTransaction.onTokenBridged(
l1Token.address,
extAmount,
onTokenBridgedData,
)
// emulating bridge. first it sends tokens to omniBridge mock then it sends to the recipient
await l1Token.transfer(omniBridge.address, extAmount)
transferTx = await l1Token.populateTransaction.transfer(l1Unwrapper.address, extAmount)
const senderBalanceBefore = await ethers.provider.getBalance(sender.address)
let tx = await omniBridge.execute([
{ who: l1Token.address, callData: transferTx.data }, // send tokens to L1Unwrapper
{ who: l1Unwrapper.address, callData: onTokenBridgedTx.data }, // call onTokenBridged on L1Unwrapper
])
let receipt = await tx.wait()
let txFee = receipt.cumulativeGasUsed.mul(receipt.effectiveGasPrice)
const senderBalanceAfter = await ethers.provider.getBalance(sender.address)
expect(senderBalanceAfter).to.be.equal(senderBalanceBefore.sub(txFee).add(l1Fee))
expect(await ethers.provider.getBalance(recipient)).to.be.equal(aliceWithdrawAmount)
})
it('should set L1FeeReceiver on L1Unwrapper contract', async function () {
const { tornadoPool, token, omniBridge, l1Unwrapper, sender, l1Token, multisig } = await loadFixture(
fixture,
)
// check init l1FeeReceiver
expect(await l1Unwrapper.l1FeeReceiver()).to.be.equal(ethers.constants.AddressZero)
// should not set from not multisig
await expect(l1Unwrapper.connect(sender).setL1FeeReceiver(multisig.address)).to.be.reverted
expect(await l1Unwrapper.l1FeeReceiver()).to.be.equal(ethers.constants.AddressZero)
// should set from multisig
await l1Unwrapper.connect(multisig).setL1FeeReceiver(multisig.address)
expect(await l1Unwrapper.l1FeeReceiver()).to.be.equal(multisig.address)
// ------------------------------------------------------------------------
// check withdraw with L1 fee ---------------------------------------------
const aliceKeypair = new Keypair() // contains private and public keys
// regular L1 deposit -------------------------------------------
const aliceDepositAmount = utils.parseEther('0.07')
const aliceDepositUtxo = new Utxo({ amount: aliceDepositAmount, keypair: aliceKeypair })
const { args, extData } = await prepareTransaction({
tornadoPool,
outputs: [aliceDepositUtxo],
})
let onTokenBridgedData = encodeDataForBridge({
proof: args,
extData,
})
let onTokenBridgedTx = await tornadoPool.populateTransaction.onTokenBridged(
token.address,
aliceDepositUtxo.amount,
onTokenBridgedData,
)
// emulating bridge. first it sends tokens to omnibridge mock then it sends to the pool
await token.transfer(omniBridge.address, aliceDepositAmount)
let transferTx = await token.populateTransaction.transfer(tornadoPool.address, aliceDepositAmount)
await omniBridge.execute([
{ who: token.address, callData: transferTx.data }, // send tokens to pool
{ who: tornadoPool.address, callData: onTokenBridgedTx.data }, // call onTokenBridgedTx
])
// withdrawal with L1 fee ---------------------------------------
// withdraws a part of his funds from the shielded pool
const aliceWithdrawAmount = utils.parseEther('0.06')
const l1Fee = utils.parseEther('0.01')
// sum of desired withdraw amount and L1 fee are stored in extAmount
const extAmount = aliceWithdrawAmount.add(l1Fee)
const recipient = '0xDeaD00000000000000000000000000000000BEEf'
const aliceChangeUtxo = new Utxo({
amount: aliceDepositAmount.sub(extAmount),
keypair: aliceKeypair,
})
await transaction({
tornadoPool,
inputs: [aliceDepositUtxo],
outputs: [aliceChangeUtxo],
recipient: recipient,
isL1Withdrawal: true,
l1Fee: l1Fee,
})
const filter = omniBridge.filters.OnTokenTransfer()
const fromBlock = await ethers.provider.getBlock()
const events = await omniBridge.queryFilter(filter, fromBlock.number)
onTokenBridgedData = events[0].args.data
const hexL1Fee = '0x' + events[0].args.data.toString().slice(66)
expect(ethers.BigNumber.from(hexL1Fee)).to.be.equal(l1Fee)
const recipientBalance = await token.balanceOf(recipient)
expect(recipientBalance).to.be.equal(0)
const omniBridgeBalance = await token.balanceOf(omniBridge.address)
expect(omniBridgeBalance).to.be.equal(extAmount)
// L1 transactions:
onTokenBridgedTx = await l1Unwrapper.populateTransaction.onTokenBridged(
l1Token.address,
extAmount,
onTokenBridgedData,
)
// emulating bridge. first it sends tokens to omniBridge mock then it sends to the recipient
await l1Token.transfer(omniBridge.address, extAmount)
transferTx = await l1Token.populateTransaction.transfer(l1Unwrapper.address, extAmount)
const senderBalanceBefore = await ethers.provider.getBalance(sender.address)
const multisigBalanceBefore = await ethers.provider.getBalance(multisig.address)
let tx = await omniBridge.execute([
{ who: l1Token.address, callData: transferTx.data }, // send tokens to L1Unwrapper
{ who: l1Unwrapper.address, callData: onTokenBridgedTx.data }, // call onTokenBridged on L1Unwrapper
])
let receipt = await tx.wait()
let txFee = receipt.cumulativeGasUsed.mul(receipt.effectiveGasPrice)
expect(await ethers.provider.getBalance(sender.address)).to.be.equal(senderBalanceBefore.sub(txFee))
expect(await ethers.provider.getBalance(multisig.address)).to.be.equal(multisigBalanceBefore.add(l1Fee))
expect(await ethers.provider.getBalance(recipient)).to.be.equal(aliceWithdrawAmount)
})
it('should transfer funds to multisig in case of L1 deposit fail', async function () {
const { tornadoPool, token, omniBridge, multisig } = await loadFixture(fixture)
const aliceKeypair = new Keypair() // contains private and public keys

View File

@ -71,11 +71,11 @@ describe('MerkleTreeWithHistory', function () {
it('should insert', async () => {
const { merkleTreeWithHistory } = await loadFixture(fixture)
const tree = getNewTree()
merkleTreeWithHistory.insert(toFixedHex(123), toFixedHex(456))
await merkleTreeWithHistory.insert(toFixedHex(123), toFixedHex(456))
tree.bulkInsert([123, 456])
expect(tree.root()).to.be.be.equal(await merkleTreeWithHistory.getLastRoot())
merkleTreeWithHistory.insert(toFixedHex(678), toFixedHex(876))
await merkleTreeWithHistory.insert(toFixedHex(678), toFixedHex(876))
tree.bulkInsert([678, 876])
expect(tree.root()).to.be.be.equal(await merkleTreeWithHistory.getLastRoot())
})

View File

@ -6,7 +6,7 @@ function encodeDataForBridge({ proof, extData }) {
return abi.encode(
[
'tuple(bytes proof,bytes32 root,bytes32[] inputNullifiers,bytes32[2] outputCommitments,uint256 publicAmount,bytes32 extDataHash)',
'tuple(address recipient,int256 extAmount,address relayer,uint256 fee,bytes encryptedOutput1,bytes encryptedOutput2,bool isL1Withdrawal)',
'tuple(address recipient,int256 extAmount,address relayer,uint256 fee,bytes encryptedOutput1,bytes encryptedOutput2,bool isL1Withdrawal,uint256 l1Fee)',
],
[proof, extData],
)

View File

@ -341,6 +341,17 @@
"@ethersproject/logger" "^5.4.0"
"@ethersproject/rlp" "^5.4.0"
"@ethersproject/address@^5.0.2":
version "5.5.0"
resolved "https://registry.yarnpkg.com/@ethersproject/address/-/address-5.5.0.tgz#bcc6f576a553f21f3dd7ba17248f81b473c9c78f"
integrity sha512-l4Nj0eWlTUh6ro5IbPTgbpT4wRbdH5l8CQf7icF7sb/SI3Nhd9Y9HzhonTSTi6CefI0necIw7LJqQPopPLZyWw==
dependencies:
"@ethersproject/bignumber" "^5.5.0"
"@ethersproject/bytes" "^5.5.0"
"@ethersproject/keccak256" "^5.5.0"
"@ethersproject/logger" "^5.5.0"
"@ethersproject/rlp" "^5.5.0"
"@ethersproject/base64@5.4.0", "@ethersproject/base64@^5.4.0":
version "5.4.0"
resolved "https://registry.yarnpkg.com/@ethersproject/base64/-/base64-5.4.0.tgz#7252bf65295954c9048c7ca5f43e5c86441b2a9a"
@ -365,6 +376,15 @@
"@ethersproject/logger" "^5.4.0"
bn.js "^4.11.9"
"@ethersproject/bignumber@^5.5.0":
version "5.5.0"
resolved "https://registry.yarnpkg.com/@ethersproject/bignumber/-/bignumber-5.5.0.tgz#875b143f04a216f4f8b96245bde942d42d279527"
integrity sha512-6Xytlwvy6Rn3U3gKEc1vP7nR92frHkv6wtVr95LFR3jREXiCPzdWxKQ1cx4JGQBXxcguAwjA8murlYN2TSiEbg==
dependencies:
"@ethersproject/bytes" "^5.5.0"
"@ethersproject/logger" "^5.5.0"
bn.js "^4.11.9"
"@ethersproject/bytes@5.4.0", "@ethersproject/bytes@>=5.0.0-beta.129", "@ethersproject/bytes@^5.0.4", "@ethersproject/bytes@^5.4.0":
version "5.4.0"
resolved "https://registry.yarnpkg.com/@ethersproject/bytes/-/bytes-5.4.0.tgz#56fa32ce3bf67153756dbaefda921d1d4774404e"
@ -372,6 +392,13 @@
dependencies:
"@ethersproject/logger" "^5.4.0"
"@ethersproject/bytes@^5.5.0":
version "5.5.0"
resolved "https://registry.yarnpkg.com/@ethersproject/bytes/-/bytes-5.5.0.tgz#cb11c526de657e7b45d2e0f0246fb3b9d29a601c"
integrity sha512-ABvc7BHWhZU9PNM/tANm/Qx4ostPGadAuQzWTr3doklZOhDlmcBqclrQe/ZXUIj3K8wC28oYeuRa+A37tX9kog==
dependencies:
"@ethersproject/logger" "^5.5.0"
"@ethersproject/constants@5.4.0", "@ethersproject/constants@>=5.0.0-beta.128", "@ethersproject/constants@^5.0.4", "@ethersproject/constants@^5.4.0":
version "5.4.0"
resolved "https://registry.yarnpkg.com/@ethersproject/constants/-/constants-5.4.0.tgz#ee0bdcb30bf1b532d2353c977bf2ef1ee117958a"
@ -454,11 +481,24 @@
"@ethersproject/bytes" "^5.4.0"
js-sha3 "0.5.7"
"@ethersproject/keccak256@^5.5.0":
version "5.5.0"
resolved "https://registry.yarnpkg.com/@ethersproject/keccak256/-/keccak256-5.5.0.tgz#e4b1f9d7701da87c564ffe336f86dcee82983492"
integrity sha512-5VoFCTjo2rYbBe1l2f4mccaRFN/4VQEYFwwn04aJV2h7qf4ZvI2wFxUE1XOX+snbwCLRzIeikOqtAoPwMza9kg==
dependencies:
"@ethersproject/bytes" "^5.5.0"
js-sha3 "0.8.0"
"@ethersproject/logger@5.4.1", "@ethersproject/logger@>=5.0.0-beta.129", "@ethersproject/logger@^5.0.5", "@ethersproject/logger@^5.4.0":
version "5.4.1"
resolved "https://registry.yarnpkg.com/@ethersproject/logger/-/logger-5.4.1.tgz#503bd33683538b923c578c07d1c2c0dd18672054"
integrity sha512-DZ+bRinnYLPw1yAC64oRl0QyVZj43QeHIhVKfD/+YwSz4wsv1pfwb5SOFjz+r710YEWzU6LrhuSjpSO+6PeE4A==
"@ethersproject/logger@^5.5.0":
version "5.5.0"
resolved "https://registry.yarnpkg.com/@ethersproject/logger/-/logger-5.5.0.tgz#0c2caebeff98e10aefa5aef27d7441c7fd18cf5d"
integrity sha512-rIY/6WPm7T8n3qS2vuHTUBPdXHl+rGxWxW5okDfo9J4Z0+gRRZT0msvUdIJkE4/HS29GUMziwGaaKO2bWONBrg==
"@ethersproject/networks@5.4.2", "@ethersproject/networks@^5.4.0":
version "5.4.2"
resolved "https://registry.yarnpkg.com/@ethersproject/networks/-/networks-5.4.2.tgz#2247d977626e97e2c3b8ee73cd2457babde0ce35"
@ -522,6 +562,14 @@
"@ethersproject/bytes" "^5.4.0"
"@ethersproject/logger" "^5.4.0"
"@ethersproject/rlp@^5.5.0":
version "5.5.0"
resolved "https://registry.yarnpkg.com/@ethersproject/rlp/-/rlp-5.5.0.tgz#530f4f608f9ca9d4f89c24ab95db58ab56ab99a0"
integrity sha512-hLv8XaQ8PTI9g2RHoQGf/WSxBfTB/NudRacbzdxmst5VHAqd1sMibWG7SENzT5Dj3yZ3kJYx+WiRYEcQTAkcYA==
dependencies:
"@ethersproject/bytes" "^5.5.0"
"@ethersproject/logger" "^5.5.0"
"@ethersproject/sha2@5.4.0", "@ethersproject/sha2@^5.4.0":
version "5.4.0"
resolved "https://registry.yarnpkg.com/@ethersproject/sha2/-/sha2-5.4.0.tgz#c9a8db1037014cbc4e9482bd662f86c090440371"
@ -662,6 +710,19 @@
resolved "https://registry.yarnpkg.com/@nomiclabs/hardhat-ethers/-/hardhat-ethers-2.0.2.tgz#c472abcba0c5185aaa4ad4070146e95213c68511"
integrity sha512-6quxWe8wwS4X5v3Au8q1jOvXYEPkS1Fh+cME5u6AwNdnI4uERvPlVjlgRWzpnb+Rrt1l/cEqiNRH9GlsBMSDQg==
"@nomiclabs/hardhat-etherscan@^2.1.8":
version "2.1.8"
resolved "https://registry.yarnpkg.com/@nomiclabs/hardhat-etherscan/-/hardhat-etherscan-2.1.8.tgz#e206275e96962cd15e5ba9148b44388bc922d8c2"
integrity sha512-0+rj0SsZotVOcTLyDOxnOc3Gulo8upo0rsw/h+gBPcmtj91YqYJNhdARHoBxOhhE8z+5IUQPx+Dii04lXT14PA==
dependencies:
"@ethersproject/abi" "^5.1.2"
"@ethersproject/address" "^5.0.2"
cbor "^5.0.2"
debug "^4.1.1"
fs-extra "^7.0.1"
node-fetch "^2.6.0"
semver "^6.3.0"
"@nomiclabs/hardhat-waffle@^2.0.1":
version "2.0.1"
resolved "https://registry.yarnpkg.com/@nomiclabs/hardhat-waffle/-/hardhat-waffle-2.0.1.tgz#5d43654fba780720c5033dea240fe14f70ef4bd2"
@ -675,11 +736,6 @@
resolved "https://registry.yarnpkg.com/@openzeppelin/contracts-upgradeable/-/contracts-upgradeable-3.4.2.tgz#2c2a1b0fa748235a1f495b6489349776365c51b3"
integrity sha512-mDlBS17ymb2wpaLcrqRYdnBAmP1EwqhOXMvqWk2c5Q1N1pm5TkiCtXM9Xzznh4bYsQBq0aIWEkFFE2+iLSN1Tw==
"@openzeppelin/contracts@3.2.2-solc-0.7":
version "3.2.2-solc-0.7"
resolved "https://registry.yarnpkg.com/@openzeppelin/contracts/-/contracts-3.2.2-solc-0.7.tgz#8ab169da64438d59f47ca285f1a10efe2f9ba19e"
integrity sha512-vFV53E4pvfsAEjzL9Um2VX9MEuXyq7Hyd9JjnP77AGsrEPxkJaYS06zZIVyhAt3rXTM6QGdW0C282Zv7fM93AA==
"@openzeppelin@git+https://github.com/tornadocash/openzeppelin-contracts.git#6e46aa6946a7f215e7604169ddf46e1aebea850f":
version "3.4.1-solc-0.7-2"
resolved "git+https://github.com/tornadocash/openzeppelin-contracts.git#6e46aa6946a7f215e7604169ddf46e1aebea850f"
@ -2277,6 +2333,14 @@ caseless@~0.12.0:
resolved "https://registry.yarnpkg.com/caseless/-/caseless-0.12.0.tgz#1b681c21ff84033c826543090689420d187151dc"
integrity sha1-G2gcIf+EAzyCZUMJBolCDRhxUdw=
cbor@^5.0.2:
version "5.2.0"
resolved "https://registry.yarnpkg.com/cbor/-/cbor-5.2.0.tgz#4cca67783ccd6de7b50ab4ed62636712f287a67c"
integrity sha512-5IMhi9e1QU76ppa5/ajP1BmMWZ2FHkhAhjeVKQ/EFCgYSEaeVaoGtL7cxJskf9oCCk+XjzaIdc3IuU/dbA/o2A==
dependencies:
bignumber.js "^9.0.1"
nofilter "^1.0.4"
chai@^4.2.0, chai@^4.3.4:
version "4.3.4"
resolved "https://registry.yarnpkg.com/chai/-/chai-4.3.4.tgz#b55e655b31e1eac7099be4c08c21964fce2e6c49"
@ -6700,6 +6764,11 @@ node-gyp-build@^4.2.0:
resolved "https://registry.yarnpkg.com/node-gyp-build/-/node-gyp-build-4.3.0.tgz#9f256b03e5826150be39c764bf51e993946d71a3"
integrity sha512-iWjXZvmboq0ja1pUGULQBexmxq8CV4xBhX7VDOTbL7ZR4FOowwY/VOtRxBN/yKxmdGoIp4j5ysNT4u3S2pDQ3Q==
nofilter@^1.0.4:
version "1.0.4"
resolved "https://registry.yarnpkg.com/nofilter/-/nofilter-1.0.4.tgz#78d6f4b6a613e7ced8b015cec534625f7667006e"
integrity sha512-N8lidFp+fCz+TD51+haYdbDGrcBWwuHX40F5+z0qkUjMJ5Tp+rdSuAkMJ9N9eoolDlEVTf6u5icM+cNKkKW2mA==
normalize-package-data@^2.3.2:
version "2.5.0"
resolved "https://registry.yarnpkg.com/normalize-package-data/-/normalize-package-data-2.5.0.tgz#e66db1838b200c1dfc233225d12cb36520e234a8"
@ -6844,11 +6913,10 @@ oboe@2.1.5:
dependencies:
http-https "^1.0.0"
"omnibridge@git+https://github.com/peppersec/omnibridge.git#aa3a970c29752a4da5f3fc7ccf0733783c1acf0b":
"omnibridge@git+https://github.com/peppersec/omnibridge.git#30081f7a735eb03c9d6821a9617cc28efe71a682":
version "1.1.0"
resolved "git+https://github.com/peppersec/omnibridge.git#aa3a970c29752a4da5f3fc7ccf0733783c1acf0b"
resolved "git+https://github.com/peppersec/omnibridge.git#30081f7a735eb03c9d6821a9617cc28efe71a682"
dependencies:
"@openzeppelin/contracts" "3.2.2-solc-0.7"
axios "^0.21.0"
bignumber.js "^9.0.1"
dotenv "^8.2.0"