return refund to a relayer in case of failure

This commit is contained in:
Alexey 2019-11-15 10:59:06 +03:00
parent a6519fb280
commit ce550eea58
3 changed files with 96 additions and 2 deletions

View File

@ -38,8 +38,13 @@ contract ERC20Mixer is Mixer {
if (_fee > 0) { if (_fee > 0) {
_safeErc20Transfer(_relayer, _fee); _safeErc20Transfer(_relayer, _fee);
} }
if (_refund > 0) { if (_refund > 0) {
_recipient.call.value(_refund)(""); (bool success, ) = _recipient.call.value(_refund)("");
if (!success) {
// let's return _refund back to the relayer
_relayer.transfer(_refund);
}
} }
} }

View File

@ -0,0 +1,7 @@
pragma solidity ^0.5.0;
contract BadRecipient {
function() external {
require(false, "this contract does not accept ETH");
}
}

View File

@ -9,6 +9,7 @@ const { toBN, toHex } = require('web3-utils')
const { takeSnapshot, revertSnapshot } = require('../lib/ganacheHelper') const { takeSnapshot, revertSnapshot } = require('../lib/ganacheHelper')
const Mixer = artifacts.require('./ERC20Mixer.sol') const Mixer = artifacts.require('./ERC20Mixer.sol')
const BadRecipient = artifacts.require('./BadRecipient.sol')
const Token = artifacts.require('./ERC20Mock.sol') const Token = artifacts.require('./ERC20Mock.sol')
const USDTToken = artifacts.require('./IUSDT.sol') const USDTToken = artifacts.require('./IUSDT.sol')
const { ETH_AMOUNT, TOKEN_AMOUNT, MERKLE_TREE_HEIGHT, ERC20_TOKEN } = process.env const { ETH_AMOUNT, TOKEN_AMOUNT, MERKLE_TREE_HEIGHT, ERC20_TOKEN } = process.env
@ -54,6 +55,7 @@ contract('ERC20Mixer', accounts => {
let mixer let mixer
let token let token
let usdtToken let usdtToken
let badRecipient
const sender = accounts[0] const sender = accounts[0]
const operator = accounts[0] const operator = accounts[0]
const levels = MERKLE_TREE_HEIGHT || 16 const levels = MERKLE_TREE_HEIGHT || 16
@ -63,7 +65,7 @@ contract('ERC20Mixer', accounts => {
let tree let tree
const fee = bigInt(ETH_AMOUNT).shr(1) || bigInt(1e17) const fee = bigInt(ETH_AMOUNT).shr(1) || bigInt(1e17)
const refund = ETH_AMOUNT || '1000000000000000000' // 1 ether const refund = ETH_AMOUNT || '1000000000000000000' // 1 ether
const recipient = getRandomRecipient() let recipient = getRandomRecipient()
const relayer = accounts[1] const relayer = accounts[1]
let groth16 let groth16
let circuit let circuit
@ -83,6 +85,7 @@ contract('ERC20Mixer', accounts => {
token = await Token.deployed() token = await Token.deployed()
await token.mint(sender, tokenDenomination) await token.mint(sender, tokenDenomination)
} }
badRecipient = await BadRecipient.new()
snapshotId = await takeSnapshot() snapshotId = await takeSnapshot()
groth16 = await buildGroth16() groth16 = await buildGroth16()
circuit = require('../build/circuits/withdraw.json') circuit = require('../build/circuits/withdraw.json')
@ -201,6 +204,85 @@ contract('ERC20Mixer', accounts => {
isSpent.should.be.equal(true) isSpent.should.be.equal(true)
}) })
it('should return refund to the relayer is case of fail', async () => {
const deposit = generateDeposit()
const user = accounts[4]
recipient = bigInt(badRecipient.address)
await tree.insert(deposit.commitment)
await token.mint(user, tokenDenomination)
const balanceUserBefore = await token.balanceOf(user)
await token.approve(mixer.address, tokenDenomination, { from: user })
await mixer.deposit(toFixedHex(deposit.commitment), { from: user, gasPrice: '0' })
const balanceUserAfter = await token.balanceOf(user)
balanceUserAfter.should.be.eq.BN(toBN(balanceUserBefore).sub(toBN(tokenDenomination)))
const { root, path_elements, path_index } = await tree.path(0)
// Circuit input
const input = stringifyBigInts({
// public
root,
nullifierHash: pedersenHash(deposit.nullifier.leInt2Buff(31)),
relayer,
recipient,
fee,
refund,
// private
nullifier: deposit.nullifier,
secret: deposit.secret,
pathElements: path_elements,
pathIndices: path_index,
})
const proofData = await websnarkUtils.genWitnessAndProve(groth16, input, circuit, proving_key)
const { proof } = websnarkUtils.toSolidityInput(proofData)
const balanceMixerBefore = await token.balanceOf(mixer.address)
const balanceRelayerBefore = await token.balanceOf(relayer)
const balanceRecieverBefore = await token.balanceOf(toHex(recipient.toString()))
const ethBalanceOperatorBefore = await web3.eth.getBalance(operator)
const ethBalanceRecieverBefore = await web3.eth.getBalance(toHex(recipient.toString()))
const ethBalanceRelayerBefore = await web3.eth.getBalance(relayer)
let isSpent = await mixer.isSpent(toFixedHex(input.nullifierHash))
isSpent.should.be.equal(false)
const args = [
toFixedHex(input.root),
toFixedHex(input.nullifierHash),
toFixedHex(input.recipient, 20),
toFixedHex(input.relayer, 20),
toFixedHex(input.fee),
toFixedHex(input.refund)
]
const { logs } = await mixer.withdraw(proof, ...args, { value: refund, from: relayer, gasPrice: '0' })
const balanceMixerAfter = await token.balanceOf(mixer.address)
const balanceRelayerAfter = await token.balanceOf(relayer)
const ethBalanceOperatorAfter = await web3.eth.getBalance(operator)
const balanceRecieverAfter = await token.balanceOf(toHex(recipient.toString()))
const ethBalanceRecieverAfter = await web3.eth.getBalance(toHex(recipient.toString()))
const ethBalanceRelayerAfter = await web3.eth.getBalance(relayer)
const feeBN = toBN(fee.toString())
balanceMixerAfter.should.be.eq.BN(toBN(balanceMixerBefore).sub(toBN(tokenDenomination)))
balanceRelayerAfter.should.be.eq.BN(toBN(balanceRelayerBefore).add(feeBN))
balanceRecieverAfter.should.be.eq.BN(toBN(balanceRecieverBefore).add(toBN(tokenDenomination).sub(feeBN)))
ethBalanceOperatorAfter.should.be.eq.BN(toBN(ethBalanceOperatorBefore))
ethBalanceRecieverAfter.should.be.eq.BN(toBN(ethBalanceRecieverBefore))
ethBalanceRelayerAfter.should.be.eq.BN(toBN(ethBalanceRelayerBefore))
logs[0].event.should.be.equal('Withdrawal')
logs[0].args.nullifierHash.should.be.equal(toFixedHex(input.nullifierHash))
logs[0].args.relayer.should.be.eq.BN(relayer)
logs[0].args.fee.should.be.eq.BN(feeBN)
isSpent = await mixer.isSpent(toFixedHex(input.nullifierHash))
isSpent.should.be.equal(true)
})
it('should reject with wrong refund value', async () => { it('should reject with wrong refund value', async () => {
const deposit = generateDeposit() const deposit = generateDeposit()
const user = accounts[4] const user = accounts[4]