Skip to content

Commit

Permalink
bug fixes and improvements
Browse files Browse the repository at this point in the history
  • Loading branch information
BkChoy committed Feb 20, 2024
1 parent 31729fd commit e7162be
Show file tree
Hide file tree
Showing 18 changed files with 192 additions and 54 deletions.
5 changes: 5 additions & 0 deletions contracts/core/ccip/SDLPoolCCIPControllerPrimary.sol
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,11 @@ interface ISDLPoolPrimary is ISDLPool {
function handleIncomingUpdate(uint256 _numNewRESDLTokens, int256 _totalRESDLSupplyChange) external returns (uint256);
}

/**
* @title SDL Pool CCIP Controller Secondary
* @notice Acts as interface between CCIP and primary SDL Pool
* @dev deployed only on primary chain
*/
contract SDLPoolCCIPControllerPrimary is SDLPoolCCIPController {
using SafeERC20 for IERC20;

Expand Down
10 changes: 9 additions & 1 deletion contracts/core/ccip/SDLPoolCCIPControllerSecondary.sol
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,12 @@ interface ISDLPoolSecondary is ISDLPool {
function shouldUpdate() external view returns (bool);
}

/**
* @title SDL Pool CCIP Controller Secondary
* @notice Acts as interface between CCIP and secondary SDL Pools
* @dev deployed on secondary chains, should always hold a small protocol owned reSDL
* position to negate certain edge cases
*/
contract SDLPoolCCIPControllerSecondary is SDLPoolCCIPController {
using SafeERC20 for IERC20;

Expand Down Expand Up @@ -72,16 +78,18 @@ contract SDLPoolCCIPControllerSecondary is SDLPoolCCIPController {

/**
* @notice Handles the outgoing transfer of an reSDL token to the primary chain
* @param _destinationChainSelector id of the destination chain
* @param _sender sender of the transfer
* @param _tokenId id of token
* @return the destination address
* @return the token being transferred
**/
function handleOutgoingRESDL(
uint64,
uint64 _destinationChainSelector,
address _sender,
uint256 _tokenId
) external override onlyBridge returns (address, ISDLPool.RESDLToken memory) {
if (_destinationChainSelector != primaryChainSelector) revert InvalidDestination();
return (primaryChainDestination, ISDLPoolSecondary(sdlPool).handleOutgoingRESDL(_sender, _tokenId, address(this)));
}

Expand Down
32 changes: 20 additions & 12 deletions contracts/core/ccip/WrappedTokenBridge.sol
Original file line number Diff line number Diff line change
Expand Up @@ -3,11 +3,10 @@ pragma solidity 0.8.15;

import {IRouterClient} from "@chainlink/contracts-ccip/src/v0.8/ccip/interfaces/IRouterClient.sol";
import {Client} from "@chainlink/contracts-ccip/src/v0.8/ccip/libraries/Client.sol";
import {CCIPReceiver} from "@chainlink/contracts-ccip/src/v0.8/ccip/applications/CCIPReceiver.sol";
import "@openzeppelin/contracts/access/Ownable.sol";
import "@openzeppelin/contracts/token/ERC20/utils/SafeERC20.sol";

import "../interfaces/IWrappedLST.sol";
import "./base/CCIPReceiver.sol";

/**
* @title Wrapped token bridge
Expand All @@ -16,7 +15,7 @@ import "../interfaces/IWrappedLST.sol";
* - can wrap tokens and initiate a CCIP transfer of the wrapped tokens to a destination chain
* - can receive a CCIP transfer of wrapped tokens, unwrap them, and send them to the receiver
*/
contract WrappedTokenBridge is Ownable, CCIPReceiver {
contract WrappedTokenBridge is CCIPReceiver {
using SafeERC20 for IERC20;

IERC20 linkToken;
Expand Down Expand Up @@ -119,30 +118,39 @@ contract WrappedTokenBridge is Ownable, CCIPReceiver {
/**
* @notice Returns the current fee for a token transfer
* @param _destinationChainSelector id of destination chain
* @param _amount amount of tokens to transfer
* @param _payNative whether fee should be paid natively or with LINK
* @return fee current fee
**/
function getFee(uint64 _destinationChainSelector, bool _payNative) external view returns (uint256) {
function getFee(
uint64 _destinationChainSelector,
uint256 _amount,
bool _payNative
) external view returns (uint256) {
Client.EVM2AnyMessage memory evm2AnyMessage = _buildCCIPMessage(
address(this),
1000 ether,
_amount,
_payNative ? address(0) : address(linkToken)
);

return IRouterClient(this.getRouter()).getFee(_destinationChainSelector, evm2AnyMessage);
}

/**
* @notice Recovers tokens that were accidentally sent to this contract
* @param _tokens list of tokens to recover
* @param _receiver address to receive recovered tokens
* @notice Withdraws tokens held by this contract
* @param _tokens list of tokens to withdraw
* @param _amounts list of corresponding amounts to withdraw
* @param _receiver address to receive tokens
**/
function recoverTokens(address[] calldata _tokens, address _receiver) external onlyOwner {
function recoverTokens(
address[] calldata _tokens,
uint256[] calldata _amounts,
address _receiver
) external onlyOwner {
if (_receiver == address(0)) revert InvalidReceiver();

for (uint256 i = 0; i < _tokens.length; ++i) {
IERC20 tokenToTransfer = IERC20(_tokens[i]);
tokenToTransfer.safeTransfer(_receiver, tokenToTransfer.balanceOf(address(this)));
IERC20(_tokens[i]).safeTransfer(_receiver, _amounts[i]);
}
}

Expand Down Expand Up @@ -220,7 +228,7 @@ contract WrappedTokenBridge is Ownable, CCIPReceiver {
receiver: abi.encode(_receiver),
data: "",
tokenAmounts: tokenAmounts,
extraArgs: "0x",
extraArgs: Client._argsToBytes(Client.EVMExtraArgsV1({gasLimit: 0})),
feeToken: _feeTokenAddress
});

Expand Down
66 changes: 66 additions & 0 deletions contracts/core/ccip/base/CCIPReceiver.sol
Original file line number Diff line number Diff line change
@@ -0,0 +1,66 @@
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.15;

import {IAny2EVMMessageReceiver} from "@chainlink/contracts-ccip/src/v0.8/ccip/interfaces/IAny2EVMMessageReceiver.sol";
import {Client} from "@chainlink/contracts-ccip/src/v0.8/ccip/libraries/Client.sol";
import {IERC165} from "@openzeppelin/contracts/utils/introspection/IERC165.sol";
import "@openzeppelin/contracts/access/Ownable.sol";

/// @title CCIPReceiver - Base contract for CCIP applications that can receive messages.
/// @dev copied from https://github.com/smartcontractkit and modified to make i_router settable
abstract contract CCIPReceiver is IAny2EVMMessageReceiver, IERC165, Ownable {
address internal i_router;

constructor(address _router) {
if (_router == address(0)) revert InvalidRouter(address(0));
i_router = _router;
}

/// @notice IERC165 supports an interfaceId
/// @param _interfaceId The interfaceId to check
/// @return true if the interfaceId is supported
/// @dev Should indicate whether the contract implements IAny2EVMMessageReceiver
/// e.g. return interfaceId == type(IAny2EVMMessageReceiver).interfaceId || interfaceId == type(IERC165).interfaceId
/// This allows CCIP to check if ccipReceive is available before calling it.
/// If this returns false or reverts, only tokens are transferred to the receiver.
/// If this returns true, tokens are transferred and ccipReceive is called atomically.
/// Additionally, if the receiver address does not have code associated with
/// it at the time of execution (EXTCODESIZE returns 0), only tokens will be transferred.
function supportsInterface(bytes4 _interfaceId) public pure virtual override returns (bool) {
return _interfaceId == type(IAny2EVMMessageReceiver).interfaceId || _interfaceId == type(IERC165).interfaceId;
}

/// @inheritdoc IAny2EVMMessageReceiver
function ccipReceive(Client.Any2EVMMessage calldata _message) external virtual override onlyRouter {
_ccipReceive(_message);
}

/// @notice Override this function in your implementation.
/// @param _message Any2EVMMessage
function _ccipReceive(Client.Any2EVMMessage memory _message) internal virtual;

/////////////////////////////////////////////////////////////////////
// Plumbing
/////////////////////////////////////////////////////////////////////

/// @notice Return the current router
/// @return i_router address
function getRouter() public view returns (address) {
return address(i_router);
}

/// @notice Sets the router
/// @param _router router address
function setRouter(address _router) external onlyOwner {
if (_router == address(0)) revert InvalidRouter(address(0));
i_router = _router;
}

error InvalidRouter(address router);

/// @dev only calls from the set router are accepted.
modifier onlyRouter() {
if (msg.sender != address(i_router)) revert InvalidRouter(msg.sender);
_;
}
}
25 changes: 16 additions & 9 deletions contracts/core/ccip/base/SDLPoolCCIPController.sol
Original file line number Diff line number Diff line change
Expand Up @@ -3,14 +3,17 @@ pragma solidity 0.8.15;

import {IRouterClient} from "@chainlink/contracts-ccip/src/v0.8/ccip/interfaces/IRouterClient.sol";
import {Client} from "@chainlink/contracts-ccip/src/v0.8/ccip/libraries/Client.sol";
import {CCIPReceiver} from "@chainlink/contracts-ccip/src/v0.8/ccip/applications/CCIPReceiver.sol";
import "@openzeppelin/contracts/access/Ownable.sol";
import "@openzeppelin/contracts/token/ERC20/utils/SafeERC20.sol";

import "../../interfaces/IRESDLTokenBridge.sol";
import "../../interfaces/ISDLPool.sol";
import "./CCIPReceiver.sol";

abstract contract SDLPoolCCIPController is Ownable, CCIPReceiver {
/**
* @title SDL Pool CCIP Controller
* @notice Base contract for SDL Pool CCIP controllers
*/
abstract contract SDLPoolCCIPController is CCIPReceiver {
using SafeERC20 for IERC20;

IERC20 public immutable linkToken;
Expand Down Expand Up @@ -110,16 +113,20 @@ abstract contract SDLPoolCCIPController is Ownable, CCIPReceiver {
}

/**
* @notice Recovers tokens that were accidentally sent to this contract
* @param _tokens list of tokens to recover
* @param _receiver address to receive recovered tokens
* @notice Withdraws tokens held by this contract
* @param _tokens list of tokens to withdraw
* @param _amounts list of corresponding amounts to withdraw
* @param _receiver address to receive tokens
**/
function recoverTokens(address[] calldata _tokens, address _receiver) external onlyOwner {
function recoverTokens(
address[] calldata _tokens,
uint256[] calldata _amounts,
address _receiver
) external onlyOwner {
if (_receiver == address(0)) revert InvalidReceiver();

for (uint256 i = 0; i < _tokens.length; ++i) {
IERC20 tokenToTransfer = IERC20(_tokens[i]);
tokenToTransfer.safeTransfer(_receiver, tokenToTransfer.balanceOf(address(this)));
IERC20(_tokens[i]).safeTransfer(_receiver, _amounts[i]);
}
}

Expand Down
28 changes: 23 additions & 5 deletions contracts/core/sdlPool/LinearBoostController.sol
Original file line number Diff line number Diff line change
Expand Up @@ -8,20 +8,28 @@ import "@openzeppelin/contracts/access/Ownable.sol";
* @notice Handles boost calculations
*/
contract LinearBoostController is Ownable {
uint64 public minLockingDuration;
uint64 public maxLockingDuration;
uint64 public maxBoost;

event SetMaxLockingDuration(uint256 _maxLockingDuration);
event SetMaxBoost(uint256 _maxBoost);
event SetMinLockingDuration(uint64 _minLockingDuration);
event SetMaxLockingDuration(uint64 _maxLockingDuration);
event SetMaxBoost(uint64 _maxBoost);

error MaxLockingDurationExceeded();
error InvalidLockingDuration();

/**
* @notice initializes the contract state
* @param _minLockingDuration minimum non-zero locking duration in seconds
* @param _maxLockingDuration maximum locking duration in seconds
* @param _maxBoost maximum boost multiplier
*/
constructor(uint64 _maxLockingDuration, uint64 _maxBoost) {
constructor(
uint64 _minLockingDuration,
uint64 _maxLockingDuration,
uint64 _maxBoost
) {
minLockingDuration = _minLockingDuration;
maxLockingDuration = _maxLockingDuration;
maxBoost = _maxBoost;
}
Expand All @@ -34,10 +42,20 @@ contract LinearBoostController is Ownable {
* @return amount of boost balance received in addition to the unboosted balance
*/
function getBoostAmount(uint256 _amount, uint64 _lockingDuration) external view returns (uint256) {
if (_lockingDuration > maxLockingDuration) revert MaxLockingDurationExceeded();
if ((_lockingDuration != 0 && _lockingDuration < minLockingDuration) || _lockingDuration > maxLockingDuration)
revert InvalidLockingDuration();
return (_amount * uint256(maxBoost) * uint256(_lockingDuration)) / uint256(maxLockingDuration);
}

/**
* @notice sets the minimum non-zero locking duration
* @param _minLockingDuration min non-zero locking duration in seconds
*/
function setMinLockingDuration(uint64 _minLockingDuration) external onlyOwner {
minLockingDuration = _minLockingDuration;
emit SetMinLockingDuration(_minLockingDuration);
}

/**
* @notice sets the maximum locking duration
* @param _maxLockingDuration max locking duration in seconds
Expand Down
5 changes: 4 additions & 1 deletion contracts/core/sdlPool/SDLPoolPrimary.sol
Original file line number Diff line number Diff line change
Expand Up @@ -33,10 +33,11 @@ contract SDLPoolPrimary is SDLPool {
address _sdlToken,
address _boostController
) public reinitializer(2) {
if (delegatorPool == address(0)) {
if (ccipController == address(0)) {
__SDLPoolBase_init(_name, _symbol, _sdlToken, _boostController);
} else {
delegatorPool = ccipController;
delete ccipController;
}
}

Expand Down Expand Up @@ -186,6 +187,7 @@ contract SDLPoolPrimary is SDLPool {
delete locks[_lockId].amount;
delete lockOwners[_lockId];
balances[_sender] -= 1;
delete tokenApprovals[_lockId];

uint256 totalAmount = lock.amount + lock.boostAmount;
effectiveBalances[_sender] -= totalAmount;
Expand Down Expand Up @@ -231,6 +233,7 @@ contract SDLPoolPrimary is SDLPool {
function handleIncomingUpdate(uint256 _numNewRESDLTokens, int256 _totalRESDLSupplyChange)
external
onlyCCIPController
updateRewards(ccipController)
returns (uint256)
{
uint256 mintStartIndex;
Expand Down
14 changes: 9 additions & 5 deletions contracts/core/sdlPool/SDLPoolSecondary.sol
Original file line number Diff line number Diff line change
Expand Up @@ -49,6 +49,7 @@ contract SDLPoolSecondary is SDLPool {
error UpdateInProgress();
error NoUpdateInProgress();
error TooManyQueuedLocks();
error LockWithdrawn();

/// @custom:oz-upgrades-unsafe-allow constructor
constructor() {
Expand Down Expand Up @@ -268,6 +269,7 @@ contract SDLPoolSecondary is SDLPool {
delete locks[_lockId].amount;
delete lockOwners[_lockId];
balances[_sender] -= 1;
delete tokenApprovals[_lockId];

uint256 totalAmount = lock.amount + lock.boostAmount;
effectiveBalances[_sender] -= totalAmount;
Expand Down Expand Up @@ -432,6 +434,8 @@ contract SDLPoolSecondary is SDLPool {
uint64 _lockingDuration
) internal onlyLockOwner(_lockId, _owner) {
Lock memory lock = _getQueuedLockState(_lockId);
if (lock.amount == 0) revert LockWithdrawn();

LockUpdate memory lockUpdate = LockUpdate(updateBatchIndex, _updateLock(lock, _amount, _lockingDuration));
queuedLockUpdates[_lockId].push(lockUpdate);
queuedRESDLSupplyChange +=
Expand Down Expand Up @@ -471,21 +475,21 @@ contract SDLPoolSecondary is SDLPool {
delete locks[lockId];
delete lockOwners[lockId];
balances[_owner] -= 1;
if (tokenApprovals[lockId] != address(0)) delete tokenApprovals[lockId];
delete tokenApprovals[lockId];
emit Transfer(_owner, address(0), lockId);
} else {
locks[lockId].amount = updateLockState.amount;
}
sdlToken.safeTransfer(_owner, uint256(-1 * baseAmountDiff));
} else if (boostAmountDiff < 0) {
} else if (boostAmountDiff < 0 && updateLockState.boostAmount == 0) {
locks[lockId].expiry = updateLockState.expiry;
locks[lockId].boostAmount = 0;
emit InitiateUnlock(_owner, lockId, updateLockState.expiry);
} else {
locks[lockId] = updateLockState;
uint256 totalDiff = uint256(baseAmountDiff + boostAmountDiff);
effectiveBalances[_owner] += totalDiff;
totalEffectiveBalance += totalDiff;
int256 totalDiff = baseAmountDiff + boostAmountDiff;
effectiveBalances[_owner] = uint256(int256(effectiveBalances[_owner]) + totalDiff);
totalEffectiveBalance = uint256(int256(totalEffectiveBalance) + totalDiff);
emit UpdateLock(
_owner,
lockId,
Expand Down
Loading

0 comments on commit e7162be

Please sign in to comment.