UNPKG

leap-node

Version:

[![codecov](https://codecov.io/gh/leapdao/leap-node/branch/master/graph/badge.svg)](https://codecov.io/gh/leapdao/leap-node) [![Docker Repository on Quay](https://quay.io/repository/leapdao/leap-node/status "Docker Repository on Quay")](https://quay.io/re

494 lines (455 loc) 15.5 kB
const { Tx, Input, Outpoint, Output } = require('leap-core'); const { BigInt, equal } = require('jsbi-utils'); const { checkOutpoints, checkInsAndOuts, addOutputs, removeInputs, } = require('./utils'); const ADDR_1 = '0x4436373705394267350db2c06613990d34621d69'; const PRIV_1 = '0xad8e31c8862f5f86459e7cca97ac9302c5e1817077902540779eef66e21f394a'; const ADDR_2 = '0xc5a72c0bf9f59ed5a1d2ac9f29bd80c55279d2d3'; const periodVoteTx = Tx.periodVote( 0, new Input( new Outpoint( '0x7777777777777777777777777777777777777777777777777777777777777777', 0 ) ) ); function getDefaultState() { return { balances: {}, owners: {}, unspent: {}, }; } describe('applyTx utils', () => { describe('checkInsAndOuts', () => { test('ERC20', () => { const deposit = Tx.deposit(12, 500, ADDR_1, 0); const outpoint = new Outpoint(deposit.hash(), 0); const state = { unspent: { [outpoint.hex()]: deposit.outputs[0].toJSON(), }, gas: { minPrice: 0, }, }; const transfer = Tx.transfer( [new Input(outpoint)], [new Output(500, ADDR_1, 0)] ).signAll(PRIV_1); checkInsAndOuts( transfer, state, {}, ({ address }, i) => address === transfer.inputs[i].signer ); }); test('ERC721', () => { const color = 2 ** 15 + 1; const deposit = Tx.deposit(12, 500, ADDR_1, color); const outpoint = new Outpoint(deposit.hash(), 0); const state = { unspent: { [outpoint.hex()]: deposit.outputs[0].toJSON(), }, gas: { minPrice: 0, }, }; const transfer = Tx.transfer( [new Input(outpoint)], [new Output(500, ADDR_1, color)] ).signAll(PRIV_1); checkInsAndOuts( transfer, state, {}, ({ address }, i) => address === transfer.inputs[i].signer ); }); test('ERC721 (big value)', () => { const color = 2 ** 15 + 1; const value = '0xffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff'; const deposit = Tx.deposit(12, value, ADDR_1, color); const outpoint = new Outpoint(deposit.hash(), 0); const state = { unspent: { [outpoint.hex()]: deposit.outputs[0].toJSON(), }, gas: { minPrice: 0, }, }; const transfer = Tx.transfer( [new Input(outpoint)], [new Output(value, ADDR_1, color)] ).signAll(PRIV_1); checkInsAndOuts( transfer, state, {}, ({ address }, i) => address === transfer.inputs[i].signer ); }); test('ERC20 + ERC721', () => { const colorERC721 = 2 ** 15 + 1; const colorERC20 = 0; const depositERC20 = Tx.deposit(12, 500, ADDR_1, colorERC20); const outpointERC20 = new Outpoint(depositERC20.hash(), 0); const depositERC721 = Tx.deposit(12, 500, ADDR_1, colorERC721); const outpointERC721 = new Outpoint(depositERC721.hash(), 0); const state = { unspent: { [outpointERC20.hex()]: depositERC20.outputs[0].toJSON(), [outpointERC721.hex()]: depositERC721.outputs[0].toJSON(), }, gas: { minPrice: 0, }, }; const transfer = Tx.transfer( [new Input(outpointERC721), new Input(outpointERC20)], [ new Output(500, ADDR_1, colorERC721), new Output(500, ADDR_1, colorERC20), ] ).signAll(PRIV_1); checkInsAndOuts( transfer, state, {}, ({ address }, i) => address === transfer.inputs[i].signer ); }); test('Ins and outs mismatch ERC20', () => { const colorERC721 = 2 ** 15 + 1; const colorERC20 = 0; const depositERC20 = Tx.deposit(12, 500, ADDR_1, colorERC20); const outpointERC20 = new Outpoint(depositERC20.hash(), 0); const depositERC721 = Tx.deposit(12, 500, ADDR_1, colorERC721); const outpointERC721 = new Outpoint(depositERC721.hash(), 0); const state = { unspent: { [outpointERC20.hex()]: depositERC20.outputs[0].toJSON(), [outpointERC721.hex()]: depositERC721.outputs[0].toJSON(), }, gas: { minPrice: 0, }, }; const transfer = Tx.transfer( [new Input(outpointERC721), new Input(outpointERC20)], [ new Output(500, ADDR_1, colorERC721), new Output(600, ADDR_1, colorERC20), ] ).signAll(PRIV_1); expect(() => { checkInsAndOuts( transfer, state, {}, ({ address }, i) => address === transfer.inputs[i].signer ); }).toThrow(`Ins and outs values are mismatch for color ${colorERC20}`); }); test('Ins and outs mismatch ERC721', () => { const colorERC721 = 2 ** 15 + 1; const depositERC20 = Tx.deposit(12, 500, ADDR_1, 0); const outpointERC20 = new Outpoint(depositERC20.hash(), 0); const depositERC721 = Tx.deposit(12, 500, ADDR_1, colorERC721); const outpointERC721 = new Outpoint(depositERC721.hash(), 0); const state = { unspent: { [outpointERC20.hex()]: depositERC20.outputs[0].toJSON(), [outpointERC721.hex()]: depositERC721.outputs[0].toJSON(), }, gas: { minPrice: 0, }, }; const transfer = Tx.transfer( [new Input(outpointERC721), new Input(outpointERC20)], [new Output(400, ADDR_1, colorERC721), new Output(500, ADDR_1, 0)] ).signAll(PRIV_1); expect(() => { checkInsAndOuts( transfer, state, {}, ({ address }, i) => address === transfer.inputs[i].signer ); }).toThrow(`Ins and outs values are mismatch for color ${colorERC721}`); }); test('should throw if overspending ERC20', () => { const color = 0; const depositERC20 = Tx.deposit(12, 500, ADDR_1, color); const outpointERC20 = new Outpoint(depositERC20.hash(), color); const state = { unspent: { [outpointERC20.hex()]: depositERC20.outputs[0].toJSON(), }, gas: { minPrice: 0, }, }; // outputs > inputs const transfer = Tx.transfer( [new Input(outpointERC20)], [new Output(600, ADDR_1, color)] ).signAll(PRIV_1); expect(() => { checkInsAndOuts( transfer, state, {}, ({ address }, i) => address === transfer.inputs[i].signer ); }).toThrow(`Ins and outs values are mismatch for color ${color}`); }); test('With minGasPrice', () => { const colorERC20 = 0; const depositERC20 = Tx.deposit(12, 50000, ADDR_1, colorERC20); const outpointERC20 = new Outpoint(depositERC20.hash(), 0); const state = { unspent: { [outpointERC20.hex()]: depositERC20.outputs[0].toJSON(), }, gas: { minPrice: 0, }, }; const transfer = Tx.transfer( [new Input(outpointERC20)], [new Output(30000, ADDR_1, colorERC20)] ).signAll(PRIV_1); checkInsAndOuts( transfer, state, { minGasPrices: ['2'] }, ({ address }, i) => address === transfer.inputs[i].signer ); }); test('Do not fail overpriced tx', () => { const deposit1 = Tx.deposit(12, 50000, ADDR_1, 0); const outpoint1 = new Outpoint(deposit1.hash(), 0); const state = { unspent: { [outpoint1.hex()]: deposit1.outputs[0].toJSON(), }, gas: { minPrice: 0, }, }; const transfer = Tx.transfer( [new Input(outpoint1)], [new Output(30000, ADDR_1, 0)] ).signAll(PRIV_1); checkInsAndOuts( transfer, state, { minGasPrices: ['1'] }, ({ address }, i) => address === transfer.inputs[i].signer ); }); test('Underpriced', () => { const colorERC20 = 0; const depositERC20 = Tx.deposit(12, 50000, ADDR_1, colorERC20); const outpointERC20 = new Outpoint(depositERC20.hash(), 0); const state = { unspent: { [outpointERC20.hex()]: depositERC20.outputs[0].toJSON(), }, gas: { minPrice: '3', }, }; const transfer = Tx.transfer( [new Input(outpointERC20)], [new Output(30000, ADDR_1, colorERC20)] ).signAll(PRIV_1); expect(() => { checkInsAndOuts( transfer, state, {}, ({ address }, i) => address === transfer.inputs[i].signer ); }).toThrow(`Tx underpriced`); }); }); describe('checkOutpoints', () => { test('non-existent outpoint', () => { const state = { unspent: {}, gas: { minPrice: 0, }, }; const deposit = Tx.deposit(12, 500, ADDR_1, 0); const transfer = Tx.transfer( [new Input(new Outpoint(deposit.hash(), 0))], [new Output(500, ADDR_1, 0)] ); expect(() => { checkOutpoints(state, transfer); }).toThrow('Trying to spend non-existing output'); }); test('existent outpoint', () => { const deposit = Tx.deposit(12, 500, ADDR_1, 0); const outpoint = new Outpoint(deposit.hash(), 0); const state = { unspent: { [outpoint.hex()]: deposit.outputs[0].toJSON(), }, }; const transfer = Tx.transfer( [new Input(outpoint)], [new Output(500, ADDR_1, 0)] ); checkOutpoints(state, transfer); }); test('always pass for periodVote', () => { const state = getDefaultState(); checkOutpoints(state, periodVoteTx); }); }); describe('addOuputs', () => { test('ERC20', () => { const deposit1 = Tx.deposit(12, '100000000000000000', ADDR_1, 0); const outpoint = new Outpoint(deposit1.hash(), 0); const state = getDefaultState(); addOutputs(state, deposit1); expect(state.unspent[outpoint.hex()]).toBeDefined(); expect(equal(state.balances[0][ADDR_1], BigInt(100000000000000000))); const deposit2 = Tx.deposit(13, '10000000', ADDR_1, 0); addOutputs(state, deposit2); expect(equal(state.balances[0][ADDR_1], BigInt(100000000010000000))); }); test('ERC721', () => { const color = 2 ** 15 + 1; const value1 = BigInt( '0xffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff' ); const value2 = BigInt( '0xfffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffa' ); const value3 = BigInt( '0xfffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffe' ); const deposit1 = Tx.deposit(12, value1, ADDR_1, color); const deposit2 = Tx.deposit(12, value2, ADDR_1, color); const deposit3 = Tx.deposit(12, value3, ADDR_2, color); const outpoint1 = new Outpoint(deposit1.hash(), 0); const outpoint2 = new Outpoint(deposit2.hash(), 0); const outpoint3 = new Outpoint(deposit3.hash(), 0); const state = getDefaultState(); addOutputs(state, deposit1); expect(state.unspent[outpoint1.hex()]).toBeDefined(); expect(state.balances[color][ADDR_1]).toEqual([value1]); expect(state.owners[color][value1]).toBe(ADDR_1); addOutputs(state, deposit2); expect(state.unspent[outpoint2.hex()]).toBeDefined(); expect(state.balances[color][ADDR_1]).toEqual([value1, value2]); expect(state.owners[color][value2]).toBe(ADDR_1); addOutputs(state, deposit3); expect(state.unspent[outpoint3.hex()]).toBeDefined(); expect(state.balances[color][ADDR_2]).toEqual([value3]); expect(state.owners[color][value3]).toBe(ADDR_2); }); test('Existing outpoint', () => { const deposit = Tx.deposit(12, 500, ADDR_1, 0); const state = getDefaultState(); addOutputs(state, deposit); expect(() => addOutputs(state, deposit)).toThrow( 'Attempt to create existing output' ); }); test('skip for periodVote', () => { const state = getDefaultState(); addOutputs(state, periodVoteTx); }); }); describe('removeInputs', () => { test('ERC20', () => { const deposit = Tx.deposit(12, 500, ADDR_1, 0); const outpoint = new Outpoint(deposit.hash(), 0); const state = getDefaultState(); addOutputs(state, deposit); expect(state.unspent[outpoint.hex()]).toBeDefined(); const transfer = Tx.transfer( [new Input(outpoint)], [new Output(500, ADDR_1, 0)] ); removeInputs(state, transfer); expect(state.unspent[outpoint.hex()]).toBeUndefined(); expect(equal(state.balances[0][ADDR_1], BigInt(0))); }); test('ERC721', () => { const color = 2 ** 15 + 1; const value1 = BigInt( '0xffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff' ); const value2 = BigInt( '0xfffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffa' ); const value3 = BigInt( '0xfffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffe' ); const deposit1 = Tx.deposit(12, value1, ADDR_1, color); const deposit2 = Tx.deposit(12, value2, ADDR_1, color); const deposit3 = Tx.deposit(12, value3, ADDR_2, color); const outpoint1 = new Outpoint(deposit1.hash(), 0); const outpoint2 = new Outpoint(deposit2.hash(), 0); const outpoint3 = new Outpoint(deposit3.hash(), 0); const state = getDefaultState(); addOutputs(state, deposit1); expect(state.unspent[outpoint1.hex()]).toBeDefined(); expect(state.balances[color][ADDR_1]).toEqual([value1]); expect(state.owners[color][value1]).toBe(ADDR_1); addOutputs(state, deposit2); expect(state.unspent[outpoint2.hex()]).toBeDefined(); expect(state.balances[color][ADDR_1]).toEqual([value1, value2]); expect(state.owners[color][value2]).toBe(ADDR_1); addOutputs(state, deposit3); expect(state.unspent[outpoint3.hex()]).toBeDefined(); expect(state.balances[color][ADDR_2]).toEqual([value3]); expect(state.owners[color][value3]).toBe(ADDR_2); const transfer1 = Tx.transfer( [new Input(outpoint1)], [new Output(value1, ADDR_1, color)] ); removeInputs(state, transfer1); expect(state.unspent[outpoint1.hex()]).toBeUndefined(); expect(equal(state.balances[color][ADDR_1][0], BigInt(value2))); expect(state.owners[color][value1]).toBeUndefined(); const transfer2 = Tx.transfer( [new Input(outpoint2)], [new Output(value2, ADDR_1, color)] ); removeInputs(state, transfer2); expect(state.unspent[outpoint2.hex()]).toBeUndefined(); expect(state.balances[color][ADDR_1]).toEqual([]); expect(state.owners[color][value2]).toBeUndefined(); const transfer3 = Tx.transfer( [new Input(outpoint3)], [new Output(value3, ADDR_2, color)] ); removeInputs(state, transfer3); expect(state.unspent[outpoint3.hex()]).toBeUndefined(); expect(state.balances[color][ADDR_2]).toEqual([]); expect(state.owners[color][value3]).toBeUndefined(); }); test('skip for periodVote', () => { const state = getDefaultState(); removeInputs(state, periodVoteTx); }); }); });