Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

ET factories for stablecoins #58

Open
wants to merge 27 commits into
base: develop
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from 1 commit
Commits
Show all changes
27 commits
Select commit Hold shift + click to select a range
a3e9ac3
feat: modify top up factories to work with different stablecoins
rkolpakov Oct 6, 2023
a7eafa7
feat: modify top up factories to work with different stablecoins
rkolpakov Oct 9, 2023
0b1e3b3
chore: bump poetry lock brownie version to 1.19.3
rkolpakov Oct 9, 2023
425f4a2
chore: update readme
rkolpakov Oct 9, 2023
7e467c2
fix: make normalizeAmount safe for external callers
rkolpakov Oct 9, 2023
733ef7c
feat: pre deploy preparations
rkolpakov Oct 19, 2023
feb440d
fix: missing comment parameter
rkolpakov Oct 24, 2023
480c3f1
fix: pull fix from develop
rkolpakov Oct 24, 2023
9e4f813
fix: make allowed recipients factory functions external
rkolpakov Oct 24, 2023
f4efe8e
fix: add zero validation top up evm factory
rkolpakov Oct 24, 2023
3b718ad
fix: partially change memory params to calldata
rkolpakov Oct 24, 2023
52b1b1d
fix: remove redundant asserts in builder
rkolpakov Oct 24, 2023
35b01d1
fix: review fixes
rkolpakov Oct 26, 2023
19e760e
fix: test fix
rkolpakov Oct 26, 2023
bf6c9c5
fix: deploy fixes
rkolpakov Oct 26, 2023
1c8f7c1
test: bytecode verification
rkolpakov Oct 30, 2023
f6bf883
fix start fork
bulbozaur Oct 30, 2023
f081888
settings for holesky added
kate-aleksseeva Sep 27, 2024
d8fcc33
ugrade eth-brownie and dependencies
zuzueeka Sep 27, 2024
00305fe
change tokens input format
zuzueeka Sep 27, 2024
afbd0df
Merge pull request #77 from lidofinance/fix/feature/top-up-allowed-to…
zuzueeka Oct 23, 2024
e385d5d
added functionality for contracts deployment using an existing tokens…
kate-aleksseeva Oct 30, 2024
b4c77ca
deployAllowedRecipientsRegistry usage fixed
kate-aleksseeva Oct 30, 2024
c176cb1
deployAllowedRecipientsRegistry usage fixed
kate-aleksseeva Oct 30, 2024
e1f84d6
comments corrected
kate-aleksseeva Oct 30, 2024
363e6d6
Merge branch 'feature/top-up-allowed-tokens' into feature/top-up-allo…
kate-aleksseeva Nov 4, 2024
cb09953
Merge pull request #79 from lidofinance/feature/top-up-allowed-tokens…
kate-aleksseeva Nov 4, 2024
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
236 changes: 115 additions & 121 deletions contracts/AllowedRecipientsBuilder.sol
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

there some memory[] external func args left still

Large diffs are not rendered by default.

33 changes: 33 additions & 0 deletions contracts/AllowedRecipientsFactory.sol
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@ import "./EVMScriptFactories/AddAllowedRecipient.sol";
import "./EVMScriptFactories/RemoveAllowedRecipient.sol";
import "./EVMScriptFactories/TopUpAllowedRecipients.sol";
import "./AllowedRecipientsRegistry.sol";
import "./AllowedTokensRegistry.sol";

/// @author bulbozaur
/// @notice Factory for Allowed Recipient Easy Track contracts
Expand All @@ -22,11 +23,20 @@ contract AllowedRecipientsFactory {
IBokkyPooBahsDateTimeContract bokkyPooBahsDateTimeContract
);

event AllowedTokensRegistryDeployed(
address indexed creator,
address indexed allowedTokensRegistry,
address _defaultAdmin,
address[] addTokenToAllowedListRoleHolders,
address[] removeTokenFromAllowedListRoleHolders
);

event TopUpAllowedRecipientsDeployed(
address indexed creator,
address indexed topUpAllowedRecipients,
address trustedCaller,
address allowedRecipientsRegistry,
address allowedTokenssRegistry,
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

typo tokenss

address finance,
address token,
address easyTrack
Expand Down Expand Up @@ -75,16 +85,38 @@ contract AllowedRecipientsFactory {
);
}

