Skip to content

Commit

Permalink
feat: add ability to move liquidity from Compound to CapybaraFinance (#5
Browse files Browse the repository at this point in the history
)

* feat: add ability to move liquidity from Compound to Capybara
* feat: update contracts version 1.0.0 => 1.1.0
* docs: improve comments
  • Loading branch information
EvgeniiZaitsevCW authored Mar 7, 2025
1 parent f74820c commit b31ae14
Show file tree
Hide file tree
Showing 5 changed files with 433 additions and 30 deletions.
83 changes: 83 additions & 0 deletions contracts/Dispatcher.sol
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,8 @@

pragma solidity 0.8.24;

import { IERC20 } from "@openzeppelin/contracts/token/ERC20/IERC20.sol";

import { AccessControlExtUpgradeable } from "./base/AccessControlExtUpgradeable.sol";
import { PausableExtUpgradeable } from "./base/PausableExtUpgradeable.sol";
import { RescuableUpgradeable } from "./base/RescuableUpgradeable.sol";
Expand All @@ -14,6 +16,13 @@ import { DispatcherStorage } from "./DispatcherStorage.sol";
interface ICompoundAgent {
function transferOwnership(address newOwner) external;
function configureAdmin(address account, bool newStatus) external;
function redeemUnderlying(uint256 redeemAmount) external;
}

/// @dev Interface for the liquidity pool contract from the `CapybaraFinance` protocol with the necessary functions.
interface ILiquidityPool {
function deposit(uint256 amount) external;
function token() external view returns (address);
}

/**
Expand All @@ -34,11 +43,17 @@ contract Dispatcher is
/// @dev The role of this contract owner.
bytes32 public constant OWNER_ROLE = keccak256("OWNER_ROLE");

/// @dev The role that allows to move liquidity from Compound to Capybara.
bytes32 public constant LIQUIDITY_MOVER_ROLE = keccak256("LIQUIDITY_MOVER_ROLE");

// ------------------ Errors ---------------------------------- //

/// @dev Thrown if the provided new implementation address is not of a dispatcher contract.
error Dispatcher_ImplementationAddressInvalid();

/// @dev Thrown if the provided account address is zero.
error Dispatcher_AccountAddressZero();

// ------------------ Initializers ---------------------------- //

/**
Expand Down Expand Up @@ -81,6 +96,50 @@ contract Dispatcher is

// ------------------ Functions ------------------------------- //

/**
* @dev Initializes the liquidity mover role for a batch of accounts and
* sets the owner role as the admin for the liquidity mover role.
*
* Requirements:
*
* - The caller must have the {OWNER_ROLE} role.
*
* @param accounts The addresses of the accounts to initialize the liquidity mover role for.
*/
function initLiquidityMoverRole(address[] calldata accounts) external onlyRole(OWNER_ROLE) {
_setRoleAdmin(LIQUIDITY_MOVER_ROLE, OWNER_ROLE);
uint256 len = accounts.length;
for (uint256 i = 0; i < len; ++i) {
address account = accounts[i];
if (account == address(0)) {
revert Dispatcher_AccountAddressZero();
}
_grantRole(LIQUIDITY_MOVER_ROLE, account);
}
}

/**
* @dev Removes the liquidity mover role for a batch of accounts
* and sets the default role as the admin for the liquidity mover role.
*
* Requirements:
*
* - The caller must have the {OWNER_ROLE} role.
*
* @param accounts The addresses of the accounts to remove the liquidity mover role for.
*/
function removeLiquidityMoverRole(address[] calldata accounts) external onlyRole(OWNER_ROLE) {
uint256 len = accounts.length;
for (uint256 i = 0; i < len; ++i) {
address account = accounts[i];
if (account == address(0)) {
revert Dispatcher_AccountAddressZero();
}
_revokeRole(LIQUIDITY_MOVER_ROLE, account);
}
_setRoleAdmin(LIQUIDITY_MOVER_ROLE, DEFAULT_ADMIN_ROLE);
}

/**
* @dev Transfers ownership of a CompoundAgent contract to a new owner.
*
Expand Down Expand Up @@ -122,6 +181,30 @@ contract Dispatcher is
}
}

/**
* @dev Moves liquidity from a CompoundAgent contract to a liquidity pool of the `CapybaraFinance` protocol.
*
* Requirements:
*
* - The contract must not be paused.
* - The caller must have the {LIQUIDITY_MOVER_ROLE} role.
*
* @param amount The amount of liquidity to move.
* @param compoundAgent The address of the CompoundAgent contract to move liquidity from.
* @param capybaraLiquidityPool The address of the liquidity pool to move liquidity to.
*/
function moveLiquidityFromCompoundToCapybara(
uint256 amount,
address compoundAgent,
address capybaraLiquidityPool
) external whenNotPaused onlyRole(LIQUIDITY_MOVER_ROLE) {
address underlyingToken = ILiquidityPool(capybaraLiquidityPool).token();
ICompoundAgent(compoundAgent).redeemUnderlying(amount);
IERC20(underlyingToken).transferFrom(compoundAgent, address(this), amount);
IERC20(underlyingToken).approve(capybaraLiquidityPool, amount);
ILiquidityPool(capybaraLiquidityPool).deposit(amount);
}

