/* eslint-env node, mocha */
/* global artifacts, contract, expect, web3 */
/* eslint no-underscore-dangle: 1 */
const Tx = require('ethereumjs-tx').Transaction
const ethereumUtil = require('ethereumjs-util')
const moment = require('moment')
const BigNumber = web3.utils.BN
const ERC20 = artifacts.require('./Mocks/MockToken.sol')
const ETHSender = artifacts.require('./ETHSender.sol')
const Proxy = artifacts.require('./Proxy.sol')
const sender = '0xdb1b9e1708aec862fee256821702fa1906ceff67'
const senderPrivateKey = Buffer.from('a8345d27c6d41e4816163fe133daddf38298bb74c16ea5f8245727d03a5f85f8', 'hex')
contract('Proxy', (accounts) => {
const [ProxyOwner] = accounts
const [, TokenOwner] = accounts
const [, , issuer] = accounts
const [, , , receiver] = accounts
describe('forwardWithNoValue', () => {
let ETHSenderInstance
let MockTokenInstance
let ProxyInstance
beforeEach(async () => {
ProxyInstance = await Proxy.new({ from: ProxyOwner })
ETHSenderInstance = await ETHSender.new(ProxyInstance.address, { from: issuer, value: web3.utils.toWei('1') })
MockTokenInstance = await ERC20.new(
'MockToken',
'ERC20',
18,
web3.utils.toWei('100'),
{ from: TokenOwner },
)
await MockTokenInstance.transfer(sender, web3.utils.toWei('100'), { from: TokenOwner })
})
it('Should complete successfully the entire flow where an etherless account pay a transaction fee in ERC20', async () => {
// sender:
// - has no ETH
// - has 100 ERC0
// - want to send 10 ERC20 to another account
// - never approved issuer to move its funds
// in order to send the 10 ERC0 to another account the flow needs:
// - create the raw transaction of the approveTx (approve(Proxy, ~ ∞))
// - create the raw transaction of the primaryTx (transfer(receiver, amount))
// - create a metaTx signed by issuer and sender for the approve where
// - 0 ERC20 are asked to the user by issuer
// - the equivalent of the approve cost is sent to the user by issuer in ETH
// - as soon the sender receive the ETH for the approve the approve raw transaction must be broadcasted
// - create a metaTx signed by issuer and sender for the original Tx where
// - the equivalent of the original Tx cost is withdraw from the sender by issuer in ERC20
// - the equivalent of the original Tx cost is sent to the user by issuer in ETH
// - as soon the sender receive the ETH for the original Tx it must be broadcasted
const senderERC20InitialBalance = await MockTokenInstance.balanceOf(sender)
const amountToReceiver = new BigNumber(web3.utils.toWei('10'))
// prepare approve
const approveTxData = MockTokenInstance.contract.methods.approve(ProxyInstance.address, web3.utils.toWei('1000000000000000000000')).encodeABI()
const approveGasEstimation = new BigNumber(await MockTokenInstance.approve.estimateGas(ProxyInstance.address, web3.utils.toWei('1000000000000000000000'), { from: sender }))
const approveTx = new Tx({
from: sender,
to: MockTokenInstance.address,
data: approveTxData,
nonce: web3.utils.toHex(await web3.eth.getTransactionCount(sender)),
value: web3.utils.toHex('0'),
gasPrice: web3.utils.toHex(web3.utils.toWei('0.00000001')),
gas: web3.utils.toHex(approveGasEstimation),
})
// sign approve
approveTx.sign(senderPrivateKey)
const approveRawTx = ethereumUtil.bufferToHex(approveTx.serialize())
const approveETHCost = approveTx.getUpfrontCost()
// prepare transfer which is the primary sender intention
const primaryTxData = MockTokenInstance.contract.methods.transfer(receiver, amountToReceiver.toString()).encodeABI()
const primaryGasEstimation = new BigNumber(await MockTokenInstance.transfer.estimateGas(receiver, amountToReceiver.toString(), { from: sender }))
const primaryTx = new Tx({
to: MockTokenInstance.address,
data: primaryTxData,
nonce: web3.utils.toHex(await web3.eth.getTransactionCount(sender) + 1),
value: web3.utils.toHex('0'),
gasPrice: web3.utils.toHex(web3.utils.toWei('0.00000001')),
gas: web3.utils.toHex(primaryGasEstimation), // web3.utils.toHex(primaryGasEstimation.add(new BigNumber('30000')))
})
// sign primary tx
primaryTx.sign(senderPrivateKey)
const primaryRawTx = ethereumUtil.bufferToHex(primaryTx.serialize())
const primaryETHCost = primaryTx.getUpfrontCost()
// prepare approve meta tx
// transferFrom(sender, issuer, 0)
let senderMetaTxData = MockTokenInstance
.contract
.methods
.transferFrom(sender, issuer, '0').encodeABI()
// sendETH(issuer, sender, approveETHCost)
let issuerMetaTxData = ETHSenderInstance
.contract
.methods
.sendETH(issuer, sender, approveETHCost.toString()).encodeABI()
// commons
let recipients = [MockTokenInstance.address, ETHSenderInstance.address]
let metaTxsValue = [new BigNumber('0'), new BigNumber('0')]
let packedTxsDataField = senderMetaTxData + issuerMetaTxData.substring(2)
let txsDataSizes = [senderMetaTxData.substring(2).length / 2, issuerMetaTxData.substring(2).length / 2]
let salt = new BigNumber(web3.utils.randomHex(32))
let expiration = new BigNumber(moment().unix()).add(new BigNumber('72000'))
// hash
let hashedMetaTx = web3.utils.soliditySha3(
{ t: 'address[]', v: recipients },
{ t: 'uint256[]', v: metaTxsValue },
{ t: 'bytes', v: packedTxsDataField },
{ t: 'uint256', v: salt },
{ t: 'uint256', v: expiration },
)
// sign
let senderSig = ethereumUtil.ecsign(
ethereumUtil.hashPersonalMessage(Buffer.from(hashedMetaTx.substring(2), 'hex')),
senderPrivateKey,
)
let senderSigHex = ethereumUtil.bufferToHex(Buffer.concat([senderSig.r, senderSig.s])) + web3.utils.toHex(senderSig.v).substring(2)
let packedSignature = senderSigHex
await ProxyInstance.forwardWithNoValue(
packedSignature,
recipients,
packedTxsDataField,
txsDataSizes,
salt,
expiration,
{ from: issuer },
)
expect(await web3.eth.getBalance(sender)).to.equal(approveETHCost.toString())
// send the previously signed approve tx
await web3.eth.sendSignedTransaction(approveRawTx)
expect((await MockTokenInstance.allowance(sender, ProxyInstance.address)).toString()).to.equal(web3.utils.toWei('1000000000000000000000'))
// the empty account ETH balance should be = to 0
expect(await web3.eth.getBalance(sender)).to.equal('0')
// prepare transfer meta tx
const transferERC20FeeCost = new BigNumber(web3.utils.toWei('1'))
// transferFrom(sender, issuer, 1)
senderMetaTxData = MockTokenInstance
.contract
.methods
.transferFrom(sender, issuer, transferERC20FeeCost.toString()).encodeABI()
// sendETH(issuer, sender, primaryETHCost)
issuerMetaTxData = ETHSenderInstance
.contract
.methods
.sendETH(issuer, sender, primaryETHCost.toString()).encodeABI()
// commons
recipients = [MockTokenInstance.address, ETHSenderInstance.address]
metaTxsValue = [new BigNumber('0'), new BigNumber('0')]
packedTxsDataField = senderMetaTxData + issuerMetaTxData.substring(2)
txsDataSizes = [senderMetaTxData.substring(2).length / 2, issuerMetaTxData.substring(2).length / 2]
salt = new BigNumber(web3.utils.randomHex(32))
expiration = new BigNumber(moment().unix()).add(new BigNumber('72000'))
// hash
hashedMetaTx = web3.utils.soliditySha3(
{ t: 'address[]', v: recipients },
{ t: 'uint256[]', v: metaTxsValue },
{ t: 'bytes', v: packedTxsDataField },
{ t: 'uint256', v: salt },
{ t: 'uint256', v: expiration },
)
// sign
senderSig = ethereumUtil.ecsign(
ethereumUtil.hashPersonalMessage(Buffer.from(hashedMetaTx.substring(2), 'hex')),
senderPrivateKey,
)
senderSigHex = ethereumUtil.bufferToHex(Buffer.concat([senderSig.r, senderSig.s])) + web3.utils.toHex(senderSig.v).substring(2)
packedSignature = senderSigHex
await ProxyInstance.forwardWithNoValue(
packedSignature,
recipients,
packedTxsDataField,
txsDataSizes,
salt,
expiration,
{ from: issuer },
)
expect(await web3.eth.getBalance(sender)).to.equal(primaryETHCost.toString())
const receiverBalanceBeforeTransfer = await MockTokenInstance.balanceOf(receiver)
await web3.eth.sendSignedTransaction(primaryRawTx)
// At the end of the flow
// sender:
// - has 100 - 10 - 1 (transaction fee) ERC20s
// - has 0 ETH
//
// receiver:
// - has + 10 ERC20s
//
// issuer:
// - has + 1 ERC20s
const senderERC20FinalBalance = await MockTokenInstance.balanceOf(sender)
const issuerERC20Balance = await MockTokenInstance.balanceOf(issuer)
const receiverBalanceAfterTransfer = await MockTokenInstance.balanceOf(receiver)
const senderETHFinalBalance = await web3.eth.getBalance(sender)
expect(
receiverBalanceBeforeTransfer.add(amountToReceiver).toString(),
).to.equal(receiverBalanceAfterTransfer.toString())
expect(senderETHFinalBalance).to.equal('0')
expect(senderERC20FinalBalance.toString()).to.equal(senderERC20InitialBalance.sub(amountToReceiver).sub(transferERC20FeeCost).toString())
expect(issuerERC20Balance.toString()).to.equal(transferERC20FeeCost.toString())
})
})
})
Created
December 18, 2019 14:40
-
-
Save andreafspeziale/de8443832629cd58c9723e7df91cc814 to your computer and use it in GitHub Desktop.
Test case for the atomic proxy SC
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment