-
Notifications
You must be signed in to change notification settings - Fork 6
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
feat: modify top up factories to work with different stablecoins
- Loading branch information
Showing
16 changed files
with
1,139 additions
and
293 deletions.
There are no files selected for viewing
Large diffs are not rendered by default.
Oops, something went wrong.
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
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,127 @@ | ||
// SPDX-FileCopyrightText: 2022 Lido <info@lido.fi> | ||
// SPDX-License-Identifier: GPL-3.0 | ||
pragma solidity ^0.8.4; | ||
|
||
import "OpenZeppelin/openzeppelin-contracts@4.3.2/contracts/access/AccessControl.sol"; | ||
import "OpenZeppelin/openzeppelin-contracts@4.3.2/contracts/token/ERC20/extensions/IERC20Metadata.sol"; | ||
|
||
contract AllowedTokensRegistry is AccessControl { | ||
// ------------- | ||
// EVENTS | ||
// ------------- | ||
event TokenAdded(address indexed _token); | ||
event TokenRemoved(address indexed _token); | ||
|
||
// ------------- | ||
// ROLES | ||
// ------------- | ||
|
||
bytes32 public constant ADD_TOKEN_TO_ALLOWED_LIST_ROLE = keccak256("ADD_TOKEN_TO_ALLOWED_LIST_ROLE"); | ||
bytes32 public constant REMOVE_TOKEN_FROM_ALLOWED_LIST_ROLE = keccak256("REMOVE_TOKEN_FROM_ALLOWED_LIST_ROLE"); | ||
|
||
// ------------- | ||
// ERRORS | ||
// ------------- | ||
string private constant ERROR_TOKEN_ALREADY_ADDED_TO_ALLOWED_LIST = "TOKEN_ALREADY_ADDED_TO_ALLOWED_LIST"; | ||
string private constant ERROR_TOKEN_NOT_FOUND_IN_ALLOWED_LIST = "TOKEN_NOT_FOUND_IN_ALLOWED_LIST"; | ||
string private constant ERROR_TOKEN_ADDRESS_IS_ZERO = "TOKEN_ADDRESS_IS_ZERO"; | ||
|
||
// ------------- | ||
// VARIABLES | ||
// ------------- | ||
/// @dev List of allowed tokens for payouts | ||
address[] public allowedTokens; | ||
|
||
// Position of the address in the `allowedTokens` array, | ||
// plus 1 because index 0 means a value is not in the set. | ||
mapping(address => uint256) private allowedTokenIndices; | ||
|
||
/// @notice Precise number of tokens in the system | ||
uint8 public constant PRECISION = 18; | ||
|
||
constructor( | ||
address _admin, | ||
address[] memory _addTokenToAllowedListRoleHolders, | ||
address[] memory _removeTokenFromAllowedListRoleHolders | ||
) { | ||
_setupRole(DEFAULT_ADMIN_ROLE, _admin); | ||
for (uint256 i = 0; i < _addTokenToAllowedListRoleHolders.length; i++) { | ||
_setupRole(ADD_TOKEN_TO_ALLOWED_LIST_ROLE, _addTokenToAllowedListRoleHolders[i]); | ||
} | ||
for (uint256 i = 0; i < _removeTokenFromAllowedListRoleHolders.length; i++) { | ||
_setupRole(REMOVE_TOKEN_FROM_ALLOWED_LIST_ROLE, _removeTokenFromAllowedListRoleHolders[i]); | ||
} | ||
} | ||
|
||
// ------------- | ||
// EXTERNAL METHODS | ||
// ------------- | ||
|
||
/// @notice Adds address to list of allowed tokens for payouts | ||
function addToken(address _token) external onlyRole(ADD_TOKEN_TO_ALLOWED_LIST_ROLE) { | ||
require(_token != address(0), ERROR_TOKEN_ADDRESS_IS_ZERO); | ||
require(allowedTokenIndices[_token] == 0, ERROR_TOKEN_ALREADY_ADDED_TO_ALLOWED_LIST); | ||
|
||
allowedTokens.push(_token); | ||
allowedTokenIndices[_token] = allowedTokens.length; | ||
emit TokenAdded(_token); | ||
} | ||
|
||
/// @notice Removes address from list of allowed tokens for payouts | ||
/// @dev To delete an allowed token from the allowedTokens array in O(1), | ||
/// we swap the element to delete with the last one in the array, | ||
/// and then remove the last element (sometimes called as 'swap and pop'). | ||
function removeToken(address _token) external onlyRole(REMOVE_TOKEN_FROM_ALLOWED_LIST_ROLE) { | ||
uint256 index = _getAllowedTokenIndex(_token); | ||
uint256 lastIndex = allowedTokens.length - 1; | ||
|
||
if (index != lastIndex) { | ||
address lastAllowedToken = allowedTokens[lastIndex]; | ||
allowedTokens[index] = lastAllowedToken; | ||
allowedTokenIndices[lastAllowedToken] = index + 1; | ||
} | ||
|
||
allowedTokens.pop(); | ||
delete allowedTokenIndices[_token]; | ||
emit TokenRemoved(_token); | ||
} | ||
|
||
/// @notice Returns if passed address is listed as allowed token in the registry | ||
function isTokenAllowed(address _token) external view returns (bool) { | ||
return allowedTokenIndices[_token] > 0; | ||
} | ||
|
||
/// @notice Returns current list of allowed tokens | ||
function getAllowedTokens() external view returns (address[] memory) { | ||
return allowedTokens; | ||
} | ||
|
||
/// @notice Transforms amout from token format to precise format | ||
function normalizeAmount(uint256 _tokenAmount, address _token) external view returns (uint256) { | ||
require(_token != address(0), ERROR_TOKEN_ADDRESS_IS_ZERO); | ||
|
||
uint8 tokenDecimals = IERC20Metadata(_token).decimals(); | ||
|
||
if (tokenDecimals == PRECISION) return _tokenAmount; | ||
if (tokenDecimals > PRECISION) { | ||
uint256 difference = tokenDecimals - PRECISION; | ||
uint256 remainder = _tokenAmount % (10 ** difference); | ||
uint256 quotient = _tokenAmount / (10 ** difference); | ||
if (remainder > 0) { | ||
quotient += 1; | ||
} | ||
return quotient; | ||
} | ||
return _tokenAmount * 10 ** (PRECISION - tokenDecimals); | ||
} | ||
|
||
// ------------------ | ||
// PRIVATE METHODS | ||
// ------------------ | ||
|
||
function _getAllowedTokenIndex(address _token) private view returns (uint256 _index) { | ||
_index = allowedTokenIndices[_token]; | ||
require(_index > 0, ERROR_TOKEN_NOT_FOUND_IN_ALLOWED_LIST); | ||
_index -= 1; | ||
} | ||
} |
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
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,13 @@ | ||
// SPDX-FileCopyrightText: 2021 Lido <info@lido.fi> | ||
// SPDX-License-Identifier: GPL-3.0 | ||
|
||
pragma solidity ^0.8.4; | ||
|
||
contract MockERC20 { | ||
|
||
uint8 public decimals; | ||
|
||
constructor(uint8 _decimals) { | ||
decimals = _decimals; | ||
} | ||
} |
77 changes: 77 additions & 0 deletions
77
dependencies/OpenZeppelin/openzeppelin-contracts@4.3.2/contracts/token/ERC20/IERC20.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,77 @@ | ||
// SPDX-License-Identifier: MIT | ||
|
||
pragma solidity ^0.8.0; | ||
|
||
/** | ||
* @dev Interface of the ERC20 standard as defined in the EIP. | ||
*/ | ||
interface IERC20 { | ||
/** | ||
* @dev Returns the amount of tokens in existence. | ||
*/ | ||
function totalSupply() external view returns (uint256); | ||
|
||
/** | ||
* @dev Returns the amount of tokens owned by `account`. | ||
*/ | ||
function balanceOf(address account) external view returns (uint256); | ||
|
||
/** | ||
* @dev Moves `amount` tokens from the caller's account to `recipient`. | ||
* | ||
* Returns a boolean value indicating whether the operation succeeded. | ||
* | ||
* Emits a {Transfer} event. | ||
*/ | ||
function transfer(address recipient, uint256 amount) external returns (bool); | ||
|
||
/** | ||
* @dev Returns the remaining number of tokens that `spender` will be | ||
* allowed to spend on behalf of `owner` through {transferFrom}. This is | ||
* zero by default. | ||
* | ||
* This value changes when {approve} or {transferFrom} are called. | ||
*/ | ||
function allowance(address owner, address spender) external view returns (uint256); | ||
|
||
/** | ||
* @dev Sets `amount` as the allowance of `spender` over the caller's tokens. | ||
* | ||
* Returns a boolean value indicating whether the operation succeeded. | ||
* | ||
* IMPORTANT: Beware that changing an allowance with this method brings the risk | ||
* that someone may use both the old and the new allowance by unfortunate | ||
* transaction ordering. One possible solution to mitigate this race | ||
* condition is to first reduce the spender's allowance to 0 and set the | ||
* desired value afterwards: | ||
* https://github.com/ethereum/EIPs/issues/20#issuecomment-263524729 | ||
* | ||
* Emits an {Approval} event. | ||
*/ | ||
function approve(address spender, uint256 amount) external returns (bool); | ||
|
||
/** | ||
* @dev Moves `amount` tokens from `sender` to `recipient` using the | ||
* allowance mechanism. `amount` is then deducted from the caller's | ||
* allowance. | ||
* | ||
* Returns a boolean value indicating whether the operation succeeded. | ||
* | ||
* Emits a {Transfer} event. | ||
*/ | ||
function transferFrom(address sender, address recipient, uint256 amount) external returns (bool); | ||
|
||
/** | ||
* @dev Emitted when `value` tokens are moved from one account (`from`) to | ||
* another (`to`). | ||
* | ||
* Note that `value` may be zero. | ||
*/ | ||
event Transfer(address indexed from, address indexed to, uint256 value); | ||
|
||
/** | ||
* @dev Emitted when the allowance of a `spender` for an `owner` is set by | ||
* a call to {approve}. `value` is the new allowance. | ||
*/ | ||
event Approval(address indexed owner, address indexed spender, uint256 value); | ||
} |
Oops, something went wrong.