// ------------------ Pure functions -------------------------- //

/**
Expand Down
2 changes: 1 addition & 1 deletion contracts/base/Versionable.sol
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,6 @@ abstract contract Versionable is IVersionable {
* @inheritdoc IVersionable
*/
function $__VERSION() external pure returns (Version memory) {
return Version(1, 0, 0);
return Version(1, 1, 0);
}
}
52 changes: 52 additions & 0 deletions contracts/mocks/CapybaraLiquidityPoolMock.sol
Original file line number Diff line number Diff line change
@@ -0,0 +1,52 @@
// SPDX-License-Identifier: MIT

pragma solidity ^0.8.0;

import { IERC20 } from "@openzeppelin/contracts/token/ERC20/IERC20.sol";

/**
* @title CapybaraLiquidityPoolMock contract
* @author CloudWalk Inc. (See https://cloudwalk.io)
* @dev A simplified version of the liquidity pool smart-contract of the `CapybaraFinance` protocol
* to use in tests for other contracts.
*/
contract CapybaraLiquidityPoolMock {
/// @dev The address of the underlying token.
address public underlyingToken;

// ------------------ Events ---------------------------------- //

/// @dev Emitted when the `deposit()` function is called with the parameters of the function.
event MockDepositCalled(
uint256 amount
);

// ------------------ Constructor ----------------------------- //

/**
* @dev The constructor of the contract.
* @param underlyingToken_ The address of the underlying token.
*/
constructor(address underlyingToken_) {
underlyingToken = underlyingToken_;
}

// ------------------ Functions ------------------------------ //

/**
* @dev Imitates the same-name function of the liquidity pool of the `CapybaraFinance` protocol.
* @param amount The amount of the underlying token to deposit.
*/
function deposit(uint256 amount) external {
emit MockDepositCalled(amount);
IERC20(underlyingToken).transferFrom(msg.sender, address(this), amount);
}

/**
* @dev Imitates the same-name function of the liquidity pool of the `CapybaraFinance` protocol.
* @return The address of the underlying token.
*/
function token() external view returns (address) {
return underlyingToken;
}
}
58 changes: 56 additions & 2 deletions contracts/mocks/CompoundAgentMock.sol
Original file line number Diff line number Diff line change
Expand Up @@ -2,15 +2,25 @@

pragma solidity ^0.8.0;

import { ERC20TokenMock } from "./tokens/ERC20TokenMock.sol";

/**
* @title CompoundAgentMock contract
* @author CloudWalk Inc. (See https://cloudwalk.io)
* @dev A simplified version of the CompoundAgent contract to use in tests for other contracts.
*/
contract CompoundAgentMock {
/// @dev The owner of the contract.
address public owner;

/// @dev The counter of the `configureAdmin()` function calls.
uint256 public configureAdminCallCounter;

/// @dev The address of the underlying token.
address public underlyingToken;

// ------------------ Events ---------------------------------- //

/// @dev Emitted when the `transferOwnership()` function is called with the parameters of the function.
event MockTransferOwnershipCalled(
address newOwner
Expand All @@ -23,12 +33,47 @@ contract CompoundAgentMock {
uint256 configureAdminCallCounter
);

/// @dev Imitates the same-name function of the CompoundAgent interface. Just emits an event about the call.
/// @dev Emitted when the `redeemUnderlying()` function is called with the parameters of the function
event MockRedeemUnderlyingCalled(
uint256 redeemAmount
);

// ------------------ Constructor ----------------------------- //

/**
* @dev The constructor of the contract.
* @param underlyingToken_ The address of the underlying token.
*/
constructor(address underlyingToken_) {
underlyingToken = underlyingToken_;
}

// ------------------ Functions -------------------------------- //

/**
* @dev Imitates the same-name function of the CompoundAgent smart-contract.
*
* Just emits an event about the call and manage allowance for token transfers from the contract.
*
* @param newOwner The address of the new owner.
*/
function transferOwnership(address newOwner) external {
emit MockTransferOwnershipCalled(newOwner);
address oldOwner = owner;
owner = newOwner;
if (oldOwner != address(0)) {
ERC20TokenMock(underlyingToken).approve(oldOwner, 0);
}
if (newOwner != address(0)) {
ERC20TokenMock(underlyingToken).approve(newOwner, type(uint256).max);
}
}

/// @dev Imitates the same-name function of the CompoundAgent interface. Just emits an event about the call.
/**
* @dev Imitates the same-name function of the CompoundAgent smart-contract. Just emits an event about the call.
* @param account The address of the account to configure.
* @param newStatus The new status of the account.
*/
function configureAdmin(
address account,
bool newStatus
Expand All @@ -40,4 +85,13 @@ contract CompoundAgentMock {
configureAdminCallCounter
);
}

/**
* @dev Imitates the same-name function of the CompoundAgent smart-contract.
* @param redeemAmount The amount of the underlying token to redeem.
*/
function redeemUnderlying(uint256 redeemAmount) external {
emit MockRedeemUnderlyingCalled(redeemAmount);
ERC20TokenMock(underlyingToken).mint(address(this), redeemAmount);
}
}
Loading

0 comments on commit b31ae14

Please sign in to comment.