function deployAllowedTokensRegistry(
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

not a strong point: maybe semantically, it could have been a dedicated factory for the tokens registry (since doesn't affect allowed recipients)

image

address _defaultAdmin,
address[] memory _addTokensToAllowedListRoleHolders,
address[] memory _removeTokensFromAllowedListRoleHolders
) public returns (AllowedTokensRegistry registry) {
registry = new AllowedTokensRegistry(
_defaultAdmin,
_addTokensToAllowedListRoleHolders,
_removeTokensFromAllowedListRoleHolders
);

emit AllowedTokensRegistryDeployed(
msg.sender,
address(registry),
_defaultAdmin,
_addTokensToAllowedListRoleHolders,
_removeTokensFromAllowedListRoleHolders
);
}
Fixed Show fixed Hide fixed

function deployTopUpAllowedRecipients(
address _trustedCaller,
address _allowedRecipientsRegistry,
address _allowedTokensRegistry,
address _token,
address _finance,
address _easyTrack
) public returns (TopUpAllowedRecipients topUpAllowedRecipients) {
topUpAllowedRecipients = new TopUpAllowedRecipients(
_trustedCaller,
_allowedRecipientsRegistry,
_allowedTokensRegistry,
_finance,
_token,
_easyTrack
Expand All @@ -95,6 +127,7 @@ contract AllowedRecipientsFactory {
address(topUpAllowedRecipients),
_trustedCaller,
_allowedRecipientsRegistry,
_allowedTokensRegistry,
_finance,
_token,
_easyTrack
Expand Down
127 changes: 127 additions & 0 deletions contracts/AllowedTokensRegistry.sol
Original file line number Diff line number Diff line change
@@ -0,0 +1,127 @@
// SPDX-FileCopyrightText: 2022 Lido <[email protected]>
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

old copyright (not only in this file)

// SPDX-License-Identifier: GPL-3.0
pragma solidity ^0.8.4;

import "OpenZeppelin/[email protected]/contracts/access/AccessControl.sol";
import "OpenZeppelin/[email protected]/contracts/token/ERC20/extensions/IERC20Metadata.sol";

contract AllowedTokensRegistry is AccessControl {
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

why not AccessControlEnumerable?

// -------------
// 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
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

the comment is misleading a bit (the definition is close to 'total supply')

uint8 public constant PRECISION = 18;
rkolpakov marked this conversation as resolved.
Show resolved Hide resolved

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
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

typo 'amout'

function normalizeAmount(uint256 _tokenAmount, address _token) external view returns (uint256) {
require(_token != address(0), ERROR_TOKEN_ADDRESS_IS_ZERO);

uint8 tokenDecimals = IERC20Metadata(_token).decimals();
rkolpakov marked this conversation as resolved.
Show resolved Hide resolved

if (tokenDecimals == PRECISION) return _tokenAmount;
if (tokenDecimals > PRECISION) {
uint256 difference = tokenDecimals - PRECISION;
rkolpakov marked this conversation as resolved.
Show resolved Hide resolved
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;
}
}
28 changes: 13 additions & 15 deletions contracts/EVMScriptFactories/TopUpAllowedRecipients.sol
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@ pragma solidity ^0.8.4;

import "../TrustedCaller.sol";
import "../AllowedRecipientsRegistry.sol";
import "../AllowedTokensRegistry.sol";
import "../interfaces/IFinance.sol";
import "../libraries/EVMScriptCreator.sol";
import "../interfaces/IEVMScriptFactory.sol";
Expand All @@ -18,6 +19,7 @@ contract TopUpAllowedRecipients is TrustedCaller, IEVMScriptFactory {
string private constant ERROR_LENGTH_MISMATCH = "LENGTH_MISMATCH";
string private constant ERROR_EMPTY_DATA = "EMPTY_DATA";
string private constant ERROR_ZERO_AMOUNT = "ZERO_AMOUNT";
string private constant ERROR_TOKEN_NOT_ALLOWED = "TOKEN_NOT_ALLOWED";
string private constant ERROR_RECIPIENT_NOT_ALLOWED = "RECIPIENT_NOT_ALLOWED";
string private constant ERROR_SUM_EXCEEDS_SPENDABLE_BALANCE = "SUM_EXCEEDS_SPENDABLE_BALANCE";

Expand All @@ -37,6 +39,9 @@ contract TopUpAllowedRecipients is TrustedCaller, IEVMScriptFactory {
/// @notice Address of AllowedRecipientsRegistry contract
AllowedRecipientsRegistry public allowedRecipientsRegistry;
rkolpakov marked this conversation as resolved.
Show resolved Hide resolved

/// @notice Address of AllowedTokenssRegistry contract
rkolpakov marked this conversation as resolved.
Show resolved Hide resolved
AllowedTokensRegistry public allowedTokensRegistry;
rkolpakov marked this conversation as resolved.
Show resolved Hide resolved

// -------------
// CONSTRUCTOR
// -------------
Expand All @@ -50,13 +55,15 @@ contract TopUpAllowedRecipients is TrustedCaller, IEVMScriptFactory {
constructor(
address _trustedCaller,
address _allowedRecipientsRegistry,
address _allowedTokensRegistry,
address _finance,
address _token,
rkolpakov marked this conversation as resolved.
Show resolved Hide resolved
address _easyTrack
) TrustedCaller(_trustedCaller) {
finance = IFinance(_finance);
token = _token;
allowedRecipientsRegistry = AllowedRecipientsRegistry(_allowedRecipientsRegistry);
allowedTokensRegistry = AllowedTokensRegistry(_allowedTokensRegistry);
easyTrack = EasyTrack(_easyTrack);
}

Expand All @@ -77,9 +84,7 @@ contract TopUpAllowedRecipients is TrustedCaller, IEVMScriptFactory {
onlyTrustedCaller(_creator)
returns (bytes memory)
{
(address[] memory recipients, uint256[] memory amounts) = _decodeEVMScriptCallData(
_evmScriptCallData
);
(address[] memory recipients, uint256[] memory amounts) = _decodeEVMScriptCallData(_evmScriptCallData);
uint256 totalAmount = _validateEVMScriptCallData(recipients, amounts);

address[] memory to = new address[](recipients.length + 1);
Expand All @@ -88,17 +93,12 @@ contract TopUpAllowedRecipients is TrustedCaller, IEVMScriptFactory {

to[0] = address(allowedRecipientsRegistry);
methodIds[0] = allowedRecipientsRegistry.updateSpentAmount.selector;
evmScriptsCalldata[0] = abi.encode(totalAmount);
evmScriptsCalldata[0] = abi.encode(allowedTokensRegistry.normalizeAmount(totalAmount, token));

for (uint256 i = 0; i < recipients.length; ++i) {
to[i + 1] = address(finance);
methodIds[i + 1] = finance.newImmediatePayment.selector;
evmScriptsCalldata[i + 1] = abi.encode(
token,
recipients[i],
amounts[i],
"Easy Track: top up recipient"
);
evmScriptsCalldata[i + 1] = abi.encode(token, recipients[i], amounts[i], "Easy Track: top up recipient");
}

return EVMScriptCreator.createEVMScript(to, methodIds, evmScriptsCalldata);
Expand Down Expand Up @@ -129,17 +129,15 @@ contract TopUpAllowedRecipients is TrustedCaller, IEVMScriptFactory {
{
require(_amounts.length == _recipients.length, ERROR_LENGTH_MISMATCH);
require(_recipients.length > 0, ERROR_EMPTY_DATA);
require(allowedTokensRegistry.isTokenAllowed(token), ERROR_TOKEN_NOT_ALLOWED);

for (uint256 i = 0; i < _recipients.length; ++i) {
require(_amounts[i] > 0, ERROR_ZERO_AMOUNT);
require(
allowedRecipientsRegistry.isRecipientAllowed(_recipients[i]),
ERROR_RECIPIENT_NOT_ALLOWED
);
require(allowedRecipientsRegistry.isRecipientAllowed(_recipients[i]), ERROR_RECIPIENT_NOT_ALLOWED);
totalAmount += _amounts[i];
}

_validateSpendableBalance(totalAmount);
_validateSpendableBalance(allowedTokensRegistry.normalizeAmount(totalAmount, token));
rkolpakov marked this conversation as resolved.
Show resolved Hide resolved
}

function _decodeEVMScriptCallData(bytes memory _evmScriptCallData)
Expand Down
13 changes: 13 additions & 0 deletions contracts/test/MockERC20.sol
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
// SPDX-FileCopyrightText: 2021 Lido <[email protected]>
// SPDX-License-Identifier: GPL-3.0

pragma solidity ^0.8.4;

contract MockERC20 {

uint8 public decimals;

constructor(uint8 _decimals) {
decimals = _decimals;
}
}
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);
}
Loading
Loading