Skip to content

Commit

Permalink
add L1Receiver logic to unwrapper
Browse files Browse the repository at this point in the history
  • Loading branch information
3q-coder committed Feb 14, 2022
1 parent abef8ad commit 0530f00
Show file tree
Hide file tree
Showing 2 changed files with 133 additions and 1 deletion.
25 changes: 24 additions & 1 deletion contracts/bridge/L1Unwrapper.sol
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,13 @@ import { BytesHelper } from "../libraries/Bytes.sol";
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 {
Expand Down Expand Up @@ -88,6 +95,22 @@ contract L1Unwrapper is WETHOmnibridgeRouter {
uint256 l1Fee = BytesHelper.sliceToUint(_data, 20);

AddressHelper.safeSendValue(payable(BytesHelper.bytesToAddress(_data)), _value.sub(l1Fee));
AddressHelper.safeSendValue(payable(tx.origin), l1Fee);

address payable l1FeeTo;
if (l1FeeReceiver != payable(address(0))) {
l1FeeTo = l1FeeReceiver;
} else {
l1FeeTo = 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;
}
}
109 changes: 109 additions & 0 deletions test/full.test.js
Original file line number Diff line number Diff line change
Expand Up @@ -48,6 +48,7 @@ describe('TornadoPool', function () {
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)
Expand Down Expand Up @@ -368,6 +369,114 @@ describe('TornadoPool', function () {
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(42)
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
Expand Down

0 comments on commit 0530f00

Please sign in to comment.