From c1684f8b12870dfad7c8af8a3b9744b2cfdc97aa Mon Sep 17 00:00:00 2001 From: kasperpawlowski Date: Thu, 1 Sep 2022 10:50:05 -0400 Subject: [PATCH 1/4] defer liquidity check on more than one account, support max wrap/unwrap of pTokens --- contracts/modules/Exec.sol | 41 ++++++++++++++++++++++++++++++++++---- 1 file changed, 37 insertions(+), 4 deletions(-) diff --git a/contracts/modules/Exec.sol b/contracts/modules/Exec.sol index dbc3a40e..d9dd4e69 100644 --- a/contracts/modules/Exec.sol +++ b/contracts/modules/Exec.sol @@ -100,6 +100,31 @@ contract Exec is BaseLogic { if (status == DEFERLIQUIDITY__DIRTY) checkLiquidity(account); } + /// @notice Defer liquidity checking for an array of accounts, to perform rebalancing, flash loans, etc. msg.sender must implement IDeferredLiquidityCheck + /// @param accounts The array of accounts to defer liquidity for + /// @param data Passed through to the onDeferredLiquidityCheck() callback, so contracts don't need to store transient data in storage + function deferLiquidityCheckExtended(address[] memory accounts, bytes memory data) external reentrantOK { + address msgSender = unpackTrailingParamMsgSender(); + + for (uint i = 0; i < accounts.length; ++i) { + address account = accounts[i]; + + require(accountLookup[account].deferLiquidityStatus == DEFERLIQUIDITY__NONE, "e/defer/reentrancy"); + accountLookup[account].deferLiquidityStatus = DEFERLIQUIDITY__CLEAN; + } + + IDeferredLiquidityCheck(msgSender).onDeferredLiquidityCheck(data); + + for (uint i = 0; i < accounts.length; ++i) { + address account = accounts[i]; + + uint8 status = accountLookup[account].deferLiquidityStatus; + accountLookup[account].deferLiquidityStatus = DEFERLIQUIDITY__NONE; + + if (status == DEFERLIQUIDITY__DIRTY) checkLiquidity(account); + } + } + /// @notice Execute several operations in a single transaction /// @param items List of operations to execute /// @param deferLiquidityChecks List of user accounts to defer liquidity checks for @@ -183,10 +208,14 @@ contract Exec is BaseLogic { /// @notice Transfer underlying tokens from sender's wallet into the pToken wrapper. Allowance should be set for the euler address. /// @param underlying Token address - /// @param amount The amount to wrap in underlying units + /// @param amount The amount to wrap in underlying units (use max uint256 for full underlying token balance) function pTokenWrap(address underlying, uint amount) external nonReentrant { address msgSender = unpackTrailingParamMsgSender(); + if (amount == type(uint).max) { + amount = IERC20(underlying).balanceOf(msgSender); + } + emit PTokenWrap(underlying, msgSender, amount); address pTokenAddr = reversePTokenLookup[underlying]; @@ -204,15 +233,19 @@ contract Exec is BaseLogic { /// @notice Transfer underlying tokens from the pToken wrapper to the sender's wallet. /// @param underlying Token address - /// @param amount The amount to unwrap in underlying units + /// @param amount The amount to unwrap in underlying units (use max uint256 for full underlying token balance) function pTokenUnWrap(address underlying, uint amount) external nonReentrant { address msgSender = unpackTrailingParamMsgSender(); - emit PTokenUnWrap(underlying, msgSender, amount); - address pTokenAddr = reversePTokenLookup[underlying]; require(pTokenAddr != address(0), "e/exec/ptoken-not-found"); + if (amount == type(uint).max) { + amount = PToken(pTokenAddr).balanceOf(msgSender); + } + + emit PTokenUnWrap(underlying, msgSender, amount); + PToken(pTokenAddr).forceUnwrap(msgSender, amount); } From e643f8ffc50ef0ef497636a3061ddebf534d643e Mon Sep 17 00:00:00 2001 From: kasperpawlowski Date: Thu, 1 Sep 2022 10:50:33 -0400 Subject: [PATCH 2/4] add test for max wrap/unwrap of pTokens --- test/pToken.js | 28 ++++++++++++++++++++++++++++ 1 file changed, 28 insertions(+) diff --git a/test/pToken.js b/test/pToken.js index 9a567071..634ee020 100644 --- a/test/pToken.js +++ b/test/pToken.js @@ -127,6 +127,34 @@ et.testSet({ ], }) +.test({ + desc: "batch wrapping amount max", + actions: ctx => [ + { send: 'tokens.TST.approve', args: [ctx.contracts.euler.address, et.MaxUint256,], }, + + { action: 'sendBatch', batch: [ + { send: 'exec.pTokenWrap', args: [ctx.contracts.tokens.TST.address, et.MaxUint256], }, + { send: 'eTokens.epTST.deposit', args: [0, et.eth(5)], }, + { send: 'markets.enterMarket', args: [0, ctx.contracts.pTokens.pTST.address], }, + ]}, + + { call: 'exec.detailedLiquidity', args: [ctx.wallet.address], onResult: r => { + et.equals(r[0].status.collateralValue, 3.75, 0.001); + }, }, + + { call: 'pTokens.pTST.balanceOf', args: [ctx.wallet.address], equals: 95, }, + { call: 'tokens.TST.balanceOf', args: [ctx.wallet.address], equals: 0, }, + + { action: 'sendBatch', batch: [ + { send: 'eTokens.epTST.withdraw', args: [0, et.eth(3)], }, + { send: 'exec.pTokenUnWrap', args: [ctx.contracts.tokens.TST.address, et.MaxUint256], }, + ]}, + + { call: 'pTokens.pTST.balanceOf', args: [ctx.wallet.address], equals: 0, }, + { call: 'tokens.TST.balanceOf', args: [ctx.wallet.address], equals: 98, }, + ], +}) + .test({ desc: "activate market for ptoken", From b4edbe4eae94a543ef6398f76731683db03a8875 Mon Sep 17 00:00:00 2001 From: kasperpawlowski Date: Thu, 1 Sep 2022 21:03:11 -0400 Subject: [PATCH 3/4] add tests for defer liquidity checks --- contracts/test/DeferredLiquidityCheckTest.sol | 111 ++++++++++++++++ test/batch.js | 15 +++ test/deferLiquidityCheck.js | 122 ++++++++++++++++++ test/lib/eTestLib.js | 1 + 4 files changed, 249 insertions(+) create mode 100644 contracts/test/DeferredLiquidityCheckTest.sol create mode 100644 test/deferLiquidityCheck.js diff --git a/contracts/test/DeferredLiquidityCheckTest.sol b/contracts/test/DeferredLiquidityCheckTest.sol new file mode 100644 index 00000000..748f8488 --- /dev/null +++ b/contracts/test/DeferredLiquidityCheckTest.sol @@ -0,0 +1,111 @@ +// SPDX-License-Identifier: GPL-2.0-or-later + +pragma solidity ^0.8.0; + +import "../modules/Exec.sol"; +import "../modules/Markets.sol"; +import "../modules/DToken.sol"; + + +contract DeferredLiquidityCheckTest is IDeferredLiquidityCheck { + uint constant AMOUNT = 1 ether; + address euler; + address markets; + address exec; + + event onDeferredLiquidityCheckEvent(); + + constructor(address eulerAddr, address marketsAddr, address execAddr) { + euler = eulerAddr; + markets = marketsAddr; + exec = execAddr; + } + + function getSubAccount(uint subAccountId) internal view returns (address) { + require(subAccountId < 256, "sub-account-id-too-big"); + return address(uint160(address(this)) ^ uint160(subAccountId)); + } + + function onDeferredLiquidityCheck(bytes memory data) external override { + (address underlying, address[] memory accounts, uint scenario) = abi.decode(data, (address, address[], uint)); + + address dToken = Markets(markets).underlyingToDToken(underlying); + IERC20(underlying).approve(euler, type(uint).max); + emit onDeferredLiquidityCheckEvent(); + + if (scenario == 1) { + DToken(dToken).borrow(0, AMOUNT); + DToken(dToken).repay(0, AMOUNT); + } else if (scenario == 2) { + DToken(dToken).borrow(0, AMOUNT); + DToken(dToken).borrow(1, AMOUNT); + DToken(dToken).repay(0, AMOUNT); + DToken(dToken).repay(1, AMOUNT); + } else if (scenario == 3) { + Exec(exec).deferLiquidityCheckExtended(accounts, abi.encode(underlying, accounts, 1)); + } else if (scenario == 4) { + Exec(exec).deferLiquidityCheck(accounts[accounts.length - 1], abi.encode(underlying, accounts, 1)); + } else if (scenario == 5) { + address account = getSubAccount(1); + accounts[0] = account; + Exec(exec).deferLiquidityCheck(account, abi.encode(underlying, accounts, 1)); + Exec(exec).deferLiquidityCheck(account, abi.encode(underlying, accounts, 2)); + Exec(exec).deferLiquidityCheckExtended(accounts, abi.encode(underlying, accounts, 1)); + Exec(exec).deferLiquidityCheckExtended(accounts, abi.encode(underlying, accounts, 2)); + } else { + revert("onDeferredLiquidityCheck: wrong scenario"); + } + } + + function test(address underlying, address[] memory accounts, uint scenario) external { + if (scenario == 1) { + Exec(exec).deferLiquidityCheck(accounts[0], abi.encode(underlying, accounts, scenario)); + } else if (scenario == 2) { + Exec(exec).deferLiquidityCheckExtended(accounts, abi.encode(underlying, accounts, scenario)); + } else if (scenario == 3) { + Exec(exec).deferLiquidityCheck(accounts[0], abi.encode(underlying, accounts, scenario)); + } else if (scenario == 4) { + Exec(exec).deferLiquidityCheckExtended(accounts, abi.encode(underlying, accounts, scenario)); + } else if (scenario == 5) { + Exec(exec).deferLiquidityCheck(accounts[0], abi.encode(underlying, accounts, scenario)); + } else if (scenario == 6) { + Exec.EulerBatchItem[] memory items = new Exec.EulerBatchItem[](1); + items[0] = Exec.EulerBatchItem( + false, + exec, + abi.encodeWithSelector( + Exec.deferLiquidityCheck.selector, + accounts[0], + abi.encode(underlying, accounts, 1) + ) + ); + Exec(exec).batchDispatch(items, accounts); + } else if (scenario == 7) { + Exec.EulerBatchItem[] memory items = new Exec.EulerBatchItem[](1); + items[0] = Exec.EulerBatchItem( + false, + exec, + abi.encodeWithSelector( + Exec.deferLiquidityCheckExtended.selector, + accounts, + abi.encode(underlying, accounts, 1) + ) + ); + Exec(exec).batchDispatch(items, accounts); + } else if (scenario == 8) { + Exec.EulerBatchItem[] memory items = new Exec.EulerBatchItem[](1); + items[0] = Exec.EulerBatchItem( + false, + exec, + abi.encodeWithSelector( + Exec.deferLiquidityCheck.selector, + getSubAccount(1), + abi.encode(underlying, accounts, 1) + ) + ); + Exec(exec).batchDispatch(items, accounts); + } else { + revert("test: wrong scenario"); + } + } +} diff --git a/test/batch.js b/test/batch.js index c371f645..8457ba2f 100644 --- a/test/batch.js +++ b/test/batch.js @@ -145,6 +145,21 @@ et.testSet({ }) +.test({ + desc: "defer extended reentrancy", + actions: ctx => [ + { action: 'sendBatch', deferLiquidityChecks: [et.getSubAccount(ctx.wallet.address, 1), et.getSubAccount(ctx.wallet.address, 2)], batch: [ + { send: 'eTokens.eTST.transfer', args: [et.getSubAccount(ctx.wallet.address, 2), et.eth(1)], }, + { send: 'exec.deferLiquidityCheckExtended', args: [ + [et.getSubAccount(ctx.wallet.address, 2)], + ctx.contracts.eTokens.eTST.interface.encodeFunctionData('transfer', [ctx.wallet.address, et.eth(1)]), + ]} + ], expectError: 'e/defer/reentrancy', + }, + ], +}) + + .test({ desc: "allow error", actions: ctx => [ diff --git a/test/deferLiquidityCheck.js b/test/deferLiquidityCheck.js new file mode 100644 index 00000000..9478df4b --- /dev/null +++ b/test/deferLiquidityCheck.js @@ -0,0 +1,122 @@ +const et = require('./lib/eTestLib'); + +et.testSet({ + desc: "defer liquidity check", + + preActions: ctx => [ + { send: 'tokens.TST.mint', args: [ctx.wallet.address, et.eth(100)], }, + { send: 'tokens.TST.approve', args: [ctx.contracts.euler.address, et.MaxUint256,], }, + { send: 'eTokens.eTST.deposit', args: [0, et.MaxUint256], }, + { action: 'updateUniswapPrice', pair: 'TST/WETH', price: '.01', }, + { action: 'cb', cb: async () => { + ctx.contracts.deferredLiquidityCheckTest = await (await ctx.factories.DeferredLiquidityCheckTest.deploy( + ctx.contracts.euler.address, + ctx.contracts.markets.address, + ctx.contracts.exec.address, + )).deployed(); + }} + ], +}) + +.test({ + desc: "simple defer liquidity check", + actions: ctx => [ + // should revert as liquidity deferred for wrong address + { call: 'deferredLiquidityCheckTest.test', + args: [ctx.contracts.tokens.TST.address, [ et.AddressZero ], 1], + expectError: 'e/collateral-violation' + }, + + // should pass as liquidity deferred for correct address + { call: 'deferredLiquidityCheckTest.test', + args: [ctx.contracts.tokens.TST.address, [ ctx.contracts.deferredLiquidityCheckTest.address ], 1], + onLogs: logs => { + et.expect(logs.findIndex(log => log.name === "onDeferredLiquidityCheckEvent")).to.gt(-1); + }}, + ], +}) + +.test({ + desc: "extended defer liquidity check", + actions: ctx => [ + // should revert as liquidity deferred only for one address + { call: 'deferredLiquidityCheckTest.test', + args: [ctx.contracts.tokens.TST.address, [ et.getSubAccount(ctx.contracts.deferredLiquidityCheckTest.address, 0) ], 2], + expectError: 'e/collateral-violation' + }, + + // should pass as liquidity deferred for both addresses + { call: 'deferredLiquidityCheckTest.test', + args: [ctx.contracts.tokens.TST.address, [ et.getSubAccount(ctx.contracts.deferredLiquidityCheckTest.address, 0), et.getSubAccount(ctx.contracts.deferredLiquidityCheckTest.address, 1) ], 2], + onLogs: logs => { + et.expect(logs.findIndex(log => log.name === "onDeferredLiquidityCheckEvent")).to.gt(-1); + }}, + ], +}) + +.test({ + desc: "defer liquidity check - reentrancies", + actions: ctx => [ + // should revert due to reentrancy enforced by scenario 3 + { call: 'deferredLiquidityCheckTest.test', + args: [ctx.contracts.tokens.TST.address, [ ctx.contracts.deferredLiquidityCheckTest.address ], 3], + expectError: 'e/defer/reentrancy' + }, + + // should revert due to reentrancy enforced by scenario 4 + { call: 'deferredLiquidityCheckTest.test', + args: [ctx.contracts.tokens.TST.address, + [ et.getSubAccount(ctx.contracts.deferredLiquidityCheckTest.address, 0), + et.getSubAccount(ctx.contracts.deferredLiquidityCheckTest.address, 1), + et.getSubAccount(ctx.contracts.deferredLiquidityCheckTest.address, 2) + ], 4], + expectError: 'e/defer/reentrancy' + }, + + // should pass as scenario 5 re-enters, but defers liquidity for an address not deferred before + { call: 'deferredLiquidityCheckTest.test', + args: [ctx.contracts.tokens.TST.address, [ ctx.contracts.deferredLiquidityCheckTest.address ], 5], + onLogs: logs => { + for(const i = 0; i < 4; i++) { + if (i > 0) { + logs.splice(index, 1) + } + const index = logs.findIndex(log => log.name === "onDeferredLiquidityCheckEvent") + et.expect(index).to.gt(-1); + } + }}, + ], +}) + +.test({ + desc: "defer liquidity check from batch dispatch", + actions: ctx => [ + // should revert due to reentrancy enforced from batch dispatch in scenario 6 + { call: 'deferredLiquidityCheckTest.test', + args: [ctx.contracts.tokens.TST.address, [ ctx.contracts.deferredLiquidityCheckTest.address ], 6], + expectError: 'e/defer/reentrancy' + }, + + // should revert due to reentrancy enforced from batch dispatch in scenario 7 + { call: 'deferredLiquidityCheckTest.test', + args: [ctx.contracts.tokens.TST.address, [ et.getSubAccount(ctx.contracts.deferredLiquidityCheckTest.address, 0), et.getSubAccount(ctx.contracts.deferredLiquidityCheckTest.address, 1) ], 7], + expectError: 'e/defer/reentrancy' + }, + + // should revert due to reentrancy enforced from batch dispatch in scenario 8 + { call: 'deferredLiquidityCheckTest.test', + args: [ctx.contracts.tokens.TST.address, [ et.getSubAccount(ctx.contracts.deferredLiquidityCheckTest.address, 0), et.getSubAccount(ctx.contracts.deferredLiquidityCheckTest.address, 1) ], 8], + expectError: 'e/defer/reentrancy' + }, + + // should pass as batch dispatch defers liquidity for different account than defer liquidity check called from batch dispatch + { call: 'deferredLiquidityCheckTest.test', + args: [ctx.contracts.tokens.TST.address, [ ctx.contracts.deferredLiquidityCheckTest.address ], 8], + onLogs: logs => { + et.expect(logs.findIndex(log => log.name === "onDeferredLiquidityCheckEvent")).to.gt(-1); + }}, + ], +}) + + +.run(); diff --git a/test/lib/eTestLib.js b/test/lib/eTestLib.js index 209cdbe5..2e259e4d 100644 --- a/test/lib/eTestLib.js +++ b/test/lib/eTestLib.js @@ -89,6 +89,7 @@ const contractNames = [ 'TestModule', 'MockAggregatorProxy', 'MockStETH', + 'DeferredLiquidityCheckTest', // Custom Oracles From 52d68ac0f9065dffb78fc60edab8351980b28423 Mon Sep 17 00:00:00 2001 From: kasperpawlowski Date: Wed, 7 Sep 2022 16:20:11 -0400 Subject: [PATCH 4/4] updated as per review remarks --- contracts/modules/Exec.sol | 19 ++++---- contracts/test/DeferredLiquidityCheckTest.sol | 33 ++++++++++---- test/deferLiquidityCheck.js | 45 ++++++++++++++++--- 3 files changed, 71 insertions(+), 26 deletions(-) diff --git a/contracts/modules/Exec.sol b/contracts/modules/Exec.sol index d9dd4e69..1adef7af 100644 --- a/contracts/modules/Exec.sol +++ b/contracts/modules/Exec.sol @@ -87,23 +87,20 @@ contract Exec is BaseLogic { /// @param account The account to defer liquidity for. Usually address(this), although not always /// @param data Passed through to the onDeferredLiquidityCheck() callback, so contracts don't need to store transient data in storage function deferLiquidityCheck(address account, bytes memory data) external reentrantOK { - address msgSender = unpackTrailingParamMsgSender(); - - require(accountLookup[account].deferLiquidityStatus == DEFERLIQUIDITY__NONE, "e/defer/reentrancy"); - accountLookup[account].deferLiquidityStatus = DEFERLIQUIDITY__CLEAN; - - IDeferredLiquidityCheck(msgSender).onDeferredLiquidityCheck(data); + address[] memory accounts = new address[](1); + accounts[0] = account; - uint8 status = accountLookup[account].deferLiquidityStatus; - accountLookup[account].deferLiquidityStatus = DEFERLIQUIDITY__NONE; - - if (status == DEFERLIQUIDITY__DIRTY) checkLiquidity(account); + doDeferLiquidityCheckMulti(accounts, data); } /// @notice Defer liquidity checking for an array of accounts, to perform rebalancing, flash loans, etc. msg.sender must implement IDeferredLiquidityCheck /// @param accounts The array of accounts to defer liquidity for /// @param data Passed through to the onDeferredLiquidityCheck() callback, so contracts don't need to store transient data in storage - function deferLiquidityCheckExtended(address[] memory accounts, bytes memory data) external reentrantOK { + function deferLiquidityCheckMulti(address[] memory accounts, bytes memory data) external reentrantOK { + doDeferLiquidityCheckMulti(accounts, data); + } + + function doDeferLiquidityCheckMulti(address[] memory accounts, bytes memory data) internal { address msgSender = unpackTrailingParamMsgSender(); for (uint i = 0; i < accounts.length; ++i) { diff --git a/contracts/test/DeferredLiquidityCheckTest.sol b/contracts/test/DeferredLiquidityCheckTest.sol index 748f8488..c47fc40b 100644 --- a/contracts/test/DeferredLiquidityCheckTest.sol +++ b/contracts/test/DeferredLiquidityCheckTest.sol @@ -42,7 +42,7 @@ contract DeferredLiquidityCheckTest is IDeferredLiquidityCheck { DToken(dToken).repay(0, AMOUNT); DToken(dToken).repay(1, AMOUNT); } else if (scenario == 3) { - Exec(exec).deferLiquidityCheckExtended(accounts, abi.encode(underlying, accounts, 1)); + Exec(exec).deferLiquidityCheckMulti(accounts, abi.encode(underlying, accounts, 1)); } else if (scenario == 4) { Exec(exec).deferLiquidityCheck(accounts[accounts.length - 1], abi.encode(underlying, accounts, 1)); } else if (scenario == 5) { @@ -50,8 +50,21 @@ contract DeferredLiquidityCheckTest is IDeferredLiquidityCheck { accounts[0] = account; Exec(exec).deferLiquidityCheck(account, abi.encode(underlying, accounts, 1)); Exec(exec).deferLiquidityCheck(account, abi.encode(underlying, accounts, 2)); - Exec(exec).deferLiquidityCheckExtended(accounts, abi.encode(underlying, accounts, 1)); - Exec(exec).deferLiquidityCheckExtended(accounts, abi.encode(underlying, accounts, 2)); + Exec(exec).deferLiquidityCheckMulti(accounts, abi.encode(underlying, accounts, 1)); + Exec(exec).deferLiquidityCheckMulti(accounts, abi.encode(underlying, accounts, 2)); + } else if (scenario == 6) { + Exec.EulerBatchItem[] memory items = new Exec.EulerBatchItem[](2); + items[0] = Exec.EulerBatchItem(false, dToken, abi.encodeWithSelector(DToken.borrow.selector, 0, AMOUNT)); + items[1] = Exec.EulerBatchItem(false, dToken, abi.encodeWithSelector(DToken.repay.selector, 0, AMOUNT)); + accounts[0] = getSubAccount(0); + Exec(exec).batchDispatch(items, accounts); + } else if (scenario == 7) { + Exec.EulerBatchItem[] memory items = new Exec.EulerBatchItem[](2); + items[0] = Exec.EulerBatchItem(false, dToken, abi.encodeWithSelector(DToken.borrow.selector, 0, AMOUNT)); + items[1] = Exec.EulerBatchItem(false, dToken, abi.encodeWithSelector(DToken.repay.selector, 0, AMOUNT)); + accounts[0] = getSubAccount(0); + accounts[1] = address(0); + Exec(exec).batchDispatch(items, accounts); } else { revert("onDeferredLiquidityCheck: wrong scenario"); } @@ -61,14 +74,18 @@ contract DeferredLiquidityCheckTest is IDeferredLiquidityCheck { if (scenario == 1) { Exec(exec).deferLiquidityCheck(accounts[0], abi.encode(underlying, accounts, scenario)); } else if (scenario == 2) { - Exec(exec).deferLiquidityCheckExtended(accounts, abi.encode(underlying, accounts, scenario)); + Exec(exec).deferLiquidityCheckMulti(accounts, abi.encode(underlying, accounts, scenario)); } else if (scenario == 3) { Exec(exec).deferLiquidityCheck(accounts[0], abi.encode(underlying, accounts, scenario)); } else if (scenario == 4) { - Exec(exec).deferLiquidityCheckExtended(accounts, abi.encode(underlying, accounts, scenario)); + Exec(exec).deferLiquidityCheckMulti(accounts, abi.encode(underlying, accounts, scenario)); } else if (scenario == 5) { Exec(exec).deferLiquidityCheck(accounts[0], abi.encode(underlying, accounts, scenario)); } else if (scenario == 6) { + Exec(exec).deferLiquidityCheck(accounts[0], abi.encode(underlying, accounts, scenario)); + } else if (scenario == 7) { + Exec(exec).deferLiquidityCheckMulti(accounts, abi.encode(underlying, accounts, scenario)); + } else if (scenario == 8) { Exec.EulerBatchItem[] memory items = new Exec.EulerBatchItem[](1); items[0] = Exec.EulerBatchItem( false, @@ -80,19 +97,19 @@ contract DeferredLiquidityCheckTest is IDeferredLiquidityCheck { ) ); Exec(exec).batchDispatch(items, accounts); - } else if (scenario == 7) { + } else if (scenario == 9) { Exec.EulerBatchItem[] memory items = new Exec.EulerBatchItem[](1); items[0] = Exec.EulerBatchItem( false, exec, abi.encodeWithSelector( - Exec.deferLiquidityCheckExtended.selector, + Exec.deferLiquidityCheckMulti.selector, accounts, abi.encode(underlying, accounts, 1) ) ); Exec(exec).batchDispatch(items, accounts); - } else if (scenario == 8) { + } else if (scenario == 10) { Exec.EulerBatchItem[] memory items = new Exec.EulerBatchItem[](1); items[0] = Exec.EulerBatchItem( false, diff --git a/test/deferLiquidityCheck.js b/test/deferLiquidityCheck.js index 9478df4b..d09ec604 100644 --- a/test/deferLiquidityCheck.js +++ b/test/deferLiquidityCheck.js @@ -88,30 +88,61 @@ et.testSet({ ], }) +.test({ + desc: "batch dispatch from defer liquidity check", + actions: ctx => [ + // should revert due to reentrancy enforced from defer liquidity check in scenario 6 + { call: 'deferredLiquidityCheckTest.test', + args: [ctx.contracts.tokens.TST.address, [ ctx.contracts.deferredLiquidityCheckTest.address ], 6], + expectError: 'e/batch/reentrancy' + }, + + // should pass as defer liquidity check defers liquidity for different account than batch dispatch called from defer liquidity check + { call: 'deferredLiquidityCheckTest.test', + args: [ctx.contracts.tokens.TST.address, [ et.getSubAccount(ctx.contracts.deferredLiquidityCheckTest.address, 1) ], 6], + onLogs: logs => { + et.expect(logs.findIndex(log => log.name === "onDeferredLiquidityCheckEvent")).to.gt(-1); + }}, + + // should revert due to reentrancy enforced from defer liquidity check in scenario 7 + { call: 'deferredLiquidityCheckTest.test', + args: [ctx.contracts.tokens.TST.address, [ et.getSubAccount(ctx.contracts.deferredLiquidityCheckTest.address, 0), et.getSubAccount(ctx.contracts.deferredLiquidityCheckTest.address, 1) ], 7], + expectError: 'e/batch/reentrancy' + }, + + // should pass as defer liquidity check defers liquidity for different account than batch dispatch called from defer liquidity check + { call: 'deferredLiquidityCheckTest.test', + args: [ctx.contracts.tokens.TST.address, [ et.getSubAccount(ctx.contracts.deferredLiquidityCheckTest.address, 1), et.getSubAccount(ctx.contracts.deferredLiquidityCheckTest.address, 2) ], 7], + onLogs: logs => { + et.expect(logs.findIndex(log => log.name === "onDeferredLiquidityCheckEvent")).to.gt(-1); + }}, + ], +}) + .test({ desc: "defer liquidity check from batch dispatch", actions: ctx => [ - // should revert due to reentrancy enforced from batch dispatch in scenario 6 + // should revert due to reentrancy enforced from batch dispatch in scenario 8 { call: 'deferredLiquidityCheckTest.test', - args: [ctx.contracts.tokens.TST.address, [ ctx.contracts.deferredLiquidityCheckTest.address ], 6], + args: [ctx.contracts.tokens.TST.address, [ ctx.contracts.deferredLiquidityCheckTest.address ], 8], expectError: 'e/defer/reentrancy' }, - // should revert due to reentrancy enforced from batch dispatch in scenario 7 + // should revert due to reentrancy enforced from batch dispatch in scenario 9 { call: 'deferredLiquidityCheckTest.test', - args: [ctx.contracts.tokens.TST.address, [ et.getSubAccount(ctx.contracts.deferredLiquidityCheckTest.address, 0), et.getSubAccount(ctx.contracts.deferredLiquidityCheckTest.address, 1) ], 7], + args: [ctx.contracts.tokens.TST.address, [ et.getSubAccount(ctx.contracts.deferredLiquidityCheckTest.address, 0), et.getSubAccount(ctx.contracts.deferredLiquidityCheckTest.address, 1) ], 9], expectError: 'e/defer/reentrancy' }, - // should revert due to reentrancy enforced from batch dispatch in scenario 8 + // should revert due to reentrancy enforced from batch dispatch in scenario 10 { call: 'deferredLiquidityCheckTest.test', - args: [ctx.contracts.tokens.TST.address, [ et.getSubAccount(ctx.contracts.deferredLiquidityCheckTest.address, 0), et.getSubAccount(ctx.contracts.deferredLiquidityCheckTest.address, 1) ], 8], + args: [ctx.contracts.tokens.TST.address, [ et.getSubAccount(ctx.contracts.deferredLiquidityCheckTest.address, 0), et.getSubAccount(ctx.contracts.deferredLiquidityCheckTest.address, 1) ], 10], expectError: 'e/defer/reentrancy' }, // should pass as batch dispatch defers liquidity for different account than defer liquidity check called from batch dispatch { call: 'deferredLiquidityCheckTest.test', - args: [ctx.contracts.tokens.TST.address, [ ctx.contracts.deferredLiquidityCheckTest.address ], 8], + args: [ctx.contracts.tokens.TST.address, [ ctx.contracts.deferredLiquidityCheckTest.address ], 10], onLogs: logs => { et.expect(logs.findIndex(log => log.name === "onDeferredLiquidityCheckEvent")).to.gt(-1); }},