diff --git a/contracts/mocks/ArbitraryPredicateMock.sol b/contracts/mocks/ArbitraryPredicateMock.sol new file mode 100644 index 00000000..66ee0ca6 --- /dev/null +++ b/contracts/mocks/ArbitraryPredicateMock.sol @@ -0,0 +1,8 @@ +// SPDX-License-Identifier: MIT +pragma solidity 0.8.19; + +contract ArbitraryPredicateMock { + function copyArg(uint256 arg) external pure returns (uint256) { + return arg; + } +} diff --git a/test/LimitOrderProtocol.js b/test/LimitOrderProtocol.js index 5b67daf5..299e0df4 100644 --- a/test/LimitOrderProtocol.js +++ b/test/LimitOrderProtocol.js @@ -5,7 +5,7 @@ const { fillWithMakingAmount, unwrapWethTaker, skipMakerPermit, buildMakerTraits const { getPermit, withTarget } = require('./helpers/eip712'); const { joinStaticCalls, ether, findTrace, countAllItems } = require('./helpers/utils'); const { loadFixture } = require('@nomicfoundation/hardhat-network-helpers'); -const { deploySwapTokens } = require('./helpers/fixtures'); +const { deploySwapTokens, deployArbitraryPredicate } = require('./helpers/fixtures'); describe('LimitOrderProtocol', function () { let addr, addr1, addr2; @@ -146,9 +146,9 @@ describe('LimitOrderProtocol', function () { }); const { r, vs } = compactSignature(await signOrder(order, chainId, swap.address, addr1)); - await expect(swap.fillOrder(order, r, vs, 1, fillWithMakingAmount(1))) - .to.changeTokenBalances(dai, [addr, addr1], [1, -1]) - .to.changeTokenBalances(weth, [addr, addr1], [-1, 1]); + const filltx = swap.fillOrder(order, r, vs, 1, fillWithMakingAmount(1)); + await expect(filltx).to.changeTokenBalances(dai, [addr, addr1], [1, -1]); + await expect(filltx).to.changeTokenBalances(weth, [addr, addr1], [-1, 1]); if (hre.__SOLIDITY_COVERAGE_RUNNING === undefined) { const trace = findTrace(tracer, 'CALL', swap.address); @@ -171,9 +171,9 @@ describe('LimitOrderProtocol', function () { }); const { r, vs } = compactSignature(await signOrder(order, chainId, swap.address, addr1)); - await expect(swap.fillOrder(order, r, vs, 1, fillWithMakingAmount(1))) - .to.changeTokenBalances(dai, [addr, addr1], [1, -1]) - .to.changeTokenBalances(weth, [addr, addr1], [-1, 1]); + const filltx = swap.fillOrder(order, r, vs, 1, fillWithMakingAmount(1)); + await expect(filltx).to.changeTokenBalances(dai, [addr, addr1], [1, -1]); + await expect(filltx).to.changeTokenBalances(weth, [addr, addr1], [-1, 1]); if (hre.__SOLIDITY_COVERAGE_RUNNING === undefined) { const trace = findTrace(tracer, 'CALL', swap.address); @@ -196,9 +196,9 @@ describe('LimitOrderProtocol', function () { }); const { r, vs } = compactSignature(await signOrder(order, chainId, swap.address, addr1)); - await expect(swap.fillOrder(order, r, vs, 9, 1)) - .to.changeTokenBalances(dai, [addr, addr1], [1, -1]) - .to.changeTokenBalances(weth, [addr, addr1], [-9, 9]); + const filltx = swap.fillOrder(order, r, vs, 9, 1); + await expect(filltx).to.changeTokenBalances(dai, [addr, addr1], [1, -1]); + await expect(filltx).to.changeTokenBalances(weth, [addr, addr1], [-9, 9]); }); it('should fail on floor maker amount = 0', async function () { @@ -233,9 +233,9 @@ describe('LimitOrderProtocol', function () { }); const { r, vs } = compactSignature(await signOrder(order, chainId, swap.address, addr1)); - await expect(swap.fillOrder(order, r, vs, 4, fillWithMakingAmount(1))) - .to.changeTokenBalances(dai, [addr, addr1], [4, -4]) - .to.changeTokenBalances(weth, [addr, addr1], [-1, 1]); + const filltx = swap.fillOrder(order, r, vs, 4, fillWithMakingAmount(1)); + await expect(filltx).to.changeTokenBalances(dai, [addr, addr1], [4, -4]); + await expect(filltx).to.changeTokenBalances(weth, [addr, addr1], [-1, 1]); }); it('should unwrap weth', async function () { @@ -254,10 +254,10 @@ describe('LimitOrderProtocol', function () { await weth.connect(addr1).deposit({ value: ether('2') }); const { r, vs } = compactSignature(await signOrder(order, chainId, swap.address, addr1)); - await expect(swap.fillOrder(order, r, vs, ether('5'), unwrapWethTaker(ether('1')))) - .to.changeTokenBalances(dai, [addr, addr1], [ether('-5'), ether('5')]) - .to.changeTokenBalances(weth, [addr, addr1], [ether('0'), ether('-1')]) - .to.changeEtherBalance(addr, ether('1')); + const filltx = swap.fillOrder(order, r, vs, ether('5'), unwrapWethTaker(ether('1'))); + await expect(filltx).to.changeTokenBalances(dai, [addr, addr1], [ether('-5'), ether('5')]); + await expect(filltx).to.changeTokenBalances(weth, [addr, addr1], [ether('0'), ether('-1')]); + await expect(filltx).to.changeEtherBalance(addr, ether('1')); }); it('ERC721Proxy should work', async function () { @@ -274,8 +274,9 @@ describe('LimitOrderProtocol', function () { { makerAsset: erc721proxy.address, takerAsset: erc721proxy.address, - makingAmount: 10, - takingAmount: 10, + // making and taking amounts are not used by ERC721Proxy + makingAmount: 1, + takingAmount: 1, maker: addr1.address, }, { @@ -285,9 +286,9 @@ describe('LimitOrderProtocol', function () { ); const { r, vs } = compactSignature(await signOrder(order, chainId, swap.address, addr1)); - await expect(swap.fillOrderExt(order, r, vs, 10, fillWithMakingAmount(10), order.extension)) - .to.changeTokenBalances(dai, [addr, addr1], [10, -10]) - .to.changeTokenBalances(weth, [addr, addr1], [-10, 10]); + const filltx = swap.fillOrderExt(order, r, vs, 10, fillWithMakingAmount(10), order.extension); + await expect(filltx).to.changeTokenBalances(dai, [addr, addr1], [10, -10]); + await expect(filltx).to.changeTokenBalances(weth, [addr, addr1], [-10, 10]); }); }); @@ -315,9 +316,9 @@ describe('LimitOrderProtocol', function () { }); const { r, vs } = compactSignature(await signOrder(order, chainId, swap.address, addr1)); - await expect(swap.fillOrder(order, r, vs, 4, fillWithMakingAmount(1))) - .to.changeTokenBalances(dai, [addr, addr1], [4, -4]) - .to.changeTokenBalances(weth, [addr, addr1], [-1, 1]); + const filltx = swap.fillOrder(order, r, vs, 4, fillWithMakingAmount(1)); + await expect(filltx).to.changeTokenBalances(dai, [addr, addr1], [4, -4]); + await expect(filltx).to.changeTokenBalances(weth, [addr, addr1], [-1, 1]); await expect(swap.fillOrder(order, r, vs, 4, fillWithMakingAmount(1))) .to.be.revertedWithCustomError(swap, 'BitInvalidatedOrder'); @@ -338,9 +339,9 @@ describe('LimitOrderProtocol', function () { }); const { r, vs } = compactSignature(await signOrder(order, chainId, swap.address, addr1)); - await expect(swap.fillOrder(order, r, vs, 4, fillWithMakingAmount(1))) - .to.changeTokenBalances(dai, [addr, addr1], [4, -4]) - .to.changeTokenBalances(weth, [addr, addr1], [-1, 1]); + const filltx = swap.fillOrder(order, r, vs, 4, fillWithMakingAmount(1)); + await expect(filltx).to.changeTokenBalances(dai, [addr, addr1], [4, -4]); + await expect(filltx).to.changeTokenBalances(weth, [addr, addr1], [-1, 1]); }); it('need epoch manager, fail', async function () { @@ -362,25 +363,6 @@ describe('LimitOrderProtocol', function () { .to.be.revertedWithCustomError(swap, 'WrongSeriesNonce'); }); - it('expired order', async function () { - const { dai, weth, swap, chainId } = await loadFixture(deployContractsAndInit); - // Order: 10 DAI => 2 WETH - // Swap: 4 DAI => 1 WETH - - const order = buildOrder({ - makerAsset: dai.address, - takerAsset: weth.address, - makingAmount: 10, - takingAmount: 2, - maker: addr1.address, - makerTraits: buildMakerTraits({ expiry: await time.latest() }), - }); - - const { r, vs } = compactSignature(await signOrder(order, chainId, swap.address, addr1)); - await expect(swap.fillOrder(order, r, vs, 4, fillWithMakingAmount(1))) - .to.be.revertedWithCustomError(swap, 'OrderExpired'); - }); - it('unwrap weth for maker', async function () { const { dai, weth, swap, chainId } = await loadFixture(deployContractsAndInit); // Order: 10 DAI => 2 WETH @@ -396,13 +378,13 @@ describe('LimitOrderProtocol', function () { }); const { r, vs } = compactSignature(await signOrder(order, chainId, swap.address, addr1)); - await expect(swap.fillOrder(order, r, vs, 10, fillWithMakingAmount(2))) - .to.changeTokenBalances(dai, [addr, addr1], [10, -10]) - .to.changeTokenBalance(weth, addr, -2) - .to.changeEtherBalance(addr1, 2); + const filltx = swap.fillOrder(order, r, vs, 10, fillWithMakingAmount(2)); + await expect(filltx).to.changeTokenBalances(dai, [addr, addr1], [10, -10]); + await expect(filltx).to.changeTokenBalance(weth, addr, -2); + await expect(filltx).to.changeEtherBalance(addr1, 2); }); - it('Allow taker rate improvement', async function () { + it('allow taker rate improvement', async function () { const { dai, weth, swap, chainId, takerIncreaser } = await loadFixture(deployContractsAndInit); // Order: 10 DAI => 2 WETH // Swap: 10 DAI => 3 WETH @@ -417,9 +399,9 @@ describe('LimitOrderProtocol', function () { }); const { r, vs } = compactSignature(await signOrder(order, chainId, swap.address, addr1)); - await expect(swap.fillOrderTo(order, r, vs, 10, 2, addr.address, takerIncreaser.address)) - .to.changeTokenBalances(dai, [addr, addr1], [10, -10]) - .to.changeTokenBalances(weth, [addr, addr1], [-3, 3]); + const filltx = swap.fillOrderTo(order, r, vs, 10, 2, addr.address, takerIncreaser.address); + await expect(filltx).to.changeTokenBalances(dai, [addr, addr1], [10, -10]); + await expect(filltx).to.changeTokenBalances(weth, [addr, addr1], [-3, 3]); }); }); @@ -446,9 +428,9 @@ describe('LimitOrderProtocol', function () { const permit = await getPermit(addr.address, addr, weth, '1', chainId, swap.address, '1'); const { r, vs } = compactSignature(signature); - await expect(swap.fillOrderToWithPermit(order, r, vs, 1, fillWithMakingAmount(1), addr.address, '0x', permit)) - .to.changeTokenBalances(dai, [addr, addr1], [1, -1]) - .to.changeTokenBalances(weth, [addr, addr1], [-1, 1]); + const filltx = swap.fillOrderToWithPermit(order, r, vs, 1, fillWithMakingAmount(1), addr.address, '0x', permit); + await expect(filltx).to.changeTokenBalances(dai, [addr, addr1], [1, -1]); + await expect(filltx).to.changeTokenBalances(weth, [addr, addr1], [-1, 1]); }); it('DAI => WETH, permit2 maker', async function () { @@ -468,9 +450,9 @@ describe('LimitOrderProtocol', function () { }); const { r, vs } = compactSignature(await signOrder(order, chainId, swap.address, addr1)); - await expect(swap.fillOrderToWithPermit(order, r, vs, 1, fillWithMakingAmount(1), addr.address, '0x', permit)) - .to.changeTokenBalances(dai, [addr, addr1], [1, -1]) - .to.changeTokenBalances(weth, [addr, addr1], [-1, 1]); + const filltx = swap.fillOrderToWithPermit(order, r, vs, 1, fillWithMakingAmount(1), addr.address, '0x', permit); + await expect(filltx).to.changeTokenBalances(dai, [addr, addr1], [1, -1]); + await expect(filltx).to.changeTokenBalances(weth, [addr, addr1], [-1, 1]); }); it('rejects reused permit', async function () { @@ -536,18 +518,18 @@ describe('LimitOrderProtocol', function () { it('maker permit works', async function () { const { dai, weth, swap, order, r, vs } = await loadFixture(deployContractsAndInitPermit); - await expect(swap.connect(addr1).fillOrderExt(order, r, vs, 1, fillWithMakingAmount(1), order.extension)) - .to.changeTokenBalances(dai, [addr, addr1], [1, -1]) - .to.changeTokenBalances(weth, [addr, addr1], [-1, 1]); + const filltx = swap.connect(addr1).fillOrderExt(order, r, vs, 1, fillWithMakingAmount(1), order.extension); + await expect(filltx).to.changeTokenBalances(dai, [addr, addr1], [1, -1]); + await expect(filltx).to.changeTokenBalances(weth, [addr, addr1], [-1, 1]); }); it('skips order permit flag', async function () { const { dai, weth, swap, order, r, vs, permit } = await loadFixture(deployContractsAndInitPermit); await addr1.sendTransaction({ to: weth.address, data: '0xd505accf' + permit.substring(42) }); - await expect(swap.connect(addr1).fillOrderExt(order, r, vs, 1, skipMakerPermit(0), order.extension)) - .to.changeTokenBalances(dai, [addr, addr1], [1, -1]) - .to.changeTokenBalances(weth, [addr, addr1], [-1, 1]); + const filltx = swap.connect(addr1).fillOrderExt(order, r, vs, 1, skipMakerPermit(0), order.extension); + await expect(filltx).to.changeTokenBalances(dai, [addr, addr1], [1, -1]); + await expect(filltx).to.changeTokenBalances(weth, [addr, addr1], [-1, 1]); }); }); }); @@ -572,9 +554,9 @@ describe('LimitOrderProtocol', function () { }); const { r, vs } = compactSignature(await signOrder(order, chainId, swap.address, addr1)); - await expect(swap.fillOrder(order, r, vs, 10, fillWithMakingAmount(10))) - .to.changeTokenBalances(dai, [addr, addr1], [10, -10]) - .to.changeTokenBalances(weth, [addr, addr1], [-10, 10]); + const filltx = swap.fillOrder(order, r, vs, 10, fillWithMakingAmount(10)); + await expect(filltx).to.changeTokenBalances(dai, [addr, addr1], [10, -10]); + await expect(filltx).to.changeTokenBalances(weth, [addr, addr1], [-10, 10]); }); it('empty takingAmountGetter should revert on partial fill', async function () { @@ -624,9 +606,9 @@ describe('LimitOrderProtocol', function () { }); const { r, vs } = compactSignature(await signOrder(order, chainId, swap.address, addr1)); - await expect(swap.fillOrder(order, r, vs, 10, 10)) - .to.changeTokenBalances(dai, [addr, addr1], [10, -10]) - .to.changeTokenBalances(weth, [addr, addr1], [-10, 10]); + const filltx = swap.fillOrder(order, r, vs, 10, 10); + await expect(filltx).to.changeTokenBalances(dai, [addr, addr1], [10, -10]); + await expect(filltx).to.changeTokenBalances(weth, [addr, addr1], [-10, 10]); }); }); @@ -658,9 +640,9 @@ describe('LimitOrderProtocol', function () { ); const orderHash = await swap.hashOrder(order); - await expect(ethOrders.connect(addr1).ethOrderDeposit(order, order.extension, { value: ether('0.3') })) - .to.changeEtherBalance(addr1, ether('-0.3')) - .to.changeTokenBalance(weth, ethOrders, ether('0.3')); + const deposittx = ethOrders.connect(addr1).ethOrderDeposit(order, order.extension, { value: ether('0.3') }); + await expect(deposittx).to.changeEtherBalance(addr1, ether('-0.3')); + await expect(deposittx).to.changeTokenBalance(weth, ethOrders, ether('0.3')); let orderMakerBalance = await ethOrders.ordersMakersBalances(orderHash); expect(orderMakerBalance.balance).to.equal(ether('0.3')); @@ -673,14 +655,14 @@ describe('LimitOrderProtocol', function () { const signature = await signOrder(order, chainId, swap.address, addr1); /// Partial fill - await expect(swap.fillContractOrderExt(order, signature, ether('200'), ether('0.2'), addr.address, '0x', '0x', order.extension)) - .to.changeTokenBalances(dai, [addr, ethOrders, addr1], [ether('-200'), '0', ether('200')]) - .to.changeTokenBalances(weth, [addr, ethOrders, addr1], [ether('0.2'), ether('-0.2'), '0']); + const filltx1 = swap.fillContractOrderExt(order, signature, ether('200'), ether('0.2'), addr.address, '0x', '0x', order.extension); + await expect(filltx1).to.changeTokenBalances(dai, [addr, ethOrders, addr1], [ether('-200'), '0', ether('200')]); + await expect(filltx1).to.changeTokenBalances(weth, [addr, ethOrders, addr1], [ether('0.2'), ether('-0.2'), '0']); /// Remaining fill - await expect(swap.fillContractOrderExt(order, signature, ether('100'), ether('0.1'), addr.address, '0x', '0x', order.extension)) - .to.changeTokenBalances(dai, [addr, ethOrders, addr1], [ether('-100'), '0', ether('100')]) - .to.changeTokenBalances(weth, [addr, ethOrders, addr1], [ether('0.1'), ether('-0.1'), '0']); + const filltx2 = swap.fillContractOrderExt(order, signature, ether('100'), ether('0.1'), addr.address, '0x', '0x', order.extension); + await expect(filltx2).to.changeTokenBalances(dai, [addr, ethOrders, addr1], [ether('-100'), '0', ether('100')]); + await expect(filltx2).to.changeTokenBalances(weth, [addr, ethOrders, addr1], [ether('0.1'), ether('-0.1'), '0']); orderMakerBalance = await ethOrders.ordersMakersBalances(orderHash); expect(orderMakerBalance.balance).to.equal(0); @@ -704,21 +686,21 @@ describe('LimitOrderProtocol', function () { ); const orderHash = await swap.hashOrder(order); - await expect(ethOrders.connect(addr1).ethOrderDeposit(order, order.extension, { value: ether('0.3') })) - .to.changeEtherBalance(addr1, ether('-0.3')) - .to.changeTokenBalance(weth, ethOrders, ether('0.3')); + const deposittx = ethOrders.connect(addr1).ethOrderDeposit(order, order.extension, { value: ether('0.3') }); + await expect(deposittx).to.changeEtherBalance(addr1, ether('-0.3')); + await expect(deposittx).to.changeTokenBalance(weth, ethOrders, ether('0.3')); const signature = await signOrder(order, chainId, swap.address, addr1); /// Partial fill - await expect(swap.fillContractOrderExt(order, signature, ether('200'), ether('0.2'), addr.address, '0x', '0x', order.extension)) - .to.changeTokenBalances(dai, [addr, ethOrders, addr1], [ether('-200'), '0', ether('200')]) - .to.changeTokenBalances(weth, [addr, ethOrders, addr1], [ether('0.2'), ether('-0.2'), '0']); + const filltx = swap.fillContractOrderExt(order, signature, ether('200'), ether('0.2'), addr.address, '0x', '0x', order.extension); + await expect(filltx).to.changeTokenBalances(dai, [addr, ethOrders, addr1], [ether('-200'), '0', ether('200')]); + await expect(filltx).to.changeTokenBalances(weth, [addr, ethOrders, addr1], [ether('0.2'), ether('-0.2'), '0']); /// Cancel order - await expect(ethOrders.connect(addr1).cancelOrder(order.makerTraits, orderHash)) - .to.changeTokenBalance(weth, ethOrders, ether('-0.1')) - .to.changeEtherBalance(addr1, ether('0.1')); + const canceltx = ethOrders.connect(addr1).cancelOrder(order.makerTraits, orderHash); + await expect(canceltx).to.changeTokenBalance(weth, ethOrders, ether('-0.1')); + await expect(canceltx).to.changeEtherBalance(addr1, ether('0.1')); /// Remaining fill failure await expect(swap.fillContractOrderExt(order, signature, ether('100'), ether('0.1'), addr.address, '0x', '0x', order.extension)) @@ -762,6 +744,19 @@ describe('LimitOrderProtocol', function () { return { dai, weth, swap, chainId, order }; }; + const orderWithEpochInit = async function () { + const { dai, weth, swap, chainId } = await deployContractsAndInit(); + const order = buildOrder({ + makerAsset: dai.address, + takerAsset: weth.address, + makingAmount: 2, + takingAmount: 2, + maker: addr1.address, + makerTraits: buildMakerTraits({ allowMultipleFills: true, shouldCheckEpoch: true, nonce: 0, series: 1 }), + }); + return { dai, weth, swap, chainId, order }; + }; + // TODO: it could be canceled with another makerTraits, 1n << ALLOW_MUTIPLE_FILLS_FLAG (254n) for example it('should cancel own order', async function () { const { swap, chainId, order } = await loadFixture(orderCancelationInit); @@ -789,6 +784,47 @@ describe('LimitOrderProtocol', function () { await expect(swap.fillOrder(order, r, vs, 1, fillWithMakingAmount(1))) .to.be.revertedWithCustomError(swap, 'InvalidatedOrder'); }); + + it('epoch change, order should fail', async function () { + const { swap, chainId, order } = await loadFixture(orderWithEpochInit); + + await swap.connect(addr1).increaseEpoch(1); + + const { r, vs } = compactSignature(await signOrder(order, chainId, swap.address, addr1)); + await expect(swap.fillOrder(order, r, vs, 2, fillWithMakingAmount(2))) + .to.be.revertedWithCustomError(swap, 'WrongSeriesNonce'); + }); + + it('epoch change should not affect other addresses', async function () { + const { dai, weth, swap, chainId, order } = await loadFixture(orderWithEpochInit); + + await swap.connect(addr2).increaseEpoch(1); + + const { r, vs } = compactSignature(await signOrder(order, chainId, swap.address, addr1)); + const filltx = swap.fillOrder(order, r, vs, 1, fillWithMakingAmount(1)); + await expect(filltx).to.changeTokenBalances(dai, [addr, addr1], [1, -1]); + await expect(filltx).to.changeTokenBalances(weth, [addr, addr1], [-1, 1]); + }); + + it('epoch change, partially filled order should fail', async function () { + const { dai, weth, swap, chainId, order } = await loadFixture(orderWithEpochInit); + + const { r, vs } = compactSignature(await signOrder(order, chainId, swap.address, addr1)); + const filltx = swap.fillOrder(order, r, vs, 1, fillWithMakingAmount(1)); + await expect(filltx).to.changeTokenBalances(dai, [addr, addr1], [1, -1]); + await expect(filltx).to.changeTokenBalances(weth, [addr, addr1], [-1, 1]); + + await swap.connect(addr1).increaseEpoch(1); + + await expect(swap.fillOrder(order, r, vs, 1, fillWithMakingAmount(1))) + .to.be.revertedWithCustomError(swap, 'WrongSeriesNonce'); + }); + + it('advance nonce', async function () { + const { swap } = await loadFixture(deployContractsAndInit); + await swap.increaseEpoch(0); + expect(await swap.epoch(addr.address, 0)).to.equal('1'); + }); }); describe('Private Orders', function () { @@ -811,9 +847,9 @@ describe('LimitOrderProtocol', function () { }); const { r, vs } = compactSignature(await signOrder(order, chainId, swap.address, addr1)); - await expect(swap.fillOrder(order, r, vs, 1, fillWithMakingAmount(1))) - .to.changeTokenBalances(dai, [addr, addr1], [1, -1]) - .to.changeTokenBalances(weth, [addr, addr1], [-1, 1]); + const filltx = swap.fillOrder(order, r, vs, 1, fillWithMakingAmount(1)); + await expect(filltx).to.changeTokenBalances(dai, [addr, addr1], [1, -1]); + await expect(filltx).to.changeTokenBalances(weth, [addr, addr1], [-1, 1]); }); it('should not fill with incorrect taker', async function () { @@ -837,16 +873,19 @@ describe('LimitOrderProtocol', function () { describe('Predicate', function () { const deployContractsAndInit = async function () { const { dai, weth, swap, chainId } = await deploySwapTokens(); + const { arbitraryPredicate } = await deployArbitraryPredicate(); await initContracts(dai, weth, swap); - return { dai, weth, swap, chainId }; + return { dai, weth, swap, chainId, arbitraryPredicate }; }; - it.skip('`or` should pass', async function () { - const { dai, weth, swap, chainId } = await loadFixture(deployContractsAndInit); + it('arbitrary call predicate should pass', async function () { + const { dai, weth, swap, chainId, arbitraryPredicate } = await loadFixture(deployContractsAndInit); - const tsBelow = swap.interface.encodeFunctionData('timestampBelow', ['0xff0000']); - const eqNonce = swap.interface.encodeFunctionData('epochEquals', [addr1.address, 0, 0]); - const { offsets, data } = joinStaticCalls([tsBelow, eqNonce]); + const arbitraryCall = swap.interface.encodeFunctionData('arbitraryStaticCall', [ + arbitraryPredicate.address, + arbitraryPredicate.interface.encodeFunctionData('copyArg', [1]), + ]); + const predicate = swap.interface.encodeFunctionData('lt', [10, arbitraryCall]); const order = buildOrder( { @@ -857,94 +896,150 @@ describe('LimitOrderProtocol', function () { maker: addr1.address, }, { - predicate: swap.interface.encodeFunctionData('or', [offsets, data]), + predicate, }, ); - const signature = await signOrder(order, chainId, swap.address, addr1); - const { r, vs } = compactSignature(signature); - const makerDai = await dai.balanceOf(addr1.address); - const takerDai = await dai.balanceOf(addr.address); - const makerWeth = await weth.balanceOf(addr1.address); - const takerWeth = await weth.balanceOf(addr.address); + const { r, vs } = compactSignature(await signOrder(order, chainId, swap.address, addr1)); + const filltx = swap.fillOrderExt(order, r, vs, 1, 1, order.extension); + await expect(filltx).to.changeTokenBalances(dai, [addr, addr1], [1, -1]); + await expect(filltx).to.changeTokenBalances(weth, [addr, addr1], [-1, 1]); + }); + + it('arbitrary call predicate should fail', async function () { + const { dai, weth, swap, chainId, arbitraryPredicate } = await loadFixture(deployContractsAndInit); - await expect( - swap.fillOrder(order, r, vs, 1, fillWithMakingAmount(1)), - ).to.revertedWithCustomError(swap, 'MissingOrderExtension'); + const arbitraryCall = swap.interface.encodeFunctionData('arbitraryStaticCall', [ + arbitraryPredicate.address, + arbitraryPredicate.interface.encodeFunctionData('copyArg', [1]), + ]); + const predicate = swap.interface.encodeFunctionData('gt', [10, arbitraryCall]); - await swap.fillOrderExt(order, r, vs, 1, fillWithMakingAmount(1), order.extension); + const order = buildOrder( + { + makerAsset: dai.address, + takerAsset: weth.address, + makingAmount: 1, + takingAmount: 1, + maker: addr1.address, + }, + { + predicate, + }, + ); - expect(await dai.balanceOf(addr1.address)).to.equal(makerDai.sub(1)); - expect(await dai.balanceOf(addr.address)).to.equal(takerDai.add(1)); - expect(await weth.balanceOf(addr1.address)).to.equal(makerWeth.add(1)); - expect(await weth.balanceOf(addr.address)).to.equal(takerWeth.sub(1)); + const { r, vs } = compactSignature(await signOrder(order, chainId, swap.address, addr1)); + await expect(swap.fillOrderExt(order, r, vs, 1, 1, order.extension)) + .to.be.revertedWithCustomError(swap, 'PredicateIsNotTrue'); }); - it('reverts on expiration constraint', async function () { - const { dai, weth, swap, chainId } = await loadFixture(deployContractsAndInit); + it('`or` should pass', async function () { + const { dai, weth, swap, chainId, arbitraryPredicate } = await loadFixture(deployContractsAndInit); - const order = buildOrder({ - makerAsset: dai.address, - takerAsset: weth.address, - makingAmount: 1, - takingAmount: 1, - maker: addr1.address, - makerTraits: buildMakerTraits({ expiry: 0xff0000n }), - }); + const arbitraryCallPredicate = swap.interface.encodeFunctionData('arbitraryStaticCall', [ + arbitraryPredicate.address, + arbitraryPredicate.interface.encodeFunctionData('copyArg', [10]), + ]); + const comparelt = swap.interface.encodeFunctionData('lt', [15, arbitraryCallPredicate]); + const comparegt = swap.interface.encodeFunctionData('gt', [5, arbitraryCallPredicate]); + + const { offsets, data } = joinStaticCalls([comparelt, comparegt]); + const predicate = swap.interface.encodeFunctionData('or', [offsets, data]); + + const order = buildOrder( + { + makerAsset: dai.address, + takerAsset: weth.address, + makingAmount: 1, + takingAmount: 1, + maker: addr1.address, + }, + { + predicate, + }, + ); const { r, vs } = compactSignature(await signOrder(order, chainId, swap.address, addr1)); - await expect(swap.fillOrder(order, r, vs, 1, fillWithMakingAmount(1))) - .to.be.revertedWithCustomError(swap, 'OrderExpired'); + const filltx = swap.fillOrderExt(order, r, vs, 1, 1, order.extension); + await expect(filltx).to.changeTokenBalances(dai, [addr, addr1], [1, -1]); + await expect(filltx).to.changeTokenBalances(weth, [addr, addr1], [-1, 1]); }); - it('`and` should pass', async function () { - const { dai, weth, swap, chainId } = await loadFixture(deployContractsAndInit); + it('`or` should fail', async function () { + const { dai, weth, swap, chainId, arbitraryPredicate } = await loadFixture(deployContractsAndInit); - const order = buildOrder({ - makerAsset: dai.address, - takerAsset: weth.address, - makingAmount: 1, - takingAmount: 1, - maker: addr1.address, - makerTraits: buildMakerTraits({ expiry: 0xff000000n }), - }); + const arbitraryCallPredicate = swap.interface.encodeFunctionData('arbitraryStaticCall', [ + arbitraryPredicate.address, + arbitraryPredicate.interface.encodeFunctionData('copyArg', [10]), + ]); + const comparelt = swap.interface.encodeFunctionData('lt', [5, arbitraryCallPredicate]); + const comparegt = swap.interface.encodeFunctionData('gt', [15, arbitraryCallPredicate]); + + const { offsets, data } = joinStaticCalls([comparelt, comparegt]); + const predicate = swap.interface.encodeFunctionData('or', [offsets, data]); + + const order = buildOrder( + { + makerAsset: dai.address, + takerAsset: weth.address, + makingAmount: 1, + takingAmount: 1, + maker: addr1.address, + }, + { + predicate, + }, + ); const { r, vs } = compactSignature(await signOrder(order, chainId, swap.address, addr1)); - await expect(swap.fillOrder(order, r, vs, fillWithMakingAmount(1), 1)) - .to.changeTokenBalances(dai, [addr, addr1], [1, -1]) - .to.changeTokenBalances(weth, [addr, addr1], [-1, 1]); + await expect(swap.fillOrderExt(order, r, vs, 1, 1, order.extension)) + .to.be.revertedWithCustomError(swap, 'PredicateIsNotTrue'); }); - it('nonce + ts example', async function () { - const { dai, weth, swap, chainId } = await loadFixture(deployContractsAndInit); + it('`and` should pass', async function () { + const { dai, weth, swap, chainId, arbitraryPredicate } = await loadFixture(deployContractsAndInit); - const order = buildOrder({ - makerAsset: dai.address, - takerAsset: weth.address, - makingAmount: 1, - takingAmount: 1, - maker: addr1.address, - makerTraits: buildMakerTraits({ expiry: 0xff000000n }), - }); + const arbitraryCallPredicate = swap.interface.encodeFunctionData('arbitraryStaticCall', [ + arbitraryPredicate.address, + arbitraryPredicate.interface.encodeFunctionData('copyArg', [10]), + ]); + const comparelt = swap.interface.encodeFunctionData('lt', [15, arbitraryCallPredicate]); + const comparegt = swap.interface.encodeFunctionData('gt', [5, arbitraryCallPredicate]); + + const { offsets, data } = joinStaticCalls([comparelt, comparegt]); + const predicate = swap.interface.encodeFunctionData('and', [offsets, data]); + + const order = buildOrder( + { + makerAsset: dai.address, + takerAsset: weth.address, + makingAmount: 1, + takingAmount: 1, + maker: addr1.address, + }, + { + predicate, + }, + ); const { r, vs } = compactSignature(await signOrder(order, chainId, swap.address, addr1)); - await expect(swap.fillOrder(order, r, vs, 1, fillWithMakingAmount(1))) - .to.changeTokenBalances(dai, [addr, addr1], [1, -1]) - .to.changeTokenBalances(weth, [addr, addr1], [-1, 1]); + const filltx = swap.fillOrderExt(order, r, vs, 1, 1, order.extension); + await expect(filltx).to.changeTokenBalances(dai, [addr, addr1], [1, -1]); + await expect(filltx).to.changeTokenBalances(weth, [addr, addr1], [-1, 1]); }); - it('advance nonce', async function () { - const { swap } = await loadFixture(deployContractsAndInit); - await swap.increaseEpoch(0); - expect(await swap.epoch(addr.address, 0)).to.equal('1'); - }); + it('`and` should fail', async function () { + const { dai, weth, swap, chainId, arbitraryPredicate } = await loadFixture(deployContractsAndInit); - it.skip('`and` should fail', async function () { - const { dai, weth, swap, chainId } = await loadFixture(deployContractsAndInit); + const arbitraryCallPredicate = swap.interface.encodeFunctionData('arbitraryStaticCall', [ + arbitraryPredicate.address, + arbitraryPredicate.interface.encodeFunctionData('copyArg', [10]), + ]); + const comparelt = swap.interface.encodeFunctionData('lt', [5, arbitraryCallPredicate]); + const comparegt = swap.interface.encodeFunctionData('gt', [15, arbitraryCallPredicate]); - const tsBelow = swap.interface.encodeFunctionData('timestampBelow', [0xff0000n]); - const eqNonce = swap.interface.encodeFunctionData('epochEquals', [addr1.address, 0, 0]); - const { offsets, data } = joinStaticCalls([tsBelow, eqNonce]); + const { offsets, data } = joinStaticCalls([comparelt, comparegt]); + const predicate = swap.interface.encodeFunctionData('and', [offsets, data]); const order = buildOrder( { @@ -955,12 +1050,12 @@ describe('LimitOrderProtocol', function () { maker: addr1.address, }, { - predicate: swap.interface.encodeFunctionData('and', [offsets, data]), + predicate, }, ); const { r, vs } = compactSignature(await signOrder(order, chainId, swap.address, addr1)); - await expect(swap.fillOrderExt(order, r, vs, 1, fillWithMakingAmount(1), order.extension)) + await expect(swap.fillOrderExt(order, r, vs, 1, 1, order.extension)) .to.be.revertedWithCustomError(swap, 'PredicateIsNotTrue'); }); }); @@ -981,12 +1076,13 @@ describe('LimitOrderProtocol', function () { makingAmount: 1, takingAmount: 1, maker: addr1.address, + makerTraits: buildMakerTraits({ expiry: (await time.latest()) + 3600 }), }); const { r, vs } = compactSignature(await signOrder(order, chainId, swap.address, addr1)); - await expect(swap.fillOrder(order, r, vs, fillWithMakingAmount(1), 1)) - .to.changeTokenBalances(dai, [addr, addr1], [1, -1]) - .to.changeTokenBalances(weth, [addr, addr1], [-1, 1]); + const filltx = swap.fillOrder(order, r, vs, fillWithMakingAmount(1), 1); + await expect(filltx).to.changeTokenBalances(dai, [addr, addr1], [1, -1]); + await expect(filltx).to.changeTokenBalances(weth, [addr, addr1], [-1, 1]); }); it('should not fill when expired', async function () { @@ -1006,6 +1102,46 @@ describe('LimitOrderProtocol', function () { .to.be.revertedWithCustomError(swap, 'OrderExpired'); }); + it('should not partially fill when expired', async function () { + const { dai, weth, swap, chainId } = await loadFixture(deployContractsAndInit); + + const order = buildOrder({ + makerAsset: dai.address, + takerAsset: weth.address, + makingAmount: 10, + takingAmount: 2, + maker: addr1.address, + makerTraits: buildMakerTraits({ expiry: await time.latest() }), + }); + + const { r, vs } = compactSignature(await signOrder(order, chainId, swap.address, addr1)); + await expect(swap.fillOrder(order, r, vs, 4, fillWithMakingAmount(1))) + .to.be.revertedWithCustomError(swap, 'OrderExpired'); + }); + + it('should not fill partially filled order after expiration', async function () { + const { dai, weth, swap, chainId } = await loadFixture(deployContractsAndInit); + + const order = buildOrder({ + makerAsset: dai.address, + takerAsset: weth.address, + makingAmount: 10, + takingAmount: 2, + maker: addr1.address, + makerTraits: buildMakerTraits({ expiry: await time.latest() + 1800 }), + }); + + const { r, vs } = compactSignature(await signOrder(order, chainId, swap.address, addr1)); + const filltx = swap.fillOrder(order, r, vs, 1, fillWithMakingAmount(1)); + await expect(filltx).to.changeTokenBalances(dai, [addr, addr1], [1, -1]); + await expect(filltx).to.changeTokenBalances(weth, [addr, addr1], [-1, 1]); + + await time.increase(3600); + + await expect(swap.fillOrder(order, r, vs, 1, fillWithMakingAmount(1))) + .to.be.revertedWithCustomError(swap, 'OrderExpired'); + }); + it('should fill partially if not enough coins (taker)', async function () { const { dai, weth, swap, chainId } = await loadFixture(deployContractsAndInit); @@ -1018,9 +1154,9 @@ describe('LimitOrderProtocol', function () { }); const { r, vs } = compactSignature(await signOrder(order, chainId, swap.address, addr1)); - await expect(swap.fillOrder(order, r, vs, 3, 2)) - .to.changeTokenBalances(dai, [addr, addr1], [2, -2]) - .to.changeTokenBalances(weth, [addr, addr1], [-2, 2]); + const filltx = swap.fillOrder(order, r, vs, 3, 2); + await expect(filltx).to.changeTokenBalances(dai, [addr, addr1], [2, -2]); + await expect(filltx).to.changeTokenBalances(weth, [addr, addr1], [-2, 2]); }); it('should fill partially if not enough coins (maker)', async function () { @@ -1035,9 +1171,9 @@ describe('LimitOrderProtocol', function () { }); const { r, vs } = compactSignature(await signOrder(order, chainId, swap.address, addr1)); - await expect(swap.fillOrder(order, r, vs, 3, fillWithMakingAmount(3))) - .to.changeTokenBalances(dai, [addr, addr1], [2, -2]) - .to.changeTokenBalances(weth, [addr, addr1], [-2, 2]); + const filltx = swap.fillOrder(order, r, vs, 3, fillWithMakingAmount(3)); + await expect(filltx).to.changeTokenBalances(dai, [addr, addr1], [2, -2]); + await expect(filltx).to.changeTokenBalances(weth, [addr, addr1], [-2, 2]); }); }); @@ -1060,10 +1196,10 @@ describe('LimitOrderProtocol', function () { }); const { r, vs } = compactSignature(await signOrder(order, chainId, swap.address, addr1)); - await expect(swap.fillOrder(order, r, vs, 900, fillWithMakingAmount(3), { value: 3 })) - .to.changeTokenBalances(dai, [addr, addr1], [900, -900]) - .to.changeTokenBalances(weth, [addr, addr1], [0, 3]) - .to.changeEtherBalance(addr, -3); + const filltx = swap.fillOrder(order, r, vs, 900, fillWithMakingAmount(3), { value: 3 }); + await expect(filltx).to.changeTokenBalances(dai, [addr, addr1], [900, -900]); + await expect(filltx).to.changeTokenBalances(weth, [addr, addr1], [0, 3]); + await expect(filltx).to.changeEtherBalance(addr, -3); }); it('should revert with takerAsset WETH and not enough msg.value', async function () { @@ -1094,10 +1230,10 @@ describe('LimitOrderProtocol', function () { }); const { r, vs } = compactSignature(await signOrder(order, chainId, swap.address, addr1)); - await expect(swap.fillOrder(order, r, vs, 900, fillWithMakingAmount(3), { value: 4 })) - .to.changeTokenBalances(dai, [addr, addr1], [900, -900]) - .to.changeTokenBalances(weth, [addr, addr1], [0, 3]) - .to.changeEtherBalance(addr, -3); + const filltx = swap.fillOrder(order, r, vs, 900, fillWithMakingAmount(3), { value: 4 }); + await expect(filltx).to.changeTokenBalances(dai, [addr, addr1], [900, -900]); + await expect(filltx).to.changeTokenBalances(weth, [addr, addr1], [0, 3]); + await expect(filltx).to.changeEtherBalance(addr, -3); }); it('should reverted with takerAsset non-WETH and msg.value greater than 0', async function () { diff --git a/test/helpers/fixtures.js b/test/helpers/fixtures.js index 4ed0af81..a863142e 100644 --- a/test/helpers/fixtures.js +++ b/test/helpers/fixtures.js @@ -34,6 +34,13 @@ async function deployUSDC () { return { usdc }; }; +async function deployArbitraryPredicate () { + const ArbitraryPredicateMock = await ethers.getContractFactory('ArbitraryPredicateMock'); + const arbitraryPredicate = await ArbitraryPredicateMock.deploy(); + await arbitraryPredicate.deployed(); + return { arbitraryPredicate }; +}; + async function deployUSDT () { const TokenMock = await ethers.getContractFactory('TokenMock'); const usdt = await TokenMock.deploy('USDT', 'USDT'); @@ -61,5 +68,6 @@ module.exports = { deployRangeAmountCalculator, deployUSDT, deployUSDC, + deployArbitraryPredicate, deploySwap, };