mirror of
https://github.com/tornadocash/tornado-nova
synced 2024-02-02 14:53:56 +01:00
Merge pull request #23 from tornadocash/l1-fee-from-user
L1 fee from user
This commit is contained in:
commit
f9264eeffe
@ -4,3 +4,6 @@ XDAI_RPC=https://
|
|||||||
BSC_RPC=https://
|
BSC_RPC=https://
|
||||||
MINIMUM_WITHDRAWAL_AMOUNT=0.05
|
MINIMUM_WITHDRAWAL_AMOUNT=0.05
|
||||||
MAXIMUM_DEPOSIT_AMOUNT=1
|
MAXIMUM_DEPOSIT_AMOUNT=1
|
||||||
|
ALCHEMY_KEY=
|
||||||
|
INFURA_API_KEY=
|
||||||
|
ETHERSCAN_KEY=
|
||||||
|
2
.github/workflows/build.yml
vendored
2
.github/workflows/build.yml
vendored
@ -19,6 +19,8 @@ jobs:
|
|||||||
- run: yarn lint
|
- run: yarn lint
|
||||||
- run: yarn build
|
- run: yarn build
|
||||||
- run: yarn test
|
- run: yarn test
|
||||||
|
env:
|
||||||
|
ALCHEMY_KEY: ${{ secrets.ALCHEMY_KEY }}
|
||||||
- name: Telegram Failure Notification
|
- name: Telegram Failure Notification
|
||||||
uses: appleboy/telegram-action@0.0.7
|
uses: appleboy/telegram-action@0.0.7
|
||||||
if: failure()
|
if: failure()
|
||||||
|
1
.gitignore
vendored
1
.gitignore
vendored
@ -4,3 +4,4 @@ build
|
|||||||
cache
|
cache
|
||||||
artifacts
|
artifacts
|
||||||
src/types
|
src/types
|
||||||
|
.vscode
|
||||||
|
@ -7,7 +7,8 @@
|
|||||||
"printWidth": 110
|
"printWidth": 110
|
||||||
}
|
}
|
||||||
],
|
],
|
||||||
"quotes": ["error", "double"]
|
"quotes": ["error", "double"],
|
||||||
|
"compiler-version": ["error", "^0.7.0"]
|
||||||
},
|
},
|
||||||
"plugins": ["prettier"]
|
"plugins": ["prettier"]
|
||||||
}
|
}
|
||||||
|
28
README.md
28
README.md
@ -20,3 +20,31 @@ yarn download
|
|||||||
yarn build
|
yarn build
|
||||||
yarn test
|
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
26
config.js
Normal 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
19
contracts/Mocks/WETH.sol
Normal 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");
|
||||||
|
}
|
||||||
|
}
|
@ -25,6 +25,7 @@ import "./MerkleTreeWithHistory.sol";
|
|||||||
contract TornadoPool is MerkleTreeWithHistory, IERC20Receiver, ReentrancyGuard, CrossChainGuard {
|
contract TornadoPool is MerkleTreeWithHistory, IERC20Receiver, ReentrancyGuard, CrossChainGuard {
|
||||||
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;
|
||||||
|
uint256 public constant MIN_EXT_AMOUNT_LIMIT = 0.5 ether;
|
||||||
|
|
||||||
IVerifier public immutable verifier2;
|
IVerifier public immutable verifier2;
|
||||||
IVerifier public immutable verifier16;
|
IVerifier public immutable verifier16;
|
||||||
@ -34,7 +35,7 @@ contract TornadoPool is MerkleTreeWithHistory, IERC20Receiver, ReentrancyGuard,
|
|||||||
address public immutable multisig;
|
address public immutable multisig;
|
||||||
|
|
||||||
uint256 public lastBalance;
|
uint256 public lastBalance;
|
||||||
uint256 public minimalWithdrawalAmount;
|
uint256 public __gap; // storage padding to prevent storage collision
|
||||||
uint256 public maximumDepositAmount;
|
uint256 public maximumDepositAmount;
|
||||||
mapping(bytes32 => bool) public nullifierHashes;
|
mapping(bytes32 => bool) public nullifierHashes;
|
||||||
|
|
||||||
@ -46,6 +47,7 @@ contract TornadoPool is MerkleTreeWithHistory, IERC20Receiver, ReentrancyGuard,
|
|||||||
bytes encryptedOutput1;
|
bytes encryptedOutput1;
|
||||||
bytes encryptedOutput2;
|
bytes encryptedOutput2;
|
||||||
bool isL1Withdrawal;
|
bool isL1Withdrawal;
|
||||||
|
uint256 l1Fee;
|
||||||
}
|
}
|
||||||
|
|
||||||
struct Proof {
|
struct Proof {
|
||||||
@ -66,11 +68,6 @@ contract TornadoPool is MerkleTreeWithHistory, IERC20Receiver, ReentrancyGuard,
|
|||||||
event NewNullifier(bytes32 nullifier);
|
event NewNullifier(bytes32 nullifier);
|
||||||
event PublicKey(address indexed owner, bytes key);
|
event PublicKey(address indexed owner, bytes key);
|
||||||
|
|
||||||
modifier onlyGovernance() {
|
|
||||||
require(isCalledByOwner(), "only governance");
|
|
||||||
_;
|
|
||||||
}
|
|
||||||
|
|
||||||
modifier onlyMultisig() {
|
modifier onlyMultisig() {
|
||||||
require(msg.sender == multisig, "only governance");
|
require(msg.sender == multisig, "only governance");
|
||||||
_;
|
_;
|
||||||
@ -112,8 +109,8 @@ contract TornadoPool is MerkleTreeWithHistory, IERC20Receiver, ReentrancyGuard,
|
|||||||
multisig = _multisig;
|
multisig = _multisig;
|
||||||
}
|
}
|
||||||
|
|
||||||
function initialize(uint256 _minimalWithdrawalAmount, uint256 _maximumDepositAmount) external initializer {
|
function initialize(uint256 _maximumDepositAmount) external initializer {
|
||||||
_configureLimits(_minimalWithdrawalAmount, _maximumDepositAmount);
|
_configureLimits(_maximumDepositAmount);
|
||||||
super._initialize();
|
super._initialize();
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -191,8 +188,8 @@ contract TornadoPool is MerkleTreeWithHistory, IERC20Receiver, ReentrancyGuard,
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
function configureLimits(uint256 _minimalWithdrawalAmount, uint256 _maximumDepositAmount) public onlyGovernance {
|
function configureLimits(uint256 _maximumDepositAmount) public onlyMultisig {
|
||||||
_configureLimits(_minimalWithdrawalAmount, _maximumDepositAmount);
|
_configureLimits(_maximumDepositAmount);
|
||||||
}
|
}
|
||||||
|
|
||||||
function calculatePublicAmount(int256 _extAmount, uint256 _fee) public pure returns (uint256) {
|
function calculatePublicAmount(int256 _extAmount, uint256 _fee) public pure returns (uint256) {
|
||||||
@ -275,11 +272,14 @@ contract TornadoPool is MerkleTreeWithHistory, IERC20Receiver, ReentrancyGuard,
|
|||||||
if (_extData.extAmount < 0) {
|
if (_extData.extAmount < 0) {
|
||||||
require(_extData.recipient != address(0), "Can't withdraw to zero address");
|
require(_extData.recipient != address(0), "Can't withdraw to zero address");
|
||||||
if (_extData.isL1Withdrawal) {
|
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 {
|
} else {
|
||||||
token.transfer(_extData.recipient, uint256(-_extData.extAmount));
|
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) {
|
if (_extData.fee > 0) {
|
||||||
token.transfer(_extData.relayer, _extData.fee);
|
token.transfer(_extData.relayer, _extData.fee);
|
||||||
@ -294,8 +294,7 @@ contract TornadoPool is MerkleTreeWithHistory, IERC20Receiver, ReentrancyGuard,
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
function _configureLimits(uint256 _minimalWithdrawalAmount, uint256 _maximumDepositAmount) internal {
|
function _configureLimits(uint256 _maximumDepositAmount) internal {
|
||||||
minimalWithdrawalAmount = _minimalWithdrawalAmount;
|
|
||||||
maximumDepositAmount = _maximumDepositAmount;
|
maximumDepositAmount = _maximumDepositAmount;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -14,9 +14,19 @@ pragma solidity ^0.7.0;
|
|||||||
pragma abicoder v2;
|
pragma abicoder v2;
|
||||||
|
|
||||||
import "omnibridge/contracts/helpers/WETHOmnibridgeRouter.sol";
|
import "omnibridge/contracts/helpers/WETHOmnibridgeRouter.sol";
|
||||||
|
import "@openzeppelin/contracts/math/SafeMath.sol";
|
||||||
|
|
||||||
/// @dev Extension for original WETHOmnibridgeRouter that stores TornadoPool account registrations.
|
/// @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);
|
event PublicKey(address indexed owner, bytes key);
|
||||||
|
|
||||||
struct Account {
|
struct Account {
|
||||||
@ -61,4 +71,42 @@ contract L1Helper is WETHOmnibridgeRouter {
|
|||||||
function _register(Account memory _account) internal {
|
function _register(Account memory _account) internal {
|
||||||
emit PublicKey(_account.owner, _account.publicKey);
|
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;
|
||||||
|
}
|
||||||
}
|
}
|
28
contracts/libraries/SingletonFactory.sol
Normal file
28
contracts/libraries/SingletonFactory.sol
Normal 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
|
@ -21,6 +21,15 @@ const config = {
|
|||||||
},
|
},
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
version: '0.6.2',
|
||||||
|
settings: {
|
||||||
|
optimizer: {
|
||||||
|
enabled: true,
|
||||||
|
runs: 200,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
{
|
{
|
||||||
version: '0.7.5',
|
version: '0.7.5',
|
||||||
settings: {
|
settings: {
|
||||||
@ -42,6 +51,25 @@ const config = {
|
|||||||
],
|
],
|
||||||
},
|
},
|
||||||
networks: {
|
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: {
|
xdai: {
|
||||||
url: process.env.ETH_RPC || 'https://rpc.xdaichain.com/',
|
url: process.env.ETH_RPC || 'https://rpc.xdaichain.com/',
|
||||||
accounts: process.env.PRIVATE_KEY
|
accounts: process.env.PRIVATE_KEY
|
||||||
@ -60,7 +88,7 @@ const config = {
|
|||||||
},
|
},
|
||||||
},
|
},
|
||||||
mainnet: {
|
mainnet: {
|
||||||
url: process.env.ETH_RPC || '',
|
url: `https://eth-mainnet.alchemyapi.io/v2/${process.env.ALCHEMY_KEY}`,
|
||||||
accounts: process.env.PRIVATE_KEY
|
accounts: process.env.PRIVATE_KEY
|
||||||
? [process.env.PRIVATE_KEY]
|
? [process.env.PRIVATE_KEY]
|
||||||
: {
|
: {
|
||||||
|
@ -44,7 +44,7 @@
|
|||||||
"fixed-merkle-tree": "^0.5.1",
|
"fixed-merkle-tree": "^0.5.1",
|
||||||
"hardhat": "^2.3.0",
|
"hardhat": "^2.3.0",
|
||||||
"mocha": "^9.1.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",
|
"prompt-sync": "^4.2.0",
|
||||||
"snarkjs": "git+https://github.com/tornadocash/snarkjs.git#f37f146948f3b28086493e71512006b030588fc2",
|
"snarkjs": "git+https://github.com/tornadocash/snarkjs.git#f37f146948f3b28086493e71512006b030588fc2",
|
||||||
"tmp-promise": "^3.0.2",
|
"tmp-promise": "^3.0.2",
|
||||||
|
@ -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)
|
|
||||||
})
|
|
28
scripts/deployL1Unwrapper.js
Normal file
28
scripts/deployL1Unwrapper.js
Normal 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)
|
||||||
|
})
|
@ -1,18 +1,19 @@
|
|||||||
const { ethers } = require('hardhat')
|
const { ethers } = require('hardhat')
|
||||||
const { utils } = ethers
|
const { utils } = ethers
|
||||||
const prompt = require('prompt-sync')()
|
// const prompt = require('prompt-sync')()
|
||||||
|
|
||||||
const MERKLE_TREE_HEIGHT = 23
|
const MERKLE_TREE_HEIGHT = 23
|
||||||
const { MINIMUM_WITHDRAWAL_AMOUNT, MAXIMUM_DEPOSIT_AMOUNT } = process.env
|
const { MINIMUM_WITHDRAWAL_AMOUNT, MAXIMUM_DEPOSIT_AMOUNT } = process.env
|
||||||
|
|
||||||
async function main() {
|
async function main() {
|
||||||
require('./compileHasher')
|
require('./compileHasher')
|
||||||
const govAddress = '0x03ebd0748aa4d1457cf479cce56309641e0a98f5'
|
const govAddress = '0xBAE5aBfa98466Dbe68836763B087f2d189f4D28f'
|
||||||
const omniBridge = '0x59447362798334d3485c64D1e4870Fde2DDC0d75'
|
const omniBridge = '0x59447362798334d3485c64D1e4870Fde2DDC0d75'
|
||||||
const amb = '0x162e898bd0aacb578c8d5f8d6ca588c13d2a383f'
|
const amb = '0x162e898bd0aacb578c8d5f8d6ca588c13d2a383f'
|
||||||
const token = '0xCa8d20f3e0144a72C6B5d576e9Bd3Fd8557E2B04' // WBNB
|
const token = '0xCa8d20f3e0144a72C6B5d576e9Bd3Fd8557E2B04' // WBNB
|
||||||
const l1Unwrapper = '0x2353Dcda746fa1AAD17C5650Ddf2A20112862197' // WBNB -> BNB
|
const l1Unwrapper = '0x8845F740F8B01bC7D9A4C82a6fD4A60320c07AF1' // WBNB -> BNB
|
||||||
const l1ChainId = 56
|
const l1ChainId = 56
|
||||||
|
const multisig = '0xE3611102E23a43136a13993E3a00BAD67da19119'
|
||||||
|
|
||||||
const Verifier2 = await ethers.getContractFactory('Verifier2')
|
const Verifier2 = await ethers.getContractFactory('Verifier2')
|
||||||
const verifier2 = await Verifier2.deploy()
|
const verifier2 = await Verifier2.deploy()
|
||||||
@ -41,24 +42,28 @@ async function main() {
|
|||||||
l1Unwrapper,
|
l1Unwrapper,
|
||||||
govAddress,
|
govAddress,
|
||||||
l1ChainId,
|
l1ChainId,
|
||||||
|
multisig,
|
||||||
]).slice(1, -1)}\n`,
|
]).slice(1, -1)}\n`,
|
||||||
)
|
)
|
||||||
const tornadoImpl = prompt('Deploy tornado pool implementation and provide address here:\n')
|
|
||||||
// const tornadoImpl = await Pool.deploy(
|
//const tornadoImpl = prompt('Deploy tornado pool implementation and provide address here:\n')
|
||||||
// verifier2.address,
|
const tornadoImpl = await Pool.deploy(
|
||||||
// verifier16.address,
|
verifier2.address,
|
||||||
// MERKLE_TREE_HEIGHT,
|
verifier16.address,
|
||||||
// hasher.address,
|
MERKLE_TREE_HEIGHT,
|
||||||
// token,
|
hasher.address,
|
||||||
// omniBridge,
|
token,
|
||||||
// l1Unwrapper,
|
omniBridge,
|
||||||
// govAddress,
|
l1Unwrapper,
|
||||||
// )
|
govAddress,
|
||||||
// await tornadoImpl.deployed()
|
l1ChainId,
|
||||||
// console.log(`TornadoPool implementation address: ${tornadoImpl.address}`)
|
multisig,
|
||||||
|
)
|
||||||
|
await tornadoImpl.deployed()
|
||||||
|
console.log(`TornadoPool implementation address: ${tornadoImpl.address}`)
|
||||||
|
|
||||||
const CrossChainUpgradeableProxy = await ethers.getContractFactory('CrossChainUpgradeableProxy')
|
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()
|
await proxy.deployed()
|
||||||
console.log(`proxy address: ${proxy.address}`)
|
console.log(`proxy address: ${proxy.address}`)
|
||||||
|
|
||||||
|
28
scripts/deployTornadoUpgrade.js
Normal file
28
scripts/deployTornadoUpgrade.js
Normal 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)
|
||||||
|
})
|
68
src/0_generateAddresses.js
Normal file
68
src/0_generateAddresses.js
Normal 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,
|
||||||
|
}
|
15
src/index.js
15
src/index.js
@ -16,7 +16,17 @@ async function buildMerkleTree({ tornadoPool }) {
|
|||||||
return new MerkleTree(MERKLE_TREE_HEIGHT, leaves, { hashFunction: poseidonHash2 })
|
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)
|
inputs = shuffle(inputs)
|
||||||
outputs = shuffle(outputs)
|
outputs = shuffle(outputs)
|
||||||
|
|
||||||
@ -45,6 +55,7 @@ async function getProof({ inputs, outputs, tree, extAmount, fee, recipient, rela
|
|||||||
encryptedOutput1: outputs[0].encrypt(),
|
encryptedOutput1: outputs[0].encrypt(),
|
||||||
encryptedOutput2: outputs[1].encrypt(),
|
encryptedOutput2: outputs[1].encrypt(),
|
||||||
isL1Withdrawal,
|
isL1Withdrawal,
|
||||||
|
l1Fee,
|
||||||
}
|
}
|
||||||
|
|
||||||
const extDataHash = getExtDataHash(extData)
|
const extDataHash = getExtDataHash(extData)
|
||||||
@ -94,6 +105,7 @@ async function prepareTransaction({
|
|||||||
recipient = 0,
|
recipient = 0,
|
||||||
relayer = 0,
|
relayer = 0,
|
||||||
isL1Withdrawal = false,
|
isL1Withdrawal = false,
|
||||||
|
l1Fee = 0,
|
||||||
}) {
|
}) {
|
||||||
if (inputs.length > 16 || outputs.length > 2) {
|
if (inputs.length > 16 || outputs.length > 2) {
|
||||||
throw new Error('Incorrect inputs/outputs count')
|
throw new Error('Incorrect inputs/outputs count')
|
||||||
@ -118,6 +130,7 @@ async function prepareTransaction({
|
|||||||
recipient,
|
recipient,
|
||||||
relayer,
|
relayer,
|
||||||
isL1Withdrawal,
|
isL1Withdrawal,
|
||||||
|
l1Fee,
|
||||||
})
|
})
|
||||||
|
|
||||||
return {
|
return {
|
||||||
|
@ -22,12 +22,13 @@ function getExtDataHash({
|
|||||||
encryptedOutput1,
|
encryptedOutput1,
|
||||||
encryptedOutput2,
|
encryptedOutput2,
|
||||||
isL1Withdrawal,
|
isL1Withdrawal,
|
||||||
|
l1Fee,
|
||||||
}) {
|
}) {
|
||||||
const abi = new ethers.utils.AbiCoder()
|
const abi = new ethers.utils.AbiCoder()
|
||||||
|
|
||||||
const encodedData = abi.encode(
|
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,
|
encryptedOutput1: encryptedOutput1,
|
||||||
encryptedOutput2: encryptedOutput2,
|
encryptedOutput2: encryptedOutput2,
|
||||||
isL1Withdrawal: isL1Withdrawal,
|
isL1Withdrawal: isL1Withdrawal,
|
||||||
|
l1Fee: l1Fee,
|
||||||
},
|
},
|
||||||
],
|
],
|
||||||
)
|
)
|
||||||
|
@ -9,10 +9,11 @@ const { transaction, registerAndTransact, prepareTransaction, buildMerkleTree }
|
|||||||
const { toFixedHex, poseidonHash } = require('../src/utils')
|
const { toFixedHex, poseidonHash } = require('../src/utils')
|
||||||
const { Keypair } = require('../src/keypair')
|
const { Keypair } = require('../src/keypair')
|
||||||
const { encodeDataForBridge } = require('./utils')
|
const { encodeDataForBridge } = require('./utils')
|
||||||
|
const config = require('../config')
|
||||||
|
const { generate } = require('../src/0_generateAddresses')
|
||||||
|
|
||||||
const MERKLE_TREE_HEIGHT = 5
|
const MERKLE_TREE_HEIGHT = 5
|
||||||
const l1ChainId = 1
|
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')
|
const MAXIMUM_DEPOSIT_AMOUNT = utils.parseEther(process.env.MAXIMUM_DEPOSIT_AMOUNT || '1')
|
||||||
|
|
||||||
describe('TornadoPool', function () {
|
describe('TornadoPool', function () {
|
||||||
@ -26,7 +27,7 @@ describe('TornadoPool', function () {
|
|||||||
|
|
||||||
async function fixture() {
|
async function fixture() {
|
||||||
require('../scripts/compileHasher')
|
require('../scripts/compileHasher')
|
||||||
const [sender, gov, l1Unwrapper, multisig] = await ethers.getSigners()
|
const [sender, gov, multisig] = await ethers.getSigners()
|
||||||
const verifier2 = await deploy('Verifier2')
|
const verifier2 = await deploy('Verifier2')
|
||||||
const verifier16 = await deploy('Verifier16')
|
const verifier16 = await deploy('Verifier16')
|
||||||
const hasher = await deploy('Hasher')
|
const hasher = await deploy('Hasher')
|
||||||
@ -34,9 +35,23 @@ describe('TornadoPool', function () {
|
|||||||
const token = await deploy('PermittableToken', 'Wrapped ETH', 'WETH', 18, l1ChainId)
|
const token = await deploy('PermittableToken', 'Wrapped ETH', 'WETH', 18, l1ChainId)
|
||||||
await token.mint(sender.address, utils.parseEther('10000'))
|
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 amb = await deploy('MockAMB', gov.address, l1ChainId)
|
||||||
const omniBridge = await deploy('MockOmniBridge', amb.address)
|
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} */
|
/** @type {TornadoPool} */
|
||||||
const tornadoPoolImpl = await deploy(
|
const tornadoPoolImpl = await deploy(
|
||||||
'TornadoPool',
|
'TornadoPool',
|
||||||
@ -52,10 +67,7 @@ describe('TornadoPool', function () {
|
|||||||
multisig.address,
|
multisig.address,
|
||||||
)
|
)
|
||||||
|
|
||||||
const { data } = await tornadoPoolImpl.populateTransaction.initialize(
|
const { data } = await tornadoPoolImpl.populateTransaction.initialize(MAXIMUM_DEPOSIT_AMOUNT)
|
||||||
MINIMUM_WITHDRAWAL_AMOUNT,
|
|
||||||
MAXIMUM_DEPOSIT_AMOUNT,
|
|
||||||
)
|
|
||||||
const proxy = await deploy(
|
const proxy = await deploy(
|
||||||
'CrossChainUpgradeableProxy',
|
'CrossChainUpgradeableProxy',
|
||||||
tornadoPoolImpl.address,
|
tornadoPoolImpl.address,
|
||||||
@ -69,7 +81,7 @@ describe('TornadoPool', function () {
|
|||||||
|
|
||||||
await token.approve(tornadoPool.address, utils.parseEther('10000'))
|
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', () => {
|
describe('Upgradeability tests', () => {
|
||||||
@ -88,19 +100,12 @@ describe('TornadoPool', function () {
|
|||||||
})
|
})
|
||||||
|
|
||||||
it('should configure', async () => {
|
it('should configure', async () => {
|
||||||
const { tornadoPool, amb } = await loadFixture(fixture)
|
const { tornadoPool, multisig } = await loadFixture(fixture)
|
||||||
const newWithdrawalLimit = utils.parseEther('0.01337')
|
|
||||||
const newDepositLimit = utils.parseEther('1337')
|
const newDepositLimit = utils.parseEther('1337')
|
||||||
|
|
||||||
const { data } = await tornadoPool.populateTransaction.configureLimits(
|
await tornadoPool.connect(multisig).configureLimits(newDepositLimit)
|
||||||
newWithdrawalLimit,
|
|
||||||
newDepositLimit,
|
|
||||||
)
|
|
||||||
|
|
||||||
await amb.execute([{ who: tornadoPool.address, callData: data }])
|
|
||||||
|
|
||||||
expect(await tornadoPool.maximumDepositAmount()).to.be.equal(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)
|
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 () {
|
it('should transfer funds to multisig in case of L1 deposit fail', async function () {
|
||||||
const { tornadoPool, token, omniBridge, multisig } = await loadFixture(fixture)
|
const { tornadoPool, token, omniBridge, multisig } = await loadFixture(fixture)
|
||||||
const aliceKeypair = new Keypair() // contains private and public keys
|
const aliceKeypair = new Keypair() // contains private and public keys
|
||||||
|
@ -71,11 +71,11 @@ describe('MerkleTreeWithHistory', function () {
|
|||||||
it('should insert', async () => {
|
it('should insert', async () => {
|
||||||
const { merkleTreeWithHistory } = await loadFixture(fixture)
|
const { merkleTreeWithHistory } = await loadFixture(fixture)
|
||||||
const tree = getNewTree()
|
const tree = getNewTree()
|
||||||
merkleTreeWithHistory.insert(toFixedHex(123), toFixedHex(456))
|
await merkleTreeWithHistory.insert(toFixedHex(123), toFixedHex(456))
|
||||||
tree.bulkInsert([123, 456])
|
tree.bulkInsert([123, 456])
|
||||||
expect(tree.root()).to.be.be.equal(await merkleTreeWithHistory.getLastRoot())
|
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])
|
tree.bulkInsert([678, 876])
|
||||||
expect(tree.root()).to.be.be.equal(await merkleTreeWithHistory.getLastRoot())
|
expect(tree.root()).to.be.be.equal(await merkleTreeWithHistory.getLastRoot())
|
||||||
})
|
})
|
||||||
|
@ -6,7 +6,7 @@ function encodeDataForBridge({ proof, extData }) {
|
|||||||
return abi.encode(
|
return abi.encode(
|
||||||
[
|
[
|
||||||
'tuple(bytes proof,bytes32 root,bytes32[] inputNullifiers,bytes32[2] outputCommitments,uint256 publicAmount,bytes32 extDataHash)',
|
'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],
|
[proof, extData],
|
||||||
)
|
)
|
||||||
|
10
yarn.lock
10
yarn.lock
@ -736,11 +736,6 @@
|
|||||||
resolved "https://registry.yarnpkg.com/@openzeppelin/contracts-upgradeable/-/contracts-upgradeable-3.4.2.tgz#2c2a1b0fa748235a1f495b6489349776365c51b3"
|
resolved "https://registry.yarnpkg.com/@openzeppelin/contracts-upgradeable/-/contracts-upgradeable-3.4.2.tgz#2c2a1b0fa748235a1f495b6489349776365c51b3"
|
||||||
integrity sha512-mDlBS17ymb2wpaLcrqRYdnBAmP1EwqhOXMvqWk2c5Q1N1pm5TkiCtXM9Xzznh4bYsQBq0aIWEkFFE2+iLSN1Tw==
|
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":
|
"@openzeppelin@git+https://github.com/tornadocash/openzeppelin-contracts.git#6e46aa6946a7f215e7604169ddf46e1aebea850f":
|
||||||
version "3.4.1-solc-0.7-2"
|
version "3.4.1-solc-0.7-2"
|
||||||
resolved "git+https://github.com/tornadocash/openzeppelin-contracts.git#6e46aa6946a7f215e7604169ddf46e1aebea850f"
|
resolved "git+https://github.com/tornadocash/openzeppelin-contracts.git#6e46aa6946a7f215e7604169ddf46e1aebea850f"
|
||||||
@ -6918,11 +6913,10 @@ oboe@2.1.5:
|
|||||||
dependencies:
|
dependencies:
|
||||||
http-https "^1.0.0"
|
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"
|
version "1.1.0"
|
||||||
resolved "git+https://github.com/peppersec/omnibridge.git#aa3a970c29752a4da5f3fc7ccf0733783c1acf0b"
|
resolved "git+https://github.com/peppersec/omnibridge.git#30081f7a735eb03c9d6821a9617cc28efe71a682"
|
||||||
dependencies:
|
dependencies:
|
||||||
"@openzeppelin/contracts" "3.2.2-solc-0.7"
|
|
||||||
axios "^0.21.0"
|
axios "^0.21.0"
|
||||||
bignumber.js "^9.0.1"
|
bignumber.js "^9.0.1"
|
||||||
dotenv "^8.2.0"
|
dotenv "^8.2.0"
|
||||||
|
Loading…
Reference in New Issue
Block a user