From e46be79d08611e9f2f345bb61e15874f0565526b Mon Sep 17 00:00:00 2001 From: Miniroman Date: Fri, 14 Jun 2024 06:53:00 +0200 Subject: [PATCH 01/17] Backed token with auto accrual and multiplier --- ...ntationWithMultiplierAndAutoFeeAccrual.sol | 550 ++++++++++++++++++ contracts/ERC20UpgradeableWithMultiplier.sol | 497 ++++++++++++++++ contracts/WrappedBackedToken.sol | 222 +++++++ ...entationWithMultiplierAndAutoFeeAccrual.ts | 245 ++++++++ 4 files changed, 1514 insertions(+) create mode 100644 contracts/BackedTokenImplementationWithMultiplierAndAutoFeeAccrual.sol create mode 100644 contracts/ERC20UpgradeableWithMultiplier.sol create mode 100644 contracts/WrappedBackedToken.sol create mode 100644 test/BackedTokenImplementationWithMultiplierAndAutoFeeAccrual.ts diff --git a/contracts/BackedTokenImplementationWithMultiplierAndAutoFeeAccrual.sol b/contracts/BackedTokenImplementationWithMultiplierAndAutoFeeAccrual.sol new file mode 100644 index 0000000..adcb373 --- /dev/null +++ b/contracts/BackedTokenImplementationWithMultiplierAndAutoFeeAccrual.sol @@ -0,0 +1,550 @@ +/** + * SPDX-License-Identifier: MIT + * + * Copyright (c) 2021-2022 Backed Finance AG + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + * SOFTWARE. + */ + +/** + * Disclaimer and Terms of Use + * + * These ERC-20 tokens have not been registered under the U.S. Securities Act of 1933, as + * amended or with any securities regulatory authority of any State or other jurisdiction + * of the United States and (i) may not be offered, sold or delivered within the United States + * to, or for the account or benefit of U.S. Persons, and (ii) may be offered, sold or otherwise + * delivered at any time only to transferees that are Non-United States Persons (as defined by + * the U.S. Commodities Futures Trading Commission). + * For more information and restrictions please refer to the issuer's [Website](https://www.backedassets.fi/legal-documentation) + */ + +pragma solidity 0.8.9; + +import "@openzeppelin/contracts-upgradeable/access/OwnableUpgradeable.sol"; +import "./ERC20PermitDelegateTransferWithMultiplier.sol"; +import "./SanctionsList.sol"; + +/** + * @dev + * + * This token contract is following the ERC20 standard. + * It inherits ERC20PermitDelegateTransferWithMultiplier.sol, which extends the basic ERC20 to also allow permit, delegateTransfer and delegateTransferShares EIP-712 functionality. + * Enforces Sanctions List via the Chainalysis standard interface. + * The contract contains four roles: + * - A minter, that can mint new tokens. + * - A burner, that can burn its own tokens, or contract's tokens. + * - A pauser, that can pause or restore all transfers in the contract. + * - A multiplierUpdated, that can update value of a multiplier. + * - An owner, that can set the four above, and also the sanctionsList pointer. + * The owner can also set who can use the EIP-712 functionality, either specific accounts via a whitelist, or everyone. + * + */ + +contract BackedTokenImplementationWithMultiplierAndAutoFeeAccrual is + OwnableUpgradeable, + ERC20PermitDelegateTransferWithMultiplier +{ + string public constant VERSION = "1.1.0"; + + // Roles: + address public minter; + address public burner; + address public pauser; + + // EIP-712 Delegate Functionality: + bool public delegateMode; + mapping(address => bool) public delegateWhitelist; + + // Pause: + bool public isPaused; + + // SanctionsList: + SanctionsList public sanctionsList; + + // Terms: + string public terms; + + // V2 + + // Roles: + address public multiplierUpdater; + + // Management Fee + uint256 public lastTimeFeeApplied; + uint256 public feePerPeriod; // in 1e18 precision + uint256 public periodLength; + + // Events: + event NewMinter(address indexed newMinter); + event NewBurner(address indexed newBurner); + event NewPauser(address indexed newPauser); + event NewMultiplierUpdater(address indexed newMultiplierUpdater); + event NewSanctionsList(address indexed newSanctionsList); + event DelegateWhitelistChange( + address indexed whitelistAddress, + bool status + ); + event DelegateModeChange(bool delegateMode); + event PauseModeChange(bool pauseMode); + event NewTerms(string newTerms); + + modifier allowedDelegate() { + require( + delegateMode || delegateWhitelist[_msgSender()], + "BackedToken: Unauthorized delegate" + ); + _; + } + + modifier updateMultiplier() { + (uint256 newMultiplier, uint256 periodsPassed) = getCurrentMultiplier(); + lastTimeFeeApplied = lastTimeFeeApplied + periodLength * periodsPassed; + if (multiplier() != newMultiplier) { + _updateMultiplier(newMultiplier); + } + _; + } + + // constructor, call initializer to lock the implementation instance. + constructor() { + initialize( + "Backed Token Implementation", + "BTI", + block.timestamp, + 24 * 3600 + ); + } + + function initialize( + string memory name_, + string memory symbol_, + uint256 firstFeeAccrualTime_, + uint256 periodLength_ + ) public initializer { + __ERC20_init(name_, symbol_); + __Ownable_init(); + _buildDomainSeparator(); + _setTerms("https://www.backedassets.fi/legal-documentation"); // Default Terms + lastTimeFeeApplied = firstFeeAccrualTime_; + periodLength = periodLength_; + } + + /** + * @dev See {IERC20-balanceOf}. + */ + function balanceOf( + address account + ) public view virtual override returns (uint256) { + (uint256 multiplier, ) = getCurrentMultiplier(); + return (sharesOf(account) * multiplier) / 1e18; + } + + /** + * @dev Retrieves most up to date value of multiplier + * + * Note Probably it should be renamed into multiplier and allow getting stored version of multiplier + * via getStoredMultiplier() method + */ + function getCurrentMultiplier() + public + view + virtual + returns (uint256 newMultiplier, uint256 periodsPassed) + { + periodsPassed = (block.timestamp - lastTimeFeeApplied) / periodLength; + newMultiplier = multiplier(); + if (feePerPeriod >= 0) { + for (uint256 index = 0; index < periodsPassed; index++) { + newMultiplier = (newMultiplier * (1e18 - feePerPeriod)) / 1e18; + } + } + } + + /** + * @dev See {IERC20-totalSupply}. + */ + function totalSupply() public view virtual override returns (uint256) { + (uint256 multiplier, ) = getCurrentMultiplier(); + return _totalShares * multiplier / 1e18; + } + + /** + * @dev Update allowance with a signed permit. Allowed only if + * the sender is whitelisted, or the delegateMode is set to true + * + * @param owner Token owner's address (Authorizer) + * @param spender Spender's address + * @param value Amount of allowance + * @param deadline Expiration time, seconds since the epoch + * @param v v part of the signature + * @param r r part of the signature + * @param s s part of the signature + */ + function permit( + address owner, + address spender, + uint256 value, + uint256 deadline, + uint8 v, + bytes32 r, + bytes32 s + ) public override allowedDelegate { + super.permit(owner, spender, value, deadline, v, r, s); + } + + /** + * @dev Perform an intended transfer on one account's behalf, from another account, + * who actually pays fees for the transaction. Allowed only if the sender + * is whitelisted, or the delegateMode is set to true + * + * @param owner The account that provided the signature and from which the tokens will be taken + * @param to The account that will receive the tokens + * @param value The amount of tokens to transfer + * @param deadline Expiration time, seconds since the epoch + * @param v v part of the signature + * @param r r part of the signature + * @param s s part of the signature + */ + function delegatedTransfer( + address owner, + address to, + uint256 value, + uint256 deadline, + uint8 v, + bytes32 r, + bytes32 s + ) public override allowedDelegate updateMultiplier { + super.delegatedTransfer(owner, to, value, deadline, v, r, s); + } + + /** + * @dev Perform an intended shares transfer on one account's behalf, from another account, + * who actually pays fees for the transaction. Allowed only if the sender + * is whitelisted, or the delegateMode is set to true + * + * @param owner The account that provided the signature and from which the tokens will be taken + * @param to The account that will receive the tokens + * @param value The amount of token shares to transfer + * @param deadline Expiration time, seconds since the epoch + * @param v v part of the signature + * @param r r part of the signature + * @param s s part of the signature + */ + function delegatedTransferShares( + address owner, + address to, + uint256 value, + uint256 deadline, + uint8 v, + bytes32 r, + bytes32 s + ) public virtual override updateMultiplier { + super.delegatedTransferShares(owner, to, value, deadline, v, r, s); + } + + /** + * @dev See {IERC20-transfer}. + * + * Requirements: + * + * - `to` cannot be the zero address. + * - the caller must have a balance of at least `amount`. + */ + function transfer( + address to, + uint256 amount + ) public virtual override updateMultiplier returns (bool) { + return super.transfer(to, amount); + } + + /** + * @dev Transfers underlying shares to destination account + * + * Requirements: + * + * - `to` cannot be the zero address. + * - the caller must have a balance of at least `sharesAmount`. + */ + function transferShares( + address to, + uint256 sharesAmount + ) public virtual override updateMultiplier returns (bool) { + return super.transferShares(to, sharesAmount); + } + + /** + * @dev See {IERC20-transferFrom}. + * + * Emits an {Approval} event indicating the updated allowance. This is not + * required by the EIP. See the note at the beginning of {ERC20}. + * + * NOTE: Does not update the allowance if the current allowance + * is the maximum `uint256`. + * + * Requirements: + * + * - `from` and `to` cannot be the zero address. + * - `from` must have a balance of at least `amount`. + * - the caller must have allowance for ``from``'s tokens of at least + * `amount`. + */ + function transferFrom( + address from, + address to, + uint256 amount + ) public virtual override updateMultiplier returns (bool) { + return this.transferFrom(from, to, amount); + } + + /** + * @dev Function to mint tokens. Allowed only for minter + * + * @param account The address that will receive the minted tokens + * @param amount The amount of tokens to mint + */ + function mint( + address account, + uint256 amount + ) external virtual updateMultiplier { + require(_msgSender() == minter, "BackedToken: Only minter"); + uint256 sharesAmount = getSharesByUnderlyingAmount(amount); + _mintShares(account, sharesAmount); + } + + /** + * @dev Function to burn tokens. Allowed only for burner. The burned tokens + * must be from the burner (msg.sender), or from the contract itself + * + * @param account The account from which the tokens will be burned + * @param amount The amount of tokens to be burned + */ + function burn(address account, uint256 amount) external updateMultiplier { + require(_msgSender() == burner, "BackedToken: Only burner"); + require( + account == _msgSender() || account == address(this), + "BackedToken: Cannot burn account" + ); + uint256 sharesAmount = getSharesByUnderlyingAmount(amount); + _burnShares(account, sharesAmount); + } + + /** + * @dev Function to set the new fee. Allowed only for owner + * + * @param newFeePerPeriod The new fee per period value + */ + function updateFeePerPeriod(uint256 newFeePerPeriod) external onlyOwner { + feePerPeriod = newFeePerPeriod; + } + + /** + * @dev Function to set the pause in order to block or restore all + * transfers. Allowed only for pauser + * + * Emits a { PauseModeChange } event + * + * @param newPauseMode The new pause mode + */ + function setPause(bool newPauseMode) external { + require(_msgSender() == pauser, "BackedToken: Only pauser"); + isPaused = newPauseMode; + emit PauseModeChange(newPauseMode); + } + + /** + * @dev Function to change the contract minter. Allowed only for owner + * + * Emits a { NewMinter } event + * + * @param newMinter The address of the new minter + */ + function setMinter(address newMinter) external onlyOwner { + minter = newMinter; + emit NewMinter(newMinter); + } + + /** + * @dev Function to change the contract burner. Allowed only for owner + * + * Emits a { NewBurner } event + * + * @param newBurner The address of the new burner + */ + function setBurner(address newBurner) external onlyOwner { + burner = newBurner; + emit NewBurner(newBurner); + } + + /** + * @dev Function to change the contract pauser. Allowed only for owner + * + * Emits a { NewPauser } event + * + * @param newPauser The address of the new pauser + */ + function setPauser(address newPauser) external onlyOwner { + pauser = newPauser; + emit NewPauser(newPauser); + } + + /** + * @dev Function to change the contract multiplier updater. Allowed only for owner + * + * Emits a { NewMultiplierUpdater } event + * + * @param newMultiplierUpdater The address of the new multiplier updater + */ + function setMultiplierUpdater(address newMultiplierUpdater) external onlyOwner { + multiplierUpdater = newMultiplierUpdater; + emit NewMultiplierUpdater(newMultiplierUpdater); + } + + /** + * @dev Function to change the contract multiplier, only if oldMultiplier did not change in the meantime. Allowed only for owner + * + * Emits a { MultiplierChanged } event + * + * @param newMultiplier New multiplier value + */ + function updateMultiplierValue( + uint256 newMultiplier, + uint256 oldMultiplier + ) external updateMultiplier { + require( + _msgSender() == multiplierUpdater, + "BackedToken: Only multiplier updater" + ); + require( + multiplier() == oldMultiplier, + "BackedToken: Multiplier changed in the meantime" + ); + _updateMultiplier(newMultiplier); + } + + /** + * @dev Function to change the contract Senctions List. Allowed only for owner + * + * Emits a { NewSanctionsList } event + * + * @param newSanctionsList The address of the new Senctions List following the Chainalysis standard + */ + function setSanctionsList(address newSanctionsList) external onlyOwner { + // Check the proposed sanctions list contract has the right interface: + require( + !SanctionsList(newSanctionsList).isSanctioned(address(this)), + "BackedToken: Wrong List interface" + ); + + sanctionsList = SanctionsList(newSanctionsList); + emit NewSanctionsList(newSanctionsList); + } + + /** + * @dev EIP-712 Function to change the delegate status of account. + * Allowed only for owner + * + * Emits a { DelegateWhitelistChange } event + * + * @param whitelistAddress The address for which to change the delegate status + * @param status The new delegate status + */ + function setDelegateWhitelist( + address whitelistAddress, + bool status + ) external onlyOwner { + delegateWhitelist[whitelistAddress] = status; + emit DelegateWhitelistChange(whitelistAddress, status); + } + + /** + * @dev EIP-712 Function to change the contract delegate mode. Allowed + * only for owner + * + * Emits a { DelegateModeChange } event + * + * @param _delegateMode The new delegate mode for the contract + */ + function setDelegateMode(bool _delegateMode) external onlyOwner { + delegateMode = _delegateMode; + + emit DelegateModeChange(_delegateMode); + } + + /** + * @dev Function to change the contract terms. Allowed only for owner + * + * Emits a { NewTerms } event + * + * @param newTerms A string with the terms. Usually a web or IPFS link. + */ + function setTerms(string memory newTerms) external onlyOwner { + _setTerms(newTerms); + } + + // Implement setTerms, to allow also to use from initializer: + function _setTerms(string memory newTerms) internal virtual { + terms = newTerms; + emit NewTerms(newTerms); + } + + // Implement the pause and SanctionsList functionality before transfer: + function _beforeTokenTransfer( + address from, + address to, + uint256 amount + ) internal virtual override { + // Check not paused: + require(!isPaused, "BackedToken: token transfer while paused"); + + // Check Sanctions List, but do not prevent minting burning: + if (from != address(0) && to != address(0)) { + require( + !sanctionsList.isSanctioned(from), + "BackedToken: sender is sanctioned" + ); + require( + !sanctionsList.isSanctioned(to), + "BackedToken: receiver is sanctioned" + ); + } + + super._beforeTokenTransfer(from, to, amount); + } + + // Implement the SanctionsList functionality for spender: + function _spendAllowance( + address owner, + address spender, + uint256 amount + ) internal virtual override { + require( + !sanctionsList.isSanctioned(spender), + "BackedToken: spender is sanctioned" + ); + + super._spendAllowance(owner, spender, amount); + } + + /** + * @dev This empty reserved space is put in place to allow future versions to add new + * variables without shifting down storage in the inheritance chain. + * See https://docs.openzeppelin.com/contracts/4.x/upgradeable#storage_gaps + */ + uint256[49] private __gap; +} diff --git a/contracts/ERC20UpgradeableWithMultiplier.sol b/contracts/ERC20UpgradeableWithMultiplier.sol new file mode 100644 index 0000000..33413eb --- /dev/null +++ b/contracts/ERC20UpgradeableWithMultiplier.sol @@ -0,0 +1,497 @@ +// SPDX-License-Identifier: MIT +// OpenZeppelin Contracts (last updated v4.5.0) (token/ERC20/ERC20.sol) + +pragma solidity ^0.8.0; + +import "@openzeppelin/contracts-upgradeable/token/ERC20/IERC20Upgradeable.sol"; +import "@openzeppelin/contracts-upgradeable/token/ERC20/extensions/IERC20MetadataUpgradeable.sol"; +import "@openzeppelin/contracts-upgradeable/utils/ContextUpgradeable.sol"; +import "@openzeppelin/contracts-upgradeable/proxy/utils/Initializable.sol"; + +/** + * @dev Implementation of the {IERC20} interface. + * + * This implementation is agnostic to the way tokens are created. This means + * that a supply mechanism has to be added in a derived contract using {_mint}. + * For a generic mechanism see {ERC20PresetMinterPauser}. + * + * TIP: For a detailed writeup see our guide + * https://forum.zeppelin.solutions/t/how-to-implement-erc20-supply-mechanisms/226[How + * to implement supply mechanisms]. + * + * We have followed general OpenZeppelin Contracts guidelines: functions revert + * instead returning `false` on failure. This behavior is nonetheless + * conventional and does not conflict with the expectations of ERC20 + * applications. + * + * Additionally, an {Approval} event is emitted on calls to {transferFrom}. + * This allows applications to reconstruct the allowance for all accounts just + * by listening to said events. Other implementations of the EIP may not emit + * these events, as it isn't required by the specification. + * + * Finally, the non-standard {decreaseAllowance} and {increaseAllowance} + * functions have been added to mitigate the well-known issues around setting + * allowances. See {IERC20-approve}. + */ +contract ERC20UpgradeableWithMultiplier is Initializable, ContextUpgradeable, IERC20Upgradeable, IERC20MetadataUpgradeable { + mapping(address => uint256) private _shares; + + mapping(address => mapping(address => uint256)) private _allowances; + + uint256 internal _totalShares; + + string private _name; + string private _symbol; + + /** + * @dev Defines ratio between a single share of a token to balance of a token. + * Defined in 1e18 precision. + * + */ + uint256 private _multiplier; + + /** + * @dev Emitted when `value` token shares are moved from one account (`from`) to + * another (`to`). + * + * Note that `value` may be zero. + */ + event TransferShares(address indexed from, address indexed to, uint256 value); + + /** + * @dev Emitted when multiplier value is updated + */ + event MultiplierUpdated(uint256 value); + + /** + * @dev Sets the values for {name} and {symbol}. + * + * The default value of {decimals} is 18. To select a different value for + * {decimals} you should overload it. + * + * All two of these values are immutable: they can only be set once during + * construction. + */ + function __ERC20_init(string memory name_, string memory symbol_) internal onlyInitializing { + __ERC20_init_unchained(name_, symbol_); + } + + function __ERC20_init_unchained(string memory name_, string memory symbol_) internal onlyInitializing { + _name = name_; + _symbol = symbol_; + _multiplier = 1e18; + } + + /** + * @dev Returns the name of the token. + */ + function name() public view virtual override returns (string memory) { + return _name; + } + + /** + * @dev Returns the symbol of the token, usually a shorter version of the + * name. + */ + function symbol() public view virtual override returns (string memory) { + return _symbol; + } + + /** + * @dev Returns the number of decimals used to get its user representation. + * For example, if `decimals` equals `2`, a balance of `505` tokens should + * be displayed to a user as `5.05` (`505 / 10 ** 2`). + * + * Tokens usually opt for a value of 18, imitating the relationship between + * Ether and Wei. This is the value {ERC20} uses, unless this function is + * overridden; + * + * NOTE: This information is only used for _display_ purposes: it in + * no way affects any of the arithmetic of the contract, including + * {IERC20-balanceOf} and {IERC20-transfer}. + */ + function decimals() public view virtual override returns (uint8) { + return 18; + } + + /** + * @dev See {IERC20-totalSupply}. + */ + function totalSupply() public view virtual override returns (uint256) { + return _totalShares * _multiplier / 1e18; + } + + /** + * @dev Returns ratio of shares to underlying amount in 18 decimals precision + */ + function multiplier() public view virtual returns (uint256) { + return _multiplier; + } + + /** + * @dev See {IERC20-balanceOf}. + */ + function balanceOf(address account) public view virtual override returns (uint256) { + return _shares[account] * _multiplier / 1e18; + } + + /** + * @dev Returns amount of shares owned by given account + */ + function sharesOf(address account) public view virtual returns (uint256) { + return _shares[account]; + } + + /** + * @dev See {IERC20-transfer}. + * + * Requirements: + * + * - `to` cannot be the zero address. + * - the caller must have a balance of at least `amount`. + */ + function transfer(address to, uint256 amount) public virtual override returns (bool) { + address owner = _msgSender(); + _transfer(owner, to, amount); + return true; + } + + /** + * @dev Transfers underlying shares to destination account + * + * Requirements: + * + * - `to` cannot be the zero address. + * - the caller must have a balance of at least `sharesAmount`. + */ + function transferShares(address to, uint256 sharesAmount) public virtual returns (bool) { + address owner = _msgSender(); + _transferShares(owner, to, sharesAmount); + return true; + } + + /** + * @dev See {IERC20-allowance}. + */ + function allowance(address owner, address spender) public view virtual override returns (uint256) { + return _allowances[owner][spender]; + } + + /** + * @dev See {IERC20-approve}. + * + * NOTE: If `amount` is the maximum `uint256`, the allowance is not updated on + * `transferFrom`. This is semantically equivalent to an infinite approval. + * + * Requirements: + * + * - `spender` cannot be the zero address. + */ + function approve(address spender, uint256 amount) public virtual override returns (bool) { + address owner = _msgSender(); + _approve(owner, spender, amount); + return true; + } + + /** + * @dev See {IERC20-transferFrom}. + * + * Emits an {Approval} event indicating the updated allowance. This is not + * required by the EIP. See the note at the beginning of {ERC20}. + * + * NOTE: Does not update the allowance if the current allowance + * is the maximum `uint256`. + * + * Requirements: + * + * - `from` and `to` cannot be the zero address. + * - `from` must have a balance of at least `amount`. + * - the caller must have allowance for ``from``'s tokens of at least + * `amount`. + */ + function transferFrom( + address from, + address to, + uint256 amount + ) public virtual override returns (bool) { + address spender = _msgSender(); + _spendAllowance(from, spender, amount); + _transfer(from, to, amount); + return true; + } + + /** + * @dev Atomically increases the allowance granted to `spender` by the caller. + * + * This is an alternative to {approve} that can be used as a mitigation for + * problems described in {IERC20-approve}. + * + * Emits an {Approval} event indicating the updated allowance. + * + * Requirements: + * + * - `spender` cannot be the zero address. + */ + function increaseAllowance(address spender, uint256 addedValue) public virtual returns (bool) { + address owner = _msgSender(); + _approve(owner, spender, _allowances[owner][spender] + addedValue); + return true; + } + + /** + * @dev Atomically decreases the allowance granted to `spender` by the caller. + * + * This is an alternative to {approve} that can be used as a mitigation for + * problems described in {IERC20-approve}. + * + * Emits an {Approval} event indicating the updated allowance. + * + * Requirements: + * + * - `spender` cannot be the zero address. + * - `spender` must have allowance for the caller of at least + * `subtractedValue`. + */ + function decreaseAllowance(address spender, uint256 subtractedValue) public virtual returns (bool) { + address owner = _msgSender(); + uint256 currentAllowance = _allowances[owner][spender]; + require(currentAllowance >= subtractedValue, "ERC20: decreased allowance below zero"); + unchecked { + _approve(owner, spender, currentAllowance - subtractedValue); + } + + return true; + } + + /** + * @return the amount of shares that corresponds to `_underlyingAmount` underlying amount. + */ + function getSharesByUnderlyingAmount(uint256 _underlyingAmount) public view returns (uint256) { + return _underlyingAmount + * 1e18 + / _multiplier; + } + + /** + * @return the amount of underlying that corresponds to `_sharesAmount` token shares. + */ + function getUnderlyingAmountByShares(uint256 _sharesAmount) public view returns (uint256) { + return _sharesAmount + * _multiplier + / 1e18; + } + + /** + * @notice Moves `_sharesAmount` shares from `from` to `to`. + * + * Requirements: + * + * - `from` cannot be the zero address. + * - `from` must hold at least `_sharesAmount` shares. + * - the contract must not be paused. + */ + function _transferShares(address from, address to, uint256 _sharesAmount) internal { + require(from != address(0), "ERC20: transfer from the zero address"); + require(to != address(0), "ERC20: transfer to the zero address"); + uint256 amount = getUnderlyingAmountByShares(_sharesAmount); + _beforeTokenTransfer(from, to, amount); + + uint256 currentSenderShares = _shares[from]; + require(currentSenderShares >= _sharesAmount , "ERC20: transfer amount exceeds balance"); + + unchecked { + _shares[from] = currentSenderShares - (_sharesAmount); + } + _shares[to] = _shares[to] + (_sharesAmount); + + emit Transfer(from, to, amount); + emit TransferShares(from, to, _sharesAmount); + + _afterTokenTransfer(from, to, amount); + } + + /** + * @dev Moves `amount` of tokens from `sender` to `recipient`. + * + * This internal function is equivalent to {transfer}, and can be used to + * e.g. implement automatic token fees, slashing mechanisms, etc. + * + * Emits a {Transfer} event. + * Emits a {TransferShares} event. + * + * Requirements: + * + * - `from` cannot be the zero address. + * - `to` cannot be the zero address. + * - `from` must have a balance of at least `amount`. + */ + function _transfer( + address from, + address to, + uint256 amount + ) internal virtual { + uint256 _sharesToTransfer = getSharesByUnderlyingAmount(amount); + _transferShares(from, to, _sharesToTransfer); + } + + /** @dev Creates `amount` token shares and assigns them to `account`, increasing + * the total supply. + * + * Emits a {Transfer} event with `from` set to the zero address. + * Emits a {TransferShares} event with `from` set to the zero address. + * + * Requirements: + * + * - `account` cannot be the zero address. + */ + function _mintShares(address account, uint256 sharesAmount) internal virtual { + require(account != address(0), "ERC20: mint to the zero address"); + uint256 amount = getUnderlyingAmountByShares(sharesAmount); + + _beforeTokenTransfer(address(0), account, amount); + + _totalShares += sharesAmount; + _shares[account] += sharesAmount; + emit Transfer(address(0), account, amount); + emit TransferShares(address(0), account, sharesAmount); + + _afterTokenTransfer(address(0), account, amount); + } + + /** + * @dev Destroys `amount` tokens from `account`, reducing the + * total supply. + * + * Emits a {Transfer} event with `to` set to the zero address. + * Emits a {TransferShares} event with `to` set to the zero address. + * + * Requirements: + * + * - `account` cannot be the zero address. + * - `account` must have at least `sharesAmount` token shares. + */ + function _burnShares(address account, uint256 sharesAmount) internal virtual { + require(account != address(0), "ERC20: burn from the zero address"); + uint256 amount = getUnderlyingAmountByShares(sharesAmount); + + _beforeTokenTransfer(account, address(0), amount); + + uint256 accountBalance = _shares[account]; + require(accountBalance >= sharesAmount, "ERC20: burn amount exceeds balance"); + unchecked { + _shares[account] = accountBalance - sharesAmount; + } + _totalShares -= sharesAmount; + + emit Transfer(account, address(0), amount); + emit TransferShares(account, address(0), sharesAmount); + + _afterTokenTransfer(account, address(0), amount); + } + + /** + * @dev Sets `amount` as the allowance of `spender` over the `owner` s tokens. + * + * This internal function is equivalent to `approve`, and can be used to + * e.g. set automatic allowances for certain subsystems, etc. + * + * Emits an {Approval} event. + * + * Requirements: + * + * - `owner` cannot be the zero address. + * - `spender` cannot be the zero address. + */ + function _approve( + address owner, + address spender, + uint256 amount + ) internal virtual { + require(owner != address(0), "ERC20: approve from the zero address"); + require(spender != address(0), "ERC20: approve to the zero address"); + + _allowances[owner][spender] = amount; + emit Approval(owner, spender, amount); + } + + /** + * @dev Spend `amount` form the allowance of `owner` toward `spender`. + * + * Does not update the allowance amount in case of infinite allowance. + * Revert if not enough allowance is available. + * + * Might emit an {Approval} event. + */ + function _spendAllowance( + address owner, + address spender, + uint256 amount + ) internal virtual { + uint256 currentAllowance = allowance(owner, spender); + if (currentAllowance != type(uint256).max) { + require(currentAllowance >= amount, "ERC20: insufficient allowance"); + unchecked { + _approve(owner, spender, currentAllowance - amount); + } + } + } + + /** + * @dev Updates currently stored multiplier with a new value + * + * Emit an {MultiplierUpdated} event. + */ + function _updateMultiplier( + uint256 newMultiplier + ) internal virtual { + _multiplier = newMultiplier; + emit MultiplierUpdated(newMultiplier); + } + + /** + * @dev Hook that is called before any transfer of tokens. This includes + * minting and burning. + * + * Calling conditions: + * + * - when `from` and `to` are both non-zero, `amount` of ``from``'s tokens + * will be transferred to `to`. + * - when `from` is zero, `amount` tokens will be minted for `to`. + * - when `to` is zero, `amount` of ``from``'s tokens will be burned. + * - `from` and `to` are never both zero. + * + * To learn more about hooks, head to xref:ROOT:extending-contracts.adoc#using-hooks[Using Hooks]. + */ + function _beforeTokenTransfer( + address from, + address to, + uint256 amount + ) internal virtual {} + + /** + * @dev Hook that is called after any transfer of tokens. This includes + * minting and burning. + * + * Calling conditions: + * + * - when `from` and `to` are both non-zero, `amount` of ``from``'s tokens + * has been transferred to `to`. + * - when `from` is zero, `amount` tokens have been minted for `to`. + * - when `to` is zero, `amount` of ``from``'s tokens have been burned. + * - `from` and `to` are never both zero. + * + * To learn more about hooks, head to xref:ROOT:extending-contracts.adoc#using-hooks[Using Hooks]. + */ + function _afterTokenTransfer( + address from, + address to, + uint256 amount + ) internal virtual {} + + /** + * @dev This empty reserved space is put in place to allow future versions to add new + * variables without shifting down storage in the inheritance chain. + * See https://docs.openzeppelin.com/contracts/4.x/upgradeable#storage_gaps + */ + uint256[44] private __gap; +} diff --git a/contracts/WrappedBackedToken.sol b/contracts/WrappedBackedToken.sol new file mode 100644 index 0000000..67f3b49 --- /dev/null +++ b/contracts/WrappedBackedToken.sol @@ -0,0 +1,222 @@ +/** + * SPDX-License-Identifier: MIT + * + * Copyright (c) 2021-2022 Backed Finance AG + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + * SOFTWARE. + */ + +/** + * Disclaimer and Terms of Use + * + * These ERC-20 tokens have not been registered under the U.S. Securities Act of 1933, as + * amended or with any securities regulatory authority of any State or other jurisdiction + * of the United States and (i) may not be offered, sold or delivered within the United States + * to, or for the account or benefit of U.S. Persons, and (ii) may be offered, sold or otherwise + * delivered at any time only to transferees that are Non-United States Persons (as defined by + * the U.S. Commodities Futures Trading Commission). + * For more information and restrictions please refer to the issuer's [Website](https://www.backedassets.fi/legal-documentation) + */ + +pragma solidity 0.8.9; + +import "@openzeppelin/contracts-upgradeable/token/ERC20/extensions/ERC4626Upgradeable.sol"; +import "@openzeppelin/contracts-upgradeable/access/OwnableUpgradeable.sol"; +import "./SanctionsList.sol"; + +/** + * @dev + * + * This token contract is following the ERC20 standard. + * It inherits ERC4626Upgradeable, which extends the basic ERC20 to be a representation of changing underlying token. + * Enforces Sanctions List via the Chainalysis standard interface. + * The contract contains one role: + * - A pauser, that can pause or restore all transfers in the contract. + * - An owner, that can set the above, and also the sanctionsList pointer. + * The owner can also set who can use the EIP-712 functionality, either specific accounts via a whitelist, or everyone. + * + */ + +contract WrappedBackedToken is OwnableUpgradeable, ERC4626Upgradeable { + string constant public VERSION = "1.1.0"; + + // Roles: + address public pauser; + + // EIP-712 Delegate Functionality: + bool public delegateMode; + mapping(address => bool) public delegateWhitelist; + + // Pause: + bool public isPaused; + + // SanctionsList: + SanctionsList public sanctionsList; + + // Terms: + string public terms; + + // Events: + event NewPauser(address indexed newPauser); + event NewSanctionsList(address indexed newSanctionsList); + event DelegateWhitelistChange(address indexed whitelistAddress, bool status); + event DelegateModeChange(bool delegateMode); + event PauseModeChange(bool pauseMode); + event NewTerms(string newTerms); + + modifier allowedDelegate { + require(delegateMode || delegateWhitelist[_msgSender()], "BackedToken: Unauthorized delegate"); + _; + } + + + // constructor, call initializer to lock the implementation instance. + constructor () { + initialize("Wrapped Backed Token Implementation", "wBTI", address(0x0000000000000000000000000000000000000000)); + } + + function initialize(string memory name_, string memory symbol_, address underlying_) public initializer { + __ERC20_init(name_, symbol_); + __ERC4626_init(IERC20Upgradeable(underlying_)); + __Ownable_init(); + _setTerms("https://www.backedassets.fi/legal-documentation"); // Default Terms + } + + /** + * @dev Function to set the pause in order to block or restore all + * transfers. Allowed only for pauser + * + * Emits a { PauseModeChange } event + * + * @param newPauseMode The new pause mode + */ + function setPause(bool newPauseMode) external { + require(_msgSender() == pauser, "BackedToken: Only pauser"); + isPaused = newPauseMode; + emit PauseModeChange(newPauseMode); + } + + /** + * @dev Function to change the contract pauser. Allowed only for owner + * + * Emits a { NewPauser } event + * + * @param newPauser The address of the new pauser + */ + function setPauser(address newPauser) external onlyOwner { + pauser = newPauser; + emit NewPauser(newPauser); + } + + /** + * @dev Function to change the contract Senctions List. Allowed only for owner + * + * Emits a { NewSanctionsList } event + * + * @param newSanctionsList The address of the new Senctions List following the Chainalysis standard + */ + function setSanctionsList(address newSanctionsList) external onlyOwner { + // Check the proposed sanctions list contract has the right interface: + require(!SanctionsList(newSanctionsList).isSanctioned(address(this)), "BackedToken: Wrong List interface"); + + sanctionsList = SanctionsList(newSanctionsList); + emit NewSanctionsList(newSanctionsList); + } + + + /** + * @dev EIP-712 Function to change the delegate status of account. + * Allowed only for owner + * + * Emits a { DelegateWhitelistChange } event + * + * @param whitelistAddress The address for which to change the delegate status + * @param status The new delegate status + */ + function setDelegateWhitelist(address whitelistAddress, bool status) external onlyOwner { + delegateWhitelist[whitelistAddress] = status; + emit DelegateWhitelistChange(whitelistAddress, status); + } + + /** + * @dev EIP-712 Function to change the contract delegate mode. Allowed + * only for owner + * + * Emits a { DelegateModeChange } event + * + * @param _delegateMode The new delegate mode for the contract + */ + function setDelegateMode(bool _delegateMode) external onlyOwner { + delegateMode = _delegateMode; + + emit DelegateModeChange(_delegateMode); + } + + /** + * @dev Function to change the contract terms. Allowed only for owner + * + * Emits a { NewTerms } event + * + * @param newTerms A string with the terms. Usually a web or IPFS link. + */ + function setTerms(string memory newTerms) external onlyOwner { + _setTerms(newTerms); + } + + // Implement setTerms, tp allow also to use from initializer: + function _setTerms(string memory newTerms) internal virtual { + terms = newTerms; + emit NewTerms(newTerms); + } + + // Implement the pause and SanctionsList functionality before transfer: + function _beforeTokenTransfer( + address from, + address to, + uint256 amount + ) internal virtual override { + // Check not paused: + require(!isPaused, "BackedToken: token transfer while paused"); + + // Check Sanctions List, but do not prevent minting burning: + if (from != address(0) && to != address(0)) { + require(!sanctionsList.isSanctioned(from), "BackedToken: sender is sanctioned"); + require(!sanctionsList.isSanctioned(to), "BackedToken: receiver is sanctioned"); + } + + super._beforeTokenTransfer(from, to, amount); + } + + // Implement the SanctionsList functionality for spender: + function _spendAllowance( + address owner, + address spender, + uint256 amount + ) internal virtual override { + require(!sanctionsList.isSanctioned(spender), "BackedToken: spender is sanctioned"); + + super._spendAllowance(owner, spender, amount); + } + /** + * @dev This empty reserved space is put in place to allow future versions to add new + * variables without shifting down storage in the inheritance chain. + * See https://docs.openzeppelin.com/contracts/4.x/upgradeable#storage_gaps + */ + uint256[49] private __gap; +} diff --git a/test/BackedTokenImplementationWithMultiplierAndAutoFeeAccrual.ts b/test/BackedTokenImplementationWithMultiplierAndAutoFeeAccrual.ts new file mode 100644 index 0000000..7863587 --- /dev/null +++ b/test/BackedTokenImplementationWithMultiplierAndAutoFeeAccrual.ts @@ -0,0 +1,245 @@ +import { ProxyAdmin__factory } from '../typechain/factories/ProxyAdmin__factory'; +import { ProxyAdmin } from '../typechain/ProxyAdmin'; +import { BackedTokenImplementationWithMultiplierAndAutoFeeAccrual__factory } from '../typechain/factories/BackedTokenImplementationWithMultiplierAndAutoFeeAccrual__factory'; +import { BackedTokenImplementationWithMultiplierAndAutoFeeAccrual } from '../typechain/BackedTokenImplementationWithMultiplierAndAutoFeeAccrual'; +import * as helpers from "@nomicfoundation/hardhat-network-helpers"; + +import { expect } from "chai"; +import { ethers } from "hardhat"; +import { BigNumber, Signer } from "ethers"; +import { + BackedTokenProxy__factory, + SanctionsListMock__factory, + // eslint-disable-next-line node/no-missing-import +} from "../typechain"; +import { cacheBeforeEach } from "./helpers"; +import Decimal from 'decimal.js'; + +type SignerWithAddress = { + signer: Signer; + address: string; +}; + +// BackedTokenImplementationWithMultiplierAndAutoFeeAccrual specifications +// Vast majority of comparisons are done with adjustment for precision of calculations, thus we are rather comparing difference of values, +// rather than values themselves +describe.only("BackedTokenImplementationWithMultiplierAndAutoFeeAccrual", function () { + const accrualPeriodLength = 24 * 3600; + const annualFee = 0.5; + const multiplierAdjustmentPerPeriod = nthRoot(annualFee, 365).mul(Decimal.pow(10, 18)); + const baseFeePerPeriod = Decimal.pow(10, 18).minus(multiplierAdjustmentPerPeriod).toFixed(0); + const baseTime = 2_000_000_000; + + // General config: + let token: BackedTokenImplementationWithMultiplierAndAutoFeeAccrual; + let proxyAdmin: ProxyAdmin; + let accounts: Signer[]; + + let owner: SignerWithAddress; + let actor: SignerWithAddress; + + cacheBeforeEach(async () => { + accounts = await ethers.getSigners(); + + const getSigner = async (index: number): Promise => ({ + signer: accounts[index], + address: await accounts[index].getAddress(), + }); + + owner = await getSigner(0); + actor = await getSigner(1); + + await helpers.time.setNextBlockTimestamp(baseTime); + + const tokenImplementationFactory = new BackedTokenImplementationWithMultiplierAndAutoFeeAccrual__factory(owner.signer); + const tokenImplementation = await tokenImplementationFactory.deploy(); + const proxyAdminFactory = new ProxyAdmin__factory(owner.signer) + proxyAdmin = await proxyAdminFactory.deploy(); + const tokenProxy = await new BackedTokenProxy__factory(owner.signer).deploy(tokenImplementation.address, proxyAdmin.address, tokenImplementation.interface.encodeFunctionData( + 'initialize', + [ + "Backed Test Token", + "bTest", + baseTime, + accrualPeriodLength + ] + )); + token = BackedTokenImplementationWithMultiplierAndAutoFeeAccrual__factory.connect(tokenProxy.address, owner.signer); + await token.setMinter(owner.address); + await token.setBurner(owner.address); + await token.setPauser(owner.address); + await token.setMultiplierUpdater(owner.address); + await token.setSanctionsList((await new SanctionsListMock__factory(owner.signer).deploy()).address); + await token.updateFeePerPeriod(baseFeePerPeriod); + + }); + describe('#updateModifier', () => { + describe('when time moved by 365 days forward', () => { + const periodsPassed = 365; + const baseMintedAmount = ethers.BigNumber.from(10).pow(18); + let mintedShares: BigNumber; + cacheBeforeEach(async () => { + await token.mint(owner.address, baseMintedAmount); + mintedShares = await token.sharesOf(owner.address); + await helpers.time.setNextBlockTimestamp(baseTime + periodsPassed * accrualPeriodLength); + await helpers.mine() + }) + + describe('#updateMultiplierValue', () => { + it('Should update stored multiplier value', async () => { + const { newMultiplier: currentMultiplier } = await token.getCurrentMultiplier(); + const newMultiplierValue = currentMultiplier.div(2); + await token.updateMultiplierValue(newMultiplierValue, currentMultiplier) + expect(await token.multiplier()).to.be.equal(newMultiplierValue); + expect(await token.lastTimeFeeApplied()).to.be.equal(baseTime + periodsPassed * accrualPeriodLength); + }); + it('Should reject update, if wrong past value was passed', async () => { + await expect(token.updateMultiplierValue(0, 1)).to.be.reverted; + }); + it('Should reject update, if wrong account is used', async () => { + const { newMultiplier: currentMultiplier } = await token.getCurrentMultiplier(); + await expect(token.connect(actor.signer).updateMultiplierValue(1, currentMultiplier)).to.be.reverted + }); + }); + + describe('#balanceOf', () => { + it('Should decrease balance of the user by fee accrued in 365 days', async () => { + expect((await token.balanceOf(owner.address)).sub(baseMintedAmount.mul(annualFee * 100).div(100)).abs()).to.lte( + BigNumber.from(10).pow(3) + ) + }) + }); + + describe('#totalSupply', () => { + it('Should decrease total supply of the token by the fee accrued in 365 days', async () => { + expect((await token.totalSupply()).sub(baseMintedAmount.mul(annualFee * 100).div(100)).abs()).to.lte( + BigNumber.from(10).pow(3) + ) + }) + }); + + describe('#transfer', () => { + it('Should not allow transfer of previous balance of user', async () => { + await expect(token.transfer(actor.address, baseMintedAmount)).to.be.reverted; + }) + it('Should allow transfer of current balance of user', async () => { + await expect(token.transfer(actor.address, (await token.balanceOf(actor.address)))).to.not.be.reverted; + }) + }); + describe('#transferShares', () => { + it('Should allow transfer of shares of user', async () => { + await expect(token.transfer(actor.address, mintedShares)).to.be.reverted; + }) + }); + describe('#mint', () => { + const newlyMintedTokens = ethers.BigNumber.from(10).pow(18) + cacheBeforeEach(async () => { + await token.mint(actor.address, newlyMintedTokens); + }) + it('Should mint requested number of tokens', async () => { + expect((await token.balanceOf(actor.address)).sub(newlyMintedTokens).abs()).to.be.lte(1); + }) + it('Should mint number of shares according to multiplier', async () => { + expect(await token.sharesOf(actor.address)).to.be.eq(newlyMintedTokens.mul(ethers.BigNumber.from(10).pow(18)).div((await token.getCurrentMultiplier()).newMultiplier)); + }) + }); + }) + }) + describe('#transferShares', () => { + const baseMintedAmount = ethers.BigNumber.from(10).pow(18); + cacheBeforeEach(async () => { + await token.mint(owner.address, baseMintedAmount); + }) + describe('When transfering shares to another account', () => { + const sharesToTransfer = ethers.BigNumber.from(10).pow(18); + const subject = () => token.transferShares(actor.address, sharesToTransfer) + let userBalance: BigNumber; + cacheBeforeEach(async () => { + userBalance = await token.getUnderlyingAmountByShares(sharesToTransfer); + }) + it('Should move requested shares of tokens', async () => { + await subject(); + expect((await token.sharesOf(actor.address))).to.be.eq(sharesToTransfer); + }) + it('Should increase balance of destination wallet', async () => { + await subject(); + expect((await token.balanceOf(actor.address))).to.be.eq(userBalance); + }) + }) + }); + describe('#delegatedTransferShares', () => { + const baseMintedAmount = ethers.BigNumber.from(10).pow(18); + cacheBeforeEach(async () => { + await token.mint(owner.address, baseMintedAmount); + }) + describe('When transfering shares from another account', () => { + const sharesToTransfer = ethers.BigNumber.from(10).pow(18); + let signature: string; + let deadline: number; + let nonce: BigNumber; + const subject = async () => { + const sig = ethers.utils.splitSignature(signature) + return token.connect(actor.signer).delegatedTransferShares(owner.address, actor.address, sharesToTransfer, deadline, sig.v, sig.r, sig.s); + } + let userBalance: BigNumber; + cacheBeforeEach(async () => { + userBalance = await token.getUnderlyingAmountByShares(sharesToTransfer); + + deadline = baseTime * 2; + nonce = await token.nonces(owner.address); + const domain = { + name: await token.name(), + version: "1", + chainId: await owner.signer.getChainId(), + verifyingContract: token.address + }; + const types = { + DELEGATED_TRANSFER_SHARES: [ + { + type: 'address', + name: 'owner' + }, + { + type: 'address', + name: 'to' + }, + { + type: 'uint256', + name: 'value' + }, + { + type: 'uint256', + name: 'nonce' + }, + { + type: 'uint256', + name: 'deadline' + } + ] + }; + const msg = { + owner: owner.address, + to: actor.address, + value: sharesToTransfer, + nonce: nonce, + deadline: deadline + }; + + const signer = await ethers.getSigner(owner.address); + signature = await signer._signTypedData(domain, types, msg); + }) + it('Should move requested shares of tokens', async () => { + await subject(); + expect((await token.sharesOf(actor.address))).to.be.eq(sharesToTransfer); + }) + it('Should increase balance of destination wallet', async () => { + await subject(); + expect((await token.balanceOf(actor.address))).to.be.eq(userBalance); + }) + }) + }); +}); +function nthRoot(annualFee: number, n: number) { + return Decimal.pow(1 - annualFee, new Decimal(1).div(n)); +} + From ea38600127130c5fa3f5c2907ce81c55c57f5013 Mon Sep 17 00:00:00 2001 From: Miniroman Date: Fri, 14 Jun 2024 06:58:43 +0200 Subject: [PATCH 02/17] Backed token with auto accrual and multiplier --- ...20PermitDelegateTransferWithMultiplier.sol | 158 ++++++++++++++++++ 1 file changed, 158 insertions(+) create mode 100644 contracts/ERC20PermitDelegateTransferWithMultiplier.sol diff --git a/contracts/ERC20PermitDelegateTransferWithMultiplier.sol b/contracts/ERC20PermitDelegateTransferWithMultiplier.sol new file mode 100644 index 0000000..271f959 --- /dev/null +++ b/contracts/ERC20PermitDelegateTransferWithMultiplier.sol @@ -0,0 +1,158 @@ +/** + * SPDX-License-Identifier: MIT + * + * Copyright (c) 2021-2022 Backed Finance AG + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + * SOFTWARE. + */ +pragma solidity 0.8.9; + +import "./ERC20UpgradeableWithMultiplier.sol"; +import "@openzeppelin/contracts-upgradeable/utils/cryptography/ECDSAUpgradeable.sol"; + +/** + * @dev + * + * This contract is a based (copy-paste with changes) on OpenZeppelin's draft-ERC20Permit.sol (token/ERC20/extensions/draft-ERC20Permit.sol). + * + * The changes are: + * - Adding also delegated transfer functionality, that is similar to permit, but doing the actual transfer and not approval. + * - Cutting some of the generalities to make the contacts more straight forward for this case (e.g. removing the counters library). + * +*/ + +contract ERC20PermitDelegateTransferWithMultiplier is ERC20UpgradeableWithMultiplier { + mapping(address => uint256) public nonces; + + // Calculating the Permit typehash: + bytes32 public constant PERMIT_TYPEHASH = + keccak256("Permit(address owner,address spender,uint256 value,uint256 nonce,uint256 deadline)"); + + // Calculating the Delegated Transfer typehash: + bytes32 public constant DELEGATED_TRANSFER_TYPEHASH = + keccak256("DELEGATED_TRANSFER(address owner,address to,uint256 value,uint256 nonce,uint256 deadline)"); + + // Calculating the Delegated Transfer Shares typehash: + bytes32 public constant DELEGATED_TRANSFER_SHARES_TYPEHASH = + keccak256("DELEGATED_TRANSFER_SHARES(address owner,address to,uint256 value,uint256 nonce,uint256 deadline)"); + + // Immutable variable for Domain Separator: + // solhint-disable-next-line var-name-mixedcase + bytes32 public DOMAIN_SEPARATOR; + + // A version number: + // solhint-disable-next-line var-name-mixedcase + string internal constant DOMAIN_SEPARATOR_VERSION = "1"; + + /** + * @dev Permit, approve via a sign message, using erc712. + */ + function permit( + address owner, + address spender, + uint256 value, + uint256 deadline, + uint8 v, + bytes32 r, + bytes32 s + ) public virtual { + require(block.timestamp <= deadline, "ERC20Permit: expired deadline"); + + bytes32 structHash = keccak256(abi.encode(PERMIT_TYPEHASH, owner, spender, value, _useNonce(owner), deadline)); + + _checkOwner(owner, structHash, v, r, s); + + _approve(owner, spender, value); + } + + /** + * @dev Delegated Transfer, transfer via a sign message, using erc712. + */ + function delegatedTransfer( + address owner, + address to, + uint256 value, + uint256 deadline, + uint8 v, + bytes32 r, + bytes32 s + ) public virtual { + require(block.timestamp <= deadline, "ERC20Permit: expired deadline"); + + bytes32 structHash = keccak256(abi.encode(DELEGATED_TRANSFER_TYPEHASH, owner, to, value, _useNonce(owner), deadline)); + + _checkOwner(owner, structHash, v, r, s); + + _transfer(owner, to, value); + } + + /** + * @dev Delegated Transfer Shares, transfer shares via a sign message, using erc712. + */ + function delegatedTransferShares( + address owner, + address to, + uint256 value, + uint256 deadline, + uint8 v, + bytes32 r, + bytes32 s + ) public virtual { + require(block.timestamp <= deadline, "ERC20Permit: expired deadline"); + + bytes32 structHash = keccak256(abi.encode(DELEGATED_TRANSFER_SHARES_TYPEHASH, owner, to, value, _useNonce(owner), deadline)); + _checkOwner(owner, structHash, v, r, s); + + _transferShares(owner, to, value); + } + + /** + * @dev "Consume a nonce": return the current value and increment. + */ + function _useNonce(address owner) internal virtual returns (uint256 current) { + current = nonces[owner]; + nonces[owner]++; + } + + function _checkOwner(address owner, bytes32 structHash, uint8 v, bytes32 r, bytes32 s) internal view { + bytes32 hash = ECDSAUpgradeable.toTypedDataHash(DOMAIN_SEPARATOR, structHash); + + address signer = ECDSAUpgradeable.recover(hash, v, r, s); + require(signer == owner, "ERC20Permit: invalid signature"); + } + + function _buildDomainSeparator() internal { + DOMAIN_SEPARATOR = keccak256( + abi.encode( + keccak256("EIP712Domain(string name,string version,uint256 chainId,address verifyingContract)"), + keccak256(bytes(name())), + keccak256(bytes(DOMAIN_SEPARATOR_VERSION)), + block.chainid, + address(this) + ) + ); + } + + /** + * @dev This empty reserved space is put in place to allow future versions to add new + * variables without shifting down storage in the inheritance chain. + * See https://docs.openzeppelin.com/contracts/4.x/upgradeable#storage_gaps + */ + uint256[50] private __gap; +} From 18bf4d4ccc6c7a84accbabe94ecd2b766f386240 Mon Sep 17 00:00:00 2001 From: Miniroman Date: Fri, 14 Jun 2024 11:13:34 +0200 Subject: [PATCH 03/17] Move setting last time fee applied and period length init out of initializer --- ...ntationWithMultiplierAndAutoFeeAccrual.sol | 34 +- package-lock.json | 390 ++++++++++++++---- package.json | 7 +- ...entationWithMultiplierAndAutoFeeAccrual.ts | 6 +- 4 files changed, 348 insertions(+), 89 deletions(-) diff --git a/contracts/BackedTokenImplementationWithMultiplierAndAutoFeeAccrual.sol b/contracts/BackedTokenImplementationWithMultiplierAndAutoFeeAccrual.sol index adcb373..8ef0989 100644 --- a/contracts/BackedTokenImplementationWithMultiplierAndAutoFeeAccrual.sol +++ b/contracts/BackedTokenImplementationWithMultiplierAndAutoFeeAccrual.sol @@ -125,24 +125,20 @@ contract BackedTokenImplementationWithMultiplierAndAutoFeeAccrual is constructor() { initialize( "Backed Token Implementation", - "BTI", - block.timestamp, - 24 * 3600 + "BTI" ); } function initialize( string memory name_, - string memory symbol_, - uint256 firstFeeAccrualTime_, - uint256 periodLength_ + string memory symbol_ ) public initializer { __ERC20_init(name_, symbol_); __Ownable_init(); _buildDomainSeparator(); _setTerms("https://www.backedassets.fi/legal-documentation"); // Default Terms - lastTimeFeeApplied = firstFeeAccrualTime_; - periodLength = periodLength_; + periodLength = 24 * 3600; // Set to 24h by default + lastTimeFeeApplied = block.timestamp; } /** @@ -494,8 +490,26 @@ contract BackedTokenImplementationWithMultiplierAndAutoFeeAccrual is * @param newTerms A string with the terms. Usually a web or IPFS link. */ function setTerms(string memory newTerms) external onlyOwner { - _setTerms(newTerms); - } + _setTerms(newTerms); + } + + /** + * @dev Function to change the time of last fee accrual. Allowed only for owner + * + * @param newLastTimeFeeApplied A timestamp of last time fee was applied + */ + function setLastTimeFeeApplied(uint256 newLastTimeFeeApplied) external onlyOwner { + lastTimeFeeApplied = newLastTimeFeeApplied; + } + + /** + * @dev Function to change period length. Allowed only for owner + * + * @param newPeriodLength Length of a single accrual period in seconds + */ + function setPeriodLength(uint256 newPeriodLength) external onlyOwner { + periodLength = newPeriodLength; + } // Implement setTerms, to allow also to use from initializer: function _setTerms(string memory newTerms) internal virtual { diff --git a/package-lock.json b/package-lock.json index 1caac43..33e9561 100644 --- a/package-lock.json +++ b/package-lock.json @@ -9,12 +9,12 @@ "version": "1.1.0", "license": "MIT", "dependencies": { - "@defi-wonderland/smock": "^2.3.5", - "@openzeppelin/contracts": "4.5.0", - "@openzeppelin/contracts-upgradeable": "4.5.2", + "@openzeppelin/contracts": "4.9.6", + "@openzeppelin/contracts-upgradeable": "4.9.6", "mocha": "^10.2.0" }, "devDependencies": { + "@defi-wonderland/smock": "^2.3.5", "@nomicfoundation/hardhat-network-helpers": "^1.0.8", "@nomiclabs/hardhat-ethers": "^2.0.5", "@nomiclabs/hardhat-etherscan": "^3.0.3", @@ -89,7 +89,7 @@ "version": "0.8.1", "resolved": "https://registry.npmjs.org/@cspotcode/source-map-support/-/source-map-support-0.8.1.tgz", "integrity": "sha512-IchNf6dN4tHoMFIn/7OE8LWZ19Y6q/67Bmf6vnGREv8RSbBVb9LPJxEcnwrcwX6ixSvaiGoomAUvu4YSxXrVgw==", - "devOptional": true, + "dev": true, "dependencies": { "@jridgewell/trace-mapping": "0.3.9" }, @@ -101,6 +101,7 @@ "version": "2.3.5", "resolved": "https://registry.npmjs.org/@defi-wonderland/smock/-/smock-2.3.5.tgz", "integrity": "sha512-klANj1hUpc3cd2ShXdVH/bEGwxJd+LxOngkF5gLcIbg6b37RCgMPMmR/94/hgL62F8bfWtuNKsQD7K+c6M5fWQ==", + "dev": true, "dependencies": { "@nomicfoundation/ethereumjs-evm": "^1.0.0-rc.3", "@nomicfoundation/ethereumjs-util": "^8.0.0-rc.3", @@ -124,6 +125,7 @@ "version": "6.0.0", "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-6.0.0.tgz", "integrity": "sha512-Jo6dJ04CmSjuznwJSS3pUeWmd/H0ffTlkXXgwZi+eq1UCmqQwCh+eLsYOYCwY991i2Fah4h1BEMCx4qThGbsiA==", + "dev": true, "dependencies": { "yallist": "^4.0.0" }, @@ -135,6 +137,7 @@ "version": "7.8.1", "resolved": "https://registry.npmjs.org/rxjs/-/rxjs-7.8.1.tgz", "integrity": "sha512-AA3TVj+0A2iuIoQkWEK/tqFjBq2j+6PO6Y0zJcvzLAFhEFIO3HL0vls9hWLncZbAAbK0mar7oZ4V079I/qPMxg==", + "dev": true, "dependencies": { "tslib": "^2.1.0" } @@ -143,6 +146,7 @@ "version": "7.5.3", "resolved": "https://registry.npmjs.org/semver/-/semver-7.5.3.tgz", "integrity": "sha512-QBlUtyVk/5EeHbi7X0fw6liDZc7BBmEaSYn01fMU1OUYbf6GPsbTtd8WmnqbI20SeycoHSeiybkE/q1Q+qlThQ==", + "dev": true, "dependencies": { "lru-cache": "^6.0.0" }, @@ -156,12 +160,14 @@ "node_modules/@defi-wonderland/smock/node_modules/tslib": { "version": "2.6.0", "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.6.0.tgz", - "integrity": "sha512-7At1WUettjcSRHXCyYtTselblcHl9PJFFVKiCAy/bY97+BPZXSQ2wbq0P9s8tK2G7dFQfNnlJnPAiArVBVBsfA==" + "integrity": "sha512-7At1WUettjcSRHXCyYtTselblcHl9PJFFVKiCAy/bY97+BPZXSQ2wbq0P9s8tK2G7dFQfNnlJnPAiArVBVBsfA==", + "dev": true }, "node_modules/@defi-wonderland/smock/node_modules/yallist": { "version": "4.0.0", "resolved": "https://registry.npmjs.org/yallist/-/yallist-4.0.0.tgz", - "integrity": "sha512-3wdGidZyq5PB084XLES5TpOSRA3wjXAlIWMhum2kRcv/41Sn2emQ0dycQW4uZXLejwKvg6EsvbdlVL+FYEct7A==" + "integrity": "sha512-3wdGidZyq5PB084XLES5TpOSRA3wjXAlIWMhum2kRcv/41Sn2emQ0dycQW4uZXLejwKvg6EsvbdlVL+FYEct7A==", + "dev": true }, "node_modules/@ensdomains/ens": { "version": "0.4.5", @@ -536,6 +542,7 @@ "version": "5.7.0", "resolved": "https://registry.npmjs.org/@ethersproject/abi/-/abi-5.7.0.tgz", "integrity": "sha512-351ktp42TiRcYB3H1OP8yajPeAQstMW/yCFokj/AthP9bLHzQFPlOrxOcwYEDkUAICmOHljvN4K39OMTMUa9RA==", + "dev": true, "funding": [ { "type": "individual", @@ -562,6 +569,7 @@ "version": "5.7.0", "resolved": "https://registry.npmjs.org/@ethersproject/abstract-provider/-/abstract-provider-5.7.0.tgz", "integrity": "sha512-R41c9UkchKCpAqStMYUpdunjo3pkEvZC3FAwZn5S5MGbXoMQOHIdHItezTETxAO5bevtMApSyEhn9+CHcDsWBw==", + "dev": true, "funding": [ { "type": "individual", @@ -586,6 +594,7 @@ "version": "5.7.0", "resolved": "https://registry.npmjs.org/@ethersproject/abstract-signer/-/abstract-signer-5.7.0.tgz", "integrity": "sha512-a16V8bq1/Cz+TGCkE2OPMTOUDLS3grCpdjoJCYNnVBbdYEMSgKrU0+B90s8b6H+ByYTBZN7a3g76jdIJi7UfKQ==", + "dev": true, "funding": [ { "type": "individual", @@ -608,6 +617,7 @@ "version": "5.7.0", "resolved": "https://registry.npmjs.org/@ethersproject/address/-/address-5.7.0.tgz", "integrity": "sha512-9wYhYt7aghVGo758POM5nqcOMaE168Q6aRLJZwUmiqSrAungkG74gSSeKEIR7ukixesdRZGPgVqme6vmxs1fkA==", + "dev": true, "funding": [ { "type": "individual", @@ -630,6 +640,7 @@ "version": "5.7.0", "resolved": "https://registry.npmjs.org/@ethersproject/base64/-/base64-5.7.0.tgz", "integrity": "sha512-Dr8tcHt2mEbsZr/mwTPIQAf3Ai0Bks/7gTw9dSqk1mQvhW3XvRlmDJr/4n+wg1JmCl16NZue17CDh8xb/vZ0sQ==", + "dev": true, "funding": [ { "type": "individual", @@ -648,6 +659,7 @@ "version": "5.7.0", "resolved": "https://registry.npmjs.org/@ethersproject/basex/-/basex-5.7.0.tgz", "integrity": "sha512-ywlh43GwZLv2Voc2gQVTKBoVQ1mti3d8HK5aMxsfu/nRDnMmNqaSJ3r3n85HBByT8OpoY96SXM1FogC533T4zw==", + "dev": true, "funding": [ { "type": "individual", @@ -667,6 +679,7 @@ "version": "5.7.0", "resolved": "https://registry.npmjs.org/@ethersproject/bignumber/-/bignumber-5.7.0.tgz", "integrity": "sha512-n1CAdIHRWjSucQO3MC1zPSVgV/6dy/fjL9pMrPP9peL+QxEg9wOsVqwD4+818B6LUEtaXzVHQiuivzRoxPxUGw==", + "dev": true, "funding": [ { "type": "individual", @@ -687,6 +700,7 @@ "version": "5.7.0", "resolved": "https://registry.npmjs.org/@ethersproject/bytes/-/bytes-5.7.0.tgz", "integrity": "sha512-nsbxwgFXWh9NyYWo+U8atvmMsSdKJprTcICAkvbBffT75qDocbuggBU0SJiVK2MuTrp0q+xvLkTnGMPK1+uA9A==", + "dev": true, "funding": [ { "type": "individual", @@ -705,6 +719,7 @@ "version": "5.7.0", "resolved": "https://registry.npmjs.org/@ethersproject/constants/-/constants-5.7.0.tgz", "integrity": "sha512-DHI+y5dBNvkpYUMiRQyxRBYBefZkJfo70VUkUAsRjcPs47muV9evftfZ0PJVCXYbAiCgght0DtcF9srFQmIgWA==", + "dev": true, "funding": [ { "type": "individual", @@ -723,6 +738,7 @@ "version": "5.7.0", "resolved": "https://registry.npmjs.org/@ethersproject/contracts/-/contracts-5.7.0.tgz", "integrity": "sha512-5GJbzEU3X+d33CdfPhcyS+z8MzsTrBGk/sc+G+59+tPa9yFkl6HQ9D6L0QMgNTA9q8dT0XKxxkyp883XsQvbbg==", + "dev": true, "funding": [ { "type": "individual", @@ -750,6 +766,7 @@ "version": "5.7.0", "resolved": "https://registry.npmjs.org/@ethersproject/hash/-/hash-5.7.0.tgz", "integrity": "sha512-qX5WrQfnah1EFnO5zJv1v46a8HW0+E5xuBBDTwMFZLuVTx0tbU2kkx15NqdjxecrLGatQN9FGQKpb1FKdHCt+g==", + "dev": true, "funding": [ { "type": "individual", @@ -776,6 +793,7 @@ "version": "5.7.0", "resolved": "https://registry.npmjs.org/@ethersproject/hdnode/-/hdnode-5.7.0.tgz", "integrity": "sha512-OmyYo9EENBPPf4ERhR7oj6uAtUAhYGqOnIS+jE5pTXvdKBS99ikzq1E7Iv0ZQZ5V36Lqx1qZLeak0Ra16qpeOg==", + "dev": true, "funding": [ { "type": "individual", @@ -805,6 +823,7 @@ "version": "5.7.0", "resolved": "https://registry.npmjs.org/@ethersproject/json-wallets/-/json-wallets-5.7.0.tgz", "integrity": "sha512-8oee5Xgu6+RKgJTkvEMl2wDgSPSAQ9MB/3JYjFV9jlKvcYHUXZC+cQp0njgmxdHkYWn8s6/IqIZYm0YWCjO/0g==", + "dev": true, "funding": [ { "type": "individual", @@ -835,6 +854,7 @@ "version": "5.7.0", "resolved": "https://registry.npmjs.org/@ethersproject/keccak256/-/keccak256-5.7.0.tgz", "integrity": "sha512-2UcPboeL/iW+pSg6vZ6ydF8tCnv3Iu/8tUmLLzWWGzxWKFFqOBQFLo6uLUv6BDrLgCDfN28RJ/wtByx+jZ4KBg==", + "dev": true, "funding": [ { "type": "individual", @@ -854,6 +874,7 @@ "version": "5.7.0", "resolved": "https://registry.npmjs.org/@ethersproject/logger/-/logger-5.7.0.tgz", "integrity": "sha512-0odtFdXu/XHtjQXJYA3u9G0G8btm0ND5Cu8M7i5vhEcE8/HmF4Lbdqanwyv4uQTr2tx6b7fQRmgLrsnpQlmnig==", + "dev": true, "funding": [ { "type": "individual", @@ -869,6 +890,7 @@ "version": "5.7.1", "resolved": "https://registry.npmjs.org/@ethersproject/networks/-/networks-5.7.1.tgz", "integrity": "sha512-n/MufjFYv3yFcUyfhnXotyDlNdFb7onmkSy8aQERi2PjNcnWQ66xXxa3XlS8nCcA8aJKJjIIMNJTC7tu80GwpQ==", + "dev": true, "funding": [ { "type": "individual", @@ -887,6 +909,7 @@ "version": "5.7.0", "resolved": "https://registry.npmjs.org/@ethersproject/pbkdf2/-/pbkdf2-5.7.0.tgz", "integrity": "sha512-oR/dBRZR6GTyaofd86DehG72hY6NpAjhabkhxgr3X2FpJtJuodEl2auADWBZfhDHgVCbu3/H/Ocq2uC6dpNjjw==", + "dev": true, "funding": [ { "type": "individual", @@ -906,6 +929,7 @@ "version": "5.7.0", "resolved": "https://registry.npmjs.org/@ethersproject/properties/-/properties-5.7.0.tgz", "integrity": "sha512-J87jy8suntrAkIZtecpxEPxY//szqr1mlBaYlQ0r4RCaiD2hjheqF9s1LVE8vVuJCXisjIP+JgtK/Do54ej4Sw==", + "dev": true, "funding": [ { "type": "individual", @@ -924,6 +948,7 @@ "version": "5.7.2", "resolved": "https://registry.npmjs.org/@ethersproject/providers/-/providers-5.7.2.tgz", "integrity": "sha512-g34EWZ1WWAVgr4aptGlVBF8mhl3VWjv+8hoAnzStu8Ah22VHBsuGzP17eb6xDVRzw895G4W7vvx60lFFur/1Rg==", + "dev": true, "funding": [ { "type": "individual", @@ -961,6 +986,7 @@ "version": "5.7.0", "resolved": "https://registry.npmjs.org/@ethersproject/random/-/random-5.7.0.tgz", "integrity": "sha512-19WjScqRA8IIeWclFme75VMXSBvi4e6InrUNuaR4s5pTF2qNhcGdCUwdxUVGtDDqC00sDLCO93jPQoDUH4HVmQ==", + "dev": true, "funding": [ { "type": "individual", @@ -980,6 +1006,7 @@ "version": "5.7.0", "resolved": "https://registry.npmjs.org/@ethersproject/rlp/-/rlp-5.7.0.tgz", "integrity": "sha512-rBxzX2vK8mVF7b0Tol44t5Tb8gomOHkj5guL+HhzQ1yBh/ydjGnpw6at+X6Iw0Kp3OzzzkcKp8N9r0W4kYSs9w==", + "dev": true, "funding": [ { "type": "individual", @@ -999,6 +1026,7 @@ "version": "5.7.0", "resolved": "https://registry.npmjs.org/@ethersproject/sha2/-/sha2-5.7.0.tgz", "integrity": "sha512-gKlH42riwb3KYp0reLsFTokByAKoJdgFCwI+CCiX/k+Jm2mbNs6oOaCjYQSlI1+XBVejwH2KrmCbMAT/GnRDQw==", + "dev": true, "funding": [ { "type": "individual", @@ -1019,6 +1047,7 @@ "version": "5.7.0", "resolved": "https://registry.npmjs.org/@ethersproject/signing-key/-/signing-key-5.7.0.tgz", "integrity": "sha512-MZdy2nL3wO0u7gkB4nA/pEf8lu1TlFswPNmy8AiYkfKTdO6eXBJyUdmHO/ehm/htHw9K/qF8ujnTyUAD+Ry54Q==", + "dev": true, "funding": [ { "type": "individual", @@ -1042,6 +1071,7 @@ "version": "5.7.0", "resolved": "https://registry.npmjs.org/@ethersproject/solidity/-/solidity-5.7.0.tgz", "integrity": "sha512-HmabMd2Dt/raavyaGukF4XxizWKhKQ24DoLtdNbBmNKUOPqwjsKQSdV9GQtj9CBEea9DlzETlVER1gYeXXBGaA==", + "dev": true, "funding": [ { "type": "individual", @@ -1065,6 +1095,7 @@ "version": "5.7.0", "resolved": "https://registry.npmjs.org/@ethersproject/strings/-/strings-5.7.0.tgz", "integrity": "sha512-/9nu+lj0YswRNSH0NXYqrh8775XNyEdUQAuf3f+SmOrnVewcJ5SBNAjF7lpgehKi4abvNNXyf+HX86czCdJ8Mg==", + "dev": true, "funding": [ { "type": "individual", @@ -1085,6 +1116,7 @@ "version": "5.7.0", "resolved": "https://registry.npmjs.org/@ethersproject/transactions/-/transactions-5.7.0.tgz", "integrity": "sha512-kmcNicCp1lp8qanMTC3RIikGgoJ80ztTyvtsFvCYpSCfkjhD0jZ2LOrnbcuxuToLIUYYf+4XwD1rP+B/erDIhQ==", + "dev": true, "funding": [ { "type": "individual", @@ -1111,6 +1143,7 @@ "version": "5.7.0", "resolved": "https://registry.npmjs.org/@ethersproject/units/-/units-5.7.0.tgz", "integrity": "sha512-pD3xLMy3SJu9kG5xDGI7+xhTEmGXlEqXU4OfNapmfnxLVY4EMSSRp7j1k7eezutBPH7RBN/7QPnwR7hzNlEFeg==", + "dev": true, "funding": [ { "type": "individual", @@ -1131,6 +1164,7 @@ "version": "5.7.0", "resolved": "https://registry.npmjs.org/@ethersproject/wallet/-/wallet-5.7.0.tgz", "integrity": "sha512-MhmXlJXEJFBFVKrDLB4ZdDzxcBxQ3rLyCkhNqVu3CDYvR97E+8r01UgrI+TI99Le+aYm/in/0vp86guJuM7FCA==", + "dev": true, "funding": [ { "type": "individual", @@ -1163,6 +1197,7 @@ "version": "5.7.1", "resolved": "https://registry.npmjs.org/@ethersproject/web/-/web-5.7.1.tgz", "integrity": "sha512-Gueu8lSvyjBWL4cYsWsjh6MtMwM0+H4HvqFPZfB6dV8ctbP9zFAO73VG1cMWae0FLPCtz0peKPpZY8/ugJJX2w==", + "dev": true, "funding": [ { "type": "individual", @@ -1185,6 +1220,7 @@ "version": "5.7.0", "resolved": "https://registry.npmjs.org/@ethersproject/wordlists/-/wordlists-5.7.0.tgz", "integrity": "sha512-S2TFNJNfHWVHNE6cNDjbVlZ6MgE17MIxMbMg2zv3wn+3XSJGosL1m9ZVv3GXCf/2ymSsQ+hRI5IzoMJTG6aoVA==", + "dev": true, "funding": [ { "type": "individual", @@ -1227,7 +1263,7 @@ "version": "3.1.0", "resolved": "https://registry.npmjs.org/@jridgewell/resolve-uri/-/resolve-uri-3.1.0.tgz", "integrity": "sha512-F2msla3tad+Mfht5cJq7LSXcdudKTWCVYUgw6pLFOOHSTtZlj6SWNYAp+AhuqLmWdBO2X5hPrLcu8cVP8fy28w==", - "devOptional": true, + "dev": true, "engines": { "node": ">=6.0.0" } @@ -1236,13 +1272,13 @@ "version": "1.4.14", "resolved": "https://registry.npmjs.org/@jridgewell/sourcemap-codec/-/sourcemap-codec-1.4.14.tgz", "integrity": "sha512-XPSJHWmi394fuUuzDnGz1wiKqWfo1yXecHQMRf2l6hztTO+nPru658AyDngaBe7isIxEkRsPR3FZh+s7iVa4Uw==", - "devOptional": true + "dev": true }, "node_modules/@jridgewell/trace-mapping": { "version": "0.3.9", "resolved": "https://registry.npmjs.org/@jridgewell/trace-mapping/-/trace-mapping-0.3.9.tgz", "integrity": "sha512-3Belt6tdc8bPgAtbcmdtNJlirVoTmEb5e2gC94PnkwEW9jI6CAHUeoG85tjWP5WquqfavoMtMwiG4P926ZKKuQ==", - "devOptional": true, + "dev": true, "dependencies": { "@jridgewell/resolve-uri": "^3.0.3", "@jridgewell/sourcemap-codec": "^1.4.10" @@ -1252,6 +1288,7 @@ "version": "4.0.1", "resolved": "https://registry.npmjs.org/@metamask/eth-sig-util/-/eth-sig-util-4.0.1.tgz", "integrity": "sha512-tghyZKLHZjcdlDqCA3gNZmLeR0XvOE9U1qoQO9ohyAZT6Pya+H9vkBPcsyXytmYLNgVoin7CKCmweo/R43V+tQ==", + "dev": true, "dependencies": { "ethereumjs-abi": "^0.6.8", "ethereumjs-util": "^6.2.1", @@ -1267,6 +1304,7 @@ "version": "4.11.6", "resolved": "https://registry.npmjs.org/@types/bn.js/-/bn.js-4.11.6.tgz", "integrity": "sha512-pqr857jrp2kPuO9uRjZ3PwnJTjoQy+fcdxvBTvHm6dkmEL9q+hDD/2j/0ELOBPtPnS8LjCX0gI9nbl8lVkadpg==", + "dev": true, "dependencies": { "@types/node": "*" } @@ -1274,12 +1312,14 @@ "node_modules/@metamask/eth-sig-util/node_modules/bn.js": { "version": "4.12.0", "resolved": "https://registry.npmjs.org/bn.js/-/bn.js-4.12.0.tgz", - "integrity": "sha512-c98Bf3tPniI+scsdk237ku1Dc3ujXQTSgyiPUDEOe7tRkhrqridvh8klBv0HCEso1OLOYcHuCv/cS6DNxKH+ZA==" + "integrity": "sha512-c98Bf3tPniI+scsdk237ku1Dc3ujXQTSgyiPUDEOe7tRkhrqridvh8klBv0HCEso1OLOYcHuCv/cS6DNxKH+ZA==", + "dev": true }, "node_modules/@metamask/eth-sig-util/node_modules/ethereumjs-util": { "version": "6.2.1", "resolved": "https://registry.npmjs.org/ethereumjs-util/-/ethereumjs-util-6.2.1.tgz", "integrity": "sha512-W2Ktez4L01Vexijrm5EB6w7dg4n/TgpoYU4avuT5T3Vmnw/eCRtiBrJfQYS/DCSvDIOLn2k57GcHdeBcgVxAqw==", + "dev": true, "dependencies": { "@types/bn.js": "^4.11.3", "bn.js": "^4.11.0", @@ -1294,6 +1334,7 @@ "version": "1.1.2", "resolved": "https://registry.npmjs.org/@noble/hashes/-/hashes-1.1.2.tgz", "integrity": "sha512-KYRCASVTv6aeUi1tsF8/vpyR7zpfs3FUzy2Jqm+MU+LmUKhQ0y2FpfwqkCcxSg2ua4GALJd8k2R76WxwZGbQpA==", + "dev": true, "funding": [ { "type": "individual", @@ -1305,6 +1346,7 @@ "version": "1.6.3", "resolved": "https://registry.npmjs.org/@noble/secp256k1/-/secp256k1-1.6.3.tgz", "integrity": "sha512-T04e4iTurVy7I8Sw4+c5OSN9/RkPlo1uKxAomtxQNLq8j1uPAqnsqG1bqvY3Jv7c13gyr6dui0zmh/I3+f/JaQ==", + "dev": true, "funding": [ { "type": "individual", @@ -1351,6 +1393,7 @@ "version": "4.0.0", "resolved": "https://registry.npmjs.org/@nomicfoundation/ethereumjs-block/-/ethereumjs-block-4.0.0.tgz", "integrity": "sha512-bk8uP8VuexLgyIZAHExH1QEovqx0Lzhc9Ntm63nCRKLHXIZkobaFaeCVwTESV7YkPKUk7NiK11s8ryed4CS9yA==", + "dev": true, "dependencies": { "@nomicfoundation/ethereumjs-common": "^3.0.0", "@nomicfoundation/ethereumjs-rlp": "^4.0.0", @@ -1367,6 +1410,7 @@ "version": "6.0.0", "resolved": "https://registry.npmjs.org/@nomicfoundation/ethereumjs-blockchain/-/ethereumjs-blockchain-6.0.0.tgz", "integrity": "sha512-pLFEoea6MWd81QQYSReLlLfH7N9v7lH66JC/NMPN848ySPPQA5renWnE7wPByfQFzNrPBuDDRFFULMDmj1C0xw==", + "dev": true, "dependencies": { "@nomicfoundation/ethereumjs-block": "^4.0.0", "@nomicfoundation/ethereumjs-common": "^3.0.0", @@ -1389,6 +1433,7 @@ "version": "3.0.0", "resolved": "https://registry.npmjs.org/@nomicfoundation/ethereumjs-common/-/ethereumjs-common-3.0.0.tgz", "integrity": "sha512-WS7qSshQfxoZOpHG/XqlHEGRG1zmyjYrvmATvc4c62+gZXgre1ymYP8ZNgx/3FyZY0TWe9OjFlKOfLqmgOeYwA==", + "dev": true, "dependencies": { "@nomicfoundation/ethereumjs-util": "^8.0.0", "crc-32": "^1.2.0" @@ -1398,6 +1443,7 @@ "version": "2.0.0", "resolved": "https://registry.npmjs.org/@nomicfoundation/ethereumjs-ethash/-/ethereumjs-ethash-2.0.0.tgz", "integrity": "sha512-WpDvnRncfDUuXdsAXlI4lXbqUDOA+adYRQaEezIkxqDkc+LDyYDbd/xairmY98GnQzo1zIqsIL6GB5MoMSJDew==", + "dev": true, "dependencies": { "@nomicfoundation/ethereumjs-block": "^4.0.0", "@nomicfoundation/ethereumjs-rlp": "^4.0.0", @@ -1414,6 +1460,7 @@ "version": "1.0.0", "resolved": "https://registry.npmjs.org/@nomicfoundation/ethereumjs-evm/-/ethereumjs-evm-1.0.0.tgz", "integrity": "sha512-hVS6qRo3V1PLKCO210UfcEQHvlG7GqR8iFzp0yyjTg2TmJQizcChKgWo8KFsdMw6AyoLgLhHGHw4HdlP8a4i+Q==", + "dev": true, "dependencies": { "@nomicfoundation/ethereumjs-common": "^3.0.0", "@nomicfoundation/ethereumjs-util": "^8.0.0", @@ -1432,6 +1479,7 @@ "version": "4.0.0", "resolved": "https://registry.npmjs.org/@nomicfoundation/ethereumjs-rlp/-/ethereumjs-rlp-4.0.0.tgz", "integrity": "sha512-GaSOGk5QbUk4eBP5qFbpXoZoZUj/NrW7MRa0tKY4Ew4c2HAS0GXArEMAamtFrkazp0BO4K5p2ZCG3b2FmbShmw==", + "dev": true, "bin": { "rlp": "bin/rlp" }, @@ -1443,6 +1491,7 @@ "version": "1.0.0", "resolved": "https://registry.npmjs.org/@nomicfoundation/ethereumjs-statemanager/-/ethereumjs-statemanager-1.0.0.tgz", "integrity": "sha512-jCtqFjcd2QejtuAMjQzbil/4NHf5aAWxUc+CvS0JclQpl+7M0bxMofR2AJdtz+P3u0ke2euhYREDiE7iSO31vQ==", + "dev": true, "dependencies": { "@nomicfoundation/ethereumjs-common": "^3.0.0", "@nomicfoundation/ethereumjs-rlp": "^4.0.0", @@ -1457,6 +1506,7 @@ "version": "5.0.0", "resolved": "https://registry.npmjs.org/@nomicfoundation/ethereumjs-trie/-/ethereumjs-trie-5.0.0.tgz", "integrity": "sha512-LIj5XdE+s+t6WSuq/ttegJzZ1vliwg6wlb+Y9f4RlBpuK35B9K02bO7xU+E6Rgg9RGptkWd6TVLdedTI4eNc2A==", + "dev": true, "dependencies": { "@nomicfoundation/ethereumjs-rlp": "^4.0.0", "@nomicfoundation/ethereumjs-util": "^8.0.0", @@ -1471,6 +1521,7 @@ "version": "4.0.0", "resolved": "https://registry.npmjs.org/@nomicfoundation/ethereumjs-tx/-/ethereumjs-tx-4.0.0.tgz", "integrity": "sha512-Gg3Lir2lNUck43Kp/3x6TfBNwcWC9Z1wYue9Nz3v4xjdcv6oDW9QSMJxqsKw9QEGoBBZ+gqwpW7+F05/rs/g1w==", + "dev": true, "dependencies": { "@nomicfoundation/ethereumjs-common": "^3.0.0", "@nomicfoundation/ethereumjs-rlp": "^4.0.0", @@ -1485,6 +1536,7 @@ "version": "8.0.0", "resolved": "https://registry.npmjs.org/@nomicfoundation/ethereumjs-util/-/ethereumjs-util-8.0.0.tgz", "integrity": "sha512-2emi0NJ/HmTG+CGY58fa+DQuAoroFeSH9gKu9O6JnwTtlzJtgfTixuoOqLEgyyzZVvwfIpRueuePb8TonL1y+A==", + "dev": true, "dependencies": { "@nomicfoundation/ethereumjs-rlp": "^4.0.0-beta.2", "ethereum-cryptography": "0.1.3" @@ -1497,6 +1549,7 @@ "version": "6.0.0", "resolved": "https://registry.npmjs.org/@nomicfoundation/ethereumjs-vm/-/ethereumjs-vm-6.0.0.tgz", "integrity": "sha512-JMPxvPQ3fzD063Sg3Tp+UdwUkVxMoo1uML6KSzFhMH3hoQi/LMuXBoEHAoW83/vyNS9BxEe6jm6LmT5xdeEJ6w==", + "dev": true, "dependencies": { "@nomicfoundation/ethereumjs-block": "^4.0.0", "@nomicfoundation/ethereumjs-blockchain": "^6.0.0", @@ -1535,6 +1588,7 @@ "version": "0.1.0", "resolved": "https://registry.npmjs.org/@nomicfoundation/solidity-analyzer/-/solidity-analyzer-0.1.0.tgz", "integrity": "sha512-xGWAiVCGOycvGiP/qrlf9f9eOn7fpNbyJygcB0P21a1MDuVPlKt0Srp7rvtBEutYQ48ouYnRXm33zlRnlTOPHg==", + "dev": true, "engines": { "node": ">= 12" }, @@ -1558,6 +1612,7 @@ "cpu": [ "arm64" ], + "dev": true, "optional": true, "os": [ "darwin" @@ -1573,6 +1628,7 @@ "cpu": [ "x64" ], + "dev": true, "optional": true, "os": [ "darwin" @@ -1588,6 +1644,7 @@ "cpu": [ "x64" ], + "dev": true, "optional": true, "os": [ "freebsd" @@ -1603,6 +1660,7 @@ "cpu": [ "arm64" ], + "dev": true, "optional": true, "os": [ "linux" @@ -1618,6 +1676,7 @@ "cpu": [ "arm64" ], + "dev": true, "optional": true, "os": [ "linux" @@ -1633,6 +1692,7 @@ "cpu": [ "x64" ], + "dev": true, "optional": true, "os": [ "linux" @@ -1648,6 +1708,7 @@ "cpu": [ "x64" ], + "dev": true, "optional": true, "os": [ "linux" @@ -1663,6 +1724,7 @@ "cpu": [ "arm64" ], + "dev": true, "optional": true, "os": [ "win32" @@ -1678,6 +1740,7 @@ "cpu": [ "ia32" ], + "dev": true, "optional": true, "os": [ "win32" @@ -1693,6 +1756,7 @@ "cpu": [ "x64" ], + "dev": true, "optional": true, "os": [ "win32" @@ -1705,6 +1769,7 @@ "version": "2.2.2", "resolved": "https://registry.npmjs.org/@nomiclabs/hardhat-ethers/-/hardhat-ethers-2.2.2.tgz", "integrity": "sha512-NLDlDFL2us07C0jB/9wzvR0kuLivChJWCXTKcj3yqjZqMoYp7g7wwS157F70VHx/+9gHIBGzak5pKDwG8gEefA==", + "dev": true, "peerDependencies": { "ethers": "^5.0.0", "hardhat": "^2.0.0" @@ -1748,14 +1813,14 @@ } }, "node_modules/@openzeppelin/contracts": { - "version": "4.5.0", - "resolved": "https://registry.npmjs.org/@openzeppelin/contracts/-/contracts-4.5.0.tgz", - "integrity": "sha512-fdkzKPYMjrRiPK6K4y64e6GzULR7R7RwxSigHS8DDp7aWDeoReqsQI+cxHV1UuhAqX69L1lAaWDxenfP+xiqzA==" + "version": "4.9.6", + "resolved": "https://registry.npmjs.org/@openzeppelin/contracts/-/contracts-4.9.6.tgz", + "integrity": "sha512-xSmezSupL+y9VkHZJGDoCBpmnB2ogM13ccaYDWqJTfS3dbuHkgjuwDFUmaFauBCboQMGB/S5UqUl2y54X99BmA==" }, "node_modules/@openzeppelin/contracts-upgradeable": { - "version": "4.5.2", - "resolved": "https://registry.npmjs.org/@openzeppelin/contracts-upgradeable/-/contracts-upgradeable-4.5.2.tgz", - "integrity": "sha512-xgWZYaPlrEOQo3cBj97Ufiuv79SPd8Brh4GcFYhPgb6WvAq4ppz8dWKL6h+jLAK01rUqMRp/TS9AdXgAeNvCLA==" + "version": "4.9.6", + "resolved": "https://registry.npmjs.org/@openzeppelin/contracts-upgradeable/-/contracts-upgradeable-4.9.6.tgz", + "integrity": "sha512-m4iHazOsOCv1DgM7eD7GupTJ+NFVujRZt1wzddDPSVGpWdKq1SKkla5htKG7+IS4d2XOCtzkUNwRZ7Vq5aEUMA==" }, "node_modules/@openzeppelin/hardhat-upgrades": { "version": "1.16.1", @@ -2015,6 +2080,7 @@ "version": "1.1.1", "resolved": "https://registry.npmjs.org/@scure/base/-/base-1.1.1.tgz", "integrity": "sha512-ZxOhsSyxYwLJj3pLZCefNitxsj093tb2vq90mp2txoYeBqbcjDjqFhyM8eUjq/uFm6zJ+mUuqxlS2FkuSY1MTA==", + "dev": true, "funding": [ { "type": "individual", @@ -2026,6 +2092,7 @@ "version": "1.1.0", "resolved": "https://registry.npmjs.org/@scure/bip32/-/bip32-1.1.0.tgz", "integrity": "sha512-ftTW3kKX54YXLCxH6BB7oEEoJfoE2pIgw7MINKAs5PsS6nqKPuKk1haTF/EuHmYqG330t5GSrdmtRuHaY1a62Q==", + "dev": true, "funding": [ { "type": "individual", @@ -2042,6 +2109,7 @@ "version": "1.1.0", "resolved": "https://registry.npmjs.org/@scure/bip39/-/bip39-1.1.0.tgz", "integrity": "sha512-pwrPOS16VeTKg98dYXQyIjJEcWfz7/1YJIwxUEPFfQPtc86Ym/1sVgQ2RLoD43AazMk2l/unK4ITySSpW2+82w==", + "dev": true, "funding": [ { "type": "individual", @@ -2057,6 +2125,7 @@ "version": "5.30.0", "resolved": "https://registry.npmjs.org/@sentry/core/-/core-5.30.0.tgz", "integrity": "sha512-TmfrII8w1PQZSZgPpUESqjB+jC6MvZJZdLtE/0hZ+SrnKhW3x5WlYLvTXZpcWePYBku7rl2wn1RZu6uT0qCTeg==", + "dev": true, "dependencies": { "@sentry/hub": "5.30.0", "@sentry/minimal": "5.30.0", @@ -2072,6 +2141,7 @@ "version": "5.30.0", "resolved": "https://registry.npmjs.org/@sentry/hub/-/hub-5.30.0.tgz", "integrity": "sha512-2tYrGnzb1gKz2EkMDQcfLrDTvmGcQPuWxLnJKXJvYTQDGLlEvi2tWz1VIHjunmOvJrB5aIQLhm+dcMRwFZDCqQ==", + "dev": true, "dependencies": { "@sentry/types": "5.30.0", "@sentry/utils": "5.30.0", @@ -2085,6 +2155,7 @@ "version": "5.30.0", "resolved": "https://registry.npmjs.org/@sentry/minimal/-/minimal-5.30.0.tgz", "integrity": "sha512-BwWb/owZKtkDX+Sc4zCSTNcvZUq7YcH3uAVlmh/gtR9rmUvbzAA3ewLuB3myi4wWRAMEtny6+J/FN/x+2wn9Xw==", + "dev": true, "dependencies": { "@sentry/hub": "5.30.0", "@sentry/types": "5.30.0", @@ -2098,6 +2169,7 @@ "version": "5.30.0", "resolved": "https://registry.npmjs.org/@sentry/node/-/node-5.30.0.tgz", "integrity": "sha512-Br5oyVBF0fZo6ZS9bxbJZG4ApAjRqAnqFFurMVJJdunNb80brh7a5Qva2kjhm+U6r9NJAB5OmDyPkA1Qnt+QVg==", + "dev": true, "dependencies": { "@sentry/core": "5.30.0", "@sentry/hub": "5.30.0", @@ -2117,6 +2189,7 @@ "version": "5.30.0", "resolved": "https://registry.npmjs.org/@sentry/tracing/-/tracing-5.30.0.tgz", "integrity": "sha512-dUFowCr0AIMwiLD7Fs314Mdzcug+gBVo/+NCMyDw8tFxJkwWAKl7Qa2OZxLQ0ZHjakcj1hNKfCQJ9rhyfOl4Aw==", + "dev": true, "dependencies": { "@sentry/hub": "5.30.0", "@sentry/minimal": "5.30.0", @@ -2132,6 +2205,7 @@ "version": "5.30.0", "resolved": "https://registry.npmjs.org/@sentry/types/-/types-5.30.0.tgz", "integrity": "sha512-R8xOqlSTZ+htqrfteCWU5Nk0CDN5ApUTvrlvBuiH1DyP6czDZ4ktbZB0hAgBlVcK0U+qpD3ag3Tqqpa5Q67rPw==", + "dev": true, "engines": { "node": ">=6" } @@ -2140,6 +2214,7 @@ "version": "5.30.0", "resolved": "https://registry.npmjs.org/@sentry/utils/-/utils-5.30.0.tgz", "integrity": "sha512-zaYmoH0NWWtvnJjC9/CBseXMtKHm/tm40sz3YfJRxeQjyzRqNQPgivpd9R/oDJCYj999mzdW382p/qi2ypjLww==", + "dev": true, "dependencies": { "@sentry/types": "5.30.0", "tslib": "^1.9.3" @@ -2161,25 +2236,25 @@ "version": "1.0.9", "resolved": "https://registry.npmjs.org/@tsconfig/node10/-/node10-1.0.9.tgz", "integrity": "sha512-jNsYVVxU8v5g43Erja32laIDHXeoNvFEpX33OK4d6hljo3jDhCBDhx5dhCCTMWUojscpAagGiRkBKxpdl9fxqA==", - "devOptional": true + "dev": true }, "node_modules/@tsconfig/node12": { "version": "1.0.11", "resolved": "https://registry.npmjs.org/@tsconfig/node12/-/node12-1.0.11.tgz", "integrity": "sha512-cqefuRsh12pWyGsIoBKJA9luFu3mRxCA+ORZvA4ktLSzIuCUtWVxGIuXigEwO5/ywWFMZ2QEGKWvkZG1zDMTag==", - "devOptional": true + "dev": true }, "node_modules/@tsconfig/node14": { "version": "1.0.3", "resolved": "https://registry.npmjs.org/@tsconfig/node14/-/node14-1.0.3.tgz", "integrity": "sha512-ysT8mhdixWK6Hw3i1V2AeRqZ5WfXg1G43mqoYlM2nc6388Fq5jcXyr5mRsqViLx/GJYdoL0bfXD8nmF+Zn/Iow==", - "devOptional": true + "dev": true }, "node_modules/@tsconfig/node16": { "version": "1.0.3", "resolved": "https://registry.npmjs.org/@tsconfig/node16/-/node16-1.0.3.tgz", "integrity": "sha512-yOlFc+7UtL/89t2ZhjPvvB/DeAr3r+Dq58IgzsFkOAvVC6NMJXmCGjbptdXdR9qsX7pKcTL+s87FtYREi2dEEQ==", - "devOptional": true + "dev": true }, "node_modules/@typechain/ethers-v5": { "version": "7.2.0", @@ -2252,12 +2327,14 @@ "node_modules/@types/async-eventemitter": { "version": "0.2.1", "resolved": "https://registry.npmjs.org/@types/async-eventemitter/-/async-eventemitter-0.2.1.tgz", - "integrity": "sha512-M2P4Ng26QbAeITiH7w1d7OxtldgfAe0wobpyJzVK/XOb0cUGKU2R4pfAhqcJBXAe2ife5ZOhSv4wk7p+ffURtg==" + "integrity": "sha512-M2P4Ng26QbAeITiH7w1d7OxtldgfAe0wobpyJzVK/XOb0cUGKU2R4pfAhqcJBXAe2ife5ZOhSv4wk7p+ffURtg==", + "dev": true }, "node_modules/@types/bn.js": { "version": "5.1.1", "resolved": "https://registry.npmjs.org/@types/bn.js/-/bn.js-5.1.1.tgz", "integrity": "sha512-qNrYbZqMx0uJAfKnKclPh+dTwK33KfLHYqtyODwd5HnXOjnkhc4qgn3BrK6RWyGZm5+sIFE7Q7Vz6QQtJB7w7g==", + "dev": true, "dependencies": { "@types/node": "*" } @@ -2311,7 +2388,8 @@ "node_modules/@types/lru-cache": { "version": "5.1.1", "resolved": "https://registry.npmjs.org/@types/lru-cache/-/lru-cache-5.1.1.tgz", - "integrity": "sha512-ssE3Vlrys7sdIzs5LOxCzTVMsU7i9oa/IaW92wF32JFb3CVczqOkru2xspuKczHEbG3nvmPY7IFqVmGGHdNbYw==" + "integrity": "sha512-ssE3Vlrys7sdIzs5LOxCzTVMsU7i9oa/IaW92wF32JFb3CVczqOkru2xspuKczHEbG3nvmPY7IFqVmGGHdNbYw==", + "dev": true }, "node_modules/@types/minimatch": { "version": "5.1.2", @@ -2337,7 +2415,8 @@ "node_modules/@types/node": { "version": "18.11.19", "resolved": "https://registry.npmjs.org/@types/node/-/node-18.11.19.tgz", - "integrity": "sha512-YUgMWAQBWLObABqrvx8qKO1enAvBUdjZOAWQ5grBAkp5LQv45jBvYKZ3oFS9iKRCQyFjqw6iuEa1vmFqtxYLZw==" + "integrity": "sha512-YUgMWAQBWLObABqrvx8qKO1enAvBUdjZOAWQ5grBAkp5LQv45jBvYKZ3oFS9iKRCQyFjqw6iuEa1vmFqtxYLZw==", + "dev": true }, "node_modules/@types/node-fetch": { "version": "2.6.2", @@ -2353,6 +2432,7 @@ "version": "3.1.0", "resolved": "https://registry.npmjs.org/@types/pbkdf2/-/pbkdf2-3.1.0.tgz", "integrity": "sha512-Cf63Rv7jCQ0LaL8tNXmEyqTHuIJxRdlS5vMh1mj5voN4+QFhVZnlZruezqpWYDiJ8UTzhP0VmeLXCmBk66YrMQ==", + "dev": true, "dependencies": { "@types/node": "*" } @@ -2382,6 +2462,7 @@ "version": "4.0.3", "resolved": "https://registry.npmjs.org/@types/secp256k1/-/secp256k1-4.0.3.tgz", "integrity": "sha512-Da66lEIFeIz9ltsdMZcpQvmrmmoqrfju8pm1BH8WbYjZSwUgCwXLb9C+9XYogwBITnbsSaMdVPb2ekf7TV+03w==", + "dev": true, "dependencies": { "@types/node": "*" } @@ -2666,6 +2747,7 @@ "version": "3.0.0", "resolved": "https://registry.npmjs.org/abort-controller/-/abort-controller-3.0.0.tgz", "integrity": "sha512-h8lQ8tacZYnR3vNQTgibj+tODHI5/+l06Au2Pcriv/Gmet0eaj4TwWH41sO9wnHDiQsEj19q0drzdWdeAHtweg==", + "dev": true, "dependencies": { "event-target-shim": "^5.0.0" }, @@ -2677,6 +2759,7 @@ "version": "1.0.3", "resolved": "https://registry.npmjs.org/abstract-level/-/abstract-level-1.0.3.tgz", "integrity": "sha512-t6jv+xHy+VYwc4xqZMn2Pa9DjcdzvzZmQGRjTFc8spIbRGHgBrEKbPq+rYXc7CCo0lxgYvSgKVg9qZAhpVQSjA==", + "dev": true, "dependencies": { "buffer": "^6.0.3", "catering": "^2.1.0", @@ -2715,7 +2798,7 @@ "version": "8.2.0", "resolved": "https://registry.npmjs.org/acorn-walk/-/acorn-walk-8.2.0.tgz", "integrity": "sha512-k+iyHEuPgSw6SbuDpGQM+06HQUa04DZ3o+F6CSzXMvvI5KMvnaEqXe+YVe555R9nn6GPt404fos4wcgpw12SDA==", - "devOptional": true, + "dev": true, "engines": { "node": ">=0.4.0" } @@ -2733,6 +2816,7 @@ "version": "0.4.16", "resolved": "https://registry.npmjs.org/adm-zip/-/adm-zip-0.4.16.tgz", "integrity": "sha512-TFi4HBKSGfIKsK5YCkKaaFG2m4PEDyViZmEwof3MTIgzimHLto6muaHVpbrljdIvIrFZzEq/p4nafOeLcYegrg==", + "dev": true, "engines": { "node": ">=0.3.0" } @@ -2740,12 +2824,14 @@ "node_modules/aes-js": { "version": "3.0.0", "resolved": "https://registry.npmjs.org/aes-js/-/aes-js-3.0.0.tgz", - "integrity": "sha512-H7wUZRn8WpTq9jocdxQ2c8x2sKo9ZVmzfRE13GiNJXfp7NcKYEdvl3vspKjXox6RIG2VtaRe4JFvxG4rqp2Zuw==" + "integrity": "sha512-H7wUZRn8WpTq9jocdxQ2c8x2sKo9ZVmzfRE13GiNJXfp7NcKYEdvl3vspKjXox6RIG2VtaRe4JFvxG4rqp2Zuw==", + "dev": true }, "node_modules/agent-base": { "version": "6.0.2", "resolved": "https://registry.npmjs.org/agent-base/-/agent-base-6.0.2.tgz", "integrity": "sha512-RZNwNclF7+MS/8bDg70amg32dyeZGZxiDuQmZxKLAlQjr3jGyLx+4Kkk58UO7D2QdgFIQCovuSuZESne6RG6XQ==", + "dev": true, "dependencies": { "debug": "4" }, @@ -2757,6 +2843,7 @@ "version": "3.1.0", "resolved": "https://registry.npmjs.org/aggregate-error/-/aggregate-error-3.1.0.tgz", "integrity": "sha512-4I7Td01quW/RpocfNayFdFVk1qSuoh0E7JrbRJ16nH01HhKFQ88INq9Sd+nd72zqRySlr9BmDA8xlEJ6vJMrYA==", + "dev": true, "dependencies": { "clean-stack": "^2.0.0", "indent-string": "^4.0.0" @@ -2795,6 +2882,7 @@ "version": "4.1.3", "resolved": "https://registry.npmjs.org/ansi-colors/-/ansi-colors-4.1.3.tgz", "integrity": "sha512-/6w/C21Pm1A7aZitlI5Ni/2J6FFQN8i1Cvz3kHABAAbw93v/NlvKdVOqz7CCWz/3iv/JplRSEEZ83XION15ovw==", + "dev": true, "engines": { "node": ">=6" } @@ -2803,6 +2891,7 @@ "version": "4.3.2", "resolved": "https://registry.npmjs.org/ansi-escapes/-/ansi-escapes-4.3.2.tgz", "integrity": "sha512-gKXj5ALrKWQLsYG9jlTRmR/xKluxHV+Z9QEwNIgCfM1/uwPMCuzVVnh5mwTd+OuBZcwSIMbqssNWRm1lE51QaQ==", + "dev": true, "dependencies": { "type-fest": "^0.21.3" }, @@ -2817,6 +2906,7 @@ "version": "0.21.3", "resolved": "https://registry.npmjs.org/type-fest/-/type-fest-0.21.3.tgz", "integrity": "sha512-t0rzBq87m3fVcduHDUFhKmyyX+9eo6WQjZvf51Ea/M0Q7+T374Jp1aUiyUl0GKxp8M/OETVHSDvmkyPgvX+X2w==", + "dev": true, "engines": { "node": ">=10" }, @@ -2836,6 +2926,7 @@ "version": "3.2.1", "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-3.2.1.tgz", "integrity": "sha512-VT0ZI6kZRdTh8YyJw3SMbYm/u+NqfsAxEpWO0Pf9sq8/e94WxxOpPKx9FR1FlyCtOVDNOQ+8ntlqFxiRc+r5qA==", + "dev": true, "dependencies": { "color-convert": "^1.9.0" }, @@ -2871,7 +2962,7 @@ "version": "4.1.3", "resolved": "https://registry.npmjs.org/arg/-/arg-4.1.3.tgz", "integrity": "sha512-58S9QDqG0Xx27YwPSt9fJxivjYl432YCwfDMfZ+71RAqUrZef7LrKQZ3LHLOwCS4FLNBplP533Zx895SeOCHvA==", - "devOptional": true + "dev": true }, "node_modules/argparse": { "version": "1.0.10", @@ -3038,6 +3129,7 @@ "version": "2.6.4", "resolved": "https://registry.npmjs.org/async/-/async-2.6.4.tgz", "integrity": "sha512-mzo5dfJYwAn29PeiJ0zvwTo04zj8HDJj0Mn8TD7sno7q12prdbnasKJHhkm2c1LgrhlJ0teaea8860oxi51mGA==", + "dev": true, "dependencies": { "lodash": "^4.17.14" } @@ -3046,6 +3138,7 @@ "version": "0.2.4", "resolved": "https://registry.npmjs.org/async-eventemitter/-/async-eventemitter-0.2.4.tgz", "integrity": "sha512-pd20BwL7Yt1zwDFy+8MX8F1+WCT8aQeKj0kQnTrH9WaeRETlRamVhD0JtRPmrV4GfOJ2F9CvdQkZeZhnh2TuHw==", + "dev": true, "dependencies": { "async": "^2.4.0" } @@ -3101,6 +3194,7 @@ "version": "3.0.9", "resolved": "https://registry.npmjs.org/base-x/-/base-x-3.0.9.tgz", "integrity": "sha512-H7JU6iBHTal1gp56aKoaa//YUxEaAOUiydvrV/pILqIHXTtqxSkATOnDA2u+jZ/61sD+L/412+7kzXRtWukhpQ==", + "dev": true, "dependencies": { "safe-buffer": "^5.0.1" } @@ -3109,6 +3203,7 @@ "version": "1.5.1", "resolved": "https://registry.npmjs.org/base64-js/-/base64-js-1.5.1.tgz", "integrity": "sha512-AKpaYlHn8t4SVbOHCy+b5+KKgvR4vrsD8vbvrbiQJps7fKDTkjkDry6ji0rUJjC0kzbNePLwzxq8iypo41qeWA==", + "dev": true, "funding": [ { "type": "github", @@ -3142,12 +3237,14 @@ "node_modules/bech32": { "version": "1.1.4", "resolved": "https://registry.npmjs.org/bech32/-/bech32-1.1.4.tgz", - "integrity": "sha512-s0IrSOzLlbvX7yp4WBfPITzpAU8sqQcpsmwXDiKwrG4r491vwCO/XpejasRNl0piBMe/DvP4Tz0mIS/X1DPJBQ==" + "integrity": "sha512-s0IrSOzLlbvX7yp4WBfPITzpAU8sqQcpsmwXDiKwrG4r491vwCO/XpejasRNl0piBMe/DvP4Tz0mIS/X1DPJBQ==", + "dev": true }, "node_modules/bigint-crypto-utils": { "version": "3.1.8", "resolved": "https://registry.npmjs.org/bigint-crypto-utils/-/bigint-crypto-utils-3.1.8.tgz", "integrity": "sha512-+VMV9Laq8pXLBKKKK49nOoq9bfR3j7NNQAtbA617a4nw9bVLo8rsqkKMBgM2AJWlNX9fEIyYaYX+d0laqYV4tw==", + "dev": true, "dependencies": { "bigint-mod-arith": "^3.1.0" }, @@ -3159,6 +3256,7 @@ "version": "3.1.2", "resolved": "https://registry.npmjs.org/bigint-mod-arith/-/bigint-mod-arith-3.1.2.tgz", "integrity": "sha512-nx8J8bBeiRR+NlsROFH9jHswW5HO8mgfOSqW0AmjicMMvaONDa8AO+5ViKDUUNytBPWiwfvZP4/Bj4Y3lUfvgQ==", + "dev": true, "engines": { "node": ">=10.4.0" } @@ -3174,7 +3272,8 @@ "node_modules/blakejs": { "version": "1.2.1", "resolved": "https://registry.npmjs.org/blakejs/-/blakejs-1.2.1.tgz", - "integrity": "sha512-QXUSXI3QVc/gJME0dBpXrag1kbzOqCjCX8/b54ntNyW6sjtoqxqRk3LTmXzaJoh71zMsDCjM+47jS7XiwN/+fQ==" + "integrity": "sha512-QXUSXI3QVc/gJME0dBpXrag1kbzOqCjCX8/b54ntNyW6sjtoqxqRk3LTmXzaJoh71zMsDCjM+47jS7XiwN/+fQ==", + "dev": true }, "node_modules/bluebird": { "version": "3.7.2", @@ -3185,7 +3284,8 @@ "node_modules/bn.js": { "version": "5.2.1", "resolved": "https://registry.npmjs.org/bn.js/-/bn.js-5.2.1.tgz", - "integrity": "sha512-eXRvHzWyYPBuB4NBy0cmYQjGitUrtqwbvlzP3G6VFnNRbsZQIxQ10PbKKHt8gZ/HW/D/747aDl+QkDqg3KQLMQ==" + "integrity": "sha512-eXRvHzWyYPBuB4NBy0cmYQjGitUrtqwbvlzP3G6VFnNRbsZQIxQ10PbKKHt8gZ/HW/D/747aDl+QkDqg3KQLMQ==", + "dev": true }, "node_modules/brace-expansion": { "version": "1.1.11", @@ -3210,12 +3310,14 @@ "node_modules/brorand": { "version": "1.1.0", "resolved": "https://registry.npmjs.org/brorand/-/brorand-1.1.0.tgz", - "integrity": "sha512-cKV8tMCEpQs4hK/ik71d6LrPOnpkpGBR0wzxqr68g2m/LB2GxVYQroAjMJZRVM1Y4BCjCKc3vAamxSzOY2RP+w==" + "integrity": "sha512-cKV8tMCEpQs4hK/ik71d6LrPOnpkpGBR0wzxqr68g2m/LB2GxVYQroAjMJZRVM1Y4BCjCKc3vAamxSzOY2RP+w==", + "dev": true }, "node_modules/browser-level": { "version": "1.0.1", "resolved": "https://registry.npmjs.org/browser-level/-/browser-level-1.0.1.tgz", "integrity": "sha512-XECYKJ+Dbzw0lbydyQuJzwNXtOpbMSq737qxJN11sIRTErOMShvDpbzTlgju7orJKvx4epULolZAuJGLzCmWRQ==", + "dev": true, "dependencies": { "abstract-level": "^1.0.2", "catering": "^2.1.1", @@ -3232,6 +3334,7 @@ "version": "1.2.0", "resolved": "https://registry.npmjs.org/browserify-aes/-/browserify-aes-1.2.0.tgz", "integrity": "sha512-+7CHXqGuspUn/Sl5aO7Ea0xWGAtETPXNSAjHo48JfLdPWcMng33Xe4znFvQweqc/uzk5zSOI3H52CYnjCfb5hA==", + "dev": true, "dependencies": { "buffer-xor": "^1.0.3", "cipher-base": "^1.0.0", @@ -3245,6 +3348,7 @@ "version": "4.0.1", "resolved": "https://registry.npmjs.org/bs58/-/bs58-4.0.1.tgz", "integrity": "sha512-Ok3Wdf5vOIlBrgCvTq96gBkJw+JUEzdBgyaza5HLtPm7yTHkjRy8+JzNyHF7BHa0bNWOQIp3m5YF0nnFcOIKLw==", + "dev": true, "dependencies": { "base-x": "^3.0.2" } @@ -3253,6 +3357,7 @@ "version": "2.1.2", "resolved": "https://registry.npmjs.org/bs58check/-/bs58check-2.1.2.tgz", "integrity": "sha512-0TS1jicxdU09dwJMNZtVAfzPi6Q6QeN0pM1Fkzrjn+XYHvzMKPU3pHVpva+769iNVSfIYWf7LJ6WR+BuuMf8cA==", + "dev": true, "dependencies": { "bs58": "^4.0.0", "create-hash": "^1.1.0", @@ -3263,6 +3368,7 @@ "version": "6.0.3", "resolved": "https://registry.npmjs.org/buffer/-/buffer-6.0.3.tgz", "integrity": "sha512-FTiCpNxtwiZZHEZbcbTIcZjERVICn9yq/pDFkTl95/AxzD1naBctN7YO68riM/gLSDY7sdrMby8hofADYuuqOA==", + "dev": true, "funding": [ { "type": "github", @@ -3285,17 +3391,20 @@ "node_modules/buffer-from": { "version": "1.1.2", "resolved": "https://registry.npmjs.org/buffer-from/-/buffer-from-1.1.2.tgz", - "integrity": "sha512-E+XQCRwSbaaiChtv6k6Dwgc+bx+Bs6vuKJHHl5kox/BaKbhiXzqQOwK4cO22yElGp2OCmjwVhT3HmxgyPGnJfQ==" + "integrity": "sha512-E+XQCRwSbaaiChtv6k6Dwgc+bx+Bs6vuKJHHl5kox/BaKbhiXzqQOwK4cO22yElGp2OCmjwVhT3HmxgyPGnJfQ==", + "dev": true }, "node_modules/buffer-xor": { "version": "1.0.3", "resolved": "https://registry.npmjs.org/buffer-xor/-/buffer-xor-1.0.3.tgz", - "integrity": "sha512-571s0T7nZWK6vB67HI5dyUF7wXiNcfaPPPTl6zYCNApANjIvYJTg7hlud/+cJpdAhS7dVzqMLmfhfHR3rAcOjQ==" + "integrity": "sha512-571s0T7nZWK6vB67HI5dyUF7wXiNcfaPPPTl6zYCNApANjIvYJTg7hlud/+cJpdAhS7dVzqMLmfhfHR3rAcOjQ==", + "dev": true }, "node_modules/busboy": { "version": "1.6.0", "resolved": "https://registry.npmjs.org/busboy/-/busboy-1.6.0.tgz", "integrity": "sha512-8SFQbg/0hQ9xy3UNTB0YEnsNBbWfhf7RtnzpL7TkBiTBRfrQ9Fxcnz7VJsleJpyp6rVLvXiuORqjlHi5q+PYuA==", + "dev": true, "dependencies": { "streamsearch": "^1.1.0" }, @@ -3307,6 +3416,7 @@ "version": "3.1.2", "resolved": "https://registry.npmjs.org/bytes/-/bytes-3.1.2.tgz", "integrity": "sha512-/Nf7TyzTx6S3yRJObOAV7956r8cr2+Oj8AC5dt8wSP3BQAoeX58NoHyCU8P8zGkNXStjTSi6fzO6F0pBdcYbEg==", + "dev": true, "engines": { "node": ">= 0.8" } @@ -3315,6 +3425,7 @@ "version": "1.0.2", "resolved": "https://registry.npmjs.org/call-bind/-/call-bind-1.0.2.tgz", "integrity": "sha512-7O+FbCihrB5WGbFYesctwmTKae6rOiIzmz1icreWJ+0aA7LJfuqhEso2T9ncpcFtzMQtzXf2QGGueWJGTYsqrA==", + "dev": true, "dependencies": { "function-bind": "^1.1.1", "get-intrinsic": "^1.0.2" @@ -3386,6 +3497,7 @@ "version": "2.1.1", "resolved": "https://registry.npmjs.org/catering/-/catering-2.1.1.tgz", "integrity": "sha512-K7Qy8O9p76sL3/3m7/zLKbRkyOlSZAgzEaLhyj2mXS8PsCud2Eo4hAb8aLtZqHh0QGqLcb9dlJSu6lHRVENm1w==", + "dev": true, "engines": { "node": ">=6" } @@ -3424,6 +3536,7 @@ "version": "2.4.2", "resolved": "https://registry.npmjs.org/chalk/-/chalk-2.4.2.tgz", "integrity": "sha512-Mti+f9lpJNcwF4tWV8/OrTTtF1gZi+f8FqlyAdouralcFWFQWF2+NgCHShjkCb+IFBLq9buZwE1xckQU4peSuQ==", + "dev": true, "dependencies": { "ansi-styles": "^3.2.1", "escape-string-regexp": "^1.0.5", @@ -3486,12 +3599,14 @@ "node_modules/ci-info": { "version": "2.0.0", "resolved": "https://registry.npmjs.org/ci-info/-/ci-info-2.0.0.tgz", - "integrity": "sha512-5tK7EtrZ0N+OLFMthtqOj4fI2Jeb88C4CAZPu25LDVUgXJ0A3Js4PMGqrn0JU1W0Mh1/Z8wZzYPxqUrXeBboCQ==" + "integrity": "sha512-5tK7EtrZ0N+OLFMthtqOj4fI2Jeb88C4CAZPu25LDVUgXJ0A3Js4PMGqrn0JU1W0Mh1/Z8wZzYPxqUrXeBboCQ==", + "dev": true }, "node_modules/cipher-base": { "version": "1.0.4", "resolved": "https://registry.npmjs.org/cipher-base/-/cipher-base-1.0.4.tgz", "integrity": "sha512-Kkht5ye6ZGmwv40uUDZztayT2ThLQGfnj/T71N/XzeZeo3nf8foyW7zGTsPYkEya3m5f3cAypH+qe7YOrM1U2Q==", + "dev": true, "dependencies": { "inherits": "^2.0.1", "safe-buffer": "^5.0.1" @@ -3501,6 +3616,7 @@ "version": "1.2.0", "resolved": "https://registry.npmjs.org/classic-level/-/classic-level-1.2.0.tgz", "integrity": "sha512-qw5B31ANxSluWz9xBzklRWTUAJ1SXIdaVKTVS7HcTGKOAmExx65Wo5BUICW+YGORe2FOUaDghoI9ZDxj82QcFg==", + "dev": true, "hasInstallScript": true, "dependencies": { "abstract-level": "^1.0.2", @@ -3517,6 +3633,7 @@ "version": "2.2.0", "resolved": "https://registry.npmjs.org/clean-stack/-/clean-stack-2.2.0.tgz", "integrity": "sha512-4diC9HaTE+KRAMWhDhrGOECgWZxoevMc5TlkObMqNSsVU62PYzXZ/SMTjzyGAFF1YusgxGcSWTEXBhp0CPwQ1A==", + "dev": true, "engines": { "node": ">=6" } @@ -3599,6 +3716,7 @@ "version": "1.9.3", "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-1.9.3.tgz", "integrity": "sha512-QfAUtd+vFdAtFQcC8CCyYt1fYWxSqAiK2cSD6zDB8N3cpsEBAvRxp9zOGg6G/SHHJYAT88/az/IuDGALsNVbGg==", + "dev": true, "dependencies": { "color-name": "1.1.3" } @@ -3606,7 +3724,8 @@ "node_modules/color-name": { "version": "1.1.3", "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.3.tgz", - "integrity": "sha512-72fSenhMw2HZMTVHeCA9KCmpEIbzWiQsjN+BHcBbS9vr1mtt+vJjPdksIBNUmKAW8TFUDPJK5SUU3QhE9NEXDw==" + "integrity": "sha512-72fSenhMw2HZMTVHeCA9KCmpEIbzWiQsjN+BHcBbS9vr1mtt+vJjPdksIBNUmKAW8TFUDPJK5SUU3QhE9NEXDw==", + "dev": true }, "node_modules/colors": { "version": "1.4.0", @@ -3632,7 +3751,8 @@ "node_modules/command-exists": { "version": "1.2.9", "resolved": "https://registry.npmjs.org/command-exists/-/command-exists-1.2.9.tgz", - "integrity": "sha512-LTQ/SGc+s0Xc0Fu5WaKnR0YiygZkm9eKFvyS+fRsU7/ZWFF8ykFM6Pc9aCVf1+xasOOZpO3BAVgVrKvsqKHV7w==" + "integrity": "sha512-LTQ/SGc+s0Xc0Fu5WaKnR0YiygZkm9eKFvyS+fRsU7/ZWFF8ykFM6Pc9aCVf1+xasOOZpO3BAVgVrKvsqKHV7w==", + "dev": true }, "node_modules/command-line-args": { "version": "4.0.7", @@ -3651,7 +3771,8 @@ "node_modules/commander": { "version": "3.0.2", "resolved": "https://registry.npmjs.org/commander/-/commander-3.0.2.tgz", - "integrity": "sha512-Gar0ASD4BDyKC4hl4DwHqDrmvjoxWKZigVnAbn5H1owvm4CxCPdb0HQDehwNYMJpla5+M2tPmPARzhtYuwpHow==" + "integrity": "sha512-Gar0ASD4BDyKC4hl4DwHqDrmvjoxWKZigVnAbn5H1owvm4CxCPdb0HQDehwNYMJpla5+M2tPmPARzhtYuwpHow==", + "dev": true }, "node_modules/compare-versions": { "version": "5.0.3", @@ -3722,6 +3843,7 @@ "version": "0.4.2", "resolved": "https://registry.npmjs.org/cookie/-/cookie-0.4.2.tgz", "integrity": "sha512-aSWTXFzaKWkvHO1Ny/s+ePFpvKsPnjc551iI41v3ny/ow6tBG5Vd+FuqGNhh1LxOmVzOlGUriIlOaokOvhaStA==", + "dev": true, "engines": { "node": ">= 0.6" } @@ -3773,6 +3895,7 @@ "version": "1.2.2", "resolved": "https://registry.npmjs.org/crc-32/-/crc-32-1.2.2.tgz", "integrity": "sha512-ROmzCKrTnOwybPcJApAA6WBWij23HVfGVNKqqrZpuyZOHqK2CwHSvpGuyt/UNNvaIjEd8X5IFGp4Mh+Ie1IHJQ==", + "dev": true, "bin": { "crc32": "bin/crc32.njs" }, @@ -3784,6 +3907,7 @@ "version": "1.2.0", "resolved": "https://registry.npmjs.org/create-hash/-/create-hash-1.2.0.tgz", "integrity": "sha512-z00bCGNHDG8mHAkP7CtT1qVu+bFQUPjYq/4Iv3C3kWjTFV10zIjfSoeqXo9Asws8gwSHDGj/hl2u4OGIjapeCg==", + "dev": true, "dependencies": { "cipher-base": "^1.0.1", "inherits": "^2.0.1", @@ -3796,6 +3920,7 @@ "version": "1.1.7", "resolved": "https://registry.npmjs.org/create-hmac/-/create-hmac-1.1.7.tgz", "integrity": "sha512-MJG9liiZ+ogc4TzUwuvbER1JRdgvUFSB5+VR/g5h82fGaIRWMWddtKBHi7/sVhfjQZ6SehlyhvQYrcYkaUIpLg==", + "dev": true, "dependencies": { "cipher-base": "^1.0.3", "create-hash": "^1.1.0", @@ -3809,7 +3934,7 @@ "version": "1.1.1", "resolved": "https://registry.npmjs.org/create-require/-/create-require-1.1.1.tgz", "integrity": "sha512-dcKFX3jn0MpIaXjisoRvexIJVEKzaq7z2rZKxf+MSr9TkdmHmsU4m2lcLojrj/FHl8mk5VxMmYA+ftRkP/3oKQ==", - "devOptional": true + "dev": true }, "node_modules/cross-spawn": { "version": "7.0.3", @@ -3926,6 +4051,7 @@ "version": "2.0.0", "resolved": "https://registry.npmjs.org/depd/-/depd-2.0.0.tgz", "integrity": "sha512-g7nH6P6dyDioJogAAGprGpCtVImJhpPk/roCzdb3fIh61/s/nPsfR6onyMwkCAR/OlC3yBC0lESvUoQEAssIrw==", + "dev": true, "engines": { "node": ">= 0.8" } @@ -4011,6 +4137,7 @@ "version": "6.5.4", "resolved": "https://registry.npmjs.org/elliptic/-/elliptic-6.5.4.tgz", "integrity": "sha512-iLhC6ULemrljPZb+QutR5TQGB+pdW6KGD5RSegS+8sorOZT+rdQFbsQFJgvN3eRqNALqJer4oQ16YvJHlU8hzQ==", + "dev": true, "dependencies": { "bn.js": "^4.11.9", "brorand": "^1.1.0", @@ -4024,7 +4151,8 @@ "node_modules/elliptic/node_modules/bn.js": { "version": "4.12.0", "resolved": "https://registry.npmjs.org/bn.js/-/bn.js-4.12.0.tgz", - "integrity": "sha512-c98Bf3tPniI+scsdk237ku1Dc3ujXQTSgyiPUDEOe7tRkhrqridvh8klBv0HCEso1OLOYcHuCv/cS6DNxKH+ZA==" + "integrity": "sha512-c98Bf3tPniI+scsdk237ku1Dc3ujXQTSgyiPUDEOe7tRkhrqridvh8klBv0HCEso1OLOYcHuCv/cS6DNxKH+ZA==", + "dev": true }, "node_modules/emoji-regex": { "version": "8.0.0", @@ -4035,6 +4163,7 @@ "version": "2.3.6", "resolved": "https://registry.npmjs.org/enquirer/-/enquirer-2.3.6.tgz", "integrity": "sha512-yjNnPr315/FjS4zIsUxYguYUPP2e1NK4d7E7ZOLiyYCcbFBiTMyID+2wvm2w6+pZ/odMA7cRkjhsPbltwBOrLg==", + "dev": true, "dependencies": { "ansi-colors": "^4.1.1" }, @@ -4046,6 +4175,7 @@ "version": "2.2.1", "resolved": "https://registry.npmjs.org/env-paths/-/env-paths-2.2.1.tgz", "integrity": "sha512-+h1lkLKhZMTYjog1VEpJNG7NZJWcuc2DDk/qsqSTRRCOXiLjeQ1d1/udrUGhqMxUgAlwKNZ0cf2uqan5GLuS2A==", + "dev": true, "engines": { "node": ">=6" } @@ -4164,6 +4294,7 @@ "version": "1.0.5", "resolved": "https://registry.npmjs.org/escape-string-regexp/-/escape-string-regexp-1.0.5.tgz", "integrity": "sha512-vbRorB5FUQWvla16U8R/qgaFIya2qGzwDrNmCZuYKrbdSUMG6I1ZCGQRefkRVhuOkIGVne7BQ35DSfo1qvJqFg==", + "dev": true, "engines": { "node": ">=0.8.0" } @@ -5444,6 +5575,7 @@ "version": "0.1.3", "resolved": "https://registry.npmjs.org/ethereum-cryptography/-/ethereum-cryptography-0.1.3.tgz", "integrity": "sha512-w8/4x1SGGzc+tO97TASLja6SLd3fRIK2tLVcV2Gx4IB21hE19atll5Cq9o3d0ZmAYC/8aw0ipieTSiekAea4SQ==", + "dev": true, "dependencies": { "@types/pbkdf2": "^3.0.0", "@types/secp256k1": "^4.0.1", @@ -5485,6 +5617,7 @@ "version": "0.6.8", "resolved": "https://registry.npmjs.org/ethereumjs-abi/-/ethereumjs-abi-0.6.8.tgz", "integrity": "sha512-Tx0r/iXI6r+lRsdvkFDlut0N08jWMnKRZ6Gkq+Nmw75lZe4e6o3EkSnkaBP5NF6+m5PTGAr9JP43N3LyeoglsA==", + "dev": true, "dependencies": { "bn.js": "^4.11.8", "ethereumjs-util": "^6.0.0" @@ -5494,6 +5627,7 @@ "version": "4.11.6", "resolved": "https://registry.npmjs.org/@types/bn.js/-/bn.js-4.11.6.tgz", "integrity": "sha512-pqr857jrp2kPuO9uRjZ3PwnJTjoQy+fcdxvBTvHm6dkmEL9q+hDD/2j/0ELOBPtPnS8LjCX0gI9nbl8lVkadpg==", + "dev": true, "dependencies": { "@types/node": "*" } @@ -5501,12 +5635,14 @@ "node_modules/ethereumjs-abi/node_modules/bn.js": { "version": "4.12.0", "resolved": "https://registry.npmjs.org/bn.js/-/bn.js-4.12.0.tgz", - "integrity": "sha512-c98Bf3tPniI+scsdk237ku1Dc3ujXQTSgyiPUDEOe7tRkhrqridvh8klBv0HCEso1OLOYcHuCv/cS6DNxKH+ZA==" + "integrity": "sha512-c98Bf3tPniI+scsdk237ku1Dc3ujXQTSgyiPUDEOe7tRkhrqridvh8klBv0HCEso1OLOYcHuCv/cS6DNxKH+ZA==", + "dev": true }, "node_modules/ethereumjs-abi/node_modules/ethereumjs-util": { "version": "6.2.1", "resolved": "https://registry.npmjs.org/ethereumjs-util/-/ethereumjs-util-6.2.1.tgz", "integrity": "sha512-W2Ktez4L01Vexijrm5EB6w7dg4n/TgpoYU4avuT5T3Vmnw/eCRtiBrJfQYS/DCSvDIOLn2k57GcHdeBcgVxAqw==", + "dev": true, "dependencies": { "@types/bn.js": "^4.11.3", "bn.js": "^4.11.0", @@ -5537,6 +5673,7 @@ "version": "5.7.2", "resolved": "https://registry.npmjs.org/ethers/-/ethers-5.7.2.tgz", "integrity": "sha512-wswUsmWo1aOK8rR7DIKiWSw9DbLWe6x98Jrn8wcTflTVvaXhAMaB5zGAXy0GYQEQp9iO1iSHWVyARQm11zUtyg==", + "dev": true, "funding": [ { "type": "individual", @@ -5604,6 +5741,7 @@ "version": "0.1.6", "resolved": "https://registry.npmjs.org/ethjs-util/-/ethjs-util-0.1.6.tgz", "integrity": "sha512-CUnVOQq7gSpDHZVVrQW8ExxUETWrnrvXYvYz55wOU8Uj4VCgw56XC2B/fVqQN+f7gmrnRHSLVnFAwsCuNwji8w==", + "dev": true, "dependencies": { "is-hex-prefixed": "1.0.0", "strip-hex-prefix": "1.0.0" @@ -5617,6 +5755,7 @@ "version": "5.0.1", "resolved": "https://registry.npmjs.org/event-target-shim/-/event-target-shim-5.0.1.tgz", "integrity": "sha512-i/2XbnSz/uxRCU6+NdVJgKWDTM427+MqYbkQzD321DuCQJUqOuJKIA0IM2+W2xtYHdKOmZ4dR6fExsd4SXL+WQ==", + "dev": true, "engines": { "node": ">=6" } @@ -5625,6 +5764,7 @@ "version": "1.0.3", "resolved": "https://registry.npmjs.org/evp_bytestokey/-/evp_bytestokey-1.0.3.tgz", "integrity": "sha512-/f2Go4TognH/KvCISP7OUsHn85hT9nUkxxA9BEWxFn+Oj9o8ZNLm/40hdlgSLyuOimsrTKLUMEorQexp/aPQeA==", + "dev": true, "dependencies": { "md5.js": "^1.3.4", "safe-buffer": "^5.1.1" @@ -5772,6 +5912,7 @@ "version": "2.1.0", "resolved": "https://registry.npmjs.org/find-up/-/find-up-2.1.0.tgz", "integrity": "sha512-NWzkk0jSJtTt08+FBFMvXoeZnOJD+jTtsRmBYbAIzJdX6l7dLgR7CTubCM5/eDdPUBvLCeVasP1brfVR/9/EZQ==", + "dev": true, "dependencies": { "locate-path": "^2.0.0" }, @@ -5819,6 +5960,7 @@ "version": "1.15.2", "resolved": "https://registry.npmjs.org/follow-redirects/-/follow-redirects-1.15.2.tgz", "integrity": "sha512-VQLG33o04KaQ8uYi2tVNbdrWp1QWxNNea+nmIB4EVM28v0hmP17z7aG1+wAkNzVq4KeXTq3221ye5qTJP91JwA==", + "dev": true, "funding": [ { "type": "individual", @@ -5869,12 +6011,14 @@ "node_modules/fp-ts": { "version": "1.19.3", "resolved": "https://registry.npmjs.org/fp-ts/-/fp-ts-1.19.3.tgz", - "integrity": "sha512-H5KQDspykdHuztLTg+ajGN0Z2qUjcEf3Ybxc6hLt0k7/zPkn29XnKnxlBPyW2XIddWrGaJBzBl4VLYOtk39yZg==" + "integrity": "sha512-H5KQDspykdHuztLTg+ajGN0Z2qUjcEf3Ybxc6hLt0k7/zPkn29XnKnxlBPyW2XIddWrGaJBzBl4VLYOtk39yZg==", + "dev": true }, "node_modules/fs-extra": { "version": "7.0.1", "resolved": "https://registry.npmjs.org/fs-extra/-/fs-extra-7.0.1.tgz", "integrity": "sha512-YJDaCJZEnBmcbw13fvdAM9AwNOJwOzrE4pqMqBq5nFiEqXUqHwlK4B+3pUw6JNvfSPtX05xFHtYy/1ni01eGCw==", + "dev": true, "dependencies": { "graceful-fs": "^4.1.2", "jsonfile": "^4.0.0", @@ -5911,7 +6055,8 @@ "node_modules/function-bind": { "version": "1.1.1", "resolved": "https://registry.npmjs.org/function-bind/-/function-bind-1.1.1.tgz", - "integrity": "sha512-yIovAzMX49sF8Yl58fSCWJ5svSLuaibPxXQJFLmBObTuCr0Mf1KiPopGM9NiFjiYBCbfaa2Fh6breQ6ANVTI0A==" + "integrity": "sha512-yIovAzMX49sF8Yl58fSCWJ5svSLuaibPxXQJFLmBObTuCr0Mf1KiPopGM9NiFjiYBCbfaa2Fh6breQ6ANVTI0A==", + "dev": true }, "node_modules/function.prototype.name": { "version": "1.1.5", @@ -5934,7 +6079,8 @@ "node_modules/functional-red-black-tree": { "version": "1.0.1", "resolved": "https://registry.npmjs.org/functional-red-black-tree/-/functional-red-black-tree-1.0.1.tgz", - "integrity": "sha512-dsKNQNdj6xA3T+QlADDA7mOSlX0qiMINjn0cgr+eGHGsbSHzTabcIogz2+p/iqP1Xs6EP/sS2SbqH+brGTbq0g==" + "integrity": "sha512-dsKNQNdj6xA3T+QlADDA7mOSlX0qiMINjn0cgr+eGHGsbSHzTabcIogz2+p/iqP1Xs6EP/sS2SbqH+brGTbq0g==", + "dev": true }, "node_modules/functions-have-names": { "version": "1.2.3", @@ -15144,6 +15290,7 @@ "version": "1.2.0", "resolved": "https://registry.npmjs.org/get-intrinsic/-/get-intrinsic-1.2.0.tgz", "integrity": "sha512-L049y6nFOuom5wGyRc3/gdTLO94dySVKRACj1RmJZBQXlbTMhtNIgkWkUHq+jYmZvKf14EW1EoJnnjbmoHij0Q==", + "dev": true, "dependencies": { "function-bind": "^1.1.1", "has": "^1.0.3", @@ -15333,7 +15480,8 @@ "node_modules/graceful-fs": { "version": "4.2.10", "resolved": "https://registry.npmjs.org/graceful-fs/-/graceful-fs-4.2.10.tgz", - "integrity": "sha512-9ByhssR2fPVsNZj478qUUbKfmL0+t5BDVyjShtyZZLiK7ZDAArFFfopyOTj0M05wE2tJPisA4iTnnXl2YoPvOA==" + "integrity": "sha512-9ByhssR2fPVsNZj478qUUbKfmL0+t5BDVyjShtyZZLiK7ZDAArFFfopyOTj0M05wE2tJPisA4iTnnXl2YoPvOA==", + "dev": true }, "node_modules/growl": { "version": "1.10.5", @@ -15401,6 +15549,7 @@ "version": "2.12.6", "resolved": "https://registry.npmjs.org/hardhat/-/hardhat-2.12.6.tgz", "integrity": "sha512-0Ent1O5DsPgvaVb5sxEgsQ3bJRt/Ex92tsoO+xjoNH2Qc4bFmhI5/CHVlFikulalxOPjNmw5XQ2vJFuVQFESAA==", + "dev": true, "dependencies": { "@ethersproject/abi": "^5.1.2", "@metamask/eth-sig-util": "^4.0.0", @@ -15502,6 +15651,7 @@ "version": "1.1.2", "resolved": "https://registry.npmjs.org/ethereum-cryptography/-/ethereum-cryptography-1.1.2.tgz", "integrity": "sha512-XDSJlg4BD+hq9N2FjvotwUET9Tfxpxc3kWGE2AqUG5vcbeunnbImVk3cj6e/xT3phdW21mE8R5IugU4fspQDcQ==", + "dev": true, "dependencies": { "@noble/hashes": "1.1.2", "@noble/secp256k1": "1.6.3", @@ -15513,6 +15663,7 @@ "version": "2.4.0", "resolved": "https://registry.npmjs.org/jsonfile/-/jsonfile-2.4.0.tgz", "integrity": "sha512-PKllAqbgLgxHaj8TElYymKCAgrASebJrWpTnEkOaTowt23VKXXN0sUeriJ+eh7y6ufb/CC5ap11pz71/cM0hUw==", + "dev": true, "optionalDependencies": { "graceful-fs": "^4.1.6" } @@ -15521,6 +15672,7 @@ "version": "1.17.0", "resolved": "https://registry.npmjs.org/resolve/-/resolve-1.17.0.tgz", "integrity": "sha512-ic+7JYiV8Vi2yzQGFWOkiZD5Z9z7O2Zhm9XMaTxdJExKasieFCr+yXZ/WmXsckHiKl12ar0y6XiXDx3m4RHn1w==", + "dev": true, "dependencies": { "path-parse": "^1.0.6" }, @@ -15532,6 +15684,7 @@ "version": "2.7.1", "resolved": "https://registry.npmjs.org/rimraf/-/rimraf-2.7.1.tgz", "integrity": "sha512-uWjbaKIK3T1OSVptzX7Nl6PvQ3qAGtKEtVRjRuazjfL3Bx5eI409VZSqgND+4UNnmzLVdPj9FqFJNPqBZFve4w==", + "dev": true, "dependencies": { "glob": "^7.1.3" }, @@ -15543,6 +15696,7 @@ "version": "0.7.3", "resolved": "https://registry.npmjs.org/solc/-/solc-0.7.3.tgz", "integrity": "sha512-GAsWNAjGzIDg7VxzP6mPjdurby3IkGCjQcM8GFYZT6RyaoUZKmMU6Y7YwG+tFGhv7dwZ8rmR4iwFDrrD99JwqA==", + "dev": true, "dependencies": { "command-exists": "^1.2.8", "commander": "3.0.2", @@ -15565,6 +15719,7 @@ "version": "0.30.0", "resolved": "https://registry.npmjs.org/fs-extra/-/fs-extra-0.30.0.tgz", "integrity": "sha512-UvSPKyhMn6LEd/WpUaV9C9t3zATuqoqfWc3QdPhPLb58prN9tqYPlPWi8Krxi44loBoUzlobqZ3+8tGpxxSzwA==", + "dev": true, "dependencies": { "graceful-fs": "^4.1.2", "jsonfile": "^2.1.0", @@ -15577,6 +15732,7 @@ "version": "5.7.1", "resolved": "https://registry.npmjs.org/semver/-/semver-5.7.1.tgz", "integrity": "sha512-sauaDf/PZdVgrLTNYHRtpXa1iRiKcaebiKQ1BJdpQlWH2lCvexQdX55snPFyK7QzpudqbCI0qXFfOasHdyNDGQ==", + "dev": true, "bin": { "semver": "bin/semver" } @@ -15585,6 +15741,7 @@ "version": "1.0.3", "resolved": "https://registry.npmjs.org/has/-/has-1.0.3.tgz", "integrity": "sha512-f2dvO0VU6Oej7RkWJGrehjbzMAjFp5/VKPp5tTpWIV4JHHZK1/BxbFRtf/siA2SWTe09caDmVtYYzWEIbBS4zw==", + "dev": true, "dependencies": { "function-bind": "^1.1.1" }, @@ -15605,6 +15762,7 @@ "version": "3.0.0", "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-3.0.0.tgz", "integrity": "sha512-sKJf1+ceQBr4SMkvQnBDNDtf4TXpVhVGateu0t918bl30FnbE2m4vNLX+VWe/dpjlb+HugGYzW7uQXH98HPEYw==", + "dev": true, "engines": { "node": ">=4" } @@ -15637,6 +15795,7 @@ "version": "1.0.3", "resolved": "https://registry.npmjs.org/has-symbols/-/has-symbols-1.0.3.tgz", "integrity": "sha512-l3LCuF6MgDNwTDKkdYGEihYjt5pRPbEg46rtlmnSPlUbgmB8LOIrKJbYYFBSbnPaJexMKtiPO8hmeRjRz2Td+A==", + "dev": true, "engines": { "node": ">= 0.4" }, @@ -15663,6 +15822,7 @@ "version": "3.1.0", "resolved": "https://registry.npmjs.org/hash-base/-/hash-base-3.1.0.tgz", "integrity": "sha512-1nmYp/rhMDiE7AYkDw+lLwlAzz0AntGIe51F3RfFfEqyQ3feY2eI/NcwC6umIQVOASPMsWJLJScWKSSvzL9IVA==", + "dev": true, "dependencies": { "inherits": "^2.0.4", "readable-stream": "^3.6.0", @@ -15676,6 +15836,7 @@ "version": "1.1.7", "resolved": "https://registry.npmjs.org/hash.js/-/hash.js-1.1.7.tgz", "integrity": "sha512-taOaskGt4z4SOANNseOviYDvjEJinIkRgmp7LbKP2YTTmVxWBl87s/uzK9r+44BclBSp2X7K1hqeNfz9JbBeXA==", + "dev": true, "dependencies": { "inherits": "^2.0.3", "minimalistic-assert": "^1.0.1" @@ -15699,6 +15860,7 @@ "version": "1.0.1", "resolved": "https://registry.npmjs.org/hmac-drbg/-/hmac-drbg-1.0.1.tgz", "integrity": "sha512-Tti3gMqLdZfhOQY1Mzf/AanLiqh1WTiJgEj26ZuYQ9fbkLomzGchCws4FyrSd4VkpBfiNhaE1On+lOz894jvXg==", + "dev": true, "dependencies": { "hash.js": "^1.0.3", "minimalistic-assert": "^1.0.0", @@ -15730,6 +15892,7 @@ "version": "2.0.0", "resolved": "https://registry.npmjs.org/http-errors/-/http-errors-2.0.0.tgz", "integrity": "sha512-FtwrG/euBzaEjYeRqOgly7G0qviiXoJWnvEH2Z1plBdXgbyjv34pHTSb9zoeHMyDy33+DWy5Wt9Wo+TURtOYSQ==", + "dev": true, "dependencies": { "depd": "2.0.0", "inherits": "2.0.4", @@ -15775,6 +15938,7 @@ "version": "5.0.1", "resolved": "https://registry.npmjs.org/https-proxy-agent/-/https-proxy-agent-5.0.1.tgz", "integrity": "sha512-dFcAjpTQFgoLMzC2VwU+C/CbS7uRL0lWmxDITmqm7C+7F0Odmj6s9l6alZc6AELXhrnggM2CeWSXHGOdX2YtwA==", + "dev": true, "dependencies": { "agent-base": "6", "debug": "4" @@ -15787,6 +15951,7 @@ "version": "0.4.24", "resolved": "https://registry.npmjs.org/iconv-lite/-/iconv-lite-0.4.24.tgz", "integrity": "sha512-v3MXnZAcvnywkTUEZomIActle7RXXeedOR31wwl7VlyoXO4Qi9arvSenNQWne1TcRwhCL1HwLI21bEqdpj8/rA==", + "dev": true, "dependencies": { "safer-buffer": ">= 2.1.2 < 3" }, @@ -15810,6 +15975,7 @@ "version": "1.2.1", "resolved": "https://registry.npmjs.org/ieee754/-/ieee754-1.2.1.tgz", "integrity": "sha512-dcyqhDvX1C46lXZcVqCpK+FtMRQVdIMN6/Df5js2zouUsqG7I6sFxitIC+7KYK29KdXOLHdu9zL4sFnoVQnqaA==", + "dev": true, "funding": [ { "type": "github", @@ -15837,7 +16003,8 @@ "node_modules/immutable": { "version": "4.2.3", "resolved": "https://registry.npmjs.org/immutable/-/immutable-4.2.3.tgz", - "integrity": "sha512-IHpmvaOIX4VLJwPOuQr1NpeBr2ZG6vpIj3blsLVxXRWJscLioaJRStqC+NcBsLeCDsnGlPpXd5/WZmnE7MbsKA==" + "integrity": "sha512-IHpmvaOIX4VLJwPOuQr1NpeBr2ZG6vpIj3blsLVxXRWJscLioaJRStqC+NcBsLeCDsnGlPpXd5/WZmnE7MbsKA==", + "dev": true }, "node_modules/import-fresh": { "version": "3.3.0", @@ -15868,6 +16035,7 @@ "version": "4.0.0", "resolved": "https://registry.npmjs.org/indent-string/-/indent-string-4.0.0.tgz", "integrity": "sha512-EdDDZu4A2OyIK7Lr/2zG+w5jmbuk1DVBnEwREQvBzspBJkCEbRa8GxU1lghYcaGJCnRWibjDXlq779X1/y5xwg==", + "dev": true, "engines": { "node": ">=8" } @@ -15982,6 +16150,7 @@ "version": "1.10.4", "resolved": "https://registry.npmjs.org/io-ts/-/io-ts-1.10.4.tgz", "integrity": "sha512-b23PteSnYXSONJ6JQXRAlvJhuw8KOtkqa87W4wDtvMrud/DTJd5X+NpOOI+O/zZwVq6v0VLAaJ+1EDViKEuN9g==", + "dev": true, "dependencies": { "fp-ts": "^1.0.0" } @@ -16049,6 +16218,7 @@ "version": "2.0.5", "resolved": "https://registry.npmjs.org/is-buffer/-/is-buffer-2.0.5.tgz", "integrity": "sha512-i2R6zNFDwgEHJyQUtJEk0XFi1i0dPFn/oqjK3/vPCcDeJvW5NQ83V8QbicfF1SupOaB0h8ntgBC2YiE7dfyctQ==", + "dev": true, "funding": [ { "type": "github", @@ -16174,6 +16344,7 @@ "version": "1.0.0", "resolved": "https://registry.npmjs.org/is-hex-prefixed/-/is-hex-prefixed-1.0.0.tgz", "integrity": "sha512-WvtOiug1VFrE9v1Cydwm+FnXd3+w9GaeVUss5W4v/SLy3UW00vP+6iNF2SdnfiBoLy4bTqVdkftNGTUeOFVsbA==", + "dev": true, "engines": { "node": ">=6.5.0", "npm": ">=3" @@ -16373,7 +16544,8 @@ "node_modules/js-sha3": { "version": "0.8.0", "resolved": "https://registry.npmjs.org/js-sha3/-/js-sha3-0.8.0.tgz", - "integrity": "sha512-gF1cRrHhIzNfToc802P800N8PpXS+evLLXfsVpowqmAFR9uwbi89WvXg2QspOmXL8QL86J4T1EpFu+yUkwJY3Q==" + "integrity": "sha512-gF1cRrHhIzNfToc802P800N8PpXS+evLLXfsVpowqmAFR9uwbi89WvXg2QspOmXL8QL86J4T1EpFu+yUkwJY3Q==", + "dev": true }, "node_modules/js-tokens": { "version": "4.0.0", @@ -16446,6 +16618,7 @@ "version": "4.0.0", "resolved": "https://registry.npmjs.org/jsonfile/-/jsonfile-4.0.0.tgz", "integrity": "sha512-m6F1R3z8jjlf2imQHS2Qez5sjKWQzbuuhuJ/FKYFRZvPE3PuHcSMVZzfsLhGVOkfd20obL5SWEBew5ShlquNxg==", + "dev": true, "optionalDependencies": { "graceful-fs": "^4.1.6" } @@ -16478,6 +16651,7 @@ "version": "3.0.3", "resolved": "https://registry.npmjs.org/keccak/-/keccak-3.0.3.tgz", "integrity": "sha512-JZrLIAJWuZxKbCilMpNz5Vj7Vtb4scDG3dMXLOsbzBmQGyjwE61BbW7bJkfKKCShXiQZt3T6sBgALRtmd+nZaQ==", + "dev": true, "hasInstallScript": true, "dependencies": { "node-addon-api": "^2.0.0", @@ -16501,6 +16675,7 @@ "version": "1.3.1", "resolved": "https://registry.npmjs.org/klaw/-/klaw-1.3.1.tgz", "integrity": "sha512-TED5xi9gGQjGpNnvRWknrwAB1eL5GciPfVFOt3Vk1OJCVDQbzuSfrF3hkUQKlsgKrG1F+0t5W0m+Fje1jIt8rw==", + "dev": true, "optionalDependencies": { "graceful-fs": "^4.1.9" } @@ -16530,6 +16705,7 @@ "version": "8.0.0", "resolved": "https://registry.npmjs.org/level/-/level-8.0.0.tgz", "integrity": "sha512-ypf0jjAk2BWI33yzEaaotpq7fkOPALKAgDBxggO6Q9HGX2MRXn0wbP1Jn/tJv1gtL867+YOjOB49WaUF3UoJNQ==", + "dev": true, "dependencies": { "browser-level": "^1.0.1", "classic-level": "^1.2.0" @@ -16546,6 +16722,7 @@ "version": "4.0.1", "resolved": "https://registry.npmjs.org/level-supports/-/level-supports-4.0.1.tgz", "integrity": "sha512-PbXpve8rKeNcZ9C1mUicC9auIYFyGpkV9/i6g76tLgANwWhtG2v7I4xNBUlkn3lE2/dZF3Pi0ygYGtLc4RXXdA==", + "dev": true, "engines": { "node": ">=12" } @@ -16554,6 +16731,7 @@ "version": "1.0.1", "resolved": "https://registry.npmjs.org/level-transcoder/-/level-transcoder-1.0.1.tgz", "integrity": "sha512-t7bFwFtsQeD8cl8NIoQ2iwxA0CL/9IFw7/9gAjOonH0PWTTiRfY7Hq+Ejbsxh86tXobDQ6IOiddjNYIfOBs06w==", + "dev": true, "dependencies": { "buffer": "^6.0.3", "module-error": "^1.0.1" @@ -16628,6 +16806,7 @@ "version": "2.0.0", "resolved": "https://registry.npmjs.org/locate-path/-/locate-path-2.0.0.tgz", "integrity": "sha512-NCI2kiDkyR7VeEKm27Kda/iQHyKJe1Bu0FlTbYp3CqJu+9IFe9bLyAjMxf5ZDDbEg+iMPzB5zYyUTSm8wVTKmA==", + "dev": true, "dependencies": { "p-locate": "^2.0.0", "path-exists": "^3.0.0" @@ -16639,7 +16818,8 @@ "node_modules/lodash": { "version": "4.17.21", "resolved": "https://registry.npmjs.org/lodash/-/lodash-4.17.21.tgz", - "integrity": "sha512-v2kDEe57lecTulaDIuNTPy3Ry4gLGJ6Z1O3vE1krgXZNrsQ+LFTGHVxVjcXPs17LhbZVGedAJv8XZ1tvj5FvSg==" + "integrity": "sha512-v2kDEe57lecTulaDIuNTPy3Ry4gLGJ6Z1O3vE1krgXZNrsQ+LFTGHVxVjcXPs17LhbZVGedAJv8XZ1tvj5FvSg==", + "dev": true }, "node_modules/lodash.assign": { "version": "4.2.0", @@ -16650,12 +16830,14 @@ "node_modules/lodash.isequal": { "version": "4.5.0", "resolved": "https://registry.npmjs.org/lodash.isequal/-/lodash.isequal-4.5.0.tgz", - "integrity": "sha512-pDo3lu8Jhfjqls6GkMgpahsF9kCyayhgykjyLMNFTKWrpVdAQtYyB4muAMWozBB4ig/dtWAmsMxLEI8wuz+DYQ==" + "integrity": "sha512-pDo3lu8Jhfjqls6GkMgpahsF9kCyayhgykjyLMNFTKWrpVdAQtYyB4muAMWozBB4ig/dtWAmsMxLEI8wuz+DYQ==", + "dev": true }, "node_modules/lodash.isequalwith": { "version": "4.4.0", "resolved": "https://registry.npmjs.org/lodash.isequalwith/-/lodash.isequalwith-4.4.0.tgz", - "integrity": "sha512-dcZON0IalGBpRmJBmMkaoV7d3I80R2O+FrzsZyHdNSFrANq/cgDqKQNmAHE8UEj4+QYWwwhkQOVdLHiAopzlsQ==" + "integrity": "sha512-dcZON0IalGBpRmJBmMkaoV7d3I80R2O+FrzsZyHdNSFrANq/cgDqKQNmAHE8UEj4+QYWwwhkQOVdLHiAopzlsQ==", + "dev": true }, "node_modules/lodash.merge": { "version": "4.6.2", @@ -16760,12 +16942,14 @@ "node_modules/lru_map": { "version": "0.3.3", "resolved": "https://registry.npmjs.org/lru_map/-/lru_map-0.3.3.tgz", - "integrity": "sha512-Pn9cox5CsMYngeDbmChANltQl+5pi6XmTrraMSzhPmMBbmgcxmqWry0U3PGapCU1yB4/LqCcom7qhHZiF/jGfQ==" + "integrity": "sha512-Pn9cox5CsMYngeDbmChANltQl+5pi6XmTrraMSzhPmMBbmgcxmqWry0U3PGapCU1yB4/LqCcom7qhHZiF/jGfQ==", + "dev": true }, "node_modules/lru-cache": { "version": "5.1.1", "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-5.1.1.tgz", "integrity": "sha512-KpNARQA3Iwv+jTA0utUVVbrh+Jlrr1Fv0e56GGzAFOXN7dk/FviaDW8LHmK52DlcH4WP2n6gI8vN1aesBFgo9w==", + "dev": true, "dependencies": { "yallist": "^3.0.2" } @@ -16774,7 +16958,7 @@ "version": "1.3.6", "resolved": "https://registry.npmjs.org/make-error/-/make-error-1.3.6.tgz", "integrity": "sha512-s8UhlNe7vPKomQhC1qFelMokr/Sc3AgNbso3n74mVPA5LTZwkB9NlXf4XPamLxJE8h0gh73rM94xvwRT2CVInw==", - "devOptional": true + "dev": true }, "node_modules/markdown-table": { "version": "1.1.3", @@ -16786,6 +16970,7 @@ "version": "0.7.9", "resolved": "https://registry.npmjs.org/mcl-wasm/-/mcl-wasm-0.7.9.tgz", "integrity": "sha512-iJIUcQWA88IJB/5L15GnJVnSQJmf/YaxxV6zRavv83HILHaJQb6y0iFyDMdDO0gN8X37tdxmAOrH/P8B6RB8sQ==", + "dev": true, "engines": { "node": ">=8.9.0" } @@ -16794,6 +16979,7 @@ "version": "1.3.5", "resolved": "https://registry.npmjs.org/md5.js/-/md5.js-1.3.5.tgz", "integrity": "sha512-xitP+WxNPcTTOgnTJcrhM0xvdPepipPSf3I8EIpGKeFLjt3PlJLIDG3u8EX53ZIubkb+5U2+3rELYpEhHhzdkg==", + "dev": true, "dependencies": { "hash-base": "^3.0.0", "inherits": "^2.0.1", @@ -16804,6 +16990,7 @@ "version": "1.0.0", "resolved": "https://registry.npmjs.org/memory-level/-/memory-level-1.0.0.tgz", "integrity": "sha512-UXzwewuWeHBz5krr7EvehKcmLFNoXxGcvuYhC41tRnkrTbJohtS7kVn9akmgirtRygg+f7Yjsfi8Uu5SGSQ4Og==", + "dev": true, "dependencies": { "abstract-level": "^1.0.0", "functional-red-black-tree": "^1.0.1", @@ -16817,6 +17004,7 @@ "version": "0.3.1", "resolved": "https://registry.npmjs.org/memorystream/-/memorystream-0.3.1.tgz", "integrity": "sha512-S3UwM3yj5mtUSEfP41UZmt/0SCoVYUcU1rkXv+BQ5Ig8ndL4sPoJNBUJERafdPb5jjHJGuMgytgKvKIf58XNBw==", + "dev": true, "engines": { "node": ">= 0.10.0" } @@ -16876,12 +17064,14 @@ "node_modules/minimalistic-assert": { "version": "1.0.1", "resolved": "https://registry.npmjs.org/minimalistic-assert/-/minimalistic-assert-1.0.1.tgz", - "integrity": "sha512-UtJcAD4yEaGtjPezWuO9wC4nwUnVH/8/Im3yEHQP4b67cXlD/Qr9hdITCU1xDbSEXg2XKNaP8jsReV7vQd00/A==" + "integrity": "sha512-UtJcAD4yEaGtjPezWuO9wC4nwUnVH/8/Im3yEHQP4b67cXlD/Qr9hdITCU1xDbSEXg2XKNaP8jsReV7vQd00/A==", + "dev": true }, "node_modules/minimalistic-crypto-utils": { "version": "1.0.1", "resolved": "https://registry.npmjs.org/minimalistic-crypto-utils/-/minimalistic-crypto-utils-1.0.1.tgz", - "integrity": "sha512-JIYlbt6g8i5jKfJ3xz7rF0LXmv2TkDxBLUkiBeZ7bAx4GnnNMr8xFpGnOxn6GhTEHx3SjRrZEoU+j04prX1ktg==" + "integrity": "sha512-JIYlbt6g8i5jKfJ3xz7rF0LXmv2TkDxBLUkiBeZ7bAx4GnnNMr8xFpGnOxn6GhTEHx3SjRrZEoU+j04prX1ktg==", + "dev": true }, "node_modules/minimatch": { "version": "3.1.2", @@ -16919,6 +17109,7 @@ "version": "0.38.5", "resolved": "https://registry.npmjs.org/mnemonist/-/mnemonist-0.38.5.tgz", "integrity": "sha512-bZTFT5rrPKtPJxj8KSV0WkPyNxl72vQepqqVUAW2ARUpUSF2qXMB6jZj7hW5/k7C1rtpzqbD/IIbJwLXUjCHeg==", + "dev": true, "dependencies": { "obliterator": "^2.0.0" } @@ -17112,6 +17303,7 @@ "version": "1.0.2", "resolved": "https://registry.npmjs.org/module-error/-/module-error-1.0.2.tgz", "integrity": "sha512-0yuvsqSCv8LbaOKhnsQ/T5JhyFlCYLPXK3U2sgV10zoKQwzs/MyfuQUOZQ1V/6OCOJsK/TRgNVrPuPDqtdMFtA==", + "dev": true, "engines": { "node": ">=10" } @@ -17141,7 +17333,8 @@ "node_modules/napi-macros": { "version": "2.0.0", "resolved": "https://registry.npmjs.org/napi-macros/-/napi-macros-2.0.0.tgz", - "integrity": "sha512-A0xLykHtARfueITVDernsAWdtIMbOJgKgcluwENp3AlsKN/PloyO10HtmoqnFAQAcxPkgZN7wdfPfEd0zNGxbg==" + "integrity": "sha512-A0xLykHtARfueITVDernsAWdtIMbOJgKgcluwENp3AlsKN/PloyO10HtmoqnFAQAcxPkgZN7wdfPfEd0zNGxbg==", + "dev": true }, "node_modules/natural-compare": { "version": "1.4.0", @@ -17164,7 +17357,8 @@ "node_modules/node-addon-api": { "version": "2.0.2", "resolved": "https://registry.npmjs.org/node-addon-api/-/node-addon-api-2.0.2.tgz", - "integrity": "sha512-Ntyt4AIXyaLIuMHF6IOoTakB3K+RWxwtsHNRxllEoA6vPwP9o4866g6YWDLUdnucilZhmkxiHwHr11gAENw+QA==" + "integrity": "sha512-Ntyt4AIXyaLIuMHF6IOoTakB3K+RWxwtsHNRxllEoA6vPwP9o4866g6YWDLUdnucilZhmkxiHwHr11gAENw+QA==", + "dev": true }, "node_modules/node-emoji": { "version": "1.11.0", @@ -17218,6 +17412,7 @@ "version": "4.6.0", "resolved": "https://registry.npmjs.org/node-gyp-build/-/node-gyp-build-4.6.0.tgz", "integrity": "sha512-NTZVKn9IylLwUzaKjkas1e4u2DLNcV4rdYagA4PWdPwW87Bi7z+BznyKSRwS/761tV/lzCGXplWsiaMjLqP2zQ==", + "dev": true, "bin": { "node-gyp-build": "bin.js", "node-gyp-build-optional": "optional.js", @@ -17325,6 +17520,7 @@ "version": "1.12.3", "resolved": "https://registry.npmjs.org/object-inspect/-/object-inspect-1.12.3.tgz", "integrity": "sha512-geUvdk7c+eizMNUDkRpW1wJwgfOiOeHbxBR/hLXK1aT6zmVSO0jsQcs7fj6MGw89jC/cjGfLcNOrtMYtGqm81g==", + "dev": true, "funding": { "url": "https://github.com/sponsors/ljharb" } @@ -17394,7 +17590,8 @@ "node_modules/obliterator": { "version": "2.0.4", "resolved": "https://registry.npmjs.org/obliterator/-/obliterator-2.0.4.tgz", - "integrity": "sha512-lgHwxlxV1qIg1Eap7LgIeoBWIMFibOjbrYPIPJZcI1mmGAI2m3lNYpK12Y+GBdPQ0U1hRwSord7GIaawz962qQ==" + "integrity": "sha512-lgHwxlxV1qIg1Eap7LgIeoBWIMFibOjbrYPIPJZcI1mmGAI2m3lNYpK12Y+GBdPQ0U1hRwSord7GIaawz962qQ==", + "dev": true }, "node_modules/once": { "version": "1.4.0", @@ -17465,6 +17662,7 @@ "version": "1.0.2", "resolved": "https://registry.npmjs.org/os-tmpdir/-/os-tmpdir-1.0.2.tgz", "integrity": "sha512-D2FR03Vir7FIu45XBY20mTb+/ZSWB00sjU9jdQXt83gDrI4Ztz5Fs7/yy74g2N5SVQY4xY1qDr4rNddwYRVX0g==", + "dev": true, "engines": { "node": ">=0.10.0" } @@ -17473,6 +17671,7 @@ "version": "1.3.0", "resolved": "https://registry.npmjs.org/p-limit/-/p-limit-1.3.0.tgz", "integrity": "sha512-vvcXsLAJ9Dr5rQOPk7toZQZJApBl2K4J6dANSsEuh6QI41JYcsS/qhTGa9ErIUUgK3WNQoJYvylxvjqmiqEA9Q==", + "dev": true, "dependencies": { "p-try": "^1.0.0" }, @@ -17484,6 +17683,7 @@ "version": "2.0.0", "resolved": "https://registry.npmjs.org/p-locate/-/p-locate-2.0.0.tgz", "integrity": "sha512-nQja7m7gSKuewoVRen45CtVfODR3crN3goVQ0DDZ9N3yHxgpkuBhZqsaiotSQRrADUrne346peY7kT3TSACykg==", + "dev": true, "dependencies": { "p-limit": "^1.1.0" }, @@ -17495,6 +17695,7 @@ "version": "4.0.0", "resolved": "https://registry.npmjs.org/p-map/-/p-map-4.0.0.tgz", "integrity": "sha512-/bjOqmgETBYB5BoEeGVea8dmvHb2m9GLy1E9W43yeyfP6QQCZGFNa+XRceJEuDB6zqr+gKpIAmlLebMpykw/MQ==", + "dev": true, "dependencies": { "aggregate-error": "^3.0.0" }, @@ -17509,6 +17710,7 @@ "version": "1.0.0", "resolved": "https://registry.npmjs.org/p-try/-/p-try-1.0.0.tgz", "integrity": "sha512-U1etNYuMJoIz3ZXSrrySFjsXQTWOx2/jdi86L+2pRvph/qMKL6sbcCYdH23fqsbm8TH2Gn0OybpT4eSFlCVHww==", + "dev": true, "engines": { "node": ">=4" } @@ -17777,6 +17979,7 @@ "version": "3.0.0", "resolved": "https://registry.npmjs.org/path-exists/-/path-exists-3.0.0.tgz", "integrity": "sha512-bpC7GYwiDYQ4wYLe+FA8lhRjhQCMcQGuSgGGqDkg/QerRWw9CmGRT0iSOVRSZJ29NMLZgIzqaljJ63oaL4NIJQ==", + "dev": true, "engines": { "node": ">=4" } @@ -17807,7 +18010,8 @@ "node_modules/path-parse": { "version": "1.0.7", "resolved": "https://registry.npmjs.org/path-parse/-/path-parse-1.0.7.tgz", - "integrity": "sha512-LDJzPVEEEPR+y48z93A0Ed0yXb8pAByGWo/k5YYdYgpY2/2EsOsksJrq7lOHxryrVOn1ejG6oAp8ahvOIQD8sw==" + "integrity": "sha512-LDJzPVEEEPR+y48z93A0Ed0yXb8pAByGWo/k5YYdYgpY2/2EsOsksJrq7lOHxryrVOn1ejG6oAp8ahvOIQD8sw==", + "dev": true }, "node_modules/path-type": { "version": "4.0.0", @@ -17831,6 +18035,7 @@ "version": "3.1.2", "resolved": "https://registry.npmjs.org/pbkdf2/-/pbkdf2-3.1.2.tgz", "integrity": "sha512-iuh7L6jA7JEGu2WxDwtQP1ddOpaJNC4KlDEFfdQajSGgGPNi4OyDc2R7QnbY2bR9QjBVGwgvTdNJZoE7RaxUMA==", + "dev": true, "dependencies": { "create-hash": "^1.1.2", "create-hmac": "^1.1.4", @@ -18036,6 +18241,7 @@ "version": "6.11.0", "resolved": "https://registry.npmjs.org/qs/-/qs-6.11.0.tgz", "integrity": "sha512-MvjoMCJwEarSbUYk5O+nmoSzSutSsTwF85zcHPQ9OrlFoZOYIjaqBAJIqIXjptyD5vThxGq52Xu/MaJzRkIk4Q==", + "dev": true, "dependencies": { "side-channel": "^1.0.4" }, @@ -18060,6 +18266,7 @@ "version": "1.2.3", "resolved": "https://registry.npmjs.org/queue-microtask/-/queue-microtask-1.2.3.tgz", "integrity": "sha512-NuaNSa6flKT5JaSYQzJok04JzTL1CA6aGhv5rfLW3PgqA+M2ChpZQnAC8h8i4ZFkBS8X5RqkDBHA7r4hej3K9A==", + "dev": true, "funding": [ { "type": "github", @@ -18087,6 +18294,7 @@ "version": "2.5.1", "resolved": "https://registry.npmjs.org/raw-body/-/raw-body-2.5.1.tgz", "integrity": "sha512-qqJBtEyVgS0ZmPGdCFPWJ3FreoqvG4MVQln/kCgF7Olq95IbOp0/BWyMwbdtn4VTvkM8Y7khCQ2Xgk/tcrCXig==", + "dev": true, "dependencies": { "bytes": "3.1.2", "http-errors": "2.0.0", @@ -18176,6 +18384,7 @@ "version": "3.6.0", "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-3.6.0.tgz", "integrity": "sha512-BViHy7LKeTz4oNnkcLJ+lVSL6vpiFeX6/d3oSH8zCW7UxP2onchk+vTGB143xuFjHS3deTgkKoXXymXqymiIdA==", + "dev": true, "dependencies": { "inherits": "^2.0.3", "string_decoder": "^1.1.1", @@ -18392,6 +18601,7 @@ "version": "2.0.2", "resolved": "https://registry.npmjs.org/require-from-string/-/require-from-string-2.0.2.tgz", "integrity": "sha512-Xf0nWe6RseziFMu+Ap9biiUbmplq6S9/p+7w7YXP/JBHhrUDDUhwa+vANyubuqfZWTveU//DYVGsDG7RKL/vEw==", + "dev": true, "engines": { "node": ">=0.10.0" } @@ -18479,6 +18689,7 @@ "version": "2.0.2", "resolved": "https://registry.npmjs.org/ripemd160/-/ripemd160-2.0.2.tgz", "integrity": "sha512-ii4iagi25WusVoiC4B4lq7pbXfAp3D9v5CwfkY33vffw2+pkDjY1D8GaN7spsxvCSx8dkPqOZCEZyfxcmJG2IA==", + "dev": true, "dependencies": { "hash-base": "^3.0.0", "inherits": "^2.0.1" @@ -18488,6 +18699,7 @@ "version": "2.2.7", "resolved": "https://registry.npmjs.org/rlp/-/rlp-2.2.7.tgz", "integrity": "sha512-d5gdPmgQ0Z+AklL2NVXr/IoSjNZFfTVvQWzL/AM2AOcSzYP2xjlb0AC8YyCLc41MSNf6P6QVtjgPdmVtzb+4lQ==", + "dev": true, "dependencies": { "bn.js": "^5.2.0" }, @@ -18531,6 +18743,7 @@ "version": "1.1.0", "resolved": "https://registry.npmjs.org/run-parallel-limit/-/run-parallel-limit-1.1.0.tgz", "integrity": "sha512-jJA7irRNM91jaKc3Hcl1npHsFLOXOoTkPCUL1JEa1R82O2miplXXRaGdjW/KM/98YQWDhJLiSs793CnXfblJUw==", + "dev": true, "funding": [ { "type": "github", @@ -18552,7 +18765,8 @@ "node_modules/rustbn.js": { "version": "0.2.0", "resolved": "https://registry.npmjs.org/rustbn.js/-/rustbn.js-0.2.0.tgz", - "integrity": "sha512-4VlvkRUuCJvr2J6Y0ImW7NvTCriMi7ErOAqWk1y69vAdoNIzCF3yPmgeNzx+RQTLEDFq5sHfscn1MwHxP9hNfA==" + "integrity": "sha512-4VlvkRUuCJvr2J6Y0ImW7NvTCriMi7ErOAqWk1y69vAdoNIzCF3yPmgeNzx+RQTLEDFq5sHfscn1MwHxP9hNfA==", + "dev": true }, "node_modules/rxjs": { "version": "6.6.7", @@ -18602,7 +18816,8 @@ "node_modules/safer-buffer": { "version": "2.1.2", "resolved": "https://registry.npmjs.org/safer-buffer/-/safer-buffer-2.1.2.tgz", - "integrity": "sha512-YZo3K82SD7Riyi0E1EQPojLz7kpepnSQI9IyPbHHg1XXXevb5dJI7tpyN2ADxGcQbHG7vcyRHk0cbwqcQriUtg==" + "integrity": "sha512-YZo3K82SD7Riyi0E1EQPojLz7kpepnSQI9IyPbHHg1XXXevb5dJI7tpyN2ADxGcQbHG7vcyRHk0cbwqcQriUtg==", + "dev": true }, "node_modules/sc-istanbul": { "version": "0.4.6", @@ -18706,12 +18921,14 @@ "node_modules/scrypt-js": { "version": "3.0.1", "resolved": "https://registry.npmjs.org/scrypt-js/-/scrypt-js-3.0.1.tgz", - "integrity": "sha512-cdwTTnqPu0Hyvf5in5asVdZocVDTNRmR7XEcJuIzMjJeSHybHl7vpB66AzwTaIg6CLSbtjcxc8fqcySfnTkccA==" + "integrity": "sha512-cdwTTnqPu0Hyvf5in5asVdZocVDTNRmR7XEcJuIzMjJeSHybHl7vpB66AzwTaIg6CLSbtjcxc8fqcySfnTkccA==", + "dev": true }, "node_modules/secp256k1": { "version": "4.0.3", "resolved": "https://registry.npmjs.org/secp256k1/-/secp256k1-4.0.3.tgz", "integrity": "sha512-NLZVf+ROMxwtEj3Xa562qgv2BK5e2WNmXPiOdVIPLgs6lyTzMvBq0aWTYMI5XCP9jZMVKOcqZLw/Wc4vDkuxhA==", + "dev": true, "hasInstallScript": true, "dependencies": { "elliptic": "^6.5.4", @@ -18726,6 +18943,7 @@ "version": "6.3.0", "resolved": "https://registry.npmjs.org/semver/-/semver-6.3.0.tgz", "integrity": "sha512-b39TBaTSfV6yBrapU89p5fKekE2m/NwnDocOVruQFS1/veMgdzuPcnOM34M6CwxW8jH/lxEa5rBoDeUwu5HHTw==", + "dev": true, "bin": { "semver": "bin/semver.js" } @@ -18747,17 +18965,20 @@ "node_modules/setimmediate": { "version": "1.0.5", "resolved": "https://registry.npmjs.org/setimmediate/-/setimmediate-1.0.5.tgz", - "integrity": "sha512-MATJdZp8sLqDl/68LfQmbP8zKPLQNV6BIZoIgrscFDQ+RsvK/BxeDQOgyxKKoh0y/8h3BqVFnCqQ/gd+reiIXA==" + "integrity": "sha512-MATJdZp8sLqDl/68LfQmbP8zKPLQNV6BIZoIgrscFDQ+RsvK/BxeDQOgyxKKoh0y/8h3BqVFnCqQ/gd+reiIXA==", + "dev": true }, "node_modules/setprototypeof": { "version": "1.2.0", "resolved": "https://registry.npmjs.org/setprototypeof/-/setprototypeof-1.2.0.tgz", - "integrity": "sha512-E5LDX7Wrp85Kil5bhZv46j8jOeboKq5JMmYM3gVGdGH8xFpPWXUMsNrlODCrkoxMEeNi/XZIwuRvY4XNwYMJpw==" + "integrity": "sha512-E5LDX7Wrp85Kil5bhZv46j8jOeboKq5JMmYM3gVGdGH8xFpPWXUMsNrlODCrkoxMEeNi/XZIwuRvY4XNwYMJpw==", + "dev": true }, "node_modules/sha.js": { "version": "2.4.11", "resolved": "https://registry.npmjs.org/sha.js/-/sha.js-2.4.11.tgz", "integrity": "sha512-QMEp5B7cftE7APOjk5Y6xgrbWu+WkLVQwk8JNjZ8nKRciZaByEW6MubieAiToS7+dwvrjGhH8jRXz3MVd0AYqQ==", + "dev": true, "dependencies": { "inherits": "^2.0.1", "safe-buffer": "^5.0.1" @@ -18821,6 +19042,7 @@ "version": "1.0.4", "resolved": "https://registry.npmjs.org/side-channel/-/side-channel-1.0.4.tgz", "integrity": "sha512-q5XPytqFEIKHkGdiMIrY10mvLRvnQh42/+GoBlFW3b2LXLE2xxJpZFdm94we0BaoV3RwJyGqg5wS7epxTv0Zvw==", + "dev": true, "dependencies": { "call-bind": "^1.0.0", "get-intrinsic": "^1.0.2", @@ -20024,6 +20246,7 @@ "version": "0.5.21", "resolved": "https://registry.npmjs.org/source-map-support/-/source-map-support-0.5.21.tgz", "integrity": "sha512-uBHU3L3czsIyYXKX88fdrGovxdSCoTGDRZ6SYXtSRxLZUzHg5P/66Ht6uoUlHu9EZod+inXhKo3qQgwXUT/y1w==", + "dev": true, "dependencies": { "buffer-from": "^1.0.0", "source-map": "^0.6.0" @@ -20033,6 +20256,7 @@ "version": "0.6.1", "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.6.1.tgz", "integrity": "sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g==", + "dev": true, "engines": { "node": ">=0.10.0" } @@ -20110,6 +20334,7 @@ "version": "0.1.10", "resolved": "https://registry.npmjs.org/stacktrace-parser/-/stacktrace-parser-0.1.10.tgz", "integrity": "sha512-KJP1OCML99+8fhOHxwwzyWrlUuVX5GQ0ZpJTd1DFXhdkrvg1szxfHhawXUZ3g9TkXORQd4/WG68jMlQZ2p8wlg==", + "dev": true, "dependencies": { "type-fest": "^0.7.1" }, @@ -20121,6 +20346,7 @@ "version": "0.7.1", "resolved": "https://registry.npmjs.org/type-fest/-/type-fest-0.7.1.tgz", "integrity": "sha512-Ne2YiiGN8bmrmJJEuTWTLJR32nh/JdL1+PSicowtNb0WFpn59GK8/lfD61bVtzguz7b3PBt74nxpv/Pw5po5Rg==", + "dev": true, "engines": { "node": ">=8" } @@ -20129,6 +20355,7 @@ "version": "2.0.1", "resolved": "https://registry.npmjs.org/statuses/-/statuses-2.0.1.tgz", "integrity": "sha512-RwNA9Z/7PrK06rYLIzFMlaF+l73iwpzsqRIFgbMLbTcLD6cOao82TaWefPXQvB2fOC4AjuYSEndS7N/mTCbkdQ==", + "dev": true, "engines": { "node": ">= 0.8" } @@ -20146,6 +20373,7 @@ "version": "1.1.0", "resolved": "https://registry.npmjs.org/streamsearch/-/streamsearch-1.1.0.tgz", "integrity": "sha512-Mcc5wHehp9aXz1ax6bZUyY5afg9u2rv5cqQI3mRrYkGC8rW2hM02jWuwjtL++LS5qinSyhj2QfLyNsuc+VsExg==", + "dev": true, "engines": { "node": ">=10.0.0" } @@ -20154,6 +20382,7 @@ "version": "1.3.0", "resolved": "https://registry.npmjs.org/string_decoder/-/string_decoder-1.3.0.tgz", "integrity": "sha512-hkRX8U1WjJFd8LsDJ2yQ/wWWxaopEsABU1XfkM8A+j0+85JAGppt16cr1Whg6KIbb4okU6Mql6BOj+uup/wKeA==", + "dev": true, "dependencies": { "safe-buffer": "~5.2.0" } @@ -20244,6 +20473,7 @@ "version": "1.0.0", "resolved": "https://registry.npmjs.org/strip-hex-prefix/-/strip-hex-prefix-1.0.0.tgz", "integrity": "sha512-q8d4ue7JGEiVcypji1bALTos+0pWtyGlivAWyPuTkHzuTCJqrK9sWxYQZUq6Nq3cuyv3bm734IhHvHtGGURU6A==", + "dev": true, "dependencies": { "is-hex-prefixed": "1.0.0" }, @@ -20267,6 +20497,7 @@ "version": "5.5.0", "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-5.5.0.tgz", "integrity": "sha512-QjVjwdXIt408MIiAqCX4oUKsgU2EqAGzs2Ppkm4aQYbjm+ZEWEcW4SfFNTr4uMNZma0ey4f5lgLrkB0aX0QMow==", + "dev": true, "dependencies": { "has-flag": "^3.0.0" }, @@ -20460,6 +20691,7 @@ "version": "0.0.33", "resolved": "https://registry.npmjs.org/tmp/-/tmp-0.0.33.tgz", "integrity": "sha512-jRCJlojKnZ3addtTOjdIqoRuPEKBvNXcGYqzO6zWZX8KfKEpnGY5jfggJQ3EjKuu8D4bJRr0y+cYJFmYbImXGw==", + "dev": true, "dependencies": { "os-tmpdir": "~1.0.2" }, @@ -20482,6 +20714,7 @@ "version": "1.0.1", "resolved": "https://registry.npmjs.org/toidentifier/-/toidentifier-1.0.1.tgz", "integrity": "sha512-o5sSPKEkg/DIQNmH43V0/uerLrpzVedkUh8tGNvaeXpfpuwjKenlSox/2O/BTlZUtEe+JG7s5YhEz608PlAHRA==", + "dev": true, "engines": { "node": ">=0.6" } @@ -20553,7 +20786,7 @@ "version": "10.9.1", "resolved": "https://registry.npmjs.org/ts-node/-/ts-node-10.9.1.tgz", "integrity": "sha512-NtVysVPkxxrwFGUUxGYhfux8k78pQB3JqYBXlLRZgdGUqTO5wU/UyHop5p70iEbGhB7q5KmiZiU0Y3KlJrScEw==", - "devOptional": true, + "dev": true, "dependencies": { "@cspotcode/source-map-support": "^0.8.0", "@tsconfig/node10": "^1.0.7", @@ -20596,7 +20829,7 @@ "version": "8.8.2", "resolved": "https://registry.npmjs.org/acorn/-/acorn-8.8.2.tgz", "integrity": "sha512-xjIYgE8HBrkpd/sJqOGNspf8uHG+NOHGOw6a/Urj8taM2EXfdNAH2oFcPeIFfsv3+kz/mJrS5VuMqbNLjCa2vw==", - "devOptional": true, + "dev": true, "bin": { "acorn": "bin/acorn" }, @@ -20608,7 +20841,7 @@ "version": "4.0.2", "resolved": "https://registry.npmjs.org/diff/-/diff-4.0.2.tgz", "integrity": "sha512-58lmxKSA4BNyLz+HHMUzlOEpg09FV+ev6ZMe3vJihgdxzgcwZ8VoEEPmALCZG9LmqfVoNMMKpttIYTVG6uDY7A==", - "devOptional": true, + "dev": true, "engines": { "node": ">=0.3.1" } @@ -20628,12 +20861,14 @@ "node_modules/tslib": { "version": "1.14.1", "resolved": "https://registry.npmjs.org/tslib/-/tslib-1.14.1.tgz", - "integrity": "sha512-Xni35NKzjgMrwevysHTCArtLDpPvye8zV/0E4EyYn43P7/7qvQwPh9BGkHewbMulVntbigmcT7rdX3BNo9wRJg==" + "integrity": "sha512-Xni35NKzjgMrwevysHTCArtLDpPvye8zV/0E4EyYn43P7/7qvQwPh9BGkHewbMulVntbigmcT7rdX3BNo9wRJg==", + "dev": true }, "node_modules/tsort": { "version": "0.0.1", "resolved": "https://registry.npmjs.org/tsort/-/tsort-0.0.1.tgz", - "integrity": "sha512-Tyrf5mxF8Ofs1tNoxA13lFeZ2Zrbd6cKbuH3V+MQ5sb6DtBj5FjrXVsRWT8YvNAQTqNoz66dz1WsbigI22aEnw==" + "integrity": "sha512-Tyrf5mxF8Ofs1tNoxA13lFeZ2Zrbd6cKbuH3V+MQ5sb6DtBj5FjrXVsRWT8YvNAQTqNoz66dz1WsbigI22aEnw==", + "dev": true }, "node_modules/tsutils": { "version": "3.21.0", @@ -20665,12 +20900,14 @@ "node_modules/tweetnacl": { "version": "1.0.3", "resolved": "https://registry.npmjs.org/tweetnacl/-/tweetnacl-1.0.3.tgz", - "integrity": "sha512-6rt+RN7aOi1nGMyC4Xa5DdYiukl2UWCbcJft7YhxReBGQD7OAM8Pbxw6YMo4r2diNEA8FEmu32YOn9rhaiE5yw==" + "integrity": "sha512-6rt+RN7aOi1nGMyC4Xa5DdYiukl2UWCbcJft7YhxReBGQD7OAM8Pbxw6YMo4r2diNEA8FEmu32YOn9rhaiE5yw==", + "dev": true }, "node_modules/tweetnacl-util": { "version": "0.15.1", "resolved": "https://registry.npmjs.org/tweetnacl-util/-/tweetnacl-util-0.15.1.tgz", - "integrity": "sha512-RKJBIj8lySrShN4w6i/BonWp2Z/uxwC3h4y7xsRrpP59ZboCd0GpEVsOnMDYLMmKBpYhb5TgHzZXy7wTfYFBRw==" + "integrity": "sha512-RKJBIj8lySrShN4w6i/BonWp2Z/uxwC3h4y7xsRrpP59ZboCd0GpEVsOnMDYLMmKBpYhb5TgHzZXy7wTfYFBRw==", + "dev": true }, "node_modules/type-check": { "version": "0.4.0", @@ -20765,7 +21002,7 @@ "version": "4.9.5", "resolved": "https://registry.npmjs.org/typescript/-/typescript-4.9.5.tgz", "integrity": "sha512-1FXk9E2Hm+QzZQ7z+McJiHL4NW1F2EzMu9Nq9i3zAaGqibafqYwCVU6WyWAuyQRRzOlxou8xZSyXLEN8oKj24g==", - "devOptional": true, + "dev": true, "bin": { "tsc": "bin/tsc", "tsserver": "bin/tsserver" @@ -20812,6 +21049,7 @@ "version": "5.17.1", "resolved": "https://registry.npmjs.org/undici/-/undici-5.17.1.tgz", "integrity": "sha512-qMwK+1tmJ6DsMlsl/AYsZXnkIraXmQzzBuPSiQXP6q0WvNO+dwfiBAhjZWmxxW4N0PS31fzB6ulDOUS6cv8oKw==", + "dev": true, "dependencies": { "busboy": "^1.6.0" }, @@ -20823,6 +21061,7 @@ "version": "0.1.2", "resolved": "https://registry.npmjs.org/universalify/-/universalify-0.1.2.tgz", "integrity": "sha512-rBJeI5CXAlmy1pV+617WB9J63U6XcazHHF2f2dbJix4XzpUF0RS3Zbj0FGIOCAva5P/d/GBOYaACQ1w+0azUkg==", + "dev": true, "engines": { "node": ">= 4.0.0" } @@ -20831,6 +21070,7 @@ "version": "1.0.0", "resolved": "https://registry.npmjs.org/unpipe/-/unpipe-1.0.0.tgz", "integrity": "sha512-pjy2bYhSsufwWlKwPc+l3cN7+wuJlK6uz0YdJEOlQDbl6jo/YlPi4mb8agUkVC8BF7V8NuzeyPNqRksA3hztKQ==", + "dev": true, "engines": { "node": ">= 0.8" } @@ -20869,12 +21109,14 @@ "node_modules/util-deprecate": { "version": "1.0.2", "resolved": "https://registry.npmjs.org/util-deprecate/-/util-deprecate-1.0.2.tgz", - "integrity": "sha512-EPD5q1uXyFxJpCrLnCc1nHnq3gOa6DZBocAIiI2TaSCA7VCJ1UJDMagCzIkXNsUYfD1daK//LTEQ8xiIbrHtcw==" + "integrity": "sha512-EPD5q1uXyFxJpCrLnCc1nHnq3gOa6DZBocAIiI2TaSCA7VCJ1UJDMagCzIkXNsUYfD1daK//LTEQ8xiIbrHtcw==", + "dev": true }, "node_modules/uuid": { "version": "8.3.2", "resolved": "https://registry.npmjs.org/uuid/-/uuid-8.3.2.tgz", "integrity": "sha512-+NYs2QeMWy+GWFOEm9xnn6HCDp0l7QBD7ml8zLUmJ+93Q5NF0NocErnwkTkXVFNiX3/fpC6afS8Dhb/gz7R7eg==", + "dev": true, "bin": { "uuid": "dist/bin/uuid" } @@ -20889,7 +21131,7 @@ "version": "3.0.1", "resolved": "https://registry.npmjs.org/v8-compile-cache-lib/-/v8-compile-cache-lib-3.0.1.tgz", "integrity": "sha512-wa7YjyUGfNZngI/vtK0UHAN+lgDCxBPCylVXGp0zu59Fz5aiGtNXaq3DhIov063MorB+VfufLh3JlF2KdTK3xg==", - "devOptional": true + "dev": true }, "node_modules/validate-npm-package-license": { "version": "3.0.4", @@ -21135,6 +21377,7 @@ "version": "7.4.6", "resolved": "https://registry.npmjs.org/ws/-/ws-7.4.6.tgz", "integrity": "sha512-YmhHDO4MzaDLB+M9ym/mDA5z0naX8j7SIlT8f8z+I0VtzsRbekxEutHSme7NPS2qE8StCYQNUnfWdXta/Yu85A==", + "dev": true, "engines": { "node": ">=8.3.0" }, @@ -21171,7 +21414,8 @@ "node_modules/yallist": { "version": "3.1.1", "resolved": "https://registry.npmjs.org/yallist/-/yallist-3.1.1.tgz", - "integrity": "sha512-a4UGQaWPH59mOXUYnAG2ewncQS4i4F43Tv3JoAM+s2VDAmS9NsK8GpDMLrCHPksFT7h3K6TOoUNn2pb7RoXx4g==" + "integrity": "sha512-a4UGQaWPH59mOXUYnAG2ewncQS4i4F43Tv3JoAM+s2VDAmS9NsK8GpDMLrCHPksFT7h3K6TOoUNn2pb7RoXx4g==", + "dev": true }, "node_modules/yaml": { "version": "1.10.2", @@ -21246,7 +21490,7 @@ "version": "3.1.1", "resolved": "https://registry.npmjs.org/yn/-/yn-3.1.1.tgz", "integrity": "sha512-Ux4ygGWsu2c7isFWe8Yu1YluJmqVhxqK2cLXNQA5AcC3QfbGNpM7fu0Y8b/z16pXLnFxZYvWhd3fhBY9DLmC6Q==", - "devOptional": true, + "dev": true, "engines": { "node": ">=6" } diff --git a/package.json b/package.json index d353594..278c894 100644 --- a/package.json +++ b/package.json @@ -29,6 +29,7 @@ "@typescript-eslint/eslint-plugin": "^4.33.0", "@typescript-eslint/parser": "^4.33.0", "chai": "^4.3.6", + "decimal.js": "^10.4.3", "dotenv": "^10.0.0", "eslint": "^7.32.0", "eslint-config-prettier": "^8.5.0", @@ -51,8 +52,8 @@ "typescript": "^4.6.2" }, "dependencies": { - "@openzeppelin/contracts": "4.5.0", - "@openzeppelin/contracts-upgradeable": "4.5.2", + "@openzeppelin/contracts": "4.9.6", + "@openzeppelin/contracts-upgradeable": "4.9.6", "mocha": "^10.2.0" } -} +} \ No newline at end of file diff --git a/test/BackedTokenImplementationWithMultiplierAndAutoFeeAccrual.ts b/test/BackedTokenImplementationWithMultiplierAndAutoFeeAccrual.ts index 7863587..3be62ea 100644 --- a/test/BackedTokenImplementationWithMultiplierAndAutoFeeAccrual.ts +++ b/test/BackedTokenImplementationWithMultiplierAndAutoFeeAccrual.ts @@ -59,9 +59,7 @@ describe.only("BackedTokenImplementationWithMultiplierAndAutoFeeAccrual", functi 'initialize', [ "Backed Test Token", - "bTest", - baseTime, - accrualPeriodLength + "bTest" ] )); token = BackedTokenImplementationWithMultiplierAndAutoFeeAccrual__factory.connect(tokenProxy.address, owner.signer); @@ -71,6 +69,8 @@ describe.only("BackedTokenImplementationWithMultiplierAndAutoFeeAccrual", functi await token.setMultiplierUpdater(owner.address); await token.setSanctionsList((await new SanctionsListMock__factory(owner.signer).deploy()).address); await token.updateFeePerPeriod(baseFeePerPeriod); + await token.setLastTimeFeeApplied(baseTime); + await token.setPeriodLength(accrualPeriodLength); }); describe('#updateModifier', () => { From 1c9dcd8563b9ae28bfe9da39104bb565484914e4 Mon Sep 17 00:00:00 2001 From: Miniroman Date: Fri, 14 Jun 2024 12:19:13 +0200 Subject: [PATCH 04/17] Fix transferFrom inheritance --- ...BackedTokenImplementationWithMultiplierAndAutoFeeAccrual.sol | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/contracts/BackedTokenImplementationWithMultiplierAndAutoFeeAccrual.sol b/contracts/BackedTokenImplementationWithMultiplierAndAutoFeeAccrual.sol index 8ef0989..dd699c2 100644 --- a/contracts/BackedTokenImplementationWithMultiplierAndAutoFeeAccrual.sol +++ b/contracts/BackedTokenImplementationWithMultiplierAndAutoFeeAccrual.sol @@ -305,7 +305,7 @@ contract BackedTokenImplementationWithMultiplierAndAutoFeeAccrual is address to, uint256 amount ) public virtual override updateMultiplier returns (bool) { - return this.transferFrom(from, to, amount); + return super.transferFrom(from, to, amount); } /** From d6d30fe52953338e4e382e7a7a9697e7383d7cb9 Mon Sep 17 00:00:00 2001 From: Miniroman Date: Fri, 14 Jun 2024 14:33:42 +0200 Subject: [PATCH 05/17] Inherit from OpenZeppelin ERC20Upgradeable contract --- contracts/ERC20UpgradeableWithMultiplier.sol | 252 +------------------ package-lock.json | 7 + 2 files changed, 15 insertions(+), 244 deletions(-) diff --git a/contracts/ERC20UpgradeableWithMultiplier.sol b/contracts/ERC20UpgradeableWithMultiplier.sol index 33413eb..9488252 100644 --- a/contracts/ERC20UpgradeableWithMultiplier.sol +++ b/contracts/ERC20UpgradeableWithMultiplier.sol @@ -3,10 +3,7 @@ pragma solidity ^0.8.0; -import "@openzeppelin/contracts-upgradeable/token/ERC20/IERC20Upgradeable.sol"; -import "@openzeppelin/contracts-upgradeable/token/ERC20/extensions/IERC20MetadataUpgradeable.sol"; -import "@openzeppelin/contracts-upgradeable/utils/ContextUpgradeable.sol"; -import "@openzeppelin/contracts-upgradeable/proxy/utils/Initializable.sol"; +import "@openzeppelin/contracts-upgradeable/token/ERC20/ERC20Upgradeable.sol"; /** * @dev Implementation of the {IERC20} interface. @@ -33,16 +30,11 @@ import "@openzeppelin/contracts-upgradeable/proxy/utils/Initializable.sol"; * functions have been added to mitigate the well-known issues around setting * allowances. See {IERC20-approve}. */ -contract ERC20UpgradeableWithMultiplier is Initializable, ContextUpgradeable, IERC20Upgradeable, IERC20MetadataUpgradeable { +contract ERC20UpgradeableWithMultiplier is ERC20Upgradeable { mapping(address => uint256) private _shares; - mapping(address => mapping(address => uint256)) private _allowances; - uint256 internal _totalShares; - string private _name; - string private _symbol; - /** * @dev Defines ratio between a single share of a token to balance of a token. * Defined in 1e18 precision. @@ -72,48 +64,14 @@ contract ERC20UpgradeableWithMultiplier is Initializable, ContextUpgradeable, IE * All two of these values are immutable: they can only be set once during * construction. */ - function __ERC20_init(string memory name_, string memory symbol_) internal onlyInitializing { - __ERC20_init_unchained(name_, symbol_); + function __Multiplier_init() internal onlyInitializing { + __Multiplier_init_unchained(); } - function __ERC20_init_unchained(string memory name_, string memory symbol_) internal onlyInitializing { - _name = name_; - _symbol = symbol_; + function __Multiplier_init_unchained() internal onlyInitializing { _multiplier = 1e18; } - - /** - * @dev Returns the name of the token. - */ - function name() public view virtual override returns (string memory) { - return _name; - } - - /** - * @dev Returns the symbol of the token, usually a shorter version of the - * name. - */ - function symbol() public view virtual override returns (string memory) { - return _symbol; - } - - /** - * @dev Returns the number of decimals used to get its user representation. - * For example, if `decimals` equals `2`, a balance of `505` tokens should - * be displayed to a user as `5.05` (`505 / 10 ** 2`). - * - * Tokens usually opt for a value of 18, imitating the relationship between - * Ether and Wei. This is the value {ERC20} uses, unless this function is - * overridden; - * - * NOTE: This information is only used for _display_ purposes: it in - * no way affects any of the arithmetic of the contract, including - * {IERC20-balanceOf} and {IERC20-transfer}. - */ - function decimals() public view virtual override returns (uint8) { - return 18; - } - + /** * @dev See {IERC20-totalSupply}. */ @@ -142,20 +100,6 @@ contract ERC20UpgradeableWithMultiplier is Initializable, ContextUpgradeable, IE return _shares[account]; } - /** - * @dev See {IERC20-transfer}. - * - * Requirements: - * - * - `to` cannot be the zero address. - * - the caller must have a balance of at least `amount`. - */ - function transfer(address to, uint256 amount) public virtual override returns (bool) { - address owner = _msgSender(); - _transfer(owner, to, amount); - return true; - } - /** * @dev Transfers underlying shares to destination account * @@ -170,99 +114,6 @@ contract ERC20UpgradeableWithMultiplier is Initializable, ContextUpgradeable, IE return true; } - /** - * @dev See {IERC20-allowance}. - */ - function allowance(address owner, address spender) public view virtual override returns (uint256) { - return _allowances[owner][spender]; - } - - /** - * @dev See {IERC20-approve}. - * - * NOTE: If `amount` is the maximum `uint256`, the allowance is not updated on - * `transferFrom`. This is semantically equivalent to an infinite approval. - * - * Requirements: - * - * - `spender` cannot be the zero address. - */ - function approve(address spender, uint256 amount) public virtual override returns (bool) { - address owner = _msgSender(); - _approve(owner, spender, amount); - return true; - } - - /** - * @dev See {IERC20-transferFrom}. - * - * Emits an {Approval} event indicating the updated allowance. This is not - * required by the EIP. See the note at the beginning of {ERC20}. - * - * NOTE: Does not update the allowance if the current allowance - * is the maximum `uint256`. - * - * Requirements: - * - * - `from` and `to` cannot be the zero address. - * - `from` must have a balance of at least `amount`. - * - the caller must have allowance for ``from``'s tokens of at least - * `amount`. - */ - function transferFrom( - address from, - address to, - uint256 amount - ) public virtual override returns (bool) { - address spender = _msgSender(); - _spendAllowance(from, spender, amount); - _transfer(from, to, amount); - return true; - } - - /** - * @dev Atomically increases the allowance granted to `spender` by the caller. - * - * This is an alternative to {approve} that can be used as a mitigation for - * problems described in {IERC20-approve}. - * - * Emits an {Approval} event indicating the updated allowance. - * - * Requirements: - * - * - `spender` cannot be the zero address. - */ - function increaseAllowance(address spender, uint256 addedValue) public virtual returns (bool) { - address owner = _msgSender(); - _approve(owner, spender, _allowances[owner][spender] + addedValue); - return true; - } - - /** - * @dev Atomically decreases the allowance granted to `spender` by the caller. - * - * This is an alternative to {approve} that can be used as a mitigation for - * problems described in {IERC20-approve}. - * - * Emits an {Approval} event indicating the updated allowance. - * - * Requirements: - * - * - `spender` cannot be the zero address. - * - `spender` must have allowance for the caller of at least - * `subtractedValue`. - */ - function decreaseAllowance(address spender, uint256 subtractedValue) public virtual returns (bool) { - address owner = _msgSender(); - uint256 currentAllowance = _allowances[owner][spender]; - require(currentAllowance >= subtractedValue, "ERC20: decreased allowance below zero"); - unchecked { - _approve(owner, spender, currentAllowance - subtractedValue); - } - - return true; - } - /** * @return the amount of shares that corresponds to `_underlyingAmount` underlying amount. */ @@ -329,7 +180,7 @@ contract ERC20UpgradeableWithMultiplier is Initializable, ContextUpgradeable, IE address from, address to, uint256 amount - ) internal virtual { + ) internal virtual override { uint256 _sharesToTransfer = getSharesByUnderlyingAmount(amount); _transferShares(from, to, _sharesToTransfer); } @@ -389,53 +240,6 @@ contract ERC20UpgradeableWithMultiplier is Initializable, ContextUpgradeable, IE _afterTokenTransfer(account, address(0), amount); } - /** - * @dev Sets `amount` as the allowance of `spender` over the `owner` s tokens. - * - * This internal function is equivalent to `approve`, and can be used to - * e.g. set automatic allowances for certain subsystems, etc. - * - * Emits an {Approval} event. - * - * Requirements: - * - * - `owner` cannot be the zero address. - * - `spender` cannot be the zero address. - */ - function _approve( - address owner, - address spender, - uint256 amount - ) internal virtual { - require(owner != address(0), "ERC20: approve from the zero address"); - require(spender != address(0), "ERC20: approve to the zero address"); - - _allowances[owner][spender] = amount; - emit Approval(owner, spender, amount); - } - - /** - * @dev Spend `amount` form the allowance of `owner` toward `spender`. - * - * Does not update the allowance amount in case of infinite allowance. - * Revert if not enough allowance is available. - * - * Might emit an {Approval} event. - */ - function _spendAllowance( - address owner, - address spender, - uint256 amount - ) internal virtual { - uint256 currentAllowance = allowance(owner, spender); - if (currentAllowance != type(uint256).max) { - require(currentAllowance >= amount, "ERC20: insufficient allowance"); - unchecked { - _approve(owner, spender, currentAllowance - amount); - } - } - } - /** * @dev Updates currently stored multiplier with a new value * @@ -448,50 +252,10 @@ contract ERC20UpgradeableWithMultiplier is Initializable, ContextUpgradeable, IE emit MultiplierUpdated(newMultiplier); } - /** - * @dev Hook that is called before any transfer of tokens. This includes - * minting and burning. - * - * Calling conditions: - * - * - when `from` and `to` are both non-zero, `amount` of ``from``'s tokens - * will be transferred to `to`. - * - when `from` is zero, `amount` tokens will be minted for `to`. - * - when `to` is zero, `amount` of ``from``'s tokens will be burned. - * - `from` and `to` are never both zero. - * - * To learn more about hooks, head to xref:ROOT:extending-contracts.adoc#using-hooks[Using Hooks]. - */ - function _beforeTokenTransfer( - address from, - address to, - uint256 amount - ) internal virtual {} - - /** - * @dev Hook that is called after any transfer of tokens. This includes - * minting and burning. - * - * Calling conditions: - * - * - when `from` and `to` are both non-zero, `amount` of ``from``'s tokens - * has been transferred to `to`. - * - when `from` is zero, `amount` tokens have been minted for `to`. - * - when `to` is zero, `amount` of ``from``'s tokens have been burned. - * - `from` and `to` are never both zero. - * - * To learn more about hooks, head to xref:ROOT:extending-contracts.adoc#using-hooks[Using Hooks]. - */ - function _afterTokenTransfer( - address from, - address to, - uint256 amount - ) internal virtual {} - /** * @dev This empty reserved space is put in place to allow future versions to add new * variables without shifting down storage in the inheritance chain. * See https://docs.openzeppelin.com/contracts/4.x/upgradeable#storage_gaps */ - uint256[44] private __gap; + uint256[47] private __gap; } diff --git a/package-lock.json b/package-lock.json index 33e9561..40be30e 100644 --- a/package-lock.json +++ b/package-lock.json @@ -28,6 +28,7 @@ "@typescript-eslint/eslint-plugin": "^4.33.0", "@typescript-eslint/parser": "^4.33.0", "chai": "^4.3.6", + "decimal.js": "^10.4.3", "dotenv": "^10.0.0", "eslint": "^7.32.0", "eslint-config-prettier": "^8.5.0", @@ -4004,6 +4005,12 @@ "url": "https://github.com/sponsors/sindresorhus" } }, + "node_modules/decimal.js": { + "version": "10.4.3", + "resolved": "https://registry.npmjs.org/decimal.js/-/decimal.js-10.4.3.tgz", + "integrity": "sha512-VBBaLc1MgL5XpzgIP7ny5Z6Nx3UrRkIViUkPUdtl9aya5amy3De1gsUUSB1g3+3sExYNjCAsAznmukyxCb1GRA==", + "dev": true + }, "node_modules/deep-eql": { "version": "4.1.3", "resolved": "https://registry.npmjs.org/deep-eql/-/deep-eql-4.1.3.tgz", From 61f37b1909a35568884d49659586515b74ad9fcb Mon Sep 17 00:00:00 2001 From: Miniroman Date: Fri, 14 Jun 2024 15:18:09 +0200 Subject: [PATCH 06/17] Inherit from OpenZeppelin ERC20Upgradeable contract --- .../BackedTokenImplementationWithMultiplierAndAutoFeeAccrual.sol | 1 + 1 file changed, 1 insertion(+) diff --git a/contracts/BackedTokenImplementationWithMultiplierAndAutoFeeAccrual.sol b/contracts/BackedTokenImplementationWithMultiplierAndAutoFeeAccrual.sol index dd699c2..72faa41 100644 --- a/contracts/BackedTokenImplementationWithMultiplierAndAutoFeeAccrual.sol +++ b/contracts/BackedTokenImplementationWithMultiplierAndAutoFeeAccrual.sol @@ -134,6 +134,7 @@ contract BackedTokenImplementationWithMultiplierAndAutoFeeAccrual is string memory symbol_ ) public initializer { __ERC20_init(name_, symbol_); + __Multiplier_init(); __Ownable_init(); _buildDomainSeparator(); _setTerms("https://www.backedassets.fi/legal-documentation"); // Default Terms From 785e67dc82210614cf07553f5764f710dece8aa4 Mon Sep 17 00:00:00 2001 From: Miniroman Date: Mon, 17 Jun 2024 15:30:28 +0200 Subject: [PATCH 07/17] Use new openzeppelin only for new contracts --- ...entationWithMultiplierAndAutoFeeAccrual.sol | 2 +- ...C20PermitDelegateTransferWithMultiplier.sol | 2 +- contracts/ERC20UpgradeableWithMultiplier.sol | 2 +- contracts/WrappedBackedToken.sol | 4 ++-- package-lock.json | 18 ++++++++++++++++-- package.json | 6 ++++-- 6 files changed, 25 insertions(+), 9 deletions(-) diff --git a/contracts/BackedTokenImplementationWithMultiplierAndAutoFeeAccrual.sol b/contracts/BackedTokenImplementationWithMultiplierAndAutoFeeAccrual.sol index 72faa41..7287ca5 100644 --- a/contracts/BackedTokenImplementationWithMultiplierAndAutoFeeAccrual.sol +++ b/contracts/BackedTokenImplementationWithMultiplierAndAutoFeeAccrual.sol @@ -36,7 +36,7 @@ pragma solidity 0.8.9; -import "@openzeppelin/contracts-upgradeable/access/OwnableUpgradeable.sol"; +import "@openzeppelin/contracts-upgradeable-new/access/OwnableUpgradeable.sol"; import "./ERC20PermitDelegateTransferWithMultiplier.sol"; import "./SanctionsList.sol"; diff --git a/contracts/ERC20PermitDelegateTransferWithMultiplier.sol b/contracts/ERC20PermitDelegateTransferWithMultiplier.sol index 271f959..5d05ce5 100644 --- a/contracts/ERC20PermitDelegateTransferWithMultiplier.sol +++ b/contracts/ERC20PermitDelegateTransferWithMultiplier.sol @@ -24,7 +24,7 @@ pragma solidity 0.8.9; import "./ERC20UpgradeableWithMultiplier.sol"; -import "@openzeppelin/contracts-upgradeable/utils/cryptography/ECDSAUpgradeable.sol"; +import "@openzeppelin/contracts-upgradeable-new/utils/cryptography/ECDSAUpgradeable.sol"; /** * @dev diff --git a/contracts/ERC20UpgradeableWithMultiplier.sol b/contracts/ERC20UpgradeableWithMultiplier.sol index 9488252..27af330 100644 --- a/contracts/ERC20UpgradeableWithMultiplier.sol +++ b/contracts/ERC20UpgradeableWithMultiplier.sol @@ -3,7 +3,7 @@ pragma solidity ^0.8.0; -import "@openzeppelin/contracts-upgradeable/token/ERC20/ERC20Upgradeable.sol"; +import "@openzeppelin/contracts-upgradeable-new/token/ERC20/ERC20Upgradeable.sol"; /** * @dev Implementation of the {IERC20} interface. diff --git a/contracts/WrappedBackedToken.sol b/contracts/WrappedBackedToken.sol index 67f3b49..6a71f7c 100644 --- a/contracts/WrappedBackedToken.sol +++ b/contracts/WrappedBackedToken.sol @@ -36,8 +36,8 @@ pragma solidity 0.8.9; -import "@openzeppelin/contracts-upgradeable/token/ERC20/extensions/ERC4626Upgradeable.sol"; -import "@openzeppelin/contracts-upgradeable/access/OwnableUpgradeable.sol"; +import "@openzeppelin/contracts-upgradeable-new/token/ERC20/extensions/ERC4626Upgradeable.sol"; +import "@openzeppelin/contracts-upgradeable-new/access/OwnableUpgradeable.sol"; import "./SanctionsList.sol"; /** diff --git a/package-lock.json b/package-lock.json index 40be30e..0163f74 100644 --- a/package-lock.json +++ b/package-lock.json @@ -9,8 +9,10 @@ "version": "1.1.0", "license": "MIT", "dependencies": { - "@openzeppelin/contracts": "4.9.6", - "@openzeppelin/contracts-upgradeable": "4.9.6", + "@openzeppelin/contracts": "4.5.0", + "@openzeppelin/contracts-new": "npm:@openzeppelin/contracts@4.9.6", + "@openzeppelin/contracts-upgradeable": "4.5.2", + "@openzeppelin/contracts-upgradeable-new": "npm:@openzeppelin/contracts-upgradeable@4.9.6", "mocha": "^10.2.0" }, "devDependencies": { @@ -1814,11 +1816,23 @@ } }, "node_modules/@openzeppelin/contracts": { + "version": "4.5.0", + "resolved": "https://registry.npmjs.org/@openzeppelin/contracts/-/contracts-4.5.0.tgz", + "integrity": "sha512-fdkzKPYMjrRiPK6K4y64e6GzULR7R7RwxSigHS8DDp7aWDeoReqsQI+cxHV1UuhAqX69L1lAaWDxenfP+xiqzA==" + }, + "node_modules/@openzeppelin/contracts-new": { + "name": "@openzeppelin/contracts", "version": "4.9.6", "resolved": "https://registry.npmjs.org/@openzeppelin/contracts/-/contracts-4.9.6.tgz", "integrity": "sha512-xSmezSupL+y9VkHZJGDoCBpmnB2ogM13ccaYDWqJTfS3dbuHkgjuwDFUmaFauBCboQMGB/S5UqUl2y54X99BmA==" }, "node_modules/@openzeppelin/contracts-upgradeable": { + "version": "4.5.2", + "resolved": "https://registry.npmjs.org/@openzeppelin/contracts-upgradeable/-/contracts-upgradeable-4.5.2.tgz", + "integrity": "sha512-xgWZYaPlrEOQo3cBj97Ufiuv79SPd8Brh4GcFYhPgb6WvAq4ppz8dWKL6h+jLAK01rUqMRp/TS9AdXgAeNvCLA==" + }, + "node_modules/@openzeppelin/contracts-upgradeable-new": { + "name": "@openzeppelin/contracts-upgradeable", "version": "4.9.6", "resolved": "https://registry.npmjs.org/@openzeppelin/contracts-upgradeable/-/contracts-upgradeable-4.9.6.tgz", "integrity": "sha512-m4iHazOsOCv1DgM7eD7GupTJ+NFVujRZt1wzddDPSVGpWdKq1SKkla5htKG7+IS4d2XOCtzkUNwRZ7Vq5aEUMA==" diff --git a/package.json b/package.json index 278c894..75320c9 100644 --- a/package.json +++ b/package.json @@ -52,8 +52,10 @@ "typescript": "^4.6.2" }, "dependencies": { - "@openzeppelin/contracts": "4.9.6", - "@openzeppelin/contracts-upgradeable": "4.9.6", + "@openzeppelin/contracts": "4.5.0", + "@openzeppelin/contracts-upgradeable": "4.5.2", + "@openzeppelin/contracts-new": "npm:@openzeppelin/contracts@4.9.6", + "@openzeppelin/contracts-upgradeable-new": "npm:@openzeppelin/contracts-upgradeable@4.9.6", "mocha": "^10.2.0" } } \ No newline at end of file From f78b4acf0ec6f21b6e556fab7b74c178fe206e3a Mon Sep 17 00:00:00 2001 From: Miniroman Date: Tue, 18 Jun 2024 10:42:59 +0200 Subject: [PATCH 08/17] Increase test coverage --- ...ntationWithMultiplierAndAutoFeeAccrual.sol | 2 +- ...entationWithMultiplierAndAutoFeeAccrual.ts | 747 +++++++++++++++++- 2 files changed, 743 insertions(+), 6 deletions(-) diff --git a/contracts/BackedTokenImplementationWithMultiplierAndAutoFeeAccrual.sol b/contracts/BackedTokenImplementationWithMultiplierAndAutoFeeAccrual.sol index 7287ca5..733b243 100644 --- a/contracts/BackedTokenImplementationWithMultiplierAndAutoFeeAccrual.sol +++ b/contracts/BackedTokenImplementationWithMultiplierAndAutoFeeAccrual.sol @@ -166,7 +166,7 @@ contract BackedTokenImplementationWithMultiplierAndAutoFeeAccrual is { periodsPassed = (block.timestamp - lastTimeFeeApplied) / periodLength; newMultiplier = multiplier(); - if (feePerPeriod >= 0) { + if (feePerPeriod > 0) { for (uint256 index = 0; index < periodsPassed; index++) { newMultiplier = (newMultiplier * (1e18 - feePerPeriod)) / 1e18; } diff --git a/test/BackedTokenImplementationWithMultiplierAndAutoFeeAccrual.ts b/test/BackedTokenImplementationWithMultiplierAndAutoFeeAccrual.ts index 3be62ea..26fc41f 100644 --- a/test/BackedTokenImplementationWithMultiplierAndAutoFeeAccrual.ts +++ b/test/BackedTokenImplementationWithMultiplierAndAutoFeeAccrual.ts @@ -9,6 +9,7 @@ import { ethers } from "hardhat"; import { BigNumber, Signer } from "ethers"; import { BackedTokenProxy__factory, + SanctionsListMock, SanctionsListMock__factory, // eslint-disable-next-line node/no-missing-import } from "../typechain"; @@ -29,14 +30,23 @@ describe.only("BackedTokenImplementationWithMultiplierAndAutoFeeAccrual", functi const multiplierAdjustmentPerPeriod = nthRoot(annualFee, 365).mul(Decimal.pow(10, 18)); const baseFeePerPeriod = Decimal.pow(10, 18).minus(multiplierAdjustmentPerPeriod).toFixed(0); const baseTime = 2_000_000_000; + const tokenName = "Backed Apple"; + const tokenSymbol = "bAAPL"; // General config: let token: BackedTokenImplementationWithMultiplierAndAutoFeeAccrual; + let sanctionsList: SanctionsListMock; let proxyAdmin: ProxyAdmin; let accounts: Signer[]; let owner: SignerWithAddress; let actor: SignerWithAddress; + let minter: SignerWithAddress; + let burner: SignerWithAddress; + let pauser: SignerWithAddress; + let blacklister: SignerWithAddress; + let tmpAccount: SignerWithAddress; + let chainId: BigNumber; cacheBeforeEach(async () => { accounts = await ethers.getSigners(); @@ -47,7 +57,12 @@ describe.only("BackedTokenImplementationWithMultiplierAndAutoFeeAccrual", functi }); owner = await getSigner(0); - actor = await getSigner(1); + minter = await getSigner(1); + burner = await getSigner(2); + pauser = await getSigner(3); + blacklister = await getSigner(4); + tmpAccount = await getSigner(5); + actor = await getSigner(6); await helpers.time.setNextBlockTimestamp(baseTime); @@ -58,8 +73,8 @@ describe.only("BackedTokenImplementationWithMultiplierAndAutoFeeAccrual", functi const tokenProxy = await new BackedTokenProxy__factory(owner.signer).deploy(tokenImplementation.address, proxyAdmin.address, tokenImplementation.interface.encodeFunctionData( 'initialize', [ - "Backed Test Token", - "bTest" + tokenName, + tokenSymbol ] )); token = BackedTokenImplementationWithMultiplierAndAutoFeeAccrual__factory.connect(tokenProxy.address, owner.signer); @@ -67,13 +82,112 @@ describe.only("BackedTokenImplementationWithMultiplierAndAutoFeeAccrual", functi await token.setBurner(owner.address); await token.setPauser(owner.address); await token.setMultiplierUpdater(owner.address); - await token.setSanctionsList((await new SanctionsListMock__factory(owner.signer).deploy()).address); + sanctionsList = await new SanctionsListMock__factory(blacklister.signer).deploy(); + await token.setSanctionsList(sanctionsList.address); await token.updateFeePerPeriod(baseFeePerPeriod); await token.setLastTimeFeeApplied(baseTime); await token.setPeriodLength(accrualPeriodLength); + // Chain Id + const network = await ethers.provider.getNetwork(); + chainId = BigNumber.from(network.chainId); }); - describe('#updateModifier', () => { + + + describe('#getCurrentMultiplier', () => { + describe('when time moved by 365 days forward', () => { + const periodsPassed = 365; + let preMultiplier: BigNumber; + describe('and fee is set to non-zero value', () => { + cacheBeforeEach(async () => { + preMultiplier = await token.multiplier(); + await helpers.time.setNextBlockTimestamp(baseTime + periodsPassed * accrualPeriodLength); + await helpers.mine() + }) + + it('should change current multiplier', async () => { + expect((await token.getCurrentMultiplier()).newMultiplier).to.be.not.equal(preMultiplier) + }) + it('should not update stored multiplier', async () => { + expect(await token.multiplier()).to.be.equal(preMultiplier) + }) + }) + describe('and fee is set to zero', () => { + cacheBeforeEach(async () => { + await token.updateFeePerPeriod('0'); + preMultiplier = await token.multiplier(); + await helpers.time.setNextBlockTimestamp(baseTime + periodsPassed * accrualPeriodLength); + await helpers.mine() + }) + + it('should not change current multiplier', async () => { + expect((await token.getCurrentMultiplier()).newMultiplier).to.be.equal(preMultiplier) + }) + }) + }) + }) + + describe('#setMultiplierUpdater', () => { + describe('When called by non owner', () => { + const subject = () => token.connect(actor.signer).setMultiplierUpdater(actor.address) + it('should revert transaction', async () => { + await expect(subject()).to.be.reverted + }) + }) + describe('When called by owner', () => { + const subject = () => token.setMultiplierUpdater(actor.address) + it('should update multiplier updater', async () => { + await subject() + expect(await token.multiplierUpdater()).to.be.equal(actor.address) + }) + }) + }) + describe('#setLastTimeFeeApplied', () => { + describe('When called by non owner', () => { + const subject = () => token.connect(actor.signer).setLastTimeFeeApplied(1) + it('should revert transaction', async () => { + await expect(subject()).to.be.reverted + }) + }) + describe('When called by owner', () => { + const subject = () => token.setLastTimeFeeApplied(1) + it('should update last time fee applied', async () => { + await subject() + expect(await token.lastTimeFeeApplied()).to.be.equal(1) + }) + }) + }) + describe('#setPeriodLength', () => { + describe('When called by non owner', () => { + const subject = () => token.connect(actor.signer).setPeriodLength(1) + it('should revert transaction', async () => { + await expect(subject()).to.be.reverted + }) + }) + describe('When called by owner', () => { + const subject = () => token.setPeriodLength(1) + it('should update period length', async () => { + await subject() + expect(await token.periodLength()).to.be.equal(1) + }) + }) + }) + describe('#updateFeePerPeriod', () => { + describe('When called by non owner', () => { + const subject = () => token.connect(actor.signer).updateFeePerPeriod(1) + it('should revert transaction', async () => { + await expect(subject()).to.be.reverted + }) + }) + describe('When called by owner', () => { + const subject = () => token.updateFeePerPeriod(1) + it('should update fee per period', async () => { + await subject() + expect(await token.feePerPeriod()).to.be.equal(1) + }) + }) + }) + describe('#updateMultiplier', () => { describe('when time moved by 365 days forward', () => { const periodsPassed = 365; const baseMintedAmount = ethers.BigNumber.from(10).pow(18); @@ -125,6 +239,15 @@ describe.only("BackedTokenImplementationWithMultiplierAndAutoFeeAccrual", functi it('Should allow transfer of current balance of user', async () => { await expect(token.transfer(actor.address, (await token.balanceOf(actor.address)))).to.not.be.reverted; }) + it('Should allow transfer of current balance of user', async () => { + await expect(token.transfer(actor.address, (await token.balanceOf(actor.address)).div(2))).to.not.be.reverted; + await expect(token.transfer(actor.address, (await token.balanceOf(actor.address)))).to.not.be.reverted; + }) + it('Should allow transfer of current balance of user', async () => { + await token.updateFeePerPeriod(0); + await expect(token.transfer(actor.address, (await token.balanceOf(actor.address)).div(2))).to.not.be.reverted; + await expect(token.transfer(actor.address, (await token.balanceOf(actor.address)))).to.not.be.reverted; + }) }); describe('#transferShares', () => { it('Should allow transfer of shares of user', async () => { @@ -238,6 +361,620 @@ describe.only("BackedTokenImplementationWithMultiplierAndAutoFeeAccrual", functi }) }) }); + + // Tests copied from base BackedTokenImplementation tests: + + it("Cannot initialize twice", async function () { + await expect( + token.connect(owner.signer).initialize("test1", "test2") + ).to.be.revertedWith("Initializable: contract is already initialized"); + }); + + it("Basic information check", async function () { + expect(await token.name()).to.equal(tokenName); + expect(await token.symbol()).to.equal(tokenSymbol); + expect(await token.owner()).to.equal(owner.address); + expect(await token.terms()).to.equal( + "https://www.backedassets.fi/legal-documentation" + ); + expect(await token.VERSION()).to.equal("1.1.0"); + }); + + it("Define Minter and transfer Minter", async function () { + // Set Minter + let receipt = await (await token.setMinter(minter.address)).wait(); + expect(receipt.events?.[0].event).to.equal("NewMinter"); + expect(receipt.events?.[0].args?.[0]).to.equal(minter.address); + expect(await token.minter()).to.equal(minter.address); + + // Change Minter + receipt = await (await token.setMinter(tmpAccount.address)).wait(); + expect(receipt.events?.[0].event).to.equal("NewMinter"); + expect(receipt.events?.[0].args?.[0]).to.equal(tmpAccount.address); + }); + + it("Try to define Minter from wrong address", async function () { + await expect( + token.connect(accounts[3]).setMinter(minter.address) + ).to.be.revertedWith("Ownable: caller is not the owner"); + }); + + it("Mint", async function () { + await token.setMinter(minter.address); + const receipt = await ( + await token.connect(minter.signer).mint(tmpAccount.address, 100) + ).wait(); + + expect(receipt.events?.[0].event).to.equal("Transfer"); + expect(receipt.events?.[0].args?.[0]).to.equal( + ethers.constants.AddressZero + ); + expect(receipt.events?.[0].args?.[1]).to.equal(tmpAccount.address); + expect(receipt.events?.[0].args?.[2]).to.equal(100); + expect(await token.balanceOf(tmpAccount.address)).to.equal(100); + }); + + it("Try to mint from unauthorized account", async function () { + await token.setMinter(minter.address); + await expect(token.mint(tmpAccount.address, 100)).to.revertedWith( + "BackedToken: Only minter" + ); + }); + + it("Define Burner and transfer Burner", async function () { + // Set Burner + let receipt = await (await token.setBurner(burner.address)).wait(); + expect(receipt.events?.[0].event).to.equal("NewBurner"); + expect(receipt.events?.[0].args?.[0]).to.equal(burner.address); + expect(await token.burner()).to.equal(burner.address); + + // Change Burner + receipt = await (await token.setBurner(tmpAccount.address)).wait(); + expect(receipt.events?.[0].event).to.equal("NewBurner"); + expect(receipt.events?.[0].args?.[0]).to.equal(tmpAccount.address); + }); + + it("Try to define Burner from wrong address", async function () { + await expect( + token.connect(accounts[3]).setBurner(burner.address) + ).to.be.revertedWith("Ownable: caller is not the owner"); + }); + + it("Burn", async function () { + await token.setMinter(minter.address); + await token.connect(minter.signer).mint(burner.address, 100); + await token.setBurner(burner.address); + const receipt = await ( + await token.connect(burner.signer).burn(burner.address, 10) + ).wait(); + + expect(receipt.events?.[0].event).to.equal("Transfer"); + expect(receipt.events?.[0].args?.[0]).to.equal(burner.address); + expect(receipt.events?.[0].args?.[1]).to.equal( + ethers.constants.AddressZero + ); + expect(receipt.events?.[0].args?.[2]).to.equal(10); + expect(await token.balanceOf(burner.address)).to.equal(90); + }); + + it("Burn from the token contract balance", async function () { + await token.setMinter(minter.address); + await token.connect(minter.signer).mint(token.address, 100); + await token.setBurner(burner.address); + const receipt = await ( + await token.connect(burner.signer).burn(token.address, 10) + ).wait(); + + expect(receipt.events?.[0].event).to.equal("Transfer"); + expect(receipt.events?.[0].args?.[0]).to.equal(token.address); + expect(receipt.events?.[0].args?.[1]).to.equal( + ethers.constants.AddressZero + ); + expect(receipt.events?.[0].args?.[2]).to.equal(10); + expect(await token.balanceOf(token.address)).to.equal(90); + }); + + it("Try to burn funds of another account", async function () { + await token.setMinter(minter.address); + await token.setBurner(burner.address); + await token.connect(minter.signer).mint(tmpAccount.address, 100); + await expect( + token.connect(burner.signer).burn(tmpAccount.address, 10) + ).to.revertedWith("BackedToken: Cannot burn account"); + }); + + it("Try to burn from unauthorized account", async function () { + await token.setMinter(minter.address); + await token.setBurner(burner.address); + await token.connect(minter.signer).mint(tmpAccount.address, 100); + await expect(token.burn(tmpAccount.address, 100)).to.revertedWith( + "BackedToken: Only burner" + ); + }); + + it("Define Pauser and transfer Pauser", async function () { + // Set Pauser + let receipt = await (await token.setPauser(pauser.address)).wait(); + expect(receipt.events?.[0].event).to.equal("NewPauser"); + expect(receipt.events?.[0].args?.[0]).to.equal(pauser.address); + expect(await token.pauser()).to.equal(pauser.address); + + // Change Pauser + receipt = await (await token.setPauser(tmpAccount.address)).wait(); + expect(receipt.events?.[0].event).to.equal("NewPauser"); + expect(receipt.events?.[0].args?.[0]).to.equal(tmpAccount.address); + expect(await token.pauser()).to.equal(tmpAccount.address); + }); + + it("Try to define Pauser from wrong address", async function () { + await expect( + token.connect(accounts[3]).setPauser(pauser.address) + ).to.be.revertedWith("Ownable: caller is not the owner"); + }); + + it("Pause and Unpause", async function () { + await token.setMinter(minter.address); + await token.connect(minter.signer).mint(owner.address, 100); + await token.setPauser(pauser.address); + + await expect(token.connect(accounts[2]).setPause(true)).to.be.revertedWith( + "BackedToken: Only pauser" + ); + + const receipt = await ( + await token.connect(pauser.signer).setPause(true) + ).wait(); + expect(receipt.events?.[0].event).to.equal("PauseModeChange"); + expect(receipt.events?.[0].args?.[0]).to.equal(true); + + await expect(token.transfer(tmpAccount.address, 100)).to.be.revertedWith( + "BackedToken: token transfer while paused" + ); + + // Unpause: + const receipt2 = await ( + await token.connect(pauser.signer).setPause(false) + ).wait(); + expect(receipt2.events?.[0].event).to.equal("PauseModeChange"); + expect(receipt2.events?.[0].args?.[0]).to.equal(false); + + await token.transfer(tmpAccount.address, 100); + expect(await token.balanceOf(tmpAccount.address)).to.equal(100); + }); + + it("EIP-712 Domain Separator", async function () { + const domainSeparator = ethers.utils.keccak256( + ethers.utils.defaultAbiCoder.encode( + ["bytes32", "bytes32", "bytes32", "uint256", "address"], + [ + ethers.utils.keccak256( + ethers.utils.toUtf8Bytes( + "EIP712Domain(string name,string version,uint256 chainId,address verifyingContract)" + ) + ), + ethers.utils.keccak256(ethers.utils.toUtf8Bytes(tokenName)), + ethers.utils.keccak256(ethers.utils.toUtf8Bytes("1")), + chainId, + token.address, + ] + ) + ); + // ToDo: + expect(await token.DOMAIN_SEPARATOR()).to.equal(domainSeparator); + }); + + it("EIP-712 TypeHashes", async function () { + // Check Permit TypeHash: + const permitTypehash = ethers.utils.keccak256( + ethers.utils.toUtf8Bytes( + "Permit(address owner,address spender,uint256 value,uint256 nonce,uint256 deadline)" + ) + ); + expect(await token.PERMIT_TYPEHASH()).to.equal(permitTypehash); + + // Check Permit TypeHash: + const delegatedTransferTypehash = ethers.utils.keccak256( + ethers.utils.toUtf8Bytes( + "DELEGATED_TRANSFER(address owner,address to,uint256 value,uint256 nonce,uint256 deadline)" + ) + ); + expect(await token.DELEGATED_TRANSFER_TYPEHASH()).to.equal( + delegatedTransferTypehash + ); + }); + + it("Permit EIP-712 test", async function () { + const domain = { + name: tokenName, + version: "1", + chainId: chainId, + verifyingContract: token.address, + }; + + const types = { + Permit: [ + { name: "owner", type: "address" }, + { name: "spender", type: "address" }, + { name: "value", type: "uint256" }, + { name: "nonce", type: "uint256" }, + { name: "deadline", type: "uint256" }, + ], + }; + + const msg = { + owner: tmpAccount.address, + spender: minter.address, + value: 100, + nonce: 0, + deadline: ethers.constants.MaxUint256, + }; + + // Sign permit: + const signer = await ethers.getSigner(tmpAccount.address); + const sig = await signer._signTypedData(domain, types, msg); + const splitSig = ethers.utils.splitSignature(sig); + + // Try to send it when delegation mode is off: + await expect( + token.permit( + tmpAccount.address, + minter.address, + 100, + ethers.constants.MaxUint256, + splitSig.v, + splitSig.r, + splitSig.s + ) + ).to.revertedWith("BackedToken: Unauthorized delegate"); + + // Whitelist an address and relay signature: + await token.setDelegateWhitelist(owner.address, true); + + await expect( + token.permit( + tmpAccount.address, + minter.address, + 100, + ( + await ethers.provider.getBlock(await ethers.provider.getBlockNumber()) + ).timestamp, // deadline in the past + splitSig.v, + splitSig.r, + splitSig.s + ) + ).to.revertedWith("ERC20Permit: expired deadline"); + + const tx = await token.permit( + tmpAccount.address, + minter.address, + 100, + ethers.constants.MaxUint256, + splitSig.v, + splitSig.r, + splitSig.s + ); + const receipt = await tx.wait(); + expect(receipt.events?.[0].event).to.equal("Approval"); + expect(receipt.events?.[0].args?.[0]).to.equal(tmpAccount.address); + expect(receipt.events?.[0].args?.[1]).to.equal(minter.address); + expect(receipt.events?.[0].args?.[2]).to.equal(100); + expect(await token.allowance(tmpAccount.address, minter.address)).to.equal( + 100 + ); + + // Set delegation mode to true and try again: + await token.setDelegateMode(true); + msg.nonce = 1; + msg.value = 150; + const sig2 = await signer._signTypedData(domain, types, msg); + const splitSig2 = ethers.utils.splitSignature(sig2); + + const tx2 = await token + .connect(minter.signer) + .permit( + tmpAccount.address, + minter.address, + 150, + ethers.constants.MaxUint256, + splitSig2.v, + splitSig2.r, + splitSig2.s + ); + const receipt2 = await tx2.wait(); + expect(receipt2.events?.[0].event).to.equal("Approval"); + expect(receipt2.events?.[0].args?.[0]).to.equal(tmpAccount.address); + expect(receipt2.events?.[0].args?.[1]).to.equal(minter.address); + expect(receipt2.events?.[0].args?.[2]).to.equal(150); + expect(await token.allowance(tmpAccount.address, minter.address)).to.equal( + 150 + ); + + // Replay msg should fail: + await expect( + token + .connect(minter.signer) + .permit( + tmpAccount.address, + minter.address, + 150, + ethers.constants.MaxUint256, + splitSig2.v, + splitSig2.r, + splitSig2.s + ) + ).to.revertedWith("ERC20Permit: invalid signature"); + }); + + it("Delegate Transfer EIP-712 test", async function () { + // Mint tokens: + await token.setMinter(minter.address); + await token.connect(minter.signer).mint(tmpAccount.address, 500); + + const domain = { + name: tokenName, + version: "1", + chainId: chainId, + verifyingContract: token.address, + }; + + const types = { + DELEGATED_TRANSFER: [ + { name: "owner", type: "address" }, + { name: "to", type: "address" }, + { name: "value", type: "uint256" }, + { name: "nonce", type: "uint256" }, + { name: "deadline", type: "uint256" }, + ], + }; + + const msg = { + owner: tmpAccount.address, + to: minter.address, + value: 100, + nonce: 0, + deadline: ethers.constants.MaxUint256, + }; + + // Sign delegate transfer: + const signer = await ethers.getSigner(tmpAccount.address); + const sig = await signer._signTypedData(domain, types, msg); + const splitSig = ethers.utils.splitSignature(sig); + + // Try to send it when delegation mode is off: + await expect( + token.delegatedTransfer( + tmpAccount.address, + minter.address, + 100, + ethers.constants.MaxUint256, + splitSig.v, + splitSig.r, + splitSig.s + ) + ).to.revertedWith("BackedToken: Unauthorized delegate"); + + // Whitelist an address and relay signature: + await token.setDelegateWhitelist(owner.address, true); + + await expect( + token.delegatedTransfer( + tmpAccount.address, + minter.address, + 100, + ( + await ethers.provider.getBlock(await ethers.provider.getBlockNumber()) + ).timestamp, // deadline in the past + splitSig.v, + splitSig.r, + splitSig.s + ) + ).to.revertedWith("ERC20Permit: expired deadline"); + + const tx = await token.delegatedTransfer( + tmpAccount.address, + minter.address, + 100, + ethers.constants.MaxUint256, + splitSig.v, + splitSig.r, + splitSig.s + ); + const receipt = await tx.wait(); + expect(receipt.events?.[0].event).to.equal("Transfer"); + expect(receipt.events?.[0].args?.[0]).to.equal(tmpAccount.address); + expect(receipt.events?.[0].args?.[1]).to.equal(minter.address); + expect(receipt.events?.[0].args?.[2]).to.equal(100); + expect(await token.balanceOf(tmpAccount.address)).to.equal(400); + expect(await token.balanceOf(minter.address)).to.equal(100); + + // Set delegation mode to true and try again: + await token.setDelegateMode(true); + msg.nonce = 1; + msg.value = 200; + const sig2 = await signer._signTypedData(domain, types, msg); + const splitSig2 = ethers.utils.splitSignature(sig2); + + const tx2 = await token + .connect(minter.signer) + .delegatedTransfer( + tmpAccount.address, + minter.address, + 200, + ethers.constants.MaxUint256, + splitSig2.v, + splitSig2.r, + splitSig2.s + ); + const receipt2 = await tx2.wait(); + expect(receipt2.events?.[0].event).to.equal("Transfer"); + expect(receipt2.events?.[0].args?.[0]).to.equal(tmpAccount.address); + expect(receipt2.events?.[0].args?.[1]).to.equal(minter.address); + expect(receipt2.events?.[0].args?.[2]).to.equal(200); + expect(await token.balanceOf(tmpAccount.address)).to.equal(200); + expect(await token.balanceOf(minter.address)).to.equal(300); + + // Replay msg should fail: + await expect( + token + .connect(minter.signer) + .delegatedTransfer( + tmpAccount.address, + minter.address, + 150, + ethers.constants.MaxUint256, + splitSig2.v, + splitSig2.r, + splitSig2.s + ) + ).to.revertedWith("ERC20Permit: invalid signature"); + }); + + it("Try to set delegate from wrong address", async function () { + // Delegate mode: + await expect( + token.connect(tmpAccount.signer).setDelegateMode(true) + ).to.be.revertedWith("Ownable: caller is not the owner"); + + // Delegate address: + await expect( + token + .connect(tmpAccount.signer) + .setDelegateWhitelist(tmpAccount.address, true) + ).to.be.revertedWith("Ownable: caller is not the owner"); + }); + + it("Set SanctionsList", async function () { + // Deploy a new Sanctions List: + const sanctionsList2: SanctionsListMock = await ( + await ethers.getContractFactory("SanctionsListMock", blacklister.signer) + ).deploy(); + await sanctionsList2.deployed(); + + // Test current Sanctions List: + expect(await token.sanctionsList()).to.equal(sanctionsList.address); + + // Change SanctionsList + const receipt = await ( + await token.setSanctionsList(sanctionsList2.address) + ).wait(); + expect(receipt.events?.[0].event).to.equal("NewSanctionsList"); + expect(receipt.events?.[0].args?.[0]).to.equal(sanctionsList2.address); + expect(await token.sanctionsList()).to.equal(sanctionsList2.address); + }); + + it("Try to set SanctionsList from wrong address", async function () { + await expect( + token.connect(tmpAccount.signer).setSanctionsList(tmpAccount.address) + ).to.be.revertedWith("Ownable: caller is not the owner"); + }); + + it("Try to set SanctionsList to a contract not following the interface", async function () { + await expect( + token.connect(owner.signer).setSanctionsList(token.address) + ).to.be.revertedWith( + "Transaction reverted: function selector was not recognized and there's no fallback function" + ); + }); + + it("Check blocking of address in the Sanctions List", async function () { + await token.setMinter(minter.address); + await token.connect(minter.signer).mint(owner.address, 100); + await token.connect(minter.signer).mint(tmpAccount.address, 100); + await token.setPauser(pauser.address); + + // Add an address to the sanctions list: + await ( + await sanctionsList + .connect(blacklister.signer) + .addToSanctionsList([tmpAccount.address]) + ).wait(); + + // Try to send to the sanctioned address: + await expect(token.transfer(tmpAccount.address, 100)).to.be.revertedWith( + "BackedToken: receiver is sanctioned" + ); + + // Try to send from the sanctioned address: + await expect( + token.connect(tmpAccount.signer).transfer(owner.address, 100) + ).to.be.revertedWith("BackedToken: sender is sanctioned"); + + // Try to spend from the sanctioned address: + token.connect(owner.signer).approve(tmpAccount.address, 100); + await expect( + token + .connect(tmpAccount.signer) + .transferFrom(owner.address, minter.address, 50) + ).to.be.revertedWith("BackedToken: spender is sanctioned"); + + // Remove from sanctions list: + await ( + await sanctionsList + .connect(blacklister.signer) + .removeFromSanctionsList([tmpAccount.address]) + ).wait(); + + // Check transfer is possible: + await token.transfer(tmpAccount.address, 100); + await token.connect(tmpAccount.signer).transfer(owner.address, 100); + + // Check transferFrom is possible: + await token + .connect(tmpAccount.signer) + .transferFrom(owner.address, burner.address, 50); + expect(await token.balanceOf(burner.address)).to.equal(50); + expect(await token.balanceOf(owner.address)).to.equal(50); + }); + + it("SanctionsList cannot stop minting and burning", async function () { + await token.setMinter(minter.address); + await token.connect(minter.signer).mint(owner.address, 100); + await token.connect(minter.signer).mint(tmpAccount.address, 100); + await token.setBurner(burner.address); + await token.setPauser(pauser.address); + await token.setSanctionsList(sanctionsList.address); + + // Sanction 0x0 address, and still mint: + await sanctionsList.addToSanctionsList([ethers.constants.AddressZero]); + await token.connect(minter.signer).mint(tmpAccount.address, 100); + expect(await token.balanceOf(tmpAccount.address)).to.equal(200); + + // Try to sanction minter address: + await sanctionsList + .connect(blacklister.signer) + .addToSanctionsList([minter.address]); + await token.connect(minter.signer).mint(tmpAccount.address, 100); + expect(await token.balanceOf(tmpAccount.address)).to.equal(300); + + // Try to sanction burner address: + await token.connect(minter.signer).mint(burner.address, 100); + await sanctionsList + .connect(blacklister.signer) + .addToSanctionsList([burner.address]); + await token.connect(burner.signer).burn(burner.address, 50); + expect(await token.balanceOf(burner.address)).to.equal(50); + }); + + it("Check and set Terms", async function () { + // Test current Terms: + expect(await token.terms()).to.equal( + "https://www.backedassets.fi/legal-documentation" + ); + + // Change Terms + const receipt = await (await token.setTerms("New Terms ^^")).wait(); + expect(receipt.events?.[0].event).to.equal("NewTerms"); + expect(receipt.events?.[0].args?.[0]).to.equal("New Terms ^^"); + expect(await token.terms()).to.equal("New Terms ^^"); + }); + + it("Try to set Terms from wrong address", async function () { + await expect( + token.connect(tmpAccount.signer).setTerms("Random Terms") + ).to.be.revertedWith("Ownable: caller is not the owner"); + }); + + }); function nthRoot(annualFee: number, n: number) { return Decimal.pow(1 - annualFee, new Decimal(1).div(n)); From eee28226847de0532245dc6b6779d0498d3f6fe2 Mon Sep 17 00:00:00 2001 From: Miniroman Date: Tue, 18 Jun 2024 16:10:41 +0200 Subject: [PATCH 09/17] Restructure contracts inheritance --- contracts/BackedTokenImplementation.sol | 6 +- ...ntationWithMultiplierAndAutoFeeAccrual.sol | 244 +-------- ...20PermitDelegateTransferWithMultiplier.sol | 87 --- contracts/ERC20UpgradeableWithMultiplier.sol | 4 +- package-lock.json | 496 +----------------- package.json | 2 +- ...entationWithMultiplierAndAutoFeeAccrual.ts | 39 +- 7 files changed, 49 insertions(+), 829 deletions(-) diff --git a/contracts/BackedTokenImplementation.sol b/contracts/BackedTokenImplementation.sol index 28d90ee..c397420 100644 --- a/contracts/BackedTokenImplementation.sol +++ b/contracts/BackedTokenImplementation.sol @@ -97,7 +97,7 @@ contract BackedTokenImplementation is OwnableUpgradeable, ERC20PermitDelegateTra initialize("Backed Token Implementation", "BTI"); } - function initialize(string memory name_, string memory symbol_) public initializer { + function initialize(string memory name_, string memory symbol_) public virtual initializer { __ERC20_init(name_, symbol_); __Ownable_init(); _buildDomainSeparator(); @@ -149,7 +149,7 @@ contract BackedTokenImplementation is OwnableUpgradeable, ERC20PermitDelegateTra uint8 v, bytes32 r, bytes32 s - ) public override allowedDelegate { + ) public virtual override allowedDelegate { super.delegatedTransfer(owner, to, value, deadline, v, r, s); } @@ -172,7 +172,7 @@ contract BackedTokenImplementation is OwnableUpgradeable, ERC20PermitDelegateTra * @param account The account from which the tokens will be burned * @param amount The amount of tokens to be burned */ - function burn(address account, uint256 amount) external { + function burn(address account, uint256 amount) virtual external { require(_msgSender() == burner, "BackedToken: Only burner"); require(account == _msgSender() || account == address(this), "BackedToken: Cannot burn account"); _burn(account, amount); diff --git a/contracts/BackedTokenImplementationWithMultiplierAndAutoFeeAccrual.sol b/contracts/BackedTokenImplementationWithMultiplierAndAutoFeeAccrual.sol index 733b243..9c987ad 100644 --- a/contracts/BackedTokenImplementationWithMultiplierAndAutoFeeAccrual.sol +++ b/contracts/BackedTokenImplementationWithMultiplierAndAutoFeeAccrual.sol @@ -36,7 +36,7 @@ pragma solidity 0.8.9; -import "@openzeppelin/contracts-upgradeable-new/access/OwnableUpgradeable.sol"; +import "@openzeppelin/contracts-upgradeable/access/OwnableUpgradeable.sol"; import "./ERC20PermitDelegateTransferWithMultiplier.sol"; import "./SanctionsList.sol"; @@ -56,30 +56,8 @@ import "./SanctionsList.sol"; * */ -contract BackedTokenImplementationWithMultiplierAndAutoFeeAccrual is - OwnableUpgradeable, - ERC20PermitDelegateTransferWithMultiplier +contract BackedTokenImplementationWithMultiplierAndAutoFeeAccrual is ERC20PermitDelegateTransferWithMultiplier { - string public constant VERSION = "1.1.0"; - - // Roles: - address public minter; - address public burner; - address public pauser; - - // EIP-712 Delegate Functionality: - bool public delegateMode; - mapping(address => bool) public delegateWhitelist; - - // Pause: - bool public isPaused; - - // SanctionsList: - SanctionsList public sanctionsList; - - // Terms: - string public terms; - // V2 // Roles: @@ -91,26 +69,7 @@ contract BackedTokenImplementationWithMultiplierAndAutoFeeAccrual is uint256 public periodLength; // Events: - event NewMinter(address indexed newMinter); - event NewBurner(address indexed newBurner); - event NewPauser(address indexed newPauser); event NewMultiplierUpdater(address indexed newMultiplierUpdater); - event NewSanctionsList(address indexed newSanctionsList); - event DelegateWhitelistChange( - address indexed whitelistAddress, - bool status - ); - event DelegateModeChange(bool delegateMode); - event PauseModeChange(bool pauseMode); - event NewTerms(string newTerms); - - modifier allowedDelegate() { - require( - delegateMode || delegateWhitelist[_msgSender()], - "BackedToken: Unauthorized delegate" - ); - _; - } modifier updateMultiplier() { (uint256 newMultiplier, uint256 periodsPassed) = getCurrentMultiplier(); @@ -121,23 +80,16 @@ contract BackedTokenImplementationWithMultiplierAndAutoFeeAccrual is _; } - // constructor, call initializer to lock the implementation instance. - constructor() { - initialize( - "Backed Token Implementation", - "BTI" - ); - } - function initialize( string memory name_, string memory symbol_ - ) public initializer { + ) override public virtual initializer { __ERC20_init(name_, symbol_); - __Multiplier_init(); __Ownable_init(); _buildDomainSeparator(); _setTerms("https://www.backedassets.fi/legal-documentation"); // Default Terms + __Multiplier_init(); + periodLength = 24 * 3600; // Set to 24h by default lastTimeFeeApplied = block.timestamp; } @@ -181,30 +133,6 @@ contract BackedTokenImplementationWithMultiplierAndAutoFeeAccrual is return _totalShares * multiplier / 1e18; } - /** - * @dev Update allowance with a signed permit. Allowed only if - * the sender is whitelisted, or the delegateMode is set to true - * - * @param owner Token owner's address (Authorizer) - * @param spender Spender's address - * @param value Amount of allowance - * @param deadline Expiration time, seconds since the epoch - * @param v v part of the signature - * @param r r part of the signature - * @param s s part of the signature - */ - function permit( - address owner, - address spender, - uint256 value, - uint256 deadline, - uint8 v, - bytes32 r, - bytes32 s - ) public override allowedDelegate { - super.permit(owner, spender, value, deadline, v, r, s); - } - /** * @dev Perform an intended transfer on one account's behalf, from another account, * who actually pays fees for the transaction. Allowed only if the sender @@ -226,7 +154,7 @@ contract BackedTokenImplementationWithMultiplierAndAutoFeeAccrual is uint8 v, bytes32 r, bytes32 s - ) public override allowedDelegate updateMultiplier { + ) public override updateMultiplier { super.delegatedTransfer(owner, to, value, deadline, v, r, s); } @@ -251,7 +179,7 @@ contract BackedTokenImplementationWithMultiplierAndAutoFeeAccrual is uint8 v, bytes32 r, bytes32 s - ) public virtual override updateMultiplier { + ) public virtual override allowedDelegate updateMultiplier { super.delegatedTransferShares(owner, to, value, deadline, v, r, s); } @@ -318,7 +246,7 @@ contract BackedTokenImplementationWithMultiplierAndAutoFeeAccrual is function mint( address account, uint256 amount - ) external virtual updateMultiplier { + ) override external virtual updateMultiplier { require(_msgSender() == minter, "BackedToken: Only minter"); uint256 sharesAmount = getSharesByUnderlyingAmount(amount); _mintShares(account, sharesAmount); @@ -331,7 +259,7 @@ contract BackedTokenImplementationWithMultiplierAndAutoFeeAccrual is * @param account The account from which the tokens will be burned * @param amount The amount of tokens to be burned */ - function burn(address account, uint256 amount) external updateMultiplier { + function burn(address account, uint256 amount) override external virtual updateMultiplier { require(_msgSender() == burner, "BackedToken: Only burner"); require( account == _msgSender() || account == address(this), @@ -350,56 +278,6 @@ contract BackedTokenImplementationWithMultiplierAndAutoFeeAccrual is feePerPeriod = newFeePerPeriod; } - /** - * @dev Function to set the pause in order to block or restore all - * transfers. Allowed only for pauser - * - * Emits a { PauseModeChange } event - * - * @param newPauseMode The new pause mode - */ - function setPause(bool newPauseMode) external { - require(_msgSender() == pauser, "BackedToken: Only pauser"); - isPaused = newPauseMode; - emit PauseModeChange(newPauseMode); - } - - /** - * @dev Function to change the contract minter. Allowed only for owner - * - * Emits a { NewMinter } event - * - * @param newMinter The address of the new minter - */ - function setMinter(address newMinter) external onlyOwner { - minter = newMinter; - emit NewMinter(newMinter); - } - - /** - * @dev Function to change the contract burner. Allowed only for owner - * - * Emits a { NewBurner } event - * - * @param newBurner The address of the new burner - */ - function setBurner(address newBurner) external onlyOwner { - burner = newBurner; - emit NewBurner(newBurner); - } - - /** - * @dev Function to change the contract pauser. Allowed only for owner - * - * Emits a { NewPauser } event - * - * @param newPauser The address of the new pauser - */ - function setPauser(address newPauser) external onlyOwner { - pauser = newPauser; - emit NewPauser(newPauser); - } - /** * @dev Function to change the contract multiplier updater. Allowed only for owner * @@ -434,66 +312,6 @@ contract BackedTokenImplementationWithMultiplierAndAutoFeeAccrual is _updateMultiplier(newMultiplier); } - /** - * @dev Function to change the contract Senctions List. Allowed only for owner - * - * Emits a { NewSanctionsList } event - * - * @param newSanctionsList The address of the new Senctions List following the Chainalysis standard - */ - function setSanctionsList(address newSanctionsList) external onlyOwner { - // Check the proposed sanctions list contract has the right interface: - require( - !SanctionsList(newSanctionsList).isSanctioned(address(this)), - "BackedToken: Wrong List interface" - ); - - sanctionsList = SanctionsList(newSanctionsList); - emit NewSanctionsList(newSanctionsList); - } - - /** - * @dev EIP-712 Function to change the delegate status of account. - * Allowed only for owner - * - * Emits a { DelegateWhitelistChange } event - * - * @param whitelistAddress The address for which to change the delegate status - * @param status The new delegate status - */ - function setDelegateWhitelist( - address whitelistAddress, - bool status - ) external onlyOwner { - delegateWhitelist[whitelistAddress] = status; - emit DelegateWhitelistChange(whitelistAddress, status); - } - - /** - * @dev EIP-712 Function to change the contract delegate mode. Allowed - * only for owner - * - * Emits a { DelegateModeChange } event - * - * @param _delegateMode The new delegate mode for the contract - */ - function setDelegateMode(bool _delegateMode) external onlyOwner { - delegateMode = _delegateMode; - - emit DelegateModeChange(_delegateMode); - } - - /** - * @dev Function to change the contract terms. Allowed only for owner - * - * Emits a { NewTerms } event - * - * @param newTerms A string with the terms. Usually a web or IPFS link. - */ - function setTerms(string memory newTerms) external onlyOwner { - _setTerms(newTerms); - } - /** * @dev Function to change the time of last fee accrual. Allowed only for owner * @@ -512,50 +330,6 @@ contract BackedTokenImplementationWithMultiplierAndAutoFeeAccrual is periodLength = newPeriodLength; } - // Implement setTerms, to allow also to use from initializer: - function _setTerms(string memory newTerms) internal virtual { - terms = newTerms; - emit NewTerms(newTerms); - } - - // Implement the pause and SanctionsList functionality before transfer: - function _beforeTokenTransfer( - address from, - address to, - uint256 amount - ) internal virtual override { - // Check not paused: - require(!isPaused, "BackedToken: token transfer while paused"); - - // Check Sanctions List, but do not prevent minting burning: - if (from != address(0) && to != address(0)) { - require( - !sanctionsList.isSanctioned(from), - "BackedToken: sender is sanctioned" - ); - require( - !sanctionsList.isSanctioned(to), - "BackedToken: receiver is sanctioned" - ); - } - - super._beforeTokenTransfer(from, to, amount); - } - - // Implement the SanctionsList functionality for spender: - function _spendAllowance( - address owner, - address spender, - uint256 amount - ) internal virtual override { - require( - !sanctionsList.isSanctioned(spender), - "BackedToken: spender is sanctioned" - ); - - super._spendAllowance(owner, spender, amount); - } - /** * @dev This empty reserved space is put in place to allow future versions to add new * variables without shifting down storage in the inheritance chain. diff --git a/contracts/ERC20PermitDelegateTransferWithMultiplier.sol b/contracts/ERC20PermitDelegateTransferWithMultiplier.sol index 5d05ce5..cebf83e 100644 --- a/contracts/ERC20PermitDelegateTransferWithMultiplier.sol +++ b/contracts/ERC20PermitDelegateTransferWithMultiplier.sol @@ -24,7 +24,6 @@ pragma solidity 0.8.9; import "./ERC20UpgradeableWithMultiplier.sol"; -import "@openzeppelin/contracts-upgradeable-new/utils/cryptography/ECDSAUpgradeable.sol"; /** * @dev @@ -38,70 +37,11 @@ import "@openzeppelin/contracts-upgradeable-new/utils/cryptography/ECDSAUpgradea */ contract ERC20PermitDelegateTransferWithMultiplier is ERC20UpgradeableWithMultiplier { - mapping(address => uint256) public nonces; - - // Calculating the Permit typehash: - bytes32 public constant PERMIT_TYPEHASH = - keccak256("Permit(address owner,address spender,uint256 value,uint256 nonce,uint256 deadline)"); - - // Calculating the Delegated Transfer typehash: - bytes32 public constant DELEGATED_TRANSFER_TYPEHASH = - keccak256("DELEGATED_TRANSFER(address owner,address to,uint256 value,uint256 nonce,uint256 deadline)"); // Calculating the Delegated Transfer Shares typehash: bytes32 public constant DELEGATED_TRANSFER_SHARES_TYPEHASH = keccak256("DELEGATED_TRANSFER_SHARES(address owner,address to,uint256 value,uint256 nonce,uint256 deadline)"); - // Immutable variable for Domain Separator: - // solhint-disable-next-line var-name-mixedcase - bytes32 public DOMAIN_SEPARATOR; - - // A version number: - // solhint-disable-next-line var-name-mixedcase - string internal constant DOMAIN_SEPARATOR_VERSION = "1"; - - /** - * @dev Permit, approve via a sign message, using erc712. - */ - function permit( - address owner, - address spender, - uint256 value, - uint256 deadline, - uint8 v, - bytes32 r, - bytes32 s - ) public virtual { - require(block.timestamp <= deadline, "ERC20Permit: expired deadline"); - - bytes32 structHash = keccak256(abi.encode(PERMIT_TYPEHASH, owner, spender, value, _useNonce(owner), deadline)); - - _checkOwner(owner, structHash, v, r, s); - - _approve(owner, spender, value); - } - - /** - * @dev Delegated Transfer, transfer via a sign message, using erc712. - */ - function delegatedTransfer( - address owner, - address to, - uint256 value, - uint256 deadline, - uint8 v, - bytes32 r, - bytes32 s - ) public virtual { - require(block.timestamp <= deadline, "ERC20Permit: expired deadline"); - - bytes32 structHash = keccak256(abi.encode(DELEGATED_TRANSFER_TYPEHASH, owner, to, value, _useNonce(owner), deadline)); - - _checkOwner(owner, structHash, v, r, s); - - _transfer(owner, to, value); - } - /** * @dev Delegated Transfer Shares, transfer shares via a sign message, using erc712. */ @@ -122,33 +62,6 @@ contract ERC20PermitDelegateTransferWithMultiplier is ERC20UpgradeableWithMultip _transferShares(owner, to, value); } - /** - * @dev "Consume a nonce": return the current value and increment. - */ - function _useNonce(address owner) internal virtual returns (uint256 current) { - current = nonces[owner]; - nonces[owner]++; - } - - function _checkOwner(address owner, bytes32 structHash, uint8 v, bytes32 r, bytes32 s) internal view { - bytes32 hash = ECDSAUpgradeable.toTypedDataHash(DOMAIN_SEPARATOR, structHash); - - address signer = ECDSAUpgradeable.recover(hash, v, r, s); - require(signer == owner, "ERC20Permit: invalid signature"); - } - - function _buildDomainSeparator() internal { - DOMAIN_SEPARATOR = keccak256( - abi.encode( - keccak256("EIP712Domain(string name,string version,uint256 chainId,address verifyingContract)"), - keccak256(bytes(name())), - keccak256(bytes(DOMAIN_SEPARATOR_VERSION)), - block.chainid, - address(this) - ) - ); - } - /** * @dev This empty reserved space is put in place to allow future versions to add new * variables without shifting down storage in the inheritance chain. diff --git a/contracts/ERC20UpgradeableWithMultiplier.sol b/contracts/ERC20UpgradeableWithMultiplier.sol index 27af330..d8dd91e 100644 --- a/contracts/ERC20UpgradeableWithMultiplier.sol +++ b/contracts/ERC20UpgradeableWithMultiplier.sol @@ -3,7 +3,7 @@ pragma solidity ^0.8.0; -import "@openzeppelin/contracts-upgradeable-new/token/ERC20/ERC20Upgradeable.sol"; +import "./BackedTokenImplementation.sol"; /** * @dev Implementation of the {IERC20} interface. @@ -30,7 +30,7 @@ import "@openzeppelin/contracts-upgradeable-new/token/ERC20/ERC20Upgradeable.sol * functions have been added to mitigate the well-known issues around setting * allowances. See {IERC20-approve}. */ -contract ERC20UpgradeableWithMultiplier is ERC20Upgradeable { +contract ERC20UpgradeableWithMultiplier is BackedTokenImplementation { mapping(address => uint256) private _shares; uint256 internal _totalShares; diff --git a/package-lock.json b/package-lock.json index 0163f74..4b204b1 100644 --- a/package-lock.json +++ b/package-lock.json @@ -47,7 +47,7 @@ "prettier": "^2.5.1", "prettier-plugin-solidity": "^1.0.0-beta.13", "solhint": "^3.3.7", - "solidity-coverage": "^0.8.2", + "solidity-coverage": "^0.8.12", "ts-node": "^10.7.0", "typechain": "^5.2.0", "typescript": "^4.6.2" @@ -2818,15 +2818,6 @@ "node": ">=0.4.0" } }, - "node_modules/address": { - "version": "1.2.2", - "resolved": "https://registry.npmjs.org/address/-/address-1.2.2.tgz", - "integrity": "sha512-4B/qKCfeE/ODUaAUpSwfzazo5x29WD4r3vXiWsB7I2mSDAihwEqKO+g8GELZUQSSAo5e1XTYh3ZVfLyxBc12nA==", - "dev": true, - "engines": { - "node": ">= 10.0.0" - } - }, "node_modules/adm-zip": { "version": "0.4.16", "resolved": "https://registry.npmjs.org/adm-zip/-/adm-zip-0.4.16.tgz", @@ -4077,20 +4068,6 @@ "node": ">= 0.8" } }, - "node_modules/detect-port": { - "version": "1.5.1", - "resolved": "https://registry.npmjs.org/detect-port/-/detect-port-1.5.1.tgz", - "integrity": "sha512-aBzdj76lueB6uUst5iAs7+0H/oOjqI5D16XUWxlWMIMROhcM0rfsNVk93zTngq1dDNpoXRr++Sus7ETAExppAQ==", - "dev": true, - "dependencies": { - "address": "^1.0.1", - "debug": "4" - }, - "bin": { - "detect": "bin/detect-port.js", - "detect-port": "bin/detect-port.js" - } - }, "node_modules/diff": { "version": "5.0.0", "resolved": "https://registry.npmjs.org/diff/-/diff-5.0.0.tgz", @@ -19695,24 +19672,23 @@ "dev": true }, "node_modules/solidity-coverage": { - "version": "0.8.2", - "resolved": "https://registry.npmjs.org/solidity-coverage/-/solidity-coverage-0.8.2.tgz", - "integrity": "sha512-cv2bWb7lOXPE9/SSleDO6czkFiMHgP4NXPj+iW9W7iEKLBk7Cj0AGBiNmGX3V1totl9wjPrT0gHmABZKZt65rQ==", + "version": "0.8.12", + "resolved": "https://registry.npmjs.org/solidity-coverage/-/solidity-coverage-0.8.12.tgz", + "integrity": "sha512-8cOB1PtjnjFRqOgwFiD8DaUsYJtVJ6+YdXQtSZDrLGf8cdhhh8xzTtGzVTGeBf15kTv0v7lYPJlV/az7zLEPJw==", "dev": true, "dependencies": { "@ethersproject/abi": "^5.0.9", - "@solidity-parser/parser": "^0.14.1", + "@solidity-parser/parser": "^0.18.0", "chalk": "^2.4.2", "death": "^1.1.0", - "detect-port": "^1.3.0", "difflib": "^0.2.4", "fs-extra": "^8.1.0", "ghost-testrpc": "^0.0.2", "global-modules": "^2.0.0", "globby": "^10.0.1", "jsonschema": "^1.2.4", - "lodash": "^4.17.15", - "mocha": "7.1.2", + "lodash": "^4.17.21", + "mocha": "^10.2.0", "node-emoji": "^1.10.0", "pify": "^4.0.1", "recursive-readdir": "^2.2.2", @@ -19728,123 +19704,12 @@ "hardhat": "^2.11.0" } }, - "node_modules/solidity-coverage/node_modules/ansi-colors": { - "version": "3.2.3", - "resolved": "https://registry.npmjs.org/ansi-colors/-/ansi-colors-3.2.3.tgz", - "integrity": "sha512-LEHHyuhlPY3TmuUYMh2oz89lTShfvgbmzaBcxve9t/9Wuy7Dwf4yoAKcND7KFT1HAQfqZ12qtc+DUrBMeKF9nw==", - "dev": true, - "engines": { - "node": ">=6" - } - }, - "node_modules/solidity-coverage/node_modules/ansi-regex": { - "version": "4.1.1", - "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-4.1.1.tgz", - "integrity": "sha512-ILlv4k/3f6vfQ4OoP2AGvirOktlQ98ZEL1k9FaQjxa3L1abBgbuTDAdPOpvbGncC0BTVQrl+OM8xZGK6tWXt7g==", - "dev": true, - "engines": { - "node": ">=6" - } - }, - "node_modules/solidity-coverage/node_modules/camelcase": { - "version": "5.3.1", - "resolved": "https://registry.npmjs.org/camelcase/-/camelcase-5.3.1.tgz", - "integrity": "sha512-L28STB170nwWS63UjtlEOE3dldQApaJXZkOI1uMFfzf3rRuPegHaHesyee+YxQ+W6SvRDQV6UrdOdRiR153wJg==", - "dev": true, - "engines": { - "node": ">=6" - } - }, - "node_modules/solidity-coverage/node_modules/chokidar": { - "version": "3.3.0", - "resolved": "https://registry.npmjs.org/chokidar/-/chokidar-3.3.0.tgz", - "integrity": "sha512-dGmKLDdT3Gdl7fBUe8XK+gAtGmzy5Fn0XkkWQuYxGIgWVPPse2CxFA5mtrlD0TOHaHjEUqkWNyP1XdHoJES/4A==", - "dev": true, - "dependencies": { - "anymatch": "~3.1.1", - "braces": "~3.0.2", - "glob-parent": "~5.1.0", - "is-binary-path": "~2.1.0", - "is-glob": "~4.0.1", - "normalize-path": "~3.0.0", - "readdirp": "~3.2.0" - }, - "engines": { - "node": ">= 8.10.0" - }, - "optionalDependencies": { - "fsevents": "~2.1.1" - } - }, - "node_modules/solidity-coverage/node_modules/cliui": { - "version": "5.0.0", - "resolved": "https://registry.npmjs.org/cliui/-/cliui-5.0.0.tgz", - "integrity": "sha512-PYeGSEmmHM6zvoef2w8TPzlrnNpXIjTipYK780YswmIP9vjxmd6Y2a3CB2Ks6/AU8NHjZugXvo8w3oWM2qnwXA==", - "dev": true, - "dependencies": { - "string-width": "^3.1.0", - "strip-ansi": "^5.2.0", - "wrap-ansi": "^5.1.0" - } - }, - "node_modules/solidity-coverage/node_modules/debug": { - "version": "3.2.6", - "resolved": "https://registry.npmjs.org/debug/-/debug-3.2.6.tgz", - "integrity": "sha512-mel+jf7nrtEl5Pn1Qx46zARXKDpBbvzezse7p7LqINmdoIk8PYP5SySaxEmYv6TZ0JyEKA1hsCId6DIhgITtWQ==", - "deprecated": "Debug versions >=3.2.0 <3.2.7 || >=4 <4.3.1 have a low-severity ReDos regression when used in a Node.js environment. It is recommended you upgrade to 3.2.7 or 4.3.1. (https://github.com/visionmedia/debug/issues/797)", - "dev": true, - "dependencies": { - "ms": "^2.1.1" - } - }, - "node_modules/solidity-coverage/node_modules/decamelize": { - "version": "1.2.0", - "resolved": "https://registry.npmjs.org/decamelize/-/decamelize-1.2.0.tgz", - "integrity": "sha512-z2S+W9X73hAUUki+N+9Za2lBlun89zigOyGrsax+KUQ6wKW4ZoWpEYBkGhQjwAjjDCkWxhY0VKEhk8wzY7F5cA==", - "dev": true, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/solidity-coverage/node_modules/diff": { - "version": "3.5.0", - "resolved": "https://registry.npmjs.org/diff/-/diff-3.5.0.tgz", - "integrity": "sha512-A46qtFgd+g7pDZinpnwiRJtxbC1hpgf0uzP3iG89scHk0AUC7A1TGxf5OiiOUv/JMZR8GOt8hL900hV0bOy5xA==", - "dev": true, - "engines": { - "node": ">=0.3.1" - } - }, - "node_modules/solidity-coverage/node_modules/emoji-regex": { - "version": "7.0.3", - "resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-7.0.3.tgz", - "integrity": "sha512-CwBLREIQ7LvYFB0WyRvwhq5N5qPhc6PMjD6bYggFlI5YyDgl+0vxq5VHbMOFqLg7hfWzmu8T5Z1QofhmTIhItA==", + "node_modules/solidity-coverage/node_modules/@solidity-parser/parser": { + "version": "0.18.0", + "resolved": "https://registry.npmjs.org/@solidity-parser/parser/-/parser-0.18.0.tgz", + "integrity": "sha512-yfORGUIPgLck41qyN7nbwJRAx17/jAIXCTanHOJZhB6PJ1iAk/84b/xlsVKFSyNyLXIj0dhppoE0+CRws7wlzA==", "dev": true }, - "node_modules/solidity-coverage/node_modules/find-up": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/find-up/-/find-up-3.0.0.tgz", - "integrity": "sha512-1yD6RmLI1XBfxugvORwlck6f75tYL+iR0jqwsOrOxMZyGYqUuDhJ0l4AXdO1iX/FTs9cBAMEk1gWSEx1kSbylg==", - "dev": true, - "dependencies": { - "locate-path": "^3.0.0" - }, - "engines": { - "node": ">=6" - } - }, - "node_modules/solidity-coverage/node_modules/flat": { - "version": "4.1.1", - "resolved": "https://registry.npmjs.org/flat/-/flat-4.1.1.tgz", - "integrity": "sha512-FmTtBsHskrU6FJ2VxCnsDb84wu9zhmO3cUX2kGFb5tuwhfXxGciiT0oRY+cck35QmG+NmGh5eLz6lLCpWTqwpA==", - "dev": true, - "dependencies": { - "is-buffer": "~2.0.3" - }, - "bin": { - "flat": "cli.js" - } - }, "node_modules/solidity-coverage/node_modules/fs-extra": { "version": "8.1.0", "resolved": "https://registry.npmjs.org/fs-extra/-/fs-extra-8.1.0.tgz", @@ -19859,38 +19724,6 @@ "node": ">=6 <7 || >=8" } }, - "node_modules/solidity-coverage/node_modules/fsevents": { - "version": "2.1.3", - "resolved": "https://registry.npmjs.org/fsevents/-/fsevents-2.1.3.tgz", - "integrity": "sha512-Auw9a4AxqWpa9GUfj370BMPzzyncfBABW8Mab7BGWBYDj4Isgq+cDKtx0i6u9jcX9pQDnswsaaOTgTmA5pEjuQ==", - "deprecated": "\"Please update to latest v2.3 or v2.2\"", - "dev": true, - "hasInstallScript": true, - "optional": true, - "os": [ - "darwin" - ], - "engines": { - "node": "^8.16.0 || ^10.6.0 || >=11.0.0" - } - }, - "node_modules/solidity-coverage/node_modules/glob": { - "version": "7.1.3", - "resolved": "https://registry.npmjs.org/glob/-/glob-7.1.3.tgz", - "integrity": "sha512-vcfuiIxogLV4DlGBHIUOwI0IbrJ8HWPc4MU7HzviGeNho/UJDfi6B5p3sHeWIQ0KGIU0Jpxi5ZHxemQfLkkAwQ==", - "dev": true, - "dependencies": { - "fs.realpath": "^1.0.0", - "inflight": "^1.0.4", - "inherits": "2", - "minimatch": "^3.0.4", - "once": "^1.3.0", - "path-is-absolute": "^1.0.0" - }, - "engines": { - "node": "*" - } - }, "node_modules/solidity-coverage/node_modules/globby": { "version": "10.0.2", "resolved": "https://registry.npmjs.org/globby/-/globby-10.0.2.tgz", @@ -19910,44 +19743,6 @@ "node": ">=8" } }, - "node_modules/solidity-coverage/node_modules/js-yaml": { - "version": "3.13.1", - "resolved": "https://registry.npmjs.org/js-yaml/-/js-yaml-3.13.1.tgz", - "integrity": "sha512-YfbcO7jXDdyj0DGxYVSlSeQNHbD7XPWvrVWeVUujrQEoZzWJIRrCPoyk6kL6IAjAG2IolMK4T0hNUe0HOUs5Jw==", - "dev": true, - "dependencies": { - "argparse": "^1.0.7", - "esprima": "^4.0.0" - }, - "bin": { - "js-yaml": "bin/js-yaml.js" - } - }, - "node_modules/solidity-coverage/node_modules/locate-path": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/locate-path/-/locate-path-3.0.0.tgz", - "integrity": "sha512-7AO748wWnIhNqAuaty2ZWHkQHRSNfPVIsPIfwEOWO22AmaoVrWavlOcMR5nzTLNYvp36X220/maaRsrec1G65A==", - "dev": true, - "dependencies": { - "p-locate": "^3.0.0", - "path-exists": "^3.0.0" - }, - "engines": { - "node": ">=6" - } - }, - "node_modules/solidity-coverage/node_modules/log-symbols": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/log-symbols/-/log-symbols-3.0.0.tgz", - "integrity": "sha512-dSkNGuI7iG3mfvDzUuYZyvk5dD9ocYCYzNU6CYDE6+Xqd+gwme6Z00NS3dUh8mq/73HaEtT7m6W+yUPtU6BZnQ==", - "dev": true, - "dependencies": { - "chalk": "^2.4.2" - }, - "engines": { - "node": ">=8" - } - }, "node_modules/solidity-coverage/node_modules/lru-cache": { "version": "6.0.0", "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-6.0.0.tgz", @@ -19960,148 +19755,6 @@ "node": ">=10" } }, - "node_modules/solidity-coverage/node_modules/minimatch": { - "version": "3.0.4", - "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.0.4.tgz", - "integrity": "sha512-yJHVQEhyqPLUTgt9B83PXu6W3rx4MvvHvSUvToogpwoGDOUQ+yDrR0HRot+yOCdCO7u4hX3pWft6kWBBcqh0UA==", - "dev": true, - "dependencies": { - "brace-expansion": "^1.1.7" - }, - "engines": { - "node": "*" - } - }, - "node_modules/solidity-coverage/node_modules/mkdirp": { - "version": "0.5.5", - "resolved": "https://registry.npmjs.org/mkdirp/-/mkdirp-0.5.5.tgz", - "integrity": "sha512-NKmAlESf6jMGym1++R0Ra7wvhV+wFW63FaSOFPwRahvea0gMUcGUhVeAg/0BC0wiv9ih5NYPB1Wn1UEI1/L+xQ==", - "dev": true, - "dependencies": { - "minimist": "^1.2.5" - }, - "bin": { - "mkdirp": "bin/cmd.js" - } - }, - "node_modules/solidity-coverage/node_modules/mocha": { - "version": "7.1.2", - "resolved": "https://registry.npmjs.org/mocha/-/mocha-7.1.2.tgz", - "integrity": "sha512-o96kdRKMKI3E8U0bjnfqW4QMk12MwZ4mhdBTf+B5a1q9+aq2HRnj+3ZdJu0B/ZhJeK78MgYuv6L8d/rA5AeBJA==", - "dev": true, - "dependencies": { - "ansi-colors": "3.2.3", - "browser-stdout": "1.3.1", - "chokidar": "3.3.0", - "debug": "3.2.6", - "diff": "3.5.0", - "escape-string-regexp": "1.0.5", - "find-up": "3.0.0", - "glob": "7.1.3", - "growl": "1.10.5", - "he": "1.2.0", - "js-yaml": "3.13.1", - "log-symbols": "3.0.0", - "minimatch": "3.0.4", - "mkdirp": "0.5.5", - "ms": "2.1.1", - "node-environment-flags": "1.0.6", - "object.assign": "4.1.0", - "strip-json-comments": "2.0.1", - "supports-color": "6.0.0", - "which": "1.3.1", - "wide-align": "1.1.3", - "yargs": "13.3.2", - "yargs-parser": "13.1.2", - "yargs-unparser": "1.6.0" - }, - "bin": { - "_mocha": "bin/_mocha", - "mocha": "bin/mocha" - }, - "engines": { - "node": ">= 8.10.0" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/mochajs" - } - }, - "node_modules/solidity-coverage/node_modules/ms": { - "version": "2.1.1", - "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.1.tgz", - "integrity": "sha512-tgp+dl5cGk28utYktBsrFqA7HKgrhgPsg6Z/EfhWI4gl1Hwq8B/GmY/0oXZ6nF8hDVesS/FpnYaD/kOWhYQvyg==", - "dev": true - }, - "node_modules/solidity-coverage/node_modules/object.assign": { - "version": "4.1.0", - "resolved": "https://registry.npmjs.org/object.assign/-/object.assign-4.1.0.tgz", - "integrity": "sha512-exHJeq6kBKj58mqGyTQ9DFvrZC/eR6OwxzoM9YRoGBqrXYonaFyGiFMuc9VZrXf7DarreEwMpurG3dd+CNyW5w==", - "dev": true, - "dependencies": { - "define-properties": "^1.1.2", - "function-bind": "^1.1.1", - "has-symbols": "^1.0.0", - "object-keys": "^1.0.11" - }, - "engines": { - "node": ">= 0.4" - } - }, - "node_modules/solidity-coverage/node_modules/p-limit": { - "version": "2.3.0", - "resolved": "https://registry.npmjs.org/p-limit/-/p-limit-2.3.0.tgz", - "integrity": "sha512-//88mFWSJx8lxCzwdAABTJL2MyWB12+eIY7MDL2SqLmAkeKU9qxRvWuSyTjm3FUmpBEMuFfckAIqEaVGUDxb6w==", - "dev": true, - "dependencies": { - "p-try": "^2.0.0" - }, - "engines": { - "node": ">=6" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/solidity-coverage/node_modules/p-locate": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/p-locate/-/p-locate-3.0.0.tgz", - "integrity": "sha512-x+12w/To+4GFfgJhBEpiDcLozRJGegY+Ei7/z0tSLkMmxGZNybVMSfWj9aJn8Z5Fc7dBUNJOOVgPv2H7IwulSQ==", - "dev": true, - "dependencies": { - "p-limit": "^2.0.0" - }, - "engines": { - "node": ">=6" - } - }, - "node_modules/solidity-coverage/node_modules/p-try": { - "version": "2.2.0", - "resolved": "https://registry.npmjs.org/p-try/-/p-try-2.2.0.tgz", - "integrity": "sha512-R4nPAVTAU0B9D35/Gk3uJf/7XYbQcyohSKdvAxIRSNghFl4e71hVoGnBNQz9cWaXxO2I10KTC+3jMdvvoKw6dQ==", - "dev": true, - "engines": { - "node": ">=6" - } - }, - "node_modules/solidity-coverage/node_modules/readdirp": { - "version": "3.2.0", - "resolved": "https://registry.npmjs.org/readdirp/-/readdirp-3.2.0.tgz", - "integrity": "sha512-crk4Qu3pmXwgxdSgGhgA/eXiJAPQiX4GMOZZMXnqKxHX7TaoL+3gQVo/WeuAiogr07DpnfjIMpXXa+PAIvwPGQ==", - "dev": true, - "dependencies": { - "picomatch": "^2.0.4" - }, - "engines": { - "node": ">= 8" - } - }, - "node_modules/solidity-coverage/node_modules/require-main-filename": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/require-main-filename/-/require-main-filename-2.0.0.tgz", - "integrity": "sha512-NKN5kMDylKuldxYLSUfrbo5Tuzh4hd+2E8NPPX02mZtn1VuREQToYe/ZdlJy+J3uCpfaiGF05e7B8W0iXbQHmg==", - "dev": true - }, "node_modules/solidity-coverage/node_modules/semver": { "version": "7.3.8", "resolved": "https://registry.npmjs.org/semver/-/semver-7.3.8.tgz", @@ -20117,139 +19770,12 @@ "node": ">=10" } }, - "node_modules/solidity-coverage/node_modules/string-width": { - "version": "3.1.0", - "resolved": "https://registry.npmjs.org/string-width/-/string-width-3.1.0.tgz", - "integrity": "sha512-vafcv6KjVZKSgz06oM/H6GDBrAtz8vdhQakGjFIvNrHA6y3HCF1CInLy+QLq8dTJPQ1b+KDUqDFctkdRW44e1w==", - "dev": true, - "dependencies": { - "emoji-regex": "^7.0.1", - "is-fullwidth-code-point": "^2.0.0", - "strip-ansi": "^5.1.0" - }, - "engines": { - "node": ">=6" - } - }, - "node_modules/solidity-coverage/node_modules/strip-ansi": { - "version": "5.2.0", - "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-5.2.0.tgz", - "integrity": "sha512-DuRs1gKbBqsMKIZlrffwlug8MHkcnpjs5VPmL1PAh+mA30U0DTotfDZ0d2UUsXpPmPmMMJ6W773MaA3J+lbiWA==", - "dev": true, - "dependencies": { - "ansi-regex": "^4.1.0" - }, - "engines": { - "node": ">=6" - } - }, - "node_modules/solidity-coverage/node_modules/strip-json-comments": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/strip-json-comments/-/strip-json-comments-2.0.1.tgz", - "integrity": "sha512-4gB8na07fecVVkOI6Rs4e7T6NOTki5EmL7TUduTs6bu3EdnSycntVJ4re8kgZA+wx9IueI2Y11bfbgwtzuE0KQ==", - "dev": true, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/solidity-coverage/node_modules/supports-color": { - "version": "6.0.0", - "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-6.0.0.tgz", - "integrity": "sha512-on9Kwidc1IUQo+bQdhi8+Tijpo0e1SS6RoGo2guUwn5vdaxw8RXOF9Vb2ws+ihWOmh4JnCJOvaziZWP1VABaLg==", - "dev": true, - "dependencies": { - "has-flag": "^3.0.0" - }, - "engines": { - "node": ">=6" - } - }, - "node_modules/solidity-coverage/node_modules/which": { - "version": "1.3.1", - "resolved": "https://registry.npmjs.org/which/-/which-1.3.1.tgz", - "integrity": "sha512-HxJdYWq1MTIQbJ3nw0cqssHoTNU267KlrDuGZ1WYlxDStUtKUhOaJmh112/TZmHxxUfuJqPXSOm7tDyas0OSIQ==", - "dev": true, - "dependencies": { - "isexe": "^2.0.0" - }, - "bin": { - "which": "bin/which" - } - }, - "node_modules/solidity-coverage/node_modules/which-module": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/which-module/-/which-module-2.0.0.tgz", - "integrity": "sha512-B+enWhmw6cjfVC7kS8Pj9pCrKSc5txArRyaYGe088shv/FGWH+0Rjx/xPgtsWfsUtS27FkP697E4DDhgrgoc0Q==", - "dev": true - }, - "node_modules/solidity-coverage/node_modules/wrap-ansi": { - "version": "5.1.0", - "resolved": "https://registry.npmjs.org/wrap-ansi/-/wrap-ansi-5.1.0.tgz", - "integrity": "sha512-QC1/iN/2/RPVJ5jYK8BGttj5z83LmSKmvbvrXPNCLZSEb32KKVDJDl/MOt2N01qU2H/FkzEa9PKto1BqDjtd7Q==", - "dev": true, - "dependencies": { - "ansi-styles": "^3.2.0", - "string-width": "^3.0.0", - "strip-ansi": "^5.0.0" - }, - "engines": { - "node": ">=6" - } - }, - "node_modules/solidity-coverage/node_modules/y18n": { - "version": "4.0.3", - "resolved": "https://registry.npmjs.org/y18n/-/y18n-4.0.3.tgz", - "integrity": "sha512-JKhqTOwSrqNA1NY5lSztJ1GrBiUodLMmIZuLiDaMRJ+itFd+ABVE8XBjOvIWL+rSqNDC74LCSFmlb/U4UZ4hJQ==", - "dev": true - }, "node_modules/solidity-coverage/node_modules/yallist": { "version": "4.0.0", "resolved": "https://registry.npmjs.org/yallist/-/yallist-4.0.0.tgz", "integrity": "sha512-3wdGidZyq5PB084XLES5TpOSRA3wjXAlIWMhum2kRcv/41Sn2emQ0dycQW4uZXLejwKvg6EsvbdlVL+FYEct7A==", "dev": true }, - "node_modules/solidity-coverage/node_modules/yargs": { - "version": "13.3.2", - "resolved": "https://registry.npmjs.org/yargs/-/yargs-13.3.2.tgz", - "integrity": "sha512-AX3Zw5iPruN5ie6xGRIDgqkT+ZhnRlZMLMHAs8tg7nRruy2Nb+i5o9bwghAogtM08q1dpr2LVoS8KSTMYpWXUw==", - "dev": true, - "dependencies": { - "cliui": "^5.0.0", - "find-up": "^3.0.0", - "get-caller-file": "^2.0.1", - "require-directory": "^2.1.1", - "require-main-filename": "^2.0.0", - "set-blocking": "^2.0.0", - "string-width": "^3.0.0", - "which-module": "^2.0.0", - "y18n": "^4.0.0", - "yargs-parser": "^13.1.2" - } - }, - "node_modules/solidity-coverage/node_modules/yargs-parser": { - "version": "13.1.2", - "resolved": "https://registry.npmjs.org/yargs-parser/-/yargs-parser-13.1.2.tgz", - "integrity": "sha512-3lbsNRf/j+A4QuSZfDRA7HRSfWrzO0YjqTJd5kjAq37Zep1CEgaYmrH9Q3GwPiB9cHyd1Y1UwggGhJGoxipbzg==", - "dev": true, - "dependencies": { - "camelcase": "^5.0.0", - "decamelize": "^1.2.0" - } - }, - "node_modules/solidity-coverage/node_modules/yargs-unparser": { - "version": "1.6.0", - "resolved": "https://registry.npmjs.org/yargs-unparser/-/yargs-unparser-1.6.0.tgz", - "integrity": "sha512-W9tKgmSn0DpSatfri0nx52Joq5hVXgeLiqR/5G0sZNDoLZFOr/xjBUDcShCOGNsBnEMNo1KAMBkTej1Hm62HTw==", - "dev": true, - "dependencies": { - "flat": "^4.1.0", - "lodash": "^4.17.15", - "yargs": "^13.3.0" - }, - "engines": { - "node": ">=6" - } - }, "node_modules/source-map": { "version": "0.2.0", "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.2.0.tgz", diff --git a/package.json b/package.json index 75320c9..648e202 100644 --- a/package.json +++ b/package.json @@ -46,7 +46,7 @@ "prettier": "^2.5.1", "prettier-plugin-solidity": "^1.0.0-beta.13", "solhint": "^3.3.7", - "solidity-coverage": "^0.8.2", + "solidity-coverage": "^0.8.12", "ts-node": "^10.7.0", "typechain": "^5.2.0", "typescript": "^4.6.2" diff --git a/test/BackedTokenImplementationWithMultiplierAndAutoFeeAccrual.ts b/test/BackedTokenImplementationWithMultiplierAndAutoFeeAccrual.ts index 26fc41f..e33d002 100644 --- a/test/BackedTokenImplementationWithMultiplierAndAutoFeeAccrual.ts +++ b/test/BackedTokenImplementationWithMultiplierAndAutoFeeAccrual.ts @@ -24,7 +24,7 @@ type SignerWithAddress = { // BackedTokenImplementationWithMultiplierAndAutoFeeAccrual specifications // Vast majority of comparisons are done with adjustment for precision of calculations, thus we are rather comparing difference of values, // rather than values themselves -describe.only("BackedTokenImplementationWithMultiplierAndAutoFeeAccrual", function () { +describe("BackedTokenImplementationWithMultiplierAndAutoFeeAccrual", function () { const accrualPeriodLength = 24 * 3600; const annualFee = 0.5; const multiplierAdjustmentPerPeriod = nthRoot(annualFee, 365).mul(Decimal.pow(10, 18)); @@ -239,15 +239,6 @@ describe.only("BackedTokenImplementationWithMultiplierAndAutoFeeAccrual", functi it('Should allow transfer of current balance of user', async () => { await expect(token.transfer(actor.address, (await token.balanceOf(actor.address)))).to.not.be.reverted; }) - it('Should allow transfer of current balance of user', async () => { - await expect(token.transfer(actor.address, (await token.balanceOf(actor.address)).div(2))).to.not.be.reverted; - await expect(token.transfer(actor.address, (await token.balanceOf(actor.address)))).to.not.be.reverted; - }) - it('Should allow transfer of current balance of user', async () => { - await token.updateFeePerPeriod(0); - await expect(token.transfer(actor.address, (await token.balanceOf(actor.address)).div(2))).to.not.be.reverted; - await expect(token.transfer(actor.address, (await token.balanceOf(actor.address)))).to.not.be.reverted; - }) }); describe('#transferShares', () => { it('Should allow transfer of shares of user', async () => { @@ -351,13 +342,29 @@ describe.only("BackedTokenImplementationWithMultiplierAndAutoFeeAccrual", functi const signer = await ethers.getSigner(owner.address); signature = await signer._signTypedData(domain, types, msg); }) - it('Should move requested shares of tokens', async () => { - await subject(); - expect((await token.sharesOf(actor.address))).to.be.eq(sharesToTransfer); + describe('And caller is whitelisted delegate', () => { + cacheBeforeEach(async () => { + await token.setDelegateWhitelist(actor.address, true); + }) + it('Should move requested shares of tokens', async () => { + await subject(); + expect((await token.sharesOf(actor.address))).to.be.eq(sharesToTransfer); + }) + it('Should increase balance of destination wallet', async () => { + await subject(); + expect((await token.balanceOf(actor.address))).to.be.eq(userBalance); + }) + it('Should revert if deadline already passed', async () => { + await helpers.time.setNextBlockTimestamp(deadline + 1); + await helpers.mine() + await expect(subject()).to.be.reverted; + }) }) - it('Should increase balance of destination wallet', async () => { - await subject(); - expect((await token.balanceOf(actor.address))).to.be.eq(userBalance); + describe('And caller is whitelisted delegate', () => { + + it('Should revert', async () => { + await expect(subject()).to.be.reverted; + }) }) }) }); From cc18365cbcac465fbeafb69c584ba0325caaed2b Mon Sep 17 00:00:00 2001 From: Miniroman Date: Fri, 21 Jun 2024 09:54:47 +0200 Subject: [PATCH 10/17] Rename new token implementation contract --- ...al.sol => BackedAutoFeeTokenImplementation.sol} | 2 +- ...rual.ts => BackedAutoFeeTokenImplementation.ts} | 14 +++++++------- 2 files changed, 8 insertions(+), 8 deletions(-) rename contracts/{BackedTokenImplementationWithMultiplierAndAutoFeeAccrual.sol => BackedAutoFeeTokenImplementation.sol} (96%) rename test/{BackedTokenImplementationWithMultiplierAndAutoFeeAccrual.ts => BackedAutoFeeTokenImplementation.ts} (97%) diff --git a/contracts/BackedTokenImplementationWithMultiplierAndAutoFeeAccrual.sol b/contracts/BackedAutoFeeTokenImplementation.sol similarity index 96% rename from contracts/BackedTokenImplementationWithMultiplierAndAutoFeeAccrual.sol rename to contracts/BackedAutoFeeTokenImplementation.sol index 9c987ad..2e9a063 100644 --- a/contracts/BackedTokenImplementationWithMultiplierAndAutoFeeAccrual.sol +++ b/contracts/BackedAutoFeeTokenImplementation.sol @@ -56,7 +56,7 @@ import "./SanctionsList.sol"; * */ -contract BackedTokenImplementationWithMultiplierAndAutoFeeAccrual is ERC20PermitDelegateTransferWithMultiplier +contract BackedAutoFeeTokenImplementation is ERC20PermitDelegateTransferWithMultiplier { // V2 diff --git a/test/BackedTokenImplementationWithMultiplierAndAutoFeeAccrual.ts b/test/BackedAutoFeeTokenImplementation.ts similarity index 97% rename from test/BackedTokenImplementationWithMultiplierAndAutoFeeAccrual.ts rename to test/BackedAutoFeeTokenImplementation.ts index e33d002..b9a0b6b 100644 --- a/test/BackedTokenImplementationWithMultiplierAndAutoFeeAccrual.ts +++ b/test/BackedAutoFeeTokenImplementation.ts @@ -1,7 +1,7 @@ import { ProxyAdmin__factory } from '../typechain/factories/ProxyAdmin__factory'; import { ProxyAdmin } from '../typechain/ProxyAdmin'; -import { BackedTokenImplementationWithMultiplierAndAutoFeeAccrual__factory } from '../typechain/factories/BackedTokenImplementationWithMultiplierAndAutoFeeAccrual__factory'; -import { BackedTokenImplementationWithMultiplierAndAutoFeeAccrual } from '../typechain/BackedTokenImplementationWithMultiplierAndAutoFeeAccrual'; +import { BackedAutoFeeTokenImplementation__factory } from '../typechain/factories/BackedAutoFeeTokenImplementation__factory'; +import { BackedAutoFeeTokenImplementation } from '../typechain/BackedAutoFeeTokenImplementation'; import * as helpers from "@nomicfoundation/hardhat-network-helpers"; import { expect } from "chai"; @@ -21,10 +21,10 @@ type SignerWithAddress = { address: string; }; -// BackedTokenImplementationWithMultiplierAndAutoFeeAccrual specifications +// BackedAutoFeeTokenImplementation specifications // Vast majority of comparisons are done with adjustment for precision of calculations, thus we are rather comparing difference of values, // rather than values themselves -describe("BackedTokenImplementationWithMultiplierAndAutoFeeAccrual", function () { +describe("BackedAutoFeeTokenImplementation", function () { const accrualPeriodLength = 24 * 3600; const annualFee = 0.5; const multiplierAdjustmentPerPeriod = nthRoot(annualFee, 365).mul(Decimal.pow(10, 18)); @@ -34,7 +34,7 @@ describe("BackedTokenImplementationWithMultiplierAndAutoFeeAccrual", function () const tokenSymbol = "bAAPL"; // General config: - let token: BackedTokenImplementationWithMultiplierAndAutoFeeAccrual; + let token: BackedAutoFeeTokenImplementation; let sanctionsList: SanctionsListMock; let proxyAdmin: ProxyAdmin; let accounts: Signer[]; @@ -66,7 +66,7 @@ describe("BackedTokenImplementationWithMultiplierAndAutoFeeAccrual", function () await helpers.time.setNextBlockTimestamp(baseTime); - const tokenImplementationFactory = new BackedTokenImplementationWithMultiplierAndAutoFeeAccrual__factory(owner.signer); + const tokenImplementationFactory = new BackedAutoFeeTokenImplementation__factory(owner.signer); const tokenImplementation = await tokenImplementationFactory.deploy(); const proxyAdminFactory = new ProxyAdmin__factory(owner.signer) proxyAdmin = await proxyAdminFactory.deploy(); @@ -77,7 +77,7 @@ describe("BackedTokenImplementationWithMultiplierAndAutoFeeAccrual", function () tokenSymbol ] )); - token = BackedTokenImplementationWithMultiplierAndAutoFeeAccrual__factory.connect(tokenProxy.address, owner.signer); + token = BackedAutoFeeTokenImplementation__factory.connect(tokenProxy.address, owner.signer); await token.setMinter(owner.address); await token.setBurner(owner.address); await token.setPauser(owner.address); From 3af2ddfea6f247464aa8b56671e7a243116454c6 Mon Sep 17 00:00:00 2001 From: Miniroman Date: Mon, 24 Jun 2024 07:48:28 +0200 Subject: [PATCH 11/17] Fix comments --- .../BackedAutoFeeTokenImplementation.sol | 432 ++++++++++++------ test/BackedAutoFeeTokenImplementation.ts | 10 +- 2 files changed, 291 insertions(+), 151 deletions(-) diff --git a/contracts/BackedAutoFeeTokenImplementation.sol b/contracts/BackedAutoFeeTokenImplementation.sol index 2e9a063..12187af 100644 --- a/contracts/BackedAutoFeeTokenImplementation.sol +++ b/contracts/BackedAutoFeeTokenImplementation.sol @@ -37,28 +37,27 @@ pragma solidity 0.8.9; import "@openzeppelin/contracts-upgradeable/access/OwnableUpgradeable.sol"; -import "./ERC20PermitDelegateTransferWithMultiplier.sol"; +import "./BackedTokenImplementation.sol"; import "./SanctionsList.sol"; /** * @dev * * This token contract is following the ERC20 standard. - * It inherits ERC20PermitDelegateTransferWithMultiplier.sol, which extends the basic ERC20 to also allow permit, delegateTransfer and delegateTransferShares EIP-712 functionality. - * Enforces Sanctions List via the Chainalysis standard interface. - * The contract contains four roles: - * - A minter, that can mint new tokens. - * - A burner, that can burn its own tokens, or contract's tokens. - * - A pauser, that can pause or restore all transfers in the contract. + * It inherits BackedTokenImplementation.sol, which is base Backed token implementation. BackedAutoFeeTokenImplementation extends it + * with logic of multiplier, which is used for rebasing logic of the token, thus becoming rebase token itself. Additionally, it contains + * mechanism, which changes this multiplier per configured fee periodically, on defined period length. + * It contains one additional role: * - A multiplierUpdated, that can update value of a multiplier. - * - An owner, that can set the four above, and also the sanctionsList pointer. - * The owner can also set who can use the EIP-712 functionality, either specific accounts via a whitelist, or everyone. * */ -contract BackedAutoFeeTokenImplementation is ERC20PermitDelegateTransferWithMultiplier -{ - // V2 +contract BackedAutoFeeTokenImplementation is BackedTokenImplementation { + // Calculating the Delegated Transfer Shares typehash: + bytes32 public constant DELEGATED_TRANSFER_SHARES_TYPEHASH = + keccak256( + "DELEGATED_TRANSFER_SHARES(address owner,address to,uint256 value,uint256 nonce,uint256 deadline)" + ); // Roles: address public multiplierUpdater; @@ -68,30 +67,93 @@ contract BackedAutoFeeTokenImplementation is ERC20PermitDelegateTransferWithMult uint256 public feePerPeriod; // in 1e18 precision uint256 public periodLength; + /** + * @dev Defines ratio between a single share of a token to balance of a token. + * Defined in 1e18 precision. + * + */ + uint256 public multiplier; + + mapping(address => uint256) private _shares; + + uint256 internal _totalShares; + // Events: + + /** + * @dev Emitted when multiplier updater is changed + */ event NewMultiplierUpdater(address indexed newMultiplierUpdater); + /** + * @dev Emitted when `value` token shares are moved from one account (`from`) to + * another (`to`). + * + * Note that `value` may be zero. + */ + event TransferShares( + address indexed from, + address indexed to, + uint256 value + ); + + /** + * @dev Emitted when multiplier value is updated + */ + event MultiplierUpdated(uint256 value); + + // Modifiers: + modifier updateMultiplier() { (uint256 newMultiplier, uint256 periodsPassed) = getCurrentMultiplier(); lastTimeFeeApplied = lastTimeFeeApplied + periodLength * periodsPassed; - if (multiplier() != newMultiplier) { + if (multiplier != newMultiplier) { _updateMultiplier(newMultiplier); } _; } + // Initializers: + function initialize( string memory name_, string memory symbol_ - ) override public virtual initializer { + ) public virtual override { + _initialize(name_, symbol_, 24 * 3600, block.timestamp); + } + + function initialize( + string memory name_, + string memory symbol_, + uint256 _periodLength, + uint256 _lastTimeFeeApplied + ) public virtual { + _initialize(name_, symbol_, _periodLength, _lastTimeFeeApplied); + } + + function _initialize( + string memory name_, + string memory symbol_, + uint256 _periodLength, + uint256 _lastTimeFeeApplied + ) public virtual initializer { __ERC20_init(name_, symbol_); __Ownable_init(); _buildDomainSeparator(); _setTerms("https://www.backedassets.fi/legal-documentation"); // Default Terms - __Multiplier_init(); - - periodLength = 24 * 3600; // Set to 24h by default - lastTimeFeeApplied = block.timestamp; + + multiplier = 1e18; + + periodLength = _periodLength; + lastTimeFeeApplied = _lastTimeFeeApplied; + } + + /** + * @dev See {IERC20-totalSupply}. + */ + function totalSupply() public view virtual override returns (uint256) { + (uint256 newMultiplier, ) = getCurrentMultiplier(); + return (_totalShares * newMultiplier) / 1e18; } /** @@ -100,15 +162,13 @@ contract BackedAutoFeeTokenImplementation is ERC20PermitDelegateTransferWithMult function balanceOf( address account ) public view virtual override returns (uint256) { - (uint256 multiplier, ) = getCurrentMultiplier(); - return (sharesOf(account) * multiplier) / 1e18; + (uint256 newMultiplier, ) = getCurrentMultiplier(); + return (sharesOf(account) * newMultiplier) / 1e18; } /** * @dev Retrieves most up to date value of multiplier * - * Note Probably it should be renamed into multiplier and allow getting stored version of multiplier - * via getStoredMultiplier() method */ function getCurrentMultiplier() public @@ -117,7 +177,7 @@ contract BackedAutoFeeTokenImplementation is ERC20PermitDelegateTransferWithMult returns (uint256 newMultiplier, uint256 periodsPassed) { periodsPassed = (block.timestamp - lastTimeFeeApplied) / periodLength; - newMultiplier = multiplier(); + newMultiplier = multiplier; if (feePerPeriod > 0) { for (uint256 index = 0; index < periodsPassed; index++) { newMultiplier = (newMultiplier * (1e18 - feePerPeriod)) / 1e18; @@ -126,50 +186,34 @@ contract BackedAutoFeeTokenImplementation is ERC20PermitDelegateTransferWithMult } /** - * @dev See {IERC20-totalSupply}. + * @dev Returns amount of shares owned by given account */ - function totalSupply() public view virtual override returns (uint256) { - (uint256 multiplier, ) = getCurrentMultiplier(); - return _totalShares * multiplier / 1e18; + function sharesOf(address account) public view virtual returns (uint256) { + return _shares[account]; } /** - * @dev Perform an intended transfer on one account's behalf, from another account, - * who actually pays fees for the transaction. Allowed only if the sender - * is whitelisted, or the delegateMode is set to true - * - * @param owner The account that provided the signature and from which the tokens will be taken - * @param to The account that will receive the tokens - * @param value The amount of tokens to transfer - * @param deadline Expiration time, seconds since the epoch - * @param v v part of the signature - * @param r r part of the signature - * @param s s part of the signature + * @return the amount of shares that corresponds to `_underlyingAmount` underlying amount. */ - function delegatedTransfer( - address owner, - address to, - uint256 value, - uint256 deadline, - uint8 v, - bytes32 r, - bytes32 s - ) public override updateMultiplier { - super.delegatedTransfer(owner, to, value, deadline, v, r, s); + function getSharesByUnderlyingAmount( + uint256 _underlyingAmount + ) public view returns (uint256) { + (uint256 newMultiplier, ) = getCurrentMultiplier(); + return _getSharesByUnderlyingAmount(_underlyingAmount, newMultiplier); } - + /** - * @dev Perform an intended shares transfer on one account's behalf, from another account, - * who actually pays fees for the transaction. Allowed only if the sender - * is whitelisted, or the delegateMode is set to true - * - * @param owner The account that provided the signature and from which the tokens will be taken - * @param to The account that will receive the tokens - * @param value The amount of token shares to transfer - * @param deadline Expiration time, seconds since the epoch - * @param v v part of the signature - * @param r r part of the signature - * @param s s part of the signature + * @return the amount of underlying that corresponds to `_sharesAmount` token shares. + */ + function getUnderlyingAmountByShares( + uint256 _sharesAmount + ) public view returns (uint256) { + (uint256 newMultiplier, ) = getCurrentMultiplier(); + return _getUnderlyingAmountByShares(_sharesAmount, newMultiplier); + } + + /** + * @dev Delegated Transfer Shares, transfer shares via a sign message, using erc712. */ function delegatedTransferShares( address owner, @@ -179,23 +223,23 @@ contract BackedAutoFeeTokenImplementation is ERC20PermitDelegateTransferWithMult uint8 v, bytes32 r, bytes32 s - ) public virtual override allowedDelegate updateMultiplier { - super.delegatedTransferShares(owner, to, value, deadline, v, r, s); - } + ) public virtual allowedDelegate { + require(block.timestamp <= deadline, "ERC20Permit: expired deadline"); - /** - * @dev See {IERC20-transfer}. - * - * Requirements: - * - * - `to` cannot be the zero address. - * - the caller must have a balance of at least `amount`. - */ - function transfer( - address to, - uint256 amount - ) public virtual override updateMultiplier returns (bool) { - return super.transfer(to, amount); + bytes32 structHash = keccak256( + abi.encode( + DELEGATED_TRANSFER_SHARES_TYPEHASH, + owner, + to, + value, + _useNonce(owner), + deadline + ) + ); + _checkOwner(owner, structHash, v, r, s); + + uint256 amount = _getUnderlyingAmountByShares(value, multiplier); + _transfer(owner, to, amount); } /** @@ -209,85 +253,56 @@ contract BackedAutoFeeTokenImplementation is ERC20PermitDelegateTransferWithMult function transferShares( address to, uint256 sharesAmount - ) public virtual override updateMultiplier returns (bool) { - return super.transferShares(to, sharesAmount); + ) public virtual updateMultiplier returns (bool) { + address owner = _msgSender(); + uint256 amount = _getUnderlyingAmountByShares(sharesAmount, multiplier); // This method might lead to be unable to transfer all shares from account if multiplier is below 1e18 + _transfer(owner, to, amount); + return true; } /** - * @dev See {IERC20-transferFrom}. - * - * Emits an {Approval} event indicating the updated allowance. This is not - * required by the EIP. See the note at the beginning of {ERC20}. - * - * NOTE: Does not update the allowance if the current allowance - * is the maximum `uint256`. - * - * Requirements: + * @dev Function to set the new fee. Allowed only for owner * - * - `from` and `to` cannot be the zero address. - * - `from` must have a balance of at least `amount`. - * - the caller must have allowance for ``from``'s tokens of at least - * `amount`. + * @param newFeePerPeriod The new fee per period value */ - function transferFrom( - address from, - address to, - uint256 amount - ) public virtual override updateMultiplier returns (bool) { - return super.transferFrom(from, to, amount); + function updateFeePerPeriod( + uint256 newFeePerPeriod + ) external updateMultiplier onlyOwner { + feePerPeriod = newFeePerPeriod; } /** - * @dev Function to mint tokens. Allowed only for minter + * @dev Function to change the contract multiplier updater. Allowed only for owner * - * @param account The address that will receive the minted tokens - * @param amount The amount of tokens to mint - */ - function mint( - address account, - uint256 amount - ) override external virtual updateMultiplier { - require(_msgSender() == minter, "BackedToken: Only minter"); - uint256 sharesAmount = getSharesByUnderlyingAmount(amount); - _mintShares(account, sharesAmount); - } - - /** - * @dev Function to burn tokens. Allowed only for burner. The burned tokens - * must be from the burner (msg.sender), or from the contract itself + * Emits a { NewMultiplierUpdater } event * - * @param account The account from which the tokens will be burned - * @param amount The amount of tokens to be burned + * @param newMultiplierUpdater The address of the new multiplier updater */ - function burn(address account, uint256 amount) override external virtual updateMultiplier { - require(_msgSender() == burner, "BackedToken: Only burner"); - require( - account == _msgSender() || account == address(this), - "BackedToken: Cannot burn account" - ); - uint256 sharesAmount = getSharesByUnderlyingAmount(amount); - _burnShares(account, sharesAmount); + function setMultiplierUpdater( + address newMultiplierUpdater + ) external onlyOwner { + multiplierUpdater = newMultiplierUpdater; + emit NewMultiplierUpdater(newMultiplierUpdater); } /** - * @dev Function to set the new fee. Allowed only for owner + * @dev Function to change the time of last fee accrual. Allowed only for owner * - * @param newFeePerPeriod The new fee per period value + * @param newLastTimeFeeApplied A timestamp of last time fee was applied */ - function updateFeePerPeriod(uint256 newFeePerPeriod) external onlyOwner { - feePerPeriod = newFeePerPeriod; + function setLastTimeFeeApplied( + uint256 newLastTimeFeeApplied + ) external onlyOwner { + lastTimeFeeApplied = newLastTimeFeeApplied; } /** - * @dev Function to change the contract multiplier updater. Allowed only for owner - * - * Emits a { NewMultiplierUpdater } event + * @dev Function to change period length. Allowed only for owner * - * @param newMultiplierUpdater The address of the new multiplier updater + * @param newPeriodLength Length of a single accrual period in seconds */ - function setMultiplierUpdater(address newMultiplierUpdater) external onlyOwner { - multiplierUpdater = newMultiplierUpdater; - emit NewMultiplierUpdater(newMultiplierUpdater); + function setPeriodLength(uint256 newPeriodLength) external onlyOwner { + periodLength = newPeriodLength; } /** @@ -306,29 +321,154 @@ contract BackedAutoFeeTokenImplementation is ERC20PermitDelegateTransferWithMult "BackedToken: Only multiplier updater" ); require( - multiplier() == oldMultiplier, + multiplier == oldMultiplier, "BackedToken: Multiplier changed in the meantime" ); _updateMultiplier(newMultiplier); } /** - * @dev Function to change the time of last fee accrual. Allowed only for owner + * @return the amount of shares that corresponds to `_underlyingAmount` underlying amount. + */ + function _getSharesByUnderlyingAmount( + uint256 underlyingAmount, + uint256 multiplier + ) internal view returns (uint256) { + return (underlyingAmount * 1e18) / multiplier; + } + + /** + * @return the amount of underlying that corresponds to `_sharesAmount` token shares. + */ + function _getUnderlyingAmountByShares( + uint256 sharesAmount, + uint256 multiplier + ) internal view returns (uint256) { + return (sharesAmount * multiplier) / 1e18; + } + + /** + * @dev Moves `amount` of tokens from `sender` to `recipient`. * - * @param newLastTimeFeeApplied A timestamp of last time fee was applied + * This internal function is equivalent to {transfer}, and can be used to + * e.g. implement automatic token fees, slashing mechanisms, etc. + * + * Emits a {Transfer} event. + * Emits a {TransferShares} event. + * + * Requirements: + * + * - `from` cannot be the zero address. + * - `to` cannot be the zero address. + * - `from` must have a balance of at least `amount`. */ - function setLastTimeFeeApplied(uint256 newLastTimeFeeApplied) external onlyOwner { - lastTimeFeeApplied = newLastTimeFeeApplied; - } - + function _transfer( + address from, + address to, + uint256 amount + ) internal virtual override { + require(from != address(0), "ERC20: transfer from the zero address"); + require(to != address(0), "ERC20: transfer to the zero address"); + + _beforeTokenTransfer(from, to, amount); + uint256 _sharesAmount = _getSharesByUnderlyingAmount( + amount, + multiplier + ); + + uint256 currentSenderShares = _shares[from]; + require( + currentSenderShares >= _sharesAmount, + "ERC20: transfer amount exceeds balance" + ); + + unchecked { + _shares[from] = currentSenderShares - (_sharesAmount); + } + _shares[to] = _shares[to] + (_sharesAmount); + + emit Transfer(from, to, amount); + emit TransferShares(from, to, _sharesAmount); + + _afterTokenTransfer(from, to, amount); + } + + /** @dev Creates `amount` tokens and assigns them to `account`, increasing + * the total supply. + * + * Emits a {Transfer} event with `from` set to the zero address. + * Emits a {TransferShares} event with `from` set to the zero address. + * + * Requirements: + * + * - `account` cannot be the zero address. + */ + function _mint(address account, uint256 amount) internal virtual override { + require(account != address(0), "ERC20: mint to the zero address"); + + _beforeTokenTransfer(address(0), account, amount); + uint256 sharesAmount = _getSharesByUnderlyingAmount(amount, multiplier); + + _totalShares += sharesAmount; + _shares[account] += sharesAmount; + emit Transfer(address(0), account, amount); + emit TransferShares(address(0), account, sharesAmount); + + _afterTokenTransfer(address(0), account, amount); + } + /** - * @dev Function to change period length. Allowed only for owner + * @dev Destroys `amount` tokens from `account`, reducing the + * total supply. * - * @param newPeriodLength Length of a single accrual period in seconds + * Emits a {Transfer} event with `to` set to the zero address. + * Emits a {TransferShares} event with `to` set to the zero address. + * + * Requirements: + * + * - `account` cannot be the zero address. + * - `account` must have at least `sharesAmount` token shares. */ - function setPeriodLength(uint256 newPeriodLength) external onlyOwner { - periodLength = newPeriodLength; - } + function _burn(address account, uint256 amount) internal virtual override { + require(account != address(0), "ERC20: burn from the zero address"); + + _beforeTokenTransfer(account, address(0), amount); + uint256 sharesAmount = _getSharesByUnderlyingAmount(amount, multiplier); + + uint256 accountBalance = _shares[account]; + require( + accountBalance >= sharesAmount, + "ERC20: burn amount exceeds balance" + ); + unchecked { + _shares[account] = accountBalance - sharesAmount; + } + _totalShares -= sharesAmount; + + emit Transfer(account, address(0), amount); + emit TransferShares(account, address(0), sharesAmount); + + _afterTokenTransfer(account, address(0), amount); + } + + /** + * @dev Updates currently stored multiplier with a new value + * + * Emit an {MultiplierUpdated} event. + */ + function _updateMultiplier(uint256 newMultiplier) internal virtual { + multiplier = newMultiplier; + emit MultiplierUpdated(newMultiplier); + } + + // Implement the update multiplier functionality before transfer: + function _beforeTokenTransfer( + address from, + address to, + uint256 amount + ) internal virtual override updateMultiplier { + super._beforeTokenTransfer(from, to, amount); + } /** * @dev This empty reserved space is put in place to allow future versions to add new diff --git a/test/BackedAutoFeeTokenImplementation.ts b/test/BackedAutoFeeTokenImplementation.ts index b9a0b6b..00401fa 100644 --- a/test/BackedAutoFeeTokenImplementation.ts +++ b/test/BackedAutoFeeTokenImplementation.ts @@ -71,10 +71,12 @@ describe("BackedAutoFeeTokenImplementation", function () { const proxyAdminFactory = new ProxyAdmin__factory(owner.signer) proxyAdmin = await proxyAdminFactory.deploy(); const tokenProxy = await new BackedTokenProxy__factory(owner.signer).deploy(tokenImplementation.address, proxyAdmin.address, tokenImplementation.interface.encodeFunctionData( - 'initialize', + 'initialize(string,string,uint256,uint256)', [ tokenName, - tokenSymbol + tokenSymbol, + 24 * 3600, + baseTime ] )); token = BackedAutoFeeTokenImplementation__factory.connect(tokenProxy.address, owner.signer); @@ -85,8 +87,6 @@ describe("BackedAutoFeeTokenImplementation", function () { sanctionsList = await new SanctionsListMock__factory(blacklister.signer).deploy(); await token.setSanctionsList(sanctionsList.address); await token.updateFeePerPeriod(baseFeePerPeriod); - await token.setLastTimeFeeApplied(baseTime); - await token.setPeriodLength(accrualPeriodLength); // Chain Id const network = await ethers.provider.getNetwork(); @@ -373,7 +373,7 @@ describe("BackedAutoFeeTokenImplementation", function () { it("Cannot initialize twice", async function () { await expect( - token.connect(owner.signer).initialize("test1", "test2") + token.connect(owner.signer)['initialize(string,string)']("test1", "test2") ).to.be.revertedWith("Initializable: contract is already initialized"); }); From 48e53d7bcfd7890bbc1bb3026e4af9a335d0cbff Mon Sep 17 00:00:00 2001 From: Miniroman Date: Mon, 24 Jun 2024 08:04:56 +0200 Subject: [PATCH 12/17] Remove unused contracts --- ...20PermitDelegateTransferWithMultiplier.sol | 71 ----- contracts/ERC20UpgradeableWithMultiplier.sol | 261 ------------------ 2 files changed, 332 deletions(-) delete mode 100644 contracts/ERC20PermitDelegateTransferWithMultiplier.sol delete mode 100644 contracts/ERC20UpgradeableWithMultiplier.sol diff --git a/contracts/ERC20PermitDelegateTransferWithMultiplier.sol b/contracts/ERC20PermitDelegateTransferWithMultiplier.sol deleted file mode 100644 index cebf83e..0000000 --- a/contracts/ERC20PermitDelegateTransferWithMultiplier.sol +++ /dev/null @@ -1,71 +0,0 @@ -/** - * SPDX-License-Identifier: MIT - * - * Copyright (c) 2021-2022 Backed Finance AG - * - * Permission is hereby granted, free of charge, to any person obtaining a copy - * of this software and associated documentation files (the "Software"), to deal - * in the Software without restriction, including without limitation the rights - * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell - * copies of the Software, and to permit persons to whom the Software is - * furnished to do so, subject to the following conditions: - * - * The above copyright notice and this permission notice shall be included in - * copies or substantial portions of the Software. - * - * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR - * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, - * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE - * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER - * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, - * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE - * SOFTWARE. - */ -pragma solidity 0.8.9; - -import "./ERC20UpgradeableWithMultiplier.sol"; - -/** - * @dev - * - * This contract is a based (copy-paste with changes) on OpenZeppelin's draft-ERC20Permit.sol (token/ERC20/extensions/draft-ERC20Permit.sol). - * - * The changes are: - * - Adding also delegated transfer functionality, that is similar to permit, but doing the actual transfer and not approval. - * - Cutting some of the generalities to make the contacts more straight forward for this case (e.g. removing the counters library). - * -*/ - -contract ERC20PermitDelegateTransferWithMultiplier is ERC20UpgradeableWithMultiplier { - - // Calculating the Delegated Transfer Shares typehash: - bytes32 public constant DELEGATED_TRANSFER_SHARES_TYPEHASH = - keccak256("DELEGATED_TRANSFER_SHARES(address owner,address to,uint256 value,uint256 nonce,uint256 deadline)"); - - /** - * @dev Delegated Transfer Shares, transfer shares via a sign message, using erc712. - */ - function delegatedTransferShares( - address owner, - address to, - uint256 value, - uint256 deadline, - uint8 v, - bytes32 r, - bytes32 s - ) public virtual { - require(block.timestamp <= deadline, "ERC20Permit: expired deadline"); - - bytes32 structHash = keccak256(abi.encode(DELEGATED_TRANSFER_SHARES_TYPEHASH, owner, to, value, _useNonce(owner), deadline)); - _checkOwner(owner, structHash, v, r, s); - - _transferShares(owner, to, value); - } - - /** - * @dev This empty reserved space is put in place to allow future versions to add new - * variables without shifting down storage in the inheritance chain. - * See https://docs.openzeppelin.com/contracts/4.x/upgradeable#storage_gaps - */ - uint256[50] private __gap; -} diff --git a/contracts/ERC20UpgradeableWithMultiplier.sol b/contracts/ERC20UpgradeableWithMultiplier.sol deleted file mode 100644 index d8dd91e..0000000 --- a/contracts/ERC20UpgradeableWithMultiplier.sol +++ /dev/null @@ -1,261 +0,0 @@ -// SPDX-License-Identifier: MIT -// OpenZeppelin Contracts (last updated v4.5.0) (token/ERC20/ERC20.sol) - -pragma solidity ^0.8.0; - -import "./BackedTokenImplementation.sol"; - -/** - * @dev Implementation of the {IERC20} interface. - * - * This implementation is agnostic to the way tokens are created. This means - * that a supply mechanism has to be added in a derived contract using {_mint}. - * For a generic mechanism see {ERC20PresetMinterPauser}. - * - * TIP: For a detailed writeup see our guide - * https://forum.zeppelin.solutions/t/how-to-implement-erc20-supply-mechanisms/226[How - * to implement supply mechanisms]. - * - * We have followed general OpenZeppelin Contracts guidelines: functions revert - * instead returning `false` on failure. This behavior is nonetheless - * conventional and does not conflict with the expectations of ERC20 - * applications. - * - * Additionally, an {Approval} event is emitted on calls to {transferFrom}. - * This allows applications to reconstruct the allowance for all accounts just - * by listening to said events. Other implementations of the EIP may not emit - * these events, as it isn't required by the specification. - * - * Finally, the non-standard {decreaseAllowance} and {increaseAllowance} - * functions have been added to mitigate the well-known issues around setting - * allowances. See {IERC20-approve}. - */ -contract ERC20UpgradeableWithMultiplier is BackedTokenImplementation { - mapping(address => uint256) private _shares; - - uint256 internal _totalShares; - - /** - * @dev Defines ratio between a single share of a token to balance of a token. - * Defined in 1e18 precision. - * - */ - uint256 private _multiplier; - - /** - * @dev Emitted when `value` token shares are moved from one account (`from`) to - * another (`to`). - * - * Note that `value` may be zero. - */ - event TransferShares(address indexed from, address indexed to, uint256 value); - - /** - * @dev Emitted when multiplier value is updated - */ - event MultiplierUpdated(uint256 value); - - /** - * @dev Sets the values for {name} and {symbol}. - * - * The default value of {decimals} is 18. To select a different value for - * {decimals} you should overload it. - * - * All two of these values are immutable: they can only be set once during - * construction. - */ - function __Multiplier_init() internal onlyInitializing { - __Multiplier_init_unchained(); - } - - function __Multiplier_init_unchained() internal onlyInitializing { - _multiplier = 1e18; - } - - /** - * @dev See {IERC20-totalSupply}. - */ - function totalSupply() public view virtual override returns (uint256) { - return _totalShares * _multiplier / 1e18; - } - - /** - * @dev Returns ratio of shares to underlying amount in 18 decimals precision - */ - function multiplier() public view virtual returns (uint256) { - return _multiplier; - } - - /** - * @dev See {IERC20-balanceOf}. - */ - function balanceOf(address account) public view virtual override returns (uint256) { - return _shares[account] * _multiplier / 1e18; - } - - /** - * @dev Returns amount of shares owned by given account - */ - function sharesOf(address account) public view virtual returns (uint256) { - return _shares[account]; - } - - /** - * @dev Transfers underlying shares to destination account - * - * Requirements: - * - * - `to` cannot be the zero address. - * - the caller must have a balance of at least `sharesAmount`. - */ - function transferShares(address to, uint256 sharesAmount) public virtual returns (bool) { - address owner = _msgSender(); - _transferShares(owner, to, sharesAmount); - return true; - } - - /** - * @return the amount of shares that corresponds to `_underlyingAmount` underlying amount. - */ - function getSharesByUnderlyingAmount(uint256 _underlyingAmount) public view returns (uint256) { - return _underlyingAmount - * 1e18 - / _multiplier; - } - - /** - * @return the amount of underlying that corresponds to `_sharesAmount` token shares. - */ - function getUnderlyingAmountByShares(uint256 _sharesAmount) public view returns (uint256) { - return _sharesAmount - * _multiplier - / 1e18; - } - - /** - * @notice Moves `_sharesAmount` shares from `from` to `to`. - * - * Requirements: - * - * - `from` cannot be the zero address. - * - `from` must hold at least `_sharesAmount` shares. - * - the contract must not be paused. - */ - function _transferShares(address from, address to, uint256 _sharesAmount) internal { - require(from != address(0), "ERC20: transfer from the zero address"); - require(to != address(0), "ERC20: transfer to the zero address"); - uint256 amount = getUnderlyingAmountByShares(_sharesAmount); - _beforeTokenTransfer(from, to, amount); - - uint256 currentSenderShares = _shares[from]; - require(currentSenderShares >= _sharesAmount , "ERC20: transfer amount exceeds balance"); - - unchecked { - _shares[from] = currentSenderShares - (_sharesAmount); - } - _shares[to] = _shares[to] + (_sharesAmount); - - emit Transfer(from, to, amount); - emit TransferShares(from, to, _sharesAmount); - - _afterTokenTransfer(from, to, amount); - } - - /** - * @dev Moves `amount` of tokens from `sender` to `recipient`. - * - * This internal function is equivalent to {transfer}, and can be used to - * e.g. implement automatic token fees, slashing mechanisms, etc. - * - * Emits a {Transfer} event. - * Emits a {TransferShares} event. - * - * Requirements: - * - * - `from` cannot be the zero address. - * - `to` cannot be the zero address. - * - `from` must have a balance of at least `amount`. - */ - function _transfer( - address from, - address to, - uint256 amount - ) internal virtual override { - uint256 _sharesToTransfer = getSharesByUnderlyingAmount(amount); - _transferShares(from, to, _sharesToTransfer); - } - - /** @dev Creates `amount` token shares and assigns them to `account`, increasing - * the total supply. - * - * Emits a {Transfer} event with `from` set to the zero address. - * Emits a {TransferShares} event with `from` set to the zero address. - * - * Requirements: - * - * - `account` cannot be the zero address. - */ - function _mintShares(address account, uint256 sharesAmount) internal virtual { - require(account != address(0), "ERC20: mint to the zero address"); - uint256 amount = getUnderlyingAmountByShares(sharesAmount); - - _beforeTokenTransfer(address(0), account, amount); - - _totalShares += sharesAmount; - _shares[account] += sharesAmount; - emit Transfer(address(0), account, amount); - emit TransferShares(address(0), account, sharesAmount); - - _afterTokenTransfer(address(0), account, amount); - } - - /** - * @dev Destroys `amount` tokens from `account`, reducing the - * total supply. - * - * Emits a {Transfer} event with `to` set to the zero address. - * Emits a {TransferShares} event with `to` set to the zero address. - * - * Requirements: - * - * - `account` cannot be the zero address. - * - `account` must have at least `sharesAmount` token shares. - */ - function _burnShares(address account, uint256 sharesAmount) internal virtual { - require(account != address(0), "ERC20: burn from the zero address"); - uint256 amount = getUnderlyingAmountByShares(sharesAmount); - - _beforeTokenTransfer(account, address(0), amount); - - uint256 accountBalance = _shares[account]; - require(accountBalance >= sharesAmount, "ERC20: burn amount exceeds balance"); - unchecked { - _shares[account] = accountBalance - sharesAmount; - } - _totalShares -= sharesAmount; - - emit Transfer(account, address(0), amount); - emit TransferShares(account, address(0), sharesAmount); - - _afterTokenTransfer(account, address(0), amount); - } - - /** - * @dev Updates currently stored multiplier with a new value - * - * Emit an {MultiplierUpdated} event. - */ - function _updateMultiplier( - uint256 newMultiplier - ) internal virtual { - _multiplier = newMultiplier; - emit MultiplierUpdated(newMultiplier); - } - - /** - * @dev This empty reserved space is put in place to allow future versions to add new - * variables without shifting down storage in the inheritance chain. - * See https://docs.openzeppelin.com/contracts/4.x/upgradeable#storage_gaps - */ - uint256[47] private __gap; -} From f1f33d03c0094ca07c4d741661037eb481295c63 Mon Sep 17 00:00:00 2001 From: Miniroman Date: Mon, 24 Jun 2024 08:53:03 +0200 Subject: [PATCH 13/17] Fix time-dependent tests --- .../BackedAutoFeeTokenImplementation.sol | 2 +- test/BackedAutoFeeTokenImplementation.ts | 3 + test/Upgradability.ts | 145 ++++++++++++++++++ 3 files changed, 149 insertions(+), 1 deletion(-) diff --git a/contracts/BackedAutoFeeTokenImplementation.sol b/contracts/BackedAutoFeeTokenImplementation.sol index 12187af..0626f29 100644 --- a/contracts/BackedAutoFeeTokenImplementation.sol +++ b/contracts/BackedAutoFeeTokenImplementation.sol @@ -54,7 +54,7 @@ import "./SanctionsList.sol"; contract BackedAutoFeeTokenImplementation is BackedTokenImplementation { // Calculating the Delegated Transfer Shares typehash: - bytes32 public constant DELEGATED_TRANSFER_SHARES_TYPEHASH = + bytes32 constant public DELEGATED_TRANSFER_SHARES_TYPEHASH = keccak256( "DELEGATED_TRANSFER_SHARES(address owner,address to,uint256 value,uint256 nonce,uint256 deadline)" ); diff --git a/test/BackedAutoFeeTokenImplementation.ts b/test/BackedAutoFeeTokenImplementation.ts index 00401fa..6b7f7b3 100644 --- a/test/BackedAutoFeeTokenImplementation.ts +++ b/test/BackedAutoFeeTokenImplementation.ts @@ -93,6 +93,9 @@ describe("BackedAutoFeeTokenImplementation", function () { chainId = BigNumber.from(network.chainId); }); + this.afterAll(async () => { + await helpers.reset(); + }) describe('#getCurrentMultiplier', () => { describe('when time moved by 365 days forward', () => { diff --git a/test/Upgradability.ts b/test/Upgradability.ts index 7edccce..beb1a2d 100644 --- a/test/Upgradability.ts +++ b/test/Upgradability.ts @@ -1,5 +1,7 @@ import { getSigner, SignerWithAddress } from "./helpers"; import { + BackedAutoFeeTokenImplementation, + BackedFactory, BackedFactoryV1, BackedTokenImplementation, BackedTokenImplementationV1, @@ -134,3 +136,146 @@ describe("Upgrade from v1.0.0 to v1.1.0", () => { expect(await tokenV2.balanceOf(tmpAccount.address)).to.equal(150); }); }); + +describe("Upgrade from v1.1.0 to v1.2.0", () => { + let implementationV2: BackedAutoFeeTokenImplementation; + let tokenV2: BackedAutoFeeTokenImplementation; + let tokenV1: BackedTokenImplementation; + let v1Factory: BackedFactory; + + let owner: SignerWithAddress; + let minter: SignerWithAddress; + let burner: SignerWithAddress; + let pauser: SignerWithAddress; + let blacklister: SignerWithAddress; + let tmpAccount: SignerWithAddress; + let sanctionsList: SanctionsListMock; + + const tokenName = "Wrapped Apple"; + const tokenSymbol = "WAAPL"; + + // ---- Helpers ---- // + const upgradeContract = async () => { + const proxyAdmin = await ethers.getContractAt( + "ProxyAdmin", + await v1Factory.proxyAdmin() + ); + + implementationV2 = await ( + await ethers.getContractFactory("BackedAutoFeeTokenImplementation") + ).deploy(); + + await proxyAdmin.upgrade(tokenV1.address, implementationV2.address); + + tokenV2 = await ethers.getContractAt( + "BackedAutoFeeTokenImplementation", + tokenV1.address + ); + await tokenV2.setMultiplierUpdater(owner.address); + await tokenV2.setLastTimeFeeApplied(Math.floor(Date.now() / 1000) - 3600); + await tokenV2.setPeriodLength(24 * 3600); + await tokenV2.updateMultiplierValue(ethers.BigNumber.from(10).pow(18), 0) + }; + + beforeEach(async () => { + // Roles: + owner = await getSigner(0); + minter = await getSigner(1); + burner = await getSigner(2); + pauser = await getSigner(3); + blacklister = await getSigner(4); + tmpAccount = await getSigner(5); + + // Deploy the token factory + v1Factory = await ( + await ethers.getContractFactory("BackedFactory") + ).deploy(owner.address); + + // Deploy the Sanctions List contract: + sanctionsList = await ( + await ethers.getContractFactory("SanctionsListMock", blacklister.signer) + ).deploy(); + + const tokenDeploymentReceipt = await ( + await v1Factory.deployToken( + tokenName, + tokenSymbol, + owner.address, + minter.address, + burner.address, + pauser.address, + sanctionsList.address + ) + ).wait(); + + const deployedTokenAddress = tokenDeploymentReceipt.events?.find( + (event: any) => event.event === "NewToken" + )?.args?.newToken; + + tokenV1 = await ethers.getContractAt( + "BackedTokenImplementation", + deployedTokenAddress + ); + }); + + it("should not fail update", async () => { + await upgradeContract(); + }); + + it("should have the same info", async () => { + const nameBefore = await tokenV1.name(); + const symbolBefore = await tokenV1.symbol(); + const ownerBefore = await tokenV1.owner(); + const minterBefore = await tokenV1.minter(); + + await upgradeContract(); + + expect(nameBefore).to.equal(await tokenV2.name()); + expect(symbolBefore).to.equal(await tokenV2.symbol()); + expect(ownerBefore).to.equal(await tokenV2.owner()); + expect(minterBefore).to.equal(await tokenV2.minter()); + }); + + it("should have the correct version", async () => { + await upgradeContract(); + + expect(await tokenV2.VERSION()).to.equal("1.1.0"); + }); + + it("should be able to set terms", async () => { + expect(await tokenV2.terms()).to.equal("https://www.backedassets.fi/legal-documentation"); + + await tokenV2.connect(owner.signer).setTerms("My Terms"); + + expect(await tokenV2.terms()).to.equal("My Terms"); + }); + + it("should discard balances and total supply", async () => { + await tokenV1.connect(minter.signer).mint(tmpAccount.address, 100); + + await upgradeContract(); + + // Check balance: + expect(await tokenV2.balanceOf(tmpAccount.address)).to.equal(0); + + // Check total supply: + expect(await tokenV2.totalSupply()).to.equal(0); + }); + + it("should allow minting and transferability", async () => { + + await upgradeContract(); + + await tokenV1.connect(minter.signer).mint(tmpAccount.address, 100); + // Check balance: + expect(await tokenV2.balanceOf(tmpAccount.address)).to.equal(100); + + // Transfer: + await tokenV2.connect(tmpAccount.signer).transfer(owner.address, 50); + expect(await tokenV2.balanceOf(tmpAccount.address)).to.equal(50); + + // Mint: + tokenV1.connect(minter.signer).mint(tmpAccount.address, 100); + expect(await tokenV2.balanceOf(tmpAccount.address)).to.equal(150); + }); +}); From ed9b8d9c8eff33e7d0a783cb26f00c2b3dbda923 Mon Sep 17 00:00:00 2001 From: Miniroman Date: Mon, 24 Jun 2024 09:08:41 +0200 Subject: [PATCH 14/17] Improve upgradeability --- .../BackedAutoFeeTokenImplementation.sol | 32 ++++++++++++++----- test/BackedAutoFeeTokenImplementation.ts | 6 ++-- test/Upgradability.ts | 13 ++++---- 3 files changed, 34 insertions(+), 17 deletions(-) diff --git a/contracts/BackedAutoFeeTokenImplementation.sol b/contracts/BackedAutoFeeTokenImplementation.sol index 0626f29..021b7f6 100644 --- a/contracts/BackedAutoFeeTokenImplementation.sol +++ b/contracts/BackedAutoFeeTokenImplementation.sol @@ -114,38 +114,54 @@ contract BackedAutoFeeTokenImplementation is BackedTokenImplementation { } // Initializers: - function initialize( string memory name_, string memory symbol_ ) public virtual override { - _initialize(name_, symbol_, 24 * 3600, block.timestamp); + super.initialize(name_, symbol_); + _initialize_auto_fee(24 * 3600, block.timestamp, 0); } function initialize( string memory name_, string memory symbol_, uint256 _periodLength, - uint256 _lastTimeFeeApplied + uint256 _lastTimeFeeApplied, + uint256 _feePerPeriod ) public virtual { - _initialize(name_, symbol_, _periodLength, _lastTimeFeeApplied); + super.initialize(name_, symbol_); + _initialize_auto_fee(_periodLength, _lastTimeFeeApplied, _feePerPeriod); + } + + function initialize_v2( + uint256 _periodLength, + uint256 _lastTimeFeeApplied, + uint256 _feePerPeriod + ) public virtual { + _initialize_auto_fee(_periodLength, _lastTimeFeeApplied, _feePerPeriod); } function _initialize( string memory name_, - string memory symbol_, - uint256 _periodLength, - uint256 _lastTimeFeeApplied + string memory symbol_ ) public virtual initializer { __ERC20_init(name_, symbol_); __Ownable_init(); _buildDomainSeparator(); _setTerms("https://www.backedassets.fi/legal-documentation"); // Default Terms + } - multiplier = 1e18; + function _initialize_auto_fee( + uint256 _periodLength, + uint256 _lastTimeFeeApplied, + uint256 _feePerPeriod + ) public virtual { + require(lastTimeFeeApplied == 0, "BackedAutoFeeTokenImplementation already initialized"); + multiplier = 1e18; periodLength = _periodLength; lastTimeFeeApplied = _lastTimeFeeApplied; + feePerPeriod = _feePerPeriod; } /** diff --git a/test/BackedAutoFeeTokenImplementation.ts b/test/BackedAutoFeeTokenImplementation.ts index 6b7f7b3..7715de1 100644 --- a/test/BackedAutoFeeTokenImplementation.ts +++ b/test/BackedAutoFeeTokenImplementation.ts @@ -71,12 +71,13 @@ describe("BackedAutoFeeTokenImplementation", function () { const proxyAdminFactory = new ProxyAdmin__factory(owner.signer) proxyAdmin = await proxyAdminFactory.deploy(); const tokenProxy = await new BackedTokenProxy__factory(owner.signer).deploy(tokenImplementation.address, proxyAdmin.address, tokenImplementation.interface.encodeFunctionData( - 'initialize(string,string,uint256,uint256)', + 'initialize(string,string,uint256,uint256,uint256)', [ tokenName, tokenSymbol, 24 * 3600, - baseTime + baseTime, + baseFeePerPeriod ] )); token = BackedAutoFeeTokenImplementation__factory.connect(tokenProxy.address, owner.signer); @@ -86,7 +87,6 @@ describe("BackedAutoFeeTokenImplementation", function () { await token.setMultiplierUpdater(owner.address); sanctionsList = await new SanctionsListMock__factory(blacklister.signer).deploy(); await token.setSanctionsList(sanctionsList.address); - await token.updateFeePerPeriod(baseFeePerPeriod); // Chain Id const network = await ethers.provider.getNetwork(); diff --git a/test/Upgradability.ts b/test/Upgradability.ts index beb1a2d..94ac90b 100644 --- a/test/Upgradability.ts +++ b/test/Upgradability.ts @@ -137,7 +137,7 @@ describe("Upgrade from v1.0.0 to v1.1.0", () => { }); }); -describe("Upgrade from v1.1.0 to v1.2.0", () => { +describe("Upgrade from v1.1.0 to auto fee", () => { let implementationV2: BackedAutoFeeTokenImplementation; let tokenV2: BackedAutoFeeTokenImplementation; let tokenV1: BackedTokenImplementation; @@ -165,16 +165,17 @@ describe("Upgrade from v1.1.0 to v1.2.0", () => { await ethers.getContractFactory("BackedAutoFeeTokenImplementation") ).deploy(); - await proxyAdmin.upgrade(tokenV1.address, implementationV2.address); + await proxyAdmin.upgradeAndCall(tokenV1.address, implementationV2.address, implementationV2.interface.encodeFunctionData( + 'initialize_v2', [ + 24 * 3600, + Math.floor(Date.now() / 1000) - 3600, + 0 + ])); tokenV2 = await ethers.getContractAt( "BackedAutoFeeTokenImplementation", tokenV1.address ); - await tokenV2.setMultiplierUpdater(owner.address); - await tokenV2.setLastTimeFeeApplied(Math.floor(Date.now() / 1000) - 3600); - await tokenV2.setPeriodLength(24 * 3600); - await tokenV2.updateMultiplierValue(ethers.BigNumber.from(10).pow(18), 0) }; beforeEach(async () => { From ec7f46a40bf9545a790a9478b9a98faa0653fccc Mon Sep 17 00:00:00 2001 From: Miniroman Date: Mon, 24 Jun 2024 11:38:44 +0200 Subject: [PATCH 15/17] Add token factory --- contracts/BackedAutoFeeTokenFactory.sol | 168 ++++++++++ test/BackedAutoFeeTokenFactory.ts | 399 ++++++++++++++++++++++++ 2 files changed, 567 insertions(+) create mode 100644 contracts/BackedAutoFeeTokenFactory.sol create mode 100644 test/BackedAutoFeeTokenFactory.ts diff --git a/contracts/BackedAutoFeeTokenFactory.sol b/contracts/BackedAutoFeeTokenFactory.sol new file mode 100644 index 0000000..ec1c815 --- /dev/null +++ b/contracts/BackedAutoFeeTokenFactory.sol @@ -0,0 +1,168 @@ +/** + * SPDX-License-Identifier: MIT + * + * Copyright (c) 2021-2022 Backed Finance AG + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + * SOFTWARE. + */ + +pragma solidity 0.8.9; + +import "@openzeppelin/contracts/access/Ownable.sol"; +import "@openzeppelin/contracts/proxy/transparent/ProxyAdmin.sol"; +import "@openzeppelin/contracts/proxy/transparent/TransparentUpgradeableProxy.sol"; +import "./BackedAutoFeeTokenImplementation.sol"; + +/** + * @dev + * TransparentUpgradeableProxy contract, renamed as BackedTokenProxy. + */ +contract BackedTokenProxy is TransparentUpgradeableProxy { + constructor( + address _logic, + address admin_, + bytes memory _data + ) payable TransparentUpgradeableProxy(_logic, admin_, _data) {} +} + +/** + * @dev + * + * Factory contract, used for creating new, upgradable tokens. + * + * The contract contains one role: + * - An owner, which can deploy new tokens + * + */ +contract BackedAutoFeeTokenFactory is Ownable { + ProxyAdmin public proxyAdmin; + BackedAutoFeeTokenImplementation public tokenImplementation; + + event NewToken(address indexed newToken, string name, string symbol); + event NewImplementation(address indexed newImplementation); + + /** + * @param proxyAdminOwner The address of the account that will be set as owner of the deployed ProxyAdmin + */ + constructor(address proxyAdminOwner) { + require( + proxyAdminOwner != address(0), + "Factory: address should not be 0" + ); + + tokenImplementation = new BackedAutoFeeTokenImplementation(); + proxyAdmin = new ProxyAdmin(); + proxyAdmin.transferOwnership(proxyAdminOwner); + } + + struct TokenDeploymentConfiguration { + string name; // The name that the newly created token will have + string symbol; // The symbol that the newly created token will have + address tokenOwner; // The address of the account to which the owner role will be assigned + address minter; // The address of the account to which the minter role will be assigned + address burner; // The address of the account to which the burner role will be assigned + address pauser; // The address of the account to which the pauser role will be assigned + address sanctionsList; // The address of sanctions list contract + address multiplierUpdater; // The address of the account to which the multiplier updater role will be assigned + uint256 periodLength; // Length of fee accrual period + uint256 lastTimeFeeApplied; // Initial time of fee accrual + uint256 feePerPeriod; // Percentage amount of fee accrued every period + } + + /** + * @dev Deploy and configures new instance of BackedFi Token. Callable only by the factory owner + * + * Emits a { NewToken } event + * + * @param configuration Configuration structure for token deployment + */ + function deployToken( + TokenDeploymentConfiguration calldata configuration + ) external onlyOwner returns (address) { + require( + configuration.tokenOwner != address(0) && + configuration.minter != address(0) && + configuration.burner != address(0) && + configuration.pauser != address(0), + "Factory: address should not be 0" + ); + + bytes32 salt = keccak256( + abi.encodePacked(configuration.name, configuration.symbol) + ); + + BackedTokenProxy newProxy = new BackedTokenProxy{salt: salt}( + address(tokenImplementation), + address(proxyAdmin), + abi.encodeWithSelector( + bytes4( + keccak256( + "initialize(string,string,uint256,uint256,uint256)" + ) + ), + configuration.name, + configuration.symbol, + configuration.periodLength, + configuration.lastTimeFeeApplied, + configuration.feePerPeriod + ) + ); + + BackedAutoFeeTokenImplementation newToken = BackedAutoFeeTokenImplementation( + address(newProxy) + ); + + newToken.setMinter(configuration.minter); + newToken.setBurner(configuration.burner); + newToken.setPauser(configuration.pauser); + newToken.setMultiplierUpdater(configuration.multiplierUpdater); + newToken.setSanctionsList(configuration.sanctionsList); + newToken.transferOwnership(configuration.tokenOwner); + + emit NewToken( + address(newToken), + configuration.name, + configuration.symbol + ); + + return (address(newToken)); + } + + /** + * @dev Update the implementation for future deployments + * + * Emits a { NewImplementation } event + * + * @param newImplementation address of the new implementation + */ + function updateImplementation( + address newImplementation + ) external onlyOwner { + require( + newImplementation != address(0), + "Factory: address should not be 0" + ); + + tokenImplementation = BackedAutoFeeTokenImplementation( + newImplementation + ); + + emit NewImplementation(newImplementation); + } +} diff --git a/test/BackedAutoFeeTokenFactory.ts b/test/BackedAutoFeeTokenFactory.ts new file mode 100644 index 0000000..87f3b47 --- /dev/null +++ b/test/BackedAutoFeeTokenFactory.ts @@ -0,0 +1,399 @@ +import { expect } from "chai"; +import { ethers } from "hardhat"; +import { Signer } from "ethers"; +import { + BackedAutoFeeTokenImplementation, + BackedAutoFeeTokenFactory, + ProxyAdmin, + SanctionsListMock + // eslint-disable-next-line node/no-missing-import +} from "../typechain"; + +type SignerWithAddress = { + signer: Signer; + address: string; +}; + +describe("BackedAutoFeeTokenFactory", function () { + // General config: + let implementation: BackedAutoFeeTokenImplementation; + let proxyAdmin: ProxyAdmin; + let factory: BackedAutoFeeTokenFactory; + let sanctionsList: SanctionsListMock; + + let accounts: Signer[]; + + // Basic config: + const tokenName = "Backed Apple"; + const tokenSymbol = "bAAPL"; + + let random: SignerWithAddress; + let minter: SignerWithAddress; + let burner: SignerWithAddress; + let pauser: SignerWithAddress; + let blacklister: SignerWithAddress; + let multiplierUpdater: SignerWithAddress; + let factoryOwner: SignerWithAddress; + let proxyAdminOwner: SignerWithAddress; + let tokenContractOwner: SignerWithAddress; + + beforeEach(async () => { + // Get accounts: + accounts = await ethers.getSigners(); + + const getSigner = async (index: number): Promise => ({ + signer: accounts[index], + address: await accounts[index].getAddress(), + }); + + factoryOwner = await getSigner(0); + proxyAdminOwner = await getSigner(1); + tokenContractOwner = await getSigner(2); + minter = await getSigner(3); + burner = await getSigner(4); + pauser = await getSigner(5); + random = await getSigner(6); + blacklister = await getSigner(7); + multiplierUpdater = await getSigner(8); + + // Deploy factory contract: + const BackedAutoFeeTokenFactory = await ethers.getContractFactory("BackedAutoFeeTokenFactory"); + factory = await BackedAutoFeeTokenFactory.deploy(proxyAdminOwner.address); + + // Set implementation: + const implementationAddress = await factory.tokenImplementation(); + implementation = await ethers.getContractAt( + "BackedAutoFeeTokenImplementation", + implementationAddress + ); + + // Set proxyAdmin: + const proxyAdminAddress = await factory.proxyAdmin(); + proxyAdmin = await ethers.getContractAt("ProxyAdmin", proxyAdminAddress); + + // Deploy the Sanctions List contract: + sanctionsList = await ( + await ethers.getContractFactory("SanctionsListMock", blacklister.signer) + ).deploy(); + + await sanctionsList.deployed(); + }); + + it("Basic owners check", async function () { + expect(await factory.owner(), factoryOwner.address); + expect(await proxyAdmin.owner(), proxyAdminOwner.address); + }); + + it("Test implementation", async function () { + expect(await implementation.name(), "Backed Token Implementation"); + expect(await implementation.symbol(), "BTI"); + }); + + it("should not allow deployment without proxyAdminOwnerAddress", async () => { + await expect( + ( + await ethers.getContractFactory("BackedAutoFeeTokenFactory") + ).deploy(ethers.constants.AddressZero) + ).to.revertedWith("Factory: address should not be 0"); + }); + + it("should not allow 0 address to be assigned to role", async () => { + await expect( + factory.deployToken( + { + name: tokenName, + symbol: tokenSymbol, + tokenOwner: ethers.constants.AddressZero, + minter: minter.address, + burner: burner.address, + pauser: pauser.address, + sanctionsList: sanctionsList.address, + feePerPeriod: 0, + lastTimeFeeApplied: Math.round(Date.now() / 1000), + periodLength: 24 * 3600, + multiplierUpdater: multiplierUpdater.address + } + ) + ).to.revertedWith("Factory: address should not be 0"); + + await expect( + factory.deployToken( + { + name: tokenName, + symbol: tokenSymbol, + tokenOwner: tokenContractOwner.address, + minter: ethers.constants.AddressZero, + burner: burner.address, + pauser: pauser.address, + sanctionsList: sanctionsList.address, + feePerPeriod: 0, + lastTimeFeeApplied: Math.round(Date.now() / 1000), + periodLength: 24 * 3600, + multiplierUpdater: multiplierUpdater.address + } + ) + ).to.revertedWith("Factory: address should not be 0"); + + await expect( + factory.deployToken( + { + name: tokenName, + symbol: tokenSymbol, + tokenOwner: tokenContractOwner.address, + minter: minter.address, + burner: ethers.constants.AddressZero, + pauser: pauser.address, + sanctionsList: sanctionsList.address, + feePerPeriod: 0, + lastTimeFeeApplied: Math.round(Date.now() / 1000), + periodLength: 24 * 3600, + multiplierUpdater: multiplierUpdater.address + } + ) + ).to.revertedWith("Factory: address should not be 0"); + + await expect( + factory.deployToken( + { + name: tokenName, + symbol: tokenSymbol, + tokenOwner: tokenContractOwner.address, + minter: minter.address, + burner: burner.address, + pauser: ethers.constants.AddressZero, + sanctionsList: sanctionsList.address, + feePerPeriod: 0, + lastTimeFeeApplied: Math.round(Date.now() / 1000), + periodLength: 24 * 3600, + multiplierUpdater: multiplierUpdater.address + } + ) + ).to.revertedWith("Factory: address should not be 0"); + }); + + it("should be able to deploy token", async () => { + const tokenDeployReceipt = await ( + await factory.deployToken( + { + name: tokenName, + symbol: tokenSymbol, + tokenOwner: tokenContractOwner.address, + minter: minter.address, + burner: burner.address, + pauser: pauser.address, + sanctionsList: sanctionsList.address, + feePerPeriod: 0, + lastTimeFeeApplied: Math.round(Date.now() / 1000), + periodLength: 24 * 3600, + multiplierUpdater: multiplierUpdater.address + } + ) + ).wait(); + + // Expect there to be { NewToken } event + const newTokenEvent = tokenDeployReceipt.events?.find( + (e) => e.event === "NewToken" + ); + + expect(newTokenEvent).not.equal(undefined); + expect(newTokenEvent?.args?.length).to.equal(3); + expect(newTokenEvent?.args?.newToken).to.match(/^0x[a-fA-F\d]{40}$/); + expect(newTokenEvent?.args?.name).to.equal(tokenName); + expect(newTokenEvent?.args?.symbol).to.equal(tokenSymbol); + }); + + it("should not allow non owners to deploy new token", async () => { + await expect( + factory + .connect(random.signer) + .deployToken( + { + name: tokenName, + symbol: tokenSymbol, + tokenOwner: tokenContractOwner.address, + minter: minter.address, + burner: burner.address, + pauser: pauser.address, + sanctionsList: sanctionsList.address, + feePerPeriod: 0, + lastTimeFeeApplied: Math.round(Date.now() / 1000), + periodLength: 24 * 3600, + multiplierUpdater: multiplierUpdater.address + } + ) + ).to.be.revertedWith("Ownable: caller is not the owner"); + }); + + it("should set the roles", async () => { + const tokenDeployReceipt = await ( + await factory.deployToken( + { + name: tokenName, + symbol: tokenSymbol, + tokenOwner: tokenContractOwner.address, + minter: minter.address, + burner: burner.address, + pauser: pauser.address, + sanctionsList: sanctionsList.address, + feePerPeriod: 0, + lastTimeFeeApplied: Math.round(Date.now() / 1000), + periodLength: 24 * 3600, + multiplierUpdater: multiplierUpdater.address + } + ) + ).wait(); + + const tokenContractAddress = tokenDeployReceipt.events?.find( + (e) => e.event === "NewToken" + )?.args?.newToken; + + const tokenContract = await ethers.getContractAt( + "BackedAutoFeeTokenImplementation", + tokenContractAddress + ); + + // Now check that the roles are set accordingly + expect(await tokenContract.owner()).to.equal(tokenContractOwner.address); + expect(await tokenContract.minter()).to.equal(minter.address); + expect(await tokenContract.pauser()).to.equal(pauser.address); + expect(await tokenContract.burner()).to.equal(burner.address); + expect(await tokenContract.multiplierUpdater()).to.equal(multiplierUpdater.address); + }); + + it("should set fee related properties", async () => { + const configuration = { + name: tokenName, + symbol: tokenSymbol, + tokenOwner: tokenContractOwner.address, + minter: minter.address, + burner: burner.address, + pauser: pauser.address, + sanctionsList: sanctionsList.address, + feePerPeriod: 1001, + lastTimeFeeApplied: Math.round(Date.now() / 1000), + periodLength: 24 * 3600, + multiplierUpdater: multiplierUpdater.address + }; + const tokenDeployReceipt = await ( + await factory.deployToken( + configuration + ) + ).wait(); + + const tokenContractAddress = tokenDeployReceipt.events?.find( + (e) => e.event === "NewToken" + )?.args?.newToken; + + const tokenContract = await ethers.getContractAt( + "BackedAutoFeeTokenImplementation", + tokenContractAddress + ); + + // Now check that the roles are set accordingly + expect(await tokenContract.multiplier()).to.equal(ethers.BigNumber.from(10).pow(18)); + expect(await tokenContract.feePerPeriod()).to.equal(configuration.feePerPeriod); + expect(await tokenContract.periodLength()).to.equal(configuration.periodLength); + expect(await tokenContract.lastTimeFeeApplied()).to.equal(configuration.lastTimeFeeApplied); + }); + + it("should not deploy the same token twice", async () => { + await ( + await factory.deployToken( + { + name: tokenName, + symbol: tokenSymbol, + tokenOwner: tokenContractOwner.address, + minter: minter.address, + burner: burner.address, + pauser: pauser.address, + sanctionsList: sanctionsList.address, + feePerPeriod: 0, + lastTimeFeeApplied: Math.round(Date.now() / 1000), + periodLength: 24 * 3600, + multiplierUpdater: multiplierUpdater.address + } + ) + ).wait(); + + await expect( + factory.deployToken( + { + name: tokenName, + symbol: tokenSymbol, + tokenOwner: tokenContractOwner.address, + minter: minter.address, + burner: burner.address, + pauser: pauser.address, + sanctionsList: sanctionsList.address, + feePerPeriod: 0, + lastTimeFeeApplied: Math.round(Date.now() / 1000), + periodLength: 24 * 3600, + multiplierUpdater: multiplierUpdater.address + } + ) + ).to.reverted; + }); + + it("should allow to change the implementation", async () => { + // Deploy new implementation: + const TokenImplementation2 = await ethers.getContractFactory( + "BackedAutoFeeTokenImplementation" + ); + const implementation2 = await TokenImplementation2.deploy(); + + // Change implementation: + const receipt = await ( + await factory.updateImplementation(implementation2.address) + ).wait(); + expect(receipt.events?.[0].event).to.equal("NewImplementation"); + expect(receipt.events?.[0].args?.[0]).to.equal(implementation2.address); + + // Test the new implementation: + const tokenDeploymentReceipt = await ( + await factory.deployToken( + { + name: tokenName, + symbol: tokenSymbol, + tokenOwner: tokenContractOwner.address, + minter: minter.address, + burner: burner.address, + pauser: pauser.address, + sanctionsList: sanctionsList.address, + feePerPeriod: 0, + lastTimeFeeApplied: Math.round(Date.now() / 1000), + periodLength: 24 * 3600, + multiplierUpdater: multiplierUpdater.address + } + ) + ).wait(); + + const newTokenAddress = tokenDeploymentReceipt.events?.find( + (event) => event.event === "NewToken" + )?.args?.newToken; + + expect(await proxyAdmin.getProxyImplementation(newTokenAddress)).to.equal( + implementation2.address + ); + }); + + it("should not allow 0 address to be assigned to implementation", async () => { + // Check zero implementation fail: + await expect( + factory.updateImplementation(ethers.constants.AddressZero) + ).to.revertedWith("Factory: address should not be 0"); + }); + + it("should not allow non owners to change implementation", async () => { + // Deploy new implementation: + const TokenImplementation2 = await ethers.getContractFactory( + "BackedAutoFeeTokenImplementation" + ); + const implementation2 = await TokenImplementation2.deploy(); + + await expect( + factory + .connect(random.signer) + .updateImplementation(implementation2.address) + ).to.be.revertedWith("Ownable: caller is not the owner"); + }); +}); From 655fdd415f53956ae28e67608a1b8582a0a90351 Mon Sep 17 00:00:00 2001 From: Miniroman Date: Mon, 24 Jun 2024 14:03:10 +0200 Subject: [PATCH 16/17] Increase test coverage --- .../BackedAutoFeeTokenImplementation.sol | 11 +- test/BackedAutoFeeTokenImplementation.ts | 119 ++++++++++++++++-- 2 files changed, 112 insertions(+), 18 deletions(-) diff --git a/contracts/BackedAutoFeeTokenImplementation.sol b/contracts/BackedAutoFeeTokenImplementation.sol index 021b7f6..8de4453 100644 --- a/contracts/BackedAutoFeeTokenImplementation.sol +++ b/contracts/BackedAutoFeeTokenImplementation.sol @@ -133,6 +133,7 @@ contract BackedAutoFeeTokenImplementation is BackedTokenImplementation { _initialize_auto_fee(_periodLength, _lastTimeFeeApplied, _feePerPeriod); } + // Should it be only callable by authorized address? function initialize_v2( uint256 _periodLength, uint256 _lastTimeFeeApplied, @@ -141,16 +142,6 @@ contract BackedAutoFeeTokenImplementation is BackedTokenImplementation { _initialize_auto_fee(_periodLength, _lastTimeFeeApplied, _feePerPeriod); } - function _initialize( - string memory name_, - string memory symbol_ - ) public virtual initializer { - __ERC20_init(name_, symbol_); - __Ownable_init(); - _buildDomainSeparator(); - _setTerms("https://www.backedassets.fi/legal-documentation"); // Default Terms - } - function _initialize_auto_fee( uint256 _periodLength, uint256 _lastTimeFeeApplied, diff --git a/test/BackedAutoFeeTokenImplementation.ts b/test/BackedAutoFeeTokenImplementation.ts index 7715de1..6bf7b6c 100644 --- a/test/BackedAutoFeeTokenImplementation.ts +++ b/test/BackedAutoFeeTokenImplementation.ts @@ -8,6 +8,8 @@ import { expect } from "chai"; import { ethers } from "hardhat"; import { BigNumber, Signer } from "ethers"; import { + BackedTokenImplementation__factory, + BackedTokenProxy, BackedTokenProxy__factory, SanctionsListMock, SanctionsListMock__factory, @@ -35,6 +37,7 @@ describe("BackedAutoFeeTokenImplementation", function () { // General config: let token: BackedAutoFeeTokenImplementation; + let tokenImplementation: BackedAutoFeeTokenImplementation; let sanctionsList: SanctionsListMock; let proxyAdmin: ProxyAdmin; let accounts: Signer[]; @@ -67,7 +70,7 @@ describe("BackedAutoFeeTokenImplementation", function () { await helpers.time.setNextBlockTimestamp(baseTime); const tokenImplementationFactory = new BackedAutoFeeTokenImplementation__factory(owner.signer); - const tokenImplementation = await tokenImplementationFactory.deploy(); + tokenImplementation = await tokenImplementationFactory.deploy(); const proxyAdminFactory = new ProxyAdmin__factory(owner.signer) proxyAdmin = await proxyAdminFactory.deploy(); const tokenProxy = await new BackedTokenProxy__factory(owner.signer).deploy(tokenImplementation.address, proxyAdmin.address, tokenImplementation.interface.encodeFunctionData( @@ -81,9 +84,9 @@ describe("BackedAutoFeeTokenImplementation", function () { ] )); token = BackedAutoFeeTokenImplementation__factory.connect(tokenProxy.address, owner.signer); - await token.setMinter(owner.address); - await token.setBurner(owner.address); - await token.setPauser(owner.address); + await token.setMinter(minter.address); + await token.setBurner(burner.address); + await token.setPauser(pauser.address); await token.setMultiplierUpdater(owner.address); sanctionsList = await new SanctionsListMock__factory(blacklister.signer).deploy(); await token.setSanctionsList(sanctionsList.address); @@ -97,6 +100,50 @@ describe("BackedAutoFeeTokenImplementation", function () { await helpers.reset(); }) + describe('#initializer_v2', () => { + it("Cannot initialize twice", async function () { + await expect( + token.connect(owner.signer).initialize_v2( + 24 * 3600, + baseTime, + baseFeePerPeriod + ) + ).to.be.revertedWith("BackedAutoFeeTokenImplementation already initialized"); + }); + describe('when being called on contract initialized to v1', () => { + let token: BackedAutoFeeTokenImplementation; + cacheBeforeEach(async () => { + const oldTokenImplementationFactory = new BackedTokenImplementation__factory(owner.signer); + const oldTokenImplementation = await oldTokenImplementationFactory.deploy(); + const tokenProxy = await new BackedTokenProxy__factory(owner.signer).deploy(oldTokenImplementation.address, proxyAdmin.address, oldTokenImplementation.interface.encodeFunctionData( + 'initialize', + [ + tokenName, + tokenSymbol + ] + )); + token = BackedAutoFeeTokenImplementation__factory.connect(tokenProxy.address, owner.signer); + }); + + it('Should be able to upgrade with initialize_v2 method', async () => { + await proxyAdmin.upgradeAndCall( + token.address, + tokenImplementation.address, + tokenImplementation.interface.encodeFunctionData( + 'initialize_v2', + [ + 24 * 3600, + baseTime, + baseFeePerPeriod + ] + ) + ) + expect(await token.multiplier()).to.be.eq(ethers.BigNumber.from(10).pow(18)); + expect(await token.lastTimeFeeApplied()).to.be.eq(baseTime); + expect(await token.feePerPeriod()).to.be.eq(baseFeePerPeriod); + }) + }); + }) describe('#getCurrentMultiplier', () => { describe('when time moved by 365 days forward', () => { const periodsPassed = 365; @@ -190,13 +237,62 @@ describe("BackedAutoFeeTokenImplementation", function () { }) }) }) + describe('#transfer', () => { + describe('And transfering to zero address', () => { + const subject = (toAddress: string) => token.transfer(toAddress, 1) + it('should revert transaction', async () => { + await expect(subject(ethers.constants.AddressZero)).to.be.revertedWith("ERC20: transfer to the zero address") + }) + }) + }) + describe('#transferFrom', () => { + describe('And transfering from zero address', () => { + let zeroSigner: Signer; + cacheBeforeEach(async () => { + await helpers.impersonateAccount(ethers.constants.AddressZero); + zeroSigner = await ethers.getSigner(ethers.constants.AddressZero) + }) + const subject = (toAddress: string) => token.connect(zeroSigner).transfer(toAddress, 1) + it('should revert transaction', async () => { + await expect(subject(owner.address)).to.be.revertedWith("ERC20: transfer from the zero address") + }) + }) + }) + describe('#mint', () => { + describe('And minting to zero address', () => { + const subject = (toAddress: string) => token.connect(minter.signer).mint(toAddress, 1) + it('should revert transaction', async () => { + await expect(subject(ethers.constants.AddressZero)).to.be.revertedWith("ERC20: mint to the zero address") + }) + }) + }) + describe('#burn', () => { + describe('And burning from zero address', () => { + let zeroSigner: Signer; + cacheBeforeEach(async () => { + await helpers.impersonateAccount(ethers.constants.AddressZero); + zeroSigner = await ethers.getSigner(ethers.constants.AddressZero); + await token.setBurner(ethers.constants.AddressZero); + }) + const subject = (fromAddress: string) => token.connect(zeroSigner).burn(fromAddress, 1) + it('should revert transaction', async () => { + await expect(subject(ethers.constants.AddressZero)).to.be.revertedWith("ERC20: burn from the zero address") + }) + }) + describe('And burning from address that has no sufficient balance', () => { + const subject = (fromAddress: string) => token.connect(burner.signer).burn(fromAddress, 1) + it('should revert transaction', async () => { + await expect(subject(burner.address)).to.be.revertedWith("ERC20: burn amount exceeds balance") + }) + }) + }) describe('#updateMultiplier', () => { describe('when time moved by 365 days forward', () => { const periodsPassed = 365; const baseMintedAmount = ethers.BigNumber.from(10).pow(18); let mintedShares: BigNumber; cacheBeforeEach(async () => { - await token.mint(owner.address, baseMintedAmount); + await token.connect(minter.signer).mint(owner.address, baseMintedAmount); mintedShares = await token.sharesOf(owner.address); await helpers.time.setNextBlockTimestamp(baseTime + periodsPassed * accrualPeriodLength); await helpers.mine() @@ -227,6 +323,13 @@ describe("BackedAutoFeeTokenImplementation", function () { }) }); + describe('#getSharesByUnderlyingAmount', () => { + it('Should increase amount of shares neeeded for given underlying amount', async () => { + const amount = 1000; + expect((await token.getSharesByUnderlyingAmount(amount))).to.eq(ethers.BigNumber.from(amount / annualFee)) + }) + }); + describe('#totalSupply', () => { it('Should decrease total supply of the token by the fee accrued in 365 days', async () => { expect((await token.totalSupply()).sub(baseMintedAmount.mul(annualFee * 100).div(100)).abs()).to.lte( @@ -251,7 +354,7 @@ describe("BackedAutoFeeTokenImplementation", function () { describe('#mint', () => { const newlyMintedTokens = ethers.BigNumber.from(10).pow(18) cacheBeforeEach(async () => { - await token.mint(actor.address, newlyMintedTokens); + await token.connect(minter.signer).mint(actor.address, newlyMintedTokens); }) it('Should mint requested number of tokens', async () => { expect((await token.balanceOf(actor.address)).sub(newlyMintedTokens).abs()).to.be.lte(1); @@ -265,7 +368,7 @@ describe("BackedAutoFeeTokenImplementation", function () { describe('#transferShares', () => { const baseMintedAmount = ethers.BigNumber.from(10).pow(18); cacheBeforeEach(async () => { - await token.mint(owner.address, baseMintedAmount); + await token.connect(minter.signer).mint(owner.address, baseMintedAmount); }) describe('When transfering shares to another account', () => { const sharesToTransfer = ethers.BigNumber.from(10).pow(18); @@ -287,7 +390,7 @@ describe("BackedAutoFeeTokenImplementation", function () { describe('#delegatedTransferShares', () => { const baseMintedAmount = ethers.BigNumber.from(10).pow(18); cacheBeforeEach(async () => { - await token.mint(owner.address, baseMintedAmount); + await token.connect(minter.signer).mint(owner.address, baseMintedAmount); }) describe('When transfering shares from another account', () => { const sharesToTransfer = ethers.BigNumber.from(10).pow(18); From 73ccf12af0c8feaefcec4182b0909c7972017d8f Mon Sep 17 00:00:00 2001 From: Lukasz Romanowski Date: Fri, 19 Jul 2024 11:20:57 +0200 Subject: [PATCH 17/17] AutoFeeToken Audit comments resolution (#33) * Mark proxyAdmin as immutable, improve visibility specifiers, ensure updateMultiplier usage * Remove unused import, lock implementation contract, use updateMultiplier on updating last time fee applied * Fix mispelling * Remove WrappedBackedToken --- contracts/BackedAutoFeeTokenFactory.sol | 2 +- .../BackedAutoFeeTokenImplementation.sol | 61 +++-- contracts/WrappedBackedToken.sol | 222 ------------------ test/BackedAutoFeeTokenImplementation.ts | 54 +++++ 4 files changed, 85 insertions(+), 254 deletions(-) delete mode 100644 contracts/WrappedBackedToken.sol diff --git a/contracts/BackedAutoFeeTokenFactory.sol b/contracts/BackedAutoFeeTokenFactory.sol index ec1c815..b354b23 100644 --- a/contracts/BackedAutoFeeTokenFactory.sol +++ b/contracts/BackedAutoFeeTokenFactory.sol @@ -51,7 +51,7 @@ contract BackedTokenProxy is TransparentUpgradeableProxy { * */ contract BackedAutoFeeTokenFactory is Ownable { - ProxyAdmin public proxyAdmin; + ProxyAdmin public immutable proxyAdmin; BackedAutoFeeTokenImplementation public tokenImplementation; event NewToken(address indexed newToken, string name, string symbol); diff --git a/contracts/BackedAutoFeeTokenImplementation.sol b/contracts/BackedAutoFeeTokenImplementation.sol index 8de4453..25b5738 100644 --- a/contracts/BackedAutoFeeTokenImplementation.sol +++ b/contracts/BackedAutoFeeTokenImplementation.sol @@ -1,7 +1,7 @@ /** * SPDX-License-Identifier: MIT * - * Copyright (c) 2021-2022 Backed Finance AG + * Copyright (c) 2021-2024 Backed Finance AG * * Permission is hereby granted, free of charge, to any person obtaining a copy * of this software and associated documentation files (the "Software"), to deal @@ -38,7 +38,6 @@ pragma solidity 0.8.9; import "@openzeppelin/contracts-upgradeable/access/OwnableUpgradeable.sol"; import "./BackedTokenImplementation.sol"; -import "./SanctionsList.sol"; /** * @dev @@ -48,7 +47,7 @@ import "./SanctionsList.sol"; * with logic of multiplier, which is used for rebasing logic of the token, thus becoming rebase token itself. Additionally, it contains * mechanism, which changes this multiplier per configured fee periodically, on defined period length. * It contains one additional role: - * - A multiplierUpdated, that can update value of a multiplier. + * - A multiplierUpdater, that can update value of a multiplier. * */ @@ -112,6 +111,11 @@ contract BackedAutoFeeTokenImplementation is BackedTokenImplementation { } _; } + + // constructor, set lastTimeFeeApplied to lock the implementation instance. + constructor () { + lastTimeFeeApplied = 1; + } // Initializers: function initialize( @@ -128,7 +132,7 @@ contract BackedAutoFeeTokenImplementation is BackedTokenImplementation { uint256 _periodLength, uint256 _lastTimeFeeApplied, uint256 _feePerPeriod - ) public virtual { + ) external virtual { super.initialize(name_, symbol_); _initialize_auto_fee(_periodLength, _lastTimeFeeApplied, _feePerPeriod); } @@ -138,7 +142,7 @@ contract BackedAutoFeeTokenImplementation is BackedTokenImplementation { uint256 _periodLength, uint256 _lastTimeFeeApplied, uint256 _feePerPeriod - ) public virtual { + ) external virtual { _initialize_auto_fee(_periodLength, _lastTimeFeeApplied, _feePerPeriod); } @@ -146,8 +150,9 @@ contract BackedAutoFeeTokenImplementation is BackedTokenImplementation { uint256 _periodLength, uint256 _lastTimeFeeApplied, uint256 _feePerPeriod - ) public virtual { + ) internal virtual { require(lastTimeFeeApplied == 0, "BackedAutoFeeTokenImplementation already initialized"); + require(_lastTimeFeeApplied != 0, "Invalid last time fee applied"); multiplier = 1e18; periodLength = _periodLength; @@ -160,7 +165,7 @@ contract BackedAutoFeeTokenImplementation is BackedTokenImplementation { */ function totalSupply() public view virtual override returns (uint256) { (uint256 newMultiplier, ) = getCurrentMultiplier(); - return (_totalShares * newMultiplier) / 1e18; + return _getUnderlyingAmountByShares(_totalShares, newMultiplier); } /** @@ -170,7 +175,7 @@ contract BackedAutoFeeTokenImplementation is BackedTokenImplementation { address account ) public view virtual override returns (uint256) { (uint256 newMultiplier, ) = getCurrentMultiplier(); - return (sharesOf(account) * newMultiplier) / 1e18; + return _getUnderlyingAmountByShares(sharesOf(account), newMultiplier); } /** @@ -204,7 +209,7 @@ contract BackedAutoFeeTokenImplementation is BackedTokenImplementation { */ function getSharesByUnderlyingAmount( uint256 _underlyingAmount - ) public view returns (uint256) { + ) external view returns (uint256) { (uint256 newMultiplier, ) = getCurrentMultiplier(); return _getSharesByUnderlyingAmount(_underlyingAmount, newMultiplier); } @@ -214,7 +219,7 @@ contract BackedAutoFeeTokenImplementation is BackedTokenImplementation { */ function getUnderlyingAmountByShares( uint256 _sharesAmount - ) public view returns (uint256) { + ) external view returns (uint256) { (uint256 newMultiplier, ) = getCurrentMultiplier(); return _getUnderlyingAmountByShares(_sharesAmount, newMultiplier); } @@ -230,7 +235,7 @@ contract BackedAutoFeeTokenImplementation is BackedTokenImplementation { uint8 v, bytes32 r, bytes32 s - ) public virtual allowedDelegate { + ) external virtual allowedDelegate updateMultiplier { require(block.timestamp <= deadline, "ERC20Permit: expired deadline"); bytes32 structHash = keccak256( @@ -260,9 +265,9 @@ contract BackedAutoFeeTokenImplementation is BackedTokenImplementation { function transferShares( address to, uint256 sharesAmount - ) public virtual updateMultiplier returns (bool) { + ) external virtual updateMultiplier returns (bool) { address owner = _msgSender(); - uint256 amount = _getUnderlyingAmountByShares(sharesAmount, multiplier); // This method might lead to be unable to transfer all shares from account if multiplier is below 1e18 + uint256 amount = _getUnderlyingAmountByShares(sharesAmount, multiplier); _transfer(owner, to, amount); return true; } @@ -274,7 +279,7 @@ contract BackedAutoFeeTokenImplementation is BackedTokenImplementation { */ function updateFeePerPeriod( uint256 newFeePerPeriod - ) external updateMultiplier onlyOwner { + ) external onlyOwner updateMultiplier { feePerPeriod = newFeePerPeriod; } @@ -299,7 +304,8 @@ contract BackedAutoFeeTokenImplementation is BackedTokenImplementation { */ function setLastTimeFeeApplied( uint256 newLastTimeFeeApplied - ) external onlyOwner { + ) external onlyOwner updateMultiplier { + require(newLastTimeFeeApplied != 0, "Invalid last time fee applied"); lastTimeFeeApplied = newLastTimeFeeApplied; } @@ -308,7 +314,7 @@ contract BackedAutoFeeTokenImplementation is BackedTokenImplementation { * * @param newPeriodLength Length of a single accrual period in seconds */ - function setPeriodLength(uint256 newPeriodLength) external onlyOwner { + function setPeriodLength(uint256 newPeriodLength) external onlyOwner updateMultiplier { periodLength = newPeriodLength; } @@ -338,20 +344,20 @@ contract BackedAutoFeeTokenImplementation is BackedTokenImplementation { * @return the amount of shares that corresponds to `_underlyingAmount` underlying amount. */ function _getSharesByUnderlyingAmount( - uint256 underlyingAmount, - uint256 multiplier - ) internal view returns (uint256) { - return (underlyingAmount * 1e18) / multiplier; + uint256 _underlyingAmount, + uint256 _multiplier + ) internal pure returns (uint256) { + return (_underlyingAmount * 1e18) / _multiplier; } /** * @return the amount of underlying that corresponds to `_sharesAmount` token shares. */ function _getUnderlyingAmountByShares( - uint256 sharesAmount, - uint256 multiplier - ) internal view returns (uint256) { - return (sharesAmount * multiplier) / 1e18; + uint256 _sharesAmount, + uint256 _multiplier + ) internal pure returns (uint256) { + return (_sharesAmount * _multiplier) / 1e18; } /** @@ -476,11 +482,4 @@ contract BackedAutoFeeTokenImplementation is BackedTokenImplementation { ) internal virtual override updateMultiplier { super._beforeTokenTransfer(from, to, amount); } - - /** - * @dev This empty reserved space is put in place to allow future versions to add new - * variables without shifting down storage in the inheritance chain. - * See https://docs.openzeppelin.com/contracts/4.x/upgradeable#storage_gaps - */ - uint256[49] private __gap; } diff --git a/contracts/WrappedBackedToken.sol b/contracts/WrappedBackedToken.sol deleted file mode 100644 index 6a71f7c..0000000 --- a/contracts/WrappedBackedToken.sol +++ /dev/null @@ -1,222 +0,0 @@ -/** - * SPDX-License-Identifier: MIT - * - * Copyright (c) 2021-2022 Backed Finance AG - * - * Permission is hereby granted, free of charge, to any person obtaining a copy - * of this software and associated documentation files (the "Software"), to deal - * in the Software without restriction, including without limitation the rights - * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell - * copies of the Software, and to permit persons to whom the Software is - * furnished to do so, subject to the following conditions: - * - * The above copyright notice and this permission notice shall be included in - * copies or substantial portions of the Software. - * - * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR - * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, - * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE - * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER - * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, - * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE - * SOFTWARE. - */ - -/** - * Disclaimer and Terms of Use - * - * These ERC-20 tokens have not been registered under the U.S. Securities Act of 1933, as - * amended or with any securities regulatory authority of any State or other jurisdiction - * of the United States and (i) may not be offered, sold or delivered within the United States - * to, or for the account or benefit of U.S. Persons, and (ii) may be offered, sold or otherwise - * delivered at any time only to transferees that are Non-United States Persons (as defined by - * the U.S. Commodities Futures Trading Commission). - * For more information and restrictions please refer to the issuer's [Website](https://www.backedassets.fi/legal-documentation) - */ - -pragma solidity 0.8.9; - -import "@openzeppelin/contracts-upgradeable-new/token/ERC20/extensions/ERC4626Upgradeable.sol"; -import "@openzeppelin/contracts-upgradeable-new/access/OwnableUpgradeable.sol"; -import "./SanctionsList.sol"; - -/** - * @dev - * - * This token contract is following the ERC20 standard. - * It inherits ERC4626Upgradeable, which extends the basic ERC20 to be a representation of changing underlying token. - * Enforces Sanctions List via the Chainalysis standard interface. - * The contract contains one role: - * - A pauser, that can pause or restore all transfers in the contract. - * - An owner, that can set the above, and also the sanctionsList pointer. - * The owner can also set who can use the EIP-712 functionality, either specific accounts via a whitelist, or everyone. - * - */ - -contract WrappedBackedToken is OwnableUpgradeable, ERC4626Upgradeable { - string constant public VERSION = "1.1.0"; - - // Roles: - address public pauser; - - // EIP-712 Delegate Functionality: - bool public delegateMode; - mapping(address => bool) public delegateWhitelist; - - // Pause: - bool public isPaused; - - // SanctionsList: - SanctionsList public sanctionsList; - - // Terms: - string public terms; - - // Events: - event NewPauser(address indexed newPauser); - event NewSanctionsList(address indexed newSanctionsList); - event DelegateWhitelistChange(address indexed whitelistAddress, bool status); - event DelegateModeChange(bool delegateMode); - event PauseModeChange(bool pauseMode); - event NewTerms(string newTerms); - - modifier allowedDelegate { - require(delegateMode || delegateWhitelist[_msgSender()], "BackedToken: Unauthorized delegate"); - _; - } - - - // constructor, call initializer to lock the implementation instance. - constructor () { - initialize("Wrapped Backed Token Implementation", "wBTI", address(0x0000000000000000000000000000000000000000)); - } - - function initialize(string memory name_, string memory symbol_, address underlying_) public initializer { - __ERC20_init(name_, symbol_); - __ERC4626_init(IERC20Upgradeable(underlying_)); - __Ownable_init(); - _setTerms("https://www.backedassets.fi/legal-documentation"); // Default Terms - } - - /** - * @dev Function to set the pause in order to block or restore all - * transfers. Allowed only for pauser - * - * Emits a { PauseModeChange } event - * - * @param newPauseMode The new pause mode - */ - function setPause(bool newPauseMode) external { - require(_msgSender() == pauser, "BackedToken: Only pauser"); - isPaused = newPauseMode; - emit PauseModeChange(newPauseMode); - } - - /** - * @dev Function to change the contract pauser. Allowed only for owner - * - * Emits a { NewPauser } event - * - * @param newPauser The address of the new pauser - */ - function setPauser(address newPauser) external onlyOwner { - pauser = newPauser; - emit NewPauser(newPauser); - } - - /** - * @dev Function to change the contract Senctions List. Allowed only for owner - * - * Emits a { NewSanctionsList } event - * - * @param newSanctionsList The address of the new Senctions List following the Chainalysis standard - */ - function setSanctionsList(address newSanctionsList) external onlyOwner { - // Check the proposed sanctions list contract has the right interface: - require(!SanctionsList(newSanctionsList).isSanctioned(address(this)), "BackedToken: Wrong List interface"); - - sanctionsList = SanctionsList(newSanctionsList); - emit NewSanctionsList(newSanctionsList); - } - - - /** - * @dev EIP-712 Function to change the delegate status of account. - * Allowed only for owner - * - * Emits a { DelegateWhitelistChange } event - * - * @param whitelistAddress The address for which to change the delegate status - * @param status The new delegate status - */ - function setDelegateWhitelist(address whitelistAddress, bool status) external onlyOwner { - delegateWhitelist[whitelistAddress] = status; - emit DelegateWhitelistChange(whitelistAddress, status); - } - - /** - * @dev EIP-712 Function to change the contract delegate mode. Allowed - * only for owner - * - * Emits a { DelegateModeChange } event - * - * @param _delegateMode The new delegate mode for the contract - */ - function setDelegateMode(bool _delegateMode) external onlyOwner { - delegateMode = _delegateMode; - - emit DelegateModeChange(_delegateMode); - } - - /** - * @dev Function to change the contract terms. Allowed only for owner - * - * Emits a { NewTerms } event - * - * @param newTerms A string with the terms. Usually a web or IPFS link. - */ - function setTerms(string memory newTerms) external onlyOwner { - _setTerms(newTerms); - } - - // Implement setTerms, tp allow also to use from initializer: - function _setTerms(string memory newTerms) internal virtual { - terms = newTerms; - emit NewTerms(newTerms); - } - - // Implement the pause and SanctionsList functionality before transfer: - function _beforeTokenTransfer( - address from, - address to, - uint256 amount - ) internal virtual override { - // Check not paused: - require(!isPaused, "BackedToken: token transfer while paused"); - - // Check Sanctions List, but do not prevent minting burning: - if (from != address(0) && to != address(0)) { - require(!sanctionsList.isSanctioned(from), "BackedToken: sender is sanctioned"); - require(!sanctionsList.isSanctioned(to), "BackedToken: receiver is sanctioned"); - } - - super._beforeTokenTransfer(from, to, amount); - } - - // Implement the SanctionsList functionality for spender: - function _spendAllowance( - address owner, - address spender, - uint256 amount - ) internal virtual override { - require(!sanctionsList.isSanctioned(spender), "BackedToken: spender is sanctioned"); - - super._spendAllowance(owner, spender, amount); - } - /** - * @dev This empty reserved space is put in place to allow future versions to add new - * variables without shifting down storage in the inheritance chain. - * See https://docs.openzeppelin.com/contracts/4.x/upgradeable#storage_gaps - */ - uint256[49] private __gap; -} diff --git a/test/BackedAutoFeeTokenImplementation.ts b/test/BackedAutoFeeTokenImplementation.ts index 6bf7b6c..fd07ce1 100644 --- a/test/BackedAutoFeeTokenImplementation.ts +++ b/test/BackedAutoFeeTokenImplementation.ts @@ -100,6 +100,26 @@ describe("BackedAutoFeeTokenImplementation", function () { await helpers.reset(); }) + describe('#constructor', () => { + it("block calling initializer on implementation contract", async function () { + await expect( + tokenImplementation['initialize(string,string)']( + tokenName, + tokenSymbol + ) + ).to.be.revertedWith("Initializable: contract is already initialized"); + }); + it("block calling initialize_v2 on implementation contract", async function () { + await expect( + tokenImplementation.initialize_v2( + 1, + 1, + 0 + ) + ).to.be.revertedWith("BackedAutoFeeTokenImplementation already initialized"); + }); + }); + describe('#initializer_v2', () => { it("Cannot initialize twice", async function () { await expect( @@ -124,6 +144,22 @@ describe("BackedAutoFeeTokenImplementation", function () { )); token = BackedAutoFeeTokenImplementation__factory.connect(tokenProxy.address, owner.signer); }); + it("Cannot initialize with last time fee applied set to zero ", async function () { + await expect( + proxyAdmin.upgradeAndCall( + token.address, + tokenImplementation.address, + tokenImplementation.interface.encodeFunctionData( + 'initialize_v2', + [ + 24 * 3600, + 0, + baseFeePerPeriod + ] + ) + ) + ).to.be.revertedWith("Invalid last time fee applied"); + }); it('Should be able to upgrade with initialize_v2 method', async () => { await proxyAdmin.upgradeAndCall( @@ -199,6 +235,12 @@ describe("BackedAutoFeeTokenImplementation", function () { await expect(subject()).to.be.reverted }) }) + describe('When trying to set value to zero', () => { + const subject = () => token.setLastTimeFeeApplied(0) + it('should revert transaction', async () => { + await expect(subject()).to.be.revertedWith("Invalid last time fee applied") + }) + }) describe('When called by owner', () => { const subject = () => token.setLastTimeFeeApplied(1) it('should update last time fee applied', async () => { @@ -465,6 +507,18 @@ describe("BackedAutoFeeTokenImplementation", function () { await helpers.mine() await expect(subject()).to.be.reverted; }) + + describe('when time moved by 365 days forward', () => { + const periodsPassed = 365; + cacheBeforeEach(async () => { + await helpers.time.setNextBlockTimestamp(baseTime + periodsPassed * accrualPeriodLength); + await helpers.mine() + }) + it('Should move requested shares of tokens', async () => { + await subject(); + expect((await token.sharesOf(actor.address))).to.be.eq(sharesToTransfer); + }) + }); }) describe('And caller is whitelisted delegate', () => {