-
Notifications
You must be signed in to change notification settings - Fork 5
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
- Loading branch information
1 parent
556e3db
commit 3c95072
Showing
4 changed files
with
325 additions
and
1 deletion.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
42 changes: 42 additions & 0 deletions
42
packages/tasks/contracts/interfaces/primitives/IHandleOver.sol
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,42 @@ | ||
// 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 <http://www.gnu.org/licenses/>. | ||
|
||
pragma solidity >=0.8.0; | ||
|
||
import '../ITask.sol'; | ||
|
||
/** | ||
* @dev Hand over task interface | ||
*/ | ||
interface IHandleOver is ITask { | ||
/** | ||
* @dev The token is zero | ||
*/ | ||
error TaskTokenZero(); | ||
|
||
/** | ||
* @dev The amount is zero | ||
*/ | ||
error TaskAmountZero(); | ||
|
||
/** | ||
* @dev The tokens source is zero | ||
*/ | ||
error TaskConnectorZero(bytes32 id); | ||
|
||
/** | ||
* @dev Executes the hand over task | ||
*/ | ||
function call(address token, uint256 amount) external; | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,98 @@ | ||
// 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 <http://www.gnu.org/licenses/>. | ||
|
||
pragma solidity ^0.8.0; | ||
|
||
import '@mimic-fi/v3-helpers/contracts/utils/ERC20Helpers.sol'; | ||
|
||
import '../Task.sol'; | ||
import '../interfaces/primitives/IHandleOver.sol'; | ||
|
||
/** | ||
* @title Hand over task | ||
* @dev Task that simply moves tokens from one balance connector to the other | ||
*/ | ||
contract HandleOver is IHandleOver, Task { | ||
// Execution type for relayers | ||
bytes32 public constant override EXECUTION_TYPE = keccak256('HANDLE_OVER'); | ||
|
||
/** | ||
* @dev Hand over config. Only used in the initializer. | ||
*/ | ||
struct HandleOverConfig { | ||
TaskConfig taskConfig; | ||
} | ||
|
||
/** | ||
* @dev Initializes the hand over task | ||
* @param config Hand over config | ||
*/ | ||
function initialize(HandleOverConfig memory config) external virtual initializer { | ||
__HandleOver_init(config); | ||
} | ||
|
||
/** | ||
* @dev Initializes the hand over task. It does call upper contracts initializers. | ||
* @param config Hand over config | ||
*/ | ||
function __HandleOver_init(HandleOverConfig memory config) internal onlyInitializing { | ||
__Task_init(config.taskConfig); | ||
__HandleOver_init_unchained(config); | ||
} | ||
|
||
/** | ||
* @dev Initializes the hand over task. It does not call upper contracts initializers. | ||
* @param config Hand over config | ||
*/ | ||
function __HandleOver_init_unchained(HandleOverConfig memory config) internal onlyInitializing { | ||
// solhint-disable-previous-line no-empty-blocks | ||
} | ||
|
||
/** | ||
* @dev Execute the hand over taks | ||
*/ | ||
function call(address token, uint256 amount) external override authP(authParams(token, amount)) { | ||
if (amount == 0) amount = getTaskAmount(token); | ||
_beforeHandleOver(token, amount); | ||
_afterHandleOver(token, amount); | ||
} | ||
|
||
/** | ||
* @dev Before hand over task hook | ||
*/ | ||
function _beforeHandleOver(address token, uint256 amount) internal virtual { | ||
_beforeTask(token, amount); | ||
if (token == address(0)) revert TaskTokenZero(); | ||
if (amount == 0) revert TaskAmountZero(); | ||
} | ||
|
||
/** | ||
* @dev After hand over task hook | ||
*/ | ||
function _afterHandleOver(address token, uint256 amount) internal virtual { | ||
_increaseBalanceConnector(token, amount); | ||
_afterTask(token, amount); | ||
} | ||
|
||
/** | ||
* @dev Sets the balance connectors. Both balance connector must be set. | ||
* @param previous Balance connector id of the previous task in the workflow | ||
* @param next Balance connector id of the next task in the workflow | ||
*/ | ||
function _setBalanceConnectors(bytes32 previous, bytes32 next) internal virtual override { | ||
if (previous == bytes32(0)) revert TaskConnectorZero(previous); | ||
if (next == bytes32(0)) revert TaskConnectorZero(next); | ||
super._setBalanceConnectors(previous, next); | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,184 @@ | ||
import { | ||
assertEvent, | ||
assertIndirectEvent, | ||
deployProxy, | ||
deployTokenMock, | ||
fp, | ||
getSigners, | ||
ZERO_BYTES32, | ||
} from '@mimic-fi/v3-helpers' | ||
import { SignerWithAddress } from '@nomiclabs/hardhat-ethers/dist/src/signer-with-address' | ||
import { expect } from 'chai' | ||
import { BigNumber, Contract } from 'ethers' | ||
import { ethers } from 'hardhat' | ||
|
||
import { buildEmptyTaskConfig, deployEnvironment } from '../../src/setup' | ||
|
||
describe('HandleOver', () => { | ||
const PREVIOUS = '0x0000000000000000000000000000000000000000000000000000000000000001' | ||
const NEXT = '0x0000000000000000000000000000000000000000000000000000000000000002' | ||
|
||
let task: Contract | ||
let smartVault: Contract, authorizer: Contract, owner: SignerWithAddress | ||
|
||
before('setup', async () => { | ||
// eslint-disable-next-line prettier/prettier | ||
([, owner] = await getSigners()) | ||
;({ authorizer, smartVault } = await deployEnvironment(owner)) | ||
}) | ||
|
||
beforeEach('deploy task', async () => { | ||
const taskConfig = buildEmptyTaskConfig(owner, smartVault) | ||
taskConfig.baseConfig.previousBalanceConnectorId = PREVIOUS | ||
taskConfig.baseConfig.nextBalanceConnectorId = NEXT | ||
task = await deployProxy('HandleOver', [], [{ taskConfig }]) | ||
}) | ||
|
||
describe('execution type', () => { | ||
it('defines it correctly', async () => { | ||
const expectedType = ethers.utils.solidityKeccak256(['string'], ['HANDLE_OVER']) | ||
expect(await task.EXECUTION_TYPE()).to.be.equal(expectedType) | ||
}) | ||
}) | ||
|
||
describe('setBalanceConnectors', () => { | ||
context('when the sender is authorized', () => { | ||
beforeEach('authorize sender', async () => { | ||
const setBalanceConnectorsRole = task.interface.getSighash('setBalanceConnectors') | ||
await authorizer.connect(owner).authorize(owner.address, task.address, setBalanceConnectorsRole, []) | ||
task = task.connect(owner) | ||
}) | ||
|
||
const itCanBeSet = (previous: string, next: string) => { | ||
it('can be set', async () => { | ||
const tx = await task.setBalanceConnectors(previous, next) | ||
|
||
const connectors = await task.getBalanceConnectors() | ||
expect(connectors.previous).to.be.equal(previous) | ||
expect(connectors.next).to.be.equal(next) | ||
|
||
await assertEvent(tx, 'BalanceConnectorsSet', { previous, next }) | ||
}) | ||
} | ||
|
||
context('when setting next to non-zero', () => { | ||
const next = NEXT | ||
|
||
context('when setting previous to zero', () => { | ||
const previous = PREVIOUS | ||
|
||
itCanBeSet(previous, next) | ||
}) | ||
|
||
context('when setting previous to non-zero', () => { | ||
const previous = ZERO_BYTES32 | ||
|
||
it('reverts', async () => { | ||
await expect(task.setBalanceConnectors(previous, next)).to.be.revertedWith('TaskConnectorZero') | ||
}) | ||
}) | ||
}) | ||
|
||
context('when setting next to zero', () => { | ||
const next = ZERO_BYTES32 | ||
|
||
context('when setting previous to zero', () => { | ||
const previous = ZERO_BYTES32 | ||
|
||
it('reverts', async () => { | ||
await expect(task.setBalanceConnectors(previous, next)).to.be.revertedWith('TaskConnectorZero') | ||
}) | ||
}) | ||
|
||
context('when setting previous to non-zero', () => { | ||
const previous = PREVIOUS | ||
|
||
it('reverts', async () => { | ||
await expect(task.setBalanceConnectors(previous, next)).to.be.revertedWith('TaskConnectorZero') | ||
}) | ||
}) | ||
}) | ||
}) | ||
|
||
context('when the sender is not authorized', () => { | ||
it('reverts', async () => { | ||
await expect(task.setBalanceConnectors(ZERO_BYTES32, ZERO_BYTES32)).to.be.revertedWith('AuthSenderNotAllowed') | ||
}) | ||
}) | ||
}) | ||
|
||
describe('call', () => { | ||
let token: Contract | ||
const amount = fp(20) | ||
|
||
beforeEach('deploy token', async () => { | ||
token = await deployTokenMock('USDC') | ||
await token.mint(smartVault.address, amount) | ||
}) | ||
|
||
beforeEach('update 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(PREVIOUS, token.address, amount, true) | ||
}) | ||
|
||
beforeEach('authorize task to update balance connectors', async () => { | ||
const updateBalanceConnectorRole = smartVault.interface.getSighash('updateBalanceConnector') | ||
await authorizer.connect(owner).authorize(task.address, smartVault.address, updateBalanceConnectorRole, []) | ||
}) | ||
|
||
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) | ||
}) | ||
|
||
const itExecutesTheTaskProperly = (requestedAmount: BigNumber) => { | ||
it('emits an Executed event', async () => { | ||
const tx = await task.call(token.address, requestedAmount) | ||
|
||
await assertEvent(tx, 'Executed') | ||
}) | ||
|
||
it('updates the balance connectors properly', async () => { | ||
const transactedAmount = requestedAmount.eq(fp(0)) ? amount : requestedAmount | ||
|
||
const tx = await task.call(token.address, requestedAmount) | ||
|
||
await assertIndirectEvent(tx, smartVault.interface, 'BalanceConnectorUpdated', { | ||
id: PREVIOUS, | ||
token, | ||
amount: transactedAmount, | ||
added: false, | ||
}) | ||
|
||
await assertIndirectEvent(tx, smartVault.interface, 'BalanceConnectorUpdated', { | ||
id: NEXT, | ||
token, | ||
amount: transactedAmount, | ||
added: true, | ||
}) | ||
}) | ||
} | ||
|
||
context('when requesting a specific amount', () => { | ||
const requestedAmount = fp(0) | ||
|
||
itExecutesTheTaskProperly(requestedAmount) | ||
}) | ||
|
||
context('when requesting a specific amount', () => { | ||
const requestedAmount = amount.div(2) | ||
|
||
itExecutesTheTaskProperly(requestedAmount) | ||
}) | ||
}) | ||
|
||
context('when the sender is not authorized', () => { | ||
it('reverts', async () => { | ||
await expect(task.call(token.address, 0)).to.be.revertedWith('AuthSenderNotAllowed') | ||
}) | ||
}) | ||
}) | ||
}) |