diff --git a/packages/tasks/contracts/interfaces/swap/IOdosV2Swapper.sol b/packages/tasks/contracts/interfaces/swap/IOdosV2Swapper.sol
new file mode 100644
index 0000000..d1733d4
--- /dev/null
+++ b/packages/tasks/contracts/interfaces/swap/IOdosV2Swapper.sol
@@ -0,0 +1,27 @@
+// 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 './IBaseSwapTask.sol';
+
+/**
+ * @dev Odos swapper task interface
+ */
+interface IOdosV2Swapper is IBaseSwapTask {
+ /**
+ * @dev Execution function
+ */
+ function call(address tokenIn, uint256 amountIn, uint256 slippage, bytes memory data) external;
+}
diff --git a/packages/tasks/contracts/swap/OdosV2Swapper.sol b/packages/tasks/contracts/swap/OdosV2Swapper.sol
new file mode 100644
index 0000000..45bcba4
--- /dev/null
+++ b/packages/tasks/contracts/swap/OdosV2Swapper.sol
@@ -0,0 +1,113 @@
+// 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-helpers/contracts/math/FixedPoint.sol';
+import '@mimic-fi/v3-helpers/contracts/utils/BytesHelpers.sol';
+import '@mimic-fi/v3-connectors/contracts/interfaces/odos/IOdosV2Connector.sol';
+
+import './BaseSwapTask.sol';
+import '../interfaces/swap/IOdosV2Swapper.sol';
+
+/**
+ * @title Odos v2 swapper
+ * @dev Task that extends the base swap task to use Odos v2
+ */
+contract OdosV2Swapper is IOdosV2Swapper, BaseSwapTask {
+ using FixedPoint for uint256;
+ using BytesHelpers for bytes;
+
+ // Execution type for relayers
+ bytes32 public constant override EXECUTION_TYPE = keccak256('ODOS_V2_SWAPPER');
+
+ /**
+ * @dev Odos v2 swap config. Only used in the initializer.
+ */
+ struct OdosV2SwapConfig {
+ BaseSwapConfig baseSwapConfig;
+ }
+
+ /**
+ * @dev Initializes the Odos v2 swapper
+ * @param config Odos v2 swap config
+ */
+ function initialize(OdosV2SwapConfig memory config) external virtual initializer {
+ __OdosV2Swapper_init(config);
+ }
+
+ /**
+ * @dev Initializes the Odos v2 swapper. It does call upper contracts initializers.
+ * @param config Odos v2 swap config
+ */
+ function __OdosV2Swapper_init(OdosV2SwapConfig memory config) internal onlyInitializing {
+ __BaseSwapTask_init(config.baseSwapConfig);
+ __OdosV2Swapper_init_unchained(config);
+ }
+
+ /**
+ * @dev Initializes the Odos v2 swapper. It does not call upper contracts initializers.
+ * @param config Odos v2 swap config
+ */
+ function __OdosV2Swapper_init_unchained(OdosV2SwapConfig memory config) internal onlyInitializing {
+ // solhint-disable-previous-line no-empty-blocks
+ }
+
+ /**
+ * @dev Executes the Odos V2 swapper task
+ */
+ function call(address tokenIn, uint256 amountIn, uint256 slippage, bytes memory data)
+ external
+ override
+ authP(authParams(tokenIn, amountIn, slippage))
+ {
+ if (amountIn == 0) amountIn = getTaskAmount(tokenIn);
+ _beforeOdosV2Swapper(tokenIn, amountIn, slippage);
+
+ address tokenOut = getTokenOut(tokenIn);
+ uint256 price = _getPrice(tokenIn, tokenOut);
+ uint256 minAmountOut = amountIn.mulUp(price).mulUp(FixedPoint.ONE - slippage);
+ bytes memory connectorData = abi.encodeWithSelector(
+ IOdosV2Connector.execute.selector,
+ tokenIn,
+ tokenOut,
+ amountIn,
+ minAmountOut,
+ data
+ );
+
+ bytes memory result = ISmartVault(smartVault).execute(connector, connectorData);
+ _afterOdosV2Swapper(tokenIn, amountIn, slippage, tokenOut, result.toUint256());
+ }
+
+ /**
+ * @dev Before Odos v2 swapper hook
+ */
+ function _beforeOdosV2Swapper(address token, uint256 amount, uint256 slippage) internal virtual {
+ _beforeBaseSwapTask(token, amount, slippage);
+ }
+
+ /**
+ * @dev After Odos v2 swapper hook
+ */
+ function _afterOdosV2Swapper(
+ address tokenIn,
+ uint256 amountIn,
+ uint256 slippage,
+ address tokenOut,
+ uint256 amountOut
+ ) internal virtual {
+ _afterBaseSwapTask(tokenIn, amountIn, slippage, tokenOut, amountOut);
+ }
+}
diff --git a/packages/tasks/contracts/test/swap/OdosV2ConnectorMock.sol b/packages/tasks/contracts/test/swap/OdosV2ConnectorMock.sol
new file mode 100644
index 0000000..43cc41b
--- /dev/null
+++ b/packages/tasks/contracts/test/swap/OdosV2ConnectorMock.sol
@@ -0,0 +1,27 @@
+// 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 OdosV2ConnectorMock {
+ event LogExecute(address tokenIn, address tokenOut, uint256 amountIn, uint256 minAmountOut, bytes data);
+
+ function execute(address tokenIn, address tokenOut, uint256 amountIn, uint256 minAmountOut, bytes memory data)
+ external
+ returns (uint256)
+ {
+ emit LogExecute(tokenIn, tokenOut, amountIn, minAmountOut, data);
+ return minAmountOut;
+ }
+}
diff --git a/packages/tasks/test/swap/BalancerV2BoostedSwapper.mainnet.ts b/packages/tasks/test/swap/BalancerV2BoostedSwapper.mainnet.ts
index 07360d2..cb6c91d 100644
--- a/packages/tasks/test/swap/BalancerV2BoostedSwapper.mainnet.ts
+++ b/packages/tasks/test/swap/BalancerV2BoostedSwapper.mainnet.ts
@@ -33,7 +33,7 @@ describe('BalancerV2BoostedSwapper', function () {
before('deploy connector', async () => {
connector = await deploy(
- '@mimic-fi/v3-connectors/artifacts/contracts/swap/balancer/BalancerV2SwapConnector.sol/BalancerV2SwapConnector',
+ '@mimic-fi/v3-connectors/artifacts/contracts/balancer/BalancerV2SwapConnector.sol/BalancerV2SwapConnector',
[BALANCER_VAULT]
)
const overrideConnectorCheckRole = smartVault.interface.getSighash('overrideConnectorCheck')
diff --git a/packages/tasks/test/swap/BalancerV2LinearSwapper.mainnet.ts b/packages/tasks/test/swap/BalancerV2LinearSwapper.mainnet.ts
index d0d46fb..f897f87 100644
--- a/packages/tasks/test/swap/BalancerV2LinearSwapper.mainnet.ts
+++ b/packages/tasks/test/swap/BalancerV2LinearSwapper.mainnet.ts
@@ -33,7 +33,7 @@ describe('BalancerV2LinearSwapper', function () {
before('deploy connector', async () => {
connector = await deploy(
- '@mimic-fi/v3-connectors/artifacts/contracts/swap/balancer/BalancerV2SwapConnector.sol/BalancerV2SwapConnector',
+ '@mimic-fi/v3-connectors/artifacts/contracts/balancer/BalancerV2SwapConnector.sol/BalancerV2SwapConnector',
[BALANCER_VAULT]
)
const overrideConnectorCheckRole = smartVault.interface.getSighash('overrideConnectorCheck')
diff --git a/packages/tasks/test/swap/OdosV2Swapper.test.ts b/packages/tasks/test/swap/OdosV2Swapper.test.ts
new file mode 100644
index 0000000..7a67341
--- /dev/null
+++ b/packages/tasks/test/swap/OdosV2Swapper.test.ts
@@ -0,0 +1,440 @@
+import { OP } from '@mimic-fi/v3-authorizer'
+import {
+ assertIndirectEvent,
+ assertNoEvent,
+ BigNumberish,
+ deploy,
+ deployFeedMock,
+ deployProxy,
+ deployTokenMock,
+ fp,
+ getSigners,
+ MAX_UINT256,
+ ZERO_ADDRESS,
+} from '@mimic-fi/v3-helpers'
+import { SignerWithAddress } from '@nomiclabs/hardhat-ethers/dist/src/signer-with-address'
+import { expect } from 'chai'
+import { Contract, ContractTransaction } from 'ethers'
+import { defaultAbiCoder } from 'ethers/lib/utils'
+import { ethers } from 'hardhat'
+
+import { buildEmptyTaskConfig, deployEnvironment } from '../../src/setup'
+import { itBehavesLikeBaseSwapTask } from './BaseSwapTask.behavior'
+
+describe('OdosV2Swapper', () => {
+ let task: Contract
+ let smartVault: Contract, authorizer: Contract, priceOracle: Contract, connector: Contract, owner: SignerWithAddress
+
+ before('setup', async () => {
+ // eslint-disable-next-line prettier/prettier
+ ([, owner] = await getSigners())
+ ;({ authorizer, smartVault, priceOracle } = await deployEnvironment(owner))
+ })
+
+ before('deploy connector', async () => {
+ //eslint-disable-next-line no-secrets/no-secrets
+ connector = await deploy('OdosV2ConnectorMock')
+ 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(
+ 'OdosV2Swapper',
+ [],
+ [
+ {
+ baseSwapConfig: {
+ connector: connector.address,
+ tokenOut: ZERO_ADDRESS,
+ maxSlippage: 0,
+ customTokensOut: [],
+ customMaxSlippages: [],
+ taskConfig: buildEmptyTaskConfig(owner, smartVault),
+ },
+ },
+ ]
+ )
+ })
+
+ describe('swapper', () => {
+ beforeEach('set params', async function () {
+ this.owner = owner
+ this.task = task
+ this.authorizer = authorizer
+ })
+
+ itBehavesLikeBaseSwapTask('ODOS_V2_SWAPPER')
+ })
+
+ 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 in is not the zero address', () => {
+ let tokenIn: Contract
+
+ beforeEach('set token in', async () => {
+ tokenIn = await deployTokenMock('TKN')
+ })
+
+ context('when the amount in is not zero', () => {
+ const tokenRate = 2 // 1 token in = 2 token out
+ const thresholdAmount = fp(0.1) // in token out
+ const thresholdAmountInTokenIn = thresholdAmount.div(tokenRate) // threshold expressed in token in
+ const amountIn = thresholdAmountInTokenIn
+
+ context('when the token in is allowed', () => {
+ context('when there is a token out set', () => {
+ let tokenOut: Contract
+ let extraCallData = ''
+
+ beforeEach('set default token out', async () => {
+ tokenOut = await deployTokenMock('TKN')
+ const setDefaultTokenOutRole = task.interface.getSighash('setDefaultTokenOut')
+ await authorizer.connect(owner).authorize(owner.address, task.address, setDefaultTokenOutRole, [])
+ await task.connect(owner).setDefaultTokenOut(tokenOut.address)
+ })
+
+ context('when an off-chain oracle is given', () => {
+ beforeEach('sign off-chain oracle', async () => {
+ const setSignerRole = priceOracle.interface.getSighash('setSigner')
+ await authorizer.connect(owner).authorize(owner.address, priceOracle.address, setSignerRole, [])
+ await priceOracle.connect(owner).setSigner(owner.address, true)
+
+ type PriceData = { base: string; quote: string; rate: BigNumberish; deadline: BigNumberish }
+ const pricesData: PriceData[] = [
+ {
+ base: tokenIn.address,
+ quote: tokenOut.address,
+ rate: fp(tokenRate),
+ deadline: MAX_UINT256,
+ },
+ {
+ base: tokenOut.address,
+ quote: tokenIn.address,
+ rate: fp(1).mul(fp(1)).div(fp(tokenRate)),
+ deadline: MAX_UINT256,
+ },
+ ]
+
+ const PricesDataType = 'PriceData(address base, address quote, uint256 rate, uint256 deadline)[]'
+ const encodedPrices = await defaultAbiCoder.encode([PricesDataType], [pricesData])
+ const message = ethers.utils.solidityKeccak256(['bytes'], [encodedPrices])
+ const signature = await owner.signMessage(ethers.utils.arrayify(message))
+ const data = defaultAbiCoder.encode([PricesDataType, 'bytes'], [pricesData, signature]).slice(2)
+ const dataLength = defaultAbiCoder.encode(['uint256'], [data.length / 2]).slice(2)
+ extraCallData = `${data}${dataLength}`
+ })
+
+ beforeEach('set threshold', async () => {
+ const setDefaultTokenThresholdRole = task.interface.getSighash('setDefaultTokenThreshold')
+ await authorizer
+ .connect(owner)
+ .authorize(owner.address, task.address, setDefaultTokenThresholdRole, [])
+ await task.connect(owner).setDefaultTokenThreshold(tokenOut.address, thresholdAmount, 0)
+ })
+
+ const executeTask = async (amountIn, slippage, data): Promise => {
+ const callTx = await task.populateTransaction.call(tokenIn.address, amountIn, slippage, data)
+ const callData = `${callTx.data}${extraCallData}`
+ return owner.sendTransaction({ to: task.address, data: callData })
+ }
+
+ context('when the smart vault balance passes the threshold', () => {
+ beforeEach('fund smart vault', async () => {
+ await tokenIn.mint(smartVault.address, amountIn)
+ })
+
+ context('when the slippage is below the limit', () => {
+ const data = '0xaabb'
+ const slippage = fp(0.01)
+ const expectedAmountOut = amountIn.mul(tokenRate)
+ const minAmountOut = expectedAmountOut.mul(fp(1).sub(slippage)).div(fp(1))
+
+ beforeEach('set max slippage', async () => {
+ const setDefaultMaxSlippageRole = task.interface.getSighash('setDefaultMaxSlippage')
+ await authorizer
+ .connect(owner)
+ .authorize(owner.address, task.address, setDefaultMaxSlippageRole, [])
+ await task.connect(owner).setDefaultMaxSlippage(slippage)
+ })
+
+ const itExecutesTheTaskProperly = (requestedAmount: BigNumberish) => {
+ it('executes the expected connector', async () => {
+ const tx = await executeTask(requestedAmount, slippage, data)
+
+ const connectorData = connector.interface.encodeFunctionData('execute', [
+ tokenIn.address,
+ tokenOut.address,
+ amountIn,
+ minAmountOut,
+ data,
+ ])
+
+ await assertIndirectEvent(tx, smartVault.interface, 'Executed', {
+ connector,
+ data: connectorData,
+ })
+
+ await assertIndirectEvent(tx, connector.interface, 'LogExecute', {
+ tokenIn,
+ tokenOut,
+ amountIn,
+ minAmountOut,
+ data,
+ })
+ })
+
+ it('emits an Executed event', async () => {
+ const tx = await executeTask(requestedAmount, slippage, data)
+
+ await assertIndirectEvent(tx, task.interface, 'Executed')
+ })
+ }
+
+ context('without balance connectors', () => {
+ const requestedAmount = amountIn
+
+ itExecutesTheTaskProperly(requestedAmount)
+
+ it('does not update any balance connectors', async () => {
+ const tx = await executeTask(requestedAmount, slippage, data)
+
+ 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, tokenIn.address, amountIn, true)
+ })
+
+ itExecutesTheTaskProperly(requestedAmount)
+
+ it('updates the balance connectors properly', async () => {
+ const tx = await executeTask(requestedAmount, slippage, data)
+
+ await assertIndirectEvent(tx, smartVault.interface, 'BalanceConnectorUpdated', {
+ id: prevConnectorId,
+ token: tokenIn.address,
+ amount: amountIn,
+ added: false,
+ })
+
+ await assertIndirectEvent(tx, smartVault.interface, 'BalanceConnectorUpdated', {
+ id: nextConnectorId,
+ token: tokenOut.address,
+ amount: minAmountOut,
+ added: true,
+ })
+ })
+ })
+ })
+
+ context('when the slippage is above the limit', () => {
+ const slippage = fp(0.01)
+
+ it('reverts', async () => {
+ await expect(executeTask(amountIn, slippage, '0x')).to.be.revertedWith('TaskSlippageAboveMax')
+ })
+ })
+ })
+
+ context('when the smart vault balance does not pass the threshold', () => {
+ const amountIn = thresholdAmountInTokenIn.div(2)
+
+ beforeEach('fund smart vault', async () => {
+ await tokenIn.mint(smartVault.address, amountIn)
+ })
+
+ it('reverts', async () => {
+ await expect(executeTask(amountIn, 0, '0x')).to.be.revertedWith('TaskTokenThresholdNotMet')
+ })
+ })
+ })
+
+ context('when no off-chain oracle is given', () => {
+ context('when an on-chain oracle is given', () => {
+ beforeEach('set price feed', async () => {
+ const feed = await deployFeedMock(fp(tokenRate), 18)
+ const setFeedRole = priceOracle.interface.getSighash('setFeed')
+ await authorizer.connect(owner).authorize(owner.address, priceOracle.address, setFeedRole, [])
+ await priceOracle.connect(owner).setFeed(tokenIn.address, tokenOut.address, feed.address)
+ })
+
+ beforeEach('set threshold', async () => {
+ const setDefaultTokenThresholdRole = task.interface.getSighash('setDefaultTokenThreshold')
+ await authorizer
+ .connect(owner)
+ .authorize(owner.address, task.address, setDefaultTokenThresholdRole, [])
+ await task.connect(owner).setDefaultTokenThreshold(tokenOut.address, thresholdAmount, 0)
+ })
+
+ context('when the smart vault balance passes the threshold', () => {
+ beforeEach('fund smart vault', async () => {
+ await tokenIn.mint(smartVault.address, amountIn)
+ })
+
+ context('when the slippage is below the limit', () => {
+ const data = '0xaabb'
+ const slippage = fp(0.01)
+ const expectedAmountOut = amountIn.mul(tokenRate)
+ const minAmountOut = expectedAmountOut.mul(fp(1).sub(slippage)).div(fp(1))
+
+ beforeEach('set max slippage', async () => {
+ const setDefaultMaxSlippageRole = task.interface.getSighash('setDefaultMaxSlippage')
+ await authorizer
+ .connect(owner)
+ .authorize(owner.address, task.address, setDefaultMaxSlippageRole, [])
+ await task.connect(owner).setDefaultMaxSlippage(slippage)
+ })
+
+ it('executes the expected connector', async () => {
+ const tx = await task.call(tokenIn.address, amountIn, slippage, data)
+
+ const connectorData = connector.interface.encodeFunctionData('execute', [
+ tokenIn.address,
+ tokenOut.address,
+ amountIn,
+ minAmountOut,
+ data,
+ ])
+
+ await assertIndirectEvent(tx, smartVault.interface, 'Executed', {
+ connector,
+ data: connectorData,
+ })
+
+ await assertIndirectEvent(tx, connector.interface, 'LogExecute', {
+ tokenIn,
+ tokenOut,
+ amountIn,
+ minAmountOut,
+ data,
+ })
+ })
+
+ it('emits an Executed event', async () => {
+ const tx = await task.call(tokenIn.address, amountIn, slippage, data)
+
+ await assertIndirectEvent(tx, task.interface, 'Executed')
+ })
+ })
+
+ context('when the slippage is above the limit', () => {
+ const slippage = fp(0.01)
+
+ it('reverts', async () => {
+ await expect(task.call(tokenIn.address, amountIn, slippage, '0x')).to.be.revertedWith(
+ 'TaskSlippageAboveMax'
+ )
+ })
+ })
+ })
+
+ context('when the smart vault balance does not pass the threshold', () => {
+ const amountIn = thresholdAmountInTokenIn.div(2)
+
+ beforeEach('fund smart vault', async () => {
+ await tokenIn.mint(smartVault.address, amountIn)
+ })
+
+ it('reverts', async () => {
+ await expect(task.call(tokenIn.address, amountIn, 0, '0x')).to.be.revertedWith(
+ 'TaskTokenThresholdNotMet'
+ )
+ })
+ })
+ })
+
+ context('when no on-chain oracle is given', () => {
+ it('reverts', async () => {
+ // TODO: Hardhat does not decode price oracle error properly
+ await expect(task.call(tokenIn.address, amountIn, 0, '0x')).to.be.reverted
+ })
+ })
+ })
+ })
+
+ context('when the token out is not set', () => {
+ it('reverts', async () => {
+ await expect(task.call(tokenIn.address, amountIn, 0, '0x')).to.be.revertedWith('TaskTokenOutNotSet')
+ })
+ })
+ })
+
+ context('when the token in is denied', () => {
+ beforeEach('deny token in', async () => {
+ const setTokensAcceptanceListRole = task.interface.getSighash('setTokensAcceptanceList')
+ await authorizer.connect(owner).authorize(owner.address, task.address, setTokensAcceptanceListRole, [])
+ await task.connect(owner).setTokensAcceptanceList([tokenIn.address], [true])
+ })
+
+ it('reverts', async () => {
+ await expect(task.call(tokenIn.address, 0, 0, '0x')).to.be.revertedWith('TaskTokenNotAllowed')
+ })
+ })
+ })
+
+ context('when the amount in is zero', () => {
+ const amountIn = 0
+
+ it('reverts', async () => {
+ await expect(task.call(tokenIn.address, amountIn, 0, '0x')).to.be.revertedWith('TaskAmountZero')
+ })
+ })
+ })
+
+ context('when the token in is the zero address', () => {
+ const tokenIn = ZERO_ADDRESS
+
+ it('reverts', async () => {
+ await expect(task.call(tokenIn, 0, 0, '0x')).to.be.revertedWith('TaskTokenZero')
+ })
+ })
+ })
+
+ context('when the sender is not authorized', () => {
+ it('reverts', async () => {
+ await expect(task.call(ZERO_ADDRESS, 0, 0, '0x')).to.be.revertedWith('AuthSenderNotAllowed')
+ })
+ })
+ })
+})