From 5d3dc0224e60e3690cad38b07475b32a620b28d0 Mon Sep 17 00:00:00 2001 From: lgalende Date: Thu, 9 Nov 2023 18:18:29 -0300 Subject: [PATCH 1/4] tasks: implement erc4626 liquidity tasks --- .../interfaces/erc4626/IERC4626Connector.sol | 5 + .../liquidity/erc4626/IBaseERC4626Task.sol | 53 ++++ .../liquidity/erc4626/IERC4626Exiter.sol | 32 +++ .../liquidity/erc4626/IERC4626Joiner.sol | 32 +++ .../liquidity/erc4626/BaseERC4626Task.sol | 90 +++++++ .../liquidity/erc4626/ERC4626Exiter.sol | 94 +++++++ .../liquidity/erc4626/ERC4626Joiner.sol | 94 +++++++ .../test/liquidity/ERC4626ConnectorMock.sol | 43 ++++ .../erc4626/BaseERC4626Task.behavior.ts | 57 +++++ .../liquidity/erc4626/ERC4626Exiter.test.ts | 235 ++++++++++++++++++ .../liquidity/erc4626/ERC4626Joiner.test.ts | 231 +++++++++++++++++ 11 files changed, 966 insertions(+) create mode 100644 packages/tasks/contracts/interfaces/liquidity/erc4626/IBaseERC4626Task.sol create mode 100644 packages/tasks/contracts/interfaces/liquidity/erc4626/IERC4626Exiter.sol create mode 100644 packages/tasks/contracts/interfaces/liquidity/erc4626/IERC4626Joiner.sol create mode 100644 packages/tasks/contracts/liquidity/erc4626/BaseERC4626Task.sol create mode 100644 packages/tasks/contracts/liquidity/erc4626/ERC4626Exiter.sol create mode 100644 packages/tasks/contracts/liquidity/erc4626/ERC4626Joiner.sol create mode 100644 packages/tasks/contracts/test/liquidity/ERC4626ConnectorMock.sol create mode 100644 packages/tasks/test/liquidity/erc4626/BaseERC4626Task.behavior.ts create mode 100644 packages/tasks/test/liquidity/erc4626/ERC4626Exiter.test.ts create mode 100644 packages/tasks/test/liquidity/erc4626/ERC4626Joiner.test.ts diff --git a/packages/connectors/contracts/interfaces/erc4626/IERC4626Connector.sol b/packages/connectors/contracts/interfaces/erc4626/IERC4626Connector.sol index a35a91c4..d71f674b 100644 --- a/packages/connectors/contracts/interfaces/erc4626/IERC4626Connector.sol +++ b/packages/connectors/contracts/interfaces/erc4626/IERC4626Connector.sol @@ -43,6 +43,11 @@ interface IERC4626Connector { */ function getToken(address erc4626) external view returns (address); + /** + * @dev Tells the underlying token of an ERC4626 + */ + function getToken(address erc4626) external view returns (address); + /** * @dev Deposits assets to the underlying ERC4626 * @param erc4626 Address of the ERC4626 to join diff --git a/packages/tasks/contracts/interfaces/liquidity/erc4626/IBaseERC4626Task.sol b/packages/tasks/contracts/interfaces/liquidity/erc4626/IBaseERC4626Task.sol new file mode 100644 index 00000000..bb1d0aa0 --- /dev/null +++ b/packages/tasks/contracts/interfaces/liquidity/erc4626/IBaseERC4626Task.sol @@ -0,0 +1,53 @@ +// SPDX-License-Identifier: GPL-3.0-or-later +// This program is free software: you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. + +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. + +// You should have received a copy of the GNU General Public License +// along with this program. If not, see . + +pragma solidity >=0.8.0; + +import '../../ITask.sol'; + +/** + * @dev Base ERC4626 task interface + */ +interface IBaseERC4626Task is ITask { + /** + * @dev The token is zero + */ + error TaskTokenZero(); + + /** + * @dev The amount is zero + */ + error TaskAmountZero(); + + /** + * @dev The connector is zero + */ + error TaskConnectorZero(); + + /** + * @dev Emitted every time the connector is set + */ + event ConnectorSet(address indexed connector); + + /** + * @dev Tells the connector tied to the task + */ + function connector() external view returns (address); + + /** + * @dev Sets a new connector + * @param newConnector Address of the connector to be set + */ + function setConnector(address newConnector) external; +} diff --git a/packages/tasks/contracts/interfaces/liquidity/erc4626/IERC4626Exiter.sol b/packages/tasks/contracts/interfaces/liquidity/erc4626/IERC4626Exiter.sol new file mode 100644 index 00000000..22adf19e --- /dev/null +++ b/packages/tasks/contracts/interfaces/liquidity/erc4626/IERC4626Exiter.sol @@ -0,0 +1,32 @@ +// SPDX-License-Identifier: GPL-3.0-or-later +// This program is free software: you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. + +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. + +// You should have received a copy of the GNU General Public License +// along with this program. If not, see . + +pragma solidity >=0.8.0; + +import './IBaseERC4626Task.sol'; + +/** + * @dev ERC4626 exiter task interface + */ +interface IERC4626Exiter is IBaseERC4626Task { + /** + * The token is not the ERC4626 token + */ + error TaskTokenNotERC4626(address token, address erc4626); + + /** + * @dev Executes the ERC4626 exiter task + */ + function call(address token, uint256 amount) external; +} diff --git a/packages/tasks/contracts/interfaces/liquidity/erc4626/IERC4626Joiner.sol b/packages/tasks/contracts/interfaces/liquidity/erc4626/IERC4626Joiner.sol new file mode 100644 index 00000000..7568ce27 --- /dev/null +++ b/packages/tasks/contracts/interfaces/liquidity/erc4626/IERC4626Joiner.sol @@ -0,0 +1,32 @@ +// SPDX-License-Identifier: GPL-3.0-or-later +// This program is free software: you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. + +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. + +// You should have received a copy of the GNU General Public License +// along with this program. If not, see . + +pragma solidity >=0.8.0; + +import './IBaseERC4626Task.sol'; + +/** + * @dev ERC4626 joiner task interface + */ +interface IERC4626Joiner is IBaseERC4626Task { + /** + * @dev The token is not the ERC4626 underlying token + */ + error TaskTokenNotUnderlying(address token, address underlying); + + /** + * @dev Executes the ERC4626 joiner task + */ + function call(address toke, uint256 amount) external; +} diff --git a/packages/tasks/contracts/liquidity/erc4626/BaseERC4626Task.sol b/packages/tasks/contracts/liquidity/erc4626/BaseERC4626Task.sol new file mode 100644 index 00000000..684997d2 --- /dev/null +++ b/packages/tasks/contracts/liquidity/erc4626/BaseERC4626Task.sol @@ -0,0 +1,90 @@ +// SPDX-License-Identifier: GPL-3.0-or-later +// This program is free software: you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. + +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. + +// You should have received a copy of the GNU General Public License +// along with this program. If not, see . + +pragma solidity ^0.8.0; + +import '../../Task.sol'; +import '../../interfaces/liquidity/erc4626/IBaseERC4626Task.sol'; + +/** + * @title Base ERC4626 task + * @dev Task that offers the basic components for more detailed ERC4626 related tasks + */ +abstract contract BaseERC4626Task is IBaseERC4626Task, Task { + // Task connector address + address public override connector; + + /** + * @dev Base ERC4626 config. Only used in the initializer. + */ + struct BaseERC4626Config { + address connector; + TaskConfig taskConfig; + } + + /** + * @dev Initializes the base ERC4626 task. It does call upper contracts initializers. + * @param config Base ERC4626 config + */ + function __BaseERC4626Task_init(BaseERC4626Config memory config) internal onlyInitializing { + __Task_init(config.taskConfig); + __BaseERC4626Task_init_unchained(config); + } + + /** + * @dev Initializes the base ERC4626 task. It does not call upper contracts initializers. + * @param config Base ERC4626 config + */ + function __BaseERC4626Task_init_unchained(BaseERC4626Config memory config) internal onlyInitializing { + _setConnector(config.connector); + } + + /** + * @dev Sets the task connector + * @param newConnector Address of the new connector to be set + */ + function setConnector(address newConnector) external override authP(authParams(newConnector)) { + _setConnector(newConnector); + } + + /** + * @dev Before base ERC4626 task hook + */ + function _beforeBaseERC4626Task(address token, uint256 amount) internal virtual { + _beforeTask(token, amount); + if (token == address(0)) revert TaskTokenZero(); + if (amount == 0) revert TaskAmountZero(); + } + + /** + * @dev After base ERC4626 task hook + */ + function _afterBaseERC4626Task(address tokenIn, uint256 amountIn, address tokenOut, uint256 amountOut) + internal + virtual + { + _increaseBalanceConnector(tokenOut, amountOut); + _afterTask(tokenIn, amountIn); + } + + /** + * @dev Sets the task connector + * @param newConnector New connector to be set + */ + function _setConnector(address newConnector) internal { + if (newConnector == address(0)) revert TaskConnectorZero(); + connector = newConnector; + emit ConnectorSet(newConnector); + } +} diff --git a/packages/tasks/contracts/liquidity/erc4626/ERC4626Exiter.sol b/packages/tasks/contracts/liquidity/erc4626/ERC4626Exiter.sol new file mode 100644 index 00000000..be8249fe --- /dev/null +++ b/packages/tasks/contracts/liquidity/erc4626/ERC4626Exiter.sol @@ -0,0 +1,94 @@ +// SPDX-License-Identifier: GPL-3.0-or-later +// This program is free software: you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. + +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. + +// You should have received a copy of the GNU General Public License +// along with this program. If not, see . + +pragma solidity ^0.8.0; + +import '@mimic-fi/v3-connectors/contracts/interfaces/liquidity/erc4626/IERC4626Connector.sol'; + +import './BaseERC4626Task.sol'; +import '../../interfaces/liquidity/erc4626/IERC4626Exiter.sol'; + +/** + * @title ERC4626 exiter + * @dev Task that extends the base ERC4626 task to exit an ERC4626 vault + */ +contract ERC4626Exiter is IERC4626Exiter, BaseERC4626Task { + // Execution type for relayers + bytes32 public constant override EXECUTION_TYPE = keccak256('ERC4626_EXITER'); + + /** + * @dev ERC4626 exit config. Only used in the initializer. + */ + struct ERC4626ExitConfig { + BaseERC4626Config baseERC4626Config; + } + + /** + * @dev Initializes a ERC4626 exiter + * @param config ERC4626 exit config + */ + function initialize(ERC4626ExitConfig memory config) external virtual initializer { + __ERC4626Exiter_init(config); + } + + /** + * @dev Initializes the ERC4626 exiter. It does call upper contracts initializers. + * @param config ERC4626 exit config + */ + function __ERC4626Exiter_init(ERC4626ExitConfig memory config) internal onlyInitializing { + __BaseERC4626Task_init(config.baseERC4626Config); + __ERC4626Exiter_init_unchained(config); + } + + /** + * @dev Initializes the ERC4626 exiter. It does not call upper contracts initializers. + * @param config ERC4626 exit config + */ + function __ERC4626Exiter_init_unchained(ERC4626ExitConfig memory config) internal onlyInitializing { + // solhint-disable-previous-line no-empty-blocks + } + + /** + * @dev Executes the ERC4626 exiter task + * @param token Address of the token to be exited with + * @param amount Amount of shares to be exited with + */ + function call(address token, uint256 amount) external override authP(authParams(token, amount)) { + if (amount == 0) amount = getTaskAmount(token); + _beforeERC4626Exiter(token, amount); + bytes memory connectorData = abi.encodeWithSelector(IERC4626Connector.exit.selector, amount); + bytes memory result = ISmartVault(smartVault).execute(connector, connectorData); + (address tokenOut, uint256 amountOut) = abi.decode(result, (address, uint256)); + _afterERC4626Exiter(token, amount, tokenOut, amountOut); + } + + /** + * @dev Before ERC4626 exiter hook + */ + function _beforeERC4626Exiter(address token, uint256 amount) internal virtual { + _beforeBaseERC4626Task(token, amount); + address erc4626 = IERC4626Connector(connector).erc4626(); + if (token != erc4626) revert TaskTokenNotERC4626(token, erc4626); + } + + /** + * @dev After ERC4626 exiter hook + */ + function _afterERC4626Exiter(address tokenIn, uint256 amountIn, address tokenOut, uint256 amountOut) + internal + virtual + { + _afterBaseERC4626Task(tokenIn, amountIn, tokenOut, amountOut); + } +} diff --git a/packages/tasks/contracts/liquidity/erc4626/ERC4626Joiner.sol b/packages/tasks/contracts/liquidity/erc4626/ERC4626Joiner.sol new file mode 100644 index 00000000..f8bad3dc --- /dev/null +++ b/packages/tasks/contracts/liquidity/erc4626/ERC4626Joiner.sol @@ -0,0 +1,94 @@ +// SPDX-License-Identifier: GPL-3.0-or-later +// This program is free software: you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. + +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. + +// You should have received a copy of the GNU General Public License +// along with this program. If not, see . + +pragma solidity ^0.8.0; + +import '@mimic-fi/v3-connectors/contracts/interfaces/liquidity/erc4626/IERC4626Connector.sol'; + +import './BaseERC4626Task.sol'; +import '../../interfaces/liquidity/erc4626/IERC4626Joiner.sol'; + +/** + * @title ERC4626 joiner + * @dev Task that extends the base ERC4626 task to join an ERC4626 vault + */ +contract ERC4626Joiner is IERC4626Joiner, BaseERC4626Task { + // Execution type for relayers + bytes32 public constant override EXECUTION_TYPE = keccak256('ERC4626_JOINER'); + + /** + * @dev ERC4626 join config. Only used in the initializer. + */ + struct ERC4626JoinConfig { + BaseERC4626Config baseERC4626Config; + } + + /** + * @dev Initializes a ERC4626 joiner + * @param config ERC4626 join config + */ + function initialize(ERC4626JoinConfig memory config) external virtual initializer { + __ERC4626Joiner_init(config); + } + + /** + * @dev Initializes the ERC4626 joiner. It does call upper contracts initializers. + * @param config ERC4626 join config + */ + function __ERC4626Joiner_init(ERC4626JoinConfig memory config) internal onlyInitializing { + __BaseERC4626Task_init(config.baseERC4626Config); + __ERC4626Joiner_init_unchained(config); + } + + /** + * @dev Initializes the ERC4626 joiner. It does not call upper contracts initializers. + * @param config ERC4626 join config + */ + function __ERC4626Joiner_init_unchained(ERC4626JoinConfig memory config) internal onlyInitializing { + // solhint-disable-previous-line no-empty-blocks + } + + /** + * @dev Executes the ERC4626 joiner task + * @param token Address of the token to be joined with + * @param amount Amount of assets to be joined with + */ + function call(address token, uint256 amount) external override authP(authParams(token, amount)) { + if (amount == 0) amount = getTaskAmount(token); + _beforeERC4626Joiner(token, amount); + bytes memory connectorData = abi.encodeWithSelector(IERC4626Connector.join.selector, amount); + bytes memory result = ISmartVault(smartVault).execute(connector, connectorData); + (address tokenOut, uint256 amountOut) = abi.decode(result, (address, uint256)); + _afterERC4626Joiner(token, amount, tokenOut, amountOut); + } + + /** + * @dev Before ERC4626 joiner hook + */ + function _beforeERC4626Joiner(address token, uint256 amount) internal virtual { + _beforeBaseERC4626Task(token, amount); + address underlying = IERC4626Connector(connector).getToken(); + if (token != underlying) revert TaskTokenNotUnderlying(token, underlying); + } + + /** + * @dev After ERC4626 joiner hook + */ + function _afterERC4626Joiner(address tokenIn, uint256 amountIn, address tokenOut, uint256 amountOut) + internal + virtual + { + _afterBaseERC4626Task(tokenIn, amountIn, tokenOut, amountOut); + } +} diff --git a/packages/tasks/contracts/test/liquidity/ERC4626ConnectorMock.sol b/packages/tasks/contracts/test/liquidity/ERC4626ConnectorMock.sol new file mode 100644 index 00000000..e40fbf9e --- /dev/null +++ b/packages/tasks/contracts/test/liquidity/ERC4626ConnectorMock.sol @@ -0,0 +1,43 @@ +// SPDX-License-Identifier: GPL-3.0-or-later +// This program is free software: you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. + +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. + +// You should have received a copy of the GNU General Public License +// along with this program. If not, see . + +pragma solidity ^0.8.0; + +contract ERC4626ConnectorMock { + address public erc4626; + + address public getToken; + + event LogJoin(uint256 amount); + + event LogExit(uint256 amount); + + function setERC4626(address newERC4626) external { + erc4626 = newERC4626; + } + + function setToken(address newToken) external { + getToken = newToken; + } + + function join(uint256 assets) external returns (address, uint256) { + emit LogJoin(assets); + return (erc4626, assets); + } + + function exit(uint256 shares) external returns (address, uint256) { + emit LogExit(shares); + return (getToken, shares); + } +} diff --git a/packages/tasks/test/liquidity/erc4626/BaseERC4626Task.behavior.ts b/packages/tasks/test/liquidity/erc4626/BaseERC4626Task.behavior.ts new file mode 100644 index 00000000..c0c77e74 --- /dev/null +++ b/packages/tasks/test/liquidity/erc4626/BaseERC4626Task.behavior.ts @@ -0,0 +1,57 @@ +import { assertEvent, deployTokenMock, ZERO_ADDRESS } from '@mimic-fi/v3-helpers' +import { expect } from 'chai' +import { Contract } from 'ethers' +import { ethers } from 'hardhat' + +export function itBehavesLikeBaseERC4626Task(executionType: string): void { + describe('execution type', () => { + it('defines it correctly', async function () { + const expectedType = ethers.utils.solidityKeccak256(['string'], [executionType]) + expect(await this.task.EXECUTION_TYPE()).to.be.equal(expectedType) + }) + }) + + describe('setConnector', () => { + context('when the sender is authorized', () => { + beforeEach('authorize sender', async function () { + const setConnectorRole = this.task.interface.getSighash('setConnector') + await this.authorizer.connect(this.owner).authorize(this.owner.address, this.task.address, setConnectorRole, []) + this.task = this.task.connect(this.owner) + }) + + context('when the new connector is not zero', () => { + let connector: Contract + + beforeEach('deploy connector', async function () { + connector = await deployTokenMock('TKN') + }) + + it('sets the token out', async function () { + await this.task.setConnector(connector.address) + + expect(await this.task.connector()).to.be.equal(connector.address) + }) + + it('emits an event', async function () { + const tx = await this.task.setConnector(connector.address) + + await assertEvent(tx, 'ConnectorSet', { connector }) + }) + }) + + context('when the new connector is zero', () => { + const connector = ZERO_ADDRESS + + it('reverts', async function () { + await expect(this.task.setConnector(connector)).to.be.revertedWith('TaskConnectorZero') + }) + }) + }) + + context('when the sender is not authorized', () => { + it('reverts', async function () { + await expect(this.task.setConnector(ZERO_ADDRESS)).to.be.revertedWith('AuthSenderNotAllowed') + }) + }) + }) +} diff --git a/packages/tasks/test/liquidity/erc4626/ERC4626Exiter.test.ts b/packages/tasks/test/liquidity/erc4626/ERC4626Exiter.test.ts new file mode 100644 index 00000000..c81a3207 --- /dev/null +++ b/packages/tasks/test/liquidity/erc4626/ERC4626Exiter.test.ts @@ -0,0 +1,235 @@ +import { OP } from '@mimic-fi/v3-authorizer' +import { + assertEvent, + assertIndirectEvent, + assertNoEvent, + BigNumberish, + deploy, + deployProxy, + deployTokenMock, + fp, + getSigners, + ONES_ADDRESS, + ZERO_ADDRESS, +} from '@mimic-fi/v3-helpers' +import { SignerWithAddress } from '@nomiclabs/hardhat-ethers/dist/src/signer-with-address' +import { expect } from 'chai' +import { Contract } from 'ethers' + +import { buildEmptyTaskConfig, deployEnvironment } from '../../../' +import { itBehavesLikeBaseERC4626Task } from './BaseERC4626Task.behavior' + +describe('ERC4626Exiter', () => { + let task: Contract + let smartVault: Contract, authorizer: Contract, connector: Contract, owner: SignerWithAddress + + before('setup', async () => { + // eslint-disable-next-line prettier/prettier + ([, owner] = await getSigners()) + ;({ authorizer, smartVault } = await deployEnvironment(owner)) + }) + + before('deploy connector', async () => { + connector = await deploy('ERC4626ConnectorMock') + const overrideConnectorCheckRole = smartVault.interface.getSighash('overrideConnectorCheck') + await authorizer.connect(owner).authorize(owner.address, smartVault.address, overrideConnectorCheckRole, []) + await smartVault.connect(owner).overrideConnectorCheck(connector.address, true) + }) + + beforeEach('deploy task', async () => { + task = await deployProxy( + 'ERC4626Exiter', + [], + [ + { + baseERC4626Config: { + connector: connector.address, + taskConfig: buildEmptyTaskConfig(owner, smartVault), + }, + }, + ] + ) + }) + + describe('ERC4626', () => { + beforeEach('set params', async function () { + this.owner = owner + this.task = task + this.authorizer = authorizer + }) + + itBehavesLikeBaseERC4626Task('ERC4626_EXITER') + }) + + describe('call', () => { + beforeEach('authorize task', async () => { + const executeRole = smartVault.interface.getSighash('execute') + const params = [{ op: OP.EQ, value: connector.address }] + await authorizer.connect(owner).authorize(task.address, smartVault.address, executeRole, params) + }) + + context('when the sender is authorized', () => { + beforeEach('set sender', async () => { + const callRole = task.interface.getSighash('call') + await authorizer.connect(owner).authorize(owner.address, task.address, callRole, []) + task = task.connect(owner) + }) + + context('when the token is not zero', () => { + let token: Contract, tokenOut: Contract + + beforeEach('deploy tokens', async () => { + token = await deployTokenMock('ERC4626') + tokenOut = await deployTokenMock('WETH') + }) + + beforeEach('set connector tokens', async () => { + await connector.setToken(tokenOut.address) + await connector.setERC4626(token.address) + }) + + context('when the amount is not zero', () => { + const amount = fp(10) + + beforeEach('fund smart vault', async () => { + await token.mint(smartVault.address, amount) + }) + + context('when the token is the ERC4626 token', () => { + context('when the threshold has passed', () => { + const threshold = amount + + beforeEach('set token threshold', async () => { + const setDefaultTokenThresholdRole = task.interface.getSighash('setDefaultTokenThreshold') + await authorizer.connect(owner).authorize(owner.address, task.address, setDefaultTokenThresholdRole, []) + await task.connect(owner).setDefaultTokenThreshold(token.address, threshold, 0) + }) + + const itExecutesTheTaskProperly = (requestedAmount: BigNumberish) => { + it('executes the expected connector', async () => { + const tx = await task.call(token.address, requestedAmount) + + const connectorData = connector.interface.encodeFunctionData('exit', [amount]) + + await assertIndirectEvent(tx, smartVault.interface, 'Executed', { connector, data: connectorData }) + + await assertIndirectEvent(tx, connector.interface, 'LogExit', { + amount, + }) + }) + + it('emits an Executed event', async () => { + const tx = await task.call(token.address, requestedAmount) + await assertEvent(tx, 'Executed') + }) + } + + context('without balance connectors', () => { + const requestedAmount = amount + + itExecutesTheTaskProperly(requestedAmount) + + it('does not update any balance connectors', async () => { + const tx = await task.call(token.address, requestedAmount) + + await assertNoEvent(tx, 'BalanceConnectorUpdated') + }) + }) + + context('with balance connectors', () => { + const requestedAmount = 0 + const prevConnectorId = '0x0000000000000000000000000000000000000000000000000000000000000001' + const nextConnectorId = '0x0000000000000000000000000000000000000000000000000000000000000002' + + beforeEach('set balance connectors', async () => { + const setBalanceConnectorsRole = task.interface.getSighash('setBalanceConnectors') + await authorizer.connect(owner).authorize(owner.address, task.address, setBalanceConnectorsRole, []) + await task.connect(owner).setBalanceConnectors(prevConnectorId, nextConnectorId) + }) + + beforeEach('authorize task to update balance connectors', async () => { + const updateBalanceConnectorRole = smartVault.interface.getSighash('updateBalanceConnector') + await authorizer + .connect(owner) + .authorize(task.address, smartVault.address, updateBalanceConnectorRole, []) + }) + + beforeEach('assign amount in to previous balance connector', async () => { + const updateBalanceConnectorRole = smartVault.interface.getSighash('updateBalanceConnector') + await authorizer + .connect(owner) + .authorize(owner.address, smartVault.address, updateBalanceConnectorRole, []) + await smartVault.connect(owner).updateBalanceConnector(prevConnectorId, token.address, amount, true) + }) + + itExecutesTheTaskProperly(requestedAmount) + + it('updates the balance connectors properly', async () => { + const tx = await task.call(token.address, requestedAmount) + + await assertIndirectEvent(tx, smartVault.interface, 'BalanceConnectorUpdated', { + id: prevConnectorId, + token, + amount, + added: false, + }) + + await assertIndirectEvent(tx, smartVault.interface, 'BalanceConnectorUpdated', { + id: nextConnectorId, + token: tokenOut.address, + amount, + added: true, + }) + }) + }) + }) + + context('when the threshold has not passed', () => { + const threshold = amount.add(1) + + beforeEach('set token threshold', async () => { + const setDefaultTokenThresholdRole = task.interface.getSighash('setDefaultTokenThreshold') + await authorizer.connect(owner).authorize(owner.address, task.address, setDefaultTokenThresholdRole, []) + await task.connect(owner).setDefaultTokenThreshold(token.address, threshold, 0) + }) + + it('reverts', async () => { + await expect(task.call(token.address, amount)).to.be.revertedWith('TaskTokenThresholdNotMet') + }) + }) + }) + + context('when the token is not the ERC4626 token', () => { + const token = ONES_ADDRESS + + it('reverts', async () => { + await expect(task.call(token, amount)).to.be.revertedWith('TaskTokenNotERC4626') + }) + }) + }) + + context('when the amount is zero', () => { + const amount = 0 + + it('reverts', async () => { + await expect(task.call(token.address, amount)).to.be.revertedWith('TaskAmountZero') + }) + }) + }) + + context('when the token is zero', () => { + const token = ZERO_ADDRESS + + it('reverts', async () => { + await expect(task.call(token, 0)).to.be.revertedWith('TaskTokenZero') + }) + }) + }) + + context('when the sender is not authorized', () => { + it('reverts', async () => { + await expect(task.call(ZERO_ADDRESS, 0)).to.be.revertedWith('AuthSenderNotAllowed') + }) + }) + }) +}) diff --git a/packages/tasks/test/liquidity/erc4626/ERC4626Joiner.test.ts b/packages/tasks/test/liquidity/erc4626/ERC4626Joiner.test.ts new file mode 100644 index 00000000..d4ed1172 --- /dev/null +++ b/packages/tasks/test/liquidity/erc4626/ERC4626Joiner.test.ts @@ -0,0 +1,231 @@ +import { OP } from '@mimic-fi/v3-authorizer' +import { + assertEvent, + assertIndirectEvent, + assertNoEvent, + BigNumberish, + deploy, + deployProxy, + deployTokenMock, + fp, + getSigners, + ONES_ADDRESS, + ZERO_ADDRESS, +} from '@mimic-fi/v3-helpers' +import { SignerWithAddress } from '@nomiclabs/hardhat-ethers/dist/src/signer-with-address' +import { expect } from 'chai' +import { Contract } from 'ethers' + +import { buildEmptyTaskConfig, deployEnvironment } from '../../../' +import { itBehavesLikeBaseERC4626Task } from './BaseERC4626Task.behavior' + +describe('ERC4626Joiner', () => { + let task: Contract + let smartVault: Contract, authorizer: Contract, connector: Contract, owner: SignerWithAddress + + before('setup', async () => { + // eslint-disable-next-line prettier/prettier + ([, owner] = await getSigners()) + ;({ authorizer, smartVault } = await deployEnvironment(owner)) + }) + + before('deploy connector', async () => { + connector = await deploy('ERC4626ConnectorMock') + const overrideConnectorCheckRole = smartVault.interface.getSighash('overrideConnectorCheck') + await authorizer.connect(owner).authorize(owner.address, smartVault.address, overrideConnectorCheckRole, []) + await smartVault.connect(owner).overrideConnectorCheck(connector.address, true) + }) + + beforeEach('deploy task', async () => { + task = await deployProxy( + 'ERC4626Joiner', + [], + [ + { + baseERC4626Config: { + connector: connector.address, + taskConfig: buildEmptyTaskConfig(owner, smartVault), + }, + }, + ] + ) + }) + + describe('ERC4626', () => { + beforeEach('set params', async function () { + this.owner = owner + this.task = task + this.authorizer = authorizer + }) + + itBehavesLikeBaseERC4626Task('ERC4626_JOINER') + }) + + describe('call', () => { + beforeEach('authorize task', async () => { + const executeRole = smartVault.interface.getSighash('execute') + const params = [{ op: OP.EQ, value: connector.address }] + await authorizer.connect(owner).authorize(task.address, smartVault.address, executeRole, params) + }) + + context('when the sender is authorized', () => { + beforeEach('set sender', async () => { + const callRole = task.interface.getSighash('call') + await authorizer.connect(owner).authorize(owner.address, task.address, callRole, []) + task = task.connect(owner) + }) + + context('when the token is not zero', () => { + let token: Contract, tokenOut: Contract + + beforeEach('deploy tokens', async () => { + token = await deployTokenMock('WETH') + tokenOut = await deployTokenMock('ERC4626') + }) + + beforeEach('set connector tokens', async () => { + await connector.setToken(token.address) + await connector.setERC4626(tokenOut.address) + }) + + context('when the amount is not zero', () => { + const amount = fp(10) + + beforeEach('fund smart vault', async () => { + await token.mint(smartVault.address, amount) + }) + + context('when the token is the underlying token', () => { + context('when the threshold has passed', () => { + const threshold = amount + + beforeEach('set token threshold', async () => { + const setDefaultTokenThresholdRole = task.interface.getSighash('setDefaultTokenThreshold') + await authorizer.connect(owner).authorize(owner.address, task.address, setDefaultTokenThresholdRole, []) + await task.connect(owner).setDefaultTokenThreshold(token.address, threshold, 0) + }) + + const itExecutesTheTaskProperly = (requestedAmount: BigNumberish) => { + it('executes the expected connector', async () => { + const tx = await task.call(token.address, requestedAmount) + + const connectorData = connector.interface.encodeFunctionData('join', [amount]) + await assertIndirectEvent(tx, smartVault.interface, 'Executed', { connector, data: connectorData }) + await assertIndirectEvent(tx, connector.interface, 'LogJoin', { amount }) + }) + + it('emits an Executed event', async () => { + const tx = await task.call(token.address, requestedAmount) + await assertEvent(tx, 'Executed') + }) + } + + context('without balance connectors', () => { + const requestedAmount = amount + + itExecutesTheTaskProperly(requestedAmount) + + it('does not update any balance connectors', async () => { + const tx = await task.call(token.address, requestedAmount) + + await assertNoEvent(tx, 'BalanceConnectorUpdated') + }) + }) + + context('with balance connectors', () => { + const requestedAmount = 0 + const prevConnectorId = '0x0000000000000000000000000000000000000000000000000000000000000001' + const nextConnectorId = '0x0000000000000000000000000000000000000000000000000000000000000002' + + beforeEach('set balance connectors', async () => { + const setBalanceConnectorsRole = task.interface.getSighash('setBalanceConnectors') + await authorizer.connect(owner).authorize(owner.address, task.address, setBalanceConnectorsRole, []) + await task.connect(owner).setBalanceConnectors(prevConnectorId, nextConnectorId) + }) + + beforeEach('authorize task to update balance connectors', async () => { + const updateBalanceConnectorRole = smartVault.interface.getSighash('updateBalanceConnector') + await authorizer + .connect(owner) + .authorize(task.address, smartVault.address, updateBalanceConnectorRole, []) + }) + + beforeEach('assign amount in to previous balance connector', async () => { + const updateBalanceConnectorRole = smartVault.interface.getSighash('updateBalanceConnector') + await authorizer + .connect(owner) + .authorize(owner.address, smartVault.address, updateBalanceConnectorRole, []) + await smartVault.connect(owner).updateBalanceConnector(prevConnectorId, token.address, amount, true) + }) + + itExecutesTheTaskProperly(requestedAmount) + + it('updates the balance connectors properly', async () => { + const tx = await task.call(token.address, amount) + + await assertIndirectEvent(tx, smartVault.interface, 'BalanceConnectorUpdated', { + id: prevConnectorId, + token, + amount, + added: false, + }) + + await assertIndirectEvent(tx, smartVault.interface, 'BalanceConnectorUpdated', { + id: nextConnectorId, + token: tokenOut.address, + amount, + added: true, + }) + }) + }) + }) + + context('when the threshold has not passed', () => { + const threshold = amount.add(1) + + beforeEach('set token threshold', async () => { + const setDefaultTokenThresholdRole = task.interface.getSighash('setDefaultTokenThreshold') + await authorizer.connect(owner).authorize(owner.address, task.address, setDefaultTokenThresholdRole, []) + await task.connect(owner).setDefaultTokenThreshold(token.address, threshold, 0) + }) + + it('reverts', async () => { + await expect(task.call(token.address, amount)).to.be.revertedWith('TaskTokenThresholdNotMet') + }) + }) + }) + + context('when the token is not the underlying token', () => { + const token = ONES_ADDRESS + + it('reverts', async () => { + await expect(task.call(token, amount)).to.be.revertedWith('TaskTokenNotUnderlying') + }) + }) + }) + + context('when the amount is zero', () => { + const amount = 0 + + it('reverts', async () => { + await expect(task.call(token.address, amount)).to.be.revertedWith('TaskAmountZero') + }) + }) + }) + + context('when the token is zero', () => { + const token = ZERO_ADDRESS + + it('reverts', async () => { + await expect(task.call(token, 0)).to.be.revertedWith('TaskTokenZero') + }) + }) + }) + + context('when the sender is not authorized', () => { + it('reverts', async () => { + await expect(task.call(ZERO_ADDRESS, 0)).to.be.revertedWith('AuthSenderNotAllowed') + }) + }) + }) +}) From 3c906a2ef50b36cac1b4cf24849fef0087ba1d43 Mon Sep 17 00:00:00 2001 From: lgalende Date: Wed, 22 Nov 2023 16:55:04 -0300 Subject: [PATCH 2/4] tasks: add min amount out and erc4626 parameters --- .../interfaces/erc4626/IERC4626Connector.sol | 5 - .../liquidity/erc4626/IERC4626Exiter.sol | 7 +- .../liquidity/erc4626/IERC4626Joiner.sol | 6 +- .../liquidity/erc4626/ERC4626Exiter.sol | 28 ++- .../liquidity/erc4626/ERC4626Joiner.sol | 25 ++- .../test/liquidity/ERC4626ConnectorMock.sol | 31 ++- .../liquidity/erc4626/ERC4626Exiter.test.ts | 186 +++++++++--------- .../liquidity/erc4626/ERC4626Joiner.test.ts | 59 +++--- 8 files changed, 180 insertions(+), 167 deletions(-) diff --git a/packages/connectors/contracts/interfaces/erc4626/IERC4626Connector.sol b/packages/connectors/contracts/interfaces/erc4626/IERC4626Connector.sol index d71f674b..a35a91c4 100644 --- a/packages/connectors/contracts/interfaces/erc4626/IERC4626Connector.sol +++ b/packages/connectors/contracts/interfaces/erc4626/IERC4626Connector.sol @@ -43,11 +43,6 @@ interface IERC4626Connector { */ function getToken(address erc4626) external view returns (address); - /** - * @dev Tells the underlying token of an ERC4626 - */ - function getToken(address erc4626) external view returns (address); - /** * @dev Deposits assets to the underlying ERC4626 * @param erc4626 Address of the ERC4626 to join diff --git a/packages/tasks/contracts/interfaces/liquidity/erc4626/IERC4626Exiter.sol b/packages/tasks/contracts/interfaces/liquidity/erc4626/IERC4626Exiter.sol index 22adf19e..60a380d1 100644 --- a/packages/tasks/contracts/interfaces/liquidity/erc4626/IERC4626Exiter.sol +++ b/packages/tasks/contracts/interfaces/liquidity/erc4626/IERC4626Exiter.sol @@ -20,13 +20,8 @@ import './IBaseERC4626Task.sol'; * @dev ERC4626 exiter task interface */ interface IERC4626Exiter is IBaseERC4626Task { - /** - * The token is not the ERC4626 token - */ - error TaskTokenNotERC4626(address token, address erc4626); - /** * @dev Executes the ERC4626 exiter task */ - function call(address token, uint256 amount) external; + function call(address erc4626, uint256 amount, uint256 minAmountOut) external; } diff --git a/packages/tasks/contracts/interfaces/liquidity/erc4626/IERC4626Joiner.sol b/packages/tasks/contracts/interfaces/liquidity/erc4626/IERC4626Joiner.sol index 7568ce27..e6caec6a 100644 --- a/packages/tasks/contracts/interfaces/liquidity/erc4626/IERC4626Joiner.sol +++ b/packages/tasks/contracts/interfaces/liquidity/erc4626/IERC4626Joiner.sol @@ -21,12 +21,12 @@ import './IBaseERC4626Task.sol'; */ interface IERC4626Joiner is IBaseERC4626Task { /** - * @dev The token is not the ERC4626 underlying token + * The ERC4626 reference is zero */ - error TaskTokenNotUnderlying(address token, address underlying); + error TaskERC4626Zero(); /** * @dev Executes the ERC4626 joiner task */ - function call(address toke, uint256 amount) external; + function call(address erc4626, address token, uint256 amount, uint256 minAmountOut) external; } diff --git a/packages/tasks/contracts/liquidity/erc4626/ERC4626Exiter.sol b/packages/tasks/contracts/liquidity/erc4626/ERC4626Exiter.sol index be8249fe..7a847785 100644 --- a/packages/tasks/contracts/liquidity/erc4626/ERC4626Exiter.sol +++ b/packages/tasks/contracts/liquidity/erc4626/ERC4626Exiter.sol @@ -14,7 +14,7 @@ pragma solidity ^0.8.0; -import '@mimic-fi/v3-connectors/contracts/interfaces/liquidity/erc4626/IERC4626Connector.sol'; +import '@mimic-fi/v3-connectors/contracts/interfaces/erc4626/IERC4626Connector.sol'; import './BaseERC4626Task.sol'; import '../../interfaces/liquidity/erc4626/IERC4626Exiter.sol'; @@ -60,17 +60,27 @@ contract ERC4626Exiter is IERC4626Exiter, BaseERC4626Task { } /** - * @dev Executes the ERC4626 exiter task - * @param token Address of the token to be exited with + * @dev Executes the ERC4626 exiter task. Note that the ERC4626 is also the token. + * @param erc4626 Address of the ERC4626 to be exited * @param amount Amount of shares to be exited with + * @param minAmountOut Minimum amount of assets willing to receive */ - function call(address token, uint256 amount) external override authP(authParams(token, amount)) { - if (amount == 0) amount = getTaskAmount(token); - _beforeERC4626Exiter(token, amount); - bytes memory connectorData = abi.encodeWithSelector(IERC4626Connector.exit.selector, amount); + function call(address erc4626, uint256 amount, uint256 minAmountOut) + external + override + authP(authParams(erc4626, amount)) + { + if (amount == 0) amount = getTaskAmount(erc4626); + _beforeERC4626Exiter(erc4626, amount); + bytes memory connectorData = abi.encodeWithSelector( + IERC4626Connector.exit.selector, + erc4626, + amount, + minAmountOut + ); bytes memory result = ISmartVault(smartVault).execute(connector, connectorData); (address tokenOut, uint256 amountOut) = abi.decode(result, (address, uint256)); - _afterERC4626Exiter(token, amount, tokenOut, amountOut); + _afterERC4626Exiter(erc4626, amount, tokenOut, amountOut); } /** @@ -78,8 +88,6 @@ contract ERC4626Exiter is IERC4626Exiter, BaseERC4626Task { */ function _beforeERC4626Exiter(address token, uint256 amount) internal virtual { _beforeBaseERC4626Task(token, amount); - address erc4626 = IERC4626Connector(connector).erc4626(); - if (token != erc4626) revert TaskTokenNotERC4626(token, erc4626); } /** diff --git a/packages/tasks/contracts/liquidity/erc4626/ERC4626Joiner.sol b/packages/tasks/contracts/liquidity/erc4626/ERC4626Joiner.sol index f8bad3dc..07280710 100644 --- a/packages/tasks/contracts/liquidity/erc4626/ERC4626Joiner.sol +++ b/packages/tasks/contracts/liquidity/erc4626/ERC4626Joiner.sol @@ -14,7 +14,7 @@ pragma solidity ^0.8.0; -import '@mimic-fi/v3-connectors/contracts/interfaces/liquidity/erc4626/IERC4626Connector.sol'; +import '@mimic-fi/v3-connectors/contracts/interfaces/erc4626/IERC4626Connector.sol'; import './BaseERC4626Task.sol'; import '../../interfaces/liquidity/erc4626/IERC4626Joiner.sol'; @@ -61,13 +61,25 @@ contract ERC4626Joiner is IERC4626Joiner, BaseERC4626Task { /** * @dev Executes the ERC4626 joiner task + * @param erc4626 Address of the ERC4626 to be joined * @param token Address of the token to be joined with * @param amount Amount of assets to be joined with + * @param minAmountOut Minimum amount of shares willing to receive */ - function call(address token, uint256 amount) external override authP(authParams(token, amount)) { + function call(address erc4626, address token, uint256 amount, uint256 minAmountOut) + external + override + authP(authParams(erc4626, token, amount)) + { if (amount == 0) amount = getTaskAmount(token); - _beforeERC4626Joiner(token, amount); - bytes memory connectorData = abi.encodeWithSelector(IERC4626Connector.join.selector, amount); + _beforeERC4626Joiner(erc4626, token, amount); + bytes memory connectorData = abi.encodeWithSelector( + IERC4626Connector.join.selector, + erc4626, + token, + amount, + minAmountOut + ); bytes memory result = ISmartVault(smartVault).execute(connector, connectorData); (address tokenOut, uint256 amountOut) = abi.decode(result, (address, uint256)); _afterERC4626Joiner(token, amount, tokenOut, amountOut); @@ -76,10 +88,9 @@ contract ERC4626Joiner is IERC4626Joiner, BaseERC4626Task { /** * @dev Before ERC4626 joiner hook */ - function _beforeERC4626Joiner(address token, uint256 amount) internal virtual { + function _beforeERC4626Joiner(address erc4626, address token, uint256 amount) internal virtual { _beforeBaseERC4626Task(token, amount); - address underlying = IERC4626Connector(connector).getToken(); - if (token != underlying) revert TaskTokenNotUnderlying(token, underlying); + if (erc4626 == address(0)) revert TaskERC4626Zero(); } /** diff --git a/packages/tasks/contracts/test/liquidity/ERC4626ConnectorMock.sol b/packages/tasks/contracts/test/liquidity/ERC4626ConnectorMock.sol index e40fbf9e..bc519970 100644 --- a/packages/tasks/contracts/test/liquidity/ERC4626ConnectorMock.sol +++ b/packages/tasks/contracts/test/liquidity/ERC4626ConnectorMock.sol @@ -15,29 +15,26 @@ pragma solidity ^0.8.0; contract ERC4626ConnectorMock { - address public erc4626; + address public immutable tokenOut; - address public getToken; + event LogJoin(address erc4626, address token, uint256 amount, uint256 minAmountOut); - event LogJoin(uint256 amount); + event LogExit(address erc4626, uint256 amount, uint256 minAmountOut); - event LogExit(uint256 amount); - - function setERC4626(address newERC4626) external { - erc4626 = newERC4626; - } - - function setToken(address newToken) external { - getToken = newToken; + constructor(address _tokenOut) { + tokenOut = _tokenOut; } - function join(uint256 assets) external returns (address, uint256) { - emit LogJoin(assets); - return (erc4626, assets); + function join(address erc4626, address token, uint256 assets, uint256 minSharesOut) + external + returns (address, uint256) + { + emit LogJoin(erc4626, token, assets, minSharesOut); + return (erc4626, minSharesOut); } - function exit(uint256 shares) external returns (address, uint256) { - emit LogExit(shares); - return (getToken, shares); + function exit(address erc4626, uint256 shares, uint256 minAssetsOut) external returns (address, uint256) { + emit LogExit(erc4626, shares, minAssetsOut); + return (tokenOut, minAssetsOut); } } diff --git a/packages/tasks/test/liquidity/erc4626/ERC4626Exiter.test.ts b/packages/tasks/test/liquidity/erc4626/ERC4626Exiter.test.ts index c81a3207..d4b202fc 100644 --- a/packages/tasks/test/liquidity/erc4626/ERC4626Exiter.test.ts +++ b/packages/tasks/test/liquidity/erc4626/ERC4626Exiter.test.ts @@ -23,6 +23,8 @@ describe('ERC4626Exiter', () => { let task: Contract let smartVault: Contract, authorizer: Contract, connector: Contract, owner: SignerWithAddress + const tokenOut = ONES_ADDRESS + before('setup', async () => { // eslint-disable-next-line prettier/prettier ([, owner] = await getSigners()) @@ -30,7 +32,7 @@ describe('ERC4626Exiter', () => { }) before('deploy connector', async () => { - connector = await deploy('ERC4626ConnectorMock') + connector = await deploy('ERC4626ConnectorMock', [tokenOut]) const overrideConnectorCheckRole = smartVault.interface.getSighash('overrideConnectorCheck') await authorizer.connect(owner).authorize(owner.address, smartVault.address, overrideConnectorCheckRole, []) await smartVault.connect(owner).overrideConnectorCheck(connector.address, true) @@ -76,134 +78,128 @@ describe('ERC4626Exiter', () => { }) context('when the token is not zero', () => { - let token: Contract, tokenOut: Contract + let erc4626: Contract beforeEach('deploy tokens', async () => { - token = await deployTokenMock('ERC4626') - tokenOut = await deployTokenMock('WETH') - }) - - beforeEach('set connector tokens', async () => { - await connector.setToken(tokenOut.address) - await connector.setERC4626(token.address) + erc4626 = await deployTokenMock('ERC4626') // token in }) context('when the amount is not zero', () => { const amount = fp(10) + const shareValue = fp(2.5) + const minAmountOut = amount.mul(shareValue) beforeEach('fund smart vault', async () => { - await token.mint(smartVault.address, amount) + await erc4626.mint(smartVault.address, amount) }) - context('when the token is the ERC4626 token', () => { - context('when the threshold has passed', () => { - const threshold = amount + context('when the threshold has passed', () => { + const threshold = amount - beforeEach('set token threshold', async () => { - const setDefaultTokenThresholdRole = task.interface.getSighash('setDefaultTokenThreshold') - await authorizer.connect(owner).authorize(owner.address, task.address, setDefaultTokenThresholdRole, []) - await task.connect(owner).setDefaultTokenThreshold(token.address, threshold, 0) - }) + beforeEach('set token threshold', async () => { + const setDefaultTokenThresholdRole = task.interface.getSighash('setDefaultTokenThreshold') + await authorizer.connect(owner).authorize(owner.address, task.address, setDefaultTokenThresholdRole, []) + await task.connect(owner).setDefaultTokenThreshold(erc4626.address, threshold, 0) + }) - const itExecutesTheTaskProperly = (requestedAmount: BigNumberish) => { - it('executes the expected connector', async () => { - const tx = await task.call(token.address, requestedAmount) + const itExecutesTheTaskProperly = (requestedAmount: BigNumberish) => { + it('executes the expected connector', async () => { + const tx = await task.call(erc4626.address, requestedAmount, minAmountOut) - const connectorData = connector.interface.encodeFunctionData('exit', [amount]) + const connectorData = connector.interface.encodeFunctionData('exit', [ + erc4626.address, + amount, + minAmountOut, + ]) - await assertIndirectEvent(tx, smartVault.interface, 'Executed', { connector, data: connectorData }) + await assertIndirectEvent(tx, smartVault.interface, 'Executed', { connector, data: connectorData }) - await assertIndirectEvent(tx, connector.interface, 'LogExit', { - amount, - }) + await assertIndirectEvent(tx, connector.interface, 'LogExit', { + erc4626, + amount, + minAmountOut, }) + }) - it('emits an Executed event', async () => { - const tx = await task.call(token.address, requestedAmount) - await assertEvent(tx, 'Executed') - }) - } + it('emits an Executed event', async () => { + const tx = await task.call(erc4626.address, requestedAmount, minAmountOut) + await assertEvent(tx, 'Executed') + }) + } - context('without balance connectors', () => { - const requestedAmount = amount + context('without balance connectors', () => { + const requestedAmount = amount - itExecutesTheTaskProperly(requestedAmount) + itExecutesTheTaskProperly(requestedAmount) - it('does not update any balance connectors', async () => { - const tx = await task.call(token.address, requestedAmount) + it('does not update any balance connectors', async () => { + const tx = await task.call(erc4626.address, requestedAmount, minAmountOut) - await assertNoEvent(tx, 'BalanceConnectorUpdated') - }) + await assertNoEvent(tx, 'BalanceConnectorUpdated') }) + }) - context('with balance connectors', () => { - const requestedAmount = 0 - const prevConnectorId = '0x0000000000000000000000000000000000000000000000000000000000000001' - const nextConnectorId = '0x0000000000000000000000000000000000000000000000000000000000000002' + context('with balance connectors', () => { + const requestedAmount = 0 + const prevConnectorId = '0x0000000000000000000000000000000000000000000000000000000000000001' + const nextConnectorId = '0x0000000000000000000000000000000000000000000000000000000000000002' - beforeEach('set balance connectors', async () => { - const setBalanceConnectorsRole = task.interface.getSighash('setBalanceConnectors') - await authorizer.connect(owner).authorize(owner.address, task.address, setBalanceConnectorsRole, []) - await task.connect(owner).setBalanceConnectors(prevConnectorId, nextConnectorId) - }) + beforeEach('set balance connectors', async () => { + const setBalanceConnectorsRole = task.interface.getSighash('setBalanceConnectors') + await authorizer.connect(owner).authorize(owner.address, task.address, setBalanceConnectorsRole, []) + await task.connect(owner).setBalanceConnectors(prevConnectorId, nextConnectorId) + }) - beforeEach('authorize task to update balance connectors', async () => { - const updateBalanceConnectorRole = smartVault.interface.getSighash('updateBalanceConnector') - await authorizer - .connect(owner) - .authorize(task.address, smartVault.address, updateBalanceConnectorRole, []) - }) + beforeEach('authorize task to update balance connectors', async () => { + const updateBalanceConnectorRole = smartVault.interface.getSighash('updateBalanceConnector') + await authorizer + .connect(owner) + .authorize(task.address, smartVault.address, updateBalanceConnectorRole, []) + }) - beforeEach('assign amount in to previous balance connector', async () => { - const updateBalanceConnectorRole = smartVault.interface.getSighash('updateBalanceConnector') - await authorizer - .connect(owner) - .authorize(owner.address, smartVault.address, updateBalanceConnectorRole, []) - await smartVault.connect(owner).updateBalanceConnector(prevConnectorId, token.address, amount, true) - }) + beforeEach('assign amount in to previous balance connector', async () => { + const updateBalanceConnectorRole = smartVault.interface.getSighash('updateBalanceConnector') + await authorizer + .connect(owner) + .authorize(owner.address, smartVault.address, updateBalanceConnectorRole, []) + await smartVault.connect(owner).updateBalanceConnector(prevConnectorId, erc4626.address, amount, true) + }) - itExecutesTheTaskProperly(requestedAmount) + itExecutesTheTaskProperly(requestedAmount) - it('updates the balance connectors properly', async () => { - const tx = await task.call(token.address, requestedAmount) + it('updates the balance connectors properly', async () => { + const tx = await task.call(erc4626.address, requestedAmount, minAmountOut) - await assertIndirectEvent(tx, smartVault.interface, 'BalanceConnectorUpdated', { - id: prevConnectorId, - token, - amount, - added: false, - }) + await assertIndirectEvent(tx, smartVault.interface, 'BalanceConnectorUpdated', { + id: prevConnectorId, + token: erc4626.address, + amount, + added: false, + }) - await assertIndirectEvent(tx, smartVault.interface, 'BalanceConnectorUpdated', { - id: nextConnectorId, - token: tokenOut.address, - amount, - added: true, - }) + await assertIndirectEvent(tx, smartVault.interface, 'BalanceConnectorUpdated', { + id: nextConnectorId, + token: tokenOut, + amount: minAmountOut, + added: true, }) }) }) + }) - context('when the threshold has not passed', () => { - const threshold = amount.add(1) - - beforeEach('set token threshold', async () => { - const setDefaultTokenThresholdRole = task.interface.getSighash('setDefaultTokenThreshold') - await authorizer.connect(owner).authorize(owner.address, task.address, setDefaultTokenThresholdRole, []) - await task.connect(owner).setDefaultTokenThreshold(token.address, threshold, 0) - }) + context('when the threshold has not passed', () => { + const threshold = amount.add(1) - it('reverts', async () => { - await expect(task.call(token.address, amount)).to.be.revertedWith('TaskTokenThresholdNotMet') - }) + beforeEach('set token threshold', async () => { + const setDefaultTokenThresholdRole = task.interface.getSighash('setDefaultTokenThreshold') + await authorizer.connect(owner).authorize(owner.address, task.address, setDefaultTokenThresholdRole, []) + await task.connect(owner).setDefaultTokenThreshold(erc4626.address, threshold, 0) }) - }) - - context('when the token is not the ERC4626 token', () => { - const token = ONES_ADDRESS it('reverts', async () => { - await expect(task.call(token, amount)).to.be.revertedWith('TaskTokenNotERC4626') + await expect(task.call(erc4626.address, amount, minAmountOut)).to.be.revertedWith( + 'TaskTokenThresholdNotMet' + ) }) }) }) @@ -212,23 +208,23 @@ describe('ERC4626Exiter', () => { const amount = 0 it('reverts', async () => { - await expect(task.call(token.address, amount)).to.be.revertedWith('TaskAmountZero') + await expect(task.call(erc4626.address, amount, 0)).to.be.revertedWith('TaskAmountZero') }) }) }) context('when the token is zero', () => { - const token = ZERO_ADDRESS + const erc4626 = ZERO_ADDRESS it('reverts', async () => { - await expect(task.call(token, 0)).to.be.revertedWith('TaskTokenZero') + await expect(task.call(erc4626, 0, 0)).to.be.revertedWith('TaskTokenZero') }) }) }) context('when the sender is not authorized', () => { it('reverts', async () => { - await expect(task.call(ZERO_ADDRESS, 0)).to.be.revertedWith('AuthSenderNotAllowed') + await expect(task.call(ZERO_ADDRESS, 0, 0)).to.be.revertedWith('AuthSenderNotAllowed') }) }) }) diff --git a/packages/tasks/test/liquidity/erc4626/ERC4626Joiner.test.ts b/packages/tasks/test/liquidity/erc4626/ERC4626Joiner.test.ts index d4ed1172..c580c27f 100644 --- a/packages/tasks/test/liquidity/erc4626/ERC4626Joiner.test.ts +++ b/packages/tasks/test/liquidity/erc4626/ERC4626Joiner.test.ts @@ -30,7 +30,7 @@ describe('ERC4626Joiner', () => { }) before('deploy connector', async () => { - connector = await deploy('ERC4626ConnectorMock') + connector = await deploy('ERC4626ConnectorMock', [ONES_ADDRESS]) const overrideConnectorCheckRole = smartVault.interface.getSighash('overrideConnectorCheck') await authorizer.connect(owner).authorize(owner.address, smartVault.address, overrideConnectorCheckRole, []) await smartVault.connect(owner).overrideConnectorCheck(connector.address, true) @@ -76,26 +76,23 @@ describe('ERC4626Joiner', () => { }) context('when the token is not zero', () => { - let token: Contract, tokenOut: Contract + let token: Contract, erc4626: Contract beforeEach('deploy tokens', async () => { token = await deployTokenMock('WETH') - tokenOut = await deployTokenMock('ERC4626') - }) - - beforeEach('set connector tokens', async () => { - await connector.setToken(token.address) - await connector.setERC4626(tokenOut.address) + erc4626 = await deployTokenMock('ERC4626') // token out }) context('when the amount is not zero', () => { const amount = fp(10) + const shareValue = fp(2.5) + const minAmountOut = amount.div(shareValue) beforeEach('fund smart vault', async () => { await token.mint(smartVault.address, amount) }) - context('when the token is the underlying token', () => { + context('when the ERC4626 is not zero', () => { context('when the threshold has passed', () => { const threshold = amount @@ -107,15 +104,25 @@ describe('ERC4626Joiner', () => { const itExecutesTheTaskProperly = (requestedAmount: BigNumberish) => { it('executes the expected connector', async () => { - const tx = await task.call(token.address, requestedAmount) + const tx = await task.call(erc4626.address, token.address, requestedAmount, minAmountOut) - const connectorData = connector.interface.encodeFunctionData('join', [amount]) + const connectorData = connector.interface.encodeFunctionData('join', [ + erc4626.address, + token.address, + amount, + minAmountOut, + ]) await assertIndirectEvent(tx, smartVault.interface, 'Executed', { connector, data: connectorData }) - await assertIndirectEvent(tx, connector.interface, 'LogJoin', { amount }) + await assertIndirectEvent(tx, connector.interface, 'LogJoin', { + erc4626, + token, + amount, + minAmountOut, + }) }) it('emits an Executed event', async () => { - const tx = await task.call(token.address, requestedAmount) + const tx = await task.call(erc4626.address, token.address, requestedAmount, minAmountOut) await assertEvent(tx, 'Executed') }) } @@ -126,7 +133,7 @@ describe('ERC4626Joiner', () => { itExecutesTheTaskProperly(requestedAmount) it('does not update any balance connectors', async () => { - const tx = await task.call(token.address, requestedAmount) + const tx = await task.call(erc4626.address, token.address, requestedAmount, minAmountOut) await assertNoEvent(tx, 'BalanceConnectorUpdated') }) @@ -161,7 +168,7 @@ describe('ERC4626Joiner', () => { itExecutesTheTaskProperly(requestedAmount) it('updates the balance connectors properly', async () => { - const tx = await task.call(token.address, amount) + const tx = await task.call(erc4626.address, token.address, amount, minAmountOut) await assertIndirectEvent(tx, smartVault.interface, 'BalanceConnectorUpdated', { id: prevConnectorId, @@ -172,8 +179,8 @@ describe('ERC4626Joiner', () => { await assertIndirectEvent(tx, smartVault.interface, 'BalanceConnectorUpdated', { id: nextConnectorId, - token: tokenOut.address, - amount, + token: erc4626.address, + amount: minAmountOut, added: true, }) }) @@ -190,16 +197,20 @@ describe('ERC4626Joiner', () => { }) it('reverts', async () => { - await expect(task.call(token.address, amount)).to.be.revertedWith('TaskTokenThresholdNotMet') + await expect(task.call(erc4626.address, token.address, amount, minAmountOut)).to.be.revertedWith( + 'TaskTokenThresholdNotMet' + ) }) }) }) - context('when the token is not the underlying token', () => { - const token = ONES_ADDRESS + context('when the ERC4626 is zero', () => { + const erc4626 = ZERO_ADDRESS it('reverts', async () => { - await expect(task.call(token, amount)).to.be.revertedWith('TaskTokenNotUnderlying') + await expect(task.call(erc4626, token.address, amount, minAmountOut)).to.be.revertedWith( + 'TaskERC4626Zero' + ) }) }) }) @@ -208,7 +219,7 @@ describe('ERC4626Joiner', () => { const amount = 0 it('reverts', async () => { - await expect(task.call(token.address, amount)).to.be.revertedWith('TaskAmountZero') + await expect(task.call(ZERO_ADDRESS, token.address, amount, 0)).to.be.revertedWith('TaskAmountZero') }) }) }) @@ -217,14 +228,14 @@ describe('ERC4626Joiner', () => { const token = ZERO_ADDRESS it('reverts', async () => { - await expect(task.call(token, 0)).to.be.revertedWith('TaskTokenZero') + await expect(task.call(ZERO_ADDRESS, token, 0, 0)).to.be.revertedWith('TaskTokenZero') }) }) }) context('when the sender is not authorized', () => { it('reverts', async () => { - await expect(task.call(ZERO_ADDRESS, 0)).to.be.revertedWith('AuthSenderNotAllowed') + await expect(task.call(ZERO_ADDRESS, ZERO_ADDRESS, 0, 0)).to.be.revertedWith('AuthSenderNotAllowed') }) }) }) From 14115a5023aeeeb7087857ab2a5531f9a6574b83 Mon Sep 17 00:00:00 2001 From: lgalende Date: Tue, 28 Nov 2023 00:34:51 -0300 Subject: [PATCH 3/4] tasks: reorder call params and add min amount out to auth modifier --- .../liquidity/erc4626/IERC4626Joiner.sol | 2 +- .../liquidity/erc4626/ERC4626Exiter.sol | 2 +- .../liquidity/erc4626/ERC4626Joiner.sol | 10 +++++----- .../liquidity/erc4626/ERC4626Joiner.test.ts | 18 +++++++++--------- 4 files changed, 16 insertions(+), 16 deletions(-) diff --git a/packages/tasks/contracts/interfaces/liquidity/erc4626/IERC4626Joiner.sol b/packages/tasks/contracts/interfaces/liquidity/erc4626/IERC4626Joiner.sol index e6caec6a..a998f1ee 100644 --- a/packages/tasks/contracts/interfaces/liquidity/erc4626/IERC4626Joiner.sol +++ b/packages/tasks/contracts/interfaces/liquidity/erc4626/IERC4626Joiner.sol @@ -28,5 +28,5 @@ interface IERC4626Joiner is IBaseERC4626Task { /** * @dev Executes the ERC4626 joiner task */ - function call(address erc4626, address token, uint256 amount, uint256 minAmountOut) external; + function call(address token, uint256 amount, address erc4626, uint256 minAmountOut) external; } diff --git a/packages/tasks/contracts/liquidity/erc4626/ERC4626Exiter.sol b/packages/tasks/contracts/liquidity/erc4626/ERC4626Exiter.sol index 7a847785..860f93ff 100644 --- a/packages/tasks/contracts/liquidity/erc4626/ERC4626Exiter.sol +++ b/packages/tasks/contracts/liquidity/erc4626/ERC4626Exiter.sol @@ -68,7 +68,7 @@ contract ERC4626Exiter is IERC4626Exiter, BaseERC4626Task { function call(address erc4626, uint256 amount, uint256 minAmountOut) external override - authP(authParams(erc4626, amount)) + authP(authParams(erc4626, amount, minAmountOut)) { if (amount == 0) amount = getTaskAmount(erc4626); _beforeERC4626Exiter(erc4626, amount); diff --git a/packages/tasks/contracts/liquidity/erc4626/ERC4626Joiner.sol b/packages/tasks/contracts/liquidity/erc4626/ERC4626Joiner.sol index 07280710..ffb11efd 100644 --- a/packages/tasks/contracts/liquidity/erc4626/ERC4626Joiner.sol +++ b/packages/tasks/contracts/liquidity/erc4626/ERC4626Joiner.sol @@ -61,18 +61,18 @@ contract ERC4626Joiner is IERC4626Joiner, BaseERC4626Task { /** * @dev Executes the ERC4626 joiner task - * @param erc4626 Address of the ERC4626 to be joined * @param token Address of the token to be joined with * @param amount Amount of assets to be joined with + * @param erc4626 Address of the ERC4626 to be joined * @param minAmountOut Minimum amount of shares willing to receive */ - function call(address erc4626, address token, uint256 amount, uint256 minAmountOut) + function call(address token, uint256 amount, address erc4626, uint256 minAmountOut) external override - authP(authParams(erc4626, token, amount)) + authP(authParams(erc4626, token, amount, minAmountOut)) { if (amount == 0) amount = getTaskAmount(token); - _beforeERC4626Joiner(erc4626, token, amount); + _beforeERC4626Joiner(token, amount, erc4626); bytes memory connectorData = abi.encodeWithSelector( IERC4626Connector.join.selector, erc4626, @@ -88,7 +88,7 @@ contract ERC4626Joiner is IERC4626Joiner, BaseERC4626Task { /** * @dev Before ERC4626 joiner hook */ - function _beforeERC4626Joiner(address erc4626, address token, uint256 amount) internal virtual { + function _beforeERC4626Joiner(address token, uint256 amount, address erc4626) internal virtual { _beforeBaseERC4626Task(token, amount); if (erc4626 == address(0)) revert TaskERC4626Zero(); } diff --git a/packages/tasks/test/liquidity/erc4626/ERC4626Joiner.test.ts b/packages/tasks/test/liquidity/erc4626/ERC4626Joiner.test.ts index c580c27f..1e34e0ed 100644 --- a/packages/tasks/test/liquidity/erc4626/ERC4626Joiner.test.ts +++ b/packages/tasks/test/liquidity/erc4626/ERC4626Joiner.test.ts @@ -104,7 +104,7 @@ describe('ERC4626Joiner', () => { const itExecutesTheTaskProperly = (requestedAmount: BigNumberish) => { it('executes the expected connector', async () => { - const tx = await task.call(erc4626.address, token.address, requestedAmount, minAmountOut) + const tx = await task.call(token.address, requestedAmount, erc4626.address, minAmountOut) const connectorData = connector.interface.encodeFunctionData('join', [ erc4626.address, @@ -122,7 +122,7 @@ describe('ERC4626Joiner', () => { }) it('emits an Executed event', async () => { - const tx = await task.call(erc4626.address, token.address, requestedAmount, minAmountOut) + const tx = await task.call(token.address, requestedAmount, erc4626.address, minAmountOut) await assertEvent(tx, 'Executed') }) } @@ -133,7 +133,7 @@ describe('ERC4626Joiner', () => { itExecutesTheTaskProperly(requestedAmount) it('does not update any balance connectors', async () => { - const tx = await task.call(erc4626.address, token.address, requestedAmount, minAmountOut) + const tx = await task.call(token.address, requestedAmount, erc4626.address, minAmountOut) await assertNoEvent(tx, 'BalanceConnectorUpdated') }) @@ -168,7 +168,7 @@ describe('ERC4626Joiner', () => { itExecutesTheTaskProperly(requestedAmount) it('updates the balance connectors properly', async () => { - const tx = await task.call(erc4626.address, token.address, amount, minAmountOut) + const tx = await task.call(token.address, amount, erc4626.address, minAmountOut) await assertIndirectEvent(tx, smartVault.interface, 'BalanceConnectorUpdated', { id: prevConnectorId, @@ -197,7 +197,7 @@ describe('ERC4626Joiner', () => { }) it('reverts', async () => { - await expect(task.call(erc4626.address, token.address, amount, minAmountOut)).to.be.revertedWith( + await expect(task.call(token.address, amount, erc4626.address, minAmountOut)).to.be.revertedWith( 'TaskTokenThresholdNotMet' ) }) @@ -208,7 +208,7 @@ describe('ERC4626Joiner', () => { const erc4626 = ZERO_ADDRESS it('reverts', async () => { - await expect(task.call(erc4626, token.address, amount, minAmountOut)).to.be.revertedWith( + await expect(task.call(token.address, amount, erc4626, minAmountOut)).to.be.revertedWith( 'TaskERC4626Zero' ) }) @@ -219,7 +219,7 @@ describe('ERC4626Joiner', () => { const amount = 0 it('reverts', async () => { - await expect(task.call(ZERO_ADDRESS, token.address, amount, 0)).to.be.revertedWith('TaskAmountZero') + await expect(task.call(token.address, amount, ZERO_ADDRESS, 0)).to.be.revertedWith('TaskAmountZero') }) }) }) @@ -228,14 +228,14 @@ describe('ERC4626Joiner', () => { const token = ZERO_ADDRESS it('reverts', async () => { - await expect(task.call(ZERO_ADDRESS, token, 0, 0)).to.be.revertedWith('TaskTokenZero') + await expect(task.call(token, 0, ZERO_ADDRESS, 0)).to.be.revertedWith('TaskTokenZero') }) }) }) context('when the sender is not authorized', () => { it('reverts', async () => { - await expect(task.call(ZERO_ADDRESS, ZERO_ADDRESS, 0, 0)).to.be.revertedWith('AuthSenderNotAllowed') + await expect(task.call(ZERO_ADDRESS, 0, ZERO_ADDRESS, 0)).to.be.revertedWith('AuthSenderNotAllowed') }) }) }) From 7b588dd128fe98c3bae1ca09873649ad588c70e5 Mon Sep 17 00:00:00 2001 From: lgalende Date: Tue, 28 Nov 2023 10:29:25 -0300 Subject: [PATCH 4/4] tasks: reorder auth modifier params --- packages/authorizer/contracts/AuthorizedHelpers.sol | 8 ++++++++ .../authorizer/contracts/test/AuthorizedHelpersMock.sol | 4 ++++ packages/authorizer/test/AuthorizedHelpers.test.ts | 1 + .../tasks/contracts/liquidity/erc4626/ERC4626Joiner.sol | 2 +- 4 files changed, 14 insertions(+), 1 deletion(-) diff --git a/packages/authorizer/contracts/AuthorizedHelpers.sol b/packages/authorizer/contracts/AuthorizedHelpers.sol index 7a8a2ed6..fcd0c037 100644 --- a/packages/authorizer/contracts/AuthorizedHelpers.sol +++ b/packages/authorizer/contracts/AuthorizedHelpers.sol @@ -100,6 +100,14 @@ contract AuthorizedHelpers { r[3] = p4; } + function authParams(address p1, uint256 p2, address p3, uint256 p4) internal pure returns (uint256[] memory r) { + r = new uint256[](4); + r[0] = uint256(uint160(p1)); + r[1] = p2; + r[2] = uint256(uint160(p3)); + r[3] = p4; + } + function authParams(address p1, uint256 p2, uint256 p3, uint256 p4) internal pure returns (uint256[] memory r) { r = new uint256[](4); r[0] = uint256(uint160(p1)); diff --git a/packages/authorizer/contracts/test/AuthorizedHelpersMock.sol b/packages/authorizer/contracts/test/AuthorizedHelpersMock.sol index 503f38da..5c8af2f1 100644 --- a/packages/authorizer/contracts/test/AuthorizedHelpersMock.sol +++ b/packages/authorizer/contracts/test/AuthorizedHelpersMock.sol @@ -53,6 +53,10 @@ contract AuthorizedHelpersMock is AuthorizedHelpers { return authParams(p1, p2, p3, p4); } + function getAuthParams(address p1, uint256 p2, address p3, uint256 p4) external pure returns (uint256[] memory r) { + return authParams(p1, p2, p3, p4); + } + function getAuthParams(address p1, uint256 p2, uint256 p3, uint256 p4) external pure returns (uint256[] memory r) { return authParams(p1, p2, p3, p4); } diff --git a/packages/authorizer/test/AuthorizedHelpers.test.ts b/packages/authorizer/test/AuthorizedHelpers.test.ts index f3d52fcc..34b74161 100644 --- a/packages/authorizer/test/AuthorizedHelpers.test.ts +++ b/packages/authorizer/test/AuthorizedHelpers.test.ts @@ -56,6 +56,7 @@ describe('AuthorizedHelpers', () => { context('when the number of arguments is 4', () => { itBehavesLikeAuthParams('address,address,uint256,uint256') + itBehavesLikeAuthParams('address,uint256,address,uint256') itBehavesLikeAuthParams('address,uint256,uint256,uint256') itBehavesLikeAuthParams('bytes32,address,uint256,bool') }) diff --git a/packages/tasks/contracts/liquidity/erc4626/ERC4626Joiner.sol b/packages/tasks/contracts/liquidity/erc4626/ERC4626Joiner.sol index ffb11efd..071846e8 100644 --- a/packages/tasks/contracts/liquidity/erc4626/ERC4626Joiner.sol +++ b/packages/tasks/contracts/liquidity/erc4626/ERC4626Joiner.sol @@ -69,7 +69,7 @@ contract ERC4626Joiner is IERC4626Joiner, BaseERC4626Task { function call(address token, uint256 amount, address erc4626, uint256 minAmountOut) external override - authP(authParams(erc4626, token, amount, minAmountOut)) + authP(authParams(token, amount, erc4626, minAmountOut)) { if (amount == 0) amount = getTaskAmount(token); _beforeERC4626Joiner(token, amount, erc4626);