diff --git a/contracts/modules/Exec.sol b/contracts/modules/Exec.sol index dbc3a40e..679ab37d 100644 --- a/contracts/modules/Exec.sol +++ b/contracts/modules/Exec.sol @@ -117,6 +117,29 @@ contract Exec is BaseLogic { revert("e/batch/simulation-did-not-revert"); } + /// @notice Call batch dispatch and catch the revert and parse it to EulerBatchItemResponse[] + /// @param items List of operations to execute + /// @param deferLiquidityChecks List of user accounts to defer liquidity checks for + /// @dev During simulation all batch items are executed, regardless of the `allowError` flag + function batchDispatchSimulateDecoded(EulerBatchItem[] calldata items, address[] calldata deferLiquidityChecks) external reentrantOK returns (EulerBatchItemResponse[] memory simulation) { + address msgSender = unpackTrailingParamMsgSender(); + bytes memory data = abi.encodeWithSelector(Exec.batchDispatchSimulate.selector, items, deferLiquidityChecks); + bytes memory inputWrapped = abi.encodePacked(data, uint160(msgSender), uint160(msg.sender)); // msg.sender is the proxy address + (, bytes memory reason) = moduleLookup[MODULEID__EXEC].delegatecall(inputWrapped); + + bytes4 errorSelector = bytes4(reason); + if(errorSelector != BatchDispatchSimulation.selector){ + revertBytes(reason); + } + assembly { + reason := add(reason, 0x4) + } + simulation = abi.decode( + reason, + (EulerBatchItemResponse[]) + ); + } + // Average liquidity tracking diff --git a/test/batch.js b/test/batch.js index c371f645..16cca55e 100644 --- a/test/batch.js +++ b/test/batch.js @@ -232,6 +232,26 @@ et.testSet({ ] }) +.test({ + desc: "new batch simulate", + actions: ctx => [ + { send: 'eTokens.eTST.deposit', args: [0, et.eth(1)], }, + { send: 'tokens.TST.configure', args: ['transfer/revert', []] }, + { action: 'sendBatch', simulate: true, deferLiquidityChecks: [ctx.wallet.address], batch: [ + { send: 'eTokens.eTST.withdraw', args: [0, et.eth(1)], }, + ], onResult: r => { + et.expect(r[0].success).to.equal(false); + // safeTransferFrom adds additional error bytes that we need to remove + // first 4 bytes are the error selector + // second 32 bytes are the pointer for the start of error msg + // third 32 bytes are the length of the error msg + // then we have 4 bytes of again the Error(string) selector = 08c379a0 + // size = 4 + 32 + 32 + 4 = 72 which equals 144 length slice + 2 for 0x = 146 + const msg = utils.defaultAbiCoder.decode(["string"], "0x" + r[0].result.slice(146))[0]; + et.expect(msg).to.equal("revert behaviour") + }}, + ] +}) .run(); diff --git a/test/lib/eTestLib.js b/test/lib/eTestLib.js index 4c90ae81..7d6a8f87 100644 --- a/test/lib/eTestLib.js +++ b/test/lib/eTestLib.js @@ -1455,12 +1455,7 @@ class TestSet { let result; if (action.simulate) { - try { - await ctx.contracts.exec.connect(from).callStatic.batchDispatchSimulate(items, action.deferLiquidityChecks || []); - } catch (e) { - if (e.errorName !== 'BatchDispatchSimulation') throw e; - result = e.errorArgs.simulation; - } + result = await ctx.contracts.exec.connect(from).callStatic.batchDispatchSimulateDecoded(items, action.deferLiquidityChecks || []); } else { let tx = await ctx.contracts.exec.connect(from).batchDispatch(items, action.deferLiquidityChecks || []); result = await tx.wait();