Skip to content

Commit

Permalink
test: make fee claim e2e test more similar to real situation
Browse files Browse the repository at this point in the history
The test was written with invalid block that the fee tx claims the fee out of air. This commit adds the flow that
the fee is indeed collecting from other txs within the block.
  • Loading branch information
boolafish committed Dec 9, 2019
1 parent 31349b3 commit 9d59c37
Show file tree
Hide file tree
Showing 2 changed files with 177 additions and 98 deletions.
273 changes: 176 additions & 97 deletions plasma_framework/test/endToEndTests/FeeClaim.e2e.test.js
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@ const {
constants, expectEvent,
} = require('openzeppelin-test-helpers');

const Testlang = require('../helpers/testlang.js');
const config = require('../../config.js');

const {
Expand All @@ -21,16 +22,16 @@ const { MerkleTree } = require('../helpers/merkle.js');
* First three accounts are in the order of (deployer, maintainer, authority).
* This is how migration scripts use the account.
*/
contract('PlasmaFramework - Fee Claim', ([_, _maintainer, authority, richFather]) => {
const MERKLE_TREE_DEPTH = 16;
contract('PlasmaFramework - Fee Claim', ([_, _maintainer, authority, richFather, bob, carol]) => {
const DEPOSIT_VALUE = 1000000;
const ETH = constants.ZERO_ADDRESS;
const PAYMENT_OUTPUT_TYPE = config.registerKeys.outputTypes.payment;
const PAYMENT_TX_TYPE = config.registerKeys.txTypes.payment;
const FEE_TX_TYPE = config.registerKeys.txTypes.fee;
const FEE_OUTPUT_TYPE = config.registerKeys.outputTypes.feeClaim;
const FEE_NONCE_OUTPUT_TYPE = config.registerKeys.outputTypes.feeBlockNum;

const FEE_AMOUNT = 1000;
const MERKLE_TREE_DEPTH = 16;
const PAYMENT_OUTPUT_TYPE = config.registerKeys.outputTypes.payment;
const PAYMENT_TX_TYPE = config.registerKeys.txTypes.payment;

const alicePrivateKey = '0x7151e5dab6f8e95b5436515b83f423c4df64fe4c6149f864daa209b26adb10ca';
const operatorFeeAddressPrivateKey = '0x7151e5dab6f8e95b5436515b83f423c4df64fe4c6149f864daa209b26adb10cb';
Expand Down Expand Up @@ -63,119 +64,197 @@ contract('PlasmaFramework - Fee Claim', ([_, _maintainer, authority, richFather]
this.framework.addExitQueue(config.registerKeys.vaultId.eth, ETH);
});

describe('When operator creates and mines the first fee transaction', () => {
let firstFeeTxBytes;
let firstFeeClaimUtxoPos;
let firstFeeTxMerkleProof;
describe('And then first fee collected when Alice transfer to Bob', () => {
describe('And then second fee collected when Alice transfer to Carol', () => {
describe('When operator spends the fee outputs to Payment transaction', () => {

beforeEach(async () => {
const nextBlockNum = (await this.framework.nextChildBlock()).toNumber();
const feeOutputs = [
new FeeClaimOutput(FEE_OUTPUT_TYPE, FEE_AMOUNT, operatorFeeAddress, ETH),
new FeeBlockNumOutput(FEE_NONCE_OUTPUT_TYPE, nextBlockNum),
];
});
});
});

const outputIndex = 0;
firstFeeClaimUtxoPos = buildUtxoPos(nextBlockNum, 0, outputIndex);
describe('When Alice deposits ETH to the plasma', () => {
let alicePlasmaBalance;
let aliceDepositUtxoPos;

const firstFeeTx = new FeeTransaction(FEE_TX_TYPE, [], feeOutputs);
firstFeeTxBytes = web3.utils.bytesToHex(firstFeeTx.rlpEncoded());
before(async () => {
const depositBlockNum = (await this.framework.nextDepositBlock()).toNumber();
aliceDepositUtxoPos = buildUtxoPos(depositBlockNum, 0, 0);

const merkleTree = new MerkleTree([firstFeeTxBytes], MERKLE_TREE_DEPTH);
firstFeeTxMerkleProof = merkleTree.getInclusionProof(firstFeeTxBytes);
const depositTx = Testlang.deposit(PAYMENT_OUTPUT_TYPE, DEPOSIT_VALUE, alice);
alicePlasmaBalance = DEPOSIT_VALUE;

await this.framework.submitBlock(merkleTree.root, { from: authority });
return this.ethVault.deposit(depositTx, { from: alice, value: DEPOSIT_VALUE });
});

describe('And then the operator creates and mines the second fee transaction', () => {
let secondFeeTxBytes;
let secondFeeClaimUtxoPos;
let secondFeeTxMerkleProof;

beforeEach(async () => {
const nextBlockNum = (await this.framework.nextChildBlock()).toNumber();
const feeOutputs = [
new FeeClaimOutput(FEE_OUTPUT_TYPE, FEE_AMOUNT, operatorFeeAddress, ETH),
new FeeBlockNumOutput(FEE_NONCE_OUTPUT_TYPE, nextBlockNum),
];
describe('When Alice transfer to Bob, fee is implicitly collected', () => {
let aliceBalanceUtxoPosAfterTransferToBob;
let aliceTransferToBobTxBytes;

const secondFeeTx = new FeeTransaction(FEE_TX_TYPE, [], feeOutputs);
secondFeeTxBytes = web3.utils.bytesToHex(secondFeeTx.rlpEncoded());
before(async () => {
const transferAmount = 1000;
alicePlasmaBalance -= (transferAmount + FEE_AMOUNT);
const outputBob = new PaymentTransactionOutput(PAYMENT_OUTPUT_TYPE, transferAmount, bob, ETH);
const outputAlice = new PaymentTransactionOutput(
PAYMENT_OUTPUT_TYPE, alicePlasmaBalance, alice, ETH,
);

const outputIndex = 0;
secondFeeClaimUtxoPos = buildUtxoPos(nextBlockNum, 0, outputIndex);
const aliceBalanceOutputIndex = 1;
const blockNum = (await this.framework.nextChildBlock()).toNumber();
aliceBalanceUtxoPosAfterTransferToBob = buildUtxoPos(blockNum, 0, aliceBalanceOutputIndex);

const merkleTree = new MerkleTree([secondFeeTxBytes], MERKLE_TREE_DEPTH);
secondFeeTxMerkleProof = merkleTree.getInclusionProof(secondFeeTxBytes);
await this.framework.submitBlock(merkleTree.root, { from: authority });
const txObj = new PaymentTransaction(
PAYMENT_TX_TYPE,
[aliceDepositUtxoPos],
[outputBob, outputAlice],
);
aliceTransferToBobTxBytes = web3.utils.bytesToHex(txObj.rlpEncoded());
});

describe('And then the operator spends the 2 fee claim outputs in payment transaction', () => {
let paymentTxObj;
let paymentTxBytes;
let paymentOutputUtxoPos;
let paymentTxMerkleProof;

beforeEach(async () => {
const inputs = [firstFeeClaimUtxoPos, secondFeeClaimUtxoPos];
const outputs = [
new PaymentTransactionOutput(
PAYMENT_OUTPUT_TYPE,
FEE_AMOUNT * 2,
operatorFeeAddress,
ETH,
),
];
paymentTxObj = new PaymentTransaction(PAYMENT_TX_TYPE, inputs, outputs);
paymentTxBytes = web3.utils.bytesToHex(paymentTxObj.rlpEncoded());
describe('And then operator mined the block with the first fee tx claiming the fee', () => {
let firstFeeTxBytes;
let firstFeeClaimUtxoPos;
let firstFeeTxMerkleProof;

before(async () => {
const nextBlockNum = (await this.framework.nextChildBlock()).toNumber();
const feeOutputs = [
new FeeClaimOutput(FEE_OUTPUT_TYPE, FEE_AMOUNT, operatorFeeAddress, ETH),
new FeeBlockNumOutput(FEE_NONCE_OUTPUT_TYPE, nextBlockNum),
];

const outputIndex = 0;
paymentOutputUtxoPos = buildUtxoPos(nextBlockNum, 0, outputIndex);
const feeTxIndex = 1;
firstFeeClaimUtxoPos = buildUtxoPos(nextBlockNum, feeTxIndex, outputIndex);

const firstFeeTx = new FeeTransaction(FEE_TX_TYPE, [], feeOutputs);
firstFeeTxBytes = web3.utils.bytesToHex(firstFeeTx.rlpEncoded());

const merkleTree = new MerkleTree(
[aliceTransferToBobTxBytes, firstFeeTxBytes],
MERKLE_TREE_DEPTH,
);
firstFeeTxMerkleProof = merkleTree.getInclusionProof(firstFeeTxBytes);

const merkleTree = new MerkleTree([paymentTxBytes], MERKLE_TREE_DEPTH);
paymentTxMerkleProof = merkleTree.getInclusionProof(paymentTxBytes);
await this.framework.submitBlock(merkleTree.root, { from: authority });
});

it('should be able to standard exit the fee via payment transaction', async () => {
const args = {
utxoPos: paymentOutputUtxoPos,
rlpOutputTx: paymentTxBytes,
outputTxInclusionProof: paymentTxMerkleProof,
};
describe('When Alice transfer to Carol, fee is implicitly collected', () => {
let aliceTransferToCarolTxBytes;

const bondSize = await this.paymentExitGame.startStandardExitBondSize();
const tx = await this.paymentExitGame.startStandardExit(
args, { from: operatorFeeAddress, value: bondSize },
);
await expectEvent.inLogs(
tx.logs,
'ExitStarted',
{ owner: operatorFeeAddress },
);
});
before(async () => {
const transferAmount = 1000;
alicePlasmaBalance -= (transferAmount + FEE_AMOUNT);
const outputCarol = new PaymentTransactionOutput(
PAYMENT_OUTPUT_TYPE, transferAmount, carol, ETH,
);
const outputAlice = new PaymentTransactionOutput(
PAYMENT_OUTPUT_TYPE, alicePlasmaBalance, alice, ETH,
);

it('should be able to in-flight exit the fee via Payment transaction', async () => {
const txHash = hashTx(paymentTxObj, this.framework.address);
const operatorSignature = sign(txHash, operatorFeeAddressPrivateKey);
const args = {
inFlightTx: paymentTxBytes,
inputTxs: [firstFeeTxBytes, secondFeeTxBytes],
inputUtxosPos: [firstFeeClaimUtxoPos, secondFeeClaimUtxoPos],
inputTxsInclusionProofs: [firstFeeTxMerkleProof, secondFeeTxMerkleProof],
inFlightTxWitnesses: [operatorSignature, operatorSignature],
};

const bondSize = await this.paymentExitGame.startIFEBondSize();
const tx = await this.paymentExitGame.startInFlightExit(
args,
{ from: alice, value: bondSize },
);
await expectEvent.inLogs(
tx.logs,
'InFlightExitStarted',
);
const txObj = new PaymentTransaction(
PAYMENT_TX_TYPE,
[aliceBalanceUtxoPosAfterTransferToBob],
[outputCarol, outputAlice],
);
aliceTransferToCarolTxBytes = web3.utils.bytesToHex(txObj.rlpEncoded());
});

describe('And then operator mined the block with the second fee tx claiming the fee', () => {
let secondFeeTxBytes;
let secondFeeClaimUtxoPos;
let secondFeeTxMerkleProof;

before(async () => {
const nextBlockNum = (await this.framework.nextChildBlock()).toNumber();
const feeOutputs = [
new FeeClaimOutput(FEE_OUTPUT_TYPE, FEE_AMOUNT, operatorFeeAddress, ETH),
new FeeBlockNumOutput(FEE_NONCE_OUTPUT_TYPE, nextBlockNum),
];

const secondFeeTx = new FeeTransaction(FEE_TX_TYPE, [], feeOutputs);
secondFeeTxBytes = web3.utils.bytesToHex(secondFeeTx.rlpEncoded());

const outputIndex = 0;
const feeTxIndex = 1;
secondFeeClaimUtxoPos = buildUtxoPos(nextBlockNum, feeTxIndex, outputIndex);

const merkleTree = new MerkleTree(
[aliceTransferToCarolTxBytes, secondFeeTxBytes],
MERKLE_TREE_DEPTH,
);
secondFeeTxMerkleProof = merkleTree.getInclusionProof(secondFeeTxBytes);
await this.framework.submitBlock(merkleTree.root, { from: authority });
});

describe('And then the operator spends the 2 fee claim outputs in a payment transaction', () => {
let paymentTxObj;
let paymentTxBytes;
let paymentOutputUtxoPos;
let paymentTxMerkleProof;

before(async () => {
const inputs = [firstFeeClaimUtxoPos, secondFeeClaimUtxoPos];
const outputs = [
new PaymentTransactionOutput(
PAYMENT_OUTPUT_TYPE,
FEE_AMOUNT * 2,
operatorFeeAddress,
ETH,
),
];
paymentTxObj = new PaymentTransaction(PAYMENT_TX_TYPE, inputs, outputs);
paymentTxBytes = web3.utils.bytesToHex(paymentTxObj.rlpEncoded());

const nextBlockNum = (await this.framework.nextChildBlock()).toNumber();
const outputIndex = 0;
paymentOutputUtxoPos = buildUtxoPos(nextBlockNum, 0, outputIndex);

const merkleTree = new MerkleTree([paymentTxBytes], MERKLE_TREE_DEPTH);
paymentTxMerkleProof = merkleTree.getInclusionProof(paymentTxBytes);
await this.framework.submitBlock(merkleTree.root, { from: authority });
});

it('should be able to standard exit the fee via payment transaction', async () => {
const args = {
utxoPos: paymentOutputUtxoPos,
rlpOutputTx: paymentTxBytes,
outputTxInclusionProof: paymentTxMerkleProof,
};

const bondSize = await this.paymentExitGame.startStandardExitBondSize();
const tx = await this.paymentExitGame.startStandardExit(
args, { from: operatorFeeAddress, value: bondSize },
);
await expectEvent.inLogs(
tx.logs,
'ExitStarted',
{ owner: operatorFeeAddress },
);
});

it('should be able to in-flight exit the fee via Payment transaction', async () => {
const txHash = hashTx(paymentTxObj, this.framework.address);
const operatorSignature = sign(txHash, operatorFeeAddressPrivateKey);
const args = {
inFlightTx: paymentTxBytes,
inputTxs: [firstFeeTxBytes, secondFeeTxBytes],
inputUtxosPos: [firstFeeClaimUtxoPos, secondFeeClaimUtxoPos],
inputTxsInclusionProofs: [firstFeeTxMerkleProof, secondFeeTxMerkleProof],
inFlightTxWitnesses: [operatorSignature, operatorSignature],
};

const bondSize = await this.paymentExitGame.startIFEBondSize();
const tx = await this.paymentExitGame.startInFlightExit(
args,
{ from: alice, value: bondSize },
);
await expectEvent.inLogs(
tx.logs,
'InFlightExitStarted',
);
});
});
});
});
});
});
Expand Down
2 changes: 1 addition & 1 deletion plasma_framework/test/helpers/transaction.js
Original file line number Diff line number Diff line change
Expand Up @@ -91,7 +91,7 @@ class FeeBlockNumOutput {
}

formatForRlpEncoding() {
return [this.outputType, this.nonce];
return [this.outputType, this.blockNum];
}

rlpEncoded() {
Expand Down

0 comments on commit 9d59c37

Please sign in to comment.