withdraw to L1 support and test

This commit is contained in:
Alexey 2021-09-30 18:34:07 +03:00
parent a28d887643
commit 5e14553dfb
5 changed files with 92 additions and 11 deletions

2
.gitignore vendored
View File

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

View File

@ -17,4 +17,34 @@ contract MockOmniBridge is IOmniBridge {
function execute(address _who, bytes calldata _calldata) external returns (bool success, bytes memory result) { function execute(address _who, bytes calldata _calldata) external returns (bool success, bytes memory result) {
(success, result) = _who.call(_calldata); (success, result) = _who.call(_calldata);
} }
event OnTokenTransfer(address contr, address from, address receiver, uint256 value, bytes data);
function onTokenTransfer(
address _from,
uint256 _value,
bytes memory _data
) external returns (bool) {
bytes memory data = new bytes(0);
address receiver = _from;
if (_data.length >= 20) {
receiver = bytesToAddress(_data);
if (_data.length > 20) {
assembly {
let size := sub(mload(_data), 20)
data := add(_data, 20)
mstore(data, size)
}
}
}
emit OnTokenTransfer(msg.sender, _from, receiver, _value, data);
//bridgeSpecificActionsOnTokenTransfer(msg.sender, _from, receiver, _value, data);
return true;
}
function bytesToAddress(bytes memory _bytes) internal pure returns (address addr) {
assembly {
addr := mload(add(_bytes, 20))
}
}
} }

View File

@ -16,6 +16,8 @@ pragma experimental ABIEncoderV2;
import "@openzeppelin/contracts/contracts/token/ERC20/IERC20.sol"; import "@openzeppelin/contracts/contracts/token/ERC20/IERC20.sol";
import "./MerkleTreeWithHistory.sol"; import "./MerkleTreeWithHistory.sol";
import "hardhat/console.sol";
interface IERC6777 is IERC20 { interface IERC6777 is IERC20 {
function transferAndCall( function transferAndCall(
address, address,
@ -47,6 +49,7 @@ contract TornadoPool is MerkleTreeWithHistory, IERC20Receiver {
IVerifier public immutable verifier16; IVerifier public immutable verifier16;
IERC6777 public immutable token; IERC6777 public immutable token;
address public immutable omniBridge; address public immutable omniBridge;
address public immutable l1Unwrapper;
struct ExtData { struct ExtData {
address recipient; address recipient;
@ -55,7 +58,7 @@ contract TornadoPool is MerkleTreeWithHistory, IERC20Receiver {
uint256 fee; uint256 fee;
bytes encryptedOutput1; bytes encryptedOutput1;
bytes encryptedOutput2; bytes encryptedOutput2;
bool isL1Withdraw; bool isL1Withdrawal;
} }
struct Proof { struct Proof {
@ -88,12 +91,14 @@ contract TornadoPool is MerkleTreeWithHistory, IERC20Receiver {
uint32 _levels, uint32 _levels,
address _hasher, address _hasher,
IERC6777 _token, IERC6777 _token,
address _omniBridge address _omniBridge,
address _l1Unwrapper
) MerkleTreeWithHistory(_levels, _hasher) { ) MerkleTreeWithHistory(_levels, _hasher) {
verifier2 = _verifier2; verifier2 = _verifier2;
verifier16 = _verifier16; verifier16 = _verifier16;
token = _token; token = _token;
omniBridge = _omniBridge; omniBridge = _omniBridge;
l1Unwrapper = _l1Unwrapper;
} }
function transact(Proof memory _args, ExtData memory _extData) public { function transact(Proof memory _args, ExtData memory _extData) public {
@ -120,13 +125,12 @@ contract TornadoPool is MerkleTreeWithHistory, IERC20Receiver {
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.isL1Withdraw) { if (_extData.isL1Withdrawal) {
token.transferAndCall(omniBridge, uint256(-_extData.extAmount), abi.encode(_extData.recipient)); token.transferAndCall(omniBridge, uint256(-_extData.extAmount), abi.encodePacked(l1Unwrapper, _extData.recipient));
} else { } else {
token.transfer(_extData.recipient, uint256(-_extData.extAmount)); token.transfer(_extData.recipient, uint256(-_extData.extAmount));
} }
} }
if (_extData.fee > 0) { if (_extData.fee > 0) {
token.transfer(_extData.relayer, _extData.fee); token.transfer(_extData.relayer, _extData.fee);
} }

View File

@ -146,7 +146,7 @@ async function transaction({ tornadoPool, ...rest }) {
const receipt = await tornadoPool.transact(args, extData, { const receipt = await tornadoPool.transact(args, extData, {
gasLimit: 1e6, gasLimit: 1e6,
}) })
await receipt.wait() return await receipt.wait()
} }
async function registerAndTransact({ tornadoPool, packedPrivateKeyData, poolAddress, ...rest }) { async function registerAndTransact({ tornadoPool, packedPrivateKeyData, poolAddress, ...rest }) {
@ -166,4 +166,4 @@ async function registerAndTransact({ tornadoPool, packedPrivateKeyData, poolAddr
await receipt.wait() await receipt.wait()
} }
module.exports = { transaction, registerAndTransact } module.exports = { transaction, registerAndTransact, prepareTransaction }

View File

@ -5,7 +5,7 @@ const { expect } = require('chai')
const { utils } = ethers const { utils } = ethers
const Utxo = require('../src/utxo') const Utxo = require('../src/utxo')
const { transaction, registerAndTransact } = require('../src/index') const { transaction, registerAndTransact, prepareTransaction } = require('../src/index')
const { Keypair } = require('../src/keypair') const { Keypair } = require('../src/keypair')
const MERKLE_TREE_HEIGHT = 5 const MERKLE_TREE_HEIGHT = 5
@ -21,7 +21,7 @@ describe('TornadoPool', function () {
async function fixture() { async function fixture() {
require('../scripts/compileHasher') require('../scripts/compileHasher')
const [sender, gov] = await ethers.getSigners() const [sender, gov, l1Unwrapper] = 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')
@ -41,6 +41,7 @@ describe('TornadoPool', function () {
hasher.address, hasher.address,
token.address, token.address,
omniBridge.address, omniBridge.address,
l1Unwrapper.address,
) )
await tornadoPool.initialize() await tornadoPool.initialize()
@ -211,6 +212,54 @@ describe('TornadoPool', function () {
expect(bobBalance).to.be.equal(bobWithdrawAmount) expect(bobBalance).to.be.equal(bobWithdrawAmount)
}) })
it('should deposit from L1 and withdraw to L1', async function () {
const { tornadoPool, token, omniBridge } = await loadFixture(fixture)
// console.log('tornadoPool', tornadoPool.interface)
// Alice deposits into tornado pool
const aliceDepositAmount = 1e7
const aliceDepositUtxo = new Utxo({ amount: aliceDepositAmount })
const { args, extData } = await prepareTransaction({
tornadoPool,
outputs: [aliceDepositUtxo],
})
const transactTx = await tornadoPool.populateTransaction.registerAndTransact(
{ pubKey: [], account: [] },
args,
extData,
)
const onTokenBridgedData = '0x' + transactTx.data.slice(10)
const onTokenBridgedTx = await tornadoPool.populateTransaction.onTokenBridged(
token.address,
aliceDepositUtxo.amount,
onTokenBridgedData,
)
// emulating bridge. first it sends tokens and then calls onTokenBridged method
await token.transfer(tornadoPool.address, aliceDepositAmount)
await omniBridge.execute(tornadoPool.address, onTokenBridgedTx.data)
// withdraws a part of his funds from the shielded pool
const aliceKeypair = new Keypair() // contains private and public keys
const aliceWithdrawAmount = 2e6
const recipient = '0xDeaD00000000000000000000000000000000BEEf'
const aliceChangeUtxo = new Utxo({
amount: aliceDepositAmount - aliceWithdrawAmount,
keypair: aliceKeypair,
})
await transaction({
tornadoPool,
inputs: [aliceDepositUtxo],
outputs: [aliceChangeUtxo],
recipient: recipient,
isL1Withdrawal: true,
})
const recipientBalance = await token.balanceOf(recipient)
expect(recipientBalance).to.be.equal(0)
const omniBridgeBalance = await token.balanceOf(omniBridge.address)
expect(omniBridgeBalance).to.be.equal(aliceWithdrawAmount)
})
it('should work with 16 inputs', async function () { it('should work with 16 inputs', async function () {
const { tornadoPool } = await loadFixture(fixture) const { tornadoPool } = await loadFixture(fixture)
await transaction({ tornadoPool, inputs: [new Utxo(), new Utxo(), new Utxo()] }) await transaction({ tornadoPool, inputs: [new Utxo(), new Utxo(), new Utxo()] })