From 3256d9f5ea087810c559c51c0116ceb2d686bfce Mon Sep 17 00:00:00 2001 From: Anton Kovalchuk Date: Tue, 10 Oct 2023 08:15:13 +0200 Subject: [PATCH 001/148] create new contract that represents stETH on L2 --- contracts/token/StETH.sol | 51 +++++++++++++++++++++++++++ contracts/token/interfaces/IStETH.sol | 39 ++++++++++++++++++++ 2 files changed, 90 insertions(+) create mode 100644 contracts/token/StETH.sol create mode 100644 contracts/token/interfaces/IStETH.sol diff --git a/contracts/token/StETH.sol b/contracts/token/StETH.sol new file mode 100644 index 00000000..be0a36e7 --- /dev/null +++ b/contracts/token/StETH.sol @@ -0,0 +1,51 @@ +// SPDX-FileCopyrightText: 2022 Lido +// SPDX-License-Identifier: GPL-3.0 + +pragma solidity 0.8.10; + +import {IERC20} from "@openzeppelin/contracts/token/ERC20/IERC20.sol"; + +import {IStETH} from "./interfaces/IStETH.sol"; +import {ERC20Bridged} from "./ERC20Bridged.sol"; + +/// @author kovalgek +/// @notice Extends the ERC20Bridged functionality +contract StETH is ERC20Bridged, IStETH { + + IERC20 public wstETH; + + /// @param wstETH_ address of the WstETH token to wrap + /// @param name_ The name of the token + /// @param symbol_ The symbol of the token + /// @param decimals_ The decimals places of the token + /// @param bridge_ The bridge address which allowd to mint/burn tokens + constructor( + IERC20 wstETH_, + string memory name_, + string memory symbol_, + uint8 decimals_, + address bridge_ + ) ERC20Bridged(name_, symbol_, decimals_, bridge_) { + wstETH = wstETH_; + } + + function wstETH_to_stETH_rate() public pure returns (uint256) { + return 2; + } + + function wrap(uint256 wstETHAmount_) external returns (uint256) { + require(wstETHAmount_ > 0, "stETH: can't wrap zero wstETH"); + uint256 stETHAmount = wstETHAmount_ / wstETH_to_stETH_rate(); + _mint(msg.sender, stETHAmount); + wstETH.transferFrom(msg.sender, address(this), wstETHAmount_); + return stETHAmount; + } + + function unwrap(uint256 stETHAmount_) external returns (uint256) { + require(stETHAmount_ > 0, "stETH: zero amount unwrap not allowed"); + uint256 wstETHAmount = stETHAmount_ * wstETH_to_stETH_rate(); + _burn(msg.sender, stETHAmount_); + wstETH.transfer(msg.sender, wstETHAmount); + return wstETHAmount; + } +} \ No newline at end of file diff --git a/contracts/token/interfaces/IStETH.sol b/contracts/token/interfaces/IStETH.sol new file mode 100644 index 00000000..a6acdaf2 --- /dev/null +++ b/contracts/token/interfaces/IStETH.sol @@ -0,0 +1,39 @@ +// SPDX-FileCopyrightText: 2023 Lido +// SPDX-License-Identifier: GPL-3.0 + +pragma solidity 0.8.10; + + +/// @author kovalgek +/// @notice Extends the ERC20 functionality that allows the bridge to mint/burn tokens +interface IStETH { + function wrap(uint256 _stETHAmount) external returns (uint256); + function unwrap(uint256 _wstETHAmount) external returns (uint256); + + + // /** + // * @notice Get amount of wstETH for a given amount of stETH + // * @param _stETHAmount amount of stETH + // * @return Amount of wstETH for a given stETH amount + // */ + // function getWstETHByStETH(uint256 _stETHAmount) external view returns (uint256); + + // /** + // * @notice Get amount of stETH for a given amount of wstETH + // * @param _wstETHAmount amount of wstETH + // * @return Amount of stETH for a given wstETH amount + // */ + // function getStETHByWstETH(uint256 _wstETHAmount) external view returns (uint256); + + // /** + // * @notice Get amount of stETH for a one wstETH + // * @return Amount of stETH for 1 wstETH + // */ + // function stEthPerToken() external view returns (uint256); + + // /** + // * @notice Get amount of wstETH for a one stETH + // * @return Amount of wstETH for a 1 stETH + // */ + // function tokensPerStEth() external view returns (uint256); +} \ No newline at end of file From e4b2cbd987e603a4a51b7a903a60b544daa7603e Mon Sep 17 00:00:00 2001 From: Anton Kovalchuk Date: Wed, 11 Oct 2023 17:56:21 +0200 Subject: [PATCH 002/148] add wrap/unwrap functions --- contracts/token/ERC20Rebasable.sol | 65 ++++++++++++++++++++++++++++++ 1 file changed, 65 insertions(+) create mode 100644 contracts/token/ERC20Rebasable.sol diff --git a/contracts/token/ERC20Rebasable.sol b/contracts/token/ERC20Rebasable.sol new file mode 100644 index 00000000..7bdc718e --- /dev/null +++ b/contracts/token/ERC20Rebasable.sol @@ -0,0 +1,65 @@ +// SPDX-FileCopyrightText: 2022 Lido +// SPDX-License-Identifier: GPL-3.0 + +pragma solidity 0.8.10; + +import {IERC20} from "@openzeppelin/contracts/token/ERC20/IERC20.sol"; + +import {IERC20Wrapable} from "./interfaces/IERC20Wrapable.sol"; +import {ITokensRateOracle} from "./interfaces/ITokensRateOracle.sol"; + +import {ERC20Core} from "./ERC20Core.sol"; +import {ERC20Metadata} from "./ERC20Metadata.sol"; + +/// @author kovalgek +/// @notice Extends the ERC20Core functionality +contract ERC20Rebasable is IERC20Wrapable, ERC20Core, ERC20Metadata { + + IERC20 public immutable wrappedToken; + ITokensRateOracle public immutable tokensRateOracle; + + /// @param wrappedToken_ address of the ERC20 token to wrap + /// @param tokensRateOracle_ address of oracle that returns tokens rate + /// @param name_ The name of the token + /// @param symbol_ The symbol of the token + /// @param decimals_ The decimals places of the token + constructor( + IERC20 wrappedToken_, + ITokensRateOracle tokensRateOracle_, + string memory name_, + string memory symbol_, + uint8 decimals_ + ) ERC20Metadata(name_, symbol_, decimals_) { + wrappedToken = wrappedToken_; + tokensRateOracle = tokensRateOracle_; + } + + /// @notice Sets the name and the symbol of the tokens if they both are empty + /// @param name_ The name of the token + /// @param symbol_ The symbol of the token + function initialize(string memory name_, string memory symbol_) external { + _setERC20MetadataName(name_); + _setERC20MetadataSymbol(symbol_); + } + + /// @inheritdoc IERC20Wrapable + function wrap(uint256 wstETHAmount_) external returns (uint256) { + require(wstETHAmount_ > 0, "stETH: can't wrap zero wstETH"); + uint256 stETHAmount = wstETHAmount_ / tokensRateOracle.wstETH_to_stETH_rate(); // check how to divide. + // + _mint(msg.sender, stETHAmount); + wrappedToken.transferFrom(msg.sender, address(this), wstETHAmount_); + return stETHAmount; + } + + // tests when rate is different <1, >1. + + /// @inheritdoc IERC20Wrapable + function unwrap(uint256 stETHAmount_) external returns (uint256) { + require(stETHAmount_ > 0, "stETH: zero amount unwrap not allowed"); + uint256 wstETHAmount = stETHAmount_ * tokensRateOracle.wstETH_to_stETH_rate(); + _burn(msg.sender, stETHAmount_); + wrappedToken.transfer(msg.sender, wstETHAmount); + return wstETHAmount; + } +} \ No newline at end of file From 82038ea0b823a44dd6c86cff146a52d951c95ec6 Mon Sep 17 00:00:00 2001 From: Anton Kovalchuk Date: Fri, 13 Oct 2023 11:54:40 +0200 Subject: [PATCH 003/148] add shares to rebasable token --- contracts/stubs/TokensRateOracleStub.sol | 41 +++ contracts/token/ERC20Rebasable.sol | 260 ++++++++++++++++-- contracts/token/StETH.sol | 51 ---- .../{IStETH.sol => IERC20Wrapable.sol} | 32 ++- .../token/interfaces/ITokensRateOracle.sol | 28 ++ 5 files changed, 335 insertions(+), 77 deletions(-) create mode 100644 contracts/stubs/TokensRateOracleStub.sol delete mode 100644 contracts/token/StETH.sol rename contracts/token/interfaces/{IStETH.sol => IERC20Wrapable.sol} (53%) create mode 100644 contracts/token/interfaces/ITokensRateOracle.sol diff --git a/contracts/stubs/TokensRateOracleStub.sol b/contracts/stubs/TokensRateOracleStub.sol new file mode 100644 index 00000000..15771d72 --- /dev/null +++ b/contracts/stubs/TokensRateOracleStub.sol @@ -0,0 +1,41 @@ +// SPDX-FileCopyrightText: 2022 Lido +// SPDX-License-Identifier: GPL-3.0 + +pragma solidity 0.8.10; + +import {ITokensRateOracle} from "../token/interfaces/ITokensRateOracle.sol"; + +contract TokensRateOracleStub is ITokensRateOracle { + + uint8 public _decimals; + + function setDecimals(uint8 decimals_) external { + _decimals = decimals_; + } + + function decimals() external view returns (uint8) { + return _decimals; + } + + int256 public latestRoundDataAnswer; + + function setLatestRoundDataAnswer(int256 answer_) external { + latestRoundDataAnswer = answer_; + } + + /** + * @notice get data about the latest round. + */ + function latestRoundData() + external + view + returns ( + uint80 roundId, + int256 answer, + uint256 startedAt, + uint256 updatedAt, + uint80 answeredInRound + ) { + return (0,latestRoundDataAnswer,0,0,0); + } +} \ No newline at end of file diff --git a/contracts/token/ERC20Rebasable.sol b/contracts/token/ERC20Rebasable.sol index 7bdc718e..219dfd44 100644 --- a/contracts/token/ERC20Rebasable.sol +++ b/contracts/token/ERC20Rebasable.sol @@ -4,16 +4,13 @@ pragma solidity 0.8.10; import {IERC20} from "@openzeppelin/contracts/token/ERC20/IERC20.sol"; - import {IERC20Wrapable} from "./interfaces/IERC20Wrapable.sol"; import {ITokensRateOracle} from "./interfaces/ITokensRateOracle.sol"; - -import {ERC20Core} from "./ERC20Core.sol"; import {ERC20Metadata} from "./ERC20Metadata.sol"; /// @author kovalgek -/// @notice Extends the ERC20Core functionality -contract ERC20Rebasable is IERC20Wrapable, ERC20Core, ERC20Metadata { +/// @notice Extends the ERC20Shared functionality +contract ERC20Rebasable is IERC20Wrapable, IERC20, ERC20Metadata { IERC20 public immutable wrappedToken; ITokensRateOracle public immutable tokensRateOracle; @@ -42,24 +39,247 @@ contract ERC20Rebasable is IERC20Wrapable, ERC20Core, ERC20Metadata { _setERC20MetadataSymbol(symbol_); } + + /// ------------IERC20Wrapable------------ + /// @inheritdoc IERC20Wrapable - function wrap(uint256 wstETHAmount_) external returns (uint256) { - require(wstETHAmount_ > 0, "stETH: can't wrap zero wstETH"); - uint256 stETHAmount = wstETHAmount_ / tokensRateOracle.wstETH_to_stETH_rate(); // check how to divide. - // - _mint(msg.sender, stETHAmount); - wrappedToken.transferFrom(msg.sender, address(this), wstETHAmount_); - return stETHAmount; - } + function wrap(uint256 sharesAmount_) external returns (uint256) { + require(sharesAmount_ > 0, "Rebasable: can't wrap zero shares"); + + _mintShares(msg.sender, sharesAmount_); + wrappedToken.transferFrom(msg.sender, address(this), sharesAmount_); - // tests when rate is different <1, >1. + return getTokensByShares(sharesAmount_); + } /// @inheritdoc IERC20Wrapable - function unwrap(uint256 stETHAmount_) external returns (uint256) { - require(stETHAmount_ > 0, "stETH: zero amount unwrap not allowed"); - uint256 wstETHAmount = stETHAmount_ * tokensRateOracle.wstETH_to_stETH_rate(); - _burn(msg.sender, stETHAmount_); - wrappedToken.transfer(msg.sender, wstETHAmount); - return wstETHAmount; + function unwrap(uint256 tokenAmount_) external returns (uint256) { + require(tokenAmount_ > 0, "Rebasable: zero amount unwrap not allowed"); + + uint256 sharesAmount = getSharesByTokens(tokenAmount_); + + _burnShares(msg.sender, sharesAmount); + wrappedToken.transfer(msg.sender, sharesAmount); + + return sharesAmount; + } + + + /// ------------ERC20------------ + + /// @inheritdoc IERC20 + mapping(address => mapping(address => uint256)) public allowance; + + /// @inheritdoc IERC20 + function totalSupply() external view returns (uint256) { + return getTokensByShares(totalShares); } + + /// @inheritdoc IERC20 + function balanceOf(address account_) external view returns (uint256) { + return getTokensByShares(_sharesOf(account_)); + } + + /// @inheritdoc IERC20 + function approve(address spender_, uint256 amount_) + external + returns (bool) + { + _approve(msg.sender, spender_, amount_); + return true; + } + + /// @inheritdoc IERC20 + function transfer(address to_, uint256 amount_) external returns (bool) { + _transfer(msg.sender, to_, amount_); + return true; + } + + /// @inheritdoc IERC20 + function transferFrom( + address from_, + address to_, + uint256 amount_ + ) external returns (bool) { + _spendAllowance(from_, msg.sender, amount_); + _transfer(from_, to_, amount_); + return true; + } + + /// @notice Atomically increases the allowance granted to spender by the caller. + /// @param spender_ An address of the tokens spender + /// @param addedValue_ An amount to increase the allowance + function increaseAllowance(address spender_, uint256 addedValue_) + external + returns (bool) + { + _approve( + msg.sender, + spender_, + allowance[msg.sender][spender_] + addedValue_ + ); + return true; + } + + /// @notice Atomically decreases the allowance granted to spender by the caller. + /// @param spender_ An address of the tokens spender + /// @param subtractedValue_ An amount to decrease the allowance + function decreaseAllowance(address spender_, uint256 subtractedValue_) + external + returns (bool) + { + uint256 currentAllowance = allowance[msg.sender][spender_]; + if (currentAllowance < subtractedValue_) { + revert ErrorDecreasedAllowanceBelowZero(); + } + unchecked { + _approve(msg.sender, spender_, currentAllowance - subtractedValue_); + } + return true; + } + + /// @dev Moves amount_ of tokens from sender_ to recipient_ + /// @param from_ An address of the sender of the tokens + /// @param to_ An address of the recipient of the tokens + /// @param amount_ An amount of tokens to transfer + function _transfer( + address from_, + address to_, + uint256 amount_ + ) internal onlyNonZeroAccount(from_) onlyNonZeroAccount(to_) { + uint256 sharesToTransfer = getSharesByTokens(amount_); + _transferShares(from_, to_, sharesToTransfer); + emit Transfer(from_, to_, amount_); + } + + /// @dev Updates owner_'s allowance for spender_ based on spent amount_. Does not update + /// the allowance amount in case of infinite allowance + /// @param owner_ An address of the account to spend allowance + /// @param spender_ An address of the spender of the tokens + /// @param amount_ An amount of allowance spend + function _spendAllowance( + address owner_, + address spender_, + uint256 amount_ + ) internal { + uint256 currentAllowance = allowance[owner_][spender_]; + if (currentAllowance == type(uint256).max) { + return; + } + if (amount_ > currentAllowance) { + revert ErrorNotEnoughAllowance(); + } + unchecked { + _approve(owner_, spender_, currentAllowance - amount_); + } + } + + /// @dev Sets amount_ as the allowance of spender_ over the owner_'s tokens + /// @param owner_ An address of the account to set allowance + /// @param spender_ An address of the tokens spender + /// @param amount_ An amount of tokens to allow to spend + function _approve( + address owner_, + address spender_, + uint256 amount_ + ) internal virtual onlyNonZeroAccount(owner_) onlyNonZeroAccount(spender_) { + allowance[owner_][spender_] = amount_; + emit Approval(owner_, spender_, amount_); + } + + + /// ------------Shares------------ + + mapping (address => uint256) private shares; + + uint256 private totalShares; + + function _sharesOf(address account_) internal view returns (uint256) { + return shares[account_]; + } + + function getTokensByShares(uint256 sharesAmount_) public view returns (uint256) { + (uint256 tokensRate, uint8 decimals) = _getTokensRate(); + return sharesAmount_ * (10 ** uint256(decimals)) / tokensRate; + } + + function getSharesByTokens(uint256 tokenAmount_) public view returns (uint256) { + (uint256 tokensRate, uint8 decimals) = _getTokensRate(); + return tokenAmount_ * tokensRate / (10 ** uint256(decimals)); + } + + function _getTokensRate() internal view returns (uint256, uint8) { + uint8 priceDecimals = tokensRateOracle.decimals(); + + require(priceDecimals > uint8(0) && priceDecimals <= uint8(18), "Invalid priceDecimals"); + + (, + int256 answer + , + , + uint256 updatedAt + ,) = tokensRateOracle.latestRoundData(); + + require(updatedAt != 0); + + return (uint256(answer), priceDecimals); + } + + /// @dev Creates amount_ shares and assigns them to account_, increasing the total shares supply + /// @param recipient_ An address of the account to mint shares + /// @param amount_ An amount of shares to mint + function _mintShares( + address recipient_, + uint256 amount_ + ) internal onlyNonZeroAccount(recipient_) returns (uint256) { + totalShares = totalShares + amount_; + shares[recipient_] = shares[recipient_] + amount_; + return totalShares; + } + + /// @dev Destroys amount_ shares from account_, reducing the total shares supply. + /// @param account_ An address of the account to mint shares + /// @param amount_ An amount of shares to mint + function _burnShares( + address account_, + uint256 amount_ + ) internal onlyNonZeroAccount(account_) returns (uint256) { + uint256 accountShares = shares[account_]; + require(amount_ <= accountShares, "BALANCE_EXCEEDED"); + totalShares = totalShares - amount_; + shares[account_] = accountShares - amount_; + return totalShares; + } + + /// @dev Moves `sharesAmount_` shares from `sender_` to `recipient_`. + /// @param sender_ An address of the account to take shares + /// @param recipient_ An address of the account to transfer shares + /// @param sharesAmount_ An amount of shares to transfer + function _transferShares( + address sender_, + address recipient_, + uint256 sharesAmount_ + ) internal onlyNonZeroAccount(sender_) onlyNonZeroAccount(recipient_) { + + require(recipient_ != address(this), "TRANSFER_TO_REBASABLE_CONTRACT"); + + uint256 currentSenderShares = shares[sender_]; + require(sharesAmount_ <= currentSenderShares, "BALANCE_EXCEEDED"); + + shares[sender_] = currentSenderShares - sharesAmount_; + shares[recipient_] = shares[recipient_] + sharesAmount_; + } + + /// @dev validates that account_ is not zero address + modifier onlyNonZeroAccount(address account_) { + if (account_ == address(0)) { + revert ErrorAccountIsZeroAddress(); + } + _; + } + + error ErrorNotEnoughBalance(); + error ErrorNotEnoughAllowance(); + error ErrorAccountIsZeroAddress(); + error ErrorDecreasedAllowanceBelowZero(); } \ No newline at end of file diff --git a/contracts/token/StETH.sol b/contracts/token/StETH.sol deleted file mode 100644 index be0a36e7..00000000 --- a/contracts/token/StETH.sol +++ /dev/null @@ -1,51 +0,0 @@ -// SPDX-FileCopyrightText: 2022 Lido -// SPDX-License-Identifier: GPL-3.0 - -pragma solidity 0.8.10; - -import {IERC20} from "@openzeppelin/contracts/token/ERC20/IERC20.sol"; - -import {IStETH} from "./interfaces/IStETH.sol"; -import {ERC20Bridged} from "./ERC20Bridged.sol"; - -/// @author kovalgek -/// @notice Extends the ERC20Bridged functionality -contract StETH is ERC20Bridged, IStETH { - - IERC20 public wstETH; - - /// @param wstETH_ address of the WstETH token to wrap - /// @param name_ The name of the token - /// @param symbol_ The symbol of the token - /// @param decimals_ The decimals places of the token - /// @param bridge_ The bridge address which allowd to mint/burn tokens - constructor( - IERC20 wstETH_, - string memory name_, - string memory symbol_, - uint8 decimals_, - address bridge_ - ) ERC20Bridged(name_, symbol_, decimals_, bridge_) { - wstETH = wstETH_; - } - - function wstETH_to_stETH_rate() public pure returns (uint256) { - return 2; - } - - function wrap(uint256 wstETHAmount_) external returns (uint256) { - require(wstETHAmount_ > 0, "stETH: can't wrap zero wstETH"); - uint256 stETHAmount = wstETHAmount_ / wstETH_to_stETH_rate(); - _mint(msg.sender, stETHAmount); - wstETH.transferFrom(msg.sender, address(this), wstETHAmount_); - return stETHAmount; - } - - function unwrap(uint256 stETHAmount_) external returns (uint256) { - require(stETHAmount_ > 0, "stETH: zero amount unwrap not allowed"); - uint256 wstETHAmount = stETHAmount_ * wstETH_to_stETH_rate(); - _burn(msg.sender, stETHAmount_); - wstETH.transfer(msg.sender, wstETHAmount); - return wstETHAmount; - } -} \ No newline at end of file diff --git a/contracts/token/interfaces/IStETH.sol b/contracts/token/interfaces/IERC20Wrapable.sol similarity index 53% rename from contracts/token/interfaces/IStETH.sol rename to contracts/token/interfaces/IERC20Wrapable.sol index a6acdaf2..1791842e 100644 --- a/contracts/token/interfaces/IStETH.sol +++ b/contracts/token/interfaces/IERC20Wrapable.sol @@ -3,14 +3,34 @@ pragma solidity 0.8.10; - /// @author kovalgek /// @notice Extends the ERC20 functionality that allows the bridge to mint/burn tokens -interface IStETH { - function wrap(uint256 _stETHAmount) external returns (uint256); - function unwrap(uint256 _wstETHAmount) external returns (uint256); - - +interface IERC20Wrapable { + + /** + * @notice Exchanges wstETH to stETH + * @param wrappedTokenAmount_ amount of wstETH to wrap in exchange for stETH + * @dev Requirements: + * - `wstETHAmount_` must be non-zero + * - msg.sender must approve at least `wstETHAmount_` stETH to this + * contract. + * - msg.sender must have at least `wstETHAmount_` of stETH. + * User should first approve wstETHAmount_ to the StETH contract + * @return Amount of StETH user receives after wrap + */ + function wrap(uint256 wrappedTokenAmount_) external returns (uint256); + + /** + * @notice Exchanges stETH to wstETH + * @param wrapableTokenAmount_ amount of stETH to uwrap in exchange for wstETH + * @dev Requirements: + * - `stETHAmount_` must be non-zero + * - msg.sender must have at least `stETHAmount_` stETH. + * @return Amount of wstETH user receives after unwrap + */ + function unwrap(uint256 wrapableTokenAmount_) external returns (uint256); + + // TODO: // /** // * @notice Get amount of wstETH for a given amount of stETH // * @param _stETHAmount amount of stETH diff --git a/contracts/token/interfaces/ITokensRateOracle.sol b/contracts/token/interfaces/ITokensRateOracle.sol new file mode 100644 index 00000000..843be503 --- /dev/null +++ b/contracts/token/interfaces/ITokensRateOracle.sol @@ -0,0 +1,28 @@ +// SPDX-FileCopyrightText: 2023 Lido +// SPDX-License-Identifier: GPL-3.0 + +pragma solidity 0.8.10; + +/// @author kovalgek +/// @notice Oracle interface for two tokens rate +interface ITokensRateOracle { + + /** + * @notice represents the number of decimals the oracle responses represent. + */ + function decimals() external view returns (uint8); + + /** + * @notice get data about the latest round. + */ + function latestRoundData() + external + view + returns ( + uint80 roundId, + int256 answer, + uint256 startedAt, + uint256 updatedAt, + uint80 answeredInRound + ); +} \ No newline at end of file From 380caaae66494dced5c3443131c266d773312b1b Mon Sep 17 00:00:00 2001 From: Anton Kovalchuk Date: Tue, 17 Oct 2023 14:34:01 +0200 Subject: [PATCH 004/148] add unit tests --- contracts/stubs/TokensRateOracleStub.sol | 8 +- contracts/token/ERC20Rebasable.sol | 91 ++++-- contracts/token/interfaces/IERC20Wrapable.sol | 4 +- test/token/ERC20Rebasable.unit.test.ts | 278 ++++++++++++++++++ 4 files changed, 348 insertions(+), 33 deletions(-) create mode 100644 test/token/ERC20Rebasable.unit.test.ts diff --git a/contracts/stubs/TokensRateOracleStub.sol b/contracts/stubs/TokensRateOracleStub.sol index 15771d72..6f397515 100644 --- a/contracts/stubs/TokensRateOracleStub.sol +++ b/contracts/stubs/TokensRateOracleStub.sol @@ -23,6 +23,12 @@ contract TokensRateOracleStub is ITokensRateOracle { latestRoundDataAnswer = answer_; } + uint256 public latestRoundDataUpdatedAt; + + function setUpdatedAt(uint256 updatedAt_) external { + latestRoundDataUpdatedAt = updatedAt_; + } + /** * @notice get data about the latest round. */ @@ -36,6 +42,6 @@ contract TokensRateOracleStub is ITokensRateOracle { uint256 updatedAt, uint80 answeredInRound ) { - return (0,latestRoundDataAnswer,0,0,0); + return (0,latestRoundDataAnswer,0,latestRoundDataUpdatedAt,0); } } \ No newline at end of file diff --git a/contracts/token/ERC20Rebasable.sol b/contracts/token/ERC20Rebasable.sol index 219dfd44..871f0ccf 100644 --- a/contracts/token/ERC20Rebasable.sol +++ b/contracts/token/ERC20Rebasable.sol @@ -12,6 +12,17 @@ import {ERC20Metadata} from "./ERC20Metadata.sol"; /// @notice Extends the ERC20Shared functionality contract ERC20Rebasable is IERC20Wrapable, IERC20, ERC20Metadata { + error ErrorZeroSharesWrap(); + error ErrorZeroTokensUnwrap(); + error ErrorInvalidRateDecimals(uint8); + error ErrorWrongOracleUpdateTime(); + error ErrorOracleAnswerIsNegative(); + error ErrorTrasferToRebasableContract(); + error ErrorNotEnoughBalance(); + error ErrorNotEnoughAllowance(); + error ErrorAccountIsZeroAddress(); + error ErrorDecreasedAllowanceBelowZero(); + IERC20 public immutable wrappedToken; ITokensRateOracle public immutable tokensRateOracle; @@ -39,24 +50,23 @@ contract ERC20Rebasable is IERC20Wrapable, IERC20, ERC20Metadata { _setERC20MetadataSymbol(symbol_); } - /// ------------IERC20Wrapable------------ /// @inheritdoc IERC20Wrapable function wrap(uint256 sharesAmount_) external returns (uint256) { - require(sharesAmount_ > 0, "Rebasable: can't wrap zero shares"); - + if (sharesAmount_ == 0) revert ErrorZeroSharesWrap(); + _mintShares(msg.sender, sharesAmount_); wrappedToken.transferFrom(msg.sender, address(this), sharesAmount_); - return getTokensByShares(sharesAmount_); + return _getTokensByShares(sharesAmount_); } /// @inheritdoc IERC20Wrapable function unwrap(uint256 tokenAmount_) external returns (uint256) { - require(tokenAmount_ > 0, "Rebasable: zero amount unwrap not allowed"); + if (tokenAmount_ == 0) revert ErrorZeroTokensUnwrap(); - uint256 sharesAmount = getSharesByTokens(tokenAmount_); + uint256 sharesAmount = _getSharesByTokens(tokenAmount_); _burnShares(msg.sender, sharesAmount); wrappedToken.transfer(msg.sender, sharesAmount); @@ -64,7 +74,6 @@ contract ERC20Rebasable is IERC20Wrapable, IERC20, ERC20Metadata { return sharesAmount; } - /// ------------ERC20------------ /// @inheritdoc IERC20 @@ -72,12 +81,12 @@ contract ERC20Rebasable is IERC20Wrapable, IERC20, ERC20Metadata { /// @inheritdoc IERC20 function totalSupply() external view returns (uint256) { - return getTokensByShares(totalShares); + return _getTokensByShares(totalShares); } /// @inheritdoc IERC20 function balanceOf(address account_) external view returns (uint256) { - return getTokensByShares(_sharesOf(account_)); + return _getTokensByShares(_sharesOf(account_)); } /// @inheritdoc IERC20 @@ -147,7 +156,7 @@ contract ERC20Rebasable is IERC20Wrapable, IERC20, ERC20Metadata { address to_, uint256 amount_ ) internal onlyNonZeroAccount(from_) onlyNonZeroAccount(to_) { - uint256 sharesToTransfer = getSharesByTokens(amount_); + uint256 sharesToTransfer = _getSharesByTokens(amount_); _transferShares(from_, to_, sharesToTransfer); emit Transfer(from_, to_, amount_); } @@ -189,6 +198,28 @@ contract ERC20Rebasable is IERC20Wrapable, IERC20, ERC20Metadata { /// ------------Shares------------ + // API + function sharesOf(address _account) external view returns (uint256) { + return _sharesOf(_account); + } + + function getTotalShares() external view returns (uint256) { + return _getTotalShares(); + } + + function getTokensByShares(uint256 sharesAmount_) external view returns (uint256) { + return _getTokensByShares(sharesAmount_); + } + + function getSharesByTokens(uint256 tokenAmount_) external view returns (uint256) { + return _getSharesByTokens(tokenAmount_); + } + + function getTokensRateAndDecimal() external view returns (uint256, uint256) { + return _getTokensRateAndDecimal(); + } + + // private/internal mapping (address => uint256) private shares; @@ -198,20 +229,24 @@ contract ERC20Rebasable is IERC20Wrapable, IERC20, ERC20Metadata { return shares[account_]; } - function getTokensByShares(uint256 sharesAmount_) public view returns (uint256) { - (uint256 tokensRate, uint8 decimals) = _getTokensRate(); - return sharesAmount_ * (10 ** uint256(decimals)) / tokensRate; + function _getTotalShares() internal view returns (uint256) { + return totalShares; + } + + function _getTokensByShares(uint256 sharesAmount_) internal view returns (uint256) { + (uint256 tokensRate, uint256 decimals) = _getTokensRateAndDecimal(); + return (sharesAmount_ * (10 ** decimals)) / tokensRate; } - function getSharesByTokens(uint256 tokenAmount_) public view returns (uint256) { - (uint256 tokensRate, uint8 decimals) = _getTokensRate(); - return tokenAmount_ * tokensRate / (10 ** uint256(decimals)); + function _getSharesByTokens(uint256 tokenAmount_) internal view returns (uint256) { + (uint256 tokensRate, uint256 decimals) = _getTokensRateAndDecimal(); + return (tokenAmount_ * tokensRate) / (10 ** decimals); } - function _getTokensRate() internal view returns (uint256, uint8) { - uint8 priceDecimals = tokensRateOracle.decimals(); + function _getTokensRateAndDecimal() internal view returns (uint256, uint256) { + uint8 rateDecimals = tokensRateOracle.decimals(); - require(priceDecimals > uint8(0) && priceDecimals <= uint8(18), "Invalid priceDecimals"); + if (rateDecimals == uint8(0) || rateDecimals > uint8(18)) revert ErrorInvalidRateDecimals(rateDecimals); (, int256 answer @@ -220,9 +255,10 @@ contract ERC20Rebasable is IERC20Wrapable, IERC20, ERC20Metadata { uint256 updatedAt ,) = tokensRateOracle.latestRoundData(); - require(updatedAt != 0); - - return (uint256(answer), priceDecimals); + if (updatedAt == 0) revert ErrorWrongOracleUpdateTime(); + if (answer <= 0) revert ErrorOracleAnswerIsNegative(); + + return (uint256(answer), uint256(rateDecimals)); } /// @dev Creates amount_ shares and assigns them to account_, increasing the total shares supply @@ -245,7 +281,7 @@ contract ERC20Rebasable is IERC20Wrapable, IERC20, ERC20Metadata { uint256 amount_ ) internal onlyNonZeroAccount(account_) returns (uint256) { uint256 accountShares = shares[account_]; - require(amount_ <= accountShares, "BALANCE_EXCEEDED"); + if (accountShares < amount_) revert ErrorNotEnoughBalance(); totalShares = totalShares - amount_; shares[account_] = accountShares - amount_; return totalShares; @@ -261,10 +297,10 @@ contract ERC20Rebasable is IERC20Wrapable, IERC20, ERC20Metadata { uint256 sharesAmount_ ) internal onlyNonZeroAccount(sender_) onlyNonZeroAccount(recipient_) { - require(recipient_ != address(this), "TRANSFER_TO_REBASABLE_CONTRACT"); + if (recipient_ == address(this)) revert ErrorTrasferToRebasableContract(); uint256 currentSenderShares = shares[sender_]; - require(sharesAmount_ <= currentSenderShares, "BALANCE_EXCEEDED"); + if (sharesAmount_ > currentSenderShares) revert ErrorNotEnoughBalance(); shares[sender_] = currentSenderShares - sharesAmount_; shares[recipient_] = shares[recipient_] + sharesAmount_; @@ -277,9 +313,4 @@ contract ERC20Rebasable is IERC20Wrapable, IERC20, ERC20Metadata { } _; } - - error ErrorNotEnoughBalance(); - error ErrorNotEnoughAllowance(); - error ErrorAccountIsZeroAddress(); - error ErrorDecreasedAllowanceBelowZero(); } \ No newline at end of file diff --git a/contracts/token/interfaces/IERC20Wrapable.sol b/contracts/token/interfaces/IERC20Wrapable.sol index 1791842e..15213ccd 100644 --- a/contracts/token/interfaces/IERC20Wrapable.sol +++ b/contracts/token/interfaces/IERC20Wrapable.sol @@ -9,7 +9,7 @@ interface IERC20Wrapable { /** * @notice Exchanges wstETH to stETH - * @param wrappedTokenAmount_ amount of wstETH to wrap in exchange for stETH + * @param sharesAmount_ amount of wstETH to wrap in exchange for stETH * @dev Requirements: * - `wstETHAmount_` must be non-zero * - msg.sender must approve at least `wstETHAmount_` stETH to this @@ -18,7 +18,7 @@ interface IERC20Wrapable { * User should first approve wstETHAmount_ to the StETH contract * @return Amount of StETH user receives after wrap */ - function wrap(uint256 wrappedTokenAmount_) external returns (uint256); + function wrap(uint256 sharesAmount_) external returns (uint256); /** * @notice Exchanges stETH to wstETH diff --git a/test/token/ERC20Rebasable.unit.test.ts b/test/token/ERC20Rebasable.unit.test.ts new file mode 100644 index 00000000..849155a7 --- /dev/null +++ b/test/token/ERC20Rebasable.unit.test.ts @@ -0,0 +1,278 @@ +import hre from "hardhat"; +import { assert } from "chai"; +import { unit } from "../../utils/testing"; +import { wei } from "../../utils/wei"; + +import { ERC20Stub__factory, ERC20Rebasable__factory, TokensRateOracleStub__factory, OssifiableProxy__factory } from "../../typechain"; +import { BigNumber } from "ethers"; + + +unit("ERC20Rebasable", ctxFactory) + + .test("wrappedToken", async (ctx) => { + const { rebasableProxied, wrappedTokenStub } = ctx.contracts; + assert.equal(await rebasableProxied.wrappedToken(), wrappedTokenStub.address) + }) + + .test("tokensRateOracle", async (ctx) => { + const { rebasableProxied, tokensRateOracleStub } = ctx.contracts; + assert.equal(await rebasableProxied.tokensRateOracle(), tokensRateOracleStub.address) + }) + + .test("name()", async (ctx) => + assert.equal(await ctx.contracts.rebasableProxied.name(), ctx.constants.name) + ) + + .test("symbol()", async (ctx) => + assert.equal(await ctx.contracts.rebasableProxied.symbol(), ctx.constants.symbol) + ) + + .test("decimals", async (ctx) => + assert.equal(await ctx.contracts.rebasableProxied.decimals(), ctx.constants.decimals) + ) + + .test("wrap(0)", async (ctx) => { + const { rebasableProxied } = ctx.contracts; + await assert.revertsWith(rebasableProxied.wrap(0), "ErrorZeroSharesWrap()"); + }) + + .test("unwrap(0)", async (ctx) => { + const { rebasableProxied } = ctx.contracts; + await assert.revertsWith(rebasableProxied.unwrap(0), "ErrorZeroTokensUnwrap()"); + }) + + .test("wrap() positive scenario", async (ctx) => { + const { rebasableProxied, tokensRateOracleStub, wrappedTokenStub } = ctx.contracts; + const {user1, user2 } = ctx.accounts; + + await tokensRateOracleStub.setDecimals(5); + await tokensRateOracleStub.setLatestRoundDataAnswer(120000); + await tokensRateOracleStub.setUpdatedAt(1000); + + // user1 + assert.equalBN(await rebasableProxied.callStatic.wrap(100), 83); + const tx = await rebasableProxied.wrap(100); + + assert.equalBN(await rebasableProxied.getTotalShares(), 100); + assert.equalBN(await rebasableProxied.sharesOf(user1.address), 100); + + assert.equal(await wrappedTokenStub.transferFromAddress(), user1.address); + assert.equal(await wrappedTokenStub.transferFromTo(), rebasableProxied.address); + assert.equalBN(await wrappedTokenStub.transferFromAmount(), 100); + + // user2 + assert.equalBN(await rebasableProxied.connect(user2).callStatic.wrap(50), 41); + const tx2 = await rebasableProxied.connect(user2).wrap(50); + + assert.equalBN(await rebasableProxied.getTotalShares(), 150); + assert.equalBN(await rebasableProxied.sharesOf(user2.address), 50); + + assert.equal(await wrappedTokenStub.transferFromAddress(), user2.address); + assert.equal(await wrappedTokenStub.transferFromTo(), rebasableProxied.address); + assert.equalBN(await wrappedTokenStub.transferFromAmount(), 50); + + // common state changes + assert.equalBN(await rebasableProxied.totalSupply(), 125); + }) + + .test("wrap() with wrong oracle decimals", async (ctx) => { + + const { rebasableProxied, tokensRateOracleStub } = ctx.contracts; + + await tokensRateOracleStub.setDecimals(0); + await tokensRateOracleStub.setLatestRoundDataAnswer(120000); + await tokensRateOracleStub.setUpdatedAt(1000); + + await assert.revertsWith(rebasableProxied.wrap(23), "ErrorInvalidRateDecimals(0)"); + + await tokensRateOracleStub.setDecimals(19); + await tokensRateOracleStub.setLatestRoundDataAnswer(120000); + await tokensRateOracleStub.setUpdatedAt(1000); + + await assert.revertsWith(rebasableProxied.wrap(23), "ErrorInvalidRateDecimals(19)"); + }) + + .test("wrap() with wrong oracle update time", async (ctx) => { + + const { rebasableProxied, tokensRateOracleStub } = ctx.contracts; + + await tokensRateOracleStub.setDecimals(10); + await tokensRateOracleStub.setLatestRoundDataAnswer(120000); + await tokensRateOracleStub.setUpdatedAt(0); + + await assert.revertsWith(rebasableProxied.wrap(5), "ErrorWrongOracleUpdateTime()"); + }) + + .test("wrap() with wrong oracle answer", async (ctx) => { + + const { rebasableProxied, tokensRateOracleStub } = ctx.contracts; + + await tokensRateOracleStub.setDecimals(10); + await tokensRateOracleStub.setLatestRoundDataAnswer(0); + await tokensRateOracleStub.setUpdatedAt(10); + + await assert.revertsWith(rebasableProxied.wrap(21), "ErrorOracleAnswerIsNegative()"); + }) + + + .test("unwrap() positive scenario", async (ctx) => { + + const { rebasableProxied, tokensRateOracleStub, wrappedTokenStub } = ctx.contracts; + const {user1, user2 } = ctx.accounts; + + await tokensRateOracleStub.setDecimals(7); + await tokensRateOracleStub.setLatestRoundDataAnswer(14000000); + await tokensRateOracleStub.setUpdatedAt(14000); + + // user1 + const tx0 = await rebasableProxied.wrap(4500); + + assert.equalBN(await rebasableProxied.callStatic.unwrap(59), 82); + const tx = await rebasableProxied.unwrap(59); + + assert.equalBN(await rebasableProxied.getTotalShares(), 4418); + assert.equalBN(await rebasableProxied.sharesOf(user1.address), 4418); + + assert.equal(await wrappedTokenStub.transferTo(), user1.address); + assert.equalBN(await wrappedTokenStub.transferAmount(), 82); + + // // user2 + await rebasableProxied.connect(user2).wrap(200); + + assert.equalBN(await rebasableProxied.connect(user2).callStatic.unwrap(50), 70); + const tx2 = await rebasableProxied.connect(user2).unwrap(50); + + assert.equalBN(await rebasableProxied.getTotalShares(), 4548); + assert.equalBN(await rebasableProxied.sharesOf(user2.address), 130); + + assert.equal(await wrappedTokenStub.transferTo(), user2.address); + assert.equalBN(await wrappedTokenStub.transferAmount(), 70); + + // common state changes + assert.equalBN(await rebasableProxied.totalSupply(), 3248); + }) + + .test("unwrap() with wrong oracle decimals", async (ctx) => { + + const { rebasableProxied, tokensRateOracleStub } = ctx.contracts; + + + await tokensRateOracleStub.setDecimals(10); + await tokensRateOracleStub.setLatestRoundDataAnswer(120000); + await tokensRateOracleStub.setUpdatedAt(1000); + + await rebasableProxied.wrap(100); + await tokensRateOracleStub.setDecimals(0); + + await assert.revertsWith(rebasableProxied.unwrap(23), "ErrorInvalidRateDecimals(0)"); + + await tokensRateOracleStub.setDecimals(19); + await tokensRateOracleStub.setLatestRoundDataAnswer(120000); + await tokensRateOracleStub.setUpdatedAt(1000); + + await assert.revertsWith(rebasableProxied.unwrap(23), "ErrorInvalidRateDecimals(19)"); + }) + + .test("unwrap() with wrong oracle update time", async (ctx) => { + + const { rebasableProxied, tokensRateOracleStub } = ctx.contracts; + + await tokensRateOracleStub.setDecimals(10); + await tokensRateOracleStub.setLatestRoundDataAnswer(120000); + await tokensRateOracleStub.setUpdatedAt(300); + + await rebasableProxied.wrap(100); + await tokensRateOracleStub.setUpdatedAt(0); + + await assert.revertsWith(rebasableProxied.unwrap(5), "ErrorWrongOracleUpdateTime()"); + }) + + .test("unwrap() when no balance", async (ctx) => { + const { rebasableProxied, tokensRateOracleStub } = ctx.contracts; + + await tokensRateOracleStub.setDecimals(8); + await tokensRateOracleStub.setLatestRoundDataAnswer(12000000); + await tokensRateOracleStub.setUpdatedAt(1000); + + await assert.revertsWith(rebasableProxied.unwrap(10), "ErrorNotEnoughBalance()"); + }) + + .test("approve()", async (ctx) => { + const { rebasableProxied } = ctx.contracts; + const { user1, user2 } = ctx.accounts; + + // validate initially allowance is zero + assert.equalBN( + await rebasableProxied.allowance(user1.address, user2.address), + "0" + ); + + const amount = 3; + + // validate return value of the method + assert.isTrue( + await rebasableProxied.callStatic.approve(user2.address, amount) + ); + + // approve tokens + const tx = await rebasableProxied.approve(user2.address, amount); + + // validate Approval event was emitted + await assert.emits(rebasableProxied, tx, "Approval", [ + user1.address, + user2.address, + amount, + ]); + + // validate allowance was set + assert.equalBN( + await rebasableProxied.allowance(user1.address, user2.address), + amount + ); + }) + + .run(); + +async function ctxFactory() { + const name = "StETH Test Token"; + const symbol = "StETH"; + const decimals = 18; + const [deployer, user1, user2] = await hre.ethers.getSigners(); + + const wrappedTokenStub = await new ERC20Stub__factory(deployer).deploy(); + + const tokensRateOracleStub = await new TokensRateOracleStub__factory(deployer).deploy(); + + const rebasableTokenImpl = await new ERC20Rebasable__factory(deployer).deploy( + wrappedTokenStub.address, + tokensRateOracleStub.address, + name, + symbol, + decimals + ); + rebasableTokenImpl.wrap + await hre.network.provider.request({ + method: "hardhat_impersonateAccount", + params: [hre.ethers.constants.AddressZero], + }); + + const l2TokensProxy = await new OssifiableProxy__factory(deployer).deploy( + rebasableTokenImpl.address, + deployer.address, + ERC20Rebasable__factory.createInterface().encodeFunctionData("initialize", [ + name, + symbol, + ]) + ); + + const rebasableProxied = ERC20Rebasable__factory.connect( + l2TokensProxy.address, + user1 + ); + + return { + accounts: { deployer, user1, user2 }, + constants: { name, symbol, decimals }, + contracts: { rebasableProxied, wrappedTokenStub, tokensRateOracleStub } + }; +} From eab8718a34311a3c38df676741cd1325694383a2 Mon Sep 17 00:00:00 2001 From: Anton Kovalchuk Date: Sat, 4 Nov 2023 15:16:04 +0100 Subject: [PATCH 005/148] add deposit flow for new rebasable token --- contracts/BridgeableTokens.sol | 14 +- .../arbitrum/InterchainERC20TokenGateway.sol | 8 +- contracts/arbitrum/L1ERC20TokenGateway.sol | 8 +- contracts/arbitrum/L2ERC20TokenGateway.sol | 9 +- contracts/optimism/L1ERC20TokenBridge.sol | 37 +- contracts/optimism/L2ERC20TokenBridge.sol | 26 +- contracts/stubs/ERC20Stub.sol | 66 ++ contracts/stubs/ERC20WrapableStub.sol | 56 ++ contracts/token/ERC20Core.sol | 2 + contracts/token/ERC20Rebasable.sol | 21 +- .../optimism.integration.test.ts | 16 +- .../bridging-rebase.integration.test.ts | 654 ++++++++++++++++++ utils/optimism/deployment.ts | 55 +- utils/optimism/testing.ts | 37 +- 14 files changed, 977 insertions(+), 32 deletions(-) create mode 100644 contracts/stubs/ERC20Stub.sol create mode 100644 contracts/stubs/ERC20WrapableStub.sol create mode 100644 test/optimism/bridging-rebase.integration.test.ts diff --git a/contracts/BridgeableTokens.sol b/contracts/BridgeableTokens.sol index 52ec31af..36a636b9 100644 --- a/contracts/BridgeableTokens.sol +++ b/contracts/BridgeableTokens.sol @@ -9,19 +9,27 @@ contract BridgeableTokens { /// @notice Address of the bridged token in the L1 chain address public immutable l1Token; + /// @notice Address of the bridged rebasable token in the L1 chain + address public immutable l1TokenRebasable; + /// @notice Address of the token minted on the L2 chain when token bridged address public immutable l2Token; + /// @notice Address of the rebasable token minted on the L2 chain when token bridged + address public immutable l2TokenRebasable; + /// @param l1Token_ Address of the bridged token in the L1 chain /// @param l2Token_ Address of the token minted on the L2 chain when token bridged - constructor(address l1Token_, address l2Token_) { + constructor(address l1Token_, address l1TokenRebasable_, address l2Token_, address l2TokenRebasable_) { l1Token = l1Token_; + l1TokenRebasable = l1TokenRebasable_; l2Token = l2Token_; + l2TokenRebasable = l2TokenRebasable_; } /// @dev Validates that passed l1Token_ is supported by the bridge modifier onlySupportedL1Token(address l1Token_) { - if (l1Token_ != l1Token) { + if (l1Token_ != l1Token && l1Token_ != l1TokenRebasable) { revert ErrorUnsupportedL1Token(); } _; @@ -29,7 +37,7 @@ contract BridgeableTokens { /// @dev Validates that passed l2Token_ is supported by the bridge modifier onlySupportedL2Token(address l2Token_) { - if (l2Token_ != l2Token) { + if (l2Token_ != l2Token && l2Token_ != l2TokenRebasable) { revert ErrorUnsupportedL2Token(); } _; diff --git a/contracts/arbitrum/InterchainERC20TokenGateway.sol b/contracts/arbitrum/InterchainERC20TokenGateway.sol index 329f4c87..f75334d1 100644 --- a/contracts/arbitrum/InterchainERC20TokenGateway.sol +++ b/contracts/arbitrum/InterchainERC20TokenGateway.sol @@ -25,13 +25,17 @@ abstract contract InterchainERC20TokenGateway is /// @param router_ Address of the router in the corresponding chain /// @param counterpartGateway_ Address of the counterpart gateway used in the bridging process /// @param l1Token_ Address of the bridged token in the Ethereum chain + /// @param l1TokenRebasable_ Address of the bridged token in the Ethereum chain /// @param l2Token_ Address of the token minted on the Arbitrum chain when token bridged + /// @param l2TokenRebasable_ Address of the token minted on the Arbitrum chain when token bridged constructor( address router_, address counterpartGateway_, address l1Token_, - address l2Token_ - ) BridgeableTokens(l1Token_, l2Token_) { + address l1TokenRebasable_, + address l2Token_, + address l2TokenRebasable_ + ) BridgeableTokens(l1Token_, l1TokenRebasable_, l2Token_, l2TokenRebasable_) { router = router_; counterpartGateway = counterpartGateway_; } diff --git a/contracts/arbitrum/L1ERC20TokenGateway.sol b/contracts/arbitrum/L1ERC20TokenGateway.sol index 1be951aa..7b91b6a2 100644 --- a/contracts/arbitrum/L1ERC20TokenGateway.sol +++ b/contracts/arbitrum/L1ERC20TokenGateway.sol @@ -32,13 +32,17 @@ contract L1ERC20TokenGateway is address router_, address counterpartGateway_, address l1Token_, - address l2Token_ + address l1TokenRebasable_, + address l2Token_, + address l2TokenRebasable_ ) InterchainERC20TokenGateway( router_, counterpartGateway_, l1Token_, - l2Token_ + l1TokenRebasable_, + l2Token_, + l2TokenRebasable_ ) L1CrossDomainEnabled(inbox_) {} diff --git a/contracts/arbitrum/L2ERC20TokenGateway.sol b/contracts/arbitrum/L2ERC20TokenGateway.sol index 5853d0ac..d65fd3ac 100644 --- a/contracts/arbitrum/L2ERC20TokenGateway.sol +++ b/contracts/arbitrum/L2ERC20TokenGateway.sol @@ -22,19 +22,24 @@ contract L2ERC20TokenGateway is /// @param router_ Address of the router in the L2 chain /// @param counterpartGateway_ Address of the counterpart L1 gateway /// @param l1Token_ Address of the bridged token in the L1 chain + /// @param l1TokenRebasable_ Address of the bridged token in the L1 chain /// @param l2Token_ Address of the token minted on the Arbitrum chain when token bridged constructor( address arbSys_, address router_, address counterpartGateway_, address l1Token_, - address l2Token_ + address l1TokenRebasable_, + address l2Token_, + address l2TokenRebasable_ ) InterchainERC20TokenGateway( router_, counterpartGateway_, l1Token_, - l2Token_ + l1TokenRebasable_, + l2Token_, + l2TokenRebasable_ ) L2CrossDomainEnabled(arbSys_) {} diff --git a/contracts/optimism/L1ERC20TokenBridge.sol b/contracts/optimism/L1ERC20TokenBridge.sol index a4438b88..d543a961 100644 --- a/contracts/optimism/L1ERC20TokenBridge.sol +++ b/contracts/optimism/L1ERC20TokenBridge.sol @@ -14,6 +14,9 @@ import {BridgingManager} from "../BridgingManager.sol"; import {BridgeableTokens} from "../BridgeableTokens.sol"; import {CrossDomainEnabled} from "./CrossDomainEnabled.sol"; +import {IERC20Wrapable} from "../token/interfaces/IERC20Wrapable.sol"; +import "hardhat/console.sol"; + /// @author psirex /// @notice The L1 ERC20 token bridge locks bridged tokens on the L1 side, sends deposit messages /// on the L2 side, and finalizes token withdrawals from L2. Additionally, adds the methods for @@ -32,19 +35,22 @@ contract L1ERC20TokenBridge is /// @param messenger_ L1 messenger address being used for cross-chain communications /// @param l2TokenBridge_ Address of the corresponding L2 bridge /// @param l1Token_ Address of the bridged token in the L1 chain + /// @param l1TokenRebasable_ Address of the bridged token in the L1 chain /// @param l2Token_ Address of the token minted on the L2 chain when token bridged constructor( address messenger_, address l2TokenBridge_, - address l1Token_, - address l2Token_ - ) CrossDomainEnabled(messenger_) BridgeableTokens(l1Token_, l2Token_) { + address l1Token_, // wstETH + address l1TokenRebasable_, // stETH + address l2Token_, + address l2TokenRebasable_ + ) CrossDomainEnabled(messenger_) BridgeableTokens(l1Token_, l1TokenRebasable_, l2Token_, l2TokenRebasable_) { l2TokenBridge = l2TokenBridge_; } /// @inheritdoc IL1ERC20Bridge function depositERC20( - address l1Token_, + address l1Token_, // stETH or wstETH address l2Token_, uint256 amount_, uint32 l2Gas_, @@ -54,11 +60,26 @@ contract L1ERC20TokenBridge is whenDepositsEnabled onlySupportedL1Token(l1Token_) onlySupportedL2Token(l2Token_) - { + { if (Address.isContract(msg.sender)) { revert ErrorSenderNotEOA(); } - _initiateERC20Deposit(msg.sender, msg.sender, amount_, l2Gas_, data_); + + if(l1Token_ == l1TokenRebasable) { + bytes memory data = bytes.concat(hex'01', data_); // add rate + IERC20(l1TokenRebasable).safeTransferFrom(msg.sender, address(this), amount_); + IERC20(l1TokenRebasable).approve(l1Token, amount_); + uint256 wstETHAmount = IERC20Wrapable(l1Token).wrap(amount_); + console.log("wstETHAmount=",wstETHAmount); + + uint256 balanceOfl1Token = IERC20(l1Token).balanceOf(address(this)); + console.log("balanceOfl1Token=",balanceOfl1Token); + + _initiateERC20Deposit(msg.sender, msg.sender, wstETHAmount, l2Gas_, data); + } else { + IERC20(l1Token).safeTransferFrom(msg.sender, address(this), amount_); + _initiateERC20Deposit(msg.sender, msg.sender, amount_, l2Gas_, data_); + } } /// @inheritdoc IL1ERC20Bridge @@ -120,9 +141,8 @@ contract L1ERC20TokenBridge is address to_, uint256 amount_, uint32 l2Gas_, - bytes calldata data_ + bytes memory data_ ) internal { - IERC20(l1Token).safeTransferFrom(from_, address(this), amount_); bytes memory message = abi.encodeWithSelector( IL2ERC20Bridge.finalizeDeposit.selector, @@ -133,6 +153,7 @@ contract L1ERC20TokenBridge is amount_, data_ ); + console.log("SEND l2TokenBridge=%s l1Token=%s l2Token=%s", l2TokenBridge, l1Token, l2Token); sendCrossDomainMessage(l2TokenBridge, l2Gas_, message); diff --git a/contracts/optimism/L2ERC20TokenBridge.sol b/contracts/optimism/L2ERC20TokenBridge.sol index 1b1870e0..32c72053 100644 --- a/contracts/optimism/L2ERC20TokenBridge.sol +++ b/contracts/optimism/L2ERC20TokenBridge.sol @@ -6,11 +6,14 @@ pragma solidity 0.8.10; import {IL1ERC20Bridge} from "./interfaces/IL1ERC20Bridge.sol"; import {IL2ERC20Bridge} from "./interfaces/IL2ERC20Bridge.sol"; import {IERC20Bridged} from "../token/interfaces/IERC20Bridged.sol"; +import {ERC20Rebasable} from "../token/ERC20Rebasable.sol"; import {BridgingManager} from "../BridgingManager.sol"; import {BridgeableTokens} from "../BridgeableTokens.sol"; import {CrossDomainEnabled} from "./CrossDomainEnabled.sol"; +import { console } from "hardhat/console.sol"; + /// @author psirex /// @notice The L2 token bridge works with the L1 token bridge to enable ERC20 token bridging /// between L1 and L2. It acts as a minter for new tokens when it hears about @@ -34,8 +37,10 @@ contract L2ERC20TokenBridge is address messenger_, address l1TokenBridge_, address l1Token_, - address l2Token_ - ) CrossDomainEnabled(messenger_) BridgeableTokens(l1Token_, l2Token_) { + address l1TokenRebasable_, + address l2Token_, + address l2TokenRebasable_ + ) CrossDomainEnabled(messenger_) BridgeableTokens(l1Token_, l1TokenRebasable_, l2Token_, l2TokenRebasable_) { l1TokenBridge = l1TokenBridge_; } @@ -75,8 +80,21 @@ contract L2ERC20TokenBridge is onlySupportedL2Token(l2Token_) onlyFromCrossDomainAccount(l1TokenBridge) { - IERC20Bridged(l2Token_).bridgeMint(to_, amount_); - emit DepositFinalized(l1Token_, l2Token_, from_, to_, amount_, data_); + if (data_.length > 0 && data_[0] == hex'01') { + console.log("finalizeDeposit1 l2TokenRebasable balanceBefore=",ERC20Rebasable(l2TokenRebasable).balanceOf(to_)); + + ERC20Rebasable(l2TokenRebasable).mintShares(to_, amount_); + + console.log("finalizeDeposit1 l2TokenRebasable balanceafter==",ERC20Rebasable(l2TokenRebasable).balanceOf(to_)); + + bytes memory data = data_[1:]; + emit DepositFinalized(l1Token_, l2Token_, from_, to_, amount_, data); + } else { + console.log("finalizeDeposit2 data=", data_.length); + + IERC20Bridged(l2Token_).bridgeMint(to_, amount_); + emit DepositFinalized(l1Token_, l2Token_, from_, to_, amount_, data_); + } } /// @notice Performs the logic for withdrawals by burning the token and informing diff --git a/contracts/stubs/ERC20Stub.sol b/contracts/stubs/ERC20Stub.sol new file mode 100644 index 00000000..686ea516 --- /dev/null +++ b/contracts/stubs/ERC20Stub.sol @@ -0,0 +1,66 @@ +// SPDX-FileCopyrightText: 2022 Lido +// SPDX-License-Identifier: GPL-3.0 + +pragma solidity 0.8.10; + +import {IERC20} from "@openzeppelin/contracts/token/ERC20/IERC20.sol"; +import { console } from "hardhat/console.sol"; + +contract ERC20Stub is IERC20 { + + uint256 totalSupply_; + uint256 balanceOf_; + bool transfer_; + uint256 allowance_; + bool approve_; + bool transferFrom_; + + constructor() { + totalSupply_ = 0; + balanceOf_ = 0; + transfer_ = true; + allowance_ = 0; + approve_ = true; + transferFrom_ = true; + } + + function totalSupply() external view returns (uint256) { + return totalSupply_; + } + + function balanceOf(address account) external view returns (uint256) { + return balanceOf_; + } + + address public transferTo; + uint256 public transferAmount; + + function transfer(address to, uint256 amount) external returns (bool) { + transferTo = to; + transferAmount = amount; + return true; + } + + function allowance(address owner, address spender) external view returns (uint256) { + return 0; + } + + function approve(address spender, uint256 amount) external returns (bool) { + return true; + } + + address public transferFromAddress; + address public transferFromTo; + uint256 public transferFromAmount; + + function transferFrom( + address from, + address to, + uint256 amount + ) external returns (bool) { + transferFromAddress = from; + transferFromTo = to; + transferFromAmount = amount; + return true; + } +} \ No newline at end of file diff --git a/contracts/stubs/ERC20WrapableStub.sol b/contracts/stubs/ERC20WrapableStub.sol new file mode 100644 index 00000000..fe49ff83 --- /dev/null +++ b/contracts/stubs/ERC20WrapableStub.sol @@ -0,0 +1,56 @@ +// SPDX-FileCopyrightText: 2022 Lido +// SPDX-License-Identifier: GPL-3.0 + +pragma solidity 0.8.10; + +import {IERC20Bridged} from "../token/interfaces/IERC20Bridged.sol"; +import {ERC20} from "@openzeppelin/contracts/token/ERC20/ERC20.sol"; +import {IERC20} from "@openzeppelin/contracts/token/ERC20/IERC20.sol"; +import {IERC20Wrapable} from "../token/interfaces/IERC20Wrapable.sol"; +// import {ERC20Core} from "../token/ERC20Core.sol"; +import { console } from "hardhat/console.sol"; + +// represents wstETH on L1 +contract ERC20WrapableStub is IERC20Wrapable, ERC20 { + + IERC20 public stETH; + address public bridge; + uint256 public tokensRate; /// wst/st + + constructor(IERC20 stETH_, string memory name_, string memory symbol_) + ERC20(name_, symbol_) + { + stETH = stETH_; + console.log("constructor wrap stETH=",address(stETH)); + + tokensRate = 2 * 10 **18; + _mint(msg.sender, 1000000 * 10**18); + } + + function wrap(uint256 _stETHAmount) external returns (uint256) { + require(_stETHAmount > 0, "wstETH: can't wrap zero stETH"); + + uint256 wstETHAmount = (_stETHAmount * tokensRate) / (10 ** uint256(decimals())); + + + console.log("wrap msg.sender=",msg.sender); + console.log("wrap address(this)=",address(this)); + console.log("wrap _stETHAmount=",_stETHAmount); + console.log("wrap wstETHAmount=",wstETHAmount); + + _mint(msg.sender, wstETHAmount); + stETH.transferFrom(msg.sender, address(this), _stETHAmount); + + return wstETHAmount; + } + + function unwrap(uint256 _wstETHAmount) external returns (uint256) { + require(_wstETHAmount > 0, "wstETH: zero amount unwrap not allowed"); + + // uint256 stETHAmount = (_wstETHAmount * (10 ** uint256(decimals()))) / tokensRate; + // _burn(msg.sender, _wstETHAmount); + // stETH.transfer(msg.sender, stETHAmount); + + return 0; //stETHAmount; + } +} diff --git a/contracts/token/ERC20Core.sol b/contracts/token/ERC20Core.sol index bf4e67db..9fb618cb 100644 --- a/contracts/token/ERC20Core.sol +++ b/contracts/token/ERC20Core.sol @@ -4,6 +4,7 @@ pragma solidity 0.8.10; import {IERC20} from "@openzeppelin/contracts/token/ERC20/IERC20.sol"; +import { console } from "hardhat/console.sol"; /// @author psirex /// @notice Contains the required logic of the ERC20 standard as defined in the EIP. Additionally @@ -121,6 +122,7 @@ contract ERC20Core is IERC20 { address spender_, uint256 amount_ ) internal virtual onlyNonZeroAccount(owner_) onlyNonZeroAccount(spender_) { + console.log("_approve %@ %@ %@", msg.sender, owner_, spender_); allowance[owner_][spender_] = amount_; emit Approval(owner_, spender_, amount_); } diff --git a/contracts/token/ERC20Rebasable.sol b/contracts/token/ERC20Rebasable.sol index 871f0ccf..04899582 100644 --- a/contracts/token/ERC20Rebasable.sol +++ b/contracts/token/ERC20Rebasable.sol @@ -7,6 +7,7 @@ import {IERC20} from "@openzeppelin/contracts/token/ERC20/IERC20.sol"; import {IERC20Wrapable} from "./interfaces/IERC20Wrapable.sol"; import {ITokensRateOracle} from "./interfaces/ITokensRateOracle.sol"; import {ERC20Metadata} from "./ERC20Metadata.sol"; +import { console } from "hardhat/console.sol"; /// @author kovalgek /// @notice Extends the ERC20Shared functionality @@ -74,6 +75,15 @@ contract ERC20Rebasable is IERC20Wrapable, IERC20, ERC20Metadata { return sharesAmount; } + function mintShares(address account_, uint256 amount_) external returns (uint256) { + return _mintShares(account_, amount_); + } + + function burnShares(address account_, uint256 amount_) external { + _burnShares(account_, amount_); + } + + /// ------------ERC20------------ /// @inheritdoc IERC20 @@ -234,7 +244,12 @@ contract ERC20Rebasable is IERC20Wrapable, IERC20, ERC20Metadata { } function _getTokensByShares(uint256 sharesAmount_) internal view returns (uint256) { + console.log("_getTokensByShares"); (uint256 tokensRate, uint256 decimals) = _getTokensRateAndDecimal(); + console.log("sharesAmount_=",sharesAmount_); + console.log("decimals=",decimals); + console.log("tokensRate=",tokensRate); + return (sharesAmount_ * (10 ** decimals)) / tokensRate; } @@ -245,8 +260,10 @@ contract ERC20Rebasable is IERC20Wrapable, IERC20, ERC20Metadata { function _getTokensRateAndDecimal() internal view returns (uint256, uint256) { uint8 rateDecimals = tokensRateOracle.decimals(); + console.log("_getTokensRateAndDecimal1"); if (rateDecimals == uint8(0) || rateDecimals > uint8(18)) revert ErrorInvalidRateDecimals(rateDecimals); + console.log("_getTokensRateAndDecimal2"); (, int256 answer @@ -254,10 +271,12 @@ contract ERC20Rebasable is IERC20Wrapable, IERC20, ERC20Metadata { , uint256 updatedAt ,) = tokensRateOracle.latestRoundData(); + console.log("_getTokensRateAndDecimal3"); if (updatedAt == 0) revert ErrorWrongOracleUpdateTime(); if (answer <= 0) revert ErrorOracleAnswerIsNegative(); - + console.log("_getTokensRateAndDecimal4"); + return (uint256(answer), uint256(rateDecimals)); } diff --git a/test/bridge-executor/optimism.integration.test.ts b/test/bridge-executor/optimism.integration.test.ts index 7c33be93..de7f5902 100644 --- a/test/bridge-executor/optimism.integration.test.ts +++ b/test/bridge-executor/optimism.integration.test.ts @@ -5,6 +5,8 @@ import { OssifiableProxy__factory, OptimismBridgeExecutor__factory, ERC20Bridged__factory, + ERC20Rebasable__factory, + TokensRateOracleStub__factory, } from "../../typechain"; import { wei } from "../../utils/wei"; import optimism from "../../utils/optimism"; @@ -212,6 +214,13 @@ async function ctxFactory() { "TT" ); + const l1TokenRebasable = await new ERC20Rebasable__factory(l1Deployer).deploy( + "Test Token Rebasable", + "TTR" + ); + + const tokensRateOracleStub = await new TokensRateOracleStub__factory(l2Deployer).deploy(); + const optAddresses = optimism.addresses(networkName); const govBridgeExecutor = testingOnDeployedContracts @@ -233,9 +242,14 @@ async function ctxFactory() { .deployment(networkName) .erc20TokenBridgeDeployScript( l1Token.address, + l1TokenRebasable.address, + tokensRateOracleStub.address, { deployer: l1Deployer, - admins: { proxy: l1Deployer.address, bridge: l1Deployer.address }, + admins: { + proxy: l1Deployer.address, + bridge: l1Deployer.address + }, }, { deployer: l2Deployer, diff --git a/test/optimism/bridging-rebase.integration.test.ts b/test/optimism/bridging-rebase.integration.test.ts new file mode 100644 index 00000000..5c72b102 --- /dev/null +++ b/test/optimism/bridging-rebase.integration.test.ts @@ -0,0 +1,654 @@ +import { assert } from "chai"; + +import env from "../../utils/env"; +import { wei } from "../../utils/wei"; +import optimism from "../../utils/optimism"; +import testing, { scenario } from "../../utils/testing"; +import hre, { ethers } from "hardhat"; +import { BigNumber, FixedNumber } from "ethers"; + +scenario("Optimism :: Bridging integration test", ctxFactory) + .after(async (ctx) => { + await ctx.l1Provider.send("evm_revert", [ctx.snapshot.l1]); + await ctx.l2Provider.send("evm_revert", [ctx.snapshot.l2]); + }) + + .step("Activate bridging on L1", async (ctx) => { + const { l1ERC20TokenBridge } = ctx; + const { l1ERC20TokenBridgeAdmin } = ctx.accounts; + + const isDepositsEnabled = await l1ERC20TokenBridge.isDepositsEnabled(); + + if (!isDepositsEnabled) { + await l1ERC20TokenBridge + .connect(l1ERC20TokenBridgeAdmin) + .enableDeposits(); + } else { + console.log("L1 deposits already enabled"); + } + + const isWithdrawalsEnabled = + await l1ERC20TokenBridge.isWithdrawalsEnabled(); + + if (!isWithdrawalsEnabled) { + await l1ERC20TokenBridge + .connect(l1ERC20TokenBridgeAdmin) + .enableWithdrawals(); + } else { + console.log("L1 withdrawals already enabled"); + } + + assert.isTrue(await l1ERC20TokenBridge.isDepositsEnabled()); + assert.isTrue(await l1ERC20TokenBridge.isWithdrawalsEnabled()); + }) + + .step("Activate bridging on L2", async (ctx) => { + const { l2ERC20TokenBridge } = ctx; + const { l2ERC20TokenBridgeAdmin } = ctx.accounts; + + const isDepositsEnabled = await l2ERC20TokenBridge.isDepositsEnabled(); + + if (!isDepositsEnabled) { + await l2ERC20TokenBridge + .connect(l2ERC20TokenBridgeAdmin) + .enableDeposits(); + } else { + console.log("L2 deposits already enabled"); + } + + const isWithdrawalsEnabled = + await l2ERC20TokenBridge.isWithdrawalsEnabled(); + + if (!isWithdrawalsEnabled) { + await l2ERC20TokenBridge + .connect(l2ERC20TokenBridgeAdmin) + .enableWithdrawals(); + } else { + console.log("L2 withdrawals already enabled"); + } + + assert.isTrue(await l2ERC20TokenBridge.isDepositsEnabled()); + assert.isTrue(await l2ERC20TokenBridge.isWithdrawalsEnabled()); + }) + + .step("L1 -> L2 deposit via depositERC20() method", async (ctx) => { + const { + l1Token, // wstETH + l1TokenRebasable, // stETH + l1ERC20TokenBridge, + l2Token, + l1CrossDomainMessenger, + l2ERC20TokenBridge, + } = ctx; + const { accountA: tokenHolderA } = ctx.accounts; + const { depositAmount: depositAmountInRebasableTokens } = ctx.common; + + const depositAmount = wei.toBigNumber(depositAmountInRebasableTokens).mul(2); + console.log("depositAmount=",depositAmount); + console.log("depositAmountInRebasableTokens=",depositAmountInRebasableTokens); + + console.log("l1Token=",l1Token.address); + console.log("l1TokenRebasable=",l1TokenRebasable.address); + console.log("l1ERC20TokenBridge=",l1ERC20TokenBridge.address); + + await l1TokenRebasable + .connect(tokenHolderA.l1Signer) + .approve(l1ERC20TokenBridge.address, depositAmountInRebasableTokens); + + const tokenHolderABalanceBefore = await l1TokenRebasable.balanceOf( + tokenHolderA.address + ); + console.log("tokenHolderA=", tokenHolderA.address); + console.log("tokenHolderABalanceBefore=", tokenHolderABalanceBefore); + + const l1ERC20TokenBridgeBalanceBefore = await l1TokenRebasable.balanceOf( + l1ERC20TokenBridge.address + ); + console.log("l1ERC20TokenBridgeBalanceBefore=", l1ERC20TokenBridgeBalanceBefore); + + const tx = await l1ERC20TokenBridge + .connect(tokenHolderA.l1Signer) + .depositERC20( + l1TokenRebasable.address, + l2Token.address, + depositAmountInRebasableTokens, + 200_000, + "0x" + ); + + await assert.emits(l1ERC20TokenBridge, tx, "ERC20DepositInitiated", [ + l1Token.address, + l2Token.address, + tokenHolderA.address, + tokenHolderA.address, + depositAmount, + "0x01", + ]); + + const l2DepositCalldata = l2ERC20TokenBridge.interface.encodeFunctionData( + "finalizeDeposit", + [ + l1Token.address, + l2Token.address, + tokenHolderA.address, + tokenHolderA.address, + depositAmount, + "0x01", + ] + ); + + const messageNonce = await l1CrossDomainMessenger.messageNonce(); + + await assert.emits(l1CrossDomainMessenger, tx, "SentMessage", [ + l2ERC20TokenBridge.address, + l1ERC20TokenBridge.address, + l2DepositCalldata, + messageNonce, + 200_000, + ]); + + + + assert.equalBN( + await l1Token.balanceOf(l1ERC20TokenBridge.address), + l1ERC20TokenBridgeBalanceBefore.add(depositAmount) + ); + + // console.log("qwe=",await l1TokenRebasable.balanceOf(tokenHolderA.address)); + // console.log("asd=",tokenHolderABalanceBefore.sub(depositAmount)); + + assert.equalBN( + await l1TokenRebasable.balanceOf(tokenHolderA.address), // stETH + tokenHolderABalanceBefore.sub(depositAmountInRebasableTokens) + ); + }) + + .step("Finalize deposit on L2", async (ctx) => { + const { + l1TokenRebasable, + l2Token, + l2TokenRebasable, + l1ERC20TokenBridge, + l2CrossDomainMessenger, + l2ERC20TokenBridge, + } = ctx; + // const { depositAmount } = ctx.common; + const { depositAmount: depositAmountInRebasableTokens } = ctx.common; + const depositAmount = wei.toBigNumber(depositAmountInRebasableTokens).mul(2); + + const { accountA: tokenHolderA, l1CrossDomainMessengerAliased } = + ctx.accounts; + + console.log("Finalize1 depositAmount=",depositAmount); + + const tokenHolderABalanceBefore = await l2TokenRebasable.balanceOf( + tokenHolderA.address + ); + console.log("tokenHolderABalanceBefore=",tokenHolderABalanceBefore); + + const l2TokenRebasableTotalSupplyBefore = await l2TokenRebasable.totalSupply(); + + console.log("test1", l1ERC20TokenBridge.address, l2ERC20TokenBridge.address, l1TokenRebasable.address, l2Token.address); + + const tx = await l2CrossDomainMessenger + .connect(l1CrossDomainMessengerAliased) + .relayMessage( + 1, + l1ERC20TokenBridge.address, + l2ERC20TokenBridge.address, + 0, + 300_000, + l2ERC20TokenBridge.interface.encodeFunctionData("finalizeDeposit", [ + l1TokenRebasable.address, + l2TokenRebasable.address, + tokenHolderA.address, + tokenHolderA.address, + depositAmount, + "0x01", + ]), + { gasLimit: 5_000_000 } + ); + + console.log("test2"); + + await assert.emits(l2ERC20TokenBridge, tx, "DepositFinalized", [ + l1TokenRebasable.address, + l2TokenRebasable.address, + tokenHolderA.address, + tokenHolderA.address, + depositAmount, + "0x", + ]); + // console.log(await l1TokenRebasable.balanceOf(tokenHolderA.address)); + + console.log(await l2TokenRebasable.balanceOf(tokenHolderA.address)); + console.log(tokenHolderABalanceBefore.add(depositAmountInRebasableTokens)); + + assert.equalBN( + await l2TokenRebasable.balanceOf(tokenHolderA.address), + tokenHolderABalanceBefore.add(depositAmountInRebasableTokens) + ); + assert.equalBN( + await l2TokenRebasable.totalSupply(), + l2TokenRebasableTotalSupplyBefore.add(depositAmountInRebasableTokens) + ); + }) + +// .step("L2 -> L1 withdrawal via withdraw()", async (ctx) => { +// const { accountA: tokenHolderA } = ctx.accounts; +// const { withdrawalAmount } = ctx.common; +// const { l1Token, l2Token, l2ERC20TokenBridge } = ctx; + +// const tokenHolderABalanceBefore = await l2Token.balanceOf( +// tokenHolderA.address +// ); +// const l2TotalSupplyBefore = await l2Token.totalSupply(); + +// const tx = await l2ERC20TokenBridge +// .connect(tokenHolderA.l2Signer) +// .withdraw(l2Token.address, withdrawalAmount, 0, "0x"); + +// await assert.emits(l2ERC20TokenBridge, tx, "WithdrawalInitiated", [ +// l1Token.address, +// l2Token.address, +// tokenHolderA.address, +// tokenHolderA.address, +// withdrawalAmount, +// "0x", +// ]); +// assert.equalBN( +// await l2Token.balanceOf(tokenHolderA.address), +// tokenHolderABalanceBefore.sub(withdrawalAmount) +// ); +// assert.equalBN( +// await l2Token.totalSupply(), +// l2TotalSupplyBefore.sub(withdrawalAmount) +// ); +// }) + +// .step("Finalize withdrawal on L1", async (ctx) => { +// const { +// l1Token, +// l1CrossDomainMessenger, +// l1ERC20TokenBridge, +// l2CrossDomainMessenger, +// l2Token, +// l2ERC20TokenBridge, +// } = ctx; +// const { accountA: tokenHolderA, l1Stranger } = ctx.accounts; +// const { withdrawalAmount } = ctx.common; + +// const tokenHolderABalanceBefore = await l1Token.balanceOf( +// tokenHolderA.address +// ); +// const l1ERC20TokenBridgeBalanceBefore = await l1Token.balanceOf( +// l1ERC20TokenBridge.address +// ); + +// await l1CrossDomainMessenger +// .connect(l1Stranger) +// .setXDomainMessageSender(l2ERC20TokenBridge.address); + +// const tx = await l1CrossDomainMessenger +// .connect(l1Stranger) +// .relayMessage( +// l1ERC20TokenBridge.address, +// l2CrossDomainMessenger.address, +// l1ERC20TokenBridge.interface.encodeFunctionData( +// "finalizeERC20Withdrawal", +// [ +// l1Token.address, +// l2Token.address, +// tokenHolderA.address, +// tokenHolderA.address, +// withdrawalAmount, +// "0x", +// ] +// ), +// 0 +// ); + +// await assert.emits(l1ERC20TokenBridge, tx, "ERC20WithdrawalFinalized", [ +// l1Token.address, +// l2Token.address, +// tokenHolderA.address, +// tokenHolderA.address, +// withdrawalAmount, +// "0x", +// ]); + +// assert.equalBN( +// await l1Token.balanceOf(l1ERC20TokenBridge.address), +// l1ERC20TokenBridgeBalanceBefore.sub(withdrawalAmount) +// ); + +// assert.equalBN( +// await l1Token.balanceOf(tokenHolderA.address), +// tokenHolderABalanceBefore.add(withdrawalAmount) +// ); +// }) + +// .step("L1 -> L2 deposit via depositERC20To()", async (ctx) => { +// const { +// l1Token, +// l2Token, +// l1ERC20TokenBridge, +// l2ERC20TokenBridge, +// l1CrossDomainMessenger, +// } = ctx; +// const { accountA: tokenHolderA, accountB: tokenHolderB } = ctx.accounts; +// const { depositAmount } = ctx.common; + +// assert.notEqual(tokenHolderA.address, tokenHolderB.address); + +// await l1Token +// .connect(tokenHolderA.l1Signer) +// .approve(l1ERC20TokenBridge.address, depositAmount); + +// const tokenHolderABalanceBefore = await l1Token.balanceOf( +// tokenHolderA.address +// ); +// const l1ERC20TokenBridgeBalanceBefore = await l1Token.balanceOf( +// l1ERC20TokenBridge.address +// ); + +// const tx = await l1ERC20TokenBridge +// .connect(tokenHolderA.l1Signer) +// .depositERC20To( +// l1Token.address, +// l2Token.address, +// tokenHolderB.address, +// depositAmount, +// 200_000, +// "0x" +// ); + +// await assert.emits(l1ERC20TokenBridge, tx, "ERC20DepositInitiated", [ +// l1Token.address, +// l2Token.address, +// tokenHolderA.address, +// tokenHolderB.address, +// depositAmount, +// "0x", +// ]); + +// const l2DepositCalldata = l2ERC20TokenBridge.interface.encodeFunctionData( +// "finalizeDeposit", +// [ +// l1Token.address, +// l2Token.address, +// tokenHolderA.address, +// tokenHolderB.address, +// depositAmount, +// "0x", +// ] +// ); + +// const messageNonce = await l1CrossDomainMessenger.messageNonce(); + +// await assert.emits(l1CrossDomainMessenger, tx, "SentMessage", [ +// l2ERC20TokenBridge.address, +// l1ERC20TokenBridge.address, +// l2DepositCalldata, +// messageNonce, +// 200_000, +// ]); + +// assert.equalBN( +// await l1Token.balanceOf(l1ERC20TokenBridge.address), +// l1ERC20TokenBridgeBalanceBefore.add(depositAmount) +// ); + +// assert.equalBN( +// await l1Token.balanceOf(tokenHolderA.address), +// tokenHolderABalanceBefore.sub(depositAmount) +// ); +// }) + +// .step("Finalize deposit on L2", async (ctx) => { +// const { +// l1Token, +// l1ERC20TokenBridge, +// l2Token, +// l2CrossDomainMessenger, +// l2ERC20TokenBridge, +// } = ctx; +// const { +// accountA: tokenHolderA, +// accountB: tokenHolderB, +// l1CrossDomainMessengerAliased, +// } = ctx.accounts; +// const { depositAmount } = ctx.common; + +// const l2TokenTotalSupplyBefore = await l2Token.totalSupply(); +// const tokenHolderBBalanceBefore = await l2Token.balanceOf( +// tokenHolderB.address +// ); + +// const tx = await l2CrossDomainMessenger +// .connect(l1CrossDomainMessengerAliased) +// .relayMessage( +// 1, +// l1ERC20TokenBridge.address, +// l2ERC20TokenBridge.address, +// 0, +// 300_000, +// l2ERC20TokenBridge.interface.encodeFunctionData("finalizeDeposit", [ +// l1Token.address, +// l2Token.address, +// tokenHolderA.address, +// tokenHolderB.address, +// depositAmount, +// "0x", +// ]), +// { gasLimit: 5_000_000 } +// ); + +// await assert.emits(l2ERC20TokenBridge, tx, "DepositFinalized", [ +// l1Token.address, +// l2Token.address, +// tokenHolderA.address, +// tokenHolderB.address, +// depositAmount, +// "0x", +// ]); + +// assert.equalBN( +// await l2Token.totalSupply(), +// l2TokenTotalSupplyBefore.add(depositAmount) +// ); +// assert.equalBN( +// await l2Token.balanceOf(tokenHolderB.address), +// tokenHolderBBalanceBefore.add(depositAmount) +// ); +// }) + +// .step("L2 -> L1 withdrawal via withdrawTo()", async (ctx) => { +// const { l1Token, l2Token, l2ERC20TokenBridge } = ctx; +// const { accountA: tokenHolderA, accountB: tokenHolderB } = ctx.accounts; +// const { withdrawalAmount } = ctx.common; + +// const tokenHolderBBalanceBefore = await l2Token.balanceOf( +// tokenHolderB.address +// ); +// const l2TotalSupplyBefore = await l2Token.totalSupply(); + +// const tx = await l2ERC20TokenBridge +// .connect(tokenHolderB.l2Signer) +// .withdrawTo( +// l2Token.address, +// tokenHolderA.address, +// withdrawalAmount, +// 0, +// "0x" +// ); + +// await assert.emits(l2ERC20TokenBridge, tx, "WithdrawalInitiated", [ +// l1Token.address, +// l2Token.address, +// tokenHolderB.address, +// tokenHolderA.address, +// withdrawalAmount, +// "0x", +// ]); + +// assert.equalBN( +// await l2Token.balanceOf(tokenHolderB.address), +// tokenHolderBBalanceBefore.sub(withdrawalAmount) +// ); + +// assert.equalBN( +// await l2Token.totalSupply(), +// l2TotalSupplyBefore.sub(withdrawalAmount) +// ); +// }) + +// .step("Finalize withdrawal on L1", async (ctx) => { +// const { +// l1Token, +// l1CrossDomainMessenger, +// l1ERC20TokenBridge, +// l2CrossDomainMessenger, +// l2Token, +// l2ERC20TokenBridge, +// } = ctx; +// const { +// accountA: tokenHolderA, +// accountB: tokenHolderB, +// l1Stranger, +// } = ctx.accounts; +// const { withdrawalAmount } = ctx.common; + +// const tokenHolderABalanceBefore = await l1Token.balanceOf( +// tokenHolderA.address +// ); +// const l1ERC20TokenBridgeBalanceBefore = await l1Token.balanceOf( +// l1ERC20TokenBridge.address +// ); + +// await l1CrossDomainMessenger +// .connect(l1Stranger) +// .setXDomainMessageSender(l2ERC20TokenBridge.address); + +// const tx = await l1CrossDomainMessenger +// .connect(l1Stranger) +// .relayMessage( +// l1ERC20TokenBridge.address, +// l2CrossDomainMessenger.address, +// l1ERC20TokenBridge.interface.encodeFunctionData( +// "finalizeERC20Withdrawal", +// [ +// l1Token.address, +// l2Token.address, +// tokenHolderB.address, +// tokenHolderA.address, +// withdrawalAmount, +// "0x", +// ] +// ), +// 0 +// ); + +// await assert.emits(l1ERC20TokenBridge, tx, "ERC20WithdrawalFinalized", [ +// l1Token.address, +// l2Token.address, +// tokenHolderB.address, +// tokenHolderA.address, +// withdrawalAmount, +// "0x", +// ]); + +// assert.equalBN( +// await l1Token.balanceOf(l1ERC20TokenBridge.address), +// l1ERC20TokenBridgeBalanceBefore.sub(withdrawalAmount) +// ); + +// assert.equalBN( +// await l1Token.balanceOf(tokenHolderA.address), +// tokenHolderABalanceBefore.add(withdrawalAmount) +// ); +// }) + + .run(); + +async function ctxFactory() { + const networkName = env.network("TESTING_OPT_NETWORK", "mainnet"); + console.log("networkName=",networkName); + + const { + l1Provider, + l2Provider, + l1ERC20TokenBridgeAdmin, + l2ERC20TokenBridgeAdmin, + ...contracts + } = await optimism.testing(networkName).getIntegrationTestSetup(); + + const l1Snapshot = await l1Provider.send("evm_snapshot", []); + const l2Snapshot = await l2Provider.send("evm_snapshot", []); + + await optimism.testing(networkName).stubL1CrossChainMessengerContract(); + + const accountA = testing.accounts.accountA(l1Provider, l2Provider); + const accountB = testing.accounts.accountB(l1Provider, l2Provider); + + const depositAmount = wei`0.15 ether`; + const withdrawalAmount = wei`0.05 ether`; + + await testing.setBalance( + await contracts.l1TokensHolder.getAddress(), + wei.toBigNumber(wei`1 ether`), + l1Provider + ); + + await testing.setBalance( + await l1ERC20TokenBridgeAdmin.getAddress(), + wei.toBigNumber(wei`1 ether`), + l1Provider + ); + + await testing.setBalance( + await l2ERC20TokenBridgeAdmin.getAddress(), + wei.toBigNumber(wei`1 ether`), + l2Provider + ); + + await contracts.l1TokenRebasable + .connect(contracts.l1TokensHolder) + .transfer(accountA.l1Signer.address, wei.toBigNumber(depositAmount).mul(2)); + + const l1CrossDomainMessengerAliased = await testing.impersonate( + testing.accounts.applyL1ToL2Alias(contracts.l1CrossDomainMessenger.address), + l2Provider + ); + + console.log("l1CrossDomainMessengerAliased=",l1CrossDomainMessengerAliased); + console.log("contracts.l1CrossDomainMessenger.address=",contracts.l1CrossDomainMessenger.address); + + await testing.setBalance( + await l1CrossDomainMessengerAliased.getAddress(), + wei.toBigNumber(wei`1 ether`), + l2Provider + ); + + return { + l1Provider, + l2Provider, + ...contracts, + accounts: { + accountA, + accountB, + l1Stranger: testing.accounts.stranger(l1Provider), + l1ERC20TokenBridgeAdmin, + l2ERC20TokenBridgeAdmin, + l1CrossDomainMessengerAliased, + }, + common: { + depositAmount, + withdrawalAmount, + }, + snapshot: { + l1: l1Snapshot, + l2: l2Snapshot, + }, + }; +} diff --git a/utils/optimism/deployment.ts b/utils/optimism/deployment.ts index c3b6aa3a..39ce1f7f 100644 --- a/utils/optimism/deployment.ts +++ b/utils/optimism/deployment.ts @@ -2,6 +2,7 @@ import { assert } from "chai"; import { Overrides, Wallet } from "ethers"; import { ERC20Bridged__factory, + ERC20Rebasable__factory, IERC20Metadata__factory, L1ERC20TokenBridge__factory, L2ERC20TokenBridge__factory, @@ -20,6 +21,7 @@ interface OptL1DeployScriptParams { interface OptL2DeployScriptParams extends OptL1DeployScriptParams { l2Token?: { name?: string; symbol?: string }; + l2TokenRebasable?: { name?: string; symbol?: string }; } interface OptDeploymentOptions extends CommonOptions { @@ -35,21 +37,26 @@ export default function deployment( return { async erc20TokenBridgeDeployScript( l1Token: string, + l1TokenRebasable: string, + tokensRateOracleStub: string, l1Params: OptL1DeployScriptParams, - l2Params: OptL2DeployScriptParams + l2Params: OptL2DeployScriptParams, ) { + const [ expectedL1TokenBridgeImplAddress, expectedL1TokenBridgeProxyAddress, ] = await network.predictAddresses(l1Params.deployer, 2); - + const [ expectedL2TokenImplAddress, expectedL2TokenProxyAddress, + expectedL2TokenRebasableImplAddress, + expectedL2TokenRebasableProxyAddress, expectedL2TokenBridgeImplAddress, expectedL2TokenBridgeProxyAddress, - ] = await network.predictAddresses(l2Params.deployer, 4); - + ] = await network.predictAddresses(l2Params.deployer, 6); + const l1DeployScript = new DeployScript( l1Params.deployer, options?.logger @@ -60,7 +67,9 @@ export default function deployment( optAddresses.L1CrossDomainMessenger, expectedL2TokenBridgeProxyAddress, l1Token, + l1TokenRebasable, expectedL2TokenProxyAddress, + expectedL2TokenRebasableProxyAddress, options?.overrides, ], afterDeploy: (c) => @@ -86,10 +95,16 @@ export default function deployment( l1Params.deployer ); - const [decimals, l2TokenName, l2TokenSymbol] = await Promise.all([ + const l1TokenRebasableInfo = IERC20Metadata__factory.connect( + l1TokenRebasable, + l1Params.deployer + ); + const [decimals, l2TokenName, l2TokenSymbol, l2TokenRebasableName, l2TokenRebasableSymbol] = await Promise.all([ l1TokenInfo.decimals(), l2Params.l2Token?.name ?? l1TokenInfo.name(), l2Params.l2Token?.symbol ?? l1TokenInfo.symbol(), + l2Params.l2TokenRebasable?.name ?? l1TokenRebasableInfo.name(), + l2Params.l2TokenRebasable?.symbol ?? l1TokenRebasableInfo.symbol(), ]); const l2DeployScript = new DeployScript( @@ -122,13 +137,43 @@ export default function deployment( afterDeploy: (c) => assert.equal(c.address, expectedL2TokenProxyAddress), }) + .addStep({ + factory: ERC20Rebasable__factory, + args: [ + expectedL2TokenProxyAddress, + tokensRateOracleStub, + l2TokenRebasableName, + l2TokenRebasableSymbol, + decimals, + options?.overrides, + ], + afterDeploy: (c) => + assert.equal(c.address, expectedL2TokenRebasableImplAddress), + }) + .addStep({ + factory: OssifiableProxy__factory, + args: [ + expectedL2TokenRebasableImplAddress, + l2Params.admins.proxy, + ERC20Rebasable__factory.createInterface().encodeFunctionData( + "initialize", + [l2TokenRebasableName, l2TokenRebasableSymbol] + ), + options?.overrides, + ], + afterDeploy: (c) => + assert.equal(c.address, expectedL2TokenRebasableProxyAddress), + }) + .addStep({ factory: L2ERC20TokenBridge__factory, args: [ optAddresses.L2CrossDomainMessenger, expectedL1TokenBridgeProxyAddress, l1Token, + l1TokenRebasable, expectedL2TokenProxyAddress, + expectedL2TokenRebasableProxyAddress, options?.overrides, ], afterDeploy: (c) => diff --git a/utils/optimism/testing.ts b/utils/optimism/testing.ts index febb4de7..bb030fcf 100644 --- a/utils/optimism/testing.ts +++ b/utils/optimism/testing.ts @@ -1,4 +1,4 @@ -import { Signer } from "ethers"; +import { BigNumber, Signer } from "ethers"; import { JsonRpcProvider } from "@ethersproject/providers"; import { @@ -9,9 +9,12 @@ import { L2ERC20TokenBridge, ERC20Bridged__factory, ERC20BridgedStub__factory, + ERC20WrapableStub__factory, + TokensRateOracleStub__factory, L1ERC20TokenBridge__factory, L2ERC20TokenBridge__factory, CrossDomainMessengerStub__factory, + ERC20Rebasable__factory, } from "../../typechain"; import addresses from "./addresses"; import contracts from "./contracts"; @@ -157,9 +160,14 @@ async function loadDeployedBridges( testingUtils.env.OPT_L1_TOKEN(), l1SignerOrProvider ), + l1TokenRebasable: IERC20__factory.connect( + testingUtils.env.OPT_L1_TOKEN(), + l1SignerOrProvider + ), ...connectBridgeContracts( { l2Token: testingUtils.env.OPT_L2_TOKEN(), + l2TokenRebasable: testingUtils.env.OPT_L2_TOKEN(), // fix l1ERC20TokenBridge: testingUtils.env.OPT_L1_ERC20_TOKEN_BRIDGE(), l2ERC20TokenBridge: testingUtils.env.OPT_L2_ERC20_TOKEN_BRIDGE(), }, @@ -177,15 +185,28 @@ async function deployTestBridge( const ethDeployer = testingUtils.accounts.deployer(ethProvider); const optDeployer = testingUtils.accounts.deployer(optProvider); - const l1Token = await new ERC20BridgedStub__factory(ethDeployer).deploy( + const l1TokenRebasable = await new ERC20BridgedStub__factory(ethDeployer).deploy( + "Test Token Rebasable", + "TTR" + ); + + const l1Token = await new ERC20WrapableStub__factory(ethDeployer).deploy( + l1TokenRebasable.address, "Test Token", "TT" ); + const tokensRateOracleStub = await new TokensRateOracleStub__factory(optDeployer).deploy(); + await tokensRateOracleStub.setLatestRoundDataAnswer(BigNumber.from("2000000000000000000")); + await tokensRateOracleStub.setDecimals(18); + await tokensRateOracleStub.setUpdatedAt(100); + const [ethDeployScript, optDeployScript] = await deployment( networkName ).erc20TokenBridgeDeployScript( l1Token.address, + l1TokenRebasable.address, + tokensRateOracleStub.address, { deployer: ethDeployer, admins: { proxy: ethDeployer.address, bridge: ethDeployer.address }, @@ -205,7 +226,7 @@ async function deployTestBridge( ethDeployer ); - const l2ERC20TokenBridgeProxyDeployStepIndex = 3; + const l2ERC20TokenBridgeProxyDeployStepIndex = 5; const l2BridgingManagement = new BridgingManagement( optDeployScript.getContractAddress(l2ERC20TokenBridgeProxyDeployStepIndex), optDeployer @@ -225,11 +246,13 @@ async function deployTestBridge( return { l1Token: l1Token.connect(ethProvider), + l1TokenRebasable: l1TokenRebasable.connect(ethProvider), ...connectBridgeContracts( { l2Token: optDeployScript.getContractAddress(1), + l2TokenRebasable: optDeployScript.getContractAddress(3), l1ERC20TokenBridge: ethDeployScript.getContractAddress(1), - l2ERC20TokenBridge: optDeployScript.getContractAddress(3), + l2ERC20TokenBridge: optDeployScript.getContractAddress(5), }, ethProvider, optProvider @@ -240,6 +263,7 @@ async function deployTestBridge( function connectBridgeContracts( addresses: { l2Token: string; + l2TokenRebasable: string; l1ERC20TokenBridge: string; l2ERC20TokenBridge: string; }, @@ -258,8 +282,13 @@ function connectBridgeContracts( addresses.l2Token, optSignerOrProvider ); + const l2TokenRebasable = ERC20Rebasable__factory.connect( + addresses.l2TokenRebasable, + optSignerOrProvider + ); return { l2Token, + l2TokenRebasable, l1ERC20TokenBridge, l2ERC20TokenBridge, }; From c3bc0db60c2bba7eb6876ca68c425968a61c004a Mon Sep 17 00:00:00 2001 From: Anton Kovalchuk Date: Mon, 6 Nov 2023 00:40:15 +0100 Subject: [PATCH 006/148] add withdraw flow for rebasable token on optimism --- contracts/BridgeableTokens.sol | 24 +- .../arbitrum/InterchainERC20TokenGateway.sol | 14 +- contracts/arbitrum/L1ERC20TokenGateway.sol | 4 +- contracts/arbitrum/L2ERC20TokenGateway.sol | 17 +- contracts/optimism/L1ERC20TokenBridge.sol | 52 ++-- contracts/optimism/L2ERC20TokenBridge.sol | 48 ++-- .../optimism/interfaces/IL2ERC20Bridge.sol | 1 + contracts/stubs/ERC20WrapableStub.sol | 14 +- .../bridging-rebase.integration.test.ts | 224 +++++++++--------- 9 files changed, 204 insertions(+), 194 deletions(-) diff --git a/contracts/BridgeableTokens.sol b/contracts/BridgeableTokens.sol index 36a636b9..a0e85e50 100644 --- a/contracts/BridgeableTokens.sol +++ b/contracts/BridgeableTokens.sol @@ -6,30 +6,32 @@ pragma solidity 0.8.10; /// @author psirex /// @notice Contains the logic for validation of tokens used in the bridging process contract BridgeableTokens { - /// @notice Address of the bridged token in the L1 chain - address public immutable l1Token; + /// @notice Address of the bridged non rebasable token in the L1 chain + address public immutable l1TokenNonRebasable; /// @notice Address of the bridged rebasable token in the L1 chain address public immutable l1TokenRebasable; - /// @notice Address of the token minted on the L2 chain when token bridged - address public immutable l2Token; + /// @notice Address of the non rebasable token minted on the L2 chain when token bridged + address public immutable l2TokenNonRebasable; /// @notice Address of the rebasable token minted on the L2 chain when token bridged address public immutable l2TokenRebasable; - /// @param l1Token_ Address of the bridged token in the L1 chain - /// @param l2Token_ Address of the token minted on the L2 chain when token bridged - constructor(address l1Token_, address l1TokenRebasable_, address l2Token_, address l2TokenRebasable_) { - l1Token = l1Token_; + /// @param l1TokenNonRebasable_ Address of the bridged non rebasable token in the L1 chain + /// @param l1TokenRebasable_ Address of the bridged rebasable token in the L1 chain + /// @param l2TokenNonRebasable_ Address of the non rebasable token minted on the L2 chain when token bridged + /// @param l2TokenRebasable_ Address of the rebasable token minted on the L2 chain when token bridged + constructor(address l1TokenNonRebasable_, address l1TokenRebasable_, address l2TokenNonRebasable_, address l2TokenRebasable_) { + l1TokenNonRebasable = l1TokenNonRebasable_; l1TokenRebasable = l1TokenRebasable_; - l2Token = l2Token_; + l2TokenNonRebasable = l2TokenNonRebasable_; l2TokenRebasable = l2TokenRebasable_; } /// @dev Validates that passed l1Token_ is supported by the bridge modifier onlySupportedL1Token(address l1Token_) { - if (l1Token_ != l1Token && l1Token_ != l1TokenRebasable) { + if (l1Token_ != l1TokenNonRebasable && l1Token_ != l1TokenRebasable) { revert ErrorUnsupportedL1Token(); } _; @@ -37,7 +39,7 @@ contract BridgeableTokens { /// @dev Validates that passed l2Token_ is supported by the bridge modifier onlySupportedL2Token(address l2Token_) { - if (l2Token_ != l2Token && l2Token_ != l2TokenRebasable) { + if (l2Token_ != l2TokenNonRebasable && l2Token_ != l2TokenRebasable) { revert ErrorUnsupportedL2Token(); } _; diff --git a/contracts/arbitrum/InterchainERC20TokenGateway.sol b/contracts/arbitrum/InterchainERC20TokenGateway.sol index f75334d1..78f9be68 100644 --- a/contracts/arbitrum/InterchainERC20TokenGateway.sol +++ b/contracts/arbitrum/InterchainERC20TokenGateway.sol @@ -24,18 +24,18 @@ abstract contract InterchainERC20TokenGateway is /// @param router_ Address of the router in the corresponding chain /// @param counterpartGateway_ Address of the counterpart gateway used in the bridging process - /// @param l1Token_ Address of the bridged token in the Ethereum chain + /// @param l1TokenNonRebasable Address of the bridged token in the Ethereum chain /// @param l1TokenRebasable_ Address of the bridged token in the Ethereum chain - /// @param l2Token_ Address of the token minted on the Arbitrum chain when token bridged + /// @param l2TokenNonRebasable_ Address of the token minted on the Arbitrum chain when token bridged /// @param l2TokenRebasable_ Address of the token minted on the Arbitrum chain when token bridged constructor( address router_, address counterpartGateway_, - address l1Token_, + address l1TokenNonRebasable, address l1TokenRebasable_, - address l2Token_, + address l2TokenNonRebasable_, address l2TokenRebasable_ - ) BridgeableTokens(l1Token_, l1TokenRebasable_, l2Token_, l2TokenRebasable_) { + ) BridgeableTokens(l1TokenNonRebasable, l1TokenRebasable_, l2TokenNonRebasable_, l2TokenRebasable_) { router = router_; counterpartGateway = counterpartGateway_; } @@ -48,8 +48,8 @@ abstract contract InterchainERC20TokenGateway is view returns (address) { - if (l1Token_ == l1Token) { - return l2Token; + if (l1Token_ == l1TokenRebasable) { + return l2TokenNonRebasable; } return address(0); } diff --git a/contracts/arbitrum/L1ERC20TokenGateway.sol b/contracts/arbitrum/L1ERC20TokenGateway.sol index 7b91b6a2..85161aec 100644 --- a/contracts/arbitrum/L1ERC20TokenGateway.sol +++ b/contracts/arbitrum/L1ERC20TokenGateway.sol @@ -82,7 +82,7 @@ contract L1ERC20TokenGateway is }) ); - emit DepositInitiated(l1Token, from, to_, retryableTicketId, amount_); + emit DepositInitiated(l1TokenNonRebasable, from, to_, retryableTicketId, amount_); return abi.encode(retryableTicketId); } @@ -117,7 +117,7 @@ contract L1ERC20TokenGateway is sendCrossDomainMessage( from_, counterpartGateway, - getOutboundCalldata(l1Token, from_, to_, amount_, ""), + getOutboundCalldata(l1TokenNonRebasable, from_, to_, amount_, ""), messageOptions ); } diff --git a/contracts/arbitrum/L2ERC20TokenGateway.sol b/contracts/arbitrum/L2ERC20TokenGateway.sol index d65fd3ac..0c6df5ac 100644 --- a/contracts/arbitrum/L2ERC20TokenGateway.sol +++ b/contracts/arbitrum/L2ERC20TokenGateway.sol @@ -21,24 +21,25 @@ contract L2ERC20TokenGateway is /// @param arbSys_ Address of the Arbitrum’s ArbSys contract in the L2 chain /// @param router_ Address of the router in the L2 chain /// @param counterpartGateway_ Address of the counterpart L1 gateway - /// @param l1Token_ Address of the bridged token in the L1 chain + /// @param l1TokenNonRebasable_ Address of the bridged token in the L1 chain /// @param l1TokenRebasable_ Address of the bridged token in the L1 chain - /// @param l2Token_ Address of the token minted on the Arbitrum chain when token bridged + /// @param l2TokenNonRebasable_ Address of the token minted on the Arbitrum chain when token bridged + /// @param l2TokenRebasable_ Address of the token minted on the Arbitrum chain when token bridged constructor( address arbSys_, address router_, address counterpartGateway_, - address l1Token_, + address l1TokenNonRebasable_, address l1TokenRebasable_, - address l2Token_, + address l2TokenNonRebasable_, address l2TokenRebasable_ ) InterchainERC20TokenGateway( router_, counterpartGateway_, - l1Token_, + l1TokenNonRebasable_, l1TokenRebasable_, - l2Token_, + l2TokenNonRebasable_, l2TokenRebasable_ ) L2CrossDomainEnabled(arbSys_) @@ -60,7 +61,7 @@ contract L2ERC20TokenGateway is { address from = L2OutboundDataParser.decode(router, data_); - IERC20Bridged(l2Token).bridgeBurn(from, amount_); + IERC20Bridged(l2TokenNonRebasable).bridgeBurn(from, amount_); uint256 id = sendCrossDomainMessage( from, @@ -88,7 +89,7 @@ contract L2ERC20TokenGateway is onlySupportedL1Token(l1Token_) onlyFromCrossDomainAccount(counterpartGateway) { - IERC20Bridged(l2Token).bridgeMint(to_, amount_); + IERC20Bridged(l2TokenNonRebasable).bridgeMint(to_, amount_); emit DepositFinalized(l1Token_, from_, to_, amount_); } diff --git a/contracts/optimism/L1ERC20TokenBridge.sol b/contracts/optimism/L1ERC20TokenBridge.sol index d543a961..93b9535f 100644 --- a/contracts/optimism/L1ERC20TokenBridge.sol +++ b/contracts/optimism/L1ERC20TokenBridge.sol @@ -34,23 +34,24 @@ contract L1ERC20TokenBridge is /// @param messenger_ L1 messenger address being used for cross-chain communications /// @param l2TokenBridge_ Address of the corresponding L2 bridge - /// @param l1Token_ Address of the bridged token in the L1 chain + /// @param l1TokenNonRebasable_ Address of the bridged token in the L1 chain /// @param l1TokenRebasable_ Address of the bridged token in the L1 chain - /// @param l2Token_ Address of the token minted on the L2 chain when token bridged + /// @param l2TokenNonRebasable_ Address of the token minted on the L2 chain when token bridged + /// @param l2TokenRebasable_ Address of the token minted on the L2 chain when token bridged constructor( address messenger_, address l2TokenBridge_, - address l1Token_, // wstETH - address l1TokenRebasable_, // stETH - address l2Token_, + address l1TokenNonRebasable_, + address l1TokenRebasable_, + address l2TokenNonRebasable_, address l2TokenRebasable_ - ) CrossDomainEnabled(messenger_) BridgeableTokens(l1Token_, l1TokenRebasable_, l2Token_, l2TokenRebasable_) { + ) CrossDomainEnabled(messenger_) BridgeableTokens(l1TokenNonRebasable_, l1TokenRebasable_, l2TokenNonRebasable_, l2TokenRebasable_) { l2TokenBridge = l2TokenBridge_; } /// @inheritdoc IL1ERC20Bridge function depositERC20( - address l1Token_, // stETH or wstETH + address l1Token_, address l2Token_, uint256 amount_, uint32 l2Gas_, @@ -66,19 +67,14 @@ contract L1ERC20TokenBridge is } if(l1Token_ == l1TokenRebasable) { - bytes memory data = bytes.concat(hex'01', data_); // add rate + bytes memory data = bytes.concat(hex'01', data_); IERC20(l1TokenRebasable).safeTransferFrom(msg.sender, address(this), amount_); - IERC20(l1TokenRebasable).approve(l1Token, amount_); - uint256 wstETHAmount = IERC20Wrapable(l1Token).wrap(amount_); - console.log("wstETHAmount=",wstETHAmount); - - uint256 balanceOfl1Token = IERC20(l1Token).balanceOf(address(this)); - console.log("balanceOfl1Token=",balanceOfl1Token); - - _initiateERC20Deposit(msg.sender, msg.sender, wstETHAmount, l2Gas_, data); + IERC20(l1TokenRebasable).approve(l1TokenNonRebasable, amount_); + uint256 wstETHAmount = IERC20Wrapable(l1TokenNonRebasable).wrap(amount_); + _initiateERC20Deposit(l1Token_, l2Token_, msg.sender, msg.sender, wstETHAmount, l2Gas_, data); } else { - IERC20(l1Token).safeTransferFrom(msg.sender, address(this), amount_); - _initiateERC20Deposit(msg.sender, msg.sender, amount_, l2Gas_, data_); + IERC20(l1TokenNonRebasable).safeTransferFrom(msg.sender, address(this), amount_); + _initiateERC20Deposit(l1Token_, l2Token_, msg.sender, msg.sender, amount_, l2Gas_, data_); } } @@ -97,7 +93,7 @@ contract L1ERC20TokenBridge is onlySupportedL1Token(l1Token_) onlySupportedL2Token(l2Token_) { - _initiateERC20Deposit(msg.sender, to_, amount_, l2Gas_, data_); + _initiateERC20Deposit(l1Token_, l2Token_, msg.sender, to_, amount_, l2Gas_, data_); } /// @inheritdoc IL1ERC20Bridge @@ -115,7 +111,12 @@ contract L1ERC20TokenBridge is onlySupportedL2Token(l2Token_) onlyFromCrossDomainAccount(l2TokenBridge) { - IERC20(l1Token_).safeTransfer(to_, amount_); + if (data_.length > 0 && data_[0] == hex'01') { + uint256 stETHAmount = IERC20Wrapable(l1TokenNonRebasable).unwrap(amount_); + IERC20(l1TokenRebasable).safeTransfer(to_, stETHAmount); + } else { + IERC20(l1Token_).safeTransfer(to_, amount_); + } emit ERC20WithdrawalFinalized( l1Token_, @@ -137,6 +138,8 @@ contract L1ERC20TokenBridge is /// solely as a convenience for external contracts. Aside from enforcing a maximum /// length, these contracts provide no guarantees about its content. function _initiateERC20Deposit( + address l1Token_, + address l2Token_, address from_, address to_, uint256 amount_, @@ -146,20 +149,19 @@ contract L1ERC20TokenBridge is bytes memory message = abi.encodeWithSelector( IL2ERC20Bridge.finalizeDeposit.selector, - l1Token, - l2Token, + l1Token_, + l2Token_, from_, to_, amount_, data_ ); - console.log("SEND l2TokenBridge=%s l1Token=%s l2Token=%s", l2TokenBridge, l1Token, l2Token); sendCrossDomainMessage(l2TokenBridge, l2Gas_, message); emit ERC20DepositInitiated( - l1Token, - l2Token, + l1Token_, + l2Token_, from_, to_, amount_, diff --git a/contracts/optimism/L2ERC20TokenBridge.sol b/contracts/optimism/L2ERC20TokenBridge.sol index 32c72053..a8f9df01 100644 --- a/contracts/optimism/L2ERC20TokenBridge.sol +++ b/contracts/optimism/L2ERC20TokenBridge.sol @@ -7,6 +7,9 @@ import {IL1ERC20Bridge} from "./interfaces/IL1ERC20Bridge.sol"; import {IL2ERC20Bridge} from "./interfaces/IL2ERC20Bridge.sol"; import {IERC20Bridged} from "../token/interfaces/IERC20Bridged.sol"; import {ERC20Rebasable} from "../token/ERC20Rebasable.sol"; +import {IERC20} from "@openzeppelin/contracts/token/ERC20/IERC20.sol"; +import {IERC20Wrapable} from "../token/interfaces/IERC20Wrapable.sol"; +import {SafeERC20} from "@openzeppelin/contracts/token/ERC20/utils/SafeERC20.sol"; import {BridgingManager} from "../BridgingManager.sol"; import {BridgeableTokens} from "../BridgeableTokens.sol"; @@ -26,32 +29,45 @@ contract L2ERC20TokenBridge is BridgeableTokens, CrossDomainEnabled { + using SafeERC20 for IERC20; + /// @inheritdoc IL2ERC20Bridge address public immutable l1TokenBridge; /// @param messenger_ L2 messenger address being used for cross-chain communications /// @param l1TokenBridge_ Address of the corresponding L1 bridge - /// @param l1Token_ Address of the bridged token in the L1 chain - /// @param l2Token_ Address of the token minted on the L2 chain when token bridged + /// @param l1TokenNonRebasable_ Address of the bridged token in the L1 chain + /// @param l1TokenRebasable_ Address of the bridged token in the L1 chain + /// @param l2TokenNonRebasable_ Address of the token minted on the L2 chain when token bridged + /// @param l2TokenRebasable_ Address of the token minted on the L2 chain when token bridged constructor( address messenger_, address l1TokenBridge_, - address l1Token_, + address l1TokenNonRebasable_, address l1TokenRebasable_, - address l2Token_, + address l2TokenNonRebasable_, address l2TokenRebasable_ - ) CrossDomainEnabled(messenger_) BridgeableTokens(l1Token_, l1TokenRebasable_, l2Token_, l2TokenRebasable_) { + ) CrossDomainEnabled(messenger_) BridgeableTokens(l1TokenNonRebasable_, l1TokenRebasable_, l2TokenNonRebasable_, l2TokenRebasable_) { l1TokenBridge = l1TokenBridge_; } /// @inheritdoc IL2ERC20Bridge function withdraw( + address l1Token_, address l2Token_, uint256 amount_, uint32 l1Gas_, bytes calldata data_ ) external whenWithdrawalsEnabled onlySupportedL2Token(l2Token_) { - _initiateWithdrawal(msg.sender, msg.sender, amount_, l1Gas_, data_); + if(l2Token_ == l2TokenRebasable) { + bytes memory data = bytes.concat(hex'01', data_); + uint256 shares = ERC20Rebasable(l2TokenRebasable).getSharesByTokens(amount_); + ERC20Rebasable(l2TokenRebasable).burnShares(msg.sender, shares); + _initiateWithdrawal(l1Token_, l2Token_, msg.sender, msg.sender, shares, l1Gas_, data); + } else { + IERC20Bridged(l2TokenNonRebasable).bridgeBurn(msg.sender, amount_); + _initiateWithdrawal(l1Token_, l2Token_, msg.sender, msg.sender, amount_, l1Gas_, data_); + } } /// @inheritdoc IL2ERC20Bridge @@ -62,7 +78,7 @@ contract L2ERC20TokenBridge is uint32 l1Gas_, bytes calldata data_ ) external whenWithdrawalsEnabled onlySupportedL2Token(l2Token_) { - _initiateWithdrawal(msg.sender, to_, amount_, l1Gas_, data_); + _initiateWithdrawal(l1TokenNonRebasable, l2Token_, msg.sender, to_, amount_, l1Gas_, data_); } /// @inheritdoc IL2ERC20Bridge @@ -81,17 +97,10 @@ contract L2ERC20TokenBridge is onlyFromCrossDomainAccount(l1TokenBridge) { if (data_.length > 0 && data_[0] == hex'01') { - console.log("finalizeDeposit1 l2TokenRebasable balanceBefore=",ERC20Rebasable(l2TokenRebasable).balanceOf(to_)); - ERC20Rebasable(l2TokenRebasable).mintShares(to_, amount_); - - console.log("finalizeDeposit1 l2TokenRebasable balanceafter==",ERC20Rebasable(l2TokenRebasable).balanceOf(to_)); - bytes memory data = data_[1:]; emit DepositFinalized(l1Token_, l2Token_, from_, to_, amount_, data); } else { - console.log("finalizeDeposit2 data=", data_.length); - IERC20Bridged(l2Token_).bridgeMint(to_, amount_); emit DepositFinalized(l1Token_, l2Token_, from_, to_, amount_, data_); } @@ -107,18 +116,19 @@ contract L2ERC20TokenBridge is /// solely as a convenience for external contracts. Aside from enforcing a maximum /// length, these contracts provide no guarantees about its content function _initiateWithdrawal( + address l1Token_, + address l2Token_, address from_, address to_, uint256 amount_, uint32 l1Gas_, - bytes calldata data_ + bytes memory data_ ) internal { - IERC20Bridged(l2Token).bridgeBurn(from_, amount_); bytes memory message = abi.encodeWithSelector( IL1ERC20Bridge.finalizeERC20Withdrawal.selector, - l1Token, - l2Token, + l1Token_, + l2Token_, from_, to_, amount_, @@ -127,6 +137,6 @@ contract L2ERC20TokenBridge is sendCrossDomainMessage(l1TokenBridge, l1Gas_, message); - emit WithdrawalInitiated(l1Token, l2Token, from_, to_, amount_, data_); + emit WithdrawalInitiated(l1Token_, l2Token_, from_, to_, amount_, data_); } } diff --git a/contracts/optimism/interfaces/IL2ERC20Bridge.sol b/contracts/optimism/interfaces/IL2ERC20Bridge.sol index 448dfb8b..04c27baf 100644 --- a/contracts/optimism/interfaces/IL2ERC20Bridge.sol +++ b/contracts/optimism/interfaces/IL2ERC20Bridge.sol @@ -46,6 +46,7 @@ interface IL2ERC20Bridge { /// solely as a convenience for external contracts. Aside from enforcing a maximum /// length, these contracts provide no guarantees about its content. function withdraw( + address l1Token_, address l2Token_, uint256 amount_, uint32 l1Gas_, diff --git a/contracts/stubs/ERC20WrapableStub.sol b/contracts/stubs/ERC20WrapableStub.sol index fe49ff83..2edf81d1 100644 --- a/contracts/stubs/ERC20WrapableStub.sol +++ b/contracts/stubs/ERC20WrapableStub.sol @@ -32,12 +32,6 @@ contract ERC20WrapableStub is IERC20Wrapable, ERC20 { uint256 wstETHAmount = (_stETHAmount * tokensRate) / (10 ** uint256(decimals())); - - console.log("wrap msg.sender=",msg.sender); - console.log("wrap address(this)=",address(this)); - console.log("wrap _stETHAmount=",_stETHAmount); - console.log("wrap wstETHAmount=",wstETHAmount); - _mint(msg.sender, wstETHAmount); stETH.transferFrom(msg.sender, address(this), _stETHAmount); @@ -47,10 +41,10 @@ contract ERC20WrapableStub is IERC20Wrapable, ERC20 { function unwrap(uint256 _wstETHAmount) external returns (uint256) { require(_wstETHAmount > 0, "wstETH: zero amount unwrap not allowed"); - // uint256 stETHAmount = (_wstETHAmount * (10 ** uint256(decimals()))) / tokensRate; - // _burn(msg.sender, _wstETHAmount); - // stETH.transfer(msg.sender, stETHAmount); + uint256 stETHAmount = (_wstETHAmount * (10 ** uint256(decimals()))) / tokensRate; + _burn(msg.sender, _wstETHAmount); + stETH.transfer(msg.sender, stETHAmount); - return 0; //stETHAmount; + return stETHAmount; } } diff --git a/test/optimism/bridging-rebase.integration.test.ts b/test/optimism/bridging-rebase.integration.test.ts index 5c72b102..71193d4c 100644 --- a/test/optimism/bridging-rebase.integration.test.ts +++ b/test/optimism/bridging-rebase.integration.test.ts @@ -77,6 +77,7 @@ scenario("Optimism :: Bridging integration test", ctxFactory) l1TokenRebasable, // stETH l1ERC20TokenBridge, l2Token, + l2TokenRebasable, l1CrossDomainMessenger, l2ERC20TokenBridge, } = ctx; @@ -84,12 +85,6 @@ scenario("Optimism :: Bridging integration test", ctxFactory) const { depositAmount: depositAmountInRebasableTokens } = ctx.common; const depositAmount = wei.toBigNumber(depositAmountInRebasableTokens).mul(2); - console.log("depositAmount=",depositAmount); - console.log("depositAmountInRebasableTokens=",depositAmountInRebasableTokens); - - console.log("l1Token=",l1Token.address); - console.log("l1TokenRebasable=",l1TokenRebasable.address); - console.log("l1ERC20TokenBridge=",l1ERC20TokenBridge.address); await l1TokenRebasable .connect(tokenHolderA.l1Signer) @@ -98,27 +93,24 @@ scenario("Optimism :: Bridging integration test", ctxFactory) const tokenHolderABalanceBefore = await l1TokenRebasable.balanceOf( tokenHolderA.address ); - console.log("tokenHolderA=", tokenHolderA.address); - console.log("tokenHolderABalanceBefore=", tokenHolderABalanceBefore); const l1ERC20TokenBridgeBalanceBefore = await l1TokenRebasable.balanceOf( l1ERC20TokenBridge.address ); - console.log("l1ERC20TokenBridgeBalanceBefore=", l1ERC20TokenBridgeBalanceBefore); const tx = await l1ERC20TokenBridge .connect(tokenHolderA.l1Signer) .depositERC20( l1TokenRebasable.address, - l2Token.address, + l2TokenRebasable.address, depositAmountInRebasableTokens, 200_000, "0x" ); await assert.emits(l1ERC20TokenBridge, tx, "ERC20DepositInitiated", [ - l1Token.address, - l2Token.address, + l1TokenRebasable.address, + l2TokenRebasable.address, tokenHolderA.address, tokenHolderA.address, depositAmount, @@ -128,8 +120,8 @@ scenario("Optimism :: Bridging integration test", ctxFactory) const l2DepositCalldata = l2ERC20TokenBridge.interface.encodeFunctionData( "finalizeDeposit", [ - l1Token.address, - l2Token.address, + l1TokenRebasable.address, + l2TokenRebasable.address, tokenHolderA.address, tokenHolderA.address, depositAmount, @@ -147,16 +139,11 @@ scenario("Optimism :: Bridging integration test", ctxFactory) 200_000, ]); - - assert.equalBN( await l1Token.balanceOf(l1ERC20TokenBridge.address), l1ERC20TokenBridgeBalanceBefore.add(depositAmount) ); - // console.log("qwe=",await l1TokenRebasable.balanceOf(tokenHolderA.address)); - // console.log("asd=",tokenHolderABalanceBefore.sub(depositAmount)); - assert.equalBN( await l1TokenRebasable.balanceOf(tokenHolderA.address), // stETH tokenHolderABalanceBefore.sub(depositAmountInRebasableTokens) @@ -172,24 +159,19 @@ scenario("Optimism :: Bridging integration test", ctxFactory) l2CrossDomainMessenger, l2ERC20TokenBridge, } = ctx; - // const { depositAmount } = ctx.common; const { depositAmount: depositAmountInRebasableTokens } = ctx.common; const depositAmount = wei.toBigNumber(depositAmountInRebasableTokens).mul(2); const { accountA: tokenHolderA, l1CrossDomainMessengerAliased } = ctx.accounts; - console.log("Finalize1 depositAmount=",depositAmount); const tokenHolderABalanceBefore = await l2TokenRebasable.balanceOf( tokenHolderA.address ); - console.log("tokenHolderABalanceBefore=",tokenHolderABalanceBefore); const l2TokenRebasableTotalSupplyBefore = await l2TokenRebasable.totalSupply(); - - console.log("test1", l1ERC20TokenBridge.address, l2ERC20TokenBridge.address, l1TokenRebasable.address, l2Token.address); - + const tx = await l2CrossDomainMessenger .connect(l1CrossDomainMessengerAliased) .relayMessage( @@ -219,10 +201,6 @@ scenario("Optimism :: Bridging integration test", ctxFactory) depositAmount, "0x", ]); - // console.log(await l1TokenRebasable.balanceOf(tokenHolderA.address)); - - console.log(await l2TokenRebasable.balanceOf(tokenHolderA.address)); - console.log(tokenHolderABalanceBefore.add(depositAmountInRebasableTokens)); assert.equalBN( await l2TokenRebasable.balanceOf(tokenHolderA.address), @@ -234,99 +212,121 @@ scenario("Optimism :: Bridging integration test", ctxFactory) ); }) -// .step("L2 -> L1 withdrawal via withdraw()", async (ctx) => { -// const { accountA: tokenHolderA } = ctx.accounts; -// const { withdrawalAmount } = ctx.common; -// const { l1Token, l2Token, l2ERC20TokenBridge } = ctx; + .step("L2 -> L1 withdrawal via withdraw()", async (ctx) => { + const { accountA: tokenHolderA } = ctx.accounts; + const { withdrawalAmount: withdrawalAmountInRebasableTokens } = ctx.common; + const { + l1Token, + l2Token, + l1TokenRebasable, + l2TokenRebasable, + l2ERC20TokenBridge + } = ctx; -// const tokenHolderABalanceBefore = await l2Token.balanceOf( -// tokenHolderA.address -// ); -// const l2TotalSupplyBefore = await l2Token.totalSupply(); + const withdrawalAmount = wei.toBigNumber(withdrawalAmountInRebasableTokens).mul(2); -// const tx = await l2ERC20TokenBridge -// .connect(tokenHolderA.l2Signer) -// .withdraw(l2Token.address, withdrawalAmount, 0, "0x"); + const tokenHolderABalanceBefore = await l2TokenRebasable.balanceOf( + tokenHolderA.address + ); + const l2TotalSupplyBefore = await l2TokenRebasable.totalSupply(); -// await assert.emits(l2ERC20TokenBridge, tx, "WithdrawalInitiated", [ -// l1Token.address, -// l2Token.address, -// tokenHolderA.address, -// tokenHolderA.address, -// withdrawalAmount, -// "0x", -// ]); -// assert.equalBN( -// await l2Token.balanceOf(tokenHolderA.address), -// tokenHolderABalanceBefore.sub(withdrawalAmount) -// ); -// assert.equalBN( -// await l2Token.totalSupply(), -// l2TotalSupplyBefore.sub(withdrawalAmount) -// ); -// }) + await l2TokenRebasable + .connect(tokenHolderA.l2Signer) + .approve(l2ERC20TokenBridge.address, withdrawalAmountInRebasableTokens); -// .step("Finalize withdrawal on L1", async (ctx) => { -// const { -// l1Token, -// l1CrossDomainMessenger, -// l1ERC20TokenBridge, -// l2CrossDomainMessenger, -// l2Token, -// l2ERC20TokenBridge, -// } = ctx; -// const { accountA: tokenHolderA, l1Stranger } = ctx.accounts; -// const { withdrawalAmount } = ctx.common; + const tx = await l2ERC20TokenBridge + .connect(tokenHolderA.l2Signer) + .withdraw( + l1TokenRebasable.address, + l2TokenRebasable.address, + withdrawalAmountInRebasableTokens, + 0, + "0x" + ); -// const tokenHolderABalanceBefore = await l1Token.balanceOf( -// tokenHolderA.address -// ); -// const l1ERC20TokenBridgeBalanceBefore = await l1Token.balanceOf( -// l1ERC20TokenBridge.address -// ); + await assert.emits(l2ERC20TokenBridge, tx, "WithdrawalInitiated", [ + l1TokenRebasable.address, + l2TokenRebasable.address, + tokenHolderA.address, + tokenHolderA.address, + withdrawalAmount, + "0x01", + ]); + -// await l1CrossDomainMessenger -// .connect(l1Stranger) -// .setXDomainMessageSender(l2ERC20TokenBridge.address); + assert.equalBN( + await l2TokenRebasable.balanceOf(tokenHolderA.address), + tokenHolderABalanceBefore.sub(withdrawalAmountInRebasableTokens) + ); + assert.equalBN( + await l2TokenRebasable.totalSupply(), + l2TotalSupplyBefore.sub(withdrawalAmountInRebasableTokens) + ); + }) -// const tx = await l1CrossDomainMessenger -// .connect(l1Stranger) -// .relayMessage( -// l1ERC20TokenBridge.address, -// l2CrossDomainMessenger.address, -// l1ERC20TokenBridge.interface.encodeFunctionData( -// "finalizeERC20Withdrawal", -// [ -// l1Token.address, -// l2Token.address, -// tokenHolderA.address, -// tokenHolderA.address, -// withdrawalAmount, -// "0x", -// ] -// ), -// 0 -// ); + .step("Finalize withdrawal on L1", async (ctx) => { + const { + l1Token, + l1TokenRebasable, + l1CrossDomainMessenger, + l1ERC20TokenBridge, + l2CrossDomainMessenger, + l2TokenRebasable, + l2ERC20TokenBridge, + } = ctx; + const { accountA: tokenHolderA, l1Stranger } = ctx.accounts; + const { withdrawalAmount: withdrawalAmountInRebasableTokens } = ctx.common; + const withdrawalAmount = wei.toBigNumber(withdrawalAmountInRebasableTokens).mul(2); -// await assert.emits(l1ERC20TokenBridge, tx, "ERC20WithdrawalFinalized", [ -// l1Token.address, -// l2Token.address, -// tokenHolderA.address, -// tokenHolderA.address, -// withdrawalAmount, -// "0x", -// ]); + const tokenHolderABalanceBefore = await l1TokenRebasable.balanceOf( + tokenHolderA.address + ); + const l1ERC20TokenBridgeBalanceBefore = await l1Token.balanceOf( + l1ERC20TokenBridge.address + ); -// assert.equalBN( -// await l1Token.balanceOf(l1ERC20TokenBridge.address), -// l1ERC20TokenBridgeBalanceBefore.sub(withdrawalAmount) -// ); + await l1CrossDomainMessenger + .connect(l1Stranger) + .setXDomainMessageSender(l2ERC20TokenBridge.address); -// assert.equalBN( -// await l1Token.balanceOf(tokenHolderA.address), -// tokenHolderABalanceBefore.add(withdrawalAmount) -// ); -// }) + const tx = await l1CrossDomainMessenger + .connect(l1Stranger) + .relayMessage( + l1ERC20TokenBridge.address, + l2CrossDomainMessenger.address, + l1ERC20TokenBridge.interface.encodeFunctionData( + "finalizeERC20Withdrawal", + [ + l1TokenRebasable.address, + l2TokenRebasable.address, + tokenHolderA.address, + tokenHolderA.address, + withdrawalAmount, + "0x01", + ] + ), + 0 + ); + + await assert.emits(l1ERC20TokenBridge, tx, "ERC20WithdrawalFinalized", [ + l1TokenRebasable.address, + l2TokenRebasable.address, + tokenHolderA.address, + tokenHolderA.address, + withdrawalAmount, + "0x01", + ]); + + assert.equalBN( + await l1Token.balanceOf(l1ERC20TokenBridge.address), + l1ERC20TokenBridgeBalanceBefore.sub(withdrawalAmount) + ); + + assert.equalBN( + await l1TokenRebasable.balanceOf(tokenHolderA.address), + tokenHolderABalanceBefore.add(withdrawalAmountInRebasableTokens) + ); + }) // .step("L1 -> L2 deposit via depositERC20To()", async (ctx) => { // const { From eb98d0b976e280797469ec0be1c48a12e695fc50 Mon Sep 17 00:00:00 2001 From: Anton Kovalchuk Date: Thu, 9 Nov 2023 20:54:45 +0100 Subject: [PATCH 007/148] send rate in data and time during deposit flow --- contracts/BridgeableTokens.sol | 8 ++++ contracts/optimism/DepositDataCodec.sol | 38 +++++++++++++++++ contracts/optimism/L1ERC20TokenBridge.sol | 24 +++++++---- contracts/optimism/L2ERC20TokenBridge.sol | 29 ++++++++----- contracts/stubs/ERC20WrapableStub.sol | 4 ++ contracts/stubs/TokensRateOracleStub.sol | 5 +++ contracts/token/ERC20Rebasable.sol | 4 ++ contracts/token/interfaces/IERC20Wrapable.sol | 31 +++----------- .../token/interfaces/ITokensRateOracle.sol | 6 +++ .../bridging-rebase.integration.test.ts | 42 +++++++++++++++---- utils/optimism/deployment.ts | 1 + utils/optimism/testing.ts | 10 ++++- 12 files changed, 148 insertions(+), 54 deletions(-) create mode 100644 contracts/optimism/DepositDataCodec.sol diff --git a/contracts/BridgeableTokens.sol b/contracts/BridgeableTokens.sol index a0e85e50..6416d97d 100644 --- a/contracts/BridgeableTokens.sol +++ b/contracts/BridgeableTokens.sol @@ -53,6 +53,14 @@ contract BridgeableTokens { _; } + function isRebasableTokenFlow(address l1Token_, address l2Token_) internal view returns (bool) { + return l1Token_ == l1TokenRebasable && l2Token_ == l2TokenRebasable; + } + + function isNonRebasableTokenFlow(address l1Token_, address l2Token_) internal view returns (bool) { + return l1Token_ == l1TokenNonRebasable && l2Token_ == l2TokenNonRebasable; + } + error ErrorUnsupportedL1Token(); error ErrorUnsupportedL2Token(); error ErrorAccountIsZeroAddress(); diff --git a/contracts/optimism/DepositDataCodec.sol b/contracts/optimism/DepositDataCodec.sol new file mode 100644 index 00000000..9da4265b --- /dev/null +++ b/contracts/optimism/DepositDataCodec.sol @@ -0,0 +1,38 @@ +// SPDX-FileCopyrightText: 2023 Lido +// SPDX-License-Identifier: GPL-3.0 + +pragma solidity 0.8.10; + +contract DepositDataCodec { + + struct DepositData { + uint256 rate; + uint256 time; + bytes data; + } + + function encodeDepositData(DepositData memory depositData) internal pure returns (bytes memory) { + bytes memory data = bytes.concat( + abi.encodePacked(depositData.rate), + abi.encodePacked(depositData.time), + abi.encodePacked(depositData.data) + ); + return data; + } + + function decodeDepositData(bytes calldata buffer) internal pure returns (DepositData memory) { + + if (buffer.length < 32 * 2) { + revert ErrorDepositDataLength(); + } + + DepositData memory depositData; + depositData.rate = uint256(bytes32(buffer[0:31])); + depositData.time = uint256(bytes32(buffer[32:63])); + depositData.data = buffer[64:]; + + return depositData; + } + + error ErrorDepositDataLength(); +} \ No newline at end of file diff --git a/contracts/optimism/L1ERC20TokenBridge.sol b/contracts/optimism/L1ERC20TokenBridge.sol index 93b9535f..96f18a7f 100644 --- a/contracts/optimism/L1ERC20TokenBridge.sol +++ b/contracts/optimism/L1ERC20TokenBridge.sol @@ -13,6 +13,7 @@ import {IL2ERC20Bridge} from "./interfaces/IL2ERC20Bridge.sol"; import {BridgingManager} from "../BridgingManager.sol"; import {BridgeableTokens} from "../BridgeableTokens.sol"; import {CrossDomainEnabled} from "./CrossDomainEnabled.sol"; +import {DepositDataCodec} from "./DepositDataCodec.sol"; import {IERC20Wrapable} from "../token/interfaces/IERC20Wrapable.sol"; import "hardhat/console.sol"; @@ -25,7 +26,8 @@ contract L1ERC20TokenBridge is IL1ERC20Bridge, BridgingManager, BridgeableTokens, - CrossDomainEnabled + CrossDomainEnabled, + DepositDataCodec { using SafeERC20 for IERC20; @@ -65,16 +67,22 @@ contract L1ERC20TokenBridge is if (Address.isContract(msg.sender)) { revert ErrorSenderNotEOA(); } + + DepositData memory depositData; + depositData.rate = IERC20Wrapable(l1TokenNonRebasable).tokensPerStEth(); + depositData.time = block.timestamp; + depositData.data = data_; - if(l1Token_ == l1TokenRebasable) { - bytes memory data = bytes.concat(hex'01', data_); + bytes memory encodedDepositData = encodeDepositData(depositData); + + if (isRebasableTokenFlow(l1Token_, l2Token_)) { IERC20(l1TokenRebasable).safeTransferFrom(msg.sender, address(this), amount_); IERC20(l1TokenRebasable).approve(l1TokenNonRebasable, amount_); uint256 wstETHAmount = IERC20Wrapable(l1TokenNonRebasable).wrap(amount_); - _initiateERC20Deposit(l1Token_, l2Token_, msg.sender, msg.sender, wstETHAmount, l2Gas_, data); - } else { + _initiateERC20Deposit(l1TokenRebasable, l2TokenRebasable, msg.sender, msg.sender, wstETHAmount, l2Gas_, encodedDepositData); + } else if (isNonRebasableTokenFlow(l1Token_, l2Token_)) { IERC20(l1TokenNonRebasable).safeTransferFrom(msg.sender, address(this), amount_); - _initiateERC20Deposit(l1Token_, l2Token_, msg.sender, msg.sender, amount_, l2Gas_, data_); + _initiateERC20Deposit(l1TokenNonRebasable, l2TokenNonRebasable, msg.sender, msg.sender, amount_, l2Gas_, encodedDepositData); } } @@ -111,10 +119,10 @@ contract L1ERC20TokenBridge is onlySupportedL2Token(l2Token_) onlyFromCrossDomainAccount(l2TokenBridge) { - if (data_.length > 0 && data_[0] == hex'01') { + if (isRebasableTokenFlow(l1Token_, l2Token_)) { uint256 stETHAmount = IERC20Wrapable(l1TokenNonRebasable).unwrap(amount_); IERC20(l1TokenRebasable).safeTransfer(to_, stETHAmount); - } else { + } else if (isNonRebasableTokenFlow(l1Token_, l2Token_)) { IERC20(l1Token_).safeTransfer(to_, amount_); } diff --git a/contracts/optimism/L2ERC20TokenBridge.sol b/contracts/optimism/L2ERC20TokenBridge.sol index a8f9df01..595d1bf1 100644 --- a/contracts/optimism/L2ERC20TokenBridge.sol +++ b/contracts/optimism/L2ERC20TokenBridge.sol @@ -6,6 +6,7 @@ pragma solidity 0.8.10; import {IL1ERC20Bridge} from "./interfaces/IL1ERC20Bridge.sol"; import {IL2ERC20Bridge} from "./interfaces/IL2ERC20Bridge.sol"; import {IERC20Bridged} from "../token/interfaces/IERC20Bridged.sol"; +import {ITokensRateOracle} from "../token/interfaces/ITokensRateOracle.sol"; import {ERC20Rebasable} from "../token/ERC20Rebasable.sol"; import {IERC20} from "@openzeppelin/contracts/token/ERC20/IERC20.sol"; import {IERC20Wrapable} from "../token/interfaces/IERC20Wrapable.sol"; @@ -14,6 +15,7 @@ import {SafeERC20} from "@openzeppelin/contracts/token/ERC20/utils/SafeERC20.sol import {BridgingManager} from "../BridgingManager.sol"; import {BridgeableTokens} from "../BridgeableTokens.sol"; import {CrossDomainEnabled} from "./CrossDomainEnabled.sol"; +import {DepositDataCodec} from "./DepositDataCodec.sol"; import { console } from "hardhat/console.sol"; @@ -27,13 +29,18 @@ contract L2ERC20TokenBridge is IL2ERC20Bridge, BridgingManager, BridgeableTokens, - CrossDomainEnabled + CrossDomainEnabled, + DepositDataCodec { using SafeERC20 for IERC20; /// @inheritdoc IL2ERC20Bridge address public immutable l1TokenBridge; + address public immutable tokensRateOracle; + + + /// @param messenger_ L2 messenger address being used for cross-chain communications /// @param l1TokenBridge_ Address of the corresponding L1 bridge /// @param l1TokenNonRebasable_ Address of the bridged token in the L1 chain @@ -46,9 +53,11 @@ contract L2ERC20TokenBridge is address l1TokenNonRebasable_, address l1TokenRebasable_, address l2TokenNonRebasable_, - address l2TokenRebasable_ + address l2TokenRebasable_, + address tokensRateOracle_ ) CrossDomainEnabled(messenger_) BridgeableTokens(l1TokenNonRebasable_, l1TokenRebasable_, l2TokenNonRebasable_, l2TokenRebasable_) { l1TokenBridge = l1TokenBridge_; + tokensRateOracle = tokensRateOracle_; } /// @inheritdoc IL2ERC20Bridge @@ -60,10 +69,9 @@ contract L2ERC20TokenBridge is bytes calldata data_ ) external whenWithdrawalsEnabled onlySupportedL2Token(l2Token_) { if(l2Token_ == l2TokenRebasable) { - bytes memory data = bytes.concat(hex'01', data_); uint256 shares = ERC20Rebasable(l2TokenRebasable).getSharesByTokens(amount_); ERC20Rebasable(l2TokenRebasable).burnShares(msg.sender, shares); - _initiateWithdrawal(l1Token_, l2Token_, msg.sender, msg.sender, shares, l1Gas_, data); + _initiateWithdrawal(l1Token_, l2Token_, msg.sender, msg.sender, shares, l1Gas_, data_); } else { IERC20Bridged(l2TokenNonRebasable).bridgeBurn(msg.sender, amount_); _initiateWithdrawal(l1Token_, l2Token_, msg.sender, msg.sender, amount_, l1Gas_, data_); @@ -96,14 +104,15 @@ contract L2ERC20TokenBridge is onlySupportedL2Token(l2Token_) onlyFromCrossDomainAccount(l1TokenBridge) { - if (data_.length > 0 && data_[0] == hex'01') { + DepositData memory depositData = decodeDepositData(data_); + ITokensRateOracle(tokensRateOracle).updateRate(int256(depositData.rate), depositData.time); + + if (isRebasableTokenFlow(l1Token_, l2Token_)) { ERC20Rebasable(l2TokenRebasable).mintShares(to_, amount_); - bytes memory data = data_[1:]; - emit DepositFinalized(l1Token_, l2Token_, from_, to_, amount_, data); - } else { - IERC20Bridged(l2Token_).bridgeMint(to_, amount_); - emit DepositFinalized(l1Token_, l2Token_, from_, to_, amount_, data_); + } else if (isNonRebasableTokenFlow(l1Token_, l2Token_)) { + IERC20Bridged(l2TokenNonRebasable).bridgeMint(to_, amount_); } + emit DepositFinalized(l1Token_, l2Token_, from_, to_, amount_, depositData.data); } /// @notice Performs the logic for withdrawals by burning the token and informing diff --git a/contracts/stubs/ERC20WrapableStub.sol b/contracts/stubs/ERC20WrapableStub.sol index 2edf81d1..b8e35599 100644 --- a/contracts/stubs/ERC20WrapableStub.sol +++ b/contracts/stubs/ERC20WrapableStub.sol @@ -47,4 +47,8 @@ contract ERC20WrapableStub is IERC20Wrapable, ERC20 { return stETHAmount; } + + function tokensPerStEth() external view returns (uint256) { + return tokensRate; + } } diff --git a/contracts/stubs/TokensRateOracleStub.sol b/contracts/stubs/TokensRateOracleStub.sol index 6f397515..4c43af26 100644 --- a/contracts/stubs/TokensRateOracleStub.sol +++ b/contracts/stubs/TokensRateOracleStub.sol @@ -44,4 +44,9 @@ contract TokensRateOracleStub is ITokensRateOracle { ) { return (0,latestRoundDataAnswer,0,latestRoundDataUpdatedAt,0); } + + function updateRate(int256 rate, uint256 updatedAt) external { + latestRoundDataAnswer = rate; + latestRoundDataUpdatedAt = updatedAt; + } } \ No newline at end of file diff --git a/contracts/token/ERC20Rebasable.sol b/contracts/token/ERC20Rebasable.sol index 04899582..0501dde8 100644 --- a/contracts/token/ERC20Rebasable.sol +++ b/contracts/token/ERC20Rebasable.sol @@ -75,6 +75,10 @@ contract ERC20Rebasable is IERC20Wrapable, IERC20, ERC20Metadata { return sharesAmount; } + function tokensPerStEth() external view returns (uint256) { + return 0; + } + function mintShares(address account_, uint256 amount_) external returns (uint256) { return _mintShares(account_, amount_); } diff --git a/contracts/token/interfaces/IERC20Wrapable.sol b/contracts/token/interfaces/IERC20Wrapable.sol index 15213ccd..de82ee27 100644 --- a/contracts/token/interfaces/IERC20Wrapable.sol +++ b/contracts/token/interfaces/IERC20Wrapable.sol @@ -30,30 +30,9 @@ interface IERC20Wrapable { */ function unwrap(uint256 wrapableTokenAmount_) external returns (uint256); - // TODO: - // /** - // * @notice Get amount of wstETH for a given amount of stETH - // * @param _stETHAmount amount of stETH - // * @return Amount of wstETH for a given stETH amount - // */ - // function getWstETHByStETH(uint256 _stETHAmount) external view returns (uint256); - - // /** - // * @notice Get amount of stETH for a given amount of wstETH - // * @param _wstETHAmount amount of wstETH - // * @return Amount of stETH for a given wstETH amount - // */ - // function getStETHByWstETH(uint256 _wstETHAmount) external view returns (uint256); - - // /** - // * @notice Get amount of stETH for a one wstETH - // * @return Amount of stETH for 1 wstETH - // */ - // function stEthPerToken() external view returns (uint256); - - // /** - // * @notice Get amount of wstETH for a one stETH - // * @return Amount of wstETH for a 1 stETH - // */ - // function tokensPerStEth() external view returns (uint256); + /** + * @notice Get amount of wstETH for a one stETH + * @return Amount of wstETH for a 1 stETH + */ + function tokensPerStEth() external view returns (uint256); } \ No newline at end of file diff --git a/contracts/token/interfaces/ITokensRateOracle.sol b/contracts/token/interfaces/ITokensRateOracle.sol index 843be503..c28cabc9 100644 --- a/contracts/token/interfaces/ITokensRateOracle.sol +++ b/contracts/token/interfaces/ITokensRateOracle.sol @@ -7,6 +7,8 @@ pragma solidity 0.8.10; /// @notice Oracle interface for two tokens rate interface ITokensRateOracle { + function updateRate(int256 rate, uint256 updatedAt) external; + /** * @notice represents the number of decimals the oracle responses represent. */ @@ -25,4 +27,8 @@ interface ITokensRateOracle { uint256 updatedAt, uint80 answeredInRound ); +} + +interface ITokensRateOracleUpdatable { + function updateRate(int256 rate, uint256 updatedAt) external; } \ No newline at end of file diff --git a/test/optimism/bridging-rebase.integration.test.ts b/test/optimism/bridging-rebase.integration.test.ts index 71193d4c..6b4ab756 100644 --- a/test/optimism/bridging-rebase.integration.test.ts +++ b/test/optimism/bridging-rebase.integration.test.ts @@ -80,11 +80,15 @@ scenario("Optimism :: Bridging integration test", ctxFactory) l2TokenRebasable, l1CrossDomainMessenger, l2ERC20TokenBridge, + tokensRateOracle, + l1Provider } = ctx; const { accountA: tokenHolderA } = ctx.accounts; const { depositAmount: depositAmountInRebasableTokens } = ctx.common; - const depositAmount = wei.toBigNumber(depositAmountInRebasableTokens).mul(2); + const tokensPerStEth = await l1Token.tokensPerStEth(); + + await tokensRateOracle.setLatestRoundDataAnswer(BigNumber.from("1000000000000000000")); await l1TokenRebasable .connect(tokenHolderA.l1Signer) @@ -108,13 +112,19 @@ scenario("Optimism :: Bridging integration test", ctxFactory) "0x" ); + const blockNumber = await l1Provider.getBlockNumber(); + const blockTimestamp = (await l1Provider.getBlock(blockNumber)).timestamp; + const blockTimestampStr = ethers.utils.hexZeroPad(ethers.utils.hexlify(blockTimestamp), 32) + const tokensPerStEthStr = ethers.utils.hexZeroPad(tokensPerStEth.toHexString(), 32) + const dataToSend = tokensPerStEthStr + blockTimestampStr.slice(2); + await assert.emits(l1ERC20TokenBridge, tx, "ERC20DepositInitiated", [ l1TokenRebasable.address, l2TokenRebasable.address, tokenHolderA.address, tokenHolderA.address, depositAmount, - "0x01", + dataToSend, ]); const l2DepositCalldata = l2ERC20TokenBridge.interface.encodeFunctionData( @@ -125,7 +135,7 @@ scenario("Optimism :: Bridging integration test", ctxFactory) tokenHolderA.address, tokenHolderA.address, depositAmount, - "0x01", + dataToSend, ] ); @@ -152,15 +162,27 @@ scenario("Optimism :: Bridging integration test", ctxFactory) .step("Finalize deposit on L2", async (ctx) => { const { + l1Token, l1TokenRebasable, l2Token, l2TokenRebasable, l1ERC20TokenBridge, l2CrossDomainMessenger, l2ERC20TokenBridge, + tokensRateOracle, + l2Provider } = ctx; const { depositAmount: depositAmountInRebasableTokens } = ctx.common; const depositAmount = wei.toBigNumber(depositAmountInRebasableTokens).mul(2); + const tokensPerStEth = await l1Token.tokensPerStEth(); + + + const blockNumber = await l2Provider.getBlockNumber(); + const blockTimestamp = (await l2Provider.getBlock(blockNumber)).timestamp; + const blockTimestampStr = ethers.utils.hexZeroPad(ethers.utils.hexlify(blockTimestamp), 32) + const tokensPerStEthStr = ethers.utils.hexZeroPad(tokensPerStEth.toHexString(), 32) + const dataToReceive = tokensPerStEthStr + blockTimestampStr.slice(2); + const { accountA: tokenHolderA, l1CrossDomainMessengerAliased } = ctx.accounts; @@ -186,12 +208,16 @@ scenario("Optimism :: Bridging integration test", ctxFactory) tokenHolderA.address, tokenHolderA.address, depositAmount, - "0x01", + dataToReceive, ]), { gasLimit: 5_000_000 } ); - console.log("test2"); + + const [,tokensRate,,,] = await tokensRateOracle.latestRoundData(); + console.log("tokensPerStEth=",tokensPerStEth); + console.log("tokensRate=",tokensRate); + assert.equalBN(tokensPerStEth, tokensRate); await assert.emits(l2ERC20TokenBridge, tx, "DepositFinalized", [ l1TokenRebasable.address, @@ -250,7 +276,7 @@ scenario("Optimism :: Bridging integration test", ctxFactory) tokenHolderA.address, tokenHolderA.address, withdrawalAmount, - "0x01", + "0x", ]); @@ -302,7 +328,7 @@ scenario("Optimism :: Bridging integration test", ctxFactory) tokenHolderA.address, tokenHolderA.address, withdrawalAmount, - "0x01", + "0x", ] ), 0 @@ -314,7 +340,7 @@ scenario("Optimism :: Bridging integration test", ctxFactory) tokenHolderA.address, tokenHolderA.address, withdrawalAmount, - "0x01", + "0x", ]); assert.equalBN( diff --git a/utils/optimism/deployment.ts b/utils/optimism/deployment.ts index 39ce1f7f..4a3625a3 100644 --- a/utils/optimism/deployment.ts +++ b/utils/optimism/deployment.ts @@ -174,6 +174,7 @@ export default function deployment( l1TokenRebasable, expectedL2TokenProxyAddress, expectedL2TokenRebasableProxyAddress, + tokensRateOracleStub, options?.overrides, ], afterDeploy: (c) => diff --git a/utils/optimism/testing.ts b/utils/optimism/testing.ts index bb030fcf..721ed54b 100644 --- a/utils/optimism/testing.ts +++ b/utils/optimism/testing.ts @@ -156,7 +156,7 @@ async function loadDeployedBridges( l2SignerOrProvider: SignerOrProvider ) { return { - l1Token: IERC20__factory.connect( + l1Token: ERC20WrapableStub__factory.connect( testingUtils.env.OPT_L1_TOKEN(), l1SignerOrProvider ), @@ -164,6 +164,11 @@ async function loadDeployedBridges( testingUtils.env.OPT_L1_TOKEN(), l1SignerOrProvider ), + tokensRateOracle: TokensRateOracleStub__factory.connect( + testingUtils.env.OPT_L1_TOKEN(), + l1SignerOrProvider + ), + ...connectBridgeContracts( { l2Token: testingUtils.env.OPT_L2_TOKEN(), @@ -197,7 +202,7 @@ async function deployTestBridge( ); const tokensRateOracleStub = await new TokensRateOracleStub__factory(optDeployer).deploy(); - await tokensRateOracleStub.setLatestRoundDataAnswer(BigNumber.from("2000000000000000000")); + await tokensRateOracleStub.setLatestRoundDataAnswer(BigNumber.from("1000000000000000000")); await tokensRateOracleStub.setDecimals(18); await tokensRateOracleStub.setUpdatedAt(100); @@ -247,6 +252,7 @@ async function deployTestBridge( return { l1Token: l1Token.connect(ethProvider), l1TokenRebasable: l1TokenRebasable.connect(ethProvider), + tokensRateOracle: tokensRateOracleStub, ...connectBridgeContracts( { l2Token: optDeployScript.getContractAddress(1), From 800e33fde9fe77c8d83e80eb025319c67a554669 Mon Sep 17 00:00:00 2001 From: Anton Kovalchuk Date: Fri, 10 Nov 2023 00:05:31 +0100 Subject: [PATCH 008/148] add possibility to depost 0 tokens --- contracts/optimism/DepositDataCodec.sol | 4 +- contracts/optimism/L1ERC20TokenBridge.sol | 53 +++-- contracts/optimism/L2ERC20TokenBridge.sol | 37 ++-- .../optimism/interfaces/IL2ERC20Bridge.sol | 1 - contracts/stubs/ERC20WrapableStub.sol | 3 +- .../bridging-rebase.integration.test.ts | 187 ++++++++++++++++-- 6 files changed, 232 insertions(+), 53 deletions(-) diff --git a/contracts/optimism/DepositDataCodec.sol b/contracts/optimism/DepositDataCodec.sol index 9da4265b..a7bc919a 100644 --- a/contracts/optimism/DepositDataCodec.sol +++ b/contracts/optimism/DepositDataCodec.sol @@ -27,8 +27,8 @@ contract DepositDataCodec { } DepositData memory depositData; - depositData.rate = uint256(bytes32(buffer[0:31])); - depositData.time = uint256(bytes32(buffer[32:63])); + depositData.rate = uint256(bytes32(buffer[0:32])); + depositData.time = uint256(bytes32(buffer[32:64])); depositData.data = buffer[64:]; return depositData; diff --git a/contracts/optimism/L1ERC20TokenBridge.sol b/contracts/optimism/L1ERC20TokenBridge.sol index 96f18a7f..1353325f 100644 --- a/contracts/optimism/L1ERC20TokenBridge.sol +++ b/contracts/optimism/L1ERC20TokenBridge.sol @@ -68,22 +68,7 @@ contract L1ERC20TokenBridge is revert ErrorSenderNotEOA(); } - DepositData memory depositData; - depositData.rate = IERC20Wrapable(l1TokenNonRebasable).tokensPerStEth(); - depositData.time = block.timestamp; - depositData.data = data_; - - bytes memory encodedDepositData = encodeDepositData(depositData); - - if (isRebasableTokenFlow(l1Token_, l2Token_)) { - IERC20(l1TokenRebasable).safeTransferFrom(msg.sender, address(this), amount_); - IERC20(l1TokenRebasable).approve(l1TokenNonRebasable, amount_); - uint256 wstETHAmount = IERC20Wrapable(l1TokenNonRebasable).wrap(amount_); - _initiateERC20Deposit(l1TokenRebasable, l2TokenRebasable, msg.sender, msg.sender, wstETHAmount, l2Gas_, encodedDepositData); - } else if (isNonRebasableTokenFlow(l1Token_, l2Token_)) { - IERC20(l1TokenNonRebasable).safeTransferFrom(msg.sender, address(this), amount_); - _initiateERC20Deposit(l1TokenNonRebasable, l2TokenNonRebasable, msg.sender, msg.sender, amount_, l2Gas_, encodedDepositData); - } + _depositERC20To(l1Token_, l2Token_, msg.sender, amount_, l2Gas_, data_); } /// @inheritdoc IL1ERC20Bridge @@ -101,7 +86,7 @@ contract L1ERC20TokenBridge is onlySupportedL1Token(l1Token_) onlySupportedL2Token(l2Token_) { - _initiateERC20Deposit(l1Token_, l2Token_, msg.sender, to_, amount_, l2Gas_, data_); + _depositERC20To(l1Token_, l2Token_, to_, amount_, l2Gas_, data_); } /// @inheritdoc IL1ERC20Bridge @@ -123,7 +108,7 @@ contract L1ERC20TokenBridge is uint256 stETHAmount = IERC20Wrapable(l1TokenNonRebasable).unwrap(amount_); IERC20(l1TokenRebasable).safeTransfer(to_, stETHAmount); } else if (isNonRebasableTokenFlow(l1Token_, l2Token_)) { - IERC20(l1Token_).safeTransfer(to_, amount_); + IERC20(l1TokenNonRebasable).safeTransfer(to_, amount_); } emit ERC20WithdrawalFinalized( @@ -136,6 +121,38 @@ contract L1ERC20TokenBridge is ); } + function _depositERC20To( + address l1Token_, + address l2Token_, + address to_, + uint256 amount_, + uint32 l2Gas_, + bytes calldata data_ + ) internal { + + DepositData memory depositData; + depositData.rate = IERC20Wrapable(l1TokenNonRebasable).tokensPerStEth(); + depositData.time = block.timestamp; + depositData.data = data_; + + bytes memory encodedDepositData = encodeDepositData(depositData); + + if (amount_ == 0) { + _initiateERC20Deposit(l1Token_, l2Token_, msg.sender, to_, amount_, l2Gas_, encodedDepositData); + return; + } + + if (isRebasableTokenFlow(l1Token_, l2Token_)) { + IERC20(l1TokenRebasable).safeTransferFrom(msg.sender, address(this), amount_); + IERC20(l1TokenRebasable).approve(l1TokenNonRebasable, amount_); + uint256 wstETHAmount = IERC20Wrapable(l1TokenNonRebasable).wrap(amount_); + _initiateERC20Deposit(l1TokenRebasable, l2TokenRebasable, msg.sender, to_, wstETHAmount, l2Gas_, encodedDepositData); + } else if (isNonRebasableTokenFlow(l1Token_, l2Token_)) { + IERC20(l1TokenNonRebasable).safeTransferFrom(msg.sender, address(this), amount_); + _initiateERC20Deposit(l1TokenNonRebasable, l2TokenNonRebasable, msg.sender, to_, amount_, l2Gas_, encodedDepositData); + } + } + /// @dev Performs the logic for deposits by informing the L2 token bridge contract /// of the deposit and calling safeTransferFrom to lock the L1 funds. /// @param from_ Account to pull the deposit from on L1 diff --git a/contracts/optimism/L2ERC20TokenBridge.sol b/contracts/optimism/L2ERC20TokenBridge.sol index 595d1bf1..68f2bceb 100644 --- a/contracts/optimism/L2ERC20TokenBridge.sol +++ b/contracts/optimism/L2ERC20TokenBridge.sol @@ -39,8 +39,6 @@ contract L2ERC20TokenBridge is address public immutable tokensRateOracle; - - /// @param messenger_ L2 messenger address being used for cross-chain communications /// @param l1TokenBridge_ Address of the corresponding L1 bridge /// @param l1TokenNonRebasable_ Address of the bridged token in the L1 chain @@ -62,20 +60,12 @@ contract L2ERC20TokenBridge is /// @inheritdoc IL2ERC20Bridge function withdraw( - address l1Token_, address l2Token_, uint256 amount_, uint32 l1Gas_, bytes calldata data_ ) external whenWithdrawalsEnabled onlySupportedL2Token(l2Token_) { - if(l2Token_ == l2TokenRebasable) { - uint256 shares = ERC20Rebasable(l2TokenRebasable).getSharesByTokens(amount_); - ERC20Rebasable(l2TokenRebasable).burnShares(msg.sender, shares); - _initiateWithdrawal(l1Token_, l2Token_, msg.sender, msg.sender, shares, l1Gas_, data_); - } else { - IERC20Bridged(l2TokenNonRebasable).bridgeBurn(msg.sender, amount_); - _initiateWithdrawal(l1Token_, l2Token_, msg.sender, msg.sender, amount_, l1Gas_, data_); - } + _withdrawTo(l2Token_, msg.sender, amount_, l1Gas_, data_); } /// @inheritdoc IL2ERC20Bridge @@ -85,8 +75,28 @@ contract L2ERC20TokenBridge is uint256 amount_, uint32 l1Gas_, bytes calldata data_ - ) external whenWithdrawalsEnabled onlySupportedL2Token(l2Token_) { - _initiateWithdrawal(l1TokenNonRebasable, l2Token_, msg.sender, to_, amount_, l1Gas_, data_); + ) external whenWithdrawalsEnabled onlySupportedL2Token(l2Token_) { + _withdrawTo(l2Token_, to_, amount_, l1Gas_, data_); + } + + function _withdrawTo( + address l2Token_, + address to_, + uint256 amount_, + uint32 l1Gas_, + bytes calldata data_ + ) internal { + if (l2Token_ == l2TokenRebasable) { + + uint256 shares = ERC20Rebasable(l2TokenRebasable).getSharesByTokens(amount_); + ERC20Rebasable(l2TokenRebasable).burnShares(msg.sender, shares); + _initiateWithdrawal(l1TokenRebasable, l2TokenRebasable, msg.sender, to_, shares, l1Gas_, data_); + + } else if (l2Token_ == l2TokenNonRebasable) { + + IERC20Bridged(l2TokenNonRebasable).bridgeBurn(msg.sender, amount_); + _initiateWithdrawal(l2TokenNonRebasable, l2TokenNonRebasable, msg.sender, to_, amount_, l1Gas_, data_); + } } /// @inheritdoc IL2ERC20Bridge @@ -112,6 +122,7 @@ contract L2ERC20TokenBridge is } else if (isNonRebasableTokenFlow(l1Token_, l2Token_)) { IERC20Bridged(l2TokenNonRebasable).bridgeMint(to_, amount_); } + emit DepositFinalized(l1Token_, l2Token_, from_, to_, amount_, depositData.data); } diff --git a/contracts/optimism/interfaces/IL2ERC20Bridge.sol b/contracts/optimism/interfaces/IL2ERC20Bridge.sol index 04c27baf..448dfb8b 100644 --- a/contracts/optimism/interfaces/IL2ERC20Bridge.sol +++ b/contracts/optimism/interfaces/IL2ERC20Bridge.sol @@ -46,7 +46,6 @@ interface IL2ERC20Bridge { /// solely as a convenience for external contracts. Aside from enforcing a maximum /// length, these contracts provide no guarantees about its content. function withdraw( - address l1Token_, address l2Token_, uint256 amount_, uint32 l1Gas_, diff --git a/contracts/stubs/ERC20WrapableStub.sol b/contracts/stubs/ERC20WrapableStub.sol index b8e35599..f3c7f33a 100644 --- a/contracts/stubs/ERC20WrapableStub.sol +++ b/contracts/stubs/ERC20WrapableStub.sol @@ -15,13 +15,12 @@ contract ERC20WrapableStub is IERC20Wrapable, ERC20 { IERC20 public stETH; address public bridge; - uint256 public tokensRate; /// wst/st + uint256 public tokensRate; constructor(IERC20 stETH_, string memory name_, string memory symbol_) ERC20(name_, symbol_) { stETH = stETH_; - console.log("constructor wrap stETH=",address(stETH)); tokensRate = 2 * 10 **18; _mint(msg.sender, 1000000 * 10**18); diff --git a/test/optimism/bridging-rebase.integration.test.ts b/test/optimism/bridging-rebase.integration.test.ts index 6b4ab756..e0822643 100644 --- a/test/optimism/bridging-rebase.integration.test.ts +++ b/test/optimism/bridging-rebase.integration.test.ts @@ -4,8 +4,8 @@ import env from "../../utils/env"; import { wei } from "../../utils/wei"; import optimism from "../../utils/optimism"; import testing, { scenario } from "../../utils/testing"; -import hre, { ethers } from "hardhat"; -import { BigNumber, FixedNumber } from "ethers"; +import { ethers } from "hardhat"; +import { BigNumber } from "ethers"; scenario("Optimism :: Bridging integration test", ctxFactory) .after(async (ctx) => { @@ -71,12 +71,169 @@ scenario("Optimism :: Bridging integration test", ctxFactory) assert.isTrue(await l2ERC20TokenBridge.isWithdrawalsEnabled()); }) + .step("L1 -> L2 deposit zero tokens via depositERC20() method", async (ctx) => { + const { + l1Token, + l1TokenRebasable, + l1ERC20TokenBridge, + l2TokenRebasable, + l1CrossDomainMessenger, + l2ERC20TokenBridge, + tokensRateOracle, + l1Provider + } = ctx; + + const { accountA: tokenHolderA } = ctx.accounts; + const tokensPerStEth = await l1Token.tokensPerStEth(); + + await l1TokenRebasable + .connect(tokenHolderA.l1Signer) + .approve(l1ERC20TokenBridge.address, 0); + + const tokenHolderABalanceBefore = await l1TokenRebasable.balanceOf( + tokenHolderA.address + ); + + const l1ERC20TokenBridgeBalanceBefore = await l1TokenRebasable.balanceOf( + l1ERC20TokenBridge.address + ); + + const tx = await l1ERC20TokenBridge + .connect(tokenHolderA.l1Signer) + .depositERC20( + l1TokenRebasable.address, + l2TokenRebasable.address, + 0, + 200_000, + "0x" + ); + + const blockNumber = await l1Provider.getBlockNumber(); + const blockTimestamp = (await l1Provider.getBlock(blockNumber)).timestamp; + const blockTimestampStr = ethers.utils.hexZeroPad(ethers.utils.hexlify(blockTimestamp), 32) + const tokensPerStEthStr = ethers.utils.hexZeroPad(tokensPerStEth.toHexString(), 32) + const dataToSend = ethers.utils.hexConcat([tokensPerStEthStr, blockTimestampStr]); + + + await assert.emits(l1ERC20TokenBridge, tx, "ERC20DepositInitiated", [ + l1TokenRebasable.address, + l2TokenRebasable.address, + tokenHolderA.address, + tokenHolderA.address, + 0, + dataToSend, + ]); + + const l2DepositCalldata = l2ERC20TokenBridge.interface.encodeFunctionData( + "finalizeDeposit", + [ + l1TokenRebasable.address, + l2TokenRebasable.address, + tokenHolderA.address, + tokenHolderA.address, + 0, + dataToSend, + ] + ); + + const messageNonce = await l1CrossDomainMessenger.messageNonce(); + + await assert.emits(l1CrossDomainMessenger, tx, "SentMessage", [ + l2ERC20TokenBridge.address, + l1ERC20TokenBridge.address, + l2DepositCalldata, + messageNonce, + 200_000, + ]); + + assert.equalBN( + await l1Token.balanceOf(l1ERC20TokenBridge.address), + l1ERC20TokenBridgeBalanceBefore + ); + + assert.equalBN( + await l1TokenRebasable.balanceOf(tokenHolderA.address), + tokenHolderABalanceBefore + ); + }) + + .step("Finalize deposit zero tokens on L2", async (ctx) => { + const { + l1Token, + l1TokenRebasable, + l2TokenRebasable, + l1ERC20TokenBridge, + l2CrossDomainMessenger, + l2ERC20TokenBridge, + tokensRateOracle, + l2Provider + } = ctx; + + const tokensPerStEth = await l1Token.tokensPerStEth(); + const blockNumber = await l2Provider.getBlockNumber(); + const blockTimestamp = (await l2Provider.getBlock(blockNumber)).timestamp; + const blockTimestampStr = ethers.utils.hexZeroPad(ethers.utils.hexlify(blockTimestamp), 32) + const tokensPerStEthStr = ethers.utils.hexZeroPad(tokensPerStEth.toHexString(), 32) + const dataToReceive = ethers.utils.hexConcat([tokensPerStEthStr, blockTimestampStr]); + + const { accountA: tokenHolderA, l1CrossDomainMessengerAliased } = + ctx.accounts; + + + const tokenHolderABalanceBefore = await l2TokenRebasable.balanceOf( + tokenHolderA.address + ); + + const l2TokenRebasableTotalSupplyBefore = await l2TokenRebasable.totalSupply(); + + const tx = await l2CrossDomainMessenger + .connect(l1CrossDomainMessengerAliased) + .relayMessage( + 1, + l1ERC20TokenBridge.address, + l2ERC20TokenBridge.address, + 0, + 300_000, + l2ERC20TokenBridge.interface.encodeFunctionData("finalizeDeposit", [ + l1TokenRebasable.address, + l2TokenRebasable.address, + tokenHolderA.address, + tokenHolderA.address, + 0, + dataToReceive, + ]), + { gasLimit: 5_000_000 } + ); + + + const [,tokensRate,,updatedAt,] = await tokensRateOracle.latestRoundData(); + assert.equalBN(tokensPerStEth, tokensRate); + assert.equalBN(blockTimestamp, updatedAt); + + await assert.emits(l2ERC20TokenBridge, tx, "DepositFinalized", [ + l1TokenRebasable.address, + l2TokenRebasable.address, + tokenHolderA.address, + tokenHolderA.address, + 0, + "0x", + ]); + + assert.equalBN( + await l2TokenRebasable.balanceOf(tokenHolderA.address), + tokenHolderABalanceBefore + ); + assert.equalBN( + await l2TokenRebasable.totalSupply(), + l2TokenRebasableTotalSupplyBefore + ); + }) + .step("L1 -> L2 deposit via depositERC20() method", async (ctx) => { const { - l1Token, // wstETH - l1TokenRebasable, // stETH + l1Token, + l1TokenRebasable, l1ERC20TokenBridge, - l2Token, l2TokenRebasable, l1CrossDomainMessenger, l2ERC20TokenBridge, @@ -116,7 +273,8 @@ scenario("Optimism :: Bridging integration test", ctxFactory) const blockTimestamp = (await l1Provider.getBlock(blockNumber)).timestamp; const blockTimestampStr = ethers.utils.hexZeroPad(ethers.utils.hexlify(blockTimestamp), 32) const tokensPerStEthStr = ethers.utils.hexZeroPad(tokensPerStEth.toHexString(), 32) - const dataToSend = tokensPerStEthStr + blockTimestampStr.slice(2); + const dataToSend = ethers.utils.hexConcat([tokensPerStEthStr, blockTimestampStr]); + await assert.emits(l1ERC20TokenBridge, tx, "ERC20DepositInitiated", [ l1TokenRebasable.address, @@ -164,7 +322,6 @@ scenario("Optimism :: Bridging integration test", ctxFactory) const { l1Token, l1TokenRebasable, - l2Token, l2TokenRebasable, l1ERC20TokenBridge, l2CrossDomainMessenger, @@ -181,7 +338,7 @@ scenario("Optimism :: Bridging integration test", ctxFactory) const blockTimestamp = (await l2Provider.getBlock(blockNumber)).timestamp; const blockTimestampStr = ethers.utils.hexZeroPad(ethers.utils.hexlify(blockTimestamp), 32) const tokensPerStEthStr = ethers.utils.hexZeroPad(tokensPerStEth.toHexString(), 32) - const dataToReceive = tokensPerStEthStr + blockTimestampStr.slice(2); + const dataToReceive = ethers.utils.hexConcat([tokensPerStEthStr, blockTimestampStr]); const { accountA: tokenHolderA, l1CrossDomainMessengerAliased } = @@ -214,10 +371,9 @@ scenario("Optimism :: Bridging integration test", ctxFactory) ); - const [,tokensRate,,,] = await tokensRateOracle.latestRoundData(); - console.log("tokensPerStEth=",tokensPerStEth); - console.log("tokensRate=",tokensRate); + const [,tokensRate,,updatedAt,] = await tokensRateOracle.latestRoundData(); assert.equalBN(tokensPerStEth, tokensRate); + assert.equalBN(blockTimestamp, updatedAt); await assert.emits(l2ERC20TokenBridge, tx, "DepositFinalized", [ l1TokenRebasable.address, @@ -241,9 +397,7 @@ scenario("Optimism :: Bridging integration test", ctxFactory) .step("L2 -> L1 withdrawal via withdraw()", async (ctx) => { const { accountA: tokenHolderA } = ctx.accounts; const { withdrawalAmount: withdrawalAmountInRebasableTokens } = ctx.common; - const { - l1Token, - l2Token, + const { l1TokenRebasable, l2TokenRebasable, l2ERC20TokenBridge @@ -263,7 +417,6 @@ scenario("Optimism :: Bridging integration test", ctxFactory) const tx = await l2ERC20TokenBridge .connect(tokenHolderA.l2Signer) .withdraw( - l1TokenRebasable.address, l2TokenRebasable.address, withdrawalAmountInRebasableTokens, 0, @@ -278,7 +431,6 @@ scenario("Optimism :: Bridging integration test", ctxFactory) withdrawalAmount, "0x", ]); - assert.equalBN( await l2TokenRebasable.balanceOf(tokenHolderA.address), @@ -292,7 +444,7 @@ scenario("Optimism :: Bridging integration test", ctxFactory) .step("Finalize withdrawal on L1", async (ctx) => { const { - l1Token, + l1Token, l1TokenRebasable, l1CrossDomainMessenger, l1ERC20TokenBridge, @@ -354,6 +506,7 @@ scenario("Optimism :: Bridging integration test", ctxFactory) ); }) + // .step("L1 -> L2 deposit via depositERC20To()", async (ctx) => { // const { // l1Token, From d9ac556d663b6a79e50688cc58737ae6bde17cb9 Mon Sep 17 00:00:00 2001 From: Anton Kovalchuk Date: Fri, 10 Nov 2023 00:14:30 +0100 Subject: [PATCH 009/148] init structs --- contracts/optimism/DepositDataCodec.sol | 9 +++++---- contracts/optimism/L1ERC20TokenBridge.sol | 11 ++++++----- contracts/token/ERC20Rebasable.sol | 2 +- 3 files changed, 12 insertions(+), 10 deletions(-) diff --git a/contracts/optimism/DepositDataCodec.sol b/contracts/optimism/DepositDataCodec.sol index a7bc919a..91b8c574 100644 --- a/contracts/optimism/DepositDataCodec.sol +++ b/contracts/optimism/DepositDataCodec.sol @@ -26,10 +26,11 @@ contract DepositDataCodec { revert ErrorDepositDataLength(); } - DepositData memory depositData; - depositData.rate = uint256(bytes32(buffer[0:32])); - depositData.time = uint256(bytes32(buffer[32:64])); - depositData.data = buffer[64:]; + DepositData memory depositData = DepositData({ + rate: uint256(bytes32(buffer[0:32])), + time: uint256(bytes32(buffer[32:64])), + data: buffer[64:] + }); return depositData; } diff --git a/contracts/optimism/L1ERC20TokenBridge.sol b/contracts/optimism/L1ERC20TokenBridge.sol index 1353325f..e1aa72be 100644 --- a/contracts/optimism/L1ERC20TokenBridge.sol +++ b/contracts/optimism/L1ERC20TokenBridge.sol @@ -129,11 +129,12 @@ contract L1ERC20TokenBridge is uint32 l2Gas_, bytes calldata data_ ) internal { - - DepositData memory depositData; - depositData.rate = IERC20Wrapable(l1TokenNonRebasable).tokensPerStEth(); - depositData.time = block.timestamp; - depositData.data = data_; + + DepositData memory depositData = DepositData({ + rate: IERC20Wrapable(l1TokenNonRebasable).tokensPerStEth(), + time: block.timestamp, + data: data_ + }); bytes memory encodedDepositData = encodeDepositData(depositData); diff --git a/contracts/token/ERC20Rebasable.sol b/contracts/token/ERC20Rebasable.sol index 0501dde8..d55c1542 100644 --- a/contracts/token/ERC20Rebasable.sol +++ b/contracts/token/ERC20Rebasable.sol @@ -75,7 +75,7 @@ contract ERC20Rebasable is IERC20Wrapable, IERC20, ERC20Metadata { return sharesAmount; } - function tokensPerStEth() external view returns (uint256) { + function tokensPerStEth() external pure returns (uint256) { return 0; } From ddb9ca342fb6342c6c2126a0e5670b88ce2a671b Mon Sep 17 00:00:00 2001 From: Anton Kovalchuk Date: Mon, 11 Dec 2023 19:27:52 +0100 Subject: [PATCH 010/148] add comments --- contracts/optimism/L1ERC20TokenBridge.sol | 51 +++++++++++++++-------- contracts/optimism/L2ERC20TokenBridge.sol | 6 +-- contracts/stubs/TokensRateOracleStub.sol | 1 + contracts/token/ERC20Rebasable.sol | 7 +--- 4 files changed, 38 insertions(+), 27 deletions(-) diff --git a/contracts/optimism/L1ERC20TokenBridge.sol b/contracts/optimism/L1ERC20TokenBridge.sol index e1aa72be..7af0573e 100644 --- a/contracts/optimism/L1ERC20TokenBridge.sol +++ b/contracts/optimism/L1ERC20TokenBridge.sol @@ -18,7 +18,11 @@ import {DepositDataCodec} from "./DepositDataCodec.sol"; import {IERC20Wrapable} from "../token/interfaces/IERC20Wrapable.sol"; import "hardhat/console.sol"; -/// @author psirex +// Check if Optimism changed API for bridges. They could depricate methods. +// Optimise gas usage with data transfer. Maybe cache rate and see if it changed. + + +/// @author psirex, kovalgek /// @notice The L1 ERC20 token bridge locks bridged tokens on the L1 side, sends deposit messages /// on the L2 side, and finalizes token withdrawals from L2. Additionally, adds the methods for /// bridging management: enabling and disabling withdrawals/deposits @@ -51,6 +55,11 @@ contract L1ERC20TokenBridge is l2TokenBridge = l2TokenBridge_; } + function pushTokenRate(address to_, uint32 l2Gas_) external { + bytes memory empty = new bytes(0); + _depositERC20To(l1TokenRebasable, l2TokenRebasable, to_, 0, l2Gas_, empty); + } + /// @inheritdoc IL1ERC20Bridge function depositERC20( address l1Token_, @@ -127,30 +136,36 @@ contract L1ERC20TokenBridge is address to_, uint256 amount_, uint32 l2Gas_, - bytes calldata data_ + bytes memory data_ ) internal { - DepositData memory depositData = DepositData({ - rate: IERC20Wrapable(l1TokenNonRebasable).tokensPerStEth(), - time: block.timestamp, - data: data_ - }); - - bytes memory encodedDepositData = encodeDepositData(depositData); - - if (amount_ == 0) { - _initiateERC20Deposit(l1Token_, l2Token_, msg.sender, to_, amount_, l2Gas_, encodedDepositData); - return; - } - if (isRebasableTokenFlow(l1Token_, l2Token_)) { + console.log("isRebasableTokenFlow"); + DepositData memory depositData = DepositData({ + rate: IERC20Wrapable(l1TokenNonRebasable).tokensPerStEth(), // replace by stETHPerToken + time: block.timestamp, + data: data_ + }); + + bytes memory encodedDepositData = encodeDepositData(depositData); + + // probably need to add a new method for amount zero + if (amount_ == 0) { + _initiateERC20Deposit(l1Token_, l2Token_, msg.sender, to_, amount_, l2Gas_, encodedDepositData); + return; + } + + // maybe loosing 1 wei for stETH. Check another method IERC20(l1TokenRebasable).safeTransferFrom(msg.sender, address(this), amount_); IERC20(l1TokenRebasable).approve(l1TokenNonRebasable, amount_); + // when 1 wei wasnt't transfer, can this wrap be failed? uint256 wstETHAmount = IERC20Wrapable(l1TokenNonRebasable).wrap(amount_); _initiateERC20Deposit(l1TokenRebasable, l2TokenRebasable, msg.sender, to_, wstETHAmount, l2Gas_, encodedDepositData); } else if (isNonRebasableTokenFlow(l1Token_, l2Token_)) { - IERC20(l1TokenNonRebasable).safeTransferFrom(msg.sender, address(this), amount_); - _initiateERC20Deposit(l1TokenNonRebasable, l2TokenNonRebasable, msg.sender, to_, amount_, l2Gas_, encodedDepositData); + console.log("isNonRebasableTokenFlow"); + + // IERC20(l1TokenNonRebasable).safeTransferFrom(msg.sender, address(this), amount_); + _initiateERC20Deposit(l1TokenNonRebasable, l2TokenNonRebasable, msg.sender, to_, amount_, l2Gas_, data_); } } @@ -182,7 +197,7 @@ contract L1ERC20TokenBridge is amount_, data_ ); - + console.logBytes(data_); sendCrossDomainMessage(l2TokenBridge, l2Gas_, message); emit ERC20DepositInitiated( diff --git a/contracts/optimism/L2ERC20TokenBridge.sol b/contracts/optimism/L2ERC20TokenBridge.sol index 68f2bceb..de6e467a 100644 --- a/contracts/optimism/L2ERC20TokenBridge.sol +++ b/contracts/optimism/L2ERC20TokenBridge.sol @@ -87,15 +87,13 @@ contract L2ERC20TokenBridge is bytes calldata data_ ) internal { if (l2Token_ == l2TokenRebasable) { - + // maybe loosing 1 wei her as well uint256 shares = ERC20Rebasable(l2TokenRebasable).getSharesByTokens(amount_); ERC20Rebasable(l2TokenRebasable).burnShares(msg.sender, shares); _initiateWithdrawal(l1TokenRebasable, l2TokenRebasable, msg.sender, to_, shares, l1Gas_, data_); - } else if (l2Token_ == l2TokenNonRebasable) { - IERC20Bridged(l2TokenNonRebasable).bridgeBurn(msg.sender, amount_); - _initiateWithdrawal(l2TokenNonRebasable, l2TokenNonRebasable, msg.sender, to_, amount_, l1Gas_, data_); + _initiateWithdrawal(l1TokenNonRebasable, l2TokenNonRebasable, msg.sender, to_, amount_, l1Gas_, data_); } } diff --git a/contracts/stubs/TokensRateOracleStub.sol b/contracts/stubs/TokensRateOracleStub.sol index 4c43af26..65ed5642 100644 --- a/contracts/stubs/TokensRateOracleStub.sol +++ b/contracts/stubs/TokensRateOracleStub.sol @@ -46,6 +46,7 @@ contract TokensRateOracleStub is ITokensRateOracle { } function updateRate(int256 rate, uint256 updatedAt) external { + // check timestamp not late as current one. latestRoundDataAnswer = rate; latestRoundDataUpdatedAt = updatedAt; } diff --git a/contracts/token/ERC20Rebasable.sol b/contracts/token/ERC20Rebasable.sol index d55c1542..ab5658bc 100644 --- a/contracts/token/ERC20Rebasable.sol +++ b/contracts/token/ERC20Rebasable.sol @@ -79,10 +79,12 @@ contract ERC20Rebasable is IERC20Wrapable, IERC20, ERC20Metadata { return 0; } + // allow call only bridge function mintShares(address account_, uint256 amount_) external returns (uint256) { return _mintShares(account_, amount_); } + // allow call only bridge function burnShares(address account_, uint256 amount_) external { _burnShares(account_, amount_); } @@ -248,12 +250,7 @@ contract ERC20Rebasable is IERC20Wrapable, IERC20, ERC20Metadata { } function _getTokensByShares(uint256 sharesAmount_) internal view returns (uint256) { - console.log("_getTokensByShares"); (uint256 tokensRate, uint256 decimals) = _getTokensRateAndDecimal(); - console.log("sharesAmount_=",sharesAmount_); - console.log("decimals=",decimals); - console.log("tokensRate=",tokensRate); - return (sharesAmount_ * (10 ** decimals)) / tokensRate; } From a73bcf4229715ab05c74b5deec299ae9fd31e787 Mon Sep 17 00:00:00 2001 From: Anton Kovalchuk Date: Mon, 11 Dec 2023 19:30:40 +0100 Subject: [PATCH 011/148] add gas test --- test/optimism/deposit-gas-estimation.test.ts | 272 +++++++++++++++++++ 1 file changed, 272 insertions(+) create mode 100644 test/optimism/deposit-gas-estimation.test.ts diff --git a/test/optimism/deposit-gas-estimation.test.ts b/test/optimism/deposit-gas-estimation.test.ts new file mode 100644 index 00000000..25e7252a --- /dev/null +++ b/test/optimism/deposit-gas-estimation.test.ts @@ -0,0 +1,272 @@ +import { assert } from "chai"; + +import env from "../../utils/env"; +import { wei } from "../../utils/wei"; +import optimism from "../../utils/optimism"; +import testing, { scenario } from "../../utils/testing"; +import { ethers } from "hardhat"; +import { BigNumber } from "ethers"; + +scenario("Optimism :: Bridging integration test", ctxFactory) + .after(async (ctx) => { + await ctx.l1Provider.send("evm_revert", [ctx.snapshot.l1]); + await ctx.l2Provider.send("evm_revert", [ctx.snapshot.l2]); + }) + + .step("Activate bridging on L1", async (ctx) => { + const { l1ERC20TokenBridge } = ctx; + const { l1ERC20TokenBridgeAdmin } = ctx.accounts; + + const isDepositsEnabled = await l1ERC20TokenBridge.isDepositsEnabled(); + + if (!isDepositsEnabled) { + await l1ERC20TokenBridge + .connect(l1ERC20TokenBridgeAdmin) + .enableDeposits(); + } else { + console.log("L1 deposits already enabled"); + } + + const isWithdrawalsEnabled = + await l1ERC20TokenBridge.isWithdrawalsEnabled(); + + if (!isWithdrawalsEnabled) { + await l1ERC20TokenBridge + .connect(l1ERC20TokenBridgeAdmin) + .enableWithdrawals(); + } else { + console.log("L1 withdrawals already enabled"); + } + + assert.isTrue(await l1ERC20TokenBridge.isDepositsEnabled()); + assert.isTrue(await l1ERC20TokenBridge.isWithdrawalsEnabled()); + }) + + .step("Activate bridging on L2", async (ctx) => { + const { l2ERC20TokenBridge } = ctx; + const { l2ERC20TokenBridgeAdmin } = ctx.accounts; + + const isDepositsEnabled = await l2ERC20TokenBridge.isDepositsEnabled(); + + if (!isDepositsEnabled) { + await l2ERC20TokenBridge + .connect(l2ERC20TokenBridgeAdmin) + .enableDeposits(); + } else { + console.log("L2 deposits already enabled"); + } + + const isWithdrawalsEnabled = + await l2ERC20TokenBridge.isWithdrawalsEnabled(); + + if (!isWithdrawalsEnabled) { + await l2ERC20TokenBridge + .connect(l2ERC20TokenBridgeAdmin) + .enableWithdrawals(); + } else { + console.log("L2 withdrawals already enabled"); + } + + assert.isTrue(await l2ERC20TokenBridge.isDepositsEnabled()); + assert.isTrue(await l2ERC20TokenBridge.isWithdrawalsEnabled()); + }) + + .step("L1 -> L2 deposit zero tokens via depositERC20() method", async (ctx) => { + const { + l1Token, + l2Token, + l1TokenRebasable, + l1ERC20TokenBridge, + l2TokenRebasable, + l1CrossDomainMessenger, + l2ERC20TokenBridge, + tokensRateOracle, + l1Provider + } = ctx; + + const { accountA: tokenHolderA } = ctx.accounts; + const tokensPerStEth = await l1Token.tokensPerStEth(); + + await l1TokenRebasable + .connect(tokenHolderA.l1Signer) + .approve(l1ERC20TokenBridge.address, 0); + + const tokenHolderABalanceBefore = await l1TokenRebasable.balanceOf( + tokenHolderA.address + ); + + const l1ERC20TokenBridgeBalanceBefore = await l1TokenRebasable.balanceOf( + l1ERC20TokenBridge.address + ); + + for(var x = 0; x< 2; ++x) { + const tx0 = await l1ERC20TokenBridge + .connect(tokenHolderA.l1Signer) + .depositERC20( + l1Token.address, + l2Token.address, + 0, + 200_000, + "0x" + ); + + const receipt0 = await tx0.wait(); + + console.log("l1Token gasUsed=",receipt0.gasUsed); + } + + for(var x = 0; x< 2; ++x) { + + const tx = await l1ERC20TokenBridge + .connect(tokenHolderA.l1Signer) + .depositERC20( + l1TokenRebasable.address, + l2TokenRebasable.address, + 0, + 200_000, + "0x" + ); + + const receipt1 = await tx.wait(); + + console.log("l1TokenRebasable gasUsed=",receipt1.gasUsed); + } + //const gasDifference = receipt1.gasUsed.sub(receipt0.gasUsed); + + //console.log("gasUsed difference=", gasDifference); + + + // const blockNumber = await l1Provider.getBlockNumber(); + // const blockTimestamp = (await l1Provider.getBlock(blockNumber)).timestamp; + // const blockTimestampStr = ethers.utils.hexZeroPad(ethers.utils.hexlify(blockTimestamp), 32) + // const tokensPerStEthStr = ethers.utils.hexZeroPad(tokensPerStEth.toHexString(), 32) + // const dataToSend = ethers.utils.hexConcat([tokensPerStEthStr, blockTimestampStr]); + + + // await assert.emits(l1ERC20TokenBridge, tx, "ERC20DepositInitiated", [ + // l1TokenRebasable.address, + // l2TokenRebasable.address, + // tokenHolderA.address, + // tokenHolderA.address, + // 0, + // dataToSend, + // ]); + + // const l2DepositCalldata = l2ERC20TokenBridge.interface.encodeFunctionData( + // "finalizeDeposit", + // [ + // l1TokenRebasable.address, + // l2TokenRebasable.address, + // tokenHolderA.address, + // tokenHolderA.address, + // 0, + // dataToSend, + // ] + // ); + + // const messageNonce = await l1CrossDomainMessenger.messageNonce(); + + // await assert.emits(l1CrossDomainMessenger, tx, "SentMessage", [ + // l2ERC20TokenBridge.address, + // l1ERC20TokenBridge.address, + // l2DepositCalldata, + // messageNonce, + // 200_000, + // ]); + + // assert.equalBN( + // await l1Token.balanceOf(l1ERC20TokenBridge.address), + // l1ERC20TokenBridgeBalanceBefore + // ); + + // assert.equalBN( + // await l1TokenRebasable.balanceOf(tokenHolderA.address), + // tokenHolderABalanceBefore + // ); + }) + + + + .run(); + +async function ctxFactory() { + const networkName = env.network("TESTING_OPT_NETWORK", "mainnet"); + console.log("networkName=",networkName); + + const { + l1Provider, + l2Provider, + l1ERC20TokenBridgeAdmin, + l2ERC20TokenBridgeAdmin, + ...contracts + } = await optimism.testing(networkName).getIntegrationTestSetup(); + + const l1Snapshot = await l1Provider.send("evm_snapshot", []); + const l2Snapshot = await l2Provider.send("evm_snapshot", []); + + // await optimism.testing(networkName).stubL1CrossChainMessengerContract(); + + const accountA = testing.accounts.accountA(l1Provider, l2Provider); + const accountB = testing.accounts.accountB(l1Provider, l2Provider); + + const depositAmount = wei`0.15 ether`; + const withdrawalAmount = wei`0.05 ether`; + + await testing.setBalance( + await contracts.l1TokensHolder.getAddress(), + wei.toBigNumber(wei`1 ether`), + l1Provider + ); + + await testing.setBalance( + await l1ERC20TokenBridgeAdmin.getAddress(), + wei.toBigNumber(wei`1 ether`), + l1Provider + ); + + await testing.setBalance( + await l2ERC20TokenBridgeAdmin.getAddress(), + wei.toBigNumber(wei`1 ether`), + l2Provider + ); + + await contracts.l1TokenRebasable + .connect(contracts.l1TokensHolder) + .transfer(accountA.l1Signer.address, wei.toBigNumber(depositAmount).mul(2)); + + const l1CrossDomainMessengerAliased = await testing.impersonate( + testing.accounts.applyL1ToL2Alias(contracts.l1CrossDomainMessenger.address), + l2Provider + ); + + console.log("l1CrossDomainMessengerAliased=",l1CrossDomainMessengerAliased); + console.log("contracts.l1CrossDomainMessenger.address=",contracts.l1CrossDomainMessenger.address); + + await testing.setBalance( + await l1CrossDomainMessengerAliased.getAddress(), + wei.toBigNumber(wei`1 ether`), + l2Provider + ); + + return { + l1Provider, + l2Provider, + ...contracts, + accounts: { + accountA, + accountB, + l1Stranger: testing.accounts.stranger(l1Provider), + l1ERC20TokenBridgeAdmin, + l2ERC20TokenBridgeAdmin, + l1CrossDomainMessengerAliased, + }, + common: { + depositAmount, + withdrawalAmount, + }, + snapshot: { + l1: l1Snapshot, + l2: l2Snapshot, + }, + }; +} From 6d37edce5b15c4ce88de98c51505ef771de4d9f4 Mon Sep 17 00:00:00 2001 From: Anton Kovalchuk Date: Tue, 12 Dec 2023 09:29:28 +0100 Subject: [PATCH 012/148] update gas test --- test/optimism/deposit-gas-estimation.test.ts | 94 ++++---------------- 1 file changed, 16 insertions(+), 78 deletions(-) diff --git a/test/optimism/deposit-gas-estimation.test.ts b/test/optimism/deposit-gas-estimation.test.ts index 25e7252a..79ee4185 100644 --- a/test/optimism/deposit-gas-estimation.test.ts +++ b/test/optimism/deposit-gas-estimation.test.ts @@ -4,8 +4,6 @@ import env from "../../utils/env"; import { wei } from "../../utils/wei"; import optimism from "../../utils/optimism"; import testing, { scenario } from "../../utils/testing"; -import { ethers } from "hardhat"; -import { BigNumber } from "ethers"; scenario("Optimism :: Bridging integration test", ctxFactory) .after(async (ctx) => { @@ -77,11 +75,7 @@ scenario("Optimism :: Bridging integration test", ctxFactory) l2Token, l1TokenRebasable, l1ERC20TokenBridge, - l2TokenRebasable, - l1CrossDomainMessenger, - l2ERC20TokenBridge, - tokensRateOracle, - l1Provider + l2TokenRebasable } = ctx; const { accountA: tokenHolderA } = ctx.accounts; @@ -99,25 +93,20 @@ scenario("Optimism :: Bridging integration test", ctxFactory) l1ERC20TokenBridge.address ); - for(var x = 0; x< 2; ++x) { - const tx0 = await l1ERC20TokenBridge - .connect(tokenHolderA.l1Signer) - .depositERC20( - l1Token.address, - l2Token.address, - 0, - 200_000, - "0x" - ); - - const receipt0 = await tx0.wait(); - - console.log("l1Token gasUsed=",receipt0.gasUsed); - } + const tx0 = await l1ERC20TokenBridge + .connect(tokenHolderA.l1Signer) + .depositERC20( + l1Token.address, + l2Token.address, + 0, + 200_000, + "0x" + ); - for(var x = 0; x< 2; ++x) { + const receipt0 = await tx0.wait(); + console.log("l1Token gasUsed=",receipt0.gasUsed); - const tx = await l1ERC20TokenBridge + const tx1 = await l1ERC20TokenBridge .connect(tokenHolderA.l1Signer) .depositERC20( l1TokenRebasable.address, @@ -127,62 +116,11 @@ scenario("Optimism :: Bridging integration test", ctxFactory) "0x" ); - const receipt1 = await tx.wait(); - + const receipt1 = await tx1.wait(); console.log("l1TokenRebasable gasUsed=",receipt1.gasUsed); - } - //const gasDifference = receipt1.gasUsed.sub(receipt0.gasUsed); - - //console.log("gasUsed difference=", gasDifference); - - // const blockNumber = await l1Provider.getBlockNumber(); - // const blockTimestamp = (await l1Provider.getBlock(blockNumber)).timestamp; - // const blockTimestampStr = ethers.utils.hexZeroPad(ethers.utils.hexlify(blockTimestamp), 32) - // const tokensPerStEthStr = ethers.utils.hexZeroPad(tokensPerStEth.toHexString(), 32) - // const dataToSend = ethers.utils.hexConcat([tokensPerStEthStr, blockTimestampStr]); - - - // await assert.emits(l1ERC20TokenBridge, tx, "ERC20DepositInitiated", [ - // l1TokenRebasable.address, - // l2TokenRebasable.address, - // tokenHolderA.address, - // tokenHolderA.address, - // 0, - // dataToSend, - // ]); - - // const l2DepositCalldata = l2ERC20TokenBridge.interface.encodeFunctionData( - // "finalizeDeposit", - // [ - // l1TokenRebasable.address, - // l2TokenRebasable.address, - // tokenHolderA.address, - // tokenHolderA.address, - // 0, - // dataToSend, - // ] - // ); - - // const messageNonce = await l1CrossDomainMessenger.messageNonce(); - - // await assert.emits(l1CrossDomainMessenger, tx, "SentMessage", [ - // l2ERC20TokenBridge.address, - // l1ERC20TokenBridge.address, - // l2DepositCalldata, - // messageNonce, - // 200_000, - // ]); - - // assert.equalBN( - // await l1Token.balanceOf(l1ERC20TokenBridge.address), - // l1ERC20TokenBridgeBalanceBefore - // ); - - // assert.equalBN( - // await l1TokenRebasable.balanceOf(tokenHolderA.address), - // tokenHolderABalanceBefore - // ); + const gasDifference = receipt1.gasUsed.sub(receipt0.gasUsed); + console.log("gasUsed difference=", gasDifference); }) From 8098fe17dbc63a7e4603eaf9c01eabe62d3979c9 Mon Sep 17 00:00:00 2001 From: Anton Kovalchuk Date: Sat, 16 Dec 2023 13:34:34 +0100 Subject: [PATCH 013/148] fix tests --- contracts/BridgeableTokens.sol | 40 +++-------- .../arbitrum/InterchainERC20TokenGateway.sol | 18 ++--- contracts/arbitrum/L1ERC20TokenGateway.sol | 12 ++-- contracts/arbitrum/L2ERC20TokenGateway.sol | 22 +++--- .../optimism/BridgeableTokensOptimism.sol | 67 +++++++++++++++++++ contracts/optimism/L1ERC20TokenBridge.sol | 18 +++-- contracts/optimism/L2ERC20TokenBridge.sol | 15 ++--- test/optimism/L1ERC20TokenBridge.unit.test.ts | 8 ++- 8 files changed, 118 insertions(+), 82 deletions(-) create mode 100644 contracts/optimism/BridgeableTokensOptimism.sol diff --git a/contracts/BridgeableTokens.sol b/contracts/BridgeableTokens.sol index 6416d97d..52ec31af 100644 --- a/contracts/BridgeableTokens.sol +++ b/contracts/BridgeableTokens.sol @@ -6,32 +6,22 @@ pragma solidity 0.8.10; /// @author psirex /// @notice Contains the logic for validation of tokens used in the bridging process contract BridgeableTokens { - /// @notice Address of the bridged non rebasable token in the L1 chain - address public immutable l1TokenNonRebasable; + /// @notice Address of the bridged token in the L1 chain + address public immutable l1Token; - /// @notice Address of the bridged rebasable token in the L1 chain - address public immutable l1TokenRebasable; + /// @notice Address of the token minted on the L2 chain when token bridged + address public immutable l2Token; - /// @notice Address of the non rebasable token minted on the L2 chain when token bridged - address public immutable l2TokenNonRebasable; - - /// @notice Address of the rebasable token minted on the L2 chain when token bridged - address public immutable l2TokenRebasable; - - /// @param l1TokenNonRebasable_ Address of the bridged non rebasable token in the L1 chain - /// @param l1TokenRebasable_ Address of the bridged rebasable token in the L1 chain - /// @param l2TokenNonRebasable_ Address of the non rebasable token minted on the L2 chain when token bridged - /// @param l2TokenRebasable_ Address of the rebasable token minted on the L2 chain when token bridged - constructor(address l1TokenNonRebasable_, address l1TokenRebasable_, address l2TokenNonRebasable_, address l2TokenRebasable_) { - l1TokenNonRebasable = l1TokenNonRebasable_; - l1TokenRebasable = l1TokenRebasable_; - l2TokenNonRebasable = l2TokenNonRebasable_; - l2TokenRebasable = l2TokenRebasable_; + /// @param l1Token_ Address of the bridged token in the L1 chain + /// @param l2Token_ Address of the token minted on the L2 chain when token bridged + constructor(address l1Token_, address l2Token_) { + l1Token = l1Token_; + l2Token = l2Token_; } /// @dev Validates that passed l1Token_ is supported by the bridge modifier onlySupportedL1Token(address l1Token_) { - if (l1Token_ != l1TokenNonRebasable && l1Token_ != l1TokenRebasable) { + if (l1Token_ != l1Token) { revert ErrorUnsupportedL1Token(); } _; @@ -39,7 +29,7 @@ contract BridgeableTokens { /// @dev Validates that passed l2Token_ is supported by the bridge modifier onlySupportedL2Token(address l2Token_) { - if (l2Token_ != l2TokenNonRebasable && l2Token_ != l2TokenRebasable) { + if (l2Token_ != l2Token) { revert ErrorUnsupportedL2Token(); } _; @@ -53,14 +43,6 @@ contract BridgeableTokens { _; } - function isRebasableTokenFlow(address l1Token_, address l2Token_) internal view returns (bool) { - return l1Token_ == l1TokenRebasable && l2Token_ == l2TokenRebasable; - } - - function isNonRebasableTokenFlow(address l1Token_, address l2Token_) internal view returns (bool) { - return l1Token_ == l1TokenNonRebasable && l2Token_ == l2TokenNonRebasable; - } - error ErrorUnsupportedL1Token(); error ErrorUnsupportedL2Token(); error ErrorAccountIsZeroAddress(); diff --git a/contracts/arbitrum/InterchainERC20TokenGateway.sol b/contracts/arbitrum/InterchainERC20TokenGateway.sol index 78f9be68..329f4c87 100644 --- a/contracts/arbitrum/InterchainERC20TokenGateway.sol +++ b/contracts/arbitrum/InterchainERC20TokenGateway.sol @@ -24,18 +24,14 @@ abstract contract InterchainERC20TokenGateway is /// @param router_ Address of the router in the corresponding chain /// @param counterpartGateway_ Address of the counterpart gateway used in the bridging process - /// @param l1TokenNonRebasable Address of the bridged token in the Ethereum chain - /// @param l1TokenRebasable_ Address of the bridged token in the Ethereum chain - /// @param l2TokenNonRebasable_ Address of the token minted on the Arbitrum chain when token bridged - /// @param l2TokenRebasable_ Address of the token minted on the Arbitrum chain when token bridged + /// @param l1Token_ Address of the bridged token in the Ethereum chain + /// @param l2Token_ Address of the token minted on the Arbitrum chain when token bridged constructor( address router_, address counterpartGateway_, - address l1TokenNonRebasable, - address l1TokenRebasable_, - address l2TokenNonRebasable_, - address l2TokenRebasable_ - ) BridgeableTokens(l1TokenNonRebasable, l1TokenRebasable_, l2TokenNonRebasable_, l2TokenRebasable_) { + address l1Token_, + address l2Token_ + ) BridgeableTokens(l1Token_, l2Token_) { router = router_; counterpartGateway = counterpartGateway_; } @@ -48,8 +44,8 @@ abstract contract InterchainERC20TokenGateway is view returns (address) { - if (l1Token_ == l1TokenRebasable) { - return l2TokenNonRebasable; + if (l1Token_ == l1Token) { + return l2Token; } return address(0); } diff --git a/contracts/arbitrum/L1ERC20TokenGateway.sol b/contracts/arbitrum/L1ERC20TokenGateway.sol index 85161aec..1be951aa 100644 --- a/contracts/arbitrum/L1ERC20TokenGateway.sol +++ b/contracts/arbitrum/L1ERC20TokenGateway.sol @@ -32,17 +32,13 @@ contract L1ERC20TokenGateway is address router_, address counterpartGateway_, address l1Token_, - address l1TokenRebasable_, - address l2Token_, - address l2TokenRebasable_ + address l2Token_ ) InterchainERC20TokenGateway( router_, counterpartGateway_, l1Token_, - l1TokenRebasable_, - l2Token_, - l2TokenRebasable_ + l2Token_ ) L1CrossDomainEnabled(inbox_) {} @@ -82,7 +78,7 @@ contract L1ERC20TokenGateway is }) ); - emit DepositInitiated(l1TokenNonRebasable, from, to_, retryableTicketId, amount_); + emit DepositInitiated(l1Token, from, to_, retryableTicketId, amount_); return abi.encode(retryableTicketId); } @@ -117,7 +113,7 @@ contract L1ERC20TokenGateway is sendCrossDomainMessage( from_, counterpartGateway, - getOutboundCalldata(l1TokenNonRebasable, from_, to_, amount_, ""), + getOutboundCalldata(l1Token, from_, to_, amount_, ""), messageOptions ); } diff --git a/contracts/arbitrum/L2ERC20TokenGateway.sol b/contracts/arbitrum/L2ERC20TokenGateway.sol index 0c6df5ac..5853d0ac 100644 --- a/contracts/arbitrum/L2ERC20TokenGateway.sol +++ b/contracts/arbitrum/L2ERC20TokenGateway.sol @@ -21,26 +21,20 @@ contract L2ERC20TokenGateway is /// @param arbSys_ Address of the Arbitrum’s ArbSys contract in the L2 chain /// @param router_ Address of the router in the L2 chain /// @param counterpartGateway_ Address of the counterpart L1 gateway - /// @param l1TokenNonRebasable_ Address of the bridged token in the L1 chain - /// @param l1TokenRebasable_ Address of the bridged token in the L1 chain - /// @param l2TokenNonRebasable_ Address of the token minted on the Arbitrum chain when token bridged - /// @param l2TokenRebasable_ Address of the token minted on the Arbitrum chain when token bridged + /// @param l1Token_ Address of the bridged token in the L1 chain + /// @param l2Token_ Address of the token minted on the Arbitrum chain when token bridged constructor( address arbSys_, address router_, address counterpartGateway_, - address l1TokenNonRebasable_, - address l1TokenRebasable_, - address l2TokenNonRebasable_, - address l2TokenRebasable_ + address l1Token_, + address l2Token_ ) InterchainERC20TokenGateway( router_, counterpartGateway_, - l1TokenNonRebasable_, - l1TokenRebasable_, - l2TokenNonRebasable_, - l2TokenRebasable_ + l1Token_, + l2Token_ ) L2CrossDomainEnabled(arbSys_) {} @@ -61,7 +55,7 @@ contract L2ERC20TokenGateway is { address from = L2OutboundDataParser.decode(router, data_); - IERC20Bridged(l2TokenNonRebasable).bridgeBurn(from, amount_); + IERC20Bridged(l2Token).bridgeBurn(from, amount_); uint256 id = sendCrossDomainMessage( from, @@ -89,7 +83,7 @@ contract L2ERC20TokenGateway is onlySupportedL1Token(l1Token_) onlyFromCrossDomainAccount(counterpartGateway) { - IERC20Bridged(l2TokenNonRebasable).bridgeMint(to_, amount_); + IERC20Bridged(l2Token).bridgeMint(to_, amount_); emit DepositFinalized(l1Token_, from_, to_, amount_); } diff --git a/contracts/optimism/BridgeableTokensOptimism.sol b/contracts/optimism/BridgeableTokensOptimism.sol new file mode 100644 index 00000000..087c845d --- /dev/null +++ b/contracts/optimism/BridgeableTokensOptimism.sol @@ -0,0 +1,67 @@ +// SPDX-FileCopyrightText: 2022 Lido +// SPDX-License-Identifier: GPL-3.0 + +pragma solidity 0.8.10; + +/// @author psirex +/// @notice Contains the logic for validation of tokens used in the bridging process +contract BridgeableTokensOptimism { + /// @notice Address of the bridged non rebasable token in the L1 chain + address public immutable l1TokenNonRebasable; + + /// @notice Address of the bridged rebasable token in the L1 chain + address public immutable l1TokenRebasable; + + /// @notice Address of the non rebasable token minted on the L2 chain when token bridged + address public immutable l2TokenNonRebasable; + + /// @notice Address of the rebasable token minted on the L2 chain when token bridged + address public immutable l2TokenRebasable; + + /// @param l1TokenNonRebasable_ Address of the bridged non rebasable token in the L1 chain + /// @param l1TokenRebasable_ Address of the bridged rebasable token in the L1 chain + /// @param l2TokenNonRebasable_ Address of the non rebasable token minted on the L2 chain when token bridged + /// @param l2TokenRebasable_ Address of the rebasable token minted on the L2 chain when token bridged + constructor(address l1TokenNonRebasable_, address l1TokenRebasable_, address l2TokenNonRebasable_, address l2TokenRebasable_) { + l1TokenNonRebasable = l1TokenNonRebasable_; + l1TokenRebasable = l1TokenRebasable_; + l2TokenNonRebasable = l2TokenNonRebasable_; + l2TokenRebasable = l2TokenRebasable_; + } + + /// @dev Validates that passed l1Token_ is supported by the bridge + modifier onlySupportedL1Token(address l1Token_) { + if (l1Token_ != l1TokenNonRebasable && l1Token_ != l1TokenRebasable) { + revert ErrorUnsupportedL1Token(); + } + _; + } + + /// @dev Validates that passed l2Token_ is supported by the bridge + modifier onlySupportedL2Token(address l2Token_) { + if (l2Token_ != l2TokenNonRebasable && l2Token_ != l2TokenRebasable) { + revert ErrorUnsupportedL2Token(); + } + _; + } + + /// @dev validates that account_ is not zero address + modifier onlyNonZeroAccount(address account_) { + if (account_ == address(0)) { + revert ErrorAccountIsZeroAddress(); + } + _; + } + + function isRebasableTokenFlow(address l1Token_, address l2Token_) internal view returns (bool) { + return l1Token_ == l1TokenRebasable && l2Token_ == l2TokenRebasable; + } + + function isNonRebasableTokenFlow(address l1Token_, address l2Token_) internal view returns (bool) { + return l1Token_ == l1TokenNonRebasable && l2Token_ == l2TokenNonRebasable; + } + + error ErrorUnsupportedL1Token(); + error ErrorUnsupportedL2Token(); + error ErrorAccountIsZeroAddress(); +} diff --git a/contracts/optimism/L1ERC20TokenBridge.sol b/contracts/optimism/L1ERC20TokenBridge.sol index 7af0573e..67009c86 100644 --- a/contracts/optimism/L1ERC20TokenBridge.sol +++ b/contracts/optimism/L1ERC20TokenBridge.sol @@ -11,7 +11,7 @@ import {IL1ERC20Bridge} from "./interfaces/IL1ERC20Bridge.sol"; import {IL2ERC20Bridge} from "./interfaces/IL2ERC20Bridge.sol"; import {BridgingManager} from "../BridgingManager.sol"; -import {BridgeableTokens} from "../BridgeableTokens.sol"; +import {BridgeableTokensOptimism} from "./BridgeableTokensOptimism.sol"; import {CrossDomainEnabled} from "./CrossDomainEnabled.sol"; import {DepositDataCodec} from "./DepositDataCodec.sol"; @@ -21,7 +21,6 @@ import "hardhat/console.sol"; // Check if Optimism changed API for bridges. They could depricate methods. // Optimise gas usage with data transfer. Maybe cache rate and see if it changed. - /// @author psirex, kovalgek /// @notice The L1 ERC20 token bridge locks bridged tokens on the L1 side, sends deposit messages /// on the L2 side, and finalizes token withdrawals from L2. Additionally, adds the methods for @@ -29,7 +28,7 @@ import "hardhat/console.sol"; contract L1ERC20TokenBridge is IL1ERC20Bridge, BridgingManager, - BridgeableTokens, + BridgeableTokensOptimism, CrossDomainEnabled, DepositDataCodec { @@ -51,7 +50,7 @@ contract L1ERC20TokenBridge is address l1TokenRebasable_, address l2TokenNonRebasable_, address l2TokenRebasable_ - ) CrossDomainEnabled(messenger_) BridgeableTokens(l1TokenNonRebasable_, l1TokenRebasable_, l2TokenNonRebasable_, l2TokenRebasable_) { + ) CrossDomainEnabled(messenger_) BridgeableTokensOptimism(l1TokenNonRebasable_, l1TokenRebasable_, l2TokenNonRebasable_, l2TokenRebasable_) { l2TokenBridge = l2TokenBridge_; } @@ -138,9 +137,8 @@ contract L1ERC20TokenBridge is uint32 l2Gas_, bytes memory data_ ) internal { - if (isRebasableTokenFlow(l1Token_, l2Token_)) { - console.log("isRebasableTokenFlow"); + DepositData memory depositData = DepositData({ rate: IERC20Wrapable(l1TokenNonRebasable).tokensPerStEth(), // replace by stETHPerToken time: block.timestamp, @@ -158,13 +156,13 @@ contract L1ERC20TokenBridge is // maybe loosing 1 wei for stETH. Check another method IERC20(l1TokenRebasable).safeTransferFrom(msg.sender, address(this), amount_); IERC20(l1TokenRebasable).approve(l1TokenNonRebasable, amount_); + // when 1 wei wasnt't transfer, can this wrap be failed? uint256 wstETHAmount = IERC20Wrapable(l1TokenNonRebasable).wrap(amount_); _initiateERC20Deposit(l1TokenRebasable, l2TokenRebasable, msg.sender, to_, wstETHAmount, l2Gas_, encodedDepositData); - } else if (isNonRebasableTokenFlow(l1Token_, l2Token_)) { - console.log("isNonRebasableTokenFlow"); - // IERC20(l1TokenNonRebasable).safeTransferFrom(msg.sender, address(this), amount_); + } else if (isNonRebasableTokenFlow(l1Token_, l2Token_)) { + IERC20(l1TokenNonRebasable).safeTransferFrom(msg.sender, address(this), amount_); _initiateERC20Deposit(l1TokenNonRebasable, l2TokenNonRebasable, msg.sender, to_, amount_, l2Gas_, data_); } } @@ -197,7 +195,7 @@ contract L1ERC20TokenBridge is amount_, data_ ); - console.logBytes(data_); + sendCrossDomainMessage(l2TokenBridge, l2Gas_, message); emit ERC20DepositInitiated( diff --git a/contracts/optimism/L2ERC20TokenBridge.sol b/contracts/optimism/L2ERC20TokenBridge.sol index de6e467a..323e2969 100644 --- a/contracts/optimism/L2ERC20TokenBridge.sol +++ b/contracts/optimism/L2ERC20TokenBridge.sol @@ -13,7 +13,7 @@ import {IERC20Wrapable} from "../token/interfaces/IERC20Wrapable.sol"; import {SafeERC20} from "@openzeppelin/contracts/token/ERC20/utils/SafeERC20.sol"; import {BridgingManager} from "../BridgingManager.sol"; -import {BridgeableTokens} from "../BridgeableTokens.sol"; +import {BridgeableTokensOptimism} from "./BridgeableTokensOptimism.sol"; import {CrossDomainEnabled} from "./CrossDomainEnabled.sol"; import {DepositDataCodec} from "./DepositDataCodec.sol"; @@ -28,7 +28,7 @@ import { console } from "hardhat/console.sol"; contract L2ERC20TokenBridge is IL2ERC20Bridge, BridgingManager, - BridgeableTokens, + BridgeableTokensOptimism, CrossDomainEnabled, DepositDataCodec { @@ -53,7 +53,7 @@ contract L2ERC20TokenBridge is address l2TokenNonRebasable_, address l2TokenRebasable_, address tokensRateOracle_ - ) CrossDomainEnabled(messenger_) BridgeableTokens(l1TokenNonRebasable_, l1TokenRebasable_, l2TokenNonRebasable_, l2TokenRebasable_) { + ) CrossDomainEnabled(messenger_) BridgeableTokensOptimism(l1TokenNonRebasable_, l1TokenRebasable_, l2TokenNonRebasable_, l2TokenRebasable_) { l1TokenBridge = l1TokenBridge_; tokensRateOracle = tokensRateOracle_; } @@ -112,16 +112,15 @@ contract L2ERC20TokenBridge is onlySupportedL2Token(l2Token_) onlyFromCrossDomainAccount(l1TokenBridge) { - DepositData memory depositData = decodeDepositData(data_); - ITokensRateOracle(tokensRateOracle).updateRate(int256(depositData.rate), depositData.time); - if (isRebasableTokenFlow(l1Token_, l2Token_)) { + DepositData memory depositData = decodeDepositData(data_); + ITokensRateOracle(tokensRateOracle).updateRate(int256(depositData.rate), depositData.time); ERC20Rebasable(l2TokenRebasable).mintShares(to_, amount_); + emit DepositFinalized(l1Token_, l2Token_, from_, to_, amount_, depositData.data); } else if (isNonRebasableTokenFlow(l1Token_, l2Token_)) { IERC20Bridged(l2TokenNonRebasable).bridgeMint(to_, amount_); + emit DepositFinalized(l1Token_, l2Token_, from_, to_, amount_, data_); } - - emit DepositFinalized(l1Token_, l2Token_, from_, to_, amount_, depositData.data); } /// @notice Performs the logic for withdrawals by burning the token and informing diff --git a/test/optimism/L1ERC20TokenBridge.unit.test.ts b/test/optimism/L1ERC20TokenBridge.unit.test.ts index 09aeefa2..774811c2 100644 --- a/test/optimism/L1ERC20TokenBridge.unit.test.ts +++ b/test/optimism/L1ERC20TokenBridge.unit.test.ts @@ -456,7 +456,7 @@ unit("Optimism :: L1ERC20TokenBridge", ctxFactory) .run(); async function ctxFactory() { - const [deployer, l2TokenBridgeEOA, stranger, recipient] = + const [deployer, l2TokenBridgeEOA, stranger, recipient, rebasableToken] = await hre.ethers.getSigners(); const l1MessengerStub = await new CrossDomainMessengerStub__factory( @@ -488,7 +488,9 @@ async function ctxFactory() { l1MessengerStub.address, l2TokenBridgeEOA.address, l1TokenStub.address, - l2TokenStub.address + rebasableToken.address, + l2TokenStub.address, + rebasableToken.address ); const l1TokenBridgeProxy = await new OssifiableProxy__factory( @@ -534,6 +536,8 @@ async function ctxFactory() { stubs: { l1Token: l1TokenStub, l2Token: l2TokenStub, + l1TokenRebasable: l1TokenStub, + l2TokenRebasable: l2TokenStub, l1Messenger: l1MessengerStub, }, l1TokenBridge, From 3335339b8fca4baa5920a48b39d74005737fb882 Mon Sep 17 00:00:00 2001 From: Anton Kovalchuk Date: Wed, 20 Dec 2023 16:33:21 +0100 Subject: [PATCH 014/148] add token rate oracle --- contracts/optimism/L2ERC20TokenBridge.sol | 13 +- contracts/optimism/TokenRateOracle.sol | 158 ++++++++++++++++++ ...OracleStub.sol => TokenRateOracleStub.sol} | 8 +- contracts/token/ERC20Rebasable.sol | 12 +- .../token/interfaces/ITokenRateOracle.sol | 30 ++++ .../token/interfaces/ITokensRateOracle.sol | 34 ---- .../optimism.integration.test.ts | 4 +- test/optimism/L2ERC20TokenBridge.unit.test.ts | 6 +- test/optimism/TokenRateOracle.unit.test.ts | 95 +++++++++++ test/optimism/deposit-gas-estimation.test.ts | 2 - test/token/ERC20Rebasable.unit.test.ts | 4 +- 11 files changed, 308 insertions(+), 58 deletions(-) create mode 100644 contracts/optimism/TokenRateOracle.sol rename contracts/stubs/{TokensRateOracleStub.sol => TokenRateOracleStub.sol} (83%) create mode 100644 contracts/token/interfaces/ITokenRateOracle.sol delete mode 100644 contracts/token/interfaces/ITokensRateOracle.sol create mode 100644 test/optimism/TokenRateOracle.unit.test.ts diff --git a/contracts/optimism/L2ERC20TokenBridge.sol b/contracts/optimism/L2ERC20TokenBridge.sol index 323e2969..724c2db9 100644 --- a/contracts/optimism/L2ERC20TokenBridge.sol +++ b/contracts/optimism/L2ERC20TokenBridge.sol @@ -6,10 +6,10 @@ pragma solidity 0.8.10; import {IL1ERC20Bridge} from "./interfaces/IL1ERC20Bridge.sol"; import {IL2ERC20Bridge} from "./interfaces/IL2ERC20Bridge.sol"; import {IERC20Bridged} from "../token/interfaces/IERC20Bridged.sol"; -import {ITokensRateOracle} from "../token/interfaces/ITokensRateOracle.sol"; +import {ITokenRateOracle} from "../token/interfaces/ITokenRateOracle.sol"; import {ERC20Rebasable} from "../token/ERC20Rebasable.sol"; -import {IERC20} from "@openzeppelin/contracts/token/ERC20/IERC20.sol"; import {IERC20Wrapable} from "../token/interfaces/IERC20Wrapable.sol"; +import {IERC20} from "@openzeppelin/contracts/token/ERC20/IERC20.sol"; import {SafeERC20} from "@openzeppelin/contracts/token/ERC20/utils/SafeERC20.sol"; import {BridgingManager} from "../BridgingManager.sol"; @@ -37,8 +37,6 @@ contract L2ERC20TokenBridge is /// @inheritdoc IL2ERC20Bridge address public immutable l1TokenBridge; - address public immutable tokensRateOracle; - /// @param messenger_ L2 messenger address being used for cross-chain communications /// @param l1TokenBridge_ Address of the corresponding L1 bridge /// @param l1TokenNonRebasable_ Address of the bridged token in the L1 chain @@ -51,11 +49,9 @@ contract L2ERC20TokenBridge is address l1TokenNonRebasable_, address l1TokenRebasable_, address l2TokenNonRebasable_, - address l2TokenRebasable_, - address tokensRateOracle_ + address l2TokenRebasable_ ) CrossDomainEnabled(messenger_) BridgeableTokensOptimism(l1TokenNonRebasable_, l1TokenRebasable_, l2TokenNonRebasable_, l2TokenRebasable_) { l1TokenBridge = l1TokenBridge_; - tokensRateOracle = tokensRateOracle_; } /// @inheritdoc IL2ERC20Bridge @@ -114,7 +110,8 @@ contract L2ERC20TokenBridge is { if (isRebasableTokenFlow(l1Token_, l2Token_)) { DepositData memory depositData = decodeDepositData(data_); - ITokensRateOracle(tokensRateOracle).updateRate(int256(depositData.rate), depositData.time); + ITokenRateOracle tokensRateOracle = ERC20Rebasable(l2TokenRebasable).tokensRateOracle(); + tokensRateOracle.updateRate(int256(depositData.rate), depositData.time); ERC20Rebasable(l2TokenRebasable).mintShares(to_, amount_); emit DepositFinalized(l1Token_, l2Token_, from_, to_, amount_, depositData.data); } else if (isNonRebasableTokenFlow(l1Token_, l2Token_)) { diff --git a/contracts/optimism/TokenRateOracle.sol b/contracts/optimism/TokenRateOracle.sol new file mode 100644 index 00000000..0f8cd320 --- /dev/null +++ b/contracts/optimism/TokenRateOracle.sol @@ -0,0 +1,158 @@ +// SPDX-FileCopyrightText: 2022 Lido +// SPDX-License-Identifier: GPL-3.0 + +pragma solidity 0.8.10; + +import {ITokenRateOracle} from "../token/interfaces/ITokenRateOracle.sol"; +// import { SafeCast } from "@openzeppelin/contracts-v4.4/utils/math/SafeCast.sol"; + +contract TokenRateOracle is ITokenRateOracle { + + /// Chain specification + uint256 private immutable slotsPerEpoch; + uint256 private immutable secondsPerSlot; + uint256 private immutable genesisTime; + uint256 private immutable initialEpoch; + uint256 private immutable epochsPerFrame; + + error InvalidChainConfig(); + error InitialEpochRefSlotCannotBeEarlierThanProcessingSlot(); + error InitialEpochIsYetToArrive(); + + int256 private tokenRate; + uint8 private decimalsInAnswer; + uint256 private rateL1Timestamp; + uint80 private answeredInRound; + + constructor( + uint256 slotsPerEpoch_, + uint256 secondsPerSlot_, + uint256 genesisTime_, + uint256 initialEpoch_, + uint256 epochsPerFrame_ + ) { + if (slotsPerEpoch_ == 0) revert InvalidChainConfig(); + if (secondsPerSlot_ == 0) revert InvalidChainConfig(); + + // Should I use toUint64(); + slotsPerEpoch = slotsPerEpoch_; + secondsPerSlot = secondsPerSlot_; + genesisTime = genesisTime_; + initialEpoch = initialEpoch_; + epochsPerFrame = epochsPerFrame_; + } + + /// @inheritdoc ITokenRateOracle + /// @return roundId_ is reference slot of HashConsensus + /// @return answer_ is wstETH/stETH token rate. + /// @return startedAt_ is HashConsensus frame start. + /// @return updatedAt_ is L2 timestamp of token rate update. + /// @return answeredInRound_ is the round ID of the round in which the answer was computed + function latestRoundData() external view returns ( + uint80 roundId_, + int256 answer_, + uint256 startedAt_, + uint256 updatedAt_, + uint80 answeredInRound_ + ) { + uint256 refSlot = _getRefSlot(initialEpoch, epochsPerFrame); + uint80 roundId = uint80(refSlot); + uint256 startedAt = _computeTimestampAtSlot(refSlot); + + return ( + roundId, + tokenRate, + startedAt, + rateL1Timestamp, + answeredInRound + ); + } + + /// @inheritdoc ITokenRateOracle + function latestAnswer() external view returns (int256) { + return tokenRate; + } + + /// @inheritdoc ITokenRateOracle + function decimals() external view returns (uint8) { + return decimalsInAnswer; + } + + /// @inheritdoc ITokenRateOracle + function updateRate(int256 rate, uint256 rateL1Timestamp_) external { + // check timestamp not late as current one. + if (rateL1Timestamp_ < _getTime()) { + return; + } + tokenRate = rate; + rateL1Timestamp = rateL1Timestamp_; + answeredInRound = 666; + decimalsInAnswer = 10; + } + + /// Frame utilities + + function _getTime() internal virtual view returns (uint256) { + return block.timestamp; // solhint-disable-line not-rely-on-time + } + + function _getRefSlot(uint256 initialEpoch_, uint256 epochsPerFrame_) internal view returns (uint256) { + return _getRefSlotAtTimestamp(_getTime(), initialEpoch_, epochsPerFrame_); + } + + function _getRefSlotAtTimestamp(uint256 timestamp_, uint256 initialEpoch_, uint256 epochsPerFrame_) + internal view returns (uint256) + { + return _getRefSlotAtIndex(_computeFrameIndex(timestamp_, initialEpoch_, epochsPerFrame_), initialEpoch_, epochsPerFrame_); + } + + function _getRefSlotAtIndex(uint256 frameIndex_, uint256 initialEpoch_, uint256 epochsPerFrame_) + internal view returns (uint256) + { + uint256 frameStartEpoch = _computeStartEpochOfFrameWithIndex(frameIndex_, initialEpoch_, epochsPerFrame_); + uint256 frameStartSlot = _computeStartSlotAtEpoch(frameStartEpoch); + return uint64(frameStartSlot - 1); + } + + function _computeStartSlotAtEpoch(uint256 epoch_) internal view returns (uint256) { + // See: github.com/ethereum/consensus-specs/blob/dev/specs/phase0/beacon-chain.md#compute_start_slot_at_epoch + return epoch_ * slotsPerEpoch; + } + + function _computeStartEpochOfFrameWithIndex(uint256 frameIndex_, uint256 initialEpoch_, uint256 epochsPerFrame_) + internal pure returns (uint256) + { + return initialEpoch_ + frameIndex_ * epochsPerFrame_; + } + + function _computeFrameIndex( + uint256 timestamp_, + uint256 initialEpoch_, + uint256 epochsPerFrame_ + ) internal view returns (uint256) + { + uint256 epoch = _computeEpochAtTimestamp(timestamp_); + if (epoch < initialEpoch_) { + revert InitialEpochIsYetToArrive(); + } + return (epoch - initialEpoch_) / epochsPerFrame_; + } + + function _computeEpochAtTimestamp(uint256 timestamp_) internal view returns (uint256) { + return _computeEpochAtSlot(_computeSlotAtTimestamp(timestamp_)); + } + + function _computeEpochAtSlot(uint256 slot_) internal view returns (uint256) { + // See: github.com/ethereum/consensus-specs/blob/dev/specs/phase0/beacon-chain.md#compute_epoch_at_slot + return slot_ / slotsPerEpoch; + } + + function _computeSlotAtTimestamp(uint256 timestamp_) internal view returns (uint256) { + return (timestamp_ - genesisTime) / secondsPerSlot; + } + + function _computeTimestampAtSlot(uint256 slot_) internal view returns (uint256) { + // See: github.com/ethereum/consensus-specs/blob/dev/specs/bellatrix/beacon-chain.md#compute_timestamp_at_slot + return genesisTime + slot_ * secondsPerSlot; + } +} \ No newline at end of file diff --git a/contracts/stubs/TokensRateOracleStub.sol b/contracts/stubs/TokenRateOracleStub.sol similarity index 83% rename from contracts/stubs/TokensRateOracleStub.sol rename to contracts/stubs/TokenRateOracleStub.sol index 65ed5642..4be61315 100644 --- a/contracts/stubs/TokensRateOracleStub.sol +++ b/contracts/stubs/TokenRateOracleStub.sol @@ -3,9 +3,9 @@ pragma solidity 0.8.10; -import {ITokensRateOracle} from "../token/interfaces/ITokensRateOracle.sol"; +import {ITokenRateOracle} from "../token/interfaces/ITokenRateOracle.sol"; -contract TokensRateOracleStub is ITokensRateOracle { +contract TokenRateOracleStub is ITokenRateOracle { uint8 public _decimals; @@ -45,6 +45,10 @@ contract TokensRateOracleStub is ITokensRateOracle { return (0,latestRoundDataAnswer,0,latestRoundDataUpdatedAt,0); } + function latestAnswer() external view returns (int256) { + return latestRoundDataAnswer; + } + function updateRate(int256 rate, uint256 updatedAt) external { // check timestamp not late as current one. latestRoundDataAnswer = rate; diff --git a/contracts/token/ERC20Rebasable.sol b/contracts/token/ERC20Rebasable.sol index ab5658bc..c975689a 100644 --- a/contracts/token/ERC20Rebasable.sol +++ b/contracts/token/ERC20Rebasable.sol @@ -5,7 +5,7 @@ pragma solidity 0.8.10; import {IERC20} from "@openzeppelin/contracts/token/ERC20/IERC20.sol"; import {IERC20Wrapable} from "./interfaces/IERC20Wrapable.sol"; -import {ITokensRateOracle} from "./interfaces/ITokensRateOracle.sol"; +import {ITokenRateOracle} from "./interfaces/ITokenRateOracle.sol"; import {ERC20Metadata} from "./ERC20Metadata.sol"; import { console } from "hardhat/console.sol"; @@ -25,7 +25,7 @@ contract ERC20Rebasable is IERC20Wrapable, IERC20, ERC20Metadata { error ErrorDecreasedAllowanceBelowZero(); IERC20 public immutable wrappedToken; - ITokensRateOracle public immutable tokensRateOracle; + ITokenRateOracle public immutable tokensRateOracle; /// @param wrappedToken_ address of the ERC20 token to wrap /// @param tokensRateOracle_ address of oracle that returns tokens rate @@ -33,14 +33,14 @@ contract ERC20Rebasable is IERC20Wrapable, IERC20, ERC20Metadata { /// @param symbol_ The symbol of the token /// @param decimals_ The decimals places of the token constructor( - IERC20 wrappedToken_, - ITokensRateOracle tokensRateOracle_, + address wrappedToken_, + address tokensRateOracle_, string memory name_, string memory symbol_, uint8 decimals_ ) ERC20Metadata(name_, symbol_, decimals_) { - wrappedToken = wrappedToken_; - tokensRateOracle = tokensRateOracle_; + wrappedToken = IERC20(wrappedToken_); + tokensRateOracle = ITokenRateOracle(tokensRateOracle_); } /// @notice Sets the name and the symbol of the tokens if they both are empty diff --git a/contracts/token/interfaces/ITokenRateOracle.sol b/contracts/token/interfaces/ITokenRateOracle.sol new file mode 100644 index 00000000..648b346d --- /dev/null +++ b/contracts/token/interfaces/ITokenRateOracle.sol @@ -0,0 +1,30 @@ +// SPDX-FileCopyrightText: 2023 Lido +// SPDX-License-Identifier: GPL-3.0 + +pragma solidity 0.8.10; + +/// @author kovalgek +/// @notice Oracle interface for two tokens rate. A subset of Chainlink data feed interface. +interface ITokenRateOracle { + + /// @notice get data about the latest round. + function latestRoundData() + external + view + returns ( + uint80 roundId, + int256 answer, + uint256 startedAt, + uint256 updatedAt, + uint80 answeredInRound + ); + + /// @notice get answer about the latest round. + function latestAnswer() external view returns (int256); + + /// @notice represents the number of decimals the oracle responses represent. + function decimals() external view returns (uint8); + + /// @notice Updates token rate. + function updateRate(int256 rate, uint256 rateL1Timestamp) external; +} \ No newline at end of file diff --git a/contracts/token/interfaces/ITokensRateOracle.sol b/contracts/token/interfaces/ITokensRateOracle.sol deleted file mode 100644 index c28cabc9..00000000 --- a/contracts/token/interfaces/ITokensRateOracle.sol +++ /dev/null @@ -1,34 +0,0 @@ -// SPDX-FileCopyrightText: 2023 Lido -// SPDX-License-Identifier: GPL-3.0 - -pragma solidity 0.8.10; - -/// @author kovalgek -/// @notice Oracle interface for two tokens rate -interface ITokensRateOracle { - - function updateRate(int256 rate, uint256 updatedAt) external; - - /** - * @notice represents the number of decimals the oracle responses represent. - */ - function decimals() external view returns (uint8); - - /** - * @notice get data about the latest round. - */ - function latestRoundData() - external - view - returns ( - uint80 roundId, - int256 answer, - uint256 startedAt, - uint256 updatedAt, - uint80 answeredInRound - ); -} - -interface ITokensRateOracleUpdatable { - function updateRate(int256 rate, uint256 updatedAt) external; -} \ No newline at end of file diff --git a/test/bridge-executor/optimism.integration.test.ts b/test/bridge-executor/optimism.integration.test.ts index de7f5902..2a8949f4 100644 --- a/test/bridge-executor/optimism.integration.test.ts +++ b/test/bridge-executor/optimism.integration.test.ts @@ -6,7 +6,7 @@ import { OptimismBridgeExecutor__factory, ERC20Bridged__factory, ERC20Rebasable__factory, - TokensRateOracleStub__factory, + TokenRateOracle__factory, } from "../../typechain"; import { wei } from "../../utils/wei"; import optimism from "../../utils/optimism"; @@ -219,7 +219,7 @@ async function ctxFactory() { "TTR" ); - const tokensRateOracleStub = await new TokensRateOracleStub__factory(l2Deployer).deploy(); + const tokensRateOracleStub = await new TokenRateOracle__factory(l2Deployer).deploy(); const optAddresses = optimism.addresses(networkName); diff --git a/test/optimism/L2ERC20TokenBridge.unit.test.ts b/test/optimism/L2ERC20TokenBridge.unit.test.ts index 97e40afa..9c5eae8f 100644 --- a/test/optimism/L2ERC20TokenBridge.unit.test.ts +++ b/test/optimism/L2ERC20TokenBridge.unit.test.ts @@ -375,7 +375,7 @@ unit("Optimism:: L2ERC20TokenBridge", ctxFactory) .run(); async function ctxFactory() { - const [deployer, stranger, recipient, l1TokenBridgeEOA] = + const [deployer, stranger, recipient, l1TokenBridgeEOA, token2] = await hre.ethers.getSigners(); const l2Messenger = await new CrossDomainMessengerStub__factory( @@ -405,7 +405,9 @@ async function ctxFactory() { l2Messenger.address, l1TokenBridgeEOA.address, l1Token.address, - l2Token.address + token2.address, + l2Token.address, + token2.address ); const l2TokenBridgeProxy = await new OssifiableProxy__factory( diff --git a/test/optimism/TokenRateOracle.unit.test.ts b/test/optimism/TokenRateOracle.unit.test.ts new file mode 100644 index 00000000..3858ed2e --- /dev/null +++ b/test/optimism/TokenRateOracle.unit.test.ts @@ -0,0 +1,95 @@ +import hre from "hardhat"; +import { assert } from "chai"; +import { unit } from "../../utils/testing"; +import { TokenRateOracle__factory } from "../../typechain"; +import { ethers } from "ethers"; + +unit("TokenRateOracle", ctxFactory) + + .test("init zero slotsPerEpoch", async (ctx) => { + const [deployer] = await hre.ethers.getSigners(); + await assert.revertsWith(new TokenRateOracle__factory(deployer).deploy( + 0, + 10, + 1000, + 100, + 50 + ), "InvalidChainConfig()"); + }) + + .test("init zero secondsPerSlot", async (ctx) => { + const [deployer] = await hre.ethers.getSigners(); + await assert.revertsWith(new TokenRateOracle__factory(deployer).deploy( + 41, + 0, + 1000, + 100, + 50 + ), "InvalidChainConfig()"); + }) + + .test("state after init", async (ctx) => { + const { tokensRateOracle } = ctx.contracts; + + assert.equalBN(await tokensRateOracle.latestAnswer(), 0); + + const { + roundId_, + answer_, + startedAt_, + updatedAt_, + answeredInRound_ + } = await tokensRateOracle.latestRoundData(); + + assert.equalBN(roundId_, 170307199); + assert.equalBN(answer_, 0); + assert.equalBN(startedAt_, 1703072990); + assert.equalBN(updatedAt_, 0); + assert.equalBN(answeredInRound_, 0); + + assert.equalBN(await tokensRateOracle.decimals(), 0); + }) + + .test("state after update token rate", async (ctx) => { + const { tokensRateOracle } = ctx.contracts; + + await tokensRateOracle.updateRate(2, ethers.constants.MaxInt256 ); + + assert.equalBN(await tokensRateOracle.latestAnswer(), 2); + + const { + roundId_, + answer_, + startedAt_, + updatedAt_, + answeredInRound_ + } = await tokensRateOracle.latestRoundData(); + + assert.equalBN(roundId_, 170307199); + assert.equalBN(answer_, 2); + assert.equalBN(startedAt_, 1703072990); + assert.equalBN(updatedAt_, ethers.constants.MaxInt256); + assert.equalBN(answeredInRound_, 666); + + assert.equalBN(await tokensRateOracle.decimals(), 10); + }) + + .run(); + +async function ctxFactory() { + + const [deployer] = await hre.ethers.getSigners(); + + const tokensRateOracle = await new TokenRateOracle__factory(deployer).deploy( + 32, + 10, + 1000, + 100, + 50 + ); + + return { + accounts: { deployer }, + contracts: { tokensRateOracle } + }; +} diff --git a/test/optimism/deposit-gas-estimation.test.ts b/test/optimism/deposit-gas-estimation.test.ts index 79ee4185..c6fc8db7 100644 --- a/test/optimism/deposit-gas-estimation.test.ts +++ b/test/optimism/deposit-gas-estimation.test.ts @@ -142,8 +142,6 @@ async function ctxFactory() { const l1Snapshot = await l1Provider.send("evm_snapshot", []); const l2Snapshot = await l2Provider.send("evm_snapshot", []); - // await optimism.testing(networkName).stubL1CrossChainMessengerContract(); - const accountA = testing.accounts.accountA(l1Provider, l2Provider); const accountB = testing.accounts.accountB(l1Provider, l2Provider); diff --git a/test/token/ERC20Rebasable.unit.test.ts b/test/token/ERC20Rebasable.unit.test.ts index 849155a7..2d49abc6 100644 --- a/test/token/ERC20Rebasable.unit.test.ts +++ b/test/token/ERC20Rebasable.unit.test.ts @@ -3,7 +3,7 @@ import { assert } from "chai"; import { unit } from "../../utils/testing"; import { wei } from "../../utils/wei"; -import { ERC20Stub__factory, ERC20Rebasable__factory, TokensRateOracleStub__factory, OssifiableProxy__factory } from "../../typechain"; +import { ERC20Stub__factory, ERC20Rebasable__factory, TokenRateOracleStub__factory, OssifiableProxy__factory } from "../../typechain"; import { BigNumber } from "ethers"; @@ -241,7 +241,7 @@ async function ctxFactory() { const wrappedTokenStub = await new ERC20Stub__factory(deployer).deploy(); - const tokensRateOracleStub = await new TokensRateOracleStub__factory(deployer).deploy(); + const tokensRateOracleStub = await new TokenRateOracleStub__factory(deployer).deploy(); const rebasableTokenImpl = await new ERC20Rebasable__factory(deployer).deploy( wrappedTokenStub.address, From 86037dcebc4b61ddf579ea32c76851b1a0e61b7f Mon Sep 17 00:00:00 2001 From: Anton Kovalchuk Date: Sun, 24 Dec 2023 16:44:33 +0100 Subject: [PATCH 015/148] add tests for new token, renaming --- contracts/optimism/L1ERC20TokenBridge.sol | 4 +- contracts/optimism/L2ERC20TokenBridge.sol | 4 +- contracts/stubs/ERC20WrapableStub.sol | 2 +- contracts/stubs/TokenRateOracleStub.sol | 6 +- contracts/token/ERC20Rebasable.sol | 99 +- contracts/token/interfaces/IERC20Wrapable.sol | 2 +- .../token/interfaces/ITokenRateOracle.sol | 2 +- .../optimism.integration.test.ts | 4 +- .../bridging-rebase.integration.test.ts | 46 +- test/optimism/deposit-gas-estimation.test.ts | 2 +- test/token/ERC20Rebasable.unit.test.ts | 931 +++++++++++++++--- utils/optimism/deployment.ts | 5 +- utils/optimism/testing.ts | 16 +- 13 files changed, 921 insertions(+), 202 deletions(-) diff --git a/contracts/optimism/L1ERC20TokenBridge.sol b/contracts/optimism/L1ERC20TokenBridge.sol index 67009c86..1c39797e 100644 --- a/contracts/optimism/L1ERC20TokenBridge.sol +++ b/contracts/optimism/L1ERC20TokenBridge.sol @@ -71,7 +71,7 @@ contract L1ERC20TokenBridge is whenDepositsEnabled onlySupportedL1Token(l1Token_) onlySupportedL2Token(l2Token_) - { + { if (Address.isContract(msg.sender)) { revert ErrorSenderNotEOA(); } @@ -140,7 +140,7 @@ contract L1ERC20TokenBridge is if (isRebasableTokenFlow(l1Token_, l2Token_)) { DepositData memory depositData = DepositData({ - rate: IERC20Wrapable(l1TokenNonRebasable).tokensPerStEth(), // replace by stETHPerToken + rate: IERC20Wrapable(l1TokenNonRebasable).stETHPerToken(), time: block.timestamp, data: data_ }); diff --git a/contracts/optimism/L2ERC20TokenBridge.sol b/contracts/optimism/L2ERC20TokenBridge.sol index 724c2db9..36ae5c0b 100644 --- a/contracts/optimism/L2ERC20TokenBridge.sol +++ b/contracts/optimism/L2ERC20TokenBridge.sol @@ -110,8 +110,8 @@ contract L2ERC20TokenBridge is { if (isRebasableTokenFlow(l1Token_, l2Token_)) { DepositData memory depositData = decodeDepositData(data_); - ITokenRateOracle tokensRateOracle = ERC20Rebasable(l2TokenRebasable).tokensRateOracle(); - tokensRateOracle.updateRate(int256(depositData.rate), depositData.time); + ITokenRateOracle tokenRateOracle = ERC20Rebasable(l2TokenRebasable).tokenRateOracle(); + tokenRateOracle.updateRate(int256(depositData.rate), depositData.time, 0); ERC20Rebasable(l2TokenRebasable).mintShares(to_, amount_); emit DepositFinalized(l1Token_, l2Token_, from_, to_, amount_, depositData.data); } else if (isNonRebasableTokenFlow(l1Token_, l2Token_)) { diff --git a/contracts/stubs/ERC20WrapableStub.sol b/contracts/stubs/ERC20WrapableStub.sol index f3c7f33a..9f48b24f 100644 --- a/contracts/stubs/ERC20WrapableStub.sol +++ b/contracts/stubs/ERC20WrapableStub.sol @@ -47,7 +47,7 @@ contract ERC20WrapableStub is IERC20Wrapable, ERC20 { return stETHAmount; } - function tokensPerStEth() external view returns (uint256) { + function stETHPerToken() external view returns (uint256) { return tokensRate; } } diff --git a/contracts/stubs/TokenRateOracleStub.sol b/contracts/stubs/TokenRateOracleStub.sol index 4be61315..40463af7 100644 --- a/contracts/stubs/TokenRateOracleStub.sol +++ b/contracts/stubs/TokenRateOracleStub.sol @@ -49,9 +49,9 @@ contract TokenRateOracleStub is ITokenRateOracle { return latestRoundDataAnswer; } - function updateRate(int256 rate, uint256 updatedAt) external { + function updateRate(int256 tokenRate_, uint256 rateL1Timestamp_, uint256 lastProcessingRefSlot_) external { // check timestamp not late as current one. - latestRoundDataAnswer = rate; - latestRoundDataUpdatedAt = updatedAt; + latestRoundDataAnswer = tokenRate_; + latestRoundDataUpdatedAt = rateL1Timestamp_; } } \ No newline at end of file diff --git a/contracts/token/ERC20Rebasable.sol b/contracts/token/ERC20Rebasable.sol index c975689a..b7a32798 100644 --- a/contracts/token/ERC20Rebasable.sol +++ b/contracts/token/ERC20Rebasable.sol @@ -7,7 +7,7 @@ import {IERC20} from "@openzeppelin/contracts/token/ERC20/IERC20.sol"; import {IERC20Wrapable} from "./interfaces/IERC20Wrapable.sol"; import {ITokenRateOracle} from "./interfaces/ITokenRateOracle.sol"; import {ERC20Metadata} from "./ERC20Metadata.sol"; -import { console } from "hardhat/console.sol"; +// import { console } from "hardhat/console.sol"; /// @author kovalgek /// @notice Extends the ERC20Shared functionality @@ -23,24 +23,43 @@ contract ERC20Rebasable is IERC20Wrapable, IERC20, ERC20Metadata { error ErrorNotEnoughAllowance(); error ErrorAccountIsZeroAddress(); error ErrorDecreasedAllowanceBelowZero(); + error ErrorNotBridge(); + /// @notice Bridge which can mint and burn tokens on L2. + address public immutable bridge; + + /// @notice Contract of non-rebasable token to wrap. IERC20 public immutable wrappedToken; - ITokenRateOracle public immutable tokensRateOracle; - /// @param wrappedToken_ address of the ERC20 token to wrap - /// @param tokensRateOracle_ address of oracle that returns tokens rate + /// @notice Oracle contract used to get token rate for wrapping/unwrapping tokens. + ITokenRateOracle public immutable tokenRateOracle; + + /// @inheritdoc IERC20 + mapping(address => mapping(address => uint256)) public allowance; + + /// @notice Basic unit representing the token holder's share in the total amount of ether controlled by the protocol. + mapping (address => uint256) private shares; + + /// @notice The total amount of shares in existence. + uint256 private totalShares; + /// @param name_ The name of the token /// @param symbol_ The symbol of the token /// @param decimals_ The decimals places of the token + /// @param wrappedToken_ address of the ERC20 token to wrap + /// @param tokenRateOracle_ address of oracle that returns tokens rate + /// @param bridge_ The bridge address which allowd to mint/burn tokens constructor( - address wrappedToken_, - address tokensRateOracle_, string memory name_, string memory symbol_, - uint8 decimals_ + uint8 decimals_, + address wrappedToken_, + address tokenRateOracle_, + address bridge_ ) ERC20Metadata(name_, symbol_, decimals_) { wrappedToken = IERC20(wrappedToken_); - tokensRateOracle = ITokenRateOracle(tokensRateOracle_); + tokenRateOracle = ITokenRateOracle(tokenRateOracle_); + bridge = bridge_; } /// @notice Sets the name and the symbol of the tokens if they both are empty @@ -51,8 +70,6 @@ contract ERC20Rebasable is IERC20Wrapable, IERC20, ERC20Metadata { _setERC20MetadataSymbol(symbol_); } - /// ------------IERC20Wrapable------------ - /// @inheritdoc IERC20Wrapable function wrap(uint256 sharesAmount_) external returns (uint256) { if (sharesAmount_ == 0) revert ErrorZeroSharesWrap(); @@ -75,25 +92,28 @@ contract ERC20Rebasable is IERC20Wrapable, IERC20, ERC20Metadata { return sharesAmount; } - function tokensPerStEth() external pure returns (uint256) { - return 0; + /// @inheritdoc IERC20Wrapable + function stETHPerToken() external view returns (uint256) { + return uint256(tokenRateOracle.latestAnswer()); } // allow call only bridge - function mintShares(address account_, uint256 amount_) external returns (uint256) { + function mintShares(address account_, uint256 amount_) external onlyBridge returns (uint256) { return _mintShares(account_, amount_); } // allow call only bridge - function burnShares(address account_, uint256 amount_) external { + function burnShares(address account_, uint256 amount_) external onlyBridge { _burnShares(account_, amount_); } - - /// ------------ERC20------------ - - /// @inheritdoc IERC20 - mapping(address => mapping(address => uint256)) public allowance; + /// @dev Validates that sender of the transaction is the bridge + modifier onlyBridge() { + if (msg.sender != bridge) { + revert ErrorNotBridge(); + } + _; + } /// @inheritdoc IERC20 function totalSupply() external view returns (uint256) { @@ -212,35 +232,32 @@ contract ERC20Rebasable is IERC20Wrapable, IERC20, ERC20Metadata { emit Approval(owner_, spender_, amount_); } - - /// ------------Shares------------ - // API - function sharesOf(address _account) external view returns (uint256) { - return _sharesOf(_account); + /// @notice Get shares amount of the provided account. + /// @param account_ provided account address. + /// @return amount of shares owned by `_account`. + function sharesOf(address account_) external view returns (uint256) { + return _sharesOf(account_); } + /// @return total amount of shares. function getTotalShares() external view returns (uint256) { return _getTotalShares(); } + /// @notice Get amount of tokens for a given amount of shares. + /// @param sharesAmount_ amount of shares. + /// @return amount of tokens for a given shares amount. function getTokensByShares(uint256 sharesAmount_) external view returns (uint256) { return _getTokensByShares(sharesAmount_); } + /// @notice Get amount of shares for a given amount of tokens. + /// @param tokenAmount_ provided tokens amount. + /// @return amount of shares for a given tokens amount. function getSharesByTokens(uint256 tokenAmount_) external view returns (uint256) { return _getSharesByTokens(tokenAmount_); } - function getTokensRateAndDecimal() external view returns (uint256, uint256) { - return _getTokensRateAndDecimal(); - } - - // private/internal - - mapping (address => uint256) private shares; - - uint256 private totalShares; - function _sharesOf(address account_) internal view returns (uint256) { return shares[account_]; } @@ -251,32 +268,28 @@ contract ERC20Rebasable is IERC20Wrapable, IERC20, ERC20Metadata { function _getTokensByShares(uint256 sharesAmount_) internal view returns (uint256) { (uint256 tokensRate, uint256 decimals) = _getTokensRateAndDecimal(); - return (sharesAmount_ * (10 ** decimals)) / tokensRate; + return (sharesAmount_ * tokensRate) / (10 ** decimals); } function _getSharesByTokens(uint256 tokenAmount_) internal view returns (uint256) { (uint256 tokensRate, uint256 decimals) = _getTokensRateAndDecimal(); - return (tokenAmount_ * tokensRate) / (10 ** decimals); + return (tokenAmount_ * (10 ** decimals)) / tokensRate; } function _getTokensRateAndDecimal() internal view returns (uint256, uint256) { - uint8 rateDecimals = tokensRateOracle.decimals(); - console.log("_getTokensRateAndDecimal1"); + uint8 rateDecimals = tokenRateOracle.decimals(); if (rateDecimals == uint8(0) || rateDecimals > uint8(18)) revert ErrorInvalidRateDecimals(rateDecimals); - console.log("_getTokensRateAndDecimal2"); (, int256 answer , , uint256 updatedAt - ,) = tokensRateOracle.latestRoundData(); - console.log("_getTokensRateAndDecimal3"); + ,) = tokenRateOracle.latestRoundData(); if (updatedAt == 0) revert ErrorWrongOracleUpdateTime(); if (answer <= 0) revert ErrorOracleAnswerIsNegative(); - console.log("_getTokensRateAndDecimal4"); return (uint256(answer), uint256(rateDecimals)); } @@ -290,6 +303,7 @@ contract ERC20Rebasable is IERC20Wrapable, IERC20, ERC20Metadata { ) internal onlyNonZeroAccount(recipient_) returns (uint256) { totalShares = totalShares + amount_; shares[recipient_] = shares[recipient_] + amount_; + emit Transfer(address(0), recipient_, amount_); return totalShares; } @@ -304,6 +318,7 @@ contract ERC20Rebasable is IERC20Wrapable, IERC20, ERC20Metadata { if (accountShares < amount_) revert ErrorNotEnoughBalance(); totalShares = totalShares - amount_; shares[account_] = accountShares - amount_; + emit Transfer(account_, address(0), amount_); return totalShares; } diff --git a/contracts/token/interfaces/IERC20Wrapable.sol b/contracts/token/interfaces/IERC20Wrapable.sol index de82ee27..0fb2d2dc 100644 --- a/contracts/token/interfaces/IERC20Wrapable.sol +++ b/contracts/token/interfaces/IERC20Wrapable.sol @@ -34,5 +34,5 @@ interface IERC20Wrapable { * @notice Get amount of wstETH for a one stETH * @return Amount of wstETH for a 1 stETH */ - function tokensPerStEth() external view returns (uint256); + function stETHPerToken() external view returns (uint256); } \ No newline at end of file diff --git a/contracts/token/interfaces/ITokenRateOracle.sol b/contracts/token/interfaces/ITokenRateOracle.sol index 648b346d..eb9aa8aa 100644 --- a/contracts/token/interfaces/ITokenRateOracle.sol +++ b/contracts/token/interfaces/ITokenRateOracle.sol @@ -26,5 +26,5 @@ interface ITokenRateOracle { function decimals() external view returns (uint8); /// @notice Updates token rate. - function updateRate(int256 rate, uint256 rateL1Timestamp) external; + function updateRate(int256 tokenRate_, uint256 rateL1Timestamp_, uint256 lastProcessingRefSlot_) external; } \ No newline at end of file diff --git a/test/bridge-executor/optimism.integration.test.ts b/test/bridge-executor/optimism.integration.test.ts index 2a8949f4..6cd18db8 100644 --- a/test/bridge-executor/optimism.integration.test.ts +++ b/test/bridge-executor/optimism.integration.test.ts @@ -219,7 +219,7 @@ async function ctxFactory() { "TTR" ); - const tokensRateOracleStub = await new TokenRateOracle__factory(l2Deployer).deploy(); + const tokenRateOracleStub = await new TokenRateOracle__factory(l2Deployer).deploy(); const optAddresses = optimism.addresses(networkName); @@ -243,7 +243,7 @@ async function ctxFactory() { .erc20TokenBridgeDeployScript( l1Token.address, l1TokenRebasable.address, - tokensRateOracleStub.address, + tokenRateOracleStub.address, { deployer: l1Deployer, admins: { diff --git a/test/optimism/bridging-rebase.integration.test.ts b/test/optimism/bridging-rebase.integration.test.ts index e0822643..cda397c2 100644 --- a/test/optimism/bridging-rebase.integration.test.ts +++ b/test/optimism/bridging-rebase.integration.test.ts @@ -79,12 +79,12 @@ scenario("Optimism :: Bridging integration test", ctxFactory) l2TokenRebasable, l1CrossDomainMessenger, l2ERC20TokenBridge, - tokensRateOracle, + tokenRateOracle, l1Provider } = ctx; const { accountA: tokenHolderA } = ctx.accounts; - const tokensPerStEth = await l1Token.tokensPerStEth(); + const stETHPerToken = await l1Token.stETHPerToken(); await l1TokenRebasable .connect(tokenHolderA.l1Signer) @@ -111,8 +111,8 @@ scenario("Optimism :: Bridging integration test", ctxFactory) const blockNumber = await l1Provider.getBlockNumber(); const blockTimestamp = (await l1Provider.getBlock(blockNumber)).timestamp; const blockTimestampStr = ethers.utils.hexZeroPad(ethers.utils.hexlify(blockTimestamp), 32) - const tokensPerStEthStr = ethers.utils.hexZeroPad(tokensPerStEth.toHexString(), 32) - const dataToSend = ethers.utils.hexConcat([tokensPerStEthStr, blockTimestampStr]); + const stETHPerTokenStr = ethers.utils.hexZeroPad(stETHPerToken.toHexString(), 32) + const dataToSend = ethers.utils.hexConcat([stETHPerTokenStr, blockTimestampStr]); await assert.emits(l1ERC20TokenBridge, tx, "ERC20DepositInitiated", [ @@ -165,16 +165,16 @@ scenario("Optimism :: Bridging integration test", ctxFactory) l1ERC20TokenBridge, l2CrossDomainMessenger, l2ERC20TokenBridge, - tokensRateOracle, + tokenRateOracle, l2Provider } = ctx; - const tokensPerStEth = await l1Token.tokensPerStEth(); + const stETHPerToken = await l1Token.stETHPerToken(); const blockNumber = await l2Provider.getBlockNumber(); const blockTimestamp = (await l2Provider.getBlock(blockNumber)).timestamp; const blockTimestampStr = ethers.utils.hexZeroPad(ethers.utils.hexlify(blockTimestamp), 32) - const tokensPerStEthStr = ethers.utils.hexZeroPad(tokensPerStEth.toHexString(), 32) - const dataToReceive = ethers.utils.hexConcat([tokensPerStEthStr, blockTimestampStr]); + const stETHPerTokenStr = ethers.utils.hexZeroPad(stETHPerToken.toHexString(), 32) + const dataToReceive = ethers.utils.hexConcat([stETHPerTokenStr, blockTimestampStr]); const { accountA: tokenHolderA, l1CrossDomainMessengerAliased } = ctx.accounts; @@ -206,8 +206,8 @@ scenario("Optimism :: Bridging integration test", ctxFactory) ); - const [,tokensRate,,updatedAt,] = await tokensRateOracle.latestRoundData(); - assert.equalBN(tokensPerStEth, tokensRate); + const [,tokensRate,,updatedAt,] = await tokenRateOracle.latestRoundData(); + assert.equalBN(stETHPerToken, tokensRate); assert.equalBN(blockTimestamp, updatedAt); await assert.emits(l2ERC20TokenBridge, tx, "DepositFinalized", [ @@ -237,15 +237,15 @@ scenario("Optimism :: Bridging integration test", ctxFactory) l2TokenRebasable, l1CrossDomainMessenger, l2ERC20TokenBridge, - tokensRateOracle, + tokenRateOracle, l1Provider } = ctx; const { accountA: tokenHolderA } = ctx.accounts; const { depositAmount: depositAmountInRebasableTokens } = ctx.common; const depositAmount = wei.toBigNumber(depositAmountInRebasableTokens).mul(2); - const tokensPerStEth = await l1Token.tokensPerStEth(); + const stETHPerToken = await l1Token.stETHPerToken(); - await tokensRateOracle.setLatestRoundDataAnswer(BigNumber.from("1000000000000000000")); + await tokenRateOracle.setLatestRoundDataAnswer(BigNumber.from("1000000000000000000")); await l1TokenRebasable .connect(tokenHolderA.l1Signer) @@ -272,8 +272,8 @@ scenario("Optimism :: Bridging integration test", ctxFactory) const blockNumber = await l1Provider.getBlockNumber(); const blockTimestamp = (await l1Provider.getBlock(blockNumber)).timestamp; const blockTimestampStr = ethers.utils.hexZeroPad(ethers.utils.hexlify(blockTimestamp), 32) - const tokensPerStEthStr = ethers.utils.hexZeroPad(tokensPerStEth.toHexString(), 32) - const dataToSend = ethers.utils.hexConcat([tokensPerStEthStr, blockTimestampStr]); + const stETHPerTokenStr = ethers.utils.hexZeroPad(stETHPerToken.toHexString(), 32) + const dataToSend = ethers.utils.hexConcat([stETHPerTokenStr, blockTimestampStr]); await assert.emits(l1ERC20TokenBridge, tx, "ERC20DepositInitiated", [ @@ -326,19 +326,19 @@ scenario("Optimism :: Bridging integration test", ctxFactory) l1ERC20TokenBridge, l2CrossDomainMessenger, l2ERC20TokenBridge, - tokensRateOracle, + tokenRateOracle, l2Provider } = ctx; const { depositAmount: depositAmountInRebasableTokens } = ctx.common; - const depositAmount = wei.toBigNumber(depositAmountInRebasableTokens).mul(2); - const tokensPerStEth = await l1Token.tokensPerStEth(); + const depositAmount = wei.toBigNumber(depositAmountInRebasableTokens).div(2); + const stETHPerToken = await l1Token.stETHPerToken(); const blockNumber = await l2Provider.getBlockNumber(); const blockTimestamp = (await l2Provider.getBlock(blockNumber)).timestamp; const blockTimestampStr = ethers.utils.hexZeroPad(ethers.utils.hexlify(blockTimestamp), 32) - const tokensPerStEthStr = ethers.utils.hexZeroPad(tokensPerStEth.toHexString(), 32) - const dataToReceive = ethers.utils.hexConcat([tokensPerStEthStr, blockTimestampStr]); + const stETHPerTokenStr = ethers.utils.hexZeroPad(stETHPerToken.toHexString(), 32) + const dataToReceive = ethers.utils.hexConcat([stETHPerTokenStr, blockTimestampStr]); const { accountA: tokenHolderA, l1CrossDomainMessengerAliased } = @@ -371,8 +371,8 @@ scenario("Optimism :: Bridging integration test", ctxFactory) ); - const [,tokensRate,,updatedAt,] = await tokensRateOracle.latestRoundData(); - assert.equalBN(tokensPerStEth, tokensRate); + const [,tokensRate,,updatedAt,] = await tokenRateOracle.latestRoundData(); + assert.equalBN(stETHPerToken, tokensRate); assert.equalBN(blockTimestamp, updatedAt); await assert.emits(l2ERC20TokenBridge, tx, "DepositFinalized", [ @@ -403,7 +403,7 @@ scenario("Optimism :: Bridging integration test", ctxFactory) l2ERC20TokenBridge } = ctx; - const withdrawalAmount = wei.toBigNumber(withdrawalAmountInRebasableTokens).mul(2); + const withdrawalAmount = wei.toBigNumber(withdrawalAmountInRebasableTokens).div(2); const tokenHolderABalanceBefore = await l2TokenRebasable.balanceOf( tokenHolderA.address diff --git a/test/optimism/deposit-gas-estimation.test.ts b/test/optimism/deposit-gas-estimation.test.ts index c6fc8db7..5a14cda6 100644 --- a/test/optimism/deposit-gas-estimation.test.ts +++ b/test/optimism/deposit-gas-estimation.test.ts @@ -79,7 +79,7 @@ scenario("Optimism :: Bridging integration test", ctxFactory) } = ctx; const { accountA: tokenHolderA } = ctx.accounts; - const tokensPerStEth = await l1Token.tokensPerStEth(); + const stETHPerToken = await l1Token.stETHPerToken(); await l1TokenRebasable .connect(tokenHolderA.l1Signer) diff --git a/test/token/ERC20Rebasable.unit.test.ts b/test/token/ERC20Rebasable.unit.test.ts index 2d49abc6..ac87c9d5 100644 --- a/test/token/ERC20Rebasable.unit.test.ts +++ b/test/token/ERC20Rebasable.unit.test.ts @@ -6,7 +6,6 @@ import { wei } from "../../utils/wei"; import { ERC20Stub__factory, ERC20Rebasable__factory, TokenRateOracleStub__factory, OssifiableProxy__factory } from "../../typechain"; import { BigNumber } from "ethers"; - unit("ERC20Rebasable", ctxFactory) .test("wrappedToken", async (ctx) => { @@ -14,9 +13,9 @@ unit("ERC20Rebasable", ctxFactory) assert.equal(await rebasableProxied.wrappedToken(), wrappedTokenStub.address) }) - .test("tokensRateOracle", async (ctx) => { - const { rebasableProxied, tokensRateOracleStub } = ctx.contracts; - assert.equal(await rebasableProxied.tokensRateOracle(), tokensRateOracleStub.address) + .test("tokenRateOracle", async (ctx) => { + const { rebasableProxied, tokenRateOracleStub } = ctx.contracts; + assert.equal(await rebasableProxied.tokenRateOracle(), tokenRateOracleStub.address) }) .test("name()", async (ctx) => @@ -27,235 +26,935 @@ unit("ERC20Rebasable", ctxFactory) assert.equal(await ctx.contracts.rebasableProxied.symbol(), ctx.constants.symbol) ) + .test("initialize() :: name already set", async (ctx) => { + const { deployer, owner } = ctx.accounts; + + // deploy new implementation + const wrappedTokenStub = await new ERC20Stub__factory(deployer).deploy(); + const tokenRateOracleStub = await new TokenRateOracleStub__factory(deployer).deploy(); + const rebasableTokenImpl = await new ERC20Rebasable__factory(deployer).deploy( + "name", + "", + 10, + wrappedTokenStub.address, + tokenRateOracleStub.address, + owner.address + ); + await assert.revertsWith( + rebasableTokenImpl.initialize("New Name", ""), + "ErrorNameAlreadySet()" + ); + }) + + .test("initialize() :: symbol already set", async (ctx) => { + const { deployer, owner } = ctx.accounts; + + // deploy new implementation + const wrappedTokenStub = await new ERC20Stub__factory(deployer).deploy(); + const tokenRateOracleStub = await new TokenRateOracleStub__factory(deployer).deploy(); + const rebasableTokenImpl = await new ERC20Rebasable__factory(deployer).deploy( + "", + "symbol", + 10, + wrappedTokenStub.address, + tokenRateOracleStub.address, + owner.address + ); + await assert.revertsWith( + rebasableTokenImpl.initialize("", "New Symbol"), + "ErrorSymbolAlreadySet()" + ); + }) + .test("decimals", async (ctx) => - assert.equal(await ctx.contracts.rebasableProxied.decimals(), ctx.constants.decimals) + assert.equal(await ctx.contracts.rebasableProxied.decimals(), ctx.constants.decimalsToSet) ) + .test("totalShares", async (ctx) => { + const { premintShares } = ctx.constants; + assert.equalBN(await ctx.contracts.rebasableProxied.getTotalShares(), premintShares); + }) + .test("wrap(0)", async (ctx) => { const { rebasableProxied } = ctx.contracts; - await assert.revertsWith(rebasableProxied.wrap(0), "ErrorZeroSharesWrap()"); + const { user1 } = ctx.accounts; + await assert.revertsWith(rebasableProxied.connect(user1).wrap(0), "ErrorZeroSharesWrap()"); }) .test("unwrap(0)", async (ctx) => { const { rebasableProxied } = ctx.contracts; - await assert.revertsWith(rebasableProxied.unwrap(0), "ErrorZeroTokensUnwrap()"); + const { user1 } = ctx.accounts; + await assert.revertsWith(rebasableProxied.connect(user1).unwrap(0), "ErrorZeroTokensUnwrap()"); }) .test("wrap() positive scenario", async (ctx) => { - const { rebasableProxied, tokensRateOracleStub, wrappedTokenStub } = ctx.contracts; + + const { rebasableProxied, wrappedTokenStub } = ctx.contracts; const {user1, user2 } = ctx.accounts; + const { rate, decimals, premintShares } = ctx.constants; + + const totalSupply = rate.mul(premintShares).div(decimals); - await tokensRateOracleStub.setDecimals(5); - await tokensRateOracleStub.setLatestRoundDataAnswer(120000); - await tokensRateOracleStub.setUpdatedAt(1000); + assert.equalBN(await rebasableProxied.getTotalShares(), premintShares); + assert.equalBN(await rebasableProxied.totalSupply(), totalSupply); // user1 - assert.equalBN(await rebasableProxied.callStatic.wrap(100), 83); - const tx = await rebasableProxied.wrap(100); + assert.equalBN(await rebasableProxied.sharesOf(user1.address), 0); + assert.equalBN(await rebasableProxied.balanceOf(user1.address), 0); - assert.equalBN(await rebasableProxied.getTotalShares(), 100); - assert.equalBN(await rebasableProxied.sharesOf(user1.address), 100); + const user1Shares = wei`100 ether`; + const user1Tokens = rate.mul(user1Shares).div(decimals); + + assert.equalBN(await rebasableProxied.connect(user1).callStatic.wrap(user1Shares), user1Tokens); + const tx = await rebasableProxied.connect(user1).wrap(user1Shares); + + assert.equalBN(await rebasableProxied.sharesOf(user1.address), user1Shares); + assert.equalBN(await rebasableProxied.balanceOf(user1.address), user1Tokens); assert.equal(await wrappedTokenStub.transferFromAddress(), user1.address); assert.equal(await wrappedTokenStub.transferFromTo(), rebasableProxied.address); - assert.equalBN(await wrappedTokenStub.transferFromAmount(), 100); + assert.equalBN(await wrappedTokenStub.transferFromAmount(), user1Shares); + + // common state changes + assert.equalBN(await rebasableProxied.getTotalShares(), BigNumber.from(premintShares).add(user1Shares)); + assert.equalBN(await rebasableProxied.totalSupply(), totalSupply.add(user1Tokens)); // user2 - assert.equalBN(await rebasableProxied.connect(user2).callStatic.wrap(50), 41); - const tx2 = await rebasableProxied.connect(user2).wrap(50); + assert.equalBN(await rebasableProxied.sharesOf(user2.address), 0); + assert.equalBN(await rebasableProxied.balanceOf(user2.address), 0); - assert.equalBN(await rebasableProxied.getTotalShares(), 150); - assert.equalBN(await rebasableProxied.sharesOf(user2.address), 50); + const user2Shares = wei`50 ether`; + const user2Tokens = rate.mul(user2Shares).div(decimals); + assert.equalBN(await rebasableProxied.connect(user2).callStatic.wrap(user2Shares), user2Tokens); + const tx2 = await rebasableProxied.connect(user2).wrap(user2Shares); + + assert.equalBN(await rebasableProxied.sharesOf(user2.address), user2Shares); + assert.equalBN(await rebasableProxied.balanceOf(user2.address), user2Tokens); assert.equal(await wrappedTokenStub.transferFromAddress(), user2.address); assert.equal(await wrappedTokenStub.transferFromTo(), rebasableProxied.address); - assert.equalBN(await wrappedTokenStub.transferFromAmount(), 50); + assert.equalBN(await wrappedTokenStub.transferFromAmount(), user2Shares); // common state changes - assert.equalBN(await rebasableProxied.totalSupply(), 125); + assert.equalBN(await rebasableProxied.getTotalShares(), BigNumber.from(premintShares).add(user1Shares).add(user2Shares)); + assert.equalBN(await rebasableProxied.totalSupply(), totalSupply.add(user1Tokens).add(user2Tokens)); }) .test("wrap() with wrong oracle decimals", async (ctx) => { - const { rebasableProxied, tokensRateOracleStub } = ctx.contracts; - - await tokensRateOracleStub.setDecimals(0); - await tokensRateOracleStub.setLatestRoundDataAnswer(120000); - await tokensRateOracleStub.setUpdatedAt(1000); + const { rebasableProxied, tokenRateOracleStub } = ctx.contracts; + const { user1 } = ctx.accounts; - await assert.revertsWith(rebasableProxied.wrap(23), "ErrorInvalidRateDecimals(0)"); + await tokenRateOracleStub.setDecimals(0); + await assert.revertsWith(rebasableProxied.connect(user1).wrap(23), "ErrorInvalidRateDecimals(0)"); - await tokensRateOracleStub.setDecimals(19); - await tokensRateOracleStub.setLatestRoundDataAnswer(120000); - await tokensRateOracleStub.setUpdatedAt(1000); - - await assert.revertsWith(rebasableProxied.wrap(23), "ErrorInvalidRateDecimals(19)"); + await tokenRateOracleStub.setDecimals(19); + await assert.revertsWith(rebasableProxied.connect(user1).wrap(23), "ErrorInvalidRateDecimals(19)"); }) .test("wrap() with wrong oracle update time", async (ctx) => { - const { rebasableProxied, tokensRateOracleStub } = ctx.contracts; - - await tokensRateOracleStub.setDecimals(10); - await tokensRateOracleStub.setLatestRoundDataAnswer(120000); - await tokensRateOracleStub.setUpdatedAt(0); + const { rebasableProxied, tokenRateOracleStub } = ctx.contracts; + const { user1 } = ctx.accounts; - await assert.revertsWith(rebasableProxied.wrap(5), "ErrorWrongOracleUpdateTime()"); + await tokenRateOracleStub.setUpdatedAt(0); + await assert.revertsWith(rebasableProxied.connect(user1).wrap(5), "ErrorWrongOracleUpdateTime()"); }) .test("wrap() with wrong oracle answer", async (ctx) => { - const { rebasableProxied, tokensRateOracleStub } = ctx.contracts; + const { rebasableProxied, tokenRateOracleStub } = ctx.contracts; + const { user1 } = ctx.accounts; - await tokensRateOracleStub.setDecimals(10); - await tokensRateOracleStub.setLatestRoundDataAnswer(0); - await tokensRateOracleStub.setUpdatedAt(10); - - await assert.revertsWith(rebasableProxied.wrap(21), "ErrorOracleAnswerIsNegative()"); + await tokenRateOracleStub.setLatestRoundDataAnswer(0); + await assert.revertsWith(rebasableProxied.connect(user1).wrap(21), "ErrorOracleAnswerIsNegative()"); }) - .test("unwrap() positive scenario", async (ctx) => { - const { rebasableProxied, tokensRateOracleStub, wrappedTokenStub } = ctx.contracts; + const { rebasableProxied, wrappedTokenStub } = ctx.contracts; const {user1, user2 } = ctx.accounts; + const { rate, decimals, premintShares } = ctx.constants; - await tokensRateOracleStub.setDecimals(7); - await tokensRateOracleStub.setLatestRoundDataAnswer(14000000); - await tokensRateOracleStub.setUpdatedAt(14000); + const totalSupply = BigNumber.from(rate).mul(premintShares).div(decimals); + + assert.equalBN(await rebasableProxied.getTotalShares(), premintShares); + assert.equalBN(await rebasableProxied.totalSupply(), totalSupply); // user1 - const tx0 = await rebasableProxied.wrap(4500); + assert.equalBN(await rebasableProxied.sharesOf(user1.address), 0); + assert.equalBN(await rebasableProxied.balanceOf(user1.address), 0); + + const user1SharesToWrap = wei`100 ether`; + const user1SharesToUnwrap = wei`59 ether`; + const user1TokensToUnwrap = rate.mul(user1SharesToUnwrap).div(decimals); - assert.equalBN(await rebasableProxied.callStatic.unwrap(59), 82); - const tx = await rebasableProxied.unwrap(59); + const user1Shares = BigNumber.from(user1SharesToWrap).sub(user1SharesToUnwrap); + const user1Tokens = BigNumber.from(rate).mul(user1Shares).div(decimals); - assert.equalBN(await rebasableProxied.getTotalShares(), 4418); - assert.equalBN(await rebasableProxied.sharesOf(user1.address), 4418); + const tx0 = await rebasableProxied.connect(user1).wrap(user1SharesToWrap); + assert.equalBN(await rebasableProxied.connect(user1).callStatic.unwrap(user1TokensToUnwrap), user1SharesToUnwrap); + const tx = await rebasableProxied.connect(user1).unwrap(user1TokensToUnwrap); + assert.equalBN(await rebasableProxied.sharesOf(user1.address), user1Shares); + assert.equalBN(await rebasableProxied.balanceOf(user1.address), user1Tokens); assert.equal(await wrappedTokenStub.transferTo(), user1.address); - assert.equalBN(await wrappedTokenStub.transferAmount(), 82); + assert.equalBN(await wrappedTokenStub.transferAmount(), user1SharesToUnwrap); - // // user2 - await rebasableProxied.connect(user2).wrap(200); + // common state changes + assert.equalBN(await rebasableProxied.getTotalShares(), premintShares.add(user1Shares)); + assert.equalBN(await rebasableProxied.totalSupply(), totalSupply.add(user1Tokens)); + + // user2 + const user2SharesToWrap = wei`145 ether`; + const user2SharesToUnwrap = wei`14 ether`; + const user2TokensToUnwrap = rate.mul(user2SharesToUnwrap).div(decimals); - assert.equalBN(await rebasableProxied.connect(user2).callStatic.unwrap(50), 70); - const tx2 = await rebasableProxied.connect(user2).unwrap(50); + const user2Shares = BigNumber.from(user2SharesToWrap).sub(user2SharesToUnwrap); + const user2Tokens = BigNumber.from(rate).mul(user2Shares).div(decimals); - assert.equalBN(await rebasableProxied.getTotalShares(), 4548); - assert.equalBN(await rebasableProxied.sharesOf(user2.address), 130); + assert.equalBN(await rebasableProxied.sharesOf(user2.address), 0); + assert.equalBN(await rebasableProxied.balanceOf(user2.address), 0); + await rebasableProxied.connect(user2).wrap(user2SharesToWrap); + assert.equalBN(await rebasableProxied.connect(user2).callStatic.unwrap(user2TokensToUnwrap), user2SharesToUnwrap); + const tx2 = await rebasableProxied.connect(user2).unwrap(user2TokensToUnwrap); + + assert.equalBN(await rebasableProxied.sharesOf(user2.address), user2Shares); + assert.equalBN(await rebasableProxied.balanceOf(user2.address), user2Tokens); assert.equal(await wrappedTokenStub.transferTo(), user2.address); - assert.equalBN(await wrappedTokenStub.transferAmount(), 70); + assert.equalBN(await wrappedTokenStub.transferAmount(), user2SharesToUnwrap); // common state changes - assert.equalBN(await rebasableProxied.totalSupply(), 3248); + assert.equalBN(await rebasableProxied.getTotalShares(), premintShares.add(user1Shares).add(user2Shares)); + assert.equalBN(await rebasableProxied.totalSupply(), totalSupply.add(user1Tokens).add(user2Tokens)); }) .test("unwrap() with wrong oracle decimals", async (ctx) => { - const { rebasableProxied, tokensRateOracleStub } = ctx.contracts; + const { rebasableProxied, tokenRateOracleStub } = ctx.contracts; + const { user1 } = ctx.accounts; - - await tokensRateOracleStub.setDecimals(10); - await tokensRateOracleStub.setLatestRoundDataAnswer(120000); - await tokensRateOracleStub.setUpdatedAt(1000); + await rebasableProxied.connect(user1).wrap(wei`2 ether`); + + await tokenRateOracleStub.setDecimals(0); + await assert.revertsWith(rebasableProxied.connect(user1).unwrap(23), "ErrorInvalidRateDecimals(0)"); + + await tokenRateOracleStub.setDecimals(19); + await assert.revertsWith(rebasableProxied.connect(user1).unwrap(23), "ErrorInvalidRateDecimals(19)"); + }) + + .test("unwrap() with wrong oracle update time", async (ctx) => { - await rebasableProxied.wrap(100); - await tokensRateOracleStub.setDecimals(0); + const { rebasableProxied, tokenRateOracleStub } = ctx.contracts; + const { user1 } = ctx.accounts; - await assert.revertsWith(rebasableProxied.unwrap(23), "ErrorInvalidRateDecimals(0)"); + await rebasableProxied.connect(user1).wrap(wei`6 ether`); + await tokenRateOracleStub.setUpdatedAt(0); + await assert.revertsWith(rebasableProxied.connect(user1).unwrap(wei`1 ether`), "ErrorWrongOracleUpdateTime()"); + }) - await tokensRateOracleStub.setDecimals(19); - await tokensRateOracleStub.setLatestRoundDataAnswer(120000); - await tokensRateOracleStub.setUpdatedAt(1000); + .test("unwrap() when no balance", async (ctx) => { + const { rebasableProxied } = ctx.contracts; + const { user1 } = ctx.accounts; - await assert.revertsWith(rebasableProxied.unwrap(23), "ErrorInvalidRateDecimals(19)"); + await assert.revertsWith(rebasableProxied.connect(user1).unwrap(wei`4 ether`), "ErrorNotEnoughBalance()"); }) - .test("unwrap() with wrong oracle update time", async (ctx) => { + .test("mintShares() positive scenario", async (ctx) => { + + const { rebasableProxied } = ctx.contracts; + const {user1, user2, owner } = ctx.accounts; + const { rate, decimals, premintShares, premintTokens } = ctx.constants; + + assert.equalBN(await rebasableProxied.getTotalShares(), premintShares); + assert.equalBN(await rebasableProxied.totalSupply(), premintTokens); + + // user1 + const user1SharesToMint = wei`44 ether`; + const user1TokensMinted = rate.mul(user1SharesToMint).div(decimals); + + assert.equalBN(await rebasableProxied.sharesOf(user1.address), 0); + assert.equalBN(await rebasableProxied.balanceOf(user1.address), 0); + + assert.equalBN(await rebasableProxied.connect(owner).callStatic.mintShares(user1.address, user1SharesToMint), premintShares.add(user1SharesToMint)); + const tx0 = await rebasableProxied.connect(owner).mintShares(user1.address, user1SharesToMint); + + assert.equalBN(await rebasableProxied.sharesOf(user1.address), user1SharesToMint); + assert.equalBN(await rebasableProxied.balanceOf(user1.address), user1TokensMinted); + + // common state changes + assert.equalBN(await rebasableProxied.getTotalShares(), premintShares.add(user1SharesToMint)); + assert.equalBN(await rebasableProxied.totalSupply(), premintTokens.add(user1TokensMinted)); + + // // user2 + const user2SharesToMint = wei`75 ether`; + const user2TokensMinted = rate.mul(user2SharesToMint).div(decimals); - const { rebasableProxied, tokensRateOracleStub } = ctx.contracts; + assert.equalBN(await rebasableProxied.sharesOf(user2.address), 0); + assert.equalBN(await rebasableProxied.balanceOf(user2.address), 0); - await tokensRateOracleStub.setDecimals(10); - await tokensRateOracleStub.setLatestRoundDataAnswer(120000); - await tokensRateOracleStub.setUpdatedAt(300); + assert.equalBN( + await rebasableProxied.connect(owner).callStatic.mintShares(user2.address, user2SharesToMint), + premintShares.add(user1SharesToMint).add(user2SharesToMint) + ); + const tx1 = await rebasableProxied.connect(owner).mintShares(user2.address, user2SharesToMint); - await rebasableProxied.wrap(100); - await tokensRateOracleStub.setUpdatedAt(0); + assert.equalBN(await rebasableProxied.sharesOf(user2.address), user2SharesToMint); + assert.equalBN(await rebasableProxied.balanceOf(user2.address), user2TokensMinted); - await assert.revertsWith(rebasableProxied.unwrap(5), "ErrorWrongOracleUpdateTime()"); + // common state changes + assert.equalBN(await rebasableProxied.getTotalShares(), premintShares.add(user1SharesToMint).add(user2SharesToMint)); + assert.equalBN(await rebasableProxied.totalSupply(), premintTokens.add(user1TokensMinted).add(user2TokensMinted)); }) - .test("unwrap() when no balance", async (ctx) => { - const { rebasableProxied, tokensRateOracleStub } = ctx.contracts; + .test("burnShares() positive scenario", async (ctx) => { + + const { rebasableProxied, tokenRateOracleStub } = ctx.contracts; + const {user1, user2, owner } = ctx.accounts; + const { rate, decimals, premintShares, premintTokens } = ctx.constants; - await tokensRateOracleStub.setDecimals(8); - await tokensRateOracleStub.setLatestRoundDataAnswer(12000000); - await tokensRateOracleStub.setUpdatedAt(1000); + assert.equalBN(await rebasableProxied.getTotalShares(), premintShares); + assert.equalBN(await rebasableProxied.totalSupply(), premintTokens); - await assert.revertsWith(rebasableProxied.unwrap(10), "ErrorNotEnoughBalance()"); + // user1 + const user1SharesToMint = wei`12 ether`; + const user1TokensMinted = rate.mul(user1SharesToMint).div(decimals); + + const user1SharesToBurn = wei`4 ether`; + const user1TokensBurned = rate.mul(user1SharesToBurn).div(decimals); + + const user1Shares = BigNumber.from(user1SharesToMint).sub(user1SharesToBurn); + const user1Tokens = user1TokensMinted.sub(user1TokensBurned); + + assert.equalBN(await rebasableProxied.sharesOf(user1.address), 0); + assert.equalBN(await rebasableProxied.balanceOf(user1.address), 0); + + await rebasableProxied.connect(owner).mintShares(user1.address, user1SharesToMint); + assert.equalBN(await rebasableProxied.sharesOf(user1.address), user1SharesToMint); + assert.equalBN(await rebasableProxied.balanceOf(user1.address), user1TokensMinted); + + await rebasableProxied.connect(owner).burnShares(user1.address, user1SharesToBurn); + assert.equalBN(await rebasableProxied.sharesOf(user1.address), user1Shares); + assert.equalBN(await rebasableProxied.balanceOf(user1.address), user1Tokens); + + // common state changes + assert.equalBN(await rebasableProxied.getTotalShares(), premintShares.add(user1Shares)); + assert.equalBN(await rebasableProxied.totalSupply(), premintTokens.add(user1Tokens)); + + // // user2 + const user2SharesToMint = wei`64 ether`; + const user2TokensMinted = rate.mul(user2SharesToMint).div(decimals); + + const user2SharesToBurn = wei`22 ether`; + const user2TokensBurned = rate.mul(user2SharesToBurn).div(decimals); + + const user2Shares = BigNumber.from(user2SharesToMint).sub(user2SharesToBurn); + const user2Tokens = user2TokensMinted.sub(user2TokensBurned); + + assert.equalBN(await rebasableProxied.sharesOf(user2.address), 0); + assert.equalBN(await rebasableProxied.balanceOf(user2.address), 0); + + await rebasableProxied.connect(owner).mintShares(user2.address, user2SharesToMint); + assert.equalBN(await rebasableProxied.sharesOf(user2.address), user2SharesToMint); + assert.equalBN(await rebasableProxied.balanceOf(user2.address), user2TokensMinted); + await rebasableProxied.connect(owner).burnShares(user2.address, user2SharesToBurn); + assert.equalBN(await rebasableProxied.sharesOf(user2.address), user2Shares); + assert.equalBN(await rebasableProxied.balanceOf(user2.address), user2Tokens); + + // common state changes + assert.equalBN(await rebasableProxied.getTotalShares(), premintShares.add(user1Shares).add(user2Shares)); + assert.equalBN(await rebasableProxied.totalSupply(), premintTokens.add(user1Tokens).add(user2Tokens)); }) .test("approve()", async (ctx) => { const { rebasableProxied } = ctx.contracts; - const { user1, user2 } = ctx.accounts; + const { holder, spender } = ctx.accounts; // validate initially allowance is zero assert.equalBN( - await rebasableProxied.allowance(user1.address, user2.address), + await rebasableProxied.allowance(holder.address, spender.address), "0" ); - const amount = 3; + const amount = wei`1 ether`; // validate return value of the method assert.isTrue( - await rebasableProxied.callStatic.approve(user2.address, amount) + await rebasableProxied.callStatic.approve(spender.address, amount) ); // approve tokens - const tx = await rebasableProxied.approve(user2.address, amount); + const tx = await rebasableProxied.approve(spender.address, amount); // validate Approval event was emitted await assert.emits(rebasableProxied, tx, "Approval", [ - user1.address, - user2.address, + holder.address, + spender.address, amount, ]); // validate allowance was set assert.equalBN( - await rebasableProxied.allowance(user1.address, user2.address), + await rebasableProxied.allowance(holder.address, spender.address), amount ); }) + .test("transfer() :: sender is zero address", async (ctx) => { + const { rebasableProxied } = ctx.contracts; + + const { + accounts: { zero, recipient }, + } = ctx; + await assert.revertsWith( + rebasableProxied.connect(zero).transfer(recipient.address, wei`1 ether`), + "ErrorAccountIsZeroAddress()" + ); + }) + + .test("transfer() :: recipient is zero address", async (ctx) => { + const { zero, holder } = ctx.accounts; + await assert.revertsWith( + ctx.contracts.rebasableProxied.connect(holder).transfer(zero.address, wei`1 ether`), + "ErrorAccountIsZeroAddress()" + ); + }) + + .test("transfer() :: zero balance", async (ctx) => { + const { rebasableProxied } = ctx.contracts; + const { premintTokens } = ctx.constants; + const { recipient, holder } = ctx.accounts; + + // validate balance before transfer + assert.equalBN(await rebasableProxied.balanceOf(holder.address), premintTokens); + + // transfer tokens + await rebasableProxied.connect(holder).transfer(recipient.address, "0"); + + // validate balance stays same + assert.equalBN(await rebasableProxied.balanceOf(holder.address), premintTokens); + }) + + .test("transfer() :: not enough balance", async (ctx) => { + const { rebasableProxied } = ctx.contracts; + const { premintTokens } = ctx.constants; + const { recipient, holder } = ctx.accounts; + + // validate balance before transfer + assert.equalBN(await rebasableProxied.balanceOf(holder.address), premintTokens); + + const amount = premintTokens.add(wei`1 ether`); + + // transfer tokens + await assert.revertsWith( + rebasableProxied.connect(holder).transfer(recipient.address, amount), + "ErrorNotEnoughBalance()" + ); + }) + + .test("transfer()", async (ctx) => { + const { rebasableProxied } = ctx.contracts; + const { premintTokens } = ctx.constants; + const { recipient, holder } = ctx.accounts; + + // validate balance before transfer + assert.equalBN(await rebasableProxied.balanceOf(holder.address), premintTokens); + + const amount = wei`1 ether`; + + // transfer tokens + const tx = await rebasableProxied + .connect(holder) + .transfer(recipient.address, amount); + + // validate Transfer event was emitted + await assert.emits(rebasableProxied, tx, "Transfer", [ + holder.address, + recipient.address, + amount, + ]); + + // validate balance was updated + assert.equalBN( + await rebasableProxied.balanceOf(holder.address), + premintTokens.sub(amount) + ); + + // validate total supply stays same + assert.equalBN(await rebasableProxied.totalSupply(), premintTokens); + }) + + .test("transferFrom()", async (ctx) => { + const { rebasableProxied } = ctx.contracts; + const { premintTokens } = ctx.constants; + const { recipient, holder, spender } = ctx.accounts; + + const initialAllowance = wei`2 ether`; + + // holder sets allowance for spender + await rebasableProxied.approve(spender.address, initialAllowance); + + // validate allowance is set + assert.equalBN( + await rebasableProxied.allowance(holder.address, spender.address), + initialAllowance + ); + + // validate balance before transfer + assert.equalBN(await rebasableProxied.balanceOf(holder.address), premintTokens); + + const amount = wei`1 ether`; + + const holderBalanceBefore = await rebasableProxied.balanceOf(holder.address); + + // transfer tokens + const tx = await rebasableProxied + .connect(spender) + .transferFrom(holder.address, recipient.address, amount); + + // validate Approval event was emitted + await assert.emits(rebasableProxied, tx, "Approval", [ + holder.address, + spender.address, + wei.toBigNumber(initialAllowance).sub(amount), + ]); + + // validate Transfer event was emitted + await assert.emits(rebasableProxied, tx, "Transfer", [ + holder.address, + recipient.address, + amount, + ]); + + // validate allowance updated + assert.equalBN( + await rebasableProxied.allowance(holder.address, spender.address), + wei.toBigNumber(initialAllowance).sub(amount) + ); + + // validate holder balance updated + assert.equalBN( + await rebasableProxied.balanceOf(holder.address), + holderBalanceBefore.sub(amount) + ); + + const recipientBalance = await rebasableProxied.balanceOf(recipient.address); + + // validate recipient balance updated + assert.equalBN(BigNumber.from(amount).sub(recipientBalance), "1"); + }) + + .test("transferFrom() :: max allowance", async (ctx) => { + const { rebasableProxied } = ctx.contracts; + const { premintTokens } = ctx.constants; + const { recipient, holder, spender } = ctx.accounts; + + const initialAllowance = hre.ethers.constants.MaxUint256; + + // set allowance + await rebasableProxied.approve(spender.address, initialAllowance); + + // validate allowance is set + assert.equalBN( + await rebasableProxied.allowance(holder.address, spender.address), + initialAllowance + ); + + // validate balance before transfer + assert.equalBN(await rebasableProxied.balanceOf(holder.address), premintTokens); + + const amount = wei`1 ether`; + + const holderBalanceBefore = await rebasableProxied.balanceOf(holder.address); + + // transfer tokens + const tx = await rebasableProxied + .connect(spender) + .transferFrom(holder.address, recipient.address, amount); + + // validate Approval event was not emitted + await assert.notEmits(rebasableProxied, tx, "Approval"); + + // validate Transfer event was emitted + await assert.emits(rebasableProxied, tx, "Transfer", [ + holder.address, + recipient.address, + amount, + ]); + + // validate allowance wasn't changed + assert.equalBN( + await rebasableProxied.allowance(holder.address, spender.address), + initialAllowance + ); + + // validate holder balance updated + assert.equalBN( + await rebasableProxied.balanceOf(holder.address), + holderBalanceBefore.sub(amount) + ); + + // validate recipient balance updated + const recipientBalance = await rebasableProxied.balanceOf(recipient.address); + assert.equalBN(BigNumber.from(amount).sub(recipientBalance), "1"); + }) + + .test("transferFrom() :: not enough allowance", async (ctx) => { + const { rebasableProxied } = ctx.contracts; + const { premintTokens } = ctx.constants; + const { recipient, holder, spender } = ctx.accounts; + + const initialAllowance = wei`0.9 ether`; + + // set allowance + await rebasableProxied.approve(recipient.address, initialAllowance); + + // validate allowance is set + assert.equalBN( + await rebasableProxied.allowance(holder.address, recipient.address), + initialAllowance + ); + + // validate balance before transfer + assert.equalBN(await rebasableProxied.balanceOf(holder.address), premintTokens); + + const amount = wei`1 ether`; + + // transfer tokens + await assert.revertsWith( + rebasableProxied + .connect(spender) + .transferFrom(holder.address, recipient.address, amount), + "ErrorNotEnoughAllowance()" + ); + }) + + .test("increaseAllowance() :: initial allowance is zero", async (ctx) => { + const { rebasableProxied } = ctx.contracts; + const { holder, spender } = ctx.accounts; + + // validate allowance before increasing + assert.equalBN( + await rebasableProxied.allowance(holder.address, spender.address), + "0" + ); + + const allowanceIncrease = wei`1 ether`; + + // increase allowance + const tx = await rebasableProxied.increaseAllowance( + spender.address, + allowanceIncrease + ); + + // validate Approval event was emitted + await assert.emits(rebasableProxied, tx, "Approval", [ + holder.address, + spender.address, + allowanceIncrease, + ]); + + // validate allowance was updated correctly + assert.equalBN( + await rebasableProxied.allowance(holder.address, spender.address), + allowanceIncrease + ); + }) + + .test("increaseAllowance() :: initial allowance is not zero", async (ctx) => { + const { rebasableProxied } = ctx.contracts; + const { holder, spender } = ctx.accounts; + + const initialAllowance = wei`2 ether`; + + // set initial allowance + await rebasableProxied.approve(spender.address, initialAllowance); + + // validate allowance before increasing + assert.equalBN( + await rebasableProxied.allowance(holder.address, spender.address), + initialAllowance + ); + + const allowanceIncrease = wei`1 ether`; + + // increase allowance + const tx = await rebasableProxied.increaseAllowance( + spender.address, + allowanceIncrease + ); + + const expectedAllowance = wei + .toBigNumber(initialAllowance) + .add(allowanceIncrease); + + // validate Approval event was emitted + await assert.emits(rebasableProxied, tx, "Approval", [ + holder.address, + spender.address, + expectedAllowance, + ]); + + // validate allowance was updated correctly + assert.equalBN( + await rebasableProxied.allowance(holder.address, spender.address), + expectedAllowance + ); + }) + + .test("increaseAllowance() :: the increase is not zero", async (ctx) => { + const { rebasableProxied } = ctx.contracts; + const { holder, spender } = ctx.accounts; + + const initialAllowance = wei`2 ether`; + + // set initial allowance + await rebasableProxied.approve(spender.address, initialAllowance); + + // validate allowance before increasing + assert.equalBN( + await rebasableProxied.allowance(holder.address, spender.address), + initialAllowance + ); + + // increase allowance + const tx = await rebasableProxied.increaseAllowance(spender.address, "0"); + + // validate Approval event was emitted + await assert.emits(rebasableProxied, tx, "Approval", [ + holder.address, + spender.address, + initialAllowance, + ]); + + // validate allowance was updated correctly + assert.equalBN( + await rebasableProxied.allowance(holder.address, spender.address), + initialAllowance + ); + }) + + .test( + "decreaseAllowance() :: decrease is greater than current allowance", + async (ctx) => { + const { rebasableProxied } = ctx.contracts; + const { holder, spender } = ctx.accounts; + + // validate allowance before increasing + assert.equalBN( + await rebasableProxied.allowance(holder.address, spender.address), + "0" + ); + + const allowanceDecrease = wei`1 ether`; + + // decrease allowance + await assert.revertsWith( + rebasableProxied.decreaseAllowance(spender.address, allowanceDecrease), + "ErrorDecreasedAllowanceBelowZero()" + ); + } + ) + + .group([wei`1 ether`, "0"], (allowanceDecrease) => [ + `decreaseAllowance() :: the decrease is ${allowanceDecrease} wei`, + async (ctx) => { + const { rebasableProxied } = ctx.contracts; + const { holder, spender } = ctx.accounts; + + const initialAllowance = wei`2 ether`; + + // set initial allowance + await rebasableProxied.approve(spender.address, initialAllowance); + + // validate allowance before increasing + assert.equalBN( + await rebasableProxied.allowance(holder.address, spender.address), + initialAllowance + ); + + // decrease allowance + const tx = await rebasableProxied.decreaseAllowance( + spender.address, + allowanceDecrease + ); + + const expectedAllowance = wei + .toBigNumber(initialAllowance) + .sub(allowanceDecrease); + + // validate Approval event was emitted + await assert.emits(rebasableProxied, tx, "Approval", [ + holder.address, + spender.address, + expectedAllowance, + ]); + + // validate allowance was updated correctly + assert.equalBN( + await rebasableProxied.allowance(holder.address, spender.address), + expectedAllowance + ); + }, + ]) + + .test("bridgeMint() :: not owner", async (ctx) => { + const { rebasableProxied } = ctx.contracts; + const { stranger } = ctx.accounts; + + await assert.revertsWith( + rebasableProxied + .connect(stranger) + .mintShares(stranger.address, wei`1000 ether`), + "ErrorNotBridge()" + ); + }) + + .group([wei`1000 ether`, "0"], (mintAmount) => [ + `bridgeMint() :: amount is ${mintAmount} wei`, + async (ctx) => { + const { rebasableProxied } = ctx.contracts; + const { premintShares } = ctx.constants; + const { recipient, owner } = ctx.accounts; + + // validate balance before mint + assert.equalBN(await rebasableProxied.balanceOf(recipient.address), 0); + + // validate total supply before mint + assert.equalBN(await rebasableProxied.getTotalShares(), premintShares); + + // mint tokens + const tx = await rebasableProxied + .connect(owner) + .mintShares(recipient.address, mintAmount); + + // validate Transfer event was emitted + await assert.emits(rebasableProxied, tx, "Transfer", [ + hre.ethers.constants.AddressZero, + recipient.address, + mintAmount, + ]); + + // validate balance was updated + assert.equalBN( + await rebasableProxied.sharesOf(recipient.address), + mintAmount + ); + + // validate total supply was updated + assert.equalBN( + await rebasableProxied.getTotalShares(), + premintShares.add(mintAmount) + ); + }, + ]) + + .test("bridgeBurn() :: not owner", async (ctx) => { + const { rebasableProxied } = ctx.contracts; + const { holder, stranger } = ctx.accounts; + + await assert.revertsWith( + rebasableProxied.connect(stranger).burnShares(holder.address, wei`100 ether`), + "ErrorNotBridge()" + ); + }) + + .test("bridgeBurn() :: amount exceeds balance", async (ctx) => { + const { rebasableProxied } = ctx.contracts; + const { owner, stranger } = ctx.accounts; + + // validate stranger has no tokens + assert.equalBN(await rebasableProxied.balanceOf(stranger.address), 0); + + await assert.revertsWith( + rebasableProxied.connect(owner).burnShares(stranger.address, wei`100 ether`), + "ErrorNotEnoughBalance()" + ); + }) + + .group([wei`10 ether`, "0"], (burnAmount) => [ + `bridgeBurn() :: amount is ${burnAmount} wei`, + async (ctx) => { + const { rebasableProxied } = ctx.contracts; + const { premintShares } = ctx.constants; + const { owner, holder } = ctx.accounts; + + // validate balance before mint + assert.equalBN(await rebasableProxied.sharesOf(holder.address), premintShares); + + // validate total supply before mint + assert.equalBN(await rebasableProxied.getTotalShares(), premintShares); + + // burn tokens + const tx = await rebasableProxied + .connect(owner) + .burnShares(holder.address, burnAmount); + + // validate Transfer event was emitted + await assert.emits(rebasableProxied, tx, "Transfer", [ + holder.address, + hre.ethers.constants.AddressZero, + burnAmount, + ]); + + const expectedBalanceAndTotalSupply = premintShares + .sub(burnAmount); + + // validate balance was updated + assert.equalBN( + await rebasableProxied.sharesOf(holder.address), + expectedBalanceAndTotalSupply + ); + + // validate total supply was updated + assert.equalBN( + await rebasableProxied.getTotalShares(), + expectedBalanceAndTotalSupply + ); + }, + ]) + .run(); async function ctxFactory() { const name = "StETH Test Token"; const symbol = "StETH"; - const decimals = 18; - const [deployer, user1, user2] = await hre.ethers.getSigners(); + const decimalsToSet = 16; + const decimals = BigNumber.from(10).pow(decimalsToSet); + const rate = BigNumber.from('12').pow(decimalsToSet - 1); + const premintShares = wei.toBigNumber(wei`100 ether`); + const premintTokens = BigNumber.from(rate).mul(premintShares).div(decimals); + + const [ + deployer, + owner, + recipient, + spender, + holder, + stranger, + user1, + user2 + ] = await hre.ethers.getSigners(); const wrappedTokenStub = await new ERC20Stub__factory(deployer).deploy(); - - const tokensRateOracleStub = await new TokenRateOracleStub__factory(deployer).deploy(); - + const tokenRateOracleStub = await new TokenRateOracleStub__factory(deployer).deploy(); const rebasableTokenImpl = await new ERC20Rebasable__factory(deployer).deploy( - wrappedTokenStub.address, - tokensRateOracleStub.address, name, symbol, - decimals + decimalsToSet, + wrappedTokenStub.address, + tokenRateOracleStub.address, + owner.address ); - rebasableTokenImpl.wrap + await hre.network.provider.request({ method: "hardhat_impersonateAccount", params: [hre.ethers.constants.AddressZero], }); - + + const zero = await hre.ethers.getSigner(hre.ethers.constants.AddressZero); + const l2TokensProxy = await new OssifiableProxy__factory(deployer).deploy( rebasableTokenImpl.address, deployer.address, @@ -267,12 +966,18 @@ async function ctxFactory() { const rebasableProxied = ERC20Rebasable__factory.connect( l2TokensProxy.address, - user1 + holder ); - + + await tokenRateOracleStub.setDecimals(decimalsToSet); + await tokenRateOracleStub.setLatestRoundDataAnswer(rate); + await tokenRateOracleStub.setUpdatedAt(1000); + + await rebasableProxied.connect(owner).mintShares(holder.address, premintShares); + return { - accounts: { deployer, user1, user2 }, - constants: { name, symbol, decimals }, - contracts: { rebasableProxied, wrappedTokenStub, tokensRateOracleStub } + accounts: { deployer, owner, recipient, spender, holder, stranger, zero, user1, user2 }, + constants: { name, symbol, decimalsToSet, decimals, premintShares, premintTokens, rate }, + contracts: { rebasableProxied, wrappedTokenStub, tokenRateOracleStub } }; } diff --git a/utils/optimism/deployment.ts b/utils/optimism/deployment.ts index 4a3625a3..33b3fdb0 100644 --- a/utils/optimism/deployment.ts +++ b/utils/optimism/deployment.ts @@ -38,7 +38,7 @@ export default function deployment( async erc20TokenBridgeDeployScript( l1Token: string, l1TokenRebasable: string, - tokensRateOracleStub: string, + tokenRateOracleStub: string, l1Params: OptL1DeployScriptParams, l2Params: OptL2DeployScriptParams, ) { @@ -141,7 +141,7 @@ export default function deployment( factory: ERC20Rebasable__factory, args: [ expectedL2TokenProxyAddress, - tokensRateOracleStub, + tokenRateOracleStub, l2TokenRebasableName, l2TokenRebasableSymbol, decimals, @@ -174,7 +174,6 @@ export default function deployment( l1TokenRebasable, expectedL2TokenProxyAddress, expectedL2TokenRebasableProxyAddress, - tokensRateOracleStub, options?.overrides, ], afterDeploy: (c) => diff --git a/utils/optimism/testing.ts b/utils/optimism/testing.ts index 721ed54b..4042a351 100644 --- a/utils/optimism/testing.ts +++ b/utils/optimism/testing.ts @@ -10,7 +10,7 @@ import { ERC20Bridged__factory, ERC20BridgedStub__factory, ERC20WrapableStub__factory, - TokensRateOracleStub__factory, + TokenRateOracleStub__factory, L1ERC20TokenBridge__factory, L2ERC20TokenBridge__factory, CrossDomainMessengerStub__factory, @@ -164,7 +164,7 @@ async function loadDeployedBridges( testingUtils.env.OPT_L1_TOKEN(), l1SignerOrProvider ), - tokensRateOracle: TokensRateOracleStub__factory.connect( + tokenRateOracle: TokenRateOracleStub__factory.connect( testingUtils.env.OPT_L1_TOKEN(), l1SignerOrProvider ), @@ -201,17 +201,17 @@ async function deployTestBridge( "TT" ); - const tokensRateOracleStub = await new TokensRateOracleStub__factory(optDeployer).deploy(); - await tokensRateOracleStub.setLatestRoundDataAnswer(BigNumber.from("1000000000000000000")); - await tokensRateOracleStub.setDecimals(18); - await tokensRateOracleStub.setUpdatedAt(100); + const tokenRateOracleStub = await new TokenRateOracleStub__factory(optDeployer).deploy(); + await tokenRateOracleStub.setLatestRoundDataAnswer(BigNumber.from("1000000000000000000")); + await tokenRateOracleStub.setDecimals(18); + await tokenRateOracleStub.setUpdatedAt(100); const [ethDeployScript, optDeployScript] = await deployment( networkName ).erc20TokenBridgeDeployScript( l1Token.address, l1TokenRebasable.address, - tokensRateOracleStub.address, + tokenRateOracleStub.address, { deployer: ethDeployer, admins: { proxy: ethDeployer.address, bridge: ethDeployer.address }, @@ -252,7 +252,7 @@ async function deployTestBridge( return { l1Token: l1Token.connect(ethProvider), l1TokenRebasable: l1TokenRebasable.connect(ethProvider), - tokensRateOracle: tokensRateOracleStub, + tokenRateOracle: tokenRateOracleStub, ...connectBridgeContracts( { l2Token: optDeployScript.getContractAddress(1), From 86788b201d8f8689f7a3359dac528039b19146d2 Mon Sep 17 00:00:00 2001 From: Anton Kovalchuk Date: Wed, 27 Dec 2023 23:26:12 +0100 Subject: [PATCH 016/148] simplify oracle, remove warnings --- .solhint.json | 3 +- contracts/optimism/DepositDataCodec.sol | 12 +- contracts/optimism/L1ERC20TokenBridge.sol | 11 +- contracts/optimism/L2ERC20TokenBridge.sol | 4 +- contracts/optimism/TokenRateOracle.sol | 147 +++++------------- contracts/stubs/ERC20Stub.sol | 1 - contracts/stubs/ERC20WrapableStub.sol | 2 - contracts/stubs/TokenRateOracleStub.sol | 16 +- contracts/token/ERC20Core.sol | 2 - contracts/token/ERC20Rebasable.sol | 10 +- .../token/interfaces/IERC20BridgedShares.sol | 23 +++ .../token/interfaces/ITokenRateOracle.sol | 23 +-- 12 files changed, 102 insertions(+), 152 deletions(-) create mode 100644 contracts/token/interfaces/IERC20BridgedShares.sol diff --git a/.solhint.json b/.solhint.json index ff8b9e54..83b993c5 100644 --- a/.solhint.json +++ b/.solhint.json @@ -14,6 +14,7 @@ "ignoreConstructors": true } ], - "lido/fixed-compiler-version": "error" + "lido/fixed-compiler-version": "error", + "const-name-snakecase": false } } diff --git a/contracts/optimism/DepositDataCodec.sol b/contracts/optimism/DepositDataCodec.sol index 91b8c574..55dd9ea8 100644 --- a/contracts/optimism/DepositDataCodec.sol +++ b/contracts/optimism/DepositDataCodec.sol @@ -6,8 +6,8 @@ pragma solidity 0.8.10; contract DepositDataCodec { struct DepositData { - uint256 rate; - uint256 time; + uint96 rate; + uint40 time; bytes data; } @@ -22,14 +22,14 @@ contract DepositDataCodec { function decodeDepositData(bytes calldata buffer) internal pure returns (DepositData memory) { - if (buffer.length < 32 * 2) { + if (buffer.length < 12 + 5) { revert ErrorDepositDataLength(); } DepositData memory depositData = DepositData({ - rate: uint256(bytes32(buffer[0:32])), - time: uint256(bytes32(buffer[32:64])), - data: buffer[64:] + rate: uint96(bytes12(buffer[0:12])), + time: uint40(bytes5(buffer[12:17])), + data: buffer[17:] }); return depositData; diff --git a/contracts/optimism/L1ERC20TokenBridge.sol b/contracts/optimism/L1ERC20TokenBridge.sol index 1c39797e..a475594b 100644 --- a/contracts/optimism/L1ERC20TokenBridge.sol +++ b/contracts/optimism/L1ERC20TokenBridge.sol @@ -16,9 +16,8 @@ import {CrossDomainEnabled} from "./CrossDomainEnabled.sol"; import {DepositDataCodec} from "./DepositDataCodec.sol"; import {IERC20Wrapable} from "../token/interfaces/IERC20Wrapable.sol"; -import "hardhat/console.sol"; -// Check if Optimism changed API for bridges. They could depricate methods. +// Check if Optimism changed API for bridges. They could deprecate methods. // Optimise gas usage with data transfer. Maybe cache rate and see if it changed. /// @author psirex, kovalgek @@ -54,9 +53,9 @@ contract L1ERC20TokenBridge is l2TokenBridge = l2TokenBridge_; } - function pushTokenRate(address to_, uint32 l2Gas_) external { + function pushTokenRate(uint32 l2Gas_) external { bytes memory empty = new bytes(0); - _depositERC20To(l1TokenRebasable, l2TokenRebasable, to_, 0, l2Gas_, empty); + _depositERC20To(l1TokenRebasable, l2TokenRebasable, l2TokenBridge, 0, l2Gas_, empty); } /// @inheritdoc IL1ERC20Bridge @@ -140,8 +139,8 @@ contract L1ERC20TokenBridge is if (isRebasableTokenFlow(l1Token_, l2Token_)) { DepositData memory depositData = DepositData({ - rate: IERC20Wrapable(l1TokenNonRebasable).stETHPerToken(), - time: block.timestamp, + rate: uint96(IERC20Wrapable(l1TokenNonRebasable).stETHPerToken()), + time: uint40(block.timestamp), data: data_ }); diff --git a/contracts/optimism/L2ERC20TokenBridge.sol b/contracts/optimism/L2ERC20TokenBridge.sol index 36ae5c0b..3e52b948 100644 --- a/contracts/optimism/L2ERC20TokenBridge.sol +++ b/contracts/optimism/L2ERC20TokenBridge.sol @@ -17,8 +17,6 @@ import {BridgeableTokensOptimism} from "./BridgeableTokensOptimism.sol"; import {CrossDomainEnabled} from "./CrossDomainEnabled.sol"; import {DepositDataCodec} from "./DepositDataCodec.sol"; -import { console } from "hardhat/console.sol"; - /// @author psirex /// @notice The L2 token bridge works with the L1 token bridge to enable ERC20 token bridging /// between L1 and L2. It acts as a minter for new tokens when it hears about @@ -111,7 +109,7 @@ contract L2ERC20TokenBridge is if (isRebasableTokenFlow(l1Token_, l2Token_)) { DepositData memory depositData = decodeDepositData(data_); ITokenRateOracle tokenRateOracle = ERC20Rebasable(l2TokenRebasable).tokenRateOracle(); - tokenRateOracle.updateRate(int256(depositData.rate), depositData.time, 0); + tokenRateOracle.updateRate(depositData.rate, depositData.time); ERC20Rebasable(l2TokenRebasable).mintShares(to_, amount_); emit DepositFinalized(l1Token_, l2Token_, from_, to_, amount_, depositData.data); } else if (isNonRebasableTokenFlow(l1Token_, l2Token_)) { diff --git a/contracts/optimism/TokenRateOracle.sol b/contracts/optimism/TokenRateOracle.sol index 0f8cd320..6190ee90 100644 --- a/contracts/optimism/TokenRateOracle.sol +++ b/contracts/optimism/TokenRateOracle.sol @@ -4,50 +4,34 @@ pragma solidity 0.8.10; import {ITokenRateOracle} from "../token/interfaces/ITokenRateOracle.sol"; -// import { SafeCast } from "@openzeppelin/contracts-v4.4/utils/math/SafeCast.sol"; +/// @author kovalgek +/// @notice Oracle for storing token rate. contract TokenRateOracle is ITokenRateOracle { - /// Chain specification - uint256 private immutable slotsPerEpoch; - uint256 private immutable secondsPerSlot; - uint256 private immutable genesisTime; - uint256 private immutable initialEpoch; - uint256 private immutable epochsPerFrame; + error NotAnOwner(address caller); + error IncorrectRateTimestamp(); - error InvalidChainConfig(); - error InitialEpochRefSlotCannotBeEarlierThanProcessingSlot(); - error InitialEpochIsYetToArrive(); + /// @notice wstETH/stETH token rate. + uint256 private tokenRate; - int256 private tokenRate; - uint8 private decimalsInAnswer; + /// @notice L1 time when token rate was pushed. uint256 private rateL1Timestamp; - uint80 private answeredInRound; - constructor( - uint256 slotsPerEpoch_, - uint256 secondsPerSlot_, - uint256 genesisTime_, - uint256 initialEpoch_, - uint256 epochsPerFrame_ - ) { - if (slotsPerEpoch_ == 0) revert InvalidChainConfig(); - if (secondsPerSlot_ == 0) revert InvalidChainConfig(); + /// @notice A bridge which can update oracle. + address public immutable bridge; + + /// @notice An updater which can update oracle. + address public immutable tokenRateUpdater; - // Should I use toUint64(); - slotsPerEpoch = slotsPerEpoch_; - secondsPerSlot = secondsPerSlot_; - genesisTime = genesisTime_; - initialEpoch = initialEpoch_; - epochsPerFrame = epochsPerFrame_; + /// @param bridge_ the bridge address that has a right to updates oracle. + /// @param tokenRateUpdater_ address of oracle updater that has a right to updates oracle. + constructor(address bridge_, address tokenRateUpdater_) { + bridge = bridge_; + tokenRateUpdater = tokenRateUpdater_; } - + /// @inheritdoc ITokenRateOracle - /// @return roundId_ is reference slot of HashConsensus - /// @return answer_ is wstETH/stETH token rate. - /// @return startedAt_ is HashConsensus frame start. - /// @return updatedAt_ is L2 timestamp of token rate update. - /// @return answeredInRound_ is the round ID of the round in which the answer was computed function latestRoundData() external view returns ( uint80 roundId_, int256 answer_, @@ -55,14 +39,13 @@ contract TokenRateOracle is ITokenRateOracle { uint256 updatedAt_, uint80 answeredInRound_ ) { - uint256 refSlot = _getRefSlot(initialEpoch, epochsPerFrame); - uint80 roundId = uint80(refSlot); - uint256 startedAt = _computeTimestampAtSlot(refSlot); + uint80 roundId = uint80(rateL1Timestamp); // TODO: add solt + uint80 answeredInRound = roundId; return ( roundId, - tokenRate, - startedAt, + int256(tokenRate), + rateL1Timestamp, rateL1Timestamp, answeredInRound ); @@ -70,89 +53,29 @@ contract TokenRateOracle is ITokenRateOracle { /// @inheritdoc ITokenRateOracle function latestAnswer() external view returns (int256) { - return tokenRate; + return int256(tokenRate); } /// @inheritdoc ITokenRateOracle - function decimals() external view returns (uint8) { - return decimalsInAnswer; + function decimals() external pure returns (uint8) { + return 18; } /// @inheritdoc ITokenRateOracle - function updateRate(int256 rate, uint256 rateL1Timestamp_) external { - // check timestamp not late as current one. - if (rateL1Timestamp_ < _getTime()) { - return; + function updateRate(uint256 tokenRate_, uint256 rateL1Timestamp_) external onlyOwner { + // reject rates from the future + if (rateL1Timestamp_ < rateL1Timestamp) { + revert IncorrectRateTimestamp(); } - tokenRate = rate; + tokenRate = tokenRate_; rateL1Timestamp = rateL1Timestamp_; - answeredInRound = 666; - decimalsInAnswer = 10; - } - - /// Frame utilities - - function _getTime() internal virtual view returns (uint256) { - return block.timestamp; // solhint-disable-line not-rely-on-time - } - - function _getRefSlot(uint256 initialEpoch_, uint256 epochsPerFrame_) internal view returns (uint256) { - return _getRefSlotAtTimestamp(_getTime(), initialEpoch_, epochsPerFrame_); - } - - function _getRefSlotAtTimestamp(uint256 timestamp_, uint256 initialEpoch_, uint256 epochsPerFrame_) - internal view returns (uint256) - { - return _getRefSlotAtIndex(_computeFrameIndex(timestamp_, initialEpoch_, epochsPerFrame_), initialEpoch_, epochsPerFrame_); - } - - function _getRefSlotAtIndex(uint256 frameIndex_, uint256 initialEpoch_, uint256 epochsPerFrame_) - internal view returns (uint256) - { - uint256 frameStartEpoch = _computeStartEpochOfFrameWithIndex(frameIndex_, initialEpoch_, epochsPerFrame_); - uint256 frameStartSlot = _computeStartSlotAtEpoch(frameStartEpoch); - return uint64(frameStartSlot - 1); } - function _computeStartSlotAtEpoch(uint256 epoch_) internal view returns (uint256) { - // See: github.com/ethereum/consensus-specs/blob/dev/specs/phase0/beacon-chain.md#compute_start_slot_at_epoch - return epoch_ * slotsPerEpoch; - } - - function _computeStartEpochOfFrameWithIndex(uint256 frameIndex_, uint256 initialEpoch_, uint256 epochsPerFrame_) - internal pure returns (uint256) - { - return initialEpoch_ + frameIndex_ * epochsPerFrame_; - } - - function _computeFrameIndex( - uint256 timestamp_, - uint256 initialEpoch_, - uint256 epochsPerFrame_ - ) internal view returns (uint256) - { - uint256 epoch = _computeEpochAtTimestamp(timestamp_); - if (epoch < initialEpoch_) { - revert InitialEpochIsYetToArrive(); + /// @dev validates that method called by one of the owners + modifier onlyOwner() { + if (msg.sender != bridge || msg.sender != tokenRateUpdater) { + revert NotAnOwner(msg.sender); } - return (epoch - initialEpoch_) / epochsPerFrame_; - } - - function _computeEpochAtTimestamp(uint256 timestamp_) internal view returns (uint256) { - return _computeEpochAtSlot(_computeSlotAtTimestamp(timestamp_)); - } - - function _computeEpochAtSlot(uint256 slot_) internal view returns (uint256) { - // See: github.com/ethereum/consensus-specs/blob/dev/specs/phase0/beacon-chain.md#compute_epoch_at_slot - return slot_ / slotsPerEpoch; - } - - function _computeSlotAtTimestamp(uint256 timestamp_) internal view returns (uint256) { - return (timestamp_ - genesisTime) / secondsPerSlot; - } - - function _computeTimestampAtSlot(uint256 slot_) internal view returns (uint256) { - // See: github.com/ethereum/consensus-specs/blob/dev/specs/bellatrix/beacon-chain.md#compute_timestamp_at_slot - return genesisTime + slot_ * secondsPerSlot; + _; } } \ No newline at end of file diff --git a/contracts/stubs/ERC20Stub.sol b/contracts/stubs/ERC20Stub.sol index 686ea516..b8ef5902 100644 --- a/contracts/stubs/ERC20Stub.sol +++ b/contracts/stubs/ERC20Stub.sol @@ -4,7 +4,6 @@ pragma solidity 0.8.10; import {IERC20} from "@openzeppelin/contracts/token/ERC20/IERC20.sol"; -import { console } from "hardhat/console.sol"; contract ERC20Stub is IERC20 { diff --git a/contracts/stubs/ERC20WrapableStub.sol b/contracts/stubs/ERC20WrapableStub.sol index 9f48b24f..de8f244b 100644 --- a/contracts/stubs/ERC20WrapableStub.sol +++ b/contracts/stubs/ERC20WrapableStub.sol @@ -7,8 +7,6 @@ import {IERC20Bridged} from "../token/interfaces/IERC20Bridged.sol"; import {ERC20} from "@openzeppelin/contracts/token/ERC20/ERC20.sol"; import {IERC20} from "@openzeppelin/contracts/token/ERC20/IERC20.sol"; import {IERC20Wrapable} from "../token/interfaces/IERC20Wrapable.sol"; -// import {ERC20Core} from "../token/ERC20Core.sol"; -import { console } from "hardhat/console.sol"; // represents wstETH on L1 contract ERC20WrapableStub is IERC20Wrapable, ERC20 { diff --git a/contracts/stubs/TokenRateOracleStub.sol b/contracts/stubs/TokenRateOracleStub.sol index 40463af7..6581e512 100644 --- a/contracts/stubs/TokenRateOracleStub.sol +++ b/contracts/stubs/TokenRateOracleStub.sol @@ -17,9 +17,9 @@ contract TokenRateOracleStub is ITokenRateOracle { return _decimals; } - int256 public latestRoundDataAnswer; + uint256 public latestRoundDataAnswer; - function setLatestRoundDataAnswer(int256 answer_) external { + function setLatestRoundDataAnswer(uint256 answer_) external { latestRoundDataAnswer = answer_; } @@ -42,14 +42,20 @@ contract TokenRateOracleStub is ITokenRateOracle { uint256 updatedAt, uint80 answeredInRound ) { - return (0,latestRoundDataAnswer,0,latestRoundDataUpdatedAt,0); + return ( + 0, + int256(latestRoundDataAnswer), + 0, + latestRoundDataUpdatedAt, + 0 + ); } function latestAnswer() external view returns (int256) { - return latestRoundDataAnswer; + return int256(latestRoundDataAnswer); } - function updateRate(int256 tokenRate_, uint256 rateL1Timestamp_, uint256 lastProcessingRefSlot_) external { + function updateRate(uint256 tokenRate_, uint256 rateL1Timestamp_) external { // check timestamp not late as current one. latestRoundDataAnswer = tokenRate_; latestRoundDataUpdatedAt = rateL1Timestamp_; diff --git a/contracts/token/ERC20Core.sol b/contracts/token/ERC20Core.sol index 9fb618cb..bf4e67db 100644 --- a/contracts/token/ERC20Core.sol +++ b/contracts/token/ERC20Core.sol @@ -4,7 +4,6 @@ pragma solidity 0.8.10; import {IERC20} from "@openzeppelin/contracts/token/ERC20/IERC20.sol"; -import { console } from "hardhat/console.sol"; /// @author psirex /// @notice Contains the required logic of the ERC20 standard as defined in the EIP. Additionally @@ -122,7 +121,6 @@ contract ERC20Core is IERC20 { address spender_, uint256 amount_ ) internal virtual onlyNonZeroAccount(owner_) onlyNonZeroAccount(spender_) { - console.log("_approve %@ %@ %@", msg.sender, owner_, spender_); allowance[owner_][spender_] = amount_; emit Approval(owner_, spender_, amount_); } diff --git a/contracts/token/ERC20Rebasable.sol b/contracts/token/ERC20Rebasable.sol index b7a32798..a46fa2da 100644 --- a/contracts/token/ERC20Rebasable.sol +++ b/contracts/token/ERC20Rebasable.sol @@ -5,13 +5,13 @@ pragma solidity 0.8.10; import {IERC20} from "@openzeppelin/contracts/token/ERC20/IERC20.sol"; import {IERC20Wrapable} from "./interfaces/IERC20Wrapable.sol"; +import {IERC20BridgedShares} from "./interfaces/IERC20BridgedShares.sol"; import {ITokenRateOracle} from "./interfaces/ITokenRateOracle.sol"; import {ERC20Metadata} from "./ERC20Metadata.sol"; -// import { console } from "hardhat/console.sol"; /// @author kovalgek /// @notice Extends the ERC20Shared functionality -contract ERC20Rebasable is IERC20Wrapable, IERC20, ERC20Metadata { +contract ERC20Rebasable is IERC20, IERC20Wrapable, IERC20BridgedShares, ERC20Metadata { error ErrorZeroSharesWrap(); error ErrorZeroTokensUnwrap(); @@ -25,7 +25,7 @@ contract ERC20Rebasable is IERC20Wrapable, IERC20, ERC20Metadata { error ErrorDecreasedAllowanceBelowZero(); error ErrorNotBridge(); - /// @notice Bridge which can mint and burn tokens on L2. + /// @inheritdoc IERC20BridgedShares address public immutable bridge; /// @notice Contract of non-rebasable token to wrap. @@ -97,12 +97,12 @@ contract ERC20Rebasable is IERC20Wrapable, IERC20, ERC20Metadata { return uint256(tokenRateOracle.latestAnswer()); } - // allow call only bridge + /// @inheritdoc IERC20BridgedShares function mintShares(address account_, uint256 amount_) external onlyBridge returns (uint256) { return _mintShares(account_, amount_); } - // allow call only bridge + /// @inheritdoc IERC20BridgedShares function burnShares(address account_, uint256 amount_) external onlyBridge { _burnShares(account_, amount_); } diff --git a/contracts/token/interfaces/IERC20BridgedShares.sol b/contracts/token/interfaces/IERC20BridgedShares.sol new file mode 100644 index 00000000..11f3ba78 --- /dev/null +++ b/contracts/token/interfaces/IERC20BridgedShares.sol @@ -0,0 +1,23 @@ +// SPDX-FileCopyrightText: 2022 Lido +// SPDX-License-Identifier: GPL-3.0 + +pragma solidity 0.8.10; + +import {IERC20} from "@openzeppelin/contracts/token/ERC20/IERC20.sol"; + +/// @author kovalgek +/// @notice Extends the ERC20 functionality that allows the bridge to mint/burn tokens +interface IERC20BridgedShares is IERC20 { + /// @notice Returns bridge which can mint and burn tokens on L2 + function bridge() external view returns (address); + + /// @notice Creates amount_ tokens and assigns them to account_, increasing the total supply + /// @param account_ An address of the account to mint tokens + /// @param amount_ An amount of tokens to mint + function mintShares(address account_, uint256 amount_) external returns (uint256); + + /// @notice Destroys amount_ tokens from account_, reducing the total supply + /// @param account_ An address of the account to burn tokens + /// @param amount_ An amount of tokens to burn + function burnShares(address account_, uint256 amount_) external; +} diff --git a/contracts/token/interfaces/ITokenRateOracle.sol b/contracts/token/interfaces/ITokenRateOracle.sol index eb9aa8aa..1c33fc21 100644 --- a/contracts/token/interfaces/ITokenRateOracle.sol +++ b/contracts/token/interfaces/ITokenRateOracle.sol @@ -4,27 +4,32 @@ pragma solidity 0.8.10; /// @author kovalgek -/// @notice Oracle interface for two tokens rate. A subset of Chainlink data feed interface. +/// @notice Oracle interface for token rate. A subset of Chainlink data feed interface. interface ITokenRateOracle { - /// @notice get data about the latest round. + /// @notice get the latest token rate data. + /// @return roundId_ is a unique id for each answer. The value is based on timestamp. + /// @return answer_ is wstETH/stETH token rate. + /// @return startedAt_ is time when rate was pushed on L1 side. + /// @return updatedAt_ is the same as startedAt_. + /// @return answeredInRound_ is the same as roundId_. function latestRoundData() external view returns ( - uint80 roundId, - int256 answer, - uint256 startedAt, - uint256 updatedAt, - uint80 answeredInRound + uint80 roundId_, + int256 answer_, + uint256 startedAt_, + uint256 updatedAt_, + uint80 answeredInRound_ ); - /// @notice get answer about the latest round. + /// @notice get the lastest token rate. function latestAnswer() external view returns (int256); /// @notice represents the number of decimals the oracle responses represent. function decimals() external view returns (uint8); /// @notice Updates token rate. - function updateRate(int256 tokenRate_, uint256 rateL1Timestamp_, uint256 lastProcessingRefSlot_) external; + function updateRate(uint256 tokenRate_, uint256 rateL1Timestamp_) external; } \ No newline at end of file From ddffe1ffee7b756a71b82f54c0acaec4fb819f35 Mon Sep 17 00:00:00 2001 From: Anton Kovalchuk Date: Thu, 28 Dec 2023 10:43:11 +0100 Subject: [PATCH 017/148] update unit tests for token rate oracle --- contracts/optimism/TokenRateOracle.sol | 2 +- test/optimism/TokenRateOracle.unit.test.ts | 96 +++++++++++----------- 2 files changed, 47 insertions(+), 51 deletions(-) diff --git a/contracts/optimism/TokenRateOracle.sol b/contracts/optimism/TokenRateOracle.sol index 6190ee90..ef5e8dc5 100644 --- a/contracts/optimism/TokenRateOracle.sol +++ b/contracts/optimism/TokenRateOracle.sol @@ -73,7 +73,7 @@ contract TokenRateOracle is ITokenRateOracle { /// @dev validates that method called by one of the owners modifier onlyOwner() { - if (msg.sender != bridge || msg.sender != tokenRateUpdater) { + if (msg.sender != bridge && msg.sender != tokenRateUpdater) { revert NotAnOwner(msg.sender); } _; diff --git a/test/optimism/TokenRateOracle.unit.test.ts b/test/optimism/TokenRateOracle.unit.test.ts index 3858ed2e..f5981628 100644 --- a/test/optimism/TokenRateOracle.unit.test.ts +++ b/test/optimism/TokenRateOracle.unit.test.ts @@ -2,36 +2,17 @@ import hre from "hardhat"; import { assert } from "chai"; import { unit } from "../../utils/testing"; import { TokenRateOracle__factory } from "../../typechain"; -import { ethers } from "ethers"; unit("TokenRateOracle", ctxFactory) - .test("init zero slotsPerEpoch", async (ctx) => { - const [deployer] = await hre.ethers.getSigners(); - await assert.revertsWith(new TokenRateOracle__factory(deployer).deploy( - 0, - 10, - 1000, - 100, - 50 - ), "InvalidChainConfig()"); - }) - - .test("init zero secondsPerSlot", async (ctx) => { - const [deployer] = await hre.ethers.getSigners(); - await assert.revertsWith(new TokenRateOracle__factory(deployer).deploy( - 41, - 0, - 1000, - 100, - 50 - ), "InvalidChainConfig()"); - }) - .test("state after init", async (ctx) => { - const { tokensRateOracle } = ctx.contracts; + const { tokenRateOracle } = ctx.contracts; + const { bridge, updater } = ctx.accounts; + + assert.equal(await tokenRateOracle.bridge(), bridge.address); + assert.equal(await tokenRateOracle.tokenRateUpdater(), updater.address); - assert.equalBN(await tokensRateOracle.latestAnswer(), 0); + assert.equalBN(await tokenRateOracle.latestAnswer(), 0); const { roundId_, @@ -39,23 +20,42 @@ unit("TokenRateOracle", ctxFactory) startedAt_, updatedAt_, answeredInRound_ - } = await tokensRateOracle.latestRoundData(); + } = await tokenRateOracle.latestRoundData(); - assert.equalBN(roundId_, 170307199); + assert.equalBN(roundId_, 0); assert.equalBN(answer_, 0); - assert.equalBN(startedAt_, 1703072990); + assert.equalBN(startedAt_, 0); assert.equalBN(updatedAt_, 0); assert.equalBN(answeredInRound_, 0); + assert.equalBN(await tokenRateOracle.decimals(), 18); + }) - assert.equalBN(await tokensRateOracle.decimals(), 0); + .test("wrong owner", async (ctx) => { + const { tokenRateOracle } = ctx.contracts; + const { bridge, updater, stranger } = ctx.accounts; + tokenRateOracle.connect(bridge).updateRate(10, 20); + tokenRateOracle.connect(updater).updateRate(10, 23); + await assert.revertsWith(tokenRateOracle.connect(stranger).updateRate(10, 40), "NotAnOwner(\""+stranger.address+"\")"); + }) + + .test("incorrect time", async (ctx) => { + const { tokenRateOracle } = ctx.contracts; + const { bridge } = ctx.accounts; + + tokenRateOracle.connect(bridge).updateRate(10, 1000); + await assert.revertsWith(tokenRateOracle.connect(bridge).updateRate(12, 20), "IncorrectRateTimestamp()"); }) .test("state after update token rate", async (ctx) => { - const { tokensRateOracle } = ctx.contracts; + const { tokenRateOracle } = ctx.contracts; + const { updater } = ctx.accounts; - await tokensRateOracle.updateRate(2, ethers.constants.MaxInt256 ); + const currentTime = Date.now(); + const tokenRate = 123; - assert.equalBN(await tokensRateOracle.latestAnswer(), 2); + await tokenRateOracle.connect(updater).updateRate(tokenRate, currentTime ); + + assert.equalBN(await tokenRateOracle.latestAnswer(), tokenRate); const { roundId_, @@ -63,33 +63,29 @@ unit("TokenRateOracle", ctxFactory) startedAt_, updatedAt_, answeredInRound_ - } = await tokensRateOracle.latestRoundData(); - - assert.equalBN(roundId_, 170307199); - assert.equalBN(answer_, 2); - assert.equalBN(startedAt_, 1703072990); - assert.equalBN(updatedAt_, ethers.constants.MaxInt256); - assert.equalBN(answeredInRound_, 666); - - assert.equalBN(await tokensRateOracle.decimals(), 10); + } = await tokenRateOracle.latestRoundData(); + + assert.equalBN(roundId_, currentTime); + assert.equalBN(answer_, tokenRate); + assert.equalBN(startedAt_, currentTime); + assert.equalBN(updatedAt_, currentTime); + assert.equalBN(answeredInRound_, currentTime); + assert.equalBN(await tokenRateOracle.decimals(), 18); }) .run(); async function ctxFactory() { - const [deployer] = await hre.ethers.getSigners(); + const [deployer, bridge, updater, stranger] = await hre.ethers.getSigners(); - const tokensRateOracle = await new TokenRateOracle__factory(deployer).deploy( - 32, - 10, - 1000, - 100, - 50 + const tokenRateOracle = await new TokenRateOracle__factory(deployer).deploy( + bridge.address, + updater.address ); return { - accounts: { deployer }, - contracts: { tokensRateOracle } + accounts: { deployer, bridge, updater, stranger }, + contracts: { tokenRateOracle } }; } From 7c79d2d7fb76679a7627b88f3ed5bb7bfa2644ea Mon Sep 17 00:00:00 2001 From: Anton Kovalchuk Date: Tue, 9 Jan 2024 09:23:37 +0100 Subject: [PATCH 018/148] fix integration tests for rebasable token --- contracts/optimism/L2ERC20TokenBridge.sol | 4 +- contracts/stubs/ERC20WrapableStub.sol | 5 +- scripts/optimism/deploy-bridge.ts | 1 + .../optimism.integration.test.ts | 1 - .../bridging-rebase.integration.test.ts | 681 +++++++++--------- test/token/ERC20Rebasable.unit.test.ts | 6 +- utils/optimism/deployment.ts | 24 +- utils/optimism/testing.ts | 32 +- 8 files changed, 396 insertions(+), 358 deletions(-) diff --git a/contracts/optimism/L2ERC20TokenBridge.sol b/contracts/optimism/L2ERC20TokenBridge.sol index 3e52b948..873e0380 100644 --- a/contracts/optimism/L2ERC20TokenBridge.sol +++ b/contracts/optimism/L2ERC20TokenBridge.sol @@ -85,9 +85,11 @@ contract L2ERC20TokenBridge is uint256 shares = ERC20Rebasable(l2TokenRebasable).getSharesByTokens(amount_); ERC20Rebasable(l2TokenRebasable).burnShares(msg.sender, shares); _initiateWithdrawal(l1TokenRebasable, l2TokenRebasable, msg.sender, to_, shares, l1Gas_, data_); + emit WithdrawalInitiated(l1TokenRebasable, l2TokenRebasable, msg.sender, to_, amount_, data_); } else if (l2Token_ == l2TokenNonRebasable) { IERC20Bridged(l2TokenNonRebasable).bridgeBurn(msg.sender, amount_); _initiateWithdrawal(l1TokenNonRebasable, l2TokenNonRebasable, msg.sender, to_, amount_, l1Gas_, data_); + emit WithdrawalInitiated(l1TokenNonRebasable, l2TokenNonRebasable, msg.sender, to_, amount_, data_); } } @@ -148,7 +150,5 @@ contract L2ERC20TokenBridge is ); sendCrossDomainMessage(l1TokenBridge, l1Gas_, message); - - emit WithdrawalInitiated(l1Token_, l2Token_, from_, to_, amount_, data_); } } diff --git a/contracts/stubs/ERC20WrapableStub.sol b/contracts/stubs/ERC20WrapableStub.sol index de8f244b..3f77b88c 100644 --- a/contracts/stubs/ERC20WrapableStub.sol +++ b/contracts/stubs/ERC20WrapableStub.sol @@ -27,7 +27,7 @@ contract ERC20WrapableStub is IERC20Wrapable, ERC20 { function wrap(uint256 _stETHAmount) external returns (uint256) { require(_stETHAmount > 0, "wstETH: can't wrap zero stETH"); - uint256 wstETHAmount = (_stETHAmount * tokensRate) / (10 ** uint256(decimals())); + uint256 wstETHAmount = (_stETHAmount * (10 ** uint256(decimals()))) / tokensRate; _mint(msg.sender, wstETHAmount); stETH.transferFrom(msg.sender, address(this), _stETHAmount); @@ -38,7 +38,8 @@ contract ERC20WrapableStub is IERC20Wrapable, ERC20 { function unwrap(uint256 _wstETHAmount) external returns (uint256) { require(_wstETHAmount > 0, "wstETH: zero amount unwrap not allowed"); - uint256 stETHAmount = (_wstETHAmount * (10 ** uint256(decimals()))) / tokensRate; + uint256 stETHAmount = (_wstETHAmount * tokensRate) / (10 ** uint256(decimals())); + _burn(msg.sender, _wstETHAmount); stETH.transfer(msg.sender, stETHAmount); diff --git a/scripts/optimism/deploy-bridge.ts b/scripts/optimism/deploy-bridge.ts index 77d3ecb7..633edb29 100644 --- a/scripts/optimism/deploy-bridge.ts +++ b/scripts/optimism/deploy-bridge.ts @@ -25,6 +25,7 @@ async function main() { .deployment(networkName, { logger: console }) .erc20TokenBridgeDeployScript( deploymentConfig.token, + deploymentConfig.token, // FIX { deployer: ethDeployer, admins: { diff --git a/test/bridge-executor/optimism.integration.test.ts b/test/bridge-executor/optimism.integration.test.ts index 6cd18db8..ab28543e 100644 --- a/test/bridge-executor/optimism.integration.test.ts +++ b/test/bridge-executor/optimism.integration.test.ts @@ -243,7 +243,6 @@ async function ctxFactory() { .erc20TokenBridgeDeployScript( l1Token.address, l1TokenRebasable.address, - tokenRateOracleStub.address, { deployer: l1Deployer, admins: { diff --git a/test/optimism/bridging-rebase.integration.test.ts b/test/optimism/bridging-rebase.integration.test.ts index cda397c2..2bc623cb 100644 --- a/test/optimism/bridging-rebase.integration.test.ts +++ b/test/optimism/bridging-rebase.integration.test.ts @@ -6,6 +6,8 @@ import optimism from "../../utils/optimism"; import testing, { scenario } from "../../utils/testing"; import { ethers } from "hardhat"; import { BigNumber } from "ethers"; +import { JsonRpcProvider } from "@ethersproject/providers"; +import { ERC20WrapableStub } from "../../typechain"; scenario("Optimism :: Bridging integration test", ctxFactory) .after(async (ctx) => { @@ -71,6 +73,42 @@ scenario("Optimism :: Bridging integration test", ctxFactory) assert.isTrue(await l2ERC20TokenBridge.isWithdrawalsEnabled()); }) + .step("Set up Token Rate Oracle by pushing first rate", async (ctx) => { + + const { + l1Token, + l1TokenRebasable, + l2TokenRebasable, + l1ERC20TokenBridge, + l2CrossDomainMessenger, + l2ERC20TokenBridge, + l2Provider + } = ctx; + + const { accountA: tokenHolderA, l1CrossDomainMessengerAliased } = + ctx.accounts; + const dataToReceive = await packedTokenRateAndTimestamp(l2Provider, l1Token); + + const tx = await l2CrossDomainMessenger + .connect(l1CrossDomainMessengerAliased) + .relayMessage( + 1, + l1ERC20TokenBridge.address, + l2ERC20TokenBridge.address, + 0, + 300_000, + l2ERC20TokenBridge.interface.encodeFunctionData("finalizeDeposit", [ + l1TokenRebasable.address, + l2TokenRebasable.address, + tokenHolderA.address, + tokenHolderA.address, + 0, + dataToReceive, + ]), + { gasLimit: 5_000_000 } + ); + }) + .step("L1 -> L2 deposit zero tokens via depositERC20() method", async (ctx) => { const { l1Token, @@ -79,12 +117,10 @@ scenario("Optimism :: Bridging integration test", ctxFactory) l2TokenRebasable, l1CrossDomainMessenger, l2ERC20TokenBridge, - tokenRateOracle, l1Provider } = ctx; const { accountA: tokenHolderA } = ctx.accounts; - const stETHPerToken = await l1Token.stETHPerToken(); await l1TokenRebasable .connect(tokenHolderA.l1Signer) @@ -108,12 +144,7 @@ scenario("Optimism :: Bridging integration test", ctxFactory) "0x" ); - const blockNumber = await l1Provider.getBlockNumber(); - const blockTimestamp = (await l1Provider.getBlock(blockNumber)).timestamp; - const blockTimestampStr = ethers.utils.hexZeroPad(ethers.utils.hexlify(blockTimestamp), 32) - const stETHPerTokenStr = ethers.utils.hexZeroPad(stETHPerToken.toHexString(), 32) - const dataToSend = ethers.utils.hexConcat([stETHPerTokenStr, blockTimestampStr]); - + const dataToSend = await packedTokenRateAndTimestamp(l1Provider, l1Token); await assert.emits(l1ERC20TokenBridge, tx, "ERC20DepositInitiated", [ l1TokenRebasable.address, @@ -165,21 +196,14 @@ scenario("Optimism :: Bridging integration test", ctxFactory) l1ERC20TokenBridge, l2CrossDomainMessenger, l2ERC20TokenBridge, - tokenRateOracle, l2Provider } = ctx; - const stETHPerToken = await l1Token.stETHPerToken(); - const blockNumber = await l2Provider.getBlockNumber(); - const blockTimestamp = (await l2Provider.getBlock(blockNumber)).timestamp; - const blockTimestampStr = ethers.utils.hexZeroPad(ethers.utils.hexlify(blockTimestamp), 32) - const stETHPerTokenStr = ethers.utils.hexZeroPad(stETHPerToken.toHexString(), 32) - const dataToReceive = ethers.utils.hexConcat([stETHPerTokenStr, blockTimestampStr]); + const dataToReceive = await packedTokenRateAndTimestamp(l2Provider, l1Token); const { accountA: tokenHolderA, l1CrossDomainMessengerAliased } = ctx.accounts; - const tokenHolderABalanceBefore = await l2TokenRebasable.balanceOf( tokenHolderA.address ); @@ -204,11 +228,6 @@ scenario("Optimism :: Bridging integration test", ctxFactory) ]), { gasLimit: 5_000_000 } ); - - - const [,tokensRate,,updatedAt,] = await tokenRateOracle.latestRoundData(); - assert.equalBN(stETHPerToken, tokensRate); - assert.equalBN(blockTimestamp, updatedAt); await assert.emits(l2ERC20TokenBridge, tx, "DepositFinalized", [ l1TokenRebasable.address, @@ -237,19 +256,14 @@ scenario("Optimism :: Bridging integration test", ctxFactory) l2TokenRebasable, l1CrossDomainMessenger, l2ERC20TokenBridge, - tokenRateOracle, l1Provider } = ctx; const { accountA: tokenHolderA } = ctx.accounts; - const { depositAmount: depositAmountInRebasableTokens } = ctx.common; - const depositAmount = wei.toBigNumber(depositAmountInRebasableTokens).mul(2); - const stETHPerToken = await l1Token.stETHPerToken(); + const { depositAmountNonRebasable, depositAmountRebasable } = ctx.common; - await tokenRateOracle.setLatestRoundDataAnswer(BigNumber.from("1000000000000000000")); - await l1TokenRebasable .connect(tokenHolderA.l1Signer) - .approve(l1ERC20TokenBridge.address, depositAmountInRebasableTokens); + .approve(l1ERC20TokenBridge.address, depositAmountRebasable); const tokenHolderABalanceBefore = await l1TokenRebasable.balanceOf( tokenHolderA.address @@ -264,24 +278,19 @@ scenario("Optimism :: Bridging integration test", ctxFactory) .depositERC20( l1TokenRebasable.address, l2TokenRebasable.address, - depositAmountInRebasableTokens, + depositAmountRebasable, 200_000, "0x" ); - const blockNumber = await l1Provider.getBlockNumber(); - const blockTimestamp = (await l1Provider.getBlock(blockNumber)).timestamp; - const blockTimestampStr = ethers.utils.hexZeroPad(ethers.utils.hexlify(blockTimestamp), 32) - const stETHPerTokenStr = ethers.utils.hexZeroPad(stETHPerToken.toHexString(), 32) - const dataToSend = ethers.utils.hexConcat([stETHPerTokenStr, blockTimestampStr]); - + const dataToSend = await packedTokenRateAndTimestamp(l1Provider, l1Token); await assert.emits(l1ERC20TokenBridge, tx, "ERC20DepositInitiated", [ l1TokenRebasable.address, l2TokenRebasable.address, tokenHolderA.address, tokenHolderA.address, - depositAmount, + depositAmountNonRebasable, dataToSend, ]); @@ -292,7 +301,7 @@ scenario("Optimism :: Bridging integration test", ctxFactory) l2TokenRebasable.address, tokenHolderA.address, tokenHolderA.address, - depositAmount, + depositAmountNonRebasable, dataToSend, ] ); @@ -309,12 +318,12 @@ scenario("Optimism :: Bridging integration test", ctxFactory) assert.equalBN( await l1Token.balanceOf(l1ERC20TokenBridge.address), - l1ERC20TokenBridgeBalanceBefore.add(depositAmount) + l1ERC20TokenBridgeBalanceBefore.add(depositAmountNonRebasable) ); assert.equalBN( await l1TokenRebasable.balanceOf(tokenHolderA.address), // stETH - tokenHolderABalanceBefore.sub(depositAmountInRebasableTokens) + tokenHolderABalanceBefore.sub(depositAmountRebasable) ); }) @@ -326,31 +335,20 @@ scenario("Optimism :: Bridging integration test", ctxFactory) l1ERC20TokenBridge, l2CrossDomainMessenger, l2ERC20TokenBridge, - tokenRateOracle, l2Provider } = ctx; - const { depositAmount: depositAmountInRebasableTokens } = ctx.common; - const depositAmount = wei.toBigNumber(depositAmountInRebasableTokens).div(2); - const stETHPerToken = await l1Token.stETHPerToken(); - - - const blockNumber = await l2Provider.getBlockNumber(); - const blockTimestamp = (await l2Provider.getBlock(blockNumber)).timestamp; - const blockTimestampStr = ethers.utils.hexZeroPad(ethers.utils.hexlify(blockTimestamp), 32) - const stETHPerTokenStr = ethers.utils.hexZeroPad(stETHPerToken.toHexString(), 32) - const dataToReceive = ethers.utils.hexConcat([stETHPerTokenStr, blockTimestampStr]); - + const { depositAmountNonRebasable, depositAmountRebasable } = ctx.common; const { accountA: tokenHolderA, l1CrossDomainMessengerAliased } = ctx.accounts; - const tokenHolderABalanceBefore = await l2TokenRebasable.balanceOf( tokenHolderA.address ); const l2TokenRebasableTotalSupplyBefore = await l2TokenRebasable.totalSupply(); - + const dataToReceive = await packedTokenRateAndTimestamp(l2Provider, l1Token); + const tx = await l2CrossDomainMessenger .connect(l1CrossDomainMessengerAliased) .relayMessage( @@ -364,61 +362,50 @@ scenario("Optimism :: Bridging integration test", ctxFactory) l2TokenRebasable.address, tokenHolderA.address, tokenHolderA.address, - depositAmount, + depositAmountNonRebasable, dataToReceive, ]), { gasLimit: 5_000_000 } ); - - - const [,tokensRate,,updatedAt,] = await tokenRateOracle.latestRoundData(); - assert.equalBN(stETHPerToken, tokensRate); - assert.equalBN(blockTimestamp, updatedAt); await assert.emits(l2ERC20TokenBridge, tx, "DepositFinalized", [ l1TokenRebasable.address, l2TokenRebasable.address, tokenHolderA.address, tokenHolderA.address, - depositAmount, + depositAmountNonRebasable, "0x", ]); assert.equalBN( await l2TokenRebasable.balanceOf(tokenHolderA.address), - tokenHolderABalanceBefore.add(depositAmountInRebasableTokens) + tokenHolderABalanceBefore.add(depositAmountRebasable) ); assert.equalBN( await l2TokenRebasable.totalSupply(), - l2TokenRebasableTotalSupplyBefore.add(depositAmountInRebasableTokens) + l2TokenRebasableTotalSupplyBefore.add(depositAmountRebasable) ); }) .step("L2 -> L1 withdrawal via withdraw()", async (ctx) => { const { accountA: tokenHolderA } = ctx.accounts; - const { withdrawalAmount: withdrawalAmountInRebasableTokens } = ctx.common; + const { withdrawalAmountRebasable } = ctx.common; const { l1TokenRebasable, l2TokenRebasable, l2ERC20TokenBridge } = ctx; - const withdrawalAmount = wei.toBigNumber(withdrawalAmountInRebasableTokens).div(2); - const tokenHolderABalanceBefore = await l2TokenRebasable.balanceOf( tokenHolderA.address ); const l2TotalSupplyBefore = await l2TokenRebasable.totalSupply(); - await l2TokenRebasable - .connect(tokenHolderA.l2Signer) - .approve(l2ERC20TokenBridge.address, withdrawalAmountInRebasableTokens); - const tx = await l2ERC20TokenBridge .connect(tokenHolderA.l2Signer) .withdraw( l2TokenRebasable.address, - withdrawalAmountInRebasableTokens, + withdrawalAmountRebasable, 0, "0x" ); @@ -428,17 +415,17 @@ scenario("Optimism :: Bridging integration test", ctxFactory) l2TokenRebasable.address, tokenHolderA.address, tokenHolderA.address, - withdrawalAmount, + withdrawalAmountRebasable, "0x", ]); assert.equalBN( await l2TokenRebasable.balanceOf(tokenHolderA.address), - tokenHolderABalanceBefore.sub(withdrawalAmountInRebasableTokens) + tokenHolderABalanceBefore.sub(withdrawalAmountRebasable) ); assert.equalBN( await l2TokenRebasable.totalSupply(), - l2TotalSupplyBefore.sub(withdrawalAmountInRebasableTokens) + l2TotalSupplyBefore.sub(withdrawalAmountRebasable) ); }) @@ -453,8 +440,7 @@ scenario("Optimism :: Bridging integration test", ctxFactory) l2ERC20TokenBridge, } = ctx; const { accountA: tokenHolderA, l1Stranger } = ctx.accounts; - const { withdrawalAmount: withdrawalAmountInRebasableTokens } = ctx.common; - const withdrawalAmount = wei.toBigNumber(withdrawalAmountInRebasableTokens).mul(2); + const { withdrawalAmountNonRebasable, withdrawalAmountRebasable } = ctx.common; const tokenHolderABalanceBefore = await l1TokenRebasable.balanceOf( tokenHolderA.address @@ -479,7 +465,7 @@ scenario("Optimism :: Bridging integration test", ctxFactory) l2TokenRebasable.address, tokenHolderA.address, tokenHolderA.address, - withdrawalAmount, + withdrawalAmountNonRebasable, "0x", ] ), @@ -491,268 +477,292 @@ scenario("Optimism :: Bridging integration test", ctxFactory) l2TokenRebasable.address, tokenHolderA.address, tokenHolderA.address, - withdrawalAmount, + withdrawalAmountNonRebasable, "0x", ]); assert.equalBN( await l1Token.balanceOf(l1ERC20TokenBridge.address), - l1ERC20TokenBridgeBalanceBefore.sub(withdrawalAmount) + l1ERC20TokenBridgeBalanceBefore.sub(withdrawalAmountNonRebasable) ); assert.equalBN( await l1TokenRebasable.balanceOf(tokenHolderA.address), - tokenHolderABalanceBefore.add(withdrawalAmountInRebasableTokens) + tokenHolderABalanceBefore.add(withdrawalAmountRebasable) + ); + }) + + + .step("L1 -> L2 deposit via depositERC20To()", async (ctx) => { + + const { + l1Token, + l1TokenRebasable, + l1ERC20TokenBridge, + l2TokenRebasable, + l1CrossDomainMessenger, + l2ERC20TokenBridge, + l1Provider + } = ctx; + const { accountA: tokenHolderA, accountB: tokenHolderB } = ctx.accounts; + assert.notEqual(tokenHolderA.address, tokenHolderB.address); + + const { exchangeRate } = ctx.common; + const depositAmountNonRebasable = wei`0.03 ether`; + const depositAmountRebasable = wei.toBigNumber(depositAmountNonRebasable).mul(exchangeRate); + + await l1TokenRebasable + .connect(tokenHolderA.l1Signer) + .approve(l1ERC20TokenBridge.address, depositAmountRebasable); + + const tokenHolderABalanceBefore = await l1TokenRebasable.balanceOf( + tokenHolderA.address + ); + const l1ERC20TokenBridgeBalanceBefore = await l1Token.balanceOf( + l1ERC20TokenBridge.address + ); + + const tx = await l1ERC20TokenBridge + .connect(tokenHolderA.l1Signer) + .depositERC20To( + l1TokenRebasable.address, + l2TokenRebasable.address, + tokenHolderB.address, + depositAmountRebasable, + 200_000, + "0x" + ); + + const dataToSend = await packedTokenRateAndTimestamp(l1Provider, l1Token); + + await assert.emits(l1ERC20TokenBridge, tx, "ERC20DepositInitiated", [ + l1TokenRebasable.address, + l2TokenRebasable.address, + tokenHolderA.address, + tokenHolderB.address, + depositAmountNonRebasable, + dataToSend, + ]); + + const l2DepositCalldata = l2ERC20TokenBridge.interface.encodeFunctionData( + "finalizeDeposit", + [ + l1TokenRebasable.address, + l2TokenRebasable.address, + tokenHolderA.address, + tokenHolderB.address, + depositAmountNonRebasable, + dataToSend, + ] + ); + + const messageNonce = await l1CrossDomainMessenger.messageNonce(); + + await assert.emits(l1CrossDomainMessenger, tx, "SentMessage", [ + l2ERC20TokenBridge.address, + l1ERC20TokenBridge.address, + l2DepositCalldata, + messageNonce, + 200_000, + ]); + + assert.equalBN( + await l1Token.balanceOf(l1ERC20TokenBridge.address), + l1ERC20TokenBridgeBalanceBefore.add(depositAmountNonRebasable) + ); + + assert.equalBN( + await l1TokenRebasable.balanceOf(tokenHolderA.address), // stETH + tokenHolderABalanceBefore.sub(depositAmountRebasable) + ); + }) + + .step("Finalize deposit on L2", async (ctx) => { + const { + l1Token, + l1TokenRebasable, + l1ERC20TokenBridge, + l2TokenRebasable, + l2CrossDomainMessenger, + l2ERC20TokenBridge, + l2Provider + } = ctx; + + const { + accountA: tokenHolderA, + accountB: tokenHolderB, + l1CrossDomainMessengerAliased, + } = ctx.accounts; + + const { exchangeRate } = ctx.common; + + const depositAmountNonRebasable = wei`0.03 ether`; + const depositAmountRebasable = wei.toBigNumber(depositAmountNonRebasable).mul(exchangeRate); + + const dataToReceive = await packedTokenRateAndTimestamp(l2Provider, l1Token); + + const l2TokenRebasableTotalSupplyBefore = await l2TokenRebasable.totalSupply(); + + const tokenHolderBBalanceBefore = await l2TokenRebasable.balanceOf( + tokenHolderB.address + ); + + const tx = await l2CrossDomainMessenger + .connect(l1CrossDomainMessengerAliased) + .relayMessage( + 1, + l1ERC20TokenBridge.address, + l2ERC20TokenBridge.address, + 0, + 300_000, + l2ERC20TokenBridge.interface.encodeFunctionData("finalizeDeposit", [ + l1TokenRebasable.address, + l2TokenRebasable.address, + tokenHolderA.address, + tokenHolderB.address, + depositAmountNonRebasable, + dataToReceive, + ]), + { gasLimit: 5_000_000 } + ); + + await assert.emits(l2ERC20TokenBridge, tx, "DepositFinalized", [ + l1TokenRebasable.address, + l2TokenRebasable.address, + tokenHolderA.address, + tokenHolderB.address, + depositAmountNonRebasable, + "0x", + ]); + + assert.equalBN( + await l2TokenRebasable.balanceOf(tokenHolderB.address), + tokenHolderBBalanceBefore.add(depositAmountRebasable) + ); + + assert.equalBN( + await l2TokenRebasable.totalSupply(), + l2TokenRebasableTotalSupplyBefore.add(depositAmountRebasable) + ); + }) + + .step("L2 -> L1 withdrawal via withdrawTo()", async (ctx) => { + const { l1TokenRebasable, l2TokenRebasable, l2ERC20TokenBridge } = ctx; + const { accountA: tokenHolderA, accountB: tokenHolderB } = ctx.accounts; + + const { exchangeRate } = ctx.common; + const withdrawalAmountNonRebasable = wei`0.03 ether`; + const withdrawalAmountRebasable = wei.toBigNumber(withdrawalAmountNonRebasable).mul(exchangeRate); + + const tokenHolderBBalanceBefore = await l2TokenRebasable.balanceOf( + tokenHolderB.address + ); + const l2TotalSupplyBefore = await l2TokenRebasable.totalSupply(); + + const tx = await l2ERC20TokenBridge + .connect(tokenHolderB.l2Signer) + .withdrawTo( + l2TokenRebasable.address, + tokenHolderA.address, + withdrawalAmountRebasable, + 0, + "0x" + ); + + await assert.emits(l2ERC20TokenBridge, tx, "WithdrawalInitiated", [ + l1TokenRebasable.address, + l2TokenRebasable.address, + tokenHolderB.address, + tokenHolderA.address, + withdrawalAmountRebasable, + "0x", + ]); + + assert.equalBN( + await l2TokenRebasable.balanceOf(tokenHolderB.address), + tokenHolderBBalanceBefore.sub(withdrawalAmountRebasable) + ); + + assert.equalBN( + await l2TokenRebasable.totalSupply(), + l2TotalSupplyBefore.sub(withdrawalAmountRebasable) ); }) + .step("Finalize withdrawal on L1", async (ctx) => { + const { + l1Token, + l1TokenRebasable, + l1CrossDomainMessenger, + l1ERC20TokenBridge, + l2CrossDomainMessenger, + l2TokenRebasable, + l2ERC20TokenBridge, + } = ctx; + const { + accountA: tokenHolderA, + accountB: tokenHolderB, + l1Stranger, + } = ctx.accounts; + + const { exchangeRate } = ctx.common; + const withdrawalAmountNonRebasable = wei`0.03 ether`; + const withdrawalAmountRebasable = wei.toBigNumber(withdrawalAmountNonRebasable).mul(exchangeRate); + + const tokenHolderABalanceBefore = await l1TokenRebasable.balanceOf( + tokenHolderA.address + ); + const l1ERC20TokenBridgeBalanceBefore = await l1Token.balanceOf( + l1ERC20TokenBridge.address + ); + + await l1CrossDomainMessenger + .connect(l1Stranger) + .setXDomainMessageSender(l2ERC20TokenBridge.address); + + const tx = await l1CrossDomainMessenger + .connect(l1Stranger) + .relayMessage( + l1ERC20TokenBridge.address, + l2CrossDomainMessenger.address, + l1ERC20TokenBridge.interface.encodeFunctionData( + "finalizeERC20Withdrawal", + [ + l1TokenRebasable.address, + l2TokenRebasable.address, + tokenHolderB.address, + tokenHolderA.address, + withdrawalAmountNonRebasable, + "0x", + ] + ), + 0 + ); + + await assert.emits(l1ERC20TokenBridge, tx, "ERC20WithdrawalFinalized", [ + l1TokenRebasable.address, + l2TokenRebasable.address, + tokenHolderB.address, + tokenHolderA.address, + withdrawalAmountNonRebasable, + "0x", + ]); -// .step("L1 -> L2 deposit via depositERC20To()", async (ctx) => { -// const { -// l1Token, -// l2Token, -// l1ERC20TokenBridge, -// l2ERC20TokenBridge, -// l1CrossDomainMessenger, -// } = ctx; -// const { accountA: tokenHolderA, accountB: tokenHolderB } = ctx.accounts; -// const { depositAmount } = ctx.common; - -// assert.notEqual(tokenHolderA.address, tokenHolderB.address); - -// await l1Token -// .connect(tokenHolderA.l1Signer) -// .approve(l1ERC20TokenBridge.address, depositAmount); - -// const tokenHolderABalanceBefore = await l1Token.balanceOf( -// tokenHolderA.address -// ); -// const l1ERC20TokenBridgeBalanceBefore = await l1Token.balanceOf( -// l1ERC20TokenBridge.address -// ); - -// const tx = await l1ERC20TokenBridge -// .connect(tokenHolderA.l1Signer) -// .depositERC20To( -// l1Token.address, -// l2Token.address, -// tokenHolderB.address, -// depositAmount, -// 200_000, -// "0x" -// ); - -// await assert.emits(l1ERC20TokenBridge, tx, "ERC20DepositInitiated", [ -// l1Token.address, -// l2Token.address, -// tokenHolderA.address, -// tokenHolderB.address, -// depositAmount, -// "0x", -// ]); - -// const l2DepositCalldata = l2ERC20TokenBridge.interface.encodeFunctionData( -// "finalizeDeposit", -// [ -// l1Token.address, -// l2Token.address, -// tokenHolderA.address, -// tokenHolderB.address, -// depositAmount, -// "0x", -// ] -// ); - -// const messageNonce = await l1CrossDomainMessenger.messageNonce(); - -// await assert.emits(l1CrossDomainMessenger, tx, "SentMessage", [ -// l2ERC20TokenBridge.address, -// l1ERC20TokenBridge.address, -// l2DepositCalldata, -// messageNonce, -// 200_000, -// ]); - -// assert.equalBN( -// await l1Token.balanceOf(l1ERC20TokenBridge.address), -// l1ERC20TokenBridgeBalanceBefore.add(depositAmount) -// ); - -// assert.equalBN( -// await l1Token.balanceOf(tokenHolderA.address), -// tokenHolderABalanceBefore.sub(depositAmount) -// ); -// }) - -// .step("Finalize deposit on L2", async (ctx) => { -// const { -// l1Token, -// l1ERC20TokenBridge, -// l2Token, -// l2CrossDomainMessenger, -// l2ERC20TokenBridge, -// } = ctx; -// const { -// accountA: tokenHolderA, -// accountB: tokenHolderB, -// l1CrossDomainMessengerAliased, -// } = ctx.accounts; -// const { depositAmount } = ctx.common; - -// const l2TokenTotalSupplyBefore = await l2Token.totalSupply(); -// const tokenHolderBBalanceBefore = await l2Token.balanceOf( -// tokenHolderB.address -// ); - -// const tx = await l2CrossDomainMessenger -// .connect(l1CrossDomainMessengerAliased) -// .relayMessage( -// 1, -// l1ERC20TokenBridge.address, -// l2ERC20TokenBridge.address, -// 0, -// 300_000, -// l2ERC20TokenBridge.interface.encodeFunctionData("finalizeDeposit", [ -// l1Token.address, -// l2Token.address, -// tokenHolderA.address, -// tokenHolderB.address, -// depositAmount, -// "0x", -// ]), -// { gasLimit: 5_000_000 } -// ); - -// await assert.emits(l2ERC20TokenBridge, tx, "DepositFinalized", [ -// l1Token.address, -// l2Token.address, -// tokenHolderA.address, -// tokenHolderB.address, -// depositAmount, -// "0x", -// ]); - -// assert.equalBN( -// await l2Token.totalSupply(), -// l2TokenTotalSupplyBefore.add(depositAmount) -// ); -// assert.equalBN( -// await l2Token.balanceOf(tokenHolderB.address), -// tokenHolderBBalanceBefore.add(depositAmount) -// ); -// }) - -// .step("L2 -> L1 withdrawal via withdrawTo()", async (ctx) => { -// const { l1Token, l2Token, l2ERC20TokenBridge } = ctx; -// const { accountA: tokenHolderA, accountB: tokenHolderB } = ctx.accounts; -// const { withdrawalAmount } = ctx.common; - -// const tokenHolderBBalanceBefore = await l2Token.balanceOf( -// tokenHolderB.address -// ); -// const l2TotalSupplyBefore = await l2Token.totalSupply(); - -// const tx = await l2ERC20TokenBridge -// .connect(tokenHolderB.l2Signer) -// .withdrawTo( -// l2Token.address, -// tokenHolderA.address, -// withdrawalAmount, -// 0, -// "0x" -// ); - -// await assert.emits(l2ERC20TokenBridge, tx, "WithdrawalInitiated", [ -// l1Token.address, -// l2Token.address, -// tokenHolderB.address, -// tokenHolderA.address, -// withdrawalAmount, -// "0x", -// ]); - -// assert.equalBN( -// await l2Token.balanceOf(tokenHolderB.address), -// tokenHolderBBalanceBefore.sub(withdrawalAmount) -// ); - -// assert.equalBN( -// await l2Token.totalSupply(), -// l2TotalSupplyBefore.sub(withdrawalAmount) -// ); -// }) - -// .step("Finalize withdrawal on L1", async (ctx) => { -// const { -// l1Token, -// l1CrossDomainMessenger, -// l1ERC20TokenBridge, -// l2CrossDomainMessenger, -// l2Token, -// l2ERC20TokenBridge, -// } = ctx; -// const { -// accountA: tokenHolderA, -// accountB: tokenHolderB, -// l1Stranger, -// } = ctx.accounts; -// const { withdrawalAmount } = ctx.common; - -// const tokenHolderABalanceBefore = await l1Token.balanceOf( -// tokenHolderA.address -// ); -// const l1ERC20TokenBridgeBalanceBefore = await l1Token.balanceOf( -// l1ERC20TokenBridge.address -// ); - -// await l1CrossDomainMessenger -// .connect(l1Stranger) -// .setXDomainMessageSender(l2ERC20TokenBridge.address); - -// const tx = await l1CrossDomainMessenger -// .connect(l1Stranger) -// .relayMessage( -// l1ERC20TokenBridge.address, -// l2CrossDomainMessenger.address, -// l1ERC20TokenBridge.interface.encodeFunctionData( -// "finalizeERC20Withdrawal", -// [ -// l1Token.address, -// l2Token.address, -// tokenHolderB.address, -// tokenHolderA.address, -// withdrawalAmount, -// "0x", -// ] -// ), -// 0 -// ); - -// await assert.emits(l1ERC20TokenBridge, tx, "ERC20WithdrawalFinalized", [ -// l1Token.address, -// l2Token.address, -// tokenHolderB.address, -// tokenHolderA.address, -// withdrawalAmount, -// "0x", -// ]); - -// assert.equalBN( -// await l1Token.balanceOf(l1ERC20TokenBridge.address), -// l1ERC20TokenBridgeBalanceBefore.sub(withdrawalAmount) -// ); - -// assert.equalBN( -// await l1Token.balanceOf(tokenHolderA.address), -// tokenHolderABalanceBefore.add(withdrawalAmount) -// ); -// }) + assert.equalBN( + await l1Token.balanceOf(l1ERC20TokenBridge.address), + l1ERC20TokenBridgeBalanceBefore.sub(withdrawalAmountNonRebasable) + ); + + assert.equalBN( + await l1TokenRebasable.balanceOf(tokenHolderA.address), + tokenHolderABalanceBefore.add(withdrawalAmountRebasable) + ); + }) .run(); async function ctxFactory() { const networkName = env.network("TESTING_OPT_NETWORK", "mainnet"); - console.log("networkName=",networkName); const { l1Provider, @@ -770,8 +780,12 @@ async function ctxFactory() { const accountA = testing.accounts.accountA(l1Provider, l2Provider); const accountB = testing.accounts.accountB(l1Provider, l2Provider); - const depositAmount = wei`0.15 ether`; - const withdrawalAmount = wei`0.05 ether`; + const exchangeRate = 2; + const depositAmountNonRebasable = wei`0.15 ether`; + const depositAmountRebasable = wei.toBigNumber(depositAmountNonRebasable).mul(exchangeRate); + + const withdrawalAmountNonRebasable = wei`0.05 ether`; + const withdrawalAmountRebasable = wei.toBigNumber(withdrawalAmountNonRebasable).mul(exchangeRate); await testing.setBalance( await contracts.l1TokensHolder.getAddress(), @@ -793,22 +807,21 @@ async function ctxFactory() { await contracts.l1TokenRebasable .connect(contracts.l1TokensHolder) - .transfer(accountA.l1Signer.address, wei.toBigNumber(depositAmount).mul(2)); + .transfer(accountA.l1Signer.address, depositAmountRebasable); const l1CrossDomainMessengerAliased = await testing.impersonate( testing.accounts.applyL1ToL2Alias(contracts.l1CrossDomainMessenger.address), l2Provider ); - console.log("l1CrossDomainMessengerAliased=",l1CrossDomainMessengerAliased); - console.log("contracts.l1CrossDomainMessenger.address=",contracts.l1CrossDomainMessenger.address); - await testing.setBalance( await l1CrossDomainMessengerAliased.getAddress(), wei.toBigNumber(wei`1 ether`), l2Provider ); + await contracts.l1ERC20TokenBridge.connect(l1ERC20TokenBridgeAdmin).pushTokenRate(1000000); + return { l1Provider, l2Provider, @@ -822,8 +835,11 @@ async function ctxFactory() { l1CrossDomainMessengerAliased, }, common: { - depositAmount, - withdrawalAmount, + depositAmountNonRebasable, + depositAmountRebasable, + withdrawalAmountNonRebasable, + withdrawalAmountRebasable, + exchangeRate, }, snapshot: { l1: l1Snapshot, @@ -831,3 +847,12 @@ async function ctxFactory() { }, }; } + +async function packedTokenRateAndTimestamp(l1Provider: JsonRpcProvider, l1Token: ERC20WrapableStub) { + const stETHPerToken = await l1Token.stETHPerToken(); + const blockNumber = await l1Provider.getBlockNumber(); + const blockTimestamp = (await l1Provider.getBlock(blockNumber)).timestamp; + const stETHPerTokenStr = ethers.utils.hexZeroPad(stETHPerToken.toHexString(), 12); + const blockTimestampStr = ethers.utils.hexZeroPad(ethers.utils.hexlify(blockTimestamp), 5); + return ethers.utils.hexConcat([stETHPerTokenStr, blockTimestampStr]); +} \ No newline at end of file diff --git a/test/token/ERC20Rebasable.unit.test.ts b/test/token/ERC20Rebasable.unit.test.ts index ac87c9d5..f429ebbd 100644 --- a/test/token/ERC20Rebasable.unit.test.ts +++ b/test/token/ERC20Rebasable.unit.test.ts @@ -41,8 +41,8 @@ unit("ERC20Rebasable", ctxFactory) owner.address ); await assert.revertsWith( - rebasableTokenImpl.initialize("New Name", ""), - "ErrorNameAlreadySet()" + rebasableTokenImpl.initialize("New Name", ""), + "ErrorNameAlreadySet()" ); }) @@ -61,7 +61,7 @@ unit("ERC20Rebasable", ctxFactory) owner.address ); await assert.revertsWith( - rebasableTokenImpl.initialize("", "New Symbol"), + rebasableTokenImpl.initialize("", "New Symbol"), "ErrorSymbolAlreadySet()" ); }) diff --git a/utils/optimism/deployment.ts b/utils/optimism/deployment.ts index 33b3fdb0..a73a1ed4 100644 --- a/utils/optimism/deployment.ts +++ b/utils/optimism/deployment.ts @@ -7,6 +7,8 @@ import { L1ERC20TokenBridge__factory, L2ERC20TokenBridge__factory, OssifiableProxy__factory, + TokenRateOracle, + TokenRateOracle__factory, } from "../../typechain"; import addresses from "./addresses"; @@ -38,7 +40,6 @@ export default function deployment( async erc20TokenBridgeDeployScript( l1Token: string, l1TokenRebasable: string, - tokenRateOracleStub: string, l1Params: OptL1DeployScriptParams, l2Params: OptL2DeployScriptParams, ) { @@ -49,14 +50,15 @@ export default function deployment( ] = await network.predictAddresses(l1Params.deployer, 2); const [ + expectedL2TokenRateOracleImplAddress, expectedL2TokenImplAddress, expectedL2TokenProxyAddress, expectedL2TokenRebasableImplAddress, expectedL2TokenRebasableProxyAddress, expectedL2TokenBridgeImplAddress, expectedL2TokenBridgeProxyAddress, - ] = await network.predictAddresses(l2Params.deployer, 6); - + ] = await network.predictAddresses(l2Params.deployer, 7); + const l1DeployScript = new DeployScript( l1Params.deployer, options?.logger @@ -111,6 +113,17 @@ export default function deployment( l2Params.deployer, options?.logger ) + .addStep({ + factory: TokenRateOracle__factory, + args: [ + expectedL2TokenBridgeProxyAddress, + expectedL2TokenBridgeProxyAddress, + options?.overrides, + ], + afterDeploy: (c) => + assert.equal(c.address, expectedL2TokenRateOracleImplAddress), + }) + .addStep({ factory: ERC20Bridged__factory, args: [ @@ -140,11 +153,12 @@ export default function deployment( .addStep({ factory: ERC20Rebasable__factory, args: [ - expectedL2TokenProxyAddress, - tokenRateOracleStub, l2TokenRebasableName, l2TokenRebasableSymbol, decimals, + expectedL2TokenProxyAddress, + expectedL2TokenRateOracleImplAddress, + expectedL2TokenBridgeProxyAddress, options?.overrides, ], afterDeploy: (c) => diff --git a/utils/optimism/testing.ts b/utils/optimism/testing.ts index 4042a351..1da87bb5 100644 --- a/utils/optimism/testing.ts +++ b/utils/optimism/testing.ts @@ -10,7 +10,7 @@ import { ERC20Bridged__factory, ERC20BridgedStub__factory, ERC20WrapableStub__factory, - TokenRateOracleStub__factory, + TokenRateOracle__factory, L1ERC20TokenBridge__factory, L2ERC20TokenBridge__factory, CrossDomainMessengerStub__factory, @@ -164,13 +164,10 @@ async function loadDeployedBridges( testingUtils.env.OPT_L1_TOKEN(), l1SignerOrProvider ), - tokenRateOracle: TokenRateOracleStub__factory.connect( - testingUtils.env.OPT_L1_TOKEN(), - l1SignerOrProvider - ), ...connectBridgeContracts( { + tokenRateOracle: testingUtils.env.OPT_L2_TOKEN(), // fix l2Token: testingUtils.env.OPT_L2_TOKEN(), l2TokenRebasable: testingUtils.env.OPT_L2_TOKEN(), // fix l1ERC20TokenBridge: testingUtils.env.OPT_L1_ERC20_TOKEN_BRIDGE(), @@ -201,17 +198,11 @@ async function deployTestBridge( "TT" ); - const tokenRateOracleStub = await new TokenRateOracleStub__factory(optDeployer).deploy(); - await tokenRateOracleStub.setLatestRoundDataAnswer(BigNumber.from("1000000000000000000")); - await tokenRateOracleStub.setDecimals(18); - await tokenRateOracleStub.setUpdatedAt(100); - const [ethDeployScript, optDeployScript] = await deployment( networkName ).erc20TokenBridgeDeployScript( l1Token.address, l1TokenRebasable.address, - tokenRateOracleStub.address, { deployer: ethDeployer, admins: { proxy: ethDeployer.address, bridge: ethDeployer.address }, @@ -231,7 +222,7 @@ async function deployTestBridge( ethDeployer ); - const l2ERC20TokenBridgeProxyDeployStepIndex = 5; + const l2ERC20TokenBridgeProxyDeployStepIndex = 6; const l2BridgingManagement = new BridgingManagement( optDeployScript.getContractAddress(l2ERC20TokenBridgeProxyDeployStepIndex), optDeployer @@ -252,13 +243,13 @@ async function deployTestBridge( return { l1Token: l1Token.connect(ethProvider), l1TokenRebasable: l1TokenRebasable.connect(ethProvider), - tokenRateOracle: tokenRateOracleStub, ...connectBridgeContracts( { - l2Token: optDeployScript.getContractAddress(1), - l2TokenRebasable: optDeployScript.getContractAddress(3), + tokenRateOracle: optDeployScript.getContractAddress(0), + l2Token: optDeployScript.getContractAddress(2), + l2TokenRebasable: optDeployScript.getContractAddress(4), l1ERC20TokenBridge: ethDeployScript.getContractAddress(1), - l2ERC20TokenBridge: optDeployScript.getContractAddress(5), + l2ERC20TokenBridge: optDeployScript.getContractAddress(6) }, ethProvider, optProvider @@ -268,6 +259,7 @@ async function deployTestBridge( function connectBridgeContracts( addresses: { + tokenRateOracle: string; l2Token: string; l2TokenRebasable: string; l1ERC20TokenBridge: string; @@ -276,6 +268,7 @@ function connectBridgeContracts( ethSignerOrProvider: SignerOrProvider, optSignerOrProvider: SignerOrProvider ) { + const l1ERC20TokenBridge = L1ERC20TokenBridge__factory.connect( addresses.l1ERC20TokenBridge, ethSignerOrProvider @@ -292,11 +285,16 @@ function connectBridgeContracts( addresses.l2TokenRebasable, optSignerOrProvider ); + const tokenRateOracle = TokenRateOracle__factory.connect( + addresses.tokenRateOracle, + optSignerOrProvider + ); return { + tokenRateOracle, l2Token, l2TokenRebasable, l1ERC20TokenBridge, - l2ERC20TokenBridge, + l2ERC20TokenBridge }; } From f0b891a0e9f943d2a62cde439932e0cf1cc60d8b Mon Sep 17 00:00:00 2001 From: Anton Kovalchuk Date: Tue, 9 Jan 2024 10:06:20 +0100 Subject: [PATCH 019/148] add test for push token rate method --- contracts/optimism/L1ERC20TokenBridge.sol | 10 ++- .../bridging-rebase.integration.test.ts | 69 +++++++++++++++++++ 2 files changed, 76 insertions(+), 3 deletions(-) diff --git a/contracts/optimism/L1ERC20TokenBridge.sol b/contracts/optimism/L1ERC20TokenBridge.sol index a475594b..210a11e2 100644 --- a/contracts/optimism/L1ERC20TokenBridge.sol +++ b/contracts/optimism/L1ERC20TokenBridge.sol @@ -53,9 +53,13 @@ contract L1ERC20TokenBridge is l2TokenBridge = l2TokenBridge_; } - function pushTokenRate(uint32 l2Gas_) external { - bytes memory empty = new bytes(0); - _depositERC20To(l1TokenRebasable, l2TokenRebasable, l2TokenBridge, 0, l2Gas_, empty); + /// @notice Pushes token rate to L2 by depositing zero tokens. + /// @param l2Gas_ Gas limit required to complete the deposit on L2. + function pushTokenRate(uint32 l2Gas_) + external + whenDepositsEnabled + { + _depositERC20To(l1TokenRebasable, l2TokenRebasable, l2TokenBridge, 0, l2Gas_, ""); } /// @inheritdoc IL1ERC20Bridge diff --git a/test/optimism/bridging-rebase.integration.test.ts b/test/optimism/bridging-rebase.integration.test.ts index 2bc623cb..d5664bc3 100644 --- a/test/optimism/bridging-rebase.integration.test.ts +++ b/test/optimism/bridging-rebase.integration.test.ts @@ -109,6 +109,75 @@ scenario("Optimism :: Bridging integration test", ctxFactory) ); }) + .step("Push token rate to L2", async (ctx) => { + const { + l1Token, + l1TokenRebasable, + l1ERC20TokenBridge, + l2TokenRebasable, + l1CrossDomainMessenger, + l2ERC20TokenBridge, + l1Provider + } = ctx; + + const { l1Stranger } = ctx.accounts; + + const tokenHolderStrangerBalanceBefore = await l1TokenRebasable.balanceOf( + l1Stranger.address + ); + + const l1ERC20TokenBridgeBalanceBefore = await l1TokenRebasable.balanceOf( + l1ERC20TokenBridge.address + ); + + const tx = await l1ERC20TokenBridge + .connect(l1Stranger) + .pushTokenRate(200_000); + + const dataToSend = await packedTokenRateAndTimestamp(l1Provider, l1Token); + + await assert.emits(l1ERC20TokenBridge, tx, "ERC20DepositInitiated", [ + l1TokenRebasable.address, + l2TokenRebasable.address, + l1Stranger.address, + l2ERC20TokenBridge.address, + 0, + dataToSend, + ]); + + const l2DepositCalldata = l2ERC20TokenBridge.interface.encodeFunctionData( + "finalizeDeposit", + [ + l1TokenRebasable.address, + l2TokenRebasable.address, + l1Stranger.address, + l2ERC20TokenBridge.address, + 0, + dataToSend, + ] + ); + + const messageNonce = await l1CrossDomainMessenger.messageNonce(); + + await assert.emits(l1CrossDomainMessenger, tx, "SentMessage", [ + l2ERC20TokenBridge.address, + l1ERC20TokenBridge.address, + l2DepositCalldata, + messageNonce, + 200_000, + ]); + + assert.equalBN( + await l1Token.balanceOf(l1ERC20TokenBridge.address), + l1ERC20TokenBridgeBalanceBefore + ); + + assert.equalBN( + await l1TokenRebasable.balanceOf(l1Stranger.address), + tokenHolderStrangerBalanceBefore + ); + }) + .step("L1 -> L2 deposit zero tokens via depositERC20() method", async (ctx) => { const { l1Token, From e314bb1695c92d18edba6f4f74d4c20d12f6cbfa Mon Sep 17 00:00:00 2001 From: Anton Kovalchuk Date: Tue, 9 Jan 2024 14:04:17 +0100 Subject: [PATCH 020/148] unused return values fix --- contracts/optimism/L1ERC20TokenBridge.sol | 3 ++- contracts/optimism/L2ERC20TokenBridge.sol | 1 + contracts/optimism/TokenRateOracle.sol | 3 +-- contracts/token/ERC20Rebasable.sol | 9 +++++---- 4 files changed, 9 insertions(+), 7 deletions(-) diff --git a/contracts/optimism/L1ERC20TokenBridge.sol b/contracts/optimism/L1ERC20TokenBridge.sol index 210a11e2..d54f5ea5 100644 --- a/contracts/optimism/L1ERC20TokenBridge.sol +++ b/contracts/optimism/L1ERC20TokenBridge.sol @@ -158,7 +158,7 @@ contract L1ERC20TokenBridge is // maybe loosing 1 wei for stETH. Check another method IERC20(l1TokenRebasable).safeTransferFrom(msg.sender, address(this), amount_); - IERC20(l1TokenRebasable).approve(l1TokenNonRebasable, amount_); + if(!IERC20(l1TokenRebasable).approve(l1TokenNonRebasable, amount_)) revert ErrorRebasableTokenApprove(); // when 1 wei wasnt't transfer, can this wrap be failed? uint256 wstETHAmount = IERC20Wrapable(l1TokenNonRebasable).wrap(amount_); @@ -212,4 +212,5 @@ contract L1ERC20TokenBridge is } error ErrorSenderNotEOA(); + error ErrorRebasableTokenApprove(); } diff --git a/contracts/optimism/L2ERC20TokenBridge.sol b/contracts/optimism/L2ERC20TokenBridge.sol index 873e0380..3bf6063e 100644 --- a/contracts/optimism/L2ERC20TokenBridge.sol +++ b/contracts/optimism/L2ERC20TokenBridge.sol @@ -112,6 +112,7 @@ contract L2ERC20TokenBridge is DepositData memory depositData = decodeDepositData(data_); ITokenRateOracle tokenRateOracle = ERC20Rebasable(l2TokenRebasable).tokenRateOracle(); tokenRateOracle.updateRate(depositData.rate, depositData.time); + //slither-disable-next-line unused-return ERC20Rebasable(l2TokenRebasable).mintShares(to_, amount_); emit DepositFinalized(l1Token_, l2Token_, from_, to_, amount_, depositData.data); } else if (isNonRebasableTokenFlow(l1Token_, l2Token_)) { diff --git a/contracts/optimism/TokenRateOracle.sol b/contracts/optimism/TokenRateOracle.sol index ef5e8dc5..18d42fbc 100644 --- a/contracts/optimism/TokenRateOracle.sol +++ b/contracts/optimism/TokenRateOracle.sol @@ -40,14 +40,13 @@ contract TokenRateOracle is ITokenRateOracle { uint80 answeredInRound_ ) { uint80 roundId = uint80(rateL1Timestamp); // TODO: add solt - uint80 answeredInRound = roundId; return ( roundId, int256(tokenRate), rateL1Timestamp, rateL1Timestamp, - answeredInRound + roundId ); } diff --git a/contracts/token/ERC20Rebasable.sol b/contracts/token/ERC20Rebasable.sol index a46fa2da..cdf47609 100644 --- a/contracts/token/ERC20Rebasable.sol +++ b/contracts/token/ERC20Rebasable.sol @@ -24,6 +24,7 @@ contract ERC20Rebasable is IERC20, IERC20Wrapable, IERC20BridgedShares, ERC20Met error ErrorAccountIsZeroAddress(); error ErrorDecreasedAllowanceBelowZero(); error ErrorNotBridge(); + error ErrorERC20Transfer(); /// @inheritdoc IERC20BridgedShares address public immutable bridge; @@ -73,9 +74,9 @@ contract ERC20Rebasable is IERC20, IERC20Wrapable, IERC20BridgedShares, ERC20Met /// @inheritdoc IERC20Wrapable function wrap(uint256 sharesAmount_) external returns (uint256) { if (sharesAmount_ == 0) revert ErrorZeroSharesWrap(); - + _mintShares(msg.sender, sharesAmount_); - wrappedToken.transferFrom(msg.sender, address(this), sharesAmount_); + if(!wrappedToken.transferFrom(msg.sender, address(this), sharesAmount_)) revert ErrorERC20Transfer(); return _getTokensByShares(sharesAmount_); } @@ -85,9 +86,8 @@ contract ERC20Rebasable is IERC20, IERC20Wrapable, IERC20BridgedShares, ERC20Met if (tokenAmount_ == 0) revert ErrorZeroTokensUnwrap(); uint256 sharesAmount = _getSharesByTokens(tokenAmount_); - _burnShares(msg.sender, sharesAmount); - wrappedToken.transfer(msg.sender, sharesAmount); + if(!wrappedToken.transfer(msg.sender, sharesAmount)) revert ErrorERC20Transfer(); return sharesAmount; } @@ -281,6 +281,7 @@ contract ERC20Rebasable is IERC20, IERC20Wrapable, IERC20BridgedShares, ERC20Met if (rateDecimals == uint8(0) || rateDecimals > uint8(18)) revert ErrorInvalidRateDecimals(rateDecimals); + //slither-disable-next-line unused-return (, int256 answer , From 4b56ccf140e9204ac1f6bacb08ad24bcfd880313 Mon Sep 17 00:00:00 2001 From: Anton Kovalchuk Date: Tue, 9 Jan 2024 17:20:22 +0100 Subject: [PATCH 021/148] fix deployment scripts for new oracle and token --- .env.example | 3 +++ .env.wsteth.opt_mainnet | 3 +++ README.md | 9 +++++++-- scripts/optimism/deploy-bridge.ts | 8 ++++---- utils/deployment.ts | 6 ++++-- utils/testing/scenario.ts | 4 ++-- utils/testing/unit.ts | 4 ++-- 7 files changed, 25 insertions(+), 12 deletions(-) diff --git a/.env.example b/.env.example index 2f00c733..767859ae 100644 --- a/.env.example +++ b/.env.example @@ -28,6 +28,9 @@ ETHERSCAN_API_KEY_OPT= # Address of the token to deploy the bridge/gateway for TOKEN= +# Address of the rebasable token to deploy the bridge/gateway for +STETH_TOKEN= + # Name of the network environments used by deployment scripts. # Might be one of: "mainnet", "goerli". NETWORK=mainnet diff --git a/.env.wsteth.opt_mainnet b/.env.wsteth.opt_mainnet index 6bf3ae55..ccb7f7d6 100644 --- a/.env.wsteth.opt_mainnet +++ b/.env.wsteth.opt_mainnet @@ -21,6 +21,9 @@ ETHERSCAN_API_KEY_OPT= # Address of the token to deploy the bridge/gateway for TOKEN=0x7f39C581F595B53c5cb19bD0b3f8dA6c935E2Ca0 +# Address of the rebasable token to deploy the bridge/gateway for +STETH_TOKEN= + # Name of the network environments used by deployment scripts. # Might be one of: "mainnet", "goerli". NETWORK=mainnet diff --git a/README.md b/README.md index 5f9ca478..97b1a883 100644 --- a/README.md +++ b/README.md @@ -42,7 +42,8 @@ Fill the newly created `.env` file with the required variables. See the [Project The configuration of the deployment scripts happens via the ENV variables. The following variables are required: -- [`TOKEN`](#TOKEN) - address of the token to deploy a new bridge on the Ethereum chain. +- [`TOKEN`](#TOKEN) - address of the non-rebasable token to deploy a new bridge on the Ethereum chain. +- [`STETH_TOKEN`] (#STETH_TOKEN) - address of the rebasable token to deploy new bridge on the Ethereum chain. - [`NETWORK`](#NETWORK) - name of the network environments used by deployment scripts. Allowed values: `mainnet`, `goerli`. - [`FORKING`](#FORKING) - run deployment in the forking network instead of real ones - [`ETH_DEPLOYER_PRIVATE_KEY`](#ETH_DEPLOYER_PRIVATE_KEY) - The private key of the deployer account in the Ethereum network is used during the deployment process. @@ -314,7 +315,11 @@ Below variables used in the Arbitrum/Optimism bridge deployment process. #### `TOKEN` -Address of the token to deploy a new bridge on the Ethereum chain. +Address of the non-rebasable token to deploy a new bridge on the Ethereum chain. + +#### `STETH_TOKEN` + +Address of the rebasable token to deploy new bridge on the Ethereum chain. #### `NETWORK` diff --git a/scripts/optimism/deploy-bridge.ts b/scripts/optimism/deploy-bridge.ts index 633edb29..7680befb 100644 --- a/scripts/optimism/deploy-bridge.ts +++ b/scripts/optimism/deploy-bridge.ts @@ -25,7 +25,7 @@ async function main() { .deployment(networkName, { logger: console }) .erc20TokenBridgeDeployScript( deploymentConfig.token, - deploymentConfig.token, // FIX + deploymentConfig.stETHToken, { deployer: ethDeployer, admins: { @@ -63,15 +63,15 @@ async function main() { { logger: console } ); - const l2ERC20TokenBridgeProxyDeployStepIndex = 3; + const l2ERC20TokenBridgeProxyDeployStepIndex = 6; const l2BridgingManagement = new BridgingManagement( l2DeployScript.getContractAddress(l2ERC20TokenBridgeProxyDeployStepIndex), optDeployer, { logger: console } ); - await l1BridgingManagement.setup(deploymentConfig.l1); - await l2BridgingManagement.setup(deploymentConfig.l2); + await l1BridgingManagement.setup(deploymentConfig.l1); + await l2BridgingManagement.setup(deploymentConfig.l2); } main().catch((error) => { diff --git a/utils/deployment.ts b/utils/deployment.ts index 617ca5b9..f18aa609 100644 --- a/utils/deployment.ts +++ b/utils/deployment.ts @@ -11,6 +11,7 @@ interface ChainDeploymentConfig extends BridgingManagerSetupConfig { interface MultiChainDeploymentConfig { token: string; + stETHToken: string; l1: ChainDeploymentConfig; l2: ChainDeploymentConfig; } @@ -18,6 +19,7 @@ interface MultiChainDeploymentConfig { export function loadMultiChainDeploymentConfig(): MultiChainDeploymentConfig { return { token: env.address("TOKEN"), + stETHToken: env.address("STETH_TOKEN"), l1: { proxyAdmin: env.address("L1_PROXY_ADMIN"), bridgeAdmin: env.address("L1_BRIDGE_ADMIN"), @@ -49,8 +51,8 @@ export async function printMultiChainDeploymentConfig( l1DeployScript: DeployScript, l2DeployScript: DeployScript ) { - const { token, l1, l2 } = deploymentParams; - console.log(chalk.bold(`${title} :: ${chalk.underline(token)}\n`)); + const { token, stETHToken, l1, l2 } = deploymentParams; + console.log(chalk.bold(`${title} :: ${chalk.underline(token)} :: ${chalk.underline(stETHToken)}\n`)); console.log(chalk.bold(" · L1 Deployment Params:")); await printChainDeploymentConfig(l1Deployer, l1); console.log(); diff --git a/utils/testing/scenario.ts b/utils/testing/scenario.ts index a11ba74f..66998a17 100644 --- a/utils/testing/scenario.ts +++ b/utils/testing/scenario.ts @@ -1,6 +1,6 @@ import { CtxFactory, StepTest, CtxFn } from "./types"; -class ScenarioTest { +class ScenarioTest { private afterFn?: CtxFn; private beforeFn?: CtxFn; @@ -68,6 +68,6 @@ class ScenarioTest { } } -export function scenario(title: string, ctxFactory: CtxFactory) { +export function scenario(title: string, ctxFactory: CtxFactory) { return new ScenarioTest(title, ctxFactory); } diff --git a/utils/testing/unit.ts b/utils/testing/unit.ts index 246a847e..a282e77e 100644 --- a/utils/testing/unit.ts +++ b/utils/testing/unit.ts @@ -1,11 +1,11 @@ import hre from "hardhat"; import { CtxFactory, StepTest, CtxFn } from "./types"; -export function unit(title: string, ctxFactory: CtxFactory) { +export function unit(title: string, ctxFactory: CtxFactory) { return new UnitTest(title, ctxFactory); } -class UnitTest { +class UnitTest { public readonly title: string; private readonly ctxFactory: CtxFactory; From 3b82da7668c72a07c822d7abb28bf5c3a005ff52 Mon Sep 17 00:00:00 2001 From: Anton Kovalchuk Date: Wed, 10 Jan 2024 12:08:51 +0100 Subject: [PATCH 022/148] add hearbeat to oracle --- contracts/optimism/L1ERC20TokenBridge.sol | 5 +---- contracts/optimism/TokenRateOracle.sol | 12 +++++++++++- contracts/token/interfaces/ITokenRateOracle.sol | 4 ++++ test/optimism/TokenRateOracle.unit.test.ts | 3 ++- utils/optimism/deployment.ts | 1 + 5 files changed, 19 insertions(+), 6 deletions(-) diff --git a/contracts/optimism/L1ERC20TokenBridge.sol b/contracts/optimism/L1ERC20TokenBridge.sol index d54f5ea5..8baa3b56 100644 --- a/contracts/optimism/L1ERC20TokenBridge.sol +++ b/contracts/optimism/L1ERC20TokenBridge.sol @@ -55,10 +55,7 @@ contract L1ERC20TokenBridge is /// @notice Pushes token rate to L2 by depositing zero tokens. /// @param l2Gas_ Gas limit required to complete the deposit on L2. - function pushTokenRate(uint32 l2Gas_) - external - whenDepositsEnabled - { + function pushTokenRate(uint32 l2Gas_) external { _depositERC20To(l1TokenRebasable, l2TokenRebasable, l2TokenBridge, 0, l2Gas_, ""); } diff --git a/contracts/optimism/TokenRateOracle.sol b/contracts/optimism/TokenRateOracle.sol index 18d42fbc..812e4bac 100644 --- a/contracts/optimism/TokenRateOracle.sol +++ b/contracts/optimism/TokenRateOracle.sol @@ -24,11 +24,16 @@ contract TokenRateOracle is ITokenRateOracle { /// @notice An updater which can update oracle. address public immutable tokenRateUpdater; + /// @notice A time period when token rate can be considered outdated. + uint256 public immutable heartbeatPeriodTime; + /// @param bridge_ the bridge address that has a right to updates oracle. /// @param tokenRateUpdater_ address of oracle updater that has a right to updates oracle. - constructor(address bridge_, address tokenRateUpdater_) { + /// @param heartbeatPeriodTime_ time period when token rate can be considered outdated. + constructor(address bridge_, address tokenRateUpdater_, uint256 heartbeatPeriodTime_) { bridge = bridge_; tokenRateUpdater = tokenRateUpdater_; + heartbeatPeriodTime = heartbeatPeriodTime_; } /// @inheritdoc ITokenRateOracle @@ -70,6 +75,11 @@ contract TokenRateOracle is ITokenRateOracle { rateL1Timestamp = rateL1Timestamp_; } + /// @notice Returns flag that shows that token rate can be considered outdated. + function isLikelyOutdated() external view returns (bool) { + return block.timestamp - rateL1Timestamp > heartbeatPeriodTime; + } + /// @dev validates that method called by one of the owners modifier onlyOwner() { if (msg.sender != bridge && msg.sender != tokenRateUpdater) { diff --git a/contracts/token/interfaces/ITokenRateOracle.sol b/contracts/token/interfaces/ITokenRateOracle.sol index 1c33fc21..2f2d32f6 100644 --- a/contracts/token/interfaces/ITokenRateOracle.sol +++ b/contracts/token/interfaces/ITokenRateOracle.sol @@ -25,11 +25,15 @@ interface ITokenRateOracle { ); /// @notice get the lastest token rate. + /// @return wstETH/stETH token rate. function latestAnswer() external view returns (int256); /// @notice represents the number of decimals the oracle responses represent. + /// @return decimals of the oracle response. function decimals() external view returns (uint8); /// @notice Updates token rate. + /// @param tokenRate_ wstETH/stETH token rate. + /// @param rateL1Timestamp_ L1 time when rate was pushed on L1 side. function updateRate(uint256 tokenRate_, uint256 rateL1Timestamp_) external; } \ No newline at end of file diff --git a/test/optimism/TokenRateOracle.unit.test.ts b/test/optimism/TokenRateOracle.unit.test.ts index f5981628..95384c96 100644 --- a/test/optimism/TokenRateOracle.unit.test.ts +++ b/test/optimism/TokenRateOracle.unit.test.ts @@ -81,7 +81,8 @@ async function ctxFactory() { const tokenRateOracle = await new TokenRateOracle__factory(deployer).deploy( bridge.address, - updater.address + updater.address, + 86400 ); return { diff --git a/utils/optimism/deployment.ts b/utils/optimism/deployment.ts index a73a1ed4..dbbd9fba 100644 --- a/utils/optimism/deployment.ts +++ b/utils/optimism/deployment.ts @@ -118,6 +118,7 @@ export default function deployment( args: [ expectedL2TokenBridgeProxyAddress, expectedL2TokenBridgeProxyAddress, + 86400, options?.overrides, ], afterDeploy: (c) => From 171db5ab523a34b3865faff2639af5d0aff51ca0 Mon Sep 17 00:00:00 2001 From: Anton Kovalchuk Date: Mon, 22 Jan 2024 10:08:26 +0100 Subject: [PATCH 023/148] pr fixes from first rount review: apply unstructured storage in token, rename consts, rename variables, fix comments --- .solhint.json | 3 +- .../optimism/BridgeableTokensOptimism.sol | 24 +- contracts/optimism/CrossDomainEnabled.sol | 10 +- contracts/optimism/DepositDataCodec.sol | 19 +- contracts/optimism/L1ERC20TokenBridge.sol | 49 +++-- contracts/optimism/L2ERC20TokenBridge.sol | 69 +++--- contracts/optimism/TokenRateOracle.sol | 34 +-- ...rapableStub.sol => ERC20WrappableStub.sol} | 4 +- contracts/stubs/TokenRateOracleStub.sol | 8 +- contracts/token/ERC20Rebasable.sol | 205 ++++++++++-------- contracts/token/UnstructuredRefStorage.sol | 18 ++ contracts/token/UnstructuredStorage.sol | 38 ++++ .../token/interfaces/IERC20BridgedShares.sol | 2 +- ...IERC20Wrapable.sol => IERC20Wrappable.sol} | 6 +- .../optimism.integration.test.ts | 10 +- test/optimism/TokenRateOracle.unit.test.ts | 8 +- .../bridging-rebase.integration.test.ts | 8 +- test/token/ERC20Rebasable.unit.test.ts | 10 +- utils/optimism/testing.ts | 6 +- 19 files changed, 309 insertions(+), 222 deletions(-) rename contracts/stubs/{ERC20WrapableStub.sol => ERC20WrappableStub.sol} (92%) create mode 100644 contracts/token/UnstructuredRefStorage.sol create mode 100644 contracts/token/UnstructuredStorage.sol rename contracts/token/interfaces/{IERC20Wrapable.sol => IERC20Wrappable.sol} (86%) diff --git a/.solhint.json b/.solhint.json index 83b993c5..ff8b9e54 100644 --- a/.solhint.json +++ b/.solhint.json @@ -14,7 +14,6 @@ "ignoreConstructors": true } ], - "lido/fixed-compiler-version": "error", - "const-name-snakecase": false + "lido/fixed-compiler-version": "error" } } diff --git a/contracts/optimism/BridgeableTokensOptimism.sol b/contracts/optimism/BridgeableTokensOptimism.sol index 087c845d..6bf417a3 100644 --- a/contracts/optimism/BridgeableTokensOptimism.sol +++ b/contracts/optimism/BridgeableTokensOptimism.sol @@ -7,31 +7,31 @@ pragma solidity 0.8.10; /// @notice Contains the logic for validation of tokens used in the bridging process contract BridgeableTokensOptimism { /// @notice Address of the bridged non rebasable token in the L1 chain - address public immutable l1TokenNonRebasable; + address public immutable L1_TOKEN_NON_REBASABLE; /// @notice Address of the bridged rebasable token in the L1 chain - address public immutable l1TokenRebasable; + address public immutable L1_TOKEN_REBASABLE; /// @notice Address of the non rebasable token minted on the L2 chain when token bridged - address public immutable l2TokenNonRebasable; + address public immutable L2_TOKEN_NON_REBASABLE; /// @notice Address of the rebasable token minted on the L2 chain when token bridged - address public immutable l2TokenRebasable; + address public immutable L2_TOKEN_REBASABLE; /// @param l1TokenNonRebasable_ Address of the bridged non rebasable token in the L1 chain /// @param l1TokenRebasable_ Address of the bridged rebasable token in the L1 chain /// @param l2TokenNonRebasable_ Address of the non rebasable token minted on the L2 chain when token bridged /// @param l2TokenRebasable_ Address of the rebasable token minted on the L2 chain when token bridged constructor(address l1TokenNonRebasable_, address l1TokenRebasable_, address l2TokenNonRebasable_, address l2TokenRebasable_) { - l1TokenNonRebasable = l1TokenNonRebasable_; - l1TokenRebasable = l1TokenRebasable_; - l2TokenNonRebasable = l2TokenNonRebasable_; - l2TokenRebasable = l2TokenRebasable_; + L1_TOKEN_NON_REBASABLE = l1TokenNonRebasable_; + L1_TOKEN_REBASABLE = l1TokenRebasable_; + L2_TOKEN_NON_REBASABLE = l2TokenNonRebasable_; + L2_TOKEN_REBASABLE = l2TokenRebasable_; } /// @dev Validates that passed l1Token_ is supported by the bridge modifier onlySupportedL1Token(address l1Token_) { - if (l1Token_ != l1TokenNonRebasable && l1Token_ != l1TokenRebasable) { + if (l1Token_ != L1_TOKEN_NON_REBASABLE && l1Token_ != L1_TOKEN_REBASABLE) { revert ErrorUnsupportedL1Token(); } _; @@ -39,7 +39,7 @@ contract BridgeableTokensOptimism { /// @dev Validates that passed l2Token_ is supported by the bridge modifier onlySupportedL2Token(address l2Token_) { - if (l2Token_ != l2TokenNonRebasable && l2Token_ != l2TokenRebasable) { + if (l2Token_ != L2_TOKEN_NON_REBASABLE && l2Token_ != L2_TOKEN_REBASABLE) { revert ErrorUnsupportedL2Token(); } _; @@ -54,11 +54,11 @@ contract BridgeableTokensOptimism { } function isRebasableTokenFlow(address l1Token_, address l2Token_) internal view returns (bool) { - return l1Token_ == l1TokenRebasable && l2Token_ == l2TokenRebasable; + return l1Token_ == L1_TOKEN_REBASABLE && l2Token_ == L2_TOKEN_REBASABLE; } function isNonRebasableTokenFlow(address l1Token_, address l2Token_) internal view returns (bool) { - return l1Token_ == l1TokenNonRebasable && l2Token_ == l2TokenNonRebasable; + return l1Token_ == L1_TOKEN_NON_REBASABLE && l2Token_ == L2_TOKEN_NON_REBASABLE; } error ErrorUnsupportedL1Token(); diff --git a/contracts/optimism/CrossDomainEnabled.sol b/contracts/optimism/CrossDomainEnabled.sol index 0fe0e5bb..23935681 100644 --- a/contracts/optimism/CrossDomainEnabled.sol +++ b/contracts/optimism/CrossDomainEnabled.sol @@ -8,11 +8,11 @@ import {ICrossDomainMessenger} from "./interfaces/ICrossDomainMessenger.sol"; /// @dev Helper contract for contracts performing cross-domain communications contract CrossDomainEnabled { /// @notice Messenger contract used to send and receive messages from the other domain - ICrossDomainMessenger public immutable messenger; + ICrossDomainMessenger public immutable MESSENGER; /// @param messenger_ Address of the CrossDomainMessenger on the current layer constructor(address messenger_) { - messenger = ICrossDomainMessenger(messenger_); + MESSENGER = ICrossDomainMessenger(messenger_); } /// @dev Sends a message to an account on another domain @@ -25,17 +25,17 @@ contract CrossDomainEnabled { uint32 gasLimit_, bytes memory message_ ) internal { - messenger.sendMessage(crossDomainTarget_, message_, gasLimit_); + MESSENGER.sendMessage(crossDomainTarget_, message_, gasLimit_); } /// @dev Enforces that the modified function is only callable by a specific cross-domain account /// @param sourceDomainAccount_ The only account on the originating domain which is /// authenticated to call this function modifier onlyFromCrossDomainAccount(address sourceDomainAccount_) { - if (msg.sender != address(messenger)) { + if (msg.sender != address(MESSENGER)) { revert ErrorUnauthorizedMessenger(); } - if (messenger.xDomainMessageSender() != sourceDomainAccount_) { + if (MESSENGER.xDomainMessageSender() != sourceDomainAccount_) { revert ErrorWrongCrossDomainSender(); } _; diff --git a/contracts/optimism/DepositDataCodec.sol b/contracts/optimism/DepositDataCodec.sol index 55dd9ea8..68ada77b 100644 --- a/contracts/optimism/DepositDataCodec.sol +++ b/contracts/optimism/DepositDataCodec.sol @@ -3,18 +3,23 @@ pragma solidity 0.8.10; +/// @author kovalgek +/// @notice encodes and decodes DepositData for crosschain transfering. contract DepositDataCodec { + + uint8 internal constant RATE_FIELD_SIZE = 12; + uint8 internal constant TIMESTAMP_FIELD_SIZE = 5; struct DepositData { uint96 rate; - uint40 time; + uint40 timestamp; bytes data; } function encodeDepositData(DepositData memory depositData) internal pure returns (bytes memory) { bytes memory data = bytes.concat( abi.encodePacked(depositData.rate), - abi.encodePacked(depositData.time), + abi.encodePacked(depositData.timestamp), abi.encodePacked(depositData.data) ); return data; @@ -22,18 +27,18 @@ contract DepositDataCodec { function decodeDepositData(bytes calldata buffer) internal pure returns (DepositData memory) { - if (buffer.length < 12 + 5) { + if (buffer.length < RATE_FIELD_SIZE + TIMESTAMP_FIELD_SIZE) { revert ErrorDepositDataLength(); } DepositData memory depositData = DepositData({ - rate: uint96(bytes12(buffer[0:12])), - time: uint40(bytes5(buffer[12:17])), - data: buffer[17:] + rate: uint96(bytes12(buffer[0:RATE_FIELD_SIZE])), + timestamp: uint40(bytes5(buffer[RATE_FIELD_SIZE:RATE_FIELD_SIZE + TIMESTAMP_FIELD_SIZE])), + data: buffer[RATE_FIELD_SIZE + TIMESTAMP_FIELD_SIZE:] }); return depositData; } error ErrorDepositDataLength(); -} \ No newline at end of file +} diff --git a/contracts/optimism/L1ERC20TokenBridge.sol b/contracts/optimism/L1ERC20TokenBridge.sol index 8baa3b56..1948040c 100644 --- a/contracts/optimism/L1ERC20TokenBridge.sol +++ b/contracts/optimism/L1ERC20TokenBridge.sol @@ -15,11 +15,8 @@ import {BridgeableTokensOptimism} from "./BridgeableTokensOptimism.sol"; import {CrossDomainEnabled} from "./CrossDomainEnabled.sol"; import {DepositDataCodec} from "./DepositDataCodec.sol"; -import {IERC20Wrapable} from "../token/interfaces/IERC20Wrapable.sol"; +import {IERC20Wrappable} from "../token/interfaces/IERC20Wrappable.sol"; -// Check if Optimism changed API for bridges. They could deprecate methods. -// Optimise gas usage with data transfer. Maybe cache rate and see if it changed. - /// @author psirex, kovalgek /// @notice The L1 ERC20 token bridge locks bridged tokens on the L1 side, sends deposit messages /// on the L2 side, and finalizes token withdrawals from L2. Additionally, adds the methods for @@ -33,8 +30,7 @@ contract L1ERC20TokenBridge is { using SafeERC20 for IERC20; - /// @inheritdoc IL1ERC20Bridge - address public immutable l2TokenBridge; + address public immutable L2_TOKEN_BRIDGE; /// @param messenger_ L1 messenger address being used for cross-chain communications /// @param l2TokenBridge_ Address of the corresponding L2 bridge @@ -50,13 +46,18 @@ contract L1ERC20TokenBridge is address l2TokenNonRebasable_, address l2TokenRebasable_ ) CrossDomainEnabled(messenger_) BridgeableTokensOptimism(l1TokenNonRebasable_, l1TokenRebasable_, l2TokenNonRebasable_, l2TokenRebasable_) { - l2TokenBridge = l2TokenBridge_; + L2_TOKEN_BRIDGE = l2TokenBridge_; } /// @notice Pushes token rate to L2 by depositing zero tokens. /// @param l2Gas_ Gas limit required to complete the deposit on L2. function pushTokenRate(uint32 l2Gas_) external { - _depositERC20To(l1TokenRebasable, l2TokenRebasable, l2TokenBridge, 0, l2Gas_, ""); + _depositERC20To(L1_TOKEN_REBASABLE, L2_TOKEN_REBASABLE, L2_TOKEN_BRIDGE, 0, l2Gas_, ""); + } + + /// @inheritdoc IL1ERC20Bridge + function l2TokenBridge() external view returns (address) { + return L2_TOKEN_BRIDGE; } /// @inheritdoc IL1ERC20Bridge @@ -75,7 +76,7 @@ contract L1ERC20TokenBridge is if (Address.isContract(msg.sender)) { revert ErrorSenderNotEOA(); } - + _depositERC20To(l1Token_, l2Token_, msg.sender, amount_, l2Gas_, data_); } @@ -110,13 +111,13 @@ contract L1ERC20TokenBridge is whenWithdrawalsEnabled onlySupportedL1Token(l1Token_) onlySupportedL2Token(l2Token_) - onlyFromCrossDomainAccount(l2TokenBridge) + onlyFromCrossDomainAccount(L2_TOKEN_BRIDGE) { if (isRebasableTokenFlow(l1Token_, l2Token_)) { - uint256 stETHAmount = IERC20Wrapable(l1TokenNonRebasable).unwrap(amount_); - IERC20(l1TokenRebasable).safeTransfer(to_, stETHAmount); + uint256 stETHAmount = IERC20Wrappable(L1_TOKEN_NON_REBASABLE).unwrap(amount_); + IERC20(L1_TOKEN_REBASABLE).safeTransfer(to_, stETHAmount); } else if (isNonRebasableTokenFlow(l1Token_, l2Token_)) { - IERC20(l1TokenNonRebasable).safeTransfer(to_, amount_); + IERC20(L1_TOKEN_NON_REBASABLE).safeTransfer(to_, amount_); } emit ERC20WithdrawalFinalized( @@ -140,8 +141,8 @@ contract L1ERC20TokenBridge is if (isRebasableTokenFlow(l1Token_, l2Token_)) { DepositData memory depositData = DepositData({ - rate: uint96(IERC20Wrapable(l1TokenNonRebasable).stETHPerToken()), - time: uint40(block.timestamp), + rate: uint96(IERC20Wrappable(L1_TOKEN_NON_REBASABLE).stETHPerToken()), + timestamp: uint40(block.timestamp), data: data_ }); @@ -152,18 +153,18 @@ contract L1ERC20TokenBridge is _initiateERC20Deposit(l1Token_, l2Token_, msg.sender, to_, amount_, l2Gas_, encodedDepositData); return; } - + // maybe loosing 1 wei for stETH. Check another method - IERC20(l1TokenRebasable).safeTransferFrom(msg.sender, address(this), amount_); - if(!IERC20(l1TokenRebasable).approve(l1TokenNonRebasable, amount_)) revert ErrorRebasableTokenApprove(); + IERC20(L1_TOKEN_REBASABLE).safeTransferFrom(msg.sender, address(this), amount_); + if(!IERC20(L1_TOKEN_REBASABLE).approve(L1_TOKEN_NON_REBASABLE, amount_)) revert ErrorRebasableTokenApprove(); // when 1 wei wasnt't transfer, can this wrap be failed? - uint256 wstETHAmount = IERC20Wrapable(l1TokenNonRebasable).wrap(amount_); - _initiateERC20Deposit(l1TokenRebasable, l2TokenRebasable, msg.sender, to_, wstETHAmount, l2Gas_, encodedDepositData); + uint256 wstETHAmount = IERC20Wrappable(L1_TOKEN_NON_REBASABLE).wrap(amount_); + _initiateERC20Deposit(L1_TOKEN_REBASABLE, L2_TOKEN_REBASABLE, msg.sender, to_, wstETHAmount, l2Gas_, encodedDepositData); } else if (isNonRebasableTokenFlow(l1Token_, l2Token_)) { - IERC20(l1TokenNonRebasable).safeTransferFrom(msg.sender, address(this), amount_); - _initiateERC20Deposit(l1TokenNonRebasable, l2TokenNonRebasable, msg.sender, to_, amount_, l2Gas_, data_); + IERC20(L1_TOKEN_NON_REBASABLE).safeTransferFrom(msg.sender, address(this), amount_); + _initiateERC20Deposit(L1_TOKEN_NON_REBASABLE, L2_TOKEN_NON_REBASABLE, msg.sender, to_, amount_, l2Gas_, data_); } } @@ -195,8 +196,8 @@ contract L1ERC20TokenBridge is amount_, data_ ); - - sendCrossDomainMessage(l2TokenBridge, l2Gas_, message); + + sendCrossDomainMessage(L2_TOKEN_BRIDGE, l2Gas_, message); emit ERC20DepositInitiated( l1Token_, diff --git a/contracts/optimism/L2ERC20TokenBridge.sol b/contracts/optimism/L2ERC20TokenBridge.sol index 3bf6063e..a5cfb46d 100644 --- a/contracts/optimism/L2ERC20TokenBridge.sol +++ b/contracts/optimism/L2ERC20TokenBridge.sol @@ -8,7 +8,7 @@ import {IL2ERC20Bridge} from "./interfaces/IL2ERC20Bridge.sol"; import {IERC20Bridged} from "../token/interfaces/IERC20Bridged.sol"; import {ITokenRateOracle} from "../token/interfaces/ITokenRateOracle.sol"; import {ERC20Rebasable} from "../token/ERC20Rebasable.sol"; -import {IERC20Wrapable} from "../token/interfaces/IERC20Wrapable.sol"; +import {IERC20Wrappable} from "../token/interfaces/IERC20Wrappable.sol"; import {IERC20} from "@openzeppelin/contracts/token/ERC20/IERC20.sol"; import {SafeERC20} from "@openzeppelin/contracts/token/ERC20/utils/SafeERC20.sol"; @@ -32,8 +32,7 @@ contract L2ERC20TokenBridge is { using SafeERC20 for IERC20; - /// @inheritdoc IL2ERC20Bridge - address public immutable l1TokenBridge; + address public immutable L1_TOKEN_BRIDGE; /// @param messenger_ L2 messenger address being used for cross-chain communications /// @param l1TokenBridge_ Address of the corresponding L1 bridge @@ -49,7 +48,12 @@ contract L2ERC20TokenBridge is address l2TokenNonRebasable_, address l2TokenRebasable_ ) CrossDomainEnabled(messenger_) BridgeableTokensOptimism(l1TokenNonRebasable_, l1TokenRebasable_, l2TokenNonRebasable_, l2TokenRebasable_) { - l1TokenBridge = l1TokenBridge_; + L1_TOKEN_BRIDGE = l1TokenBridge_; + } + + /// @inheritdoc IL2ERC20Bridge + function l1TokenBridge() external view returns (address) { + return L1_TOKEN_BRIDGE; } /// @inheritdoc IL2ERC20Bridge @@ -69,30 +73,10 @@ contract L2ERC20TokenBridge is uint256 amount_, uint32 l1Gas_, bytes calldata data_ - ) external whenWithdrawalsEnabled onlySupportedL2Token(l2Token_) { + ) external whenWithdrawalsEnabled onlySupportedL2Token(l2Token_) { _withdrawTo(l2Token_, to_, amount_, l1Gas_, data_); } - function _withdrawTo( - address l2Token_, - address to_, - uint256 amount_, - uint32 l1Gas_, - bytes calldata data_ - ) internal { - if (l2Token_ == l2TokenRebasable) { - // maybe loosing 1 wei her as well - uint256 shares = ERC20Rebasable(l2TokenRebasable).getSharesByTokens(amount_); - ERC20Rebasable(l2TokenRebasable).burnShares(msg.sender, shares); - _initiateWithdrawal(l1TokenRebasable, l2TokenRebasable, msg.sender, to_, shares, l1Gas_, data_); - emit WithdrawalInitiated(l1TokenRebasable, l2TokenRebasable, msg.sender, to_, amount_, data_); - } else if (l2Token_ == l2TokenNonRebasable) { - IERC20Bridged(l2TokenNonRebasable).bridgeBurn(msg.sender, amount_); - _initiateWithdrawal(l1TokenNonRebasable, l2TokenNonRebasable, msg.sender, to_, amount_, l1Gas_, data_); - emit WithdrawalInitiated(l1TokenNonRebasable, l2TokenNonRebasable, msg.sender, to_, amount_, data_); - } - } - /// @inheritdoc IL2ERC20Bridge function finalizeDeposit( address l1Token_, @@ -106,21 +90,42 @@ contract L2ERC20TokenBridge is whenDepositsEnabled onlySupportedL1Token(l1Token_) onlySupportedL2Token(l2Token_) - onlyFromCrossDomainAccount(l1TokenBridge) + onlyFromCrossDomainAccount(L1_TOKEN_BRIDGE) { if (isRebasableTokenFlow(l1Token_, l2Token_)) { DepositData memory depositData = decodeDepositData(data_); - ITokenRateOracle tokenRateOracle = ERC20Rebasable(l2TokenRebasable).tokenRateOracle(); - tokenRateOracle.updateRate(depositData.rate, depositData.time); + ITokenRateOracle tokenRateOracle = ERC20Rebasable(L2_TOKEN_REBASABLE).TOKEN_RATE_ORACLE(); + tokenRateOracle.updateRate(depositData.rate, depositData.timestamp); //slither-disable-next-line unused-return - ERC20Rebasable(l2TokenRebasable).mintShares(to_, amount_); - emit DepositFinalized(l1Token_, l2Token_, from_, to_, amount_, depositData.data); + ERC20Rebasable(L2_TOKEN_REBASABLE).mintShares(to_, amount_); + uint256 rebasableTokenAmount = ERC20Rebasable(L2_TOKEN_REBASABLE).getTokensByShares(amount_); + emit DepositFinalized(l1Token_, l2Token_, from_, to_, rebasableTokenAmount, depositData.data); } else if (isNonRebasableTokenFlow(l1Token_, l2Token_)) { - IERC20Bridged(l2TokenNonRebasable).bridgeMint(to_, amount_); + IERC20Bridged(L2_TOKEN_NON_REBASABLE).bridgeMint(to_, amount_); emit DepositFinalized(l1Token_, l2Token_, from_, to_, amount_, data_); } } + function _withdrawTo( + address l2Token_, + address to_, + uint256 amount_, + uint32 l1Gas_, + bytes calldata data_ + ) internal { + if (l2Token_ == L2_TOKEN_REBASABLE) { + // maybe loosing 1 wei here as well + uint256 shares = ERC20Rebasable(L2_TOKEN_REBASABLE).getSharesByTokens(amount_); + ERC20Rebasable(L2_TOKEN_REBASABLE).burnShares(msg.sender, shares); + _initiateWithdrawal(L2_TOKEN_REBASABLE, L2_TOKEN_REBASABLE, msg.sender, to_, shares, l1Gas_, data_); + emit WithdrawalInitiated(L1_TOKEN_REBASABLE, L2_TOKEN_REBASABLE, msg.sender, to_, amount_, data_); + } else if (l2Token_ == L2_TOKEN_NON_REBASABLE) { + IERC20Bridged(L2_TOKEN_NON_REBASABLE).bridgeBurn(msg.sender, amount_); + _initiateWithdrawal(L1_TOKEN_NON_REBASABLE, L2_TOKEN_NON_REBASABLE, msg.sender, to_, amount_, l1Gas_, data_); + emit WithdrawalInitiated(L1_TOKEN_NON_REBASABLE, L2_TOKEN_NON_REBASABLE, msg.sender, to_, amount_, data_); + } + } + /// @notice Performs the logic for withdrawals by burning the token and informing /// the L1 token Gateway of the withdrawal /// @param from_ Account to pull the withdrawal from on L2 @@ -150,6 +155,6 @@ contract L2ERC20TokenBridge is data_ ); - sendCrossDomainMessage(l1TokenBridge, l1Gas_, message); + sendCrossDomainMessage(L1_TOKEN_BRIDGE, l1Gas_, message); } } diff --git a/contracts/optimism/TokenRateOracle.sol b/contracts/optimism/TokenRateOracle.sol index 812e4bac..e9a1290d 100644 --- a/contracts/optimism/TokenRateOracle.sol +++ b/contracts/optimism/TokenRateOracle.sol @@ -9,31 +9,28 @@ import {ITokenRateOracle} from "../token/interfaces/ITokenRateOracle.sol"; /// @notice Oracle for storing token rate. contract TokenRateOracle is ITokenRateOracle { - error NotAnOwner(address caller); - error IncorrectRateTimestamp(); - /// @notice wstETH/stETH token rate. uint256 private tokenRate; - /// @notice L1 time when token rate was pushed. + /// @notice L1 time when token rate was pushed. uint256 private rateL1Timestamp; /// @notice A bridge which can update oracle. - address public immutable bridge; + address public immutable BRIDGE; /// @notice An updater which can update oracle. - address public immutable tokenRateUpdater; + address public immutable TOKEN_RATE_UPDATER; /// @notice A time period when token rate can be considered outdated. - uint256 public immutable heartbeatPeriodTime; + uint256 public immutable RATE_VALIDITY_PERIOD; /// @param bridge_ the bridge address that has a right to updates oracle. /// @param tokenRateUpdater_ address of oracle updater that has a right to updates oracle. - /// @param heartbeatPeriodTime_ time period when token rate can be considered outdated. - constructor(address bridge_, address tokenRateUpdater_, uint256 heartbeatPeriodTime_) { - bridge = bridge_; - tokenRateUpdater = tokenRateUpdater_; - heartbeatPeriodTime = heartbeatPeriodTime_; + /// @param rateValidityPeriod_ time period when token rate can be considered outdated. + constructor(address bridge_, address tokenRateUpdater_, uint256 rateValidityPeriod_) { + BRIDGE = bridge_; + TOKEN_RATE_UPDATER = tokenRateUpdater_; + RATE_VALIDITY_PERIOD = rateValidityPeriod_; } /// @inheritdoc ITokenRateOracle @@ -69,7 +66,7 @@ contract TokenRateOracle is ITokenRateOracle { function updateRate(uint256 tokenRate_, uint256 rateL1Timestamp_) external onlyOwner { // reject rates from the future if (rateL1Timestamp_ < rateL1Timestamp) { - revert IncorrectRateTimestamp(); + revert ErrorIncorrectRateTimestamp(); } tokenRate = tokenRate_; rateL1Timestamp = rateL1Timestamp_; @@ -77,14 +74,17 @@ contract TokenRateOracle is ITokenRateOracle { /// @notice Returns flag that shows that token rate can be considered outdated. function isLikelyOutdated() external view returns (bool) { - return block.timestamp - rateL1Timestamp > heartbeatPeriodTime; + return block.timestamp - rateL1Timestamp > RATE_VALIDITY_PERIOD; } /// @dev validates that method called by one of the owners modifier onlyOwner() { - if (msg.sender != bridge && msg.sender != tokenRateUpdater) { - revert NotAnOwner(msg.sender); + if (msg.sender != BRIDGE && msg.sender != TOKEN_RATE_UPDATER) { + revert ErrorNotAnOwner(msg.sender); } _; } -} \ No newline at end of file + + error ErrorNotAnOwner(address caller); + error ErrorIncorrectRateTimestamp(); +} diff --git a/contracts/stubs/ERC20WrapableStub.sol b/contracts/stubs/ERC20WrappableStub.sol similarity index 92% rename from contracts/stubs/ERC20WrapableStub.sol rename to contracts/stubs/ERC20WrappableStub.sol index 3f77b88c..f1796105 100644 --- a/contracts/stubs/ERC20WrapableStub.sol +++ b/contracts/stubs/ERC20WrappableStub.sol @@ -6,10 +6,10 @@ pragma solidity 0.8.10; import {IERC20Bridged} from "../token/interfaces/IERC20Bridged.sol"; import {ERC20} from "@openzeppelin/contracts/token/ERC20/ERC20.sol"; import {IERC20} from "@openzeppelin/contracts/token/ERC20/IERC20.sol"; -import {IERC20Wrapable} from "../token/interfaces/IERC20Wrapable.sol"; +import {IERC20Wrappable} from "../token/interfaces/IERC20Wrappable.sol"; // represents wstETH on L1 -contract ERC20WrapableStub is IERC20Wrapable, ERC20 { +contract ERC20WrappableStub is IERC20Wrappable, ERC20 { IERC20 public stETH; address public bridge; diff --git a/contracts/stubs/TokenRateOracleStub.sol b/contracts/stubs/TokenRateOracleStub.sol index 6581e512..25ab6763 100644 --- a/contracts/stubs/TokenRateOracleStub.sol +++ b/contracts/stubs/TokenRateOracleStub.sol @@ -29,9 +29,6 @@ contract TokenRateOracleStub is ITokenRateOracle { latestRoundDataUpdatedAt = updatedAt_; } - /** - * @notice get data about the latest round. - */ function latestRoundData() external view @@ -56,8 +53,7 @@ contract TokenRateOracleStub is ITokenRateOracle { } function updateRate(uint256 tokenRate_, uint256 rateL1Timestamp_) external { - // check timestamp not late as current one. - latestRoundDataAnswer = tokenRate_; - latestRoundDataUpdatedAt = rateL1Timestamp_; + latestRoundDataAnswer = tokenRate_; + latestRoundDataUpdatedAt = rateL1Timestamp_; } } \ No newline at end of file diff --git a/contracts/token/ERC20Rebasable.sol b/contracts/token/ERC20Rebasable.sol index cdf47609..22372f5e 100644 --- a/contracts/token/ERC20Rebasable.sol +++ b/contracts/token/ERC20Rebasable.sol @@ -4,45 +4,37 @@ pragma solidity 0.8.10; import {IERC20} from "@openzeppelin/contracts/token/ERC20/IERC20.sol"; -import {IERC20Wrapable} from "./interfaces/IERC20Wrapable.sol"; +import {IERC20Wrappable} from "./interfaces/IERC20Wrappable.sol"; import {IERC20BridgedShares} from "./interfaces/IERC20BridgedShares.sol"; import {ITokenRateOracle} from "./interfaces/ITokenRateOracle.sol"; import {ERC20Metadata} from "./ERC20Metadata.sol"; +import {UnstructuredRefStorage} from "./UnstructuredRefStorage.sol"; +import {UnstructuredStorage} from "./UnstructuredStorage.sol"; /// @author kovalgek -/// @notice Extends the ERC20Shared functionality -contract ERC20Rebasable is IERC20, IERC20Wrapable, IERC20BridgedShares, ERC20Metadata { +/// @notice Rebasable token that wraps/unwraps non-rebasable token and allow to mint/burn tokens by bridge. +contract ERC20Rebasable is IERC20, IERC20Wrappable, IERC20BridgedShares, ERC20Metadata { - error ErrorZeroSharesWrap(); - error ErrorZeroTokensUnwrap(); - error ErrorInvalidRateDecimals(uint8); - error ErrorWrongOracleUpdateTime(); - error ErrorOracleAnswerIsNegative(); - error ErrorTrasferToRebasableContract(); - error ErrorNotEnoughBalance(); - error ErrorNotEnoughAllowance(); - error ErrorAccountIsZeroAddress(); - error ErrorDecreasedAllowanceBelowZero(); - error ErrorNotBridge(); - error ErrorERC20Transfer(); + using UnstructuredRefStorage for bytes32; + using UnstructuredStorage for bytes32; /// @inheritdoc IERC20BridgedShares - address public immutable bridge; + address public immutable BRIDGE; /// @notice Contract of non-rebasable token to wrap. - IERC20 public immutable wrappedToken; + IERC20 public immutable WRAPPED_TOKEN; /// @notice Oracle contract used to get token rate for wrapping/unwrapping tokens. - ITokenRateOracle public immutable tokenRateOracle; + ITokenRateOracle public immutable TOKEN_RATE_ORACLE; - /// @inheritdoc IERC20 - mapping(address => mapping(address => uint256)) public allowance; + /// @dev token allowance slot position. + bytes32 internal constant TOKEN_ALLOWANCE_POSITION = keccak256("ERC20Rebasable.TOKEN_ALLOWANCE_POSITION"); - /// @notice Basic unit representing the token holder's share in the total amount of ether controlled by the protocol. - mapping (address => uint256) private shares; + /// @dev user shares slot position. + bytes32 internal constant SHARES_POSITION = keccak256("ERC20Rebasable.SHARES_POSITION"); - /// @notice The total amount of shares in existence. - uint256 private totalShares; + /// @dev token shares slot position. + bytes32 internal constant TOTAL_SHARES_POSITION = keccak256("ERC20Rebasable.TOTAL_SHARES_POSITION"); /// @param name_ The name of the token /// @param symbol_ The symbol of the token @@ -58,9 +50,9 @@ contract ERC20Rebasable is IERC20, IERC20Wrapable, IERC20BridgedShares, ERC20Met address tokenRateOracle_, address bridge_ ) ERC20Metadata(name_, symbol_, decimals_) { - wrappedToken = IERC20(wrappedToken_); - tokenRateOracle = ITokenRateOracle(tokenRateOracle_); - bridge = bridge_; + WRAPPED_TOKEN = IERC20(wrappedToken_); + TOKEN_RATE_ORACLE = ITokenRateOracle(tokenRateOracle_); + BRIDGE = bridge_; } /// @notice Sets the name and the symbol of the tokens if they both are empty @@ -71,30 +63,30 @@ contract ERC20Rebasable is IERC20, IERC20Wrapable, IERC20BridgedShares, ERC20Met _setERC20MetadataSymbol(symbol_); } - /// @inheritdoc IERC20Wrapable + /// @inheritdoc IERC20Wrappable function wrap(uint256 sharesAmount_) external returns (uint256) { if (sharesAmount_ == 0) revert ErrorZeroSharesWrap(); _mintShares(msg.sender, sharesAmount_); - if(!wrappedToken.transferFrom(msg.sender, address(this), sharesAmount_)) revert ErrorERC20Transfer(); + if(!WRAPPED_TOKEN.transferFrom(msg.sender, address(this), sharesAmount_)) revert ErrorERC20Transfer(); return _getTokensByShares(sharesAmount_); } - /// @inheritdoc IERC20Wrapable + /// @inheritdoc IERC20Wrappable function unwrap(uint256 tokenAmount_) external returns (uint256) { if (tokenAmount_ == 0) revert ErrorZeroTokensUnwrap(); uint256 sharesAmount = _getSharesByTokens(tokenAmount_); _burnShares(msg.sender, sharesAmount); - if(!wrappedToken.transfer(msg.sender, sharesAmount)) revert ErrorERC20Transfer(); + if(!WRAPPED_TOKEN.transfer(msg.sender, sharesAmount)) revert ErrorERC20Transfer(); return sharesAmount; } - /// @inheritdoc IERC20Wrapable + /// @inheritdoc IERC20Wrappable function stETHPerToken() external view returns (uint256) { - return uint256(tokenRateOracle.latestAnswer()); + return uint256(TOKEN_RATE_ORACLE.latestAnswer()); } /// @inheritdoc IERC20BridgedShares @@ -107,17 +99,14 @@ contract ERC20Rebasable is IERC20, IERC20Wrapable, IERC20BridgedShares, ERC20Met _burnShares(account_, amount_); } - /// @dev Validates that sender of the transaction is the bridge - modifier onlyBridge() { - if (msg.sender != bridge) { - revert ErrorNotBridge(); - } - _; + /// @inheritdoc IERC20 + function allowance(address owner, address spender) external view returns (uint256) { + return _getTokenAllowance()[owner][spender]; } /// @inheritdoc IERC20 function totalSupply() external view returns (uint256) { - return _getTokensByShares(totalShares); + return _getTokensByShares(_getTotalShares()); } /// @inheritdoc IERC20 @@ -125,6 +114,32 @@ contract ERC20Rebasable is IERC20, IERC20Wrapable, IERC20BridgedShares, ERC20Met return _getTokensByShares(_sharesOf(account_)); } + /// @notice Get shares amount of the provided account. + /// @param account_ provided account address. + /// @return amount of shares owned by `_account`. + function sharesOf(address account_) external view returns (uint256) { + return _sharesOf(account_); + } + + /// @return total amount of shares. + function getTotalShares() external view returns (uint256) { + return _getTotalShares(); + } + + /// @notice Get amount of tokens for a given amount of shares. + /// @param sharesAmount_ amount of shares. + /// @return amount of tokens for a given shares amount. + function getTokensByShares(uint256 sharesAmount_) external view returns (uint256) { + return _getTokensByShares(sharesAmount_); + } + + /// @notice Get amount of shares for a given amount of tokens. + /// @param tokenAmount_ provided tokens amount. + /// @return amount of shares for a given tokens amount. + function getSharesByTokens(uint256 tokenAmount_) external view returns (uint256) { + return _getSharesByTokens(tokenAmount_); + } + /// @inheritdoc IERC20 function approve(address spender_, uint256 amount_) external @@ -161,19 +176,19 @@ contract ERC20Rebasable is IERC20, IERC20Wrapable, IERC20BridgedShares, ERC20Met _approve( msg.sender, spender_, - allowance[msg.sender][spender_] + addedValue_ + _getTokenAllowance()[msg.sender][spender_] + addedValue_ ); return true; } /// @notice Atomically decreases the allowance granted to spender by the caller. /// @param spender_ An address of the tokens spender - /// @param subtractedValue_ An amount to decrease the allowance + /// @param subtractedValue_ An amount to decrease the allowance function decreaseAllowance(address spender_, uint256 subtractedValue_) external returns (bool) { - uint256 currentAllowance = allowance[msg.sender][spender_]; + uint256 currentAllowance = _getTokenAllowance()[msg.sender][spender_]; if (currentAllowance < subtractedValue_) { revert ErrorDecreasedAllowanceBelowZero(); } @@ -183,6 +198,25 @@ contract ERC20Rebasable is IERC20, IERC20Wrapable, IERC20BridgedShares, ERC20Met return true; } + function _getTokenAllowance() internal pure returns (mapping(address => mapping(address => uint256)) storage) { + return TOKEN_ALLOWANCE_POSITION.storageMapAddressMapAddressUint256(); + } + + /// @notice Amount of shares (locked wstETH amount) owned by the holder. + function _getShares() internal pure returns (mapping(address => uint256) storage) { + return SHARES_POSITION.storageMapAddressAddressUint256(); + } + + /// @notice The total amount of shares in existence. + function _getTotalShares() internal view returns (uint256) { + return TOTAL_SHARES_POSITION.getStorageUint256(); + } + + /// @notice Set total amount of shares. + function _setTotalShares(uint256 _newTotalShares) internal { + TOTAL_SHARES_POSITION.setStorageUint256(_newTotalShares); + } + /// @dev Moves amount_ of tokens from sender_ to recipient_ /// @param from_ An address of the sender of the tokens /// @param to_ An address of the recipient of the tokens @@ -200,14 +234,14 @@ contract ERC20Rebasable is IERC20, IERC20Wrapable, IERC20BridgedShares, ERC20Met /// @dev Updates owner_'s allowance for spender_ based on spent amount_. Does not update /// the allowance amount in case of infinite allowance /// @param owner_ An address of the account to spend allowance - /// @param spender_ An address of the spender of the tokens + /// @param spender_ An address of the spender of the tokens /// @param amount_ An amount of allowance spend function _spendAllowance( address owner_, address spender_, uint256 amount_ ) internal { - uint256 currentAllowance = allowance[owner_][spender_]; + uint256 currentAllowance = _getTokenAllowance()[owner_][spender_]; if (currentAllowance == type(uint256).max) { return; } @@ -221,49 +255,19 @@ contract ERC20Rebasable is IERC20, IERC20Wrapable, IERC20BridgedShares, ERC20Met /// @dev Sets amount_ as the allowance of spender_ over the owner_'s tokens /// @param owner_ An address of the account to set allowance - /// @param spender_ An address of the tokens spender + /// @param spender_ An address of the tokens spender /// @param amount_ An amount of tokens to allow to spend function _approve( address owner_, address spender_, uint256 amount_ ) internal virtual onlyNonZeroAccount(owner_) onlyNonZeroAccount(spender_) { - allowance[owner_][spender_] = amount_; + _getTokenAllowance()[owner_][spender_] = amount_; emit Approval(owner_, spender_, amount_); } - /// @notice Get shares amount of the provided account. - /// @param account_ provided account address. - /// @return amount of shares owned by `_account`. - function sharesOf(address account_) external view returns (uint256) { - return _sharesOf(account_); - } - - /// @return total amount of shares. - function getTotalShares() external view returns (uint256) { - return _getTotalShares(); - } - - /// @notice Get amount of tokens for a given amount of shares. - /// @param sharesAmount_ amount of shares. - /// @return amount of tokens for a given shares amount. - function getTokensByShares(uint256 sharesAmount_) external view returns (uint256) { - return _getTokensByShares(sharesAmount_); - } - - /// @notice Get amount of shares for a given amount of tokens. - /// @param tokenAmount_ provided tokens amount. - /// @return amount of shares for a given tokens amount. - function getSharesByTokens(uint256 tokenAmount_) external view returns (uint256) { - return _getSharesByTokens(tokenAmount_); - } - function _sharesOf(address account_) internal view returns (uint256) { - return shares[account_]; - } - - function _getTotalShares() internal view returns (uint256) { - return totalShares; + return _getShares()[account_]; } function _getTokensByShares(uint256 sharesAmount_) internal view returns (uint256) { @@ -277,7 +281,7 @@ contract ERC20Rebasable is IERC20, IERC20Wrapable, IERC20BridgedShares, ERC20Met } function _getTokensRateAndDecimal() internal view returns (uint256, uint256) { - uint8 rateDecimals = tokenRateOracle.decimals(); + uint8 rateDecimals = TOKEN_RATE_ORACLE.decimals(); if (rateDecimals == uint8(0) || rateDecimals > uint8(18)) revert ErrorInvalidRateDecimals(rateDecimals); @@ -287,7 +291,7 @@ contract ERC20Rebasable is IERC20, IERC20Wrapable, IERC20BridgedShares, ERC20Met , , uint256 updatedAt - ,) = tokenRateOracle.latestRoundData(); + ,) = TOKEN_RATE_ORACLE.latestRoundData(); if (updatedAt == 0) revert ErrorWrongOracleUpdateTime(); if (answer <= 0) revert ErrorOracleAnswerIsNegative(); @@ -302,10 +306,10 @@ contract ERC20Rebasable is IERC20, IERC20Wrapable, IERC20BridgedShares, ERC20Met address recipient_, uint256 amount_ ) internal onlyNonZeroAccount(recipient_) returns (uint256) { - totalShares = totalShares + amount_; - shares[recipient_] = shares[recipient_] + amount_; + _setTotalShares(_getTotalShares() + amount_); + _getShares()[recipient_] = _getShares()[recipient_] + amount_; emit Transfer(address(0), recipient_, amount_); - return totalShares; + return _getTotalShares(); } /// @dev Destroys amount_ shares from account_, reducing the total shares supply. @@ -315,12 +319,12 @@ contract ERC20Rebasable is IERC20, IERC20Wrapable, IERC20BridgedShares, ERC20Met address account_, uint256 amount_ ) internal onlyNonZeroAccount(account_) returns (uint256) { - uint256 accountShares = shares[account_]; + uint256 accountShares = _getShares()[account_]; if (accountShares < amount_) revert ErrorNotEnoughBalance(); - totalShares = totalShares - amount_; - shares[account_] = accountShares - amount_; + _setTotalShares(_getTotalShares() - amount_); + _getShares()[account_] = accountShares - amount_; emit Transfer(account_, address(0), amount_); - return totalShares; + return _getTotalShares(); } /// @dev Moves `sharesAmount_` shares from `sender_` to `recipient_`. @@ -335,11 +339,11 @@ contract ERC20Rebasable is IERC20, IERC20Wrapable, IERC20BridgedShares, ERC20Met if (recipient_ == address(this)) revert ErrorTrasferToRebasableContract(); - uint256 currentSenderShares = shares[sender_]; + uint256 currentSenderShares = _getShares()[sender_]; if (sharesAmount_ > currentSenderShares) revert ErrorNotEnoughBalance(); - shares[sender_] = currentSenderShares - sharesAmount_; - shares[recipient_] = shares[recipient_] + sharesAmount_; + _getShares()[sender_] = currentSenderShares - sharesAmount_; + _getShares()[recipient_] = _getShares()[recipient_] + sharesAmount_; } /// @dev validates that account_ is not zero address @@ -349,4 +353,25 @@ contract ERC20Rebasable is IERC20, IERC20Wrapable, IERC20BridgedShares, ERC20Met } _; } + + /// @dev Validates that sender of the transaction is the bridge + modifier onlyBridge() { + if (msg.sender != BRIDGE) { + revert ErrorNotBridge(); + } + _; + } + + error ErrorZeroSharesWrap(); + error ErrorZeroTokensUnwrap(); + error ErrorInvalidRateDecimals(uint8); + error ErrorWrongOracleUpdateTime(); + error ErrorOracleAnswerIsNegative(); + error ErrorTrasferToRebasableContract(); + error ErrorNotEnoughBalance(); + error ErrorNotEnoughAllowance(); + error ErrorAccountIsZeroAddress(); + error ErrorDecreasedAllowanceBelowZero(); + error ErrorNotBridge(); + error ErrorERC20Transfer(); } \ No newline at end of file diff --git a/contracts/token/UnstructuredRefStorage.sol b/contracts/token/UnstructuredRefStorage.sol new file mode 100644 index 00000000..f4657639 --- /dev/null +++ b/contracts/token/UnstructuredRefStorage.sol @@ -0,0 +1,18 @@ +// SPDX-FileCopyrightText: 2024 Lido +// SPDX-License-Identifier: GPL-3.0 + +pragma solidity 0.8.10; + +library UnstructuredRefStorage { + function storageMapAddressMapAddressUint256(bytes32 _position) internal pure returns ( + mapping(address => mapping(address => uint256)) storage result + ) { + assembly { result.slot := _position } + } + + function storageMapAddressAddressUint256(bytes32 _position) internal pure returns ( + mapping(address => uint256) storage result + ) { + assembly { result.slot := _position } + } +} \ No newline at end of file diff --git a/contracts/token/UnstructuredStorage.sol b/contracts/token/UnstructuredStorage.sol new file mode 100644 index 00000000..058d1ed3 --- /dev/null +++ b/contracts/token/UnstructuredStorage.sol @@ -0,0 +1,38 @@ +// SPDX-FileCopyrightText: 2024 Lido +// SPDX-License-Identifier: GPL-3.0 + +pragma solidity 0.8.10; + +library UnstructuredStorage { + function getStorageBool(bytes32 position) internal view returns (bool data) { + assembly { data := sload(position) } + } + + function getStorageAddress(bytes32 position) internal view returns (address data) { + assembly { data := sload(position) } + } + + function getStorageBytes32(bytes32 position) internal view returns (bytes32 data) { + assembly { data := sload(position) } + } + + function getStorageUint256(bytes32 position) internal view returns (uint256 data) { + assembly { data := sload(position) } + } + + function setStorageBool(bytes32 position, bool data) internal { + assembly { sstore(position, data) } + } + + function setStorageAddress(bytes32 position, address data) internal { + assembly { sstore(position, data) } + } + + function setStorageBytes32(bytes32 position, bytes32 data) internal { + assembly { sstore(position, data) } + } + + function setStorageUint256(bytes32 position, uint256 data) internal { + assembly { sstore(position, data) } + } +} \ No newline at end of file diff --git a/contracts/token/interfaces/IERC20BridgedShares.sol b/contracts/token/interfaces/IERC20BridgedShares.sol index 11f3ba78..2b95cb1a 100644 --- a/contracts/token/interfaces/IERC20BridgedShares.sol +++ b/contracts/token/interfaces/IERC20BridgedShares.sol @@ -9,7 +9,7 @@ import {IERC20} from "@openzeppelin/contracts/token/ERC20/IERC20.sol"; /// @notice Extends the ERC20 functionality that allows the bridge to mint/burn tokens interface IERC20BridgedShares is IERC20 { /// @notice Returns bridge which can mint and burn tokens on L2 - function bridge() external view returns (address); + function BRIDGE() external view returns (address); /// @notice Creates amount_ tokens and assigns them to account_, increasing the total supply /// @param account_ An address of the account to mint tokens diff --git a/contracts/token/interfaces/IERC20Wrapable.sol b/contracts/token/interfaces/IERC20Wrappable.sol similarity index 86% rename from contracts/token/interfaces/IERC20Wrapable.sol rename to contracts/token/interfaces/IERC20Wrappable.sol index 0fb2d2dc..809e3c82 100644 --- a/contracts/token/interfaces/IERC20Wrapable.sol +++ b/contracts/token/interfaces/IERC20Wrappable.sol @@ -5,7 +5,7 @@ pragma solidity 0.8.10; /// @author kovalgek /// @notice Extends the ERC20 functionality that allows the bridge to mint/burn tokens -interface IERC20Wrapable { +interface IERC20Wrappable { /** * @notice Exchanges wstETH to stETH @@ -22,13 +22,13 @@ interface IERC20Wrapable { /** * @notice Exchanges stETH to wstETH - * @param wrapableTokenAmount_ amount of stETH to uwrap in exchange for wstETH + * @param wrappableTokenAmount_ amount of stETH to uwrap in exchange for wstETH * @dev Requirements: * - `stETHAmount_` must be non-zero * - msg.sender must have at least `stETHAmount_` stETH. * @return Amount of wstETH user receives after unwrap */ - function unwrap(uint256 wrapableTokenAmount_) external returns (uint256); + function unwrap(uint256 wrappableTokenAmount_) external returns (uint256); /** * @notice Get amount of wstETH for a one stETH diff --git a/test/bridge-executor/optimism.integration.test.ts b/test/bridge-executor/optimism.integration.test.ts index ab28543e..ab3bc69d 100644 --- a/test/bridge-executor/optimism.integration.test.ts +++ b/test/bridge-executor/optimism.integration.test.ts @@ -7,6 +7,7 @@ import { ERC20Bridged__factory, ERC20Rebasable__factory, TokenRateOracle__factory, + ERC20WrappableStub__factory, } from "../../typechain"; import { wei } from "../../utils/wei"; import optimism from "../../utils/optimism"; @@ -214,13 +215,12 @@ async function ctxFactory() { "TT" ); - const l1TokenRebasable = await new ERC20Rebasable__factory(l1Deployer).deploy( - "Test Token Rebasable", - "TTR" + const l1TokenRebasable = await new ERC20WrappableStub__factory(l1Deployer).deploy( + l1Token.address, + "Test Token", + "TT" ); - const tokenRateOracleStub = await new TokenRateOracle__factory(l2Deployer).deploy(); - const optAddresses = optimism.addresses(networkName); const govBridgeExecutor = testingOnDeployedContracts diff --git a/test/optimism/TokenRateOracle.unit.test.ts b/test/optimism/TokenRateOracle.unit.test.ts index 95384c96..42e42cf5 100644 --- a/test/optimism/TokenRateOracle.unit.test.ts +++ b/test/optimism/TokenRateOracle.unit.test.ts @@ -9,8 +9,8 @@ unit("TokenRateOracle", ctxFactory) const { tokenRateOracle } = ctx.contracts; const { bridge, updater } = ctx.accounts; - assert.equal(await tokenRateOracle.bridge(), bridge.address); - assert.equal(await tokenRateOracle.tokenRateUpdater(), updater.address); + assert.equal(await tokenRateOracle.BRIDGE(), bridge.address); + assert.equal(await tokenRateOracle.TOKEN_RATE_UPDATER(), updater.address); assert.equalBN(await tokenRateOracle.latestAnswer(), 0); @@ -35,7 +35,7 @@ unit("TokenRateOracle", ctxFactory) const { bridge, updater, stranger } = ctx.accounts; tokenRateOracle.connect(bridge).updateRate(10, 20); tokenRateOracle.connect(updater).updateRate(10, 23); - await assert.revertsWith(tokenRateOracle.connect(stranger).updateRate(10, 40), "NotAnOwner(\""+stranger.address+"\")"); + await assert.revertsWith(tokenRateOracle.connect(stranger).updateRate(10, 40), "ErrorNotAnOwner(\""+stranger.address+"\")"); }) .test("incorrect time", async (ctx) => { @@ -43,7 +43,7 @@ unit("TokenRateOracle", ctxFactory) const { bridge } = ctx.accounts; tokenRateOracle.connect(bridge).updateRate(10, 1000); - await assert.revertsWith(tokenRateOracle.connect(bridge).updateRate(12, 20), "IncorrectRateTimestamp()"); + await assert.revertsWith(tokenRateOracle.connect(bridge).updateRate(12, 20), "ErrorIncorrectRateTimestamp()"); }) .test("state after update token rate", async (ctx) => { diff --git a/test/optimism/bridging-rebase.integration.test.ts b/test/optimism/bridging-rebase.integration.test.ts index d5664bc3..77f89921 100644 --- a/test/optimism/bridging-rebase.integration.test.ts +++ b/test/optimism/bridging-rebase.integration.test.ts @@ -7,7 +7,7 @@ import testing, { scenario } from "../../utils/testing"; import { ethers } from "hardhat"; import { BigNumber } from "ethers"; import { JsonRpcProvider } from "@ethersproject/providers"; -import { ERC20WrapableStub } from "../../typechain"; +import { ERC20WrappableStub } from "../../typechain"; scenario("Optimism :: Bridging integration test", ctxFactory) .after(async (ctx) => { @@ -442,7 +442,7 @@ scenario("Optimism :: Bridging integration test", ctxFactory) l2TokenRebasable.address, tokenHolderA.address, tokenHolderA.address, - depositAmountNonRebasable, + depositAmountRebasable, "0x", ]); @@ -700,7 +700,7 @@ scenario("Optimism :: Bridging integration test", ctxFactory) l2TokenRebasable.address, tokenHolderA.address, tokenHolderB.address, - depositAmountNonRebasable, + depositAmountRebasable, "0x", ]); @@ -917,7 +917,7 @@ async function ctxFactory() { }; } -async function packedTokenRateAndTimestamp(l1Provider: JsonRpcProvider, l1Token: ERC20WrapableStub) { +async function packedTokenRateAndTimestamp(l1Provider: JsonRpcProvider, l1Token: ERC20WrappableStub) { const stETHPerToken = await l1Token.stETHPerToken(); const blockNumber = await l1Provider.getBlockNumber(); const blockTimestamp = (await l1Provider.getBlock(blockNumber)).timestamp; diff --git a/test/token/ERC20Rebasable.unit.test.ts b/test/token/ERC20Rebasable.unit.test.ts index f429ebbd..a7049144 100644 --- a/test/token/ERC20Rebasable.unit.test.ts +++ b/test/token/ERC20Rebasable.unit.test.ts @@ -87,7 +87,7 @@ unit("ERC20Rebasable", ctxFactory) await assert.revertsWith(rebasableProxied.connect(user1).unwrap(0), "ErrorZeroTokensUnwrap()"); }) - .test("wrap() positive scenario", async (ctx) => { + .test("wrap() happy path", async (ctx) => { const { rebasableProxied, wrappedTokenStub } = ctx.contracts; const {user1, user2 } = ctx.accounts; @@ -170,7 +170,7 @@ unit("ERC20Rebasable", ctxFactory) await assert.revertsWith(rebasableProxied.connect(user1).wrap(21), "ErrorOracleAnswerIsNegative()"); }) - .test("unwrap() positive scenario", async (ctx) => { + .test("unwrap() happy path", async (ctx) => { const { rebasableProxied, wrappedTokenStub } = ctx.contracts; const {user1, user2 } = ctx.accounts; @@ -261,7 +261,7 @@ unit("ERC20Rebasable", ctxFactory) await assert.revertsWith(rebasableProxied.connect(user1).unwrap(wei`4 ether`), "ErrorNotEnoughBalance()"); }) - .test("mintShares() positive scenario", async (ctx) => { + .test("mintShares() happy path", async (ctx) => { const { rebasableProxied } = ctx.contracts; const {user1, user2, owner } = ctx.accounts; @@ -308,9 +308,9 @@ unit("ERC20Rebasable", ctxFactory) assert.equalBN(await rebasableProxied.totalSupply(), premintTokens.add(user1TokensMinted).add(user2TokensMinted)); }) - .test("burnShares() positive scenario", async (ctx) => { + .test("burnShares() happy path", async (ctx) => { - const { rebasableProxied, tokenRateOracleStub } = ctx.contracts; + const { rebasableProxied } = ctx.contracts; const {user1, user2, owner } = ctx.accounts; const { rate, decimals, premintShares, premintTokens } = ctx.constants; diff --git a/utils/optimism/testing.ts b/utils/optimism/testing.ts index 1da87bb5..bc0380af 100644 --- a/utils/optimism/testing.ts +++ b/utils/optimism/testing.ts @@ -9,7 +9,7 @@ import { L2ERC20TokenBridge, ERC20Bridged__factory, ERC20BridgedStub__factory, - ERC20WrapableStub__factory, + ERC20WrappableStub__factory, TokenRateOracle__factory, L1ERC20TokenBridge__factory, L2ERC20TokenBridge__factory, @@ -156,7 +156,7 @@ async function loadDeployedBridges( l2SignerOrProvider: SignerOrProvider ) { return { - l1Token: ERC20WrapableStub__factory.connect( + l1Token: ERC20WrappableStub__factory.connect( testingUtils.env.OPT_L1_TOKEN(), l1SignerOrProvider ), @@ -192,7 +192,7 @@ async function deployTestBridge( "TTR" ); - const l1Token = await new ERC20WrapableStub__factory(ethDeployer).deploy( + const l1Token = await new ERC20WrappableStub__factory(ethDeployer).deploy( l1TokenRebasable.address, "Test Token", "TT" From 76b4ff3e3077c51d5af05614c6f11a26e81e373b Mon Sep 17 00:00:00 2001 From: Anton Kovalchuk Date: Mon, 29 Jan 2024 15:45:33 +0100 Subject: [PATCH 024/148] PR fixes: fix comments, rename interface, abstract wst --- .editorconfig | 15 ++++ contracts/optimism/L1ERC20TokenBridge.sol | 32 ++++---- contracts/optimism/L2ERC20TokenBridge.sol | 18 ++--- ...sol => RebasableAndNonRebasableTokens.sol} | 2 +- contracts/optimism/TokenRateOracle.sol | 52 +++++++------ ...WrappableStub.sol => ERC20WrapperStub.sol} | 4 +- contracts/token/ERC20Rebasable.sol | 41 +++++----- .../token/L1TokenNonRebasableAdapter.sol | 23 ++++++ .../token/interfaces/IERC20BridgedShares.sol | 18 ++--- .../token/interfaces/IERC20TokenRate.sol | 12 +++ .../token/interfaces/IERC20Wrappable.sol | 38 ---------- contracts/token/interfaces/IERC20Wrapper.sol | 19 +++++ contracts/token/interfaces/IERC20WstETH.sol | 14 ++++ .../optimism.integration.test.ts | 4 +- test/optimism/L2ERC20TokenBridge.unit.test.ts | 6 +- test/optimism/TokenRateOracle.unit.test.ts | 14 ++-- .../bridging-rebase.integration.test.ts | 46 ++++++------ test/token/ERC20Rebasable.unit.test.ts | 74 +++++++++---------- utils/optimism/testing.ts | 6 +- 19 files changed, 236 insertions(+), 202 deletions(-) create mode 100644 .editorconfig rename contracts/optimism/{BridgeableTokensOptimism.sol => RebasableAndNonRebasableTokens.sol} (98%) rename contracts/stubs/{ERC20WrappableStub.sol => ERC20WrapperStub.sol} (92%) create mode 100644 contracts/token/L1TokenNonRebasableAdapter.sol create mode 100644 contracts/token/interfaces/IERC20TokenRate.sol delete mode 100644 contracts/token/interfaces/IERC20Wrappable.sol create mode 100644 contracts/token/interfaces/IERC20Wrapper.sol create mode 100644 contracts/token/interfaces/IERC20WstETH.sol diff --git a/.editorconfig b/.editorconfig new file mode 100644 index 00000000..99a93420 --- /dev/null +++ b/.editorconfig @@ -0,0 +1,15 @@ +# top-most EditorConfig file +root = true + +# Unix-style newlines with a newline ending every file +[*] +end_of_line = lf +insert_final_newline = true +trim_trailing_whitespace = true +charset = utf-8 +indent_style = space +indent_size = 4 + +[*.{js,yml,json,cjs}] +indent_size = 2 +max_line_length = 120 diff --git a/contracts/optimism/L1ERC20TokenBridge.sol b/contracts/optimism/L1ERC20TokenBridge.sol index 1948040c..61cdb73a 100644 --- a/contracts/optimism/L1ERC20TokenBridge.sol +++ b/contracts/optimism/L1ERC20TokenBridge.sol @@ -9,14 +9,13 @@ import {SafeERC20} from "@openzeppelin/contracts/token/ERC20/utils/SafeERC20.sol import {IL1ERC20Bridge} from "./interfaces/IL1ERC20Bridge.sol"; import {IL2ERC20Bridge} from "./interfaces/IL2ERC20Bridge.sol"; - +import {IERC20Wrapper} from "../token/interfaces/IERC20Wrapper.sol"; +import {L1TokenNonRebasableAdapter} from "../token/L1TokenNonRebasableAdapter.sol"; import {BridgingManager} from "../BridgingManager.sol"; -import {BridgeableTokensOptimism} from "./BridgeableTokensOptimism.sol"; +import {RebasableAndNonRebasableTokens} from "./RebasableAndNonRebasableTokens.sol"; import {CrossDomainEnabled} from "./CrossDomainEnabled.sol"; import {DepositDataCodec} from "./DepositDataCodec.sol"; -import {IERC20Wrappable} from "../token/interfaces/IERC20Wrappable.sol"; - /// @author psirex, kovalgek /// @notice The L1 ERC20 token bridge locks bridged tokens on the L1 side, sends deposit messages /// on the L2 side, and finalizes token withdrawals from L2. Additionally, adds the methods for @@ -24,7 +23,7 @@ import {IERC20Wrappable} from "../token/interfaces/IERC20Wrappable.sol"; contract L1ERC20TokenBridge is IL1ERC20Bridge, BridgingManager, - BridgeableTokensOptimism, + RebasableAndNonRebasableTokens, CrossDomainEnabled, DepositDataCodec { @@ -32,6 +31,8 @@ contract L1ERC20TokenBridge is address public immutable L2_TOKEN_BRIDGE; + L1TokenNonRebasableAdapter public immutable L1_TOKEN_NON_REBASABLE_ADAPTER; + /// @param messenger_ L1 messenger address being used for cross-chain communications /// @param l2TokenBridge_ Address of the corresponding L2 bridge /// @param l1TokenNonRebasable_ Address of the bridged token in the L1 chain @@ -45,8 +46,9 @@ contract L1ERC20TokenBridge is address l1TokenRebasable_, address l2TokenNonRebasable_, address l2TokenRebasable_ - ) CrossDomainEnabled(messenger_) BridgeableTokensOptimism(l1TokenNonRebasable_, l1TokenRebasable_, l2TokenNonRebasable_, l2TokenRebasable_) { + ) CrossDomainEnabled(messenger_) RebasableAndNonRebasableTokens(l1TokenNonRebasable_, l1TokenRebasable_, l2TokenNonRebasable_, l2TokenRebasable_) { L2_TOKEN_BRIDGE = l2TokenBridge_; + L1_TOKEN_NON_REBASABLE_ADAPTER = new L1TokenNonRebasableAdapter(l1TokenNonRebasable_); } /// @notice Pushes token rate to L2 by depositing zero tokens. @@ -114,20 +116,13 @@ contract L1ERC20TokenBridge is onlyFromCrossDomainAccount(L2_TOKEN_BRIDGE) { if (isRebasableTokenFlow(l1Token_, l2Token_)) { - uint256 stETHAmount = IERC20Wrappable(L1_TOKEN_NON_REBASABLE).unwrap(amount_); + uint256 stETHAmount = IERC20Wrapper(L1_TOKEN_NON_REBASABLE).unwrap(amount_); IERC20(L1_TOKEN_REBASABLE).safeTransfer(to_, stETHAmount); + emit ERC20WithdrawalFinalized(l1Token_, l2Token_, from_, to_, amount_, data_); } else if (isNonRebasableTokenFlow(l1Token_, l2Token_)) { IERC20(L1_TOKEN_NON_REBASABLE).safeTransfer(to_, amount_); + emit ERC20WithdrawalFinalized(l1Token_, l2Token_, from_, to_, amount_, data_); } - - emit ERC20WithdrawalFinalized( - l1Token_, - l2Token_, - from_, - to_, - amount_, - data_ - ); } function _depositERC20To( @@ -141,14 +136,13 @@ contract L1ERC20TokenBridge is if (isRebasableTokenFlow(l1Token_, l2Token_)) { DepositData memory depositData = DepositData({ - rate: uint96(IERC20Wrappable(L1_TOKEN_NON_REBASABLE).stETHPerToken()), + rate: uint96(L1_TOKEN_NON_REBASABLE_ADAPTER.tokenRate()), timestamp: uint40(block.timestamp), data: data_ }); bytes memory encodedDepositData = encodeDepositData(depositData); - // probably need to add a new method for amount zero if (amount_ == 0) { _initiateERC20Deposit(l1Token_, l2Token_, msg.sender, to_, amount_, l2Gas_, encodedDepositData); return; @@ -159,7 +153,7 @@ contract L1ERC20TokenBridge is if(!IERC20(L1_TOKEN_REBASABLE).approve(L1_TOKEN_NON_REBASABLE, amount_)) revert ErrorRebasableTokenApprove(); // when 1 wei wasnt't transfer, can this wrap be failed? - uint256 wstETHAmount = IERC20Wrappable(L1_TOKEN_NON_REBASABLE).wrap(amount_); + uint256 wstETHAmount = IERC20Wrapper(L1_TOKEN_NON_REBASABLE).wrap(amount_); _initiateERC20Deposit(L1_TOKEN_REBASABLE, L2_TOKEN_REBASABLE, msg.sender, to_, wstETHAmount, l2Gas_, encodedDepositData); } else if (isNonRebasableTokenFlow(l1Token_, l2Token_)) { diff --git a/contracts/optimism/L2ERC20TokenBridge.sol b/contracts/optimism/L2ERC20TokenBridge.sol index a5cfb46d..f939eb62 100644 --- a/contracts/optimism/L2ERC20TokenBridge.sol +++ b/contracts/optimism/L2ERC20TokenBridge.sol @@ -3,17 +3,18 @@ pragma solidity 0.8.10; +import {IERC20} from "@openzeppelin/contracts/token/ERC20/IERC20.sol"; +import {SafeERC20} from "@openzeppelin/contracts/token/ERC20/utils/SafeERC20.sol"; + import {IL1ERC20Bridge} from "./interfaces/IL1ERC20Bridge.sol"; import {IL2ERC20Bridge} from "./interfaces/IL2ERC20Bridge.sol"; import {IERC20Bridged} from "../token/interfaces/IERC20Bridged.sol"; import {ITokenRateOracle} from "../token/interfaces/ITokenRateOracle.sol"; -import {ERC20Rebasable} from "../token/ERC20Rebasable.sol"; -import {IERC20Wrappable} from "../token/interfaces/IERC20Wrappable.sol"; -import {IERC20} from "@openzeppelin/contracts/token/ERC20/IERC20.sol"; -import {SafeERC20} from "@openzeppelin/contracts/token/ERC20/utils/SafeERC20.sol"; +import {IERC20Wrapper} from "../token/interfaces/IERC20Wrapper.sol"; +import {ERC20Rebasable} from "../token/ERC20Rebasable.sol"; import {BridgingManager} from "../BridgingManager.sol"; -import {BridgeableTokensOptimism} from "./BridgeableTokensOptimism.sol"; +import {RebasableAndNonRebasableTokens} from "./RebasableAndNonRebasableTokens.sol"; import {CrossDomainEnabled} from "./CrossDomainEnabled.sol"; import {DepositDataCodec} from "./DepositDataCodec.sol"; @@ -26,7 +27,7 @@ import {DepositDataCodec} from "./DepositDataCodec.sol"; contract L2ERC20TokenBridge is IL2ERC20Bridge, BridgingManager, - BridgeableTokensOptimism, + RebasableAndNonRebasableTokens, CrossDomainEnabled, DepositDataCodec { @@ -47,7 +48,7 @@ contract L2ERC20TokenBridge is address l1TokenRebasable_, address l2TokenNonRebasable_, address l2TokenRebasable_ - ) CrossDomainEnabled(messenger_) BridgeableTokensOptimism(l1TokenNonRebasable_, l1TokenRebasable_, l2TokenNonRebasable_, l2TokenRebasable_) { + ) CrossDomainEnabled(messenger_) RebasableAndNonRebasableTokens(l1TokenNonRebasable_, l1TokenRebasable_, l2TokenNonRebasable_, l2TokenRebasable_) { L1_TOKEN_BRIDGE = l1TokenBridge_; } @@ -96,7 +97,6 @@ contract L2ERC20TokenBridge is DepositData memory depositData = decodeDepositData(data_); ITokenRateOracle tokenRateOracle = ERC20Rebasable(L2_TOKEN_REBASABLE).TOKEN_RATE_ORACLE(); tokenRateOracle.updateRate(depositData.rate, depositData.timestamp); - //slither-disable-next-line unused-return ERC20Rebasable(L2_TOKEN_REBASABLE).mintShares(to_, amount_); uint256 rebasableTokenAmount = ERC20Rebasable(L2_TOKEN_REBASABLE).getTokensByShares(amount_); emit DepositFinalized(l1Token_, l2Token_, from_, to_, rebasableTokenAmount, depositData.data); @@ -114,7 +114,7 @@ contract L2ERC20TokenBridge is bytes calldata data_ ) internal { if (l2Token_ == L2_TOKEN_REBASABLE) { - // maybe loosing 1 wei here as well + // TODO: maybe loosing 1 wei here as well uint256 shares = ERC20Rebasable(L2_TOKEN_REBASABLE).getSharesByTokens(amount_); ERC20Rebasable(L2_TOKEN_REBASABLE).burnShares(msg.sender, shares); _initiateWithdrawal(L2_TOKEN_REBASABLE, L2_TOKEN_REBASABLE, msg.sender, to_, shares, l1Gas_, data_); diff --git a/contracts/optimism/BridgeableTokensOptimism.sol b/contracts/optimism/RebasableAndNonRebasableTokens.sol similarity index 98% rename from contracts/optimism/BridgeableTokensOptimism.sol rename to contracts/optimism/RebasableAndNonRebasableTokens.sol index 6bf417a3..a7cca519 100644 --- a/contracts/optimism/BridgeableTokensOptimism.sol +++ b/contracts/optimism/RebasableAndNonRebasableTokens.sol @@ -5,7 +5,7 @@ pragma solidity 0.8.10; /// @author psirex /// @notice Contains the logic for validation of tokens used in the bridging process -contract BridgeableTokensOptimism { +contract RebasableAndNonRebasableTokens { /// @notice Address of the bridged non rebasable token in the L1 chain address public immutable L1_TOKEN_NON_REBASABLE; diff --git a/contracts/optimism/TokenRateOracle.sol b/contracts/optimism/TokenRateOracle.sol index e9a1290d..b01030d1 100644 --- a/contracts/optimism/TokenRateOracle.sol +++ b/contracts/optimism/TokenRateOracle.sol @@ -9,12 +9,6 @@ import {ITokenRateOracle} from "../token/interfaces/ITokenRateOracle.sol"; /// @notice Oracle for storing token rate. contract TokenRateOracle is ITokenRateOracle { - /// @notice wstETH/stETH token rate. - uint256 private tokenRate; - - /// @notice L1 time when token rate was pushed. - uint256 private rateL1Timestamp; - /// @notice A bridge which can update oracle. address public immutable BRIDGE; @@ -22,15 +16,24 @@ contract TokenRateOracle is ITokenRateOracle { address public immutable TOKEN_RATE_UPDATER; /// @notice A time period when token rate can be considered outdated. - uint256 public immutable RATE_VALIDITY_PERIOD; + uint256 public immutable RATE_OUTDATED_DELAY; + + /// @notice wstETH/stETH token rate. + uint256 private tokenRate; + + /// @notice L1 time when token rate was pushed. + uint256 private rateL1Timestamp; + + /// @notice Decimals of the oracle response. + uint8 private constant DECIMALS = 18; /// @param bridge_ the bridge address that has a right to updates oracle. /// @param tokenRateUpdater_ address of oracle updater that has a right to updates oracle. - /// @param rateValidityPeriod_ time period when token rate can be considered outdated. - constructor(address bridge_, address tokenRateUpdater_, uint256 rateValidityPeriod_) { + /// @param rateOutdatedDelay_ time period when token rate can be considered outdated. + constructor(address bridge_, address tokenRateUpdater_, uint256 rateOutdatedDelay_) { BRIDGE = bridge_; TOKEN_RATE_UPDATER = tokenRateUpdater_; - RATE_VALIDITY_PERIOD = rateValidityPeriod_; + RATE_OUTDATED_DELAY = rateOutdatedDelay_; } /// @inheritdoc ITokenRateOracle @@ -59,32 +62,37 @@ contract TokenRateOracle is ITokenRateOracle { /// @inheritdoc ITokenRateOracle function decimals() external pure returns (uint8) { - return 18; + return DECIMALS; } /// @inheritdoc ITokenRateOracle - function updateRate(uint256 tokenRate_, uint256 rateL1Timestamp_) external onlyOwner { - // reject rates from the future + function updateRate(uint256 tokenRate_, uint256 rateL1Timestamp_) external { + + if (msg.sender != BRIDGE && msg.sender != TOKEN_RATE_UPDATER) { + revert ErrorNoRights(msg.sender); + } + if (rateL1Timestamp_ < rateL1Timestamp) { revert ErrorIncorrectRateTimestamp(); } + + if (tokenRate_ == tokenRate && rateL1Timestamp_ == rateL1Timestamp) { + return; + } + tokenRate = tokenRate_; rateL1Timestamp = rateL1Timestamp_; + + emit RateUpdated(tokenRate, rateL1Timestamp); } /// @notice Returns flag that shows that token rate can be considered outdated. function isLikelyOutdated() external view returns (bool) { - return block.timestamp - rateL1Timestamp > RATE_VALIDITY_PERIOD; + return block.timestamp - rateL1Timestamp > RATE_OUTDATED_DELAY; } - /// @dev validates that method called by one of the owners - modifier onlyOwner() { - if (msg.sender != BRIDGE && msg.sender != TOKEN_RATE_UPDATER) { - revert ErrorNotAnOwner(msg.sender); - } - _; - } + event RateUpdated(uint256 tokenRate_, uint256 rateL1Timestamp_); - error ErrorNotAnOwner(address caller); + error ErrorNoRights(address caller); error ErrorIncorrectRateTimestamp(); } diff --git a/contracts/stubs/ERC20WrappableStub.sol b/contracts/stubs/ERC20WrapperStub.sol similarity index 92% rename from contracts/stubs/ERC20WrappableStub.sol rename to contracts/stubs/ERC20WrapperStub.sol index f1796105..414065dd 100644 --- a/contracts/stubs/ERC20WrappableStub.sol +++ b/contracts/stubs/ERC20WrapperStub.sol @@ -6,10 +6,10 @@ pragma solidity 0.8.10; import {IERC20Bridged} from "../token/interfaces/IERC20Bridged.sol"; import {ERC20} from "@openzeppelin/contracts/token/ERC20/ERC20.sol"; import {IERC20} from "@openzeppelin/contracts/token/ERC20/IERC20.sol"; -import {IERC20Wrappable} from "../token/interfaces/IERC20Wrappable.sol"; +import {IERC20WstETH} from "../token/interfaces/IERC20WstETH.sol"; // represents wstETH on L1 -contract ERC20WrappableStub is IERC20Wrappable, ERC20 { +contract ERC20WrapperStub is IERC20WstETH, ERC20 { IERC20 public stETH; address public bridge; diff --git a/contracts/token/ERC20Rebasable.sol b/contracts/token/ERC20Rebasable.sol index 22372f5e..38bc18b3 100644 --- a/contracts/token/ERC20Rebasable.sol +++ b/contracts/token/ERC20Rebasable.sol @@ -4,7 +4,7 @@ pragma solidity 0.8.10; import {IERC20} from "@openzeppelin/contracts/token/ERC20/IERC20.sol"; -import {IERC20Wrappable} from "./interfaces/IERC20Wrappable.sol"; +import {IERC20Wrapper} from "./interfaces/IERC20Wrapper.sol"; import {IERC20BridgedShares} from "./interfaces/IERC20BridgedShares.sol"; import {ITokenRateOracle} from "./interfaces/ITokenRateOracle.sol"; import {ERC20Metadata} from "./ERC20Metadata.sol"; @@ -13,7 +13,7 @@ import {UnstructuredStorage} from "./UnstructuredStorage.sol"; /// @author kovalgek /// @notice Rebasable token that wraps/unwraps non-rebasable token and allow to mint/burn tokens by bridge. -contract ERC20Rebasable is IERC20, IERC20Wrappable, IERC20BridgedShares, ERC20Metadata { +contract ERC20Rebasable is IERC20, IERC20Wrapper, IERC20BridgedShares, ERC20Metadata { using UnstructuredRefStorage for bytes32; using UnstructuredStorage for bytes32; @@ -26,7 +26,7 @@ contract ERC20Rebasable is IERC20, IERC20Wrappable, IERC20BridgedShares, ERC20Me /// @notice Oracle contract used to get token rate for wrapping/unwrapping tokens. ITokenRateOracle public immutable TOKEN_RATE_ORACLE; - + /// @dev token allowance slot position. bytes32 internal constant TOKEN_ALLOWANCE_POSITION = keccak256("ERC20Rebasable.TOKEN_ALLOWANCE_POSITION"); @@ -63,17 +63,17 @@ contract ERC20Rebasable is IERC20, IERC20Wrappable, IERC20BridgedShares, ERC20Me _setERC20MetadataSymbol(symbol_); } - /// @inheritdoc IERC20Wrappable + /// @inheritdoc IERC20Wrapper function wrap(uint256 sharesAmount_) external returns (uint256) { if (sharesAmount_ == 0) revert ErrorZeroSharesWrap(); - + _mintShares(msg.sender, sharesAmount_); if(!WRAPPED_TOKEN.transferFrom(msg.sender, address(this), sharesAmount_)) revert ErrorERC20Transfer(); return _getTokensByShares(sharesAmount_); } - /// @inheritdoc IERC20Wrappable + /// @inheritdoc IERC20Wrapper function unwrap(uint256 tokenAmount_) external returns (uint256) { if (tokenAmount_ == 0) revert ErrorZeroTokensUnwrap(); @@ -84,14 +84,9 @@ contract ERC20Rebasable is IERC20, IERC20Wrappable, IERC20BridgedShares, ERC20Me return sharesAmount; } - /// @inheritdoc IERC20Wrappable - function stETHPerToken() external view returns (uint256) { - return uint256(TOKEN_RATE_ORACLE.latestAnswer()); - } - /// @inheritdoc IERC20BridgedShares - function mintShares(address account_, uint256 amount_) external onlyBridge returns (uint256) { - return _mintShares(account_, amount_); + function mintShares(address account_, uint256 amount_) external onlyBridge { + _mintShares(account_, amount_); } /// @inheritdoc IERC20BridgedShares @@ -206,7 +201,7 @@ contract ERC20Rebasable is IERC20, IERC20Wrappable, IERC20BridgedShares, ERC20Me function _getShares() internal pure returns (mapping(address => uint256) storage) { return SHARES_POSITION.storageMapAddressAddressUint256(); } - + /// @notice The total amount of shares in existence. function _getTotalShares() internal view returns (uint256) { return TOTAL_SHARES_POSITION.getStorageUint256(); @@ -271,19 +266,19 @@ contract ERC20Rebasable is IERC20, IERC20Wrappable, IERC20BridgedShares, ERC20Me } function _getTokensByShares(uint256 sharesAmount_) internal view returns (uint256) { - (uint256 tokensRate, uint256 decimals) = _getTokensRateAndDecimal(); + (uint256 tokensRate, uint256 decimals) = _getTokensRateAndDecimal(); return (sharesAmount_ * tokensRate) / (10 ** decimals); } function _getSharesByTokens(uint256 tokenAmount_) internal view returns (uint256) { - (uint256 tokensRate, uint256 decimals) = _getTokensRateAndDecimal(); + (uint256 tokensRate, uint256 decimals) = _getTokensRateAndDecimal(); return (tokenAmount_ * (10 ** decimals)) / tokensRate; } function _getTokensRateAndDecimal() internal view returns (uint256, uint256) { uint8 rateDecimals = TOKEN_RATE_ORACLE.decimals(); - if (rateDecimals == uint8(0) || rateDecimals > uint8(18)) revert ErrorInvalidRateDecimals(rateDecimals); + if (rateDecimals == uint8(0)) revert ErrorTokenRateDecimalsIsZero(); //slither-disable-next-line unused-return (, @@ -305,11 +300,10 @@ contract ERC20Rebasable is IERC20, IERC20Wrappable, IERC20BridgedShares, ERC20Me function _mintShares( address recipient_, uint256 amount_ - ) internal onlyNonZeroAccount(recipient_) returns (uint256) { + ) internal onlyNonZeroAccount(recipient_) { _setTotalShares(_getTotalShares() + amount_); _getShares()[recipient_] = _getShares()[recipient_] + amount_; emit Transfer(address(0), recipient_, amount_); - return _getTotalShares(); } /// @dev Destroys amount_ shares from account_, reducing the total shares supply. @@ -318,13 +312,12 @@ contract ERC20Rebasable is IERC20, IERC20Wrappable, IERC20BridgedShares, ERC20Me function _burnShares( address account_, uint256 amount_ - ) internal onlyNonZeroAccount(account_) returns (uint256) { + ) internal onlyNonZeroAccount(account_) { uint256 accountShares = _getShares()[account_]; if (accountShares < amount_) revert ErrorNotEnoughBalance(); _setTotalShares(_getTotalShares() - amount_); _getShares()[account_] = accountShares - amount_; emit Transfer(account_, address(0), amount_); - return _getTotalShares(); } /// @dev Moves `sharesAmount_` shares from `sender_` to `recipient_`. @@ -336,7 +329,7 @@ contract ERC20Rebasable is IERC20, IERC20Wrappable, IERC20BridgedShares, ERC20Me address recipient_, uint256 sharesAmount_ ) internal onlyNonZeroAccount(sender_) onlyNonZeroAccount(recipient_) { - + if (recipient_ == address(this)) revert ErrorTrasferToRebasableContract(); uint256 currentSenderShares = _getShares()[sender_]; @@ -364,7 +357,7 @@ contract ERC20Rebasable is IERC20, IERC20Wrappable, IERC20BridgedShares, ERC20Me error ErrorZeroSharesWrap(); error ErrorZeroTokensUnwrap(); - error ErrorInvalidRateDecimals(uint8); + error ErrorTokenRateDecimalsIsZero(); error ErrorWrongOracleUpdateTime(); error ErrorOracleAnswerIsNegative(); error ErrorTrasferToRebasableContract(); @@ -374,4 +367,4 @@ contract ERC20Rebasable is IERC20, IERC20Wrappable, IERC20BridgedShares, ERC20Me error ErrorDecreasedAllowanceBelowZero(); error ErrorNotBridge(); error ErrorERC20Transfer(); -} \ No newline at end of file +} diff --git a/contracts/token/L1TokenNonRebasableAdapter.sol b/contracts/token/L1TokenNonRebasableAdapter.sol new file mode 100644 index 00000000..1d943853 --- /dev/null +++ b/contracts/token/L1TokenNonRebasableAdapter.sol @@ -0,0 +1,23 @@ +// SPDX-FileCopyrightText: 2022 Lido +// SPDX-License-Identifier: GPL-3.0 + +pragma solidity 0.8.10; + +import {IERC20TokenRate} from "../token/interfaces/IERC20TokenRate.sol"; +import {IERC20WstETH} from "../token/interfaces/IERC20WstETH.sol"; + +/// @author kovalgek +/// @notice Hides wstETH concept from other contracts to save level of abstraction. +contract L1TokenNonRebasableAdapter is IERC20TokenRate { + + IERC20WstETH public immutable WSTETH; + + constructor(address wstETH_) { + WSTETH = IERC20WstETH(wstETH_); + } + + /// @inheritdoc IERC20TokenRate + function tokenRate() external view returns (uint256) { + return WSTETH.stETHPerToken(); + } +} diff --git a/contracts/token/interfaces/IERC20BridgedShares.sol b/contracts/token/interfaces/IERC20BridgedShares.sol index 2b95cb1a..df7a0d9e 100644 --- a/contracts/token/interfaces/IERC20BridgedShares.sol +++ b/contracts/token/interfaces/IERC20BridgedShares.sol @@ -6,18 +6,18 @@ pragma solidity 0.8.10; import {IERC20} from "@openzeppelin/contracts/token/ERC20/IERC20.sol"; /// @author kovalgek -/// @notice Extends the ERC20 functionality that allows the bridge to mint/burn tokens +/// @notice Extends the ERC20 functionality that allows the bridge to mint/burn shares interface IERC20BridgedShares is IERC20 { - /// @notice Returns bridge which can mint and burn tokens on L2 + /// @notice Returns bridge which can mint and burn shares on L2 function BRIDGE() external view returns (address); - /// @notice Creates amount_ tokens and assigns them to account_, increasing the total supply - /// @param account_ An address of the account to mint tokens - /// @param amount_ An amount of tokens to mint - function mintShares(address account_, uint256 amount_) external returns (uint256); + /// @notice Creates amount_ shares and assigns them to account_, increasing the total shares supply + /// @param account_ An address of the account to mint shares + /// @param amount_ An amount of shares to mint + function mintShares(address account_, uint256 amount_) external; - /// @notice Destroys amount_ tokens from account_, reducing the total supply - /// @param account_ An address of the account to burn tokens - /// @param amount_ An amount of tokens to burn + /// @notice Destroys amount_ shares from account_, reducing the total shares supply + /// @param account_ An address of the account to burn shares + /// @param amount_ An amount of shares to burn function burnShares(address account_, uint256 amount_) external; } diff --git a/contracts/token/interfaces/IERC20TokenRate.sol b/contracts/token/interfaces/IERC20TokenRate.sol new file mode 100644 index 00000000..16c51870 --- /dev/null +++ b/contracts/token/interfaces/IERC20TokenRate.sol @@ -0,0 +1,12 @@ +// SPDX-FileCopyrightText: 2023 Lido +// SPDX-License-Identifier: GPL-3.0 + +pragma solidity 0.8.10; + +/// @author kovalgek +/// @notice Token rate interface. +interface IERC20TokenRate { + + /// @notice Returns token rate. + function tokenRate() external view returns (uint256); +} diff --git a/contracts/token/interfaces/IERC20Wrappable.sol b/contracts/token/interfaces/IERC20Wrappable.sol deleted file mode 100644 index 809e3c82..00000000 --- a/contracts/token/interfaces/IERC20Wrappable.sol +++ /dev/null @@ -1,38 +0,0 @@ -// SPDX-FileCopyrightText: 2023 Lido -// SPDX-License-Identifier: GPL-3.0 - -pragma solidity 0.8.10; - -/// @author kovalgek -/// @notice Extends the ERC20 functionality that allows the bridge to mint/burn tokens -interface IERC20Wrappable { - - /** - * @notice Exchanges wstETH to stETH - * @param sharesAmount_ amount of wstETH to wrap in exchange for stETH - * @dev Requirements: - * - `wstETHAmount_` must be non-zero - * - msg.sender must approve at least `wstETHAmount_` stETH to this - * contract. - * - msg.sender must have at least `wstETHAmount_` of stETH. - * User should first approve wstETHAmount_ to the StETH contract - * @return Amount of StETH user receives after wrap - */ - function wrap(uint256 sharesAmount_) external returns (uint256); - - /** - * @notice Exchanges stETH to wstETH - * @param wrappableTokenAmount_ amount of stETH to uwrap in exchange for wstETH - * @dev Requirements: - * - `stETHAmount_` must be non-zero - * - msg.sender must have at least `stETHAmount_` stETH. - * @return Amount of wstETH user receives after unwrap - */ - function unwrap(uint256 wrappableTokenAmount_) external returns (uint256); - - /** - * @notice Get amount of wstETH for a one stETH - * @return Amount of wstETH for a 1 stETH - */ - function stETHPerToken() external view returns (uint256); -} \ No newline at end of file diff --git a/contracts/token/interfaces/IERC20Wrapper.sol b/contracts/token/interfaces/IERC20Wrapper.sol new file mode 100644 index 00000000..6b3125d4 --- /dev/null +++ b/contracts/token/interfaces/IERC20Wrapper.sol @@ -0,0 +1,19 @@ +// SPDX-FileCopyrightText: 2023 Lido +// SPDX-License-Identifier: GPL-3.0 + +pragma solidity 0.8.10; + +/// @author kovalgek +/// @notice Extends the ERC20 functionality that allows to wrap/unwrap token. +interface IERC20Wrapper { + + /// @notice Exchanges wrappable token to wrapper one. + /// @param wrappableTokenAmount_ amount of wrappable token to wrap. + /// @return Amount of wrapper token user receives after wrap. + function wrap(uint256 wrappableTokenAmount_) external returns (uint256); + + /// @notice Exchanges wrapper token to wrappable one. + /// @param wrapperTokenAmount_ amount of wrapper token to uwrap in exchange for wrappable. + /// @return Amount of wrappable token user receives after unwrap. + function unwrap(uint256 wrapperTokenAmount_) external returns (uint256); +} diff --git a/contracts/token/interfaces/IERC20WstETH.sol b/contracts/token/interfaces/IERC20WstETH.sol new file mode 100644 index 00000000..19a9badb --- /dev/null +++ b/contracts/token/interfaces/IERC20WstETH.sol @@ -0,0 +1,14 @@ +// SPDX-FileCopyrightText: 2024 Lido +// SPDX-License-Identifier: GPL-3.0 + +pragma solidity 0.8.10; + +/// @author kovalgek +/// @notice A subset of wstETH token interface of core LIDO protocol. +interface IERC20WstETH { + /** + * @notice Get amount of wstETH for a one stETH + * @return Amount of wstETH for a 1 stETH + */ + function stETHPerToken() external view returns (uint256); +} diff --git a/test/bridge-executor/optimism.integration.test.ts b/test/bridge-executor/optimism.integration.test.ts index ab3bc69d..c4ad2883 100644 --- a/test/bridge-executor/optimism.integration.test.ts +++ b/test/bridge-executor/optimism.integration.test.ts @@ -7,7 +7,7 @@ import { ERC20Bridged__factory, ERC20Rebasable__factory, TokenRateOracle__factory, - ERC20WrappableStub__factory, + ERC20WrapperStub__factory, } from "../../typechain"; import { wei } from "../../utils/wei"; import optimism from "../../utils/optimism"; @@ -215,7 +215,7 @@ async function ctxFactory() { "TT" ); - const l1TokenRebasable = await new ERC20WrappableStub__factory(l1Deployer).deploy( + const l1TokenRebasable = await new ERC20WrapperStub__factory(l1Deployer).deploy( l1Token.address, "Test Token", "TT" diff --git a/test/optimism/L2ERC20TokenBridge.unit.test.ts b/test/optimism/L2ERC20TokenBridge.unit.test.ts index 9c5eae8f..20c594c9 100644 --- a/test/optimism/L2ERC20TokenBridge.unit.test.ts +++ b/test/optimism/L2ERC20TokenBridge.unit.test.ts @@ -375,7 +375,7 @@ unit("Optimism:: L2ERC20TokenBridge", ctxFactory) .run(); async function ctxFactory() { - const [deployer, stranger, recipient, l1TokenBridgeEOA, token2] = + const [deployer, stranger, recipient, l1TokenBridgeEOA, rebasableToken] = await hre.ethers.getSigners(); const l2Messenger = await new CrossDomainMessengerStub__factory( @@ -405,9 +405,9 @@ async function ctxFactory() { l2Messenger.address, l1TokenBridgeEOA.address, l1Token.address, - token2.address, + rebasableToken.address, l2Token.address, - token2.address + rebasableToken.address ); const l2TokenBridgeProxy = await new OssifiableProxy__factory( diff --git a/test/optimism/TokenRateOracle.unit.test.ts b/test/optimism/TokenRateOracle.unit.test.ts index 42e42cf5..3150f773 100644 --- a/test/optimism/TokenRateOracle.unit.test.ts +++ b/test/optimism/TokenRateOracle.unit.test.ts @@ -30,23 +30,23 @@ unit("TokenRateOracle", ctxFactory) assert.equalBN(await tokenRateOracle.decimals(), 18); }) - .test("wrong owner", async (ctx) => { + .test("updateRate() :: no rights to call", async (ctx) => { const { tokenRateOracle } = ctx.contracts; const { bridge, updater, stranger } = ctx.accounts; tokenRateOracle.connect(bridge).updateRate(10, 20); tokenRateOracle.connect(updater).updateRate(10, 23); - await assert.revertsWith(tokenRateOracle.connect(stranger).updateRate(10, 40), "ErrorNotAnOwner(\""+stranger.address+"\")"); + await assert.revertsWith(tokenRateOracle.connect(stranger).updateRate(10, 40), "ErrorNoRights(\""+stranger.address+"\")"); }) - .test("incorrect time", async (ctx) => { + .test("updateRate() :: incorrect time", async (ctx) => { const { tokenRateOracle } = ctx.contracts; const { bridge } = ctx.accounts; - + tokenRateOracle.connect(bridge).updateRate(10, 1000); await assert.revertsWith(tokenRateOracle.connect(bridge).updateRate(12, 20), "ErrorIncorrectRateTimestamp()"); }) - .test("state after update token rate", async (ctx) => { + .test("updateRate() :: happy path", async (ctx) => { const { tokenRateOracle } = ctx.contracts; const { updater } = ctx.accounts; @@ -83,8 +83,8 @@ async function ctxFactory() { bridge.address, updater.address, 86400 - ); - + ); + return { accounts: { deployer, bridge, updater, stranger }, contracts: { tokenRateOracle } diff --git a/test/optimism/bridging-rebase.integration.test.ts b/test/optimism/bridging-rebase.integration.test.ts index 77f89921..757f6188 100644 --- a/test/optimism/bridging-rebase.integration.test.ts +++ b/test/optimism/bridging-rebase.integration.test.ts @@ -7,7 +7,7 @@ import testing, { scenario } from "../../utils/testing"; import { ethers } from "hardhat"; import { BigNumber } from "ethers"; import { JsonRpcProvider } from "@ethersproject/providers"; -import { ERC20WrappableStub } from "../../typechain"; +import { ERC20WrapperStub } from "../../typechain"; scenario("Optimism :: Bridging integration test", ctxFactory) .after(async (ctx) => { @@ -119,13 +119,13 @@ scenario("Optimism :: Bridging integration test", ctxFactory) l2ERC20TokenBridge, l1Provider } = ctx; - + const { l1Stranger } = ctx.accounts; const tokenHolderStrangerBalanceBefore = await l1TokenRebasable.balanceOf( l1Stranger.address ); - + const l1ERC20TokenBridgeBalanceBefore = await l1TokenRebasable.balanceOf( l1ERC20TokenBridge.address ); @@ -144,7 +144,7 @@ scenario("Optimism :: Bridging integration test", ctxFactory) 0, dataToSend, ]); - + const l2DepositCalldata = l2ERC20TokenBridge.interface.encodeFunctionData( "finalizeDeposit", [ @@ -156,9 +156,9 @@ scenario("Optimism :: Bridging integration test", ctxFactory) dataToSend, ] ); - + const messageNonce = await l1CrossDomainMessenger.messageNonce(); - + await assert.emits(l1CrossDomainMessenger, tx, "SentMessage", [ l2ERC20TokenBridge.address, l1ERC20TokenBridge.address, @@ -166,12 +166,12 @@ scenario("Optimism :: Bridging integration test", ctxFactory) messageNonce, 200_000, ]); - + assert.equalBN( await l1Token.balanceOf(l1ERC20TokenBridge.address), l1ERC20TokenBridgeBalanceBefore ); - + assert.equalBN( await l1TokenRebasable.balanceOf(l1Stranger.address), tokenHolderStrangerBalanceBefore @@ -190,7 +190,7 @@ scenario("Optimism :: Bridging integration test", ctxFactory) } = ctx; const { accountA: tokenHolderA } = ctx.accounts; - + await l1TokenRebasable .connect(tokenHolderA.l1Signer) .approve(l1ERC20TokenBridge.address, 0); @@ -269,16 +269,16 @@ scenario("Optimism :: Bridging integration test", ctxFactory) } = ctx; const dataToReceive = await packedTokenRateAndTimestamp(l2Provider, l1Token); - + const { accountA: tokenHolderA, l1CrossDomainMessengerAliased } = ctx.accounts; const tokenHolderABalanceBefore = await l2TokenRebasable.balanceOf( tokenHolderA.address ); - + const l2TokenRebasableTotalSupplyBefore = await l2TokenRebasable.totalSupply(); - + const tx = await l2CrossDomainMessenger .connect(l1CrossDomainMessengerAliased) .relayMessage( @@ -329,7 +329,7 @@ scenario("Optimism :: Bridging integration test", ctxFactory) } = ctx; const { accountA: tokenHolderA } = ctx.accounts; const { depositAmountNonRebasable, depositAmountRebasable } = ctx.common; - + await l1TokenRebasable .connect(tokenHolderA.l1Signer) .approve(l1ERC20TokenBridge.address, depositAmountRebasable); @@ -414,7 +414,7 @@ scenario("Optimism :: Bridging integration test", ctxFactory) const tokenHolderABalanceBefore = await l2TokenRebasable.balanceOf( tokenHolderA.address ); - + const l2TokenRebasableTotalSupplyBefore = await l2TokenRebasable.totalSupply(); const dataToReceive = await packedTokenRateAndTimestamp(l2Provider, l1Token); @@ -601,7 +601,7 @@ scenario("Optimism :: Bridging integration test", ctxFactory) 200_000, "0x" ); - + const dataToSend = await packedTokenRateAndTimestamp(l1Provider, l1Token); await assert.emits(l1ERC20TokenBridge, tx, "ERC20DepositInitiated", [ @@ -612,7 +612,7 @@ scenario("Optimism :: Bridging integration test", ctxFactory) depositAmountNonRebasable, dataToSend, ]); - + const l2DepositCalldata = l2ERC20TokenBridge.interface.encodeFunctionData( "finalizeDeposit", [ @@ -624,9 +624,9 @@ scenario("Optimism :: Bridging integration test", ctxFactory) dataToSend, ] ); - + const messageNonce = await l1CrossDomainMessenger.messageNonce(); - + await assert.emits(l1CrossDomainMessenger, tx, "SentMessage", [ l2ERC20TokenBridge.address, l1ERC20TokenBridge.address, @@ -639,7 +639,7 @@ scenario("Optimism :: Bridging integration test", ctxFactory) await l1Token.balanceOf(l1ERC20TokenBridge.address), l1ERC20TokenBridgeBalanceBefore.add(depositAmountNonRebasable) ); - + assert.equalBN( await l1TokenRebasable.balanceOf(tokenHolderA.address), // stETH tokenHolderABalanceBefore.sub(depositAmountRebasable) @@ -664,7 +664,7 @@ scenario("Optimism :: Bridging integration test", ctxFactory) } = ctx.accounts; const { exchangeRate } = ctx.common; - + const depositAmountNonRebasable = wei`0.03 ether`; const depositAmountRebasable = wei.toBigNumber(depositAmountNonRebasable).mul(exchangeRate); @@ -832,7 +832,7 @@ scenario("Optimism :: Bridging integration test", ctxFactory) async function ctxFactory() { const networkName = env.network("TESTING_OPT_NETWORK", "mainnet"); - + const { l1Provider, l2Provider, @@ -917,11 +917,11 @@ async function ctxFactory() { }; } -async function packedTokenRateAndTimestamp(l1Provider: JsonRpcProvider, l1Token: ERC20WrappableStub) { +async function packedTokenRateAndTimestamp(l1Provider: JsonRpcProvider, l1Token: ERC20WrapperStub) { const stETHPerToken = await l1Token.stETHPerToken(); const blockNumber = await l1Provider.getBlockNumber(); const blockTimestamp = (await l1Provider.getBlock(blockNumber)).timestamp; const stETHPerTokenStr = ethers.utils.hexZeroPad(stETHPerToken.toHexString(), 12); const blockTimestampStr = ethers.utils.hexZeroPad(ethers.utils.hexlify(blockTimestamp), 5); return ethers.utils.hexConcat([stETHPerTokenStr, blockTimestampStr]); -} \ No newline at end of file +} diff --git a/test/token/ERC20Rebasable.unit.test.ts b/test/token/ERC20Rebasable.unit.test.ts index a7049144..ab8e5dd1 100644 --- a/test/token/ERC20Rebasable.unit.test.ts +++ b/test/token/ERC20Rebasable.unit.test.ts @@ -8,21 +8,21 @@ import { BigNumber } from "ethers"; unit("ERC20Rebasable", ctxFactory) - .test("wrappedToken", async (ctx) => { + .test("wrappedToken() :: has the same address is in constructor", async (ctx) => { const { rebasableProxied, wrappedTokenStub } = ctx.contracts; - assert.equal(await rebasableProxied.wrappedToken(), wrappedTokenStub.address) + assert.equal(await rebasableProxied.WRAPPED_TOKEN(), wrappedTokenStub.address) }) - .test("tokenRateOracle", async (ctx) => { + .test("tokenRateOracle() :: has the same address is in constructor", async (ctx) => { const { rebasableProxied, tokenRateOracleStub } = ctx.contracts; - assert.equal(await rebasableProxied.tokenRateOracle(), tokenRateOracleStub.address) + assert.equal(await rebasableProxied.TOKEN_RATE_ORACLE(), tokenRateOracleStub.address) }) - .test("name()", async (ctx) => + .test("name() :: has the same value is in constructor", async (ctx) => assert.equal(await ctx.contracts.rebasableProxied.name(), ctx.constants.name) ) - .test("symbol()", async (ctx) => + .test("symbol() :: has the same value is in constructor", async (ctx) => assert.equal(await ctx.contracts.rebasableProxied.symbol(), ctx.constants.symbol) ) @@ -30,8 +30,8 @@ unit("ERC20Rebasable", ctxFactory) const { deployer, owner } = ctx.accounts; // deploy new implementation - const wrappedTokenStub = await new ERC20Stub__factory(deployer).deploy(); - const tokenRateOracleStub = await new TokenRateOracleStub__factory(deployer).deploy(); + const wrappedTokenStub = await new ERC20Stub__factory(deployer).deploy(); + const tokenRateOracleStub = await new TokenRateOracleStub__factory(deployer).deploy(); const rebasableTokenImpl = await new ERC20Rebasable__factory(deployer).deploy( "name", "", @@ -50,8 +50,8 @@ unit("ERC20Rebasable", ctxFactory) const { deployer, owner } = ctx.accounts; // deploy new implementation - const wrappedTokenStub = await new ERC20Stub__factory(deployer).deploy(); - const tokenRateOracleStub = await new TokenRateOracleStub__factory(deployer).deploy(); + const wrappedTokenStub = await new ERC20Stub__factory(deployer).deploy(); + const tokenRateOracleStub = await new TokenRateOracleStub__factory(deployer).deploy(); const rebasableTokenImpl = await new ERC20Rebasable__factory(deployer).deploy( "", "symbol", @@ -66,29 +66,29 @@ unit("ERC20Rebasable", ctxFactory) ); }) - .test("decimals", async (ctx) => + .test("decimals() :: has the same value is in constructor", async (ctx) => assert.equal(await ctx.contracts.rebasableProxied.decimals(), ctx.constants.decimalsToSet) ) - .test("totalShares", async (ctx) => { + .test("getTotalShares() :: returns preminted amount", async (ctx) => { const { premintShares } = ctx.constants; assert.equalBN(await ctx.contracts.rebasableProxied.getTotalShares(), premintShares); }) - .test("wrap(0)", async (ctx) => { + .test("wrap() :: revert if wrap 0 wstETH", async (ctx) => { const { rebasableProxied } = ctx.contracts; const { user1 } = ctx.accounts; await assert.revertsWith(rebasableProxied.connect(user1).wrap(0), "ErrorZeroSharesWrap()"); }) - .test("unwrap(0)", async (ctx) => { + .test("unwrap() :: revert if unwrap 0 wstETH", async (ctx) => { const { rebasableProxied } = ctx.contracts; const { user1 } = ctx.accounts; await assert.revertsWith(rebasableProxied.connect(user1).unwrap(0), "ErrorZeroTokensUnwrap()"); }) - .test("wrap() happy path", async (ctx) => { - + .test("wrap() :: happy path", async (ctx) => { + const { rebasableProxied, wrappedTokenStub } = ctx.contracts; const {user1, user2 } = ctx.accounts; const { rate, decimals, premintShares } = ctx.constants; @@ -114,7 +114,7 @@ unit("ERC20Rebasable", ctxFactory) assert.equal(await wrappedTokenStub.transferFromAddress(), user1.address); assert.equal(await wrappedTokenStub.transferFromTo(), rebasableProxied.address); assert.equalBN(await wrappedTokenStub.transferFromAmount(), user1Shares); - + // common state changes assert.equalBN(await rebasableProxied.getTotalShares(), BigNumber.from(premintShares).add(user1Shares)); assert.equalBN(await rebasableProxied.totalSupply(), totalSupply.add(user1Tokens)); @@ -140,19 +140,16 @@ unit("ERC20Rebasable", ctxFactory) assert.equalBN(await rebasableProxied.totalSupply(), totalSupply.add(user1Tokens).add(user2Tokens)); }) - .test("wrap() with wrong oracle decimals", async (ctx) => { + .test("wrap() :: wrong oracle decimals", async (ctx) => { const { rebasableProxied, tokenRateOracleStub } = ctx.contracts; const { user1 } = ctx.accounts; await tokenRateOracleStub.setDecimals(0); - await assert.revertsWith(rebasableProxied.connect(user1).wrap(23), "ErrorInvalidRateDecimals(0)"); - - await tokenRateOracleStub.setDecimals(19); - await assert.revertsWith(rebasableProxied.connect(user1).wrap(23), "ErrorInvalidRateDecimals(19)"); + await assert.revertsWith(rebasableProxied.connect(user1).wrap(23), "ErrorTokenRateDecimalsIsZero()"); }) - .test("wrap() with wrong oracle update time", async (ctx) => { + .test("wrap() :: wrong oracle update time", async (ctx) => { const { rebasableProxied, tokenRateOracleStub } = ctx.contracts; const { user1 } = ctx.accounts; @@ -161,7 +158,7 @@ unit("ERC20Rebasable", ctxFactory) await assert.revertsWith(rebasableProxied.connect(user1).wrap(5), "ErrorWrongOracleUpdateTime()"); }) - .test("wrap() with wrong oracle answer", async (ctx) => { + .test("wrap() :: wrong oracle answer", async (ctx) => { const { rebasableProxied, tokenRateOracleStub } = ctx.contracts; const { user1 } = ctx.accounts; @@ -170,7 +167,7 @@ unit("ERC20Rebasable", ctxFactory) await assert.revertsWith(rebasableProxied.connect(user1).wrap(21), "ErrorOracleAnswerIsNegative()"); }) - .test("unwrap() happy path", async (ctx) => { + .test("unwrap() :: happy path", async (ctx) => { const { rebasableProxied, wrappedTokenStub } = ctx.contracts; const {user1, user2 } = ctx.accounts; @@ -230,7 +227,7 @@ unit("ERC20Rebasable", ctxFactory) assert.equalBN(await rebasableProxied.totalSupply(), totalSupply.add(user1Tokens).add(user2Tokens)); }) - .test("unwrap() with wrong oracle decimals", async (ctx) => { + .test("unwrap() :: with wrong oracle decimals", async (ctx) => { const { rebasableProxied, tokenRateOracleStub } = ctx.contracts; const { user1 } = ctx.accounts; @@ -238,13 +235,10 @@ unit("ERC20Rebasable", ctxFactory) await rebasableProxied.connect(user1).wrap(wei`2 ether`); await tokenRateOracleStub.setDecimals(0); - await assert.revertsWith(rebasableProxied.connect(user1).unwrap(23), "ErrorInvalidRateDecimals(0)"); - - await tokenRateOracleStub.setDecimals(19); - await assert.revertsWith(rebasableProxied.connect(user1).unwrap(23), "ErrorInvalidRateDecimals(19)"); + await assert.revertsWith(rebasableProxied.connect(user1).unwrap(23), "ErrorTokenRateDecimalsIsZero()"); }) - .test("unwrap() with wrong oracle update time", async (ctx) => { + .test("unwrap() :: with wrong oracle update time", async (ctx) => { const { rebasableProxied, tokenRateOracleStub } = ctx.contracts; const { user1 } = ctx.accounts; @@ -254,14 +248,14 @@ unit("ERC20Rebasable", ctxFactory) await assert.revertsWith(rebasableProxied.connect(user1).unwrap(wei`1 ether`), "ErrorWrongOracleUpdateTime()"); }) - .test("unwrap() when no balance", async (ctx) => { + .test("unwrap() :: when no balance", async (ctx) => { const { rebasableProxied } = ctx.contracts; const { user1 } = ctx.accounts; await assert.revertsWith(rebasableProxied.connect(user1).unwrap(wei`4 ether`), "ErrorNotEnoughBalance()"); }) - .test("mintShares() happy path", async (ctx) => { + .test("mintShares() :: happy path", async (ctx) => { const { rebasableProxied } = ctx.contracts; const {user1, user2, owner } = ctx.accounts; @@ -308,7 +302,7 @@ unit("ERC20Rebasable", ctxFactory) assert.equalBN(await rebasableProxied.totalSupply(), premintTokens.add(user1TokensMinted).add(user2TokensMinted)); }) - .test("burnShares() happy path", async (ctx) => { + .test("burnShares() :: happy path", async (ctx) => { const { rebasableProxied } = ctx.contracts; const {user1, user2, owner } = ctx.accounts; @@ -367,7 +361,7 @@ unit("ERC20Rebasable", ctxFactory) assert.equalBN(await rebasableProxied.totalSupply(), premintTokens.add(user1Tokens).add(user2Tokens)); }) - .test("approve()", async (ctx) => { + .test("approve() :: happy path", async (ctx) => { const { rebasableProxied } = ctx.contracts; const { holder, spender } = ctx.accounts; @@ -453,7 +447,7 @@ unit("ERC20Rebasable", ctxFactory) ); }) - .test("transfer()", async (ctx) => { + .test("transfer() :: happy path", async (ctx) => { const { rebasableProxied } = ctx.contracts; const { premintTokens } = ctx.constants; const { recipient, holder } = ctx.accounts; @@ -485,7 +479,7 @@ unit("ERC20Rebasable", ctxFactory) assert.equalBN(await rebasableProxied.totalSupply(), premintTokens); }) - .test("transferFrom()", async (ctx) => { + .test("transferFrom() :: happy path", async (ctx) => { const { rebasableProxied } = ctx.contracts; const { premintTokens } = ctx.constants; const { recipient, holder, spender } = ctx.accounts; @@ -937,8 +931,8 @@ async function ctxFactory() { user2 ] = await hre.ethers.getSigners(); - const wrappedTokenStub = await new ERC20Stub__factory(deployer).deploy(); - const tokenRateOracleStub = await new TokenRateOracleStub__factory(deployer).deploy(); + const wrappedTokenStub = await new ERC20Stub__factory(deployer).deploy(); + const tokenRateOracleStub = await new TokenRateOracleStub__factory(deployer).deploy(); const rebasableTokenImpl = await new ERC20Rebasable__factory(deployer).deploy( name, symbol, @@ -963,7 +957,7 @@ async function ctxFactory() { symbol, ]) ); - + const rebasableProxied = ERC20Rebasable__factory.connect( l2TokensProxy.address, holder diff --git a/utils/optimism/testing.ts b/utils/optimism/testing.ts index bc0380af..a4879427 100644 --- a/utils/optimism/testing.ts +++ b/utils/optimism/testing.ts @@ -9,7 +9,7 @@ import { L2ERC20TokenBridge, ERC20Bridged__factory, ERC20BridgedStub__factory, - ERC20WrappableStub__factory, + ERC20WrapperStub__factory, TokenRateOracle__factory, L1ERC20TokenBridge__factory, L2ERC20TokenBridge__factory, @@ -156,7 +156,7 @@ async function loadDeployedBridges( l2SignerOrProvider: SignerOrProvider ) { return { - l1Token: ERC20WrappableStub__factory.connect( + l1Token: ERC20WrapperStub__factory.connect( testingUtils.env.OPT_L1_TOKEN(), l1SignerOrProvider ), @@ -192,7 +192,7 @@ async function deployTestBridge( "TTR" ); - const l1Token = await new ERC20WrappableStub__factory(ethDeployer).deploy( + const l1Token = await new ERC20WrapperStub__factory(ethDeployer).deploy( l1TokenRebasable.address, "Test Token", "TT" From 7abf60f92ba37f4d40019855e69ce60060a0b9fb Mon Sep 17 00:00:00 2001 From: Anton Kovalchuk Date: Fri, 2 Feb 2024 17:18:52 +0100 Subject: [PATCH 025/148] use real implemnations of non rebasable token and oracle contracts in rebnasable token tests --- contracts/optimism/TokenRateOracle.sol | 9 +- contracts/stubs/ERC20Stub.sol | 65 ------ contracts/stubs/TokenRateOracleStub.sol | 59 ----- contracts/token/ERC20Rebasable.sol | 10 +- test/optimism/TokenRateOracle.unit.test.ts | 15 +- test/token/ERC20Rebasable.unit.test.ts | 247 ++++++++++++++------- utils/optimism/deployment.ts | 5 +- 7 files changed, 177 insertions(+), 233 deletions(-) delete mode 100644 contracts/stubs/ERC20Stub.sol delete mode 100644 contracts/stubs/TokenRateOracleStub.sol diff --git a/contracts/optimism/TokenRateOracle.sol b/contracts/optimism/TokenRateOracle.sol index b01030d1..dae90761 100644 --- a/contracts/optimism/TokenRateOracle.sol +++ b/contracts/optimism/TokenRateOracle.sol @@ -12,9 +12,6 @@ contract TokenRateOracle is ITokenRateOracle { /// @notice A bridge which can update oracle. address public immutable BRIDGE; - /// @notice An updater which can update oracle. - address public immutable TOKEN_RATE_UPDATER; - /// @notice A time period when token rate can be considered outdated. uint256 public immutable RATE_OUTDATED_DELAY; @@ -28,11 +25,9 @@ contract TokenRateOracle is ITokenRateOracle { uint8 private constant DECIMALS = 18; /// @param bridge_ the bridge address that has a right to updates oracle. - /// @param tokenRateUpdater_ address of oracle updater that has a right to updates oracle. /// @param rateOutdatedDelay_ time period when token rate can be considered outdated. - constructor(address bridge_, address tokenRateUpdater_, uint256 rateOutdatedDelay_) { + constructor(address bridge_, uint256 rateOutdatedDelay_) { BRIDGE = bridge_; - TOKEN_RATE_UPDATER = tokenRateUpdater_; RATE_OUTDATED_DELAY = rateOutdatedDelay_; } @@ -68,7 +63,7 @@ contract TokenRateOracle is ITokenRateOracle { /// @inheritdoc ITokenRateOracle function updateRate(uint256 tokenRate_, uint256 rateL1Timestamp_) external { - if (msg.sender != BRIDGE && msg.sender != TOKEN_RATE_UPDATER) { + if (msg.sender != BRIDGE) { revert ErrorNoRights(msg.sender); } diff --git a/contracts/stubs/ERC20Stub.sol b/contracts/stubs/ERC20Stub.sol deleted file mode 100644 index b8ef5902..00000000 --- a/contracts/stubs/ERC20Stub.sol +++ /dev/null @@ -1,65 +0,0 @@ -// SPDX-FileCopyrightText: 2022 Lido -// SPDX-License-Identifier: GPL-3.0 - -pragma solidity 0.8.10; - -import {IERC20} from "@openzeppelin/contracts/token/ERC20/IERC20.sol"; - -contract ERC20Stub is IERC20 { - - uint256 totalSupply_; - uint256 balanceOf_; - bool transfer_; - uint256 allowance_; - bool approve_; - bool transferFrom_; - - constructor() { - totalSupply_ = 0; - balanceOf_ = 0; - transfer_ = true; - allowance_ = 0; - approve_ = true; - transferFrom_ = true; - } - - function totalSupply() external view returns (uint256) { - return totalSupply_; - } - - function balanceOf(address account) external view returns (uint256) { - return balanceOf_; - } - - address public transferTo; - uint256 public transferAmount; - - function transfer(address to, uint256 amount) external returns (bool) { - transferTo = to; - transferAmount = amount; - return true; - } - - function allowance(address owner, address spender) external view returns (uint256) { - return 0; - } - - function approve(address spender, uint256 amount) external returns (bool) { - return true; - } - - address public transferFromAddress; - address public transferFromTo; - uint256 public transferFromAmount; - - function transferFrom( - address from, - address to, - uint256 amount - ) external returns (bool) { - transferFromAddress = from; - transferFromTo = to; - transferFromAmount = amount; - return true; - } -} \ No newline at end of file diff --git a/contracts/stubs/TokenRateOracleStub.sol b/contracts/stubs/TokenRateOracleStub.sol deleted file mode 100644 index 25ab6763..00000000 --- a/contracts/stubs/TokenRateOracleStub.sol +++ /dev/null @@ -1,59 +0,0 @@ -// SPDX-FileCopyrightText: 2022 Lido -// SPDX-License-Identifier: GPL-3.0 - -pragma solidity 0.8.10; - -import {ITokenRateOracle} from "../token/interfaces/ITokenRateOracle.sol"; - -contract TokenRateOracleStub is ITokenRateOracle { - - uint8 public _decimals; - - function setDecimals(uint8 decimals_) external { - _decimals = decimals_; - } - - function decimals() external view returns (uint8) { - return _decimals; - } - - uint256 public latestRoundDataAnswer; - - function setLatestRoundDataAnswer(uint256 answer_) external { - latestRoundDataAnswer = answer_; - } - - uint256 public latestRoundDataUpdatedAt; - - function setUpdatedAt(uint256 updatedAt_) external { - latestRoundDataUpdatedAt = updatedAt_; - } - - function latestRoundData() - external - view - returns ( - uint80 roundId, - int256 answer, - uint256 startedAt, - uint256 updatedAt, - uint80 answeredInRound - ) { - return ( - 0, - int256(latestRoundDataAnswer), - 0, - latestRoundDataUpdatedAt, - 0 - ); - } - - function latestAnswer() external view returns (int256) { - return int256(latestRoundDataAnswer); - } - - function updateRate(uint256 tokenRate_, uint256 rateL1Timestamp_) external { - latestRoundDataAnswer = tokenRate_; - latestRoundDataUpdatedAt = rateL1Timestamp_; - } -} \ No newline at end of file diff --git a/contracts/token/ERC20Rebasable.sol b/contracts/token/ERC20Rebasable.sol index 38bc18b3..81c4eb09 100644 --- a/contracts/token/ERC20Rebasable.sol +++ b/contracts/token/ERC20Rebasable.sol @@ -266,16 +266,16 @@ contract ERC20Rebasable is IERC20, IERC20Wrapper, IERC20BridgedShares, ERC20Meta } function _getTokensByShares(uint256 sharesAmount_) internal view returns (uint256) { - (uint256 tokensRate, uint256 decimals) = _getTokensRateAndDecimal(); + (uint256 tokensRate, uint256 decimals) = _getTokenRateAndDecimal(); return (sharesAmount_ * tokensRate) / (10 ** decimals); } function _getSharesByTokens(uint256 tokenAmount_) internal view returns (uint256) { - (uint256 tokensRate, uint256 decimals) = _getTokensRateAndDecimal(); + (uint256 tokensRate, uint256 decimals) = _getTokenRateAndDecimal(); return (tokenAmount_ * (10 ** decimals)) / tokensRate; } - function _getTokensRateAndDecimal() internal view returns (uint256, uint256) { + function _getTokenRateAndDecimal() internal view returns (uint256, uint256) { uint8 rateDecimals = TOKEN_RATE_ORACLE.decimals(); if (rateDecimals == uint8(0)) revert ErrorTokenRateDecimalsIsZero(); @@ -289,7 +289,7 @@ contract ERC20Rebasable is IERC20, IERC20Wrapper, IERC20BridgedShares, ERC20Meta ,) = TOKEN_RATE_ORACLE.latestRoundData(); if (updatedAt == 0) revert ErrorWrongOracleUpdateTime(); - if (answer <= 0) revert ErrorOracleAnswerIsNegative(); + if (answer <= 0) revert ErrorOracleAnswerIsNotPositive(); return (uint256(answer), uint256(rateDecimals)); } @@ -359,7 +359,7 @@ contract ERC20Rebasable is IERC20, IERC20Wrapper, IERC20BridgedShares, ERC20Meta error ErrorZeroTokensUnwrap(); error ErrorTokenRateDecimalsIsZero(); error ErrorWrongOracleUpdateTime(); - error ErrorOracleAnswerIsNegative(); + error ErrorOracleAnswerIsNotPositive(); error ErrorTrasferToRebasableContract(); error ErrorNotEnoughBalance(); error ErrorNotEnoughAllowance(); diff --git a/test/optimism/TokenRateOracle.unit.test.ts b/test/optimism/TokenRateOracle.unit.test.ts index 3150f773..a2bde5e0 100644 --- a/test/optimism/TokenRateOracle.unit.test.ts +++ b/test/optimism/TokenRateOracle.unit.test.ts @@ -7,10 +7,9 @@ unit("TokenRateOracle", ctxFactory) .test("state after init", async (ctx) => { const { tokenRateOracle } = ctx.contracts; - const { bridge, updater } = ctx.accounts; + const { bridge } = ctx.accounts; assert.equal(await tokenRateOracle.BRIDGE(), bridge.address); - assert.equal(await tokenRateOracle.TOKEN_RATE_UPDATER(), updater.address); assert.equalBN(await tokenRateOracle.latestAnswer(), 0); @@ -32,9 +31,8 @@ unit("TokenRateOracle", ctxFactory) .test("updateRate() :: no rights to call", async (ctx) => { const { tokenRateOracle } = ctx.contracts; - const { bridge, updater, stranger } = ctx.accounts; + const { bridge, stranger } = ctx.accounts; tokenRateOracle.connect(bridge).updateRate(10, 20); - tokenRateOracle.connect(updater).updateRate(10, 23); await assert.revertsWith(tokenRateOracle.connect(stranger).updateRate(10, 40), "ErrorNoRights(\""+stranger.address+"\")"); }) @@ -48,12 +46,12 @@ unit("TokenRateOracle", ctxFactory) .test("updateRate() :: happy path", async (ctx) => { const { tokenRateOracle } = ctx.contracts; - const { updater } = ctx.accounts; + const { bridge } = ctx.accounts; const currentTime = Date.now(); const tokenRate = 123; - await tokenRateOracle.connect(updater).updateRate(tokenRate, currentTime ); + await tokenRateOracle.connect(bridge).updateRate(tokenRate, currentTime ); assert.equalBN(await tokenRateOracle.latestAnswer(), tokenRate); @@ -77,16 +75,15 @@ unit("TokenRateOracle", ctxFactory) async function ctxFactory() { - const [deployer, bridge, updater, stranger] = await hre.ethers.getSigners(); + const [deployer, bridge, stranger] = await hre.ethers.getSigners(); const tokenRateOracle = await new TokenRateOracle__factory(deployer).deploy( bridge.address, - updater.address, 86400 ); return { - accounts: { deployer, bridge, updater, stranger }, + accounts: { deployer, bridge, stranger }, contracts: { tokenRateOracle } }; } diff --git a/test/token/ERC20Rebasable.unit.test.ts b/test/token/ERC20Rebasable.unit.test.ts index ab8e5dd1..2561256c 100644 --- a/test/token/ERC20Rebasable.unit.test.ts +++ b/test/token/ERC20Rebasable.unit.test.ts @@ -3,19 +3,24 @@ import { assert } from "chai"; import { unit } from "../../utils/testing"; import { wei } from "../../utils/wei"; -import { ERC20Stub__factory, ERC20Rebasable__factory, TokenRateOracleStub__factory, OssifiableProxy__factory } from "../../typechain"; +import { + ERC20Bridged__factory, + TokenRateOracle__factory, + ERC20Rebasable__factory, + OssifiableProxy__factory +} from "../../typechain"; import { BigNumber } from "ethers"; unit("ERC20Rebasable", ctxFactory) .test("wrappedToken() :: has the same address is in constructor", async (ctx) => { - const { rebasableProxied, wrappedTokenStub } = ctx.contracts; - assert.equal(await rebasableProxied.WRAPPED_TOKEN(), wrappedTokenStub.address) + const { rebasableProxied, wrappedToken } = ctx.contracts; + assert.equal(await rebasableProxied.WRAPPED_TOKEN(), wrappedToken.address) }) .test("tokenRateOracle() :: has the same address is in constructor", async (ctx) => { - const { rebasableProxied, tokenRateOracleStub } = ctx.contracts; - assert.equal(await rebasableProxied.TOKEN_RATE_ORACLE(), tokenRateOracleStub.address) + const { rebasableProxied, tokenRateOracle } = ctx.contracts; + assert.equal(await rebasableProxied.TOKEN_RATE_ORACLE(), tokenRateOracle.address) }) .test("name() :: has the same value is in constructor", async (ctx) => @@ -28,16 +33,25 @@ unit("ERC20Rebasable", ctxFactory) .test("initialize() :: name already set", async (ctx) => { const { deployer, owner } = ctx.accounts; + const { decimalsToSet } = ctx.constants; // deploy new implementation - const wrappedTokenStub = await new ERC20Stub__factory(deployer).deploy(); - const tokenRateOracleStub = await new TokenRateOracleStub__factory(deployer).deploy(); + const wrappedToken = await new ERC20Bridged__factory(deployer).deploy( + "WsETH Test Token", + "WsETH", + decimalsToSet, + owner.address + ); + const tokenRateOracle = await new TokenRateOracle__factory(deployer).deploy( + owner.address, + 86400 + ); const rebasableTokenImpl = await new ERC20Rebasable__factory(deployer).deploy( "name", "", 10, - wrappedTokenStub.address, - tokenRateOracleStub.address, + wrappedToken.address, + tokenRateOracle.address, owner.address ); await assert.revertsWith( @@ -48,16 +62,25 @@ unit("ERC20Rebasable", ctxFactory) .test("initialize() :: symbol already set", async (ctx) => { const { deployer, owner } = ctx.accounts; + const { decimalsToSet } = ctx.constants; // deploy new implementation - const wrappedTokenStub = await new ERC20Stub__factory(deployer).deploy(); - const tokenRateOracleStub = await new TokenRateOracleStub__factory(deployer).deploy(); + const wrappedToken = await new ERC20Bridged__factory(deployer).deploy( + "WsETH Test Token", + "WsETH", + decimalsToSet, + owner.address + ); + const tokenRateOracle = await new TokenRateOracle__factory(deployer).deploy( + owner.address, + 86400 + ); const rebasableTokenImpl = await new ERC20Rebasable__factory(deployer).deploy( "", "symbol", 10, - wrappedTokenStub.address, - tokenRateOracleStub.address, + wrappedToken.address, + tokenRateOracle.address, owner.address ); await assert.revertsWith( @@ -66,7 +89,7 @@ unit("ERC20Rebasable", ctxFactory) ); }) - .test("decimals() :: has the same value is in constructor", async (ctx) => + .test("decimals() :: has the same value as is in constructor", async (ctx) => assert.equal(await ctx.contracts.rebasableProxied.decimals(), ctx.constants.decimalsToSet) ) @@ -81,39 +104,86 @@ unit("ERC20Rebasable", ctxFactory) await assert.revertsWith(rebasableProxied.connect(user1).wrap(0), "ErrorZeroSharesWrap()"); }) - .test("unwrap() :: revert if unwrap 0 wstETH", async (ctx) => { - const { rebasableProxied } = ctx.contracts; + .test("wrap() :: wrong oracle update time", async (ctx) => { + + const { deployer, user1, owner } = ctx.accounts; + const { decimalsToSet } = ctx.constants; + + // deploy new implementation to test initial oracle state + const wrappedToken = await new ERC20Bridged__factory(deployer).deploy( + "WsETH Test Token", + "WsETH", + decimalsToSet, + owner.address + ); + const tokenRateOracle = await new TokenRateOracle__factory(deployer).deploy( + owner.address, + 86400 + ); + const rebasableProxied = await new ERC20Rebasable__factory(deployer).deploy( + "", + "symbol", + 10, + wrappedToken.address, + tokenRateOracle.address, + owner.address + ); + + await wrappedToken.connect(owner).bridgeMint(user1.address, 1000); + await wrappedToken.connect(user1).approve(rebasableProxied.address, 1000); + + await assert.revertsWith(rebasableProxied.connect(user1).wrap(5), "ErrorWrongOracleUpdateTime()"); +}) + +.test("wrap() :: wrong oracle answer", async (ctx) => { + + const { rebasableProxied, wrappedToken, tokenRateOracle } = ctx.contracts; + const { user1, owner } = ctx.accounts; + + await tokenRateOracle.connect(owner).updateRate(0, 2000); + await wrappedToken.connect(owner).bridgeMint(user1.address, 1000); + await wrappedToken.connect(user1).approve(rebasableProxied.address, 1000); + + await assert.revertsWith(rebasableProxied.connect(user1).wrap(21), "ErrorOracleAnswerIsNotPositive()"); + }) + + .test("wrap() :: when no balance", async (ctx) => { + const { rebasableProxied, wrappedToken } = ctx.contracts; const { user1 } = ctx.accounts; - await assert.revertsWith(rebasableProxied.connect(user1).unwrap(0), "ErrorZeroTokensUnwrap()"); + + await wrappedToken.connect(user1).approve(rebasableProxied.address, 1000); + await assert.revertsWith(rebasableProxied.connect(user1).wrap(2), "ErrorNotEnoughBalance()"); }) .test("wrap() :: happy path", async (ctx) => { - const { rebasableProxied, wrappedTokenStub } = ctx.contracts; - const {user1, user2 } = ctx.accounts; + const { rebasableProxied, wrappedToken, tokenRateOracle } = ctx.contracts; + const {user1, user2, owner } = ctx.accounts; const { rate, decimals, premintShares } = ctx.constants; + await tokenRateOracle.connect(owner).updateRate(rate, 1000); + const totalSupply = rate.mul(premintShares).div(decimals); assert.equalBN(await rebasableProxied.getTotalShares(), premintShares); assert.equalBN(await rebasableProxied.totalSupply(), totalSupply); // user1 + const user1Shares = wei`100 ether`; + const user1Tokens = rate.mul(user1Shares).div(decimals); + assert.equalBN(await rebasableProxied.sharesOf(user1.address), 0); assert.equalBN(await rebasableProxied.balanceOf(user1.address), 0); - const user1Shares = wei`100 ether`; - const user1Tokens = rate.mul(user1Shares).div(decimals); + await wrappedToken.connect(owner).bridgeMint(user1.address, user1Tokens); + await wrappedToken.connect(user1).approve(rebasableProxied.address, user1Shares); assert.equalBN(await rebasableProxied.connect(user1).callStatic.wrap(user1Shares), user1Tokens); const tx = await rebasableProxied.connect(user1).wrap(user1Shares); assert.equalBN(await rebasableProxied.sharesOf(user1.address), user1Shares); assert.equalBN(await rebasableProxied.balanceOf(user1.address), user1Tokens); - - assert.equal(await wrappedTokenStub.transferFromAddress(), user1.address); - assert.equal(await wrappedTokenStub.transferFromTo(), rebasableProxied.address); - assert.equalBN(await wrappedTokenStub.transferFromAmount(), user1Shares); + assert.equalBN(await wrappedToken.balanceOf(rebasableProxied.address), user1Shares); // common state changes assert.equalBN(await rebasableProxied.getTotalShares(), BigNumber.from(premintShares).add(user1Shares)); @@ -126,51 +196,31 @@ unit("ERC20Rebasable", ctxFactory) const user2Shares = wei`50 ether`; const user2Tokens = rate.mul(user2Shares).div(decimals); + await wrappedToken.connect(owner).bridgeMint(user2.address, user2Tokens); + await wrappedToken.connect(user2).approve(rebasableProxied.address, user2Shares); + assert.equalBN(await rebasableProxied.connect(user2).callStatic.wrap(user2Shares), user2Tokens); const tx2 = await rebasableProxied.connect(user2).wrap(user2Shares); assert.equalBN(await rebasableProxied.sharesOf(user2.address), user2Shares); assert.equalBN(await rebasableProxied.balanceOf(user2.address), user2Tokens); - assert.equal(await wrappedTokenStub.transferFromAddress(), user2.address); - assert.equal(await wrappedTokenStub.transferFromTo(), rebasableProxied.address); - assert.equalBN(await wrappedTokenStub.transferFromAmount(), user2Shares); + assert.equalBN(await wrappedToken.balanceOf(rebasableProxied.address), BigNumber.from(user1Shares).add(user2Shares)); // common state changes assert.equalBN(await rebasableProxied.getTotalShares(), BigNumber.from(premintShares).add(user1Shares).add(user2Shares)); assert.equalBN(await rebasableProxied.totalSupply(), totalSupply.add(user1Tokens).add(user2Tokens)); }) - .test("wrap() :: wrong oracle decimals", async (ctx) => { - - const { rebasableProxied, tokenRateOracleStub } = ctx.contracts; - const { user1 } = ctx.accounts; - - await tokenRateOracleStub.setDecimals(0); - await assert.revertsWith(rebasableProxied.connect(user1).wrap(23), "ErrorTokenRateDecimalsIsZero()"); - }) - - .test("wrap() :: wrong oracle update time", async (ctx) => { - - const { rebasableProxied, tokenRateOracleStub } = ctx.contracts; - const { user1 } = ctx.accounts; - - await tokenRateOracleStub.setUpdatedAt(0); - await assert.revertsWith(rebasableProxied.connect(user1).wrap(5), "ErrorWrongOracleUpdateTime()"); - }) - - .test("wrap() :: wrong oracle answer", async (ctx) => { - - const { rebasableProxied, tokenRateOracleStub } = ctx.contracts; + .test("unwrap() :: revert if unwrap 0 wstETH", async (ctx) => { + const { rebasableProxied } = ctx.contracts; const { user1 } = ctx.accounts; - - await tokenRateOracleStub.setLatestRoundDataAnswer(0); - await assert.revertsWith(rebasableProxied.connect(user1).wrap(21), "ErrorOracleAnswerIsNegative()"); + await assert.revertsWith(rebasableProxied.connect(user1).unwrap(0), "ErrorZeroTokensUnwrap()"); }) .test("unwrap() :: happy path", async (ctx) => { - const { rebasableProxied, wrappedTokenStub } = ctx.contracts; - const {user1, user2 } = ctx.accounts; + const { rebasableProxied, wrappedToken } = ctx.contracts; + const {user1, user2, owner } = ctx.accounts; const { rate, decimals, premintShares } = ctx.constants; const totalSupply = BigNumber.from(rate).mul(premintShares).div(decimals); @@ -179,6 +229,7 @@ unit("ERC20Rebasable", ctxFactory) assert.equalBN(await rebasableProxied.totalSupply(), totalSupply); // user1 + assert.equalBN(await rebasableProxied.sharesOf(user1.address), 0); assert.equalBN(await rebasableProxied.balanceOf(user1.address), 0); @@ -189,14 +240,16 @@ unit("ERC20Rebasable", ctxFactory) const user1Shares = BigNumber.from(user1SharesToWrap).sub(user1SharesToUnwrap); const user1Tokens = BigNumber.from(rate).mul(user1Shares).div(decimals); + await wrappedToken.connect(owner).bridgeMint(user1.address, user1SharesToWrap); + await wrappedToken.connect(user1).approve(rebasableProxied.address, user1SharesToWrap); + const tx0 = await rebasableProxied.connect(user1).wrap(user1SharesToWrap); assert.equalBN(await rebasableProxied.connect(user1).callStatic.unwrap(user1TokensToUnwrap), user1SharesToUnwrap); const tx = await rebasableProxied.connect(user1).unwrap(user1TokensToUnwrap); assert.equalBN(await rebasableProxied.sharesOf(user1.address), user1Shares); assert.equalBN(await rebasableProxied.balanceOf(user1.address), user1Tokens); - assert.equal(await wrappedTokenStub.transferTo(), user1.address); - assert.equalBN(await wrappedTokenStub.transferAmount(), user1SharesToUnwrap); + assert.equalBN(await wrappedToken.balanceOf(rebasableProxied.address), user1Shares); // common state changes assert.equalBN(await rebasableProxied.getTotalShares(), premintShares.add(user1Shares)); @@ -213,39 +266,63 @@ unit("ERC20Rebasable", ctxFactory) assert.equalBN(await rebasableProxied.sharesOf(user2.address), 0); assert.equalBN(await rebasableProxied.balanceOf(user2.address), 0); + await wrappedToken.connect(owner).bridgeMint(user2.address, user2SharesToWrap); + await wrappedToken.connect(user2).approve(rebasableProxied.address, user2SharesToWrap); + await rebasableProxied.connect(user2).wrap(user2SharesToWrap); assert.equalBN(await rebasableProxied.connect(user2).callStatic.unwrap(user2TokensToUnwrap), user2SharesToUnwrap); const tx2 = await rebasableProxied.connect(user2).unwrap(user2TokensToUnwrap); assert.equalBN(await rebasableProxied.sharesOf(user2.address), user2Shares); assert.equalBN(await rebasableProxied.balanceOf(user2.address), user2Tokens); - assert.equal(await wrappedTokenStub.transferTo(), user2.address); - assert.equalBN(await wrappedTokenStub.transferAmount(), user2SharesToUnwrap); + assert.equalBN(await wrappedToken.balanceOf(rebasableProxied.address), BigNumber.from(user1Shares).add(user2Shares)); // common state changes assert.equalBN(await rebasableProxied.getTotalShares(), premintShares.add(user1Shares).add(user2Shares)); assert.equalBN(await rebasableProxied.totalSupply(), totalSupply.add(user1Tokens).add(user2Tokens)); }) - .test("unwrap() :: with wrong oracle decimals", async (ctx) => { + .test("unwrap() :: with wrong oracle update time", async (ctx) => { - const { rebasableProxied, tokenRateOracleStub } = ctx.contracts; - const { user1 } = ctx.accounts; + const { deployer, user1, owner } = ctx.accounts; + const { decimalsToSet } = ctx.constants; + + // deploy new implementation to test initial oracle state + const wrappedToken = await new ERC20Bridged__factory(deployer).deploy( + "WsETH Test Token", + "WsETH", + decimalsToSet, + owner.address + ); + const tokenRateOracle = await new TokenRateOracle__factory(deployer).deploy( + owner.address, + 86400 + ); + const rebasableProxied = await new ERC20Rebasable__factory(deployer).deploy( + "", + "symbol", + 10, + wrappedToken.address, + tokenRateOracle.address, + owner.address + ); - await rebasableProxied.connect(user1).wrap(wei`2 ether`); + await wrappedToken.connect(owner).bridgeMint(user1.address, 1000); + await wrappedToken.connect(user1).approve(rebasableProxied.address, 1000); - await tokenRateOracleStub.setDecimals(0); - await assert.revertsWith(rebasableProxied.connect(user1).unwrap(23), "ErrorTokenRateDecimalsIsZero()"); + await assert.revertsWith(rebasableProxied.connect(user1).unwrap(5), "ErrorWrongOracleUpdateTime()"); }) - .test("unwrap() :: with wrong oracle update time", async (ctx) => { +.test("unwrap() :: wrong oracle answer", async (ctx) => { - const { rebasableProxied, tokenRateOracleStub } = ctx.contracts; - const { user1 } = ctx.accounts; + const { rebasableProxied, wrappedToken, tokenRateOracle } = ctx.contracts; + const { user1, owner } = ctx.accounts; - await rebasableProxied.connect(user1).wrap(wei`6 ether`); - await tokenRateOracleStub.setUpdatedAt(0); - await assert.revertsWith(rebasableProxied.connect(user1).unwrap(wei`1 ether`), "ErrorWrongOracleUpdateTime()"); + await tokenRateOracle.connect(owner).updateRate(0, 2000); + await wrappedToken.connect(owner).bridgeMint(user1.address, 1000); + await wrappedToken.connect(user1).approve(rebasableProxied.address, 1000); + + await assert.revertsWith(rebasableProxied.connect(user1).unwrap(21), "ErrorOracleAnswerIsNotPositive()"); }) .test("unwrap() :: when no balance", async (ctx) => { @@ -271,7 +348,6 @@ unit("ERC20Rebasable", ctxFactory) assert.equalBN(await rebasableProxied.sharesOf(user1.address), 0); assert.equalBN(await rebasableProxied.balanceOf(user1.address), 0); - assert.equalBN(await rebasableProxied.connect(owner).callStatic.mintShares(user1.address, user1SharesToMint), premintShares.add(user1SharesToMint)); const tx0 = await rebasableProxied.connect(owner).mintShares(user1.address, user1SharesToMint); assert.equalBN(await rebasableProxied.sharesOf(user1.address), user1SharesToMint); @@ -288,10 +364,6 @@ unit("ERC20Rebasable", ctxFactory) assert.equalBN(await rebasableProxied.sharesOf(user2.address), 0); assert.equalBN(await rebasableProxied.balanceOf(user2.address), 0); - assert.equalBN( - await rebasableProxied.connect(owner).callStatic.mintShares(user2.address, user2SharesToMint), - premintShares.add(user1SharesToMint).add(user2SharesToMint) - ); const tx1 = await rebasableProxied.connect(owner).mintShares(user2.address, user2SharesToMint); assert.equalBN(await rebasableProxied.sharesOf(user2.address), user2SharesToMint); @@ -914,7 +986,7 @@ unit("ERC20Rebasable", ctxFactory) async function ctxFactory() { const name = "StETH Test Token"; const symbol = "StETH"; - const decimalsToSet = 16; + const decimalsToSet = 18; const decimals = BigNumber.from(10).pow(decimalsToSet); const rate = BigNumber.from('12').pow(decimalsToSet - 1); const premintShares = wei.toBigNumber(wei`100 ether`); @@ -931,14 +1003,22 @@ async function ctxFactory() { user2 ] = await hre.ethers.getSigners(); - const wrappedTokenStub = await new ERC20Stub__factory(deployer).deploy(); - const tokenRateOracleStub = await new TokenRateOracleStub__factory(deployer).deploy(); + const wrappedToken = await new ERC20Bridged__factory(deployer).deploy( + "WsETH Test Token", + "WsETH", + decimalsToSet, + owner.address + ); + const tokenRateOracle = await new TokenRateOracle__factory(deployer).deploy( + owner.address, + 86400 + ); const rebasableTokenImpl = await new ERC20Rebasable__factory(deployer).deploy( name, symbol, decimalsToSet, - wrappedTokenStub.address, - tokenRateOracleStub.address, + wrappedToken.address, + tokenRateOracle.address, owner.address ); @@ -963,15 +1043,12 @@ async function ctxFactory() { holder ); - await tokenRateOracleStub.setDecimals(decimalsToSet); - await tokenRateOracleStub.setLatestRoundDataAnswer(rate); - await tokenRateOracleStub.setUpdatedAt(1000); - + await tokenRateOracle.connect(owner).updateRate(rate, 1000); await rebasableProxied.connect(owner).mintShares(holder.address, premintShares); return { accounts: { deployer, owner, recipient, spender, holder, stranger, zero, user1, user2 }, constants: { name, symbol, decimalsToSet, decimals, premintShares, premintTokens, rate }, - contracts: { rebasableProxied, wrappedTokenStub, tokenRateOracleStub } + contracts: { rebasableProxied, wrappedToken, tokenRateOracle } }; } diff --git a/utils/optimism/deployment.ts b/utils/optimism/deployment.ts index dbbd9fba..354c6eb4 100644 --- a/utils/optimism/deployment.ts +++ b/utils/optimism/deployment.ts @@ -48,7 +48,7 @@ export default function deployment( expectedL1TokenBridgeImplAddress, expectedL1TokenBridgeProxyAddress, ] = await network.predictAddresses(l1Params.deployer, 2); - + const [ expectedL2TokenRateOracleImplAddress, expectedL2TokenImplAddress, @@ -116,7 +116,6 @@ export default function deployment( .addStep({ factory: TokenRateOracle__factory, args: [ - expectedL2TokenBridgeProxyAddress, expectedL2TokenBridgeProxyAddress, 86400, options?.overrides, @@ -124,7 +123,7 @@ export default function deployment( afterDeploy: (c) => assert.equal(c.address, expectedL2TokenRateOracleImplAddress), }) - + .addStep({ factory: ERC20Bridged__factory, args: [ From 707da9b41e755208e4407b75502bf2adca9ffff0 Mon Sep 17 00:00:00 2001 From: Anton Kovalchuk Date: Thu, 8 Feb 2024 09:59:18 +0100 Subject: [PATCH 026/148] update unit tests for bridges --- contracts/optimism/L2ERC20TokenBridge.sol | 6 +- contracts/stubs/ERC20WrapperStub.sol | 3 +- test/optimism/L1ERC20TokenBridge.unit.test.ts | 535 ++++++-- test/optimism/L2ERC20TokenBridge.unit.test.ts | 1217 +++++++++++------ test/optimism/TokenRateOracle.unit.test.ts | 11 + 5 files changed, 1258 insertions(+), 514 deletions(-) diff --git a/contracts/optimism/L2ERC20TokenBridge.sol b/contracts/optimism/L2ERC20TokenBridge.sol index f939eb62..ae195718 100644 --- a/contracts/optimism/L2ERC20TokenBridge.sol +++ b/contracts/optimism/L2ERC20TokenBridge.sol @@ -114,13 +114,17 @@ contract L2ERC20TokenBridge is bytes calldata data_ ) internal { if (l2Token_ == L2_TOKEN_REBASABLE) { + // TODO: maybe loosing 1 wei here as well uint256 shares = ERC20Rebasable(L2_TOKEN_REBASABLE).getSharesByTokens(amount_); ERC20Rebasable(L2_TOKEN_REBASABLE).burnShares(msg.sender, shares); - _initiateWithdrawal(L2_TOKEN_REBASABLE, L2_TOKEN_REBASABLE, msg.sender, to_, shares, l1Gas_, data_); + + _initiateWithdrawal(L1_TOKEN_REBASABLE, L2_TOKEN_REBASABLE, msg.sender, to_, shares, l1Gas_, data_); emit WithdrawalInitiated(L1_TOKEN_REBASABLE, L2_TOKEN_REBASABLE, msg.sender, to_, amount_, data_); } else if (l2Token_ == L2_TOKEN_NON_REBASABLE) { + IERC20Bridged(L2_TOKEN_NON_REBASABLE).bridgeBurn(msg.sender, amount_); + _initiateWithdrawal(L1_TOKEN_NON_REBASABLE, L2_TOKEN_NON_REBASABLE, msg.sender, to_, amount_, l1Gas_, data_); emit WithdrawalInitiated(L1_TOKEN_NON_REBASABLE, L2_TOKEN_NON_REBASABLE, msg.sender, to_, amount_, data_); } diff --git a/contracts/stubs/ERC20WrapperStub.sol b/contracts/stubs/ERC20WrapperStub.sol index 414065dd..2adaa9e9 100644 --- a/contracts/stubs/ERC20WrapperStub.sol +++ b/contracts/stubs/ERC20WrapperStub.sol @@ -7,9 +7,10 @@ import {IERC20Bridged} from "../token/interfaces/IERC20Bridged.sol"; import {ERC20} from "@openzeppelin/contracts/token/ERC20/ERC20.sol"; import {IERC20} from "@openzeppelin/contracts/token/ERC20/IERC20.sol"; import {IERC20WstETH} from "../token/interfaces/IERC20WstETH.sol"; +import {IERC20Wrapper} from "../token/interfaces/IERC20Wrapper.sol"; // represents wstETH on L1 -contract ERC20WrapperStub is IERC20WstETH, ERC20 { +contract ERC20WrapperStub is IERC20Wrapper, IERC20WstETH, ERC20 { IERC20 public stETH; address public bridge; diff --git a/test/optimism/L1ERC20TokenBridge.unit.test.ts b/test/optimism/L1ERC20TokenBridge.unit.test.ts index 774811c2..05236679 100644 --- a/test/optimism/L1ERC20TokenBridge.unit.test.ts +++ b/test/optimism/L1ERC20TokenBridge.unit.test.ts @@ -2,14 +2,18 @@ import { assert } from "chai"; import hre, { ethers } from "hardhat"; import { ERC20BridgedStub__factory, + ERC20WrapperStub__factory, L1ERC20TokenBridge__factory, L2ERC20TokenBridge__factory, OssifiableProxy__factory, EmptyContractStub__factory, } from "../../typechain"; +import { JsonRpcProvider } from "@ethersproject/providers"; import { CrossDomainMessengerStub__factory } from "../../typechain/factories/CrossDomainMessengerStub__factory"; import testing, { unit } from "../../utils/testing"; import { wei } from "../../utils/wei"; +import { BigNumber } from "ethers"; +import { ERC20WrapperStub } from "../../typechain"; unit("Optimism :: L1ERC20TokenBridge", ctxFactory) .test("l2TokenBridge()", async (ctx) => { @@ -26,33 +30,54 @@ unit("Optimism :: L1ERC20TokenBridge", ctxFactory) await assert.revertsWith( ctx.l1TokenBridge.depositERC20( - ctx.stubs.l1Token.address, - ctx.stubs.l2Token.address, + ctx.stubs.l1TokenNonRebasable.address, + ctx.stubs.l2TokenNonRebasable.address, wei`1 ether`, wei`1 gwei`, "0x" ), "ErrorDepositsDisabled()" ); + + await assert.revertsWith( + ctx.l1TokenBridge.depositERC20( + ctx.stubs.l1TokenRebasable.address, + ctx.stubs.l2TokenRebasable.address, + wei`1 ether`, + wei`1 gwei`, + "0x" + ), + "ErrorDepositsDisabled()" + ); }) .test("depositsERC20() :: wrong l1Token address", async (ctx) => { await assert.revertsWith( ctx.l1TokenBridge.depositERC20( ctx.accounts.stranger.address, - ctx.stubs.l2Token.address, + ctx.stubs.l2TokenNonRebasable.address, wei`1 ether`, wei`1 gwei`, "0x" ), "ErrorUnsupportedL1Token()" ); + await assert.revertsWith( + ctx.l1TokenBridge.depositERC20( + ctx.accounts.stranger.address, + ctx.stubs.l2TokenRebasable.address, + wei`1 ether`, + wei`1 gwei`, + "0x" + ), + "ErrorUnsupportedL1Token()" + ); }) .test("depositsERC20() :: wrong l2Token address", async (ctx) => { await assert.revertsWith( ctx.l1TokenBridge.depositERC20( - ctx.stubs.l1Token.address, + ctx.stubs.l1TokenNonRebasable.address, ctx.accounts.stranger.address, wei`1 ether`, wei`1 gwei`, @@ -60,6 +85,16 @@ unit("Optimism :: L1ERC20TokenBridge", ctxFactory) ), "ErrorUnsupportedL2Token()" ); + await assert.revertsWith( + ctx.l1TokenBridge.depositERC20( + ctx.stubs.l1TokenRebasable.address, + ctx.accounts.stranger.address, + wei`1 ether`, + wei`1 gwei`, + "0x" + ), + "ErrorUnsupportedL2Token()" + ); }) .test("depositERC20() :: not from EOA", async (ctx) => { @@ -67,43 +102,55 @@ unit("Optimism :: L1ERC20TokenBridge", ctxFactory) ctx.l1TokenBridge .connect(ctx.accounts.emptyContractAsEOA) .depositERC20( - ctx.stubs.l1Token.address, - ctx.stubs.l2Token.address, + ctx.stubs.l1TokenNonRebasable.address, + ctx.stubs.l2TokenNonRebasable.address, wei`1 ether`, wei`1 gwei`, "0x" ), "ErrorSenderNotEOA()" ); + await assert.revertsWith( + ctx.l1TokenBridge + .connect(ctx.accounts.emptyContractAsEOA) + .depositERC20( + ctx.stubs.l1TokenRebasable.address, + ctx.stubs.l2TokenRebasable.address, + wei`1 ether`, + wei`1 gwei`, + "0x" + ), + "ErrorSenderNotEOA()" + ); }) - .test("depositERC20()", async (ctx) => { + .test("depositERC20() :: non rebasable token flow", async (ctx) => { const { l1TokenBridge, accounts: { deployer, l2TokenBridgeEOA }, - stubs: { l1Token, l2Token, l1Messenger }, + stubs: { l1TokenNonRebasable, l2TokenNonRebasable, l1Messenger }, } = ctx; const l2Gas = wei`0.99 wei`; const amount = wei`1 ether`; const data = "0xdeadbeaf"; - await l1Token.approve(l1TokenBridge.address, amount); + await l1TokenNonRebasable.approve(l1TokenBridge.address, amount); - const deployerBalanceBefore = await l1Token.balanceOf(deployer.address); - const bridgeBalanceBefore = await l1Token.balanceOf(l1TokenBridge.address); + const deployerBalanceBefore = await l1TokenNonRebasable.balanceOf(deployer.address); + const bridgeBalanceBefore = await l1TokenNonRebasable.balanceOf(l1TokenBridge.address); const tx = await l1TokenBridge.depositERC20( - l1Token.address, - l2Token.address, + l1TokenNonRebasable.address, + l2TokenNonRebasable.address, amount, l2Gas, data ); await assert.emits(l1TokenBridge, tx, "ERC20DepositInitiated", [ - l1Token.address, - l2Token.address, + l1TokenNonRebasable.address, + l2TokenNonRebasable.address, deployer.address, deployer.address, amount, @@ -116,8 +163,8 @@ unit("Optimism :: L1ERC20TokenBridge", ctxFactory) L2ERC20TokenBridge__factory.createInterface().encodeFunctionData( "finalizeDeposit", [ - l1Token.address, - l2Token.address, + l1TokenNonRebasable.address, + l2TokenNonRebasable.address, deployer.address, deployer.address, amount, @@ -129,20 +176,89 @@ unit("Optimism :: L1ERC20TokenBridge", ctxFactory) ]); assert.equalBN( - await l1Token.balanceOf(deployer.address), + await l1TokenNonRebasable.balanceOf(deployer.address), deployerBalanceBefore.sub(amount) ); assert.equalBN( - await l1Token.balanceOf(l1TokenBridge.address), + await l1TokenNonRebasable.balanceOf(l1TokenBridge.address), bridgeBalanceBefore.add(amount) ); }) + .test("depositERC20() :: rebasable token flow", async (ctx) => { + const { + l1TokenBridge, + accounts: { deployer, l2TokenBridgeEOA }, + stubs: { l1TokenRebasable, l2TokenRebasable, l1TokenNonRebasable, l1Messenger }, + } = ctx; + + const l2Gas = wei`0.99 wei`; + const amount = wei`1 ether`; + const data = "0xdeadbeaf"; + const rate = await l1TokenNonRebasable.stETHPerToken(); + const decimalsStr = await l1TokenNonRebasable.decimals(); + const decimals = BigNumber.from(10).pow(decimalsStr); + + const amountWrapped = (wei.toBigNumber(amount)).mul(BigNumber.from(decimals)).div(rate); + const deployerBalanceBefore = await l1TokenRebasable.balanceOf(deployer.address); + const bridgeBalanceBefore = await l1TokenNonRebasable.balanceOf(l1TokenBridge.address); + + await l1TokenRebasable.approve(l1TokenBridge.address, amount); + + const tx = await l1TokenBridge.depositERC20( + l1TokenRebasable.address, + l2TokenRebasable.address, + amount, + l2Gas, + data + ); + + const packedTokenRateAndTimestampData = await packedTokenRateAndTimestamp(ctx.provider, l1TokenNonRebasable); + const dataToReceive = ethers.utils.hexConcat([packedTokenRateAndTimestampData, data]); + + await assert.emits(l1TokenBridge, tx, "ERC20DepositInitiated", [ + l1TokenRebasable.address, + l2TokenRebasable.address, + deployer.address, + deployer.address, + amountWrapped, + dataToReceive, + ]); + + await assert.emits(l1Messenger, tx, "SentMessage", [ + l2TokenBridgeEOA.address, + l1TokenBridge.address, + L2ERC20TokenBridge__factory.createInterface().encodeFunctionData( + "finalizeDeposit", + [ + l1TokenRebasable.address, + l2TokenRebasable.address, + deployer.address, + deployer.address, + amountWrapped, + dataToReceive, + ] + ), + 1, // message nonce + l2Gas, + ]); + + assert.equalBN( + await l1TokenRebasable.balanceOf(deployer.address), + deployerBalanceBefore.sub(amount) + ); + + assert.equalBN( + await l1TokenNonRebasable.balanceOf(l1TokenBridge.address), + bridgeBalanceBefore.add(amountWrapped) + ); + }) + .test("depositERC20To() :: deposits disabled", async (ctx) => { const { l1TokenBridge, - stubs: { l1Token, l2Token }, + stubs: { l1TokenNonRebasable, l2TokenNonRebasable, l1TokenRebasable, l2TokenRebasable }, accounts: { recipient }, } = ctx; await l1TokenBridge.disableDeposits(); @@ -151,8 +267,8 @@ unit("Optimism :: L1ERC20TokenBridge", ctxFactory) await assert.revertsWith( l1TokenBridge.depositERC20To( - l1Token.address, - l2Token.address, + l1TokenNonRebasable.address, + l2TokenNonRebasable.address, recipient.address, wei`1 ether`, wei`1 gwei`, @@ -160,12 +276,24 @@ unit("Optimism :: L1ERC20TokenBridge", ctxFactory) ), "ErrorDepositsDisabled()" ); + + await assert.revertsWith( + l1TokenBridge.depositERC20To( + l1TokenRebasable.address, + l2TokenRebasable.address, + recipient.address, + wei`1 ether`, + wei`1 gwei`, + "0x" + ), + "ErrorDepositsDisabled()" + ); }) .test("depositsERC20To() :: wrong l1Token address", async (ctx) => { const { l1TokenBridge, - stubs: { l2Token }, + stubs: { l2TokenNonRebasable, l2TokenRebasable }, accounts: { recipient, stranger }, } = ctx; await l1TokenBridge.disableDeposits(); @@ -175,7 +303,7 @@ unit("Optimism :: L1ERC20TokenBridge", ctxFactory) await assert.revertsWith( l1TokenBridge.depositERC20To( stranger.address, - l2Token.address, + l2TokenNonRebasable.address, recipient.address, wei`1 ether`, wei`1 gwei`, @@ -183,12 +311,23 @@ unit("Optimism :: L1ERC20TokenBridge", ctxFactory) ), "ErrorDepositsDisabled()" ); + await assert.revertsWith( + l1TokenBridge.depositERC20To( + stranger.address, + l2TokenRebasable.address, + recipient.address, + wei`1 ether`, + wei`1 gwei`, + "0x" + ), + "ErrorDepositsDisabled()" + ); }) .test("depositsERC20To() :: wrong l2Token address", async (ctx) => { const { l1TokenBridge, - stubs: { l1Token }, + stubs: { l1TokenNonRebasable, l1TokenRebasable }, accounts: { recipient, stranger }, } = ctx; await l1TokenBridge.disableDeposits(); @@ -197,7 +336,7 @@ unit("Optimism :: L1ERC20TokenBridge", ctxFactory) await assert.revertsWith( l1TokenBridge.depositERC20To( - l1Token.address, + l1TokenNonRebasable.address, stranger.address, recipient.address, wei`1 ether`, @@ -206,19 +345,29 @@ unit("Optimism :: L1ERC20TokenBridge", ctxFactory) ), "ErrorDepositsDisabled()" ); + await assert.revertsWith( + l1TokenBridge.depositERC20To( + l1TokenRebasable.address, + stranger.address, + recipient.address, + wei`1 ether`, + wei`1 gwei`, + "0x" + ), + "ErrorDepositsDisabled()" + ); }) .test("depositsERC20To() :: recipient is zero address", async (ctx) => { const { l1TokenBridge, - stubs: { l1Token }, - accounts: { stranger }, + stubs: { l1TokenNonRebasable, l2TokenNonRebasable, l1TokenRebasable, l2TokenRebasable } } = ctx; await assert.revertsWith( l1TokenBridge.depositERC20To( - l1Token.address, - stranger.address, + l1TokenNonRebasable.address, + l2TokenNonRebasable.address, ethers.constants.AddressZero, wei`1 ether`, wei`1 gwei`, @@ -226,27 +375,38 @@ unit("Optimism :: L1ERC20TokenBridge", ctxFactory) ), "ErrorAccountIsZeroAddress()" ); + await assert.revertsWith( + l1TokenBridge.depositERC20To( + l1TokenRebasable.address, + l2TokenRebasable.address, + ethers.constants.AddressZero, + wei`1 ether`, + wei`1 gwei`, + "0x" + ), + "ErrorAccountIsZeroAddress()" + ); }) - .test("depositERC20To()", async (ctx) => { + .test("depositERC20To() :: non rebasable token flow", async (ctx) => { const { l1TokenBridge, accounts: { deployer, l2TokenBridgeEOA, recipient }, - stubs: { l1Token, l2Token, l1Messenger }, + stubs: { l1TokenNonRebasable, l2TokenNonRebasable, l1Messenger }, } = ctx; const l2Gas = wei`0.99 wei`; const amount = wei`1 ether`; const data = "0x"; - await l1Token.approve(l1TokenBridge.address, amount); + await l1TokenNonRebasable.approve(l1TokenBridge.address, amount); - const deployerBalanceBefore = await l1Token.balanceOf(deployer.address); - const bridgeBalanceBefore = await l1Token.balanceOf(l1TokenBridge.address); + const deployerBalanceBefore = await l1TokenNonRebasable.balanceOf(deployer.address); + const bridgeBalanceBefore = await l1TokenNonRebasable.balanceOf(l1TokenBridge.address); const tx = await l1TokenBridge.depositERC20To( - l1Token.address, - l2Token.address, + l1TokenNonRebasable.address, + l2TokenNonRebasable.address, recipient.address, amount, l2Gas, @@ -254,8 +414,8 @@ unit("Optimism :: L1ERC20TokenBridge", ctxFactory) ); await assert.emits(l1TokenBridge, tx, "ERC20DepositInitiated", [ - l1Token.address, - l2Token.address, + l1TokenNonRebasable.address, + l2TokenNonRebasable.address, deployer.address, recipient.address, amount, @@ -268,8 +428,8 @@ unit("Optimism :: L1ERC20TokenBridge", ctxFactory) L2ERC20TokenBridge__factory.createInterface().encodeFunctionData( "finalizeDeposit", [ - l1Token.address, - l2Token.address, + l1TokenNonRebasable.address, + l2TokenNonRebasable.address, deployer.address, recipient.address, amount, @@ -281,22 +441,94 @@ unit("Optimism :: L1ERC20TokenBridge", ctxFactory) ]); assert.equalBN( - await l1Token.balanceOf(deployer.address), + await l1TokenNonRebasable.balanceOf(deployer.address), deployerBalanceBefore.sub(amount) ); assert.equalBN( - await l1Token.balanceOf(l1TokenBridge.address), + await l1TokenNonRebasable.balanceOf(l1TokenBridge.address), bridgeBalanceBefore.add(amount) ); }) + .test("depositERC20To() :: rebasable token flow", async (ctx) => { + const { + l1TokenBridge, + accounts: { deployer, l2TokenBridgeEOA, recipient }, + stubs: { l1TokenNonRebasable, l2TokenNonRebasable, l1TokenRebasable, l2TokenRebasable, l1Messenger }, + } = ctx; + + const l2Gas = wei`0.99 wei`; + const amount = wei`1 ether`; + const data = "0x"; + + const rate = await l1TokenNonRebasable.stETHPerToken(); + const decimalsStr = await l1TokenNonRebasable.decimals(); + const decimals = BigNumber.from(10).pow(decimalsStr); + + const amountWrapped = (wei.toBigNumber(amount)).mul(BigNumber.from(decimals)).div(rate); + + await l1TokenRebasable.approve(l1TokenBridge.address, amount); + + const deployerBalanceBefore = await l1TokenRebasable.balanceOf(deployer.address); + const bridgeBalanceBefore = await l1TokenNonRebasable.balanceOf(l1TokenBridge.address); + + const tx = await l1TokenBridge.depositERC20To( + l1TokenRebasable.address, + l2TokenRebasable.address, + recipient.address, + amount, + l2Gas, + data + ); + + const packedTokenRateAndTimestampData = await packedTokenRateAndTimestamp(ctx.provider, l1TokenNonRebasable); + const dataToReceive = ethers.utils.hexConcat([packedTokenRateAndTimestampData, data]); + + await assert.emits(l1TokenBridge, tx, "ERC20DepositInitiated", [ + l1TokenRebasable.address, + l2TokenRebasable.address, + deployer.address, + recipient.address, + amountWrapped, + dataToReceive, + ]); + + await assert.emits(l1Messenger, tx, "SentMessage", [ + l2TokenBridgeEOA.address, + l1TokenBridge.address, + L2ERC20TokenBridge__factory.createInterface().encodeFunctionData( + "finalizeDeposit", + [ + l1TokenRebasable.address, + l2TokenRebasable.address, + deployer.address, + recipient.address, + amountWrapped, + dataToReceive, + ] + ), + 1, // message nonce + l2Gas, + ]); + + assert.equalBN( + await l1TokenRebasable.balanceOf(deployer.address), + deployerBalanceBefore.sub(amount) + ); + + assert.equalBN( + await l1TokenNonRebasable.balanceOf(l1TokenBridge.address), + bridgeBalanceBefore.add(amountWrapped) + ); + }) + .test( "finalizeERC20Withdrawal() :: withdrawals are disabled", async (ctx) => { const { l1TokenBridge, - stubs: { l1Token, l2Token }, + stubs: { l1TokenNonRebasable, l2TokenNonRebasable, l1TokenRebasable, l2TokenRebasable }, accounts: { deployer, recipient, l2TokenBridgeEOA }, } = ctx; await l1TokenBridge.disableWithdrawals(); @@ -307,8 +539,21 @@ unit("Optimism :: L1ERC20TokenBridge", ctxFactory) l1TokenBridge .connect(l2TokenBridgeEOA) .finalizeERC20Withdrawal( - l1Token.address, - l2Token.address, + l1TokenNonRebasable.address, + l2TokenNonRebasable.address, + deployer.address, + recipient.address, + wei`1 ether`, + "0x" + ), + "ErrorWithdrawalsDisabled()" + ); + await assert.revertsWith( + l1TokenBridge + .connect(l2TokenBridgeEOA) + .finalizeERC20Withdrawal( + l1TokenRebasable.address, + l2TokenRebasable.address, deployer.address, recipient.address, wei`1 ether`, @@ -322,7 +567,7 @@ unit("Optimism :: L1ERC20TokenBridge", ctxFactory) .test("finalizeERC20Withdrawal() :: wrong l1Token", async (ctx) => { const { l1TokenBridge, - stubs: { l2Token }, + stubs: { l2TokenNonRebasable, l2TokenRebasable }, accounts: { deployer, recipient, l2TokenBridgeEOA, stranger }, } = ctx; @@ -331,7 +576,7 @@ unit("Optimism :: L1ERC20TokenBridge", ctxFactory) .connect(l2TokenBridgeEOA) .finalizeERC20Withdrawal( stranger.address, - l2Token.address, + l2TokenNonRebasable.address, deployer.address, recipient.address, wei`1 ether`, @@ -339,12 +584,26 @@ unit("Optimism :: L1ERC20TokenBridge", ctxFactory) ), "ErrorUnsupportedL1Token()" ); + + await assert.revertsWith( + l1TokenBridge + .connect(l2TokenBridgeEOA) + .finalizeERC20Withdrawal( + stranger.address, + l2TokenRebasable.address, + deployer.address, + recipient.address, + wei`1 ether`, + "0x" + ), + "ErrorUnsupportedL1Token()" + ); }) .test("finalizeERC20Withdrawal() :: wrong l2Token", async (ctx) => { const { l1TokenBridge, - stubs: { l1Token }, + stubs: { l1TokenNonRebasable, l1TokenRebasable }, accounts: { deployer, recipient, l2TokenBridgeEOA, stranger }, } = ctx; @@ -352,7 +611,7 @@ unit("Optimism :: L1ERC20TokenBridge", ctxFactory) l1TokenBridge .connect(l2TokenBridgeEOA) .finalizeERC20Withdrawal( - l1Token.address, + l1TokenNonRebasable.address, stranger.address, deployer.address, recipient.address, @@ -361,12 +620,26 @@ unit("Optimism :: L1ERC20TokenBridge", ctxFactory) ), "ErrorUnsupportedL2Token()" ); + + await assert.revertsWith( + l1TokenBridge + .connect(l2TokenBridgeEOA) + .finalizeERC20Withdrawal( + l1TokenRebasable.address, + stranger.address, + deployer.address, + recipient.address, + wei`1 ether`, + "0x" + ), + "ErrorUnsupportedL2Token()" + ); }) .test("finalizeERC20Withdrawal() :: unauthorized messenger", async (ctx) => { const { l1TokenBridge, - stubs: { l1Token, l2Token }, + stubs: { l1TokenNonRebasable, l2TokenNonRebasable, l1TokenRebasable, l2TokenRebasable }, accounts: { deployer, recipient, stranger }, } = ctx; @@ -374,8 +647,8 @@ unit("Optimism :: L1ERC20TokenBridge", ctxFactory) l1TokenBridge .connect(stranger) .finalizeERC20Withdrawal( - l1Token.address, - l2Token.address, + l1TokenNonRebasable.address, + l2TokenNonRebasable.address, deployer.address, recipient.address, wei`1 ether`, @@ -383,6 +656,19 @@ unit("Optimism :: L1ERC20TokenBridge", ctxFactory) ), "ErrorUnauthorizedMessenger()" ); + await assert.revertsWith( + l1TokenBridge + .connect(stranger) + .finalizeERC20Withdrawal( + l1TokenRebasable.address, + l2TokenRebasable.address, + deployer.address, + recipient.address, + wei`1 ether`, + "0x" + ), + "ErrorUnauthorizedMessenger()" + ); }) .test( @@ -390,7 +676,7 @@ unit("Optimism :: L1ERC20TokenBridge", ctxFactory) async (ctx) => { const { l1TokenBridge, - stubs: { l1Token, l2Token, l1Messenger }, + stubs: { l1TokenNonRebasable, l2TokenNonRebasable, l1TokenRebasable, l2TokenRebasable, l1Messenger }, accounts: { deployer, recipient, stranger, l1MessengerStubAsEOA }, } = ctx; @@ -400,8 +686,21 @@ unit("Optimism :: L1ERC20TokenBridge", ctxFactory) l1TokenBridge .connect(l1MessengerStubAsEOA) .finalizeERC20Withdrawal( - l1Token.address, - l2Token.address, + l1TokenNonRebasable.address, + l2TokenNonRebasable.address, + deployer.address, + recipient.address, + wei`1 ether`, + "0x" + ), + "ErrorWrongCrossDomainSender()" + ); + await assert.revertsWith( + l1TokenBridge + .connect(l1MessengerStubAsEOA) + .finalizeERC20Withdrawal( + l1TokenRebasable.address, + l2TokenRebasable.address, deployer.address, recipient.address, wei`1 ether`, @@ -412,25 +711,74 @@ unit("Optimism :: L1ERC20TokenBridge", ctxFactory) } ) - .test("finalizeERC20Withdrawal()", async (ctx) => { + .test("finalizeERC20Withdrawal() :: non rebasable token flow", async (ctx) => { const { l1TokenBridge, - stubs: { l1Token, l2Token, l1Messenger }, + stubs: { l1TokenNonRebasable, l2TokenNonRebasable, l1Messenger }, accounts: { deployer, recipient, l1MessengerStubAsEOA, l2TokenBridgeEOA }, } = ctx; await l1Messenger.setXDomainMessageSender(l2TokenBridgeEOA.address); - const bridgeBalanceBefore = await l1Token.balanceOf(l1TokenBridge.address); + const bridgeBalanceBefore = await l1TokenNonRebasable.balanceOf(l1TokenBridge.address); + + const amount = wei`1 ether`; + const data = "0xdeadbeaf"; + + const tx = await l1TokenBridge + .connect(l1MessengerStubAsEOA) + .finalizeERC20Withdrawal( + l1TokenNonRebasable.address, + l2TokenNonRebasable.address, + deployer.address, + recipient.address, + amount, + data + ); + + await assert.emits(l1TokenBridge, tx, "ERC20WithdrawalFinalized", [ + l1TokenNonRebasable.address, + l2TokenNonRebasable.address, + deployer.address, + recipient.address, + amount, + data, + ]); + + assert.equalBN(await l1TokenNonRebasable.balanceOf(recipient.address), amount); + assert.equalBN( + await l1TokenNonRebasable.balanceOf(l1TokenBridge.address), + bridgeBalanceBefore.sub(amount) + ); + }) + + .test("finalizeERC20Withdrawal() :: rebasable token flow", async (ctx) => { + const { + l1TokenBridge, + stubs: { l1TokenRebasable, l2TokenRebasable, l1TokenNonRebasable, l2TokenNonRebasable, l1Messenger }, + accounts: { deployer, recipient, l1MessengerStubAsEOA, l2TokenBridgeEOA }, + } = ctx; const amount = wei`1 ether`; const data = "0xdeadbeaf"; + const rate = await l1TokenNonRebasable.stETHPerToken(); + const decimalsStr = await l1TokenNonRebasable.decimals(); + const decimals = BigNumber.from(10).pow(decimalsStr); + + const amountUnwrapped = (wei.toBigNumber(amount)).mul(rate).div(BigNumber.from(decimals)); + const deployerBalanceBefore = await l1TokenRebasable.balanceOf(deployer.address); + + await l1Messenger.setXDomainMessageSender(l2TokenBridgeEOA.address); + + await l1TokenRebasable.transfer(l1TokenNonRebasable.address, wei`100 ether`); + + const bridgeBalanceBefore = await l1TokenRebasable.balanceOf(l1TokenBridge.address); const tx = await l1TokenBridge .connect(l1MessengerStubAsEOA) .finalizeERC20Withdrawal( - l1Token.address, - l2Token.address, + l1TokenRebasable.address, + l2TokenRebasable.address, deployer.address, recipient.address, amount, @@ -438,17 +786,17 @@ unit("Optimism :: L1ERC20TokenBridge", ctxFactory) ); await assert.emits(l1TokenBridge, tx, "ERC20WithdrawalFinalized", [ - l1Token.address, - l2Token.address, + l1TokenRebasable.address, + l2TokenRebasable.address, deployer.address, recipient.address, amount, data, ]); - assert.equalBN(await l1Token.balanceOf(recipient.address), amount); + assert.equalBN(await l1TokenRebasable.balanceOf(recipient.address), amountUnwrapped); assert.equalBN( - await l1Token.balanceOf(l1TokenBridge.address), + await l1TokenNonRebasable.balanceOf(l1TokenBridge.address), bridgeBalanceBefore.sub(amount) ); }) @@ -456,21 +804,35 @@ unit("Optimism :: L1ERC20TokenBridge", ctxFactory) .run(); async function ctxFactory() { - const [deployer, l2TokenBridgeEOA, stranger, recipient, rebasableToken] = + const [deployer, l2TokenBridgeEOA, stranger, recipient] = await hre.ethers.getSigners(); + const provider = await hre.ethers.provider; + const l1MessengerStub = await new CrossDomainMessengerStub__factory( deployer ).deploy({ value: wei.toBigNumber(wei`1 ether`) }); - const l1TokenStub = await new ERC20BridgedStub__factory(deployer).deploy( - "L1 Token", - "L1" + const l1TokenRebasableStub = await new ERC20BridgedStub__factory(deployer).deploy( + "L1 Token Rebasable", + "L1R" + ); + + const l1TokenNonRebasableStub = await new ERC20WrapperStub__factory(deployer).deploy( + l1TokenRebasableStub.address, + "L1 Token Non Rebasable", + "L1NR" ); - const l2TokenStub = await new ERC20BridgedStub__factory(deployer).deploy( - "L2 Token", - "L2" + const l2TokenNonRebasableStub = await new ERC20BridgedStub__factory(deployer).deploy( + "L2 Token Non Rebasable", + "L2NR" + ); + + const l2TokenRebasableStub = await new ERC20WrapperStub__factory(deployer).deploy( + l2TokenNonRebasableStub.address, + "L2 Token Rebasable", + "L2R" ); const emptyContract = await new EmptyContractStub__factory(deployer).deploy({ @@ -487,10 +849,10 @@ async function ctxFactory() { ).deploy( l1MessengerStub.address, l2TokenBridgeEOA.address, - l1TokenStub.address, - rebasableToken.address, - l2TokenStub.address, - rebasableToken.address + l1TokenNonRebasableStub.address, + l1TokenRebasableStub.address, + l2TokenNonRebasableStub.address, + l2TokenRebasableStub.address ); const l1TokenBridgeProxy = await new OssifiableProxy__factory( @@ -508,7 +870,8 @@ async function ctxFactory() { deployer ); - await l1TokenStub.transfer(l1TokenBridge.address, wei`100 ether`); + await l1TokenNonRebasableStub.transfer(l1TokenBridge.address, wei`100 ether`); + await l1TokenRebasableStub.transfer(l1TokenBridge.address, wei`100 ether`); const roles = await Promise.all([ l1TokenBridge.DEPOSITS_ENABLER_ROLE(), @@ -525,6 +888,7 @@ async function ctxFactory() { await l1TokenBridge.enableWithdrawals(); return { + provider: provider, accounts: { deployer, stranger, @@ -534,12 +898,21 @@ async function ctxFactory() { l1MessengerStubAsEOA, }, stubs: { - l1Token: l1TokenStub, - l2Token: l2TokenStub, - l1TokenRebasable: l1TokenStub, - l2TokenRebasable: l2TokenStub, + l1TokenNonRebasable: l1TokenNonRebasableStub, + l1TokenRebasable: l1TokenRebasableStub, + l2TokenNonRebasable: l2TokenNonRebasableStub, + l2TokenRebasable: l2TokenRebasableStub, l1Messenger: l1MessengerStub, }, l1TokenBridge, }; } + +async function packedTokenRateAndTimestamp(l1Provider: JsonRpcProvider, l1Token: ERC20WrapperStub) { + const stETHPerToken = await l1Token.stETHPerToken(); + const blockNumber = await l1Provider.getBlockNumber(); + const blockTimestamp = (await l1Provider.getBlock(blockNumber)).timestamp; + const stETHPerTokenStr = ethers.utils.hexZeroPad(stETHPerToken.toHexString(), 12); + const blockTimestampStr = ethers.utils.hexZeroPad(ethers.utils.hexlify(blockTimestamp), 5); + return ethers.utils.hexConcat([stETHPerTokenStr, blockTimestampStr]); +} diff --git a/test/optimism/L2ERC20TokenBridge.unit.test.ts b/test/optimism/L2ERC20TokenBridge.unit.test.ts index 20c594c9..17bbb425 100644 --- a/test/optimism/L2ERC20TokenBridge.unit.test.ts +++ b/test/optimism/L2ERC20TokenBridge.unit.test.ts @@ -1,456 +1,811 @@ -import hre from "hardhat"; +import hre, { ethers } from "hardhat"; import { - ERC20BridgedStub__factory, - L1ERC20TokenBridge__factory, - L2ERC20TokenBridge__factory, - OssifiableProxy__factory, - EmptyContractStub__factory, - CrossDomainMessengerStub__factory, + ERC20BridgedStub__factory, + ERC20WrapperStub__factory, + TokenRateOracle__factory, + ERC20Rebasable__factory, + L1ERC20TokenBridge__factory, + L2ERC20TokenBridge__factory, + OssifiableProxy__factory, + EmptyContractStub__factory, + CrossDomainMessengerStub__factory, } from "../../typechain"; import testing, { unit } from "../../utils/testing"; import { wei } from "../../utils/wei"; import { assert } from "chai"; +import { SignerWithAddress } from "@nomiclabs/hardhat-ethers/signers"; +import { getContractAddress } from "ethers/lib/utils"; +import { JsonRpcProvider } from "@ethersproject/providers"; +import { BigNumber } from "ethers"; unit("Optimism:: L2ERC20TokenBridge", ctxFactory) - .test("l1TokenBridge()", async (ctx) => { - assert.equal( - await ctx.l2TokenBridge.l1TokenBridge(), - ctx.accounts.l1TokenBridgeEOA.address - ); - }) - - .test("withdraw() :: withdrawals disabled", async (ctx) => { - const { - l2TokenBridge, - stubs: { l2Token: l2TokenStub }, - } = ctx; - - await ctx.l2TokenBridge.disableWithdrawals(); - - assert.isFalse(await ctx.l2TokenBridge.isWithdrawalsEnabled()); - - await assert.revertsWith( - l2TokenBridge.withdraw( - l2TokenStub.address, - wei`1 ether`, - wei`1 gwei`, - "0x" - ), - "ErrorWithdrawalsDisabled()" - ); - }) - - .test("withdraw() :: unsupported l2Token", async (ctx) => { - const { - l2TokenBridge, - accounts: { stranger }, - } = ctx; - await assert.revertsWith( - l2TokenBridge.withdraw(stranger.address, wei`1 ether`, wei`1 gwei`, "0x"), - "ErrorUnsupportedL2Token()" - ); - }) - - .test("withdraw()", async (ctx) => { - const { - l2TokenBridge, - accounts: { deployer, l1TokenBridgeEOA }, - stubs: { - l2Messenger: l2MessengerStub, - l1Token: l1TokenStub, - l2Token: l2TokenStub, - }, - } = ctx; - - const deployerBalanceBefore = await l2TokenStub.balanceOf(deployer.address); - const totalSupplyBefore = await l2TokenStub.totalSupply(); - - const amount = wei`1 ether`; - const l1Gas = wei`1 wei`; - const data = "0xdeadbeaf"; - - const tx = await l2TokenBridge.withdraw( - l2TokenStub.address, - amount, - l1Gas, - data - ); - - await assert.emits(l2TokenBridge, tx, "WithdrawalInitiated", [ - l1TokenStub.address, - l2TokenStub.address, - deployer.address, - deployer.address, - amount, - data, - ]); - - await assert.emits(l2MessengerStub, tx, "SentMessage", [ - l1TokenBridgeEOA.address, - l2TokenBridge.address, - L1ERC20TokenBridge__factory.createInterface().encodeFunctionData( - "finalizeERC20Withdrawal", - [ - l1TokenStub.address, - l2TokenStub.address, - deployer.address, - deployer.address, - amount, - data, - ] - ), - 1, // message nonce - l1Gas, - ]); + .test("l1TokenBridge()", async (ctx) => { + assert.equal( + await ctx.l2TokenBridge.l1TokenBridge(), + ctx.accounts.l1TokenBridgeEOA.address + ); + }) + + .test("withdraw() :: withdrawals disabled", async (ctx) => { + const { + l2TokenBridge, + stubs: { l2TokenNonRebasable, l2TokenRebasable }, + } = ctx; + + await ctx.l2TokenBridge.disableWithdrawals(); + + assert.isFalse(await ctx.l2TokenBridge.isWithdrawalsEnabled()); + + await assert.revertsWith( + l2TokenBridge.withdraw( + l2TokenNonRebasable.address, + wei`1 ether`, + wei`1 gwei`, + "0x" + ), + "ErrorWithdrawalsDisabled()" + ); + + await assert.revertsWith( + l2TokenBridge.withdraw( + l2TokenRebasable.address, + wei`1 ether`, + wei`1 gwei`, + "0x" + ), + "ErrorWithdrawalsDisabled()" + ); + }) + + .test("withdraw() :: unsupported l2Token", async (ctx) => { + const { + l2TokenBridge, + accounts: { stranger }, + } = ctx; + await assert.revertsWith( + l2TokenBridge.withdraw(stranger.address, wei`1 ether`, wei`1 gwei`, "0x"), + "ErrorUnsupportedL2Token()" + ); + }) + + .test("withdraw() :: non rebasable token flow", async (ctx) => { + const { + l2TokenBridge, + accounts: { deployer, l1TokenBridgeEOA }, + stubs: { + l2Messenger, + l1TokenNonRebasable, + l2TokenNonRebasable, + }, + } = ctx; + + const deployerBalanceBefore = await l2TokenNonRebasable.balanceOf(deployer.address); + const totalSupplyBefore = await l2TokenNonRebasable.totalSupply(); + + const amount = wei`1 ether`; + const l1Gas = wei`1 wei`; + const data = "0xdeadbeaf"; + + const tx = await l2TokenBridge.withdraw( + l2TokenNonRebasable.address, + amount, + l1Gas, + data + ); + + await assert.emits(l2TokenBridge, tx, "WithdrawalInitiated", [ + l1TokenNonRebasable.address, + l2TokenNonRebasable.address, + deployer.address, + deployer.address, + amount, + data, + ]); + + await assert.emits(l2Messenger, tx, "SentMessage", [ + l1TokenBridgeEOA.address, + l2TokenBridge.address, + L1ERC20TokenBridge__factory.createInterface().encodeFunctionData( + "finalizeERC20Withdrawal", + [ + l1TokenNonRebasable.address, + l2TokenNonRebasable.address, + deployer.address, + deployer.address, + amount, + data, + ] + ), + 1, // message nonce + l1Gas, + ]); + + assert.equalBN( + await l2TokenNonRebasable.balanceOf(deployer.address), + deployerBalanceBefore.sub(amount) + ); + + assert.equalBN( + await l2TokenNonRebasable.totalSupply(), + totalSupplyBefore.sub(amount) + ); + }) + + .test("withdraw() :: rebasable token flow", async (ctx) => { + const { + l2TokenBridge, + accounts: { deployer, l1TokenBridgeEOA, l2MessengerStubEOA, recipient }, + stubs: { + l2Messenger, + l1TokenNonRebasable, + l2TokenNonRebasable, + l1TokenRebasable, + l2TokenRebasable + }, + } = ctx; + + const amountToDeposit = wei`1 ether`; + const amountToWithdraw = wei.toBigNumber(amountToDeposit).mul(ctx.exchangeRate).div(ctx.decimalsBN); + const l1Gas = wei`1 wei`; + const data = "0xdeadbeaf"; + const provider = await hre.ethers.provider; + const packedTokenRateAndTimestampData = await packedTokenRateAndTimestamp(provider, ctx.exchangeRate); + + const tx1 = await l2TokenBridge + .connect(l2MessengerStubEOA) + .finalizeDeposit( + l1TokenRebasable.address, + l2TokenRebasable.address, + deployer.address, + recipient.address, + amountToDeposit, + packedTokenRateAndTimestampData + ); + + const recipientBalanceBefore = await l2TokenRebasable.balanceOf(recipient.address); + const totalSupplyBefore = await l2TokenRebasable.totalSupply(); + + const tx = await l2TokenBridge.connect(recipient).withdraw( + l2TokenRebasable.address, + amountToWithdraw, + l1Gas, + data + ); + + await assert.emits(l2TokenBridge, tx, "WithdrawalInitiated", [ + l1TokenRebasable.address, + l2TokenRebasable.address, + recipient.address, + recipient.address, + amountToWithdraw, + data, + ]); + + await assert.emits(l2Messenger, tx, "SentMessage", [ + l1TokenBridgeEOA.address, + l2TokenBridge.address, + L1ERC20TokenBridge__factory.createInterface().encodeFunctionData( + "finalizeERC20Withdrawal", + [ + l1TokenRebasable.address, + l2TokenRebasable.address, + recipient.address, + recipient.address, + amountToDeposit, + data, + ] + ), + 1, // message nonce + l1Gas, + ]); + + assert.equalBN( + await l2TokenRebasable.balanceOf(deployer.address), + recipientBalanceBefore.sub(amountToWithdraw) + ); + + assert.equalBN( + await l2TokenRebasable.totalSupply(), + totalSupplyBefore.sub(amountToWithdraw) + ); + }) + + .test("withdrawTo() :: withdrawals disabled", async (ctx) => { + const { + l2TokenBridge, + stubs: { l2TokenNonRebasable, l2TokenRebasable }, + accounts: { recipient }, + } = ctx; + + await ctx.l2TokenBridge.disableWithdrawals(); + + assert.isFalse(await ctx.l2TokenBridge.isWithdrawalsEnabled()); + + await assert.revertsWith( + l2TokenBridge.withdrawTo( + l2TokenNonRebasable.address, + recipient.address, + wei`1 ether`, + wei`1 gwei`, + "0x" + ), + "ErrorWithdrawalsDisabled()" + ); + await assert.revertsWith( + l2TokenBridge.withdrawTo( + l2TokenRebasable.address, + recipient.address, + wei`1 ether`, + wei`1 gwei`, + "0x" + ), + "ErrorWithdrawalsDisabled()" + ); + }) + + .test("withdrawTo() :: unsupported l2Token", async (ctx) => { + const { + l2TokenBridge, + accounts: { stranger, recipient }, + } = ctx; + await assert.revertsWith( + l2TokenBridge.withdrawTo( + stranger.address, + recipient.address, + wei`1 ether`, + wei`1 gwei`, + "0x" + ), + "ErrorUnsupportedL2Token()" + ); + }) + + .test("withdrawTo() :: non rebasable token flow", async (ctx) => { + const { + l2TokenBridge, + accounts: { deployer, recipient, l1TokenBridgeEOA }, + stubs: { + l2Messenger: l2MessengerStub, + l1TokenNonRebasable, + l2TokenNonRebasable + }, + } = ctx; + + const deployerBalanceBefore = await l2TokenNonRebasable.balanceOf(deployer.address); + const totalSupplyBefore = await l2TokenNonRebasable.totalSupply(); + + const amount = wei`1 ether`; + const l1Gas = wei`1 wei`; + const data = "0xdeadbeaf"; + + const tx = await l2TokenBridge.withdrawTo( + l2TokenNonRebasable.address, + recipient.address, + amount, + l1Gas, + data + ); + + await assert.emits(l2TokenBridge, tx, "WithdrawalInitiated", [ + l1TokenNonRebasable.address, + l2TokenNonRebasable.address, + deployer.address, + recipient.address, + amount, + data, + ]); + + await assert.emits(l2MessengerStub, tx, "SentMessage", [ + l1TokenBridgeEOA.address, + l2TokenBridge.address, + L1ERC20TokenBridge__factory.createInterface().encodeFunctionData( + "finalizeERC20Withdrawal", + [ + l1TokenNonRebasable.address, + l2TokenNonRebasable.address, + deployer.address, + recipient.address, + amount, + data, + ] + ), + 1, // message nonce + l1Gas, + ]); + + assert.equalBN( + await l2TokenNonRebasable.balanceOf(deployer.address), + deployerBalanceBefore.sub(amount) + ); + + assert.equalBN( + await l2TokenNonRebasable.totalSupply(), + totalSupplyBefore.sub(amount) + ); + }) + + .test("withdrawTo() :: rebasable token flow", async (ctx) => { + + const { + l2TokenBridge, + accounts: { deployer, l1TokenBridgeEOA, l2MessengerStubEOA, recipient }, + stubs: { + l2Messenger, + l1TokenNonRebasable, + l2TokenNonRebasable, + l1TokenRebasable, + l2TokenRebasable + }, + } = ctx; + + const amountToDeposit = wei`1 ether`; + const amountToWithdraw = wei.toBigNumber(amountToDeposit).mul(ctx.exchangeRate).div(ctx.decimalsBN); + const l1Gas = wei`1 wei`; + const data = "0xdeadbeaf"; + const provider = await hre.ethers.provider; + const packedTokenRateAndTimestampData = await packedTokenRateAndTimestamp(provider, ctx.exchangeRate); + + const tx1 = await l2TokenBridge + .connect(l2MessengerStubEOA) + .finalizeDeposit( + l1TokenRebasable.address, + l2TokenRebasable.address, + deployer.address, + deployer.address, + amountToDeposit, + packedTokenRateAndTimestampData + ); + + const deployerBalanceBefore = await l2TokenRebasable.balanceOf(deployer.address); + const totalSupplyBefore = await l2TokenRebasable.totalSupply(); + + const tx = await l2TokenBridge.connect(deployer).withdrawTo( + l2TokenRebasable.address, + recipient.address, + amountToWithdraw, + l1Gas, + data + ); + + await assert.emits(l2TokenBridge, tx, "WithdrawalInitiated", [ + l1TokenRebasable.address, + l2TokenRebasable.address, + deployer.address, + recipient.address, + amountToWithdraw, + data, + ]); + + await assert.emits(l2Messenger, tx, "SentMessage", [ + l1TokenBridgeEOA.address, + l2TokenBridge.address, + L1ERC20TokenBridge__factory.createInterface().encodeFunctionData( + "finalizeERC20Withdrawal", + [ + l1TokenRebasable.address, + l2TokenRebasable.address, + deployer.address, + recipient.address, + amountToDeposit, + data, + ] + ), + 1, // message nonce + l1Gas, + ]); + + assert.equalBN( + await l2TokenRebasable.balanceOf(recipient.address), + deployerBalanceBefore.sub(amountToWithdraw) + ); + + assert.equalBN( + await l2TokenRebasable.totalSupply(), + totalSupplyBefore.sub(amountToWithdraw) + ); + }) + + .test("finalizeDeposit() :: deposits disabled", async (ctx) => { + const { + l2TokenBridge, + accounts: { l2MessengerStubEOA, deployer, recipient }, + stubs: { l1TokenNonRebasable, l2TokenNonRebasable, l1TokenRebasable, l2TokenRebasable }, + } = ctx; + + await l2TokenBridge.disableDeposits(); + + assert.isFalse(await l2TokenBridge.isDepositsEnabled()); + + await assert.revertsWith( + l2TokenBridge + .connect(l2MessengerStubEOA) + .finalizeDeposit( + l1TokenNonRebasable.address, + l2TokenNonRebasable.address, + deployer.address, + recipient.address, + wei`1 ether`, + "0x" + ), + "ErrorDepositsDisabled()" + ); + await assert.revertsWith( + l2TokenBridge + .connect(l2MessengerStubEOA) + .finalizeDeposit( + l1TokenRebasable.address, + l2TokenRebasable.address, + deployer.address, + recipient.address, + wei`1 ether`, + "0x" + ), + "ErrorDepositsDisabled()" + ); + }) + + .test("finalizeDeposit() :: unsupported l1Token", async (ctx) => { + const { + l2TokenBridge, + accounts: { l2MessengerStubEOA, deployer, recipient, stranger }, + stubs: { l2TokenNonRebasable, l2TokenRebasable }, + } = ctx; + + await assert.revertsWith( + l2TokenBridge + .connect(l2MessengerStubEOA) + .finalizeDeposit( + stranger.address, + l2TokenNonRebasable.address, + deployer.address, + recipient.address, + wei`1 ether`, + "0x" + ), + "ErrorUnsupportedL1Token()" + ); + await assert.revertsWith( + l2TokenBridge + .connect(l2MessengerStubEOA) + .finalizeDeposit( + stranger.address, + l2TokenRebasable.address, + deployer.address, + recipient.address, + wei`1 ether`, + "0x" + ), + "ErrorUnsupportedL1Token()" + ); + }) + + .test("finalizeDeposit() :: unsupported l2Token", async (ctx) => { + const { + l2TokenBridge, + accounts: { l2MessengerStubEOA, deployer, recipient, stranger }, + stubs: { l1TokenNonRebasable, l1TokenRebasable }, + } = ctx; + + await assert.revertsWith( + l2TokenBridge + .connect(l2MessengerStubEOA) + .finalizeDeposit( + l1TokenNonRebasable.address, + stranger.address, + deployer.address, + recipient.address, + wei`1 ether`, + "0x" + ), + "ErrorUnsupportedL2Token()" + ); + await assert.revertsWith( + l2TokenBridge + .connect(l2MessengerStubEOA) + .finalizeDeposit( + l1TokenRebasable.address, + stranger.address, + deployer.address, + recipient.address, + wei`1 ether`, + "0x" + ), + "ErrorUnsupportedL2Token()" + ); + }) + + .test("finalizeDeposit() :: unauthorized messenger", async (ctx) => { + const { + l2TokenBridge, + stubs: { l1TokenNonRebasable, l2TokenNonRebasable, l1TokenRebasable, l2TokenRebasable }, + accounts: { deployer, recipient, stranger }, + } = ctx; + + await assert.revertsWith( + l2TokenBridge + .connect(stranger) + .finalizeDeposit( + l1TokenNonRebasable.address, + l2TokenNonRebasable.address, + deployer.address, + recipient.address, + wei`1 ether`, + "0x" + ), + "ErrorUnauthorizedMessenger()" + ); + await assert.revertsWith( + l2TokenBridge + .connect(stranger) + .finalizeDeposit( + l1TokenRebasable.address, + l2TokenRebasable.address, + deployer.address, + recipient.address, + wei`1 ether`, + "0x" + ), + "ErrorUnauthorizedMessenger()" + ); + }) + + .test("finalizeDeposit() :: wrong cross domain sender", async (ctx) => { + const { + l2TokenBridge, + stubs: { l1TokenNonRebasable, l2TokenNonRebasable, l1TokenRebasable, l2TokenRebasable, l2Messenger }, + accounts: { deployer, recipient, stranger, l2MessengerStubEOA }, + } = ctx; + + await l2Messenger.setXDomainMessageSender(stranger.address); + + await assert.revertsWith( + l2TokenBridge + .connect(l2MessengerStubEOA) + .finalizeDeposit( + l1TokenNonRebasable.address, + l2TokenNonRebasable.address, + deployer.address, + recipient.address, + wei`1 ether`, + "0x" + ), + "ErrorWrongCrossDomainSender()" + ); + + await assert.revertsWith( + l2TokenBridge + .connect(l2MessengerStubEOA) + .finalizeDeposit( + l1TokenRebasable.address, + l2TokenRebasable.address, + deployer.address, + recipient.address, + wei`1 ether`, + "0x" + ), + "ErrorWrongCrossDomainSender()" + ); + }) + + .test("finalizeDeposit() :: non rebasable token flow", async (ctx) => { + const { + l2TokenBridge, + stubs: { l1TokenNonRebasable, l2TokenNonRebasable, l2Messenger }, + accounts: { deployer, recipient, l2MessengerStubEOA, l1TokenBridgeEOA }, + } = ctx; + + await l2Messenger.setXDomainMessageSender(l1TokenBridgeEOA.address); + + const totalSupplyBefore = await l2TokenNonRebasable.totalSupply(); + + const amount = wei`1 ether`; + const data = "0xdeadbeaf"; + + const tx = await l2TokenBridge + .connect(l2MessengerStubEOA) + .finalizeDeposit( + l1TokenNonRebasable.address, + l2TokenNonRebasable.address, + deployer.address, + recipient.address, + amount, + data + ); + + await assert.emits(l2TokenBridge, tx, "DepositFinalized", [ + l1TokenNonRebasable.address, + l2TokenNonRebasable.address, + deployer.address, + recipient.address, + amount, + data, + ]); + + assert.equalBN(await l2TokenNonRebasable.balanceOf(recipient.address), amount); + assert.equalBN(await l2TokenNonRebasable.totalSupply(), totalSupplyBefore.add(amount)); + }) + + .test("finalizeDeposit() :: rebasable token flow", async (ctx) => { + const { + l2TokenBridge, + stubs: { l1TokenRebasable, l2TokenRebasable, l2Messenger }, + accounts: { deployer, recipient, l2MessengerStubEOA, l1TokenBridgeEOA }, + } = ctx; + + await l2Messenger.setXDomainMessageSender(l1TokenBridgeEOA.address); + + const amountToDeposit = wei`1 ether`; + const amountToEmit = wei.toBigNumber(amountToDeposit).mul(ctx.exchangeRate).div(ctx.decimalsBN); + const data = "0xdeadbeaf"; + const provider = await hre.ethers.provider; + const packedTokenRateAndTimestampData = await packedTokenRateAndTimestamp(provider, ctx.exchangeRate); + const dataToReceive = ethers.utils.hexConcat([packedTokenRateAndTimestampData, data]); + + const tx = await l2TokenBridge + .connect(l2MessengerStubEOA) + .finalizeDeposit( + l1TokenRebasable.address, + l2TokenRebasable.address, + deployer.address, + recipient.address, + amountToDeposit, + dataToReceive + ); + + await assert.emits(l2TokenBridge, tx, "DepositFinalized", [ + l1TokenRebasable.address, + l2TokenRebasable.address, + deployer.address, + recipient.address, + amountToEmit, + data, + ]); + + assert.equalBN(await l2TokenRebasable.balanceOf(recipient.address), amountToEmit); + }) + + .run(); - assert.equalBN( - await l2TokenStub.balanceOf(deployer.address), - deployerBalanceBefore.sub(amount) +async function ctxFactory() { + const [deployer, stranger, recipient, l1TokenBridgeEOA] = + await hre.ethers.getSigners(); + + const decimals = 18; + const decimalsBN = BigNumber.from(10).pow(decimals); + const exchangeRate = BigNumber.from('12').pow(decimals - 1); + + const l2MessengerStub = await new CrossDomainMessengerStub__factory( + deployer + ).deploy({ value: wei.toBigNumber(wei`1 ether`) }); + const l2MessengerStubEOA = await testing.impersonate(l2MessengerStub.address); + await l2MessengerStub.setXDomainMessageSender(l1TokenBridgeEOA.address); + + const emptyContract = await new EmptyContractStub__factory(deployer).deploy({ + value: wei.toBigNumber(wei`1 ether`), + }); + const emptyContractEOA = await testing.impersonate(emptyContract.address); + + const [ + l1TokenRebasableAddress, + l1TokenNonRebasableAddress, + l2TokenNonRebasableAddress, + tokenRateOracleAddress, + l2TokenRebasableAddress, + l2TokenBridgeImplAddress, + l2TokenBridgeProxyAddress + ] = await predictAddresses(deployer, 7); + + const l1TokenRebasableStub = await new ERC20BridgedStub__factory(deployer).deploy( + "L1 Token Rebasable", + "L1R" ); - assert.equalBN( - await l2TokenStub.totalSupply(), - totalSupplyBefore.sub(amount) - ); - }) - - .test("withdrawTo() :: withdrawals disabled", async (ctx) => { - const { - l2TokenBridge, - stubs: { l2Token: l2TokenStub }, - accounts: { recipient }, - } = ctx; - - await ctx.l2TokenBridge.disableWithdrawals(); - - assert.isFalse(await ctx.l2TokenBridge.isWithdrawalsEnabled()); - - await assert.revertsWith( - l2TokenBridge.withdrawTo( - l2TokenStub.address, - recipient.address, - wei`1 ether`, - wei`1 gwei`, - "0x" - ), - "ErrorWithdrawalsDisabled()" - ); - }) - - .test("withdrawTo() :: unsupported l2Token", async (ctx) => { - const { - l2TokenBridge, - accounts: { stranger, recipient }, - } = ctx; - await assert.revertsWith( - l2TokenBridge.withdrawTo( - stranger.address, - recipient.address, - wei`1 ether`, - wei`1 gwei`, - "0x" - ), - "ErrorUnsupportedL2Token()" - ); - }) - - .test("withdrawTo()", async (ctx) => { - const { - l2TokenBridge, - accounts: { deployer, recipient, l1TokenBridgeEOA }, - stubs: { - l2Messenger: l2MessengerStub, - l1Token: l1TokenStub, - l2Token: l2TokenStub, - }, - } = ctx; - - const deployerBalanceBefore = await l2TokenStub.balanceOf(deployer.address); - const totalSupplyBefore = await l2TokenStub.totalSupply(); - - const amount = wei`1 ether`; - const l1Gas = wei`1 wei`; - const data = "0xdeadbeaf"; - - const tx = await l2TokenBridge.withdrawTo( - l2TokenStub.address, - recipient.address, - amount, - l1Gas, - data + const l1TokenNonRebasableStub = await new ERC20WrapperStub__factory(deployer).deploy( + l1TokenRebasableStub.address, + "L1 Token Non Rebasable", + "L1NR" ); - await assert.emits(l2TokenBridge, tx, "WithdrawalInitiated", [ - l1TokenStub.address, - l2TokenStub.address, - deployer.address, - recipient.address, - amount, - data, - ]); - - await assert.emits(l2MessengerStub, tx, "SentMessage", [ - l1TokenBridgeEOA.address, - l2TokenBridge.address, - L1ERC20TokenBridge__factory.createInterface().encodeFunctionData( - "finalizeERC20Withdrawal", - [ - l1TokenStub.address, - l2TokenStub.address, - deployer.address, - recipient.address, - amount, - data, - ] - ), - 1, // message nonce - l1Gas, - ]); - - assert.equalBN( - await l2TokenStub.balanceOf(deployer.address), - deployerBalanceBefore.sub(amount) + const l2TokenNonRebasableStub = await new ERC20BridgedStub__factory(deployer).deploy( + "L2 Token Non Rebasable", + "L2NR" ); - assert.equalBN( - await l2TokenStub.totalSupply(), - totalSupplyBefore.sub(amount) - ); - }) - - .test("finalizeDeposit() :: deposits disabled", async (ctx) => { - const { - l2TokenBridge, - accounts: { l2MessengerStubEOA, deployer, recipient }, - stubs: { l1Token: l1TokenStub, l2Token: l2TokenStub }, - } = ctx; - - await l2TokenBridge.disableDeposits(); - - assert.isFalse(await l2TokenBridge.isDepositsEnabled()); - - await assert.revertsWith( - l2TokenBridge - .connect(l2MessengerStubEOA) - .finalizeDeposit( - l1TokenStub.address, - l2TokenStub.address, - deployer.address, - recipient.address, - wei`1 ether`, - "0x" - ), - "ErrorDepositsDisabled()" - ); - }) - - .test("finalizeDeposit() :: unsupported l1Token", async (ctx) => { - const { - l2TokenBridge, - accounts: { l2MessengerStubEOA, deployer, recipient, stranger }, - stubs: { l2Token: l2TokenStub }, - } = ctx; - - await assert.revertsWith( - l2TokenBridge - .connect(l2MessengerStubEOA) - .finalizeDeposit( - stranger.address, - l2TokenStub.address, - deployer.address, - recipient.address, - wei`1 ether`, - "0x" - ), - "ErrorUnsupportedL1Token()" - ); - }) - - .test("finalizeDeposit() :: unsupported l2Token", async (ctx) => { - const { - l2TokenBridge, - accounts: { l2MessengerStubEOA, deployer, recipient, stranger }, - stubs: { l1Token: l1TokenStub }, - } = ctx; - - await assert.revertsWith( - l2TokenBridge - .connect(l2MessengerStubEOA) - .finalizeDeposit( - l1TokenStub.address, - stranger.address, - deployer.address, - recipient.address, - wei`1 ether`, - "0x" - ), - "ErrorUnsupportedL2Token()" + const tokenRateOracle = await new TokenRateOracle__factory(deployer).deploy( + l2TokenBridgeProxyAddress, + 86400 ); - }) - - .test("finalizeDeposit() :: unauthorized messenger", async (ctx) => { - const { - l2TokenBridge, - stubs: { l1Token, l2Token }, - accounts: { deployer, recipient, stranger }, - } = ctx; - - await assert.revertsWith( - l2TokenBridge - .connect(stranger) - .finalizeDeposit( - l1Token.address, - l2Token.address, - deployer.address, - recipient.address, - wei`1 ether`, - "0x" - ), - "ErrorUnauthorizedMessenger()" - ); - }) - - .test("finalizeDeposit() :: wrong cross domain sender", async (ctx) => { - const { - l2TokenBridge, - stubs: { l1Token, l2Token, l2Messenger }, - accounts: { deployer, recipient, stranger, l2MessengerStubEOA }, - } = ctx; - - await l2Messenger.setXDomainMessageSender(stranger.address); - - await assert.revertsWith( - l2TokenBridge - .connect(l2MessengerStubEOA) - .finalizeDeposit( - l1Token.address, - l2Token.address, - deployer.address, - recipient.address, - wei`1 ether`, - "0x" - ), - "ErrorWrongCrossDomainSender()" - ); - }) - .test("finalizeDeposit()", async (ctx) => { - const { - l2TokenBridge, - stubs: { l1Token, l2Token, l2Messenger }, - accounts: { deployer, recipient, l2MessengerStubEOA, l1TokenBridgeEOA }, - } = ctx; + const l2TokenRebasableStub = await new ERC20Rebasable__factory(deployer).deploy( + "L2 Token Rebasable", + "L2R", + decimals, + l2TokenNonRebasableStub.address, + tokenRateOracle.address, + l2TokenBridgeProxyAddress + ); - await l2Messenger.setXDomainMessageSender(l1TokenBridgeEOA.address); + const l2TokenBridgeImpl = await new L2ERC20TokenBridge__factory( + deployer + ).deploy( + l2MessengerStub.address, + l1TokenBridgeEOA.address, + l1TokenNonRebasableStub.address, + l1TokenRebasableStub.address, + l2TokenNonRebasableStub.address, + l2TokenRebasableStub.address + ); - const totalSupplyBefore = await l2Token.totalSupply(); + const l2TokenBridgeProxy = await new OssifiableProxy__factory( + deployer + ).deploy( + l2TokenBridgeImpl.address, + deployer.address, + l2TokenBridgeImpl.interface.encodeFunctionData("initialize", [ + deployer.address, + ]) + ); - const amount = wei`1 ether`; - const data = "0xdeadbeaf"; + const l2TokenBridge = L2ERC20TokenBridge__factory.connect( + l2TokenBridgeProxy.address, + deployer + ); - const tx = await l2TokenBridge - .connect(l2MessengerStubEOA) - .finalizeDeposit( - l1Token.address, - l2Token.address, - deployer.address, - recipient.address, - amount, - data - ); - - await assert.emits(l2TokenBridge, tx, "DepositFinalized", [ - l1Token.address, - l2Token.address, - deployer.address, - recipient.address, - amount, - data, + const roles = await Promise.all([ + l2TokenBridge.DEPOSITS_ENABLER_ROLE(), + l2TokenBridge.DEPOSITS_DISABLER_ROLE(), + l2TokenBridge.WITHDRAWALS_ENABLER_ROLE(), + l2TokenBridge.WITHDRAWALS_DISABLER_ROLE(), ]); - assert.equalBN(await l2Token.balanceOf(recipient.address), amount); - assert.equalBN(await l2Token.totalSupply(), totalSupplyBefore.add(amount)); - }) + for (const role of roles) { + await l2TokenBridge.grantRole(role, deployer.address); + } + + await l2TokenBridge.enableDeposits(); + await l2TokenBridge.enableWithdrawals(); + + return { + stubs: { + l1TokenNonRebasable: l1TokenNonRebasableStub, + l1TokenRebasable: l1TokenRebasableStub, + l2TokenNonRebasable: l2TokenNonRebasableStub, + l2TokenRebasable: l2TokenRebasableStub, + l2Messenger: l2MessengerStub, + }, + accounts: { + deployer, + stranger, + recipient, + l2MessengerStubEOA, + emptyContractEOA, + l1TokenBridgeEOA, + }, + l2TokenBridge, + exchangeRate, + decimalsBN + }; +} - .run(); +async function predictAddresses(account: SignerWithAddress, txsCount: number) { + const currentNonce = await account.getTransactionCount(); + + const res: string[] = []; + for (let i = 0; i < txsCount; ++i) { + res.push( + getContractAddress({ + from: account.address, + nonce: currentNonce + i, + }) + ); + } + return res; +} -async function ctxFactory() { - const [deployer, stranger, recipient, l1TokenBridgeEOA, rebasableToken] = - await hre.ethers.getSigners(); - - const l2Messenger = await new CrossDomainMessengerStub__factory( - deployer - ).deploy({ value: wei.toBigNumber(wei`1 ether`) }); - - const l2MessengerStubEOA = await testing.impersonate(l2Messenger.address); - - const l1Token = await new ERC20BridgedStub__factory(deployer).deploy( - "L1 Token", - "L1" - ); - - const l2Token = await new ERC20BridgedStub__factory(deployer).deploy( - "L2 Token", - "L2" - ); - - const emptyContract = await new EmptyContractStub__factory(deployer).deploy({ - value: wei.toBigNumber(wei`1 ether`), - }); - const emptyContractEOA = await testing.impersonate(emptyContract.address); - - const l2TokenBridgeImpl = await new L2ERC20TokenBridge__factory( - deployer - ).deploy( - l2Messenger.address, - l1TokenBridgeEOA.address, - l1Token.address, - rebasableToken.address, - l2Token.address, - rebasableToken.address - ); - - const l2TokenBridgeProxy = await new OssifiableProxy__factory( - deployer - ).deploy( - l2TokenBridgeImpl.address, - deployer.address, - l2TokenBridgeImpl.interface.encodeFunctionData("initialize", [ - deployer.address, - ]) - ); - - const l2TokenBridge = L2ERC20TokenBridge__factory.connect( - l2TokenBridgeProxy.address, - deployer - ); - - await l2Token.transfer(l2TokenBridge.address, wei`100 ether`); - - const roles = await Promise.all([ - l2TokenBridge.DEPOSITS_ENABLER_ROLE(), - l2TokenBridge.DEPOSITS_DISABLER_ROLE(), - l2TokenBridge.WITHDRAWALS_ENABLER_ROLE(), - l2TokenBridge.WITHDRAWALS_DISABLER_ROLE(), - ]); - - for (const role of roles) { - await l2TokenBridge.grantRole(role, deployer.address); - } - - await l2TokenBridge.enableDeposits(); - await l2TokenBridge.enableWithdrawals(); - - return { - stubs: { l1Token, l2Token, l2Messenger: l2Messenger }, - accounts: { - deployer, - stranger, - recipient, - l2MessengerStubEOA, - emptyContractEOA, - l1TokenBridgeEOA, - }, - l2TokenBridge, - }; +async function packedTokenRateAndTimestamp(provider: JsonRpcProvider, tokenRate: BigNumber) { + const blockNumber = await provider.getBlockNumber(); + const blockTimestamp = (await provider.getBlock(blockNumber)).timestamp; + const stETHPerTokenStr = ethers.utils.hexZeroPad(tokenRate.toHexString(), 12); + const blockTimestampStr = ethers.utils.hexZeroPad(ethers.utils.hexlify(blockTimestamp), 5); + return ethers.utils.hexConcat([stETHPerTokenStr, blockTimestampStr]); } diff --git a/test/optimism/TokenRateOracle.unit.test.ts b/test/optimism/TokenRateOracle.unit.test.ts index a2bde5e0..56aafc81 100644 --- a/test/optimism/TokenRateOracle.unit.test.ts +++ b/test/optimism/TokenRateOracle.unit.test.ts @@ -44,6 +44,17 @@ unit("TokenRateOracle", ctxFactory) await assert.revertsWith(tokenRateOracle.connect(bridge).updateRate(12, 20), "ErrorIncorrectRateTimestamp()"); }) + .test("updateRate() :: dont update state if values are the same", async (ctx) => { + const { tokenRateOracle } = ctx.contracts; + const { bridge } = ctx.accounts; + + const tx1 = await tokenRateOracle.connect(bridge).updateRate(10, 1000); + await assert.emits(tokenRateOracle, tx1, "RateUpdated", [10, 1000]); + + const tx2 = await tokenRateOracle.connect(bridge).updateRate(10, 1000); + await assert.notEmits(tokenRateOracle, tx2, "RateUpdated"); + }) + .test("updateRate() :: happy path", async (ctx) => { const { tokenRateOracle } = ctx.contracts; const { bridge } = ctx.accounts; From 1cb344aa7b0355dadd8d95774111986fa4219d47 Mon Sep 17 00:00:00 2001 From: Anton Kovalchuk Date: Tue, 13 Feb 2024 11:23:11 +0100 Subject: [PATCH 027/148] fix evetns + formating --- contracts/optimism/L1ERC20TokenBridge.sol | 102 ++++++++++++++---- contracts/optimism/L2ERC20TokenBridge.sol | 62 +++++++++-- test/optimism/L1ERC20TokenBridge.unit.test.ts | 10 +- .../bridging-rebase.integration.test.ts | 8 +- 4 files changed, 140 insertions(+), 42 deletions(-) diff --git a/contracts/optimism/L1ERC20TokenBridge.sol b/contracts/optimism/L1ERC20TokenBridge.sol index 61cdb73a..be7852ed 100644 --- a/contracts/optimism/L1ERC20TokenBridge.sol +++ b/contracts/optimism/L1ERC20TokenBridge.sol @@ -116,12 +116,28 @@ contract L1ERC20TokenBridge is onlyFromCrossDomainAccount(L2_TOKEN_BRIDGE) { if (isRebasableTokenFlow(l1Token_, l2Token_)) { - uint256 stETHAmount = IERC20Wrapper(L1_TOKEN_NON_REBASABLE).unwrap(amount_); - IERC20(L1_TOKEN_REBASABLE).safeTransfer(to_, stETHAmount); - emit ERC20WithdrawalFinalized(l1Token_, l2Token_, from_, to_, amount_, data_); + uint256 rebasableTokenAmount = IERC20Wrapper(L1_TOKEN_NON_REBASABLE).unwrap(amount_); + IERC20(L1_TOKEN_REBASABLE).safeTransfer(to_, rebasableTokenAmount); + + emit ERC20WithdrawalFinalized( + L1_TOKEN_REBASABLE, + L2_TOKEN_REBASABLE, + from_, + to_, + rebasableTokenAmount, + data_ + ); } else if (isNonRebasableTokenFlow(l1Token_, l2Token_)) { IERC20(L1_TOKEN_NON_REBASABLE).safeTransfer(to_, amount_); - emit ERC20WithdrawalFinalized(l1Token_, l2Token_, from_, to_, amount_, data_); + + emit ERC20WithdrawalFinalized( + L1_TOKEN_NON_REBASABLE, + L2_TOKEN_NON_REBASABLE, + from_, + to_, + amount_, + data_ + ); } } @@ -134,31 +150,81 @@ contract L1ERC20TokenBridge is bytes memory data_ ) internal { if (isRebasableTokenFlow(l1Token_, l2Token_)) { - DepositData memory depositData = DepositData({ rate: uint96(L1_TOKEN_NON_REBASABLE_ADAPTER.tokenRate()), timestamp: uint40(block.timestamp), data: data_ }); - bytes memory encodedDepositData = encodeDepositData(depositData); if (amount_ == 0) { - _initiateERC20Deposit(l1Token_, l2Token_, msg.sender, to_, amount_, l2Gas_, encodedDepositData); + _initiateERC20Deposit( + L1_TOKEN_REBASABLE, + L2_TOKEN_REBASABLE, + msg.sender, + to_, + 0, + l2Gas_, + encodedDepositData + ); + + emit ERC20DepositInitiated( + L1_TOKEN_REBASABLE, + L2_TOKEN_REBASABLE, + msg.sender, + to_, + 0, + encodedDepositData + ); + return; } - // maybe loosing 1 wei for stETH. Check another method IERC20(L1_TOKEN_REBASABLE).safeTransferFrom(msg.sender, address(this), amount_); - if(!IERC20(L1_TOKEN_REBASABLE).approve(L1_TOKEN_NON_REBASABLE, amount_)) revert ErrorRebasableTokenApprove(); + if(!IERC20(L1_TOKEN_REBASABLE).approve(L1_TOKEN_NON_REBASABLE, amount_)) { + revert ErrorRebasableTokenApprove(); + } + uint256 nonRebasableTokenAmount = IERC20Wrapper(L1_TOKEN_NON_REBASABLE).wrap(amount_); - // when 1 wei wasnt't transfer, can this wrap be failed? - uint256 wstETHAmount = IERC20Wrapper(L1_TOKEN_NON_REBASABLE).wrap(amount_); - _initiateERC20Deposit(L1_TOKEN_REBASABLE, L2_TOKEN_REBASABLE, msg.sender, to_, wstETHAmount, l2Gas_, encodedDepositData); + _initiateERC20Deposit( + L1_TOKEN_REBASABLE, + L2_TOKEN_REBASABLE, + msg.sender, + to_, + nonRebasableTokenAmount, + l2Gas_, + encodedDepositData + ); + emit ERC20DepositInitiated( + L1_TOKEN_REBASABLE, + L2_TOKEN_REBASABLE, + msg.sender, + to_, + amount_, + encodedDepositData + ); } else if (isNonRebasableTokenFlow(l1Token_, l2Token_)) { IERC20(L1_TOKEN_NON_REBASABLE).safeTransferFrom(msg.sender, address(this), amount_); - _initiateERC20Deposit(L1_TOKEN_NON_REBASABLE, L2_TOKEN_NON_REBASABLE, msg.sender, to_, amount_, l2Gas_, data_); + + _initiateERC20Deposit( + L1_TOKEN_NON_REBASABLE, + L2_TOKEN_NON_REBASABLE, + msg.sender, + to_, + amount_, + l2Gas_, + data_ + ); + + emit ERC20DepositInitiated( + L1_TOKEN_NON_REBASABLE, + L2_TOKEN_NON_REBASABLE, + msg.sender, + to_, + amount_, + data_ + ); } } @@ -180,7 +246,6 @@ contract L1ERC20TokenBridge is uint32 l2Gas_, bytes memory data_ ) internal { - bytes memory message = abi.encodeWithSelector( IL2ERC20Bridge.finalizeDeposit.selector, l1Token_, @@ -192,15 +257,6 @@ contract L1ERC20TokenBridge is ); sendCrossDomainMessage(L2_TOKEN_BRIDGE, l2Gas_, message); - - emit ERC20DepositInitiated( - l1Token_, - l2Token_, - from_, - to_, - amount_, - data_ - ); } error ErrorSenderNotEOA(); diff --git a/contracts/optimism/L2ERC20TokenBridge.sol b/contracts/optimism/L2ERC20TokenBridge.sol index ae195718..cb8cc58f 100644 --- a/contracts/optimism/L2ERC20TokenBridge.sol +++ b/contracts/optimism/L2ERC20TokenBridge.sol @@ -97,12 +97,28 @@ contract L2ERC20TokenBridge is DepositData memory depositData = decodeDepositData(data_); ITokenRateOracle tokenRateOracle = ERC20Rebasable(L2_TOKEN_REBASABLE).TOKEN_RATE_ORACLE(); tokenRateOracle.updateRate(depositData.rate, depositData.timestamp); + ERC20Rebasable(L2_TOKEN_REBASABLE).mintShares(to_, amount_); + uint256 rebasableTokenAmount = ERC20Rebasable(L2_TOKEN_REBASABLE).getTokensByShares(amount_); - emit DepositFinalized(l1Token_, l2Token_, from_, to_, rebasableTokenAmount, depositData.data); + emit DepositFinalized( + L1_TOKEN_REBASABLE, + L2_TOKEN_REBASABLE, + from_, + to_, + rebasableTokenAmount, + depositData.data + ); } else if (isNonRebasableTokenFlow(l1Token_, l2Token_)) { IERC20Bridged(L2_TOKEN_NON_REBASABLE).bridgeMint(to_, amount_); - emit DepositFinalized(l1Token_, l2Token_, from_, to_, amount_, data_); + emit DepositFinalized( + L1_TOKEN_NON_REBASABLE, + L2_TOKEN_NON_REBASABLE, + from_, + to_, + amount_, + data_ + ); } } @@ -114,19 +130,46 @@ contract L2ERC20TokenBridge is bytes calldata data_ ) internal { if (l2Token_ == L2_TOKEN_REBASABLE) { - - // TODO: maybe loosing 1 wei here as well uint256 shares = ERC20Rebasable(L2_TOKEN_REBASABLE).getSharesByTokens(amount_); ERC20Rebasable(L2_TOKEN_REBASABLE).burnShares(msg.sender, shares); - _initiateWithdrawal(L1_TOKEN_REBASABLE, L2_TOKEN_REBASABLE, msg.sender, to_, shares, l1Gas_, data_); - emit WithdrawalInitiated(L1_TOKEN_REBASABLE, L2_TOKEN_REBASABLE, msg.sender, to_, amount_, data_); + _initiateWithdrawal( + L1_TOKEN_REBASABLE, + L2_TOKEN_REBASABLE, + msg.sender, + to_, + shares, + l1Gas_, + data_ + ); + emit WithdrawalInitiated( + L1_TOKEN_REBASABLE, + L2_TOKEN_REBASABLE, + msg.sender, + to_, + amount_, + data_ + ); } else if (l2Token_ == L2_TOKEN_NON_REBASABLE) { - IERC20Bridged(L2_TOKEN_NON_REBASABLE).bridgeBurn(msg.sender, amount_); - _initiateWithdrawal(L1_TOKEN_NON_REBASABLE, L2_TOKEN_NON_REBASABLE, msg.sender, to_, amount_, l1Gas_, data_); - emit WithdrawalInitiated(L1_TOKEN_NON_REBASABLE, L2_TOKEN_NON_REBASABLE, msg.sender, to_, amount_, data_); + _initiateWithdrawal( + L1_TOKEN_NON_REBASABLE, + L2_TOKEN_NON_REBASABLE, + msg.sender, + to_, + amount_, + l1Gas_, + data_ + ); + emit WithdrawalInitiated( + L1_TOKEN_NON_REBASABLE, + L2_TOKEN_NON_REBASABLE, + msg.sender, + to_, + amount_, + data_ + ); } } @@ -148,7 +191,6 @@ contract L2ERC20TokenBridge is uint32 l1Gas_, bytes memory data_ ) internal { - bytes memory message = abi.encodeWithSelector( IL1ERC20Bridge.finalizeERC20Withdrawal.selector, l1Token_, diff --git a/test/optimism/L1ERC20TokenBridge.unit.test.ts b/test/optimism/L1ERC20TokenBridge.unit.test.ts index 05236679..e89ecc8e 100644 --- a/test/optimism/L1ERC20TokenBridge.unit.test.ts +++ b/test/optimism/L1ERC20TokenBridge.unit.test.ts @@ -222,7 +222,7 @@ unit("Optimism :: L1ERC20TokenBridge", ctxFactory) l2TokenRebasable.address, deployer.address, deployer.address, - amountWrapped, + amount, dataToReceive, ]); @@ -474,8 +474,8 @@ unit("Optimism :: L1ERC20TokenBridge", ctxFactory) const bridgeBalanceBefore = await l1TokenNonRebasable.balanceOf(l1TokenBridge.address); const tx = await l1TokenBridge.depositERC20To( - l1TokenRebasable.address, - l2TokenRebasable.address, + l1TokenRebasable.address, + l2TokenRebasable.address, recipient.address, amount, l2Gas, @@ -490,7 +490,7 @@ unit("Optimism :: L1ERC20TokenBridge", ctxFactory) l2TokenRebasable.address, deployer.address, recipient.address, - amountWrapped, + amount, dataToReceive, ]); @@ -790,7 +790,7 @@ unit("Optimism :: L1ERC20TokenBridge", ctxFactory) l2TokenRebasable.address, deployer.address, recipient.address, - amount, + amountUnwrapped, data, ]); diff --git a/test/optimism/bridging-rebase.integration.test.ts b/test/optimism/bridging-rebase.integration.test.ts index 757f6188..1ec08c0a 100644 --- a/test/optimism/bridging-rebase.integration.test.ts +++ b/test/optimism/bridging-rebase.integration.test.ts @@ -359,7 +359,7 @@ scenario("Optimism :: Bridging integration test", ctxFactory) l2TokenRebasable.address, tokenHolderA.address, tokenHolderA.address, - depositAmountNonRebasable, + depositAmountRebasable, dataToSend, ]); @@ -546,7 +546,7 @@ scenario("Optimism :: Bridging integration test", ctxFactory) l2TokenRebasable.address, tokenHolderA.address, tokenHolderA.address, - withdrawalAmountNonRebasable, + withdrawalAmountRebasable, "0x", ]); @@ -609,7 +609,7 @@ scenario("Optimism :: Bridging integration test", ctxFactory) l2TokenRebasable.address, tokenHolderA.address, tokenHolderB.address, - depositAmountNonRebasable, + depositAmountRebasable, dataToSend, ]); @@ -813,7 +813,7 @@ scenario("Optimism :: Bridging integration test", ctxFactory) l2TokenRebasable.address, tokenHolderB.address, tokenHolderA.address, - withdrawalAmountNonRebasable, + withdrawalAmountRebasable, "0x", ]); From 3fd1e94d555fb2b7b9f6a1f04b8c87c75b3691ab Mon Sep 17 00:00:00 2001 From: Anton Kovalchuk Date: Wed, 6 Mar 2024 10:29:34 +0200 Subject: [PATCH 028/148] move token rate to the base contract --- contracts/optimism/L1ERC20TokenBridge.sol | 35 +++++++++++++++---- contracts/stubs/ERC20WrapperStub.sol | 2 +- .../token/L1TokenNonRebasableAdapter.sol | 23 ------------ contracts/token/interfaces/IERC20WstETH.sol | 2 +- test/optimism/L1ERC20TokenBridge.unit.test.ts | 12 +++---- test/optimism/L2ERC20TokenBridge.unit.test.ts | 4 +-- .../bridging-rebase.integration.test.ts | 6 ++-- 7 files changed, 42 insertions(+), 42 deletions(-) delete mode 100644 contracts/token/L1TokenNonRebasableAdapter.sol diff --git a/contracts/optimism/L1ERC20TokenBridge.sol b/contracts/optimism/L1ERC20TokenBridge.sol index be7852ed..dc72fd1b 100644 --- a/contracts/optimism/L1ERC20TokenBridge.sol +++ b/contracts/optimism/L1ERC20TokenBridge.sol @@ -10,17 +10,17 @@ import {SafeERC20} from "@openzeppelin/contracts/token/ERC20/utils/SafeERC20.sol import {IL1ERC20Bridge} from "./interfaces/IL1ERC20Bridge.sol"; import {IL2ERC20Bridge} from "./interfaces/IL2ERC20Bridge.sol"; import {IERC20Wrapper} from "../token/interfaces/IERC20Wrapper.sol"; -import {L1TokenNonRebasableAdapter} from "../token/L1TokenNonRebasableAdapter.sol"; import {BridgingManager} from "../BridgingManager.sol"; import {RebasableAndNonRebasableTokens} from "./RebasableAndNonRebasableTokens.sol"; import {CrossDomainEnabled} from "./CrossDomainEnabled.sol"; import {DepositDataCodec} from "./DepositDataCodec.sol"; +import {IERC20WstETH} from "../token/interfaces/IERC20WstETH.sol"; /// @author psirex, kovalgek /// @notice The L1 ERC20 token bridge locks bridged tokens on the L1 side, sends deposit messages /// on the L2 side, and finalizes token withdrawals from L2. Additionally, adds the methods for /// bridging management: enabling and disabling withdrawals/deposits -contract L1ERC20TokenBridge is +abstract contract L1ERC20TokenBridgeBase is IL1ERC20Bridge, BridgingManager, RebasableAndNonRebasableTokens, @@ -31,8 +31,6 @@ contract L1ERC20TokenBridge is address public immutable L2_TOKEN_BRIDGE; - L1TokenNonRebasableAdapter public immutable L1_TOKEN_NON_REBASABLE_ADAPTER; - /// @param messenger_ L1 messenger address being used for cross-chain communications /// @param l2TokenBridge_ Address of the corresponding L2 bridge /// @param l1TokenNonRebasable_ Address of the bridged token in the L1 chain @@ -48,9 +46,10 @@ contract L1ERC20TokenBridge is address l2TokenRebasable_ ) CrossDomainEnabled(messenger_) RebasableAndNonRebasableTokens(l1TokenNonRebasable_, l1TokenRebasable_, l2TokenNonRebasable_, l2TokenRebasable_) { L2_TOKEN_BRIDGE = l2TokenBridge_; - L1_TOKEN_NON_REBASABLE_ADAPTER = new L1TokenNonRebasableAdapter(l1TokenNonRebasable_); } + function tokenRate() virtual internal view returns (uint256); + /// @notice Pushes token rate to L2 by depositing zero tokens. /// @param l2Gas_ Gas limit required to complete the deposit on L2. function pushTokenRate(uint32 l2Gas_) external { @@ -151,7 +150,7 @@ contract L1ERC20TokenBridge is ) internal { if (isRebasableTokenFlow(l1Token_, l2Token_)) { DepositData memory depositData = DepositData({ - rate: uint96(L1_TOKEN_NON_REBASABLE_ADAPTER.tokenRate()), + rate: uint96(tokenRate()), timestamp: uint40(block.timestamp), data: data_ }); @@ -262,3 +261,27 @@ contract L1ERC20TokenBridge is error ErrorSenderNotEOA(); error ErrorRebasableTokenApprove(); } + +contract L1ERC20TokenBridge is L1ERC20TokenBridgeBase { + + constructor( + address messenger_, + address l2TokenBridge_, + address l1TokenNonRebasable_, + address l1TokenRebasable_, + address l2TokenNonRebasable_, + address l2TokenRebasable_ + ) L1ERC20TokenBridgeBase( + messenger_, + l2TokenBridge_, + l1TokenNonRebasable_, + l1TokenRebasable_, + l2TokenNonRebasable_, + l2TokenRebasable_ + ) { + } + + function tokenRate() override internal view returns (uint256) { + return IERC20WstETH(L1_TOKEN_NON_REBASABLE).stEthPerToken(); + } +} diff --git a/contracts/stubs/ERC20WrapperStub.sol b/contracts/stubs/ERC20WrapperStub.sol index 2adaa9e9..b23817bc 100644 --- a/contracts/stubs/ERC20WrapperStub.sol +++ b/contracts/stubs/ERC20WrapperStub.sol @@ -47,7 +47,7 @@ contract ERC20WrapperStub is IERC20Wrapper, IERC20WstETH, ERC20 { return stETHAmount; } - function stETHPerToken() external view returns (uint256) { + function stEthPerToken() external view returns (uint256) { return tokensRate; } } diff --git a/contracts/token/L1TokenNonRebasableAdapter.sol b/contracts/token/L1TokenNonRebasableAdapter.sol deleted file mode 100644 index 1d943853..00000000 --- a/contracts/token/L1TokenNonRebasableAdapter.sol +++ /dev/null @@ -1,23 +0,0 @@ -// SPDX-FileCopyrightText: 2022 Lido -// SPDX-License-Identifier: GPL-3.0 - -pragma solidity 0.8.10; - -import {IERC20TokenRate} from "../token/interfaces/IERC20TokenRate.sol"; -import {IERC20WstETH} from "../token/interfaces/IERC20WstETH.sol"; - -/// @author kovalgek -/// @notice Hides wstETH concept from other contracts to save level of abstraction. -contract L1TokenNonRebasableAdapter is IERC20TokenRate { - - IERC20WstETH public immutable WSTETH; - - constructor(address wstETH_) { - WSTETH = IERC20WstETH(wstETH_); - } - - /// @inheritdoc IERC20TokenRate - function tokenRate() external view returns (uint256) { - return WSTETH.stETHPerToken(); - } -} diff --git a/contracts/token/interfaces/IERC20WstETH.sol b/contracts/token/interfaces/IERC20WstETH.sol index 19a9badb..4bb216c4 100644 --- a/contracts/token/interfaces/IERC20WstETH.sol +++ b/contracts/token/interfaces/IERC20WstETH.sol @@ -10,5 +10,5 @@ interface IERC20WstETH { * @notice Get amount of wstETH for a one stETH * @return Amount of wstETH for a 1 stETH */ - function stETHPerToken() external view returns (uint256); + function stEthPerToken() external view returns (uint256); } diff --git a/test/optimism/L1ERC20TokenBridge.unit.test.ts b/test/optimism/L1ERC20TokenBridge.unit.test.ts index e89ecc8e..00f269e1 100644 --- a/test/optimism/L1ERC20TokenBridge.unit.test.ts +++ b/test/optimism/L1ERC20TokenBridge.unit.test.ts @@ -196,7 +196,7 @@ unit("Optimism :: L1ERC20TokenBridge", ctxFactory) const l2Gas = wei`0.99 wei`; const amount = wei`1 ether`; const data = "0xdeadbeaf"; - const rate = await l1TokenNonRebasable.stETHPerToken(); + const rate = await l1TokenNonRebasable.stEthPerToken(); const decimalsStr = await l1TokenNonRebasable.decimals(); const decimals = BigNumber.from(10).pow(decimalsStr); @@ -462,7 +462,7 @@ unit("Optimism :: L1ERC20TokenBridge", ctxFactory) const amount = wei`1 ether`; const data = "0x"; - const rate = await l1TokenNonRebasable.stETHPerToken(); + const rate = await l1TokenNonRebasable.stEthPerToken(); const decimalsStr = await l1TokenNonRebasable.decimals(); const decimals = BigNumber.from(10).pow(decimalsStr); @@ -761,7 +761,7 @@ unit("Optimism :: L1ERC20TokenBridge", ctxFactory) const amount = wei`1 ether`; const data = "0xdeadbeaf"; - const rate = await l1TokenNonRebasable.stETHPerToken(); + const rate = await l1TokenNonRebasable.stEthPerToken(); const decimalsStr = await l1TokenNonRebasable.decimals(); const decimals = BigNumber.from(10).pow(decimalsStr); @@ -909,10 +909,10 @@ async function ctxFactory() { } async function packedTokenRateAndTimestamp(l1Provider: JsonRpcProvider, l1Token: ERC20WrapperStub) { - const stETHPerToken = await l1Token.stETHPerToken(); + const stEthPerToken = await l1Token.stEthPerToken(); const blockNumber = await l1Provider.getBlockNumber(); const blockTimestamp = (await l1Provider.getBlock(blockNumber)).timestamp; - const stETHPerTokenStr = ethers.utils.hexZeroPad(stETHPerToken.toHexString(), 12); + const stEthPerTokenStr = ethers.utils.hexZeroPad(stEthPerToken.toHexString(), 12); const blockTimestampStr = ethers.utils.hexZeroPad(ethers.utils.hexlify(blockTimestamp), 5); - return ethers.utils.hexConcat([stETHPerTokenStr, blockTimestampStr]); + return ethers.utils.hexConcat([stEthPerTokenStr, blockTimestampStr]); } diff --git a/test/optimism/L2ERC20TokenBridge.unit.test.ts b/test/optimism/L2ERC20TokenBridge.unit.test.ts index 17bbb425..4695718d 100644 --- a/test/optimism/L2ERC20TokenBridge.unit.test.ts +++ b/test/optimism/L2ERC20TokenBridge.unit.test.ts @@ -805,7 +805,7 @@ async function predictAddresses(account: SignerWithAddress, txsCount: number) { async function packedTokenRateAndTimestamp(provider: JsonRpcProvider, tokenRate: BigNumber) { const blockNumber = await provider.getBlockNumber(); const blockTimestamp = (await provider.getBlock(blockNumber)).timestamp; - const stETHPerTokenStr = ethers.utils.hexZeroPad(tokenRate.toHexString(), 12); + const stEthPerTokenStr = ethers.utils.hexZeroPad(tokenRate.toHexString(), 12); const blockTimestampStr = ethers.utils.hexZeroPad(ethers.utils.hexlify(blockTimestamp), 5); - return ethers.utils.hexConcat([stETHPerTokenStr, blockTimestampStr]); + return ethers.utils.hexConcat([stEthPerTokenStr, blockTimestampStr]); } diff --git a/test/optimism/bridging-rebase.integration.test.ts b/test/optimism/bridging-rebase.integration.test.ts index 1ec08c0a..7ff06ba1 100644 --- a/test/optimism/bridging-rebase.integration.test.ts +++ b/test/optimism/bridging-rebase.integration.test.ts @@ -918,10 +918,10 @@ async function ctxFactory() { } async function packedTokenRateAndTimestamp(l1Provider: JsonRpcProvider, l1Token: ERC20WrapperStub) { - const stETHPerToken = await l1Token.stETHPerToken(); + const stEthPerToken = await l1Token.stEthPerToken(); const blockNumber = await l1Provider.getBlockNumber(); const blockTimestamp = (await l1Provider.getBlock(blockNumber)).timestamp; - const stETHPerTokenStr = ethers.utils.hexZeroPad(stETHPerToken.toHexString(), 12); + const stEthPerTokenStr = ethers.utils.hexZeroPad(stEthPerToken.toHexString(), 12); const blockTimestampStr = ethers.utils.hexZeroPad(ethers.utils.hexlify(blockTimestamp), 5); - return ethers.utils.hexConcat([stETHPerTokenStr, blockTimestampStr]); + return ethers.utils.hexConcat([stEthPerTokenStr, blockTimestampStr]); } From 66b250119a702ccdd07f3a7e5932ea5ca041e075 Mon Sep 17 00:00:00 2001 From: Anton Kovalchuk Date: Wed, 6 Mar 2024 15:20:51 +0200 Subject: [PATCH 029/148] use inheritance for token rate in l1 bridge, fix e2e tests --- contracts/optimism/L1ERC20TokenBridge.sol | 26 +---- contracts/optimism/L1LidoTokensBridge.sol | 33 ++++++ test/optimism/L1ERC20TokenBridge.unit.test.ts | 8 +- test/optimism/L2ERC20TokenBridge.unit.test.ts | 10 +- test/optimism/_launch.test.ts | 26 ++--- .../bridging-rebase.integration.test.ts | 110 +++++++++--------- test/optimism/bridging-to.e2e.test.ts | 10 +- test/optimism/bridging.e2e.test.ts | 12 +- test/optimism/bridging.integration.test.ts | 74 ++++++------ test/optimism/deployment.acceptance.test.ts | 36 +++--- test/optimism/deposit-gas-estimation.test.ts | 34 +++--- utils/optimism/LidoBridgeAdapter.ts | 45 +++++++ utils/optimism/deployment.ts | 7 +- utils/optimism/testing.ts | 30 ++--- 14 files changed, 257 insertions(+), 204 deletions(-) create mode 100644 contracts/optimism/L1LidoTokensBridge.sol create mode 100644 utils/optimism/LidoBridgeAdapter.ts diff --git a/contracts/optimism/L1ERC20TokenBridge.sol b/contracts/optimism/L1ERC20TokenBridge.sol index dc72fd1b..7b30efe2 100644 --- a/contracts/optimism/L1ERC20TokenBridge.sol +++ b/contracts/optimism/L1ERC20TokenBridge.sol @@ -20,7 +20,7 @@ import {IERC20WstETH} from "../token/interfaces/IERC20WstETH.sol"; /// @notice The L1 ERC20 token bridge locks bridged tokens on the L1 side, sends deposit messages /// on the L2 side, and finalizes token withdrawals from L2. Additionally, adds the methods for /// bridging management: enabling and disabling withdrawals/deposits -abstract contract L1ERC20TokenBridgeBase is +abstract contract L1ERC20TokenBridge is IL1ERC20Bridge, BridgingManager, RebasableAndNonRebasableTokens, @@ -261,27 +261,3 @@ abstract contract L1ERC20TokenBridgeBase is error ErrorSenderNotEOA(); error ErrorRebasableTokenApprove(); } - -contract L1ERC20TokenBridge is L1ERC20TokenBridgeBase { - - constructor( - address messenger_, - address l2TokenBridge_, - address l1TokenNonRebasable_, - address l1TokenRebasable_, - address l2TokenNonRebasable_, - address l2TokenRebasable_ - ) L1ERC20TokenBridgeBase( - messenger_, - l2TokenBridge_, - l1TokenNonRebasable_, - l1TokenRebasable_, - l2TokenNonRebasable_, - l2TokenRebasable_ - ) { - } - - function tokenRate() override internal view returns (uint256) { - return IERC20WstETH(L1_TOKEN_NON_REBASABLE).stEthPerToken(); - } -} diff --git a/contracts/optimism/L1LidoTokensBridge.sol b/contracts/optimism/L1LidoTokensBridge.sol new file mode 100644 index 00000000..b78e223e --- /dev/null +++ b/contracts/optimism/L1LidoTokensBridge.sol @@ -0,0 +1,33 @@ +// SPDX-FileCopyrightText: 2022 Lido +// SPDX-License-Identifier: GPL-3.0 + +pragma solidity 0.8.10; + +import {L1ERC20TokenBridge} from "./L1ERC20TokenBridge.sol"; +import {IERC20WstETH} from "../token/interfaces/IERC20WstETH.sol"; + +/// @author kovalgek +/// @notice Hides wstETH concept from other contracts to save level of abstraction. +contract L1LidoTokensBridge is L1ERC20TokenBridge { + + constructor( + address messenger_, + address l2TokenBridge_, + address l1TokenNonRebasable_, + address l1TokenRebasable_, + address l2TokenNonRebasable_, + address l2TokenRebasable_ + ) L1ERC20TokenBridge( + messenger_, + l2TokenBridge_, + l1TokenNonRebasable_, + l1TokenRebasable_, + l2TokenNonRebasable_, + l2TokenRebasable_ + ) { + } + + function tokenRate() override internal view returns (uint256) { + return IERC20WstETH(L1_TOKEN_NON_REBASABLE).stEthPerToken(); + } +} diff --git a/test/optimism/L1ERC20TokenBridge.unit.test.ts b/test/optimism/L1ERC20TokenBridge.unit.test.ts index 00f269e1..efafeb57 100644 --- a/test/optimism/L1ERC20TokenBridge.unit.test.ts +++ b/test/optimism/L1ERC20TokenBridge.unit.test.ts @@ -3,7 +3,7 @@ import hre, { ethers } from "hardhat"; import { ERC20BridgedStub__factory, ERC20WrapperStub__factory, - L1ERC20TokenBridge__factory, + L1LidoTokensBridge__factory, L2ERC20TokenBridge__factory, OssifiableProxy__factory, EmptyContractStub__factory, @@ -15,7 +15,7 @@ import { wei } from "../../utils/wei"; import { BigNumber } from "ethers"; import { ERC20WrapperStub } from "../../typechain"; -unit("Optimism :: L1ERC20TokenBridge", ctxFactory) +unit("Optimism :: L1LidoTokensBridge", ctxFactory) .test("l2TokenBridge()", async (ctx) => { assert.equal( await ctx.l1TokenBridge.l2TokenBridge(), @@ -844,7 +844,7 @@ async function ctxFactory() { l1MessengerStub.address ); - const l1TokenBridgeImpl = await new L1ERC20TokenBridge__factory( + const l1TokenBridgeImpl = await new L1LidoTokensBridge__factory( deployer ).deploy( l1MessengerStub.address, @@ -865,7 +865,7 @@ async function ctxFactory() { ]) ); - const l1TokenBridge = L1ERC20TokenBridge__factory.connect( + const l1TokenBridge = L1LidoTokensBridge__factory.connect( l1TokenBridgeProxy.address, deployer ); diff --git a/test/optimism/L2ERC20TokenBridge.unit.test.ts b/test/optimism/L2ERC20TokenBridge.unit.test.ts index 4695718d..a3906b0b 100644 --- a/test/optimism/L2ERC20TokenBridge.unit.test.ts +++ b/test/optimism/L2ERC20TokenBridge.unit.test.ts @@ -4,7 +4,7 @@ import { ERC20WrapperStub__factory, TokenRateOracle__factory, ERC20Rebasable__factory, - L1ERC20TokenBridge__factory, + L1LidoTokensBridge__factory, L2ERC20TokenBridge__factory, OssifiableProxy__factory, EmptyContractStub__factory, @@ -105,7 +105,7 @@ unit("Optimism:: L2ERC20TokenBridge", ctxFactory) await assert.emits(l2Messenger, tx, "SentMessage", [ l1TokenBridgeEOA.address, l2TokenBridge.address, - L1ERC20TokenBridge__factory.createInterface().encodeFunctionData( + L1LidoTokensBridge__factory.createInterface().encodeFunctionData( "finalizeERC20Withdrawal", [ l1TokenNonRebasable.address, @@ -184,7 +184,7 @@ unit("Optimism:: L2ERC20TokenBridge", ctxFactory) await assert.emits(l2Messenger, tx, "SentMessage", [ l1TokenBridgeEOA.address, l2TokenBridge.address, - L1ERC20TokenBridge__factory.createInterface().encodeFunctionData( + L1LidoTokensBridge__factory.createInterface().encodeFunctionData( "finalizeERC20Withdrawal", [ l1TokenRebasable.address, @@ -298,7 +298,7 @@ unit("Optimism:: L2ERC20TokenBridge", ctxFactory) await assert.emits(l2MessengerStub, tx, "SentMessage", [ l1TokenBridgeEOA.address, l2TokenBridge.address, - L1ERC20TokenBridge__factory.createInterface().encodeFunctionData( + L1LidoTokensBridge__factory.createInterface().encodeFunctionData( "finalizeERC20Withdrawal", [ l1TokenNonRebasable.address, @@ -379,7 +379,7 @@ unit("Optimism:: L2ERC20TokenBridge", ctxFactory) await assert.emits(l2Messenger, tx, "SentMessage", [ l1TokenBridgeEOA.address, l2TokenBridge.address, - L1ERC20TokenBridge__factory.createInterface().encodeFunctionData( + L1LidoTokensBridge__factory.createInterface().encodeFunctionData( "finalizeERC20Withdrawal", [ l1TokenRebasable.address, diff --git a/test/optimism/_launch.test.ts b/test/optimism/_launch.test.ts index 4d192be3..41040dd2 100644 --- a/test/optimism/_launch.test.ts +++ b/test/optimism/_launch.test.ts @@ -5,7 +5,7 @@ import { wei } from "../../utils/wei"; import optimism from "../../utils/optimism"; import testing, { scenario } from "../../utils/testing"; import { BridgingManagerRole } from "../../utils/bridging-management"; -import { L1ERC20TokenBridge__factory } from "../../typechain"; +import { L1LidoTokensBridge__factory } from "../../typechain"; const REVERT = env.bool("REVERT", true); @@ -22,28 +22,28 @@ scenario("Optimism :: Launch integration test", ctxFactory) }) .step("Enable deposits", async (ctx) => { - const { l1ERC20TokenBridge } = ctx; - assert.isFalse(await l1ERC20TokenBridge.isDepositsEnabled()); + const { l1LidoTokensBridge } = ctx; + assert.isFalse(await l1LidoTokensBridge.isDepositsEnabled()); - await l1ERC20TokenBridge.enableDeposits(); - assert.isTrue(await l1ERC20TokenBridge.isDepositsEnabled()); + await l1LidoTokensBridge.enableDeposits(); + assert.isTrue(await l1LidoTokensBridge.isDepositsEnabled()); }) .step("Renounce role", async (ctx) => { - const { l1ERC20TokenBridge, l1DevMultisig } = ctx; + const { l1LidoTokensBridge, l1DevMultisig } = ctx; assert.isTrue( - await l1ERC20TokenBridge.hasRole( + await l1LidoTokensBridge.hasRole( BridgingManagerRole.DEPOSITS_ENABLER_ROLE.hash, await l1DevMultisig.getAddress() ) ); - await l1ERC20TokenBridge.renounceRole( + await l1LidoTokensBridge.renounceRole( BridgingManagerRole.DEPOSITS_ENABLER_ROLE.hash, await l1DevMultisig.getAddress() ); assert.isFalse( - await l1ERC20TokenBridge.hasRole( + await l1LidoTokensBridge.hasRole( BridgingManagerRole.DEPOSITS_ENABLER_ROLE.hash, await l1DevMultisig.getAddress() ) @@ -55,7 +55,7 @@ scenario("Optimism :: Launch integration test", ctxFactory) async function ctxFactory() { const networkName = env.network("TESTING_OPT_NETWORK", "mainnet"); - const { l1Provider, l2Provider, l1ERC20TokenBridge } = await optimism + const { l1Provider, l2Provider, l1LidoTokensBridge } = await optimism .testing(networkName) .getIntegrationTestSetup(); @@ -73,8 +73,8 @@ async function ctxFactory() { l1Provider ); - const l1ERC20TokenBridgeImpl = L1ERC20TokenBridge__factory.connect( - l1ERC20TokenBridge.address, + const l1LidoTokensBridgeImpl = L1LidoTokensBridge__factory.connect( + l1LidoTokensBridge.address, l1DevMultisig ); @@ -82,7 +82,7 @@ async function ctxFactory() { l1Provider, l2Provider, l1DevMultisig, - l1ERC20TokenBridge: l1ERC20TokenBridgeImpl, + l1LidoTokensBridge: l1LidoTokensBridgeImpl, snapshot: { l1: l1Snapshot, l2: l2Snapshot, diff --git a/test/optimism/bridging-rebase.integration.test.ts b/test/optimism/bridging-rebase.integration.test.ts index 7ff06ba1..3d5f62a8 100644 --- a/test/optimism/bridging-rebase.integration.test.ts +++ b/test/optimism/bridging-rebase.integration.test.ts @@ -16,13 +16,13 @@ scenario("Optimism :: Bridging integration test", ctxFactory) }) .step("Activate bridging on L1", async (ctx) => { - const { l1ERC20TokenBridge } = ctx; + const { l1LidoTokensBridge } = ctx; const { l1ERC20TokenBridgeAdmin } = ctx.accounts; - const isDepositsEnabled = await l1ERC20TokenBridge.isDepositsEnabled(); + const isDepositsEnabled = await l1LidoTokensBridge.isDepositsEnabled(); if (!isDepositsEnabled) { - await l1ERC20TokenBridge + await l1LidoTokensBridge .connect(l1ERC20TokenBridgeAdmin) .enableDeposits(); } else { @@ -30,18 +30,18 @@ scenario("Optimism :: Bridging integration test", ctxFactory) } const isWithdrawalsEnabled = - await l1ERC20TokenBridge.isWithdrawalsEnabled(); + await l1LidoTokensBridge.isWithdrawalsEnabled(); if (!isWithdrawalsEnabled) { - await l1ERC20TokenBridge + await l1LidoTokensBridge .connect(l1ERC20TokenBridgeAdmin) .enableWithdrawals(); } else { console.log("L1 withdrawals already enabled"); } - assert.isTrue(await l1ERC20TokenBridge.isDepositsEnabled()); - assert.isTrue(await l1ERC20TokenBridge.isWithdrawalsEnabled()); + assert.isTrue(await l1LidoTokensBridge.isDepositsEnabled()); + assert.isTrue(await l1LidoTokensBridge.isWithdrawalsEnabled()); }) .step("Activate bridging on L2", async (ctx) => { @@ -79,7 +79,7 @@ scenario("Optimism :: Bridging integration test", ctxFactory) l1Token, l1TokenRebasable, l2TokenRebasable, - l1ERC20TokenBridge, + l1LidoTokensBridge, l2CrossDomainMessenger, l2ERC20TokenBridge, l2Provider @@ -93,7 +93,7 @@ scenario("Optimism :: Bridging integration test", ctxFactory) .connect(l1CrossDomainMessengerAliased) .relayMessage( 1, - l1ERC20TokenBridge.address, + l1LidoTokensBridge.address, l2ERC20TokenBridge.address, 0, 300_000, @@ -113,7 +113,7 @@ scenario("Optimism :: Bridging integration test", ctxFactory) const { l1Token, l1TokenRebasable, - l1ERC20TokenBridge, + l1LidoTokensBridge, l2TokenRebasable, l1CrossDomainMessenger, l2ERC20TokenBridge, @@ -127,16 +127,16 @@ scenario("Optimism :: Bridging integration test", ctxFactory) ); const l1ERC20TokenBridgeBalanceBefore = await l1TokenRebasable.balanceOf( - l1ERC20TokenBridge.address + l1LidoTokensBridge.address ); - const tx = await l1ERC20TokenBridge + const tx = await l1LidoTokensBridge .connect(l1Stranger) .pushTokenRate(200_000); const dataToSend = await packedTokenRateAndTimestamp(l1Provider, l1Token); - await assert.emits(l1ERC20TokenBridge, tx, "ERC20DepositInitiated", [ + await assert.emits(l1LidoTokensBridge, tx, "ERC20DepositInitiated", [ l1TokenRebasable.address, l2TokenRebasable.address, l1Stranger.address, @@ -161,14 +161,14 @@ scenario("Optimism :: Bridging integration test", ctxFactory) await assert.emits(l1CrossDomainMessenger, tx, "SentMessage", [ l2ERC20TokenBridge.address, - l1ERC20TokenBridge.address, + l1LidoTokensBridge.address, l2DepositCalldata, messageNonce, 200_000, ]); assert.equalBN( - await l1Token.balanceOf(l1ERC20TokenBridge.address), + await l1Token.balanceOf(l1LidoTokensBridge.address), l1ERC20TokenBridgeBalanceBefore ); @@ -182,7 +182,7 @@ scenario("Optimism :: Bridging integration test", ctxFactory) const { l1Token, l1TokenRebasable, - l1ERC20TokenBridge, + l1LidoTokensBridge, l2TokenRebasable, l1CrossDomainMessenger, l2ERC20TokenBridge, @@ -193,17 +193,17 @@ scenario("Optimism :: Bridging integration test", ctxFactory) await l1TokenRebasable .connect(tokenHolderA.l1Signer) - .approve(l1ERC20TokenBridge.address, 0); + .approve(l1LidoTokensBridge.address, 0); const tokenHolderABalanceBefore = await l1TokenRebasable.balanceOf( tokenHolderA.address ); const l1ERC20TokenBridgeBalanceBefore = await l1TokenRebasable.balanceOf( - l1ERC20TokenBridge.address + l1LidoTokensBridge.address ); - const tx = await l1ERC20TokenBridge + const tx = await l1LidoTokensBridge .connect(tokenHolderA.l1Signer) .depositERC20( l1TokenRebasable.address, @@ -215,7 +215,7 @@ scenario("Optimism :: Bridging integration test", ctxFactory) const dataToSend = await packedTokenRateAndTimestamp(l1Provider, l1Token); - await assert.emits(l1ERC20TokenBridge, tx, "ERC20DepositInitiated", [ + await assert.emits(l1LidoTokensBridge, tx, "ERC20DepositInitiated", [ l1TokenRebasable.address, l2TokenRebasable.address, tokenHolderA.address, @@ -240,14 +240,14 @@ scenario("Optimism :: Bridging integration test", ctxFactory) await assert.emits(l1CrossDomainMessenger, tx, "SentMessage", [ l2ERC20TokenBridge.address, - l1ERC20TokenBridge.address, + l1LidoTokensBridge.address, l2DepositCalldata, messageNonce, 200_000, ]); assert.equalBN( - await l1Token.balanceOf(l1ERC20TokenBridge.address), + await l1Token.balanceOf(l1LidoTokensBridge.address), l1ERC20TokenBridgeBalanceBefore ); @@ -262,7 +262,7 @@ scenario("Optimism :: Bridging integration test", ctxFactory) l1Token, l1TokenRebasable, l2TokenRebasable, - l1ERC20TokenBridge, + l1LidoTokensBridge, l2CrossDomainMessenger, l2ERC20TokenBridge, l2Provider @@ -283,7 +283,7 @@ scenario("Optimism :: Bridging integration test", ctxFactory) .connect(l1CrossDomainMessengerAliased) .relayMessage( 1, - l1ERC20TokenBridge.address, + l1LidoTokensBridge.address, l2ERC20TokenBridge.address, 0, 300_000, @@ -321,7 +321,7 @@ scenario("Optimism :: Bridging integration test", ctxFactory) const { l1Token, l1TokenRebasable, - l1ERC20TokenBridge, + l1LidoTokensBridge, l2TokenRebasable, l1CrossDomainMessenger, l2ERC20TokenBridge, @@ -332,17 +332,17 @@ scenario("Optimism :: Bridging integration test", ctxFactory) await l1TokenRebasable .connect(tokenHolderA.l1Signer) - .approve(l1ERC20TokenBridge.address, depositAmountRebasable); + .approve(l1LidoTokensBridge.address, depositAmountRebasable); const tokenHolderABalanceBefore = await l1TokenRebasable.balanceOf( tokenHolderA.address ); const l1ERC20TokenBridgeBalanceBefore = await l1TokenRebasable.balanceOf( - l1ERC20TokenBridge.address + l1LidoTokensBridge.address ); - const tx = await l1ERC20TokenBridge + const tx = await l1LidoTokensBridge .connect(tokenHolderA.l1Signer) .depositERC20( l1TokenRebasable.address, @@ -354,7 +354,7 @@ scenario("Optimism :: Bridging integration test", ctxFactory) const dataToSend = await packedTokenRateAndTimestamp(l1Provider, l1Token); - await assert.emits(l1ERC20TokenBridge, tx, "ERC20DepositInitiated", [ + await assert.emits(l1LidoTokensBridge, tx, "ERC20DepositInitiated", [ l1TokenRebasable.address, l2TokenRebasable.address, tokenHolderA.address, @@ -379,14 +379,14 @@ scenario("Optimism :: Bridging integration test", ctxFactory) await assert.emits(l1CrossDomainMessenger, tx, "SentMessage", [ l2ERC20TokenBridge.address, - l1ERC20TokenBridge.address, + l1LidoTokensBridge.address, l2DepositCalldata, messageNonce, 200_000, ]); assert.equalBN( - await l1Token.balanceOf(l1ERC20TokenBridge.address), + await l1Token.balanceOf(l1LidoTokensBridge.address), l1ERC20TokenBridgeBalanceBefore.add(depositAmountNonRebasable) ); @@ -401,7 +401,7 @@ scenario("Optimism :: Bridging integration test", ctxFactory) l1Token, l1TokenRebasable, l2TokenRebasable, - l1ERC20TokenBridge, + l1LidoTokensBridge, l2CrossDomainMessenger, l2ERC20TokenBridge, l2Provider @@ -422,7 +422,7 @@ scenario("Optimism :: Bridging integration test", ctxFactory) .connect(l1CrossDomainMessengerAliased) .relayMessage( 1, - l1ERC20TokenBridge.address, + l1LidoTokensBridge.address, l2ERC20TokenBridge.address, 0, 300_000, @@ -503,7 +503,7 @@ scenario("Optimism :: Bridging integration test", ctxFactory) l1Token, l1TokenRebasable, l1CrossDomainMessenger, - l1ERC20TokenBridge, + l1LidoTokensBridge, l2CrossDomainMessenger, l2TokenRebasable, l2ERC20TokenBridge, @@ -515,7 +515,7 @@ scenario("Optimism :: Bridging integration test", ctxFactory) tokenHolderA.address ); const l1ERC20TokenBridgeBalanceBefore = await l1Token.balanceOf( - l1ERC20TokenBridge.address + l1LidoTokensBridge.address ); await l1CrossDomainMessenger @@ -525,9 +525,9 @@ scenario("Optimism :: Bridging integration test", ctxFactory) const tx = await l1CrossDomainMessenger .connect(l1Stranger) .relayMessage( - l1ERC20TokenBridge.address, + l1LidoTokensBridge.address, l2CrossDomainMessenger.address, - l1ERC20TokenBridge.interface.encodeFunctionData( + l1LidoTokensBridge.interface.encodeFunctionData( "finalizeERC20Withdrawal", [ l1TokenRebasable.address, @@ -541,7 +541,7 @@ scenario("Optimism :: Bridging integration test", ctxFactory) 0 ); - await assert.emits(l1ERC20TokenBridge, tx, "ERC20WithdrawalFinalized", [ + await assert.emits(l1LidoTokensBridge, tx, "ERC20WithdrawalFinalized", [ l1TokenRebasable.address, l2TokenRebasable.address, tokenHolderA.address, @@ -551,7 +551,7 @@ scenario("Optimism :: Bridging integration test", ctxFactory) ]); assert.equalBN( - await l1Token.balanceOf(l1ERC20TokenBridge.address), + await l1Token.balanceOf(l1LidoTokensBridge.address), l1ERC20TokenBridgeBalanceBefore.sub(withdrawalAmountNonRebasable) ); @@ -567,7 +567,7 @@ scenario("Optimism :: Bridging integration test", ctxFactory) const { l1Token, l1TokenRebasable, - l1ERC20TokenBridge, + l1LidoTokensBridge, l2TokenRebasable, l1CrossDomainMessenger, l2ERC20TokenBridge, @@ -582,16 +582,16 @@ scenario("Optimism :: Bridging integration test", ctxFactory) await l1TokenRebasable .connect(tokenHolderA.l1Signer) - .approve(l1ERC20TokenBridge.address, depositAmountRebasable); + .approve(l1LidoTokensBridge.address, depositAmountRebasable); const tokenHolderABalanceBefore = await l1TokenRebasable.balanceOf( tokenHolderA.address ); const l1ERC20TokenBridgeBalanceBefore = await l1Token.balanceOf( - l1ERC20TokenBridge.address + l1LidoTokensBridge.address ); - const tx = await l1ERC20TokenBridge + const tx = await l1LidoTokensBridge .connect(tokenHolderA.l1Signer) .depositERC20To( l1TokenRebasable.address, @@ -604,7 +604,7 @@ scenario("Optimism :: Bridging integration test", ctxFactory) const dataToSend = await packedTokenRateAndTimestamp(l1Provider, l1Token); - await assert.emits(l1ERC20TokenBridge, tx, "ERC20DepositInitiated", [ + await assert.emits(l1LidoTokensBridge, tx, "ERC20DepositInitiated", [ l1TokenRebasable.address, l2TokenRebasable.address, tokenHolderA.address, @@ -629,14 +629,14 @@ scenario("Optimism :: Bridging integration test", ctxFactory) await assert.emits(l1CrossDomainMessenger, tx, "SentMessage", [ l2ERC20TokenBridge.address, - l1ERC20TokenBridge.address, + l1LidoTokensBridge.address, l2DepositCalldata, messageNonce, 200_000, ]); assert.equalBN( - await l1Token.balanceOf(l1ERC20TokenBridge.address), + await l1Token.balanceOf(l1LidoTokensBridge.address), l1ERC20TokenBridgeBalanceBefore.add(depositAmountNonRebasable) ); @@ -650,7 +650,7 @@ scenario("Optimism :: Bridging integration test", ctxFactory) const { l1Token, l1TokenRebasable, - l1ERC20TokenBridge, + l1LidoTokensBridge, l2TokenRebasable, l2CrossDomainMessenger, l2ERC20TokenBridge, @@ -680,7 +680,7 @@ scenario("Optimism :: Bridging integration test", ctxFactory) .connect(l1CrossDomainMessengerAliased) .relayMessage( 1, - l1ERC20TokenBridge.address, + l1LidoTokensBridge.address, l2ERC20TokenBridge.address, 0, 300_000, @@ -763,7 +763,7 @@ scenario("Optimism :: Bridging integration test", ctxFactory) l1Token, l1TokenRebasable, l1CrossDomainMessenger, - l1ERC20TokenBridge, + l1LidoTokensBridge, l2CrossDomainMessenger, l2TokenRebasable, l2ERC20TokenBridge, @@ -782,7 +782,7 @@ scenario("Optimism :: Bridging integration test", ctxFactory) tokenHolderA.address ); const l1ERC20TokenBridgeBalanceBefore = await l1Token.balanceOf( - l1ERC20TokenBridge.address + l1LidoTokensBridge.address ); await l1CrossDomainMessenger @@ -792,9 +792,9 @@ scenario("Optimism :: Bridging integration test", ctxFactory) const tx = await l1CrossDomainMessenger .connect(l1Stranger) .relayMessage( - l1ERC20TokenBridge.address, + l1LidoTokensBridge.address, l2CrossDomainMessenger.address, - l1ERC20TokenBridge.interface.encodeFunctionData( + l1LidoTokensBridge.interface.encodeFunctionData( "finalizeERC20Withdrawal", [ l1TokenRebasable.address, @@ -808,7 +808,7 @@ scenario("Optimism :: Bridging integration test", ctxFactory) 0 ); - await assert.emits(l1ERC20TokenBridge, tx, "ERC20WithdrawalFinalized", [ + await assert.emits(l1LidoTokensBridge, tx, "ERC20WithdrawalFinalized", [ l1TokenRebasable.address, l2TokenRebasable.address, tokenHolderB.address, @@ -818,7 +818,7 @@ scenario("Optimism :: Bridging integration test", ctxFactory) ]); assert.equalBN( - await l1Token.balanceOf(l1ERC20TokenBridge.address), + await l1Token.balanceOf(l1LidoTokensBridge.address), l1ERC20TokenBridgeBalanceBefore.sub(withdrawalAmountNonRebasable) ); @@ -889,7 +889,7 @@ async function ctxFactory() { l2Provider ); - await contracts.l1ERC20TokenBridge.connect(l1ERC20TokenBridgeAdmin).pushTokenRate(1000000); + await contracts.l1LidoTokensBridge.connect(l1ERC20TokenBridgeAdmin).pushTokenRate(1000000); return { l1Provider, diff --git a/test/optimism/bridging-to.e2e.test.ts b/test/optimism/bridging-to.e2e.test.ts index a7a0dbc6..5c9788ed 100644 --- a/test/optimism/bridging-to.e2e.test.ts +++ b/test/optimism/bridging-to.e2e.test.ts @@ -38,7 +38,7 @@ scenario("Optimism :: Bridging via depositTo/withdrawTo E2E test", ctxFactory) } ) - .step("Set allowance for L1ERC20TokenBridge to deposit", async (ctx) => { + .step("Set allowance for L1LidoTokensBridge to deposit", async (ctx) => { const allowanceTxResponse = await ctx.crossChainMessenger.approveERC20( ctx.l1Token.address, ctx.l2Token.address, @@ -50,14 +50,14 @@ scenario("Optimism :: Bridging via depositTo/withdrawTo E2E test", ctxFactory) assert.equalBN( await ctx.l1Token.allowance( ctx.l1Tester.address, - ctx.l1ERC20TokenBridge.address + ctx.l1LidoTokensBridge.address ), ctx.depositAmount ); }) .step("Bridge tokens to L2 via depositERC20To()", async (ctx) => { - depositTokensTxResponse = await ctx.l1ERC20TokenBridge + depositTokensTxResponse = await ctx.l1LidoTokensBridge .connect(ctx.l1Tester) .depositERC20To( ctx.l1Token.address, @@ -145,7 +145,7 @@ async function ctxFactory() { l2Tester: testingSetup.l2Tester, l1Token: testingSetup.l1Token, l2Token: testingSetup.l2Token, - l1ERC20TokenBridge: testingSetup.l1ERC20TokenBridge, + l1LidoTokensBridge: testingSetup.l1LidoTokensBridge, l2ERC20TokenBridge: testingSetup.l2ERC20TokenBridge, crossChainMessenger: new CrossChainMessenger({ l2ChainId: network.chainId("opt", networkName), @@ -155,7 +155,7 @@ async function ctxFactory() { bridges: { LidoBridge: { Adapter: DAIBridgeAdapter, - l1Bridge: testingSetup.l1ERC20TokenBridge.address, + l1Bridge: testingSetup.l1LidoTokensBridge.address, l2Bridge: testingSetup.l2ERC20TokenBridge.address, }, }, diff --git a/test/optimism/bridging.e2e.test.ts b/test/optimism/bridging.e2e.test.ts index d578e860..ea346bdd 100644 --- a/test/optimism/bridging.e2e.test.ts +++ b/test/optimism/bridging.e2e.test.ts @@ -1,6 +1,5 @@ import { CrossChainMessenger, - DAIBridgeAdapter, MessageStatus, } from "@eth-optimism/sdk"; import { assert } from "chai"; @@ -13,6 +12,7 @@ import optimism from "../../utils/optimism"; import { ERC20Mintable } from "../../typechain"; import { scenario } from "../../utils/testing"; import { sleep } from "../../utils/testing/e2e"; +import { LidoBridgeAdapter } from "../../utils/optimism/LidoBridgeAdapter"; let depositTokensTxResponse: TransactionResponse; let withdrawTokensTxResponse: TransactionResponse; @@ -38,7 +38,7 @@ scenario("Optimism :: Bridging via deposit/withdraw E2E test", ctxFactory) } ) - .step("Set allowance for L1ERC20TokenBridge to deposit", async (ctx) => { + .step("Set allowance for L1LidoTokensBridge to deposit", async (ctx) => { const allowanceTxResponse = await ctx.crossChainMessenger.approveERC20( ctx.l1Token.address, ctx.l2Token.address, @@ -50,7 +50,7 @@ scenario("Optimism :: Bridging via deposit/withdraw E2E test", ctxFactory) assert.equalBN( await ctx.l1Token.allowance( ctx.l1Tester.address, - ctx.l1ERC20TokenBridge.address + ctx.l1LidoTokensBridge.address ), ctx.depositAmount ); @@ -134,7 +134,7 @@ async function ctxFactory() { l1Tester: testingSetup.l1Tester, l1Token: testingSetup.l1Token, l2Token: testingSetup.l2Token, - l1ERC20TokenBridge: testingSetup.l1ERC20TokenBridge, + l1LidoTokensBridge: testingSetup.l1LidoTokensBridge, crossChainMessenger: new CrossChainMessenger({ l2ChainId: network.chainId("opt", networkName), l1ChainId: network.chainId("eth", networkName), @@ -142,8 +142,8 @@ async function ctxFactory() { l2SignerOrProvider: testingSetup.l2Tester, bridges: { LidoBridge: { - Adapter: DAIBridgeAdapter, - l1Bridge: testingSetup.l1ERC20TokenBridge.address, + Adapter: LidoBridgeAdapter, + l1Bridge: testingSetup.l1LidoTokensBridge.address, l2Bridge: testingSetup.l2ERC20TokenBridge.address, }, }, diff --git a/test/optimism/bridging.integration.test.ts b/test/optimism/bridging.integration.test.ts index 23eb66f6..57d9e5b3 100644 --- a/test/optimism/bridging.integration.test.ts +++ b/test/optimism/bridging.integration.test.ts @@ -12,13 +12,13 @@ scenario("Optimism :: Bridging integration test", ctxFactory) }) .step("Activate bridging on L1", async (ctx) => { - const { l1ERC20TokenBridge } = ctx; + const { l1LidoTokensBridge } = ctx; const { l1ERC20TokenBridgeAdmin } = ctx.accounts; - const isDepositsEnabled = await l1ERC20TokenBridge.isDepositsEnabled(); + const isDepositsEnabled = await l1LidoTokensBridge.isDepositsEnabled(); if (!isDepositsEnabled) { - await l1ERC20TokenBridge + await l1LidoTokensBridge .connect(l1ERC20TokenBridgeAdmin) .enableDeposits(); } else { @@ -26,18 +26,18 @@ scenario("Optimism :: Bridging integration test", ctxFactory) } const isWithdrawalsEnabled = - await l1ERC20TokenBridge.isWithdrawalsEnabled(); + await l1LidoTokensBridge.isWithdrawalsEnabled(); if (!isWithdrawalsEnabled) { - await l1ERC20TokenBridge + await l1LidoTokensBridge .connect(l1ERC20TokenBridgeAdmin) .enableWithdrawals(); } else { console.log("L1 withdrawals already enabled"); } - assert.isTrue(await l1ERC20TokenBridge.isDepositsEnabled()); - assert.isTrue(await l1ERC20TokenBridge.isWithdrawalsEnabled()); + assert.isTrue(await l1LidoTokensBridge.isDepositsEnabled()); + assert.isTrue(await l1LidoTokensBridge.isWithdrawalsEnabled()); }) .step("Activate bridging on L2", async (ctx) => { @@ -72,7 +72,7 @@ scenario("Optimism :: Bridging integration test", ctxFactory) .step("L1 -> L2 deposit via depositERC20() method", async (ctx) => { const { l1Token, - l1ERC20TokenBridge, + l1LidoTokensBridge, l2Token, l1CrossDomainMessenger, l2ERC20TokenBridge, @@ -82,16 +82,16 @@ scenario("Optimism :: Bridging integration test", ctxFactory) await l1Token .connect(tokenHolderA.l1Signer) - .approve(l1ERC20TokenBridge.address, depositAmount); + .approve(l1LidoTokensBridge.address, depositAmount); const tokenHolderABalanceBefore = await l1Token.balanceOf( tokenHolderA.address ); const l1ERC20TokenBridgeBalanceBefore = await l1Token.balanceOf( - l1ERC20TokenBridge.address + l1LidoTokensBridge.address ); - const tx = await l1ERC20TokenBridge + const tx = await l1LidoTokensBridge .connect(tokenHolderA.l1Signer) .depositERC20( l1Token.address, @@ -101,7 +101,7 @@ scenario("Optimism :: Bridging integration test", ctxFactory) "0x" ); - await assert.emits(l1ERC20TokenBridge, tx, "ERC20DepositInitiated", [ + await assert.emits(l1LidoTokensBridge, tx, "ERC20DepositInitiated", [ l1Token.address, l2Token.address, tokenHolderA.address, @@ -126,14 +126,14 @@ scenario("Optimism :: Bridging integration test", ctxFactory) await assert.emits(l1CrossDomainMessenger, tx, "SentMessage", [ l2ERC20TokenBridge.address, - l1ERC20TokenBridge.address, + l1LidoTokensBridge.address, l2DepositCalldata, messageNonce, 200_000, ]); assert.equalBN( - await l1Token.balanceOf(l1ERC20TokenBridge.address), + await l1Token.balanceOf(l1LidoTokensBridge.address), l1ERC20TokenBridgeBalanceBefore.add(depositAmount) ); @@ -147,7 +147,7 @@ scenario("Optimism :: Bridging integration test", ctxFactory) const { l1Token, l2Token, - l1ERC20TokenBridge, + l1LidoTokensBridge, l2CrossDomainMessenger, l2ERC20TokenBridge, } = ctx; @@ -164,7 +164,7 @@ scenario("Optimism :: Bridging integration test", ctxFactory) .connect(l1CrossDomainMessengerAliased) .relayMessage( 1, - l1ERC20TokenBridge.address, + l1LidoTokensBridge.address, l2ERC20TokenBridge.address, 0, 300_000, @@ -233,7 +233,7 @@ scenario("Optimism :: Bridging integration test", ctxFactory) const { l1Token, l1CrossDomainMessenger, - l1ERC20TokenBridge, + l1LidoTokensBridge, l2CrossDomainMessenger, l2Token, l2ERC20TokenBridge, @@ -245,7 +245,7 @@ scenario("Optimism :: Bridging integration test", ctxFactory) tokenHolderA.address ); const l1ERC20TokenBridgeBalanceBefore = await l1Token.balanceOf( - l1ERC20TokenBridge.address + l1LidoTokensBridge.address ); await l1CrossDomainMessenger @@ -255,9 +255,9 @@ scenario("Optimism :: Bridging integration test", ctxFactory) const tx = await l1CrossDomainMessenger .connect(l1Stranger) .relayMessage( - l1ERC20TokenBridge.address, + l1LidoTokensBridge.address, l2CrossDomainMessenger.address, - l1ERC20TokenBridge.interface.encodeFunctionData( + l1LidoTokensBridge.interface.encodeFunctionData( "finalizeERC20Withdrawal", [ l1Token.address, @@ -271,7 +271,7 @@ scenario("Optimism :: Bridging integration test", ctxFactory) 0 ); - await assert.emits(l1ERC20TokenBridge, tx, "ERC20WithdrawalFinalized", [ + await assert.emits(l1LidoTokensBridge, tx, "ERC20WithdrawalFinalized", [ l1Token.address, l2Token.address, tokenHolderA.address, @@ -281,7 +281,7 @@ scenario("Optimism :: Bridging integration test", ctxFactory) ]); assert.equalBN( - await l1Token.balanceOf(l1ERC20TokenBridge.address), + await l1Token.balanceOf(l1LidoTokensBridge.address), l1ERC20TokenBridgeBalanceBefore.sub(withdrawalAmount) ); @@ -295,7 +295,7 @@ scenario("Optimism :: Bridging integration test", ctxFactory) const { l1Token, l2Token, - l1ERC20TokenBridge, + l1LidoTokensBridge, l2ERC20TokenBridge, l1CrossDomainMessenger, } = ctx; @@ -306,16 +306,16 @@ scenario("Optimism :: Bridging integration test", ctxFactory) await l1Token .connect(tokenHolderA.l1Signer) - .approve(l1ERC20TokenBridge.address, depositAmount); + .approve(l1LidoTokensBridge.address, depositAmount); const tokenHolderABalanceBefore = await l1Token.balanceOf( tokenHolderA.address ); const l1ERC20TokenBridgeBalanceBefore = await l1Token.balanceOf( - l1ERC20TokenBridge.address + l1LidoTokensBridge.address ); - const tx = await l1ERC20TokenBridge + const tx = await l1LidoTokensBridge .connect(tokenHolderA.l1Signer) .depositERC20To( l1Token.address, @@ -326,7 +326,7 @@ scenario("Optimism :: Bridging integration test", ctxFactory) "0x" ); - await assert.emits(l1ERC20TokenBridge, tx, "ERC20DepositInitiated", [ + await assert.emits(l1LidoTokensBridge, tx, "ERC20DepositInitiated", [ l1Token.address, l2Token.address, tokenHolderA.address, @@ -351,14 +351,14 @@ scenario("Optimism :: Bridging integration test", ctxFactory) await assert.emits(l1CrossDomainMessenger, tx, "SentMessage", [ l2ERC20TokenBridge.address, - l1ERC20TokenBridge.address, + l1LidoTokensBridge.address, l2DepositCalldata, messageNonce, 200_000, ]); assert.equalBN( - await l1Token.balanceOf(l1ERC20TokenBridge.address), + await l1Token.balanceOf(l1LidoTokensBridge.address), l1ERC20TokenBridgeBalanceBefore.add(depositAmount) ); @@ -371,7 +371,7 @@ scenario("Optimism :: Bridging integration test", ctxFactory) .step("Finalize deposit on L2", async (ctx) => { const { l1Token, - l1ERC20TokenBridge, + l1LidoTokensBridge, l2Token, l2CrossDomainMessenger, l2ERC20TokenBridge, @@ -392,7 +392,7 @@ scenario("Optimism :: Bridging integration test", ctxFactory) .connect(l1CrossDomainMessengerAliased) .relayMessage( 1, - l1ERC20TokenBridge.address, + l1LidoTokensBridge.address, l2ERC20TokenBridge.address, 0, 300_000, @@ -470,7 +470,7 @@ scenario("Optimism :: Bridging integration test", ctxFactory) const { l1Token, l1CrossDomainMessenger, - l1ERC20TokenBridge, + l1LidoTokensBridge, l2CrossDomainMessenger, l2Token, l2ERC20TokenBridge, @@ -486,7 +486,7 @@ scenario("Optimism :: Bridging integration test", ctxFactory) tokenHolderA.address ); const l1ERC20TokenBridgeBalanceBefore = await l1Token.balanceOf( - l1ERC20TokenBridge.address + l1LidoTokensBridge.address ); await l1CrossDomainMessenger @@ -496,9 +496,9 @@ scenario("Optimism :: Bridging integration test", ctxFactory) const tx = await l1CrossDomainMessenger .connect(l1Stranger) .relayMessage( - l1ERC20TokenBridge.address, + l1LidoTokensBridge.address, l2CrossDomainMessenger.address, - l1ERC20TokenBridge.interface.encodeFunctionData( + l1LidoTokensBridge.interface.encodeFunctionData( "finalizeERC20Withdrawal", [ l1Token.address, @@ -512,7 +512,7 @@ scenario("Optimism :: Bridging integration test", ctxFactory) 0 ); - await assert.emits(l1ERC20TokenBridge, tx, "ERC20WithdrawalFinalized", [ + await assert.emits(l1LidoTokensBridge, tx, "ERC20WithdrawalFinalized", [ l1Token.address, l2Token.address, tokenHolderB.address, @@ -522,7 +522,7 @@ scenario("Optimism :: Bridging integration test", ctxFactory) ]); assert.equalBN( - await l1Token.balanceOf(l1ERC20TokenBridge.address), + await l1Token.balanceOf(l1LidoTokensBridge.address), l1ERC20TokenBridgeBalanceBefore.sub(withdrawalAmount) ); diff --git a/test/optimism/deployment.acceptance.test.ts b/test/optimism/deployment.acceptance.test.ts index be49d3c5..b5a7a766 100644 --- a/test/optimism/deployment.acceptance.test.ts +++ b/test/optimism/deployment.acceptance.test.ts @@ -13,55 +13,55 @@ import { wei } from "../../utils/wei"; scenario("Optimism Bridge :: deployment acceptance test", ctxFactory) .step("L1 Bridge :: proxy admin", async (ctx) => { assert.equal( - await ctx.l1ERC20TokenBridgeProxy.proxy__getAdmin(), + await ctx.l1LidoTokensBridgeProxy.proxy__getAdmin(), ctx.deployment.l1.proxyAdmin ); }) .step("L1 Bridge :: bridge admin", async (ctx) => { const currentAdmins = await getRoleHolders( - ctx.l1ERC20TokenBridge, + ctx.l1LidoTokensBridge, BridgingManagerRole.DEFAULT_ADMIN_ROLE.hash ); assert.equal(currentAdmins.size, 1); assert.isTrue(currentAdmins.has(ctx.deployment.l1.bridgeAdmin)); await assert.isTrue( - await ctx.l1ERC20TokenBridge.hasRole( + await ctx.l1LidoTokensBridge.hasRole( BridgingManagerRole.DEFAULT_ADMIN_ROLE.hash, ctx.deployment.l1.bridgeAdmin ) ); }) .step("L1 bridge :: L1 token", async (ctx) => { - assert.equal(await ctx.l1ERC20TokenBridge.l1Token(), ctx.deployment.token); + assert.equal(await ctx.l1LidoTokensBridge.L1_TOKEN_NON_REBASABLE(), ctx.deployment.token); }) .step("L1 bridge :: L2 token", async (ctx) => { assert.equal( - await ctx.l1ERC20TokenBridge.l2Token(), + await ctx.l1LidoTokensBridge.L2_TOKEN_NON_REBASABLE(), ctx.erc20Bridged.address ); }) .step("L1 bridge :: L2 token bridge", async (ctx) => { assert.equal( - await ctx.l1ERC20TokenBridge.l2TokenBridge(), + await ctx.l1LidoTokensBridge.l2TokenBridge(), ctx.l2ERC20TokenBridge.address ); }) .step("L1 Bridge :: is deposits enabled", async (ctx) => { assert.equal( - await ctx.l1ERC20TokenBridge.isDepositsEnabled(), + await ctx.l1LidoTokensBridge.isDepositsEnabled(), ctx.deployment.l1.depositsEnabled ); }) .step("L1 Bridge :: is withdrawals enabled", async (ctx) => { assert.equal( - await ctx.l1ERC20TokenBridge.isWithdrawalsEnabled(), + await ctx.l1LidoTokensBridge.isWithdrawalsEnabled(), ctx.deployment.l1.withdrawalsEnabled ); }) .step("L1 Bridge :: deposits enablers", async (ctx) => { const actualDepositsEnablers = await getRoleHolders( - ctx.l1ERC20TokenBridge, + ctx.l1LidoTokensBridge, BridgingManagerRole.DEPOSITS_ENABLER_ROLE.hash ); const expectedDepositsEnablers = ctx.deployment.l1.depositsEnablers || []; @@ -73,7 +73,7 @@ scenario("Optimism Bridge :: deployment acceptance test", ctxFactory) }) .step("L1 Bridge :: deposits disablers", async (ctx) => { const actualDepositsDisablers = await getRoleHolders( - ctx.l1ERC20TokenBridge, + ctx.l1LidoTokensBridge, BridgingManagerRole.DEPOSITS_DISABLER_ROLE.hash ); const expectedDepositsDisablers = ctx.deployment.l1.depositsDisablers || []; @@ -87,7 +87,7 @@ scenario("Optimism Bridge :: deployment acceptance test", ctxFactory) }) .step("L1 Bridge :: withdrawals enablers", async (ctx) => { const actualWithdrawalsEnablers = await getRoleHolders( - ctx.l1ERC20TokenBridge, + ctx.l1LidoTokensBridge, BridgingManagerRole.WITHDRAWALS_ENABLER_ROLE.hash ); const expectedWithdrawalsEnablers = @@ -103,7 +103,7 @@ scenario("Optimism Bridge :: deployment acceptance test", ctxFactory) }) .step("L1 Bridge :: withdrawals disablers", async (ctx) => { const actualWithdrawalsDisablers = await getRoleHolders( - ctx.l1ERC20TokenBridge, + ctx.l1LidoTokensBridge, BridgingManagerRole.WITHDRAWALS_DISABLER_ROLE.hash ); const expectedWithdrawalsDisablers = @@ -142,18 +142,18 @@ scenario("Optimism Bridge :: deployment acceptance test", ctxFactory) ); }) .step("L2 bridge :: L1 token", async (ctx) => { - assert.equal(await ctx.l2ERC20TokenBridge.l1Token(), ctx.deployment.token); + assert.equal(await ctx.l2ERC20TokenBridge.L1_TOKEN_NON_REBASABLE(), ctx.deployment.token); }) .step("L2 bridge :: L2 token", async (ctx) => { assert.equal( - await ctx.l2ERC20TokenBridge.l2Token(), + await ctx.l2ERC20TokenBridge.L2_TOKEN_NON_REBASABLE(), ctx.erc20Bridged.address ); }) .step("L2 bridge :: L1 token bridge", async (ctx) => { assert.equal( await ctx.l2ERC20TokenBridge.l1TokenBridge(), - ctx.l1ERC20TokenBridge.address + ctx.l1LidoTokensBridge.address ); }) .step("L2 Bridge :: is deposits enabled", async (ctx) => { @@ -282,9 +282,9 @@ async function ctxFactory() { symbol, decimals, }, - l1ERC20TokenBridge: testingSetup.l1ERC20TokenBridge, - l1ERC20TokenBridgeProxy: OssifiableProxy__factory.connect( - testingSetup.l1ERC20TokenBridge.address, + l1LidoTokensBridge: testingSetup.l1LidoTokensBridge, + l1LidoTokensBridgeProxy: OssifiableProxy__factory.connect( + testingSetup.l1LidoTokensBridge.address, testingSetup.l1Provider ), l2ERC20TokenBridge: testingSetup.l2ERC20TokenBridge, diff --git a/test/optimism/deposit-gas-estimation.test.ts b/test/optimism/deposit-gas-estimation.test.ts index 5a14cda6..ea3ae063 100644 --- a/test/optimism/deposit-gas-estimation.test.ts +++ b/test/optimism/deposit-gas-estimation.test.ts @@ -12,13 +12,13 @@ scenario("Optimism :: Bridging integration test", ctxFactory) }) .step("Activate bridging on L1", async (ctx) => { - const { l1ERC20TokenBridge } = ctx; + const { l1LidoTokensBridge } = ctx; const { l1ERC20TokenBridgeAdmin } = ctx.accounts; - const isDepositsEnabled = await l1ERC20TokenBridge.isDepositsEnabled(); + const isDepositsEnabled = await l1LidoTokensBridge.isDepositsEnabled(); if (!isDepositsEnabled) { - await l1ERC20TokenBridge + await l1LidoTokensBridge .connect(l1ERC20TokenBridgeAdmin) .enableDeposits(); } else { @@ -26,18 +26,18 @@ scenario("Optimism :: Bridging integration test", ctxFactory) } const isWithdrawalsEnabled = - await l1ERC20TokenBridge.isWithdrawalsEnabled(); + await l1LidoTokensBridge.isWithdrawalsEnabled(); if (!isWithdrawalsEnabled) { - await l1ERC20TokenBridge + await l1LidoTokensBridge .connect(l1ERC20TokenBridgeAdmin) .enableWithdrawals(); } else { console.log("L1 withdrawals already enabled"); } - assert.isTrue(await l1ERC20TokenBridge.isDepositsEnabled()); - assert.isTrue(await l1ERC20TokenBridge.isWithdrawalsEnabled()); + assert.isTrue(await l1LidoTokensBridge.isDepositsEnabled()); + assert.isTrue(await l1LidoTokensBridge.isWithdrawalsEnabled()); }) .step("Activate bridging on L2", async (ctx) => { @@ -74,26 +74,26 @@ scenario("Optimism :: Bridging integration test", ctxFactory) l1Token, l2Token, l1TokenRebasable, - l1ERC20TokenBridge, + l1LidoTokensBridge, l2TokenRebasable } = ctx; const { accountA: tokenHolderA } = ctx.accounts; - const stETHPerToken = await l1Token.stETHPerToken(); - + const stEthPerToken = await l1Token.stEthPerToken(); + await l1TokenRebasable .connect(tokenHolderA.l1Signer) - .approve(l1ERC20TokenBridge.address, 0); + .approve(l1LidoTokensBridge.address, 0); const tokenHolderABalanceBefore = await l1TokenRebasable.balanceOf( tokenHolderA.address ); const l1ERC20TokenBridgeBalanceBefore = await l1TokenRebasable.balanceOf( - l1ERC20TokenBridge.address + l1LidoTokensBridge.address ); - const tx0 = await l1ERC20TokenBridge + const tx0 = await l1LidoTokensBridge .connect(tokenHolderA.l1Signer) .depositERC20( l1Token.address, @@ -106,7 +106,7 @@ scenario("Optimism :: Bridging integration test", ctxFactory) const receipt0 = await tx0.wait(); console.log("l1Token gasUsed=",receipt0.gasUsed); - const tx1 = await l1ERC20TokenBridge + const tx1 = await l1LidoTokensBridge .connect(tokenHolderA.l1Signer) .depositERC20( l1TokenRebasable.address, @@ -118,19 +118,19 @@ scenario("Optimism :: Bridging integration test", ctxFactory) const receipt1 = await tx1.wait(); console.log("l1TokenRebasable gasUsed=",receipt1.gasUsed); - + const gasDifference = receipt1.gasUsed.sub(receipt0.gasUsed); console.log("gasUsed difference=", gasDifference); }) - + .run(); async function ctxFactory() { const networkName = env.network("TESTING_OPT_NETWORK", "mainnet"); console.log("networkName=",networkName); - + const { l1Provider, l2Provider, diff --git a/utils/optimism/LidoBridgeAdapter.ts b/utils/optimism/LidoBridgeAdapter.ts new file mode 100644 index 00000000..14e70967 --- /dev/null +++ b/utils/optimism/LidoBridgeAdapter.ts @@ -0,0 +1,45 @@ +import { StandardBridgeAdapter, toAddress } from "@eth-optimism/sdk"; +import { hexStringEquals } from "@eth-optimism/core-utils"; +import { Contract } from 'ethers'; + +export class LidoBridgeAdapter extends StandardBridgeAdapter { + async supportsTokenPair(l1Token: Contract, l2Token: Contract) { + const l1Bridge = new Contract(this.l1Bridge.address, [ + { + inputs: [], + name: 'L1_TOKEN_NON_REBASABLE', + outputs: [ + { + internalType: 'address', + name: '', + type: 'address', + }, + ], + stateMutability: 'view', + type: 'function', + }, + { + inputs: [], + name: 'L2_TOKEN_NON_REBASABLE', + outputs: [ + { + internalType: 'address', + name: '', + type: 'address', + }, + ], + stateMutability: 'view', + type: 'function', + }, + ], this.messenger.l1Provider); + const allowedL1Token = await l1Bridge.L1_TOKEN_NON_REBASABLE(); + if (!(0, hexStringEquals)(allowedL1Token, (0, toAddress)(l1Token))) { + return false; + } + const allowedL2Token = await l1Bridge.L2_TOKEN_NON_REBASABLE(); + if (!(0, hexStringEquals)(allowedL2Token, (0, toAddress)(l2Token))) { + return false; + } + return true; + } +} diff --git a/utils/optimism/deployment.ts b/utils/optimism/deployment.ts index 354c6eb4..6a64d757 100644 --- a/utils/optimism/deployment.ts +++ b/utils/optimism/deployment.ts @@ -4,10 +4,9 @@ import { ERC20Bridged__factory, ERC20Rebasable__factory, IERC20Metadata__factory, - L1ERC20TokenBridge__factory, + L1LidoTokensBridge__factory, L2ERC20TokenBridge__factory, OssifiableProxy__factory, - TokenRateOracle, TokenRateOracle__factory, } from "../../typechain"; @@ -64,7 +63,7 @@ export default function deployment( options?.logger ) .addStep({ - factory: L1ERC20TokenBridge__factory, + factory: L1LidoTokensBridge__factory, args: [ optAddresses.L1CrossDomainMessenger, expectedL2TokenBridgeProxyAddress, @@ -82,7 +81,7 @@ export default function deployment( args: [ expectedL1TokenBridgeImplAddress, l1Params.admins.proxy, - L1ERC20TokenBridge__factory.createInterface().encodeFunctionData( + L1LidoTokensBridge__factory.createInterface().encodeFunctionData( "initialize", [l1Params.admins.bridge] ), diff --git a/utils/optimism/testing.ts b/utils/optimism/testing.ts index 2d6b4434..9517877c 100644 --- a/utils/optimism/testing.ts +++ b/utils/optimism/testing.ts @@ -5,13 +5,13 @@ import { IERC20, ERC20Bridged, IERC20__factory, - L1ERC20TokenBridge, + L1LidoTokensBridge, L2ERC20TokenBridge, ERC20Bridged__factory, ERC20BridgedStub__factory, ERC20WrapperStub__factory, TokenRateOracle__factory, - L1ERC20TokenBridge__factory, + L1LidoTokensBridge__factory, L2ERC20TokenBridge__factory, CrossDomainMessengerStub__factory, ERC20Rebasable__factory, @@ -59,7 +59,7 @@ export default function testing(networkName: NetworkName) { : await deployTestBridge(networkName, ethProvider, optProvider); const [l1ERC20TokenBridgeAdminAddress] = - await BridgingManagement.getAdmins(bridgeContracts.l1ERC20TokenBridge); + await BridgingManagement.getAdmins(bridgeContracts.l1LidoTokensBridge); const [l2ERC20TokenBridgeAdminAddress] = await BridgingManagement.getAdmins(bridgeContracts.l2ERC20TokenBridge); @@ -166,10 +166,10 @@ async function loadDeployedBridges( ...connectBridgeContracts( { - tokenRateOracle: testingUtils.env.OPT_L2_TOKEN(), // fix + tokenRateOracle: testingUtils.env.OPT_L2_TOKEN_RATE_ORACLE(), l2Token: testingUtils.env.OPT_L2_TOKEN(), - l2TokenRebasable: testingUtils.env.OPT_L2_TOKEN(), // fix - l1ERC20TokenBridge: testingUtils.env.OPT_L1_ERC20_TOKEN_BRIDGE(), + l2TokenRebasable: testingUtils.env.OPT_L2_REBASABLE_TOKEN(), + l1LidoTokensBridge: testingUtils.env.OPT_L1_ERC20_TOKEN_BRIDGE(), l2ERC20TokenBridge: testingUtils.env.OPT_L2_ERC20_TOKEN_BRIDGE(), }, l1SignerOrProvider, @@ -215,9 +215,9 @@ async function deployTestBridge( await ethDeployScript.run(); await optDeployScript.run(); - const l1ERC20TokenBridgeProxyDeployStepIndex = 1; + const l1LidoTokensBridgeProxyDeployStepIndex = 1; const l1BridgingManagement = new BridgingManagement( - ethDeployScript.getContractAddress(l1ERC20TokenBridgeProxyDeployStepIndex), + ethDeployScript.getContractAddress(l1LidoTokensBridgeProxyDeployStepIndex), ethDeployer ); @@ -247,7 +247,7 @@ async function deployTestBridge( tokenRateOracle: optDeployScript.getContractAddress(0), l2Token: optDeployScript.getContractAddress(2), l2TokenRebasable: optDeployScript.getContractAddress(4), - l1ERC20TokenBridge: ethDeployScript.getContractAddress(1), + l1LidoTokensBridge: ethDeployScript.getContractAddress(1), l2ERC20TokenBridge: optDeployScript.getContractAddress(6) }, ethProvider, @@ -261,15 +261,15 @@ function connectBridgeContracts( tokenRateOracle: string; l2Token: string; l2TokenRebasable: string; - l1ERC20TokenBridge: string; + l1LidoTokensBridge: string; l2ERC20TokenBridge: string; }, ethSignerOrProvider: SignerOrProvider, optSignerOrProvider: SignerOrProvider ) { - const l1ERC20TokenBridge = L1ERC20TokenBridge__factory.connect( - addresses.l1ERC20TokenBridge, + const l1LidoTokensBridge = L1LidoTokensBridge__factory.connect( + addresses.l1LidoTokensBridge, ethSignerOrProvider ); const l2ERC20TokenBridge = L2ERC20TokenBridge__factory.connect( @@ -292,7 +292,7 @@ function connectBridgeContracts( tokenRateOracle, l2Token, l2TokenRebasable, - l1ERC20TokenBridge, + l1LidoTokensBridge, l2ERC20TokenBridge }; } @@ -302,7 +302,7 @@ async function printLoadedTestConfig( bridgeContracts: { l1Token: IERC20; l2Token: ERC20Bridged; - l1ERC20TokenBridge: L1ERC20TokenBridge; + l1LidoTokensBridge: L1LidoTokensBridge; l2ERC20TokenBridge: L2ERC20TokenBridge; }, l1TokensHolder?: Signer @@ -323,7 +323,7 @@ async function printLoadedTestConfig( console.log(` · L1 Tokens Holder Balance: ${holderBalance.toString()}`); } console.log( - ` · L1 ERC20 Token Bridge: ${bridgeContracts.l1ERC20TokenBridge.address}` + ` · L1 ERC20 Token Bridge: ${bridgeContracts.l1LidoTokensBridge.address}` ); console.log( ` · L2 ERC20 Token Bridge: ${bridgeContracts.l2ERC20TokenBridge.address}` From 7badd9eec2e2080621de6f5308c3f7f084eb3bac Mon Sep 17 00:00:00 2001 From: Anton Kovalchuk Date: Wed, 6 Mar 2024 17:23:24 +0200 Subject: [PATCH 030/148] add rebasable token e2e tests --- .env.example | 3 + test/optimism/bridging-rebasable.e2e.test.ts | 152 ++++++++++++++++++ ...=> bridging-rebasable.integration.test.ts} | 0 utils/optimism/LidoBridgeAdapter.ts | 43 ++++- utils/optimism/testing.ts | 2 +- utils/testing/env.ts | 9 ++ 6 files changed, 204 insertions(+), 5 deletions(-) create mode 100644 test/optimism/bridging-rebasable.e2e.test.ts rename test/optimism/{bridging-rebase.integration.test.ts => bridging-rebasable.integration.test.ts} (100%) diff --git a/.env.example b/.env.example index 767859ae..fbf40032 100644 --- a/.env.example +++ b/.env.example @@ -77,6 +77,9 @@ TESTING_ARB_L2_GATEWAY_ROUTER=0x57f54f87C44d816f60b92864e23b8c0897D4d81D TESTING_OPT_NETWORK= TESTING_OPT_L1_TOKEN=0xaF8a2F0aE374b03376155BF745A3421Dac711C12 TESTING_OPT_L2_TOKEN=0xAED5F9aaF167923D34174b8E636aaF040A11f6F7 +TESTING_OPT_L1_REBASABLE_TOKEN=0xB82381A3fBD3FaFA77B3a7bE693342618240067b +TESTING_OPT_L2_REBASABLE_TOKEN=0x6696Cb7bb602FC744254Ad9E07EfC474FBF78857 +TESTING_OPT_L2_TOKEN_RATE_ORACLE=0x8ea513d1e5Be31fb5FC2f2971897594720de9E70 TESTING_OPT_L1_ERC20_TOKEN_BRIDGE=0x243b661276670bD17399C488E7287ea4D416115b TESTING_OPT_L2_ERC20_TOKEN_BRIDGE=0x447CD1794d209Ac4E6B4097B34658bc00C4d0a51 diff --git a/test/optimism/bridging-rebasable.e2e.test.ts b/test/optimism/bridging-rebasable.e2e.test.ts new file mode 100644 index 00000000..868f0a68 --- /dev/null +++ b/test/optimism/bridging-rebasable.e2e.test.ts @@ -0,0 +1,152 @@ +import { + CrossChainMessenger, + MessageStatus, + } from "@eth-optimism/sdk"; + import { assert } from "chai"; + import { TransactionResponse } from "@ethersproject/providers"; + + import env from "../../utils/env"; + import { wei } from "../../utils/wei"; + import network from "../../utils/network"; + import optimism from "../../utils/optimism"; + import { ERC20Mintable } from "../../typechain"; + import { scenario } from "../../utils/testing"; + import { sleep } from "../../utils/testing/e2e"; + import { LidoBridgeAdapter } from "../../utils/optimism/LidoBridgeAdapter"; + + let depositTokensTxResponse: TransactionResponse; + let withdrawTokensTxResponse: TransactionResponse; + + scenario("Optimism :: Bridging via deposit/withdraw E2E test", ctxFactory) + .step( + "Validate tester has required amount of L1 token", + async ({ l1TokenRebasable, l1Tester, depositAmount }) => { + const balanceBefore = await l1TokenRebasable.balanceOf(l1Tester.address); + if (balanceBefore.lt(depositAmount)) { + try { + await (l1TokenRebasable as ERC20Mintable).mint( + l1Tester.address, + depositAmount + ); + } catch {} + const balanceAfter = await l1TokenRebasable.balanceOf(l1Tester.address); + assert.isTrue( + balanceAfter.gte(depositAmount), + "Tester has not enough L1 token" + ); + } + } + ) + + .step("Set allowance for L1LidoTokensBridge to deposit", async (ctx) => { + const allowanceTxResponse = await ctx.crossChainMessenger.approveERC20( + ctx.l1TokenRebasable.address, + ctx.l2TokenRebasable.address, + ctx.depositAmount + ); + + await allowanceTxResponse.wait(); + + assert.equalBN( + await ctx.l1TokenRebasable.allowance( + ctx.l1Tester.address, + ctx.l1LidoTokensBridge.address + ), + ctx.depositAmount + ); + }) + + .step("Bridge tokens to L2 via depositERC20()", async (ctx) => { + depositTokensTxResponse = await ctx.crossChainMessenger.depositERC20( + ctx.l1TokenRebasable.address, + ctx.l2TokenRebasable.address, + ctx.depositAmount + ); + await depositTokensTxResponse.wait(); + }) + + .step("Waiting for status to change to RELAYED", async (ctx) => { + await ctx.crossChainMessenger.waitForMessageStatus( + depositTokensTxResponse.hash, + MessageStatus.RELAYED + ); + }) + + .step("Withdraw tokens from L2 via withdrawERC20()", async (ctx) => { + withdrawTokensTxResponse = await ctx.crossChainMessenger.withdrawERC20( + ctx.l1TokenRebasable.address, + ctx.l2TokenRebasable.address, + ctx.withdrawalAmount + ); + await withdrawTokensTxResponse.wait(); + }) + + .step("Waiting for status to change to READY_TO_PROVE", async (ctx) => { + await ctx.crossChainMessenger.waitForMessageStatus( + withdrawTokensTxResponse.hash, + MessageStatus.READY_TO_PROVE + ); + }) + + .step("Proving the L2 -> L1 message", async (ctx) => { + const tx = await ctx.crossChainMessenger.proveMessage( + withdrawTokensTxResponse.hash + ); + await tx.wait(); + }) + + .step("Waiting for status to change to IN_CHALLENGE_PERIOD", async (ctx) => { + await ctx.crossChainMessenger.waitForMessageStatus( + withdrawTokensTxResponse.hash, + MessageStatus.IN_CHALLENGE_PERIOD + ); + }) + + .step("Waiting for status to change to READY_FOR_RELAY", async (ctx) => { + await ctx.crossChainMessenger.waitForMessageStatus( + withdrawTokensTxResponse.hash, + MessageStatus.READY_FOR_RELAY + ); + }) + + .step("Finalizing L2 -> L1 message", async (ctx) => { + const finalizationPeriod = await ctx.crossChainMessenger.contracts.l1.L2OutputOracle.FINALIZATION_PERIOD_SECONDS(); + await sleep(finalizationPeriod * 1000); + await ctx.crossChainMessenger.finalizeMessage(withdrawTokensTxResponse); + }) + + .step("Waiting for status to change to RELAYED", async (ctx) => { + await ctx.crossChainMessenger.waitForMessageStatus( + withdrawTokensTxResponse, + MessageStatus.RELAYED + ); + }) + + .run(); + + async function ctxFactory() { + const networkName = env.network("TESTING_OPT_NETWORK", "sepolia"); + const testingSetup = await optimism.testing(networkName).getE2ETestSetup(); + + return { + depositAmount: wei`0.0025 ether`, + withdrawalAmount: wei`0.0025 ether`, + l1Tester: testingSetup.l1Tester, + l1TokenRebasable: testingSetup.l1TokenRebasable, + l2TokenRebasable: testingSetup.l2TokenRebasable, + l1LidoTokensBridge: testingSetup.l1LidoTokensBridge, + crossChainMessenger: new CrossChainMessenger({ + l2ChainId: network.chainId("opt", networkName), + l1ChainId: network.chainId("eth", networkName), + l1SignerOrProvider: testingSetup.l1Tester, + l2SignerOrProvider: testingSetup.l2Tester, + bridges: { + LidoBridge: { + Adapter: LidoBridgeAdapter, + l1Bridge: testingSetup.l1LidoTokensBridge.address, + l2Bridge: testingSetup.l2ERC20TokenBridge.address, + }, + }, + }), + }; + } diff --git a/test/optimism/bridging-rebase.integration.test.ts b/test/optimism/bridging-rebasable.integration.test.ts similarity index 100% rename from test/optimism/bridging-rebase.integration.test.ts rename to test/optimism/bridging-rebasable.integration.test.ts diff --git a/utils/optimism/LidoBridgeAdapter.ts b/utils/optimism/LidoBridgeAdapter.ts index 14e70967..ac561d4b 100644 --- a/utils/optimism/LidoBridgeAdapter.ts +++ b/utils/optimism/LidoBridgeAdapter.ts @@ -31,13 +31,48 @@ export class LidoBridgeAdapter extends StandardBridgeAdapter { stateMutability: 'view', type: 'function', }, + { + inputs: [], + name: 'L1_TOKEN_REBASABLE', + outputs: [ + { + internalType: 'address', + name: '', + type: 'address', + }, + ], + stateMutability: 'view', + type: 'function', + }, + { + inputs: [], + name: 'L2_TOKEN_REBASABLE', + outputs: [ + { + internalType: 'address', + name: '', + type: 'address', + }, + ], + stateMutability: 'view', + type: 'function', + }, ], this.messenger.l1Provider); - const allowedL1Token = await l1Bridge.L1_TOKEN_NON_REBASABLE(); - if (!(0, hexStringEquals)(allowedL1Token, (0, toAddress)(l1Token))) { + + const allowedL1RebasableToken = await l1Bridge.L1_TOKEN_REBASABLE(); + const allowedL1NonRebasableToken = await l1Bridge.L1_TOKEN_NON_REBASABLE(); + + if ((!(0, hexStringEquals)(allowedL1RebasableToken, (0, toAddress)(l1Token))) && + (!(0, hexStringEquals)(allowedL1NonRebasableToken, (0, toAddress)(l1Token)))) + { return false; } - const allowedL2Token = await l1Bridge.L2_TOKEN_NON_REBASABLE(); - if (!(0, hexStringEquals)(allowedL2Token, (0, toAddress)(l2Token))) { + + const allowedL2RebasableToken = await l1Bridge.L2_TOKEN_REBASABLE(); + const allowedL2NonRebasableToken = await l1Bridge.L2_TOKEN_NON_REBASABLE(); + + if ((!(0, hexStringEquals)(allowedL2RebasableToken, (0, toAddress)(l2Token))) && + (!(0, hexStringEquals)(allowedL2NonRebasableToken, (0, toAddress)(l2Token)))) { return false; } return true; diff --git a/utils/optimism/testing.ts b/utils/optimism/testing.ts index 9517877c..5f36907c 100644 --- a/utils/optimism/testing.ts +++ b/utils/optimism/testing.ts @@ -160,7 +160,7 @@ async function loadDeployedBridges( l1SignerOrProvider ), l1TokenRebasable: IERC20__factory.connect( - testingUtils.env.OPT_L1_TOKEN(), + testingUtils.env.OPT_L1_REBASABLE_TOKEN(), l1SignerOrProvider ), diff --git a/utils/testing/env.ts b/utils/testing/env.ts index faf8acd6..1a00941e 100644 --- a/utils/testing/env.ts +++ b/utils/testing/env.ts @@ -33,6 +33,15 @@ export default { OPT_L2_TOKEN() { return env.address("TESTING_OPT_L2_TOKEN"); }, + OPT_L2_TOKEN_RATE_ORACLE() { + return env.address("TESTING_OPT_L2_TOKEN_RATE_ORACLE"); + }, + OPT_L1_REBASABLE_TOKEN() { + return env.address("TESTING_OPT_L1_REBASABLE_TOKEN"); + }, + OPT_L2_REBASABLE_TOKEN() { + return env.address("TESTING_OPT_L2_REBASABLE_TOKEN"); + }, OPT_L1_ERC20_TOKEN_BRIDGE() { return env.address("TESTING_OPT_L1_ERC20_TOKEN_BRIDGE"); }, From 72bc8f329e782149e29f7ae4fb03fb25309da62e Mon Sep 17 00:00:00 2001 From: Anton Kovalchuk Date: Wed, 6 Mar 2024 19:09:44 +0200 Subject: [PATCH 031/148] add bridging-to e2e tests for rebasable token --- .../bridging-rebasable-to.e2e.test.ts | 165 ++++++++++++++++++ 1 file changed, 165 insertions(+) create mode 100644 test/optimism/bridging-rebasable-to.e2e.test.ts diff --git a/test/optimism/bridging-rebasable-to.e2e.test.ts b/test/optimism/bridging-rebasable-to.e2e.test.ts new file mode 100644 index 00000000..ca582770 --- /dev/null +++ b/test/optimism/bridging-rebasable-to.e2e.test.ts @@ -0,0 +1,165 @@ +import { + CrossChainMessenger, + DAIBridgeAdapter, + MessageStatus, + } from "@eth-optimism/sdk"; + import { assert } from "chai"; + import { TransactionResponse } from "@ethersproject/providers"; + + import env from "../../utils/env"; + import { wei } from "../../utils/wei"; + import network from "../../utils/network"; + import optimism from "../../utils/optimism"; + import { ERC20Mintable } from "../../typechain"; + import { scenario } from "../../utils/testing"; + import { sleep } from "../../utils/testing/e2e"; + import { LidoBridgeAdapter } from "../../utils/optimism/LidoBridgeAdapter"; + + let depositTokensTxResponse: TransactionResponse; + let withdrawTokensTxResponse: TransactionResponse; + + scenario("Optimism :: Bridging via depositTo/withdrawTo E2E test", ctxFactory) + .step( + "Validate tester has required amount of L1 token", + async ({ l1TokenRebasable, l1Tester, depositAmount }) => { + const balanceBefore = await l1TokenRebasable.balanceOf(l1Tester.address); + if (balanceBefore.lt(depositAmount)) { + try { + await (l1TokenRebasable as ERC20Mintable).mint( + l1Tester.address, + depositAmount + ); + } catch {} + const balanceAfter = await l1TokenRebasable.balanceOf(l1Tester.address); + assert.isTrue( + balanceAfter.gte(depositAmount), + "Tester has not enough L1 token" + ); + } + } + ) + + .step("Set allowance for L1LidoTokensBridge to deposit", async (ctx) => { + const allowanceTxResponse = await ctx.crossChainMessenger.approveERC20( + ctx.l1TokenRebasable.address, + ctx.l2TokenRebasable.address, + ctx.depositAmount + ); + + await allowanceTxResponse.wait(); + + assert.equalBN( + await ctx.l1TokenRebasable.allowance( + ctx.l1Tester.address, + ctx.l1LidoTokensBridge.address + ), + ctx.depositAmount + ); + }) + + .step("Bridge tokens to L2 via depositERC20To()", async (ctx) => { + depositTokensTxResponse = await ctx.l1LidoTokensBridge + .connect(ctx.l1Tester) + .depositERC20To( + ctx.l1TokenRebasable.address, + ctx.l2TokenRebasable.address, + ctx.l1Tester.address, + ctx.depositAmount, + 2_000_000, + "0x" + ); + + await depositTokensTxResponse.wait(); + }) + + .step("Waiting for status to change to RELAYED", async (ctx) => { + await ctx.crossChainMessenger.waitForMessageStatus( + depositTokensTxResponse.hash, + MessageStatus.RELAYED + ); + }) + + .step("Withdraw tokens from L2 via withdrawERC20To()", async (ctx) => { + withdrawTokensTxResponse = await ctx.l2ERC20TokenBridge + .connect(ctx.l2Tester) + .withdrawTo( + ctx.l2TokenRebasable.address, + ctx.l1Tester.address, + ctx.withdrawalAmount, + 0, + "0x" + ); + await withdrawTokensTxResponse.wait(); + }) + + .step("Waiting for status to change to READY_TO_PROVE", async (ctx) => { + await ctx.crossChainMessenger.waitForMessageStatus( + withdrawTokensTxResponse.hash, + MessageStatus.READY_TO_PROVE + ); + }) + + .step("Proving the L2 -> L1 message", async (ctx) => { + const tx = await ctx.crossChainMessenger.proveMessage( + withdrawTokensTxResponse.hash + ); + await tx.wait(); + }) + + .step("Waiting for status to change to IN_CHALLENGE_PERIOD", async (ctx) => { + await ctx.crossChainMessenger.waitForMessageStatus( + withdrawTokensTxResponse.hash, + MessageStatus.IN_CHALLENGE_PERIOD + ); + }) + + .step("Waiting for status to change to READY_FOR_RELAY", async (ctx) => { + await ctx.crossChainMessenger.waitForMessageStatus( + withdrawTokensTxResponse.hash, + MessageStatus.READY_FOR_RELAY + ); + }) + + .step("Finalizing L2 -> L1 message", async (ctx) => { + const finalizationPeriod = await ctx.crossChainMessenger.contracts.l1.L2OutputOracle.FINALIZATION_PERIOD_SECONDS(); + await sleep(finalizationPeriod * 1000); + await ctx.crossChainMessenger.finalizeMessage(withdrawTokensTxResponse); + }) + + .step("Waiting for status to change to RELAYED", async (ctx) => { + await ctx.crossChainMessenger.waitForMessageStatus( + withdrawTokensTxResponse, + MessageStatus.RELAYED + ); + }) + + .run(); + + async function ctxFactory() { + const networkName = env.network("TESTING_OPT_NETWORK", "sepolia"); + const testingSetup = await optimism.testing(networkName).getE2ETestSetup(); + + return { + depositAmount: wei`0.0025 ether`, + withdrawalAmount: wei`0.0025 ether`, + l1Tester: testingSetup.l1Tester, + l2Tester: testingSetup.l2Tester, + l1TokenRebasable: testingSetup.l1TokenRebasable, + l2TokenRebasable: testingSetup.l2TokenRebasable, + l1LidoTokensBridge: testingSetup.l1LidoTokensBridge, + l2ERC20TokenBridge: testingSetup.l2ERC20TokenBridge, + crossChainMessenger: new CrossChainMessenger({ + l2ChainId: network.chainId("opt", networkName), + l1ChainId: network.chainId("eth", networkName), + l1SignerOrProvider: testingSetup.l1Tester, + l2SignerOrProvider: testingSetup.l2Tester, + bridges: { + LidoBridge: { + Adapter: LidoBridgeAdapter, + l1Bridge: testingSetup.l1LidoTokensBridge.address, + l2Bridge: testingSetup.l2ERC20TokenBridge.address, + }, + }, + }), + }; + } From 83e292b4dc107969c3cb1cd4553b71820582e515 Mon Sep 17 00:00:00 2001 From: Anton Kovalchuk Date: Thu, 7 Mar 2024 11:54:37 +0200 Subject: [PATCH 032/148] add token rate observer --- contracts/lido/IPostTokenRebaseReceiver.sol | 20 +++++++++ contracts/lido/ITokenRateObserver.sol | 10 +++++ contracts/lido/TokenRateNotifier.sol | 44 +++++++++++++++++++ .../optimism/OptimismTokenRateObserver.sol | 22 ++++++++++ 4 files changed, 96 insertions(+) create mode 100644 contracts/lido/IPostTokenRebaseReceiver.sol create mode 100644 contracts/lido/ITokenRateObserver.sol create mode 100644 contracts/lido/TokenRateNotifier.sol create mode 100644 contracts/optimism/OptimismTokenRateObserver.sol diff --git a/contracts/lido/IPostTokenRebaseReceiver.sol b/contracts/lido/IPostTokenRebaseReceiver.sol new file mode 100644 index 00000000..e5eb7e90 --- /dev/null +++ b/contracts/lido/IPostTokenRebaseReceiver.sol @@ -0,0 +1,20 @@ +// SPDX-FileCopyrightText: 2022 Lido +// SPDX-License-Identifier: GPL-3.0 + +pragma solidity 0.8.10; + +/// @author kovalgek +/// @notice An interface for Lido core protocol rebase event. +interface IPostTokenRebaseReceiver { + + /// @notice Is called when Lido core protocol rebasable event occures. + function handlePostTokenRebase( + uint256 _reportTimestamp, + uint256 _timeElapsed, + uint256 _preTotalShares, + uint256 _preTotalEther, + uint256 _postTotalShares, + uint256 _postTotalEther, + uint256 _sharesMintedAsFees + ) external; +} diff --git a/contracts/lido/ITokenRateObserver.sol b/contracts/lido/ITokenRateObserver.sol new file mode 100644 index 00000000..25c7c9f2 --- /dev/null +++ b/contracts/lido/ITokenRateObserver.sol @@ -0,0 +1,10 @@ +// SPDX-FileCopyrightText: 2022 Lido +// SPDX-License-Identifier: GPL-3.0 + +pragma solidity 0.8.10; + +/// @author kovalgek +/// @notice An interface for Lido core protocol rebase event. +interface ITokenRateObserver { + function update() external; +} diff --git a/contracts/lido/TokenRateNotifier.sol b/contracts/lido/TokenRateNotifier.sol new file mode 100644 index 00000000..df735f41 --- /dev/null +++ b/contracts/lido/TokenRateNotifier.sol @@ -0,0 +1,44 @@ +// SPDX-FileCopyrightText: 2022 Lido +// SPDX-License-Identifier: GPL-3.0 + +pragma solidity 0.8.10; + +import {IPostTokenRebaseReceiver} from "./IPostTokenRebaseReceiver.sol"; +import {ITokenRateObserver} from "./ITokenRateObserver.sol"; + +/// @author kovalgek +/// @notice An interface for Lido core protocol rebase event. +contract TokenRateNotifier is IPostTokenRebaseReceiver { + + event FailObserverNotification(address indexed observer); + + address[] private observers; + + constructor() { + } + + function registerObserver(address observer) external { + observers.push(observer); + } + + function _notifyObservers() internal { + for(uint observerIndex = 0; observerIndex < observers.length; observerIndex++) { + _notifyObserver(observers[observerIndex]); + } + } + + function _notifyObserver(address observer) internal { + ITokenRateObserver tokenRateObserver = ITokenRateObserver(observer); + + (bool success, bytes memory returnData) = address(observer).call( + abi.encodePacked(tokenRateObserver.update.selector) + ); + if (!success) { + emit FailObserverNotification(observer); + } + } + + function handlePostTokenRebase(uint256, uint256, uint256, uint256, uint256, uint256, uint256) external { + _notifyObservers(); + } +} diff --git a/contracts/optimism/OptimismTokenRateObserver.sol b/contracts/optimism/OptimismTokenRateObserver.sol new file mode 100644 index 00000000..ecdb9f3a --- /dev/null +++ b/contracts/optimism/OptimismTokenRateObserver.sol @@ -0,0 +1,22 @@ +// SPDX-FileCopyrightText: 2022 Lido +// SPDX-License-Identifier: GPL-3.0 + +pragma solidity 0.8.10; + +import {ITokenRateObserver} from "../lido/ITokenRateObserver.sol"; +import {L1LidoTokensBridge} from "./L1LidoTokensBridge.sol"; + +/// @author kovalgek +/// @notice An interface for Lido core protocol rebase event. +contract OptimismTokenRateObserver is ITokenRateObserver { + + L1LidoTokensBridge l1LidoTokensBridge; + + constructor(address lidoTokensBridge) { + l1LidoTokensBridge = L1LidoTokensBridge(lidoTokensBridge); + } + + function update() external { + l1LidoTokensBridge.pushTokenRate(10_000); + } +} From dbf2f92129df6a8d0b1ce8d923869cdefa45ece3 Mon Sep 17 00:00:00 2001 From: Anton Kovalchuk Date: Thu, 14 Mar 2024 14:43:11 +0100 Subject: [PATCH 033/148] add observers array --- contracts/lido/ITokenRateObserver.sol | 10 -- contracts/lido/ObserversArray.sol | 94 +++++++++++++++++++ contracts/lido/TokenRateNotifier.sol | 65 +++++++------ contracts/lido/interfaces/IObserversArray.sol | 51 ++++++++++ .../IPostTokenRebaseReceiver.sol | 6 +- .../lido/interfaces/ITokenRateObserver.sol | 12 +++ .../optimism/OpStackTokenRateObserver.sol | 29 ++++++ .../optimism/OptimismTokenRateObserver.sol | 22 ----- 8 files changed, 224 insertions(+), 65 deletions(-) delete mode 100644 contracts/lido/ITokenRateObserver.sol create mode 100644 contracts/lido/ObserversArray.sol create mode 100644 contracts/lido/interfaces/IObserversArray.sol rename contracts/lido/{ => interfaces}/IPostTokenRebaseReceiver.sol (59%) create mode 100644 contracts/lido/interfaces/ITokenRateObserver.sol create mode 100644 contracts/optimism/OpStackTokenRateObserver.sol delete mode 100644 contracts/optimism/OptimismTokenRateObserver.sol diff --git a/contracts/lido/ITokenRateObserver.sol b/contracts/lido/ITokenRateObserver.sol deleted file mode 100644 index 25c7c9f2..00000000 --- a/contracts/lido/ITokenRateObserver.sol +++ /dev/null @@ -1,10 +0,0 @@ -// SPDX-FileCopyrightText: 2022 Lido -// SPDX-License-Identifier: GPL-3.0 - -pragma solidity 0.8.10; - -/// @author kovalgek -/// @notice An interface for Lido core protocol rebase event. -interface ITokenRateObserver { - function update() external; -} diff --git a/contracts/lido/ObserversArray.sol b/contracts/lido/ObserversArray.sol new file mode 100644 index 00000000..5250d6b0 --- /dev/null +++ b/contracts/lido/ObserversArray.sol @@ -0,0 +1,94 @@ +// SPDX-FileCopyrightText: 2024 Lido +// SPDX-License-Identifier: GPL-3.0 + +pragma solidity 0.8.10; + +import {IPostTokenRebaseReceiver} from "./interfaces/IPostTokenRebaseReceiver.sol"; +import {ITokenRateObserver} from "./interfaces/ITokenRateObserver.sol"; +import {IObserversArray} from "./interfaces/IObserversArray.sol"; +import {ERC165Checker} from "@openzeppelin/contracts/utils/introspection/ERC165Checker.sol"; +import {Ownable} from "@openzeppelin/contracts/access/Ownable.sol"; + +/// @author kovalgek +/// @notice Manage observers. +contract ObserversArray is Ownable, IObserversArray { + using ERC165Checker for address; + + /// @notice Maximum amount of observers to be supported. + uint256 public constant MAX_OBSERVERS_COUNT = 16; + + /// @notice Invalid interface id. + bytes4 constant INVALID_INTERFACE_ID = 0xffffffff; + + /// @notice An interface that each observer should support. + bytes4 public immutable REQUIRED_INTERFACE; + + /// @notice all observers. + address[] public observers; + + /// @param requiredInterface_ An interface that each observer should support. + constructor(bytes4 requiredInterface_) { + if (requiredInterface_ == INVALID_INTERFACE_ID) { + revert ErrorInvalidInterface(); + } + + REQUIRED_INTERFACE = requiredInterface_; + } + + /// @inheritdoc IObserversArray + function observersLength() public view returns (uint256) { + return observers.length; + } + + /// @inheritdoc IObserversArray + function addObserver(address observer_) external virtual onlyOwner { + if (observer_ == address(0)) { + revert ErrorZeroAddressObserver(); + } + if (!observer_.supportsInterface(REQUIRED_INTERFACE)) { + revert ErrorBadObserverInterface(); + } + if (observers.length >= MAX_OBSERVERS_COUNT) { + revert ErrorMaxObserversCountExceeded(); + } + + observers.push(observer_); + emit ObserverAdded(observer_); + } + + /// @inheritdoc IObserversArray + function removeObserver(address observer_) external virtual onlyOwner { + + uint256 observerIndexToRemove = _observerIndex(observer_); + + if (observerIndexToRemove == type(uint256).max) { + revert ErrorNoObserverToRemove(); + } + + for (uint256 obIndex = observerIndexToRemove; obIndex < observers.length - 1; obIndex++) { + observers[obIndex] = observers[obIndex + 1]; + } + + observers.pop(); + + emit ObserverRemoved(observer_, observerIndexToRemove); + } + + /// @notice `observer_` index in `observers` array. + function _observerIndex(address observer_) internal view returns (uint256) { + for (uint256 obIndex = 0; obIndex < observers.length; obIndex++) { + if (observers[obIndex] == observer_) { + return obIndex; + } + } + return type(uint256).max; + } + + event FailObserverNotification(address indexed observer); + + error ErrorInvalidInterface(); + error ErrorZeroAddressObserver(); + error ErrorBadObserverInterface(); + error ErrorMaxObserversCountExceeded(); + error ErrorNoObserverToRemove(); +} diff --git a/contracts/lido/TokenRateNotifier.sol b/contracts/lido/TokenRateNotifier.sol index df735f41..15f173c5 100644 --- a/contracts/lido/TokenRateNotifier.sol +++ b/contracts/lido/TokenRateNotifier.sol @@ -1,44 +1,49 @@ -// SPDX-FileCopyrightText: 2022 Lido +// SPDX-FileCopyrightText: 2024 Lido // SPDX-License-Identifier: GPL-3.0 pragma solidity 0.8.10; -import {IPostTokenRebaseReceiver} from "./IPostTokenRebaseReceiver.sol"; -import {ITokenRateObserver} from "./ITokenRateObserver.sol"; +import {IPostTokenRebaseReceiver} from "./interfaces/IPostTokenRebaseReceiver.sol"; +import {ITokenRateObserver} from "./interfaces/ITokenRateObserver.sol"; +import {ObserversArray} from "./ObserversArray.sol"; /// @author kovalgek -/// @notice An interface for Lido core protocol rebase event. -contract TokenRateNotifier is IPostTokenRebaseReceiver { +/// @notice Notifies all observers when rebase event occures. +contract TokenRateNotifier is ObserversArray, IPostTokenRebaseReceiver { - event FailObserverNotification(address indexed observer); - - address[] private observers; - - constructor() { - } - - function registerObserver(address observer) external { - observers.push(observer); + constructor() ObserversArray(type(ITokenRateObserver).interfaceId) { } - function _notifyObservers() internal { - for(uint observerIndex = 0; observerIndex < observers.length; observerIndex++) { - _notifyObserver(observers[observerIndex]); + /// @inheritdoc IPostTokenRebaseReceiver + function handlePostTokenRebase( + uint256, + uint256, + uint256, + uint256, + uint256, + uint256, + uint256 + ) external { + uint256 observersLength = observersLength(); + + for (uint256 obIndex = 0; obIndex < observersLength; obIndex++) { + try ITokenRateObserver(observers[obIndex]).handleTokenRebased() {} + catch (bytes memory lowLevelRevertData) { + /// @dev This check is required to prevent incorrect gas estimation of the method. + /// Without it, Ethereum nodes that use binary search for gas estimation may + /// return an invalid value when the handleTokenRebased() reverts because of the + /// "out of gas" error. Here we assume that the handleTokenRebased() method doesn't + /// have reverts with empty error data except "out of gas". + if (lowLevelRevertData.length == 0) revert ErrorUnrecoverableObserver(); + emit HandleTokenRebasedFailed( + observers[obIndex], + lowLevelRevertData + ); + } } } - function _notifyObserver(address observer) internal { - ITokenRateObserver tokenRateObserver = ITokenRateObserver(observer); + event HandleTokenRebasedFailed(address indexed observer, bytes lowLevelRevertData); - (bool success, bytes memory returnData) = address(observer).call( - abi.encodePacked(tokenRateObserver.update.selector) - ); - if (!success) { - emit FailObserverNotification(observer); - } - } - - function handlePostTokenRebase(uint256, uint256, uint256, uint256, uint256, uint256, uint256) external { - _notifyObservers(); - } + error ErrorUnrecoverableObserver(); } diff --git a/contracts/lido/interfaces/IObserversArray.sol b/contracts/lido/interfaces/IObserversArray.sol new file mode 100644 index 00000000..b378af07 --- /dev/null +++ b/contracts/lido/interfaces/IObserversArray.sol @@ -0,0 +1,51 @@ +// SPDX-FileCopyrightText: 2024 Lido +// SPDX-License-Identifier: GPL-3.0 + +pragma solidity 0.8.10; + +/// @author kovalgek +interface IObserversArray { + /** + * @notice Observer added event + * + * @dev emitted by `addObserver` and `insertObserver` functions + */ + event ObserverAdded(address indexed observer); + + /** + * @notice Observer removed event + * + * @dev emitted by `removeObserver` function + */ + event ObserverRemoved(address indexed observer, uint256 atIndex); + + /** + * @notice Observer length + * @return Added observers count + */ + function observersLength() external view returns (uint256); + + /** + * @notice Add a `_observer` to the back of array + * @param observer_ observer address + * + * @dev cheapest way to insert new item (doesn't incur additional moves) + */ + function addObserver(address observer_) external; + + /** + * @notice Remove a observer at the given `observer_` position + * @param observer_ observer remove position + * + * @dev remove gas cost is higher for the lower `observer_` values + */ + function removeObserver(address observer_) external; + + /** + * @notice Get observer at position + * @return Observer at the given `atIndex_` + * + * @dev function reverts if `atIndex_` is out of range + */ + function observers(uint256 atIndex_) external view returns (address); +} diff --git a/contracts/lido/IPostTokenRebaseReceiver.sol b/contracts/lido/interfaces/IPostTokenRebaseReceiver.sol similarity index 59% rename from contracts/lido/IPostTokenRebaseReceiver.sol rename to contracts/lido/interfaces/IPostTokenRebaseReceiver.sol index e5eb7e90..65b1fe90 100644 --- a/contracts/lido/IPostTokenRebaseReceiver.sol +++ b/contracts/lido/interfaces/IPostTokenRebaseReceiver.sol @@ -1,13 +1,13 @@ -// SPDX-FileCopyrightText: 2022 Lido +// SPDX-FileCopyrightText: 2024 Lido // SPDX-License-Identifier: GPL-3.0 pragma solidity 0.8.10; /// @author kovalgek -/// @notice An interface for Lido core protocol rebase event. +/// @notice An interface to subscribe on the `stETH` token rebases (defined in the `Lido` core contract) interface IPostTokenRebaseReceiver { - /// @notice Is called when Lido core protocol rebasable event occures. + /// @notice Is called in the context of `Lido.handleOracleReport` to notify the subscribers about each token rebase function handlePostTokenRebase( uint256 _reportTimestamp, uint256 _timeElapsed, diff --git a/contracts/lido/interfaces/ITokenRateObserver.sol b/contracts/lido/interfaces/ITokenRateObserver.sol new file mode 100644 index 00000000..cc59efdf --- /dev/null +++ b/contracts/lido/interfaces/ITokenRateObserver.sol @@ -0,0 +1,12 @@ +// SPDX-FileCopyrightText: 2024 Lido +// SPDX-License-Identifier: GPL-3.0 + +pragma solidity 0.8.10; + +/// @author kovalgek +/// @notice An interface to subscribe for token rebases. Is used to handle different rollups. +interface ITokenRateObserver { + + /// @notice Is called when rebase event occures. + function handleTokenRebased() external; +} diff --git a/contracts/optimism/OpStackTokenRateObserver.sol b/contracts/optimism/OpStackTokenRateObserver.sol new file mode 100644 index 00000000..4ad4b58e --- /dev/null +++ b/contracts/optimism/OpStackTokenRateObserver.sol @@ -0,0 +1,29 @@ +// SPDX-FileCopyrightText: 2024 Lido +// SPDX-License-Identifier: GPL-3.0 + +pragma solidity 0.8.10; + +import {ITokenRateObserver} from "../lido/interfaces/ITokenRateObserver.sol"; +import {L1LidoTokensBridge} from "./L1LidoTokensBridge.sol"; + +/// @author kovalgek +/// @notice Pushes token rate when rebase event happens. +contract OpStackTokenRateObserver is ITokenRateObserver { + + /// @notice Contract of OpStack bridge. + L1LidoTokensBridge public immutable L1_LIDO_TOKENS_BRIDGE; + + /// @notice Gas limit required to complete pushing token rate on L2. + uint32 public immutable L2_GAS_LIMIT_FOR_PUSHING_TOKEN_RATE; + + /// @param lidoTokensBridge_ OpStack bridge. + constructor(address lidoTokensBridge_, uint32 l2GasLimitForPushingTokenRate_) { + L1_LIDO_TOKENS_BRIDGE = L1LidoTokensBridge(lidoTokensBridge_); + L2_GAS_LIMIT_FOR_PUSHING_TOKEN_RATE = l2GasLimitForPushingTokenRate_; + } + + /// @inheritdoc ITokenRateObserver + function handleTokenRebased() external { + L1_LIDO_TOKENS_BRIDGE.pushTokenRate(L2_GAS_LIMIT_FOR_PUSHING_TOKEN_RATE); + } +} diff --git a/contracts/optimism/OptimismTokenRateObserver.sol b/contracts/optimism/OptimismTokenRateObserver.sol deleted file mode 100644 index ecdb9f3a..00000000 --- a/contracts/optimism/OptimismTokenRateObserver.sol +++ /dev/null @@ -1,22 +0,0 @@ -// SPDX-FileCopyrightText: 2022 Lido -// SPDX-License-Identifier: GPL-3.0 - -pragma solidity 0.8.10; - -import {ITokenRateObserver} from "../lido/ITokenRateObserver.sol"; -import {L1LidoTokensBridge} from "./L1LidoTokensBridge.sol"; - -/// @author kovalgek -/// @notice An interface for Lido core protocol rebase event. -contract OptimismTokenRateObserver is ITokenRateObserver { - - L1LidoTokensBridge l1LidoTokensBridge; - - constructor(address lidoTokensBridge) { - l1LidoTokensBridge = L1LidoTokensBridge(lidoTokensBridge); - } - - function update() external { - l1LidoTokensBridge.pushTokenRate(10_000); - } -} From 47c50c6d874a1841b9a10d347370819976dae2d4 Mon Sep 17 00:00:00 2001 From: Anton Kovalchuk Date: Mon, 18 Mar 2024 16:39:19 +0100 Subject: [PATCH 034/148] add factory and tests --- contracts/lido/ObserversArray.sol | 24 ++-- contracts/lido/TokenRateNotifier.sol | 2 +- contracts/lido/interfaces/IObserversArray.sol | 51 +++---- .../lido/interfaces/ITokenRateObserver.sol | 4 +- .../optimism/OpStackTokenRateObserver.sol | 12 +- .../OpStackTokenRateObserverFactory.sol | 41 ++++++ test/optimism/TokenRateNotifier.unit.test.ts | 126 ++++++++++++++++++ 7 files changed, 208 insertions(+), 52 deletions(-) create mode 100644 contracts/optimism/OpStackTokenRateObserverFactory.sol create mode 100644 test/optimism/TokenRateNotifier.unit.test.ts diff --git a/contracts/lido/ObserversArray.sol b/contracts/lido/ObserversArray.sol index 5250d6b0..3c1e6fcf 100644 --- a/contracts/lido/ObserversArray.sol +++ b/contracts/lido/ObserversArray.sol @@ -3,11 +3,9 @@ pragma solidity 0.8.10; -import {IPostTokenRebaseReceiver} from "./interfaces/IPostTokenRebaseReceiver.sol"; -import {ITokenRateObserver} from "./interfaces/ITokenRateObserver.sol"; -import {IObserversArray} from "./interfaces/IObserversArray.sol"; -import {ERC165Checker} from "@openzeppelin/contracts/utils/introspection/ERC165Checker.sol"; import {Ownable} from "@openzeppelin/contracts/access/Ownable.sol"; +import {ERC165Checker} from "@openzeppelin/contracts/utils/introspection/ERC165Checker.sol"; +import {IObserversArray} from "./interfaces/IObserversArray.sol"; /// @author kovalgek /// @notice Manage observers. @@ -18,12 +16,12 @@ contract ObserversArray is Ownable, IObserversArray { uint256 public constant MAX_OBSERVERS_COUNT = 16; /// @notice Invalid interface id. - bytes4 constant INVALID_INTERFACE_ID = 0xffffffff; + bytes4 public constant INVALID_INTERFACE_ID = 0xffffffff; /// @notice An interface that each observer should support. bytes4 public immutable REQUIRED_INTERFACE; - /// @notice all observers. + /// @notice All observers. address[] public observers; /// @param requiredInterface_ An interface that each observer should support. @@ -35,11 +33,6 @@ contract ObserversArray is Ownable, IObserversArray { REQUIRED_INTERFACE = requiredInterface_; } - /// @inheritdoc IObserversArray - function observersLength() public view returns (uint256) { - return observers.length; - } - /// @inheritdoc IObserversArray function addObserver(address observer_) external virtual onlyOwner { if (observer_ == address(0)) { @@ -71,7 +64,12 @@ contract ObserversArray is Ownable, IObserversArray { observers.pop(); - emit ObserverRemoved(observer_, observerIndexToRemove); + emit ObserverRemoved(observer_); + } + + /// @inheritdoc IObserversArray + function observersLength() public view returns (uint256) { + return observers.length; } /// @notice `observer_` index in `observers` array. @@ -84,8 +82,6 @@ contract ObserversArray is Ownable, IObserversArray { return type(uint256).max; } - event FailObserverNotification(address indexed observer); - error ErrorInvalidInterface(); error ErrorZeroAddressObserver(); error ErrorBadObserverInterface(); diff --git a/contracts/lido/TokenRateNotifier.sol b/contracts/lido/TokenRateNotifier.sol index 15f173c5..59904d2e 100644 --- a/contracts/lido/TokenRateNotifier.sol +++ b/contracts/lido/TokenRateNotifier.sol @@ -11,7 +11,7 @@ import {ObserversArray} from "./ObserversArray.sol"; /// @notice Notifies all observers when rebase event occures. contract TokenRateNotifier is ObserversArray, IPostTokenRebaseReceiver { - constructor() ObserversArray(type(ITokenRateObserver).interfaceId) { + constructor() ObserversArray(type(ITokenRateObserver).interfaceId) { } /// @inheritdoc IPostTokenRebaseReceiver diff --git a/contracts/lido/interfaces/IObserversArray.sol b/contracts/lido/interfaces/IObserversArray.sol index b378af07..f3a867b7 100644 --- a/contracts/lido/interfaces/IObserversArray.sol +++ b/contracts/lido/interfaces/IObserversArray.sol @@ -4,48 +4,31 @@ pragma solidity 0.8.10; /// @author kovalgek +/// @notice An interface for observer pattern interface IObserversArray { - /** - * @notice Observer added event - * - * @dev emitted by `addObserver` and `insertObserver` functions - */ + + /// @notice Observer added event + /// @dev emitted by `addObserver` function event ObserverAdded(address indexed observer); - /** - * @notice Observer removed event - * - * @dev emitted by `removeObserver` function - */ - event ObserverRemoved(address indexed observer, uint256 atIndex); - - /** - * @notice Observer length - * @return Added observers count - */ + /// @notice Observer removed event + /// @dev emitted by `removeObserver` function + event ObserverRemoved(address indexed observer); + + /// @notice Observer length + /// @return Added observers count function observersLength() external view returns (uint256); - /** - * @notice Add a `_observer` to the back of array - * @param observer_ observer address - * - * @dev cheapest way to insert new item (doesn't incur additional moves) - */ + /// @notice Add a `observer_` to the back of array + /// @param observer_ observer address function addObserver(address observer_) external; - /** - * @notice Remove a observer at the given `observer_` position - * @param observer_ observer remove position - * - * @dev remove gas cost is higher for the lower `observer_` values - */ + /// @notice Remove a observer at the given `observer_` position + /// @param observer_ observer remove position function removeObserver(address observer_) external; - /** - * @notice Get observer at position - * @return Observer at the given `atIndex_` - * - * @dev function reverts if `atIndex_` is out of range - */ + /// @notice Get observer at position + /// @return Observer at the given `atIndex_` + /// @dev function reverts if `atIndex_` is out of range function observers(uint256 atIndex_) external view returns (address); } diff --git a/contracts/lido/interfaces/ITokenRateObserver.sol b/contracts/lido/interfaces/ITokenRateObserver.sol index cc59efdf..5f86ce24 100644 --- a/contracts/lido/interfaces/ITokenRateObserver.sol +++ b/contracts/lido/interfaces/ITokenRateObserver.sol @@ -4,9 +4,9 @@ pragma solidity 0.8.10; /// @author kovalgek -/// @notice An interface to subscribe for token rebases. Is used to handle different rollups. +/// @notice An interface to subscribe for token rebases. It is used to handle different rollups. interface ITokenRateObserver { - /// @notice Is called when rebase event occures. + /// @notice It's called when rebase event occures. function handleTokenRebased() external; } diff --git a/contracts/optimism/OpStackTokenRateObserver.sol b/contracts/optimism/OpStackTokenRateObserver.sol index 4ad4b58e..38d08d69 100644 --- a/contracts/optimism/OpStackTokenRateObserver.sol +++ b/contracts/optimism/OpStackTokenRateObserver.sol @@ -5,10 +5,11 @@ pragma solidity 0.8.10; import {ITokenRateObserver} from "../lido/interfaces/ITokenRateObserver.sol"; import {L1LidoTokensBridge} from "./L1LidoTokensBridge.sol"; +import {ERC165} from "@openzeppelin/contracts/utils/introspection/ERC165.sol"; /// @author kovalgek /// @notice Pushes token rate when rebase event happens. -contract OpStackTokenRateObserver is ITokenRateObserver { +contract OpStackTokenRateObserver is ERC165, ITokenRateObserver { /// @notice Contract of OpStack bridge. L1LidoTokensBridge public immutable L1_LIDO_TOKENS_BRIDGE; @@ -17,6 +18,7 @@ contract OpStackTokenRateObserver is ITokenRateObserver { uint32 public immutable L2_GAS_LIMIT_FOR_PUSHING_TOKEN_RATE; /// @param lidoTokensBridge_ OpStack bridge. + /// @param l2GasLimitForPushingTokenRate_ Gas limit required to complete pushing token rate on L2. constructor(address lidoTokensBridge_, uint32 l2GasLimitForPushingTokenRate_) { L1_LIDO_TOKENS_BRIDGE = L1LidoTokensBridge(lidoTokensBridge_); L2_GAS_LIMIT_FOR_PUSHING_TOKEN_RATE = l2GasLimitForPushingTokenRate_; @@ -26,4 +28,12 @@ contract OpStackTokenRateObserver is ITokenRateObserver { function handleTokenRebased() external { L1_LIDO_TOKENS_BRIDGE.pushTokenRate(L2_GAS_LIMIT_FOR_PUSHING_TOKEN_RATE); } + + /// @inheritdoc ERC165 + function supportsInterface(bytes4 _interfaceId) public view virtual override returns (bool) { + return ( + _interfaceId == type(ITokenRateObserver).interfaceId + || super.supportsInterface(_interfaceId) + ); + } } diff --git a/contracts/optimism/OpStackTokenRateObserverFactory.sol b/contracts/optimism/OpStackTokenRateObserverFactory.sol new file mode 100644 index 00000000..10065b07 --- /dev/null +++ b/contracts/optimism/OpStackTokenRateObserverFactory.sol @@ -0,0 +1,41 @@ +// SPDX-FileCopyrightText: 2024 Lido +// SPDX-License-Identifier: GPL-3.0 + +pragma solidity 0.8.10; + +import {OpStackTokenRateObserver} from "./OpStackTokenRateObserver.sol"; + +/// @author kovalgek +/// @notice Factory for deploying observers for OP stack. +contract OpStackTokenRateObserverFactory { + + /// @notice deploys observer for OP stack. + /// @param lidoTokensBridge_ OpStack bridge. + /// @param l2GasLimitForPushingTokenRate_ Gas limit required to complete pushing token rate on L2. + function deployOpStackTokenRateObserver( + address lidoTokensBridge_, + uint32 l2GasLimitForPushingTokenRate_ + ) external returns (OpStackTokenRateObserver opStackTokenRateObserver) { + + opStackTokenRateObserver = new OpStackTokenRateObserver( + lidoTokensBridge_, + l2GasLimitForPushingTokenRate_ + ); + + emit OpStackTokenRateObserverDeployed( + msg.sender, + address(opStackTokenRateObserver), + lidoTokensBridge_, + l2GasLimitForPushingTokenRate_ + ); + + return opStackTokenRateObserver; + } + + event OpStackTokenRateObserverDeployed( + address indexed creator, + address indexed opStackTokenRateObserver, + address lidoTokensBridge, + uint32 l2GasLimitForPushingTokenRate + ); +} diff --git a/test/optimism/TokenRateNotifier.unit.test.ts b/test/optimism/TokenRateNotifier.unit.test.ts new file mode 100644 index 00000000..7415c5a7 --- /dev/null +++ b/test/optimism/TokenRateNotifier.unit.test.ts @@ -0,0 +1,126 @@ +import hre from "hardhat"; +import { assert } from "chai"; +import { unit } from "../../utils/testing"; +import { BigNumber, utils } from 'ethers' +import { ethers } from 'hardhat' + +import { + TokenRateNotifier__factory, + ObserversArray__factory, + OpStackTokenRateObserver__factory, + ITokenRateObserver__factory, +} from "../../typechain"; + +unit("TokenRateNotifier", ctxFactory) + + .test("init with wrong interface", async (ctx) => { + const { deployer } = ctx.accounts; + await assert.revertsWith(new ObserversArray__factory(deployer).deploy(BigNumber.from("0xffffffff")._hex), "ErrorInvalidInterface()"); + }) + + .test("initial state", async (ctx) => { + const { tokenRateNotifier } = ctx.contracts; + + assert.equalBN(await tokenRateNotifier.MAX_OBSERVERS_COUNT(), 16); + assert.equal(await tokenRateNotifier.INVALID_INTERFACE_ID(), "0xffffffff"); + const iTokenRateObserver = getInterfaceID(ITokenRateObserver__factory.createInterface()); + assert.equal(await tokenRateNotifier.REQUIRED_INTERFACE(), iTokenRateObserver._hex); + assert.equalBN(await tokenRateNotifier.observersLength(), 0); + }) + + .test("add zero address observer", async (ctx) => { + const { tokenRateNotifier } = ctx.contracts; + await assert.revertsWith(tokenRateNotifier.addObserver(hre.ethers.constants.AddressZero), "ErrorZeroAddressObserver()"); + }) + + .test("add bad interface observer", async (ctx) => { + const { tokenRateNotifier } = ctx.contracts; + const { deployer } = ctx.accounts; + + const observer = await new TokenRateNotifier__factory(deployer).deploy(); + await assert.revertsWith(tokenRateNotifier.addObserver(observer.address), "ErrorBadObserverInterface()"); + }) + + .test("add too many observers", async (ctx) => { + const { tokenRateNotifier } = ctx.contracts; + const { deployer } = ctx.accounts; + + assert.equalBN(await tokenRateNotifier.observersLength(), 0); + + const maxObservers = await tokenRateNotifier.MAX_OBSERVERS_COUNT(); + for (let i = 0; i < maxObservers.toNumber(); i++) { + const observer = await new OpStackTokenRateObserver__factory(deployer).deploy(hre.ethers.constants.AddressZero, 10); + await tokenRateNotifier.addObserver(observer.address); + } + + assert.equalBN(await tokenRateNotifier.observersLength(), maxObservers); + + const observer = await new OpStackTokenRateObserver__factory(deployer).deploy(hre.ethers.constants.AddressZero, 10); + await assert.revertsWith(tokenRateNotifier.addObserver(observer.address), "ErrorMaxObserversCountExceeded()"); + }) + + .test("add observer", async (ctx) => { + const { tokenRateNotifier } = ctx.contracts; + const { deployer } = ctx.accounts; + + assert.equalBN(await tokenRateNotifier.observersLength(), 0); + + const observer = await new OpStackTokenRateObserver__factory(deployer).deploy(hre.ethers.constants.AddressZero, 10); + const tx = await tokenRateNotifier.addObserver(observer.address); + + assert.equalBN(await tokenRateNotifier.observersLength(), 1); + + await assert.emits(tokenRateNotifier, tx, "ObserverAdded", [observer.address]); + }) + + .test("remove non-added observer", async (ctx) => { + const { tokenRateNotifier } = ctx.contracts; + const { deployer } = ctx.accounts; + + assert.equalBN(await tokenRateNotifier.observersLength(), 0); + + const observer = await new OpStackTokenRateObserver__factory(deployer).deploy(hre.ethers.constants.AddressZero, 10); + await assert.revertsWith(tokenRateNotifier.removeObserver(observer.address), "ErrorNoObserverToRemove()"); + }) + + .test("remove observer", async (ctx) => { + const { tokenRateNotifier } = ctx.contracts; + const { deployer } = ctx.accounts; + + assert.equalBN(await tokenRateNotifier.observersLength(), 0); + + const observer = await new OpStackTokenRateObserver__factory(deployer).deploy(hre.ethers.constants.AddressZero, 10); + await tokenRateNotifier.addObserver(observer.address); + + assert.equalBN(await tokenRateNotifier.observersLength(), 1); + + const tx = await tokenRateNotifier.removeObserver(observer.address); + await assert.emits(tokenRateNotifier, tx, "ObserverRemoved", [observer.address]); + + assert.equalBN(await tokenRateNotifier.observersLength(), 0); + }) + + .run(); + +async function ctxFactory() { + + const [deployer, bridge, stranger] = await hre.ethers.getSigners(); + + const tokenRateNotifier = await new TokenRateNotifier__factory(deployer).deploy(); + + return { + accounts: { deployer, bridge, stranger }, + contracts: { tokenRateNotifier } + }; +} + +export function getInterfaceID(contractInterface: utils.Interface) { + let interfaceID = ethers.constants.Zero; + const functions: string[] = Object.keys(contractInterface.functions); + for (let i = 0; i < functions.length; i++) { + interfaceID = interfaceID.xor(contractInterface.getSighash(functions[i])); + } + return interfaceID; +} + + From 7e454105c93cbc2332af955876fa3e0616f98bbc Mon Sep 17 00:00:00 2001 From: Anton Kovalchuk Date: Tue, 19 Mar 2024 14:07:24 +0100 Subject: [PATCH 035/148] add unit tests for notifier --- ...TokenRateObserverWithOutOfGasErrorStub.sol | 30 ++++++++++++ .../TokenRateObserverWithSomeErrorStub.sol | 24 ++++++++++ .../optimism/interfaces/ITokenRatePusher.sol | 12 +++++ .../optimism/stubs/TokenRatePusherStub.sol | 15 ++++++ test/optimism/TokenRateNotifier.unit.test.ts | 47 ++++++++++++++++--- 5 files changed, 122 insertions(+), 6 deletions(-) create mode 100644 contracts/lido/stubs/TokenRateObserverWithOutOfGasErrorStub.sol create mode 100644 contracts/lido/stubs/TokenRateObserverWithSomeErrorStub.sol create mode 100644 contracts/optimism/interfaces/ITokenRatePusher.sol create mode 100644 contracts/optimism/stubs/TokenRatePusherStub.sol diff --git a/contracts/lido/stubs/TokenRateObserverWithOutOfGasErrorStub.sol b/contracts/lido/stubs/TokenRateObserverWithOutOfGasErrorStub.sol new file mode 100644 index 00000000..09cf84a4 --- /dev/null +++ b/contracts/lido/stubs/TokenRateObserverWithOutOfGasErrorStub.sol @@ -0,0 +1,30 @@ +// SPDX-FileCopyrightText: 2024 Lido +// SPDX-License-Identifier: GPL-3.0 + +pragma solidity 0.8.10; + +import {ITokenRateObserver} from "../interfaces/ITokenRateObserver.sol"; +import {ERC165} from "@openzeppelin/contracts/utils/introspection/ERC165.sol"; + +contract TokenRateObserverWithOutOfGasErrorStub is ERC165, ITokenRateObserver { + + error SomeError(); + + mapping (uint256 => uint256) data; + + function handleTokenRebased() external { + for (uint256 i = 0; i < 1000000000000; ++i) { + data[i] = i; + } + + //revert SomeError(); + } + + /// @inheritdoc ERC165 + function supportsInterface(bytes4 _interfaceId) public view virtual override returns (bool) { + return ( + _interfaceId == type(ITokenRateObserver).interfaceId + || super.supportsInterface(_interfaceId) + ); + } +} diff --git a/contracts/lido/stubs/TokenRateObserverWithSomeErrorStub.sol b/contracts/lido/stubs/TokenRateObserverWithSomeErrorStub.sol new file mode 100644 index 00000000..ff0bd615 --- /dev/null +++ b/contracts/lido/stubs/TokenRateObserverWithSomeErrorStub.sol @@ -0,0 +1,24 @@ +// SPDX-FileCopyrightText: 2024 Lido +// SPDX-License-Identifier: GPL-3.0 + +pragma solidity 0.8.10; + +import {ITokenRateObserver} from "../interfaces/ITokenRateObserver.sol"; +import {ERC165} from "@openzeppelin/contracts/utils/introspection/ERC165.sol"; + +contract TokenRateObserverWithSomeErrorStub is ERC165, ITokenRateObserver { + + error SomeError(); + + function handleTokenRebased() external { + revert SomeError(); + } + + /// @inheritdoc ERC165 + function supportsInterface(bytes4 _interfaceId) public view virtual override returns (bool) { + return ( + _interfaceId == type(ITokenRateObserver).interfaceId + || super.supportsInterface(_interfaceId) + ); + } +} diff --git a/contracts/optimism/interfaces/ITokenRatePusher.sol b/contracts/optimism/interfaces/ITokenRatePusher.sol new file mode 100644 index 00000000..14952553 --- /dev/null +++ b/contracts/optimism/interfaces/ITokenRatePusher.sol @@ -0,0 +1,12 @@ +// SPDX-FileCopyrightText: 2024 Lido +// SPDX-License-Identifier: GPL-3.0 + +pragma solidity 0.8.10; + +/// @author kovalgek +/// @notice An interface for entity that pushes rate. +interface ITokenRatePusher { + /// @notice Pushes token rate to L2 by depositing zero tokens. + /// @param l2Gas_ Gas limit required to complete the deposit on L2. + function pushTokenRate(uint32 l2Gas_) external; +} diff --git a/contracts/optimism/stubs/TokenRatePusherStub.sol b/contracts/optimism/stubs/TokenRatePusherStub.sol new file mode 100644 index 00000000..8748e26d --- /dev/null +++ b/contracts/optimism/stubs/TokenRatePusherStub.sol @@ -0,0 +1,15 @@ +// SPDX-FileCopyrightText: 2024 Lido +// SPDX-License-Identifier: GPL-3.0 + +pragma solidity 0.8.10; + +import {ITokenRatePusher} from "../interfaces/ITokenRatePusher.sol"; + +contract TokenRatePusherStub is ITokenRatePusher { + + uint32 public l2Gas; + + function pushTokenRate(uint32 l2Gas_) external { + l2Gas = l2Gas_; + } +} diff --git a/test/optimism/TokenRateNotifier.unit.test.ts b/test/optimism/TokenRateNotifier.unit.test.ts index 7415c5a7..73a1af4a 100644 --- a/test/optimism/TokenRateNotifier.unit.test.ts +++ b/test/optimism/TokenRateNotifier.unit.test.ts @@ -2,13 +2,15 @@ import hre from "hardhat"; import { assert } from "chai"; import { unit } from "../../utils/testing"; import { BigNumber, utils } from 'ethers' -import { ethers } from 'hardhat' import { TokenRateNotifier__factory, ObserversArray__factory, OpStackTokenRateObserver__factory, ITokenRateObserver__factory, + TokenRateObserverWithSomeErrorStub__factory, + TokenRateObserverWithOutOfGasErrorStub__factory, + TokenRatePusherStub__factory } from "../../typechain"; unit("TokenRateNotifier", ctxFactory) @@ -100,12 +102,47 @@ unit("TokenRateNotifier", ctxFactory) assert.equalBN(await tokenRateNotifier.observersLength(), 0); }) + .test("notify observers with some error", async (ctx) => { + const { tokenRateNotifier } = ctx.contracts; + const { deployer } = ctx.accounts; + + const observer = await new TokenRateObserverWithSomeErrorStub__factory(deployer).deploy(); + await tokenRateNotifier.addObserver(observer.address); + + const tx = await tokenRateNotifier.handlePostTokenRebase(1,2,3,4,5,6,7); + + await assert.emits(tokenRateNotifier, tx, "HandleTokenRebasedFailed", [observer.address, "0x332e27d2"]); + }) + + .test("notify observers with out of gas error", async (ctx) => { + const { tokenRateNotifier } = ctx.contracts; + const { deployer } = ctx.accounts; + + const observer = await new TokenRateObserverWithOutOfGasErrorStub__factory(deployer).deploy(); + await tokenRateNotifier.addObserver(observer.address); + + await assert.revertsWith(tokenRateNotifier.handlePostTokenRebase(1,2,3,4,5,6,7), "ErrorUnrecoverableObserver()"); + }) + + .test("notify observers", async (ctx) => { + const { tokenRateNotifier } = ctx.contracts; + const { deployer } = ctx.accounts; + + const tokenRatePusher = await new TokenRatePusherStub__factory(deployer).deploy(); + const observer = await new OpStackTokenRateObserver__factory(deployer).deploy(tokenRatePusher.address, 22); + await tokenRateNotifier.addObserver(observer.address); + + assert.equalBN(await tokenRatePusher.l2Gas(), 0); + + await tokenRateNotifier.handlePostTokenRebase(1,2,3,4,5,6,7); + + assert.equalBN(await tokenRatePusher.l2Gas(), 22); + }) + .run(); async function ctxFactory() { - const [deployer, bridge, stranger] = await hre.ethers.getSigners(); - const tokenRateNotifier = await new TokenRateNotifier__factory(deployer).deploy(); return { @@ -115,12 +152,10 @@ async function ctxFactory() { } export function getInterfaceID(contractInterface: utils.Interface) { - let interfaceID = ethers.constants.Zero; + let interfaceID = hre.ethers.constants.Zero; const functions: string[] = Object.keys(contractInterface.functions); for (let i = 0; i < functions.length; i++) { interfaceID = interfaceID.xor(contractInterface.getSighash(functions[i])); } return interfaceID; } - - From 55111b8cf71c57ba96af6a8fa7a7de25e6dbe33a Mon Sep 17 00:00:00 2001 From: Anton Kovalchuk Date: Fri, 22 Mar 2024 13:54:53 +0100 Subject: [PATCH 036/148] add token rate pusher --- contracts/lido/ObserversArray.sol | 4 +- contracts/lido/TokenRateNotifier.sol | 6 +-- .../lido/interfaces/ITokenRateObserver.sol | 12 ----- .../interfaces/ITokenRatePusher.sol | 3 +- contracts/optimism/L1ERC20TokenBridge.sol | 15 ++---- contracts/optimism/L2ERC20TokenBridge.sol | 4 +- .../optimism/OpStackTokenRateObserver.sol | 39 -------------- .../OpStackTokenRateObserverFactory.sol | 41 --------------- contracts/optimism/OpStackTokenRatePusher.sol | 51 +++++++++++++++++++ .../RebasableAndNonRebasableTokens.sol | 4 +- contracts/optimism/TokenRateOracle.sol | 19 +++++-- 11 files changed, 82 insertions(+), 116 deletions(-) delete mode 100644 contracts/lido/interfaces/ITokenRateObserver.sol rename contracts/{optimism => lido}/interfaces/ITokenRatePusher.sol (69%) delete mode 100644 contracts/optimism/OpStackTokenRateObserver.sol delete mode 100644 contracts/optimism/OpStackTokenRateObserverFactory.sol create mode 100644 contracts/optimism/OpStackTokenRatePusher.sol diff --git a/contracts/lido/ObserversArray.sol b/contracts/lido/ObserversArray.sol index 3c1e6fcf..8d1698f8 100644 --- a/contracts/lido/ObserversArray.sol +++ b/contracts/lido/ObserversArray.sol @@ -34,7 +34,7 @@ contract ObserversArray is Ownable, IObserversArray { } /// @inheritdoc IObserversArray - function addObserver(address observer_) external virtual onlyOwner { + function addObserver(address observer_) external onlyOwner { if (observer_ == address(0)) { revert ErrorZeroAddressObserver(); } @@ -50,7 +50,7 @@ contract ObserversArray is Ownable, IObserversArray { } /// @inheritdoc IObserversArray - function removeObserver(address observer_) external virtual onlyOwner { + function removeObserver(address observer_) external onlyOwner { uint256 observerIndexToRemove = _observerIndex(observer_); diff --git a/contracts/lido/TokenRateNotifier.sol b/contracts/lido/TokenRateNotifier.sol index 59904d2e..8bd28d39 100644 --- a/contracts/lido/TokenRateNotifier.sol +++ b/contracts/lido/TokenRateNotifier.sol @@ -4,14 +4,14 @@ pragma solidity 0.8.10; import {IPostTokenRebaseReceiver} from "./interfaces/IPostTokenRebaseReceiver.sol"; -import {ITokenRateObserver} from "./interfaces/ITokenRateObserver.sol"; +import {ITokenRatePusher} from "./interfaces/ITokenRatePusher.sol"; import {ObserversArray} from "./ObserversArray.sol"; /// @author kovalgek /// @notice Notifies all observers when rebase event occures. contract TokenRateNotifier is ObserversArray, IPostTokenRebaseReceiver { - constructor() ObserversArray(type(ITokenRateObserver).interfaceId) { + constructor() ObserversArray(type(ITokenRatePusher).interfaceId) { } /// @inheritdoc IPostTokenRebaseReceiver @@ -27,7 +27,7 @@ contract TokenRateNotifier is ObserversArray, IPostTokenRebaseReceiver { uint256 observersLength = observersLength(); for (uint256 obIndex = 0; obIndex < observersLength; obIndex++) { - try ITokenRateObserver(observers[obIndex]).handleTokenRebased() {} + try ITokenRatePusher(observers[obIndex]).pushTokenRate() {} catch (bytes memory lowLevelRevertData) { /// @dev This check is required to prevent incorrect gas estimation of the method. /// Without it, Ethereum nodes that use binary search for gas estimation may diff --git a/contracts/lido/interfaces/ITokenRateObserver.sol b/contracts/lido/interfaces/ITokenRateObserver.sol deleted file mode 100644 index 5f86ce24..00000000 --- a/contracts/lido/interfaces/ITokenRateObserver.sol +++ /dev/null @@ -1,12 +0,0 @@ -// SPDX-FileCopyrightText: 2024 Lido -// SPDX-License-Identifier: GPL-3.0 - -pragma solidity 0.8.10; - -/// @author kovalgek -/// @notice An interface to subscribe for token rebases. It is used to handle different rollups. -interface ITokenRateObserver { - - /// @notice It's called when rebase event occures. - function handleTokenRebased() external; -} diff --git a/contracts/optimism/interfaces/ITokenRatePusher.sol b/contracts/lido/interfaces/ITokenRatePusher.sol similarity index 69% rename from contracts/optimism/interfaces/ITokenRatePusher.sol rename to contracts/lido/interfaces/ITokenRatePusher.sol index 14952553..9492a240 100644 --- a/contracts/optimism/interfaces/ITokenRatePusher.sol +++ b/contracts/lido/interfaces/ITokenRatePusher.sol @@ -7,6 +7,5 @@ pragma solidity 0.8.10; /// @notice An interface for entity that pushes rate. interface ITokenRatePusher { /// @notice Pushes token rate to L2 by depositing zero tokens. - /// @param l2Gas_ Gas limit required to complete the deposit on L2. - function pushTokenRate(uint32 l2Gas_) external; + function pushTokenRate() external; } diff --git a/contracts/optimism/L1ERC20TokenBridge.sol b/contracts/optimism/L1ERC20TokenBridge.sol index 7b30efe2..087df07f 100644 --- a/contracts/optimism/L1ERC20TokenBridge.sol +++ b/contracts/optimism/L1ERC20TokenBridge.sol @@ -48,14 +48,9 @@ abstract contract L1ERC20TokenBridge is L2_TOKEN_BRIDGE = l2TokenBridge_; } + /// @notice required to abstact a way token rate is requested. function tokenRate() virtual internal view returns (uint256); - /// @notice Pushes token rate to L2 by depositing zero tokens. - /// @param l2Gas_ Gas limit required to complete the deposit on L2. - function pushTokenRate(uint32 l2Gas_) external { - _depositERC20To(L1_TOKEN_REBASABLE, L2_TOKEN_REBASABLE, L2_TOKEN_BRIDGE, 0, l2Gas_, ""); - } - /// @inheritdoc IL1ERC20Bridge function l2TokenBridge() external view returns (address) { return L2_TOKEN_BRIDGE; @@ -114,7 +109,7 @@ abstract contract L1ERC20TokenBridge is onlySupportedL2Token(l2Token_) onlyFromCrossDomainAccount(L2_TOKEN_BRIDGE) { - if (isRebasableTokenFlow(l1Token_, l2Token_)) { + if (_isRebasableTokenFlow(l1Token_, l2Token_)) { uint256 rebasableTokenAmount = IERC20Wrapper(L1_TOKEN_NON_REBASABLE).unwrap(amount_); IERC20(L1_TOKEN_REBASABLE).safeTransfer(to_, rebasableTokenAmount); @@ -126,7 +121,7 @@ abstract contract L1ERC20TokenBridge is rebasableTokenAmount, data_ ); - } else if (isNonRebasableTokenFlow(l1Token_, l2Token_)) { + } else if (_isNonRebasableTokenFlow(l1Token_, l2Token_)) { IERC20(L1_TOKEN_NON_REBASABLE).safeTransfer(to_, amount_); emit ERC20WithdrawalFinalized( @@ -148,7 +143,7 @@ abstract contract L1ERC20TokenBridge is uint32 l2Gas_, bytes memory data_ ) internal { - if (isRebasableTokenFlow(l1Token_, l2Token_)) { + if (_isRebasableTokenFlow(l1Token_, l2Token_)) { DepositData memory depositData = DepositData({ rate: uint96(tokenRate()), timestamp: uint40(block.timestamp), @@ -203,7 +198,7 @@ abstract contract L1ERC20TokenBridge is amount_, encodedDepositData ); - } else if (isNonRebasableTokenFlow(l1Token_, l2Token_)) { + } else if (_isNonRebasableTokenFlow(l1Token_, l2Token_)) { IERC20(L1_TOKEN_NON_REBASABLE).safeTransferFrom(msg.sender, address(this), amount_); _initiateERC20Deposit( diff --git a/contracts/optimism/L2ERC20TokenBridge.sol b/contracts/optimism/L2ERC20TokenBridge.sol index cb8cc58f..2c7b54b4 100644 --- a/contracts/optimism/L2ERC20TokenBridge.sol +++ b/contracts/optimism/L2ERC20TokenBridge.sol @@ -93,7 +93,7 @@ contract L2ERC20TokenBridge is onlySupportedL2Token(l2Token_) onlyFromCrossDomainAccount(L1_TOKEN_BRIDGE) { - if (isRebasableTokenFlow(l1Token_, l2Token_)) { + if (_isRebasableTokenFlow(l1Token_, l2Token_)) { DepositData memory depositData = decodeDepositData(data_); ITokenRateOracle tokenRateOracle = ERC20Rebasable(L2_TOKEN_REBASABLE).TOKEN_RATE_ORACLE(); tokenRateOracle.updateRate(depositData.rate, depositData.timestamp); @@ -109,7 +109,7 @@ contract L2ERC20TokenBridge is rebasableTokenAmount, depositData.data ); - } else if (isNonRebasableTokenFlow(l1Token_, l2Token_)) { + } else if (_isNonRebasableTokenFlow(l1Token_, l2Token_)) { IERC20Bridged(L2_TOKEN_NON_REBASABLE).bridgeMint(to_, amount_); emit DepositFinalized( L1_TOKEN_NON_REBASABLE, diff --git a/contracts/optimism/OpStackTokenRateObserver.sol b/contracts/optimism/OpStackTokenRateObserver.sol deleted file mode 100644 index 38d08d69..00000000 --- a/contracts/optimism/OpStackTokenRateObserver.sol +++ /dev/null @@ -1,39 +0,0 @@ -// SPDX-FileCopyrightText: 2024 Lido -// SPDX-License-Identifier: GPL-3.0 - -pragma solidity 0.8.10; - -import {ITokenRateObserver} from "../lido/interfaces/ITokenRateObserver.sol"; -import {L1LidoTokensBridge} from "./L1LidoTokensBridge.sol"; -import {ERC165} from "@openzeppelin/contracts/utils/introspection/ERC165.sol"; - -/// @author kovalgek -/// @notice Pushes token rate when rebase event happens. -contract OpStackTokenRateObserver is ERC165, ITokenRateObserver { - - /// @notice Contract of OpStack bridge. - L1LidoTokensBridge public immutable L1_LIDO_TOKENS_BRIDGE; - - /// @notice Gas limit required to complete pushing token rate on L2. - uint32 public immutable L2_GAS_LIMIT_FOR_PUSHING_TOKEN_RATE; - - /// @param lidoTokensBridge_ OpStack bridge. - /// @param l2GasLimitForPushingTokenRate_ Gas limit required to complete pushing token rate on L2. - constructor(address lidoTokensBridge_, uint32 l2GasLimitForPushingTokenRate_) { - L1_LIDO_TOKENS_BRIDGE = L1LidoTokensBridge(lidoTokensBridge_); - L2_GAS_LIMIT_FOR_PUSHING_TOKEN_RATE = l2GasLimitForPushingTokenRate_; - } - - /// @inheritdoc ITokenRateObserver - function handleTokenRebased() external { - L1_LIDO_TOKENS_BRIDGE.pushTokenRate(L2_GAS_LIMIT_FOR_PUSHING_TOKEN_RATE); - } - - /// @inheritdoc ERC165 - function supportsInterface(bytes4 _interfaceId) public view virtual override returns (bool) { - return ( - _interfaceId == type(ITokenRateObserver).interfaceId - || super.supportsInterface(_interfaceId) - ); - } -} diff --git a/contracts/optimism/OpStackTokenRateObserverFactory.sol b/contracts/optimism/OpStackTokenRateObserverFactory.sol deleted file mode 100644 index 10065b07..00000000 --- a/contracts/optimism/OpStackTokenRateObserverFactory.sol +++ /dev/null @@ -1,41 +0,0 @@ -// SPDX-FileCopyrightText: 2024 Lido -// SPDX-License-Identifier: GPL-3.0 - -pragma solidity 0.8.10; - -import {OpStackTokenRateObserver} from "./OpStackTokenRateObserver.sol"; - -/// @author kovalgek -/// @notice Factory for deploying observers for OP stack. -contract OpStackTokenRateObserverFactory { - - /// @notice deploys observer for OP stack. - /// @param lidoTokensBridge_ OpStack bridge. - /// @param l2GasLimitForPushingTokenRate_ Gas limit required to complete pushing token rate on L2. - function deployOpStackTokenRateObserver( - address lidoTokensBridge_, - uint32 l2GasLimitForPushingTokenRate_ - ) external returns (OpStackTokenRateObserver opStackTokenRateObserver) { - - opStackTokenRateObserver = new OpStackTokenRateObserver( - lidoTokensBridge_, - l2GasLimitForPushingTokenRate_ - ); - - emit OpStackTokenRateObserverDeployed( - msg.sender, - address(opStackTokenRateObserver), - lidoTokensBridge_, - l2GasLimitForPushingTokenRate_ - ); - - return opStackTokenRateObserver; - } - - event OpStackTokenRateObserverDeployed( - address indexed creator, - address indexed opStackTokenRateObserver, - address lidoTokensBridge, - uint32 l2GasLimitForPushingTokenRate - ); -} diff --git a/contracts/optimism/OpStackTokenRatePusher.sol b/contracts/optimism/OpStackTokenRatePusher.sol new file mode 100644 index 00000000..336252fb --- /dev/null +++ b/contracts/optimism/OpStackTokenRatePusher.sol @@ -0,0 +1,51 @@ +// SPDX-FileCopyrightText: 2024 Lido +// SPDX-License-Identifier: GPL-3.0 + +pragma solidity 0.8.10; + +import {CrossDomainEnabled} from "./CrossDomainEnabled.sol"; +import {ITokenRatePusher} from "../lido/interfaces/ITokenRatePusher.sol"; +import {IERC20WstETH} from "../token/interfaces/IERC20WstETH.sol"; +import {ITokenRateOracle} from "../token/interfaces/ITokenRateOracle.sol"; + +/// @author kovalgek +/// @notice Pushes token rate to L2 Oracle. +contract OpStackTokenRatePusher is CrossDomainEnabled, ITokenRatePusher { + + /// @notice Oracle address on L2 for receiving token rate. + address public immutable L2_TOKEN_RATE_ORACLE; + + /// @notice Non-rebasable token of Core Lido procotol. + address public immutable WST_ETH; + + /// @notice Gas limit required to complete pushing token rate on L2. + uint32 public immutable L2_GAS_LIMIT_FOR_PUSHING_TOKEN_RATE; + + /// @param messenger_ L1 messenger address being used for cross-chain communications + /// @param wstEth_ Non-rebasable token of Core Lido procotol. + /// @param tokenRateOracle_ Oracle address on L2 for receiving token rate. + /// @param l2GasLimitForPushingTokenRate_ Gas limit required to complete pushing token rate on L2. + constructor( + address messenger_, + address wstEth_, + address tokenRateOracle_, + uint32 l2GasLimitForPushingTokenRate_ + ) CrossDomainEnabled(messenger_) { + WST_ETH = wstEth_; + L2_TOKEN_RATE_ORACLE = tokenRateOracle_; + L2_GAS_LIMIT_FOR_PUSHING_TOKEN_RATE = l2GasLimitForPushingTokenRate_; + } + + /// @inheritdoc ITokenRatePusher + function pushTokenRate() external { + uint256 tokenRate = IERC20WstETH(WST_ETH).stEthPerToken(); + + bytes memory message = abi.encodeWithSelector( + ITokenRateOracle.updateRate.selector, + tokenRate, + block.timestamp + ); + + sendCrossDomainMessage(L2_TOKEN_RATE_ORACLE, L2_GAS_LIMIT_FOR_PUSHING_TOKEN_RATE, message); + } +} diff --git a/contracts/optimism/RebasableAndNonRebasableTokens.sol b/contracts/optimism/RebasableAndNonRebasableTokens.sol index a7cca519..bffff151 100644 --- a/contracts/optimism/RebasableAndNonRebasableTokens.sol +++ b/contracts/optimism/RebasableAndNonRebasableTokens.sol @@ -53,11 +53,11 @@ contract RebasableAndNonRebasableTokens { _; } - function isRebasableTokenFlow(address l1Token_, address l2Token_) internal view returns (bool) { + function _isRebasableTokenFlow(address l1Token_, address l2Token_) internal view returns (bool) { return l1Token_ == L1_TOKEN_REBASABLE && l2Token_ == L2_TOKEN_REBASABLE; } - function isNonRebasableTokenFlow(address l1Token_, address l2Token_) internal view returns (bool) { + function _isNonRebasableTokenFlow(address l1Token_, address l2Token_) internal view returns (bool) { return l1Token_ == L1_TOKEN_NON_REBASABLE && l2Token_ == L2_TOKEN_NON_REBASABLE; } diff --git a/contracts/optimism/TokenRateOracle.sol b/contracts/optimism/TokenRateOracle.sol index dae90761..20e92a25 100644 --- a/contracts/optimism/TokenRateOracle.sol +++ b/contracts/optimism/TokenRateOracle.sol @@ -4,14 +4,18 @@ pragma solidity 0.8.10; import {ITokenRateOracle} from "../token/interfaces/ITokenRateOracle.sol"; +import {CrossDomainEnabled} from "./CrossDomainEnabled.sol"; /// @author kovalgek /// @notice Oracle for storing token rate. -contract TokenRateOracle is ITokenRateOracle { +contract TokenRateOracle is CrossDomainEnabled, ITokenRateOracle { /// @notice A bridge which can update oracle. address public immutable BRIDGE; + /// @notice An address of account on L1 that can update token rate. + address public immutable L1_TOKEN_RATE_PUSHER; + /// @notice A time period when token rate can be considered outdated. uint256 public immutable RATE_OUTDATED_DELAY; @@ -24,10 +28,18 @@ contract TokenRateOracle is ITokenRateOracle { /// @notice Decimals of the oracle response. uint8 private constant DECIMALS = 18; + /// @param messenger_ L2 messenger address being used for cross-chain communications /// @param bridge_ the bridge address that has a right to updates oracle. + /// @param l1TokenRatePusher_ An address of account on L1 that can update token rate. /// @param rateOutdatedDelay_ time period when token rate can be considered outdated. - constructor(address bridge_, uint256 rateOutdatedDelay_) { + constructor( + address messenger_, + address bridge_, + address l1TokenRatePusher_, + uint256 rateOutdatedDelay_ + ) CrossDomainEnabled(messenger_) { BRIDGE = bridge_; + L1_TOKEN_RATE_PUSHER = l1TokenRatePusher_; RATE_OUTDATED_DELAY = rateOutdatedDelay_; } @@ -63,7 +75,8 @@ contract TokenRateOracle is ITokenRateOracle { /// @inheritdoc ITokenRateOracle function updateRate(uint256 tokenRate_, uint256 rateL1Timestamp_) external { - if (msg.sender != BRIDGE) { + if ((msg.sender != address(MESSENGER) || MESSENGER.xDomainMessageSender() != L1_TOKEN_RATE_PUSHER) && + (msg.sender != BRIDGE) { revert ErrorNoRights(msg.sender); } From d99a52f961edf13c2a2931c5285c52e955342fc2 Mon Sep 17 00:00:00 2001 From: Anton Kovalchuk Date: Fri, 22 Mar 2024 14:24:40 +0100 Subject: [PATCH 037/148] update unit tests for token rate oracle --- contracts/optimism/TokenRateOracle.sol | 2 +- test/optimism/TokenRateOracle.unit.test.ts | 77 ++++++++++++++++++---- 2 files changed, 64 insertions(+), 15 deletions(-) diff --git a/contracts/optimism/TokenRateOracle.sol b/contracts/optimism/TokenRateOracle.sol index 20e92a25..8c908da8 100644 --- a/contracts/optimism/TokenRateOracle.sol +++ b/contracts/optimism/TokenRateOracle.sol @@ -76,7 +76,7 @@ contract TokenRateOracle is CrossDomainEnabled, ITokenRateOracle { function updateRate(uint256 tokenRate_, uint256 rateL1Timestamp_) external { if ((msg.sender != address(MESSENGER) || MESSENGER.xDomainMessageSender() != L1_TOKEN_RATE_PUSHER) && - (msg.sender != BRIDGE) { + msg.sender != BRIDGE) { revert ErrorNoRights(msg.sender); } diff --git a/test/optimism/TokenRateOracle.unit.test.ts b/test/optimism/TokenRateOracle.unit.test.ts index 56aafc81..6851237d 100644 --- a/test/optimism/TokenRateOracle.unit.test.ts +++ b/test/optimism/TokenRateOracle.unit.test.ts @@ -1,15 +1,22 @@ import hre from "hardhat"; import { assert } from "chai"; -import { unit } from "../../utils/testing"; -import { TokenRateOracle__factory } from "../../typechain"; +import testing, { unit } from "../../utils/testing"; +import { + TokenRateOracle__factory, + CrossDomainMessengerStub__factory +} from "../../typechain"; +import { wei } from "../../utils/wei"; unit("TokenRateOracle", ctxFactory) .test("state after init", async (ctx) => { - const { tokenRateOracle } = ctx.contracts; - const { bridge } = ctx.accounts; + const { tokenRateOracle, l2MessengerStub } = ctx.contracts; + const { bridge, l1TokenBridgeEOA } = ctx.accounts; + assert.equal(await tokenRateOracle.MESSENGER(), l2MessengerStub.address); assert.equal(await tokenRateOracle.BRIDGE(), bridge.address); + assert.equal(await tokenRateOracle.L1_TOKEN_RATE_PUSHER(), l1TokenBridgeEOA.address); + assert.equalBN(await tokenRateOracle.RATE_OUTDATED_DELAY(), 86400); assert.equalBN(await tokenRateOracle.latestAnswer(), 0); @@ -29,22 +36,28 @@ unit("TokenRateOracle", ctxFactory) assert.equalBN(await tokenRateOracle.decimals(), 18); }) - .test("updateRate() :: no rights to call", async (ctx) => { + .test("updateRate() :: called by non-bridge account", async (ctx) => { const { tokenRateOracle } = ctx.contracts; - const { bridge, stranger } = ctx.accounts; - tokenRateOracle.connect(bridge).updateRate(10, 20); + const { stranger } = ctx.accounts; await assert.revertsWith(tokenRateOracle.connect(stranger).updateRate(10, 40), "ErrorNoRights(\""+stranger.address+"\")"); }) + .test("updateRate() :: called by messenger with incorrect cross-domain sender", async (ctx) => { + const { tokenRateOracle, l2MessengerStub } = ctx.contracts; + const { stranger, l2MessengerStubEOA } = ctx.accounts; + await l2MessengerStub.setXDomainMessageSender(stranger.address); + await assert.revertsWith(tokenRateOracle.connect(l2MessengerStubEOA).updateRate(10, 40), "ErrorNoRights(\""+l2MessengerStubEOA._address+"\")"); + }) + .test("updateRate() :: incorrect time", async (ctx) => { const { tokenRateOracle } = ctx.contracts; const { bridge } = ctx.accounts; - tokenRateOracle.connect(bridge).updateRate(10, 1000); + await tokenRateOracle.connect(bridge).updateRate(10, 1000); await assert.revertsWith(tokenRateOracle.connect(bridge).updateRate(12, 20), "ErrorIncorrectRateTimestamp()"); }) - .test("updateRate() :: dont update state if values are the same", async (ctx) => { + .test("updateRate() :: don't update state if values are the same", async (ctx) => { const { tokenRateOracle } = ctx.contracts; const { bridge } = ctx.accounts; @@ -55,14 +68,43 @@ unit("TokenRateOracle", ctxFactory) await assert.notEmits(tokenRateOracle, tx2, "RateUpdated"); }) - .test("updateRate() :: happy path", async (ctx) => { + .test("updateRate() :: happy path called by bridge", async (ctx) => { const { tokenRateOracle } = ctx.contracts; const { bridge } = ctx.accounts; const currentTime = Date.now(); const tokenRate = 123; - await tokenRateOracle.connect(bridge).updateRate(tokenRate, currentTime ); + await tokenRateOracle.connect(bridge).updateRate(tokenRate, currentTime); + + assert.equalBN(await tokenRateOracle.latestAnswer(), tokenRate); + + const { + roundId_, + answer_, + startedAt_, + updatedAt_, + answeredInRound_ + } = await tokenRateOracle.latestRoundData(); + + assert.equalBN(roundId_, currentTime); + assert.equalBN(answer_, tokenRate); + assert.equalBN(startedAt_, currentTime); + assert.equalBN(updatedAt_, currentTime); + assert.equalBN(answeredInRound_, currentTime); + assert.equalBN(await tokenRateOracle.decimals(), 18); + }) + + .test("updateRate() :: happy path called by messenger with correct cross-domain sender", async (ctx) => { + const { tokenRateOracle, l2MessengerStub } = ctx.contracts; + const { l2MessengerStubEOA, l1TokenBridgeEOA } = ctx.accounts; + + await l2MessengerStub.setXDomainMessageSender(l1TokenBridgeEOA.address); + + const currentTime = Date.now(); + const tokenRate = 123; + + await tokenRateOracle.connect(l2MessengerStubEOA).updateRate(tokenRate, currentTime); assert.equalBN(await tokenRateOracle.latestAnswer(), tokenRate); @@ -86,15 +128,22 @@ unit("TokenRateOracle", ctxFactory) async function ctxFactory() { - const [deployer, bridge, stranger] = await hre.ethers.getSigners(); + const [deployer, bridge, stranger, l1TokenBridgeEOA] = await hre.ethers.getSigners(); + + const l2MessengerStub = await new CrossDomainMessengerStub__factory( + deployer + ).deploy({ value: wei.toBigNumber(wei`1 ether`) }); + const l2MessengerStubEOA = await testing.impersonate(l2MessengerStub.address); const tokenRateOracle = await new TokenRateOracle__factory(deployer).deploy( + l2MessengerStub.address, bridge.address, + l1TokenBridgeEOA.address, 86400 ); return { - accounts: { deployer, bridge, stranger }, - contracts: { tokenRateOracle } + accounts: { deployer, bridge, stranger, l1TokenBridgeEOA, l2MessengerStubEOA }, + contracts: { tokenRateOracle, l2MessengerStub } }; } From fb3eb406bd2f0557a231d84002bed5c2a22bc175 Mon Sep 17 00:00:00 2001 From: Anton Kovalchuk Date: Fri, 22 Mar 2024 19:03:57 +0100 Subject: [PATCH 038/148] remove observer array --- contracts/lido/ObserversArray.sol | 90 ----------------- contracts/lido/TokenRateNotifier.sol | 97 ++++++++++++++++--- contracts/lido/interfaces/IObserversArray.sol | 34 ------- contracts/optimism/OpStackTokenRatePusher.sol | 6 +- contracts/optimism/TokenRateOracle.sol | 6 +- test/token/ERC20Rebasable.unit.test.ts | 25 +++-- 6 files changed, 111 insertions(+), 147 deletions(-) delete mode 100644 contracts/lido/ObserversArray.sol delete mode 100644 contracts/lido/interfaces/IObserversArray.sol diff --git a/contracts/lido/ObserversArray.sol b/contracts/lido/ObserversArray.sol deleted file mode 100644 index 8d1698f8..00000000 --- a/contracts/lido/ObserversArray.sol +++ /dev/null @@ -1,90 +0,0 @@ -// SPDX-FileCopyrightText: 2024 Lido -// SPDX-License-Identifier: GPL-3.0 - -pragma solidity 0.8.10; - -import {Ownable} from "@openzeppelin/contracts/access/Ownable.sol"; -import {ERC165Checker} from "@openzeppelin/contracts/utils/introspection/ERC165Checker.sol"; -import {IObserversArray} from "./interfaces/IObserversArray.sol"; - -/// @author kovalgek -/// @notice Manage observers. -contract ObserversArray is Ownable, IObserversArray { - using ERC165Checker for address; - - /// @notice Maximum amount of observers to be supported. - uint256 public constant MAX_OBSERVERS_COUNT = 16; - - /// @notice Invalid interface id. - bytes4 public constant INVALID_INTERFACE_ID = 0xffffffff; - - /// @notice An interface that each observer should support. - bytes4 public immutable REQUIRED_INTERFACE; - - /// @notice All observers. - address[] public observers; - - /// @param requiredInterface_ An interface that each observer should support. - constructor(bytes4 requiredInterface_) { - if (requiredInterface_ == INVALID_INTERFACE_ID) { - revert ErrorInvalidInterface(); - } - - REQUIRED_INTERFACE = requiredInterface_; - } - - /// @inheritdoc IObserversArray - function addObserver(address observer_) external onlyOwner { - if (observer_ == address(0)) { - revert ErrorZeroAddressObserver(); - } - if (!observer_.supportsInterface(REQUIRED_INTERFACE)) { - revert ErrorBadObserverInterface(); - } - if (observers.length >= MAX_OBSERVERS_COUNT) { - revert ErrorMaxObserversCountExceeded(); - } - - observers.push(observer_); - emit ObserverAdded(observer_); - } - - /// @inheritdoc IObserversArray - function removeObserver(address observer_) external onlyOwner { - - uint256 observerIndexToRemove = _observerIndex(observer_); - - if (observerIndexToRemove == type(uint256).max) { - revert ErrorNoObserverToRemove(); - } - - for (uint256 obIndex = observerIndexToRemove; obIndex < observers.length - 1; obIndex++) { - observers[obIndex] = observers[obIndex + 1]; - } - - observers.pop(); - - emit ObserverRemoved(observer_); - } - - /// @inheritdoc IObserversArray - function observersLength() public view returns (uint256) { - return observers.length; - } - - /// @notice `observer_` index in `observers` array. - function _observerIndex(address observer_) internal view returns (uint256) { - for (uint256 obIndex = 0; obIndex < observers.length; obIndex++) { - if (observers[obIndex] == observer_) { - return obIndex; - } - } - return type(uint256).max; - } - - error ErrorInvalidInterface(); - error ErrorZeroAddressObserver(); - error ErrorBadObserverInterface(); - error ErrorMaxObserversCountExceeded(); - error ErrorNoObserverToRemove(); -} diff --git a/contracts/lido/TokenRateNotifier.sol b/contracts/lido/TokenRateNotifier.sol index 8bd28d39..55004123 100644 --- a/contracts/lido/TokenRateNotifier.sol +++ b/contracts/lido/TokenRateNotifier.sol @@ -5,13 +5,63 @@ pragma solidity 0.8.10; import {IPostTokenRebaseReceiver} from "./interfaces/IPostTokenRebaseReceiver.sol"; import {ITokenRatePusher} from "./interfaces/ITokenRatePusher.sol"; -import {ObserversArray} from "./ObserversArray.sol"; +import {Ownable} from "@openzeppelin/contracts/access/Ownable.sol"; +import {ERC165Checker} from "@openzeppelin/contracts/utils/introspection/ERC165Checker.sol"; /// @author kovalgek /// @notice Notifies all observers when rebase event occures. -contract TokenRateNotifier is ObserversArray, IPostTokenRebaseReceiver { +contract TokenRateNotifier is Ownable, IPostTokenRebaseReceiver { + using ERC165Checker for address; - constructor() ObserversArray(type(ITokenRatePusher).interfaceId) { + /// @notice Maximum amount of observers to be supported. + uint256 public constant MAX_OBSERVERS_COUNT = 16; + + /// @notice Invalid interface id. + bytes4 public constant INVALID_INTERFACE_ID = 0xffffffff; + + /// @notice A value that indicates that value was not found. + uint256 public constant INDEX_NOT_FOUND = type(uint256).max; + + /// @notice An interface that each observer should support. + bytes4 public constant REQUIRED_INTERFACE = type(ITokenRatePusher).interfaceId; + + /// @notice All observers. + address[] public observers; + + /// @notice Add a `observer_` to the back of array + /// @param observer_ observer address + function addObserver(address observer_) external onlyOwner { + if (observer_ == address(0)) { + revert ErrorZeroAddressObserver(); + } + if (!observer_.supportsInterface(REQUIRED_INTERFACE)) { + revert ErrorBadObserverInterface(); + } + if (observers.length >= MAX_OBSERVERS_COUNT) { + revert ErrorMaxObserversCountExceeded(); + } + + observers.push(observer_); + emit ObserverAdded(observer_); + } + + /// @notice Remove a observer at the given `observer_` position + /// @param observer_ observer remove position + function removeObserver(address observer_) external onlyOwner { + + uint256 observerIndexToRemove = _observerIndex(observer_); + + if (observerIndexToRemove == INDEX_NOT_FOUND) { + revert ErrorNoObserverToRemove(); + } + + for (uint256 obIndex = observerIndexToRemove; obIndex < observers.length - 1; obIndex++) { + observers[obIndex] = observers[obIndex + 1]; + } + + observers.pop(); + + emit ObserverRemoved(observer_); } /// @inheritdoc IPostTokenRebaseReceiver @@ -24,18 +74,18 @@ contract TokenRateNotifier is ObserversArray, IPostTokenRebaseReceiver { uint256, uint256 ) external { - uint256 observersLength = observersLength(); + uint256 obLength = observersLength(); - for (uint256 obIndex = 0; obIndex < observersLength; obIndex++) { + for (uint256 obIndex = 0; obIndex < obLength; obIndex++) { try ITokenRatePusher(observers[obIndex]).pushTokenRate() {} catch (bytes memory lowLevelRevertData) { /// @dev This check is required to prevent incorrect gas estimation of the method. /// Without it, Ethereum nodes that use binary search for gas estimation may - /// return an invalid value when the handleTokenRebased() reverts because of the - /// "out of gas" error. Here we assume that the handleTokenRebased() method doesn't + /// return an invalid value when the pushTokenRate() reverts because of the + /// "out of gas" error. Here we assume that the pushTokenRate() method doesn't /// have reverts with empty error data except "out of gas". - if (lowLevelRevertData.length == 0) revert ErrorUnrecoverableObserver(); - emit HandleTokenRebasedFailed( + if (lowLevelRevertData.length == 0) revert ErrorTokenRateNotifierRevertedWithNoData(); + emit PushTokenRateFailed( observers[obIndex], lowLevelRevertData ); @@ -43,7 +93,32 @@ contract TokenRateNotifier is ObserversArray, IPostTokenRebaseReceiver { } } - event HandleTokenRebasedFailed(address indexed observer, bytes lowLevelRevertData); + /// @notice Observer length + /// @return Added observers count + function observersLength() public view returns (uint256) { + return observers.length; + } + + /// @notice `observer_` index in `observers` array. + /// @return An index of `observer_` or `INDEX_NOT_FOUND` if it wasn't found. + function _observerIndex(address observer_) internal view returns (uint256) { + uint256 obLength = observersLength(); + + for (uint256 obIndex = 0; obIndex < obLength; obIndex++) { + if (observers[obIndex] == observer_) { + return obIndex; + } + } + return INDEX_NOT_FOUND; + } + + event PushTokenRateFailed(address indexed observer, bytes lowLevelRevertData); + event ObserverAdded(address indexed observer); + event ObserverRemoved(address indexed observer); - error ErrorUnrecoverableObserver(); + error ErrorTokenRateNotifierRevertedWithNoData(); + error ErrorZeroAddressObserver(); + error ErrorBadObserverInterface(); + error ErrorMaxObserversCountExceeded(); + error ErrorNoObserverToRemove(); } diff --git a/contracts/lido/interfaces/IObserversArray.sol b/contracts/lido/interfaces/IObserversArray.sol deleted file mode 100644 index f3a867b7..00000000 --- a/contracts/lido/interfaces/IObserversArray.sol +++ /dev/null @@ -1,34 +0,0 @@ -// SPDX-FileCopyrightText: 2024 Lido -// SPDX-License-Identifier: GPL-3.0 - -pragma solidity 0.8.10; - -/// @author kovalgek -/// @notice An interface for observer pattern -interface IObserversArray { - - /// @notice Observer added event - /// @dev emitted by `addObserver` function - event ObserverAdded(address indexed observer); - - /// @notice Observer removed event - /// @dev emitted by `removeObserver` function - event ObserverRemoved(address indexed observer); - - /// @notice Observer length - /// @return Added observers count - function observersLength() external view returns (uint256); - - /// @notice Add a `observer_` to the back of array - /// @param observer_ observer address - function addObserver(address observer_) external; - - /// @notice Remove a observer at the given `observer_` position - /// @param observer_ observer remove position - function removeObserver(address observer_) external; - - /// @notice Get observer at position - /// @return Observer at the given `atIndex_` - /// @dev function reverts if `atIndex_` is out of range - function observers(uint256 atIndex_) external view returns (address); -} diff --git a/contracts/optimism/OpStackTokenRatePusher.sol b/contracts/optimism/OpStackTokenRatePusher.sol index 336252fb..c0c3e747 100644 --- a/contracts/optimism/OpStackTokenRatePusher.sol +++ b/contracts/optimism/OpStackTokenRatePusher.sol @@ -16,7 +16,7 @@ contract OpStackTokenRatePusher is CrossDomainEnabled, ITokenRatePusher { address public immutable L2_TOKEN_RATE_ORACLE; /// @notice Non-rebasable token of Core Lido procotol. - address public immutable WST_ETH; + address public immutable WSTETH; /// @notice Gas limit required to complete pushing token rate on L2. uint32 public immutable L2_GAS_LIMIT_FOR_PUSHING_TOKEN_RATE; @@ -31,14 +31,14 @@ contract OpStackTokenRatePusher is CrossDomainEnabled, ITokenRatePusher { address tokenRateOracle_, uint32 l2GasLimitForPushingTokenRate_ ) CrossDomainEnabled(messenger_) { - WST_ETH = wstEth_; + WSTETH = wstEth_; L2_TOKEN_RATE_ORACLE = tokenRateOracle_; L2_GAS_LIMIT_FOR_PUSHING_TOKEN_RATE = l2GasLimitForPushingTokenRate_; } /// @inheritdoc ITokenRatePusher function pushTokenRate() external { - uint256 tokenRate = IERC20WstETH(WST_ETH).stEthPerToken(); + uint256 tokenRate = IERC20WstETH(WSTETH).stEthPerToken(); bytes memory message = abi.encodeWithSelector( ITokenRateOracle.updateRate.selector, diff --git a/contracts/optimism/TokenRateOracle.sol b/contracts/optimism/TokenRateOracle.sol index 8c908da8..a191e216 100644 --- a/contracts/optimism/TokenRateOracle.sol +++ b/contracts/optimism/TokenRateOracle.sol @@ -75,8 +75,10 @@ contract TokenRateOracle is CrossDomainEnabled, ITokenRateOracle { /// @inheritdoc ITokenRateOracle function updateRate(uint256 tokenRate_, uint256 rateL1Timestamp_) external { - if ((msg.sender != address(MESSENGER) || MESSENGER.xDomainMessageSender() != L1_TOKEN_RATE_PUSHER) && - msg.sender != BRIDGE) { + if (!( + (msg.sender == address(MESSENGER) && MESSENGER.xDomainMessageSender() == L1_TOKEN_RATE_PUSHER) + || (msg.sender == BRIDGE) + )) { revert ErrorNoRights(msg.sender); } diff --git a/test/token/ERC20Rebasable.unit.test.ts b/test/token/ERC20Rebasable.unit.test.ts index 2561256c..b8cb3e47 100644 --- a/test/token/ERC20Rebasable.unit.test.ts +++ b/test/token/ERC20Rebasable.unit.test.ts @@ -7,7 +7,8 @@ import { ERC20Bridged__factory, TokenRateOracle__factory, ERC20Rebasable__factory, - OssifiableProxy__factory + OssifiableProxy__factory, + CrossDomainMessengerStub__factory } from "../../typechain"; import { BigNumber } from "ethers"; @@ -32,7 +33,7 @@ unit("ERC20Rebasable", ctxFactory) ) .test("initialize() :: name already set", async (ctx) => { - const { deployer, owner } = ctx.accounts; + const { deployer, owner, zero } = ctx.accounts; const { decimalsToSet } = ctx.constants; // deploy new implementation @@ -42,8 +43,11 @@ unit("ERC20Rebasable", ctxFactory) decimalsToSet, owner.address ); + const tokenRateOracle = await new TokenRateOracle__factory(deployer).deploy( + zero.address, owner.address, + zero.address, 86400 ); const rebasableTokenImpl = await new ERC20Rebasable__factory(deployer).deploy( @@ -61,7 +65,7 @@ unit("ERC20Rebasable", ctxFactory) }) .test("initialize() :: symbol already set", async (ctx) => { - const { deployer, owner } = ctx.accounts; + const { deployer, owner, zero } = ctx.accounts; const { decimalsToSet } = ctx.constants; // deploy new implementation @@ -72,7 +76,9 @@ unit("ERC20Rebasable", ctxFactory) owner.address ); const tokenRateOracle = await new TokenRateOracle__factory(deployer).deploy( + zero.address, owner.address, + zero.address, 86400 ); const rebasableTokenImpl = await new ERC20Rebasable__factory(deployer).deploy( @@ -106,7 +112,7 @@ unit("ERC20Rebasable", ctxFactory) .test("wrap() :: wrong oracle update time", async (ctx) => { - const { deployer, user1, owner } = ctx.accounts; + const { deployer, user1, owner, zero } = ctx.accounts; const { decimalsToSet } = ctx.constants; // deploy new implementation to test initial oracle state @@ -117,7 +123,9 @@ unit("ERC20Rebasable", ctxFactory) owner.address ); const tokenRateOracle = await new TokenRateOracle__factory(deployer).deploy( + zero.address, owner.address, + zero.address, 86400 ); const rebasableProxied = await new ERC20Rebasable__factory(deployer).deploy( @@ -284,7 +292,7 @@ unit("ERC20Rebasable", ctxFactory) .test("unwrap() :: with wrong oracle update time", async (ctx) => { - const { deployer, user1, owner } = ctx.accounts; + const { deployer, user1, owner, zero } = ctx.accounts; const { decimalsToSet } = ctx.constants; // deploy new implementation to test initial oracle state @@ -295,7 +303,9 @@ unit("ERC20Rebasable", ctxFactory) owner.address ); const tokenRateOracle = await new TokenRateOracle__factory(deployer).deploy( + zero.address, owner.address, + zero.address, 86400 ); const rebasableProxied = await new ERC20Rebasable__factory(deployer).deploy( @@ -1002,6 +1012,7 @@ async function ctxFactory() { user1, user2 ] = await hre.ethers.getSigners(); + const zero = await hre.ethers.getSigner(hre.ethers.constants.AddressZero); const wrappedToken = await new ERC20Bridged__factory(deployer).deploy( "WsETH Test Token", @@ -1010,7 +1021,9 @@ async function ctxFactory() { owner.address ); const tokenRateOracle = await new TokenRateOracle__factory(deployer).deploy( + zero.address, owner.address, + zero.address, 86400 ); const rebasableTokenImpl = await new ERC20Rebasable__factory(deployer).deploy( @@ -1027,8 +1040,6 @@ async function ctxFactory() { params: [hre.ethers.constants.AddressZero], }); - const zero = await hre.ethers.getSigner(hre.ethers.constants.AddressZero); - const l2TokensProxy = await new OssifiableProxy__factory(deployer).deploy( rebasableTokenImpl.address, deployer.address, From b167ebd8276bac4076a64dec4334ef6aa25a9abb Mon Sep 17 00:00:00 2001 From: Anton Kovalchuk Date: Sat, 23 Mar 2024 00:35:44 +0100 Subject: [PATCH 039/148] fix token rate publicher and notifier unit tests --- ...kTokenRatePusherWithOutOfGasErrorStub.sol} | 12 +- ...StackTokenRatePusherWithSomeErrorStub.sol} | 8 +- contracts/optimism/OpStackTokenRatePusher.sol | 11 +- .../optimism/stubs/TokenRatePusherStub.sol | 15 -- test/optimism/L2ERC20TokenBridge.unit.test.ts | 2 + .../OpStackTokenRatePusher.unit.test.ts | 102 +++++++++ test/optimism/TokenRateNotifier.unit.test.ts | 203 ++++++++++++------ 7 files changed, 257 insertions(+), 96 deletions(-) rename contracts/lido/stubs/{TokenRateObserverWithOutOfGasErrorStub.sol => OpStackTokenRatePusherWithOutOfGasErrorStub.sol} (63%) rename contracts/lido/stubs/{TokenRateObserverWithSomeErrorStub.sol => OpStackTokenRatePusherWithSomeErrorStub.sol} (64%) delete mode 100644 contracts/optimism/stubs/TokenRatePusherStub.sol create mode 100644 test/optimism/OpStackTokenRatePusher.unit.test.ts diff --git a/contracts/lido/stubs/TokenRateObserverWithOutOfGasErrorStub.sol b/contracts/lido/stubs/OpStackTokenRatePusherWithOutOfGasErrorStub.sol similarity index 63% rename from contracts/lido/stubs/TokenRateObserverWithOutOfGasErrorStub.sol rename to contracts/lido/stubs/OpStackTokenRatePusherWithOutOfGasErrorStub.sol index 09cf84a4..818b4229 100644 --- a/contracts/lido/stubs/TokenRateObserverWithOutOfGasErrorStub.sol +++ b/contracts/lido/stubs/OpStackTokenRatePusherWithOutOfGasErrorStub.sol @@ -3,27 +3,23 @@ pragma solidity 0.8.10; -import {ITokenRateObserver} from "../interfaces/ITokenRateObserver.sol"; +import {ITokenRatePusher} from "../interfaces/ITokenRatePusher.sol"; import {ERC165} from "@openzeppelin/contracts/utils/introspection/ERC165.sol"; -contract TokenRateObserverWithOutOfGasErrorStub is ERC165, ITokenRateObserver { - - error SomeError(); +contract OpStackTokenRatePusherWithOutOfGasErrorStub is ERC165, ITokenRatePusher { mapping (uint256 => uint256) data; - function handleTokenRebased() external { + function pushTokenRate() external { for (uint256 i = 0; i < 1000000000000; ++i) { data[i] = i; } - - //revert SomeError(); } /// @inheritdoc ERC165 function supportsInterface(bytes4 _interfaceId) public view virtual override returns (bool) { return ( - _interfaceId == type(ITokenRateObserver).interfaceId + _interfaceId == type(ITokenRatePusher).interfaceId || super.supportsInterface(_interfaceId) ); } diff --git a/contracts/lido/stubs/TokenRateObserverWithSomeErrorStub.sol b/contracts/lido/stubs/OpStackTokenRatePusherWithSomeErrorStub.sol similarity index 64% rename from contracts/lido/stubs/TokenRateObserverWithSomeErrorStub.sol rename to contracts/lido/stubs/OpStackTokenRatePusherWithSomeErrorStub.sol index ff0bd615..5a8ddfdd 100644 --- a/contracts/lido/stubs/TokenRateObserverWithSomeErrorStub.sol +++ b/contracts/lido/stubs/OpStackTokenRatePusherWithSomeErrorStub.sol @@ -3,21 +3,21 @@ pragma solidity 0.8.10; -import {ITokenRateObserver} from "../interfaces/ITokenRateObserver.sol"; +import {ITokenRatePusher} from "../interfaces/ITokenRatePusher.sol"; import {ERC165} from "@openzeppelin/contracts/utils/introspection/ERC165.sol"; -contract TokenRateObserverWithSomeErrorStub is ERC165, ITokenRateObserver { +contract OpStackTokenRatePusherWithSomeErrorStub is ERC165, ITokenRatePusher { error SomeError(); - function handleTokenRebased() external { + function pushTokenRate() external { revert SomeError(); } /// @inheritdoc ERC165 function supportsInterface(bytes4 _interfaceId) public view virtual override returns (bool) { return ( - _interfaceId == type(ITokenRateObserver).interfaceId + _interfaceId == type(ITokenRatePusher).interfaceId || super.supportsInterface(_interfaceId) ); } diff --git a/contracts/optimism/OpStackTokenRatePusher.sol b/contracts/optimism/OpStackTokenRatePusher.sol index c0c3e747..65b06a34 100644 --- a/contracts/optimism/OpStackTokenRatePusher.sol +++ b/contracts/optimism/OpStackTokenRatePusher.sol @@ -7,10 +7,11 @@ import {CrossDomainEnabled} from "./CrossDomainEnabled.sol"; import {ITokenRatePusher} from "../lido/interfaces/ITokenRatePusher.sol"; import {IERC20WstETH} from "../token/interfaces/IERC20WstETH.sol"; import {ITokenRateOracle} from "../token/interfaces/ITokenRateOracle.sol"; +import {ERC165} from "@openzeppelin/contracts/utils/introspection/ERC165.sol"; /// @author kovalgek /// @notice Pushes token rate to L2 Oracle. -contract OpStackTokenRatePusher is CrossDomainEnabled, ITokenRatePusher { +contract OpStackTokenRatePusher is CrossDomainEnabled, ERC165, ITokenRatePusher { /// @notice Oracle address on L2 for receiving token rate. address public immutable L2_TOKEN_RATE_ORACLE; @@ -48,4 +49,12 @@ contract OpStackTokenRatePusher is CrossDomainEnabled, ITokenRatePusher { sendCrossDomainMessage(L2_TOKEN_RATE_ORACLE, L2_GAS_LIMIT_FOR_PUSHING_TOKEN_RATE, message); } + + /// @inheritdoc ERC165 + function supportsInterface(bytes4 _interfaceId) public view virtual override returns (bool) { + return ( + _interfaceId == type(ITokenRatePusher).interfaceId + || super.supportsInterface(_interfaceId) + ); + } } diff --git a/contracts/optimism/stubs/TokenRatePusherStub.sol b/contracts/optimism/stubs/TokenRatePusherStub.sol deleted file mode 100644 index 8748e26d..00000000 --- a/contracts/optimism/stubs/TokenRatePusherStub.sol +++ /dev/null @@ -1,15 +0,0 @@ -// SPDX-FileCopyrightText: 2024 Lido -// SPDX-License-Identifier: GPL-3.0 - -pragma solidity 0.8.10; - -import {ITokenRatePusher} from "../interfaces/ITokenRatePusher.sol"; - -contract TokenRatePusherStub is ITokenRatePusher { - - uint32 public l2Gas; - - function pushTokenRate(uint32 l2Gas_) external { - l2Gas = l2Gas_; - } -} diff --git a/test/optimism/L2ERC20TokenBridge.unit.test.ts b/test/optimism/L2ERC20TokenBridge.unit.test.ts index a3906b0b..fc0a54db 100644 --- a/test/optimism/L2ERC20TokenBridge.unit.test.ts +++ b/test/optimism/L2ERC20TokenBridge.unit.test.ts @@ -712,7 +712,9 @@ async function ctxFactory() { ); const tokenRateOracle = await new TokenRateOracle__factory(deployer).deploy( + l2MessengerStub.address, l2TokenBridgeProxyAddress, + l1TokenBridgeEOA.address, 86400 ); diff --git a/test/optimism/OpStackTokenRatePusher.unit.test.ts b/test/optimism/OpStackTokenRatePusher.unit.test.ts new file mode 100644 index 00000000..039c54dd --- /dev/null +++ b/test/optimism/OpStackTokenRatePusher.unit.test.ts @@ -0,0 +1,102 @@ +import hre from "hardhat"; +import { assert } from "chai"; +import { utils } from 'ethers' +import { unit } from "../../utils/testing"; +import { wei } from "../../utils/wei"; + +import { + OpStackTokenRatePusher__factory, + CrossDomainMessengerStub__factory, + ERC20BridgedStub__factory, + ERC20WrapperStub__factory, + ITokenRateOracle__factory, + ITokenRatePusher__factory +} from "../../typechain"; + +unit("OpStackTokenRatePusher", ctxFactory) + + .test("initial state", async (ctx) => { + const { tokenRateOracle } = ctx.accounts; + const { opStackTokenRatePusher, l1MessengerStub, l1TokenNonRebasableStub } = ctx.contracts; + + assert.equal(await opStackTokenRatePusher.MESSENGER(), l1MessengerStub.address); + assert.equal(await opStackTokenRatePusher.WSTETH(), l1TokenNonRebasableStub.address); + assert.equal(await opStackTokenRatePusher.L2_TOKEN_RATE_ORACLE(), tokenRateOracle.address); + assert.equalBN(await opStackTokenRatePusher.L2_GAS_LIMIT_FOR_PUSHING_TOKEN_RATE(), 123); + const iTokenRatePusher = getInterfaceID(ITokenRatePusher__factory.createInterface()); + assert.isTrue(await opStackTokenRatePusher.supportsInterface(iTokenRatePusher._hex)); + }) + + .test("pushTokenRate() :: success", async (ctx) => { + const { tokenRateOracle } = ctx.accounts; + const { l2GasLimitForPushingTokenRate } = ctx.constants; + const { opStackTokenRatePusher, l1MessengerStub, l1TokenNonRebasableStub } = ctx.contracts; + + let tokenRate = await l1TokenNonRebasableStub.stEthPerToken(); + + let tx = await opStackTokenRatePusher.pushTokenRate(); + + const provider = await hre.ethers.provider; + const blockNumber = await provider.getBlockNumber(); + const blockTimestamp = (await provider.getBlock(blockNumber)).timestamp; + + await assert.emits(l1MessengerStub , tx, "SentMessage", [ + tokenRateOracle.address, + opStackTokenRatePusher.address, + ITokenRateOracle__factory.createInterface().encodeFunctionData( + "updateRate", + [ + tokenRate, + blockTimestamp + ] + ), + 1, + l2GasLimitForPushingTokenRate, + ]); + }) + + .run(); + +async function ctxFactory() { + const [deployer, bridge, stranger, tokenRateOracle, l1TokenBridgeEOA] = await hre.ethers.getSigners(); + + const l1TokenRebasableStub = await new ERC20BridgedStub__factory(deployer).deploy( + "L1 Token Rebasable", + "L1R" + ); + + const l1TokenNonRebasableStub = await new ERC20WrapperStub__factory(deployer).deploy( + l1TokenRebasableStub.address, + "L1 Token Non Rebasable", + "L1NR" + ); + + const l1MessengerStub = await new CrossDomainMessengerStub__factory( + deployer + ).deploy({ value: wei.toBigNumber(wei`1 ether`) }); + await l1MessengerStub.setXDomainMessageSender(l1TokenBridgeEOA.address); + + const l2GasLimitForPushingTokenRate = 123; + + const opStackTokenRatePusher = await new OpStackTokenRatePusher__factory(deployer).deploy( + l1MessengerStub.address, + l1TokenNonRebasableStub.address, + tokenRateOracle.address, + l2GasLimitForPushingTokenRate + ); + + return { + accounts: { deployer, bridge, stranger, tokenRateOracle }, + contracts: { opStackTokenRatePusher, l1MessengerStub, l1TokenNonRebasableStub }, + constants: { l2GasLimitForPushingTokenRate } + }; +} + +export function getInterfaceID(contractInterface: utils.Interface) { + let interfaceID = hre.ethers.constants.Zero; + const functions: string[] = Object.keys(contractInterface.functions); + for (let i = 0; i < functions.length; i++) { + interfaceID = interfaceID.xor(contractInterface.getSighash(functions[i])); + } + return interfaceID; +} diff --git a/test/optimism/TokenRateNotifier.unit.test.ts b/test/optimism/TokenRateNotifier.unit.test.ts index 73a1af4a..9366d2bf 100644 --- a/test/optimism/TokenRateNotifier.unit.test.ts +++ b/test/optimism/TokenRateNotifier.unit.test.ts @@ -1,153 +1,220 @@ import hre from "hardhat"; import { assert } from "chai"; +import { utils } from 'ethers' import { unit } from "../../utils/testing"; -import { BigNumber, utils } from 'ethers' - +import { wei } from "../../utils/wei"; import { TokenRateNotifier__factory, - ObserversArray__factory, - OpStackTokenRateObserver__factory, - ITokenRateObserver__factory, - TokenRateObserverWithSomeErrorStub__factory, - TokenRateObserverWithOutOfGasErrorStub__factory, - TokenRatePusherStub__factory + ITokenRatePusher__factory, + OpStackTokenRatePusher__factory, + ITokenRateOracle__factory, + ERC20BridgedStub__factory, + ERC20WrapperStub__factory, + CrossDomainMessengerStub__factory, + OpStackTokenRatePusherWithSomeErrorStub__factory, + OpStackTokenRatePusherWithOutOfGasErrorStub__factory } from "../../typechain"; unit("TokenRateNotifier", ctxFactory) - .test("init with wrong interface", async (ctx) => { - const { deployer } = ctx.accounts; - await assert.revertsWith(new ObserversArray__factory(deployer).deploy(BigNumber.from("0xffffffff")._hex), "ErrorInvalidInterface()"); - }) - .test("initial state", async (ctx) => { const { tokenRateNotifier } = ctx.contracts; assert.equalBN(await tokenRateNotifier.MAX_OBSERVERS_COUNT(), 16); assert.equal(await tokenRateNotifier.INVALID_INTERFACE_ID(), "0xffffffff"); - const iTokenRateObserver = getInterfaceID(ITokenRateObserver__factory.createInterface()); + const iTokenRateObserver = getInterfaceID(ITokenRatePusher__factory.createInterface()); assert.equal(await tokenRateNotifier.REQUIRED_INTERFACE(), iTokenRateObserver._hex); assert.equalBN(await tokenRateNotifier.observersLength(), 0); }) - .test("add zero address observer", async (ctx) => { + .test("addObserver() :: not the owner", async (ctx) => { const { tokenRateNotifier } = ctx.contracts; - await assert.revertsWith(tokenRateNotifier.addObserver(hre.ethers.constants.AddressZero), "ErrorZeroAddressObserver()"); + const { stranger } = ctx.accounts; + + await assert.revertsWith( + tokenRateNotifier + .connect(stranger) + .addObserver(hre.ethers.constants.AddressZero), + "Ownable: caller is not the owner" + ); }) - .test("add bad interface observer", async (ctx) => { + .test("addObserver() :: zero address observer", async (ctx) => { const { tokenRateNotifier } = ctx.contracts; - const { deployer } = ctx.accounts; - const observer = await new TokenRateNotifier__factory(deployer).deploy(); - await assert.revertsWith(tokenRateNotifier.addObserver(observer.address), "ErrorBadObserverInterface()"); + await assert.revertsWith( + tokenRateNotifier.addObserver(hre.ethers.constants.AddressZero), + "ErrorZeroAddressObserver()" + ); }) - .test("add too many observers", async (ctx) => { + .test("addObserver() :: bad interface observer", async (ctx) => { const { tokenRateNotifier } = ctx.contracts; const { deployer } = ctx.accounts; - assert.equalBN(await tokenRateNotifier.observersLength(), 0); + const observer = await new TokenRateNotifier__factory(deployer).deploy(); + await assert.revertsWith( + tokenRateNotifier.addObserver(observer.address), + "ErrorBadObserverInterface()" + ); + }) + .test("addObserver() :: too many observers", async (ctx) => { + const { tokenRateNotifier, opStackTokenRatePusher } = ctx.contracts; + + assert.equalBN(await tokenRateNotifier.observersLength(), 0); const maxObservers = await tokenRateNotifier.MAX_OBSERVERS_COUNT(); for (let i = 0; i < maxObservers.toNumber(); i++) { - const observer = await new OpStackTokenRateObserver__factory(deployer).deploy(hre.ethers.constants.AddressZero, 10); - await tokenRateNotifier.addObserver(observer.address); + await tokenRateNotifier.addObserver(opStackTokenRatePusher.address); } - assert.equalBN(await tokenRateNotifier.observersLength(), maxObservers); - const observer = await new OpStackTokenRateObserver__factory(deployer).deploy(hre.ethers.constants.AddressZero, 10); - await assert.revertsWith(tokenRateNotifier.addObserver(observer.address), "ErrorMaxObserversCountExceeded()"); + await assert.revertsWith( + tokenRateNotifier.addObserver(opStackTokenRatePusher.address), + "ErrorMaxObserversCountExceeded()" + ); }) - .test("add observer", async (ctx) => { - const { tokenRateNotifier } = ctx.contracts; - const { deployer } = ctx.accounts; + .test("addObserver() :: success", async (ctx) => { + const { tokenRateNotifier, opStackTokenRatePusher } = ctx.contracts; assert.equalBN(await tokenRateNotifier.observersLength(), 0); - - const observer = await new OpStackTokenRateObserver__factory(deployer).deploy(hre.ethers.constants.AddressZero, 10); - const tx = await tokenRateNotifier.addObserver(observer.address); - + const tx = await tokenRateNotifier.addObserver(opStackTokenRatePusher.address); assert.equalBN(await tokenRateNotifier.observersLength(), 1); - await assert.emits(tokenRateNotifier, tx, "ObserverAdded", [observer.address]); + await assert.emits(tokenRateNotifier, tx, "ObserverAdded", [opStackTokenRatePusher.address]); }) - .test("remove non-added observer", async (ctx) => { + .test("removeObserver() :: not the owner", async (ctx) => { const { tokenRateNotifier } = ctx.contracts; - const { deployer } = ctx.accounts; + const { stranger } = ctx.accounts; + + await assert.revertsWith( + tokenRateNotifier + .connect(stranger) + .removeObserver(hre.ethers.constants.AddressZero), + "Ownable: caller is not the owner" + ); + }) + + .test("removeObserver() :: non-added observer", async (ctx) => { + const { tokenRateNotifier, opStackTokenRatePusher } = ctx.contracts; assert.equalBN(await tokenRateNotifier.observersLength(), 0); - const observer = await new OpStackTokenRateObserver__factory(deployer).deploy(hre.ethers.constants.AddressZero, 10); - await assert.revertsWith(tokenRateNotifier.removeObserver(observer.address), "ErrorNoObserverToRemove()"); + await assert.revertsWith( + tokenRateNotifier.removeObserver(opStackTokenRatePusher.address), + "ErrorNoObserverToRemove()" + ); }) - .test("remove observer", async (ctx) => { - const { tokenRateNotifier } = ctx.contracts; - const { deployer } = ctx.accounts; + .test("removeObserver() :: success", async (ctx) => { + const { tokenRateNotifier, opStackTokenRatePusher } = ctx.contracts; assert.equalBN(await tokenRateNotifier.observersLength(), 0); - const observer = await new OpStackTokenRateObserver__factory(deployer).deploy(hre.ethers.constants.AddressZero, 10); - await tokenRateNotifier.addObserver(observer.address); + await tokenRateNotifier.addObserver(opStackTokenRatePusher.address); assert.equalBN(await tokenRateNotifier.observersLength(), 1); - const tx = await tokenRateNotifier.removeObserver(observer.address); - await assert.emits(tokenRateNotifier, tx, "ObserverRemoved", [observer.address]); + const tx = await tokenRateNotifier.removeObserver(opStackTokenRatePusher.address); + await assert.emits(tokenRateNotifier, tx, "ObserverRemoved", [opStackTokenRatePusher.address]); assert.equalBN(await tokenRateNotifier.observersLength(), 0); }) - .test("notify observers with some error", async (ctx) => { + .test("handlePostTokenRebase() :: failed with some error", async (ctx) => { const { tokenRateNotifier } = ctx.contracts; const { deployer } = ctx.accounts; - const observer = await new TokenRateObserverWithSomeErrorStub__factory(deployer).deploy(); + const observer = await new OpStackTokenRatePusherWithSomeErrorStub__factory(deployer).deploy(); await tokenRateNotifier.addObserver(observer.address); const tx = await tokenRateNotifier.handlePostTokenRebase(1,2,3,4,5,6,7); - await assert.emits(tokenRateNotifier, tx, "HandleTokenRebasedFailed", [observer.address, "0x332e27d2"]); + await assert.emits(tokenRateNotifier, tx, "PushTokenRateFailed", [observer.address, "0x332e27d2"]); }) - .test("notify observers with out of gas error", async (ctx) => { + .test("handlePostTokenRebase() :: out of gas error", async (ctx) => { const { tokenRateNotifier } = ctx.contracts; const { deployer } = ctx.accounts; - const observer = await new TokenRateObserverWithOutOfGasErrorStub__factory(deployer).deploy(); + const observer = await new OpStackTokenRatePusherWithOutOfGasErrorStub__factory(deployer).deploy(); await tokenRateNotifier.addObserver(observer.address); - await assert.revertsWith(tokenRateNotifier.handlePostTokenRebase(1,2,3,4,5,6,7), "ErrorUnrecoverableObserver()"); + await assert.revertsWith( + tokenRateNotifier.handlePostTokenRebase(1,2,3,4,5,6,7), + "ErrorTokenRateNotifierRevertedWithNoData()" + ); }) - .test("notify observers", async (ctx) => { - const { tokenRateNotifier } = ctx.contracts; - const { deployer } = ctx.accounts; - - const tokenRatePusher = await new TokenRatePusherStub__factory(deployer).deploy(); - const observer = await new OpStackTokenRateObserver__factory(deployer).deploy(tokenRatePusher.address, 22); - await tokenRateNotifier.addObserver(observer.address); - - assert.equalBN(await tokenRatePusher.l2Gas(), 0); - - await tokenRateNotifier.handlePostTokenRebase(1,2,3,4,5,6,7); - - assert.equalBN(await tokenRatePusher.l2Gas(), 22); + .test("handlePostTokenRebase() :: success", async (ctx) => { + const { + tokenRateNotifier, + l1MessengerStub, + opStackTokenRatePusher, + l1TokenNonRebasableStub + } = ctx.contracts; + const { tokenRateOracle } = ctx.accounts; + const { l2GasLimitForPushingTokenRate } = ctx.constants; + + let tokenRate = await l1TokenNonRebasableStub.stEthPerToken(); + await tokenRateNotifier.addObserver(opStackTokenRatePusher.address); + let tx = await tokenRateNotifier.handlePostTokenRebase(1,2,3,4,5,6,7); + + const provider = await hre.ethers.provider; + const blockNumber = await provider.getBlockNumber(); + const blockTimestamp = (await provider.getBlock(blockNumber)).timestamp; + + await assert.emits(l1MessengerStub, tx, "SentMessage", [ + tokenRateOracle.address, + opStackTokenRatePusher.address, + ITokenRateOracle__factory.createInterface().encodeFunctionData( + "updateRate", + [ + tokenRate, + blockTimestamp + ] + ), + 1, + l2GasLimitForPushingTokenRate, + ]); }) .run(); async function ctxFactory() { - const [deployer, bridge, stranger] = await hre.ethers.getSigners(); + const [deployer, bridge, stranger, tokenRateOracle] = await hre.ethers.getSigners(); const tokenRateNotifier = await new TokenRateNotifier__factory(deployer).deploy(); + const l1TokenRebasableStub = await new ERC20BridgedStub__factory(deployer).deploy( + "L1 Token Rebasable", + "L1R" + ); + + const l1TokenNonRebasableStub = await new ERC20WrapperStub__factory(deployer).deploy( + l1TokenRebasableStub.address, + "L1 Token Non Rebasable", + "L1NR" + ); + + const l1MessengerStub = await new CrossDomainMessengerStub__factory( + deployer + ).deploy({ value: wei.toBigNumber(wei`1 ether`) }); + + const l2GasLimitForPushingTokenRate = 123; + + const opStackTokenRatePusher = await new OpStackTokenRatePusher__factory(deployer).deploy( + l1MessengerStub.address, + l1TokenNonRebasableStub.address, + tokenRateOracle.address, + l2GasLimitForPushingTokenRate + ); + return { - accounts: { deployer, bridge, stranger }, - contracts: { tokenRateNotifier } + accounts: { deployer, bridge, stranger, tokenRateOracle }, + contracts: { tokenRateNotifier, opStackTokenRatePusher, l1MessengerStub, l1TokenNonRebasableStub }, + constants: { l2GasLimitForPushingTokenRate } }; } From 55d2e3f85a4da7c768e873fb601ac3abf54b7671 Mon Sep 17 00:00:00 2001 From: Anton Kovalchuk Date: Sat, 23 Mar 2024 00:48:18 +0100 Subject: [PATCH 040/148] fix small comments from PR review --- contracts/lido/TokenRateNotifier.sol | 8 ++--- .../OpStackTokenRatePusher.unit.test.ts | 8 ++--- test/optimism/TokenRateNotifier.unit.test.ts | 32 +++++++++---------- 3 files changed, 22 insertions(+), 26 deletions(-) diff --git a/contracts/lido/TokenRateNotifier.sol b/contracts/lido/TokenRateNotifier.sol index 55004123..b9e9e17c 100644 --- a/contracts/lido/TokenRateNotifier.sol +++ b/contracts/lido/TokenRateNotifier.sol @@ -74,9 +74,7 @@ contract TokenRateNotifier is Ownable, IPostTokenRebaseReceiver { uint256, uint256 ) external { - uint256 obLength = observersLength(); - - for (uint256 obIndex = 0; obIndex < obLength; obIndex++) { + for (uint256 obIndex = 0; obIndex < observers.length; obIndex++) { try ITokenRatePusher(observers[obIndex]).pushTokenRate() {} catch (bytes memory lowLevelRevertData) { /// @dev This check is required to prevent incorrect gas estimation of the method. @@ -102,9 +100,7 @@ contract TokenRateNotifier is Ownable, IPostTokenRebaseReceiver { /// @notice `observer_` index in `observers` array. /// @return An index of `observer_` or `INDEX_NOT_FOUND` if it wasn't found. function _observerIndex(address observer_) internal view returns (uint256) { - uint256 obLength = observersLength(); - - for (uint256 obIndex = 0; obIndex < obLength; obIndex++) { + for (uint256 obIndex = 0; obIndex < observers.length; obIndex++) { if (observers[obIndex] == observer_) { return obIndex; } diff --git a/test/optimism/OpStackTokenRatePusher.unit.test.ts b/test/optimism/OpStackTokenRatePusher.unit.test.ts index 039c54dd..cf7a99f6 100644 --- a/test/optimism/OpStackTokenRatePusher.unit.test.ts +++ b/test/optimism/OpStackTokenRatePusher.unit.test.ts @@ -1,4 +1,4 @@ -import hre from "hardhat"; +import { ethers } from "hardhat"; import { assert } from "chai"; import { utils } from 'ethers' import { unit } from "../../utils/testing"; @@ -36,7 +36,7 @@ unit("OpStackTokenRatePusher", ctxFactory) let tx = await opStackTokenRatePusher.pushTokenRate(); - const provider = await hre.ethers.provider; + const provider = await ethers.provider; const blockNumber = await provider.getBlockNumber(); const blockTimestamp = (await provider.getBlock(blockNumber)).timestamp; @@ -58,7 +58,7 @@ unit("OpStackTokenRatePusher", ctxFactory) .run(); async function ctxFactory() { - const [deployer, bridge, stranger, tokenRateOracle, l1TokenBridgeEOA] = await hre.ethers.getSigners(); + const [deployer, bridge, stranger, tokenRateOracle, l1TokenBridgeEOA] = await ethers.getSigners(); const l1TokenRebasableStub = await new ERC20BridgedStub__factory(deployer).deploy( "L1 Token Rebasable", @@ -93,7 +93,7 @@ async function ctxFactory() { } export function getInterfaceID(contractInterface: utils.Interface) { - let interfaceID = hre.ethers.constants.Zero; + let interfaceID = ethers.constants.Zero; const functions: string[] = Object.keys(contractInterface.functions); for (let i = 0; i < functions.length; i++) { interfaceID = interfaceID.xor(contractInterface.getSighash(functions[i])); diff --git a/test/optimism/TokenRateNotifier.unit.test.ts b/test/optimism/TokenRateNotifier.unit.test.ts index 9366d2bf..14997721 100644 --- a/test/optimism/TokenRateNotifier.unit.test.ts +++ b/test/optimism/TokenRateNotifier.unit.test.ts @@ -1,4 +1,4 @@ -import hre from "hardhat"; +import { ethers } from "hardhat"; import { assert } from "chai"; import { utils } from 'ethers' import { unit } from "../../utils/testing"; @@ -34,21 +34,21 @@ unit("TokenRateNotifier", ctxFactory) await assert.revertsWith( tokenRateNotifier .connect(stranger) - .addObserver(hre.ethers.constants.AddressZero), + .addObserver(ethers.constants.AddressZero), "Ownable: caller is not the owner" ); }) - .test("addObserver() :: zero address observer", async (ctx) => { + .test("addObserver() :: revert on adding zero address observer", async (ctx) => { const { tokenRateNotifier } = ctx.contracts; await assert.revertsWith( - tokenRateNotifier.addObserver(hre.ethers.constants.AddressZero), + tokenRateNotifier.addObserver(ethers.constants.AddressZero), "ErrorZeroAddressObserver()" ); }) - .test("addObserver() :: bad interface observer", async (ctx) => { + .test("addObserver() :: revert on adding observer with bad interface", async (ctx) => { const { tokenRateNotifier } = ctx.contracts; const { deployer } = ctx.accounts; @@ -59,7 +59,7 @@ unit("TokenRateNotifier", ctxFactory) ); }) - .test("addObserver() :: too many observers", async (ctx) => { + .test("addObserver() :: revert on adding too many observers", async (ctx) => { const { tokenRateNotifier, opStackTokenRatePusher } = ctx.contracts; assert.equalBN(await tokenRateNotifier.observersLength(), 0); @@ -75,7 +75,7 @@ unit("TokenRateNotifier", ctxFactory) ); }) - .test("addObserver() :: success", async (ctx) => { + .test("addObserver() :: happy path of adding observer", async (ctx) => { const { tokenRateNotifier, opStackTokenRatePusher } = ctx.contracts; assert.equalBN(await tokenRateNotifier.observersLength(), 0); @@ -85,19 +85,19 @@ unit("TokenRateNotifier", ctxFactory) await assert.emits(tokenRateNotifier, tx, "ObserverAdded", [opStackTokenRatePusher.address]); }) - .test("removeObserver() :: not the owner", async (ctx) => { + .test("removeObserver() :: revert on calling by not the owner", async (ctx) => { const { tokenRateNotifier } = ctx.contracts; const { stranger } = ctx.accounts; await assert.revertsWith( tokenRateNotifier .connect(stranger) - .removeObserver(hre.ethers.constants.AddressZero), + .removeObserver(ethers.constants.AddressZero), "Ownable: caller is not the owner" ); }) - .test("removeObserver() :: non-added observer", async (ctx) => { + .test("removeObserver() :: revert on removing non-added observer", async (ctx) => { const { tokenRateNotifier, opStackTokenRatePusher } = ctx.contracts; assert.equalBN(await tokenRateNotifier.observersLength(), 0); @@ -108,7 +108,7 @@ unit("TokenRateNotifier", ctxFactory) ); }) - .test("removeObserver() :: success", async (ctx) => { + .test("removeObserver() :: happy path of removing observer", async (ctx) => { const { tokenRateNotifier, opStackTokenRatePusher } = ctx.contracts; assert.equalBN(await tokenRateNotifier.observersLength(), 0); @@ -135,7 +135,7 @@ unit("TokenRateNotifier", ctxFactory) await assert.emits(tokenRateNotifier, tx, "PushTokenRateFailed", [observer.address, "0x332e27d2"]); }) - .test("handlePostTokenRebase() :: out of gas error", async (ctx) => { + .test("handlePostTokenRebase() :: revert when observer has out of gas error", async (ctx) => { const { tokenRateNotifier } = ctx.contracts; const { deployer } = ctx.accounts; @@ -148,7 +148,7 @@ unit("TokenRateNotifier", ctxFactory) ); }) - .test("handlePostTokenRebase() :: success", async (ctx) => { + .test("handlePostTokenRebase() :: happy path of handling token rebase", async (ctx) => { const { tokenRateNotifier, l1MessengerStub, @@ -162,7 +162,7 @@ unit("TokenRateNotifier", ctxFactory) await tokenRateNotifier.addObserver(opStackTokenRatePusher.address); let tx = await tokenRateNotifier.handlePostTokenRebase(1,2,3,4,5,6,7); - const provider = await hre.ethers.provider; + const provider = await ethers.provider; const blockNumber = await provider.getBlockNumber(); const blockTimestamp = (await provider.getBlock(blockNumber)).timestamp; @@ -184,7 +184,7 @@ unit("TokenRateNotifier", ctxFactory) .run(); async function ctxFactory() { - const [deployer, bridge, stranger, tokenRateOracle] = await hre.ethers.getSigners(); + const [deployer, bridge, stranger, tokenRateOracle] = await ethers.getSigners(); const tokenRateNotifier = await new TokenRateNotifier__factory(deployer).deploy(); const l1TokenRebasableStub = await new ERC20BridgedStub__factory(deployer).deploy( @@ -219,7 +219,7 @@ async function ctxFactory() { } export function getInterfaceID(contractInterface: utils.Interface) { - let interfaceID = hre.ethers.constants.Zero; + let interfaceID = ethers.constants.Zero; const functions: string[] = Object.keys(contractInterface.functions); for (let i = 0; i < functions.length; i++) { interfaceID = interfaceID.xor(contractInterface.getSighash(functions[i])); From 13712caea7a1cc2b0e17dc69a7a96fbcce30cfda Mon Sep 17 00:00:00 2001 From: Artyom Veremeenko Date: Wed, 27 Mar 2024 14:08:45 +0300 Subject: [PATCH 041/148] feat(steth): intermediate work on adding ERC-2612/EIP-1271 permit --- contracts/lib/SignatureChecker.sol | 77 +++++++ contracts/stubs/ERC1271PermitSignerMock.sol | 21 ++ contracts/token/ERC20RebasablePermit.sol | 159 +++++++++++++++ package.json | 2 + test/token/ERC20Permit.unit.test.ts | 214 ++++++++++++++++++++ utils/testing/permit-helpers.ts | 98 +++++++++ 6 files changed, 571 insertions(+) create mode 100644 contracts/lib/SignatureChecker.sol create mode 100644 contracts/stubs/ERC1271PermitSignerMock.sol create mode 100644 contracts/token/ERC20RebasablePermit.sol create mode 100644 test/token/ERC20Permit.unit.test.ts create mode 100644 utils/testing/permit-helpers.ts diff --git a/contracts/lib/SignatureChecker.sol b/contracts/lib/SignatureChecker.sol new file mode 100644 index 00000000..d42561c4 --- /dev/null +++ b/contracts/lib/SignatureChecker.sol @@ -0,0 +1,77 @@ +// SPDX-FileCopyrightText: 2024 OpenZeppelin, Lido +// SPDX-License-Identifier: GPL-3.0 +// Writen based on (utils/cryptography/SignatureChecker.sol from d398d68 + +pragma solidity 0.8.10; + +import {ECDSA} from "@openzeppelin/contracts-v4.9/utils/cryptography/ECDSA.sol"; +import {IERC1271} from "@openzeppelin/contracts-v4.9/interfaces/IERC1271.sol"; + + + +/** + * @dev Signature verification helper that can be used instead of `ECDSA.recover` to seamlessly support both ECDSA + * signatures from externally owned accounts (EOAs) as well as ERC-1271 signatures from smart contract wallets like + * Argent and Safe Wallet (previously Gnosis Safe). + */ +library SignatureChecker { + /** + * @dev Checks if a signature is valid for a given signer and data hash. If the signer is a smart contract, the + * signature is validated against that smart contract using ERC-1271, otherwise it's validated using `ECDSA.recover`. + * + * NOTE: Unlike ECDSA signatures, contract signatures are revocable, and the outcome of this function can thus + * change through time. It could return true at block N and false at block N+1 (or the opposite). + */ + function isValidSignatureNow(address signer, bytes32 hash, bytes memory signature) internal view returns (bool) { + if (signer.code.length == 0) { + // return true; + (address recovered, ECDSA.RecoverError err) = ECDSA.tryRecover(hash, signature); + return err == ECDSA.RecoverError.NoError && recovered == signer; + } else { + return isValidERC1271SignatureNow(signer, hash, signature); + } + } + + /** + * @dev Checks signature validity. + * + * If the signer address doesn't contain any code, assumes that the address is externally owned + * and the signature is a ECDSA signature generated using its private key. Otherwise, issues a + * static call to the signer address to check the signature validity using the ERC-1271 standard. + */ + function isValidSignatureNow( + address signer, + bytes32 msgHash, + uint8 v, + bytes32 r, + bytes32 s + ) internal view returns (bool) { + if (signer.code.length == 0) { + (address recovered, ECDSA.RecoverError err) = ECDSA.tryRecover(msgHash, v, r, s); + return err == ECDSA.RecoverError.NoError && recovered == signer; + } else { + bytes memory signature = abi.encodePacked(r, s, v); + return isValidERC1271SignatureNow(signer, msgHash, signature); + } + } + + /** + * @dev Checks if a signature is valid for a given signer and data hash. The signature is validated + * against the signer smart contract using ERC-1271. + * + * NOTE: Unlike ECDSA signatures, contract signatures are revocable, and the outcome of this function can thus + * change through time. It could return true at block N and false at block N+1 (or the opposite). + */ + function isValidERC1271SignatureNow( + address signer, + bytes32 hash, + bytes memory signature + ) internal view returns (bool) { + (bool success, bytes memory result) = signer.staticcall( + abi.encodeWithSelector(IERC1271.isValidSignature.selector, hash, signature) + ); + return (success && + result.length >= 32 && + abi.decode(result, (bytes32)) == bytes32(IERC1271.isValidSignature.selector)); + } +} diff --git a/contracts/stubs/ERC1271PermitSignerMock.sol b/contracts/stubs/ERC1271PermitSignerMock.sol new file mode 100644 index 00000000..c865691a --- /dev/null +++ b/contracts/stubs/ERC1271PermitSignerMock.sol @@ -0,0 +1,21 @@ +// SPDX-FileCopyrightText: 2024 Lido +// SPDX-License-Identifier: GPL-3.0 + +pragma solidity 0.8.10; + + +contract ERC1271PermitSignerMock { + bytes4 public constant ERC1271_MAGIC_VALUE = 0x1626ba7e; + + function sign(bytes32 hash) public view returns (bytes1 v, bytes32 r, bytes32 s) { + v = 0x42; + r = hash; + s = bytes32(bytes20(address(this))); + } + + function isValidSignature(bytes32 hash, bytes memory sig) external view returns (bytes4) { + (bytes1 v, bytes32 r, bytes32 s) = sign(hash); + bytes memory validSig = abi.encodePacked(r, s, v); + return keccak256(sig) == keccak256(validSig) ? ERC1271_MAGIC_VALUE : bytes4(0); + } +} diff --git a/contracts/token/ERC20RebasablePermit.sol b/contracts/token/ERC20RebasablePermit.sol new file mode 100644 index 00000000..5a7843e2 --- /dev/null +++ b/contracts/token/ERC20RebasablePermit.sol @@ -0,0 +1,159 @@ +// SPDX-FileCopyrightText: 2024 OpenZeppelin, Lido +// SPDX-License-Identifier: GPL-3.0 + +pragma solidity 0.8.10; + +// import {UnstructuredStorage} from "@aragon/os/contracts/common/UnstructuredStorage.sol"; + +// import {SignatureUtils} from "../common/lib/SignatureUtils.sol"; +// import {IEIP712ERC20Rebasable} from "../lib/IEIP712ERC20Rebasable.sol"; + +import {UnstructuredStorage} from "./UnstructuredStorage.sol"; +import {ERC20Rebasable} from "./ERC20Rebasable.sol"; +import {EIP712} from "@openzeppelin/contracts-v4.9/utils/cryptography/EIP712.sol"; +import {SignatureChecker} from "../lib/SignatureChecker.sol"; + + +/** + * @dev Interface of the ERC20 Permit extension allowing approvals to be made via signatures, as defined in + * https://eips.ethereum.org/EIPS/eip-2612[EIP-2612]. + * + * Adds the {permit} method, which can be used to change an account's ERC20 allowance (see {IERC20-allowance}) by + * presenting a message signed by the account. By not relying on {IERC20-approve}, the token holder account doesn't + * need to send a transaction, and thus is not required to hold Ether at all. + */ +interface IERC2612 { + /** + * @dev Sets `value` as the allowance of `spender` over ``owner``'s tokens, + * given ``owner``'s signed approval. + * Emits an {Approval} event. + * + * Requirements: + * + * - `spender` cannot be the zero address. + * - `deadline` must be a timestamp in the future. + * - `v`, `r` and `s` must be a valid `secp256k1` signature from `owner` + * over the EIP712-formatted function arguments. + * - the signature must use ``owner``'s current nonce (see {nonces}). + */ + function permit( + address owner, address spender, uint256 value, uint256 deadline, uint8 v, bytes32 r, bytes32 s + ) external; + + /** + * @dev Returns the current nonce for `owner`. This value must be + * included whenever a signature is generated for {permit}. + * + * Every successful call to {permit} increases ``owner``'s nonce by one. This + * prevents a signature from being used multiple times. + */ + function nonces(address owner) external view returns (uint256); + + /** + * @dev Returns the domain separator used in the encoding of the signature for {permit}, as defined by {EIP712}. + */ + // solhint-disable-next-line func-name-mixedcase + function DOMAIN_SEPARATOR() external view returns (bytes32); +} + + +contract ERC20RebasablePermit is IERC2612, ERC20Rebasable, EIP712 { + using UnstructuredStorage for bytes32; + + /** + * @dev Nonces for ERC-2612 (Permit) + */ + mapping(address => uint256) internal noncesByAddress; + + /** + * @dev Typehash constant for ERC-2612 (Permit) + * + * keccak256("Permit(address owner,address spender,uint256 value,uint256 nonce,uint256 deadline)") + */ + bytes32 internal constant PERMIT_TYPEHASH = + 0x6e71edae12b1b97f4d1f60370fef10105fa2faae0126114a169c64845d6126c9; + + // TODO: outline structured storage used because at least EIP712 uses it + // TODO: use custom errors + + /// @param name_ The name of the token + /// @param symbol_ The symbol of the token + /// @param decimals_ The decimals places of the token + /// @param wrappedToken_ address of the ERC20 token to wrap + /// @param tokenRateOracle_ address of oracle that returns tokens rate + /// @param bridge_ The bridge address which allowd to mint/burn tokens + constructor( + string memory name_, + string memory symbol_, + uint8 decimals_, + address wrappedToken_, + address tokenRateOracle_, + address bridge_ + ) + ERC20Rebasable(name_, symbol_, decimals_, wrappedToken_, tokenRateOracle_, bridge_) + EIP712("Liquid staked Ether 2.0", "1") + { + } + + /** + * @dev Sets `value` as the allowance of `spender` over ``owner``'s tokens, + * given ``owner``'s signed approval. + * Emits an {Approval} event. + * + * Requirements: + * + * - `spender` cannot be the zero address. + * - `deadline` must be a timestamp in the future. + * - `v`, `r` and `s` must be a valid `secp256k1` signature from `owner` + * over the EIP712-formatted function arguments. + * - the signature must use ``owner``'s current nonce (see {nonces}). + */ + function permit( + address _owner, address _spender, uint256 _value, uint256 _deadline, uint8 _v, bytes32 _r, bytes32 _s + ) external { + if (block.timestamp > _deadline) { + revert ErrorDeadlineExpired(); + } + + bytes32 structHash = keccak256( + abi.encode(PERMIT_TYPEHASH, _owner, _spender, _value, _useNonce(_owner), _deadline) + ); + + bytes32 hash = _hashTypedDataV4(structHash); + + if (!SignatureChecker.isValidSignatureNow(_owner, hash, _v, _r, _s)) { + revert ErrorInvalidSignature(); + } + _approve(_owner, _spender, _value); + } + + /** + * @dev Returns the current nonce for `owner`. This value must be + * included whenever a signature is generated for {permit}. + * + * Every successful call to {permit} increases ``owner``'s nonce by one. This + * prevents a signature from being used multiple times. + */ + function nonces(address owner) external view returns (uint256) { + return noncesByAddress[owner]; + } + + /** + * @dev Returns the domain separator used in the encoding of the signature for {permit}, as defined by {EIP712}. + */ + // solhint-disable-next-line func-name-mixedcase + function DOMAIN_SEPARATOR() external view returns (bytes32) { + return _domainSeparatorV4(); + } + + /** + * @dev "Consume a nonce": return the current value and increment. + */ + function _useNonce(address _owner) internal returns (uint256 current) { + current = noncesByAddress[_owner]; + noncesByAddress[_owner] = current + 1; + } + + error ErrorInvalidSignature(); + error ErrorDeadlineExpired(); +} diff --git a/package.json b/package.json index 820a38ee..456c3985 100644 --- a/package.json +++ b/package.json @@ -57,6 +57,7 @@ "eslint-plugin-prettier": "^3.4.1", "eslint-plugin-promise": "^5.2.0", "ethereum-waffle": "^3.4.4", + "ethereumjs-util": "^7.0.8", "ethers": "^5.6.2", "hardhat": "^2.12.2", "hardhat-gas-reporter": "^1.0.8", @@ -74,6 +75,7 @@ "@ethersproject/providers": "^5.6.8", "@lidofinance/evm-script-decoder": "^0.2.2", "@openzeppelin/contracts": "4.6.0", + "@openzeppelin/contracts-v4.9": "npm:@openzeppelin/contracts@4.9.6", "chalk": "4.1.2" } } diff --git a/test/token/ERC20Permit.unit.test.ts b/test/token/ERC20Permit.unit.test.ts new file mode 100644 index 00000000..4ec18250 --- /dev/null +++ b/test/token/ERC20Permit.unit.test.ts @@ -0,0 +1,214 @@ +import hre from "hardhat"; +import { assert } from "chai"; +import { unit } from "../../utils/testing"; +import { wei } from "../../utils/wei"; +import { makeDomainSeparator, signPermit } from "../../utils/testing/permit-helpers"; + +import { + ERC20Bridged__factory, + TokenRateOracle__factory, + OssifiableProxy__factory, + ERC20RebasablePermit__factory, + ERC1271PermitSignerMock__factory, +} from "../../typechain"; +import { BigNumber } from "ethers"; + + +const TOKEN_NAME = 'Liquid staked Ether 2.0' +const TOKEN_VERSION = '1' + +// derived from mnemonic: want believe mosquito cat design route voice cause gold benefit gospel bulk often attitude rural +const ACCOUNTS_AND_KEYS = [ + { + address: '0xF4C028683CAd61ff284d265bC0F77EDd67B4e65A', + privateKey: '0x5f7edf5892efb4a5cd75dedd496598f48e579b562a70eb1360474cc83a982987', + }, + { + address: '0x7F94c1F9e4BfFccc8Cd79195554E0d83a0a5c5f2', + privateKey: '0x3fe2f6bd9dbc7d507a6cb95ec36a36787706617e34385292b66c74cd39874605', + }, +] + +const getAccountsEOA = async () => { + return { + alice: ACCOUNTS_AND_KEYS[0], + bob: ACCOUNTS_AND_KEYS[1], + } +} + +const getAccountsEIP1271 = async () => { + const deployer = (await hre.ethers.getSigners())[0] + const alice = await new ERC1271PermitSignerMock__factory(deployer).deploy() + const bob = await new ERC1271PermitSignerMock__factory(deployer).deploy() + return { alice, bob } +} + +// const signPermit = async (owner, spender, value, nonce, domainSeparator, deadline, acct) => { +// const digest = calculatePermitDigest(owner, spender, value, nonce, domainSeparator, deadline) +// return await sign(digest, acct) +// } + + +unit("ERC20Permit", ctxFactory) + + .test("wrappedToken() :: has the same address is in constructor", async (ctx) => { + const { rebasableProxied, wrappedToken } = ctx.contracts; + assert.equal(await rebasableProxied.WRAPPED_TOKEN(), wrappedToken.address) + }) + + .test('eip712Domain() is correct', async (ctx) => { + const token = ctx.contracts.rebasableProxied + const [ fields, name, version, chainId, verifyingContract, salt, extensions ] = await token.eip712Domain() + + assert.equal(name, TOKEN_NAME) + assert.equal(version, TOKEN_VERSION) + assert.isDefined(hre.network.config.chainId) + assert.equal(chainId.toNumber(), hre.network.config.chainId as number) + assert.equal(verifyingContract, token.address) + + const domainSeparator = makeDomainSeparator(TOKEN_NAME, TOKEN_VERSION, chainId, token.address) + assert.equal(makeDomainSeparator(name, version, chainId, verifyingContract), domainSeparator) + }) + + .test('DOMAIN_SEPARATOR() is correct', async (ctx) => { + const token = ctx.contracts.rebasableProxied + const [ fields, name, version, chainId, verifyingContract, salt, extensions ] = await token.eip712Domain() + + assert.equal(name, TOKEN_NAME) + assert.equal(version, TOKEN_VERSION) + assert.isDefined(hre.network.config.chainId) + assert.equal(chainId.toNumber(), hre.network.config.chainId as number) + assert.equal(verifyingContract, token.address) + + const domainSeparator = makeDomainSeparator(TOKEN_NAME, TOKEN_VERSION, chainId, token.address) + assert.equal(await ctx.contracts.rebasableProxied.DOMAIN_SEPARATOR(), domainSeparator) + }) + + .test('grants allowance when a valid permit is given', async (ctx) => { + const token = ctx.contracts.rebasableProxied + + const { owner, spender, deadline } = ctx.permitParams + let { value } = ctx.permitParams + // create a signed permit to grant Bob permission to spend Alice's funds + // on behalf, and sign with Alice's key + let nonce = 0 + const charlie = ctx.accounts.user2 + const charlieSigner = hre.ethers.provider.getSigner(charlie.address) + // const bobSigner = hre.ethers.provider.getSigner(BOB.address) + + const domainSeparator = makeDomainSeparator(TOKEN_NAME, TOKEN_VERSION, hre.network.config.chainId as number, token.address) + let { v, r, s } = await signPermit(owner, spender.address, value, nonce, deadline, domainSeparator) + + // check that the allowance is initially zero + assert.equalBN(await token.allowance(owner.address, spender.address), BigNumber.from(0)) + // check that the next nonce expected is zero + assert.equalBN(await token.nonces(owner.address), BigNumber.from(0)) + // check domain separator + assert.equal(await token.DOMAIN_SEPARATOR(), domainSeparator) + + // a third-party, Charlie (not Alice) submits the permit + // TODO: handle unpredictable gas limit somehow better than setting it a random constant + const tx = await token.connect(charlieSigner) + .permit(owner.address, spender.address, value, deadline, v, r, s, { gasLimit: '0xffffff' }) + + // check that allowance is updated + assert.equalBN(await token.allowance(owner.address, spender.address), BigNumber.from(value)) + await assert.emits(token, tx, 'Approval', [ owner, spender, value ]) + assert.equalBN(await token.nonces(owner.address), BigNumber.from(1)) + + // increment nonce + nonce = 1 + value = 4e5 + ;({ v, r, s } = await signPermit(owner, spender.address, value, nonce, deadline, domainSeparator)) + + // submit the permit + const tx2 = await token.connect(charlieSigner).permit(owner.address, spender.address, value, deadline, v, r, s) + + // check that allowance is updated + assert.equalBN(await token.allowance(owner.address, spender.address), BigNumber.from(value)) + assert.emits(token, tx2, 'Approval', [ owner.address, spender, BigNumber.from(value) ] ) + assert.equalBN(await token.nonces(owner.address), BigNumber.from(2)) + }) + + .run(); + +async function ctxFactory() { + // const name = "StETH Test Token"; + const name = TOKEN_NAME; + const symbol = "StETH"; + const decimalsToSet = 18; + const decimals = BigNumber.from(10).pow(decimalsToSet); + const rate = BigNumber.from('12').pow(decimalsToSet - 1); + const premintShares = wei.toBigNumber(wei`100 ether`); + const premintTokens = BigNumber.from(rate).mul(premintShares).div(decimals); + + const [ + deployer, + owner, + recipient, + spender, + holder, + stranger, + user1, + user2, + ] = await hre.ethers.getSigners(); + + const wrappedToken = await new ERC20Bridged__factory(deployer).deploy( + "WsETH Test Token", + "WsETH", + decimalsToSet, + owner.address + ); + const tokenRateOracle = await new TokenRateOracle__factory(deployer).deploy( + owner.address, + 86400 + ); + const rebasableTokenImpl = await new ERC20RebasablePermit__factory(deployer).deploy( + name, + symbol, + decimalsToSet, + wrappedToken.address, + tokenRateOracle.address, + owner.address + ); + + await hre.network.provider.request({ + method: "hardhat_impersonateAccount", + params: [hre.ethers.constants.AddressZero], + }); + + const zero = await hre.ethers.getSigner(hre.ethers.constants.AddressZero); + + const l2TokensProxy = await new OssifiableProxy__factory(deployer).deploy( + rebasableTokenImpl.address, + deployer.address, + ERC20RebasablePermit__factory.createInterface().encodeFunctionData("initialize", [ + name, + symbol, + ]) + ); + + const rebasableProxied = ERC20RebasablePermit__factory.connect( + l2TokensProxy.address, + holder + ); + + await tokenRateOracle.connect(owner).updateRate(rate, 1000); + await rebasableProxied.connect(owner).mintShares(holder.address, premintShares); + // const { alice, bob } = await getAccountsEOA(); + const { alice, bob } = await getAccountsEIP1271(); + + const MAX_UINT256 = '0xffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff' + return { + accounts: { deployer, owner, recipient, spender, holder, stranger, zero, user1, user2 }, + constants: { name, symbol, decimalsToSet, decimals, premintShares, premintTokens, rate }, + contracts: { rebasableProxied, wrappedToken, tokenRateOracle }, + permitParams: { + owner: alice, + spender: bob, + value: 6e6, + nonce: 0, + deadline: MAX_UINT256, + } + }; +} diff --git a/utils/testing/permit-helpers.ts b/utils/testing/permit-helpers.ts new file mode 100644 index 00000000..d7151a30 --- /dev/null +++ b/utils/testing/permit-helpers.ts @@ -0,0 +1,98 @@ +import { BigNumberish, Signer } from "ethers"; +import { ExternallyOwnedAccount } from "@ethersproject/abstract-signer"; + +import { keccak256, toUtf8Bytes, defaultAbiCoder } from "ethers/lib/utils"; +import { ecsign as ecSignBuf } from "ethereumjs-util"; + +const PERMIT_TYPE_HASH = streccak( + 'Permit(address owner,address spender,uint256 value,uint256 nonce,uint256 deadline)' +) +console.log({ PERMIT_TYPE_HASH }) + +interface Eip1271Contract { + address: string; + sign( + hash: string + ): Promise<[string, string, string] & { v: string; r: string; s: string }>; +} + +async function signEOA(digest: string, account: ExternallyOwnedAccount) { + return ecSign(digest, account.privateKey) +} + + +async function signEIP1271(digest: string, eip1271Contract: Eip1271Contract) { + const sig = await eip1271Contract.sign(digest) + console.log({ sig }) + return { v: sig.v, r: sig.r, s: sig.s } +} + + +export function makeDomainSeparator(name: string, version: string, chainId: BigNumberish, verifyingContract: string) { + return keccak256( + defaultAbiCoder.encode( + ['bytes32', 'bytes32', 'bytes32', 'uint256', 'address'], + [ + streccak('EIP712Domain(string name,string version,uint256 chainId,address verifyingContract)'), + streccak(name), + streccak(version), + chainId, + verifyingContract, + ] + ) + ) +} + +export async function signPermit(owner: ExternallyOwnedAccount | Eip1271Contract, spender: string, value: number, nonce: number, deadline: string, domainSeparator: string) { + const digest = calculatePermitDigest(owner.address, spender, value, nonce, deadline, domainSeparator) + if (owner.hasOwnProperty('sign')) { + return await signEIP1271(digest, owner as Eip1271Contract); + } else { + return await signEOA(digest, owner as ExternallyOwnedAccount); + } +} + +function calculatePermitDigest(owner: string, spender: string, value: number, nonce: number, deadline: string, domainSeparator: string) { + return calculateEIP712Digest( + domainSeparator, + PERMIT_TYPE_HASH, + ['address', 'address', 'uint256', 'uint256', 'uint256'], + [owner, spender, value, nonce, deadline] + ) +} + +function calculateEIP712Digest(domainSeparator: string, typeHash: string, types: string[], parameters: unknown[]) { + return streccak( + '0x1901' + + strip0x(domainSeparator) + + strip0x(keccak256(defaultAbiCoder.encode(['bytes32', ...types], [typeHash, ...parameters]))) + ) +} + +function ecSign(digest: string, privateKey: string) { + const { v, r, s } = ecSignBuf(bufferFromHexString(digest), bufferFromHexString(privateKey)) + return { v, r: hexStringFromBuffer(r), s: hexStringFromBuffer(s) } +} + +function strip0x(s: string) { + return s.substr(0, 2) === '0x' ? s.substr(2) : s +} + + +function hex(n: number, byteLen = undefined) { + const s = n.toString(16) + return byteLen === undefined ? s : s.padStart(byteLen * 2, '0') +} + + +export function streccak(s: string) { + return keccak256(toUtf8Bytes(s)); +} + +function hexStringFromBuffer(buf: Buffer) { + return '0x' + buf.toString('hex') +} + +function bufferFromHexString(hex: string) { + return Buffer.from(strip0x(hex), 'hex') +} From 6a46704f8955446ddbf4e815c62870ca22bb5ead Mon Sep 17 00:00:00 2001 From: Anton Kovalchuk Date: Fri, 29 Mar 2024 10:47:53 +0100 Subject: [PATCH 042/148] update optimism sdk version to fix integration tests --- package-lock.json | 19 +++++++++---------- package.json | 2 +- 2 files changed, 10 insertions(+), 11 deletions(-) diff --git a/package-lock.json b/package-lock.json index cfddd6b4..bf98439e 100644 --- a/package-lock.json +++ b/package-lock.json @@ -10,7 +10,7 @@ "license": "ISC", "dependencies": { "@arbitrum/sdk": "3.1.6", - "@eth-optimism/sdk": "3.2.0", + "@eth-optimism/sdk": "3.2.3", "@ethersproject/providers": "^5.6.8", "@lidofinance/evm-script-decoder": "^0.2.2", "@openzeppelin/contracts": "4.6.0", @@ -531,10 +531,9 @@ } }, "node_modules/@eth-optimism/sdk": { - "version": "3.2.0", - "resolved": "https://registry.npmjs.org/@eth-optimism/sdk/-/sdk-3.2.0.tgz", - "integrity": "sha512-+ZEO/mDWz3WLzaPVHvgOAK4iN723HmI6sLLr2tmO1/RUoCHVfWMUDwuiikrA49cAsdsjMxCV9+0XNZ8btD2JUg==", - "hasInstallScript": true, + "version": "3.2.3", + "resolved": "https://registry.npmjs.org/@eth-optimism/sdk/-/sdk-3.2.3.tgz", + "integrity": "sha512-e3XQTbbU+HTzsEv/VIsJpZifK6YZVlzEtF6tj/Vz/VIEDCjZk5JPcnCQOMVcs9ICI4EJyyur+y/+RU7fPa6qtg==", "dependencies": { "@eth-optimism/contracts": "0.6.0", "@eth-optimism/contracts-bedrock": "0.17.1", @@ -542,7 +541,7 @@ "lodash": "^4.17.21", "merkletreejs": "^0.3.11", "rlp": "^2.2.7", - "semver": "^7.5.4" + "semver": "^7.6.0" }, "peerDependencies": { "ethers": "^5" @@ -25229,9 +25228,9 @@ } }, "@eth-optimism/sdk": { - "version": "3.2.0", - "resolved": "https://registry.npmjs.org/@eth-optimism/sdk/-/sdk-3.2.0.tgz", - "integrity": "sha512-+ZEO/mDWz3WLzaPVHvgOAK4iN723HmI6sLLr2tmO1/RUoCHVfWMUDwuiikrA49cAsdsjMxCV9+0XNZ8btD2JUg==", + "version": "3.2.3", + "resolved": "https://registry.npmjs.org/@eth-optimism/sdk/-/sdk-3.2.3.tgz", + "integrity": "sha512-e3XQTbbU+HTzsEv/VIsJpZifK6YZVlzEtF6tj/Vz/VIEDCjZk5JPcnCQOMVcs9ICI4EJyyur+y/+RU7fPa6qtg==", "requires": { "@eth-optimism/contracts": "0.6.0", "@eth-optimism/contracts-bedrock": "0.17.1", @@ -25239,7 +25238,7 @@ "lodash": "^4.17.21", "merkletreejs": "^0.3.11", "rlp": "^2.2.7", - "semver": "^7.5.4" + "semver": "^7.6.0" }, "dependencies": { "lru-cache": { diff --git a/package.json b/package.json index 820a38ee..359469f0 100644 --- a/package.json +++ b/package.json @@ -70,7 +70,7 @@ }, "dependencies": { "@arbitrum/sdk": "3.1.6", - "@eth-optimism/sdk": "3.2.0", + "@eth-optimism/sdk": "3.2.3", "@ethersproject/providers": "^5.6.8", "@lidofinance/evm-script-decoder": "^0.2.2", "@openzeppelin/contracts": "4.6.0", From b9d5e5667323c22815998825535fa4de591457ad Mon Sep 17 00:00:00 2001 From: Anton Kovalchuk Date: Fri, 29 Mar 2024 10:53:11 +0100 Subject: [PATCH 043/148] update addresses to run e2e tests --- utils/lido.ts | 6 +++--- utils/testing/e2e.ts | 20 ++++++++++---------- 2 files changed, 13 insertions(+), 13 deletions(-) diff --git a/utils/lido.ts b/utils/lido.ts index ac3de401..f31f0ef3 100644 --- a/utils/lido.ts +++ b/utils/lido.ts @@ -16,9 +16,9 @@ const ARAGON_MAINNET = { }; const ARAGON_SEPOLIA = { - agent: "0x4333218072D5d7008546737786663c38B4D561A4", - voting: "0xbc0B67b4553f4CF52a913DE9A6eD0057E2E758Db", - tokenManager: "0xDfe76d11b365f5e0023343A367f0b311701B3bc1", + agent: "0x32A0E5828B62AAb932362a4816ae03b860b65e83", + voting: "0x39A0EbdEE54cB319f4F42141daaBDb6ba25D341A", + tokenManager: "0xC73cd4B2A7c1CBC5BF046eB4A7019365558ABF66", }; const ARAGON_CONTRACTS_BY_NAME = { diff --git a/utils/testing/e2e.ts b/utils/testing/e2e.ts index 010596e7..b089a24a 100644 --- a/utils/testing/e2e.ts +++ b/utils/testing/e2e.ts @@ -6,18 +6,18 @@ const abiCoder = ethers.utils.defaultAbiCoder; export const E2E_TEST_CONTRACTS_OPTIMISM = { l1: { - l1Token: "0xaF8a2F0aE374b03376155BF745A3421Dac711C12", - l1LDOToken: "0xcAdf242b97BFdD1Cb4Fd282E5FcADF965883065f", - l1ERC20TokenBridge: "0x2DD0CD60b6048549ab576f06BC20EC53B457244E", - aragonVoting: "0x86f4C03aB9fCE83970Ce3FC7C23f25339f484EE5", - tokenManager: "0x4A63e41611B7c70DA6f42a806dFBcECB0B2D314F", - agent: "0x80720229bdB8caf9f67ddf871e98a76655A39AFe", - l1CrossDomainMessenger: "0x4361d0F75A0186C05f971c566dC6bEa5957483fD", + l1Token: "0xB82381A3fBD3FaFA77B3a7bE693342618240067b", + l1LDOToken: "0xd06dF83b8ad6D89C86a187fba4Eae918d497BdCB", + l1ERC20TokenBridge: "0x4Abf633d9c0F4aEebB4C2E3213c7aa1b8505D332", + aragonVoting: "0x39A0EbdEE54cB319f4F42141daaBDb6ba25D341A", + tokenManager: "0xC73cd4B2A7c1CBC5BF046eB4A7019365558ABF66", + agent: "0x32A0E5828B62AAb932362a4816ae03b860b65e83", + l1CrossDomainMessenger: "0x58Cc85b8D04EA49cC6DBd3CbFFd00B4B8D6cb3ef", }, l2: { - l2Token: "0x4c2ECf847C89d5De3187F1b0081E4dcdBe063C68", - l2ERC20TokenBridge: "0x0A5c6AB7B41E066b5C40907dd06063703be21B19", - govBridgeExecutor: "0x2365F00fFD70958EC2c20B601D501e4b75798D93", + l2Token: "0x24B47cd3A74f1799b32B2de11073764Cb1bb318B", + l2ERC20TokenBridge: "0xdBA2760246f315203F8B716b3a7590F0FFdc704a", + govBridgeExecutor: "0xf695357C66bA514150Da95b189acb37b46DDe602", }, }; From a3f4544e536adb93a9f63a517edaf0ba2c37748e Mon Sep 17 00:00:00 2001 From: Anton Kovalchuk Date: Fri, 29 Mar 2024 10:54:20 +0100 Subject: [PATCH 044/148] fix integration test --- .../bridging-rebasable.integration.test.ts | 71 ------------------- 1 file changed, 71 deletions(-) diff --git a/test/optimism/bridging-rebasable.integration.test.ts b/test/optimism/bridging-rebasable.integration.test.ts index 3d5f62a8..68cbabee 100644 --- a/test/optimism/bridging-rebasable.integration.test.ts +++ b/test/optimism/bridging-rebasable.integration.test.ts @@ -109,75 +109,6 @@ scenario("Optimism :: Bridging integration test", ctxFactory) ); }) - .step("Push token rate to L2", async (ctx) => { - const { - l1Token, - l1TokenRebasable, - l1LidoTokensBridge, - l2TokenRebasable, - l1CrossDomainMessenger, - l2ERC20TokenBridge, - l1Provider - } = ctx; - - const { l1Stranger } = ctx.accounts; - - const tokenHolderStrangerBalanceBefore = await l1TokenRebasable.balanceOf( - l1Stranger.address - ); - - const l1ERC20TokenBridgeBalanceBefore = await l1TokenRebasable.balanceOf( - l1LidoTokensBridge.address - ); - - const tx = await l1LidoTokensBridge - .connect(l1Stranger) - .pushTokenRate(200_000); - - const dataToSend = await packedTokenRateAndTimestamp(l1Provider, l1Token); - - await assert.emits(l1LidoTokensBridge, tx, "ERC20DepositInitiated", [ - l1TokenRebasable.address, - l2TokenRebasable.address, - l1Stranger.address, - l2ERC20TokenBridge.address, - 0, - dataToSend, - ]); - - const l2DepositCalldata = l2ERC20TokenBridge.interface.encodeFunctionData( - "finalizeDeposit", - [ - l1TokenRebasable.address, - l2TokenRebasable.address, - l1Stranger.address, - l2ERC20TokenBridge.address, - 0, - dataToSend, - ] - ); - - const messageNonce = await l1CrossDomainMessenger.messageNonce(); - - await assert.emits(l1CrossDomainMessenger, tx, "SentMessage", [ - l2ERC20TokenBridge.address, - l1LidoTokensBridge.address, - l2DepositCalldata, - messageNonce, - 200_000, - ]); - - assert.equalBN( - await l1Token.balanceOf(l1LidoTokensBridge.address), - l1ERC20TokenBridgeBalanceBefore - ); - - assert.equalBN( - await l1TokenRebasable.balanceOf(l1Stranger.address), - tokenHolderStrangerBalanceBefore - ); - }) - .step("L1 -> L2 deposit zero tokens via depositERC20() method", async (ctx) => { const { l1Token, @@ -889,8 +820,6 @@ async function ctxFactory() { l2Provider ); - await contracts.l1LidoTokensBridge.connect(l1ERC20TokenBridgeAdmin).pushTokenRate(1000000); - return { l1Provider, l2Provider, From e2c29e0f4fa2e5ef8cc121288fe1d55207fcfce3 Mon Sep 17 00:00:00 2001 From: Anton Kovalchuk Date: Mon, 1 Apr 2024 13:03:02 +0200 Subject: [PATCH 045/148] deployment scripts --- scripts/optimism/deploy-bridge.ts | 7 +- scripts/optimism/deploy-oracle.ts | 61 ++++ .../optimism.integration.test.ts | 54 +-- test/optimism/L2ERC20TokenBridge.unit.test.ts | 12 +- utils/deployment.ts | 2 + ... => deploymentBridgesAndRebasableToken.ts} | 99 ++++-- .../deploymentBridgesBothTokensAndOracle.ts | 318 ++++++++++++++++++ utils/optimism/deploymentOracle.ts | 103 ++++++ utils/optimism/testing.ts | 25 +- utils/optimism/upgradeOracle.ts | 79 +++++ 10 files changed, 686 insertions(+), 74 deletions(-) create mode 100644 scripts/optimism/deploy-oracle.ts rename utils/optimism/{deployment.ts => deploymentBridgesAndRebasableToken.ts} (70%) create mode 100644 utils/optimism/deploymentBridgesBothTokensAndOracle.ts create mode 100644 utils/optimism/deploymentOracle.ts create mode 100644 utils/optimism/upgradeOracle.ts diff --git a/scripts/optimism/deploy-bridge.ts b/scripts/optimism/deploy-bridge.ts index 7680befb..3453071c 100644 --- a/scripts/optimism/deploy-bridge.ts +++ b/scripts/optimism/deploy-bridge.ts @@ -26,12 +26,14 @@ async function main() { .erc20TokenBridgeDeployScript( deploymentConfig.token, deploymentConfig.stETHToken, + deploymentConfig.l2TokenRateOracle, { deployer: ethDeployer, admins: { proxy: deploymentConfig.l1.proxyAdmin, - bridge: ethDeployer.address, + bridge: ethDeployer.address }, + contractsShift: 0 }, { deployer: optDeployer, @@ -39,6 +41,7 @@ async function main() { proxy: deploymentConfig.l2.proxyAdmin, bridge: optDeployer.address, }, + contractsShift: 0 } ); @@ -63,7 +66,7 @@ async function main() { { logger: console } ); - const l2ERC20TokenBridgeProxyDeployStepIndex = 6; + const l2ERC20TokenBridgeProxyDeployStepIndex = 5; const l2BridgingManagement = new BridgingManagement( l2DeployScript.getContractAddress(l2ERC20TokenBridgeProxyDeployStepIndex), optDeployer, diff --git a/scripts/optimism/deploy-oracle.ts b/scripts/optimism/deploy-oracle.ts new file mode 100644 index 00000000..d5ef78d1 --- /dev/null +++ b/scripts/optimism/deploy-oracle.ts @@ -0,0 +1,61 @@ +import env from "../../utils/env"; +import prompt from "../../utils/prompt"; +import network from "../../utils/network"; +import optimism from "../../utils/optimism"; +import deploymentOracle from "../../utils/deployment"; + +async function main() { + const networkName = env.network(); + const ethOptNetwork = network.multichain(["eth", "opt"], networkName); + + const [ethDeployer] = ethOptNetwork.getSigners(env.privateKey(), { + forking: env.forking(), + }); + const [, optDeployer] = ethOptNetwork.getSigners( + env.string("OPT_DEPLOYER_PRIVATE_KEY"), + { + forking: env.forking(), + } + ); + + const deploymentConfig = deploymentOracle.loadMultiChainDeploymentConfig(); + + const [l1DeployScript, l2DeployScript] = await optimism + .deploymentOracle(networkName, { logger: console }) + .oracleDeployScript( + deploymentConfig.token, + { + deployer: ethDeployer, + admins: { + proxy: deploymentConfig.l1.proxyAdmin, + bridge: ethDeployer.address, + }, + }, + { + deployer: optDeployer, + admins: { + proxy: deploymentConfig.l2.proxyAdmin, + bridge: optDeployer.address, + }, + } + ); + + await deploymentOracle.printMultiChainDeploymentConfig( + "Deploy Optimism Bridge", + ethDeployer, + optDeployer, + deploymentConfig, + l1DeployScript, + l2DeployScript + ); + + await prompt.proceed(); + + await l1DeployScript.run(); + await l2DeployScript.run(); +} + +main().catch((error) => { + console.error(error); + process.exitCode = 1; +}); diff --git a/test/bridge-executor/optimism.integration.test.ts b/test/bridge-executor/optimism.integration.test.ts index c4ad2883..77cac7f5 100644 --- a/test/bridge-executor/optimism.integration.test.ts +++ b/test/bridge-executor/optimism.integration.test.ts @@ -5,8 +5,6 @@ import { OssifiableProxy__factory, OptimismBridgeExecutor__factory, ERC20Bridged__factory, - ERC20Rebasable__factory, - TokenRateOracle__factory, ERC20WrapperStub__factory, } from "../../typechain"; import { wei } from "../../utils/wei"; @@ -17,6 +15,7 @@ import { BridgingManagerRole } from "../../utils/bridging-management"; import env from "../../utils/env"; import network from "../../utils/network"; import { getBridgeExecutorParams } from "../../utils/bridge-executor"; +import deploymentAll, { L1DeployAllScript, L2DeployAllScript } from "../../utils/optimism/deploymentBridgesBothTokensAndOracle"; scenario("Optimism :: Bridge Executor integration test", ctxFactory) .step("Activate L2 bridge", async (ctx) => { @@ -238,39 +237,42 @@ async function ctxFactory() { const l1EthGovExecutorAddress = await govBridgeExecutor.getEthereumGovernanceExecutor(); - const [, l2DeployScript] = await optimism - .deployment(networkName) - .erc20TokenBridgeDeployScript( - l1Token.address, - l1TokenRebasable.address, - { - deployer: l1Deployer, - admins: { - proxy: l1Deployer.address, - bridge: l1Deployer.address - }, - }, - { - deployer: l2Deployer, - admins: { - proxy: govBridgeExecutor.address, - bridge: govBridgeExecutor.address, - }, - } - ); - await l2DeployScript.run(); + const [, optDeployScript] = await deploymentAll( + networkName + ).erc20TokenBridgeDeployScript( + l1Token.address, + l1TokenRebasable.address, + { + deployer: l1Deployer, + admins: { + proxy: l1Deployer.address, + bridge: l1Deployer.address + }, + contractsShift: 0 + }, + { + deployer: l2Deployer, + admins: { + proxy: govBridgeExecutor.address, + bridge: govBridgeExecutor.address, + }, + contractsShift: 0 + } + ); + + await optDeployScript.run(); const l2Token = ERC20Bridged__factory.connect( - l2DeployScript.getContractAddress(1), + optDeployScript.tokenProxyAddress, l2Deployer ); const l2ERC20TokenBridge = L2ERC20TokenBridge__factory.connect( - l2DeployScript.getContractAddress(3), + optDeployScript.tokenBridgeProxyAddress, l2Deployer ); const l2ERC20TokenBridgeProxy = OssifiableProxy__factory.connect( - l2DeployScript.getContractAddress(3), + optDeployScript.tokenBridgeProxyAddress, l2Deployer ); diff --git a/test/optimism/L2ERC20TokenBridge.unit.test.ts b/test/optimism/L2ERC20TokenBridge.unit.test.ts index fc0a54db..1fd77dd3 100644 --- a/test/optimism/L2ERC20TokenBridge.unit.test.ts +++ b/test/optimism/L2ERC20TokenBridge.unit.test.ts @@ -686,12 +686,12 @@ async function ctxFactory() { const emptyContractEOA = await testing.impersonate(emptyContract.address); const [ - l1TokenRebasableAddress, - l1TokenNonRebasableAddress, - l2TokenNonRebasableAddress, - tokenRateOracleAddress, - l2TokenRebasableAddress, - l2TokenBridgeImplAddress, + , + , + , + , + , + , l2TokenBridgeProxyAddress ] = await predictAddresses(deployer, 7); diff --git a/utils/deployment.ts b/utils/deployment.ts index f18aa609..7612bdf7 100644 --- a/utils/deployment.ts +++ b/utils/deployment.ts @@ -12,6 +12,7 @@ interface ChainDeploymentConfig extends BridgingManagerSetupConfig { interface MultiChainDeploymentConfig { token: string; stETHToken: string; + l2TokenRateOracle: string; l1: ChainDeploymentConfig; l2: ChainDeploymentConfig; } @@ -20,6 +21,7 @@ export function loadMultiChainDeploymentConfig(): MultiChainDeploymentConfig { return { token: env.address("TOKEN"), stETHToken: env.address("STETH_TOKEN"), + l2TokenRateOracle: env.address("TOKEN_RATE_ORACLE"), l1: { proxyAdmin: env.address("L1_PROXY_ADMIN"), bridgeAdmin: env.address("L1_BRIDGE_ADMIN"), diff --git a/utils/optimism/deployment.ts b/utils/optimism/deploymentBridgesAndRebasableToken.ts similarity index 70% rename from utils/optimism/deployment.ts rename to utils/optimism/deploymentBridgesAndRebasableToken.ts index 6a64d757..c8fbd9d4 100644 --- a/utils/optimism/deployment.ts +++ b/utils/optimism/deploymentBridgesAndRebasableToken.ts @@ -1,23 +1,22 @@ import { assert } from "chai"; import { Overrides, Wallet } from "ethers"; -import { - ERC20Bridged__factory, - ERC20Rebasable__factory, - IERC20Metadata__factory, - L1LidoTokensBridge__factory, - L2ERC20TokenBridge__factory, - OssifiableProxy__factory, - TokenRateOracle__factory, -} from "../../typechain"; - import addresses from "./addresses"; import { CommonOptions } from "./types"; import network, { NetworkName } from "../network"; import { DeployScript, Logger } from "../deployment/DeployScript"; +import { + ERC20Bridged__factory, + ERC20Rebasable__factory, + IERC20Metadata__factory, + L1LidoTokensBridge__factory, + L2ERC20TokenBridge__factory, + OssifiableProxy__factory, + } from "../../typechain"; interface OptL1DeployScriptParams { deployer: Wallet; admins: { proxy: string; bridge: string }; + contractsShift: number; } interface OptL2DeployScriptParams extends OptL1DeployScriptParams { @@ -30,6 +29,54 @@ interface OptDeploymentOptions extends CommonOptions { overrides?: Overrides; } +export class BridgeL1DeployScript extends DeployScript { + + constructor( + deployer: Wallet, + bridgeImplAddress: string, + bridgeProxyAddress: string, + logger?: Logger + ) { + super(deployer, logger); + this.bridgeImplAddress = bridgeImplAddress; + this.bridgeProxyAddress = bridgeProxyAddress; + } + + public bridgeImplAddress: string; + public bridgeProxyAddress: string; +} + +export class BridgeL2DeployScript extends DeployScript { + + constructor( + deployer: Wallet, + tokenImplAddress: string, + tokenProxyAddress: string, + tokenRebasableImplAddress: string, + tokenRebasableProxyAddress: string, + tokenBridgeImplAddress: string, + tokenBridgeProxyAddress: string, + logger?: Logger + ) { + super(deployer, logger); + this.tokenImplAddress = tokenImplAddress; + this.tokenProxyAddress = tokenProxyAddress; + this.tokenRebasableImplAddress = tokenRebasableImplAddress; + this.tokenRebasableProxyAddress = tokenRebasableProxyAddress; + this.tokenBridgeImplAddress = tokenBridgeImplAddress; + this.tokenBridgeProxyAddress = tokenBridgeProxyAddress; + } + + public tokenImplAddress: string; + public tokenProxyAddress: string; + public tokenRebasableImplAddress: string; + public tokenRebasableProxyAddress: string; + public tokenBridgeImplAddress: string; + public tokenBridgeProxyAddress: string; +} + +/// deploy Oracle first +/// deploys from scratch wstETH on L2, stETH on L2, bridgeL1, bridgeL2 export default function deployment( networkName: NetworkName, options: OptDeploymentOptions = {} @@ -39,6 +86,7 @@ export default function deployment( async erc20TokenBridgeDeployScript( l1Token: string, l1TokenRebasable: string, + l2TokenRateOracle: string, l1Params: OptL1DeployScriptParams, l2Params: OptL2DeployScriptParams, ) { @@ -46,20 +94,21 @@ export default function deployment( const [ expectedL1TokenBridgeImplAddress, expectedL1TokenBridgeProxyAddress, - ] = await network.predictAddresses(l1Params.deployer, 2); + ] = await network.predictAddresses(l1Params.deployer, l1Params.contractsShift + 2); const [ - expectedL2TokenRateOracleImplAddress, expectedL2TokenImplAddress, expectedL2TokenProxyAddress, expectedL2TokenRebasableImplAddress, expectedL2TokenRebasableProxyAddress, expectedL2TokenBridgeImplAddress, expectedL2TokenBridgeProxyAddress, - ] = await network.predictAddresses(l2Params.deployer, 7); + ] = await network.predictAddresses(l2Params.deployer, l2Params.contractsShift + 6); - const l1DeployScript = new DeployScript( + const l1DeployScript = new BridgeL1DeployScript( l1Params.deployer, + expectedL1TokenBridgeImplAddress, + expectedL1TokenBridgeProxyAddress, options?.logger ) .addStep({ @@ -108,21 +157,16 @@ export default function deployment( l2Params.l2TokenRebasable?.symbol ?? l1TokenRebasableInfo.symbol(), ]); - const l2DeployScript = new DeployScript( + const l2DeployScript = new BridgeL2DeployScript( l2Params.deployer, + expectedL2TokenImplAddress, + expectedL2TokenProxyAddress, + expectedL2TokenRebasableImplAddress, + expectedL2TokenRebasableProxyAddress, + expectedL2TokenBridgeImplAddress, + expectedL2TokenBridgeProxyAddress, options?.logger ) - .addStep({ - factory: TokenRateOracle__factory, - args: [ - expectedL2TokenBridgeProxyAddress, - 86400, - options?.overrides, - ], - afterDeploy: (c) => - assert.equal(c.address, expectedL2TokenRateOracleImplAddress), - }) - .addStep({ factory: ERC20Bridged__factory, args: [ @@ -156,7 +200,7 @@ export default function deployment( l2TokenRebasableSymbol, decimals, expectedL2TokenProxyAddress, - expectedL2TokenRateOracleImplAddress, + l2TokenRateOracle, expectedL2TokenBridgeProxyAddress, options?.overrides, ], @@ -177,7 +221,6 @@ export default function deployment( afterDeploy: (c) => assert.equal(c.address, expectedL2TokenRebasableProxyAddress), }) - .addStep({ factory: L2ERC20TokenBridge__factory, args: [ diff --git a/utils/optimism/deploymentBridgesBothTokensAndOracle.ts b/utils/optimism/deploymentBridgesBothTokensAndOracle.ts new file mode 100644 index 00000000..f84be05a --- /dev/null +++ b/utils/optimism/deploymentBridgesBothTokensAndOracle.ts @@ -0,0 +1,318 @@ +import { assert } from "chai"; +import { Overrides, Wallet } from "ethers"; +import addresses from "./addresses"; +import { CommonOptions } from "./types"; +import network, { NetworkName } from "../network"; +import { DeployScript, Logger } from "../deployment/DeployScript"; +import { + ERC20Bridged__factory, + ERC20Rebasable__factory, + IERC20Metadata__factory, + L1LidoTokensBridge__factory, + L2ERC20TokenBridge__factory, + OssifiableProxy__factory, + TokenRateOracle__factory, + TokenRateNotifier__factory, + OpStackTokenRatePusher__factory + } from "../../typechain"; + +interface OptL1DeployScriptParams { + deployer: Wallet; + admins: { proxy: string; bridge: string }; + contractsShift: number; +} + +interface OptL2DeployScriptParams extends OptL1DeployScriptParams { + l2Token?: { name?: string; symbol?: string }; + l2TokenRebasable?: { name?: string; symbol?: string }; +} + +interface OptDeploymentOptions extends CommonOptions { + logger?: Logger; + overrides?: Overrides; +} + +export class L1DeployAllScript extends DeployScript { + + constructor( + deployer: Wallet, + bridgeImplAddress: string, + bridgeProxyAddress: string, + tokenRateNotifierImplAddress: string, + opStackTokenRatePusherImplAddress: string, + logger?: Logger + ) { + super(deployer, logger); + this.bridgeImplAddress = bridgeImplAddress; + this.bridgeProxyAddress = bridgeProxyAddress; + this.tokenRateNotifierImplAddress = tokenRateNotifierImplAddress; + this.opStackTokenRatePusherImplAddress = opStackTokenRatePusherImplAddress; + } + + public bridgeImplAddress: string; + public bridgeProxyAddress: string; + public tokenRateNotifierImplAddress: string; + public opStackTokenRatePusherImplAddress: string; +} + +export class L2DeployAllScript extends DeployScript { + + constructor( + deployer: Wallet, + tokenImplAddress: string, + tokenProxyAddress: string, + tokenRebasableImplAddress: string, + tokenRebasableProxyAddress: string, + tokenBridgeImplAddress: string, + tokenBridgeProxyAddress: string, + tokenRateOracleImplAddress: string, + tokenRateOracleProxyAddress: string, + logger?: Logger + ) { + super(deployer, logger); + this.tokenImplAddress = tokenImplAddress; + this.tokenProxyAddress = tokenProxyAddress; + this.tokenRebasableImplAddress = tokenRebasableImplAddress; + this.tokenRebasableProxyAddress = tokenRebasableProxyAddress; + this.tokenBridgeImplAddress = tokenBridgeImplAddress; + this.tokenBridgeProxyAddress = tokenBridgeProxyAddress; + this.tokenRateOracleImplAddress = tokenRateOracleImplAddress; + this.tokenRateOracleProxyAddress = tokenRateOracleProxyAddress; + } + + public tokenImplAddress: string; + public tokenProxyAddress: string; + public tokenRebasableImplAddress: string; + public tokenRebasableProxyAddress: string; + public tokenBridgeImplAddress: string; + public tokenBridgeProxyAddress: string; + public tokenRateOracleImplAddress: string; + public tokenRateOracleProxyAddress: string; +} + +/// deploys from scratch wstETH on L2, stETH on L2, bridgeL1, bridgeL2 and Oracle +export default function deploymentAll( + networkName: NetworkName, + options: OptDeploymentOptions = {} +) { + const optAddresses = addresses(networkName, options); + return { + async erc20TokenBridgeDeployScript( + l1Token: string, + l1TokenRebasable: string, + l1Params: OptL1DeployScriptParams, + l2Params: OptL2DeployScriptParams, + ) { + + const [ + expectedL1TokenBridgeImplAddress, + expectedL1TokenBridgeProxyAddress, + expectedL1TokenRateNotifierImplAddress, + expectedL1OpStackTokenRatePusherImplAddress, + ] = await network.predictAddresses(l1Params.deployer, l1Params.contractsShift + 4); + + const [ + expectedL2TokenImplAddress, + expectedL2TokenProxyAddress, + expectedL2TokenRebasableImplAddress, + expectedL2TokenRebasableProxyAddress, + expectedL2TokenBridgeImplAddress, + expectedL2TokenBridgeProxyAddress, + expectedL2TokenRateOracleImplAddress, + expectedL2TokenRateOracleProxyAddress + ] = await network.predictAddresses(l2Params.deployer, l2Params.contractsShift + 8); + + const l1DeployScript = new L1DeployAllScript( + l1Params.deployer, + expectedL1TokenBridgeImplAddress, + expectedL1TokenBridgeProxyAddress, + expectedL1TokenRateNotifierImplAddress, + expectedL1OpStackTokenRatePusherImplAddress, + options?.logger + ) + .addStep({ + factory: L1LidoTokensBridge__factory, + args: [ + optAddresses.L1CrossDomainMessenger, + expectedL2TokenBridgeProxyAddress, + l1Token, + l1TokenRebasable, + expectedL2TokenProxyAddress, + expectedL2TokenRebasableProxyAddress, + options?.overrides, + ], + afterDeploy: (c) => + assert.equal(c.address, expectedL1TokenBridgeImplAddress), + }) + .addStep({ + factory: OssifiableProxy__factory, + args: [ + expectedL1TokenBridgeImplAddress, + l1Params.admins.proxy, + L1LidoTokensBridge__factory.createInterface().encodeFunctionData( + "initialize", + [l1Params.admins.bridge] + ), + options?.overrides, + ], + afterDeploy: (c) => + assert.equal(c.address, expectedL1TokenBridgeProxyAddress), + }) + .addStep({ + factory: TokenRateNotifier__factory, + args: [ + options?.overrides, + ], + afterDeploy: (c) => + assert.equal(c.address, expectedL1TokenRateNotifierImplAddress), + }) + .addStep({ + factory: OpStackTokenRatePusher__factory, + args: [ + optAddresses.L1CrossDomainMessenger, + l1Token, + expectedL2TokenRateOracleProxyAddress, + 1000, + options?.overrides, + ], + afterDeploy: (c) => + assert.equal(c.address, expectedL1OpStackTokenRatePusherImplAddress), + }); + + const l1TokenInfo = IERC20Metadata__factory.connect( + l1Token, + l1Params.deployer + ); + + const l1TokenRebasableInfo = IERC20Metadata__factory.connect( + l1TokenRebasable, + l1Params.deployer + ); + const [decimals, l2TokenName, l2TokenSymbol, l2TokenRebasableName, l2TokenRebasableSymbol] = await Promise.all([ + l1TokenInfo.decimals(), + l2Params.l2Token?.name ?? l1TokenInfo.name(), + l2Params.l2Token?.symbol ?? l1TokenInfo.symbol(), + l2Params.l2TokenRebasable?.name ?? l1TokenRebasableInfo.name(), + l2Params.l2TokenRebasable?.symbol ?? l1TokenRebasableInfo.symbol(), + ]); + + const l2DeployScript = new L2DeployAllScript( + l2Params.deployer, + expectedL2TokenImplAddress, + expectedL2TokenProxyAddress, + expectedL2TokenRebasableImplAddress, + expectedL2TokenRebasableProxyAddress, + expectedL2TokenBridgeImplAddress, + expectedL2TokenBridgeProxyAddress, + expectedL2TokenRateOracleImplAddress, + expectedL2TokenRateOracleProxyAddress, + options?.logger + ) + .addStep({ + factory: ERC20Bridged__factory, + args: [ + l2TokenName, + l2TokenSymbol, + decimals, + expectedL2TokenBridgeProxyAddress, + options?.overrides, + ], + afterDeploy: (c) => + assert.equal(c.address, expectedL2TokenImplAddress), + }) + .addStep({ + factory: OssifiableProxy__factory, + args: [ + expectedL2TokenImplAddress, + l2Params.admins.proxy, + ERC20Bridged__factory.createInterface().encodeFunctionData( + "initialize", + [l2TokenName, l2TokenSymbol] + ), + options?.overrides, + ], + afterDeploy: (c) => + assert.equal(c.address, expectedL2TokenProxyAddress), + }) + .addStep({ + factory: ERC20Rebasable__factory, + args: [ + l2TokenRebasableName, + l2TokenRebasableSymbol, + decimals, + expectedL2TokenProxyAddress, + expectedL2TokenRateOracleProxyAddress, + expectedL2TokenBridgeProxyAddress, + options?.overrides, + ], + afterDeploy: (c) => + assert.equal(c.address, expectedL2TokenRebasableImplAddress), + }) + .addStep({ + factory: OssifiableProxy__factory, + args: [ + expectedL2TokenRebasableImplAddress, + l2Params.admins.proxy, + ERC20Rebasable__factory.createInterface().encodeFunctionData( + "initialize", + [l2TokenRebasableName, l2TokenRebasableSymbol] + ), + options?.overrides, + ], + afterDeploy: (c) => + assert.equal(c.address, expectedL2TokenRebasableProxyAddress), + }) + .addStep({ + factory: L2ERC20TokenBridge__factory, + args: [ + optAddresses.L2CrossDomainMessenger, + expectedL1TokenBridgeProxyAddress, + l1Token, + l1TokenRebasable, + expectedL2TokenProxyAddress, + expectedL2TokenRebasableProxyAddress, + options?.overrides, + ], + afterDeploy: (c) => + assert.equal(c.address, expectedL2TokenBridgeImplAddress), + }) + .addStep({ + factory: OssifiableProxy__factory, + args: [ + expectedL2TokenBridgeImplAddress, + l2Params.admins.proxy, + L2ERC20TokenBridge__factory.createInterface().encodeFunctionData( + "initialize", + [l2Params.admins.bridge] + ), + options?.overrides, + ], + }) + .addStep({ + factory: TokenRateOracle__factory, + args: [ + optAddresses.L2CrossDomainMessenger, + expectedL2TokenBridgeProxyAddress, + expectedL1OpStackTokenRatePusherImplAddress, + 86400, + options?.overrides, + ], + afterDeploy: (c) => + assert.equal(c.address, expectedL2TokenRateOracleImplAddress), + }) + .addStep({ + factory: OssifiableProxy__factory, + args: [ + expectedL2TokenRateOracleImplAddress, + l2Params.admins.proxy, + [], + options?.overrides, + ], + afterDeploy: (c) => + assert.equal(c.address, expectedL2TokenRateOracleProxyAddress), + }); + + return [l1DeployScript, l2DeployScript]; + }, + }; +} diff --git a/utils/optimism/deploymentOracle.ts b/utils/optimism/deploymentOracle.ts new file mode 100644 index 00000000..da5f25a4 --- /dev/null +++ b/utils/optimism/deploymentOracle.ts @@ -0,0 +1,103 @@ +import { assert } from "chai"; +import { Overrides, Wallet } from "ethers"; +import { ethers } from "hardhat"; +import addresses from "./addresses"; +import { CommonOptions } from "./types"; +import network, { NetworkName } from "../network"; +import { DeployScript, Logger } from "../deployment/DeployScript"; +import { + OssifiableProxy__factory, + TokenRateOracle__factory, + TokenRateNotifier__factory, + OpStackTokenRatePusher__factory + } from "../../typechain"; + +interface OptDeployScriptParams { + deployer: Wallet; + admins: { proxy: string; bridge: string }; +} + +interface OptDeploymentOptions extends CommonOptions { + logger?: Logger; + overrides?: Overrides; +} + +export default function deploymentOracle( + networkName: NetworkName, + options: OptDeploymentOptions = {} + ) { + const optAddresses = addresses(networkName, options); + return { + async oracleDeployScript( + l1Token: string, + l1Params: OptDeployScriptParams, + l2Params: OptDeployScriptParams, + ) { + + const [ + expectedL1TokenRateNotifierImplAddress, + expectedL1OpStackTokenRatePusherImplAddress, + ] = await network.predictAddresses(l1Params.deployer, 2); + + const [ + expectedL2TokenRateOracleImplAddress, + expectedL2TokenRateOracleProxyAddress + ] = await network.predictAddresses(l2Params.deployer, 2); + + const l1DeployScript = new DeployScript( + l1Params.deployer, + options?.logger + ) + .addStep({ + factory: TokenRateNotifier__factory, + args: [ + options?.overrides, + ], + afterDeploy: (c) => + assert.equal(c.address, expectedL1TokenRateNotifierImplAddress), + }) + .addStep({ + factory: OpStackTokenRatePusher__factory, + args: [ + optAddresses.L1CrossDomainMessenger, + l1Token, + expectedL2TokenRateOracleProxyAddress, + 1000, + options?.overrides, + ], + afterDeploy: (c) => + assert.equal(c.address, expectedL1OpStackTokenRatePusherImplAddress), + }); + + const l2DeployScript = new DeployScript( + l2Params.deployer, + options?.logger + ) + .addStep({ + factory: TokenRateOracle__factory, + args: [ + optAddresses.L2CrossDomainMessenger, + ethers.constants.AddressZero, + expectedL1OpStackTokenRatePusherImplAddress, + 86400, + options?.overrides, + ], + afterDeploy: (c) => + assert.equal(c.address, expectedL2TokenRateOracleImplAddress), + }) + .addStep({ + factory: OssifiableProxy__factory, + args: [ + expectedL2TokenRateOracleImplAddress, + l2Params.admins.proxy, + [], + options?.overrides, + ], + afterDeploy: (c) => + assert.equal(c.address, expectedL2TokenRateOracleProxyAddress), + }); + + return [l1DeployScript, l2DeployScript]; + }, + }; + } diff --git a/utils/optimism/testing.ts b/utils/optimism/testing.ts index 5f36907c..65be4e31 100644 --- a/utils/optimism/testing.ts +++ b/utils/optimism/testing.ts @@ -1,4 +1,4 @@ -import { BigNumber, Signer } from "ethers"; +import { Signer } from "ethers"; import { JsonRpcProvider } from "@ethersproject/providers"; import { @@ -18,7 +18,7 @@ import { } from "../../typechain"; import addresses from "./addresses"; import contracts from "./contracts"; -import deployment from "./deployment"; +import deploymentAll, { L1DeployAllScript, L2DeployAllScript } from "./deploymentBridgesBothTokensAndOracle"; import testingUtils from "../testing"; import { BridgingManagement } from "../bridging-management"; import network, { NetworkName, SignerOrProvider } from "../network"; @@ -197,7 +197,7 @@ async function deployTestBridge( "TT" ); - const [ethDeployScript, optDeployScript] = await deployment( + const [ethDeployScript, optDeployScript] = await deploymentAll( networkName ).erc20TokenBridgeDeployScript( l1Token.address, @@ -205,25 +205,26 @@ async function deployTestBridge( { deployer: ethDeployer, admins: { proxy: ethDeployer.address, bridge: ethDeployer.address }, + contractsShift: 0 }, { deployer: optDeployer, admins: { proxy: optDeployer.address, bridge: optDeployer.address }, + contractsShift: 0 } ); await ethDeployScript.run(); await optDeployScript.run(); - const l1LidoTokensBridgeProxyDeployStepIndex = 1; + const l1BridgingManagement = new BridgingManagement( - ethDeployScript.getContractAddress(l1LidoTokensBridgeProxyDeployStepIndex), + ethDeployScript.bridgeProxyAddress, ethDeployer ); - const l2ERC20TokenBridgeProxyDeployStepIndex = 6; const l2BridgingManagement = new BridgingManagement( - optDeployScript.getContractAddress(l2ERC20TokenBridgeProxyDeployStepIndex), + optDeployScript.tokenBridgeProxyAddress, optDeployer ); @@ -244,11 +245,11 @@ async function deployTestBridge( l1TokenRebasable: l1TokenRebasable.connect(ethProvider), ...connectBridgeContracts( { - tokenRateOracle: optDeployScript.getContractAddress(0), - l2Token: optDeployScript.getContractAddress(2), - l2TokenRebasable: optDeployScript.getContractAddress(4), - l1LidoTokensBridge: ethDeployScript.getContractAddress(1), - l2ERC20TokenBridge: optDeployScript.getContractAddress(6) + tokenRateOracle: optDeployScript.tokenRateOracleProxyAddress, + l2Token: optDeployScript.tokenProxyAddress, + l2TokenRebasable: optDeployScript.tokenRebasableProxyAddress, + l1LidoTokensBridge: ethDeployScript.bridgeProxyAddress, + l2ERC20TokenBridge: optDeployScript.tokenBridgeProxyAddress }, ethProvider, optProvider diff --git a/utils/optimism/upgradeOracle.ts b/utils/optimism/upgradeOracle.ts new file mode 100644 index 00000000..17fd0b26 --- /dev/null +++ b/utils/optimism/upgradeOracle.ts @@ -0,0 +1,79 @@ +import { + OssifiableProxy__factory, + OptimismBridgeExecutor__factory +} from "../../typechain"; + +import network, { NetworkName } from "../network"; +import testingUtils from "../testing"; +import contracts from "./contracts"; +import testing from "../../utils/testing"; +import optimism from "../../utils/optimism"; +import { getBridgeExecutorParams } from "../../utils/bridge-executor"; + +export async function upgradeOracle( + networkName: NetworkName, + oracleProxyAddress: string, + newOracleAddress: string + ) { + const ethOptNetworks = network.multichain(["eth", "opt"], networkName); + const [ + ethProvider, + optProvider + ] = ethOptNetworks.getProviders({ forking: true }); + const ethDeployer = testing.accounts.deployer(ethProvider); + const optDeployer = testing.accounts.deployer(optProvider); + + + const optContracts = contracts(networkName, { forking: true }); + const l1CrossDomainMessengerAliased = await testingUtils.impersonate( + testingUtils.accounts.applyL1ToL2Alias(optContracts.L1CrossDomainMessenger.address), + optProvider + ); + const l2CrossDomainMessenger = await optContracts.L2CrossDomainMessenger.connect( + l1CrossDomainMessengerAliased + ); + + + const testingOnDeployedContracts = testing.env.USE_DEPLOYED_CONTRACTS(false); + const optAddresses = optimism.addresses(networkName); + const govBridgeExecutor = testingOnDeployedContracts + ? OptimismBridgeExecutor__factory.connect( + testing.env.OPT_GOV_BRIDGE_EXECUTOR(), + optProvider + ) + : await new OptimismBridgeExecutor__factory(optDeployer).deploy( + optAddresses.L2CrossDomainMessenger, + ethDeployer.address, + ...getBridgeExecutorParams(), + optDeployer.address + ); + + + const l1EthGovExecutorAddress = await govBridgeExecutor.getEthereumGovernanceExecutor(); + const bridgeExecutor = govBridgeExecutor.connect(optDeployer); + const l2OracleProxy = OssifiableProxy__factory.connect( + oracleProxyAddress, + optDeployer + ); + + await l2CrossDomainMessenger.relayMessage( + 0, + l1EthGovExecutorAddress, + bridgeExecutor.address, + 0, + 300_000, + bridgeExecutor.interface.encodeFunctionData("queue", [ + [oracleProxyAddress], + [0], + ["proxy__upgradeTo(address)"], + [ + "0x" + + l2OracleProxy.interface + .encodeFunctionData("proxy__upgradeTo", [newOracleAddress]) + .substring(10), + ], + [false], + ]), + { gasLimit: 5_000_000 } + ); +} From 0d2df1d450e304526d89ffe8c5e5b27c79b83252 Mon Sep 17 00:00:00 2001 From: Anton Kovalchuk Date: Mon, 1 Apr 2024 23:44:22 +0200 Subject: [PATCH 046/148] add token rate oracle integration tests --- .../optimism.integration.test.ts | 51 +++-- .../pushingTokenRate.integration.test.ts | 212 ++++++++++++++++++ ...othTokensAndOracle.ts => deploymentAll.ts} | 4 +- .../deploymentBridgesAndRebasableToken.ts | 4 +- utils/optimism/deploymentOracle.ts | 46 +++- utils/optimism/index.ts | 5 +- utils/optimism/testing.ts | 3 +- 7 files changed, 288 insertions(+), 37 deletions(-) create mode 100644 test/optimism/pushingTokenRate.integration.test.ts rename utils/optimism/{deploymentBridgesBothTokensAndOracle.ts => deploymentAll.ts} (98%) diff --git a/test/bridge-executor/optimism.integration.test.ts b/test/bridge-executor/optimism.integration.test.ts index 77cac7f5..1cf1db45 100644 --- a/test/bridge-executor/optimism.integration.test.ts +++ b/test/bridge-executor/optimism.integration.test.ts @@ -15,7 +15,7 @@ import { BridgingManagerRole } from "../../utils/bridging-management"; import env from "../../utils/env"; import network from "../../utils/network"; import { getBridgeExecutorParams } from "../../utils/bridge-executor"; -import deploymentAll, { L1DeployAllScript, L2DeployAllScript } from "../../utils/optimism/deploymentBridgesBothTokensAndOracle"; +import deploymentAll from "../../utils/optimism/deploymentAll"; scenario("Optimism :: Bridge Executor integration test", ctxFactory) .step("Activate L2 bridge", async (ctx) => { @@ -202,7 +202,6 @@ async function ctxFactory() { .multichain(["eth", "opt"], networkName) .getProviders({ forking: true }); - const testingOnDeployedContracts = testing.env.USE_DEPLOYED_CONTRACTS(false); const l1Deployer = testing.accounts.deployer(l1Provider); const l2Deployer = testing.accounts.deployer(l2Provider); @@ -221,6 +220,7 @@ async function ctxFactory() { ); const optAddresses = optimism.addresses(networkName); + const testingOnDeployedContracts = testing.env.USE_DEPLOYED_CONTRACTS(false); const govBridgeExecutor = testingOnDeployedContracts ? OptimismBridgeExecutor__factory.connect( @@ -237,31 +237,30 @@ async function ctxFactory() { const l1EthGovExecutorAddress = await govBridgeExecutor.getEthereumGovernanceExecutor(); + const [, optDeployScript] = await deploymentAll( + networkName + ).erc20TokenBridgeDeployScript( + l1Token.address, + l1TokenRebasable.address, + { + deployer: l1Deployer, + admins: { + proxy: l1Deployer.address, + bridge: l1Deployer.address + }, + contractsShift: 0 + }, + { + deployer: l2Deployer, + admins: { + proxy: govBridgeExecutor.address, + bridge: govBridgeExecutor.address, + }, + contractsShift: 0 + } + ); - const [, optDeployScript] = await deploymentAll( - networkName - ).erc20TokenBridgeDeployScript( - l1Token.address, - l1TokenRebasable.address, - { - deployer: l1Deployer, - admins: { - proxy: l1Deployer.address, - bridge: l1Deployer.address - }, - contractsShift: 0 - }, - { - deployer: l2Deployer, - admins: { - proxy: govBridgeExecutor.address, - bridge: govBridgeExecutor.address, - }, - contractsShift: 0 - } - ); - - await optDeployScript.run(); + await optDeployScript.run(); const l2Token = ERC20Bridged__factory.connect( optDeployScript.tokenProxyAddress, diff --git a/test/optimism/pushingTokenRate.integration.test.ts b/test/optimism/pushingTokenRate.integration.test.ts new file mode 100644 index 00000000..50f8211f --- /dev/null +++ b/test/optimism/pushingTokenRate.integration.test.ts @@ -0,0 +1,212 @@ +import { assert } from "chai"; +import { ethers } from "hardhat"; +import env from "../../utils/env"; +import { wei } from "../../utils/wei"; +import optimism from "../../utils/optimism"; +import network from "../../utils/network"; +import testing, { scenario } from "../../utils/testing"; +import deploymentOracle from "../../utils/optimism/deploymentOracle"; +import { getBridgeExecutorParams } from "../../utils/bridge-executor"; +import { JsonRpcProvider } from "@ethersproject/providers"; +import { + ERC20BridgedStub__factory, + ERC20WrapperStub__factory, + OptimismBridgeExecutor__factory, + TokenRateNotifier__factory, + TokenRateOracle__factory +} from "../../typechain"; + +scenario("Optimism :: Token Rate Oracle integration test", ctxFactory) + + .step("Push Token Rate", async (ctx) => { + const { + tokenRateNotifier, + tokenRateOracle, + opTokenRatePusher, + l1CrossDomainMessenger, + l1Token, + l1Provider + } = ctx; + + const tokenRate = await l1Token.stEthPerToken(); + + const account = ctx.accounts.accountA; + + const tx = await tokenRateNotifier + .connect(account.l1Signer) + .handlePostTokenRebase(1, 2, 3, 4, 5, 6, 7); + + const messageNonce = await l1CrossDomainMessenger.messageNonce(); + const [stEthPerTokenStr, blockTimestampStr] = await tokenRateAndTimestamp(l1Provider, tokenRate); + const l2Calldata = tokenRateOracle.interface.encodeFunctionData( + "updateRate", + [ + stEthPerTokenStr, + blockTimestampStr + ] + ); + + await assert.emits(l1CrossDomainMessenger, tx, "SentMessage", [ + tokenRateOracle.address, + opTokenRatePusher, + l2Calldata, + messageNonce, + 1000, + ]); + }) + + .step("finalize pushing rate", async (ctx) => { + const { + opTokenRatePusher, + tokenRateOracle, + l1Token, + l1Provider, + l1CrossDomainMessenger + } = ctx; + + const account = ctx.accounts.accountA; + await l1CrossDomainMessenger + .connect(account.l1Signer) + .setXDomainMessageSender(opTokenRatePusher); + + const tokenRate = await l1Token.stEthPerToken(); + const [stEthPerTokenStr, blockTimestampStr] = await tokenRateAndTimestamp(l1Provider, tokenRate); + + const tx = await ctx.l2CrossDomainMessenger + .connect(ctx.accounts.l1CrossDomainMessengerAliased) + .relayMessage( + 1, + opTokenRatePusher, + tokenRateOracle.address, + 0, + 300_000, + tokenRateOracle.interface.encodeFunctionData("updateRate", [ + stEthPerTokenStr, + blockTimestampStr + ]), + { gasLimit: 5_000_000 } + ); + + const answer = await tokenRateOracle.latestAnswer(); + assert.equalBN(answer, tokenRate); + + const [ + answer1, + answer2, + answer3, + answer4, + answer5 + ] = await tokenRateOracle.latestRoundData(); + + assert.equalBN(answer2, tokenRate); + assert.equalBN(answer4, blockTimestampStr); + }) + + .run(); + +async function ctxFactory() { + const networkName = env.network("TESTING_OPT_NETWORK", "mainnet"); + const [l1Provider, l2Provider] = network + .multichain(["eth", "opt"], networkName) + .getProviders({ forking: true }); + const l1Deployer = testing.accounts.deployer(l1Provider); + const l2Deployer = testing.accounts.deployer(l2Provider); + + const optContracts = optimism.contracts(networkName, { forking: true }); + const l2CrossDomainMessenger = optContracts.L2CrossDomainMessenger; + const testingOnDeployedContracts = testing.env.USE_DEPLOYED_CONTRACTS(false); + const optAddresses = optimism.addresses(networkName); + + const govBridgeExecutor = testingOnDeployedContracts + ? OptimismBridgeExecutor__factory.connect( + testing.env.OPT_GOV_BRIDGE_EXECUTOR(), + l2Provider + ) + : await new OptimismBridgeExecutor__factory(l2Deployer).deploy( + optAddresses.L2CrossDomainMessenger, + l1Deployer.address, + ...getBridgeExecutorParams(), + l2Deployer.address + ); + + const l1TokenRebasable = await new ERC20BridgedStub__factory(l1Deployer).deploy( + "Test Token Rebasable", + "TTR" + ); + const l1Token = await new ERC20WrapperStub__factory(l1Deployer).deploy( + l1TokenRebasable.address, + "Test Token", + "TT" + ); + const [ethDeployScript, optDeployScript] = await deploymentOracle( + networkName + ).oracleDeployScript( + l1Token.address, + { + deployer: l1Deployer, + admins: { + proxy: l1Deployer.address, + bridge: l1Deployer.address + }, + }, + { + deployer: l2Deployer, + admins: { + proxy: govBridgeExecutor.address, + bridge: govBridgeExecutor.address, + }, + } + ); + + await ethDeployScript.run(); + await optDeployScript.run(); + + await optimism.testing(networkName).stubL1CrossChainMessengerContract(); + + const l1CrossDomainMessengerAliased = await testing.impersonate( + testing.accounts.applyL1ToL2Alias(optContracts.L1CrossDomainMessengerStub.address), + l2Provider + ); + await testing.setBalance( + await l1CrossDomainMessengerAliased.getAddress(), + wei.toBigNumber(wei`1 ether`), + l2Provider + ); + + const tokenRateNotifier = TokenRateNotifier__factory.connect( + ethDeployScript.tokenRateNotifierImplAddress, + l1Provider + ); + await tokenRateNotifier + .connect(l1Deployer) + .addObserver(ethDeployScript.opStackTokenRatePusherImplAddress); + const tokenRateOracle = TokenRateOracle__factory.connect( + optDeployScript.tokenRateOracleProxyAddress, + l2Provider + ); + + const accountA = testing.accounts.accountA(l1Provider, l2Provider); + const l1CrossDomainMessenger = optContracts.L1CrossDomainMessengerStub; + + return { + tokenRateNotifier, + tokenRateOracle, + opTokenRatePusher: ethDeployScript.opStackTokenRatePusherImplAddress, + l1CrossDomainMessenger, + l2CrossDomainMessenger, + l1Token, + l1Provider, + accounts: { + accountA, + l1CrossDomainMessengerAliased + } + }; +} + +async function tokenRateAndTimestamp(provider: JsonRpcProvider, tokenRate: BigNumber) { + const blockNumber = await provider.getBlockNumber(); + const blockTimestamp = (await provider.getBlock(blockNumber)).timestamp; + const stEthPerTokenStr = ethers.utils.hexZeroPad(tokenRate.toHexString(), 12); + const blockTimestampStr = ethers.utils.hexZeroPad(ethers.utils.hexlify(blockTimestamp), 5); + return [stEthPerTokenStr, blockTimestampStr]; +} diff --git a/utils/optimism/deploymentBridgesBothTokensAndOracle.ts b/utils/optimism/deploymentAll.ts similarity index 98% rename from utils/optimism/deploymentBridgesBothTokensAndOracle.ts rename to utils/optimism/deploymentAll.ts index f84be05a..993b7a2c 100644 --- a/utils/optimism/deploymentBridgesBothTokensAndOracle.ts +++ b/utils/optimism/deploymentAll.ts @@ -102,7 +102,7 @@ export default function deploymentAll( l1TokenRebasable: string, l1Params: OptL1DeployScriptParams, l2Params: OptL2DeployScriptParams, - ) { + ): Promise<[L1DeployAllScript, L2DeployAllScript]> { const [ expectedL1TokenBridgeImplAddress, @@ -312,7 +312,7 @@ export default function deploymentAll( assert.equal(c.address, expectedL2TokenRateOracleProxyAddress), }); - return [l1DeployScript, l2DeployScript]; + return [l1DeployScript as L1DeployAllScript, l2DeployScript as L2DeployAllScript]; }, }; } diff --git a/utils/optimism/deploymentBridgesAndRebasableToken.ts b/utils/optimism/deploymentBridgesAndRebasableToken.ts index c8fbd9d4..1035b7a5 100644 --- a/utils/optimism/deploymentBridgesAndRebasableToken.ts +++ b/utils/optimism/deploymentBridgesAndRebasableToken.ts @@ -89,7 +89,7 @@ export default function deployment( l2TokenRateOracle: string, l1Params: OptL1DeployScriptParams, l2Params: OptL2DeployScriptParams, - ) { + ): Promise<[BridgeL1DeployScript, BridgeL2DeployScript]> { const [ expectedL1TokenBridgeImplAddress, @@ -248,7 +248,7 @@ export default function deployment( ], }); - return [l1DeployScript, l2DeployScript]; + return [l1DeployScript as BridgeL1DeployScript, l2DeployScript as BridgeL2DeployScript]; }, }; } diff --git a/utils/optimism/deploymentOracle.ts b/utils/optimism/deploymentOracle.ts index da5f25a4..ec28c820 100644 --- a/utils/optimism/deploymentOracle.ts +++ b/utils/optimism/deploymentOracle.ts @@ -22,6 +22,40 @@ interface OptDeploymentOptions extends CommonOptions { overrides?: Overrides; } +export class OracleL1DeployScript extends DeployScript { + + constructor( + deployer: Wallet, + tokenRateNotifierImplAddress: string, + opStackTokenRatePusherImplAddress: string, + logger?: Logger + ) { + super(deployer, logger); + this.tokenRateNotifierImplAddress = tokenRateNotifierImplAddress; + this.opStackTokenRatePusherImplAddress = opStackTokenRatePusherImplAddress; + } + + public tokenRateNotifierImplAddress: string; + public opStackTokenRatePusherImplAddress: string; +} + +export class OracleL2DeployScript extends DeployScript { + + constructor( + deployer: Wallet, + tokenRateOracleImplAddress: string, + tokenRateOracleProxyAddress: string, + logger?: Logger + ) { + super(deployer, logger); + this.tokenRateOracleImplAddress = tokenRateOracleImplAddress; + this.tokenRateOracleProxyAddress = tokenRateOracleProxyAddress; + } + + public tokenRateOracleImplAddress: string; + public tokenRateOracleProxyAddress: string; +} + export default function deploymentOracle( networkName: NetworkName, options: OptDeploymentOptions = {} @@ -32,7 +66,7 @@ export default function deploymentOracle( l1Token: string, l1Params: OptDeployScriptParams, l2Params: OptDeployScriptParams, - ) { + ): Promise<[OracleL1DeployScript, OracleL2DeployScript]> { const [ expectedL1TokenRateNotifierImplAddress, @@ -44,8 +78,10 @@ export default function deploymentOracle( expectedL2TokenRateOracleProxyAddress ] = await network.predictAddresses(l2Params.deployer, 2); - const l1DeployScript = new DeployScript( + const l1DeployScript = new OracleL1DeployScript( l1Params.deployer, + expectedL1TokenRateNotifierImplAddress, + expectedL1OpStackTokenRatePusherImplAddress, options?.logger ) .addStep({ @@ -69,8 +105,10 @@ export default function deploymentOracle( assert.equal(c.address, expectedL1OpStackTokenRatePusherImplAddress), }); - const l2DeployScript = new DeployScript( + const l2DeployScript = new OracleL2DeployScript( l2Params.deployer, + expectedL2TokenRateOracleImplAddress, + expectedL2TokenRateOracleProxyAddress, options?.logger ) .addStep({ @@ -97,7 +135,7 @@ export default function deploymentOracle( assert.equal(c.address, expectedL2TokenRateOracleProxyAddress), }); - return [l1DeployScript, l2DeployScript]; + return [l1DeployScript as OracleL1DeployScript, l2DeployScript as OracleL2DeployScript]; }, }; } diff --git a/utils/optimism/index.ts b/utils/optimism/index.ts index 0ca0e9a2..9b00eed7 100644 --- a/utils/optimism/index.ts +++ b/utils/optimism/index.ts @@ -1,6 +1,7 @@ import addresses from "./addresses"; import contracts from "./contracts"; -import deployment from "./deployment"; +import deployment from "./deploymentBridgesAndRebasableToken"; +import deploymentOracle from "./deploymentOracle"; import testing from "./testing"; import messaging from "./messaging"; @@ -10,4 +11,6 @@ export default { contracts, messaging, deployment, + deploymentOracle }; + diff --git a/utils/optimism/testing.ts b/utils/optimism/testing.ts index 65be4e31..a0cd5148 100644 --- a/utils/optimism/testing.ts +++ b/utils/optimism/testing.ts @@ -18,7 +18,7 @@ import { } from "../../typechain"; import addresses from "./addresses"; import contracts from "./contracts"; -import deploymentAll, { L1DeployAllScript, L2DeployAllScript } from "./deploymentBridgesBothTokensAndOracle"; +import deploymentAll from "./deploymentAll"; import testingUtils from "../testing"; import { BridgingManagement } from "../bridging-management"; import network, { NetworkName, SignerOrProvider } from "../network"; @@ -217,7 +217,6 @@ async function deployTestBridge( await ethDeployScript.run(); await optDeployScript.run(); - const l1BridgingManagement = new BridgingManagement( ethDeployScript.bridgeProxyAddress, ethDeployer From 8dc246e23fe977ca786afdc8f7798da1fc4b95e6 Mon Sep 17 00:00:00 2001 From: Artyom Veremeenko Date: Tue, 2 Apr 2024 10:33:09 +0300 Subject: [PATCH 047/148] fix happy path test for permit for rebasable --- contracts/token/ERC20RebasablePermit.sol | 3 +- test/token/ERC20Permit.unit.test.ts | 65 ++++++++++++------------ utils/testing/permit-helpers.ts | 14 ++--- utils/testing/unit.ts | 2 +- 4 files changed, 41 insertions(+), 43 deletions(-) diff --git a/contracts/token/ERC20RebasablePermit.sol b/contracts/token/ERC20RebasablePermit.sol index 5a7843e2..6321672d 100644 --- a/contracts/token/ERC20RebasablePermit.sol +++ b/contracts/token/ERC20RebasablePermit.sol @@ -85,13 +85,14 @@ contract ERC20RebasablePermit is IERC2612, ERC20Rebasable, EIP712 { constructor( string memory name_, string memory symbol_, + // TODO: pass signing domain version uint8 decimals_, address wrappedToken_, address tokenRateOracle_, address bridge_ ) ERC20Rebasable(name_, symbol_, decimals_, wrappedToken_, tokenRateOracle_, bridge_) - EIP712("Liquid staked Ether 2.0", "1") + EIP712(name_, "2") { } diff --git a/test/token/ERC20Permit.unit.test.ts b/test/token/ERC20Permit.unit.test.ts index 4ec18250..a19bf940 100644 --- a/test/token/ERC20Permit.unit.test.ts +++ b/test/token/ERC20Permit.unit.test.ts @@ -1,6 +1,6 @@ import hre from "hardhat"; import { assert } from "chai"; -import { unit } from "../../utils/testing"; +import { unit, UnitTest } from "../../utils/testing"; import { wei } from "../../utils/wei"; import { makeDomainSeparator, signPermit } from "../../utils/testing/permit-helpers"; @@ -14,8 +14,10 @@ import { import { BigNumber } from "ethers"; +type ContextType = Awaited>> + const TOKEN_NAME = 'Liquid staked Ether 2.0' -const TOKEN_VERSION = '1' +const SIGNING_DOMAIN_VERSION = '2' // derived from mnemonic: want believe mosquito cat design route voice cause gold benefit gospel bulk often attitude rural const ACCOUNTS_AND_KEYS = [ @@ -29,6 +31,10 @@ const ACCOUNTS_AND_KEYS = [ }, ] +function getChainId() { + return hre.network.config.chainId as number; +} + const getAccountsEOA = async () => { return { alice: ACCOUNTS_AND_KEYS[0], @@ -43,13 +49,9 @@ const getAccountsEIP1271 = async () => { return { alice, bob } } -// const signPermit = async (owner, spender, value, nonce, domainSeparator, deadline, acct) => { -// const digest = calculatePermitDigest(owner, spender, value, nonce, domainSeparator, deadline) -// return await sign(digest, acct) -// } - - -unit("ERC20Permit", ctxFactory) +function permitTestsSuit(unitInstance: UnitTest) +{ + unitInstance .test("wrappedToken() :: has the same address is in constructor", async (ctx) => { const { rebasableProxied, wrappedToken } = ctx.contracts; @@ -58,29 +60,22 @@ unit("ERC20Permit", ctxFactory) .test('eip712Domain() is correct', async (ctx) => { const token = ctx.contracts.rebasableProxied - const [ fields, name, version, chainId, verifyingContract, salt, extensions ] = await token.eip712Domain() + const [ , name, version, chainId, verifyingContract, , ] = await token.eip712Domain() assert.equal(name, TOKEN_NAME) - assert.equal(version, TOKEN_VERSION) + assert.equal(version, SIGNING_DOMAIN_VERSION) assert.isDefined(hre.network.config.chainId) - assert.equal(chainId.toNumber(), hre.network.config.chainId as number) + assert.equal(chainId.toNumber(), getChainId()) assert.equal(verifyingContract, token.address) - const domainSeparator = makeDomainSeparator(TOKEN_NAME, TOKEN_VERSION, chainId, token.address) + const domainSeparator = makeDomainSeparator(TOKEN_NAME, SIGNING_DOMAIN_VERSION, chainId, token.address) assert.equal(makeDomainSeparator(name, version, chainId, verifyingContract), domainSeparator) }) .test('DOMAIN_SEPARATOR() is correct', async (ctx) => { const token = ctx.contracts.rebasableProxied - const [ fields, name, version, chainId, verifyingContract, salt, extensions ] = await token.eip712Domain() - assert.equal(name, TOKEN_NAME) - assert.equal(version, TOKEN_VERSION) - assert.isDefined(hre.network.config.chainId) - assert.equal(chainId.toNumber(), hre.network.config.chainId as number) - assert.equal(verifyingContract, token.address) - - const domainSeparator = makeDomainSeparator(TOKEN_NAME, TOKEN_VERSION, chainId, token.address) + const domainSeparator = makeDomainSeparator(TOKEN_NAME, SIGNING_DOMAIN_VERSION, getChainId(), token.address) assert.equal(await ctx.contracts.rebasableProxied.DOMAIN_SEPARATOR(), domainSeparator) }) @@ -94,10 +89,9 @@ unit("ERC20Permit", ctxFactory) let nonce = 0 const charlie = ctx.accounts.user2 const charlieSigner = hre.ethers.provider.getSigner(charlie.address) - // const bobSigner = hre.ethers.provider.getSigner(BOB.address) - const domainSeparator = makeDomainSeparator(TOKEN_NAME, TOKEN_VERSION, hre.network.config.chainId as number, token.address) - let { v, r, s } = await signPermit(owner, spender.address, value, nonce, deadline, domainSeparator) + const domainSeparator = makeDomainSeparator(TOKEN_NAME, SIGNING_DOMAIN_VERSION, getChainId(), token.address) + let { v, r, s } = await signPermit(owner, spender.address, value, deadline, nonce, domainSeparator) // check that the allowance is initially zero assert.equalBN(await token.allowance(owner.address, spender.address), BigNumber.from(0)) @@ -107,33 +101,35 @@ unit("ERC20Permit", ctxFactory) assert.equal(await token.DOMAIN_SEPARATOR(), domainSeparator) // a third-party, Charlie (not Alice) submits the permit - // TODO: handle unpredictable gas limit somehow better than setting it a random constant + // TODO: handle unpredictable gas limit somehow better than setting it to a random constant const tx = await token.connect(charlieSigner) .permit(owner.address, spender.address, value, deadline, v, r, s, { gasLimit: '0xffffff' }) // check that allowance is updated assert.equalBN(await token.allowance(owner.address, spender.address), BigNumber.from(value)) - await assert.emits(token, tx, 'Approval', [ owner, spender, value ]) + await assert.emits(token, tx, 'Approval', [ owner.address, spender.address, value ]) assert.equalBN(await token.nonces(owner.address), BigNumber.from(1)) + // increment nonce nonce = 1 value = 4e5 - ;({ v, r, s } = await signPermit(owner, spender.address, value, nonce, deadline, domainSeparator)) + ;({ v, r, s } = await signPermit(owner, spender.address, value, deadline, nonce, domainSeparator)) // submit the permit const tx2 = await token.connect(charlieSigner).permit(owner.address, spender.address, value, deadline, v, r, s) // check that allowance is updated assert.equalBN(await token.allowance(owner.address, spender.address), BigNumber.from(value)) - assert.emits(token, tx2, 'Approval', [ owner.address, spender, BigNumber.from(value) ] ) + assert.emits(token, tx2, 'Approval', [ owner.address, spender.address, BigNumber.from(value) ] ) assert.equalBN(await token.nonces(owner.address), BigNumber.from(2)) }) .run(); +} -async function ctxFactory() { - // const name = "StETH Test Token"; +function ctxFactoryFactory(signingAccountsFuncFactory: typeof getAccountsEIP1271 | typeof getAccountsEOA) { + return async () => { const name = TOKEN_NAME; const symbol = "StETH"; const decimalsToSet = 18; @@ -160,7 +156,9 @@ async function ctxFactory() { owner.address ); const tokenRateOracle = await new TokenRateOracle__factory(deployer).deploy( + hre.ethers.constants.AddressZero, owner.address, + hre.ethers.constants.AddressZero, 86400 ); const rebasableTokenImpl = await new ERC20RebasablePermit__factory(deployer).deploy( @@ -195,8 +193,7 @@ async function ctxFactory() { await tokenRateOracle.connect(owner).updateRate(rate, 1000); await rebasableProxied.connect(owner).mintShares(holder.address, premintShares); - // const { alice, bob } = await getAccountsEOA(); - const { alice, bob } = await getAccountsEIP1271(); + const { alice, bob } = await signingAccountsFuncFactory(); const MAX_UINT256 = '0xffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff' return { @@ -211,4 +208,8 @@ async function ctxFactory() { deadline: MAX_UINT256, } }; + } } + +permitTestsSuit(unit("ERC20Permit with EIP1271 (contract) signing", ctxFactoryFactory(getAccountsEIP1271))); +permitTestsSuit(unit("ERC20Permit with ECDSA (EOA) signing", ctxFactoryFactory(getAccountsEOA))); diff --git a/utils/testing/permit-helpers.ts b/utils/testing/permit-helpers.ts index d7151a30..06bd5617 100644 --- a/utils/testing/permit-helpers.ts +++ b/utils/testing/permit-helpers.ts @@ -7,7 +7,6 @@ import { ecsign as ecSignBuf } from "ethereumjs-util"; const PERMIT_TYPE_HASH = streccak( 'Permit(address owner,address spender,uint256 value,uint256 nonce,uint256 deadline)' ) -console.log({ PERMIT_TYPE_HASH }) interface Eip1271Contract { address: string; @@ -23,7 +22,6 @@ async function signEOA(digest: string, account: ExternallyOwnedAccount) { async function signEIP1271(digest: string, eip1271Contract: Eip1271Contract) { const sig = await eip1271Contract.sign(digest) - console.log({ sig }) return { v: sig.v, r: sig.r, s: sig.s } } @@ -43,7 +41,7 @@ export function makeDomainSeparator(name: string, version: string, chainId: BigN ) } -export async function signPermit(owner: ExternallyOwnedAccount | Eip1271Contract, spender: string, value: number, nonce: number, deadline: string, domainSeparator: string) { +export async function signPermit(owner: ExternallyOwnedAccount | Eip1271Contract, spender: string, value: number, deadline: string, nonce: number, domainSeparator: string) { const digest = calculatePermitDigest(owner.address, spender, value, nonce, deadline, domainSeparator) if (owner.hasOwnProperty('sign')) { return await signEIP1271(digest, owner as Eip1271Contract); @@ -52,7 +50,7 @@ export async function signPermit(owner: ExternallyOwnedAccount | Eip1271Contract } } -function calculatePermitDigest(owner: string, spender: string, value: number, nonce: number, deadline: string, domainSeparator: string) { +export function calculatePermitDigest(owner: string, spender: string, value: number, nonce: number, deadline: string, domainSeparator: string) { return calculateEIP712Digest( domainSeparator, PERMIT_TYPE_HASH, @@ -62,11 +60,9 @@ function calculatePermitDigest(owner: string, spender: string, value: number, no } function calculateEIP712Digest(domainSeparator: string, typeHash: string, types: string[], parameters: unknown[]) { - return streccak( - '0x1901' + - strip0x(domainSeparator) + - strip0x(keccak256(defaultAbiCoder.encode(['bytes32', ...types], [typeHash, ...parameters]))) - ) + const structHash = keccak256(defaultAbiCoder.encode(['bytes32', ...types], [typeHash, ...parameters])); + const data = '0x1901' + strip0x(domainSeparator) + strip0x(structHash) + return keccak256(data) } function ecSign(digest: string, privateKey: string) { diff --git a/utils/testing/unit.ts b/utils/testing/unit.ts index a282e77e..f6d83c95 100644 --- a/utils/testing/unit.ts +++ b/utils/testing/unit.ts @@ -5,7 +5,7 @@ export function unit(title: string, ctxFactory: CtxFactory) return new UnitTest(title, ctxFactory); } -class UnitTest { +export class UnitTest { public readonly title: string; private readonly ctxFactory: CtxFactory; From ba0b41e8c0b388a673eedc236a20bade7bcf2e71 Mon Sep 17 00:00:00 2001 From: Artyom Veremeenko Date: Tue, 2 Apr 2024 14:49:55 +0300 Subject: [PATCH 048/148] test(rebasable permit): move the rest tests from core stethpermit.test.js --- contracts/token/ERC20RebasablePermit.sol | 59 +----- test/token/ERC20Permit.unit.test.ts | 224 +++++++++++++++++++++-- utils/testing/permit-helpers.ts | 38 +++- 3 files changed, 245 insertions(+), 76 deletions(-) diff --git a/contracts/token/ERC20RebasablePermit.sol b/contracts/token/ERC20RebasablePermit.sol index 6321672d..22353a02 100644 --- a/contracts/token/ERC20RebasablePermit.sol +++ b/contracts/token/ERC20RebasablePermit.sol @@ -3,60 +3,13 @@ pragma solidity 0.8.10; -// import {UnstructuredStorage} from "@aragon/os/contracts/common/UnstructuredStorage.sol"; - -// import {SignatureUtils} from "../common/lib/SignatureUtils.sol"; -// import {IEIP712ERC20Rebasable} from "../lib/IEIP712ERC20Rebasable.sol"; - import {UnstructuredStorage} from "./UnstructuredStorage.sol"; import {ERC20Rebasable} from "./ERC20Rebasable.sol"; import {EIP712} from "@openzeppelin/contracts-v4.9/utils/cryptography/EIP712.sol"; +import {IERC2612} from "@openzeppelin/contracts-v4.9/interfaces/IERC2612.sol"; import {SignatureChecker} from "../lib/SignatureChecker.sol"; -/** - * @dev Interface of the ERC20 Permit extension allowing approvals to be made via signatures, as defined in - * https://eips.ethereum.org/EIPS/eip-2612[EIP-2612]. - * - * Adds the {permit} method, which can be used to change an account's ERC20 allowance (see {IERC20-allowance}) by - * presenting a message signed by the account. By not relying on {IERC20-approve}, the token holder account doesn't - * need to send a transaction, and thus is not required to hold Ether at all. - */ -interface IERC2612 { - /** - * @dev Sets `value` as the allowance of `spender` over ``owner``'s tokens, - * given ``owner``'s signed approval. - * Emits an {Approval} event. - * - * Requirements: - * - * - `spender` cannot be the zero address. - * - `deadline` must be a timestamp in the future. - * - `v`, `r` and `s` must be a valid `secp256k1` signature from `owner` - * over the EIP712-formatted function arguments. - * - the signature must use ``owner``'s current nonce (see {nonces}). - */ - function permit( - address owner, address spender, uint256 value, uint256 deadline, uint8 v, bytes32 r, bytes32 s - ) external; - - /** - * @dev Returns the current nonce for `owner`. This value must be - * included whenever a signature is generated for {permit}. - * - * Every successful call to {permit} increases ``owner``'s nonce by one. This - * prevents a signature from being used multiple times. - */ - function nonces(address owner) external view returns (uint256); - - /** - * @dev Returns the domain separator used in the encoding of the signature for {permit}, as defined by {EIP712}. - */ - // solhint-disable-next-line func-name-mixedcase - function DOMAIN_SEPARATOR() external view returns (bytes32); -} - - contract ERC20RebasablePermit is IERC2612, ERC20Rebasable, EIP712 { using UnstructuredStorage for bytes32; @@ -65,6 +18,8 @@ contract ERC20RebasablePermit is IERC2612, ERC20Rebasable, EIP712 { */ mapping(address => uint256) internal noncesByAddress; + // TODO: outline structured storage used because at least EIP712 uses it + /** * @dev Typehash constant for ERC-2612 (Permit) * @@ -73,11 +28,9 @@ contract ERC20RebasablePermit is IERC2612, ERC20Rebasable, EIP712 { bytes32 internal constant PERMIT_TYPEHASH = 0x6e71edae12b1b97f4d1f60370fef10105fa2faae0126114a169c64845d6126c9; - // TODO: outline structured storage used because at least EIP712 uses it - // TODO: use custom errors - /// @param name_ The name of the token /// @param symbol_ The symbol of the token + /// @param version_ The current major version of the signing domain (aka token version) /// @param decimals_ The decimals places of the token /// @param wrappedToken_ address of the ERC20 token to wrap /// @param tokenRateOracle_ address of oracle that returns tokens rate @@ -85,14 +38,14 @@ contract ERC20RebasablePermit is IERC2612, ERC20Rebasable, EIP712 { constructor( string memory name_, string memory symbol_, - // TODO: pass signing domain version + string memory version_, uint8 decimals_, address wrappedToken_, address tokenRateOracle_, address bridge_ ) ERC20Rebasable(name_, symbol_, decimals_, wrappedToken_, tokenRateOracle_, bridge_) - EIP712(name_, "2") + EIP712(name_, version_) { } diff --git a/test/token/ERC20Permit.unit.test.ts b/test/token/ERC20Permit.unit.test.ts index a19bf940..9aedeada 100644 --- a/test/token/ERC20Permit.unit.test.ts +++ b/test/token/ERC20Permit.unit.test.ts @@ -2,7 +2,8 @@ import hre from "hardhat"; import { assert } from "chai"; import { unit, UnitTest } from "../../utils/testing"; import { wei } from "../../utils/wei"; -import { makeDomainSeparator, signPermit } from "../../utils/testing/permit-helpers"; +import { makeDomainSeparator, signPermit, calculateTransferAuthorizationDigest, signEOAorEIP1271 } from "../../utils/testing/permit-helpers"; +import testing from "../../utils/testing"; import { ERC20Bridged__factory, @@ -17,7 +18,8 @@ import { BigNumber } from "ethers"; type ContextType = Awaited>> const TOKEN_NAME = 'Liquid staked Ether 2.0' -const SIGNING_DOMAIN_VERSION = '2' +const SIGNING_DOMAIN_VERSION = '2' // aka token version, used in signing permit +const MAX_UINT256 = '0xffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff' // derived from mnemonic: want believe mosquito cat design route voice cause gold benefit gospel bulk often attitude rural const ACCOUNTS_AND_KEYS = [ @@ -67,14 +69,10 @@ function permitTestsSuit(unitInstance: UnitTest) assert.isDefined(hre.network.config.chainId) assert.equal(chainId.toNumber(), getChainId()) assert.equal(verifyingContract, token.address) - - const domainSeparator = makeDomainSeparator(TOKEN_NAME, SIGNING_DOMAIN_VERSION, chainId, token.address) - assert.equal(makeDomainSeparator(name, version, chainId, verifyingContract), domainSeparator) }) .test('DOMAIN_SEPARATOR() is correct', async (ctx) => { const token = ctx.contracts.rebasableProxied - const domainSeparator = makeDomainSeparator(TOKEN_NAME, SIGNING_DOMAIN_VERSION, getChainId(), token.address) assert.equal(await ctx.contracts.rebasableProxied.DOMAIN_SEPARATOR(), domainSeparator) }) @@ -88,21 +86,20 @@ function permitTestsSuit(unitInstance: UnitTest) // on behalf, and sign with Alice's key let nonce = 0 const charlie = ctx.accounts.user2 - const charlieSigner = hre.ethers.provider.getSigner(charlie.address) + // const charlieSigner = hre.ethers.provider.getSigner(charlie.address) - const domainSeparator = makeDomainSeparator(TOKEN_NAME, SIGNING_DOMAIN_VERSION, getChainId(), token.address) - let { v, r, s } = await signPermit(owner, spender.address, value, deadline, nonce, domainSeparator) + let { v, r, s } = await signPermit(owner.address, owner, spender.address, value, deadline, nonce, ctx.domainSeparator) // check that the allowance is initially zero assert.equalBN(await token.allowance(owner.address, spender.address), BigNumber.from(0)) // check that the next nonce expected is zero assert.equalBN(await token.nonces(owner.address), BigNumber.from(0)) // check domain separator - assert.equal(await token.DOMAIN_SEPARATOR(), domainSeparator) + assert.equal(await token.DOMAIN_SEPARATOR(), ctx.domainSeparator) // a third-party, Charlie (not Alice) submits the permit // TODO: handle unpredictable gas limit somehow better than setting it to a random constant - const tx = await token.connect(charlieSigner) + const tx = await token.connect(charlie) .permit(owner.address, spender.address, value, deadline, v, r, s, { gasLimit: '0xffffff' }) // check that allowance is updated @@ -114,10 +111,10 @@ function permitTestsSuit(unitInstance: UnitTest) // increment nonce nonce = 1 value = 4e5 - ;({ v, r, s } = await signPermit(owner, spender.address, value, deadline, nonce, domainSeparator)) + ;({ v, r, s } = await signPermit(owner.address, owner, spender.address, value, deadline, nonce, ctx.domainSeparator)) // submit the permit - const tx2 = await token.connect(charlieSigner).permit(owner.address, spender.address, value, deadline, v, r, s) + const tx2 = await token.connect(charlie).permit(owner.address, spender.address, value, deadline, v, r, s) // check that allowance is updated assert.equalBN(await token.allowance(owner.address, spender.address), BigNumber.from(value)) @@ -125,6 +122,202 @@ function permitTestsSuit(unitInstance: UnitTest) assert.equalBN(await token.nonces(owner.address), BigNumber.from(2)) }) + + .test('reverts if the signature does not match given parameters', async (ctx) => { + const { owner, spender, value, nonce, deadline } = ctx.permitParams + const token = ctx.contracts.rebasableProxied + const charlie = ctx.accounts.user2 + + // create a signed permit + const { v, r, s } = await signPermit(owner.address, owner, spender.address, value, deadline, nonce, ctx.domainSeparator) + + // try to cheat by claiming the approved amount + 1 + await assert.revertsWith( + token.connect(charlie).permit( + owner.address, + spender.address, + value + 1, // pass more than signed value + deadline, + v, + r, + s, + ), + 'ErrorInvalidSignature()' + ) + + // check that msg is incorrect even if claim the approved amount - 1 + await assert.revertsWith( + token.connect(charlie).permit( + owner.address, + spender.address, + value - 1, // pass less than signed + deadline, + v, + r, + s, + ), + 'ErrorInvalidSignature()' + ) + }) + + .test('reverts if the signature is not signed with the right key', async (ctx) => { + const { owner, spender, value, nonce, deadline } = ctx.permitParams + const token = ctx.contracts.rebasableProxied + const spenderSigner = await hre.ethers.getSigner(spender.address) + const charlie = ctx.accounts.user2 + + // create a signed permit to grant Bob permission to spend + // Alice's funds on behalf, but sign with Bob's key instead of Alice's + const { v, r, s } = await signPermit(owner.address, spender, spender.address, value, deadline, nonce, ctx.domainSeparator) + + // try to cheat by submitting the permit that is signed by a + // wrong person + await assert.revertsWith( + token.connect(charlie).permit(owner.address, spender.address, value, deadline, v, r, s), + 'ErrorInvalidSignature()' + ) + + await testing.impersonate(spender.address) + await testing.setBalance(spender.address, wei.toBigNumber(wei`10 ether`)) + + // even Bob himself can't call permit with the invalid sig + await assert.revertsWith( + token.connect(spenderSigner).permit(owner.address, spender.address, value, deadline, v, r, s), + 'ErrorInvalidSignature()' + ) + }) + + .test('reverts if the permit is expired', async (ctx) => { + const token = ctx.contracts.rebasableProxied + const { owner, spender, value, nonce } = ctx.permitParams + const charlie = ctx.accounts.user2 + + // create a signed permit that already invalid + const deadline = ((await hre.ethers.provider.getBlock('latest')).timestamp - 1).toString() + const { v, r, s } = await signPermit(owner.address, owner, spender.address, value, deadline, nonce, ctx.domainSeparator) + + // try to submit the permit that is expired + await assert.revertsWith( + token.connect(charlie).permit(owner.address, spender.address, value, deadline, v, r, s, { gasLimit: '0xffffff' }), + 'ErrorDeadlineExpired()' + ) + + { + // create a signed permit that valid for 1 minute (approximately) + const deadline1min = ((await hre.ethers.provider.getBlock('latest')).timestamp + 60).toString() + const { v, r, s } = await signPermit(owner.address, owner, spender.address, value, deadline1min, nonce, ctx.domainSeparator) + const tx = await token.connect(charlie).permit(owner.address, spender.address, value, deadline1min, v, r, s) + + assert.equalBN(await token.nonces(owner.address), BigNumber.from(1)) + assert.emits(token, tx, 'Approval', [ owner, spender, BigNumber.from(value) ]) + } + }) + + .test('reverts if the nonce given does not match the next nonce expected', async (ctx) => { + const token = ctx.contracts.rebasableProxied + const { owner, spender, value, deadline } = ctx.permitParams + const charlie = ctx.accounts.user2 + const nonce = 1 + // create a signed permit + const { v, r, s } = await signPermit(owner.address, owner, spender.address, value, deadline, nonce, ctx.domainSeparator) + // check that the next nonce expected is 0, not 1 + assert.equalBN(await token.nonces(owner.address), BigNumber.from(0)) + + // try to submit the permit + await assert.revertsWith( + token.connect(charlie).permit(owner.address, spender.address, value, deadline, v, r, s), + 'ErrorInvalidSignature()' + ) + }) + + .test('reverts if the permit has already been used', async (ctx) => { + const token = ctx.contracts.rebasableProxied + const { owner, spender, value, nonce, deadline } = ctx.permitParams + const charlie = ctx.accounts.user2 + // create a signed permit + const { v, r, s } = await signPermit(owner.address, owner, spender.address, value, deadline, nonce, ctx.domainSeparator) + + // submit the permit + await token.connect(charlie).permit(owner.address, spender.address, value, deadline, v, r, s) + + // try to submit the permit again + await assert.revertsWith( + token.connect(charlie).permit(owner.address, spender.address, value, deadline, v, r, s), + 'ErrorInvalidSignature()' + ) + + await testing.impersonate(owner.address) + await testing.setBalance(owner.address, wei.toBigNumber(wei`10 ether`)) + + // try to submit the permit again from Alice herself + await assert.revertsWith( + token.connect(await hre.ethers.getSigner(owner.address)).permit(owner.address, spender.address, value, deadline, v, r, s), + 'ErrorInvalidSignature()' + ) + }) + + .test('reverts if the permit has a nonce that has already been used by the signer', async (ctx) => { + const token = ctx.contracts.rebasableProxied + const { owner, spender, value, nonce, deadline } = ctx.permitParams + const charlie = ctx.accounts.user2 + // create a signed permit + const permit = await signPermit(owner.address, owner, spender.address, value, deadline, nonce, ctx.domainSeparator) + + // submit the permit + await token.connect(charlie).permit(owner.address, spender.address, value, deadline, permit.v, permit.r, permit.s) + + // create another signed permit with the same nonce, but + // with different parameters + const permit2 = await signPermit(owner.address, owner, spender.address, 1e6, deadline, nonce, ctx.domainSeparator) + + // try to submit the permit again + await assert.revertsWith( + token.connect(charlie).permit(owner.address, spender.address, 1e6, deadline, permit2.v, permit2.r, permit2.s), + 'ErrorInvalidSignature()' + ) + }) + + .test('reverts if the permit includes invalid approval parameters', async (ctx) => { + const token = ctx.contracts.rebasableProxied + const { owner, value, nonce, deadline } = ctx.permitParams + const charlie = ctx.accounts.user2 + // create a signed permit that attempts to grant allowance to the + // zero address + const spender = hre.ethers.constants.AddressZero + const { v, r, s } = await signPermit(owner.address, owner, spender, value, deadline, nonce, ctx.domainSeparator) + + // try to submit the permit with invalid approval parameters + await assert.revertsWith( + token.connect(charlie).permit(owner.address, spender, value, deadline, v, r, s), + 'ErrorAccountIsZeroAddress()' + ) + }) + + .test('reverts if the permit is not for an approval', async (ctx) => { + const token = ctx.contracts.rebasableProxied + const charlie = ctx.accounts.user2 + const { owner: from, spender: to, value, deadline: validBefore } = ctx.permitParams + // create a signed permit for a transfer + const validAfter = '0' + const nonce = '0x1234567890abcdef1234567890abcdef1234567890abcdef1234567890abcdef' + const digest = calculateTransferAuthorizationDigest( + from.address, + to.address, + value, + validAfter, + validBefore, + nonce, + ctx.domainSeparator + ) + const { v, r, s } = await signEOAorEIP1271(digest, from) + + // try to submit the transfer permit + await assert.revertsWith( + token.connect(charlie).permit(from.address, to.address, value, validBefore, v, r, s), + 'ErrorInvalidSignature()' + ) + }) + .run(); } @@ -164,6 +357,7 @@ function ctxFactoryFactory(signingAccountsFuncFactory: typeof getAccountsEIP1271 const rebasableTokenImpl = await new ERC20RebasablePermit__factory(deployer).deploy( name, symbol, + SIGNING_DOMAIN_VERSION, decimalsToSet, wrappedToken.address, tokenRateOracle.address, @@ -195,7 +389,6 @@ function ctxFactoryFactory(signingAccountsFuncFactory: typeof getAccountsEIP1271 await rebasableProxied.connect(owner).mintShares(holder.address, premintShares); const { alice, bob } = await signingAccountsFuncFactory(); - const MAX_UINT256 = '0xffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff' return { accounts: { deployer, owner, recipient, spender, holder, stranger, zero, user1, user2 }, constants: { name, symbol, decimalsToSet, decimals, premintShares, premintTokens, rate }, @@ -206,7 +399,8 @@ function ctxFactoryFactory(signingAccountsFuncFactory: typeof getAccountsEIP1271 value: 6e6, nonce: 0, deadline: MAX_UINT256, - } + }, + domainSeparator: makeDomainSeparator(TOKEN_NAME, SIGNING_DOMAIN_VERSION, getChainId(), rebasableProxied.address), }; } } diff --git a/utils/testing/permit-helpers.ts b/utils/testing/permit-helpers.ts index 06bd5617..16531c79 100644 --- a/utils/testing/permit-helpers.ts +++ b/utils/testing/permit-helpers.ts @@ -7,6 +7,9 @@ import { ecsign as ecSignBuf } from "ethereumjs-util"; const PERMIT_TYPE_HASH = streccak( 'Permit(address owner,address spender,uint256 value,uint256 nonce,uint256 deadline)' ) +const TRANSFER_WITH_AUTHORIZATION_TYPE_HASH = streccak( + 'TransferWithAuthorization(address from,address to,uint256 value,uint256 validAfter,uint256 validBefore,bytes32 nonce)' +) interface Eip1271Contract { address: string; @@ -19,12 +22,18 @@ async function signEOA(digest: string, account: ExternallyOwnedAccount) { return ecSign(digest, account.privateKey) } - async function signEIP1271(digest: string, eip1271Contract: Eip1271Contract) { const sig = await eip1271Contract.sign(digest) return { v: sig.v, r: sig.r, s: sig.s } } +export async function signEOAorEIP1271(digest: string, signer: Eip1271Contract | ExternallyOwnedAccount) { + if (signer.hasOwnProperty('sign')) { + return await signEIP1271(digest, signer as Eip1271Contract); + } else { + return await signEOA(digest, signer as ExternallyOwnedAccount); + } +} export function makeDomainSeparator(name: string, version: string, chainId: BigNumberish, verifyingContract: string) { return keccak256( @@ -41,13 +50,17 @@ export function makeDomainSeparator(name: string, version: string, chainId: BigN ) } -export async function signPermit(owner: ExternallyOwnedAccount | Eip1271Contract, spender: string, value: number, deadline: string, nonce: number, domainSeparator: string) { - const digest = calculatePermitDigest(owner.address, spender, value, nonce, deadline, domainSeparator) - if (owner.hasOwnProperty('sign')) { - return await signEIP1271(digest, owner as Eip1271Contract); - } else { - return await signEOA(digest, owner as ExternallyOwnedAccount); - } +export async function signPermit( + owner: string, + signer: ExternallyOwnedAccount | Eip1271Contract, + spender: string, + value: number, + deadline: string, + nonce: number, + domainSeparator: string +) { + const digest = calculatePermitDigest(owner, spender, value, nonce, deadline, domainSeparator) + return await signEOAorEIP1271(digest, signer) } export function calculatePermitDigest(owner: string, spender: string, value: number, nonce: number, deadline: string, domainSeparator: string) { @@ -59,6 +72,15 @@ export function calculatePermitDigest(owner: string, spender: string, value: num ) } +export function calculateTransferAuthorizationDigest(from: string, to: string, value: number, validAfter: string, validBefore: string, nonce: string, domainSeparator: string) { + return calculateEIP712Digest( + domainSeparator, + TRANSFER_WITH_AUTHORIZATION_TYPE_HASH, + ['address', 'address', 'uint256', 'uint256', 'uint256', 'bytes32'], + [from, to, value, validAfter, validBefore, nonce] + ) +} + function calculateEIP712Digest(domainSeparator: string, typeHash: string, types: string[], parameters: unknown[]) { const structHash = keccak256(defaultAbiCoder.encode(['bytes32', ...types], [typeHash, ...parameters])); const data = '0x1901' + strip0x(domainSeparator) + strip0x(structHash) From 18a1dc5adbf969ba291f0be821dd76c265b55f2f Mon Sep 17 00:00:00 2001 From: Anton Kovalchuk Date: Tue, 2 Apr 2024 15:37:03 +0200 Subject: [PATCH 049/148] add e2e tests for oracle --- .env.example | 1 + scripts/optimism/deploy-oracle.ts | 36 ++++--- test/optimism/pushingTokenRate.e2e.test.ts | 96 +++++++++++++++++++ .../pushingTokenRate.integration.test.ts | 17 ++-- utils/testing/env.ts | 3 + 5 files changed, 133 insertions(+), 20 deletions(-) create mode 100644 test/optimism/pushingTokenRate.e2e.test.ts diff --git a/.env.example b/.env.example index fbf40032..4d4ffc94 100644 --- a/.env.example +++ b/.env.example @@ -77,6 +77,7 @@ TESTING_ARB_L2_GATEWAY_ROUTER=0x57f54f87C44d816f60b92864e23b8c0897D4d81D TESTING_OPT_NETWORK= TESTING_OPT_L1_TOKEN=0xaF8a2F0aE374b03376155BF745A3421Dac711C12 TESTING_OPT_L2_TOKEN=0xAED5F9aaF167923D34174b8E636aaF040A11f6F7 +TESTING_OPT_L1_TOKEN_RATE_NOTIFIER=0x554f2C7D58522c050d38Ebea4FF072ED7C4e61cb TESTING_OPT_L1_REBASABLE_TOKEN=0xB82381A3fBD3FaFA77B3a7bE693342618240067b TESTING_OPT_L2_REBASABLE_TOKEN=0x6696Cb7bb602FC744254Ad9E07EfC474FBF78857 TESTING_OPT_L2_TOKEN_RATE_ORACLE=0x8ea513d1e5Be31fb5FC2f2971897594720de9E70 diff --git a/scripts/optimism/deploy-oracle.ts b/scripts/optimism/deploy-oracle.ts index d5ef78d1..bf5e38fb 100644 --- a/scripts/optimism/deploy-oracle.ts +++ b/scripts/optimism/deploy-oracle.ts @@ -3,6 +3,7 @@ import prompt from "../../utils/prompt"; import network from "../../utils/network"; import optimism from "../../utils/optimism"; import deploymentOracle from "../../utils/deployment"; +import { TokenRateNotifier__factory } from "../../typechain"; async function main() { const networkName = env.network(); @@ -18,41 +19,52 @@ async function main() { } ); - const deploymentConfig = deploymentOracle.loadMultiChainDeploymentConfig(); + const l1Token = env.address("TOKEN") + const l1Admin = env.address("L1_PROXY_ADMIN"); + const l2Admin = env.address("L2_PROXY_ADMIN"); const [l1DeployScript, l2DeployScript] = await optimism .deploymentOracle(networkName, { logger: console }) .oracleDeployScript( - deploymentConfig.token, + l1Token, { deployer: ethDeployer, admins: { - proxy: deploymentConfig.l1.proxyAdmin, + proxy: l1Admin, bridge: ethDeployer.address, }, }, { deployer: optDeployer, admins: { - proxy: deploymentConfig.l2.proxyAdmin, + proxy: l2Admin, bridge: optDeployer.address, }, } ); - await deploymentOracle.printMultiChainDeploymentConfig( - "Deploy Optimism Bridge", - ethDeployer, - optDeployer, - deploymentConfig, - l1DeployScript, - l2DeployScript - ); +// await deploymentOracle.printMultiChainDeploymentConfig( +// "Deploy Token Rate Oracle", +// ethDeployer, +// optDeployer, +// deploymentConfig, +// l1DeployScript, +// l2DeployScript +// ); await prompt.proceed(); await l1DeployScript.run(); await l2DeployScript.run(); + + /// setup, add observer + const tokenRateNotifier = TokenRateNotifier__factory.connect( + l1DeployScript.tokenRateNotifierImplAddress, + ethDeployer + ); + await tokenRateNotifier + .connect(ethDeployer) + .addObserver(l1DeployScript.opStackTokenRatePusherImplAddress); } main().catch((error) => { diff --git a/test/optimism/pushingTokenRate.e2e.test.ts b/test/optimism/pushingTokenRate.e2e.test.ts new file mode 100644 index 00000000..06fb2fb5 --- /dev/null +++ b/test/optimism/pushingTokenRate.e2e.test.ts @@ -0,0 +1,96 @@ +import { assert } from "chai"; +import env from "../../utils/env"; +import network, { SignerOrProvider } from "../../utils/network"; +import testingUtils, { scenario } from "../../utils/testing"; +import { + ERC20WrapperStub__factory, + TokenRateNotifier__factory, + TokenRateOracle__factory +} from "../../typechain"; + +scenario("Optimism :: Push token rate to Oracle E2E test", ctxFactory) + + .step("Push Token Rate", async (ctx) => { + await ctx.tokenRateNotifier + .connect(ctx.l1Tester) + .handlePostTokenRebase(1, 2, 3, 4, 5, 6, 7); + }) + + .step("Receive token rate", async (ctx) => { + const tokenRate = await ctx.l1Token.stEthPerToken(); + + const answer = await ctx.tokenRateOracle.latestAnswer(); + assert.equalBN(answer, tokenRate); + + const [ + , + latestRoundDataAnswer, + , + , + ] = await ctx.tokenRateOracle.latestRoundData(); + assert.equalBN(latestRoundDataAnswer, tokenRate); + }) + + .run(); + +async function ctxFactory() { + const testingSetup = await getE2ETestSetup(); + + return { + l1Tester: testingSetup.l1Tester, + l2Tester: testingSetup.l2Tester, + l1Provider: testingSetup.l1Provider, + l2Provider: testingSetup.l2Provider, + l1Token: testingSetup.l1Token, + tokenRateNotifier: testingSetup.tokenRateNotifier, + tokenRateOracle: testingSetup.tokenRateOracle + }; +} + +async function getE2ETestSetup() { + const testerPrivateKey = testingUtils.env.TESTING_PRIVATE_KEY(); + const networkName = env.network("TESTING_OPT_NETWORK", "sepolia"); + + const ethOptNetworks = network.multichain(["eth", "opt"], networkName); + + const [ethProvider, optProvider] = ethOptNetworks.getProviders({ + forking: false, + }); + const [l1Tester, l2Tester] = ethOptNetworks.getSigners(testerPrivateKey, { + forking: false, + }); + + const contracts = await loadDeployedContracts(l1Tester, l2Tester); + + // await printLoadedTestConfig(networkName, bridgeContracts, l1Tester); + + return { + l1Tester, + l2Tester, + l1Provider: ethProvider, + l2Provider: optProvider, + ...contracts, + }; +} + +async function loadDeployedContracts( + l1SignerOrProvider: SignerOrProvider, + l2SignerOrProvider: SignerOrProvider +) { + return { + l1Token: ERC20WrapperStub__factory.connect( + testingUtils.env.OPT_L1_TOKEN(), + l1SignerOrProvider + ), + tokenRateNotifier: TokenRateNotifier__factory.connect( + testingUtils.env.OPT_L1_TOKEN_RATE_NOTIFIER(), + l1SignerOrProvider + ), + tokenRateOracle: TokenRateOracle__factory.connect( + testingUtils.env.OPT_L2_TOKEN_RATE_ORACLE(), + l2SignerOrProvider + ), + l1SignerOrProvider, + l2SignerOrProvider + }; +} diff --git a/test/optimism/pushingTokenRate.integration.test.ts b/test/optimism/pushingTokenRate.integration.test.ts index 50f8211f..36f93e83 100644 --- a/test/optimism/pushingTokenRate.integration.test.ts +++ b/test/optimism/pushingTokenRate.integration.test.ts @@ -8,6 +8,7 @@ import testing, { scenario } from "../../utils/testing"; import deploymentOracle from "../../utils/optimism/deploymentOracle"; import { getBridgeExecutorParams } from "../../utils/bridge-executor"; import { JsonRpcProvider } from "@ethersproject/providers"; +import { BigNumber } from "ethers"; import { ERC20BridgedStub__factory, ERC20WrapperStub__factory, @@ -55,7 +56,7 @@ scenario("Optimism :: Token Rate Oracle integration test", ctxFactory) ]); }) - .step("finalize pushing rate", async (ctx) => { + .step("Finalize pushing rate", async (ctx) => { const { opTokenRatePusher, tokenRateOracle, @@ -91,15 +92,15 @@ scenario("Optimism :: Token Rate Oracle integration test", ctxFactory) assert.equalBN(answer, tokenRate); const [ - answer1, - answer2, - answer3, - answer4, - answer5 + , + tokenRateAnswer, + , + updatedAt, + ] = await tokenRateOracle.latestRoundData(); - assert.equalBN(answer2, tokenRate); - assert.equalBN(answer4, blockTimestampStr); + assert.equalBN(tokenRateAnswer, tokenRate); + assert.equalBN(updatedAt, blockTimestampStr); }) .run(); diff --git a/utils/testing/env.ts b/utils/testing/env.ts index 1a00941e..0f61419e 100644 --- a/utils/testing/env.ts +++ b/utils/testing/env.ts @@ -33,6 +33,9 @@ export default { OPT_L2_TOKEN() { return env.address("TESTING_OPT_L2_TOKEN"); }, + OPT_L1_TOKEN_RATE_NOTIFIER() { + return env.address("TESTING_OPT_L1_TOKEN_RATE_NOTIFIER"); + }, OPT_L2_TOKEN_RATE_ORACLE() { return env.address("TESTING_OPT_L2_TOKEN_RATE_ORACLE"); }, From 5f86091943e1dbd3df9cb1eb92b1ecd060296641 Mon Sep 17 00:00:00 2001 From: Anton Kovalchuk Date: Wed, 3 Apr 2024 09:53:59 +0200 Subject: [PATCH 050/148] remove increaseAllowance and decreaseAllowance from tokens --- contracts/arbitrum/README.md | 30 ----- contracts/optimism/README.md | 30 ----- contracts/token/ERC20Core.sol | 32 ----- contracts/token/ERC20Rebasable.sol | 32 ----- test/token/ERC20Bridged.unit.test.ts | 169 ------------------------- test/token/ERC20Rebasable.unit.test.ts | 169 ------------------------- 6 files changed, 462 deletions(-) diff --git a/contracts/arbitrum/README.md b/contracts/arbitrum/README.md index cf76c5c1..26f3c4fb 100644 --- a/contracts/arbitrum/README.md +++ b/contracts/arbitrum/README.md @@ -695,36 +695,6 @@ Returns a `bool` value indicating whether the operation succeeded. Transfers `amount` of token from the `from_` account to `to_` using the allowance mechanism. `amount_` is then deducted from the caller's allowance. Returns a `bool` value indicating whether the operation succeed. -#### `increaseAllowance(address,uint256)` - -> **Visibility:**     `external` -> -> **Returns**        `(bool)` -> -> **Arguments:** -> -> - **`spender_`** - an address of the tokens spender -> - **`addedValue_`** - a number to increase allowance -> -> **Emits:** `Approval(address indexed owner, address indexed spender, uint256 value)` - -Atomically increases the allowance granted to `spender` by the caller. Returns a `bool` value indicating whether the operation succeed. - -#### `decreaseAllowance(address,uint256)` - -> **Visibility:**     `external` -> -> **Returns**        `(bool)` -> -> **Arguments:** -> -> - **`spender_`** - an address of the tokens spender -> - **`subtractedValue_`** - a number to decrease allowance -> -> **Emits:** `Approval(address indexed owner, address indexed spender, uint256 value)` - -Atomically decreases the allowance granted to `spender` by the caller. Returns a `bool` value indicating whether the operation succeed. - ## `ERC20Bridged` **Implements:** [`IERC20Bridged`](https://github.com/lidofinance/lido-l2/blob/main/contracts/token/interfaces/IERC20Bridged.sol) diff --git a/contracts/optimism/README.md b/contracts/optimism/README.md index 598f7b99..954556d6 100644 --- a/contracts/optimism/README.md +++ b/contracts/optimism/README.md @@ -512,36 +512,6 @@ Returns a `bool` value indicating whether the operation succeeded. Transfers `amount` of token from the `from_` account to `to_` using the allowance mechanism. `amount_` is then deducted from the caller's allowance. Returns a `bool` value indicating whether the operation succeed. -#### `increaseAllowance(address,uint256)` - -> **Visibility:**     `external` -> -> **Returns**        `(bool)` -> -> **Arguments:** -> -> - **`spender_`** - an address of the tokens spender -> - **`addedValue_`** - a number to increase allowance -> -> **Emits:** `Approval(address indexed owner, address indexed spender, uint256 value)` - -Atomically increases the allowance granted to `spender` by the caller. Returns a `bool` value indicating whether the operation succeed. - -#### `decreaseAllowance(address,uint256)` - -> **Visibility:**     `external` -> -> **Returns**        `(bool)` -> -> **Arguments:** -> -> - **`spender_`** - an address of the tokens spender -> - **`subtractedValue_`** - a number to decrease allowance -> -> **Emits:** `Approval(address indexed owner, address indexed spender, uint256 value)` - -Atomically decreases the allowance granted to `spender` by the caller. Returns a `bool` value indicating whether the operation succeed. - ## `ERC20Bridged` **Implements:** [`IERC20Bridged`](https://github.com/lidofinance/lido-l2/blob/main/contracts/token/interfaces/IERC20Bridged.sol) diff --git a/contracts/token/ERC20Core.sol b/contracts/token/ERC20Core.sol index bf4e67db..354c28bd 100644 --- a/contracts/token/ERC20Core.sol +++ b/contracts/token/ERC20Core.sol @@ -44,38 +44,6 @@ contract ERC20Core is IERC20 { return true; } - /// @notice Atomically increases the allowance granted to spender by the caller. - /// @param spender_ An address of the tokens spender - /// @param addedValue_ An amount to increase the allowance - function increaseAllowance(address spender_, uint256 addedValue_) - external - returns (bool) - { - _approve( - msg.sender, - spender_, - allowance[msg.sender][spender_] + addedValue_ - ); - return true; - } - - /// @notice Atomically decreases the allowance granted to spender by the caller. - /// @param spender_ An address of the tokens spender - /// @param subtractedValue_ An amount to decrease the allowance - function decreaseAllowance(address spender_, uint256 subtractedValue_) - external - returns (bool) - { - uint256 currentAllowance = allowance[msg.sender][spender_]; - if (currentAllowance < subtractedValue_) { - revert ErrorDecreasedAllowanceBelowZero(); - } - unchecked { - _approve(msg.sender, spender_, currentAllowance - subtractedValue_); - } - return true; - } - /// @dev Moves amount_ of tokens from sender_ to recipient_ /// @param from_ An address of the sender of the tokens /// @param to_ An address of the recipient of the tokens diff --git a/contracts/token/ERC20Rebasable.sol b/contracts/token/ERC20Rebasable.sol index 81c4eb09..ba69ba13 100644 --- a/contracts/token/ERC20Rebasable.sol +++ b/contracts/token/ERC20Rebasable.sol @@ -161,38 +161,6 @@ contract ERC20Rebasable is IERC20, IERC20Wrapper, IERC20BridgedShares, ERC20Meta return true; } - /// @notice Atomically increases the allowance granted to spender by the caller. - /// @param spender_ An address of the tokens spender - /// @param addedValue_ An amount to increase the allowance - function increaseAllowance(address spender_, uint256 addedValue_) - external - returns (bool) - { - _approve( - msg.sender, - spender_, - _getTokenAllowance()[msg.sender][spender_] + addedValue_ - ); - return true; - } - - /// @notice Atomically decreases the allowance granted to spender by the caller. - /// @param spender_ An address of the tokens spender - /// @param subtractedValue_ An amount to decrease the allowance - function decreaseAllowance(address spender_, uint256 subtractedValue_) - external - returns (bool) - { - uint256 currentAllowance = _getTokenAllowance()[msg.sender][spender_]; - if (currentAllowance < subtractedValue_) { - revert ErrorDecreasedAllowanceBelowZero(); - } - unchecked { - _approve(msg.sender, spender_, currentAllowance - subtractedValue_); - } - return true; - } - function _getTokenAllowance() internal pure returns (mapping(address => mapping(address => uint256)) storage) { return TOKEN_ALLOWANCE_POSITION.storageMapAddressMapAddressUint256(); } diff --git a/test/token/ERC20Bridged.unit.test.ts b/test/token/ERC20Bridged.unit.test.ts index 2ec65bfa..a3359635 100644 --- a/test/token/ERC20Bridged.unit.test.ts +++ b/test/token/ERC20Bridged.unit.test.ts @@ -306,175 +306,6 @@ unit("ERC20Bridged", ctxFactory) ); }) - .test("increaseAllowance() :: initial allowance is zero", async (ctx) => { - const { erc20Bridged } = ctx; - const { holder, spender } = ctx.accounts; - - // validate allowance before increasing - assert.equalBN( - await erc20Bridged.allowance(holder.address, spender.address), - "0" - ); - - const allowanceIncrease = wei`1 ether`; - - // increase allowance - const tx = await erc20Bridged.increaseAllowance( - spender.address, - allowanceIncrease - ); - - // validate Approval event was emitted - await assert.emits(erc20Bridged, tx, "Approval", [ - holder.address, - spender.address, - allowanceIncrease, - ]); - - // validate allowance was updated correctly - assert.equalBN( - await erc20Bridged.allowance(holder.address, spender.address), - allowanceIncrease - ); - }) - - .test("increaseAllowance() :: initial allowance is not zero", async (ctx) => { - const { erc20Bridged } = ctx; - const { holder, spender } = ctx.accounts; - - const initialAllowance = wei`2 ether`; - - // set initial allowance - await erc20Bridged.approve(spender.address, initialAllowance); - - // validate allowance before increasing - assert.equalBN( - await erc20Bridged.allowance(holder.address, spender.address), - initialAllowance - ); - - const allowanceIncrease = wei`1 ether`; - - // increase allowance - const tx = await erc20Bridged.increaseAllowance( - spender.address, - allowanceIncrease - ); - - const expectedAllowance = wei - .toBigNumber(initialAllowance) - .add(allowanceIncrease); - - // validate Approval event was emitted - await assert.emits(erc20Bridged, tx, "Approval", [ - holder.address, - spender.address, - expectedAllowance, - ]); - - // validate allowance was updated correctly - assert.equalBN( - await erc20Bridged.allowance(holder.address, spender.address), - expectedAllowance - ); - }) - - .test("increaseAllowance() :: the increase is not zero", async (ctx) => { - const { erc20Bridged } = ctx; - const { holder, spender } = ctx.accounts; - - const initialAllowance = wei`2 ether`; - - // set initial allowance - await erc20Bridged.approve(spender.address, initialAllowance); - - // validate allowance before increasing - assert.equalBN( - await erc20Bridged.allowance(holder.address, spender.address), - initialAllowance - ); - - // increase allowance - const tx = await erc20Bridged.increaseAllowance(spender.address, "0"); - - // validate Approval event was emitted - await assert.emits(erc20Bridged, tx, "Approval", [ - holder.address, - spender.address, - initialAllowance, - ]); - - // validate allowance was updated correctly - assert.equalBN( - await erc20Bridged.allowance(holder.address, spender.address), - initialAllowance - ); - }) - - .test( - "decreaseAllowance() :: decrease is greater than current allowance", - async (ctx) => { - const { erc20Bridged } = ctx; - const { holder, spender } = ctx.accounts; - - // validate allowance before increasing - assert.equalBN( - await erc20Bridged.allowance(holder.address, spender.address), - "0" - ); - - const allowanceDecrease = wei`1 ether`; - - // decrease allowance - await assert.revertsWith( - erc20Bridged.decreaseAllowance(spender.address, allowanceDecrease), - "ErrorDecreasedAllowanceBelowZero()" - ); - } - ) - - .group([wei`1 ether`, "0"], (allowanceDecrease) => [ - `decreaseAllowance() :: the decrease is ${allowanceDecrease} wei`, - async (ctx) => { - const { erc20Bridged } = ctx; - const { holder, spender } = ctx.accounts; - - const initialAllowance = wei`2 ether`; - - // set initial allowance - await erc20Bridged.approve(spender.address, initialAllowance); - - // validate allowance before increasing - assert.equalBN( - await erc20Bridged.allowance(holder.address, spender.address), - initialAllowance - ); - - // decrease allowance - const tx = await erc20Bridged.decreaseAllowance( - spender.address, - allowanceDecrease - ); - - const expectedAllowance = wei - .toBigNumber(initialAllowance) - .sub(allowanceDecrease); - - // validate Approval event was emitted - await assert.emits(erc20Bridged, tx, "Approval", [ - holder.address, - spender.address, - expectedAllowance, - ]); - - // validate allowance was updated correctly - assert.equalBN( - await erc20Bridged.allowance(holder.address, spender.address), - expectedAllowance - ); - }, - ]) - .test("bridgeMint() :: not owner", async (ctx) => { const { erc20Bridged } = ctx; const { stranger } = ctx.accounts; diff --git a/test/token/ERC20Rebasable.unit.test.ts b/test/token/ERC20Rebasable.unit.test.ts index b8cb3e47..201a1551 100644 --- a/test/token/ERC20Rebasable.unit.test.ts +++ b/test/token/ERC20Rebasable.unit.test.ts @@ -706,175 +706,6 @@ unit("ERC20Rebasable", ctxFactory) ); }) - .test("increaseAllowance() :: initial allowance is zero", async (ctx) => { - const { rebasableProxied } = ctx.contracts; - const { holder, spender } = ctx.accounts; - - // validate allowance before increasing - assert.equalBN( - await rebasableProxied.allowance(holder.address, spender.address), - "0" - ); - - const allowanceIncrease = wei`1 ether`; - - // increase allowance - const tx = await rebasableProxied.increaseAllowance( - spender.address, - allowanceIncrease - ); - - // validate Approval event was emitted - await assert.emits(rebasableProxied, tx, "Approval", [ - holder.address, - spender.address, - allowanceIncrease, - ]); - - // validate allowance was updated correctly - assert.equalBN( - await rebasableProxied.allowance(holder.address, spender.address), - allowanceIncrease - ); - }) - - .test("increaseAllowance() :: initial allowance is not zero", async (ctx) => { - const { rebasableProxied } = ctx.contracts; - const { holder, spender } = ctx.accounts; - - const initialAllowance = wei`2 ether`; - - // set initial allowance - await rebasableProxied.approve(spender.address, initialAllowance); - - // validate allowance before increasing - assert.equalBN( - await rebasableProxied.allowance(holder.address, spender.address), - initialAllowance - ); - - const allowanceIncrease = wei`1 ether`; - - // increase allowance - const tx = await rebasableProxied.increaseAllowance( - spender.address, - allowanceIncrease - ); - - const expectedAllowance = wei - .toBigNumber(initialAllowance) - .add(allowanceIncrease); - - // validate Approval event was emitted - await assert.emits(rebasableProxied, tx, "Approval", [ - holder.address, - spender.address, - expectedAllowance, - ]); - - // validate allowance was updated correctly - assert.equalBN( - await rebasableProxied.allowance(holder.address, spender.address), - expectedAllowance - ); - }) - - .test("increaseAllowance() :: the increase is not zero", async (ctx) => { - const { rebasableProxied } = ctx.contracts; - const { holder, spender } = ctx.accounts; - - const initialAllowance = wei`2 ether`; - - // set initial allowance - await rebasableProxied.approve(spender.address, initialAllowance); - - // validate allowance before increasing - assert.equalBN( - await rebasableProxied.allowance(holder.address, spender.address), - initialAllowance - ); - - // increase allowance - const tx = await rebasableProxied.increaseAllowance(spender.address, "0"); - - // validate Approval event was emitted - await assert.emits(rebasableProxied, tx, "Approval", [ - holder.address, - spender.address, - initialAllowance, - ]); - - // validate allowance was updated correctly - assert.equalBN( - await rebasableProxied.allowance(holder.address, spender.address), - initialAllowance - ); - }) - - .test( - "decreaseAllowance() :: decrease is greater than current allowance", - async (ctx) => { - const { rebasableProxied } = ctx.contracts; - const { holder, spender } = ctx.accounts; - - // validate allowance before increasing - assert.equalBN( - await rebasableProxied.allowance(holder.address, spender.address), - "0" - ); - - const allowanceDecrease = wei`1 ether`; - - // decrease allowance - await assert.revertsWith( - rebasableProxied.decreaseAllowance(spender.address, allowanceDecrease), - "ErrorDecreasedAllowanceBelowZero()" - ); - } - ) - - .group([wei`1 ether`, "0"], (allowanceDecrease) => [ - `decreaseAllowance() :: the decrease is ${allowanceDecrease} wei`, - async (ctx) => { - const { rebasableProxied } = ctx.contracts; - const { holder, spender } = ctx.accounts; - - const initialAllowance = wei`2 ether`; - - // set initial allowance - await rebasableProxied.approve(spender.address, initialAllowance); - - // validate allowance before increasing - assert.equalBN( - await rebasableProxied.allowance(holder.address, spender.address), - initialAllowance - ); - - // decrease allowance - const tx = await rebasableProxied.decreaseAllowance( - spender.address, - allowanceDecrease - ); - - const expectedAllowance = wei - .toBigNumber(initialAllowance) - .sub(allowanceDecrease); - - // validate Approval event was emitted - await assert.emits(rebasableProxied, tx, "Approval", [ - holder.address, - spender.address, - expectedAllowance, - ]); - - // validate allowance was updated correctly - assert.equalBN( - await rebasableProxied.allowance(holder.address, spender.address), - expectedAllowance - ); - }, - ]) - .test("bridgeMint() :: not owner", async (ctx) => { const { rebasableProxied } = ctx.contracts; const { stranger } = ctx.accounts; From cc868ef046985ed7b8e225bfe99f8f93a09e9fc5 Mon Sep 17 00:00:00 2001 From: Anton Kovalchuk Date: Wed, 3 Apr 2024 10:01:18 +0200 Subject: [PATCH 051/148] change function names --- contracts/optimism/L2ERC20TokenBridge.sol | 4 +-- contracts/token/ERC20Rebasable.sol | 4 +-- .../token/interfaces/IERC20BridgedShares.sol | 4 +-- test/token/ERC20Permit.unit.test.ts | 2 +- test/token/ERC20Rebasable.unit.test.ts | 28 +++++++++---------- 5 files changed, 21 insertions(+), 21 deletions(-) diff --git a/contracts/optimism/L2ERC20TokenBridge.sol b/contracts/optimism/L2ERC20TokenBridge.sol index 2c7b54b4..dd01601e 100644 --- a/contracts/optimism/L2ERC20TokenBridge.sol +++ b/contracts/optimism/L2ERC20TokenBridge.sol @@ -98,7 +98,7 @@ contract L2ERC20TokenBridge is ITokenRateOracle tokenRateOracle = ERC20Rebasable(L2_TOKEN_REBASABLE).TOKEN_RATE_ORACLE(); tokenRateOracle.updateRate(depositData.rate, depositData.timestamp); - ERC20Rebasable(L2_TOKEN_REBASABLE).mintShares(to_, amount_); + ERC20Rebasable(L2_TOKEN_REBASABLE).bridgeMintShares(to_, amount_); uint256 rebasableTokenAmount = ERC20Rebasable(L2_TOKEN_REBASABLE).getTokensByShares(amount_); emit DepositFinalized( @@ -131,7 +131,7 @@ contract L2ERC20TokenBridge is ) internal { if (l2Token_ == L2_TOKEN_REBASABLE) { uint256 shares = ERC20Rebasable(L2_TOKEN_REBASABLE).getSharesByTokens(amount_); - ERC20Rebasable(L2_TOKEN_REBASABLE).burnShares(msg.sender, shares); + ERC20Rebasable(L2_TOKEN_REBASABLE).bridgeBurnShares(msg.sender, shares); _initiateWithdrawal( L1_TOKEN_REBASABLE, diff --git a/contracts/token/ERC20Rebasable.sol b/contracts/token/ERC20Rebasable.sol index ba69ba13..eb7f8751 100644 --- a/contracts/token/ERC20Rebasable.sol +++ b/contracts/token/ERC20Rebasable.sol @@ -85,12 +85,12 @@ contract ERC20Rebasable is IERC20, IERC20Wrapper, IERC20BridgedShares, ERC20Meta } /// @inheritdoc IERC20BridgedShares - function mintShares(address account_, uint256 amount_) external onlyBridge { + function bridgeMintShares(address account_, uint256 amount_) external onlyBridge { _mintShares(account_, amount_); } /// @inheritdoc IERC20BridgedShares - function burnShares(address account_, uint256 amount_) external onlyBridge { + function bridgeBurnShares(address account_, uint256 amount_) external onlyBridge { _burnShares(account_, amount_); } diff --git a/contracts/token/interfaces/IERC20BridgedShares.sol b/contracts/token/interfaces/IERC20BridgedShares.sol index df7a0d9e..8ae4be6d 100644 --- a/contracts/token/interfaces/IERC20BridgedShares.sol +++ b/contracts/token/interfaces/IERC20BridgedShares.sol @@ -14,10 +14,10 @@ interface IERC20BridgedShares is IERC20 { /// @notice Creates amount_ shares and assigns them to account_, increasing the total shares supply /// @param account_ An address of the account to mint shares /// @param amount_ An amount of shares to mint - function mintShares(address account_, uint256 amount_) external; + function bridgeMintShares(address account_, uint256 amount_) external; /// @notice Destroys amount_ shares from account_, reducing the total shares supply /// @param account_ An address of the account to burn shares /// @param amount_ An amount of shares to burn - function burnShares(address account_, uint256 amount_) external; + function bridgeBurnShares(address account_, uint256 amount_) external; } diff --git a/test/token/ERC20Permit.unit.test.ts b/test/token/ERC20Permit.unit.test.ts index 9aedeada..81a0e20f 100644 --- a/test/token/ERC20Permit.unit.test.ts +++ b/test/token/ERC20Permit.unit.test.ts @@ -386,7 +386,7 @@ function ctxFactoryFactory(signingAccountsFuncFactory: typeof getAccountsEIP1271 ); await tokenRateOracle.connect(owner).updateRate(rate, 1000); - await rebasableProxied.connect(owner).mintShares(holder.address, premintShares); + await rebasableProxied.connect(owner).bridgeMintShares(holder.address, premintShares); const { alice, bob } = await signingAccountsFuncFactory(); return { diff --git a/test/token/ERC20Rebasable.unit.test.ts b/test/token/ERC20Rebasable.unit.test.ts index 201a1551..e763ec09 100644 --- a/test/token/ERC20Rebasable.unit.test.ts +++ b/test/token/ERC20Rebasable.unit.test.ts @@ -342,7 +342,7 @@ unit("ERC20Rebasable", ctxFactory) await assert.revertsWith(rebasableProxied.connect(user1).unwrap(wei`4 ether`), "ErrorNotEnoughBalance()"); }) - .test("mintShares() :: happy path", async (ctx) => { + .test("bridgeMintShares() :: happy path", async (ctx) => { const { rebasableProxied } = ctx.contracts; const {user1, user2, owner } = ctx.accounts; @@ -358,7 +358,7 @@ unit("ERC20Rebasable", ctxFactory) assert.equalBN(await rebasableProxied.sharesOf(user1.address), 0); assert.equalBN(await rebasableProxied.balanceOf(user1.address), 0); - const tx0 = await rebasableProxied.connect(owner).mintShares(user1.address, user1SharesToMint); + const tx0 = await rebasableProxied.connect(owner).bridgeMintShares(user1.address, user1SharesToMint); assert.equalBN(await rebasableProxied.sharesOf(user1.address), user1SharesToMint); assert.equalBN(await rebasableProxied.balanceOf(user1.address), user1TokensMinted); @@ -374,7 +374,7 @@ unit("ERC20Rebasable", ctxFactory) assert.equalBN(await rebasableProxied.sharesOf(user2.address), 0); assert.equalBN(await rebasableProxied.balanceOf(user2.address), 0); - const tx1 = await rebasableProxied.connect(owner).mintShares(user2.address, user2SharesToMint); + const tx1 = await rebasableProxied.connect(owner).bridgeMintShares(user2.address, user2SharesToMint); assert.equalBN(await rebasableProxied.sharesOf(user2.address), user2SharesToMint); assert.equalBN(await rebasableProxied.balanceOf(user2.address), user2TokensMinted); @@ -384,7 +384,7 @@ unit("ERC20Rebasable", ctxFactory) assert.equalBN(await rebasableProxied.totalSupply(), premintTokens.add(user1TokensMinted).add(user2TokensMinted)); }) - .test("burnShares() :: happy path", async (ctx) => { + .test("bridgeBurnShares() :: happy path", async (ctx) => { const { rebasableProxied } = ctx.contracts; const {user1, user2, owner } = ctx.accounts; @@ -406,11 +406,11 @@ unit("ERC20Rebasable", ctxFactory) assert.equalBN(await rebasableProxied.sharesOf(user1.address), 0); assert.equalBN(await rebasableProxied.balanceOf(user1.address), 0); - await rebasableProxied.connect(owner).mintShares(user1.address, user1SharesToMint); + await rebasableProxied.connect(owner).bridgeMintShares(user1.address, user1SharesToMint); assert.equalBN(await rebasableProxied.sharesOf(user1.address), user1SharesToMint); assert.equalBN(await rebasableProxied.balanceOf(user1.address), user1TokensMinted); - await rebasableProxied.connect(owner).burnShares(user1.address, user1SharesToBurn); + await rebasableProxied.connect(owner).bridgeBurnShares(user1.address, user1SharesToBurn); assert.equalBN(await rebasableProxied.sharesOf(user1.address), user1Shares); assert.equalBN(await rebasableProxied.balanceOf(user1.address), user1Tokens); @@ -431,10 +431,10 @@ unit("ERC20Rebasable", ctxFactory) assert.equalBN(await rebasableProxied.sharesOf(user2.address), 0); assert.equalBN(await rebasableProxied.balanceOf(user2.address), 0); - await rebasableProxied.connect(owner).mintShares(user2.address, user2SharesToMint); + await rebasableProxied.connect(owner).bridgeMintShares(user2.address, user2SharesToMint); assert.equalBN(await rebasableProxied.sharesOf(user2.address), user2SharesToMint); assert.equalBN(await rebasableProxied.balanceOf(user2.address), user2TokensMinted); - await rebasableProxied.connect(owner).burnShares(user2.address, user2SharesToBurn); + await rebasableProxied.connect(owner).bridgeBurnShares(user2.address, user2SharesToBurn); assert.equalBN(await rebasableProxied.sharesOf(user2.address), user2Shares); assert.equalBN(await rebasableProxied.balanceOf(user2.address), user2Tokens); @@ -713,7 +713,7 @@ unit("ERC20Rebasable", ctxFactory) await assert.revertsWith( rebasableProxied .connect(stranger) - .mintShares(stranger.address, wei`1000 ether`), + .bridgeMintShares(stranger.address, wei`1000 ether`), "ErrorNotBridge()" ); }) @@ -734,7 +734,7 @@ unit("ERC20Rebasable", ctxFactory) // mint tokens const tx = await rebasableProxied .connect(owner) - .mintShares(recipient.address, mintAmount); + .bridgeMintShares(recipient.address, mintAmount); // validate Transfer event was emitted await assert.emits(rebasableProxied, tx, "Transfer", [ @@ -762,7 +762,7 @@ unit("ERC20Rebasable", ctxFactory) const { holder, stranger } = ctx.accounts; await assert.revertsWith( - rebasableProxied.connect(stranger).burnShares(holder.address, wei`100 ether`), + rebasableProxied.connect(stranger).bridgeBurnShares(holder.address, wei`100 ether`), "ErrorNotBridge()" ); }) @@ -775,7 +775,7 @@ unit("ERC20Rebasable", ctxFactory) assert.equalBN(await rebasableProxied.balanceOf(stranger.address), 0); await assert.revertsWith( - rebasableProxied.connect(owner).burnShares(stranger.address, wei`100 ether`), + rebasableProxied.connect(owner).bridgeBurnShares(stranger.address, wei`100 ether`), "ErrorNotEnoughBalance()" ); }) @@ -796,7 +796,7 @@ unit("ERC20Rebasable", ctxFactory) // burn tokens const tx = await rebasableProxied .connect(owner) - .burnShares(holder.address, burnAmount); + .bridgeBurnShares(holder.address, burnAmount); // validate Transfer event was emitted await assert.emits(rebasableProxied, tx, "Transfer", [ @@ -886,7 +886,7 @@ async function ctxFactory() { ); await tokenRateOracle.connect(owner).updateRate(rate, 1000); - await rebasableProxied.connect(owner).mintShares(holder.address, premintShares); + await rebasableProxied.connect(owner).bridgeMintShares(holder.address, premintShares); return { accounts: { deployer, owner, recipient, spender, holder, stranger, zero, user1, user2 }, From 06d00c7c4433c4ab70378083e01df462ec712a9f Mon Sep 17 00:00:00 2001 From: Anton Kovalchuk Date: Wed, 3 Apr 2024 10:10:05 +0200 Subject: [PATCH 052/148] rename stETH to rebasable token in env --- .env.example | 2 +- .env.wsteth.opt_mainnet | 2 +- README.md | 4 ++-- contracts/optimism/L1LidoTokensBridge.sol | 2 +- scripts/optimism/deploy-bridge.ts | 2 +- utils/deployment.ts | 4 ++-- 6 files changed, 8 insertions(+), 8 deletions(-) diff --git a/.env.example b/.env.example index 4d4ffc94..d3ecb36a 100644 --- a/.env.example +++ b/.env.example @@ -29,7 +29,7 @@ ETHERSCAN_API_KEY_OPT= TOKEN= # Address of the rebasable token to deploy the bridge/gateway for -STETH_TOKEN= +REBASABLE_TOKEN= # Name of the network environments used by deployment scripts. # Might be one of: "mainnet", "goerli". diff --git a/.env.wsteth.opt_mainnet b/.env.wsteth.opt_mainnet index ccb7f7d6..86ebcad0 100644 --- a/.env.wsteth.opt_mainnet +++ b/.env.wsteth.opt_mainnet @@ -22,7 +22,7 @@ ETHERSCAN_API_KEY_OPT= TOKEN=0x7f39C581F595B53c5cb19bD0b3f8dA6c935E2Ca0 # Address of the rebasable token to deploy the bridge/gateway for -STETH_TOKEN= +REBASABLE_TOKEN= # Name of the network environments used by deployment scripts. # Might be one of: "mainnet", "goerli". diff --git a/README.md b/README.md index 97b1a883..7781dbe3 100644 --- a/README.md +++ b/README.md @@ -43,7 +43,7 @@ Fill the newly created `.env` file with the required variables. See the [Project The configuration of the deployment scripts happens via the ENV variables. The following variables are required: - [`TOKEN`](#TOKEN) - address of the non-rebasable token to deploy a new bridge on the Ethereum chain. -- [`STETH_TOKEN`] (#STETH_TOKEN) - address of the rebasable token to deploy new bridge on the Ethereum chain. +- [`REBASABLE_TOKEN`] (#REBASABLE_TOKEN) - address of the rebasable token to deploy new bridge on the Ethereum chain. - [`NETWORK`](#NETWORK) - name of the network environments used by deployment scripts. Allowed values: `mainnet`, `goerli`. - [`FORKING`](#FORKING) - run deployment in the forking network instead of real ones - [`ETH_DEPLOYER_PRIVATE_KEY`](#ETH_DEPLOYER_PRIVATE_KEY) - The private key of the deployer account in the Ethereum network is used during the deployment process. @@ -317,7 +317,7 @@ Below variables used in the Arbitrum/Optimism bridge deployment process. Address of the non-rebasable token to deploy a new bridge on the Ethereum chain. -#### `STETH_TOKEN` +#### `REBASABLE_TOKEN` Address of the rebasable token to deploy new bridge on the Ethereum chain. diff --git a/contracts/optimism/L1LidoTokensBridge.sol b/contracts/optimism/L1LidoTokensBridge.sol index b78e223e..cd144d16 100644 --- a/contracts/optimism/L1LidoTokensBridge.sol +++ b/contracts/optimism/L1LidoTokensBridge.sol @@ -7,7 +7,7 @@ import {L1ERC20TokenBridge} from "./L1ERC20TokenBridge.sol"; import {IERC20WstETH} from "../token/interfaces/IERC20WstETH.sol"; /// @author kovalgek -/// @notice Hides wstETH concept from other contracts to save level of abstraction. +/// @notice Hides wstETH concept from other contracts to keep `L1ERC20TokenBridge` reusable. contract L1LidoTokensBridge is L1ERC20TokenBridge { constructor( diff --git a/scripts/optimism/deploy-bridge.ts b/scripts/optimism/deploy-bridge.ts index 3453071c..682eeb4a 100644 --- a/scripts/optimism/deploy-bridge.ts +++ b/scripts/optimism/deploy-bridge.ts @@ -25,7 +25,7 @@ async function main() { .deployment(networkName, { logger: console }) .erc20TokenBridgeDeployScript( deploymentConfig.token, - deploymentConfig.stETHToken, + deploymentConfig.rebasableToken, deploymentConfig.l2TokenRateOracle, { deployer: ethDeployer, diff --git a/utils/deployment.ts b/utils/deployment.ts index 7612bdf7..eba5c079 100644 --- a/utils/deployment.ts +++ b/utils/deployment.ts @@ -11,7 +11,7 @@ interface ChainDeploymentConfig extends BridgingManagerSetupConfig { interface MultiChainDeploymentConfig { token: string; - stETHToken: string; + rebasableToken: string; l2TokenRateOracle: string; l1: ChainDeploymentConfig; l2: ChainDeploymentConfig; @@ -20,7 +20,7 @@ interface MultiChainDeploymentConfig { export function loadMultiChainDeploymentConfig(): MultiChainDeploymentConfig { return { token: env.address("TOKEN"), - stETHToken: env.address("STETH_TOKEN"), + rebasableToken: env.address("REBASABLE_TOKEN"), l2TokenRateOracle: env.address("TOKEN_RATE_ORACLE"), l1: { proxyAdmin: env.address("L1_PROXY_ADMIN"), From 0b22b0f3652f410e370e5eea3e40d2da8128de67 Mon Sep 17 00:00:00 2001 From: Anton Kovalchuk Date: Mon, 8 Apr 2024 12:57:28 +0200 Subject: [PATCH 053/148] deploy all script, refactor other deploy scripts --- .env.example | 27 ++ scripts/optimism/deploy-bridge.ts | 4 +- scripts/optimism/deploy-new-impls.ts | 73 ++++ scripts/optimism/deploy-oracle.ts | 112 +++--- .../optimism.integration.test.ts | 4 +- utils/deployment.ts | 28 +- utils/optimism/deploymentAll.ts | 318 ----------------- utils/optimism/deploymentAllFromScratch.ts | 320 ++++++++++++++++++ .../optimism/deploymentNewImplementations.ts | 229 +++++++++++++ utils/optimism/deploymentOracle.ts | 167 +++++---- utils/optimism/testing.ts | 4 +- utils/optimism/types.ts | 17 + utils/optimism/upgradeOracle.ts | 79 ----- 13 files changed, 829 insertions(+), 553 deletions(-) create mode 100644 scripts/optimism/deploy-new-impls.ts delete mode 100644 utils/optimism/deploymentAll.ts create mode 100644 utils/optimism/deploymentAllFromScratch.ts create mode 100644 utils/optimism/deploymentNewImplementations.ts delete mode 100644 utils/optimism/upgradeOracle.ts diff --git a/.env.example b/.env.example index d3ecb36a..db673518 100644 --- a/.env.example +++ b/.env.example @@ -31,6 +31,33 @@ TOKEN= # Address of the rebasable token to deploy the bridge/gateway for REBASABLE_TOKEN= +# Address of token rate notifier. Connects Lido core protocol. +TOKEN_RATE_NOTIFIER= + +# Address of token rate pusher +L1_OP_STACK_TOKEN_RATE_PUSHER= + +# Gas limit required to complete pushing token rate on L2. +L2_GAS_LIMIT_FOR_PUSHING_TOKEN_RATE= + +# A time period when token rate can be considered outdated. +RATE_OUTDATED_DELAY= + +# Address of L1 token bridge proxy. +L1_TOKEN_BRIDGE= + +# Address of L2 token bridge proxy. +L2_TOKEN_BRIDGE= + +# Address of the non-rebasable token proxy on L2. +L2_TOKEN= + +# Address of token rate oracle on L2 +L2_TOKEN_RATE_ORACLE= + +# Address of bridge executor. +GOV_BRIDGE_EXECUTOR= + # Name of the network environments used by deployment scripts. # Might be one of: "mainnet", "goerli". NETWORK=mainnet diff --git a/scripts/optimism/deploy-bridge.ts b/scripts/optimism/deploy-bridge.ts index 682eeb4a..2e4f272b 100644 --- a/scripts/optimism/deploy-bridge.ts +++ b/scripts/optimism/deploy-bridge.ts @@ -24,8 +24,8 @@ async function main() { const [l1DeployScript, l2DeployScript] = await optimism .deployment(networkName, { logger: console }) .erc20TokenBridgeDeployScript( - deploymentConfig.token, - deploymentConfig.rebasableToken, + deploymentConfig.l1Token, + deploymentConfig.l1RebasableToken, deploymentConfig.l2TokenRateOracle, { deployer: ethDeployer, diff --git a/scripts/optimism/deploy-new-impls.ts b/scripts/optimism/deploy-new-impls.ts new file mode 100644 index 00000000..2026c9c5 --- /dev/null +++ b/scripts/optimism/deploy-new-impls.ts @@ -0,0 +1,73 @@ +import env from "../../utils/env"; +import prompt from "../../utils/prompt"; +import network from "../../utils/network"; +import deployment from "../../utils/deployment"; + +import deploymentNewImplementations from "../../utils/optimism/deploymentNewImplementations"; + +async function main() { + const networkName = env.network(); + const ethOptNetwork = network.multichain(["eth", "opt"], networkName); + + const [ethDeployer] = ethOptNetwork.getSigners(env.privateKey(), { + forking: env.forking(), + }); + const [, optDeployer] = ethOptNetwork.getSigners( + env.string("OPT_DEPLOYER_PRIVATE_KEY"), + { + forking: env.forking(), + } + ); + + const deploymentConfig = deployment.loadMultiChainDeploymentConfig(); + + const [l1DeployScript, l2DeployScript] = await deploymentNewImplementations( + networkName, + { logger: console } + ) + .deployScript( + { + deployer: ethDeployer, + admins: { + proxy: deploymentConfig.l1.proxyAdmin, + bridge: ethDeployer.address + }, + contractsShift: 0, + tokenProxyAddress: deploymentConfig.l1Token, + tokenRebasableProxyAddress: deploymentConfig.l1RebasableToken, + opStackTokenRatePusherImplAddress: deploymentConfig.l1OpStackTokenRatePusher, + tokenBridgeProxyAddress: deploymentConfig.l1TokenBridge, + }, + { + deployer: optDeployer, + admins: { + proxy: deploymentConfig.l2.proxyAdmin, + bridge: optDeployer.address, + }, + contractsShift: 0, + tokenBridgeProxyAddress: deploymentConfig.l2TokenBridge, + tokenProxyAddress: deploymentConfig.l2Token, + tokenRateOracleProxyAddress: deploymentConfig.l2TokenRateOracle, + tokenRateOracleRateOutdatedDelay: deploymentConfig.rateOutdatedDelay, + } + ); + + await deployment.printMultiChainDeploymentConfig( + "Deploy new implementations: bridges, wstETH, stETH", + ethDeployer, + optDeployer, + deploymentConfig, + l1DeployScript, + l2DeployScript + ); + + await prompt.proceed(); + + await l1DeployScript.run(); + await l2DeployScript.run(); +} + +main().catch((error) => { + console.error(error); + process.exitCode = 1; +}); diff --git a/scripts/optimism/deploy-oracle.ts b/scripts/optimism/deploy-oracle.ts index bf5e38fb..7d75e0f3 100644 --- a/scripts/optimism/deploy-oracle.ts +++ b/scripts/optimism/deploy-oracle.ts @@ -2,72 +2,74 @@ import env from "../../utils/env"; import prompt from "../../utils/prompt"; import network from "../../utils/network"; import optimism from "../../utils/optimism"; -import deploymentOracle from "../../utils/deployment"; +import deployment from "../../utils/deployment"; import { TokenRateNotifier__factory } from "../../typechain"; async function main() { - const networkName = env.network(); - const ethOptNetwork = network.multichain(["eth", "opt"], networkName); + const networkName = env.network(); + const ethOptNetwork = network.multichain(["eth", "opt"], networkName); - const [ethDeployer] = ethOptNetwork.getSigners(env.privateKey(), { - forking: env.forking(), - }); - const [, optDeployer] = ethOptNetwork.getSigners( - env.string("OPT_DEPLOYER_PRIVATE_KEY"), - { - forking: env.forking(), - } - ); + const [ethDeployer] = ethOptNetwork.getSigners(env.privateKey(), { + forking: env.forking(), + }); + const [, optDeployer] = ethOptNetwork.getSigners( + env.string("OPT_DEPLOYER_PRIVATE_KEY"), + { + forking: env.forking(), + } + ); - const l1Token = env.address("TOKEN") - const l1Admin = env.address("L1_PROXY_ADMIN"); - const l2Admin = env.address("L2_PROXY_ADMIN"); + const deploymentConfig = deployment.loadMultiChainDeploymentConfig(); - const [l1DeployScript, l2DeployScript] = await optimism - .deploymentOracle(networkName, { logger: console }) - .oracleDeployScript( - l1Token, - { - deployer: ethDeployer, - admins: { - proxy: l1Admin, - bridge: ethDeployer.address, - }, - }, - { - deployer: optDeployer, - admins: { - proxy: l2Admin, - bridge: optDeployer.address, - }, - } - ); + const [l1DeployScript, l2DeployScript] = await optimism + .deploymentOracle(networkName, { logger: console }) + .oracleDeployScript( + deploymentConfig.l1Token, + deploymentConfig.l2GasLimitForPushingTokenRate, + deploymentConfig.rateOutdatedDelay, + { + deployer: ethDeployer, + admins: { + proxy: deploymentConfig.l1.proxyAdmin, + bridge: ethDeployer.address, + }, + contractsShift: 0 + }, + { + deployer: optDeployer, + admins: { + proxy: deploymentConfig.l2.proxyAdmin, + bridge: optDeployer.address, + }, + contractsShift: 0 + } + ); -// await deploymentOracle.printMultiChainDeploymentConfig( -// "Deploy Token Rate Oracle", -// ethDeployer, -// optDeployer, -// deploymentConfig, -// l1DeployScript, -// l2DeployScript -// ); + await deployment.printMultiChainDeploymentConfig( + "Deploy Token Rate Oracle", + ethDeployer, + optDeployer, + deploymentConfig, + l1DeployScript, + l2DeployScript + ); - await prompt.proceed(); + await prompt.proceed(); - await l1DeployScript.run(); - await l2DeployScript.run(); + await l1DeployScript.run(); + await l2DeployScript.run(); - /// setup, add observer - const tokenRateNotifier = TokenRateNotifier__factory.connect( - l1DeployScript.tokenRateNotifierImplAddress, - ethDeployer - ); - await tokenRateNotifier - .connect(ethDeployer) - .addObserver(l1DeployScript.opStackTokenRatePusherImplAddress); + /// setup by adding observer + const tokenRateNotifier = TokenRateNotifier__factory.connect( + l1DeployScript.tokenRateNotifierImplAddress, + ethDeployer + ); + await tokenRateNotifier + .connect(ethDeployer) + .addObserver(l1DeployScript.opStackTokenRatePusherImplAddress); } main().catch((error) => { - console.error(error); - process.exitCode = 1; + console.error(error); + process.exitCode = 1; }); diff --git a/test/bridge-executor/optimism.integration.test.ts b/test/bridge-executor/optimism.integration.test.ts index 1cf1db45..bc8fcc13 100644 --- a/test/bridge-executor/optimism.integration.test.ts +++ b/test/bridge-executor/optimism.integration.test.ts @@ -15,7 +15,7 @@ import { BridgingManagerRole } from "../../utils/bridging-management"; import env from "../../utils/env"; import network from "../../utils/network"; import { getBridgeExecutorParams } from "../../utils/bridge-executor"; -import deploymentAll from "../../utils/optimism/deploymentAll"; +import deploymentAll from "../../utils/optimism/deploymentAllFromScratch"; scenario("Optimism :: Bridge Executor integration test", ctxFactory) .step("Activate L2 bridge", async (ctx) => { @@ -239,7 +239,7 @@ async function ctxFactory() { const [, optDeployScript] = await deploymentAll( networkName - ).erc20TokenBridgeDeployScript( + ).deployAllScript( l1Token.address, l1TokenRebasable.address, { diff --git a/utils/deployment.ts b/utils/deployment.ts index eba5c079..e820bd8f 100644 --- a/utils/deployment.ts +++ b/utils/deployment.ts @@ -10,18 +10,32 @@ interface ChainDeploymentConfig extends BridgingManagerSetupConfig { } interface MultiChainDeploymentConfig { - token: string; - rebasableToken: string; + l1Token: string; + l1RebasableToken: string; + l1OpStackTokenRatePusher: string; + l2GasLimitForPushingTokenRate: number; + rateOutdatedDelay: number; + l1TokenBridge: string; + l2TokenBridge: string; + l2Token: string; l2TokenRateOracle: string; + govBridgeExecutor: string; l1: ChainDeploymentConfig; l2: ChainDeploymentConfig; } export function loadMultiChainDeploymentConfig(): MultiChainDeploymentConfig { return { - token: env.address("TOKEN"), - rebasableToken: env.address("REBASABLE_TOKEN"), - l2TokenRateOracle: env.address("TOKEN_RATE_ORACLE"), + l1Token: env.address("TOKEN"), + l1RebasableToken: env.address("REBASABLE_TOKEN"), + l1OpStackTokenRatePusher: env.address("L1_OP_STACK_TOKEN_RATE_PUSHER"), + l2GasLimitForPushingTokenRate: Number(env.string("L2_GAS_LIMIT_FOR_PUSHING_TOKEN_RATE")), + rateOutdatedDelay: Number(env.string("RATE_OUTDATED_DELAY")), + l1TokenBridge: env.address("L1_TOKEN_BRIDGE"), + l2TokenBridge: env.address("L2_TOKEN_BRIDGE"), + l2Token: env.address("L2_TOKEN"), + l2TokenRateOracle: env.address("L2_TOKEN_RATE_ORACLE"), + govBridgeExecutor: env.address("GOV_BRIDGE_EXECUTOR"), l1: { proxyAdmin: env.address("L1_PROXY_ADMIN"), bridgeAdmin: env.address("L1_BRIDGE_ADMIN"), @@ -53,8 +67,8 @@ export async function printMultiChainDeploymentConfig( l1DeployScript: DeployScript, l2DeployScript: DeployScript ) { - const { token, stETHToken, l1, l2 } = deploymentParams; - console.log(chalk.bold(`${title} :: ${chalk.underline(token)} :: ${chalk.underline(stETHToken)}\n`)); + const { l1Token, l1RebasableToken, l1, l2 } = deploymentParams; + console.log(chalk.bold(`${title} :: ${chalk.underline(l1Token)} :: ${chalk.underline(l1RebasableToken)}\n`)); console.log(chalk.bold(" · L1 Deployment Params:")); await printChainDeploymentConfig(l1Deployer, l1); console.log(); diff --git a/utils/optimism/deploymentAll.ts b/utils/optimism/deploymentAll.ts deleted file mode 100644 index 993b7a2c..00000000 --- a/utils/optimism/deploymentAll.ts +++ /dev/null @@ -1,318 +0,0 @@ -import { assert } from "chai"; -import { Overrides, Wallet } from "ethers"; -import addresses from "./addresses"; -import { CommonOptions } from "./types"; -import network, { NetworkName } from "../network"; -import { DeployScript, Logger } from "../deployment/DeployScript"; -import { - ERC20Bridged__factory, - ERC20Rebasable__factory, - IERC20Metadata__factory, - L1LidoTokensBridge__factory, - L2ERC20TokenBridge__factory, - OssifiableProxy__factory, - TokenRateOracle__factory, - TokenRateNotifier__factory, - OpStackTokenRatePusher__factory - } from "../../typechain"; - -interface OptL1DeployScriptParams { - deployer: Wallet; - admins: { proxy: string; bridge: string }; - contractsShift: number; -} - -interface OptL2DeployScriptParams extends OptL1DeployScriptParams { - l2Token?: { name?: string; symbol?: string }; - l2TokenRebasable?: { name?: string; symbol?: string }; -} - -interface OptDeploymentOptions extends CommonOptions { - logger?: Logger; - overrides?: Overrides; -} - -export class L1DeployAllScript extends DeployScript { - - constructor( - deployer: Wallet, - bridgeImplAddress: string, - bridgeProxyAddress: string, - tokenRateNotifierImplAddress: string, - opStackTokenRatePusherImplAddress: string, - logger?: Logger - ) { - super(deployer, logger); - this.bridgeImplAddress = bridgeImplAddress; - this.bridgeProxyAddress = bridgeProxyAddress; - this.tokenRateNotifierImplAddress = tokenRateNotifierImplAddress; - this.opStackTokenRatePusherImplAddress = opStackTokenRatePusherImplAddress; - } - - public bridgeImplAddress: string; - public bridgeProxyAddress: string; - public tokenRateNotifierImplAddress: string; - public opStackTokenRatePusherImplAddress: string; -} - -export class L2DeployAllScript extends DeployScript { - - constructor( - deployer: Wallet, - tokenImplAddress: string, - tokenProxyAddress: string, - tokenRebasableImplAddress: string, - tokenRebasableProxyAddress: string, - tokenBridgeImplAddress: string, - tokenBridgeProxyAddress: string, - tokenRateOracleImplAddress: string, - tokenRateOracleProxyAddress: string, - logger?: Logger - ) { - super(deployer, logger); - this.tokenImplAddress = tokenImplAddress; - this.tokenProxyAddress = tokenProxyAddress; - this.tokenRebasableImplAddress = tokenRebasableImplAddress; - this.tokenRebasableProxyAddress = tokenRebasableProxyAddress; - this.tokenBridgeImplAddress = tokenBridgeImplAddress; - this.tokenBridgeProxyAddress = tokenBridgeProxyAddress; - this.tokenRateOracleImplAddress = tokenRateOracleImplAddress; - this.tokenRateOracleProxyAddress = tokenRateOracleProxyAddress; - } - - public tokenImplAddress: string; - public tokenProxyAddress: string; - public tokenRebasableImplAddress: string; - public tokenRebasableProxyAddress: string; - public tokenBridgeImplAddress: string; - public tokenBridgeProxyAddress: string; - public tokenRateOracleImplAddress: string; - public tokenRateOracleProxyAddress: string; -} - -/// deploys from scratch wstETH on L2, stETH on L2, bridgeL1, bridgeL2 and Oracle -export default function deploymentAll( - networkName: NetworkName, - options: OptDeploymentOptions = {} -) { - const optAddresses = addresses(networkName, options); - return { - async erc20TokenBridgeDeployScript( - l1Token: string, - l1TokenRebasable: string, - l1Params: OptL1DeployScriptParams, - l2Params: OptL2DeployScriptParams, - ): Promise<[L1DeployAllScript, L2DeployAllScript]> { - - const [ - expectedL1TokenBridgeImplAddress, - expectedL1TokenBridgeProxyAddress, - expectedL1TokenRateNotifierImplAddress, - expectedL1OpStackTokenRatePusherImplAddress, - ] = await network.predictAddresses(l1Params.deployer, l1Params.contractsShift + 4); - - const [ - expectedL2TokenImplAddress, - expectedL2TokenProxyAddress, - expectedL2TokenRebasableImplAddress, - expectedL2TokenRebasableProxyAddress, - expectedL2TokenBridgeImplAddress, - expectedL2TokenBridgeProxyAddress, - expectedL2TokenRateOracleImplAddress, - expectedL2TokenRateOracleProxyAddress - ] = await network.predictAddresses(l2Params.deployer, l2Params.contractsShift + 8); - - const l1DeployScript = new L1DeployAllScript( - l1Params.deployer, - expectedL1TokenBridgeImplAddress, - expectedL1TokenBridgeProxyAddress, - expectedL1TokenRateNotifierImplAddress, - expectedL1OpStackTokenRatePusherImplAddress, - options?.logger - ) - .addStep({ - factory: L1LidoTokensBridge__factory, - args: [ - optAddresses.L1CrossDomainMessenger, - expectedL2TokenBridgeProxyAddress, - l1Token, - l1TokenRebasable, - expectedL2TokenProxyAddress, - expectedL2TokenRebasableProxyAddress, - options?.overrides, - ], - afterDeploy: (c) => - assert.equal(c.address, expectedL1TokenBridgeImplAddress), - }) - .addStep({ - factory: OssifiableProxy__factory, - args: [ - expectedL1TokenBridgeImplAddress, - l1Params.admins.proxy, - L1LidoTokensBridge__factory.createInterface().encodeFunctionData( - "initialize", - [l1Params.admins.bridge] - ), - options?.overrides, - ], - afterDeploy: (c) => - assert.equal(c.address, expectedL1TokenBridgeProxyAddress), - }) - .addStep({ - factory: TokenRateNotifier__factory, - args: [ - options?.overrides, - ], - afterDeploy: (c) => - assert.equal(c.address, expectedL1TokenRateNotifierImplAddress), - }) - .addStep({ - factory: OpStackTokenRatePusher__factory, - args: [ - optAddresses.L1CrossDomainMessenger, - l1Token, - expectedL2TokenRateOracleProxyAddress, - 1000, - options?.overrides, - ], - afterDeploy: (c) => - assert.equal(c.address, expectedL1OpStackTokenRatePusherImplAddress), - }); - - const l1TokenInfo = IERC20Metadata__factory.connect( - l1Token, - l1Params.deployer - ); - - const l1TokenRebasableInfo = IERC20Metadata__factory.connect( - l1TokenRebasable, - l1Params.deployer - ); - const [decimals, l2TokenName, l2TokenSymbol, l2TokenRebasableName, l2TokenRebasableSymbol] = await Promise.all([ - l1TokenInfo.decimals(), - l2Params.l2Token?.name ?? l1TokenInfo.name(), - l2Params.l2Token?.symbol ?? l1TokenInfo.symbol(), - l2Params.l2TokenRebasable?.name ?? l1TokenRebasableInfo.name(), - l2Params.l2TokenRebasable?.symbol ?? l1TokenRebasableInfo.symbol(), - ]); - - const l2DeployScript = new L2DeployAllScript( - l2Params.deployer, - expectedL2TokenImplAddress, - expectedL2TokenProxyAddress, - expectedL2TokenRebasableImplAddress, - expectedL2TokenRebasableProxyAddress, - expectedL2TokenBridgeImplAddress, - expectedL2TokenBridgeProxyAddress, - expectedL2TokenRateOracleImplAddress, - expectedL2TokenRateOracleProxyAddress, - options?.logger - ) - .addStep({ - factory: ERC20Bridged__factory, - args: [ - l2TokenName, - l2TokenSymbol, - decimals, - expectedL2TokenBridgeProxyAddress, - options?.overrides, - ], - afterDeploy: (c) => - assert.equal(c.address, expectedL2TokenImplAddress), - }) - .addStep({ - factory: OssifiableProxy__factory, - args: [ - expectedL2TokenImplAddress, - l2Params.admins.proxy, - ERC20Bridged__factory.createInterface().encodeFunctionData( - "initialize", - [l2TokenName, l2TokenSymbol] - ), - options?.overrides, - ], - afterDeploy: (c) => - assert.equal(c.address, expectedL2TokenProxyAddress), - }) - .addStep({ - factory: ERC20Rebasable__factory, - args: [ - l2TokenRebasableName, - l2TokenRebasableSymbol, - decimals, - expectedL2TokenProxyAddress, - expectedL2TokenRateOracleProxyAddress, - expectedL2TokenBridgeProxyAddress, - options?.overrides, - ], - afterDeploy: (c) => - assert.equal(c.address, expectedL2TokenRebasableImplAddress), - }) - .addStep({ - factory: OssifiableProxy__factory, - args: [ - expectedL2TokenRebasableImplAddress, - l2Params.admins.proxy, - ERC20Rebasable__factory.createInterface().encodeFunctionData( - "initialize", - [l2TokenRebasableName, l2TokenRebasableSymbol] - ), - options?.overrides, - ], - afterDeploy: (c) => - assert.equal(c.address, expectedL2TokenRebasableProxyAddress), - }) - .addStep({ - factory: L2ERC20TokenBridge__factory, - args: [ - optAddresses.L2CrossDomainMessenger, - expectedL1TokenBridgeProxyAddress, - l1Token, - l1TokenRebasable, - expectedL2TokenProxyAddress, - expectedL2TokenRebasableProxyAddress, - options?.overrides, - ], - afterDeploy: (c) => - assert.equal(c.address, expectedL2TokenBridgeImplAddress), - }) - .addStep({ - factory: OssifiableProxy__factory, - args: [ - expectedL2TokenBridgeImplAddress, - l2Params.admins.proxy, - L2ERC20TokenBridge__factory.createInterface().encodeFunctionData( - "initialize", - [l2Params.admins.bridge] - ), - options?.overrides, - ], - }) - .addStep({ - factory: TokenRateOracle__factory, - args: [ - optAddresses.L2CrossDomainMessenger, - expectedL2TokenBridgeProxyAddress, - expectedL1OpStackTokenRatePusherImplAddress, - 86400, - options?.overrides, - ], - afterDeploy: (c) => - assert.equal(c.address, expectedL2TokenRateOracleImplAddress), - }) - .addStep({ - factory: OssifiableProxy__factory, - args: [ - expectedL2TokenRateOracleImplAddress, - l2Params.admins.proxy, - [], - options?.overrides, - ], - afterDeploy: (c) => - assert.equal(c.address, expectedL2TokenRateOracleProxyAddress), - }); - - return [l1DeployScript as L1DeployAllScript, l2DeployScript as L2DeployAllScript]; - }, - }; -} diff --git a/utils/optimism/deploymentAllFromScratch.ts b/utils/optimism/deploymentAllFromScratch.ts new file mode 100644 index 00000000..6b95f1b1 --- /dev/null +++ b/utils/optimism/deploymentAllFromScratch.ts @@ -0,0 +1,320 @@ +import { assert } from "chai"; +import { Wallet } from "ethers"; +import addresses from "./addresses"; +import { OptDeploymentOptions, DeployScriptParams } from "./types"; +import network, { NetworkName } from "../network"; +import { DeployScript, Logger } from "../deployment/DeployScript"; +import { + ERC20Bridged__factory, + ERC20Rebasable__factory, + IERC20Metadata__factory, + L1LidoTokensBridge__factory, + L2ERC20TokenBridge__factory, + OssifiableProxy__factory, + TokenRateOracle__factory, + TokenRateNotifier__factory, + OpStackTokenRatePusher__factory +} from "../../typechain"; + +interface OptL1DeployScriptParams extends DeployScriptParams { +} +interface OptL2DeployScriptParams extends DeployScriptParams { + l2Token?: { + name?: string; + symbol?: string + }; + l2TokenRebasable?: { + name?: string; + symbol?: string + }; +} + +export class L1DeployAllScript extends DeployScript { + + constructor( + deployer: Wallet, + bridgeImplAddress: string, + bridgeProxyAddress: string, + tokenRateNotifierImplAddress: string, + opStackTokenRatePusherImplAddress: string, + logger?: Logger + ) { + super(deployer, logger); + this.bridgeImplAddress = bridgeImplAddress; + this.bridgeProxyAddress = bridgeProxyAddress; + this.tokenRateNotifierImplAddress = tokenRateNotifierImplAddress; + this.opStackTokenRatePusherImplAddress = opStackTokenRatePusherImplAddress; + } + + public bridgeImplAddress: string; + public bridgeProxyAddress: string; + public tokenRateNotifierImplAddress: string; + public opStackTokenRatePusherImplAddress: string; +} + +export class L2DeployAllScript extends DeployScript { + + constructor( + deployer: Wallet, + tokenImplAddress: string, + tokenProxyAddress: string, + tokenRebasableImplAddress: string, + tokenRebasableProxyAddress: string, + tokenBridgeImplAddress: string, + tokenBridgeProxyAddress: string, + tokenRateOracleImplAddress: string, + tokenRateOracleProxyAddress: string, + logger?: Logger + ) { + super(deployer, logger); + this.tokenImplAddress = tokenImplAddress; + this.tokenProxyAddress = tokenProxyAddress; + this.tokenRebasableImplAddress = tokenRebasableImplAddress; + this.tokenRebasableProxyAddress = tokenRebasableProxyAddress; + this.tokenBridgeImplAddress = tokenBridgeImplAddress; + this.tokenBridgeProxyAddress = tokenBridgeProxyAddress; + this.tokenRateOracleImplAddress = tokenRateOracleImplAddress; + this.tokenRateOracleProxyAddress = tokenRateOracleProxyAddress; + } + + public tokenImplAddress: string; + public tokenProxyAddress: string; + public tokenRebasableImplAddress: string; + public tokenRebasableProxyAddress: string; + public tokenBridgeImplAddress: string; + public tokenBridgeProxyAddress: string; + public tokenRateOracleImplAddress: string; + public tokenRateOracleProxyAddress: string; +} + +/// deploys from scratch +/// - wstETH on L2 +/// - stETH on L2 +/// - bridgeL1 +/// - bridgeL2 +/// - Oracle +export default function deploymentAll( + networkName: NetworkName, + options: OptDeploymentOptions = {} +) { + const optAddresses = addresses(networkName, options); + return { + async deployAllScript( + l1Token: string, + l1TokenRebasable: string, + l1Params: OptL1DeployScriptParams, + l2Params: OptL2DeployScriptParams, + ): Promise<[L1DeployAllScript, L2DeployAllScript]> { + + const [ + expectedL1TokenBridgeImplAddress, + expectedL1TokenBridgeProxyAddress, + expectedL1TokenRateNotifierImplAddress, + expectedL1OpStackTokenRatePusherImplAddress, + ] = await network.predictAddresses(l1Params.deployer, l1Params.contractsShift + 4); + + const [ + expectedL2TokenImplAddress, + expectedL2TokenProxyAddress, + expectedL2TokenRebasableImplAddress, + expectedL2TokenRebasableProxyAddress, + expectedL2TokenBridgeImplAddress, + expectedL2TokenBridgeProxyAddress, + expectedL2TokenRateOracleImplAddress, + expectedL2TokenRateOracleProxyAddress + ] = await network.predictAddresses(l2Params.deployer, l2Params.contractsShift + 8); + + const l1DeployScript = new L1DeployAllScript( + l1Params.deployer, + expectedL1TokenBridgeImplAddress, + expectedL1TokenBridgeProxyAddress, + expectedL1TokenRateNotifierImplAddress, + expectedL1OpStackTokenRatePusherImplAddress, + options?.logger + ) + .addStep({ + factory: L1LidoTokensBridge__factory, + args: [ + optAddresses.L1CrossDomainMessenger, + expectedL2TokenBridgeProxyAddress, + l1Token, + l1TokenRebasable, + expectedL2TokenProxyAddress, + expectedL2TokenRebasableProxyAddress, + options?.overrides, + ], + afterDeploy: (c) => + assert.equal(c.address, expectedL1TokenBridgeImplAddress), + }) + .addStep({ + factory: OssifiableProxy__factory, + args: [ + expectedL1TokenBridgeImplAddress, + l1Params.admins.proxy, + L1LidoTokensBridge__factory.createInterface().encodeFunctionData( + "initialize", + [l1Params.admins.bridge] + ), + options?.overrides, + ], + afterDeploy: (c) => + assert.equal(c.address, expectedL1TokenBridgeProxyAddress), + }) + .addStep({ + factory: TokenRateNotifier__factory, + args: [ + options?.overrides, + ], + afterDeploy: (c) => + assert.equal(c.address, expectedL1TokenRateNotifierImplAddress), + }) + .addStep({ + factory: OpStackTokenRatePusher__factory, + args: [ + optAddresses.L1CrossDomainMessenger, + l1Token, + expectedL2TokenRateOracleProxyAddress, + 1000, + options?.overrides, + ], + afterDeploy: (c) => + assert.equal(c.address, expectedL1OpStackTokenRatePusherImplAddress), + }); + + const l1TokenInfo = IERC20Metadata__factory.connect( + l1Token, + l1Params.deployer + ); + + const l1TokenRebasableInfo = IERC20Metadata__factory.connect( + l1TokenRebasable, + l1Params.deployer + ); + const [decimals, l2TokenName, l2TokenSymbol, l2TokenRebasableName, l2TokenRebasableSymbol] = await Promise.all([ + l1TokenInfo.decimals(), + l2Params.l2Token?.name ?? l1TokenInfo.name(), + l2Params.l2Token?.symbol ?? l1TokenInfo.symbol(), + l2Params.l2TokenRebasable?.name ?? l1TokenRebasableInfo.name(), + l2Params.l2TokenRebasable?.symbol ?? l1TokenRebasableInfo.symbol(), + ]); + + const l2DeployScript = new L2DeployAllScript( + l2Params.deployer, + expectedL2TokenImplAddress, + expectedL2TokenProxyAddress, + expectedL2TokenRebasableImplAddress, + expectedL2TokenRebasableProxyAddress, + expectedL2TokenBridgeImplAddress, + expectedL2TokenBridgeProxyAddress, + expectedL2TokenRateOracleImplAddress, + expectedL2TokenRateOracleProxyAddress, + options?.logger + ) + .addStep({ + factory: ERC20Bridged__factory, + args: [ + l2TokenName, + l2TokenSymbol, + decimals, + expectedL2TokenBridgeProxyAddress, + options?.overrides, + ], + afterDeploy: (c) => + assert.equal(c.address, expectedL2TokenImplAddress), + }) + .addStep({ + factory: OssifiableProxy__factory, + args: [ + expectedL2TokenImplAddress, + l2Params.admins.proxy, + ERC20Bridged__factory.createInterface().encodeFunctionData( + "initialize", + [l2TokenName, l2TokenSymbol] + ), + options?.overrides, + ], + afterDeploy: (c) => + assert.equal(c.address, expectedL2TokenProxyAddress), + }) + .addStep({ + factory: ERC20Rebasable__factory, + args: [ + l2TokenRebasableName, + l2TokenRebasableSymbol, + decimals, + expectedL2TokenProxyAddress, + expectedL2TokenRateOracleProxyAddress, + expectedL2TokenBridgeProxyAddress, + options?.overrides, + ], + afterDeploy: (c) => + assert.equal(c.address, expectedL2TokenRebasableImplAddress), + }) + .addStep({ + factory: OssifiableProxy__factory, + args: [ + expectedL2TokenRebasableImplAddress, + l2Params.admins.proxy, + ERC20Rebasable__factory.createInterface().encodeFunctionData( + "initialize", + [l2TokenRebasableName, l2TokenRebasableSymbol] + ), + options?.overrides, + ], + afterDeploy: (c) => + assert.equal(c.address, expectedL2TokenRebasableProxyAddress), + }) + .addStep({ + factory: L2ERC20TokenBridge__factory, + args: [ + optAddresses.L2CrossDomainMessenger, + expectedL1TokenBridgeProxyAddress, + l1Token, + l1TokenRebasable, + expectedL2TokenProxyAddress, + expectedL2TokenRebasableProxyAddress, + options?.overrides, + ], + afterDeploy: (c) => + assert.equal(c.address, expectedL2TokenBridgeImplAddress), + }) + .addStep({ + factory: OssifiableProxy__factory, + args: [ + expectedL2TokenBridgeImplAddress, + l2Params.admins.proxy, + L2ERC20TokenBridge__factory.createInterface().encodeFunctionData( + "initialize", + [l2Params.admins.bridge] + ), + options?.overrides, + ], + }) + .addStep({ + factory: TokenRateOracle__factory, + args: [ + optAddresses.L2CrossDomainMessenger, + expectedL2TokenBridgeProxyAddress, + expectedL1OpStackTokenRatePusherImplAddress, + 86400, + options?.overrides, + ], + afterDeploy: (c) => + assert.equal(c.address, expectedL2TokenRateOracleImplAddress), + }) + .addStep({ + factory: OssifiableProxy__factory, + args: [ + expectedL2TokenRateOracleImplAddress, + l2Params.admins.proxy, + [], + options?.overrides, + ], + afterDeploy: (c) => + assert.equal(c.address, expectedL2TokenRateOracleProxyAddress), + }); + + return [l1DeployScript as L1DeployAllScript, l2DeployScript as L2DeployAllScript]; + }, + }; +} diff --git a/utils/optimism/deploymentNewImplementations.ts b/utils/optimism/deploymentNewImplementations.ts new file mode 100644 index 00000000..706dbf41 --- /dev/null +++ b/utils/optimism/deploymentNewImplementations.ts @@ -0,0 +1,229 @@ +import { assert } from "chai"; +import { Wallet } from "ethers"; +import addresses from "./addresses"; +import { OptDeploymentOptions, DeployScriptParams } from "./types"; +import network, { NetworkName } from "../network"; +import { DeployScript, Logger } from "../deployment/DeployScript"; +import { + ERC20Bridged__factory, + ERC20Rebasable__factory, + IERC20Metadata__factory, + L1LidoTokensBridge__factory, + L2ERC20TokenBridge__factory, + OssifiableProxy__factory, + TokenRateOracle__factory +} from "../../typechain"; + +interface OptL1DeployScriptParams extends DeployScriptParams { + tokenProxyAddress: string; + tokenRebasableProxyAddress: string; + opStackTokenRatePusherImplAddress: string; + tokenBridgeProxyAddress: string; + deployer: Wallet; + admins: { + proxy: string; + bridge: string + }; + contractsShift: number; +} + +interface OptL2DeployScriptParams extends DeployScriptParams { + tokenBridgeProxyAddress: string; + tokenProxyAddress: string; + tokenRateOracleProxyAddress: string; + tokenRateOracleRateOutdatedDelay: number; + token?: { + name?: string; + symbol?: string + }; + tokenRebasable?: { + name?: string; + symbol?: string + }; +} + +export class BridgeL1DeployScript extends DeployScript { + + constructor( + deployer: Wallet, + bridgeImplAddress: string, + logger?: Logger + ) { + super(deployer, logger); + this.bridgeImplAddress = bridgeImplAddress; + } + + public bridgeImplAddress: string; +} + +export class BridgeL2DeployScript extends DeployScript { + + constructor( + deployer: Wallet, + tokenImplAddress: string, + tokenRebasableImplAddress: string, + tokenRebasableProxyAddress: string, + tokenBridgeImplAddress: string, + tokenRateOracleImplAddress: string, + logger?: Logger + ) { + super(deployer, logger); + this.tokenImplAddress = tokenImplAddress; + this.tokenRebasableImplAddress = tokenRebasableImplAddress; + this.tokenRebasableProxyAddress = tokenRebasableProxyAddress; + this.tokenBridgeImplAddress = tokenBridgeImplAddress; + this.tokenRateOracleImplAddress = tokenRateOracleImplAddress; + } + + public tokenImplAddress: string; + public tokenRebasableImplAddress: string; + public tokenRebasableProxyAddress: string; + public tokenBridgeImplAddress: string; + public tokenRateOracleImplAddress: string; +} + +/// deploys +/// - new L1Bridge Impl +/// - new L2Bridge Impl +/// - RebasableToken(stETH) Impl and Proxy (because it was never deployed before) +/// - Non-rebasable token (wstETH) new Impl with Permissions +export default function deploymentNewImplementations( + networkName: NetworkName, + options: OptDeploymentOptions = {} +) { + const optAddresses = addresses(networkName, options); + return { + async deployScript( + l1Params: OptL1DeployScriptParams, + l2Params: OptL2DeployScriptParams, + ): Promise<[BridgeL1DeployScript, BridgeL2DeployScript]> { + + const [ + expectedL1TokenBridgeImplAddress, + ] = await network.predictAddresses(l1Params.deployer, l1Params.contractsShift + 1); + + const [ + expectedL2TokenImplAddress, + expectedL2TokenRebasableImplAddress, + expectedL2TokenRebasableProxyAddress, + expectedL2TokenBridgeImplAddress, + expectedL2TokenRateOracleImplAddress + ] = await network.predictAddresses(l2Params.deployer, l2Params.contractsShift + 5); + + const l1DeployScript = new BridgeL1DeployScript( + l1Params.deployer, + expectedL1TokenBridgeImplAddress, + options?.logger + ) + .addStep({ + factory: L1LidoTokensBridge__factory, + args: [ + optAddresses.L1CrossDomainMessenger, + l2Params.tokenBridgeProxyAddress, + l1Params.tokenProxyAddress, + l1Params.tokenRebasableProxyAddress, + l2Params.tokenProxyAddress, + expectedL2TokenRebasableProxyAddress, + options?.overrides, + ], + afterDeploy: (c) => + assert.equal(c.address, expectedL1TokenBridgeImplAddress), + }); + + const l1TokenInfo = IERC20Metadata__factory.connect( + l1Params.tokenProxyAddress, + l1Params.deployer + ); + + const l1TokenRebasableInfo = IERC20Metadata__factory.connect( + l1Params.tokenRebasableProxyAddress, + l1Params.deployer + ); + const [decimals, l2TokenName, l2TokenSymbol, l2TokenRebasableName, l2TokenRebasableSymbol] = await Promise.all([ + l1TokenInfo.decimals(), + l2Params.token?.name ?? l1TokenInfo.name(), + l2Params.token?.symbol ?? l1TokenInfo.symbol(), + l2Params.tokenRebasable?.name ?? l1TokenRebasableInfo.name(), + l2Params.tokenRebasable?.symbol ?? l1TokenRebasableInfo.symbol(), + ]); + + const l2DeployScript = new BridgeL2DeployScript( + l2Params.deployer, + expectedL2TokenImplAddress, + expectedL2TokenRebasableImplAddress, + expectedL2TokenRebasableProxyAddress, + expectedL2TokenBridgeImplAddress, + expectedL2TokenRateOracleImplAddress, + options?.logger + ) + .addStep({ + factory: ERC20Bridged__factory, + args: [ + l2TokenName, + l2TokenSymbol, + decimals, + l2Params.tokenBridgeProxyAddress, + options?.overrides, + ], + afterDeploy: (c) => + assert.equal(c.address, expectedL2TokenImplAddress), + }) + .addStep({ + factory: ERC20Rebasable__factory, + args: [ + l2TokenRebasableName, + l2TokenRebasableSymbol, + decimals, + l2Params.tokenProxyAddress, + l2Params.tokenRateOracleProxyAddress, + l2Params.tokenBridgeProxyAddress, + options?.overrides, + ], + afterDeploy: (c) => + assert.equal(c.address, expectedL2TokenRebasableImplAddress), + }) + .addStep({ + factory: OssifiableProxy__factory, + args: [ + expectedL2TokenRebasableImplAddress, + l2Params.admins.proxy, + ERC20Rebasable__factory.createInterface().encodeFunctionData( + "initialize", + [l2TokenRebasableName, l2TokenRebasableSymbol] + ), + options?.overrides, + ], + afterDeploy: (c) => + assert.equal(c.address, expectedL2TokenRebasableProxyAddress), + }) + .addStep({ + factory: L2ERC20TokenBridge__factory, + args: [ + optAddresses.L2CrossDomainMessenger, + l1Params.tokenBridgeProxyAddress, + l1Params.tokenProxyAddress, + l1Params.tokenRebasableProxyAddress, + l2Params.tokenProxyAddress, + expectedL2TokenRebasableProxyAddress, + options?.overrides, + ], + afterDeploy: (c) => + assert.equal(c.address, expectedL2TokenBridgeImplAddress), + }) + .addStep({ + factory: TokenRateOracle__factory, + args: [ + optAddresses.L2CrossDomainMessenger, + l2Params.tokenBridgeProxyAddress, + l1Params.opStackTokenRatePusherImplAddress, + l2Params.tokenRateOracleRateOutdatedDelay, + options?.overrides, + ], + afterDeploy: (c) => + assert.equal(c.address, expectedL2TokenRateOracleImplAddress), + }); + + return [l1DeployScript as BridgeL1DeployScript, l2DeployScript as BridgeL2DeployScript]; + }, + }; +} diff --git a/utils/optimism/deploymentOracle.ts b/utils/optimism/deploymentOracle.ts index ec28c820..002a9230 100644 --- a/utils/optimism/deploymentOracle.ts +++ b/utils/optimism/deploymentOracle.ts @@ -1,8 +1,8 @@ import { assert } from "chai"; -import { Overrides, Wallet } from "ethers"; +import { Wallet } from "ethers"; import { ethers } from "hardhat"; import addresses from "./addresses"; -import { CommonOptions } from "./types"; +import { DeployScriptParams, OptDeploymentOptions } from "./types"; import network, { NetworkName } from "../network"; import { DeployScript, Logger } from "../deployment/DeployScript"; import { @@ -10,20 +10,10 @@ import { TokenRateOracle__factory, TokenRateNotifier__factory, OpStackTokenRatePusher__factory - } from "../../typechain"; - -interface OptDeployScriptParams { - deployer: Wallet; - admins: { proxy: string; bridge: string }; -} - -interface OptDeploymentOptions extends CommonOptions { - logger?: Logger; - overrides?: Overrides; -} +} from "../../typechain"; +interface OptDeployScriptParams extends DeployScriptParams {} export class OracleL1DeployScript extends DeployScript { - constructor( deployer: Wallet, tokenRateNotifierImplAddress: string, @@ -40,7 +30,6 @@ export class OracleL1DeployScript extends DeployScript { } export class OracleL2DeployScript extends DeployScript { - constructor( deployer: Wallet, tokenRateOracleImplAddress: string, @@ -50,7 +39,7 @@ export class OracleL2DeployScript extends DeployScript { super(deployer, logger); this.tokenRateOracleImplAddress = tokenRateOracleImplAddress; this.tokenRateOracleProxyAddress = tokenRateOracleProxyAddress; - } + } public tokenRateOracleImplAddress: string; public tokenRateOracleProxyAddress: string; @@ -59,83 +48,85 @@ export class OracleL2DeployScript extends DeployScript { export default function deploymentOracle( networkName: NetworkName, options: OptDeploymentOptions = {} - ) { +) { const optAddresses = addresses(networkName, options); return { - async oracleDeployScript( - l1Token: string, - l1Params: OptDeployScriptParams, - l2Params: OptDeployScriptParams, - ): Promise<[OracleL1DeployScript, OracleL2DeployScript]> { + async oracleDeployScript( + l1Token: string, + l2GasLimitForPushingTokenRate: number, + rateOutdatedDelay: number, + l1Params: OptDeployScriptParams, + l2Params: OptDeployScriptParams, + ): Promise<[OracleL1DeployScript, OracleL2DeployScript]> { - const [ - expectedL1TokenRateNotifierImplAddress, - expectedL1OpStackTokenRatePusherImplAddress, - ] = await network.predictAddresses(l1Params.deployer, 2); + const [ + expectedL1TokenRateNotifierImplAddress, + expectedL1OpStackTokenRatePusherImplAddress, + ] = await network.predictAddresses(l1Params.deployer, 2); - const [ - expectedL2TokenRateOracleImplAddress, - expectedL2TokenRateOracleProxyAddress - ] = await network.predictAddresses(l2Params.deployer, 2); + const [ + expectedL2TokenRateOracleImplAddress, + expectedL2TokenRateOracleProxyAddress + ] = await network.predictAddresses(l2Params.deployer, 2); - const l1DeployScript = new OracleL1DeployScript( - l1Params.deployer, - expectedL1TokenRateNotifierImplAddress, - expectedL1OpStackTokenRatePusherImplAddress, - options?.logger - ) - .addStep({ - factory: TokenRateNotifier__factory, - args: [ - options?.overrides, - ], - afterDeploy: (c) => - assert.equal(c.address, expectedL1TokenRateNotifierImplAddress), - }) - .addStep({ - factory: OpStackTokenRatePusher__factory, - args: [ - optAddresses.L1CrossDomainMessenger, - l1Token, - expectedL2TokenRateOracleProxyAddress, - 1000, - options?.overrides, - ], - afterDeploy: (c) => - assert.equal(c.address, expectedL1OpStackTokenRatePusherImplAddress), - }); + const l1DeployScript = new OracleL1DeployScript( + l1Params.deployer, + expectedL1TokenRateNotifierImplAddress, + expectedL1OpStackTokenRatePusherImplAddress, + options?.logger + ) + .addStep({ + factory: TokenRateNotifier__factory, + args: [ + options?.overrides, + ], + afterDeploy: (c) => + assert.equal(c.address, expectedL1TokenRateNotifierImplAddress), + }) + .addStep({ + factory: OpStackTokenRatePusher__factory, + args: [ + optAddresses.L1CrossDomainMessenger, + l1Token, + expectedL2TokenRateOracleProxyAddress, + l2GasLimitForPushingTokenRate, + options?.overrides, + ], + afterDeploy: (c) => + assert.equal(c.address, expectedL1OpStackTokenRatePusherImplAddress), + }); - const l2DeployScript = new OracleL2DeployScript( - l2Params.deployer, - expectedL2TokenRateOracleImplAddress, - expectedL2TokenRateOracleProxyAddress, - options?.logger - ) - .addStep({ - factory: TokenRateOracle__factory, - args: [ - optAddresses.L2CrossDomainMessenger, - ethers.constants.AddressZero, - expectedL1OpStackTokenRatePusherImplAddress, - 86400, - options?.overrides, - ], - afterDeploy: (c) => - assert.equal(c.address, expectedL2TokenRateOracleImplAddress), - }) - .addStep({ - factory: OssifiableProxy__factory, - args: [ - expectedL2TokenRateOracleImplAddress, - l2Params.admins.proxy, - [], - options?.overrides, - ], - afterDeploy: (c) => - assert.equal(c.address, expectedL2TokenRateOracleProxyAddress), - }); + const l2DeployScript = new OracleL2DeployScript( + l2Params.deployer, + expectedL2TokenRateOracleImplAddress, + expectedL2TokenRateOracleProxyAddress, + options?.logger + ) + .addStep({ + factory: TokenRateOracle__factory, + args: [ + optAddresses.L2CrossDomainMessenger, + ethers.constants.AddressZero, + expectedL1OpStackTokenRatePusherImplAddress, + rateOutdatedDelay, + options?.overrides, + ], + afterDeploy: (c) => + assert.equal(c.address, expectedL2TokenRateOracleImplAddress), + }) + .addStep({ + factory: OssifiableProxy__factory, + args: [ + expectedL2TokenRateOracleImplAddress, + l2Params.admins.proxy, + [], + options?.overrides, + ], + afterDeploy: (c) => + assert.equal(c.address, expectedL2TokenRateOracleProxyAddress), + }); - return [l1DeployScript as OracleL1DeployScript, l2DeployScript as OracleL2DeployScript]; - }, + return [l1DeployScript as OracleL1DeployScript, l2DeployScript as OracleL2DeployScript]; + }, }; - } +} diff --git a/utils/optimism/testing.ts b/utils/optimism/testing.ts index a0cd5148..e7848efe 100644 --- a/utils/optimism/testing.ts +++ b/utils/optimism/testing.ts @@ -18,7 +18,7 @@ import { } from "../../typechain"; import addresses from "./addresses"; import contracts from "./contracts"; -import deploymentAll from "./deploymentAll"; +import deploymentAll from "./deploymentAllFromScratch"; import testingUtils from "../testing"; import { BridgingManagement } from "../bridging-management"; import network, { NetworkName, SignerOrProvider } from "../network"; @@ -199,7 +199,7 @@ async function deployTestBridge( const [ethDeployScript, optDeployScript] = await deploymentAll( networkName - ).erc20TokenBridgeDeployScript( + ).deployAllScript( l1Token.address, l1TokenRebasable.address, { diff --git a/utils/optimism/types.ts b/utils/optimism/types.ts index 38cea940..461be4c1 100644 --- a/utils/optimism/types.ts +++ b/utils/optimism/types.ts @@ -1,3 +1,6 @@ +import { Overrides, Wallet } from "ethers"; +import { Logger } from "../deployment/DeployScript"; + export type OptContractNames = | "L1CrossDomainMessenger" | "L2CrossDomainMessenger"; @@ -7,3 +10,17 @@ export type CustomOptContractAddresses = Partial; export interface CommonOptions { customAddresses?: CustomOptContractAddresses; } + +export interface DeployScriptParams { + deployer: Wallet; + admins: { + proxy: string; + bridge: string + }; + contractsShift: number; +} + +export interface OptDeploymentOptions extends CommonOptions { + logger?: Logger; + overrides?: Overrides; +} diff --git a/utils/optimism/upgradeOracle.ts b/utils/optimism/upgradeOracle.ts deleted file mode 100644 index 17fd0b26..00000000 --- a/utils/optimism/upgradeOracle.ts +++ /dev/null @@ -1,79 +0,0 @@ -import { - OssifiableProxy__factory, - OptimismBridgeExecutor__factory -} from "../../typechain"; - -import network, { NetworkName } from "../network"; -import testingUtils from "../testing"; -import contracts from "./contracts"; -import testing from "../../utils/testing"; -import optimism from "../../utils/optimism"; -import { getBridgeExecutorParams } from "../../utils/bridge-executor"; - -export async function upgradeOracle( - networkName: NetworkName, - oracleProxyAddress: string, - newOracleAddress: string - ) { - const ethOptNetworks = network.multichain(["eth", "opt"], networkName); - const [ - ethProvider, - optProvider - ] = ethOptNetworks.getProviders({ forking: true }); - const ethDeployer = testing.accounts.deployer(ethProvider); - const optDeployer = testing.accounts.deployer(optProvider); - - - const optContracts = contracts(networkName, { forking: true }); - const l1CrossDomainMessengerAliased = await testingUtils.impersonate( - testingUtils.accounts.applyL1ToL2Alias(optContracts.L1CrossDomainMessenger.address), - optProvider - ); - const l2CrossDomainMessenger = await optContracts.L2CrossDomainMessenger.connect( - l1CrossDomainMessengerAliased - ); - - - const testingOnDeployedContracts = testing.env.USE_DEPLOYED_CONTRACTS(false); - const optAddresses = optimism.addresses(networkName); - const govBridgeExecutor = testingOnDeployedContracts - ? OptimismBridgeExecutor__factory.connect( - testing.env.OPT_GOV_BRIDGE_EXECUTOR(), - optProvider - ) - : await new OptimismBridgeExecutor__factory(optDeployer).deploy( - optAddresses.L2CrossDomainMessenger, - ethDeployer.address, - ...getBridgeExecutorParams(), - optDeployer.address - ); - - - const l1EthGovExecutorAddress = await govBridgeExecutor.getEthereumGovernanceExecutor(); - const bridgeExecutor = govBridgeExecutor.connect(optDeployer); - const l2OracleProxy = OssifiableProxy__factory.connect( - oracleProxyAddress, - optDeployer - ); - - await l2CrossDomainMessenger.relayMessage( - 0, - l1EthGovExecutorAddress, - bridgeExecutor.address, - 0, - 300_000, - bridgeExecutor.interface.encodeFunctionData("queue", [ - [oracleProxyAddress], - [0], - ["proxy__upgradeTo(address)"], - [ - "0x" + - l2OracleProxy.interface - .encodeFunctionData("proxy__upgradeTo", [newOracleAddress]) - .substring(10), - ], - [false], - ]), - { gasLimit: 5_000_000 } - ); -} From f563fab9045c620d6fbbefbbb9ffef6b51d19b9e Mon Sep 17 00:00:00 2001 From: Anton Kovalchuk Date: Tue, 9 Apr 2024 23:02:58 +0200 Subject: [PATCH 054/148] add permit to non-rebasable token --- contracts/token/ERC20BridgedPermit.sol | 35 ++++++++ contracts/token/ERC20Permit.sol | 107 +++++++++++++++++++++++ contracts/token/ERC20Rebasable.sol | 24 ++++- contracts/token/ERC20RebasablePermit.sol | 92 ++----------------- test/token/ERC20Rebasable.unit.test.ts | 6 ++ 5 files changed, 179 insertions(+), 85 deletions(-) create mode 100644 contracts/token/ERC20BridgedPermit.sol create mode 100644 contracts/token/ERC20Permit.sol diff --git a/contracts/token/ERC20BridgedPermit.sol b/contracts/token/ERC20BridgedPermit.sol new file mode 100644 index 00000000..d936ae37 --- /dev/null +++ b/contracts/token/ERC20BridgedPermit.sol @@ -0,0 +1,35 @@ +// SPDX-FileCopyrightText: 2024 OpenZeppelin, Lido +// SPDX-License-Identifier: GPL-3.0 + +pragma solidity 0.8.10; + +import {ERC20Bridged} from "./ERC20Bridged.sol"; +import {ERC20Permit} from "./ERC20Permit.sol"; + +contract ERC20BridgedPermit is ERC20Bridged, ERC20Permit { + + /// @param name_ The name of the token + /// @param symbol_ The symbol of the token + /// @param version_ The current major version of the signing domain (aka token version) + /// @param decimals_ The decimals places of the token + /// @param bridge_ The bridge address which allowd to mint/burn tokens + constructor( + string memory name_, + string memory symbol_, + string memory version_, + uint8 decimals_, + address bridge_ + ) + ERC20Bridged(name_, symbol_, decimals_, bridge_) + ERC20Permit(name_, version_) + { + } + + function _permitAccepted( + address owner_, + address spender_, + uint256 amount_ + ) internal override { + _approve(owner_, spender_, amount_); + } +} diff --git a/contracts/token/ERC20Permit.sol b/contracts/token/ERC20Permit.sol new file mode 100644 index 00000000..634731c7 --- /dev/null +++ b/contracts/token/ERC20Permit.sol @@ -0,0 +1,107 @@ +// SPDX-FileCopyrightText: 2024 OpenZeppelin, Lido +// SPDX-License-Identifier: GPL-3.0 + +pragma solidity 0.8.10; + +import {UnstructuredStorage} from "./UnstructuredStorage.sol"; +import {EIP712} from "@openzeppelin/contracts-v4.9/utils/cryptography/EIP712.sol"; +import {IERC2612} from "@openzeppelin/contracts-v4.9/interfaces/IERC2612.sol"; +import {SignatureChecker} from "../lib/SignatureChecker.sol"; + +contract ERC20Permit is IERC2612, EIP712 { + using UnstructuredStorage for bytes32; + + /** + * @dev Nonces for ERC-2612 (Permit) + */ + mapping(address => uint256) internal noncesByAddress; + + // TODO: outline structured storage used because at least EIP712 uses it + + /** + * @dev Typehash constant for ERC-2612 (Permit) + * + * keccak256("Permit(address owner,address spender,uint256 value,uint256 nonce,uint256 deadline)") + */ + bytes32 internal constant PERMIT_TYPEHASH = + 0x6e71edae12b1b97f4d1f60370fef10105fa2faae0126114a169c64845d6126c9; + + /// @param name_ The name of the token + /// @param version_ The current major version of the signing domain (aka token version) + constructor( + string memory name_, + string memory version_ + ) EIP712(name_, version_) + { + } + + /** + * @dev Sets `value` as the allowance of `spender` over ``owner``'s tokens, + * given ``owner``'s signed approval. + * Emits an {Approval} event. + * + * Requirements: + * + * - `spender` cannot be the zero address. + * - `deadline` must be a timestamp in the future. + * - `v`, `r` and `s` must be a valid `secp256k1` signature from `owner` + * over the EIP712-formatted function arguments. + * - the signature must use ``owner``'s current nonce (see {nonces}). + */ + function permit( + address _owner, address _spender, uint256 _value, uint256 _deadline, uint8 _v, bytes32 _r, bytes32 _s + ) external { + if (block.timestamp > _deadline) { + revert ErrorDeadlineExpired(); + } + + bytes32 structHash = keccak256( + abi.encode(PERMIT_TYPEHASH, _owner, _spender, _value, _useNonce(_owner), _deadline) + ); + + bytes32 hash = _hashTypedDataV4(structHash); + + if (!SignatureChecker.isValidSignatureNow(_owner, hash, _v, _r, _s)) { + revert ErrorInvalidSignature(); + } + + _permitAccepted(_owner, _spender, _value); + } + + function _permitAccepted( + address owner_, + address spender_, + uint256 amount_ + ) internal virtual { + } + + /** + * @dev Returns the current nonce for `owner`. This value must be + * included whenever a signature is generated for {permit}. + * + * Every successful call to {permit} increases ``owner``'s nonce by one. This + * prevents a signature from being used multiple times. + */ + function nonces(address owner) external view returns (uint256) { + return noncesByAddress[owner]; + } + + /** + * @dev Returns the domain separator used in the encoding of the signature for {permit}, as defined by {EIP712}. + */ + // solhint-disable-next-line func-name-mixedcase + function DOMAIN_SEPARATOR() external view returns (bytes32) { + return _domainSeparatorV4(); + } + + /** + * @dev "Consume a nonce": return the current value and increment. + */ + function _useNonce(address _owner) internal returns (uint256 current) { + current = noncesByAddress[_owner]; + noncesByAddress[_owner] = current + 1; + } + + error ErrorInvalidSignature(); + error ErrorDeadlineExpired(); +} diff --git a/contracts/token/ERC20Rebasable.sol b/contracts/token/ERC20Rebasable.sol index eb7f8751..49939548 100644 --- a/contracts/token/ERC20Rebasable.sol +++ b/contracts/token/ERC20Rebasable.sol @@ -191,7 +191,7 @@ contract ERC20Rebasable is IERC20, IERC20Wrapper, IERC20BridgedShares, ERC20Meta ) internal onlyNonZeroAccount(from_) onlyNonZeroAccount(to_) { uint256 sharesToTransfer = _getSharesByTokens(amount_); _transferShares(from_, to_, sharesToTransfer); - emit Transfer(from_, to_, amount_); + _emitTransferEvents(from_, to_, amount_ ,sharesToTransfer); } /// @dev Updates owner_'s allowance for spender_ based on spent amount_. Does not update @@ -271,7 +271,8 @@ contract ERC20Rebasable is IERC20, IERC20Wrapper, IERC20BridgedShares, ERC20Meta ) internal onlyNonZeroAccount(recipient_) { _setTotalShares(_getTotalShares() + amount_); _getShares()[recipient_] = _getShares()[recipient_] + amount_; - emit Transfer(address(0), recipient_, amount_); + uint256 tokensAmount = _getTokensByShares(amount_); + _emitTransferEvents(address(0), recipient_, tokensAmount ,amount_); } /// @dev Destroys amount_ shares from account_, reducing the total shares supply. @@ -307,6 +308,17 @@ contract ERC20Rebasable is IERC20, IERC20Wrapper, IERC20BridgedShares, ERC20Meta _getShares()[recipient_] = _getShares()[recipient_] + sharesAmount_; } + /// @dev Emits `Transfer` and `TransferShares` events + function _emitTransferEvents( + address _from, + address _to, + uint _tokenAmount, + uint256 _sharesAmount + ) internal { + emit Transfer(_from, _to, _tokenAmount); + emit TransferShares(_from, _to, _sharesAmount); + } + /// @dev validates that account_ is not zero address modifier onlyNonZeroAccount(address account_) { if (account_ == address(0)) { @@ -323,6 +335,14 @@ contract ERC20Rebasable is IERC20, IERC20Wrapper, IERC20BridgedShares, ERC20Meta _; } + /// @notice An executed shares transfer from `sender` to `recipient`. + /// @dev emitted in pair with an ERC20-defined `Transfer` event. + event TransferShares( + address indexed from, + address indexed to, + uint256 sharesValue + ); + error ErrorZeroSharesWrap(); error ErrorZeroTokensUnwrap(); error ErrorTokenRateDecimalsIsZero(); diff --git a/contracts/token/ERC20RebasablePermit.sol b/contracts/token/ERC20RebasablePermit.sol index 22353a02..57606446 100644 --- a/contracts/token/ERC20RebasablePermit.sol +++ b/contracts/token/ERC20RebasablePermit.sol @@ -3,30 +3,10 @@ pragma solidity 0.8.10; -import {UnstructuredStorage} from "./UnstructuredStorage.sol"; import {ERC20Rebasable} from "./ERC20Rebasable.sol"; -import {EIP712} from "@openzeppelin/contracts-v4.9/utils/cryptography/EIP712.sol"; -import {IERC2612} from "@openzeppelin/contracts-v4.9/interfaces/IERC2612.sol"; -import {SignatureChecker} from "../lib/SignatureChecker.sol"; +import {ERC20Permit} from "./ERC20Permit.sol"; - -contract ERC20RebasablePermit is IERC2612, ERC20Rebasable, EIP712 { - using UnstructuredStorage for bytes32; - - /** - * @dev Nonces for ERC-2612 (Permit) - */ - mapping(address => uint256) internal noncesByAddress; - - // TODO: outline structured storage used because at least EIP712 uses it - - /** - * @dev Typehash constant for ERC-2612 (Permit) - * - * keccak256("Permit(address owner,address spender,uint256 value,uint256 nonce,uint256 deadline)") - */ - bytes32 internal constant PERMIT_TYPEHASH = - 0x6e71edae12b1b97f4d1f60370fef10105fa2faae0126114a169c64845d6126c9; +contract ERC20RebasablePermit is ERC20Rebasable, ERC20Permit { /// @param name_ The name of the token /// @param symbol_ The symbol of the token @@ -45,69 +25,15 @@ contract ERC20RebasablePermit is IERC2612, ERC20Rebasable, EIP712 { address bridge_ ) ERC20Rebasable(name_, symbol_, decimals_, wrappedToken_, tokenRateOracle_, bridge_) - EIP712(name_, version_) + ERC20Permit(name_, version_) { } - /** - * @dev Sets `value` as the allowance of `spender` over ``owner``'s tokens, - * given ``owner``'s signed approval. - * Emits an {Approval} event. - * - * Requirements: - * - * - `spender` cannot be the zero address. - * - `deadline` must be a timestamp in the future. - * - `v`, `r` and `s` must be a valid `secp256k1` signature from `owner` - * over the EIP712-formatted function arguments. - * - the signature must use ``owner``'s current nonce (see {nonces}). - */ - function permit( - address _owner, address _spender, uint256 _value, uint256 _deadline, uint8 _v, bytes32 _r, bytes32 _s - ) external { - if (block.timestamp > _deadline) { - revert ErrorDeadlineExpired(); - } - - bytes32 structHash = keccak256( - abi.encode(PERMIT_TYPEHASH, _owner, _spender, _value, _useNonce(_owner), _deadline) - ); - - bytes32 hash = _hashTypedDataV4(structHash); - - if (!SignatureChecker.isValidSignatureNow(_owner, hash, _v, _r, _s)) { - revert ErrorInvalidSignature(); - } - _approve(_owner, _spender, _value); - } - - /** - * @dev Returns the current nonce for `owner`. This value must be - * included whenever a signature is generated for {permit}. - * - * Every successful call to {permit} increases ``owner``'s nonce by one. This - * prevents a signature from being used multiple times. - */ - function nonces(address owner) external view returns (uint256) { - return noncesByAddress[owner]; - } - - /** - * @dev Returns the domain separator used in the encoding of the signature for {permit}, as defined by {EIP712}. - */ - // solhint-disable-next-line func-name-mixedcase - function DOMAIN_SEPARATOR() external view returns (bytes32) { - return _domainSeparatorV4(); + function _permitAccepted( + address owner_, + address spender_, + uint256 amount_ + ) internal override { + _approve(owner_, spender_, amount_); } - - /** - * @dev "Consume a nonce": return the current value and increment. - */ - function _useNonce(address _owner) internal returns (uint256 current) { - current = noncesByAddress[_owner]; - noncesByAddress[_owner] = current + 1; - } - - error ErrorInvalidSignature(); - error ErrorDeadlineExpired(); } diff --git a/test/token/ERC20Rebasable.unit.test.ts b/test/token/ERC20Rebasable.unit.test.ts index e763ec09..84f7c223 100644 --- a/test/token/ERC20Rebasable.unit.test.ts +++ b/test/token/ERC20Rebasable.unit.test.ts @@ -737,7 +737,13 @@ unit("ERC20Rebasable", ctxFactory) .bridgeMintShares(recipient.address, mintAmount); // validate Transfer event was emitted + const mintAmountInTokens = await rebasableProxied.getTokensByShares(mintAmount); await assert.emits(rebasableProxied, tx, "Transfer", [ + hre.ethers.constants.AddressZero, + recipient.address, + mintAmountInTokens, + ]); + await assert.emits(rebasableProxied, tx, "TransferShares", [ hre.ethers.constants.AddressZero, recipient.address, mintAmount, From 3332e3069b25a0aa51c839a888f6fbf26ff238d9 Mon Sep 17 00:00:00 2001 From: Anton Kovalchuk Date: Wed, 10 Apr 2024 13:37:40 +0200 Subject: [PATCH 055/148] add unit tests for non-rebasable token --- test/token/ERC20Permit.unit.test.ts | 818 ++++++++++++++++------------ 1 file changed, 457 insertions(+), 361 deletions(-) diff --git a/test/token/ERC20Permit.unit.test.ts b/test/token/ERC20Permit.unit.test.ts index 81a0e20f..35ce44b3 100644 --- a/test/token/ERC20Permit.unit.test.ts +++ b/test/token/ERC20Permit.unit.test.ts @@ -1,409 +1,505 @@ import hre from "hardhat"; import { assert } from "chai"; +import { BigNumber } from "ethers"; import { unit, UnitTest } from "../../utils/testing"; import { wei } from "../../utils/wei"; import { makeDomainSeparator, signPermit, calculateTransferAuthorizationDigest, signEOAorEIP1271 } from "../../utils/testing/permit-helpers"; import testing from "../../utils/testing"; +import { SignerWithAddress } from "@nomiclabs/hardhat-ethers/signers"; import { - ERC20Bridged__factory, TokenRateOracle__factory, OssifiableProxy__factory, ERC20RebasablePermit__factory, ERC1271PermitSignerMock__factory, + ERC20BridgedPermit__factory, } from "../../typechain"; -import { BigNumber } from "ethers"; type ContextType = Awaited>> -const TOKEN_NAME = 'Liquid staked Ether 2.0' const SIGNING_DOMAIN_VERSION = '2' // aka token version, used in signing permit const MAX_UINT256 = '0xffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff' // derived from mnemonic: want believe mosquito cat design route voice cause gold benefit gospel bulk often attitude rural const ACCOUNTS_AND_KEYS = [ - { - address: '0xF4C028683CAd61ff284d265bC0F77EDd67B4e65A', - privateKey: '0x5f7edf5892efb4a5cd75dedd496598f48e579b562a70eb1360474cc83a982987', - }, - { - address: '0x7F94c1F9e4BfFccc8Cd79195554E0d83a0a5c5f2', - privateKey: '0x3fe2f6bd9dbc7d507a6cb95ec36a36787706617e34385292b66c74cd39874605', - }, + { + address: '0xF4C028683CAd61ff284d265bC0F77EDd67B4e65A', + privateKey: '0x5f7edf5892efb4a5cd75dedd496598f48e579b562a70eb1360474cc83a982987', + }, + { + address: '0x7F94c1F9e4BfFccc8Cd79195554E0d83a0a5c5f2', + privateKey: '0x3fe2f6bd9dbc7d507a6cb95ec36a36787706617e34385292b66c74cd39874605', + }, ] function getChainId() { - return hre.network.config.chainId as number; + return hre.network.config.chainId as number; } const getAccountsEOA = async () => { - return { - alice: ACCOUNTS_AND_KEYS[0], - bob: ACCOUNTS_AND_KEYS[1], - } + return { + alice: ACCOUNTS_AND_KEYS[0], + bob: ACCOUNTS_AND_KEYS[1], + } } const getAccountsEIP1271 = async () => { - const deployer = (await hre.ethers.getSigners())[0] - const alice = await new ERC1271PermitSignerMock__factory(deployer).deploy() - const bob = await new ERC1271PermitSignerMock__factory(deployer).deploy() - return { alice, bob } + const deployer = (await hre.ethers.getSigners())[0] + const alice = await new ERC1271PermitSignerMock__factory(deployer).deploy() + const bob = await new ERC1271PermitSignerMock__factory(deployer).deploy() + return { alice, bob } } -function permitTestsSuit(unitInstance: UnitTest) -{ - unitInstance - - .test("wrappedToken() :: has the same address is in constructor", async (ctx) => { - const { rebasableProxied, wrappedToken } = ctx.contracts; - assert.equal(await rebasableProxied.WRAPPED_TOKEN(), wrappedToken.address) - }) - - .test('eip712Domain() is correct', async (ctx) => { - const token = ctx.contracts.rebasableProxied - const [ , name, version, chainId, verifyingContract, , ] = await token.eip712Domain() - - assert.equal(name, TOKEN_NAME) - assert.equal(version, SIGNING_DOMAIN_VERSION) - assert.isDefined(hre.network.config.chainId) - assert.equal(chainId.toNumber(), getChainId()) - assert.equal(verifyingContract, token.address) - }) - - .test('DOMAIN_SEPARATOR() is correct', async (ctx) => { - const token = ctx.contracts.rebasableProxied - const domainSeparator = makeDomainSeparator(TOKEN_NAME, SIGNING_DOMAIN_VERSION, getChainId(), token.address) - assert.equal(await ctx.contracts.rebasableProxied.DOMAIN_SEPARATOR(), domainSeparator) - }) - - .test('grants allowance when a valid permit is given', async (ctx) => { - const token = ctx.contracts.rebasableProxied - - const { owner, spender, deadline } = ctx.permitParams - let { value } = ctx.permitParams - // create a signed permit to grant Bob permission to spend Alice's funds - // on behalf, and sign with Alice's key - let nonce = 0 - const charlie = ctx.accounts.user2 - // const charlieSigner = hre.ethers.provider.getSigner(charlie.address) - - let { v, r, s } = await signPermit(owner.address, owner, spender.address, value, deadline, nonce, ctx.domainSeparator) - - // check that the allowance is initially zero - assert.equalBN(await token.allowance(owner.address, spender.address), BigNumber.from(0)) - // check that the next nonce expected is zero - assert.equalBN(await token.nonces(owner.address), BigNumber.from(0)) - // check domain separator - assert.equal(await token.DOMAIN_SEPARATOR(), ctx.domainSeparator) - - // a third-party, Charlie (not Alice) submits the permit - // TODO: handle unpredictable gas limit somehow better than setting it to a random constant - const tx = await token.connect(charlie) - .permit(owner.address, spender.address, value, deadline, v, r, s, { gasLimit: '0xffffff' }) - - // check that allowance is updated - assert.equalBN(await token.allowance(owner.address, spender.address), BigNumber.from(value)) - await assert.emits(token, tx, 'Approval', [ owner.address, spender.address, value ]) - assert.equalBN(await token.nonces(owner.address), BigNumber.from(1)) - - - // increment nonce - nonce = 1 - value = 4e5 - ;({ v, r, s } = await signPermit(owner.address, owner, spender.address, value, deadline, nonce, ctx.domainSeparator)) - - // submit the permit - const tx2 = await token.connect(charlie).permit(owner.address, spender.address, value, deadline, v, r, s) - - // check that allowance is updated - assert.equalBN(await token.allowance(owner.address, spender.address), BigNumber.from(value)) - assert.emits(token, tx2, 'Approval', [ owner.address, spender.address, BigNumber.from(value) ] ) - assert.equalBN(await token.nonces(owner.address), BigNumber.from(2)) - }) - - - .test('reverts if the signature does not match given parameters', async (ctx) => { - const { owner, spender, value, nonce, deadline } = ctx.permitParams - const token = ctx.contracts.rebasableProxied - const charlie = ctx.accounts.user2 - - // create a signed permit - const { v, r, s } = await signPermit(owner.address, owner, spender.address, value, deadline, nonce, ctx.domainSeparator) - - // try to cheat by claiming the approved amount + 1 - await assert.revertsWith( - token.connect(charlie).permit( - owner.address, - spender.address, - value + 1, // pass more than signed value - deadline, - v, - r, - s, - ), - 'ErrorInvalidSignature()' - ) - - // check that msg is incorrect even if claim the approved amount - 1 - await assert.revertsWith( - token.connect(charlie).permit( - owner.address, - spender.address, - value - 1, // pass less than signed - deadline, - v, - r, - s, - ), - 'ErrorInvalidSignature()' - ) - }) - - .test('reverts if the signature is not signed with the right key', async (ctx) => { - const { owner, spender, value, nonce, deadline } = ctx.permitParams - const token = ctx.contracts.rebasableProxied - const spenderSigner = await hre.ethers.getSigner(spender.address) - const charlie = ctx.accounts.user2 - - // create a signed permit to grant Bob permission to spend - // Alice's funds on behalf, but sign with Bob's key instead of Alice's - const { v, r, s } = await signPermit(owner.address, spender, spender.address, value, deadline, nonce, ctx.domainSeparator) - - // try to cheat by submitting the permit that is signed by a - // wrong person - await assert.revertsWith( - token.connect(charlie).permit(owner.address, spender.address, value, deadline, v, r, s), - 'ErrorInvalidSignature()' - ) - - await testing.impersonate(spender.address) - await testing.setBalance(spender.address, wei.toBigNumber(wei`10 ether`)) - - // even Bob himself can't call permit with the invalid sig - await assert.revertsWith( - token.connect(spenderSigner).permit(owner.address, spender.address, value, deadline, v, r, s), - 'ErrorInvalidSignature()' - ) - }) - - .test('reverts if the permit is expired', async (ctx) => { - const token = ctx.contracts.rebasableProxied - const { owner, spender, value, nonce } = ctx.permitParams - const charlie = ctx.accounts.user2 - - // create a signed permit that already invalid - const deadline = ((await hre.ethers.provider.getBlock('latest')).timestamp - 1).toString() - const { v, r, s } = await signPermit(owner.address, owner, spender.address, value, deadline, nonce, ctx.domainSeparator) - - // try to submit the permit that is expired - await assert.revertsWith( - token.connect(charlie).permit(owner.address, spender.address, value, deadline, v, r, s, { gasLimit: '0xffffff' }), - 'ErrorDeadlineExpired()' - ) - - { - // create a signed permit that valid for 1 minute (approximately) - const deadline1min = ((await hre.ethers.provider.getBlock('latest')).timestamp + 60).toString() - const { v, r, s } = await signPermit(owner.address, owner, spender.address, value, deadline1min, nonce, ctx.domainSeparator) - const tx = await token.connect(charlie).permit(owner.address, spender.address, value, deadline1min, v, r, s) +function permitTestsSuit(unitInstance: UnitTest) { + unitInstance + + // .test("wrappedToken() :: has the same address is in constructor", async (ctx) => { + // const { rebasableProxied, wrappedToken } = ctx.contracts; + // assert.equal(await rebasableProxied.WRAPPED_TOKEN(), wrappedToken.address) + // }) + + .test('eip712Domain() is correct', async (ctx) => { + const token = ctx.contracts.rebasableProxied + const [, name, version, chainId, verifyingContract, ,] = await token.eip712Domain() + + assert.equal(name, ctx.constants.name) + assert.equal(version, SIGNING_DOMAIN_VERSION) + assert.isDefined(hre.network.config.chainId) + assert.equal(chainId.toNumber(), getChainId()) + assert.equal(verifyingContract, token.address) + }) + + .test('DOMAIN_SEPARATOR() is correct', async (ctx) => { + const token = ctx.contracts.rebasableProxied + const domainSeparator = makeDomainSeparator(ctx.constants.name, SIGNING_DOMAIN_VERSION, getChainId(), token.address) + assert.equal(await ctx.contracts.rebasableProxied.DOMAIN_SEPARATOR(), domainSeparator) + }) + + .test('grants allowance when a valid permit is given', async (ctx) => { + const token = ctx.contracts.rebasableProxied + + const { owner, spender, deadline } = ctx.permitParams + let { value } = ctx.permitParams + // create a signed permit to grant Bob permission to spend Alice's funds + // on behalf, and sign with Alice's key + let nonce = 0 + const charlie = ctx.accounts.user2 + + let { v, r, s } = await signPermit(owner.address, owner, spender.address, value, deadline, nonce, ctx.domainSeparator) + + // check that the allowance is initially zero + assert.equalBN(await token.allowance(owner.address, spender.address), BigNumber.from(0)) + // check that the next nonce expected is zero + assert.equalBN(await token.nonces(owner.address), BigNumber.from(0)) + // check domain separator + assert.equal(await token.DOMAIN_SEPARATOR(), ctx.domainSeparator) + + // a third-party, Charlie (not Alice) submits the permit + // TODO: handle unpredictable gas limit somehow better than setting it to a random constant + const tx = await token.connect(charlie) + .permit(owner.address, spender.address, value, deadline, v, r, s, { gasLimit: '0xffffff' }) + + // check that allowance is updated + assert.equalBN(await token.allowance(owner.address, spender.address), BigNumber.from(value)) + await assert.emits(token, tx, 'Approval', [owner.address, spender.address, value]) + assert.equalBN(await token.nonces(owner.address), BigNumber.from(1)) + + + // increment nonce + nonce = 1 + value = 4e5 + ; ({ v, r, s } = await signPermit(owner.address, owner, spender.address, value, deadline, nonce, ctx.domainSeparator)) + + // submit the permit + const tx2 = await token.connect(charlie).permit(owner.address, spender.address, value, deadline, v, r, s) + + // check that allowance is updated + assert.equalBN(await token.allowance(owner.address, spender.address), BigNumber.from(value)) + assert.emits(token, tx2, 'Approval', [owner.address, spender.address, BigNumber.from(value)]) + assert.equalBN(await token.nonces(owner.address), BigNumber.from(2)) + }) + + + .test('reverts if the signature does not match given parameters', async (ctx) => { + const { owner, spender, value, nonce, deadline } = ctx.permitParams + const token = ctx.contracts.rebasableProxied + const charlie = ctx.accounts.user2 + + // create a signed permit + const { v, r, s } = await signPermit(owner.address, owner, spender.address, value, deadline, nonce, ctx.domainSeparator) + + // try to cheat by claiming the approved amount + 1 + await assert.revertsWith( + token.connect(charlie).permit( + owner.address, + spender.address, + value + 1, // pass more than signed value + deadline, + v, + r, + s, + ), + 'ErrorInvalidSignature()' + ) + + // check that msg is incorrect even if claim the approved amount - 1 + await assert.revertsWith( + token.connect(charlie).permit( + owner.address, + spender.address, + value - 1, // pass less than signed + deadline, + v, + r, + s, + ), + 'ErrorInvalidSignature()' + ) + }) + + .test('reverts if the signature is not signed with the right key', async (ctx) => { + const { owner, spender, value, nonce, deadline } = ctx.permitParams + const token = ctx.contracts.rebasableProxied + const spenderSigner = await hre.ethers.getSigner(spender.address) + const charlie = ctx.accounts.user2 + + // create a signed permit to grant Bob permission to spend + // Alice's funds on behalf, but sign with Bob's key instead of Alice's + const { v, r, s } = await signPermit(owner.address, spender, spender.address, value, deadline, nonce, ctx.domainSeparator) + + // try to cheat by submitting the permit that is signed by a + // wrong person + await assert.revertsWith( + token.connect(charlie).permit(owner.address, spender.address, value, deadline, v, r, s), + 'ErrorInvalidSignature()' + ) + + await testing.impersonate(spender.address) + await testing.setBalance(spender.address, wei.toBigNumber(wei`10 ether`)) + + // even Bob himself can't call permit with the invalid sig + await assert.revertsWith( + token.connect(spenderSigner).permit(owner.address, spender.address, value, deadline, v, r, s), + 'ErrorInvalidSignature()' + ) + }) + + .test('reverts if the permit is expired', async (ctx) => { + const token = ctx.contracts.rebasableProxied + const { owner, spender, value, nonce } = ctx.permitParams + const charlie = ctx.accounts.user2 + + // create a signed permit that already invalid + const deadline = ((await hre.ethers.provider.getBlock('latest')).timestamp - 1).toString() + const { v, r, s } = await signPermit(owner.address, owner, spender.address, value, deadline, nonce, ctx.domainSeparator) + + // try to submit the permit that is expired + await assert.revertsWith( + token.connect(charlie).permit(owner.address, spender.address, value, deadline, v, r, s, { gasLimit: '0xffffff' }), + 'ErrorDeadlineExpired()' + ) + + { + // create a signed permit that valid for 1 minute (approximately) + const deadline1min = ((await hre.ethers.provider.getBlock('latest')).timestamp + 60).toString() + const { v, r, s } = await signPermit(owner.address, owner, spender.address, value, deadline1min, nonce, ctx.domainSeparator) + const tx = await token.connect(charlie).permit(owner.address, spender.address, value, deadline1min, v, r, s) + + assert.equalBN(await token.nonces(owner.address), BigNumber.from(1)) + assert.emits(token, tx, 'Approval', [owner, spender, BigNumber.from(value)]) + } + }) + + .test('reverts if the nonce given does not match the next nonce expected', async (ctx) => { + const token = ctx.contracts.rebasableProxied + const { owner, spender, value, deadline } = ctx.permitParams + const charlie = ctx.accounts.user2 + const nonce = 1 + // create a signed permit + const { v, r, s } = await signPermit(owner.address, owner, spender.address, value, deadline, nonce, ctx.domainSeparator) + // check that the next nonce expected is 0, not 1 + assert.equalBN(await token.nonces(owner.address), BigNumber.from(0)) + + // try to submit the permit + await assert.revertsWith( + token.connect(charlie).permit(owner.address, spender.address, value, deadline, v, r, s), + 'ErrorInvalidSignature()' + ) + }) + + .test('reverts if the permit has already been used', async (ctx) => { + const token = ctx.contracts.rebasableProxied + const { owner, spender, value, nonce, deadline } = ctx.permitParams + const charlie = ctx.accounts.user2 + // create a signed permit + const { v, r, s } = await signPermit(owner.address, owner, spender.address, value, deadline, nonce, ctx.domainSeparator) + + // submit the permit + await token.connect(charlie).permit(owner.address, spender.address, value, deadline, v, r, s) + + // try to submit the permit again + await assert.revertsWith( + token.connect(charlie).permit(owner.address, spender.address, value, deadline, v, r, s), + 'ErrorInvalidSignature()' + ) + + await testing.impersonate(owner.address) + await testing.setBalance(owner.address, wei.toBigNumber(wei`10 ether`)) + + // try to submit the permit again from Alice herself + await assert.revertsWith( + token.connect(await hre.ethers.getSigner(owner.address)).permit(owner.address, spender.address, value, deadline, v, r, s), + 'ErrorInvalidSignature()' + ) + }) + + .test('reverts if the permit has a nonce that has already been used by the signer', async (ctx) => { + const token = ctx.contracts.rebasableProxied + const { owner, spender, value, nonce, deadline } = ctx.permitParams + const charlie = ctx.accounts.user2 + // create a signed permit + const permit = await signPermit(owner.address, owner, spender.address, value, deadline, nonce, ctx.domainSeparator) + + // submit the permit + await token.connect(charlie).permit(owner.address, spender.address, value, deadline, permit.v, permit.r, permit.s) + + // create another signed permit with the same nonce, but + // with different parameters + const permit2 = await signPermit(owner.address, owner, spender.address, 1e6, deadline, nonce, ctx.domainSeparator) + + // try to submit the permit again + await assert.revertsWith( + token.connect(charlie).permit(owner.address, spender.address, 1e6, deadline, permit2.v, permit2.r, permit2.s), + 'ErrorInvalidSignature()' + ) + }) + + .test('reverts if the permit includes invalid approval parameters', async (ctx) => { + const token = ctx.contracts.rebasableProxied + const { owner, value, nonce, deadline } = ctx.permitParams + const charlie = ctx.accounts.user2 + // create a signed permit that attempts to grant allowance to the + // zero address + const spender = hre.ethers.constants.AddressZero + const { v, r, s } = await signPermit(owner.address, owner, spender, value, deadline, nonce, ctx.domainSeparator) + + // try to submit the permit with invalid approval parameters + await assert.revertsWith( + token.connect(charlie).permit(owner.address, spender, value, deadline, v, r, s), + 'ErrorAccountIsZeroAddress()' + ) + }) + + .test('reverts if the permit is not for an approval', async (ctx) => { + const token = ctx.contracts.rebasableProxied + const charlie = ctx.accounts.user2 + const { owner: from, spender: to, value, deadline: validBefore } = ctx.permitParams + // create a signed permit for a transfer + const validAfter = '0' + const nonce = '0x1234567890abcdef1234567890abcdef1234567890abcdef1234567890abcdef' + const digest = calculateTransferAuthorizationDigest( + from.address, + to.address, + value, + validAfter, + validBefore, + nonce, + ctx.domainSeparator + ) + const { v, r, s } = await signEOAorEIP1271(digest, from) + + // try to submit the transfer permit + await assert.revertsWith( + token.connect(charlie).permit(from.address, to.address, value, validBefore, v, r, s), + 'ErrorInvalidSignature()' + ) + }) + + .run(); +} - assert.equalBN(await token.nonces(owner.address), BigNumber.from(1)) - assert.emits(token, tx, 'Approval', [ owner, spender, BigNumber.from(value) ]) +function ctxFactoryFactory( + name: string, + symbol: string, + isRebasable: boolean, + signingAccountsFuncFactory: typeof getAccountsEIP1271 | typeof getAccountsEOA +) { + return async () => { + const decimalsToSet = 18; + const decimals = BigNumber.from(10).pow(decimalsToSet); + const rate = BigNumber.from('12').pow(decimalsToSet - 1); + const premintShares = wei.toBigNumber(wei`100 ether`); + const premintTokens = BigNumber.from(rate).mul(premintShares).div(decimals); + + const [ + deployer, + owner, + recipient, + spender, + holder, + stranger, + user1, + user2, + ] = await hre.ethers.getSigners(); + + await hre.network.provider.request({ + method: "hardhat_impersonateAccount", + params: [hre.ethers.constants.AddressZero], + }); + + const zero = await hre.ethers.getSigner(hre.ethers.constants.AddressZero); + + const rebasableProxied = await tokenProxied( + name, + symbol, + decimalsToSet, + rate, + isRebasable, + owner, + deployer, + holder + ); + + const { alice, bob } = await signingAccountsFuncFactory(); + + return { + accounts: { deployer, owner, recipient, spender, holder, stranger, zero, user1, user2 }, + constants: { name, symbol, decimalsToSet, decimals, premintShares, premintTokens, rate }, + contracts: { rebasableProxied }, + permitParams: { + owner: alice, + spender: bob, + value: 6e6, + nonce: 0, + deadline: MAX_UINT256, + }, + domainSeparator: makeDomainSeparator(name, SIGNING_DOMAIN_VERSION, getChainId(), rebasableProxied.address), + }; } - }) - - .test('reverts if the nonce given does not match the next nonce expected', async (ctx) => { - const token = ctx.contracts.rebasableProxied - const { owner, spender, value, deadline } = ctx.permitParams - const charlie = ctx.accounts.user2 - const nonce = 1 - // create a signed permit - const { v, r, s } = await signPermit(owner.address, owner, spender.address, value, deadline, nonce, ctx.domainSeparator) - // check that the next nonce expected is 0, not 1 - assert.equalBN(await token.nonces(owner.address), BigNumber.from(0)) - - // try to submit the permit - await assert.revertsWith( - token.connect(charlie).permit(owner.address, spender.address, value, deadline, v, r, s), - 'ErrorInvalidSignature()' - ) - }) - - .test('reverts if the permit has already been used', async (ctx) => { - const token = ctx.contracts.rebasableProxied - const { owner, spender, value, nonce, deadline } = ctx.permitParams - const charlie = ctx.accounts.user2 - // create a signed permit - const { v, r, s } = await signPermit(owner.address, owner, spender.address, value, deadline, nonce, ctx.domainSeparator) - - // submit the permit - await token.connect(charlie).permit(owner.address, spender.address, value, deadline, v, r, s) - - // try to submit the permit again - await assert.revertsWith( - token.connect(charlie).permit(owner.address, spender.address, value, deadline, v, r, s), - 'ErrorInvalidSignature()' - ) - - await testing.impersonate(owner.address) - await testing.setBalance(owner.address, wei.toBigNumber(wei`10 ether`)) - - // try to submit the permit again from Alice herself - await assert.revertsWith( - token.connect(await hre.ethers.getSigner(owner.address)).permit(owner.address, spender.address, value, deadline, v, r, s), - 'ErrorInvalidSignature()' - ) - }) - - .test('reverts if the permit has a nonce that has already been used by the signer', async (ctx) => { - const token = ctx.contracts.rebasableProxied - const { owner, spender, value, nonce, deadline } = ctx.permitParams - const charlie = ctx.accounts.user2 - // create a signed permit - const permit = await signPermit(owner.address, owner, spender.address, value, deadline, nonce, ctx.domainSeparator) - - // submit the permit - await token.connect(charlie).permit(owner.address, spender.address, value, deadline, permit.v, permit.r, permit.s) - - // create another signed permit with the same nonce, but - // with different parameters - const permit2 = await signPermit(owner.address, owner, spender.address, 1e6, deadline, nonce, ctx.domainSeparator) - - // try to submit the permit again - await assert.revertsWith( - token.connect(charlie).permit(owner.address, spender.address, 1e6, deadline, permit2.v, permit2.r, permit2.s), - 'ErrorInvalidSignature()' - ) - }) - - .test('reverts if the permit includes invalid approval parameters', async (ctx) => { - const token = ctx.contracts.rebasableProxied - const { owner, value, nonce, deadline } = ctx.permitParams - const charlie = ctx.accounts.user2 - // create a signed permit that attempts to grant allowance to the - // zero address - const spender = hre.ethers.constants.AddressZero - const { v, r, s } = await signPermit(owner.address, owner, spender, value, deadline, nonce, ctx.domainSeparator) - - // try to submit the permit with invalid approval parameters - await assert.revertsWith( - token.connect(charlie).permit(owner.address, spender, value, deadline, v, r, s), - 'ErrorAccountIsZeroAddress()' - ) - }) - - .test('reverts if the permit is not for an approval', async (ctx) => { - const token = ctx.contracts.rebasableProxied - const charlie = ctx.accounts.user2 - const { owner: from, spender: to, value, deadline: validBefore } = ctx.permitParams - // create a signed permit for a transfer - const validAfter = '0' - const nonce = '0x1234567890abcdef1234567890abcdef1234567890abcdef1234567890abcdef' - const digest = calculateTransferAuthorizationDigest( - from.address, - to.address, - value, - validAfter, - validBefore, - nonce, - ctx.domainSeparator - ) - const { v, r, s } = await signEOAorEIP1271(digest, from) - - // try to submit the transfer permit - await assert.revertsWith( - token.connect(charlie).permit(from.address, to.address, value, validBefore, v, r, s), - 'ErrorInvalidSignature()' - ) - }) - - .run(); } -function ctxFactoryFactory(signingAccountsFuncFactory: typeof getAccountsEIP1271 | typeof getAccountsEOA) { - return async () => { - const name = TOKEN_NAME; - const symbol = "StETH"; - const decimalsToSet = 18; - const decimals = BigNumber.from(10).pow(decimalsToSet); - const rate = BigNumber.from('12').pow(decimalsToSet - 1); - const premintShares = wei.toBigNumber(wei`100 ether`); - const premintTokens = BigNumber.from(rate).mul(premintShares).div(decimals); - - const [ - deployer, - owner, - recipient, - spender, - holder, - stranger, - user1, - user2, - ] = await hre.ethers.getSigners(); - - const wrappedToken = await new ERC20Bridged__factory(deployer).deploy( - "WsETH Test Token", - "WsETH", +async function tokenProxied( + name: string, + symbol: string, + decimalsToSet: number, + rate: BigNumber, + isRebasable: boolean, + owner: SignerWithAddress, + deployer: SignerWithAddress, + holder: SignerWithAddress) { + + if (isRebasable) { + + const wrappedToken = await new ERC20BridgedPermit__factory(deployer).deploy( + "WstETH Test Token", + "WstETH", + SIGNING_DOMAIN_VERSION, + decimalsToSet, + owner.address + ); + const tokenRateOracle = await new TokenRateOracle__factory(deployer).deploy( + hre.ethers.constants.AddressZero, + owner.address, + hre.ethers.constants.AddressZero, + 86400 + ); + const rebasableTokenImpl = await new ERC20RebasablePermit__factory(deployer).deploy( + name, + symbol, + SIGNING_DOMAIN_VERSION, + decimalsToSet, + wrappedToken.address, + tokenRateOracle.address, + owner.address + ); + + const l2TokensProxy = await new OssifiableProxy__factory(deployer).deploy( + rebasableTokenImpl.address, + deployer.address, + ERC20RebasablePermit__factory.createInterface().encodeFunctionData("initialize", [ + name, + symbol, + ]) + ); + + const rebasableProxied = ERC20RebasablePermit__factory.connect( + l2TokensProxy.address, + holder + ); + + await tokenRateOracle.connect(owner).updateRate(rate, 1000); + const premintShares = wei.toBigNumber(wei`100 ether`); + await rebasableProxied.connect(owner).bridgeMintShares(holder.address, premintShares); + + return rebasableProxied; + } + + const wrappedToken = await new ERC20BridgedPermit__factory(deployer).deploy( + name, + symbol, + SIGNING_DOMAIN_VERSION, decimalsToSet, owner.address ); - const tokenRateOracle = await new TokenRateOracle__factory(deployer).deploy( - hre.ethers.constants.AddressZero, - owner.address, - hre.ethers.constants.AddressZero, - 86400 - ); - const rebasableTokenImpl = await new ERC20RebasablePermit__factory(deployer).deploy( - name, - symbol, - SIGNING_DOMAIN_VERSION, - decimalsToSet, - wrappedToken.address, - tokenRateOracle.address, - owner.address - ); - - await hre.network.provider.request({ - method: "hardhat_impersonateAccount", - params: [hre.ethers.constants.AddressZero], - }); - - const zero = await hre.ethers.getSigner(hre.ethers.constants.AddressZero); const l2TokensProxy = await new OssifiableProxy__factory(deployer).deploy( - rebasableTokenImpl.address, - deployer.address, - ERC20RebasablePermit__factory.createInterface().encodeFunctionData("initialize", [ - name, - symbol, - ]) + wrappedToken.address, + deployer.address, + ERC20BridgedPermit__factory.createInterface().encodeFunctionData("initialize", [ + name, + symbol, + ]) ); - const rebasableProxied = ERC20RebasablePermit__factory.connect( - l2TokensProxy.address, - holder + const nonRebasableProxied = ERC20BridgedPermit__factory.connect( + l2TokensProxy.address, + holder ); - await tokenRateOracle.connect(owner).updateRate(rate, 1000); - await rebasableProxied.connect(owner).bridgeMintShares(holder.address, premintShares); - const { alice, bob } = await signingAccountsFuncFactory(); - - return { - accounts: { deployer, owner, recipient, spender, holder, stranger, zero, user1, user2 }, - constants: { name, symbol, decimalsToSet, decimals, premintShares, premintTokens, rate }, - contracts: { rebasableProxied, wrappedToken, tokenRateOracle }, - permitParams: { - owner: alice, - spender: bob, - value: 6e6, - nonce: 0, - deadline: MAX_UINT256, - }, - domainSeparator: makeDomainSeparator(TOKEN_NAME, SIGNING_DOMAIN_VERSION, getChainId(), rebasableProxied.address), - }; - } + return nonRebasableProxied; } -permitTestsSuit(unit("ERC20Permit with EIP1271 (contract) signing", ctxFactoryFactory(getAccountsEIP1271))); -permitTestsSuit(unit("ERC20Permit with ECDSA (EOA) signing", ctxFactoryFactory(getAccountsEOA))); +permitTestsSuit( + unit("ERC20RebasablePermit with EIP1271 (contract) signing", + ctxFactoryFactory( + "Liquid staked Ether 2.0", + "stETH", + true, + getAccountsEIP1271 + ) + ) +); + +permitTestsSuit( + unit("ERC20RebasablePermit with ECDSA (EOA) signing", + ctxFactoryFactory( + "Liquid staked Ether 2.0", + "stETH", + true, + getAccountsEOA + ) + ) +); + +permitTestsSuit( + unit("ERC20BridgedPermit with EIP1271 (contract) signing", + ctxFactoryFactory( + "Wrapped liquid staked Ether 2.0", + "wstETH", + false, + getAccountsEIP1271 + ) + ) +); + +permitTestsSuit( + unit("ERC20BridgedPermit with ECDSA (EOA) signing", + ctxFactoryFactory( + "Wrapped liquid staked Ether 2.0", + "WstETH", + false, + getAccountsEOA + ) + ) +); From 0545e33eaec087aecce4948853c9a19c8bedcb78 Mon Sep 17 00:00:00 2001 From: Anton Kovalchuk Date: Wed, 10 Apr 2024 22:34:35 +0200 Subject: [PATCH 056/148] PR fixes: fix comments, readme, rename bridges, notifier refactoring --- .env.example | 13 +- README.md | 21 +- contracts/lib/SignatureChecker.sol | 3 +- contracts/lido/TokenRateNotifier.sol | 48 ++- .../interfaces/IPostTokenRebaseReceiver.sol | 20 - .../lido/interfaces/ITokenRatePusher.sol | 4 +- ...ckTokenRatePusherWithOutOfGasErrorStub.sol | 6 +- contracts/optimism/DepositDataCodec.sol | 8 +- ...ge.sol => L1ERC20ExtendedTokensBridge.sol} | 9 +- contracts/optimism/L1LidoTokensBridge.sol | 8 +- ...ge.sol => L2ERC20ExtendedTokensBridge.sol} | 10 +- contracts/optimism/README.md | 12 +- .../token/interfaces/IERC20TokenRate.sol | 2 +- contracts/token/interfaces/IERC20Wrapper.sol | 2 +- .../token/interfaces/ITokenRateOracle.sol | 8 +- scripts/optimism/deploy-bridge.ts | 8 +- test/arbitrum/_launch.test.ts | 4 +- .../optimism.integration.test.ts | 58 +-- test/optimism/L1ERC20TokenBridge.unit.test.ts | 10 +- test/optimism/L2ERC20TokenBridge.unit.test.ts | 8 +- test/optimism/TokenRateNotifier.unit.test.ts | 381 +++++++++--------- .../bridging-rebasable-to.e2e.test.ts | 6 +- test/optimism/bridging-rebasable.e2e.test.ts | 2 +- .../bridging-rebasable.integration.test.ts | 126 +++--- test/optimism/bridging-to.e2e.test.ts | 6 +- test/optimism/bridging.e2e.test.ts | 2 +- test/optimism/bridging.integration.test.ts | 102 ++--- test/optimism/deployment.acceptance.test.ts | 34 +- test/optimism/deposit-gas-estimation.test.ts | 57 +-- test/optimism/managing-deposits.e2e.test.ts | 22 +- test/optimism/managing-executor.e2e.test.ts | 6 +- test/optimism/managing-proxy.e2e.test.ts | 10 +- .../pushingTokenRate.integration.test.ts | 4 + utils/arbitrum/testing.ts | 8 +- utils/optimism/deploymentAllFromScratch.ts | 7 +- .../deploymentBridgesAndRebasableToken.ts | 6 +- .../optimism/deploymentNewImplementations.ts | 4 +- utils/optimism/deploymentOracle.ts | 1 + utils/optimism/testing.ts | 38 +- utils/testing/e2e.ts | 4 +- 40 files changed, 568 insertions(+), 520 deletions(-) delete mode 100644 contracts/lido/interfaces/IPostTokenRebaseReceiver.sol rename contracts/optimism/{L1ERC20TokenBridge.sol => L1ERC20ExtendedTokensBridge.sol} (98%) rename contracts/optimism/{L2ERC20TokenBridge.sol => L2ERC20ExtendedTokensBridge.sol} (97%) diff --git a/.env.example b/.env.example index db673518..b683fcb0 100644 --- a/.env.example +++ b/.env.example @@ -25,23 +25,20 @@ ETHERSCAN_API_KEY_OPT= # Bridge/Gateway Deployment # ############################ -# Address of the token to deploy the bridge/gateway for +# Address of the token on L1 to deploy the bridge/gateway for TOKEN= -# Address of the rebasable token to deploy the bridge/gateway for +# Address of the rebasable token on L1 to deploy the bridge/gateway for REBASABLE_TOKEN= -# Address of token rate notifier. Connects Lido core protocol. -TOKEN_RATE_NOTIFIER= - -# Address of token rate pusher +# Address of token rate pusher. Required to config TokenRateOracle. L1_OP_STACK_TOKEN_RATE_PUSHER= # Gas limit required to complete pushing token rate on L2. L2_GAS_LIMIT_FOR_PUSHING_TOKEN_RATE= # A time period when token rate can be considered outdated. -RATE_OUTDATED_DELAY= +RATE_OUTDATED_DELAY=86400 # default is 24 hours # Address of L1 token bridge proxy. L1_TOKEN_BRIDGE= @@ -59,7 +56,7 @@ L2_TOKEN_RATE_ORACLE= GOV_BRIDGE_EXECUTOR= # Name of the network environments used by deployment scripts. -# Might be one of: "mainnet", "goerli". +# Might be one of: "mainnet", "sepolia". NETWORK=mainnet # Run deployment in the forking network instead of public ones diff --git a/README.md b/README.md index 7781dbe3..caf32a0d 100644 --- a/README.md +++ b/README.md @@ -6,6 +6,7 @@ To retrieve more detailed info about the bridging process, see the specification - [Lido's Arbitrum Gateway](https://github.com/lidofinance/lido-l2/blob/main/contracts/arbitrum/README.md). - [Lido's Optimism Bridge](https://github.com/lidofinance/lido-l2/blob/main/contracts/optimism/README.md). +- [wstETH Bridging Guide](https://docs.lido.fi/token-guides/wsteth-bridging-guide/#r-5-bridging-l1-lido-dao-decisions) ## Project setup @@ -44,7 +45,15 @@ The configuration of the deployment scripts happens via the ENV variables. The f - [`TOKEN`](#TOKEN) - address of the non-rebasable token to deploy a new bridge on the Ethereum chain. - [`REBASABLE_TOKEN`] (#REBASABLE_TOKEN) - address of the rebasable token to deploy new bridge on the Ethereum chain. -- [`NETWORK`](#NETWORK) - name of the network environments used by deployment scripts. Allowed values: `mainnet`, `goerli`. +- [`L1_OP_STACK_TOKEN_RATE_PUSHER`](#L1_OP_STACK_TOKEN_RATE_PUSHER) - address of token rate pusher. Required to config TokenRateOracle. +- [`L2_GAS_LIMIT_FOR_PUSHING_TOKEN_RATE`](#L2_GAS_LIMIT_FOR_PUSHING_TOKEN_RATE) - gas limit required to complete pushing token rate on L2. +- [`RATE_OUTDATED_DELAY`](#RATE_OUTDATED_DELAY) - a time period when token rate can be considered outdated. Default is 24 hours. +- [`L1_TOKEN_BRIDGE`](#L1_TOKEN_BRIDGE) - address of L1 token bridge. +- [`L2_TOKEN_BRIDGE`](#L2_TOKEN_BRIDGE) - address of L2 token bridge. +- [`L2_TOKEN`](#L2_TOKEN) - address of the non-rebasable token on L2. +- [`L2_TOKEN_RATE_ORACLE`](#L2_TOKEN_RATE_ORACLE) - address of token rate oracle on L2. +- [`GOV_BRIDGE_EXECUTOR`](#GOV_BRIDGE_EXECUTOR) - address of bridge executor. +- [`NETWORK`](#NETWORK) - name of the network environments used by deployment scripts. Allowed values: `mainnet`, `sepolia`. - [`FORKING`](#FORKING) - run deployment in the forking network instead of real ones - [`ETH_DEPLOYER_PRIVATE_KEY`](#ETH_DEPLOYER_PRIVATE_KEY) - The private key of the deployer account in the Ethereum network is used during the deployment process. - [`ARB_DEPLOYER_PRIVATE_KEY`](#ARB_DEPLOYER_PRIVATE_KEY) - The private key of the deployer account in the Arbitrum network is used during the deployment process. @@ -315,17 +324,17 @@ Below variables used in the Arbitrum/Optimism bridge deployment process. #### `TOKEN` -Address of the non-rebasable token to deploy a new bridge on the Ethereum chain. +Address of the existing non-rebasable token to deploy a new bridge for on the Ethereum chain. #### `REBASABLE_TOKEN` -Address of the rebasable token to deploy new bridge on the Ethereum chain. +Address of the existing rebasable token to deploy new bridge for on the Ethereum chain. #### `NETWORK` > Default value: `mainnet` -Name of the network environments used by deployment scripts. Might be one of: `mainnet`, `goerli`. +Name of the network environments used by deployment scripts. Might be one of: `mainnet`, `sepolia`. #### `FORKING` @@ -447,7 +456,7 @@ The following variables are used in the process of the Integration & E2E testing #### `TESTING_ARB_NETWORK` -Name of the network environments used for Arbitrum Integration & E2E testing. Might be one of: `mainnet`, `goerli`. +Name of the network environments used for Arbitrum Integration & E2E testing. Might be one of: `mainnet`, `sepolia`. #### `TESTING_ARB_L1_TOKEN` @@ -487,7 +496,7 @@ Address of the L2 gateway router used in the Acceptance Integration & E2E (when #### `TESTING_OPT_NETWORK` -Name of the network environments used for Optimism Integration & E2E testing. Might be one of: `mainnet`, `goerli`. +Name of the network environments used for Optimism Integration & E2E testing. Might be one of: `mainnet`, `sepolia`. #### `TESTING_OPT_L1_TOKEN` diff --git a/contracts/lib/SignatureChecker.sol b/contracts/lib/SignatureChecker.sol index d42561c4..e4e3ab59 100644 --- a/contracts/lib/SignatureChecker.sol +++ b/contracts/lib/SignatureChecker.sol @@ -1,6 +1,6 @@ // SPDX-FileCopyrightText: 2024 OpenZeppelin, Lido // SPDX-License-Identifier: GPL-3.0 -// Writen based on (utils/cryptography/SignatureChecker.sol from d398d68 +// Written based on (utils/cryptography/SignatureChecker.sol from d398d68 pragma solidity 0.8.10; @@ -24,7 +24,6 @@ library SignatureChecker { */ function isValidSignatureNow(address signer, bytes32 hash, bytes memory signature) internal view returns (bool) { if (signer.code.length == 0) { - // return true; (address recovered, ECDSA.RecoverError err) = ECDSA.tryRecover(hash, signature); return err == ECDSA.RecoverError.NoError && recovered == signer; } else { diff --git a/contracts/lido/TokenRateNotifier.sol b/contracts/lido/TokenRateNotifier.sol index b9e9e17c..28cf18f0 100644 --- a/contracts/lido/TokenRateNotifier.sol +++ b/contracts/lido/TokenRateNotifier.sol @@ -3,21 +3,32 @@ pragma solidity 0.8.10; -import {IPostTokenRebaseReceiver} from "./interfaces/IPostTokenRebaseReceiver.sol"; import {ITokenRatePusher} from "./interfaces/ITokenRatePusher.sol"; import {Ownable} from "@openzeppelin/contracts/access/Ownable.sol"; import {ERC165Checker} from "@openzeppelin/contracts/utils/introspection/ERC165Checker.sol"; +/// @notice An interface to subscribe on the `stETH` token rebases (defined in the `Lido` core contract) +interface IPostTokenRebaseReceiver { + + /// @notice Is called in the context of `Lido.handleOracleReport` to notify the subscribers about each token rebase + function handlePostTokenRebase( + uint256 _reportTimestamp, + uint256 _timeElapsed, + uint256 _preTotalShares, + uint256 _preTotalEther, + uint256 _postTotalShares, + uint256 _postTotalEther, + uint256 _sharesMintedAsFees + ) external; +} + /// @author kovalgek /// @notice Notifies all observers when rebase event occures. contract TokenRateNotifier is Ownable, IPostTokenRebaseReceiver { using ERC165Checker for address; /// @notice Maximum amount of observers to be supported. - uint256 public constant MAX_OBSERVERS_COUNT = 16; - - /// @notice Invalid interface id. - bytes4 public constant INVALID_INTERFACE_ID = 0xffffffff; + uint256 public constant MAX_OBSERVERS_COUNT = 32; /// @notice A value that indicates that value was not found. uint256 public constant INDEX_NOT_FOUND = type(uint256).max; @@ -28,6 +39,11 @@ contract TokenRateNotifier is Ownable, IPostTokenRebaseReceiver { /// @notice All observers. address[] public observers; + /// @param initialOwner_ initial owner + constructor(address initialOwner_) { + _transferOwnership(initialOwner_); + } + /// @notice Add a `observer_` to the back of array /// @param observer_ observer address function addObserver(address observer_) external onlyOwner { @@ -55,24 +71,22 @@ contract TokenRateNotifier is Ownable, IPostTokenRebaseReceiver { revert ErrorNoObserverToRemove(); } - for (uint256 obIndex = observerIndexToRemove; obIndex < observers.length - 1; obIndex++) { - observers[obIndex] = observers[obIndex + 1]; - } - + observers[observerIndexToRemove] = observers[observers.length - 1]; observers.pop(); emit ObserverRemoved(observer_); } /// @inheritdoc IPostTokenRebaseReceiver + /// @dev Parameters aren't used because all required data further components fetch by themselves. function handlePostTokenRebase( - uint256, - uint256, - uint256, - uint256, - uint256, - uint256, - uint256 + uint256, /* reportTimestamp */ + uint256, /* timeElapsed */ + uint256, /* preTotalShares */ + uint256, /* preTotalEther */ + uint256, /* postTotalShares */ + uint256, /* postTotalEther */ + uint256 /* sharesMintedAsFees */ ) external { for (uint256 obIndex = 0; obIndex < observers.length; obIndex++) { try ITokenRatePusher(observers[obIndex]).pushTokenRate() {} @@ -93,7 +107,7 @@ contract TokenRateNotifier is Ownable, IPostTokenRebaseReceiver { /// @notice Observer length /// @return Added observers count - function observersLength() public view returns (uint256) { + function observersLength() external view returns (uint256) { return observers.length; } diff --git a/contracts/lido/interfaces/IPostTokenRebaseReceiver.sol b/contracts/lido/interfaces/IPostTokenRebaseReceiver.sol deleted file mode 100644 index 65b1fe90..00000000 --- a/contracts/lido/interfaces/IPostTokenRebaseReceiver.sol +++ /dev/null @@ -1,20 +0,0 @@ -// SPDX-FileCopyrightText: 2024 Lido -// SPDX-License-Identifier: GPL-3.0 - -pragma solidity 0.8.10; - -/// @author kovalgek -/// @notice An interface to subscribe on the `stETH` token rebases (defined in the `Lido` core contract) -interface IPostTokenRebaseReceiver { - - /// @notice Is called in the context of `Lido.handleOracleReport` to notify the subscribers about each token rebase - function handlePostTokenRebase( - uint256 _reportTimestamp, - uint256 _timeElapsed, - uint256 _preTotalShares, - uint256 _preTotalEther, - uint256 _postTotalShares, - uint256 _postTotalEther, - uint256 _sharesMintedAsFees - ) external; -} diff --git a/contracts/lido/interfaces/ITokenRatePusher.sol b/contracts/lido/interfaces/ITokenRatePusher.sol index 9492a240..9d157c3c 100644 --- a/contracts/lido/interfaces/ITokenRatePusher.sol +++ b/contracts/lido/interfaces/ITokenRatePusher.sol @@ -4,8 +4,8 @@ pragma solidity 0.8.10; /// @author kovalgek -/// @notice An interface for entity that pushes rate. +/// @notice An interface for entity that pushes token rate. interface ITokenRatePusher { - /// @notice Pushes token rate to L2 by depositing zero tokens. + /// @notice Pushes token rate to L2 by depositing zero token amount. function pushTokenRate() external; } diff --git a/contracts/lido/stubs/OpStackTokenRatePusherWithOutOfGasErrorStub.sol b/contracts/lido/stubs/OpStackTokenRatePusherWithOutOfGasErrorStub.sol index 818b4229..cb8d1c26 100644 --- a/contracts/lido/stubs/OpStackTokenRatePusherWithOutOfGasErrorStub.sol +++ b/contracts/lido/stubs/OpStackTokenRatePusherWithOutOfGasErrorStub.sol @@ -8,10 +8,12 @@ import {ERC165} from "@openzeppelin/contracts/utils/introspection/ERC165.sol"; contract OpStackTokenRatePusherWithOutOfGasErrorStub is ERC165, ITokenRatePusher { - mapping (uint256 => uint256) data; + uint256 public constant OUT_OF_GAS_INCURRING_MAX = 1000000000000; + + mapping (uint256 => uint256) public data; function pushTokenRate() external { - for (uint256 i = 0; i < 1000000000000; ++i) { + for (uint256 i = 0; i < OUT_OF_GAS_INCURRING_MAX; ++i) { data[i] = i; } } diff --git a/contracts/optimism/DepositDataCodec.sol b/contracts/optimism/DepositDataCodec.sol index 68ada77b..55178758 100644 --- a/contracts/optimism/DepositDataCodec.sol +++ b/contracts/optimism/DepositDataCodec.sol @@ -1,4 +1,4 @@ -// SPDX-FileCopyrightText: 2023 Lido +// SPDX-FileCopyrightText: 2024 Lido // SPDX-License-Identifier: GPL-3.0 pragma solidity 0.8.10; @@ -9,7 +9,7 @@ contract DepositDataCodec { uint8 internal constant RATE_FIELD_SIZE = 12; uint8 internal constant TIMESTAMP_FIELD_SIZE = 5; - + struct DepositData { uint96 rate; uint40 timestamp; @@ -26,11 +26,11 @@ contract DepositDataCodec { } function decodeDepositData(bytes calldata buffer) internal pure returns (DepositData memory) { - + if (buffer.length < RATE_FIELD_SIZE + TIMESTAMP_FIELD_SIZE) { revert ErrorDepositDataLength(); } - + DepositData memory depositData = DepositData({ rate: uint96(bytes12(buffer[0:RATE_FIELD_SIZE])), timestamp: uint40(bytes5(buffer[RATE_FIELD_SIZE:RATE_FIELD_SIZE + TIMESTAMP_FIELD_SIZE])), diff --git a/contracts/optimism/L1ERC20TokenBridge.sol b/contracts/optimism/L1ERC20ExtendedTokensBridge.sol similarity index 98% rename from contracts/optimism/L1ERC20TokenBridge.sol rename to contracts/optimism/L1ERC20ExtendedTokensBridge.sol index 087df07f..7e303a97 100644 --- a/contracts/optimism/L1ERC20TokenBridge.sol +++ b/contracts/optimism/L1ERC20ExtendedTokensBridge.sol @@ -20,7 +20,7 @@ import {IERC20WstETH} from "../token/interfaces/IERC20WstETH.sol"; /// @notice The L1 ERC20 token bridge locks bridged tokens on the L1 side, sends deposit messages /// on the L2 side, and finalizes token withdrawals from L2. Additionally, adds the methods for /// bridging management: enabling and disabling withdrawals/deposits -abstract contract L1ERC20TokenBridge is +abstract contract L1ERC20ExtendedTokensBridge is IL1ERC20Bridge, BridgingManager, RebasableAndNonRebasableTokens, @@ -44,7 +44,12 @@ abstract contract L1ERC20TokenBridge is address l1TokenRebasable_, address l2TokenNonRebasable_, address l2TokenRebasable_ - ) CrossDomainEnabled(messenger_) RebasableAndNonRebasableTokens(l1TokenNonRebasable_, l1TokenRebasable_, l2TokenNonRebasable_, l2TokenRebasable_) { + ) CrossDomainEnabled(messenger_) RebasableAndNonRebasableTokens( + l1TokenNonRebasable_, + l1TokenRebasable_, + l2TokenNonRebasable_, + l2TokenRebasable_ + ) { L2_TOKEN_BRIDGE = l2TokenBridge_; } diff --git a/contracts/optimism/L1LidoTokensBridge.sol b/contracts/optimism/L1LidoTokensBridge.sol index cd144d16..491ff28b 100644 --- a/contracts/optimism/L1LidoTokensBridge.sol +++ b/contracts/optimism/L1LidoTokensBridge.sol @@ -3,12 +3,12 @@ pragma solidity 0.8.10; -import {L1ERC20TokenBridge} from "./L1ERC20TokenBridge.sol"; +import {L1ERC20ExtendedTokensBridge} from "./L1ERC20ExtendedTokensBridge.sol"; import {IERC20WstETH} from "../token/interfaces/IERC20WstETH.sol"; /// @author kovalgek -/// @notice Hides wstETH concept from other contracts to keep `L1ERC20TokenBridge` reusable. -contract L1LidoTokensBridge is L1ERC20TokenBridge { +/// @notice Hides wstETH concept from other contracts to keep `L1ERC20ExtendedTokensBridge` reusable. +contract L1LidoTokensBridge is L1ERC20ExtendedTokensBridge { constructor( address messenger_, @@ -17,7 +17,7 @@ contract L1LidoTokensBridge is L1ERC20TokenBridge { address l1TokenRebasable_, address l2TokenNonRebasable_, address l2TokenRebasable_ - ) L1ERC20TokenBridge( + ) L1ERC20ExtendedTokensBridge( messenger_, l2TokenBridge_, l1TokenNonRebasable_, diff --git a/contracts/optimism/L2ERC20TokenBridge.sol b/contracts/optimism/L2ERC20ExtendedTokensBridge.sol similarity index 97% rename from contracts/optimism/L2ERC20TokenBridge.sol rename to contracts/optimism/L2ERC20ExtendedTokensBridge.sol index dd01601e..f2b630be 100644 --- a/contracts/optimism/L2ERC20TokenBridge.sol +++ b/contracts/optimism/L2ERC20ExtendedTokensBridge.sol @@ -24,7 +24,7 @@ import {DepositDataCodec} from "./DepositDataCodec.sol"; /// deposits into the L1 token bridge. It also acts as a burner of the tokens /// intended for withdrawal, informing the L1 bridge to release L1 funds. Additionally, adds /// the methods for bridging management: enabling and disabling withdrawals/deposits -contract L2ERC20TokenBridge is +contract L2ERC20ExtendedTokensBridge is IL2ERC20Bridge, BridgingManager, RebasableAndNonRebasableTokens, @@ -48,7 +48,12 @@ contract L2ERC20TokenBridge is address l1TokenRebasable_, address l2TokenNonRebasable_, address l2TokenRebasable_ - ) CrossDomainEnabled(messenger_) RebasableAndNonRebasableTokens(l1TokenNonRebasable_, l1TokenRebasable_, l2TokenNonRebasable_, l2TokenRebasable_) { + ) CrossDomainEnabled(messenger_) RebasableAndNonRebasableTokens( + l1TokenNonRebasable_, + l1TokenRebasable_, + l2TokenNonRebasable_, + l2TokenRebasable_ + ) { L1_TOKEN_BRIDGE = l1TokenBridge_; } @@ -95,6 +100,7 @@ contract L2ERC20TokenBridge is { if (_isRebasableTokenFlow(l1Token_, l2Token_)) { DepositData memory depositData = decodeDepositData(data_); + ITokenRateOracle tokenRateOracle = ERC20Rebasable(L2_TOKEN_REBASABLE).TOKEN_RATE_ORACLE(); tokenRateOracle.updateRate(depositData.rate, depositData.timestamp); diff --git a/contracts/optimism/README.md b/contracts/optimism/README.md index 954556d6..c493349d 100644 --- a/contracts/optimism/README.md +++ b/contracts/optimism/README.md @@ -41,8 +41,8 @@ A high-level overview of the proposed solution might be found in the below diagr - [**`BridgingManager`**](#BridgingManager) - contains administrative methods to retrieve and control the state of the bridging process. - [**`BridgeableTokens`**](#BridgeableTokens) - contains the logic for validation of tokens used in the bridging process. - [**`CrossDomainEnabled`**](#CrossDomainEnabled) - helper contract for contracts performing cross-domain communications -- [**`L1ERC20TokenBridge`**](#L1ERC20TokenBridge) - Ethereum's counterpart of the bridge to bridge registered ERC20 compatible tokens between Ethereum and Optimism chains. -- [**`L2ERC20TokenBridge`**](#L2ERC20TokenBridge) - Optimism's counterpart of the bridge to bridge registered ERC20 compatible tokens between Ethereum and Optimism chains +- [**`L1ERC20ExtendedTokensBridge`**](#L1ERC20ExtendedTokensBridge) - Ethereum's counterpart of the bridge to bridge registered ERC20 compatible tokens between Ethereum and Optimism chains. +- [**`L2ERC20ExtendedTokensBridge`**](#L2ERC20ExtendedTokensBridge) - Optimism's counterpart of the bridge to bridge registered ERC20 compatible tokens between Ethereum and Optimism chains - [**`ERC20Bridged`**](#ERC20Bridged) - an implementation of the `ERC20` token with administrative methods to mint and burn tokens. - [**`OssifiableProxy`**](#OssifiableProxy) - the ERC1967 proxy with extra admin functionality. @@ -216,7 +216,7 @@ Sends a message to an account on another domain. Enforces that the modified function is only callable by a specific cross-domain account. -## `L1ERC20TokenBridge` +## `L1ERC20ExtendedTokensBridge` **Implements:** [`IL1ERC20Bridge`](https://github.com/ethereum-optimism/optimism/blob/develop/packages/contracts/contracts/L1/messaging/IL1ERC20Bridge.sol) **Inherits:** [`BridgingManager`](#BridgingManager) [`BridgeableTokens`](#BridgeableTokens) [`CrossDomainEnabled`](#CrossDomainEnabled) @@ -300,7 +300,7 @@ Complete a withdrawal from L2 to L1, and credit funds to the recipient's balance Performs the logic for deposits by informing the L2 Deposited Token contract of the deposit and calling safeTransferFrom to lock the L1 funds. -## `L2ERC20TokenBridge` +## `L2ERC20ExtendedTokensBridge` **Implements:** [`IL2ERC20Bridge`](https://github.com/ethereum-optimism/optimism/blob/develop/packages/contracts/contracts/L2/messaging/IL2ERC20Bridge.sol) **Extends** [`BridgingManager`](#BridgingManager) [`BridgeableTokens`](#BridgeableTokens) [`CrossDomainEnabled`](#CrossDomainEnabled) @@ -661,7 +661,7 @@ Validates that that proxy is not ossified and that method is called by the admin ## Deployment Process -To reduce the gas costs for users, contracts `L1ERC20TokenBridge`, `L2ERC20TokenBridge`, and `ERC20Bridged` contracts use immutable variables as much as possible. But some of those variables are cross-referred. For example, `L1ERC20TokenBridge` has reference to `L2ERC20TokenBridge` and vice versa. As we use proxies, we can deploy proxies at first and stub the implementation with an empty contract. Then deploy actual implementations with addresses of deployed proxies and then upgrade proxies with new implementations. For stub, the following contract might be used: +To reduce the gas costs for users, contracts `L1ERC20ExtendedTokensBridge`, `L2ERC20ExtendedTokensBridge`, and `ERC20Bridged` contracts use immutable variables as much as possible. But some of those variables are cross-referred. For example, `L1ERC20ExtendedTokensBridge` has reference to `L2ERC20ExtendedTokensBridge` and vice versa. As we use proxies, we can deploy proxies at first and stub the implementation with an empty contract. Then deploy actual implementations with addresses of deployed proxies and then upgrade proxies with new implementations. For stub, the following contract might be used: ``` pragma solidity ^0.8.0; @@ -676,7 +676,7 @@ As an additional link in the tokens flow chain, the Optimism protocol and bridge ## Minting of uncollateralized L2 token -Such an attack might happen if an attacker obtains the right to call `L2ERC20TokenBridge.finalizeDeposit()` directly. In such a scenario, an attacker can mint uncollaterized tokens on L2 and initiate withdrawal later. +Such an attack might happen if an attacker obtains the right to call `L2ERC20ExtendedTokensBridge.finalizeDeposit()` directly. In such a scenario, an attacker can mint uncollaterized tokens on L2 and initiate withdrawal later. The best way to detect such an attack is an offchain monitoring of the minting and depositing/withdrawal events. Based on such events might be tracked following stats: diff --git a/contracts/token/interfaces/IERC20TokenRate.sol b/contracts/token/interfaces/IERC20TokenRate.sol index 16c51870..0b57716e 100644 --- a/contracts/token/interfaces/IERC20TokenRate.sol +++ b/contracts/token/interfaces/IERC20TokenRate.sol @@ -1,4 +1,4 @@ -// SPDX-FileCopyrightText: 2023 Lido +// SPDX-FileCopyrightText: 2024 Lido // SPDX-License-Identifier: GPL-3.0 pragma solidity 0.8.10; diff --git a/contracts/token/interfaces/IERC20Wrapper.sol b/contracts/token/interfaces/IERC20Wrapper.sol index 6b3125d4..c443f3cd 100644 --- a/contracts/token/interfaces/IERC20Wrapper.sol +++ b/contracts/token/interfaces/IERC20Wrapper.sol @@ -1,4 +1,4 @@ -// SPDX-FileCopyrightText: 2023 Lido +// SPDX-FileCopyrightText: 2024 Lido // SPDX-License-Identifier: GPL-3.0 pragma solidity 0.8.10; diff --git a/contracts/token/interfaces/ITokenRateOracle.sol b/contracts/token/interfaces/ITokenRateOracle.sol index 2f2d32f6..ce057ac8 100644 --- a/contracts/token/interfaces/ITokenRateOracle.sol +++ b/contracts/token/interfaces/ITokenRateOracle.sol @@ -1,4 +1,4 @@ -// SPDX-FileCopyrightText: 2023 Lido +// SPDX-FileCopyrightText: 2024 Lido // SPDX-License-Identifier: GPL-3.0 pragma solidity 0.8.10; @@ -10,7 +10,7 @@ interface ITokenRateOracle { /// @notice get the latest token rate data. /// @return roundId_ is a unique id for each answer. The value is based on timestamp. /// @return answer_ is wstETH/stETH token rate. - /// @return startedAt_ is time when rate was pushed on L1 side. + /// @return startedAt_ is time when rate was pushed on L1 side. /// @return updatedAt_ is the same as startedAt_. /// @return answeredInRound_ is the same as roundId_. function latestRoundData() @@ -34,6 +34,6 @@ interface ITokenRateOracle { /// @notice Updates token rate. /// @param tokenRate_ wstETH/stETH token rate. - /// @param rateL1Timestamp_ L1 time when rate was pushed on L1 side. + /// @param rateL1Timestamp_ L1 time when rate was pushed on L1 side. function updateRate(uint256 tokenRate_, uint256 rateL1Timestamp_) external; -} \ No newline at end of file +} diff --git a/scripts/optimism/deploy-bridge.ts b/scripts/optimism/deploy-bridge.ts index 2e4f272b..57538f49 100644 --- a/scripts/optimism/deploy-bridge.ts +++ b/scripts/optimism/deploy-bridge.ts @@ -59,16 +59,16 @@ async function main() { await l1DeployScript.run(); await l2DeployScript.run(); - const l1ERC20TokenBridgeProxyDeployStepIndex = 1; + const l1ERC20ExtendedTokensBridgeProxyDeployStepIndex = 1; const l1BridgingManagement = new BridgingManagement( - l1DeployScript.getContractAddress(l1ERC20TokenBridgeProxyDeployStepIndex), + l1DeployScript.getContractAddress(l1ERC20ExtendedTokensBridgeProxyDeployStepIndex), ethDeployer, { logger: console } ); - const l2ERC20TokenBridgeProxyDeployStepIndex = 5; + const l2ERC20ExtendedTokensBridgeProxyDeployStepIndex = 5; const l2BridgingManagement = new BridgingManagement( - l2DeployScript.getContractAddress(l2ERC20TokenBridgeProxyDeployStepIndex), + l2DeployScript.getContractAddress(l2ERC20ExtendedTokensBridgeProxyDeployStepIndex), optDeployer, { logger: console } ); diff --git a/test/arbitrum/_launch.test.ts b/test/arbitrum/_launch.test.ts index e5711360..504fdde9 100644 --- a/test/arbitrum/_launch.test.ts +++ b/test/arbitrum/_launch.test.ts @@ -2,7 +2,7 @@ import { assert } from "chai"; import env from "../../utils/env"; import arbitrum from "../../utils/arbitrum"; -import { L1ERC20TokenBridge__factory } from "../../typechain"; +import { L1ERC20ExtendedTokensBridge__factory } from "../../typechain"; import { wei } from "../../utils/wei"; import testing, { scenario } from "../../utils/testing"; import { BridgingManagerRole } from "../../utils/bridging-management"; @@ -71,7 +71,7 @@ async function ctx() { wei.toBigNumber(wei`1 ether`) ); - const l1ERC20TokenGatewayImpl = L1ERC20TokenBridge__factory.connect( + const l1ERC20TokenGatewayImpl = L1ERC20ExtendedTokensBridge__factory.connect( l1ERC20TokenGateway.address, l1DevMultisig ); diff --git a/test/bridge-executor/optimism.integration.test.ts b/test/bridge-executor/optimism.integration.test.ts index bc8fcc13..ee290cb6 100644 --- a/test/bridge-executor/optimism.integration.test.ts +++ b/test/bridge-executor/optimism.integration.test.ts @@ -1,7 +1,7 @@ import { assert } from "chai"; import { ERC20BridgedStub__factory, - L2ERC20TokenBridge__factory, + L2ERC20ExtendedTokensBridge__factory, OssifiableProxy__factory, OptimismBridgeExecutor__factory, ERC20Bridged__factory, @@ -19,23 +19,23 @@ import deploymentAll from "../../utils/optimism/deploymentAllFromScratch"; scenario("Optimism :: Bridge Executor integration test", ctxFactory) .step("Activate L2 bridge", async (ctx) => { - const { l2ERC20TokenBridge, bridgeExecutor, l2CrossDomainMessenger } = + const { l2ERC20ExtendedTokensBridge, bridgeExecutor, l2CrossDomainMessenger } = ctx.l2; assert.isFalse( - await l2ERC20TokenBridge.hasRole( + await l2ERC20ExtendedTokensBridge.hasRole( BridgingManagerRole.DEPOSITS_ENABLER_ROLE.hash, bridgeExecutor.address ) ); assert.isFalse( - await l2ERC20TokenBridge.hasRole( + await l2ERC20ExtendedTokensBridge.hasRole( BridgingManagerRole.WITHDRAWALS_ENABLER_ROLE.hash, bridgeExecutor.address ) ); - assert.isFalse(await l2ERC20TokenBridge.isDepositsEnabled()); - assert.isFalse(await l2ERC20TokenBridge.isWithdrawalsEnabled()); + assert.isFalse(await l2ERC20ExtendedTokensBridge.isDepositsEnabled()); + assert.isFalse(await l2ERC20ExtendedTokensBridge.isWithdrawalsEnabled()); const actionsSetCountBefore = await bridgeExecutor.getActionsSetCount(); @@ -46,7 +46,7 @@ scenario("Optimism :: Bridge Executor integration test", ctxFactory) 0, 300_000, bridgeExecutor.interface.encodeFunctionData("queue", [ - new Array(4).fill(l2ERC20TokenBridge.address), + new Array(4).fill(l2ERC20ExtendedTokensBridge.address), new Array(4).fill(0), [ "grantRole(bytes32,address)", @@ -56,25 +56,25 @@ scenario("Optimism :: Bridge Executor integration test", ctxFactory) ], [ "0x" + - l2ERC20TokenBridge.interface + l2ERC20ExtendedTokensBridge.interface .encodeFunctionData("grantRole", [ BridgingManagerRole.DEPOSITS_ENABLER_ROLE.hash, bridgeExecutor.address, ]) .substring(10), "0x" + - l2ERC20TokenBridge.interface + l2ERC20ExtendedTokensBridge.interface .encodeFunctionData("grantRole", [ BridgingManagerRole.WITHDRAWALS_ENABLER_ROLE.hash, bridgeExecutor.address, ]) .substring(10), "0x" + - l2ERC20TokenBridge.interface + l2ERC20ExtendedTokensBridge.interface .encodeFunctionData("enableDeposits") .substring(10), "0x" + - l2ERC20TokenBridge.interface + l2ERC20ExtendedTokensBridge.interface .encodeFunctionData("enableWithdrawals") .substring(10), ], @@ -91,33 +91,33 @@ scenario("Optimism :: Bridge Executor integration test", ctxFactory) await bridgeExecutor.execute(actionsSetCountAfter.sub(1), { value: 0 }); assert.isTrue( - await l2ERC20TokenBridge.hasRole( + await l2ERC20ExtendedTokensBridge.hasRole( BridgingManagerRole.DEPOSITS_ENABLER_ROLE.hash, bridgeExecutor.address ) ); assert.isTrue( - await l2ERC20TokenBridge.hasRole( + await l2ERC20ExtendedTokensBridge.hasRole( BridgingManagerRole.WITHDRAWALS_ENABLER_ROLE.hash, bridgeExecutor.address ) ); - assert.isTrue(await l2ERC20TokenBridge.isDepositsEnabled()); - assert.isTrue(await l2ERC20TokenBridge.isWithdrawalsEnabled()); + assert.isTrue(await l2ERC20ExtendedTokensBridge.isDepositsEnabled()); + assert.isTrue(await l2ERC20ExtendedTokensBridge.isWithdrawalsEnabled()); }) .step("Change Proxy implementation", async (ctx) => { const { l2Token, l2CrossDomainMessenger, - l2ERC20TokenBridgeProxy, + l2ERC20ExtendedTokensBridgeProxy, bridgeExecutor, } = ctx.l2; const actionsSetCountBefore = await bridgeExecutor.getActionsSetCount(); const proxyImplBefore = - await l2ERC20TokenBridgeProxy.proxy__getImplementation(); + await l2ERC20ExtendedTokensBridgeProxy.proxy__getImplementation(); await l2CrossDomainMessenger.relayMessage( 0, @@ -126,12 +126,12 @@ scenario("Optimism :: Bridge Executor integration test", ctxFactory) 0, 300_000, bridgeExecutor.interface.encodeFunctionData("queue", [ - [l2ERC20TokenBridgeProxy.address], + [l2ERC20ExtendedTokensBridgeProxy.address], [0], ["proxy__upgradeTo(address)"], [ "0x" + - l2ERC20TokenBridgeProxy.interface + l2ERC20ExtendedTokensBridgeProxy.interface .encodeFunctionData("proxy__upgradeTo", [l2Token.address]) .substring(10), ], @@ -145,7 +145,7 @@ scenario("Optimism :: Bridge Executor integration test", ctxFactory) await bridgeExecutor.execute(actionsSetCountBefore, { value: 0 }); const proxyImplAfter = - await l2ERC20TokenBridgeProxy.proxy__getImplementation(); + await l2ERC20ExtendedTokensBridgeProxy.proxy__getImplementation(); assert.notEqual(proxyImplBefore, proxyImplAfter); assert.equal(proxyImplAfter, l2Token.address); @@ -154,14 +154,14 @@ scenario("Optimism :: Bridge Executor integration test", ctxFactory) .step("Change proxy Admin", async (ctx) => { const { l2CrossDomainMessenger, - l2ERC20TokenBridgeProxy, + l2ERC20ExtendedTokensBridgeProxy, bridgeExecutor, accounts: { sender }, } = ctx.l2; const actionsSetCountBefore = await bridgeExecutor.getActionsSetCount(); - const proxyAdminBefore = await l2ERC20TokenBridgeProxy.proxy__getAdmin(); + const proxyAdminBefore = await l2ERC20ExtendedTokensBridgeProxy.proxy__getAdmin(); await l2CrossDomainMessenger.relayMessage( 0, @@ -170,12 +170,12 @@ scenario("Optimism :: Bridge Executor integration test", ctxFactory) 0, 300_000, bridgeExecutor.interface.encodeFunctionData("queue", [ - [l2ERC20TokenBridgeProxy.address], + [l2ERC20ExtendedTokensBridgeProxy.address], [0], ["proxy__changeAdmin(address)"], [ "0x" + - l2ERC20TokenBridgeProxy.interface + l2ERC20ExtendedTokensBridgeProxy.interface .encodeFunctionData("proxy__changeAdmin", [sender.address]) .substring(10), ], @@ -188,7 +188,7 @@ scenario("Optimism :: Bridge Executor integration test", ctxFactory) assert.equalBN(actionsSetCountBefore.add(1), actionSetCount); await bridgeExecutor.execute(actionsSetCountBefore, { value: 0 }); - const proxyAdminAfter = await l2ERC20TokenBridgeProxy.proxy__getAdmin(); + const proxyAdminAfter = await l2ERC20ExtendedTokensBridgeProxy.proxy__getAdmin(); assert.notEqual(proxyAdminBefore, proxyAdminAfter); assert.equal(proxyAdminAfter, sender.address); @@ -266,11 +266,11 @@ async function ctxFactory() { optDeployScript.tokenProxyAddress, l2Deployer ); - const l2ERC20TokenBridge = L2ERC20TokenBridge__factory.connect( + const l2ERC20ExtendedTokensBridge = L2ERC20ExtendedTokensBridge__factory.connect( optDeployScript.tokenBridgeProxyAddress, l2Deployer ); - const l2ERC20TokenBridgeProxy = OssifiableProxy__factory.connect( + const l2ERC20ExtendedTokensBridgeProxy = OssifiableProxy__factory.connect( optDeployScript.tokenBridgeProxyAddress, l2Deployer ); @@ -305,9 +305,9 @@ async function ctxFactory() { l2: { l2Token, bridgeExecutor: govBridgeExecutor.connect(l2Deployer), - l2ERC20TokenBridge, + l2ERC20ExtendedTokensBridge, l2CrossDomainMessenger, - l2ERC20TokenBridgeProxy, + l2ERC20ExtendedTokensBridgeProxy, accounts: { sender: testing.accounts.sender(l2Provider), admin: l2Deployer, diff --git a/test/optimism/L1ERC20TokenBridge.unit.test.ts b/test/optimism/L1ERC20TokenBridge.unit.test.ts index efafeb57..d70705db 100644 --- a/test/optimism/L1ERC20TokenBridge.unit.test.ts +++ b/test/optimism/L1ERC20TokenBridge.unit.test.ts @@ -4,7 +4,7 @@ import { ERC20BridgedStub__factory, ERC20WrapperStub__factory, L1LidoTokensBridge__factory, - L2ERC20TokenBridge__factory, + L2ERC20ExtendedTokensBridge__factory, OssifiableProxy__factory, EmptyContractStub__factory, } from "../../typechain"; @@ -160,7 +160,7 @@ unit("Optimism :: L1LidoTokensBridge", ctxFactory) await assert.emits(l1Messenger, tx, "SentMessage", [ l2TokenBridgeEOA.address, l1TokenBridge.address, - L2ERC20TokenBridge__factory.createInterface().encodeFunctionData( + L2ERC20ExtendedTokensBridge__factory.createInterface().encodeFunctionData( "finalizeDeposit", [ l1TokenNonRebasable.address, @@ -229,7 +229,7 @@ unit("Optimism :: L1LidoTokensBridge", ctxFactory) await assert.emits(l1Messenger, tx, "SentMessage", [ l2TokenBridgeEOA.address, l1TokenBridge.address, - L2ERC20TokenBridge__factory.createInterface().encodeFunctionData( + L2ERC20ExtendedTokensBridge__factory.createInterface().encodeFunctionData( "finalizeDeposit", [ l1TokenRebasable.address, @@ -425,7 +425,7 @@ unit("Optimism :: L1LidoTokensBridge", ctxFactory) await assert.emits(l1Messenger, tx, "SentMessage", [ l2TokenBridgeEOA.address, l1TokenBridge.address, - L2ERC20TokenBridge__factory.createInterface().encodeFunctionData( + L2ERC20ExtendedTokensBridge__factory.createInterface().encodeFunctionData( "finalizeDeposit", [ l1TokenNonRebasable.address, @@ -497,7 +497,7 @@ unit("Optimism :: L1LidoTokensBridge", ctxFactory) await assert.emits(l1Messenger, tx, "SentMessage", [ l2TokenBridgeEOA.address, l1TokenBridge.address, - L2ERC20TokenBridge__factory.createInterface().encodeFunctionData( + L2ERC20ExtendedTokensBridge__factory.createInterface().encodeFunctionData( "finalizeDeposit", [ l1TokenRebasable.address, diff --git a/test/optimism/L2ERC20TokenBridge.unit.test.ts b/test/optimism/L2ERC20TokenBridge.unit.test.ts index 1fd77dd3..5ea2dc66 100644 --- a/test/optimism/L2ERC20TokenBridge.unit.test.ts +++ b/test/optimism/L2ERC20TokenBridge.unit.test.ts @@ -5,7 +5,7 @@ import { TokenRateOracle__factory, ERC20Rebasable__factory, L1LidoTokensBridge__factory, - L2ERC20TokenBridge__factory, + L2ERC20ExtendedTokensBridge__factory, OssifiableProxy__factory, EmptyContractStub__factory, CrossDomainMessengerStub__factory, @@ -18,7 +18,7 @@ import { getContractAddress } from "ethers/lib/utils"; import { JsonRpcProvider } from "@ethersproject/providers"; import { BigNumber } from "ethers"; -unit("Optimism:: L2ERC20TokenBridge", ctxFactory) +unit("Optimism:: L2ERC20ExtendedTokensBridge", ctxFactory) .test("l1TokenBridge()", async (ctx) => { assert.equal( await ctx.l2TokenBridge.l1TokenBridge(), @@ -727,7 +727,7 @@ async function ctxFactory() { l2TokenBridgeProxyAddress ); - const l2TokenBridgeImpl = await new L2ERC20TokenBridge__factory( + const l2TokenBridgeImpl = await new L2ERC20ExtendedTokensBridge__factory( deployer ).deploy( l2MessengerStub.address, @@ -748,7 +748,7 @@ async function ctxFactory() { ]) ); - const l2TokenBridge = L2ERC20TokenBridge__factory.connect( + const l2TokenBridge = L2ERC20ExtendedTokensBridge__factory.connect( l2TokenBridgeProxy.address, deployer ); diff --git a/test/optimism/TokenRateNotifier.unit.test.ts b/test/optimism/TokenRateNotifier.unit.test.ts index 14997721..d8ca1bc0 100644 --- a/test/optimism/TokenRateNotifier.unit.test.ts +++ b/test/optimism/TokenRateNotifier.unit.test.ts @@ -17,204 +17,225 @@ import { unit("TokenRateNotifier", ctxFactory) - .test("initial state", async (ctx) => { - const { tokenRateNotifier } = ctx.contracts; - - assert.equalBN(await tokenRateNotifier.MAX_OBSERVERS_COUNT(), 16); - assert.equal(await tokenRateNotifier.INVALID_INTERFACE_ID(), "0xffffffff"); - const iTokenRateObserver = getInterfaceID(ITokenRatePusher__factory.createInterface()); - assert.equal(await tokenRateNotifier.REQUIRED_INTERFACE(), iTokenRateObserver._hex); - assert.equalBN(await tokenRateNotifier.observersLength(), 0); - }) - - .test("addObserver() :: not the owner", async (ctx) => { - const { tokenRateNotifier } = ctx.contracts; - const { stranger } = ctx.accounts; - - await assert.revertsWith( - tokenRateNotifier - .connect(stranger) - .addObserver(ethers.constants.AddressZero), - "Ownable: caller is not the owner" - ); - }) - - .test("addObserver() :: revert on adding zero address observer", async (ctx) => { - const { tokenRateNotifier } = ctx.contracts; - - await assert.revertsWith( - tokenRateNotifier.addObserver(ethers.constants.AddressZero), - "ErrorZeroAddressObserver()" - ); - }) - - .test("addObserver() :: revert on adding observer with bad interface", async (ctx) => { - const { tokenRateNotifier } = ctx.contracts; - const { deployer } = ctx.accounts; - - const observer = await new TokenRateNotifier__factory(deployer).deploy(); - await assert.revertsWith( - tokenRateNotifier.addObserver(observer.address), - "ErrorBadObserverInterface()" - ); - }) - - .test("addObserver() :: revert on adding too many observers", async (ctx) => { - const { tokenRateNotifier, opStackTokenRatePusher } = ctx.contracts; - - assert.equalBN(await tokenRateNotifier.observersLength(), 0); - const maxObservers = await tokenRateNotifier.MAX_OBSERVERS_COUNT(); - for (let i = 0; i < maxObservers.toNumber(); i++) { - await tokenRateNotifier.addObserver(opStackTokenRatePusher.address); - } - assert.equalBN(await tokenRateNotifier.observersLength(), maxObservers); - - await assert.revertsWith( - tokenRateNotifier.addObserver(opStackTokenRatePusher.address), - "ErrorMaxObserversCountExceeded()" - ); - }) - - .test("addObserver() :: happy path of adding observer", async (ctx) => { - const { tokenRateNotifier, opStackTokenRatePusher } = ctx.contracts; - - assert.equalBN(await tokenRateNotifier.observersLength(), 0); - const tx = await tokenRateNotifier.addObserver(opStackTokenRatePusher.address); - assert.equalBN(await tokenRateNotifier.observersLength(), 1); - - await assert.emits(tokenRateNotifier, tx, "ObserverAdded", [opStackTokenRatePusher.address]); - }) - - .test("removeObserver() :: revert on calling by not the owner", async (ctx) => { - const { tokenRateNotifier } = ctx.contracts; - const { stranger } = ctx.accounts; - - await assert.revertsWith( - tokenRateNotifier - .connect(stranger) - .removeObserver(ethers.constants.AddressZero), - "Ownable: caller is not the owner" - ); - }) - - .test("removeObserver() :: revert on removing non-added observer", async (ctx) => { - const { tokenRateNotifier, opStackTokenRatePusher } = ctx.contracts; - - assert.equalBN(await tokenRateNotifier.observersLength(), 0); - - await assert.revertsWith( - tokenRateNotifier.removeObserver(opStackTokenRatePusher.address), - "ErrorNoObserverToRemove()" - ); - }) - - .test("removeObserver() :: happy path of removing observer", async (ctx) => { - const { tokenRateNotifier, opStackTokenRatePusher } = ctx.contracts; - - assert.equalBN(await tokenRateNotifier.observersLength(), 0); - - await tokenRateNotifier.addObserver(opStackTokenRatePusher.address); - - assert.equalBN(await tokenRateNotifier.observersLength(), 1); - - const tx = await tokenRateNotifier.removeObserver(opStackTokenRatePusher.address); - await assert.emits(tokenRateNotifier, tx, "ObserverRemoved", [opStackTokenRatePusher.address]); - - assert.equalBN(await tokenRateNotifier.observersLength(), 0); - }) - - .test("handlePostTokenRebase() :: failed with some error", async (ctx) => { - const { tokenRateNotifier } = ctx.contracts; - const { deployer } = ctx.accounts; - - const observer = await new OpStackTokenRatePusherWithSomeErrorStub__factory(deployer).deploy(); - await tokenRateNotifier.addObserver(observer.address); - - const tx = await tokenRateNotifier.handlePostTokenRebase(1,2,3,4,5,6,7); - - await assert.emits(tokenRateNotifier, tx, "PushTokenRateFailed", [observer.address, "0x332e27d2"]); - }) - - .test("handlePostTokenRebase() :: revert when observer has out of gas error", async (ctx) => { - const { tokenRateNotifier } = ctx.contracts; - const { deployer } = ctx.accounts; - - const observer = await new OpStackTokenRatePusherWithOutOfGasErrorStub__factory(deployer).deploy(); - await tokenRateNotifier.addObserver(observer.address); - - await assert.revertsWith( - tokenRateNotifier.handlePostTokenRebase(1,2,3,4,5,6,7), - "ErrorTokenRateNotifierRevertedWithNoData()" - ); - }) - - .test("handlePostTokenRebase() :: happy path of handling token rebase", async (ctx) => { - const { - tokenRateNotifier, - l1MessengerStub, - opStackTokenRatePusher, - l1TokenNonRebasableStub - } = ctx.contracts; - const { tokenRateOracle } = ctx.accounts; - const { l2GasLimitForPushingTokenRate } = ctx.constants; - - let tokenRate = await l1TokenNonRebasableStub.stEthPerToken(); - await tokenRateNotifier.addObserver(opStackTokenRatePusher.address); - let tx = await tokenRateNotifier.handlePostTokenRebase(1,2,3,4,5,6,7); - - const provider = await ethers.provider; - const blockNumber = await provider.getBlockNumber(); - const blockTimestamp = (await provider.getBlock(blockNumber)).timestamp; - - await assert.emits(l1MessengerStub, tx, "SentMessage", [ - tokenRateOracle.address, - opStackTokenRatePusher.address, - ITokenRateOracle__factory.createInterface().encodeFunctionData( - "updateRate", - [ - tokenRate, - blockTimestamp - ] - ), - 1, - l2GasLimitForPushingTokenRate, - ]); - }) - - .run(); + .test("initial state", async (ctx) => { + const { tokenRateNotifier } = ctx.contracts; + + assert.equalBN(await tokenRateNotifier.MAX_OBSERVERS_COUNT(), 32); + const iTokenRateObserver = getInterfaceID(ITokenRatePusher__factory.createInterface()); + assert.equal(await tokenRateNotifier.REQUIRED_INTERFACE(), iTokenRateObserver._hex); + assert.equalBN(await tokenRateNotifier.observersLength(), 0); + }) + + .test("addObserver() :: not the owner", async (ctx) => { + const { tokenRateNotifier } = ctx.contracts; + const { stranger } = ctx.accounts; + + await assert.revertsWith( + tokenRateNotifier + .connect(stranger) + .addObserver(ethers.constants.AddressZero), + "Ownable: caller is not the owner" + ); + }) + + .test("addObserver() :: revert on adding zero address observer", async (ctx) => { + const { tokenRateNotifier } = ctx.contracts; + + await assert.revertsWith( + tokenRateNotifier + .connect(ctx.accounts.owner) + .addObserver(ethers.constants.AddressZero), + "ErrorZeroAddressObserver()" + ); + }) + + .test("addObserver() :: revert on adding observer with bad interface", async (ctx) => { + const { tokenRateNotifier } = ctx.contracts; + const { deployer } = ctx.accounts; + + const observer = await new TokenRateNotifier__factory(deployer).deploy(deployer.address); + await assert.revertsWith( + tokenRateNotifier + .connect(ctx.accounts.owner) + .addObserver(observer.address), + "ErrorBadObserverInterface()" + ); + }) + + .test("addObserver() :: revert on adding too many observers", async (ctx) => { + const { tokenRateNotifier, opStackTokenRatePusher } = ctx.contracts; + + assert.equalBN(await tokenRateNotifier.observersLength(), 0); + const maxObservers = await tokenRateNotifier.MAX_OBSERVERS_COUNT(); + for (let i = 0; i < maxObservers.toNumber(); i++) { + await tokenRateNotifier + .connect(ctx.accounts.owner) + .addObserver(opStackTokenRatePusher.address); + } + assert.equalBN(await tokenRateNotifier.observersLength(), maxObservers); + + await assert.revertsWith( + tokenRateNotifier + .connect(ctx.accounts.owner) + .addObserver(opStackTokenRatePusher.address), + "ErrorMaxObserversCountExceeded()" + ); + }) + + .test("addObserver() :: happy path of adding observer", async (ctx) => { + const { tokenRateNotifier, opStackTokenRatePusher } = ctx.contracts; + + assert.equalBN(await tokenRateNotifier.observersLength(), 0); + const tx = await tokenRateNotifier + .connect(ctx.accounts.owner) + .addObserver(opStackTokenRatePusher.address); + assert.equalBN(await tokenRateNotifier.observersLength(), 1); + + await assert.emits(tokenRateNotifier, tx, "ObserverAdded", [opStackTokenRatePusher.address]); + }) + + .test("removeObserver() :: revert on calling by not the owner", async (ctx) => { + const { tokenRateNotifier } = ctx.contracts; + const { stranger } = ctx.accounts; + + await assert.revertsWith( + tokenRateNotifier + .connect(stranger) + .removeObserver(ethers.constants.AddressZero), + "Ownable: caller is not the owner" + ); + }) + + .test("removeObserver() :: revert on removing non-added observer", async (ctx) => { + const { tokenRateNotifier, opStackTokenRatePusher } = ctx.contracts; + + assert.equalBN(await tokenRateNotifier.observersLength(), 0); + + await assert.revertsWith( + tokenRateNotifier + .connect(ctx.accounts.owner) + .removeObserver(opStackTokenRatePusher.address), + "ErrorNoObserverToRemove()" + ); + }) + + .test("removeObserver() :: happy path of removing observer", async (ctx) => { + const { tokenRateNotifier, opStackTokenRatePusher } = ctx.contracts; + + assert.equalBN(await tokenRateNotifier.observersLength(), 0); + + await tokenRateNotifier + .connect(ctx.accounts.owner) + .addObserver(opStackTokenRatePusher.address); + + assert.equalBN(await tokenRateNotifier.observersLength(), 1); + + const tx = await tokenRateNotifier + .connect(ctx.accounts.owner) + .removeObserver(opStackTokenRatePusher.address); + await assert.emits(tokenRateNotifier, tx, "ObserverRemoved", [opStackTokenRatePusher.address]); + + assert.equalBN(await tokenRateNotifier.observersLength(), 0); + }) + + .test("handlePostTokenRebase() :: failed with some error", async (ctx) => { + const { tokenRateNotifier } = ctx.contracts; + const { deployer } = ctx.accounts; + + const observer = await new OpStackTokenRatePusherWithSomeErrorStub__factory(deployer).deploy(); + await tokenRateNotifier + .connect(ctx.accounts.owner) + .addObserver(observer.address); + + const tx = await tokenRateNotifier.handlePostTokenRebase(1, 2, 3, 4, 5, 6, 7); + + await assert.emits(tokenRateNotifier, tx, "PushTokenRateFailed", [observer.address, "0x332e27d2"]); + }) + + .test("handlePostTokenRebase() :: revert when observer has out of gas error", async (ctx) => { + const { tokenRateNotifier } = ctx.contracts; + const { deployer } = ctx.accounts; + + const observer = await new OpStackTokenRatePusherWithOutOfGasErrorStub__factory(deployer).deploy(); + await tokenRateNotifier + .connect(ctx.accounts.owner) + .addObserver(observer.address); + + await assert.revertsWith( + tokenRateNotifier.handlePostTokenRebase(1, 2, 3, 4, 5, 6, 7), + "ErrorTokenRateNotifierRevertedWithNoData()" + ); + }) + + .test("handlePostTokenRebase() :: happy path of handling token rebase", async (ctx) => { + const { + tokenRateNotifier, + l1MessengerStub, + opStackTokenRatePusher, + l1TokenNonRebasableStub + } = ctx.contracts; + const { tokenRateOracle } = ctx.accounts; + const { l2GasLimitForPushingTokenRate } = ctx.constants; + + let tokenRate = await l1TokenNonRebasableStub.stEthPerToken(); + await tokenRateNotifier + .connect(ctx.accounts.owner) + .addObserver(opStackTokenRatePusher.address); + let tx = await tokenRateNotifier.handlePostTokenRebase(1, 2, 3, 4, 5, 6, 7); + + const provider = await ethers.provider; + const blockNumber = await provider.getBlockNumber(); + const blockTimestamp = (await provider.getBlock(blockNumber)).timestamp; + + await assert.emits(l1MessengerStub, tx, "SentMessage", [ + tokenRateOracle.address, + opStackTokenRatePusher.address, + ITokenRateOracle__factory.createInterface().encodeFunctionData( + "updateRate", + [ + tokenRate, + blockTimestamp + ] + ), + 1, + l2GasLimitForPushingTokenRate, + ]); + }) + + .run(); async function ctxFactory() { - const [deployer, bridge, stranger, tokenRateOracle] = await ethers.getSigners(); - const tokenRateNotifier = await new TokenRateNotifier__factory(deployer).deploy(); + const [deployer, owner, stranger, tokenRateOracle] = await ethers.getSigners(); + const tokenRateNotifier = await new TokenRateNotifier__factory(deployer).deploy(owner.address); const l1TokenRebasableStub = await new ERC20BridgedStub__factory(deployer).deploy( - "L1 Token Rebasable", - "L1R" + "L1 Token Rebasable", + "L1R" ); const l1TokenNonRebasableStub = await new ERC20WrapperStub__factory(deployer).deploy( - l1TokenRebasableStub.address, - "L1 Token Non Rebasable", - "L1NR" + l1TokenRebasableStub.address, + "L1 Token Non Rebasable", + "L1NR" ); const l1MessengerStub = await new CrossDomainMessengerStub__factory( - deployer + deployer ).deploy({ value: wei.toBigNumber(wei`1 ether`) }); const l2GasLimitForPushingTokenRate = 123; const opStackTokenRatePusher = await new OpStackTokenRatePusher__factory(deployer).deploy( - l1MessengerStub.address, - l1TokenNonRebasableStub.address, - tokenRateOracle.address, - l2GasLimitForPushingTokenRate + l1MessengerStub.address, + l1TokenNonRebasableStub.address, + tokenRateOracle.address, + l2GasLimitForPushingTokenRate ); return { - accounts: { deployer, bridge, stranger, tokenRateOracle }, - contracts: { tokenRateNotifier, opStackTokenRatePusher, l1MessengerStub, l1TokenNonRebasableStub }, - constants: { l2GasLimitForPushingTokenRate } + accounts: { deployer, owner, stranger, tokenRateOracle }, + contracts: { tokenRateNotifier, opStackTokenRatePusher, l1MessengerStub, l1TokenNonRebasableStub }, + constants: { l2GasLimitForPushingTokenRate } }; } diff --git a/test/optimism/bridging-rebasable-to.e2e.test.ts b/test/optimism/bridging-rebasable-to.e2e.test.ts index ca582770..6dc3d5ac 100644 --- a/test/optimism/bridging-rebasable-to.e2e.test.ts +++ b/test/optimism/bridging-rebasable-to.e2e.test.ts @@ -80,7 +80,7 @@ import { }) .step("Withdraw tokens from L2 via withdrawERC20To()", async (ctx) => { - withdrawTokensTxResponse = await ctx.l2ERC20TokenBridge + withdrawTokensTxResponse = await ctx.l2ERC20ExtendedTokensBridge .connect(ctx.l2Tester) .withdrawTo( ctx.l2TokenRebasable.address, @@ -147,7 +147,7 @@ import { l1TokenRebasable: testingSetup.l1TokenRebasable, l2TokenRebasable: testingSetup.l2TokenRebasable, l1LidoTokensBridge: testingSetup.l1LidoTokensBridge, - l2ERC20TokenBridge: testingSetup.l2ERC20TokenBridge, + l2ERC20ExtendedTokensBridge: testingSetup.l2ERC20ExtendedTokensBridge, crossChainMessenger: new CrossChainMessenger({ l2ChainId: network.chainId("opt", networkName), l1ChainId: network.chainId("eth", networkName), @@ -157,7 +157,7 @@ import { LidoBridge: { Adapter: LidoBridgeAdapter, l1Bridge: testingSetup.l1LidoTokensBridge.address, - l2Bridge: testingSetup.l2ERC20TokenBridge.address, + l2Bridge: testingSetup.l2ERC20ExtendedTokensBridge.address, }, }, }), diff --git a/test/optimism/bridging-rebasable.e2e.test.ts b/test/optimism/bridging-rebasable.e2e.test.ts index 868f0a68..27aaa65d 100644 --- a/test/optimism/bridging-rebasable.e2e.test.ts +++ b/test/optimism/bridging-rebasable.e2e.test.ts @@ -144,7 +144,7 @@ import { LidoBridge: { Adapter: LidoBridgeAdapter, l1Bridge: testingSetup.l1LidoTokensBridge.address, - l2Bridge: testingSetup.l2ERC20TokenBridge.address, + l2Bridge: testingSetup.l2ERC20ExtendedTokensBridge.address, }, }, }), diff --git a/test/optimism/bridging-rebasable.integration.test.ts b/test/optimism/bridging-rebasable.integration.test.ts index 68cbabee..fbe208ae 100644 --- a/test/optimism/bridging-rebasable.integration.test.ts +++ b/test/optimism/bridging-rebasable.integration.test.ts @@ -17,13 +17,13 @@ scenario("Optimism :: Bridging integration test", ctxFactory) .step("Activate bridging on L1", async (ctx) => { const { l1LidoTokensBridge } = ctx; - const { l1ERC20TokenBridgeAdmin } = ctx.accounts; + const { l1ERC20ExtendedTokensBridgeAdmin } = ctx.accounts; const isDepositsEnabled = await l1LidoTokensBridge.isDepositsEnabled(); if (!isDepositsEnabled) { await l1LidoTokensBridge - .connect(l1ERC20TokenBridgeAdmin) + .connect(l1ERC20ExtendedTokensBridgeAdmin) .enableDeposits(); } else { console.log("L1 deposits already enabled"); @@ -34,7 +34,7 @@ scenario("Optimism :: Bridging integration test", ctxFactory) if (!isWithdrawalsEnabled) { await l1LidoTokensBridge - .connect(l1ERC20TokenBridgeAdmin) + .connect(l1ERC20ExtendedTokensBridgeAdmin) .enableWithdrawals(); } else { console.log("L1 withdrawals already enabled"); @@ -45,32 +45,32 @@ scenario("Optimism :: Bridging integration test", ctxFactory) }) .step("Activate bridging on L2", async (ctx) => { - const { l2ERC20TokenBridge } = ctx; - const { l2ERC20TokenBridgeAdmin } = ctx.accounts; + const { l2ERC20ExtendedTokensBridge } = ctx; + const { l2ERC20ExtendedTokensBridgeAdmin } = ctx.accounts; - const isDepositsEnabled = await l2ERC20TokenBridge.isDepositsEnabled(); + const isDepositsEnabled = await l2ERC20ExtendedTokensBridge.isDepositsEnabled(); if (!isDepositsEnabled) { - await l2ERC20TokenBridge - .connect(l2ERC20TokenBridgeAdmin) + await l2ERC20ExtendedTokensBridge + .connect(l2ERC20ExtendedTokensBridgeAdmin) .enableDeposits(); } else { console.log("L2 deposits already enabled"); } const isWithdrawalsEnabled = - await l2ERC20TokenBridge.isWithdrawalsEnabled(); + await l2ERC20ExtendedTokensBridge.isWithdrawalsEnabled(); if (!isWithdrawalsEnabled) { - await l2ERC20TokenBridge - .connect(l2ERC20TokenBridgeAdmin) + await l2ERC20ExtendedTokensBridge + .connect(l2ERC20ExtendedTokensBridgeAdmin) .enableWithdrawals(); } else { console.log("L2 withdrawals already enabled"); } - assert.isTrue(await l2ERC20TokenBridge.isDepositsEnabled()); - assert.isTrue(await l2ERC20TokenBridge.isWithdrawalsEnabled()); + assert.isTrue(await l2ERC20ExtendedTokensBridge.isDepositsEnabled()); + assert.isTrue(await l2ERC20ExtendedTokensBridge.isWithdrawalsEnabled()); }) .step("Set up Token Rate Oracle by pushing first rate", async (ctx) => { @@ -81,7 +81,7 @@ scenario("Optimism :: Bridging integration test", ctxFactory) l2TokenRebasable, l1LidoTokensBridge, l2CrossDomainMessenger, - l2ERC20TokenBridge, + l2ERC20ExtendedTokensBridge, l2Provider } = ctx; @@ -94,10 +94,10 @@ scenario("Optimism :: Bridging integration test", ctxFactory) .relayMessage( 1, l1LidoTokensBridge.address, - l2ERC20TokenBridge.address, + l2ERC20ExtendedTokensBridge.address, 0, 300_000, - l2ERC20TokenBridge.interface.encodeFunctionData("finalizeDeposit", [ + l2ERC20ExtendedTokensBridge.interface.encodeFunctionData("finalizeDeposit", [ l1TokenRebasable.address, l2TokenRebasable.address, tokenHolderA.address, @@ -116,7 +116,7 @@ scenario("Optimism :: Bridging integration test", ctxFactory) l1LidoTokensBridge, l2TokenRebasable, l1CrossDomainMessenger, - l2ERC20TokenBridge, + l2ERC20ExtendedTokensBridge, l1Provider } = ctx; @@ -130,7 +130,7 @@ scenario("Optimism :: Bridging integration test", ctxFactory) tokenHolderA.address ); - const l1ERC20TokenBridgeBalanceBefore = await l1TokenRebasable.balanceOf( + const l1ERC20ExtendedTokensBridgeBalanceBefore = await l1TokenRebasable.balanceOf( l1LidoTokensBridge.address ); @@ -155,7 +155,7 @@ scenario("Optimism :: Bridging integration test", ctxFactory) dataToSend, ]); - const l2DepositCalldata = l2ERC20TokenBridge.interface.encodeFunctionData( + const l2DepositCalldata = l2ERC20ExtendedTokensBridge.interface.encodeFunctionData( "finalizeDeposit", [ l1TokenRebasable.address, @@ -170,7 +170,7 @@ scenario("Optimism :: Bridging integration test", ctxFactory) const messageNonce = await l1CrossDomainMessenger.messageNonce(); await assert.emits(l1CrossDomainMessenger, tx, "SentMessage", [ - l2ERC20TokenBridge.address, + l2ERC20ExtendedTokensBridge.address, l1LidoTokensBridge.address, l2DepositCalldata, messageNonce, @@ -179,7 +179,7 @@ scenario("Optimism :: Bridging integration test", ctxFactory) assert.equalBN( await l1Token.balanceOf(l1LidoTokensBridge.address), - l1ERC20TokenBridgeBalanceBefore + l1ERC20ExtendedTokensBridgeBalanceBefore ); assert.equalBN( @@ -195,7 +195,7 @@ scenario("Optimism :: Bridging integration test", ctxFactory) l2TokenRebasable, l1LidoTokensBridge, l2CrossDomainMessenger, - l2ERC20TokenBridge, + l2ERC20ExtendedTokensBridge, l2Provider } = ctx; @@ -215,10 +215,10 @@ scenario("Optimism :: Bridging integration test", ctxFactory) .relayMessage( 1, l1LidoTokensBridge.address, - l2ERC20TokenBridge.address, + l2ERC20ExtendedTokensBridge.address, 0, 300_000, - l2ERC20TokenBridge.interface.encodeFunctionData("finalizeDeposit", [ + l2ERC20ExtendedTokensBridge.interface.encodeFunctionData("finalizeDeposit", [ l1TokenRebasable.address, l2TokenRebasable.address, tokenHolderA.address, @@ -229,7 +229,7 @@ scenario("Optimism :: Bridging integration test", ctxFactory) { gasLimit: 5_000_000 } ); - await assert.emits(l2ERC20TokenBridge, tx, "DepositFinalized", [ + await assert.emits(l2ERC20ExtendedTokensBridge, tx, "DepositFinalized", [ l1TokenRebasable.address, l2TokenRebasable.address, tokenHolderA.address, @@ -255,7 +255,7 @@ scenario("Optimism :: Bridging integration test", ctxFactory) l1LidoTokensBridge, l2TokenRebasable, l1CrossDomainMessenger, - l2ERC20TokenBridge, + l2ERC20ExtendedTokensBridge, l1Provider } = ctx; const { accountA: tokenHolderA } = ctx.accounts; @@ -269,7 +269,7 @@ scenario("Optimism :: Bridging integration test", ctxFactory) tokenHolderA.address ); - const l1ERC20TokenBridgeBalanceBefore = await l1TokenRebasable.balanceOf( + const l1ERC20ExtendedTokensBridgeBalanceBefore = await l1TokenRebasable.balanceOf( l1LidoTokensBridge.address ); @@ -294,7 +294,7 @@ scenario("Optimism :: Bridging integration test", ctxFactory) dataToSend, ]); - const l2DepositCalldata = l2ERC20TokenBridge.interface.encodeFunctionData( + const l2DepositCalldata = l2ERC20ExtendedTokensBridge.interface.encodeFunctionData( "finalizeDeposit", [ l1TokenRebasable.address, @@ -309,7 +309,7 @@ scenario("Optimism :: Bridging integration test", ctxFactory) const messageNonce = await l1CrossDomainMessenger.messageNonce(); await assert.emits(l1CrossDomainMessenger, tx, "SentMessage", [ - l2ERC20TokenBridge.address, + l2ERC20ExtendedTokensBridge.address, l1LidoTokensBridge.address, l2DepositCalldata, messageNonce, @@ -318,7 +318,7 @@ scenario("Optimism :: Bridging integration test", ctxFactory) assert.equalBN( await l1Token.balanceOf(l1LidoTokensBridge.address), - l1ERC20TokenBridgeBalanceBefore.add(depositAmountNonRebasable) + l1ERC20ExtendedTokensBridgeBalanceBefore.add(depositAmountNonRebasable) ); assert.equalBN( @@ -334,7 +334,7 @@ scenario("Optimism :: Bridging integration test", ctxFactory) l2TokenRebasable, l1LidoTokensBridge, l2CrossDomainMessenger, - l2ERC20TokenBridge, + l2ERC20ExtendedTokensBridge, l2Provider } = ctx; const { depositAmountNonRebasable, depositAmountRebasable } = ctx.common; @@ -354,10 +354,10 @@ scenario("Optimism :: Bridging integration test", ctxFactory) .relayMessage( 1, l1LidoTokensBridge.address, - l2ERC20TokenBridge.address, + l2ERC20ExtendedTokensBridge.address, 0, 300_000, - l2ERC20TokenBridge.interface.encodeFunctionData("finalizeDeposit", [ + l2ERC20ExtendedTokensBridge.interface.encodeFunctionData("finalizeDeposit", [ l1TokenRebasable.address, l2TokenRebasable.address, tokenHolderA.address, @@ -368,7 +368,7 @@ scenario("Optimism :: Bridging integration test", ctxFactory) { gasLimit: 5_000_000 } ); - await assert.emits(l2ERC20TokenBridge, tx, "DepositFinalized", [ + await assert.emits(l2ERC20ExtendedTokensBridge, tx, "DepositFinalized", [ l1TokenRebasable.address, l2TokenRebasable.address, tokenHolderA.address, @@ -393,7 +393,7 @@ scenario("Optimism :: Bridging integration test", ctxFactory) const { l1TokenRebasable, l2TokenRebasable, - l2ERC20TokenBridge + l2ERC20ExtendedTokensBridge } = ctx; const tokenHolderABalanceBefore = await l2TokenRebasable.balanceOf( @@ -401,7 +401,7 @@ scenario("Optimism :: Bridging integration test", ctxFactory) ); const l2TotalSupplyBefore = await l2TokenRebasable.totalSupply(); - const tx = await l2ERC20TokenBridge + const tx = await l2ERC20ExtendedTokensBridge .connect(tokenHolderA.l2Signer) .withdraw( l2TokenRebasable.address, @@ -410,7 +410,7 @@ scenario("Optimism :: Bridging integration test", ctxFactory) "0x" ); - await assert.emits(l2ERC20TokenBridge, tx, "WithdrawalInitiated", [ + await assert.emits(l2ERC20ExtendedTokensBridge, tx, "WithdrawalInitiated", [ l1TokenRebasable.address, l2TokenRebasable.address, tokenHolderA.address, @@ -437,7 +437,7 @@ scenario("Optimism :: Bridging integration test", ctxFactory) l1LidoTokensBridge, l2CrossDomainMessenger, l2TokenRebasable, - l2ERC20TokenBridge, + l2ERC20ExtendedTokensBridge, } = ctx; const { accountA: tokenHolderA, l1Stranger } = ctx.accounts; const { withdrawalAmountNonRebasable, withdrawalAmountRebasable } = ctx.common; @@ -445,13 +445,13 @@ scenario("Optimism :: Bridging integration test", ctxFactory) const tokenHolderABalanceBefore = await l1TokenRebasable.balanceOf( tokenHolderA.address ); - const l1ERC20TokenBridgeBalanceBefore = await l1Token.balanceOf( + const l1ERC20ExtendedTokensBridgeBalanceBefore = await l1Token.balanceOf( l1LidoTokensBridge.address ); await l1CrossDomainMessenger .connect(l1Stranger) - .setXDomainMessageSender(l2ERC20TokenBridge.address); + .setXDomainMessageSender(l2ERC20ExtendedTokensBridge.address); const tx = await l1CrossDomainMessenger .connect(l1Stranger) @@ -483,7 +483,7 @@ scenario("Optimism :: Bridging integration test", ctxFactory) assert.equalBN( await l1Token.balanceOf(l1LidoTokensBridge.address), - l1ERC20TokenBridgeBalanceBefore.sub(withdrawalAmountNonRebasable) + l1ERC20ExtendedTokensBridgeBalanceBefore.sub(withdrawalAmountNonRebasable) ); assert.equalBN( @@ -501,7 +501,7 @@ scenario("Optimism :: Bridging integration test", ctxFactory) l1LidoTokensBridge, l2TokenRebasable, l1CrossDomainMessenger, - l2ERC20TokenBridge, + l2ERC20ExtendedTokensBridge, l1Provider } = ctx; const { accountA: tokenHolderA, accountB: tokenHolderB } = ctx.accounts; @@ -518,7 +518,7 @@ scenario("Optimism :: Bridging integration test", ctxFactory) const tokenHolderABalanceBefore = await l1TokenRebasable.balanceOf( tokenHolderA.address ); - const l1ERC20TokenBridgeBalanceBefore = await l1Token.balanceOf( + const l1ERC20ExtendedTokensBridgeBalanceBefore = await l1Token.balanceOf( l1LidoTokensBridge.address ); @@ -544,7 +544,7 @@ scenario("Optimism :: Bridging integration test", ctxFactory) dataToSend, ]); - const l2DepositCalldata = l2ERC20TokenBridge.interface.encodeFunctionData( + const l2DepositCalldata = l2ERC20ExtendedTokensBridge.interface.encodeFunctionData( "finalizeDeposit", [ l1TokenRebasable.address, @@ -559,7 +559,7 @@ scenario("Optimism :: Bridging integration test", ctxFactory) const messageNonce = await l1CrossDomainMessenger.messageNonce(); await assert.emits(l1CrossDomainMessenger, tx, "SentMessage", [ - l2ERC20TokenBridge.address, + l2ERC20ExtendedTokensBridge.address, l1LidoTokensBridge.address, l2DepositCalldata, messageNonce, @@ -568,7 +568,7 @@ scenario("Optimism :: Bridging integration test", ctxFactory) assert.equalBN( await l1Token.balanceOf(l1LidoTokensBridge.address), - l1ERC20TokenBridgeBalanceBefore.add(depositAmountNonRebasable) + l1ERC20ExtendedTokensBridgeBalanceBefore.add(depositAmountNonRebasable) ); assert.equalBN( @@ -584,7 +584,7 @@ scenario("Optimism :: Bridging integration test", ctxFactory) l1LidoTokensBridge, l2TokenRebasable, l2CrossDomainMessenger, - l2ERC20TokenBridge, + l2ERC20ExtendedTokensBridge, l2Provider } = ctx; @@ -612,10 +612,10 @@ scenario("Optimism :: Bridging integration test", ctxFactory) .relayMessage( 1, l1LidoTokensBridge.address, - l2ERC20TokenBridge.address, + l2ERC20ExtendedTokensBridge.address, 0, 300_000, - l2ERC20TokenBridge.interface.encodeFunctionData("finalizeDeposit", [ + l2ERC20ExtendedTokensBridge.interface.encodeFunctionData("finalizeDeposit", [ l1TokenRebasable.address, l2TokenRebasable.address, tokenHolderA.address, @@ -626,7 +626,7 @@ scenario("Optimism :: Bridging integration test", ctxFactory) { gasLimit: 5_000_000 } ); - await assert.emits(l2ERC20TokenBridge, tx, "DepositFinalized", [ + await assert.emits(l2ERC20ExtendedTokensBridge, tx, "DepositFinalized", [ l1TokenRebasable.address, l2TokenRebasable.address, tokenHolderA.address, @@ -647,7 +647,7 @@ scenario("Optimism :: Bridging integration test", ctxFactory) }) .step("L2 -> L1 withdrawal via withdrawTo()", async (ctx) => { - const { l1TokenRebasable, l2TokenRebasable, l2ERC20TokenBridge } = ctx; + const { l1TokenRebasable, l2TokenRebasable, l2ERC20ExtendedTokensBridge } = ctx; const { accountA: tokenHolderA, accountB: tokenHolderB } = ctx.accounts; const { exchangeRate } = ctx.common; @@ -659,7 +659,7 @@ scenario("Optimism :: Bridging integration test", ctxFactory) ); const l2TotalSupplyBefore = await l2TokenRebasable.totalSupply(); - const tx = await l2ERC20TokenBridge + const tx = await l2ERC20ExtendedTokensBridge .connect(tokenHolderB.l2Signer) .withdrawTo( l2TokenRebasable.address, @@ -669,7 +669,7 @@ scenario("Optimism :: Bridging integration test", ctxFactory) "0x" ); - await assert.emits(l2ERC20TokenBridge, tx, "WithdrawalInitiated", [ + await assert.emits(l2ERC20ExtendedTokensBridge, tx, "WithdrawalInitiated", [ l1TokenRebasable.address, l2TokenRebasable.address, tokenHolderB.address, @@ -697,7 +697,7 @@ scenario("Optimism :: Bridging integration test", ctxFactory) l1LidoTokensBridge, l2CrossDomainMessenger, l2TokenRebasable, - l2ERC20TokenBridge, + l2ERC20ExtendedTokensBridge, } = ctx; const { accountA: tokenHolderA, @@ -712,13 +712,13 @@ scenario("Optimism :: Bridging integration test", ctxFactory) const tokenHolderABalanceBefore = await l1TokenRebasable.balanceOf( tokenHolderA.address ); - const l1ERC20TokenBridgeBalanceBefore = await l1Token.balanceOf( + const l1ERC20ExtendedTokensBridgeBalanceBefore = await l1Token.balanceOf( l1LidoTokensBridge.address ); await l1CrossDomainMessenger .connect(l1Stranger) - .setXDomainMessageSender(l2ERC20TokenBridge.address); + .setXDomainMessageSender(l2ERC20ExtendedTokensBridge.address); const tx = await l1CrossDomainMessenger .connect(l1Stranger) @@ -750,7 +750,7 @@ scenario("Optimism :: Bridging integration test", ctxFactory) assert.equalBN( await l1Token.balanceOf(l1LidoTokensBridge.address), - l1ERC20TokenBridgeBalanceBefore.sub(withdrawalAmountNonRebasable) + l1ERC20ExtendedTokensBridgeBalanceBefore.sub(withdrawalAmountNonRebasable) ); assert.equalBN( @@ -767,8 +767,8 @@ async function ctxFactory() { const { l1Provider, l2Provider, - l1ERC20TokenBridgeAdmin, - l2ERC20TokenBridgeAdmin, + l1ERC20ExtendedTokensBridgeAdmin, + l2ERC20ExtendedTokensBridgeAdmin, ...contracts } = await optimism.testing(networkName).getIntegrationTestSetup(); @@ -794,13 +794,13 @@ async function ctxFactory() { ); await testing.setBalance( - await l1ERC20TokenBridgeAdmin.getAddress(), + await l1ERC20ExtendedTokensBridgeAdmin.getAddress(), wei.toBigNumber(wei`1 ether`), l1Provider ); await testing.setBalance( - await l2ERC20TokenBridgeAdmin.getAddress(), + await l2ERC20ExtendedTokensBridgeAdmin.getAddress(), wei.toBigNumber(wei`1 ether`), l2Provider ); @@ -828,8 +828,8 @@ async function ctxFactory() { accountA, accountB, l1Stranger: testing.accounts.stranger(l1Provider), - l1ERC20TokenBridgeAdmin, - l2ERC20TokenBridgeAdmin, + l1ERC20ExtendedTokensBridgeAdmin, + l2ERC20ExtendedTokensBridgeAdmin, l1CrossDomainMessengerAliased, }, common: { diff --git a/test/optimism/bridging-to.e2e.test.ts b/test/optimism/bridging-to.e2e.test.ts index 5c9788ed..13d9833f 100644 --- a/test/optimism/bridging-to.e2e.test.ts +++ b/test/optimism/bridging-to.e2e.test.ts @@ -79,7 +79,7 @@ scenario("Optimism :: Bridging via depositTo/withdrawTo E2E test", ctxFactory) }) .step("Withdraw tokens from L2 via withdrawERC20To()", async (ctx) => { - withdrawTokensTxResponse = await ctx.l2ERC20TokenBridge + withdrawTokensTxResponse = await ctx.l2ERC20ExtendedTokensBridge .connect(ctx.l2Tester) .withdrawTo( ctx.l2Token.address, @@ -146,7 +146,7 @@ async function ctxFactory() { l1Token: testingSetup.l1Token, l2Token: testingSetup.l2Token, l1LidoTokensBridge: testingSetup.l1LidoTokensBridge, - l2ERC20TokenBridge: testingSetup.l2ERC20TokenBridge, + l2ERC20ExtendedTokensBridge: testingSetup.l2ERC20ExtendedTokensBridge, crossChainMessenger: new CrossChainMessenger({ l2ChainId: network.chainId("opt", networkName), l1ChainId: network.chainId("eth", networkName), @@ -156,7 +156,7 @@ async function ctxFactory() { LidoBridge: { Adapter: DAIBridgeAdapter, l1Bridge: testingSetup.l1LidoTokensBridge.address, - l2Bridge: testingSetup.l2ERC20TokenBridge.address, + l2Bridge: testingSetup.l2ERC20ExtendedTokensBridge.address, }, }, }), diff --git a/test/optimism/bridging.e2e.test.ts b/test/optimism/bridging.e2e.test.ts index ea346bdd..c045267e 100644 --- a/test/optimism/bridging.e2e.test.ts +++ b/test/optimism/bridging.e2e.test.ts @@ -144,7 +144,7 @@ async function ctxFactory() { LidoBridge: { Adapter: LidoBridgeAdapter, l1Bridge: testingSetup.l1LidoTokensBridge.address, - l2Bridge: testingSetup.l2ERC20TokenBridge.address, + l2Bridge: testingSetup.l2ERC20ExtendedTokensBridge.address, }, }, }), diff --git a/test/optimism/bridging.integration.test.ts b/test/optimism/bridging.integration.test.ts index 57d9e5b3..6cc2a509 100644 --- a/test/optimism/bridging.integration.test.ts +++ b/test/optimism/bridging.integration.test.ts @@ -13,13 +13,13 @@ scenario("Optimism :: Bridging integration test", ctxFactory) .step("Activate bridging on L1", async (ctx) => { const { l1LidoTokensBridge } = ctx; - const { l1ERC20TokenBridgeAdmin } = ctx.accounts; + const { l1ERC20ExtendedTokensBridgeAdmin } = ctx.accounts; const isDepositsEnabled = await l1LidoTokensBridge.isDepositsEnabled(); if (!isDepositsEnabled) { await l1LidoTokensBridge - .connect(l1ERC20TokenBridgeAdmin) + .connect(l1ERC20ExtendedTokensBridgeAdmin) .enableDeposits(); } else { console.log("L1 deposits already enabled"); @@ -30,7 +30,7 @@ scenario("Optimism :: Bridging integration test", ctxFactory) if (!isWithdrawalsEnabled) { await l1LidoTokensBridge - .connect(l1ERC20TokenBridgeAdmin) + .connect(l1ERC20ExtendedTokensBridgeAdmin) .enableWithdrawals(); } else { console.log("L1 withdrawals already enabled"); @@ -41,32 +41,32 @@ scenario("Optimism :: Bridging integration test", ctxFactory) }) .step("Activate bridging on L2", async (ctx) => { - const { l2ERC20TokenBridge } = ctx; - const { l2ERC20TokenBridgeAdmin } = ctx.accounts; + const { l2ERC20ExtendedTokensBridge } = ctx; + const { l2ERC20ExtendedTokensBridgeAdmin } = ctx.accounts; - const isDepositsEnabled = await l2ERC20TokenBridge.isDepositsEnabled(); + const isDepositsEnabled = await l2ERC20ExtendedTokensBridge.isDepositsEnabled(); if (!isDepositsEnabled) { - await l2ERC20TokenBridge - .connect(l2ERC20TokenBridgeAdmin) + await l2ERC20ExtendedTokensBridge + .connect(l2ERC20ExtendedTokensBridgeAdmin) .enableDeposits(); } else { console.log("L2 deposits already enabled"); } const isWithdrawalsEnabled = - await l2ERC20TokenBridge.isWithdrawalsEnabled(); + await l2ERC20ExtendedTokensBridge.isWithdrawalsEnabled(); if (!isWithdrawalsEnabled) { - await l2ERC20TokenBridge - .connect(l2ERC20TokenBridgeAdmin) + await l2ERC20ExtendedTokensBridge + .connect(l2ERC20ExtendedTokensBridgeAdmin) .enableWithdrawals(); } else { console.log("L2 withdrawals already enabled"); } - assert.isTrue(await l2ERC20TokenBridge.isDepositsEnabled()); - assert.isTrue(await l2ERC20TokenBridge.isWithdrawalsEnabled()); + assert.isTrue(await l2ERC20ExtendedTokensBridge.isDepositsEnabled()); + assert.isTrue(await l2ERC20ExtendedTokensBridge.isWithdrawalsEnabled()); }) .step("L1 -> L2 deposit via depositERC20() method", async (ctx) => { @@ -75,7 +75,7 @@ scenario("Optimism :: Bridging integration test", ctxFactory) l1LidoTokensBridge, l2Token, l1CrossDomainMessenger, - l2ERC20TokenBridge, + l2ERC20ExtendedTokensBridge, } = ctx; const { accountA: tokenHolderA } = ctx.accounts; const { depositAmount } = ctx.common; @@ -87,7 +87,7 @@ scenario("Optimism :: Bridging integration test", ctxFactory) const tokenHolderABalanceBefore = await l1Token.balanceOf( tokenHolderA.address ); - const l1ERC20TokenBridgeBalanceBefore = await l1Token.balanceOf( + const l1ERC20ExtendedTokensBridgeBalanceBefore = await l1Token.balanceOf( l1LidoTokensBridge.address ); @@ -110,7 +110,7 @@ scenario("Optimism :: Bridging integration test", ctxFactory) "0x", ]); - const l2DepositCalldata = l2ERC20TokenBridge.interface.encodeFunctionData( + const l2DepositCalldata = l2ERC20ExtendedTokensBridge.interface.encodeFunctionData( "finalizeDeposit", [ l1Token.address, @@ -125,7 +125,7 @@ scenario("Optimism :: Bridging integration test", ctxFactory) const messageNonce = await l1CrossDomainMessenger.messageNonce(); await assert.emits(l1CrossDomainMessenger, tx, "SentMessage", [ - l2ERC20TokenBridge.address, + l2ERC20ExtendedTokensBridge.address, l1LidoTokensBridge.address, l2DepositCalldata, messageNonce, @@ -134,7 +134,7 @@ scenario("Optimism :: Bridging integration test", ctxFactory) assert.equalBN( await l1Token.balanceOf(l1LidoTokensBridge.address), - l1ERC20TokenBridgeBalanceBefore.add(depositAmount) + l1ERC20ExtendedTokensBridgeBalanceBefore.add(depositAmount) ); assert.equalBN( @@ -149,7 +149,7 @@ scenario("Optimism :: Bridging integration test", ctxFactory) l2Token, l1LidoTokensBridge, l2CrossDomainMessenger, - l2ERC20TokenBridge, + l2ERC20ExtendedTokensBridge, } = ctx; const { depositAmount } = ctx.common; const { accountA: tokenHolderA, l1CrossDomainMessengerAliased } = @@ -165,10 +165,10 @@ scenario("Optimism :: Bridging integration test", ctxFactory) .relayMessage( 1, l1LidoTokensBridge.address, - l2ERC20TokenBridge.address, + l2ERC20ExtendedTokensBridge.address, 0, 300_000, - l2ERC20TokenBridge.interface.encodeFunctionData("finalizeDeposit", [ + l2ERC20ExtendedTokensBridge.interface.encodeFunctionData("finalizeDeposit", [ l1Token.address, l2Token.address, tokenHolderA.address, @@ -179,7 +179,7 @@ scenario("Optimism :: Bridging integration test", ctxFactory) { gasLimit: 5_000_000 } ); - await assert.emits(l2ERC20TokenBridge, tx, "DepositFinalized", [ + await assert.emits(l2ERC20ExtendedTokensBridge, tx, "DepositFinalized", [ l1Token.address, l2Token.address, tokenHolderA.address, @@ -200,18 +200,18 @@ scenario("Optimism :: Bridging integration test", ctxFactory) .step("L2 -> L1 withdrawal via withdraw()", async (ctx) => { const { accountA: tokenHolderA } = ctx.accounts; const { withdrawalAmount } = ctx.common; - const { l1Token, l2Token, l2ERC20TokenBridge } = ctx; + const { l1Token, l2Token, l2ERC20ExtendedTokensBridge } = ctx; const tokenHolderABalanceBefore = await l2Token.balanceOf( tokenHolderA.address ); const l2TotalSupplyBefore = await l2Token.totalSupply(); - const tx = await l2ERC20TokenBridge + const tx = await l2ERC20ExtendedTokensBridge .connect(tokenHolderA.l2Signer) .withdraw(l2Token.address, withdrawalAmount, 0, "0x"); - await assert.emits(l2ERC20TokenBridge, tx, "WithdrawalInitiated", [ + await assert.emits(l2ERC20ExtendedTokensBridge, tx, "WithdrawalInitiated", [ l1Token.address, l2Token.address, tokenHolderA.address, @@ -236,7 +236,7 @@ scenario("Optimism :: Bridging integration test", ctxFactory) l1LidoTokensBridge, l2CrossDomainMessenger, l2Token, - l2ERC20TokenBridge, + l2ERC20ExtendedTokensBridge, } = ctx; const { accountA: tokenHolderA, l1Stranger } = ctx.accounts; const { withdrawalAmount } = ctx.common; @@ -244,13 +244,13 @@ scenario("Optimism :: Bridging integration test", ctxFactory) const tokenHolderABalanceBefore = await l1Token.balanceOf( tokenHolderA.address ); - const l1ERC20TokenBridgeBalanceBefore = await l1Token.balanceOf( + const l1ERC20ExtendedTokensBridgeBalanceBefore = await l1Token.balanceOf( l1LidoTokensBridge.address ); await l1CrossDomainMessenger .connect(l1Stranger) - .setXDomainMessageSender(l2ERC20TokenBridge.address); + .setXDomainMessageSender(l2ERC20ExtendedTokensBridge.address); const tx = await l1CrossDomainMessenger .connect(l1Stranger) @@ -282,7 +282,7 @@ scenario("Optimism :: Bridging integration test", ctxFactory) assert.equalBN( await l1Token.balanceOf(l1LidoTokensBridge.address), - l1ERC20TokenBridgeBalanceBefore.sub(withdrawalAmount) + l1ERC20ExtendedTokensBridgeBalanceBefore.sub(withdrawalAmount) ); assert.equalBN( @@ -296,7 +296,7 @@ scenario("Optimism :: Bridging integration test", ctxFactory) l1Token, l2Token, l1LidoTokensBridge, - l2ERC20TokenBridge, + l2ERC20ExtendedTokensBridge, l1CrossDomainMessenger, } = ctx; const { accountA: tokenHolderA, accountB: tokenHolderB } = ctx.accounts; @@ -311,7 +311,7 @@ scenario("Optimism :: Bridging integration test", ctxFactory) const tokenHolderABalanceBefore = await l1Token.balanceOf( tokenHolderA.address ); - const l1ERC20TokenBridgeBalanceBefore = await l1Token.balanceOf( + const l1ERC20ExtendedTokensBridgeBalanceBefore = await l1Token.balanceOf( l1LidoTokensBridge.address ); @@ -335,7 +335,7 @@ scenario("Optimism :: Bridging integration test", ctxFactory) "0x", ]); - const l2DepositCalldata = l2ERC20TokenBridge.interface.encodeFunctionData( + const l2DepositCalldata = l2ERC20ExtendedTokensBridge.interface.encodeFunctionData( "finalizeDeposit", [ l1Token.address, @@ -350,7 +350,7 @@ scenario("Optimism :: Bridging integration test", ctxFactory) const messageNonce = await l1CrossDomainMessenger.messageNonce(); await assert.emits(l1CrossDomainMessenger, tx, "SentMessage", [ - l2ERC20TokenBridge.address, + l2ERC20ExtendedTokensBridge.address, l1LidoTokensBridge.address, l2DepositCalldata, messageNonce, @@ -359,7 +359,7 @@ scenario("Optimism :: Bridging integration test", ctxFactory) assert.equalBN( await l1Token.balanceOf(l1LidoTokensBridge.address), - l1ERC20TokenBridgeBalanceBefore.add(depositAmount) + l1ERC20ExtendedTokensBridgeBalanceBefore.add(depositAmount) ); assert.equalBN( @@ -374,7 +374,7 @@ scenario("Optimism :: Bridging integration test", ctxFactory) l1LidoTokensBridge, l2Token, l2CrossDomainMessenger, - l2ERC20TokenBridge, + l2ERC20ExtendedTokensBridge, } = ctx; const { accountA: tokenHolderA, @@ -393,10 +393,10 @@ scenario("Optimism :: Bridging integration test", ctxFactory) .relayMessage( 1, l1LidoTokensBridge.address, - l2ERC20TokenBridge.address, + l2ERC20ExtendedTokensBridge.address, 0, 300_000, - l2ERC20TokenBridge.interface.encodeFunctionData("finalizeDeposit", [ + l2ERC20ExtendedTokensBridge.interface.encodeFunctionData("finalizeDeposit", [ l1Token.address, l2Token.address, tokenHolderA.address, @@ -407,7 +407,7 @@ scenario("Optimism :: Bridging integration test", ctxFactory) { gasLimit: 5_000_000 } ); - await assert.emits(l2ERC20TokenBridge, tx, "DepositFinalized", [ + await assert.emits(l2ERC20ExtendedTokensBridge, tx, "DepositFinalized", [ l1Token.address, l2Token.address, tokenHolderA.address, @@ -427,7 +427,7 @@ scenario("Optimism :: Bridging integration test", ctxFactory) }) .step("L2 -> L1 withdrawal via withdrawTo()", async (ctx) => { - const { l1Token, l2Token, l2ERC20TokenBridge } = ctx; + const { l1Token, l2Token, l2ERC20ExtendedTokensBridge } = ctx; const { accountA: tokenHolderA, accountB: tokenHolderB } = ctx.accounts; const { withdrawalAmount } = ctx.common; @@ -436,7 +436,7 @@ scenario("Optimism :: Bridging integration test", ctxFactory) ); const l2TotalSupplyBefore = await l2Token.totalSupply(); - const tx = await l2ERC20TokenBridge + const tx = await l2ERC20ExtendedTokensBridge .connect(tokenHolderB.l2Signer) .withdrawTo( l2Token.address, @@ -446,7 +446,7 @@ scenario("Optimism :: Bridging integration test", ctxFactory) "0x" ); - await assert.emits(l2ERC20TokenBridge, tx, "WithdrawalInitiated", [ + await assert.emits(l2ERC20ExtendedTokensBridge, tx, "WithdrawalInitiated", [ l1Token.address, l2Token.address, tokenHolderB.address, @@ -473,7 +473,7 @@ scenario("Optimism :: Bridging integration test", ctxFactory) l1LidoTokensBridge, l2CrossDomainMessenger, l2Token, - l2ERC20TokenBridge, + l2ERC20ExtendedTokensBridge, } = ctx; const { accountA: tokenHolderA, @@ -485,13 +485,13 @@ scenario("Optimism :: Bridging integration test", ctxFactory) const tokenHolderABalanceBefore = await l1Token.balanceOf( tokenHolderA.address ); - const l1ERC20TokenBridgeBalanceBefore = await l1Token.balanceOf( + const l1ERC20ExtendedTokensBridgeBalanceBefore = await l1Token.balanceOf( l1LidoTokensBridge.address ); await l1CrossDomainMessenger .connect(l1Stranger) - .setXDomainMessageSender(l2ERC20TokenBridge.address); + .setXDomainMessageSender(l2ERC20ExtendedTokensBridge.address); const tx = await l1CrossDomainMessenger .connect(l1Stranger) @@ -523,7 +523,7 @@ scenario("Optimism :: Bridging integration test", ctxFactory) assert.equalBN( await l1Token.balanceOf(l1LidoTokensBridge.address), - l1ERC20TokenBridgeBalanceBefore.sub(withdrawalAmount) + l1ERC20ExtendedTokensBridgeBalanceBefore.sub(withdrawalAmount) ); assert.equalBN( @@ -540,8 +540,8 @@ async function ctxFactory() { const { l1Provider, l2Provider, - l1ERC20TokenBridgeAdmin, - l2ERC20TokenBridgeAdmin, + l1ERC20ExtendedTokensBridgeAdmin, + l2ERC20ExtendedTokensBridgeAdmin, ...contracts } = await optimism.testing(networkName).getIntegrationTestSetup(); @@ -563,13 +563,13 @@ async function ctxFactory() { ); await testing.setBalance( - await l1ERC20TokenBridgeAdmin.getAddress(), + await l1ERC20ExtendedTokensBridgeAdmin.getAddress(), wei.toBigNumber(wei`1 ether`), l1Provider ); await testing.setBalance( - await l2ERC20TokenBridgeAdmin.getAddress(), + await l2ERC20ExtendedTokensBridgeAdmin.getAddress(), wei.toBigNumber(wei`1 ether`), l2Provider ); @@ -597,8 +597,8 @@ async function ctxFactory() { accountA, accountB, l1Stranger: testing.accounts.stranger(l1Provider), - l1ERC20TokenBridgeAdmin, - l2ERC20TokenBridgeAdmin, + l1ERC20ExtendedTokensBridgeAdmin, + l2ERC20ExtendedTokensBridgeAdmin, l1CrossDomainMessengerAliased, }, common: { diff --git a/test/optimism/deployment.acceptance.test.ts b/test/optimism/deployment.acceptance.test.ts index b5a7a766..ec30d93f 100644 --- a/test/optimism/deployment.acceptance.test.ts +++ b/test/optimism/deployment.acceptance.test.ts @@ -44,7 +44,7 @@ scenario("Optimism Bridge :: deployment acceptance test", ctxFactory) .step("L1 bridge :: L2 token bridge", async (ctx) => { assert.equal( await ctx.l1LidoTokensBridge.l2TokenBridge(), - ctx.l2ERC20TokenBridge.address + ctx.l2ERC20ExtendedTokensBridge.address ); }) .step("L1 Bridge :: is deposits enabled", async (ctx) => { @@ -122,55 +122,55 @@ scenario("Optimism Bridge :: deployment acceptance test", ctxFactory) .step("L2 Bridge :: proxy admin", async (ctx) => { assert.equal( - await ctx.l2ERC20TokenBridgeProxy.proxy__getAdmin(), + await ctx.l2ERC20ExtendedTokensBridgeProxy.proxy__getAdmin(), ctx.deployment.l2.proxyAdmin ); }) .step("L2 Bridge :: bridge admin", async (ctx) => { const currentAdmins = await getRoleHolders( - ctx.l2ERC20TokenBridge, + ctx.l2ERC20ExtendedTokensBridge, BridgingManagerRole.DEFAULT_ADMIN_ROLE.hash ); assert.equal(currentAdmins.size, 1); assert.isTrue(currentAdmins.has(ctx.deployment.l2.bridgeAdmin)); await assert.isTrue( - await ctx.l2ERC20TokenBridge.hasRole( + await ctx.l2ERC20ExtendedTokensBridge.hasRole( BridgingManagerRole.DEFAULT_ADMIN_ROLE.hash, ctx.deployment.l2.bridgeAdmin ) ); }) .step("L2 bridge :: L1 token", async (ctx) => { - assert.equal(await ctx.l2ERC20TokenBridge.L1_TOKEN_NON_REBASABLE(), ctx.deployment.token); + assert.equal(await ctx.l2ERC20ExtendedTokensBridge.L1_TOKEN_NON_REBASABLE(), ctx.deployment.token); }) .step("L2 bridge :: L2 token", async (ctx) => { assert.equal( - await ctx.l2ERC20TokenBridge.L2_TOKEN_NON_REBASABLE(), + await ctx.l2ERC20ExtendedTokensBridge.L2_TOKEN_NON_REBASABLE(), ctx.erc20Bridged.address ); }) .step("L2 bridge :: L1 token bridge", async (ctx) => { assert.equal( - await ctx.l2ERC20TokenBridge.l1TokenBridge(), + await ctx.l2ERC20ExtendedTokensBridge.l1TokenBridge(), ctx.l1LidoTokensBridge.address ); }) .step("L2 Bridge :: is deposits enabled", async (ctx) => { assert.equal( - await ctx.l2ERC20TokenBridge.isDepositsEnabled(), + await ctx.l2ERC20ExtendedTokensBridge.isDepositsEnabled(), ctx.deployment.l2.depositsEnabled ); }) .step("L2 Bridge :: is withdrawals enabled", async (ctx) => { assert.equal( - await ctx.l2ERC20TokenBridge.isWithdrawalsEnabled(), + await ctx.l2ERC20ExtendedTokensBridge.isWithdrawalsEnabled(), ctx.deployment.l2.withdrawalsEnabled ); }) .step("L2 Bridge :: deposits enablers", async (ctx) => { const actualDepositsEnablers = await getRoleHolders( - ctx.l2ERC20TokenBridge, + ctx.l2ERC20ExtendedTokensBridge, BridgingManagerRole.DEPOSITS_ENABLER_ROLE.hash ); const expectedDepositsEnablers = ctx.deployment.l2.depositsEnablers || []; @@ -182,7 +182,7 @@ scenario("Optimism Bridge :: deployment acceptance test", ctxFactory) }) .step("L2 Bridge :: deposits disablers", async (ctx) => { const actualDepositsDisablers = await getRoleHolders( - ctx.l2ERC20TokenBridge, + ctx.l2ERC20ExtendedTokensBridge, BridgingManagerRole.DEPOSITS_DISABLER_ROLE.hash ); const expectedDepositsDisablers = ctx.deployment.l2.depositsDisablers || []; @@ -197,7 +197,7 @@ scenario("Optimism Bridge :: deployment acceptance test", ctxFactory) }) .step("L2 Bridge :: withdrawals enablers", async (ctx) => { const actualWithdrawalsEnablers = await getRoleHolders( - ctx.l2ERC20TokenBridge, + ctx.l2ERC20ExtendedTokensBridge, BridgingManagerRole.WITHDRAWALS_ENABLER_ROLE.hash ); const expectedWithdrawalsEnablers = @@ -213,7 +213,7 @@ scenario("Optimism Bridge :: deployment acceptance test", ctxFactory) }) .step("L2 Bridge :: withdrawals disablers", async (ctx) => { const actualWithdrawalsDisablers = await getRoleHolders( - ctx.l2ERC20TokenBridge, + ctx.l2ERC20ExtendedTokensBridge, BridgingManagerRole.WITHDRAWALS_DISABLER_ROLE.hash ); const expectedWithdrawalsDisablers = @@ -251,7 +251,7 @@ scenario("Optimism Bridge :: deployment acceptance test", ctxFactory) .step("L2 token :: bridge", async (ctx) => { assert.equalBN( await ctx.erc20Bridged.bridge(), - ctx.l2ERC20TokenBridge.address + ctx.l2ERC20ExtendedTokensBridge.address ); }) @@ -287,9 +287,9 @@ async function ctxFactory() { testingSetup.l1LidoTokensBridge.address, testingSetup.l1Provider ), - l2ERC20TokenBridge: testingSetup.l2ERC20TokenBridge, - l2ERC20TokenBridgeProxy: OssifiableProxy__factory.connect( - testingSetup.l2ERC20TokenBridge.address, + l2ERC20ExtendedTokensBridge: testingSetup.l2ERC20ExtendedTokensBridge, + l2ERC20ExtendedTokensBridgeProxy: OssifiableProxy__factory.connect( + testingSetup.l2ERC20ExtendedTokensBridge.address, testingSetup.l2Provider ), erc20Bridged: testingSetup.l2Token, diff --git a/test/optimism/deposit-gas-estimation.test.ts b/test/optimism/deposit-gas-estimation.test.ts index ea3ae063..e6d4857e 100644 --- a/test/optimism/deposit-gas-estimation.test.ts +++ b/test/optimism/deposit-gas-estimation.test.ts @@ -13,13 +13,13 @@ scenario("Optimism :: Bridging integration test", ctxFactory) .step("Activate bridging on L1", async (ctx) => { const { l1LidoTokensBridge } = ctx; - const { l1ERC20TokenBridgeAdmin } = ctx.accounts; + const { l1ERC20ExtendedTokensBridgeAdmin } = ctx.accounts; const isDepositsEnabled = await l1LidoTokensBridge.isDepositsEnabled(); if (!isDepositsEnabled) { await l1LidoTokensBridge - .connect(l1ERC20TokenBridgeAdmin) + .connect(l1ERC20ExtendedTokensBridgeAdmin) .enableDeposits(); } else { console.log("L1 deposits already enabled"); @@ -30,7 +30,7 @@ scenario("Optimism :: Bridging integration test", ctxFactory) if (!isWithdrawalsEnabled) { await l1LidoTokensBridge - .connect(l1ERC20TokenBridgeAdmin) + .connect(l1ERC20ExtendedTokensBridgeAdmin) .enableWithdrawals(); } else { console.log("L1 withdrawals already enabled"); @@ -41,32 +41,32 @@ scenario("Optimism :: Bridging integration test", ctxFactory) }) .step("Activate bridging on L2", async (ctx) => { - const { l2ERC20TokenBridge } = ctx; - const { l2ERC20TokenBridgeAdmin } = ctx.accounts; + const { l2ERC20ExtendedTokensBridge } = ctx; + const { l2ERC20ExtendedTokensBridgeAdmin } = ctx.accounts; - const isDepositsEnabled = await l2ERC20TokenBridge.isDepositsEnabled(); + const isDepositsEnabled = await l2ERC20ExtendedTokensBridge.isDepositsEnabled(); if (!isDepositsEnabled) { - await l2ERC20TokenBridge - .connect(l2ERC20TokenBridgeAdmin) + await l2ERC20ExtendedTokensBridge + .connect(l2ERC20ExtendedTokensBridgeAdmin) .enableDeposits(); } else { console.log("L2 deposits already enabled"); } const isWithdrawalsEnabled = - await l2ERC20TokenBridge.isWithdrawalsEnabled(); + await l2ERC20ExtendedTokensBridge.isWithdrawalsEnabled(); if (!isWithdrawalsEnabled) { - await l2ERC20TokenBridge - .connect(l2ERC20TokenBridgeAdmin) + await l2ERC20ExtendedTokensBridge + .connect(l2ERC20ExtendedTokensBridgeAdmin) .enableWithdrawals(); } else { console.log("L2 withdrawals already enabled"); } - assert.isTrue(await l2ERC20TokenBridge.isDepositsEnabled()); - assert.isTrue(await l2ERC20TokenBridge.isWithdrawalsEnabled()); + assert.isTrue(await l2ERC20ExtendedTokensBridge.isDepositsEnabled()); + assert.isTrue(await l2ERC20ExtendedTokensBridge.isWithdrawalsEnabled()); }) .step("L1 -> L2 deposit zero tokens via depositERC20() method", async (ctx) => { @@ -83,13 +83,18 @@ scenario("Optimism :: Bridging integration test", ctxFactory) await l1TokenRebasable .connect(tokenHolderA.l1Signer) - .approve(l1LidoTokensBridge.address, 0); + .approve(l1LidoTokensBridge.address, 10); - const tokenHolderABalanceBefore = await l1TokenRebasable.balanceOf( + await l1Token + .connect(tokenHolderA.l1Signer) + .approve(l1LidoTokensBridge.address, 10); + + const tokenHolderABalanceBefore = await l1Token.balanceOf( tokenHolderA.address ); + console.log("tokenHolderABalanceBefore=",tokenHolderABalanceBefore); - const l1ERC20TokenBridgeBalanceBefore = await l1TokenRebasable.balanceOf( + const l1ERC20ExtendedTokensBridgeBalanceBefore = await l1TokenRebasable.balanceOf( l1LidoTokensBridge.address ); @@ -98,7 +103,7 @@ scenario("Optimism :: Bridging integration test", ctxFactory) .depositERC20( l1Token.address, l2Token.address, - 0, + 10, 200_000, "0x" ); @@ -111,7 +116,7 @@ scenario("Optimism :: Bridging integration test", ctxFactory) .depositERC20( l1TokenRebasable.address, l2TokenRebasable.address, - 0, + 10, 200_000, "0x" ); @@ -134,8 +139,8 @@ async function ctxFactory() { const { l1Provider, l2Provider, - l1ERC20TokenBridgeAdmin, - l2ERC20TokenBridgeAdmin, + l1ERC20ExtendedTokensBridgeAdmin, + l2ERC20ExtendedTokensBridgeAdmin, ...contracts } = await optimism.testing(networkName).getIntegrationTestSetup(); @@ -155,17 +160,21 @@ async function ctxFactory() { ); await testing.setBalance( - await l1ERC20TokenBridgeAdmin.getAddress(), + await l1ERC20ExtendedTokensBridgeAdmin.getAddress(), wei.toBigNumber(wei`1 ether`), l1Provider ); await testing.setBalance( - await l2ERC20TokenBridgeAdmin.getAddress(), + await l2ERC20ExtendedTokensBridgeAdmin.getAddress(), wei.toBigNumber(wei`1 ether`), l2Provider ); + await contracts.l1Token + .connect(contracts.l1TokensHolder) + .transfer(accountA.l1Signer.address, depositAmount); + await contracts.l1TokenRebasable .connect(contracts.l1TokensHolder) .transfer(accountA.l1Signer.address, wei.toBigNumber(depositAmount).mul(2)); @@ -192,8 +201,8 @@ async function ctxFactory() { accountA, accountB, l1Stranger: testing.accounts.stranger(l1Provider), - l1ERC20TokenBridgeAdmin, - l2ERC20TokenBridgeAdmin, + l1ERC20ExtendedTokensBridgeAdmin, + l2ERC20ExtendedTokensBridgeAdmin, l1CrossDomainMessengerAliased, }, common: { diff --git a/test/optimism/managing-deposits.e2e.test.ts b/test/optimism/managing-deposits.e2e.test.ts index 1566d551..36712d50 100644 --- a/test/optimism/managing-deposits.e2e.test.ts +++ b/test/optimism/managing-deposits.e2e.test.ts @@ -2,7 +2,7 @@ import { assert } from "chai"; import { TransactionResponse } from "@ethersproject/providers"; import { - L2ERC20TokenBridge__factory, + L2ERC20ExtendedTokensBridge__factory, GovBridgeExecutor__factory, } from "../../typechain"; import { @@ -33,27 +33,27 @@ const scenarioTest = scenario( assert.gte(await l1LDOHolder.getBalance(), gasAmount); }) - .step("Checking deposits status", async ({ l2ERC20TokenBridge }) => { - l2DepositsInitialState = await l2ERC20TokenBridge.isDepositsEnabled(); + .step("Checking deposits status", async ({ l2ERC20ExtendedTokensBridge }) => { + l2DepositsInitialState = await l2ERC20ExtendedTokensBridge.isDepositsEnabled(); }) .step(`Starting DAO vote`, async (ctx) => { const grantRoleCalldata = - ctx.l2ERC20TokenBridge.interface.encodeFunctionData("grantRole", [ + ctx.l2ERC20ExtendedTokensBridge.interface.encodeFunctionData("grantRole", [ l2DepositsInitialState ? DEPOSIT_DISABLER_ROLE : DEPOSIT_ENABLER_ROLE, ctx.govBridgeExecutor.address, ]); const grantRoleData = "0x" + grantRoleCalldata.substring(10); const actionCalldata = l2DepositsInitialState - ? ctx.l2ERC20TokenBridge.interface.encodeFunctionData("disableDeposits") - : ctx.l2ERC20TokenBridge.interface.encodeFunctionData("enableDeposits"); + ? ctx.l2ERC20ExtendedTokensBridge.interface.encodeFunctionData("disableDeposits") + : ctx.l2ERC20ExtendedTokensBridge.interface.encodeFunctionData("enableDeposits"); const actionData = "0x" + actionCalldata.substring(10); const executorCalldata = await ctx.govBridgeExecutor.interface.encodeFunctionData("queue", [ - [ctx.l2ERC20TokenBridge.address, ctx.l2ERC20TokenBridge.address], + [ctx.l2ERC20ExtendedTokensBridge.address, ctx.l2ERC20ExtendedTokensBridge.address], [0, 0], [ "grantRole(bytes32,address)", @@ -124,9 +124,9 @@ const scenarioTest = scenario( await tx.wait(); }) - .step("Checking deposits state", async ({ l2ERC20TokenBridge }) => { + .step("Checking deposits state", async ({ l2ERC20ExtendedTokensBridge }) => { assert.equal( - await l2ERC20TokenBridge.isDepositsEnabled(), + await l2ERC20ExtendedTokensBridge.isDepositsEnabled(), !l2DepositsInitialState ); }); @@ -158,8 +158,8 @@ async function ctxFactory() { l1Tester, l2Tester, l1LDOHolder, - l2ERC20TokenBridge: L2ERC20TokenBridge__factory.connect( - E2E_TEST_CONTRACTS.l2.l2ERC20TokenBridge, + l2ERC20ExtendedTokensBridge: L2ERC20ExtendedTokensBridge__factory.connect( + E2E_TEST_CONTRACTS.l2.l2ERC20ExtendedTokensBridge, l2Tester ), govBridgeExecutor: GovBridgeExecutor__factory.connect( diff --git a/test/optimism/managing-executor.e2e.test.ts b/test/optimism/managing-executor.e2e.test.ts index 340a050e..f48112e9 100644 --- a/test/optimism/managing-executor.e2e.test.ts +++ b/test/optimism/managing-executor.e2e.test.ts @@ -2,7 +2,7 @@ import { assert } from "chai"; import { TransactionResponse } from "@ethersproject/providers"; import { - L2ERC20TokenBridge__factory, + L2ERC20ExtendedTokensBridge__factory, GovBridgeExecutor__factory, } from "../../typechain"; import { @@ -134,8 +134,8 @@ async function ctxFactory() { gasAmount: wei`0.1 ether`, l2Tester, l1LDOHolder, - l2ERC20TokenBridge: L2ERC20TokenBridge__factory.connect( - E2E_TEST_CONTRACTS.l2.l2ERC20TokenBridge, + l2ERC20ExtendedTokensBridge: L2ERC20ExtendedTokensBridge__factory.connect( + E2E_TEST_CONTRACTS.l2.l2ERC20ExtendedTokensBridge, l2Tester ), govBridgeExecutor: GovBridgeExecutor__factory.connect( diff --git a/test/optimism/managing-proxy.e2e.test.ts b/test/optimism/managing-proxy.e2e.test.ts index 632de88c..20ff14af 100644 --- a/test/optimism/managing-proxy.e2e.test.ts +++ b/test/optimism/managing-proxy.e2e.test.ts @@ -5,7 +5,7 @@ import { ERC20Bridged__factory, GovBridgeExecutor__factory, OssifiableProxy__factory, - L2ERC20TokenBridge__factory, + L2ERC20ExtendedTokensBridge__factory, } from "../../typechain"; import { E2E_TEST_CONTRACTS_OPTIMISM as E2E_TEST_CONTRACTS } from "../../utils/testing/e2e"; import env from "../../utils/env"; @@ -32,7 +32,7 @@ scenario( .step("Proxy upgrade: send crosschain message", async (ctx) => { const implBefore = await await ctx.proxyToOssify.proxy__getImplementation(); - assert.equal(implBefore, ctx.l2ERC20TokenBridge.address); + assert.equal(implBefore, ctx.l2ERC20ExtendedTokensBridge.address); const executorCalldata = await ctx.govBridgeExecutor.interface.encodeFunctionData("queue", [ [ctx.proxyToOssify.address], @@ -204,8 +204,8 @@ async function ctxFactory() { E2E_TEST_CONTRACTS.l2.l2Token, l2Tester ), - l2ERC20TokenBridge: L2ERC20TokenBridge__factory.connect( - E2E_TEST_CONTRACTS.l2.l2ERC20TokenBridge, + l2ERC20ExtendedTokensBridge: L2ERC20ExtendedTokensBridge__factory.connect( + E2E_TEST_CONTRACTS.l2.l2ERC20ExtendedTokensBridge, l2Tester ), govBridgeExecutor: GovBridgeExecutor__factory.connect( @@ -213,7 +213,7 @@ async function ctxFactory() { l2Tester ), proxyToOssify: await new OssifiableProxy__factory(l2Tester).deploy( - E2E_TEST_CONTRACTS.l2.l2ERC20TokenBridge, + E2E_TEST_CONTRACTS.l2.l2ERC20ExtendedTokensBridge, E2E_TEST_CONTRACTS.l2.govBridgeExecutor, "0x" ), diff --git a/test/optimism/pushingTokenRate.integration.test.ts b/test/optimism/pushingTokenRate.integration.test.ts index 36f93e83..f5d9606c 100644 --- a/test/optimism/pushingTokenRate.integration.test.ts +++ b/test/optimism/pushingTokenRate.integration.test.ts @@ -143,12 +143,15 @@ async function ctxFactory() { networkName ).oracleDeployScript( l1Token.address, + 1000, + 86400, { deployer: l1Deployer, admins: { proxy: l1Deployer.address, bridge: l1Deployer.address }, + contractsShift: 0 }, { deployer: l2Deployer, @@ -156,6 +159,7 @@ async function ctxFactory() { proxy: govBridgeExecutor.address, bridge: govBridgeExecutor.address, }, + contractsShift: 0 } ); diff --git a/utils/arbitrum/testing.ts b/utils/arbitrum/testing.ts index e34dce4c..081628a5 100644 --- a/utils/arbitrum/testing.ts +++ b/utils/arbitrum/testing.ts @@ -206,15 +206,15 @@ async function deployTestGateway( await ethDeployScript.run(); await arbDeployScript.run(); - const l1ERC20TokenBridgeProxyDeployStepIndex = 1; + const l1ERC20ExtendedTokensBridgeProxyDeployStepIndex = 1; const l1BridgingManagement = new BridgingManagement( - ethDeployScript.getContractAddress(l1ERC20TokenBridgeProxyDeployStepIndex), + ethDeployScript.getContractAddress(l1ERC20ExtendedTokensBridgeProxyDeployStepIndex), ethDeployer ); - const l2ERC20TokenBridgeProxyDeployStepIndex = 3; + const l2ERC20ExtendedTokensBridgeProxyDeployStepIndex = 3; const l2BridgingManagement = new BridgingManagement( - arbDeployScript.getContractAddress(l2ERC20TokenBridgeProxyDeployStepIndex), + arbDeployScript.getContractAddress(l2ERC20ExtendedTokensBridgeProxyDeployStepIndex), arbDeployer ); diff --git a/utils/optimism/deploymentAllFromScratch.ts b/utils/optimism/deploymentAllFromScratch.ts index 6b95f1b1..8d59c629 100644 --- a/utils/optimism/deploymentAllFromScratch.ts +++ b/utils/optimism/deploymentAllFromScratch.ts @@ -9,7 +9,7 @@ import { ERC20Rebasable__factory, IERC20Metadata__factory, L1LidoTokensBridge__factory, - L2ERC20TokenBridge__factory, + L2ERC20ExtendedTokensBridge__factory, OssifiableProxy__factory, TokenRateOracle__factory, TokenRateNotifier__factory, @@ -163,6 +163,7 @@ export default function deploymentAll( .addStep({ factory: TokenRateNotifier__factory, args: [ + l1Params.deployer.address, options?.overrides, ], afterDeploy: (c) => @@ -265,7 +266,7 @@ export default function deploymentAll( assert.equal(c.address, expectedL2TokenRebasableProxyAddress), }) .addStep({ - factory: L2ERC20TokenBridge__factory, + factory: L2ERC20ExtendedTokensBridge__factory, args: [ optAddresses.L2CrossDomainMessenger, expectedL1TokenBridgeProxyAddress, @@ -283,7 +284,7 @@ export default function deploymentAll( args: [ expectedL2TokenBridgeImplAddress, l2Params.admins.proxy, - L2ERC20TokenBridge__factory.createInterface().encodeFunctionData( + L2ERC20ExtendedTokensBridge__factory.createInterface().encodeFunctionData( "initialize", [l2Params.admins.bridge] ), diff --git a/utils/optimism/deploymentBridgesAndRebasableToken.ts b/utils/optimism/deploymentBridgesAndRebasableToken.ts index 1035b7a5..1241ab24 100644 --- a/utils/optimism/deploymentBridgesAndRebasableToken.ts +++ b/utils/optimism/deploymentBridgesAndRebasableToken.ts @@ -9,7 +9,7 @@ import { ERC20Rebasable__factory, IERC20Metadata__factory, L1LidoTokensBridge__factory, - L2ERC20TokenBridge__factory, + L2ERC20ExtendedTokensBridge__factory, OssifiableProxy__factory, } from "../../typechain"; @@ -222,7 +222,7 @@ export default function deployment( assert.equal(c.address, expectedL2TokenRebasableProxyAddress), }) .addStep({ - factory: L2ERC20TokenBridge__factory, + factory: L2ERC20ExtendedTokensBridge__factory, args: [ optAddresses.L2CrossDomainMessenger, expectedL1TokenBridgeProxyAddress, @@ -240,7 +240,7 @@ export default function deployment( args: [ expectedL2TokenBridgeImplAddress, l2Params.admins.proxy, - L2ERC20TokenBridge__factory.createInterface().encodeFunctionData( + L2ERC20ExtendedTokensBridge__factory.createInterface().encodeFunctionData( "initialize", [l2Params.admins.bridge] ), diff --git a/utils/optimism/deploymentNewImplementations.ts b/utils/optimism/deploymentNewImplementations.ts index 706dbf41..60398029 100644 --- a/utils/optimism/deploymentNewImplementations.ts +++ b/utils/optimism/deploymentNewImplementations.ts @@ -9,7 +9,7 @@ import { ERC20Rebasable__factory, IERC20Metadata__factory, L1LidoTokensBridge__factory, - L2ERC20TokenBridge__factory, + L2ERC20ExtendedTokensBridge__factory, OssifiableProxy__factory, TokenRateOracle__factory } from "../../typechain"; @@ -197,7 +197,7 @@ export default function deploymentNewImplementations( assert.equal(c.address, expectedL2TokenRebasableProxyAddress), }) .addStep({ - factory: L2ERC20TokenBridge__factory, + factory: L2ERC20ExtendedTokensBridge__factory, args: [ optAddresses.L2CrossDomainMessenger, l1Params.tokenBridgeProxyAddress, diff --git a/utils/optimism/deploymentOracle.ts b/utils/optimism/deploymentOracle.ts index 002a9230..2d43f6b9 100644 --- a/utils/optimism/deploymentOracle.ts +++ b/utils/optimism/deploymentOracle.ts @@ -78,6 +78,7 @@ export default function deploymentOracle( .addStep({ factory: TokenRateNotifier__factory, args: [ + l1Params.deployer.address, options?.overrides, ], afterDeploy: (c) => diff --git a/utils/optimism/testing.ts b/utils/optimism/testing.ts index e7848efe..97de55c4 100644 --- a/utils/optimism/testing.ts +++ b/utils/optimism/testing.ts @@ -6,13 +6,13 @@ import { ERC20Bridged, IERC20__factory, L1LidoTokensBridge, - L2ERC20TokenBridge, + L2ERC20ExtendedTokensBridge, ERC20Bridged__factory, ERC20BridgedStub__factory, ERC20WrapperStub__factory, TokenRateOracle__factory, L1LidoTokensBridge__factory, - L2ERC20TokenBridge__factory, + L2ERC20ExtendedTokensBridge__factory, CrossDomainMessengerStub__factory, ERC20Rebasable__factory, } from "../../typechain"; @@ -58,11 +58,11 @@ export default function testing(networkName: NetworkName) { ? await loadDeployedBridges(ethProvider, optProvider) : await deployTestBridge(networkName, ethProvider, optProvider); - const [l1ERC20TokenBridgeAdminAddress] = + const [l1ERC20ExtendedTokensAdminAddress] = await BridgingManagement.getAdmins(bridgeContracts.l1LidoTokensBridge); - const [l2ERC20TokenBridgeAdminAddress] = - await BridgingManagement.getAdmins(bridgeContracts.l2ERC20TokenBridge); + const [l2ERC20ExtendedTokensBridgeAdminAddress] = + await BridgingManagement.getAdmins(bridgeContracts.l2ERC20ExtendedTokensBridge); const l1TokensHolder = hasDeployedContracts ? await testingUtils.impersonate( @@ -82,13 +82,13 @@ export default function testing(networkName: NetworkName) { // if the L1 bridge admin is a contract, remove it's code to // make it behave as EOA await ethProvider.send("hardhat_setCode", [ - l1ERC20TokenBridgeAdminAddress, + l1ERC20ExtendedTokensAdminAddress, "0x", ]); // same for the L2 bridge admin await optProvider.send("hardhat_setCode", [ - l2ERC20TokenBridgeAdminAddress, + l2ERC20ExtendedTokensBridgeAdminAddress, "0x", ]); @@ -101,12 +101,12 @@ export default function testing(networkName: NetworkName) { ...bridgeContracts, l1CrossDomainMessenger: optContracts.L1CrossDomainMessengerStub, l2CrossDomainMessenger: optContracts.L2CrossDomainMessenger, - l1ERC20TokenBridgeAdmin: await testingUtils.impersonate( - l1ERC20TokenBridgeAdminAddress, + l1ERC20ExtendedTokensBridgeAdmin: await testingUtils.impersonate( + l1ERC20ExtendedTokensAdminAddress, ethProvider ), - l2ERC20TokenBridgeAdmin: await testingUtils.impersonate( - l2ERC20TokenBridgeAdminAddress, + l2ERC20ExtendedTokensBridgeAdmin: await testingUtils.impersonate( + l2ERC20ExtendedTokensBridgeAdminAddress, optProvider ) }; @@ -170,7 +170,7 @@ async function loadDeployedBridges( l2Token: testingUtils.env.OPT_L2_TOKEN(), l2TokenRebasable: testingUtils.env.OPT_L2_REBASABLE_TOKEN(), l1LidoTokensBridge: testingUtils.env.OPT_L1_ERC20_TOKEN_BRIDGE(), - l2ERC20TokenBridge: testingUtils.env.OPT_L2_ERC20_TOKEN_BRIDGE(), + l2ERC20ExtendedTokensBridge: testingUtils.env.OPT_L2_ERC20_TOKEN_BRIDGE(), }, l1SignerOrProvider, l2SignerOrProvider @@ -248,7 +248,7 @@ async function deployTestBridge( l2Token: optDeployScript.tokenProxyAddress, l2TokenRebasable: optDeployScript.tokenRebasableProxyAddress, l1LidoTokensBridge: ethDeployScript.bridgeProxyAddress, - l2ERC20TokenBridge: optDeployScript.tokenBridgeProxyAddress + l2ERC20ExtendedTokensBridge: optDeployScript.tokenBridgeProxyAddress }, ethProvider, optProvider @@ -262,7 +262,7 @@ function connectBridgeContracts( l2Token: string; l2TokenRebasable: string; l1LidoTokensBridge: string; - l2ERC20TokenBridge: string; + l2ERC20ExtendedTokensBridge: string; }, ethSignerOrProvider: SignerOrProvider, optSignerOrProvider: SignerOrProvider @@ -272,8 +272,8 @@ function connectBridgeContracts( addresses.l1LidoTokensBridge, ethSignerOrProvider ); - const l2ERC20TokenBridge = L2ERC20TokenBridge__factory.connect( - addresses.l2ERC20TokenBridge, + const l2ERC20ExtendedTokensBridge = L2ERC20ExtendedTokensBridge__factory.connect( + addresses.l2ERC20ExtendedTokensBridge, optSignerOrProvider ); const l2Token = ERC20Bridged__factory.connect( @@ -293,7 +293,7 @@ function connectBridgeContracts( l2Token, l2TokenRebasable, l1LidoTokensBridge, - l2ERC20TokenBridge + l2ERC20ExtendedTokensBridge }; } @@ -303,7 +303,7 @@ async function printLoadedTestConfig( l1Token: IERC20; l2Token: ERC20Bridged; l1LidoTokensBridge: L1LidoTokensBridge; - l2ERC20TokenBridge: L2ERC20TokenBridge; + l2ERC20ExtendedTokensBridge: L2ERC20ExtendedTokensBridge; }, l1TokensHolder?: Signer ) { @@ -326,7 +326,7 @@ async function printLoadedTestConfig( ` · L1 ERC20 Token Bridge: ${bridgeContracts.l1LidoTokensBridge.address}` ); console.log( - ` · L2 ERC20 Token Bridge: ${bridgeContracts.l2ERC20TokenBridge.address}` + ` · L2 ERC20 Token Bridge: ${bridgeContracts.l2ERC20ExtendedTokensBridge.address}` ); console.log(); } diff --git a/utils/testing/e2e.ts b/utils/testing/e2e.ts index b089a24a..203633e5 100644 --- a/utils/testing/e2e.ts +++ b/utils/testing/e2e.ts @@ -8,7 +8,7 @@ export const E2E_TEST_CONTRACTS_OPTIMISM = { l1: { l1Token: "0xB82381A3fBD3FaFA77B3a7bE693342618240067b", l1LDOToken: "0xd06dF83b8ad6D89C86a187fba4Eae918d497BdCB", - l1ERC20TokenBridge: "0x4Abf633d9c0F4aEebB4C2E3213c7aa1b8505D332", + l1ERC20ExtendedTokensBridge: "0x4Abf633d9c0F4aEebB4C2E3213c7aa1b8505D332", aragonVoting: "0x39A0EbdEE54cB319f4F42141daaBDb6ba25D341A", tokenManager: "0xC73cd4B2A7c1CBC5BF046eB4A7019365558ABF66", agent: "0x32A0E5828B62AAb932362a4816ae03b860b65e83", @@ -16,7 +16,7 @@ export const E2E_TEST_CONTRACTS_OPTIMISM = { }, l2: { l2Token: "0x24B47cd3A74f1799b32B2de11073764Cb1bb318B", - l2ERC20TokenBridge: "0xdBA2760246f315203F8B716b3a7590F0FFdc704a", + l2ERC20ExtendedTokensBridge: "0xdBA2760246f315203F8B716b3a7590F0FFdc704a", govBridgeExecutor: "0xf695357C66bA514150Da95b189acb37b46DDe602", }, }; From a55b5f030592edd35c253316fcfbe6d29380724f Mon Sep 17 00:00:00 2001 From: Anton Kovalchuk Date: Mon, 15 Apr 2024 08:24:52 +0200 Subject: [PATCH 057/148] use mapping for tokens in bridges --- .../optimism/L1ERC20ExtendedTokensBridge.sol | 215 +++++++----------- contracts/optimism/L1LidoTokensBridge.sol | 4 +- .../optimism/L2ERC20ExtendedTokensBridge.sol | 156 +++++-------- .../RebasableAndNonRebasableTokens.sol | 126 ++++++++-- 4 files changed, 242 insertions(+), 259 deletions(-) diff --git a/contracts/optimism/L1ERC20ExtendedTokensBridge.sol b/contracts/optimism/L1ERC20ExtendedTokensBridge.sol index 7e303a97..97046a94 100644 --- a/contracts/optimism/L1ERC20ExtendedTokensBridge.sol +++ b/contracts/optimism/L1ERC20ExtendedTokensBridge.sol @@ -1,4 +1,4 @@ -// SPDX-FileCopyrightText: 2022 Lido +// SPDX-FileCopyrightText: 2024 Lido // SPDX-License-Identifier: GPL-3.0 pragma solidity 0.8.10; @@ -6,15 +6,13 @@ pragma solidity 0.8.10; import {IERC20} from "@openzeppelin/contracts/token/ERC20/IERC20.sol"; import {Address} from "@openzeppelin/contracts/utils/Address.sol"; import {SafeERC20} from "@openzeppelin/contracts/token/ERC20/utils/SafeERC20.sol"; - import {IL1ERC20Bridge} from "./interfaces/IL1ERC20Bridge.sol"; import {IL2ERC20Bridge} from "./interfaces/IL2ERC20Bridge.sol"; import {IERC20Wrapper} from "../token/interfaces/IERC20Wrapper.sol"; import {BridgingManager} from "../BridgingManager.sol"; import {RebasableAndNonRebasableTokens} from "./RebasableAndNonRebasableTokens.sol"; import {CrossDomainEnabled} from "./CrossDomainEnabled.sol"; -import {DepositDataCodec} from "./DepositDataCodec.sol"; -import {IERC20WstETH} from "../token/interfaces/IERC20WstETH.sol"; +import {DepositDataCodec} from "../lib//DepositDataCodec.sol"; /// @author psirex, kovalgek /// @notice The L1 ERC20 token bridge locks bridged tokens on the L1 side, sends deposit messages @@ -24,12 +22,11 @@ abstract contract L1ERC20ExtendedTokensBridge is IL1ERC20Bridge, BridgingManager, RebasableAndNonRebasableTokens, - CrossDomainEnabled, - DepositDataCodec + CrossDomainEnabled { using SafeERC20 for IERC20; - address public immutable L2_TOKEN_BRIDGE; + address private immutable L2_TOKEN_BRIDGE; /// @param messenger_ L1 messenger address being used for cross-chain communications /// @param l2TokenBridge_ Address of the corresponding L2 bridge @@ -53,8 +50,24 @@ abstract contract L1ERC20ExtendedTokensBridge is L2_TOKEN_BRIDGE = l2TokenBridge_; } + function initialize2( + address admin_, + address l1TokenNonRebasable_, + address l1TokenRebasable_, + address l2TokenNonRebasable_, + address l2TokenRebasable_ + ) external { + BridgingManager.initialize(admin_); + RebasableAndNonRebasableTokens.initialize( + l1TokenNonRebasable_, + l1TokenRebasable_, + l2TokenNonRebasable_, + l2TokenRebasable_ + ); + } + /// @notice required to abstact a way token rate is requested. - function tokenRate() virtual internal view returns (uint256); + function tokenRate(address l1NonRebasableToken) virtual internal view returns (uint256); /// @inheritdoc IL1ERC20Bridge function l2TokenBridge() external view returns (address) { @@ -71,14 +84,19 @@ abstract contract L1ERC20ExtendedTokensBridge is ) external whenDepositsEnabled - onlySupportedL1Token(l1Token_) - onlySupportedL2Token(l2Token_) + onlySupportedL1L2TokensPair(l1Token_, l2Token_) { if (Address.isContract(msg.sender)) { revert ErrorSenderNotEOA(); } - - _depositERC20To(l1Token_, l2Token_, msg.sender, amount_, l2Gas_, data_); + uint256 rate = tokenRate(_l1NonRebasableToken(l1Token_)); + bytes memory encodedDepositData = DepositDataCodec.encodeDepositData(DepositDataCodec.DepositData({ + rate: uint96(rate), + timestamp: uint40(block.timestamp), + data: data_ + })); + _depositERC20To(l1Token_, l2Token_, msg.sender, msg.sender, amount_, l2Gas_, data_); + emit ERC20DepositInitiated(l1Token_, l2Token_, msg.sender, msg.sender, amount_, encodedDepositData); } /// @inheritdoc IL1ERC20Bridge @@ -93,10 +111,18 @@ abstract contract L1ERC20ExtendedTokensBridge is external whenDepositsEnabled onlyNonZeroAccount(to_) - onlySupportedL1Token(l1Token_) - onlySupportedL2Token(l2Token_) + // onlySupportedL1Token(l1Token_) + // onlySupportedL2Token(l2Token_) + onlySupportedL1L2TokensPair(l1Token_, l2Token_) { - _depositERC20To(l1Token_, l2Token_, to_, amount_, l2Gas_, data_); + uint256 rate = tokenRate(_l1NonRebasableToken(l1Token_)); + bytes memory encodedDepositData = DepositDataCodec.encodeDepositData(DepositDataCodec.DepositData({ + rate: uint96(rate), + timestamp: uint40(block.timestamp), + data: data_ + })); + _depositERC20To(l1Token_, l2Token_, msg.sender, to_, amount_, l2Gas_, encodedDepositData); + emit ERC20DepositInitiated(l1Token_, l2Token_, msg.sender, to_, amount_, encodedDepositData); } /// @inheritdoc IL1ERC20Bridge @@ -110,154 +136,71 @@ abstract contract L1ERC20ExtendedTokensBridge is ) external whenWithdrawalsEnabled - onlySupportedL1Token(l1Token_) - onlySupportedL2Token(l2Token_) + onlySupportedL1L2TokensPair(l1Token_, l2Token_) onlyFromCrossDomainAccount(L2_TOKEN_BRIDGE) { - if (_isRebasableTokenFlow(l1Token_, l2Token_)) { - uint256 rebasableTokenAmount = IERC20Wrapper(L1_TOKEN_NON_REBASABLE).unwrap(amount_); - IERC20(L1_TOKEN_REBASABLE).safeTransfer(to_, rebasableTokenAmount); - - emit ERC20WithdrawalFinalized( - L1_TOKEN_REBASABLE, - L2_TOKEN_REBASABLE, - from_, - to_, - rebasableTokenAmount, - data_ - ); - } else if (_isNonRebasableTokenFlow(l1Token_, l2Token_)) { - IERC20(L1_TOKEN_NON_REBASABLE).safeTransfer(to_, amount_); - - emit ERC20WithdrawalFinalized( - L1_TOKEN_NON_REBASABLE, - L2_TOKEN_NON_REBASABLE, - from_, - to_, - amount_, - data_ - ); - } - } - - function _depositERC20To( - address l1Token_, - address l2Token_, - address to_, - uint256 amount_, - uint32 l2Gas_, - bytes memory data_ - ) internal { - if (_isRebasableTokenFlow(l1Token_, l2Token_)) { - DepositData memory depositData = DepositData({ - rate: uint96(tokenRate()), - timestamp: uint40(block.timestamp), - data: data_ - }); - bytes memory encodedDepositData = encodeDepositData(depositData); - - if (amount_ == 0) { - _initiateERC20Deposit( - L1_TOKEN_REBASABLE, - L2_TOKEN_REBASABLE, - msg.sender, - to_, - 0, - l2Gas_, - encodedDepositData - ); - - emit ERC20DepositInitiated( - L1_TOKEN_REBASABLE, - L2_TOKEN_REBASABLE, - msg.sender, - to_, - 0, - encodedDepositData - ); - - return; - } - - IERC20(L1_TOKEN_REBASABLE).safeTransferFrom(msg.sender, address(this), amount_); - if(!IERC20(L1_TOKEN_REBASABLE).approve(L1_TOKEN_NON_REBASABLE, amount_)) { - revert ErrorRebasableTokenApprove(); - } - uint256 nonRebasableTokenAmount = IERC20Wrapper(L1_TOKEN_NON_REBASABLE).wrap(amount_); - - _initiateERC20Deposit( - L1_TOKEN_REBASABLE, - L2_TOKEN_REBASABLE, - msg.sender, - to_, - nonRebasableTokenAmount, - l2Gas_, - encodedDepositData - ); - - emit ERC20DepositInitiated( - L1_TOKEN_REBASABLE, - L2_TOKEN_REBASABLE, - msg.sender, - to_, - amount_, - encodedDepositData - ); - } else if (_isNonRebasableTokenFlow(l1Token_, l2Token_)) { - IERC20(L1_TOKEN_NON_REBASABLE).safeTransferFrom(msg.sender, address(this), amount_); - - _initiateERC20Deposit( - L1_TOKEN_NON_REBASABLE, - L2_TOKEN_NON_REBASABLE, - msg.sender, - to_, - amount_, - l2Gas_, - data_ - ); - - emit ERC20DepositInitiated( - L1_TOKEN_NON_REBASABLE, - L2_TOKEN_NON_REBASABLE, - msg.sender, - to_, - amount_, - data_ - ); + if(_isRebasable(l1Token_)) { + address l1NonRebasableToken = _getRebasableTokens()[l1Token_].pairedToken; + uint256 rebasableTokenAmount = IERC20Wrapper(l1NonRebasableToken).unwrap(amount_); + IERC20(l1Token_).safeTransfer(to_, rebasableTokenAmount); + emit ERC20WithdrawalFinalized(l1Token_, l2Token_, from_, to_, rebasableTokenAmount, data_); + } else { + IERC20(l1Token_).safeTransfer(to_, amount_); + emit ERC20WithdrawalFinalized(l1Token_, l2Token_, from_, to_, amount_, data_); } } /// @dev Performs the logic for deposits by informing the L2 token bridge contract /// of the deposit and calling safeTransferFrom to lock the L1 funds. + + /// @param l1Token_ Address of the L1 ERC20 we are depositing + /// @param l2Token_ Address of the L1 respective L2 ERC20 /// @param from_ Account to pull the deposit from on L1 /// @param to_ Account to give the deposit to on L2 /// @param amount_ Amount of the ERC20 to deposit. /// @param l2Gas_ Gas limit required to complete the deposit on L2. - /// @param data_ Optional data to forward to L2. This data is provided + + /// @param encodedDepositData_ Optional data to forward to L2. This data is provided /// solely as a convenience for external contracts. Aside from enforcing a maximum /// length, these contracts provide no guarantees about its content. - function _initiateERC20Deposit( + function _depositERC20To( address l1Token_, address l2Token_, address from_, address to_, uint256 amount_, uint32 l2Gas_, - bytes memory data_ + bytes memory encodedDepositData_ ) internal { + uint256 amountToDeposit = _transferToBridge(l1Token_, from_, amount_); + bytes memory message = abi.encodeWithSelector( IL2ERC20Bridge.finalizeDeposit.selector, - l1Token_, - l2Token_, - from_, - to_, - amount_, - data_ + l1Token_, l2Token_, from_, to_, amountToDeposit, encodedDepositData_ ); sendCrossDomainMessage(L2_TOKEN_BRIDGE, l2Gas_, message); } + function _transferToBridge( + address l1Token_, + address from_, + uint256 amount_ + ) internal returns (uint256) { + + if (amount_ == 0) { + return amount_; + } + + IERC20(l1Token_).safeTransferFrom(from_, address(this), amount_); + if(_isRebasable(l1Token_)) { + address l1NonRebasableToken = _getRebasableTokens()[l1Token_].pairedToken; + if(!IERC20(l1Token_).approve(l1NonRebasableToken, amount_)) revert ErrorRebasableTokenApprove(); + return IERC20Wrapper(l1NonRebasableToken).wrap(amount_); + } + return amount_; + } + error ErrorSenderNotEOA(); error ErrorRebasableTokenApprove(); } diff --git a/contracts/optimism/L1LidoTokensBridge.sol b/contracts/optimism/L1LidoTokensBridge.sol index 491ff28b..85809780 100644 --- a/contracts/optimism/L1LidoTokensBridge.sol +++ b/contracts/optimism/L1LidoTokensBridge.sol @@ -27,7 +27,7 @@ contract L1LidoTokensBridge is L1ERC20ExtendedTokensBridge { ) { } - function tokenRate() override internal view returns (uint256) { - return IERC20WstETH(L1_TOKEN_NON_REBASABLE).stEthPerToken(); + function tokenRate(address l1NonRebasableToken) override internal view returns (uint256) { + return IERC20WstETH(l1NonRebasableToken).stEthPerToken(); } } diff --git a/contracts/optimism/L2ERC20ExtendedTokensBridge.sol b/contracts/optimism/L2ERC20ExtendedTokensBridge.sol index f2b630be..88caab8d 100644 --- a/contracts/optimism/L2ERC20ExtendedTokensBridge.sol +++ b/contracts/optimism/L2ERC20ExtendedTokensBridge.sol @@ -5,18 +5,16 @@ pragma solidity 0.8.10; import {IERC20} from "@openzeppelin/contracts/token/ERC20/IERC20.sol"; import {SafeERC20} from "@openzeppelin/contracts/token/ERC20/utils/SafeERC20.sol"; - import {IL1ERC20Bridge} from "./interfaces/IL1ERC20Bridge.sol"; import {IL2ERC20Bridge} from "./interfaces/IL2ERC20Bridge.sol"; import {IERC20Bridged} from "../token/interfaces/IERC20Bridged.sol"; import {ITokenRateOracle} from "../token/interfaces/ITokenRateOracle.sol"; import {IERC20Wrapper} from "../token/interfaces/IERC20Wrapper.sol"; - import {ERC20Rebasable} from "../token/ERC20Rebasable.sol"; import {BridgingManager} from "../BridgingManager.sol"; import {RebasableAndNonRebasableTokens} from "./RebasableAndNonRebasableTokens.sol"; import {CrossDomainEnabled} from "./CrossDomainEnabled.sol"; -import {DepositDataCodec} from "./DepositDataCodec.sol"; +import {DepositDataCodec} from "../lib/DepositDataCodec.sol"; /// @author psirex /// @notice The L2 token bridge works with the L1 token bridge to enable ERC20 token bridging @@ -28,8 +26,7 @@ contract L2ERC20ExtendedTokensBridge is IL2ERC20Bridge, BridgingManager, RebasableAndNonRebasableTokens, - CrossDomainEnabled, - DepositDataCodec + CrossDomainEnabled { using SafeERC20 for IERC20; @@ -68,8 +65,12 @@ contract L2ERC20ExtendedTokensBridge is uint256 amount_, uint32 l1Gas_, bytes calldata data_ - ) external whenWithdrawalsEnabled onlySupportedL2Token(l2Token_) { - _withdrawTo(l2Token_, msg.sender, amount_, l1Gas_, data_); + ) external + whenWithdrawalsEnabled + onlySupportedL2Token(l2Token_) + { + _withdrawTo(l2Token_, msg.sender, msg.sender, amount_, l1Gas_, data_); + emit WithdrawalInitiated(_l1Token(l2Token_), l2Token_, msg.sender, msg.sender, amount_, data_); } /// @inheritdoc IL2ERC20Bridge @@ -79,8 +80,12 @@ contract L2ERC20ExtendedTokensBridge is uint256 amount_, uint32 l1Gas_, bytes calldata data_ - ) external whenWithdrawalsEnabled onlySupportedL2Token(l2Token_) { - _withdrawTo(l2Token_, to_, amount_, l1Gas_, data_); + ) external + whenWithdrawalsEnabled + onlySupportedL2Token(l2Token_) + { + _withdrawTo(l2Token_, msg.sender, to_, amount_, l1Gas_, data_); + emit WithdrawalInitiated(_l1Token(l2Token_), l2Token_, msg.sender, to_, amount_, data_); } /// @inheritdoc IL2ERC20Bridge @@ -93,90 +98,16 @@ contract L2ERC20ExtendedTokensBridge is bytes calldata data_ ) external - whenDepositsEnabled - onlySupportedL1Token(l1Token_) - onlySupportedL2Token(l2Token_) + whenDepositsEnabled() + onlySupportedL1L2TokensPair(l1Token_, l2Token_) onlyFromCrossDomainAccount(L1_TOKEN_BRIDGE) { - if (_isRebasableTokenFlow(l1Token_, l2Token_)) { - DepositData memory depositData = decodeDepositData(data_); - - ITokenRateOracle tokenRateOracle = ERC20Rebasable(L2_TOKEN_REBASABLE).TOKEN_RATE_ORACLE(); - tokenRateOracle.updateRate(depositData.rate, depositData.timestamp); - - ERC20Rebasable(L2_TOKEN_REBASABLE).bridgeMintShares(to_, amount_); - - uint256 rebasableTokenAmount = ERC20Rebasable(L2_TOKEN_REBASABLE).getTokensByShares(amount_); - emit DepositFinalized( - L1_TOKEN_REBASABLE, - L2_TOKEN_REBASABLE, - from_, - to_, - rebasableTokenAmount, - depositData.data - ); - } else if (_isNonRebasableTokenFlow(l1Token_, l2Token_)) { - IERC20Bridged(L2_TOKEN_NON_REBASABLE).bridgeMint(to_, amount_); - emit DepositFinalized( - L1_TOKEN_NON_REBASABLE, - L2_TOKEN_NON_REBASABLE, - from_, - to_, - amount_, - data_ - ); - } - } + DepositDataCodec.DepositData memory depositData = DepositDataCodec.decodeDepositData(data_); + ITokenRateOracle tokenRateOracle = ERC20Rebasable(l2Token_).TOKEN_RATE_ORACLE(); + tokenRateOracle.updateRate(depositData.rate, depositData.timestamp); - function _withdrawTo( - address l2Token_, - address to_, - uint256 amount_, - uint32 l1Gas_, - bytes calldata data_ - ) internal { - if (l2Token_ == L2_TOKEN_REBASABLE) { - uint256 shares = ERC20Rebasable(L2_TOKEN_REBASABLE).getSharesByTokens(amount_); - ERC20Rebasable(L2_TOKEN_REBASABLE).bridgeBurnShares(msg.sender, shares); - - _initiateWithdrawal( - L1_TOKEN_REBASABLE, - L2_TOKEN_REBASABLE, - msg.sender, - to_, - shares, - l1Gas_, - data_ - ); - emit WithdrawalInitiated( - L1_TOKEN_REBASABLE, - L2_TOKEN_REBASABLE, - msg.sender, - to_, - amount_, - data_ - ); - } else if (l2Token_ == L2_TOKEN_NON_REBASABLE) { - IERC20Bridged(L2_TOKEN_NON_REBASABLE).bridgeBurn(msg.sender, amount_); - - _initiateWithdrawal( - L1_TOKEN_NON_REBASABLE, - L2_TOKEN_NON_REBASABLE, - msg.sender, - to_, - amount_, - l1Gas_, - data_ - ); - emit WithdrawalInitiated( - L1_TOKEN_NON_REBASABLE, - L2_TOKEN_NON_REBASABLE, - msg.sender, - to_, - amount_, - data_ - ); - } + uint256 depositedAmount = _mintTokens(l1Token_, l2Token_, to_, amount_); + emit DepositFinalized(l1Token_, l2Token_, from_, to_, depositedAmount, depositData.data); } /// @notice Performs the logic for withdrawals by burning the token and informing @@ -188,25 +119,50 @@ contract L2ERC20ExtendedTokensBridge is /// @param data_ Optional data to forward to L1. This data is provided /// solely as a convenience for external contracts. Aside from enforcing a maximum /// length, these contracts provide no guarantees about its content - function _initiateWithdrawal( - address l1Token_, + function _withdrawTo( address l2Token_, address from_, address to_, uint256 amount_, uint32 l1Gas_, - bytes memory data_ + bytes calldata data_ ) internal { + uint256 amountToWithdraw = _burnTokens(l2Token_, from_, amount_); + bytes memory message = abi.encodeWithSelector( IL1ERC20Bridge.finalizeERC20Withdrawal.selector, - l1Token_, - l2Token_, - from_, - to_, - amount_, - data_ + _l1Token(l2Token_), l2Token_, from_, to_, amountToWithdraw, data_ ); - sendCrossDomainMessage(L1_TOKEN_BRIDGE, l1Gas_, message); } + + function _mintTokens( + address l1Token_, + address l2Token_, + address to_, + uint256 amount_ + ) internal returns (uint256) { + if(_isRebasable(l1Token_)) { + ERC20Rebasable(l2Token_).bridgeMintShares(to_, amount_); + return ERC20Rebasable(l2Token_).getTokensByShares(amount_); + } + + IERC20Bridged(l2Token_).bridgeMint(to_, amount_); + return amount_; + } + + function _burnTokens( + address l2Token_, + address from_, + uint256 amount_ + ) internal returns (uint256) { + if(_isRebasable(l2Token_)) { + uint256 shares = ERC20Rebasable(l2Token_).getSharesByTokens(amount_); + ERC20Rebasable(l2Token_).bridgeBurnShares(from_, shares); + return shares; + } + + IERC20Bridged(l2Token_).bridgeBurn(from_, amount_); + return amount_; + } } diff --git a/contracts/optimism/RebasableAndNonRebasableTokens.sol b/contracts/optimism/RebasableAndNonRebasableTokens.sol index bffff151..45313fc6 100644 --- a/contracts/optimism/RebasableAndNonRebasableTokens.sol +++ b/contracts/optimism/RebasableAndNonRebasableTokens.sol @@ -1,37 +1,113 @@ -// SPDX-FileCopyrightText: 2022 Lido +// SPDX-FileCopyrightText: 2024 Lido // SPDX-License-Identifier: GPL-3.0 pragma solidity 0.8.10; -/// @author psirex +import {UnstructuredRefStorage} from "../token/UnstructuredRefStorage.sol"; + +/// @author psirex, kovalgek /// @notice Contains the logic for validation of tokens used in the bridging process contract RebasableAndNonRebasableTokens { - /// @notice Address of the bridged non rebasable token in the L1 chain - address public immutable L1_TOKEN_NON_REBASABLE; + using UnstructuredRefStorage for bytes32; + + /// @dev Servers for pairing tokens by one-layer and wrapping. + /// @param `oppositeLayerToken` token representation on opposite layer. + /// @param `pairedToken` paired token address on the same domain. + struct TokenInfo { + address oppositeLayerToken; + address pairedToken; + } - /// @notice Address of the bridged rebasable token in the L1 chain - address public immutable L1_TOKEN_REBASABLE; + bytes32 internal constant REBASABLE_TOKENS_POSITION = keccak256("RebasableAndNonRebasableTokens.REBASABLE_TOKENS_POSITION"); + bytes32 internal constant NON_REBASABLE_TOKENS_POSITION = keccak256("RebasableAndNonRebasableTokens.NON_REBASABLE_TOKENS_POSITION"); + + function _getRebasableTokens() internal pure returns (mapping(address => TokenInfo) storage) { + return _storageMapAddressTokenInfo(REBASABLE_TOKENS_POSITION); + } - /// @notice Address of the non rebasable token minted on the L2 chain when token bridged - address public immutable L2_TOKEN_NON_REBASABLE; + function _getNonRebasableTokens() internal pure returns (mapping(address => TokenInfo) storage) { + return _storageMapAddressTokenInfo(REBASABLE_TOKENS_POSITION); + } - /// @notice Address of the rebasable token minted on the L2 chain when token bridged - address public immutable L2_TOKEN_REBASABLE; + function _storageMapAddressTokenInfo(bytes32 _position) internal pure returns ( + mapping(address => TokenInfo) storage result + ) { + assembly { result.slot := _position } + } /// @param l1TokenNonRebasable_ Address of the bridged non rebasable token in the L1 chain /// @param l1TokenRebasable_ Address of the bridged rebasable token in the L1 chain /// @param l2TokenNonRebasable_ Address of the non rebasable token minted on the L2 chain when token bridged /// @param l2TokenRebasable_ Address of the rebasable token minted on the L2 chain when token bridged - constructor(address l1TokenNonRebasable_, address l1TokenRebasable_, address l2TokenNonRebasable_, address l2TokenRebasable_) { - L1_TOKEN_NON_REBASABLE = l1TokenNonRebasable_; - L1_TOKEN_REBASABLE = l1TokenRebasable_; - L2_TOKEN_NON_REBASABLE = l2TokenNonRebasable_; - L2_TOKEN_REBASABLE = l2TokenRebasable_; + constructor( + address l1TokenNonRebasable_, + address l1TokenRebasable_, + address l2TokenNonRebasable_, + address l2TokenRebasable_ + ) { + _getRebasableTokens()[l1TokenRebasable_] = TokenInfo({ + oppositeLayerToken: l2TokenRebasable_, + pairedToken: l1TokenNonRebasable_ + }); + _getRebasableTokens()[l2TokenRebasable_] = TokenInfo({ + oppositeLayerToken: l1TokenRebasable_, + pairedToken: l2TokenNonRebasable_ + }); + _getNonRebasableTokens()[l1TokenNonRebasable_] = TokenInfo({ + oppositeLayerToken: l2TokenNonRebasable_, + pairedToken: l1TokenRebasable_ + }); + _getNonRebasableTokens()[l2TokenNonRebasable_] = TokenInfo({ + oppositeLayerToken: l1TokenNonRebasable_, + pairedToken: l2TokenRebasable_ + }); + } + + function initialize( + address l1TokenNonRebasable_, + address l1TokenRebasable_, + address l2TokenNonRebasable_, + address l2TokenRebasable_ + ) public { + _getRebasableTokens()[l1TokenRebasable_] = TokenInfo({ + oppositeLayerToken: l2TokenRebasable_, + pairedToken: l1TokenNonRebasable_ + }); + _getRebasableTokens()[l2TokenRebasable_] = TokenInfo({ + oppositeLayerToken: l1TokenRebasable_, + pairedToken: l2TokenNonRebasable_ + }); + _getNonRebasableTokens()[l1TokenNonRebasable_] = TokenInfo({ + oppositeLayerToken: l2TokenNonRebasable_, + pairedToken: l1TokenRebasable_ + }); + _getNonRebasableTokens()[l2TokenNonRebasable_] = TokenInfo({ + oppositeLayerToken: l1TokenNonRebasable_, + pairedToken: l2TokenRebasable_ + }); + } + + /// @dev Validates that passed l1Token_ and l2Token_ tokens pair is supported by the bridge. + modifier onlySupportedL1L2TokensPair(address l1Token_, address l2Token_) { + if (_getRebasableTokens()[l1Token_].oppositeLayerToken == address(0) && + _getNonRebasableTokens()[l1Token_].oppositeLayerToken == address(0)) { + revert ErrorUnsupportedL1Token(); + } + if (_getRebasableTokens()[l2Token_].oppositeLayerToken == address(0) && + _getNonRebasableTokens()[l2Token_].oppositeLayerToken == address(0)) { + revert ErrorUnsupportedL2Token(); + } + if (_getRebasableTokens()[l1Token_].oppositeLayerToken != l2Token_ && + _getNonRebasableTokens()[l2Token_].oppositeLayerToken != l1Token_) { + revert ErrorUnsupportedL1L2TokensPair(); + } + _; } /// @dev Validates that passed l1Token_ is supported by the bridge modifier onlySupportedL1Token(address l1Token_) { - if (l1Token_ != L1_TOKEN_NON_REBASABLE && l1Token_ != L1_TOKEN_REBASABLE) { + if (_getRebasableTokens()[l1Token_].oppositeLayerToken == address(0) && + _getNonRebasableTokens()[l1Token_].oppositeLayerToken == address(0)) { revert ErrorUnsupportedL1Token(); } _; @@ -39,7 +115,8 @@ contract RebasableAndNonRebasableTokens { /// @dev Validates that passed l2Token_ is supported by the bridge modifier onlySupportedL2Token(address l2Token_) { - if (l2Token_ != L2_TOKEN_NON_REBASABLE && l2Token_ != L2_TOKEN_REBASABLE) { + if (_getRebasableTokens()[l2Token_].oppositeLayerToken == address(0) && + _getNonRebasableTokens()[l2Token_].oppositeLayerToken == address(0)) { revert ErrorUnsupportedL2Token(); } _; @@ -53,15 +130,22 @@ contract RebasableAndNonRebasableTokens { _; } - function _isRebasableTokenFlow(address l1Token_, address l2Token_) internal view returns (bool) { - return l1Token_ == L1_TOKEN_REBASABLE && l2Token_ == L2_TOKEN_REBASABLE; + function _isRebasable(address token_) internal view returns (bool) { + return _getRebasableTokens()[token_].oppositeLayerToken != address(0); + } + + function _l1Token(address l2Token_) internal view returns (address) { + return _isRebasable(l2Token_) ? + _getRebasableTokens()[l2Token_].oppositeLayerToken : + _getNonRebasableTokens()[l2Token_].oppositeLayerToken; } - function _isNonRebasableTokenFlow(address l1Token_, address l2Token_) internal view returns (bool) { - return l1Token_ == L1_TOKEN_NON_REBASABLE && l2Token_ == L2_TOKEN_NON_REBASABLE; + function _l1NonRebasableToken(address l1Token_) internal view returns (address) { + return _isRebasable(l1Token_) ? _getRebasableTokens()[l1Token_].pairedToken : l1Token_; } error ErrorUnsupportedL1Token(); error ErrorUnsupportedL2Token(); + error ErrorUnsupportedL1L2TokensPair(); error ErrorAccountIsZeroAddress(); } From f54eecdf85908d1e2ab6a37e6e71c238ae965a99 Mon Sep 17 00:00:00 2001 From: Anton Kovalchuk Date: Mon, 15 Apr 2024 15:46:28 +0200 Subject: [PATCH 058/148] fix bridge unit and integration tests, remove mapping in storing tokens in bridge --- .../optimism/L1ERC20ExtendedTokensBridge.sol | 51 +- contracts/optimism/L1LidoTokensBridge.sol | 6 +- .../optimism/L2ERC20ExtendedTokensBridge.sol | 6 +- test/optimism/L1ERC20TokenBridge.unit.test.ts | 918 --------------- test/optimism/L1LidoTokensBridge.unit.test.ts | 1014 +++++++++++++++++ ... L2ERC20ExtendedTokensBridge.unit.test.ts} | 58 +- .../bridging-rebasable.integration.test.ts | 3 +- test/optimism/bridging.integration.test.ts | 33 +- 8 files changed, 1107 insertions(+), 982 deletions(-) delete mode 100644 test/optimism/L1ERC20TokenBridge.unit.test.ts create mode 100644 test/optimism/L1LidoTokensBridge.unit.test.ts rename test/optimism/{L2ERC20TokenBridge.unit.test.ts => L2ERC20ExtendedTokensBridge.unit.test.ts} (91%) diff --git a/contracts/optimism/L1ERC20ExtendedTokensBridge.sol b/contracts/optimism/L1ERC20ExtendedTokensBridge.sol index 97046a94..6a16129d 100644 --- a/contracts/optimism/L1ERC20ExtendedTokensBridge.sol +++ b/contracts/optimism/L1ERC20ExtendedTokensBridge.sol @@ -50,24 +50,8 @@ abstract contract L1ERC20ExtendedTokensBridge is L2_TOKEN_BRIDGE = l2TokenBridge_; } - function initialize2( - address admin_, - address l1TokenNonRebasable_, - address l1TokenRebasable_, - address l2TokenNonRebasable_, - address l2TokenRebasable_ - ) external { - BridgingManager.initialize(admin_); - RebasableAndNonRebasableTokens.initialize( - l1TokenNonRebasable_, - l1TokenRebasable_, - l2TokenNonRebasable_, - l2TokenRebasable_ - ); - } - /// @notice required to abstact a way token rate is requested. - function tokenRate(address l1NonRebasableToken) virtual internal view returns (uint256); + function tokenRate() virtual internal view returns (uint256); /// @inheritdoc IL1ERC20Bridge function l2TokenBridge() external view returns (address) { @@ -89,13 +73,12 @@ abstract contract L1ERC20ExtendedTokensBridge is if (Address.isContract(msg.sender)) { revert ErrorSenderNotEOA(); } - uint256 rate = tokenRate(_l1NonRebasableToken(l1Token_)); bytes memory encodedDepositData = DepositDataCodec.encodeDepositData(DepositDataCodec.DepositData({ - rate: uint96(rate), + rate: uint96(tokenRate()), timestamp: uint40(block.timestamp), data: data_ })); - _depositERC20To(l1Token_, l2Token_, msg.sender, msg.sender, amount_, l2Gas_, data_); + _depositERC20To(l1Token_, l2Token_, msg.sender, msg.sender, amount_, l2Gas_, encodedDepositData); emit ERC20DepositInitiated(l1Token_, l2Token_, msg.sender, msg.sender, amount_, encodedDepositData); } @@ -111,13 +94,10 @@ abstract contract L1ERC20ExtendedTokensBridge is external whenDepositsEnabled onlyNonZeroAccount(to_) - // onlySupportedL1Token(l1Token_) - // onlySupportedL2Token(l2Token_) onlySupportedL1L2TokensPair(l1Token_, l2Token_) { - uint256 rate = tokenRate(_l1NonRebasableToken(l1Token_)); bytes memory encodedDepositData = DepositDataCodec.encodeDepositData(DepositDataCodec.DepositData({ - rate: uint96(rate), + rate: uint96(tokenRate()), timestamp: uint40(block.timestamp), data: data_ })); @@ -136,12 +116,11 @@ abstract contract L1ERC20ExtendedTokensBridge is ) external whenWithdrawalsEnabled - onlySupportedL1L2TokensPair(l1Token_, l2Token_) onlyFromCrossDomainAccount(L2_TOKEN_BRIDGE) + onlySupportedL1L2TokensPair(l1Token_, l2Token_) { if(_isRebasable(l1Token_)) { - address l1NonRebasableToken = _getRebasableTokens()[l1Token_].pairedToken; - uint256 rebasableTokenAmount = IERC20Wrapper(l1NonRebasableToken).unwrap(amount_); + uint256 rebasableTokenAmount = IERC20Wrapper(L1_TOKEN_NON_REBASABLE).unwrap(amount_); IERC20(l1Token_).safeTransfer(to_, rebasableTokenAmount); emit ERC20WithdrawalFinalized(l1Token_, l2Token_, from_, to_, rebasableTokenAmount, data_); } else { @@ -152,14 +131,12 @@ abstract contract L1ERC20ExtendedTokensBridge is /// @dev Performs the logic for deposits by informing the L2 token bridge contract /// of the deposit and calling safeTransferFrom to lock the L1 funds. - /// @param l1Token_ Address of the L1 ERC20 we are depositing /// @param l2Token_ Address of the L1 respective L2 ERC20 /// @param from_ Account to pull the deposit from on L1 /// @param to_ Account to give the deposit to on L2 /// @param amount_ Amount of the ERC20 to deposit. /// @param l2Gas_ Gas limit required to complete the deposit on L2. - /// @param encodedDepositData_ Optional data to forward to L2. This data is provided /// solely as a convenience for external contracts. Aside from enforcing a maximum /// length, these contracts provide no guarantees about its content. @@ -187,16 +164,12 @@ abstract contract L1ERC20ExtendedTokensBridge is address from_, uint256 amount_ ) internal returns (uint256) { - - if (amount_ == 0) { - return amount_; - } - - IERC20(l1Token_).safeTransferFrom(from_, address(this), amount_); - if(_isRebasable(l1Token_)) { - address l1NonRebasableToken = _getRebasableTokens()[l1Token_].pairedToken; - if(!IERC20(l1Token_).approve(l1NonRebasableToken, amount_)) revert ErrorRebasableTokenApprove(); - return IERC20Wrapper(l1NonRebasableToken).wrap(amount_); + if (amount_ != 0) { + IERC20(l1Token_).safeTransferFrom(from_, address(this), amount_); + if(_isRebasable(l1Token_)) { + if(!IERC20(l1Token_).approve(L1_TOKEN_NON_REBASABLE, amount_)) revert ErrorRebasableTokenApprove(); + return IERC20Wrapper(L1_TOKEN_NON_REBASABLE).wrap(amount_); + } } return amount_; } diff --git a/contracts/optimism/L1LidoTokensBridge.sol b/contracts/optimism/L1LidoTokensBridge.sol index 85809780..c28ed325 100644 --- a/contracts/optimism/L1LidoTokensBridge.sol +++ b/contracts/optimism/L1LidoTokensBridge.sol @@ -1,4 +1,4 @@ -// SPDX-FileCopyrightText: 2022 Lido +// SPDX-FileCopyrightText: 2024 Lido // SPDX-License-Identifier: GPL-3.0 pragma solidity 0.8.10; @@ -27,7 +27,7 @@ contract L1LidoTokensBridge is L1ERC20ExtendedTokensBridge { ) { } - function tokenRate(address l1NonRebasableToken) override internal view returns (uint256) { - return IERC20WstETH(l1NonRebasableToken).stEthPerToken(); + function tokenRate() override internal view returns (uint256) { + return IERC20WstETH(L1_TOKEN_NON_REBASABLE).stEthPerToken(); } } diff --git a/contracts/optimism/L2ERC20ExtendedTokensBridge.sol b/contracts/optimism/L2ERC20ExtendedTokensBridge.sol index 88caab8d..21bc55e7 100644 --- a/contracts/optimism/L2ERC20ExtendedTokensBridge.sol +++ b/contracts/optimism/L2ERC20ExtendedTokensBridge.sol @@ -1,4 +1,4 @@ -// SPDX-FileCopyrightText: 2022 Lido +// SPDX-FileCopyrightText: 2024 Lido // SPDX-License-Identifier: GPL-3.0 pragma solidity 0.8.10; @@ -16,7 +16,7 @@ import {RebasableAndNonRebasableTokens} from "./RebasableAndNonRebasableTokens.s import {CrossDomainEnabled} from "./CrossDomainEnabled.sol"; import {DepositDataCodec} from "../lib/DepositDataCodec.sol"; -/// @author psirex +/// @author psirex, kovalgek /// @notice The L2 token bridge works with the L1 token bridge to enable ERC20 token bridging /// between L1 and L2. It acts as a minter for new tokens when it hears about /// deposits into the L1 token bridge. It also acts as a burner of the tokens @@ -103,7 +103,7 @@ contract L2ERC20ExtendedTokensBridge is onlyFromCrossDomainAccount(L1_TOKEN_BRIDGE) { DepositDataCodec.DepositData memory depositData = DepositDataCodec.decodeDepositData(data_); - ITokenRateOracle tokenRateOracle = ERC20Rebasable(l2Token_).TOKEN_RATE_ORACLE(); + ITokenRateOracle tokenRateOracle = ERC20Rebasable(L2_TOKEN_REBASABLE).TOKEN_RATE_ORACLE(); tokenRateOracle.updateRate(depositData.rate, depositData.timestamp); uint256 depositedAmount = _mintTokens(l1Token_, l2Token_, to_, amount_); diff --git a/test/optimism/L1ERC20TokenBridge.unit.test.ts b/test/optimism/L1ERC20TokenBridge.unit.test.ts deleted file mode 100644 index d70705db..00000000 --- a/test/optimism/L1ERC20TokenBridge.unit.test.ts +++ /dev/null @@ -1,918 +0,0 @@ -import { assert } from "chai"; -import hre, { ethers } from "hardhat"; -import { - ERC20BridgedStub__factory, - ERC20WrapperStub__factory, - L1LidoTokensBridge__factory, - L2ERC20ExtendedTokensBridge__factory, - OssifiableProxy__factory, - EmptyContractStub__factory, -} from "../../typechain"; -import { JsonRpcProvider } from "@ethersproject/providers"; -import { CrossDomainMessengerStub__factory } from "../../typechain/factories/CrossDomainMessengerStub__factory"; -import testing, { unit } from "../../utils/testing"; -import { wei } from "../../utils/wei"; -import { BigNumber } from "ethers"; -import { ERC20WrapperStub } from "../../typechain"; - -unit("Optimism :: L1LidoTokensBridge", ctxFactory) - .test("l2TokenBridge()", async (ctx) => { - assert.equal( - await ctx.l1TokenBridge.l2TokenBridge(), - ctx.accounts.l2TokenBridgeEOA.address - ); - }) - - .test("depositERC20() :: deposits disabled", async (ctx) => { - await ctx.l1TokenBridge.disableDeposits(); - - assert.isFalse(await ctx.l1TokenBridge.isDepositsEnabled()); - - await assert.revertsWith( - ctx.l1TokenBridge.depositERC20( - ctx.stubs.l1TokenNonRebasable.address, - ctx.stubs.l2TokenNonRebasable.address, - wei`1 ether`, - wei`1 gwei`, - "0x" - ), - "ErrorDepositsDisabled()" - ); - - await assert.revertsWith( - ctx.l1TokenBridge.depositERC20( - ctx.stubs.l1TokenRebasable.address, - ctx.stubs.l2TokenRebasable.address, - wei`1 ether`, - wei`1 gwei`, - "0x" - ), - "ErrorDepositsDisabled()" - ); - }) - - .test("depositsERC20() :: wrong l1Token address", async (ctx) => { - await assert.revertsWith( - ctx.l1TokenBridge.depositERC20( - ctx.accounts.stranger.address, - ctx.stubs.l2TokenNonRebasable.address, - wei`1 ether`, - wei`1 gwei`, - "0x" - ), - "ErrorUnsupportedL1Token()" - ); - await assert.revertsWith( - ctx.l1TokenBridge.depositERC20( - ctx.accounts.stranger.address, - ctx.stubs.l2TokenRebasable.address, - wei`1 ether`, - wei`1 gwei`, - "0x" - ), - "ErrorUnsupportedL1Token()" - ); - }) - - .test("depositsERC20() :: wrong l2Token address", async (ctx) => { - await assert.revertsWith( - ctx.l1TokenBridge.depositERC20( - ctx.stubs.l1TokenNonRebasable.address, - ctx.accounts.stranger.address, - wei`1 ether`, - wei`1 gwei`, - "0x" - ), - "ErrorUnsupportedL2Token()" - ); - await assert.revertsWith( - ctx.l1TokenBridge.depositERC20( - ctx.stubs.l1TokenRebasable.address, - ctx.accounts.stranger.address, - wei`1 ether`, - wei`1 gwei`, - "0x" - ), - "ErrorUnsupportedL2Token()" - ); - }) - - .test("depositERC20() :: not from EOA", async (ctx) => { - await assert.revertsWith( - ctx.l1TokenBridge - .connect(ctx.accounts.emptyContractAsEOA) - .depositERC20( - ctx.stubs.l1TokenNonRebasable.address, - ctx.stubs.l2TokenNonRebasable.address, - wei`1 ether`, - wei`1 gwei`, - "0x" - ), - "ErrorSenderNotEOA()" - ); - await assert.revertsWith( - ctx.l1TokenBridge - .connect(ctx.accounts.emptyContractAsEOA) - .depositERC20( - ctx.stubs.l1TokenRebasable.address, - ctx.stubs.l2TokenRebasable.address, - wei`1 ether`, - wei`1 gwei`, - "0x" - ), - "ErrorSenderNotEOA()" - ); - }) - - .test("depositERC20() :: non rebasable token flow", async (ctx) => { - const { - l1TokenBridge, - accounts: { deployer, l2TokenBridgeEOA }, - stubs: { l1TokenNonRebasable, l2TokenNonRebasable, l1Messenger }, - } = ctx; - - const l2Gas = wei`0.99 wei`; - const amount = wei`1 ether`; - const data = "0xdeadbeaf"; - - await l1TokenNonRebasable.approve(l1TokenBridge.address, amount); - - const deployerBalanceBefore = await l1TokenNonRebasable.balanceOf(deployer.address); - const bridgeBalanceBefore = await l1TokenNonRebasable.balanceOf(l1TokenBridge.address); - - const tx = await l1TokenBridge.depositERC20( - l1TokenNonRebasable.address, - l2TokenNonRebasable.address, - amount, - l2Gas, - data - ); - - await assert.emits(l1TokenBridge, tx, "ERC20DepositInitiated", [ - l1TokenNonRebasable.address, - l2TokenNonRebasable.address, - deployer.address, - deployer.address, - amount, - data, - ]); - - await assert.emits(l1Messenger, tx, "SentMessage", [ - l2TokenBridgeEOA.address, - l1TokenBridge.address, - L2ERC20ExtendedTokensBridge__factory.createInterface().encodeFunctionData( - "finalizeDeposit", - [ - l1TokenNonRebasable.address, - l2TokenNonRebasable.address, - deployer.address, - deployer.address, - amount, - data, - ] - ), - 1, // message nonce - l2Gas, - ]); - - assert.equalBN( - await l1TokenNonRebasable.balanceOf(deployer.address), - deployerBalanceBefore.sub(amount) - ); - - assert.equalBN( - await l1TokenNonRebasable.balanceOf(l1TokenBridge.address), - bridgeBalanceBefore.add(amount) - ); - }) - - .test("depositERC20() :: rebasable token flow", async (ctx) => { - const { - l1TokenBridge, - accounts: { deployer, l2TokenBridgeEOA }, - stubs: { l1TokenRebasable, l2TokenRebasable, l1TokenNonRebasable, l1Messenger }, - } = ctx; - - const l2Gas = wei`0.99 wei`; - const amount = wei`1 ether`; - const data = "0xdeadbeaf"; - const rate = await l1TokenNonRebasable.stEthPerToken(); - const decimalsStr = await l1TokenNonRebasable.decimals(); - const decimals = BigNumber.from(10).pow(decimalsStr); - - const amountWrapped = (wei.toBigNumber(amount)).mul(BigNumber.from(decimals)).div(rate); - const deployerBalanceBefore = await l1TokenRebasable.balanceOf(deployer.address); - const bridgeBalanceBefore = await l1TokenNonRebasable.balanceOf(l1TokenBridge.address); - - await l1TokenRebasable.approve(l1TokenBridge.address, amount); - - const tx = await l1TokenBridge.depositERC20( - l1TokenRebasable.address, - l2TokenRebasable.address, - amount, - l2Gas, - data - ); - - const packedTokenRateAndTimestampData = await packedTokenRateAndTimestamp(ctx.provider, l1TokenNonRebasable); - const dataToReceive = ethers.utils.hexConcat([packedTokenRateAndTimestampData, data]); - - await assert.emits(l1TokenBridge, tx, "ERC20DepositInitiated", [ - l1TokenRebasable.address, - l2TokenRebasable.address, - deployer.address, - deployer.address, - amount, - dataToReceive, - ]); - - await assert.emits(l1Messenger, tx, "SentMessage", [ - l2TokenBridgeEOA.address, - l1TokenBridge.address, - L2ERC20ExtendedTokensBridge__factory.createInterface().encodeFunctionData( - "finalizeDeposit", - [ - l1TokenRebasable.address, - l2TokenRebasable.address, - deployer.address, - deployer.address, - amountWrapped, - dataToReceive, - ] - ), - 1, // message nonce - l2Gas, - ]); - - assert.equalBN( - await l1TokenRebasable.balanceOf(deployer.address), - deployerBalanceBefore.sub(amount) - ); - - assert.equalBN( - await l1TokenNonRebasable.balanceOf(l1TokenBridge.address), - bridgeBalanceBefore.add(amountWrapped) - ); - }) - - .test("depositERC20To() :: deposits disabled", async (ctx) => { - const { - l1TokenBridge, - stubs: { l1TokenNonRebasable, l2TokenNonRebasable, l1TokenRebasable, l2TokenRebasable }, - accounts: { recipient }, - } = ctx; - await l1TokenBridge.disableDeposits(); - - assert.isFalse(await l1TokenBridge.isDepositsEnabled()); - - await assert.revertsWith( - l1TokenBridge.depositERC20To( - l1TokenNonRebasable.address, - l2TokenNonRebasable.address, - recipient.address, - wei`1 ether`, - wei`1 gwei`, - "0x" - ), - "ErrorDepositsDisabled()" - ); - - await assert.revertsWith( - l1TokenBridge.depositERC20To( - l1TokenRebasable.address, - l2TokenRebasable.address, - recipient.address, - wei`1 ether`, - wei`1 gwei`, - "0x" - ), - "ErrorDepositsDisabled()" - ); - }) - - .test("depositsERC20To() :: wrong l1Token address", async (ctx) => { - const { - l1TokenBridge, - stubs: { l2TokenNonRebasable, l2TokenRebasable }, - accounts: { recipient, stranger }, - } = ctx; - await l1TokenBridge.disableDeposits(); - - assert.isFalse(await l1TokenBridge.isDepositsEnabled()); - - await assert.revertsWith( - l1TokenBridge.depositERC20To( - stranger.address, - l2TokenNonRebasable.address, - recipient.address, - wei`1 ether`, - wei`1 gwei`, - "0x" - ), - "ErrorDepositsDisabled()" - ); - await assert.revertsWith( - l1TokenBridge.depositERC20To( - stranger.address, - l2TokenRebasable.address, - recipient.address, - wei`1 ether`, - wei`1 gwei`, - "0x" - ), - "ErrorDepositsDisabled()" - ); - }) - - .test("depositsERC20To() :: wrong l2Token address", async (ctx) => { - const { - l1TokenBridge, - stubs: { l1TokenNonRebasable, l1TokenRebasable }, - accounts: { recipient, stranger }, - } = ctx; - await l1TokenBridge.disableDeposits(); - - assert.isFalse(await l1TokenBridge.isDepositsEnabled()); - - await assert.revertsWith( - l1TokenBridge.depositERC20To( - l1TokenNonRebasable.address, - stranger.address, - recipient.address, - wei`1 ether`, - wei`1 gwei`, - "0x" - ), - "ErrorDepositsDisabled()" - ); - await assert.revertsWith( - l1TokenBridge.depositERC20To( - l1TokenRebasable.address, - stranger.address, - recipient.address, - wei`1 ether`, - wei`1 gwei`, - "0x" - ), - "ErrorDepositsDisabled()" - ); - }) - - .test("depositsERC20To() :: recipient is zero address", async (ctx) => { - const { - l1TokenBridge, - stubs: { l1TokenNonRebasable, l2TokenNonRebasable, l1TokenRebasable, l2TokenRebasable } - } = ctx; - - await assert.revertsWith( - l1TokenBridge.depositERC20To( - l1TokenNonRebasable.address, - l2TokenNonRebasable.address, - ethers.constants.AddressZero, - wei`1 ether`, - wei`1 gwei`, - "0x" - ), - "ErrorAccountIsZeroAddress()" - ); - await assert.revertsWith( - l1TokenBridge.depositERC20To( - l1TokenRebasable.address, - l2TokenRebasable.address, - ethers.constants.AddressZero, - wei`1 ether`, - wei`1 gwei`, - "0x" - ), - "ErrorAccountIsZeroAddress()" - ); - }) - - .test("depositERC20To() :: non rebasable token flow", async (ctx) => { - const { - l1TokenBridge, - accounts: { deployer, l2TokenBridgeEOA, recipient }, - stubs: { l1TokenNonRebasable, l2TokenNonRebasable, l1Messenger }, - } = ctx; - - const l2Gas = wei`0.99 wei`; - const amount = wei`1 ether`; - const data = "0x"; - - await l1TokenNonRebasable.approve(l1TokenBridge.address, amount); - - const deployerBalanceBefore = await l1TokenNonRebasable.balanceOf(deployer.address); - const bridgeBalanceBefore = await l1TokenNonRebasable.balanceOf(l1TokenBridge.address); - - const tx = await l1TokenBridge.depositERC20To( - l1TokenNonRebasable.address, - l2TokenNonRebasable.address, - recipient.address, - amount, - l2Gas, - data - ); - - await assert.emits(l1TokenBridge, tx, "ERC20DepositInitiated", [ - l1TokenNonRebasable.address, - l2TokenNonRebasable.address, - deployer.address, - recipient.address, - amount, - data, - ]); - - await assert.emits(l1Messenger, tx, "SentMessage", [ - l2TokenBridgeEOA.address, - l1TokenBridge.address, - L2ERC20ExtendedTokensBridge__factory.createInterface().encodeFunctionData( - "finalizeDeposit", - [ - l1TokenNonRebasable.address, - l2TokenNonRebasable.address, - deployer.address, - recipient.address, - amount, - data, - ] - ), - 1, // message nonce - l2Gas, - ]); - - assert.equalBN( - await l1TokenNonRebasable.balanceOf(deployer.address), - deployerBalanceBefore.sub(amount) - ); - - assert.equalBN( - await l1TokenNonRebasable.balanceOf(l1TokenBridge.address), - bridgeBalanceBefore.add(amount) - ); - }) - - .test("depositERC20To() :: rebasable token flow", async (ctx) => { - const { - l1TokenBridge, - accounts: { deployer, l2TokenBridgeEOA, recipient }, - stubs: { l1TokenNonRebasable, l2TokenNonRebasable, l1TokenRebasable, l2TokenRebasable, l1Messenger }, - } = ctx; - - const l2Gas = wei`0.99 wei`; - const amount = wei`1 ether`; - const data = "0x"; - - const rate = await l1TokenNonRebasable.stEthPerToken(); - const decimalsStr = await l1TokenNonRebasable.decimals(); - const decimals = BigNumber.from(10).pow(decimalsStr); - - const amountWrapped = (wei.toBigNumber(amount)).mul(BigNumber.from(decimals)).div(rate); - - await l1TokenRebasable.approve(l1TokenBridge.address, amount); - - const deployerBalanceBefore = await l1TokenRebasable.balanceOf(deployer.address); - const bridgeBalanceBefore = await l1TokenNonRebasable.balanceOf(l1TokenBridge.address); - - const tx = await l1TokenBridge.depositERC20To( - l1TokenRebasable.address, - l2TokenRebasable.address, - recipient.address, - amount, - l2Gas, - data - ); - - const packedTokenRateAndTimestampData = await packedTokenRateAndTimestamp(ctx.provider, l1TokenNonRebasable); - const dataToReceive = ethers.utils.hexConcat([packedTokenRateAndTimestampData, data]); - - await assert.emits(l1TokenBridge, tx, "ERC20DepositInitiated", [ - l1TokenRebasable.address, - l2TokenRebasable.address, - deployer.address, - recipient.address, - amount, - dataToReceive, - ]); - - await assert.emits(l1Messenger, tx, "SentMessage", [ - l2TokenBridgeEOA.address, - l1TokenBridge.address, - L2ERC20ExtendedTokensBridge__factory.createInterface().encodeFunctionData( - "finalizeDeposit", - [ - l1TokenRebasable.address, - l2TokenRebasable.address, - deployer.address, - recipient.address, - amountWrapped, - dataToReceive, - ] - ), - 1, // message nonce - l2Gas, - ]); - - assert.equalBN( - await l1TokenRebasable.balanceOf(deployer.address), - deployerBalanceBefore.sub(amount) - ); - - assert.equalBN( - await l1TokenNonRebasable.balanceOf(l1TokenBridge.address), - bridgeBalanceBefore.add(amountWrapped) - ); - }) - - .test( - "finalizeERC20Withdrawal() :: withdrawals are disabled", - async (ctx) => { - const { - l1TokenBridge, - stubs: { l1TokenNonRebasable, l2TokenNonRebasable, l1TokenRebasable, l2TokenRebasable }, - accounts: { deployer, recipient, l2TokenBridgeEOA }, - } = ctx; - await l1TokenBridge.disableWithdrawals(); - - assert.isFalse(await l1TokenBridge.isWithdrawalsEnabled()); - - await assert.revertsWith( - l1TokenBridge - .connect(l2TokenBridgeEOA) - .finalizeERC20Withdrawal( - l1TokenNonRebasable.address, - l2TokenNonRebasable.address, - deployer.address, - recipient.address, - wei`1 ether`, - "0x" - ), - "ErrorWithdrawalsDisabled()" - ); - await assert.revertsWith( - l1TokenBridge - .connect(l2TokenBridgeEOA) - .finalizeERC20Withdrawal( - l1TokenRebasable.address, - l2TokenRebasable.address, - deployer.address, - recipient.address, - wei`1 ether`, - "0x" - ), - "ErrorWithdrawalsDisabled()" - ); - } - ) - - .test("finalizeERC20Withdrawal() :: wrong l1Token", async (ctx) => { - const { - l1TokenBridge, - stubs: { l2TokenNonRebasable, l2TokenRebasable }, - accounts: { deployer, recipient, l2TokenBridgeEOA, stranger }, - } = ctx; - - await assert.revertsWith( - l1TokenBridge - .connect(l2TokenBridgeEOA) - .finalizeERC20Withdrawal( - stranger.address, - l2TokenNonRebasable.address, - deployer.address, - recipient.address, - wei`1 ether`, - "0x" - ), - "ErrorUnsupportedL1Token()" - ); - - await assert.revertsWith( - l1TokenBridge - .connect(l2TokenBridgeEOA) - .finalizeERC20Withdrawal( - stranger.address, - l2TokenRebasable.address, - deployer.address, - recipient.address, - wei`1 ether`, - "0x" - ), - "ErrorUnsupportedL1Token()" - ); - }) - - .test("finalizeERC20Withdrawal() :: wrong l2Token", async (ctx) => { - const { - l1TokenBridge, - stubs: { l1TokenNonRebasable, l1TokenRebasable }, - accounts: { deployer, recipient, l2TokenBridgeEOA, stranger }, - } = ctx; - - await assert.revertsWith( - l1TokenBridge - .connect(l2TokenBridgeEOA) - .finalizeERC20Withdrawal( - l1TokenNonRebasable.address, - stranger.address, - deployer.address, - recipient.address, - wei`1 ether`, - "0x" - ), - "ErrorUnsupportedL2Token()" - ); - - await assert.revertsWith( - l1TokenBridge - .connect(l2TokenBridgeEOA) - .finalizeERC20Withdrawal( - l1TokenRebasable.address, - stranger.address, - deployer.address, - recipient.address, - wei`1 ether`, - "0x" - ), - "ErrorUnsupportedL2Token()" - ); - }) - - .test("finalizeERC20Withdrawal() :: unauthorized messenger", async (ctx) => { - const { - l1TokenBridge, - stubs: { l1TokenNonRebasable, l2TokenNonRebasable, l1TokenRebasable, l2TokenRebasable }, - accounts: { deployer, recipient, stranger }, - } = ctx; - - await assert.revertsWith( - l1TokenBridge - .connect(stranger) - .finalizeERC20Withdrawal( - l1TokenNonRebasable.address, - l2TokenNonRebasable.address, - deployer.address, - recipient.address, - wei`1 ether`, - "0x" - ), - "ErrorUnauthorizedMessenger()" - ); - await assert.revertsWith( - l1TokenBridge - .connect(stranger) - .finalizeERC20Withdrawal( - l1TokenRebasable.address, - l2TokenRebasable.address, - deployer.address, - recipient.address, - wei`1 ether`, - "0x" - ), - "ErrorUnauthorizedMessenger()" - ); - }) - - .test( - "finalizeERC20Withdrawal() :: wrong cross domain sender", - async (ctx) => { - const { - l1TokenBridge, - stubs: { l1TokenNonRebasable, l2TokenNonRebasable, l1TokenRebasable, l2TokenRebasable, l1Messenger }, - accounts: { deployer, recipient, stranger, l1MessengerStubAsEOA }, - } = ctx; - - await l1Messenger.setXDomainMessageSender(stranger.address); - - await assert.revertsWith( - l1TokenBridge - .connect(l1MessengerStubAsEOA) - .finalizeERC20Withdrawal( - l1TokenNonRebasable.address, - l2TokenNonRebasable.address, - deployer.address, - recipient.address, - wei`1 ether`, - "0x" - ), - "ErrorWrongCrossDomainSender()" - ); - await assert.revertsWith( - l1TokenBridge - .connect(l1MessengerStubAsEOA) - .finalizeERC20Withdrawal( - l1TokenRebasable.address, - l2TokenRebasable.address, - deployer.address, - recipient.address, - wei`1 ether`, - "0x" - ), - "ErrorWrongCrossDomainSender()" - ); - } - ) - - .test("finalizeERC20Withdrawal() :: non rebasable token flow", async (ctx) => { - const { - l1TokenBridge, - stubs: { l1TokenNonRebasable, l2TokenNonRebasable, l1Messenger }, - accounts: { deployer, recipient, l1MessengerStubAsEOA, l2TokenBridgeEOA }, - } = ctx; - - await l1Messenger.setXDomainMessageSender(l2TokenBridgeEOA.address); - - const bridgeBalanceBefore = await l1TokenNonRebasable.balanceOf(l1TokenBridge.address); - - const amount = wei`1 ether`; - const data = "0xdeadbeaf"; - - const tx = await l1TokenBridge - .connect(l1MessengerStubAsEOA) - .finalizeERC20Withdrawal( - l1TokenNonRebasable.address, - l2TokenNonRebasable.address, - deployer.address, - recipient.address, - amount, - data - ); - - await assert.emits(l1TokenBridge, tx, "ERC20WithdrawalFinalized", [ - l1TokenNonRebasable.address, - l2TokenNonRebasable.address, - deployer.address, - recipient.address, - amount, - data, - ]); - - assert.equalBN(await l1TokenNonRebasable.balanceOf(recipient.address), amount); - assert.equalBN( - await l1TokenNonRebasable.balanceOf(l1TokenBridge.address), - bridgeBalanceBefore.sub(amount) - ); - }) - - .test("finalizeERC20Withdrawal() :: rebasable token flow", async (ctx) => { - const { - l1TokenBridge, - stubs: { l1TokenRebasable, l2TokenRebasable, l1TokenNonRebasable, l2TokenNonRebasable, l1Messenger }, - accounts: { deployer, recipient, l1MessengerStubAsEOA, l2TokenBridgeEOA }, - } = ctx; - - const amount = wei`1 ether`; - const data = "0xdeadbeaf"; - const rate = await l1TokenNonRebasable.stEthPerToken(); - const decimalsStr = await l1TokenNonRebasable.decimals(); - const decimals = BigNumber.from(10).pow(decimalsStr); - - const amountUnwrapped = (wei.toBigNumber(amount)).mul(rate).div(BigNumber.from(decimals)); - const deployerBalanceBefore = await l1TokenRebasable.balanceOf(deployer.address); - - await l1Messenger.setXDomainMessageSender(l2TokenBridgeEOA.address); - - await l1TokenRebasable.transfer(l1TokenNonRebasable.address, wei`100 ether`); - - const bridgeBalanceBefore = await l1TokenRebasable.balanceOf(l1TokenBridge.address); - - const tx = await l1TokenBridge - .connect(l1MessengerStubAsEOA) - .finalizeERC20Withdrawal( - l1TokenRebasable.address, - l2TokenRebasable.address, - deployer.address, - recipient.address, - amount, - data - ); - - await assert.emits(l1TokenBridge, tx, "ERC20WithdrawalFinalized", [ - l1TokenRebasable.address, - l2TokenRebasable.address, - deployer.address, - recipient.address, - amountUnwrapped, - data, - ]); - - assert.equalBN(await l1TokenRebasable.balanceOf(recipient.address), amountUnwrapped); - assert.equalBN( - await l1TokenNonRebasable.balanceOf(l1TokenBridge.address), - bridgeBalanceBefore.sub(amount) - ); - }) - - .run(); - -async function ctxFactory() { - const [deployer, l2TokenBridgeEOA, stranger, recipient] = - await hre.ethers.getSigners(); - - const provider = await hre.ethers.provider; - - const l1MessengerStub = await new CrossDomainMessengerStub__factory( - deployer - ).deploy({ value: wei.toBigNumber(wei`1 ether`) }); - - const l1TokenRebasableStub = await new ERC20BridgedStub__factory(deployer).deploy( - "L1 Token Rebasable", - "L1R" - ); - - const l1TokenNonRebasableStub = await new ERC20WrapperStub__factory(deployer).deploy( - l1TokenRebasableStub.address, - "L1 Token Non Rebasable", - "L1NR" - ); - - const l2TokenNonRebasableStub = await new ERC20BridgedStub__factory(deployer).deploy( - "L2 Token Non Rebasable", - "L2NR" - ); - - const l2TokenRebasableStub = await new ERC20WrapperStub__factory(deployer).deploy( - l2TokenNonRebasableStub.address, - "L2 Token Rebasable", - "L2R" - ); - - const emptyContract = await new EmptyContractStub__factory(deployer).deploy({ - value: wei.toBigNumber(wei`1 ether`), - }); - const emptyContractAsEOA = await testing.impersonate(emptyContract.address); - - const l1MessengerStubAsEOA = await testing.impersonate( - l1MessengerStub.address - ); - - const l1TokenBridgeImpl = await new L1LidoTokensBridge__factory( - deployer - ).deploy( - l1MessengerStub.address, - l2TokenBridgeEOA.address, - l1TokenNonRebasableStub.address, - l1TokenRebasableStub.address, - l2TokenNonRebasableStub.address, - l2TokenRebasableStub.address - ); - - const l1TokenBridgeProxy = await new OssifiableProxy__factory( - deployer - ).deploy( - l1TokenBridgeImpl.address, - deployer.address, - l1TokenBridgeImpl.interface.encodeFunctionData("initialize", [ - deployer.address, - ]) - ); - - const l1TokenBridge = L1LidoTokensBridge__factory.connect( - l1TokenBridgeProxy.address, - deployer - ); - - await l1TokenNonRebasableStub.transfer(l1TokenBridge.address, wei`100 ether`); - await l1TokenRebasableStub.transfer(l1TokenBridge.address, wei`100 ether`); - - const roles = await Promise.all([ - l1TokenBridge.DEPOSITS_ENABLER_ROLE(), - l1TokenBridge.DEPOSITS_DISABLER_ROLE(), - l1TokenBridge.WITHDRAWALS_ENABLER_ROLE(), - l1TokenBridge.WITHDRAWALS_DISABLER_ROLE(), - ]); - - for (const role of roles) { - await l1TokenBridge.grantRole(role, deployer.address); - } - - await l1TokenBridge.enableDeposits(); - await l1TokenBridge.enableWithdrawals(); - - return { - provider: provider, - accounts: { - deployer, - stranger, - l2TokenBridgeEOA, - emptyContractAsEOA, - recipient, - l1MessengerStubAsEOA, - }, - stubs: { - l1TokenNonRebasable: l1TokenNonRebasableStub, - l1TokenRebasable: l1TokenRebasableStub, - l2TokenNonRebasable: l2TokenNonRebasableStub, - l2TokenRebasable: l2TokenRebasableStub, - l1Messenger: l1MessengerStub, - }, - l1TokenBridge, - }; -} - -async function packedTokenRateAndTimestamp(l1Provider: JsonRpcProvider, l1Token: ERC20WrapperStub) { - const stEthPerToken = await l1Token.stEthPerToken(); - const blockNumber = await l1Provider.getBlockNumber(); - const blockTimestamp = (await l1Provider.getBlock(blockNumber)).timestamp; - const stEthPerTokenStr = ethers.utils.hexZeroPad(stEthPerToken.toHexString(), 12); - const blockTimestampStr = ethers.utils.hexZeroPad(ethers.utils.hexlify(blockTimestamp), 5); - return ethers.utils.hexConcat([stEthPerTokenStr, blockTimestampStr]); -} diff --git a/test/optimism/L1LidoTokensBridge.unit.test.ts b/test/optimism/L1LidoTokensBridge.unit.test.ts new file mode 100644 index 00000000..15642120 --- /dev/null +++ b/test/optimism/L1LidoTokensBridge.unit.test.ts @@ -0,0 +1,1014 @@ +import { assert } from "chai"; +import hre, { ethers } from "hardhat"; +import { BigNumber } from "ethers"; +import { + ERC20BridgedStub__factory, + ERC20WrapperStub__factory, + L1LidoTokensBridge__factory, + L2ERC20ExtendedTokensBridge__factory, + OssifiableProxy__factory, + EmptyContractStub__factory, + ERC20WrapperStub +} from "../../typechain"; +import { JsonRpcProvider } from "@ethersproject/providers"; +import { CrossDomainMessengerStub__factory } from "../../typechain/factories/CrossDomainMessengerStub__factory"; +import testing, { unit } from "../../utils/testing"; +import { wei } from "../../utils/wei"; + +unit("Optimism :: L1LidoTokensBridge", ctxFactory) + + .test("initial state", async (ctx) => { + assert.equal(await ctx.l1TokenBridge.l2TokenBridge(), ctx.accounts.l2TokenBridgeEOA.address); + assert.equal(await ctx.l1TokenBridge.MESSENGER(), ctx.accounts.l1MessengerStubAsEOA._address); + assert.equal(await ctx.l1TokenBridge.L1_TOKEN_NON_REBASABLE(), ctx.stubs.l1TokenNonRebasable.address); + assert.equal(await ctx.l1TokenBridge.L1_TOKEN_REBASABLE(), ctx.stubs.l1TokenRebasable.address); + assert.equal(await ctx.l1TokenBridge.L2_TOKEN_NON_REBASABLE(), ctx.stubs.l2TokenNonRebasable.address); + assert.equal(await ctx.l1TokenBridge.L2_TOKEN_REBASABLE(), ctx.stubs.l2TokenRebasable.address); + }) + + .test("depositERC20() :: deposits disabled", async (ctx) => { + await ctx.l1TokenBridge.disableDeposits(); + + assert.isFalse(await ctx.l1TokenBridge.isDepositsEnabled()); + + await assert.revertsWith( + ctx.l1TokenBridge.depositERC20( + ctx.stubs.l1TokenNonRebasable.address, + ctx.stubs.l2TokenNonRebasable.address, + wei`1 ether`, + wei`1 gwei`, + "0x" + ), + "ErrorDepositsDisabled()" + ); + + await assert.revertsWith( + ctx.l1TokenBridge.depositERC20( + ctx.stubs.l1TokenRebasable.address, + ctx.stubs.l2TokenRebasable.address, + wei`1 ether`, + wei`1 gwei`, + "0x" + ), + "ErrorDepositsDisabled()" + ); + }) + + .test("depositERC20() :: wrong l1Token address", async (ctx) => { + await assert.revertsWith( + ctx.l1TokenBridge.depositERC20( + ctx.accounts.stranger.address, + ctx.stubs.l2TokenNonRebasable.address, + wei`1 ether`, + wei`1 gwei`, + "0x" + ), + "ErrorUnsupportedL1Token()" + ); + await assert.revertsWith( + ctx.l1TokenBridge.depositERC20( + ctx.accounts.stranger.address, + ctx.stubs.l2TokenRebasable.address, + wei`1 ether`, + wei`1 gwei`, + "0x" + ), + "ErrorUnsupportedL1Token()" + ); + }) + + .test("depositERC20() :: wrong l2Token address", async (ctx) => { + await assert.revertsWith( + ctx.l1TokenBridge.depositERC20( + ctx.stubs.l1TokenNonRebasable.address, + ctx.accounts.stranger.address, + wei`1 ether`, + wei`1 gwei`, + "0x" + ), + "ErrorUnsupportedL2Token()" + ); + await assert.revertsWith( + ctx.l1TokenBridge.depositERC20( + ctx.stubs.l1TokenRebasable.address, + ctx.accounts.stranger.address, + wei`1 ether`, + wei`1 gwei`, + "0x" + ), + "ErrorUnsupportedL2Token()" + ); + }) + + .test("depositERC20() :: wrong tokens combination", async (ctx) => { + await assert.revertsWith( + ctx.l1TokenBridge.depositERC20( + ctx.stubs.l1TokenRebasable.address, + ctx.stubs.l2TokenNonRebasable.address, + wei`1 ether`, + wei`1 gwei`, + "0x" + ), + "ErrorUnsupportedL1L2TokensPair()" + ); + await assert.revertsWith( + ctx.l1TokenBridge.depositERC20( + ctx.stubs.l1TokenNonRebasable.address, + ctx.stubs.l2TokenRebasable.address, + wei`1 ether`, + wei`1 gwei`, + "0x" + ), + "ErrorUnsupportedL1L2TokensPair()" + ); + }) + + .test("depositERC20() :: not from EOA", async (ctx) => { + await assert.revertsWith( + ctx.l1TokenBridge + .connect(ctx.accounts.emptyContractAsEOA) + .depositERC20( + ctx.stubs.l1TokenNonRebasable.address, + ctx.stubs.l2TokenNonRebasable.address, + wei`1 ether`, + wei`1 gwei`, + "0x" + ), + "ErrorSenderNotEOA()" + ); + await assert.revertsWith( + ctx.l1TokenBridge + .connect(ctx.accounts.emptyContractAsEOA) + .depositERC20( + ctx.stubs.l1TokenRebasable.address, + ctx.stubs.l2TokenRebasable.address, + wei`1 ether`, + wei`1 gwei`, + "0x" + ), + "ErrorSenderNotEOA()" + ); + }) + + .test("depositERC20() :: non-rebasable token flow", async (ctx) => { + const { + l1TokenBridge, + accounts: { deployer, l2TokenBridgeEOA }, + stubs: { l1TokenNonRebasable, l2TokenNonRebasable, l1Messenger }, + } = ctx; + + const l2Gas = wei`0.99 wei`; + const amount = wei`1 ether`; + const data = "0xdeadbeaf"; + + await l1TokenNonRebasable.approve(l1TokenBridge.address, amount); + + const deployerBalanceBefore = await l1TokenNonRebasable.balanceOf(deployer.address); + const bridgeBalanceBefore = await l1TokenNonRebasable.balanceOf(l1TokenBridge.address); + + const tx = await l1TokenBridge.depositERC20( + l1TokenNonRebasable.address, + l2TokenNonRebasable.address, + amount, + l2Gas, + data + ); + + const packedTokenRateAndTimestampData = await packedTokenRateAndTimestamp(ctx.provider, l1TokenNonRebasable); + const dataToReceive = ethers.utils.hexConcat([packedTokenRateAndTimestampData, data]); + + await assert.emits(l1TokenBridge, tx, "ERC20DepositInitiated", [ + l1TokenNonRebasable.address, + l2TokenNonRebasable.address, + deployer.address, + deployer.address, + amount, + dataToReceive, + ]); + + await assert.emits(l1Messenger, tx, "SentMessage", [ + l2TokenBridgeEOA.address, + l1TokenBridge.address, + L2ERC20ExtendedTokensBridge__factory.createInterface().encodeFunctionData( + "finalizeDeposit", + [ + l1TokenNonRebasable.address, + l2TokenNonRebasable.address, + deployer.address, + deployer.address, + amount, + dataToReceive, + ] + ), + 1, // message nonce + l2Gas, + ]); + + assert.equalBN( + await l1TokenNonRebasable.balanceOf(deployer.address), + deployerBalanceBefore.sub(amount) + ); + + assert.equalBN( + await l1TokenNonRebasable.balanceOf(l1TokenBridge.address), + bridgeBalanceBefore.add(amount) + ); + }) + + .test("depositERC20() :: rebasable token flow", async (ctx) => { + const { + l1TokenBridge, + accounts: { deployer, l2TokenBridgeEOA }, + stubs: { l1TokenRebasable, l2TokenRebasable, l1TokenNonRebasable, l1Messenger }, + } = ctx; + + const l2Gas = wei`0.99 wei`; + const amount = wei`1 ether`; + const data = "0xdeadbeaf"; + const rate = await l1TokenNonRebasable.stEthPerToken(); + const decimalsStr = await l1TokenNonRebasable.decimals(); + const decimals = BigNumber.from(10).pow(decimalsStr); + + const amountWrapped = (wei.toBigNumber(amount)).mul(BigNumber.from(decimals)).div(rate); + const deployerBalanceBefore = await l1TokenRebasable.balanceOf(deployer.address); + const bridgeBalanceBefore = await l1TokenNonRebasable.balanceOf(l1TokenBridge.address); + + await l1TokenRebasable.approve(l1TokenBridge.address, amount); + + const tx = await l1TokenBridge.depositERC20( + l1TokenRebasable.address, + l2TokenRebasable.address, + amount, + l2Gas, + data + ); + + const packedTokenRateAndTimestampData = await packedTokenRateAndTimestamp(ctx.provider, l1TokenNonRebasable); + const dataToReceive = ethers.utils.hexConcat([packedTokenRateAndTimestampData, data]); + + await assert.emits(l1TokenBridge, tx, "ERC20DepositInitiated", [ + l1TokenRebasable.address, + l2TokenRebasable.address, + deployer.address, + deployer.address, + amount, + dataToReceive, + ]); + + await assert.emits(l1Messenger, tx, "SentMessage", [ + l2TokenBridgeEOA.address, + l1TokenBridge.address, + L2ERC20ExtendedTokensBridge__factory.createInterface().encodeFunctionData( + "finalizeDeposit", + [ + l1TokenRebasable.address, + l2TokenRebasable.address, + deployer.address, + deployer.address, + amountWrapped, + dataToReceive, + ] + ), + 1, // message nonce + l2Gas, + ]); + + assert.equalBN( + await l1TokenRebasable.balanceOf(deployer.address), + deployerBalanceBefore.sub(amount) + ); + + assert.equalBN( + await l1TokenNonRebasable.balanceOf(l1TokenBridge.address), + bridgeBalanceBefore.add(amountWrapped) + ); + }) + + .test("depositERC20To() :: deposits disabled", async (ctx) => { + const { + l1TokenBridge, + stubs: { l1TokenNonRebasable, l2TokenNonRebasable, l1TokenRebasable, l2TokenRebasable }, + accounts: { recipient }, + } = ctx; + await l1TokenBridge.disableDeposits(); + + assert.isFalse(await l1TokenBridge.isDepositsEnabled()); + + await assert.revertsWith( + l1TokenBridge.depositERC20To( + l1TokenNonRebasable.address, + l2TokenNonRebasable.address, + recipient.address, + wei`1 ether`, + wei`1 gwei`, + "0x" + ), + "ErrorDepositsDisabled()" + ); + + await assert.revertsWith( + l1TokenBridge.depositERC20To( + l1TokenRebasable.address, + l2TokenRebasable.address, + recipient.address, + wei`1 ether`, + wei`1 gwei`, + "0x" + ), + "ErrorDepositsDisabled()" + ); + }) + + .test("depositERC20To() :: wrong l1Token address", async (ctx) => { + const { + l1TokenBridge, + stubs: { l2TokenNonRebasable, l2TokenRebasable }, + accounts: { recipient, stranger }, + } = ctx; + + await assert.revertsWith( + l1TokenBridge.depositERC20To( + stranger.address, + l2TokenNonRebasable.address, + recipient.address, + wei`1 ether`, + wei`1 gwei`, + "0x" + ), + "ErrorUnsupportedL1Token()" + ); + await assert.revertsWith( + l1TokenBridge.depositERC20To( + stranger.address, + l2TokenRebasable.address, + recipient.address, + wei`1 ether`, + wei`1 gwei`, + "0x" + ), + "ErrorUnsupportedL1Token()" + ); + }) + + .test("depositERC20To() :: wrong l2Token address", async (ctx) => { + const { + l1TokenBridge, + stubs: { l2TokenNonRebasable, l2TokenRebasable }, + accounts: { recipient, stranger }, + } = ctx; + + await assert.revertsWith( + l1TokenBridge.depositERC20To( + stranger.address, + l2TokenNonRebasable.address, + recipient.address, + wei`1 ether`, + wei`1 gwei`, + "0x" + ), + "ErrorUnsupportedL1Token()" + ); + await assert.revertsWith( + l1TokenBridge.depositERC20To( + stranger.address, + l2TokenRebasable.address, + recipient.address, + wei`1 ether`, + wei`1 gwei`, + "0x" + ), + "ErrorUnsupportedL1Token()" + ); + }) + + .test("depositERC20To() :: wrong tokens combination", async (ctx) => { + const { + l1TokenBridge, + stubs: { l1TokenNonRebasable, l1TokenRebasable, l2TokenNonRebasable, l2TokenRebasable }, + accounts: { recipient }, + } = ctx; + + await assert.revertsWith( + l1TokenBridge.depositERC20To( + l1TokenNonRebasable.address, + l2TokenRebasable.address, + recipient.address, + wei`1 ether`, + wei`1 gwei`, + "0x" + ), + "ErrorUnsupportedL1L2TokensPair()" + ); + await assert.revertsWith( + l1TokenBridge.depositERC20To( + l1TokenRebasable.address, + l2TokenNonRebasable.address, + recipient.address, + wei`1 ether`, + wei`1 gwei`, + "0x" + ), + "ErrorUnsupportedL1L2TokensPair()" + ); + }) + + .test("depositERC20To() :: recipient is zero address", async (ctx) => { + const { + l1TokenBridge, + stubs: { l1TokenNonRebasable, l2TokenNonRebasable, l1TokenRebasable, l2TokenRebasable } + } = ctx; + + await assert.revertsWith( + l1TokenBridge.depositERC20To( + l1TokenNonRebasable.address, + l2TokenNonRebasable.address, + ethers.constants.AddressZero, + wei`1 ether`, + wei`1 gwei`, + "0x" + ), + "ErrorAccountIsZeroAddress()" + ); + await assert.revertsWith( + l1TokenBridge.depositERC20To( + l1TokenRebasable.address, + l2TokenRebasable.address, + ethers.constants.AddressZero, + wei`1 ether`, + wei`1 gwei`, + "0x" + ), + "ErrorAccountIsZeroAddress()" + ); + }) + + .test("depositERC20To() :: non-rebasable token flow", async (ctx) => { + const { + l1TokenBridge, + accounts: { deployer, l2TokenBridgeEOA, recipient }, + stubs: { l1TokenNonRebasable, l2TokenNonRebasable, l1Messenger }, + } = ctx; + + const l2Gas = wei`0.99 wei`; + const amount = wei`1 ether`; + const data = "0x"; + + await l1TokenNonRebasable.approve(l1TokenBridge.address, amount); + + const deployerBalanceBefore = await l1TokenNonRebasable.balanceOf(deployer.address); + const bridgeBalanceBefore = await l1TokenNonRebasable.balanceOf(l1TokenBridge.address); + + const tx = await l1TokenBridge.depositERC20To( + l1TokenNonRebasable.address, + l2TokenNonRebasable.address, + recipient.address, + amount, + l2Gas, + data + ); + + const packedTokenRateAndTimestampData = await packedTokenRateAndTimestamp(ctx.provider, l1TokenNonRebasable); + const dataToReceive = ethers.utils.hexConcat([packedTokenRateAndTimestampData, data]); + + await assert.emits(l1TokenBridge, tx, "ERC20DepositInitiated", [ + l1TokenNonRebasable.address, + l2TokenNonRebasable.address, + deployer.address, + recipient.address, + amount, + dataToReceive, + ]); + + await assert.emits(l1Messenger, tx, "SentMessage", [ + l2TokenBridgeEOA.address, + l1TokenBridge.address, + L2ERC20ExtendedTokensBridge__factory.createInterface().encodeFunctionData( + "finalizeDeposit", + [ + l1TokenNonRebasable.address, + l2TokenNonRebasable.address, + deployer.address, + recipient.address, + amount, + dataToReceive, + ] + ), + 1, // message nonce + l2Gas, + ]); + + assert.equalBN( + await l1TokenNonRebasable.balanceOf(deployer.address), + deployerBalanceBefore.sub(amount) + ); + + assert.equalBN( + await l1TokenNonRebasable.balanceOf(l1TokenBridge.address), + bridgeBalanceBefore.add(amount) + ); + }) + + .test("depositERC20To() :: rebasable token flow", async (ctx) => { + const { + l1TokenBridge, + accounts: { deployer, l2TokenBridgeEOA, recipient }, + stubs: { l1TokenNonRebasable, l2TokenNonRebasable, l1TokenRebasable, l2TokenRebasable, l1Messenger }, + } = ctx; + + const l2Gas = wei`0.99 wei`; + const amount = wei`1 ether`; + const data = "0x"; + + const rate = await l1TokenNonRebasable.stEthPerToken(); + const decimalsStr = await l1TokenNonRebasable.decimals(); + const decimals = BigNumber.from(10).pow(decimalsStr); + + const amountWrapped = (wei.toBigNumber(amount)).mul(BigNumber.from(decimals)).div(rate); + + await l1TokenRebasable.approve(l1TokenBridge.address, amount); + + const deployerBalanceBefore = await l1TokenRebasable.balanceOf(deployer.address); + const bridgeBalanceBefore = await l1TokenNonRebasable.balanceOf(l1TokenBridge.address); + + const tx = await l1TokenBridge.depositERC20To( + l1TokenRebasable.address, + l2TokenRebasable.address, + recipient.address, + amount, + l2Gas, + data + ); + + const packedTokenRateAndTimestampData = await packedTokenRateAndTimestamp(ctx.provider, l1TokenNonRebasable); + const dataToReceive = ethers.utils.hexConcat([packedTokenRateAndTimestampData, data]); + + await assert.emits(l1TokenBridge, tx, "ERC20DepositInitiated", [ + l1TokenRebasable.address, + l2TokenRebasable.address, + deployer.address, + recipient.address, + amount, + dataToReceive, + ]); + + await assert.emits(l1Messenger, tx, "SentMessage", [ + l2TokenBridgeEOA.address, + l1TokenBridge.address, + L2ERC20ExtendedTokensBridge__factory.createInterface().encodeFunctionData( + "finalizeDeposit", + [ + l1TokenRebasable.address, + l2TokenRebasable.address, + deployer.address, + recipient.address, + amountWrapped, + dataToReceive, + ] + ), + 1, // message nonce + l2Gas, + ]); + + assert.equalBN( + await l1TokenRebasable.balanceOf(deployer.address), + deployerBalanceBefore.sub(amount) + ); + + assert.equalBN( + await l1TokenNonRebasable.balanceOf(l1TokenBridge.address), + bridgeBalanceBefore.add(amountWrapped) + ); + }) + + .test( + "finalizeERC20Withdrawal() :: withdrawals are disabled", + async (ctx) => { + const { + l1TokenBridge, + stubs: { l1TokenNonRebasable, l2TokenNonRebasable, l1TokenRebasable, l2TokenRebasable }, + accounts: { deployer, recipient, l2TokenBridgeEOA }, + } = ctx; + await l1TokenBridge.disableWithdrawals(); + + assert.isFalse(await l1TokenBridge.isWithdrawalsEnabled()); + + await assert.revertsWith( + l1TokenBridge + .connect(l2TokenBridgeEOA) + .finalizeERC20Withdrawal( + l1TokenNonRebasable.address, + l2TokenNonRebasable.address, + deployer.address, + recipient.address, + wei`1 ether`, + "0x" + ), + "ErrorWithdrawalsDisabled()" + ); + await assert.revertsWith( + l1TokenBridge + .connect(l2TokenBridgeEOA) + .finalizeERC20Withdrawal( + l1TokenRebasable.address, + l2TokenRebasable.address, + deployer.address, + recipient.address, + wei`1 ether`, + "0x" + ), + "ErrorWithdrawalsDisabled()" + ); + } + ) + + .test("finalizeERC20Withdrawal() :: wrong l1Token", async (ctx) => { + const { + l1TokenBridge, + stubs: { l2TokenNonRebasable, l2TokenRebasable, l1Messenger }, + accounts: { deployer, recipient, l1MessengerStubAsEOA, stranger, l2TokenBridgeEOA }, + } = ctx; + + await l1Messenger.setXDomainMessageSender(l2TokenBridgeEOA.address); + + await assert.revertsWith( + l1TokenBridge + .connect(l1MessengerStubAsEOA) + .finalizeERC20Withdrawal( + stranger.address, + l2TokenNonRebasable.address, + deployer.address, + recipient.address, + wei`1 ether`, + "0x" + ), + "ErrorUnsupportedL1Token()" + ); + + await assert.revertsWith( + l1TokenBridge + .connect(l1MessengerStubAsEOA) + .finalizeERC20Withdrawal( + stranger.address, + l2TokenRebasable.address, + deployer.address, + recipient.address, + wei`1 ether`, + "0x" + ), + "ErrorUnsupportedL1Token()" + ); + }) + + .test("finalizeERC20Withdrawal() :: wrong l2Token", async (ctx) => { + const { + l1TokenBridge, + stubs: { l1TokenNonRebasable, l1TokenRebasable, l1Messenger }, + accounts: { deployer, recipient, l2TokenBridgeEOA, l1MessengerStubAsEOA, stranger }, + } = ctx; + + await l1Messenger.setXDomainMessageSender(l2TokenBridgeEOA.address); + + await assert.revertsWith( + l1TokenBridge + .connect(l1MessengerStubAsEOA) + .finalizeERC20Withdrawal( + l1TokenNonRebasable.address, + stranger.address, + deployer.address, + recipient.address, + wei`1 ether`, + "0x" + ), + "ErrorUnsupportedL2Token()" + ); + + await assert.revertsWith( + l1TokenBridge + .connect(l1MessengerStubAsEOA) + .finalizeERC20Withdrawal( + l1TokenRebasable.address, + stranger.address, + deployer.address, + recipient.address, + wei`1 ether`, + "0x" + ), + "ErrorUnsupportedL2Token()" + ); + }) + + .test("finalizeERC20Withdrawal() :: wrong token combination", async (ctx) => { + const { + l1TokenBridge, + stubs: { l1TokenNonRebasable, l1TokenRebasable, l2TokenNonRebasable, l2TokenRebasable, l1Messenger }, + accounts: { deployer, recipient, l2TokenBridgeEOA, l1MessengerStubAsEOA }, + } = ctx; + await l1Messenger.setXDomainMessageSender(l2TokenBridgeEOA.address); + + await assert.revertsWith( + l1TokenBridge + .connect(l1MessengerStubAsEOA) + .finalizeERC20Withdrawal( + l1TokenNonRebasable.address, + l2TokenRebasable.address, + deployer.address, + recipient.address, + wei`1 ether`, + "0x" + ), + "ErrorUnsupportedL1L2TokensPair()" + ); + + await assert.revertsWith( + l1TokenBridge + .connect(l1MessengerStubAsEOA) + .finalizeERC20Withdrawal( + l1TokenRebasable.address, + l2TokenNonRebasable.address, + deployer.address, + recipient.address, + wei`1 ether`, + "0x" + ), + "ErrorUnsupportedL1L2TokensPair()" + ); + }) + + .test("finalizeERC20Withdrawal() :: unauthorized messenger", async (ctx) => { + const { + l1TokenBridge, + stubs: { l1TokenNonRebasable, l2TokenNonRebasable, l1TokenRebasable, l2TokenRebasable }, + accounts: { deployer, recipient, stranger }, + } = ctx; + + await assert.revertsWith( + l1TokenBridge + .connect(stranger) + .finalizeERC20Withdrawal( + l1TokenNonRebasable.address, + l2TokenNonRebasable.address, + deployer.address, + recipient.address, + wei`1 ether`, + "0x" + ), + "ErrorUnauthorizedMessenger()" + ); + await assert.revertsWith( + l1TokenBridge + .connect(stranger) + .finalizeERC20Withdrawal( + l1TokenRebasable.address, + l2TokenRebasable.address, + deployer.address, + recipient.address, + wei`1 ether`, + "0x" + ), + "ErrorUnauthorizedMessenger()" + ); + }) + + .test("finalizeERC20Withdrawal() :: wrong cross domain sender", async (ctx) => { + const { + l1TokenBridge, + stubs: { l1TokenNonRebasable, l2TokenNonRebasable, l1TokenRebasable, l2TokenRebasable, l1Messenger }, + accounts: { deployer, recipient, stranger, l1MessengerStubAsEOA }, + } = ctx; + + await l1Messenger.setXDomainMessageSender(stranger.address); + + await assert.revertsWith( + l1TokenBridge + .connect(l1MessengerStubAsEOA) + .finalizeERC20Withdrawal( + l1TokenNonRebasable.address, + l2TokenNonRebasable.address, + deployer.address, + recipient.address, + wei`1 ether`, + "0x" + ), + "ErrorWrongCrossDomainSender()" + ); + await assert.revertsWith( + l1TokenBridge + .connect(l1MessengerStubAsEOA) + .finalizeERC20Withdrawal( + l1TokenRebasable.address, + l2TokenRebasable.address, + deployer.address, + recipient.address, + wei`1 ether`, + "0x" + ), + "ErrorWrongCrossDomainSender()" + ); + } + ) + + .test("finalizeERC20Withdrawal() :: non rebasable token flow", async (ctx) => { + const { + l1TokenBridge, + stubs: { l1TokenNonRebasable, l2TokenNonRebasable, l1Messenger }, + accounts: { deployer, recipient, l1MessengerStubAsEOA, l2TokenBridgeEOA }, + } = ctx; + + await l1Messenger.setXDomainMessageSender(l2TokenBridgeEOA.address); + + const bridgeBalanceBefore = await l1TokenNonRebasable.balanceOf(l1TokenBridge.address); + + const amount = wei`1 ether`; + const data = "0xdeadbeaf"; + + const tx = await l1TokenBridge + .connect(l1MessengerStubAsEOA) + .finalizeERC20Withdrawal( + l1TokenNonRebasable.address, + l2TokenNonRebasable.address, + deployer.address, + recipient.address, + amount, + data + ); + + await assert.emits(l1TokenBridge, tx, "ERC20WithdrawalFinalized", [ + l1TokenNonRebasable.address, + l2TokenNonRebasable.address, + deployer.address, + recipient.address, + amount, + data, + ]); + + assert.equalBN(await l1TokenNonRebasable.balanceOf(recipient.address), amount); + assert.equalBN( + await l1TokenNonRebasable.balanceOf(l1TokenBridge.address), + bridgeBalanceBefore.sub(amount) + ); + }) + + .test("finalizeERC20Withdrawal() :: rebasable token flow", async (ctx) => { + const { + l1TokenBridge, + stubs: { l1TokenRebasable, l2TokenRebasable, l1TokenNonRebasable, l2TokenNonRebasable, l1Messenger }, + accounts: { deployer, recipient, l1MessengerStubAsEOA, l2TokenBridgeEOA }, + } = ctx; + + const amount = wei`1 ether`; + const data = "0xdeadbeaf"; + const rate = await l1TokenNonRebasable.stEthPerToken(); + const decimalsStr = await l1TokenNonRebasable.decimals(); + const decimals = BigNumber.from(10).pow(decimalsStr); + + const amountUnwrapped = (wei.toBigNumber(amount)).mul(rate).div(BigNumber.from(decimals)); + const deployerBalanceBefore = await l1TokenRebasable.balanceOf(deployer.address); + + await l1Messenger.setXDomainMessageSender(l2TokenBridgeEOA.address); + + await l1TokenRebasable.transfer(l1TokenNonRebasable.address, wei`100 ether`); + + const bridgeBalanceBefore = await l1TokenRebasable.balanceOf(l1TokenBridge.address); + + const tx = await l1TokenBridge + .connect(l1MessengerStubAsEOA) + .finalizeERC20Withdrawal( + l1TokenRebasable.address, + l2TokenRebasable.address, + deployer.address, + recipient.address, + amount, + data + ); + + await assert.emits(l1TokenBridge, tx, "ERC20WithdrawalFinalized", [ + l1TokenRebasable.address, + l2TokenRebasable.address, + deployer.address, + recipient.address, + amountUnwrapped, + data, + ]); + + assert.equalBN(await l1TokenRebasable.balanceOf(recipient.address), amountUnwrapped); + assert.equalBN( + await l1TokenNonRebasable.balanceOf(l1TokenBridge.address), + bridgeBalanceBefore.sub(amount) + ); + }) + + .run(); + +async function ctxFactory() { + const [deployer, l2TokenBridgeEOA, stranger, recipient] = + await hre.ethers.getSigners(); + + const provider = await hre.ethers.provider; + + const l1MessengerStub = await new CrossDomainMessengerStub__factory( + deployer + ).deploy({ value: wei.toBigNumber(wei`1 ether`) }); + + const l1TokenRebasableStub = await new ERC20BridgedStub__factory(deployer).deploy( + "L1 Token Rebasable", + "L1R" + ); + + const l1TokenNonRebasableStub = await new ERC20WrapperStub__factory(deployer).deploy( + l1TokenRebasableStub.address, + "L1 Token Non Rebasable", + "L1NR" + ); + + const l2TokenNonRebasableStub = await new ERC20BridgedStub__factory(deployer).deploy( + "L2 Token Non Rebasable", + "L2NR" + ); + + const l2TokenRebasableStub = await new ERC20WrapperStub__factory(deployer).deploy( + l2TokenNonRebasableStub.address, + "L2 Token Rebasable", + "L2R" + ); + + const emptyContract = await new EmptyContractStub__factory(deployer).deploy({ + value: wei.toBigNumber(wei`1 ether`), + }); + const emptyContractAsEOA = await testing.impersonate(emptyContract.address); + + const l1MessengerStubAsEOA = await testing.impersonate( + l1MessengerStub.address + ); + + const l1TokenBridgeImpl = await new L1LidoTokensBridge__factory( + deployer + ).deploy( + l1MessengerStub.address, + l2TokenBridgeEOA.address, + l1TokenNonRebasableStub.address, + l1TokenRebasableStub.address, + l2TokenNonRebasableStub.address, + l2TokenRebasableStub.address + ); + + const l1TokenBridgeProxy = await new OssifiableProxy__factory( + deployer + ).deploy( + l1TokenBridgeImpl.address, + deployer.address, + l1TokenBridgeImpl.interface.encodeFunctionData("initialize", [ + deployer.address + ]) + ); + + const l1TokenBridge = L1LidoTokensBridge__factory.connect( + l1TokenBridgeProxy.address, + deployer + ); + + await l1TokenNonRebasableStub.transfer(l1TokenBridge.address, wei`100 ether`); + await l1TokenRebasableStub.transfer(l1TokenBridge.address, wei`100 ether`); + + const roles = await Promise.all([ + l1TokenBridge.DEPOSITS_ENABLER_ROLE(), + l1TokenBridge.DEPOSITS_DISABLER_ROLE(), + l1TokenBridge.WITHDRAWALS_ENABLER_ROLE(), + l1TokenBridge.WITHDRAWALS_DISABLER_ROLE(), + ]); + + for (const role of roles) { + await l1TokenBridge.grantRole(role, deployer.address); + } + + await l1TokenBridge.enableDeposits(); + await l1TokenBridge.enableWithdrawals(); + + return { + provider: provider, + accounts: { + deployer, + stranger, + l2TokenBridgeEOA, + emptyContractAsEOA, + recipient, + l1MessengerStubAsEOA, + }, + stubs: { + l1TokenNonRebasable: l1TokenNonRebasableStub, + l1TokenRebasable: l1TokenRebasableStub, + l2TokenNonRebasable: l2TokenNonRebasableStub, + l2TokenRebasable: l2TokenRebasableStub, + l1Messenger: l1MessengerStub, + }, + l1TokenBridge, + }; +} + +async function packedTokenRateAndTimestamp(l1Provider: JsonRpcProvider, l1Token: ERC20WrapperStub) { + const stEthPerToken = await l1Token.stEthPerToken(); + const blockNumber = await l1Provider.getBlockNumber(); + const blockTimestamp = (await l1Provider.getBlock(blockNumber)).timestamp; + const stEthPerTokenStr = ethers.utils.hexZeroPad(stEthPerToken.toHexString(), 12); + const blockTimestampStr = ethers.utils.hexZeroPad(ethers.utils.hexlify(blockTimestamp), 5); + return ethers.utils.hexConcat([stEthPerTokenStr, blockTimestampStr]); +} diff --git a/test/optimism/L2ERC20TokenBridge.unit.test.ts b/test/optimism/L2ERC20ExtendedTokensBridge.unit.test.ts similarity index 91% rename from test/optimism/L2ERC20TokenBridge.unit.test.ts rename to test/optimism/L2ERC20ExtendedTokensBridge.unit.test.ts index 5ea2dc66..d84ff111 100644 --- a/test/optimism/L2ERC20TokenBridge.unit.test.ts +++ b/test/optimism/L2ERC20ExtendedTokensBridge.unit.test.ts @@ -19,11 +19,13 @@ import { JsonRpcProvider } from "@ethersproject/providers"; import { BigNumber } from "ethers"; unit("Optimism:: L2ERC20ExtendedTokensBridge", ctxFactory) - .test("l1TokenBridge()", async (ctx) => { - assert.equal( - await ctx.l2TokenBridge.l1TokenBridge(), - ctx.accounts.l1TokenBridgeEOA.address - ); + .test("initial state", async (ctx) => { + assert.equal(await ctx.l2TokenBridge.l1TokenBridge(), ctx.accounts.l1TokenBridgeEOA.address); + assert.equal(await ctx.l2TokenBridge.MESSENGER(), ctx.accounts.l2MessengerStubEOA._address); + assert.equal(await ctx.l2TokenBridge.L1_TOKEN_NON_REBASABLE(), ctx.stubs.l1TokenNonRebasable.address); + assert.equal(await ctx.l2TokenBridge.L1_TOKEN_REBASABLE(), ctx.stubs.l1TokenRebasable.address); + assert.equal(await ctx.l2TokenBridge.L2_TOKEN_NON_REBASABLE(), ctx.stubs.l2TokenNonRebasable.address); + assert.equal(await ctx.l2TokenBridge.L2_TOKEN_REBASABLE(), ctx.stubs.l2TokenRebasable.address); }) .test("withdraw() :: withdrawals disabled", async (ctx) => { @@ -68,7 +70,7 @@ unit("Optimism:: L2ERC20ExtendedTokensBridge", ctxFactory) ); }) - .test("withdraw() :: non rebasable token flow", async (ctx) => { + .test("withdraw() :: non-rebasable token flow", async (ctx) => { const { l2TokenBridge, accounts: { deployer, l1TokenBridgeEOA }, @@ -137,8 +139,6 @@ unit("Optimism:: L2ERC20ExtendedTokensBridge", ctxFactory) accounts: { deployer, l1TokenBridgeEOA, l2MessengerStubEOA, recipient }, stubs: { l2Messenger, - l1TokenNonRebasable, - l2TokenNonRebasable, l1TokenRebasable, l2TokenRebasable }, @@ -514,6 +514,41 @@ unit("Optimism:: L2ERC20ExtendedTokensBridge", ctxFactory) ); }) + .test("finalizeDeposit() :: unsupported tokens combination", async (ctx) => { + const { + l2TokenBridge, + accounts: { l2MessengerStubEOA, deployer, recipient, stranger }, + stubs: { l1TokenNonRebasable, l1TokenRebasable, l2TokenNonRebasable, l2TokenRebasable }, + } = ctx; + + await assert.revertsWith( + l2TokenBridge + .connect(l2MessengerStubEOA) + .finalizeDeposit( + l1TokenNonRebasable.address, + l2TokenRebasable.address, + deployer.address, + recipient.address, + wei`1 ether`, + "0x" + ), + "ErrorUnsupportedL1L2TokensPair()" + ); + await assert.revertsWith( + l2TokenBridge + .connect(l2MessengerStubEOA) + .finalizeDeposit( + l1TokenRebasable.address, + l2TokenNonRebasable.address, + deployer.address, + recipient.address, + wei`1 ether`, + "0x" + ), + "ErrorUnsupportedL1L2TokensPair()" + ); + }) + .test("finalizeDeposit() :: unauthorized messenger", async (ctx) => { const { l2TokenBridge, @@ -587,7 +622,7 @@ unit("Optimism:: L2ERC20ExtendedTokensBridge", ctxFactory) ); }) - .test("finalizeDeposit() :: non rebasable token flow", async (ctx) => { + .test("finalizeDeposit() :: non-rebasable token flow", async (ctx) => { const { l2TokenBridge, stubs: { l1TokenNonRebasable, l2TokenNonRebasable, l2Messenger }, @@ -600,6 +635,9 @@ unit("Optimism:: L2ERC20ExtendedTokensBridge", ctxFactory) const amount = wei`1 ether`; const data = "0xdeadbeaf"; + const provider = await hre.ethers.provider; + const packedTokenRateAndTimestampData = await packedTokenRateAndTimestamp(provider, ctx.exchangeRate); + const dataToReceive = ethers.utils.hexConcat([packedTokenRateAndTimestampData, data]); const tx = await l2TokenBridge .connect(l2MessengerStubEOA) @@ -609,7 +647,7 @@ unit("Optimism:: L2ERC20ExtendedTokensBridge", ctxFactory) deployer.address, recipient.address, amount, - data + dataToReceive ); await assert.emits(l2TokenBridge, tx, "DepositFinalized", [ diff --git a/test/optimism/bridging-rebasable.integration.test.ts b/test/optimism/bridging-rebasable.integration.test.ts index fbe208ae..905b6b36 100644 --- a/test/optimism/bridging-rebasable.integration.test.ts +++ b/test/optimism/bridging-rebasable.integration.test.ts @@ -5,11 +5,10 @@ import { wei } from "../../utils/wei"; import optimism from "../../utils/optimism"; import testing, { scenario } from "../../utils/testing"; import { ethers } from "hardhat"; -import { BigNumber } from "ethers"; import { JsonRpcProvider } from "@ethersproject/providers"; import { ERC20WrapperStub } from "../../typechain"; -scenario("Optimism :: Bridging integration test", ctxFactory) +scenario("Optimism :: Bridging rebasable token integration test", ctxFactory) .after(async (ctx) => { await ctx.l1Provider.send("evm_revert", [ctx.snapshot.l1]); await ctx.l2Provider.send("evm_revert", [ctx.snapshot.l2]); diff --git a/test/optimism/bridging.integration.test.ts b/test/optimism/bridging.integration.test.ts index 6cc2a509..9ec87c7d 100644 --- a/test/optimism/bridging.integration.test.ts +++ b/test/optimism/bridging.integration.test.ts @@ -4,8 +4,11 @@ import env from "../../utils/env"; import { wei } from "../../utils/wei"; import optimism from "../../utils/optimism"; import testing, { scenario } from "../../utils/testing"; +import { ethers } from "hardhat"; +import { JsonRpcProvider } from "@ethersproject/providers"; +import { ERC20WrapperStub } from "../../typechain"; -scenario("Optimism :: Bridging integration test", ctxFactory) +scenario("Optimism :: Bridging non-rebasable token integration test", ctxFactory) .after(async (ctx) => { await ctx.l1Provider.send("evm_revert", [ctx.snapshot.l1]); await ctx.l2Provider.send("evm_revert", [ctx.snapshot.l2]); @@ -101,13 +104,15 @@ scenario("Optimism :: Bridging integration test", ctxFactory) "0x" ); + const dataToSend = await packedTokenRateAndTimestamp(ctx.l1Provider, l1Token); + await assert.emits(l1LidoTokensBridge, tx, "ERC20DepositInitiated", [ l1Token.address, l2Token.address, tokenHolderA.address, tokenHolderA.address, depositAmount, - "0x", + dataToSend, ]); const l2DepositCalldata = l2ERC20ExtendedTokensBridge.interface.encodeFunctionData( @@ -118,7 +123,7 @@ scenario("Optimism :: Bridging integration test", ctxFactory) tokenHolderA.address, tokenHolderA.address, depositAmount, - "0x", + dataToSend, ] ); @@ -159,6 +164,7 @@ scenario("Optimism :: Bridging integration test", ctxFactory) tokenHolderA.address ); const l2TokenTotalSupplyBefore = await l2Token.totalSupply(); + const dataToReceive = await packedTokenRateAndTimestamp(ctx.l2Provider, l1Token); const tx = await l2CrossDomainMessenger .connect(l1CrossDomainMessengerAliased) @@ -174,7 +180,7 @@ scenario("Optimism :: Bridging integration test", ctxFactory) tokenHolderA.address, tokenHolderA.address, depositAmount, - "0x", + dataToReceive, ]), { gasLimit: 5_000_000 } ); @@ -326,13 +332,15 @@ scenario("Optimism :: Bridging integration test", ctxFactory) "0x" ); + const dataToSend = await packedTokenRateAndTimestamp(ctx.l1Provider, l1Token); + await assert.emits(l1LidoTokensBridge, tx, "ERC20DepositInitiated", [ l1Token.address, l2Token.address, tokenHolderA.address, tokenHolderB.address, depositAmount, - "0x", + dataToSend, ]); const l2DepositCalldata = l2ERC20ExtendedTokensBridge.interface.encodeFunctionData( @@ -343,7 +351,7 @@ scenario("Optimism :: Bridging integration test", ctxFactory) tokenHolderA.address, tokenHolderB.address, depositAmount, - "0x", + dataToSend, ] ); @@ -388,6 +396,8 @@ scenario("Optimism :: Bridging integration test", ctxFactory) tokenHolderB.address ); + const dataToReceive = await packedTokenRateAndTimestamp(ctx.l2Provider, l1Token); + const tx = await l2CrossDomainMessenger .connect(l1CrossDomainMessengerAliased) .relayMessage( @@ -402,7 +412,7 @@ scenario("Optimism :: Bridging integration test", ctxFactory) tokenHolderA.address, tokenHolderB.address, depositAmount, - "0x", + dataToReceive, ]), { gasLimit: 5_000_000 } ); @@ -611,3 +621,12 @@ async function ctxFactory() { }, }; } + +async function packedTokenRateAndTimestamp(l1Provider: JsonRpcProvider, l1Token: ERC20WrapperStub) { + const stEthPerToken = await l1Token.stEthPerToken(); + const blockNumber = await l1Provider.getBlockNumber(); + const blockTimestamp = (await l1Provider.getBlock(blockNumber)).timestamp; + const stEthPerTokenStr = ethers.utils.hexZeroPad(stEthPerToken.toHexString(), 12); + const blockTimestampStr = ethers.utils.hexZeroPad(ethers.utils.hexlify(blockTimestamp), 5); + return ethers.utils.hexConcat([stEthPerTokenStr, blockTimestampStr]); +} From bc1c56fbb5b539348c73a41650a2db90660fad61 Mon Sep 17 00:00:00 2001 From: Anton Kovalchuk Date: Mon, 15 Apr 2024 23:59:42 +0200 Subject: [PATCH 059/148] fix scripts --- .env.example | 5 +- .storage-layout | 8 +-- README.md | 4 +- artifacts-opt.json | 8 +-- contracts/arbitrum/L2ERC20TokenGateway.sol | 2 +- contracts/arbitrum/README.md | 28 ++++++++ scripts/optimism/deploy-new-impls.ts | 2 +- scripts/optimism/deploy-oracle.ts | 2 +- .../L2ERC20ExtendedTokensBridge.unit.test.ts | 31 +++++++++ test/optimism/TokenRateNotifier.unit.test.ts | 65 ++++++++++++++++--- test/optimism/TokenRateOracle.unit.test.ts | 4 +- test/token/ERC20Rebasable.unit.test.ts | 2 +- utils/deployment.ts | 4 +- utils/optimism/deploymentOracle.ts | 4 +- 14 files changed, 140 insertions(+), 29 deletions(-) diff --git a/.env.example b/.env.example index b683fcb0..3838caf4 100644 --- a/.env.example +++ b/.env.example @@ -35,10 +35,13 @@ REBASABLE_TOKEN= L1_OP_STACK_TOKEN_RATE_PUSHER= # Gas limit required to complete pushing token rate on L2. +# Default is: 300_000. +# This value was calculated by formula: +# l2GasLimit = (gas cost of L2Bridge.finalizeDeposit() + OptimismPortal.minimumGasLimit(depositData.length)) * 1.5 L2_GAS_LIMIT_FOR_PUSHING_TOKEN_RATE= # A time period when token rate can be considered outdated. -RATE_OUTDATED_DELAY=86400 # default is 24 hours +TOKEN_RATE_OUTDATED_DELAY=86400 # default is 86400 (24 hours) # Address of L1 token bridge proxy. L1_TOKEN_BRIDGE= diff --git a/.storage-layout b/.storage-layout index 393bb17e..e7f10d83 100644 --- a/.storage-layout +++ b/.storage-layout @@ -58,12 +58,12 @@ |------|------|------|--------|-------|----------| ======================= -➡ L1ERC20TokenBridge +➡ L1ERC20ExtendedTokensBridge ======================= | Name | Type | Slot | Offset | Bytes | Contract | |--------|---------------------------------------------------|------|--------|-------|--------------------------------------------------------------| -| _roles | mapping(bytes32 => struct AccessControl.RoleData) | 0 | 0 | 32 | contracts/optimism/L1ERC20TokenBridge.sol:L1ERC20TokenBridge | +| _roles | mapping(bytes32 => struct AccessControl.RoleData) | 0 | 0 | 32 | contracts/optimism/L1ERC20ExtendedTokensBridge.sol:L1ERC20ExtendedTokensBridge | ======================= ➡ L1ERC20TokenGateway @@ -81,12 +81,12 @@ |------|------|------|--------|-------|----------| ======================= -➡ L2ERC20TokenBridge +➡ L2ERC20ExtendedTokensBridge ======================= | Name | Type | Slot | Offset | Bytes | Contract | |--------|---------------------------------------------------|------|--------|-------|--------------------------------------------------------------| -| _roles | mapping(bytes32 => struct AccessControl.RoleData) | 0 | 0 | 32 | contracts/optimism/L2ERC20TokenBridge.sol:L2ERC20TokenBridge | +| _roles | mapping(bytes32 => struct AccessControl.RoleData) | 0 | 0 | 32 | contracts/optimism/L2ERC20ExtendedTokensBridge.sol:L2ERC20ExtendedTokensBridge | ======================= ➡ L2ERC20TokenGateway diff --git a/README.md b/README.md index caf32a0d..ce8ce02d 100644 --- a/README.md +++ b/README.md @@ -46,8 +46,8 @@ The configuration of the deployment scripts happens via the ENV variables. The f - [`TOKEN`](#TOKEN) - address of the non-rebasable token to deploy a new bridge on the Ethereum chain. - [`REBASABLE_TOKEN`] (#REBASABLE_TOKEN) - address of the rebasable token to deploy new bridge on the Ethereum chain. - [`L1_OP_STACK_TOKEN_RATE_PUSHER`](#L1_OP_STACK_TOKEN_RATE_PUSHER) - address of token rate pusher. Required to config TokenRateOracle. -- [`L2_GAS_LIMIT_FOR_PUSHING_TOKEN_RATE`](#L2_GAS_LIMIT_FOR_PUSHING_TOKEN_RATE) - gas limit required to complete pushing token rate on L2. -- [`RATE_OUTDATED_DELAY`](#RATE_OUTDATED_DELAY) - a time period when token rate can be considered outdated. Default is 24 hours. +- [`L2_GAS_LIMIT_FOR_PUSHING_TOKEN_RATE`](#L2_GAS_LIMIT_FOR_PUSHING_TOKEN_RATE) - gas limit required to complete pushing token rate on L2.This value was calculated by formula: l2GasLimit = (gas cost of L2Bridge.finalizeDeposit() + OptimismPortal.minimumGasLimit(depositData.length)) * 1.5 +- [`TOKEN_RATE_OUTDATED_DELAY`](#TOKEN_RATE_OUTDATED_DELAY) - a time period when token rate can be considered outdated. Default is 86400 (24 hours). - [`L1_TOKEN_BRIDGE`](#L1_TOKEN_BRIDGE) - address of L1 token bridge. - [`L2_TOKEN_BRIDGE`](#L2_TOKEN_BRIDGE) - address of L2 token bridge. - [`L2_TOKEN`](#L2_TOKEN) - address of the non-rebasable token on L2. diff --git a/artifacts-opt.json b/artifacts-opt.json index b97c0d24..7a302e37 100644 --- a/artifacts-opt.json +++ b/artifacts-opt.json @@ -2,13 +2,13 @@ { "artifactPath": "artifacts/contracts/proxy/OssifiableProxy.sol/OssifiableProxy.json", "sourcePath": "contracts/proxy/OssifiableProxy.sol", - "name": "L2ERC20TokenBridge proxy", + "name": "L2ERC20ExtendedTokensBridge proxy", "address": "0x8E01013243a96601a86eb3153F0d9Fa4fbFb6957" }, { - "artifactPath": "artifacts/contracts/optimism/L2ERC20TokenBridge.sol/L2ERC20TokenBridge.json", - "sourcePath": "contracts/optimism/L2ERC20TokenBridge.sol", - "name": "L2ERC20TokenBridge", + "artifactPath": "artifacts/contracts/optimism/L2ERC20ExtendedTokensBridge.sol/L2ERC20ExtendedTokensBridge.json", + "sourcePath": "contracts/optimism/L2ERC20ExtendedTokensBridge.sol", + "name": "L2ERC20ExtendedTokensBridge", "address": "0x23B96aDD54c479C6784Dd504670B5376B808f4C7", "txHash": "0x5d69e9c6ec1d634f0d90812c2189c925993d1fffbc9b0b416fdc123e15407c56" }, diff --git a/contracts/arbitrum/L2ERC20TokenGateway.sol b/contracts/arbitrum/L2ERC20TokenGateway.sol index 5853d0ac..f7850448 100644 --- a/contracts/arbitrum/L2ERC20TokenGateway.sol +++ b/contracts/arbitrum/L2ERC20TokenGateway.sol @@ -3,7 +3,7 @@ pragma solidity 0.8.10; -import {IERC20Bridged} from "../token/interfaces/IERC20Bridged.sol"; +import {IERC20Bridged} from "../token/ERC20Bridged.sol"; import {IL2TokenGateway, IInterchainTokenGateway} from "./interfaces/IL2TokenGateway.sol"; import {L2CrossDomainEnabled} from "./L2CrossDomainEnabled.sol"; diff --git a/contracts/arbitrum/README.md b/contracts/arbitrum/README.md index 26f3c4fb..4197694f 100644 --- a/contracts/arbitrum/README.md +++ b/contracts/arbitrum/README.md @@ -695,6 +695,34 @@ Returns a `bool` value indicating whether the operation succeeded. Transfers `amount` of token from the `from_` account to `to_` using the allowance mechanism. `amount_` is then deducted from the caller's allowance. Returns a `bool` value indicating whether the operation succeed. +#### `increaseAllowance(address,uint256)` + +> **Visibility:**     `external` +> +> **Returns**        `(bool)` +> +> **Arguments:** +> +> - **`spender_`** - an address of the tokens spender +> - **`addedValue_`** - a number to increase allowance +> +> **Emits:** `Approval(address indexed owner, address indexed spender, uint256 value)` +Atomically increases the allowance granted to `spender` by the caller. Returns a `bool` value indicating whether the operation succeed. + +#### `decreaseAllowance(address,uint256)` + +> **Visibility:**     `external` +> +> **Returns**        `(bool)` +> +> **Arguments:** +> +> - **`spender_`** - an address of the tokens spender +> - **`subtractedValue_`** - a number to decrease allowance +> +> **Emits:** `Approval(address indexed owner, address indexed spender, uint256 value)` +Atomically decreases the allowance granted to `spender` by the caller. Returns a `bool` value indicating whether the operation succeed. + ## `ERC20Bridged` **Implements:** [`IERC20Bridged`](https://github.com/lidofinance/lido-l2/blob/main/contracts/token/interfaces/IERC20Bridged.sol) diff --git a/scripts/optimism/deploy-new-impls.ts b/scripts/optimism/deploy-new-impls.ts index 2026c9c5..b28ea202 100644 --- a/scripts/optimism/deploy-new-impls.ts +++ b/scripts/optimism/deploy-new-impls.ts @@ -48,7 +48,7 @@ async function main() { tokenBridgeProxyAddress: deploymentConfig.l2TokenBridge, tokenProxyAddress: deploymentConfig.l2Token, tokenRateOracleProxyAddress: deploymentConfig.l2TokenRateOracle, - tokenRateOracleRateOutdatedDelay: deploymentConfig.rateOutdatedDelay, + tokenRateOracleRateOutdatedDelay: deploymentConfig.tokenRateOutdatedDelay, } ); diff --git a/scripts/optimism/deploy-oracle.ts b/scripts/optimism/deploy-oracle.ts index 7d75e0f3..08edc448 100644 --- a/scripts/optimism/deploy-oracle.ts +++ b/scripts/optimism/deploy-oracle.ts @@ -26,7 +26,7 @@ async function main() { .oracleDeployScript( deploymentConfig.l1Token, deploymentConfig.l2GasLimitForPushingTokenRate, - deploymentConfig.rateOutdatedDelay, + deploymentConfig.tokenRateOutdatedDelay, { deployer: ethDeployer, admins: { diff --git a/test/optimism/L2ERC20ExtendedTokensBridge.unit.test.ts b/test/optimism/L2ERC20ExtendedTokensBridge.unit.test.ts index d84ff111..53040ce7 100644 --- a/test/optimism/L2ERC20ExtendedTokensBridge.unit.test.ts +++ b/test/optimism/L2ERC20ExtendedTokensBridge.unit.test.ts @@ -70,6 +70,37 @@ unit("Optimism:: L2ERC20ExtendedTokensBridge", ctxFactory) ); }) + .test("withdraw() :: not from EOA", async (ctx) => { + const { + l2TokenBridge, + accounts: { emptyContractEOA }, + stubs: { l2TokenRebasable, l2TokenNonRebasable }, + } = ctx; + + await assert.revertsWith( + l2TokenBridge + .connect(emptyContractEOA) + .withdraw( + l2TokenNonRebasable.address, + wei`1 ether`, + wei`1 gwei`, + "0x" + ), + "ErrorSenderNotEOA()" + ); + await assert.revertsWith( + l2TokenBridge + .connect(emptyContractEOA) + .withdraw( + l2TokenRebasable.address, + wei`1 ether`, + wei`1 gwei`, + "0x" + ), + "ErrorSenderNotEOA()" + ); + }) + .test("withdraw() :: non-rebasable token flow", async (ctx) => { const { l2TokenBridge, diff --git a/test/optimism/TokenRateNotifier.unit.test.ts b/test/optimism/TokenRateNotifier.unit.test.ts index d8ca1bc0..c2f3864c 100644 --- a/test/optimism/TokenRateNotifier.unit.test.ts +++ b/test/optimism/TokenRateNotifier.unit.test.ts @@ -3,6 +3,7 @@ import { assert } from "chai"; import { utils } from 'ethers' import { unit } from "../../utils/testing"; import { wei } from "../../utils/wei"; +import { SignerWithAddress } from "@nomiclabs/hardhat-ethers/signers"; import { TokenRateNotifier__factory, ITokenRatePusher__factory, @@ -17,6 +18,15 @@ import { unit("TokenRateNotifier", ctxFactory) + .test("deploy with zero address owner", async (ctx) => { + const { deployer } = ctx.accounts; + + await assert.revertsWith( + new TokenRateNotifier__factory(deployer).deploy(ethers.constants.AddressZero), + "ErrorZeroAddressOwner()" + ); + }) + .test("initial state", async (ctx) => { const { tokenRateNotifier } = ctx.contracts; @@ -64,10 +74,17 @@ unit("TokenRateNotifier", ctxFactory) .test("addObserver() :: revert on adding too many observers", async (ctx) => { const { tokenRateNotifier, opStackTokenRatePusher } = ctx.contracts; + const { deployer, owner, tokenRateOracle } = ctx.accounts; + const { l2GasLimitForPushingTokenRate } = ctx.constants; assert.equalBN(await tokenRateNotifier.observersLength(), 0); const maxObservers = await tokenRateNotifier.MAX_OBSERVERS_COUNT(); for (let i = 0; i < maxObservers.toNumber(); i++) { + + const { + opStackTokenRatePusher + } = await getOpStackTokenRatePusher(deployer, owner, tokenRateOracle, l2GasLimitForPushingTokenRate); + await tokenRateNotifier .connect(ctx.accounts.owner) .addObserver(opStackTokenRatePusher.address); @@ -82,6 +99,21 @@ unit("TokenRateNotifier", ctxFactory) ); }) + .test("addObserver() :: revert on adding the same observer twice", async (ctx) => { + const { tokenRateNotifier, opStackTokenRatePusher } = ctx.contracts; + + await tokenRateNotifier + .connect(ctx.accounts.owner) + .addObserver(opStackTokenRatePusher.address); + + await assert.revertsWith( + tokenRateNotifier + .connect(ctx.accounts.owner) + .addObserver(opStackTokenRatePusher.address), + "ErrorAddExistedObserver()" + ); + }) + .test("addObserver() :: happy path of adding observer", async (ctx) => { const { tokenRateNotifier, opStackTokenRatePusher } = ctx.contracts; @@ -204,10 +236,18 @@ unit("TokenRateNotifier", ctxFactory) .run(); -async function ctxFactory() { - const [deployer, owner, stranger, tokenRateOracle] = await ethers.getSigners(); +async function getOpStackTokenRatePusher( + deployer: SignerWithAddress, + owner: SignerWithAddress, + tokenRateOracle: SignerWithAddress, + l2GasLimitForPushingTokenRate: number) { + const tokenRateNotifier = await new TokenRateNotifier__factory(deployer).deploy(owner.address); + const l1MessengerStub = await new CrossDomainMessengerStub__factory( + deployer + ).deploy({ value: wei.toBigNumber(wei`1 ether`) }); + const l1TokenRebasableStub = await new ERC20BridgedStub__factory(deployer).deploy( "L1 Token Rebasable", "L1R" @@ -219,12 +259,6 @@ async function ctxFactory() { "L1NR" ); - const l1MessengerStub = await new CrossDomainMessengerStub__factory( - deployer - ).deploy({ value: wei.toBigNumber(wei`1 ether`) }); - - const l2GasLimitForPushingTokenRate = 123; - const opStackTokenRatePusher = await new OpStackTokenRatePusher__factory(deployer).deploy( l1MessengerStub.address, l1TokenNonRebasableStub.address, @@ -232,6 +266,21 @@ async function ctxFactory() { l2GasLimitForPushingTokenRate ); + return {tokenRateNotifier, opStackTokenRatePusher, l1MessengerStub, l1TokenNonRebasableStub} +} + +async function ctxFactory() { + const [deployer, owner, stranger, tokenRateOracle] = await ethers.getSigners(); + + const l2GasLimitForPushingTokenRate = 123; + + const { + tokenRateNotifier, + opStackTokenRatePusher, + l1MessengerStub, + l1TokenNonRebasableStub + } = await getOpStackTokenRatePusher(deployer, owner, tokenRateOracle, l2GasLimitForPushingTokenRate); + return { accounts: { deployer, owner, stranger, tokenRateOracle }, contracts: { tokenRateNotifier, opStackTokenRatePusher, l1MessengerStub, l1TokenNonRebasableStub }, diff --git a/test/optimism/TokenRateOracle.unit.test.ts b/test/optimism/TokenRateOracle.unit.test.ts index 6851237d..ca4d7b02 100644 --- a/test/optimism/TokenRateOracle.unit.test.ts +++ b/test/optimism/TokenRateOracle.unit.test.ts @@ -14,9 +14,9 @@ unit("TokenRateOracle", ctxFactory) const { bridge, l1TokenBridgeEOA } = ctx.accounts; assert.equal(await tokenRateOracle.MESSENGER(), l2MessengerStub.address); - assert.equal(await tokenRateOracle.BRIDGE(), bridge.address); + assert.equal(await tokenRateOracle.L2_ERC20_TOKEN_BRIDGE(), bridge.address); assert.equal(await tokenRateOracle.L1_TOKEN_RATE_PUSHER(), l1TokenBridgeEOA.address); - assert.equalBN(await tokenRateOracle.RATE_OUTDATED_DELAY(), 86400); + assert.equalBN(await tokenRateOracle.TOKEN_RATE_OUTDATED_DELAY(), 86400); assert.equalBN(await tokenRateOracle.latestAnswer(), 0); diff --git a/test/token/ERC20Rebasable.unit.test.ts b/test/token/ERC20Rebasable.unit.test.ts index 84f7c223..7517f3f0 100644 --- a/test/token/ERC20Rebasable.unit.test.ts +++ b/test/token/ERC20Rebasable.unit.test.ts @@ -16,7 +16,7 @@ unit("ERC20Rebasable", ctxFactory) .test("wrappedToken() :: has the same address is in constructor", async (ctx) => { const { rebasableProxied, wrappedToken } = ctx.contracts; - assert.equal(await rebasableProxied.WRAPPED_TOKEN(), wrappedToken.address) + assert.equal(await rebasableProxied.TOKEN_TO_WRAP_FROM(), wrappedToken.address) }) .test("tokenRateOracle() :: has the same address is in constructor", async (ctx) => { diff --git a/utils/deployment.ts b/utils/deployment.ts index e820bd8f..d9037d42 100644 --- a/utils/deployment.ts +++ b/utils/deployment.ts @@ -14,7 +14,7 @@ interface MultiChainDeploymentConfig { l1RebasableToken: string; l1OpStackTokenRatePusher: string; l2GasLimitForPushingTokenRate: number; - rateOutdatedDelay: number; + tokenRateOutdatedDelay: number; l1TokenBridge: string; l2TokenBridge: string; l2Token: string; @@ -30,7 +30,7 @@ export function loadMultiChainDeploymentConfig(): MultiChainDeploymentConfig { l1RebasableToken: env.address("REBASABLE_TOKEN"), l1OpStackTokenRatePusher: env.address("L1_OP_STACK_TOKEN_RATE_PUSHER"), l2GasLimitForPushingTokenRate: Number(env.string("L2_GAS_LIMIT_FOR_PUSHING_TOKEN_RATE")), - rateOutdatedDelay: Number(env.string("RATE_OUTDATED_DELAY")), + tokenRateOutdatedDelay: Number(env.string("TOKEN_RATE_OUTDATED_DELAY")), l1TokenBridge: env.address("L1_TOKEN_BRIDGE"), l2TokenBridge: env.address("L2_TOKEN_BRIDGE"), l2Token: env.address("L2_TOKEN"), diff --git a/utils/optimism/deploymentOracle.ts b/utils/optimism/deploymentOracle.ts index 2d43f6b9..5be8d466 100644 --- a/utils/optimism/deploymentOracle.ts +++ b/utils/optimism/deploymentOracle.ts @@ -54,7 +54,7 @@ export default function deploymentOracle( async oracleDeployScript( l1Token: string, l2GasLimitForPushingTokenRate: number, - rateOutdatedDelay: number, + tokenRateOutdatedDelay: number, l1Params: OptDeployScriptParams, l2Params: OptDeployScriptParams, ): Promise<[OracleL1DeployScript, OracleL2DeployScript]> { @@ -109,7 +109,7 @@ export default function deploymentOracle( optAddresses.L2CrossDomainMessenger, ethers.constants.AddressZero, expectedL1OpStackTokenRatePusherImplAddress, - rateOutdatedDelay, + tokenRateOutdatedDelay, options?.overrides, ], afterDeploy: (c) => From b62fbeeed79ee93e7afff3092396451ed7c1fa6e Mon Sep 17 00:00:00 2001 From: Anton Kovalchuk Date: Tue, 16 Apr 2024 00:05:07 +0200 Subject: [PATCH 060/148] PR fixes: add comments, rename constracts, more interfaces to contract files --- .../{optimism => lib}/DepositDataCodec.sol | 2 +- contracts/lib/ECDSA.sol | 57 ++++++++++ contracts/lib/SignatureChecker.sol | 84 ++++++-------- contracts/lido/TokenRateNotifier.sol | 14 ++- ...ckTokenRatePusherWithOutOfGasErrorStub.sol | 1 + ...pStackTokenRatePusherWithSomeErrorStub.sol | 3 +- .../optimism/L1ERC20ExtendedTokensBridge.sol | 13 +-- contracts/optimism/L1LidoTokensBridge.sol | 9 +- .../optimism/L2ERC20ExtendedTokensBridge.sol | 14 ++- contracts/optimism/OpStackTokenRatePusher.sol | 12 +- contracts/optimism/README.md | 2 +- .../RebasableAndNonRebasableTokens.sol | 105 +++++------------- contracts/optimism/TokenRateOracle.sol | 57 +++++----- .../IChainlinkAggregatorInterface.sol} | 10 +- .../interfaces/ITokenRateUpdatable.sol | 13 +++ .../stubs/CrossDomainMessengerStub.sol | 1 + contracts/stubs/ERC1271PermitSignerMock.sol | 2 +- contracts/stubs/ERC20BridgedStub.sol | 5 +- contracts/stubs/ERC20WrapperStub.sol | 8 +- contracts/stubs/EmptyContractStub.sol | 3 +- contracts/token/ERC20Bridged.sol | 24 +++- contracts/token/ERC20BridgedPermit.sol | 7 +- contracts/token/ERC20Metadata.sol | 13 ++- contracts/token/ERC20Rebasable.sol | 57 ++++++---- contracts/token/ERC20RebasablePermit.sol | 7 +- .../{ERC20Permit.sol => PermitExtension.sol} | 74 ++++++------ contracts/token/interfaces/IERC20Bridged.sol | 23 ---- .../token/interfaces/IERC20BridgedShares.sol | 23 ---- contracts/token/interfaces/IERC20Metadata.sol | 17 --- .../token/interfaces/IERC20TokenRate.sol | 12 -- contracts/token/interfaces/IERC20WstETH.sol | 14 --- 31 files changed, 333 insertions(+), 353 deletions(-) rename contracts/{optimism => lib}/DepositDataCodec.sol (97%) create mode 100644 contracts/lib/ECDSA.sol rename contracts/{token/interfaces/ITokenRateOracle.sol => optimism/interfaces/IChainlinkAggregatorInterface.sol} (75%) create mode 100644 contracts/optimism/interfaces/ITokenRateUpdatable.sol rename contracts/token/{ERC20Permit.sol => PermitExtension.sol} (57%) delete mode 100644 contracts/token/interfaces/IERC20Bridged.sol delete mode 100644 contracts/token/interfaces/IERC20BridgedShares.sol delete mode 100644 contracts/token/interfaces/IERC20Metadata.sol delete mode 100644 contracts/token/interfaces/IERC20TokenRate.sol delete mode 100644 contracts/token/interfaces/IERC20WstETH.sol diff --git a/contracts/optimism/DepositDataCodec.sol b/contracts/lib/DepositDataCodec.sol similarity index 97% rename from contracts/optimism/DepositDataCodec.sol rename to contracts/lib/DepositDataCodec.sol index 55178758..af8a9910 100644 --- a/contracts/optimism/DepositDataCodec.sol +++ b/contracts/lib/DepositDataCodec.sol @@ -5,7 +5,7 @@ pragma solidity 0.8.10; /// @author kovalgek /// @notice encodes and decodes DepositData for crosschain transfering. -contract DepositDataCodec { +library DepositDataCodec { uint8 internal constant RATE_FIELD_SIZE = 12; uint8 internal constant TIMESTAMP_FIELD_SIZE = 5; diff --git a/contracts/lib/ECDSA.sol b/contracts/lib/ECDSA.sol new file mode 100644 index 00000000..0f694d8d --- /dev/null +++ b/contracts/lib/ECDSA.sol @@ -0,0 +1,57 @@ +// SPDX-FileCopyrightText: 2024 Lido +// SPDX-License-Identifier: MIT + +// Extracted from: +// https://github.com/OpenZeppelin/openzeppelin-contracts/blob/v3.4.0/contracts/cryptography/ECDSA.sol#L53 +// https://github.com/OpenZeppelin/openzeppelin-contracts/blob/541e821/contracts/utils/cryptography/ECDSA.sol#L112 + +pragma solidity 0.8.10; + +library ECDSA { + /** + * @dev Returns the address that signed a hashed message (`hash`). + * This address can then be used for verification purposes. + * Receives the `v`, `r` and `s` signature fields separately. + * + * The `ecrecover` EVM opcode allows for malleable (non-unique) signatures: + * this function rejects them by requiring the `s` value to be in the lower + * half order, and the `v` value to be either 27 or 28. + * + * IMPORTANT: `hash` _must_ be the result of a hash operation for the + * verification to be secure: it is possible to craft signatures that + * recover to arbitrary addresses for non-hashed data. + */ + function recover(bytes32 hash, uint8 v, bytes32 r, bytes32 s) internal pure returns (address) + { + // EIP-2 still allows signature malleability for ecrecover(). Remove this possibility and make the signature + // unique. Appendix F in the Ethereum Yellow paper (https://ethereum.github.io/yellowpaper/paper.pdf), defines + // the valid range for s in (281): 0 < s < secp256k1n ÷ 2 + 1, and for v in (282): v ∈ {27, 28}. Most + // signatures from current libraries generate a unique signature with an s-value in the lower half order. + // + // If your library generates malleable signatures, such as s-values in the upper range, calculate a new s-value + // with 0xFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFEBAAEDCE6AF48A03BBFD25E8CD0364141 - s1 and flip v from 27 to 28 or + // vice versa. If your library also generates signatures with 0/1 for v instead 27/28, add 27 to v to accept + // these malleable signatures as well. + require(uint256(s) <= 0x7FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF5D576E7357A4501DDFE92F46681B20A0, "ECDSA: invalid signature 's' value"); + + // If the signature is valid (and not malleable), return the signer address + address signer = ecrecover(hash, v, r, s); + require(signer != address(0), "ECDSA: invalid signature"); + + return signer; + } + + /** + * @dev Overload of `recover` that receives the `r` and `vs` short-signature fields separately. + * See https://eips.ethereum.org/EIPS/eip-2098[EIP-2098 short signatures] + */ + function recover(bytes32 hash, bytes32 r, bytes32 vs) internal pure returns (address) { + bytes32 s; + uint8 v; + assembly { + s := and(vs, 0x7fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff) + v := add(shr(255, vs), 27) + } + return recover(hash, v, r, s); + } +} diff --git a/contracts/lib/SignatureChecker.sol b/contracts/lib/SignatureChecker.sol index e4e3ab59..183e0266 100644 --- a/contracts/lib/SignatureChecker.sol +++ b/contracts/lib/SignatureChecker.sol @@ -1,35 +1,22 @@ -// SPDX-FileCopyrightText: 2024 OpenZeppelin, Lido -// SPDX-License-Identifier: GPL-3.0 -// Written based on (utils/cryptography/SignatureChecker.sol from d398d68 +// SPDX-FileCopyrightText: 2024 Lido +// SPDX-License-Identifier: MIT pragma solidity 0.8.10; -import {ECDSA} from "@openzeppelin/contracts-v4.9/utils/cryptography/ECDSA.sol"; -import {IERC1271} from "@openzeppelin/contracts-v4.9/interfaces/IERC1271.sol"; +import {ECDSA} from "./ECDSA.sol"; - - -/** - * @dev Signature verification helper that can be used instead of `ECDSA.recover` to seamlessly support both ECDSA - * signatures from externally owned accounts (EOAs) as well as ERC-1271 signatures from smart contract wallets like - * Argent and Safe Wallet (previously Gnosis Safe). - */ +/// @dev A copy of SignatureUtils.sol contract from Lido Core Protocol +/// https://github.com/lidofinance/lido-dao/blob/master/contracts/common/lib/SignatureUtils.sol library SignatureChecker { /** - * @dev Checks if a signature is valid for a given signer and data hash. If the signer is a smart contract, the - * signature is validated against that smart contract using ERC-1271, otherwise it's validated using `ECDSA.recover`. + * @dev The selector of the ERC1271's `isValidSignature(bytes32 hash, bytes signature)` function, + * serving at the same time as the magic value that the function should return upon success. + * + * See https://eips.ethereum.org/EIPS/eip-1271. * - * NOTE: Unlike ECDSA signatures, contract signatures are revocable, and the outcome of this function can thus - * change through time. It could return true at block N and false at block N+1 (or the opposite). + * bytes4(keccak256("isValidSignature(bytes32,bytes)") */ - function isValidSignatureNow(address signer, bytes32 hash, bytes memory signature) internal view returns (bool) { - if (signer.code.length == 0) { - (address recovered, ECDSA.RecoverError err) = ECDSA.tryRecover(hash, signature); - return err == ECDSA.RecoverError.NoError && recovered == signer; - } else { - return isValidERC1271SignatureNow(signer, hash, signature); - } - } + bytes4 internal constant ERC1271_IS_VALID_SIGNATURE_SELECTOR = 0x1626ba7e; /** * @dev Checks signature validity. @@ -38,39 +25,40 @@ library SignatureChecker { * and the signature is a ECDSA signature generated using its private key. Otherwise, issues a * static call to the signer address to check the signature validity using the ERC-1271 standard. */ - function isValidSignatureNow( + function isValidSignature( address signer, bytes32 msgHash, uint8 v, bytes32 r, bytes32 s ) internal view returns (bool) { - if (signer.code.length == 0) { - (address recovered, ECDSA.RecoverError err) = ECDSA.tryRecover(msgHash, v, r, s); - return err == ECDSA.RecoverError.NoError && recovered == signer; + if (_hasCode(signer)) { + bytes memory sig = abi.encodePacked(r, s, v); + // Solidity <0.5 generates a regular CALL instruction even if the function being called + // is marked as `view`, and the only way to perform a STATICCALL is to use assembly + bytes memory data = abi.encodeWithSelector(ERC1271_IS_VALID_SIGNATURE_SELECTOR, msgHash, sig); + bytes32 retval; + /// @solidity memory-safe-assembly + assembly { + // allocate memory for storing the return value + let outDataOffset := mload(0x40) + mstore(0x40, add(outDataOffset, 32)) + // issue a static call and load the result if the call succeeded + let success := staticcall(gas(), signer, add(data, 32), mload(data), outDataOffset, 32) + if and(eq(success, 1), eq(returndatasize(), 32)) { + retval := mload(outDataOffset) + } + } + return retval == bytes32(ERC1271_IS_VALID_SIGNATURE_SELECTOR); } else { - bytes memory signature = abi.encodePacked(r, s, v); - return isValidERC1271SignatureNow(signer, msgHash, signature); + return ECDSA.recover(msgHash, v, r, s) == signer; } } - /** - * @dev Checks if a signature is valid for a given signer and data hash. The signature is validated - * against the signer smart contract using ERC-1271. - * - * NOTE: Unlike ECDSA signatures, contract signatures are revocable, and the outcome of this function can thus - * change through time. It could return true at block N and false at block N+1 (or the opposite). - */ - function isValidERC1271SignatureNow( - address signer, - bytes32 hash, - bytes memory signature - ) internal view returns (bool) { - (bool success, bytes memory result) = signer.staticcall( - abi.encodeWithSelector(IERC1271.isValidSignature.selector, hash, signature) - ); - return (success && - result.length >= 32 && - abi.decode(result, (bytes32)) == bytes32(IERC1271.isValidSignature.selector)); + function _hasCode(address addr) internal view returns (bool) { + uint256 size; + /// @solidity memory-safe-assembly + assembly { size := extcodesize(addr) } + return size > 0; } } diff --git a/contracts/lido/TokenRateNotifier.sol b/contracts/lido/TokenRateNotifier.sol index 28cf18f0..24ca4c31 100644 --- a/contracts/lido/TokenRateNotifier.sol +++ b/contracts/lido/TokenRateNotifier.sol @@ -41,6 +41,9 @@ contract TokenRateNotifier is Ownable, IPostTokenRebaseReceiver { /// @param initialOwner_ initial owner constructor(address initialOwner_) { + if (initialOwner_ == address(0)) { + revert ErrorZeroAddressOwner(); + } _transferOwnership(initialOwner_); } @@ -56,6 +59,9 @@ contract TokenRateNotifier is Ownable, IPostTokenRebaseReceiver { if (observers.length >= MAX_OBSERVERS_COUNT) { revert ErrorMaxObserversCountExceeded(); } + if (_observerIndex(observer_) != INDEX_NOT_FOUND) { + revert ErrorAddExistedObserver(); + } observers.push(observer_); emit ObserverAdded(observer_); @@ -70,8 +76,9 @@ contract TokenRateNotifier is Ownable, IPostTokenRebaseReceiver { if (observerIndexToRemove == INDEX_NOT_FOUND) { revert ErrorNoObserverToRemove(); } - - observers[observerIndexToRemove] = observers[observers.length - 1]; + if (observers.length > 1) { + observers[observerIndexToRemove] = observers[observers.length - 1]; + } observers.pop(); emit ObserverRemoved(observer_); @@ -89,6 +96,7 @@ contract TokenRateNotifier is Ownable, IPostTokenRebaseReceiver { uint256 /* sharesMintedAsFees */ ) external { for (uint256 obIndex = 0; obIndex < observers.length; obIndex++) { + // solhint-disable-next-line no-empty-blocks try ITokenRatePusher(observers[obIndex]).pushTokenRate() {} catch (bytes memory lowLevelRevertData) { /// @dev This check is required to prevent incorrect gas estimation of the method. @@ -131,4 +139,6 @@ contract TokenRateNotifier is Ownable, IPostTokenRebaseReceiver { error ErrorBadObserverInterface(); error ErrorMaxObserversCountExceeded(); error ErrorNoObserverToRemove(); + error ErrorZeroAddressOwner(); + error ErrorAddExistedObserver(); } diff --git a/contracts/lido/stubs/OpStackTokenRatePusherWithOutOfGasErrorStub.sol b/contracts/lido/stubs/OpStackTokenRatePusherWithOutOfGasErrorStub.sol index cb8d1c26..0ce7974c 100644 --- a/contracts/lido/stubs/OpStackTokenRatePusherWithOutOfGasErrorStub.sol +++ b/contracts/lido/stubs/OpStackTokenRatePusherWithOutOfGasErrorStub.sol @@ -6,6 +6,7 @@ pragma solidity 0.8.10; import {ITokenRatePusher} from "../interfaces/ITokenRatePusher.sol"; import {ERC165} from "@openzeppelin/contracts/utils/introspection/ERC165.sol"; +/// @dev For testing purposes. contract OpStackTokenRatePusherWithOutOfGasErrorStub is ERC165, ITokenRatePusher { uint256 public constant OUT_OF_GAS_INCURRING_MAX = 1000000000000; diff --git a/contracts/lido/stubs/OpStackTokenRatePusherWithSomeErrorStub.sol b/contracts/lido/stubs/OpStackTokenRatePusherWithSomeErrorStub.sol index 5a8ddfdd..6df0b7fa 100644 --- a/contracts/lido/stubs/OpStackTokenRatePusherWithSomeErrorStub.sol +++ b/contracts/lido/stubs/OpStackTokenRatePusherWithSomeErrorStub.sol @@ -6,11 +6,12 @@ pragma solidity 0.8.10; import {ITokenRatePusher} from "../interfaces/ITokenRatePusher.sol"; import {ERC165} from "@openzeppelin/contracts/utils/introspection/ERC165.sol"; +/// @dev For testing purposes. contract OpStackTokenRatePusherWithSomeErrorStub is ERC165, ITokenRatePusher { error SomeError(); - function pushTokenRate() external { + function pushTokenRate() pure external { revert SomeError(); } diff --git a/contracts/optimism/L1ERC20ExtendedTokensBridge.sol b/contracts/optimism/L1ERC20ExtendedTokensBridge.sol index 6a16129d..bb67010c 100644 --- a/contracts/optimism/L1ERC20ExtendedTokensBridge.sol +++ b/contracts/optimism/L1ERC20ExtendedTokensBridge.sol @@ -119,14 +119,11 @@ abstract contract L1ERC20ExtendedTokensBridge is onlyFromCrossDomainAccount(L2_TOKEN_BRIDGE) onlySupportedL1L2TokensPair(l1Token_, l2Token_) { - if(_isRebasable(l1Token_)) { - uint256 rebasableTokenAmount = IERC20Wrapper(L1_TOKEN_NON_REBASABLE).unwrap(amount_); - IERC20(l1Token_).safeTransfer(to_, rebasableTokenAmount); - emit ERC20WithdrawalFinalized(l1Token_, l2Token_, from_, to_, rebasableTokenAmount, data_); - } else { - IERC20(l1Token_).safeTransfer(to_, amount_); - emit ERC20WithdrawalFinalized(l1Token_, l2Token_, from_, to_, amount_, data_); - } + uint256 amountToWithdraw = _isRebasable(l1Token_) ? + IERC20Wrapper(L1_TOKEN_NON_REBASABLE).unwrap(amount_) : + amount_; + IERC20(l1Token_).safeTransfer(to_, amountToWithdraw); + emit ERC20WithdrawalFinalized(l1Token_, l2Token_, from_, to_, amountToWithdraw, data_); } /// @dev Performs the logic for deposits by informing the L2 token bridge contract diff --git a/contracts/optimism/L1LidoTokensBridge.sol b/contracts/optimism/L1LidoTokensBridge.sol index c28ed325..d749e034 100644 --- a/contracts/optimism/L1LidoTokensBridge.sol +++ b/contracts/optimism/L1LidoTokensBridge.sol @@ -4,7 +4,14 @@ pragma solidity 0.8.10; import {L1ERC20ExtendedTokensBridge} from "./L1ERC20ExtendedTokensBridge.sol"; -import {IERC20WstETH} from "../token/interfaces/IERC20WstETH.sol"; + +/// @author kovalgek +/// @notice A subset of wstETH token interface of core LIDO protocol. +interface IERC20WstETH { + /// @notice Get amount of wstETH for a one stETH + /// @return Amount of wstETH for a 1 stETH + function stEthPerToken() external view returns (uint256); +} /// @author kovalgek /// @notice Hides wstETH concept from other contracts to keep `L1ERC20ExtendedTokensBridge` reusable. diff --git a/contracts/optimism/L2ERC20ExtendedTokensBridge.sol b/contracts/optimism/L2ERC20ExtendedTokensBridge.sol index 21bc55e7..38a6c834 100644 --- a/contracts/optimism/L2ERC20ExtendedTokensBridge.sol +++ b/contracts/optimism/L2ERC20ExtendedTokensBridge.sol @@ -5,10 +5,11 @@ pragma solidity 0.8.10; import {IERC20} from "@openzeppelin/contracts/token/ERC20/IERC20.sol"; import {SafeERC20} from "@openzeppelin/contracts/token/ERC20/utils/SafeERC20.sol"; +import {Address} from "@openzeppelin/contracts/utils/Address.sol"; import {IL1ERC20Bridge} from "./interfaces/IL1ERC20Bridge.sol"; import {IL2ERC20Bridge} from "./interfaces/IL2ERC20Bridge.sol"; -import {IERC20Bridged} from "../token/interfaces/IERC20Bridged.sol"; -import {ITokenRateOracle} from "../token/interfaces/ITokenRateOracle.sol"; +import {IERC20Bridged} from "../token/ERC20Bridged.sol"; +import {ITokenRateUpdatable} from "../optimism/interfaces/ITokenRateUpdatable.sol"; import {IERC20Wrapper} from "../token/interfaces/IERC20Wrapper.sol"; import {ERC20Rebasable} from "../token/ERC20Rebasable.sol"; import {BridgingManager} from "../BridgingManager.sol"; @@ -30,7 +31,7 @@ contract L2ERC20ExtendedTokensBridge is { using SafeERC20 for IERC20; - address public immutable L1_TOKEN_BRIDGE; + address private immutable L1_TOKEN_BRIDGE; /// @param messenger_ L2 messenger address being used for cross-chain communications /// @param l1TokenBridge_ Address of the corresponding L1 bridge @@ -69,6 +70,9 @@ contract L2ERC20ExtendedTokensBridge is whenWithdrawalsEnabled onlySupportedL2Token(l2Token_) { + if (Address.isContract(msg.sender)) { + revert ErrorSenderNotEOA(); + } _withdrawTo(l2Token_, msg.sender, msg.sender, amount_, l1Gas_, data_); emit WithdrawalInitiated(_l1Token(l2Token_), l2Token_, msg.sender, msg.sender, amount_, data_); } @@ -103,7 +107,7 @@ contract L2ERC20ExtendedTokensBridge is onlyFromCrossDomainAccount(L1_TOKEN_BRIDGE) { DepositDataCodec.DepositData memory depositData = DepositDataCodec.decodeDepositData(data_); - ITokenRateOracle tokenRateOracle = ERC20Rebasable(L2_TOKEN_REBASABLE).TOKEN_RATE_ORACLE(); + ITokenRateUpdatable tokenRateOracle = ERC20Rebasable(L2_TOKEN_REBASABLE).TOKEN_RATE_ORACLE(); tokenRateOracle.updateRate(depositData.rate, depositData.timestamp); uint256 depositedAmount = _mintTokens(l1Token_, l2Token_, to_, amount_); @@ -165,4 +169,6 @@ contract L2ERC20ExtendedTokensBridge is IERC20Bridged(l2Token_).bridgeBurn(from_, amount_); return amount_; } + + error ErrorSenderNotEOA(); } diff --git a/contracts/optimism/OpStackTokenRatePusher.sol b/contracts/optimism/OpStackTokenRatePusher.sol index 65b06a34..52479a22 100644 --- a/contracts/optimism/OpStackTokenRatePusher.sol +++ b/contracts/optimism/OpStackTokenRatePusher.sol @@ -5,8 +5,8 @@ pragma solidity 0.8.10; import {CrossDomainEnabled} from "./CrossDomainEnabled.sol"; import {ITokenRatePusher} from "../lido/interfaces/ITokenRatePusher.sol"; -import {IERC20WstETH} from "../token/interfaces/IERC20WstETH.sol"; -import {ITokenRateOracle} from "../token/interfaces/ITokenRateOracle.sol"; +import {IERC20WstETH} from "./L1LidoTokensBridge.sol"; +import {ITokenRateUpdatable} from "../optimism/interfaces/ITokenRateUpdatable.sol"; import {ERC165} from "@openzeppelin/contracts/utils/introspection/ERC165.sol"; /// @author kovalgek @@ -19,7 +19,11 @@ contract OpStackTokenRatePusher is CrossDomainEnabled, ERC165, ITokenRatePusher /// @notice Non-rebasable token of Core Lido procotol. address public immutable WSTETH; - /// @notice Gas limit required to complete pushing token rate on L2. + /// @notice Gas limit for L2 required to finish pushing token rate on L2 side. + /// Client pays for gas on L2 by burning it on L1. + /// Depends linearly on deposit data length and gas used for finalizing deposit on L2. + /// Formula to find value: + /// (gas cost of L2Bridge.finalizeDeposit() + OptimismPortal.minimumGasLimit(depositData.length)) * 1.5 uint32 public immutable L2_GAS_LIMIT_FOR_PUSHING_TOKEN_RATE; /// @param messenger_ L1 messenger address being used for cross-chain communications @@ -42,7 +46,7 @@ contract OpStackTokenRatePusher is CrossDomainEnabled, ERC165, ITokenRatePusher uint256 tokenRate = IERC20WstETH(WSTETH).stEthPerToken(); bytes memory message = abi.encodeWithSelector( - ITokenRateOracle.updateRate.selector, + ITokenRateUpdatable.updateRate.selector, tokenRate, block.timestamp ); diff --git a/contracts/optimism/README.md b/contracts/optimism/README.md index c493349d..6b853ba3 100644 --- a/contracts/optimism/README.md +++ b/contracts/optimism/README.md @@ -514,7 +514,7 @@ Transfers `amount` of token from the `from_` account to `to_` using the allowanc ## `ERC20Bridged` -**Implements:** [`IERC20Bridged`](https://github.com/lidofinance/lido-l2/blob/main/contracts/token/interfaces/IERC20Bridged.sol) +**Implements:** [`IERC20Bridged`](https://github.com/lidofinance/lido-l2/blob/main/contracts/token/ERC20Bridged.sol) **Inherits:** [`ERC20Metadata`](#ERC20Metadata) [`ERC20Core`](#ERC20CoreLogic) Inherits the `ERC20` default functionality that allows the bridge to mint and burn tokens. diff --git a/contracts/optimism/RebasableAndNonRebasableTokens.sol b/contracts/optimism/RebasableAndNonRebasableTokens.sol index 45313fc6..21491d19 100644 --- a/contracts/optimism/RebasableAndNonRebasableTokens.sol +++ b/contracts/optimism/RebasableAndNonRebasableTokens.sol @@ -8,32 +8,18 @@ import {UnstructuredRefStorage} from "../token/UnstructuredRefStorage.sol"; /// @author psirex, kovalgek /// @notice Contains the logic for validation of tokens used in the bridging process contract RebasableAndNonRebasableTokens { - using UnstructuredRefStorage for bytes32; - - /// @dev Servers for pairing tokens by one-layer and wrapping. - /// @param `oppositeLayerToken` token representation on opposite layer. - /// @param `pairedToken` paired token address on the same domain. - struct TokenInfo { - address oppositeLayerToken; - address pairedToken; - } - bytes32 internal constant REBASABLE_TOKENS_POSITION = keccak256("RebasableAndNonRebasableTokens.REBASABLE_TOKENS_POSITION"); - bytes32 internal constant NON_REBASABLE_TOKENS_POSITION = keccak256("RebasableAndNonRebasableTokens.NON_REBASABLE_TOKENS_POSITION"); + /// @notice Address of the bridged non rebasable token in the L1 chain + address public immutable L1_TOKEN_NON_REBASABLE; - function _getRebasableTokens() internal pure returns (mapping(address => TokenInfo) storage) { - return _storageMapAddressTokenInfo(REBASABLE_TOKENS_POSITION); - } + /// @notice Address of the bridged rebasable token in the L1 chain + address public immutable L1_TOKEN_REBASABLE; - function _getNonRebasableTokens() internal pure returns (mapping(address => TokenInfo) storage) { - return _storageMapAddressTokenInfo(REBASABLE_TOKENS_POSITION); - } + /// @notice Address of the non rebasable token minted on the L2 chain when token bridged + address public immutable L2_TOKEN_NON_REBASABLE; - function _storageMapAddressTokenInfo(bytes32 _position) internal pure returns ( - mapping(address => TokenInfo) storage result - ) { - assembly { result.slot := _position } - } + /// @notice Address of the rebasable token minted on the L2 chain when token bridged + address public immutable L2_TOKEN_REBASABLE; /// @param l1TokenNonRebasable_ Address of the bridged non rebasable token in the L1 chain /// @param l1TokenRebasable_ Address of the bridged rebasable token in the L1 chain @@ -45,69 +31,35 @@ contract RebasableAndNonRebasableTokens { address l2TokenNonRebasable_, address l2TokenRebasable_ ) { - _getRebasableTokens()[l1TokenRebasable_] = TokenInfo({ - oppositeLayerToken: l2TokenRebasable_, - pairedToken: l1TokenNonRebasable_ - }); - _getRebasableTokens()[l2TokenRebasable_] = TokenInfo({ - oppositeLayerToken: l1TokenRebasable_, - pairedToken: l2TokenNonRebasable_ - }); - _getNonRebasableTokens()[l1TokenNonRebasable_] = TokenInfo({ - oppositeLayerToken: l2TokenNonRebasable_, - pairedToken: l1TokenRebasable_ - }); - _getNonRebasableTokens()[l2TokenNonRebasable_] = TokenInfo({ - oppositeLayerToken: l1TokenNonRebasable_, - pairedToken: l2TokenRebasable_ - }); - } - - function initialize( - address l1TokenNonRebasable_, - address l1TokenRebasable_, - address l2TokenNonRebasable_, - address l2TokenRebasable_ - ) public { - _getRebasableTokens()[l1TokenRebasable_] = TokenInfo({ - oppositeLayerToken: l2TokenRebasable_, - pairedToken: l1TokenNonRebasable_ - }); - _getRebasableTokens()[l2TokenRebasable_] = TokenInfo({ - oppositeLayerToken: l1TokenRebasable_, - pairedToken: l2TokenNonRebasable_ - }); - _getNonRebasableTokens()[l1TokenNonRebasable_] = TokenInfo({ - oppositeLayerToken: l2TokenNonRebasable_, - pairedToken: l1TokenRebasable_ - }); - _getNonRebasableTokens()[l2TokenNonRebasable_] = TokenInfo({ - oppositeLayerToken: l1TokenNonRebasable_, - pairedToken: l2TokenRebasable_ - }); + L1_TOKEN_NON_REBASABLE = l1TokenNonRebasable_; + L1_TOKEN_REBASABLE = l1TokenRebasable_; + L2_TOKEN_NON_REBASABLE = l2TokenNonRebasable_; + L2_TOKEN_REBASABLE = l2TokenRebasable_; } /// @dev Validates that passed l1Token_ and l2Token_ tokens pair is supported by the bridge. modifier onlySupportedL1L2TokensPair(address l1Token_, address l2Token_) { - if (_getRebasableTokens()[l1Token_].oppositeLayerToken == address(0) && - _getNonRebasableTokens()[l1Token_].oppositeLayerToken == address(0)) { + if (l1Token_ != L1_TOKEN_NON_REBASABLE && l1Token_ != L1_TOKEN_REBASABLE) { revert ErrorUnsupportedL1Token(); } - if (_getRebasableTokens()[l2Token_].oppositeLayerToken == address(0) && - _getNonRebasableTokens()[l2Token_].oppositeLayerToken == address(0)) { + if (l2Token_ != L2_TOKEN_NON_REBASABLE && l2Token_ != L2_TOKEN_REBASABLE) { revert ErrorUnsupportedL2Token(); } - if (_getRebasableTokens()[l1Token_].oppositeLayerToken != l2Token_ && - _getNonRebasableTokens()[l2Token_].oppositeLayerToken != l1Token_) { + if (!_isSupportedL1L2TokensPair(l1Token_, l2Token_)) { revert ErrorUnsupportedL1L2TokensPair(); } _; } + function _isSupportedL1L2TokensPair(address l1Token_, address l2Token_) internal view returns (bool) { + bool isNonRebasablePair = l1Token_ == L1_TOKEN_NON_REBASABLE && l2Token_ == L2_TOKEN_NON_REBASABLE; + bool isRebasablePair = l1Token_ == L1_TOKEN_REBASABLE && l2Token_ == L2_TOKEN_REBASABLE; + return isNonRebasablePair || isRebasablePair; + } + /// @dev Validates that passed l1Token_ is supported by the bridge modifier onlySupportedL1Token(address l1Token_) { - if (_getRebasableTokens()[l1Token_].oppositeLayerToken == address(0) && - _getNonRebasableTokens()[l1Token_].oppositeLayerToken == address(0)) { + if (l1Token_ != L1_TOKEN_NON_REBASABLE && l1Token_ != L1_TOKEN_REBASABLE) { revert ErrorUnsupportedL1Token(); } _; @@ -115,8 +67,7 @@ contract RebasableAndNonRebasableTokens { /// @dev Validates that passed l2Token_ is supported by the bridge modifier onlySupportedL2Token(address l2Token_) { - if (_getRebasableTokens()[l2Token_].oppositeLayerToken == address(0) && - _getNonRebasableTokens()[l2Token_].oppositeLayerToken == address(0)) { + if (l2Token_ != L2_TOKEN_NON_REBASABLE && l2Token_ != L2_TOKEN_REBASABLE) { revert ErrorUnsupportedL2Token(); } _; @@ -131,17 +82,11 @@ contract RebasableAndNonRebasableTokens { } function _isRebasable(address token_) internal view returns (bool) { - return _getRebasableTokens()[token_].oppositeLayerToken != address(0); + return token_ == L1_TOKEN_REBASABLE || token_ == L2_TOKEN_REBASABLE; } function _l1Token(address l2Token_) internal view returns (address) { - return _isRebasable(l2Token_) ? - _getRebasableTokens()[l2Token_].oppositeLayerToken : - _getNonRebasableTokens()[l2Token_].oppositeLayerToken; - } - - function _l1NonRebasableToken(address l1Token_) internal view returns (address) { - return _isRebasable(l1Token_) ? _getRebasableTokens()[l1Token_].pairedToken : l1Token_; + return _isRebasable(l2Token_) ? L1_TOKEN_REBASABLE : L1_TOKEN_NON_REBASABLE; } error ErrorUnsupportedL1Token(); diff --git a/contracts/optimism/TokenRateOracle.sol b/contracts/optimism/TokenRateOracle.sol index a191e216..beeef21a 100644 --- a/contracts/optimism/TokenRateOracle.sol +++ b/contracts/optimism/TokenRateOracle.sol @@ -1,49 +1,52 @@ -// SPDX-FileCopyrightText: 2022 Lido +// SPDX-FileCopyrightText: 2024 Lido // SPDX-License-Identifier: GPL-3.0 pragma solidity 0.8.10; -import {ITokenRateOracle} from "../token/interfaces/ITokenRateOracle.sol"; +import {ITokenRateUpdatable} from "./interfaces/ITokenRateUpdatable.sol"; +import {IChainlinkAggregatorInterface} from "./interfaces/IChainlinkAggregatorInterface.sol"; import {CrossDomainEnabled} from "./CrossDomainEnabled.sol"; +interface ITokenRateOracle is ITokenRateUpdatable, IChainlinkAggregatorInterface {} + /// @author kovalgek /// @notice Oracle for storing token rate. contract TokenRateOracle is CrossDomainEnabled, ITokenRateOracle { /// @notice A bridge which can update oracle. - address public immutable BRIDGE; + address public immutable L2_ERC20_TOKEN_BRIDGE; /// @notice An address of account on L1 that can update token rate. address public immutable L1_TOKEN_RATE_PUSHER; /// @notice A time period when token rate can be considered outdated. - uint256 public immutable RATE_OUTDATED_DELAY; + uint256 public immutable TOKEN_RATE_OUTDATED_DELAY; + + /// @notice Decimals of the oracle response. + uint8 public constant DECIMALS = 18; /// @notice wstETH/stETH token rate. - uint256 private tokenRate; + uint256 public tokenRate; /// @notice L1 time when token rate was pushed. - uint256 private rateL1Timestamp; - - /// @notice Decimals of the oracle response. - uint8 private constant DECIMALS = 18; + uint256 public rateL1Timestamp; /// @param messenger_ L2 messenger address being used for cross-chain communications - /// @param bridge_ the bridge address that has a right to updates oracle. + /// @param l2ERC20TokenBridge_ the bridge address that has a right to updates oracle. /// @param l1TokenRatePusher_ An address of account on L1 that can update token rate. - /// @param rateOutdatedDelay_ time period when token rate can be considered outdated. + /// @param tokenRateOutdatedDelay_ time period when token rate can be considered outdated. constructor( address messenger_, - address bridge_, + address l2ERC20TokenBridge_, address l1TokenRatePusher_, - uint256 rateOutdatedDelay_ + uint256 tokenRateOutdatedDelay_ ) CrossDomainEnabled(messenger_) { - BRIDGE = bridge_; + L2_ERC20_TOKEN_BRIDGE = l2ERC20TokenBridge_; L1_TOKEN_RATE_PUSHER = l1TokenRatePusher_; - RATE_OUTDATED_DELAY = rateOutdatedDelay_; + TOKEN_RATE_OUTDATED_DELAY = tokenRateOutdatedDelay_; } - /// @inheritdoc ITokenRateOracle + /// @inheritdoc IChainlinkAggregatorInterface function latestRoundData() external view returns ( uint80 roundId_, int256 answer_, @@ -51,7 +54,7 @@ contract TokenRateOracle is CrossDomainEnabled, ITokenRateOracle { uint256 updatedAt_, uint80 answeredInRound_ ) { - uint80 roundId = uint80(rateL1Timestamp); // TODO: add solt + uint80 roundId = uint80(rateL1Timestamp); return ( roundId, @@ -62,23 +65,20 @@ contract TokenRateOracle is CrossDomainEnabled, ITokenRateOracle { ); } - /// @inheritdoc ITokenRateOracle + /// @inheritdoc IChainlinkAggregatorInterface function latestAnswer() external view returns (int256) { return int256(tokenRate); } - /// @inheritdoc ITokenRateOracle + /// @inheritdoc IChainlinkAggregatorInterface function decimals() external pure returns (uint8) { return DECIMALS; } - /// @inheritdoc ITokenRateOracle + /// @inheritdoc ITokenRateUpdatable function updateRate(uint256 tokenRate_, uint256 rateL1Timestamp_) external { - if (!( - (msg.sender == address(MESSENGER) && MESSENGER.xDomainMessageSender() == L1_TOKEN_RATE_PUSHER) - || (msg.sender == BRIDGE) - )) { + if (_isNoRightsToCall(msg.sender)) { revert ErrorNoRights(msg.sender); } @@ -98,7 +98,14 @@ contract TokenRateOracle is CrossDomainEnabled, ITokenRateOracle { /// @notice Returns flag that shows that token rate can be considered outdated. function isLikelyOutdated() external view returns (bool) { - return block.timestamp - rateL1Timestamp > RATE_OUTDATED_DELAY; + return block.timestamp - rateL1Timestamp > TOKEN_RATE_OUTDATED_DELAY; + } + + function _isNoRightsToCall(address caller_) internal view returns (bool) { + bool isCalledFromL1TokenRatePusher = caller_ == address(MESSENGER) && + MESSENGER.xDomainMessageSender() == L1_TOKEN_RATE_PUSHER; + bool isCalledFromERC20TokenRateBridge = caller_ == L2_ERC20_TOKEN_BRIDGE; + return !isCalledFromL1TokenRatePusher && !isCalledFromERC20TokenRateBridge; } event RateUpdated(uint256 tokenRate_, uint256 rateL1Timestamp_); diff --git a/contracts/token/interfaces/ITokenRateOracle.sol b/contracts/optimism/interfaces/IChainlinkAggregatorInterface.sol similarity index 75% rename from contracts/token/interfaces/ITokenRateOracle.sol rename to contracts/optimism/interfaces/IChainlinkAggregatorInterface.sol index ce057ac8..e9a1e624 100644 --- a/contracts/token/interfaces/ITokenRateOracle.sol +++ b/contracts/optimism/interfaces/IChainlinkAggregatorInterface.sol @@ -4,9 +4,8 @@ pragma solidity 0.8.10; /// @author kovalgek -/// @notice Oracle interface for token rate. A subset of Chainlink data feed interface. -interface ITokenRateOracle { - +/// @notice A subset of chainlink data feed interface for token rate oracle. +interface IChainlinkAggregatorInterface { /// @notice get the latest token rate data. /// @return roundId_ is a unique id for each answer. The value is based on timestamp. /// @return answer_ is wstETH/stETH token rate. @@ -31,9 +30,4 @@ interface ITokenRateOracle { /// @notice represents the number of decimals the oracle responses represent. /// @return decimals of the oracle response. function decimals() external view returns (uint8); - - /// @notice Updates token rate. - /// @param tokenRate_ wstETH/stETH token rate. - /// @param rateL1Timestamp_ L1 time when rate was pushed on L1 side. - function updateRate(uint256 tokenRate_, uint256 rateL1Timestamp_) external; } diff --git a/contracts/optimism/interfaces/ITokenRateUpdatable.sol b/contracts/optimism/interfaces/ITokenRateUpdatable.sol new file mode 100644 index 00000000..c14461ac --- /dev/null +++ b/contracts/optimism/interfaces/ITokenRateUpdatable.sol @@ -0,0 +1,13 @@ +// SPDX-FileCopyrightText: 2024 Lido +// SPDX-License-Identifier: GPL-3.0 + +pragma solidity 0.8.10; + +/// @author kovalgek +/// @notice An interface for updating token rate of token rate oracle. +interface ITokenRateUpdatable { + /// @notice Updates token rate. + /// @param tokenRate_ wstETH/stETH token rate. + /// @param rateL1Timestamp_ L1 time when rate was pushed on L1 side. + function updateRate(uint256 tokenRate_, uint256 rateL1Timestamp_) external; +} diff --git a/contracts/optimism/stubs/CrossDomainMessengerStub.sol b/contracts/optimism/stubs/CrossDomainMessengerStub.sol index f2d30805..d552ab3f 100644 --- a/contracts/optimism/stubs/CrossDomainMessengerStub.sol +++ b/contracts/optimism/stubs/CrossDomainMessengerStub.sol @@ -5,6 +5,7 @@ pragma solidity 0.8.10; import {ICrossDomainMessenger} from "../interfaces/ICrossDomainMessenger.sol"; +/// @dev For testing purposes. contract CrossDomainMessengerStub is ICrossDomainMessenger { address public xDomainMessageSender; uint256 public messageNonce; diff --git a/contracts/stubs/ERC1271PermitSignerMock.sol b/contracts/stubs/ERC1271PermitSignerMock.sol index c865691a..56e94718 100644 --- a/contracts/stubs/ERC1271PermitSignerMock.sol +++ b/contracts/stubs/ERC1271PermitSignerMock.sol @@ -3,7 +3,7 @@ pragma solidity 0.8.10; - +/// @dev For testing purposes. contract ERC1271PermitSignerMock { bytes4 public constant ERC1271_MAGIC_VALUE = 0x1626ba7e; diff --git a/contracts/stubs/ERC20BridgedStub.sol b/contracts/stubs/ERC20BridgedStub.sol index 85e457a9..fd0b33f8 100644 --- a/contracts/stubs/ERC20BridgedStub.sol +++ b/contracts/stubs/ERC20BridgedStub.sol @@ -1,11 +1,12 @@ -// SPDX-FileCopyrightText: 2022 Lido +// SPDX-FileCopyrightText: 2024 Lido // SPDX-License-Identifier: GPL-3.0 pragma solidity 0.8.10; -import {IERC20Bridged} from "../token/interfaces/IERC20Bridged.sol"; +import {IERC20Bridged} from "../token/ERC20Bridged.sol"; import {ERC20} from "@openzeppelin/contracts/token/ERC20/ERC20.sol"; +/// @dev For testing purposes. contract ERC20BridgedStub is IERC20Bridged, ERC20 { address public bridge; diff --git a/contracts/stubs/ERC20WrapperStub.sol b/contracts/stubs/ERC20WrapperStub.sol index b23817bc..fc9ab194 100644 --- a/contracts/stubs/ERC20WrapperStub.sol +++ b/contracts/stubs/ERC20WrapperStub.sol @@ -1,15 +1,15 @@ -// SPDX-FileCopyrightText: 2022 Lido +// SPDX-FileCopyrightText: 2024 Lido // SPDX-License-Identifier: GPL-3.0 pragma solidity 0.8.10; -import {IERC20Bridged} from "../token/interfaces/IERC20Bridged.sol"; +import {IERC20Bridged} from "../token/ERC20Bridged.sol"; import {ERC20} from "@openzeppelin/contracts/token/ERC20/ERC20.sol"; import {IERC20} from "@openzeppelin/contracts/token/ERC20/IERC20.sol"; -import {IERC20WstETH} from "../token/interfaces/IERC20WstETH.sol"; +import {IERC20WstETH} from "../optimism/L1LidoTokensBridge.sol"; import {IERC20Wrapper} from "../token/interfaces/IERC20Wrapper.sol"; -// represents wstETH on L1 +/// @dev represents wstETH on L1. For testing purposes. contract ERC20WrapperStub is IERC20Wrapper, IERC20WstETH, ERC20 { IERC20 public stETH; diff --git a/contracts/stubs/EmptyContractStub.sol b/contracts/stubs/EmptyContractStub.sol index 9a334ca1..96e3995c 100644 --- a/contracts/stubs/EmptyContractStub.sol +++ b/contracts/stubs/EmptyContractStub.sol @@ -1,8 +1,9 @@ -// SPDX-FileCopyrightText: 2022 Lido +// SPDX-FileCopyrightText: 2024 Lido // SPDX-License-Identifier: GPL-3.0 pragma solidity 0.8.10; +/// @dev For testing purposes. contract EmptyContractStub { constructor() payable {} } diff --git a/contracts/token/ERC20Bridged.sol b/contracts/token/ERC20Bridged.sol index 574ddd8b..dee94ec0 100644 --- a/contracts/token/ERC20Bridged.sol +++ b/contracts/token/ERC20Bridged.sol @@ -1,14 +1,30 @@ -// SPDX-FileCopyrightText: 2022 Lido +// SPDX-FileCopyrightText: 2024 Lido // SPDX-License-Identifier: GPL-3.0 pragma solidity 0.8.10; -import {IERC20Bridged} from "./interfaces/IERC20Bridged.sol"; - +import {IERC20} from "@openzeppelin/contracts/token/ERC20/IERC20.sol"; import {ERC20Core} from "./ERC20Core.sol"; import {ERC20Metadata} from "./ERC20Metadata.sol"; -/// @author psirex +/// @author psirex, kovalgek +/// @notice Extends the ERC20 functionality that allows the bridge to mint/burn tokens +interface IERC20Bridged is IERC20 { + /// @notice Returns bridge which can mint and burn tokens on L2 + function bridge() external view returns (address); + + /// @notice Creates amount_ tokens and assigns them to account_, increasing the total supply + /// @param account_ An address of the account to mint tokens + /// @param amount_ An amount of tokens to mint + function bridgeMint(address account_, uint256 amount_) external; + + /// @notice Destroys amount_ tokens from account_, reducing the total supply + /// @param account_ An address of the account to burn tokens + /// @param amount_ An amount of tokens to burn + function bridgeBurn(address account_, uint256 amount_) external; +} + +/// @author psirex, kovalgek /// @notice Extends the ERC20 functionality that allows the bridge to mint/burn tokens contract ERC20Bridged is IERC20Bridged, ERC20Core, ERC20Metadata { /// @inheritdoc IERC20Bridged diff --git a/contracts/token/ERC20BridgedPermit.sol b/contracts/token/ERC20BridgedPermit.sol index d936ae37..c6341000 100644 --- a/contracts/token/ERC20BridgedPermit.sol +++ b/contracts/token/ERC20BridgedPermit.sol @@ -4,9 +4,10 @@ pragma solidity 0.8.10; import {ERC20Bridged} from "./ERC20Bridged.sol"; -import {ERC20Permit} from "./ERC20Permit.sol"; +import {PermitExtension} from "./PermitExtension.sol"; -contract ERC20BridgedPermit is ERC20Bridged, ERC20Permit { +/// @author kovalgek +contract ERC20BridgedPermit is ERC20Bridged, PermitExtension { /// @param name_ The name of the token /// @param symbol_ The symbol of the token @@ -21,7 +22,7 @@ contract ERC20BridgedPermit is ERC20Bridged, ERC20Permit { address bridge_ ) ERC20Bridged(name_, symbol_, decimals_, bridge_) - ERC20Permit(name_, version_) + PermitExtension(name_, version_) { } diff --git a/contracts/token/ERC20Metadata.sol b/contracts/token/ERC20Metadata.sol index 397b4d0d..e3781f26 100644 --- a/contracts/token/ERC20Metadata.sol +++ b/contracts/token/ERC20Metadata.sol @@ -3,7 +3,18 @@ pragma solidity 0.8.10; -import {IERC20Metadata} from "./interfaces/IERC20Metadata.sol"; +/// @author psirex +/// @notice Interface for the optional metadata functions from the ERC20 standard. +interface IERC20Metadata { + /// @dev Returns the name of the token. + function name() external view returns (string memory); + + /// @dev Returns the symbol of the token. + function symbol() external view returns (string memory); + + /// @dev Returns the decimals places of the token. + function decimals() external view returns (uint8); +} /// @author psirex /// @notice Contains the optional metadata functions from the ERC20 standard diff --git a/contracts/token/ERC20Rebasable.sol b/contracts/token/ERC20Rebasable.sol index 49939548..b8660ded 100644 --- a/contracts/token/ERC20Rebasable.sol +++ b/contracts/token/ERC20Rebasable.sol @@ -1,16 +1,32 @@ -// SPDX-FileCopyrightText: 2022 Lido +// SPDX-FileCopyrightText: 2024 Lido // SPDX-License-Identifier: GPL-3.0 pragma solidity 0.8.10; import {IERC20} from "@openzeppelin/contracts/token/ERC20/IERC20.sol"; import {IERC20Wrapper} from "./interfaces/IERC20Wrapper.sol"; -import {IERC20BridgedShares} from "./interfaces/IERC20BridgedShares.sol"; -import {ITokenRateOracle} from "./interfaces/ITokenRateOracle.sol"; +import {ITokenRateOracle} from "../optimism/TokenRateOracle.sol"; import {ERC20Metadata} from "./ERC20Metadata.sol"; import {UnstructuredRefStorage} from "./UnstructuredRefStorage.sol"; import {UnstructuredStorage} from "./UnstructuredStorage.sol"; +/// @author kovalgek +/// @notice Extends the ERC20 functionality that allows the bridge to mint/burn shares +interface IERC20BridgedShares is IERC20 { + /// @notice Returns bridge which can mint and burn shares on L2 + function L2_ERC20_TOKEN_BRIDGE() external view returns (address); + + /// @notice Creates amount_ shares and assigns them to account_, increasing the total shares supply + /// @param account_ An address of the account to mint shares + /// @param amount_ An amount of shares to mint + function bridgeMintShares(address account_, uint256 amount_) external; + + /// @notice Destroys amount_ shares from account_, reducing the total shares supply + /// @param account_ An address of the account to burn shares + /// @param amount_ An amount of shares to burn + function bridgeBurnShares(address account_, uint256 amount_) external; +} + /// @author kovalgek /// @notice Rebasable token that wraps/unwraps non-rebasable token and allow to mint/burn tokens by bridge. contract ERC20Rebasable is IERC20, IERC20Wrapper, IERC20BridgedShares, ERC20Metadata { @@ -19,10 +35,10 @@ contract ERC20Rebasable is IERC20, IERC20Wrapper, IERC20BridgedShares, ERC20Meta using UnstructuredStorage for bytes32; /// @inheritdoc IERC20BridgedShares - address public immutable BRIDGE; + address public immutable L2_ERC20_TOKEN_BRIDGE; - /// @notice Contract of non-rebasable token to wrap. - IERC20 public immutable WRAPPED_TOKEN; + /// @notice Contract of non-rebasable token to wrap from. + IERC20 public immutable TOKEN_TO_WRAP_FROM; /// @notice Oracle contract used to get token rate for wrapping/unwrapping tokens. ITokenRateOracle public immutable TOKEN_RATE_ORACLE; @@ -41,18 +57,18 @@ contract ERC20Rebasable is IERC20, IERC20Wrapper, IERC20BridgedShares, ERC20Meta /// @param decimals_ The decimals places of the token /// @param wrappedToken_ address of the ERC20 token to wrap /// @param tokenRateOracle_ address of oracle that returns tokens rate - /// @param bridge_ The bridge address which allowd to mint/burn tokens + /// @param l2ERC20TokenBridge_ The bridge address which allowd to mint/burn tokens constructor( string memory name_, string memory symbol_, uint8 decimals_, address wrappedToken_, address tokenRateOracle_, - address bridge_ + address l2ERC20TokenBridge_ ) ERC20Metadata(name_, symbol_, decimals_) { - WRAPPED_TOKEN = IERC20(wrappedToken_); + TOKEN_TO_WRAP_FROM = IERC20(wrappedToken_); TOKEN_RATE_ORACLE = ITokenRateOracle(tokenRateOracle_); - BRIDGE = bridge_; + L2_ERC20_TOKEN_BRIDGE = l2ERC20TokenBridge_; } /// @notice Sets the name and the symbol of the tokens if they both are empty @@ -68,7 +84,7 @@ contract ERC20Rebasable is IERC20, IERC20Wrapper, IERC20BridgedShares, ERC20Meta if (sharesAmount_ == 0) revert ErrorZeroSharesWrap(); _mintShares(msg.sender, sharesAmount_); - if(!WRAPPED_TOKEN.transferFrom(msg.sender, address(this), sharesAmount_)) revert ErrorERC20Transfer(); + if(!TOKEN_TO_WRAP_FROM.transferFrom(msg.sender, address(this), sharesAmount_)) revert ErrorERC20Transfer(); return _getTokensByShares(sharesAmount_); } @@ -79,7 +95,7 @@ contract ERC20Rebasable is IERC20, IERC20Wrapper, IERC20BridgedShares, ERC20Meta uint256 sharesAmount = _getSharesByTokens(tokenAmount_); _burnShares(msg.sender, sharesAmount); - if(!WRAPPED_TOKEN.transfer(msg.sender, sharesAmount)) revert ErrorERC20Transfer(); + if(!TOKEN_TO_WRAP_FROM.transfer(msg.sender, sharesAmount)) revert ErrorERC20Transfer(); return sharesAmount; } @@ -249,12 +265,13 @@ contract ERC20Rebasable is IERC20, IERC20Wrapper, IERC20BridgedShares, ERC20Meta if (rateDecimals == uint8(0)) revert ErrorTokenRateDecimalsIsZero(); //slither-disable-next-line unused-return - (, - int256 answer - , - , - uint256 updatedAt - ,) = TOKEN_RATE_ORACLE.latestRoundData(); + ( + /* roundId_ */, + int256 answer, + /* startedAt_ */, + uint256 updatedAt, + /* answeredInRound_ */ + ) = TOKEN_RATE_ORACLE.latestRoundData(); if (updatedAt == 0) revert ErrorWrongOracleUpdateTime(); if (answer <= 0) revert ErrorOracleAnswerIsNotPositive(); @@ -312,7 +329,7 @@ contract ERC20Rebasable is IERC20, IERC20Wrapper, IERC20BridgedShares, ERC20Meta function _emitTransferEvents( address _from, address _to, - uint _tokenAmount, + uint256 _tokenAmount, uint256 _sharesAmount ) internal { emit Transfer(_from, _to, _tokenAmount); @@ -329,7 +346,7 @@ contract ERC20Rebasable is IERC20, IERC20Wrapper, IERC20BridgedShares, ERC20Meta /// @dev Validates that sender of the transaction is the bridge modifier onlyBridge() { - if (msg.sender != BRIDGE) { + if (msg.sender != L2_ERC20_TOKEN_BRIDGE) { revert ErrorNotBridge(); } _; diff --git a/contracts/token/ERC20RebasablePermit.sol b/contracts/token/ERC20RebasablePermit.sol index 57606446..03c4fb65 100644 --- a/contracts/token/ERC20RebasablePermit.sol +++ b/contracts/token/ERC20RebasablePermit.sol @@ -4,9 +4,10 @@ pragma solidity 0.8.10; import {ERC20Rebasable} from "./ERC20Rebasable.sol"; -import {ERC20Permit} from "./ERC20Permit.sol"; +import {PermitExtension} from "./PermitExtension.sol"; -contract ERC20RebasablePermit is ERC20Rebasable, ERC20Permit { +/// @author kovalgek +contract ERC20RebasablePermit is ERC20Rebasable, PermitExtension { /// @param name_ The name of the token /// @param symbol_ The symbol of the token @@ -25,7 +26,7 @@ contract ERC20RebasablePermit is ERC20Rebasable, ERC20Permit { address bridge_ ) ERC20Rebasable(name_, symbol_, decimals_, wrappedToken_, tokenRateOracle_, bridge_) - ERC20Permit(name_, version_) + PermitExtension(name_, version_) { } diff --git a/contracts/token/ERC20Permit.sol b/contracts/token/PermitExtension.sol similarity index 57% rename from contracts/token/ERC20Permit.sol rename to contracts/token/PermitExtension.sol index 634731c7..ed68d6c3 100644 --- a/contracts/token/ERC20Permit.sol +++ b/contracts/token/PermitExtension.sol @@ -8,21 +8,19 @@ import {EIP712} from "@openzeppelin/contracts-v4.9/utils/cryptography/EIP712.sol import {IERC2612} from "@openzeppelin/contracts-v4.9/interfaces/IERC2612.sol"; import {SignatureChecker} from "../lib/SignatureChecker.sol"; -contract ERC20Permit is IERC2612, EIP712 { +abstract contract PermitExtension is IERC2612, EIP712 { using UnstructuredStorage for bytes32; - /** - * @dev Nonces for ERC-2612 (Permit) - */ + /// @dev Nonces for ERC-2612 (Permit) mapping(address => uint256) internal noncesByAddress; // TODO: outline structured storage used because at least EIP712 uses it - /** - * @dev Typehash constant for ERC-2612 (Permit) - * - * keccak256("Permit(address owner,address spender,uint256 value,uint256 nonce,uint256 deadline)") - */ + + /// @dev Typehash constant for ERC-2612 (Permit) + /// + /// keccak256("Permit(address owner,address spender,uint256 value,uint256 nonce,uint256 deadline)") + /// bytes32 internal constant PERMIT_TYPEHASH = 0x6e71edae12b1b97f4d1f60370fef10105fa2faae0126114a169c64845d6126c9; @@ -35,19 +33,18 @@ contract ERC20Permit is IERC2612, EIP712 { { } - /** - * @dev Sets `value` as the allowance of `spender` over ``owner``'s tokens, - * given ``owner``'s signed approval. - * Emits an {Approval} event. - * - * Requirements: - * - * - `spender` cannot be the zero address. - * - `deadline` must be a timestamp in the future. - * - `v`, `r` and `s` must be a valid `secp256k1` signature from `owner` - * over the EIP712-formatted function arguments. - * - the signature must use ``owner``'s current nonce (see {nonces}). - */ + /// @dev Sets `value` as the allowance of `spender` over ``owner``'s tokens, + /// given ``owner``'s signed approval. + /// Emits an {Approval} event. + /// + /// Requirements: + /// + /// - `spender` cannot be the zero address. + /// - `deadline` must be a timestamp in the future. + /// - `v`, `r` and `s` must be a valid `secp256k1` signature from `owner` + /// over the EIP712-formatted function arguments. + /// - the signature must use ``owner``'s current nonce (see {nonces}). + /// function permit( address _owner, address _spender, uint256 _value, uint256 _deadline, uint8 _v, bytes32 _r, bytes32 _s ) external { @@ -61,47 +58,40 @@ contract ERC20Permit is IERC2612, EIP712 { bytes32 hash = _hashTypedDataV4(structHash); - if (!SignatureChecker.isValidSignatureNow(_owner, hash, _v, _r, _s)) { + if (!SignatureChecker.isValidSignature(_owner, hash, _v, _r, _s)) { revert ErrorInvalidSignature(); } _permitAccepted(_owner, _spender, _value); } - function _permitAccepted( - address owner_, - address spender_, - uint256 amount_ - ) internal virtual { - } - /** - * @dev Returns the current nonce for `owner`. This value must be - * included whenever a signature is generated for {permit}. - * - * Every successful call to {permit} increases ``owner``'s nonce by one. This - * prevents a signature from being used multiple times. - */ + /// @dev Returns the current nonce for `owner`. This value must be + /// included whenever a signature is generated for {permit}. + /// + /// Every successful call to {permit} increases ``owner``'s nonce by one. This + /// prevents a signature from being used multiple times. + /// function nonces(address owner) external view returns (uint256) { return noncesByAddress[owner]; } - /** - * @dev Returns the domain separator used in the encoding of the signature for {permit}, as defined by {EIP712}. - */ + /// @dev Returns the domain separator used in the encoding of the signature for {permit}, as defined by {EIP712}. // solhint-disable-next-line func-name-mixedcase function DOMAIN_SEPARATOR() external view returns (bytes32) { return _domainSeparatorV4(); } - /** - * @dev "Consume a nonce": return the current value and increment. - */ + + /// @dev "Consume a nonce": return the current value and increment. function _useNonce(address _owner) internal returns (uint256 current) { current = noncesByAddress[_owner]; noncesByAddress[_owner] = current + 1; } + /// @dev is used to override in inherited contracts and call approve function + function _permitAccepted(address owner_, address spender_, uint256 amount_) internal virtual; + error ErrorInvalidSignature(); error ErrorDeadlineExpired(); } diff --git a/contracts/token/interfaces/IERC20Bridged.sol b/contracts/token/interfaces/IERC20Bridged.sol deleted file mode 100644 index f29633d9..00000000 --- a/contracts/token/interfaces/IERC20Bridged.sol +++ /dev/null @@ -1,23 +0,0 @@ -// SPDX-FileCopyrightText: 2022 Lido -// SPDX-License-Identifier: GPL-3.0 - -pragma solidity 0.8.10; - -import {IERC20} from "@openzeppelin/contracts/token/ERC20/IERC20.sol"; - -/// @author psirex -/// @notice Extends the ERC20 functionality that allows the bridge to mint/burn tokens -interface IERC20Bridged is IERC20 { - /// @notice Returns bridge which can mint and burn tokens on L2 - function bridge() external view returns (address); - - /// @notice Creates amount_ tokens and assigns them to account_, increasing the total supply - /// @param account_ An address of the account to mint tokens - /// @param amount_ An amount of tokens to mint - function bridgeMint(address account_, uint256 amount_) external; - - /// @notice Destroys amount_ tokens from account_, reducing the total supply - /// @param account_ An address of the account to burn tokens - /// @param amount_ An amount of tokens to burn - function bridgeBurn(address account_, uint256 amount_) external; -} diff --git a/contracts/token/interfaces/IERC20BridgedShares.sol b/contracts/token/interfaces/IERC20BridgedShares.sol deleted file mode 100644 index 8ae4be6d..00000000 --- a/contracts/token/interfaces/IERC20BridgedShares.sol +++ /dev/null @@ -1,23 +0,0 @@ -// SPDX-FileCopyrightText: 2022 Lido -// SPDX-License-Identifier: GPL-3.0 - -pragma solidity 0.8.10; - -import {IERC20} from "@openzeppelin/contracts/token/ERC20/IERC20.sol"; - -/// @author kovalgek -/// @notice Extends the ERC20 functionality that allows the bridge to mint/burn shares -interface IERC20BridgedShares is IERC20 { - /// @notice Returns bridge which can mint and burn shares on L2 - function BRIDGE() external view returns (address); - - /// @notice Creates amount_ shares and assigns them to account_, increasing the total shares supply - /// @param account_ An address of the account to mint shares - /// @param amount_ An amount of shares to mint - function bridgeMintShares(address account_, uint256 amount_) external; - - /// @notice Destroys amount_ shares from account_, reducing the total shares supply - /// @param account_ An address of the account to burn shares - /// @param amount_ An amount of shares to burn - function bridgeBurnShares(address account_, uint256 amount_) external; -} diff --git a/contracts/token/interfaces/IERC20Metadata.sol b/contracts/token/interfaces/IERC20Metadata.sol deleted file mode 100644 index a7c82d00..00000000 --- a/contracts/token/interfaces/IERC20Metadata.sol +++ /dev/null @@ -1,17 +0,0 @@ -// SPDX-FileCopyrightText: 2022 Lido -// SPDX-License-Identifier: GPL-3.0 - -pragma solidity 0.8.10; - -/// @author psirex -/// @notice Interface for the optional metadata functions from the ERC20 standard. -interface IERC20Metadata { - /// @dev Returns the name of the token. - function name() external view returns (string memory); - - /// @dev Returns the symbol of the token. - function symbol() external view returns (string memory); - - /// @dev Returns the decimals places of the token. - function decimals() external view returns (uint8); -} diff --git a/contracts/token/interfaces/IERC20TokenRate.sol b/contracts/token/interfaces/IERC20TokenRate.sol deleted file mode 100644 index 0b57716e..00000000 --- a/contracts/token/interfaces/IERC20TokenRate.sol +++ /dev/null @@ -1,12 +0,0 @@ -// SPDX-FileCopyrightText: 2024 Lido -// SPDX-License-Identifier: GPL-3.0 - -pragma solidity 0.8.10; - -/// @author kovalgek -/// @notice Token rate interface. -interface IERC20TokenRate { - - /// @notice Returns token rate. - function tokenRate() external view returns (uint256); -} diff --git a/contracts/token/interfaces/IERC20WstETH.sol b/contracts/token/interfaces/IERC20WstETH.sol deleted file mode 100644 index 4bb216c4..00000000 --- a/contracts/token/interfaces/IERC20WstETH.sol +++ /dev/null @@ -1,14 +0,0 @@ -// SPDX-FileCopyrightText: 2024 Lido -// SPDX-License-Identifier: GPL-3.0 - -pragma solidity 0.8.10; - -/// @author kovalgek -/// @notice A subset of wstETH token interface of core LIDO protocol. -interface IERC20WstETH { - /** - * @notice Get amount of wstETH for a one stETH - * @return Amount of wstETH for a 1 stETH - */ - function stEthPerToken() external view returns (uint256); -} From 11c2b0478ec70691a43f1b87432dea3e6e09593b Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Tue, 16 Apr 2024 17:38:55 +0000 Subject: [PATCH 061/148] Bump undici from 5.26.3 to 5.28.4 Bumps [undici](https://github.com/nodejs/undici) from 5.26.3 to 5.28.4. - [Release notes](https://github.com/nodejs/undici/releases) - [Commits](https://github.com/nodejs/undici/compare/v5.26.3...v5.28.4) --- updated-dependencies: - dependency-name: undici dependency-type: indirect ... Signed-off-by: dependabot[bot] --- package-lock.json | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/package-lock.json b/package-lock.json index cfddd6b4..64dc9b69 100644 --- a/package-lock.json +++ b/package-lock.json @@ -23148,9 +23148,9 @@ } }, "node_modules/undici": { - "version": "5.26.3", - "resolved": "https://registry.npmjs.org/undici/-/undici-5.26.3.tgz", - "integrity": "sha512-H7n2zmKEWgOllKkIUkLvFmsJQj062lSm3uA4EYApG8gLuiOM0/go9bIoC3HVaSnfg4xunowDE2i9p8drkXuvDw==", + "version": "5.28.4", + "resolved": "https://registry.npmjs.org/undici/-/undici-5.28.4.tgz", + "integrity": "sha512-72RFADWFqKmUb2hmmvNODKL3p9hcB6Gt2DOQMis1SEBaV6a4MH8soBvzg+95CYhCKPFedut2JY9bMfrDl9D23g==", "dev": true, "dependencies": { "@fastify/busboy": "^2.0.0" @@ -42631,9 +42631,9 @@ } }, "undici": { - "version": "5.26.3", - "resolved": "https://registry.npmjs.org/undici/-/undici-5.26.3.tgz", - "integrity": "sha512-H7n2zmKEWgOllKkIUkLvFmsJQj062lSm3uA4EYApG8gLuiOM0/go9bIoC3HVaSnfg4xunowDE2i9p8drkXuvDw==", + "version": "5.28.4", + "resolved": "https://registry.npmjs.org/undici/-/undici-5.28.4.tgz", + "integrity": "sha512-72RFADWFqKmUb2hmmvNODKL3p9hcB6Gt2DOQMis1SEBaV6a4MH8soBvzg+95CYhCKPFedut2JY9bMfrDl9D23g==", "dev": true, "requires": { "@fastify/busboy": "^2.0.0" From d13586bc7576e8986383cf6f900943edb96471ee Mon Sep 17 00:00:00 2001 From: Anton Kovalchuk Date: Tue, 16 Apr 2024 21:54:56 +0200 Subject: [PATCH 062/148] remove arbitrum folders --- .env.wsteth.arb_goerli | 75 -- .env.wsteth.arb_mainnet | 75 -- abi/arbitrum/L1GatewayRouter.json | 362 ------- abi/arbitrum/L2GatewayRouter.json | 244 ----- .../arbitrum/InterchainERC20TokenGateway.sol | 71 -- contracts/arbitrum/L1CrossDomainEnabled.sol | 104 -- contracts/arbitrum/L1ERC20TokenGateway.sol | 120 --- contracts/arbitrum/L2CrossDomainEnabled.sol | 64 -- contracts/arbitrum/L2ERC20TokenGateway.sol | 90 -- contracts/arbitrum/README.md | 910 ----------------- contracts/arbitrum/interfaces/IArbSys.sol | 17 - contracts/arbitrum/interfaces/IBridge.sol | 8 - contracts/arbitrum/interfaces/IInbox.sol | 31 - .../interfaces/IInterchainTokenGateway.sol | 50 - .../arbitrum/interfaces/IL1TokenGateway.sol | 42 - .../arbitrum/interfaces/IL2TokenGateway.sol | 40 - contracts/arbitrum/interfaces/IOutbox.sol | 8 - .../libraries/L1OutboundDataParser.sol | 45 - .../libraries/L2OutboundDataParser.sol | 30 - contracts/arbitrum/stubs/ArbSysStub.sol | 24 - contracts/arbitrum/stubs/BridgeStub.sol | 18 - contracts/arbitrum/stubs/InboxStub.sol | 55 - contracts/arbitrum/stubs/OutboxStub.sol | 14 - interfaces/arbitrum/Bridge.json | 265 ----- interfaces/arbitrum/IMessageProvider.json | 21 - interfaces/arbitrum/Inbox.json | 328 ------ scripts/arbitrum/deploy-gateway-routers.ts | 28 - scripts/arbitrum/deploy-gateway.ts | 77 -- scripts/arbitrum/finalize-message.ts | 66 -- scripts/arbitrum/update-ethereum-executor.ts | 161 --- .../L1ERC20TokensGateway.unit.test.ts | 940 ------------------ .../L2ERC20TokensGateway.unit.test.ts | 663 ------------ test/arbitrum/_launch.test.ts | 89 -- test/arbitrum/bridging-native.e2e.test.ts | 205 ---- test/arbitrum/bridging-router.e2e.test.ts | 145 --- test/arbitrum/bridging.integration.test.ts | 504 ---------- test/arbitrum/deployment.acceptance.test.ts | 313 ------ test/arbitrum/managing-deposits.e2e.test.ts | 194 ---- test/arbitrum/managing-executor.e2e.test.ts | 164 --- test/arbitrum/managing-proxy.e2e.test.ts | 284 ------ .../arbitrum.integration.test.ts | 273 ----- utils/arbitrum/addresses.ts | 34 - .../artifacts/ArbitrumBridgeExecutor.json | 696 ------------- utils/arbitrum/artifacts/L1GatewayRouter.json | 582 ----------- utils/arbitrum/artifacts/L2GatewayRouter.json | 408 -------- utils/arbitrum/contracts.ts | 37 - utils/arbitrum/deployment.ts | 203 ---- utils/arbitrum/index.ts | 13 - utils/arbitrum/messaging.ts | 111 --- utils/arbitrum/testing.ts | 337 ------- utils/arbitrum/types.ts | 15 - 51 files changed, 9653 deletions(-) delete mode 100644 .env.wsteth.arb_goerli delete mode 100644 .env.wsteth.arb_mainnet delete mode 100644 abi/arbitrum/L1GatewayRouter.json delete mode 100644 abi/arbitrum/L2GatewayRouter.json delete mode 100644 contracts/arbitrum/InterchainERC20TokenGateway.sol delete mode 100644 contracts/arbitrum/L1CrossDomainEnabled.sol delete mode 100644 contracts/arbitrum/L1ERC20TokenGateway.sol delete mode 100644 contracts/arbitrum/L2CrossDomainEnabled.sol delete mode 100644 contracts/arbitrum/L2ERC20TokenGateway.sol delete mode 100644 contracts/arbitrum/README.md delete mode 100644 contracts/arbitrum/interfaces/IArbSys.sol delete mode 100644 contracts/arbitrum/interfaces/IBridge.sol delete mode 100644 contracts/arbitrum/interfaces/IInbox.sol delete mode 100644 contracts/arbitrum/interfaces/IInterchainTokenGateway.sol delete mode 100644 contracts/arbitrum/interfaces/IL1TokenGateway.sol delete mode 100644 contracts/arbitrum/interfaces/IL2TokenGateway.sol delete mode 100644 contracts/arbitrum/interfaces/IOutbox.sol delete mode 100644 contracts/arbitrum/libraries/L1OutboundDataParser.sol delete mode 100644 contracts/arbitrum/libraries/L2OutboundDataParser.sol delete mode 100644 contracts/arbitrum/stubs/ArbSysStub.sol delete mode 100644 contracts/arbitrum/stubs/BridgeStub.sol delete mode 100644 contracts/arbitrum/stubs/InboxStub.sol delete mode 100644 contracts/arbitrum/stubs/OutboxStub.sol delete mode 100644 interfaces/arbitrum/Bridge.json delete mode 100644 interfaces/arbitrum/IMessageProvider.json delete mode 100644 interfaces/arbitrum/Inbox.json delete mode 100644 scripts/arbitrum/deploy-gateway-routers.ts delete mode 100644 scripts/arbitrum/deploy-gateway.ts delete mode 100644 scripts/arbitrum/finalize-message.ts delete mode 100644 scripts/arbitrum/update-ethereum-executor.ts delete mode 100644 test/arbitrum/L1ERC20TokensGateway.unit.test.ts delete mode 100644 test/arbitrum/L2ERC20TokensGateway.unit.test.ts delete mode 100644 test/arbitrum/_launch.test.ts delete mode 100644 test/arbitrum/bridging-native.e2e.test.ts delete mode 100644 test/arbitrum/bridging-router.e2e.test.ts delete mode 100644 test/arbitrum/bridging.integration.test.ts delete mode 100644 test/arbitrum/deployment.acceptance.test.ts delete mode 100644 test/arbitrum/managing-deposits.e2e.test.ts delete mode 100644 test/arbitrum/managing-executor.e2e.test.ts delete mode 100644 test/arbitrum/managing-proxy.e2e.test.ts delete mode 100644 test/bridge-executor/arbitrum.integration.test.ts delete mode 100644 utils/arbitrum/addresses.ts delete mode 100644 utils/arbitrum/artifacts/ArbitrumBridgeExecutor.json delete mode 100644 utils/arbitrum/artifacts/L1GatewayRouter.json delete mode 100644 utils/arbitrum/artifacts/L2GatewayRouter.json delete mode 100644 utils/arbitrum/contracts.ts delete mode 100644 utils/arbitrum/deployment.ts delete mode 100644 utils/arbitrum/index.ts delete mode 100644 utils/arbitrum/messaging.ts delete mode 100644 utils/arbitrum/testing.ts delete mode 100644 utils/arbitrum/types.ts diff --git a/.env.wsteth.arb_goerli b/.env.wsteth.arb_goerli deleted file mode 100644 index 6694eda7..00000000 --- a/.env.wsteth.arb_goerli +++ /dev/null @@ -1,75 +0,0 @@ -# Detailed info: https://github.com/lidofinance/lido-l2#Project-Configuration - -# ############################ -# RPCs -# ############################ - -RPC_ETH_GOERLI= -RPC_ARB_GOERLI=https://goerli-rollup.arbitrum.io/rpc - -# ############################ -# Etherscan -# ############################ - -ETHERSCAN_API_KEY_ETH= -ETHERSCAN_API_KEY_ARB= - -# ############################ -# Bridge/Gateway Deployment -# ############################ - -# Address of the token to deploy the bridge/gateway for -TOKEN=0x6320cd32aa674d2898a68ec82e869385fc5f7e2f - -# Name of the network environments used by deployment scripts. -# Might be one of: "mainnet", "goerli". -NETWORK=goerli - -# Run deployment in the forking network instead of public ones -FORKING=true - -# Private key of the deployer account used for deployment process - -ETH_DEPLOYER_PRIVATE_KEY= -ARB_DEPLOYER_PRIVATE_KEY= - -L1_PROXY_ADMIN=0x4333218072D5d7008546737786663c38B4D561A4 -L1_BRIDGE_ADMIN=0x4333218072D5d7008546737786663c38B4D561A4 -L1_DEPOSITS_ENABLED=true -L1_WITHDRAWALS_ENABLED=true -L1_DEPOSITS_ENABLERS=["0x4333218072D5d7008546737786663c38B4D561A4"] -L1_DEPOSITS_DISABLERS=["0x4333218072D5d7008546737786663c38B4D561A4","0x7fE7fa4EF7D134Dbf8B616Ba7B675F26286BC2cd"] -L1_WITHDRAWALS_ENABLERS=["0x4333218072D5d7008546737786663c38B4D561A4"] -L1_WITHDRAWALS_DISABLERS=["0x4333218072D5d7008546737786663c38B4D561A4","0x7fE7fa4EF7D134Dbf8B616Ba7B675F26286BC2cd"] - -L2_PROXY_ADMIN=0x43De3B7115baA4EbAbd7c5Eaf4cB2856238C6A50 -L2_BRIDGE_ADMIN=0x43De3B7115baA4EbAbd7c5Eaf4cB2856238C6A50 -L2_DEPOSITS_ENABLED=true -L2_WITHDRAWALS_ENABLED=true -L2_DEPOSITS_ENABLERS=["0x43De3B7115baA4EbAbd7c5Eaf4cB2856238C6A50"] -L2_DEPOSITS_DISABLERS=["0x43De3B7115baA4EbAbd7c5Eaf4cB2856238C6A50","0x7fE7fa4EF7D134Dbf8B616Ba7B675F26286BC2cd"] -L2_WITHDRAWALS_ENABLERS=["0x43De3B7115baA4EbAbd7c5Eaf4cB2856238C6A50"] -L2_WITHDRAWALS_DISABLERS=["0x43De3B7115baA4EbAbd7c5Eaf4cB2856238C6A50","0x7fE7fa4EF7D134Dbf8B616Ba7B675F26286BC2cd"] - -# ############################ -# Integration Acceptance & E2E Testing -# ############################ - -TESTING_ARB_NETWORK=goerli -TESTING_ARB_L1_TOKEN=0x6320cd32aa674d2898a68ec82e869385fc5f7e2f -TESTING_ARB_L2_TOKEN=0xbED18985eC648Ce4b0C5Fc3061d1323116702BC4 -TESTING_ARB_L1_ERC20_TOKEN_GATEWAY=0x0ecCFbBEe34f04187361818832385EB4cC11b678 -TESTING_ARB_L2_ERC20_TOKEN_GATEWAY=0x12dD5832Fd7e02e49d97D1CBBd14579794945c1E - -# ############################ -# Integration Testing -# ############################ - -TESTING_USE_DEPLOYED_CONTRACTS=true -TESTING_L1_TOKENS_HOLDER= - -# ############################ -# E2E Testing -# ############################ - -TESTING_PRIVATE_KEY= diff --git a/.env.wsteth.arb_mainnet b/.env.wsteth.arb_mainnet deleted file mode 100644 index 782ee7a1..00000000 --- a/.env.wsteth.arb_mainnet +++ /dev/null @@ -1,75 +0,0 @@ -# Detailed info: https://github.com/lidofinance/lido-l2#Project-Configuration - -# ############################ -# RPCs -# ############################ - -RPC_ETH_MAINNET= -RPC_ARB_MAINNET= - -# ############################ -# Etherscan -# ############################ - -ETHERSCAN_API_KEY_ETH= -ETHERSCAN_API_KEY_ARB= - -# ############################ -# Bridge/Gateway Deployment -# ############################ - -# Address of the token to deploy the bridge/gateway for -TOKEN=0x7f39C581F595B53c5cb19bD0b3f8dA6c935E2Ca0 - -# Name of the network environments used by deployment scripts. -# Might be one of: "mainnet", "goerli". -NETWORK=mainnet - -# Run deployment in the forking network instead of public ones -FORKING=true - -# Private key of the deployer account used for deployment process - -ETH_DEPLOYER_PRIVATE_KEY= -ARB_DEPLOYER_PRIVATE_KEY= - -L1_PROXY_ADMIN=0x3e40D73EB977Dc6a537aF587D48316feE66E9C8c -L1_BRIDGE_ADMIN=0x3e40D73EB977Dc6a537aF587D48316feE66E9C8c -L1_DEPOSITS_ENABLED=false -L1_WITHDRAWALS_ENABLED=true -L1_DEPOSITS_ENABLERS=["0x3e40D73EB977Dc6a537aF587D48316feE66E9C8c","0x3cd9F71F80AB08ea5a7Dca348B5e94BC595f26A0"] -L1_DEPOSITS_DISABLERS=["0x3e40D73EB977Dc6a537aF587D48316feE66E9C8c","0x73b047fe6337183A454c5217241D780a932777bD"] -L1_WITHDRAWALS_ENABLERS=["0x3e40D73EB977Dc6a537aF587D48316feE66E9C8c"] -L1_WITHDRAWALS_DISABLERS=["0x3e40D73EB977Dc6a537aF587D48316feE66E9C8c","0x73b047fe6337183A454c5217241D780a932777bD"] - -L2_PROXY_ADMIN=0x1dcA41859Cd23b526CBe74dA8F48aC96e14B1A29 -L2_BRIDGE_ADMIN=0x1dcA41859Cd23b526CBe74dA8F48aC96e14B1A29 -L2_DEPOSITS_ENABLED=true -L2_WITHDRAWALS_ENABLED=true -L2_DEPOSITS_ENABLERS=["0x1dcA41859Cd23b526CBe74dA8F48aC96e14B1A29"] -L2_DEPOSITS_DISABLERS=["0x1dcA41859Cd23b526CBe74dA8F48aC96e14B1A29","0xfDCf209A213a0b3C403d543F87E74FCbcA11de34"] -L2_WITHDRAWALS_ENABLERS=["0x1dcA41859Cd23b526CBe74dA8F48aC96e14B1A29"] -L2_WITHDRAWALS_DISABLERS=["0x1dcA41859Cd23b526CBe74dA8F48aC96e14B1A29","0xfDCf209A213a0b3C403d543F87E74FCbcA11de34"] - -# ############################ -# Integration Acceptance & E2E Testing -# ############################ - -TESTING_ARB_NETWORK=mainnet -TESTING_ARB_L1_TOKEN=0x7f39C581F595B53c5cb19bD0b3f8dA6c935E2Ca0 -TESTING_ARB_L2_TOKEN=0x5979D7b546E38E414F7E9822514be443A4800529 -TESTING_ARB_L1_ERC20_TOKEN_GATEWAY=0x0F25c1DC2a9922304f2eac71DCa9B07E310e8E5a -TESTING_ARB_L2_ERC20_TOKEN_GATEWAY=0x07D4692291B9E30E326fd31706f686f83f331B82 - -# ############################ -# Integration Testing -# ############################ - -TESTING_USE_DEPLOYED_CONTRACTS=true -TESTING_L1_TOKENS_HOLDER= - -# ############################ -# E2E Testing -# ############################ - -TESTING_PRIVATE_KEY= diff --git a/abi/arbitrum/L1GatewayRouter.json b/abi/arbitrum/L1GatewayRouter.json deleted file mode 100644 index c3fd4bcf..00000000 --- a/abi/arbitrum/L1GatewayRouter.json +++ /dev/null @@ -1,362 +0,0 @@ -[ - { - "anonymous": false, - "inputs": [ - { - "indexed": false, - "internalType": "address", - "name": "newDefaultGateway", - "type": "address" - } - ], - "name": "DefaultGatewayUpdated", - "type": "event" - }, - { - "anonymous": false, - "inputs": [ - { - "indexed": true, - "internalType": "address", - "name": "l1Token", - "type": "address" - }, - { - "indexed": true, - "internalType": "address", - "name": "gateway", - "type": "address" - } - ], - "name": "GatewaySet", - "type": "event" - }, - { - "anonymous": false, - "inputs": [ - { - "indexed": true, - "internalType": "address", - "name": "token", - "type": "address" - }, - { - "indexed": true, - "internalType": "address", - "name": "_userFrom", - "type": "address" - }, - { - "indexed": true, - "internalType": "address", - "name": "_userTo", - "type": "address" - }, - { - "indexed": false, - "internalType": "address", - "name": "gateway", - "type": "address" - } - ], - "name": "TransferRouted", - "type": "event" - }, - { - "anonymous": false, - "inputs": [ - { - "indexed": true, - "internalType": "address", - "name": "_from", - "type": "address" - }, - { - "indexed": true, - "internalType": "address", - "name": "_to", - "type": "address" - }, - { - "indexed": true, - "internalType": "uint256", - "name": "_seqNum", - "type": "uint256" - }, - { - "indexed": false, - "internalType": "bytes", - "name": "_data", - "type": "bytes" - } - ], - "name": "TxToL2", - "type": "event" - }, - { - "anonymous": false, - "inputs": [ - { - "indexed": false, - "internalType": "address", - "name": "newSource", - "type": "address" - } - ], - "name": "WhitelistSourceUpdated", - "type": "event" - }, - { - "inputs": [ - { "internalType": "address", "name": "l1ERC20", "type": "address" } - ], - "name": "calculateL2TokenAddress", - "outputs": [{ "internalType": "address", "name": "", "type": "address" }], - "stateMutability": "view", - "type": "function" - }, - { - "inputs": [], - "name": "counterpartGateway", - "outputs": [{ "internalType": "address", "name": "", "type": "address" }], - "stateMutability": "view", - "type": "function" - }, - { - "inputs": [], - "name": "defaultGateway", - "outputs": [{ "internalType": "address", "name": "", "type": "address" }], - "stateMutability": "view", - "type": "function" - }, - { - "inputs": [ - { "internalType": "address", "name": "", "type": "address" }, - { "internalType": "address", "name": "", "type": "address" }, - { "internalType": "address", "name": "", "type": "address" }, - { "internalType": "uint256", "name": "", "type": "uint256" }, - { "internalType": "bytes", "name": "", "type": "bytes" } - ], - "name": "finalizeInboundTransfer", - "outputs": [], - "stateMutability": "payable", - "type": "function" - }, - { - "inputs": [ - { "internalType": "address", "name": "_token", "type": "address" } - ], - "name": "getGateway", - "outputs": [ - { "internalType": "address", "name": "gateway", "type": "address" } - ], - "stateMutability": "view", - "type": "function" - }, - { - "inputs": [ - { "internalType": "address", "name": "_token", "type": "address" }, - { "internalType": "address", "name": "_from", "type": "address" }, - { "internalType": "address", "name": "_to", "type": "address" }, - { "internalType": "uint256", "name": "_amount", "type": "uint256" }, - { "internalType": "bytes", "name": "_data", "type": "bytes" } - ], - "name": "getOutboundCalldata", - "outputs": [{ "internalType": "bytes", "name": "", "type": "bytes" }], - "stateMutability": "view", - "type": "function" - }, - { - "inputs": [], - "name": "inbox", - "outputs": [{ "internalType": "address", "name": "", "type": "address" }], - "stateMutability": "view", - "type": "function" - }, - { - "inputs": [ - { "internalType": "address", "name": "_owner", "type": "address" }, - { - "internalType": "address", - "name": "_defaultGateway", - "type": "address" - }, - { "internalType": "address", "name": "", "type": "address" }, - { - "internalType": "address", - "name": "_counterpartGateway", - "type": "address" - }, - { "internalType": "address", "name": "_inbox", "type": "address" } - ], - "name": "initialize", - "outputs": [], - "stateMutability": "nonpayable", - "type": "function" - }, - { - "inputs": [{ "internalType": "address", "name": "", "type": "address" }], - "name": "l1TokenToGateway", - "outputs": [{ "internalType": "address", "name": "", "type": "address" }], - "stateMutability": "view", - "type": "function" - }, - { - "inputs": [ - { "internalType": "address", "name": "_token", "type": "address" }, - { "internalType": "address", "name": "_to", "type": "address" }, - { "internalType": "uint256", "name": "_amount", "type": "uint256" }, - { "internalType": "uint256", "name": "_maxGas", "type": "uint256" }, - { "internalType": "uint256", "name": "_gasPriceBid", "type": "uint256" }, - { "internalType": "bytes", "name": "_data", "type": "bytes" } - ], - "name": "outboundTransfer", - "outputs": [{ "internalType": "bytes", "name": "", "type": "bytes" }], - "stateMutability": "payable", - "type": "function" - }, - { - "inputs": [ - { "internalType": "address", "name": "_token", "type": "address" }, - { "internalType": "address", "name": "_refundTo", "type": "address" }, - { "internalType": "address", "name": "_to", "type": "address" }, - { "internalType": "uint256", "name": "_amount", "type": "uint256" }, - { "internalType": "uint256", "name": "_maxGas", "type": "uint256" }, - { "internalType": "uint256", "name": "_gasPriceBid", "type": "uint256" }, - { "internalType": "bytes", "name": "_data", "type": "bytes" } - ], - "name": "outboundTransferCustomRefund", - "outputs": [{ "internalType": "bytes", "name": "", "type": "bytes" }], - "stateMutability": "payable", - "type": "function" - }, - { - "inputs": [], - "name": "owner", - "outputs": [{ "internalType": "address", "name": "", "type": "address" }], - "stateMutability": "view", - "type": "function" - }, - { - "inputs": [], - "name": "postUpgradeInit", - "outputs": [], - "stateMutability": "nonpayable", - "type": "function" - }, - { - "inputs": [], - "name": "router", - "outputs": [{ "internalType": "address", "name": "", "type": "address" }], - "stateMutability": "view", - "type": "function" - }, - { - "inputs": [ - { - "internalType": "address", - "name": "newL1DefaultGateway", - "type": "address" - }, - { "internalType": "uint256", "name": "_maxGas", "type": "uint256" }, - { "internalType": "uint256", "name": "_gasPriceBid", "type": "uint256" }, - { - "internalType": "uint256", - "name": "_maxSubmissionCost", - "type": "uint256" - } - ], - "name": "setDefaultGateway", - "outputs": [{ "internalType": "uint256", "name": "", "type": "uint256" }], - "stateMutability": "payable", - "type": "function" - }, - { - "inputs": [ - { "internalType": "address", "name": "_gateway", "type": "address" }, - { "internalType": "uint256", "name": "_maxGas", "type": "uint256" }, - { "internalType": "uint256", "name": "_gasPriceBid", "type": "uint256" }, - { - "internalType": "uint256", - "name": "_maxSubmissionCost", - "type": "uint256" - }, - { - "internalType": "address", - "name": "_creditBackAddress", - "type": "address" - } - ], - "name": "setGateway", - "outputs": [{ "internalType": "uint256", "name": "", "type": "uint256" }], - "stateMutability": "payable", - "type": "function" - }, - { - "inputs": [ - { "internalType": "address", "name": "_gateway", "type": "address" }, - { "internalType": "uint256", "name": "_maxGas", "type": "uint256" }, - { "internalType": "uint256", "name": "_gasPriceBid", "type": "uint256" }, - { - "internalType": "uint256", - "name": "_maxSubmissionCost", - "type": "uint256" - } - ], - "name": "setGateway", - "outputs": [{ "internalType": "uint256", "name": "", "type": "uint256" }], - "stateMutability": "payable", - "type": "function" - }, - { - "inputs": [ - { "internalType": "address[]", "name": "_token", "type": "address[]" }, - { "internalType": "address[]", "name": "_gateway", "type": "address[]" }, - { "internalType": "uint256", "name": "_maxGas", "type": "uint256" }, - { "internalType": "uint256", "name": "_gasPriceBid", "type": "uint256" }, - { - "internalType": "uint256", - "name": "_maxSubmissionCost", - "type": "uint256" - } - ], - "name": "setGateways", - "outputs": [{ "internalType": "uint256", "name": "", "type": "uint256" }], - "stateMutability": "payable", - "type": "function" - }, - { - "inputs": [ - { "internalType": "address", "name": "newOwner", "type": "address" } - ], - "name": "setOwner", - "outputs": [], - "stateMutability": "nonpayable", - "type": "function" - }, - { - "inputs": [ - { "internalType": "bytes4", "name": "interfaceId", "type": "bytes4" } - ], - "name": "supportsInterface", - "outputs": [{ "internalType": "bool", "name": "", "type": "bool" }], - "stateMutability": "view", - "type": "function" - }, - { - "inputs": [ - { "internalType": "address", "name": "newSource", "type": "address" } - ], - "name": "updateWhitelistSource", - "outputs": [], - "stateMutability": "nonpayable", - "type": "function" - }, - { - "inputs": [], - "name": "whitelist", - "outputs": [{ "internalType": "address", "name": "", "type": "address" }], - "stateMutability": "view", - "type": "function" - } -] diff --git a/abi/arbitrum/L2GatewayRouter.json b/abi/arbitrum/L2GatewayRouter.json deleted file mode 100644 index c03fa729..00000000 --- a/abi/arbitrum/L2GatewayRouter.json +++ /dev/null @@ -1,244 +0,0 @@ -[ - { - "anonymous": false, - "inputs": [ - { - "indexed": false, - "internalType": "address", - "name": "newDefaultGateway", - "type": "address" - } - ], - "name": "DefaultGatewayUpdated", - "type": "event" - }, - { - "anonymous": false, - "inputs": [ - { - "indexed": true, - "internalType": "address", - "name": "l1Token", - "type": "address" - }, - { - "indexed": true, - "internalType": "address", - "name": "gateway", - "type": "address" - } - ], - "name": "GatewaySet", - "type": "event" - }, - { - "anonymous": false, - "inputs": [ - { - "indexed": true, - "internalType": "address", - "name": "token", - "type": "address" - }, - { - "indexed": true, - "internalType": "address", - "name": "_userFrom", - "type": "address" - }, - { - "indexed": true, - "internalType": "address", - "name": "_userTo", - "type": "address" - }, - { - "indexed": false, - "internalType": "address", - "name": "gateway", - "type": "address" - } - ], - "name": "TransferRouted", - "type": "event" - }, - { - "anonymous": false, - "inputs": [ - { - "indexed": true, - "internalType": "address", - "name": "_from", - "type": "address" - }, - { - "indexed": true, - "internalType": "address", - "name": "_to", - "type": "address" - }, - { - "indexed": true, - "internalType": "uint256", - "name": "_id", - "type": "uint256" - }, - { - "indexed": false, - "internalType": "bytes", - "name": "_data", - "type": "bytes" - } - ], - "name": "TxToL1", - "type": "event" - }, - { - "inputs": [ - { "internalType": "address", "name": "l1ERC20", "type": "address" } - ], - "name": "calculateL2TokenAddress", - "outputs": [{ "internalType": "address", "name": "", "type": "address" }], - "stateMutability": "view", - "type": "function" - }, - { - "inputs": [], - "name": "counterpartGateway", - "outputs": [{ "internalType": "address", "name": "", "type": "address" }], - "stateMutability": "view", - "type": "function" - }, - { - "inputs": [], - "name": "defaultGateway", - "outputs": [{ "internalType": "address", "name": "", "type": "address" }], - "stateMutability": "view", - "type": "function" - }, - { - "inputs": [ - { "internalType": "address", "name": "", "type": "address" }, - { "internalType": "address", "name": "", "type": "address" }, - { "internalType": "address", "name": "", "type": "address" }, - { "internalType": "uint256", "name": "", "type": "uint256" }, - { "internalType": "bytes", "name": "", "type": "bytes" } - ], - "name": "finalizeInboundTransfer", - "outputs": [], - "stateMutability": "payable", - "type": "function" - }, - { - "inputs": [ - { "internalType": "address", "name": "_token", "type": "address" } - ], - "name": "getGateway", - "outputs": [ - { "internalType": "address", "name": "gateway", "type": "address" } - ], - "stateMutability": "view", - "type": "function" - }, - { - "inputs": [ - { "internalType": "address", "name": "_token", "type": "address" }, - { "internalType": "address", "name": "_from", "type": "address" }, - { "internalType": "address", "name": "_to", "type": "address" }, - { "internalType": "uint256", "name": "_amount", "type": "uint256" }, - { "internalType": "bytes", "name": "_data", "type": "bytes" } - ], - "name": "getOutboundCalldata", - "outputs": [{ "internalType": "bytes", "name": "", "type": "bytes" }], - "stateMutability": "view", - "type": "function" - }, - { - "inputs": [ - { - "internalType": "address", - "name": "_counterpartGateway", - "type": "address" - }, - { - "internalType": "address", - "name": "_defaultGateway", - "type": "address" - } - ], - "name": "initialize", - "outputs": [], - "stateMutability": "nonpayable", - "type": "function" - }, - { - "inputs": [{ "internalType": "address", "name": "", "type": "address" }], - "name": "l1TokenToGateway", - "outputs": [{ "internalType": "address", "name": "", "type": "address" }], - "stateMutability": "view", - "type": "function" - }, - { - "inputs": [ - { "internalType": "address", "name": "_l1Token", "type": "address" }, - { "internalType": "address", "name": "_to", "type": "address" }, - { "internalType": "uint256", "name": "_amount", "type": "uint256" }, - { "internalType": "bytes", "name": "_data", "type": "bytes" } - ], - "name": "outboundTransfer", - "outputs": [{ "internalType": "bytes", "name": "", "type": "bytes" }], - "stateMutability": "payable", - "type": "function" - }, - { - "inputs": [ - { "internalType": "address", "name": "_token", "type": "address" }, - { "internalType": "address", "name": "_to", "type": "address" }, - { "internalType": "uint256", "name": "_amount", "type": "uint256" }, - { "internalType": "uint256", "name": "_maxGas", "type": "uint256" }, - { "internalType": "uint256", "name": "_gasPriceBid", "type": "uint256" }, - { "internalType": "bytes", "name": "_data", "type": "bytes" } - ], - "name": "outboundTransfer", - "outputs": [{ "internalType": "bytes", "name": "", "type": "bytes" }], - "stateMutability": "payable", - "type": "function" - }, - { - "inputs": [], - "name": "postUpgradeInit", - "outputs": [], - "stateMutability": "nonpayable", - "type": "function" - }, - { - "inputs": [], - "name": "router", - "outputs": [{ "internalType": "address", "name": "", "type": "address" }], - "stateMutability": "view", - "type": "function" - }, - { - "inputs": [ - { - "internalType": "address", - "name": "newL2DefaultGateway", - "type": "address" - } - ], - "name": "setDefaultGateway", - "outputs": [], - "stateMutability": "nonpayable", - "type": "function" - }, - { - "inputs": [ - { "internalType": "address[]", "name": "_l1Token", "type": "address[]" }, - { "internalType": "address[]", "name": "_gateway", "type": "address[]" } - ], - "name": "setGateway", - "outputs": [], - "stateMutability": "nonpayable", - "type": "function" - } -] diff --git a/contracts/arbitrum/InterchainERC20TokenGateway.sol b/contracts/arbitrum/InterchainERC20TokenGateway.sol deleted file mode 100644 index 329f4c87..00000000 --- a/contracts/arbitrum/InterchainERC20TokenGateway.sol +++ /dev/null @@ -1,71 +0,0 @@ -// SPDX-FileCopyrightText: 2022 Lido -// SPDX-License-Identifier: GPL-3.0 - -pragma solidity 0.8.10; - -import {BridgingManager} from "../BridgingManager.sol"; -import {BridgeableTokens} from "../BridgeableTokens.sol"; - -import {IInterchainTokenGateway} from "./interfaces/IInterchainTokenGateway.sol"; - -/// @author psirex -/// @notice The contract keeps logic shared among both L1 and L2 gateways, adding the methods for -/// bridging management: enabling and disabling withdrawals/deposits -abstract contract InterchainERC20TokenGateway is - BridgingManager, - BridgeableTokens, - IInterchainTokenGateway -{ - /// @notice Address of the router in the corresponding chain - address public immutable router; - - /// @inheritdoc IInterchainTokenGateway - address public immutable counterpartGateway; - - /// @param router_ Address of the router in the corresponding chain - /// @param counterpartGateway_ Address of the counterpart gateway used in the bridging process - /// @param l1Token_ Address of the bridged token in the Ethereum chain - /// @param l2Token_ Address of the token minted on the Arbitrum chain when token bridged - constructor( - address router_, - address counterpartGateway_, - address l1Token_, - address l2Token_ - ) BridgeableTokens(l1Token_, l2Token_) { - router = router_; - counterpartGateway = counterpartGateway_; - } - - /// @inheritdoc IInterchainTokenGateway - /// @dev The current implementation returns the l2Token address when passed l1Token_ equals - /// to l1Token declared in the contract and address(0) in other cases - function calculateL2TokenAddress(address l1Token_) - external - view - returns (address) - { - if (l1Token_ == l1Token) { - return l2Token; - } - return address(0); - } - - /// @inheritdoc IInterchainTokenGateway - function getOutboundCalldata( - address l1Token_, - address from_, - address to_, - uint256 amount_, - bytes memory // data_ - ) public pure returns (bytes memory) { - return - abi.encodeWithSelector( - IInterchainTokenGateway.finalizeInboundTransfer.selector, - l1Token_, - from_, - to_, - amount_, - "" - ); - } -} diff --git a/contracts/arbitrum/L1CrossDomainEnabled.sol b/contracts/arbitrum/L1CrossDomainEnabled.sol deleted file mode 100644 index 5a7b4e06..00000000 --- a/contracts/arbitrum/L1CrossDomainEnabled.sol +++ /dev/null @@ -1,104 +0,0 @@ -// SPDX-FileCopyrightText: 2022 Lido -// SPDX-License-Identifier: GPL-3.0 - -pragma solidity 0.8.10; - -import {IInbox} from "./interfaces/IInbox.sol"; -import {IBridge} from "./interfaces/IBridge.sol"; -import {IOutbox} from "./interfaces/IOutbox.sol"; - -/// @author psirex -/// @notice A helper contract to simplify Ethereum to Arbitrum communication process process -/// via Retryable Tickets -contract L1CrossDomainEnabled { - /// @notice Address of the Arbitrum's Inbox contract - IInbox public immutable inbox; - - /// @param inbox_ Address of the Arbitrum's Inbox contract - constructor(address inbox_) { - inbox = IInbox(inbox_); - } - - /// @dev Properties required to create RetryableTicket - /// @param maxGas Gas limit for immediate L2 execution attempt - /// @param callValue Call-value for L2 transaction - /// @param gasPriceBid L2 Gas price bid for immediate L2 execution attempt - /// @param maxSubmissionCost Amount of ETH allocated to pay for the base submission fee - struct CrossDomainMessageOptions { - uint256 maxGas; - uint256 callValue; - uint256 gasPriceBid; - uint256 maxSubmissionCost; - } - - /// @notice Creates a Retryable Ticket via Inbox.createRetryableTicket function using - /// the provided arguments - /// @param sender_ Address of the sender of the message - /// @param recipient_ Address of the recipient of the message on the L2 chain - /// @param data_ Data passed to the recipient_ in the message - /// @param msgOptions_ Instance of the `CrossDomainMessageOptions` struct - /// @return seqNum Unique id of created Retryable Ticket. - function sendCrossDomainMessage( - address sender_, - address recipient_, - bytes memory data_, - CrossDomainMessageOptions memory msgOptions_ - ) internal returns (uint256 seqNum) { - if (msgOptions_.maxSubmissionCost == 0) { - revert ErrorNoMaxSubmissionCost(); - } - - uint256 minEthValue = msgOptions_.callValue + - msgOptions_.maxSubmissionCost + - (msgOptions_.maxGas * msgOptions_.gasPriceBid); - - if (msg.value < minEthValue) { - revert ErrorETHValueTooLow(); - } - - seqNum = inbox.createRetryableTicket{value: msg.value}( - recipient_, - msgOptions_.callValue, - msgOptions_.maxSubmissionCost, - sender_, - sender_, - msgOptions_.maxGas, - msgOptions_.gasPriceBid, - data_ - ); - - emit TxToL2(sender_, recipient_, seqNum, data_); - } - - /// @notice Validates that transaction was initiated by the crossDomainAccount_ address from - /// the L2 chain - modifier onlyFromCrossDomainAccount(address crossDomainAccount_) { - address bridge = inbox.bridge(); - - // a message coming from the counterpart gateway was executed by the bridge - if (msg.sender != bridge) { - revert ErrorUnauthorizedBridge(); - } - - address l2ToL1Sender = IOutbox(IBridge(bridge).activeOutbox()) - .l2ToL1Sender(); - - // and the outbox reports that the L2 address of the sender is the counterpart gateway - if (l2ToL1Sender != crossDomainAccount_) { - revert ErrorWrongCrossDomainSender(); - } - _; - } - - event TxToL2( - address indexed from, - address indexed to, - uint256 indexed seqNum, - bytes data - ); - - error ErrorETHValueTooLow(); - error ErrorUnauthorizedBridge(); - error ErrorNoMaxSubmissionCost(); - error ErrorWrongCrossDomainSender(); -} diff --git a/contracts/arbitrum/L1ERC20TokenGateway.sol b/contracts/arbitrum/L1ERC20TokenGateway.sol deleted file mode 100644 index 1be951aa..00000000 --- a/contracts/arbitrum/L1ERC20TokenGateway.sol +++ /dev/null @@ -1,120 +0,0 @@ -// SPDX-FileCopyrightText: 2022 Lido -// SPDX-License-Identifier: GPL-3.0 - -pragma solidity 0.8.10; - -import {IERC20} from "@openzeppelin/contracts/token/ERC20/IERC20.sol"; -import {SafeERC20} from "@openzeppelin/contracts/token/ERC20/utils/SafeERC20.sol"; - -import {IL1TokenGateway, IInterchainTokenGateway} from "./interfaces/IL1TokenGateway.sol"; - -import {L1CrossDomainEnabled} from "./L1CrossDomainEnabled.sol"; -import {L1OutboundDataParser} from "./libraries/L1OutboundDataParser.sol"; -import {InterchainERC20TokenGateway} from "./InterchainERC20TokenGateway.sol"; - -/// @author psirex -/// @notice Contract implements ITokenGateway interface and with counterpart L2ERC20TokenGatewy -/// allows bridging registered ERC20 compatible tokens between Ethereum and Arbitrum chains -contract L1ERC20TokenGateway is - InterchainERC20TokenGateway, - L1CrossDomainEnabled, - IL1TokenGateway -{ - using SafeERC20 for IERC20; - - /// @param inbox_ Address of the Arbitrum’s Inbox contract in the L1 chain - /// @param router_ Address of the router in the L1 chain - /// @param counterpartGateway_ Address of the counterpart L2 gateway - /// @param l1Token_ Address of the bridged token in the L1 chain - /// @param l2Token_ Address of the token minted on the Arbitrum chain when token bridged - constructor( - address inbox_, - address router_, - address counterpartGateway_, - address l1Token_, - address l2Token_ - ) - InterchainERC20TokenGateway( - router_, - counterpartGateway_, - l1Token_, - l2Token_ - ) - L1CrossDomainEnabled(inbox_) - {} - - /// @inheritdoc IL1TokenGateway - function outboundTransfer( - address l1Token_, - address to_, - uint256 amount_, - uint256 maxGas_, - uint256 gasPriceBid_, - bytes calldata data_ - ) - external - payable - whenDepositsEnabled - onlyNonZeroAccount(to_) - onlySupportedL1Token(l1Token_) - returns (bytes memory) - { - (address from, uint256 maxSubmissionCost) = L1OutboundDataParser.decode( - router, - data_ - ); - - IERC20(l1Token_).safeTransferFrom(from, address(this), amount_); - - uint256 retryableTicketId = _sendOutboundTransferMessage( - from, - to_, - amount_, - CrossDomainMessageOptions({ - maxGas: maxGas_, - callValue: 0, - gasPriceBid: gasPriceBid_, - maxSubmissionCost: maxSubmissionCost - }) - ); - - emit DepositInitiated(l1Token, from, to_, retryableTicketId, amount_); - - return abi.encode(retryableTicketId); - } - - /// @inheritdoc IInterchainTokenGateway - function finalizeInboundTransfer( - address l1Token_, - address from_, - address to_, - uint256 amount_, - bytes calldata // data_ - ) - external - whenWithdrawalsEnabled - onlySupportedL1Token(l1Token_) - onlyFromCrossDomainAccount(counterpartGateway) - { - IERC20(l1Token_).safeTransfer(to_, amount_); - - // The current implementation doesn't support fast withdrawals, so we - // always use 0 for the exitNum argument in the event - emit WithdrawalFinalized(l1Token_, from_, to_, 0, amount_); - } - - function _sendOutboundTransferMessage( - address from_, - address to_, - uint256 amount_, - CrossDomainMessageOptions memory messageOptions - ) private returns (uint256) { - return - sendCrossDomainMessage( - from_, - counterpartGateway, - getOutboundCalldata(l1Token, from_, to_, amount_, ""), - messageOptions - ); - } -} diff --git a/contracts/arbitrum/L2CrossDomainEnabled.sol b/contracts/arbitrum/L2CrossDomainEnabled.sol deleted file mode 100644 index b3ec7fea..00000000 --- a/contracts/arbitrum/L2CrossDomainEnabled.sol +++ /dev/null @@ -1,64 +0,0 @@ -// SPDX-FileCopyrightText: 2022 Lido -// SPDX-License-Identifier: GPL-3.0 - -pragma solidity 0.8.10; - -import {IArbSys} from "./interfaces/IArbSys.sol"; - -/// @author psirex -/// @notice A helper contract to simplify Arbitrum to Ethereum communication process -contract L2CrossDomainEnabled { - uint160 private constant ADDRESS_OFFSET = - uint160(0x1111000000000000000000000000000000001111); - - /// @notice Address of the Arbitrum’s ArbSys contract - IArbSys public immutable arbSys; - - /// @param arbSys_ Address of the Arbitrum’s ArbSys contract - constructor(address arbSys_) { - arbSys = IArbSys(arbSys_); - } - - /// @notice Sends the message to the Ethereum chain - /// @param sender_ Address of the sender of the message - /// @param recipient_ Address of the recipient of the message on the Ethereum chain - /// @param data_ Data passed to the recipient in the message - /// @return id Unique identifier for this L2-to-L1 transaction - function sendCrossDomainMessage( - address sender_, - address recipient_, - bytes memory data_ - ) internal returns (uint256 id) { - id = IArbSys(arbSys).sendTxToL1(recipient_, data_); - emit TxToL1(sender_, recipient_, id, data_); - } - - /// @dev L1 addresses are transformed durng l1 -> l2 calls - function applyL1ToL2Alias(address l1Address_) - private - pure - returns (address l1Address) - { - unchecked { - l1Address = address(uint160(l1Address_) + ADDRESS_OFFSET); - } - } - - /// @notice Validates that the sender address with applied Arbitrum's aliasing is equal to - /// the crossDomainAccount_ address - modifier onlyFromCrossDomainAccount(address crossDomainAccount_) { - if (msg.sender != applyL1ToL2Alias(crossDomainAccount_)) { - revert ErrorWrongCrossDomainSender(); - } - _; - } - - event TxToL1( - address indexed from, - address indexed to, - uint256 indexed id, - bytes data - ); - - error ErrorWrongCrossDomainSender(); -} diff --git a/contracts/arbitrum/L2ERC20TokenGateway.sol b/contracts/arbitrum/L2ERC20TokenGateway.sol deleted file mode 100644 index 5853d0ac..00000000 --- a/contracts/arbitrum/L2ERC20TokenGateway.sol +++ /dev/null @@ -1,90 +0,0 @@ -// SPDX-FileCopyrightText: 2022 Lido -// SPDX-License-Identifier: GPL-3.0 - -pragma solidity 0.8.10; - -import {IERC20Bridged} from "../token/interfaces/IERC20Bridged.sol"; -import {IL2TokenGateway, IInterchainTokenGateway} from "./interfaces/IL2TokenGateway.sol"; - -import {L2CrossDomainEnabled} from "./L2CrossDomainEnabled.sol"; -import {L2OutboundDataParser} from "./libraries/L2OutboundDataParser.sol"; -import {InterchainERC20TokenGateway} from "./InterchainERC20TokenGateway.sol"; - -/// @author psirex -/// @notice Contract implements ITokenGateway interface and with counterpart L1ERC20TokenGateway -/// allows bridging registered ERC20 compatible tokens between Arbitrum and Ethereum chains -contract L2ERC20TokenGateway is - InterchainERC20TokenGateway, - L2CrossDomainEnabled, - IL2TokenGateway -{ - /// @param arbSys_ Address of the Arbitrum’s ArbSys contract in the L2 chain - /// @param router_ Address of the router in the L2 chain - /// @param counterpartGateway_ Address of the counterpart L1 gateway - /// @param l1Token_ Address of the bridged token in the L1 chain - /// @param l2Token_ Address of the token minted on the Arbitrum chain when token bridged - constructor( - address arbSys_, - address router_, - address counterpartGateway_, - address l1Token_, - address l2Token_ - ) - InterchainERC20TokenGateway( - router_, - counterpartGateway_, - l1Token_, - l2Token_ - ) - L2CrossDomainEnabled(arbSys_) - {} - - /// @inheritdoc IL2TokenGateway - function outboundTransfer( - address l1Token_, - address to_, - uint256 amount_, - uint256, // maxGas - uint256, // gasPriceBid - bytes calldata data_ - ) - external - whenWithdrawalsEnabled - onlySupportedL1Token(l1Token_) - returns (bytes memory res) - { - address from = L2OutboundDataParser.decode(router, data_); - - IERC20Bridged(l2Token).bridgeBurn(from, amount_); - - uint256 id = sendCrossDomainMessage( - from, - counterpartGateway, - getOutboundCalldata(l1Token_, from, to_, amount_, "") - ); - - // The current implementation doesn't support fast withdrawals, so we - // always use 0 for the exitNum argument in the event - emit WithdrawalInitiated(l1Token_, from, to_, id, 0, amount_); - - return abi.encode(id); - } - - /// @inheritdoc IInterchainTokenGateway - function finalizeInboundTransfer( - address l1Token_, - address from_, - address to_, - uint256 amount_, - bytes calldata - ) - external - whenDepositsEnabled - onlySupportedL1Token(l1Token_) - onlyFromCrossDomainAccount(counterpartGateway) - { - IERC20Bridged(l2Token).bridgeMint(to_, amount_); - - emit DepositFinalized(l1Token_, from_, to_, amount_); - } -} diff --git a/contracts/arbitrum/README.md b/contracts/arbitrum/README.md deleted file mode 100644 index cf76c5c1..00000000 --- a/contracts/arbitrum/README.md +++ /dev/null @@ -1,910 +0,0 @@ -# Lido's Arbitrum Gateway - -The document details implementation of the bridging of the ERC20 compatible tokens[^*] between Ethereum and Arbitrum chains via Arbitrum's “Canonical Bridge”. - -It's the first step of the Lido's integration into the Arbitrum protocol. The main goal of the current implementation is to be the strong foundation for the long-term goals of the Lido expansion in the Arbitrum chain. The long-run picture of the Lido's integration into L2s includes: - -- Bridging of Lido's tokens from L1 to L2 chains -- Instant ETH staking on L2 chains with receiving stETH/wstETH on the corresponding L2 immediately -- Keeping UX on L2 as close as possible to the UX on Ethereum mainnet - -At this point, the implementation must provide a scalable and reliable solution for Lido to bridge ERC20 compatible tokens between Arbitrum and Ethereum chain. - -[^*]: The current implementation might not support the non-standard functionality of the ERC20 tokens. For example, rebasable tokens or tokens with transfers fee will work incorrectly. In case your token implements some non-typical ERC20 logic, make sure it is compatible with the gateway before usage. - -## Arbitrum's Bridging Flow - -Arbitrum’s “Canonical Bridge” tokens-bridging architecture consists of three types of contracts: - -1. **Asset contracts**: these are the token contracts themselves, i.e., an ERC20 on L1 and it's counterpart on Arbitrum. -2. **Gateways**: Pairs of contracts (one on L1, one on L2) that implement a particular type of cross chain asset bridging. -3. **Routers**: Exactly two contracts - (one on L1, one on L2) that route each asset to its designated Gateway. - -All Ethereum to Arbitrum token transfers are initiated via the `L1GatewayRouter` contract. `L1GatewayRouter` is responsible for mapping L1 token addresses to `L1Gateway`, thus acting as an L1/L2 address oracle and ensuring that each token corresponds to only one gateway. The `L1Gateway` communicates to an `L2Gateway` (typically/expectedly via retryable tickets). - -Similarly, Arbitrum to Ethereum transfers are initiated via the `L2GatewayRouter` contract, which forwards calls the token's `L2Gateway`, which in turn communicates to its corresponding `L1Gateway` (typically/expectedly via sending messages to the Outbox.) - -To be compatible with Arbitrum's `GatewayRouter`, both L1 and L2 gateways must conform to the `ITokenGateway` interface. - -```solidity -interface ITokenGateway { - function calculateL2TokenAddress(address l1ERC20) - external - view - returns (address); - - function outboundTransfer( - address _token, - address _to, - uint256 _amount, - uint256 _maxGas, - uint256 _gasPriceBid, - bytes calldata _data - ) external returns (bytes memory); - - function getOutboundCalldata( - address _token, - address _from, - address _to, - uint256 _amount, - bytes memory _data - ) external view returns (bytes memory); - - function finalizeInboundTransfer( - address _token, - address _from, - address _to, - uint256 _amount, - bytes calldata _data - ) external virtual override; -} - -``` - -The general process of tokens bridging via Arbitrum's `GatewayRouter` consists of next steps: - -### Deposits - -1. A user calls `L1GatewayRouter.outboundTransfer()` (with `L1Token`'s L1 address as an argument). -2. `L1GatewayRouter` looks up `L1Token`'s gateway. -3. `L1GatewayRouter` calls `L1TokensGateway.outboundTransfer()`, forwarding the appropriate parameters. -4. `L1TokensGateway` escrows tokens and triggers `L2TokensGateway.finalizeInboundTransfer()` method on L2 (typically via a creation of a retryable ticket). -5. `finalizeInboundTransfer` mints the appropriate amount of tokens at the `L2Token` contract on L2. - -![](https://i.imgur.com/A8B1xgI.png) - -### Withdrawals - -1. On Arbitrum, a user calls `L2GatewayRouter.outboundTransfer()`, which in turn calls `outboundTransfer` on `L2Token`'s gateway (i.e., `L2TokensGateway`). -2. This burns `L2Token` tokens and calls [`ArbSys`](https://developer.offchainlabs.com/docs/arbsys) with an encoded message to `L1TokensGateway.finalizeInboundTransfer()`, which will be eventually executed on L1. -3. After the dispute window expires and the assertion with the user's transaction is confirmed, a user can call `Outbox.executeTransaction()`, which in turn calls the encoded `L1ERC20Gateway.finalizeInboundTransfer()` message, releasing the user's tokens from the `L1TokensGateway` contract's escrow. - -![](https://i.imgur.com/KOPguoa.png) - -The `L1GatewayRouter` allows registering custom gateways for certain tokens via `setGateways()` method, which might be called by the OffchainLabs team manually. - -The rest of the document provides a technical specification of the gateways Lido will use to transfer tokens between Arbitrum and Ethereum chains. - -## Lido's Gateways Implementation - -The current implementation of the gateways provides functionality to bridge the specified type of ERC20 compatible token between Ethereum and Arbitrum chains. Additionally, the bridge provides some administrative features, like the **temporary disabling of the deposits and withdrawals**. It's necessary when bridging must be disabled fast because of the malicious usage of the bridge or vulnerability in the contracts. Also, it might be helpful in the implementation upgrade process. - -The technical implementation focuses on the following requirements for the contracts: - -- **Scalability** - current implementation must provide the ability to be extended with new functionality in the future. -- **Simplicity** - implemented contracts must be clear, simple, and expressive for developers who will work with code in the future. -- **Gas efficiency** - implemented solution must be efficient in terms of gas costs for the end-user, but at the same time, it must not violate the previous requirement. - -A high-level overview of the proposed solution might be found in the below diagram: - -![](https://i.imgur.com/TPfEr29.png) - -- Libraries: - - [**`L1OutboundDataParser`**](#L1OutboundDataParser) - a helper library to parse data passed to `outboundTransfer()` of `L1ERC20TokenGateway`. - - [**`L2OutboundDataParser`**](#L2OutboundDataParser) - a helper library to parse data passed to `outboundTransfer()` of `L2ERC20TokenGateway`. -- Abstract Contracts: - - [_**`InterchainERC20TokenGateway`**_](#InterchainERC20TokenGateway) - an abstract contract that implements logic shared between L1 and L2 gateways. -- Contracts: - - [**`AccessControl`**](https://github.com/OpenZeppelin/openzeppelin-contracts/blob/master/contracts/access/AccessControl.sol) - contract from the @openzeppelin package that allows children to implement role-based access. - - [**`BridgingManager`**](#BridgingManager) - contains administrative methods to retrieve and control the state of the bridging process. - - [**`BridgeableTokens`**](#BridgeableTokens) - contains the logic for validation of tokens used in the bridging process. - - [**`L1CrossDomainEnabled`**](#L1CrossDomainEnabled) - helper contract for contracts performing Ethereum to Arbitrum communication process via Retryable Tickets. - - [**`L1ERC20TokenGateway`**](#L1ERC20TokenGateway) - Ethereum's counterpart of the gateway to bridge registered ERC20 compatible tokens between Ethereum and Arbitrum chains. - - [**`L2CrossDomainEnabled`**](#L2Messenger) - helper contract to simplify Arbitrum to Ethereum communication process - - [**`L2ERC20TokenGateway`**](#L2ERC20TokenGateway) - Arbitrum's counterpart of the gateway to bridge registered ERC20 compatible tokens between Ethereum and Arbitrum chains - - [**`ERC20Bridged`**](#ERC20Bridged) - an implementation of the `ERC20` token with administrative methods to mint and burn tokens. - - [**`OssifiableProxy`**](#OssifiableProxy) - the ERC1967 proxy with extra admin functionality. - -## L1OutboundDataParser - -A helper library to parse data passed to `outboundTransfer()` of `L1ERC20TokenGateway`. - -### Functions - -#### `decode(address,bytes memory)` - -> **Visibility:**     `internal` -> -> **Mutability:**   `view` -> -> **Returns:**       `(address, uint256)` -> -> **Arguments:** -> -> - **`router_`** - an address of the Arbitrum's `L1GatewayRouter` -> - **`data_`** - bytes array encoded via the following rules: -> - If the `msg.sender` of the method is the `router_` address, `data_` must contain the result of the function call: `abi.encode(address from, abi.encode(uint256 maxSubmissionCost, bytes emptyData))`, where `emptyData` - is an empty bytes array. -> - In other cases, data must contain the result of the function call: `abi.encode(uint256 maxSubmissionCost, bytes emptyData)` where `emptyData` - is an empty bytes array. - -Decodes value contained in `data_` bytes array and returns decoded value: `(address from, uint256 maxSubmissionCost)`. Such encoding rules are required to be compatible with the `L1GatewaysRouter`. - -## L2OutboundDataParser - -A helper library to parse data passed to `outboundTransfer()` of `L2ERC20TokenGateway`. - -### Functions - -#### decode(address,bytes memory) - -> **Visibility:**     `internal` -> -> **Mutability:**   `view` -> -> **Returns**        `(address)` -> -> **Arguments:** -> -> - **`router_`** - an address of the Arbitrum's `L1GatewayRouter` -> - **`data_`** - bytes array encoded via the following rules: -> - If the `msg.sender` of the method is the `router_` address, `data_` must contain the result of the function call: `abi.encode(address from, bytes emptyData)`, where `emptyData` - is an empty bytes array. -> - In other cases, `data` must be empty bytes array. - -Decodes value contained in `data_` bytes array and returns decoded value: `(address from)`. Such encoding rules are required to be compatible with the `L2GatewaysRouter`. - -## BridgingManager - -- **inherits:** [`@openzeppelin/AccessControl`](https://github.com/OpenZeppelin/openzeppelin-contracts/blob/d4fb3a89f9d0a39c7ee6f2601d33ffbf30085322/contracts/access/AccessControl.sol) - -Contains administrative methods to retrieve and control the state of the bridging process. Allows to enable/disable withdrawals or deposits and check whether the gateway functionality is suspended or not. Allows granting standalone privileges to certain accounts to enable/disable deposits or withdrawals of the gateway. The rights to grant permissions have accounts with an admin role. - -### Constants - -- **DEPOSITS_ENABLER_ROLE** - a `bytes32` equal to a result of the `keccak256()` hashing of the string `"BridgingManager.DEPOSITS_ENABLER_ROLE"`. This role must be used when grants/revokes privileges to enable deposits. -- **DEPOSITS_DISABLER_ROLE** - a `bytes32` equal to a result of the `keccak256()` hashing of the string `"BridgingManager.DEPOSITS_DISABLER_ROLE"`. This role must be used when grants/revokes privileges to disable deposits. -- **WITHDRAWALS_ENABLER_ROLE** - a `bytes32` equal to a result of the `keccak256()` hashing of the string `"BridgingManager.WITHDRAWALS_ENABLER_ROLE"`. This role must be used when grants/revokes privileges to enable withdrawals. -- **WITHDRAWALS_DISABLER_ROLE** - a `bytes32` equal to a result of the `keccak256()` hashing of the string `"BridgingManager.WITHDRAWALS_DISABLER_ROLE"`. This role must be used when grants/revokes privileges to disable withdrawals. - -### Variables - -The contract uses the Unstructured Storage pattern to store the current state of the bridge using the struct `BridgingState`. `BridgingState` struct has the next type: - -```solidity= -struct BridgingState { - bool isInitialized; // Shows whether the contract is initialized or not. - bool isDepositsEnabled; // Stores the state of the deposits - bool isWithdrawalsEnabled; // Stores the state of the withdrawals -} -``` - -### Functions - -#### `initialize(address)` - -> **Visibility:**     `public` -> -> **Arguments:** -> -> - **`admin_`** - an address of the account to grant the `DEFAULT_ADMIN_ROLE` -> -> **Emits:** `RoleGranted(bytes32 indexed role, address indexed account, address indexed sender)` - -Initializes the contract to grant `DEFAULT_ADMIN_ROLE` to the `admin_` address. The method might be called only once. Reverts with error `ErrorAlreadyInitialized()` when called on the already initialized contract. Allows using this contract with the proxy pattern. - -#### `isDepositsEnabled()` - -> **Visibility:**     `public` -> -> **Mutability:**   `view` -> -> **Returns**        `(bool)` - -Returns whether the deposits enabled or not. - -#### `isWithdrawalsEnabled()` - -> **Visibility:**     `public` -> -> **Mutability:**   `view` -> -> **Returns**        `(bool)` - -Returns whether the withdrawals enabled or not. - -#### `enableDeposits()` - -> **Visibility:**     `external` -> -> **Modifiers:**    [`onlyRole(DEPOSITS_ENABLER_ROLE)`](https://github.com/OpenZeppelin/openzeppelin-contracts/blob/d4fb3a89f9d0a39c7ee6f2601d33ffbf30085322/contracts/access/AccessControl.sol#L69) -> -> **Emits:**           `DepositsEnabled(address account)` - -Enables the deposits if they are disabled. Reverts with the error `ErrorDepositsEnabled()` if deposits aren't enabled. Only accounts with the granted `DEPOSITS_ENABLER_ROLE` can call this method. - -#### `disableDeposits()` - -> **Visibility:**     `external` -> -> **Modifiers:**    [`whenDepositsEnabled`](#whenDepositsEnabled) [`onlyRole(DEPOSITS_DISABLER_ROLE)`](https://github.com/OpenZeppelin/openzeppelin-contracts/blob/d4fb3a89f9d0a39c7ee6f2601d33ffbf30085322/contracts/access/AccessControl.sol#L69) -> -> **Emits:**           `DepositsDisabled(address account)` - -Disables the deposits if they aren't disabled yet. Reverts with the error `ErrorDepositsDisabled()` if deposits have already disabled. Only accounts with the granted `DEPOSITS_DISABLER_ROLE` can call this method. - -#### `enableWithdrawals()` - -> **Visibility:**     `external` -> -> **Modifiers:**    [`onlyRole(WITHDRAWALS_ENABLER_ROLE)`](https://github.com/OpenZeppelin/openzeppelin-contracts/blob/d4fb3a89f9d0a39c7ee6f2601d33ffbf30085322/contracts/access/AccessControl.sol#L69) -> -> **Emits:**           `WithdrawalsEnabled(address account)` - -Enables the withdrawals if they are disabled. Reverts with the error `ErrorWithdrawalsEnabled()` if withdrawals are enabled. Only accounts with the granted `WITHDRAWALS_ENABLER_ROLE` can call this method. - -#### `disableWithdrawals()` - -> **Visibility:**     `external` -> -> **Modifiers:**    [`whenWithdrawalsEnabled`](#whenWithdrawalsEnabled)[`onlyRole(WITHDRAWALS_DISABLER_ROLE)`](https://github.com/OpenZeppelin/openzeppelin-contracts/blob/d4fb3a89f9d0a39c7ee6f2601d33ffbf30085322/contracts/access/AccessControl.sol#L69) -> -> **Emits:**           `WithdrawalsDisabled(address account)` - -Disables the withdrawals if they aren't disabled yet. Reverts with the error `ErrorWithdrawalsDisabled()` if withdrawals have already disabled. Only accounts with the granted `WITHDRAWALS_DISABLER_ROLE` can call this method. - -#### `_loadState()` - -> **Visibility:**     `private` -> -> **Mutability:**   `pure` -> -> **Returns**        `(BridgingState storage)` - -Loads and returns the `BridgingState` variable from the slot at address `keccak256("BridgingManager.bridgingState")`. - -### Modifiers - -#### `whenDepositsEnabled()` - -Validates that deposits are enabled. Reverts with the error `ErrorDepositsDisabled()` when called on contract with disabled deposits. - -#### `whenWithdrawalsEnabled()` - -Validates that withdrawals are enabled. Reverts with the error `ErrorWithdrawalsDisabled()` when called on contract with disabled withdrawals. - -## BridgeableTokens - -Contains the logic for validation of tokens used in the bridging process - -### Variables - -The contract keeps the addresses of L1/L2 tokens used in the bridging: - -- **`l1Token`** - an immutable address of the bridged token in the L1 chain -- **`l2Token`** - an immutable address of the token minted on the L2 chain when token bridged - -### Modifiers - -#### `onlySupportedL1Token(address l1Token_)` - -Validates that passed `l1Token_` is supported by the bridge. Reverts with error `ErrorUnsupportedL1Token()` when addresses mismatch. - -#### `onlySupportedL2Token(address l2Token_)` - -Validates that passed `l2Token_` is supported by the bridge. Reverts with error `ErrorUnsupportedL2Token()` when addresses mismatch. - -## InterchainERC20TokenGateway - -**Implements:** `IInterchainERC20TokenGateway` -**Inherits:** [`BridgingManager`](#BridgingManager) [`BridgeableTokens`](#BridgeableTokens) - -The contract keeps logic shared among both L1 and L2 gateways, adding the methods for bridging management: enabling and disabling withdrawals/deposits. - -### Variables - -The contract keeps the variables required by both L1/L2 gateways: - -- **`router`** - an address of the router in the corresponding chain -- **`counterpartGateway`** - an address of the counterpart gateway used in the bridging process - -All variables are declared as `immutable` to reduce transactions gas costs. - -### Functions - -#### `calculateL2TokenAddress(address)` - -> **Visibility:**     `external` -> -> **Mutability:**   `view` -> -> **Returns**        `(address)` -> -> **Arguments:** -> -> - **l1Token\_** - an address of the token on the Ethereum chain - -Returns an address of token, which will be minted on the Arbitrum chain, on `l1Token_` bridging. The current implementation returns the `l2Token` address when passed `l1Token_` equals to `l1Token` declared in the contract and `address(0)` in other cases. - -#### `getOutboundCalldata(address,address,address,uint256,bytes memory)` - -> **Visibility:**     `public` -> -> **Mutability:**   `view` -> -> **Returns**        `(bytes memory)` -> -> **Arguments:** -> -> - **`l1Token_`** - an address in the Ethereum chain of the token to bridge -> - **`from_`** - an address in the Ethereum chain of the account initiated bridging -> - **`to_`** - an address in the Ethereum chain of the recipient of the token on the corresponding chain -> - **`amount_`** - an amount of tokens to bridge -> - **`data_`** - Custom data to pass into finalizeInboundTransfer method. Unused, required to be compatible with @arbitrum/sdk package. - -Returns encoded transaction data to send into the corresponding gateway to finalize the tokens bridging process. The result of this method might be used to estimate the amount of ether required to pass to the `outboundTransfer()` method call. In the current implementation returns the transaction data of `finalizeInboundTransfer(token_, from_, to_, amount_)`. - -## L1CrossDomainEnabled - -A helper contract for contracts performing Ethereum to Arbitrum communication process via Retryable Tickets. - -### Variables - -The contract declares one immutable variable **`inbox_`** - an address of the Arbitrum's [`Inbox`](https://developer.offchainlabs.com/docs/sol_contract_docs/md_docs/arb-bridge-eth/bridge/inbox) contract - -### Functions - -#### `sendCrossDomainMessage(address, address, bytes memory, CrossDomainMessageOptions memory)` - -> **Visibility:**     `internal` -> -> **Returns**        `(uint256)` -> -> **Arguments**: -> -> - **`sender_`** - an address of the sender of the message. It's also the address to credit all excess ETH from gas and call-value on the Arbitrum chain. Call-value is refunded if the retryable ticket times out or is canceled. `sender_` is also the address with the right to cancel a Retryable Ticket. -> - **`recipient_`** - an address of the recipient of the message on the Arbitrum chain -> - **`data_`** - data passed to the `recipient_` in the message -> - **`msgOptions_`** - an instance of the `CrossDomainMessageOptions` struct. The `CrossDomainMessageOptions` struct has the following properties: -> - **`maxGas`** - gas limit for immediate L2 execution attempt (can be estimated via `NodeInterface.estimateRetryableTicket()`) -> - **`callValue`** - call-value for L2 transaction -> - **`gasPriceBid`** - L2 Gas price bid for immediate L2 execution attempt (queryable via standard `eth_gasPrice` RPC) -> - **`maxSubmissionCost`** - an amount of ETH allocated to pay for the base submission fee -> -> **Emits:** `TxToL2(address indexed from, address indexed to, uint256 indexed seqNum, bytes data)` - -Creates a Retryable Ticket via [`Inbox.createRetryableTicket`](https://github.com/OffchainLabs/arbitrum/blob/52356eeebc573de8c4dd571c8f1c2a6f5585f359/packages/arb-bridge-eth/contracts/bridge/Inbox.sol#L325) function using the provided arguments. Sends all passed ether with Retryable Ticket into Arbitrum chain. Reverts with error `ErrorETHValueTooLow()` if passed `msg.value` is less than `msgOptions_.callVaue + msgOptions_.maxSubmissionCost + (msgOptions_.maxGas * msgOptions_.gasPriceBid)` and with error `ErrorNoMaxSubmissionCost()` when `msgOptions_.maxSubmissionCost` is equal to 0. Returns a unique id of created Retryable Ticket. - -### Modifiers - -#### `onlyFromCrossDomainAccount(address crossDomainAccount_)` - -Validates that transaction was initiated by the `crossDomainAccount_` address from the L2 chain. Reverts with error `ErrorUnauthorizedBridge()` if called not by Arbitrum's bridge and with error `ErrorWrongCrossDomainSender()` if the transaction was sent not from the `crossDomainAccount_` address. - -## L1ERC20TokenGateway - -- **Inherits**: [`InterchainERC20TokenGateway`](#InterchainERC20TokenGateway) [`L1CrossDomainEnabled`](#L1CrossDomainEnabled) -- **Implements**: `IL1TokenGateway` - -Contract implements `ITokenGateway` interface and with counterpart `L2TokensGatewy` allows bridging registered ERC20 compatible tokens between Ethereum and Arbitrum chains. The contract is compatible with `L1GatewayRouter` and might be used to transfer tokens via the "canonical" Arbitrum's bridge. - -Additionally, the contract provides administrative methods to temporarily disable bridging from Ethereum to Arbitrum via the `BridgingManager` contract. - -### Functions - -#### `outboundTransfer(address,address,uint256,uint256, uint256,bytes calldata)` - -> **Visibility:**     `external` -> -> **Mutability:**   `payble` -> -> **Modifiers:**    [`whenDepositsEnabled()`](#whenDepositsEnabled) [`onlySupportedL1Token(l1Token_)`](#onlySupportedL1Tokenaddress-l1Token_) -> -> **Returns**        `(bytes memory)` -> -> **Arguments:** -> -> - **l1Token\_** - an address in the Ethereum chain of the token to bridge. It must be equal to the `l1Token` address. The method will be reverted with the error `ErrorUnsupportedL1Token()` if would be called with a different address. -> - **to\_** - an address of the recipient of the token on the corresponding chain -> - **amount\_** - an amount of tokens to bridge. The user has to approve spending of the `l1Token` for the gateway or the transaction will be reverted. -> - **maxGas\_** - a gas limit for immediate L2 execution attempt (can be estimated via `_NodeInterface.estimateRetryableTicket`). -> - **gasPriceBid\_** - an L2 gas price bid for immediate L2 execution attempt (queryable via standard eth\*gasPrice RPC). -> - **data** - stores an additional data required for the transaction. Data will be decoded via the `L1OutboundDataParser.decode()` method to retrieve the `maxSubmissionCost` value and `from` address, where `from` - contains an address of the sender, and `maxSubmissionCost` - is an amount of ETH allocated to pay for the base submission fee. -> -> **Emits:** `DepositInitiated(address l1Token, address indexed from, address indexed to, uint256 indexed sequenceNumber, uint256 amount)` - -Initiates the tokens bridging from the Ethereum into the Arbitrum chain. Escrows the `amount_` of `l1Token_` from the user on the address of the gateway and creates a Retryable Ticket via the `sendCrossDomainMessage()` method: - -```solidity= -sendCrossDomainMessage( - counterpartGateway, // recipient - getOutboundCalldata(l1Token, from, to, amount, ""), // data - CrossDomainMessageOptions({ - maxGas: maxGas, - callValue: 0, - gasPriceBid: gasPriceBid_, - refundAddress: from, - maxSubmissionCost: maxSubmissionCost - }) -) -``` - -Returns an encoded value of the id for created Retryable Ticket. Same value is used as `sequenceNumber` in `DepositInitiated` event. - -#### `finalizeInboundTransfer(address,address,address,uint256,bytes calldata)` - -> **Visibility:**     `internal` -> -> **Modifiers:**    [`whenWithdrawalsEnabled()`](#whenWithdrawalsEnabled) [`onlySupportedL1Token(l1Token_)`](#onlySupportedL1Tokenaddress-l1Token_) [`onlyFromCrossDomainAccount(counterpartGateway)`](#onlyFromCrossDomainAccountaddress-crossDomainAccount_) -> -> **Arguments:** -> -> - **`l1Token_`** - an address in the Ethereum chain of the token to withdraw -> - **`from_`** - an address of the account initiated bridging -> - **`to_`** - an address of the recipient of the tokens -> - **`amount_`** - an amount of tokens to withdraw -> - **`data_`** - unused variable, required to be compatible with `L1GatewayRouter` and `L2GatewayRouter` -> -> **Emits:** `WithdrawalFinalized(address l1Token, address indexed from, address indexed to, uint256 indexed exitNum, uint256 amount)` - -This method is called to finalize the withdrawal of the tokens from the L2 chain. It transfers the `amount_` of tokens from the gateway to the `to_` address via `safeTransfer()` method. - -**Note**: `exitNum` - always is equal to 0 in the `WithdrawalFinalized` event cause the current implementation doesn't support fast withdraws. To read more about fast withdrawals, see [Offchain Labs Docs](https://developer.offchainlabs.com/docs/withdrawals). - -## L2CrossDomainEnabled - -A helper contract to simplify Arbitrum to Ethereum communication process. - -### Variables - -The contract declares one immutable variable **`arbSys`** - an address of the Arbitrum's [`ArbSys`](https://developer.offchainlabs.com/docs/arbsys) contract - -### Functions - -#### `sendCrossDomainMessage(address,address,bytes memory)` - -> **Visibility:**     `internal` -> -> **Returns**        `(uint256)` -> -> **Arguments**: -> -> - **`sender_`** - an address of the sender of the message -> - **`recipient_`** - an address of the recipient of the message on the Ethereum chain -> - **`data_`** - Data passed to the `recipient_` in the message -> -> **Emits**: `event TxToL1(address indexed from, address indexed to, uint256 indexed id, bytes data)` - -Sends the message to the Ethereum chain via `ArbSys.sendTxToL1()` method. - -#### `applyL1ToL2Alias(address)` - -> **Visibility:**     `private` -> -> **Returns**        `(address)` -> -> **Arguments**: -> -> - **`l1Address_`** - an L1 address to apply aliasing - -Applies the [Arbitrum's L1 -> L2 aliasing](https://developer.offchainlabs.com/docs/l1_l2_messages#address-aliasing) to the address. - -### Modifiers - -#### `onlyFromCrossDomainAccount(address crossDomainAccount_)` - -Validates that the `msg.sender` is equal to the `crossDomainAccount_` with applied [Arbitrum's aliasing](https://developer.offchainlabs.com/docs/l1_l2_messages#address-aliasing). Reverts with the error `ErrorWrongCrossDomainSender()` if validation fails. - -## L2ERC20TokenGateway - -- **Inherits**: [`InterchainERC20TokenGateway`](#InterchainERC20TokenGateway) [`L2CrossDomainEnabled`](#L2CrossDomainEnabled) -- **Implements**: `IL2TokenGateway` - -Contract implements `ITokenGateway` interface and with counterpart `L1ERC20TokenGateway` allows bridging registered ERC20 compatible tokens between Arbitrum and Ethereum chains. The contract is compatible with `L2GatewayRouter` and might be used to transfer tokens via the “canonical” Arbitrum’s bridge. - -Additionally, the contract provides administrative methods to temporarily disable bridging from Arbitrum to Ethereum via the `BridgingManager` contract. - -### Functions - -#### `outboundTransfer(address,address,uint256,uint256, uint256,bytes memory)` - -> **Visibility:**     `external` -> -> **Modifiers:**    [`whenWithdrawalsEnabled()`](#whenWithdrawalsEnabled) [`onlySupportedL1Token(l1Token_)`](#onlySupportedL1Tokenaddress-l1Token_) -> -> **Returns**        `(bytes memory)` -> -> **Arguments:** -> -> - **l1Token\_** - an address in the Ethereum chain of the token to bridge. It must be equal to the `l1Token` address. The method will be reverted with the error `ErrorUnsupportedL1Token()` if would be called with a different address. -> - **to\_** - an address of the recipient of the token on the corresponding chain -> - **amount\_** - an amount of tokens to bridge. The user has to approve spending of the `l1Token` for the gateway or the transaction will be reverted. -> - **maxGas\_** - Doesn't used -> - **gasPriceBid\_** - Doesn't used -> - **data** - stores an additional data required for transaction. Data will be decoded via `L2OutboundDataParser.decode()` method to retrieve `from` address - an address of the sender. -> -> **Emits:** `WithdrawalInitiated(address l1Token, address indexed from, address indexed to, uint256 indexed l2ToL1Id, uint256 exitNum, uint256 amount)` - -Initiates the withdrawing process from the Arbitrum chain into the Ethereum chain. The method burns the `amount_` of `l2Token` on the `from_` account, sends message to the Ethereum chain via `sendCrossDomainMessage()` method: - -```solidity= -sendCrossDomainMessage( - counterpartGateway, - getOutboundCalldata(l1Token_, from_, to_, amount_, "") -); -``` - -Returns encoded value of the unique id for L2-to-L1 transaction. Same value is used as `l2ToL1Id` in the `WithdrawalInitiated` event. - -**Note**: `exitNum` - always is equal to 0 in the `WithdrawalInitiated` event cause the current implementation doesn't support fast withdraws. To read more about fast withdrawals, see [Offchain Labs Docs](https://developer.offchainlabs.com/docs/withdrawals). - -#### `finalizeInboundTransfer(address,address,address,uint256,bytes calldata)` - -> **Visibility:**     `internal` -> -> **Modifiers:**    [`whenDepositsEnabled()`](#whenDepositsEnabled) [`onlySupportedL1Token(l1Token_)`](#onlySupportedL1Tokenaddress-l1Token_) [`onlyFromCrossDomainAccount(counterpartGateway)`](#onlyFromCrossDomainAccountaddress-crossDomainAccount_1) -> -> **Arguments:** -> -> - **`l1Token_`** - an address in the Ethereum chain of the token to bridge -> - **`from_`** - an address of the account initiated bridging -> - **`to_`** - an address of the recipient of the tokens -> - **`amount_`** - an amount of tokens to bridge -> - **`data_`** - unused variable, required to be compatible with `L1GatewayRouter` and `L2GatewayRouter` -> -> **Emits:** `DepositFinalized(address indexed l1Token, address indexed from, address indexed to, uint256 amount)` - -This method is called on the finalizing of the bridging from the Ethereum chain. This method mints the `amount_` of `l2Token` token to the `to_` address. - -## `ERC20Metadata` - -Contains optional methods for the `ERC20` tokens. It uses the UnstructuredStorage pattern to store strings with name and symbol info. Might be used with the upgradable proxies. - -### Variables - -Contract declares `public` and `immutable` variable **`decimals`** of type `uint8`. - -The `name` and `symbol` info are stored in the structure: - -```solidity= -struct DynamicMetadata { - string name; - string symbol; -} -``` - -### Funcations - -#### `name()` - -> **Visibility:**     `external` -> -> **Mutability:**   `view` -> -> **Returns**        `(string memory)` - -Returns the name of the token. - -#### `symbol()` - -> **Visibility:**     `external` -> -> **Mutability:**   `view` -> -> **Returns**        `(string memory)` - -Returns the symbol of the token. - -#### `_setERC20MetadataName(string memory)` - -> **Visibility:**     `internal` -> -> **Arguments:** -> -> - **`name_`** - string with name of the token - -Sets the `name` of the token. Might be called only when the `name` is empty. - -#### `_setERC20MetadataSymbol(string memory)` - -> **Visibility:**     `internal` -> -> **Arguments:** -> -> - **`symbol_`** - string with symbol of the token - -Sets the `symbol` of the token. Might be called only when the `symbol` is empty. - -#### `_loadDynamicMetadata()` - -> **Visibility:**     `private` -> -> **Mutability:**   `pure` -> -> **Returns**        `(DynamicMetadata storage r)` - -Returns the reference to the slot with `DynamicMetadta` struct - -## `ERC20Core` - -- **Implements:** [`@openzeppelin/IERC20`](https://github.com/OpenZeppelin/openzeppelin-contracts/blob/d4fb3a89f9d0a39c7ee6f2601d33ffbf30085322/contracts/token/ERC20/IERC20.sol) - -Contains the required variables and logic of the `ERC20` token. The contract is a slightly modified version of the [`ERC20`](https://github.com/OpenZeppelin/openzeppelin-contracts/blob/d4fb3a89f9d0a39c7ee6f2601d33ffbf30085322/contracts/token/ERC20/ERC20.sol) contract from the OpenZeppelin package. - -### Variables - -Contract declares the following variables to store state of the token: - -- **`uint256 public totalSupply`** - the total supply of the token -- **`mapping(address => uint256) public balanceOf`** - stores balances of the token holders -- **`mapping(address => mapping(address => uint256)) public allowance`** - stores allowances of the token holders - -### Functions - -#### `approve(address,uint256)` - -> **Visibility:**     `external` -> -> **Returns**        `(bool)` -> -> **Arguments:** -> -> - **`spender_`** - an address of the tokens spender -> - **`amount_`** - a number of tokens to allow to spend -> -> **Emits:** `Approval(address indexed owner, address indexed spender, uint256 value)` - -Allows _spender to withdraw from the `msg.sender` account multiple times, up to the `amount_`. If this function is called again it overwrites the current allowance with `amount\_`. Returns a `bool` value indicating whether the operation succeeded. - -#### `transfer(address,uint256)` - -> **Visibility:**     `external` -> -> **Returns**        `(bool)` -> -> **Arguments:** -> -> - **`to_`** - an address of the recipient of the tokens -> - **`amount_`** - a number of tokens to transfer -> -> **Emits:** `Transfer(address indexed from, address indexed to, uint256 value)` - -Transfers `amount` of tokens from sender to `to` account. -Returns a `bool` value indicating whether the operation succeeded. - -#### `transferFrom(address,address,uint256)` - -> **Visibility:**     `external` -> -> **Returns**        `(bool)` -> -> **Arguments:** -> -> - **`from_`** - an address to transfer tokens from -> - **`to_`** - an address of the recipient of the tokens -> - **`amount_`** - a number of tokens to transfer -> -> **Emits:** `Transfer(address indexed from, address indexed to, uint256 value)` `Approval(address indexed owner, address indexed spender, uint256 value)` - -Transfers `amount` of token from the `from_` account to `to_` using the allowance mechanism. `amount_` is then deducted from the caller's allowance. Returns a `bool` value indicating whether the operation succeed. - -#### `increaseAllowance(address,uint256)` - -> **Visibility:**     `external` -> -> **Returns**        `(bool)` -> -> **Arguments:** -> -> - **`spender_`** - an address of the tokens spender -> - **`addedValue_`** - a number to increase allowance -> -> **Emits:** `Approval(address indexed owner, address indexed spender, uint256 value)` - -Atomically increases the allowance granted to `spender` by the caller. Returns a `bool` value indicating whether the operation succeed. - -#### `decreaseAllowance(address,uint256)` - -> **Visibility:**     `external` -> -> **Returns**        `(bool)` -> -> **Arguments:** -> -> - **`spender_`** - an address of the tokens spender -> - **`subtractedValue_`** - a number to decrease allowance -> -> **Emits:** `Approval(address indexed owner, address indexed spender, uint256 value)` - -Atomically decreases the allowance granted to `spender` by the caller. Returns a `bool` value indicating whether the operation succeed. - -## `ERC20Bridged` - -**Implements:** [`IERC20Bridged`](https://github.com/lidofinance/lido-l2/blob/main/contracts/token/interfaces/IERC20Bridged.sol) -**Inherits:** [`ERC20Metadata`](#ERC20Metadata) [`ERC20Core`](#ERC20CoreLogic) - -Inherits the `ERC20` default functionality that allows the bridge to mint and burn tokens. - -### Variables - -Contract declares an immutable variable **`bridge`** which can mint/burn the token. - -### Functions - -#### `mint(address,uint256)` - -> **Visibility:**     `external` -> -> **Modifiers:**    [`onlyBridge`](#onlybridge) -> -> **Arguments:** -> -> - **`account_`** - an address of the tokens recipient -> - **`amount_`** - a number to mint -> -> **Emits:** `Transfer(address indexed from, address indexed to, uint256 value)` - -Mints the `amount_` of tokens to the `account_`. The method might be called only by the bridge. Reverts with the error `ErrorNotBridge()` when called not by bridge. - -#### `burn(address,uint256)` - -> **Visibility:**     `external` -> -> **Modifiers:**    [`onlyBridge`](#onlybridge) -> -> **Arguments:** -> -> - **`account_`** - an address of the tokens recipient -> - **`amount_`** - a number to burn -> -> **Emits:** `Transfer(address indexed from, address indexed to, uint256 value)` - -Destroys the `amount_` of tokens from the `account_`. The method might be called only by the bridge. Reverts with the error `ErrorNotBridge()` when called not by bridge. - -### Modifiers - -#### `onlyBridge()` - -Validates that the `msg.sender` of the method is the `bridge`. Reverts with error `ErrorNotBridge()` in other cases. - -## `OssifiableProxy` - -- **Inherits:** [`@openzeppelin/ERC1967Proxy`](https://github.com/OpenZeppelin/openzeppelin-contracts/blob/d4fb3a89f9d0a39c7ee6f2601d33ffbf30085322/contracts/proxy/ERC1967/ERC1967Proxy.sol) - -Extends the [`ERC1967Proxy`](https://github.com/OpenZeppelin/openzeppelin-contracts/blob/d4fb3a89f9d0a39c7ee6f2601d33ffbf30085322/contracts/proxy/ERC1967/ERC1967Proxy.sol) contract from the OpenZeppelin package and adds some admin methods. In contrast to [`UUPSUpgradableProxy`](https://github.com/OpenZeppelin/openzeppelin-contracts/blob/d4fb3a89f9d0a39c7ee6f2601d33ffbf30085322/contracts/proxy/utils/UUPSUpgradeable.sol), it doesn't increase the inheritance chain of the implementation contracts. And allows saving one extra `SLOAD` operation on every user request in contrast to [`TransparentUpgradeableProxy`](https://github.com/OpenZeppelin/openzeppelin-contracts/blob/d4fb3a89f9d0a39c7ee6f2601d33ffbf30085322/contracts/proxy/transparent/TransparentUpgradeableProxy.sol). But adding any external methods to the `ERC1967Proxy` creates the risk of selectors clashing, as described in the OpenZepplin [proxies docs](https://docs.openzeppelin.com/upgrades-plugins/1.x/proxies#transparent-proxies-and-function-clashes). To avoid the risk of clashing, the implementation upgrade process must contain a step with a search of the collisions between proxy and implementation. - -### Functions - -#### `proxy__getAdmin()` - -> **Visibility:**     `external` -> -> **Mutability:**   `view` -> -> **Returns**        `(address)` - -Returns the admin of the proxy. - -#### `proxy__getImplementation()` - -> **Visibility:**     `external` -> -> **Mutability:**   `view` -> -> **Returns**        `(address)` - -Returns the address of the implementation. - -#### `proxy__getIsOssified()` - -> **Visibility:**     `external` -> -> **Mutability:**   `view` -> -> **Returns**        `(bool)` - -Returns whether the proxy is ossified or not. - -#### `proxy__ossify()` - -> **Visibility:**     `external` -> -> **Modifiers:**    [`onlyAdmin`](#onlyAdmin) -> -> **Emits:**           `AdminChanged(address previousAdmin, address newAdmin)` - -Allows to transfer admin rights to zero address and prevent future upgrades of the proxy. - -#### `proxy__changeAdmin(address)` - -> **Visibility:**     `external` -> -> **Modifiers:**    [`onlyAdmin`](#onlyAdmin) -> -> **Arguments:** -> -> - **`newAdmin_`** - an address of the new admin. Must not be zero address. -> -> **Emits:** `AdminChanged(address previousAdmin, address newAdmin)` - -Changes the admin of the proxy. Reverts with message "ERC1967: new admin is the zero address" if `newAdmin_` is zero address. - -#### `proxy__upgradeTo(address)` - -> **Visibility:**     `external` -> -> **Modifiers:**    [`onlyAdmin`](#onlyAdmin) -> -> **Arguments:** -> -> - **`newImplementation_`** - an address of the new implementation. Must be a contract. -> -> **Emits:** `Upgraded(address indexed implementation)` - -Upgrades the implementation of the proxy. Reverts with the error "ERC1967: new implementation is not a contract" if the `newImplementation_` is not a contract. - -#### `proxy__upgradeToAndCall(address,bytes memory,bool)` - -> **Visibility:**     `external` -> -> **Modifiers:**    [`onlyAdmin`](#onlyAdmin) -> -> **Arguments:** -> -> - **`newImplementation_`** - an address of the new implementation. Must be a contract. -> - **`setupCalldata_`** - a data to pass into setup call after implementation upgrade. -> - **`forceCall_`** - forces make delegate call to the implementation even with empty `setupCalldata_` -> -> **Emits:** `Upgraded(address indexed implementation)` - -Upgrades the implementation of the proxy with an additional setup call. Reverts with the error "ERC1967: new implementation is not a contract" if the `newImplementation_` is not a contract. If `setupCalldata_.length` equals zero setup step will be skipped, if forceCall is false. - -### Modifiers - -#### `onlyAdmin()` - -Validates that that proxy is not ossified and that method is called by the admin of the proxy. Reverts with error `ErrorProxyIsOssified()` when called on ossified contract and with error `ErrorNotAdmin()` when called not by admin. - -## Deployment Process - -To reduce the gas costs for users, contracts `L1ERC20TokenGateway`, `L2ERC20TokenGateway`, and `L2TokensToken` use immutable variables as much as possible. But some of those variables are cross-referred. For example, `L1ERC20TokenGateway` has reference to `L2ERC20TokenGateway` and vice versa. As we use proxies, we can deploy proxies at first and stub the implementation with an empty contract. Then deploy actual implementations with addresses of deployed proxies and then upgrade proxies with new implementations. For stub might be used next contract: - -``` -pragma solidity ^0.8.0; -contract EmptyContract {} -``` - -Another option - pre-calculate the future address of the deployed contract offchain and deployed the implementation using pre-calculated addresses. But it is less fault-tolerant than the solution with an implementation stub. - -## Integration Risks - -As an additional link in the tokens flow chain, the Arbitrum and gateways possibly add points of failure. Below are the main risks of the current integration: - -### Minting of uncollateralized `L2Token` - -Such an attack might happen if an attacker obtains the right to call `L2ERC20TokenGateway.finalizeOutboundTransfer()` directly to mint uncollateralized L2Token. In such a scenario, an attacker can mint tokens on L2 and initiate withdrawal of those tokens. - -The best way to detect such an attack is an offchain monitoring of the minting and depositing/withdrawal events. Based on such events might be tracked following stats: - -- `l1GatewayBalance` - a total number of locked tokens on the L1 gateway -- `l2TokenTotalSupply` - total number of minted L2 tokens -- `l2TokenNotWithdrawn` - total number of burned L2 tokens which aren't withdrawn from the L1 gateway - -At any time following invariant must be sutisfied: `l1GatewayBalance == l2TokenTotalSupply + l2TokenNotWithdrawn`. - -In the case of invariant violation, Lido will have a dispute period to suspend L1 and L2 gateways. Paused gateways forbid minting of L2Token and withdrawing of minted tokens till the resolution of the issue. - -### Attack on fraud-proof system - -Such an attack might be seeking to take control over validators or abuse the fraud-proof system to submit incorrect state root. In such a case, the proposed incorrect block will be subject to a dispute period. Lido may run its validator with a "watchtower" strategy, which will ring the alarm when an invalid block is proposed. When it happens, the gateway must be suspended to protect users from potential funds lost till the resolution of the issue. - -### Attack on `L1GatewaysRouter` - -Theoretical situation, when an attacker takes control over `L1GatewaysRouter` and replaces an address of the gateway responsible for token bridging on some malicious contract. It potentially allows to steal the tokens transferred after the gateway substitution. To react to such an attack fastly, Lido has to monitor the `GatewaySet` event with the address of the Lido token. In case such an event was emitted, the Offchain Labs Team must be reached out to investigate the details and fix an issue asap to minimize the damage. diff --git a/contracts/arbitrum/interfaces/IArbSys.sol b/contracts/arbitrum/interfaces/IArbSys.sol deleted file mode 100644 index f7221e8a..00000000 --- a/contracts/arbitrum/interfaces/IArbSys.sol +++ /dev/null @@ -1,17 +0,0 @@ -// SPDX-FileCopyrightText: 2022 Lido -// SPDX-License-Identifier: GPL-3.0 - -pragma solidity 0.8.10; - -/// @title Precompiled contract that exists in every Arbitrum chain at address(100), -/// 0x0000000000000000000000000000000000000064. Exposes a variety of system-level functionality -interface IArbSys { - /// @notice Send a transaction to L1 - /// @param destination_ Recipient address on L1 - /// @param calldataForL1_ (optional) Calldata for L1 contract call - /// @return Unique identifier for this L2-to-L1 transaction - function sendTxToL1(address destination_, bytes calldata calldataForL1_) - external - payable - returns (uint256); -} diff --git a/contracts/arbitrum/interfaces/IBridge.sol b/contracts/arbitrum/interfaces/IBridge.sol deleted file mode 100644 index 4b6e8a42..00000000 --- a/contracts/arbitrum/interfaces/IBridge.sol +++ /dev/null @@ -1,8 +0,0 @@ -// SPDX-FileCopyrightText: 2022 Lido -// SPDX-License-Identifier: GPL-3.0 - -pragma solidity 0.8.10; - -interface IBridge { - function activeOutbox() external view returns (address); -} diff --git a/contracts/arbitrum/interfaces/IInbox.sol b/contracts/arbitrum/interfaces/IInbox.sol deleted file mode 100644 index 372182ec..00000000 --- a/contracts/arbitrum/interfaces/IInbox.sol +++ /dev/null @@ -1,31 +0,0 @@ -// SPDX-FileCopyrightText: 2022 Lido -// SPDX-License-Identifier: GPL-3.0 - -pragma solidity >=0.4.21; - -interface IInbox { - /// @notice Put an message in the L2 inbox that can be reexecuted for some fixed amount of time - /// if it reverts all msg.value will deposited to callValueRefundAddress on L2 - /// @param destAddr_ Destination L2 contract address - /// @param arbTxCallValue_ Call value for retryable L2 message - /// @param maxSubmissionCost_ Max gas deducted from user's L2 balance to cover base submission fee - /// @param submissionRefundAddress_ maxGas x gasprice - execution cost gets credited here on L2 balance - /// @param valueRefundAddress_ l2Callvalue gets credited here on L2 if retryable txn times out or gets cancelled - /// @param maxGas_ Max gas deducted from user's L2 balance to cover L2 execution - /// @param gasPriceBid_ Price bid for L2 execution - /// @param data_ ABI encoded data of L2 message - /// @return unique id for retryable transaction (keccak256(requestID, uint(0) ) - function createRetryableTicket( - address destAddr_, - uint256 arbTxCallValue_, - uint256 maxSubmissionCost_, - address submissionRefundAddress_, - address valueRefundAddress_, - uint256 maxGas_, - uint256 gasPriceBid_, - bytes calldata data_ - ) external payable returns (uint256); - - /// @notice Returns address of the Arbitumr's bridge - function bridge() external view returns (address); -} diff --git a/contracts/arbitrum/interfaces/IInterchainTokenGateway.sol b/contracts/arbitrum/interfaces/IInterchainTokenGateway.sol deleted file mode 100644 index 0915eac5..00000000 --- a/contracts/arbitrum/interfaces/IInterchainTokenGateway.sol +++ /dev/null @@ -1,50 +0,0 @@ -// SPDX-FileCopyrightText: 2022 Lido -// SPDX-License-Identifier: GPL-3.0 - -pragma solidity 0.8.10; - -/// @author psirex -/// @notice Keeps logic shared among both L1 and L2 gateways. -interface IInterchainTokenGateway { - /// @notice Finalizes the bridging of the tokens between chains - /// @param l1Token_ Address in the L1 chain of the token to withdraw - /// @param from_ Address of the account initiated withdrawing - /// @param to_ Address of the recipient of the tokens - /// @param amount_ Amount of tokens to withdraw - /// @param data_ Additional data required for the transaction - function finalizeInboundTransfer( - address l1Token_, - address from_, - address to_, - uint256 amount_, - bytes calldata data_ - ) external; - - /// @notice Calculates address of token, which will be minted on the Arbitrum chain, - /// on l1Token_ bridging - /// @param l1Token_ Address of the token on the Ethereum chain - /// @return Address of the token minted on the L2 on bridging - function calculateL2TokenAddress(address l1Token_) - external - view - returns (address); - - /// @notice Returns address of the counterpart gateway used in the bridging process - function counterpartGateway() external view returns (address); - - /// @notice Returns encoded transaction data to send into the counterpart gateway to finalize - /// the tokens bridging process. - /// @param l1Token_ Address in the Ethereum chain of the token to bridge - /// @param from_ Address of the account initiated bridging in the current chain - /// @param to_ Address of the recipient of the token in the counterpart chain - /// @param amount_ Amount of tokens to bridge - /// @param data_ Custom data to pass into finalizeInboundTransfer method - /// @return Encoded transaction data of finalizeInboundTransfer call - function getOutboundCalldata( - address l1Token_, - address from_, - address to_, - uint256 amount_, - bytes memory data_ - ) external view returns (bytes memory); -} diff --git a/contracts/arbitrum/interfaces/IL1TokenGateway.sol b/contracts/arbitrum/interfaces/IL1TokenGateway.sol deleted file mode 100644 index 2ec88be5..00000000 --- a/contracts/arbitrum/interfaces/IL1TokenGateway.sol +++ /dev/null @@ -1,42 +0,0 @@ -// SPDX-FileCopyrightText: 2022 Lido -// SPDX-License-Identifier: GPL-3.0 - -pragma solidity 0.8.10; - -import {IInterchainTokenGateway} from "./IInterchainTokenGateway.sol"; - -/// @author psirex -/// @notice L1 part of the tokens bridge compatible with Arbitrum's GatewayRouter -interface IL1TokenGateway is IInterchainTokenGateway { - /// @notice Initiates the tokens bridging from the Ethereum into the Arbitrum chain - /// @param l1Token_ Address in the L1 chain of the token to bridge - /// @param to_ Address of the recipient of the token on the corresponding chain - /// @param amount_ Amount of tokens to bridge - /// @param maxGas_ Gas limit for immediate L2 execution attempt - /// @param gasPriceBid_ L2 gas price bid for immediate L2 execution attempt - /// @param data_ Additional data required for the transaction - function outboundTransfer( - address l1Token_, - address to_, - uint256 amount_, - uint256 maxGas_, - uint256 gasPriceBid_, - bytes calldata data_ - ) external payable returns (bytes memory); - - event DepositInitiated( - address l1Token, - address indexed from, - address indexed to, - uint256 indexed sequenceNumber, - uint256 amount - ); - - event WithdrawalFinalized( - address l1Token, - address indexed from, - address indexed to, - uint256 indexed exitNum, - uint256 amount - ); -} diff --git a/contracts/arbitrum/interfaces/IL2TokenGateway.sol b/contracts/arbitrum/interfaces/IL2TokenGateway.sol deleted file mode 100644 index 182e157b..00000000 --- a/contracts/arbitrum/interfaces/IL2TokenGateway.sol +++ /dev/null @@ -1,40 +0,0 @@ -// SPDX-FileCopyrightText: 2022 Lido -// SPDX-License-Identifier: GPL-3.0 - -pragma solidity 0.8.10; - -import {IInterchainTokenGateway} from "./IInterchainTokenGateway.sol"; - -/// @author psirex -/// @notice L2 part of the tokens bridge compatible with Arbitrum's GatewayRouter -interface IL2TokenGateway is IInterchainTokenGateway { - /// @notice Initiates the withdrawing process from the Arbitrum chain into the Ethereum chain - /// @param l1Token_ Address in the L1 chain of the token to withdraw - /// @param to_ Address of the recipient of the token on the corresponding chain - /// @param amount_ Amount of tokens to bridge - /// @param data_ Additional data required for transaction - function outboundTransfer( - address l1Token_, - address to_, - uint256 amount_, - uint256 maxGas_, - uint256 gasPriceBid_, - bytes calldata data_ - ) external returns (bytes memory); - - event DepositFinalized( - address indexed l1Token, - address indexed from, - address indexed to, - uint256 amount - ); - - event WithdrawalInitiated( - address l1Token, - address indexed from, - address indexed to, - uint256 indexed l2ToL1Id, - uint256 exitNum, - uint256 amount - ); -} diff --git a/contracts/arbitrum/interfaces/IOutbox.sol b/contracts/arbitrum/interfaces/IOutbox.sol deleted file mode 100644 index 02fe1d64..00000000 --- a/contracts/arbitrum/interfaces/IOutbox.sol +++ /dev/null @@ -1,8 +0,0 @@ -// SPDX-FileCopyrightText: 2022 Lido -// SPDX-License-Identifier: GPL-3.0 - -pragma solidity 0.8.10; - -interface IOutbox { - function l2ToL1Sender() external view returns (address); -} diff --git a/contracts/arbitrum/libraries/L1OutboundDataParser.sol b/contracts/arbitrum/libraries/L1OutboundDataParser.sol deleted file mode 100644 index 03c99b8b..00000000 --- a/contracts/arbitrum/libraries/L1OutboundDataParser.sol +++ /dev/null @@ -1,45 +0,0 @@ -// SPDX-FileCopyrightText: 2022 Lido -// SPDX-License-Identifier: GPL-3.0 - -pragma solidity 0.8.10; - -/// @author psirex -/// @notice A helper library to parse data passed to outboundTransfer() of L1TokensGateway -library L1OutboundDataParser { - /// @dev Decodes value contained in data_ bytes array and returns it - /// @param router_ Address of the Arbitrum’s L1GatewayRouter - /// @param data_ Data encoded for the outboundTransfer() method - /// @return Decoded (from, maxSubmissionCost) values - function decode(address router_, bytes memory data_) - internal - view - returns (address, uint256) - { - if (msg.sender != router_) { - return (msg.sender, _parseSubmissionCostData(data_)); - } - (address from, bytes memory extraData) = abi.decode( - data_, - (address, bytes) - ); - return (from, _parseSubmissionCostData(extraData)); - } - - /// @dev Extracts the maxSubmissionCost value from the outboundTransfer() data - function _parseSubmissionCostData(bytes memory data_) - private - pure - returns (uint256) - { - (uint256 maxSubmissionCost, bytes memory extraData) = abi.decode( - data_, - (uint256, bytes) - ); - if (extraData.length != 0) { - revert ExtraDataNotEmpty(); - } - return maxSubmissionCost; - } - - error ExtraDataNotEmpty(); -} diff --git a/contracts/arbitrum/libraries/L2OutboundDataParser.sol b/contracts/arbitrum/libraries/L2OutboundDataParser.sol deleted file mode 100644 index f10acf90..00000000 --- a/contracts/arbitrum/libraries/L2OutboundDataParser.sol +++ /dev/null @@ -1,30 +0,0 @@ -// SPDX-FileCopyrightText: 2022 Lido -// SPDX-License-Identifier: GPL-3.0 - -pragma solidity 0.8.10; - -/// @author psirex -/// @notice A helper library to parse data passed to outboundTransfer() of L2ERC20TokenGateway -library L2OutboundDataParser { - /// @dev Decodes value contained in data_ bytes array and returns it - /// @param router_ Address of the Arbitrum’s L2GatewayRouter - /// @param data_ Data encoded for the outboundTransfer() method - /// @return from_ address of the sender - function decode(address router_, bytes memory data_) - internal - view - returns (address from_) - { - bytes memory extraData; - if (msg.sender == router_) { - (from_, extraData) = abi.decode(data_, (address, bytes)); - } else { - (from_, extraData) = (msg.sender, data_); - } - if (extraData.length != 0) { - revert ExtraDataNotEmpty(); - } - } - - error ExtraDataNotEmpty(); -} diff --git a/contracts/arbitrum/stubs/ArbSysStub.sol b/contracts/arbitrum/stubs/ArbSysStub.sol deleted file mode 100644 index 5315414b..00000000 --- a/contracts/arbitrum/stubs/ArbSysStub.sol +++ /dev/null @@ -1,24 +0,0 @@ -// SPDX-FileCopyrightText: 2022 Lido -// SPDX-License-Identifier: GPL-3.0 - -pragma solidity 0.8.10; - -contract ArbSysStub { - uint256 public l2ToL1TxId; - - function setl2ToL1TxId(uint256 l2ToL1TxId_) public { - l2ToL1TxId = l2ToL1TxId_; - } - - function sendTxToL1(address recipient, bytes calldata data) - external - payable - returns (uint256) - { - l2ToL1TxId += 1; - emit CreateL2ToL1Tx(recipient, data); - return l2ToL1TxId; - } - - event CreateL2ToL1Tx(address recipient, bytes data); -} diff --git a/contracts/arbitrum/stubs/BridgeStub.sol b/contracts/arbitrum/stubs/BridgeStub.sol deleted file mode 100644 index 9498e68e..00000000 --- a/contracts/arbitrum/stubs/BridgeStub.sol +++ /dev/null @@ -1,18 +0,0 @@ -// SPDX-FileCopyrightText: 2022 Lido -// SPDX-License-Identifier: GPL-3.0 - -pragma solidity 0.8.10; - -import {IBridge} from "../interfaces/IBridge.sol"; - -contract BridgeStub is IBridge { - address public activeOutbox; - - constructor(address activeOutbox_) payable { - activeOutbox = activeOutbox_; - } - - function setOutbox(address outbox_) external { - activeOutbox = outbox_; - } -} diff --git a/contracts/arbitrum/stubs/InboxStub.sol b/contracts/arbitrum/stubs/InboxStub.sol deleted file mode 100644 index 7cd5e884..00000000 --- a/contracts/arbitrum/stubs/InboxStub.sol +++ /dev/null @@ -1,55 +0,0 @@ -// SPDX-FileCopyrightText: 2022 Lido -// SPDX-License-Identifier: GPL-3.0 - -pragma solidity 0.8.10; - -import {IInbox} from "../interfaces/IInbox.sol"; - -contract InboxStub is IInbox { - uint256 public retryableTicketId; - address public immutable bridge; - - constructor(address bridge_) { - bridge = bridge_; - } - - function setRetryableTicketId(uint256 retryableTicketId_) public { - retryableTicketId = retryableTicketId_; - } - - function createRetryableTicket( - address destAddr, - uint256 arbTxCallValue, - uint256 maxSubmissionCost, - address submissionRefundAddress, - address valueRefundAddress, - uint256 maxGas, - uint256 gasPriceBid, - bytes calldata data - ) external payable returns (uint256) { - emit CreateRetryableTicketCalled( - msg.value, - destAddr, - arbTxCallValue, - maxSubmissionCost, - submissionRefundAddress, - valueRefundAddress, - maxGas, - gasPriceBid, - data - ); - return retryableTicketId; - } - - event CreateRetryableTicketCalled( - uint256 value, - address destAddr, - uint256 arbTxCallValue, - uint256 maxSubmissionCost, - address submissionRefundAddress, - address valueRefundAddress, - uint256 maxGas, - uint256 gasPriceBid, - bytes data - ); -} diff --git a/contracts/arbitrum/stubs/OutboxStub.sol b/contracts/arbitrum/stubs/OutboxStub.sol deleted file mode 100644 index 4278af8c..00000000 --- a/contracts/arbitrum/stubs/OutboxStub.sol +++ /dev/null @@ -1,14 +0,0 @@ -// SPDX-FileCopyrightText: 2022 Lido -// SPDX-License-Identifier: GPL-3.0 - -pragma solidity 0.8.10; - -import {IOutbox} from "../interfaces/IOutbox.sol"; - -contract OutboxStub is IOutbox { - address public l2ToL1Sender; - - function setL2ToL1Sender(address l2ToL1Sender_) external { - l2ToL1Sender = l2ToL1Sender_; - } -} diff --git a/interfaces/arbitrum/Bridge.json b/interfaces/arbitrum/Bridge.json deleted file mode 100644 index fbbbd6f5..00000000 --- a/interfaces/arbitrum/Bridge.json +++ /dev/null @@ -1,265 +0,0 @@ -[ - { - "anonymous": false, - "inputs": [ - { - "indexed": true, - "internalType": "address", - "name": "outbox", - "type": "address" - }, - { - "indexed": true, - "internalType": "address", - "name": "destAddr", - "type": "address" - }, - { - "indexed": false, - "internalType": "uint256", - "name": "amount", - "type": "uint256" - }, - { - "indexed": false, - "internalType": "bytes", - "name": "data", - "type": "bytes" - } - ], - "name": "BridgeCallTriggered", - "type": "event" - }, - { - "anonymous": false, - "inputs": [ - { - "indexed": true, - "internalType": "address", - "name": "inbox", - "type": "address" - }, - { - "indexed": false, - "internalType": "bool", - "name": "enabled", - "type": "bool" - } - ], - "name": "InboxToggle", - "type": "event" - }, - { - "anonymous": false, - "inputs": [ - { - "indexed": true, - "internalType": "uint256", - "name": "messageIndex", - "type": "uint256" - }, - { - "indexed": true, - "internalType": "bytes32", - "name": "beforeInboxAcc", - "type": "bytes32" - }, - { - "indexed": false, - "internalType": "address", - "name": "inbox", - "type": "address" - }, - { - "indexed": false, - "internalType": "uint8", - "name": "kind", - "type": "uint8" - }, - { - "indexed": false, - "internalType": "address", - "name": "sender", - "type": "address" - }, - { - "indexed": false, - "internalType": "bytes32", - "name": "messageDataHash", - "type": "bytes32" - } - ], - "name": "MessageDelivered", - "type": "event" - }, - { - "anonymous": false, - "inputs": [ - { - "indexed": true, - "internalType": "address", - "name": "outbox", - "type": "address" - }, - { - "indexed": false, - "internalType": "bool", - "name": "enabled", - "type": "bool" - } - ], - "name": "OutboxToggle", - "type": "event" - }, - { - "anonymous": false, - "inputs": [ - { - "indexed": true, - "internalType": "address", - "name": "previousOwner", - "type": "address" - }, - { - "indexed": true, - "internalType": "address", - "name": "newOwner", - "type": "address" - } - ], - "name": "OwnershipTransferred", - "type": "event" - }, - { - "inputs": [], - "name": "activeOutbox", - "outputs": [{ "internalType": "address", "name": "", "type": "address" }], - "stateMutability": "view", - "type": "function" - }, - { - "inputs": [{ "internalType": "uint256", "name": "", "type": "uint256" }], - "name": "allowedInboxList", - "outputs": [{ "internalType": "address", "name": "", "type": "address" }], - "stateMutability": "view", - "type": "function" - }, - { - "inputs": [ - { "internalType": "address", "name": "inbox", "type": "address" } - ], - "name": "allowedInboxes", - "outputs": [{ "internalType": "bool", "name": "", "type": "bool" }], - "stateMutability": "view", - "type": "function" - }, - { - "inputs": [{ "internalType": "uint256", "name": "", "type": "uint256" }], - "name": "allowedOutboxList", - "outputs": [{ "internalType": "address", "name": "", "type": "address" }], - "stateMutability": "view", - "type": "function" - }, - { - "inputs": [ - { "internalType": "address", "name": "outbox", "type": "address" } - ], - "name": "allowedOutboxes", - "outputs": [{ "internalType": "bool", "name": "", "type": "bool" }], - "stateMutability": "view", - "type": "function" - }, - { - "inputs": [ - { "internalType": "uint8", "name": "kind", "type": "uint8" }, - { "internalType": "address", "name": "sender", "type": "address" }, - { - "internalType": "bytes32", - "name": "messageDataHash", - "type": "bytes32" - } - ], - "name": "deliverMessageToInbox", - "outputs": [{ "internalType": "uint256", "name": "", "type": "uint256" }], - "stateMutability": "payable", - "type": "function" - }, - { - "inputs": [ - { "internalType": "address", "name": "destAddr", "type": "address" }, - { "internalType": "uint256", "name": "amount", "type": "uint256" }, - { "internalType": "bytes", "name": "data", "type": "bytes" } - ], - "name": "executeCall", - "outputs": [ - { "internalType": "bool", "name": "success", "type": "bool" }, - { "internalType": "bytes", "name": "returnData", "type": "bytes" } - ], - "stateMutability": "nonpayable", - "type": "function" - }, - { - "inputs": [{ "internalType": "uint256", "name": "", "type": "uint256" }], - "name": "inboxAccs", - "outputs": [{ "internalType": "bytes32", "name": "", "type": "bytes32" }], - "stateMutability": "view", - "type": "function" - }, - { - "inputs": [], - "name": "initialize", - "outputs": [], - "stateMutability": "nonpayable", - "type": "function" - }, - { - "inputs": [], - "name": "messageCount", - "outputs": [{ "internalType": "uint256", "name": "", "type": "uint256" }], - "stateMutability": "view", - "type": "function" - }, - { - "inputs": [], - "name": "owner", - "outputs": [{ "internalType": "address", "name": "", "type": "address" }], - "stateMutability": "view", - "type": "function" - }, - { - "inputs": [], - "name": "renounceOwnership", - "outputs": [], - "stateMutability": "nonpayable", - "type": "function" - }, - { - "inputs": [ - { "internalType": "address", "name": "inbox", "type": "address" }, - { "internalType": "bool", "name": "enabled", "type": "bool" } - ], - "name": "setInbox", - "outputs": [], - "stateMutability": "nonpayable", - "type": "function" - }, - { - "inputs": [ - { "internalType": "address", "name": "outbox", "type": "address" }, - { "internalType": "bool", "name": "enabled", "type": "bool" } - ], - "name": "setOutbox", - "outputs": [], - "stateMutability": "nonpayable", - "type": "function" - }, - { - "inputs": [ - { "internalType": "address", "name": "newOwner", "type": "address" } - ], - "name": "transferOwnership", - "outputs": [], - "stateMutability": "nonpayable", - "type": "function" - } -] diff --git a/interfaces/arbitrum/IMessageProvider.json b/interfaces/arbitrum/IMessageProvider.json deleted file mode 100644 index a4ae9c5c..00000000 --- a/interfaces/arbitrum/IMessageProvider.json +++ /dev/null @@ -1,21 +0,0 @@ -[ - { - "anonymous": false, - "inputs": [ - { - "indexed": true, - "internalType": "uint256", - "name": "messageNum", - "type": "uint256" - }, - { - "indexed": false, - "internalType": "bytes", - "name": "data", - "type": "bytes" - } - ], - "name": "InboxMessageDelivered", - "type": "event" - } -] diff --git a/interfaces/arbitrum/Inbox.json b/interfaces/arbitrum/Inbox.json deleted file mode 100644 index 9c224494..00000000 --- a/interfaces/arbitrum/Inbox.json +++ /dev/null @@ -1,328 +0,0 @@ -[ - { - "anonymous": false, - "inputs": [ - { - "indexed": true, - "internalType": "uint256", - "name": "messageNum", - "type": "uint256" - }, - { - "indexed": false, - "internalType": "bytes", - "name": "data", - "type": "bytes" - } - ], - "name": "InboxMessageDelivered", - "type": "event" - }, - { - "anonymous": false, - "inputs": [ - { - "indexed": true, - "internalType": "uint256", - "name": "messageNum", - "type": "uint256" - } - ], - "name": "InboxMessageDeliveredFromOrigin", - "type": "event" - }, - { - "anonymous": false, - "inputs": [ - { - "indexed": false, - "internalType": "bool", - "name": "enabled", - "type": "bool" - } - ], - "name": "PauseToggled", - "type": "event" - }, - { - "anonymous": false, - "inputs": [ - { - "indexed": false, - "internalType": "bool", - "name": "enabled", - "type": "bool" - } - ], - "name": "RewriteToggled", - "type": "event" - }, - { - "anonymous": false, - "inputs": [ - { - "indexed": false, - "internalType": "address", - "name": "newSource", - "type": "address" - } - ], - "name": "WhitelistSourceUpdated", - "type": "event" - }, - { - "inputs": [], - "name": "bridge", - "outputs": [ - { "internalType": "contract IBridge", "name": "", "type": "address" } - ], - "stateMutability": "view", - "type": "function" - }, - { - "inputs": [ - { "internalType": "address", "name": "destAddr", "type": "address" }, - { "internalType": "uint256", "name": "l2CallValue", "type": "uint256" }, - { - "internalType": "uint256", - "name": "maxSubmissionCost", - "type": "uint256" - }, - { - "internalType": "address", - "name": "excessFeeRefundAddress", - "type": "address" - }, - { - "internalType": "address", - "name": "callValueRefundAddress", - "type": "address" - }, - { "internalType": "uint256", "name": "maxGas", "type": "uint256" }, - { "internalType": "uint256", "name": "gasPriceBid", "type": "uint256" }, - { "internalType": "bytes", "name": "data", "type": "bytes" } - ], - "name": "createRetryableTicket", - "outputs": [{ "internalType": "uint256", "name": "", "type": "uint256" }], - "stateMutability": "payable", - "type": "function" - }, - { - "inputs": [ - { "internalType": "address", "name": "destAddr", "type": "address" }, - { "internalType": "uint256", "name": "l2CallValue", "type": "uint256" }, - { - "internalType": "uint256", - "name": "maxSubmissionCost", - "type": "uint256" - }, - { - "internalType": "address", - "name": "excessFeeRefundAddress", - "type": "address" - }, - { - "internalType": "address", - "name": "callValueRefundAddress", - "type": "address" - }, - { "internalType": "uint256", "name": "maxGas", "type": "uint256" }, - { "internalType": "uint256", "name": "gasPriceBid", "type": "uint256" }, - { "internalType": "bytes", "name": "data", "type": "bytes" } - ], - "name": "createRetryableTicketNoRefundAliasRewrite", - "outputs": [{ "internalType": "uint256", "name": "", "type": "uint256" }], - "stateMutability": "payable", - "type": "function" - }, - { - "inputs": [ - { - "internalType": "uint256", - "name": "maxSubmissionCost", - "type": "uint256" - } - ], - "name": "depositEth", - "outputs": [{ "internalType": "uint256", "name": "", "type": "uint256" }], - "stateMutability": "payable", - "type": "function" - }, - { - "inputs": [ - { - "internalType": "contract IBridge", - "name": "_bridge", - "type": "address" - }, - { "internalType": "address", "name": "_whitelist", "type": "address" } - ], - "name": "initialize", - "outputs": [], - "stateMutability": "nonpayable", - "type": "function" - }, - { - "inputs": [], - "name": "isCreateRetryablePaused", - "outputs": [{ "internalType": "bool", "name": "", "type": "bool" }], - "stateMutability": "view", - "type": "function" - }, - { - "inputs": [], - "name": "isMaster", - "outputs": [{ "internalType": "bool", "name": "", "type": "bool" }], - "stateMutability": "view", - "type": "function" - }, - { - "inputs": [], - "name": "pauseCreateRetryables", - "outputs": [], - "stateMutability": "nonpayable", - "type": "function" - }, - { - "inputs": [ - { "internalType": "uint256", "name": "maxGas", "type": "uint256" }, - { "internalType": "uint256", "name": "gasPriceBid", "type": "uint256" }, - { "internalType": "address", "name": "destAddr", "type": "address" }, - { "internalType": "uint256", "name": "amount", "type": "uint256" }, - { "internalType": "bytes", "name": "data", "type": "bytes" } - ], - "name": "sendContractTransaction", - "outputs": [{ "internalType": "uint256", "name": "", "type": "uint256" }], - "stateMutability": "nonpayable", - "type": "function" - }, - { - "inputs": [ - { "internalType": "uint256", "name": "maxGas", "type": "uint256" }, - { "internalType": "uint256", "name": "gasPriceBid", "type": "uint256" }, - { "internalType": "address", "name": "destAddr", "type": "address" }, - { "internalType": "bytes", "name": "data", "type": "bytes" } - ], - "name": "sendL1FundedContractTransaction", - "outputs": [{ "internalType": "uint256", "name": "", "type": "uint256" }], - "stateMutability": "payable", - "type": "function" - }, - { - "inputs": [ - { "internalType": "uint256", "name": "maxGas", "type": "uint256" }, - { "internalType": "uint256", "name": "gasPriceBid", "type": "uint256" }, - { "internalType": "uint256", "name": "nonce", "type": "uint256" }, - { "internalType": "address", "name": "destAddr", "type": "address" }, - { "internalType": "bytes", "name": "data", "type": "bytes" } - ], - "name": "sendL1FundedUnsignedTransaction", - "outputs": [{ "internalType": "uint256", "name": "", "type": "uint256" }], - "stateMutability": "payable", - "type": "function" - }, - { - "inputs": [ - { "internalType": "bytes", "name": "messageData", "type": "bytes" } - ], - "name": "sendL2Message", - "outputs": [{ "internalType": "uint256", "name": "", "type": "uint256" }], - "stateMutability": "nonpayable", - "type": "function" - }, - { - "inputs": [ - { "internalType": "bytes", "name": "messageData", "type": "bytes" } - ], - "name": "sendL2MessageFromOrigin", - "outputs": [{ "internalType": "uint256", "name": "", "type": "uint256" }], - "stateMutability": "nonpayable", - "type": "function" - }, - { - "inputs": [ - { "internalType": "uint256", "name": "maxGas", "type": "uint256" }, - { "internalType": "uint256", "name": "gasPriceBid", "type": "uint256" }, - { "internalType": "uint256", "name": "nonce", "type": "uint256" }, - { "internalType": "address", "name": "destAddr", "type": "address" }, - { "internalType": "uint256", "name": "amount", "type": "uint256" }, - { "internalType": "bytes", "name": "data", "type": "bytes" } - ], - "name": "sendUnsignedTransaction", - "outputs": [{ "internalType": "uint256", "name": "", "type": "uint256" }], - "stateMutability": "nonpayable", - "type": "function" - }, - { - "inputs": [], - "name": "shouldRewriteSender", - "outputs": [{ "internalType": "bool", "name": "", "type": "bool" }], - "stateMutability": "view", - "type": "function" - }, - { - "inputs": [], - "name": "startRewriteAddress", - "outputs": [], - "stateMutability": "nonpayable", - "type": "function" - }, - { - "inputs": [], - "name": "stopRewriteAddress", - "outputs": [], - "stateMutability": "nonpayable", - "type": "function" - }, - { - "inputs": [], - "name": "unpauseCreateRetryables", - "outputs": [], - "stateMutability": "nonpayable", - "type": "function" - }, - { - "inputs": [ - { "internalType": "address", "name": "destAddr", "type": "address" }, - { "internalType": "uint256", "name": "l2CallValue", "type": "uint256" }, - { - "internalType": "uint256", - "name": "maxSubmissionCost", - "type": "uint256" - }, - { - "internalType": "address", - "name": "excessFeeRefundAddress", - "type": "address" - }, - { - "internalType": "address", - "name": "callValueRefundAddress", - "type": "address" - }, - { "internalType": "uint256", "name": "maxGas", "type": "uint256" }, - { "internalType": "uint256", "name": "gasPriceBid", "type": "uint256" }, - { "internalType": "bytes", "name": "data", "type": "bytes" } - ], - "name": "unsafeCreateRetryableTicket", - "outputs": [{ "internalType": "uint256", "name": "", "type": "uint256" }], - "stateMutability": "payable", - "type": "function" - }, - { - "inputs": [ - { "internalType": "address", "name": "newSource", "type": "address" } - ], - "name": "updateWhitelistSource", - "outputs": [], - "stateMutability": "nonpayable", - "type": "function" - }, - { - "inputs": [], - "name": "whitelist", - "outputs": [{ "internalType": "address", "name": "", "type": "address" }], - "stateMutability": "view", - "type": "function" - } -] diff --git a/scripts/arbitrum/deploy-gateway-routers.ts b/scripts/arbitrum/deploy-gateway-routers.ts deleted file mode 100644 index ef48beea..00000000 --- a/scripts/arbitrum/deploy-gateway-routers.ts +++ /dev/null @@ -1,28 +0,0 @@ -import arbitrum from "../../utils/arbitrum"; -import env from "../../utils/env"; -import network from "../../utils/network"; -import prompt from "../../utils/prompt"; - -async function main() { - const networkName = env.network(); - - const [ethDeployer, arbDeployer] = network - .multichain(["eth", "arb"], networkName) - .getSigners(env.privateKey(), { forking: env.forking() }); - - const [l1DeployScript, l2DeployScript] = await arbitrum - .deployment(networkName, { logger: console }) - .gatewayRouterDeployScript(ethDeployer, arbDeployer); - - l1DeployScript.print(); - l2DeployScript.print(); - await prompt.proceed(); - - await l1DeployScript.run(); - await l2DeployScript.run(); -} - -main().catch((error) => { - console.error(error); - process.exitCode = 1; -}); diff --git a/scripts/arbitrum/deploy-gateway.ts b/scripts/arbitrum/deploy-gateway.ts deleted file mode 100644 index cebe3dbd..00000000 --- a/scripts/arbitrum/deploy-gateway.ts +++ /dev/null @@ -1,77 +0,0 @@ -import env from "../../utils/env"; -import prompt from "../../utils/prompt"; -import network from "../../utils/network"; -import arbitrum from "../../utils/arbitrum"; -import deployment from "../../utils/deployment"; -import { BridgingManagement } from "../../utils/bridging-management"; - -async function main() { - const networkName = env.network(); - const [ethDeployer] = network - .multichain(["eth", "arb"], networkName) - .getSigners(env.privateKey(), { forking: env.forking() }); - - const [, arbDeployer] = network - .multichain(["eth", "arb"], networkName) - .getSigners(env.string("ARB_DEPLOYER_PRIVATE_KEY"), { - forking: env.forking(), - }); - - const deploymentConfig = deployment.loadMultiChainDeploymentConfig(); - - const [l1DeployScript, l2DeployScript] = await arbitrum - .deployment(networkName, { logger: console }) - .erc20TokenGatewayDeployScript( - deploymentConfig.token, - { - deployer: ethDeployer, - admins: { - proxy: deploymentConfig.l1.proxyAdmin, - bridge: ethDeployer.address, - }, - }, - { - deployer: arbDeployer, - admins: { - proxy: deploymentConfig.l2.proxyAdmin, - bridge: arbDeployer.address, - }, - } - ); - - await deployment.printMultiChainDeploymentConfig( - "Deploy Arbitrum Gateway", - ethDeployer, - arbDeployer, - deploymentConfig, - l1DeployScript, - l2DeployScript - ); - - await prompt.proceed(); - - await l1DeployScript.run(); - await l2DeployScript.run(); - - const l1ERC20TokenGatewayProxyDeployStepIndex = 1; - const l1BridgingManagement = new BridgingManagement( - l1DeployScript.getContractAddress(l1ERC20TokenGatewayProxyDeployStepIndex), - ethDeployer, - { logger: console } - ); - - const l2ERC20TokenGatewayProxyDeployStepIndex = 3; - const l2BridgingManagement = new BridgingManagement( - l2DeployScript.getContractAddress(l2ERC20TokenGatewayProxyDeployStepIndex), - arbDeployer, - { logger: console } - ); - - await l1BridgingManagement.setup(deploymentConfig.l1); - await l2BridgingManagement.setup(deploymentConfig.l2); -} - -main().catch((error) => { - console.error(error); - process.exitCode = 1; -}); diff --git a/scripts/arbitrum/finalize-message.ts b/scripts/arbitrum/finalize-message.ts deleted file mode 100644 index 5eb83975..00000000 --- a/scripts/arbitrum/finalize-message.ts +++ /dev/null @@ -1,66 +0,0 @@ -import { L2ToL1MessageStatus, L2TransactionReceipt } from "@arbitrum/sdk"; -import env from "../../utils/env"; -import network from "../../utils/network"; - -async function main() { - const networkName = env.network(); - const ethArbNetwork = network.multichain(["eth", "arb"], networkName); - - const [ethProvider, arbProvider] = ethArbNetwork.getProviders({ - forking: false, - }); - const [ethSigner] = ethArbNetwork.getSigners(env.privateKey(), { - forking: false, - }); - - const l2TxHash = env.string("TX_HASH"); - - console.log("Tx hash:", l2TxHash); - - const receipt = await arbProvider.getTransactionReceipt(l2TxHash); - - if (!receipt) { - throw new Error( - `Receipt for tx ${l2TxHash} not found on "${networkName}" network` - ); - } - console.log(`Receipt for tx found!`); - - const l2Receipt = new L2TransactionReceipt(receipt); - - const messages = await l2Receipt.getL2ToL1Messages(ethSigner, arbProvider); - - const l2ToL1Msg = messages[0]; - - const status = await l2ToL1Msg.status(arbProvider); - if (status === L2ToL1MessageStatus.EXECUTED) { - console.log(`Message already executed! Nothing else to do here`); - return; - } - - const timeToWaitMs = 1000 * 60; - const [estimatedConfirmationBlock, currentBlock] = await Promise.all([ - l2ToL1Msg.getFirstExecutableBlock(arbProvider), - ethProvider.getBlockNumber(), - ]); - - if (estimatedConfirmationBlock) { - console.log( - `Estimated block number tx will be confirmed: ${estimatedConfirmationBlock.toString()}` - ); - console.log(`Current block number is ${currentBlock}`); - } - console.log("Waiting for the outbox entry to be created..."); - - await l2ToL1Msg.waitUntilReadyToExecute(arbProvider, timeToWaitMs); - console.log("Outbox entry exists! Trying to execute now"); - - const res = await l2ToL1Msg.execute(arbProvider); - const rec = await res.wait(); - console.log("Done! Your transaction is executed", rec); -} - -main().catch((error) => { - console.error(error); - process.exitCode = 1; -}); diff --git a/scripts/arbitrum/update-ethereum-executor.ts b/scripts/arbitrum/update-ethereum-executor.ts deleted file mode 100644 index e4407706..00000000 --- a/scripts/arbitrum/update-ethereum-executor.ts +++ /dev/null @@ -1,161 +0,0 @@ -import env from "../../utils/env"; -import network from "../../utils/network"; -import lido from "../../utils/lido"; -import arbitrum from "../../utils/arbitrum"; -import { GovBridgeExecutor__factory } from "../../typechain"; -import { assert } from "chai"; -import { L1ToL2MessageStatus } from "@arbitrum/sdk"; -import prompt from "../../utils/prompt"; -import { ethers } from "hardhat"; - -// Set address of the bridge executor to run the script -const GOV_BRIDGE_EXECUTOR = ""; - -async function main() { - const isForking = env.forking(); - const networkName = env.network(); - const ethArbNetwork = network.multichain(["eth", "arb"], networkName); - - const [l1LDOHolder] = ethArbNetwork.getSigners( - env.string("TESTING_ARB_LDO_HOLDER_PRIVATE_KEY"), - { forking: isForking } - ); - const [, arbRunner] = ethArbNetwork.getSigners(env.privateKey(), { - forking: isForking, - }); - - const govBridgeExecutor = GovBridgeExecutor__factory.connect( - GOV_BRIDGE_EXECUTOR, - arbRunner - ); - - const newEthExecutorLidoDAO = lido(networkName, l1LDOHolder); - const oldEthExecutorLidoDAO = lido( - networkName === "mainnet" ? "mainnet_test" : networkName, - l1LDOHolder - ); - const prevEthGovExecutorAddress = - await govBridgeExecutor.getEthereumGovernanceExecutor(); - - assert.equal( - oldEthExecutorLidoDAO.agent.address.toLocaleLowerCase(), - prevEthGovExecutorAddress.toLowerCase(), - `${oldEthExecutorLidoDAO.agent.address} is not current ethereumGovernanceExecutor` - ); - - console.log(` · Is forking: ${isForking}`); - console.log(` · Network Name: ${networkName}`); - console.log( - ` · Prev Ethereum Governance Executor: ${prevEthGovExecutorAddress}` - ); - console.log( - ` · New Ethereum Governance Executor: ${newEthExecutorLidoDAO.agent.address}` - ); - console.log(` · LDO Holder: ${l1LDOHolder.address}`); - console.log(` · LDO Holder ETH balance: ${await l1LDOHolder.getBalance()}`); - console.log(` · L2 tx runner: ${arbRunner.address}`); - console.log(` · L2 tx runner ETH balance: ${await arbRunner.getBalance()}`); - - await prompt.proceed(); - - console.log(`Preparing the voting tx...`); - - const arbAddresses = arbitrum.addresses(networkName); - - // Prepare data for Governance Bridge Executor - const executorCalldata = await govBridgeExecutor.interface.encodeFunctionData( - "queue", - [ - [GOV_BRIDGE_EXECUTOR], - [0], - ["updateEthereumGovernanceExecutor(address)"], - [ - ethers.utils.defaultAbiCoder.encode( - ["address"], - [newEthExecutorLidoDAO.agent.address] - ), - ], - [false], - ] - ); - - const { callvalue, calldata } = await arbitrum - .messaging(networkName, { forking: isForking }) - .prepareRetryableTicketTx({ - calldata: executorCalldata, - recipient: GOV_BRIDGE_EXECUTOR, - refundAddress: l1LDOHolder.address, - sender: oldEthExecutorLidoDAO.agent.address, - }); - - const txTransfer = await l1LDOHolder.sendTransaction({ - to: oldEthExecutorLidoDAO.agent.address, - value: callvalue, - }); - - await txTransfer.wait(); - - const createVoteTx = await oldEthExecutorLidoDAO.createVote( - l1LDOHolder, - "Update ethereumGovernanceExecutor on Arbitrum Governance Bridge Executor", - { - address: oldEthExecutorLidoDAO.agent.address, - signature: "execute(address,uint256,bytes)", - decodedCallData: [arbAddresses.Inbox, callvalue, calldata], - } - ); - - console.log("Creating voting to update ethereumGovernanceExecutor..."); - await createVoteTx.wait(); - console.log(`Vote was created!`); - - const votesCount = await oldEthExecutorLidoDAO.voting.votesLength(); - const voteId = votesCount.sub(1); - - console.log(`New vote id ${voteId.toString()}`); - console.log(`Voting for and executing the vote...`); - - const voteAndExecuteTx = await oldEthExecutorLidoDAO.voteAndExecute( - l1LDOHolder, - voteId, - true - ); - const executeTxReceipt = await voteAndExecuteTx.wait(); - - console.log(`Vote ${voteId.toString()} was executed!`); - - console.log("Waiting for L2 transaction..."); - const { status } = await arbitrum - .messaging(networkName, { forking: isForking }) - .waitForL2Message(executeTxReceipt.transactionHash); - - console.log(`Message delivered to L2`); - - assert.equal( - status, - L1ToL2MessageStatus.REDEEMED, - `L2 retryable txn failed with status ${L1ToL2MessageStatus[status]}` - ); - console.log("Task was queued on L2!"); - - console.log("Executing the queued task..."); - // execute task on L2 - const tasksCount = await govBridgeExecutor.getActionsSetCount(); - const targetTaskId = tasksCount.toNumber() - 1; - - const tx = await govBridgeExecutor.execute(targetTaskId); - await tx.wait(); - console.log("Task executed on L2!"); - - const ethereumGovernanceExecutor = - await govBridgeExecutor.getEthereumGovernanceExecutor(); - - console.log( - `New ethereum governance executor is: ${ethereumGovernanceExecutor}` - ); -} - -main().catch((error) => { - console.error(error); - process.exitCode = 1; -}); diff --git a/test/arbitrum/L1ERC20TokensGateway.unit.test.ts b/test/arbitrum/L1ERC20TokensGateway.unit.test.ts deleted file mode 100644 index fa997e5d..00000000 --- a/test/arbitrum/L1ERC20TokensGateway.unit.test.ts +++ /dev/null @@ -1,940 +0,0 @@ -import hre, { ethers } from "hardhat"; -import { wei } from "../../utils/wei"; -import { - BridgeStub__factory, - ERC20BridgedStub__factory, - InboxStub__factory, - L1ERC20TokenGateway__factory, - L2ERC20TokenGateway__factory, - OssifiableProxy__factory, - OutboxStub__factory, - EmptyContractStub__factory, -} from "../../typechain"; -import { assert } from "chai"; -import { unit } from "../../utils/testing"; - -unit("Arbitrum :: L1ERC20TokensGateway", ctxFactory) - .test("l1Token() ", async (ctx) => { - assert.equal( - await ctx.l1TokensGateway.l1Token(), - ctx.stubs.l1Token.address - ); - }) - - .test("l2Token()", async (ctx) => { - assert.equal( - await ctx.l1TokensGateway.l2Token(), - ctx.stubs.l2Token.address - ); - }) - - .test("counterpartGateway()", async (ctx) => { - assert.equal( - await ctx.l1TokensGateway.counterpartGateway(), - ctx.stubs.l2TokensGateway.address - ); - }) - - .test("router()", async (ctx) => { - assert.equal( - await ctx.l1TokensGateway.router(), - ctx.stubs.l1Router.address - ); - }) - - .test("calculateL2TokenAddress() :: correct l1Token", async (ctx) => { - const actualL2TokenAddress = - await ctx.l1TokensGateway.calculateL2TokenAddress( - ctx.stubs.l1Token.address - ); - assert.equal(actualL2TokenAddress, ctx.stubs.l2Token.address); - }) - - .test("calculateL2TokenAddress() :: incorrect l1Token", async (ctx) => { - const wrongAddress = ctx.accounts.stranger.address; - const actualL2TokenAddress = - await ctx.l1TokensGateway.calculateL2TokenAddress(wrongAddress); - assert.equal(actualL2TokenAddress, hre.ethers.constants.AddressZero); - }) - - .test("getOutboundCalldata()", async (ctx) => { - const [sender, recipient] = await hre.ethers.getSigners(); - const amount = wei`1.5 ether`; - const actualCalldata = await ctx.l1TokensGateway.getOutboundCalldata( - ctx.stubs.l1Token.address, - sender.address, - recipient.address, - amount, - "0x" - ); - - const expectedCalldata = - ctx.stubs.l2TokensGateway.interface.encodeFunctionData( - "finalizeInboundTransfer", - [ - ctx.stubs.l1Token.address, - sender.address, - recipient.address, - amount, - "0x", - ] - ); - - assert.equal(actualCalldata, expectedCalldata); - }) - - .test("outboundTransfer() :: deposits are disabled", async (ctx) => { - // validate deposits are disabled - assert.isFalse(await ctx.l1TokensGateway.isDepositsEnabled()); - - const [sender, recipient] = await hre.ethers.getSigners(); - const amount = wei`1.2 ether`; - const maxGas = wei`1000 gwei`; - const gasPriceBid = wei`2000 gwei`; - const maxSubmissionCost = wei`11_000 gwei`; - const data = encodeSenderOutboundTransferData(maxSubmissionCost); - - // validate deposit reverts with error ErrorDepositsDisabled() - await assert.revertsWith( - ctx.l1TokensGateway - .connect(sender) - .outboundTransfer( - ctx.stubs.l1Token.address, - recipient.address, - amount, - maxGas, - gasPriceBid, - data - ), - "ErrorDepositsDisabled()" - ); - }) - - .test("outboundTransfer() :: wrong l1Token address", async (ctx) => { - const [sender, recipient, wrongAddress] = await hre.ethers.getSigners(); - const amount = wei`1.2 ether`; - const maxGas = wei`1000 gwei`; - const gasPriceBid = wei`2000 gwei`; - const maxSubmissionCost = wei`11_000 gwei`; - const data = encodeSenderOutboundTransferData(maxSubmissionCost); - - const { - l1TokensGateway, - accounts: { deployer }, - } = ctx; - - // initialize gateway - await l1TokensGateway.initialize(deployer.address); - - // validate gateway was initialized - assert.isTrue(await l1TokensGateway.isInitialized()); - - // grant DEPOSITS_ENABLER_ROLE to the l1Deployer to enable deposits - await l1TokensGateway.grantRole( - await l1TokensGateway.DEPOSITS_ENABLER_ROLE(), - deployer.address - ); - - // enable deposits - await l1TokensGateway.enableDeposits(); - - // validate deposits was enabled - assert.isTrue(await l1TokensGateway.isDepositsEnabled()); - - await assert.revertsWith( - l1TokensGateway - .connect(sender) - .outboundTransfer( - wrongAddress.address, - recipient.address, - amount, - maxGas, - gasPriceBid, - data - ), - "ErrorUnsupportedL1Token()" - ); - }) - - .test("outboundTransfer() :: max submission cost is zero", async (ctx) => { - const { - l1TokensGateway, - stubs: { l1Token, inbox }, - accounts: { deployer }, - } = ctx; - - // initialize gateway - await l1TokensGateway.initialize(deployer.address); - - // validate gateway was initialized - assert.isTrue(await l1TokensGateway.isInitialized()); - - // grant DEPOSITS_ENABLER_ROLE to the l1Deployer to enable deposits - await l1TokensGateway.grantRole( - await l1TokensGateway.DEPOSITS_ENABLER_ROLE(), - deployer.address - ); - - // enable deposits - await l1TokensGateway.enableDeposits(); - - // validate deposits was enabled - assert.isTrue(await l1TokensGateway.isDepositsEnabled()); - - const [sender, recipient] = await hre.ethers.getSigners(); - const amount = wei`1.2 ether`; - const maxGas = wei`1000 gwei`; - const gasPriceBid = wei`2000 gwei`; - const maxSubmissionCost = wei`0 wei`; - const value = wei`3000 gwei`; - const data = encodeSenderOutboundTransferData(maxSubmissionCost); - - // set allowance for l1TokensGateway before transfer - await l1Token.connect(sender).approve(l1TokensGateway.address, amount); - - const retryableTicketId = 13; - await inbox.setRetryableTicketId(retryableTicketId); - - assert.equalBN(await inbox.retryableTicketId(), retryableTicketId); - - // initiate outbound transfer - await assert.revertsWith( - l1TokensGateway - .connect(sender) - .outboundTransfer( - l1Token.address, - recipient.address, - amount, - maxGas, - gasPriceBid, - data, - { value } - ), - "ErrorNoMaxSubmissionCost()" - ); - }) - - .test("outboundTransfer() :: ETH value too low", async (ctx) => { - const { - l1TokensGateway, - stubs: { l1Token, inbox }, - accounts: { deployer }, - } = ctx; - - // initialize gateway - await l1TokensGateway.initialize(deployer.address); - - // validate gateway was initialized - assert.isTrue(await l1TokensGateway.isInitialized()); - - // grant DEPOSITS_ENABLER_ROLE to the l1Deployer to enable deposits - await l1TokensGateway.grantRole( - await l1TokensGateway.DEPOSITS_ENABLER_ROLE(), - deployer.address - ); - - // enable deposits - await l1TokensGateway.enableDeposits(); - - // validate deposits was enabled - assert.isTrue(await l1TokensGateway.isDepositsEnabled()); - - const [sender, recipient] = await hre.ethers.getSigners(); - const amount = wei`1.2 ether`; - const maxGas = wei`100_000`; - const gasPriceBid = wei`2 gwei`; - const maxSubmissionCost = wei`50_000 gwei`; - const value = wei`200_000 gwei`; - const data = encodeSenderOutboundTransferData(maxSubmissionCost); - - // set allowance for l1TokensGateway before transfer - await l1Token.connect(sender).approve(l1TokensGateway.address, amount); - - const retryableTicketId = 13; - await inbox.setRetryableTicketId(retryableTicketId); - - assert.equalBN(await inbox.retryableTicketId(), retryableTicketId); - - // initiate outbound transfer - await assert.revertsWith( - l1TokensGateway - .connect(sender) - .outboundTransfer( - l1Token.address, - recipient.address, - amount, - maxGas, - gasPriceBid, - data, - { value } - ), - "ErrorETHValueTooLow()" - ); - }) - - .test("outboundTransfer() :: extra data not empty", async (ctx) => { - const { - l1TokensGateway, - stubs: { l1Token }, - accounts: { deployer, l1RouterAsEOA }, - } = ctx; - - // initialize gateway - await l1TokensGateway.initialize(deployer.address); - - // validate gateway was initialized - assert.isTrue(await l1TokensGateway.isInitialized()); - - // grant DEPOSITS_ENABLER_ROLE to the l1Deployer to enable deposits - await l1TokensGateway.grantRole( - await l1TokensGateway.DEPOSITS_ENABLER_ROLE(), - deployer.address - ); - - // enable deposits - await l1TokensGateway.enableDeposits(); - - // validate deposits was enabled - assert.isTrue(await l1TokensGateway.isDepositsEnabled()); - - const [sender, recipient] = await hre.ethers.getSigners(); - const amount = wei`1.2 ether`; - const maxGas = wei`1000 gwei`; - const gasPriceBid = wei`2000 gwei`; - const maxSubmissionCost = wei`11_000 gwei`; - const value = wei`3000 gwei`; - const data = encodeRouterOutboundTransferData( - sender.address, - maxSubmissionCost, - "0xdeadbeef" - ); - - // initiate outbound transfer - await assert.revertsWith( - l1TokensGateway - .connect(l1RouterAsEOA) - .outboundTransfer( - l1Token.address, - recipient.address, - amount, - maxGas, - gasPriceBid, - data, - { value } - ), - "ExtraDataNotEmpty()" - ); - }) - - .test("outboundTransfer() :: ETH value too low", async (ctx) => { - const { - l1TokensGateway, - stubs: { l1Token, inbox }, - accounts: { deployer }, - } = ctx; - - // initialize gateway - await l1TokensGateway.initialize(deployer.address); - - // validate gateway was initialized - assert.isTrue(await l1TokensGateway.isInitialized()); - - // grant DEPOSITS_ENABLER_ROLE to the l1Deployer to enable deposits - await l1TokensGateway.grantRole( - await l1TokensGateway.DEPOSITS_ENABLER_ROLE(), - deployer.address - ); - - // enable deposits - await l1TokensGateway.enableDeposits(); - - // validate deposits was enabled - assert.isTrue(await l1TokensGateway.isDepositsEnabled()); - - const [sender, recipient] = await hre.ethers.getSigners(); - const amount = wei`1.2 ether`; - const maxGas = wei`100_000`; - const gasPriceBid = wei`2 gwei`; - const maxSubmissionCost = wei`50_000 gwei`; - const value = wei`200_000 gwei`; - const data = encodeSenderOutboundTransferData(maxSubmissionCost); - - // set allowance for l1TokensGateway before transfer - await l1Token.connect(sender).approve(l1TokensGateway.address, amount); - - const retryableTicketId = 13; - await inbox.setRetryableTicketId(retryableTicketId); - - assert.equalBN(await inbox.retryableTicketId(), retryableTicketId); - - // initiate outbound transfer - await assert.revertsWith( - l1TokensGateway - .connect(sender) - .outboundTransfer( - l1Token.address, - recipient.address, - amount, - maxGas, - gasPriceBid, - data, - { value } - ), - "ErrorETHValueTooLow()" - ); - }) - - .test("outboundTransfer() :: called by router", async (ctx) => { - const { - l1TokensGateway, - stubs: { l1Token, inbox, l2TokensGateway }, - accounts: { deployer, sender, recipient, l1RouterAsEOA }, - } = ctx; - - // initialize gateway - await l1TokensGateway.initialize(deployer.address); - - // validate gateway was initialized - assert.isTrue(await l1TokensGateway.isInitialized()); - - // grant DEPOSITS_ENABLER_ROLE to the l1Deployer to enable deposits - await l1TokensGateway.grantRole( - await l1TokensGateway.DEPOSITS_ENABLER_ROLE(), - deployer.address - ); - - // enable deposits - await l1TokensGateway.enableDeposits(); - - // validate deposits was enabled - assert.isTrue(await l1TokensGateway.isDepositsEnabled()); - - const amount = wei`1.2 ether`; - const maxGas = wei`100_000`; - const gasPriceBid = wei`2 gwei`; - const maxSubmissionCost = wei`50_000 gwei`; - const value = wei`250_000 gwei`; - const data = encodeRouterOutboundTransferData( - sender.address, - maxSubmissionCost - ); - - const retryableTicketId = 7; - await inbox.setRetryableTicketId(retryableTicketId); - - assert.equalBN(await inbox.retryableTicketId(), retryableTicketId); - - const senderBalanceBefore = await l1Token.balanceOf(sender.address); - - // set allowance for l1TokensGateway before transfer - await l1Token.connect(sender).approve(l1TokensGateway.address, amount); - - // call tx locally to check return value - assert.equal( - await l1TokensGateway - .connect(l1RouterAsEOA) - .callStatic.outboundTransfer( - l1Token.address, - recipient.address, - amount, - maxGas, - gasPriceBid, - data, - { value } - ), - hre.ethers.utils.defaultAbiCoder.encode(["uint256"], [retryableTicketId]) - ); - - // initiate outbound transfer - const tx = await l1TokensGateway - .connect(l1RouterAsEOA) - .outboundTransfer( - l1Token.address, - recipient.address, - amount, - maxGas, - gasPriceBid, - data, - { value } - ); - - // validate DepositInitiated event was emitted - await assert.emits(l1TokensGateway, tx, "DepositInitiated", [ - l1Token.address, - sender.address, - recipient.address, - retryableTicketId, - amount, - ]); - - const expectedCalldata = l2TokensGateway.interface.encodeFunctionData( - "finalizeInboundTransfer", - [l1Token.address, sender.address, recipient.address, amount, "0x"] - ); - - // validate TxToL2 was emitted - await assert.emits(l1TokensGateway, tx, "TxToL2", [ - sender.address, - l2TokensGateway.address, - retryableTicketId, - expectedCalldata, - ]); - - // validate CreateRetryableTicketCalled event was emitted - await assert.emits(inbox, tx, "CreateRetryableTicketCalled", [ - value, - l2TokensGateway.address, - 0, - maxSubmissionCost, - sender.address, - sender.address, - maxGas, - gasPriceBid, - expectedCalldata, - ]); - - // validate balance of the sender decreased - assert.equalBN( - await l1Token.balanceOf(sender.address), - senderBalanceBefore.sub(amount) - ); - - // validate balance of the gateway increased - assert.equalBN(await l1Token.balanceOf(l1TokensGateway.address), amount); - }) - - .test("outboundTransfer() :: called by sender", async (ctx) => { - const { - l1TokensGateway, - stubs: { l1Token, inbox, l2TokensGateway }, - accounts: { deployer }, - } = ctx; - - // initialize gateway - await l1TokensGateway.initialize(deployer.address); - - // validate gateway was initialized - assert.isTrue(await l1TokensGateway.isInitialized()); - - // grant DEPOSITS_ENABLER_ROLE to the l1Deployer to enable deposits - await l1TokensGateway.grantRole( - await l1TokensGateway.DEPOSITS_ENABLER_ROLE(), - deployer.address - ); - - // enable deposits - await l1TokensGateway.enableDeposits(); - - // validate deposits was enabled - assert.isTrue(await l1TokensGateway.isDepositsEnabled()); - - const [sender, recipient] = await hre.ethers.getSigners(); - const amount = wei`1.2 ether`; - const maxGas = wei`100_000`; - const gasPriceBid = wei`2 gwei`; - const maxSubmissionCost = wei`50_000 gwei`; - const value = wei`250_000 gwei`; - const data = encodeSenderOutboundTransferData(maxSubmissionCost); - - const senderBalanceBefore = await l1Token.balanceOf(sender.address); - - // set allowance for l1TokensGateway before transfer - await l1Token.connect(sender).approve(l1TokensGateway.address, amount); - - const retryableTicketId = 13; - await inbox.setRetryableTicketId(retryableTicketId); - - assert.equalBN(await inbox.retryableTicketId(), retryableTicketId); - - // initiate outbound transfer - const tx = await l1TokensGateway - .connect(sender) - .outboundTransfer( - l1Token.address, - recipient.address, - amount, - maxGas, - gasPriceBid, - data, - { value } - ); - - // validate DepositInitiated event was emitted - await assert.emits(l1TokensGateway, tx, "DepositInitiated", [ - l1Token.address, - sender.address, - recipient.address, - retryableTicketId, - amount, - ]); - - const expectedCalldata = l2TokensGateway.interface.encodeFunctionData( - "finalizeInboundTransfer", - [l1Token.address, sender.address, recipient.address, amount, "0x"] - ); - - // validate TxToL2 was emitted - await assert.emits(l1TokensGateway, tx, "TxToL2", [ - sender.address, - l2TokensGateway.address, - retryableTicketId, - expectedCalldata, - ]); - - // validate CreateRetryableTicketCalled event was emitted - await assert.emits(inbox, tx, "CreateRetryableTicketCalled", [ - value, - l2TokensGateway.address, - 0, - maxSubmissionCost, - sender.address, - sender.address, - maxGas, - gasPriceBid, - expectedCalldata, - ]); - - // validate balance of the sender decreased - assert.equalBN( - await l1Token.balanceOf(sender.address), - senderBalanceBefore.sub(amount) - ); - - // validate balance of the gateway increased - assert.equalBN(await l1Token.balanceOf(l1TokensGateway.address), amount); - }) - - .test("finalizeInboundTransfer() :: withdrawals disabled", async (ctx) => { - const { - l1TokensGateway, - accounts: { deployer, bridgeAsEOA, sender, recipient }, - stubs: { l1Token }, - } = ctx; - - // initialize gateway - await l1TokensGateway.initialize(deployer.address); - - // validate gateway was initialized - assert.isTrue(await l1TokensGateway.isInitialized()); - - // validate withdrawals disabled - assert.isFalse(await l1TokensGateway.isWithdrawalsEnabled()); - - await assert.revertsWith( - l1TokensGateway - .connect(bridgeAsEOA) - .finalizeInboundTransfer( - l1Token.address, - sender.address, - recipient.address, - wei`10 ether`, - "0x" - ), - "ErrorWithdrawalsDisabled()" - ); - }) - .test("finalizeInboundTransfer() :: unauthorized bridge", async (ctx) => { - const { - l1TokensGateway, - accounts: { deployer, stranger, sender, recipient }, - stubs: { l1Token }, - } = ctx; - - // initialize gateway - await l1TokensGateway.initialize(deployer.address); - - // validate gateway was initialized - assert.isTrue(await l1TokensGateway.isInitialized()); - - // grant WITHDRAWALS_ENABLER_ROLE to the l1Deployer to enable withdrawals - await l1TokensGateway.grantRole( - await l1TokensGateway.WITHDRAWALS_ENABLER_ROLE(), - deployer.address - ); - - // enable withdrawals - await l1TokensGateway.enableWithdrawals(); - - // validate withdrawals were enabled - assert.isTrue(await l1TokensGateway.isWithdrawalsEnabled()); - - // validate that stranger address is not counterpartGateway - assert.notEqual( - await l1TokensGateway.counterpartGateway(), - stranger.address - ); - - // validate gateway reverts with ErrorUnauthorizedBridge() - await assert.revertsWith( - l1TokensGateway - .connect(stranger) - .finalizeInboundTransfer( - l1Token.address, - sender.address, - recipient.address, - wei`10 ether`, - "0x" - ), - "ErrorUnauthorizedBridge()" - ); - }) - - .test( - "finalizeInboundTransfer() :: wrong cross domain sender", - async (ctx) => { - const { - l1TokensGateway, - accounts: { deployer, stranger, sender, recipient, bridgeAsEOA }, - stubs: { l1Token, outbox }, - } = ctx; - - // initialize gateway - await l1TokensGateway.initialize(deployer.address); - - // validate gateway was initialized - assert.isTrue(await l1TokensGateway.isInitialized()); - - // grant WITHDRAWALS_ENABLER_ROLE to the l1Deployer to enable withdrawals - await l1TokensGateway.grantRole( - await l1TokensGateway.WITHDRAWALS_ENABLER_ROLE(), - deployer.address - ); - - // enable withdrawals - await l1TokensGateway.enableWithdrawals(); - - // validate withdrawals were enabled - assert.isTrue(await l1TokensGateway.isWithdrawalsEnabled()); - - // validate that stranger address is not counterpartGateway - assert.notEqual( - await l1TokensGateway.counterpartGateway(), - stranger.address - ); - - // prepare OutboxStub to return wrong gateway address - await outbox.setL2ToL1Sender(stranger.address); - - // validate gateway reverts with ErrorWrongCrossDomainSender() - await assert.revertsWith( - l1TokensGateway - .connect(bridgeAsEOA) - .finalizeInboundTransfer( - l1Token.address, - sender.address, - recipient.address, - wei`10 ether`, - "0x" - ), - "ErrorWrongCrossDomainSender()" - ); - } - ) - - .test("finalizeInboundTransfer() :: wrong token", async (ctx) => { - const { - l1TokensGateway, - accounts: { stranger, sender, recipient, deployer, bridgeAsEOA }, - } = ctx; - const wrongTokenAddress = stranger.address; - - // initialize gateway - await l1TokensGateway.initialize(deployer.address); - - // validate gateway was initialized - assert.isTrue(await l1TokensGateway.isInitialized()); - - // grant WITHDRAWALS_ENABLER_ROLE to the l1Deployer to enable withdrawals - await l1TokensGateway.grantRole( - await l1TokensGateway.WITHDRAWALS_ENABLER_ROLE(), - deployer.address - ); - - // enable withdrawals - await l1TokensGateway.enableWithdrawals(); - - // validate withdrawals were enabled - assert.isTrue(await l1TokensGateway.isWithdrawalsEnabled()); - - // validate gateway reverts with ErrorUnsupportedL1Token() - await assert.revertsWith( - l1TokensGateway - .connect(bridgeAsEOA) - .finalizeInboundTransfer( - wrongTokenAddress, - sender.address, - recipient.address, - wei`10 ether`, - "0x" - ), - "ErrorUnsupportedL1Token()" - ); - }) - - .test("finalizeInboundTransfer() :: works as expected", async (ctx) => { - const { - l1TokensGateway, - accounts: { sender, recipient, deployer, bridgeAsEOA }, - stubs: { l1Token }, - } = ctx; - - // initialize gateway - await l1TokensGateway.initialize(deployer.address); - - // validate gateway was initialized - assert.isTrue(await l1TokensGateway.isInitialized()); - - // grant WITHDRAWALS_ENABLER_ROLE to the l1Deployer to enable withdrawals - await l1TokensGateway.grantRole( - await l1TokensGateway.WITHDRAWALS_ENABLER_ROLE(), - deployer.address - ); - - // enable withdrawals - await l1TokensGateway.enableWithdrawals(); - - // validate withdrawals were enabled - assert.isTrue(await l1TokensGateway.isWithdrawalsEnabled()); - - // transfer l1Tokens to l1TokensGateway - const initialL1GatewayBalance = wei`10_000 ether`; - await l1Token.transfer(l1TokensGateway.address, initialL1GatewayBalance); - - // validate l1TokensGateway has enough amount of tokens - assert.equal( - await l1Token.balanceOf(l1TokensGateway.address).then(wei.fromBigNumber), - initialL1GatewayBalance - ); - - const amount = wei`10 ether`; - - const tx = await l1TokensGateway - .connect(bridgeAsEOA) - .finalizeInboundTransfer( - l1Token.address, - sender.address, - recipient.address, - amount, - "0x" - ); - - // validate WithdrawalFinalized was emitted - assert.emits(l1TokensGateway, tx, "WithdrawalFinalized", [ - l1Token.address, - sender.address, - recipient.address, - 0, - amount, - ]); - - // validate tokens were transferred to recipient - assert.equalBN(await l1Token.balanceOf(recipient.address), amount); - - // validate balance of the l1TokensGateway was decreased - assert.equalBN( - await l1Token.balanceOf(l1TokensGateway.address), - wei.toBigNumber(initialL1GatewayBalance).sub(amount) - ); - }) - - .run(); - -async function ctxFactory() { - const [deployer, stranger, sender, recipient] = await hre.ethers.getSigners(); - const outboxStub = await new OutboxStub__factory(deployer).deploy(); - const bridgeStub = await new BridgeStub__factory(deployer).deploy( - outboxStub.address, - { value: wei.toBigNumber(wei`1 ether`) } - ); - const inboxStub = await new InboxStub__factory(deployer).deploy( - bridgeStub.address - ); - const l1RouterStub = await new EmptyContractStub__factory(deployer).deploy({ - value: wei.toBigNumber(wei`1 ether`), - }); - const l2TokensGatewayStub = await new EmptyContractStub__factory( - deployer - ).deploy(); - - await outboxStub.setL2ToL1Sender(l2TokensGatewayStub.address); - await hre.network.provider.request({ - method: "hardhat_impersonateAccount", - params: [bridgeStub.address], - }); - const l2TokenStub = await new EmptyContractStub__factory(deployer).deploy(); - const l1TokenStub = await new ERC20BridgedStub__factory(deployer).deploy( - "ERC20 Mock", - "ERC20" - ); - await l1TokenStub.transfer(sender.address, wei`100 ether`); - - const l1TokensGatewayImpl = await new L1ERC20TokenGateway__factory( - deployer - ).deploy( - inboxStub.address, - l1RouterStub.address, - l2TokensGatewayStub.address, - l1TokenStub.address, - l2TokenStub.address - ); - const l1TokensGatewayProxy = await new OssifiableProxy__factory( - deployer - ).deploy(l1TokensGatewayImpl.address, deployer.address, "0x"); - - await hre.network.provider.request({ - method: "hardhat_impersonateAccount", - params: [l1RouterStub.address], - }); - const l1RouterAsEOA = await hre.ethers.getSigner(l1RouterStub.address); - - return { - accounts: { - deployer, - stranger, - sender, - recipient, - bridgeAsEOA: await ethers.getSigner(bridgeStub.address), - l1RouterAsEOA, - }, - stubs: { - inbox: inboxStub, - outbox: outboxStub, - bridge: bridgeStub, - l1Token: l1TokenStub, - l2Token: l2TokenStub, - l1Router: l1RouterStub, - l2TokensGateway: L2ERC20TokenGateway__factory.connect( - l2TokensGatewayStub.address, - deployer - ), - }, - l1TokensGateway: L1ERC20TokenGateway__factory.connect( - l1TokensGatewayProxy.address, - deployer - ), - }; -} - -function encodeSenderOutboundTransferData(maxSubmissionCost: string) { - return hre.ethers.utils.defaultAbiCoder.encode( - ["uint256", "bytes"], - [maxSubmissionCost, "0x"] - ); -} - -function encodeRouterOutboundTransferData( - sender: string, - maxSubmissionCost: string, - extraData = "0x" -) { - return hre.ethers.utils.defaultAbiCoder.encode( - ["address", "bytes"], - [ - sender, - hre.ethers.utils.defaultAbiCoder.encode( - ["uint256", "bytes"], - [maxSubmissionCost, extraData] - ), - ] - ); -} diff --git a/test/arbitrum/L2ERC20TokensGateway.unit.test.ts b/test/arbitrum/L2ERC20TokensGateway.unit.test.ts deleted file mode 100644 index 81fb6068..00000000 --- a/test/arbitrum/L2ERC20TokensGateway.unit.test.ts +++ /dev/null @@ -1,663 +0,0 @@ -import hre, { ethers } from "hardhat"; -import { wei } from "../../utils/wei"; -import { - ERC20BridgedStub__factory, - L1ERC20TokenGateway__factory, - L2ERC20TokenGateway__factory, - OssifiableProxy__factory, - EmptyContractStub__factory, -} from "../../typechain"; -import { assert } from "chai"; -import testing, { unit } from "../../utils/testing"; -import { ArbSysStub__factory } from "../../typechain/factories/ArbSysStub__factory"; - -unit("Arbitrum :: L2ERC20TokensGateway", ctxFactory) - .test("l1Token()", async (ctx) => { - assert.equal( - await ctx.l2TokensGateway.l1Token(), - ctx.stubs.l1Token.address - ); - }) - - .test("l2Token()", async (ctx) => { - assert.equal( - await ctx.l2TokensGateway.l2Token(), - ctx.stubs.l2Token.address - ); - }) - - .test("counterpartGateway()", async (ctx) => { - assert.equal( - await ctx.l2TokensGateway.counterpartGateway(), - ctx.stubs.l1TokensGateway.address - ); - }) - - .test("router() ", async (ctx) => { - assert.equal( - await ctx.l2TokensGateway.router(), - ctx.stubs.l2Router.address - ); - }) - - .test("calculateL2TokenAddress() :: correct l1Token address", async (ctx) => { - const actualL2TokenAddress = - await ctx.l2TokensGateway.calculateL2TokenAddress( - ctx.stubs.l1Token.address - ); - assert.equal(actualL2TokenAddress, ctx.stubs.l2Token.address); - }) - - .test( - "calculateL2TokenAddress() :: incorrect l1Token address", - async (ctx) => { - const wrongAddress = ctx.accounts.stranger.address; - const actualL2TokenAddress = - await ctx.l2TokensGateway.calculateL2TokenAddress(wrongAddress); - assert.equal(actualL2TokenAddress, hre.ethers.constants.AddressZero); - } - ) - - .test("getOutboundCalldata()", async (ctx) => { - const { - l2TokensGateway, - stubs: { l1Token, l1TokensGateway }, - accounts: { sender, recipient }, - } = ctx; - const amount = wei`1.5 ether`; - const actualCalldata = await l2TokensGateway.getOutboundCalldata( - l1Token.address, - sender.address, - recipient.address, - amount, - "0x" - ); - - const expectedCalldata = l1TokensGateway.interface.encodeFunctionData( - "finalizeInboundTransfer", - [l1Token.address, sender.address, recipient.address, amount, "0x"] - ); - - assert.equal(actualCalldata, expectedCalldata); - }) - - .test("outboundTransfer() :: withdrawals are disabled", async (ctx) => { - const { - l2TokensGateway, - accounts: { sender, recipient }, - } = ctx; - // validate deposits are disabled - assert.isFalse(await l2TokensGateway.isDepositsEnabled()); - - const amount = wei`1.2 ether`; - const maxGas = wei`1000 gwei`; - const gasPriceBid = wei`2000 gwei`; - const maxSubmissionCost = wei`11_000 gwei`; - const data = encodeOutboundTransferData(maxSubmissionCost); - - // validate deposit reverts with error ErrorWithdrawalsDisabled() - await assert.revertsWith( - l2TokensGateway - .connect(sender) - .outboundTransfer( - ctx.stubs.l1Token.address, - recipient.address, - amount, - maxGas, - gasPriceBid, - data - ), - "ErrorWithdrawalsDisabled()" - ); - }) - - .test("outboundTransfer() :: wrong l1Token address", async (ctx) => { - const amount = wei`1.2 ether`; - const maxGas = wei`1000 gwei`; - const gasPriceBid = wei`2000 gwei`; - const maxSubmissionCost = wei`11_000 gwei`; - const data = encodeOutboundTransferData(maxSubmissionCost); - - const { - l2TokensGateway, - accounts: { deployer, sender, recipient }, - stubs: { l2Token: wrongAddress }, - } = ctx; - - // initialize gateway - await l2TokensGateway.initialize(deployer.address); - - // validate gateway was initialized - assert.isTrue(await l2TokensGateway.isInitialized()); - - // grant WITHDRAWALS_ENABLER_ROLE to the l1Deployer to enable deposits - await l2TokensGateway.grantRole( - await l2TokensGateway.WITHDRAWALS_ENABLER_ROLE(), - deployer.address - ); - - // enable deposits - await l2TokensGateway.enableWithdrawals(); - - // validate deposits was enabled - assert.isTrue(await l2TokensGateway.isWithdrawalsEnabled()); - - await assert.revertsWith( - l2TokensGateway - .connect(sender) - .outboundTransfer( - wrongAddress.address, - recipient.address, - amount, - maxGas, - gasPriceBid, - data - ), - "ErrorUnsupportedL1Token()" - ); - }) - - .test("outboundTransfer() :: extra data not empty", async (ctx) => { - const { - l2TokensGateway, - stubs: { l1Token }, - accounts: { deployer }, - } = ctx; - - // initialize gateway - await l2TokensGateway.initialize(deployer.address); - - // validate gateway was initialized - assert.isTrue(await l2TokensGateway.isInitialized()); - - // grant WITHDRAWALS_ENABLER_ROLE to the l1Deployer to enable deposits - await l2TokensGateway.grantRole( - await l2TokensGateway.WITHDRAWALS_ENABLER_ROLE(), - deployer.address - ); - - // enable deposits - await l2TokensGateway.enableWithdrawals(); - - // validate deposits was enabled - assert.isTrue(await l2TokensGateway.isWithdrawalsEnabled()); - - const [sender, recipient] = await hre.ethers.getSigners(); - const amount = wei`1.2 ether`; - const maxGas = wei`1000 gwei`; - const gasPriceBid = wei`2000 gwei`; - const data = "0xdeadbeaf"; - - // initiate outbound transfer - await assert.revertsWith( - l2TokensGateway - .connect(sender) - .outboundTransfer( - l1Token.address, - recipient.address, - amount, - maxGas, - gasPriceBid, - data - ), - "ExtraDataNotEmpty()" - ); - }) - - .test("outboundTransfer() :: called by router", async (ctx) => { - const { - l2TokensGateway, - stubs: { l1Token, arbSys, l1TokensGateway }, - accounts: { deployer, l2RouterAsEOA }, - } = ctx; - - // initialize gateway - await l2TokensGateway.initialize(deployer.address); - - // validate gateway was initialized - assert.isTrue(await l2TokensGateway.isInitialized()); - - // grant WITHDRAWALS_ENABLER_ROLE to the l1Deployer to enable deposits - await l2TokensGateway.grantRole( - await l2TokensGateway.WITHDRAWALS_ENABLER_ROLE(), - deployer.address - ); - - // enable deposits - await l2TokensGateway.enableWithdrawals(); - - // validate deposits was enabled - assert.isTrue(await l2TokensGateway.isWithdrawalsEnabled()); - - const l2ToL1Id = 10; - - // set l2ToL1Id value in ArbSysStub - await arbSys.setl2ToL1TxId(l2ToL1Id); - - // validate value was set - assert.equalBN(await arbSys.l2ToL1TxId(), l2ToL1Id); - - const [sender, recipient] = await hre.ethers.getSigners(); - const amount = wei`1.2 ether`; - const maxGas = wei`1000 gwei`; - const gasPriceBid = wei`2000 gwei`; - const data = hre.ethers.utils.defaultAbiCoder.encode( - ["address", "bytes"], - [sender.address, "0x"] - ); - - // initiate outbound transfer - const tx = await l2TokensGateway - .connect(l2RouterAsEOA) - .outboundTransfer( - l1Token.address, - recipient.address, - amount, - maxGas, - gasPriceBid, - data - ); - - const expectedCalldata = - ctx.stubs.l1TokensGateway.interface.encodeFunctionData( - "finalizeInboundTransfer", - [l1Token.address, sender.address, recipient.address, amount, "0x"] - ); - - // validate TxToL1 event was emitted - await assert.emits(l2TokensGateway, tx, "TxToL1", [ - sender.address, - l1TokensGateway.address, - l2ToL1Id + 1, - expectedCalldata, - ]); - - // validate DepositInitiated event was emitted - await assert.emits(l2TokensGateway, tx, "WithdrawalInitiated", [ - l1Token.address, - sender.address, - recipient.address, - l2ToL1Id + 1, - 0, - amount, - ]); - - // validate CreateL2ToL1Tx event was emitted - await assert.emits(arbSys, tx, "CreateL2ToL1Tx", [ - l1TokensGateway.address, - expectedCalldata, - ]); - }) - - .test("outboundTransfer() :: called by sender", async (ctx) => { - const { - l2TokensGateway, - stubs: { l1Token, arbSys, l1TokensGateway }, - accounts: { deployer }, - } = ctx; - - // initialize gateway - await l2TokensGateway.initialize(deployer.address); - - // validate gateway was initialized - assert.isTrue(await l2TokensGateway.isInitialized()); - - // grant WITHDRAWALS_ENABLER_ROLE to the l1Deployer to enable deposits - await l2TokensGateway.grantRole( - await l2TokensGateway.WITHDRAWALS_ENABLER_ROLE(), - deployer.address - ); - - // enable deposits - await l2TokensGateway.enableWithdrawals(); - - // validate deposits was enabled - assert.isTrue(await l2TokensGateway.isWithdrawalsEnabled()); - - const l2ToL1Id = 10; - - // set l2ToL1Id value in ArbSysStub - await arbSys.setl2ToL1TxId(l2ToL1Id); - - // validate value was set - assert.equalBN(await arbSys.l2ToL1TxId(), l2ToL1Id); - - const [sender, recipient] = await hre.ethers.getSigners(); - const amount = wei`1.2 ether`; - const maxGas = wei`1000 gwei`; - const gasPriceBid = wei`2000 gwei`; - const data = "0x"; - - // call tx locally to check return value - assert.equal( - await l2TokensGateway - .connect(sender) - .callStatic.outboundTransfer( - l1Token.address, - recipient.address, - amount, - maxGas, - gasPriceBid, - data - ), - hre.ethers.utils.defaultAbiCoder.encode(["uint256"], [l2ToL1Id + 1]) - ); - - // initiate outbound transfer - const tx = await l2TokensGateway - .connect(sender) - .outboundTransfer( - l1Token.address, - recipient.address, - amount, - maxGas, - gasPriceBid, - data - ); - - const expectedCalldata = - ctx.stubs.l1TokensGateway.interface.encodeFunctionData( - "finalizeInboundTransfer", - [l1Token.address, sender.address, recipient.address, amount, "0x"] - ); - - // validate TxToL1 event was emitted - await assert.emits(l2TokensGateway, tx, "TxToL1", [ - sender.address, - l1TokensGateway.address, - l2ToL1Id + 1, - expectedCalldata, - ]); - - // validate DepositInitiated event was emitted - await assert.emits(l2TokensGateway, tx, "WithdrawalInitiated", [ - l1Token.address, - sender.address, - recipient.address, - l2ToL1Id + 1, - 0, - amount, - ]); - - // validate CreateL2ToL1Tx event was emitted - await assert.emits(arbSys, tx, "CreateL2ToL1Tx", [ - l1TokensGateway.address, - expectedCalldata, - ]); - }) - - .test("finalizeInboundTransfer() :: deposits disabled", async (ctx) => { - const { - l2TokensGateway, - accounts: { deployer, l1TokensGatewayAliasedEOA, sender, recipient }, - stubs: { l1Token }, - } = ctx; - - // initialize gateway - await l2TokensGateway.initialize(deployer.address); - - // validate gateway was initialized - assert.isTrue(await l2TokensGateway.isInitialized()); - - // validate withdrawals disabled - assert.isFalse(await l2TokensGateway.isWithdrawalsEnabled()); - - await assert.revertsWith( - l2TokensGateway - .connect(l1TokensGatewayAliasedEOA) - .finalizeInboundTransfer( - l1Token.address, - sender.address, - recipient.address, - wei`10 ether`, - "0x" - ), - "ErrorDepositsDisabled()" - ); - }) - - .test("finalizeInboundTransfer() :: wrong token", async (ctx) => { - const { - l2TokensGateway, - accounts: { - stranger, - sender, - recipient, - deployer, - l1TokensGatewayAliasedEOA, - }, - } = ctx; - const wrongTokenAddress = stranger.address; - - // initialize gateway - await l2TokensGateway.initialize(deployer.address); - - // validate gateway was initialized - assert.isTrue(await l2TokensGateway.isInitialized()); - - // grant DEPOSITS_ENABLER_ROLE to the l1Deployer to enable withdrawals - await l2TokensGateway.grantRole( - await l2TokensGateway.DEPOSITS_ENABLER_ROLE(), - deployer.address - ); - - // enable withdrawals - await l2TokensGateway.enableDeposits(); - - // validate withdrawals were enabled - assert.isTrue(await l2TokensGateway.isDepositsEnabled()); - - // validate gateway reverts with error ErrorUnsupportedL1Token() - await assert.revertsWith( - l2TokensGateway - .connect(l1TokensGatewayAliasedEOA) - .finalizeInboundTransfer( - wrongTokenAddress, - sender.address, - recipient.address, - wei`10 ether`, - "0x" - ), - "ErrorUnsupportedL1Token()" - ); - }) - - .test("finalizeInboundTransfer() :: not counterpart gateway", async (ctx) => { - const { - l2TokensGateway, - accounts: { deployer, stranger, sender, recipient }, - stubs: { l1Token }, - } = ctx; - - // initialize gateway - await l2TokensGateway.initialize(deployer.address); - - // validate gateway was initialized - assert.isTrue(await l2TokensGateway.isInitialized()); - - // grant DEPOSITS_ENABLER_ROLE to the l1Deployer to enable withdrawals - await l2TokensGateway.grantRole( - await l2TokensGateway.DEPOSITS_ENABLER_ROLE(), - deployer.address - ); - - // enable withdrawals - await l2TokensGateway.enableDeposits(); - - // validate withdrawals were enabled - assert.isTrue(await l2TokensGateway.isDepositsEnabled()); - - // validate that stranger address is not counterpartGateway - assert.notEqual( - await l2TokensGateway.counterpartGateway(), - stranger.address - ); - - // validate gateway reverts with error ErrorWrongCrossDomainSender() - await assert.revertsWith( - l2TokensGateway - .connect(stranger) - .finalizeInboundTransfer( - l1Token.address, - sender.address, - recipient.address, - wei`10 ether`, - "0x" - ), - "ErrorWrongCrossDomainSender()" - ); - }) - - .test("finalizeInboundTransfer() :: works as expected", async (ctx) => { - const { - l2TokensGateway, - accounts: { sender, recipient, deployer, l1TokensGatewayAliasedEOA }, - stubs: { l1Token, l2Token }, - } = ctx; - - // initialize gateway - await l2TokensGateway.initialize(deployer.address); - - // validate gateway was initialized - assert.isTrue(await l2TokensGateway.isInitialized()); - - // grant WITHDRAWALS_ENABLER_ROLE to the l1Deployer to enable withdrawals - await l2TokensGateway.grantRole( - await l2TokensGateway.DEPOSITS_ENABLER_ROLE(), - deployer.address - ); - - // enable withdrawals - await l2TokensGateway.enableDeposits(); - - // validate withdrawals were enabled - assert.isTrue(await l2TokensGateway.isDepositsEnabled()); - - // transfer l2Tokens to l2TokensGateway - const initialL2GatewayBalance = wei`10_000 ether`; - await l2Token.transfer(l2TokensGateway.address, initialL2GatewayBalance); - - // validate l1TokensGateway has enough amount of tokens - assert.equal( - await l2Token.balanceOf(l2TokensGateway.address).then(wei.fromBigNumber), - initialL2GatewayBalance - ); - - const amount = wei`10 ether`; - - const tx = await l2TokensGateway - .connect(l1TokensGatewayAliasedEOA) - .finalizeInboundTransfer( - l1Token.address, - sender.address, - recipient.address, - wei`10 ether`, - "0x" - ); - - // validate DepositFinalized was emitted - await assert.emits(l2TokensGateway, tx, "DepositFinalized", [ - l1Token.address, - sender.address, - recipient.address, - amount, - ]); - - // validate tokens were minted to recipient - assert.equalBN(await l2Token.balanceOf(recipient.address), amount); - }) - - .run(); - -async function ctxFactory() { - const [deployer, stranger, sender, recipient] = await hre.ethers.getSigners(); - const l2RouterStub = await new EmptyContractStub__factory(deployer).deploy({ - value: wei.toBigNumber(wei`1 ether`), - }); - const l1TokensGatewayStub = await new EmptyContractStub__factory( - deployer - ).deploy({ value: wei.toBigNumber(wei`1 ether`) }); - await hre.network.provider.request({ - method: "hardhat_impersonateAccount", - params: [l1TokensGatewayStub.address], - }); - const l2TokenStub = await new ERC20BridgedStub__factory(deployer).deploy( - "L2Token stub", - "L2ERC20" - ); - const l1TokenStub = await new ERC20BridgedStub__factory(deployer).deploy( - "ERC20 Mock", - "ERC20" - ); - - const arbSysStub = await new ArbSysStub__factory(deployer).deploy(); - const l2TokensGatewayImpl = await new L2ERC20TokenGateway__factory( - deployer - ).deploy( - arbSysStub.address, // the default address of the - l2RouterStub.address, - l1TokensGatewayStub.address, - l1TokenStub.address, - l2TokenStub.address - ); - const l2TokensGatewayProxy = await new OssifiableProxy__factory( - deployer - ).deploy(l2TokensGatewayImpl.address, deployer.address, "0x"); - - const l1TokensGatewayAliasedEOAAddress = testing.accounts.applyL1ToL2Alias( - l1TokensGatewayStub.address - ); - - await hre.network.provider.request({ - method: "hardhat_impersonateAccount", - params: [l1TokensGatewayAliasedEOAAddress], - }); - - const l1TokensGatewayAliasedEOA = await hre.ethers.getSigner( - l1TokensGatewayAliasedEOAAddress - ); - - await testing.setBalance( - l1TokensGatewayAliasedEOA.address, - wei.toBigNumber(wei`1 ether`) - ); - - await hre.network.provider.request({ - method: "hardhat_impersonateAccount", - params: [l2RouterStub.address], - }); - const l2RouterAsEOA = await hre.ethers.getSigner(l2RouterStub.address); - - return { - accounts: { - deployer, - stranger, - sender, - recipient, - l2RouterAsEOA, - l1TokensGatewayEOA: await ethers.getSigner(l1TokensGatewayStub.address), - l1TokensGatewayAliasedEOA, - }, - stubs: { - arbSys: arbSysStub, - l1Token: l1TokenStub, - l2Token: l2TokenStub, - l2Router: l2RouterStub, - l1TokensGateway: L1ERC20TokenGateway__factory.connect( - l1TokensGatewayStub.address, - deployer - ), - }, - l2TokensGateway: L2ERC20TokenGateway__factory.connect( - l2TokensGatewayProxy.address, - deployer - ), - }; -} - -function encodeOutboundTransferData(maxSubmissionCost: string) { - return hre.ethers.utils.defaultAbiCoder.encode( - ["uint256", "bytes"], - [maxSubmissionCost, "0x"] - ); -} diff --git a/test/arbitrum/_launch.test.ts b/test/arbitrum/_launch.test.ts deleted file mode 100644 index e5711360..00000000 --- a/test/arbitrum/_launch.test.ts +++ /dev/null @@ -1,89 +0,0 @@ -import { assert } from "chai"; - -import env from "../../utils/env"; -import arbitrum from "../../utils/arbitrum"; -import { L1ERC20TokenBridge__factory } from "../../typechain"; -import { wei } from "../../utils/wei"; -import testing, { scenario } from "../../utils/testing"; -import { BridgingManagerRole } from "../../utils/bridging-management"; - -const REVERT = env.bool("REVERT", true); - -scenario("Arbitrum :: Launch integration test", ctx) - .after(async (ctx) => { - if (REVERT) { - await ctx.l1Provider.send("evm_revert", [ctx.snapshot.l1]); - await ctx.l2Provider.send("evm_revert", [ctx.snapshot.l2]); - } else { - console.warn( - "Revert is skipped! Forked node restart might be required for repeated launches!" - ); - } - }) - - .step("Enable deposits", async (ctx) => { - const { l1ERC20TokenGateway } = ctx; - assert.isFalse(await l1ERC20TokenGateway.isDepositsEnabled()); - - await l1ERC20TokenGateway.enableDeposits(); - assert.isTrue(await l1ERC20TokenGateway.isDepositsEnabled()); - }) - - .step("Renounce role", async (ctx) => { - const { l1ERC20TokenGateway, l1DevMultisig } = ctx; - assert.isTrue( - await l1ERC20TokenGateway.hasRole( - BridgingManagerRole.DEPOSITS_ENABLER_ROLE.hash, - await l1DevMultisig.getAddress() - ) - ); - - await l1ERC20TokenGateway.renounceRole( - BridgingManagerRole.DEPOSITS_ENABLER_ROLE.hash, - await l1DevMultisig.getAddress() - ); - assert.isFalse( - await l1ERC20TokenGateway.hasRole( - BridgingManagerRole.DEPOSITS_ENABLER_ROLE.hash, - await l1DevMultisig.getAddress() - ) - ); - }) - - .run(); - -async function ctx() { - const networkName = env.network("TESTING_ARB_NETWORK", "mainnet"); - const { l1Provider, l2Provider, l1ERC20TokenGateway } = await arbitrum - .testing(networkName) - .getIntegrationTestSetup(); - - const hasDeployedContracts = testing.env.USE_DEPLOYED_CONTRACTS(false); - const l1DevMultisig = hasDeployedContracts - ? await testing.impersonate(testing.env.L1_DEV_MULTISIG(), l1Provider) - : testing.accounts.deployer(l1Provider); - - const l1Snapshot = await l1Provider.send("evm_snapshot", []); - const l2Snapshot = await l2Provider.send("evm_snapshot", []); - - await testing.setBalance( - await l1DevMultisig.getAddress(), - wei.toBigNumber(wei`1 ether`) - ); - - const l1ERC20TokenGatewayImpl = L1ERC20TokenBridge__factory.connect( - l1ERC20TokenGateway.address, - l1DevMultisig - ); - - return { - l1Provider, - l2Provider, - l1DevMultisig, - l1ERC20TokenGateway: l1ERC20TokenGatewayImpl, - snapshot: { - l1: l1Snapshot, - l2: l2Snapshot, - }, - }; -} diff --git a/test/arbitrum/bridging-native.e2e.test.ts b/test/arbitrum/bridging-native.e2e.test.ts deleted file mode 100644 index 6e56499b..00000000 --- a/test/arbitrum/bridging-native.e2e.test.ts +++ /dev/null @@ -1,205 +0,0 @@ -import { assert } from "chai"; -import { ERC20Mintable } from "../../typechain"; -import env from "../../utils/env"; -import { wei } from "../../utils/wei"; -import { - getL2Network, - L1ToL2MessageStatus, - L1TransactionReceipt, -} from "@arbitrum/sdk"; -import { scenario } from "../../utils/testing"; -import arbitrum from "../../utils/arbitrum"; -import { ethers } from "hardhat"; - -async function ctxFactory() { - const networkName = env.network("TESTING_ARB_NETWORK", "sepolia"); - const testingSetup = await arbitrum.testing(networkName).getE2ETestSetup(); - - const l2Network = await getL2Network(testingSetup.l2Provider); - - // replace gateway router addresses with test - l2Network.tokenBridge.l1GatewayRouter = testingSetup.l1GatewayRouter.address; - l2Network.tokenBridge.l2GatewayRouter = testingSetup.l2GatewayRouter.address; - - return { - ...testingSetup, - messaging: arbitrum.messaging(networkName, { - forking: false, - customAddresses: { - L1GatewayRouter: testingSetup.l1GatewayRouter.address, - L2GatewayRouter: testingSetup.l2GatewayRouter.address, - }, - }), - l2Network, - depositAmount: wei`0.025 ether`, - withdrawalAmount: wei`0.025 ether`, - }; -} - -scenario("Arbitrum :: Bridging E2E test natively", ctxFactory) - .step("Validate tester has required amount of L1 token", async (ctx) => { - const { l1Token, l1Tester, depositAmount } = ctx; - const balanceBefore = await l1Token.balanceOf(l1Tester.address); - if (balanceBefore.lt(depositAmount)) { - try { - await (l1Token as ERC20Mintable).mint(l1Tester.address, depositAmount); - } catch {} - } - const balanceAfter = await l1Token.balanceOf(l1Tester.address); - assert.isTrue( - balanceAfter.gte(depositAmount), - "Tester has not enough L1 token" - ); - }) - - .step("Set allowance for L1ERC20TokenGateway to deposit", async (ctx) => { - const { l1Tester, l1Token, depositAmount, l1ERC20TokenGateway } = ctx; - - const allowanceTxResponse = await l1Token - .connect(l1Tester) - .approve(l1ERC20TokenGateway.address, wei.toBigNumber(depositAmount)); - - await allowanceTxResponse.wait(); - - assert.equalBN( - await l1Token.allowance(l1Tester.address, l1ERC20TokenGateway.address), - depositAmount - ); - }) - - .step("Deposit tokens to L2 via L1ERC20Gateway", async (ctx) => { - const { - l1Tester, - l1Token, - l2Tester, - l2Token, - depositAmount, - l1ERC20TokenGateway, - l2ERC20TokenGateway, - } = ctx; - const l1ERC20TokenGatewayBalanceBefore = await l1Token.balanceOf( - ctx.l1ERC20TokenGateway.address - ); - const testerL1TokenBalanceBefore = await l1Token.balanceOf( - l1Tester.address - ); - const testerL2TokenBalanceBefore = await l2Token.balanceOf( - l2Tester.address - ); - - // To estimate params required for L1 -> L2 retryable ticket creation - // we need to know which message will be send on L2 - const finalizeInboundTransferCalldata = - await l1ERC20TokenGateway.getOutboundCalldata( - l1Token.address, - l1Tester.address, - l1Tester.address, - wei.toBigNumber(depositAmount), - "0x" - ); - - const { callvalue, gasPriceBid, maxGas, maxSubmissionCost } = - await ctx.messaging.getRetryableTicketSendParams({ - callvalue: 0, - sender: l1ERC20TokenGateway.address, - recipient: l2ERC20TokenGateway.address, - calldata: finalizeInboundTransferCalldata, - refundAddress: l2Tester.address, - }); - - const maxSubmissionCostEncoded = ethers.utils.defaultAbiCoder.encode( - ["uint256", "bytes"], - [maxSubmissionCost, "0x"] - ); - - const depositTxResponse = await l1ERC20TokenGateway - .connect(l1Tester) - .outboundTransfer( - l1Token.address, - l1Tester.address, - depositAmount, - maxGas, - gasPriceBid, - maxSubmissionCostEncoded, - { value: callvalue } - ); - - const depositL1Receipt = await depositTxResponse.wait(); - - assert.equalBN( - await l1Token.balanceOf(l1Tester.address), - testerL1TokenBalanceBefore.sub(depositAmount) - ); - - assert.equalBN( - await l1Token.balanceOf(ctx.l1ERC20TokenGateway.address), - l1ERC20TokenGatewayBalanceBefore.add(depositAmount) - ); - - const l1TxReceipt = new L1TransactionReceipt(depositL1Receipt); - - const [message] = await l1TxReceipt.getL1ToL2Messages(l2Tester); - - const { status } = await message.waitForStatus(); - - if (status === L1ToL2MessageStatus.FUNDS_DEPOSITED_ON_L2) { - console.warn( - `Auto redeem for tx ${l1TxReceipt.transactionHash} failed. Redeeming it manually...` - ); - const redeemResponse = await message.redeem({ gasLimit: 300_000 }); - await redeemResponse.wait(); - console.log("Tx was redeemed"); - } else if (status === L1ToL2MessageStatus.REDEEMED) { - console.log("Tx was auto redeemed"); - } else { - assert.isTrue( - false, - `L2 retryable txn failed with status ${L1ToL2MessageStatus[status]}` - ); - } - - assert.equalBN( - await l2Token.balanceOf(l2Tester.address), - testerL2TokenBalanceBefore.add(depositAmount) - ); - }) - - .step("Withdraw tokens from L2 via L2ERC20Gateway", async (ctx) => { - const { - l2Token, - l1Tester, - l2Tester, - l1Token, - withdrawalAmount, - l2ERC20TokenGateway, - } = ctx; - - const testerL2TokenBalanceBefore = await l2Token.balanceOf( - l2Tester.address - ); - - const withdrawTxResponse = await l2ERC20TokenGateway.outboundTransfer( - l1Token.address, - l1Tester.address, - withdrawalAmount, - wei`0`, - wei`0`, - "0x" - ); - - const withdrawRec = await withdrawTxResponse.wait(); - - console.log(`Token withdrawal initiated: ${withdrawRec.transactionHash}`); - - assert.equalBN( - await l2Token.balanceOf(l2Tester.address), - testerL2TokenBalanceBefore.sub(withdrawalAmount) - ); - }) - - .step( - "L2 -> L1 transactions takes much time and must be redeemed manually", - async () => {} - ) - - .run(); diff --git a/test/arbitrum/bridging-router.e2e.test.ts b/test/arbitrum/bridging-router.e2e.test.ts deleted file mode 100644 index fc7fcc76..00000000 --- a/test/arbitrum/bridging-router.e2e.test.ts +++ /dev/null @@ -1,145 +0,0 @@ -import { assert } from "chai"; -import { ERC20Mintable } from "../../typechain"; -import env from "../../utils/env"; -import { wei } from "../../utils/wei"; -import { Erc20Bridger, getL2Network, L1ToL2MessageStatus } from "@arbitrum/sdk"; -import { scenario } from "../../utils/testing"; -import arbitrum from "../../utils/arbitrum"; - -async function ctxFactory() { - const networkName = env.network("TESTING_ARB_NETWORK", "sepolia"); - const testingSetup = await arbitrum.testing(networkName).getE2ETestSetup(); - - const l2Network = await getL2Network(testingSetup.l2Provider); - - // replace gateway router addresses with test - l2Network.tokenBridge.l1GatewayRouter = testingSetup.l1GatewayRouter.address; - l2Network.tokenBridge.l2GatewayRouter = testingSetup.l2GatewayRouter.address; - - return { - ...testingSetup, - l2Network, - erc20Bridge: new Erc20Bridger(l2Network), - depositAmount: wei`0.025 ether`, - withdrawalAmount: wei`0.025 ether`, - }; -} - -scenario("Arbitrum :: Bridging E2E test via router", ctxFactory) - .step( - "Check test environment is set correctly", - async ({ erc20Bridge, l1Token, ...ctx }) => { - assert.equal( - await erc20Bridge.getL1GatewayAddress(l1Token.address, ctx.l1Provider), - ctx.l1ERC20TokenGateway.address - ); - assert.equal( - await erc20Bridge.getL2GatewayAddress(l1Token.address, ctx.l2Provider), - ctx.l2ERC20TokenGateway.address - ); - } - ) - - .step("Validate tester has required amount of L1 token", async (ctx) => { - const { l1Token, l1Tester, depositAmount } = ctx; - const balanceBefore = await l1Token.balanceOf(l1Tester.address); - if (balanceBefore.lt(depositAmount)) { - try { - await (l1Token as ERC20Mintable).mint(l1Tester.address, depositAmount); - } catch {} - } - const balanceAfter = await l1Token.balanceOf(l1Tester.address); - assert.isTrue( - balanceAfter.gte(depositAmount), - "Tester has not enough L1 token" - ); - }) - - .step("Set allowance for L1ERC20TokenGateway to deposit", async (ctx) => { - const { l1Tester, l1Token, depositAmount, l1ERC20TokenGateway } = ctx; - - const allowanceTxResponse = await ctx.erc20Bridge.approveToken({ - l1Signer: l1Tester, - erc20L1Address: l1Token.address, - amount: wei.toBigNumber(depositAmount), - }); - - await allowanceTxResponse.wait(); - - assert.equalBN( - await l1Token.allowance(l1Tester.address, l1ERC20TokenGateway.address), - depositAmount - ); - }) - - .step("Deposit tokens to L2 via L1GatewayRouter", async (ctx) => { - const { l1Tester, l1Token, l2Tester, l2Token, depositAmount } = ctx; - const l1ERC20TokenGatewayBalanceBefore = await l1Token.balanceOf( - ctx.l1ERC20TokenGateway.address - ); - const testerL1TokenBalanceBefore = await l1Token.balanceOf( - l1Tester.address - ); - const testerL2TokenBalanceBefore = await l2Token.balanceOf( - l2Tester.address - ); - - const depositTxResponse = await ctx.erc20Bridge.deposit({ - amount: wei.toBigNumber(depositAmount), - erc20L1Address: l1Token.address, - l1Signer: l1Tester, - l2Provider: ctx.l2Provider, - }); - - const depositL1Receipt = await depositTxResponse.wait(); - - assert.equalBN( - await l1Token.balanceOf(l1Tester.address), - testerL1TokenBalanceBefore.sub(depositAmount) - ); - - assert.equalBN( - await l1Token.balanceOf(ctx.l1ERC20TokenGateway.address), - l1ERC20TokenGatewayBalanceBefore.add(depositAmount) - ); - - const l2Result = await depositL1Receipt.waitForL2(l2Tester.provider); - - assert.isTrue( - l2Result.complete, - `L2 message failed: status ${L1ToL2MessageStatus[l2Result.status]}` - ); - - assert.equalBN( - await l2Token.balanceOf(l2Tester.address), - testerL2TokenBalanceBefore.add(depositAmount) - ); - }) - - .step("Withdraw tokens from L2 via L2GatewayRouter", async (ctx) => { - const { l2Token, l2Tester, l1Token, erc20Bridge, withdrawalAmount } = ctx; - const testerL2TokenBalanceBefore = await l2Token.balanceOf( - l2Tester.address - ); - - const withdrawTxResponse = await erc20Bridge.withdraw({ - l2Signer: l2Tester, - erc20l1Address: l1Token.address, - destinationAddress: l2Tester.address, - amount: wei.toBigNumber(withdrawalAmount), - }); - const withdrawRec = await withdrawTxResponse.wait(); - console.log(`Token withdrawal initiated: ${withdrawRec.transactionHash}`); - - assert.equalBN( - await l2Token.balanceOf(l2Tester.address), - testerL2TokenBalanceBefore.sub(withdrawalAmount) - ); - }) - - .step( - "L2 -> L1 transactions takes much time and must be redeemed manually", - async () => {} - ) - - .run(); diff --git a/test/arbitrum/bridging.integration.test.ts b/test/arbitrum/bridging.integration.test.ts deleted file mode 100644 index 00dab003..00000000 --- a/test/arbitrum/bridging.integration.test.ts +++ /dev/null @@ -1,504 +0,0 @@ -import { assert } from "chai"; -import { ethers } from "hardhat"; - -import env from "../../utils/env"; -import { wei } from "../../utils/wei"; -import arbitrum from "../../utils/arbitrum"; -import arbitrumAddresses from "../../utils/arbitrum/addresses"; -import testing, { scenario } from "../../utils/testing"; -import { - OutboxStub__factory, - BridgeStub__factory, - IMessageProvider__factory, -} from "../../typechain"; - -scenario("Arbitrum :: Bridging integration test", ctx) - .after(async (ctx) => { - await ctx.l1Provider.send("evm_revert", [ctx.snapshot.l1]); - await ctx.l2Provider.send("evm_revert", [ctx.snapshot.l2]); - }) - - .step("Activate Bridging on L1", async (ctx) => { - const { l1ERC20TokenGateway } = ctx; - const { l1BridgeAdmin } = ctx.accounts; - - const isDepositsEnabled = await l1ERC20TokenGateway.isDepositsEnabled(); - - if (!isDepositsEnabled) { - await l1ERC20TokenGateway.connect(l1BridgeAdmin).enableDeposits(); - } else { - console.log("L1 deposits already enabled"); - } - - const isWithdrawalsEnabled = - await l1ERC20TokenGateway.isWithdrawalsEnabled(); - - if (!isWithdrawalsEnabled) { - await l1ERC20TokenGateway.connect(l1BridgeAdmin).enableWithdrawals(); - } else { - console.log("L1 withdrawals already enabled"); - } - - assert.isTrue(await l1ERC20TokenGateway.isDepositsEnabled()); - assert.isTrue(await l1ERC20TokenGateway.isWithdrawalsEnabled()); - }) - - .step("Activate Bridging on L2", async (ctx) => { - const { l2ERC20TokenGateway } = ctx; - const { l2BridgeAdmin } = ctx.accounts; - - const isDepositsEnabled = await l2ERC20TokenGateway.isDepositsEnabled(); - - if (!isDepositsEnabled) { - await l2ERC20TokenGateway.connect(l2BridgeAdmin).enableDeposits(); - } else { - console.log("L2 deposits already enabled"); - } - - const isWithdrawalsEnabled = - await l2ERC20TokenGateway.isWithdrawalsEnabled(); - - if (!isWithdrawalsEnabled) { - await l2ERC20TokenGateway.connect(l2BridgeAdmin).enableWithdrawals(); - } else { - console.log("L2 withdrawals already enabled"); - } - - assert.isTrue(await l2ERC20TokenGateway.isDepositsEnabled()); - assert.isTrue(await l2ERC20TokenGateway.isWithdrawalsEnabled()); - }) - - .step( - "Set L1ERC20TokenGateway for new token in L1GatewayRouter", - async (ctx) => { - const { l1ERC20TokenGateway, l1Token, l1GatewayRouter } = ctx; - const { l1GatewayRouterAdmin } = ctx.accounts; - const { maxGas, gasPriceBid, maxSubmissionCost, callValue } = - ctx.constants; - - await l1GatewayRouter - .connect(l1GatewayRouterAdmin) - .setGateways( - [l1Token.address], - [l1ERC20TokenGateway.address], - maxGas, - gasPriceBid, - maxSubmissionCost, - { value: callValue } - ); - - assert.equal( - await l1GatewayRouter.getGateway(l1Token.address), - l1ERC20TokenGateway.address - ); - } - ) - - .step( - "Set L2ERC20TokenGateway for new token in L2GatewayRouter", - async (ctx) => { - const { l1Token, l2GatewayRouter, l2ERC20TokenGateway } = ctx; - const { l1GatewayRouterAliased } = ctx.accounts; - - await l2GatewayRouter - .connect(l1GatewayRouterAliased) - .setGateway([l1Token.address], [l2ERC20TokenGateway.address]); - - assert.equal( - await l2GatewayRouter.getGateway(l1Token.address), - l2ERC20TokenGateway.address - ); - } - ) - - .step("L1 -> L2 deposit via L1GatewayRouter", async (ctx) => { - const { accountA, accountB } = ctx.accounts; - const { l1Token, l1ERC20TokenGateway } = ctx; - const { - depositAmount, - outbdoundTransferData, - maxGas, - gasPriceBid, - callValue, - } = ctx.constants; - - await l1Token - .connect(accountA.l1Signer) - .approve(l1ERC20TokenGateway.address, depositAmount); - - const accountABalanceBefore = await l1Token.balanceOf(accountA.address); - const l1ERC20TokenGatewayBalanceBefore = await l1Token.balanceOf( - l1ERC20TokenGateway.address - ); - - const tx = await ctx.l1GatewayRouter - .connect(accountA.l1Signer) - .outboundTransfer( - l1Token.address, - accountB.address, - depositAmount, - maxGas, - gasPriceBid, - outbdoundTransferData, - { value: callValue } - ); - - const receipt = await tx.wait(); - - const messageProviderInterface = - IMessageProvider__factory.createInterface(); - const inboxMessageDeliveredTopic = messageProviderInterface.getEventTopic( - "InboxMessageDelivered" - ); - const messageDeliveredLog = receipt.logs.find( - (l) => l.topics[0] === inboxMessageDeliveredTopic - ); - if (!messageDeliveredLog) { - throw new Error("InboxMessageDelivered message wasn't fired"); - } - const messageDeliveredEvent = - messageProviderInterface.parseLog(messageDeliveredLog); - - // Validate that message data were passed correctly. - // Inbox contract uses the abi.encodePackedValue(), so it's an overhead - // to parse all data of the event when we only need the last one - assert.isTrue( - messageDeliveredEvent.args.data.endsWith( - ctx.constants.finalizeInboundTransferCalldata.deposit.slice(2) - ) - ); - - assert.equalBN( - await l1Token.balanceOf(accountA.address), - accountABalanceBefore.sub(depositAmount) - ); - - assert.equalBN( - await l1Token.balanceOf(l1ERC20TokenGateway.address), - l1ERC20TokenGatewayBalanceBefore.add(depositAmount) - ); - }) - - .step("Finalize deposit on L2", async (ctx) => { - const { depositAmount } = ctx.constants; - const { l1Token, l2Token, l2ERC20TokenGateway } = ctx; - const { accountA, accountB, l1ERC20TokenGatewayAliased } = ctx.accounts; - - const l2TokenSupplyBefore = await l2Token.totalSupply(); - const accountBBalanceBefore = await l2Token.balanceOf(accountB.address); - - const tx = await l1ERC20TokenGatewayAliased.sendTransaction({ - to: l2ERC20TokenGateway.address, - data: ctx.constants.finalizeInboundTransferCalldata.deposit, - }); - - await assert.emits(l2Token, tx, "Transfer", [ - ethers.constants.AddressZero, - accountB.address, - depositAmount, - ]); - - await assert.emits(l2ERC20TokenGateway, tx, "DepositFinalized", [ - l1Token.address, - accountA.address, - accountB.address, - depositAmount, - ]); - - assert.equalBN( - await l2Token.totalSupply(), - l2TokenSupplyBefore.add(depositAmount) - ); - assert.equalBN( - await l2Token.balanceOf(accountB.address), - accountBBalanceBefore.add(depositAmount) - ); - }) - - .step("L2 -> L1 withdrawal via L2GatewayRouter", async (ctx) => { - const { accountA, accountB } = ctx.accounts; - const { l1Token, arbSys, l2ERC20TokenGateway, l2Token } = ctx; - const { withdrawalAmount } = ctx.constants; - - const l2TokenSupplyBefore = await l2Token.totalSupply(); - const accountBBalanceBefore = await l2Token.balanceOf(accountB.address); - - const prevL2ToL1TxId = await arbSys.l2ToL1TxId(); - const tx = await ctx.l2GatewayRouter - .connect(accountB.l2Signer) - ["outboundTransfer(address,address,uint256,bytes)"]( - l1Token.address, - accountA.address, - withdrawalAmount, - "0x" - ); - - await assert.emits(ctx.l2Token, tx, "Transfer", [ - accountB.address, - ethers.constants.AddressZero, - withdrawalAmount, - ]); - - await assert.emits(arbSys, tx, "CreateL2ToL1Tx", [ - ctx.l1ERC20TokenGateway.address, - ctx.constants.finalizeInboundTransferCalldata.withdraw, - ]); - - await assert.emits(l2ERC20TokenGateway, tx, "WithdrawalInitiated", [ - l1Token.address, - accountB.address, - accountA.address, - prevL2ToL1TxId.add(1), - 0, - withdrawalAmount, - ]); - - assert.equalBN( - await l2Token.totalSupply(), - l2TokenSupplyBefore.sub(withdrawalAmount) - ); - assert.equalBN( - await l2Token.balanceOf(accountB.address), - accountBBalanceBefore.sub(withdrawalAmount) - ); - }) - - .step("Finalize withdrawal on L1", async (ctx) => { - const { accountA, accountB } = ctx.accounts; - const { withdrawalAmount } = ctx.constants; - const { - l1OutboxStub, - l1Provider, - l1Token, - l1ERC20TokenGateway, - l1Bridge, - l1BridgeStub, - } = ctx; - - const accountABalanceBefore = await l1Token.balanceOf(accountA.address); - const l1ERC20TokenGatewayBalanceBefore = await l1Token.balanceOf( - l1ERC20TokenGateway.address - ); - - const [bridgeCodeBefore, bridgeStubCode] = await Promise.all([ - l1Provider.send("eth_getCode", [l1Bridge.address]), - l1Provider.send("eth_getCode", [l1BridgeStub.address]), - ]); - - await l1Provider.send("hardhat_setCode", [ - l1Bridge.address, - bridgeStubCode, - ]); - const bridgeCodeAfter = await l1Provider.send("eth_getCode", [ - l1Bridge.address, - ]); - - const l1BridgeEOA = await testing.impersonate(l1Bridge.address, l1Provider); - - await l1Bridge.setOutbox(l1OutboxStub.address); - - assert.equal(bridgeStubCode, bridgeCodeAfter); - assert.notEqual(bridgeCodeBefore, bridgeCodeAfter); - - const tx = await l1BridgeEOA.sendTransaction({ - to: l1ERC20TokenGateway.address, - data: ctx.constants.finalizeInboundTransferCalldata.withdraw, - }); - - await tx.wait(); - - await assert.emits(l1ERC20TokenGateway, tx, "WithdrawalFinalized", [ - l1Token.address, - accountB.address, - accountA.address, - 0, - withdrawalAmount, - ]); - - assert.equalBN( - accountABalanceBefore.add(withdrawalAmount), - await l1Token.balanceOf(accountA.address) - ); - - assert.equalBN( - l1ERC20TokenGatewayBalanceBefore.sub(withdrawalAmount), - await l1Token.balanceOf(l1ERC20TokenGateway.address) - ); - }) - - .run(); - -async function ctx() { - const networkName = env.network("TESTING_ARB_NETWORK", "mainnet"); - const { - l1Provider, - l2Provider, - l1ERC20TokenGatewayAdmin, - l2ERC20TokenGatewayAdmin, - ...contracts - } = await arbitrum.testing(networkName).getIntegrationTestSetup(); - - const l1Snapshot = await l1Provider.send("evm_snapshot", []); - const l2Snapshot = await l2Provider.send("evm_snapshot", []); - - // by default arbSys contract doesn't exist on the hardhat fork - // so we have to deploy there a stub contract - await arbitrum.testing(networkName).stubArbSysContract(); - - const accountA = testing.accounts.accountA(l1Provider, l2Provider); - const accountB = testing.accounts.accountB(l1Provider, l2Provider); - - const l1TokensHolderAddress = await contracts.l1TokensHolder.getAddress(); - - await testing.setBalance( - l1TokensHolderAddress, - wei.toBigNumber(wei`1 ether`), - l1Provider - ); - - const depositAmount = wei`0.15 ether`; - const withdrawalAmount = wei`0.05 ether`; - - await contracts.l1Token - .connect(contracts.l1TokensHolder) - .transfer(accountA.address, depositAmount); - - const l1ERC20TokenGatewayAliased = await testing.impersonate( - testing.accounts.applyL1ToL2Alias(contracts.l1ERC20TokenGateway.address), - l2Provider - ); - - const l1GatewayRouterAliased = await testing.impersonate( - testing.accounts.applyL1ToL2Alias(contracts.l1GatewayRouter.address), - l2Provider - ); - - await testing.setBalance( - await contracts.l1TokensHolder.getAddress(), - wei.toBigNumber(wei`1 ether`), - l1Provider - ); - - await testing.setBalance( - await l1ERC20TokenGatewayAdmin.getAddress(), - wei.toBigNumber(wei`1 ether`), - l1Provider - ); - - await testing.setBalance( - await l2ERC20TokenGatewayAdmin.getAddress(), - wei.toBigNumber(wei`1 ether`), - l2Provider - ); - - // send ether to l1GatewayRouterAliased to run transactions from it - // as from EOA - await testing.setBalance( - await l1GatewayRouterAliased.getAddress(), - wei.toBigNumber(wei`1 ether`), - l2Provider - ); - - // send ether to l1ERC20TokenGatewayAliased to run transactions from it - // as from EOA - - await testing.setBalance( - await l1ERC20TokenGatewayAliased.getAddress(), - wei.toBigNumber(wei`1 ether`), - l2Provider - ); - - const maxSubmissionCost = wei`200_000 gwei`; - - const l1GatewayRouterAdminAddress = await contracts.l1GatewayRouter.owner(); - - const l1GatewayRouterAdmin = await testing.impersonate( - l1GatewayRouterAdminAddress, - l1Provider - ); - - await testing.setBalance( - l1GatewayRouterAdminAddress, - wei.toBigNumber(wei`1 ether`), - l1Provider - ); - - const l1OutboxStub = await new OutboxStub__factory( - accountA.l1Signer - ).deploy(); - - await l1OutboxStub.setL2ToL1Sender(contracts.l2ERC20TokenGateway.address); - - const l1BridgeStub = await new BridgeStub__factory(accountA.l1Signer).deploy( - l1OutboxStub.address - ); - - const { Bridge: l1BridgeAddress } = arbitrumAddresses(networkName); - const l1Bridge = BridgeStub__factory.connect( - l1BridgeAddress, - accountA.l1Signer - ); - - return { - l1Provider, - l2Provider, - l1Bridge, - l1BridgeStub, - l1OutboxStub, - l1Token: contracts.l1Token, - l2Token: contracts.l2Token, - l2GatewayRouter: contracts.l2GatewayRouter, - l2ERC20TokenGateway: contracts.l2ERC20TokenGateway, - arbSys: contracts.arbSysStub, - l1GatewayRouter: contracts.l1GatewayRouter, - l1ERC20TokenGateway: contracts.l1ERC20TokenGateway, - accounts: { - accountA, - accountB, - l1BridgeAdmin: l1ERC20TokenGatewayAdmin, - l1GatewayRouterAdmin, - l2BridgeAdmin: l2ERC20TokenGatewayAdmin, - l1GatewayRouterAliased, - l1ERC20TokenGatewayAliased, - }, - constants: { - depositAmount, - withdrawalAmount, - maxGas: wei`300_000`, - gasPriceBid: wei`1 gwei`, - callValue: wei`500_000 gwei`, - maxSubmissionCost, - // data for outboundTransfer must contain encoded tuple with (maxSubmissionCost, emptyData) - outbdoundTransferData: ethers.utils.defaultAbiCoder.encode( - ["uint256", "bytes"], - [maxSubmissionCost, "0x"] - ), - finalizeInboundTransferCalldata: { - deposit: contracts.l2ERC20TokenGateway.interface.encodeFunctionData( - "finalizeInboundTransfer", - [ - contracts.l1Token.address, - accountA.address, - accountB.address, - depositAmount, - "0x", - ] - ), - withdraw: contracts.l2ERC20TokenGateway.interface.encodeFunctionData( - "finalizeInboundTransfer", - [ - contracts.l1Token.address, - accountB.address, - accountA.address, - withdrawalAmount, - "0x", - ] - ), - }, - }, - snapshot: { - l1: l1Snapshot, - l2: l2Snapshot, - }, - }; -} diff --git a/test/arbitrum/deployment.acceptance.test.ts b/test/arbitrum/deployment.acceptance.test.ts deleted file mode 100644 index 6ce0c0e0..00000000 --- a/test/arbitrum/deployment.acceptance.test.ts +++ /dev/null @@ -1,313 +0,0 @@ -import { assert } from "chai"; -import { - IERC20Metadata__factory, - OssifiableProxy__factory, -} from "../../typechain"; -import arbitrum from "../../utils/arbitrum"; -import { BridgingManagerRole } from "../../utils/bridging-management"; -import deployment from "../../utils/deployment"; -import env from "../../utils/env"; -import { getRoleHolders, scenario } from "../../utils/testing"; -import { wei } from "../../utils/wei"; - -scenario("Arbitrum Gateway :: deployment acceptance test", ctxFactory) - .step("L1 Bridge :: proxy admin", async (ctx) => { - assert.equal( - await ctx.l1ERC20TokenGatewayProxy.proxy__getAdmin(), - ctx.deployment.l1.proxyAdmin - ); - }) - - .step("L1 Bridge :: bridge admin", async (ctx) => { - const currentAdmins = await getRoleHolders( - ctx.l1ERC20TokenGateway, - BridgingManagerRole.DEFAULT_ADMIN_ROLE.hash - ); - assert.equal(currentAdmins.size, 1); - assert.isTrue(currentAdmins.has(ctx.deployment.l1.bridgeAdmin)); - - assert.isTrue( - await ctx.l1ERC20TokenGateway.hasRole( - BridgingManagerRole.DEFAULT_ADMIN_ROLE.hash, - ctx.deployment.l1.bridgeAdmin - ) - ); - }) - - .step("L1 bridge :: router", async (ctx) => { - assert.equal(await ctx.l1ERC20TokenGateway.router(), ctx.l1Router.address); - }) - - .step("L1 bridge :: L1 token", async (ctx) => { - assert.equal(await ctx.l1ERC20TokenGateway.l1Token(), ctx.deployment.token); - }) - - .step("L1 bridge :: L2 token", async (ctx) => { - assert.equal( - await ctx.l1ERC20TokenGateway.l2Token(), - ctx.erc20Bridged.address - ); - }) - .step("L1 bridge :: counterpart gateway", async (ctx) => { - assert.equal( - await ctx.l1ERC20TokenGateway.counterpartGateway(), - ctx.l2ERC20TokenGateway.address - ); - }) - .step("L1 Bridge :: is deposits enabled", async (ctx) => { - assert.equal( - await ctx.l1ERC20TokenGateway.isDepositsEnabled(), - ctx.deployment.l1.depositsEnabled - ); - }) - .step("L1 Bridge :: is withdrawals enabled", async (ctx) => { - assert.equal( - await ctx.l1ERC20TokenGateway.isWithdrawalsEnabled(), - ctx.deployment.l1.withdrawalsEnabled - ); - }) - .step("L1 Bridge :: deposits enablers", async (ctx) => { - const actualDepositsEnablers = await getRoleHolders( - ctx.l1ERC20TokenGateway, - BridgingManagerRole.DEPOSITS_ENABLER_ROLE.hash - ); - const expectedDepositsEnablers = ctx.deployment.l1.depositsEnablers || []; - - assert.equal(actualDepositsEnablers.size, expectedDepositsEnablers.length); - for (const expectedDepositsEnabler of expectedDepositsEnablers) { - assert.isTrue(actualDepositsEnablers.has(expectedDepositsEnabler)); - } - }) - .step("L1 Bridge :: deposits disablers", async (ctx) => { - const actualDepositsDisablers = await getRoleHolders( - ctx.l1ERC20TokenGateway, - BridgingManagerRole.DEPOSITS_DISABLER_ROLE.hash - ); - const expectedDepositsDisablers = ctx.deployment.l1.depositsDisablers || []; - - assert.equal( - actualDepositsDisablers.size, - expectedDepositsDisablers.length - ); - for (const expectedDepositsDisabler of expectedDepositsDisablers) { - assert.isTrue(actualDepositsDisablers.has(expectedDepositsDisabler)); - } - }) - .step("L1 Bridge :: withdrawals enablers", async (ctx) => { - const actualWithdrawalsEnablers = await getRoleHolders( - ctx.l1ERC20TokenGateway, - BridgingManagerRole.WITHDRAWALS_ENABLER_ROLE.hash - ); - const expectedWithdrawalsEnablers = - ctx.deployment.l1.withdrawalsEnablers || []; - - assert.equal( - actualWithdrawalsEnablers.size, - expectedWithdrawalsEnablers.length - ); - for (const expectedWithdrawalsEnabler of expectedWithdrawalsEnablers) { - assert.isTrue(actualWithdrawalsEnablers.has(expectedWithdrawalsEnabler)); - } - }) - .step("L1 Bridge :: withdrawals disablers", async (ctx) => { - const actualWithdrawalsDisablers = await getRoleHolders( - ctx.l1ERC20TokenGateway, - BridgingManagerRole.WITHDRAWALS_DISABLER_ROLE.hash - ); - const expectedWithdrawalsDisablers = - ctx.deployment.l1.withdrawalsDisablers || []; - - assert.equal( - actualWithdrawalsDisablers.size, - expectedWithdrawalsDisablers.length - ); - for (const expectedWithdrawalsDisabler of expectedWithdrawalsDisablers) { - assert.isTrue( - actualWithdrawalsDisablers.has(expectedWithdrawalsDisabler) - ); - } - }) - - .step("L2 Bridge :: proxy admin", async (ctx) => { - assert.equal( - await ctx.l2ERC20TokenGatewayProxy.proxy__getAdmin(), - ctx.deployment.l2.proxyAdmin - ); - }) - .step("L2 Bridge :: bridge admin", async (ctx) => { - const currentAdmins = await getRoleHolders( - ctx.l2ERC20TokenGateway, - BridgingManagerRole.DEFAULT_ADMIN_ROLE.hash - ); - assert.equal(currentAdmins.size, 1); - assert.isTrue(currentAdmins.has(ctx.deployment.l2.bridgeAdmin)); - - await assert.isTrue( - await ctx.l2ERC20TokenGateway.hasRole( - BridgingManagerRole.DEFAULT_ADMIN_ROLE.hash, - ctx.deployment.l2.bridgeAdmin - ) - ); - }) - .step("L2 bridge :: router", async (ctx) => { - assert.equal(await ctx.l2ERC20TokenGateway.router(), ctx.l2Router.address); - }) - .step("L2 bridge :: L1 token", async (ctx) => { - assert.equal(await ctx.l2ERC20TokenGateway.l1Token(), ctx.deployment.token); - }) - .step("L2 bridge :: L2 token", async (ctx) => { - assert.equal( - await ctx.l2ERC20TokenGateway.l2Token(), - ctx.erc20Bridged.address - ); - }) - .step("L2 bridge :: counterpart gateway", async (ctx) => { - assert.equal( - await ctx.l2ERC20TokenGateway.counterpartGateway(), - ctx.l1ERC20TokenGateway.address - ); - }) - .step("L2 Bridge :: is deposits enabled", async (ctx) => { - assert.equal( - await ctx.l2ERC20TokenGateway.isDepositsEnabled(), - ctx.deployment.l2.depositsEnabled - ); - }) - .step("L2 Bridge :: is withdrawals enabled", async (ctx) => { - assert.equal( - await ctx.l2ERC20TokenGateway.isWithdrawalsEnabled(), - ctx.deployment.l2.withdrawalsEnabled - ); - }) - .step("L2 Bridge :: deposits enablers", async (ctx) => { - const actualDepositsEnablers = await getRoleHolders( - ctx.l2ERC20TokenGateway, - BridgingManagerRole.DEPOSITS_ENABLER_ROLE.hash - ); - const expectedDepositsEnablers = ctx.deployment.l2.depositsEnablers || []; - - assert.equal(actualDepositsEnablers.size, expectedDepositsEnablers.length); - for (const expectedDepositsEnabler of expectedDepositsEnablers) { - assert.isTrue(actualDepositsEnablers.has(expectedDepositsEnabler)); - } - }) - .step("L2 Bridge :: deposits disablers", async (ctx) => { - const actualDepositsDisablers = await getRoleHolders( - ctx.l2ERC20TokenGateway, - BridgingManagerRole.DEPOSITS_DISABLER_ROLE.hash - ); - const expectedDepositsDisablers = ctx.deployment.l2.depositsDisablers || []; - - assert.equal( - actualDepositsDisablers.size, - expectedDepositsDisablers.length - ); - for (const expectedDepositsDisabler of expectedDepositsDisablers) { - assert.isTrue(actualDepositsDisablers.has(expectedDepositsDisabler)); - } - }) - .step("L2 Bridge :: withdrawals enablers", async (ctx) => { - const actualWithdrawalsEnablers = await getRoleHolders( - ctx.l2ERC20TokenGateway, - BridgingManagerRole.WITHDRAWALS_ENABLER_ROLE.hash - ); - const expectedWithdrawalsEnablers = - ctx.deployment.l2.withdrawalsEnablers || []; - - assert.equal( - actualWithdrawalsEnablers.size, - expectedWithdrawalsEnablers.length - ); - for (const expectedWithdrawalsEnabler of expectedWithdrawalsEnablers) { - assert.isTrue(actualWithdrawalsEnablers.has(expectedWithdrawalsEnabler)); - } - }) - .step("L2 Bridge :: withdrawals disablers", async (ctx) => { - const actualWithdrawalsDisablers = await getRoleHolders( - ctx.l2ERC20TokenGateway, - BridgingManagerRole.WITHDRAWALS_DISABLER_ROLE.hash - ); - const expectedWithdrawalsDisablers = - ctx.deployment.l2.withdrawalsDisablers || []; - - assert.equal( - actualWithdrawalsDisablers.size, - expectedWithdrawalsDisablers.length - ); - for (const expectedWithdrawalsDisabler of expectedWithdrawalsDisablers) { - assert.isTrue( - actualWithdrawalsDisablers.has(expectedWithdrawalsDisabler) - ); - } - }) - - .step("L2 Token :: proxy admin", async (ctx) => { - assert.equal( - await ctx.erc20BridgedProxy.proxy__getAdmin(), - ctx.deployment.l2.proxyAdmin - ); - }) - .step("L2 Token :: name", async (ctx) => { - assert.equal(await ctx.erc20Bridged.name(), ctx.l2TokenInfo.name); - }) - .step("L2 Token :: symbol", async (ctx) => { - assert.equal(await ctx.erc20Bridged.symbol(), ctx.l2TokenInfo.symbol); - }) - .step("L2 Token :: decimals", async (ctx) => { - assert.equal(await ctx.erc20Bridged.decimals(), ctx.l2TokenInfo.decimals); - }) - .step("L2 Token :: total supply", async (ctx) => { - assert.equalBN(await ctx.erc20Bridged.totalSupply(), wei`0`); - }) - .step("L2 token :: bridge", async (ctx) => { - assert.equalBN( - await ctx.erc20Bridged.bridge(), - ctx.l2ERC20TokenGateway.address - ); - }) - - .run(); - -async function ctxFactory() { - const networkName = env.network(); - const deploymentConfig = deployment.loadMultiChainDeploymentConfig(); - const testingSetup = await arbitrum - .testing(networkName) - .getAcceptanceTestSetup(); - - const l1TokenMeta = IERC20Metadata__factory.connect( - deploymentConfig.token, - testingSetup.l1Provider - ); - const [name, symbol, decimals] = await Promise.all([ - l1TokenMeta.name(), - l1TokenMeta.symbol(), - l1TokenMeta.decimals(), - ]); - - return { - deployment: deploymentConfig, - l1Router: testingSetup.l1GatewayRouter, - l2Router: testingSetup.l2GatewayRouter, - l2TokenInfo: { - name, - symbol, - decimals, - }, - l1ERC20TokenGateway: testingSetup.l1ERC20TokenGateway, - l1ERC20TokenGatewayProxy: OssifiableProxy__factory.connect( - testingSetup.l1ERC20TokenGateway.address, - testingSetup.l1Provider - ), - l2ERC20TokenGateway: testingSetup.l2ERC20TokenGateway, - l2ERC20TokenGatewayProxy: OssifiableProxy__factory.connect( - testingSetup.l2ERC20TokenGateway.address, - testingSetup.l2Provider - ), - erc20Bridged: testingSetup.l2Token, - erc20BridgedProxy: OssifiableProxy__factory.connect( - testingSetup.l2Token.address, - testingSetup.l2Provider - ), - }; -} diff --git a/test/arbitrum/managing-deposits.e2e.test.ts b/test/arbitrum/managing-deposits.e2e.test.ts deleted file mode 100644 index 1154cbca..00000000 --- a/test/arbitrum/managing-deposits.e2e.test.ts +++ /dev/null @@ -1,194 +0,0 @@ -import { L1ToL2MessageStatus } from "@arbitrum/sdk"; -import { assert } from "chai"; -import { ContractReceipt } from "ethers"; - -import { - L2ERC20TokenGateway__factory, - GovBridgeExecutor__factory, -} from "../../typechain"; -import { - E2E_TEST_CONTRACTS_ARBITRUM as E2E_TEST_CONTRACTS, - sleep, -} from "../../utils/testing/e2e"; -import { wei } from "../../utils/wei"; -import network from "../../utils/network"; -import env from "../../utils/env"; -import { scenario } from "../../utils/testing"; -import arbitrum from "../../utils/arbitrum"; -import lido from "../../utils/lido"; - -const DEPOSIT_ENABLER_ROLE = - "0x4b43b36766bde12c5e9cbbc37d15f8d1f769f08f54720ab370faeb4ce893753a"; -const DEPOSIT_DISABLER_ROLE = - "0x63f736f21cb2943826cd50b191eb054ebbea670e4e962d0527611f830cd399d6"; - -let l2DepositsInitialState: boolean; -let ticketTx: ContractReceipt; - -const scenarioTest = scenario( - "Arbitrum :: AAVE governance crosschain bridge: token bridge management", - ctxFactory -) - .step("LDO Holder has enought ETH", async ({ l1LDOHolder, gasAmount }) => { - assert.gte(await l1LDOHolder.getBalance(), gasAmount); - }) - - .step("L2 Tester has enought ETH", async ({ l2Tester, gasAmount }) => { - assert.gte(await l2Tester.getBalance(), gasAmount); - }) - - .step( - "L2 Agent has enought ETH", - async ({ l1Provider, lidoAragonDAO, gasAmount }) => { - assert.gte( - await l1Provider.getBalance(lidoAragonDAO.agent.address), - gasAmount - ); - } - ) - - .step("Checking deposits status", async ({ l2ERC20TokenGateway }) => { - l2DepositsInitialState = await l2ERC20TokenGateway.isDepositsEnabled(); - }) - - .step(`Starting DAO vote`, async (ctx) => { - const grantRoleCalldata = - ctx.l2ERC20TokenGateway.interface.encodeFunctionData("grantRole", [ - l2DepositsInitialState ? DEPOSIT_DISABLER_ROLE : DEPOSIT_ENABLER_ROLE, - ctx.govBridgeExecutor.address, - ]); - const grantRoleData = "0x" + grantRoleCalldata.substring(10); - - const actionCalldata = l2DepositsInitialState - ? ctx.l2ERC20TokenGateway.interface.encodeFunctionData("disableDeposits") - : ctx.l2ERC20TokenGateway.interface.encodeFunctionData("enableDeposits"); - - const actionData = "0x" + actionCalldata.substring(10); - - const executorCalldata = - await ctx.govBridgeExecutor.interface.encodeFunctionData("queue", [ - [ctx.l2ERC20TokenGateway.address, ctx.l2ERC20TokenGateway.address], - [0, 0], - [ - "grantRole(bytes32,address)", - l2DepositsInitialState ? "disableDeposits()" : "enableDeposits()", - ], - [grantRoleData, actionData], - [false, false], - ]); - - const arbAddresses = arbitrum.addresses("sepolia"); - - const { calldata, callvalue } = - await ctx.messaging.prepareRetryableTicketTx({ - sender: ctx.lidoAragonDAO.agent.address, - recipient: ctx.govBridgeExecutor.address, - calldata: executorCalldata, - refundAddress: ctx.l2Tester.address, - }); - - const tx = await ctx.lidoAragonDAO.createVote( - ctx.l1LDOHolder, - "E2E Test Voting", - { - address: ctx.lidoAragonDAO.agent.address, - signature: "execute(address,uint256,bytes)", - decodedCallData: [arbAddresses.Inbox, callvalue, calldata], - } - ); - - await tx.wait(); - }) - - .step("Enacting Vote", async ({ l1LDOHolder, lidoAragonDAO }) => { - const votesLength = await lidoAragonDAO.voting.votesLength(); - - const tx = await lidoAragonDAO.voteAndExecute( - l1LDOHolder, - votesLength.toNumber() - 1 - ); - - ticketTx = await tx.wait(); - }) - - .step("Waiting for L2 tx", async ({ messaging }) => { - const { status } = await messaging.waitForL2Message( - ticketTx.transactionHash - ); - - assert.equal( - status, - L1ToL2MessageStatus.REDEEMED, - `L2 retryable txn failed with status ${L1ToL2MessageStatus[status]}` - ); - }) - - .step("Execute queued task", async ({ govBridgeExecutor, l2Tester }) => { - const tasksCount = await govBridgeExecutor.getActionsSetCount(); - - const targetTask = tasksCount.toNumber() - 1; - - const executionTime = ( - await govBridgeExecutor.getActionsSetById(targetTask) - ).executionTime.toNumber(); - let chainTime; - - do { - await sleep(5000); - const currentBlockNumber = await l2Tester.provider.getBlockNumber(); - const currentBlock = await l2Tester.provider.getBlock(currentBlockNumber); - chainTime = currentBlock.timestamp; - } while (chainTime <= executionTime); - - const tx = await govBridgeExecutor.execute(targetTask, { - gasLimit: 1000000, - }); - await tx.wait(); - }) - - .step("Checking deposits state", async ({ l2ERC20TokenGateway }) => { - assert.equal( - await l2ERC20TokenGateway.isDepositsEnabled(), - !l2DepositsInitialState - ); - }); - -// make first run to change state from enabled/disabled -> disabled/enabled -scenarioTest.run(); - -// make another run to return the state to the initial and test vice versa actions -scenarioTest.run(); - -async function ctxFactory() { - const ethArbNetwork = network.multichain(["eth", "arb"], "sepolia"); - - const [l1Provider] = ethArbNetwork.getProviders({ - forking: false, - }); - const [, l2Tester] = ethArbNetwork.getSigners( - env.string("TESTING_PRIVATE_KEY"), - { forking: false } - ); - - const [l1LDOHolder] = ethArbNetwork.getSigners( - env.string("TESTING_ARB_LDO_HOLDER_PRIVATE_KEY"), - { forking: false } - ); - - return { - lidoAragonDAO: lido("sepolia", l1Provider), - messaging: arbitrum.messaging("sepolia", { forking: false }), - gasAmount: wei`0.1 ether`, - l2Tester, - l1Provider, - l1LDOHolder, - l2ERC20TokenGateway: L2ERC20TokenGateway__factory.connect( - E2E_TEST_CONTRACTS.l2.l2ERC20TokenGateway, - l2Tester - ), - govBridgeExecutor: GovBridgeExecutor__factory.connect( - E2E_TEST_CONTRACTS.l2.govBridgeExecutor, - l2Tester - ), - }; -} diff --git a/test/arbitrum/managing-executor.e2e.test.ts b/test/arbitrum/managing-executor.e2e.test.ts deleted file mode 100644 index 0dc6a15b..00000000 --- a/test/arbitrum/managing-executor.e2e.test.ts +++ /dev/null @@ -1,164 +0,0 @@ -import { assert } from "chai"; -import { ContractReceipt } from "ethers"; -import { L1ToL2MessageStatus } from "@arbitrum/sdk"; - -import { GovBridgeExecutor__factory } from "../../typechain"; -import { - E2E_TEST_CONTRACTS_ARBITRUM as E2E_TEST_CONTRACTS, - sleep, -} from "../../utils/testing/e2e"; -import env from "../../utils/env"; -import network from "../../utils/network"; -import { wei } from "../../utils/wei"; -import { scenario } from "../../utils/testing"; -import arbitrum from "../../utils/arbitrum"; -import lido from "../../utils/lido"; - -let oldGuardian: string; -let newGuardian: string; -let ticketTx: ContractReceipt; - -scenario("Arbitrum :: Update guardian", ctxFactory) - .step("LDO Holder has enought ETH", async ({ l1LDOHolder, gasAmount }) => { - assert.gte(await l1LDOHolder.getBalance(), gasAmount); - }) - - .step("L2 Tester has enought ETH", async ({ l2Tester, gasAmount }) => { - assert.gte(await l2Tester.getBalance(), gasAmount); - }) - - .step( - "L2 Agent has enought ETH", - async ({ l1Provider, lidoAragonDAO, gasAmount }) => { - assert.gte( - await l1Provider.getBalance(lidoAragonDAO.agent.address), - gasAmount - ); - } - ) - - .step(`Starting DAO vote: Update guardian`, async (ctx) => { - oldGuardian = await ctx.govBridgeExecutor.getGuardian(); - newGuardian = - oldGuardian === "0x4e8CC9024Ea3FE886623025fF2aD0CA4bb3D1F42" - ? "0xD06491e4C8B3107B83dC134894C4c96ED8ddbfa2" - : "0x4e8CC9024Ea3FE886623025fF2aD0CA4bb3D1F42"; - - const updateGuardianCalldata = - ctx.govBridgeExecutor.interface.encodeFunctionData("updateGuardian", [ - newGuardian, - ]); - const updateGuardianData = "0x" + updateGuardianCalldata.substring(10); - - const executorCalldata = - await ctx.govBridgeExecutor.interface.encodeFunctionData("queue", [ - [ctx.govBridgeExecutor.address], - [0], - ["updateGuardian(address)"], - [updateGuardianData], - [false], - ]); - - const arbAddresses = arbitrum.addresses("sepolia"); - - const { calldata, callvalue } = - await ctx.messaging.prepareRetryableTicketTx({ - sender: ctx.lidoAragonDAO.agent.address, - recipient: ctx.govBridgeExecutor.address, - calldata: executorCalldata, - refundAddress: ctx.l2Tester.address, - }); - - const tx = await ctx.lidoAragonDAO.createVote( - ctx.l1LDOHolder, - "E2E Test Voting", - { - address: ctx.lidoAragonDAO.agent.address, - signature: "execute(address,uint256,bytes)", - decodedCallData: [arbAddresses.Inbox, callvalue, calldata], - } - ); - - await tx.wait(); - }) - - .step("Enacting Vote", async ({ l1LDOHolder, lidoAragonDAO }) => { - const votesLength = await lidoAragonDAO.voting.votesLength(); - - const tx = await lidoAragonDAO.voteAndExecute( - l1LDOHolder, - votesLength.toNumber() - 1 - ); - - ticketTx = await tx.wait(); - }) - - .step("Waiting for L2 tx", async ({ messaging }) => { - const { status } = await messaging.waitForL2Message( - ticketTx.transactionHash - ); - - assert.equal( - status, - L1ToL2MessageStatus.REDEEMED, - `L2 retryable txn failed with status ${L1ToL2MessageStatus[status]}` - ); - }) - - .step("Execute queued task", async ({ govBridgeExecutor, l2Tester }) => { - const tasksCount = await govBridgeExecutor.getActionsSetCount(); - - const targetTask = tasksCount.toNumber() - 1; - - const executionTime = ( - await govBridgeExecutor.getActionsSetById(targetTask) - ).executionTime.toNumber(); - let chainTime; - - do { - await sleep(5000); - const currentBlockNumber = await l2Tester.provider.getBlockNumber(); - const currentBlock = await l2Tester.provider.getBlock(currentBlockNumber); - chainTime = currentBlock.timestamp; - } while (chainTime <= executionTime); - - const tx = await govBridgeExecutor.execute(targetTask, { - gasLimit: 1000000, - }); - await tx.wait(); - }) - - .step("Checking guardian", async ({ govBridgeExecutor }) => { - assert.equal(await govBridgeExecutor.getGuardian(), newGuardian); - }) - .run(); - -async function ctxFactory() { - const ethArbNetwork = network.multichain(["eth", "arb"], "sepolia"); - - const [l1Provider] = ethArbNetwork.getProviders({ - forking: false, - }); - const [, l2Tester] = ethArbNetwork.getSigners( - env.string("TESTING_PRIVATE_KEY"), - { forking: false } - ); - - const [l1LDOHolder] = ethArbNetwork.getSigners( - env.string("TESTING_ARB_LDO_HOLDER_PRIVATE_KEY"), - { forking: false } - ); - - return { - lidoAragonDAO: lido("sepolia", l1Provider), - messaging: arbitrum.messaging("sepolia", { forking: false }), - gasAmount: wei`0.1 ether`, - l2Tester, - l1Provider, - l1LDOHolder, - govBridgeExecutor: GovBridgeExecutor__factory.connect( - E2E_TEST_CONTRACTS.l2.govBridgeExecutor, - l2Tester - ), - }; -} diff --git a/test/arbitrum/managing-proxy.e2e.test.ts b/test/arbitrum/managing-proxy.e2e.test.ts deleted file mode 100644 index e8342cfa..00000000 --- a/test/arbitrum/managing-proxy.e2e.test.ts +++ /dev/null @@ -1,284 +0,0 @@ -import { L1ToL2MessageStatus, L1TransactionReceipt } from "@arbitrum/sdk"; -import { assert } from "chai"; -import { ContractReceipt } from "ethers"; - -import { - ERC20Bridged__factory, - L2ERC20TokenGateway__factory, - GovBridgeExecutor__factory, - OssifiableProxy__factory, -} from "../../typechain"; -import { - E2E_TEST_CONTRACTS_ARBITRUM as E2E_TEST_CONTRACTS, - sleep, -} from "../../utils/testing/e2e"; -import env from "../../utils/env"; -import { wei } from "../../utils/wei"; -import network from "../../utils/network"; -import { scenario } from "../../utils/testing"; -import arbitrum from "../../utils/arbitrum"; -import lido from "../../utils/lido"; - -let upgradeMessageResponse: ContractReceipt; -let ossifyMessageResponse: ContractReceipt; - -scenario("Arbitrum :: AAVE governance crosschain bridge", ctxFactory) - .step("LDO Holder has enought ETH", async ({ l1LDOHolder, gasAmount }) => { - assert.gte(await l1LDOHolder.getBalance(), gasAmount); - }) - - .step("L2 Tester has enought ETH", async ({ l2Tester, gasAmount }) => { - assert.gte(await l2Tester.getBalance(), gasAmount); - }) - - .step( - "L2 Agent has enought ETH", - async ({ l1Provider, lidoAragonDAO, gasAmount }) => { - assert.gte( - await l1Provider.getBalance(lidoAragonDAO.agent.address), - gasAmount - ); - } - ) - .step("Check OssifiableProxy deployed correct", async (ctx) => { - const { proxyToOssify } = ctx; - const admin = await proxyToOssify.proxy__getAdmin(); - - assert.equal(admin, E2E_TEST_CONTRACTS.l2.govBridgeExecutor); - }) - - .step("Proxy upgrade: send crosschain message", async (ctx) => { - const implBefore = await await ctx.proxyToOssify.proxy__getImplementation(); - - assert.equal(implBefore, ctx.l2ERC20TokenGateway.address); - - const executorCalldata = - await ctx.govBridgeExecutor.interface.encodeFunctionData("queue", [ - [ctx.proxyToOssify.address], - [0], - ["proxy__upgradeTo(address)"], - [ - "0x" + - ctx.proxyToOssify.interface - .encodeFunctionData("proxy__upgradeTo", [ctx.l2Token.address]) - .substring(10), - ], - [false], - ]); - - const arbAddresses = arbitrum.addresses("sepolia"); - - const { calldata, callvalue } = - await ctx.messaging.prepareRetryableTicketTx({ - sender: ctx.lidoAragonDAO.agent.address, - recipient: ctx.govBridgeExecutor.address, - calldata: executorCalldata, - refundAddress: ctx.l2Tester.address, - }); - - const tx = await ctx.lidoAragonDAO.createVote( - ctx.l1LDOHolder, - "E2E Test Voting", - { - address: ctx.lidoAragonDAO.agent.address, - signature: "execute(address,uint256,bytes)", - decodedCallData: [arbAddresses.Inbox, callvalue, calldata], - } - ); - - await tx.wait(); - }) - - .step( - "Proxy upgrade: Enacting Voting", - async ({ l1LDOHolder, lidoAragonDAO }) => { - const votesLength = await lidoAragonDAO.voting.votesLength(); - - const tx = await lidoAragonDAO.voteAndExecute( - l1LDOHolder, - votesLength.toNumber() - 1 - ); - - upgradeMessageResponse = await tx.wait(); - } - ) - - .step("Proxy upgrade: Waiting for L2 tx", async ({ messaging, l2Tester }) => { - const { status } = await messaging.waitForL2Message( - upgradeMessageResponse.transactionHash - ); - - if (status === L1ToL2MessageStatus.FUNDS_DEPOSITED_ON_L2) { - console.warn( - `Auto redeem for tx ${upgradeMessageResponse.transactionHash} failed. Redeeming it manually...` - ); - const l1TxReceipt = new L1TransactionReceipt(upgradeMessageResponse); - const [message] = await l1TxReceipt.getL1ToL2Messages(l2Tester); - const redeemResponse = await message.redeem({ gasLimit: 300_000 }); - await redeemResponse.wait(); - console.log("Tx was redeemed"); - } else if (status === L1ToL2MessageStatus.REDEEMED) { - console.log("Tx was auto redeemed"); - } else { - assert.isTrue( - false, - `L2 retryable txn failed with status ${L1ToL2MessageStatus[status]}` - ); - } - }) - - .step( - "Proxy upgrade: Execute queued task", - async ({ govBridgeExecutor, l2Tester }) => { - const tasksCount = await govBridgeExecutor.getActionsSetCount(); - - const targetTask = tasksCount.toNumber() - 1; - - const executionTime = ( - await govBridgeExecutor.getActionsSetById(targetTask) - ).executionTime.toNumber(); - let chainTime; - - do { - await sleep(5000); - const currentBlockNumber = await l2Tester.provider.getBlockNumber(); - const currentBlock = await l2Tester.provider.getBlock( - currentBlockNumber - ); - chainTime = currentBlock.timestamp; - } while (chainTime <= executionTime); - - const tx = await govBridgeExecutor.execute(targetTask, { - gasLimit: 1000000, - }); - await tx.wait(); - } - ) - - .step("Proxy upgrade: check state", async ({ proxyToOssify, l2Token }) => { - const implAfter = await await proxyToOssify.proxy__getImplementation(); - assert.equal(implAfter, l2Token.address); - }) - - .step("Proxy ossify: send crosschain message", async (ctx) => { - const isOssifiedBefore = await ctx.proxyToOssify.proxy__getIsOssified(); - assert.isFalse(isOssifiedBefore); - - const executorCalldata = - await ctx.govBridgeExecutor.interface.encodeFunctionData("queue", [ - [ctx.proxyToOssify.address], - [0], - ["proxy__ossify()"], - ["0x00"], - [false], - ]); - - const arbAddresses = arbitrum.addresses("sepolia"); - - const { calldata, callvalue } = - await ctx.messaging.prepareRetryableTicketTx({ - sender: ctx.lidoAragonDAO.agent.address, - recipient: ctx.govBridgeExecutor.address, - calldata: executorCalldata, - refundAddress: ctx.l2Tester.address, - }); - - const tx = await ctx.lidoAragonDAO.createVote( - ctx.l1LDOHolder, - "E2E Test Voting", - { - address: ctx.lidoAragonDAO.agent.address, - signature: "execute(address,uint256,bytes)", - decodedCallData: [arbAddresses.Inbox, callvalue, calldata], - } - ); - - await tx.wait(); - }) - - .step( - "Proxy ossify: Enacting Voting", - async ({ lidoAragonDAO, l1LDOHolder }) => { - const votesLength = await lidoAragonDAO.voting.votesLength(); - - const tx = await lidoAragonDAO.voteAndExecute( - l1LDOHolder, - votesLength.toNumber() - 1 - ); - - ossifyMessageResponse = await tx.wait(); - } - ) - - .step("Proxy ossify: Waiting for L2 tx", async ({ messaging }) => { - const { status } = await messaging.waitForL2Message( - ossifyMessageResponse.transactionHash - ); - - assert.equal( - status, - L1ToL2MessageStatus.REDEEMED, - `L2 retryable txn failed with status ${L1ToL2MessageStatus[status]}` - ); - }) - - .step("Proxy ossify: execute", async ({ govBridgeExecutor }) => { - const taskId = - (await govBridgeExecutor.getActionsSetCount()).toNumber() - 1; - const executeTx = await govBridgeExecutor.execute(taskId, { - gasLimit: 2000000, - }); - await executeTx.wait(); - }) - - .step("Proxy upgrade: check state", async ({ proxyToOssify }) => { - const isOssifiedAfter = await proxyToOssify.proxy__getIsOssified(); - - assert.isTrue(isOssifiedAfter); - }) - - .run(); - -async function ctxFactory() { - const ethArbNetwork = network.multichain(["eth", "arb"], "sepolia"); - - const [l1Provider] = ethArbNetwork.getProviders({ - forking: false, - }); - const [, l2Tester] = ethArbNetwork.getSigners( - env.string("TESTING_PRIVATE_KEY"), - { forking: false } - ); - - const [l1LDOHolder] = ethArbNetwork.getSigners( - env.string("TESTING_ARB_LDO_HOLDER_PRIVATE_KEY"), - { forking: false } - ); - - return { - lidoAragonDAO: lido("sepolia", l1Provider), - messaging: arbitrum.messaging("sepolia", { forking: false }), - gasAmount: wei`0.1 ether`, - l2Tester, - l1LDOHolder, - l1Provider, - - l2Token: ERC20Bridged__factory.connect( - E2E_TEST_CONTRACTS.l2.l2Token, - l2Tester - ), - l2ERC20TokenGateway: L2ERC20TokenGateway__factory.connect( - E2E_TEST_CONTRACTS.l2.l2ERC20TokenGateway, - l2Tester - ), - govBridgeExecutor: GovBridgeExecutor__factory.connect( - E2E_TEST_CONTRACTS.l2.govBridgeExecutor, - l2Tester - ), - proxyToOssify: await new OssifiableProxy__factory(l2Tester).deploy( - E2E_TEST_CONTRACTS.l2.l2ERC20TokenGateway, - E2E_TEST_CONTRACTS.l2.govBridgeExecutor, - "0x" - ), - }; -} diff --git a/test/bridge-executor/arbitrum.integration.test.ts b/test/bridge-executor/arbitrum.integration.test.ts deleted file mode 100644 index 3bfcce5a..00000000 --- a/test/bridge-executor/arbitrum.integration.test.ts +++ /dev/null @@ -1,273 +0,0 @@ -import { assert } from "chai"; -import testing, { scenario } from "../../utils/testing"; -import { - ERC20BridgedStub__factory, - L2ERC20TokenGateway__factory, - ArbitrumBridgeExecutor__factory, - ERC20Bridged__factory, - OssifiableProxy__factory, -} from "../../typechain"; -import { wei } from "../../utils/wei"; -import { getBridgeExecutorParams } from "../../utils/bridge-executor"; -import { BridgingManagerRole } from "../../utils/bridging-management"; - -import arbitrum from "../../utils/arbitrum"; -import network from "../../utils/network"; -import env from "../../utils/env"; - -scenario("Arbitrum :: Bridge Executor integration test", ctx) - .before(async (ctx) => { - ctx.snapshot.l2 = await ctx.l2.provider.send("evm_snapshot", []); - }) - - .after(async (ctx) => { - await ctx.l2.provider.send("evm_revert", [ctx.snapshot.l2]); - }) - - .step("Activate Bridging", async (ctx) => { - const { - l2: { bridgeExecutor, l2ERC20TokenGateway }, - } = ctx; - - assert.isFalse( - await l2ERC20TokenGateway.hasRole( - BridgingManagerRole.DEPOSITS_ENABLER_ROLE.hash, - bridgeExecutor.address - ) - ); - assert.isFalse( - await l2ERC20TokenGateway.hasRole( - BridgingManagerRole.WITHDRAWALS_ENABLER_ROLE.hash, - bridgeExecutor.address - ) - ); - assert.isFalse(await l2ERC20TokenGateway.isDepositsEnabled()); - assert.isFalse(await l2ERC20TokenGateway.isWithdrawalsEnabled()); - - const actionsSetCountBefore = await bridgeExecutor.getActionsSetCount(); - await bridgeExecutor.queue( - new Array(4).fill(l2ERC20TokenGateway.address), - new Array(4).fill(0), - [ - "grantRole(bytes32,address)", - "grantRole(bytes32,address)", - "enableDeposits()", - "enableWithdrawals()", - ], - [ - "0x" + - l2ERC20TokenGateway.interface - .encodeFunctionData("grantRole", [ - BridgingManagerRole.DEPOSITS_ENABLER_ROLE.hash, - bridgeExecutor.address, - ]) - .substring(10), - "0x" + - l2ERC20TokenGateway.interface - .encodeFunctionData("grantRole", [ - BridgingManagerRole.WITHDRAWALS_ENABLER_ROLE.hash, - bridgeExecutor.address, - ]) - .substring(10), - "0x" + - l2ERC20TokenGateway.interface - .encodeFunctionData("enableDeposits") - .substring(10), - "0x" + - l2ERC20TokenGateway.interface - .encodeFunctionData("enableWithdrawals") - .substring(10), - ], - new Array(4).fill(false) - ); - - const actionsSetCountAfter = await bridgeExecutor.getActionsSetCount(); - - assert.equalBN(actionsSetCountBefore.add(1), actionsSetCountAfter); - // execute the last added actions set - await bridgeExecutor.execute(actionsSetCountAfter.sub(1), { value: 0 }); - - assert.isTrue( - await l2ERC20TokenGateway.hasRole( - BridgingManagerRole.DEPOSITS_ENABLER_ROLE.hash, - bridgeExecutor.address - ) - ); - assert.isTrue( - await l2ERC20TokenGateway.hasRole( - BridgingManagerRole.WITHDRAWALS_ENABLER_ROLE.hash, - bridgeExecutor.address - ) - ); - assert.isTrue(await l2ERC20TokenGateway.isDepositsEnabled()); - assert.isTrue(await l2ERC20TokenGateway.isWithdrawalsEnabled()); - }) - - .step("Change Proxy implementation", async (ctx) => { - const { - l2: { l2Token, bridgeExecutor, l2ERC20TokenGatewayProxy }, - } = ctx; - - const actionsSetCountBefore = await bridgeExecutor.getActionsSetCount(); - - const proxyImplBefore = - await l2ERC20TokenGatewayProxy.proxy__getImplementation(); - - await bridgeExecutor.queue( - [l2ERC20TokenGatewayProxy.address], - [0], - ["proxy__upgradeTo(address)"], - [ - "0x" + - l2ERC20TokenGatewayProxy.interface - .encodeFunctionData("proxy__upgradeTo", [l2Token.address]) - .substring(10), - ], - [false] - ); - - const actionSetCount = await bridgeExecutor.getActionsSetCount(); - - assert.equalBN(actionsSetCountBefore.add(1), actionSetCount); - - await bridgeExecutor.execute(actionsSetCountBefore, { value: 0 }); - const proxyImplAfter = - await l2ERC20TokenGatewayProxy.proxy__getImplementation(); - - assert.notEqual(proxyImplBefore, proxyImplAfter); - assert.equal(proxyImplAfter, l2Token.address); - }) - - .step("Change proxy Admin", async (ctx) => { - const { - l2: { - l2ERC20TokenGatewayProxy, - bridgeExecutor, - accounts: { deployer }, - }, - } = ctx; - const proxyAdminBefore = await l2ERC20TokenGatewayProxy.proxy__getAdmin(); - - const actionsSetCountBefore = await bridgeExecutor.getActionsSetCount(); - - await bridgeExecutor.queue( - [l2ERC20TokenGatewayProxy.address], - [0], - ["proxy__changeAdmin(address)"], - [ - "0x" + - l2ERC20TokenGatewayProxy.interface - .encodeFunctionData("proxy__changeAdmin", [deployer.address]) - .substring(10), - ], - [false] - ); - - const actionSetCount = await bridgeExecutor.getActionsSetCount(); - assert.equalBN(actionsSetCountBefore.add(1), actionSetCount); - - await bridgeExecutor.execute(actionsSetCountBefore, { value: 0 }); - const proxyAdminAfter = await l2ERC20TokenGatewayProxy.proxy__getAdmin(); - - assert.notEqual(proxyAdminBefore, proxyAdminAfter); - assert.equal(proxyAdminAfter, deployer.address); - }) - - .run(); - -async function ctx() { - const networkName = env.network("TESTING_ARB_NETWORK", "mainnet"); - const [l1Provider, l2Provider] = network - .multichain(["eth", "arb"], networkName) - .getProviders({ forking: true }); - - const testingOnDeployedContracts = testing.env.USE_DEPLOYED_CONTRACTS(false); - - const l1Deployer = testing.accounts.deployer(l1Provider); - const l2Deployer = testing.accounts.deployer(l2Provider); - - await arbitrum.testing(networkName).stubArbSysContract(); - - const l1Token = await new ERC20BridgedStub__factory(l1Deployer).deploy( - "Test Token", - "TT" - ); - const govBridgeExecutor = testingOnDeployedContracts - ? ArbitrumBridgeExecutor__factory.connect( - testing.env.ARB_GOV_BRIDGE_EXECUTOR(), - l2Provider - ) - : await new ArbitrumBridgeExecutor__factory(l2Deployer).deploy( - l1Deployer.address, - ...getBridgeExecutorParams(), - l2Deployer.address - ); - - const l1EthGovExecutorAddress = - await govBridgeExecutor.getEthereumGovernanceExecutor(); - - const [, l2DeployScript] = await arbitrum - .deployment(networkName) - .erc20TokenGatewayDeployScript( - l1Token.address, - { - deployer: l1Deployer, - admins: { proxy: l1Deployer.address, bridge: l1Deployer.address }, - }, - { - deployer: l2Deployer, - admins: { - proxy: govBridgeExecutor.address, - bridge: govBridgeExecutor.address, - }, - } - ); - - await l2DeployScript.run(); - - const l2Token = ERC20Bridged__factory.connect( - l2DeployScript.getContractAddress(1), - l2Deployer - ); - const l2ERC20TokenGateway = L2ERC20TokenGateway__factory.connect( - l2DeployScript.getContractAddress(3), - l2Deployer - ); - const l2ERC20TokenGatewayProxy = OssifiableProxy__factory.connect( - l2DeployScript.getContractAddress(3), - l2Deployer - ); - const l1ExecutorAliased = await testing.impersonate( - testing.accounts.applyL1ToL2Alias(l1EthGovExecutorAddress), - l2Provider - ); - - await testing.setBalance( - await l1ExecutorAliased.getAddress(), - wei.toBigNumber(wei`1 ether`), - l2Provider - ); - - if (testingOnDeployedContracts) { - console.log("Testing on deployed contracts"); - console.log(` Network name: ${networkName}`); - console.log(` Gov Bridge Executor Address: ${govBridgeExecutor.address}`); - } - - return { - l2: { - l2Token, - bridgeExecutor: govBridgeExecutor.connect(l1ExecutorAliased), - l2ERC20TokenGateway, - l2ERC20TokenGatewayProxy, - accounts: { - deployer: l2Deployer, - }, - provider: l2Provider, - }, - snapshot: { - l1: "", - l2: "", - }, - }; -} diff --git a/utils/arbitrum/addresses.ts b/utils/arbitrum/addresses.ts deleted file mode 100644 index d06b22ed..00000000 --- a/utils/arbitrum/addresses.ts +++ /dev/null @@ -1,34 +0,0 @@ -import { NetworkName } from "../network"; -import { ArbContractAddresses, CommonOptions } from "./types"; - -const ArbitrumMainnetAddresses: ArbContractAddresses = { - Inbox: "0x4Dbd4fc535Ac27206064B68FfCf827b0A60BAB3f", - ArbSys: "0x0000000000000000000000000000000000000064", - Bridge: "0x8315177ab297ba92a06054ce80a67ed4dbd7ed3a", - Outbox: "0x760723CD2e632826c38Fef8CD438A4CC7E7E1A40", - L1GatewayRouter: "0x72Ce9c846789fdB6fC1f34aC4AD25Dd9ef7031ef", - L2GatewayRouter: "0x5288c571Fd7aD117beA99bF60FE0846C4E84F933", -}; - -const ArbitrumGoerliAddresses: ArbContractAddresses = { - Inbox: "0x6BEbC4925716945D46F0Ec336D5C2564F419682C", - ArbSys: "0x0000000000000000000000000000000000000064", - Bridge: "0xaf4159A80B6Cc41ED517DB1c453d1Ef5C2e4dB72", - Outbox: "0x45Af9Ed1D03703e480CE7d328fB684bb67DA5049", - L1GatewayRouter: "0x4c7708168395aEa569453Fc36862D2ffcDaC588c", - L2GatewayRouter: "0xE5B9d8d42d656d1DcB8065A6c012FE3780246041", -}; - -export default function addresses( - networkName: NetworkName, - options: CommonOptions = {} -) { - switch (networkName) { - case "mainnet": - return { ...ArbitrumMainnetAddresses, ...options.customAddresses }; - case "sepolia": - return { ...ArbitrumGoerliAddresses, ...options.customAddresses }; - default: - throw new Error(`Network "${networkName}" is not supported`); - } -} diff --git a/utils/arbitrum/artifacts/ArbitrumBridgeExecutor.json b/utils/arbitrum/artifacts/ArbitrumBridgeExecutor.json deleted file mode 100644 index 73fa1fbb..00000000 --- a/utils/arbitrum/artifacts/ArbitrumBridgeExecutor.json +++ /dev/null @@ -1,696 +0,0 @@ -{ - "_format": "hh-sol-artifact-1", - "contractName": "ArbitrumBridgeExecutor", - "sourceName": "contracts/bridges/ArbitrumBridgeExecutor.sol", - "abi": [ - { - "inputs": [ - { - "internalType": "address", - "name": "ethereumGovernanceExecutor", - "type": "address" - }, - { - "internalType": "uint256", - "name": "delay", - "type": "uint256" - }, - { - "internalType": "uint256", - "name": "gracePeriod", - "type": "uint256" - }, - { - "internalType": "uint256", - "name": "minimumDelay", - "type": "uint256" - }, - { - "internalType": "uint256", - "name": "maximumDelay", - "type": "uint256" - }, - { - "internalType": "address", - "name": "guardian", - "type": "address" - } - ], - "stateMutability": "nonpayable", - "type": "constructor" - }, - { - "inputs": [], - "name": "DelayLongerThanMax", - "type": "error" - }, - { - "inputs": [], - "name": "DelayShorterThanMin", - "type": "error" - }, - { - "inputs": [], - "name": "DuplicateAction", - "type": "error" - }, - { - "inputs": [], - "name": "EmptyTargets", - "type": "error" - }, - { - "inputs": [], - "name": "FailedActionExecution", - "type": "error" - }, - { - "inputs": [], - "name": "GracePeriodTooShort", - "type": "error" - }, - { - "inputs": [], - "name": "InconsistentParamsLength", - "type": "error" - }, - { - "inputs": [], - "name": "InsufficientBalance", - "type": "error" - }, - { - "inputs": [], - "name": "InvalidActionsSetId", - "type": "error" - }, - { - "inputs": [], - "name": "InvalidInitParams", - "type": "error" - }, - { - "inputs": [], - "name": "MaximumDelayTooShort", - "type": "error" - }, - { - "inputs": [], - "name": "MinimumDelayTooLong", - "type": "error" - }, - { - "inputs": [], - "name": "NotGuardian", - "type": "error" - }, - { - "inputs": [], - "name": "OnlyCallableByThis", - "type": "error" - }, - { - "inputs": [], - "name": "OnlyQueuedActions", - "type": "error" - }, - { - "inputs": [], - "name": "TimelockNotFinished", - "type": "error" - }, - { - "inputs": [], - "name": "UnauthorizedEthereumExecutor", - "type": "error" - }, - { - "anonymous": false, - "inputs": [ - { - "indexed": true, - "internalType": "uint256", - "name": "id", - "type": "uint256" - } - ], - "name": "ActionsSetCanceled", - "type": "event" - }, - { - "anonymous": false, - "inputs": [ - { - "indexed": true, - "internalType": "uint256", - "name": "id", - "type": "uint256" - }, - { - "indexed": true, - "internalType": "address", - "name": "initiatorExecution", - "type": "address" - }, - { - "indexed": false, - "internalType": "bytes[]", - "name": "returnedData", - "type": "bytes[]" - } - ], - "name": "ActionsSetExecuted", - "type": "event" - }, - { - "anonymous": false, - "inputs": [ - { - "indexed": true, - "internalType": "uint256", - "name": "id", - "type": "uint256" - }, - { - "indexed": false, - "internalType": "address[]", - "name": "targets", - "type": "address[]" - }, - { - "indexed": false, - "internalType": "uint256[]", - "name": "values", - "type": "uint256[]" - }, - { - "indexed": false, - "internalType": "string[]", - "name": "signatures", - "type": "string[]" - }, - { - "indexed": false, - "internalType": "bytes[]", - "name": "calldatas", - "type": "bytes[]" - }, - { - "indexed": false, - "internalType": "bool[]", - "name": "withDelegatecalls", - "type": "bool[]" - }, - { - "indexed": false, - "internalType": "uint256", - "name": "executionTime", - "type": "uint256" - } - ], - "name": "ActionsSetQueued", - "type": "event" - }, - { - "anonymous": false, - "inputs": [ - { - "indexed": false, - "internalType": "uint256", - "name": "oldDelay", - "type": "uint256" - }, - { - "indexed": false, - "internalType": "uint256", - "name": "newDelay", - "type": "uint256" - } - ], - "name": "DelayUpdate", - "type": "event" - }, - { - "anonymous": false, - "inputs": [ - { - "indexed": false, - "internalType": "address", - "name": "oldEthereumGovernanceExecutor", - "type": "address" - }, - { - "indexed": false, - "internalType": "address", - "name": "newEthereumGovernanceExecutor", - "type": "address" - } - ], - "name": "EthereumGovernanceExecutorUpdate", - "type": "event" - }, - { - "anonymous": false, - "inputs": [ - { - "indexed": false, - "internalType": "uint256", - "name": "oldGracePeriod", - "type": "uint256" - }, - { - "indexed": false, - "internalType": "uint256", - "name": "newGracePeriod", - "type": "uint256" - } - ], - "name": "GracePeriodUpdate", - "type": "event" - }, - { - "anonymous": false, - "inputs": [ - { - "indexed": false, - "internalType": "address", - "name": "oldGuardian", - "type": "address" - }, - { - "indexed": false, - "internalType": "address", - "name": "newGuardian", - "type": "address" - } - ], - "name": "GuardianUpdate", - "type": "event" - }, - { - "anonymous": false, - "inputs": [ - { - "indexed": false, - "internalType": "uint256", - "name": "oldMaximumDelay", - "type": "uint256" - }, - { - "indexed": false, - "internalType": "uint256", - "name": "newMaximumDelay", - "type": "uint256" - } - ], - "name": "MaximumDelayUpdate", - "type": "event" - }, - { - "anonymous": false, - "inputs": [ - { - "indexed": false, - "internalType": "uint256", - "name": "oldMinimumDelay", - "type": "uint256" - }, - { - "indexed": false, - "internalType": "uint256", - "name": "newMinimumDelay", - "type": "uint256" - } - ], - "name": "MinimumDelayUpdate", - "type": "event" - }, - { - "inputs": [ - { - "internalType": "uint256", - "name": "actionsSetId", - "type": "uint256" - } - ], - "name": "cancel", - "outputs": [], - "stateMutability": "nonpayable", - "type": "function" - }, - { - "inputs": [ - { - "internalType": "uint256", - "name": "actionsSetId", - "type": "uint256" - } - ], - "name": "execute", - "outputs": [], - "stateMutability": "payable", - "type": "function" - }, - { - "inputs": [ - { - "internalType": "address", - "name": "target", - "type": "address" - }, - { - "internalType": "bytes", - "name": "data", - "type": "bytes" - } - ], - "name": "executeDelegateCall", - "outputs": [ - { - "internalType": "bool", - "name": "", - "type": "bool" - }, - { - "internalType": "bytes", - "name": "", - "type": "bytes" - } - ], - "stateMutability": "payable", - "type": "function" - }, - { - "inputs": [ - { - "internalType": "uint256", - "name": "actionsSetId", - "type": "uint256" - } - ], - "name": "getActionsSetById", - "outputs": [ - { - "components": [ - { - "internalType": "address[]", - "name": "targets", - "type": "address[]" - }, - { - "internalType": "uint256[]", - "name": "values", - "type": "uint256[]" - }, - { - "internalType": "string[]", - "name": "signatures", - "type": "string[]" - }, - { - "internalType": "bytes[]", - "name": "calldatas", - "type": "bytes[]" - }, - { - "internalType": "bool[]", - "name": "withDelegatecalls", - "type": "bool[]" - }, - { - "internalType": "uint256", - "name": "executionTime", - "type": "uint256" - }, - { - "internalType": "bool", - "name": "executed", - "type": "bool" - }, - { - "internalType": "bool", - "name": "canceled", - "type": "bool" - } - ], - "internalType": "struct IExecutorBase.ActionsSet", - "name": "", - "type": "tuple" - } - ], - "stateMutability": "view", - "type": "function" - }, - { - "inputs": [], - "name": "getActionsSetCount", - "outputs": [ - { - "internalType": "uint256", - "name": "", - "type": "uint256" - } - ], - "stateMutability": "view", - "type": "function" - }, - { - "inputs": [ - { - "internalType": "uint256", - "name": "actionsSetId", - "type": "uint256" - } - ], - "name": "getCurrentState", - "outputs": [ - { - "internalType": "enum IExecutorBase.ActionsSetState", - "name": "", - "type": "uint8" - } - ], - "stateMutability": "view", - "type": "function" - }, - { - "inputs": [], - "name": "getDelay", - "outputs": [ - { - "internalType": "uint256", - "name": "", - "type": "uint256" - } - ], - "stateMutability": "view", - "type": "function" - }, - { - "inputs": [], - "name": "getEthereumGovernanceExecutor", - "outputs": [ - { - "internalType": "address", - "name": "", - "type": "address" - } - ], - "stateMutability": "view", - "type": "function" - }, - { - "inputs": [], - "name": "getGracePeriod", - "outputs": [ - { - "internalType": "uint256", - "name": "", - "type": "uint256" - } - ], - "stateMutability": "view", - "type": "function" - }, - { - "inputs": [], - "name": "getGuardian", - "outputs": [ - { - "internalType": "address", - "name": "", - "type": "address" - } - ], - "stateMutability": "view", - "type": "function" - }, - { - "inputs": [], - "name": "getMaximumDelay", - "outputs": [ - { - "internalType": "uint256", - "name": "", - "type": "uint256" - } - ], - "stateMutability": "view", - "type": "function" - }, - { - "inputs": [], - "name": "getMinimumDelay", - "outputs": [ - { - "internalType": "uint256", - "name": "", - "type": "uint256" - } - ], - "stateMutability": "view", - "type": "function" - }, - { - "inputs": [ - { - "internalType": "bytes32", - "name": "actionHash", - "type": "bytes32" - } - ], - "name": "isActionQueued", - "outputs": [ - { - "internalType": "bool", - "name": "", - "type": "bool" - } - ], - "stateMutability": "view", - "type": "function" - }, - { - "inputs": [ - { - "internalType": "address[]", - "name": "targets", - "type": "address[]" - }, - { - "internalType": "uint256[]", - "name": "values", - "type": "uint256[]" - }, - { - "internalType": "string[]", - "name": "signatures", - "type": "string[]" - }, - { - "internalType": "bytes[]", - "name": "calldatas", - "type": "bytes[]" - }, - { - "internalType": "bool[]", - "name": "withDelegatecalls", - "type": "bool[]" - } - ], - "name": "queue", - "outputs": [], - "stateMutability": "nonpayable", - "type": "function" - }, - { - "inputs": [], - "name": "receiveFunds", - "outputs": [], - "stateMutability": "payable", - "type": "function" - }, - { - "inputs": [ - { - "internalType": "uint256", - "name": "delay", - "type": "uint256" - } - ], - "name": "updateDelay", - "outputs": [], - "stateMutability": "nonpayable", - "type": "function" - }, - { - "inputs": [ - { - "internalType": "address", - "name": "ethereumGovernanceExecutor", - "type": "address" - } - ], - "name": "updateEthereumGovernanceExecutor", - "outputs": [], - "stateMutability": "nonpayable", - "type": "function" - }, - { - "inputs": [ - { - "internalType": "uint256", - "name": "gracePeriod", - "type": "uint256" - } - ], - "name": "updateGracePeriod", - "outputs": [], - "stateMutability": "nonpayable", - "type": "function" - }, - { - "inputs": [ - { - "internalType": "address", - "name": "guardian", - "type": "address" - } - ], - "name": "updateGuardian", - "outputs": [], - "stateMutability": "nonpayable", - "type": "function" - }, - { - "inputs": [ - { - "internalType": "uint256", - "name": "maximumDelay", - "type": "uint256" - } - ], - "name": "updateMaximumDelay", - "outputs": [], - "stateMutability": "nonpayable", - "type": "function" - }, - { - "inputs": [ - { - "internalType": "uint256", - "name": "minimumDelay", - "type": "uint256" - } - ], - "name": "updateMinimumDelay", - "outputs": [], - "stateMutability": "nonpayable", - "type": "function" - } - ], - "bytecode": "0x60806040523480156200001157600080fd5b50604051620026bb380380620026bb83398101604081905262000034916200027e565b8585858585858484848484610258841080620000505750818310155b806200005b57508285105b806200006657508185115b156200008557604051630a5addd160e21b815260040160405180910390fd5b6200009085620000f4565b6200009b8462000135565b620000a68362000176565b620000b182620001b7565b620000bc81620001f8565b5050600880546001600160a01b0319166001600160a01b039a909a169990991790985550620002db9c50505050505050505050505050565b60005460408051918252602082018390527f43de56b886294fc29767e51a88b5c67fd24aefebc5ddf813b1d9b91b1df38444910160405180910390a1600055565b60015460408051918252602082018390527f9953f3a71052edcd4ae7a0f97302839d1dda32ab93c1039207c91c866b094f72910160405180910390a1600155565b60025460408051918252602082018390527fc534cdcbe9b52100810d787afd57e4174322776fcb58872ea706f23e9319fa8d910160405180910390a1600255565b60035460408051918252602082018390527faf46013422363beb5e6f00ab923cffe3574670494c864de2828b9d7c201fdde5910160405180910390a1600355565b600454604080516001600160a01b03928316815291831660208301527f85bd8788d3c4a160f0f6254229589f137d5633a870dcb46f99ffe07b4da1894b910160405180910390a1600480546001600160a01b0319166001600160a01b0392909216919091179055565b80516001600160a01b03811681146200027957600080fd5b919050565b60008060008060008060c087890312156200029857600080fd5b620002a38762000261565b955060208701519450604087015193506060870151925060808701519150620002cf60a0880162000261565b90509295509295509295565b6123d080620002eb6000396000f3fe6080604052600436106101295760003560e01c8063b3c82e92116100ab578063d9a4cbdf1161006f578063d9a4cbdf1461031e578063dbd183881461033e578063e471b02614610353578063e68a5c3d14610373578063fc52539514610393578063fe0d94c1146103b357600080fd5b8063b3c82e9214610288578063b68df16d146102b5578063c3a76886146102d6578063cebc9a82146102f4578063d89aac391461030957600080fd5b80635ab98d5a116100f25780635ab98d5a146101c157806364d62353146101e15780638533f33714610201578063a75b87d214610216578063b1fc87961461024857600080fd5b80625c33e11461012e57806303c2762114610130578063083a73a21461015457806340e58ee5146101745780635748c13014610194575b600080fd5b005b34801561013c57600080fd5b506002545b6040519081526020015b60405180910390f35b34801561016057600080fd5b5061012e61016f3660046119a8565b6103c6565b34801561018057600080fd5b5061012e61018f3660046119a8565b61041f565b3480156101a057600080fd5b506101b46101af3660046119a8565b6106ca565b60405161014b91906119d7565b3480156101cd57600080fd5b5061012e6101dc3660046119a8565b610760565b3480156101ed57600080fd5b5061012e6101fc3660046119a8565b6107ac565b34801561020d57600080fd5b50600554610141565b34801561022257600080fd5b506004546001600160a01b03165b6040516001600160a01b03909116815260200161014b565b34801561025457600080fd5b506102786102633660046119a8565b60009081526007602052604090205460ff1690565b604051901515815260200161014b565b34801561029457600080fd5b506102a86102a33660046119a8565b6107de565b60405161014b9190611b56565b6102c86102c3366004611c3e565b610b51565b60405161014b929190611cc1565b3480156102e257600080fd5b506008546001600160a01b0316610230565b34801561030057600080fd5b50600054610141565b34801561031557600080fd5b50600354610141565b34801561032a57600080fd5b5061012e610339366004612019565b610be2565b34801561034a57600080fd5b50600154610141565b34801561035f57600080fd5b5061012e61036e3660046119a8565b610c41565b34801561037f57600080fd5b5061012e61038e3660046120eb565b610c8c565b34801561039f57600080fd5b5061012e6103ae3660046120eb565b610d15565b61012e6103c13660046119a8565b610d3e565b3330146103e657604051631dbf5f2360e01b815260040160405180910390fd5b60025481116104085760405163cb2f2b2360e01b815260040160405180910390fd5b61041181611066565b61041c6000546110a7565b50565b6004546001600160a01b0316331461044a576040516377b6878160e11b815260040160405180910390fd5b6000610455826106ca565b6003811115610466576104666119c1565b146104845760405163050ac78b60e11b815260040160405180910390fd5b60008181526006602081905260408220908101805461ff001916610100179055805490915b81811015610699576106918360000182815481106104c9576104c961210d565b6000918252602090912001546001850180546001600160a01b0390921691849081106104f7576104f761210d565b90600052602060002001548560020184815481106105175761051761210d565b90600052602060002001805461052c90612123565b80601f016020809104026020016040519081016040528092919081815260200182805461055890612123565b80156105a55780601f1061057a576101008083540402835291602001916105a5565b820191906000526020600020905b81548152906001019060200180831161058857829003601f168201915b50505050508660030185815481106105bf576105bf61210d565b9060005260206000200180546105d490612123565b80601f016020809104026020016040519081016040528092919081815260200182805461060090612123565b801561064d5780601f106106225761010080835404028352916020019161064d565b820191906000526020600020905b81548152906001019060200180831161063057829003601f168201915b5050505050876005015488600401878154811061066c5761066c61210d565b90600052602060002090602091828204019190069054906101000a900460ff166110ed565b6001016104a9565b5060405183907f0743c673685efcbf3db8591d9e1d98336bc844fe4f4599e6f7efb6d71c02563490600090a2505050565b600081600554116106ee5760405163e5bc0e7b60e01b815260040160405180910390fd5b600082815260066020819052604090912090810154610100900460ff16156107195750600292915050565b600681015460ff161561072f5750600192915050565b60015481600501546107419190612158565b4211156107515750600392915050565b50600092915050565b50919050565b33301461078057604051631dbf5f2360e01b815260040160405180910390fd5b6102588110156107a3576040516301f6f9e560e71b815260040160405180910390fd5b61041c8161113f565b3330146107cc57604051631dbf5f2360e01b815260040160405180910390fd5b6107d5816110a7565b61041c81611180565b61082a6040518061010001604052806060815260200160608152602001606081526020016060815260200160608152602001600081526020016000151581526020016000151581525090565b60008281526006602090815260409182902082518154610120938102820184019094526101008101848152909391928492849184018282801561089657602002820191906000526020600020905b81546001600160a01b03168152600190910190602001808311610878575b50505050508152602001600182018054806020026020016040519081016040528092919081815260200182805480156108ee57602002820191906000526020600020905b8154815260200190600101908083116108da575b5050505050815260200160028201805480602002602001604051908101604052809291908181526020016000905b828210156109c857838290600052602060002001805461093b90612123565b80601f016020809104026020016040519081016040528092919081815260200182805461096790612123565b80156109b45780601f10610989576101008083540402835291602001916109b4565b820191906000526020600020905b81548152906001019060200180831161099757829003601f168201915b50505050508152602001906001019061091c565b50505050815260200160038201805480602002602001604051908101604052809291908181526020016000905b82821015610aa1578382906000526020600020018054610a1490612123565b80601f0160208091040260200160405190810160405280929190818152602001828054610a4090612123565b8015610a8d5780601f10610a6257610100808354040283529160200191610a8d565b820191906000526020600020905b815481529060010190602001808311610a7057829003601f168201915b5050505050815260200190600101906109f5565b50505050815260200160048201805480602002602001604051908101604052809291908181526020018280548015610b1857602002820191906000526020600020906000905b825461010083900a900460ff161515815260206001928301818104948501949093039092029101808411610ae75790505b50505091835250506005820154602082015260069091015460ff8082161515604084015261010090910416151560609091015292915050565b60006060333014610b7557604051631dbf5f2360e01b815260040160405180910390fd5b60006060866001600160a01b03168686604051610b9392919061217e565b600060405180830381855af49150503d8060008114610bce576040519150601f19603f3d011682016040523d82523d6000602084013e610bd3565b606091505b50909890975095505050505050565b6008546001600160a01b03167311110000000000000000000000000000000011101933016001600160a01b031614610c2d576040516359e8359960e01b815260040160405180910390fd5b610c3a85858585856111c1565b5050505050565b333014610c6157604051631dbf5f2360e01b815260040160405180910390fd5b6003548110610c83576040516301b1029b60e61b815260040160405180910390fd5b6104118161142e565b333014610cac57604051631dbf5f2360e01b815260040160405180910390fd5b600854604080516001600160a01b03928316815291831660208301527f01dd0ca50426f21c1b37ce7f3e95eef45b4a55bc5da80a7b67b2c65a7463caf0910160405180910390a1600880546001600160a01b0319166001600160a01b0392909216919091179055565b333014610d3557604051631dbf5f2360e01b815260040160405180910390fd5b61041c8161146f565b6000610d49826106ca565b6003811115610d5a57610d5a6119c1565b14610d785760405163050ac78b60e11b815260040160405180910390fd5b60008181526006602052604090206005810154421015610dab57604051635192dd5560e01b815260040160405180910390fd5b60068101805460ff19166001179055805460008167ffffffffffffffff811115610dd757610dd7611ce4565b604051908082528060200260200182016040528015610e0a57816020015b6060815260200190600190039081610df55790505b50905060005b8281101561101d57610ff8846000018281548110610e3057610e3061210d565b6000918252602090912001546001860180546001600160a01b039092169184908110610e5e57610e5e61210d565b9060005260206000200154866002018481548110610e7e57610e7e61210d565b906000526020600020018054610e9390612123565b80601f0160208091040260200160405190810160405280929190818152602001828054610ebf90612123565b8015610f0c5780601f10610ee157610100808354040283529160200191610f0c565b820191906000526020600020905b815481529060010190602001808311610eef57829003601f168201915b5050505050876003018581548110610f2657610f2661210d565b906000526020600020018054610f3b90612123565b80601f0160208091040260200160405190810160405280929190818152602001828054610f6790612123565b8015610fb45780601f10610f8957610100808354040283529160200191610fb4565b820191906000526020600020905b815481529060010190602001808311610f9757829003601f168201915b50505050508860050154896004018781548110610fd357610fd361210d565b90600052602060002090602091828204019190069054906101000a900460ff166114d8565b82828151811061100a5761100a61210d565b6020908102919091010152600101610e10565b50336001600160a01b0316847ff5efc4bb09a12b6c9561a7e7ab02938a72a4351316b473d574fdaaa89c43eb9a83604051611058919061218e565b60405180910390a350505050565b60035460408051918252602082018390527faf46013422363beb5e6f00ab923cffe3574670494c864de2828b9d7c201fdde5910160405180910390a1600355565b6002548110156110ca576040516361759e6560e01b815260040160405180910390fd5b60035481111561041c576040516386dac63560e01b815260040160405180910390fd5b600086868686868660405160200161110a969594939291906121a1565b60408051601f198184030181529181528151602092830120600090815260079092529020805460ff1916905550505050505050565b60015460408051918252602082018390527f9953f3a71052edcd4ae7a0f97302839d1dda32ab93c1039207c91c866b094f72910160405180910390a1600155565b60005460408051918252602082018390527f43de56b886294fc29767e51a88b5c67fd24aefebc5ddf813b1d9b91b1df38444910160405180910390a1600055565b84516111e057604051636a8e3e9360e11b815260040160405180910390fd5b84518451811415806111f3575083518114155b806111ff575082518114155b8061120b575081518114155b1561122957604051630d10f63b60e01b815260040160405180910390fd5b6005546000805461123a9042612158565b600580546001019055905060005b8381101561135b5760008982815181106112645761126461210d565b602002602001015189838151811061127e5761127e61210d565b60200260200101518984815181106112985761129861210d565b60200260200101518985815181106112b2576112b261210d565b6020026020010151868a87815181106112cd576112cd61210d565b60200260200101516040516020016112ea969594939291906121a1565b60405160208183030381529060405280519060200120905061131b8160009081526007602052604090205460ff1690565b1561133957604051633b2f04e360e21b815260040160405180910390fd5b6000908152600760205260409020805460ff1916600190811790915501611248565b5060008281526006602090815260409091208951909161137f9183918c01906116be565b50875161139590600183019060208b0190611723565b5086516113ab90600283019060208a019061175e565b5085516113c190600383019060208901906117b7565b5084516113d79060048301906020880190611810565b50818160050181905550827f0325966a4aa089b42f4766ec96f599405102bb309e065f24874aff59082dbc8b8a8a8a8a8a8860405161141b969594939291906121f5565b60405180910390a2505050505050505050565b60025460408051918252602082018390527fc534cdcbe9b52100810d787afd57e4174322776fcb58872ea706f23e9319fa8d910160405180910390a1600255565b600454604080516001600160a01b03928316815291831660208301527f85bd8788d3c4a160f0f6254229589f137d5633a870dcb46f99ffe07b4da1894b910160405180910390a1600480546001600160a01b0319166001600160a01b0392909216919091179055565b6060854710156114fb57604051631e9acf1760e31b815260040160405180910390fd5b6000878787878787604051602001611518969594939291906121a1565b60408051601f198184030181529181528151602092830120600081815260079093529120805460ff191690558651909150606090611557575084611583565b86805190602001208660405160200161157192919061229c565b60405160208183030381529060405290505b6000606085156116055760405163b68df16d60e01b8152309063b68df16d908c906115b4908f9088906004016122cd565b60006040518083038185885af11580156115d2573d6000803e3d6000fd5b50505050506040513d6000823e601f3d908101601f191682016040526115fb91908101906122f1565b9092509050611667565b8a6001600160a01b03168a8460405161161e919061237e565b60006040518083038185875af1925050503d806000811461165b576040519150601f19603f3d011682016040523d82523d6000602084013e611660565b606091505b5090925090505b6116718282611680565b9b9a5050505050505050505050565b6060821561168f5750806116b8565b81511561169f5781518083602001fd5b6040516332f63ed360e21b815260040160405180910390fd5b92915050565b828054828255906000526020600020908101928215611713579160200282015b8281111561171357825182546001600160a01b0319166001600160a01b039091161782556020909201916001909101906116de565b5061171f9291506118ac565b5090565b828054828255906000526020600020908101928215611713579160200282015b82811115611713578251825591602001919060010190611743565b8280548282559060005260206000209081019282156117ab579160200282015b828111156117ab578251805161179b9184916020909101906118c1565b509160200191906001019061177e565b5061171f929150611934565b828054828255906000526020600020908101928215611804579160200282015b8281111561180457825180516117f49184916020909101906118c1565b50916020019190600101906117d7565b5061171f929150611951565b82805482825590600052602060002090601f016020900481019282156117135791602002820160005b8382111561187657835183826101000a81548160ff0219169083151502179055509260200192600101602081600001049283019260010302611839565b80156118a35782816101000a81549060ff0219169055600101602081600001049283019260010302611876565b505061171f9291505b5b8082111561171f57600081556001016118ad565b8280546118cd90612123565b90600052602060002090601f0160209004810192826118ef5760008555611713565b82601f1061190857805160ff1916838001178555611713565b828001600101855582156117135791820182811115611713578251825591602001919060010190611743565b8082111561171f576000611948828261196e565b50600101611934565b8082111561171f576000611965828261196e565b50600101611951565b50805461197a90612123565b6000825580601f1061198a575050565b601f01602090049060005260206000209081019061041c91906118ac565b6000602082840312156119ba57600080fd5b5035919050565b634e487b7160e01b600052602160045260246000fd5b60208101600483106119f957634e487b7160e01b600052602160045260246000fd5b91905290565b600081518084526020808501945080840160005b83811015611a385781516001600160a01b031687529582019590820190600101611a13565b509495945050505050565b600081518084526020808501945080840160005b83811015611a3857815187529582019590820190600101611a57565b60005b83811015611a8e578181015183820152602001611a76565b83811115611a9d576000848401525b50505050565b60008151808452611abb816020860160208601611a73565b601f01601f19169290920160200192915050565b600081518084526020808501808196508360051b8101915082860160005b85811015611b17578284038952611b05848351611aa3565b98850198935090840190600101611aed565b5091979650505050505050565b600081518084526020808501945080840160005b83811015611a38578151151587529582019590820190600101611b38565b6020815260008251610100806020850152611b756101208501836119ff565b91506020850151601f1980868503016040870152611b938483611a43565b93506040870151915080868503016060870152611bb08483611acf565b93506060870151915080868503016080870152611bcd8483611acf565b935060808701519150808685030160a087015250611beb8382611b24565b92505060a085015160c085015260c0850151611c0b60e086018215159052565b5060e0850151801515858301525090949350505050565b80356001600160a01b0381168114611c3957600080fd5b919050565b600080600060408486031215611c5357600080fd5b611c5c84611c22565b9250602084013567ffffffffffffffff80821115611c7957600080fd5b818601915086601f830112611c8d57600080fd5b813581811115611c9c57600080fd5b876020828501011115611cae57600080fd5b6020830194508093505050509250925092565b8215158152604060208201526000611cdc6040830184611aa3565b949350505050565b634e487b7160e01b600052604160045260246000fd5b604051601f8201601f1916810167ffffffffffffffff81118282101715611d2357611d23611ce4565b604052919050565b600067ffffffffffffffff821115611d4557611d45611ce4565b5060051b60200190565b600082601f830112611d6057600080fd5b81356020611d75611d7083611d2b565b611cfa565b82815260059290921b84018101918181019086841115611d9457600080fd5b8286015b84811015611db657611da981611c22565b8352918301918301611d98565b509695505050505050565b600082601f830112611dd257600080fd5b81356020611de2611d7083611d2b565b82815260059290921b84018101918181019086841115611e0157600080fd5b8286015b84811015611db65780358352918301918301611e05565b600067ffffffffffffffff821115611e3657611e36611ce4565b50601f01601f191660200190565b6000611e52611d7084611e1c565b9050828152838383011115611e6657600080fd5b828260208301376000602084830101529392505050565b600082601f830112611e8e57600080fd5b81356020611e9e611d7083611d2b565b82815260059290921b84018101918181019086841115611ebd57600080fd5b8286015b84811015611db657803567ffffffffffffffff811115611ee15760008081fd5b8701603f81018913611ef35760008081fd5b611f04898683013560408401611e44565b845250918301918301611ec1565b600082601f830112611f2357600080fd5b81356020611f33611d7083611d2b565b82815260059290921b84018101918181019086841115611f5257600080fd5b8286015b84811015611db657803567ffffffffffffffff811115611f765760008081fd5b8701603f81018913611f885760008081fd5b611f99898683013560408401611e44565b845250918301918301611f56565b801515811461041c57600080fd5b600082601f830112611fc657600080fd5b81356020611fd6611d7083611d2b565b82815260059290921b84018101918181019086841115611ff557600080fd5b8286015b84811015611db657803561200c81611fa7565b8352918301918301611ff9565b600080600080600060a0868803121561203157600080fd5b853567ffffffffffffffff8082111561204957600080fd5b61205589838a01611d4f565b9650602088013591508082111561206b57600080fd5b61207789838a01611dc1565b9550604088013591508082111561208d57600080fd5b61209989838a01611e7d565b945060608801359150808211156120af57600080fd5b6120bb89838a01611f12565b935060808801359150808211156120d157600080fd5b506120de88828901611fb5565b9150509295509295909350565b6000602082840312156120fd57600080fd5b61210682611c22565b9392505050565b634e487b7160e01b600052603260045260246000fd5b600181811c9082168061213757607f821691505b6020821081141561075a57634e487b7160e01b600052602260045260246000fd5b6000821982111561217957634e487b7160e01b600052601160045260246000fd5b500190565b8183823760009101908152919050565b6020815260006121066020830184611acf565b60018060a01b038716815285602082015260c0604082015260006121c860c0830187611aa3565b82810360608401526121da8187611aa3565b6080840195909552505090151560a090910152949350505050565b60c0808252875190820181905260009060209060e0840190828b01845b828110156122375781516001600160a01b031684529284019290840190600101612212565b5050508381038285015261224b818a611a43565b91505082810360408401526122608188611acf565b905082810360608401526122748187611acf565b905082810360808401526122888186611b24565b9150508260a0830152979650505050505050565b6001600160e01b03198316815281516000906122bf816004850160208701611a73565b919091016004019392505050565b6001600160a01b0383168152604060208201819052600090611cdc90830184611aa3565b6000806040838503121561230457600080fd5b825161230f81611fa7565b602084015190925067ffffffffffffffff81111561232c57600080fd5b8301601f8101851361233d57600080fd5b805161234b611d7082611e1c565b81815286602083850101111561236057600080fd5b612371826020830160208601611a73565b8093505050509250929050565b60008251612390818460208701611a73565b919091019291505056fea26469706673582212204d15d998b970c88abcf61ca388d9d45b990eea314f3479db111535845712274d64736f6c634300080a0033", - "deployedBytecode": "0x6080604052600436106101295760003560e01c8063b3c82e92116100ab578063d9a4cbdf1161006f578063d9a4cbdf1461031e578063dbd183881461033e578063e471b02614610353578063e68a5c3d14610373578063fc52539514610393578063fe0d94c1146103b357600080fd5b8063b3c82e9214610288578063b68df16d146102b5578063c3a76886146102d6578063cebc9a82146102f4578063d89aac391461030957600080fd5b80635ab98d5a116100f25780635ab98d5a146101c157806364d62353146101e15780638533f33714610201578063a75b87d214610216578063b1fc87961461024857600080fd5b80625c33e11461012e57806303c2762114610130578063083a73a21461015457806340e58ee5146101745780635748c13014610194575b600080fd5b005b34801561013c57600080fd5b506002545b6040519081526020015b60405180910390f35b34801561016057600080fd5b5061012e61016f3660046119a8565b6103c6565b34801561018057600080fd5b5061012e61018f3660046119a8565b61041f565b3480156101a057600080fd5b506101b46101af3660046119a8565b6106ca565b60405161014b91906119d7565b3480156101cd57600080fd5b5061012e6101dc3660046119a8565b610760565b3480156101ed57600080fd5b5061012e6101fc3660046119a8565b6107ac565b34801561020d57600080fd5b50600554610141565b34801561022257600080fd5b506004546001600160a01b03165b6040516001600160a01b03909116815260200161014b565b34801561025457600080fd5b506102786102633660046119a8565b60009081526007602052604090205460ff1690565b604051901515815260200161014b565b34801561029457600080fd5b506102a86102a33660046119a8565b6107de565b60405161014b9190611b56565b6102c86102c3366004611c3e565b610b51565b60405161014b929190611cc1565b3480156102e257600080fd5b506008546001600160a01b0316610230565b34801561030057600080fd5b50600054610141565b34801561031557600080fd5b50600354610141565b34801561032a57600080fd5b5061012e610339366004612019565b610be2565b34801561034a57600080fd5b50600154610141565b34801561035f57600080fd5b5061012e61036e3660046119a8565b610c41565b34801561037f57600080fd5b5061012e61038e3660046120eb565b610c8c565b34801561039f57600080fd5b5061012e6103ae3660046120eb565b610d15565b61012e6103c13660046119a8565b610d3e565b3330146103e657604051631dbf5f2360e01b815260040160405180910390fd5b60025481116104085760405163cb2f2b2360e01b815260040160405180910390fd5b61041181611066565b61041c6000546110a7565b50565b6004546001600160a01b0316331461044a576040516377b6878160e11b815260040160405180910390fd5b6000610455826106ca565b6003811115610466576104666119c1565b146104845760405163050ac78b60e11b815260040160405180910390fd5b60008181526006602081905260408220908101805461ff001916610100179055805490915b81811015610699576106918360000182815481106104c9576104c961210d565b6000918252602090912001546001850180546001600160a01b0390921691849081106104f7576104f761210d565b90600052602060002001548560020184815481106105175761051761210d565b90600052602060002001805461052c90612123565b80601f016020809104026020016040519081016040528092919081815260200182805461055890612123565b80156105a55780601f1061057a576101008083540402835291602001916105a5565b820191906000526020600020905b81548152906001019060200180831161058857829003601f168201915b50505050508660030185815481106105bf576105bf61210d565b9060005260206000200180546105d490612123565b80601f016020809104026020016040519081016040528092919081815260200182805461060090612123565b801561064d5780601f106106225761010080835404028352916020019161064d565b820191906000526020600020905b81548152906001019060200180831161063057829003601f168201915b5050505050876005015488600401878154811061066c5761066c61210d565b90600052602060002090602091828204019190069054906101000a900460ff166110ed565b6001016104a9565b5060405183907f0743c673685efcbf3db8591d9e1d98336bc844fe4f4599e6f7efb6d71c02563490600090a2505050565b600081600554116106ee5760405163e5bc0e7b60e01b815260040160405180910390fd5b600082815260066020819052604090912090810154610100900460ff16156107195750600292915050565b600681015460ff161561072f5750600192915050565b60015481600501546107419190612158565b4211156107515750600392915050565b50600092915050565b50919050565b33301461078057604051631dbf5f2360e01b815260040160405180910390fd5b6102588110156107a3576040516301f6f9e560e71b815260040160405180910390fd5b61041c8161113f565b3330146107cc57604051631dbf5f2360e01b815260040160405180910390fd5b6107d5816110a7565b61041c81611180565b61082a6040518061010001604052806060815260200160608152602001606081526020016060815260200160608152602001600081526020016000151581526020016000151581525090565b60008281526006602090815260409182902082518154610120938102820184019094526101008101848152909391928492849184018282801561089657602002820191906000526020600020905b81546001600160a01b03168152600190910190602001808311610878575b50505050508152602001600182018054806020026020016040519081016040528092919081815260200182805480156108ee57602002820191906000526020600020905b8154815260200190600101908083116108da575b5050505050815260200160028201805480602002602001604051908101604052809291908181526020016000905b828210156109c857838290600052602060002001805461093b90612123565b80601f016020809104026020016040519081016040528092919081815260200182805461096790612123565b80156109b45780601f10610989576101008083540402835291602001916109b4565b820191906000526020600020905b81548152906001019060200180831161099757829003601f168201915b50505050508152602001906001019061091c565b50505050815260200160038201805480602002602001604051908101604052809291908181526020016000905b82821015610aa1578382906000526020600020018054610a1490612123565b80601f0160208091040260200160405190810160405280929190818152602001828054610a4090612123565b8015610a8d5780601f10610a6257610100808354040283529160200191610a8d565b820191906000526020600020905b815481529060010190602001808311610a7057829003601f168201915b5050505050815260200190600101906109f5565b50505050815260200160048201805480602002602001604051908101604052809291908181526020018280548015610b1857602002820191906000526020600020906000905b825461010083900a900460ff161515815260206001928301818104948501949093039092029101808411610ae75790505b50505091835250506005820154602082015260069091015460ff8082161515604084015261010090910416151560609091015292915050565b60006060333014610b7557604051631dbf5f2360e01b815260040160405180910390fd5b60006060866001600160a01b03168686604051610b9392919061217e565b600060405180830381855af49150503d8060008114610bce576040519150601f19603f3d011682016040523d82523d6000602084013e610bd3565b606091505b50909890975095505050505050565b6008546001600160a01b03167311110000000000000000000000000000000011101933016001600160a01b031614610c2d576040516359e8359960e01b815260040160405180910390fd5b610c3a85858585856111c1565b5050505050565b333014610c6157604051631dbf5f2360e01b815260040160405180910390fd5b6003548110610c83576040516301b1029b60e61b815260040160405180910390fd5b6104118161142e565b333014610cac57604051631dbf5f2360e01b815260040160405180910390fd5b600854604080516001600160a01b03928316815291831660208301527f01dd0ca50426f21c1b37ce7f3e95eef45b4a55bc5da80a7b67b2c65a7463caf0910160405180910390a1600880546001600160a01b0319166001600160a01b0392909216919091179055565b333014610d3557604051631dbf5f2360e01b815260040160405180910390fd5b61041c8161146f565b6000610d49826106ca565b6003811115610d5a57610d5a6119c1565b14610d785760405163050ac78b60e11b815260040160405180910390fd5b60008181526006602052604090206005810154421015610dab57604051635192dd5560e01b815260040160405180910390fd5b60068101805460ff19166001179055805460008167ffffffffffffffff811115610dd757610dd7611ce4565b604051908082528060200260200182016040528015610e0a57816020015b6060815260200190600190039081610df55790505b50905060005b8281101561101d57610ff8846000018281548110610e3057610e3061210d565b6000918252602090912001546001860180546001600160a01b039092169184908110610e5e57610e5e61210d565b9060005260206000200154866002018481548110610e7e57610e7e61210d565b906000526020600020018054610e9390612123565b80601f0160208091040260200160405190810160405280929190818152602001828054610ebf90612123565b8015610f0c5780601f10610ee157610100808354040283529160200191610f0c565b820191906000526020600020905b815481529060010190602001808311610eef57829003601f168201915b5050505050876003018581548110610f2657610f2661210d565b906000526020600020018054610f3b90612123565b80601f0160208091040260200160405190810160405280929190818152602001828054610f6790612123565b8015610fb45780601f10610f8957610100808354040283529160200191610fb4565b820191906000526020600020905b815481529060010190602001808311610f9757829003601f168201915b50505050508860050154896004018781548110610fd357610fd361210d565b90600052602060002090602091828204019190069054906101000a900460ff166114d8565b82828151811061100a5761100a61210d565b6020908102919091010152600101610e10565b50336001600160a01b0316847ff5efc4bb09a12b6c9561a7e7ab02938a72a4351316b473d574fdaaa89c43eb9a83604051611058919061218e565b60405180910390a350505050565b60035460408051918252602082018390527faf46013422363beb5e6f00ab923cffe3574670494c864de2828b9d7c201fdde5910160405180910390a1600355565b6002548110156110ca576040516361759e6560e01b815260040160405180910390fd5b60035481111561041c576040516386dac63560e01b815260040160405180910390fd5b600086868686868660405160200161110a969594939291906121a1565b60408051601f198184030181529181528151602092830120600090815260079092529020805460ff1916905550505050505050565b60015460408051918252602082018390527f9953f3a71052edcd4ae7a0f97302839d1dda32ab93c1039207c91c866b094f72910160405180910390a1600155565b60005460408051918252602082018390527f43de56b886294fc29767e51a88b5c67fd24aefebc5ddf813b1d9b91b1df38444910160405180910390a1600055565b84516111e057604051636a8e3e9360e11b815260040160405180910390fd5b84518451811415806111f3575083518114155b806111ff575082518114155b8061120b575081518114155b1561122957604051630d10f63b60e01b815260040160405180910390fd5b6005546000805461123a9042612158565b600580546001019055905060005b8381101561135b5760008982815181106112645761126461210d565b602002602001015189838151811061127e5761127e61210d565b60200260200101518984815181106112985761129861210d565b60200260200101518985815181106112b2576112b261210d565b6020026020010151868a87815181106112cd576112cd61210d565b60200260200101516040516020016112ea969594939291906121a1565b60405160208183030381529060405280519060200120905061131b8160009081526007602052604090205460ff1690565b1561133957604051633b2f04e360e21b815260040160405180910390fd5b6000908152600760205260409020805460ff1916600190811790915501611248565b5060008281526006602090815260409091208951909161137f9183918c01906116be565b50875161139590600183019060208b0190611723565b5086516113ab90600283019060208a019061175e565b5085516113c190600383019060208901906117b7565b5084516113d79060048301906020880190611810565b50818160050181905550827f0325966a4aa089b42f4766ec96f599405102bb309e065f24874aff59082dbc8b8a8a8a8a8a8860405161141b969594939291906121f5565b60405180910390a2505050505050505050565b60025460408051918252602082018390527fc534cdcbe9b52100810d787afd57e4174322776fcb58872ea706f23e9319fa8d910160405180910390a1600255565b600454604080516001600160a01b03928316815291831660208301527f85bd8788d3c4a160f0f6254229589f137d5633a870dcb46f99ffe07b4da1894b910160405180910390a1600480546001600160a01b0319166001600160a01b0392909216919091179055565b6060854710156114fb57604051631e9acf1760e31b815260040160405180910390fd5b6000878787878787604051602001611518969594939291906121a1565b60408051601f198184030181529181528151602092830120600081815260079093529120805460ff191690558651909150606090611557575084611583565b86805190602001208660405160200161157192919061229c565b60405160208183030381529060405290505b6000606085156116055760405163b68df16d60e01b8152309063b68df16d908c906115b4908f9088906004016122cd565b60006040518083038185885af11580156115d2573d6000803e3d6000fd5b50505050506040513d6000823e601f3d908101601f191682016040526115fb91908101906122f1565b9092509050611667565b8a6001600160a01b03168a8460405161161e919061237e565b60006040518083038185875af1925050503d806000811461165b576040519150601f19603f3d011682016040523d82523d6000602084013e611660565b606091505b5090925090505b6116718282611680565b9b9a5050505050505050505050565b6060821561168f5750806116b8565b81511561169f5781518083602001fd5b6040516332f63ed360e21b815260040160405180910390fd5b92915050565b828054828255906000526020600020908101928215611713579160200282015b8281111561171357825182546001600160a01b0319166001600160a01b039091161782556020909201916001909101906116de565b5061171f9291506118ac565b5090565b828054828255906000526020600020908101928215611713579160200282015b82811115611713578251825591602001919060010190611743565b8280548282559060005260206000209081019282156117ab579160200282015b828111156117ab578251805161179b9184916020909101906118c1565b509160200191906001019061177e565b5061171f929150611934565b828054828255906000526020600020908101928215611804579160200282015b8281111561180457825180516117f49184916020909101906118c1565b50916020019190600101906117d7565b5061171f929150611951565b82805482825590600052602060002090601f016020900481019282156117135791602002820160005b8382111561187657835183826101000a81548160ff0219169083151502179055509260200192600101602081600001049283019260010302611839565b80156118a35782816101000a81549060ff0219169055600101602081600001049283019260010302611876565b505061171f9291505b5b8082111561171f57600081556001016118ad565b8280546118cd90612123565b90600052602060002090601f0160209004810192826118ef5760008555611713565b82601f1061190857805160ff1916838001178555611713565b828001600101855582156117135791820182811115611713578251825591602001919060010190611743565b8082111561171f576000611948828261196e565b50600101611934565b8082111561171f576000611965828261196e565b50600101611951565b50805461197a90612123565b6000825580601f1061198a575050565b601f01602090049060005260206000209081019061041c91906118ac565b6000602082840312156119ba57600080fd5b5035919050565b634e487b7160e01b600052602160045260246000fd5b60208101600483106119f957634e487b7160e01b600052602160045260246000fd5b91905290565b600081518084526020808501945080840160005b83811015611a385781516001600160a01b031687529582019590820190600101611a13565b509495945050505050565b600081518084526020808501945080840160005b83811015611a3857815187529582019590820190600101611a57565b60005b83811015611a8e578181015183820152602001611a76565b83811115611a9d576000848401525b50505050565b60008151808452611abb816020860160208601611a73565b601f01601f19169290920160200192915050565b600081518084526020808501808196508360051b8101915082860160005b85811015611b17578284038952611b05848351611aa3565b98850198935090840190600101611aed565b5091979650505050505050565b600081518084526020808501945080840160005b83811015611a38578151151587529582019590820190600101611b38565b6020815260008251610100806020850152611b756101208501836119ff565b91506020850151601f1980868503016040870152611b938483611a43565b93506040870151915080868503016060870152611bb08483611acf565b93506060870151915080868503016080870152611bcd8483611acf565b935060808701519150808685030160a087015250611beb8382611b24565b92505060a085015160c085015260c0850151611c0b60e086018215159052565b5060e0850151801515858301525090949350505050565b80356001600160a01b0381168114611c3957600080fd5b919050565b600080600060408486031215611c5357600080fd5b611c5c84611c22565b9250602084013567ffffffffffffffff80821115611c7957600080fd5b818601915086601f830112611c8d57600080fd5b813581811115611c9c57600080fd5b876020828501011115611cae57600080fd5b6020830194508093505050509250925092565b8215158152604060208201526000611cdc6040830184611aa3565b949350505050565b634e487b7160e01b600052604160045260246000fd5b604051601f8201601f1916810167ffffffffffffffff81118282101715611d2357611d23611ce4565b604052919050565b600067ffffffffffffffff821115611d4557611d45611ce4565b5060051b60200190565b600082601f830112611d6057600080fd5b81356020611d75611d7083611d2b565b611cfa565b82815260059290921b84018101918181019086841115611d9457600080fd5b8286015b84811015611db657611da981611c22565b8352918301918301611d98565b509695505050505050565b600082601f830112611dd257600080fd5b81356020611de2611d7083611d2b565b82815260059290921b84018101918181019086841115611e0157600080fd5b8286015b84811015611db65780358352918301918301611e05565b600067ffffffffffffffff821115611e3657611e36611ce4565b50601f01601f191660200190565b6000611e52611d7084611e1c565b9050828152838383011115611e6657600080fd5b828260208301376000602084830101529392505050565b600082601f830112611e8e57600080fd5b81356020611e9e611d7083611d2b565b82815260059290921b84018101918181019086841115611ebd57600080fd5b8286015b84811015611db657803567ffffffffffffffff811115611ee15760008081fd5b8701603f81018913611ef35760008081fd5b611f04898683013560408401611e44565b845250918301918301611ec1565b600082601f830112611f2357600080fd5b81356020611f33611d7083611d2b565b82815260059290921b84018101918181019086841115611f5257600080fd5b8286015b84811015611db657803567ffffffffffffffff811115611f765760008081fd5b8701603f81018913611f885760008081fd5b611f99898683013560408401611e44565b845250918301918301611f56565b801515811461041c57600080fd5b600082601f830112611fc657600080fd5b81356020611fd6611d7083611d2b565b82815260059290921b84018101918181019086841115611ff557600080fd5b8286015b84811015611db657803561200c81611fa7565b8352918301918301611ff9565b600080600080600060a0868803121561203157600080fd5b853567ffffffffffffffff8082111561204957600080fd5b61205589838a01611d4f565b9650602088013591508082111561206b57600080fd5b61207789838a01611dc1565b9550604088013591508082111561208d57600080fd5b61209989838a01611e7d565b945060608801359150808211156120af57600080fd5b6120bb89838a01611f12565b935060808801359150808211156120d157600080fd5b506120de88828901611fb5565b9150509295509295909350565b6000602082840312156120fd57600080fd5b61210682611c22565b9392505050565b634e487b7160e01b600052603260045260246000fd5b600181811c9082168061213757607f821691505b6020821081141561075a57634e487b7160e01b600052602260045260246000fd5b6000821982111561217957634e487b7160e01b600052601160045260246000fd5b500190565b8183823760009101908152919050565b6020815260006121066020830184611acf565b60018060a01b038716815285602082015260c0604082015260006121c860c0830187611aa3565b82810360608401526121da8187611aa3565b6080840195909552505090151560a090910152949350505050565b60c0808252875190820181905260009060209060e0840190828b01845b828110156122375781516001600160a01b031684529284019290840190600101612212565b5050508381038285015261224b818a611a43565b91505082810360408401526122608188611acf565b905082810360608401526122748187611acf565b905082810360808401526122888186611b24565b9150508260a0830152979650505050505050565b6001600160e01b03198316815281516000906122bf816004850160208701611a73565b919091016004019392505050565b6001600160a01b0383168152604060208201819052600090611cdc90830184611aa3565b6000806040838503121561230457600080fd5b825161230f81611fa7565b602084015190925067ffffffffffffffff81111561232c57600080fd5b8301601f8101851361233d57600080fd5b805161234b611d7082611e1c565b81815286602083850101111561236057600080fd5b612371826020830160208601611a73565b8093505050509250929050565b60008251612390818460208701611a73565b919091019291505056fea26469706673582212204d15d998b970c88abcf61ca388d9d45b990eea314f3479db111535845712274d64736f6c634300080a0033", - "linkReferences": {}, - "deployedLinkReferences": {} -} diff --git a/utils/arbitrum/artifacts/L1GatewayRouter.json b/utils/arbitrum/artifacts/L1GatewayRouter.json deleted file mode 100644 index 45af03c2..00000000 --- a/utils/arbitrum/artifacts/L1GatewayRouter.json +++ /dev/null @@ -1,582 +0,0 @@ -{ - "_format": "hh-sol-artifact-1", - "contractName": "L1GatewayRouter", - "sourceName": "contracts/tokenbridge/ethereum/gateway/L1GatewayRouter.sol", - "abi": [ - { - "anonymous": false, - "inputs": [ - { - "indexed": false, - "internalType": "address", - "name": "newDefaultGateway", - "type": "address" - } - ], - "name": "DefaultGatewayUpdated", - "type": "event" - }, - { - "anonymous": false, - "inputs": [ - { - "indexed": true, - "internalType": "address", - "name": "l1Token", - "type": "address" - }, - { - "indexed": true, - "internalType": "address", - "name": "gateway", - "type": "address" - } - ], - "name": "GatewaySet", - "type": "event" - }, - { - "anonymous": false, - "inputs": [ - { - "indexed": true, - "internalType": "address", - "name": "token", - "type": "address" - }, - { - "indexed": true, - "internalType": "address", - "name": "_userFrom", - "type": "address" - }, - { - "indexed": true, - "internalType": "address", - "name": "_userTo", - "type": "address" - }, - { - "indexed": false, - "internalType": "address", - "name": "gateway", - "type": "address" - } - ], - "name": "TransferRouted", - "type": "event" - }, - { - "anonymous": false, - "inputs": [ - { - "indexed": true, - "internalType": "address", - "name": "_from", - "type": "address" - }, - { - "indexed": true, - "internalType": "address", - "name": "_to", - "type": "address" - }, - { - "indexed": true, - "internalType": "uint256", - "name": "_seqNum", - "type": "uint256" - }, - { - "indexed": false, - "internalType": "bytes", - "name": "_data", - "type": "bytes" - } - ], - "name": "TxToL2", - "type": "event" - }, - { - "anonymous": false, - "inputs": [ - { - "indexed": false, - "internalType": "address", - "name": "newSource", - "type": "address" - } - ], - "name": "WhitelistSourceUpdated", - "type": "event" - }, - { - "inputs": [ - { - "internalType": "address", - "name": "l1ERC20", - "type": "address" - } - ], - "name": "calculateL2TokenAddress", - "outputs": [ - { - "internalType": "address", - "name": "", - "type": "address" - } - ], - "stateMutability": "view", - "type": "function" - }, - { - "inputs": [], - "name": "counterpartGateway", - "outputs": [ - { - "internalType": "address", - "name": "", - "type": "address" - } - ], - "stateMutability": "view", - "type": "function" - }, - { - "inputs": [], - "name": "defaultGateway", - "outputs": [ - { - "internalType": "address", - "name": "", - "type": "address" - } - ], - "stateMutability": "view", - "type": "function" - }, - { - "inputs": [ - { - "internalType": "address", - "name": "", - "type": "address" - }, - { - "internalType": "address", - "name": "", - "type": "address" - }, - { - "internalType": "address", - "name": "", - "type": "address" - }, - { - "internalType": "uint256", - "name": "", - "type": "uint256" - }, - { - "internalType": "bytes", - "name": "", - "type": "bytes" - } - ], - "name": "finalizeInboundTransfer", - "outputs": [], - "stateMutability": "payable", - "type": "function" - }, - { - "inputs": [ - { - "internalType": "address", - "name": "_token", - "type": "address" - } - ], - "name": "getGateway", - "outputs": [ - { - "internalType": "address", - "name": "gateway", - "type": "address" - } - ], - "stateMutability": "view", - "type": "function" - }, - { - "inputs": [ - { - "internalType": "address", - "name": "_token", - "type": "address" - }, - { - "internalType": "address", - "name": "_from", - "type": "address" - }, - { - "internalType": "address", - "name": "_to", - "type": "address" - }, - { - "internalType": "uint256", - "name": "_amount", - "type": "uint256" - }, - { - "internalType": "bytes", - "name": "_data", - "type": "bytes" - } - ], - "name": "getOutboundCalldata", - "outputs": [ - { - "internalType": "bytes", - "name": "", - "type": "bytes" - } - ], - "stateMutability": "view", - "type": "function" - }, - { - "inputs": [], - "name": "inbox", - "outputs": [ - { - "internalType": "address", - "name": "", - "type": "address" - } - ], - "stateMutability": "view", - "type": "function" - }, - { - "inputs": [ - { - "internalType": "address", - "name": "_owner", - "type": "address" - }, - { - "internalType": "address", - "name": "_defaultGateway", - "type": "address" - }, - { - "internalType": "address", - "name": "_whitelist", - "type": "address" - }, - { - "internalType": "address", - "name": "_counterpartGateway", - "type": "address" - }, - { - "internalType": "address", - "name": "_inbox", - "type": "address" - } - ], - "name": "initialize", - "outputs": [], - "stateMutability": "nonpayable", - "type": "function" - }, - { - "inputs": [ - { - "internalType": "address", - "name": "", - "type": "address" - } - ], - "name": "l1TokenToGateway", - "outputs": [ - { - "internalType": "address", - "name": "", - "type": "address" - } - ], - "stateMutability": "view", - "type": "function" - }, - { - "inputs": [ - { - "internalType": "address", - "name": "_token", - "type": "address" - }, - { - "internalType": "address", - "name": "_to", - "type": "address" - }, - { - "internalType": "uint256", - "name": "_amount", - "type": "uint256" - }, - { - "internalType": "uint256", - "name": "_maxGas", - "type": "uint256" - }, - { - "internalType": "uint256", - "name": "_gasPriceBid", - "type": "uint256" - }, - { - "internalType": "bytes", - "name": "_data", - "type": "bytes" - } - ], - "name": "outboundTransfer", - "outputs": [ - { - "internalType": "bytes", - "name": "", - "type": "bytes" - } - ], - "stateMutability": "payable", - "type": "function" - }, - { - "inputs": [], - "name": "owner", - "outputs": [ - { - "internalType": "address", - "name": "", - "type": "address" - } - ], - "stateMutability": "view", - "type": "function" - }, - { - "inputs": [], - "name": "postUpgradeInit", - "outputs": [], - "stateMutability": "nonpayable", - "type": "function" - }, - { - "inputs": [], - "name": "router", - "outputs": [ - { - "internalType": "address", - "name": "", - "type": "address" - } - ], - "stateMutability": "view", - "type": "function" - }, - { - "inputs": [ - { - "internalType": "address", - "name": "newL1DefaultGateway", - "type": "address" - }, - { - "internalType": "uint256", - "name": "_maxGas", - "type": "uint256" - }, - { - "internalType": "uint256", - "name": "_gasPriceBid", - "type": "uint256" - }, - { - "internalType": "uint256", - "name": "_maxSubmissionCost", - "type": "uint256" - } - ], - "name": "setDefaultGateway", - "outputs": [ - { - "internalType": "uint256", - "name": "", - "type": "uint256" - } - ], - "stateMutability": "payable", - "type": "function" - }, - { - "inputs": [ - { - "internalType": "address", - "name": "_gateway", - "type": "address" - }, - { - "internalType": "uint256", - "name": "_maxGas", - "type": "uint256" - }, - { - "internalType": "uint256", - "name": "_gasPriceBid", - "type": "uint256" - }, - { - "internalType": "uint256", - "name": "_maxSubmissionCost", - "type": "uint256" - }, - { - "internalType": "address", - "name": "_creditBackAddress", - "type": "address" - } - ], - "name": "setGateway", - "outputs": [ - { - "internalType": "uint256", - "name": "", - "type": "uint256" - } - ], - "stateMutability": "payable", - "type": "function" - }, - { - "inputs": [ - { - "internalType": "address", - "name": "_gateway", - "type": "address" - }, - { - "internalType": "uint256", - "name": "_maxGas", - "type": "uint256" - }, - { - "internalType": "uint256", - "name": "_gasPriceBid", - "type": "uint256" - }, - { - "internalType": "uint256", - "name": "_maxSubmissionCost", - "type": "uint256" - } - ], - "name": "setGateway", - "outputs": [ - { - "internalType": "uint256", - "name": "", - "type": "uint256" - } - ], - "stateMutability": "payable", - "type": "function" - }, - { - "inputs": [ - { - "internalType": "address[]", - "name": "_token", - "type": "address[]" - }, - { - "internalType": "address[]", - "name": "_gateway", - "type": "address[]" - }, - { - "internalType": "uint256", - "name": "_maxGas", - "type": "uint256" - }, - { - "internalType": "uint256", - "name": "_gasPriceBid", - "type": "uint256" - }, - { - "internalType": "uint256", - "name": "_maxSubmissionCost", - "type": "uint256" - } - ], - "name": "setGateways", - "outputs": [ - { - "internalType": "uint256", - "name": "", - "type": "uint256" - } - ], - "stateMutability": "payable", - "type": "function" - }, - { - "inputs": [ - { - "internalType": "address", - "name": "newOwner", - "type": "address" - } - ], - "name": "setOwner", - "outputs": [], - "stateMutability": "nonpayable", - "type": "function" - }, - { - "inputs": [ - { - "internalType": "address", - "name": "newSource", - "type": "address" - } - ], - "name": "updateWhitelistSource", - "outputs": [], - "stateMutability": "nonpayable", - "type": "function" - }, - { - "inputs": [], - "name": "whitelist", - "outputs": [ - { - "internalType": "address", - "name": "", - "type": "address" - } - ], - "stateMutability": "view", - "type": "function" - } - ], - "bytecode": "0x608060405234801561001057600080fd5b50611f1d806100206000396000f3fe60806040526004361061011f5760003560e01c806393e59dc1116100a0578063d2ce7d6511610064578063d2ce7d651461066a578063dd61456914610704578063ed08fdc61461073c578063f887ea401461076f578063fb0e722b146107845761011f565b806393e59dc11461048c57806395fcea78146104a1578063a0c76a96146104b6578063a7e28d4814610604578063bda009fe146106375761011f565b80632e567b36116100e75780632e567b361461024857806347466f98146102de5780635625a95214610311578063658b53f4146103495780638da5cb5b146104775761011f565b8063032958021461012457806313af4035146101555780631459457a1461018a5780632d67b72d146101df5780632db09c1c14610233575b600080fd5b34801561013057600080fd5b50610139610799565b604080516001600160a01b039092168252519081900360200190f35b34801561016157600080fd5b506101886004803603602081101561017857600080fd5b50356001600160a01b03166107a8565b005b34801561019657600080fd5b50610188600480360360a08110156101ad57600080fd5b506001600160a01b03813581169160208101358216916040820135811691606081013582169160809091013516610861565b610221600480360360a08110156101f557600080fd5b506001600160a01b038135811691602081013591604082013591606081013591608090910135166108af565b60408051918252519081900360200190f35b34801561023f57600080fd5b50610139610b0c565b610188600480360360a081101561025e57600080fd5b6001600160a01b03823581169260208101358216926040820135909216916060820135919081019060a081016080820135600160201b8111156102a057600080fd5b8201836020820111156102b257600080fd5b803590602001918460018302840111600160201b831117156102d357600080fd5b509092509050610b1b565b3480156102ea57600080fd5b506101886004803603602081101561030157600080fd5b50356001600160a01b0316610b5f565b6102216004803603608081101561032757600080fd5b506001600160a01b038135169060208101359060408101359060600135610c02565b610221600480360360a081101561035f57600080fd5b810190602081018135600160201b81111561037957600080fd5b82018360208201111561038b57600080fd5b803590602001918460208302840111600160201b831117156103ac57600080fd5b9190808060200260200160405190810160405280939291908181526020018383602002808284376000920191909152509295949360208101935035915050600160201b8111156103fb57600080fd5b82018360208201111561040d57600080fd5b803590602001918460208302840111600160201b8311171561042e57600080fd5b9190808060200260200160405190810160405280939291908181526020018383602002808284376000920191909152509295505082359350505060208101359060400135610d9f565b34801561048357600080fd5b50610139610e06565b34801561049857600080fd5b50610139610e15565b3480156104ad57600080fd5b50610188610e24565b3480156104c257600080fd5b5061058f600480360360a08110156104d957600080fd5b6001600160a01b03823581169260208101358216926040820135909216916060820135919081019060a081016080820135600160201b81111561051b57600080fd5b82018360208201111561052d57600080fd5b803590602001918460018302840111600160201b8311171561054e57600080fd5b91908080601f016020809104026020016040519081016040528093929190818152602001838380828437600092019190915250929550610e81945050505050565b6040805160208082528351818301528351919283929083019185019080838360005b838110156105c95781810151838201526020016105b1565b50505050905090810190601f1680156105f65780820380516001836020036101000a031916815260200191505b509250505060405180910390f35b34801561061057600080fd5b506101396004803603602081101561062757600080fd5b50356001600160a01b0316611083565b34801561064357600080fd5b506101396004803603602081101561065a57600080fd5b50356001600160a01b0316611134565b61058f600480360360c081101561068057600080fd5b6001600160a01b0382358116926020810135909116916040820135916060810135916080820135919081019060c0810160a0820135600160201b8111156106c657600080fd5b8201836020820111156106d857600080fd5b803590602001918460018302840111600160201b831117156106f957600080fd5b509092509050611196565b6102216004803603608081101561071a57600080fd5b506001600160a01b0381351690602081013590604081013590606001356113ba565b34801561074857600080fd5b506101396004803603602081101561075f57600080fd5b50356001600160a01b03166113d2565b34801561077b57600080fd5b506101396113ed565b34801561079057600080fd5b506101396113fc565b6004546001600160a01b031681565b6005546001600160a01b031633146107f4576040805162461bcd60e51b815260206004820152600a60248201526927a7262cafa7aba722a960b11b604482015290519081900360640190fd5b6001600160a01b03811661083f576040805162461bcd60e51b815260206004820152600d60248201526c24a72b20a624a22fa7aba722a960991b604482015290519081900360640190fd5b600580546001600160a01b0319166001600160a01b0392909216919091179055565b61086d8260008661140b565b600580546001600160a01b03199081166001600160a01b0397881617909155600080548216948716949094179093556006805490931694169390931790555050565b600061a4b160ff16336001600160a01b0316638e5f5ad16040518163ffffffff1660e01b815260040160206040518083038186803b1580156108f057600080fd5b505afa158015610904573d6000803e3d6000fd5b505050506040513d602081101561091a57600080fd5b505160ff1614610963576040805162461bcd60e51b815260206004820152600f60248201526e1393d517d0549097d1539050931151608a1b604482015290519081900360640190fd5b610975866001600160a01b0316611482565b6109b8576040805162461bcd60e51b815260206004820152600f60248201526e1393d517d513d7d0d3d395149050d5608a1b604482015290519081900360640190fd5b60006109c333611134565b90506001600160a01b038116158015906109eb57506004546001600160a01b03828116911614155b15610a5657866001600160a01b0316816001600160a01b031614610a56576040805162461bcd60e51b815260206004820152601b60248201527f4e4f5f5550444154455f544f5f444946464552454e545f414444520000000000604482015290519081900360640190fd5b604080516001808252818301909252606091602080830190803683370190505090503381600081518110610a8657fe5b6001600160a01b0392909216602092830291909101909101526040805160018082528183019092526060918160200160208202803683370190505090508881600081518110610ad157fe5b60200260200101906001600160a01b031690816001600160a01b031681525050610aff82828a8a8a8a611488565b9998505050505050505050565b6001546001600160a01b031681565b6040805162461bcd60e51b815260206004820152601460248201527327a7262cafa7aaaa2127aaa7222fa927aaaa22a960611b604482015290519081900360640190fd5b6000546001600160a01b03163314610bae576040805162461bcd60e51b815260206004820152600d60248201526c1393d517d19493d357d31254d5609a1b604482015290519081900360640190fd5b600080546001600160a01b0383166001600160a01b0319909116811790915560408051918252517f37389c47920d5cc3229678a0205d0455002c07541a4139ebdce91ac2274657779181900360200190a150565b6005546000906001600160a01b03163314610c51576040805162461bcd60e51b815260206004820152600a60248201526927a7262cafa7aba722a960b11b604482015290519081900360640190fd5b600480546001600160a01b0387166001600160a01b0319909116811790915560408051918252517f3a8f8eb961383a94d41d193e16a3af73eaddfd5764a4c640257323a1603ac3319181900360200190a160006001600160a01b03861615610d1b57856001600160a01b0316632db09c1c6040518163ffffffff1660e01b815260040160206040518083038186803b158015610cec57600080fd5b505afa158015610d00573d6000803e3d6000fd5b505050506040513d6020811015610d1657600080fd5b505190505b604080516001600160a01b038084166024808401919091528351808403909101815260449092018352602082810180516001600160e01b031663f7c9362f60e01b17905260065460015485516060810187528981529283018b90529482018990529293610d949383169216903390349060009087611905565b979650505050505050565b6005546000906001600160a01b03163314610dee576040805162461bcd60e51b815260206004820152600a60248201526927a7262cafa7aba722a960b11b604482015290519081900360640190fd5b610dfc868686868633611488565b9695505050505050565b6005546001600160a01b031681565b6000546001600160a01b031681565b6000610e2e611924565b9050336001600160a01b03821614610e7e576040805162461bcd60e51b815260206004820152600e60248201526d2727aa2fa32927a6afa0a226a4a760911b604482015290519081900360640190fd5b50565b60606000610e8e87611134565b9050806001600160a01b031663a0c76a9688888888886040518663ffffffff1660e01b815260040180866001600160a01b03166001600160a01b03168152602001856001600160a01b03166001600160a01b03168152602001846001600160a01b03166001600160a01b0316815260200183815260200180602001828103825283818151815260200191508051906020019080838360005b83811015610f3e578181015183820152602001610f26565b50505050905090810190601f168015610f6b5780820380516001836020036101000a031916815260200191505b50965050505050505060006040518083038186803b158015610f8c57600080fd5b505afa158015610fa0573d6000803e3d6000fd5b505050506040513d6000823e601f3d908101601f191682016040526020811015610fc957600080fd5b8101908080516040519392919084600160201b821115610fe857600080fd5b908301906020820185811115610ffd57600080fd5b8251600160201b81118282018810171561101657600080fd5b82525081516020918201929091019080838360005b8381101561104357818101518382015260200161102b565b50505050905090810190601f1680156110705780820380516001836020036101000a031916815260200191505b5060405250505091505095945050505050565b60008061108f83611134565b90506001600160a01b0381166110a957600091505061112f565b806001600160a01b031663a7e28d48846040518263ffffffff1660e01b815260040180826001600160a01b03166001600160a01b0316815260200191505060206040518083038186803b1580156110ff57600080fd5b505afa158015611113573d6000803e3d6000fd5b505050506040513d602081101561112957600080fd5b50519150505b919050565b6001600160a01b03808216600090815260036020526040902054168061116257506004546001600160a01b03165b6001600160a01b038116600114806111895750611187816001600160a01b0316611482565b155b1561112f5750600061112f565b6000546060906001600160a01b031615611264576000546040805163babcc53960e01b815233600482015290516001600160a01b039092169163babcc53991602480820192602092909190829003018186803b1580156111f557600080fd5b505afa158015611209573d6000803e3d6000fd5b505050506040513d602081101561121f57600080fd5b5051611264576040805162461bcd60e51b815260206004820152600f60248201526e1393d517d5d2125511531254d51151608a1b604482015290519081900360640190fd5b60008383604081101561127657600080fd5b81359190810190604081016020820135600160201b81111561129757600080fd5b8201836020820111156112a957600080fd5b803590602001918460018302840111600160201b831117156112ca57600080fd5b91908080601f0160208091040260200160405190810160405280939291908181526020018383808284376000920191909152509697505050508989028501935050508215159050611357576040805162461bcd60e51b81526020600482015260126024820152711393d7d4d550935254d4d253d397d0d3d4d560721b604482015290519081900360640190fd5b80341461139d576040805162461bcd60e51b815260206004820152600f60248201526e57524f4e475f4554485f56414c554560881b604482015290519081900360640190fd5b6113ac8a8a8a8a8a8a8a611949565b9a9950505050505050505050565b60006113c985858585336108af565b95945050505050565b6003602052600090815260409020546001600160a01b031681565b6002546001600160a01b031681565b6006546001600160a01b031681565b6001600160a01b03821615611454576040805162461bcd60e51b815260206004820152600a6024820152692120a22fa927aaaa22a960b11b604482015290519081900360640190fd5b61145e8383611b9e565b600480546001600160a01b0319166001600160a01b03929092169190911790555050565b3b151590565b600085518751146114cf576040805162461bcd60e51b815260206004820152600c60248201526b0aea49e9c8ebe988a9c8ea8960a31b604482015290519081900360640190fd5b60005b87518110156117d0578681815181106114e757fe5b6020026020010151600360008a84815181106114ff57fe5b60200260200101516001600160a01b03166001600160a01b0316815260200190815260200160002060006101000a8154816001600160a01b0302191690836001600160a01b0316021790555086818151811061155757fe5b60200260200101516001600160a01b031688828151811061157457fe5b60200260200101516001600160a01b03167f812ca95fe4492a9e2d1f2723c2c40c03a60a27b059581ae20ac4e4d73bfba35460405160405180910390a360006001600160a01b03168782815181106115c857fe5b60200260200101516001600160a01b03161415801561160d575060016001600160a01b03168782815181106115f957fe5b60200260200101516001600160a01b031614155b156117c85760006001600160a01b031687828151811061162957fe5b60200260200101516001600160a01b031663a7e28d488a848151811061164b57fe5b60200260200101516040518263ffffffff1660e01b815260040180826001600160a01b03166001600160a01b0316815260200191505060206040518083038186803b15801561169957600080fd5b505afa1580156116ad573d6000803e3d6000fd5b505050506040513d60208110156116c357600080fd5b50516001600160a01b03161415611721576040805162461bcd60e51b815260206004820152601c60248201527f544f4b454e5f4e4f545f48414e444c45445f42595f4741544557415900000000604482015290519081900360640190fd5b86818151811061172d57fe5b60200260200101516001600160a01b0316632db09c1c6040518163ffffffff1660e01b815260040160206040518083038186803b15801561176d57600080fd5b505afa158015611781573d6000803e3d6000fd5b505050506040513d602081101561179757600080fd5b505187518890839081106117a757fe5b60200260200101906001600160a01b031690816001600160a01b0316815250505b6001016114d2565b506060634201f98560e01b8888604051602401808060200180602001838103835285818151815260200191508051906020019060200280838360005b8381101561182457818101518382015260200161180c565b50505050905001838103825284818151815260200191508051906020019060200280838360005b8381101561186357818101518382015260200161184b565b50505050905001945050505050604051602081830303815290604052906001600160e01b0319166020820180516001600160e01b03838183161783525050505090506118f9600660009054906101000a90046001600160a01b0316600160009054906101000a90046001600160a01b03168534600060405180606001604052808b81526020018d81526020018c81525087611905565b98975050505050505050565b60006118f98888888888886000015189602001518a604001518a611c6a565b7fb53127684a568b3173ae13b9f8a6016e243e63b6e8ee1178d6a717850b5d61035490565b6060600061195689611134565b90506060611965338686611e7d565b604080516001600160a01b0385811682529151929350818c169233928e16917f85291dff2161a93c2f12c819d31889c96c63042116f5bc5a205aa701c2c429f5919081900360200190a4816001600160a01b031663d2ce7d65348c8c8c8c8c886040518863ffffffff1660e01b815260040180876001600160a01b03166001600160a01b03168152602001866001600160a01b03166001600160a01b0316815260200185815260200184815260200183815260200180602001828103825283818151815260200191508051906020019080838360005b83811015611a53578181015183820152602001611a3b565b50505050905090810190601f168015611a805780820380516001836020036101000a031916815260200191505b509750505050505050506000604051808303818588803b158015611aa357600080fd5b505af1158015611ab7573d6000803e3d6000fd5b50505050506040513d6000823e601f3d908101601f191682016040526020811015611ae157600080fd5b8101908080516040519392919084600160201b821115611b0057600080fd5b908301906020820185811115611b1557600080fd5b8251600160201b811182820188101715611b2e57600080fd5b82525081516020918201929091019080838360005b83811015611b5b578181015183820152602001611b43565b50505050905090810190601f168015611b885780820380516001836020036101000a031916815260200191505b5060405250505092505050979650505050505050565b6001600160a01b038216611bef576040805162461bcd60e51b81526020600482015260136024820152721253959053125117d0d3d55395115494105495606a1b604482015290519081900360640190fd5b6001546001600160a01b031615611c3c576040805162461bcd60e51b815260206004820152600c60248201526b1053149150511657d253925560a21b604482015290519081900360640190fd5b600180546001600160a01b039384166001600160a01b03199182161790915560028054929093169116179055565b6000808a6001600160a01b031663679b6ded898c8a8a8e8f8c8c8c6040518a63ffffffff1660e01b815260040180896001600160a01b03166001600160a01b03168152602001888152602001878152602001866001600160a01b03166001600160a01b03168152602001856001600160a01b03166001600160a01b0316815260200184815260200183815260200180602001828103825283818151815260200191508051906020019080838360005b83811015611d31578181015183820152602001611d19565b50505050905090810190601f168015611d5e5780820380516001836020036101000a031916815260200191505b5099505050505050505050506020604051808303818588803b158015611d8357600080fd5b505af1158015611d97573d6000803e3d6000fd5b50505050506040513d6020811015611dae57600080fd5b81019080805190602001909291905050509050808a6001600160a01b03168a6001600160a01b03167fc1d1490cf25c3b40d600dfb27c7680340ed1ab901b7e8f3551280968a3b372b0866040518080602001828103825283818151815260200191508051906020019080838360005b83811015611e35578181015183820152602001611e1d565b50505050905090810190601f168015611e625780820380516001836020036101000a031916815260200191505b509250505060405180910390a49a9950505050505050505050565b606083838360405160200180846001600160a01b03166001600160a01b0316815260200180602001828103825284848281815260200192508082843760008184015260408051601f19601f909301831690940184810390920184525250999850505050505050505056fea2646970667358221220d7743bc55082d15fef9972e04a86235c42530952c92b25d83792f50a8a6830f564736f6c634300060b0033", - "deployedBytecode": "0x60806040526004361061011f5760003560e01c806393e59dc1116100a0578063d2ce7d6511610064578063d2ce7d651461066a578063dd61456914610704578063ed08fdc61461073c578063f887ea401461076f578063fb0e722b146107845761011f565b806393e59dc11461048c57806395fcea78146104a1578063a0c76a96146104b6578063a7e28d4814610604578063bda009fe146106375761011f565b80632e567b36116100e75780632e567b361461024857806347466f98146102de5780635625a95214610311578063658b53f4146103495780638da5cb5b146104775761011f565b8063032958021461012457806313af4035146101555780631459457a1461018a5780632d67b72d146101df5780632db09c1c14610233575b600080fd5b34801561013057600080fd5b50610139610799565b604080516001600160a01b039092168252519081900360200190f35b34801561016157600080fd5b506101886004803603602081101561017857600080fd5b50356001600160a01b03166107a8565b005b34801561019657600080fd5b50610188600480360360a08110156101ad57600080fd5b506001600160a01b03813581169160208101358216916040820135811691606081013582169160809091013516610861565b610221600480360360a08110156101f557600080fd5b506001600160a01b038135811691602081013591604082013591606081013591608090910135166108af565b60408051918252519081900360200190f35b34801561023f57600080fd5b50610139610b0c565b610188600480360360a081101561025e57600080fd5b6001600160a01b03823581169260208101358216926040820135909216916060820135919081019060a081016080820135600160201b8111156102a057600080fd5b8201836020820111156102b257600080fd5b803590602001918460018302840111600160201b831117156102d357600080fd5b509092509050610b1b565b3480156102ea57600080fd5b506101886004803603602081101561030157600080fd5b50356001600160a01b0316610b5f565b6102216004803603608081101561032757600080fd5b506001600160a01b038135169060208101359060408101359060600135610c02565b610221600480360360a081101561035f57600080fd5b810190602081018135600160201b81111561037957600080fd5b82018360208201111561038b57600080fd5b803590602001918460208302840111600160201b831117156103ac57600080fd5b9190808060200260200160405190810160405280939291908181526020018383602002808284376000920191909152509295949360208101935035915050600160201b8111156103fb57600080fd5b82018360208201111561040d57600080fd5b803590602001918460208302840111600160201b8311171561042e57600080fd5b9190808060200260200160405190810160405280939291908181526020018383602002808284376000920191909152509295505082359350505060208101359060400135610d9f565b34801561048357600080fd5b50610139610e06565b34801561049857600080fd5b50610139610e15565b3480156104ad57600080fd5b50610188610e24565b3480156104c257600080fd5b5061058f600480360360a08110156104d957600080fd5b6001600160a01b03823581169260208101358216926040820135909216916060820135919081019060a081016080820135600160201b81111561051b57600080fd5b82018360208201111561052d57600080fd5b803590602001918460018302840111600160201b8311171561054e57600080fd5b91908080601f016020809104026020016040519081016040528093929190818152602001838380828437600092019190915250929550610e81945050505050565b6040805160208082528351818301528351919283929083019185019080838360005b838110156105c95781810151838201526020016105b1565b50505050905090810190601f1680156105f65780820380516001836020036101000a031916815260200191505b509250505060405180910390f35b34801561061057600080fd5b506101396004803603602081101561062757600080fd5b50356001600160a01b0316611083565b34801561064357600080fd5b506101396004803603602081101561065a57600080fd5b50356001600160a01b0316611134565b61058f600480360360c081101561068057600080fd5b6001600160a01b0382358116926020810135909116916040820135916060810135916080820135919081019060c0810160a0820135600160201b8111156106c657600080fd5b8201836020820111156106d857600080fd5b803590602001918460018302840111600160201b831117156106f957600080fd5b509092509050611196565b6102216004803603608081101561071a57600080fd5b506001600160a01b0381351690602081013590604081013590606001356113ba565b34801561074857600080fd5b506101396004803603602081101561075f57600080fd5b50356001600160a01b03166113d2565b34801561077b57600080fd5b506101396113ed565b34801561079057600080fd5b506101396113fc565b6004546001600160a01b031681565b6005546001600160a01b031633146107f4576040805162461bcd60e51b815260206004820152600a60248201526927a7262cafa7aba722a960b11b604482015290519081900360640190fd5b6001600160a01b03811661083f576040805162461bcd60e51b815260206004820152600d60248201526c24a72b20a624a22fa7aba722a960991b604482015290519081900360640190fd5b600580546001600160a01b0319166001600160a01b0392909216919091179055565b61086d8260008661140b565b600580546001600160a01b03199081166001600160a01b0397881617909155600080548216948716949094179093556006805490931694169390931790555050565b600061a4b160ff16336001600160a01b0316638e5f5ad16040518163ffffffff1660e01b815260040160206040518083038186803b1580156108f057600080fd5b505afa158015610904573d6000803e3d6000fd5b505050506040513d602081101561091a57600080fd5b505160ff1614610963576040805162461bcd60e51b815260206004820152600f60248201526e1393d517d0549097d1539050931151608a1b604482015290519081900360640190fd5b610975866001600160a01b0316611482565b6109b8576040805162461bcd60e51b815260206004820152600f60248201526e1393d517d513d7d0d3d395149050d5608a1b604482015290519081900360640190fd5b60006109c333611134565b90506001600160a01b038116158015906109eb57506004546001600160a01b03828116911614155b15610a5657866001600160a01b0316816001600160a01b031614610a56576040805162461bcd60e51b815260206004820152601b60248201527f4e4f5f5550444154455f544f5f444946464552454e545f414444520000000000604482015290519081900360640190fd5b604080516001808252818301909252606091602080830190803683370190505090503381600081518110610a8657fe5b6001600160a01b0392909216602092830291909101909101526040805160018082528183019092526060918160200160208202803683370190505090508881600081518110610ad157fe5b60200260200101906001600160a01b031690816001600160a01b031681525050610aff82828a8a8a8a611488565b9998505050505050505050565b6001546001600160a01b031681565b6040805162461bcd60e51b815260206004820152601460248201527327a7262cafa7aaaa2127aaa7222fa927aaaa22a960611b604482015290519081900360640190fd5b6000546001600160a01b03163314610bae576040805162461bcd60e51b815260206004820152600d60248201526c1393d517d19493d357d31254d5609a1b604482015290519081900360640190fd5b600080546001600160a01b0383166001600160a01b0319909116811790915560408051918252517f37389c47920d5cc3229678a0205d0455002c07541a4139ebdce91ac2274657779181900360200190a150565b6005546000906001600160a01b03163314610c51576040805162461bcd60e51b815260206004820152600a60248201526927a7262cafa7aba722a960b11b604482015290519081900360640190fd5b600480546001600160a01b0387166001600160a01b0319909116811790915560408051918252517f3a8f8eb961383a94d41d193e16a3af73eaddfd5764a4c640257323a1603ac3319181900360200190a160006001600160a01b03861615610d1b57856001600160a01b0316632db09c1c6040518163ffffffff1660e01b815260040160206040518083038186803b158015610cec57600080fd5b505afa158015610d00573d6000803e3d6000fd5b505050506040513d6020811015610d1657600080fd5b505190505b604080516001600160a01b038084166024808401919091528351808403909101815260449092018352602082810180516001600160e01b031663f7c9362f60e01b17905260065460015485516060810187528981529283018b90529482018990529293610d949383169216903390349060009087611905565b979650505050505050565b6005546000906001600160a01b03163314610dee576040805162461bcd60e51b815260206004820152600a60248201526927a7262cafa7aba722a960b11b604482015290519081900360640190fd5b610dfc868686868633611488565b9695505050505050565b6005546001600160a01b031681565b6000546001600160a01b031681565b6000610e2e611924565b9050336001600160a01b03821614610e7e576040805162461bcd60e51b815260206004820152600e60248201526d2727aa2fa32927a6afa0a226a4a760911b604482015290519081900360640190fd5b50565b60606000610e8e87611134565b9050806001600160a01b031663a0c76a9688888888886040518663ffffffff1660e01b815260040180866001600160a01b03166001600160a01b03168152602001856001600160a01b03166001600160a01b03168152602001846001600160a01b03166001600160a01b0316815260200183815260200180602001828103825283818151815260200191508051906020019080838360005b83811015610f3e578181015183820152602001610f26565b50505050905090810190601f168015610f6b5780820380516001836020036101000a031916815260200191505b50965050505050505060006040518083038186803b158015610f8c57600080fd5b505afa158015610fa0573d6000803e3d6000fd5b505050506040513d6000823e601f3d908101601f191682016040526020811015610fc957600080fd5b8101908080516040519392919084600160201b821115610fe857600080fd5b908301906020820185811115610ffd57600080fd5b8251600160201b81118282018810171561101657600080fd5b82525081516020918201929091019080838360005b8381101561104357818101518382015260200161102b565b50505050905090810190601f1680156110705780820380516001836020036101000a031916815260200191505b5060405250505091505095945050505050565b60008061108f83611134565b90506001600160a01b0381166110a957600091505061112f565b806001600160a01b031663a7e28d48846040518263ffffffff1660e01b815260040180826001600160a01b03166001600160a01b0316815260200191505060206040518083038186803b1580156110ff57600080fd5b505afa158015611113573d6000803e3d6000fd5b505050506040513d602081101561112957600080fd5b50519150505b919050565b6001600160a01b03808216600090815260036020526040902054168061116257506004546001600160a01b03165b6001600160a01b038116600114806111895750611187816001600160a01b0316611482565b155b1561112f5750600061112f565b6000546060906001600160a01b031615611264576000546040805163babcc53960e01b815233600482015290516001600160a01b039092169163babcc53991602480820192602092909190829003018186803b1580156111f557600080fd5b505afa158015611209573d6000803e3d6000fd5b505050506040513d602081101561121f57600080fd5b5051611264576040805162461bcd60e51b815260206004820152600f60248201526e1393d517d5d2125511531254d51151608a1b604482015290519081900360640190fd5b60008383604081101561127657600080fd5b81359190810190604081016020820135600160201b81111561129757600080fd5b8201836020820111156112a957600080fd5b803590602001918460018302840111600160201b831117156112ca57600080fd5b91908080601f0160208091040260200160405190810160405280939291908181526020018383808284376000920191909152509697505050508989028501935050508215159050611357576040805162461bcd60e51b81526020600482015260126024820152711393d7d4d550935254d4d253d397d0d3d4d560721b604482015290519081900360640190fd5b80341461139d576040805162461bcd60e51b815260206004820152600f60248201526e57524f4e475f4554485f56414c554560881b604482015290519081900360640190fd5b6113ac8a8a8a8a8a8a8a611949565b9a9950505050505050505050565b60006113c985858585336108af565b95945050505050565b6003602052600090815260409020546001600160a01b031681565b6002546001600160a01b031681565b6006546001600160a01b031681565b6001600160a01b03821615611454576040805162461bcd60e51b815260206004820152600a6024820152692120a22fa927aaaa22a960b11b604482015290519081900360640190fd5b61145e8383611b9e565b600480546001600160a01b0319166001600160a01b03929092169190911790555050565b3b151590565b600085518751146114cf576040805162461bcd60e51b815260206004820152600c60248201526b0aea49e9c8ebe988a9c8ea8960a31b604482015290519081900360640190fd5b60005b87518110156117d0578681815181106114e757fe5b6020026020010151600360008a84815181106114ff57fe5b60200260200101516001600160a01b03166001600160a01b0316815260200190815260200160002060006101000a8154816001600160a01b0302191690836001600160a01b0316021790555086818151811061155757fe5b60200260200101516001600160a01b031688828151811061157457fe5b60200260200101516001600160a01b03167f812ca95fe4492a9e2d1f2723c2c40c03a60a27b059581ae20ac4e4d73bfba35460405160405180910390a360006001600160a01b03168782815181106115c857fe5b60200260200101516001600160a01b03161415801561160d575060016001600160a01b03168782815181106115f957fe5b60200260200101516001600160a01b031614155b156117c85760006001600160a01b031687828151811061162957fe5b60200260200101516001600160a01b031663a7e28d488a848151811061164b57fe5b60200260200101516040518263ffffffff1660e01b815260040180826001600160a01b03166001600160a01b0316815260200191505060206040518083038186803b15801561169957600080fd5b505afa1580156116ad573d6000803e3d6000fd5b505050506040513d60208110156116c357600080fd5b50516001600160a01b03161415611721576040805162461bcd60e51b815260206004820152601c60248201527f544f4b454e5f4e4f545f48414e444c45445f42595f4741544557415900000000604482015290519081900360640190fd5b86818151811061172d57fe5b60200260200101516001600160a01b0316632db09c1c6040518163ffffffff1660e01b815260040160206040518083038186803b15801561176d57600080fd5b505afa158015611781573d6000803e3d6000fd5b505050506040513d602081101561179757600080fd5b505187518890839081106117a757fe5b60200260200101906001600160a01b031690816001600160a01b0316815250505b6001016114d2565b506060634201f98560e01b8888604051602401808060200180602001838103835285818151815260200191508051906020019060200280838360005b8381101561182457818101518382015260200161180c565b50505050905001838103825284818151815260200191508051906020019060200280838360005b8381101561186357818101518382015260200161184b565b50505050905001945050505050604051602081830303815290604052906001600160e01b0319166020820180516001600160e01b03838183161783525050505090506118f9600660009054906101000a90046001600160a01b0316600160009054906101000a90046001600160a01b03168534600060405180606001604052808b81526020018d81526020018c81525087611905565b98975050505050505050565b60006118f98888888888886000015189602001518a604001518a611c6a565b7fb53127684a568b3173ae13b9f8a6016e243e63b6e8ee1178d6a717850b5d61035490565b6060600061195689611134565b90506060611965338686611e7d565b604080516001600160a01b0385811682529151929350818c169233928e16917f85291dff2161a93c2f12c819d31889c96c63042116f5bc5a205aa701c2c429f5919081900360200190a4816001600160a01b031663d2ce7d65348c8c8c8c8c886040518863ffffffff1660e01b815260040180876001600160a01b03166001600160a01b03168152602001866001600160a01b03166001600160a01b0316815260200185815260200184815260200183815260200180602001828103825283818151815260200191508051906020019080838360005b83811015611a53578181015183820152602001611a3b565b50505050905090810190601f168015611a805780820380516001836020036101000a031916815260200191505b509750505050505050506000604051808303818588803b158015611aa357600080fd5b505af1158015611ab7573d6000803e3d6000fd5b50505050506040513d6000823e601f3d908101601f191682016040526020811015611ae157600080fd5b8101908080516040519392919084600160201b821115611b0057600080fd5b908301906020820185811115611b1557600080fd5b8251600160201b811182820188101715611b2e57600080fd5b82525081516020918201929091019080838360005b83811015611b5b578181015183820152602001611b43565b50505050905090810190601f168015611b885780820380516001836020036101000a031916815260200191505b5060405250505092505050979650505050505050565b6001600160a01b038216611bef576040805162461bcd60e51b81526020600482015260136024820152721253959053125117d0d3d55395115494105495606a1b604482015290519081900360640190fd5b6001546001600160a01b031615611c3c576040805162461bcd60e51b815260206004820152600c60248201526b1053149150511657d253925560a21b604482015290519081900360640190fd5b600180546001600160a01b039384166001600160a01b03199182161790915560028054929093169116179055565b6000808a6001600160a01b031663679b6ded898c8a8a8e8f8c8c8c6040518a63ffffffff1660e01b815260040180896001600160a01b03166001600160a01b03168152602001888152602001878152602001866001600160a01b03166001600160a01b03168152602001856001600160a01b03166001600160a01b0316815260200184815260200183815260200180602001828103825283818151815260200191508051906020019080838360005b83811015611d31578181015183820152602001611d19565b50505050905090810190601f168015611d5e5780820380516001836020036101000a031916815260200191505b5099505050505050505050506020604051808303818588803b158015611d8357600080fd5b505af1158015611d97573d6000803e3d6000fd5b50505050506040513d6020811015611dae57600080fd5b81019080805190602001909291905050509050808a6001600160a01b03168a6001600160a01b03167fc1d1490cf25c3b40d600dfb27c7680340ed1ab901b7e8f3551280968a3b372b0866040518080602001828103825283818151815260200191508051906020019080838360005b83811015611e35578181015183820152602001611e1d565b50505050905090810190601f168015611e625780820380516001836020036101000a031916815260200191505b509250505060405180910390a49a9950505050505050505050565b606083838360405160200180846001600160a01b03166001600160a01b0316815260200180602001828103825284848281815260200192508082843760008184015260408051601f19601f909301831690940184810390920184525250999850505050505050505056fea2646970667358221220d7743bc55082d15fef9972e04a86235c42530952c92b25d83792f50a8a6830f564736f6c634300060b0033", - "linkReferences": {}, - "deployedLinkReferences": {} - } - \ No newline at end of file diff --git a/utils/arbitrum/artifacts/L2GatewayRouter.json b/utils/arbitrum/artifacts/L2GatewayRouter.json deleted file mode 100644 index 8fd5cce8..00000000 --- a/utils/arbitrum/artifacts/L2GatewayRouter.json +++ /dev/null @@ -1,408 +0,0 @@ -{ - "_format": "hh-sol-artifact-1", - "contractName": "L2GatewayRouter", - "sourceName": "contracts/tokenbridge/arbitrum/gateway/L2GatewayRouter.sol", - "abi": [ - { - "anonymous": false, - "inputs": [ - { - "indexed": false, - "internalType": "address", - "name": "newDefaultGateway", - "type": "address" - } - ], - "name": "DefaultGatewayUpdated", - "type": "event" - }, - { - "anonymous": false, - "inputs": [ - { - "indexed": true, - "internalType": "address", - "name": "l1Token", - "type": "address" - }, - { - "indexed": true, - "internalType": "address", - "name": "gateway", - "type": "address" - } - ], - "name": "GatewaySet", - "type": "event" - }, - { - "anonymous": false, - "inputs": [ - { - "indexed": true, - "internalType": "address", - "name": "token", - "type": "address" - }, - { - "indexed": true, - "internalType": "address", - "name": "_userFrom", - "type": "address" - }, - { - "indexed": true, - "internalType": "address", - "name": "_userTo", - "type": "address" - }, - { - "indexed": false, - "internalType": "address", - "name": "gateway", - "type": "address" - } - ], - "name": "TransferRouted", - "type": "event" - }, - { - "anonymous": false, - "inputs": [ - { - "indexed": true, - "internalType": "address", - "name": "_from", - "type": "address" - }, - { - "indexed": true, - "internalType": "address", - "name": "_to", - "type": "address" - }, - { - "indexed": true, - "internalType": "uint256", - "name": "_id", - "type": "uint256" - }, - { - "indexed": false, - "internalType": "bytes", - "name": "_data", - "type": "bytes" - } - ], - "name": "TxToL1", - "type": "event" - }, - { - "inputs": [ - { - "internalType": "address", - "name": "l1ERC20", - "type": "address" - } - ], - "name": "calculateL2TokenAddress", - "outputs": [ - { - "internalType": "address", - "name": "", - "type": "address" - } - ], - "stateMutability": "view", - "type": "function" - }, - { - "inputs": [], - "name": "counterpartGateway", - "outputs": [ - { - "internalType": "address", - "name": "", - "type": "address" - } - ], - "stateMutability": "view", - "type": "function" - }, - { - "inputs": [], - "name": "defaultGateway", - "outputs": [ - { - "internalType": "address", - "name": "", - "type": "address" - } - ], - "stateMutability": "view", - "type": "function" - }, - { - "inputs": [ - { - "internalType": "address", - "name": "", - "type": "address" - }, - { - "internalType": "address", - "name": "", - "type": "address" - }, - { - "internalType": "address", - "name": "", - "type": "address" - }, - { - "internalType": "uint256", - "name": "", - "type": "uint256" - }, - { - "internalType": "bytes", - "name": "", - "type": "bytes" - } - ], - "name": "finalizeInboundTransfer", - "outputs": [], - "stateMutability": "payable", - "type": "function" - }, - { - "inputs": [ - { - "internalType": "address", - "name": "_token", - "type": "address" - } - ], - "name": "getGateway", - "outputs": [ - { - "internalType": "address", - "name": "gateway", - "type": "address" - } - ], - "stateMutability": "view", - "type": "function" - }, - { - "inputs": [ - { - "internalType": "address", - "name": "_token", - "type": "address" - }, - { - "internalType": "address", - "name": "_from", - "type": "address" - }, - { - "internalType": "address", - "name": "_to", - "type": "address" - }, - { - "internalType": "uint256", - "name": "_amount", - "type": "uint256" - }, - { - "internalType": "bytes", - "name": "_data", - "type": "bytes" - } - ], - "name": "getOutboundCalldata", - "outputs": [ - { - "internalType": "bytes", - "name": "", - "type": "bytes" - } - ], - "stateMutability": "view", - "type": "function" - }, - { - "inputs": [ - { - "internalType": "address", - "name": "_counterpartGateway", - "type": "address" - }, - { - "internalType": "address", - "name": "_defaultGateway", - "type": "address" - } - ], - "name": "initialize", - "outputs": [], - "stateMutability": "nonpayable", - "type": "function" - }, - { - "inputs": [ - { - "internalType": "address", - "name": "", - "type": "address" - } - ], - "name": "l1TokenToGateway", - "outputs": [ - { - "internalType": "address", - "name": "", - "type": "address" - } - ], - "stateMutability": "view", - "type": "function" - }, - { - "inputs": [ - { - "internalType": "address", - "name": "_l1Token", - "type": "address" - }, - { - "internalType": "address", - "name": "_to", - "type": "address" - }, - { - "internalType": "uint256", - "name": "_amount", - "type": "uint256" - }, - { - "internalType": "bytes", - "name": "_data", - "type": "bytes" - } - ], - "name": "outboundTransfer", - "outputs": [ - { - "internalType": "bytes", - "name": "", - "type": "bytes" - } - ], - "stateMutability": "payable", - "type": "function" - }, - { - "inputs": [ - { - "internalType": "address", - "name": "_token", - "type": "address" - }, - { - "internalType": "address", - "name": "_to", - "type": "address" - }, - { - "internalType": "uint256", - "name": "_amount", - "type": "uint256" - }, - { - "internalType": "uint256", - "name": "_maxGas", - "type": "uint256" - }, - { - "internalType": "uint256", - "name": "_gasPriceBid", - "type": "uint256" - }, - { - "internalType": "bytes", - "name": "_data", - "type": "bytes" - } - ], - "name": "outboundTransfer", - "outputs": [ - { - "internalType": "bytes", - "name": "", - "type": "bytes" - } - ], - "stateMutability": "payable", - "type": "function" - }, - { - "inputs": [], - "name": "postUpgradeInit", - "outputs": [], - "stateMutability": "nonpayable", - "type": "function" - }, - { - "inputs": [], - "name": "router", - "outputs": [ - { - "internalType": "address", - "name": "", - "type": "address" - } - ], - "stateMutability": "view", - "type": "function" - }, - { - "inputs": [ - { - "internalType": "address", - "name": "newL2DefaultGateway", - "type": "address" - } - ], - "name": "setDefaultGateway", - "outputs": [], - "stateMutability": "nonpayable", - "type": "function" - }, - { - "inputs": [ - { - "internalType": "address[]", - "name": "_l1Token", - "type": "address[]" - }, - { - "internalType": "address[]", - "name": "_gateway", - "type": "address[]" - } - ], - "name": "setGateway", - "outputs": [], - "stateMutability": "nonpayable", - "type": "function" - } - ], - "bytecode": "0x608060405234801561001057600080fd5b50611161806100206000396000f3fe6080604052600436106100bd5760003560e01c8063a0c76a961161006f578063a0c76a9614610423578063a7e28d48146104fc578063bda009fe1461052f578063d2ce7d6514610562578063ed08fdc6146105fc578063f7c9362f1461062f578063f887ea4014610662576100bd565b806303295802146100c25780632db09c1c146100f35780632e567b36146101085780634201f985146101a0578063485cc955146102d05780637b3a3c8b1461030b57806395fcea781461040e575b600080fd5b3480156100ce57600080fd5b506100d7610677565b604080516001600160a01b039092168252519081900360200190f35b3480156100ff57600080fd5b506100d7610686565b61019e600480360360a081101561011e57600080fd5b6001600160a01b03823581169260208101358216926040820135909216916060820135919081019060a081016080820135600160201b81111561016057600080fd5b82018360208201111561017257600080fd5b803590602001918460018302840111600160201b8311171561019357600080fd5b509092509050610695565b005b3480156101ac57600080fd5b5061019e600480360360408110156101c357600080fd5b810190602081018135600160201b8111156101dd57600080fd5b8201836020820111156101ef57600080fd5b803590602001918460208302840111600160201b8311171561021057600080fd5b9190808060200260200160405190810160405280939291908181526020018383602002808284376000920191909152509295949360208101935035915050600160201b81111561025f57600080fd5b82018360208201111561027157600080fd5b803590602001918460208302840111600160201b8311171561029257600080fd5b9190808060200260200160405190810160405280939291908181526020018383602002808284376000920191909152509295506106d9945050505050565b3480156102dc57600080fd5b5061019e600480360360408110156102f357600080fd5b506001600160a01b0381358116916020013516610853565b6103996004803603608081101561032157600080fd5b6001600160a01b03823581169260208101359091169160408201359190810190608081016060820135600160201b81111561035b57600080fd5b82018360208201111561036d57600080fd5b803590602001918460018302840111600160201b8311171561038e57600080fd5b509092509050610863565b6040805160208082528351818301528351919283929083019185019080838360005b838110156103d35781810151838201526020016103bb565b50505050905090810190601f1680156104005780820380516001836020036101000a031916815260200191505b509250505060405180910390f35b34801561041a57600080fd5b5061019e61087f565b34801561042f57600080fd5b50610399600480360360a081101561044657600080fd5b6001600160a01b03823581169260208101358216926040820135909216916060820135919081019060a081016080820135600160201b81111561048857600080fd5b82018360208201111561049a57600080fd5b803590602001918460018302840111600160201b831117156104bb57600080fd5b91908080601f0160208091040260200160405190810160405280939291908181526020018383808284376000920191909152509295506108dc945050505050565b34801561050857600080fd5b506100d76004803603602081101561051f57600080fd5b50356001600160a01b0316610ade565b34801561053b57600080fd5b506100d76004803603602081101561055257600080fd5b50356001600160a01b0316610b8f565b610399600480360360c081101561057857600080fd5b6001600160a01b0382358116926020810135909116916040820135916060810135916080820135919081019060c0810160a0820135600160201b8111156105be57600080fd5b8201836020820111156105d057600080fd5b803590602001918460018302840111600160201b831117156105f157600080fd5b509092509050610bf1565b34801561060857600080fd5b506100d76004803603602081101561061f57600080fd5b50356001600160a01b0316610e46565b34801561063b57600080fd5b5061019e6004803603602081101561065257600080fd5b50356001600160a01b0316610e61565b34801561066e57600080fd5b506100d7610f35565b6003546001600160a01b031681565b6000546001600160a01b031681565b6040805162461bcd60e51b815260206004820152601460248201527327a7262cafa7aaaa2127aaa7222fa927aaaa22a960611b604482015290519081900360640190fd5b6000546001600160a01b031633148061070d57506000546001600160a01b031661070233610f44565b6001600160a01b0316145b610759576040805162461bcd60e51b81526020600482015260186024820152774f4e4c595f434f554e544552504152545f4741544557415960401b604482015290519081900360640190fd5b805182511461076457fe5b60005b825181101561084e5781818151811061077c57fe5b60200260200101516002600085848151811061079457fe5b60200260200101516001600160a01b03166001600160a01b0316815260200190815260200160002060006101000a8154816001600160a01b0302191690836001600160a01b031602179055508181815181106107ec57fe5b60200260200101516001600160a01b031683828151811061080957fe5b60200260200101516001600160a01b03167f812ca95fe4492a9e2d1f2723c2c40c03a60a27b059581ae20ac4e4d73bfba35460405160405180910390a3600101610767565b505050565b61085f82600083610f53565b5050565b60606108758686866000808888610bf1565b9695505050505050565b6000610889610fca565b9050336001600160a01b038216146108d9576040805162461bcd60e51b815260206004820152600e60248201526d2727aa2fa32927a6afa0a226a4a760911b604482015290519081900360640190fd5b50565b606060006108e987610b8f565b9050806001600160a01b031663a0c76a9688888888886040518663ffffffff1660e01b815260040180866001600160a01b03166001600160a01b03168152602001856001600160a01b03166001600160a01b03168152602001846001600160a01b03166001600160a01b0316815260200183815260200180602001828103825283818151815260200191508051906020019080838360005b83811015610999578181015183820152602001610981565b50505050905090810190601f1680156109c65780820380516001836020036101000a031916815260200191505b50965050505050505060006040518083038186803b1580156109e757600080fd5b505afa1580156109fb573d6000803e3d6000fd5b505050506040513d6000823e601f3d908101601f191682016040526020811015610a2457600080fd5b8101908080516040519392919084600160201b821115610a4357600080fd5b908301906020820185811115610a5857600080fd5b8251600160201b811182820188101715610a7157600080fd5b82525081516020918201929091019080838360005b83811015610a9e578181015183820152602001610a86565b50505050905090810190601f168015610acb5780820380516001836020036101000a031916815260200191505b5060405250505091505095945050505050565b600080610aea83610b8f565b90506001600160a01b038116610b04576000915050610b8a565b806001600160a01b031663a7e28d48846040518263ffffffff1660e01b815260040180826001600160a01b03166001600160a01b0316815260200191505060206040518083038186803b158015610b5a57600080fd5b505afa158015610b6e573d6000803e3d6000fd5b505050506040513d6020811015610b8457600080fd5b50519150505b919050565b6001600160a01b038082166000908152600260205260409020541680610bbd57506003546001600160a01b03165b6001600160a01b03811660011480610be45750610be2816001600160a01b0316610fef565b155b15610b8a57506000610b8a565b60606000610bfe89610b8f565b90506060610c0d338686610ff5565b604080516001600160a01b0385811682529151929350818c169233928e16917f85291dff2161a93c2f12c819d31889c96c63042116f5bc5a205aa701c2c429f5919081900360200190a4816001600160a01b031663d2ce7d65348c8c8c8c8c886040518863ffffffff1660e01b815260040180876001600160a01b03166001600160a01b03168152602001866001600160a01b03166001600160a01b0316815260200185815260200184815260200183815260200180602001828103825283818151815260200191508051906020019080838360005b83811015610cfb578181015183820152602001610ce3565b50505050905090810190601f168015610d285780820380516001836020036101000a031916815260200191505b509750505050505050506000604051808303818588803b158015610d4b57600080fd5b505af1158015610d5f573d6000803e3d6000fd5b50505050506040513d6000823e601f3d908101601f191682016040526020811015610d8957600080fd5b8101908080516040519392919084600160201b821115610da857600080fd5b908301906020820185811115610dbd57600080fd5b8251600160201b811182820188101715610dd657600080fd5b82525081516020918201929091019080838360005b83811015610e03578181015183820152602001610deb565b50505050905090810190601f168015610e305780820380516001836020036101000a031916815260200191505b5060405250505092505050979650505050505050565b6002602052600090815260409020546001600160a01b031681565b6000546001600160a01b0316331480610e9557506000546001600160a01b0316610e8a33610f44565b6001600160a01b0316145b610ee1576040805162461bcd60e51b81526020600482015260186024820152774f4e4c595f434f554e544552504152545f4741544557415960401b604482015290519081900360640190fd5b600380546001600160a01b0383166001600160a01b0319909116811790915560408051918252517f3a8f8eb961383a94d41d193e16a3af73eaddfd5764a4c640257323a1603ac3319181900360200190a150565b6001546001600160a01b031681565b61111061111160901b01190190565b6001600160a01b03821615610f9c576040805162461bcd60e51b815260206004820152600a6024820152692120a22fa927aaaa22a960b11b604482015290519081900360640190fd5b610fa6838361105f565b600380546001600160a01b0319166001600160a01b03929092169190911790555050565b7fb53127684a568b3173ae13b9f8a6016e243e63b6e8ee1178d6a717850b5d61035490565b3b151590565b606083838360405160200180846001600160a01b03166001600160a01b0316815260200180602001828103825284848281815260200192508082843760008184015260408051601f19601f9093018316909401848103909201845252509998505050505050505050565b6001600160a01b0382166110b0576040805162461bcd60e51b81526020600482015260136024820152721253959053125117d0d3d55395115494105495606a1b604482015290519081900360640190fd5b6000546001600160a01b0316156110fd576040805162461bcd60e51b815260206004820152600c60248201526b1053149150511657d253925560a21b604482015290519081900360640190fd5b600080546001600160a01b039384166001600160a01b0319918216179091556001805492909316911617905556fea26469706673582212207198cc8944389c1932c3adbf1316bc74ec7525726bc255b125ee69816950af4464736f6c634300060b0033", - "deployedBytecode": "0x6080604052600436106100bd5760003560e01c8063a0c76a961161006f578063a0c76a9614610423578063a7e28d48146104fc578063bda009fe1461052f578063d2ce7d6514610562578063ed08fdc6146105fc578063f7c9362f1461062f578063f887ea4014610662576100bd565b806303295802146100c25780632db09c1c146100f35780632e567b36146101085780634201f985146101a0578063485cc955146102d05780637b3a3c8b1461030b57806395fcea781461040e575b600080fd5b3480156100ce57600080fd5b506100d7610677565b604080516001600160a01b039092168252519081900360200190f35b3480156100ff57600080fd5b506100d7610686565b61019e600480360360a081101561011e57600080fd5b6001600160a01b03823581169260208101358216926040820135909216916060820135919081019060a081016080820135600160201b81111561016057600080fd5b82018360208201111561017257600080fd5b803590602001918460018302840111600160201b8311171561019357600080fd5b509092509050610695565b005b3480156101ac57600080fd5b5061019e600480360360408110156101c357600080fd5b810190602081018135600160201b8111156101dd57600080fd5b8201836020820111156101ef57600080fd5b803590602001918460208302840111600160201b8311171561021057600080fd5b9190808060200260200160405190810160405280939291908181526020018383602002808284376000920191909152509295949360208101935035915050600160201b81111561025f57600080fd5b82018360208201111561027157600080fd5b803590602001918460208302840111600160201b8311171561029257600080fd5b9190808060200260200160405190810160405280939291908181526020018383602002808284376000920191909152509295506106d9945050505050565b3480156102dc57600080fd5b5061019e600480360360408110156102f357600080fd5b506001600160a01b0381358116916020013516610853565b6103996004803603608081101561032157600080fd5b6001600160a01b03823581169260208101359091169160408201359190810190608081016060820135600160201b81111561035b57600080fd5b82018360208201111561036d57600080fd5b803590602001918460018302840111600160201b8311171561038e57600080fd5b509092509050610863565b6040805160208082528351818301528351919283929083019185019080838360005b838110156103d35781810151838201526020016103bb565b50505050905090810190601f1680156104005780820380516001836020036101000a031916815260200191505b509250505060405180910390f35b34801561041a57600080fd5b5061019e61087f565b34801561042f57600080fd5b50610399600480360360a081101561044657600080fd5b6001600160a01b03823581169260208101358216926040820135909216916060820135919081019060a081016080820135600160201b81111561048857600080fd5b82018360208201111561049a57600080fd5b803590602001918460018302840111600160201b831117156104bb57600080fd5b91908080601f0160208091040260200160405190810160405280939291908181526020018383808284376000920191909152509295506108dc945050505050565b34801561050857600080fd5b506100d76004803603602081101561051f57600080fd5b50356001600160a01b0316610ade565b34801561053b57600080fd5b506100d76004803603602081101561055257600080fd5b50356001600160a01b0316610b8f565b610399600480360360c081101561057857600080fd5b6001600160a01b0382358116926020810135909116916040820135916060810135916080820135919081019060c0810160a0820135600160201b8111156105be57600080fd5b8201836020820111156105d057600080fd5b803590602001918460018302840111600160201b831117156105f157600080fd5b509092509050610bf1565b34801561060857600080fd5b506100d76004803603602081101561061f57600080fd5b50356001600160a01b0316610e46565b34801561063b57600080fd5b5061019e6004803603602081101561065257600080fd5b50356001600160a01b0316610e61565b34801561066e57600080fd5b506100d7610f35565b6003546001600160a01b031681565b6000546001600160a01b031681565b6040805162461bcd60e51b815260206004820152601460248201527327a7262cafa7aaaa2127aaa7222fa927aaaa22a960611b604482015290519081900360640190fd5b6000546001600160a01b031633148061070d57506000546001600160a01b031661070233610f44565b6001600160a01b0316145b610759576040805162461bcd60e51b81526020600482015260186024820152774f4e4c595f434f554e544552504152545f4741544557415960401b604482015290519081900360640190fd5b805182511461076457fe5b60005b825181101561084e5781818151811061077c57fe5b60200260200101516002600085848151811061079457fe5b60200260200101516001600160a01b03166001600160a01b0316815260200190815260200160002060006101000a8154816001600160a01b0302191690836001600160a01b031602179055508181815181106107ec57fe5b60200260200101516001600160a01b031683828151811061080957fe5b60200260200101516001600160a01b03167f812ca95fe4492a9e2d1f2723c2c40c03a60a27b059581ae20ac4e4d73bfba35460405160405180910390a3600101610767565b505050565b61085f82600083610f53565b5050565b60606108758686866000808888610bf1565b9695505050505050565b6000610889610fca565b9050336001600160a01b038216146108d9576040805162461bcd60e51b815260206004820152600e60248201526d2727aa2fa32927a6afa0a226a4a760911b604482015290519081900360640190fd5b50565b606060006108e987610b8f565b9050806001600160a01b031663a0c76a9688888888886040518663ffffffff1660e01b815260040180866001600160a01b03166001600160a01b03168152602001856001600160a01b03166001600160a01b03168152602001846001600160a01b03166001600160a01b0316815260200183815260200180602001828103825283818151815260200191508051906020019080838360005b83811015610999578181015183820152602001610981565b50505050905090810190601f1680156109c65780820380516001836020036101000a031916815260200191505b50965050505050505060006040518083038186803b1580156109e757600080fd5b505afa1580156109fb573d6000803e3d6000fd5b505050506040513d6000823e601f3d908101601f191682016040526020811015610a2457600080fd5b8101908080516040519392919084600160201b821115610a4357600080fd5b908301906020820185811115610a5857600080fd5b8251600160201b811182820188101715610a7157600080fd5b82525081516020918201929091019080838360005b83811015610a9e578181015183820152602001610a86565b50505050905090810190601f168015610acb5780820380516001836020036101000a031916815260200191505b5060405250505091505095945050505050565b600080610aea83610b8f565b90506001600160a01b038116610b04576000915050610b8a565b806001600160a01b031663a7e28d48846040518263ffffffff1660e01b815260040180826001600160a01b03166001600160a01b0316815260200191505060206040518083038186803b158015610b5a57600080fd5b505afa158015610b6e573d6000803e3d6000fd5b505050506040513d6020811015610b8457600080fd5b50519150505b919050565b6001600160a01b038082166000908152600260205260409020541680610bbd57506003546001600160a01b03165b6001600160a01b03811660011480610be45750610be2816001600160a01b0316610fef565b155b15610b8a57506000610b8a565b60606000610bfe89610b8f565b90506060610c0d338686610ff5565b604080516001600160a01b0385811682529151929350818c169233928e16917f85291dff2161a93c2f12c819d31889c96c63042116f5bc5a205aa701c2c429f5919081900360200190a4816001600160a01b031663d2ce7d65348c8c8c8c8c886040518863ffffffff1660e01b815260040180876001600160a01b03166001600160a01b03168152602001866001600160a01b03166001600160a01b0316815260200185815260200184815260200183815260200180602001828103825283818151815260200191508051906020019080838360005b83811015610cfb578181015183820152602001610ce3565b50505050905090810190601f168015610d285780820380516001836020036101000a031916815260200191505b509750505050505050506000604051808303818588803b158015610d4b57600080fd5b505af1158015610d5f573d6000803e3d6000fd5b50505050506040513d6000823e601f3d908101601f191682016040526020811015610d8957600080fd5b8101908080516040519392919084600160201b821115610da857600080fd5b908301906020820185811115610dbd57600080fd5b8251600160201b811182820188101715610dd657600080fd5b82525081516020918201929091019080838360005b83811015610e03578181015183820152602001610deb565b50505050905090810190601f168015610e305780820380516001836020036101000a031916815260200191505b5060405250505092505050979650505050505050565b6002602052600090815260409020546001600160a01b031681565b6000546001600160a01b0316331480610e9557506000546001600160a01b0316610e8a33610f44565b6001600160a01b0316145b610ee1576040805162461bcd60e51b81526020600482015260186024820152774f4e4c595f434f554e544552504152545f4741544557415960401b604482015290519081900360640190fd5b600380546001600160a01b0383166001600160a01b0319909116811790915560408051918252517f3a8f8eb961383a94d41d193e16a3af73eaddfd5764a4c640257323a1603ac3319181900360200190a150565b6001546001600160a01b031681565b61111061111160901b01190190565b6001600160a01b03821615610f9c576040805162461bcd60e51b815260206004820152600a6024820152692120a22fa927aaaa22a960b11b604482015290519081900360640190fd5b610fa6838361105f565b600380546001600160a01b0319166001600160a01b03929092169190911790555050565b7fb53127684a568b3173ae13b9f8a6016e243e63b6e8ee1178d6a717850b5d61035490565b3b151590565b606083838360405160200180846001600160a01b03166001600160a01b0316815260200180602001828103825284848281815260200192508082843760008184015260408051601f19601f9093018316909401848103909201845252509998505050505050505050565b6001600160a01b0382166110b0576040805162461bcd60e51b81526020600482015260136024820152721253959053125117d0d3d55395115494105495606a1b604482015290519081900360640190fd5b6000546001600160a01b0316156110fd576040805162461bcd60e51b815260206004820152600c60248201526b1053149150511657d253925560a21b604482015290519081900360640190fd5b600080546001600160a01b039384166001600160a01b0319918216179091556001805492909316911617905556fea26469706673582212207198cc8944389c1932c3adbf1316bc74ec7525726bc255b125ee69816950af4464736f6c634300060b0033", - "linkReferences": {}, - "deployedLinkReferences": {} - } - \ No newline at end of file diff --git a/utils/arbitrum/contracts.ts b/utils/arbitrum/contracts.ts deleted file mode 100644 index 778e2eb5..00000000 --- a/utils/arbitrum/contracts.ts +++ /dev/null @@ -1,37 +0,0 @@ -import { - L1GatewayRouter__factory, - L2GatewayRouter__factory, - ArbSysStub__factory, - Inbox__factory, -} from "../../typechain/"; -import addresses from "./addresses"; -import { CommonOptions } from "./types"; -import network, { NetworkName } from "../network"; - -interface ContractsOptions extends CommonOptions { - forking: boolean; -} - -export default function contracts( - networkName: NetworkName, - options: ContractsOptions -) { - const [l1Provider, l2Provider] = network - .multichain(["eth", "arb"], networkName) - .getProviders(options); - - const arbAddresses = addresses(networkName, options); - - return { - ArbSysStub: ArbSysStub__factory.connect(arbAddresses.ArbSys, l2Provider), - L1GatewayRouter: L1GatewayRouter__factory.connect( - arbAddresses.L1GatewayRouter, - l1Provider - ), - L2GatewayRouter: L2GatewayRouter__factory.connect( - arbAddresses.L2GatewayRouter, - l2Provider - ), - Inbox: Inbox__factory.connect(arbAddresses.Inbox, l1Provider), - }; -} diff --git a/utils/arbitrum/deployment.ts b/utils/arbitrum/deployment.ts deleted file mode 100644 index 3327cff2..00000000 --- a/utils/arbitrum/deployment.ts +++ /dev/null @@ -1,203 +0,0 @@ -import { assert } from "chai"; -import { ethers } from "hardhat"; -import { Overrides, Wallet } from "ethers"; -import { - ERC20Bridged__factory, - IERC20Metadata__factory, - L1ERC20TokenGateway__factory, - L1GatewayRouter__factory, - L2ERC20TokenGateway__factory, - L2GatewayRouter__factory, - OssifiableProxy__factory, -} from "../../typechain"; - -import addresses from "./addresses"; -import { CommonOptions } from "./types"; -import network, { NetworkName } from "../network"; -import { DeployScript, Logger } from "../deployment/DeployScript"; - -interface ArbL1DeployScriptParams { - deployer: Wallet; - admins: { proxy: string; bridge: string }; -} - -interface ArbL2DeployScriptParams extends ArbL1DeployScriptParams { - l2Token?: { name?: string; symbol?: string }; -} - -interface ArbDeploymentOptions extends CommonOptions { - logger?: Logger; - overrides?: Overrides; -} - -export default function deployment( - networkName: NetworkName, - options: ArbDeploymentOptions = {} -) { - const arbAddresses = addresses(networkName, options); - - return { - async erc20TokenGatewayDeployScript( - l1Token: string, - l1Params: ArbL1DeployScriptParams, - l2Params: ArbL2DeployScriptParams - ) { - const [ - expectedL1TokensGatewayImplAddress, - expectedL1TokensGatewayProxyAddress, - ] = await network.predictAddresses(l1Params.deployer, 2); - - const [ - expectedL2TokenImplAddress, - expectedL2TokenProxyAddress, - expectedL2TokensGatewayImplAddress, - expectedL2TokensGatewayProxyAddress, - ] = await network.predictAddresses(l2Params.deployer, 4); - - const l1DeployScript = new DeployScript( - l1Params.deployer, - options?.logger - ) - .addStep({ - factory: L1ERC20TokenGateway__factory, - args: [ - arbAddresses.Inbox, - arbAddresses.L1GatewayRouter, - expectedL2TokensGatewayProxyAddress, - l1Token, - expectedL2TokenProxyAddress, - options?.overrides, - ], - afterDeploy: (c) => - assert.equal(c.address, expectedL1TokensGatewayImplAddress), - }) - .addStep({ - factory: OssifiableProxy__factory, - args: [ - expectedL1TokensGatewayImplAddress, - l1Params.admins.proxy, - L1ERC20TokenGateway__factory.createInterface().encodeFunctionData( - "initialize", - [l1Params.admins.bridge] - ), - options?.overrides, - ], - afterDeploy: (c) => - assert.equal(c.address, expectedL1TokensGatewayProxyAddress), - }); - - const l1TokenInfo = IERC20Metadata__factory.connect( - l1Token, - l1Params.deployer - ); - - const [decimals, l2TokenName, l2TokenSymbol] = await Promise.all([ - l1TokenInfo.decimals(), - l2Params.l2Token?.name ?? l1TokenInfo.name(), - l2Params.l2Token?.symbol ?? l1TokenInfo.symbol(), - ]); - - const l2DeployScript = new DeployScript( - l2Params.deployer, - options?.logger - ) - .addStep({ - factory: ERC20Bridged__factory, - args: [ - l2TokenName, - l2TokenSymbol, - decimals, - expectedL2TokensGatewayProxyAddress, - options?.overrides, - ], - afterDeploy: (c) => - assert.equal(c.address, expectedL2TokenImplAddress), - }) - .addStep({ - factory: OssifiableProxy__factory, - args: [ - expectedL2TokenImplAddress, - l2Params.admins.proxy, - ERC20Bridged__factory.createInterface().encodeFunctionData( - "initialize", - [l2TokenName, l2TokenSymbol] - ), - options?.overrides, - ], - afterDeploy: (c) => - assert.equal(c.address, expectedL2TokenProxyAddress), - }) - .addStep({ - factory: L2ERC20TokenGateway__factory, - args: [ - arbAddresses.ArbSys, - arbAddresses.L2GatewayRouter, - expectedL1TokensGatewayProxyAddress, - l1Token, - expectedL2TokenProxyAddress, - options?.overrides, - ], - afterDeploy: (c) => - assert.equal(c.address, expectedL2TokensGatewayImplAddress), - }) - .addStep({ - factory: OssifiableProxy__factory, - args: [ - expectedL2TokensGatewayImplAddress, - l2Params.admins.proxy, - L2ERC20TokenGateway__factory.createInterface().encodeFunctionData( - "initialize", - [l2Params.admins.bridge] - ), - options?.overrides, - ], - }); - - return [l1DeployScript, l2DeployScript]; - }, - async gatewayRouterDeployScript(l1Deployer: Wallet, l2Deployer: Wallet) { - const [expectedL1GatewayRouter] = await network.predictAddresses( - l1Deployer, - 1 - ); - const [expectedL2GatewayRouter] = await network.predictAddresses( - l2Deployer, - 1 - ); - - const l1DeployScript = new DeployScript( - l1Deployer, - options?.logger - ).addStep({ - factory: L1GatewayRouter__factory, - args: [options?.overrides], - afterDeploy: async (l1GatewayRouter) => { - assert.equal(l1GatewayRouter.address, expectedL1GatewayRouter); - await l1GatewayRouter.initialize( - l1Deployer.address, - ethers.constants.AddressZero, - ethers.constants.AddressZero, - expectedL2GatewayRouter, - arbAddresses.Inbox - ); - }, - }); - - const l2DeployScript = new DeployScript( - l2Deployer, - options?.logger - ).addStep({ - factory: L2GatewayRouter__factory, - args: [options?.overrides], - afterDeploy: async (l2GatewayRouter) => { - assert.equal(l2GatewayRouter.address, expectedL2GatewayRouter); - await l2GatewayRouter.initialize( - expectedL1GatewayRouter, - ethers.constants.AddressZero - ); - }, - }); - return [l1DeployScript, l2DeployScript]; - }, - }; -} diff --git a/utils/arbitrum/index.ts b/utils/arbitrum/index.ts deleted file mode 100644 index 73d4d32b..00000000 --- a/utils/arbitrum/index.ts +++ /dev/null @@ -1,13 +0,0 @@ -import testing from "./testing"; -import addresses from "./addresses"; -import contracts from "./contracts"; -import deployment from "./deployment"; -import messaging from "./messaging"; - -export default { - testing, - addresses, - contracts, - messaging, - deployment, -}; diff --git a/utils/arbitrum/messaging.ts b/utils/arbitrum/messaging.ts deleted file mode 100644 index 5fb57499..00000000 --- a/utils/arbitrum/messaging.ts +++ /dev/null @@ -1,111 +0,0 @@ -import { L1ToL2MessageGasEstimator, L1TransactionReceipt } from "@arbitrum/sdk"; -import { JsonRpcProvider } from "@ethersproject/providers"; -import { BigNumber, BigNumberish } from "ethers"; -import { hexDataLength } from "ethers/lib/utils"; -import network, { NetworkName } from "../network"; -import contracts from "./contracts"; - -import { CommonOptions } from "./types"; - -interface RetryableTicketOptions extends CommonOptions { - forking: boolean; -} - -interface MessageData { - sender: string; - recipient: string; - calldata: string; - callvalue?: BigNumberish; - refundAddress?: string; -} - -const SUBMISSION_PRICE_MULTIPLIER = 2; - -async function getRetryableTicketSendParams( - ethProvider: JsonRpcProvider, - arbProvider: JsonRpcProvider, - msg: MessageData -) { - const l1ToL2MessageGasEstimator = new L1ToL2MessageGasEstimator(arbProvider); - - const { baseFeePerGas } = await ethProvider.getBlock( - await ethProvider.getBlockNumber() - ); - if (!baseFeePerGas) { - throw new Error( - "Latest block did not contain base fee, ensure provider is connected to a network that supports EIP 1559." - ); - } - - const maxSubmissionCost = await l1ToL2MessageGasEstimator - .estimateSubmissionFee( - ethProvider, - baseFeePerGas, - hexDataLength(msg.calldata) + 4 - ) - .then((submissionPrice) => - submissionPrice.mul(SUBMISSION_PRICE_MULTIPLIER) - ); - - const arbGasPriceBid = await arbProvider.getGasPrice(); - - const maxGas = - await l1ToL2MessageGasEstimator.estimateRetryableTicketGasLimit({ - from: msg.sender, - to: msg.recipient, - l2CallValue: BigNumber.from(msg.callvalue || 0), - excessFeeRefundAddress: msg.refundAddress || msg.sender, - callValueRefundAddress: msg.refundAddress || msg.sender, - data: msg.calldata, - }); - - return { - maxGas, - maxSubmissionCost, - gasPriceBid: arbGasPriceBid, - callvalue: maxSubmissionCost.add(arbGasPriceBid.mul(maxGas)), - }; -} - -export default function messaging( - networkName: NetworkName, - options: RetryableTicketOptions -) { - const [ethProvider, arbProvider] = network - .multichain(["eth", "arb"], networkName) - .getProviders(options); - const arbContracts = contracts(networkName, options); - return { - async waitForL2Message(l1TxHash: string) { - const l1TxReceipt = new L1TransactionReceipt( - await ethProvider.getTransactionReceipt(l1TxHash) - ); - const [message] = await l1TxReceipt.getL1ToL2Messages(arbProvider); - return message.waitForStatus(); - }, - async getRetryableTicketSendParams(msg: MessageData) { - return getRetryableTicketSendParams(ethProvider, arbProvider, msg); - }, - async prepareRetryableTicketTx(msg: MessageData) { - const { maxGas, gasPriceBid, maxSubmissionCost, callvalue } = - await getRetryableTicketSendParams(ethProvider, arbProvider, msg); - - return { - callvalue, - calldata: arbContracts.Inbox.interface.encodeFunctionData( - "createRetryableTicket", - [ - msg.recipient, - BigNumber.from(msg.callvalue || 0), - maxSubmissionCost, - msg.refundAddress || msg.sender, - msg.refundAddress || msg.sender, - maxGas, - gasPriceBid, - msg.calldata, - ] - ), - }; - }, - }; -} diff --git a/utils/arbitrum/testing.ts b/utils/arbitrum/testing.ts deleted file mode 100644 index e34dce4c..00000000 --- a/utils/arbitrum/testing.ts +++ /dev/null @@ -1,337 +0,0 @@ -import { - IERC20__factory, - ERC20BridgedStub__factory, - L1ERC20TokenGateway__factory, - L2ERC20TokenGateway__factory, - ArbSysStub__factory, - ERC20Bridged__factory, -} from "../../typechain"; -import contracts from "./contracts"; -import addresses from "./addresses"; -import deployment from "./deployment"; -import testingUtils from "../testing"; -import { BridgingManagement } from "../bridging-management"; -import network, { NetworkName, SignerOrProvider } from "../network"; -import { JsonRpcProvider } from "@ethersproject/providers"; - -export default function testing(networkName: NetworkName) { - const defaultArbAddresses = addresses(networkName); - const ethArbNetworks = network.multichain(["eth", "arb"], networkName); - - const [ethProviderForking, arbProviderForking] = ethArbNetworks.getProviders({ - forking: true, - }); - - return { - async getAcceptanceTestSetup() { - const gatewayContracts = await loadDeployedGateways( - ethProviderForking, - arbProviderForking - ); - - const { L1GatewayRouter, L2GatewayRouter } = contracts(networkName, { - customAddresses: loadGatewayRouterAddresses(networkName), - forking: true, - }); - - return { - l1Provider: ethProviderForking, - l2Provider: arbProviderForking, - ...gatewayContracts, - l1GatewayRouter: L1GatewayRouter, - l2GatewayRouter: L2GatewayRouter, - }; - }, - async getIntegrationTestSetup() { - const hasDeployedContracts = - testingUtils.env.USE_DEPLOYED_CONTRACTS(false); - - const gatewayContracts = hasDeployedContracts - ? await loadDeployedGateways(ethProviderForking, arbProviderForking) - : await deployTestGateway( - networkName, - ethProviderForking, - arbProviderForking - ); - - const [l1ERC20TokenGatewayAdminAddress] = - await BridgingManagement.getAdmins( - gatewayContracts.l1ERC20TokenGateway - ); - - const [l2ERC20TokenGatewayAdminAddress] = - await BridgingManagement.getAdmins( - gatewayContracts.l2ERC20TokenGateway - ); - - const customGatewayRouterAddresses = hasDeployedContracts - ? loadGatewayRouterAddresses(networkName) - : undefined; - - const { - L1GatewayRouter: l1GatewayRouter, - L2GatewayRouter: l2GatewayRouter, - } = contracts(networkName, { - customAddresses: customGatewayRouterAddresses, - forking: true, - }); - - const l1TokensHolder = hasDeployedContracts - ? await testingUtils.impersonate( - testingUtils.env.L1_TOKENS_HOLDER(), - ethProviderForking - ) - : testingUtils.accounts.deployer(ethProviderForking); - - if (hasDeployedContracts) { - await printLoadedTestConfig( - networkName, - l1TokensHolder, - gatewayContracts, - { l1GatewayRouter, l2GatewayRouter } - ); - } - - // if the L1 bridge admin is a contract, remove it's code to - // make it behave as EOA - await ethProviderForking.send("hardhat_setCode", [ - l1ERC20TokenGatewayAdminAddress, - "0x", - ]); - - // same for the L2 bridge admin - await arbProviderForking.send("hardhat_setCode", [ - l2ERC20TokenGatewayAdminAddress, - "0x", - ]); - - const { ArbSysStub } = contracts(networkName, { forking: true }); - - return { - l1GatewayRouter, - l2GatewayRouter, - l1Provider: ethProviderForking, - l2Provider: arbProviderForking, - l1TokensHolder, - ...gatewayContracts, - arbSysStub: ArbSysStub, - l1ERC20TokenGatewayAdmin: await testingUtils.impersonate( - l1ERC20TokenGatewayAdminAddress, - ethProviderForking - ), - l2ERC20TokenGatewayAdmin: await testingUtils.impersonate( - l2ERC20TokenGatewayAdminAddress, - arbProviderForking - ), - }; - }, - async getE2ETestSetup() { - const testerPrivateKey = testingUtils.env.TESTING_PRIVATE_KEY(); - - const [l1Provider, l2Provider] = ethArbNetworks.getProviders({ - forking: false, - }); - - const [l1Tester, l2Tester] = ethArbNetworks.getSigners(testerPrivateKey, { - forking: false, - }); - - const gatewayContracts = await loadDeployedGateways(l1Tester, l2Tester); - - const { - L1GatewayRouter: l1GatewayRouter, - L2GatewayRouter: l2GatewayRouter, - } = contracts(networkName, { - customAddresses: loadGatewayRouterAddresses(networkName), - forking: true, - }); - - await printLoadedTestConfig(networkName, l1Tester, gatewayContracts, { - l1GatewayRouter, - l2GatewayRouter, - }); - - return { - l1Tester, - l2Tester, - l1Provider, - l2Provider, - l1GatewayRouter, - l2GatewayRouter, - ...gatewayContracts, - }; - }, - async stubArbSysContract() { - const deployer = testingUtils.accounts.deployer(arbProviderForking); - const stub = await new ArbSysStub__factory(deployer).deploy(); - const stubBytecode = await arbProviderForking.send("eth_getCode", [ - stub.address, - ]); - - await arbProviderForking.send("hardhat_setCode", [ - defaultArbAddresses.ArbSys, - stubBytecode, - ]); - }, - }; -} - -async function deployTestGateway( - networkName: NetworkName, - ethProvider: JsonRpcProvider, - arbProvider: JsonRpcProvider -) { - const ethDeployer = testingUtils.accounts.deployer(ethProvider); - const arbDeployer = testingUtils.accounts.deployer(arbProvider); - - const l1Token = await new ERC20BridgedStub__factory(ethDeployer).deploy( - "Test Token", - "TT" - ); - - const [ethDeployScript, arbDeployScript] = await deployment( - networkName - ).erc20TokenGatewayDeployScript( - l1Token.address, - { - deployer: ethDeployer, - admins: { proxy: ethDeployer.address, bridge: ethDeployer.address }, - }, - { - deployer: arbDeployer, - admins: { proxy: arbDeployer.address, bridge: arbDeployer.address }, - } - ); - - await ethDeployScript.run(); - await arbDeployScript.run(); - - const l1ERC20TokenBridgeProxyDeployStepIndex = 1; - const l1BridgingManagement = new BridgingManagement( - ethDeployScript.getContractAddress(l1ERC20TokenBridgeProxyDeployStepIndex), - ethDeployer - ); - - const l2ERC20TokenBridgeProxyDeployStepIndex = 3; - const l2BridgingManagement = new BridgingManagement( - arbDeployScript.getContractAddress(l2ERC20TokenBridgeProxyDeployStepIndex), - arbDeployer - ); - - await l1BridgingManagement.setup({ - bridgeAdmin: ethDeployer.address, - depositsEnabled: true, - withdrawalsEnabled: true, - }); - - await l2BridgingManagement.setup({ - bridgeAdmin: arbDeployer.address, - depositsEnabled: true, - withdrawalsEnabled: true, - }); - - return { - l1Token: l1Token.connect(ethProvider), - ...connectGatewayContracts( - { - l2Token: arbDeployScript.getContractAddress(1), - l1ERC20TokenGateway: ethDeployScript.getContractAddress(1), - l2ERC20TokenGateway: arbDeployScript.getContractAddress(3), - }, - ethProvider, - arbProvider - ), - }; -} - -async function loadDeployedGateways( - l1SignerOrProvider: SignerOrProvider, - l2SignerOrProvider: SignerOrProvider -) { - return { - l1Token: IERC20__factory.connect( - testingUtils.env.ARB_L1_TOKEN(), - l1SignerOrProvider - ), - ...connectGatewayContracts( - { - l2Token: testingUtils.env.ARB_L2_TOKEN(), - l1ERC20TokenGateway: testingUtils.env.ARB_L1_ERC20_TOKEN_GATEWAY(), - l2ERC20TokenGateway: testingUtils.env.ARB_L2_ERC20_TOKEN_GATEWAY(), - }, - l1SignerOrProvider, - l2SignerOrProvider - ), - }; -} - -function connectGatewayContracts( - addresses: { - l2Token: string; - l1ERC20TokenGateway: string; - l2ERC20TokenGateway: string; - }, - l1SignerOrProvider: SignerOrProvider, - l2SignerOrProvider: SignerOrProvider -) { - const l1ERC20TokenGateway = L1ERC20TokenGateway__factory.connect( - addresses.l1ERC20TokenGateway, - l1SignerOrProvider - ); - const l2ERC20TokenGateway = L2ERC20TokenGateway__factory.connect( - addresses.l2ERC20TokenGateway, - l2SignerOrProvider - ); - const l2Token = ERC20Bridged__factory.connect( - addresses.l2Token, - l2SignerOrProvider - ); - return { - l2Token, - l1ERC20TokenGateway, - l2ERC20TokenGateway, - }; -} - -function loadGatewayRouterAddresses(networkName: NetworkName) { - const defaultArbAddresses = addresses(networkName); - return { - L1GatewayRouter: testingUtils.env.ARB_L1_GATEWAY_ROUTER( - defaultArbAddresses.L1GatewayRouter - ), - L2GatewayRouter: testingUtils.env.ARB_L2_GATEWAY_ROUTER( - defaultArbAddresses.L2GatewayRouter - ), - }; -} - -async function printLoadedTestConfig( - networkName: NetworkName, - l1TokensHolder: any, - gatewayContracts: any, - gatewayRouters: any -) { - console.log("Using the deployed contracts for integration tests"); - console.log( - "In case of unexpected fails, please, make sure that you are forking correct Ethereum/Arbitrum networks" - ); - console.log(` Network Name: ${networkName}`); - console.log(` L1 Token: ${gatewayContracts.l1Token.address}`); - console.log(` L2 Token: ${gatewayContracts.l2Token.address}`); - const l1TokensHolderAddress = await l1TokensHolder.getAddress(); - console.log(` L1 Tokens Holder: ${l1TokensHolderAddress}`); - const holderBalance = await gatewayContracts.l1Token.balanceOf( - l1TokensHolderAddress - ); - console.log(` L1 Tokens Holder Balance: ${holderBalance.toString()}`); - console.log( - ` L1 ERC20 Token Gateway: ${gatewayContracts.l1ERC20TokenGateway.address}` - ); - console.log( - ` L2 ERC20 Token Gateway: ${gatewayContracts.l2ERC20TokenGateway.address}` - ); - console.log(` L1 Gateway Router: ${gatewayRouters.l1GatewayRouter.address}`); - console.log( - ` L2 Gateway Routery: ${gatewayRouters.l2GatewayRouter.address}` - ); -} diff --git a/utils/arbitrum/types.ts b/utils/arbitrum/types.ts deleted file mode 100644 index e17ed21b..00000000 --- a/utils/arbitrum/types.ts +++ /dev/null @@ -1,15 +0,0 @@ -export type ArbContractNames = - | "Inbox" - | "ArbSys" - | "Bridge" - | "Outbox" - | "L1GatewayRouter" - | "L2GatewayRouter"; - -export type ArbContractAddresses = Record; - -export type CustomArbContractAddresses = Partial; - -export interface CommonOptions { - customAddresses?: CustomArbContractAddresses; -} From ac8e5843d4b7d089858a36b88826b10c963c4f7b Mon Sep 17 00:00:00 2001 From: Anton Kovalchuk Date: Tue, 16 Apr 2024 22:20:32 +0200 Subject: [PATCH 063/148] remove arbitrum in configs and scripts --- .env.example | 17 +-- .github/workflows/verify-bytecode.yml | 6 -- .solcover.js | 2 +- README.md | 149 ++------------------------ artifacts-arb.json | 28 ----- artifacts-eth.json | 14 --- hardhat.config.ts | 18 ---- package-lock.json | 28 ----- package.json | 11 -- utils/deployment/DeployScript.ts | 2 - utils/network.ts | 17 +-- utils/testing/e2e.ts | 54 ---------- utils/testing/env.ts | 23 ---- 13 files changed, 12 insertions(+), 357 deletions(-) delete mode 100644 artifacts-arb.json diff --git a/.env.example b/.env.example index 2f00c733..fd9f66e3 100644 --- a/.env.example +++ b/.env.example @@ -10,15 +10,11 @@ RPC_ETH_GOERLI= RPC_OPT_MAINNET=https://mainnet.optimism.io RPC_OPT_GOERLI=https://goerli.optimism.io -RPC_ARB_MAINNET=https://arb1.arbitrum.io/rpc -RPC_ARB_GOERLI=https://goerli-rollup.arbitrum.io/rpc - # ############################ # Etherscan # ############################ ETHERSCAN_API_KEY_ETH= -ETHERSCAN_API_KEY_ARB= ETHERSCAN_API_KEY_OPT= # ############################ @@ -38,7 +34,6 @@ FORKING=true # Private key of the deployer account used for deployment process ETH_DEPLOYER_PRIVATE_KEY= OPT_DEPLOYER_PRIVATE_KEY= -ARB_DEPLOYER_PRIVATE_KEY= L1_DEV_MULTISIG= L1_PROXY_ADMIN= @@ -63,14 +58,6 @@ L2_WITHDRAWALS_DISABLERS=[] # Integration Acceptance & E2E Testing # ############################ -TESTING_ARB_NETWORK= -TESTING_ARB_L1_TOKEN=0x7AEE39c46f20135114e85A03C02aB4FE73fB8127 -TESTING_ARB_L2_TOKEN=0x775ede8029C117effce283b3391E420EacF3c85F -TESTING_ARB_L1_ERC20_TOKEN_GATEWAY=0x0A7e12b563Ba623646a31a09F0182e8aD45D6cfD -TESTING_ARB_L2_ERC20_TOKEN_GATEWAY=0x8c269989D839eE9DaEe64D57C8c41404DF87F722 -TESTING_ARB_L1_GATEWAY_ROUTER=0xa2a8F940752aDc4A3278B63B96d56D72D2b075B1 -TESTING_ARB_L2_GATEWAY_ROUTER=0x57f54f87C44d816f60b92864e23b8c0897D4d81D - TESTING_OPT_NETWORK= TESTING_OPT_L1_TOKEN=0xaF8a2F0aE374b03376155BF745A3421Dac711C12 TESTING_OPT_L2_TOKEN=0xAED5F9aaF167923D34174b8E636aaF040A11f6F7 @@ -84,7 +71,6 @@ TESTING_OPT_L2_ERC20_TOKEN_BRIDGE=0x447CD1794d209Ac4E6B4097B34658bc00C4d0a51 TESTING_USE_DEPLOYED_CONTRACTS=false TESTING_L1_TOKENS_HOLDER= -TESTING_ARB_GOV_BRIDGE_EXECUTOR= TESTING_OPT_GOV_BRIDGE_EXECUTOR= # ############################ @@ -92,5 +78,4 @@ TESTING_OPT_GOV_BRIDGE_EXECUTOR= # ############################ TESTING_PRIVATE_KEY= -TESTING_OPT_LDO_HOLDER_PRIVATE_KEY= -TESTING_ARB_LDO_HOLDER_PRIVATE_KEY= +TESTING_OPT_LDO_HOLDER_PRIVATE_KEY= \ No newline at end of file diff --git a/.github/workflows/verify-bytecode.yml b/.github/workflows/verify-bytecode.yml index a9185fd4..55630fdd 100644 --- a/.github/workflows/verify-bytecode.yml +++ b/.github/workflows/verify-bytecode.yml @@ -49,9 +49,3 @@ jobs: file: artifacts-opt.json rpcUrl: ${{ secrets.OPTIMISM_RPC }} - - name: Verify bytecode of contracts on Arbitrum chain - uses: lidofinance/action-verify-bytecode@master - if: always() - with: - file: artifacts-arb.json - rpcUrl: ${{ secrets.ARBITRUM_RPC }} diff --git a/.solcover.js b/.solcover.js index 7554c821..108d686a 100644 --- a/.solcover.js +++ b/.solcover.js @@ -1,3 +1,3 @@ module.exports = { - skipFiles: ["stubs", "optimism/stubs", "proxy/stubs", "arbitrum/stubs"], + skipFiles: ["stubs", "optimism/stubs", "proxy/stubs"], }; diff --git a/README.md b/README.md index 5f9ca478..8d13cf99 100644 --- a/README.md +++ b/README.md @@ -1,10 +1,9 @@ # Lido L2 -This project contains the implementations of the L2 ERC20 token bridges for Arbitrum and Optimism chains. The current solution allows transferring ERC20 tokens between L1 and L2 chains. +This project contains the implementations of the L2 ERC20 token bridge for Optimism chain. The current solution allows transferring ERC20 tokens between L1 and L2 chains. To retrieve more detailed info about the bridging process, see the specifications for certain chains: -- [Lido's Arbitrum Gateway](https://github.com/lidofinance/lido-l2/blob/main/contracts/arbitrum/README.md). - [Lido's Optimism Bridge](https://github.com/lidofinance/lido-l2/blob/main/contracts/optimism/README.md). ## Project setup @@ -46,8 +45,7 @@ The configuration of the deployment scripts happens via the ENV variables. The f - [`NETWORK`](#NETWORK) - name of the network environments used by deployment scripts. Allowed values: `mainnet`, `goerli`. - [`FORKING`](#FORKING) - run deployment in the forking network instead of real ones - [`ETH_DEPLOYER_PRIVATE_KEY`](#ETH_DEPLOYER_PRIVATE_KEY) - The private key of the deployer account in the Ethereum network is used during the deployment process. -- [`ARB_DEPLOYER_PRIVATE_KEY`](#ARB_DEPLOYER_PRIVATE_KEY) - The private key of the deployer account in the Arbitrum network is used during the deployment process. -- [`OPT_DEPLOYER_PRIVATE_KEY`](#ARB_DEPLOYER_PRIVATE_KEY) - The private key of the deployer account in the Optimism network is used during the deployment process. +- [`OPT_DEPLOYER_PRIVATE_KEY`](#OPT_DEPLOYER_PRIVATE_KEY) - The private key of the deployer account in the Optimism network is used during the deployment process. - [`L1_PROXY_ADMIN`](#L1_PROXY_ADMIN) - The address to grant admin rights of the `OssifiableProxy` on the L1 bridge - [`L1_BRIDGE_ADMIN`](#L1_BRIDGE_ADMIN) - Address to grant the `DEFAULT_ADMIN_ROLE` on the L1 bridge - [`L2_PROXY_ADMIN`](#L2_PROXY_ADMIN) - The address to grant admin rights of the `OssifiableProxy` on the L2 bridge @@ -68,14 +66,6 @@ The following ENV variables are optional and might be used to make an additional - [`L2_WITHDRAWALS_ENABLERS`](#L2_WITHDRAWALS_ENABLES) - array of addresses to grant `WITHDRAWALS_ENABLER_ROLE` on the L2 bridge. - [`L2_WITHDRAWALS_DISABLERS`](#L2_WITHDRAWALS_DISABLERS) - array of addresses to grant `WITHDRAWALS_DISABLER_ROLE` on the L2 bridge. -### Deploying Arbitrum gateway - -To run the deployment of the ERC20 token gateway for the Ethereum <-> Arbitrum chains use the following command: - -```bash -npm run arbitrum:deploy -``` - ### Deploying Optimism bridge To run the deployment of the ERC20 token gateway for the Ethereum <-> Optimism chains use the following command: @@ -92,40 +82,30 @@ To run unit tests use one of the following commands: ```bash -# Run tests for both Arbitrum and Optimism bridges +# Run tests for Optimism bridge npm run test:unit -# Run tests only for Arbitrum gateway -npm run arbitrum:test:unit - # Run tests only for Optimism bridge npm run optimism:test:unit ``` ### Integration tests -Before running integration tests, run the hardhat forked nodes in the standalone tabs corresponding to `TESTING_ARB_NETWORK` \ `TESTING_OPT_NETWORK` env variable or if it's not set use `mainnet` network. Example of the commands for the `mainnet` network: +Before running integration tests, run the hardhat forked nodes in the standalone tabs corresponding to `TESTING_OPT_NETWORK` env variable or if it's not set use `mainnet` network. Example of the commands for the `mainnet` network: ```bash -# Required to run both Arbitrum and Optimism integraton tests +# Required to run Optimism integraton tests npm run fork:eth:mainnet # Required to run Optimism integration tests npm run fork:opt:mainnet -# Required to run Arbitrum integration tests -npm run fork:arb:mainnet -``` - The integration tests might be run via the following commands: ```bash -# Run integration tests for both Arbitrum and Optimism bridges +# Run integration tests for Optimism bridge npm run test:integration -# Run integration tests for Arbitrum bridge -npm run arbitrum:test:integration - # Run integration tests for Optimism bridge npm run optimism:test:integration ``` @@ -138,17 +118,6 @@ TESTING_USE_DEPLOYED_CONTRACTS=true # Address of the account which has tokens to test TESTING_L1_TOKENS_HOLDER= -# Addresses of the Arbitrum gateway -TESTING_ARB_NETWORK= -TESTING_ARB_L1_TOKEN= -TESTING_ARB_L2_TOKEN= -TESTING_ARB_L1_ERC20_TOKEN_GATEWAY= -TESTING_ARB_L2_ERC20_TOKEN_GATEWAY= - -# Below addresses are not required when the gateway uses the default Arbitrum router -TESTING_ARB_L1_GATEWAY_ROUTER= -TESTING_ARB_L2_GATEWAY_ROUTER= - # Addresses of the Optimism bridge TESTING_OPT_NETWORK= TESTING_OPT_L1_TOKEN= @@ -163,17 +132,13 @@ E2E tests run on the real contracts deployed on the testnet networks. To run suc [`TESTING_PRIVATE_KEY`](#TESTING_PRIVATE_KEY) [`TESTING_OPT_LDO_HOLDER_PRIVATE_KEY`](#TESTING_OPT_LDO_HOLDER_PRIVATE_KEY) -[`TESTING_ARB_LDO_HOLDER_PRIVATE_KEY`](#TESTING_ARB_LDO_HOLDER_PRIVATE_KEY) To run E2E tests use the following commands: ```bash -# Run E2E tests for both Arbitrum and Optimism bridges +# Run E2E tests for Optimism bridge npm run test:e2e -# Run E2E tests for Arbitrum bridge -npm run arbitrum:test:e2e - # Run E2E tests for Optimism bridge npm run optimism:test:e2e ``` @@ -184,17 +149,6 @@ Additionally, tests might be run on the deployed contracts. To do it, set the fo # private key of the tester. It must have tokens for testing TESTING_PRIVATE_KEY= -# Addresses of the Arbitrum gateway -TESTING_ARB_NETWORK= -TESTING_ARB_L1_TOKEN= -TESTING_ARB_L2_TOKEN= -TESTING_ARB_L1_ERC20_TOKEN_GATEWAY= -TESTING_ARB_L2_ERC20_TOKEN_GATEWAY= - -# Below addresses are not required when the gateway uses the default Arbitrum router -TESTING_ARB_L1_GATEWAY_ROUTER= -TESTING_ARB_L2_GATEWAY_ROUTER= - # Addresses of the Optimism bridge TESTING_OPT_NETWORK= TESTING_OPT_L1_TOKEN= @@ -210,16 +164,6 @@ The acceptance tests might be run after the deployment to validate that the brid The following ENV variables must be set before the tests running: ```bash -# Addresses of the Arbitrum gateway -TESTING_ARB_L1_TOKEN= -TESTING_ARB_L2_TOKEN= -TESTING_ARB_L1_ERC20_TOKEN_GATEWAY= -TESTING_ARB_L2_ERC20_TOKEN_GATEWAY= - -# Below addresses are not required when the gateway uses the default Arbitrum router -TESTING_ARB_L1_GATEWAY_ROUTER= -TESTING_ARB_L2_GATEWAY_ROUTER= - # Addresses of the Optimism bridge TESTING_OPT_L1_TOKEN= TESTING_OPT_L2_TOKEN= @@ -232,9 +176,6 @@ To run the acceptance tests, use the following commands: ```bash # Optimism bridge npm run optimism:test:acceptance - -# Arbitrum bridge -npm run arbitrum:test:acceptance ``` ## Code Coverage @@ -259,28 +200,10 @@ Address of the RPC node for **Mainnet** Ethereum network. Address of the RPC node for **Goerli** Ethereum network. -#### `RPC_ARB_MAINNET` - -> **Warning** -> -> Please, don't use the default value for production deployments! The default RPC node might not be available or fail suddenly during the request. - -Address of the RPC node for **Mainnet** Arbitrum network. - -> Default value: `https://arb1.arbitrum.io/rpc` - -#### `RPC_ARB_GOERLI` - -Address of the RPC node for **Goerli** Arbitrum network. - -> Default value: `https://goerli-rollup.arbitrum.io/rpc` - #### `RPC_OPT_GOERLI` Address of the RPC node for **Goerli** Optimism network. -> Default value: `https://goerli-rollup.arbitrum.io/rpc` - #### `RPC_OPT_MAINNET` > **Warning** @@ -300,9 +223,6 @@ Below variables are required for successfull verification of the contracts on bl API key from the [Etherscan](https://etherscan.io/) block explorer. See details here: https://info.etherscan.com/api-keys/ -#### `ETHERSCAN_API_KEY_ARB` - -API key from the [Arbiscan](https://arbiscan.io/) block explorer. #### `ETHERSCAN_API_KEY_OPT` @@ -310,7 +230,7 @@ API key from the [Optimistic Ethereum](https://optimistic.etherscan.io/) block e ### Bridge/Gateway Deployment -Below variables used in the Arbitrum/Optimism bridge deployment process. +Below variables used in the Optimism bridge deployment process. #### `TOKEN` @@ -332,10 +252,6 @@ Run deployment in the forking network instead of public ones The private key of the deployer account in the Ethereum network is used during the deployment process. -#### `ARB_DEPLOYER_PRIVATE_KEY` - -The private key of the deployer account in the Arbitrum network is used during the deployment process. - #### `OPT_DEPLOYER_PRIVATE_KEY` The private key of the deployer account in the Optimism network is used during the deployment process. @@ -440,46 +356,6 @@ The array of addresses to grant `WITHDRAWALS_DISABLER_ROLE` on L2 bridge/gateway The following variables are used in the process of the Integration & E2E testing. -#### `TESTING_ARB_NETWORK` - -Name of the network environments used for Arbitrum Integration & E2E testing. Might be one of: `mainnet`, `goerli`. - -#### `TESTING_ARB_L1_TOKEN` - -Address of the token to use in the Acceptance Integration & E2E (when `TESTING_USE_DEPLOYED_CONTRACTS` is set to true) testing of the bridging between Ethereum and Arbitrum networks. - -> Default value: `0x7AEE39c46f20135114e85A03C02aB4FE73fB8127` - -#### `TESTING_ARB_L2_TOKEN` - -Address of the token minted on L2 in the Acceptance Integration & E2E (when `TESTING_USE_DEPLOYED_CONTRACTS` is set to true) testing of the bridging between Ethereum and Arbitrum networks. - -> Default value: `0x775ede8029C117effce283b3391E420EacF3c85F` - -#### `TESTING_ARB_L1_ERC20_TOKEN_GATEWAY` - -Address of the L1 ERC20 token gateway used in the Acceptance Integration & E2E (when `TESTING_USE_DEPLOYED_CONTRACTS` is set to true) testing of the bridging between Ethereum and Arbitrum networks. - -> Default value: `0x0A7e12b563Ba623646a31a09F0182e8aD45D6cfD` - -#### `TESTING_ARB_L2_ERC20_TOKEN_GATEWAY` - -Address of the L2 ERC20 token gateway used in the Acceptance Integration & E2E (when `TESTING_USE_DEPLOYED_CONTRACTS` is set to true) testing of the bridging between Ethereum and Arbitrum networks. - -> Default value: `0x8c269989D839eE9DaEe64D57C8c41404DF87F722` - -#### `TESTING_ARB_L1_GATEWAY_ROUTER` - -Address of the L1 gateway router used in the Acceptance Integration & E2E (when `TESTING_USE_DEPLOYED_CONTRACTS` is set to true) testing of the bridging between Ethereum and Arbitrum networks. If not set, will be used default Arbitrum's L1 Gateway Router - -> Default value: `0xa2a8F940752aDc4A3278B63B96d56D72D2b075B1` - -#### `TESTING_ARB_L2_GATEWAY_ROUTER` - -Address of the L2 gateway router used in the Acceptance Integration & E2E (when `TESTING_USE_DEPLOYED_CONTRACTS` is set to true) testing of the bridging between Ethereum and Arbitrum networks. If not set, will be used default Arbitrum's L2 Gateway Router - -> Default value: `0x57f54f87C44d816f60b92864e23b8c0897D4d81D` - #### `TESTING_OPT_NETWORK` Name of the network environments used for Optimism Integration & E2E testing. Might be one of: `mainnet`, `goerli`. @@ -518,10 +394,6 @@ When set to `true` integration tests will use addresses of deployed contracts se When `TESTING_USE_DEPLOYED_CONTRACTS` is set to true, this address will be used as the holder of the tokens, bridged between L1 and L2. -#### `TESTING_ARB_GOV_BRIDGE_EXECUTOR` - -Address of the deployed Governance Bridge Executor in the Arbitrum network. If set, this contract will be used for integration tests of Governance Bridge. - #### `TESTING_OPT_GOV_BRIDGE_EXECUTOR` Address of the deployed Governance Bridge Executor in the Optimism network. If set, this contract will be used for integration tests of Governance Bridge. @@ -532,7 +404,7 @@ Address of the deployed Governance Bridge Executor in the Optimism network. If s The private key from the address which holds: -- Goerli and Arbitrum/Optimistic Goerli Ether to launch Arbitrum/Optimism E2E tests +- Goerli and Optimistic Goerli Ether to launch Optimism E2E tests The test Ether might be retrived via [Paradigm Faucet](https://faucet.paradigm.xyz/). @@ -540,6 +412,3 @@ The test Ether might be retrived via [Paradigm Faucet](https://faucet.paradigm.x The private key from the address which holds 50+% TLDO -#### `TESTING_ARB_LDO_HOLDER_PRIVATE_KEY` - -The private key from the address which holds 50+% TLDO diff --git a/artifacts-arb.json b/artifacts-arb.json deleted file mode 100644 index d27f07ba..00000000 --- a/artifacts-arb.json +++ /dev/null @@ -1,28 +0,0 @@ -[ - { - "artifactPath": "artifacts/contracts/proxy/OssifiableProxy.sol/OssifiableProxy.json", - "sourcePath": "contracts/proxy/OssifiableProxy.sol", - "name": "L2ERC20TokenGateway proxy", - "address": "0x07D4692291B9E30E326fd31706f686f83f331B82" - }, - { - "artifactPath": "artifacts/contracts/arbitrum/L2ERC20TokenGateway.sol/L2ERC20TokenGateway.json", - "sourcePath": "contracts/arbitrum/L2ERC20TokenGateway.sol", - "name": "L2ERC20TokenGateway", - "address": "0xe75886DE20dF66827e321EfdB88726e6Baa4b0A7", - "txHash": "0xe99cd25baf61300dcf57466bead148dac28fd975076593a8874bbdc64973696d" - }, - { - "artifactPath": "artifacts/contracts/proxy/OssifiableProxy.sol/OssifiableProxy.json", - "sourcePath": "contracts/proxy/OssifiableProxy.sol", - "name": "WstETH ERC20Bridged proxy", - "address": "0x5979D7b546E38E414F7E9822514be443A4800529" - }, - { - "artifactPath": "artifacts/contracts/token/ERC20Bridged.sol/ERC20Bridged.json", - "sourcePath": "contracts/token/ERC20Bridged.sol", - "name": "WstETH ERC20Bridged", - "address": "0x0fBcbaEA96Ce0cF7Ee00A8c19c3ab6f5Dc8E1921", - "txHash": "0x517ea5c206473d2116324939ab42f93ca1079a6388c0db41684e926e36e1a411" - } -] diff --git a/artifacts-eth.json b/artifacts-eth.json index 24dd4b91..c6f6ab47 100644 --- a/artifacts-eth.json +++ b/artifacts-eth.json @@ -1,18 +1,4 @@ [ - { - "artifactPath": "artifacts/contracts/proxy/OssifiableProxy.sol/OssifiableProxy.json", - "sourcePath": "contracts/proxy/OssifiableProxy.sol", - "name": "L1ERC20TokenGateway proxy", - "address": "0x0F25c1DC2a9922304f2eac71DCa9B07E310e8E5a", - "txHash": "0x912fc5992f5a24c2ffe5e230ac51fcc4724cb3e4a23535b04eec34f99f77e3a8" - }, - { - "artifactPath": "artifacts/contracts/arbitrum/L1ERC20TokenGateway.sol/L1ERC20TokenGateway.json", - "sourcePath": "contracts/arbitrum/L1ERC20TokenGateway.sol", - "name": "L1ERC20TokenGateway", - "address": "0xc4E3ff0b5B106f88Fc64c43031BE8b076ee9F21C", - "txHash": "0xce43376cd317471806c6ac2a8efbd5dc6dcfb7201ba643e447f446e50354edcf" - }, { "artifactPath": "artifacts/contracts/proxy/OssifiableProxy.sol/OssifiableProxy.json", "sourcePath": "contracts/proxy/OssifiableProxy.sol", diff --git a/hardhat.config.ts b/hardhat.config.ts index ed47221c..119bab69 100644 --- a/hardhat.config.ts +++ b/hardhat.config.ts @@ -52,22 +52,6 @@ const config: HardhatUserConfig = { url: "http://localhost:8545", }, - // Arbitrum Public Chains - arb_mainnet: { - url: env.string("RPC_ARB_MAINNET", ""), - }, - arb_sepolia: { - url: env.string("RPC_ARB_SEPOLIA", ""), - }, - - // Arbitrum Fork Chains - arb_mainnet_fork: { - url: "http://localhost:8546", - }, - arb_sepolia_fork: { - url: "http://localhost:8546", - }, - // Optimism Public Chains opt_mainnet: { url: env.string("RPC_OPT_MAINNET", ""), @@ -92,7 +76,6 @@ const config: HardhatUserConfig = { apiKey: { mainnet: env.string("ETHERSCAN_API_KEY_ETH", ""), sepolia: env.string("ETHERSCAN_API_KEY_ETH", ""), - arbitrumOne: env.string("ETHERSCAN_API_KEY_ARB", ""), optimisticEthereum: env.string("ETHERSCAN_API_KEY_OPT", ""), "opt_sepolia": env.string("ETHERSCAN_API_KEY_OPT", ""), }, @@ -120,7 +103,6 @@ const config: HardhatUserConfig = { externalArtifacts: [ "./interfaces/**/*.json", "./utils/optimism/artifacts/*.json", - "./utils/arbitrum/artifacts/*.json", ], }, mocha: { diff --git a/package-lock.json b/package-lock.json index cfddd6b4..f0518138 100644 --- a/package-lock.json +++ b/package-lock.json @@ -9,7 +9,6 @@ "version": "1.0.0", "license": "ISC", "dependencies": { - "@arbitrum/sdk": "3.1.6", "@eth-optimism/sdk": "3.2.0", "@ethersproject/providers": "^5.6.8", "@lidofinance/evm-script-decoder": "^0.2.2", @@ -58,22 +57,6 @@ "node": ">=0.10.0" } }, - "node_modules/@arbitrum/sdk": { - "version": "3.1.6", - "resolved": "https://registry.npmjs.org/@arbitrum/sdk/-/sdk-3.1.6.tgz", - "integrity": "sha512-wY7RHmvY26yc/OuwpJY+kjgAmUJZDGDXaQxfSQTp2t6sSFO+8oFFVsKIthBCY2RpDGFUVTGpRjCUEXiuJ6/SFA==", - "dependencies": { - "@ethersproject/address": "^5.0.8", - "@ethersproject/bignumber": "^5.1.1", - "@ethersproject/bytes": "^5.0.8", - "ethers": "^5.1.0" - }, - "engines": { - "node": ">=v11", - "npm": "please-use-yarn", - "yarn": ">= 1.0.0" - } - }, "node_modules/@babel/code-frame": { "version": "7.12.11", "resolved": "https://registry.npmjs.org/@babel/code-frame/-/code-frame-7.12.11.tgz", @@ -24823,17 +24806,6 @@ "integrity": "sha512-1Yjs2SvM8TflER/OD3cOjhWWOZb58A2t7wpE2S9XfBYTiIl+XFhQG2bjy4Pu1I+EAlCNUzRDYDdFwFYUKvXcIA==", "dev": true }, - "@arbitrum/sdk": { - "version": "3.1.6", - "resolved": "https://registry.npmjs.org/@arbitrum/sdk/-/sdk-3.1.6.tgz", - "integrity": "sha512-wY7RHmvY26yc/OuwpJY+kjgAmUJZDGDXaQxfSQTp2t6sSFO+8oFFVsKIthBCY2RpDGFUVTGpRjCUEXiuJ6/SFA==", - "requires": { - "@ethersproject/address": "^5.0.8", - "@ethersproject/bignumber": "^5.1.1", - "@ethersproject/bytes": "^5.0.8", - "ethers": "^5.1.0" - } - }, "@babel/code-frame": { "version": "7.12.11", "resolved": "https://registry.npmjs.org/@babel/code-frame/-/code-frame-7.12.11.tgz", diff --git a/package.json b/package.json index 820a38ee..0207de90 100644 --- a/package.json +++ b/package.json @@ -12,18 +12,8 @@ "test:integration": "hardhat test ./test/**/*.integration.test.ts", "fork:eth:mainnet": "hardhat node:fork eth_mainnet 8545", "fork:eth:sepolia": "hardhat node:fork eth_sepolia 8545", - "fork:arb:sepolia": "hardhat node:fork arb_sepolia 8546", - "fork:arb:mainnet": "hardhat node:fork arb_mainnet 8546", "fork:opt:sepolia": "hardhat node:fork opt_sepolia 9545", "fork:opt:mainnet": "hardhat node:fork opt_mainnet 9545", - "arbitrum:deploy": "ts-node --files ./scripts/arbitrum/deploy-gateway.ts", - "arbitrum:finalize-message": "ts-node --files ./scripts/arbitrum/finalize-message.ts", - "arbitrum:test:e2e": "hardhat test ./test/arbitrum/*.e2e.test.ts", - "arbitrum:test:unit": "hardhat test ./test/arbitrum/*.unit.test.ts", - "arbitrum:test:integration": "hardhat test ./test/arbitrum/*.integration.test.ts", - "arbitrum:test:acceptance": "hardhat test ./test/arbitrum/*.acceptance.test.ts", - "arbitrum:test:executor": "hardhat test ./test/bridge-executor/arbitrum.integration.test.ts", - "arbitrum:test:launch": "REVERT=false hardhat test ./test/arbitrum/{_launch.test.ts,bridging.integration.test.ts}", "optimism:deploy": "ts-node --files ./scripts/optimism/deploy-bridge.ts", "optimism:finalize-message": "ts-node --files ./scripts/optimism/finalize-message.ts", "optimism:test:e2e": "hardhat test ./test/optimism/*.e2e.test.ts", @@ -69,7 +59,6 @@ "typescript": "^4.6.3" }, "dependencies": { - "@arbitrum/sdk": "3.1.6", "@eth-optimism/sdk": "3.2.0", "@ethersproject/providers": "^5.6.8", "@lidofinance/evm-script-decoder": "^0.2.2", diff --git a/utils/deployment/DeployScript.ts b/utils/deployment/DeployScript.ts index a6adb1d9..3e9f7b8c 100644 --- a/utils/deployment/DeployScript.ts +++ b/utils/deployment/DeployScript.ts @@ -170,8 +170,6 @@ export class DeployScript { 10: "opt_mainnet", 11155420: "opt_sepolia", 31337: "hardhat", - 42161: "arb_mainnet", - 421613: "arb_sepolia", }; const networkName = networkNameByChainId[chainId] || ""; const arsString = stepInfo.args.map((a) => `"${a.value}"`).join(" "); diff --git a/utils/network.ts b/utils/network.ts index 1564aa27..30603cda 100644 --- a/utils/network.ts +++ b/utils/network.ts @@ -6,7 +6,7 @@ import { HardhatRuntimeEnvironment, HttpNetworkConfig } from "hardhat/types"; import env from "./env"; -type ChainNameShort = "arb" | "opt" | "eth"; +type ChainNameShort = "opt" | "eth"; export type NetworkName = "sepolia" | "mainnet"; export type SignerOrProvider = Signer | Provider; @@ -15,10 +15,6 @@ const HARDHAT_NETWORK_NAMES = { sepolia: "eth_sepolia", mainnet: "eth_mainnet", }, - arb: { - sepolia: "arb_sepolia", - mainnet: "arb_mainnet", - }, opt: { sepolia: "opt_sepolia", mainnet: "opt_mainnet", @@ -30,10 +26,6 @@ const HARDHAT_NETWORK_NAMES_FORK = { sepolia: "eth_sepolia_fork", mainnet: "eth_mainnet_fork", }, - arb: { - sepolia: "arb_sepolia_fork", - mainnet: "arb_mainnet_fork", - }, opt: { sepolia: "opt_sepolia_fork", mainnet: "opt_mainnet_fork", @@ -125,10 +117,6 @@ function getChainId(protocol: ChainNameShort, networkName: NetworkName) { mainnet: 10, sepolia: 11155420, }, - arb: { - mainnet: 42161, - sepolia: 421613, - }, }; const chainId = chainIds[protocol][networkName]; if (!chainId) { @@ -142,9 +130,6 @@ function getBlockExplorerBaseUrlByChainId(chainId: number) { // ethereum 1: "https://etherscan.io", 11155111: "https://sepolia.etherscan.io", - // arbitrum - 42161: "https://arbiscan.io", - 421613: "https://sepolia-rollup-explorer.arbitrum.io", // optimism 10: "https://optimistic.etherscan.io", 11155420: "https://blockscout.com/optimism/sepolia", diff --git a/utils/testing/e2e.ts b/utils/testing/e2e.ts index 010596e7..1c60955c 100644 --- a/utils/testing/e2e.ts +++ b/utils/testing/e2e.ts @@ -21,25 +21,6 @@ export const E2E_TEST_CONTRACTS_OPTIMISM = { }, }; -export const E2E_TEST_CONTRACTS_ARBITRUM = { - l1: { - l1Token: "0x7AEE39c46f20135114e85A03C02aB4FE73fB8127", - l1GatewayRouter: "0xa2a8F940752aDc4A3278B63B96d56D72D2b075B1", - l1ERC20TokenGateway: "0x46b10f1E65f19876F50bfdD59C9B39E9De6B9150", - aragonVoting: "0x04F9590D3EEC8e619D7714ffeF664aD3fd53b880", - tokenManager: "0x1ee7e87486f9ae6e27a5e58310a5319394360cf0", - agent: "0x12869c3349f993c5c20bab9482b7d16aff0ae2f9", - l1LDOToken: "0x84b4c77b260910fc02dddac41ef0e45e658b18af", - inbox: "0x578BAde599406A8fE3d24Fd7f7211c0911F5B29e", - }, - l2: { - l2Token: "0x57FA50b80f79b9140fe7249A93D432d9fa8C4192", - l2GatewayRouter: "0x57f54f87C44d816f60b92864e23b8c0897D4d81D", - l2ERC20TokenGateway: "0xD06491e4C8B3107B83dC134894C4c96ED8ddbfa2", - govBridgeExecutor: "0x4e8CC9024Ea3FE886623025fF2aD0CA4bb3D1F42", - }, -}; - export const createOptimismVoting = async ( ctx: any, executorCalldata: string @@ -84,40 +65,5 @@ export const encodeEVMScript = ( ); }; -export const createArbitrumVoting = async ( - ctx: any, - executorCalldata: string, - options: Record = {} -) => { - const messageCalldata = await ctx.inbox.interface.encodeFunctionData( - "createRetryableTicket", - [ - ctx.govBridgeExecutor.address, - 0, - options.maxSubmissionCost || wei`0.01 ether`, - ctx.l2Tester.address, - ctx.l2Tester.address, - options.maxGas || 3000000, - options.gasPriceBid || 5000000000, - executorCalldata, - ] - ); - - const agentCalldata = ctx.agent.interface.encodeFunctionData("execute", [ - ctx.inbox.address, - options.callValue || wei`0.01 ether`, - messageCalldata, - ]); - const agentEvmScript = encodeEVMScript(ctx.agent.address, agentCalldata); - - const newVoteCalldata = - "0xd5db2c80" + - abiCoder.encode(["bytes", "string"], [agentEvmScript, ""]).substring(2); - const votingEvmScript = encodeEVMScript(ctx.voting.address, newVoteCalldata); - const newVotingTx = await ctx.tokenMnanager.forward(votingEvmScript); - - await newVotingTx.wait(); -}; - export const sleep = (ms: number) => new Promise((resolve) => setTimeout(resolve, ms)); diff --git a/utils/testing/env.ts b/utils/testing/env.ts index faf8acd6..7d78ab5b 100644 --- a/utils/testing/env.ts +++ b/utils/testing/env.ts @@ -4,29 +4,6 @@ export default { USE_DEPLOYED_CONTRACTS(defaultValue: boolean = false) { return env.bool("TESTING_USE_DEPLOYED_CONTRACTS", defaultValue); }, - - ARB_L1_TOKEN() { - return env.address("TESTING_ARB_L1_TOKEN"); - }, - ARB_L2_TOKEN() { - return env.address("TESTING_ARB_L2_TOKEN"); - }, - ARB_L1_ERC20_TOKEN_GATEWAY() { - return env.address("TESTING_ARB_L1_ERC20_TOKEN_GATEWAY"); - }, - ARB_L2_ERC20_TOKEN_GATEWAY() { - return env.address("TESTING_ARB_L2_ERC20_TOKEN_GATEWAY"); - }, - ARB_L1_GATEWAY_ROUTER(defaultValue?: string) { - return env.address("TESTING_ARB_L1_GATEWAY_ROUTER", defaultValue); - }, - ARB_L2_GATEWAY_ROUTER(defaultValue?: string) { - return env.address("TESTING_ARB_L2_GATEWAY_ROUTER", defaultValue); - }, - ARB_GOV_BRIDGE_EXECUTOR() { - return env.address("TESTING_ARB_GOV_BRIDGE_EXECUTOR"); - }, - OPT_L1_TOKEN() { return env.address("TESTING_OPT_L1_TOKEN"); }, From f387de67cec527b2dd3075c4a847c3e14d060859 Mon Sep 17 00:00:00 2001 From: Anton Kovalchuk Date: Tue, 16 Apr 2024 23:20:33 +0200 Subject: [PATCH 064/148] PR fixes: rename tokens, add sanity check to oracle update, add unit tests, fix comments --- .../optimism/L1ERC20ExtendedTokensBridge.sol | 12 +- .../optimism/L2ERC20ExtendedTokensBridge.sol | 24 +- contracts/optimism/TokenRateOracle.sol | 19 +- .../IChainlinkAggregatorInterface.sol | 4 +- contracts/token/ERC20BridgedPermit.sol | 7 +- ...ebasable.sol => ERC20RebasableBridged.sol} | 26 +- ...it.sol => ERC20RebasableBridgedPermit.sol} | 17 +- contracts/token/PermitExtension.sol | 2 +- test/optimism/L1LidoTokensBridge.unit.test.ts | 92 ++++++- .../L2ERC20ExtendedTokensBridge.unit.test.ts | 254 +++++++++++++++++- test/optimism/TokenRateOracle.unit.test.ts | 91 +++++-- test/token/ERC20Permit.unit.test.ts | 14 +- test/token/ERC20Rebasable.unit.test.ts | 77 +++--- utils/optimism/deploymentAllFromScratch.ts | 6 +- .../deploymentBridgesAndRebasableToken.ts | 6 +- .../optimism/deploymentNewImplementations.ts | 6 +- utils/optimism/testing.ts | 4 +- 17 files changed, 519 insertions(+), 142 deletions(-) rename contracts/token/{ERC20Rebasable.sol => ERC20RebasableBridged.sol} (94%) rename contracts/token/{ERC20RebasablePermit.sol => ERC20RebasableBridgedPermit.sol} (64%) diff --git a/contracts/optimism/L1ERC20ExtendedTokensBridge.sol b/contracts/optimism/L1ERC20ExtendedTokensBridge.sol index bb67010c..8bdaf2d5 100644 --- a/contracts/optimism/L1ERC20ExtendedTokensBridge.sol +++ b/contracts/optimism/L1ERC20ExtendedTokensBridge.sol @@ -119,7 +119,7 @@ abstract contract L1ERC20ExtendedTokensBridge is onlyFromCrossDomainAccount(L2_TOKEN_BRIDGE) onlySupportedL1L2TokensPair(l1Token_, l2Token_) { - uint256 amountToWithdraw = _isRebasable(l1Token_) ? + uint256 amountToWithdraw = (_isRebasable(l1Token_) && amount_ != 0) ? IERC20Wrapper(L1_TOKEN_NON_REBASABLE).unwrap(amount_) : amount_; IERC20(l1Token_).safeTransfer(to_, amountToWithdraw); @@ -134,9 +134,8 @@ abstract contract L1ERC20ExtendedTokensBridge is /// @param to_ Account to give the deposit to on L2 /// @param amount_ Amount of the ERC20 to deposit. /// @param l2Gas_ Gas limit required to complete the deposit on L2. - /// @param encodedDepositData_ Optional data to forward to L2. This data is provided - /// solely as a convenience for external contracts. Aside from enforcing a maximum - /// length, these contracts provide no guarantees about its content. + /// @param encodedDepositData_ a concatenation of packed token rate with L1 time and + /// optional data passed by external contract function _depositERC20To( address l1Token_, address l2Token_, @@ -156,6 +155,11 @@ abstract contract L1ERC20ExtendedTokensBridge is sendCrossDomainMessage(L2_TOKEN_BRIDGE, l2Gas_, message); } + /// @dev Transfers tokens to the bridge and wraps if needed. + /// @param l1Token_ Address of the L1 ERC20 we are depositing. + /// @param from_ Account to pull the deposit from on L1. + /// @param amount_ Amount of the ERC20 to deposit. + /// @return Amount of non-rebasable token. function _transferToBridge( address l1Token_, address from_, diff --git a/contracts/optimism/L2ERC20ExtendedTokensBridge.sol b/contracts/optimism/L2ERC20ExtendedTokensBridge.sol index 38a6c834..ccba2bdd 100644 --- a/contracts/optimism/L2ERC20ExtendedTokensBridge.sol +++ b/contracts/optimism/L2ERC20ExtendedTokensBridge.sol @@ -11,7 +11,7 @@ import {IL2ERC20Bridge} from "./interfaces/IL2ERC20Bridge.sol"; import {IERC20Bridged} from "../token/ERC20Bridged.sol"; import {ITokenRateUpdatable} from "../optimism/interfaces/ITokenRateUpdatable.sol"; import {IERC20Wrapper} from "../token/interfaces/IERC20Wrapper.sol"; -import {ERC20Rebasable} from "../token/ERC20Rebasable.sol"; +import {ERC20RebasableBridged} from "../token/ERC20RebasableBridged.sol"; import {BridgingManager} from "../BridgingManager.sol"; import {RebasableAndNonRebasableTokens} from "./RebasableAndNonRebasableTokens.sol"; import {CrossDomainEnabled} from "./CrossDomainEnabled.sol"; @@ -107,7 +107,7 @@ contract L2ERC20ExtendedTokensBridge is onlyFromCrossDomainAccount(L1_TOKEN_BRIDGE) { DepositDataCodec.DepositData memory depositData = DepositDataCodec.decodeDepositData(data_); - ITokenRateUpdatable tokenRateOracle = ERC20Rebasable(L2_TOKEN_REBASABLE).TOKEN_RATE_ORACLE(); + ITokenRateUpdatable tokenRateOracle = ERC20RebasableBridged(L2_TOKEN_REBASABLE).TOKEN_RATE_ORACLE(); tokenRateOracle.updateRate(depositData.rate, depositData.timestamp); uint256 depositedAmount = _mintTokens(l1Token_, l2Token_, to_, amount_); @@ -116,6 +116,7 @@ contract L2ERC20ExtendedTokensBridge is /// @notice Performs the logic for withdrawals by burning the token and informing /// the L1 token Gateway of the withdrawal + /// @param l2Token_ Address of L2 token where withdrawal was initiated. /// @param from_ Account to pull the withdrawal from on L2 /// @param to_ Account to give the withdrawal to on L1 /// @param amount_ Amount of the token to withdraw @@ -140,6 +141,12 @@ contract L2ERC20ExtendedTokensBridge is sendCrossDomainMessage(L1_TOKEN_BRIDGE, l1Gas_, message); } + /// @dev Mints tokens. + /// @param l1Token_ Address of L1 token for which deposit is finalizing. + /// @param l2Token_ Address of L2 token for which deposit is finalizing. + /// @param to_ Account that token mints for. + /// @param amount_ Amount of token or shares to mint. + /// @return returns amount of minted tokens. function _mintTokens( address l1Token_, address l2Token_, @@ -147,22 +154,27 @@ contract L2ERC20ExtendedTokensBridge is uint256 amount_ ) internal returns (uint256) { if(_isRebasable(l1Token_)) { - ERC20Rebasable(l2Token_).bridgeMintShares(to_, amount_); - return ERC20Rebasable(l2Token_).getTokensByShares(amount_); + ERC20RebasableBridged(l2Token_).bridgeMintShares(to_, amount_); + return ERC20RebasableBridged(l2Token_).getTokensByShares(amount_); } IERC20Bridged(l2Token_).bridgeMint(to_, amount_); return amount_; } + /// @dev Burns tokens + /// @param l2Token_ Address of L2 token where withdrawal was initiated. + /// @param from_ Account which tokens are burns. + /// @param amount_ Amount of token to burn. + /// @return returns amount of non-rebasable token to withdraw. function _burnTokens( address l2Token_, address from_, uint256 amount_ ) internal returns (uint256) { if(_isRebasable(l2Token_)) { - uint256 shares = ERC20Rebasable(l2Token_).getSharesByTokens(amount_); - ERC20Rebasable(l2Token_).bridgeBurnShares(from_, shares); + uint256 shares = ERC20RebasableBridged(l2Token_).getSharesByTokens(amount_); + ERC20RebasableBridged(l2Token_).bridgeBurnShares(from_, shares); return shares; } diff --git a/contracts/optimism/TokenRateOracle.sol b/contracts/optimism/TokenRateOracle.sol index beeef21a..164807b4 100644 --- a/contracts/optimism/TokenRateOracle.sol +++ b/contracts/optimism/TokenRateOracle.sol @@ -25,6 +25,10 @@ contract TokenRateOracle is CrossDomainEnabled, ITokenRateOracle { /// @notice Decimals of the oracle response. uint8 public constant DECIMALS = 18; + uint256 public constant MIN_TOKEN_RATE = 1_000_000_000_000_000; // 0.001 + + uint256 public constant MAX_TOKEN_RATE = 1_000_000_000_000_000_000_000; // 1000 + /// @notice wstETH/stETH token rate. uint256 public tokenRate; @@ -83,7 +87,16 @@ contract TokenRateOracle is CrossDomainEnabled, ITokenRateOracle { } if (rateL1Timestamp_ < rateL1Timestamp) { - revert ErrorIncorrectRateTimestamp(); + emit NewTokenRateOutdated(tokenRate_, rateL1Timestamp, rateL1Timestamp_); + return; + } + + if (rateL1Timestamp_ > block.timestamp) { + revert ErrorL1TimestampInFuture(tokenRate_, rateL1Timestamp_); + } + + if (tokenRate_ < MIN_TOKEN_RATE || tokenRate_ > MAX_TOKEN_RATE) { + revert ErrorTokenRateIsOutOfRange(tokenRate_, rateL1Timestamp_); } if (tokenRate_ == tokenRate && rateL1Timestamp_ == rateL1Timestamp) { @@ -109,7 +122,9 @@ contract TokenRateOracle is CrossDomainEnabled, ITokenRateOracle { } event RateUpdated(uint256 tokenRate_, uint256 rateL1Timestamp_); + event NewTokenRateOutdated(uint256 tokenRate_, uint256 rateL1Timestamp_, uint256 newTateL1Timestamp_); error ErrorNoRights(address caller); - error ErrorIncorrectRateTimestamp(); + error ErrorL1TimestampInFuture(uint256 tokenRate_, uint256 rateL1Timestamp_); + error ErrorTokenRateIsOutOfRange(uint256 tokenRate_, uint256 rateL1Timestamp_); } diff --git a/contracts/optimism/interfaces/IChainlinkAggregatorInterface.sol b/contracts/optimism/interfaces/IChainlinkAggregatorInterface.sol index e9a1e624..03332adf 100644 --- a/contracts/optimism/interfaces/IChainlinkAggregatorInterface.sol +++ b/contracts/optimism/interfaces/IChainlinkAggregatorInterface.sol @@ -8,7 +8,7 @@ pragma solidity 0.8.10; interface IChainlinkAggregatorInterface { /// @notice get the latest token rate data. /// @return roundId_ is a unique id for each answer. The value is based on timestamp. - /// @return answer_ is wstETH/stETH token rate. + /// @return answer_ is wstETH/stETH token rate. It is a chainlink convention to return int256. /// @return startedAt_ is time when rate was pushed on L1 side. /// @return updatedAt_ is the same as startedAt_. /// @return answeredInRound_ is the same as roundId_. @@ -24,7 +24,7 @@ interface IChainlinkAggregatorInterface { ); /// @notice get the lastest token rate. - /// @return wstETH/stETH token rate. + /// @return wstETH/stETH token rate. It is a chainlink convention to return int256. function latestAnswer() external view returns (int256); /// @notice represents the number of decimals the oracle responses represent. diff --git a/contracts/token/ERC20BridgedPermit.sol b/contracts/token/ERC20BridgedPermit.sol index c6341000..d7eca099 100644 --- a/contracts/token/ERC20BridgedPermit.sol +++ b/contracts/token/ERC20BridgedPermit.sol @@ -26,11 +26,8 @@ contract ERC20BridgedPermit is ERC20Bridged, PermitExtension { { } - function _permitAccepted( - address owner_, - address spender_, - uint256 amount_ - ) internal override { + /// @inheritdoc PermitExtension + function _permitAccepted(address owner_, address spender_, uint256 amount_) internal override { _approve(owner_, spender_, amount_); } } diff --git a/contracts/token/ERC20Rebasable.sol b/contracts/token/ERC20RebasableBridged.sol similarity index 94% rename from contracts/token/ERC20Rebasable.sol rename to contracts/token/ERC20RebasableBridged.sol index b8660ded..4de21b66 100644 --- a/contracts/token/ERC20Rebasable.sol +++ b/contracts/token/ERC20RebasableBridged.sol @@ -4,6 +4,7 @@ pragma solidity 0.8.10; import {IERC20} from "@openzeppelin/contracts/token/ERC20/IERC20.sol"; +import {SafeERC20} from "@openzeppelin/contracts/token/ERC20/utils/SafeERC20.sol"; import {IERC20Wrapper} from "./interfaces/IERC20Wrapper.sol"; import {ITokenRateOracle} from "../optimism/TokenRateOracle.sol"; import {ERC20Metadata} from "./ERC20Metadata.sol"; @@ -29,8 +30,8 @@ interface IERC20BridgedShares is IERC20 { /// @author kovalgek /// @notice Rebasable token that wraps/unwraps non-rebasable token and allow to mint/burn tokens by bridge. -contract ERC20Rebasable is IERC20, IERC20Wrapper, IERC20BridgedShares, ERC20Metadata { - +contract ERC20RebasableBridged is IERC20, IERC20Wrapper, IERC20BridgedShares, ERC20Metadata { + using SafeERC20 for IERC20; using UnstructuredRefStorage for bytes32; using UnstructuredStorage for bytes32; @@ -44,29 +45,29 @@ contract ERC20Rebasable is IERC20, IERC20Wrapper, IERC20BridgedShares, ERC20Meta ITokenRateOracle public immutable TOKEN_RATE_ORACLE; /// @dev token allowance slot position. - bytes32 internal constant TOKEN_ALLOWANCE_POSITION = keccak256("ERC20Rebasable.TOKEN_ALLOWANCE_POSITION"); + bytes32 internal constant TOKEN_ALLOWANCE_POSITION = keccak256("ERC20RebasableBridged.TOKEN_ALLOWANCE_POSITION"); /// @dev user shares slot position. - bytes32 internal constant SHARES_POSITION = keccak256("ERC20Rebasable.SHARES_POSITION"); + bytes32 internal constant SHARES_POSITION = keccak256("ERC20RebasableBridged.SHARES_POSITION"); /// @dev token shares slot position. - bytes32 internal constant TOTAL_SHARES_POSITION = keccak256("ERC20Rebasable.TOTAL_SHARES_POSITION"); + bytes32 internal constant TOTAL_SHARES_POSITION = keccak256("ERC20RebasableBridged.TOTAL_SHARES_POSITION"); /// @param name_ The name of the token /// @param symbol_ The symbol of the token /// @param decimals_ The decimals places of the token - /// @param wrappedToken_ address of the ERC20 token to wrap + /// @param tokenToWrapFrom_ address of the ERC20 token to wrap /// @param tokenRateOracle_ address of oracle that returns tokens rate - /// @param l2ERC20TokenBridge_ The bridge address which allowd to mint/burn tokens + /// @param l2ERC20TokenBridge_ The bridge address which allows to mint/burn tokens constructor( string memory name_, string memory symbol_, uint8 decimals_, - address wrappedToken_, + address tokenToWrapFrom_, address tokenRateOracle_, address l2ERC20TokenBridge_ ) ERC20Metadata(name_, symbol_, decimals_) { - TOKEN_TO_WRAP_FROM = IERC20(wrappedToken_); + TOKEN_TO_WRAP_FROM = IERC20(tokenToWrapFrom_); TOKEN_RATE_ORACLE = ITokenRateOracle(tokenRateOracle_); L2_ERC20_TOKEN_BRIDGE = l2ERC20TokenBridge_; } @@ -84,7 +85,7 @@ contract ERC20Rebasable is IERC20, IERC20Wrapper, IERC20BridgedShares, ERC20Meta if (sharesAmount_ == 0) revert ErrorZeroSharesWrap(); _mintShares(msg.sender, sharesAmount_); - if(!TOKEN_TO_WRAP_FROM.transferFrom(msg.sender, address(this), sharesAmount_)) revert ErrorERC20Transfer(); + TOKEN_TO_WRAP_FROM.safeTransferFrom(msg.sender, address(this), sharesAmount_); return _getTokensByShares(sharesAmount_); } @@ -95,7 +96,7 @@ contract ERC20Rebasable is IERC20, IERC20Wrapper, IERC20BridgedShares, ERC20Meta uint256 sharesAmount = _getSharesByTokens(tokenAmount_); _burnShares(msg.sender, sharesAmount); - if(!TOKEN_TO_WRAP_FROM.transfer(msg.sender, sharesAmount)) revert ErrorERC20Transfer(); + TOKEN_TO_WRAP_FROM.safeTransfer(msg.sender, sharesAmount); return sharesAmount; } @@ -274,7 +275,6 @@ contract ERC20Rebasable is IERC20, IERC20Wrapper, IERC20BridgedShares, ERC20Meta ) = TOKEN_RATE_ORACLE.latestRoundData(); if (updatedAt == 0) revert ErrorWrongOracleUpdateTime(); - if (answer <= 0) revert ErrorOracleAnswerIsNotPositive(); return (uint256(answer), uint256(rateDecimals)); } @@ -364,12 +364,10 @@ contract ERC20Rebasable is IERC20, IERC20Wrapper, IERC20BridgedShares, ERC20Meta error ErrorZeroTokensUnwrap(); error ErrorTokenRateDecimalsIsZero(); error ErrorWrongOracleUpdateTime(); - error ErrorOracleAnswerIsNotPositive(); error ErrorTrasferToRebasableContract(); error ErrorNotEnoughBalance(); error ErrorNotEnoughAllowance(); error ErrorAccountIsZeroAddress(); error ErrorDecreasedAllowanceBelowZero(); error ErrorNotBridge(); - error ErrorERC20Transfer(); } diff --git a/contracts/token/ERC20RebasablePermit.sol b/contracts/token/ERC20RebasableBridgedPermit.sol similarity index 64% rename from contracts/token/ERC20RebasablePermit.sol rename to contracts/token/ERC20RebasableBridgedPermit.sol index 03c4fb65..6b9be86d 100644 --- a/contracts/token/ERC20RebasablePermit.sol +++ b/contracts/token/ERC20RebasableBridgedPermit.sol @@ -3,17 +3,17 @@ pragma solidity 0.8.10; -import {ERC20Rebasable} from "./ERC20Rebasable.sol"; +import {ERC20RebasableBridged} from "./ERC20RebasableBridged.sol"; import {PermitExtension} from "./PermitExtension.sol"; /// @author kovalgek -contract ERC20RebasablePermit is ERC20Rebasable, PermitExtension { +contract ERC20RebasableBridgedPermit is ERC20RebasableBridged, PermitExtension { /// @param name_ The name of the token /// @param symbol_ The symbol of the token /// @param version_ The current major version of the signing domain (aka token version) /// @param decimals_ The decimals places of the token - /// @param wrappedToken_ address of the ERC20 token to wrap + /// @param tokenToWrapFrom_ address of the ERC20 token to wrap /// @param tokenRateOracle_ address of oracle that returns tokens rate /// @param bridge_ The bridge address which allowd to mint/burn tokens constructor( @@ -21,20 +21,17 @@ contract ERC20RebasablePermit is ERC20Rebasable, PermitExtension { string memory symbol_, string memory version_, uint8 decimals_, - address wrappedToken_, + address tokenToWrapFrom_, address tokenRateOracle_, address bridge_ ) - ERC20Rebasable(name_, symbol_, decimals_, wrappedToken_, tokenRateOracle_, bridge_) + ERC20RebasableBridged(name_, symbol_, decimals_, tokenToWrapFrom_, tokenRateOracle_, bridge_) PermitExtension(name_, version_) { } - function _permitAccepted( - address owner_, - address spender_, - uint256 amount_ - ) internal override { + /// @inheritdoc PermitExtension + function _permitAccepted(address owner_, address spender_, uint256 amount_) internal override { _approve(owner_, spender_, amount_); } } diff --git a/contracts/token/PermitExtension.sol b/contracts/token/PermitExtension.sol index ed68d6c3..40cd3617 100644 --- a/contracts/token/PermitExtension.sol +++ b/contracts/token/PermitExtension.sol @@ -89,7 +89,7 @@ abstract contract PermitExtension is IERC2612, EIP712 { noncesByAddress[_owner] = current + 1; } - /// @dev is used to override in inherited contracts and call approve function + /// @dev Override this function in the inherited contract to invoke the approve() function of ERC20. function _permitAccepted(address owner_, address spender_, uint256 amount_) internal virtual; error ErrorInvalidSignature(); diff --git a/test/optimism/L1LidoTokensBridge.unit.test.ts b/test/optimism/L1LidoTokensBridge.unit.test.ts index 15642120..df1aaae0 100644 --- a/test/optimism/L1LidoTokensBridge.unit.test.ts +++ b/test/optimism/L1LidoTokensBridge.unit.test.ts @@ -804,10 +804,9 @@ unit("Optimism :: L1LidoTokensBridge", ctxFactory) ), "ErrorWrongCrossDomainSender()" ); - } - ) + }) - .test("finalizeERC20Withdrawal() :: non rebasable token flow", async (ctx) => { + .test("finalizeERC20Withdrawal() :: non-rebasable token flow", async (ctx) => { const { l1TokenBridge, stubs: { l1TokenNonRebasable, l2TokenNonRebasable, l1Messenger }, @@ -816,10 +815,9 @@ unit("Optimism :: L1LidoTokensBridge", ctxFactory) await l1Messenger.setXDomainMessageSender(l2TokenBridgeEOA.address); - const bridgeBalanceBefore = await l1TokenNonRebasable.balanceOf(l1TokenBridge.address); - const amount = wei`1 ether`; const data = "0xdeadbeaf"; + const bridgeBalanceBefore = await l1TokenNonRebasable.balanceOf(l1TokenBridge.address); const tx = await l1TokenBridge .connect(l1MessengerStubAsEOA) @@ -855,19 +853,15 @@ unit("Optimism :: L1LidoTokensBridge", ctxFactory) accounts: { deployer, recipient, l1MessengerStubAsEOA, l2TokenBridgeEOA }, } = ctx; + await l1Messenger.setXDomainMessageSender(l2TokenBridgeEOA.address); + await l1TokenRebasable.transfer(l1TokenNonRebasable.address, wei`100 ether`); + const amount = wei`1 ether`; const data = "0xdeadbeaf"; const rate = await l1TokenNonRebasable.stEthPerToken(); const decimalsStr = await l1TokenNonRebasable.decimals(); const decimals = BigNumber.from(10).pow(decimalsStr); - const amountUnwrapped = (wei.toBigNumber(amount)).mul(rate).div(BigNumber.from(decimals)); - const deployerBalanceBefore = await l1TokenRebasable.balanceOf(deployer.address); - - await l1Messenger.setXDomainMessageSender(l2TokenBridgeEOA.address); - - await l1TokenRebasable.transfer(l1TokenNonRebasable.address, wei`100 ether`); - const bridgeBalanceBefore = await l1TokenRebasable.balanceOf(l1TokenBridge.address); const tx = await l1TokenBridge @@ -897,6 +891,80 @@ unit("Optimism :: L1LidoTokensBridge", ctxFactory) ); }) + .test("finalizeERC20Withdrawal() :: zero amount of rebasable token", async (ctx) => { + const { + l1TokenBridge, + stubs: { l1TokenRebasable, l2TokenRebasable, l1Messenger }, + accounts: { deployer, recipient, l1MessengerStubAsEOA, l2TokenBridgeEOA }, + } = ctx; + + await l1Messenger.setXDomainMessageSender(l2TokenBridgeEOA.address); + + const data = "0xdeadbeaf"; + const recipientBalanceBefore = await l1TokenRebasable.balanceOf(recipient.address); + const bridgeBalanceBefore = await l1TokenRebasable.balanceOf(l1TokenBridge.address); + + const tx = await l1TokenBridge + .connect(l1MessengerStubAsEOA) + .finalizeERC20Withdrawal( + l1TokenRebasable.address, + l2TokenRebasable.address, + deployer.address, + recipient.address, + 0, + data + ); + + await assert.emits(l1TokenBridge, tx, "ERC20WithdrawalFinalized", [ + l1TokenRebasable.address, + l2TokenRebasable.address, + deployer.address, + recipient.address, + 0, + data, + ]); + + assert.equalBN(await l1TokenRebasable.balanceOf(recipient.address), recipientBalanceBefore); + assert.equalBN(await l1TokenRebasable.balanceOf(l1TokenBridge.address), bridgeBalanceBefore); + }) + + .test("finalizeERC20Withdrawal() :: zero amount of non-rebasable token", async (ctx) => { + const { + l1TokenBridge, + stubs: { l1TokenNonRebasable, l2TokenNonRebasable, l1Messenger }, + accounts: { deployer, recipient, l1MessengerStubAsEOA, l2TokenBridgeEOA }, + } = ctx; + + await l1Messenger.setXDomainMessageSender(l2TokenBridgeEOA.address); + + const data = "0xdeadbeaf"; + const recipientBalanceBefore = await l1TokenNonRebasable.balanceOf(recipient.address); + const bridgeBalanceBefore = await l1TokenNonRebasable.balanceOf(l1TokenBridge.address); + + const tx = await l1TokenBridge + .connect(l1MessengerStubAsEOA) + .finalizeERC20Withdrawal( + l1TokenNonRebasable.address, + l2TokenNonRebasable.address, + deployer.address, + recipient.address, + 0, + data + ); + + await assert.emits(l1TokenBridge, tx, "ERC20WithdrawalFinalized", [ + l1TokenNonRebasable.address, + l2TokenNonRebasable.address, + deployer.address, + recipient.address, + 0, + data, + ]); + + assert.equalBN(await l1TokenNonRebasable.balanceOf(recipient.address), recipientBalanceBefore); + assert.equalBN(await l1TokenNonRebasable.balanceOf(l1TokenBridge.address), bridgeBalanceBefore); + }) + .run(); async function ctxFactory() { diff --git a/test/optimism/L2ERC20ExtendedTokensBridge.unit.test.ts b/test/optimism/L2ERC20ExtendedTokensBridge.unit.test.ts index 53040ce7..ed800439 100644 --- a/test/optimism/L2ERC20ExtendedTokensBridge.unit.test.ts +++ b/test/optimism/L2ERC20ExtendedTokensBridge.unit.test.ts @@ -3,12 +3,13 @@ import { ERC20BridgedStub__factory, ERC20WrapperStub__factory, TokenRateOracle__factory, - ERC20Rebasable__factory, + ERC20RebasableBridged__factory, L1LidoTokensBridge__factory, L2ERC20ExtendedTokensBridge__factory, OssifiableProxy__factory, EmptyContractStub__factory, CrossDomainMessengerStub__factory, + L2ERC20ExtendedTokensBridge } from "../../typechain"; import testing, { unit } from "../../utils/testing"; import { wei } from "../../utils/wei"; @@ -241,6 +242,120 @@ unit("Optimism:: L2ERC20ExtendedTokensBridge", ctxFactory) ); }) + .test("withdraw() :: zero rebasable tokens", async (ctx) => { + const { + l2TokenBridge, + accounts: { deployer, l1TokenBridgeEOA, recipient }, + stubs: { + l2Messenger, + l1TokenRebasable, + l2TokenRebasable + }, + } = ctx; + + await pushTokenRate(ctx); + + const l1Gas = wei`1 wei`; + const data = "0xdeadbeaf"; + const recipientBalanceBefore = await l2TokenRebasable.balanceOf(recipient.address); + const totalSupplyBefore = await l2TokenRebasable.totalSupply(); + + const tx = await l2TokenBridge + .connect(recipient) + .withdraw( + l2TokenRebasable.address, + 0, + l1Gas, + data); + + await assert.emits(l2TokenBridge, tx, "WithdrawalInitiated", [ + l1TokenRebasable.address, + l2TokenRebasable.address, + recipient.address, + recipient.address, + 0, + data, + ]); + + await assert.emits(l2Messenger, tx, "SentMessage", [ + l1TokenBridgeEOA.address, + l2TokenBridge.address, + L1LidoTokensBridge__factory.createInterface().encodeFunctionData( + "finalizeERC20Withdrawal", + [ + l1TokenRebasable.address, + l2TokenRebasable.address, + recipient.address, + recipient.address, + 0, + data, + ] + ), + 1, // message nonce + l1Gas, + ]); + + assert.equalBN(await l2TokenRebasable.balanceOf(deployer.address), recipientBalanceBefore); + assert.equalBN(await l2TokenRebasable.totalSupply(), totalSupplyBefore); + }) + + .test("withdraw() :: zero non-rebasable tokens", async (ctx) => { + const { + l2TokenBridge, + accounts: { deployer, l1TokenBridgeEOA, recipient }, + stubs: { + l2Messenger, + l1TokenNonRebasable, + l2TokenNonRebasable + }, + } = ctx; + + await pushTokenRate(ctx); + + const l1Gas = wei`1 wei`; + const data = "0xdeadbeaf"; + const recipientBalanceBefore = await l2TokenNonRebasable.balanceOf(recipient.address); + const totalSupplyBefore = await l2TokenNonRebasable.totalSupply(); + + const tx = await l2TokenBridge + .connect(recipient) + .withdraw( + l2TokenNonRebasable.address, + 0, + l1Gas, + data); + + await assert.emits(l2TokenBridge, tx, "WithdrawalInitiated", [ + l1TokenNonRebasable.address, + l2TokenNonRebasable.address, + recipient.address, + recipient.address, + 0, + data, + ]); + + await assert.emits(l2Messenger, tx, "SentMessage", [ + l1TokenBridgeEOA.address, + l2TokenBridge.address, + L1LidoTokensBridge__factory.createInterface().encodeFunctionData( + "finalizeERC20Withdrawal", + [ + l1TokenNonRebasable.address, + l2TokenNonRebasable.address, + recipient.address, + recipient.address, + 0, + data, + ] + ), + 1, // message nonce + l1Gas, + ]); + + assert.equalBN(await l2TokenNonRebasable.balanceOf(recipient.address), recipientBalanceBefore); + assert.equalBN(await l2TokenNonRebasable.totalSupply(), totalSupplyBefore); + }) + .test("withdrawTo() :: withdrawals disabled", async (ctx) => { const { l2TokenBridge, @@ -436,6 +551,122 @@ unit("Optimism:: L2ERC20ExtendedTokensBridge", ctxFactory) ); }) + .test("withdrawTo() :: zero rebasable tokens", async (ctx) => { + const { + l2TokenBridge, + accounts: { deployer, l1TokenBridgeEOA, recipient }, + stubs: { + l2Messenger, + l1TokenRebasable, + l2TokenRebasable + }, + } = ctx; + + await pushTokenRate(ctx); + + const l1Gas = wei`1 wei`; + const data = "0xdeadbeaf"; + const recipientBalanceBefore = await l2TokenRebasable.balanceOf(recipient.address); + const totalSupplyBefore = await l2TokenRebasable.totalSupply(); + + const tx = await l2TokenBridge + .connect(recipient) + .withdrawTo( + l2TokenRebasable.address, + recipient.address, + 0, + l1Gas, + data); + + await assert.emits(l2TokenBridge, tx, "WithdrawalInitiated", [ + l1TokenRebasable.address, + l2TokenRebasable.address, + recipient.address, + recipient.address, + 0, + data, + ]); + + await assert.emits(l2Messenger, tx, "SentMessage", [ + l1TokenBridgeEOA.address, + l2TokenBridge.address, + L1LidoTokensBridge__factory.createInterface().encodeFunctionData( + "finalizeERC20Withdrawal", + [ + l1TokenRebasable.address, + l2TokenRebasable.address, + recipient.address, + recipient.address, + 0, + data, + ] + ), + 1, // message nonce + l1Gas, + ]); + + assert.equalBN(await l2TokenRebasable.balanceOf(deployer.address), recipientBalanceBefore); + assert.equalBN(await l2TokenRebasable.totalSupply(), totalSupplyBefore); + }) + + .test("withdrawTo() :: zero non-rebasable tokens", async (ctx) => { + const { + l2TokenBridge, + accounts: { deployer, l1TokenBridgeEOA, recipient }, + stubs: { + l2Messenger, + l1TokenNonRebasable, + l2TokenNonRebasable + }, + } = ctx; + + await pushTokenRate(ctx); + + const l1Gas = wei`1 wei`; + const data = "0xdeadbeaf"; + const recipientBalanceBefore = await l2TokenNonRebasable.balanceOf(recipient.address); + const totalSupplyBefore = await l2TokenNonRebasable.totalSupply(); + + const tx = await l2TokenBridge + .connect(recipient) + .withdrawTo( + l2TokenNonRebasable.address, + recipient.address, + 0, + l1Gas, + data); + + await assert.emits(l2TokenBridge, tx, "WithdrawalInitiated", [ + l1TokenNonRebasable.address, + l2TokenNonRebasable.address, + recipient.address, + recipient.address, + 0, + data, + ]); + + await assert.emits(l2Messenger, tx, "SentMessage", [ + l1TokenBridgeEOA.address, + l2TokenBridge.address, + L1LidoTokensBridge__factory.createInterface().encodeFunctionData( + "finalizeERC20Withdrawal", + [ + l1TokenNonRebasable.address, + l2TokenNonRebasable.address, + recipient.address, + recipient.address, + 0, + data, + ] + ), + 1, // message nonce + l1Gas, + ]); + + assert.equalBN(await l2TokenNonRebasable.balanceOf(recipient.address), recipientBalanceBefore); + assert.equalBN(await l2TokenNonRebasable.totalSupply(), totalSupplyBefore); + }) + .test("finalizeDeposit() :: deposits disabled", async (ctx) => { const { l2TokenBridge, @@ -787,7 +1018,7 @@ async function ctxFactory() { 86400 ); - const l2TokenRebasableStub = await new ERC20Rebasable__factory(deployer).deploy( + const l2TokenRebasableStub = await new ERC20RebasableBridged__factory(deployer).deploy( "L2 Token Rebasable", "L2R", decimals, @@ -880,3 +1111,22 @@ async function packedTokenRateAndTimestamp(provider: JsonRpcProvider, tokenRate: const blockTimestampStr = ethers.utils.hexZeroPad(ethers.utils.hexlify(blockTimestamp), 5); return ethers.utils.hexConcat([stEthPerTokenStr, blockTimestampStr]); } + +type ContextType = Awaited> + +async function pushTokenRate(ctx: ContextType) { + const provider = await hre.ethers.provider; + + const packedTokenRateAndTimestampData = await packedTokenRateAndTimestamp(provider, ctx.exchangeRate); + + await ctx.l2TokenBridge + .connect(ctx.accounts.l2MessengerStubEOA) + .finalizeDeposit( + ctx.stubs.l1TokenRebasable.address, + ctx.stubs.l2TokenRebasable.address, + ctx.accounts.deployer.address, + ctx.accounts.deployer.address, + 0, + packedTokenRateAndTimestampData + ); +} diff --git a/test/optimism/TokenRateOracle.unit.test.ts b/test/optimism/TokenRateOracle.unit.test.ts index ca4d7b02..b309ef77 100644 --- a/test/optimism/TokenRateOracle.unit.test.ts +++ b/test/optimism/TokenRateOracle.unit.test.ts @@ -1,5 +1,6 @@ import hre from "hardhat"; import { assert } from "chai"; +import { BigNumber } from "ethers"; import testing, { unit } from "../../utils/testing"; import { TokenRateOracle__factory, @@ -52,32 +53,61 @@ unit("TokenRateOracle", ctxFactory) .test("updateRate() :: incorrect time", async (ctx) => { const { tokenRateOracle } = ctx.contracts; const { bridge } = ctx.accounts; + const { tokenRateCorrect } = ctx.constants; - await tokenRateOracle.connect(bridge).updateRate(10, 1000); - await assert.revertsWith(tokenRateOracle.connect(bridge).updateRate(12, 20), "ErrorIncorrectRateTimestamp()"); + const tx0 = await tokenRateOracle.connect(bridge).updateRate(tokenRateCorrect, 1000); + const tx1 = await tokenRateOracle.connect(bridge).updateRate(tokenRateCorrect, 20); + + await assert.emits(tokenRateOracle, tx1, "NewTokenRateOutdated", [tokenRateCorrect, 1000, 20]); + }) + + .test("updateRate() :: time in future", async (ctx) => { + const { tokenRateOracle } = ctx.contracts; + const { bridge } = ctx.accounts; + const { tokenRateCorrect, blockTimestamp } = ctx.constants; + + const timeInFuture = blockTimestamp + 100000; + await assert.revertsWith( + tokenRateOracle.connect(bridge).updateRate(tokenRateCorrect, timeInFuture), + "ErrorL1TimestampInFuture("+tokenRateCorrect+", "+timeInFuture+")" + ); + }) + + .test("updateRate() :: rate is out of range", async (ctx) => { + const { tokenRateOracle } = ctx.contracts; + const { bridge } = ctx.accounts; + const { tokenRateTooBig, tokenRateTooSmall, blockTimestamp } = ctx.constants; + + await assert.revertsWith( + tokenRateOracle.connect(bridge).updateRate(tokenRateTooBig, blockTimestamp), + "ErrorTokenRateIsOutOfRange("+tokenRateTooBig+", "+blockTimestamp+")" + ); + await assert.revertsWith( + tokenRateOracle.connect(bridge).updateRate(tokenRateTooSmall, blockTimestamp), + "ErrorTokenRateIsOutOfRange("+tokenRateTooSmall+", "+blockTimestamp+")" + ); }) .test("updateRate() :: don't update state if values are the same", async (ctx) => { const { tokenRateOracle } = ctx.contracts; const { bridge } = ctx.accounts; + const { tokenRateCorrect } = ctx.constants; - const tx1 = await tokenRateOracle.connect(bridge).updateRate(10, 1000); - await assert.emits(tokenRateOracle, tx1, "RateUpdated", [10, 1000]); + const tx1 = await tokenRateOracle.connect(bridge).updateRate(tokenRateCorrect, 1000); + await assert.emits(tokenRateOracle, tx1, "RateUpdated", [tokenRateCorrect, 1000]); - const tx2 = await tokenRateOracle.connect(bridge).updateRate(10, 1000); + const tx2 = await tokenRateOracle.connect(bridge).updateRate(tokenRateCorrect, 1000); await assert.notEmits(tokenRateOracle, tx2, "RateUpdated"); }) .test("updateRate() :: happy path called by bridge", async (ctx) => { const { tokenRateOracle } = ctx.contracts; const { bridge } = ctx.accounts; + const { tokenRateCorrect, blockTimestamp } = ctx.constants; - const currentTime = Date.now(); - const tokenRate = 123; + await tokenRateOracle.connect(bridge).updateRate(tokenRateCorrect, blockTimestamp); - await tokenRateOracle.connect(bridge).updateRate(tokenRate, currentTime); - - assert.equalBN(await tokenRateOracle.latestAnswer(), tokenRate); + assert.equalBN(await tokenRateOracle.latestAnswer(), tokenRateCorrect); const { roundId_, @@ -87,26 +117,24 @@ unit("TokenRateOracle", ctxFactory) answeredInRound_ } = await tokenRateOracle.latestRoundData(); - assert.equalBN(roundId_, currentTime); - assert.equalBN(answer_, tokenRate); - assert.equalBN(startedAt_, currentTime); - assert.equalBN(updatedAt_, currentTime); - assert.equalBN(answeredInRound_, currentTime); + assert.equalBN(roundId_, blockTimestamp); + assert.equalBN(answer_, tokenRateCorrect); + assert.equalBN(startedAt_, blockTimestamp); + assert.equalBN(updatedAt_, blockTimestamp); + assert.equalBN(answeredInRound_, blockTimestamp); assert.equalBN(await tokenRateOracle.decimals(), 18); }) .test("updateRate() :: happy path called by messenger with correct cross-domain sender", async (ctx) => { const { tokenRateOracle, l2MessengerStub } = ctx.contracts; const { l2MessengerStubEOA, l1TokenBridgeEOA } = ctx.accounts; + const { tokenRateCorrect, blockTimestamp } = ctx.constants; await l2MessengerStub.setXDomainMessageSender(l1TokenBridgeEOA.address); - const currentTime = Date.now(); - const tokenRate = 123; - - await tokenRateOracle.connect(l2MessengerStubEOA).updateRate(tokenRate, currentTime); + await tokenRateOracle.connect(l2MessengerStubEOA).updateRate(tokenRateCorrect, blockTimestamp); - assert.equalBN(await tokenRateOracle.latestAnswer(), tokenRate); + assert.equalBN(await tokenRateOracle.latestAnswer(), tokenRateCorrect); const { roundId_, @@ -116,11 +144,11 @@ unit("TokenRateOracle", ctxFactory) answeredInRound_ } = await tokenRateOracle.latestRoundData(); - assert.equalBN(roundId_, currentTime); - assert.equalBN(answer_, tokenRate); - assert.equalBN(startedAt_, currentTime); - assert.equalBN(updatedAt_, currentTime); - assert.equalBN(answeredInRound_, currentTime); + assert.equalBN(roundId_, blockTimestamp); + assert.equalBN(answer_, tokenRateCorrect); + assert.equalBN(startedAt_, blockTimestamp); + assert.equalBN(updatedAt_, blockTimestamp); + assert.equalBN(answeredInRound_, blockTimestamp); assert.equalBN(await tokenRateOracle.decimals(), 18); }) @@ -142,8 +170,19 @@ async function ctxFactory() { 86400 ); + const decimals = 18; + const decimalsBN = BigNumber.from(10).pow(decimals); + const tokenRateCorrect = BigNumber.from('12').pow(decimals - 1); + const tokenRateTooBig = BigNumber.from('2000').pow(decimals); + const tokenRateTooSmall = BigNumber.from('1').pow(decimals-3); + + const provider = await hre.ethers.provider; + const blockNumber = await provider.getBlockNumber(); + const blockTimestamp = (await provider.getBlock(blockNumber)).timestamp; + return { accounts: { deployer, bridge, stranger, l1TokenBridgeEOA, l2MessengerStubEOA }, - contracts: { tokenRateOracle, l2MessengerStub } + contracts: { tokenRateOracle, l2MessengerStub }, + constants: { tokenRateCorrect, tokenRateTooBig, tokenRateTooSmall, blockTimestamp } }; } diff --git a/test/token/ERC20Permit.unit.test.ts b/test/token/ERC20Permit.unit.test.ts index 35ce44b3..20e2ae7f 100644 --- a/test/token/ERC20Permit.unit.test.ts +++ b/test/token/ERC20Permit.unit.test.ts @@ -10,7 +10,7 @@ import { SignerWithAddress } from "@nomiclabs/hardhat-ethers/signers"; import { TokenRateOracle__factory, OssifiableProxy__factory, - ERC20RebasablePermit__factory, + ERC20RebasableBridgedPermit__factory, ERC1271PermitSignerMock__factory, ERC20BridgedPermit__factory, } from "../../typechain"; @@ -56,7 +56,7 @@ function permitTestsSuit(unitInstance: UnitTest) { // .test("wrappedToken() :: has the same address is in constructor", async (ctx) => { // const { rebasableProxied, wrappedToken } = ctx.contracts; - // assert.equal(await rebasableProxied.WRAPPED_TOKEN(), wrappedToken.address) + // assert.equal(await rebasableProxied.TOKEN_TO_WRAP_FROM(), wrappedToken.address) // }) .test('eip712Domain() is correct', async (ctx) => { @@ -404,7 +404,7 @@ async function tokenProxied( hre.ethers.constants.AddressZero, 86400 ); - const rebasableTokenImpl = await new ERC20RebasablePermit__factory(deployer).deploy( + const rebasableTokenImpl = await new ERC20RebasableBridgedPermit__factory(deployer).deploy( name, symbol, SIGNING_DOMAIN_VERSION, @@ -417,13 +417,13 @@ async function tokenProxied( const l2TokensProxy = await new OssifiableProxy__factory(deployer).deploy( rebasableTokenImpl.address, deployer.address, - ERC20RebasablePermit__factory.createInterface().encodeFunctionData("initialize", [ + ERC20RebasableBridgedPermit__factory.createInterface().encodeFunctionData("initialize", [ name, symbol, ]) ); - const rebasableProxied = ERC20RebasablePermit__factory.connect( + const rebasableProxied = ERC20RebasableBridgedPermit__factory.connect( l2TokensProxy.address, holder ); @@ -461,7 +461,7 @@ async function tokenProxied( } permitTestsSuit( - unit("ERC20RebasablePermit with EIP1271 (contract) signing", + unit("ERC20RebasableBridgedPermit with EIP1271 (contract) signing", ctxFactoryFactory( "Liquid staked Ether 2.0", "stETH", @@ -472,7 +472,7 @@ permitTestsSuit( ); permitTestsSuit( - unit("ERC20RebasablePermit with ECDSA (EOA) signing", + unit("ERC20RebasableBridgedPermit with ECDSA (EOA) signing", ctxFactoryFactory( "Liquid staked Ether 2.0", "stETH", diff --git a/test/token/ERC20Rebasable.unit.test.ts b/test/token/ERC20Rebasable.unit.test.ts index 7517f3f0..e71c6c73 100644 --- a/test/token/ERC20Rebasable.unit.test.ts +++ b/test/token/ERC20Rebasable.unit.test.ts @@ -6,13 +6,13 @@ import { wei } from "../../utils/wei"; import { ERC20Bridged__factory, TokenRateOracle__factory, - ERC20Rebasable__factory, + ERC20RebasableBridged__factory, OssifiableProxy__factory, CrossDomainMessengerStub__factory } from "../../typechain"; import { BigNumber } from "ethers"; -unit("ERC20Rebasable", ctxFactory) +unit("ERC20RebasableBridged", ctxFactory) .test("wrappedToken() :: has the same address is in constructor", async (ctx) => { const { rebasableProxied, wrappedToken } = ctx.contracts; @@ -50,7 +50,7 @@ unit("ERC20Rebasable", ctxFactory) zero.address, 86400 ); - const rebasableTokenImpl = await new ERC20Rebasable__factory(deployer).deploy( + const rebasableTokenImpl = await new ERC20RebasableBridged__factory(deployer).deploy( "name", "", 10, @@ -81,7 +81,7 @@ unit("ERC20Rebasable", ctxFactory) zero.address, 86400 ); - const rebasableTokenImpl = await new ERC20Rebasable__factory(deployer).deploy( + const rebasableTokenImpl = await new ERC20RebasableBridged__factory(deployer).deploy( "", "symbol", 10, @@ -128,7 +128,7 @@ unit("ERC20Rebasable", ctxFactory) zero.address, 86400 ); - const rebasableProxied = await new ERC20Rebasable__factory(deployer).deploy( + const rebasableProxied = await new ERC20RebasableBridged__factory(deployer).deploy( "", "symbol", 10, @@ -143,18 +143,6 @@ unit("ERC20Rebasable", ctxFactory) await assert.revertsWith(rebasableProxied.connect(user1).wrap(5), "ErrorWrongOracleUpdateTime()"); }) -.test("wrap() :: wrong oracle answer", async (ctx) => { - - const { rebasableProxied, wrappedToken, tokenRateOracle } = ctx.contracts; - const { user1, owner } = ctx.accounts; - - await tokenRateOracle.connect(owner).updateRate(0, 2000); - await wrappedToken.connect(owner).bridgeMint(user1.address, 1000); - await wrappedToken.connect(user1).approve(rebasableProxied.address, 1000); - - await assert.revertsWith(rebasableProxied.connect(user1).wrap(21), "ErrorOracleAnswerIsNotPositive()"); - }) - .test("wrap() :: when no balance", async (ctx) => { const { rebasableProxied, wrappedToken } = ctx.contracts; const { user1 } = ctx.accounts; @@ -166,7 +154,7 @@ unit("ERC20Rebasable", ctxFactory) .test("wrap() :: happy path", async (ctx) => { const { rebasableProxied, wrappedToken, tokenRateOracle } = ctx.contracts; - const {user1, user2, owner } = ctx.accounts; + const {user1, user2, owner, zero } = ctx.accounts; const { rate, decimals, premintShares } = ctx.constants; await tokenRateOracle.connect(owner).updateRate(rate, 1000); @@ -189,6 +177,9 @@ unit("ERC20Rebasable", ctxFactory) assert.equalBN(await rebasableProxied.connect(user1).callStatic.wrap(user1Shares), user1Tokens); const tx = await rebasableProxied.connect(user1).wrap(user1Shares); + await assert.emits(rebasableProxied, tx, "Transfer", [zero.address, user1.address, user1Tokens]); + await assert.emits(rebasableProxied, tx, "TransferShares", [zero.address, user1.address, user1Shares]); + assert.equalBN(await rebasableProxied.sharesOf(user1.address), user1Shares); assert.equalBN(await rebasableProxied.balanceOf(user1.address), user1Tokens); assert.equalBN(await wrappedToken.balanceOf(rebasableProxied.address), user1Shares); @@ -210,6 +201,9 @@ unit("ERC20Rebasable", ctxFactory) assert.equalBN(await rebasableProxied.connect(user2).callStatic.wrap(user2Shares), user2Tokens); const tx2 = await rebasableProxied.connect(user2).wrap(user2Shares); + await assert.emits(rebasableProxied, tx2, "Transfer", [zero.address, user2.address, user2Tokens]); + await assert.emits(rebasableProxied, tx2, "TransferShares", [zero.address, user2.address, user2Shares]); + assert.equalBN(await rebasableProxied.sharesOf(user2.address), user2Shares); assert.equalBN(await rebasableProxied.balanceOf(user2.address), user2Tokens); assert.equalBN(await wrappedToken.balanceOf(rebasableProxied.address), BigNumber.from(user1Shares).add(user2Shares)); @@ -308,7 +302,7 @@ unit("ERC20Rebasable", ctxFactory) zero.address, 86400 ); - const rebasableProxied = await new ERC20Rebasable__factory(deployer).deploy( + const rebasableProxied = await new ERC20RebasableBridged__factory(deployer).deploy( "", "symbol", 10, @@ -323,18 +317,6 @@ unit("ERC20Rebasable", ctxFactory) await assert.revertsWith(rebasableProxied.connect(user1).unwrap(5), "ErrorWrongOracleUpdateTime()"); }) -.test("unwrap() :: wrong oracle answer", async (ctx) => { - - const { rebasableProxied, wrappedToken, tokenRateOracle } = ctx.contracts; - const { user1, owner } = ctx.accounts; - - await tokenRateOracle.connect(owner).updateRate(0, 2000); - await wrappedToken.connect(owner).bridgeMint(user1.address, 1000); - await wrappedToken.connect(user1).approve(rebasableProxied.address, 1000); - - await assert.revertsWith(rebasableProxied.connect(user1).unwrap(21), "ErrorOracleAnswerIsNotPositive()"); - }) - .test("unwrap() :: when no balance", async (ctx) => { const { rebasableProxied } = ctx.contracts; const { user1 } = ctx.accounts; @@ -345,7 +327,7 @@ unit("ERC20Rebasable", ctxFactory) .test("bridgeMintShares() :: happy path", async (ctx) => { const { rebasableProxied } = ctx.contracts; - const {user1, user2, owner } = ctx.accounts; + const {user1, user2, owner, zero } = ctx.accounts; const { rate, decimals, premintShares, premintTokens } = ctx.constants; assert.equalBN(await rebasableProxied.getTotalShares(), premintShares); @@ -359,6 +341,8 @@ unit("ERC20Rebasable", ctxFactory) assert.equalBN(await rebasableProxied.balanceOf(user1.address), 0); const tx0 = await rebasableProxied.connect(owner).bridgeMintShares(user1.address, user1SharesToMint); + await assert.emits(rebasableProxied, tx0, "Transfer", [zero.address, user1.address, user1TokensMinted]); + await assert.emits(rebasableProxied, tx0, "TransferShares", [zero.address, user1.address, user1SharesToMint]); assert.equalBN(await rebasableProxied.sharesOf(user1.address), user1SharesToMint); assert.equalBN(await rebasableProxied.balanceOf(user1.address), user1TokensMinted); @@ -375,6 +359,8 @@ unit("ERC20Rebasable", ctxFactory) assert.equalBN(await rebasableProxied.balanceOf(user2.address), 0); const tx1 = await rebasableProxied.connect(owner).bridgeMintShares(user2.address, user2SharesToMint); + await assert.emits(rebasableProxied, tx1, "Transfer", [zero.address, user2.address, user2TokensMinted]); + await assert.emits(rebasableProxied, tx1, "TransferShares", [zero.address, user2.address, user2SharesToMint]); assert.equalBN(await rebasableProxied.sharesOf(user2.address), user2SharesToMint); assert.equalBN(await rebasableProxied.balanceOf(user2.address), user2TokensMinted); @@ -538,6 +524,7 @@ unit("ERC20Rebasable", ctxFactory) assert.equalBN(await rebasableProxied.balanceOf(holder.address), premintTokens); const amount = wei`1 ether`; + const sharesToTransfer = await rebasableProxied.getSharesByTokens(amount); // transfer tokens const tx = await rebasableProxied @@ -551,6 +538,12 @@ unit("ERC20Rebasable", ctxFactory) amount, ]); + await assert.emits(rebasableProxied, tx, "TransferShares", [ + holder.address, + recipient.address, + sharesToTransfer, + ]); + // validate balance was updated assert.equalBN( await rebasableProxied.balanceOf(holder.address), @@ -723,7 +716,7 @@ unit("ERC20Rebasable", ctxFactory) async (ctx) => { const { rebasableProxied } = ctx.contracts; const { premintShares } = ctx.constants; - const { recipient, owner } = ctx.accounts; + const { recipient, owner, zero } = ctx.accounts; // validate balance before mint assert.equalBN(await rebasableProxied.balanceOf(recipient.address), 0); @@ -739,12 +732,12 @@ unit("ERC20Rebasable", ctxFactory) // validate Transfer event was emitted const mintAmountInTokens = await rebasableProxied.getTokensByShares(mintAmount); await assert.emits(rebasableProxied, tx, "Transfer", [ - hre.ethers.constants.AddressZero, + zero.address, recipient.address, mintAmountInTokens, ]); await assert.emits(rebasableProxied, tx, "TransferShares", [ - hre.ethers.constants.AddressZero, + zero.address, recipient.address, mintAmount, ]); @@ -839,6 +832,10 @@ async function ctxFactory() { const premintShares = wei.toBigNumber(wei`100 ether`); const premintTokens = BigNumber.from(rate).mul(premintShares).div(decimals); + const provider = await hre.ethers.provider; + const blockNumber = await provider.getBlockNumber(); + const blockTimestamp = (await provider.getBlock(blockNumber)).timestamp; + const [ deployer, owner, @@ -863,7 +860,7 @@ async function ctxFactory() { zero.address, 86400 ); - const rebasableTokenImpl = await new ERC20Rebasable__factory(deployer).deploy( + const rebasableTokenImpl = await new ERC20RebasableBridged__factory(deployer).deploy( name, symbol, decimalsToSet, @@ -880,23 +877,23 @@ async function ctxFactory() { const l2TokensProxy = await new OssifiableProxy__factory(deployer).deploy( rebasableTokenImpl.address, deployer.address, - ERC20Rebasable__factory.createInterface().encodeFunctionData("initialize", [ + ERC20RebasableBridged__factory.createInterface().encodeFunctionData("initialize", [ name, symbol, ]) ); - const rebasableProxied = ERC20Rebasable__factory.connect( + const rebasableProxied = ERC20RebasableBridged__factory.connect( l2TokensProxy.address, holder ); - await tokenRateOracle.connect(owner).updateRate(rate, 1000); + await tokenRateOracle.connect(owner).updateRate(rate, blockTimestamp - 1000); await rebasableProxied.connect(owner).bridgeMintShares(holder.address, premintShares); return { accounts: { deployer, owner, recipient, spender, holder, stranger, zero, user1, user2 }, - constants: { name, symbol, decimalsToSet, decimals, premintShares, premintTokens, rate }, + constants: { name, symbol, decimalsToSet, decimals, premintShares, premintTokens, rate, blockTimestamp }, contracts: { rebasableProxied, wrappedToken, tokenRateOracle } }; } diff --git a/utils/optimism/deploymentAllFromScratch.ts b/utils/optimism/deploymentAllFromScratch.ts index 8d59c629..6fdfaad4 100644 --- a/utils/optimism/deploymentAllFromScratch.ts +++ b/utils/optimism/deploymentAllFromScratch.ts @@ -6,7 +6,7 @@ import network, { NetworkName } from "../network"; import { DeployScript, Logger } from "../deployment/DeployScript"; import { ERC20Bridged__factory, - ERC20Rebasable__factory, + ERC20RebasableBridged__factory, IERC20Metadata__factory, L1LidoTokensBridge__factory, L2ERC20ExtendedTokensBridge__factory, @@ -238,7 +238,7 @@ export default function deploymentAll( assert.equal(c.address, expectedL2TokenProxyAddress), }) .addStep({ - factory: ERC20Rebasable__factory, + factory: ERC20RebasableBridged__factory, args: [ l2TokenRebasableName, l2TokenRebasableSymbol, @@ -256,7 +256,7 @@ export default function deploymentAll( args: [ expectedL2TokenRebasableImplAddress, l2Params.admins.proxy, - ERC20Rebasable__factory.createInterface().encodeFunctionData( + ERC20RebasableBridged__factory.createInterface().encodeFunctionData( "initialize", [l2TokenRebasableName, l2TokenRebasableSymbol] ), diff --git a/utils/optimism/deploymentBridgesAndRebasableToken.ts b/utils/optimism/deploymentBridgesAndRebasableToken.ts index 1241ab24..f5ad4bc4 100644 --- a/utils/optimism/deploymentBridgesAndRebasableToken.ts +++ b/utils/optimism/deploymentBridgesAndRebasableToken.ts @@ -6,7 +6,7 @@ import network, { NetworkName } from "../network"; import { DeployScript, Logger } from "../deployment/DeployScript"; import { ERC20Bridged__factory, - ERC20Rebasable__factory, + ERC20RebasableBridged__factory, IERC20Metadata__factory, L1LidoTokensBridge__factory, L2ERC20ExtendedTokensBridge__factory, @@ -194,7 +194,7 @@ export default function deployment( assert.equal(c.address, expectedL2TokenProxyAddress), }) .addStep({ - factory: ERC20Rebasable__factory, + factory: ERC20RebasableBridged__factory, args: [ l2TokenRebasableName, l2TokenRebasableSymbol, @@ -212,7 +212,7 @@ export default function deployment( args: [ expectedL2TokenRebasableImplAddress, l2Params.admins.proxy, - ERC20Rebasable__factory.createInterface().encodeFunctionData( + ERC20RebasableBridged__factory.createInterface().encodeFunctionData( "initialize", [l2TokenRebasableName, l2TokenRebasableSymbol] ), diff --git a/utils/optimism/deploymentNewImplementations.ts b/utils/optimism/deploymentNewImplementations.ts index 60398029..776fe8df 100644 --- a/utils/optimism/deploymentNewImplementations.ts +++ b/utils/optimism/deploymentNewImplementations.ts @@ -6,7 +6,7 @@ import network, { NetworkName } from "../network"; import { DeployScript, Logger } from "../deployment/DeployScript"; import { ERC20Bridged__factory, - ERC20Rebasable__factory, + ERC20RebasableBridged__factory, IERC20Metadata__factory, L1LidoTokensBridge__factory, L2ERC20ExtendedTokensBridge__factory, @@ -169,7 +169,7 @@ export default function deploymentNewImplementations( assert.equal(c.address, expectedL2TokenImplAddress), }) .addStep({ - factory: ERC20Rebasable__factory, + factory: ERC20RebasableBridged__factory, args: [ l2TokenRebasableName, l2TokenRebasableSymbol, @@ -187,7 +187,7 @@ export default function deploymentNewImplementations( args: [ expectedL2TokenRebasableImplAddress, l2Params.admins.proxy, - ERC20Rebasable__factory.createInterface().encodeFunctionData( + ERC20RebasableBridged__factory.createInterface().encodeFunctionData( "initialize", [l2TokenRebasableName, l2TokenRebasableSymbol] ), diff --git a/utils/optimism/testing.ts b/utils/optimism/testing.ts index 97de55c4..af114de0 100644 --- a/utils/optimism/testing.ts +++ b/utils/optimism/testing.ts @@ -14,7 +14,7 @@ import { L1LidoTokensBridge__factory, L2ERC20ExtendedTokensBridge__factory, CrossDomainMessengerStub__factory, - ERC20Rebasable__factory, + ERC20RebasableBridged__factory, } from "../../typechain"; import addresses from "./addresses"; import contracts from "./contracts"; @@ -280,7 +280,7 @@ function connectBridgeContracts( addresses.l2Token, optSignerOrProvider ); - const l2TokenRebasable = ERC20Rebasable__factory.connect( + const l2TokenRebasable = ERC20RebasableBridged__factory.connect( addresses.l2TokenRebasable, optSignerOrProvider ); From 135968ab0588baeb4b2f026311f22e00d6a96764 Mon Sep 17 00:00:00 2001 From: Anton Kovalchuk Date: Wed, 17 Apr 2024 09:44:01 +0200 Subject: [PATCH 065/148] remove goerli leftovers, 0.6.11 solidity config --- .env.example | 6 ++-- .env.wsteth.opt_goerli | 71 ----------------------------------------- .env.wsteth.opt_mainnet | 2 +- README.md | 16 +++++----- hardhat.config.ts | 9 ------ 5 files changed, 12 insertions(+), 92 deletions(-) delete mode 100644 .env.wsteth.opt_goerli diff --git a/.env.example b/.env.example index fd9f66e3..037aa5cd 100644 --- a/.env.example +++ b/.env.example @@ -5,10 +5,10 @@ # ############################ RPC_ETH_MAINNET= -RPC_ETH_GOERLI= +RPC_ETH_SEPOLIA= RPC_OPT_MAINNET=https://mainnet.optimism.io -RPC_OPT_GOERLI=https://goerli.optimism.io +RPC_OPT_SEPOLIA=https://sepolia.optimism.io # ############################ # Etherscan @@ -25,7 +25,7 @@ ETHERSCAN_API_KEY_OPT= TOKEN= # Name of the network environments used by deployment scripts. -# Might be one of: "mainnet", "goerli". +# Might be one of: "mainnet", "sepolia". NETWORK=mainnet # Run deployment in the forking network instead of public ones diff --git a/.env.wsteth.opt_goerli b/.env.wsteth.opt_goerli deleted file mode 100644 index 18eb8378..00000000 --- a/.env.wsteth.opt_goerli +++ /dev/null @@ -1,71 +0,0 @@ -# Detailed info: https://github.com/lidofinance/lido-l2#Project-Configuration - -# ############################ -# RPCs -# ############################ - -RPC_ETH_GOERLI= -RPC_OPT_GOERLI=https://goerli.optimism.io - -# ############################ -# Etherscan -# ############################ - -ETHERSCAN_API_KEY_ETH= -ETHERSCAN_API_KEY_OPT= - -# ############################ -# Bridge/Gateway Deployment -# ############################ - -# Address of the token to deploy the bridge/gateway for -TOKEN=0x6320cd32aa674d2898a68ec82e869385fc5f7e2f - -# Name of the network environments used by deployment scripts. -# Might be one of: "mainnet", "goerli". -NETWORK=goerli - -# Private key of the deployer account used for deployment process -ETH_DEPLOYER_PRIVATE_KEY= -OPT_DEPLOYER_PRIVATE_KEY= - -L1_PROXY_ADMIN=0x4333218072D5d7008546737786663c38B4D561A4 -L1_BRIDGE_ADMIN=0x4333218072D5d7008546737786663c38B4D561A4 -L1_DEPOSITS_ENABLED=true -L1_WITHDRAWALS_ENABLED=true -L1_DEPOSITS_ENABLERS=["0x4333218072D5d7008546737786663c38B4D561A4"] -L1_DEPOSITS_DISABLERS="["0x4333218072D5d7008546737786663c38B4D561A4", "0x7fE7fa4EF7D134Dbf8B616Ba7B675F26286BC2cd"]" -L1_WITHDRAWALS_ENABLERS=["0x4333218072D5d7008546737786663c38B4D561A4"] -L1_WITHDRAWALS_DISABLERS="["0x4333218072D5d7008546737786663c38B4D561A4", "0x7fE7fa4EF7D134Dbf8B616Ba7B675F26286BC2cd"]" - -L2_PROXY_ADMIN=0x55C39356C714Cde16F8a80302c1Ce9DfAC6f5a35 -L2_BRIDGE_ADMIN=0x55C39356C714Cde16F8a80302c1Ce9DfAC6f5a35 -L2_DEPOSITS_ENABLED=true -L2_WITHDRAWALS_ENABLED=true -L2_DEPOSITS_ENABLERS=["0x55C39356C714Cde16F8a80302c1Ce9DfAC6f5a35"] -L2_DEPOSITS_DISABLERS="["0x55C39356C714Cde16F8a80302c1Ce9DfAC6f5a35", "0x7fE7fa4EF7D134Dbf8B616Ba7B675F26286BC2cd"]" -L2_WITHDRAWALS_ENABLERS=["0x55C39356C714Cde16F8a80302c1Ce9DfAC6f5a35"] -L2_WITHDRAWALS_DISABLERS="["0x55C39356C714Cde16F8a80302c1Ce9DfAC6f5a35", "0x7fE7fa4EF7D134Dbf8B616Ba7B675F26286BC2cd"]" - -# ############################ -# Integration & E2E Testing -# ############################ - -TESTING_OPT_NETWORK=goerli -TESTING_OPT_L1_TOKEN=0x6320cd32aa674d2898a68ec82e869385fc5f7e2f -TESTING_OPT_L2_TOKEN=0xe8964a99d5DE7cEE2743B20113a52C953b0916E9 -TESTING_OPT_L1_ERC20_TOKEN_BRIDGE=0xDdC89Bd27F9A1C47A5c20dF0783dE52f55513598 -TESTING_OPT_L2_ERC20_TOKEN_BRIDGE=0x423702bC3Fb92f59Be440354456f0481934bF1f5 - -# ############################ -# Integration Testing -# ############################ - -TESTING_USE_DEPLOYED_CONTRACTS=true -TESTING_L1_TOKENS_HOLDER= - -# ############################ -# E2E Testing -# ############################ - -TESTING_PRIVATE_KEY= diff --git a/.env.wsteth.opt_mainnet b/.env.wsteth.opt_mainnet index 6bf3ae55..ed236c7e 100644 --- a/.env.wsteth.opt_mainnet +++ b/.env.wsteth.opt_mainnet @@ -22,7 +22,7 @@ ETHERSCAN_API_KEY_OPT= TOKEN=0x7f39C581F595B53c5cb19bD0b3f8dA6c935E2Ca0 # Name of the network environments used by deployment scripts. -# Might be one of: "mainnet", "goerli". +# Might be one of: "mainnet", "sepolia". NETWORK=mainnet # Run deployment in the forking network instead of public ones diff --git a/README.md b/README.md index 8d13cf99..a623b4ce 100644 --- a/README.md +++ b/README.md @@ -42,7 +42,7 @@ Fill the newly created `.env` file with the required variables. See the [Project The configuration of the deployment scripts happens via the ENV variables. The following variables are required: - [`TOKEN`](#TOKEN) - address of the token to deploy a new bridge on the Ethereum chain. -- [`NETWORK`](#NETWORK) - name of the network environments used by deployment scripts. Allowed values: `mainnet`, `goerli`. +- [`NETWORK`](#NETWORK) - name of the network environments used by deployment scripts. Allowed values: `mainnet`, `sepolia`. - [`FORKING`](#FORKING) - run deployment in the forking network instead of real ones - [`ETH_DEPLOYER_PRIVATE_KEY`](#ETH_DEPLOYER_PRIVATE_KEY) - The private key of the deployer account in the Ethereum network is used during the deployment process. - [`OPT_DEPLOYER_PRIVATE_KEY`](#OPT_DEPLOYER_PRIVATE_KEY) - The private key of the deployer account in the Optimism network is used during the deployment process. @@ -196,13 +196,13 @@ The configuration of the project happens via set of ENV variables. The full list Address of the RPC node for **Mainnet** Ethereum network. -#### `RPC_ETH_GOERLI` +#### `RPC_ETH_SEPOLIA` -Address of the RPC node for **Goerli** Ethereum network. +Address of the RPC node for **Sepolia** Ethereum network. -#### `RPC_OPT_GOERLI` +#### `RPC_OPT_SEPOLIA` -Address of the RPC node for **Goerli** Optimism network. +Address of the RPC node for **Sepolia** Optimism network. #### `RPC_OPT_MAINNET` @@ -240,7 +240,7 @@ Address of the token to deploy a new bridge on the Ethereum chain. > Default value: `mainnet` -Name of the network environments used by deployment scripts. Might be one of: `mainnet`, `goerli`. +Name of the network environments used by deployment scripts. Might be one of: `mainnet`, `sepolia`. #### `FORKING` @@ -358,7 +358,7 @@ The following variables are used in the process of the Integration & E2E testing #### `TESTING_OPT_NETWORK` -Name of the network environments used for Optimism Integration & E2E testing. Might be one of: `mainnet`, `goerli`. +Name of the network environments used for Optimism Integration & E2E testing. Might be one of: `mainnet`, `sepolia`. #### `TESTING_OPT_L1_TOKEN` @@ -404,7 +404,7 @@ Address of the deployed Governance Bridge Executor in the Optimism network. If s The private key from the address which holds: -- Goerli and Optimistic Goerli Ether to launch Optimism E2E tests +- Sepolia and Optimistic Sepolia Ether to launch Optimism E2E tests The test Ether might be retrived via [Paradigm Faucet](https://faucet.paradigm.xyz/). diff --git a/hardhat.config.ts b/hardhat.config.ts index 119bab69..83cac4bd 100644 --- a/hardhat.config.ts +++ b/hardhat.config.ts @@ -15,15 +15,6 @@ dotenv.config(); const config: HardhatUserConfig = { solidity: { compilers: [ - { - version: "0.6.11", - settings: { - optimizer: { - enabled: true, - runs: 100, - }, - }, - }, { version: "0.8.10", settings: { From d4672ae6a18dd0dd8a34b040015b6242e1ac81ce Mon Sep 17 00:00:00 2001 From: TheDZhon Date: Wed, 17 Apr 2024 08:05:42 +0000 Subject: [PATCH 066/148] fix: Make storage-layout up to date --- .storage-layout | 30 ------------------------------ 1 file changed, 30 deletions(-) diff --git a/.storage-layout b/.storage-layout index 393bb17e..328c7a31 100644 --- a/.storage-layout +++ b/.storage-layout @@ -50,13 +50,6 @@ | Name | Type | Slot | Offset | Bytes | Contract | |------|------|------|--------|-------|----------| -======================= -➡ L1CrossDomainEnabled -======================= - -| Name | Type | Slot | Offset | Bytes | Contract | -|------|------|------|--------|-------|----------| - ======================= ➡ L1ERC20TokenBridge ======================= @@ -65,21 +58,6 @@ |--------|---------------------------------------------------|------|--------|-------|--------------------------------------------------------------| | _roles | mapping(bytes32 => struct AccessControl.RoleData) | 0 | 0 | 32 | contracts/optimism/L1ERC20TokenBridge.sol:L1ERC20TokenBridge | -======================= -➡ L1ERC20TokenGateway -======================= - -| Name | Type | Slot | Offset | Bytes | Contract | -|--------|---------------------------------------------------|------|--------|-------|----------------------------------------------------------------| -| _roles | mapping(bytes32 => struct AccessControl.RoleData) | 0 | 0 | 32 | contracts/arbitrum/L1ERC20TokenGateway.sol:L1ERC20TokenGateway | - -======================= -➡ L2CrossDomainEnabled -======================= - -| Name | Type | Slot | Offset | Bytes | Contract | -|------|------|------|--------|-------|----------| - ======================= ➡ L2ERC20TokenBridge ======================= @@ -88,14 +66,6 @@ |--------|---------------------------------------------------|------|--------|-------|--------------------------------------------------------------| | _roles | mapping(bytes32 => struct AccessControl.RoleData) | 0 | 0 | 32 | contracts/optimism/L2ERC20TokenBridge.sol:L2ERC20TokenBridge | -======================= -➡ L2ERC20TokenGateway -======================= - -| Name | Type | Slot | Offset | Bytes | Contract | -|--------|---------------------------------------------------|------|--------|-------|----------------------------------------------------------------| -| _roles | mapping(bytes32 => struct AccessControl.RoleData) | 0 | 0 | 32 | contracts/arbitrum/L2ERC20TokenGateway.sol:L2ERC20TokenGateway | - ======================= ➡ OssifiableProxy ======================= From 06768b6809df25f18bd9482248e1b9f42e432a02 Mon Sep 17 00:00:00 2001 From: Anton Kovalchuk Date: Wed, 17 Apr 2024 10:21:06 +0200 Subject: [PATCH 067/148] remove arbitrum leftovers after mergin --- contracts/arbitrum/L2ERC20TokenGateway.sol | 90 -- contracts/arbitrum/README.md | 908 --------------------- test/arbitrum/_launch.test.ts | 89 -- utils/arbitrum/testing.ts | 337 -------- 4 files changed, 1424 deletions(-) delete mode 100644 contracts/arbitrum/L2ERC20TokenGateway.sol delete mode 100644 contracts/arbitrum/README.md delete mode 100644 test/arbitrum/_launch.test.ts delete mode 100644 utils/arbitrum/testing.ts diff --git a/contracts/arbitrum/L2ERC20TokenGateway.sol b/contracts/arbitrum/L2ERC20TokenGateway.sol deleted file mode 100644 index f7850448..00000000 --- a/contracts/arbitrum/L2ERC20TokenGateway.sol +++ /dev/null @@ -1,90 +0,0 @@ -// SPDX-FileCopyrightText: 2022 Lido -// SPDX-License-Identifier: GPL-3.0 - -pragma solidity 0.8.10; - -import {IERC20Bridged} from "../token/ERC20Bridged.sol"; -import {IL2TokenGateway, IInterchainTokenGateway} from "./interfaces/IL2TokenGateway.sol"; - -import {L2CrossDomainEnabled} from "./L2CrossDomainEnabled.sol"; -import {L2OutboundDataParser} from "./libraries/L2OutboundDataParser.sol"; -import {InterchainERC20TokenGateway} from "./InterchainERC20TokenGateway.sol"; - -/// @author psirex -/// @notice Contract implements ITokenGateway interface and with counterpart L1ERC20TokenGateway -/// allows bridging registered ERC20 compatible tokens between Arbitrum and Ethereum chains -contract L2ERC20TokenGateway is - InterchainERC20TokenGateway, - L2CrossDomainEnabled, - IL2TokenGateway -{ - /// @param arbSys_ Address of the Arbitrum’s ArbSys contract in the L2 chain - /// @param router_ Address of the router in the L2 chain - /// @param counterpartGateway_ Address of the counterpart L1 gateway - /// @param l1Token_ Address of the bridged token in the L1 chain - /// @param l2Token_ Address of the token minted on the Arbitrum chain when token bridged - constructor( - address arbSys_, - address router_, - address counterpartGateway_, - address l1Token_, - address l2Token_ - ) - InterchainERC20TokenGateway( - router_, - counterpartGateway_, - l1Token_, - l2Token_ - ) - L2CrossDomainEnabled(arbSys_) - {} - - /// @inheritdoc IL2TokenGateway - function outboundTransfer( - address l1Token_, - address to_, - uint256 amount_, - uint256, // maxGas - uint256, // gasPriceBid - bytes calldata data_ - ) - external - whenWithdrawalsEnabled - onlySupportedL1Token(l1Token_) - returns (bytes memory res) - { - address from = L2OutboundDataParser.decode(router, data_); - - IERC20Bridged(l2Token).bridgeBurn(from, amount_); - - uint256 id = sendCrossDomainMessage( - from, - counterpartGateway, - getOutboundCalldata(l1Token_, from, to_, amount_, "") - ); - - // The current implementation doesn't support fast withdrawals, so we - // always use 0 for the exitNum argument in the event - emit WithdrawalInitiated(l1Token_, from, to_, id, 0, amount_); - - return abi.encode(id); - } - - /// @inheritdoc IInterchainTokenGateway - function finalizeInboundTransfer( - address l1Token_, - address from_, - address to_, - uint256 amount_, - bytes calldata - ) - external - whenDepositsEnabled - onlySupportedL1Token(l1Token_) - onlyFromCrossDomainAccount(counterpartGateway) - { - IERC20Bridged(l2Token).bridgeMint(to_, amount_); - - emit DepositFinalized(l1Token_, from_, to_, amount_); - } -} diff --git a/contracts/arbitrum/README.md b/contracts/arbitrum/README.md deleted file mode 100644 index 4197694f..00000000 --- a/contracts/arbitrum/README.md +++ /dev/null @@ -1,908 +0,0 @@ -# Lido's Arbitrum Gateway - -The document details implementation of the bridging of the ERC20 compatible tokens[^*] between Ethereum and Arbitrum chains via Arbitrum's “Canonical Bridge”. - -It's the first step of the Lido's integration into the Arbitrum protocol. The main goal of the current implementation is to be the strong foundation for the long-term goals of the Lido expansion in the Arbitrum chain. The long-run picture of the Lido's integration into L2s includes: - -- Bridging of Lido's tokens from L1 to L2 chains -- Instant ETH staking on L2 chains with receiving stETH/wstETH on the corresponding L2 immediately -- Keeping UX on L2 as close as possible to the UX on Ethereum mainnet - -At this point, the implementation must provide a scalable and reliable solution for Lido to bridge ERC20 compatible tokens between Arbitrum and Ethereum chain. - -[^*]: The current implementation might not support the non-standard functionality of the ERC20 tokens. For example, rebasable tokens or tokens with transfers fee will work incorrectly. In case your token implements some non-typical ERC20 logic, make sure it is compatible with the gateway before usage. - -## Arbitrum's Bridging Flow - -Arbitrum’s “Canonical Bridge” tokens-bridging architecture consists of three types of contracts: - -1. **Asset contracts**: these are the token contracts themselves, i.e., an ERC20 on L1 and it's counterpart on Arbitrum. -2. **Gateways**: Pairs of contracts (one on L1, one on L2) that implement a particular type of cross chain asset bridging. -3. **Routers**: Exactly two contracts - (one on L1, one on L2) that route each asset to its designated Gateway. - -All Ethereum to Arbitrum token transfers are initiated via the `L1GatewayRouter` contract. `L1GatewayRouter` is responsible for mapping L1 token addresses to `L1Gateway`, thus acting as an L1/L2 address oracle and ensuring that each token corresponds to only one gateway. The `L1Gateway` communicates to an `L2Gateway` (typically/expectedly via retryable tickets). - -Similarly, Arbitrum to Ethereum transfers are initiated via the `L2GatewayRouter` contract, which forwards calls the token's `L2Gateway`, which in turn communicates to its corresponding `L1Gateway` (typically/expectedly via sending messages to the Outbox.) - -To be compatible with Arbitrum's `GatewayRouter`, both L1 and L2 gateways must conform to the `ITokenGateway` interface. - -```solidity -interface ITokenGateway { - function calculateL2TokenAddress(address l1ERC20) - external - view - returns (address); - - function outboundTransfer( - address _token, - address _to, - uint256 _amount, - uint256 _maxGas, - uint256 _gasPriceBid, - bytes calldata _data - ) external returns (bytes memory); - - function getOutboundCalldata( - address _token, - address _from, - address _to, - uint256 _amount, - bytes memory _data - ) external view returns (bytes memory); - - function finalizeInboundTransfer( - address _token, - address _from, - address _to, - uint256 _amount, - bytes calldata _data - ) external virtual override; -} - -``` - -The general process of tokens bridging via Arbitrum's `GatewayRouter` consists of next steps: - -### Deposits - -1. A user calls `L1GatewayRouter.outboundTransfer()` (with `L1Token`'s L1 address as an argument). -2. `L1GatewayRouter` looks up `L1Token`'s gateway. -3. `L1GatewayRouter` calls `L1TokensGateway.outboundTransfer()`, forwarding the appropriate parameters. -4. `L1TokensGateway` escrows tokens and triggers `L2TokensGateway.finalizeInboundTransfer()` method on L2 (typically via a creation of a retryable ticket). -5. `finalizeInboundTransfer` mints the appropriate amount of tokens at the `L2Token` contract on L2. - -![](https://i.imgur.com/A8B1xgI.png) - -### Withdrawals - -1. On Arbitrum, a user calls `L2GatewayRouter.outboundTransfer()`, which in turn calls `outboundTransfer` on `L2Token`'s gateway (i.e., `L2TokensGateway`). -2. This burns `L2Token` tokens and calls [`ArbSys`](https://developer.offchainlabs.com/docs/arbsys) with an encoded message to `L1TokensGateway.finalizeInboundTransfer()`, which will be eventually executed on L1. -3. After the dispute window expires and the assertion with the user's transaction is confirmed, a user can call `Outbox.executeTransaction()`, which in turn calls the encoded `L1ERC20Gateway.finalizeInboundTransfer()` message, releasing the user's tokens from the `L1TokensGateway` contract's escrow. - -![](https://i.imgur.com/KOPguoa.png) - -The `L1GatewayRouter` allows registering custom gateways for certain tokens via `setGateways()` method, which might be called by the OffchainLabs team manually. - -The rest of the document provides a technical specification of the gateways Lido will use to transfer tokens between Arbitrum and Ethereum chains. - -## Lido's Gateways Implementation - -The current implementation of the gateways provides functionality to bridge the specified type of ERC20 compatible token between Ethereum and Arbitrum chains. Additionally, the bridge provides some administrative features, like the **temporary disabling of the deposits and withdrawals**. It's necessary when bridging must be disabled fast because of the malicious usage of the bridge or vulnerability in the contracts. Also, it might be helpful in the implementation upgrade process. - -The technical implementation focuses on the following requirements for the contracts: - -- **Scalability** - current implementation must provide the ability to be extended with new functionality in the future. -- **Simplicity** - implemented contracts must be clear, simple, and expressive for developers who will work with code in the future. -- **Gas efficiency** - implemented solution must be efficient in terms of gas costs for the end-user, but at the same time, it must not violate the previous requirement. - -A high-level overview of the proposed solution might be found in the below diagram: - -![](https://i.imgur.com/TPfEr29.png) - -- Libraries: - - [**`L1OutboundDataParser`**](#L1OutboundDataParser) - a helper library to parse data passed to `outboundTransfer()` of `L1ERC20TokenGateway`. - - [**`L2OutboundDataParser`**](#L2OutboundDataParser) - a helper library to parse data passed to `outboundTransfer()` of `L2ERC20TokenGateway`. -- Abstract Contracts: - - [_**`InterchainERC20TokenGateway`**_](#InterchainERC20TokenGateway) - an abstract contract that implements logic shared between L1 and L2 gateways. -- Contracts: - - [**`AccessControl`**](https://github.com/OpenZeppelin/openzeppelin-contracts/blob/master/contracts/access/AccessControl.sol) - contract from the @openzeppelin package that allows children to implement role-based access. - - [**`BridgingManager`**](#BridgingManager) - contains administrative methods to retrieve and control the state of the bridging process. - - [**`BridgeableTokens`**](#BridgeableTokens) - contains the logic for validation of tokens used in the bridging process. - - [**`L1CrossDomainEnabled`**](#L1CrossDomainEnabled) - helper contract for contracts performing Ethereum to Arbitrum communication process via Retryable Tickets. - - [**`L1ERC20TokenGateway`**](#L1ERC20TokenGateway) - Ethereum's counterpart of the gateway to bridge registered ERC20 compatible tokens between Ethereum and Arbitrum chains. - - [**`L2CrossDomainEnabled`**](#L2Messenger) - helper contract to simplify Arbitrum to Ethereum communication process - - [**`L2ERC20TokenGateway`**](#L2ERC20TokenGateway) - Arbitrum's counterpart of the gateway to bridge registered ERC20 compatible tokens between Ethereum and Arbitrum chains - - [**`ERC20Bridged`**](#ERC20Bridged) - an implementation of the `ERC20` token with administrative methods to mint and burn tokens. - - [**`OssifiableProxy`**](#OssifiableProxy) - the ERC1967 proxy with extra admin functionality. - -## L1OutboundDataParser - -A helper library to parse data passed to `outboundTransfer()` of `L1ERC20TokenGateway`. - -### Functions - -#### `decode(address,bytes memory)` - -> **Visibility:**     `internal` -> -> **Mutability:**   `view` -> -> **Returns:**       `(address, uint256)` -> -> **Arguments:** -> -> - **`router_`** - an address of the Arbitrum's `L1GatewayRouter` -> - **`data_`** - bytes array encoded via the following rules: -> - If the `msg.sender` of the method is the `router_` address, `data_` must contain the result of the function call: `abi.encode(address from, abi.encode(uint256 maxSubmissionCost, bytes emptyData))`, where `emptyData` - is an empty bytes array. -> - In other cases, data must contain the result of the function call: `abi.encode(uint256 maxSubmissionCost, bytes emptyData)` where `emptyData` - is an empty bytes array. - -Decodes value contained in `data_` bytes array and returns decoded value: `(address from, uint256 maxSubmissionCost)`. Such encoding rules are required to be compatible with the `L1GatewaysRouter`. - -## L2OutboundDataParser - -A helper library to parse data passed to `outboundTransfer()` of `L2ERC20TokenGateway`. - -### Functions - -#### decode(address,bytes memory) - -> **Visibility:**     `internal` -> -> **Mutability:**   `view` -> -> **Returns**        `(address)` -> -> **Arguments:** -> -> - **`router_`** - an address of the Arbitrum's `L1GatewayRouter` -> - **`data_`** - bytes array encoded via the following rules: -> - If the `msg.sender` of the method is the `router_` address, `data_` must contain the result of the function call: `abi.encode(address from, bytes emptyData)`, where `emptyData` - is an empty bytes array. -> - In other cases, `data` must be empty bytes array. - -Decodes value contained in `data_` bytes array and returns decoded value: `(address from)`. Such encoding rules are required to be compatible with the `L2GatewaysRouter`. - -## BridgingManager - -- **inherits:** [`@openzeppelin/AccessControl`](https://github.com/OpenZeppelin/openzeppelin-contracts/blob/d4fb3a89f9d0a39c7ee6f2601d33ffbf30085322/contracts/access/AccessControl.sol) - -Contains administrative methods to retrieve and control the state of the bridging process. Allows to enable/disable withdrawals or deposits and check whether the gateway functionality is suspended or not. Allows granting standalone privileges to certain accounts to enable/disable deposits or withdrawals of the gateway. The rights to grant permissions have accounts with an admin role. - -### Constants - -- **DEPOSITS_ENABLER_ROLE** - a `bytes32` equal to a result of the `keccak256()` hashing of the string `"BridgingManager.DEPOSITS_ENABLER_ROLE"`. This role must be used when grants/revokes privileges to enable deposits. -- **DEPOSITS_DISABLER_ROLE** - a `bytes32` equal to a result of the `keccak256()` hashing of the string `"BridgingManager.DEPOSITS_DISABLER_ROLE"`. This role must be used when grants/revokes privileges to disable deposits. -- **WITHDRAWALS_ENABLER_ROLE** - a `bytes32` equal to a result of the `keccak256()` hashing of the string `"BridgingManager.WITHDRAWALS_ENABLER_ROLE"`. This role must be used when grants/revokes privileges to enable withdrawals. -- **WITHDRAWALS_DISABLER_ROLE** - a `bytes32` equal to a result of the `keccak256()` hashing of the string `"BridgingManager.WITHDRAWALS_DISABLER_ROLE"`. This role must be used when grants/revokes privileges to disable withdrawals. - -### Variables - -The contract uses the Unstructured Storage pattern to store the current state of the bridge using the struct `BridgingState`. `BridgingState` struct has the next type: - -```solidity= -struct BridgingState { - bool isInitialized; // Shows whether the contract is initialized or not. - bool isDepositsEnabled; // Stores the state of the deposits - bool isWithdrawalsEnabled; // Stores the state of the withdrawals -} -``` - -### Functions - -#### `initialize(address)` - -> **Visibility:**     `public` -> -> **Arguments:** -> -> - **`admin_`** - an address of the account to grant the `DEFAULT_ADMIN_ROLE` -> -> **Emits:** `RoleGranted(bytes32 indexed role, address indexed account, address indexed sender)` - -Initializes the contract to grant `DEFAULT_ADMIN_ROLE` to the `admin_` address. The method might be called only once. Reverts with error `ErrorAlreadyInitialized()` when called on the already initialized contract. Allows using this contract with the proxy pattern. - -#### `isDepositsEnabled()` - -> **Visibility:**     `public` -> -> **Mutability:**   `view` -> -> **Returns**        `(bool)` - -Returns whether the deposits enabled or not. - -#### `isWithdrawalsEnabled()` - -> **Visibility:**     `public` -> -> **Mutability:**   `view` -> -> **Returns**        `(bool)` - -Returns whether the withdrawals enabled or not. - -#### `enableDeposits()` - -> **Visibility:**     `external` -> -> **Modifiers:**    [`onlyRole(DEPOSITS_ENABLER_ROLE)`](https://github.com/OpenZeppelin/openzeppelin-contracts/blob/d4fb3a89f9d0a39c7ee6f2601d33ffbf30085322/contracts/access/AccessControl.sol#L69) -> -> **Emits:**           `DepositsEnabled(address account)` - -Enables the deposits if they are disabled. Reverts with the error `ErrorDepositsEnabled()` if deposits aren't enabled. Only accounts with the granted `DEPOSITS_ENABLER_ROLE` can call this method. - -#### `disableDeposits()` - -> **Visibility:**     `external` -> -> **Modifiers:**    [`whenDepositsEnabled`](#whenDepositsEnabled) [`onlyRole(DEPOSITS_DISABLER_ROLE)`](https://github.com/OpenZeppelin/openzeppelin-contracts/blob/d4fb3a89f9d0a39c7ee6f2601d33ffbf30085322/contracts/access/AccessControl.sol#L69) -> -> **Emits:**           `DepositsDisabled(address account)` - -Disables the deposits if they aren't disabled yet. Reverts with the error `ErrorDepositsDisabled()` if deposits have already disabled. Only accounts with the granted `DEPOSITS_DISABLER_ROLE` can call this method. - -#### `enableWithdrawals()` - -> **Visibility:**     `external` -> -> **Modifiers:**    [`onlyRole(WITHDRAWALS_ENABLER_ROLE)`](https://github.com/OpenZeppelin/openzeppelin-contracts/blob/d4fb3a89f9d0a39c7ee6f2601d33ffbf30085322/contracts/access/AccessControl.sol#L69) -> -> **Emits:**           `WithdrawalsEnabled(address account)` - -Enables the withdrawals if they are disabled. Reverts with the error `ErrorWithdrawalsEnabled()` if withdrawals are enabled. Only accounts with the granted `WITHDRAWALS_ENABLER_ROLE` can call this method. - -#### `disableWithdrawals()` - -> **Visibility:**     `external` -> -> **Modifiers:**    [`whenWithdrawalsEnabled`](#whenWithdrawalsEnabled)[`onlyRole(WITHDRAWALS_DISABLER_ROLE)`](https://github.com/OpenZeppelin/openzeppelin-contracts/blob/d4fb3a89f9d0a39c7ee6f2601d33ffbf30085322/contracts/access/AccessControl.sol#L69) -> -> **Emits:**           `WithdrawalsDisabled(address account)` - -Disables the withdrawals if they aren't disabled yet. Reverts with the error `ErrorWithdrawalsDisabled()` if withdrawals have already disabled. Only accounts with the granted `WITHDRAWALS_DISABLER_ROLE` can call this method. - -#### `_loadState()` - -> **Visibility:**     `private` -> -> **Mutability:**   `pure` -> -> **Returns**        `(BridgingState storage)` - -Loads and returns the `BridgingState` variable from the slot at address `keccak256("BridgingManager.bridgingState")`. - -### Modifiers - -#### `whenDepositsEnabled()` - -Validates that deposits are enabled. Reverts with the error `ErrorDepositsDisabled()` when called on contract with disabled deposits. - -#### `whenWithdrawalsEnabled()` - -Validates that withdrawals are enabled. Reverts with the error `ErrorWithdrawalsDisabled()` when called on contract with disabled withdrawals. - -## BridgeableTokens - -Contains the logic for validation of tokens used in the bridging process - -### Variables - -The contract keeps the addresses of L1/L2 tokens used in the bridging: - -- **`l1Token`** - an immutable address of the bridged token in the L1 chain -- **`l2Token`** - an immutable address of the token minted on the L2 chain when token bridged - -### Modifiers - -#### `onlySupportedL1Token(address l1Token_)` - -Validates that passed `l1Token_` is supported by the bridge. Reverts with error `ErrorUnsupportedL1Token()` when addresses mismatch. - -#### `onlySupportedL2Token(address l2Token_)` - -Validates that passed `l2Token_` is supported by the bridge. Reverts with error `ErrorUnsupportedL2Token()` when addresses mismatch. - -## InterchainERC20TokenGateway - -**Implements:** `IInterchainERC20TokenGateway` -**Inherits:** [`BridgingManager`](#BridgingManager) [`BridgeableTokens`](#BridgeableTokens) - -The contract keeps logic shared among both L1 and L2 gateways, adding the methods for bridging management: enabling and disabling withdrawals/deposits. - -### Variables - -The contract keeps the variables required by both L1/L2 gateways: - -- **`router`** - an address of the router in the corresponding chain -- **`counterpartGateway`** - an address of the counterpart gateway used in the bridging process - -All variables are declared as `immutable` to reduce transactions gas costs. - -### Functions - -#### `calculateL2TokenAddress(address)` - -> **Visibility:**     `external` -> -> **Mutability:**   `view` -> -> **Returns**        `(address)` -> -> **Arguments:** -> -> - **l1Token\_** - an address of the token on the Ethereum chain - -Returns an address of token, which will be minted on the Arbitrum chain, on `l1Token_` bridging. The current implementation returns the `l2Token` address when passed `l1Token_` equals to `l1Token` declared in the contract and `address(0)` in other cases. - -#### `getOutboundCalldata(address,address,address,uint256,bytes memory)` - -> **Visibility:**     `public` -> -> **Mutability:**   `view` -> -> **Returns**        `(bytes memory)` -> -> **Arguments:** -> -> - **`l1Token_`** - an address in the Ethereum chain of the token to bridge -> - **`from_`** - an address in the Ethereum chain of the account initiated bridging -> - **`to_`** - an address in the Ethereum chain of the recipient of the token on the corresponding chain -> - **`amount_`** - an amount of tokens to bridge -> - **`data_`** - Custom data to pass into finalizeInboundTransfer method. Unused, required to be compatible with @arbitrum/sdk package. - -Returns encoded transaction data to send into the corresponding gateway to finalize the tokens bridging process. The result of this method might be used to estimate the amount of ether required to pass to the `outboundTransfer()` method call. In the current implementation returns the transaction data of `finalizeInboundTransfer(token_, from_, to_, amount_)`. - -## L1CrossDomainEnabled - -A helper contract for contracts performing Ethereum to Arbitrum communication process via Retryable Tickets. - -### Variables - -The contract declares one immutable variable **`inbox_`** - an address of the Arbitrum's [`Inbox`](https://developer.offchainlabs.com/docs/sol_contract_docs/md_docs/arb-bridge-eth/bridge/inbox) contract - -### Functions - -#### `sendCrossDomainMessage(address, address, bytes memory, CrossDomainMessageOptions memory)` - -> **Visibility:**     `internal` -> -> **Returns**        `(uint256)` -> -> **Arguments**: -> -> - **`sender_`** - an address of the sender of the message. It's also the address to credit all excess ETH from gas and call-value on the Arbitrum chain. Call-value is refunded if the retryable ticket times out or is canceled. `sender_` is also the address with the right to cancel a Retryable Ticket. -> - **`recipient_`** - an address of the recipient of the message on the Arbitrum chain -> - **`data_`** - data passed to the `recipient_` in the message -> - **`msgOptions_`** - an instance of the `CrossDomainMessageOptions` struct. The `CrossDomainMessageOptions` struct has the following properties: -> - **`maxGas`** - gas limit for immediate L2 execution attempt (can be estimated via `NodeInterface.estimateRetryableTicket()`) -> - **`callValue`** - call-value for L2 transaction -> - **`gasPriceBid`** - L2 Gas price bid for immediate L2 execution attempt (queryable via standard `eth_gasPrice` RPC) -> - **`maxSubmissionCost`** - an amount of ETH allocated to pay for the base submission fee -> -> **Emits:** `TxToL2(address indexed from, address indexed to, uint256 indexed seqNum, bytes data)` - -Creates a Retryable Ticket via [`Inbox.createRetryableTicket`](https://github.com/OffchainLabs/arbitrum/blob/52356eeebc573de8c4dd571c8f1c2a6f5585f359/packages/arb-bridge-eth/contracts/bridge/Inbox.sol#L325) function using the provided arguments. Sends all passed ether with Retryable Ticket into Arbitrum chain. Reverts with error `ErrorETHValueTooLow()` if passed `msg.value` is less than `msgOptions_.callVaue + msgOptions_.maxSubmissionCost + (msgOptions_.maxGas * msgOptions_.gasPriceBid)` and with error `ErrorNoMaxSubmissionCost()` when `msgOptions_.maxSubmissionCost` is equal to 0. Returns a unique id of created Retryable Ticket. - -### Modifiers - -#### `onlyFromCrossDomainAccount(address crossDomainAccount_)` - -Validates that transaction was initiated by the `crossDomainAccount_` address from the L2 chain. Reverts with error `ErrorUnauthorizedBridge()` if called not by Arbitrum's bridge and with error `ErrorWrongCrossDomainSender()` if the transaction was sent not from the `crossDomainAccount_` address. - -## L1ERC20TokenGateway - -- **Inherits**: [`InterchainERC20TokenGateway`](#InterchainERC20TokenGateway) [`L1CrossDomainEnabled`](#L1CrossDomainEnabled) -- **Implements**: `IL1TokenGateway` - -Contract implements `ITokenGateway` interface and with counterpart `L2TokensGatewy` allows bridging registered ERC20 compatible tokens between Ethereum and Arbitrum chains. The contract is compatible with `L1GatewayRouter` and might be used to transfer tokens via the "canonical" Arbitrum's bridge. - -Additionally, the contract provides administrative methods to temporarily disable bridging from Ethereum to Arbitrum via the `BridgingManager` contract. - -### Functions - -#### `outboundTransfer(address,address,uint256,uint256, uint256,bytes calldata)` - -> **Visibility:**     `external` -> -> **Mutability:**   `payble` -> -> **Modifiers:**    [`whenDepositsEnabled()`](#whenDepositsEnabled) [`onlySupportedL1Token(l1Token_)`](#onlySupportedL1Tokenaddress-l1Token_) -> -> **Returns**        `(bytes memory)` -> -> **Arguments:** -> -> - **l1Token\_** - an address in the Ethereum chain of the token to bridge. It must be equal to the `l1Token` address. The method will be reverted with the error `ErrorUnsupportedL1Token()` if would be called with a different address. -> - **to\_** - an address of the recipient of the token on the corresponding chain -> - **amount\_** - an amount of tokens to bridge. The user has to approve spending of the `l1Token` for the gateway or the transaction will be reverted. -> - **maxGas\_** - a gas limit for immediate L2 execution attempt (can be estimated via `_NodeInterface.estimateRetryableTicket`). -> - **gasPriceBid\_** - an L2 gas price bid for immediate L2 execution attempt (queryable via standard eth\*gasPrice RPC). -> - **data** - stores an additional data required for the transaction. Data will be decoded via the `L1OutboundDataParser.decode()` method to retrieve the `maxSubmissionCost` value and `from` address, where `from` - contains an address of the sender, and `maxSubmissionCost` - is an amount of ETH allocated to pay for the base submission fee. -> -> **Emits:** `DepositInitiated(address l1Token, address indexed from, address indexed to, uint256 indexed sequenceNumber, uint256 amount)` - -Initiates the tokens bridging from the Ethereum into the Arbitrum chain. Escrows the `amount_` of `l1Token_` from the user on the address of the gateway and creates a Retryable Ticket via the `sendCrossDomainMessage()` method: - -```solidity= -sendCrossDomainMessage( - counterpartGateway, // recipient - getOutboundCalldata(l1Token, from, to, amount, ""), // data - CrossDomainMessageOptions({ - maxGas: maxGas, - callValue: 0, - gasPriceBid: gasPriceBid_, - refundAddress: from, - maxSubmissionCost: maxSubmissionCost - }) -) -``` - -Returns an encoded value of the id for created Retryable Ticket. Same value is used as `sequenceNumber` in `DepositInitiated` event. - -#### `finalizeInboundTransfer(address,address,address,uint256,bytes calldata)` - -> **Visibility:**     `internal` -> -> **Modifiers:**    [`whenWithdrawalsEnabled()`](#whenWithdrawalsEnabled) [`onlySupportedL1Token(l1Token_)`](#onlySupportedL1Tokenaddress-l1Token_) [`onlyFromCrossDomainAccount(counterpartGateway)`](#onlyFromCrossDomainAccountaddress-crossDomainAccount_) -> -> **Arguments:** -> -> - **`l1Token_`** - an address in the Ethereum chain of the token to withdraw -> - **`from_`** - an address of the account initiated bridging -> - **`to_`** - an address of the recipient of the tokens -> - **`amount_`** - an amount of tokens to withdraw -> - **`data_`** - unused variable, required to be compatible with `L1GatewayRouter` and `L2GatewayRouter` -> -> **Emits:** `WithdrawalFinalized(address l1Token, address indexed from, address indexed to, uint256 indexed exitNum, uint256 amount)` - -This method is called to finalize the withdrawal of the tokens from the L2 chain. It transfers the `amount_` of tokens from the gateway to the `to_` address via `safeTransfer()` method. - -**Note**: `exitNum` - always is equal to 0 in the `WithdrawalFinalized` event cause the current implementation doesn't support fast withdraws. To read more about fast withdrawals, see [Offchain Labs Docs](https://developer.offchainlabs.com/docs/withdrawals). - -## L2CrossDomainEnabled - -A helper contract to simplify Arbitrum to Ethereum communication process. - -### Variables - -The contract declares one immutable variable **`arbSys`** - an address of the Arbitrum's [`ArbSys`](https://developer.offchainlabs.com/docs/arbsys) contract - -### Functions - -#### `sendCrossDomainMessage(address,address,bytes memory)` - -> **Visibility:**     `internal` -> -> **Returns**        `(uint256)` -> -> **Arguments**: -> -> - **`sender_`** - an address of the sender of the message -> - **`recipient_`** - an address of the recipient of the message on the Ethereum chain -> - **`data_`** - Data passed to the `recipient_` in the message -> -> **Emits**: `event TxToL1(address indexed from, address indexed to, uint256 indexed id, bytes data)` - -Sends the message to the Ethereum chain via `ArbSys.sendTxToL1()` method. - -#### `applyL1ToL2Alias(address)` - -> **Visibility:**     `private` -> -> **Returns**        `(address)` -> -> **Arguments**: -> -> - **`l1Address_`** - an L1 address to apply aliasing - -Applies the [Arbitrum's L1 -> L2 aliasing](https://developer.offchainlabs.com/docs/l1_l2_messages#address-aliasing) to the address. - -### Modifiers - -#### `onlyFromCrossDomainAccount(address crossDomainAccount_)` - -Validates that the `msg.sender` is equal to the `crossDomainAccount_` with applied [Arbitrum's aliasing](https://developer.offchainlabs.com/docs/l1_l2_messages#address-aliasing). Reverts with the error `ErrorWrongCrossDomainSender()` if validation fails. - -## L2ERC20TokenGateway - -- **Inherits**: [`InterchainERC20TokenGateway`](#InterchainERC20TokenGateway) [`L2CrossDomainEnabled`](#L2CrossDomainEnabled) -- **Implements**: `IL2TokenGateway` - -Contract implements `ITokenGateway` interface and with counterpart `L1ERC20TokenGateway` allows bridging registered ERC20 compatible tokens between Arbitrum and Ethereum chains. The contract is compatible with `L2GatewayRouter` and might be used to transfer tokens via the “canonical” Arbitrum’s bridge. - -Additionally, the contract provides administrative methods to temporarily disable bridging from Arbitrum to Ethereum via the `BridgingManager` contract. - -### Functions - -#### `outboundTransfer(address,address,uint256,uint256, uint256,bytes memory)` - -> **Visibility:**     `external` -> -> **Modifiers:**    [`whenWithdrawalsEnabled()`](#whenWithdrawalsEnabled) [`onlySupportedL1Token(l1Token_)`](#onlySupportedL1Tokenaddress-l1Token_) -> -> **Returns**        `(bytes memory)` -> -> **Arguments:** -> -> - **l1Token\_** - an address in the Ethereum chain of the token to bridge. It must be equal to the `l1Token` address. The method will be reverted with the error `ErrorUnsupportedL1Token()` if would be called with a different address. -> - **to\_** - an address of the recipient of the token on the corresponding chain -> - **amount\_** - an amount of tokens to bridge. The user has to approve spending of the `l1Token` for the gateway or the transaction will be reverted. -> - **maxGas\_** - Doesn't used -> - **gasPriceBid\_** - Doesn't used -> - **data** - stores an additional data required for transaction. Data will be decoded via `L2OutboundDataParser.decode()` method to retrieve `from` address - an address of the sender. -> -> **Emits:** `WithdrawalInitiated(address l1Token, address indexed from, address indexed to, uint256 indexed l2ToL1Id, uint256 exitNum, uint256 amount)` - -Initiates the withdrawing process from the Arbitrum chain into the Ethereum chain. The method burns the `amount_` of `l2Token` on the `from_` account, sends message to the Ethereum chain via `sendCrossDomainMessage()` method: - -```solidity= -sendCrossDomainMessage( - counterpartGateway, - getOutboundCalldata(l1Token_, from_, to_, amount_, "") -); -``` - -Returns encoded value of the unique id for L2-to-L1 transaction. Same value is used as `l2ToL1Id` in the `WithdrawalInitiated` event. - -**Note**: `exitNum` - always is equal to 0 in the `WithdrawalInitiated` event cause the current implementation doesn't support fast withdraws. To read more about fast withdrawals, see [Offchain Labs Docs](https://developer.offchainlabs.com/docs/withdrawals). - -#### `finalizeInboundTransfer(address,address,address,uint256,bytes calldata)` - -> **Visibility:**     `internal` -> -> **Modifiers:**    [`whenDepositsEnabled()`](#whenDepositsEnabled) [`onlySupportedL1Token(l1Token_)`](#onlySupportedL1Tokenaddress-l1Token_) [`onlyFromCrossDomainAccount(counterpartGateway)`](#onlyFromCrossDomainAccountaddress-crossDomainAccount_1) -> -> **Arguments:** -> -> - **`l1Token_`** - an address in the Ethereum chain of the token to bridge -> - **`from_`** - an address of the account initiated bridging -> - **`to_`** - an address of the recipient of the tokens -> - **`amount_`** - an amount of tokens to bridge -> - **`data_`** - unused variable, required to be compatible with `L1GatewayRouter` and `L2GatewayRouter` -> -> **Emits:** `DepositFinalized(address indexed l1Token, address indexed from, address indexed to, uint256 amount)` - -This method is called on the finalizing of the bridging from the Ethereum chain. This method mints the `amount_` of `l2Token` token to the `to_` address. - -## `ERC20Metadata` - -Contains optional methods for the `ERC20` tokens. It uses the UnstructuredStorage pattern to store strings with name and symbol info. Might be used with the upgradable proxies. - -### Variables - -Contract declares `public` and `immutable` variable **`decimals`** of type `uint8`. - -The `name` and `symbol` info are stored in the structure: - -```solidity= -struct DynamicMetadata { - string name; - string symbol; -} -``` - -### Funcations - -#### `name()` - -> **Visibility:**     `external` -> -> **Mutability:**   `view` -> -> **Returns**        `(string memory)` - -Returns the name of the token. - -#### `symbol()` - -> **Visibility:**     `external` -> -> **Mutability:**   `view` -> -> **Returns**        `(string memory)` - -Returns the symbol of the token. - -#### `_setERC20MetadataName(string memory)` - -> **Visibility:**     `internal` -> -> **Arguments:** -> -> - **`name_`** - string with name of the token - -Sets the `name` of the token. Might be called only when the `name` is empty. - -#### `_setERC20MetadataSymbol(string memory)` - -> **Visibility:**     `internal` -> -> **Arguments:** -> -> - **`symbol_`** - string with symbol of the token - -Sets the `symbol` of the token. Might be called only when the `symbol` is empty. - -#### `_loadDynamicMetadata()` - -> **Visibility:**     `private` -> -> **Mutability:**   `pure` -> -> **Returns**        `(DynamicMetadata storage r)` - -Returns the reference to the slot with `DynamicMetadta` struct - -## `ERC20Core` - -- **Implements:** [`@openzeppelin/IERC20`](https://github.com/OpenZeppelin/openzeppelin-contracts/blob/d4fb3a89f9d0a39c7ee6f2601d33ffbf30085322/contracts/token/ERC20/IERC20.sol) - -Contains the required variables and logic of the `ERC20` token. The contract is a slightly modified version of the [`ERC20`](https://github.com/OpenZeppelin/openzeppelin-contracts/blob/d4fb3a89f9d0a39c7ee6f2601d33ffbf30085322/contracts/token/ERC20/ERC20.sol) contract from the OpenZeppelin package. - -### Variables - -Contract declares the following variables to store state of the token: - -- **`uint256 public totalSupply`** - the total supply of the token -- **`mapping(address => uint256) public balanceOf`** - stores balances of the token holders -- **`mapping(address => mapping(address => uint256)) public allowance`** - stores allowances of the token holders - -### Functions - -#### `approve(address,uint256)` - -> **Visibility:**     `external` -> -> **Returns**        `(bool)` -> -> **Arguments:** -> -> - **`spender_`** - an address of the tokens spender -> - **`amount_`** - a number of tokens to allow to spend -> -> **Emits:** `Approval(address indexed owner, address indexed spender, uint256 value)` - -Allows _spender to withdraw from the `msg.sender` account multiple times, up to the `amount_`. If this function is called again it overwrites the current allowance with `amount\_`. Returns a `bool` value indicating whether the operation succeeded. - -#### `transfer(address,uint256)` - -> **Visibility:**     `external` -> -> **Returns**        `(bool)` -> -> **Arguments:** -> -> - **`to_`** - an address of the recipient of the tokens -> - **`amount_`** - a number of tokens to transfer -> -> **Emits:** `Transfer(address indexed from, address indexed to, uint256 value)` - -Transfers `amount` of tokens from sender to `to` account. -Returns a `bool` value indicating whether the operation succeeded. - -#### `transferFrom(address,address,uint256)` - -> **Visibility:**     `external` -> -> **Returns**        `(bool)` -> -> **Arguments:** -> -> - **`from_`** - an address to transfer tokens from -> - **`to_`** - an address of the recipient of the tokens -> - **`amount_`** - a number of tokens to transfer -> -> **Emits:** `Transfer(address indexed from, address indexed to, uint256 value)` `Approval(address indexed owner, address indexed spender, uint256 value)` - -Transfers `amount` of token from the `from_` account to `to_` using the allowance mechanism. `amount_` is then deducted from the caller's allowance. Returns a `bool` value indicating whether the operation succeed. - -#### `increaseAllowance(address,uint256)` - -> **Visibility:**     `external` -> -> **Returns**        `(bool)` -> -> **Arguments:** -> -> - **`spender_`** - an address of the tokens spender -> - **`addedValue_`** - a number to increase allowance -> -> **Emits:** `Approval(address indexed owner, address indexed spender, uint256 value)` -Atomically increases the allowance granted to `spender` by the caller. Returns a `bool` value indicating whether the operation succeed. - -#### `decreaseAllowance(address,uint256)` - -> **Visibility:**     `external` -> -> **Returns**        `(bool)` -> -> **Arguments:** -> -> - **`spender_`** - an address of the tokens spender -> - **`subtractedValue_`** - a number to decrease allowance -> -> **Emits:** `Approval(address indexed owner, address indexed spender, uint256 value)` -Atomically decreases the allowance granted to `spender` by the caller. Returns a `bool` value indicating whether the operation succeed. - -## `ERC20Bridged` - -**Implements:** [`IERC20Bridged`](https://github.com/lidofinance/lido-l2/blob/main/contracts/token/interfaces/IERC20Bridged.sol) -**Inherits:** [`ERC20Metadata`](#ERC20Metadata) [`ERC20Core`](#ERC20CoreLogic) - -Inherits the `ERC20` default functionality that allows the bridge to mint and burn tokens. - -### Variables - -Contract declares an immutable variable **`bridge`** which can mint/burn the token. - -### Functions - -#### `mint(address,uint256)` - -> **Visibility:**     `external` -> -> **Modifiers:**    [`onlyBridge`](#onlybridge) -> -> **Arguments:** -> -> - **`account_`** - an address of the tokens recipient -> - **`amount_`** - a number to mint -> -> **Emits:** `Transfer(address indexed from, address indexed to, uint256 value)` - -Mints the `amount_` of tokens to the `account_`. The method might be called only by the bridge. Reverts with the error `ErrorNotBridge()` when called not by bridge. - -#### `burn(address,uint256)` - -> **Visibility:**     `external` -> -> **Modifiers:**    [`onlyBridge`](#onlybridge) -> -> **Arguments:** -> -> - **`account_`** - an address of the tokens recipient -> - **`amount_`** - a number to burn -> -> **Emits:** `Transfer(address indexed from, address indexed to, uint256 value)` - -Destroys the `amount_` of tokens from the `account_`. The method might be called only by the bridge. Reverts with the error `ErrorNotBridge()` when called not by bridge. - -### Modifiers - -#### `onlyBridge()` - -Validates that the `msg.sender` of the method is the `bridge`. Reverts with error `ErrorNotBridge()` in other cases. - -## `OssifiableProxy` - -- **Inherits:** [`@openzeppelin/ERC1967Proxy`](https://github.com/OpenZeppelin/openzeppelin-contracts/blob/d4fb3a89f9d0a39c7ee6f2601d33ffbf30085322/contracts/proxy/ERC1967/ERC1967Proxy.sol) - -Extends the [`ERC1967Proxy`](https://github.com/OpenZeppelin/openzeppelin-contracts/blob/d4fb3a89f9d0a39c7ee6f2601d33ffbf30085322/contracts/proxy/ERC1967/ERC1967Proxy.sol) contract from the OpenZeppelin package and adds some admin methods. In contrast to [`UUPSUpgradableProxy`](https://github.com/OpenZeppelin/openzeppelin-contracts/blob/d4fb3a89f9d0a39c7ee6f2601d33ffbf30085322/contracts/proxy/utils/UUPSUpgradeable.sol), it doesn't increase the inheritance chain of the implementation contracts. And allows saving one extra `SLOAD` operation on every user request in contrast to [`TransparentUpgradeableProxy`](https://github.com/OpenZeppelin/openzeppelin-contracts/blob/d4fb3a89f9d0a39c7ee6f2601d33ffbf30085322/contracts/proxy/transparent/TransparentUpgradeableProxy.sol). But adding any external methods to the `ERC1967Proxy` creates the risk of selectors clashing, as described in the OpenZepplin [proxies docs](https://docs.openzeppelin.com/upgrades-plugins/1.x/proxies#transparent-proxies-and-function-clashes). To avoid the risk of clashing, the implementation upgrade process must contain a step with a search of the collisions between proxy and implementation. - -### Functions - -#### `proxy__getAdmin()` - -> **Visibility:**     `external` -> -> **Mutability:**   `view` -> -> **Returns**        `(address)` - -Returns the admin of the proxy. - -#### `proxy__getImplementation()` - -> **Visibility:**     `external` -> -> **Mutability:**   `view` -> -> **Returns**        `(address)` - -Returns the address of the implementation. - -#### `proxy__getIsOssified()` - -> **Visibility:**     `external` -> -> **Mutability:**   `view` -> -> **Returns**        `(bool)` - -Returns whether the proxy is ossified or not. - -#### `proxy__ossify()` - -> **Visibility:**     `external` -> -> **Modifiers:**    [`onlyAdmin`](#onlyAdmin) -> -> **Emits:**           `AdminChanged(address previousAdmin, address newAdmin)` - -Allows to transfer admin rights to zero address and prevent future upgrades of the proxy. - -#### `proxy__changeAdmin(address)` - -> **Visibility:**     `external` -> -> **Modifiers:**    [`onlyAdmin`](#onlyAdmin) -> -> **Arguments:** -> -> - **`newAdmin_`** - an address of the new admin. Must not be zero address. -> -> **Emits:** `AdminChanged(address previousAdmin, address newAdmin)` - -Changes the admin of the proxy. Reverts with message "ERC1967: new admin is the zero address" if `newAdmin_` is zero address. - -#### `proxy__upgradeTo(address)` - -> **Visibility:**     `external` -> -> **Modifiers:**    [`onlyAdmin`](#onlyAdmin) -> -> **Arguments:** -> -> - **`newImplementation_`** - an address of the new implementation. Must be a contract. -> -> **Emits:** `Upgraded(address indexed implementation)` - -Upgrades the implementation of the proxy. Reverts with the error "ERC1967: new implementation is not a contract" if the `newImplementation_` is not a contract. - -#### `proxy__upgradeToAndCall(address,bytes memory,bool)` - -> **Visibility:**     `external` -> -> **Modifiers:**    [`onlyAdmin`](#onlyAdmin) -> -> **Arguments:** -> -> - **`newImplementation_`** - an address of the new implementation. Must be a contract. -> - **`setupCalldata_`** - a data to pass into setup call after implementation upgrade. -> - **`forceCall_`** - forces make delegate call to the implementation even with empty `setupCalldata_` -> -> **Emits:** `Upgraded(address indexed implementation)` - -Upgrades the implementation of the proxy with an additional setup call. Reverts with the error "ERC1967: new implementation is not a contract" if the `newImplementation_` is not a contract. If `setupCalldata_.length` equals zero setup step will be skipped, if forceCall is false. - -### Modifiers - -#### `onlyAdmin()` - -Validates that that proxy is not ossified and that method is called by the admin of the proxy. Reverts with error `ErrorProxyIsOssified()` when called on ossified contract and with error `ErrorNotAdmin()` when called not by admin. - -## Deployment Process - -To reduce the gas costs for users, contracts `L1ERC20TokenGateway`, `L2ERC20TokenGateway`, and `L2TokensToken` use immutable variables as much as possible. But some of those variables are cross-referred. For example, `L1ERC20TokenGateway` has reference to `L2ERC20TokenGateway` and vice versa. As we use proxies, we can deploy proxies at first and stub the implementation with an empty contract. Then deploy actual implementations with addresses of deployed proxies and then upgrade proxies with new implementations. For stub might be used next contract: - -``` -pragma solidity ^0.8.0; -contract EmptyContract {} -``` - -Another option - pre-calculate the future address of the deployed contract offchain and deployed the implementation using pre-calculated addresses. But it is less fault-tolerant than the solution with an implementation stub. - -## Integration Risks - -As an additional link in the tokens flow chain, the Arbitrum and gateways possibly add points of failure. Below are the main risks of the current integration: - -### Minting of uncollateralized `L2Token` - -Such an attack might happen if an attacker obtains the right to call `L2ERC20TokenGateway.finalizeOutboundTransfer()` directly to mint uncollateralized L2Token. In such a scenario, an attacker can mint tokens on L2 and initiate withdrawal of those tokens. - -The best way to detect such an attack is an offchain monitoring of the minting and depositing/withdrawal events. Based on such events might be tracked following stats: - -- `l1GatewayBalance` - a total number of locked tokens on the L1 gateway -- `l2TokenTotalSupply` - total number of minted L2 tokens -- `l2TokenNotWithdrawn` - total number of burned L2 tokens which aren't withdrawn from the L1 gateway - -At any time following invariant must be sutisfied: `l1GatewayBalance == l2TokenTotalSupply + l2TokenNotWithdrawn`. - -In the case of invariant violation, Lido will have a dispute period to suspend L1 and L2 gateways. Paused gateways forbid minting of L2Token and withdrawing of minted tokens till the resolution of the issue. - -### Attack on fraud-proof system - -Such an attack might be seeking to take control over validators or abuse the fraud-proof system to submit incorrect state root. In such a case, the proposed incorrect block will be subject to a dispute period. Lido may run its validator with a "watchtower" strategy, which will ring the alarm when an invalid block is proposed. When it happens, the gateway must be suspended to protect users from potential funds lost till the resolution of the issue. - -### Attack on `L1GatewaysRouter` - -Theoretical situation, when an attacker takes control over `L1GatewaysRouter` and replaces an address of the gateway responsible for token bridging on some malicious contract. It potentially allows to steal the tokens transferred after the gateway substitution. To react to such an attack fastly, Lido has to monitor the `GatewaySet` event with the address of the Lido token. In case such an event was emitted, the Offchain Labs Team must be reached out to investigate the details and fix an issue asap to minimize the damage. diff --git a/test/arbitrum/_launch.test.ts b/test/arbitrum/_launch.test.ts deleted file mode 100644 index 504fdde9..00000000 --- a/test/arbitrum/_launch.test.ts +++ /dev/null @@ -1,89 +0,0 @@ -import { assert } from "chai"; - -import env from "../../utils/env"; -import arbitrum from "../../utils/arbitrum"; -import { L1ERC20ExtendedTokensBridge__factory } from "../../typechain"; -import { wei } from "../../utils/wei"; -import testing, { scenario } from "../../utils/testing"; -import { BridgingManagerRole } from "../../utils/bridging-management"; - -const REVERT = env.bool("REVERT", true); - -scenario("Arbitrum :: Launch integration test", ctx) - .after(async (ctx) => { - if (REVERT) { - await ctx.l1Provider.send("evm_revert", [ctx.snapshot.l1]); - await ctx.l2Provider.send("evm_revert", [ctx.snapshot.l2]); - } else { - console.warn( - "Revert is skipped! Forked node restart might be required for repeated launches!" - ); - } - }) - - .step("Enable deposits", async (ctx) => { - const { l1ERC20TokenGateway } = ctx; - assert.isFalse(await l1ERC20TokenGateway.isDepositsEnabled()); - - await l1ERC20TokenGateway.enableDeposits(); - assert.isTrue(await l1ERC20TokenGateway.isDepositsEnabled()); - }) - - .step("Renounce role", async (ctx) => { - const { l1ERC20TokenGateway, l1DevMultisig } = ctx; - assert.isTrue( - await l1ERC20TokenGateway.hasRole( - BridgingManagerRole.DEPOSITS_ENABLER_ROLE.hash, - await l1DevMultisig.getAddress() - ) - ); - - await l1ERC20TokenGateway.renounceRole( - BridgingManagerRole.DEPOSITS_ENABLER_ROLE.hash, - await l1DevMultisig.getAddress() - ); - assert.isFalse( - await l1ERC20TokenGateway.hasRole( - BridgingManagerRole.DEPOSITS_ENABLER_ROLE.hash, - await l1DevMultisig.getAddress() - ) - ); - }) - - .run(); - -async function ctx() { - const networkName = env.network("TESTING_ARB_NETWORK", "mainnet"); - const { l1Provider, l2Provider, l1ERC20TokenGateway } = await arbitrum - .testing(networkName) - .getIntegrationTestSetup(); - - const hasDeployedContracts = testing.env.USE_DEPLOYED_CONTRACTS(false); - const l1DevMultisig = hasDeployedContracts - ? await testing.impersonate(testing.env.L1_DEV_MULTISIG(), l1Provider) - : testing.accounts.deployer(l1Provider); - - const l1Snapshot = await l1Provider.send("evm_snapshot", []); - const l2Snapshot = await l2Provider.send("evm_snapshot", []); - - await testing.setBalance( - await l1DevMultisig.getAddress(), - wei.toBigNumber(wei`1 ether`) - ); - - const l1ERC20TokenGatewayImpl = L1ERC20ExtendedTokensBridge__factory.connect( - l1ERC20TokenGateway.address, - l1DevMultisig - ); - - return { - l1Provider, - l2Provider, - l1DevMultisig, - l1ERC20TokenGateway: l1ERC20TokenGatewayImpl, - snapshot: { - l1: l1Snapshot, - l2: l2Snapshot, - }, - }; -} diff --git a/utils/arbitrum/testing.ts b/utils/arbitrum/testing.ts deleted file mode 100644 index 081628a5..00000000 --- a/utils/arbitrum/testing.ts +++ /dev/null @@ -1,337 +0,0 @@ -import { - IERC20__factory, - ERC20BridgedStub__factory, - L1ERC20TokenGateway__factory, - L2ERC20TokenGateway__factory, - ArbSysStub__factory, - ERC20Bridged__factory, -} from "../../typechain"; -import contracts from "./contracts"; -import addresses from "./addresses"; -import deployment from "./deployment"; -import testingUtils from "../testing"; -import { BridgingManagement } from "../bridging-management"; -import network, { NetworkName, SignerOrProvider } from "../network"; -import { JsonRpcProvider } from "@ethersproject/providers"; - -export default function testing(networkName: NetworkName) { - const defaultArbAddresses = addresses(networkName); - const ethArbNetworks = network.multichain(["eth", "arb"], networkName); - - const [ethProviderForking, arbProviderForking] = ethArbNetworks.getProviders({ - forking: true, - }); - - return { - async getAcceptanceTestSetup() { - const gatewayContracts = await loadDeployedGateways( - ethProviderForking, - arbProviderForking - ); - - const { L1GatewayRouter, L2GatewayRouter } = contracts(networkName, { - customAddresses: loadGatewayRouterAddresses(networkName), - forking: true, - }); - - return { - l1Provider: ethProviderForking, - l2Provider: arbProviderForking, - ...gatewayContracts, - l1GatewayRouter: L1GatewayRouter, - l2GatewayRouter: L2GatewayRouter, - }; - }, - async getIntegrationTestSetup() { - const hasDeployedContracts = - testingUtils.env.USE_DEPLOYED_CONTRACTS(false); - - const gatewayContracts = hasDeployedContracts - ? await loadDeployedGateways(ethProviderForking, arbProviderForking) - : await deployTestGateway( - networkName, - ethProviderForking, - arbProviderForking - ); - - const [l1ERC20TokenGatewayAdminAddress] = - await BridgingManagement.getAdmins( - gatewayContracts.l1ERC20TokenGateway - ); - - const [l2ERC20TokenGatewayAdminAddress] = - await BridgingManagement.getAdmins( - gatewayContracts.l2ERC20TokenGateway - ); - - const customGatewayRouterAddresses = hasDeployedContracts - ? loadGatewayRouterAddresses(networkName) - : undefined; - - const { - L1GatewayRouter: l1GatewayRouter, - L2GatewayRouter: l2GatewayRouter, - } = contracts(networkName, { - customAddresses: customGatewayRouterAddresses, - forking: true, - }); - - const l1TokensHolder = hasDeployedContracts - ? await testingUtils.impersonate( - testingUtils.env.L1_TOKENS_HOLDER(), - ethProviderForking - ) - : testingUtils.accounts.deployer(ethProviderForking); - - if (hasDeployedContracts) { - await printLoadedTestConfig( - networkName, - l1TokensHolder, - gatewayContracts, - { l1GatewayRouter, l2GatewayRouter } - ); - } - - // if the L1 bridge admin is a contract, remove it's code to - // make it behave as EOA - await ethProviderForking.send("hardhat_setCode", [ - l1ERC20TokenGatewayAdminAddress, - "0x", - ]); - - // same for the L2 bridge admin - await arbProviderForking.send("hardhat_setCode", [ - l2ERC20TokenGatewayAdminAddress, - "0x", - ]); - - const { ArbSysStub } = contracts(networkName, { forking: true }); - - return { - l1GatewayRouter, - l2GatewayRouter, - l1Provider: ethProviderForking, - l2Provider: arbProviderForking, - l1TokensHolder, - ...gatewayContracts, - arbSysStub: ArbSysStub, - l1ERC20TokenGatewayAdmin: await testingUtils.impersonate( - l1ERC20TokenGatewayAdminAddress, - ethProviderForking - ), - l2ERC20TokenGatewayAdmin: await testingUtils.impersonate( - l2ERC20TokenGatewayAdminAddress, - arbProviderForking - ), - }; - }, - async getE2ETestSetup() { - const testerPrivateKey = testingUtils.env.TESTING_PRIVATE_KEY(); - - const [l1Provider, l2Provider] = ethArbNetworks.getProviders({ - forking: false, - }); - - const [l1Tester, l2Tester] = ethArbNetworks.getSigners(testerPrivateKey, { - forking: false, - }); - - const gatewayContracts = await loadDeployedGateways(l1Tester, l2Tester); - - const { - L1GatewayRouter: l1GatewayRouter, - L2GatewayRouter: l2GatewayRouter, - } = contracts(networkName, { - customAddresses: loadGatewayRouterAddresses(networkName), - forking: true, - }); - - await printLoadedTestConfig(networkName, l1Tester, gatewayContracts, { - l1GatewayRouter, - l2GatewayRouter, - }); - - return { - l1Tester, - l2Tester, - l1Provider, - l2Provider, - l1GatewayRouter, - l2GatewayRouter, - ...gatewayContracts, - }; - }, - async stubArbSysContract() { - const deployer = testingUtils.accounts.deployer(arbProviderForking); - const stub = await new ArbSysStub__factory(deployer).deploy(); - const stubBytecode = await arbProviderForking.send("eth_getCode", [ - stub.address, - ]); - - await arbProviderForking.send("hardhat_setCode", [ - defaultArbAddresses.ArbSys, - stubBytecode, - ]); - }, - }; -} - -async function deployTestGateway( - networkName: NetworkName, - ethProvider: JsonRpcProvider, - arbProvider: JsonRpcProvider -) { - const ethDeployer = testingUtils.accounts.deployer(ethProvider); - const arbDeployer = testingUtils.accounts.deployer(arbProvider); - - const l1Token = await new ERC20BridgedStub__factory(ethDeployer).deploy( - "Test Token", - "TT" - ); - - const [ethDeployScript, arbDeployScript] = await deployment( - networkName - ).erc20TokenGatewayDeployScript( - l1Token.address, - { - deployer: ethDeployer, - admins: { proxy: ethDeployer.address, bridge: ethDeployer.address }, - }, - { - deployer: arbDeployer, - admins: { proxy: arbDeployer.address, bridge: arbDeployer.address }, - } - ); - - await ethDeployScript.run(); - await arbDeployScript.run(); - - const l1ERC20ExtendedTokensBridgeProxyDeployStepIndex = 1; - const l1BridgingManagement = new BridgingManagement( - ethDeployScript.getContractAddress(l1ERC20ExtendedTokensBridgeProxyDeployStepIndex), - ethDeployer - ); - - const l2ERC20ExtendedTokensBridgeProxyDeployStepIndex = 3; - const l2BridgingManagement = new BridgingManagement( - arbDeployScript.getContractAddress(l2ERC20ExtendedTokensBridgeProxyDeployStepIndex), - arbDeployer - ); - - await l1BridgingManagement.setup({ - bridgeAdmin: ethDeployer.address, - depositsEnabled: true, - withdrawalsEnabled: true, - }); - - await l2BridgingManagement.setup({ - bridgeAdmin: arbDeployer.address, - depositsEnabled: true, - withdrawalsEnabled: true, - }); - - return { - l1Token: l1Token.connect(ethProvider), - ...connectGatewayContracts( - { - l2Token: arbDeployScript.getContractAddress(1), - l1ERC20TokenGateway: ethDeployScript.getContractAddress(1), - l2ERC20TokenGateway: arbDeployScript.getContractAddress(3), - }, - ethProvider, - arbProvider - ), - }; -} - -async function loadDeployedGateways( - l1SignerOrProvider: SignerOrProvider, - l2SignerOrProvider: SignerOrProvider -) { - return { - l1Token: IERC20__factory.connect( - testingUtils.env.ARB_L1_TOKEN(), - l1SignerOrProvider - ), - ...connectGatewayContracts( - { - l2Token: testingUtils.env.ARB_L2_TOKEN(), - l1ERC20TokenGateway: testingUtils.env.ARB_L1_ERC20_TOKEN_GATEWAY(), - l2ERC20TokenGateway: testingUtils.env.ARB_L2_ERC20_TOKEN_GATEWAY(), - }, - l1SignerOrProvider, - l2SignerOrProvider - ), - }; -} - -function connectGatewayContracts( - addresses: { - l2Token: string; - l1ERC20TokenGateway: string; - l2ERC20TokenGateway: string; - }, - l1SignerOrProvider: SignerOrProvider, - l2SignerOrProvider: SignerOrProvider -) { - const l1ERC20TokenGateway = L1ERC20TokenGateway__factory.connect( - addresses.l1ERC20TokenGateway, - l1SignerOrProvider - ); - const l2ERC20TokenGateway = L2ERC20TokenGateway__factory.connect( - addresses.l2ERC20TokenGateway, - l2SignerOrProvider - ); - const l2Token = ERC20Bridged__factory.connect( - addresses.l2Token, - l2SignerOrProvider - ); - return { - l2Token, - l1ERC20TokenGateway, - l2ERC20TokenGateway, - }; -} - -function loadGatewayRouterAddresses(networkName: NetworkName) { - const defaultArbAddresses = addresses(networkName); - return { - L1GatewayRouter: testingUtils.env.ARB_L1_GATEWAY_ROUTER( - defaultArbAddresses.L1GatewayRouter - ), - L2GatewayRouter: testingUtils.env.ARB_L2_GATEWAY_ROUTER( - defaultArbAddresses.L2GatewayRouter - ), - }; -} - -async function printLoadedTestConfig( - networkName: NetworkName, - l1TokensHolder: any, - gatewayContracts: any, - gatewayRouters: any -) { - console.log("Using the deployed contracts for integration tests"); - console.log( - "In case of unexpected fails, please, make sure that you are forking correct Ethereum/Arbitrum networks" - ); - console.log(` Network Name: ${networkName}`); - console.log(` L1 Token: ${gatewayContracts.l1Token.address}`); - console.log(` L2 Token: ${gatewayContracts.l2Token.address}`); - const l1TokensHolderAddress = await l1TokensHolder.getAddress(); - console.log(` L1 Tokens Holder: ${l1TokensHolderAddress}`); - const holderBalance = await gatewayContracts.l1Token.balanceOf( - l1TokensHolderAddress - ); - console.log(` L1 Tokens Holder Balance: ${holderBalance.toString()}`); - console.log( - ` L1 ERC20 Token Gateway: ${gatewayContracts.l1ERC20TokenGateway.address}` - ); - console.log( - ` L2 ERC20 Token Gateway: ${gatewayContracts.l2ERC20TokenGateway.address}` - ); - console.log(` L1 Gateway Router: ${gatewayRouters.l1GatewayRouter.address}`); - console.log( - ` L2 Gateway Routery: ${gatewayRouters.l2GatewayRouter.address}` - ); -} From 5d26aab85501b7d4c185b3f187ba7ac422e87747 Mon Sep 17 00:00:00 2001 From: Anton Kovalchuk Date: Wed, 17 Apr 2024 10:24:04 +0200 Subject: [PATCH 068/148] update package lock --- package-lock.json | 20684 +------------------------------------------- 1 file changed, 375 insertions(+), 20309 deletions(-) diff --git a/package-lock.json b/package-lock.json index f0518138..89ad2558 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1,7 +1,7 @@ { "name": "lido-l2", "version": "1.0.0", - "lockfileVersion": 2, + "lockfileVersion": 3, "requires": true, "packages": { "": { @@ -9,10 +9,11 @@ "version": "1.0.0", "license": "ISC", "dependencies": { - "@eth-optimism/sdk": "3.2.0", + "@eth-optimism/sdk": "3.2.3", "@ethersproject/providers": "^5.6.8", "@lidofinance/evm-script-decoder": "^0.2.2", "@openzeppelin/contracts": "4.6.0", + "@openzeppelin/contracts-v4.9": "npm:@openzeppelin/contracts@4.9.6", "chalk": "4.1.2" }, "devDependencies": { @@ -36,6 +37,7 @@ "eslint-plugin-prettier": "^3.4.1", "eslint-plugin-promise": "^5.2.0", "ethereum-waffle": "^3.4.4", + "ethereumjs-util": "^7.0.8", "ethers": "^5.6.2", "hardhat": "^2.12.2", "hardhat-gas-reporter": "^1.0.8", @@ -514,10 +516,9 @@ } }, "node_modules/@eth-optimism/sdk": { - "version": "3.2.0", - "resolved": "https://registry.npmjs.org/@eth-optimism/sdk/-/sdk-3.2.0.tgz", - "integrity": "sha512-+ZEO/mDWz3WLzaPVHvgOAK4iN723HmI6sLLr2tmO1/RUoCHVfWMUDwuiikrA49cAsdsjMxCV9+0XNZ8btD2JUg==", - "hasInstallScript": true, + "version": "3.2.3", + "resolved": "https://registry.npmjs.org/@eth-optimism/sdk/-/sdk-3.2.3.tgz", + "integrity": "sha512-e3XQTbbU+HTzsEv/VIsJpZifK6YZVlzEtF6tj/Vz/VIEDCjZk5JPcnCQOMVcs9ICI4EJyyur+y/+RU7fPa6qtg==", "dependencies": { "@eth-optimism/contracts": "0.6.0", "@eth-optimism/contracts-bedrock": "0.17.1", @@ -525,7 +526,7 @@ "lodash": "^4.17.21", "merkletreejs": "^0.3.11", "rlp": "^2.2.7", - "semver": "^7.5.4" + "semver": "^7.6.0" }, "peerDependencies": { "ethers": "^5" @@ -689,45 +690,6 @@ "ethereumjs-util": "^7.1.5" } }, - "node_modules/@ethereumjs/common/node_modules/ethereum-cryptography": { - "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", - "blakejs": "^1.1.0", - "browserify-aes": "^1.2.0", - "bs58check": "^2.1.2", - "create-hash": "^1.2.0", - "create-hmac": "^1.1.7", - "hash.js": "^1.1.7", - "keccak": "^3.0.0", - "pbkdf2": "^3.0.17", - "randombytes": "^2.1.0", - "safe-buffer": "^5.1.2", - "scrypt-js": "^3.0.0", - "secp256k1": "^4.0.1", - "setimmediate": "^1.0.5" - } - }, - "node_modules/@ethereumjs/common/node_modules/ethereumjs-util": { - "version": "7.1.5", - "resolved": "https://registry.npmjs.org/ethereumjs-util/-/ethereumjs-util-7.1.5.tgz", - "integrity": "sha512-SDl5kKrQAudFBUe5OJM9Ac6WmMyYmXX/6sTmLZ3ffG2eY6ZIGBes3pEDxNN6V72WyOw4CPD5RomKdsa8DAAwLg==", - "dev": true, - "dependencies": { - "@types/bn.js": "^5.1.0", - "bn.js": "^5.1.2", - "create-hash": "^1.1.2", - "ethereum-cryptography": "^0.1.3", - "rlp": "^2.2.4" - }, - "engines": { - "node": ">=10.0.0" - } - }, "node_modules/@ethereumjs/tx": { "version": "3.5.2", "resolved": "https://registry.npmjs.org/@ethereumjs/tx/-/tx-3.5.2.tgz", @@ -738,45 +700,6 @@ "ethereumjs-util": "^7.1.5" } }, - "node_modules/@ethereumjs/tx/node_modules/ethereum-cryptography": { - "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", - "blakejs": "^1.1.0", - "browserify-aes": "^1.2.0", - "bs58check": "^2.1.2", - "create-hash": "^1.2.0", - "create-hmac": "^1.1.7", - "hash.js": "^1.1.7", - "keccak": "^3.0.0", - "pbkdf2": "^3.0.17", - "randombytes": "^2.1.0", - "safe-buffer": "^5.1.2", - "scrypt-js": "^3.0.0", - "secp256k1": "^4.0.1", - "setimmediate": "^1.0.5" - } - }, - "node_modules/@ethereumjs/tx/node_modules/ethereumjs-util": { - "version": "7.1.5", - "resolved": "https://registry.npmjs.org/ethereumjs-util/-/ethereumjs-util-7.1.5.tgz", - "integrity": "sha512-SDl5kKrQAudFBUe5OJM9Ac6WmMyYmXX/6sTmLZ3ffG2eY6ZIGBes3pEDxNN6V72WyOw4CPD5RomKdsa8DAAwLg==", - "dev": true, - "dependencies": { - "@types/bn.js": "^5.1.0", - "bn.js": "^5.1.2", - "create-hash": "^1.1.2", - "ethereum-cryptography": "^0.1.3", - "rlp": "^2.2.4" - }, - "engines": { - "node": ">=10.0.0" - } - }, "node_modules/@ethersproject/abi": { "version": "5.7.0", "resolved": "https://registry.npmjs.org/@ethersproject/abi/-/abi-5.7.0.tgz", @@ -1527,6 +1450,59 @@ "node": ">=12.0.0" } }, + "node_modules/@metamask/eth-sig-util/node_modules/@types/bn.js": { + "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": "*" + } + }, + "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==", + "dev": true + }, + "node_modules/@metamask/eth-sig-util/node_modules/ethereum-cryptography": { + "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", + "blakejs": "^1.1.0", + "browserify-aes": "^1.2.0", + "bs58check": "^2.1.2", + "create-hash": "^1.2.0", + "create-hmac": "^1.1.7", + "hash.js": "^1.1.7", + "keccak": "^3.0.0", + "pbkdf2": "^3.0.17", + "randombytes": "^2.1.0", + "safe-buffer": "^5.1.2", + "scrypt-js": "^3.0.0", + "secp256k1": "^4.0.1", + "setimmediate": "^1.0.5" + } + }, + "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", + "create-hash": "^1.1.2", + "elliptic": "^6.5.2", + "ethereum-cryptography": "^0.1.3", + "ethjs-util": "0.1.6", + "rlp": "^2.2.3" + } + }, "node_modules/@noble/hashes": { "version": "1.2.0", "resolved": "https://registry.npmjs.org/@noble/hashes/-/hashes-1.2.0.tgz", @@ -2034,150 +2010,6 @@ "node": ">= 10" } }, - "node_modules/@nomicfoundation/solidity-analyzer-darwin-x64": { - "version": "0.1.1", - "resolved": "https://registry.npmjs.org/@nomicfoundation/solidity-analyzer-darwin-x64/-/solidity-analyzer-darwin-x64-0.1.1.tgz", - "integrity": "sha512-XhQG4BaJE6cIbjAVtzGOGbK3sn1BO9W29uhk9J8y8fZF1DYz0Doj8QDMfpMu+A6TjPDs61lbsmeYodIDnfveSA==", - "cpu": [ - "x64" - ], - "dev": true, - "optional": true, - "os": [ - "darwin" - ], - "engines": { - "node": ">= 10" - } - }, - "node_modules/@nomicfoundation/solidity-analyzer-freebsd-x64": { - "version": "0.1.1", - "resolved": "https://registry.npmjs.org/@nomicfoundation/solidity-analyzer-freebsd-x64/-/solidity-analyzer-freebsd-x64-0.1.1.tgz", - "integrity": "sha512-GHF1VKRdHW3G8CndkwdaeLkVBi5A9u2jwtlS7SLhBc8b5U/GcoL39Q+1CSO3hYqePNP+eV5YI7Zgm0ea6kMHoA==", - "cpu": [ - "x64" - ], - "dev": true, - "optional": true, - "os": [ - "freebsd" - ], - "engines": { - "node": ">= 10" - } - }, - "node_modules/@nomicfoundation/solidity-analyzer-linux-arm64-gnu": { - "version": "0.1.1", - "resolved": "https://registry.npmjs.org/@nomicfoundation/solidity-analyzer-linux-arm64-gnu/-/solidity-analyzer-linux-arm64-gnu-0.1.1.tgz", - "integrity": "sha512-g4Cv2fO37ZsUENQ2vwPnZc2zRenHyAxHcyBjKcjaSmmkKrFr64yvzeNO8S3GBFCo90rfochLs99wFVGT/0owpg==", - "cpu": [ - "arm64" - ], - "dev": true, - "optional": true, - "os": [ - "linux" - ], - "engines": { - "node": ">= 10" - } - }, - "node_modules/@nomicfoundation/solidity-analyzer-linux-arm64-musl": { - "version": "0.1.1", - "resolved": "https://registry.npmjs.org/@nomicfoundation/solidity-analyzer-linux-arm64-musl/-/solidity-analyzer-linux-arm64-musl-0.1.1.tgz", - "integrity": "sha512-WJ3CE5Oek25OGE3WwzK7oaopY8xMw9Lhb0mlYuJl/maZVo+WtP36XoQTb7bW/i8aAdHW5Z+BqrHMux23pvxG3w==", - "cpu": [ - "arm64" - ], - "dev": true, - "optional": true, - "os": [ - "linux" - ], - "engines": { - "node": ">= 10" - } - }, - "node_modules/@nomicfoundation/solidity-analyzer-linux-x64-gnu": { - "version": "0.1.1", - "resolved": "https://registry.npmjs.org/@nomicfoundation/solidity-analyzer-linux-x64-gnu/-/solidity-analyzer-linux-x64-gnu-0.1.1.tgz", - "integrity": "sha512-5WN7leSr5fkUBBjE4f3wKENUy9HQStu7HmWqbtknfXkkil+eNWiBV275IOlpXku7v3uLsXTOKpnnGHJYI2qsdA==", - "cpu": [ - "x64" - ], - "dev": true, - "optional": true, - "os": [ - "linux" - ], - "engines": { - "node": ">= 10" - } - }, - "node_modules/@nomicfoundation/solidity-analyzer-linux-x64-musl": { - "version": "0.1.1", - "resolved": "https://registry.npmjs.org/@nomicfoundation/solidity-analyzer-linux-x64-musl/-/solidity-analyzer-linux-x64-musl-0.1.1.tgz", - "integrity": "sha512-KdYMkJOq0SYPQMmErv/63CwGwMm5XHenEna9X9aB8mQmhDBrYrlAOSsIPgFCUSL0hjxE3xHP65/EPXR/InD2+w==", - "cpu": [ - "x64" - ], - "dev": true, - "optional": true, - "os": [ - "linux" - ], - "engines": { - "node": ">= 10" - } - }, - "node_modules/@nomicfoundation/solidity-analyzer-win32-arm64-msvc": { - "version": "0.1.1", - "resolved": "https://registry.npmjs.org/@nomicfoundation/solidity-analyzer-win32-arm64-msvc/-/solidity-analyzer-win32-arm64-msvc-0.1.1.tgz", - "integrity": "sha512-VFZASBfl4qiBYwW5xeY20exWhmv6ww9sWu/krWSesv3q5hA0o1JuzmPHR4LPN6SUZj5vcqci0O6JOL8BPw+APg==", - "cpu": [ - "arm64" - ], - "dev": true, - "optional": true, - "os": [ - "win32" - ], - "engines": { - "node": ">= 10" - } - }, - "node_modules/@nomicfoundation/solidity-analyzer-win32-ia32-msvc": { - "version": "0.1.1", - "resolved": "https://registry.npmjs.org/@nomicfoundation/solidity-analyzer-win32-ia32-msvc/-/solidity-analyzer-win32-ia32-msvc-0.1.1.tgz", - "integrity": "sha512-JnFkYuyCSA70j6Si6cS1A9Gh1aHTEb8kOTBApp/c7NRTFGNMH8eaInKlyuuiIbvYFhlXW4LicqyYuWNNq9hkpQ==", - "cpu": [ - "ia32" - ], - "dev": true, - "optional": true, - "os": [ - "win32" - ], - "engines": { - "node": ">= 10" - } - }, - "node_modules/@nomicfoundation/solidity-analyzer-win32-x64-msvc": { - "version": "0.1.1", - "resolved": "https://registry.npmjs.org/@nomicfoundation/solidity-analyzer-win32-x64-msvc/-/solidity-analyzer-win32-x64-msvc-0.1.1.tgz", - "integrity": "sha512-HrVJr6+WjIXGnw3Q9u6KQcbZCtk0caVWhCdFADySvRyUxJ8PnzlaP+MhwNE8oyT8OZ6ejHBRrrgjSqDCFXGirw==", - "cpu": [ - "x64" - ], - "dev": true, - "optional": true, - "os": [ - "win32" - ], - "engines": { - "node": ">= 10" - } - }, "node_modules/@nomiclabs/hardhat-ethers": { "version": "2.2.3", "resolved": "https://registry.npmjs.org/@nomiclabs/hardhat-ethers/-/hardhat-ethers-2.2.3.tgz", @@ -2299,6 +2131,12 @@ "resolved": "https://registry.npmjs.org/@openzeppelin/contracts/-/contracts-4.6.0.tgz", "integrity": "sha512-8vi4d50NNya/bQqCmaVzvHNmwHvS0OBKb7HNtuNwEE3scXWrP31fKQoGxNMT+KbzmrNZzatE3QK5p2gFONI/hg==" }, + "node_modules/@openzeppelin/contracts-v4.9": { + "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/@resolver-engine/core": { "version": "0.3.3", "resolved": "https://registry.npmjs.org/@resolver-engine/core/-/core-0.3.3.tgz", @@ -2701,71 +2539,10 @@ "integrity": "sha512-c98Bf3tPniI+scsdk237ku1Dc3ujXQTSgyiPUDEOe7tRkhrqridvh8klBv0HCEso1OLOYcHuCv/cS6DNxKH+ZA==", "dev": true }, - "node_modules/@truffle/interface-adapter/node_modules/ethereum-cryptography": { - "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", - "blakejs": "^1.1.0", - "browserify-aes": "^1.2.0", - "bs58check": "^2.1.2", - "create-hash": "^1.2.0", - "create-hmac": "^1.1.7", - "hash.js": "^1.1.7", - "keccak": "^3.0.0", - "pbkdf2": "^3.0.17", - "randombytes": "^2.1.0", - "safe-buffer": "^5.1.2", - "scrypt-js": "^3.0.0", - "secp256k1": "^4.0.1", - "setimmediate": "^1.0.5" - } - }, - "node_modules/@truffle/interface-adapter/node_modules/ethereum-cryptography/node_modules/hash.js": { - "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" - } - }, - "node_modules/@truffle/interface-adapter/node_modules/ethereum-cryptography/node_modules/scrypt-js": { - "version": "3.0.1", - "resolved": "https://registry.npmjs.org/scrypt-js/-/scrypt-js-3.0.1.tgz", - "integrity": "sha512-cdwTTnqPu0Hyvf5in5asVdZocVDTNRmR7XEcJuIzMjJeSHybHl7vpB66AzwTaIg6CLSbtjcxc8fqcySfnTkccA==", - "dev": true - }, - "node_modules/@truffle/interface-adapter/node_modules/ethereum-cryptography/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==", - "dev": true - }, - "node_modules/@truffle/interface-adapter/node_modules/ethereumjs-util": { - "version": "7.1.5", - "resolved": "https://registry.npmjs.org/ethereumjs-util/-/ethereumjs-util-7.1.5.tgz", - "integrity": "sha512-SDl5kKrQAudFBUe5OJM9Ac6WmMyYmXX/6sTmLZ3ffG2eY6ZIGBes3pEDxNN6V72WyOw4CPD5RomKdsa8DAAwLg==", - "dev": true, - "dependencies": { - "@types/bn.js": "^5.1.0", - "bn.js": "^5.1.2", - "create-hash": "^1.1.2", - "ethereum-cryptography": "^0.1.3", - "rlp": "^2.2.4" - }, - "engines": { - "node": ">=10.0.0" - } - }, - "node_modules/@truffle/interface-adapter/node_modules/ethers": { - "version": "4.0.49", - "resolved": "https://registry.npmjs.org/ethers/-/ethers-4.0.49.tgz", - "integrity": "sha512-kPltTvWiyu+OktYy1IStSO16i2e7cS9D9OxZ81q2UUaiNPVrm/RTcbxamCXF9VUSKzJIdJV68EAIhTEVBalRWg==", + "node_modules/@truffle/interface-adapter/node_modules/ethers": { + "version": "4.0.49", + "resolved": "https://registry.npmjs.org/ethers/-/ethers-4.0.49.tgz", + "integrity": "sha512-kPltTvWiyu+OktYy1IStSO16i2e7cS9D9OxZ81q2UUaiNPVrm/RTcbxamCXF9VUSKzJIdJV68EAIhTEVBalRWg==", "dev": true, "dependencies": { "aes-js": "3.0.0", @@ -7273,13 +7050,45 @@ "ethereumjs-util": "^6.0.0" } }, + "node_modules/ethereumjs-abi/node_modules/@types/bn.js": { + "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": "*" + } + }, "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==", "dev": true }, - "node_modules/ethereumjs-util": { + "node_modules/ethereumjs-abi/node_modules/ethereum-cryptography": { + "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", + "blakejs": "^1.1.0", + "browserify-aes": "^1.2.0", + "bs58check": "^2.1.2", + "create-hash": "^1.2.0", + "create-hmac": "^1.1.7", + "hash.js": "^1.1.7", + "keccak": "^3.0.0", + "pbkdf2": "^3.0.17", + "randombytes": "^2.1.0", + "safe-buffer": "^5.1.2", + "scrypt-js": "^3.0.0", + "secp256k1": "^4.0.1", + "setimmediate": "^1.0.5" + } + }, + "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==", @@ -7294,26 +7103,25 @@ "rlp": "^2.2.3" } }, - "node_modules/ethereumjs-util/node_modules/@types/bn.js": { - "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, + "node_modules/ethereumjs-util": { + "version": "7.1.5", + "resolved": "https://registry.npmjs.org/ethereumjs-util/-/ethereumjs-util-7.1.5.tgz", + "integrity": "sha512-SDl5kKrQAudFBUe5OJM9Ac6WmMyYmXX/6sTmLZ3ffG2eY6ZIGBes3pEDxNN6V72WyOw4CPD5RomKdsa8DAAwLg==", "dependencies": { - "@types/node": "*" + "@types/bn.js": "^5.1.0", + "bn.js": "^5.1.2", + "create-hash": "^1.1.2", + "ethereum-cryptography": "^0.1.3", + "rlp": "^2.2.4" + }, + "engines": { + "node": ">=10.0.0" } }, - "node_modules/ethereumjs-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==", - "dev": true - }, "node_modules/ethereumjs-util/node_modules/ethereum-cryptography": { "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", @@ -23388,45 +23196,6 @@ "node": ">=8.0.0" } }, - "node_modules/web3-core-helpers/node_modules/ethereum-cryptography": { - "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", - "blakejs": "^1.1.0", - "browserify-aes": "^1.2.0", - "bs58check": "^2.1.2", - "create-hash": "^1.2.0", - "create-hmac": "^1.1.7", - "hash.js": "^1.1.7", - "keccak": "^3.0.0", - "pbkdf2": "^3.0.17", - "randombytes": "^2.1.0", - "safe-buffer": "^5.1.2", - "scrypt-js": "^3.0.0", - "secp256k1": "^4.0.1", - "setimmediate": "^1.0.5" - } - }, - "node_modules/web3-core-helpers/node_modules/ethereumjs-util": { - "version": "7.1.5", - "resolved": "https://registry.npmjs.org/ethereumjs-util/-/ethereumjs-util-7.1.5.tgz", - "integrity": "sha512-SDl5kKrQAudFBUe5OJM9Ac6WmMyYmXX/6sTmLZ3ffG2eY6ZIGBes3pEDxNN6V72WyOw4CPD5RomKdsa8DAAwLg==", - "dev": true, - "dependencies": { - "@types/bn.js": "^5.1.0", - "bn.js": "^5.1.2", - "create-hash": "^1.1.2", - "ethereum-cryptography": "^0.1.3", - "rlp": "^2.2.4" - }, - "engines": { - "node": ">=10.0.0" - } - }, "node_modules/web3-core-helpers/node_modules/web3-utils": { "version": "1.7.4", "resolved": "https://registry.npmjs.org/web3-utils/-/web3-utils-1.7.4.tgz", @@ -23461,45 +23230,6 @@ "node": ">=8.0.0" } }, - "node_modules/web3-core-method/node_modules/ethereum-cryptography": { - "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", - "blakejs": "^1.1.0", - "browserify-aes": "^1.2.0", - "bs58check": "^2.1.2", - "create-hash": "^1.2.0", - "create-hmac": "^1.1.7", - "hash.js": "^1.1.7", - "keccak": "^3.0.0", - "pbkdf2": "^3.0.17", - "randombytes": "^2.1.0", - "safe-buffer": "^5.1.2", - "scrypt-js": "^3.0.0", - "secp256k1": "^4.0.1", - "setimmediate": "^1.0.5" - } - }, - "node_modules/web3-core-method/node_modules/ethereumjs-util": { - "version": "7.1.5", - "resolved": "https://registry.npmjs.org/ethereumjs-util/-/ethereumjs-util-7.1.5.tgz", - "integrity": "sha512-SDl5kKrQAudFBUe5OJM9Ac6WmMyYmXX/6sTmLZ3ffG2eY6ZIGBes3pEDxNN6V72WyOw4CPD5RomKdsa8DAAwLg==", - "dev": true, - "dependencies": { - "@types/bn.js": "^5.1.0", - "bn.js": "^5.1.2", - "create-hash": "^1.1.2", - "ethereum-cryptography": "^0.1.3", - "rlp": "^2.2.4" - }, - "engines": { - "node": ">=10.0.0" - } - }, "node_modules/web3-core-method/node_modules/web3-utils": { "version": "1.7.4", "resolved": "https://registry.npmjs.org/web3-utils/-/web3-utils-1.7.4.tgz", @@ -23559,45 +23289,6 @@ "node": ">=8.0.0" } }, - "node_modules/web3-core/node_modules/ethereum-cryptography": { - "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", - "blakejs": "^1.1.0", - "browserify-aes": "^1.2.0", - "bs58check": "^2.1.2", - "create-hash": "^1.2.0", - "create-hmac": "^1.1.7", - "hash.js": "^1.1.7", - "keccak": "^3.0.0", - "pbkdf2": "^3.0.17", - "randombytes": "^2.1.0", - "safe-buffer": "^5.1.2", - "scrypt-js": "^3.0.0", - "secp256k1": "^4.0.1", - "setimmediate": "^1.0.5" - } - }, - "node_modules/web3-core/node_modules/ethereumjs-util": { - "version": "7.1.5", - "resolved": "https://registry.npmjs.org/ethereumjs-util/-/ethereumjs-util-7.1.5.tgz", - "integrity": "sha512-SDl5kKrQAudFBUe5OJM9Ac6WmMyYmXX/6sTmLZ3ffG2eY6ZIGBes3pEDxNN6V72WyOw4CPD5RomKdsa8DAAwLg==", - "dev": true, - "dependencies": { - "@types/bn.js": "^5.1.0", - "bn.js": "^5.1.2", - "create-hash": "^1.1.2", - "ethereum-cryptography": "^0.1.3", - "rlp": "^2.2.4" - }, - "engines": { - "node": ">=10.0.0" - } - }, "node_modules/web3-core/node_modules/web3-utils": { "version": "1.7.4", "resolved": "https://registry.npmjs.org/web3-utils/-/web3-utils-1.7.4.tgz", @@ -23652,45 +23343,6 @@ "node": ">=8.0.0" } }, - "node_modules/web3-eth-abi/node_modules/ethereum-cryptography": { - "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", - "blakejs": "^1.1.0", - "browserify-aes": "^1.2.0", - "bs58check": "^2.1.2", - "create-hash": "^1.2.0", - "create-hmac": "^1.1.7", - "hash.js": "^1.1.7", - "keccak": "^3.0.0", - "pbkdf2": "^3.0.17", - "randombytes": "^2.1.0", - "safe-buffer": "^5.1.2", - "scrypt-js": "^3.0.0", - "secp256k1": "^4.0.1", - "setimmediate": "^1.0.5" - } - }, - "node_modules/web3-eth-abi/node_modules/ethereumjs-util": { - "version": "7.1.5", - "resolved": "https://registry.npmjs.org/ethereumjs-util/-/ethereumjs-util-7.1.5.tgz", - "integrity": "sha512-SDl5kKrQAudFBUe5OJM9Ac6WmMyYmXX/6sTmLZ3ffG2eY6ZIGBes3pEDxNN6V72WyOw4CPD5RomKdsa8DAAwLg==", - "dev": true, - "dependencies": { - "@types/bn.js": "^5.1.0", - "bn.js": "^5.1.2", - "create-hash": "^1.1.2", - "ethereum-cryptography": "^0.1.3", - "rlp": "^2.2.4" - }, - "engines": { - "node": ">=10.0.0" - } - }, "node_modules/web3-eth-abi/node_modules/web3-utils": { "version": "1.7.4", "resolved": "https://registry.npmjs.org/web3-utils/-/web3-utils-1.7.4.tgz", @@ -23748,59 +23400,20 @@ "integrity": "sha512-c98Bf3tPniI+scsdk237ku1Dc3ujXQTSgyiPUDEOe7tRkhrqridvh8klBv0HCEso1OLOYcHuCv/cS6DNxKH+ZA==", "dev": true }, - "node_modules/web3-eth-accounts/node_modules/ethereum-cryptography": { - "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", - "blakejs": "^1.1.0", - "browserify-aes": "^1.2.0", - "bs58check": "^2.1.2", - "create-hash": "^1.2.0", - "create-hmac": "^1.1.7", - "hash.js": "^1.1.7", - "keccak": "^3.0.0", - "pbkdf2": "^3.0.17", - "randombytes": "^2.1.0", - "safe-buffer": "^5.1.2", - "scrypt-js": "^3.0.0", - "secp256k1": "^4.0.1", - "setimmediate": "^1.0.5" - } - }, - "node_modules/web3-eth-accounts/node_modules/ethereumjs-util": { - "version": "7.1.5", - "resolved": "https://registry.npmjs.org/ethereumjs-util/-/ethereumjs-util-7.1.5.tgz", - "integrity": "sha512-SDl5kKrQAudFBUe5OJM9Ac6WmMyYmXX/6sTmLZ3ffG2eY6ZIGBes3pEDxNN6V72WyOw4CPD5RomKdsa8DAAwLg==", - "dev": true, - "dependencies": { - "@types/bn.js": "^5.1.0", - "bn.js": "^5.1.2", - "create-hash": "^1.1.2", - "ethereum-cryptography": "^0.1.3", - "rlp": "^2.2.4" - }, - "engines": { - "node": ">=10.0.0" - } - }, - "node_modules/web3-eth-accounts/node_modules/uuid": { - "version": "3.3.2", - "resolved": "https://registry.npmjs.org/uuid/-/uuid-3.3.2.tgz", - "integrity": "sha512-yXJmeNaw3DnnKAOKJE51sL/ZaYfWJRl1pK9dr19YFCu0ObS231AB1/LbqTKRAQ5kw8A90rA6fr4riOUpTZvQZA==", - "deprecated": "Please upgrade to version 7 or higher. Older versions may use Math.random() in certain circumstances, which is known to be problematic. See https://v8.dev/blog/math-random for details.", - "dev": true, - "bin": { - "uuid": "bin/uuid" - } - }, - "node_modules/web3-eth-accounts/node_modules/web3-utils": { - "version": "1.7.4", - "resolved": "https://registry.npmjs.org/web3-utils/-/web3-utils-1.7.4.tgz", - "integrity": "sha512-acBdm6Evd0TEZRnChM/MCvGsMwYKmSh7OaUfNf5OKG0CIeGWD/6gqLOWIwmwSnre/2WrA1nKGId5uW2e5EfluA==", + "node_modules/web3-eth-accounts/node_modules/uuid": { + "version": "3.3.2", + "resolved": "https://registry.npmjs.org/uuid/-/uuid-3.3.2.tgz", + "integrity": "sha512-yXJmeNaw3DnnKAOKJE51sL/ZaYfWJRl1pK9dr19YFCu0ObS231AB1/LbqTKRAQ5kw8A90rA6fr4riOUpTZvQZA==", + "deprecated": "Please upgrade to version 7 or higher. Older versions may use Math.random() in certain circumstances, which is known to be problematic. See https://v8.dev/blog/math-random for details.", + "dev": true, + "bin": { + "uuid": "bin/uuid" + } + }, + "node_modules/web3-eth-accounts/node_modules/web3-utils": { + "version": "1.7.4", + "resolved": "https://registry.npmjs.org/web3-utils/-/web3-utils-1.7.4.tgz", + "integrity": "sha512-acBdm6Evd0TEZRnChM/MCvGsMwYKmSh7OaUfNf5OKG0CIeGWD/6gqLOWIwmwSnre/2WrA1nKGId5uW2e5EfluA==", "dev": true, "dependencies": { "bn.js": "^5.2.1", @@ -23834,45 +23447,6 @@ "node": ">=8.0.0" } }, - "node_modules/web3-eth-contract/node_modules/ethereum-cryptography": { - "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", - "blakejs": "^1.1.0", - "browserify-aes": "^1.2.0", - "bs58check": "^2.1.2", - "create-hash": "^1.2.0", - "create-hmac": "^1.1.7", - "hash.js": "^1.1.7", - "keccak": "^3.0.0", - "pbkdf2": "^3.0.17", - "randombytes": "^2.1.0", - "safe-buffer": "^5.1.2", - "scrypt-js": "^3.0.0", - "secp256k1": "^4.0.1", - "setimmediate": "^1.0.5" - } - }, - "node_modules/web3-eth-contract/node_modules/ethereumjs-util": { - "version": "7.1.5", - "resolved": "https://registry.npmjs.org/ethereumjs-util/-/ethereumjs-util-7.1.5.tgz", - "integrity": "sha512-SDl5kKrQAudFBUe5OJM9Ac6WmMyYmXX/6sTmLZ3ffG2eY6ZIGBes3pEDxNN6V72WyOw4CPD5RomKdsa8DAAwLg==", - "dev": true, - "dependencies": { - "@types/bn.js": "^5.1.0", - "bn.js": "^5.1.2", - "create-hash": "^1.1.2", - "ethereum-cryptography": "^0.1.3", - "rlp": "^2.2.4" - }, - "engines": { - "node": ">=10.0.0" - } - }, "node_modules/web3-eth-contract/node_modules/web3-utils": { "version": "1.7.4", "resolved": "https://registry.npmjs.org/web3-utils/-/web3-utils-1.7.4.tgz", @@ -23910,45 +23484,6 @@ "node": ">=8.0.0" } }, - "node_modules/web3-eth-ens/node_modules/ethereum-cryptography": { - "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", - "blakejs": "^1.1.0", - "browserify-aes": "^1.2.0", - "bs58check": "^2.1.2", - "create-hash": "^1.2.0", - "create-hmac": "^1.1.7", - "hash.js": "^1.1.7", - "keccak": "^3.0.0", - "pbkdf2": "^3.0.17", - "randombytes": "^2.1.0", - "safe-buffer": "^5.1.2", - "scrypt-js": "^3.0.0", - "secp256k1": "^4.0.1", - "setimmediate": "^1.0.5" - } - }, - "node_modules/web3-eth-ens/node_modules/ethereumjs-util": { - "version": "7.1.5", - "resolved": "https://registry.npmjs.org/ethereumjs-util/-/ethereumjs-util-7.1.5.tgz", - "integrity": "sha512-SDl5kKrQAudFBUe5OJM9Ac6WmMyYmXX/6sTmLZ3ffG2eY6ZIGBes3pEDxNN6V72WyOw4CPD5RomKdsa8DAAwLg==", - "dev": true, - "dependencies": { - "@types/bn.js": "^5.1.0", - "bn.js": "^5.1.2", - "create-hash": "^1.1.2", - "ethereum-cryptography": "^0.1.3", - "rlp": "^2.2.4" - }, - "engines": { - "node": ">=10.0.0" - } - }, "node_modules/web3-eth-ens/node_modules/web3-utils": { "version": "1.7.4", "resolved": "https://registry.npmjs.org/web3-utils/-/web3-utils-1.7.4.tgz", @@ -23980,45 +23515,6 @@ "node": ">=8.0.0" } }, - "node_modules/web3-eth-iban/node_modules/ethereum-cryptography": { - "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", - "blakejs": "^1.1.0", - "browserify-aes": "^1.2.0", - "bs58check": "^2.1.2", - "create-hash": "^1.2.0", - "create-hmac": "^1.1.7", - "hash.js": "^1.1.7", - "keccak": "^3.0.0", - "pbkdf2": "^3.0.17", - "randombytes": "^2.1.0", - "safe-buffer": "^5.1.2", - "scrypt-js": "^3.0.0", - "secp256k1": "^4.0.1", - "setimmediate": "^1.0.5" - } - }, - "node_modules/web3-eth-iban/node_modules/ethereumjs-util": { - "version": "7.1.5", - "resolved": "https://registry.npmjs.org/ethereumjs-util/-/ethereumjs-util-7.1.5.tgz", - "integrity": "sha512-SDl5kKrQAudFBUe5OJM9Ac6WmMyYmXX/6sTmLZ3ffG2eY6ZIGBes3pEDxNN6V72WyOw4CPD5RomKdsa8DAAwLg==", - "dev": true, - "dependencies": { - "@types/bn.js": "^5.1.0", - "bn.js": "^5.1.2", - "create-hash": "^1.1.2", - "ethereum-cryptography": "^0.1.3", - "rlp": "^2.2.4" - }, - "engines": { - "node": ">=10.0.0" - } - }, "node_modules/web3-eth-iban/node_modules/web3-utils": { "version": "1.7.4", "resolved": "https://registry.npmjs.org/web3-utils/-/web3-utils-1.7.4.tgz", @@ -24054,45 +23550,6 @@ "node": ">=8.0.0" } }, - "node_modules/web3-eth-personal/node_modules/ethereum-cryptography": { - "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", - "blakejs": "^1.1.0", - "browserify-aes": "^1.2.0", - "bs58check": "^2.1.2", - "create-hash": "^1.2.0", - "create-hmac": "^1.1.7", - "hash.js": "^1.1.7", - "keccak": "^3.0.0", - "pbkdf2": "^3.0.17", - "randombytes": "^2.1.0", - "safe-buffer": "^5.1.2", - "scrypt-js": "^3.0.0", - "secp256k1": "^4.0.1", - "setimmediate": "^1.0.5" - } - }, - "node_modules/web3-eth-personal/node_modules/ethereumjs-util": { - "version": "7.1.5", - "resolved": "https://registry.npmjs.org/ethereumjs-util/-/ethereumjs-util-7.1.5.tgz", - "integrity": "sha512-SDl5kKrQAudFBUe5OJM9Ac6WmMyYmXX/6sTmLZ3ffG2eY6ZIGBes3pEDxNN6V72WyOw4CPD5RomKdsa8DAAwLg==", - "dev": true, - "dependencies": { - "@types/bn.js": "^5.1.0", - "bn.js": "^5.1.2", - "create-hash": "^1.1.2", - "ethereum-cryptography": "^0.1.3", - "rlp": "^2.2.4" - }, - "engines": { - "node": ">=10.0.0" - } - }, "node_modules/web3-eth-personal/node_modules/web3-utils": { "version": "1.7.4", "resolved": "https://registry.npmjs.org/web3-utils/-/web3-utils-1.7.4.tgz", @@ -24111,45 +23568,6 @@ "node": ">=8.0.0" } }, - "node_modules/web3-eth/node_modules/ethereum-cryptography": { - "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", - "blakejs": "^1.1.0", - "browserify-aes": "^1.2.0", - "bs58check": "^2.1.2", - "create-hash": "^1.2.0", - "create-hmac": "^1.1.7", - "hash.js": "^1.1.7", - "keccak": "^3.0.0", - "pbkdf2": "^3.0.17", - "randombytes": "^2.1.0", - "safe-buffer": "^5.1.2", - "scrypt-js": "^3.0.0", - "secp256k1": "^4.0.1", - "setimmediate": "^1.0.5" - } - }, - "node_modules/web3-eth/node_modules/ethereumjs-util": { - "version": "7.1.5", - "resolved": "https://registry.npmjs.org/ethereumjs-util/-/ethereumjs-util-7.1.5.tgz", - "integrity": "sha512-SDl5kKrQAudFBUe5OJM9Ac6WmMyYmXX/6sTmLZ3ffG2eY6ZIGBes3pEDxNN6V72WyOw4CPD5RomKdsa8DAAwLg==", - "dev": true, - "dependencies": { - "@types/bn.js": "^5.1.0", - "bn.js": "^5.1.2", - "create-hash": "^1.1.2", - "ethereum-cryptography": "^0.1.3", - "rlp": "^2.2.4" - }, - "engines": { - "node": ">=10.0.0" - } - }, "node_modules/web3-eth/node_modules/web3-utils": { "version": "1.7.4", "resolved": "https://registry.npmjs.org/web3-utils/-/web3-utils-1.7.4.tgz", @@ -24182,19508 +23600,85 @@ "node": ">=8.0.0" } }, - "node_modules/web3-net/node_modules/ethereum-cryptography": { - "version": "0.1.3", - "resolved": "https://registry.npmjs.org/ethereum-cryptography/-/ethereum-cryptography-0.1.3.tgz", - "integrity": "sha512-w8/4x1SGGzc+tO97TASLja6SLd3fRIK2tLVcV2Gx4IB21hE19atll5Cq9o3d0ZmAYC/8aw0ipieTSiekAea4SQ==", + "node_modules/web3-net/node_modules/web3-utils": { + "version": "1.7.4", + "resolved": "https://registry.npmjs.org/web3-utils/-/web3-utils-1.7.4.tgz", + "integrity": "sha512-acBdm6Evd0TEZRnChM/MCvGsMwYKmSh7OaUfNf5OKG0CIeGWD/6gqLOWIwmwSnre/2WrA1nKGId5uW2e5EfluA==", "dev": true, "dependencies": { - "@types/pbkdf2": "^3.0.0", - "@types/secp256k1": "^4.0.1", - "blakejs": "^1.1.0", - "browserify-aes": "^1.2.0", - "bs58check": "^2.1.2", - "create-hash": "^1.2.0", - "create-hmac": "^1.1.7", - "hash.js": "^1.1.7", - "keccak": "^3.0.0", - "pbkdf2": "^3.0.17", + "bn.js": "^5.2.1", + "ethereum-bloom-filters": "^1.0.6", + "ethereumjs-util": "^7.1.0", + "ethjs-unit": "0.1.6", + "number-to-bn": "1.7.0", "randombytes": "^2.1.0", - "safe-buffer": "^5.1.2", - "scrypt-js": "^3.0.0", - "secp256k1": "^4.0.1", - "setimmediate": "^1.0.5" - } - }, - "node_modules/web3-net/node_modules/ethereumjs-util": { - "version": "7.1.5", - "resolved": "https://registry.npmjs.org/ethereumjs-util/-/ethereumjs-util-7.1.5.tgz", - "integrity": "sha512-SDl5kKrQAudFBUe5OJM9Ac6WmMyYmXX/6sTmLZ3ffG2eY6ZIGBes3pEDxNN6V72WyOw4CPD5RomKdsa8DAAwLg==", - "dev": true, - "dependencies": { - "@types/bn.js": "^5.1.0", - "bn.js": "^5.1.2", - "create-hash": "^1.1.2", - "ethereum-cryptography": "^0.1.3", - "rlp": "^2.2.4" - }, - "engines": { - "node": ">=10.0.0" - } - }, - "node_modules/web3-net/node_modules/web3-utils": { - "version": "1.7.4", - "resolved": "https://registry.npmjs.org/web3-utils/-/web3-utils-1.7.4.tgz", - "integrity": "sha512-acBdm6Evd0TEZRnChM/MCvGsMwYKmSh7OaUfNf5OKG0CIeGWD/6gqLOWIwmwSnre/2WrA1nKGId5uW2e5EfluA==", - "dev": true, - "dependencies": { - "bn.js": "^5.2.1", - "ethereum-bloom-filters": "^1.0.6", - "ethereumjs-util": "^7.1.0", - "ethjs-unit": "0.1.6", - "number-to-bn": "1.7.0", - "randombytes": "^2.1.0", - "utf8": "3.0.0" - }, - "engines": { - "node": ">=8.0.0" - } - }, - "node_modules/web3-providers-http": { - "version": "1.7.4", - "resolved": "https://registry.npmjs.org/web3-providers-http/-/web3-providers-http-1.7.4.tgz", - "integrity": "sha512-AU+/S+49rcogUER99TlhW+UBMk0N2DxvN54CJ2pK7alc2TQ7+cprNPLHJu4KREe8ndV0fT6JtWUfOMyTvl+FRA==", - "dev": true, - "dependencies": { - "web3-core-helpers": "1.7.4", - "xhr2-cookies": "1.1.0" - }, - "engines": { - "node": ">=8.0.0" - } - }, - "node_modules/web3-providers-ipc": { - "version": "1.7.4", - "resolved": "https://registry.npmjs.org/web3-providers-ipc/-/web3-providers-ipc-1.7.4.tgz", - "integrity": "sha512-jhArOZ235dZy8fS8090t60nTxbd1ap92ibQw5xIrAQ9m7LcZKNfmLAQUVsD+3dTFvadRMi6z1vCO7zRi84gWHw==", - "dev": true, - "dependencies": { - "oboe": "2.1.5", - "web3-core-helpers": "1.7.4" - }, - "engines": { - "node": ">=8.0.0" - } - }, - "node_modules/web3-providers-ws": { - "version": "1.7.4", - "resolved": "https://registry.npmjs.org/web3-providers-ws/-/web3-providers-ws-1.7.4.tgz", - "integrity": "sha512-g72X77nrcHMFU8hRzQJzfgi/072n8dHwRCoTw+WQrGp+XCQ71fsk2qIu3Tp+nlp5BPn8bRudQbPblVm2uT4myQ==", - "dev": true, - "dependencies": { - "eventemitter3": "4.0.4", - "web3-core-helpers": "1.7.4", - "websocket": "^1.0.32" - }, - "engines": { - "node": ">=8.0.0" - } - }, - "node_modules/web3-shh": { - "version": "1.7.4", - "resolved": "https://registry.npmjs.org/web3-shh/-/web3-shh-1.7.4.tgz", - "integrity": "sha512-mlSZxSYcMkuMCxqhTYnZkUdahZ11h+bBv/8TlkXp/IHpEe4/Gg+KAbmfudakq3EzG/04z70XQmPgWcUPrsEJ+A==", - "dev": true, - "hasInstallScript": true, - "dependencies": { - "web3-core": "1.7.4", - "web3-core-method": "1.7.4", - "web3-core-subscriptions": "1.7.4", - "web3-net": "1.7.4" - }, - "engines": { - "node": ">=8.0.0" - } - }, - "node_modules/web3-utils": { - "version": "1.10.0", - "resolved": "https://registry.npmjs.org/web3-utils/-/web3-utils-1.10.0.tgz", - "integrity": "sha512-kSaCM0uMcZTNUSmn5vMEhlo02RObGNRRCkdX0V9UTAU0+lrvn0HSaudyCo6CQzuXUsnuY2ERJGCGPfeWmv19Rg==", - "dependencies": { - "bn.js": "^5.2.1", - "ethereum-bloom-filters": "^1.0.6", - "ethereumjs-util": "^7.1.0", - "ethjs-unit": "0.1.6", - "number-to-bn": "1.7.0", - "randombytes": "^2.1.0", - "utf8": "3.0.0" - }, - "engines": { - "node": ">=8.0.0" - } - }, - "node_modules/web3-utils/node_modules/ethereum-cryptography": { - "version": "0.1.3", - "resolved": "https://registry.npmjs.org/ethereum-cryptography/-/ethereum-cryptography-0.1.3.tgz", - "integrity": "sha512-w8/4x1SGGzc+tO97TASLja6SLd3fRIK2tLVcV2Gx4IB21hE19atll5Cq9o3d0ZmAYC/8aw0ipieTSiekAea4SQ==", - "dependencies": { - "@types/pbkdf2": "^3.0.0", - "@types/secp256k1": "^4.0.1", - "blakejs": "^1.1.0", - "browserify-aes": "^1.2.0", - "bs58check": "^2.1.2", - "create-hash": "^1.2.0", - "create-hmac": "^1.1.7", - "hash.js": "^1.1.7", - "keccak": "^3.0.0", - "pbkdf2": "^3.0.17", - "randombytes": "^2.1.0", - "safe-buffer": "^5.1.2", - "scrypt-js": "^3.0.0", - "secp256k1": "^4.0.1", - "setimmediate": "^1.0.5" - } - }, - "node_modules/web3-utils/node_modules/ethereumjs-util": { - "version": "7.1.5", - "resolved": "https://registry.npmjs.org/ethereumjs-util/-/ethereumjs-util-7.1.5.tgz", - "integrity": "sha512-SDl5kKrQAudFBUe5OJM9Ac6WmMyYmXX/6sTmLZ3ffG2eY6ZIGBes3pEDxNN6V72WyOw4CPD5RomKdsa8DAAwLg==", - "dependencies": { - "@types/bn.js": "^5.1.0", - "bn.js": "^5.1.2", - "create-hash": "^1.1.2", - "ethereum-cryptography": "^0.1.3", - "rlp": "^2.2.4" - }, - "engines": { - "node": ">=10.0.0" - } - }, - "node_modules/web3/node_modules/ethereum-cryptography": { - "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", - "blakejs": "^1.1.0", - "browserify-aes": "^1.2.0", - "bs58check": "^2.1.2", - "create-hash": "^1.2.0", - "create-hmac": "^1.1.7", - "hash.js": "^1.1.7", - "keccak": "^3.0.0", - "pbkdf2": "^3.0.17", - "randombytes": "^2.1.0", - "safe-buffer": "^5.1.2", - "scrypt-js": "^3.0.0", - "secp256k1": "^4.0.1", - "setimmediate": "^1.0.5" - } - }, - "node_modules/web3/node_modules/ethereumjs-util": { - "version": "7.1.5", - "resolved": "https://registry.npmjs.org/ethereumjs-util/-/ethereumjs-util-7.1.5.tgz", - "integrity": "sha512-SDl5kKrQAudFBUe5OJM9Ac6WmMyYmXX/6sTmLZ3ffG2eY6ZIGBes3pEDxNN6V72WyOw4CPD5RomKdsa8DAAwLg==", - "dev": true, - "dependencies": { - "@types/bn.js": "^5.1.0", - "bn.js": "^5.1.2", - "create-hash": "^1.1.2", - "ethereum-cryptography": "^0.1.3", - "rlp": "^2.2.4" - }, - "engines": { - "node": ">=10.0.0" - } - }, - "node_modules/web3/node_modules/web3-utils": { - "version": "1.7.4", - "resolved": "https://registry.npmjs.org/web3-utils/-/web3-utils-1.7.4.tgz", - "integrity": "sha512-acBdm6Evd0TEZRnChM/MCvGsMwYKmSh7OaUfNf5OKG0CIeGWD/6gqLOWIwmwSnre/2WrA1nKGId5uW2e5EfluA==", - "dev": true, - "dependencies": { - "bn.js": "^5.2.1", - "ethereum-bloom-filters": "^1.0.6", - "ethereumjs-util": "^7.1.0", - "ethjs-unit": "0.1.6", - "number-to-bn": "1.7.0", - "randombytes": "^2.1.0", - "utf8": "3.0.0" - }, - "engines": { - "node": ">=8.0.0" - } - }, - "node_modules/webidl-conversions": { - "version": "3.0.1", - "resolved": "https://registry.npmjs.org/webidl-conversions/-/webidl-conversions-3.0.1.tgz", - "integrity": "sha512-2JAn3z8AR6rjK8Sm8orRC0h/bcl/DqL7tRPdGZ4I1CjdF+EaMLmYxBHyXuKL849eucPFhvBoxMsflfOb8kxaeQ==" - }, - "node_modules/websocket": { - "version": "1.0.34", - "resolved": "https://registry.npmjs.org/websocket/-/websocket-1.0.34.tgz", - "integrity": "sha512-PRDso2sGwF6kM75QykIesBijKSVceR6jL2G8NGYyq2XrItNC2P5/qL5XeR056GhA+Ly7JMFvJb9I312mJfmqnQ==", - "dev": true, - "dependencies": { - "bufferutil": "^4.0.1", - "debug": "^2.2.0", - "es5-ext": "^0.10.50", - "typedarray-to-buffer": "^3.1.5", - "utf-8-validate": "^5.0.2", - "yaeti": "^0.0.6" - }, - "engines": { - "node": ">=4.0.0" - } - }, - "node_modules/websocket/node_modules/debug": { - "version": "2.6.9", - "resolved": "https://registry.npmjs.org/debug/-/debug-2.6.9.tgz", - "integrity": "sha512-bC7ElrdJaJnPbAP+1EotYvqZsb3ecl5wi6Bfi6BJTUcNowp6cvspg0jXznRTKDjm/E7AdgFBVeAPVMNcKGsHMA==", - "dev": true, - "dependencies": { - "ms": "2.0.0" - } - }, - "node_modules/websocket/node_modules/ms": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/ms/-/ms-2.0.0.tgz", - "integrity": "sha512-Tpp60P6IUJDTuOq/5Z8cdskzJujfwqfOTkrwIwj7IRISpnkJnT6SyJ4PCPnGMoFjC9ddhal5KVIYtAt97ix05A==", - "dev": true - }, - "node_modules/whatwg-url": { - "version": "5.0.0", - "resolved": "https://registry.npmjs.org/whatwg-url/-/whatwg-url-5.0.0.tgz", - "integrity": "sha512-saE57nupxk6v3HY35+jzBwYa0rKSy0XR8JSxZPwgLr7ys0IBzhGviA1/TUGJLmSVqs8pb9AnvICXEuOHLprYTw==", - "dependencies": { - "tr46": "~0.0.3", - "webidl-conversions": "^3.0.0" - } - }, - "node_modules/which": { - "version": "2.0.2", - "resolved": "https://registry.npmjs.org/which/-/which-2.0.2.tgz", - "integrity": "sha512-BLI3Tl1TW3Pvl70l3yq3Y64i+awpwXqsGBYWkkqMtnbXgrMD+yj7rhW0kuEDxzJaYXGjEW5ogapKNMEKNMjibA==", - "dev": true, - "dependencies": { - "isexe": "^2.0.0" - }, - "bin": { - "node-which": "bin/node-which" - }, - "engines": { - "node": ">= 8" - } - }, - "node_modules/which-boxed-primitive": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/which-boxed-primitive/-/which-boxed-primitive-1.0.2.tgz", - "integrity": "sha512-bwZdv0AKLpplFY2KZRX6TvyuN7ojjr7lwkg6ml0roIy9YeuSr7JS372qlNW18UQYzgYK9ziGcerWqZOmEn9VNg==", - "dev": true, - "dependencies": { - "is-bigint": "^1.0.1", - "is-boolean-object": "^1.1.0", - "is-number-object": "^1.0.4", - "is-string": "^1.0.5", - "is-symbol": "^1.0.3" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, - "node_modules/which-module": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/which-module/-/which-module-1.0.0.tgz", - "integrity": "sha512-F6+WgncZi/mJDrammbTuHe1q0R5hOXv/mBaiNA2TCNT/LTHusX0V+CJnj9XT8ki5ln2UZyyddDgHfCzyrOH7MQ==", - "dev": true - }, - "node_modules/which-typed-array": { - "version": "1.1.11", - "resolved": "https://registry.npmjs.org/which-typed-array/-/which-typed-array-1.1.11.tgz", - "integrity": "sha512-qe9UWWpkeG5yzZ0tNYxDmd7vo58HDBc39mZ0xWWpolAGADdFOzkfamWLDxkOWcvHQKVmdTyQdLD4NOfjLWTKew==", - "dev": true, - "dependencies": { - "available-typed-arrays": "^1.0.5", - "call-bind": "^1.0.2", - "for-each": "^0.3.3", - "gopd": "^1.0.1", - "has-tostringtag": "^1.0.0" - }, - "engines": { - "node": ">= 0.4" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, - "node_modules/wide-align": { - "version": "1.1.3", - "resolved": "https://registry.npmjs.org/wide-align/-/wide-align-1.1.3.tgz", - "integrity": "sha512-QGkOQc8XL6Bt5PwnsExKBPuMKBxnGxWWW3fU55Xt4feHozMUhdUMaBCk290qpm/wG5u/RSKzwdAC4i51YigihA==", - "dev": true, - "dependencies": { - "string-width": "^1.0.2 || 2" - } - }, - "node_modules/window-size": { - "version": "0.2.0", - "resolved": "https://registry.npmjs.org/window-size/-/window-size-0.2.0.tgz", - "integrity": "sha512-UD7d8HFA2+PZsbKyaOCEy8gMh1oDtHgJh1LfgjQ4zVXmYjAT/kvz3PueITKuqDiIXQe7yzpPnxX3lNc+AhQMyw==", - "dev": true, - "bin": { - "window-size": "cli.js" - }, - "engines": { - "node": ">= 0.10.0" - } - }, - "node_modules/word-wrap": { - "version": "1.2.4", - "resolved": "https://registry.npmjs.org/word-wrap/-/word-wrap-1.2.4.tgz", - "integrity": "sha512-2V81OA4ugVo5pRo46hAoD2ivUJx8jXmWXfUkY4KFNw0hEptvN0QfH3K4nHiwzGeKl5rFKedV48QVoqYavy4YpA==", - "dev": true, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/wordwrap": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/wordwrap/-/wordwrap-1.0.0.tgz", - "integrity": "sha512-gvVzJFlPycKc5dZN4yPkP8w7Dc37BtP1yczEneOb4uq34pXZcvrtRTmWV8W+Ume+XCxKgbjM+nevkyFPMybd4Q==", - "dev": true - }, - "node_modules/workerpool": { - "version": "6.2.1", - "resolved": "https://registry.npmjs.org/workerpool/-/workerpool-6.2.1.tgz", - "integrity": "sha512-ILEIE97kDZvF9Wb9f6h5aXK4swSlKGUcOEGiIYb2OOu/IrDU9iwj0fD//SsA6E5ibwJxpEvhullJY4Sl4GcpAw==", - "dev": true - }, - "node_modules/wrap-ansi": { - "version": "7.0.0", - "resolved": "https://registry.npmjs.org/wrap-ansi/-/wrap-ansi-7.0.0.tgz", - "integrity": "sha512-YVGIj2kamLSTxw6NsZjoBxfSwsn0ycdesmc4p+Q21c5zPuZ1pl+NfxVdxPtdHvmNVOQ6XSYG4AUtyt/Fi7D16Q==", - "dev": true, - "dependencies": { - "ansi-styles": "^4.0.0", - "string-width": "^4.1.0", - "strip-ansi": "^6.0.0" - }, - "engines": { - "node": ">=10" - }, - "funding": { - "url": "https://github.com/chalk/wrap-ansi?sponsor=1" - } - }, - "node_modules/wrap-ansi/node_modules/is-fullwidth-code-point": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/is-fullwidth-code-point/-/is-fullwidth-code-point-3.0.0.tgz", - "integrity": "sha512-zymm5+u+sCsSWyD9qNaejV3DFvhCKclKdizYaJUuHA83RLjb7nSuGnddCHGv0hk+KY7BMAlsWeK4Ueg6EV6XQg==", - "dev": true, - "engines": { - "node": ">=8" - } - }, - "node_modules/wrap-ansi/node_modules/string-width": { - "version": "4.2.3", - "resolved": "https://registry.npmjs.org/string-width/-/string-width-4.2.3.tgz", - "integrity": "sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g==", - "dev": true, - "dependencies": { - "emoji-regex": "^8.0.0", - "is-fullwidth-code-point": "^3.0.0", - "strip-ansi": "^6.0.1" - }, - "engines": { - "node": ">=8" - } - }, - "node_modules/wrappy": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/wrappy/-/wrappy-1.0.2.tgz", - "integrity": "sha512-l4Sp/DRseor9wL6EvV2+TuQn63dMkPjZ/sp9XkghTEbV9KlPS1xUsZ3u7/IQO4wxtcFB4bgpQPRcR3QCvezPcQ==", - "dev": true - }, - "node_modules/ws": { - "version": "7.4.6", - "resolved": "https://registry.npmjs.org/ws/-/ws-7.4.6.tgz", - "integrity": "sha512-YmhHDO4MzaDLB+M9ym/mDA5z0naX8j7SIlT8f8z+I0VtzsRbekxEutHSme7NPS2qE8StCYQNUnfWdXta/Yu85A==", - "engines": { - "node": ">=8.3.0" - }, - "peerDependencies": { - "bufferutil": "^4.0.1", - "utf-8-validate": "^5.0.2" - }, - "peerDependenciesMeta": { - "bufferutil": { - "optional": true - }, - "utf-8-validate": { - "optional": true - } - } - }, - "node_modules/xhr": { - "version": "2.6.0", - "resolved": "https://registry.npmjs.org/xhr/-/xhr-2.6.0.tgz", - "integrity": "sha512-/eCGLb5rxjx5e3mF1A7s+pLlR6CGyqWN91fv1JgER5mVWg1MZmlhBvy9kjcsOdRk8RrIujotWyJamfyrp+WIcA==", - "dev": true, - "dependencies": { - "global": "~4.4.0", - "is-function": "^1.0.1", - "parse-headers": "^2.0.0", - "xtend": "^4.0.0" - } - }, - "node_modules/xhr-request": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/xhr-request/-/xhr-request-1.1.0.tgz", - "integrity": "sha512-Y7qzEaR3FDtL3fP30k9wO/e+FBnBByZeybKOhASsGP30NIkRAAkKD/sCnLvgEfAIEC1rcmK7YG8f4oEnIrrWzA==", - "dev": true, - "dependencies": { - "buffer-to-arraybuffer": "^0.0.5", - "object-assign": "^4.1.1", - "query-string": "^5.0.1", - "simple-get": "^2.7.0", - "timed-out": "^4.0.1", - "url-set-query": "^1.0.0", - "xhr": "^2.0.4" - } - }, - "node_modules/xhr-request-promise": { - "version": "0.1.3", - "resolved": "https://registry.npmjs.org/xhr-request-promise/-/xhr-request-promise-0.1.3.tgz", - "integrity": "sha512-YUBytBsuwgitWtdRzXDDkWAXzhdGB8bYm0sSzMPZT7Z2MBjMSTHFsyCT1yCRATY+XC69DUrQraRAEgcoCRaIPg==", - "dev": true, - "dependencies": { - "xhr-request": "^1.1.0" - } - }, - "node_modules/xhr2-cookies": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/xhr2-cookies/-/xhr2-cookies-1.1.0.tgz", - "integrity": "sha512-hjXUA6q+jl/bd8ADHcVfFsSPIf+tyLIjuO9TwJC9WI6JP2zKcS7C+p56I9kCLLsaCiNT035iYvEUUzdEFj/8+g==", - "dev": true, - "dependencies": { - "cookiejar": "^2.1.1" - } - }, - "node_modules/xmlhttprequest": { - "version": "1.8.0", - "resolved": "https://registry.npmjs.org/xmlhttprequest/-/xmlhttprequest-1.8.0.tgz", - "integrity": "sha512-58Im/U0mlVBLM38NdZjHyhuMtCqa61469k2YP/AaPbvCoV9aQGUpbJBj1QRm2ytRiVQBD/fsw7L2bJGDVQswBA==", - "dev": true, - "engines": { - "node": ">=0.4.0" - } - }, - "node_modules/xtend": { - "version": "4.0.2", - "resolved": "https://registry.npmjs.org/xtend/-/xtend-4.0.2.tgz", - "integrity": "sha512-LKYU1iAXJXUgAXn9URjiu+MWhyUXHsvfp7mcuYm9dSUKK0/CjtrUwFAxD82/mCWbtLsGjFIad0wIsod4zrTAEQ==", - "dev": true, - "engines": { - "node": ">=0.4" - } - }, - "node_modules/y18n": { - "version": "5.0.8", - "resolved": "https://registry.npmjs.org/y18n/-/y18n-5.0.8.tgz", - "integrity": "sha512-0pfFzegeDWJHJIAmTLRP2DwHjdF5s7jo9tuztdQxAhINCdvS+3nGINqPd00AphqJR/0LhANUS6/+7SCb98YOfA==", - "dev": true, - "engines": { - "node": ">=10" - } - }, - "node_modules/yaeti": { - "version": "0.0.6", - "resolved": "https://registry.npmjs.org/yaeti/-/yaeti-0.0.6.tgz", - "integrity": "sha512-MvQa//+KcZCUkBTIC9blM+CU9J2GzuTytsOUwf2lidtvkx/6gnEp1QvJv34t9vdjhFmha/mUiNDbN0D0mJWdug==", - "dev": true, - "engines": { - "node": ">=0.10.32" - } - }, - "node_modules/yallist": { - "version": "3.1.1", - "resolved": "https://registry.npmjs.org/yallist/-/yallist-3.1.1.tgz", - "integrity": "sha512-a4UGQaWPH59mOXUYnAG2ewncQS4i4F43Tv3JoAM+s2VDAmS9NsK8GpDMLrCHPksFT7h3K6TOoUNn2pb7RoXx4g==", - "dev": true - }, - "node_modules/yaml": { - "version": "1.10.2", - "resolved": "https://registry.npmjs.org/yaml/-/yaml-1.10.2.tgz", - "integrity": "sha512-r3vXyErRCYJ7wg28yvBY5VSoAF8ZvlcW9/BwUzEtUsjvX/DKs24dIkuwjtuprwJJHsbyUbLApepYTR1BN4uHrg==", - "dev": true, - "engines": { - "node": ">= 6" - } - }, - "node_modules/yargs": { - "version": "16.2.0", - "resolved": "https://registry.npmjs.org/yargs/-/yargs-16.2.0.tgz", - "integrity": "sha512-D1mvvtDG0L5ft/jGWkLpG1+m0eQxOfaBvTNELraWj22wSVUMWxZUvYgJYcKh6jGGIkJFhH4IZPQhR4TKpc8mBw==", - "dev": true, - "dependencies": { - "cliui": "^7.0.2", - "escalade": "^3.1.1", - "get-caller-file": "^2.0.5", - "require-directory": "^2.1.1", - "string-width": "^4.2.0", - "y18n": "^5.0.5", - "yargs-parser": "^20.2.2" - }, - "engines": { - "node": ">=10" - } - }, - "node_modules/yargs-parser": { - "version": "20.2.4", - "resolved": "https://registry.npmjs.org/yargs-parser/-/yargs-parser-20.2.4.tgz", - "integrity": "sha512-WOkpgNhPTlE73h4VFAFsOnomJVaovO8VqLDzy5saChRBFQFBoMYirowyW+Q9HB4HFF4Z7VZTiG3iSzJJA29yRA==", - "dev": true, - "engines": { - "node": ">=10" - } - }, - "node_modules/yargs-unparser": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/yargs-unparser/-/yargs-unparser-2.0.0.tgz", - "integrity": "sha512-7pRTIA9Qc1caZ0bZ6RYRGbHJthJWuakf+WmHK0rVeLkNrrGhfoabBNdue6kdINI6r4if7ocq9aD/n7xwKOdzOA==", - "dev": true, - "dependencies": { - "camelcase": "^6.0.0", - "decamelize": "^4.0.0", - "flat": "^5.0.2", - "is-plain-obj": "^2.1.0" - }, - "engines": { - "node": ">=10" - } - }, - "node_modules/yargs/node_modules/is-fullwidth-code-point": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/is-fullwidth-code-point/-/is-fullwidth-code-point-3.0.0.tgz", - "integrity": "sha512-zymm5+u+sCsSWyD9qNaejV3DFvhCKclKdizYaJUuHA83RLjb7nSuGnddCHGv0hk+KY7BMAlsWeK4Ueg6EV6XQg==", - "dev": true, - "engines": { - "node": ">=8" - } - }, - "node_modules/yargs/node_modules/string-width": { - "version": "4.2.3", - "resolved": "https://registry.npmjs.org/string-width/-/string-width-4.2.3.tgz", - "integrity": "sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g==", - "dev": true, - "dependencies": { - "emoji-regex": "^8.0.0", - "is-fullwidth-code-point": "^3.0.0", - "strip-ansi": "^6.0.1" - }, - "engines": { - "node": ">=8" - } - }, - "node_modules/yn": { - "version": "3.1.1", - "resolved": "https://registry.npmjs.org/yn/-/yn-3.1.1.tgz", - "integrity": "sha512-Ux4ygGWsu2c7isFWe8Yu1YluJmqVhxqK2cLXNQA5AcC3QfbGNpM7fu0Y8b/z16pXLnFxZYvWhd3fhBY9DLmC6Q==", - "dev": true, - "engines": { - "node": ">=6" - } - }, - "node_modules/yocto-queue": { - "version": "0.1.0", - "resolved": "https://registry.npmjs.org/yocto-queue/-/yocto-queue-0.1.0.tgz", - "integrity": "sha512-rVksvsnNCdJ/ohGc6xgPwyN8eheCxsiLM8mxuE/t/mOVqJewPuO1miLpTHQiRgTKCLexL4MeAFVagts7HmNZ2Q==", - "dev": true, - "engines": { - "node": ">=10" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - } - }, - "dependencies": { - "@aashutoshrathi/word-wrap": { - "version": "1.2.6", - "resolved": "https://registry.npmjs.org/@aashutoshrathi/word-wrap/-/word-wrap-1.2.6.tgz", - "integrity": "sha512-1Yjs2SvM8TflER/OD3cOjhWWOZb58A2t7wpE2S9XfBYTiIl+XFhQG2bjy4Pu1I+EAlCNUzRDYDdFwFYUKvXcIA==", - "dev": true - }, - "@babel/code-frame": { - "version": "7.12.11", - "resolved": "https://registry.npmjs.org/@babel/code-frame/-/code-frame-7.12.11.tgz", - "integrity": "sha512-Zt1yodBx1UcyiePMSkWnU4hPqhwq7hGi2nFL1LeA3EUl+q2LQx16MISgJ0+z7dnmgvP9QtIleuETGOiOH1RcIw==", - "dev": true, - "requires": { - "@babel/highlight": "^7.10.4" - } - }, - "@babel/helper-validator-identifier": { - "version": "7.22.5", - "resolved": "https://registry.npmjs.org/@babel/helper-validator-identifier/-/helper-validator-identifier-7.22.5.tgz", - "integrity": "sha512-aJXu+6lErq8ltp+JhkJUfk1MTGyuA4v7f3pA+BJ5HLfNC6nAQ0Cpi9uOquUj8Hehg0aUiHzWQbOVJGao6ztBAQ==", - "dev": true - }, - "@babel/highlight": { - "version": "7.22.5", - "resolved": "https://registry.npmjs.org/@babel/highlight/-/highlight-7.22.5.tgz", - "integrity": "sha512-BSKlD1hgnedS5XRnGOljZawtag7H1yPfQp0tdNJCHoH6AZ+Pcm9VvkrK59/Yy593Ypg0zMxH2BxD1VPYUQ7UIw==", - "dev": true, - "requires": { - "@babel/helper-validator-identifier": "^7.22.5", - "chalk": "^2.0.0", - "js-tokens": "^4.0.0" - }, - "dependencies": { - "ansi-styles": { - "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, - "requires": { - "color-convert": "^1.9.0" - } - }, - "chalk": { - "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, - "requires": { - "ansi-styles": "^3.2.1", - "escape-string-regexp": "^1.0.5", - "supports-color": "^5.3.0" - } - }, - "color-convert": { - "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, - "requires": { - "color-name": "1.1.3" - } - }, - "color-name": { - "version": "1.1.3", - "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.3.tgz", - "integrity": "sha512-72fSenhMw2HZMTVHeCA9KCmpEIbzWiQsjN+BHcBbS9vr1mtt+vJjPdksIBNUmKAW8TFUDPJK5SUU3QhE9NEXDw==", - "dev": true - }, - "escape-string-regexp": { - "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 - }, - "has-flag": { - "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 - }, - "supports-color": { - "version": "5.5.0", - "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-5.5.0.tgz", - "integrity": "sha512-QjVjwdXIt408MIiAqCX4oUKsgU2EqAGzs2Ppkm4aQYbjm+ZEWEcW4SfFNTr4uMNZma0ey4f5lgLrkB0aX0QMow==", - "dev": true, - "requires": { - "has-flag": "^3.0.0" - } - } - } - }, - "@chainsafe/as-sha256": { - "version": "0.3.1", - "resolved": "https://registry.npmjs.org/@chainsafe/as-sha256/-/as-sha256-0.3.1.tgz", - "integrity": "sha512-hldFFYuf49ed7DAakWVXSJODuq3pzJEguD8tQ7h+sGkM18vja+OFoJI9krnGmgzyuZC2ETX0NOIcCTy31v2Mtg==", - "dev": true - }, - "@chainsafe/persistent-merkle-tree": { - "version": "0.4.2", - "resolved": "https://registry.npmjs.org/@chainsafe/persistent-merkle-tree/-/persistent-merkle-tree-0.4.2.tgz", - "integrity": "sha512-lLO3ihKPngXLTus/L7WHKaw9PnNJWizlOF1H9NNzHP6Xvh82vzg9F2bzkXhYIFshMZ2gTCEz8tq6STe7r5NDfQ==", - "dev": true, - "requires": { - "@chainsafe/as-sha256": "^0.3.1" - } - }, - "@chainsafe/ssz": { - "version": "0.9.4", - "resolved": "https://registry.npmjs.org/@chainsafe/ssz/-/ssz-0.9.4.tgz", - "integrity": "sha512-77Qtg2N1ayqs4Bg/wvnWfg5Bta7iy7IRh8XqXh7oNMeP2HBbBwx8m6yTpA8p0EHItWPEBkgZd5S5/LSlp3GXuQ==", - "dev": true, - "requires": { - "@chainsafe/as-sha256": "^0.3.1", - "@chainsafe/persistent-merkle-tree": "^0.4.2", - "case": "^1.6.3" - } - }, - "@cspotcode/source-map-support": { - "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==", - "dev": true, - "requires": { - "@jridgewell/trace-mapping": "0.3.9" - } - }, - "@ensdomains/ens": { - "version": "0.4.5", - "resolved": "https://registry.npmjs.org/@ensdomains/ens/-/ens-0.4.5.tgz", - "integrity": "sha512-JSvpj1iNMFjK6K+uVl4unqMoa9rf5jopb8cya5UGBWz23Nw8hSNT7efgUx4BTlAPAgpNlEioUfeTyQ6J9ZvTVw==", - "dev": true, - "requires": { - "bluebird": "^3.5.2", - "eth-ens-namehash": "^2.0.8", - "solc": "^0.4.20", - "testrpc": "0.0.1", - "web3-utils": "^1.0.0-beta.31" - }, - "dependencies": { - "ansi-regex": { - "version": "2.1.1", - "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-2.1.1.tgz", - "integrity": "sha512-TIGnTpdo+E3+pCyAluZvtED5p5wCqLdezCyhPZzKPcxvFplEt4i+W7OONCKgeZFT3+y5NZZfOOS/Bdcanm1MYA==", - "dev": true - }, - "camelcase": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/camelcase/-/camelcase-3.0.0.tgz", - "integrity": "sha512-4nhGqUkc4BqbBBB4Q6zLuD7lzzrHYrjKGeYaEji/3tFR5VdJu9v+LilhGIVe8wxEJPPOeWo7eg8dwY13TZ1BNg==", - "dev": true - }, - "cliui": { - "version": "3.2.0", - "resolved": "https://registry.npmjs.org/cliui/-/cliui-3.2.0.tgz", - "integrity": "sha512-0yayqDxWQbqk3ojkYqUKqaAQ6AfNKeKWRNA8kR0WXzAsdHpP4BIaOmMAG87JGuO6qcobyW4GjxHd9PmhEd+T9w==", - "dev": true, - "requires": { - "string-width": "^1.0.1", - "strip-ansi": "^3.0.1", - "wrap-ansi": "^2.0.0" - } - }, - "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 - }, - "fs-extra": { - "version": "0.30.0", - "resolved": "https://registry.npmjs.org/fs-extra/-/fs-extra-0.30.0.tgz", - "integrity": "sha512-UvSPKyhMn6LEd/WpUaV9C9t3zATuqoqfWc3QdPhPLb58prN9tqYPlPWi8Krxi44loBoUzlobqZ3+8tGpxxSzwA==", - "dev": true, - "requires": { - "graceful-fs": "^4.1.2", - "jsonfile": "^2.1.0", - "klaw": "^1.0.0", - "path-is-absolute": "^1.0.0", - "rimraf": "^2.2.8" - } - }, - "get-caller-file": { - "version": "1.0.3", - "resolved": "https://registry.npmjs.org/get-caller-file/-/get-caller-file-1.0.3.tgz", - "integrity": "sha512-3t6rVToeoZfYSGd8YoLFR2DJkiQrIiUrGcjvFX2mDw3bn6k2OtwHN0TNCLbBO+w8qTvimhDkv+LSscbJY1vE6w==", - "dev": true - }, - "is-fullwidth-code-point": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/is-fullwidth-code-point/-/is-fullwidth-code-point-1.0.0.tgz", - "integrity": "sha512-1pqUqRjkhPJ9miNq9SwMfdvi6lBJcd6eFxvfaivQhaH3SgisfiuudvFntdKOmxuee/77l+FPjKrQjWvmPjWrRw==", - "dev": true, - "requires": { - "number-is-nan": "^1.0.0" - } - }, - "jsonfile": { - "version": "2.4.0", - "resolved": "https://registry.npmjs.org/jsonfile/-/jsonfile-2.4.0.tgz", - "integrity": "sha512-PKllAqbgLgxHaj8TElYymKCAgrASebJrWpTnEkOaTowt23VKXXN0sUeriJ+eh7y6ufb/CC5ap11pz71/cM0hUw==", - "dev": true, - "requires": { - "graceful-fs": "^4.1.6" - } - }, - "require-from-string": { - "version": "1.2.1", - "resolved": "https://registry.npmjs.org/require-from-string/-/require-from-string-1.2.1.tgz", - "integrity": "sha512-H7AkJWMobeskkttHyhTVtS0fxpFLjxhbfMa6Bk3wimP7sdPRGL3EyCg3sAQenFfAe+xQ+oAc85Nmtvq0ROM83Q==", - "dev": true - }, - "rimraf": { - "version": "2.7.1", - "resolved": "https://registry.npmjs.org/rimraf/-/rimraf-2.7.1.tgz", - "integrity": "sha512-uWjbaKIK3T1OSVptzX7Nl6PvQ3qAGtKEtVRjRuazjfL3Bx5eI409VZSqgND+4UNnmzLVdPj9FqFJNPqBZFve4w==", - "dev": true, - "requires": { - "glob": "^7.1.3" - } - }, - "semver": { - "version": "5.7.2", - "resolved": "https://registry.npmjs.org/semver/-/semver-5.7.2.tgz", - "integrity": "sha512-cBznnQ9KjJqU67B52RMC65CMarK2600WFnbkcaiwWq3xy/5haFJlshgnpjovMVJ+Hff49d8GEn0b87C5pDQ10g==", - "dev": true - }, - "solc": { - "version": "0.4.26", - "resolved": "https://registry.npmjs.org/solc/-/solc-0.4.26.tgz", - "integrity": "sha512-o+c6FpkiHd+HPjmjEVpQgH7fqZ14tJpXhho+/bQXlXbliLIS/xjXb42Vxh+qQY1WCSTMQ0+a5vR9vi0MfhU6mA==", - "dev": true, - "requires": { - "fs-extra": "^0.30.0", - "memorystream": "^0.3.1", - "require-from-string": "^1.1.0", - "semver": "^5.3.0", - "yargs": "^4.7.1" - } - }, - "string-width": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/string-width/-/string-width-1.0.2.tgz", - "integrity": "sha512-0XsVpQLnVCXHJfyEs8tC0zpTVIr5PKKsQtkT29IwupnPTjtPmQ3xT/4yCREF9hYkV/3M3kzcUTSAZT6a6h81tw==", - "dev": true, - "requires": { - "code-point-at": "^1.0.0", - "is-fullwidth-code-point": "^1.0.0", - "strip-ansi": "^3.0.0" - } - }, - "strip-ansi": { - "version": "3.0.1", - "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-3.0.1.tgz", - "integrity": "sha512-VhumSSbBqDTP8p2ZLKj40UjBCV4+v8bUSEpUb4KjRgWk9pbqGF4REFj6KEagidb2f/M6AzC0EmFyDNGaw9OCzg==", - "dev": true, - "requires": { - "ansi-regex": "^2.0.0" - } - }, - "wrap-ansi": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/wrap-ansi/-/wrap-ansi-2.1.0.tgz", - "integrity": "sha512-vAaEaDM946gbNpH5pLVNR+vX2ht6n0Bt3GXwVB1AuAqZosOvHNF3P7wDnh8KLkSqgUh0uh77le7Owgoz+Z9XBw==", - "dev": true, - "requires": { - "string-width": "^1.0.1", - "strip-ansi": "^3.0.1" - } - }, - "y18n": { - "version": "3.2.2", - "resolved": "https://registry.npmjs.org/y18n/-/y18n-3.2.2.tgz", - "integrity": "sha512-uGZHXkHnhF0XeeAPgnKfPv1bgKAYyVvmNL1xlKsPYZPaIHxGti2hHqvOCQv71XMsLxu1QjergkqogUnms5D3YQ==", - "dev": true - }, - "yargs": { - "version": "4.8.1", - "resolved": "https://registry.npmjs.org/yargs/-/yargs-4.8.1.tgz", - "integrity": "sha512-LqodLrnIDM3IFT+Hf/5sxBnEGECrfdC1uIbgZeJmESCSo4HoCAaKEus8MylXHAkdacGc0ye+Qa+dpkuom8uVYA==", - "dev": true, - "requires": { - "cliui": "^3.2.0", - "decamelize": "^1.1.1", - "get-caller-file": "^1.0.1", - "lodash.assign": "^4.0.3", - "os-locale": "^1.4.0", - "read-pkg-up": "^1.0.1", - "require-directory": "^2.1.1", - "require-main-filename": "^1.0.1", - "set-blocking": "^2.0.0", - "string-width": "^1.0.1", - "which-module": "^1.0.0", - "window-size": "^0.2.0", - "y18n": "^3.2.1", - "yargs-parser": "^2.4.1" - } - }, - "yargs-parser": { - "version": "2.4.1", - "resolved": "https://registry.npmjs.org/yargs-parser/-/yargs-parser-2.4.1.tgz", - "integrity": "sha512-9pIKIJhnI5tonzG6OnCFlz/yln8xHYcGl+pn3xR0Vzff0vzN1PbNRaelgfgRUwZ3s4i3jvxT9WhmUGL4whnasA==", - "dev": true, - "requires": { - "camelcase": "^3.0.0", - "lodash.assign": "^4.0.6" - } - } - } - }, - "@ensdomains/resolver": { - "version": "0.2.4", - "resolved": "https://registry.npmjs.org/@ensdomains/resolver/-/resolver-0.2.4.tgz", - "integrity": "sha512-bvaTH34PMCbv6anRa9I/0zjLJgY4EuznbEMgbV77JBCQ9KNC46rzi0avuxpOfu+xDjPEtSFGqVEOr5GlUSGudA==", - "dev": true - }, - "@eslint/eslintrc": { - "version": "0.4.3", - "resolved": "https://registry.npmjs.org/@eslint/eslintrc/-/eslintrc-0.4.3.tgz", - "integrity": "sha512-J6KFFz5QCYUJq3pf0mjEcCJVERbzv71PUIDczuh9JkwGEzced6CO5ADLHB1rbf/+oPBtoPfMYNOpGDzCANlbXw==", - "dev": true, - "requires": { - "ajv": "^6.12.4", - "debug": "^4.1.1", - "espree": "^7.3.0", - "globals": "^13.9.0", - "ignore": "^4.0.6", - "import-fresh": "^3.2.1", - "js-yaml": "^3.13.1", - "minimatch": "^3.0.4", - "strip-json-comments": "^3.1.1" - }, - "dependencies": { - "ignore": { - "version": "4.0.6", - "resolved": "https://registry.npmjs.org/ignore/-/ignore-4.0.6.tgz", - "integrity": "sha512-cyFDKrqc/YdcWFniJhzI42+AzS+gNwmUzOSFcRCQYwySuBBBy/KjuxWLZ/FHEH6Moq1NizMOBWyTcv8O4OZIMg==", - "dev": true - } - } - }, - "@eth-optimism/contracts": { - "version": "0.6.0", - "resolved": "https://registry.npmjs.org/@eth-optimism/contracts/-/contracts-0.6.0.tgz", - "integrity": "sha512-vQ04wfG9kMf1Fwy3FEMqH2QZbgS0gldKhcBeBUPfO8zu68L61VI97UDXmsMQXzTsEAxK8HnokW3/gosl4/NW3w==", - "requires": { - "@eth-optimism/core-utils": "0.12.0", - "@ethersproject/abstract-provider": "^5.7.0", - "@ethersproject/abstract-signer": "^5.7.0" - }, - "dependencies": { - "@eth-optimism/core-utils": { - "version": "0.12.0", - "resolved": "https://registry.npmjs.org/@eth-optimism/core-utils/-/core-utils-0.12.0.tgz", - "integrity": "sha512-qW+7LZYCz7i8dRa7SRlUKIo1VBU8lvN0HeXCxJR+z+xtMzMQpPds20XJNCMclszxYQHkXY00fOT6GvFw9ZL6nw==", - "requires": { - "@ethersproject/abi": "^5.7.0", - "@ethersproject/abstract-provider": "^5.7.0", - "@ethersproject/address": "^5.7.0", - "@ethersproject/bignumber": "^5.7.0", - "@ethersproject/bytes": "^5.7.0", - "@ethersproject/constants": "^5.7.0", - "@ethersproject/contracts": "^5.7.0", - "@ethersproject/hash": "^5.7.0", - "@ethersproject/keccak256": "^5.7.0", - "@ethersproject/properties": "^5.7.0", - "@ethersproject/providers": "^5.7.0", - "@ethersproject/rlp": "^5.7.0", - "@ethersproject/transactions": "^5.7.0", - "@ethersproject/web": "^5.7.0", - "bufio": "^1.0.7", - "chai": "^4.3.4" - } - } - } - }, - "@eth-optimism/contracts-bedrock": { - "version": "0.17.1", - "resolved": "https://registry.npmjs.org/@eth-optimism/contracts-bedrock/-/contracts-bedrock-0.17.1.tgz", - "integrity": "sha512-Hc5peN5PM8kzl9dzqSD5jv6ED3QliO1DF0dXLRJxfrXR7/rmEeyuAYESUwUM0gdJZjkwRYiS5m230BI6bQmnlw==" - }, - "@eth-optimism/core-utils": { - "version": "0.13.1", - "resolved": "https://registry.npmjs.org/@eth-optimism/core-utils/-/core-utils-0.13.1.tgz", - "integrity": "sha512-1FvzbUmCEy9zSKPG1QWg2VfA2Cy90xBA9Wkp11lXXrz91zUPCNCNSRTujXWYIC86ketNsZp7p4njSf6lTycHCw==", - "requires": { - "@ethersproject/abi": "^5.7.0", - "@ethersproject/abstract-provider": "^5.7.0", - "@ethersproject/address": "^5.7.0", - "@ethersproject/bignumber": "^5.7.0", - "@ethersproject/bytes": "^5.7.0", - "@ethersproject/constants": "^5.7.0", - "@ethersproject/contracts": "^5.7.0", - "@ethersproject/keccak256": "^5.7.0", - "@ethersproject/properties": "^5.7.0", - "@ethersproject/rlp": "^5.7.0", - "@ethersproject/web": "^5.7.1", - "chai": "^4.3.9", - "ethers": "^5.7.2", - "node-fetch": "^2.6.7" - } - }, - "@eth-optimism/sdk": { - "version": "3.2.0", - "resolved": "https://registry.npmjs.org/@eth-optimism/sdk/-/sdk-3.2.0.tgz", - "integrity": "sha512-+ZEO/mDWz3WLzaPVHvgOAK4iN723HmI6sLLr2tmO1/RUoCHVfWMUDwuiikrA49cAsdsjMxCV9+0XNZ8btD2JUg==", - "requires": { - "@eth-optimism/contracts": "0.6.0", - "@eth-optimism/contracts-bedrock": "0.17.1", - "@eth-optimism/core-utils": "0.13.1", - "lodash": "^4.17.21", - "merkletreejs": "^0.3.11", - "rlp": "^2.2.7", - "semver": "^7.5.4" - }, - "dependencies": { - "lru-cache": { - "version": "6.0.0", - "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-6.0.0.tgz", - "integrity": "sha512-Jo6dJ04CmSjuznwJSS3pUeWmd/H0ffTlkXXgwZi+eq1UCmqQwCh+eLsYOYCwY991i2Fah4h1BEMCx4qThGbsiA==", - "requires": { - "yallist": "^4.0.0" - } - }, - "semver": { - "version": "7.6.0", - "resolved": "https://registry.npmjs.org/semver/-/semver-7.6.0.tgz", - "integrity": "sha512-EnwXhrlwXMk9gKu5/flx5sv/an57AkRplG3hTK68W7FRDN+k+OWBj65M7719OkA82XLBxrcX0KSHj+X5COhOVg==", - "requires": { - "lru-cache": "^6.0.0" - } - }, - "yallist": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/yallist/-/yallist-4.0.0.tgz", - "integrity": "sha512-3wdGidZyq5PB084XLES5TpOSRA3wjXAlIWMhum2kRcv/41Sn2emQ0dycQW4uZXLejwKvg6EsvbdlVL+FYEct7A==" - } - } - }, - "@ethereum-waffle/chai": { - "version": "3.4.4", - "resolved": "https://registry.npmjs.org/@ethereum-waffle/chai/-/chai-3.4.4.tgz", - "integrity": "sha512-/K8czydBtXXkcM9X6q29EqEkc5dN3oYenyH2a9hF7rGAApAJUpH8QBtojxOY/xQ2up5W332jqgxwp0yPiYug1g==", - "dev": true, - "requires": { - "@ethereum-waffle/provider": "^3.4.4", - "ethers": "^5.5.2" - } - }, - "@ethereum-waffle/compiler": { - "version": "3.4.4", - "resolved": "https://registry.npmjs.org/@ethereum-waffle/compiler/-/compiler-3.4.4.tgz", - "integrity": "sha512-RUK3axJ8IkD5xpWjWoJgyHclOeEzDLQFga6gKpeGxiS/zBu+HB0W2FvsrrLalTFIaPw/CGYACRBSIxqiCqwqTQ==", - "dev": true, - "requires": { - "@resolver-engine/imports": "^0.3.3", - "@resolver-engine/imports-fs": "^0.3.3", - "@typechain/ethers-v5": "^2.0.0", - "@types/mkdirp": "^0.5.2", - "@types/node-fetch": "^2.5.5", - "ethers": "^5.0.1", - "mkdirp": "^0.5.1", - "node-fetch": "^2.6.1", - "solc": "^0.6.3", - "ts-generator": "^0.1.1", - "typechain": "^3.0.0" - }, - "dependencies": { - "@typechain/ethers-v5": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/@typechain/ethers-v5/-/ethers-v5-2.0.0.tgz", - "integrity": "sha512-0xdCkyGOzdqh4h5JSf+zoWx85IusEjDcPIwNEHP8mrWSnCae4rvrqB+/gtpdNfX7zjlFlZiMeePn2r63EI3Lrw==", - "dev": true, - "requires": { - "ethers": "^5.0.2" - } - }, - "ts-essentials": { - "version": "6.0.7", - "resolved": "https://registry.npmjs.org/ts-essentials/-/ts-essentials-6.0.7.tgz", - "integrity": "sha512-2E4HIIj4tQJlIHuATRHayv0EfMGK3ris/GRk1E3CFnsZzeNV+hUmelbaTZHLtXaZppM5oLhHRtO04gINC4Jusw==", - "dev": true, - "requires": {} - }, - "typechain": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/typechain/-/typechain-3.0.0.tgz", - "integrity": "sha512-ft4KVmiN3zH4JUFu2WJBrwfHeDf772Tt2d8bssDTo/YcckKW2D+OwFrHXRC6hJvO3mHjFQTihoMV6fJOi0Hngg==", - "dev": true, - "requires": { - "command-line-args": "^4.0.7", - "debug": "^4.1.1", - "fs-extra": "^7.0.0", - "js-sha3": "^0.8.0", - "lodash": "^4.17.15", - "ts-essentials": "^6.0.3", - "ts-generator": "^0.1.1" - } - } - } - }, - "@ethereum-waffle/ens": { - "version": "3.4.4", - "resolved": "https://registry.npmjs.org/@ethereum-waffle/ens/-/ens-3.4.4.tgz", - "integrity": "sha512-0m4NdwWxliy3heBYva1Wr4WbJKLnwXizmy5FfSSr5PMbjI7SIGCdCB59U7/ZzY773/hY3bLnzLwvG5mggVjJWg==", - "dev": true, - "requires": { - "@ensdomains/ens": "^0.4.4", - "@ensdomains/resolver": "^0.2.4", - "ethers": "^5.5.2" - } - }, - "@ethereum-waffle/mock-contract": { - "version": "3.4.4", - "resolved": "https://registry.npmjs.org/@ethereum-waffle/mock-contract/-/mock-contract-3.4.4.tgz", - "integrity": "sha512-Mp0iB2YNWYGUV+VMl5tjPsaXKbKo8MDH9wSJ702l9EBjdxFf/vBvnMBAC1Fub1lLtmD0JHtp1pq+mWzg/xlLnA==", - "dev": true, - "requires": { - "@ethersproject/abi": "^5.5.0", - "ethers": "^5.5.2" - } - }, - "@ethereum-waffle/provider": { - "version": "3.4.4", - "resolved": "https://registry.npmjs.org/@ethereum-waffle/provider/-/provider-3.4.4.tgz", - "integrity": "sha512-GK8oKJAM8+PKy2nK08yDgl4A80mFuI8zBkE0C9GqTRYQqvuxIyXoLmJ5NZU9lIwyWVv5/KsoA11BgAv2jXE82g==", - "dev": true, - "requires": { - "@ethereum-waffle/ens": "^3.4.4", - "ethers": "^5.5.2", - "ganache-core": "^2.13.2", - "patch-package": "^6.2.2", - "postinstall-postinstall": "^2.1.0" - } - }, - "@ethereumjs/common": { - "version": "2.6.5", - "resolved": "https://registry.npmjs.org/@ethereumjs/common/-/common-2.6.5.tgz", - "integrity": "sha512-lRyVQOeCDaIVtgfbowla32pzeDv2Obr8oR8Put5RdUBNRGr1VGPGQNGP6elWIpgK3YdpzqTOh4GyUGOureVeeA==", - "dev": true, - "requires": { - "crc-32": "^1.2.0", - "ethereumjs-util": "^7.1.5" - }, - "dependencies": { - "ethereum-cryptography": { - "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, - "requires": { - "@types/pbkdf2": "^3.0.0", - "@types/secp256k1": "^4.0.1", - "blakejs": "^1.1.0", - "browserify-aes": "^1.2.0", - "bs58check": "^2.1.2", - "create-hash": "^1.2.0", - "create-hmac": "^1.1.7", - "hash.js": "^1.1.7", - "keccak": "^3.0.0", - "pbkdf2": "^3.0.17", - "randombytes": "^2.1.0", - "safe-buffer": "^5.1.2", - "scrypt-js": "^3.0.0", - "secp256k1": "^4.0.1", - "setimmediate": "^1.0.5" - } - }, - "ethereumjs-util": { - "version": "7.1.5", - "resolved": "https://registry.npmjs.org/ethereumjs-util/-/ethereumjs-util-7.1.5.tgz", - "integrity": "sha512-SDl5kKrQAudFBUe5OJM9Ac6WmMyYmXX/6sTmLZ3ffG2eY6ZIGBes3pEDxNN6V72WyOw4CPD5RomKdsa8DAAwLg==", - "dev": true, - "requires": { - "@types/bn.js": "^5.1.0", - "bn.js": "^5.1.2", - "create-hash": "^1.1.2", - "ethereum-cryptography": "^0.1.3", - "rlp": "^2.2.4" - } - } - } - }, - "@ethereumjs/tx": { - "version": "3.5.2", - "resolved": "https://registry.npmjs.org/@ethereumjs/tx/-/tx-3.5.2.tgz", - "integrity": "sha512-gQDNJWKrSDGu2w7w0PzVXVBNMzb7wwdDOmOqczmhNjqFxFuIbhVJDwiGEnxFNC2/b8ifcZzY7MLcluizohRzNw==", - "dev": true, - "requires": { - "@ethereumjs/common": "^2.6.4", - "ethereumjs-util": "^7.1.5" - }, - "dependencies": { - "ethereum-cryptography": { - "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, - "requires": { - "@types/pbkdf2": "^3.0.0", - "@types/secp256k1": "^4.0.1", - "blakejs": "^1.1.0", - "browserify-aes": "^1.2.0", - "bs58check": "^2.1.2", - "create-hash": "^1.2.0", - "create-hmac": "^1.1.7", - "hash.js": "^1.1.7", - "keccak": "^3.0.0", - "pbkdf2": "^3.0.17", - "randombytes": "^2.1.0", - "safe-buffer": "^5.1.2", - "scrypt-js": "^3.0.0", - "secp256k1": "^4.0.1", - "setimmediate": "^1.0.5" - } - }, - "ethereumjs-util": { - "version": "7.1.5", - "resolved": "https://registry.npmjs.org/ethereumjs-util/-/ethereumjs-util-7.1.5.tgz", - "integrity": "sha512-SDl5kKrQAudFBUe5OJM9Ac6WmMyYmXX/6sTmLZ3ffG2eY6ZIGBes3pEDxNN6V72WyOw4CPD5RomKdsa8DAAwLg==", - "dev": true, - "requires": { - "@types/bn.js": "^5.1.0", - "bn.js": "^5.1.2", - "create-hash": "^1.1.2", - "ethereum-cryptography": "^0.1.3", - "rlp": "^2.2.4" - } - } - } - }, - "@ethersproject/abi": { - "version": "5.7.0", - "resolved": "https://registry.npmjs.org/@ethersproject/abi/-/abi-5.7.0.tgz", - "integrity": "sha512-351ktp42TiRcYB3H1OP8yajPeAQstMW/yCFokj/AthP9bLHzQFPlOrxOcwYEDkUAICmOHljvN4K39OMTMUa9RA==", - "requires": { - "@ethersproject/address": "^5.7.0", - "@ethersproject/bignumber": "^5.7.0", - "@ethersproject/bytes": "^5.7.0", - "@ethersproject/constants": "^5.7.0", - "@ethersproject/hash": "^5.7.0", - "@ethersproject/keccak256": "^5.7.0", - "@ethersproject/logger": "^5.7.0", - "@ethersproject/properties": "^5.7.0", - "@ethersproject/strings": "^5.7.0" - } - }, - "@ethersproject/abstract-provider": { - "version": "5.7.0", - "resolved": "https://registry.npmjs.org/@ethersproject/abstract-provider/-/abstract-provider-5.7.0.tgz", - "integrity": "sha512-R41c9UkchKCpAqStMYUpdunjo3pkEvZC3FAwZn5S5MGbXoMQOHIdHItezTETxAO5bevtMApSyEhn9+CHcDsWBw==", - "requires": { - "@ethersproject/bignumber": "^5.7.0", - "@ethersproject/bytes": "^5.7.0", - "@ethersproject/logger": "^5.7.0", - "@ethersproject/networks": "^5.7.0", - "@ethersproject/properties": "^5.7.0", - "@ethersproject/transactions": "^5.7.0", - "@ethersproject/web": "^5.7.0" - } - }, - "@ethersproject/abstract-signer": { - "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==", - "requires": { - "@ethersproject/abstract-provider": "^5.7.0", - "@ethersproject/bignumber": "^5.7.0", - "@ethersproject/bytes": "^5.7.0", - "@ethersproject/logger": "^5.7.0", - "@ethersproject/properties": "^5.7.0" - } - }, - "@ethersproject/address": { - "version": "5.7.0", - "resolved": "https://registry.npmjs.org/@ethersproject/address/-/address-5.7.0.tgz", - "integrity": "sha512-9wYhYt7aghVGo758POM5nqcOMaE168Q6aRLJZwUmiqSrAungkG74gSSeKEIR7ukixesdRZGPgVqme6vmxs1fkA==", - "requires": { - "@ethersproject/bignumber": "^5.7.0", - "@ethersproject/bytes": "^5.7.0", - "@ethersproject/keccak256": "^5.7.0", - "@ethersproject/logger": "^5.7.0", - "@ethersproject/rlp": "^5.7.0" - } - }, - "@ethersproject/base64": { - "version": "5.7.0", - "resolved": "https://registry.npmjs.org/@ethersproject/base64/-/base64-5.7.0.tgz", - "integrity": "sha512-Dr8tcHt2mEbsZr/mwTPIQAf3Ai0Bks/7gTw9dSqk1mQvhW3XvRlmDJr/4n+wg1JmCl16NZue17CDh8xb/vZ0sQ==", - "requires": { - "@ethersproject/bytes": "^5.7.0" - } - }, - "@ethersproject/basex": { - "version": "5.7.0", - "resolved": "https://registry.npmjs.org/@ethersproject/basex/-/basex-5.7.0.tgz", - "integrity": "sha512-ywlh43GwZLv2Voc2gQVTKBoVQ1mti3d8HK5aMxsfu/nRDnMmNqaSJ3r3n85HBByT8OpoY96SXM1FogC533T4zw==", - "requires": { - "@ethersproject/bytes": "^5.7.0", - "@ethersproject/properties": "^5.7.0" - } - }, - "@ethersproject/bignumber": { - "version": "5.7.0", - "resolved": "https://registry.npmjs.org/@ethersproject/bignumber/-/bignumber-5.7.0.tgz", - "integrity": "sha512-n1CAdIHRWjSucQO3MC1zPSVgV/6dy/fjL9pMrPP9peL+QxEg9wOsVqwD4+818B6LUEtaXzVHQiuivzRoxPxUGw==", - "requires": { - "@ethersproject/bytes": "^5.7.0", - "@ethersproject/logger": "^5.7.0", - "bn.js": "^5.2.1" - } - }, - "@ethersproject/bytes": { - "version": "5.7.0", - "resolved": "https://registry.npmjs.org/@ethersproject/bytes/-/bytes-5.7.0.tgz", - "integrity": "sha512-nsbxwgFXWh9NyYWo+U8atvmMsSdKJprTcICAkvbBffT75qDocbuggBU0SJiVK2MuTrp0q+xvLkTnGMPK1+uA9A==", - "requires": { - "@ethersproject/logger": "^5.7.0" - } - }, - "@ethersproject/constants": { - "version": "5.7.0", - "resolved": "https://registry.npmjs.org/@ethersproject/constants/-/constants-5.7.0.tgz", - "integrity": "sha512-DHI+y5dBNvkpYUMiRQyxRBYBefZkJfo70VUkUAsRjcPs47muV9evftfZ0PJVCXYbAiCgght0DtcF9srFQmIgWA==", - "requires": { - "@ethersproject/bignumber": "^5.7.0" - } - }, - "@ethersproject/contracts": { - "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==", - "requires": { - "@ethersproject/abi": "^5.7.0", - "@ethersproject/abstract-provider": "^5.7.0", - "@ethersproject/abstract-signer": "^5.7.0", - "@ethersproject/address": "^5.7.0", - "@ethersproject/bignumber": "^5.7.0", - "@ethersproject/bytes": "^5.7.0", - "@ethersproject/constants": "^5.7.0", - "@ethersproject/logger": "^5.7.0", - "@ethersproject/properties": "^5.7.0", - "@ethersproject/transactions": "^5.7.0" - } - }, - "@ethersproject/hash": { - "version": "5.7.0", - "resolved": "https://registry.npmjs.org/@ethersproject/hash/-/hash-5.7.0.tgz", - "integrity": "sha512-qX5WrQfnah1EFnO5zJv1v46a8HW0+E5xuBBDTwMFZLuVTx0tbU2kkx15NqdjxecrLGatQN9FGQKpb1FKdHCt+g==", - "requires": { - "@ethersproject/abstract-signer": "^5.7.0", - "@ethersproject/address": "^5.7.0", - "@ethersproject/base64": "^5.7.0", - "@ethersproject/bignumber": "^5.7.0", - "@ethersproject/bytes": "^5.7.0", - "@ethersproject/keccak256": "^5.7.0", - "@ethersproject/logger": "^5.7.0", - "@ethersproject/properties": "^5.7.0", - "@ethersproject/strings": "^5.7.0" - } - }, - "@ethersproject/hdnode": { - "version": "5.7.0", - "resolved": "https://registry.npmjs.org/@ethersproject/hdnode/-/hdnode-5.7.0.tgz", - "integrity": "sha512-OmyYo9EENBPPf4ERhR7oj6uAtUAhYGqOnIS+jE5pTXvdKBS99ikzq1E7Iv0ZQZ5V36Lqx1qZLeak0Ra16qpeOg==", - "requires": { - "@ethersproject/abstract-signer": "^5.7.0", - "@ethersproject/basex": "^5.7.0", - "@ethersproject/bignumber": "^5.7.0", - "@ethersproject/bytes": "^5.7.0", - "@ethersproject/logger": "^5.7.0", - "@ethersproject/pbkdf2": "^5.7.0", - "@ethersproject/properties": "^5.7.0", - "@ethersproject/sha2": "^5.7.0", - "@ethersproject/signing-key": "^5.7.0", - "@ethersproject/strings": "^5.7.0", - "@ethersproject/transactions": "^5.7.0", - "@ethersproject/wordlists": "^5.7.0" - } - }, - "@ethersproject/json-wallets": { - "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==", - "requires": { - "@ethersproject/abstract-signer": "^5.7.0", - "@ethersproject/address": "^5.7.0", - "@ethersproject/bytes": "^5.7.0", - "@ethersproject/hdnode": "^5.7.0", - "@ethersproject/keccak256": "^5.7.0", - "@ethersproject/logger": "^5.7.0", - "@ethersproject/pbkdf2": "^5.7.0", - "@ethersproject/properties": "^5.7.0", - "@ethersproject/random": "^5.7.0", - "@ethersproject/strings": "^5.7.0", - "@ethersproject/transactions": "^5.7.0", - "aes-js": "3.0.0", - "scrypt-js": "3.0.1" - } - }, - "@ethersproject/keccak256": { - "version": "5.7.0", - "resolved": "https://registry.npmjs.org/@ethersproject/keccak256/-/keccak256-5.7.0.tgz", - "integrity": "sha512-2UcPboeL/iW+pSg6vZ6ydF8tCnv3Iu/8tUmLLzWWGzxWKFFqOBQFLo6uLUv6BDrLgCDfN28RJ/wtByx+jZ4KBg==", - "requires": { - "@ethersproject/bytes": "^5.7.0", - "js-sha3": "0.8.0" - } - }, - "@ethersproject/logger": { - "version": "5.7.0", - "resolved": "https://registry.npmjs.org/@ethersproject/logger/-/logger-5.7.0.tgz", - "integrity": "sha512-0odtFdXu/XHtjQXJYA3u9G0G8btm0ND5Cu8M7i5vhEcE8/HmF4Lbdqanwyv4uQTr2tx6b7fQRmgLrsnpQlmnig==" - }, - "@ethersproject/networks": { - "version": "5.7.1", - "resolved": "https://registry.npmjs.org/@ethersproject/networks/-/networks-5.7.1.tgz", - "integrity": "sha512-n/MufjFYv3yFcUyfhnXotyDlNdFb7onmkSy8aQERi2PjNcnWQ66xXxa3XlS8nCcA8aJKJjIIMNJTC7tu80GwpQ==", - "requires": { - "@ethersproject/logger": "^5.7.0" - } - }, - "@ethersproject/pbkdf2": { - "version": "5.7.0", - "resolved": "https://registry.npmjs.org/@ethersproject/pbkdf2/-/pbkdf2-5.7.0.tgz", - "integrity": "sha512-oR/dBRZR6GTyaofd86DehG72hY6NpAjhabkhxgr3X2FpJtJuodEl2auADWBZfhDHgVCbu3/H/Ocq2uC6dpNjjw==", - "requires": { - "@ethersproject/bytes": "^5.7.0", - "@ethersproject/sha2": "^5.7.0" - } - }, - "@ethersproject/properties": { - "version": "5.7.0", - "resolved": "https://registry.npmjs.org/@ethersproject/properties/-/properties-5.7.0.tgz", - "integrity": "sha512-J87jy8suntrAkIZtecpxEPxY//szqr1mlBaYlQ0r4RCaiD2hjheqF9s1LVE8vVuJCXisjIP+JgtK/Do54ej4Sw==", - "requires": { - "@ethersproject/logger": "^5.7.0" - } - }, - "@ethersproject/providers": { - "version": "5.7.2", - "resolved": "https://registry.npmjs.org/@ethersproject/providers/-/providers-5.7.2.tgz", - "integrity": "sha512-g34EWZ1WWAVgr4aptGlVBF8mhl3VWjv+8hoAnzStu8Ah22VHBsuGzP17eb6xDVRzw895G4W7vvx60lFFur/1Rg==", - "requires": { - "@ethersproject/abstract-provider": "^5.7.0", - "@ethersproject/abstract-signer": "^5.7.0", - "@ethersproject/address": "^5.7.0", - "@ethersproject/base64": "^5.7.0", - "@ethersproject/basex": "^5.7.0", - "@ethersproject/bignumber": "^5.7.0", - "@ethersproject/bytes": "^5.7.0", - "@ethersproject/constants": "^5.7.0", - "@ethersproject/hash": "^5.7.0", - "@ethersproject/logger": "^5.7.0", - "@ethersproject/networks": "^5.7.0", - "@ethersproject/properties": "^5.7.0", - "@ethersproject/random": "^5.7.0", - "@ethersproject/rlp": "^5.7.0", - "@ethersproject/sha2": "^5.7.0", - "@ethersproject/strings": "^5.7.0", - "@ethersproject/transactions": "^5.7.0", - "@ethersproject/web": "^5.7.0", - "bech32": "1.1.4", - "ws": "7.4.6" - } - }, - "@ethersproject/random": { - "version": "5.7.0", - "resolved": "https://registry.npmjs.org/@ethersproject/random/-/random-5.7.0.tgz", - "integrity": "sha512-19WjScqRA8IIeWclFme75VMXSBvi4e6InrUNuaR4s5pTF2qNhcGdCUwdxUVGtDDqC00sDLCO93jPQoDUH4HVmQ==", - "requires": { - "@ethersproject/bytes": "^5.7.0", - "@ethersproject/logger": "^5.7.0" - } - }, - "@ethersproject/rlp": { - "version": "5.7.0", - "resolved": "https://registry.npmjs.org/@ethersproject/rlp/-/rlp-5.7.0.tgz", - "integrity": "sha512-rBxzX2vK8mVF7b0Tol44t5Tb8gomOHkj5guL+HhzQ1yBh/ydjGnpw6at+X6Iw0Kp3OzzzkcKp8N9r0W4kYSs9w==", - "requires": { - "@ethersproject/bytes": "^5.7.0", - "@ethersproject/logger": "^5.7.0" - } - }, - "@ethersproject/sha2": { - "version": "5.7.0", - "resolved": "https://registry.npmjs.org/@ethersproject/sha2/-/sha2-5.7.0.tgz", - "integrity": "sha512-gKlH42riwb3KYp0reLsFTokByAKoJdgFCwI+CCiX/k+Jm2mbNs6oOaCjYQSlI1+XBVejwH2KrmCbMAT/GnRDQw==", - "requires": { - "@ethersproject/bytes": "^5.7.0", - "@ethersproject/logger": "^5.7.0", - "hash.js": "1.1.7" - } - }, - "@ethersproject/signing-key": { - "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==", - "requires": { - "@ethersproject/bytes": "^5.7.0", - "@ethersproject/logger": "^5.7.0", - "@ethersproject/properties": "^5.7.0", - "bn.js": "^5.2.1", - "elliptic": "6.5.4", - "hash.js": "1.1.7" - } - }, - "@ethersproject/solidity": { - "version": "5.7.0", - "resolved": "https://registry.npmjs.org/@ethersproject/solidity/-/solidity-5.7.0.tgz", - "integrity": "sha512-HmabMd2Dt/raavyaGukF4XxizWKhKQ24DoLtdNbBmNKUOPqwjsKQSdV9GQtj9CBEea9DlzETlVER1gYeXXBGaA==", - "requires": { - "@ethersproject/bignumber": "^5.7.0", - "@ethersproject/bytes": "^5.7.0", - "@ethersproject/keccak256": "^5.7.0", - "@ethersproject/logger": "^5.7.0", - "@ethersproject/sha2": "^5.7.0", - "@ethersproject/strings": "^5.7.0" - } - }, - "@ethersproject/strings": { - "version": "5.7.0", - "resolved": "https://registry.npmjs.org/@ethersproject/strings/-/strings-5.7.0.tgz", - "integrity": "sha512-/9nu+lj0YswRNSH0NXYqrh8775XNyEdUQAuf3f+SmOrnVewcJ5SBNAjF7lpgehKi4abvNNXyf+HX86czCdJ8Mg==", - "requires": { - "@ethersproject/bytes": "^5.7.0", - "@ethersproject/constants": "^5.7.0", - "@ethersproject/logger": "^5.7.0" - } - }, - "@ethersproject/transactions": { - "version": "5.7.0", - "resolved": "https://registry.npmjs.org/@ethersproject/transactions/-/transactions-5.7.0.tgz", - "integrity": "sha512-kmcNicCp1lp8qanMTC3RIikGgoJ80ztTyvtsFvCYpSCfkjhD0jZ2LOrnbcuxuToLIUYYf+4XwD1rP+B/erDIhQ==", - "requires": { - "@ethersproject/address": "^5.7.0", - "@ethersproject/bignumber": "^5.7.0", - "@ethersproject/bytes": "^5.7.0", - "@ethersproject/constants": "^5.7.0", - "@ethersproject/keccak256": "^5.7.0", - "@ethersproject/logger": "^5.7.0", - "@ethersproject/properties": "^5.7.0", - "@ethersproject/rlp": "^5.7.0", - "@ethersproject/signing-key": "^5.7.0" - } - }, - "@ethersproject/units": { - "version": "5.7.0", - "resolved": "https://registry.npmjs.org/@ethersproject/units/-/units-5.7.0.tgz", - "integrity": "sha512-pD3xLMy3SJu9kG5xDGI7+xhTEmGXlEqXU4OfNapmfnxLVY4EMSSRp7j1k7eezutBPH7RBN/7QPnwR7hzNlEFeg==", - "requires": { - "@ethersproject/bignumber": "^5.7.0", - "@ethersproject/constants": "^5.7.0", - "@ethersproject/logger": "^5.7.0" - } - }, - "@ethersproject/wallet": { - "version": "5.7.0", - "resolved": "https://registry.npmjs.org/@ethersproject/wallet/-/wallet-5.7.0.tgz", - "integrity": "sha512-MhmXlJXEJFBFVKrDLB4ZdDzxcBxQ3rLyCkhNqVu3CDYvR97E+8r01UgrI+TI99Le+aYm/in/0vp86guJuM7FCA==", - "requires": { - "@ethersproject/abstract-provider": "^5.7.0", - "@ethersproject/abstract-signer": "^5.7.0", - "@ethersproject/address": "^5.7.0", - "@ethersproject/bignumber": "^5.7.0", - "@ethersproject/bytes": "^5.7.0", - "@ethersproject/hash": "^5.7.0", - "@ethersproject/hdnode": "^5.7.0", - "@ethersproject/json-wallets": "^5.7.0", - "@ethersproject/keccak256": "^5.7.0", - "@ethersproject/logger": "^5.7.0", - "@ethersproject/properties": "^5.7.0", - "@ethersproject/random": "^5.7.0", - "@ethersproject/signing-key": "^5.7.0", - "@ethersproject/transactions": "^5.7.0", - "@ethersproject/wordlists": "^5.7.0" - } - }, - "@ethersproject/web": { - "version": "5.7.1", - "resolved": "https://registry.npmjs.org/@ethersproject/web/-/web-5.7.1.tgz", - "integrity": "sha512-Gueu8lSvyjBWL4cYsWsjh6MtMwM0+H4HvqFPZfB6dV8ctbP9zFAO73VG1cMWae0FLPCtz0peKPpZY8/ugJJX2w==", - "requires": { - "@ethersproject/base64": "^5.7.0", - "@ethersproject/bytes": "^5.7.0", - "@ethersproject/logger": "^5.7.0", - "@ethersproject/properties": "^5.7.0", - "@ethersproject/strings": "^5.7.0" - } - }, - "@ethersproject/wordlists": { - "version": "5.7.0", - "resolved": "https://registry.npmjs.org/@ethersproject/wordlists/-/wordlists-5.7.0.tgz", - "integrity": "sha512-S2TFNJNfHWVHNE6cNDjbVlZ6MgE17MIxMbMg2zv3wn+3XSJGosL1m9ZVv3GXCf/2ymSsQ+hRI5IzoMJTG6aoVA==", - "requires": { - "@ethersproject/bytes": "^5.7.0", - "@ethersproject/hash": "^5.7.0", - "@ethersproject/logger": "^5.7.0", - "@ethersproject/properties": "^5.7.0", - "@ethersproject/strings": "^5.7.0" - } - }, - "@fastify/busboy": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/@fastify/busboy/-/busboy-2.0.0.tgz", - "integrity": "sha512-JUFJad5lv7jxj926GPgymrWQxxjPYuJNiNjNMzqT+HiuP6Vl3dk5xzG+8sTX96np0ZAluvaMzPsjhHZ5rNuNQQ==", - "dev": true - }, - "@humanwhocodes/config-array": { - "version": "0.5.0", - "resolved": "https://registry.npmjs.org/@humanwhocodes/config-array/-/config-array-0.5.0.tgz", - "integrity": "sha512-FagtKFz74XrTl7y6HCzQpwDfXP0yhxe9lHLD1UZxjvZIcbyRz8zTFF/yYNfSfzU414eDwZ1SrO0Qvtyf+wFMQg==", - "dev": true, - "requires": { - "@humanwhocodes/object-schema": "^1.2.0", - "debug": "^4.1.1", - "minimatch": "^3.0.4" - } - }, - "@humanwhocodes/object-schema": { - "version": "1.2.1", - "resolved": "https://registry.npmjs.org/@humanwhocodes/object-schema/-/object-schema-1.2.1.tgz", - "integrity": "sha512-ZnQMnLV4e7hDlUvw8H+U8ASL02SS2Gn6+9Ac3wGGLIe7+je2AeAOxPY+izIPJDfFDb7eDjev0Us8MO1iFRN8hA==", - "dev": true - }, - "@jridgewell/resolve-uri": { - "version": "3.1.1", - "resolved": "https://registry.npmjs.org/@jridgewell/resolve-uri/-/resolve-uri-3.1.1.tgz", - "integrity": "sha512-dSYZh7HhCDtCKm4QakX0xFpsRDqjjtZf/kjI/v3T3Nwt5r8/qz/M19F9ySyOqU94SXBmeG9ttTul+YnR4LOxFA==", - "dev": true - }, - "@jridgewell/sourcemap-codec": { - "version": "1.4.15", - "resolved": "https://registry.npmjs.org/@jridgewell/sourcemap-codec/-/sourcemap-codec-1.4.15.tgz", - "integrity": "sha512-eF2rxCRulEKXHTRiDrDy6erMYWqNw4LPdQ8UQA4huuxaQsVeRPFl2oM8oDGxMFhJUWZf9McpLtJasDDZb/Bpeg==", - "dev": true - }, - "@jridgewell/trace-mapping": { - "version": "0.3.9", - "resolved": "https://registry.npmjs.org/@jridgewell/trace-mapping/-/trace-mapping-0.3.9.tgz", - "integrity": "sha512-3Belt6tdc8bPgAtbcmdtNJlirVoTmEb5e2gC94PnkwEW9jI6CAHUeoG85tjWP5WquqfavoMtMwiG4P926ZKKuQ==", - "dev": true, - "requires": { - "@jridgewell/resolve-uri": "^3.0.3", - "@jridgewell/sourcemap-codec": "^1.4.10" - } - }, - "@lidofinance/evm-script-decoder": { - "version": "0.2.2", - "resolved": "https://registry.npmjs.org/@lidofinance/evm-script-decoder/-/evm-script-decoder-0.2.2.tgz", - "integrity": "sha512-Gk7gDus2QvOOCCg8NCSOgv43rNIdMFohfRtyc9PSWViIH0GLqNiOFXR71tfwkemrNpu2A0LlBakoB9vgCY+Yfw==", - "requires": { - "@ethersproject/abi": "^5.0.7", - "keccak256": "^1.0.3" - } - }, - "@metamask/eth-sig-util": { - "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, - "requires": { - "ethereumjs-abi": "^0.6.8", - "ethereumjs-util": "^6.2.1", - "ethjs-util": "^0.1.6", - "tweetnacl": "^1.0.3", - "tweetnacl-util": "^0.15.1" - } - }, - "@noble/hashes": { - "version": "1.2.0", - "resolved": "https://registry.npmjs.org/@noble/hashes/-/hashes-1.2.0.tgz", - "integrity": "sha512-FZfhjEDbT5GRswV3C6uvLPHMiVD6lQBmpoX5+eSiPaMTXte/IKqI5dykDxzZB/WBeK/CDuQRBWarPdi3FNY2zQ==", - "dev": true - }, - "@noble/secp256k1": { - "version": "1.7.1", - "resolved": "https://registry.npmjs.org/@noble/secp256k1/-/secp256k1-1.7.1.tgz", - "integrity": "sha512-hOUk6AyBFmqVrv7k5WAw/LpszxVbj9gGN4JRkIX52fdFAj1UA61KXmZDvqVEm+pOyec3+fIeZB02LYa/pWOArw==", - "dev": true - }, - "@nodelib/fs.scandir": { - "version": "2.1.5", - "resolved": "https://registry.npmjs.org/@nodelib/fs.scandir/-/fs.scandir-2.1.5.tgz", - "integrity": "sha512-vq24Bq3ym5HEQm2NKCr3yXDwjc7vTsEThRDnkp2DK9p1uqLR+DHurm/NOTo0KG7HYHU7eppKZj3MyqYuMBf62g==", - "dev": true, - "requires": { - "@nodelib/fs.stat": "2.0.5", - "run-parallel": "^1.1.9" - } - }, - "@nodelib/fs.stat": { - "version": "2.0.5", - "resolved": "https://registry.npmjs.org/@nodelib/fs.stat/-/fs.stat-2.0.5.tgz", - "integrity": "sha512-RkhPPp2zrqDAQA/2jNhnztcPAlv64XdhIp7a7454A5ovI7Bukxgt7MX7udwAu3zg1DcpPU0rz3VV1SeaqvY4+A==", - "dev": true - }, - "@nodelib/fs.walk": { - "version": "1.2.8", - "resolved": "https://registry.npmjs.org/@nodelib/fs.walk/-/fs.walk-1.2.8.tgz", - "integrity": "sha512-oGB+UxlgWcgQkgwo8GcEGwemoTFt3FIO9ababBmaGwXIoBKZ+GTy0pP185beGg7Llih/NSHSV2XAs1lnznocSg==", - "dev": true, - "requires": { - "@nodelib/fs.scandir": "2.1.5", - "fastq": "^1.6.0" - } - }, - "@nomicfoundation/ethereumjs-block": { - "version": "5.0.1", - "resolved": "https://registry.npmjs.org/@nomicfoundation/ethereumjs-block/-/ethereumjs-block-5.0.1.tgz", - "integrity": "sha512-u1Yioemi6Ckj3xspygu/SfFvm8vZEO8/Yx5a1QLzi6nVU0jz3Pg2OmHKJ5w+D9Ogk1vhwRiqEBAqcb0GVhCyHw==", - "dev": true, - "requires": { - "@nomicfoundation/ethereumjs-common": "4.0.1", - "@nomicfoundation/ethereumjs-rlp": "5.0.1", - "@nomicfoundation/ethereumjs-trie": "6.0.1", - "@nomicfoundation/ethereumjs-tx": "5.0.1", - "@nomicfoundation/ethereumjs-util": "9.0.1", - "ethereum-cryptography": "0.1.3", - "ethers": "^5.7.1" - }, - "dependencies": { - "ethereum-cryptography": { - "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, - "requires": { - "@types/pbkdf2": "^3.0.0", - "@types/secp256k1": "^4.0.1", - "blakejs": "^1.1.0", - "browserify-aes": "^1.2.0", - "bs58check": "^2.1.2", - "create-hash": "^1.2.0", - "create-hmac": "^1.1.7", - "hash.js": "^1.1.7", - "keccak": "^3.0.0", - "pbkdf2": "^3.0.17", - "randombytes": "^2.1.0", - "safe-buffer": "^5.1.2", - "scrypt-js": "^3.0.0", - "secp256k1": "^4.0.1", - "setimmediate": "^1.0.5" - } - } - } - }, - "@nomicfoundation/ethereumjs-blockchain": { - "version": "7.0.1", - "resolved": "https://registry.npmjs.org/@nomicfoundation/ethereumjs-blockchain/-/ethereumjs-blockchain-7.0.1.tgz", - "integrity": "sha512-NhzndlGg829XXbqJEYrF1VeZhAwSPgsK/OB7TVrdzft3y918hW5KNd7gIZ85sn6peDZOdjBsAXIpXZ38oBYE5A==", - "dev": true, - "requires": { - "@nomicfoundation/ethereumjs-block": "5.0.1", - "@nomicfoundation/ethereumjs-common": "4.0.1", - "@nomicfoundation/ethereumjs-ethash": "3.0.1", - "@nomicfoundation/ethereumjs-rlp": "5.0.1", - "@nomicfoundation/ethereumjs-trie": "6.0.1", - "@nomicfoundation/ethereumjs-tx": "5.0.1", - "@nomicfoundation/ethereumjs-util": "9.0.1", - "abstract-level": "^1.0.3", - "debug": "^4.3.3", - "ethereum-cryptography": "0.1.3", - "level": "^8.0.0", - "lru-cache": "^5.1.1", - "memory-level": "^1.0.0" - }, - "dependencies": { - "ethereum-cryptography": { - "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, - "requires": { - "@types/pbkdf2": "^3.0.0", - "@types/secp256k1": "^4.0.1", - "blakejs": "^1.1.0", - "browserify-aes": "^1.2.0", - "bs58check": "^2.1.2", - "create-hash": "^1.2.0", - "create-hmac": "^1.1.7", - "hash.js": "^1.1.7", - "keccak": "^3.0.0", - "pbkdf2": "^3.0.17", - "randombytes": "^2.1.0", - "safe-buffer": "^5.1.2", - "scrypt-js": "^3.0.0", - "secp256k1": "^4.0.1", - "setimmediate": "^1.0.5" - } - } - } - }, - "@nomicfoundation/ethereumjs-common": { - "version": "4.0.1", - "resolved": "https://registry.npmjs.org/@nomicfoundation/ethereumjs-common/-/ethereumjs-common-4.0.1.tgz", - "integrity": "sha512-OBErlkfp54GpeiE06brBW/TTbtbuBJV5YI5Nz/aB2evTDo+KawyEzPjBlSr84z/8MFfj8wS2wxzQX1o32cev5g==", - "dev": true, - "requires": { - "@nomicfoundation/ethereumjs-util": "9.0.1", - "crc-32": "^1.2.0" - } - }, - "@nomicfoundation/ethereumjs-ethash": { - "version": "3.0.1", - "resolved": "https://registry.npmjs.org/@nomicfoundation/ethereumjs-ethash/-/ethereumjs-ethash-3.0.1.tgz", - "integrity": "sha512-KDjGIB5igzWOp8Ik5I6QiRH5DH+XgILlplsHR7TEuWANZA759G6krQ6o8bvj+tRUz08YygMQu/sGd9mJ1DYT8w==", - "dev": true, - "requires": { - "@nomicfoundation/ethereumjs-block": "5.0.1", - "@nomicfoundation/ethereumjs-rlp": "5.0.1", - "@nomicfoundation/ethereumjs-util": "9.0.1", - "abstract-level": "^1.0.3", - "bigint-crypto-utils": "^3.0.23", - "ethereum-cryptography": "0.1.3" - }, - "dependencies": { - "ethereum-cryptography": { - "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, - "requires": { - "@types/pbkdf2": "^3.0.0", - "@types/secp256k1": "^4.0.1", - "blakejs": "^1.1.0", - "browserify-aes": "^1.2.0", - "bs58check": "^2.1.2", - "create-hash": "^1.2.0", - "create-hmac": "^1.1.7", - "hash.js": "^1.1.7", - "keccak": "^3.0.0", - "pbkdf2": "^3.0.17", - "randombytes": "^2.1.0", - "safe-buffer": "^5.1.2", - "scrypt-js": "^3.0.0", - "secp256k1": "^4.0.1", - "setimmediate": "^1.0.5" - } - } - } - }, - "@nomicfoundation/ethereumjs-evm": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/@nomicfoundation/ethereumjs-evm/-/ethereumjs-evm-2.0.1.tgz", - "integrity": "sha512-oL8vJcnk0Bx/onl+TgQOQ1t/534GKFaEG17fZmwtPFeH8S5soiBYPCLUrvANOl4sCp9elYxIMzIiTtMtNNN8EQ==", - "dev": true, - "requires": { - "@ethersproject/providers": "^5.7.1", - "@nomicfoundation/ethereumjs-common": "4.0.1", - "@nomicfoundation/ethereumjs-tx": "5.0.1", - "@nomicfoundation/ethereumjs-util": "9.0.1", - "debug": "^4.3.3", - "ethereum-cryptography": "0.1.3", - "mcl-wasm": "^0.7.1", - "rustbn.js": "~0.2.0" - }, - "dependencies": { - "ethereum-cryptography": { - "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, - "requires": { - "@types/pbkdf2": "^3.0.0", - "@types/secp256k1": "^4.0.1", - "blakejs": "^1.1.0", - "browserify-aes": "^1.2.0", - "bs58check": "^2.1.2", - "create-hash": "^1.2.0", - "create-hmac": "^1.1.7", - "hash.js": "^1.1.7", - "keccak": "^3.0.0", - "pbkdf2": "^3.0.17", - "randombytes": "^2.1.0", - "safe-buffer": "^5.1.2", - "scrypt-js": "^3.0.0", - "secp256k1": "^4.0.1", - "setimmediate": "^1.0.5" - } - } - } - }, - "@nomicfoundation/ethereumjs-rlp": { - "version": "5.0.1", - "resolved": "https://registry.npmjs.org/@nomicfoundation/ethereumjs-rlp/-/ethereumjs-rlp-5.0.1.tgz", - "integrity": "sha512-xtxrMGa8kP4zF5ApBQBtjlSbN5E2HI8m8FYgVSYAnO6ssUoY5pVPGy2H8+xdf/bmMa22Ce8nWMH3aEW8CcqMeQ==", - "dev": true - }, - "@nomicfoundation/ethereumjs-statemanager": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/@nomicfoundation/ethereumjs-statemanager/-/ethereumjs-statemanager-2.0.1.tgz", - "integrity": "sha512-B5ApMOnlruVOR7gisBaYwFX+L/AP7i/2oAahatssjPIBVDF6wTX1K7Qpa39E/nzsH8iYuL3krkYeUFIdO3EMUQ==", - "dev": true, - "requires": { - "@nomicfoundation/ethereumjs-common": "4.0.1", - "@nomicfoundation/ethereumjs-rlp": "5.0.1", - "debug": "^4.3.3", - "ethereum-cryptography": "0.1.3", - "ethers": "^5.7.1", - "js-sdsl": "^4.1.4" - }, - "dependencies": { - "ethereum-cryptography": { - "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, - "requires": { - "@types/pbkdf2": "^3.0.0", - "@types/secp256k1": "^4.0.1", - "blakejs": "^1.1.0", - "browserify-aes": "^1.2.0", - "bs58check": "^2.1.2", - "create-hash": "^1.2.0", - "create-hmac": "^1.1.7", - "hash.js": "^1.1.7", - "keccak": "^3.0.0", - "pbkdf2": "^3.0.17", - "randombytes": "^2.1.0", - "safe-buffer": "^5.1.2", - "scrypt-js": "^3.0.0", - "secp256k1": "^4.0.1", - "setimmediate": "^1.0.5" - } - } - } - }, - "@nomicfoundation/ethereumjs-trie": { - "version": "6.0.1", - "resolved": "https://registry.npmjs.org/@nomicfoundation/ethereumjs-trie/-/ethereumjs-trie-6.0.1.tgz", - "integrity": "sha512-A64It/IMpDVODzCgxDgAAla8jNjNtsoQZIzZUfIV5AY6Coi4nvn7+VReBn5itlxMiL2yaTlQr9TRWp3CSI6VoA==", - "dev": true, - "requires": { - "@nomicfoundation/ethereumjs-rlp": "5.0.1", - "@nomicfoundation/ethereumjs-util": "9.0.1", - "@types/readable-stream": "^2.3.13", - "ethereum-cryptography": "0.1.3", - "readable-stream": "^3.6.0" - }, - "dependencies": { - "ethereum-cryptography": { - "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, - "requires": { - "@types/pbkdf2": "^3.0.0", - "@types/secp256k1": "^4.0.1", - "blakejs": "^1.1.0", - "browserify-aes": "^1.2.0", - "bs58check": "^2.1.2", - "create-hash": "^1.2.0", - "create-hmac": "^1.1.7", - "hash.js": "^1.1.7", - "keccak": "^3.0.0", - "pbkdf2": "^3.0.17", - "randombytes": "^2.1.0", - "safe-buffer": "^5.1.2", - "scrypt-js": "^3.0.0", - "secp256k1": "^4.0.1", - "setimmediate": "^1.0.5" - } - } - } - }, - "@nomicfoundation/ethereumjs-tx": { - "version": "5.0.1", - "resolved": "https://registry.npmjs.org/@nomicfoundation/ethereumjs-tx/-/ethereumjs-tx-5.0.1.tgz", - "integrity": "sha512-0HwxUF2u2hrsIM1fsasjXvlbDOq1ZHFV2dd1yGq8CA+MEYhaxZr8OTScpVkkxqMwBcc5y83FyPl0J9MZn3kY0w==", - "dev": true, - "requires": { - "@chainsafe/ssz": "^0.9.2", - "@ethersproject/providers": "^5.7.2", - "@nomicfoundation/ethereumjs-common": "4.0.1", - "@nomicfoundation/ethereumjs-rlp": "5.0.1", - "@nomicfoundation/ethereumjs-util": "9.0.1", - "ethereum-cryptography": "0.1.3" - }, - "dependencies": { - "ethereum-cryptography": { - "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, - "requires": { - "@types/pbkdf2": "^3.0.0", - "@types/secp256k1": "^4.0.1", - "blakejs": "^1.1.0", - "browserify-aes": "^1.2.0", - "bs58check": "^2.1.2", - "create-hash": "^1.2.0", - "create-hmac": "^1.1.7", - "hash.js": "^1.1.7", - "keccak": "^3.0.0", - "pbkdf2": "^3.0.17", - "randombytes": "^2.1.0", - "safe-buffer": "^5.1.2", - "scrypt-js": "^3.0.0", - "secp256k1": "^4.0.1", - "setimmediate": "^1.0.5" - } - } - } - }, - "@nomicfoundation/ethereumjs-util": { - "version": "9.0.1", - "resolved": "https://registry.npmjs.org/@nomicfoundation/ethereumjs-util/-/ethereumjs-util-9.0.1.tgz", - "integrity": "sha512-TwbhOWQ8QoSCFhV/DDfSmyfFIHjPjFBj957219+V3jTZYZ2rf9PmDtNOeZWAE3p3vlp8xb02XGpd0v6nTUPbsA==", - "dev": true, - "requires": { - "@chainsafe/ssz": "^0.10.0", - "@nomicfoundation/ethereumjs-rlp": "5.0.1", - "ethereum-cryptography": "0.1.3" - }, - "dependencies": { - "@chainsafe/persistent-merkle-tree": { - "version": "0.5.0", - "resolved": "https://registry.npmjs.org/@chainsafe/persistent-merkle-tree/-/persistent-merkle-tree-0.5.0.tgz", - "integrity": "sha512-l0V1b5clxA3iwQLXP40zYjyZYospQLZXzBVIhhr9kDg/1qHZfzzHw0jj4VPBijfYCArZDlPkRi1wZaV2POKeuw==", - "dev": true, - "requires": { - "@chainsafe/as-sha256": "^0.3.1" - } - }, - "@chainsafe/ssz": { - "version": "0.10.2", - "resolved": "https://registry.npmjs.org/@chainsafe/ssz/-/ssz-0.10.2.tgz", - "integrity": "sha512-/NL3Lh8K+0q7A3LsiFq09YXS9fPE+ead2rr7vM2QK8PLzrNsw3uqrif9bpRX5UxgeRjM+vYi+boCM3+GM4ovXg==", - "dev": true, - "requires": { - "@chainsafe/as-sha256": "^0.3.1", - "@chainsafe/persistent-merkle-tree": "^0.5.0" - } - }, - "ethereum-cryptography": { - "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, - "requires": { - "@types/pbkdf2": "^3.0.0", - "@types/secp256k1": "^4.0.1", - "blakejs": "^1.1.0", - "browserify-aes": "^1.2.0", - "bs58check": "^2.1.2", - "create-hash": "^1.2.0", - "create-hmac": "^1.1.7", - "hash.js": "^1.1.7", - "keccak": "^3.0.0", - "pbkdf2": "^3.0.17", - "randombytes": "^2.1.0", - "safe-buffer": "^5.1.2", - "scrypt-js": "^3.0.0", - "secp256k1": "^4.0.1", - "setimmediate": "^1.0.5" - } - } - } - }, - "@nomicfoundation/ethereumjs-vm": { - "version": "7.0.1", - "resolved": "https://registry.npmjs.org/@nomicfoundation/ethereumjs-vm/-/ethereumjs-vm-7.0.1.tgz", - "integrity": "sha512-rArhyn0jPsS/D+ApFsz3yVJMQ29+pVzNZ0VJgkzAZ+7FqXSRtThl1C1prhmlVr3YNUlfpZ69Ak+RUT4g7VoOuQ==", - "dev": true, - "requires": { - "@nomicfoundation/ethereumjs-block": "5.0.1", - "@nomicfoundation/ethereumjs-blockchain": "7.0.1", - "@nomicfoundation/ethereumjs-common": "4.0.1", - "@nomicfoundation/ethereumjs-evm": "2.0.1", - "@nomicfoundation/ethereumjs-rlp": "5.0.1", - "@nomicfoundation/ethereumjs-statemanager": "2.0.1", - "@nomicfoundation/ethereumjs-trie": "6.0.1", - "@nomicfoundation/ethereumjs-tx": "5.0.1", - "@nomicfoundation/ethereumjs-util": "9.0.1", - "debug": "^4.3.3", - "ethereum-cryptography": "0.1.3", - "mcl-wasm": "^0.7.1", - "rustbn.js": "~0.2.0" - }, - "dependencies": { - "ethereum-cryptography": { - "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, - "requires": { - "@types/pbkdf2": "^3.0.0", - "@types/secp256k1": "^4.0.1", - "blakejs": "^1.1.0", - "browserify-aes": "^1.2.0", - "bs58check": "^2.1.2", - "create-hash": "^1.2.0", - "create-hmac": "^1.1.7", - "hash.js": "^1.1.7", - "keccak": "^3.0.0", - "pbkdf2": "^3.0.17", - "randombytes": "^2.1.0", - "safe-buffer": "^5.1.2", - "scrypt-js": "^3.0.0", - "secp256k1": "^4.0.1", - "setimmediate": "^1.0.5" - } - } - } - }, - "@nomicfoundation/solidity-analyzer": { - "version": "0.1.1", - "resolved": "https://registry.npmjs.org/@nomicfoundation/solidity-analyzer/-/solidity-analyzer-0.1.1.tgz", - "integrity": "sha512-1LMtXj1puAxyFusBgUIy5pZk3073cNXYnXUpuNKFghHbIit/xZgbk0AokpUADbNm3gyD6bFWl3LRFh3dhVdREg==", - "dev": true, - "requires": { - "@nomicfoundation/solidity-analyzer-darwin-arm64": "0.1.1", - "@nomicfoundation/solidity-analyzer-darwin-x64": "0.1.1", - "@nomicfoundation/solidity-analyzer-freebsd-x64": "0.1.1", - "@nomicfoundation/solidity-analyzer-linux-arm64-gnu": "0.1.1", - "@nomicfoundation/solidity-analyzer-linux-arm64-musl": "0.1.1", - "@nomicfoundation/solidity-analyzer-linux-x64-gnu": "0.1.1", - "@nomicfoundation/solidity-analyzer-linux-x64-musl": "0.1.1", - "@nomicfoundation/solidity-analyzer-win32-arm64-msvc": "0.1.1", - "@nomicfoundation/solidity-analyzer-win32-ia32-msvc": "0.1.1", - "@nomicfoundation/solidity-analyzer-win32-x64-msvc": "0.1.1" - } - }, - "@nomicfoundation/solidity-analyzer-darwin-arm64": { - "version": "0.1.1", - "resolved": "https://registry.npmjs.org/@nomicfoundation/solidity-analyzer-darwin-arm64/-/solidity-analyzer-darwin-arm64-0.1.1.tgz", - "integrity": "sha512-KcTodaQw8ivDZyF+D76FokN/HdpgGpfjc/gFCImdLUyqB6eSWVaZPazMbeAjmfhx3R0zm/NYVzxwAokFKgrc0w==", - "dev": true, - "optional": true - }, - "@nomicfoundation/solidity-analyzer-darwin-x64": { - "version": "0.1.1", - "resolved": "https://registry.npmjs.org/@nomicfoundation/solidity-analyzer-darwin-x64/-/solidity-analyzer-darwin-x64-0.1.1.tgz", - "integrity": "sha512-XhQG4BaJE6cIbjAVtzGOGbK3sn1BO9W29uhk9J8y8fZF1DYz0Doj8QDMfpMu+A6TjPDs61lbsmeYodIDnfveSA==", - "dev": true, - "optional": true - }, - "@nomicfoundation/solidity-analyzer-freebsd-x64": { - "version": "0.1.1", - "resolved": "https://registry.npmjs.org/@nomicfoundation/solidity-analyzer-freebsd-x64/-/solidity-analyzer-freebsd-x64-0.1.1.tgz", - "integrity": "sha512-GHF1VKRdHW3G8CndkwdaeLkVBi5A9u2jwtlS7SLhBc8b5U/GcoL39Q+1CSO3hYqePNP+eV5YI7Zgm0ea6kMHoA==", - "dev": true, - "optional": true - }, - "@nomicfoundation/solidity-analyzer-linux-arm64-gnu": { - "version": "0.1.1", - "resolved": "https://registry.npmjs.org/@nomicfoundation/solidity-analyzer-linux-arm64-gnu/-/solidity-analyzer-linux-arm64-gnu-0.1.1.tgz", - "integrity": "sha512-g4Cv2fO37ZsUENQ2vwPnZc2zRenHyAxHcyBjKcjaSmmkKrFr64yvzeNO8S3GBFCo90rfochLs99wFVGT/0owpg==", - "dev": true, - "optional": true - }, - "@nomicfoundation/solidity-analyzer-linux-arm64-musl": { - "version": "0.1.1", - "resolved": "https://registry.npmjs.org/@nomicfoundation/solidity-analyzer-linux-arm64-musl/-/solidity-analyzer-linux-arm64-musl-0.1.1.tgz", - "integrity": "sha512-WJ3CE5Oek25OGE3WwzK7oaopY8xMw9Lhb0mlYuJl/maZVo+WtP36XoQTb7bW/i8aAdHW5Z+BqrHMux23pvxG3w==", - "dev": true, - "optional": true - }, - "@nomicfoundation/solidity-analyzer-linux-x64-gnu": { - "version": "0.1.1", - "resolved": "https://registry.npmjs.org/@nomicfoundation/solidity-analyzer-linux-x64-gnu/-/solidity-analyzer-linux-x64-gnu-0.1.1.tgz", - "integrity": "sha512-5WN7leSr5fkUBBjE4f3wKENUy9HQStu7HmWqbtknfXkkil+eNWiBV275IOlpXku7v3uLsXTOKpnnGHJYI2qsdA==", - "dev": true, - "optional": true - }, - "@nomicfoundation/solidity-analyzer-linux-x64-musl": { - "version": "0.1.1", - "resolved": "https://registry.npmjs.org/@nomicfoundation/solidity-analyzer-linux-x64-musl/-/solidity-analyzer-linux-x64-musl-0.1.1.tgz", - "integrity": "sha512-KdYMkJOq0SYPQMmErv/63CwGwMm5XHenEna9X9aB8mQmhDBrYrlAOSsIPgFCUSL0hjxE3xHP65/EPXR/InD2+w==", - "dev": true, - "optional": true - }, - "@nomicfoundation/solidity-analyzer-win32-arm64-msvc": { - "version": "0.1.1", - "resolved": "https://registry.npmjs.org/@nomicfoundation/solidity-analyzer-win32-arm64-msvc/-/solidity-analyzer-win32-arm64-msvc-0.1.1.tgz", - "integrity": "sha512-VFZASBfl4qiBYwW5xeY20exWhmv6ww9sWu/krWSesv3q5hA0o1JuzmPHR4LPN6SUZj5vcqci0O6JOL8BPw+APg==", - "dev": true, - "optional": true - }, - "@nomicfoundation/solidity-analyzer-win32-ia32-msvc": { - "version": "0.1.1", - "resolved": "https://registry.npmjs.org/@nomicfoundation/solidity-analyzer-win32-ia32-msvc/-/solidity-analyzer-win32-ia32-msvc-0.1.1.tgz", - "integrity": "sha512-JnFkYuyCSA70j6Si6cS1A9Gh1aHTEb8kOTBApp/c7NRTFGNMH8eaInKlyuuiIbvYFhlXW4LicqyYuWNNq9hkpQ==", - "dev": true, - "optional": true - }, - "@nomicfoundation/solidity-analyzer-win32-x64-msvc": { - "version": "0.1.1", - "resolved": "https://registry.npmjs.org/@nomicfoundation/solidity-analyzer-win32-x64-msvc/-/solidity-analyzer-win32-x64-msvc-0.1.1.tgz", - "integrity": "sha512-HrVJr6+WjIXGnw3Q9u6KQcbZCtk0caVWhCdFADySvRyUxJ8PnzlaP+MhwNE8oyT8OZ6ejHBRrrgjSqDCFXGirw==", - "dev": true, - "optional": true - }, - "@nomiclabs/hardhat-ethers": { - "version": "2.2.3", - "resolved": "https://registry.npmjs.org/@nomiclabs/hardhat-ethers/-/hardhat-ethers-2.2.3.tgz", - "integrity": "sha512-YhzPdzb612X591FOe68q+qXVXGG2ANZRvDo0RRUtimev85rCrAlv/TLMEZw5c+kq9AbzocLTVX/h2jVIFPL9Xg==", - "dev": true, - "requires": {} - }, - "@nomiclabs/hardhat-etherscan": { - "version": "3.1.8", - "resolved": "https://registry.npmjs.org/@nomiclabs/hardhat-etherscan/-/hardhat-etherscan-3.1.8.tgz", - "integrity": "sha512-v5F6IzQhrsjHh6kQz4uNrym49brK9K5bYCq2zQZ729RYRaifI9hHbtmK+KkIVevfhut7huQFEQ77JLRMAzWYjQ==", - "dev": true, - "requires": { - "@ethersproject/abi": "^5.1.2", - "@ethersproject/address": "^5.0.2", - "cbor": "^8.1.0", - "chalk": "^2.4.2", - "debug": "^4.1.1", - "fs-extra": "^7.0.1", - "lodash": "^4.17.11", - "semver": "^6.3.0", - "table": "^6.8.0", - "undici": "^5.14.0" - }, - "dependencies": { - "ansi-styles": { - "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, - "requires": { - "color-convert": "^1.9.0" - } - }, - "chalk": { - "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, - "requires": { - "ansi-styles": "^3.2.1", - "escape-string-regexp": "^1.0.5", - "supports-color": "^5.3.0" - } - }, - "color-convert": { - "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, - "requires": { - "color-name": "1.1.3" - } - }, - "color-name": { - "version": "1.1.3", - "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.3.tgz", - "integrity": "sha512-72fSenhMw2HZMTVHeCA9KCmpEIbzWiQsjN+BHcBbS9vr1mtt+vJjPdksIBNUmKAW8TFUDPJK5SUU3QhE9NEXDw==", - "dev": true - }, - "escape-string-regexp": { - "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 - }, - "has-flag": { - "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 - }, - "supports-color": { - "version": "5.5.0", - "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-5.5.0.tgz", - "integrity": "sha512-QjVjwdXIt408MIiAqCX4oUKsgU2EqAGzs2Ppkm4aQYbjm+ZEWEcW4SfFNTr4uMNZma0ey4f5lgLrkB0aX0QMow==", - "dev": true, - "requires": { - "has-flag": "^3.0.0" - } - } - } - }, - "@nomiclabs/hardhat-waffle": { - "version": "2.0.6", - "resolved": "https://registry.npmjs.org/@nomiclabs/hardhat-waffle/-/hardhat-waffle-2.0.6.tgz", - "integrity": "sha512-+Wz0hwmJGSI17B+BhU/qFRZ1l6/xMW82QGXE/Gi+WTmwgJrQefuBs1lIf7hzQ1hLk6hpkvb/zwcNkpVKRYTQYg==", - "dev": true, - "requires": {} - }, - "@openzeppelin/contracts": { - "version": "4.6.0", - "resolved": "https://registry.npmjs.org/@openzeppelin/contracts/-/contracts-4.6.0.tgz", - "integrity": "sha512-8vi4d50NNya/bQqCmaVzvHNmwHvS0OBKb7HNtuNwEE3scXWrP31fKQoGxNMT+KbzmrNZzatE3QK5p2gFONI/hg==" - }, - "@resolver-engine/core": { - "version": "0.3.3", - "resolved": "https://registry.npmjs.org/@resolver-engine/core/-/core-0.3.3.tgz", - "integrity": "sha512-eB8nEbKDJJBi5p5SrvrvILn4a0h42bKtbCTri3ZxCGt6UvoQyp7HnGOfki944bUjBSHKK3RvgfViHn+kqdXtnQ==", - "dev": true, - "requires": { - "debug": "^3.1.0", - "is-url": "^1.2.4", - "request": "^2.85.0" - }, - "dependencies": { - "debug": { - "version": "3.2.7", - "resolved": "https://registry.npmjs.org/debug/-/debug-3.2.7.tgz", - "integrity": "sha512-CFjzYYAi4ThfiQvizrFQevTTXHtnCqWfe7x1AhgEscTz6ZbLbfoLRLPugTQyBth6f8ZERVUSyWHFD/7Wu4t1XQ==", - "dev": true, - "requires": { - "ms": "^2.1.1" - } - } - } - }, - "@resolver-engine/fs": { - "version": "0.3.3", - "resolved": "https://registry.npmjs.org/@resolver-engine/fs/-/fs-0.3.3.tgz", - "integrity": "sha512-wQ9RhPUcny02Wm0IuJwYMyAG8fXVeKdmhm8xizNByD4ryZlx6PP6kRen+t/haF43cMfmaV7T3Cx6ChOdHEhFUQ==", - "dev": true, - "requires": { - "@resolver-engine/core": "^0.3.3", - "debug": "^3.1.0" - }, - "dependencies": { - "debug": { - "version": "3.2.7", - "resolved": "https://registry.npmjs.org/debug/-/debug-3.2.7.tgz", - "integrity": "sha512-CFjzYYAi4ThfiQvizrFQevTTXHtnCqWfe7x1AhgEscTz6ZbLbfoLRLPugTQyBth6f8ZERVUSyWHFD/7Wu4t1XQ==", - "dev": true, - "requires": { - "ms": "^2.1.1" - } - } - } - }, - "@resolver-engine/imports": { - "version": "0.3.3", - "resolved": "https://registry.npmjs.org/@resolver-engine/imports/-/imports-0.3.3.tgz", - "integrity": "sha512-anHpS4wN4sRMwsAbMXhMfOD/y4a4Oo0Cw/5+rue7hSwGWsDOQaAU1ClK1OxjUC35/peazxEl8JaSRRS+Xb8t3Q==", - "dev": true, - "requires": { - "@resolver-engine/core": "^0.3.3", - "debug": "^3.1.0", - "hosted-git-info": "^2.6.0", - "path-browserify": "^1.0.0", - "url": "^0.11.0" - }, - "dependencies": { - "debug": { - "version": "3.2.7", - "resolved": "https://registry.npmjs.org/debug/-/debug-3.2.7.tgz", - "integrity": "sha512-CFjzYYAi4ThfiQvizrFQevTTXHtnCqWfe7x1AhgEscTz6ZbLbfoLRLPugTQyBth6f8ZERVUSyWHFD/7Wu4t1XQ==", - "dev": true, - "requires": { - "ms": "^2.1.1" - } - } - } - }, - "@resolver-engine/imports-fs": { - "version": "0.3.3", - "resolved": "https://registry.npmjs.org/@resolver-engine/imports-fs/-/imports-fs-0.3.3.tgz", - "integrity": "sha512-7Pjg/ZAZtxpeyCFlZR5zqYkz+Wdo84ugB5LApwriT8XFeQoLwGUj4tZFFvvCuxaNCcqZzCYbonJgmGObYBzyCA==", - "dev": true, - "requires": { - "@resolver-engine/fs": "^0.3.3", - "@resolver-engine/imports": "^0.3.3", - "debug": "^3.1.0" - }, - "dependencies": { - "debug": { - "version": "3.2.7", - "resolved": "https://registry.npmjs.org/debug/-/debug-3.2.7.tgz", - "integrity": "sha512-CFjzYYAi4ThfiQvizrFQevTTXHtnCqWfe7x1AhgEscTz6ZbLbfoLRLPugTQyBth6f8ZERVUSyWHFD/7Wu4t1XQ==", - "dev": true, - "requires": { - "ms": "^2.1.1" - } - } - } - }, - "@scure/base": { - "version": "1.1.1", - "resolved": "https://registry.npmjs.org/@scure/base/-/base-1.1.1.tgz", - "integrity": "sha512-ZxOhsSyxYwLJj3pLZCefNitxsj093tb2vq90mp2txoYeBqbcjDjqFhyM8eUjq/uFm6zJ+mUuqxlS2FkuSY1MTA==", - "dev": true - }, - "@scure/bip32": { - "version": "1.1.5", - "resolved": "https://registry.npmjs.org/@scure/bip32/-/bip32-1.1.5.tgz", - "integrity": "sha512-XyNh1rB0SkEqd3tXcXMi+Xe1fvg+kUIcoRIEujP1Jgv7DqW2r9lg3Ah0NkFaCs9sTkQAQA8kw7xiRXzENi9Rtw==", - "dev": true, - "requires": { - "@noble/hashes": "~1.2.0", - "@noble/secp256k1": "~1.7.0", - "@scure/base": "~1.1.0" - } - }, - "@scure/bip39": { - "version": "1.1.1", - "resolved": "https://registry.npmjs.org/@scure/bip39/-/bip39-1.1.1.tgz", - "integrity": "sha512-t+wDck2rVkh65Hmv280fYdVdY25J9YeEUIgn2LG1WM6gxFkGzcksoDiUkWVpVp3Oex9xGC68JU2dSbUfwZ2jPg==", - "dev": true, - "requires": { - "@noble/hashes": "~1.2.0", - "@scure/base": "~1.1.0" - } - }, - "@sentry/core": { - "version": "5.30.0", - "resolved": "https://registry.npmjs.org/@sentry/core/-/core-5.30.0.tgz", - "integrity": "sha512-TmfrII8w1PQZSZgPpUESqjB+jC6MvZJZdLtE/0hZ+SrnKhW3x5WlYLvTXZpcWePYBku7rl2wn1RZu6uT0qCTeg==", - "dev": true, - "requires": { - "@sentry/hub": "5.30.0", - "@sentry/minimal": "5.30.0", - "@sentry/types": "5.30.0", - "@sentry/utils": "5.30.0", - "tslib": "^1.9.3" - } - }, - "@sentry/hub": { - "version": "5.30.0", - "resolved": "https://registry.npmjs.org/@sentry/hub/-/hub-5.30.0.tgz", - "integrity": "sha512-2tYrGnzb1gKz2EkMDQcfLrDTvmGcQPuWxLnJKXJvYTQDGLlEvi2tWz1VIHjunmOvJrB5aIQLhm+dcMRwFZDCqQ==", - "dev": true, - "requires": { - "@sentry/types": "5.30.0", - "@sentry/utils": "5.30.0", - "tslib": "^1.9.3" - } - }, - "@sentry/minimal": { - "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, - "requires": { - "@sentry/hub": "5.30.0", - "@sentry/types": "5.30.0", - "tslib": "^1.9.3" - } - }, - "@sentry/node": { - "version": "5.30.0", - "resolved": "https://registry.npmjs.org/@sentry/node/-/node-5.30.0.tgz", - "integrity": "sha512-Br5oyVBF0fZo6ZS9bxbJZG4ApAjRqAnqFFurMVJJdunNb80brh7a5Qva2kjhm+U6r9NJAB5OmDyPkA1Qnt+QVg==", - "dev": true, - "requires": { - "@sentry/core": "5.30.0", - "@sentry/hub": "5.30.0", - "@sentry/tracing": "5.30.0", - "@sentry/types": "5.30.0", - "@sentry/utils": "5.30.0", - "cookie": "^0.4.1", - "https-proxy-agent": "^5.0.0", - "lru_map": "^0.3.3", - "tslib": "^1.9.3" - } - }, - "@sentry/tracing": { - "version": "5.30.0", - "resolved": "https://registry.npmjs.org/@sentry/tracing/-/tracing-5.30.0.tgz", - "integrity": "sha512-dUFowCr0AIMwiLD7Fs314Mdzcug+gBVo/+NCMyDw8tFxJkwWAKl7Qa2OZxLQ0ZHjakcj1hNKfCQJ9rhyfOl4Aw==", - "dev": true, - "requires": { - "@sentry/hub": "5.30.0", - "@sentry/minimal": "5.30.0", - "@sentry/types": "5.30.0", - "@sentry/utils": "5.30.0", - "tslib": "^1.9.3" - } - }, - "@sentry/types": { - "version": "5.30.0", - "resolved": "https://registry.npmjs.org/@sentry/types/-/types-5.30.0.tgz", - "integrity": "sha512-R8xOqlSTZ+htqrfteCWU5Nk0CDN5ApUTvrlvBuiH1DyP6czDZ4ktbZB0hAgBlVcK0U+qpD3ag3Tqqpa5Q67rPw==", - "dev": true - }, - "@sentry/utils": { - "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, - "requires": { - "@sentry/types": "5.30.0", - "tslib": "^1.9.3" - } - }, - "@sindresorhus/is": { - "version": "0.14.0", - "resolved": "https://registry.npmjs.org/@sindresorhus/is/-/is-0.14.0.tgz", - "integrity": "sha512-9NET910DNaIPngYnLLPeg+Ogzqsi9uM4mSboU5y6p8S5DzMTVEsJZrawi+BoDNUVBa2DhJqQYUFvMDfgU062LQ==", - "dev": true - }, - "@solidity-parser/parser": { - "version": "0.14.5", - "resolved": "https://registry.npmjs.org/@solidity-parser/parser/-/parser-0.14.5.tgz", - "integrity": "sha512-6dKnHZn7fg/iQATVEzqyUOyEidbn05q7YA2mQ9hC0MMXhhV3/JrsxmFSYZAcr7j1yUP700LLhTruvJ3MiQmjJg==", - "dev": true, - "requires": { - "antlr4ts": "^0.5.0-alpha.4" - } - }, - "@szmarczak/http-timer": { - "version": "1.1.2", - "resolved": "https://registry.npmjs.org/@szmarczak/http-timer/-/http-timer-1.1.2.tgz", - "integrity": "sha512-XIB2XbzHTN6ieIjfIMV9hlVcfPU26s2vafYWQcZHWXHOxiaRZYEDKEwdl129Zyg50+foYV2jCgtrqSA6qNuNSA==", - "dev": true, - "requires": { - "defer-to-connect": "^1.0.1" - } - }, - "@truffle/error": { - "version": "0.1.1", - "resolved": "https://registry.npmjs.org/@truffle/error/-/error-0.1.1.tgz", - "integrity": "sha512-sE7c9IHIGdbK4YayH4BC8i8qMjoAOeg6nUXUDZZp8wlU21/EMpaG+CLx+KqcIPyR+GSWIW3Dm0PXkr2nlggFDA==", - "dev": true - }, - "@truffle/interface-adapter": { - "version": "0.5.35", - "resolved": "https://registry.npmjs.org/@truffle/interface-adapter/-/interface-adapter-0.5.35.tgz", - "integrity": "sha512-B5gtJnvsum5j2do393n0UfCT8MklrlAZxuqvEFBeMM9UKnreYct0/D368FVMlZwWo1N50HgGeZ0hlpSJqR/nvg==", - "dev": true, - "requires": { - "bn.js": "^5.1.3", - "ethers": "^4.0.32", - "web3": "1.10.0" - }, - "dependencies": { - "@ethereumjs/common": { - "version": "2.5.0", - "resolved": "https://registry.npmjs.org/@ethereumjs/common/-/common-2.5.0.tgz", - "integrity": "sha512-DEHjW6e38o+JmB/NO3GZBpW4lpaiBpkFgXF6jLcJ6gETBYpEyaA5nTimsWBUJR3Vmtm/didUEbNjajskugZORg==", - "dev": true, - "requires": { - "crc-32": "^1.2.0", - "ethereumjs-util": "^7.1.1" - } - }, - "@ethereumjs/tx": { - "version": "3.3.2", - "resolved": "https://registry.npmjs.org/@ethereumjs/tx/-/tx-3.3.2.tgz", - "integrity": "sha512-6AaJhwg4ucmwTvw/1qLaZUX5miWrwZ4nLOUsKyb/HtzS3BMw/CasKhdi1ims9mBKeK9sOJCH4qGKOBGyJCeeog==", - "dev": true, - "requires": { - "@ethereumjs/common": "^2.5.0", - "ethereumjs-util": "^7.1.2" - } - }, - "@sindresorhus/is": { - "version": "4.6.0", - "resolved": "https://registry.npmjs.org/@sindresorhus/is/-/is-4.6.0.tgz", - "integrity": "sha512-t09vSN3MdfsyCHoFcTRCH/iUtG7OJ0CsjzB8cjAmKc/va/kIgeDI/TxsigdncE/4be734m0cvIYwNaV4i2XqAw==", - "dev": true - }, - "@szmarczak/http-timer": { - "version": "5.0.1", - "resolved": "https://registry.npmjs.org/@szmarczak/http-timer/-/http-timer-5.0.1.tgz", - "integrity": "sha512-+PmQX0PiAYPMeVYe237LJAYvOMYW1j2rH5YROyS3b4CTVJum34HfRvKvAzozHAQG0TnHNdUfY9nCeUyRAs//cw==", - "dev": true, - "requires": { - "defer-to-connect": "^2.0.1" - } - }, - "cacheable-request": { - "version": "7.0.4", - "resolved": "https://registry.npmjs.org/cacheable-request/-/cacheable-request-7.0.4.tgz", - "integrity": "sha512-v+p6ongsrp0yTGbJXjgxPow2+DL93DASP4kXCDKb8/bwRtt9OEF3whggkkDkGNzgcWy2XaF4a8nZglC7uElscg==", - "dev": true, - "requires": { - "clone-response": "^1.0.2", - "get-stream": "^5.1.0", - "http-cache-semantics": "^4.0.0", - "keyv": "^4.0.0", - "lowercase-keys": "^2.0.0", - "normalize-url": "^6.0.1", - "responselike": "^2.0.0" - }, - "dependencies": { - "get-stream": { - "version": "5.2.0", - "resolved": "https://registry.npmjs.org/get-stream/-/get-stream-5.2.0.tgz", - "integrity": "sha512-nBF+F1rAZVCu/p7rjzgA+Yb4lfYXrpl7a6VmJrU8wF9I1CKvP/QwPNZHnOlwbTkY6dvtFIzFMSyQXbLoTQPRpA==", - "dev": true, - "requires": { - "pump": "^3.0.0" - } - }, - "lowercase-keys": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/lowercase-keys/-/lowercase-keys-2.0.0.tgz", - "integrity": "sha512-tqNXrS78oMOE73NMxK4EMLQsQowWf8jKooH9g7xPavRT706R6bkQJ6DY2Te7QukaZsulxa30wQ7bk0pm4XiHmA==", - "dev": true - } - } - }, - "decompress-response": { - "version": "6.0.0", - "resolved": "https://registry.npmjs.org/decompress-response/-/decompress-response-6.0.0.tgz", - "integrity": "sha512-aW35yZM6Bb/4oJlZncMH2LCoZtJXTRxES17vE3hoRiowU2kWHaJKFkSBDnDR+cm9J+9QhXmREyIfv0pji9ejCQ==", - "dev": true, - "requires": { - "mimic-response": "^3.1.0" - } - }, - "defer-to-connect": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/defer-to-connect/-/defer-to-connect-2.0.1.tgz", - "integrity": "sha512-4tvttepXG1VaYGrRibk5EwJd1t4udunSOVMdLSAL6mId1ix438oPwPZMALY41FCijukO1L0twNcGsdzS7dHgDg==", - "dev": true - }, - "eth-lib": { - "version": "0.2.8", - "resolved": "https://registry.npmjs.org/eth-lib/-/eth-lib-0.2.8.tgz", - "integrity": "sha512-ArJ7x1WcWOlSpzdoTBX8vkwlkSQ85CjjifSZtV4co64vWxSV8geWfPI9x4SVYu3DSxnX4yWFVTtGL+j9DUFLNw==", - "dev": true, - "requires": { - "bn.js": "^4.11.6", - "elliptic": "^6.4.0", - "xhr-request-promise": "^0.1.2" - }, - "dependencies": { - "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==", - "dev": true - } - } - }, - "ethereum-cryptography": { - "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, - "requires": { - "@types/pbkdf2": "^3.0.0", - "@types/secp256k1": "^4.0.1", - "blakejs": "^1.1.0", - "browserify-aes": "^1.2.0", - "bs58check": "^2.1.2", - "create-hash": "^1.2.0", - "create-hmac": "^1.1.7", - "hash.js": "^1.1.7", - "keccak": "^3.0.0", - "pbkdf2": "^3.0.17", - "randombytes": "^2.1.0", - "safe-buffer": "^5.1.2", - "scrypt-js": "^3.0.0", - "secp256k1": "^4.0.1", - "setimmediate": "^1.0.5" - }, - "dependencies": { - "hash.js": { - "version": "1.1.7", - "resolved": "https://registry.npmjs.org/hash.js/-/hash.js-1.1.7.tgz", - "integrity": "sha512-taOaskGt4z4SOANNseOviYDvjEJinIkRgmp7LbKP2YTTmVxWBl87s/uzK9r+44BclBSp2X7K1hqeNfz9JbBeXA==", - "dev": true, - "requires": { - "inherits": "^2.0.3", - "minimalistic-assert": "^1.0.1" - } - }, - "scrypt-js": { - "version": "3.0.1", - "resolved": "https://registry.npmjs.org/scrypt-js/-/scrypt-js-3.0.1.tgz", - "integrity": "sha512-cdwTTnqPu0Hyvf5in5asVdZocVDTNRmR7XEcJuIzMjJeSHybHl7vpB66AzwTaIg6CLSbtjcxc8fqcySfnTkccA==", - "dev": true - }, - "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==", - "dev": true - } - } - }, - "ethereumjs-util": { - "version": "7.1.5", - "resolved": "https://registry.npmjs.org/ethereumjs-util/-/ethereumjs-util-7.1.5.tgz", - "integrity": "sha512-SDl5kKrQAudFBUe5OJM9Ac6WmMyYmXX/6sTmLZ3ffG2eY6ZIGBes3pEDxNN6V72WyOw4CPD5RomKdsa8DAAwLg==", - "dev": true, - "requires": { - "@types/bn.js": "^5.1.0", - "bn.js": "^5.1.2", - "create-hash": "^1.1.2", - "ethereum-cryptography": "^0.1.3", - "rlp": "^2.2.4" - } - }, - "ethers": { - "version": "4.0.49", - "resolved": "https://registry.npmjs.org/ethers/-/ethers-4.0.49.tgz", - "integrity": "sha512-kPltTvWiyu+OktYy1IStSO16i2e7cS9D9OxZ81q2UUaiNPVrm/RTcbxamCXF9VUSKzJIdJV68EAIhTEVBalRWg==", - "dev": true, - "requires": { - "aes-js": "3.0.0", - "bn.js": "^4.11.9", - "elliptic": "6.5.4", - "hash.js": "1.1.3", - "js-sha3": "0.5.7", - "scrypt-js": "2.0.4", - "setimmediate": "1.0.4", - "uuid": "2.0.1", - "xmlhttprequest": "1.8.0" - }, - "dependencies": { - "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==", - "dev": true - } - } - }, - "get-stream": { - "version": "6.0.1", - "resolved": "https://registry.npmjs.org/get-stream/-/get-stream-6.0.1.tgz", - "integrity": "sha512-ts6Wi+2j3jQjqi70w5AlN8DFnkSwC+MqmxEzdEALB2qXZYV3X/b1CTfgPLGJNMeAWxdPfU8FO1ms3NUfaHCPYg==", - "dev": true - }, - "got": { - "version": "12.1.0", - "resolved": "https://registry.npmjs.org/got/-/got-12.1.0.tgz", - "integrity": "sha512-hBv2ty9QN2RdbJJMK3hesmSkFTjVIHyIDDbssCKnSmq62edGgImJWD10Eb1k77TiV1bxloxqcFAVK8+9pkhOig==", - "dev": true, - "requires": { - "@sindresorhus/is": "^4.6.0", - "@szmarczak/http-timer": "^5.0.1", - "@types/cacheable-request": "^6.0.2", - "@types/responselike": "^1.0.0", - "cacheable-lookup": "^6.0.4", - "cacheable-request": "^7.0.2", - "decompress-response": "^6.0.0", - "form-data-encoder": "1.7.1", - "get-stream": "^6.0.1", - "http2-wrapper": "^2.1.10", - "lowercase-keys": "^3.0.0", - "p-cancelable": "^3.0.0", - "responselike": "^2.0.0" - } - }, - "hash.js": { - "version": "1.1.3", - "resolved": "https://registry.npmjs.org/hash.js/-/hash.js-1.1.3.tgz", - "integrity": "sha512-/UETyP0W22QILqS+6HowevwhEFJ3MBJnwTf75Qob9Wz9t0DPuisL8kW8YZMK62dHAKE1c1p+gY1TtOLY+USEHA==", - "dev": true, - "requires": { - "inherits": "^2.0.3", - "minimalistic-assert": "^1.0.0" - } - }, - "js-sha3": { - "version": "0.5.7", - "resolved": "https://registry.npmjs.org/js-sha3/-/js-sha3-0.5.7.tgz", - "integrity": "sha512-GII20kjaPX0zJ8wzkTbNDYMY7msuZcTWk8S5UOh6806Jq/wz1J8/bnr8uGU0DAUmYDjj2Mr4X1cW8v/GLYnR+g==", - "dev": true - }, - "json-buffer": { - "version": "3.0.1", - "resolved": "https://registry.npmjs.org/json-buffer/-/json-buffer-3.0.1.tgz", - "integrity": "sha512-4bV5BfR2mqfQTJm+V5tPPdf+ZpuhiIvTuAB5g8kcrXOZpTT/QwwVRWBywX1ozr6lEuPdbHxwaJlm9G6mI2sfSQ==", - "dev": true - }, - "keyv": { - "version": "4.5.3", - "resolved": "https://registry.npmjs.org/keyv/-/keyv-4.5.3.tgz", - "integrity": "sha512-QCiSav9WaX1PgETJ+SpNnx2PRRapJ/oRSXM4VO5OGYGSjrxbKPVFVhB3l2OCbLCk329N8qyAtsJjSjvVBWzEug==", - "dev": true, - "requires": { - "json-buffer": "3.0.1" - } - }, - "lowercase-keys": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/lowercase-keys/-/lowercase-keys-3.0.0.tgz", - "integrity": "sha512-ozCC6gdQ+glXOQsveKD0YsDy8DSQFjDTz4zyzEHNV5+JP5D62LmfDZ6o1cycFx9ouG940M5dE8C8CTewdj2YWQ==", - "dev": true - }, - "mimic-response": { - "version": "3.1.0", - "resolved": "https://registry.npmjs.org/mimic-response/-/mimic-response-3.1.0.tgz", - "integrity": "sha512-z0yWI+4FDrrweS8Zmt4Ej5HdJmky15+L2e6Wgn3+iK5fWzb6T3fhNFq2+MeTRb064c6Wr4N/wv0DzQTjNzHNGQ==", - "dev": true - }, - "normalize-url": { - "version": "6.1.0", - "resolved": "https://registry.npmjs.org/normalize-url/-/normalize-url-6.1.0.tgz", - "integrity": "sha512-DlL+XwOy3NxAQ8xuC0okPgK46iuVNAK01YN7RueYBqqFeGsBjV9XmCAzAdgt+667bCl5kPh9EqKKDwnaPG1I7A==", - "dev": true - }, - "p-cancelable": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/p-cancelable/-/p-cancelable-3.0.0.tgz", - "integrity": "sha512-mlVgR3PGuzlo0MmTdk4cXqXWlwQDLnONTAg6sm62XkMJEiRxN3GL3SffkYvqwonbkJBcrI7Uvv5Zh9yjvn2iUw==", - "dev": true - }, - "responselike": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/responselike/-/responselike-2.0.1.tgz", - "integrity": "sha512-4gl03wn3hj1HP3yzgdI7d3lCkF95F21Pz4BPGvKHinyQzALR5CapwC8yIi0Rh58DEMQ/SguC03wFj2k0M/mHhw==", - "dev": true, - "requires": { - "lowercase-keys": "^2.0.0" - }, - "dependencies": { - "lowercase-keys": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/lowercase-keys/-/lowercase-keys-2.0.0.tgz", - "integrity": "sha512-tqNXrS78oMOE73NMxK4EMLQsQowWf8jKooH9g7xPavRT706R6bkQJ6DY2Te7QukaZsulxa30wQ7bk0pm4XiHmA==", - "dev": true - } - } - }, - "scrypt-js": { - "version": "2.0.4", - "resolved": "https://registry.npmjs.org/scrypt-js/-/scrypt-js-2.0.4.tgz", - "integrity": "sha512-4KsaGcPnuhtCZQCxFxN3GVYIhKFPTdLd8PLC552XwbMndtD0cjRFAhDuuydXQ0h08ZfPgzqe6EKHozpuH74iDw==", - "dev": true - }, - "setimmediate": { - "version": "1.0.4", - "resolved": "https://registry.npmjs.org/setimmediate/-/setimmediate-1.0.4.tgz", - "integrity": "sha512-/TjEmXQVEzdod/FFskf3o7oOAsGhHf2j1dZqRFbDzq4F3mvvxflIIi4Hd3bLQE9y/CpwqfSQam5JakI/mi3Pog==", - "dev": true - }, - "uuid": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/uuid/-/uuid-2.0.1.tgz", - "integrity": "sha512-nWg9+Oa3qD2CQzHIP4qKUqwNfzKn8P0LtFhotaCTFchsV7ZfDhAybeip/HZVeMIpZi9JgY1E3nUlwaCmZT1sEg==", - "dev": true - }, - "web3": { - "version": "1.10.0", - "resolved": "https://registry.npmjs.org/web3/-/web3-1.10.0.tgz", - "integrity": "sha512-YfKY9wSkGcM8seO+daR89oVTcbu18NsVfvOngzqMYGUU0pPSQmE57qQDvQzUeoIOHAnXEBNzrhjQJmm8ER0rng==", - "dev": true, - "requires": { - "web3-bzz": "1.10.0", - "web3-core": "1.10.0", - "web3-eth": "1.10.0", - "web3-eth-personal": "1.10.0", - "web3-net": "1.10.0", - "web3-shh": "1.10.0", - "web3-utils": "1.10.0" - } - }, - "web3-bzz": { - "version": "1.10.0", - "resolved": "https://registry.npmjs.org/web3-bzz/-/web3-bzz-1.10.0.tgz", - "integrity": "sha512-o9IR59io3pDUsXTsps5pO5hW1D5zBmg46iNc2t4j2DkaYHNdDLwk2IP9ukoM2wg47QILfPEJYzhTfkS/CcX0KA==", - "dev": true, - "requires": { - "@types/node": "^12.12.6", - "got": "12.1.0", - "swarm-js": "^0.1.40" - } - }, - "web3-core": { - "version": "1.10.0", - "resolved": "https://registry.npmjs.org/web3-core/-/web3-core-1.10.0.tgz", - "integrity": "sha512-fWySwqy2hn3TL89w5TM8wXF1Z2Q6frQTKHWmP0ppRQorEK8NcHJRfeMiv/mQlSKoTS1F6n/nv2uyZsixFycjYQ==", - "dev": true, - "requires": { - "@types/bn.js": "^5.1.1", - "@types/node": "^12.12.6", - "bignumber.js": "^9.0.0", - "web3-core-helpers": "1.10.0", - "web3-core-method": "1.10.0", - "web3-core-requestmanager": "1.10.0", - "web3-utils": "1.10.0" - } - }, - "web3-core-helpers": { - "version": "1.10.0", - "resolved": "https://registry.npmjs.org/web3-core-helpers/-/web3-core-helpers-1.10.0.tgz", - "integrity": "sha512-pIxAzFDS5vnbXvfvLSpaA1tfRykAe9adw43YCKsEYQwH0gCLL0kMLkaCX3q+Q8EVmAh+e1jWL/nl9U0de1+++g==", - "dev": true, - "requires": { - "web3-eth-iban": "1.10.0", - "web3-utils": "1.10.0" - } - }, - "web3-core-method": { - "version": "1.10.0", - "resolved": "https://registry.npmjs.org/web3-core-method/-/web3-core-method-1.10.0.tgz", - "integrity": "sha512-4R700jTLAMKDMhQ+nsVfIXvH6IGJlJzGisIfMKWAIswH31h5AZz7uDUW2YctI+HrYd+5uOAlS4OJeeT9bIpvkA==", - "dev": true, - "requires": { - "@ethersproject/transactions": "^5.6.2", - "web3-core-helpers": "1.10.0", - "web3-core-promievent": "1.10.0", - "web3-core-subscriptions": "1.10.0", - "web3-utils": "1.10.0" - } - }, - "web3-core-promievent": { - "version": "1.10.0", - "resolved": "https://registry.npmjs.org/web3-core-promievent/-/web3-core-promievent-1.10.0.tgz", - "integrity": "sha512-68N7k5LWL5R38xRaKFrTFT2pm2jBNFaM4GioS00YjAKXRQ3KjmhijOMG3TICz6Aa5+6GDWYelDNx21YAeZ4YTg==", - "dev": true, - "requires": { - "eventemitter3": "4.0.4" - } - }, - "web3-core-requestmanager": { - "version": "1.10.0", - "resolved": "https://registry.npmjs.org/web3-core-requestmanager/-/web3-core-requestmanager-1.10.0.tgz", - "integrity": "sha512-3z/JKE++Os62APml4dvBM+GAuId4h3L9ckUrj7ebEtS2AR0ixyQPbrBodgL91Sv7j7cQ3Y+hllaluqjguxvSaQ==", - "dev": true, - "requires": { - "util": "^0.12.5", - "web3-core-helpers": "1.10.0", - "web3-providers-http": "1.10.0", - "web3-providers-ipc": "1.10.0", - "web3-providers-ws": "1.10.0" - } - }, - "web3-core-subscriptions": { - "version": "1.10.0", - "resolved": "https://registry.npmjs.org/web3-core-subscriptions/-/web3-core-subscriptions-1.10.0.tgz", - "integrity": "sha512-HGm1PbDqsxejI075gxBc5OSkwymilRWZufIy9zEpnWKNmfbuv5FfHgW1/chtJP6aP3Uq2vHkvTDl3smQBb8l+g==", - "dev": true, - "requires": { - "eventemitter3": "4.0.4", - "web3-core-helpers": "1.10.0" - } - }, - "web3-eth": { - "version": "1.10.0", - "resolved": "https://registry.npmjs.org/web3-eth/-/web3-eth-1.10.0.tgz", - "integrity": "sha512-Z5vT6slNMLPKuwRyKGbqeGYC87OAy8bOblaqRTgg94CXcn/mmqU7iPIlG4506YdcdK3x6cfEDG7B6w+jRxypKA==", - "dev": true, - "requires": { - "web3-core": "1.10.0", - "web3-core-helpers": "1.10.0", - "web3-core-method": "1.10.0", - "web3-core-subscriptions": "1.10.0", - "web3-eth-abi": "1.10.0", - "web3-eth-accounts": "1.10.0", - "web3-eth-contract": "1.10.0", - "web3-eth-ens": "1.10.0", - "web3-eth-iban": "1.10.0", - "web3-eth-personal": "1.10.0", - "web3-net": "1.10.0", - "web3-utils": "1.10.0" - } - }, - "web3-eth-abi": { - "version": "1.10.0", - "resolved": "https://registry.npmjs.org/web3-eth-abi/-/web3-eth-abi-1.10.0.tgz", - "integrity": "sha512-cwS+qRBWpJ43aI9L3JS88QYPfFcSJJ3XapxOQ4j40v6mk7ATpA8CVK1vGTzpihNlOfMVRBkR95oAj7oL6aiDOg==", - "dev": true, - "requires": { - "@ethersproject/abi": "^5.6.3", - "web3-utils": "1.10.0" - } - }, - "web3-eth-accounts": { - "version": "1.10.0", - "resolved": "https://registry.npmjs.org/web3-eth-accounts/-/web3-eth-accounts-1.10.0.tgz", - "integrity": "sha512-wiq39Uc3mOI8rw24wE2n15hboLE0E9BsQLdlmsL4Zua9diDS6B5abXG0XhFcoNsXIGMWXVZz4TOq3u4EdpXF/Q==", - "dev": true, - "requires": { - "@ethereumjs/common": "2.5.0", - "@ethereumjs/tx": "3.3.2", - "eth-lib": "0.2.8", - "ethereumjs-util": "^7.1.5", - "scrypt-js": "^3.0.1", - "uuid": "^9.0.0", - "web3-core": "1.10.0", - "web3-core-helpers": "1.10.0", - "web3-core-method": "1.10.0", - "web3-utils": "1.10.0" - }, - "dependencies": { - "scrypt-js": { - "version": "3.0.1", - "resolved": "https://registry.npmjs.org/scrypt-js/-/scrypt-js-3.0.1.tgz", - "integrity": "sha512-cdwTTnqPu0Hyvf5in5asVdZocVDTNRmR7XEcJuIzMjJeSHybHl7vpB66AzwTaIg6CLSbtjcxc8fqcySfnTkccA==", - "dev": true - }, - "uuid": { - "version": "9.0.0", - "resolved": "https://registry.npmjs.org/uuid/-/uuid-9.0.0.tgz", - "integrity": "sha512-MXcSTerfPa4uqyzStbRoTgt5XIe3x5+42+q1sDuy3R5MDk66URdLMOZe5aPX/SQd+kuYAh0FdP/pO28IkQyTeg==", - "dev": true - } - } - }, - "web3-eth-contract": { - "version": "1.10.0", - "resolved": "https://registry.npmjs.org/web3-eth-contract/-/web3-eth-contract-1.10.0.tgz", - "integrity": "sha512-MIC5FOzP/+2evDksQQ/dpcXhSqa/2hFNytdl/x61IeWxhh6vlFeSjq0YVTAyIzdjwnL7nEmZpjfI6y6/Ufhy7w==", - "dev": true, - "requires": { - "@types/bn.js": "^5.1.1", - "web3-core": "1.10.0", - "web3-core-helpers": "1.10.0", - "web3-core-method": "1.10.0", - "web3-core-promievent": "1.10.0", - "web3-core-subscriptions": "1.10.0", - "web3-eth-abi": "1.10.0", - "web3-utils": "1.10.0" - } - }, - "web3-eth-ens": { - "version": "1.10.0", - "resolved": "https://registry.npmjs.org/web3-eth-ens/-/web3-eth-ens-1.10.0.tgz", - "integrity": "sha512-3hpGgzX3qjgxNAmqdrC2YUQMTfnZbs4GeLEmy8aCWziVwogbuqQZ+Gzdfrym45eOZodk+lmXyLuAdqkNlvkc1g==", - "dev": true, - "requires": { - "content-hash": "^2.5.2", - "eth-ens-namehash": "2.0.8", - "web3-core": "1.10.0", - "web3-core-helpers": "1.10.0", - "web3-core-promievent": "1.10.0", - "web3-eth-abi": "1.10.0", - "web3-eth-contract": "1.10.0", - "web3-utils": "1.10.0" - } - }, - "web3-eth-iban": { - "version": "1.10.0", - "resolved": "https://registry.npmjs.org/web3-eth-iban/-/web3-eth-iban-1.10.0.tgz", - "integrity": "sha512-0l+SP3IGhInw7Q20LY3IVafYEuufo4Dn75jAHT7c2aDJsIolvf2Lc6ugHkBajlwUneGfbRQs/ccYPQ9JeMUbrg==", - "dev": true, - "requires": { - "bn.js": "^5.2.1", - "web3-utils": "1.10.0" - } - }, - "web3-eth-personal": { - "version": "1.10.0", - "resolved": "https://registry.npmjs.org/web3-eth-personal/-/web3-eth-personal-1.10.0.tgz", - "integrity": "sha512-anseKn98w/d703eWq52uNuZi7GhQeVjTC5/svrBWEKob0WZ5kPdo+EZoFN0sp5a5ubbrk/E0xSl1/M5yORMtpg==", - "dev": true, - "requires": { - "@types/node": "^12.12.6", - "web3-core": "1.10.0", - "web3-core-helpers": "1.10.0", - "web3-core-method": "1.10.0", - "web3-net": "1.10.0", - "web3-utils": "1.10.0" - } - }, - "web3-net": { - "version": "1.10.0", - "resolved": "https://registry.npmjs.org/web3-net/-/web3-net-1.10.0.tgz", - "integrity": "sha512-NLH/N3IshYWASpxk4/18Ge6n60GEvWBVeM8inx2dmZJVmRI6SJIlUxbL8jySgiTn3MMZlhbdvrGo8fpUW7a1GA==", - "dev": true, - "requires": { - "web3-core": "1.10.0", - "web3-core-method": "1.10.0", - "web3-utils": "1.10.0" - } - }, - "web3-providers-http": { - "version": "1.10.0", - "resolved": "https://registry.npmjs.org/web3-providers-http/-/web3-providers-http-1.10.0.tgz", - "integrity": "sha512-eNr965YB8a9mLiNrkjAWNAPXgmQWfpBfkkn7tpEFlghfww0u3I0tktMZiaToJVcL2+Xq+81cxbkpeWJ5XQDwOA==", - "dev": true, - "requires": { - "abortcontroller-polyfill": "^1.7.3", - "cross-fetch": "^3.1.4", - "es6-promise": "^4.2.8", - "web3-core-helpers": "1.10.0" - } - }, - "web3-providers-ipc": { - "version": "1.10.0", - "resolved": "https://registry.npmjs.org/web3-providers-ipc/-/web3-providers-ipc-1.10.0.tgz", - "integrity": "sha512-OfXG1aWN8L1OUqppshzq8YISkWrYHaATW9H8eh0p89TlWMc1KZOL9vttBuaBEi96D/n0eYDn2trzt22bqHWfXA==", - "dev": true, - "requires": { - "oboe": "2.1.5", - "web3-core-helpers": "1.10.0" - } - }, - "web3-providers-ws": { - "version": "1.10.0", - "resolved": "https://registry.npmjs.org/web3-providers-ws/-/web3-providers-ws-1.10.0.tgz", - "integrity": "sha512-sK0fNcglW36yD5xjnjtSGBnEtf59cbw4vZzJ+CmOWIKGIR96mP5l684g0WD0Eo+f4NQc2anWWXG74lRc9OVMCQ==", - "dev": true, - "requires": { - "eventemitter3": "4.0.4", - "web3-core-helpers": "1.10.0", - "websocket": "^1.0.32" - } - }, - "web3-shh": { - "version": "1.10.0", - "resolved": "https://registry.npmjs.org/web3-shh/-/web3-shh-1.10.0.tgz", - "integrity": "sha512-uNUUuNsO2AjX41GJARV9zJibs11eq6HtOe6Wr0FtRUcj8SN6nHeYIzwstAvJ4fXA53gRqFMTxdntHEt9aXVjpg==", - "dev": true, - "requires": { - "web3-core": "1.10.0", - "web3-core-method": "1.10.0", - "web3-core-subscriptions": "1.10.0", - "web3-net": "1.10.0" - } - } - } - }, - "@truffle/provider": { - "version": "0.2.64", - "resolved": "https://registry.npmjs.org/@truffle/provider/-/provider-0.2.64.tgz", - "integrity": "sha512-ZwPsofw4EsCq/2h0t73SPnnFezu4YQWBmK4FxFaOUX0F+o8NsZuHKyfJzuZwyZbiktYmefM3yD9rM0Dj4BhNbw==", - "dev": true, - "requires": { - "@truffle/error": "^0.1.1", - "@truffle/interface-adapter": "^0.5.25", - "debug": "^4.3.1", - "web3": "1.7.4" - } - }, - "@tsconfig/node10": { - "version": "1.0.9", - "resolved": "https://registry.npmjs.org/@tsconfig/node10/-/node10-1.0.9.tgz", - "integrity": "sha512-jNsYVVxU8v5g43Erja32laIDHXeoNvFEpX33OK4d6hljo3jDhCBDhx5dhCCTMWUojscpAagGiRkBKxpdl9fxqA==", - "dev": true - }, - "@tsconfig/node12": { - "version": "1.0.11", - "resolved": "https://registry.npmjs.org/@tsconfig/node12/-/node12-1.0.11.tgz", - "integrity": "sha512-cqefuRsh12pWyGsIoBKJA9luFu3mRxCA+ORZvA4ktLSzIuCUtWVxGIuXigEwO5/ywWFMZ2QEGKWvkZG1zDMTag==", - "dev": true - }, - "@tsconfig/node14": { - "version": "1.0.3", - "resolved": "https://registry.npmjs.org/@tsconfig/node14/-/node14-1.0.3.tgz", - "integrity": "sha512-ysT8mhdixWK6Hw3i1V2AeRqZ5WfXg1G43mqoYlM2nc6388Fq5jcXyr5mRsqViLx/GJYdoL0bfXD8nmF+Zn/Iow==", - "dev": true - }, - "@tsconfig/node16": { - "version": "1.0.4", - "resolved": "https://registry.npmjs.org/@tsconfig/node16/-/node16-1.0.4.tgz", - "integrity": "sha512-vxhUy4J8lyeyinH7Azl1pdd43GJhZH/tP2weN8TntQblOY+A0XbT8DJk1/oCPuOOyg/Ja757rG0CgHcWC8OfMA==", - "dev": true - }, - "@typechain/ethers-v5": { - "version": "7.2.0", - "resolved": "https://registry.npmjs.org/@typechain/ethers-v5/-/ethers-v5-7.2.0.tgz", - "integrity": "sha512-jfcmlTvaaJjng63QsT49MT6R1HFhtO/TBMWbyzPFSzMmVIqb2tL6prnKBs4ZJrSvmgIXWy+ttSjpaxCTq8D/Tw==", - "dev": true, - "requires": { - "lodash": "^4.17.15", - "ts-essentials": "^7.0.1" - } - }, - "@typechain/hardhat": { - "version": "2.3.1", - "resolved": "https://registry.npmjs.org/@typechain/hardhat/-/hardhat-2.3.1.tgz", - "integrity": "sha512-BQV8OKQi0KAzLXCdsPO0pZBNQQ6ra8A2ucC26uFX/kquRBtJu1yEyWnVSmtr07b5hyRoJRpzUeINLnyqz4/MAw==", - "dev": true, - "requires": { - "fs-extra": "^9.1.0" - }, - "dependencies": { - "fs-extra": { - "version": "9.1.0", - "resolved": "https://registry.npmjs.org/fs-extra/-/fs-extra-9.1.0.tgz", - "integrity": "sha512-hcg3ZmepS30/7BSFqRvoo3DOMQu7IjqxO5nCDt+zM9XWjb33Wg7ziNT+Qvqbuc3+gWpzO02JubVyk2G4Zvo1OQ==", - "dev": true, - "requires": { - "at-least-node": "^1.0.0", - "graceful-fs": "^4.2.0", - "jsonfile": "^6.0.1", - "universalify": "^2.0.0" - } - }, - "jsonfile": { - "version": "6.1.0", - "resolved": "https://registry.npmjs.org/jsonfile/-/jsonfile-6.1.0.tgz", - "integrity": "sha512-5dgndWOriYSm5cnYaJNhalLNDKOqFwyDB/rr1E9ZsGciGvKPs8R2xYGCacuf3z6K1YKDz182fd+fY3cn3pMqXQ==", - "dev": true, - "requires": { - "graceful-fs": "^4.1.6", - "universalify": "^2.0.0" - } - }, - "universalify": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/universalify/-/universalify-2.0.0.tgz", - "integrity": "sha512-hAZsKq7Yy11Zu1DE0OzWjw7nnLZmJZYTDZZyEFHZdUhV8FkH5MCfoU1XMaxXovpyW5nq5scPqq0ZDP9Zyl04oQ==", - "dev": true - } - } - }, - "@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==", - "requires": { - "@types/node": "*" - } - }, - "@types/cacheable-request": { - "version": "6.0.3", - "resolved": "https://registry.npmjs.org/@types/cacheable-request/-/cacheable-request-6.0.3.tgz", - "integrity": "sha512-IQ3EbTzGxIigb1I3qPZc1rWJnH0BmSKv5QYTalEwweFvyBDLSAe24zP0le/hyi7ecGfZVlIVAg4BZqb8WBwKqw==", - "dev": true, - "requires": { - "@types/http-cache-semantics": "*", - "@types/keyv": "^3.1.4", - "@types/node": "*", - "@types/responselike": "^1.0.0" - } - }, - "@types/chai": { - "version": "4.3.5", - "resolved": "https://registry.npmjs.org/@types/chai/-/chai-4.3.5.tgz", - "integrity": "sha512-mEo1sAde+UCE6b2hxn332f1g1E8WfYRu6p5SvTKr2ZKC1f7gFJXk4h5PyGP9Dt6gCaG8y8XhwnXWC6Iy2cmBng==", - "dev": true - }, - "@types/concat-stream": { - "version": "1.6.1", - "resolved": "https://registry.npmjs.org/@types/concat-stream/-/concat-stream-1.6.1.tgz", - "integrity": "sha512-eHE4cQPoj6ngxBZMvVf6Hw7Mh4jMW4U9lpGmS5GBPB9RYxlFg+CHaVN7ErNY4W9XfLIEn20b4VDYaIrbq0q4uA==", - "dev": true, - "requires": { - "@types/node": "*" - } - }, - "@types/form-data": { - "version": "0.0.33", - "resolved": "https://registry.npmjs.org/@types/form-data/-/form-data-0.0.33.tgz", - "integrity": "sha512-8BSvG1kGm83cyJITQMZSulnl6QV8jqAGreJsc5tPu1Jq0vTSOiY/k24Wx82JRpWwZSqrala6sd5rWi6aNXvqcw==", - "dev": true, - "requires": { - "@types/node": "*" - } - }, - "@types/glob": { - "version": "7.2.0", - "resolved": "https://registry.npmjs.org/@types/glob/-/glob-7.2.0.tgz", - "integrity": "sha512-ZUxbzKl0IfJILTS6t7ip5fQQM/J3TJYubDm3nMbgubNNYS62eXeUpoLUC8/7fJNiFYHTrGPQn7hspDUzIHX3UA==", - "dev": true, - "requires": { - "@types/minimatch": "*", - "@types/node": "*" - } - }, - "@types/http-cache-semantics": { - "version": "4.0.1", - "resolved": "https://registry.npmjs.org/@types/http-cache-semantics/-/http-cache-semantics-4.0.1.tgz", - "integrity": "sha512-SZs7ekbP8CN0txVG2xVRH6EgKmEm31BOxA07vkFaETzZz1xh+cbt8BcI0slpymvwhx5dlFnQG2rTlPVQn+iRPQ==", - "dev": true - }, - "@types/json-schema": { - "version": "7.0.12", - "resolved": "https://registry.npmjs.org/@types/json-schema/-/json-schema-7.0.12.tgz", - "integrity": "sha512-Hr5Jfhc9eYOQNPYO5WLDq/n4jqijdHNlDXjuAQkkt+mWdQR+XJToOHrsD4cPaMXpn6KO7y2+wM8AZEs8VpBLVA==", - "dev": true - }, - "@types/json5": { - "version": "0.0.29", - "resolved": "https://registry.npmjs.org/@types/json5/-/json5-0.0.29.tgz", - "integrity": "sha512-dRLjCWHYg4oaA77cxO64oO+7JwCwnIzkZPdrrC71jQmQtlhM556pwKo5bUzqvZndkVbeFLIIi+9TC40JNF5hNQ==", - "dev": true - }, - "@types/keyv": { - "version": "3.1.4", - "resolved": "https://registry.npmjs.org/@types/keyv/-/keyv-3.1.4.tgz", - "integrity": "sha512-BQ5aZNSCpj7D6K2ksrRCTmKRLEpnPvWDiLPfoGyhZ++8YtiK9d/3DBKPJgry359X/P1PfruyYwvnvwFjuEiEIg==", - "dev": true, - "requires": { - "@types/node": "*" - } - }, - "@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==", - "dev": true - }, - "@types/minimatch": { - "version": "5.1.2", - "resolved": "https://registry.npmjs.org/@types/minimatch/-/minimatch-5.1.2.tgz", - "integrity": "sha512-K0VQKziLUWkVKiRVrx4a40iPaxTUefQmjtkQofBkYRcoaaL/8rhwDWww9qWbrgicNOgnpIsMxyNIUM4+n6dUIA==", - "dev": true - }, - "@types/mkdirp": { - "version": "0.5.2", - "resolved": "https://registry.npmjs.org/@types/mkdirp/-/mkdirp-0.5.2.tgz", - "integrity": "sha512-U5icWpv7YnZYGsN4/cmh3WD2onMY0aJIiTE6+51TwJCttdHvtCYmkBNOobHlXwrJRL0nkH9jH4kD+1FAdMN4Tg==", - "dev": true, - "requires": { - "@types/node": "*" - } - }, - "@types/mocha": { - "version": "9.1.1", - "resolved": "https://registry.npmjs.org/@types/mocha/-/mocha-9.1.1.tgz", - "integrity": "sha512-Z61JK7DKDtdKTWwLeElSEBcWGRLY8g95ic5FoQqI9CMx0ns/Ghep3B4DfcEimiKMvtamNVULVNKEsiwV3aQmXw==", - "dev": true - }, - "@types/node": { - "version": "12.20.55", - "resolved": "https://registry.npmjs.org/@types/node/-/node-12.20.55.tgz", - "integrity": "sha512-J8xLz7q2OFulZ2cyGTLE1TbbZcjpno7FaN6zdJNrgAdrJ+DZzh/uFR6YrTb4C+nXakvud8Q4+rbhoIWlYQbUFQ==" - }, - "@types/node-fetch": { - "version": "2.6.4", - "resolved": "https://registry.npmjs.org/@types/node-fetch/-/node-fetch-2.6.4.tgz", - "integrity": "sha512-1ZX9fcN4Rvkvgv4E6PAY5WXUFWFcRWxZa3EW83UjycOB9ljJCedb2CupIP4RZMEwF/M3eTcCihbBRgwtGbg5Rg==", - "dev": true, - "requires": { - "@types/node": "*", - "form-data": "^3.0.0" - } - }, - "@types/pbkdf2": { - "version": "3.1.0", - "resolved": "https://registry.npmjs.org/@types/pbkdf2/-/pbkdf2-3.1.0.tgz", - "integrity": "sha512-Cf63Rv7jCQ0LaL8tNXmEyqTHuIJxRdlS5vMh1mj5voN4+QFhVZnlZruezqpWYDiJ8UTzhP0VmeLXCmBk66YrMQ==", - "requires": { - "@types/node": "*" - } - }, - "@types/prettier": { - "version": "2.7.3", - "resolved": "https://registry.npmjs.org/@types/prettier/-/prettier-2.7.3.tgz", - "integrity": "sha512-+68kP9yzs4LMp7VNh8gdzMSPZFL44MLGqiHWvttYJe+6qnuVr4Ek9wSBQoveqY/r+LwjCcU29kNVkidwim+kYA==", - "dev": true - }, - "@types/qs": { - "version": "6.9.7", - "resolved": "https://registry.npmjs.org/@types/qs/-/qs-6.9.7.tgz", - "integrity": "sha512-FGa1F62FT09qcrueBA6qYTrJPVDzah9a+493+o2PCXsesWHIn27G98TsSMs3WPNbZIEj4+VJf6saSFpvD+3Zsw==", - "dev": true - }, - "@types/readable-stream": { - "version": "2.3.15", - "resolved": "https://registry.npmjs.org/@types/readable-stream/-/readable-stream-2.3.15.tgz", - "integrity": "sha512-oM5JSKQCcICF1wvGgmecmHldZ48OZamtMxcGGVICOJA8o8cahXC1zEVAif8iwoc5j8etxFaRFnf095+CDsuoFQ==", - "dev": true, - "requires": { - "@types/node": "*", - "safe-buffer": "~5.1.1" - } - }, - "@types/resolve": { - "version": "0.0.8", - "resolved": "https://registry.npmjs.org/@types/resolve/-/resolve-0.0.8.tgz", - "integrity": "sha512-auApPaJf3NPfe18hSoJkp8EbZzer2ISk7o8mCC3M9he/a04+gbMF97NkpD2S8riMGvm4BMRI59/SZQSaLTKpsQ==", - "dev": true, - "requires": { - "@types/node": "*" - } - }, - "@types/responselike": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/@types/responselike/-/responselike-1.0.0.tgz", - "integrity": "sha512-85Y2BjiufFzaMIlvJDvTTB8Fxl2xfLo4HgmHzVBz08w4wDePCTjYw66PdrolO0kzli3yam/YCgRufyo1DdQVTA==", - "dev": true, - "requires": { - "@types/node": "*" - } - }, - "@types/secp256k1": { - "version": "4.0.3", - "resolved": "https://registry.npmjs.org/@types/secp256k1/-/secp256k1-4.0.3.tgz", - "integrity": "sha512-Da66lEIFeIz9ltsdMZcpQvmrmmoqrfju8pm1BH8WbYjZSwUgCwXLb9C+9XYogwBITnbsSaMdVPb2ekf7TV+03w==", - "requires": { - "@types/node": "*" - } - }, - "@types/sinon": { - "version": "10.0.15", - "resolved": "https://registry.npmjs.org/@types/sinon/-/sinon-10.0.15.tgz", - "integrity": "sha512-3lrFNQG0Kr2LDzvjyjB6AMJk4ge+8iYhQfdnSwIwlG88FUOV43kPcQqDZkDa/h3WSZy6i8Fr0BSjfQtB1B3xuQ==", - "dev": true, - "peer": true, - "requires": { - "@types/sinonjs__fake-timers": "*" - } - }, - "@types/sinon-chai": { - "version": "3.2.9", - "resolved": "https://registry.npmjs.org/@types/sinon-chai/-/sinon-chai-3.2.9.tgz", - "integrity": "sha512-/19t63pFYU0ikrdbXKBWj9PCdnKyTd0Qkz0X91Ta081cYsq90OxYdcWwK/dwEoDa6dtXgj2HJfmzgq+QZTHdmQ==", - "dev": true, - "peer": true, - "requires": { - "@types/chai": "*", - "@types/sinon": "*" - } - }, - "@types/sinonjs__fake-timers": { - "version": "8.1.2", - "resolved": "https://registry.npmjs.org/@types/sinonjs__fake-timers/-/sinonjs__fake-timers-8.1.2.tgz", - "integrity": "sha512-9GcLXF0/v3t80caGs5p2rRfkB+a8VBGLJZVih6CNFkx8IZ994wiKKLSRs9nuFwk1HevWs/1mnUmkApGrSGsShA==", - "dev": true, - "peer": true - }, - "@typescript-eslint/eslint-plugin": { - "version": "4.33.0", - "resolved": "https://registry.npmjs.org/@typescript-eslint/eslint-plugin/-/eslint-plugin-4.33.0.tgz", - "integrity": "sha512-aINiAxGVdOl1eJyVjaWn/YcVAq4Gi/Yo35qHGCnqbWVz61g39D0h23veY/MA0rFFGfxK7TySg2uwDeNv+JgVpg==", - "dev": true, - "requires": { - "@typescript-eslint/experimental-utils": "4.33.0", - "@typescript-eslint/scope-manager": "4.33.0", - "debug": "^4.3.1", - "functional-red-black-tree": "^1.0.1", - "ignore": "^5.1.8", - "regexpp": "^3.1.0", - "semver": "^7.3.5", - "tsutils": "^3.21.0" - }, - "dependencies": { - "lru-cache": { - "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, - "requires": { - "yallist": "^4.0.0" - } - }, - "semver": { - "version": "7.5.4", - "resolved": "https://registry.npmjs.org/semver/-/semver-7.5.4.tgz", - "integrity": "sha512-1bCSESV6Pv+i21Hvpxp3Dx+pSD8lIPt8uVjRrxAUt/nbswYc+tK6Y2btiULjd4+fnq15PX+nqQDC7Oft7WkwcA==", - "dev": true, - "requires": { - "lru-cache": "^6.0.0" - } - }, - "yallist": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/yallist/-/yallist-4.0.0.tgz", - "integrity": "sha512-3wdGidZyq5PB084XLES5TpOSRA3wjXAlIWMhum2kRcv/41Sn2emQ0dycQW4uZXLejwKvg6EsvbdlVL+FYEct7A==", - "dev": true - } - } - }, - "@typescript-eslint/experimental-utils": { - "version": "4.33.0", - "resolved": "https://registry.npmjs.org/@typescript-eslint/experimental-utils/-/experimental-utils-4.33.0.tgz", - "integrity": "sha512-zeQjOoES5JFjTnAhI5QY7ZviczMzDptls15GFsI6jyUOq0kOf9+WonkhtlIhh0RgHRnqj5gdNxW5j1EvAyYg6Q==", - "dev": true, - "requires": { - "@types/json-schema": "^7.0.7", - "@typescript-eslint/scope-manager": "4.33.0", - "@typescript-eslint/types": "4.33.0", - "@typescript-eslint/typescript-estree": "4.33.0", - "eslint-scope": "^5.1.1", - "eslint-utils": "^3.0.0" - } - }, - "@typescript-eslint/parser": { - "version": "4.33.0", - "resolved": "https://registry.npmjs.org/@typescript-eslint/parser/-/parser-4.33.0.tgz", - "integrity": "sha512-ZohdsbXadjGBSK0/r+d87X0SBmKzOq4/S5nzK6SBgJspFo9/CUDJ7hjayuze+JK7CZQLDMroqytp7pOcFKTxZA==", - "dev": true, - "requires": { - "@typescript-eslint/scope-manager": "4.33.0", - "@typescript-eslint/types": "4.33.0", - "@typescript-eslint/typescript-estree": "4.33.0", - "debug": "^4.3.1" - } - }, - "@typescript-eslint/scope-manager": { - "version": "4.33.0", - "resolved": "https://registry.npmjs.org/@typescript-eslint/scope-manager/-/scope-manager-4.33.0.tgz", - "integrity": "sha512-5IfJHpgTsTZuONKbODctL4kKuQje/bzBRkwHE8UOZ4f89Zeddg+EGZs8PD8NcN4LdM3ygHWYB3ukPAYjvl/qbQ==", - "dev": true, - "requires": { - "@typescript-eslint/types": "4.33.0", - "@typescript-eslint/visitor-keys": "4.33.0" - } - }, - "@typescript-eslint/types": { - "version": "4.33.0", - "resolved": "https://registry.npmjs.org/@typescript-eslint/types/-/types-4.33.0.tgz", - "integrity": "sha512-zKp7CjQzLQImXEpLt2BUw1tvOMPfNoTAfb8l51evhYbOEEzdWyQNmHWWGPR6hwKJDAi+1VXSBmnhL9kyVTTOuQ==", - "dev": true - }, - "@typescript-eslint/typescript-estree": { - "version": "4.33.0", - "resolved": "https://registry.npmjs.org/@typescript-eslint/typescript-estree/-/typescript-estree-4.33.0.tgz", - "integrity": "sha512-rkWRY1MPFzjwnEVHsxGemDzqqddw2QbTJlICPD9p9I9LfsO8fdmfQPOX3uKfUaGRDFJbfrtm/sXhVXN4E+bzCA==", - "dev": true, - "requires": { - "@typescript-eslint/types": "4.33.0", - "@typescript-eslint/visitor-keys": "4.33.0", - "debug": "^4.3.1", - "globby": "^11.0.3", - "is-glob": "^4.0.1", - "semver": "^7.3.5", - "tsutils": "^3.21.0" - }, - "dependencies": { - "lru-cache": { - "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, - "requires": { - "yallist": "^4.0.0" - } - }, - "semver": { - "version": "7.5.4", - "resolved": "https://registry.npmjs.org/semver/-/semver-7.5.4.tgz", - "integrity": "sha512-1bCSESV6Pv+i21Hvpxp3Dx+pSD8lIPt8uVjRrxAUt/nbswYc+tK6Y2btiULjd4+fnq15PX+nqQDC7Oft7WkwcA==", - "dev": true, - "requires": { - "lru-cache": "^6.0.0" - } - }, - "yallist": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/yallist/-/yallist-4.0.0.tgz", - "integrity": "sha512-3wdGidZyq5PB084XLES5TpOSRA3wjXAlIWMhum2kRcv/41Sn2emQ0dycQW4uZXLejwKvg6EsvbdlVL+FYEct7A==", - "dev": true - } - } - }, - "@typescript-eslint/visitor-keys": { - "version": "4.33.0", - "resolved": "https://registry.npmjs.org/@typescript-eslint/visitor-keys/-/visitor-keys-4.33.0.tgz", - "integrity": "sha512-uqi/2aSz9g2ftcHWf8uLPJA70rUv6yuMW5Bohw+bwcuzaxQIHaKFZCKGoGXIrc9vkTJ3+0txM73K0Hq3d5wgIg==", - "dev": true, - "requires": { - "@typescript-eslint/types": "4.33.0", - "eslint-visitor-keys": "^2.0.0" - } - }, - "@yarnpkg/lockfile": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/@yarnpkg/lockfile/-/lockfile-1.1.0.tgz", - "integrity": "sha512-GpSwvyXOcOOlV70vbnzjj4fW5xW/FdUF6nQEt1ENy7m4ZCczi1+/buVUPAqmGfqznsORNFzUMjctTIp8a9tuCQ==", - "dev": true - }, - "abbrev": { - "version": "1.0.9", - "resolved": "https://registry.npmjs.org/abbrev/-/abbrev-1.0.9.tgz", - "integrity": "sha512-LEyx4aLEC3x6T0UguF6YILf+ntvmOaWsVfENmIW0E9H09vKlLDGelMjjSm0jkDHALj8A8quZ/HapKNigzwge+Q==", - "dev": true - }, - "abort-controller": { - "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, - "requires": { - "event-target-shim": "^5.0.0" - } - }, - "abortcontroller-polyfill": { - "version": "1.7.5", - "resolved": "https://registry.npmjs.org/abortcontroller-polyfill/-/abortcontroller-polyfill-1.7.5.tgz", - "integrity": "sha512-JMJ5soJWP18htbbxJjG7bG6yuI6pRhgJ0scHHTfkUjf6wjP912xZWvM+A4sJK3gqd9E8fcPbDnOefbA9Th/FIQ==", - "dev": true - }, - "abstract-level": { - "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, - "requires": { - "buffer": "^6.0.3", - "catering": "^2.1.0", - "is-buffer": "^2.0.5", - "level-supports": "^4.0.0", - "level-transcoder": "^1.0.1", - "module-error": "^1.0.1", - "queue-microtask": "^1.2.3" - } - }, - "accepts": { - "version": "1.3.8", - "resolved": "https://registry.npmjs.org/accepts/-/accepts-1.3.8.tgz", - "integrity": "sha512-PYAthTa2m2VKxuvSD3DPC/Gy+U+sOA1LAuT8mkmRuvw+NACSaeXEQ+NHcVF7rONl6qcaxV3Uuemwawk+7+SJLw==", - "dev": true, - "requires": { - "mime-types": "~2.1.34", - "negotiator": "0.6.3" - } - }, - "acorn": { - "version": "7.4.1", - "resolved": "https://registry.npmjs.org/acorn/-/acorn-7.4.1.tgz", - "integrity": "sha512-nQyp0o1/mNdbTO1PO6kHkwSrmgZ0MT/jCCpNiwbUjGoRN4dlBhqJtoQuCnEOKzgTVwg0ZWiCoQy6SxMebQVh8A==", - "dev": true - }, - "acorn-jsx": { - "version": "5.3.2", - "resolved": "https://registry.npmjs.org/acorn-jsx/-/acorn-jsx-5.3.2.tgz", - "integrity": "sha512-rq9s+JNhf0IChjtDXxllJ7g41oZk5SlXtp0LHwyA5cejwn7vKmKp4pPri6YEePv2PU65sAsegbXtIinmDFDXgQ==", - "dev": true, - "requires": {} - }, - "acorn-walk": { - "version": "8.2.0", - "resolved": "https://registry.npmjs.org/acorn-walk/-/acorn-walk-8.2.0.tgz", - "integrity": "sha512-k+iyHEuPgSw6SbuDpGQM+06HQUa04DZ3o+F6CSzXMvvI5KMvnaEqXe+YVe555R9nn6GPt404fos4wcgpw12SDA==", - "dev": true - }, - "address": { - "version": "1.2.2", - "resolved": "https://registry.npmjs.org/address/-/address-1.2.2.tgz", - "integrity": "sha512-4B/qKCfeE/ODUaAUpSwfzazo5x29WD4r3vXiWsB7I2mSDAihwEqKO+g8GELZUQSSAo5e1XTYh3ZVfLyxBc12nA==", - "dev": true - }, - "adm-zip": { - "version": "0.4.16", - "resolved": "https://registry.npmjs.org/adm-zip/-/adm-zip-0.4.16.tgz", - "integrity": "sha512-TFi4HBKSGfIKsK5YCkKaaFG2m4PEDyViZmEwof3MTIgzimHLto6muaHVpbrljdIvIrFZzEq/p4nafOeLcYegrg==", - "dev": true - }, - "aes-js": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/aes-js/-/aes-js-3.0.0.tgz", - "integrity": "sha512-H7wUZRn8WpTq9jocdxQ2c8x2sKo9ZVmzfRE13GiNJXfp7NcKYEdvl3vspKjXox6RIG2VtaRe4JFvxG4rqp2Zuw==" - }, - "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, - "requires": { - "debug": "4" - } - }, - "aggregate-error": { - "version": "3.1.0", - "resolved": "https://registry.npmjs.org/aggregate-error/-/aggregate-error-3.1.0.tgz", - "integrity": "sha512-4I7Td01quW/RpocfNayFdFVk1qSuoh0E7JrbRJ16nH01HhKFQ88INq9Sd+nd72zqRySlr9BmDA8xlEJ6vJMrYA==", - "dev": true, - "requires": { - "clean-stack": "^2.0.0", - "indent-string": "^4.0.0" - } - }, - "ajv": { - "version": "6.12.6", - "resolved": "https://registry.npmjs.org/ajv/-/ajv-6.12.6.tgz", - "integrity": "sha512-j3fVLgvTo527anyYyJOGTYJbG+vnnQYvE0m5mmkc1TK+nxAppkCLMIL0aZ4dblVCNoGShhm+kzE4ZUykBoMg4g==", - "dev": true, - "requires": { - "fast-deep-equal": "^3.1.1", - "fast-json-stable-stringify": "^2.0.0", - "json-schema-traverse": "^0.4.1", - "uri-js": "^4.2.2" - } - }, - "amdefine": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/amdefine/-/amdefine-1.0.1.tgz", - "integrity": "sha512-S2Hw0TtNkMJhIabBwIojKL9YHO5T0n5eNqWJ7Lrlel/zDbftQpxpapi8tZs3X1HWa+u+QeydGmzzNU0m09+Rcg==", - "dev": true, - "optional": true - }, - "ansi-colors": { - "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 - }, - "ansi-escapes": { - "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, - "requires": { - "type-fest": "^0.21.3" - }, - "dependencies": { - "type-fest": { - "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 - } - } - }, - "ansi-regex": { - "version": "5.0.1", - "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-5.0.1.tgz", - "integrity": "sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ==", - "dev": true - }, - "ansi-styles": { - "version": "4.3.0", - "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz", - "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==", - "requires": { - "color-convert": "^2.0.1" - } - }, - "antlr4": { - "version": "4.13.0", - "resolved": "https://registry.npmjs.org/antlr4/-/antlr4-4.13.0.tgz", - "integrity": "sha512-zooUbt+UscjnWyOrsuY/tVFL4rwrAGwOivpQmvmUDE22hy/lUA467Rc1rcixyRwcRUIXFYBwv7+dClDSHdmmew==", - "dev": true - }, - "antlr4ts": { - "version": "0.5.0-alpha.4", - "resolved": "https://registry.npmjs.org/antlr4ts/-/antlr4ts-0.5.0-alpha.4.tgz", - "integrity": "sha512-WPQDt1B74OfPv/IMS2ekXAKkTZIHl88uMetg6q3OTqgFxZ/dxDXI0EWLyZid/1Pe6hTftyg5N7gel5wNAGxXyQ==", - "dev": true - }, - "anymatch": { - "version": "3.1.3", - "resolved": "https://registry.npmjs.org/anymatch/-/anymatch-3.1.3.tgz", - "integrity": "sha512-KMReFUr0B4t+D+OBkjR3KYqvocp2XaSzO55UcB6mgQMd3KbcE+mWTyvVV7D/zsdEbNnV6acZUutkiHQXvTr1Rw==", - "dev": true, - "requires": { - "normalize-path": "^3.0.0", - "picomatch": "^2.0.4" - } - }, - "arg": { - "version": "4.1.3", - "resolved": "https://registry.npmjs.org/arg/-/arg-4.1.3.tgz", - "integrity": "sha512-58S9QDqG0Xx27YwPSt9fJxivjYl432YCwfDMfZ+71RAqUrZef7LrKQZ3LHLOwCS4FLNBplP533Zx895SeOCHvA==", - "dev": true - }, - "argparse": { - "version": "1.0.10", - "resolved": "https://registry.npmjs.org/argparse/-/argparse-1.0.10.tgz", - "integrity": "sha512-o5Roy6tNG4SL/FOkCAN6RzjiakZS25RLYFrcMttJqbdd8BWrnA+fGz57iN5Pb06pvBGvl5gQ0B48dJlslXvoTg==", - "dev": true, - "requires": { - "sprintf-js": "~1.0.2" - } - }, - "array-back": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/array-back/-/array-back-2.0.0.tgz", - "integrity": "sha512-eJv4pLLufP3g5kcZry0j6WXpIbzYw9GUB4mVJZno9wfwiBxbizTnHCw3VJb07cBihbFX48Y7oSrW9y+gt4glyw==", - "dev": true, - "requires": { - "typical": "^2.6.1" - } - }, - "array-buffer-byte-length": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/array-buffer-byte-length/-/array-buffer-byte-length-1.0.0.tgz", - "integrity": "sha512-LPuwb2P+NrQw3XhxGc36+XSvuBPopovXYTR9Ew++Du9Yb/bx5AzBfrIsBoj0EZUifjQU+sHL21sseZ3jerWO/A==", - "dev": true, - "requires": { - "call-bind": "^1.0.2", - "is-array-buffer": "^3.0.1" - } - }, - "array-flatten": { - "version": "1.1.1", - "resolved": "https://registry.npmjs.org/array-flatten/-/array-flatten-1.1.1.tgz", - "integrity": "sha512-PCVAQswWemu6UdxsDFFX/+gVeYqKAod3D3UVm91jHwynguOwAvYPhx8nNlM++NqRcK6CxxpUafjmhIdKiHibqg==", - "dev": true - }, - "array-includes": { - "version": "3.1.6", - "resolved": "https://registry.npmjs.org/array-includes/-/array-includes-3.1.6.tgz", - "integrity": "sha512-sgTbLvL6cNnw24FnbaDyjmvddQ2ML8arZsgaJhoABMoplz/4QRhtrYS+alr1BUM1Bwp6dhx8vVCBSLG+StwOFw==", - "dev": true, - "requires": { - "call-bind": "^1.0.2", - "define-properties": "^1.1.4", - "es-abstract": "^1.20.4", - "get-intrinsic": "^1.1.3", - "is-string": "^1.0.7" - } - }, - "array-union": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/array-union/-/array-union-2.1.0.tgz", - "integrity": "sha512-HGyxoOTYUyCM6stUe6EJgnd4EoewAI7zMdfqO+kGjnlZmBDz/cR5pf8r/cR4Wq60sL/p0IkcjUEEPwS3GFrIyw==", - "dev": true - }, - "array-uniq": { - "version": "1.0.3", - "resolved": "https://registry.npmjs.org/array-uniq/-/array-uniq-1.0.3.tgz", - "integrity": "sha512-MNha4BWQ6JbwhFhj03YK552f7cb3AzoE8SzeljgChvL1dl3IcvggXVz1DilzySZkCja+CXuZbdW7yATchWn8/Q==", - "dev": true - }, - "array.prototype.flat": { - "version": "1.3.1", - "resolved": "https://registry.npmjs.org/array.prototype.flat/-/array.prototype.flat-1.3.1.tgz", - "integrity": "sha512-roTU0KWIOmJ4DRLmwKd19Otg0/mT3qPNt0Qb3GWW8iObuZXxrjB/pzn0R3hqpRSWg4HCwqx+0vwOnWnvlOyeIA==", - "dev": true, - "requires": { - "call-bind": "^1.0.2", - "define-properties": "^1.1.4", - "es-abstract": "^1.20.4", - "es-shim-unscopables": "^1.0.0" - } - }, - "array.prototype.flatmap": { - "version": "1.3.1", - "resolved": "https://registry.npmjs.org/array.prototype.flatmap/-/array.prototype.flatmap-1.3.1.tgz", - "integrity": "sha512-8UGn9O1FDVvMNB0UlLv4voxRMze7+FpHyF5mSMRjWHUMlpoDViniy05870VlxhfgTnLbpuwTzvD76MTtWxB/mQ==", - "dev": true, - "requires": { - "call-bind": "^1.0.2", - "define-properties": "^1.1.4", - "es-abstract": "^1.20.4", - "es-shim-unscopables": "^1.0.0" - } - }, - "array.prototype.reduce": { - "version": "1.0.5", - "resolved": "https://registry.npmjs.org/array.prototype.reduce/-/array.prototype.reduce-1.0.5.tgz", - "integrity": "sha512-kDdugMl7id9COE8R7MHF5jWk7Dqt/fs4Pv+JXoICnYwqpjjjbUurz6w5fT5IG6brLdJhv6/VoHB0H7oyIBXd+Q==", - "dev": true, - "requires": { - "call-bind": "^1.0.2", - "define-properties": "^1.1.4", - "es-abstract": "^1.20.4", - "es-array-method-boxes-properly": "^1.0.0", - "is-string": "^1.0.7" - } - }, - "arraybuffer.prototype.slice": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/arraybuffer.prototype.slice/-/arraybuffer.prototype.slice-1.0.1.tgz", - "integrity": "sha512-09x0ZWFEjj4WD8PDbykUwo3t9arLn8NIzmmYEJFpYekOAQjpkGSyrQhNoRTcwwcFRu+ycWF78QZ63oWTqSjBcw==", - "dev": true, - "requires": { - "array-buffer-byte-length": "^1.0.0", - "call-bind": "^1.0.2", - "define-properties": "^1.2.0", - "get-intrinsic": "^1.2.1", - "is-array-buffer": "^3.0.2", - "is-shared-array-buffer": "^1.0.2" - } - }, - "asap": { - "version": "2.0.6", - "resolved": "https://registry.npmjs.org/asap/-/asap-2.0.6.tgz", - "integrity": "sha512-BSHWgDSAiKs50o2Re8ppvp3seVHXSRM44cdSsT9FfNEUUZLOGWVCsiWaRPWM1Znn+mqZ1OfVZ3z3DWEzSp7hRA==", - "dev": true - }, - "asn1": { - "version": "0.2.6", - "resolved": "https://registry.npmjs.org/asn1/-/asn1-0.2.6.tgz", - "integrity": "sha512-ix/FxPn0MDjeyJ7i/yoHGFt/EX6LyNbxSEhPPXODPL+KB0VPk86UYfL0lMdy+KCnv+fmvIzySwaK5COwqVbWTQ==", - "dev": true, - "requires": { - "safer-buffer": "~2.1.0" - } - }, - "asn1.js": { - "version": "5.4.1", - "resolved": "https://registry.npmjs.org/asn1.js/-/asn1.js-5.4.1.tgz", - "integrity": "sha512-+I//4cYPccV8LdmBLiX8CYvf9Sp3vQsrqu2QNXRcrbiWvcx/UdlFiqUJJzxRQxgsZmvhXhn4cSKeSmoFjVdupA==", - "dev": true, - "requires": { - "bn.js": "^4.0.0", - "inherits": "^2.0.1", - "minimalistic-assert": "^1.0.0", - "safer-buffer": "^2.1.0" - }, - "dependencies": { - "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==", - "dev": true - } - } - }, - "assert-plus": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/assert-plus/-/assert-plus-1.0.0.tgz", - "integrity": "sha512-NfJ4UzBCcQGLDlQq7nHxH+tv3kyZ0hHQqF5BO6J7tNJeP5do1llPr8dZ8zHonfhAu0PHAdMkSo+8o0wxg9lZWw==", - "dev": true - }, - "assertion-error": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/assertion-error/-/assertion-error-1.1.0.tgz", - "integrity": "sha512-jgsaNduz+ndvGyFt3uSuWqvy4lCnIJiovtouQN5JZHOKCS2QuhEdbcQHFhVksz2N2U9hXJo8odG7ETyWlEeuDw==" - }, - "ast-parents": { - "version": "0.0.1", - "resolved": "https://registry.npmjs.org/ast-parents/-/ast-parents-0.0.1.tgz", - "integrity": "sha512-XHusKxKz3zoYk1ic8Un640joHbFMhbqneyoZfoKnEGtf2ey9Uh/IdpcQplODdO/kENaMIWsD0nJm4+wX3UNLHA==", - "dev": true - }, - "astral-regex": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/astral-regex/-/astral-regex-2.0.0.tgz", - "integrity": "sha512-Z7tMw1ytTXt5jqMcOP+OQteU1VuNK9Y02uuJtKQ1Sv69jXQKKg5cibLwGJow8yzZP+eAc18EmLGPal0bp36rvQ==", - "dev": true - }, - "async": { - "version": "1.5.2", - "resolved": "https://registry.npmjs.org/async/-/async-1.5.2.tgz", - "integrity": "sha512-nSVgobk4rv61R9PUSDtYt7mPVB2olxNR5RWJcAsH676/ef11bUZwvu7+RGYrYauVdDPcO519v68wRhXQtxsV9w==", - "dev": true - }, - "async-limiter": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/async-limiter/-/async-limiter-1.0.1.tgz", - "integrity": "sha512-csOlWGAcRFJaI6m+F2WKdnMKr4HhdhFVBk0H/QbJFMCr+uO2kwohwXQPxw/9OCxp05r5ghVBFSyioixx3gfkNQ==", - "dev": true - }, - "asynckit": { - "version": "0.4.0", - "resolved": "https://registry.npmjs.org/asynckit/-/asynckit-0.4.0.tgz", - "integrity": "sha512-Oei9OH4tRh0YqU3GxhX79dM/mwVgvbZJaSNaRk+bshkj0S5cfHcgYakreBjrHwatXKbz+IoIdYLxrKim2MjW0Q==", - "dev": true - }, - "at-least-node": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/at-least-node/-/at-least-node-1.0.0.tgz", - "integrity": "sha512-+q/t7Ekv1EDY2l6Gda6LLiX14rU9TV20Wa3ofeQmwPFZbOMo9DXrLbOjFaaclkXKWidIaopwAObQDqwWtGUjqg==", - "dev": true - }, - "available-typed-arrays": { - "version": "1.0.5", - "resolved": "https://registry.npmjs.org/available-typed-arrays/-/available-typed-arrays-1.0.5.tgz", - "integrity": "sha512-DMD0KiN46eipeziST1LPP/STfDU0sufISXmjSgvVsoU2tqxctQeASejWcfNtxYKqETM1UxQ8sp2OrSBWpHY6sw==", - "dev": true - }, - "aws-sign2": { - "version": "0.7.0", - "resolved": "https://registry.npmjs.org/aws-sign2/-/aws-sign2-0.7.0.tgz", - "integrity": "sha512-08kcGqnYf/YmjoRhfxyu+CLxBjUtHLXLXX/vUfx9l2LYzG3c1m61nrpyFUZI6zeS+Li/wWMMidD9KgrqtGq3mA==", - "dev": true - }, - "aws4": { - "version": "1.12.0", - "resolved": "https://registry.npmjs.org/aws4/-/aws4-1.12.0.tgz", - "integrity": "sha512-NmWvPnx0F1SfrQbYwOi7OeaNGokp9XhzNioJ/CSBs8Qa4vxug81mhJEAVZwxXuBmYB5KDRfMq/F3RR0BIU7sWg==", - "dev": true - }, - "balanced-match": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/balanced-match/-/balanced-match-1.0.2.tgz", - "integrity": "sha512-3oSeUO0TMV67hN1AmbXsK4yaqU7tjiHlbxRDZOpH0KW9+CeX4bRAaX0Anxt0tx2MrpRpWwQaPwIlISEJhYU5Pw==", - "dev": true - }, - "base-x": { - "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==", - "requires": { - "safe-buffer": "^5.0.1" - } - }, - "base64-js": { - "version": "1.5.1", - "resolved": "https://registry.npmjs.org/base64-js/-/base64-js-1.5.1.tgz", - "integrity": "sha512-AKpaYlHn8t4SVbOHCy+b5+KKgvR4vrsD8vbvrbiQJps7fKDTkjkDry6ji0rUJjC0kzbNePLwzxq8iypo41qeWA==" - }, - "bcrypt-pbkdf": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/bcrypt-pbkdf/-/bcrypt-pbkdf-1.0.2.tgz", - "integrity": "sha512-qeFIXtP4MSoi6NLqO12WfqARWWuCKi2Rn/9hJLEmtB5yTNr9DqFWkJRCf2qShWzPeAMRnOgCrq0sg/KLv5ES9w==", - "dev": true, - "requires": { - "tweetnacl": "^0.14.3" - }, - "dependencies": { - "tweetnacl": { - "version": "0.14.5", - "resolved": "https://registry.npmjs.org/tweetnacl/-/tweetnacl-0.14.5.tgz", - "integrity": "sha512-KXXFFdAbFXY4geFIwoyNK+f5Z1b7swfXABfL7HXCmoIWMKU3dmS26672A4EeQtDzLKy7SXmfBu51JolvEKwtGA==", - "dev": true - } - } - }, - "bech32": { - "version": "1.1.4", - "resolved": "https://registry.npmjs.org/bech32/-/bech32-1.1.4.tgz", - "integrity": "sha512-s0IrSOzLlbvX7yp4WBfPITzpAU8sqQcpsmwXDiKwrG4r491vwCO/XpejasRNl0piBMe/DvP4Tz0mIS/X1DPJBQ==" - }, - "bigint-crypto-utils": { - "version": "3.3.0", - "resolved": "https://registry.npmjs.org/bigint-crypto-utils/-/bigint-crypto-utils-3.3.0.tgz", - "integrity": "sha512-jOTSb+drvEDxEq6OuUybOAv/xxoh3cuYRUIPyu8sSHQNKM303UQ2R1DAo45o1AkcIXw6fzbaFI1+xGGdaXs2lg==", - "dev": true - }, - "bignumber.js": { - "version": "9.1.1", - "resolved": "https://registry.npmjs.org/bignumber.js/-/bignumber.js-9.1.1.tgz", - "integrity": "sha512-pHm4LsMJ6lzgNGVfZHjMoO8sdoRhOzOH4MLmY65Jg70bpxCKu5iOHNJyfF6OyvYw7t8Fpf35RuzUyqnQsj8Vig==" - }, - "binary-extensions": { - "version": "2.2.0", - "resolved": "https://registry.npmjs.org/binary-extensions/-/binary-extensions-2.2.0.tgz", - "integrity": "sha512-jDctJ/IVQbZoJykoeHbhXpOlNBqGNcwXJKJog42E5HDPUwQTSdjCHdihjj0DlnheQ7blbT6dHOafNAiS8ooQKA==", - "dev": true - }, - "blakejs": { - "version": "1.2.1", - "resolved": "https://registry.npmjs.org/blakejs/-/blakejs-1.2.1.tgz", - "integrity": "sha512-QXUSXI3QVc/gJME0dBpXrag1kbzOqCjCX8/b54ntNyW6sjtoqxqRk3LTmXzaJoh71zMsDCjM+47jS7XiwN/+fQ==" - }, - "bluebird": { - "version": "3.7.2", - "resolved": "https://registry.npmjs.org/bluebird/-/bluebird-3.7.2.tgz", - "integrity": "sha512-XpNj6GDQzdfW+r2Wnn7xiSAd7TM3jzkxGXBGTtWKuSXv1xUV+azxAm8jdWZN06QTQk+2N2XB9jRDkvbmQmcRtg==", - "dev": true - }, - "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==" - }, - "body-parser": { - "version": "1.20.2", - "resolved": "https://registry.npmjs.org/body-parser/-/body-parser-1.20.2.tgz", - "integrity": "sha512-ml9pReCu3M61kGlqoTm2umSXTlRTuGTx0bfYj+uIUKKYycG5NtSbeetV3faSU6R7ajOPw0g/J1PvK4qNy7s5bA==", - "dev": true, - "requires": { - "bytes": "3.1.2", - "content-type": "~1.0.5", - "debug": "2.6.9", - "depd": "2.0.0", - "destroy": "1.2.0", - "http-errors": "2.0.0", - "iconv-lite": "0.4.24", - "on-finished": "2.4.1", - "qs": "6.11.0", - "raw-body": "2.5.2", - "type-is": "~1.6.18", - "unpipe": "1.0.0" - }, - "dependencies": { - "debug": { - "version": "2.6.9", - "resolved": "https://registry.npmjs.org/debug/-/debug-2.6.9.tgz", - "integrity": "sha512-bC7ElrdJaJnPbAP+1EotYvqZsb3ecl5wi6Bfi6BJTUcNowp6cvspg0jXznRTKDjm/E7AdgFBVeAPVMNcKGsHMA==", - "dev": true, - "requires": { - "ms": "2.0.0" - } - }, - "ms": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/ms/-/ms-2.0.0.tgz", - "integrity": "sha512-Tpp60P6IUJDTuOq/5Z8cdskzJujfwqfOTkrwIwj7IRISpnkJnT6SyJ4PCPnGMoFjC9ddhal5KVIYtAt97ix05A==", - "dev": true - }, - "qs": { - "version": "6.11.0", - "resolved": "https://registry.npmjs.org/qs/-/qs-6.11.0.tgz", - "integrity": "sha512-MvjoMCJwEarSbUYk5O+nmoSzSutSsTwF85zcHPQ9OrlFoZOYIjaqBAJIqIXjptyD5vThxGq52Xu/MaJzRkIk4Q==", - "dev": true, - "requires": { - "side-channel": "^1.0.4" - } - } - } - }, - "brace-expansion": { - "version": "1.1.11", - "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.11.tgz", - "integrity": "sha512-iCuPHDFgrHX7H2vEI/5xpz07zSHB00TpugqhmYtVmMO6518mCuRMoOYFldEBl0g187ufozdaHgWKcYFb61qGiA==", - "dev": true, - "requires": { - "balanced-match": "^1.0.0", - "concat-map": "0.0.1" - } - }, - "braces": { - "version": "3.0.2", - "resolved": "https://registry.npmjs.org/braces/-/braces-3.0.2.tgz", - "integrity": "sha512-b8um+L1RzM3WDSzvhm6gIz1yfTbBt6YTlcEKAvsmqCZZFw46z626lVj9j1yEPW33H5H+lBQpZMP1k8l+78Ha0A==", - "dev": true, - "requires": { - "fill-range": "^7.0.1" - } - }, - "brorand": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/brorand/-/brorand-1.1.0.tgz", - "integrity": "sha512-cKV8tMCEpQs4hK/ik71d6LrPOnpkpGBR0wzxqr68g2m/LB2GxVYQroAjMJZRVM1Y4BCjCKc3vAamxSzOY2RP+w==" - }, - "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, - "requires": { - "abstract-level": "^1.0.2", - "catering": "^2.1.1", - "module-error": "^1.0.2", - "run-parallel-limit": "^1.1.0" - } - }, - "browser-stdout": { - "version": "1.3.1", - "resolved": "https://registry.npmjs.org/browser-stdout/-/browser-stdout-1.3.1.tgz", - "integrity": "sha512-qhAVI1+Av2X7qelOfAIYwXONood6XlZE/fXaBSmW/T5SzLAmCgzi+eiWE7fUvbHaeNBQH13UftjpXxsfLkMpgw==", - "dev": true - }, - "browserify-aes": { - "version": "1.2.0", - "resolved": "https://registry.npmjs.org/browserify-aes/-/browserify-aes-1.2.0.tgz", - "integrity": "sha512-+7CHXqGuspUn/Sl5aO7Ea0xWGAtETPXNSAjHo48JfLdPWcMng33Xe4znFvQweqc/uzk5zSOI3H52CYnjCfb5hA==", - "requires": { - "buffer-xor": "^1.0.3", - "cipher-base": "^1.0.0", - "create-hash": "^1.1.0", - "evp_bytestokey": "^1.0.3", - "inherits": "^2.0.1", - "safe-buffer": "^5.0.1" - } - }, - "browserify-cipher": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/browserify-cipher/-/browserify-cipher-1.0.1.tgz", - "integrity": "sha512-sPhkz0ARKbf4rRQt2hTpAHqn47X3llLkUGn+xEJzLjwY8LRs2p0v7ljvI5EyoRO/mexrNunNECisZs+gw2zz1w==", - "dev": true, - "requires": { - "browserify-aes": "^1.0.4", - "browserify-des": "^1.0.0", - "evp_bytestokey": "^1.0.0" - } - }, - "browserify-des": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/browserify-des/-/browserify-des-1.0.2.tgz", - "integrity": "sha512-BioO1xf3hFwz4kc6iBhI3ieDFompMhrMlnDFC4/0/vd5MokpuAc3R+LYbwTA9A5Yc9pq9UYPqffKpW2ObuwX5A==", - "dev": true, - "requires": { - "cipher-base": "^1.0.1", - "des.js": "^1.0.0", - "inherits": "^2.0.1", - "safe-buffer": "^5.1.2" - } - }, - "browserify-rsa": { - "version": "4.1.0", - "resolved": "https://registry.npmjs.org/browserify-rsa/-/browserify-rsa-4.1.0.tgz", - "integrity": "sha512-AdEER0Hkspgno2aR97SAf6vi0y0k8NuOpGnVH3O99rcA5Q6sh8QxcngtHuJ6uXwnfAXNM4Gn1Gb7/MV1+Ymbog==", - "dev": true, - "requires": { - "bn.js": "^5.0.0", - "randombytes": "^2.0.1" - } - }, - "browserify-sign": { - "version": "4.2.2", - "resolved": "https://registry.npmjs.org/browserify-sign/-/browserify-sign-4.2.2.tgz", - "integrity": "sha512-1rudGyeYY42Dk6texmv7c4VcQ0EsvVbLwZkA+AQB7SxvXxmcD93jcHie8bzecJ+ChDlmAm2Qyu0+Ccg5uhZXCg==", - "dev": true, - "requires": { - "bn.js": "^5.2.1", - "browserify-rsa": "^4.1.0", - "create-hash": "^1.2.0", - "create-hmac": "^1.1.7", - "elliptic": "^6.5.4", - "inherits": "^2.0.4", - "parse-asn1": "^5.1.6", - "readable-stream": "^3.6.2", - "safe-buffer": "^5.2.1" - }, - "dependencies": { - "safe-buffer": { - "version": "5.2.1", - "resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.2.1.tgz", - "integrity": "sha512-rp3So07KcdmmKbGvgaNxQSJr7bGVSVk5S9Eq1F+ppbRo70+YeaDxkw5Dd8NPN+GD6bjnYm2VuPuCXmpuYvmCXQ==", - "dev": true - } - } - }, - "bs58": { - "version": "4.0.1", - "resolved": "https://registry.npmjs.org/bs58/-/bs58-4.0.1.tgz", - "integrity": "sha512-Ok3Wdf5vOIlBrgCvTq96gBkJw+JUEzdBgyaza5HLtPm7yTHkjRy8+JzNyHF7BHa0bNWOQIp3m5YF0nnFcOIKLw==", - "requires": { - "base-x": "^3.0.2" - } - }, - "bs58check": { - "version": "2.1.2", - "resolved": "https://registry.npmjs.org/bs58check/-/bs58check-2.1.2.tgz", - "integrity": "sha512-0TS1jicxdU09dwJMNZtVAfzPi6Q6QeN0pM1Fkzrjn+XYHvzMKPU3pHVpva+769iNVSfIYWf7LJ6WR+BuuMf8cA==", - "requires": { - "bs58": "^4.0.0", - "create-hash": "^1.1.0", - "safe-buffer": "^5.1.2" - } - }, - "buffer": { - "version": "6.0.3", - "resolved": "https://registry.npmjs.org/buffer/-/buffer-6.0.3.tgz", - "integrity": "sha512-FTiCpNxtwiZZHEZbcbTIcZjERVICn9yq/pDFkTl95/AxzD1naBctN7YO68riM/gLSDY7sdrMby8hofADYuuqOA==", - "requires": { - "base64-js": "^1.3.1", - "ieee754": "^1.2.1" - } - }, - "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==", - "dev": true - }, - "buffer-reverse": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/buffer-reverse/-/buffer-reverse-1.0.1.tgz", - "integrity": "sha512-M87YIUBsZ6N924W57vDwT/aOu8hw7ZgdByz6ijksLjmHJELBASmYTTlNHRgjE+pTsT9oJXGaDSgqqwfdHotDUg==" - }, - "buffer-to-arraybuffer": { - "version": "0.0.5", - "resolved": "https://registry.npmjs.org/buffer-to-arraybuffer/-/buffer-to-arraybuffer-0.0.5.tgz", - "integrity": "sha512-3dthu5CYiVB1DEJp61FtApNnNndTckcqe4pFcLdvHtrpG+kcyekCJKg4MRiDcFW7A6AODnXB9U4dwQiCW5kzJQ==", - "dev": true - }, - "buffer-xor": { - "version": "1.0.3", - "resolved": "https://registry.npmjs.org/buffer-xor/-/buffer-xor-1.0.3.tgz", - "integrity": "sha512-571s0T7nZWK6vB67HI5dyUF7wXiNcfaPPPTl6zYCNApANjIvYJTg7hlud/+cJpdAhS7dVzqMLmfhfHR3rAcOjQ==" - }, - "bufferutil": { - "version": "4.0.7", - "resolved": "https://registry.npmjs.org/bufferutil/-/bufferutil-4.0.7.tgz", - "integrity": "sha512-kukuqc39WOHtdxtw4UScxF/WVnMFVSQVKhtx3AjZJzhd0RGZZldcrfSEbVsWWe6KNH253574cq5F+wpv0G9pJw==", - "devOptional": true, - "requires": { - "node-gyp-build": "^4.3.0" - } - }, - "bufio": { - "version": "1.2.0", - "resolved": "https://registry.npmjs.org/bufio/-/bufio-1.2.0.tgz", - "integrity": "sha512-UlFk8z/PwdhYQTXSQQagwGAdtRI83gib2n4uy4rQnenxUM2yQi8lBDzF230BNk+3wAoZDxYRoBwVVUPgHa9MCA==" - }, - "bytes": { - "version": "3.1.2", - "resolved": "https://registry.npmjs.org/bytes/-/bytes-3.1.2.tgz", - "integrity": "sha512-/Nf7TyzTx6S3yRJObOAV7956r8cr2+Oj8AC5dt8wSP3BQAoeX58NoHyCU8P8zGkNXStjTSi6fzO6F0pBdcYbEg==", - "dev": true - }, - "cacheable-lookup": { - "version": "6.1.0", - "resolved": "https://registry.npmjs.org/cacheable-lookup/-/cacheable-lookup-6.1.0.tgz", - "integrity": "sha512-KJ/Dmo1lDDhmW2XDPMo+9oiy/CeqosPguPCrgcVzKyZrL6pM1gU2GmPY/xo6OQPTUaA/c0kwHuywB4E6nmT9ww==", - "dev": true - }, - "cacheable-request": { - "version": "6.1.0", - "resolved": "https://registry.npmjs.org/cacheable-request/-/cacheable-request-6.1.0.tgz", - "integrity": "sha512-Oj3cAGPCqOZX7Rz64Uny2GYAZNliQSqfbePrgAQ1wKAihYmCUnraBtJtKcGR4xz7wF+LoJC+ssFZvv5BgF9Igg==", - "dev": true, - "requires": { - "clone-response": "^1.0.2", - "get-stream": "^5.1.0", - "http-cache-semantics": "^4.0.0", - "keyv": "^3.0.0", - "lowercase-keys": "^2.0.0", - "normalize-url": "^4.1.0", - "responselike": "^1.0.2" - }, - "dependencies": { - "get-stream": { - "version": "5.2.0", - "resolved": "https://registry.npmjs.org/get-stream/-/get-stream-5.2.0.tgz", - "integrity": "sha512-nBF+F1rAZVCu/p7rjzgA+Yb4lfYXrpl7a6VmJrU8wF9I1CKvP/QwPNZHnOlwbTkY6dvtFIzFMSyQXbLoTQPRpA==", - "dev": true, - "requires": { - "pump": "^3.0.0" - } - }, - "lowercase-keys": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/lowercase-keys/-/lowercase-keys-2.0.0.tgz", - "integrity": "sha512-tqNXrS78oMOE73NMxK4EMLQsQowWf8jKooH9g7xPavRT706R6bkQJ6DY2Te7QukaZsulxa30wQ7bk0pm4XiHmA==", - "dev": true - } - } - }, - "call-bind": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/call-bind/-/call-bind-1.0.2.tgz", - "integrity": "sha512-7O+FbCihrB5WGbFYesctwmTKae6rOiIzmz1icreWJ+0aA7LJfuqhEso2T9ncpcFtzMQtzXf2QGGueWJGTYsqrA==", - "dev": true, - "requires": { - "function-bind": "^1.1.1", - "get-intrinsic": "^1.0.2" - } - }, - "callsites": { - "version": "3.1.0", - "resolved": "https://registry.npmjs.org/callsites/-/callsites-3.1.0.tgz", - "integrity": "sha512-P8BjAsXvZS+VIDUI11hHCQEv74YT67YUi5JJFNWIqL235sBmjX4+qx9Muvls5ivyNENctx46xQLQ3aTuE7ssaQ==", - "dev": true - }, - "camelcase": { - "version": "6.3.0", - "resolved": "https://registry.npmjs.org/camelcase/-/camelcase-6.3.0.tgz", - "integrity": "sha512-Gmy6FhYlCY7uOElZUSbxo2UCDH8owEk996gkbrpsgGtrJLM3J7jGxl9Ic7Qwwj4ivOE5AWZWRMecDdF7hqGjFA==", - "dev": true - }, - "case": { - "version": "1.6.3", - "resolved": "https://registry.npmjs.org/case/-/case-1.6.3.tgz", - "integrity": "sha512-mzDSXIPaFwVDvZAHqZ9VlbyF4yyXRuX6IvB06WvPYkqJVO24kX1PPhv9bfpKNFZyxYFmmgo03HUiD8iklmJYRQ==", - "dev": true - }, - "caseless": { - "version": "0.12.0", - "resolved": "https://registry.npmjs.org/caseless/-/caseless-0.12.0.tgz", - "integrity": "sha512-4tYFyifaFfGacoiObjJegolkwSU4xQNGbVgUiNYVUxbQ2x2lUsFvY4hVgVzGiIe6WLOPqycWXA40l+PWsxthUw==", - "dev": true - }, - "catering": { - "version": "2.1.1", - "resolved": "https://registry.npmjs.org/catering/-/catering-2.1.1.tgz", - "integrity": "sha512-K7Qy8O9p76sL3/3m7/zLKbRkyOlSZAgzEaLhyj2mXS8PsCud2Eo4hAb8aLtZqHh0QGqLcb9dlJSu6lHRVENm1w==", - "dev": true - }, - "cbor": { - "version": "8.1.0", - "resolved": "https://registry.npmjs.org/cbor/-/cbor-8.1.0.tgz", - "integrity": "sha512-DwGjNW9omn6EwP70aXsn7FQJx5kO12tX0bZkaTjzdVFM6/7nhA4t0EENocKGx6D2Bch9PE2KzCUf5SceBdeijg==", - "dev": true, - "requires": { - "nofilter": "^3.1.0" - } - }, - "chai": { - "version": "4.4.1", - "resolved": "https://registry.npmjs.org/chai/-/chai-4.4.1.tgz", - "integrity": "sha512-13sOfMv2+DWduEU+/xbun3LScLoqN17nBeTLUsmDfKdoiC1fr0n9PU4guu4AhRcOVFk/sW8LyZWHuhWtQZiF+g==", - "requires": { - "assertion-error": "^1.1.0", - "check-error": "^1.0.3", - "deep-eql": "^4.1.3", - "get-func-name": "^2.0.2", - "loupe": "^2.3.6", - "pathval": "^1.1.1", - "type-detect": "^4.0.8" - } - }, - "chalk": { - "version": "4.1.2", - "resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.2.tgz", - "integrity": "sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA==", - "requires": { - "ansi-styles": "^4.1.0", - "supports-color": "^7.1.0" - } - }, - "charenc": { - "version": "0.0.2", - "resolved": "https://registry.npmjs.org/charenc/-/charenc-0.0.2.tgz", - "integrity": "sha512-yrLQ/yVUFXkzg7EDQsPieE/53+0RlaWTs+wBrvW36cyilJ2SaDWfl4Yj7MtLTXleV9uEKefbAGUPv2/iWSooRA==", - "dev": true - }, - "check-error": { - "version": "1.0.3", - "resolved": "https://registry.npmjs.org/check-error/-/check-error-1.0.3.tgz", - "integrity": "sha512-iKEoDYaRmd1mxM90a2OEfWhjsjPpYPuQ+lMYsoxB126+t8fw7ySEO48nmDg5COTjxDI65/Y2OWpeEHk3ZOe8zg==", - "requires": { - "get-func-name": "^2.0.2" - } - }, - "chokidar": { - "version": "3.5.3", - "resolved": "https://registry.npmjs.org/chokidar/-/chokidar-3.5.3.tgz", - "integrity": "sha512-Dr3sfKRP6oTcjf2JmUmFJfeVMvXBdegxB0iVQ5eb2V10uFJUCAS8OByZdVAyVb8xXNz3GjjTgj9kLWsZTqE6kw==", - "dev": true, - "requires": { - "anymatch": "~3.1.2", - "braces": "~3.0.2", - "fsevents": "~2.3.2", - "glob-parent": "~5.1.2", - "is-binary-path": "~2.1.0", - "is-glob": "~4.0.1", - "normalize-path": "~3.0.0", - "readdirp": "~3.6.0" - } - }, - "chownr": { - "version": "1.1.4", - "resolved": "https://registry.npmjs.org/chownr/-/chownr-1.1.4.tgz", - "integrity": "sha512-jJ0bqzaylmJtVnNgzTeSOs8DPavpbYgEr/b0YL8/2GO3xJEhInFmhKMUnEJQjZumK7KXGFhUy89PrsJWlakBVg==", - "dev": true - }, - "ci-info": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/ci-info/-/ci-info-2.0.0.tgz", - "integrity": "sha512-5tK7EtrZ0N+OLFMthtqOj4fI2Jeb88C4CAZPu25LDVUgXJ0A3Js4PMGqrn0JU1W0Mh1/Z8wZzYPxqUrXeBboCQ==", - "dev": true - }, - "cids": { - "version": "0.7.5", - "resolved": "https://registry.npmjs.org/cids/-/cids-0.7.5.tgz", - "integrity": "sha512-zT7mPeghoWAu+ppn8+BS1tQ5qGmbMfB4AregnQjA/qHY3GC1m1ptI9GkWNlgeu38r7CuRdXB47uY2XgAYt6QVA==", - "dev": true, - "requires": { - "buffer": "^5.5.0", - "class-is": "^1.1.0", - "multibase": "~0.6.0", - "multicodec": "^1.0.0", - "multihashes": "~0.4.15" - }, - "dependencies": { - "buffer": { - "version": "5.7.1", - "resolved": "https://registry.npmjs.org/buffer/-/buffer-5.7.1.tgz", - "integrity": "sha512-EHcyIPBQ4BSGlvjB16k5KgAJ27CIsHY/2JBmCRReo48y9rQ3MaUzWX3KVlBa4U7MyX02HdVj0K7C3WaB3ju7FQ==", - "dev": true, - "requires": { - "base64-js": "^1.3.1", - "ieee754": "^1.1.13" - } - }, - "multicodec": { - "version": "1.0.4", - "resolved": "https://registry.npmjs.org/multicodec/-/multicodec-1.0.4.tgz", - "integrity": "sha512-NDd7FeS3QamVtbgfvu5h7fd1IlbaC4EQ0/pgU4zqE2vdHCmBGsUa0TiM8/TdSeG6BMPC92OOCf8F1ocE/Wkrrg==", - "dev": true, - "requires": { - "buffer": "^5.6.0", - "varint": "^5.0.0" - } - } - } - }, - "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==", - "requires": { - "inherits": "^2.0.1", - "safe-buffer": "^5.0.1" - } - }, - "class-is": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/class-is/-/class-is-1.1.0.tgz", - "integrity": "sha512-rhjH9AG1fvabIDoGRVH587413LPjTZgmDF9fOFCbFJQV4yuocX1mHxxvXI4g3cGwbVY9wAYIoKlg1N79frJKQw==", - "dev": true - }, - "classic-level": { - "version": "1.3.0", - "resolved": "https://registry.npmjs.org/classic-level/-/classic-level-1.3.0.tgz", - "integrity": "sha512-iwFAJQYtqRTRM0F6L8h4JCt00ZSGdOyqh7yVrhhjrOpFhmBjNlRUey64MCiyo6UmQHMJ+No3c81nujPv+n9yrg==", - "dev": true, - "requires": { - "abstract-level": "^1.0.2", - "catering": "^2.1.0", - "module-error": "^1.0.1", - "napi-macros": "^2.2.2", - "node-gyp-build": "^4.3.0" - } - }, - "clean-stack": { - "version": "2.2.0", - "resolved": "https://registry.npmjs.org/clean-stack/-/clean-stack-2.2.0.tgz", - "integrity": "sha512-4diC9HaTE+KRAMWhDhrGOECgWZxoevMc5TlkObMqNSsVU62PYzXZ/SMTjzyGAFF1YusgxGcSWTEXBhp0CPwQ1A==", - "dev": true - }, - "cli-table3": { - "version": "0.5.1", - "resolved": "https://registry.npmjs.org/cli-table3/-/cli-table3-0.5.1.tgz", - "integrity": "sha512-7Qg2Jrep1S/+Q3EceiZtQcDPWxhAvBw+ERf1162v4sikJrvojMHFqXt8QIVha8UlH9rgU0BeWPytZ9/TzYqlUw==", - "dev": true, - "requires": { - "colors": "^1.1.2", - "object-assign": "^4.1.0", - "string-width": "^2.1.1" - } - }, - "cliui": { - "version": "7.0.4", - "resolved": "https://registry.npmjs.org/cliui/-/cliui-7.0.4.tgz", - "integrity": "sha512-OcRE68cOsVMXp1Yvonl/fzkQOyjLSu/8bhPDfQt0e0/Eb283TKP20Fs2MqoPsr9SwA595rRCA+QMzYc9nBP+JQ==", - "dev": true, - "requires": { - "string-width": "^4.2.0", - "strip-ansi": "^6.0.0", - "wrap-ansi": "^7.0.0" - }, - "dependencies": { - "is-fullwidth-code-point": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/is-fullwidth-code-point/-/is-fullwidth-code-point-3.0.0.tgz", - "integrity": "sha512-zymm5+u+sCsSWyD9qNaejV3DFvhCKclKdizYaJUuHA83RLjb7nSuGnddCHGv0hk+KY7BMAlsWeK4Ueg6EV6XQg==", - "dev": true - }, - "string-width": { - "version": "4.2.3", - "resolved": "https://registry.npmjs.org/string-width/-/string-width-4.2.3.tgz", - "integrity": "sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g==", - "dev": true, - "requires": { - "emoji-regex": "^8.0.0", - "is-fullwidth-code-point": "^3.0.0", - "strip-ansi": "^6.0.1" - } - } - } - }, - "clone-response": { - "version": "1.0.3", - "resolved": "https://registry.npmjs.org/clone-response/-/clone-response-1.0.3.tgz", - "integrity": "sha512-ROoL94jJH2dUVML2Y/5PEDNaSHgeOdSDicUyS7izcF63G6sTc/FTjLub4b8Il9S8S0beOfYt0TaA5qvFK+w0wA==", - "dev": true, - "requires": { - "mimic-response": "^1.0.0" - } - }, - "code-point-at": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/code-point-at/-/code-point-at-1.1.0.tgz", - "integrity": "sha512-RpAVKQA5T63xEj6/giIbUEtZwJ4UFIc3ZtvEkiaUERylqe8xb5IvqcgOurZLahv93CLKfxcw5YI+DZcUBRyLXA==", - "dev": true - }, - "color-convert": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz", - "integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==", - "requires": { - "color-name": "~1.1.4" - } - }, - "color-name": { - "version": "1.1.4", - "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz", - "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==" - }, - "colors": { - "version": "1.4.0", - "resolved": "https://registry.npmjs.org/colors/-/colors-1.4.0.tgz", - "integrity": "sha512-a+UqTh4kgZg/SlGvfbzDHpgRu7AAQOmmqRHJnxhRZICKFUT91brVhNNt58CMWU9PsBbv3PDCZUHbVxuDiH2mtA==", - "dev": true - }, - "combined-stream": { - "version": "1.0.8", - "resolved": "https://registry.npmjs.org/combined-stream/-/combined-stream-1.0.8.tgz", - "integrity": "sha512-FQN4MRfuJeHf7cBbBMJFXhKSDq+2kAArBlmRBvcvFE5BB1HZKXtSFASDhdlz9zOYwxh8lDdnvmMOe/+5cdoEdg==", - "dev": true, - "requires": { - "delayed-stream": "~1.0.0" - } - }, - "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==", - "dev": true - }, - "command-line-args": { - "version": "4.0.7", - "resolved": "https://registry.npmjs.org/command-line-args/-/command-line-args-4.0.7.tgz", - "integrity": "sha512-aUdPvQRAyBvQd2n7jXcsMDz68ckBJELXNzBybCHOibUWEg0mWTnaYCSRU8h9R+aNRSvDihJtssSRCiDRpLaezA==", - "dev": true, - "requires": { - "array-back": "^2.0.0", - "find-replace": "^1.0.3", - "typical": "^2.6.1" - } - }, - "commander": { - "version": "3.0.2", - "resolved": "https://registry.npmjs.org/commander/-/commander-3.0.2.tgz", - "integrity": "sha512-Gar0ASD4BDyKC4hl4DwHqDrmvjoxWKZigVnAbn5H1owvm4CxCPdb0HQDehwNYMJpla5+M2tPmPARzhtYuwpHow==", - "dev": true - }, - "concat-map": { - "version": "0.0.1", - "resolved": "https://registry.npmjs.org/concat-map/-/concat-map-0.0.1.tgz", - "integrity": "sha512-/Srv4dswyQNBfohGpz9o6Yb3Gz3SrUDqBH5rTuhGR7ahtlbYKnVxw2bCFMRljaA7EXHaXZ8wsHdodFvbkhKmqg==", - "dev": true - }, - "concat-stream": { - "version": "1.6.2", - "resolved": "https://registry.npmjs.org/concat-stream/-/concat-stream-1.6.2.tgz", - "integrity": "sha512-27HBghJxjiZtIk3Ycvn/4kbJk/1uZuJFfuPEns6LaEvpvG1f0hTea8lilrouyo9mVc2GWdcEZ8OLoGmSADlrCw==", - "dev": true, - "requires": { - "buffer-from": "^1.0.0", - "inherits": "^2.0.3", - "readable-stream": "^2.2.2", - "typedarray": "^0.0.6" - }, - "dependencies": { - "isarray": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/isarray/-/isarray-1.0.0.tgz", - "integrity": "sha512-VLghIWNM6ELQzo7zwmcg0NmTVyWKYjvIeM83yjp0wRDTmUnrM678fQbcKBo6n2CJEF0szoG//ytg+TKla89ALQ==", - "dev": true - }, - "readable-stream": { - "version": "2.3.8", - "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-2.3.8.tgz", - "integrity": "sha512-8p0AUk4XODgIewSi0l8Epjs+EVnWiK7NoDIEGU0HhE7+ZyY8D1IMY7odu5lRrFXGg71L15KG8QrPmum45RTtdA==", - "dev": true, - "requires": { - "core-util-is": "~1.0.0", - "inherits": "~2.0.3", - "isarray": "~1.0.0", - "process-nextick-args": "~2.0.0", - "safe-buffer": "~5.1.1", - "string_decoder": "~1.1.1", - "util-deprecate": "~1.0.1" - } - }, - "string_decoder": { - "version": "1.1.1", - "resolved": "https://registry.npmjs.org/string_decoder/-/string_decoder-1.1.1.tgz", - "integrity": "sha512-n/ShnvDi6FHbbVfviro+WojiFzv+s8MPMHBczVePfUpDJLwoLT0ht1l4YwBCbi8pJAveEEdnkHyPyTP/mzRfwg==", - "dev": true, - "requires": { - "safe-buffer": "~5.1.0" - } - } - } - }, - "content-disposition": { - "version": "0.5.4", - "resolved": "https://registry.npmjs.org/content-disposition/-/content-disposition-0.5.4.tgz", - "integrity": "sha512-FveZTNuGw04cxlAiWbzi6zTAL/lhehaWbTtgluJh4/E95DqMwTmha3KZN1aAWA8cFIhHzMZUvLevkw5Rqk+tSQ==", - "dev": true, - "requires": { - "safe-buffer": "5.2.1" - }, - "dependencies": { - "safe-buffer": { - "version": "5.2.1", - "resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.2.1.tgz", - "integrity": "sha512-rp3So07KcdmmKbGvgaNxQSJr7bGVSVk5S9Eq1F+ppbRo70+YeaDxkw5Dd8NPN+GD6bjnYm2VuPuCXmpuYvmCXQ==", - "dev": true - } - } - }, - "content-hash": { - "version": "2.5.2", - "resolved": "https://registry.npmjs.org/content-hash/-/content-hash-2.5.2.tgz", - "integrity": "sha512-FvIQKy0S1JaWV10sMsA7TRx8bpU+pqPkhbsfvOJAdjRXvYxEckAwQWGwtRjiaJfh+E0DvcWUGqcdjwMGFjsSdw==", - "dev": true, - "requires": { - "cids": "^0.7.1", - "multicodec": "^0.5.5", - "multihashes": "^0.4.15" - } - }, - "content-type": { - "version": "1.0.5", - "resolved": "https://registry.npmjs.org/content-type/-/content-type-1.0.5.tgz", - "integrity": "sha512-nTjqfcBFEipKdXCv4YDQWCfmcLZKm81ldF0pAopTvyrFGVbcR6P/VAAd5G7N+0tTr8QqiU0tFadD6FK4NtJwOA==", - "dev": true - }, - "cookie": { - "version": "0.4.2", - "resolved": "https://registry.npmjs.org/cookie/-/cookie-0.4.2.tgz", - "integrity": "sha512-aSWTXFzaKWkvHO1Ny/s+ePFpvKsPnjc551iI41v3ny/ow6tBG5Vd+FuqGNhh1LxOmVzOlGUriIlOaokOvhaStA==", - "dev": true - }, - "cookie-signature": { - "version": "1.0.6", - "resolved": "https://registry.npmjs.org/cookie-signature/-/cookie-signature-1.0.6.tgz", - "integrity": "sha512-QADzlaHc8icV8I7vbaJXJwod9HWYp8uCqf1xa4OfNu1T7JVxQIrUgOWtHdNDtPiywmFbiS12VjotIXLrKM3orQ==", - "dev": true - }, - "cookiejar": { - "version": "2.1.4", - "resolved": "https://registry.npmjs.org/cookiejar/-/cookiejar-2.1.4.tgz", - "integrity": "sha512-LDx6oHrK+PhzLKJU9j5S7/Y3jM/mUHvD/DeI1WQmJn652iPC5Y4TBzC9l+5OMOXlyTTA+SmVUPm0HQUwpD5Jqw==", - "dev": true - }, - "core-util-is": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/core-util-is/-/core-util-is-1.0.2.tgz", - "integrity": "sha512-3lqz5YjWTYnW6dlDa5TLaTCcShfar1e40rmcJVwCBJC6mWlFuj0eCHIElmG1g5kyuJ/GD+8Wn4FFCcz4gJPfaQ==", - "dev": true - }, - "cors": { - "version": "2.8.5", - "resolved": "https://registry.npmjs.org/cors/-/cors-2.8.5.tgz", - "integrity": "sha512-KIHbLJqu73RGr/hnbrO9uBeixNGuvSQjul/jdFvS/KFSIH1hWVd1ng7zOHx+YrEfInLG7q4n6GHQ9cDtxv/P6g==", - "dev": true, - "requires": { - "object-assign": "^4", - "vary": "^1" - } - }, - "cosmiconfig": { - "version": "8.2.0", - "resolved": "https://registry.npmjs.org/cosmiconfig/-/cosmiconfig-8.2.0.tgz", - "integrity": "sha512-3rTMnFJA1tCOPwRxtgF4wd7Ab2qvDbL8jX+3smjIbS4HlZBagTlpERbdN7iAbWlrfxE3M8c27kTwTawQ7st+OQ==", - "dev": true, - "requires": { - "import-fresh": "^3.2.1", - "js-yaml": "^4.1.0", - "parse-json": "^5.0.0", - "path-type": "^4.0.0" - }, - "dependencies": { - "argparse": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/argparse/-/argparse-2.0.1.tgz", - "integrity": "sha512-8+9WqebbFzpX9OR+Wa6O29asIogeRMzcGtAINdpMHHyAg10f05aSFVBbcEqGf/PXw1EjAZ+q2/bEBg3DvurK3Q==", - "dev": true - }, - "js-yaml": { - "version": "4.1.0", - "resolved": "https://registry.npmjs.org/js-yaml/-/js-yaml-4.1.0.tgz", - "integrity": "sha512-wpxZs9NoxZaJESJGIZTyDEaYpl0FKSA+FB9aJiyemKhMwkxQg63h4T1KJgUGHpTqPDNRcmmYLugrRjJlBtWvRA==", - "dev": true, - "requires": { - "argparse": "^2.0.1" - } - } - } - }, - "crc-32": { - "version": "1.2.2", - "resolved": "https://registry.npmjs.org/crc-32/-/crc-32-1.2.2.tgz", - "integrity": "sha512-ROmzCKrTnOwybPcJApAA6WBWij23HVfGVNKqqrZpuyZOHqK2CwHSvpGuyt/UNNvaIjEd8X5IFGp4Mh+Ie1IHJQ==", - "dev": true - }, - "create-ecdh": { - "version": "4.0.4", - "resolved": "https://registry.npmjs.org/create-ecdh/-/create-ecdh-4.0.4.tgz", - "integrity": "sha512-mf+TCx8wWc9VpuxfP2ht0iSISLZnt0JgWlrOKZiNqyUZWnjIaCIVNQArMHnCZKfEYRg6IM7A+NeJoN8gf/Ws0A==", - "dev": true, - "requires": { - "bn.js": "^4.1.0", - "elliptic": "^6.5.3" - }, - "dependencies": { - "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==", - "dev": true - } - } - }, - "create-hash": { - "version": "1.2.0", - "resolved": "https://registry.npmjs.org/create-hash/-/create-hash-1.2.0.tgz", - "integrity": "sha512-z00bCGNHDG8mHAkP7CtT1qVu+bFQUPjYq/4Iv3C3kWjTFV10zIjfSoeqXo9Asws8gwSHDGj/hl2u4OGIjapeCg==", - "requires": { - "cipher-base": "^1.0.1", - "inherits": "^2.0.1", - "md5.js": "^1.3.4", - "ripemd160": "^2.0.1", - "sha.js": "^2.4.0" - } - }, - "create-hmac": { - "version": "1.1.7", - "resolved": "https://registry.npmjs.org/create-hmac/-/create-hmac-1.1.7.tgz", - "integrity": "sha512-MJG9liiZ+ogc4TzUwuvbER1JRdgvUFSB5+VR/g5h82fGaIRWMWddtKBHi7/sVhfjQZ6SehlyhvQYrcYkaUIpLg==", - "requires": { - "cipher-base": "^1.0.3", - "create-hash": "^1.1.0", - "inherits": "^2.0.1", - "ripemd160": "^2.0.0", - "safe-buffer": "^5.0.1", - "sha.js": "^2.4.8" - } - }, - "create-require": { - "version": "1.1.1", - "resolved": "https://registry.npmjs.org/create-require/-/create-require-1.1.1.tgz", - "integrity": "sha512-dcKFX3jn0MpIaXjisoRvexIJVEKzaq7z2rZKxf+MSr9TkdmHmsU4m2lcLojrj/FHl8mk5VxMmYA+ftRkP/3oKQ==", - "dev": true - }, - "cross-fetch": { - "version": "3.1.8", - "resolved": "https://registry.npmjs.org/cross-fetch/-/cross-fetch-3.1.8.tgz", - "integrity": "sha512-cvA+JwZoU0Xq+h6WkMvAUqPEYy92Obet6UdKLfW60qn99ftItKjB5T+BkyWOFWe2pUyfQ+IJHmpOTznqk1M6Kg==", - "dev": true, - "requires": { - "node-fetch": "^2.6.12" - } - }, - "cross-spawn": { - "version": "7.0.3", - "resolved": "https://registry.npmjs.org/cross-spawn/-/cross-spawn-7.0.3.tgz", - "integrity": "sha512-iRDPJKUPVEND7dHPO8rkbOnPpyDygcDFtWjpeWNCgy8WP2rXcxXL8TskReQl6OrB2G7+UJrags1q15Fudc7G6w==", - "dev": true, - "requires": { - "path-key": "^3.1.0", - "shebang-command": "^2.0.0", - "which": "^2.0.1" - } - }, - "crypt": { - "version": "0.0.2", - "resolved": "https://registry.npmjs.org/crypt/-/crypt-0.0.2.tgz", - "integrity": "sha512-mCxBlsHFYh9C+HVpiEacem8FEBnMXgU9gy4zmNC+SXAZNB/1idgp/aulFJ4FgCi7GPEVbfyng092GqL2k2rmow==", - "dev": true - }, - "crypto-browserify": { - "version": "3.12.0", - "resolved": "https://registry.npmjs.org/crypto-browserify/-/crypto-browserify-3.12.0.tgz", - "integrity": "sha512-fz4spIh+znjO2VjL+IdhEpRJ3YN6sMzITSBijk6FK2UvTqruSQW+/cCZTSNsMiZNvUeq0CqurF+dAbyiGOY6Wg==", - "dev": true, - "requires": { - "browserify-cipher": "^1.0.0", - "browserify-sign": "^4.0.0", - "create-ecdh": "^4.0.0", - "create-hash": "^1.1.0", - "create-hmac": "^1.1.0", - "diffie-hellman": "^5.0.0", - "inherits": "^2.0.1", - "pbkdf2": "^3.0.3", - "public-encrypt": "^4.0.0", - "randombytes": "^2.0.0", - "randomfill": "^1.0.3" - } - }, - "crypto-js": { - "version": "4.2.0", - "resolved": "https://registry.npmjs.org/crypto-js/-/crypto-js-4.2.0.tgz", - "integrity": "sha512-KALDyEYgpY+Rlob/iriUtjV6d5Eq+Y191A5g4UqLAi8CyGP9N1+FdVbkc1SxKc2r4YAYqG8JzO2KGL+AizD70Q==" - }, - "d": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/d/-/d-1.0.1.tgz", - "integrity": "sha512-m62ShEObQ39CfralilEQRjH6oAMtNCV1xJyEx5LpRYUVN+EviphDgUc/F3hnYbADmkiNs67Y+3ylmlG7Lnu+FA==", - "dev": true, - "requires": { - "es5-ext": "^0.10.50", - "type": "^1.0.1" - } - }, - "dashdash": { - "version": "1.14.1", - "resolved": "https://registry.npmjs.org/dashdash/-/dashdash-1.14.1.tgz", - "integrity": "sha512-jRFi8UDGo6j+odZiEpjazZaWqEal3w/basFjQHQEwVtZJGDpxbH1MeYluwCS8Xq5wmLJooDlMgvVarmWfGM44g==", - "dev": true, - "requires": { - "assert-plus": "^1.0.0" - } - }, - "death": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/death/-/death-1.1.0.tgz", - "integrity": "sha512-vsV6S4KVHvTGxbEcij7hkWRv0It+sGGWVOM67dQde/o5Xjnr+KmLjxWJii2uEObIrt1CcM9w0Yaovx+iOlIL+w==", - "dev": true - }, - "debug": { - "version": "4.3.4", - "resolved": "https://registry.npmjs.org/debug/-/debug-4.3.4.tgz", - "integrity": "sha512-PRWFHuSU3eDtQJPvnNY7Jcket1j0t5OuOsFzPPzsekD52Zl8qUfFIPEiswXqIvHWGVHOgX+7G/vCNNhehwxfkQ==", - "dev": true, - "requires": { - "ms": "2.1.2" - } - }, - "decamelize": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/decamelize/-/decamelize-4.0.0.tgz", - "integrity": "sha512-9iE1PgSik9HeIIw2JO94IidnE3eBoQrFJ3w7sFuzSX4DpmZ3v5sZpUiV5Swcf6mQEF+Y0ru8Neo+p+nyh2J+hQ==", - "dev": true - }, - "decode-uri-component": { - "version": "0.2.2", - "resolved": "https://registry.npmjs.org/decode-uri-component/-/decode-uri-component-0.2.2.tgz", - "integrity": "sha512-FqUYQ+8o158GyGTrMFJms9qh3CqTKvAqgqsTnkLI8sKu0028orqBhxNMFkFen0zGyg6epACD32pjVk58ngIErQ==", - "dev": true - }, - "decompress-response": { - "version": "3.3.0", - "resolved": "https://registry.npmjs.org/decompress-response/-/decompress-response-3.3.0.tgz", - "integrity": "sha512-BzRPQuY1ip+qDonAOz42gRm/pg9F768C+npV/4JOsxRC2sq+Rlk+Q4ZCAsOhnIaMrgarILY+RMUIvMmmX1qAEA==", - "dev": true, - "requires": { - "mimic-response": "^1.0.0" - } - }, - "deep-eql": { - "version": "4.1.3", - "resolved": "https://registry.npmjs.org/deep-eql/-/deep-eql-4.1.3.tgz", - "integrity": "sha512-WaEtAOpRA1MQ0eohqZjpGD8zdI0Ovsm8mmFhaDN8dvDZzyoUMcYDnf5Y6iu7HTXxf8JDS23qWa4a+hKCDyOPzw==", - "requires": { - "type-detect": "^4.0.0" - } - }, - "deep-is": { - "version": "0.1.4", - "resolved": "https://registry.npmjs.org/deep-is/-/deep-is-0.1.4.tgz", - "integrity": "sha512-oIPzksmTg4/MriiaYGO+okXDT7ztn/w3Eptv/+gSIdMdKsJo0u4CfYNFJPy+4SKMuCqGw2wxnA+URMg3t8a/bQ==", - "dev": true - }, - "defer-to-connect": { - "version": "1.1.3", - "resolved": "https://registry.npmjs.org/defer-to-connect/-/defer-to-connect-1.1.3.tgz", - "integrity": "sha512-0ISdNousHvZT2EiFlZeZAHBUvSxmKswVCEf8hW7KWgG4a8MVEu/3Vb6uWYozkjylyCxe0JBIiRB1jV45S70WVQ==", - "dev": true - }, - "define-properties": { - "version": "1.2.0", - "resolved": "https://registry.npmjs.org/define-properties/-/define-properties-1.2.0.tgz", - "integrity": "sha512-xvqAVKGfT1+UAvPwKTVw/njhdQ8ZhXK4lI0bCIuCMrp2up9nPnaDftrLtmpTazqd1o+UY4zgzU+avtMbDP+ldA==", - "dev": true, - "requires": { - "has-property-descriptors": "^1.0.0", - "object-keys": "^1.1.1" - } - }, - "delayed-stream": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/delayed-stream/-/delayed-stream-1.0.0.tgz", - "integrity": "sha512-ZySD7Nf91aLB0RxL4KGrKHBXl7Eds1DAmEdcoVawXnLD7SDhpNgtuII2aAkg7a7QS41jxPSZ17p4VdGnMHk3MQ==", - "dev": true - }, - "depd": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/depd/-/depd-2.0.0.tgz", - "integrity": "sha512-g7nH6P6dyDioJogAAGprGpCtVImJhpPk/roCzdb3fIh61/s/nPsfR6onyMwkCAR/OlC3yBC0lESvUoQEAssIrw==", - "dev": true - }, - "des.js": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/des.js/-/des.js-1.1.0.tgz", - "integrity": "sha512-r17GxjhUCjSRy8aiJpr8/UadFIzMzJGexI3Nmz4ADi9LYSFx4gTBp80+NaX/YsXWWLhpZ7v/v/ubEc/bCNfKwg==", - "dev": true, - "requires": { - "inherits": "^2.0.1", - "minimalistic-assert": "^1.0.0" - } - }, - "destroy": { - "version": "1.2.0", - "resolved": "https://registry.npmjs.org/destroy/-/destroy-1.2.0.tgz", - "integrity": "sha512-2sJGJTaXIIaR1w4iJSNoN0hnMY7Gpc/n8D4qSCJw8QqFWXf7cuAgnEHxBpweaVcPevC2l3KpjYCx3NypQQgaJg==", - "dev": true - }, - "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, - "requires": { - "address": "^1.0.1", - "debug": "4" - } - }, - "diff": { - "version": "5.0.0", - "resolved": "https://registry.npmjs.org/diff/-/diff-5.0.0.tgz", - "integrity": "sha512-/VTCrvm5Z0JGty/BWHljh+BAiw3IK+2j87NGMu8Nwc/f48WoDAC395uomO9ZD117ZOBaHmkX1oyLvkVM/aIT3w==", - "dev": true - }, - "diffie-hellman": { - "version": "5.0.3", - "resolved": "https://registry.npmjs.org/diffie-hellman/-/diffie-hellman-5.0.3.tgz", - "integrity": "sha512-kqag/Nl+f3GwyK25fhUMYj81BUOrZ9IuJsjIcDE5icNM9FJHAVm3VcUDxdLPoQtTuUylWm6ZIknYJwwaPxsUzg==", - "dev": true, - "requires": { - "bn.js": "^4.1.0", - "miller-rabin": "^4.0.0", - "randombytes": "^2.0.0" - }, - "dependencies": { - "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==", - "dev": true - } - } - }, - "dir-glob": { - "version": "3.0.1", - "resolved": "https://registry.npmjs.org/dir-glob/-/dir-glob-3.0.1.tgz", - "integrity": "sha512-WkrWp9GR4KXfKGYzOLmTuGVi1UWFfws377n9cc55/tb6DuqyF6pcQ5AbiHEshaDpY9v6oaSr2XCDidGmMwdzIA==", - "dev": true, - "requires": { - "path-type": "^4.0.0" - } - }, - "doctrine": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/doctrine/-/doctrine-3.0.0.tgz", - "integrity": "sha512-yS+Q5i3hBf7GBkd4KG8a7eBNNWNGLTaEwwYWUijIYM7zrlYDM0BFXHjjPWlWZ1Rg7UaddZeIDmi9jF3HmqiQ2w==", - "dev": true, - "requires": { - "esutils": "^2.0.2" - } - }, - "dom-walk": { - "version": "0.1.2", - "resolved": "https://registry.npmjs.org/dom-walk/-/dom-walk-0.1.2.tgz", - "integrity": "sha512-6QvTW9mrGeIegrFXdtQi9pk7O/nSK6lSdXW2eqUspN5LWD7UTji2Fqw5V2YLjBpHEoU9Xl/eUWNpDeZvoyOv2w==", - "dev": true - }, - "dotenv": { - "version": "10.0.0", - "resolved": "https://registry.npmjs.org/dotenv/-/dotenv-10.0.0.tgz", - "integrity": "sha512-rlBi9d8jpv9Sf1klPjNfFAuWDjKLwTIJJ/VxtoTwIR6hnZxcEOQCZg2oIL3MWBYw5GpUDKOEnND7LXTbIpQ03Q==", - "dev": true - }, - "duplexer3": { - "version": "0.1.5", - "resolved": "https://registry.npmjs.org/duplexer3/-/duplexer3-0.1.5.tgz", - "integrity": "sha512-1A8za6ws41LQgv9HrE/66jyC5yuSjQ3L/KOpFtoBilsAK2iA2wuS5rTt1OCzIvtS2V7nVmedsUU+DGRcjBmOYA==", - "dev": true - }, - "ecc-jsbn": { - "version": "0.1.2", - "resolved": "https://registry.npmjs.org/ecc-jsbn/-/ecc-jsbn-0.1.2.tgz", - "integrity": "sha512-eh9O+hwRHNbG4BLTjEl3nw044CkGm5X6LoaCf7LPp7UU8Qrt47JYNi6nPX8xjW97TKGKm1ouctg0QSpZe9qrnw==", - "dev": true, - "requires": { - "jsbn": "~0.1.0", - "safer-buffer": "^2.1.0" - } - }, - "ee-first": { - "version": "1.1.1", - "resolved": "https://registry.npmjs.org/ee-first/-/ee-first-1.1.1.tgz", - "integrity": "sha512-WMwm9LhRUo+WUaRN+vRuETqG89IgZphVSNkdFgeb6sS/E4OrDIN7t48CAewSHXc6C8lefD8KKfr5vY61brQlow==", - "dev": true - }, - "elliptic": { - "version": "6.5.4", - "resolved": "https://registry.npmjs.org/elliptic/-/elliptic-6.5.4.tgz", - "integrity": "sha512-iLhC6ULemrljPZb+QutR5TQGB+pdW6KGD5RSegS+8sorOZT+rdQFbsQFJgvN3eRqNALqJer4oQ16YvJHlU8hzQ==", - "requires": { - "bn.js": "^4.11.9", - "brorand": "^1.1.0", - "hash.js": "^1.0.0", - "hmac-drbg": "^1.0.1", - "inherits": "^2.0.4", - "minimalistic-assert": "^1.0.1", - "minimalistic-crypto-utils": "^1.0.1" - }, - "dependencies": { - "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==" - } - } - }, - "emoji-regex": { - "version": "8.0.0", - "resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-8.0.0.tgz", - "integrity": "sha512-MSjYzcWNOA0ewAHpz0MxpYFvwg6yjy1NG3xteoqz644VCo/RPgnr1/GGt+ic3iJTzQ8Eu3TdM14SawnVUmGE6A==", - "dev": true - }, - "encodeurl": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/encodeurl/-/encodeurl-1.0.2.tgz", - "integrity": "sha512-TPJXq8JqFaVYm2CWmPvnP2Iyo4ZSM7/QKcSmuMLDObfpH5fi7RUGmd/rTDf+rut/saiDiQEeVTNgAmJEdAOx0w==", - "dev": true - }, - "end-of-stream": { - "version": "1.4.4", - "resolved": "https://registry.npmjs.org/end-of-stream/-/end-of-stream-1.4.4.tgz", - "integrity": "sha512-+uw1inIHVPQoaVuHzRyXd21icM+cnt4CzD5rW+NC1wjOUSTOs+Te7FOv7AhN7vS9x/oIyhLP5PR1H+phQAHu5Q==", - "dev": true, - "requires": { - "once": "^1.4.0" - } - }, - "enquirer": { - "version": "2.3.6", - "resolved": "https://registry.npmjs.org/enquirer/-/enquirer-2.3.6.tgz", - "integrity": "sha512-yjNnPr315/FjS4zIsUxYguYUPP2e1NK4d7E7ZOLiyYCcbFBiTMyID+2wvm2w6+pZ/odMA7cRkjhsPbltwBOrLg==", - "dev": true, - "requires": { - "ansi-colors": "^4.1.1" - } - }, - "env-paths": { - "version": "2.2.1", - "resolved": "https://registry.npmjs.org/env-paths/-/env-paths-2.2.1.tgz", - "integrity": "sha512-+h1lkLKhZMTYjog1VEpJNG7NZJWcuc2DDk/qsqSTRRCOXiLjeQ1d1/udrUGhqMxUgAlwKNZ0cf2uqan5GLuS2A==", - "dev": true - }, - "error-ex": { - "version": "1.3.2", - "resolved": "https://registry.npmjs.org/error-ex/-/error-ex-1.3.2.tgz", - "integrity": "sha512-7dFHNmqeFSEt2ZBsCriorKnn3Z2pj+fd9kmI6QoWw4//DL+icEBfc0U7qJCisqrTsKTjw4fNFy2pW9OqStD84g==", - "dev": true, - "requires": { - "is-arrayish": "^0.2.1" - } - }, - "es-abstract": { - "version": "1.22.1", - "resolved": "https://registry.npmjs.org/es-abstract/-/es-abstract-1.22.1.tgz", - "integrity": "sha512-ioRRcXMO6OFyRpyzV3kE1IIBd4WG5/kltnzdxSCqoP8CMGs/Li+M1uF5o7lOkZVFjDs+NLesthnF66Pg/0q0Lw==", - "dev": true, - "requires": { - "array-buffer-byte-length": "^1.0.0", - "arraybuffer.prototype.slice": "^1.0.1", - "available-typed-arrays": "^1.0.5", - "call-bind": "^1.0.2", - "es-set-tostringtag": "^2.0.1", - "es-to-primitive": "^1.2.1", - "function.prototype.name": "^1.1.5", - "get-intrinsic": "^1.2.1", - "get-symbol-description": "^1.0.0", - "globalthis": "^1.0.3", - "gopd": "^1.0.1", - "has": "^1.0.3", - "has-property-descriptors": "^1.0.0", - "has-proto": "^1.0.1", - "has-symbols": "^1.0.3", - "internal-slot": "^1.0.5", - "is-array-buffer": "^3.0.2", - "is-callable": "^1.2.7", - "is-negative-zero": "^2.0.2", - "is-regex": "^1.1.4", - "is-shared-array-buffer": "^1.0.2", - "is-string": "^1.0.7", - "is-typed-array": "^1.1.10", - "is-weakref": "^1.0.2", - "object-inspect": "^1.12.3", - "object-keys": "^1.1.1", - "object.assign": "^4.1.4", - "regexp.prototype.flags": "^1.5.0", - "safe-array-concat": "^1.0.0", - "safe-regex-test": "^1.0.0", - "string.prototype.trim": "^1.2.7", - "string.prototype.trimend": "^1.0.6", - "string.prototype.trimstart": "^1.0.6", - "typed-array-buffer": "^1.0.0", - "typed-array-byte-length": "^1.0.0", - "typed-array-byte-offset": "^1.0.0", - "typed-array-length": "^1.0.4", - "unbox-primitive": "^1.0.2", - "which-typed-array": "^1.1.10" - } - }, - "es-array-method-boxes-properly": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/es-array-method-boxes-properly/-/es-array-method-boxes-properly-1.0.0.tgz", - "integrity": "sha512-wd6JXUmyHmt8T5a2xreUwKcGPq6f1f+WwIJkijUqiGcJz1qqnZgP6XIK+QyIWU5lT7imeNxUll48bziG+TSYcA==", - "dev": true - }, - "es-set-tostringtag": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/es-set-tostringtag/-/es-set-tostringtag-2.0.1.tgz", - "integrity": "sha512-g3OMbtlwY3QewlqAiMLI47KywjWZoEytKr8pf6iTC8uJq5bIAH52Z9pnQ8pVL6whrCto53JZDuUIsifGeLorTg==", - "dev": true, - "requires": { - "get-intrinsic": "^1.1.3", - "has": "^1.0.3", - "has-tostringtag": "^1.0.0" - } - }, - "es-shim-unscopables": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/es-shim-unscopables/-/es-shim-unscopables-1.0.0.tgz", - "integrity": "sha512-Jm6GPcCdC30eMLbZ2x8z2WuRwAws3zTBBKuusffYVUrNj/GVSUAZ+xKMaUpfNDR5IbyNA5LJbaecoUVbmUcB1w==", - "dev": true, - "requires": { - "has": "^1.0.3" - } - }, - "es-to-primitive": { - "version": "1.2.1", - "resolved": "https://registry.npmjs.org/es-to-primitive/-/es-to-primitive-1.2.1.tgz", - "integrity": "sha512-QCOllgZJtaUo9miYBcLChTUaHNjJF3PYs1VidD7AwiEj1kYxKeQTctLAezAOH5ZKRH0g2IgPn6KwB4IT8iRpvA==", - "dev": true, - "requires": { - "is-callable": "^1.1.4", - "is-date-object": "^1.0.1", - "is-symbol": "^1.0.2" - } - }, - "es5-ext": { - "version": "0.10.62", - "resolved": "https://registry.npmjs.org/es5-ext/-/es5-ext-0.10.62.tgz", - "integrity": "sha512-BHLqn0klhEpnOKSrzn/Xsz2UIW8j+cGmo9JLzr8BiUapV8hPL9+FliFqjwr9ngW7jWdnxv6eO+/LqyhJVqgrjA==", - "dev": true, - "requires": { - "es6-iterator": "^2.0.3", - "es6-symbol": "^3.1.3", - "next-tick": "^1.1.0" - } - }, - "es6-iterator": { - "version": "2.0.3", - "resolved": "https://registry.npmjs.org/es6-iterator/-/es6-iterator-2.0.3.tgz", - "integrity": "sha512-zw4SRzoUkd+cl+ZoE15A9o1oQd920Bb0iOJMQkQhl3jNc03YqVjAhG7scf9C5KWRU/R13Orf588uCC6525o02g==", - "dev": true, - "requires": { - "d": "1", - "es5-ext": "^0.10.35", - "es6-symbol": "^3.1.1" - } - }, - "es6-promise": { - "version": "4.2.8", - "resolved": "https://registry.npmjs.org/es6-promise/-/es6-promise-4.2.8.tgz", - "integrity": "sha512-HJDGx5daxeIvxdBxvG2cb9g4tEvwIk3i8+nhX0yGrYmZUzbkdg8QbDevheDB8gd0//uPj4c1EQua8Q+MViT0/w==", - "dev": true - }, - "es6-symbol": { - "version": "3.1.3", - "resolved": "https://registry.npmjs.org/es6-symbol/-/es6-symbol-3.1.3.tgz", - "integrity": "sha512-NJ6Yn3FuDinBaBRWl/q5X/s4koRHBrgKAu+yGI6JCBeiu3qrcbJhwT2GeR/EXVfylRk8dpQVJoLEFhK+Mu31NA==", - "dev": true, - "requires": { - "d": "^1.0.1", - "ext": "^1.1.2" - } - }, - "escalade": { - "version": "3.1.1", - "resolved": "https://registry.npmjs.org/escalade/-/escalade-3.1.1.tgz", - "integrity": "sha512-k0er2gUkLf8O0zKJiAhmkTnJlTvINGv7ygDNPbeIsX/TJjGJZHuh9B2UxbsaEkmlEo9MfhrSzmhIlhRlI2GXnw==", - "dev": true - }, - "escape-html": { - "version": "1.0.3", - "resolved": "https://registry.npmjs.org/escape-html/-/escape-html-1.0.3.tgz", - "integrity": "sha512-NiSupZ4OeuGwr68lGIeym/ksIZMJodUGOSCZ/FSnTxcrekbvqrgdUxlJOMpijaKZVjAJrWrGs/6Jy8OMuyj9ow==", - "dev": true - }, - "escape-string-regexp": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/escape-string-regexp/-/escape-string-regexp-4.0.0.tgz", - "integrity": "sha512-TtpcNJ3XAzx3Gq8sWRzJaVajRs0uVxA2YAkdb1jm2YkPz4G6egUFAyA3n5vtEIZefPk5Wa4UXbKuS5fKkJWdgA==", - "dev": true - }, - "escodegen": { - "version": "1.8.1", - "resolved": "https://registry.npmjs.org/escodegen/-/escodegen-1.8.1.tgz", - "integrity": "sha512-yhi5S+mNTOuRvyW4gWlg5W1byMaQGWWSYHXsuFZ7GBo7tpyOwi2EdzMP/QWxh9hwkD2m+wDVHJsxhRIj+v/b/A==", - "dev": true, - "requires": { - "esprima": "^2.7.1", - "estraverse": "^1.9.1", - "esutils": "^2.0.2", - "optionator": "^0.8.1", - "source-map": "~0.2.0" - }, - "dependencies": { - "esprima": { - "version": "2.7.3", - "resolved": "https://registry.npmjs.org/esprima/-/esprima-2.7.3.tgz", - "integrity": "sha512-OarPfz0lFCiW4/AV2Oy1Rp9qu0iusTKqykwTspGCZtPxmF81JR4MmIebvF1F9+UOKth2ZubLQ4XGGaU+hSn99A==", - "dev": true - }, - "estraverse": { - "version": "1.9.3", - "resolved": "https://registry.npmjs.org/estraverse/-/estraverse-1.9.3.tgz", - "integrity": "sha512-25w1fMXQrGdoquWnScXZGckOv+Wes+JDnuN/+7ex3SauFRS72r2lFDec0EKPt2YD1wUJ/IrfEex+9yp4hfSOJA==", - "dev": true - }, - "levn": { - "version": "0.3.0", - "resolved": "https://registry.npmjs.org/levn/-/levn-0.3.0.tgz", - "integrity": "sha512-0OO4y2iOHix2W6ujICbKIaEQXvFQHue65vUG3pb5EUomzPI90z9hsA1VsO/dbIIpC53J8gxM9Q4Oho0jrCM/yA==", - "dev": true, - "requires": { - "prelude-ls": "~1.1.2", - "type-check": "~0.3.2" - } - }, - "optionator": { - "version": "0.8.3", - "resolved": "https://registry.npmjs.org/optionator/-/optionator-0.8.3.tgz", - "integrity": "sha512-+IW9pACdk3XWmmTXG8m3upGUJst5XRGzxMRjXzAuJ1XnIFNvfhjjIuYkDvysnPQ7qzqVzLt78BCruntqRhWQbA==", - "dev": true, - "requires": { - "deep-is": "~0.1.3", - "fast-levenshtein": "~2.0.6", - "levn": "~0.3.0", - "prelude-ls": "~1.1.2", - "type-check": "~0.3.2", - "word-wrap": "~1.2.3" - } - }, - "prelude-ls": { - "version": "1.1.2", - "resolved": "https://registry.npmjs.org/prelude-ls/-/prelude-ls-1.1.2.tgz", - "integrity": "sha512-ESF23V4SKG6lVSGZgYNpbsiaAkdab6ZgOxe52p7+Kid3W3u3bxR4Vfd/o21dmN7jSt0IwgZ4v5MUd26FEtXE9w==", - "dev": true - }, - "type-check": { - "version": "0.3.2", - "resolved": "https://registry.npmjs.org/type-check/-/type-check-0.3.2.tgz", - "integrity": "sha512-ZCmOJdvOWDBYJlzAoFkC+Q0+bUyEOS1ltgp1MGU03fqHG+dbi9tBFU2Rd9QKiDZFAYrhPh2JUf7rZRIuHRKtOg==", - "dev": true, - "requires": { - "prelude-ls": "~1.1.2" - } - } - } - }, - "eslint": { - "version": "7.32.0", - "resolved": "https://registry.npmjs.org/eslint/-/eslint-7.32.0.tgz", - "integrity": "sha512-VHZ8gX+EDfz+97jGcgyGCyRia/dPOd6Xh9yPv8Bl1+SoaIwD+a/vlrOmGRUyOYu7MwUhc7CxqeaDZU13S4+EpA==", - "dev": true, - "requires": { - "@babel/code-frame": "7.12.11", - "@eslint/eslintrc": "^0.4.3", - "@humanwhocodes/config-array": "^0.5.0", - "ajv": "^6.10.0", - "chalk": "^4.0.0", - "cross-spawn": "^7.0.2", - "debug": "^4.0.1", - "doctrine": "^3.0.0", - "enquirer": "^2.3.5", - "escape-string-regexp": "^4.0.0", - "eslint-scope": "^5.1.1", - "eslint-utils": "^2.1.0", - "eslint-visitor-keys": "^2.0.0", - "espree": "^7.3.1", - "esquery": "^1.4.0", - "esutils": "^2.0.2", - "fast-deep-equal": "^3.1.3", - "file-entry-cache": "^6.0.1", - "functional-red-black-tree": "^1.0.1", - "glob-parent": "^5.1.2", - "globals": "^13.6.0", - "ignore": "^4.0.6", - "import-fresh": "^3.0.0", - "imurmurhash": "^0.1.4", - "is-glob": "^4.0.0", - "js-yaml": "^3.13.1", - "json-stable-stringify-without-jsonify": "^1.0.1", - "levn": "^0.4.1", - "lodash.merge": "^4.6.2", - "minimatch": "^3.0.4", - "natural-compare": "^1.4.0", - "optionator": "^0.9.1", - "progress": "^2.0.0", - "regexpp": "^3.1.0", - "semver": "^7.2.1", - "strip-ansi": "^6.0.0", - "strip-json-comments": "^3.1.0", - "table": "^6.0.9", - "text-table": "^0.2.0", - "v8-compile-cache": "^2.0.3" - }, - "dependencies": { - "eslint-utils": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/eslint-utils/-/eslint-utils-2.1.0.tgz", - "integrity": "sha512-w94dQYoauyvlDc43XnGB8lU3Zt713vNChgt4EWwhXAP2XkBvndfxF0AgIqKOOasjPIPzj9JqgwkwbCYD0/V3Zg==", - "dev": true, - "requires": { - "eslint-visitor-keys": "^1.1.0" - }, - "dependencies": { - "eslint-visitor-keys": { - "version": "1.3.0", - "resolved": "https://registry.npmjs.org/eslint-visitor-keys/-/eslint-visitor-keys-1.3.0.tgz", - "integrity": "sha512-6J72N8UNa462wa/KFODt/PJ3IU60SDpC3QXC1Hjc1BXXpfL2C9R5+AU7jhe0F6GREqVMh4Juu+NY7xn+6dipUQ==", - "dev": true - } - } - }, - "ignore": { - "version": "4.0.6", - "resolved": "https://registry.npmjs.org/ignore/-/ignore-4.0.6.tgz", - "integrity": "sha512-cyFDKrqc/YdcWFniJhzI42+AzS+gNwmUzOSFcRCQYwySuBBBy/KjuxWLZ/FHEH6Moq1NizMOBWyTcv8O4OZIMg==", - "dev": true - }, - "lru-cache": { - "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, - "requires": { - "yallist": "^4.0.0" - } - }, - "semver": { - "version": "7.5.4", - "resolved": "https://registry.npmjs.org/semver/-/semver-7.5.4.tgz", - "integrity": "sha512-1bCSESV6Pv+i21Hvpxp3Dx+pSD8lIPt8uVjRrxAUt/nbswYc+tK6Y2btiULjd4+fnq15PX+nqQDC7Oft7WkwcA==", - "dev": true, - "requires": { - "lru-cache": "^6.0.0" - } - }, - "yallist": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/yallist/-/yallist-4.0.0.tgz", - "integrity": "sha512-3wdGidZyq5PB084XLES5TpOSRA3wjXAlIWMhum2kRcv/41Sn2emQ0dycQW4uZXLejwKvg6EsvbdlVL+FYEct7A==", - "dev": true - } - } - }, - "eslint-config-prettier": { - "version": "8.8.0", - "resolved": "https://registry.npmjs.org/eslint-config-prettier/-/eslint-config-prettier-8.8.0.tgz", - "integrity": "sha512-wLbQiFre3tdGgpDv67NQKnJuTlcUVYHas3k+DZCc2U2BadthoEY4B7hLPvAxaqdyOGCzuLfii2fqGph10va7oA==", - "dev": true, - "requires": {} - }, - "eslint-config-standard": { - "version": "16.0.3", - "resolved": "https://registry.npmjs.org/eslint-config-standard/-/eslint-config-standard-16.0.3.tgz", - "integrity": "sha512-x4fmJL5hGqNJKGHSjnLdgA6U6h1YW/G2dW9fA+cyVur4SK6lyue8+UgNKWlZtUDTXvgKDD/Oa3GQjmB5kjtVvg==", - "dev": true, - "requires": {} - }, - "eslint-import-resolver-node": { - "version": "0.3.7", - "resolved": "https://registry.npmjs.org/eslint-import-resolver-node/-/eslint-import-resolver-node-0.3.7.tgz", - "integrity": "sha512-gozW2blMLJCeFpBwugLTGyvVjNoeo1knonXAcatC6bjPBZitotxdWf7Gimr25N4c0AAOo4eOUfaG82IJPDpqCA==", - "dev": true, - "requires": { - "debug": "^3.2.7", - "is-core-module": "^2.11.0", - "resolve": "^1.22.1" - }, - "dependencies": { - "debug": { - "version": "3.2.7", - "resolved": "https://registry.npmjs.org/debug/-/debug-3.2.7.tgz", - "integrity": "sha512-CFjzYYAi4ThfiQvizrFQevTTXHtnCqWfe7x1AhgEscTz6ZbLbfoLRLPugTQyBth6f8ZERVUSyWHFD/7Wu4t1XQ==", - "dev": true, - "requires": { - "ms": "^2.1.1" - } - } - } - }, - "eslint-module-utils": { - "version": "2.8.0", - "resolved": "https://registry.npmjs.org/eslint-module-utils/-/eslint-module-utils-2.8.0.tgz", - "integrity": "sha512-aWajIYfsqCKRDgUfjEXNN/JlrzauMuSEy5sbd7WXbtW3EH6A6MpwEh42c7qD+MqQo9QMJ6fWLAeIJynx0g6OAw==", - "dev": true, - "requires": { - "debug": "^3.2.7" - }, - "dependencies": { - "debug": { - "version": "3.2.7", - "resolved": "https://registry.npmjs.org/debug/-/debug-3.2.7.tgz", - "integrity": "sha512-CFjzYYAi4ThfiQvizrFQevTTXHtnCqWfe7x1AhgEscTz6ZbLbfoLRLPugTQyBth6f8ZERVUSyWHFD/7Wu4t1XQ==", - "dev": true, - "requires": { - "ms": "^2.1.1" - } - } - } - }, - "eslint-plugin-es": { - "version": "3.0.1", - "resolved": "https://registry.npmjs.org/eslint-plugin-es/-/eslint-plugin-es-3.0.1.tgz", - "integrity": "sha512-GUmAsJaN4Fc7Gbtl8uOBlayo2DqhwWvEzykMHSCZHU3XdJ+NSzzZcVhXh3VxX5icqQ+oQdIEawXX8xkR3mIFmQ==", - "dev": true, - "requires": { - "eslint-utils": "^2.0.0", - "regexpp": "^3.0.0" - }, - "dependencies": { - "eslint-utils": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/eslint-utils/-/eslint-utils-2.1.0.tgz", - "integrity": "sha512-w94dQYoauyvlDc43XnGB8lU3Zt713vNChgt4EWwhXAP2XkBvndfxF0AgIqKOOasjPIPzj9JqgwkwbCYD0/V3Zg==", - "dev": true, - "requires": { - "eslint-visitor-keys": "^1.1.0" - } - }, - "eslint-visitor-keys": { - "version": "1.3.0", - "resolved": "https://registry.npmjs.org/eslint-visitor-keys/-/eslint-visitor-keys-1.3.0.tgz", - "integrity": "sha512-6J72N8UNa462wa/KFODt/PJ3IU60SDpC3QXC1Hjc1BXXpfL2C9R5+AU7jhe0F6GREqVMh4Juu+NY7xn+6dipUQ==", - "dev": true - } - } - }, - "eslint-plugin-import": { - "version": "2.27.5", - "resolved": "https://registry.npmjs.org/eslint-plugin-import/-/eslint-plugin-import-2.27.5.tgz", - "integrity": "sha512-LmEt3GVofgiGuiE+ORpnvP+kAm3h6MLZJ4Q5HCyHADofsb4VzXFsRiWj3c0OFiV+3DWFh0qg3v9gcPlfc3zRow==", - "dev": true, - "requires": { - "array-includes": "^3.1.6", - "array.prototype.flat": "^1.3.1", - "array.prototype.flatmap": "^1.3.1", - "debug": "^3.2.7", - "doctrine": "^2.1.0", - "eslint-import-resolver-node": "^0.3.7", - "eslint-module-utils": "^2.7.4", - "has": "^1.0.3", - "is-core-module": "^2.11.0", - "is-glob": "^4.0.3", - "minimatch": "^3.1.2", - "object.values": "^1.1.6", - "resolve": "^1.22.1", - "semver": "^6.3.0", - "tsconfig-paths": "^3.14.1" - }, - "dependencies": { - "debug": { - "version": "3.2.7", - "resolved": "https://registry.npmjs.org/debug/-/debug-3.2.7.tgz", - "integrity": "sha512-CFjzYYAi4ThfiQvizrFQevTTXHtnCqWfe7x1AhgEscTz6ZbLbfoLRLPugTQyBth6f8ZERVUSyWHFD/7Wu4t1XQ==", - "dev": true, - "requires": { - "ms": "^2.1.1" - } - }, - "doctrine": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/doctrine/-/doctrine-2.1.0.tgz", - "integrity": "sha512-35mSku4ZXK0vfCuHEDAwt55dg2jNajHZ1odvF+8SSr82EsZY4QmXfuWso8oEd8zRhVObSN18aM0CjSdoBX7zIw==", - "dev": true, - "requires": { - "esutils": "^2.0.2" - } - } - } - }, - "eslint-plugin-node": { - "version": "11.1.0", - "resolved": "https://registry.npmjs.org/eslint-plugin-node/-/eslint-plugin-node-11.1.0.tgz", - "integrity": "sha512-oUwtPJ1W0SKD0Tr+wqu92c5xuCeQqB3hSCHasn/ZgjFdA9iDGNkNf2Zi9ztY7X+hNuMib23LNGRm6+uN+KLE3g==", - "dev": true, - "requires": { - "eslint-plugin-es": "^3.0.0", - "eslint-utils": "^2.0.0", - "ignore": "^5.1.1", - "minimatch": "^3.0.4", - "resolve": "^1.10.1", - "semver": "^6.1.0" - }, - "dependencies": { - "eslint-utils": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/eslint-utils/-/eslint-utils-2.1.0.tgz", - "integrity": "sha512-w94dQYoauyvlDc43XnGB8lU3Zt713vNChgt4EWwhXAP2XkBvndfxF0AgIqKOOasjPIPzj9JqgwkwbCYD0/V3Zg==", - "dev": true, - "requires": { - "eslint-visitor-keys": "^1.1.0" - } - }, - "eslint-visitor-keys": { - "version": "1.3.0", - "resolved": "https://registry.npmjs.org/eslint-visitor-keys/-/eslint-visitor-keys-1.3.0.tgz", - "integrity": "sha512-6J72N8UNa462wa/KFODt/PJ3IU60SDpC3QXC1Hjc1BXXpfL2C9R5+AU7jhe0F6GREqVMh4Juu+NY7xn+6dipUQ==", - "dev": true - } - } - }, - "eslint-plugin-prettier": { - "version": "3.4.1", - "resolved": "https://registry.npmjs.org/eslint-plugin-prettier/-/eslint-plugin-prettier-3.4.1.tgz", - "integrity": "sha512-htg25EUYUeIhKHXjOinK4BgCcDwtLHjqaxCDsMy5nbnUMkKFvIhMVCp+5GFUXQ4Nr8lBsPqtGAqBenbpFqAA2g==", - "dev": true, - "requires": { - "prettier-linter-helpers": "^1.0.0" - } - }, - "eslint-plugin-promise": { - "version": "5.2.0", - "resolved": "https://registry.npmjs.org/eslint-plugin-promise/-/eslint-plugin-promise-5.2.0.tgz", - "integrity": "sha512-SftLb1pUG01QYq2A/hGAWfDRXqYD82zE7j7TopDOyNdU+7SvvoXREls/+PRTY17vUXzXnZA/zfnyKgRH6x4JJw==", - "dev": true, - "requires": {} - }, - "eslint-scope": { - "version": "5.1.1", - "resolved": "https://registry.npmjs.org/eslint-scope/-/eslint-scope-5.1.1.tgz", - "integrity": "sha512-2NxwbF/hZ0KpepYN0cNbo+FN6XoK7GaHlQhgx/hIZl6Va0bF45RQOOwhLIy8lQDbuCiadSLCBnH2CFYquit5bw==", - "dev": true, - "requires": { - "esrecurse": "^4.3.0", - "estraverse": "^4.1.1" - } - }, - "eslint-utils": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/eslint-utils/-/eslint-utils-3.0.0.tgz", - "integrity": "sha512-uuQC43IGctw68pJA1RgbQS8/NP7rch6Cwd4j3ZBtgo4/8Flj4eGE7ZYSZRN3iq5pVUv6GPdW5Z1RFleo84uLDA==", - "dev": true, - "requires": { - "eslint-visitor-keys": "^2.0.0" - } - }, - "eslint-visitor-keys": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/eslint-visitor-keys/-/eslint-visitor-keys-2.1.0.tgz", - "integrity": "sha512-0rSmRBzXgDzIsD6mGdJgevzgezI534Cer5L/vyMX0kHzT/jiB43jRhd9YUlMGYLQy2zprNmoT8qasCGtY+QaKw==", - "dev": true - }, - "espree": { - "version": "7.3.1", - "resolved": "https://registry.npmjs.org/espree/-/espree-7.3.1.tgz", - "integrity": "sha512-v3JCNCE64umkFpmkFGqzVKsOT0tN1Zr+ueqLZfpV1Ob8e+CEgPWa+OxCoGH3tnhimMKIaBm4m/vaRpJ/krRz2g==", - "dev": true, - "requires": { - "acorn": "^7.4.0", - "acorn-jsx": "^5.3.1", - "eslint-visitor-keys": "^1.3.0" - }, - "dependencies": { - "eslint-visitor-keys": { - "version": "1.3.0", - "resolved": "https://registry.npmjs.org/eslint-visitor-keys/-/eslint-visitor-keys-1.3.0.tgz", - "integrity": "sha512-6J72N8UNa462wa/KFODt/PJ3IU60SDpC3QXC1Hjc1BXXpfL2C9R5+AU7jhe0F6GREqVMh4Juu+NY7xn+6dipUQ==", - "dev": true - } - } - }, - "esprima": { - "version": "4.0.1", - "resolved": "https://registry.npmjs.org/esprima/-/esprima-4.0.1.tgz", - "integrity": "sha512-eGuFFw7Upda+g4p+QHvnW0RyTX/SVeJBDM/gCtMARO0cLuT2HcEKnTPvhjV6aGeqrCB/sbNop0Kszm0jsaWU4A==", - "dev": true - }, - "esquery": { - "version": "1.5.0", - "resolved": "https://registry.npmjs.org/esquery/-/esquery-1.5.0.tgz", - "integrity": "sha512-YQLXUplAwJgCydQ78IMJywZCceoqk1oH01OERdSAJc/7U2AylwjhSCLDEtqwg811idIS/9fIU5GjG73IgjKMVg==", - "dev": true, - "requires": { - "estraverse": "^5.1.0" - }, - "dependencies": { - "estraverse": { - "version": "5.3.0", - "resolved": "https://registry.npmjs.org/estraverse/-/estraverse-5.3.0.tgz", - "integrity": "sha512-MMdARuVEQziNTeJD8DgMqmhwR11BRQ/cBP+pLtYdSTnf3MIO8fFeiINEbX36ZdNlfU/7A9f3gUw49B3oQsvwBA==", - "dev": true - } - } - }, - "esrecurse": { - "version": "4.3.0", - "resolved": "https://registry.npmjs.org/esrecurse/-/esrecurse-4.3.0.tgz", - "integrity": "sha512-KmfKL3b6G+RXvP8N1vr3Tq1kL/oCFgn2NYXEtqP8/L3pKapUA4G8cFVaoF3SU323CD4XypR/ffioHmkti6/Tag==", - "dev": true, - "requires": { - "estraverse": "^5.2.0" - }, - "dependencies": { - "estraverse": { - "version": "5.3.0", - "resolved": "https://registry.npmjs.org/estraverse/-/estraverse-5.3.0.tgz", - "integrity": "sha512-MMdARuVEQziNTeJD8DgMqmhwR11BRQ/cBP+pLtYdSTnf3MIO8fFeiINEbX36ZdNlfU/7A9f3gUw49B3oQsvwBA==", - "dev": true - } - } - }, - "estraverse": { - "version": "4.3.0", - "resolved": "https://registry.npmjs.org/estraverse/-/estraverse-4.3.0.tgz", - "integrity": "sha512-39nnKffWz8xN1BU/2c79n9nB9HDzo0niYUqx6xyqUnyoAnQyyWpOTdZEeiCch8BBu515t4wp9ZmgVfVhn9EBpw==", - "dev": true - }, - "esutils": { - "version": "2.0.3", - "resolved": "https://registry.npmjs.org/esutils/-/esutils-2.0.3.tgz", - "integrity": "sha512-kVscqXk4OCp68SZ0dkgEKVi6/8ij300KBWTJq32P/dYeWTSwK41WyTxalN1eRmA5Z9UU/LX9D7FWSmV9SAYx6g==", - "dev": true - }, - "etag": { - "version": "1.8.1", - "resolved": "https://registry.npmjs.org/etag/-/etag-1.8.1.tgz", - "integrity": "sha512-aIL5Fx7mawVa300al2BnEE4iNvo1qETxLrPI/o05L7z6go7fCw1J6EQmbK4FmJ2AS7kgVF/KEZWufBfdClMcPg==", - "dev": true - }, - "eth-ens-namehash": { - "version": "2.0.8", - "resolved": "https://registry.npmjs.org/eth-ens-namehash/-/eth-ens-namehash-2.0.8.tgz", - "integrity": "sha512-VWEI1+KJfz4Km//dadyvBBoBeSQ0MHTXPvr8UIXiLW6IanxvAV+DmlZAijZwAyggqGUfwQBeHf7tc9wzc1piSw==", - "dev": true, - "requires": { - "idna-uts46-hx": "^2.3.1", - "js-sha3": "^0.5.7" - }, - "dependencies": { - "js-sha3": { - "version": "0.5.7", - "resolved": "https://registry.npmjs.org/js-sha3/-/js-sha3-0.5.7.tgz", - "integrity": "sha512-GII20kjaPX0zJ8wzkTbNDYMY7msuZcTWk8S5UOh6806Jq/wz1J8/bnr8uGU0DAUmYDjj2Mr4X1cW8v/GLYnR+g==", - "dev": true - } - } - }, - "eth-gas-reporter": { - "version": "0.2.25", - "resolved": "https://registry.npmjs.org/eth-gas-reporter/-/eth-gas-reporter-0.2.25.tgz", - "integrity": "sha512-1fRgyE4xUB8SoqLgN3eDfpDfwEfRxh2Sz1b7wzFbyQA+9TekMmvSjjoRu9SKcSVyK+vLkLIsVbJDsTWjw195OQ==", - "dev": true, - "requires": { - "@ethersproject/abi": "^5.0.0-beta.146", - "@solidity-parser/parser": "^0.14.0", - "cli-table3": "^0.5.0", - "colors": "1.4.0", - "ethereum-cryptography": "^1.0.3", - "ethers": "^4.0.40", - "fs-readdir-recursive": "^1.1.0", - "lodash": "^4.17.14", - "markdown-table": "^1.1.3", - "mocha": "^7.1.1", - "req-cwd": "^2.0.0", - "request": "^2.88.0", - "request-promise-native": "^1.0.5", - "sha1": "^1.1.1", - "sync-request": "^6.0.0" - }, - "dependencies": { - "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 - }, - "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 - }, - "ansi-styles": { - "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, - "requires": { - "color-convert": "^1.9.0" - } - }, - "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==", - "dev": true - }, - "camelcase": { - "version": "5.3.1", - "resolved": "https://registry.npmjs.org/camelcase/-/camelcase-5.3.1.tgz", - "integrity": "sha512-L28STB170nwWS63UjtlEOE3dldQApaJXZkOI1uMFfzf3rRuPegHaHesyee+YxQ+W6SvRDQV6UrdOdRiR153wJg==", - "dev": true - }, - "chalk": { - "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, - "requires": { - "ansi-styles": "^3.2.1", - "escape-string-regexp": "^1.0.5", - "supports-color": "^5.3.0" - }, - "dependencies": { - "supports-color": { - "version": "5.5.0", - "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-5.5.0.tgz", - "integrity": "sha512-QjVjwdXIt408MIiAqCX4oUKsgU2EqAGzs2Ppkm4aQYbjm+ZEWEcW4SfFNTr4uMNZma0ey4f5lgLrkB0aX0QMow==", - "dev": true, - "requires": { - "has-flag": "^3.0.0" - } - } - } - }, - "chokidar": { - "version": "3.3.0", - "resolved": "https://registry.npmjs.org/chokidar/-/chokidar-3.3.0.tgz", - "integrity": "sha512-dGmKLDdT3Gdl7fBUe8XK+gAtGmzy5Fn0XkkWQuYxGIgWVPPse2CxFA5mtrlD0TOHaHjEUqkWNyP1XdHoJES/4A==", - "dev": true, - "requires": { - "anymatch": "~3.1.1", - "braces": "~3.0.2", - "fsevents": "~2.1.1", - "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" - } - }, - "cliui": { - "version": "5.0.0", - "resolved": "https://registry.npmjs.org/cliui/-/cliui-5.0.0.tgz", - "integrity": "sha512-PYeGSEmmHM6zvoef2w8TPzlrnNpXIjTipYK780YswmIP9vjxmd6Y2a3CB2Ks6/AU8NHjZugXvo8w3oWM2qnwXA==", - "dev": true, - "requires": { - "string-width": "^3.1.0", - "strip-ansi": "^5.2.0", - "wrap-ansi": "^5.1.0" - } - }, - "color-convert": { - "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, - "requires": { - "color-name": "1.1.3" - } - }, - "color-name": { - "version": "1.1.3", - "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.3.tgz", - "integrity": "sha512-72fSenhMw2HZMTVHeCA9KCmpEIbzWiQsjN+BHcBbS9vr1mtt+vJjPdksIBNUmKAW8TFUDPJK5SUU3QhE9NEXDw==", - "dev": true - }, - "debug": { - "version": "3.2.6", - "resolved": "https://registry.npmjs.org/debug/-/debug-3.2.6.tgz", - "integrity": "sha512-mel+jf7nrtEl5Pn1Qx46zARXKDpBbvzezse7p7LqINmdoIk8PYP5SySaxEmYv6TZ0JyEKA1hsCId6DIhgITtWQ==", - "dev": true, - "requires": { - "ms": "^2.1.1" - } - }, - "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 - }, - "diff": { - "version": "3.5.0", - "resolved": "https://registry.npmjs.org/diff/-/diff-3.5.0.tgz", - "integrity": "sha512-A46qtFgd+g7pDZinpnwiRJtxbC1hpgf0uzP3iG89scHk0AUC7A1TGxf5OiiOUv/JMZR8GOt8hL900hV0bOy5xA==", - "dev": true - }, - "emoji-regex": { - "version": "7.0.3", - "resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-7.0.3.tgz", - "integrity": "sha512-CwBLREIQ7LvYFB0WyRvwhq5N5qPhc6PMjD6bYggFlI5YyDgl+0vxq5VHbMOFqLg7hfWzmu8T5Z1QofhmTIhItA==", - "dev": true - }, - "escape-string-regexp": { - "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 - }, - "ethers": { - "version": "4.0.49", - "resolved": "https://registry.npmjs.org/ethers/-/ethers-4.0.49.tgz", - "integrity": "sha512-kPltTvWiyu+OktYy1IStSO16i2e7cS9D9OxZ81q2UUaiNPVrm/RTcbxamCXF9VUSKzJIdJV68EAIhTEVBalRWg==", - "dev": true, - "requires": { - "aes-js": "3.0.0", - "bn.js": "^4.11.9", - "elliptic": "6.5.4", - "hash.js": "1.1.3", - "js-sha3": "0.5.7", - "scrypt-js": "2.0.4", - "setimmediate": "1.0.4", - "uuid": "2.0.1", - "xmlhttprequest": "1.8.0" - } - }, - "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, - "requires": { - "locate-path": "^3.0.0" - } - }, - "flat": { - "version": "4.1.1", - "resolved": "https://registry.npmjs.org/flat/-/flat-4.1.1.tgz", - "integrity": "sha512-FmTtBsHskrU6FJ2VxCnsDb84wu9zhmO3cUX2kGFb5tuwhfXxGciiT0oRY+cck35QmG+NmGh5eLz6lLCpWTqwpA==", - "dev": true, - "requires": { - "is-buffer": "~2.0.3" - } - }, - "fsevents": { - "version": "2.1.3", - "resolved": "https://registry.npmjs.org/fsevents/-/fsevents-2.1.3.tgz", - "integrity": "sha512-Auw9a4AxqWpa9GUfj370BMPzzyncfBABW8Mab7BGWBYDj4Isgq+cDKtx0i6u9jcX9pQDnswsaaOTgTmA5pEjuQ==", - "dev": true, - "optional": true - }, - "glob": { - "version": "7.1.3", - "resolved": "https://registry.npmjs.org/glob/-/glob-7.1.3.tgz", - "integrity": "sha512-vcfuiIxogLV4DlGBHIUOwI0IbrJ8HWPc4MU7HzviGeNho/UJDfi6B5p3sHeWIQ0KGIU0Jpxi5ZHxemQfLkkAwQ==", - "dev": true, - "requires": { - "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" - } - }, - "has-flag": { - "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 - }, - "hash.js": { - "version": "1.1.3", - "resolved": "https://registry.npmjs.org/hash.js/-/hash.js-1.1.3.tgz", - "integrity": "sha512-/UETyP0W22QILqS+6HowevwhEFJ3MBJnwTf75Qob9Wz9t0DPuisL8kW8YZMK62dHAKE1c1p+gY1TtOLY+USEHA==", - "dev": true, - "requires": { - "inherits": "^2.0.3", - "minimalistic-assert": "^1.0.0" - } - }, - "js-sha3": { - "version": "0.5.7", - "resolved": "https://registry.npmjs.org/js-sha3/-/js-sha3-0.5.7.tgz", - "integrity": "sha512-GII20kjaPX0zJ8wzkTbNDYMY7msuZcTWk8S5UOh6806Jq/wz1J8/bnr8uGU0DAUmYDjj2Mr4X1cW8v/GLYnR+g==", - "dev": true - }, - "js-yaml": { - "version": "3.13.1", - "resolved": "https://registry.npmjs.org/js-yaml/-/js-yaml-3.13.1.tgz", - "integrity": "sha512-YfbcO7jXDdyj0DGxYVSlSeQNHbD7XPWvrVWeVUujrQEoZzWJIRrCPoyk6kL6IAjAG2IolMK4T0hNUe0HOUs5Jw==", - "dev": true, - "requires": { - "argparse": "^1.0.7", - "esprima": "^4.0.0" - } - }, - "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, - "requires": { - "p-locate": "^3.0.0", - "path-exists": "^3.0.0" - } - }, - "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, - "requires": { - "chalk": "^2.4.2" - } - }, - "minimatch": { - "version": "3.0.4", - "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.0.4.tgz", - "integrity": "sha512-yJHVQEhyqPLUTgt9B83PXu6W3rx4MvvHvSUvToogpwoGDOUQ+yDrR0HRot+yOCdCO7u4hX3pWft6kWBBcqh0UA==", - "dev": true, - "requires": { - "brace-expansion": "^1.1.7" - } - }, - "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, - "requires": { - "minimist": "^1.2.5" - } - }, - "mocha": { - "version": "7.2.0", - "resolved": "https://registry.npmjs.org/mocha/-/mocha-7.2.0.tgz", - "integrity": "sha512-O9CIypScywTVpNaRrCAgoUnJgozpIofjKUYmJhiCIJMiuYnLI6otcb1/kpW9/n/tJODHGZ7i8aLQoDVsMtOKQQ==", - "dev": true, - "requires": { - "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" - } - }, - "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 - }, - "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, - "requires": { - "define-properties": "^1.1.2", - "function-bind": "^1.1.1", - "has-symbols": "^1.0.0", - "object-keys": "^1.0.11" - } - }, - "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, - "requires": { - "p-try": "^2.0.0" - } - }, - "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, - "requires": { - "p-limit": "^2.0.0" - } - }, - "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 - }, - "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, - "requires": { - "picomatch": "^2.0.4" - } - }, - "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 - }, - "scrypt-js": { - "version": "2.0.4", - "resolved": "https://registry.npmjs.org/scrypt-js/-/scrypt-js-2.0.4.tgz", - "integrity": "sha512-4KsaGcPnuhtCZQCxFxN3GVYIhKFPTdLd8PLC552XwbMndtD0cjRFAhDuuydXQ0h08ZfPgzqe6EKHozpuH74iDw==", - "dev": true - }, - "setimmediate": { - "version": "1.0.4", - "resolved": "https://registry.npmjs.org/setimmediate/-/setimmediate-1.0.4.tgz", - "integrity": "sha512-/TjEmXQVEzdod/FFskf3o7oOAsGhHf2j1dZqRFbDzq4F3mvvxflIIi4Hd3bLQE9y/CpwqfSQam5JakI/mi3Pog==", - "dev": true - }, - "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, - "requires": { - "emoji-regex": "^7.0.1", - "is-fullwidth-code-point": "^2.0.0", - "strip-ansi": "^5.1.0" - } - }, - "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, - "requires": { - "ansi-regex": "^4.1.0" - } - }, - "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 - }, - "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, - "requires": { - "has-flag": "^3.0.0" - } - }, - "uuid": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/uuid/-/uuid-2.0.1.tgz", - "integrity": "sha512-nWg9+Oa3qD2CQzHIP4qKUqwNfzKn8P0LtFhotaCTFchsV7ZfDhAybeip/HZVeMIpZi9JgY1E3nUlwaCmZT1sEg==", - "dev": true - }, - "which": { - "version": "1.3.1", - "resolved": "https://registry.npmjs.org/which/-/which-1.3.1.tgz", - "integrity": "sha512-HxJdYWq1MTIQbJ3nw0cqssHoTNU267KlrDuGZ1WYlxDStUtKUhOaJmh112/TZmHxxUfuJqPXSOm7tDyas0OSIQ==", - "dev": true, - "requires": { - "isexe": "^2.0.0" - } - }, - "which-module": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/which-module/-/which-module-2.0.1.tgz", - "integrity": "sha512-iBdZ57RDvnOR9AGBhML2vFZf7h8vmBjhoaZqODJBFWHVtKkDmKuHai3cx5PgVMrX5YDNp27AofYbAwctSS+vhQ==", - "dev": true - }, - "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, - "requires": { - "ansi-styles": "^3.2.0", - "string-width": "^3.0.0", - "strip-ansi": "^5.0.0" - } - }, - "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 - }, - "yargs": { - "version": "13.3.2", - "resolved": "https://registry.npmjs.org/yargs/-/yargs-13.3.2.tgz", - "integrity": "sha512-AX3Zw5iPruN5ie6xGRIDgqkT+ZhnRlZMLMHAs8tg7nRruy2Nb+i5o9bwghAogtM08q1dpr2LVoS8KSTMYpWXUw==", - "dev": true, - "requires": { - "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" - } - }, - "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, - "requires": { - "camelcase": "^5.0.0", - "decamelize": "^1.2.0" - } - }, - "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, - "requires": { - "flat": "^4.1.0", - "lodash": "^4.17.15", - "yargs": "^13.3.0" - } - } - } - }, - "eth-lib": { - "version": "0.1.29", - "resolved": "https://registry.npmjs.org/eth-lib/-/eth-lib-0.1.29.tgz", - "integrity": "sha512-bfttrr3/7gG4E02HoWTDUcDDslN003OlOoBxk9virpAZQ1ja/jDgwkWB8QfJF7ojuEowrqy+lzp9VcJG7/k5bQ==", - "dev": true, - "requires": { - "bn.js": "^4.11.6", - "elliptic": "^6.4.0", - "nano-json-stream-parser": "^0.1.2", - "servify": "^0.1.12", - "ws": "^3.0.0", - "xhr-request-promise": "^0.1.2" - }, - "dependencies": { - "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==", - "dev": true - }, - "ws": { - "version": "3.3.3", - "resolved": "https://registry.npmjs.org/ws/-/ws-3.3.3.tgz", - "integrity": "sha512-nnWLa/NwZSt4KQJu51MYlCcSQ5g7INpOrOMt4XV8j4dqTXdmlUmSHQ8/oLC069ckre0fRsgfvsKwbTdtKLCDkA==", - "dev": true, - "requires": { - "async-limiter": "~1.0.0", - "safe-buffer": "~5.1.0", - "ultron": "~1.1.0" - } - } - } - }, - "ethereum-bloom-filters": { - "version": "1.0.10", - "resolved": "https://registry.npmjs.org/ethereum-bloom-filters/-/ethereum-bloom-filters-1.0.10.tgz", - "integrity": "sha512-rxJ5OFN3RwjQxDcFP2Z5+Q9ho4eIdEmSc2ht0fCu8Se9nbXjZ7/031uXoUYJ87KHCOdVeiUuwSnoS7hmYAGVHA==", - "requires": { - "js-sha3": "^0.8.0" - } - }, - "ethereum-cryptography": { - "version": "1.2.0", - "resolved": "https://registry.npmjs.org/ethereum-cryptography/-/ethereum-cryptography-1.2.0.tgz", - "integrity": "sha512-6yFQC9b5ug6/17CQpCyE3k9eKBMdhyVjzUy1WkiuY/E4vj/SXDBbCw8QEIaXqf0Mf2SnY6RmpDcwlUmBSS0EJw==", - "dev": true, - "requires": { - "@noble/hashes": "1.2.0", - "@noble/secp256k1": "1.7.1", - "@scure/bip32": "1.1.5", - "@scure/bip39": "1.1.1" - } - }, - "ethereum-waffle": { - "version": "3.4.4", - "resolved": "https://registry.npmjs.org/ethereum-waffle/-/ethereum-waffle-3.4.4.tgz", - "integrity": "sha512-PA9+jCjw4WC3Oc5ocSMBj5sXvueWQeAbvCA+hUlb6oFgwwKyq5ka3bWQ7QZcjzIX+TdFkxP4IbFmoY2D8Dkj9Q==", - "dev": true, - "requires": { - "@ethereum-waffle/chai": "^3.4.4", - "@ethereum-waffle/compiler": "^3.4.4", - "@ethereum-waffle/mock-contract": "^3.4.4", - "@ethereum-waffle/provider": "^3.4.4", - "ethers": "^5.0.1" - } - }, - "ethereumjs-abi": { - "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, - "requires": { - "bn.js": "^4.11.8", - "ethereumjs-util": "^6.0.0" - }, - "dependencies": { - "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==", - "dev": true - } - } - }, - "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, - "requires": { - "@types/bn.js": "^4.11.3", - "bn.js": "^4.11.0", - "create-hash": "^1.1.2", - "elliptic": "^6.5.2", - "ethereum-cryptography": "^0.1.3", - "ethjs-util": "0.1.6", - "rlp": "^2.2.3" - }, - "dependencies": { - "@types/bn.js": { - "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, - "requires": { - "@types/node": "*" - } - }, - "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==", - "dev": true - }, - "ethereum-cryptography": { - "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, - "requires": { - "@types/pbkdf2": "^3.0.0", - "@types/secp256k1": "^4.0.1", - "blakejs": "^1.1.0", - "browserify-aes": "^1.2.0", - "bs58check": "^2.1.2", - "create-hash": "^1.2.0", - "create-hmac": "^1.1.7", - "hash.js": "^1.1.7", - "keccak": "^3.0.0", - "pbkdf2": "^3.0.17", - "randombytes": "^2.1.0", - "safe-buffer": "^5.1.2", - "scrypt-js": "^3.0.0", - "secp256k1": "^4.0.1", - "setimmediate": "^1.0.5" - } - } - } - }, - "ethers": { - "version": "5.7.2", - "resolved": "https://registry.npmjs.org/ethers/-/ethers-5.7.2.tgz", - "integrity": "sha512-wswUsmWo1aOK8rR7DIKiWSw9DbLWe6x98Jrn8wcTflTVvaXhAMaB5zGAXy0GYQEQp9iO1iSHWVyARQm11zUtyg==", - "requires": { - "@ethersproject/abi": "5.7.0", - "@ethersproject/abstract-provider": "5.7.0", - "@ethersproject/abstract-signer": "5.7.0", - "@ethersproject/address": "5.7.0", - "@ethersproject/base64": "5.7.0", - "@ethersproject/basex": "5.7.0", - "@ethersproject/bignumber": "5.7.0", - "@ethersproject/bytes": "5.7.0", - "@ethersproject/constants": "5.7.0", - "@ethersproject/contracts": "5.7.0", - "@ethersproject/hash": "5.7.0", - "@ethersproject/hdnode": "5.7.0", - "@ethersproject/json-wallets": "5.7.0", - "@ethersproject/keccak256": "5.7.0", - "@ethersproject/logger": "5.7.0", - "@ethersproject/networks": "5.7.1", - "@ethersproject/pbkdf2": "5.7.0", - "@ethersproject/properties": "5.7.0", - "@ethersproject/providers": "5.7.2", - "@ethersproject/random": "5.7.0", - "@ethersproject/rlp": "5.7.0", - "@ethersproject/sha2": "5.7.0", - "@ethersproject/signing-key": "5.7.0", - "@ethersproject/solidity": "5.7.0", - "@ethersproject/strings": "5.7.0", - "@ethersproject/transactions": "5.7.0", - "@ethersproject/units": "5.7.0", - "@ethersproject/wallet": "5.7.0", - "@ethersproject/web": "5.7.1", - "@ethersproject/wordlists": "5.7.0" - } - }, - "ethjs-unit": { - "version": "0.1.6", - "resolved": "https://registry.npmjs.org/ethjs-unit/-/ethjs-unit-0.1.6.tgz", - "integrity": "sha512-/Sn9Y0oKl0uqQuvgFk/zQgR7aw1g36qX/jzSQ5lSwlO0GigPymk4eGQfeNTD03w1dPOqfz8V77Cy43jH56pagw==", - "requires": { - "bn.js": "4.11.6", - "number-to-bn": "1.7.0" - }, - "dependencies": { - "bn.js": { - "version": "4.11.6", - "resolved": "https://registry.npmjs.org/bn.js/-/bn.js-4.11.6.tgz", - "integrity": "sha512-XWwnNNFCuuSQ0m3r3C4LE3EiORltHd9M05pq6FOlVeiophzRbMo50Sbz1ehl8K3Z+jw9+vmgnXefY1hz8X+2wA==" - } - } - }, - "ethjs-util": { - "version": "0.1.6", - "resolved": "https://registry.npmjs.org/ethjs-util/-/ethjs-util-0.1.6.tgz", - "integrity": "sha512-CUnVOQq7gSpDHZVVrQW8ExxUETWrnrvXYvYz55wOU8Uj4VCgw56XC2B/fVqQN+f7gmrnRHSLVnFAwsCuNwji8w==", - "dev": true, - "requires": { - "is-hex-prefixed": "1.0.0", - "strip-hex-prefix": "1.0.0" - } - }, - "event-target-shim": { - "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 - }, - "eventemitter3": { - "version": "4.0.4", - "resolved": "https://registry.npmjs.org/eventemitter3/-/eventemitter3-4.0.4.tgz", - "integrity": "sha512-rlaVLnVxtxvoyLsQQFBx53YmXHDxRIzzTLbdfxqi4yocpSjAxXwkU0cScM5JgSKMqEhrZpnvQ2D9gjylR0AimQ==", - "dev": true - }, - "evp_bytestokey": { - "version": "1.0.3", - "resolved": "https://registry.npmjs.org/evp_bytestokey/-/evp_bytestokey-1.0.3.tgz", - "integrity": "sha512-/f2Go4TognH/KvCISP7OUsHn85hT9nUkxxA9BEWxFn+Oj9o8ZNLm/40hdlgSLyuOimsrTKLUMEorQexp/aPQeA==", - "requires": { - "md5.js": "^1.3.4", - "safe-buffer": "^5.1.1" - } - }, - "express": { - "version": "4.18.2", - "resolved": "https://registry.npmjs.org/express/-/express-4.18.2.tgz", - "integrity": "sha512-5/PsL6iGPdfQ/lKM1UuielYgv3BUoJfz1aUwU9vHZ+J7gyvwdQXFEBIEIaxeGf0GIcreATNyBExtalisDbuMqQ==", - "dev": true, - "requires": { - "accepts": "~1.3.8", - "array-flatten": "1.1.1", - "body-parser": "1.20.1", - "content-disposition": "0.5.4", - "content-type": "~1.0.4", - "cookie": "0.5.0", - "cookie-signature": "1.0.6", - "debug": "2.6.9", - "depd": "2.0.0", - "encodeurl": "~1.0.2", - "escape-html": "~1.0.3", - "etag": "~1.8.1", - "finalhandler": "1.2.0", - "fresh": "0.5.2", - "http-errors": "2.0.0", - "merge-descriptors": "1.0.1", - "methods": "~1.1.2", - "on-finished": "2.4.1", - "parseurl": "~1.3.3", - "path-to-regexp": "0.1.7", - "proxy-addr": "~2.0.7", - "qs": "6.11.0", - "range-parser": "~1.2.1", - "safe-buffer": "5.2.1", - "send": "0.18.0", - "serve-static": "1.15.0", - "setprototypeof": "1.2.0", - "statuses": "2.0.1", - "type-is": "~1.6.18", - "utils-merge": "1.0.1", - "vary": "~1.1.2" - }, - "dependencies": { - "body-parser": { - "version": "1.20.1", - "resolved": "https://registry.npmjs.org/body-parser/-/body-parser-1.20.1.tgz", - "integrity": "sha512-jWi7abTbYwajOytWCQc37VulmWiRae5RyTpaCyDcS5/lMdtwSz5lOpDE67srw/HYe35f1z3fDQw+3txg7gNtWw==", - "dev": true, - "requires": { - "bytes": "3.1.2", - "content-type": "~1.0.4", - "debug": "2.6.9", - "depd": "2.0.0", - "destroy": "1.2.0", - "http-errors": "2.0.0", - "iconv-lite": "0.4.24", - "on-finished": "2.4.1", - "qs": "6.11.0", - "raw-body": "2.5.1", - "type-is": "~1.6.18", - "unpipe": "1.0.0" - } - }, - "cookie": { - "version": "0.5.0", - "resolved": "https://registry.npmjs.org/cookie/-/cookie-0.5.0.tgz", - "integrity": "sha512-YZ3GUyn/o8gfKJlnlX7g7xq4gyO6OSuhGPKaaGssGB2qgDUS0gPgtTvoyZLTt9Ab6dC4hfc9dV5arkvc/OCmrw==", - "dev": true - }, - "debug": { - "version": "2.6.9", - "resolved": "https://registry.npmjs.org/debug/-/debug-2.6.9.tgz", - "integrity": "sha512-bC7ElrdJaJnPbAP+1EotYvqZsb3ecl5wi6Bfi6BJTUcNowp6cvspg0jXznRTKDjm/E7AdgFBVeAPVMNcKGsHMA==", - "dev": true, - "requires": { - "ms": "2.0.0" - } - }, - "ms": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/ms/-/ms-2.0.0.tgz", - "integrity": "sha512-Tpp60P6IUJDTuOq/5Z8cdskzJujfwqfOTkrwIwj7IRISpnkJnT6SyJ4PCPnGMoFjC9ddhal5KVIYtAt97ix05A==", - "dev": true - }, - "qs": { - "version": "6.11.0", - "resolved": "https://registry.npmjs.org/qs/-/qs-6.11.0.tgz", - "integrity": "sha512-MvjoMCJwEarSbUYk5O+nmoSzSutSsTwF85zcHPQ9OrlFoZOYIjaqBAJIqIXjptyD5vThxGq52Xu/MaJzRkIk4Q==", - "dev": true, - "requires": { - "side-channel": "^1.0.4" - } - }, - "raw-body": { - "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, - "requires": { - "bytes": "3.1.2", - "http-errors": "2.0.0", - "iconv-lite": "0.4.24", - "unpipe": "1.0.0" - } - }, - "safe-buffer": { - "version": "5.2.1", - "resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.2.1.tgz", - "integrity": "sha512-rp3So07KcdmmKbGvgaNxQSJr7bGVSVk5S9Eq1F+ppbRo70+YeaDxkw5Dd8NPN+GD6bjnYm2VuPuCXmpuYvmCXQ==", - "dev": true - } - } - }, - "ext": { - "version": "1.7.0", - "resolved": "https://registry.npmjs.org/ext/-/ext-1.7.0.tgz", - "integrity": "sha512-6hxeJYaL110a9b5TEJSj0gojyHQAmA2ch5Os+ySCiA1QGdS697XWY1pzsrSjqA9LDEEgdB/KypIlR59RcLuHYw==", - "dev": true, - "requires": { - "type": "^2.7.2" - }, - "dependencies": { - "type": { - "version": "2.7.2", - "resolved": "https://registry.npmjs.org/type/-/type-2.7.2.tgz", - "integrity": "sha512-dzlvlNlt6AXU7EBSfpAscydQ7gXB+pPGsPnfJnZpiNJBDj7IaJzQlBZYGdEi4R9HmPdBv2XmWJ6YUtoTa7lmCw==", - "dev": true - } - } - }, - "extend": { - "version": "3.0.2", - "resolved": "https://registry.npmjs.org/extend/-/extend-3.0.2.tgz", - "integrity": "sha512-fjquC59cD7CyW6urNXK0FBufkZcoiGG80wTuPujX590cB5Ttln20E2UB4S/WARVqhXffZl2LNgS+gQdPIIim/g==", - "dev": true - }, - "extsprintf": { - "version": "1.3.0", - "resolved": "https://registry.npmjs.org/extsprintf/-/extsprintf-1.3.0.tgz", - "integrity": "sha512-11Ndz7Nv+mvAC1j0ktTa7fAb0vLyGGX+rMHNBYQviQDGU0Hw7lhctJANqbPhu9nV9/izT/IntTgZ7Im/9LJs9g==", - "dev": true - }, - "fast-deep-equal": { - "version": "3.1.3", - "resolved": "https://registry.npmjs.org/fast-deep-equal/-/fast-deep-equal-3.1.3.tgz", - "integrity": "sha512-f3qQ9oQy9j2AhBe/H9VC91wLmKBCCU/gDOnKNAYG5hswO7BLKj09Hc5HYNz9cGI++xlpDCIgDaitVs03ATR84Q==", - "dev": true - }, - "fast-diff": { - "version": "1.3.0", - "resolved": "https://registry.npmjs.org/fast-diff/-/fast-diff-1.3.0.tgz", - "integrity": "sha512-VxPP4NqbUjj6MaAOafWeUn2cXWLcCtljklUtZf0Ind4XQ+QPtmA0b18zZy0jIQx+ExRVCR/ZQpBmik5lXshNsw==", - "dev": true - }, - "fast-glob": { - "version": "3.3.0", - "resolved": "https://registry.npmjs.org/fast-glob/-/fast-glob-3.3.0.tgz", - "integrity": "sha512-ChDuvbOypPuNjO8yIDf36x7BlZX1smcUMTTcyoIjycexOxd6DFsKsg21qVBzEmr3G7fUKIRy2/psii+CIUt7FA==", - "dev": true, - "requires": { - "@nodelib/fs.stat": "^2.0.2", - "@nodelib/fs.walk": "^1.2.3", - "glob-parent": "^5.1.2", - "merge2": "^1.3.0", - "micromatch": "^4.0.4" - } - }, - "fast-json-stable-stringify": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/fast-json-stable-stringify/-/fast-json-stable-stringify-2.1.0.tgz", - "integrity": "sha512-lhd/wF+Lk98HZoTCtlVraHtfh5XYijIjalXck7saUtuanSDyLMxnHhSXEDJqHxD7msR8D0uCmqlkwjCV8xvwHw==", - "dev": true - }, - "fast-levenshtein": { - "version": "2.0.6", - "resolved": "https://registry.npmjs.org/fast-levenshtein/-/fast-levenshtein-2.0.6.tgz", - "integrity": "sha512-DCXu6Ifhqcks7TZKY3Hxp3y6qphY5SJZmrWMDrKcERSOXWQdMhU9Ig/PYrzyw/ul9jOIyh0N4M0tbC5hodg8dw==", - "dev": true - }, - "fastq": { - "version": "1.15.0", - "resolved": "https://registry.npmjs.org/fastq/-/fastq-1.15.0.tgz", - "integrity": "sha512-wBrocU2LCXXa+lWBt8RoIRD89Fi8OdABODa/kEnyeyjS5aZO5/GNvI5sEINADqP/h8M29UHTHUb53sUu5Ihqdw==", - "dev": true, - "requires": { - "reusify": "^1.0.4" - } - }, - "file-entry-cache": { - "version": "6.0.1", - "resolved": "https://registry.npmjs.org/file-entry-cache/-/file-entry-cache-6.0.1.tgz", - "integrity": "sha512-7Gps/XWymbLk2QLYK4NzpMOrYjMhdIxXuIvy2QBsLE6ljuodKvdkWs/cpyJJ3CVIVpH0Oi1Hvg1ovbMzLdFBBg==", - "dev": true, - "requires": { - "flat-cache": "^3.0.4" - } - }, - "fill-range": { - "version": "7.0.1", - "resolved": "https://registry.npmjs.org/fill-range/-/fill-range-7.0.1.tgz", - "integrity": "sha512-qOo9F+dMUmC2Lcb4BbVvnKJxTPjCm+RRpe4gDuGrzkL7mEVl/djYSu2OdQ2Pa302N4oqkSg9ir6jaLWJ2USVpQ==", - "dev": true, - "requires": { - "to-regex-range": "^5.0.1" - } - }, - "finalhandler": { - "version": "1.2.0", - "resolved": "https://registry.npmjs.org/finalhandler/-/finalhandler-1.2.0.tgz", - "integrity": "sha512-5uXcUVftlQMFnWC9qu/svkWv3GTd2PfUhK/3PLkYNAe7FbqJMt3515HaxE6eRL74GdsriiwujiawdaB1BpEISg==", - "dev": true, - "requires": { - "debug": "2.6.9", - "encodeurl": "~1.0.2", - "escape-html": "~1.0.3", - "on-finished": "2.4.1", - "parseurl": "~1.3.3", - "statuses": "2.0.1", - "unpipe": "~1.0.0" - }, - "dependencies": { - "debug": { - "version": "2.6.9", - "resolved": "https://registry.npmjs.org/debug/-/debug-2.6.9.tgz", - "integrity": "sha512-bC7ElrdJaJnPbAP+1EotYvqZsb3ecl5wi6Bfi6BJTUcNowp6cvspg0jXznRTKDjm/E7AdgFBVeAPVMNcKGsHMA==", - "dev": true, - "requires": { - "ms": "2.0.0" - } - }, - "ms": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/ms/-/ms-2.0.0.tgz", - "integrity": "sha512-Tpp60P6IUJDTuOq/5Z8cdskzJujfwqfOTkrwIwj7IRISpnkJnT6SyJ4PCPnGMoFjC9ddhal5KVIYtAt97ix05A==", - "dev": true - } - } - }, - "find-replace": { - "version": "1.0.3", - "resolved": "https://registry.npmjs.org/find-replace/-/find-replace-1.0.3.tgz", - "integrity": "sha512-KrUnjzDCD9426YnCP56zGYy/eieTnhtK6Vn++j+JJzmlsWWwEkDnsyVF575spT6HJ6Ow9tlbT3TQTDsa+O4UWA==", - "dev": true, - "requires": { - "array-back": "^1.0.4", - "test-value": "^2.1.0" - }, - "dependencies": { - "array-back": { - "version": "1.0.4", - "resolved": "https://registry.npmjs.org/array-back/-/array-back-1.0.4.tgz", - "integrity": "sha512-1WxbZvrmyhkNoeYcizokbmh5oiOCIfyvGtcqbK3Ls1v1fKcquzxnQSceOx6tzq7jmai2kFLWIpGND2cLhH6TPw==", - "dev": true, - "requires": { - "typical": "^2.6.0" - } - } - } - }, - "find-up": { - "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, - "requires": { - "locate-path": "^2.0.0" - } - }, - "find-yarn-workspace-root": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/find-yarn-workspace-root/-/find-yarn-workspace-root-2.0.0.tgz", - "integrity": "sha512-1IMnbjt4KzsQfnhnzNd8wUEgXZ44IzZaZmnLYx7D5FZlaHt2gW20Cri8Q+E/t5tIj4+epTBub+2Zxu/vNILzqQ==", - "dev": true, - "requires": { - "micromatch": "^4.0.2" - } - }, - "flat": { - "version": "5.0.2", - "resolved": "https://registry.npmjs.org/flat/-/flat-5.0.2.tgz", - "integrity": "sha512-b6suED+5/3rTpUBdG1gupIl8MPFCAMA0QXwmljLhvCUKcUvdE4gWky9zpuGCcXHOsz4J9wPGNWq6OKpmIzz3hQ==", - "dev": true - }, - "flat-cache": { - "version": "3.0.4", - "resolved": "https://registry.npmjs.org/flat-cache/-/flat-cache-3.0.4.tgz", - "integrity": "sha512-dm9s5Pw7Jc0GvMYbshN6zchCA9RgQlzzEZX3vylR9IqFfS8XciblUXOKfW6SiuJ0e13eDYZoZV5wdrev7P3Nwg==", - "dev": true, - "requires": { - "flatted": "^3.1.0", - "rimraf": "^3.0.2" - } - }, - "flatted": { - "version": "3.2.7", - "resolved": "https://registry.npmjs.org/flatted/-/flatted-3.2.7.tgz", - "integrity": "sha512-5nqDSxl8nn5BSNxyR3n4I6eDmbolI6WT+QqR547RwxQapgjQBmtktdP+HTBb/a/zLsbzERTONyUB5pefh5TtjQ==", - "dev": true - }, - "follow-redirects": { - "version": "1.15.2", - "resolved": "https://registry.npmjs.org/follow-redirects/-/follow-redirects-1.15.2.tgz", - "integrity": "sha512-VQLG33o04KaQ8uYi2tVNbdrWp1QWxNNea+nmIB4EVM28v0hmP17z7aG1+wAkNzVq4KeXTq3221ye5qTJP91JwA==", - "dev": true - }, - "for-each": { - "version": "0.3.3", - "resolved": "https://registry.npmjs.org/for-each/-/for-each-0.3.3.tgz", - "integrity": "sha512-jqYfLp7mo9vIyQf8ykW2v7A+2N4QjeCeI5+Dz9XraiO1ign81wjiH7Fb9vSOWvQfNtmSa4H2RoQTrrXivdUZmw==", - "dev": true, - "requires": { - "is-callable": "^1.1.3" - } - }, - "forever-agent": { - "version": "0.6.1", - "resolved": "https://registry.npmjs.org/forever-agent/-/forever-agent-0.6.1.tgz", - "integrity": "sha512-j0KLYPhm6zeac4lz3oJ3o65qvgQCcPubiyotZrXqEaG4hNagNYO8qdlUrX5vwqv9ohqeT/Z3j6+yW067yWWdUw==", - "dev": true - }, - "form-data": { - "version": "3.0.1", - "resolved": "https://registry.npmjs.org/form-data/-/form-data-3.0.1.tgz", - "integrity": "sha512-RHkBKtLWUVwd7SqRIvCZMEvAMoGUp0XU+seQiZejj0COz3RI3hWP4sCv3gZWWLjJTd7rGwcsF5eKZGii0r/hbg==", - "dev": true, - "requires": { - "asynckit": "^0.4.0", - "combined-stream": "^1.0.8", - "mime-types": "^2.1.12" - } - }, - "form-data-encoder": { - "version": "1.7.1", - "resolved": "https://registry.npmjs.org/form-data-encoder/-/form-data-encoder-1.7.1.tgz", - "integrity": "sha512-EFRDrsMm/kyqbTQocNvRXMLjc7Es2Vk+IQFx/YW7hkUH1eBl4J1fqiP34l74Yt0pFLCNpc06fkbVk00008mzjg==", - "dev": true - }, - "forwarded": { - "version": "0.2.0", - "resolved": "https://registry.npmjs.org/forwarded/-/forwarded-0.2.0.tgz", - "integrity": "sha512-buRG0fpBtRHSTCOASe6hD258tEubFoRLb4ZNA6NxMVHNw2gOcwHo9wyablzMzOA5z9xA9L1KNjk/Nt6MT9aYow==", - "dev": true - }, - "fp-ts": { - "version": "1.19.3", - "resolved": "https://registry.npmjs.org/fp-ts/-/fp-ts-1.19.3.tgz", - "integrity": "sha512-H5KQDspykdHuztLTg+ajGN0Z2qUjcEf3Ybxc6hLt0k7/zPkn29XnKnxlBPyW2XIddWrGaJBzBl4VLYOtk39yZg==", - "dev": true - }, - "fresh": { - "version": "0.5.2", - "resolved": "https://registry.npmjs.org/fresh/-/fresh-0.5.2.tgz", - "integrity": "sha512-zJ2mQYM18rEFOudeV4GShTGIQ7RbzA7ozbU9I/XBpm7kqgMywgmylMwXHxZJmkVoYkna9d2pVXVXPdYTP9ej8Q==", - "dev": true - }, - "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, - "requires": { - "graceful-fs": "^4.1.2", - "jsonfile": "^4.0.0", - "universalify": "^0.1.0" - } - }, - "fs-minipass": { - "version": "1.2.7", - "resolved": "https://registry.npmjs.org/fs-minipass/-/fs-minipass-1.2.7.tgz", - "integrity": "sha512-GWSSJGFy4e9GUeCcbIkED+bgAoFyj7XF1mV8rma3QW4NIqX9Kyx79N/PF61H5udOV3aY1IaMLs6pGbH71nlCTA==", - "dev": true, - "requires": { - "minipass": "^2.6.0" - } - }, - "fs-readdir-recursive": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/fs-readdir-recursive/-/fs-readdir-recursive-1.1.0.tgz", - "integrity": "sha512-GNanXlVr2pf02+sPN40XN8HG+ePaNcvM0q5mZBd668Obwb0yD5GiUbZOFgwn8kGMY6I3mdyDJzieUy3PTYyTRA==", - "dev": true - }, - "fs.realpath": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/fs.realpath/-/fs.realpath-1.0.0.tgz", - "integrity": "sha512-OO0pH2lK6a0hZnAdau5ItzHPI6pUlvI7jMVnxUQRtw4owF2wk8lOSabtGDCTP4Ggrg2MbGnWO9X8K1t4+fGMDw==", - "dev": true - }, - "fsevents": { - "version": "2.3.2", - "resolved": "https://registry.npmjs.org/fsevents/-/fsevents-2.3.2.tgz", - "integrity": "sha512-xiqMQR4xAeHTuB9uWm+fFRcIOgKBMiOBP+eXiyT7jsgVCq1bkVygt00oASowB7EdtpOHaaPgKt812P9ab+DDKA==", - "dev": true, - "optional": true - }, - "function-bind": { - "version": "1.1.1", - "resolved": "https://registry.npmjs.org/function-bind/-/function-bind-1.1.1.tgz", - "integrity": "sha512-yIovAzMX49sF8Yl58fSCWJ5svSLuaibPxXQJFLmBObTuCr0Mf1KiPopGM9NiFjiYBCbfaa2Fh6breQ6ANVTI0A==", - "dev": true - }, - "function.prototype.name": { - "version": "1.1.5", - "resolved": "https://registry.npmjs.org/function.prototype.name/-/function.prototype.name-1.1.5.tgz", - "integrity": "sha512-uN7m/BzVKQnCUF/iW8jYea67v++2u7m5UgENbHRtdDVclOUP+FMPlCNdmk0h/ysGyo2tavMJEDqJAkJdRa1vMA==", - "dev": true, - "requires": { - "call-bind": "^1.0.2", - "define-properties": "^1.1.3", - "es-abstract": "^1.19.0", - "functions-have-names": "^1.2.2" - } - }, - "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==", - "dev": true - }, - "functions-have-names": { - "version": "1.2.3", - "resolved": "https://registry.npmjs.org/functions-have-names/-/functions-have-names-1.2.3.tgz", - "integrity": "sha512-xckBUXyTIqT97tq2x2AMb+g163b5JFysYk0x4qxNFwbfQkmNZoiRHb6sPzI9/QV33WeuvVYBUIiD4NzNIyqaRQ==", - "dev": true - }, - "ganache-core": { - "version": "2.13.2", - "resolved": "https://registry.npmjs.org/ganache-core/-/ganache-core-2.13.2.tgz", - "integrity": "sha512-tIF5cR+ANQz0+3pHWxHjIwHqFXcVo0Mb+kcsNhglNFALcYo49aQpnS9dqHartqPfMFjiHh/qFoD3mYK0d/qGgw==", - "dev": true, - "requires": { - "abstract-leveldown": "3.0.0", - "async": "2.6.2", - "bip39": "2.5.0", - "cachedown": "1.0.0", - "clone": "2.1.2", - "debug": "3.2.6", - "encoding-down": "5.0.4", - "eth-sig-util": "3.0.0", - "ethereumjs-abi": "0.6.8", - "ethereumjs-account": "3.0.0", - "ethereumjs-block": "2.2.2", - "ethereumjs-common": "1.5.0", - "ethereumjs-tx": "2.1.2", - "ethereumjs-util": "6.2.1", - "ethereumjs-vm": "4.2.0", - "ethereumjs-wallet": "0.6.5", - "heap": "0.2.6", - "keccak": "3.0.1", - "level-sublevel": "6.6.4", - "levelup": "3.1.1", - "lodash": "4.17.20", - "lru-cache": "5.1.1", - "merkle-patricia-tree": "3.0.0", - "patch-package": "6.2.2", - "seedrandom": "3.0.1", - "source-map-support": "0.5.12", - "tmp": "0.1.0", - "web3": "1.2.11", - "web3-provider-engine": "14.2.1", - "websocket": "1.0.32" - }, - "dependencies": { - "@ethersproject/abi": { - "version": "5.0.0-beta.153", - "dev": true, - "optional": true, - "requires": { - "@ethersproject/address": ">=5.0.0-beta.128", - "@ethersproject/bignumber": ">=5.0.0-beta.130", - "@ethersproject/bytes": ">=5.0.0-beta.129", - "@ethersproject/constants": ">=5.0.0-beta.128", - "@ethersproject/hash": ">=5.0.0-beta.128", - "@ethersproject/keccak256": ">=5.0.0-beta.127", - "@ethersproject/logger": ">=5.0.0-beta.129", - "@ethersproject/properties": ">=5.0.0-beta.131", - "@ethersproject/strings": ">=5.0.0-beta.130" - } - }, - "@ethersproject/abstract-provider": { - "version": "5.0.8", - "dev": true, - "optional": true, - "requires": { - "@ethersproject/bignumber": "^5.0.13", - "@ethersproject/bytes": "^5.0.9", - "@ethersproject/logger": "^5.0.8", - "@ethersproject/networks": "^5.0.7", - "@ethersproject/properties": "^5.0.7", - "@ethersproject/transactions": "^5.0.9", - "@ethersproject/web": "^5.0.12" - } - }, - "@ethersproject/abstract-signer": { - "version": "5.0.10", - "dev": true, - "optional": true, - "requires": { - "@ethersproject/abstract-provider": "^5.0.8", - "@ethersproject/bignumber": "^5.0.13", - "@ethersproject/bytes": "^5.0.9", - "@ethersproject/logger": "^5.0.8", - "@ethersproject/properties": "^5.0.7" - } - }, - "@ethersproject/address": { - "version": "5.0.9", - "dev": true, - "optional": true, - "requires": { - "@ethersproject/bignumber": "^5.0.13", - "@ethersproject/bytes": "^5.0.9", - "@ethersproject/keccak256": "^5.0.7", - "@ethersproject/logger": "^5.0.8", - "@ethersproject/rlp": "^5.0.7" - } - }, - "@ethersproject/base64": { - "version": "5.0.7", - "dev": true, - "optional": true, - "requires": { - "@ethersproject/bytes": "^5.0.9" - } - }, - "@ethersproject/bignumber": { - "version": "5.0.13", - "dev": true, - "optional": true, - "requires": { - "@ethersproject/bytes": "^5.0.9", - "@ethersproject/logger": "^5.0.8", - "bn.js": "^4.4.0" - } - }, - "@ethersproject/bytes": { - "version": "5.0.9", - "dev": true, - "optional": true, - "requires": { - "@ethersproject/logger": "^5.0.8" - } - }, - "@ethersproject/constants": { - "version": "5.0.8", - "dev": true, - "optional": true, - "requires": { - "@ethersproject/bignumber": "^5.0.13" - } - }, - "@ethersproject/hash": { - "version": "5.0.10", - "dev": true, - "optional": true, - "requires": { - "@ethersproject/abstract-signer": "^5.0.10", - "@ethersproject/address": "^5.0.9", - "@ethersproject/bignumber": "^5.0.13", - "@ethersproject/bytes": "^5.0.9", - "@ethersproject/keccak256": "^5.0.7", - "@ethersproject/logger": "^5.0.8", - "@ethersproject/properties": "^5.0.7", - "@ethersproject/strings": "^5.0.8" - } - }, - "@ethersproject/keccak256": { - "version": "5.0.7", - "dev": true, - "optional": true, - "requires": { - "@ethersproject/bytes": "^5.0.9", - "js-sha3": "0.5.7" - } - }, - "@ethersproject/logger": { - "version": "5.0.8", - "dev": true, - "optional": true - }, - "@ethersproject/networks": { - "version": "5.0.7", - "dev": true, - "optional": true, - "requires": { - "@ethersproject/logger": "^5.0.8" - } - }, - "@ethersproject/properties": { - "version": "5.0.7", - "dev": true, - "optional": true, - "requires": { - "@ethersproject/logger": "^5.0.8" - } - }, - "@ethersproject/rlp": { - "version": "5.0.7", - "dev": true, - "optional": true, - "requires": { - "@ethersproject/bytes": "^5.0.9", - "@ethersproject/logger": "^5.0.8" - } - }, - "@ethersproject/signing-key": { - "version": "5.0.8", - "dev": true, - "optional": true, - "requires": { - "@ethersproject/bytes": "^5.0.9", - "@ethersproject/logger": "^5.0.8", - "@ethersproject/properties": "^5.0.7", - "elliptic": "6.5.3" - } - }, - "@ethersproject/strings": { - "version": "5.0.8", - "dev": true, - "optional": true, - "requires": { - "@ethersproject/bytes": "^5.0.9", - "@ethersproject/constants": "^5.0.8", - "@ethersproject/logger": "^5.0.8" - } - }, - "@ethersproject/transactions": { - "version": "5.0.9", - "dev": true, - "optional": true, - "requires": { - "@ethersproject/address": "^5.0.9", - "@ethersproject/bignumber": "^5.0.13", - "@ethersproject/bytes": "^5.0.9", - "@ethersproject/constants": "^5.0.8", - "@ethersproject/keccak256": "^5.0.7", - "@ethersproject/logger": "^5.0.8", - "@ethersproject/properties": "^5.0.7", - "@ethersproject/rlp": "^5.0.7", - "@ethersproject/signing-key": "^5.0.8" - } - }, - "@ethersproject/web": { - "version": "5.0.12", - "dev": true, - "optional": true, - "requires": { - "@ethersproject/base64": "^5.0.7", - "@ethersproject/bytes": "^5.0.9", - "@ethersproject/logger": "^5.0.8", - "@ethersproject/properties": "^5.0.7", - "@ethersproject/strings": "^5.0.8" - } - }, - "@sindresorhus/is": { - "version": "0.14.0", - "dev": true, - "optional": true - }, - "@szmarczak/http-timer": { - "version": "1.1.2", - "dev": true, - "optional": true, - "requires": { - "defer-to-connect": "^1.0.1" - } - }, - "@types/bn.js": { - "version": "4.11.6", - "dev": true, - "requires": { - "@types/node": "*" - } - }, - "@types/node": { - "version": "14.14.20", - "dev": true - }, - "@types/pbkdf2": { - "version": "3.1.0", - "dev": true, - "requires": { - "@types/node": "*" - } - }, - "@types/secp256k1": { - "version": "4.0.1", - "dev": true, - "requires": { - "@types/node": "*" - } - }, - "@yarnpkg/lockfile": { - "version": "1.1.0", - "dev": true - }, - "abstract-leveldown": { - "version": "3.0.0", - "dev": true, - "requires": { - "xtend": "~4.0.0" - } - }, - "accepts": { - "version": "1.3.7", - "dev": true, - "optional": true, - "requires": { - "mime-types": "~2.1.24", - "negotiator": "0.6.2" - } - }, - "aes-js": { - "version": "3.1.2", - "dev": true, - "optional": true - }, - "ajv": { - "version": "6.12.6", - "dev": true, - "requires": { - "fast-deep-equal": "^3.1.1", - "fast-json-stable-stringify": "^2.0.0", - "json-schema-traverse": "^0.4.1", - "uri-js": "^4.2.2" - } - }, - "ansi-styles": { - "version": "3.2.1", - "dev": true, - "requires": { - "color-convert": "^1.9.0" - } - }, - "arr-diff": { - "version": "4.0.0", - "dev": true - }, - "arr-flatten": { - "version": "1.1.0", - "dev": true - }, - "arr-union": { - "version": "3.1.0", - "dev": true - }, - "array-flatten": { - "version": "1.1.1", - "dev": true, - "optional": true - }, - "array-unique": { - "version": "0.3.2", - "dev": true - }, - "asn1": { - "version": "0.2.4", - "dev": true, - "requires": { - "safer-buffer": "~2.1.0" - } - }, - "asn1.js": { - "version": "5.4.1", - "dev": true, - "optional": true, - "requires": { - "bn.js": "^4.0.0", - "inherits": "^2.0.1", - "minimalistic-assert": "^1.0.0", - "safer-buffer": "^2.1.0" - } - }, - "assert-plus": { - "version": "1.0.0", - "dev": true - }, - "assign-symbols": { - "version": "1.0.0", - "dev": true - }, - "async": { - "version": "2.6.2", - "dev": true, - "requires": { - "lodash": "^4.17.11" - } - }, - "async-eventemitter": { - "version": "0.2.4", - "dev": true, - "requires": { - "async": "^2.4.0" - } - }, - "async-limiter": { - "version": "1.0.1", - "dev": true - }, - "asynckit": { - "version": "0.4.0", - "dev": true - }, - "atob": { - "version": "2.1.2", - "dev": true - }, - "aws-sign2": { - "version": "0.7.0", - "dev": true - }, - "aws4": { - "version": "1.11.0", - "dev": true - }, - "babel-code-frame": { - "version": "6.26.0", - "dev": true, - "requires": { - "chalk": "^1.1.3", - "esutils": "^2.0.2", - "js-tokens": "^3.0.2" - }, - "dependencies": { - "ansi-regex": { - "version": "2.1.1", - "dev": true - }, - "ansi-styles": { - "version": "2.2.1", - "dev": true - }, - "chalk": { - "version": "1.1.3", - "dev": true, - "requires": { - "ansi-styles": "^2.2.1", - "escape-string-regexp": "^1.0.2", - "has-ansi": "^2.0.0", - "strip-ansi": "^3.0.0", - "supports-color": "^2.0.0" - } - }, - "js-tokens": { - "version": "3.0.2", - "dev": true - }, - "strip-ansi": { - "version": "3.0.1", - "dev": true, - "requires": { - "ansi-regex": "^2.0.0" - } - }, - "supports-color": { - "version": "2.0.0", - "dev": true - } - } - }, - "babel-core": { - "version": "6.26.3", - "dev": true, - "requires": { - "babel-code-frame": "^6.26.0", - "babel-generator": "^6.26.0", - "babel-helpers": "^6.24.1", - "babel-messages": "^6.23.0", - "babel-register": "^6.26.0", - "babel-runtime": "^6.26.0", - "babel-template": "^6.26.0", - "babel-traverse": "^6.26.0", - "babel-types": "^6.26.0", - "babylon": "^6.18.0", - "convert-source-map": "^1.5.1", - "debug": "^2.6.9", - "json5": "^0.5.1", - "lodash": "^4.17.4", - "minimatch": "^3.0.4", - "path-is-absolute": "^1.0.1", - "private": "^0.1.8", - "slash": "^1.0.0", - "source-map": "^0.5.7" - }, - "dependencies": { - "debug": { - "version": "2.6.9", - "dev": true, - "requires": { - "ms": "2.0.0" - } - }, - "json5": { - "version": "0.5.1", - "dev": true - }, - "ms": { - "version": "2.0.0", - "dev": true - }, - "slash": { - "version": "1.0.0", - "dev": true - } - } - }, - "babel-generator": { - "version": "6.26.1", - "dev": true, - "requires": { - "babel-messages": "^6.23.0", - "babel-runtime": "^6.26.0", - "babel-types": "^6.26.0", - "detect-indent": "^4.0.0", - "jsesc": "^1.3.0", - "lodash": "^4.17.4", - "source-map": "^0.5.7", - "trim-right": "^1.0.1" - }, - "dependencies": { - "jsesc": { - "version": "1.3.0", - "dev": true - } - } - }, - "babel-helper-builder-binary-assignment-operator-visitor": { - "version": "6.24.1", - "dev": true, - "requires": { - "babel-helper-explode-assignable-expression": "^6.24.1", - "babel-runtime": "^6.22.0", - "babel-types": "^6.24.1" - } - }, - "babel-helper-call-delegate": { - "version": "6.24.1", - "dev": true, - "requires": { - "babel-helper-hoist-variables": "^6.24.1", - "babel-runtime": "^6.22.0", - "babel-traverse": "^6.24.1", - "babel-types": "^6.24.1" - } - }, - "babel-helper-define-map": { - "version": "6.26.0", - "dev": true, - "requires": { - "babel-helper-function-name": "^6.24.1", - "babel-runtime": "^6.26.0", - "babel-types": "^6.26.0", - "lodash": "^4.17.4" - } - }, - "babel-helper-explode-assignable-expression": { - "version": "6.24.1", - "dev": true, - "requires": { - "babel-runtime": "^6.22.0", - "babel-traverse": "^6.24.1", - "babel-types": "^6.24.1" - } - }, - "babel-helper-function-name": { - "version": "6.24.1", - "dev": true, - "requires": { - "babel-helper-get-function-arity": "^6.24.1", - "babel-runtime": "^6.22.0", - "babel-template": "^6.24.1", - "babel-traverse": "^6.24.1", - "babel-types": "^6.24.1" - } - }, - "babel-helper-get-function-arity": { - "version": "6.24.1", - "dev": true, - "requires": { - "babel-runtime": "^6.22.0", - "babel-types": "^6.24.1" - } - }, - "babel-helper-hoist-variables": { - "version": "6.24.1", - "dev": true, - "requires": { - "babel-runtime": "^6.22.0", - "babel-types": "^6.24.1" - } - }, - "babel-helper-optimise-call-expression": { - "version": "6.24.1", - "dev": true, - "requires": { - "babel-runtime": "^6.22.0", - "babel-types": "^6.24.1" - } - }, - "babel-helper-regex": { - "version": "6.26.0", - "dev": true, - "requires": { - "babel-runtime": "^6.26.0", - "babel-types": "^6.26.0", - "lodash": "^4.17.4" - } - }, - "babel-helper-remap-async-to-generator": { - "version": "6.24.1", - "dev": true, - "requires": { - "babel-helper-function-name": "^6.24.1", - "babel-runtime": "^6.22.0", - "babel-template": "^6.24.1", - "babel-traverse": "^6.24.1", - "babel-types": "^6.24.1" - } - }, - "babel-helper-replace-supers": { - "version": "6.24.1", - "dev": true, - "requires": { - "babel-helper-optimise-call-expression": "^6.24.1", - "babel-messages": "^6.23.0", - "babel-runtime": "^6.22.0", - "babel-template": "^6.24.1", - "babel-traverse": "^6.24.1", - "babel-types": "^6.24.1" - } - }, - "babel-helpers": { - "version": "6.24.1", - "dev": true, - "requires": { - "babel-runtime": "^6.22.0", - "babel-template": "^6.24.1" - } - }, - "babel-messages": { - "version": "6.23.0", - "dev": true, - "requires": { - "babel-runtime": "^6.22.0" - } - }, - "babel-plugin-check-es2015-constants": { - "version": "6.22.0", - "dev": true, - "requires": { - "babel-runtime": "^6.22.0" - } - }, - "babel-plugin-syntax-async-functions": { - "version": "6.13.0", - "dev": true - }, - "babel-plugin-syntax-exponentiation-operator": { - "version": "6.13.0", - "dev": true - }, - "babel-plugin-syntax-trailing-function-commas": { - "version": "6.22.0", - "dev": true - }, - "babel-plugin-transform-async-to-generator": { - "version": "6.24.1", - "dev": true, - "requires": { - "babel-helper-remap-async-to-generator": "^6.24.1", - "babel-plugin-syntax-async-functions": "^6.8.0", - "babel-runtime": "^6.22.0" - } - }, - "babel-plugin-transform-es2015-arrow-functions": { - "version": "6.22.0", - "dev": true, - "requires": { - "babel-runtime": "^6.22.0" - } - }, - "babel-plugin-transform-es2015-block-scoped-functions": { - "version": "6.22.0", - "dev": true, - "requires": { - "babel-runtime": "^6.22.0" - } - }, - "babel-plugin-transform-es2015-block-scoping": { - "version": "6.26.0", - "dev": true, - "requires": { - "babel-runtime": "^6.26.0", - "babel-template": "^6.26.0", - "babel-traverse": "^6.26.0", - "babel-types": "^6.26.0", - "lodash": "^4.17.4" - } - }, - "babel-plugin-transform-es2015-classes": { - "version": "6.24.1", - "dev": true, - "requires": { - "babel-helper-define-map": "^6.24.1", - "babel-helper-function-name": "^6.24.1", - "babel-helper-optimise-call-expression": "^6.24.1", - "babel-helper-replace-supers": "^6.24.1", - "babel-messages": "^6.23.0", - "babel-runtime": "^6.22.0", - "babel-template": "^6.24.1", - "babel-traverse": "^6.24.1", - "babel-types": "^6.24.1" - } - }, - "babel-plugin-transform-es2015-computed-properties": { - "version": "6.24.1", - "dev": true, - "requires": { - "babel-runtime": "^6.22.0", - "babel-template": "^6.24.1" - } - }, - "babel-plugin-transform-es2015-destructuring": { - "version": "6.23.0", - "dev": true, - "requires": { - "babel-runtime": "^6.22.0" - } - }, - "babel-plugin-transform-es2015-duplicate-keys": { - "version": "6.24.1", - "dev": true, - "requires": { - "babel-runtime": "^6.22.0", - "babel-types": "^6.24.1" - } - }, - "babel-plugin-transform-es2015-for-of": { - "version": "6.23.0", - "dev": true, - "requires": { - "babel-runtime": "^6.22.0" - } - }, - "babel-plugin-transform-es2015-function-name": { - "version": "6.24.1", - "dev": true, - "requires": { - "babel-helper-function-name": "^6.24.1", - "babel-runtime": "^6.22.0", - "babel-types": "^6.24.1" - } - }, - "babel-plugin-transform-es2015-literals": { - "version": "6.22.0", - "dev": true, - "requires": { - "babel-runtime": "^6.22.0" - } - }, - "babel-plugin-transform-es2015-modules-amd": { - "version": "6.24.1", - "dev": true, - "requires": { - "babel-plugin-transform-es2015-modules-commonjs": "^6.24.1", - "babel-runtime": "^6.22.0", - "babel-template": "^6.24.1" - } - }, - "babel-plugin-transform-es2015-modules-commonjs": { - "version": "6.26.2", - "dev": true, - "requires": { - "babel-plugin-transform-strict-mode": "^6.24.1", - "babel-runtime": "^6.26.0", - "babel-template": "^6.26.0", - "babel-types": "^6.26.0" - } - }, - "babel-plugin-transform-es2015-modules-systemjs": { - "version": "6.24.1", - "dev": true, - "requires": { - "babel-helper-hoist-variables": "^6.24.1", - "babel-runtime": "^6.22.0", - "babel-template": "^6.24.1" - } - }, - "babel-plugin-transform-es2015-modules-umd": { - "version": "6.24.1", - "dev": true, - "requires": { - "babel-plugin-transform-es2015-modules-amd": "^6.24.1", - "babel-runtime": "^6.22.0", - "babel-template": "^6.24.1" - } - }, - "babel-plugin-transform-es2015-object-super": { - "version": "6.24.1", - "dev": true, - "requires": { - "babel-helper-replace-supers": "^6.24.1", - "babel-runtime": "^6.22.0" - } - }, - "babel-plugin-transform-es2015-parameters": { - "version": "6.24.1", - "dev": true, - "requires": { - "babel-helper-call-delegate": "^6.24.1", - "babel-helper-get-function-arity": "^6.24.1", - "babel-runtime": "^6.22.0", - "babel-template": "^6.24.1", - "babel-traverse": "^6.24.1", - "babel-types": "^6.24.1" - } - }, - "babel-plugin-transform-es2015-shorthand-properties": { - "version": "6.24.1", - "dev": true, - "requires": { - "babel-runtime": "^6.22.0", - "babel-types": "^6.24.1" - } - }, - "babel-plugin-transform-es2015-spread": { - "version": "6.22.0", - "dev": true, - "requires": { - "babel-runtime": "^6.22.0" - } - }, - "babel-plugin-transform-es2015-sticky-regex": { - "version": "6.24.1", - "dev": true, - "requires": { - "babel-helper-regex": "^6.24.1", - "babel-runtime": "^6.22.0", - "babel-types": "^6.24.1" - } - }, - "babel-plugin-transform-es2015-template-literals": { - "version": "6.22.0", - "dev": true, - "requires": { - "babel-runtime": "^6.22.0" - } - }, - "babel-plugin-transform-es2015-typeof-symbol": { - "version": "6.23.0", - "dev": true, - "requires": { - "babel-runtime": "^6.22.0" - } - }, - "babel-plugin-transform-es2015-unicode-regex": { - "version": "6.24.1", - "dev": true, - "requires": { - "babel-helper-regex": "^6.24.1", - "babel-runtime": "^6.22.0", - "regexpu-core": "^2.0.0" - } - }, - "babel-plugin-transform-exponentiation-operator": { - "version": "6.24.1", - "dev": true, - "requires": { - "babel-helper-builder-binary-assignment-operator-visitor": "^6.24.1", - "babel-plugin-syntax-exponentiation-operator": "^6.8.0", - "babel-runtime": "^6.22.0" - } - }, - "babel-plugin-transform-regenerator": { - "version": "6.26.0", - "dev": true, - "requires": { - "regenerator-transform": "^0.10.0" - } - }, - "babel-plugin-transform-strict-mode": { - "version": "6.24.1", - "dev": true, - "requires": { - "babel-runtime": "^6.22.0", - "babel-types": "^6.24.1" - } - }, - "babel-preset-env": { - "version": "1.7.0", - "dev": true, - "requires": { - "babel-plugin-check-es2015-constants": "^6.22.0", - "babel-plugin-syntax-trailing-function-commas": "^6.22.0", - "babel-plugin-transform-async-to-generator": "^6.22.0", - "babel-plugin-transform-es2015-arrow-functions": "^6.22.0", - "babel-plugin-transform-es2015-block-scoped-functions": "^6.22.0", - "babel-plugin-transform-es2015-block-scoping": "^6.23.0", - "babel-plugin-transform-es2015-classes": "^6.23.0", - "babel-plugin-transform-es2015-computed-properties": "^6.22.0", - "babel-plugin-transform-es2015-destructuring": "^6.23.0", - "babel-plugin-transform-es2015-duplicate-keys": "^6.22.0", - "babel-plugin-transform-es2015-for-of": "^6.23.0", - "babel-plugin-transform-es2015-function-name": "^6.22.0", - "babel-plugin-transform-es2015-literals": "^6.22.0", - "babel-plugin-transform-es2015-modules-amd": "^6.22.0", - "babel-plugin-transform-es2015-modules-commonjs": "^6.23.0", - "babel-plugin-transform-es2015-modules-systemjs": "^6.23.0", - "babel-plugin-transform-es2015-modules-umd": "^6.23.0", - "babel-plugin-transform-es2015-object-super": "^6.22.0", - "babel-plugin-transform-es2015-parameters": "^6.23.0", - "babel-plugin-transform-es2015-shorthand-properties": "^6.22.0", - "babel-plugin-transform-es2015-spread": "^6.22.0", - "babel-plugin-transform-es2015-sticky-regex": "^6.22.0", - "babel-plugin-transform-es2015-template-literals": "^6.22.0", - "babel-plugin-transform-es2015-typeof-symbol": "^6.23.0", - "babel-plugin-transform-es2015-unicode-regex": "^6.22.0", - "babel-plugin-transform-exponentiation-operator": "^6.22.0", - "babel-plugin-transform-regenerator": "^6.22.0", - "browserslist": "^3.2.6", - "invariant": "^2.2.2", - "semver": "^5.3.0" - }, - "dependencies": { - "semver": { - "version": "5.7.1", - "dev": true - } - } - }, - "babel-register": { - "version": "6.26.0", - "dev": true, - "requires": { - "babel-core": "^6.26.0", - "babel-runtime": "^6.26.0", - "core-js": "^2.5.0", - "home-or-tmp": "^2.0.0", - "lodash": "^4.17.4", - "mkdirp": "^0.5.1", - "source-map-support": "^0.4.15" - }, - "dependencies": { - "source-map-support": { - "version": "0.4.18", - "dev": true, - "requires": { - "source-map": "^0.5.6" - } - } - } - }, - "babel-runtime": { - "version": "6.26.0", - "dev": true, - "requires": { - "core-js": "^2.4.0", - "regenerator-runtime": "^0.11.0" - } - }, - "babel-template": { - "version": "6.26.0", - "dev": true, - "requires": { - "babel-runtime": "^6.26.0", - "babel-traverse": "^6.26.0", - "babel-types": "^6.26.0", - "babylon": "^6.18.0", - "lodash": "^4.17.4" - } - }, - "babel-traverse": { - "version": "6.26.0", - "dev": true, - "requires": { - "babel-code-frame": "^6.26.0", - "babel-messages": "^6.23.0", - "babel-runtime": "^6.26.0", - "babel-types": "^6.26.0", - "babylon": "^6.18.0", - "debug": "^2.6.8", - "globals": "^9.18.0", - "invariant": "^2.2.2", - "lodash": "^4.17.4" - }, - "dependencies": { - "debug": { - "version": "2.6.9", - "dev": true, - "requires": { - "ms": "2.0.0" - } - }, - "globals": { - "version": "9.18.0", - "dev": true - }, - "ms": { - "version": "2.0.0", - "dev": true - } - } - }, - "babel-types": { - "version": "6.26.0", - "dev": true, - "requires": { - "babel-runtime": "^6.26.0", - "esutils": "^2.0.2", - "lodash": "^4.17.4", - "to-fast-properties": "^1.0.3" - }, - "dependencies": { - "to-fast-properties": { - "version": "1.0.3", - "dev": true - } - } - }, - "babelify": { - "version": "7.3.0", - "dev": true, - "requires": { - "babel-core": "^6.0.14", - "object-assign": "^4.0.0" - } - }, - "babylon": { - "version": "6.18.0", - "dev": true - }, - "backoff": { - "version": "2.5.0", - "dev": true, - "requires": { - "precond": "0.2" - } - }, - "balanced-match": { - "version": "1.0.0", - "dev": true - }, - "base": { - "version": "0.11.2", - "dev": true, - "requires": { - "cache-base": "^1.0.1", - "class-utils": "^0.3.5", - "component-emitter": "^1.2.1", - "define-property": "^1.0.0", - "isobject": "^3.0.1", - "mixin-deep": "^1.2.0", - "pascalcase": "^0.1.1" - }, - "dependencies": { - "define-property": { - "version": "1.0.0", - "dev": true, - "requires": { - "is-descriptor": "^1.0.0" - } - } - } - }, - "base-x": { - "version": "3.0.8", - "dev": true, - "requires": { - "safe-buffer": "^5.0.1" - } - }, - "base64-js": { - "version": "1.5.1", - "dev": true - }, - "bcrypt-pbkdf": { - "version": "1.0.2", - "dev": true, - "requires": { - "tweetnacl": "^0.14.3" - }, - "dependencies": { - "tweetnacl": { - "version": "0.14.5", - "dev": true - } - } - }, - "bignumber.js": { - "version": "9.0.1", - "dev": true, - "optional": true - }, - "bip39": { - "version": "2.5.0", - "dev": true, - "requires": { - "create-hash": "^1.1.0", - "pbkdf2": "^3.0.9", - "randombytes": "^2.0.1", - "safe-buffer": "^5.0.1", - "unorm": "^1.3.3" - } - }, - "blakejs": { - "version": "1.1.0", - "dev": true - }, - "bluebird": { - "version": "3.7.2", - "dev": true, - "optional": true - }, - "bn.js": { - "version": "4.11.9", - "dev": true - }, - "body-parser": { - "version": "1.19.0", - "dev": true, - "optional": true, - "requires": { - "bytes": "3.1.0", - "content-type": "~1.0.4", - "debug": "2.6.9", - "depd": "~1.1.2", - "http-errors": "1.7.2", - "iconv-lite": "0.4.24", - "on-finished": "~2.3.0", - "qs": "6.7.0", - "raw-body": "2.4.0", - "type-is": "~1.6.17" - }, - "dependencies": { - "debug": { - "version": "2.6.9", - "dev": true, - "optional": true, - "requires": { - "ms": "2.0.0" - } - }, - "ms": { - "version": "2.0.0", - "dev": true, - "optional": true - }, - "qs": { - "version": "6.7.0", - "dev": true, - "optional": true - } - } - }, - "brace-expansion": { - "version": "1.1.11", - "dev": true, - "requires": { - "balanced-match": "^1.0.0", - "concat-map": "0.0.1" - } - }, - "brorand": { - "version": "1.1.0", - "dev": true - }, - "browserify-aes": { - "version": "1.2.0", - "dev": true, - "requires": { - "buffer-xor": "^1.0.3", - "cipher-base": "^1.0.0", - "create-hash": "^1.1.0", - "evp_bytestokey": "^1.0.3", - "inherits": "^2.0.1", - "safe-buffer": "^5.0.1" - } - }, - "browserify-cipher": { - "version": "1.0.1", - "dev": true, - "optional": true, - "requires": { - "browserify-aes": "^1.0.4", - "browserify-des": "^1.0.0", - "evp_bytestokey": "^1.0.0" - } - }, - "browserify-des": { - "version": "1.0.2", - "dev": true, - "optional": true, - "requires": { - "cipher-base": "^1.0.1", - "des.js": "^1.0.0", - "inherits": "^2.0.1", - "safe-buffer": "^5.1.2" - } - }, - "browserify-rsa": { - "version": "4.1.0", - "dev": true, - "optional": true, - "requires": { - "bn.js": "^5.0.0", - "randombytes": "^2.0.1" - }, - "dependencies": { - "bn.js": { - "version": "5.1.3", - "dev": true, - "optional": true - } - } - }, - "browserify-sign": { - "version": "4.2.1", - "dev": true, - "optional": true, - "requires": { - "bn.js": "^5.1.1", - "browserify-rsa": "^4.0.1", - "create-hash": "^1.2.0", - "create-hmac": "^1.1.7", - "elliptic": "^6.5.3", - "inherits": "^2.0.4", - "parse-asn1": "^5.1.5", - "readable-stream": "^3.6.0", - "safe-buffer": "^5.2.0" - }, - "dependencies": { - "bn.js": { - "version": "5.1.3", - "dev": true, - "optional": true - }, - "readable-stream": { - "version": "3.6.0", - "dev": true, - "optional": true, - "requires": { - "inherits": "^2.0.3", - "string_decoder": "^1.1.1", - "util-deprecate": "^1.0.1" - } - } - } - }, - "browserslist": { - "version": "3.2.8", - "dev": true, - "requires": { - "caniuse-lite": "^1.0.30000844", - "electron-to-chromium": "^1.3.47" - } - }, - "bs58": { - "version": "4.0.1", - "dev": true, - "requires": { - "base-x": "^3.0.2" - } - }, - "bs58check": { - "version": "2.1.2", - "dev": true, - "requires": { - "bs58": "^4.0.0", - "create-hash": "^1.1.0", - "safe-buffer": "^5.1.2" - } - }, - "buffer": { - "version": "5.7.1", - "dev": true, - "requires": { - "base64-js": "^1.3.1", - "ieee754": "^1.1.13" - } - }, - "buffer-from": { - "version": "1.1.1", - "dev": true - }, - "buffer-to-arraybuffer": { - "version": "0.0.5", - "dev": true, - "optional": true - }, - "buffer-xor": { - "version": "1.0.3", - "dev": true - }, - "bufferutil": { - "version": "4.0.3", - "dev": true, - "requires": { - "node-gyp-build": "^4.2.0" - } - }, - "bytes": { - "version": "3.1.0", - "dev": true, - "optional": true - }, - "bytewise": { - "version": "1.1.0", - "dev": true, - "requires": { - "bytewise-core": "^1.2.2", - "typewise": "^1.0.3" - } - }, - "bytewise-core": { - "version": "1.2.3", - "dev": true, - "requires": { - "typewise-core": "^1.2" - } - }, - "cache-base": { - "version": "1.0.1", - "dev": true, - "requires": { - "collection-visit": "^1.0.0", - "component-emitter": "^1.2.1", - "get-value": "^2.0.6", - "has-value": "^1.0.0", - "isobject": "^3.0.1", - "set-value": "^2.0.0", - "to-object-path": "^0.3.0", - "union-value": "^1.0.0", - "unset-value": "^1.0.0" - } - }, - "cacheable-request": { - "version": "6.1.0", - "dev": true, - "optional": true, - "requires": { - "clone-response": "^1.0.2", - "get-stream": "^5.1.0", - "http-cache-semantics": "^4.0.0", - "keyv": "^3.0.0", - "lowercase-keys": "^2.0.0", - "normalize-url": "^4.1.0", - "responselike": "^1.0.2" - }, - "dependencies": { - "lowercase-keys": { - "version": "2.0.0", - "dev": true, - "optional": true - } - } - }, - "cachedown": { - "version": "1.0.0", - "dev": true, - "requires": { - "abstract-leveldown": "^2.4.1", - "lru-cache": "^3.2.0" - }, - "dependencies": { - "abstract-leveldown": { - "version": "2.7.2", - "dev": true, - "requires": { - "xtend": "~4.0.0" - } - }, - "lru-cache": { - "version": "3.2.0", - "dev": true, - "requires": { - "pseudomap": "^1.0.1" - } - } - } - }, - "call-bind": { - "version": "1.0.2", - "dev": true, - "requires": { - "function-bind": "^1.1.1", - "get-intrinsic": "^1.0.2" - } - }, - "caniuse-lite": { - "version": "1.0.30001174", - "dev": true - }, - "caseless": { - "version": "0.12.0", - "dev": true - }, - "chalk": { - "version": "2.4.2", - "dev": true, - "requires": { - "ansi-styles": "^3.2.1", - "escape-string-regexp": "^1.0.5", - "supports-color": "^5.3.0" - } - }, - "checkpoint-store": { - "version": "1.1.0", - "dev": true, - "requires": { - "functional-red-black-tree": "^1.0.1" - } - }, - "chownr": { - "version": "1.1.4", - "dev": true, - "optional": true - }, - "ci-info": { - "version": "2.0.0", - "dev": true - }, - "cids": { - "version": "0.7.5", - "dev": true, - "optional": true, - "requires": { - "buffer": "^5.5.0", - "class-is": "^1.1.0", - "multibase": "~0.6.0", - "multicodec": "^1.0.0", - "multihashes": "~0.4.15" - }, - "dependencies": { - "multicodec": { - "version": "1.0.4", - "dev": true, - "optional": true, - "requires": { - "buffer": "^5.6.0", - "varint": "^5.0.0" - } - } - } - }, - "cipher-base": { - "version": "1.0.4", - "dev": true, - "requires": { - "inherits": "^2.0.1", - "safe-buffer": "^5.0.1" - } - }, - "class-is": { - "version": "1.1.0", - "dev": true, - "optional": true - }, - "class-utils": { - "version": "0.3.6", - "dev": true, - "requires": { - "arr-union": "^3.1.0", - "define-property": "^0.2.5", - "isobject": "^3.0.0", - "static-extend": "^0.1.1" - }, - "dependencies": { - "define-property": { - "version": "0.2.5", - "dev": true, - "requires": { - "is-descriptor": "^0.1.0" - } - }, - "is-accessor-descriptor": { - "version": "0.1.6", - "dev": true, - "requires": { - "kind-of": "^3.0.2" - }, - "dependencies": { - "kind-of": { - "version": "3.2.2", - "dev": true, - "requires": { - "is-buffer": "^1.1.5" - } - } - } - }, - "is-buffer": { - "version": "1.1.6", - "dev": true - }, - "is-data-descriptor": { - "version": "0.1.4", - "dev": true, - "requires": { - "kind-of": "^3.0.2" - }, - "dependencies": { - "kind-of": { - "version": "3.2.2", - "dev": true, - "requires": { - "is-buffer": "^1.1.5" - } - } - } - }, - "is-descriptor": { - "version": "0.1.6", - "dev": true, - "requires": { - "is-accessor-descriptor": "^0.1.6", - "is-data-descriptor": "^0.1.4", - "kind-of": "^5.0.0" - } - }, - "kind-of": { - "version": "5.1.0", - "dev": true - } - } - }, - "clone": { - "version": "2.1.2", - "dev": true - }, - "clone-response": { - "version": "1.0.2", - "dev": true, - "optional": true, - "requires": { - "mimic-response": "^1.0.0" - } - }, - "collection-visit": { - "version": "1.0.0", - "dev": true, - "requires": { - "map-visit": "^1.0.0", - "object-visit": "^1.0.0" - } - }, - "color-convert": { - "version": "1.9.3", - "dev": true, - "requires": { - "color-name": "1.1.3" - } - }, - "color-name": { - "version": "1.1.3", - "dev": true - }, - "combined-stream": { - "version": "1.0.8", - "dev": true, - "requires": { - "delayed-stream": "~1.0.0" - } - }, - "component-emitter": { - "version": "1.3.0", - "dev": true - }, - "concat-map": { - "version": "0.0.1", - "dev": true - }, - "concat-stream": { - "version": "1.6.2", - "dev": true, - "requires": { - "buffer-from": "^1.0.0", - "inherits": "^2.0.3", - "readable-stream": "^2.2.2", - "typedarray": "^0.0.6" - } - }, - "content-disposition": { - "version": "0.5.3", - "dev": true, - "optional": true, - "requires": { - "safe-buffer": "5.1.2" - }, - "dependencies": { - "safe-buffer": { - "version": "5.1.2", - "dev": true, - "optional": true - } - } - }, - "content-hash": { - "version": "2.5.2", - "dev": true, - "optional": true, - "requires": { - "cids": "^0.7.1", - "multicodec": "^0.5.5", - "multihashes": "^0.4.15" - } - }, - "content-type": { - "version": "1.0.4", - "dev": true, - "optional": true - }, - "convert-source-map": { - "version": "1.7.0", - "dev": true, - "requires": { - "safe-buffer": "~5.1.1" - }, - "dependencies": { - "safe-buffer": { - "version": "5.1.2", - "dev": true - } - } - }, - "cookie": { - "version": "0.4.0", - "dev": true, - "optional": true - }, - "cookie-signature": { - "version": "1.0.6", - "dev": true, - "optional": true - }, - "cookiejar": { - "version": "2.1.2", - "dev": true, - "optional": true - }, - "copy-descriptor": { - "version": "0.1.1", - "dev": true - }, - "core-js": { - "version": "2.6.12", - "dev": true - }, - "core-js-pure": { - "version": "3.8.2", - "dev": true - }, - "core-util-is": { - "version": "1.0.2", - "dev": true - }, - "cors": { - "version": "2.8.5", - "dev": true, - "optional": true, - "requires": { - "object-assign": "^4", - "vary": "^1" - } - }, - "create-ecdh": { - "version": "4.0.4", - "dev": true, - "optional": true, - "requires": { - "bn.js": "^4.1.0", - "elliptic": "^6.5.3" - } - }, - "create-hash": { - "version": "1.2.0", - "dev": true, - "requires": { - "cipher-base": "^1.0.1", - "inherits": "^2.0.1", - "md5.js": "^1.3.4", - "ripemd160": "^2.0.1", - "sha.js": "^2.4.0" - } - }, - "create-hmac": { - "version": "1.1.7", - "dev": true, - "requires": { - "cipher-base": "^1.0.3", - "create-hash": "^1.1.0", - "inherits": "^2.0.1", - "ripemd160": "^2.0.0", - "safe-buffer": "^5.0.1", - "sha.js": "^2.4.8" - } - }, - "cross-fetch": { - "version": "2.2.3", - "dev": true, - "requires": { - "node-fetch": "2.1.2", - "whatwg-fetch": "2.0.4" - } - }, - "crypto-browserify": { - "version": "3.12.0", - "dev": true, - "optional": true, - "requires": { - "browserify-cipher": "^1.0.0", - "browserify-sign": "^4.0.0", - "create-ecdh": "^4.0.0", - "create-hash": "^1.1.0", - "create-hmac": "^1.1.0", - "diffie-hellman": "^5.0.0", - "inherits": "^2.0.1", - "pbkdf2": "^3.0.3", - "public-encrypt": "^4.0.0", - "randombytes": "^2.0.0", - "randomfill": "^1.0.3" - } - }, - "d": { - "version": "1.0.1", - "dev": true, - "requires": { - "es5-ext": "^0.10.50", - "type": "^1.0.1" - } - }, - "dashdash": { - "version": "1.14.1", - "dev": true, - "requires": { - "assert-plus": "^1.0.0" - } - }, - "debug": { - "version": "3.2.6", - "dev": true, - "requires": { - "ms": "^2.1.1" - } - }, - "decode-uri-component": { - "version": "0.2.0", - "dev": true - }, - "decompress-response": { - "version": "3.3.0", - "dev": true, - "optional": true, - "requires": { - "mimic-response": "^1.0.0" - } - }, - "deep-equal": { - "version": "1.1.1", - "dev": true, - "requires": { - "is-arguments": "^1.0.4", - "is-date-object": "^1.0.1", - "is-regex": "^1.0.4", - "object-is": "^1.0.1", - "object-keys": "^1.1.1", - "regexp.prototype.flags": "^1.2.0" - } - }, - "defer-to-connect": { - "version": "1.1.3", - "dev": true, - "optional": true - }, - "deferred-leveldown": { - "version": "4.0.2", - "dev": true, - "requires": { - "abstract-leveldown": "~5.0.0", - "inherits": "^2.0.3" - }, - "dependencies": { - "abstract-leveldown": { - "version": "5.0.0", - "dev": true, - "requires": { - "xtend": "~4.0.0" - } - } - } - }, - "define-properties": { - "version": "1.1.3", - "dev": true, - "requires": { - "object-keys": "^1.0.12" - } - }, - "define-property": { - "version": "2.0.2", - "dev": true, - "requires": { - "is-descriptor": "^1.0.2", - "isobject": "^3.0.1" - } - }, - "defined": { - "version": "1.0.0", - "dev": true - }, - "delayed-stream": { - "version": "1.0.0", - "dev": true - }, - "depd": { - "version": "1.1.2", - "dev": true, - "optional": true - }, - "des.js": { - "version": "1.0.1", - "dev": true, - "optional": true, - "requires": { - "inherits": "^2.0.1", - "minimalistic-assert": "^1.0.0" - } - }, - "destroy": { - "version": "1.0.4", - "dev": true, - "optional": true - }, - "detect-indent": { - "version": "4.0.0", - "dev": true, - "requires": { - "repeating": "^2.0.0" - } - }, - "diffie-hellman": { - "version": "5.0.3", - "dev": true, - "optional": true, - "requires": { - "bn.js": "^4.1.0", - "miller-rabin": "^4.0.0", - "randombytes": "^2.0.0" - } - }, - "dom-walk": { - "version": "0.1.2", - "dev": true - }, - "dotignore": { - "version": "0.1.2", - "dev": true, - "requires": { - "minimatch": "^3.0.4" - } - }, - "duplexer3": { - "version": "0.1.4", - "dev": true, - "optional": true - }, - "ecc-jsbn": { - "version": "0.1.2", - "dev": true, - "requires": { - "jsbn": "~0.1.0", - "safer-buffer": "^2.1.0" - } - }, - "ee-first": { - "version": "1.1.1", - "dev": true, - "optional": true - }, - "electron-to-chromium": { - "version": "1.3.636", - "dev": true - }, - "elliptic": { - "version": "6.5.3", - "dev": true, - "requires": { - "bn.js": "^4.4.0", - "brorand": "^1.0.1", - "hash.js": "^1.0.0", - "hmac-drbg": "^1.0.0", - "inherits": "^2.0.1", - "minimalistic-assert": "^1.0.0", - "minimalistic-crypto-utils": "^1.0.0" - } - }, - "encodeurl": { - "version": "1.0.2", - "dev": true, - "optional": true - }, - "encoding": { - "version": "0.1.13", - "dev": true, - "requires": { - "iconv-lite": "^0.6.2" - }, - "dependencies": { - "iconv-lite": { - "version": "0.6.2", - "dev": true, - "requires": { - "safer-buffer": ">= 2.1.2 < 3.0.0" - } - } - } - }, - "encoding-down": { - "version": "5.0.4", - "dev": true, - "requires": { - "abstract-leveldown": "^5.0.0", - "inherits": "^2.0.3", - "level-codec": "^9.0.0", - "level-errors": "^2.0.0", - "xtend": "^4.0.1" - }, - "dependencies": { - "abstract-leveldown": { - "version": "5.0.0", - "dev": true, - "requires": { - "xtend": "~4.0.0" - } - } - } - }, - "end-of-stream": { - "version": "1.4.4", - "dev": true, - "requires": { - "once": "^1.4.0" - } - }, - "errno": { - "version": "0.1.8", - "dev": true, - "requires": { - "prr": "~1.0.1" - } - }, - "es-abstract": { - "version": "1.18.0-next.1", - "dev": true, - "requires": { - "es-to-primitive": "^1.2.1", - "function-bind": "^1.1.1", - "has": "^1.0.3", - "has-symbols": "^1.0.1", - "is-callable": "^1.2.2", - "is-negative-zero": "^2.0.0", - "is-regex": "^1.1.1", - "object-inspect": "^1.8.0", - "object-keys": "^1.1.1", - "object.assign": "^4.1.1", - "string.prototype.trimend": "^1.0.1", - "string.prototype.trimstart": "^1.0.1" - } - }, - "es-to-primitive": { - "version": "1.2.1", - "dev": true, - "requires": { - "is-callable": "^1.1.4", - "is-date-object": "^1.0.1", - "is-symbol": "^1.0.2" - } - }, - "es5-ext": { - "version": "0.10.53", - "dev": true, - "requires": { - "es6-iterator": "~2.0.3", - "es6-symbol": "~3.1.3", - "next-tick": "~1.0.0" - } - }, - "es6-iterator": { - "version": "2.0.3", - "dev": true, - "requires": { - "d": "1", - "es5-ext": "^0.10.35", - "es6-symbol": "^3.1.1" - } - }, - "es6-symbol": { - "version": "3.1.3", - "dev": true, - "requires": { - "d": "^1.0.1", - "ext": "^1.1.2" - } - }, - "escape-html": { - "version": "1.0.3", - "dev": true, - "optional": true - }, - "escape-string-regexp": { - "version": "1.0.5", - "dev": true - }, - "esutils": { - "version": "2.0.3", - "dev": true - }, - "etag": { - "version": "1.8.1", - "dev": true, - "optional": true - }, - "eth-block-tracker": { - "version": "3.0.1", - "dev": true, - "requires": { - "eth-query": "^2.1.0", - "ethereumjs-tx": "^1.3.3", - "ethereumjs-util": "^5.1.3", - "ethjs-util": "^0.1.3", - "json-rpc-engine": "^3.6.0", - "pify": "^2.3.0", - "tape": "^4.6.3" - }, - "dependencies": { - "ethereumjs-tx": { - "version": "1.3.7", - "dev": true, - "requires": { - "ethereum-common": "^0.0.18", - "ethereumjs-util": "^5.0.0" - } - }, - "ethereumjs-util": { - "version": "5.2.1", - "dev": true, - "requires": { - "bn.js": "^4.11.0", - "create-hash": "^1.1.2", - "elliptic": "^6.5.2", - "ethereum-cryptography": "^0.1.3", - "ethjs-util": "^0.1.3", - "rlp": "^2.0.0", - "safe-buffer": "^5.1.1" - } - }, - "pify": { - "version": "2.3.0", - "dev": true - } - } - }, - "eth-ens-namehash": { - "version": "2.0.8", - "dev": true, - "optional": true, - "requires": { - "idna-uts46-hx": "^2.3.1", - "js-sha3": "^0.5.7" - } - }, - "eth-json-rpc-infura": { - "version": "3.2.1", - "dev": true, - "requires": { - "cross-fetch": "^2.1.1", - "eth-json-rpc-middleware": "^1.5.0", - "json-rpc-engine": "^3.4.0", - "json-rpc-error": "^2.0.0" - } - }, - "eth-json-rpc-middleware": { - "version": "1.6.0", - "dev": true, - "requires": { - "async": "^2.5.0", - "eth-query": "^2.1.2", - "eth-tx-summary": "^3.1.2", - "ethereumjs-block": "^1.6.0", - "ethereumjs-tx": "^1.3.3", - "ethereumjs-util": "^5.1.2", - "ethereumjs-vm": "^2.1.0", - "fetch-ponyfill": "^4.0.0", - "json-rpc-engine": "^3.6.0", - "json-rpc-error": "^2.0.0", - "json-stable-stringify": "^1.0.1", - "promise-to-callback": "^1.0.0", - "tape": "^4.6.3" - }, - "dependencies": { - "abstract-leveldown": { - "version": "2.6.3", - "dev": true, - "requires": { - "xtend": "~4.0.0" - } - }, - "deferred-leveldown": { - "version": "1.2.2", - "dev": true, - "requires": { - "abstract-leveldown": "~2.6.0" - } - }, - "ethereumjs-account": { - "version": "2.0.5", - "dev": true, - "requires": { - "ethereumjs-util": "^5.0.0", - "rlp": "^2.0.0", - "safe-buffer": "^5.1.1" - } - }, - "ethereumjs-block": { - "version": "1.7.1", - "dev": true, - "requires": { - "async": "^2.0.1", - "ethereum-common": "0.2.0", - "ethereumjs-tx": "^1.2.2", - "ethereumjs-util": "^5.0.0", - "merkle-patricia-tree": "^2.1.2" - }, - "dependencies": { - "ethereum-common": { - "version": "0.2.0", - "dev": true - } - } - }, - "ethereumjs-tx": { - "version": "1.3.7", - "dev": true, - "requires": { - "ethereum-common": "^0.0.18", - "ethereumjs-util": "^5.0.0" - } - }, - "ethereumjs-util": { - "version": "5.2.1", - "dev": true, - "requires": { - "bn.js": "^4.11.0", - "create-hash": "^1.1.2", - "elliptic": "^6.5.2", - "ethereum-cryptography": "^0.1.3", - "ethjs-util": "^0.1.3", - "rlp": "^2.0.0", - "safe-buffer": "^5.1.1" - } - }, - "ethereumjs-vm": { - "version": "2.6.0", - "dev": true, - "requires": { - "async": "^2.1.2", - "async-eventemitter": "^0.2.2", - "ethereumjs-account": "^2.0.3", - "ethereumjs-block": "~2.2.0", - "ethereumjs-common": "^1.1.0", - "ethereumjs-util": "^6.0.0", - "fake-merkle-patricia-tree": "^1.0.1", - "functional-red-black-tree": "^1.0.1", - "merkle-patricia-tree": "^2.3.2", - "rustbn.js": "~0.2.0", - "safe-buffer": "^5.1.1" - }, - "dependencies": { - "ethereumjs-block": { - "version": "2.2.2", - "dev": true, - "requires": { - "async": "^2.0.1", - "ethereumjs-common": "^1.5.0", - "ethereumjs-tx": "^2.1.1", - "ethereumjs-util": "^5.0.0", - "merkle-patricia-tree": "^2.1.2" - }, - "dependencies": { - "ethereumjs-util": { - "version": "5.2.1", - "dev": true, - "requires": { - "bn.js": "^4.11.0", - "create-hash": "^1.1.2", - "elliptic": "^6.5.2", - "ethereum-cryptography": "^0.1.3", - "ethjs-util": "^0.1.3", - "rlp": "^2.0.0", - "safe-buffer": "^5.1.1" - } - } - } - }, - "ethereumjs-tx": { - "version": "2.1.2", - "dev": true, - "requires": { - "ethereumjs-common": "^1.5.0", - "ethereumjs-util": "^6.0.0" - } - }, - "ethereumjs-util": { - "version": "6.2.1", - "dev": true, - "requires": { - "@types/bn.js": "^4.11.3", - "bn.js": "^4.11.0", - "create-hash": "^1.1.2", - "elliptic": "^6.5.2", - "ethereum-cryptography": "^0.1.3", - "ethjs-util": "0.1.6", - "rlp": "^2.2.3" - } - } - } - }, - "isarray": { - "version": "0.0.1", - "dev": true - }, - "level-codec": { - "version": "7.0.1", - "dev": true - }, - "level-errors": { - "version": "1.0.5", - "dev": true, - "requires": { - "errno": "~0.1.1" - } - }, - "level-iterator-stream": { - "version": "1.3.1", - "dev": true, - "requires": { - "inherits": "^2.0.1", - "level-errors": "^1.0.3", - "readable-stream": "^1.0.33", - "xtend": "^4.0.0" - }, - "dependencies": { - "readable-stream": { - "version": "1.1.14", - "dev": true, - "requires": { - "core-util-is": "~1.0.0", - "inherits": "~2.0.1", - "isarray": "0.0.1", - "string_decoder": "~0.10.x" - } - } - } - }, - "level-ws": { - "version": "0.0.0", - "dev": true, - "requires": { - "readable-stream": "~1.0.15", - "xtend": "~2.1.1" - }, - "dependencies": { - "readable-stream": { - "version": "1.0.34", - "dev": true, - "requires": { - "core-util-is": "~1.0.0", - "inherits": "~2.0.1", - "isarray": "0.0.1", - "string_decoder": "~0.10.x" - } - }, - "xtend": { - "version": "2.1.2", - "dev": true, - "requires": { - "object-keys": "~0.4.0" - } - } - } - }, - "levelup": { - "version": "1.3.9", - "dev": true, - "requires": { - "deferred-leveldown": "~1.2.1", - "level-codec": "~7.0.0", - "level-errors": "~1.0.3", - "level-iterator-stream": "~1.3.0", - "prr": "~1.0.1", - "semver": "~5.4.1", - "xtend": "~4.0.0" - } - }, - "ltgt": { - "version": "2.2.1", - "dev": true - }, - "memdown": { - "version": "1.4.1", - "dev": true, - "requires": { - "abstract-leveldown": "~2.7.1", - "functional-red-black-tree": "^1.0.1", - "immediate": "^3.2.3", - "inherits": "~2.0.1", - "ltgt": "~2.2.0", - "safe-buffer": "~5.1.1" - }, - "dependencies": { - "abstract-leveldown": { - "version": "2.7.2", - "dev": true, - "requires": { - "xtend": "~4.0.0" - } - } - } - }, - "merkle-patricia-tree": { - "version": "2.3.2", - "dev": true, - "requires": { - "async": "^1.4.2", - "ethereumjs-util": "^5.0.0", - "level-ws": "0.0.0", - "levelup": "^1.2.1", - "memdown": "^1.0.0", - "readable-stream": "^2.0.0", - "rlp": "^2.0.0", - "semaphore": ">=1.0.1" - }, - "dependencies": { - "async": { - "version": "1.5.2", - "dev": true - } - } - }, - "object-keys": { - "version": "0.4.0", - "dev": true - }, - "safe-buffer": { - "version": "5.1.2", - "dev": true - }, - "semver": { - "version": "5.4.1", - "dev": true - }, - "string_decoder": { - "version": "0.10.31", - "dev": true - } - } - }, - "eth-lib": { - "version": "0.1.29", - "dev": true, - "optional": true, - "requires": { - "bn.js": "^4.11.6", - "elliptic": "^6.4.0", - "nano-json-stream-parser": "^0.1.2", - "servify": "^0.1.12", - "ws": "^3.0.0", - "xhr-request-promise": "^0.1.2" - } - }, - "eth-query": { - "version": "2.1.2", - "dev": true, - "requires": { - "json-rpc-random-id": "^1.0.0", - "xtend": "^4.0.1" - } - }, - "eth-sig-util": { - "version": "3.0.0", - "dev": true, - "requires": { - "buffer": "^5.2.1", - "elliptic": "^6.4.0", - "ethereumjs-abi": "0.6.5", - "ethereumjs-util": "^5.1.1", - "tweetnacl": "^1.0.0", - "tweetnacl-util": "^0.15.0" - }, - "dependencies": { - "ethereumjs-abi": { - "version": "0.6.5", - "dev": true, - "requires": { - "bn.js": "^4.10.0", - "ethereumjs-util": "^4.3.0" - }, - "dependencies": { - "ethereumjs-util": { - "version": "4.5.1", - "dev": true, - "requires": { - "bn.js": "^4.8.0", - "create-hash": "^1.1.2", - "elliptic": "^6.5.2", - "ethereum-cryptography": "^0.1.3", - "rlp": "^2.0.0" - } - } - } - }, - "ethereumjs-util": { - "version": "5.2.1", - "dev": true, - "requires": { - "bn.js": "^4.11.0", - "create-hash": "^1.1.2", - "elliptic": "^6.5.2", - "ethereum-cryptography": "^0.1.3", - "ethjs-util": "^0.1.3", - "rlp": "^2.0.0", - "safe-buffer": "^5.1.1" - } - } - } - }, - "eth-tx-summary": { - "version": "3.2.4", - "dev": true, - "requires": { - "async": "^2.1.2", - "clone": "^2.0.0", - "concat-stream": "^1.5.1", - "end-of-stream": "^1.1.0", - "eth-query": "^2.0.2", - "ethereumjs-block": "^1.4.1", - "ethereumjs-tx": "^1.1.1", - "ethereumjs-util": "^5.0.1", - "ethereumjs-vm": "^2.6.0", - "through2": "^2.0.3" - }, - "dependencies": { - "abstract-leveldown": { - "version": "2.6.3", - "dev": true, - "requires": { - "xtend": "~4.0.0" - } - }, - "deferred-leveldown": { - "version": "1.2.2", - "dev": true, - "requires": { - "abstract-leveldown": "~2.6.0" - } - }, - "ethereumjs-account": { - "version": "2.0.5", - "dev": true, - "requires": { - "ethereumjs-util": "^5.0.0", - "rlp": "^2.0.0", - "safe-buffer": "^5.1.1" - } - }, - "ethereumjs-block": { - "version": "1.7.1", - "dev": true, - "requires": { - "async": "^2.0.1", - "ethereum-common": "0.2.0", - "ethereumjs-tx": "^1.2.2", - "ethereumjs-util": "^5.0.0", - "merkle-patricia-tree": "^2.1.2" - }, - "dependencies": { - "ethereum-common": { - "version": "0.2.0", - "dev": true - } - } - }, - "ethereumjs-tx": { - "version": "1.3.7", - "dev": true, - "requires": { - "ethereum-common": "^0.0.18", - "ethereumjs-util": "^5.0.0" - } - }, - "ethereumjs-util": { - "version": "5.2.1", - "dev": true, - "requires": { - "bn.js": "^4.11.0", - "create-hash": "^1.1.2", - "elliptic": "^6.5.2", - "ethereum-cryptography": "^0.1.3", - "ethjs-util": "^0.1.3", - "rlp": "^2.0.0", - "safe-buffer": "^5.1.1" - } - }, - "ethereumjs-vm": { - "version": "2.6.0", - "dev": true, - "requires": { - "async": "^2.1.2", - "async-eventemitter": "^0.2.2", - "ethereumjs-account": "^2.0.3", - "ethereumjs-block": "~2.2.0", - "ethereumjs-common": "^1.1.0", - "ethereumjs-util": "^6.0.0", - "fake-merkle-patricia-tree": "^1.0.1", - "functional-red-black-tree": "^1.0.1", - "merkle-patricia-tree": "^2.3.2", - "rustbn.js": "~0.2.0", - "safe-buffer": "^5.1.1" - }, - "dependencies": { - "ethereumjs-block": { - "version": "2.2.2", - "dev": true, - "requires": { - "async": "^2.0.1", - "ethereumjs-common": "^1.5.0", - "ethereumjs-tx": "^2.1.1", - "ethereumjs-util": "^5.0.0", - "merkle-patricia-tree": "^2.1.2" - }, - "dependencies": { - "ethereumjs-util": { - "version": "5.2.1", - "dev": true, - "requires": { - "bn.js": "^4.11.0", - "create-hash": "^1.1.2", - "elliptic": "^6.5.2", - "ethereum-cryptography": "^0.1.3", - "ethjs-util": "^0.1.3", - "rlp": "^2.0.0", - "safe-buffer": "^5.1.1" - } - } - } - }, - "ethereumjs-tx": { - "version": "2.1.2", - "dev": true, - "requires": { - "ethereumjs-common": "^1.5.0", - "ethereumjs-util": "^6.0.0" - } - }, - "ethereumjs-util": { - "version": "6.2.1", - "dev": true, - "requires": { - "@types/bn.js": "^4.11.3", - "bn.js": "^4.11.0", - "create-hash": "^1.1.2", - "elliptic": "^6.5.2", - "ethereum-cryptography": "^0.1.3", - "ethjs-util": "0.1.6", - "rlp": "^2.2.3" - } - } - } - }, - "isarray": { - "version": "0.0.1", - "dev": true - }, - "level-codec": { - "version": "7.0.1", - "dev": true - }, - "level-errors": { - "version": "1.0.5", - "dev": true, - "requires": { - "errno": "~0.1.1" - } - }, - "level-iterator-stream": { - "version": "1.3.1", - "dev": true, - "requires": { - "inherits": "^2.0.1", - "level-errors": "^1.0.3", - "readable-stream": "^1.0.33", - "xtend": "^4.0.0" - }, - "dependencies": { - "readable-stream": { - "version": "1.1.14", - "dev": true, - "requires": { - "core-util-is": "~1.0.0", - "inherits": "~2.0.1", - "isarray": "0.0.1", - "string_decoder": "~0.10.x" - } - } - } - }, - "level-ws": { - "version": "0.0.0", - "dev": true, - "requires": { - "readable-stream": "~1.0.15", - "xtend": "~2.1.1" - }, - "dependencies": { - "readable-stream": { - "version": "1.0.34", - "dev": true, - "requires": { - "core-util-is": "~1.0.0", - "inherits": "~2.0.1", - "isarray": "0.0.1", - "string_decoder": "~0.10.x" - } - }, - "xtend": { - "version": "2.1.2", - "dev": true, - "requires": { - "object-keys": "~0.4.0" - } - } - } - }, - "levelup": { - "version": "1.3.9", - "dev": true, - "requires": { - "deferred-leveldown": "~1.2.1", - "level-codec": "~7.0.0", - "level-errors": "~1.0.3", - "level-iterator-stream": "~1.3.0", - "prr": "~1.0.1", - "semver": "~5.4.1", - "xtend": "~4.0.0" - } - }, - "ltgt": { - "version": "2.2.1", - "dev": true - }, - "memdown": { - "version": "1.4.1", - "dev": true, - "requires": { - "abstract-leveldown": "~2.7.1", - "functional-red-black-tree": "^1.0.1", - "immediate": "^3.2.3", - "inherits": "~2.0.1", - "ltgt": "~2.2.0", - "safe-buffer": "~5.1.1" - }, - "dependencies": { - "abstract-leveldown": { - "version": "2.7.2", - "dev": true, - "requires": { - "xtend": "~4.0.0" - } - } - } - }, - "merkle-patricia-tree": { - "version": "2.3.2", - "dev": true, - "requires": { - "async": "^1.4.2", - "ethereumjs-util": "^5.0.0", - "level-ws": "0.0.0", - "levelup": "^1.2.1", - "memdown": "^1.0.0", - "readable-stream": "^2.0.0", - "rlp": "^2.0.0", - "semaphore": ">=1.0.1" - }, - "dependencies": { - "async": { - "version": "1.5.2", - "dev": true - } - } - }, - "object-keys": { - "version": "0.4.0", - "dev": true - }, - "safe-buffer": { - "version": "5.1.2", - "dev": true - }, - "semver": { - "version": "5.4.1", - "dev": true - }, - "string_decoder": { - "version": "0.10.31", - "dev": true - } - } - }, - "ethashjs": { - "version": "0.0.8", - "dev": true, - "requires": { - "async": "^2.1.2", - "buffer-xor": "^2.0.1", - "ethereumjs-util": "^7.0.2", - "miller-rabin": "^4.0.0" - }, - "dependencies": { - "bn.js": { - "version": "5.1.3", - "dev": true - }, - "buffer-xor": { - "version": "2.0.2", - "dev": true, - "requires": { - "safe-buffer": "^5.1.1" - } - }, - "ethereumjs-util": { - "version": "7.0.7", - "dev": true, - "requires": { - "@types/bn.js": "^4.11.3", - "bn.js": "^5.1.2", - "create-hash": "^1.1.2", - "ethereum-cryptography": "^0.1.3", - "ethjs-util": "0.1.6", - "rlp": "^2.2.4" - } - } - } - }, - "ethereum-bloom-filters": { - "version": "1.0.7", - "dev": true, - "optional": true, - "requires": { - "js-sha3": "^0.8.0" - }, - "dependencies": { - "js-sha3": { - "version": "0.8.0", - "dev": true, - "optional": true - } - } - }, - "ethereum-common": { - "version": "0.0.18", - "dev": true - }, - "ethereum-cryptography": { - "version": "0.1.3", - "dev": true, - "requires": { - "@types/pbkdf2": "^3.0.0", - "@types/secp256k1": "^4.0.1", - "blakejs": "^1.1.0", - "browserify-aes": "^1.2.0", - "bs58check": "^2.1.2", - "create-hash": "^1.2.0", - "create-hmac": "^1.1.7", - "hash.js": "^1.1.7", - "keccak": "^3.0.0", - "pbkdf2": "^3.0.17", - "randombytes": "^2.1.0", - "safe-buffer": "^5.1.2", - "scrypt-js": "^3.0.0", - "secp256k1": "^4.0.1", - "setimmediate": "^1.0.5" - } - }, - "ethereumjs-abi": { - "version": "0.6.8", - "dev": true, - "requires": { - "bn.js": "^4.11.8", - "ethereumjs-util": "^6.0.0" - } - }, - "ethereumjs-account": { - "version": "3.0.0", - "dev": true, - "requires": { - "ethereumjs-util": "^6.0.0", - "rlp": "^2.2.1", - "safe-buffer": "^5.1.1" - } - }, - "ethereumjs-block": { - "version": "2.2.2", - "dev": true, - "requires": { - "async": "^2.0.1", - "ethereumjs-common": "^1.5.0", - "ethereumjs-tx": "^2.1.1", - "ethereumjs-util": "^5.0.0", - "merkle-patricia-tree": "^2.1.2" - }, - "dependencies": { - "abstract-leveldown": { - "version": "2.6.3", - "dev": true, - "requires": { - "xtend": "~4.0.0" - } - }, - "deferred-leveldown": { - "version": "1.2.2", - "dev": true, - "requires": { - "abstract-leveldown": "~2.6.0" - } - }, - "ethereumjs-util": { - "version": "5.2.1", - "dev": true, - "requires": { - "bn.js": "^4.11.0", - "create-hash": "^1.1.2", - "elliptic": "^6.5.2", - "ethereum-cryptography": "^0.1.3", - "ethjs-util": "^0.1.3", - "rlp": "^2.0.0", - "safe-buffer": "^5.1.1" - } - }, - "isarray": { - "version": "0.0.1", - "dev": true - }, - "level-codec": { - "version": "7.0.1", - "dev": true - }, - "level-errors": { - "version": "1.0.5", - "dev": true, - "requires": { - "errno": "~0.1.1" - } - }, - "level-iterator-stream": { - "version": "1.3.1", - "dev": true, - "requires": { - "inherits": "^2.0.1", - "level-errors": "^1.0.3", - "readable-stream": "^1.0.33", - "xtend": "^4.0.0" - }, - "dependencies": { - "readable-stream": { - "version": "1.1.14", - "dev": true, - "requires": { - "core-util-is": "~1.0.0", - "inherits": "~2.0.1", - "isarray": "0.0.1", - "string_decoder": "~0.10.x" - } - } - } - }, - "level-ws": { - "version": "0.0.0", - "dev": true, - "requires": { - "readable-stream": "~1.0.15", - "xtend": "~2.1.1" - }, - "dependencies": { - "readable-stream": { - "version": "1.0.34", - "dev": true, - "requires": { - "core-util-is": "~1.0.0", - "inherits": "~2.0.1", - "isarray": "0.0.1", - "string_decoder": "~0.10.x" - } - }, - "xtend": { - "version": "2.1.2", - "dev": true, - "requires": { - "object-keys": "~0.4.0" - } - } - } - }, - "levelup": { - "version": "1.3.9", - "dev": true, - "requires": { - "deferred-leveldown": "~1.2.1", - "level-codec": "~7.0.0", - "level-errors": "~1.0.3", - "level-iterator-stream": "~1.3.0", - "prr": "~1.0.1", - "semver": "~5.4.1", - "xtend": "~4.0.0" - } - }, - "ltgt": { - "version": "2.2.1", - "dev": true - }, - "memdown": { - "version": "1.4.1", - "dev": true, - "requires": { - "abstract-leveldown": "~2.7.1", - "functional-red-black-tree": "^1.0.1", - "immediate": "^3.2.3", - "inherits": "~2.0.1", - "ltgt": "~2.2.0", - "safe-buffer": "~5.1.1" - }, - "dependencies": { - "abstract-leveldown": { - "version": "2.7.2", - "dev": true, - "requires": { - "xtend": "~4.0.0" - } - } - } - }, - "merkle-patricia-tree": { - "version": "2.3.2", - "dev": true, - "requires": { - "async": "^1.4.2", - "ethereumjs-util": "^5.0.0", - "level-ws": "0.0.0", - "levelup": "^1.2.1", - "memdown": "^1.0.0", - "readable-stream": "^2.0.0", - "rlp": "^2.0.0", - "semaphore": ">=1.0.1" - }, - "dependencies": { - "async": { - "version": "1.5.2", - "dev": true - } - } - }, - "object-keys": { - "version": "0.4.0", - "dev": true - }, - "safe-buffer": { - "version": "5.1.2", - "dev": true - }, - "semver": { - "version": "5.4.1", - "dev": true - }, - "string_decoder": { - "version": "0.10.31", - "dev": true - } - } - }, - "ethereumjs-blockchain": { - "version": "4.0.4", - "dev": true, - "requires": { - "async": "^2.6.1", - "ethashjs": "~0.0.7", - "ethereumjs-block": "~2.2.2", - "ethereumjs-common": "^1.5.0", - "ethereumjs-util": "^6.1.0", - "flow-stoplight": "^1.0.0", - "level-mem": "^3.0.1", - "lru-cache": "^5.1.1", - "rlp": "^2.2.2", - "semaphore": "^1.1.0" - } - }, - "ethereumjs-common": { - "version": "1.5.0", - "dev": true - }, - "ethereumjs-tx": { - "version": "2.1.2", - "dev": true, - "requires": { - "ethereumjs-common": "^1.5.0", - "ethereumjs-util": "^6.0.0" - } - }, - "ethereumjs-util": { - "version": "6.2.1", - "dev": true, - "requires": { - "@types/bn.js": "^4.11.3", - "bn.js": "^4.11.0", - "create-hash": "^1.1.2", - "elliptic": "^6.5.2", - "ethereum-cryptography": "^0.1.3", - "ethjs-util": "0.1.6", - "rlp": "^2.2.3" - } - }, - "ethereumjs-vm": { - "version": "4.2.0", - "dev": true, - "requires": { - "async": "^2.1.2", - "async-eventemitter": "^0.2.2", - "core-js-pure": "^3.0.1", - "ethereumjs-account": "^3.0.0", - "ethereumjs-block": "^2.2.2", - "ethereumjs-blockchain": "^4.0.3", - "ethereumjs-common": "^1.5.0", - "ethereumjs-tx": "^2.1.2", - "ethereumjs-util": "^6.2.0", - "fake-merkle-patricia-tree": "^1.0.1", - "functional-red-black-tree": "^1.0.1", - "merkle-patricia-tree": "^2.3.2", - "rustbn.js": "~0.2.0", - "safe-buffer": "^5.1.1", - "util.promisify": "^1.0.0" - }, - "dependencies": { - "abstract-leveldown": { - "version": "2.6.3", - "dev": true, - "requires": { - "xtend": "~4.0.0" - } - }, - "deferred-leveldown": { - "version": "1.2.2", - "dev": true, - "requires": { - "abstract-leveldown": "~2.6.0" - } - }, - "isarray": { - "version": "0.0.1", - "dev": true - }, - "level-codec": { - "version": "7.0.1", - "dev": true - }, - "level-errors": { - "version": "1.0.5", - "dev": true, - "requires": { - "errno": "~0.1.1" - } - }, - "level-iterator-stream": { - "version": "1.3.1", - "dev": true, - "requires": { - "inherits": "^2.0.1", - "level-errors": "^1.0.3", - "readable-stream": "^1.0.33", - "xtend": "^4.0.0" - }, - "dependencies": { - "readable-stream": { - "version": "1.1.14", - "dev": true, - "requires": { - "core-util-is": "~1.0.0", - "inherits": "~2.0.1", - "isarray": "0.0.1", - "string_decoder": "~0.10.x" - } - } - } - }, - "level-ws": { - "version": "0.0.0", - "dev": true, - "requires": { - "readable-stream": "~1.0.15", - "xtend": "~2.1.1" - }, - "dependencies": { - "readable-stream": { - "version": "1.0.34", - "dev": true, - "requires": { - "core-util-is": "~1.0.0", - "inherits": "~2.0.1", - "isarray": "0.0.1", - "string_decoder": "~0.10.x" - } - }, - "xtend": { - "version": "2.1.2", - "dev": true, - "requires": { - "object-keys": "~0.4.0" - } - } - } - }, - "levelup": { - "version": "1.3.9", - "dev": true, - "requires": { - "deferred-leveldown": "~1.2.1", - "level-codec": "~7.0.0", - "level-errors": "~1.0.3", - "level-iterator-stream": "~1.3.0", - "prr": "~1.0.1", - "semver": "~5.4.1", - "xtend": "~4.0.0" - } - }, - "ltgt": { - "version": "2.2.1", - "dev": true - }, - "memdown": { - "version": "1.4.1", - "dev": true, - "requires": { - "abstract-leveldown": "~2.7.1", - "functional-red-black-tree": "^1.0.1", - "immediate": "^3.2.3", - "inherits": "~2.0.1", - "ltgt": "~2.2.0", - "safe-buffer": "~5.1.1" - }, - "dependencies": { - "abstract-leveldown": { - "version": "2.7.2", - "dev": true, - "requires": { - "xtend": "~4.0.0" - } - } - } - }, - "merkle-patricia-tree": { - "version": "2.3.2", - "dev": true, - "requires": { - "async": "^1.4.2", - "ethereumjs-util": "^5.0.0", - "level-ws": "0.0.0", - "levelup": "^1.2.1", - "memdown": "^1.0.0", - "readable-stream": "^2.0.0", - "rlp": "^2.0.0", - "semaphore": ">=1.0.1" - }, - "dependencies": { - "async": { - "version": "1.5.2", - "dev": true - }, - "ethereumjs-util": { - "version": "5.2.1", - "dev": true, - "requires": { - "bn.js": "^4.11.0", - "create-hash": "^1.1.2", - "elliptic": "^6.5.2", - "ethereum-cryptography": "^0.1.3", - "ethjs-util": "^0.1.3", - "rlp": "^2.0.0", - "safe-buffer": "^5.1.1" - } - } - } - }, - "object-keys": { - "version": "0.4.0", - "dev": true - }, - "safe-buffer": { - "version": "5.1.2", - "dev": true - }, - "semver": { - "version": "5.4.1", - "dev": true - }, - "string_decoder": { - "version": "0.10.31", - "dev": true - } - } - }, - "ethereumjs-wallet": { - "version": "0.6.5", - "dev": true, - "optional": true, - "requires": { - "aes-js": "^3.1.1", - "bs58check": "^2.1.2", - "ethereum-cryptography": "^0.1.3", - "ethereumjs-util": "^6.0.0", - "randombytes": "^2.0.6", - "safe-buffer": "^5.1.2", - "scryptsy": "^1.2.1", - "utf8": "^3.0.0", - "uuid": "^3.3.2" - } - }, - "ethjs-unit": { - "version": "0.1.6", - "dev": true, - "optional": true, - "requires": { - "bn.js": "4.11.6", - "number-to-bn": "1.7.0" - }, - "dependencies": { - "bn.js": { - "version": "4.11.6", - "dev": true, - "optional": true - } - } - }, - "ethjs-util": { - "version": "0.1.6", - "dev": true, - "requires": { - "is-hex-prefixed": "1.0.0", - "strip-hex-prefix": "1.0.0" - } - }, - "eventemitter3": { - "version": "4.0.4", - "dev": true, - "optional": true - }, - "events": { - "version": "3.2.0", - "dev": true - }, - "evp_bytestokey": { - "version": "1.0.3", - "dev": true, - "requires": { - "md5.js": "^1.3.4", - "safe-buffer": "^5.1.1" - } - }, - "expand-brackets": { - "version": "2.1.4", - "dev": true, - "requires": { - "debug": "^2.3.3", - "define-property": "^0.2.5", - "extend-shallow": "^2.0.1", - "posix-character-classes": "^0.1.0", - "regex-not": "^1.0.0", - "snapdragon": "^0.8.1", - "to-regex": "^3.0.1" - }, - "dependencies": { - "debug": { - "version": "2.6.9", - "dev": true, - "requires": { - "ms": "2.0.0" - } - }, - "define-property": { - "version": "0.2.5", - "dev": true, - "requires": { - "is-descriptor": "^0.1.0" - } - }, - "extend-shallow": { - "version": "2.0.1", - "dev": true, - "requires": { - "is-extendable": "^0.1.0" - } - }, - "is-accessor-descriptor": { - "version": "0.1.6", - "dev": true, - "requires": { - "kind-of": "^3.0.2" - }, - "dependencies": { - "kind-of": { - "version": "3.2.2", - "dev": true, - "requires": { - "is-buffer": "^1.1.5" - } - } - } - }, - "is-buffer": { - "version": "1.1.6", - "dev": true - }, - "is-data-descriptor": { - "version": "0.1.4", - "dev": true, - "requires": { - "kind-of": "^3.0.2" - }, - "dependencies": { - "kind-of": { - "version": "3.2.2", - "dev": true, - "requires": { - "is-buffer": "^1.1.5" - } - } - } - }, - "is-descriptor": { - "version": "0.1.6", - "dev": true, - "requires": { - "is-accessor-descriptor": "^0.1.6", - "is-data-descriptor": "^0.1.4", - "kind-of": "^5.0.0" - } - }, - "is-extendable": { - "version": "0.1.1", - "dev": true - }, - "kind-of": { - "version": "5.1.0", - "dev": true - }, - "ms": { - "version": "2.0.0", - "dev": true - } - } - }, - "express": { - "version": "4.17.1", - "dev": true, - "optional": true, - "requires": { - "accepts": "~1.3.7", - "array-flatten": "1.1.1", - "body-parser": "1.19.0", - "content-disposition": "0.5.3", - "content-type": "~1.0.4", - "cookie": "0.4.0", - "cookie-signature": "1.0.6", - "debug": "2.6.9", - "depd": "~1.1.2", - "encodeurl": "~1.0.2", - "escape-html": "~1.0.3", - "etag": "~1.8.1", - "finalhandler": "~1.1.2", - "fresh": "0.5.2", - "merge-descriptors": "1.0.1", - "methods": "~1.1.2", - "on-finished": "~2.3.0", - "parseurl": "~1.3.3", - "path-to-regexp": "0.1.7", - "proxy-addr": "~2.0.5", - "qs": "6.7.0", - "range-parser": "~1.2.1", - "safe-buffer": "5.1.2", - "send": "0.17.1", - "serve-static": "1.14.1", - "setprototypeof": "1.1.1", - "statuses": "~1.5.0", - "type-is": "~1.6.18", - "utils-merge": "1.0.1", - "vary": "~1.1.2" - }, - "dependencies": { - "debug": { - "version": "2.6.9", - "dev": true, - "optional": true, - "requires": { - "ms": "2.0.0" - } - }, - "ms": { - "version": "2.0.0", - "dev": true, - "optional": true - }, - "qs": { - "version": "6.7.0", - "dev": true, - "optional": true - }, - "safe-buffer": { - "version": "5.1.2", - "dev": true, - "optional": true - } - } - }, - "ext": { - "version": "1.4.0", - "dev": true, - "requires": { - "type": "^2.0.0" - }, - "dependencies": { - "type": { - "version": "2.1.0", - "dev": true - } - } - }, - "extend": { - "version": "3.0.2", - "dev": true - }, - "extend-shallow": { - "version": "3.0.2", - "dev": true, - "requires": { - "assign-symbols": "^1.0.0", - "is-extendable": "^1.0.1" - } - }, - "extglob": { - "version": "2.0.4", - "dev": true, - "requires": { - "array-unique": "^0.3.2", - "define-property": "^1.0.0", - "expand-brackets": "^2.1.4", - "extend-shallow": "^2.0.1", - "fragment-cache": "^0.2.1", - "regex-not": "^1.0.0", - "snapdragon": "^0.8.1", - "to-regex": "^3.0.1" - }, - "dependencies": { - "define-property": { - "version": "1.0.0", - "dev": true, - "requires": { - "is-descriptor": "^1.0.0" - } - }, - "extend-shallow": { - "version": "2.0.1", - "dev": true, - "requires": { - "is-extendable": "^0.1.0" - } - }, - "is-extendable": { - "version": "0.1.1", - "dev": true - } - } - }, - "extsprintf": { - "version": "1.3.0", - "dev": true - }, - "fake-merkle-patricia-tree": { - "version": "1.0.1", - "dev": true, - "requires": { - "checkpoint-store": "^1.1.0" - } - }, - "fast-deep-equal": { - "version": "3.1.3", - "dev": true - }, - "fast-json-stable-stringify": { - "version": "2.1.0", - "dev": true - }, - "fetch-ponyfill": { - "version": "4.1.0", - "dev": true, - "requires": { - "node-fetch": "~1.7.1" - }, - "dependencies": { - "is-stream": { - "version": "1.1.0", - "dev": true - }, - "node-fetch": { - "version": "1.7.3", - "dev": true, - "requires": { - "encoding": "^0.1.11", - "is-stream": "^1.0.1" - } - } - } - }, - "finalhandler": { - "version": "1.1.2", - "dev": true, - "optional": true, - "requires": { - "debug": "2.6.9", - "encodeurl": "~1.0.2", - "escape-html": "~1.0.3", - "on-finished": "~2.3.0", - "parseurl": "~1.3.3", - "statuses": "~1.5.0", - "unpipe": "~1.0.0" - }, - "dependencies": { - "debug": { - "version": "2.6.9", - "dev": true, - "optional": true, - "requires": { - "ms": "2.0.0" - } - }, - "ms": { - "version": "2.0.0", - "dev": true, - "optional": true - } - } - }, - "find-yarn-workspace-root": { - "version": "1.2.1", - "dev": true, - "requires": { - "fs-extra": "^4.0.3", - "micromatch": "^3.1.4" - }, - "dependencies": { - "braces": { - "version": "2.3.2", - "dev": true, - "requires": { - "arr-flatten": "^1.1.0", - "array-unique": "^0.3.2", - "extend-shallow": "^2.0.1", - "fill-range": "^4.0.0", - "isobject": "^3.0.1", - "repeat-element": "^1.1.2", - "snapdragon": "^0.8.1", - "snapdragon-node": "^2.0.1", - "split-string": "^3.0.2", - "to-regex": "^3.0.1" - }, - "dependencies": { - "extend-shallow": { - "version": "2.0.1", - "dev": true, - "requires": { - "is-extendable": "^0.1.0" - } - } - } - }, - "fill-range": { - "version": "4.0.0", - "dev": true, - "requires": { - "extend-shallow": "^2.0.1", - "is-number": "^3.0.0", - "repeat-string": "^1.6.1", - "to-regex-range": "^2.1.0" - }, - "dependencies": { - "extend-shallow": { - "version": "2.0.1", - "dev": true, - "requires": { - "is-extendable": "^0.1.0" - } - } - } - }, - "fs-extra": { - "version": "4.0.3", - "dev": true, - "requires": { - "graceful-fs": "^4.1.2", - "jsonfile": "^4.0.0", - "universalify": "^0.1.0" - } - }, - "is-buffer": { - "version": "1.1.6", - "dev": true - }, - "is-extendable": { - "version": "0.1.1", - "dev": true - }, - "is-number": { - "version": "3.0.0", - "dev": true, - "requires": { - "kind-of": "^3.0.2" - }, - "dependencies": { - "kind-of": { - "version": "3.2.2", - "dev": true, - "requires": { - "is-buffer": "^1.1.5" - } - } - } - }, - "micromatch": { - "version": "3.1.10", - "dev": true, - "requires": { - "arr-diff": "^4.0.0", - "array-unique": "^0.3.2", - "braces": "^2.3.1", - "define-property": "^2.0.2", - "extend-shallow": "^3.0.2", - "extglob": "^2.0.4", - "fragment-cache": "^0.2.1", - "kind-of": "^6.0.2", - "nanomatch": "^1.2.9", - "object.pick": "^1.3.0", - "regex-not": "^1.0.0", - "snapdragon": "^0.8.1", - "to-regex": "^3.0.2" - } - }, - "to-regex-range": { - "version": "2.1.1", - "dev": true, - "requires": { - "is-number": "^3.0.0", - "repeat-string": "^1.6.1" - } - } - } - }, - "flow-stoplight": { - "version": "1.0.0", - "dev": true - }, - "for-each": { - "version": "0.3.3", - "dev": true, - "requires": { - "is-callable": "^1.1.3" - } - }, - "for-in": { - "version": "1.0.2", - "dev": true - }, - "forever-agent": { - "version": "0.6.1", - "dev": true - }, - "form-data": { - "version": "2.3.3", - "dev": true, - "requires": { - "asynckit": "^0.4.0", - "combined-stream": "^1.0.6", - "mime-types": "^2.1.12" - } - }, - "forwarded": { - "version": "0.1.2", - "dev": true, - "optional": true - }, - "fragment-cache": { - "version": "0.2.1", - "dev": true, - "requires": { - "map-cache": "^0.2.2" - } - }, - "fresh": { - "version": "0.5.2", - "dev": true, - "optional": true - }, - "fs-extra": { - "version": "7.0.1", - "dev": true, - "requires": { - "graceful-fs": "^4.1.2", - "jsonfile": "^4.0.0", - "universalify": "^0.1.0" - } - }, - "fs.realpath": { - "version": "1.0.0", - "dev": true - }, - "function-bind": { - "version": "1.1.1", - "dev": true - }, - "functional-red-black-tree": { - "version": "1.0.1", - "dev": true - }, - "get-intrinsic": { - "version": "1.0.2", - "dev": true, - "requires": { - "function-bind": "^1.1.1", - "has": "^1.0.3", - "has-symbols": "^1.0.1" - } - }, - "get-stream": { - "version": "5.2.0", - "dev": true, - "optional": true, - "requires": { - "pump": "^3.0.0" - } - }, - "get-value": { - "version": "2.0.6", - "dev": true - }, - "getpass": { - "version": "0.1.7", - "dev": true, - "requires": { - "assert-plus": "^1.0.0" - } - }, - "glob": { - "version": "7.1.3", - "dev": true, - "requires": { - "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" - } - }, - "global": { - "version": "4.4.0", - "dev": true, - "requires": { - "min-document": "^2.19.0", - "process": "^0.11.10" - } - }, - "got": { - "version": "9.6.0", - "dev": true, - "optional": true, - "requires": { - "@sindresorhus/is": "^0.14.0", - "@szmarczak/http-timer": "^1.1.2", - "cacheable-request": "^6.0.0", - "decompress-response": "^3.3.0", - "duplexer3": "^0.1.4", - "get-stream": "^4.1.0", - "lowercase-keys": "^1.0.1", - "mimic-response": "^1.0.1", - "p-cancelable": "^1.0.0", - "to-readable-stream": "^1.0.0", - "url-parse-lax": "^3.0.0" - }, - "dependencies": { - "get-stream": { - "version": "4.1.0", - "dev": true, - "optional": true, - "requires": { - "pump": "^3.0.0" - } - } - } - }, - "graceful-fs": { - "version": "4.2.4", - "dev": true - }, - "har-schema": { - "version": "2.0.0", - "dev": true - }, - "har-validator": { - "version": "5.1.5", - "dev": true, - "requires": { - "ajv": "^6.12.3", - "har-schema": "^2.0.0" - } - }, - "has": { - "version": "1.0.3", - "dev": true, - "requires": { - "function-bind": "^1.1.1" - } - }, - "has-ansi": { - "version": "2.0.0", - "dev": true, - "requires": { - "ansi-regex": "^2.0.0" - }, - "dependencies": { - "ansi-regex": { - "version": "2.1.1", - "dev": true - } - } - }, - "has-flag": { - "version": "3.0.0", - "dev": true - }, - "has-symbol-support-x": { - "version": "1.4.2", - "dev": true, - "optional": true - }, - "has-symbols": { - "version": "1.0.1", - "dev": true - }, - "has-to-string-tag-x": { - "version": "1.4.1", - "dev": true, - "optional": true, - "requires": { - "has-symbol-support-x": "^1.4.1" - } - }, - "has-value": { - "version": "1.0.0", - "dev": true, - "requires": { - "get-value": "^2.0.6", - "has-values": "^1.0.0", - "isobject": "^3.0.0" - } - }, - "has-values": { - "version": "1.0.0", - "dev": true, - "requires": { - "is-number": "^3.0.0", - "kind-of": "^4.0.0" - }, - "dependencies": { - "is-buffer": { - "version": "1.1.6", - "dev": true - }, - "is-number": { - "version": "3.0.0", - "dev": true, - "requires": { - "kind-of": "^3.0.2" - }, - "dependencies": { - "kind-of": { - "version": "3.2.2", - "dev": true, - "requires": { - "is-buffer": "^1.1.5" - } - } - } - }, - "kind-of": { - "version": "4.0.0", - "dev": true, - "requires": { - "is-buffer": "^1.1.5" - } - } - } - }, - "hash-base": { - "version": "3.1.0", - "dev": true, - "requires": { - "inherits": "^2.0.4", - "readable-stream": "^3.6.0", - "safe-buffer": "^5.2.0" - }, - "dependencies": { - "readable-stream": { - "version": "3.6.0", - "dev": true, - "requires": { - "inherits": "^2.0.3", - "string_decoder": "^1.1.1", - "util-deprecate": "^1.0.1" - } - } - } - }, - "hash.js": { - "version": "1.1.7", - "dev": true, - "requires": { - "inherits": "^2.0.3", - "minimalistic-assert": "^1.0.1" - } - }, - "heap": { - "version": "0.2.6", - "dev": true - }, - "hmac-drbg": { - "version": "1.0.1", - "dev": true, - "requires": { - "hash.js": "^1.0.3", - "minimalistic-assert": "^1.0.0", - "minimalistic-crypto-utils": "^1.0.1" - } - }, - "home-or-tmp": { - "version": "2.0.0", - "dev": true, - "requires": { - "os-homedir": "^1.0.0", - "os-tmpdir": "^1.0.1" - } - }, - "http-cache-semantics": { - "version": "4.1.0", - "dev": true, - "optional": true - }, - "http-errors": { - "version": "1.7.2", - "dev": true, - "optional": true, - "requires": { - "depd": "~1.1.2", - "inherits": "2.0.3", - "setprototypeof": "1.1.1", - "statuses": ">= 1.5.0 < 2", - "toidentifier": "1.0.0" - }, - "dependencies": { - "inherits": { - "version": "2.0.3", - "dev": true, - "optional": true - } - } - }, - "http-https": { - "version": "1.0.0", - "dev": true, - "optional": true - }, - "http-signature": { - "version": "1.2.0", - "dev": true, - "requires": { - "assert-plus": "^1.0.0", - "jsprim": "^1.2.2", - "sshpk": "^1.7.0" - } - }, - "iconv-lite": { - "version": "0.4.24", - "dev": true, - "optional": true, - "requires": { - "safer-buffer": ">= 2.1.2 < 3" - } - }, - "idna-uts46-hx": { - "version": "2.3.1", - "dev": true, - "optional": true, - "requires": { - "punycode": "2.1.0" - }, - "dependencies": { - "punycode": { - "version": "2.1.0", - "dev": true, - "optional": true - } - } - }, - "ieee754": { - "version": "1.2.1", - "dev": true - }, - "immediate": { - "version": "3.2.3", - "dev": true - }, - "inflight": { - "version": "1.0.6", - "dev": true, - "requires": { - "once": "^1.3.0", - "wrappy": "1" - } - }, - "inherits": { - "version": "2.0.4", - "dev": true - }, - "invariant": { - "version": "2.2.4", - "dev": true, - "requires": { - "loose-envify": "^1.0.0" - } - }, - "ipaddr.js": { - "version": "1.9.1", - "dev": true, - "optional": true - }, - "is-accessor-descriptor": { - "version": "1.0.0", - "dev": true, - "requires": { - "kind-of": "^6.0.0" - } - }, - "is-arguments": { - "version": "1.1.0", - "dev": true, - "requires": { - "call-bind": "^1.0.0" - } - }, - "is-callable": { - "version": "1.2.2", - "dev": true - }, - "is-ci": { - "version": "2.0.0", - "dev": true, - "requires": { - "ci-info": "^2.0.0" - } - }, - "is-data-descriptor": { - "version": "1.0.0", - "dev": true, - "requires": { - "kind-of": "^6.0.0" - } - }, - "is-date-object": { - "version": "1.0.2", - "dev": true - }, - "is-descriptor": { - "version": "1.0.2", - "dev": true, - "requires": { - "is-accessor-descriptor": "^1.0.0", - "is-data-descriptor": "^1.0.0", - "kind-of": "^6.0.2" - } - }, - "is-extendable": { - "version": "1.0.1", - "dev": true, - "requires": { - "is-plain-object": "^2.0.4" - } - }, - "is-finite": { - "version": "1.1.0", - "dev": true - }, - "is-fn": { - "version": "1.0.0", - "dev": true - }, - "is-function": { - "version": "1.0.2", - "dev": true - }, - "is-hex-prefixed": { - "version": "1.0.0", - "dev": true - }, - "is-negative-zero": { - "version": "2.0.1", - "dev": true - }, - "is-object": { - "version": "1.0.2", - "dev": true, - "optional": true - }, - "is-plain-obj": { - "version": "1.1.0", - "dev": true, - "optional": true - }, - "is-plain-object": { - "version": "2.0.4", - "dev": true, - "requires": { - "isobject": "^3.0.1" - } - }, - "is-regex": { - "version": "1.1.1", - "dev": true, - "requires": { - "has-symbols": "^1.0.1" - } - }, - "is-retry-allowed": { - "version": "1.2.0", - "dev": true, - "optional": true - }, - "is-symbol": { - "version": "1.0.3", - "dev": true, - "requires": { - "has-symbols": "^1.0.1" - } - }, - "is-typedarray": { - "version": "1.0.0", - "dev": true - }, - "is-windows": { - "version": "1.0.2", - "dev": true - }, - "isarray": { - "version": "1.0.0", - "dev": true - }, - "isexe": { - "version": "2.0.0", - "dev": true - }, - "isobject": { - "version": "3.0.1", - "dev": true - }, - "isstream": { - "version": "0.1.2", - "dev": true - }, - "isurl": { - "version": "1.0.0", - "dev": true, - "optional": true, - "requires": { - "has-to-string-tag-x": "^1.2.0", - "is-object": "^1.0.1" - } - }, - "js-sha3": { - "version": "0.5.7", - "dev": true, - "optional": true - }, - "js-tokens": { - "version": "4.0.0", - "dev": true - }, - "jsbn": { - "version": "0.1.1", - "dev": true - }, - "json-buffer": { - "version": "3.0.0", - "dev": true, - "optional": true - }, - "json-rpc-engine": { - "version": "3.8.0", - "dev": true, - "requires": { - "async": "^2.0.1", - "babel-preset-env": "^1.7.0", - "babelify": "^7.3.0", - "json-rpc-error": "^2.0.0", - "promise-to-callback": "^1.0.0", - "safe-event-emitter": "^1.0.1" - } - }, - "json-rpc-error": { - "version": "2.0.0", - "dev": true, - "requires": { - "inherits": "^2.0.1" - } - }, - "json-rpc-random-id": { - "version": "1.0.1", - "dev": true - }, - "json-schema": { - "version": "0.2.3", - "dev": true - }, - "json-schema-traverse": { - "version": "0.4.1", - "dev": true - }, - "json-stable-stringify": { - "version": "1.0.1", - "dev": true, - "requires": { - "jsonify": "~0.0.0" - } - }, - "json-stringify-safe": { - "version": "5.0.1", - "dev": true - }, - "jsonfile": { - "version": "4.0.0", - "dev": true, - "requires": { - "graceful-fs": "^4.1.6" - } - }, - "jsonify": { - "version": "0.0.0", - "dev": true - }, - "jsprim": { - "version": "1.4.1", - "dev": true, - "requires": { - "assert-plus": "1.0.0", - "extsprintf": "1.3.0", - "json-schema": "0.2.3", - "verror": "1.10.0" - } - }, - "keccak": { - "version": "3.0.1", - "bundled": true, - "dev": true, - "requires": { - "node-addon-api": "^2.0.0", - "node-gyp-build": "^4.2.0" - } - }, - "keyv": { - "version": "3.1.0", - "dev": true, - "optional": true, - "requires": { - "json-buffer": "3.0.0" - } - }, - "kind-of": { - "version": "6.0.3", - "dev": true - }, - "klaw-sync": { - "version": "6.0.0", - "dev": true, - "requires": { - "graceful-fs": "^4.1.11" - } - }, - "level-codec": { - "version": "9.0.2", - "dev": true, - "requires": { - "buffer": "^5.6.0" - } - }, - "level-errors": { - "version": "2.0.1", - "dev": true, - "requires": { - "errno": "~0.1.1" - } - }, - "level-iterator-stream": { - "version": "2.0.3", - "dev": true, - "requires": { - "inherits": "^2.0.1", - "readable-stream": "^2.0.5", - "xtend": "^4.0.0" - } - }, - "level-mem": { - "version": "3.0.1", - "dev": true, - "requires": { - "level-packager": "~4.0.0", - "memdown": "~3.0.0" - }, - "dependencies": { - "abstract-leveldown": { - "version": "5.0.0", - "dev": true, - "requires": { - "xtend": "~4.0.0" - } - }, - "ltgt": { - "version": "2.2.1", - "dev": true - }, - "memdown": { - "version": "3.0.0", - "dev": true, - "requires": { - "abstract-leveldown": "~5.0.0", - "functional-red-black-tree": "~1.0.1", - "immediate": "~3.2.3", - "inherits": "~2.0.1", - "ltgt": "~2.2.0", - "safe-buffer": "~5.1.1" - } - }, - "safe-buffer": { - "version": "5.1.2", - "dev": true - } - } - }, - "level-packager": { - "version": "4.0.1", - "dev": true, - "requires": { - "encoding-down": "~5.0.0", - "levelup": "^3.0.0" - } - }, - "level-post": { - "version": "1.0.7", - "dev": true, - "requires": { - "ltgt": "^2.1.2" - } - }, - "level-sublevel": { - "version": "6.6.4", - "dev": true, - "requires": { - "bytewise": "~1.1.0", - "level-codec": "^9.0.0", - "level-errors": "^2.0.0", - "level-iterator-stream": "^2.0.3", - "ltgt": "~2.1.1", - "pull-defer": "^0.2.2", - "pull-level": "^2.0.3", - "pull-stream": "^3.6.8", - "typewiselite": "~1.0.0", - "xtend": "~4.0.0" - } - }, - "level-ws": { - "version": "1.0.0", - "dev": true, - "requires": { - "inherits": "^2.0.3", - "readable-stream": "^2.2.8", - "xtend": "^4.0.1" - } - }, - "levelup": { - "version": "3.1.1", - "dev": true, - "requires": { - "deferred-leveldown": "~4.0.0", - "level-errors": "~2.0.0", - "level-iterator-stream": "~3.0.0", - "xtend": "~4.0.0" - }, - "dependencies": { - "level-iterator-stream": { - "version": "3.0.1", - "dev": true, - "requires": { - "inherits": "^2.0.1", - "readable-stream": "^2.3.6", - "xtend": "^4.0.0" - } - } - } - }, - "lodash": { - "version": "4.17.20", - "dev": true - }, - "looper": { - "version": "2.0.0", - "dev": true - }, - "loose-envify": { - "version": "1.4.0", - "dev": true, - "requires": { - "js-tokens": "^3.0.0 || ^4.0.0" - } - }, - "lowercase-keys": { - "version": "1.0.1", - "dev": true, - "optional": true - }, - "lru-cache": { - "version": "5.1.1", - "dev": true, - "requires": { - "yallist": "^3.0.2" - } - }, - "ltgt": { - "version": "2.1.3", - "dev": true - }, - "map-cache": { - "version": "0.2.2", - "dev": true - }, - "map-visit": { - "version": "1.0.0", - "dev": true, - "requires": { - "object-visit": "^1.0.0" - } - }, - "md5.js": { - "version": "1.3.5", - "dev": true, - "requires": { - "hash-base": "^3.0.0", - "inherits": "^2.0.1", - "safe-buffer": "^5.1.2" - } - }, - "media-typer": { - "version": "0.3.0", - "dev": true, - "optional": true - }, - "merge-descriptors": { - "version": "1.0.1", - "dev": true, - "optional": true - }, - "merkle-patricia-tree": { - "version": "3.0.0", - "dev": true, - "requires": { - "async": "^2.6.1", - "ethereumjs-util": "^5.2.0", - "level-mem": "^3.0.1", - "level-ws": "^1.0.0", - "readable-stream": "^3.0.6", - "rlp": "^2.0.0", - "semaphore": ">=1.0.1" - }, - "dependencies": { - "ethereumjs-util": { - "version": "5.2.1", - "dev": true, - "requires": { - "bn.js": "^4.11.0", - "create-hash": "^1.1.2", - "elliptic": "^6.5.2", - "ethereum-cryptography": "^0.1.3", - "ethjs-util": "^0.1.3", - "rlp": "^2.0.0", - "safe-buffer": "^5.1.1" - } - }, - "readable-stream": { - "version": "3.6.0", - "dev": true, - "requires": { - "inherits": "^2.0.3", - "string_decoder": "^1.1.1", - "util-deprecate": "^1.0.1" - } - } - } - }, - "methods": { - "version": "1.1.2", - "dev": true, - "optional": true - }, - "miller-rabin": { - "version": "4.0.1", - "dev": true, - "requires": { - "bn.js": "^4.0.0", - "brorand": "^1.0.1" - } - }, - "mime": { - "version": "1.6.0", - "dev": true, - "optional": true - }, - "mime-db": { - "version": "1.45.0", - "dev": true - }, - "mime-types": { - "version": "2.1.28", - "dev": true, - "requires": { - "mime-db": "1.45.0" - } - }, - "mimic-response": { - "version": "1.0.1", - "dev": true, - "optional": true - }, - "min-document": { - "version": "2.19.0", - "dev": true, - "requires": { - "dom-walk": "^0.1.0" - } - }, - "minimalistic-assert": { - "version": "1.0.1", - "dev": true - }, - "minimalistic-crypto-utils": { - "version": "1.0.1", - "dev": true - }, - "minimatch": { - "version": "3.0.4", - "dev": true, - "requires": { - "brace-expansion": "^1.1.7" - } - }, - "minimist": { - "version": "1.2.5", - "dev": true - }, - "minizlib": { - "version": "1.3.3", - "dev": true, - "optional": true, - "requires": { - "minipass": "^2.9.0" - }, - "dependencies": { - "minipass": { - "version": "2.9.0", - "dev": true, - "optional": true, - "requires": { - "safe-buffer": "^5.1.2", - "yallist": "^3.0.0" - } - } - } - }, - "mixin-deep": { - "version": "1.3.2", - "dev": true, - "requires": { - "for-in": "^1.0.2", - "is-extendable": "^1.0.1" - } - }, - "mkdirp": { - "version": "0.5.5", - "dev": true, - "requires": { - "minimist": "^1.2.5" - } - }, - "mkdirp-promise": { - "version": "5.0.1", - "dev": true, - "optional": true, - "requires": { - "mkdirp": "*" - } - }, - "mock-fs": { - "version": "4.13.0", - "dev": true, - "optional": true - }, - "ms": { - "version": "2.1.3", - "dev": true - }, - "multibase": { - "version": "0.6.1", - "dev": true, - "optional": true, - "requires": { - "base-x": "^3.0.8", - "buffer": "^5.5.0" - } - }, - "multicodec": { - "version": "0.5.7", - "dev": true, - "optional": true, - "requires": { - "varint": "^5.0.0" - } - }, - "multihashes": { - "version": "0.4.21", - "dev": true, - "optional": true, - "requires": { - "buffer": "^5.5.0", - "multibase": "^0.7.0", - "varint": "^5.0.0" - }, - "dependencies": { - "multibase": { - "version": "0.7.0", - "dev": true, - "optional": true, - "requires": { - "base-x": "^3.0.8", - "buffer": "^5.5.0" - } - } - } - }, - "nano-json-stream-parser": { - "version": "0.1.2", - "dev": true, - "optional": true - }, - "nanomatch": { - "version": "1.2.13", - "dev": true, - "requires": { - "arr-diff": "^4.0.0", - "array-unique": "^0.3.2", - "define-property": "^2.0.2", - "extend-shallow": "^3.0.2", - "fragment-cache": "^0.2.1", - "is-windows": "^1.0.2", - "kind-of": "^6.0.2", - "object.pick": "^1.3.0", - "regex-not": "^1.0.0", - "snapdragon": "^0.8.1", - "to-regex": "^3.0.1" - } - }, - "negotiator": { - "version": "0.6.2", - "dev": true, - "optional": true - }, - "next-tick": { - "version": "1.0.0", - "dev": true - }, - "nice-try": { - "version": "1.0.5", - "dev": true - }, - "node-addon-api": { - "version": "2.0.2", - "bundled": true, - "dev": true - }, - "node-fetch": { - "version": "2.1.2", - "dev": true - }, - "node-gyp-build": { - "version": "4.2.3", - "bundled": true, - "dev": true - }, - "normalize-url": { - "version": "4.5.0", - "dev": true, - "optional": true - }, - "number-to-bn": { - "version": "1.7.0", - "dev": true, - "optional": true, - "requires": { - "bn.js": "4.11.6", - "strip-hex-prefix": "1.0.0" - }, - "dependencies": { - "bn.js": { - "version": "4.11.6", - "dev": true, - "optional": true - } - } - }, - "oauth-sign": { - "version": "0.9.0", - "dev": true - }, - "object-assign": { - "version": "4.1.1", - "dev": true - }, - "object-copy": { - "version": "0.1.0", - "dev": true, - "requires": { - "copy-descriptor": "^0.1.0", - "define-property": "^0.2.5", - "kind-of": "^3.0.3" - }, - "dependencies": { - "define-property": { - "version": "0.2.5", - "dev": true, - "requires": { - "is-descriptor": "^0.1.0" - } - }, - "is-accessor-descriptor": { - "version": "0.1.6", - "dev": true, - "requires": { - "kind-of": "^3.0.2" - } - }, - "is-buffer": { - "version": "1.1.6", - "dev": true - }, - "is-data-descriptor": { - "version": "0.1.4", - "dev": true, - "requires": { - "kind-of": "^3.0.2" - } - }, - "is-descriptor": { - "version": "0.1.6", - "dev": true, - "requires": { - "is-accessor-descriptor": "^0.1.6", - "is-data-descriptor": "^0.1.4", - "kind-of": "^5.0.0" - }, - "dependencies": { - "kind-of": { - "version": "5.1.0", - "dev": true - } - } - }, - "kind-of": { - "version": "3.2.2", - "dev": true, - "requires": { - "is-buffer": "^1.1.5" - } - } - } - }, - "object-inspect": { - "version": "1.9.0", - "dev": true - }, - "object-is": { - "version": "1.1.4", - "dev": true, - "requires": { - "call-bind": "^1.0.0", - "define-properties": "^1.1.3" - } - }, - "object-keys": { - "version": "1.1.1", - "dev": true - }, - "object-visit": { - "version": "1.0.1", - "dev": true, - "requires": { - "isobject": "^3.0.0" - } - }, - "object.assign": { - "version": "4.1.2", - "dev": true, - "requires": { - "call-bind": "^1.0.0", - "define-properties": "^1.1.3", - "has-symbols": "^1.0.1", - "object-keys": "^1.1.1" - } - }, - "object.getownpropertydescriptors": { - "version": "2.1.1", - "dev": true, - "requires": { - "call-bind": "^1.0.0", - "define-properties": "^1.1.3", - "es-abstract": "^1.18.0-next.1" - } - }, - "object.pick": { - "version": "1.3.0", - "dev": true, - "requires": { - "isobject": "^3.0.1" - } - }, - "oboe": { - "version": "2.1.4", - "dev": true, - "optional": true, - "requires": { - "http-https": "^1.0.0" - } - }, - "on-finished": { - "version": "2.3.0", - "dev": true, - "optional": true, - "requires": { - "ee-first": "1.1.1" - } - }, - "once": { - "version": "1.4.0", - "dev": true, - "requires": { - "wrappy": "1" - } - }, - "os-homedir": { - "version": "1.0.2", - "dev": true - }, - "os-tmpdir": { - "version": "1.0.2", - "dev": true - }, - "p-cancelable": { - "version": "1.1.0", - "dev": true, - "optional": true - }, - "p-timeout": { - "version": "1.2.1", - "dev": true, - "optional": true, - "requires": { - "p-finally": "^1.0.0" - }, - "dependencies": { - "p-finally": { - "version": "1.0.0", - "dev": true, - "optional": true - } - } - }, - "parse-asn1": { - "version": "5.1.6", - "dev": true, - "optional": true, - "requires": { - "asn1.js": "^5.2.0", - "browserify-aes": "^1.0.0", - "evp_bytestokey": "^1.0.0", - "pbkdf2": "^3.0.3", - "safe-buffer": "^5.1.1" - } - }, - "parse-headers": { - "version": "2.0.3", - "dev": true - }, - "parseurl": { - "version": "1.3.3", - "dev": true, - "optional": true - }, - "pascalcase": { - "version": "0.1.1", - "dev": true - }, - "patch-package": { - "version": "6.2.2", - "dev": true, - "requires": { - "@yarnpkg/lockfile": "^1.1.0", - "chalk": "^2.4.2", - "cross-spawn": "^6.0.5", - "find-yarn-workspace-root": "^1.2.1", - "fs-extra": "^7.0.1", - "is-ci": "^2.0.0", - "klaw-sync": "^6.0.0", - "minimist": "^1.2.0", - "rimraf": "^2.6.3", - "semver": "^5.6.0", - "slash": "^2.0.0", - "tmp": "^0.0.33" - }, - "dependencies": { - "cross-spawn": { - "version": "6.0.5", - "dev": true, - "requires": { - "nice-try": "^1.0.4", - "path-key": "^2.0.1", - "semver": "^5.5.0", - "shebang-command": "^1.2.0", - "which": "^1.2.9" - } - }, - "path-key": { - "version": "2.0.1", - "dev": true - }, - "semver": { - "version": "5.7.1", - "dev": true - }, - "shebang-command": { - "version": "1.2.0", - "dev": true, - "requires": { - "shebang-regex": "^1.0.0" - } - }, - "shebang-regex": { - "version": "1.0.0", - "dev": true - }, - "slash": { - "version": "2.0.0", - "dev": true - }, - "tmp": { - "version": "0.0.33", - "dev": true, - "requires": { - "os-tmpdir": "~1.0.2" - } - }, - "which": { - "version": "1.3.1", - "dev": true, - "requires": { - "isexe": "^2.0.0" - } - } - } - }, - "path-is-absolute": { - "version": "1.0.1", - "dev": true - }, - "path-parse": { - "version": "1.0.6", - "dev": true - }, - "path-to-regexp": { - "version": "0.1.7", - "dev": true, - "optional": true - }, - "pbkdf2": { - "version": "3.1.1", - "dev": true, - "requires": { - "create-hash": "^1.1.2", - "create-hmac": "^1.1.4", - "ripemd160": "^2.0.1", - "safe-buffer": "^5.0.1", - "sha.js": "^2.4.8" - } - }, - "performance-now": { - "version": "2.1.0", - "dev": true - }, - "posix-character-classes": { - "version": "0.1.1", - "dev": true - }, - "precond": { - "version": "0.2.3", - "dev": true - }, - "prepend-http": { - "version": "2.0.0", - "dev": true, - "optional": true - }, - "private": { - "version": "0.1.8", - "dev": true - }, - "process": { - "version": "0.11.10", - "dev": true - }, - "process-nextick-args": { - "version": "2.0.1", - "dev": true - }, - "promise-to-callback": { - "version": "1.0.0", - "dev": true, - "requires": { - "is-fn": "^1.0.0", - "set-immediate-shim": "^1.0.1" - } - }, - "proxy-addr": { - "version": "2.0.6", - "dev": true, - "optional": true, - "requires": { - "forwarded": "~0.1.2", - "ipaddr.js": "1.9.1" - } - }, - "prr": { - "version": "1.0.1", - "dev": true - }, - "pseudomap": { - "version": "1.0.2", - "dev": true - }, - "psl": { - "version": "1.8.0", - "dev": true - }, - "public-encrypt": { - "version": "4.0.3", - "dev": true, - "optional": true, - "requires": { - "bn.js": "^4.1.0", - "browserify-rsa": "^4.0.0", - "create-hash": "^1.1.0", - "parse-asn1": "^5.0.0", - "randombytes": "^2.0.1", - "safe-buffer": "^5.1.2" - } - }, - "pull-cat": { - "version": "1.1.11", - "dev": true - }, - "pull-defer": { - "version": "0.2.3", - "dev": true - }, - "pull-level": { - "version": "2.0.4", - "dev": true, - "requires": { - "level-post": "^1.0.7", - "pull-cat": "^1.1.9", - "pull-live": "^1.0.1", - "pull-pushable": "^2.0.0", - "pull-stream": "^3.4.0", - "pull-window": "^2.1.4", - "stream-to-pull-stream": "^1.7.1" - } - }, - "pull-live": { - "version": "1.0.1", - "dev": true, - "requires": { - "pull-cat": "^1.1.9", - "pull-stream": "^3.4.0" - } - }, - "pull-pushable": { - "version": "2.2.0", - "dev": true - }, - "pull-stream": { - "version": "3.6.14", - "dev": true - }, - "pull-window": { - "version": "2.1.4", - "dev": true, - "requires": { - "looper": "^2.0.0" - } - }, - "pump": { - "version": "3.0.0", - "dev": true, - "optional": true, - "requires": { - "end-of-stream": "^1.1.0", - "once": "^1.3.1" - } - }, - "punycode": { - "version": "2.1.1", - "dev": true - }, - "qs": { - "version": "6.5.2", - "dev": true - }, - "query-string": { - "version": "5.1.1", - "dev": true, - "optional": true, - "requires": { - "decode-uri-component": "^0.2.0", - "object-assign": "^4.1.0", - "strict-uri-encode": "^1.0.0" - } - }, - "randombytes": { - "version": "2.1.0", - "dev": true, - "requires": { - "safe-buffer": "^5.1.0" - } - }, - "randomfill": { - "version": "1.0.4", - "dev": true, - "optional": true, - "requires": { - "randombytes": "^2.0.5", - "safe-buffer": "^5.1.0" - } - }, - "range-parser": { - "version": "1.2.1", - "dev": true, - "optional": true - }, - "raw-body": { - "version": "2.4.0", - "dev": true, - "optional": true, - "requires": { - "bytes": "3.1.0", - "http-errors": "1.7.2", - "iconv-lite": "0.4.24", - "unpipe": "1.0.0" - } - }, - "readable-stream": { - "version": "2.3.7", - "dev": true, - "requires": { - "core-util-is": "~1.0.0", - "inherits": "~2.0.3", - "isarray": "~1.0.0", - "process-nextick-args": "~2.0.0", - "safe-buffer": "~5.1.1", - "string_decoder": "~1.1.1", - "util-deprecate": "~1.0.1" - }, - "dependencies": { - "safe-buffer": { - "version": "5.1.2", - "dev": true - } - } - }, - "regenerate": { - "version": "1.4.2", - "dev": true - }, - "regenerator-runtime": { - "version": "0.11.1", - "dev": true - }, - "regenerator-transform": { - "version": "0.10.1", - "dev": true, - "requires": { - "babel-runtime": "^6.18.0", - "babel-types": "^6.19.0", - "private": "^0.1.6" - } - }, - "regex-not": { - "version": "1.0.2", - "dev": true, - "requires": { - "extend-shallow": "^3.0.2", - "safe-regex": "^1.1.0" - } - }, - "regexp.prototype.flags": { - "version": "1.3.0", - "dev": true, - "requires": { - "define-properties": "^1.1.3", - "es-abstract": "^1.17.0-next.1" - }, - "dependencies": { - "es-abstract": { - "version": "1.17.7", - "dev": true, - "requires": { - "es-to-primitive": "^1.2.1", - "function-bind": "^1.1.1", - "has": "^1.0.3", - "has-symbols": "^1.0.1", - "is-callable": "^1.2.2", - "is-regex": "^1.1.1", - "object-inspect": "^1.8.0", - "object-keys": "^1.1.1", - "object.assign": "^4.1.1", - "string.prototype.trimend": "^1.0.1", - "string.prototype.trimstart": "^1.0.1" - } - } - } - }, - "regexpu-core": { - "version": "2.0.0", - "dev": true, - "requires": { - "regenerate": "^1.2.1", - "regjsgen": "^0.2.0", - "regjsparser": "^0.1.4" - } - }, - "regjsgen": { - "version": "0.2.0", - "dev": true - }, - "regjsparser": { - "version": "0.1.5", - "dev": true, - "requires": { - "jsesc": "~0.5.0" - }, - "dependencies": { - "jsesc": { - "version": "0.5.0", - "dev": true - } - } - }, - "repeat-element": { - "version": "1.1.3", - "dev": true - }, - "repeat-string": { - "version": "1.6.1", - "dev": true - }, - "repeating": { - "version": "2.0.1", - "dev": true, - "requires": { - "is-finite": "^1.0.0" - } - }, - "request": { - "version": "2.88.2", - "dev": true, - "requires": { - "aws-sign2": "~0.7.0", - "aws4": "^1.8.0", - "caseless": "~0.12.0", - "combined-stream": "~1.0.6", - "extend": "~3.0.2", - "forever-agent": "~0.6.1", - "form-data": "~2.3.2", - "har-validator": "~5.1.3", - "http-signature": "~1.2.0", - "is-typedarray": "~1.0.0", - "isstream": "~0.1.2", - "json-stringify-safe": "~5.0.1", - "mime-types": "~2.1.19", - "oauth-sign": "~0.9.0", - "performance-now": "^2.1.0", - "qs": "~6.5.2", - "safe-buffer": "^5.1.2", - "tough-cookie": "~2.5.0", - "tunnel-agent": "^0.6.0", - "uuid": "^3.3.2" - } - }, - "resolve-url": { - "version": "0.2.1", - "dev": true - }, - "responselike": { - "version": "1.0.2", - "dev": true, - "optional": true, - "requires": { - "lowercase-keys": "^1.0.0" - } - }, - "resumer": { - "version": "0.0.0", - "dev": true, - "requires": { - "through": "~2.3.4" - } - }, - "ret": { - "version": "0.1.15", - "dev": true - }, - "rimraf": { - "version": "2.6.3", - "dev": true, - "requires": { - "glob": "^7.1.3" - } - }, - "ripemd160": { - "version": "2.0.2", - "dev": true, - "requires": { - "hash-base": "^3.0.0", - "inherits": "^2.0.1" - } - }, - "rlp": { - "version": "2.2.6", - "dev": true, - "requires": { - "bn.js": "^4.11.1" - } - }, - "rustbn.js": { - "version": "0.2.0", - "dev": true - }, - "safe-buffer": { - "version": "5.2.1", - "dev": true - }, - "safe-event-emitter": { - "version": "1.0.1", - "dev": true, - "requires": { - "events": "^3.0.0" - } - }, - "safe-regex": { - "version": "1.1.0", - "dev": true, - "requires": { - "ret": "~0.1.10" - } - }, - "safer-buffer": { - "version": "2.1.2", - "dev": true - }, - "scrypt-js": { - "version": "3.0.1", - "dev": true - }, - "scryptsy": { - "version": "1.2.1", - "dev": true, - "optional": true, - "requires": { - "pbkdf2": "^3.0.3" - } - }, - "secp256k1": { - "version": "4.0.2", - "dev": true, - "requires": { - "elliptic": "^6.5.2", - "node-addon-api": "^2.0.0", - "node-gyp-build": "^4.2.0" - } - }, - "seedrandom": { - "version": "3.0.1", - "dev": true - }, - "semaphore": { - "version": "1.1.0", - "dev": true - }, - "send": { - "version": "0.17.1", - "dev": true, - "optional": true, - "requires": { - "debug": "2.6.9", - "depd": "~1.1.2", - "destroy": "~1.0.4", - "encodeurl": "~1.0.2", - "escape-html": "~1.0.3", - "etag": "~1.8.1", - "fresh": "0.5.2", - "http-errors": "~1.7.2", - "mime": "1.6.0", - "ms": "2.1.1", - "on-finished": "~2.3.0", - "range-parser": "~1.2.1", - "statuses": "~1.5.0" - }, - "dependencies": { - "debug": { - "version": "2.6.9", - "dev": true, - "optional": true, - "requires": { - "ms": "2.0.0" - }, - "dependencies": { - "ms": { - "version": "2.0.0", - "dev": true, - "optional": true - } - } - }, - "ms": { - "version": "2.1.1", - "dev": true, - "optional": true - } - } - }, - "serve-static": { - "version": "1.14.1", - "dev": true, - "optional": true, - "requires": { - "encodeurl": "~1.0.2", - "escape-html": "~1.0.3", - "parseurl": "~1.3.3", - "send": "0.17.1" - } - }, - "servify": { - "version": "0.1.12", - "dev": true, - "optional": true, - "requires": { - "body-parser": "^1.16.0", - "cors": "^2.8.1", - "express": "^4.14.0", - "request": "^2.79.0", - "xhr": "^2.3.3" - } - }, - "set-immediate-shim": { - "version": "1.0.1", - "dev": true - }, - "set-value": { - "version": "2.0.1", - "dev": true, - "requires": { - "extend-shallow": "^2.0.1", - "is-extendable": "^0.1.1", - "is-plain-object": "^2.0.3", - "split-string": "^3.0.1" - }, - "dependencies": { - "extend-shallow": { - "version": "2.0.1", - "dev": true, - "requires": { - "is-extendable": "^0.1.0" - } - }, - "is-extendable": { - "version": "0.1.1", - "dev": true - } - } - }, - "setimmediate": { - "version": "1.0.5", - "dev": true - }, - "setprototypeof": { - "version": "1.1.1", - "dev": true, - "optional": true - }, - "sha.js": { - "version": "2.4.11", - "dev": true, - "requires": { - "inherits": "^2.0.1", - "safe-buffer": "^5.0.1" - } - }, - "simple-concat": { - "version": "1.0.1", - "dev": true, - "optional": true - }, - "simple-get": { - "version": "2.8.1", - "dev": true, - "optional": true, - "requires": { - "decompress-response": "^3.3.0", - "once": "^1.3.1", - "simple-concat": "^1.0.0" - } - }, - "snapdragon": { - "version": "0.8.2", - "dev": true, - "requires": { - "base": "^0.11.1", - "debug": "^2.2.0", - "define-property": "^0.2.5", - "extend-shallow": "^2.0.1", - "map-cache": "^0.2.2", - "source-map": "^0.5.6", - "source-map-resolve": "^0.5.0", - "use": "^3.1.0" - }, - "dependencies": { - "debug": { - "version": "2.6.9", - "dev": true, - "requires": { - "ms": "2.0.0" - } - }, - "define-property": { - "version": "0.2.5", - "dev": true, - "requires": { - "is-descriptor": "^0.1.0" - } - }, - "extend-shallow": { - "version": "2.0.1", - "dev": true, - "requires": { - "is-extendable": "^0.1.0" - } - }, - "is-accessor-descriptor": { - "version": "0.1.6", - "dev": true, - "requires": { - "kind-of": "^3.0.2" - }, - "dependencies": { - "kind-of": { - "version": "3.2.2", - "dev": true, - "requires": { - "is-buffer": "^1.1.5" - } - } - } - }, - "is-buffer": { - "version": "1.1.6", - "dev": true - }, - "is-data-descriptor": { - "version": "0.1.4", - "dev": true, - "requires": { - "kind-of": "^3.0.2" - }, - "dependencies": { - "kind-of": { - "version": "3.2.2", - "dev": true, - "requires": { - "is-buffer": "^1.1.5" - } - } - } - }, - "is-descriptor": { - "version": "0.1.6", - "dev": true, - "requires": { - "is-accessor-descriptor": "^0.1.6", - "is-data-descriptor": "^0.1.4", - "kind-of": "^5.0.0" - } - }, - "is-extendable": { - "version": "0.1.1", - "dev": true - }, - "kind-of": { - "version": "5.1.0", - "dev": true - }, - "ms": { - "version": "2.0.0", - "dev": true - } - } - }, - "snapdragon-node": { - "version": "2.1.1", - "dev": true, - "requires": { - "define-property": "^1.0.0", - "isobject": "^3.0.0", - "snapdragon-util": "^3.0.1" - }, - "dependencies": { - "define-property": { - "version": "1.0.0", - "dev": true, - "requires": { - "is-descriptor": "^1.0.0" - } - } - } - }, - "snapdragon-util": { - "version": "3.0.1", - "dev": true, - "requires": { - "kind-of": "^3.2.0" - }, - "dependencies": { - "is-buffer": { - "version": "1.1.6", - "dev": true - }, - "kind-of": { - "version": "3.2.2", - "dev": true, - "requires": { - "is-buffer": "^1.1.5" - } - } - } - }, - "source-map": { - "version": "0.5.7", - "dev": true - }, - "source-map-resolve": { - "version": "0.5.3", - "dev": true, - "requires": { - "atob": "^2.1.2", - "decode-uri-component": "^0.2.0", - "resolve-url": "^0.2.1", - "source-map-url": "^0.4.0", - "urix": "^0.1.0" - } - }, - "source-map-support": { - "version": "0.5.12", - "dev": true, - "requires": { - "buffer-from": "^1.0.0", - "source-map": "^0.6.0" - }, - "dependencies": { - "source-map": { - "version": "0.6.1", - "dev": true - } - } - }, - "source-map-url": { - "version": "0.4.0", - "dev": true - }, - "split-string": { - "version": "3.1.0", - "dev": true, - "requires": { - "extend-shallow": "^3.0.0" - } - }, - "sshpk": { - "version": "1.16.1", - "dev": true, - "requires": { - "asn1": "~0.2.3", - "assert-plus": "^1.0.0", - "bcrypt-pbkdf": "^1.0.0", - "dashdash": "^1.12.0", - "ecc-jsbn": "~0.1.1", - "getpass": "^0.1.1", - "jsbn": "~0.1.0", - "safer-buffer": "^2.0.2", - "tweetnacl": "~0.14.0" - }, - "dependencies": { - "tweetnacl": { - "version": "0.14.5", - "dev": true - } - } - }, - "static-extend": { - "version": "0.1.2", - "dev": true, - "requires": { - "define-property": "^0.2.5", - "object-copy": "^0.1.0" - }, - "dependencies": { - "define-property": { - "version": "0.2.5", - "dev": true, - "requires": { - "is-descriptor": "^0.1.0" - } - }, - "is-accessor-descriptor": { - "version": "0.1.6", - "dev": true, - "requires": { - "kind-of": "^3.0.2" - }, - "dependencies": { - "kind-of": { - "version": "3.2.2", - "dev": true, - "requires": { - "is-buffer": "^1.1.5" - } - } - } - }, - "is-buffer": { - "version": "1.1.6", - "dev": true - }, - "is-data-descriptor": { - "version": "0.1.4", - "dev": true, - "requires": { - "kind-of": "^3.0.2" - }, - "dependencies": { - "kind-of": { - "version": "3.2.2", - "dev": true, - "requires": { - "is-buffer": "^1.1.5" - } - } - } - }, - "is-descriptor": { - "version": "0.1.6", - "dev": true, - "requires": { - "is-accessor-descriptor": "^0.1.6", - "is-data-descriptor": "^0.1.4", - "kind-of": "^5.0.0" - } - }, - "kind-of": { - "version": "5.1.0", - "dev": true - } - } - }, - "statuses": { - "version": "1.5.0", - "dev": true, - "optional": true - }, - "stream-to-pull-stream": { - "version": "1.7.3", - "dev": true, - "requires": { - "looper": "^3.0.0", - "pull-stream": "^3.2.3" - }, - "dependencies": { - "looper": { - "version": "3.0.0", - "dev": true - } - } - }, - "strict-uri-encode": { - "version": "1.1.0", - "dev": true, - "optional": true - }, - "string_decoder": { - "version": "1.1.1", - "dev": true, - "requires": { - "safe-buffer": "~5.1.0" - }, - "dependencies": { - "safe-buffer": { - "version": "5.1.2", - "dev": true - } - } - }, - "string.prototype.trim": { - "version": "1.2.3", - "dev": true, - "requires": { - "call-bind": "^1.0.0", - "define-properties": "^1.1.3", - "es-abstract": "^1.18.0-next.1" - } - }, - "string.prototype.trimend": { - "version": "1.0.3", - "dev": true, - "requires": { - "call-bind": "^1.0.0", - "define-properties": "^1.1.3" - } - }, - "string.prototype.trimstart": { - "version": "1.0.3", - "dev": true, - "requires": { - "call-bind": "^1.0.0", - "define-properties": "^1.1.3" - } - }, - "strip-hex-prefix": { - "version": "1.0.0", - "dev": true, - "requires": { - "is-hex-prefixed": "1.0.0" - } - }, - "supports-color": { - "version": "5.5.0", - "dev": true, - "requires": { - "has-flag": "^3.0.0" - } - }, - "swarm-js": { - "version": "0.1.40", - "dev": true, - "optional": true, - "requires": { - "bluebird": "^3.5.0", - "buffer": "^5.0.5", - "eth-lib": "^0.1.26", - "fs-extra": "^4.0.2", - "got": "^7.1.0", - "mime-types": "^2.1.16", - "mkdirp-promise": "^5.0.1", - "mock-fs": "^4.1.0", - "setimmediate": "^1.0.5", - "tar": "^4.0.2", - "xhr-request": "^1.0.1" - }, - "dependencies": { - "fs-extra": { - "version": "4.0.3", - "dev": true, - "optional": true, - "requires": { - "graceful-fs": "^4.1.2", - "jsonfile": "^4.0.0", - "universalify": "^0.1.0" - } - }, - "get-stream": { - "version": "3.0.0", - "dev": true, - "optional": true - }, - "got": { - "version": "7.1.0", - "dev": true, - "optional": true, - "requires": { - "decompress-response": "^3.2.0", - "duplexer3": "^0.1.4", - "get-stream": "^3.0.0", - "is-plain-obj": "^1.1.0", - "is-retry-allowed": "^1.0.0", - "is-stream": "^1.0.0", - "isurl": "^1.0.0-alpha5", - "lowercase-keys": "^1.0.0", - "p-cancelable": "^0.3.0", - "p-timeout": "^1.1.1", - "safe-buffer": "^5.0.1", - "timed-out": "^4.0.0", - "url-parse-lax": "^1.0.0", - "url-to-options": "^1.0.1" - } - }, - "is-stream": { - "version": "1.1.0", - "dev": true, - "optional": true - }, - "p-cancelable": { - "version": "0.3.0", - "dev": true, - "optional": true - }, - "prepend-http": { - "version": "1.0.4", - "dev": true, - "optional": true - }, - "url-parse-lax": { - "version": "1.0.0", - "dev": true, - "optional": true, - "requires": { - "prepend-http": "^1.0.1" - } - } - } - }, - "tape": { - "version": "4.13.3", - "dev": true, - "requires": { - "deep-equal": "~1.1.1", - "defined": "~1.0.0", - "dotignore": "~0.1.2", - "for-each": "~0.3.3", - "function-bind": "~1.1.1", - "glob": "~7.1.6", - "has": "~1.0.3", - "inherits": "~2.0.4", - "is-regex": "~1.0.5", - "minimist": "~1.2.5", - "object-inspect": "~1.7.0", - "resolve": "~1.17.0", - "resumer": "~0.0.0", - "string.prototype.trim": "~1.2.1", - "through": "~2.3.8" - }, - "dependencies": { - "glob": { - "version": "7.1.6", - "dev": true, - "requires": { - "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" - } - }, - "is-regex": { - "version": "1.0.5", - "dev": true, - "requires": { - "has": "^1.0.3" - } - }, - "object-inspect": { - "version": "1.7.0", - "dev": true - }, - "resolve": { - "version": "1.17.0", - "dev": true, - "requires": { - "path-parse": "^1.0.6" - } - } - } - }, - "tar": { - "version": "4.4.13", - "dev": true, - "optional": true, - "requires": { - "chownr": "^1.1.1", - "fs-minipass": "^1.2.5", - "minipass": "^2.8.6", - "minizlib": "^1.2.1", - "mkdirp": "^0.5.0", - "safe-buffer": "^5.1.2", - "yallist": "^3.0.3" - }, - "dependencies": { - "fs-minipass": { - "version": "1.2.7", - "dev": true, - "optional": true, - "requires": { - "minipass": "^2.6.0" - } - }, - "minipass": { - "version": "2.9.0", - "dev": true, - "optional": true, - "requires": { - "safe-buffer": "^5.1.2", - "yallist": "^3.0.0" - } - } - } - }, - "through": { - "version": "2.3.8", - "dev": true - }, - "through2": { - "version": "2.0.5", - "dev": true, - "requires": { - "readable-stream": "~2.3.6", - "xtend": "~4.0.1" - } - }, - "timed-out": { - "version": "4.0.1", - "dev": true, - "optional": true - }, - "tmp": { - "version": "0.1.0", - "dev": true, - "requires": { - "rimraf": "^2.6.3" - } - }, - "to-object-path": { - "version": "0.3.0", - "dev": true, - "requires": { - "kind-of": "^3.0.2" - }, - "dependencies": { - "is-buffer": { - "version": "1.1.6", - "dev": true - }, - "kind-of": { - "version": "3.2.2", - "dev": true, - "requires": { - "is-buffer": "^1.1.5" - } - } - } - }, - "to-readable-stream": { - "version": "1.0.0", - "dev": true, - "optional": true - }, - "to-regex": { - "version": "3.0.2", - "dev": true, - "requires": { - "define-property": "^2.0.2", - "extend-shallow": "^3.0.2", - "regex-not": "^1.0.2", - "safe-regex": "^1.1.0" - } - }, - "toidentifier": { - "version": "1.0.0", - "dev": true, - "optional": true - }, - "tough-cookie": { - "version": "2.5.0", - "dev": true, - "requires": { - "psl": "^1.1.28", - "punycode": "^2.1.1" - } - }, - "trim-right": { - "version": "1.0.1", - "dev": true - }, - "tunnel-agent": { - "version": "0.6.0", - "dev": true, - "requires": { - "safe-buffer": "^5.0.1" - } - }, - "tweetnacl": { - "version": "1.0.3", - "dev": true - }, - "tweetnacl-util": { - "version": "0.15.1", - "dev": true - }, - "type": { - "version": "1.2.0", - "dev": true - }, - "type-is": { - "version": "1.6.18", - "dev": true, - "optional": true, - "requires": { - "media-typer": "0.3.0", - "mime-types": "~2.1.24" - } - }, - "typedarray": { - "version": "0.0.6", - "dev": true - }, - "typedarray-to-buffer": { - "version": "3.1.5", - "dev": true, - "requires": { - "is-typedarray": "^1.0.0" - } - }, - "typewise": { - "version": "1.0.3", - "dev": true, - "requires": { - "typewise-core": "^1.2.0" - } - }, - "typewise-core": { - "version": "1.2.0", - "dev": true - }, - "typewiselite": { - "version": "1.0.0", - "dev": true - }, - "ultron": { - "version": "1.1.1", - "dev": true, - "optional": true - }, - "underscore": { - "version": "1.9.1", - "dev": true, - "optional": true - }, - "union-value": { - "version": "1.0.1", - "dev": true, - "requires": { - "arr-union": "^3.1.0", - "get-value": "^2.0.6", - "is-extendable": "^0.1.1", - "set-value": "^2.0.1" - }, - "dependencies": { - "is-extendable": { - "version": "0.1.1", - "dev": true - } - } - }, - "universalify": { - "version": "0.1.2", - "dev": true - }, - "unorm": { - "version": "1.6.0", - "dev": true - }, - "unpipe": { - "version": "1.0.0", - "dev": true, - "optional": true - }, - "unset-value": { - "version": "1.0.0", - "dev": true, - "requires": { - "has-value": "^0.3.1", - "isobject": "^3.0.0" - }, - "dependencies": { - "has-value": { - "version": "0.3.1", - "dev": true, - "requires": { - "get-value": "^2.0.3", - "has-values": "^0.1.4", - "isobject": "^2.0.0" - }, - "dependencies": { - "isobject": { - "version": "2.1.0", - "dev": true, - "requires": { - "isarray": "1.0.0" - } - } - } - }, - "has-values": { - "version": "0.1.4", - "dev": true - } - } - }, - "uri-js": { - "version": "4.4.1", - "dev": true, - "requires": { - "punycode": "^2.1.0" - } - }, - "urix": { - "version": "0.1.0", - "dev": true - }, - "url-parse-lax": { - "version": "3.0.0", - "dev": true, - "optional": true, - "requires": { - "prepend-http": "^2.0.0" - } - }, - "url-set-query": { - "version": "1.0.0", - "dev": true, - "optional": true - }, - "url-to-options": { - "version": "1.0.1", - "dev": true, - "optional": true - }, - "use": { - "version": "3.1.1", - "dev": true - }, - "utf-8-validate": { - "version": "5.0.4", - "dev": true, - "requires": { - "node-gyp-build": "^4.2.0" - } - }, - "utf8": { - "version": "3.0.0", - "dev": true, - "optional": true - }, - "util-deprecate": { - "version": "1.0.2", - "dev": true - }, - "util.promisify": { - "version": "1.1.1", - "dev": true, - "requires": { - "call-bind": "^1.0.0", - "define-properties": "^1.1.3", - "for-each": "^0.3.3", - "has-symbols": "^1.0.1", - "object.getownpropertydescriptors": "^2.1.1" - } - }, - "utils-merge": { - "version": "1.0.1", - "dev": true, - "optional": true - }, - "uuid": { - "version": "3.4.0", - "dev": true - }, - "varint": { - "version": "5.0.2", - "dev": true, - "optional": true - }, - "vary": { - "version": "1.1.2", - "dev": true, - "optional": true - }, - "verror": { - "version": "1.10.0", - "dev": true, - "requires": { - "assert-plus": "^1.0.0", - "core-util-is": "1.0.2", - "extsprintf": "^1.2.0" - } - }, - "web3": { - "version": "1.2.11", - "dev": true, - "optional": true, - "requires": { - "web3-bzz": "1.2.11", - "web3-core": "1.2.11", - "web3-eth": "1.2.11", - "web3-eth-personal": "1.2.11", - "web3-net": "1.2.11", - "web3-shh": "1.2.11", - "web3-utils": "1.2.11" - } - }, - "web3-bzz": { - "version": "1.2.11", - "dev": true, - "optional": true, - "requires": { - "@types/node": "^12.12.6", - "got": "9.6.0", - "swarm-js": "^0.1.40", - "underscore": "1.9.1" - }, - "dependencies": { - "@types/node": { - "version": "12.19.12", - "dev": true, - "optional": true - } - } - }, - "web3-core": { - "version": "1.2.11", - "dev": true, - "optional": true, - "requires": { - "@types/bn.js": "^4.11.5", - "@types/node": "^12.12.6", - "bignumber.js": "^9.0.0", - "web3-core-helpers": "1.2.11", - "web3-core-method": "1.2.11", - "web3-core-requestmanager": "1.2.11", - "web3-utils": "1.2.11" - }, - "dependencies": { - "@types/node": { - "version": "12.19.12", - "dev": true, - "optional": true - } - } - }, - "web3-core-helpers": { - "version": "1.2.11", - "dev": true, - "optional": true, - "requires": { - "underscore": "1.9.1", - "web3-eth-iban": "1.2.11", - "web3-utils": "1.2.11" - } - }, - "web3-core-method": { - "version": "1.2.11", - "dev": true, - "optional": true, - "requires": { - "@ethersproject/transactions": "^5.0.0-beta.135", - "underscore": "1.9.1", - "web3-core-helpers": "1.2.11", - "web3-core-promievent": "1.2.11", - "web3-core-subscriptions": "1.2.11", - "web3-utils": "1.2.11" - } - }, - "web3-core-promievent": { - "version": "1.2.11", - "dev": true, - "optional": true, - "requires": { - "eventemitter3": "4.0.4" - } - }, - "web3-core-requestmanager": { - "version": "1.2.11", - "dev": true, - "optional": true, - "requires": { - "underscore": "1.9.1", - "web3-core-helpers": "1.2.11", - "web3-providers-http": "1.2.11", - "web3-providers-ipc": "1.2.11", - "web3-providers-ws": "1.2.11" - } - }, - "web3-core-subscriptions": { - "version": "1.2.11", - "dev": true, - "optional": true, - "requires": { - "eventemitter3": "4.0.4", - "underscore": "1.9.1", - "web3-core-helpers": "1.2.11" - } - }, - "web3-eth": { - "version": "1.2.11", - "dev": true, - "optional": true, - "requires": { - "underscore": "1.9.1", - "web3-core": "1.2.11", - "web3-core-helpers": "1.2.11", - "web3-core-method": "1.2.11", - "web3-core-subscriptions": "1.2.11", - "web3-eth-abi": "1.2.11", - "web3-eth-accounts": "1.2.11", - "web3-eth-contract": "1.2.11", - "web3-eth-ens": "1.2.11", - "web3-eth-iban": "1.2.11", - "web3-eth-personal": "1.2.11", - "web3-net": "1.2.11", - "web3-utils": "1.2.11" - } - }, - "web3-eth-abi": { - "version": "1.2.11", - "dev": true, - "optional": true, - "requires": { - "@ethersproject/abi": "5.0.0-beta.153", - "underscore": "1.9.1", - "web3-utils": "1.2.11" - } - }, - "web3-eth-accounts": { - "version": "1.2.11", - "dev": true, - "optional": true, - "requires": { - "crypto-browserify": "3.12.0", - "eth-lib": "0.2.8", - "ethereumjs-common": "^1.3.2", - "ethereumjs-tx": "^2.1.1", - "scrypt-js": "^3.0.1", - "underscore": "1.9.1", - "uuid": "3.3.2", - "web3-core": "1.2.11", - "web3-core-helpers": "1.2.11", - "web3-core-method": "1.2.11", - "web3-utils": "1.2.11" - }, - "dependencies": { - "eth-lib": { - "version": "0.2.8", - "dev": true, - "optional": true, - "requires": { - "bn.js": "^4.11.6", - "elliptic": "^6.4.0", - "xhr-request-promise": "^0.1.2" - } - }, - "uuid": { - "version": "3.3.2", - "dev": true, - "optional": true - } - } - }, - "web3-eth-contract": { - "version": "1.2.11", - "dev": true, - "optional": true, - "requires": { - "@types/bn.js": "^4.11.5", - "underscore": "1.9.1", - "web3-core": "1.2.11", - "web3-core-helpers": "1.2.11", - "web3-core-method": "1.2.11", - "web3-core-promievent": "1.2.11", - "web3-core-subscriptions": "1.2.11", - "web3-eth-abi": "1.2.11", - "web3-utils": "1.2.11" - } - }, - "web3-eth-ens": { - "version": "1.2.11", - "dev": true, - "optional": true, - "requires": { - "content-hash": "^2.5.2", - "eth-ens-namehash": "2.0.8", - "underscore": "1.9.1", - "web3-core": "1.2.11", - "web3-core-helpers": "1.2.11", - "web3-core-promievent": "1.2.11", - "web3-eth-abi": "1.2.11", - "web3-eth-contract": "1.2.11", - "web3-utils": "1.2.11" - } - }, - "web3-eth-iban": { - "version": "1.2.11", - "dev": true, - "optional": true, - "requires": { - "bn.js": "^4.11.9", - "web3-utils": "1.2.11" - } - }, - "web3-eth-personal": { - "version": "1.2.11", - "dev": true, - "optional": true, - "requires": { - "@types/node": "^12.12.6", - "web3-core": "1.2.11", - "web3-core-helpers": "1.2.11", - "web3-core-method": "1.2.11", - "web3-net": "1.2.11", - "web3-utils": "1.2.11" - }, - "dependencies": { - "@types/node": { - "version": "12.19.12", - "dev": true, - "optional": true - } - } - }, - "web3-net": { - "version": "1.2.11", - "dev": true, - "optional": true, - "requires": { - "web3-core": "1.2.11", - "web3-core-method": "1.2.11", - "web3-utils": "1.2.11" - } - }, - "web3-provider-engine": { - "version": "14.2.1", - "dev": true, - "requires": { - "async": "^2.5.0", - "backoff": "^2.5.0", - "clone": "^2.0.0", - "cross-fetch": "^2.1.0", - "eth-block-tracker": "^3.0.0", - "eth-json-rpc-infura": "^3.1.0", - "eth-sig-util": "3.0.0", - "ethereumjs-block": "^1.2.2", - "ethereumjs-tx": "^1.2.0", - "ethereumjs-util": "^5.1.5", - "ethereumjs-vm": "^2.3.4", - "json-rpc-error": "^2.0.0", - "json-stable-stringify": "^1.0.1", - "promise-to-callback": "^1.0.0", - "readable-stream": "^2.2.9", - "request": "^2.85.0", - "semaphore": "^1.0.3", - "ws": "^5.1.1", - "xhr": "^2.2.0", - "xtend": "^4.0.1" - }, - "dependencies": { - "abstract-leveldown": { - "version": "2.6.3", - "dev": true, - "requires": { - "xtend": "~4.0.0" - } - }, - "deferred-leveldown": { - "version": "1.2.2", - "dev": true, - "requires": { - "abstract-leveldown": "~2.6.0" - } - }, - "eth-sig-util": { - "version": "1.4.2", - "dev": true, - "requires": { - "ethereumjs-abi": "git+https://github.com/ethereumjs/ethereumjs-abi.git", - "ethereumjs-util": "^5.1.1" - } - }, - "ethereumjs-account": { - "version": "2.0.5", - "dev": true, - "requires": { - "ethereumjs-util": "^5.0.0", - "rlp": "^2.0.0", - "safe-buffer": "^5.1.1" - } - }, - "ethereumjs-block": { - "version": "1.7.1", - "dev": true, - "requires": { - "async": "^2.0.1", - "ethereum-common": "0.2.0", - "ethereumjs-tx": "^1.2.2", - "ethereumjs-util": "^5.0.0", - "merkle-patricia-tree": "^2.1.2" - }, - "dependencies": { - "ethereum-common": { - "version": "0.2.0", - "dev": true - } - } - }, - "ethereumjs-tx": { - "version": "1.3.7", - "dev": true, - "requires": { - "ethereum-common": "^0.0.18", - "ethereumjs-util": "^5.0.0" - } - }, - "ethereumjs-util": { - "version": "5.2.1", - "dev": true, - "requires": { - "bn.js": "^4.11.0", - "create-hash": "^1.1.2", - "elliptic": "^6.5.2", - "ethereum-cryptography": "^0.1.3", - "ethjs-util": "^0.1.3", - "rlp": "^2.0.0", - "safe-buffer": "^5.1.1" - } - }, - "ethereumjs-vm": { - "version": "2.6.0", - "dev": true, - "requires": { - "async": "^2.1.2", - "async-eventemitter": "^0.2.2", - "ethereumjs-account": "^2.0.3", - "ethereumjs-block": "~2.2.0", - "ethereumjs-common": "^1.1.0", - "ethereumjs-util": "^6.0.0", - "fake-merkle-patricia-tree": "^1.0.1", - "functional-red-black-tree": "^1.0.1", - "merkle-patricia-tree": "^2.3.2", - "rustbn.js": "~0.2.0", - "safe-buffer": "^5.1.1" - }, - "dependencies": { - "ethereumjs-block": { - "version": "2.2.2", - "dev": true, - "requires": { - "async": "^2.0.1", - "ethereumjs-common": "^1.5.0", - "ethereumjs-tx": "^2.1.1", - "ethereumjs-util": "^5.0.0", - "merkle-patricia-tree": "^2.1.2" - }, - "dependencies": { - "ethereumjs-util": { - "version": "5.2.1", - "dev": true, - "requires": { - "bn.js": "^4.11.0", - "create-hash": "^1.1.2", - "elliptic": "^6.5.2", - "ethereum-cryptography": "^0.1.3", - "ethjs-util": "^0.1.3", - "rlp": "^2.0.0", - "safe-buffer": "^5.1.1" - } - } - } - }, - "ethereumjs-tx": { - "version": "2.1.2", - "dev": true, - "requires": { - "ethereumjs-common": "^1.5.0", - "ethereumjs-util": "^6.0.0" - } - }, - "ethereumjs-util": { - "version": "6.2.1", - "dev": true, - "requires": { - "@types/bn.js": "^4.11.3", - "bn.js": "^4.11.0", - "create-hash": "^1.1.2", - "elliptic": "^6.5.2", - "ethereum-cryptography": "^0.1.3", - "ethjs-util": "0.1.6", - "rlp": "^2.2.3" - } - } - } - }, - "isarray": { - "version": "0.0.1", - "dev": true - }, - "level-codec": { - "version": "7.0.1", - "dev": true - }, - "level-errors": { - "version": "1.0.5", - "dev": true, - "requires": { - "errno": "~0.1.1" - } - }, - "level-iterator-stream": { - "version": "1.3.1", - "dev": true, - "requires": { - "inherits": "^2.0.1", - "level-errors": "^1.0.3", - "readable-stream": "^1.0.33", - "xtend": "^4.0.0" - }, - "dependencies": { - "readable-stream": { - "version": "1.1.14", - "dev": true, - "requires": { - "core-util-is": "~1.0.0", - "inherits": "~2.0.1", - "isarray": "0.0.1", - "string_decoder": "~0.10.x" - } - } - } - }, - "level-ws": { - "version": "0.0.0", - "dev": true, - "requires": { - "readable-stream": "~1.0.15", - "xtend": "~2.1.1" - }, - "dependencies": { - "readable-stream": { - "version": "1.0.34", - "dev": true, - "requires": { - "core-util-is": "~1.0.0", - "inherits": "~2.0.1", - "isarray": "0.0.1", - "string_decoder": "~0.10.x" - } - }, - "xtend": { - "version": "2.1.2", - "dev": true, - "requires": { - "object-keys": "~0.4.0" - } - } - } - }, - "levelup": { - "version": "1.3.9", - "dev": true, - "requires": { - "deferred-leveldown": "~1.2.1", - "level-codec": "~7.0.0", - "level-errors": "~1.0.3", - "level-iterator-stream": "~1.3.0", - "prr": "~1.0.1", - "semver": "~5.4.1", - "xtend": "~4.0.0" - } - }, - "ltgt": { - "version": "2.2.1", - "dev": true - }, - "memdown": { - "version": "1.4.1", - "dev": true, - "requires": { - "abstract-leveldown": "~2.7.1", - "functional-red-black-tree": "^1.0.1", - "immediate": "^3.2.3", - "inherits": "~2.0.1", - "ltgt": "~2.2.0", - "safe-buffer": "~5.1.1" - }, - "dependencies": { - "abstract-leveldown": { - "version": "2.7.2", - "dev": true, - "requires": { - "xtend": "~4.0.0" - } - } - } - }, - "merkle-patricia-tree": { - "version": "2.3.2", - "dev": true, - "requires": { - "async": "^1.4.2", - "ethereumjs-util": "^5.0.0", - "level-ws": "0.0.0", - "levelup": "^1.2.1", - "memdown": "^1.0.0", - "readable-stream": "^2.0.0", - "rlp": "^2.0.0", - "semaphore": ">=1.0.1" - }, - "dependencies": { - "async": { - "version": "1.5.2", - "dev": true - } - } - }, - "object-keys": { - "version": "0.4.0", - "dev": true - }, - "safe-buffer": { - "version": "5.1.2", - "dev": true - }, - "semver": { - "version": "5.4.1", - "dev": true - }, - "string_decoder": { - "version": "0.10.31", - "dev": true - }, - "ws": { - "version": "5.2.2", - "dev": true, - "requires": { - "async-limiter": "~1.0.0" - } - } - } - }, - "web3-providers-http": { - "version": "1.2.11", - "dev": true, - "optional": true, - "requires": { - "web3-core-helpers": "1.2.11", - "xhr2-cookies": "1.1.0" - } - }, - "web3-providers-ipc": { - "version": "1.2.11", - "dev": true, - "optional": true, - "requires": { - "oboe": "2.1.4", - "underscore": "1.9.1", - "web3-core-helpers": "1.2.11" - } - }, - "web3-providers-ws": { - "version": "1.2.11", - "dev": true, - "optional": true, - "requires": { - "eventemitter3": "4.0.4", - "underscore": "1.9.1", - "web3-core-helpers": "1.2.11", - "websocket": "^1.0.31" - } - }, - "web3-shh": { - "version": "1.2.11", - "dev": true, - "optional": true, - "requires": { - "web3-core": "1.2.11", - "web3-core-method": "1.2.11", - "web3-core-subscriptions": "1.2.11", - "web3-net": "1.2.11" - } - }, - "web3-utils": { - "version": "1.2.11", - "dev": true, - "optional": true, - "requires": { - "bn.js": "^4.11.9", - "eth-lib": "0.2.8", - "ethereum-bloom-filters": "^1.0.6", - "ethjs-unit": "0.1.6", - "number-to-bn": "1.7.0", - "randombytes": "^2.1.0", - "underscore": "1.9.1", - "utf8": "3.0.0" - }, - "dependencies": { - "eth-lib": { - "version": "0.2.8", - "dev": true, - "optional": true, - "requires": { - "bn.js": "^4.11.6", - "elliptic": "^6.4.0", - "xhr-request-promise": "^0.1.2" - } - } - } - }, - "websocket": { - "version": "1.0.32", - "dev": true, - "requires": { - "bufferutil": "^4.0.1", - "debug": "^2.2.0", - "es5-ext": "^0.10.50", - "typedarray-to-buffer": "^3.1.5", - "utf-8-validate": "^5.0.2", - "yaeti": "^0.0.6" - }, - "dependencies": { - "debug": { - "version": "2.6.9", - "dev": true, - "requires": { - "ms": "2.0.0" - } - }, - "ms": { - "version": "2.0.0", - "dev": true - } - } - }, - "whatwg-fetch": { - "version": "2.0.4", - "dev": true - }, - "wrappy": { - "version": "1.0.2", - "dev": true - }, - "ws": { - "version": "3.3.3", - "dev": true, - "optional": true, - "requires": { - "async-limiter": "~1.0.0", - "safe-buffer": "~5.1.0", - "ultron": "~1.1.0" - }, - "dependencies": { - "safe-buffer": { - "version": "5.1.2", - "dev": true, - "optional": true - } - } - }, - "xhr": { - "version": "2.6.0", - "dev": true, - "requires": { - "global": "~4.4.0", - "is-function": "^1.0.1", - "parse-headers": "^2.0.0", - "xtend": "^4.0.0" - } - }, - "xhr-request": { - "version": "1.1.0", - "dev": true, - "optional": true, - "requires": { - "buffer-to-arraybuffer": "^0.0.5", - "object-assign": "^4.1.1", - "query-string": "^5.0.1", - "simple-get": "^2.7.0", - "timed-out": "^4.0.1", - "url-set-query": "^1.0.0", - "xhr": "^2.0.4" - } - }, - "xhr-request-promise": { - "version": "0.1.3", - "dev": true, - "optional": true, - "requires": { - "xhr-request": "^1.1.0" - } - }, - "xhr2-cookies": { - "version": "1.1.0", - "dev": true, - "optional": true, - "requires": { - "cookiejar": "^2.1.1" - } - }, - "xtend": { - "version": "4.0.2", - "dev": true - }, - "yaeti": { - "version": "0.0.6", - "dev": true - }, - "yallist": { - "version": "3.1.1", - "dev": true - } - } - }, - "get-caller-file": { - "version": "2.0.5", - "resolved": "https://registry.npmjs.org/get-caller-file/-/get-caller-file-2.0.5.tgz", - "integrity": "sha512-DyFP3BM/3YHTQOCUL/w0OZHR0lpKeGrxotcHWcqNEdnltqFwXVfhEBQ94eIo34AfQpo0rGki4cyIiftY06h2Fg==", - "dev": true - }, - "get-func-name": { - "version": "2.0.2", - "resolved": "https://registry.npmjs.org/get-func-name/-/get-func-name-2.0.2.tgz", - "integrity": "sha512-8vXOvuE167CtIc3OyItco7N/dpRtBbYOsPsXCz7X/PMnlGjYjSGuZJgM1Y7mmew7BKf9BqvLX2tnOVy1BBUsxQ==" - }, - "get-intrinsic": { - "version": "1.2.1", - "resolved": "https://registry.npmjs.org/get-intrinsic/-/get-intrinsic-1.2.1.tgz", - "integrity": "sha512-2DcsyfABl+gVHEfCOaTrWgyt+tb6MSEGmKq+kI5HwLbIYgjgmMcV8KQ41uaKz1xxUcn9tJtgFbQUEVcEbd0FYw==", - "dev": true, - "requires": { - "function-bind": "^1.1.1", - "has": "^1.0.3", - "has-proto": "^1.0.1", - "has-symbols": "^1.0.3" - } - }, - "get-port": { - "version": "3.2.0", - "resolved": "https://registry.npmjs.org/get-port/-/get-port-3.2.0.tgz", - "integrity": "sha512-x5UJKlgeUiNT8nyo/AcnwLnZuZNcSjSw0kogRB+Whd1fjjFq4B1hySFxSFWWSn4mIBzg3sRNUDFYc4g5gjPoLg==", - "dev": true - }, - "get-stream": { - "version": "4.1.0", - "resolved": "https://registry.npmjs.org/get-stream/-/get-stream-4.1.0.tgz", - "integrity": "sha512-GMat4EJ5161kIy2HevLlr4luNjBgvmj413KaQA7jt4V8B4RDsfpHk7WQ9GVqfYyyx8OS/L66Kox+rJRNklLK7w==", - "dev": true, - "requires": { - "pump": "^3.0.0" - } - }, - "get-symbol-description": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/get-symbol-description/-/get-symbol-description-1.0.0.tgz", - "integrity": "sha512-2EmdH1YvIQiZpltCNgkuiUnyukzxM/R6NDJX31Ke3BG1Nq5b0S2PhX59UKi9vZpPDQVdqn+1IcaAwnzTT5vCjw==", - "dev": true, - "requires": { - "call-bind": "^1.0.2", - "get-intrinsic": "^1.1.1" - } - }, - "getpass": { - "version": "0.1.7", - "resolved": "https://registry.npmjs.org/getpass/-/getpass-0.1.7.tgz", - "integrity": "sha512-0fzj9JxOLfJ+XGLhR8ze3unN0KZCgZwiSSDz168VERjK8Wl8kVSdcu2kspd4s4wtAa1y/qrVRiAA0WclVsu0ng==", - "dev": true, - "requires": { - "assert-plus": "^1.0.0" - } - }, - "ghost-testrpc": { - "version": "0.0.2", - "resolved": "https://registry.npmjs.org/ghost-testrpc/-/ghost-testrpc-0.0.2.tgz", - "integrity": "sha512-i08dAEgJ2g8z5buJIrCTduwPIhih3DP+hOCTyyryikfV8T0bNvHnGXO67i0DD1H4GBDETTclPy9njZbfluQYrQ==", - "dev": true, - "requires": { - "chalk": "^2.4.2", - "node-emoji": "^1.10.0" - }, - "dependencies": { - "ansi-styles": { - "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, - "requires": { - "color-convert": "^1.9.0" - } - }, - "chalk": { - "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, - "requires": { - "ansi-styles": "^3.2.1", - "escape-string-regexp": "^1.0.5", - "supports-color": "^5.3.0" - } - }, - "color-convert": { - "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, - "requires": { - "color-name": "1.1.3" - } - }, - "color-name": { - "version": "1.1.3", - "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.3.tgz", - "integrity": "sha512-72fSenhMw2HZMTVHeCA9KCmpEIbzWiQsjN+BHcBbS9vr1mtt+vJjPdksIBNUmKAW8TFUDPJK5SUU3QhE9NEXDw==", - "dev": true - }, - "escape-string-regexp": { - "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 - }, - "has-flag": { - "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 - }, - "supports-color": { - "version": "5.5.0", - "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-5.5.0.tgz", - "integrity": "sha512-QjVjwdXIt408MIiAqCX4oUKsgU2EqAGzs2Ppkm4aQYbjm+ZEWEcW4SfFNTr4uMNZma0ey4f5lgLrkB0aX0QMow==", - "dev": true, - "requires": { - "has-flag": "^3.0.0" - } - } - } - }, - "glob": { - "version": "7.2.0", - "resolved": "https://registry.npmjs.org/glob/-/glob-7.2.0.tgz", - "integrity": "sha512-lmLf6gtyrPq8tTjSmrO94wBeQbFR3HbLHbuyD69wuyQkImp2hWqMGB47OX65FBkPffO641IP9jWa1z4ivqG26Q==", - "dev": true, - "requires": { - "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" - } - }, - "glob-parent": { - "version": "5.1.2", - "resolved": "https://registry.npmjs.org/glob-parent/-/glob-parent-5.1.2.tgz", - "integrity": "sha512-AOIgSQCepiJYwP3ARnGx+5VnTu2HBYdzbGP45eLw1vr3zB3vZLeyed1sC9hnbcOc9/SrMyM5RPQrkGz4aS9Zow==", - "dev": true, - "requires": { - "is-glob": "^4.0.1" - } - }, - "global": { - "version": "4.4.0", - "resolved": "https://registry.npmjs.org/global/-/global-4.4.0.tgz", - "integrity": "sha512-wv/LAoHdRE3BeTGz53FAamhGlPLhlssK45usmGFThIi4XqnBmjKQ16u+RNbP7WvigRZDxUsM0J3gcQ5yicaL0w==", - "dev": true, - "requires": { - "min-document": "^2.19.0", - "process": "^0.11.10" - } - }, - "global-modules": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/global-modules/-/global-modules-2.0.0.tgz", - "integrity": "sha512-NGbfmJBp9x8IxyJSd1P+otYK8vonoJactOogrVfFRIAEY1ukil8RSKDz2Yo7wh1oihl51l/r6W4epkeKJHqL8A==", - "dev": true, - "requires": { - "global-prefix": "^3.0.0" - } - }, - "global-prefix": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/global-prefix/-/global-prefix-3.0.0.tgz", - "integrity": "sha512-awConJSVCHVGND6x3tmMaKcQvwXLhjdkmomy2W+Goaui8YPgYgXJZewhg3fWC+DlfqqQuWg8AwqjGTD2nAPVWg==", - "dev": true, - "requires": { - "ini": "^1.3.5", - "kind-of": "^6.0.2", - "which": "^1.3.1" - }, - "dependencies": { - "which": { - "version": "1.3.1", - "resolved": "https://registry.npmjs.org/which/-/which-1.3.1.tgz", - "integrity": "sha512-HxJdYWq1MTIQbJ3nw0cqssHoTNU267KlrDuGZ1WYlxDStUtKUhOaJmh112/TZmHxxUfuJqPXSOm7tDyas0OSIQ==", - "dev": true, - "requires": { - "isexe": "^2.0.0" - } - } - } - }, - "globals": { - "version": "13.20.0", - "resolved": "https://registry.npmjs.org/globals/-/globals-13.20.0.tgz", - "integrity": "sha512-Qg5QtVkCy/kv3FUSlu4ukeZDVf9ee0iXLAUYX13gbR17bnejFTzr4iS9bY7kwCf1NztRNm1t91fjOiyx4CSwPQ==", - "dev": true, - "requires": { - "type-fest": "^0.20.2" - } - }, - "globalthis": { - "version": "1.0.3", - "resolved": "https://registry.npmjs.org/globalthis/-/globalthis-1.0.3.tgz", - "integrity": "sha512-sFdI5LyBiNTHjRd7cGPWapiHWMOXKyuBNX/cWJ3NfzrZQVa8GI/8cofCl74AOVqq9W5kNmguTIzJ/1s2gyI9wA==", - "dev": true, - "requires": { - "define-properties": "^1.1.3" - } - }, - "globby": { - "version": "11.1.0", - "resolved": "https://registry.npmjs.org/globby/-/globby-11.1.0.tgz", - "integrity": "sha512-jhIXaOzy1sb8IyocaruWSn1TjmnBVs8Ayhcy83rmxNJ8q2uWKCAj3CnJY+KpGSXCueAPc0i05kVvVKtP1t9S3g==", - "dev": true, - "requires": { - "array-union": "^2.1.0", - "dir-glob": "^3.0.1", - "fast-glob": "^3.2.9", - "ignore": "^5.2.0", - "merge2": "^1.4.1", - "slash": "^3.0.0" - } - }, - "gopd": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/gopd/-/gopd-1.0.1.tgz", - "integrity": "sha512-d65bNlIadxvpb/A2abVdlqKqV563juRnZ1Wtk6s1sIR8uNsXR70xqIzVqxVf1eTqDunwT2MkczEeaezCKTZhwA==", - "dev": true, - "requires": { - "get-intrinsic": "^1.1.3" - } - }, - "got": { - "version": "9.6.0", - "resolved": "https://registry.npmjs.org/got/-/got-9.6.0.tgz", - "integrity": "sha512-R7eWptXuGYxwijs0eV+v3o6+XH1IqVK8dJOEecQfTmkncw9AV4dcw/Dhxi8MdlqPthxxpZyizMzyg8RTmEsG+Q==", - "dev": true, - "requires": { - "@sindresorhus/is": "^0.14.0", - "@szmarczak/http-timer": "^1.1.2", - "cacheable-request": "^6.0.0", - "decompress-response": "^3.3.0", - "duplexer3": "^0.1.4", - "get-stream": "^4.1.0", - "lowercase-keys": "^1.0.1", - "mimic-response": "^1.0.1", - "p-cancelable": "^1.0.0", - "to-readable-stream": "^1.0.0", - "url-parse-lax": "^3.0.0" - } - }, - "graceful-fs": { - "version": "4.2.11", - "resolved": "https://registry.npmjs.org/graceful-fs/-/graceful-fs-4.2.11.tgz", - "integrity": "sha512-RbJ5/jmFcNNCcDV5o9eTnBLJ/HszWV0P73bc+Ff4nS/rJj+YaS6IGyiOL0VoBYX+l1Wrl3k63h/KrH+nhJ0XvQ==", - "dev": true - }, - "growl": { - "version": "1.10.5", - "resolved": "https://registry.npmjs.org/growl/-/growl-1.10.5.tgz", - "integrity": "sha512-qBr4OuELkhPenW6goKVXiv47US3clb3/IbuWF9KNKEijAy9oeHxU9IgzjvJhHkUzhaj7rOUD7+YGWqUjLp5oSA==", - "dev": true - }, - "handlebars": { - "version": "4.7.7", - "resolved": "https://registry.npmjs.org/handlebars/-/handlebars-4.7.7.tgz", - "integrity": "sha512-aAcXm5OAfE/8IXkcZvCepKU3VzW1/39Fb5ZuqMtgI/hT8X2YgoMvBY5dLhq/cpOvw7Lk1nK/UF71aLG/ZnVYRA==", - "dev": true, - "requires": { - "minimist": "^1.2.5", - "neo-async": "^2.6.0", - "source-map": "^0.6.1", - "uglify-js": "^3.1.4", - "wordwrap": "^1.0.0" - }, - "dependencies": { - "source-map": { - "version": "0.6.1", - "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.6.1.tgz", - "integrity": "sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g==", - "dev": true - } - } - }, - "har-schema": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/har-schema/-/har-schema-2.0.0.tgz", - "integrity": "sha512-Oqluz6zhGX8cyRaTQlFMPw80bSJVG2x/cFb8ZPhUILGgHka9SsokCCOQgpveePerqidZOrT14ipqfJb7ILcW5Q==", - "dev": true - }, - "har-validator": { - "version": "5.1.5", - "resolved": "https://registry.npmjs.org/har-validator/-/har-validator-5.1.5.tgz", - "integrity": "sha512-nmT2T0lljbxdQZfspsno9hgrG3Uir6Ks5afism62poxqBM6sDnMEuPmzTq8XN0OEwqKLLdh1jQI3qyE66Nzb3w==", - "dev": true, - "requires": { - "ajv": "^6.12.3", - "har-schema": "^2.0.0" - } - }, - "hardhat": { - "version": "2.17.0", - "resolved": "https://registry.npmjs.org/hardhat/-/hardhat-2.17.0.tgz", - "integrity": "sha512-CaEGa13tkJNe2/rdaBiive4pmdNShwxvdWVhr1zfb6aVpRhQt9VNO0l/UIBt/zzajz38ZFjvhfM2bj8LDXo9gw==", - "dev": true, - "requires": { - "@ethersproject/abi": "^5.1.2", - "@metamask/eth-sig-util": "^4.0.0", - "@nomicfoundation/ethereumjs-block": "5.0.1", - "@nomicfoundation/ethereumjs-blockchain": "7.0.1", - "@nomicfoundation/ethereumjs-common": "4.0.1", - "@nomicfoundation/ethereumjs-evm": "2.0.1", - "@nomicfoundation/ethereumjs-rlp": "5.0.1", - "@nomicfoundation/ethereumjs-statemanager": "2.0.1", - "@nomicfoundation/ethereumjs-trie": "6.0.1", - "@nomicfoundation/ethereumjs-tx": "5.0.1", - "@nomicfoundation/ethereumjs-util": "9.0.1", - "@nomicfoundation/ethereumjs-vm": "7.0.1", - "@nomicfoundation/solidity-analyzer": "^0.1.0", - "@sentry/node": "^5.18.1", - "@types/bn.js": "^5.1.0", - "@types/lru-cache": "^5.1.0", - "abort-controller": "^3.0.0", - "adm-zip": "^0.4.16", - "aggregate-error": "^3.0.0", - "ansi-escapes": "^4.3.0", - "chalk": "^2.4.2", - "chokidar": "^3.4.0", - "ci-info": "^2.0.0", - "debug": "^4.1.1", - "enquirer": "^2.3.0", - "env-paths": "^2.2.0", - "ethereum-cryptography": "^1.0.3", - "ethereumjs-abi": "^0.6.8", - "find-up": "^2.1.0", - "fp-ts": "1.19.3", - "fs-extra": "^7.0.1", - "glob": "7.2.0", - "immutable": "^4.0.0-rc.12", - "io-ts": "1.10.4", - "keccak": "^3.0.2", - "lodash": "^4.17.11", - "mnemonist": "^0.38.0", - "mocha": "^10.0.0", - "p-map": "^4.0.0", - "raw-body": "^2.4.1", - "resolve": "1.17.0", - "semver": "^6.3.0", - "solc": "0.7.3", - "source-map-support": "^0.5.13", - "stacktrace-parser": "^0.1.10", - "tsort": "0.0.1", - "undici": "^5.14.0", - "uuid": "^8.3.2", - "ws": "^7.4.6" - }, - "dependencies": { - "ansi-styles": { - "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, - "requires": { - "color-convert": "^1.9.0" - } - }, - "chalk": { - "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, - "requires": { - "ansi-styles": "^3.2.1", - "escape-string-regexp": "^1.0.5", - "supports-color": "^5.3.0" - } - }, - "color-convert": { - "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, - "requires": { - "color-name": "1.1.3" - } - }, - "color-name": { - "version": "1.1.3", - "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.3.tgz", - "integrity": "sha512-72fSenhMw2HZMTVHeCA9KCmpEIbzWiQsjN+BHcBbS9vr1mtt+vJjPdksIBNUmKAW8TFUDPJK5SUU3QhE9NEXDw==", - "dev": true - }, - "escape-string-regexp": { - "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 - }, - "has-flag": { - "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 - }, - "jsonfile": { - "version": "2.4.0", - "resolved": "https://registry.npmjs.org/jsonfile/-/jsonfile-2.4.0.tgz", - "integrity": "sha512-PKllAqbgLgxHaj8TElYymKCAgrASebJrWpTnEkOaTowt23VKXXN0sUeriJ+eh7y6ufb/CC5ap11pz71/cM0hUw==", - "dev": true, - "requires": { - "graceful-fs": "^4.1.6" - } - }, - "resolve": { - "version": "1.17.0", - "resolved": "https://registry.npmjs.org/resolve/-/resolve-1.17.0.tgz", - "integrity": "sha512-ic+7JYiV8Vi2yzQGFWOkiZD5Z9z7O2Zhm9XMaTxdJExKasieFCr+yXZ/WmXsckHiKl12ar0y6XiXDx3m4RHn1w==", - "dev": true, - "requires": { - "path-parse": "^1.0.6" - } - }, - "rimraf": { - "version": "2.7.1", - "resolved": "https://registry.npmjs.org/rimraf/-/rimraf-2.7.1.tgz", - "integrity": "sha512-uWjbaKIK3T1OSVptzX7Nl6PvQ3qAGtKEtVRjRuazjfL3Bx5eI409VZSqgND+4UNnmzLVdPj9FqFJNPqBZFve4w==", - "dev": true, - "requires": { - "glob": "^7.1.3" - } - }, - "solc": { - "version": "0.7.3", - "resolved": "https://registry.npmjs.org/solc/-/solc-0.7.3.tgz", - "integrity": "sha512-GAsWNAjGzIDg7VxzP6mPjdurby3IkGCjQcM8GFYZT6RyaoUZKmMU6Y7YwG+tFGhv7dwZ8rmR4iwFDrrD99JwqA==", - "dev": true, - "requires": { - "command-exists": "^1.2.8", - "commander": "3.0.2", - "follow-redirects": "^1.12.1", - "fs-extra": "^0.30.0", - "js-sha3": "0.8.0", - "memorystream": "^0.3.1", - "require-from-string": "^2.0.0", - "semver": "^5.5.0", - "tmp": "0.0.33" - }, - "dependencies": { - "fs-extra": { - "version": "0.30.0", - "resolved": "https://registry.npmjs.org/fs-extra/-/fs-extra-0.30.0.tgz", - "integrity": "sha512-UvSPKyhMn6LEd/WpUaV9C9t3zATuqoqfWc3QdPhPLb58prN9tqYPlPWi8Krxi44loBoUzlobqZ3+8tGpxxSzwA==", - "dev": true, - "requires": { - "graceful-fs": "^4.1.2", - "jsonfile": "^2.1.0", - "klaw": "^1.0.0", - "path-is-absolute": "^1.0.0", - "rimraf": "^2.2.8" - } - }, - "semver": { - "version": "5.7.2", - "resolved": "https://registry.npmjs.org/semver/-/semver-5.7.2.tgz", - "integrity": "sha512-cBznnQ9KjJqU67B52RMC65CMarK2600WFnbkcaiwWq3xy/5haFJlshgnpjovMVJ+Hff49d8GEn0b87C5pDQ10g==", - "dev": true - } - } - }, - "supports-color": { - "version": "5.5.0", - "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-5.5.0.tgz", - "integrity": "sha512-QjVjwdXIt408MIiAqCX4oUKsgU2EqAGzs2Ppkm4aQYbjm+ZEWEcW4SfFNTr4uMNZma0ey4f5lgLrkB0aX0QMow==", - "dev": true, - "requires": { - "has-flag": "^3.0.0" - } - } - } - }, - "hardhat-gas-reporter": { - "version": "1.0.9", - "resolved": "https://registry.npmjs.org/hardhat-gas-reporter/-/hardhat-gas-reporter-1.0.9.tgz", - "integrity": "sha512-INN26G3EW43adGKBNzYWOlI3+rlLnasXTwW79YNnUhXPDa+yHESgt639dJEs37gCjhkbNKcRRJnomXEuMFBXJg==", - "dev": true, - "requires": { - "array-uniq": "1.0.3", - "eth-gas-reporter": "^0.2.25", - "sha1": "^1.1.1" - } - }, - "has": { - "version": "1.0.3", - "resolved": "https://registry.npmjs.org/has/-/has-1.0.3.tgz", - "integrity": "sha512-f2dvO0VU6Oej7RkWJGrehjbzMAjFp5/VKPp5tTpWIV4JHHZK1/BxbFRtf/siA2SWTe09caDmVtYYzWEIbBS4zw==", - "dev": true, - "requires": { - "function-bind": "^1.1.1" - } - }, - "has-bigints": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/has-bigints/-/has-bigints-1.0.2.tgz", - "integrity": "sha512-tSvCKtBr9lkF0Ex0aQiP9N+OpV4zi2r/Nee5VkRDbaqv35RLYMzbwQfFSZZH0kR+Rd6302UJZ2p/bJCEoR3VoQ==", - "dev": true - }, - "has-flag": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz", - "integrity": "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==" - }, - "has-property-descriptors": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/has-property-descriptors/-/has-property-descriptors-1.0.0.tgz", - "integrity": "sha512-62DVLZGoiEBDHQyqG4w9xCuZ7eJEwNmJRWw2VY84Oedb7WFcA27fiEVe8oUQx9hAUJ4ekurquucTGwsyO1XGdQ==", - "dev": true, - "requires": { - "get-intrinsic": "^1.1.1" - } - }, - "has-proto": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/has-proto/-/has-proto-1.0.1.tgz", - "integrity": "sha512-7qE+iP+O+bgF9clE5+UoBFzE65mlBiVj3tKCrlNQ0Ogwm0BjpT/gK4SlLYDMybDh5I3TCTKnPPa0oMG7JDYrhg==", - "dev": true - }, - "has-symbols": { - "version": "1.0.3", - "resolved": "https://registry.npmjs.org/has-symbols/-/has-symbols-1.0.3.tgz", - "integrity": "sha512-l3LCuF6MgDNwTDKkdYGEihYjt5pRPbEg46rtlmnSPlUbgmB8LOIrKJbYYFBSbnPaJexMKtiPO8hmeRjRz2Td+A==", - "dev": true - }, - "has-tostringtag": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/has-tostringtag/-/has-tostringtag-1.0.0.tgz", - "integrity": "sha512-kFjcSNhnlGV1kyoGk7OXKSawH5JOb/LzUc5w9B02hOTO0dfFRjbHQKvg1d6cf3HbeUmtU9VbbV3qzZ2Teh97WQ==", - "dev": true, - "requires": { - "has-symbols": "^1.0.2" - } - }, - "hash-base": { - "version": "3.1.0", - "resolved": "https://registry.npmjs.org/hash-base/-/hash-base-3.1.0.tgz", - "integrity": "sha512-1nmYp/rhMDiE7AYkDw+lLwlAzz0AntGIe51F3RfFfEqyQ3feY2eI/NcwC6umIQVOASPMsWJLJScWKSSvzL9IVA==", - "requires": { - "inherits": "^2.0.4", - "readable-stream": "^3.6.0", - "safe-buffer": "^5.2.0" - }, - "dependencies": { - "safe-buffer": { - "version": "5.2.1", - "resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.2.1.tgz", - "integrity": "sha512-rp3So07KcdmmKbGvgaNxQSJr7bGVSVk5S9Eq1F+ppbRo70+YeaDxkw5Dd8NPN+GD6bjnYm2VuPuCXmpuYvmCXQ==" - } - } - }, - "hash.js": { - "version": "1.1.7", - "resolved": "https://registry.npmjs.org/hash.js/-/hash.js-1.1.7.tgz", - "integrity": "sha512-taOaskGt4z4SOANNseOviYDvjEJinIkRgmp7LbKP2YTTmVxWBl87s/uzK9r+44BclBSp2X7K1hqeNfz9JbBeXA==", - "requires": { - "inherits": "^2.0.3", - "minimalistic-assert": "^1.0.1" - } - }, - "he": { - "version": "1.2.0", - "resolved": "https://registry.npmjs.org/he/-/he-1.2.0.tgz", - "integrity": "sha512-F/1DnUGPopORZi0ni+CvrCgHQ5FyEAHRLSApuYWMmrbSwoN2Mn/7k+Gl38gJnR7yyDZk6WLXwiGod1JOWNDKGw==", - "dev": true - }, - "hmac-drbg": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/hmac-drbg/-/hmac-drbg-1.0.1.tgz", - "integrity": "sha512-Tti3gMqLdZfhOQY1Mzf/AanLiqh1WTiJgEj26ZuYQ9fbkLomzGchCws4FyrSd4VkpBfiNhaE1On+lOz894jvXg==", - "requires": { - "hash.js": "^1.0.3", - "minimalistic-assert": "^1.0.0", - "minimalistic-crypto-utils": "^1.0.1" - } - }, - "hosted-git-info": { - "version": "2.8.9", - "resolved": "https://registry.npmjs.org/hosted-git-info/-/hosted-git-info-2.8.9.tgz", - "integrity": "sha512-mxIDAb9Lsm6DoOJ7xH+5+X4y1LU/4Hi50L9C5sIswK3JzULS4bwk1FvjdBgvYR4bzT4tuUQiC15FE2f5HbLvYw==", - "dev": true - }, - "http-basic": { - "version": "8.1.3", - "resolved": "https://registry.npmjs.org/http-basic/-/http-basic-8.1.3.tgz", - "integrity": "sha512-/EcDMwJZh3mABI2NhGfHOGOeOZITqfkEO4p/xK+l3NpyncIHUQBoMvCSF/b5GqvKtySC2srL/GGG3+EtlqlmCw==", - "dev": true, - "requires": { - "caseless": "^0.12.0", - "concat-stream": "^1.6.2", - "http-response-object": "^3.0.1", - "parse-cache-control": "^1.0.1" - } - }, - "http-cache-semantics": { - "version": "4.1.1", - "resolved": "https://registry.npmjs.org/http-cache-semantics/-/http-cache-semantics-4.1.1.tgz", - "integrity": "sha512-er295DKPVsV82j5kw1Gjt+ADA/XYHsajl82cGNQG2eyoPkvgUhX+nDIyelzhIWbbsXP39EHcI6l5tYs2FYqYXQ==", - "dev": true - }, - "http-errors": { - "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, - "requires": { - "depd": "2.0.0", - "inherits": "2.0.4", - "setprototypeof": "1.2.0", - "statuses": "2.0.1", - "toidentifier": "1.0.1" - } - }, - "http-https": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/http-https/-/http-https-1.0.0.tgz", - "integrity": "sha512-o0PWwVCSp3O0wS6FvNr6xfBCHgt0m1tvPLFOCc2iFDKTRAXhB7m8klDf7ErowFH8POa6dVdGatKU5I1YYwzUyg==", - "dev": true - }, - "http-response-object": { - "version": "3.0.2", - "resolved": "https://registry.npmjs.org/http-response-object/-/http-response-object-3.0.2.tgz", - "integrity": "sha512-bqX0XTF6fnXSQcEJ2Iuyr75yVakyjIDCqroJQ/aHfSdlM743Cwqoi2nDYMzLGWUcuTWGWy8AAvOKXTfiv6q9RA==", - "dev": true, - "requires": { - "@types/node": "^10.0.3" - }, - "dependencies": { - "@types/node": { - "version": "10.17.60", - "resolved": "https://registry.npmjs.org/@types/node/-/node-10.17.60.tgz", - "integrity": "sha512-F0KIgDJfy2nA3zMLmWGKxcH2ZVEtCZXHHdOQs2gSaQ27+lNeEfGxzkIw90aXswATX7AZ33tahPbzy6KAfUreVw==", - "dev": true - } - } - }, - "http-signature": { - "version": "1.2.0", - "resolved": "https://registry.npmjs.org/http-signature/-/http-signature-1.2.0.tgz", - "integrity": "sha512-CAbnr6Rz4CYQkLYUtSNXxQPUH2gK8f3iWexVlsnMeD+GjlsQ0Xsy1cOX+mN3dtxYomRy21CiOzU8Uhw6OwncEQ==", - "dev": true, - "requires": { - "assert-plus": "^1.0.0", - "jsprim": "^1.2.2", - "sshpk": "^1.7.0" - } - }, - "http2-wrapper": { - "version": "2.2.0", - "resolved": "https://registry.npmjs.org/http2-wrapper/-/http2-wrapper-2.2.0.tgz", - "integrity": "sha512-kZB0wxMo0sh1PehyjJUWRFEd99KC5TLjZ2cULC4f9iqJBAmKQQXEICjxl5iPJRwP40dpeHFqqhm7tYCvODpqpQ==", - "dev": true, - "requires": { - "quick-lru": "^5.1.1", - "resolve-alpn": "^1.2.0" - } - }, - "https-proxy-agent": { - "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, - "requires": { - "agent-base": "6", - "debug": "4" - } - }, - "iconv-lite": { - "version": "0.4.24", - "resolved": "https://registry.npmjs.org/iconv-lite/-/iconv-lite-0.4.24.tgz", - "integrity": "sha512-v3MXnZAcvnywkTUEZomIActle7RXXeedOR31wwl7VlyoXO4Qi9arvSenNQWne1TcRwhCL1HwLI21bEqdpj8/rA==", - "dev": true, - "requires": { - "safer-buffer": ">= 2.1.2 < 3" - } - }, - "idna-uts46-hx": { - "version": "2.3.1", - "resolved": "https://registry.npmjs.org/idna-uts46-hx/-/idna-uts46-hx-2.3.1.tgz", - "integrity": "sha512-PWoF9Keq6laYdIRwwCdhTPl60xRqAloYNMQLiyUnG42VjT53oW07BXIRM+NK7eQjzXjAk2gUvX9caRxlnF9TAA==", - "dev": true, - "requires": { - "punycode": "2.1.0" - } - }, - "ieee754": { - "version": "1.2.1", - "resolved": "https://registry.npmjs.org/ieee754/-/ieee754-1.2.1.tgz", - "integrity": "sha512-dcyqhDvX1C46lXZcVqCpK+FtMRQVdIMN6/Df5js2zouUsqG7I6sFxitIC+7KYK29KdXOLHdu9zL4sFnoVQnqaA==" - }, - "ignore": { - "version": "5.2.4", - "resolved": "https://registry.npmjs.org/ignore/-/ignore-5.2.4.tgz", - "integrity": "sha512-MAb38BcSbH0eHNBxn7ql2NH/kX33OkB3lZ1BNdh7ENeRChHTYsTvWrMubiIAMNS2llXEEgZ1MUOBtXChP3kaFQ==", - "dev": true - }, - "immutable": { - "version": "4.3.1", - "resolved": "https://registry.npmjs.org/immutable/-/immutable-4.3.1.tgz", - "integrity": "sha512-lj9cnmB/kVS0QHsJnYKD1uo3o39nrbKxszjnqS9Fr6NB7bZzW45U6WSGBPKXDL/CvDKqDNPA4r3DoDQ8GTxo2A==", - "dev": true - }, - "import-fresh": { - "version": "3.3.0", - "resolved": "https://registry.npmjs.org/import-fresh/-/import-fresh-3.3.0.tgz", - "integrity": "sha512-veYYhQa+D1QBKznvhUHxb8faxlrwUnxseDAbAp457E0wLNio2bOSKnjYDhMj+YiAq61xrMGhQk9iXVk5FzgQMw==", - "dev": true, - "requires": { - "parent-module": "^1.0.0", - "resolve-from": "^4.0.0" - } - }, - "imurmurhash": { - "version": "0.1.4", - "resolved": "https://registry.npmjs.org/imurmurhash/-/imurmurhash-0.1.4.tgz", - "integrity": "sha512-JmXMZ6wuvDmLiHEml9ykzqO6lwFbof0GG4IkcGaENdCRDDmMVnny7s5HsIgHCbaq0w2MyPhDqkhTUgS2LU2PHA==", - "dev": true - }, - "indent-string": { - "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 - }, - "inflight": { - "version": "1.0.6", - "resolved": "https://registry.npmjs.org/inflight/-/inflight-1.0.6.tgz", - "integrity": "sha512-k92I/b08q4wvFscXCLvqfsHCrjrF7yiXsQuIVvVE7N82W3+aqpzuUdBbfhWcy/FZR3/4IgflMgKLOsvPDrGCJA==", - "dev": true, - "requires": { - "once": "^1.3.0", - "wrappy": "1" - } - }, - "inherits": { - "version": "2.0.4", - "resolved": "https://registry.npmjs.org/inherits/-/inherits-2.0.4.tgz", - "integrity": "sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ==" - }, - "ini": { - "version": "1.3.8", - "resolved": "https://registry.npmjs.org/ini/-/ini-1.3.8.tgz", - "integrity": "sha512-JV/yugV2uzW5iMRSiZAyDtQd+nxtUnjeLt0acNdw98kKLrvuRVyB80tsREOE7yvGVgalhZ6RNXCmEHkUKBKxew==", - "dev": true - }, - "internal-slot": { - "version": "1.0.5", - "resolved": "https://registry.npmjs.org/internal-slot/-/internal-slot-1.0.5.tgz", - "integrity": "sha512-Y+R5hJrzs52QCG2laLn4udYVnxsfny9CpOhNhUvk/SSSVyF6T27FzRbF0sroPidSu3X8oEAkOn2K804mjpt6UQ==", - "dev": true, - "requires": { - "get-intrinsic": "^1.2.0", - "has": "^1.0.3", - "side-channel": "^1.0.4" - } - }, - "interpret": { - "version": "1.4.0", - "resolved": "https://registry.npmjs.org/interpret/-/interpret-1.4.0.tgz", - "integrity": "sha512-agE4QfB2Lkp9uICn7BAqoscw4SZP9kTE2hxiFI3jBPmXJfdqiahTbUuKGsMoN2GtqL9AxhYioAcVvgsb1HvRbA==", - "dev": true - }, - "invert-kv": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/invert-kv/-/invert-kv-1.0.0.tgz", - "integrity": "sha512-xgs2NH9AE66ucSq4cNG1nhSFghr5l6tdL15Pk+jl46bmmBapgoaY/AacXyaDznAqmGL99TiLSQgO/XazFSKYeQ==", - "dev": true - }, - "io-ts": { - "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, - "requires": { - "fp-ts": "^1.0.0" - } - }, - "ipaddr.js": { - "version": "1.9.1", - "resolved": "https://registry.npmjs.org/ipaddr.js/-/ipaddr.js-1.9.1.tgz", - "integrity": "sha512-0KI/607xoxSToH7GjN1FfSbLoU0+btTicjsQSWQlh/hZykN8KpmMf7uYwPW3R+akZ6R/w18ZlXSHBYXiYUPO3g==", - "dev": true - }, - "is-arguments": { - "version": "1.1.1", - "resolved": "https://registry.npmjs.org/is-arguments/-/is-arguments-1.1.1.tgz", - "integrity": "sha512-8Q7EARjzEnKpt/PCD7e1cgUS0a6X8u5tdSiMqXhojOdoV9TsMsiO+9VLC5vAmO8N7/GmXn7yjR8qnA6bVAEzfA==", - "dev": true, - "requires": { - "call-bind": "^1.0.2", - "has-tostringtag": "^1.0.0" - } - }, - "is-array-buffer": { - "version": "3.0.2", - "resolved": "https://registry.npmjs.org/is-array-buffer/-/is-array-buffer-3.0.2.tgz", - "integrity": "sha512-y+FyyR/w8vfIRq4eQcM1EYgSTnmHXPqaF+IgzgraytCFq5Xh8lllDVmAZolPJiZttZLeFSINPYMaEJ7/vWUa1w==", - "dev": true, - "requires": { - "call-bind": "^1.0.2", - "get-intrinsic": "^1.2.0", - "is-typed-array": "^1.1.10" - } - }, - "is-arrayish": { - "version": "0.2.1", - "resolved": "https://registry.npmjs.org/is-arrayish/-/is-arrayish-0.2.1.tgz", - "integrity": "sha512-zz06S8t0ozoDXMG+ube26zeCTNXcKIPJZJi8hBrF4idCLms4CG9QtK7qBl1boi5ODzFpjswb5JPmHCbMpjaYzg==", - "dev": true - }, - "is-bigint": { - "version": "1.0.4", - "resolved": "https://registry.npmjs.org/is-bigint/-/is-bigint-1.0.4.tgz", - "integrity": "sha512-zB9CruMamjym81i2JZ3UMn54PKGsQzsJeo6xvN3HJJ4CAsQNB6iRutp2To77OfCNuoxspsIhzaPoO1zyCEhFOg==", - "dev": true, - "requires": { - "has-bigints": "^1.0.1" - } - }, - "is-binary-path": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/is-binary-path/-/is-binary-path-2.1.0.tgz", - "integrity": "sha512-ZMERYes6pDydyuGidse7OsHxtbI7WVeUEozgR/g7rd0xUimYNlvZRE/K2MgZTjWy725IfelLeVcEM97mmtRGXw==", - "dev": true, - "requires": { - "binary-extensions": "^2.0.0" - } - }, - "is-boolean-object": { - "version": "1.1.2", - "resolved": "https://registry.npmjs.org/is-boolean-object/-/is-boolean-object-1.1.2.tgz", - "integrity": "sha512-gDYaKHJmnj4aWxyj6YHyXVpdQawtVLHU5cb+eztPGczf6cjuTdwve5ZIEfgXqH4e57An1D1AKf8CZ3kYrQRqYA==", - "dev": true, - "requires": { - "call-bind": "^1.0.2", - "has-tostringtag": "^1.0.0" - } - }, - "is-buffer": { - "version": "2.0.5", - "resolved": "https://registry.npmjs.org/is-buffer/-/is-buffer-2.0.5.tgz", - "integrity": "sha512-i2R6zNFDwgEHJyQUtJEk0XFi1i0dPFn/oqjK3/vPCcDeJvW5NQ83V8QbicfF1SupOaB0h8ntgBC2YiE7dfyctQ==", - "dev": true - }, - "is-callable": { - "version": "1.2.7", - "resolved": "https://registry.npmjs.org/is-callable/-/is-callable-1.2.7.tgz", - "integrity": "sha512-1BC0BVFhS/p0qtw6enp8e+8OD0UrK0oFLztSjNzhcKA3WDuJxxAPXzPuPtKkjEY9UUoEWlX/8fgKeu2S8i9JTA==", - "dev": true - }, - "is-ci": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/is-ci/-/is-ci-2.0.0.tgz", - "integrity": "sha512-YfJT7rkpQB0updsdHLGWrvhBJfcfzNNawYDNIyQXJz0IViGf75O8EBPKSdvw2rF+LGCsX4FZ8tcr3b19LcZq4w==", - "dev": true, - "requires": { - "ci-info": "^2.0.0" - } - }, - "is-core-module": { - "version": "2.12.1", - "resolved": "https://registry.npmjs.org/is-core-module/-/is-core-module-2.12.1.tgz", - "integrity": "sha512-Q4ZuBAe2FUsKtyQJoQHlvP8OvBERxO3jEmy1I7hcRXcJBGGHFh/aJBswbXuS9sgrDH2QUO8ilkwNPHvHMd8clg==", - "dev": true, - "requires": { - "has": "^1.0.3" - } - }, - "is-date-object": { - "version": "1.0.5", - "resolved": "https://registry.npmjs.org/is-date-object/-/is-date-object-1.0.5.tgz", - "integrity": "sha512-9YQaSxsAiSwcvS33MBk3wTCVnWK+HhF8VZR2jRxehM16QcVOdHqPn4VPHmRK4lSr38n9JriurInLcP90xsYNfQ==", - "dev": true, - "requires": { - "has-tostringtag": "^1.0.0" - } - }, - "is-docker": { - "version": "2.2.1", - "resolved": "https://registry.npmjs.org/is-docker/-/is-docker-2.2.1.tgz", - "integrity": "sha512-F+i2BKsFrH66iaUFc0woD8sLy8getkwTwtOBjvs56Cx4CgJDeKQeqfz8wAYiSb8JOprWhHH5p77PbmYCvvUuXQ==", - "dev": true - }, - "is-extglob": { - "version": "2.1.1", - "resolved": "https://registry.npmjs.org/is-extglob/-/is-extglob-2.1.1.tgz", - "integrity": "sha512-SbKbANkN603Vi4jEZv49LeVJMn4yGwsbzZworEoyEiutsN3nJYdbO36zfhGJ6QEDpOZIFkDtnq5JRxmvl3jsoQ==", - "dev": true - }, - "is-fullwidth-code-point": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/is-fullwidth-code-point/-/is-fullwidth-code-point-2.0.0.tgz", - "integrity": "sha512-VHskAKYM8RfSFXwee5t5cbN5PZeq1Wrh6qd5bkyiXIf6UQcN6w/A0eXM9r6t8d+GYOh+o6ZhiEnb88LN/Y8m2w==", - "dev": true - }, - "is-function": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/is-function/-/is-function-1.0.2.tgz", - "integrity": "sha512-lw7DUp0aWXYg+CBCN+JKkcE0Q2RayZnSvnZBlwgxHBQhqt5pZNVy4Ri7H9GmmXkdu7LUthszM+Tor1u/2iBcpQ==", - "dev": true - }, - "is-generator-function": { - "version": "1.0.10", - "resolved": "https://registry.npmjs.org/is-generator-function/-/is-generator-function-1.0.10.tgz", - "integrity": "sha512-jsEjy9l3yiXEQ+PsXdmBwEPcOxaXWLspKdplFUVI9vq1iZgIekeC0L167qeu86czQaxed3q/Uzuw0swL0irL8A==", - "dev": true, - "requires": { - "has-tostringtag": "^1.0.0" - } - }, - "is-glob": { - "version": "4.0.3", - "resolved": "https://registry.npmjs.org/is-glob/-/is-glob-4.0.3.tgz", - "integrity": "sha512-xelSayHH36ZgE7ZWhli7pW34hNbNl8Ojv5KVmkJD4hBdD3th8Tfk9vYasLM+mXWOZhFkgZfxhLSnrwRr4elSSg==", - "dev": true, - "requires": { - "is-extglob": "^2.1.1" - } - }, - "is-hex-prefixed": { - "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==" - }, - "is-negative-zero": { - "version": "2.0.2", - "resolved": "https://registry.npmjs.org/is-negative-zero/-/is-negative-zero-2.0.2.tgz", - "integrity": "sha512-dqJvarLawXsFbNDeJW7zAz8ItJ9cd28YufuuFzh0G8pNHjJMnY08Dv7sYX2uF5UpQOwieAeOExEYAWWfu7ZZUA==", - "dev": true - }, - "is-number": { - "version": "7.0.0", - "resolved": "https://registry.npmjs.org/is-number/-/is-number-7.0.0.tgz", - "integrity": "sha512-41Cifkg6e8TylSpdtTpeLVMqvSBEVzTttHvERD741+pnZ8ANv0004MRL43QKPDlK9cGvNp6NZWZUBlbGXYxxng==", - "dev": true - }, - "is-number-object": { - "version": "1.0.7", - "resolved": "https://registry.npmjs.org/is-number-object/-/is-number-object-1.0.7.tgz", - "integrity": "sha512-k1U0IRzLMo7ZlYIfzRu23Oh6MiIFasgpb9X76eqfFZAqwH44UI4KTBvBYIZ1dSL9ZzChTB9ShHfLkR4pdW5krQ==", - "dev": true, - "requires": { - "has-tostringtag": "^1.0.0" - } - }, - "is-plain-obj": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/is-plain-obj/-/is-plain-obj-2.1.0.tgz", - "integrity": "sha512-YWnfyRwxL/+SsrWYfOpUtz5b3YD+nyfkHvjbcanzk8zgyO4ASD67uVMRt8k5bM4lLMDnXfriRhOpemw+NfT1eA==", - "dev": true - }, - "is-regex": { - "version": "1.1.4", - "resolved": "https://registry.npmjs.org/is-regex/-/is-regex-1.1.4.tgz", - "integrity": "sha512-kvRdxDsxZjhzUX07ZnLydzS1TU/TJlTUHHY4YLL87e37oUA49DfkLqgy+VjFocowy29cKvcSiu+kIv728jTTVg==", - "dev": true, - "requires": { - "call-bind": "^1.0.2", - "has-tostringtag": "^1.0.0" - } - }, - "is-shared-array-buffer": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/is-shared-array-buffer/-/is-shared-array-buffer-1.0.2.tgz", - "integrity": "sha512-sqN2UDu1/0y6uvXyStCOzyhAjCSlHceFoMKJW8W9EU9cvic/QdsZ0kEU93HEy3IUEFZIiH/3w+AH/UQbPHNdhA==", - "dev": true, - "requires": { - "call-bind": "^1.0.2" - } - }, - "is-string": { - "version": "1.0.7", - "resolved": "https://registry.npmjs.org/is-string/-/is-string-1.0.7.tgz", - "integrity": "sha512-tE2UXzivje6ofPW7l23cjDOMa09gb7xlAqG6jG5ej6uPV32TlWP3NKPigtaGeHNu9fohccRYvIiZMfOOnOYUtg==", - "dev": true, - "requires": { - "has-tostringtag": "^1.0.0" - } - }, - "is-symbol": { - "version": "1.0.4", - "resolved": "https://registry.npmjs.org/is-symbol/-/is-symbol-1.0.4.tgz", - "integrity": "sha512-C/CPBqKWnvdcxqIARxyOh4v1UUEOCHpgDa0WYgpKDFMszcrPcffg5uhwSgPCLD2WWxmq6isisz87tzT01tuGhg==", - "dev": true, - "requires": { - "has-symbols": "^1.0.2" - } - }, - "is-typed-array": { - "version": "1.1.12", - "resolved": "https://registry.npmjs.org/is-typed-array/-/is-typed-array-1.1.12.tgz", - "integrity": "sha512-Z14TF2JNG8Lss5/HMqt0//T9JeHXttXy5pH/DBU4vi98ozO2btxzq9MwYDZYnKwU8nRsz/+GVFVRDq3DkVuSPg==", - "dev": true, - "requires": { - "which-typed-array": "^1.1.11" - } - }, - "is-typedarray": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/is-typedarray/-/is-typedarray-1.0.0.tgz", - "integrity": "sha512-cyA56iCMHAh5CdzjJIa4aohJyeO1YbwLi3Jc35MmRU6poroFjIGZzUzupGiRPOjgHg9TLu43xbpwXk523fMxKA==", - "dev": true - }, - "is-unicode-supported": { - "version": "0.1.0", - "resolved": "https://registry.npmjs.org/is-unicode-supported/-/is-unicode-supported-0.1.0.tgz", - "integrity": "sha512-knxG2q4UC3u8stRGyAVJCOdxFmv5DZiRcdlIaAQXAbSfJya+OhopNotLQrstBhququ4ZpuKbDc/8S6mgXgPFPw==", - "dev": true - }, - "is-url": { - "version": "1.2.4", - "resolved": "https://registry.npmjs.org/is-url/-/is-url-1.2.4.tgz", - "integrity": "sha512-ITvGim8FhRiYe4IQ5uHSkj7pVaPDrCTkNd3yq3cV7iZAcJdHTUMPMEHcqSOy9xZ9qFenQCvi+2wjH9a1nXqHww==", - "dev": true - }, - "is-utf8": { - "version": "0.2.1", - "resolved": "https://registry.npmjs.org/is-utf8/-/is-utf8-0.2.1.tgz", - "integrity": "sha512-rMYPYvCzsXywIsldgLaSoPlw5PfoB/ssr7hY4pLfcodrA5M/eArza1a9VmTiNIBNMjOGr1Ow9mTyU2o69U6U9Q==", - "dev": true - }, - "is-weakref": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/is-weakref/-/is-weakref-1.0.2.tgz", - "integrity": "sha512-qctsuLZmIQ0+vSSMfoVvyFe2+GSEvnmZ2ezTup1SBse9+twCCeial6EEi3Nc2KFcf6+qz2FBPnjXsk8xhKSaPQ==", - "dev": true, - "requires": { - "call-bind": "^1.0.2" - } - }, - "is-wsl": { - "version": "2.2.0", - "resolved": "https://registry.npmjs.org/is-wsl/-/is-wsl-2.2.0.tgz", - "integrity": "sha512-fKzAra0rGJUUBwGBgNkHZuToZcn+TtXHpeCgmkMJMMYx1sQDYaCSyjJBSCa2nH1DGm7s3n1oBnohoVTBaN7Lww==", - "dev": true, - "requires": { - "is-docker": "^2.0.0" - } - }, - "isarray": { - "version": "2.0.5", - "resolved": "https://registry.npmjs.org/isarray/-/isarray-2.0.5.tgz", - "integrity": "sha512-xHjhDr3cNBK0BzdUJSPXZntQUx/mwMS5Rw4A7lPJ90XGAO6ISP/ePDNuo0vhqOZU+UD5JoodwCAAoZQd3FeAKw==", - "dev": true - }, - "isexe": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/isexe/-/isexe-2.0.0.tgz", - "integrity": "sha512-RHxMLp9lnKHGHRng9QFhRCMbYAcVpn69smSGcq3f36xjgVVWThj4qqLbTLlq7Ssj8B+fIQ1EuCEGI2lKsyQeIw==", - "dev": true - }, - "isstream": { - "version": "0.1.2", - "resolved": "https://registry.npmjs.org/isstream/-/isstream-0.1.2.tgz", - "integrity": "sha512-Yljz7ffyPbrLpLngrMtZ7NduUgVvi6wG9RJ9IUcyCd59YQ911PBJphODUcbOVbqYfxe1wuYf/LJ8PauMRwsM/g==", - "dev": true - }, - "js-sdsl": { - "version": "4.4.1", - "resolved": "https://registry.npmjs.org/js-sdsl/-/js-sdsl-4.4.1.tgz", - "integrity": "sha512-6Gsx8R0RucyePbWqPssR8DyfuXmLBooYN5cZFZKjHGnQuaf7pEzhtpceagJxVu4LqhYY5EYA7nko3FmeHZ1KbA==", - "dev": true - }, - "js-sha3": { - "version": "0.8.0", - "resolved": "https://registry.npmjs.org/js-sha3/-/js-sha3-0.8.0.tgz", - "integrity": "sha512-gF1cRrHhIzNfToc802P800N8PpXS+evLLXfsVpowqmAFR9uwbi89WvXg2QspOmXL8QL86J4T1EpFu+yUkwJY3Q==" - }, - "js-tokens": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/js-tokens/-/js-tokens-4.0.0.tgz", - "integrity": "sha512-RdJUflcE3cUzKiMqQgsCu06FPu9UdIJO0beYbPhHN4k6apgJtifcoCtT9bcxOpYBtpD2kCM6Sbzg4CausW/PKQ==", - "dev": true - }, - "js-yaml": { - "version": "3.14.1", - "resolved": "https://registry.npmjs.org/js-yaml/-/js-yaml-3.14.1.tgz", - "integrity": "sha512-okMH7OXXJ7YrN9Ok3/SXrnu4iX9yOk+25nqX4imS2npuvTYDmo/QEZoqwZkYaIDk3jVvBOTOIEgEhaLOynBS9g==", - "dev": true, - "requires": { - "argparse": "^1.0.7", - "esprima": "^4.0.0" - } - }, - "jsbn": { - "version": "0.1.1", - "resolved": "https://registry.npmjs.org/jsbn/-/jsbn-0.1.1.tgz", - "integrity": "sha512-UVU9dibq2JcFWxQPA6KCqj5O42VOmAY3zQUfEKxU0KpTGXwNoCjkX1e13eHNvw/xPynt6pU0rZ1htjWTNTSXsg==", - "dev": true - }, - "json-buffer": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/json-buffer/-/json-buffer-3.0.0.tgz", - "integrity": "sha512-CuUqjv0FUZIdXkHPI8MezCnFCdaTAacej1TZYulLoAg1h/PhwkdXFN4V/gzY4g+fMBCOV2xF+rp7t2XD2ns/NQ==", - "dev": true - }, - "json-parse-even-better-errors": { - "version": "2.3.1", - "resolved": "https://registry.npmjs.org/json-parse-even-better-errors/-/json-parse-even-better-errors-2.3.1.tgz", - "integrity": "sha512-xyFwyhro/JEof6Ghe2iz2NcXoj2sloNsWr/XsERDK/oiPCfaNhl5ONfp+jQdAZRQQ0IJWNzH9zIZF7li91kh2w==", - "dev": true - }, - "json-schema": { - "version": "0.4.0", - "resolved": "https://registry.npmjs.org/json-schema/-/json-schema-0.4.0.tgz", - "integrity": "sha512-es94M3nTIfsEPisRafak+HDLfHXnKBhV3vU5eqPcS3flIWqcxJWgXHXiey3YrpaNsanY5ei1VoYEbOzijuq9BA==", - "dev": true - }, - "json-schema-traverse": { - "version": "0.4.1", - "resolved": "https://registry.npmjs.org/json-schema-traverse/-/json-schema-traverse-0.4.1.tgz", - "integrity": "sha512-xbbCH5dCYU5T8LcEhhuh7HJ88HXuW3qsI3Y0zOZFKfZEHcpWiHU/Jxzk629Brsab/mMiHQti9wMP+845RPe3Vg==", - "dev": true - }, - "json-stable-stringify-without-jsonify": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/json-stable-stringify-without-jsonify/-/json-stable-stringify-without-jsonify-1.0.1.tgz", - "integrity": "sha512-Bdboy+l7tA3OGW6FjyFHWkP5LuByj1Tk33Ljyq0axyzdk9//JSi2u3fP1QSmd1KNwq6VOKYGlAu87CisVir6Pw==", - "dev": true - }, - "json-stringify-safe": { - "version": "5.0.1", - "resolved": "https://registry.npmjs.org/json-stringify-safe/-/json-stringify-safe-5.0.1.tgz", - "integrity": "sha512-ZClg6AaYvamvYEE82d3Iyd3vSSIjQ+odgjaTzRuO3s7toCdFKczob2i0zCh7JE8kWn17yvAWhUVxvqGwUalsRA==", - "dev": true - }, - "json5": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/json5/-/json5-1.0.2.tgz", - "integrity": "sha512-g1MWMLBiz8FKi1e4w0UyVL3w+iJceWAFBAaBnnGKOpNa5f8TLktkbre1+s6oICydWAm+HRUGTmI+//xv2hvXYA==", - "dev": true, - "requires": { - "minimist": "^1.2.0" - } - }, - "jsonfile": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/jsonfile/-/jsonfile-4.0.0.tgz", - "integrity": "sha512-m6F1R3z8jjlf2imQHS2Qez5sjKWQzbuuhuJ/FKYFRZvPE3PuHcSMVZzfsLhGVOkfd20obL5SWEBew5ShlquNxg==", - "dev": true, - "requires": { - "graceful-fs": "^4.1.6" - } - }, - "jsonschema": { - "version": "1.4.1", - "resolved": "https://registry.npmjs.org/jsonschema/-/jsonschema-1.4.1.tgz", - "integrity": "sha512-S6cATIPVv1z0IlxdN+zUk5EPjkGCdnhN4wVSBlvoUO1tOLJootbo9CquNJmbIh4yikWHiUedhRYrNPn1arpEmQ==", - "dev": true - }, - "jsprim": { - "version": "1.4.2", - "resolved": "https://registry.npmjs.org/jsprim/-/jsprim-1.4.2.tgz", - "integrity": "sha512-P2bSOMAc/ciLz6DzgjVlGJP9+BrJWu5UDGK70C2iweC5QBIeFf0ZXRvGjEj2uYgrY2MkAAhsSWHDWlFtEroZWw==", - "dev": true, - "requires": { - "assert-plus": "1.0.0", - "extsprintf": "1.3.0", - "json-schema": "0.4.0", - "verror": "1.10.0" - } - }, - "keccak": { - "version": "3.0.3", - "resolved": "https://registry.npmjs.org/keccak/-/keccak-3.0.3.tgz", - "integrity": "sha512-JZrLIAJWuZxKbCilMpNz5Vj7Vtb4scDG3dMXLOsbzBmQGyjwE61BbW7bJkfKKCShXiQZt3T6sBgALRtmd+nZaQ==", - "requires": { - "node-addon-api": "^2.0.0", - "node-gyp-build": "^4.2.0", - "readable-stream": "^3.6.0" - } - }, - "keccak256": { - "version": "1.0.6", - "resolved": "https://registry.npmjs.org/keccak256/-/keccak256-1.0.6.tgz", - "integrity": "sha512-8GLiM01PkdJVGUhR1e6M/AvWnSqYS0HaERI+K/QtStGDGlSTx2B1zTqZk4Zlqu5TxHJNTxWAdP9Y+WI50OApUw==", - "requires": { - "bn.js": "^5.2.0", - "buffer": "^6.0.3", - "keccak": "^3.0.2" - } - }, - "keyv": { - "version": "3.1.0", - "resolved": "https://registry.npmjs.org/keyv/-/keyv-3.1.0.tgz", - "integrity": "sha512-9ykJ/46SN/9KPM/sichzQ7OvXyGDYKGTaDlKMGCAlg2UK8KRy4jb0d8sFc+0Tt0YYnThq8X2RZgCg74RPxgcVA==", - "dev": true, - "requires": { - "json-buffer": "3.0.0" - } - }, - "kind-of": { - "version": "6.0.3", - "resolved": "https://registry.npmjs.org/kind-of/-/kind-of-6.0.3.tgz", - "integrity": "sha512-dcS1ul+9tmeD95T+x28/ehLgd9mENa3LsvDTtzm3vyBEO7RPptvAD+t44WVXaUjTBRcrpFeFlC8WCruUR456hw==", - "dev": true - }, - "klaw": { - "version": "1.3.1", - "resolved": "https://registry.npmjs.org/klaw/-/klaw-1.3.1.tgz", - "integrity": "sha512-TED5xi9gGQjGpNnvRWknrwAB1eL5GciPfVFOt3Vk1OJCVDQbzuSfrF3hkUQKlsgKrG1F+0t5W0m+Fje1jIt8rw==", - "dev": true, - "requires": { - "graceful-fs": "^4.1.9" - } - }, - "klaw-sync": { - "version": "6.0.0", - "resolved": "https://registry.npmjs.org/klaw-sync/-/klaw-sync-6.0.0.tgz", - "integrity": "sha512-nIeuVSzdCCs6TDPTqI8w1Yre34sSq7AkZ4B3sfOBbI2CgVSB4Du4aLQijFU2+lhAFCwt9+42Hel6lQNIv6AntQ==", - "dev": true, - "requires": { - "graceful-fs": "^4.1.11" - } - }, - "lcid": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/lcid/-/lcid-1.0.0.tgz", - "integrity": "sha512-YiGkH6EnGrDGqLMITnGjXtGmNtjoXw9SVUzcaos8RBi7Ps0VBylkq+vOcY9QE5poLasPCR849ucFUkl0UzUyOw==", - "dev": true, - "requires": { - "invert-kv": "^1.0.0" - } - }, - "level": { - "version": "8.0.0", - "resolved": "https://registry.npmjs.org/level/-/level-8.0.0.tgz", - "integrity": "sha512-ypf0jjAk2BWI33yzEaaotpq7fkOPALKAgDBxggO6Q9HGX2MRXn0wbP1Jn/tJv1gtL867+YOjOB49WaUF3UoJNQ==", - "dev": true, - "requires": { - "browser-level": "^1.0.1", - "classic-level": "^1.2.0" - } - }, - "level-supports": { - "version": "4.0.1", - "resolved": "https://registry.npmjs.org/level-supports/-/level-supports-4.0.1.tgz", - "integrity": "sha512-PbXpve8rKeNcZ9C1mUicC9auIYFyGpkV9/i6g76tLgANwWhtG2v7I4xNBUlkn3lE2/dZF3Pi0ygYGtLc4RXXdA==", - "dev": true - }, - "level-transcoder": { - "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, - "requires": { - "buffer": "^6.0.3", - "module-error": "^1.0.1" - } - }, - "levn": { - "version": "0.4.1", - "resolved": "https://registry.npmjs.org/levn/-/levn-0.4.1.tgz", - "integrity": "sha512-+bT2uH4E5LGE7h/n3evcS/sQlJXCpIp6ym8OWJ5eV6+67Dsql/LaaT7qJBAt2rzfoa/5QBGBhxDix1dMt2kQKQ==", - "dev": true, - "requires": { - "prelude-ls": "^1.2.1", - "type-check": "~0.4.0" - } - }, - "lines-and-columns": { - "version": "1.2.4", - "resolved": "https://registry.npmjs.org/lines-and-columns/-/lines-and-columns-1.2.4.tgz", - "integrity": "sha512-7ylylesZQ/PV29jhEDl3Ufjo6ZX7gCqJr5F7PKrqc93v7fzSymt1BpwEU8nAUXs8qzzvqhbjhK5QZg6Mt/HkBg==", - "dev": true - }, - "load-json-file": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/load-json-file/-/load-json-file-1.1.0.tgz", - "integrity": "sha512-cy7ZdNRXdablkXYNI049pthVeXFurRyb9+hA/dZzerZ0pGTx42z+y+ssxBaVV2l70t1muq5IdKhn4UtcoGUY9A==", - "dev": true, - "requires": { - "graceful-fs": "^4.1.2", - "parse-json": "^2.2.0", - "pify": "^2.0.0", - "pinkie-promise": "^2.0.0", - "strip-bom": "^2.0.0" - }, - "dependencies": { - "parse-json": { - "version": "2.2.0", - "resolved": "https://registry.npmjs.org/parse-json/-/parse-json-2.2.0.tgz", - "integrity": "sha512-QR/GGaKCkhwk1ePQNYDRKYZ3mwU9ypsKhB0XyFnLQdomyEqk3e8wpW3V5Jp88zbxK4n5ST1nqo+g9juTpownhQ==", - "dev": true, - "requires": { - "error-ex": "^1.2.0" - } - }, - "pify": { - "version": "2.3.0", - "resolved": "https://registry.npmjs.org/pify/-/pify-2.3.0.tgz", - "integrity": "sha512-udgsAY+fTnvv7kI7aaxbqwWNb0AHiB0qBO89PZKPkoTmGOgdbrHDKD+0B2X4uTfJ/FT1R09r9gTsjUjNJotuog==", - "dev": true - }, - "strip-bom": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/strip-bom/-/strip-bom-2.0.0.tgz", - "integrity": "sha512-kwrX1y7czp1E69n2ajbG65mIo9dqvJ+8aBQXOGVxqwvNbsXdFM6Lq37dLAY3mknUwru8CfcCbfOLL/gMo+fi3g==", - "dev": true, - "requires": { - "is-utf8": "^0.2.0" - } - } - } - }, - "locate-path": { - "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, - "requires": { - "p-locate": "^2.0.0", - "path-exists": "^3.0.0" - } - }, - "lodash": { - "version": "4.17.21", - "resolved": "https://registry.npmjs.org/lodash/-/lodash-4.17.21.tgz", - "integrity": "sha512-v2kDEe57lecTulaDIuNTPy3Ry4gLGJ6Z1O3vE1krgXZNrsQ+LFTGHVxVjcXPs17LhbZVGedAJv8XZ1tvj5FvSg==" - }, - "lodash.assign": { - "version": "4.2.0", - "resolved": "https://registry.npmjs.org/lodash.assign/-/lodash.assign-4.2.0.tgz", - "integrity": "sha512-hFuH8TY+Yji7Eja3mGiuAxBqLagejScbG8GbG0j6o9vzn0YL14My+ktnqtZgFTosKymC9/44wP6s7xyuLfnClw==", - "dev": true - }, - "lodash.merge": { - "version": "4.6.2", - "resolved": "https://registry.npmjs.org/lodash.merge/-/lodash.merge-4.6.2.tgz", - "integrity": "sha512-0KpjqXRVvrYyCsX1swR/XTK0va6VQkQM6MNo7PqW77ByjAhoARA8EfrP1N4+KlKj8YS0ZUCtRT/YUuhyYDujIQ==", - "dev": true - }, - "lodash.truncate": { - "version": "4.4.2", - "resolved": "https://registry.npmjs.org/lodash.truncate/-/lodash.truncate-4.4.2.tgz", - "integrity": "sha512-jttmRe7bRse52OsWIMDLaXxWqRAmtIUccAQ3garviCqJjafXOfNMO0yMfNpdD6zbGaTU0P5Nz7e7gAT6cKmJRw==", - "dev": true - }, - "log-symbols": { - "version": "4.1.0", - "resolved": "https://registry.npmjs.org/log-symbols/-/log-symbols-4.1.0.tgz", - "integrity": "sha512-8XPvpAA8uyhfteu8pIvQxpJZ7SYYdpUivZpGy6sFsBuKRY/7rQGavedeB8aK+Zkyq6upMFVL/9AW6vOYzfRyLg==", - "dev": true, - "requires": { - "chalk": "^4.1.0", - "is-unicode-supported": "^0.1.0" - } - }, - "loupe": { - "version": "2.3.6", - "resolved": "https://registry.npmjs.org/loupe/-/loupe-2.3.6.tgz", - "integrity": "sha512-RaPMZKiMy8/JruncMU5Bt6na1eftNoo++R4Y+N2FrxkDVTrGvcyzFTsaGif4QTeKESheMGegbhw6iUAq+5A8zA==", - "requires": { - "get-func-name": "^2.0.0" - } - }, - "lowercase-keys": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/lowercase-keys/-/lowercase-keys-1.0.1.tgz", - "integrity": "sha512-G2Lj61tXDnVFFOi8VZds+SoQjtQC3dgokKdDG2mTm1tx4m50NUHBOZSBwQQHyy0V12A0JTG4icfZQH+xPyh8VA==", - "dev": true - }, - "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==", - "dev": true - }, - "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, - "requires": { - "yallist": "^3.0.2" - } - }, - "make-error": { - "version": "1.3.6", - "resolved": "https://registry.npmjs.org/make-error/-/make-error-1.3.6.tgz", - "integrity": "sha512-s8UhlNe7vPKomQhC1qFelMokr/Sc3AgNbso3n74mVPA5LTZwkB9NlXf4XPamLxJE8h0gh73rM94xvwRT2CVInw==", - "dev": true - }, - "markdown-table": { - "version": "1.1.3", - "resolved": "https://registry.npmjs.org/markdown-table/-/markdown-table-1.1.3.tgz", - "integrity": "sha512-1RUZVgQlpJSPWYbFSpmudq5nHY1doEIv89gBtF0s4gW1GF2XorxcA/70M5vq7rLv0a6mhOUccRsqkwhwLCIQ2Q==", - "dev": true - }, - "mcl-wasm": { - "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 - }, - "md5.js": { - "version": "1.3.5", - "resolved": "https://registry.npmjs.org/md5.js/-/md5.js-1.3.5.tgz", - "integrity": "sha512-xitP+WxNPcTTOgnTJcrhM0xvdPepipPSf3I8EIpGKeFLjt3PlJLIDG3u8EX53ZIubkb+5U2+3rELYpEhHhzdkg==", - "requires": { - "hash-base": "^3.0.0", - "inherits": "^2.0.1", - "safe-buffer": "^5.1.2" - } - }, - "media-typer": { - "version": "0.3.0", - "resolved": "https://registry.npmjs.org/media-typer/-/media-typer-0.3.0.tgz", - "integrity": "sha512-dq+qelQ9akHpcOl/gUVRTxVIOkAJ1wR3QAvb4RsVjS8oVoFjDGTc679wJYmUmknUF5HwMLOgb5O+a3KxfWapPQ==", - "dev": true - }, - "memory-level": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/memory-level/-/memory-level-1.0.0.tgz", - "integrity": "sha512-UXzwewuWeHBz5krr7EvehKcmLFNoXxGcvuYhC41tRnkrTbJohtS7kVn9akmgirtRygg+f7Yjsfi8Uu5SGSQ4Og==", - "dev": true, - "requires": { - "abstract-level": "^1.0.0", - "functional-red-black-tree": "^1.0.1", - "module-error": "^1.0.1" - } - }, - "memorystream": { - "version": "0.3.1", - "resolved": "https://registry.npmjs.org/memorystream/-/memorystream-0.3.1.tgz", - "integrity": "sha512-S3UwM3yj5mtUSEfP41UZmt/0SCoVYUcU1rkXv+BQ5Ig8ndL4sPoJNBUJERafdPb5jjHJGuMgytgKvKIf58XNBw==", - "dev": true - }, - "merge-descriptors": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/merge-descriptors/-/merge-descriptors-1.0.1.tgz", - "integrity": "sha512-cCi6g3/Zr1iqQi6ySbseM1Xvooa98N0w31jzUYrXPX2xqObmFGHJ0tQ5u74H3mVh7wLouTseZyYIq39g8cNp1w==", - "dev": true - }, - "merge2": { - "version": "1.4.1", - "resolved": "https://registry.npmjs.org/merge2/-/merge2-1.4.1.tgz", - "integrity": "sha512-8q7VEgMJW4J8tcfVPy8g09NcQwZdbwFEqhe/WZkoIzjn/3TGDwtOCYtXGxA3O8tPzpczCCDgv+P2P5y00ZJOOg==", - "dev": true - }, - "merkletreejs": { - "version": "0.3.11", - "resolved": "https://registry.npmjs.org/merkletreejs/-/merkletreejs-0.3.11.tgz", - "integrity": "sha512-LJKTl4iVNTndhL+3Uz/tfkjD0klIWsHlUzgtuNnNrsf7bAlXR30m+xYB7lHr5Z/l6e/yAIsr26Dabx6Buo4VGQ==", - "requires": { - "bignumber.js": "^9.0.1", - "buffer-reverse": "^1.0.1", - "crypto-js": "^4.2.0", - "treeify": "^1.1.0", - "web3-utils": "^1.3.4" - } - }, - "methods": { - "version": "1.1.2", - "resolved": "https://registry.npmjs.org/methods/-/methods-1.1.2.tgz", - "integrity": "sha512-iclAHeNqNm68zFtnZ0e+1L2yUIdvzNoauKU4WBA3VvH/vPFieF7qfRlwUZU+DA9P9bPXIS90ulxoUoCH23sV2w==", - "dev": true - }, - "micromatch": { - "version": "4.0.5", - "resolved": "https://registry.npmjs.org/micromatch/-/micromatch-4.0.5.tgz", - "integrity": "sha512-DMy+ERcEW2q8Z2Po+WNXuw3c5YaUSFjAO5GsJqfEl7UjvtIuFKO6ZrKvcItdy98dwFI2N1tg3zNIdKaQT+aNdA==", - "dev": true, - "requires": { - "braces": "^3.0.2", - "picomatch": "^2.3.1" - } - }, - "miller-rabin": { - "version": "4.0.1", - "resolved": "https://registry.npmjs.org/miller-rabin/-/miller-rabin-4.0.1.tgz", - "integrity": "sha512-115fLhvZVqWwHPbClyntxEVfVDfl9DLLTuJvq3g2O/Oxi8AiNouAHvDSzHS0viUJc+V5vm3eq91Xwqn9dp4jRA==", - "dev": true, - "requires": { - "bn.js": "^4.0.0", - "brorand": "^1.0.1" - }, - "dependencies": { - "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==", - "dev": true - } - } - }, - "mime": { - "version": "1.6.0", - "resolved": "https://registry.npmjs.org/mime/-/mime-1.6.0.tgz", - "integrity": "sha512-x0Vn8spI+wuJ1O6S7gnbaQg8Pxh4NNHb7KSINmEWKiPE4RKOplvijn+NkmYmmRgP68mc70j2EbeTFRsrswaQeg==", - "dev": true - }, - "mime-db": { - "version": "1.52.0", - "resolved": "https://registry.npmjs.org/mime-db/-/mime-db-1.52.0.tgz", - "integrity": "sha512-sPU4uV7dYlvtWJxwwxHD0PuihVNiE7TyAbQ5SWxDCB9mUYvOgroQOwYQQOKPJ8CIbE+1ETVlOoK1UC2nU3gYvg==", - "dev": true - }, - "mime-types": { - "version": "2.1.35", - "resolved": "https://registry.npmjs.org/mime-types/-/mime-types-2.1.35.tgz", - "integrity": "sha512-ZDY+bPm5zTTF+YpCrAU9nK0UgICYPT0QtT1NZWFv4s++TNkcgVaT0g6+4R2uI4MjQjzysHB1zxuWL50hzaeXiw==", - "dev": true, - "requires": { - "mime-db": "1.52.0" - } - }, - "mimic-response": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/mimic-response/-/mimic-response-1.0.1.tgz", - "integrity": "sha512-j5EctnkH7amfV/q5Hgmoal1g2QHFJRraOtmx0JpIqkxhBhI/lJSl1nMpQ45hVarwNETOoWEimndZ4QK0RHxuxQ==", - "dev": true - }, - "min-document": { - "version": "2.19.0", - "resolved": "https://registry.npmjs.org/min-document/-/min-document-2.19.0.tgz", - "integrity": "sha512-9Wy1B3m3f66bPPmU5hdA4DR4PB2OfDU/+GS3yAB7IQozE3tqXaVv2zOjgla7MEGSRv95+ILmOuvhLkOK6wJtCQ==", - "dev": true, - "requires": { - "dom-walk": "^0.1.0" - } - }, - "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==" - }, - "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==" - }, - "minimatch": { - "version": "3.1.2", - "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.1.2.tgz", - "integrity": "sha512-J7p63hRiAjw1NDEww1W7i37+ByIrOWO5XQQAzZ3VOcL0PNybwpfmV/N05zFAzwQ9USyEcX6t3UO+K5aqBQOIHw==", - "dev": true, - "requires": { - "brace-expansion": "^1.1.7" - } - }, - "minimist": { - "version": "1.2.8", - "resolved": "https://registry.npmjs.org/minimist/-/minimist-1.2.8.tgz", - "integrity": "sha512-2yyAR8qBkN3YuheJanUpWC5U3bb5osDywNB8RzDVlDwDHbocAJveqqj1u8+SVD7jkWT4yvsHCpWqqWqAxb0zCA==", - "dev": true - }, - "minipass": { - "version": "2.9.0", - "resolved": "https://registry.npmjs.org/minipass/-/minipass-2.9.0.tgz", - "integrity": "sha512-wxfUjg9WebH+CUDX/CdbRlh5SmfZiy/hpkxaRI16Y9W56Pa75sWgd/rvFilSgrauD9NyFymP/+JFV3KwzIsJeg==", - "dev": true, - "requires": { - "safe-buffer": "^5.1.2", - "yallist": "^3.0.0" - } - }, - "minizlib": { - "version": "1.3.3", - "resolved": "https://registry.npmjs.org/minizlib/-/minizlib-1.3.3.tgz", - "integrity": "sha512-6ZYMOEnmVsdCeTJVE0W9ZD+pVnE8h9Hma/iOwwRDsdQoePpoX56/8B6z3P9VNwppJuBKNRuFDRNRqRWexT9G9Q==", - "dev": true, - "requires": { - "minipass": "^2.9.0" - } - }, - "mkdirp": { - "version": "0.5.6", - "resolved": "https://registry.npmjs.org/mkdirp/-/mkdirp-0.5.6.tgz", - "integrity": "sha512-FP+p8RB8OWpF3YZBCrP5gtADmtXApB5AMLn+vdyA+PyxCjrCs00mjyUozssO33cwDeT3wNGdLxJ5M//YqtHAJw==", - "dev": true, - "requires": { - "minimist": "^1.2.6" - } - }, - "mkdirp-promise": { - "version": "5.0.1", - "resolved": "https://registry.npmjs.org/mkdirp-promise/-/mkdirp-promise-5.0.1.tgz", - "integrity": "sha512-Hepn5kb1lJPtVW84RFT40YG1OddBNTOVUZR2bzQUHc+Z03en8/3uX0+060JDhcEzyO08HmipsN9DcnFMxhIL9w==", - "dev": true, - "requires": { - "mkdirp": "*" - } - }, - "mnemonist": { - "version": "0.38.5", - "resolved": "https://registry.npmjs.org/mnemonist/-/mnemonist-0.38.5.tgz", - "integrity": "sha512-bZTFT5rrPKtPJxj8KSV0WkPyNxl72vQepqqVUAW2ARUpUSF2qXMB6jZj7hW5/k7C1rtpzqbD/IIbJwLXUjCHeg==", - "dev": true, - "requires": { - "obliterator": "^2.0.0" - } - }, - "mocha": { - "version": "10.2.0", - "resolved": "https://registry.npmjs.org/mocha/-/mocha-10.2.0.tgz", - "integrity": "sha512-IDY7fl/BecMwFHzoqF2sg/SHHANeBoMMXFlS9r0OXKDssYE1M5O43wUY/9BVPeIvfH2zmEbBfseqN9gBQZzXkg==", - "dev": true, - "requires": { - "ansi-colors": "4.1.1", - "browser-stdout": "1.3.1", - "chokidar": "3.5.3", - "debug": "4.3.4", - "diff": "5.0.0", - "escape-string-regexp": "4.0.0", - "find-up": "5.0.0", - "glob": "7.2.0", - "he": "1.2.0", - "js-yaml": "4.1.0", - "log-symbols": "4.1.0", - "minimatch": "5.0.1", - "ms": "2.1.3", - "nanoid": "3.3.3", - "serialize-javascript": "6.0.0", - "strip-json-comments": "3.1.1", - "supports-color": "8.1.1", - "workerpool": "6.2.1", - "yargs": "16.2.0", - "yargs-parser": "20.2.4", - "yargs-unparser": "2.0.0" - }, - "dependencies": { - "ansi-colors": { - "version": "4.1.1", - "resolved": "https://registry.npmjs.org/ansi-colors/-/ansi-colors-4.1.1.tgz", - "integrity": "sha512-JoX0apGbHaUJBNl6yF+p6JAFYZ666/hhCGKN5t9QFjbJQKUU/g8MNbFDbvfrgKXvI1QpZplPOnwIo99lX/AAmA==", - "dev": true - }, - "argparse": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/argparse/-/argparse-2.0.1.tgz", - "integrity": "sha512-8+9WqebbFzpX9OR+Wa6O29asIogeRMzcGtAINdpMHHyAg10f05aSFVBbcEqGf/PXw1EjAZ+q2/bEBg3DvurK3Q==", - "dev": true - }, - "brace-expansion": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-2.0.1.tgz", - "integrity": "sha512-XnAIvQ8eM+kC6aULx6wuQiwVsnzsi9d3WxzV3FpWTGA19F621kwdbsAcFKXgKUHZWsy+mY6iL1sHTxWEFCytDA==", - "dev": true, - "requires": { - "balanced-match": "^1.0.0" - } - }, - "find-up": { - "version": "5.0.0", - "resolved": "https://registry.npmjs.org/find-up/-/find-up-5.0.0.tgz", - "integrity": "sha512-78/PXT1wlLLDgTzDs7sjq9hzz0vXD+zn+7wypEe4fXQxCmdmqfGsEPQxmiCSQI3ajFV91bVSsvNtrJRiW6nGng==", - "dev": true, - "requires": { - "locate-path": "^6.0.0", - "path-exists": "^4.0.0" - } - }, - "js-yaml": { - "version": "4.1.0", - "resolved": "https://registry.npmjs.org/js-yaml/-/js-yaml-4.1.0.tgz", - "integrity": "sha512-wpxZs9NoxZaJESJGIZTyDEaYpl0FKSA+FB9aJiyemKhMwkxQg63h4T1KJgUGHpTqPDNRcmmYLugrRjJlBtWvRA==", - "dev": true, - "requires": { - "argparse": "^2.0.1" - } - }, - "locate-path": { - "version": "6.0.0", - "resolved": "https://registry.npmjs.org/locate-path/-/locate-path-6.0.0.tgz", - "integrity": "sha512-iPZK6eYjbxRu3uB4/WZ3EsEIMJFMqAoopl3R+zuq0UjcAm/MO6KCweDgPfP3elTztoKP3KtnVHxTn2NHBSDVUw==", - "dev": true, - "requires": { - "p-locate": "^5.0.0" - } - }, - "minimatch": { - "version": "5.0.1", - "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-5.0.1.tgz", - "integrity": "sha512-nLDxIFRyhDblz3qMuq+SoRZED4+miJ/G+tdDrjkkkRnjAsBexeGpgjLEQ0blJy7rHhR2b93rhQY4SvyWu9v03g==", - "dev": true, - "requires": { - "brace-expansion": "^2.0.1" - } - }, - "ms": { - "version": "2.1.3", - "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.3.tgz", - "integrity": "sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA==", - "dev": true - }, - "p-limit": { - "version": "3.1.0", - "resolved": "https://registry.npmjs.org/p-limit/-/p-limit-3.1.0.tgz", - "integrity": "sha512-TYOanM3wGwNGsZN2cVTYPArw454xnXj5qmWF1bEoAc4+cU/ol7GVh7odevjp1FNHduHc3KZMcFduxU5Xc6uJRQ==", - "dev": true, - "requires": { - "yocto-queue": "^0.1.0" - } - }, - "p-locate": { - "version": "5.0.0", - "resolved": "https://registry.npmjs.org/p-locate/-/p-locate-5.0.0.tgz", - "integrity": "sha512-LaNjtRWUBY++zB5nE/NwcaoMylSPk+S+ZHNB1TzdbMJMny6dynpAGt7X/tl/QYq3TIeE6nxHppbo2LGymrG5Pw==", - "dev": true, - "requires": { - "p-limit": "^3.0.2" - } - }, - "path-exists": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/path-exists/-/path-exists-4.0.0.tgz", - "integrity": "sha512-ak9Qy5Q7jYb2Wwcey5Fpvg2KoAc/ZIhLSLOSBmRmygPsGwkVVt0fZa0qrtMz+m6tJTAHfZQ8FnmB4MG4LWy7/w==", - "dev": true - }, - "supports-color": { - "version": "8.1.1", - "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-8.1.1.tgz", - "integrity": "sha512-MpUEN2OodtUzxvKQl72cUF7RQ5EiHsGvSsVG0ia9c5RbWGL2CI4C7EpPS8UTBIplnlzZiNuV56w+FuNxy3ty2Q==", - "dev": true, - "requires": { - "has-flag": "^4.0.0" - } - } - } - }, - "mock-fs": { - "version": "4.14.0", - "resolved": "https://registry.npmjs.org/mock-fs/-/mock-fs-4.14.0.tgz", - "integrity": "sha512-qYvlv/exQ4+svI3UOvPUpLDF0OMX5euvUH0Ny4N5QyRyhNdgAgUrVH3iUINSzEPLvx0kbo/Bp28GJKIqvE7URw==", - "dev": true - }, - "module-error": { - "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 - }, - "ms": { - "version": "2.1.2", - "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.2.tgz", - "integrity": "sha512-sGkPx+VjMtmA6MX27oA4FBFELFCZZ4S4XqeGOXCv68tT+jb3vk/RyaKWP0PTKyWtmLSM0b+adUTEvbs1PEaH2w==", - "dev": true - }, - "multibase": { - "version": "0.6.1", - "resolved": "https://registry.npmjs.org/multibase/-/multibase-0.6.1.tgz", - "integrity": "sha512-pFfAwyTjbbQgNc3G7D48JkJxWtoJoBMaR4xQUOuB8RnCgRqaYmWNFeJTTvrJ2w51bjLq2zTby6Rqj9TQ9elSUw==", - "dev": true, - "requires": { - "base-x": "^3.0.8", - "buffer": "^5.5.0" - }, - "dependencies": { - "buffer": { - "version": "5.7.1", - "resolved": "https://registry.npmjs.org/buffer/-/buffer-5.7.1.tgz", - "integrity": "sha512-EHcyIPBQ4BSGlvjB16k5KgAJ27CIsHY/2JBmCRReo48y9rQ3MaUzWX3KVlBa4U7MyX02HdVj0K7C3WaB3ju7FQ==", - "dev": true, - "requires": { - "base64-js": "^1.3.1", - "ieee754": "^1.1.13" - } - } - } - }, - "multicodec": { - "version": "0.5.7", - "resolved": "https://registry.npmjs.org/multicodec/-/multicodec-0.5.7.tgz", - "integrity": "sha512-PscoRxm3f+88fAtELwUnZxGDkduE2HD9Q6GHUOywQLjOGT/HAdhjLDYNZ1e7VR0s0TP0EwZ16LNUTFpoBGivOA==", - "dev": true, - "requires": { - "varint": "^5.0.0" - } - }, - "multihashes": { - "version": "0.4.21", - "resolved": "https://registry.npmjs.org/multihashes/-/multihashes-0.4.21.tgz", - "integrity": "sha512-uVSvmeCWf36pU2nB4/1kzYZjsXD9vofZKpgudqkceYY5g2aZZXJ5r9lxuzoRLl1OAp28XljXsEJ/X/85ZsKmKw==", - "dev": true, - "requires": { - "buffer": "^5.5.0", - "multibase": "^0.7.0", - "varint": "^5.0.0" - }, - "dependencies": { - "buffer": { - "version": "5.7.1", - "resolved": "https://registry.npmjs.org/buffer/-/buffer-5.7.1.tgz", - "integrity": "sha512-EHcyIPBQ4BSGlvjB16k5KgAJ27CIsHY/2JBmCRReo48y9rQ3MaUzWX3KVlBa4U7MyX02HdVj0K7C3WaB3ju7FQ==", - "dev": true, - "requires": { - "base64-js": "^1.3.1", - "ieee754": "^1.1.13" - } - }, - "multibase": { - "version": "0.7.0", - "resolved": "https://registry.npmjs.org/multibase/-/multibase-0.7.0.tgz", - "integrity": "sha512-TW8q03O0f6PNFTQDvh3xxH03c8CjGaaYrjkl9UQPG6rz53TQzzxJVCIWVjzcbN/Q5Y53Zd0IBQBMVktVgNx4Fg==", - "dev": true, - "requires": { - "base-x": "^3.0.8", - "buffer": "^5.5.0" - } - } - } - }, - "nano-json-stream-parser": { - "version": "0.1.2", - "resolved": "https://registry.npmjs.org/nano-json-stream-parser/-/nano-json-stream-parser-0.1.2.tgz", - "integrity": "sha512-9MqxMH/BSJC7dnLsEMPyfN5Dvoo49IsPFYMcHw3Bcfc2kN0lpHRBSzlMSVx4HGyJ7s9B31CyBTVehWJoQ8Ctew==", - "dev": true - }, - "nanoid": { - "version": "3.3.3", - "resolved": "https://registry.npmjs.org/nanoid/-/nanoid-3.3.3.tgz", - "integrity": "sha512-p1sjXuopFs0xg+fPASzQ28agW1oHD7xDsd9Xkf3T15H3c/cifrFHVwrh74PdoklAPi+i7MdRsE47vm2r6JoB+w==", - "dev": true - }, - "napi-macros": { - "version": "2.2.2", - "resolved": "https://registry.npmjs.org/napi-macros/-/napi-macros-2.2.2.tgz", - "integrity": "sha512-hmEVtAGYzVQpCKdbQea4skABsdXW4RUh5t5mJ2zzqowJS2OyXZTU1KhDVFhx+NlWZ4ap9mqR9TcDO3LTTttd+g==", - "dev": true - }, - "natural-compare": { - "version": "1.4.0", - "resolved": "https://registry.npmjs.org/natural-compare/-/natural-compare-1.4.0.tgz", - "integrity": "sha512-OWND8ei3VtNC9h7V60qff3SVobHr996CTwgxubgyQYEpg290h9J0buyECNNJexkFm5sOajh5G116RYA1c8ZMSw==", - "dev": true - }, - "negotiator": { - "version": "0.6.3", - "resolved": "https://registry.npmjs.org/negotiator/-/negotiator-0.6.3.tgz", - "integrity": "sha512-+EUsqGPLsM+j/zdChZjsnX51g4XrHFOIXwfnCVPGlQk/k5giakcKsuxCObBRu6DSm9opw/O6slWbJdghQM4bBg==", - "dev": true - }, - "neo-async": { - "version": "2.6.2", - "resolved": "https://registry.npmjs.org/neo-async/-/neo-async-2.6.2.tgz", - "integrity": "sha512-Yd3UES5mWCSqR+qNT93S3UoYUkqAZ9lLg8a7g9rimsWmYGK8cVToA4/sF3RrshdyV3sAGMXVUmpMYOw+dLpOuw==", - "dev": true - }, - "next-tick": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/next-tick/-/next-tick-1.1.0.tgz", - "integrity": "sha512-CXdUiJembsNjuToQvxayPZF9Vqht7hewsvy2sOWafLvi2awflj9mOC6bHIg50orX8IJvWKY9wYQ/zB2kogPslQ==", - "dev": true - }, - "nice-try": { - "version": "1.0.5", - "resolved": "https://registry.npmjs.org/nice-try/-/nice-try-1.0.5.tgz", - "integrity": "sha512-1nh45deeb5olNY7eX82BkPO7SSxR5SSYJiPTrTdFUVYwAl8CKMA5N9PjTYkHiRjisVcxcQ1HXdLhx2qxxJzLNQ==", - "dev": true - }, - "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==" - }, - "node-emoji": { - "version": "1.11.0", - "resolved": "https://registry.npmjs.org/node-emoji/-/node-emoji-1.11.0.tgz", - "integrity": "sha512-wo2DpQkQp7Sjm2A0cq+sN7EHKO6Sl0ctXeBdFZrL9T9+UywORbufTcTZxom8YqpLQt/FqNMUkOpkZrJVYSKD3A==", - "dev": true, - "requires": { - "lodash": "^4.17.21" - } - }, - "node-environment-flags": { - "version": "1.0.6", - "resolved": "https://registry.npmjs.org/node-environment-flags/-/node-environment-flags-1.0.6.tgz", - "integrity": "sha512-5Evy2epuL+6TM0lCQGpFIj6KwiEsGh1SrHUhTbNX+sLbBtjidPZFAnVK9y5yU1+h//RitLbRHTIMyxQPtxMdHw==", - "dev": true, - "requires": { - "object.getownpropertydescriptors": "^2.0.3", - "semver": "^5.7.0" - }, - "dependencies": { - "semver": { - "version": "5.7.2", - "resolved": "https://registry.npmjs.org/semver/-/semver-5.7.2.tgz", - "integrity": "sha512-cBznnQ9KjJqU67B52RMC65CMarK2600WFnbkcaiwWq3xy/5haFJlshgnpjovMVJ+Hff49d8GEn0b87C5pDQ10g==", - "dev": true - } - } - }, - "node-fetch": { - "version": "2.6.12", - "resolved": "https://registry.npmjs.org/node-fetch/-/node-fetch-2.6.12.tgz", - "integrity": "sha512-C/fGU2E8ToujUivIO0H+tpQ6HWo4eEmchoPIoXtxCrVghxdKq+QOHqEZW7tuP3KlV3bC8FRMO5nMCC7Zm1VP6g==", - "requires": { - "whatwg-url": "^5.0.0" - } - }, - "node-gyp-build": { - "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==" - }, - "nofilter": { - "version": "3.1.0", - "resolved": "https://registry.npmjs.org/nofilter/-/nofilter-3.1.0.tgz", - "integrity": "sha512-l2NNj07e9afPnhAhvgVrCD/oy2Ai1yfLpuo3EpiO1jFTsB4sFz6oIfAfSZyQzVpkZQ9xS8ZS5g1jCBgq4Hwo0g==", - "dev": true - }, - "nopt": { - "version": "3.0.6", - "resolved": "https://registry.npmjs.org/nopt/-/nopt-3.0.6.tgz", - "integrity": "sha512-4GUt3kSEYmk4ITxzB/b9vaIDfUVWN/Ml1Fwl11IlnIG2iaJ9O6WXZ9SrYM9NLI8OCBieN2Y8SWC2oJV0RQ7qYg==", - "dev": true, - "requires": { - "abbrev": "1" - } - }, - "normalize-package-data": { - "version": "2.5.0", - "resolved": "https://registry.npmjs.org/normalize-package-data/-/normalize-package-data-2.5.0.tgz", - "integrity": "sha512-/5CMN3T0R4XTj4DcGaexo+roZSdSFW/0AOOTROrjxzCG1wrWXEsGbRKevjlIL+ZDE4sZlJr5ED4YW0yqmkK+eA==", - "dev": true, - "requires": { - "hosted-git-info": "^2.1.4", - "resolve": "^1.10.0", - "semver": "2 || 3 || 4 || 5", - "validate-npm-package-license": "^3.0.1" - }, - "dependencies": { - "semver": { - "version": "5.7.2", - "resolved": "https://registry.npmjs.org/semver/-/semver-5.7.2.tgz", - "integrity": "sha512-cBznnQ9KjJqU67B52RMC65CMarK2600WFnbkcaiwWq3xy/5haFJlshgnpjovMVJ+Hff49d8GEn0b87C5pDQ10g==", - "dev": true - } - } - }, - "normalize-path": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/normalize-path/-/normalize-path-3.0.0.tgz", - "integrity": "sha512-6eZs5Ls3WtCisHWp9S2GUy8dqkpGi4BVSz3GaqiE6ezub0512ESztXUwUB6C6IKbQkY2Pnb/mD4WYojCRwcwLA==", - "dev": true - }, - "normalize-url": { - "version": "4.5.1", - "resolved": "https://registry.npmjs.org/normalize-url/-/normalize-url-4.5.1.tgz", - "integrity": "sha512-9UZCFRHQdNrfTpGg8+1INIg93B6zE0aXMVFkw1WFwvO4SlZywU6aLg5Of0Ap/PgcbSw4LNxvMWXMeugwMCX0AA==", - "dev": true - }, - "number-is-nan": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/number-is-nan/-/number-is-nan-1.0.1.tgz", - "integrity": "sha512-4jbtZXNAsfZbAHiiqjLPBiCl16dES1zI4Hpzzxw61Tk+loF+sBDBKx1ICKKKwIqQ7M0mFn1TmkN7euSncWgHiQ==", - "dev": true - }, - "number-to-bn": { - "version": "1.7.0", - "resolved": "https://registry.npmjs.org/number-to-bn/-/number-to-bn-1.7.0.tgz", - "integrity": "sha512-wsJ9gfSz1/s4ZsJN01lyonwuxA1tml6X1yBDnfpMglypcBRFZZkus26EdPSlqS5GJfYddVZa22p3VNb3z5m5Ig==", - "requires": { - "bn.js": "4.11.6", - "strip-hex-prefix": "1.0.0" - }, - "dependencies": { - "bn.js": { - "version": "4.11.6", - "resolved": "https://registry.npmjs.org/bn.js/-/bn.js-4.11.6.tgz", - "integrity": "sha512-XWwnNNFCuuSQ0m3r3C4LE3EiORltHd9M05pq6FOlVeiophzRbMo50Sbz1ehl8K3Z+jw9+vmgnXefY1hz8X+2wA==" - } - } - }, - "oauth-sign": { - "version": "0.9.0", - "resolved": "https://registry.npmjs.org/oauth-sign/-/oauth-sign-0.9.0.tgz", - "integrity": "sha512-fexhUFFPTGV8ybAtSIGbV6gOkSv8UtRbDBnAyLQw4QPKkgNlsH2ByPGtMUqdWkos6YCRmAqViwgZrJc/mRDzZQ==", - "dev": true - }, - "object-assign": { - "version": "4.1.1", - "resolved": "https://registry.npmjs.org/object-assign/-/object-assign-4.1.1.tgz", - "integrity": "sha512-rJgTQnkUnH1sFw8yT6VSU3zD3sWmu6sZhIseY8VX+GRu3P6F7Fu+JNDoXfklElbLJSnc3FUQHVe4cU5hj+BcUg==", - "dev": true - }, - "object-inspect": { - "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 - }, - "object-keys": { - "version": "1.1.1", - "resolved": "https://registry.npmjs.org/object-keys/-/object-keys-1.1.1.tgz", - "integrity": "sha512-NuAESUOUMrlIXOfHKzD6bpPu3tYt3xvjNdRIQ+FeT0lNb4K8WR70CaDxhuNguS2XG+GjkyMwOzsN5ZktImfhLA==", - "dev": true - }, - "object.assign": { - "version": "4.1.4", - "resolved": "https://registry.npmjs.org/object.assign/-/object.assign-4.1.4.tgz", - "integrity": "sha512-1mxKf0e58bvyjSCtKYY4sRe9itRk3PJpquJOjeIkz885CczcI4IvJJDLPS72oowuSh+pBxUFROpX+TU++hxhZQ==", - "dev": true, - "requires": { - "call-bind": "^1.0.2", - "define-properties": "^1.1.4", - "has-symbols": "^1.0.3", - "object-keys": "^1.1.1" - } - }, - "object.getownpropertydescriptors": { - "version": "2.1.6", - "resolved": "https://registry.npmjs.org/object.getownpropertydescriptors/-/object.getownpropertydescriptors-2.1.6.tgz", - "integrity": "sha512-lq+61g26E/BgHv0ZTFgRvi7NMEPuAxLkFU7rukXjc/AlwH4Am5xXVnIXy3un1bg/JPbXHrixRkK1itUzzPiIjQ==", - "dev": true, - "requires": { - "array.prototype.reduce": "^1.0.5", - "call-bind": "^1.0.2", - "define-properties": "^1.2.0", - "es-abstract": "^1.21.2", - "safe-array-concat": "^1.0.0" - } - }, - "object.values": { - "version": "1.1.6", - "resolved": "https://registry.npmjs.org/object.values/-/object.values-1.1.6.tgz", - "integrity": "sha512-FVVTkD1vENCsAcwNs9k6jea2uHC/X0+JcjG8YA60FN5CMaJmG95wT9jek/xX9nornqGRrBkKtzuAu2wuHpKqvw==", - "dev": true, - "requires": { - "call-bind": "^1.0.2", - "define-properties": "^1.1.4", - "es-abstract": "^1.20.4" - } - }, - "obliterator": { - "version": "2.0.4", - "resolved": "https://registry.npmjs.org/obliterator/-/obliterator-2.0.4.tgz", - "integrity": "sha512-lgHwxlxV1qIg1Eap7LgIeoBWIMFibOjbrYPIPJZcI1mmGAI2m3lNYpK12Y+GBdPQ0U1hRwSord7GIaawz962qQ==", - "dev": true - }, - "oboe": { - "version": "2.1.5", - "resolved": "https://registry.npmjs.org/oboe/-/oboe-2.1.5.tgz", - "integrity": "sha512-zRFWiF+FoicxEs3jNI/WYUrVEgA7DeET/InK0XQuudGHRg8iIob3cNPrJTKaz4004uaA9Pbe+Dwa8iluhjLZWA==", - "dev": true, - "requires": { - "http-https": "^1.0.0" - } - }, - "on-finished": { - "version": "2.4.1", - "resolved": "https://registry.npmjs.org/on-finished/-/on-finished-2.4.1.tgz", - "integrity": "sha512-oVlzkg3ENAhCk2zdv7IJwd/QUD4z2RxRwpkcGY8psCVcCYZNq4wYnVWALHM+brtuJjePWiYF/ClmuDr8Ch5+kg==", - "dev": true, - "requires": { - "ee-first": "1.1.1" - } - }, - "once": { - "version": "1.4.0", - "resolved": "https://registry.npmjs.org/once/-/once-1.4.0.tgz", - "integrity": "sha512-lNaJgI+2Q5URQBkccEKHTQOPaXdUxnZZElQTZY0MFUAuaEqe1E+Nyvgdz/aIyNi6Z9MzO5dv1H8n58/GELp3+w==", - "dev": true, - "requires": { - "wrappy": "1" - } - }, - "open": { - "version": "7.4.2", - "resolved": "https://registry.npmjs.org/open/-/open-7.4.2.tgz", - "integrity": "sha512-MVHddDVweXZF3awtlAS+6pgKLlm/JgxZ90+/NBurBoQctVOOB/zDdVjcyPzQ+0laDGbsWgrRkflI65sQeOgT9Q==", - "dev": true, - "requires": { - "is-docker": "^2.0.0", - "is-wsl": "^2.1.1" - } - }, - "optionator": { - "version": "0.9.3", - "resolved": "https://registry.npmjs.org/optionator/-/optionator-0.9.3.tgz", - "integrity": "sha512-JjCoypp+jKn1ttEFExxhetCKeJt9zhAgAve5FXHixTvFDW/5aEktX9bufBKLRRMdU7bNtpLfcGu94B3cdEJgjg==", - "dev": true, - "requires": { - "@aashutoshrathi/word-wrap": "^1.2.3", - "deep-is": "^0.1.3", - "fast-levenshtein": "^2.0.6", - "levn": "^0.4.1", - "prelude-ls": "^1.2.1", - "type-check": "^0.4.0" - } - }, - "os-locale": { - "version": "1.4.0", - "resolved": "https://registry.npmjs.org/os-locale/-/os-locale-1.4.0.tgz", - "integrity": "sha512-PRT7ZORmwu2MEFt4/fv3Q+mEfN4zetKxufQrkShY2oGvUms9r8otu5HfdyIFHkYXjO7laNsoVGmM2MANfuTA8g==", - "dev": true, - "requires": { - "lcid": "^1.0.0" - } - }, - "os-tmpdir": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/os-tmpdir/-/os-tmpdir-1.0.2.tgz", - "integrity": "sha512-D2FR03Vir7FIu45XBY20mTb+/ZSWB00sjU9jdQXt83gDrI4Ztz5Fs7/yy74g2N5SVQY4xY1qDr4rNddwYRVX0g==", - "dev": true - }, - "p-cancelable": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/p-cancelable/-/p-cancelable-1.1.0.tgz", - "integrity": "sha512-s73XxOZ4zpt1edZYZzvhqFa6uvQc1vwUa0K0BdtIZgQMAJj9IbebH+JkgKZc9h+B05PKHLOTl4ajG1BmNrVZlw==", - "dev": true - }, - "p-limit": { - "version": "1.3.0", - "resolved": "https://registry.npmjs.org/p-limit/-/p-limit-1.3.0.tgz", - "integrity": "sha512-vvcXsLAJ9Dr5rQOPk7toZQZJApBl2K4J6dANSsEuh6QI41JYcsS/qhTGa9ErIUUgK3WNQoJYvylxvjqmiqEA9Q==", - "dev": true, - "requires": { - "p-try": "^1.0.0" - } - }, - "p-locate": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/p-locate/-/p-locate-2.0.0.tgz", - "integrity": "sha512-nQja7m7gSKuewoVRen45CtVfODR3crN3goVQ0DDZ9N3yHxgpkuBhZqsaiotSQRrADUrne346peY7kT3TSACykg==", - "dev": true, - "requires": { - "p-limit": "^1.1.0" - } - }, - "p-map": { - "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, - "requires": { - "aggregate-error": "^3.0.0" - } - }, - "p-try": { - "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 - }, - "parent-module": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/parent-module/-/parent-module-1.0.1.tgz", - "integrity": "sha512-GQ2EWRpQV8/o+Aw8YqtfZZPfNRWZYkbidE9k5rpl/hC3vtHHBfGm2Ifi6qWV+coDGkrUKZAxE3Lot5kcsRlh+g==", - "dev": true, - "requires": { - "callsites": "^3.0.0" - } - }, - "parse-asn1": { - "version": "5.1.6", - "resolved": "https://registry.npmjs.org/parse-asn1/-/parse-asn1-5.1.6.tgz", - "integrity": "sha512-RnZRo1EPU6JBnra2vGHj0yhp6ebyjBZpmUCLHWiFhxlzvBCCpAuZ7elsBp1PVAbQN0/04VD/19rfzlBSwLstMw==", - "dev": true, - "requires": { - "asn1.js": "^5.2.0", - "browserify-aes": "^1.0.0", - "evp_bytestokey": "^1.0.0", - "pbkdf2": "^3.0.3", - "safe-buffer": "^5.1.1" - } - }, - "parse-cache-control": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/parse-cache-control/-/parse-cache-control-1.0.1.tgz", - "integrity": "sha512-60zvsJReQPX5/QP0Kzfd/VrpjScIQ7SHBW6bFCYfEP+fp0Eppr1SHhIO5nd1PjZtvclzSzES9D/p5nFJurwfWg==", - "dev": true - }, - "parse-headers": { - "version": "2.0.5", - "resolved": "https://registry.npmjs.org/parse-headers/-/parse-headers-2.0.5.tgz", - "integrity": "sha512-ft3iAoLOB/MlwbNXgzy43SWGP6sQki2jQvAyBg/zDFAgr9bfNWZIUj42Kw2eJIl8kEi4PbgE6U1Zau/HwI75HA==", - "dev": true - }, - "parse-json": { - "version": "5.2.0", - "resolved": "https://registry.npmjs.org/parse-json/-/parse-json-5.2.0.tgz", - "integrity": "sha512-ayCKvm/phCGxOkYRSCM82iDwct8/EonSEgCSxWxD7ve6jHggsFl4fZVQBPRNgQoKiuV/odhFrGzQXZwbifC8Rg==", - "dev": true, - "requires": { - "@babel/code-frame": "^7.0.0", - "error-ex": "^1.3.1", - "json-parse-even-better-errors": "^2.3.0", - "lines-and-columns": "^1.1.6" - } - }, - "parseurl": { - "version": "1.3.3", - "resolved": "https://registry.npmjs.org/parseurl/-/parseurl-1.3.3.tgz", - "integrity": "sha512-CiyeOxFT/JZyN5m0z9PfXw4SCBJ6Sygz1Dpl0wqjlhDEGGBP1GnsUVEL0p63hoG1fcj3fHynXi9NYO4nWOL+qQ==", - "dev": true - }, - "patch-package": { - "version": "6.5.1", - "resolved": "https://registry.npmjs.org/patch-package/-/patch-package-6.5.1.tgz", - "integrity": "sha512-I/4Zsalfhc6bphmJTlrLoOcAF87jcxko4q0qsv4bGcurbr8IskEOtdnt9iCmsQVGL1B+iUhSQqweyTLJfCF9rA==", - "dev": true, - "requires": { - "@yarnpkg/lockfile": "^1.1.0", - "chalk": "^4.1.2", - "cross-spawn": "^6.0.5", - "find-yarn-workspace-root": "^2.0.0", - "fs-extra": "^9.0.0", - "is-ci": "^2.0.0", - "klaw-sync": "^6.0.0", - "minimist": "^1.2.6", - "open": "^7.4.2", - "rimraf": "^2.6.3", - "semver": "^5.6.0", - "slash": "^2.0.0", - "tmp": "^0.0.33", - "yaml": "^1.10.2" - }, - "dependencies": { - "cross-spawn": { - "version": "6.0.5", - "resolved": "https://registry.npmjs.org/cross-spawn/-/cross-spawn-6.0.5.tgz", - "integrity": "sha512-eTVLrBSt7fjbDygz805pMnstIs2VTBNkRm0qxZd+M7A5XDdxVRWO5MxGBXZhjY4cqLYLdtrGqRf8mBPmzwSpWQ==", - "dev": true, - "requires": { - "nice-try": "^1.0.4", - "path-key": "^2.0.1", - "semver": "^5.5.0", - "shebang-command": "^1.2.0", - "which": "^1.2.9" - } - }, - "fs-extra": { - "version": "9.1.0", - "resolved": "https://registry.npmjs.org/fs-extra/-/fs-extra-9.1.0.tgz", - "integrity": "sha512-hcg3ZmepS30/7BSFqRvoo3DOMQu7IjqxO5nCDt+zM9XWjb33Wg7ziNT+Qvqbuc3+gWpzO02JubVyk2G4Zvo1OQ==", - "dev": true, - "requires": { - "at-least-node": "^1.0.0", - "graceful-fs": "^4.2.0", - "jsonfile": "^6.0.1", - "universalify": "^2.0.0" - } - }, - "jsonfile": { - "version": "6.1.0", - "resolved": "https://registry.npmjs.org/jsonfile/-/jsonfile-6.1.0.tgz", - "integrity": "sha512-5dgndWOriYSm5cnYaJNhalLNDKOqFwyDB/rr1E9ZsGciGvKPs8R2xYGCacuf3z6K1YKDz182fd+fY3cn3pMqXQ==", - "dev": true, - "requires": { - "graceful-fs": "^4.1.6", - "universalify": "^2.0.0" - } - }, - "path-key": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/path-key/-/path-key-2.0.1.tgz", - "integrity": "sha512-fEHGKCSmUSDPv4uoj8AlD+joPlq3peND+HRYyxFz4KPw4z926S/b8rIuFs2FYJg3BwsxJf6A9/3eIdLaYC+9Dw==", - "dev": true - }, - "rimraf": { - "version": "2.7.1", - "resolved": "https://registry.npmjs.org/rimraf/-/rimraf-2.7.1.tgz", - "integrity": "sha512-uWjbaKIK3T1OSVptzX7Nl6PvQ3qAGtKEtVRjRuazjfL3Bx5eI409VZSqgND+4UNnmzLVdPj9FqFJNPqBZFve4w==", - "dev": true, - "requires": { - "glob": "^7.1.3" - } - }, - "semver": { - "version": "5.7.2", - "resolved": "https://registry.npmjs.org/semver/-/semver-5.7.2.tgz", - "integrity": "sha512-cBznnQ9KjJqU67B52RMC65CMarK2600WFnbkcaiwWq3xy/5haFJlshgnpjovMVJ+Hff49d8GEn0b87C5pDQ10g==", - "dev": true - }, - "shebang-command": { - "version": "1.2.0", - "resolved": "https://registry.npmjs.org/shebang-command/-/shebang-command-1.2.0.tgz", - "integrity": "sha512-EV3L1+UQWGor21OmnvojK36mhg+TyIKDh3iFBKBohr5xeXIhNBcx8oWdgkTEEQ+BEFFYdLRuqMfd5L84N1V5Vg==", - "dev": true, - "requires": { - "shebang-regex": "^1.0.0" - } - }, - "shebang-regex": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/shebang-regex/-/shebang-regex-1.0.0.tgz", - "integrity": "sha512-wpoSFAxys6b2a2wHZ1XpDSgD7N9iVjg29Ph9uV/uaP9Ex/KXlkTZTeddxDPSYQpgvzKLGJke2UU0AzoGCjNIvQ==", - "dev": true - }, - "slash": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/slash/-/slash-2.0.0.tgz", - "integrity": "sha512-ZYKh3Wh2z1PpEXWr0MpSBZ0V6mZHAQfYevttO11c51CaWjGTaadiKZ+wVt1PbMlDV5qhMFslpZCemhwOK7C89A==", - "dev": true - }, - "universalify": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/universalify/-/universalify-2.0.0.tgz", - "integrity": "sha512-hAZsKq7Yy11Zu1DE0OzWjw7nnLZmJZYTDZZyEFHZdUhV8FkH5MCfoU1XMaxXovpyW5nq5scPqq0ZDP9Zyl04oQ==", - "dev": true - }, - "which": { - "version": "1.3.1", - "resolved": "https://registry.npmjs.org/which/-/which-1.3.1.tgz", - "integrity": "sha512-HxJdYWq1MTIQbJ3nw0cqssHoTNU267KlrDuGZ1WYlxDStUtKUhOaJmh112/TZmHxxUfuJqPXSOm7tDyas0OSIQ==", - "dev": true, - "requires": { - "isexe": "^2.0.0" - } - } - } - }, - "path-browserify": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/path-browserify/-/path-browserify-1.0.1.tgz", - "integrity": "sha512-b7uo2UCUOYZcnF/3ID0lulOJi/bafxa1xPe7ZPsammBSpjSWQkjNxlt635YGS2MiR9GjvuXCtz2emr3jbsz98g==", - "dev": true - }, - "path-exists": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/path-exists/-/path-exists-3.0.0.tgz", - "integrity": "sha512-bpC7GYwiDYQ4wYLe+FA8lhRjhQCMcQGuSgGGqDkg/QerRWw9CmGRT0iSOVRSZJ29NMLZgIzqaljJ63oaL4NIJQ==", - "dev": true - }, - "path-is-absolute": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/path-is-absolute/-/path-is-absolute-1.0.1.tgz", - "integrity": "sha512-AVbw3UJ2e9bq64vSaS9Am0fje1Pa8pbGqTTsmXfaIiMpnr5DlDhfJOuLj9Sf95ZPVDAUerDfEk88MPmPe7UCQg==", - "dev": true - }, - "path-key": { - "version": "3.1.1", - "resolved": "https://registry.npmjs.org/path-key/-/path-key-3.1.1.tgz", - "integrity": "sha512-ojmeN0qd+y0jszEtoY48r0Peq5dwMEkIlCOu6Q5f41lfkswXuKtYrhgoTpLnyIcHm24Uhqx+5Tqm2InSwLhE6Q==", - "dev": true - }, - "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==", - "dev": true - }, - "path-to-regexp": { - "version": "0.1.7", - "resolved": "https://registry.npmjs.org/path-to-regexp/-/path-to-regexp-0.1.7.tgz", - "integrity": "sha512-5DFkuoqlv1uYQKxy8omFBeJPQcdoE07Kv2sferDCrAq1ohOU+MSDswDIbnx3YAM60qIOnYa53wBhXW0EbMonrQ==", - "dev": true - }, - "path-type": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/path-type/-/path-type-4.0.0.tgz", - "integrity": "sha512-gDKb8aZMDeD/tZWs9P6+q0J9Mwkdl6xMV8TjnGP3qJVJ06bdMgkbBlLU8IdfOsIsFz2BW1rNVT3XuNEl8zPAvw==", - "dev": true - }, - "pathval": { - "version": "1.1.1", - "resolved": "https://registry.npmjs.org/pathval/-/pathval-1.1.1.tgz", - "integrity": "sha512-Dp6zGqpTdETdR63lehJYPeIOqpiNBNtc7BpWSLrOje7UaIsE5aY92r/AunQA7rsXvet3lrJ3JnZX29UPTKXyKQ==" - }, - "pbkdf2": { - "version": "3.1.2", - "resolved": "https://registry.npmjs.org/pbkdf2/-/pbkdf2-3.1.2.tgz", - "integrity": "sha512-iuh7L6jA7JEGu2WxDwtQP1ddOpaJNC4KlDEFfdQajSGgGPNi4OyDc2R7QnbY2bR9QjBVGwgvTdNJZoE7RaxUMA==", - "requires": { - "create-hash": "^1.1.2", - "create-hmac": "^1.1.4", - "ripemd160": "^2.0.1", - "safe-buffer": "^5.0.1", - "sha.js": "^2.4.8" - } - }, - "performance-now": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/performance-now/-/performance-now-2.1.0.tgz", - "integrity": "sha512-7EAHlyLHI56VEIdK57uwHdHKIaAGbnXPiw0yWbarQZOKaKpvUIgW0jWRVLiatnM+XXlSwsanIBH/hzGMJulMow==", - "dev": true - }, - "picomatch": { - "version": "2.3.1", - "resolved": "https://registry.npmjs.org/picomatch/-/picomatch-2.3.1.tgz", - "integrity": "sha512-JU3teHTNjmE2VCGFzuY8EXzCDVwEqB2a8fsIvwaStHhAWJEeVd1o1QD80CU6+ZdEXXSLbSsuLwJjkCBWqRQUVA==", - "dev": true - }, - "pify": { - "version": "4.0.1", - "resolved": "https://registry.npmjs.org/pify/-/pify-4.0.1.tgz", - "integrity": "sha512-uB80kBFb/tfd68bVleG9T5GGsGPjJrLAUpR5PZIrhBnIaRTQRjqdJSsIKkOP6OAIFbj7GOrcudc5pNjZ+geV2g==", - "dev": true - }, - "pinkie": { - "version": "2.0.4", - "resolved": "https://registry.npmjs.org/pinkie/-/pinkie-2.0.4.tgz", - "integrity": "sha512-MnUuEycAemtSaeFSjXKW/aroV7akBbY+Sv+RkyqFjgAe73F+MR0TBWKBRDkmfWq/HiFmdavfZ1G7h4SPZXaCSg==", - "dev": true - }, - "pinkie-promise": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/pinkie-promise/-/pinkie-promise-2.0.1.tgz", - "integrity": "sha512-0Gni6D4UcLTbv9c57DfxDGdr41XfgUjqWZu492f0cIGr16zDU06BWP/RAEvOuo7CQ0CNjHaLlM59YJJFm3NWlw==", - "dev": true, - "requires": { - "pinkie": "^2.0.0" - } - }, - "pluralize": { - "version": "8.0.0", - "resolved": "https://registry.npmjs.org/pluralize/-/pluralize-8.0.0.tgz", - "integrity": "sha512-Nc3IT5yHzflTfbjgqWcCPpo7DaKy4FnpB0l/zCAW0Tc7jxAiuqSxHasntB3D7887LSrA93kDJ9IXovxJYxyLCA==", - "dev": true - }, - "postinstall-postinstall": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/postinstall-postinstall/-/postinstall-postinstall-2.1.0.tgz", - "integrity": "sha512-7hQX6ZlZXIoRiWNrbMQaLzUUfH+sSx39u8EJ9HYuDc1kLo9IXKWjM5RSquZN1ad5GnH8CGFM78fsAAQi3OKEEQ==", - "dev": true - }, - "prelude-ls": { - "version": "1.2.1", - "resolved": "https://registry.npmjs.org/prelude-ls/-/prelude-ls-1.2.1.tgz", - "integrity": "sha512-vkcDPrRZo1QZLbn5RLGPpg/WmIQ65qoWWhcGKf/b5eplkkarX0m9z8ppCat4mlOqUsWpyNuYgO3VRyrYHSzX5g==", - "dev": true - }, - "prepend-http": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/prepend-http/-/prepend-http-2.0.0.tgz", - "integrity": "sha512-ravE6m9Atw9Z/jjttRUZ+clIXogdghyZAuWJ3qEzjT+jI/dL1ifAqhZeC5VHzQp1MSt1+jxKkFNemj/iO7tVUA==", - "dev": true - }, - "prettier": { - "version": "2.8.8", - "resolved": "https://registry.npmjs.org/prettier/-/prettier-2.8.8.tgz", - "integrity": "sha512-tdN8qQGvNjw4CHbY+XXk0JgCXn9QiF21a55rBe5LJAU+kDyC4WQn4+awm2Xfk2lQMk5fKup9XgzTZtGkjBdP9Q==", - "dev": true - }, - "prettier-linter-helpers": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/prettier-linter-helpers/-/prettier-linter-helpers-1.0.0.tgz", - "integrity": "sha512-GbK2cP9nraSSUF9N2XwUwqfzlAFlMNYYl+ShE/V+H8a9uNl/oUqB1w2EL54Jh0OlyRSd8RfWYJ3coVS4TROP2w==", - "dev": true, - "requires": { - "fast-diff": "^1.1.2" - } - }, - "prettier-plugin-solidity": { - "version": "1.1.3", - "resolved": "https://registry.npmjs.org/prettier-plugin-solidity/-/prettier-plugin-solidity-1.1.3.tgz", - "integrity": "sha512-fQ9yucPi2sBbA2U2Xjh6m4isUTJ7S7QLc/XDDsktqqxYfTwdYKJ0EnnywXHwCGAaYbQNK+HIYPL1OemxuMsgeg==", - "dev": true, - "requires": { - "@solidity-parser/parser": "^0.16.0", - "semver": "^7.3.8", - "solidity-comments-extractor": "^0.0.7" - }, - "dependencies": { - "@solidity-parser/parser": { - "version": "0.16.1", - "resolved": "https://registry.npmjs.org/@solidity-parser/parser/-/parser-0.16.1.tgz", - "integrity": "sha512-PdhRFNhbTtu3x8Axm0uYpqOy/lODYQK+MlYSgqIsq2L8SFYEHJPHNUiOTAJbDGzNjjr1/n9AcIayxafR/fWmYw==", - "dev": true, - "requires": { - "antlr4ts": "^0.5.0-alpha.4" - } - }, - "lru-cache": { - "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, - "requires": { - "yallist": "^4.0.0" - } - }, - "semver": { - "version": "7.5.4", - "resolved": "https://registry.npmjs.org/semver/-/semver-7.5.4.tgz", - "integrity": "sha512-1bCSESV6Pv+i21Hvpxp3Dx+pSD8lIPt8uVjRrxAUt/nbswYc+tK6Y2btiULjd4+fnq15PX+nqQDC7Oft7WkwcA==", - "dev": true, - "requires": { - "lru-cache": "^6.0.0" - } - }, - "yallist": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/yallist/-/yallist-4.0.0.tgz", - "integrity": "sha512-3wdGidZyq5PB084XLES5TpOSRA3wjXAlIWMhum2kRcv/41Sn2emQ0dycQW4uZXLejwKvg6EsvbdlVL+FYEct7A==", - "dev": true - } - } - }, - "process": { - "version": "0.11.10", - "resolved": "https://registry.npmjs.org/process/-/process-0.11.10.tgz", - "integrity": "sha512-cdGef/drWFoydD1JsMzuFf8100nZl+GT+yacc2bEced5f9Rjk4z+WtFUTBu9PhOi9j/jfmBPu0mMEY4wIdAF8A==", - "dev": true - }, - "process-nextick-args": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/process-nextick-args/-/process-nextick-args-2.0.1.tgz", - "integrity": "sha512-3ouUOpQhtgrbOa17J7+uxOTpITYWaGP7/AhoR3+A+/1e9skrzelGi/dXzEYyvbxubEF6Wn2ypscTKiKJFFn1ag==", - "dev": true - }, - "progress": { - "version": "2.0.3", - "resolved": "https://registry.npmjs.org/progress/-/progress-2.0.3.tgz", - "integrity": "sha512-7PiHtLll5LdnKIMw100I+8xJXR5gW2QwWYkT6iJva0bXitZKa/XMrSbdmg3r2Xnaidz9Qumd0VPaMrZlF9V9sA==", - "dev": true - }, - "promise": { - "version": "8.3.0", - "resolved": "https://registry.npmjs.org/promise/-/promise-8.3.0.tgz", - "integrity": "sha512-rZPNPKTOYVNEEKFaq1HqTgOwZD+4/YHS5ukLzQCypkj+OkYx7iv0mA91lJlpPPZ8vMau3IIGj5Qlwrx+8iiSmg==", - "dev": true, - "requires": { - "asap": "~2.0.6" - } - }, - "proxy-addr": { - "version": "2.0.7", - "resolved": "https://registry.npmjs.org/proxy-addr/-/proxy-addr-2.0.7.tgz", - "integrity": "sha512-llQsMLSUDUPT44jdrU/O37qlnifitDP+ZwrmmZcoSKyLKvtZxpyV0n2/bD/N4tBAAZ/gJEdZU7KMraoK1+XYAg==", - "dev": true, - "requires": { - "forwarded": "0.2.0", - "ipaddr.js": "1.9.1" - } - }, - "psl": { - "version": "1.9.0", - "resolved": "https://registry.npmjs.org/psl/-/psl-1.9.0.tgz", - "integrity": "sha512-E/ZsdU4HLs/68gYzgGTkMicWTLPdAftJLfJFlLUAAKZGkStNU72sZjT66SnMDVOfOWY/YAoiD7Jxa9iHvngcag==", - "dev": true - }, - "public-encrypt": { - "version": "4.0.3", - "resolved": "https://registry.npmjs.org/public-encrypt/-/public-encrypt-4.0.3.tgz", - "integrity": "sha512-zVpa8oKZSz5bTMTFClc1fQOnyyEzpl5ozpi1B5YcvBrdohMjH2rfsBtyXcuNuwjsDIXmBYlF2N5FlJYhR29t8Q==", - "dev": true, - "requires": { - "bn.js": "^4.1.0", - "browserify-rsa": "^4.0.0", - "create-hash": "^1.1.0", - "parse-asn1": "^5.0.0", - "randombytes": "^2.0.1", - "safe-buffer": "^5.1.2" - }, - "dependencies": { - "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==", - "dev": true - } - } - }, - "pump": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/pump/-/pump-3.0.0.tgz", - "integrity": "sha512-LwZy+p3SFs1Pytd/jYct4wpv49HiYCqd9Rlc5ZVdk0V+8Yzv6jR5Blk3TRmPL1ft69TxP0IMZGJ+WPFU2BFhww==", - "dev": true, - "requires": { - "end-of-stream": "^1.1.0", - "once": "^1.3.1" - } - }, - "punycode": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/punycode/-/punycode-2.1.0.tgz", - "integrity": "sha512-Yxz2kRwT90aPiWEMHVYnEf4+rhwF1tBmmZ4KepCP+Wkium9JxtWnUm1nqGwpiAHr/tnTSeHqr3wb++jgSkXjhA==", - "dev": true - }, - "qs": { - "version": "6.5.3", - "resolved": "https://registry.npmjs.org/qs/-/qs-6.5.3.tgz", - "integrity": "sha512-qxXIEh4pCGfHICj1mAJQ2/2XVZkjCDTcEgfoSQxc/fYivUZxTkk7L3bDBJSoNrEzXI17oUO5Dp07ktqE5KzczA==", - "dev": true - }, - "query-string": { - "version": "5.1.1", - "resolved": "https://registry.npmjs.org/query-string/-/query-string-5.1.1.tgz", - "integrity": "sha512-gjWOsm2SoGlgLEdAGt7a6slVOk9mGiXmPFMqrEhLQ68rhQuBnpfs3+EmlvqKyxnCo9/PPlF+9MtY02S1aFg+Jw==", - "dev": true, - "requires": { - "decode-uri-component": "^0.2.0", - "object-assign": "^4.1.0", - "strict-uri-encode": "^1.0.0" - } - }, - "queue-microtask": { - "version": "1.2.3", - "resolved": "https://registry.npmjs.org/queue-microtask/-/queue-microtask-1.2.3.tgz", - "integrity": "sha512-NuaNSa6flKT5JaSYQzJok04JzTL1CA6aGhv5rfLW3PgqA+M2ChpZQnAC8h8i4ZFkBS8X5RqkDBHA7r4hej3K9A==", - "dev": true - }, - "quick-lru": { - "version": "5.1.1", - "resolved": "https://registry.npmjs.org/quick-lru/-/quick-lru-5.1.1.tgz", - "integrity": "sha512-WuyALRjWPDGtt/wzJiadO5AXY+8hZ80hVpe6MyivgraREW751X3SbhRvG3eLKOYN+8VEvqLcf3wdnt44Z4S4SA==", - "dev": true - }, - "randombytes": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/randombytes/-/randombytes-2.1.0.tgz", - "integrity": "sha512-vYl3iOX+4CKUWuxGi9Ukhie6fsqXqS9FE2Zaic4tNFD2N2QQaXOMFbuKK4QmDHC0JO6B1Zp41J0LpT0oR68amQ==", - "requires": { - "safe-buffer": "^5.1.0" - } - }, - "randomfill": { - "version": "1.0.4", - "resolved": "https://registry.npmjs.org/randomfill/-/randomfill-1.0.4.tgz", - "integrity": "sha512-87lcbR8+MhcWcUiQ+9e+Rwx8MyR2P7qnt15ynUlbm3TU/fjbgz4GsvfSUDTemtCCtVCqb4ZcEFlyPNTh9bBTLw==", - "dev": true, - "requires": { - "randombytes": "^2.0.5", - "safe-buffer": "^5.1.0" - } - }, - "range-parser": { - "version": "1.2.1", - "resolved": "https://registry.npmjs.org/range-parser/-/range-parser-1.2.1.tgz", - "integrity": "sha512-Hrgsx+orqoygnmhFbKaHE6c296J+HTAQXoxEF6gNupROmmGJRoyzfG3ccAveqCBrwr/2yxQ5BVd/GTl5agOwSg==", - "dev": true - }, - "raw-body": { - "version": "2.5.2", - "resolved": "https://registry.npmjs.org/raw-body/-/raw-body-2.5.2.tgz", - "integrity": "sha512-8zGqypfENjCIqGhgXToC8aB2r7YrBX+AQAfIPs/Mlk+BtPTztOvTS01NRW/3Eh60J+a48lt8qsCzirQ6loCVfA==", - "dev": true, - "requires": { - "bytes": "3.1.2", - "http-errors": "2.0.0", - "iconv-lite": "0.4.24", - "unpipe": "1.0.0" - } - }, - "read-pkg": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/read-pkg/-/read-pkg-1.1.0.tgz", - "integrity": "sha512-7BGwRHqt4s/uVbuyoeejRn4YmFnYZiFl4AuaeXHlgZf3sONF0SOGlxs2Pw8g6hCKupo08RafIO5YXFNOKTfwsQ==", - "dev": true, - "requires": { - "load-json-file": "^1.0.0", - "normalize-package-data": "^2.3.2", - "path-type": "^1.0.0" - }, - "dependencies": { - "path-type": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/path-type/-/path-type-1.1.0.tgz", - "integrity": "sha512-S4eENJz1pkiQn9Znv33Q+deTOKmbl+jj1Fl+qiP/vYezj+S8x+J3Uo0ISrx/QoEvIlOaDWJhPaRd1flJ9HXZqg==", - "dev": true, - "requires": { - "graceful-fs": "^4.1.2", - "pify": "^2.0.0", - "pinkie-promise": "^2.0.0" - } - }, - "pify": { - "version": "2.3.0", - "resolved": "https://registry.npmjs.org/pify/-/pify-2.3.0.tgz", - "integrity": "sha512-udgsAY+fTnvv7kI7aaxbqwWNb0AHiB0qBO89PZKPkoTmGOgdbrHDKD+0B2X4uTfJ/FT1R09r9gTsjUjNJotuog==", - "dev": true - } - } - }, - "read-pkg-up": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/read-pkg-up/-/read-pkg-up-1.0.1.tgz", - "integrity": "sha512-WD9MTlNtI55IwYUS27iHh9tK3YoIVhxis8yKhLpTqWtml739uXc9NWTpxoHkfZf3+DkCCsXox94/VWZniuZm6A==", - "dev": true, - "requires": { - "find-up": "^1.0.0", - "read-pkg": "^1.0.0" - }, - "dependencies": { - "find-up": { - "version": "1.1.2", - "resolved": "https://registry.npmjs.org/find-up/-/find-up-1.1.2.tgz", - "integrity": "sha512-jvElSjyuo4EMQGoTwo1uJU5pQMwTW5lS1x05zzfJuTIyLR3zwO27LYrxNg+dlvKpGOuGy/MzBdXh80g0ve5+HA==", - "dev": true, - "requires": { - "path-exists": "^2.0.0", - "pinkie-promise": "^2.0.0" - } - }, - "path-exists": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/path-exists/-/path-exists-2.1.0.tgz", - "integrity": "sha512-yTltuKuhtNeFJKa1PiRzfLAU5182q1y4Eb4XCJ3PBqyzEDkAZRzBrKKBct682ls9reBVHf9udYLN5Nd+K1B9BQ==", - "dev": true, - "requires": { - "pinkie-promise": "^2.0.0" - } - } - } - }, - "readable-stream": { - "version": "3.6.2", - "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-3.6.2.tgz", - "integrity": "sha512-9u/sniCrY3D5WdsERHzHE4G2YCXqoG5FTHUiCC4SIbr6XcLZBY05ya9EKjYek9O5xOAwjGq+1JdGBAS7Q9ScoA==", - "requires": { - "inherits": "^2.0.3", - "string_decoder": "^1.1.1", - "util-deprecate": "^1.0.1" - } - }, - "readdirp": { - "version": "3.6.0", - "resolved": "https://registry.npmjs.org/readdirp/-/readdirp-3.6.0.tgz", - "integrity": "sha512-hOS089on8RduqdbhvQ5Z37A0ESjsqz6qnRcffsMU3495FuTdqSm+7bhJ29JvIOsBDEEnan5DPu9t3To9VRlMzA==", - "dev": true, - "requires": { - "picomatch": "^2.2.1" - } - }, - "rechoir": { - "version": "0.6.2", - "resolved": "https://registry.npmjs.org/rechoir/-/rechoir-0.6.2.tgz", - "integrity": "sha512-HFM8rkZ+i3zrV+4LQjwQ0W+ez98pApMGM3HUrN04j3CqzPOzl9nmP15Y8YXNm8QHGv/eacOVEjqhmWpkRV0NAw==", - "dev": true, - "requires": { - "resolve": "^1.1.6" - } - }, - "recursive-readdir": { - "version": "2.2.3", - "resolved": "https://registry.npmjs.org/recursive-readdir/-/recursive-readdir-2.2.3.tgz", - "integrity": "sha512-8HrF5ZsXk5FAH9dgsx3BlUer73nIhuj+9OrQwEbLTPOBzGkL1lsFCR01am+v+0m2Cmbs1nP12hLDl5FA7EszKA==", - "dev": true, - "requires": { - "minimatch": "^3.0.5" - } - }, - "regexp.prototype.flags": { - "version": "1.5.0", - "resolved": "https://registry.npmjs.org/regexp.prototype.flags/-/regexp.prototype.flags-1.5.0.tgz", - "integrity": "sha512-0SutC3pNudRKgquxGoRGIz946MZVHqbNfPjBdxeOhBrdgDKlRoXmYLQN9xRbrR09ZXWeGAdPuif7egofn6v5LA==", - "dev": true, - "requires": { - "call-bind": "^1.0.2", - "define-properties": "^1.2.0", - "functions-have-names": "^1.2.3" - } - }, - "regexpp": { - "version": "3.2.0", - "resolved": "https://registry.npmjs.org/regexpp/-/regexpp-3.2.0.tgz", - "integrity": "sha512-pq2bWo9mVD43nbts2wGv17XLiNLya+GklZ8kaDLV2Z08gDCsGpnKn9BFMepvWuHCbyVvY7J5o5+BVvoQbmlJLg==", - "dev": true - }, - "req-cwd": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/req-cwd/-/req-cwd-2.0.0.tgz", - "integrity": "sha512-ueoIoLo1OfB6b05COxAA9UpeoscNpYyM+BqYlA7H6LVF4hKGPXQQSSaD2YmvDVJMkk4UDpAHIeU1zG53IqjvlQ==", - "dev": true, - "requires": { - "req-from": "^2.0.0" - } - }, - "req-from": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/req-from/-/req-from-2.0.0.tgz", - "integrity": "sha512-LzTfEVDVQHBRfjOUMgNBA+V6DWsSnoeKzf42J7l0xa/B4jyPOuuF5MlNSmomLNGemWTnV2TIdjSSLnEn95fOQA==", - "dev": true, - "requires": { - "resolve-from": "^3.0.0" - }, - "dependencies": { - "resolve-from": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/resolve-from/-/resolve-from-3.0.0.tgz", - "integrity": "sha512-GnlH6vxLymXJNMBo7XP1fJIzBFbdYt49CuTwmB/6N53t+kMPRMFKz783LlQ4tv28XoQfMWinAJX6WCGf2IlaIw==", - "dev": true - } - } - }, - "request": { - "version": "2.88.2", - "resolved": "https://registry.npmjs.org/request/-/request-2.88.2.tgz", - "integrity": "sha512-MsvtOrfG9ZcrOwAW+Qi+F6HbD0CWXEh9ou77uOb7FM2WPhwT7smM833PzanhJLsgXjN89Ir6V2PczXNnMpwKhw==", - "dev": true, - "requires": { - "aws-sign2": "~0.7.0", - "aws4": "^1.8.0", - "caseless": "~0.12.0", - "combined-stream": "~1.0.6", - "extend": "~3.0.2", - "forever-agent": "~0.6.1", - "form-data": "~2.3.2", - "har-validator": "~5.1.3", - "http-signature": "~1.2.0", - "is-typedarray": "~1.0.0", - "isstream": "~0.1.2", - "json-stringify-safe": "~5.0.1", - "mime-types": "~2.1.19", - "oauth-sign": "~0.9.0", - "performance-now": "^2.1.0", - "qs": "~6.5.2", - "safe-buffer": "^5.1.2", - "tough-cookie": "~2.5.0", - "tunnel-agent": "^0.6.0", - "uuid": "^3.3.2" - }, - "dependencies": { - "form-data": { - "version": "2.3.3", - "resolved": "https://registry.npmjs.org/form-data/-/form-data-2.3.3.tgz", - "integrity": "sha512-1lLKB2Mu3aGP1Q/2eCOx0fNbRMe7XdwktwOruhfqqd0rIJWwN4Dh+E3hrPSlDCXnSR7UtZ1N38rVXm+6+MEhJQ==", - "dev": true, - "requires": { - "asynckit": "^0.4.0", - "combined-stream": "^1.0.6", - "mime-types": "^2.1.12" - } - }, - "uuid": { - "version": "3.4.0", - "resolved": "https://registry.npmjs.org/uuid/-/uuid-3.4.0.tgz", - "integrity": "sha512-HjSDRw6gZE5JMggctHBcjVak08+KEVhSIiDzFnT9S9aegmp85S/bReBVTb4QTFaRNptJ9kuYaNhnbNEOkbKb/A==", - "dev": true - } - } - }, - "request-promise-core": { - "version": "1.1.4", - "resolved": "https://registry.npmjs.org/request-promise-core/-/request-promise-core-1.1.4.tgz", - "integrity": "sha512-TTbAfBBRdWD7aNNOoVOBH4pN/KigV6LyapYNNlAPA8JwbovRti1E88m3sYAwsLi5ryhPKsE9APwnjFTgdUjTpw==", - "dev": true, - "requires": { - "lodash": "^4.17.19" - } - }, - "request-promise-native": { - "version": "1.0.9", - "resolved": "https://registry.npmjs.org/request-promise-native/-/request-promise-native-1.0.9.tgz", - "integrity": "sha512-wcW+sIUiWnKgNY0dqCpOZkUbF/I+YPi+f09JZIDa39Ec+q82CpSYniDp+ISgTTbKmnpJWASeJBPZmoxH84wt3g==", - "dev": true, - "requires": { - "request-promise-core": "1.1.4", - "stealthy-require": "^1.1.1", - "tough-cookie": "^2.3.3" - } - }, - "require-directory": { - "version": "2.1.1", - "resolved": "https://registry.npmjs.org/require-directory/-/require-directory-2.1.1.tgz", - "integrity": "sha512-fGxEI7+wsG9xrvdjsrlmL22OMTTiHRwAMroiEeMgq8gzoLC/PQr7RsRDSTLUg/bZAZtF+TVIkHc6/4RIKrui+Q==", - "dev": true - }, - "require-from-string": { - "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 - }, - "require-main-filename": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/require-main-filename/-/require-main-filename-1.0.1.tgz", - "integrity": "sha512-IqSUtOVP4ksd1C/ej5zeEh/BIP2ajqpn8c5x+q99gvcIG/Qf0cud5raVnE/Dwd0ua9TXYDoDc0RE5hBSdz22Ug==", - "dev": true - }, - "resolve": { - "version": "1.22.2", - "resolved": "https://registry.npmjs.org/resolve/-/resolve-1.22.2.tgz", - "integrity": "sha512-Sb+mjNHOULsBv818T40qSPeRiuWLyaGMa5ewydRLFimneixmVy2zdivRl+AF6jaYPC8ERxGDmFSiqui6SfPd+g==", - "dev": true, - "requires": { - "is-core-module": "^2.11.0", - "path-parse": "^1.0.7", - "supports-preserve-symlinks-flag": "^1.0.0" - } - }, - "resolve-alpn": { - "version": "1.2.1", - "resolved": "https://registry.npmjs.org/resolve-alpn/-/resolve-alpn-1.2.1.tgz", - "integrity": "sha512-0a1F4l73/ZFZOakJnQ3FvkJ2+gSTQWz/r2KE5OdDY0TxPm5h4GkqkWWfM47T7HsbnOtcJVEF4epCVy6u7Q3K+g==", - "dev": true - }, - "resolve-from": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/resolve-from/-/resolve-from-4.0.0.tgz", - "integrity": "sha512-pb/MYmXstAkysRFx8piNI1tGFNQIFA3vkE3Gq4EuA1dF6gHp/+vgZqsCGJapvy8N3Q+4o7FwvquPJcnZ7RYy4g==", - "dev": true - }, - "responselike": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/responselike/-/responselike-1.0.2.tgz", - "integrity": "sha512-/Fpe5guzJk1gPqdJLJR5u7eG/gNY4nImjbRDaVWVMRhne55TCmj2i9Q+54PBRfatRC8v/rIiv9BN0pMd9OV5EQ==", - "dev": true, - "requires": { - "lowercase-keys": "^1.0.0" - } - }, - "reusify": { - "version": "1.0.4", - "resolved": "https://registry.npmjs.org/reusify/-/reusify-1.0.4.tgz", - "integrity": "sha512-U9nH88a3fc/ekCF1l0/UP1IosiuIjyTh7hBvXVMHYgVcfGvt897Xguj2UOLDeI5BG2m7/uwyaLVT6fbtCwTyzw==", - "dev": true - }, - "rimraf": { - "version": "3.0.2", - "resolved": "https://registry.npmjs.org/rimraf/-/rimraf-3.0.2.tgz", - "integrity": "sha512-JZkJMZkAGFFPP2YqXZXPbMlMBgsxzE8ILs4lMIX/2o0L9UBw9O/Y3o6wFw/i9YLapcUJWwqbi3kdxIPdC62TIA==", - "dev": true, - "requires": { - "glob": "^7.1.3" - } - }, - "ripemd160": { - "version": "2.0.2", - "resolved": "https://registry.npmjs.org/ripemd160/-/ripemd160-2.0.2.tgz", - "integrity": "sha512-ii4iagi25WusVoiC4B4lq7pbXfAp3D9v5CwfkY33vffw2+pkDjY1D8GaN7spsxvCSx8dkPqOZCEZyfxcmJG2IA==", - "requires": { - "hash-base": "^3.0.0", - "inherits": "^2.0.1" - } - }, - "rlp": { - "version": "2.2.7", - "resolved": "https://registry.npmjs.org/rlp/-/rlp-2.2.7.tgz", - "integrity": "sha512-d5gdPmgQ0Z+AklL2NVXr/IoSjNZFfTVvQWzL/AM2AOcSzYP2xjlb0AC8YyCLc41MSNf6P6QVtjgPdmVtzb+4lQ==", - "requires": { - "bn.js": "^5.2.0" - } - }, - "run-parallel": { - "version": "1.2.0", - "resolved": "https://registry.npmjs.org/run-parallel/-/run-parallel-1.2.0.tgz", - "integrity": "sha512-5l4VyZR86LZ/lDxZTR6jqL8AFE2S0IFLMP26AbjsLVADxHdhB/c0GUsH+y39UfCi3dzz8OlQuPmnaJOMoDHQBA==", - "dev": true, - "requires": { - "queue-microtask": "^1.2.2" - } - }, - "run-parallel-limit": { - "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, - "requires": { - "queue-microtask": "^1.2.2" - } - }, - "rustbn.js": { - "version": "0.2.0", - "resolved": "https://registry.npmjs.org/rustbn.js/-/rustbn.js-0.2.0.tgz", - "integrity": "sha512-4VlvkRUuCJvr2J6Y0ImW7NvTCriMi7ErOAqWk1y69vAdoNIzCF3yPmgeNzx+RQTLEDFq5sHfscn1MwHxP9hNfA==", - "dev": true - }, - "safe-array-concat": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/safe-array-concat/-/safe-array-concat-1.0.0.tgz", - "integrity": "sha512-9dVEFruWIsnie89yym+xWTAYASdpw3CJV7Li/6zBewGf9z2i1j31rP6jnY0pHEO4QZh6N0K11bFjWmdR8UGdPQ==", - "dev": true, - "requires": { - "call-bind": "^1.0.2", - "get-intrinsic": "^1.2.0", - "has-symbols": "^1.0.3", - "isarray": "^2.0.5" - } - }, - "safe-buffer": { - "version": "5.1.2", - "resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.1.2.tgz", - "integrity": "sha512-Gd2UZBJDkXlY7GbJxfsE8/nvKkUEU1G38c1siN6QP6a9PT9MmHB8GnpscSmMJSoF8LOIrt8ud/wPtojys4G6+g==" - }, - "safe-regex-test": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/safe-regex-test/-/safe-regex-test-1.0.0.tgz", - "integrity": "sha512-JBUUzyOgEwXQY1NuPtvcj/qcBDbDmEvWufhlnXZIm75DEHp+afM1r1ujJpJsV/gSM4t59tpDyPi1sd6ZaPFfsA==", - "dev": true, - "requires": { - "call-bind": "^1.0.2", - "get-intrinsic": "^1.1.3", - "is-regex": "^1.1.4" - } - }, - "safer-buffer": { - "version": "2.1.2", - "resolved": "https://registry.npmjs.org/safer-buffer/-/safer-buffer-2.1.2.tgz", - "integrity": "sha512-YZo3K82SD7Riyi0E1EQPojLz7kpepnSQI9IyPbHHg1XXXevb5dJI7tpyN2ADxGcQbHG7vcyRHk0cbwqcQriUtg==", - "dev": true - }, - "sc-istanbul": { - "version": "0.4.6", - "resolved": "https://registry.npmjs.org/sc-istanbul/-/sc-istanbul-0.4.6.tgz", - "integrity": "sha512-qJFF/8tW/zJsbyfh/iT/ZM5QNHE3CXxtLJbZsL+CzdJLBsPD7SedJZoUA4d8iAcN2IoMp/Dx80shOOd2x96X/g==", - "dev": true, - "requires": { - "abbrev": "1.0.x", - "async": "1.x", - "escodegen": "1.8.x", - "esprima": "2.7.x", - "glob": "^5.0.15", - "handlebars": "^4.0.1", - "js-yaml": "3.x", - "mkdirp": "0.5.x", - "nopt": "3.x", - "once": "1.x", - "resolve": "1.1.x", - "supports-color": "^3.1.0", - "which": "^1.1.1", - "wordwrap": "^1.0.0" - }, - "dependencies": { - "esprima": { - "version": "2.7.3", - "resolved": "https://registry.npmjs.org/esprima/-/esprima-2.7.3.tgz", - "integrity": "sha512-OarPfz0lFCiW4/AV2Oy1Rp9qu0iusTKqykwTspGCZtPxmF81JR4MmIebvF1F9+UOKth2ZubLQ4XGGaU+hSn99A==", - "dev": true - }, - "glob": { - "version": "5.0.15", - "resolved": "https://registry.npmjs.org/glob/-/glob-5.0.15.tgz", - "integrity": "sha512-c9IPMazfRITpmAAKi22dK1VKxGDX9ehhqfABDriL/lzO92xcUKEJPQHrVA/2YHSNFB4iFlykVmWvwo48nr3OxA==", - "dev": true, - "requires": { - "inflight": "^1.0.4", - "inherits": "2", - "minimatch": "2 || 3", - "once": "^1.3.0", - "path-is-absolute": "^1.0.0" - } - }, - "has-flag": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-1.0.0.tgz", - "integrity": "sha512-DyYHfIYwAJmjAjSSPKANxI8bFY9YtFrgkAfinBojQ8YJTOuOuav64tMUJv584SES4xl74PmuaevIyaLESHdTAA==", - "dev": true - }, - "resolve": { - "version": "1.1.7", - "resolved": "https://registry.npmjs.org/resolve/-/resolve-1.1.7.tgz", - "integrity": "sha512-9znBF0vBcaSN3W2j7wKvdERPwqTxSpCq+if5C0WoTCyV9n24rua28jeuQ2pL/HOf+yUe/Mef+H/5p60K0Id3bg==", - "dev": true - }, - "supports-color": { - "version": "3.2.3", - "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-3.2.3.tgz", - "integrity": "sha512-Jds2VIYDrlp5ui7t8abHN2bjAu4LV/q4N2KivFPpGH0lrka0BMq/33AmECUXlKPcHigkNaqfXRENFju+rlcy+A==", - "dev": true, - "requires": { - "has-flag": "^1.0.0" - } - }, - "which": { - "version": "1.3.1", - "resolved": "https://registry.npmjs.org/which/-/which-1.3.1.tgz", - "integrity": "sha512-HxJdYWq1MTIQbJ3nw0cqssHoTNU267KlrDuGZ1WYlxDStUtKUhOaJmh112/TZmHxxUfuJqPXSOm7tDyas0OSIQ==", - "dev": true, - "requires": { - "isexe": "^2.0.0" - } - } - } - }, - "scrypt-js": { - "version": "3.0.1", - "resolved": "https://registry.npmjs.org/scrypt-js/-/scrypt-js-3.0.1.tgz", - "integrity": "sha512-cdwTTnqPu0Hyvf5in5asVdZocVDTNRmR7XEcJuIzMjJeSHybHl7vpB66AzwTaIg6CLSbtjcxc8fqcySfnTkccA==" - }, - "secp256k1": { - "version": "4.0.3", - "resolved": "https://registry.npmjs.org/secp256k1/-/secp256k1-4.0.3.tgz", - "integrity": "sha512-NLZVf+ROMxwtEj3Xa562qgv2BK5e2WNmXPiOdVIPLgs6lyTzMvBq0aWTYMI5XCP9jZMVKOcqZLw/Wc4vDkuxhA==", - "requires": { - "elliptic": "^6.5.4", - "node-addon-api": "^2.0.0", - "node-gyp-build": "^4.2.0" - } - }, - "semver": { - "version": "6.3.1", - "resolved": "https://registry.npmjs.org/semver/-/semver-6.3.1.tgz", - "integrity": "sha512-BR7VvDCVHO+q2xBEWskxS6DJE1qRnb7DxzUrogb71CWoSficBxYsiAGd+Kl0mmq/MprG9yArRkyrQxTO6XjMzA==", - "dev": true - }, - "send": { - "version": "0.18.0", - "resolved": "https://registry.npmjs.org/send/-/send-0.18.0.tgz", - "integrity": "sha512-qqWzuOjSFOuqPjFe4NOsMLafToQQwBSOEpS+FwEt3A2V3vKubTquT3vmLTQpFgMXp8AlFWFuP1qKaJZOtPpVXg==", - "dev": true, - "requires": { - "debug": "2.6.9", - "depd": "2.0.0", - "destroy": "1.2.0", - "encodeurl": "~1.0.2", - "escape-html": "~1.0.3", - "etag": "~1.8.1", - "fresh": "0.5.2", - "http-errors": "2.0.0", - "mime": "1.6.0", - "ms": "2.1.3", - "on-finished": "2.4.1", - "range-parser": "~1.2.1", - "statuses": "2.0.1" - }, - "dependencies": { - "debug": { - "version": "2.6.9", - "resolved": "https://registry.npmjs.org/debug/-/debug-2.6.9.tgz", - "integrity": "sha512-bC7ElrdJaJnPbAP+1EotYvqZsb3ecl5wi6Bfi6BJTUcNowp6cvspg0jXznRTKDjm/E7AdgFBVeAPVMNcKGsHMA==", - "dev": true, - "requires": { - "ms": "2.0.0" - }, - "dependencies": { - "ms": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/ms/-/ms-2.0.0.tgz", - "integrity": "sha512-Tpp60P6IUJDTuOq/5Z8cdskzJujfwqfOTkrwIwj7IRISpnkJnT6SyJ4PCPnGMoFjC9ddhal5KVIYtAt97ix05A==", - "dev": true - } - } - }, - "ms": { - "version": "2.1.3", - "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.3.tgz", - "integrity": "sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA==", - "dev": true - } - } - }, - "serialize-javascript": { - "version": "6.0.0", - "resolved": "https://registry.npmjs.org/serialize-javascript/-/serialize-javascript-6.0.0.tgz", - "integrity": "sha512-Qr3TosvguFt8ePWqsvRfrKyQXIiW+nGbYpy8XK24NQHE83caxWt+mIymTT19DGFbNWNLfEwsrkSmN64lVWB9ag==", - "dev": true, - "requires": { - "randombytes": "^2.1.0" - } - }, - "serve-static": { - "version": "1.15.0", - "resolved": "https://registry.npmjs.org/serve-static/-/serve-static-1.15.0.tgz", - "integrity": "sha512-XGuRDNjXUijsUL0vl6nSD7cwURuzEgglbOaFuZM9g3kwDXOWVTck0jLzjPzGD+TazWbboZYu52/9/XPdUgne9g==", - "dev": true, - "requires": { - "encodeurl": "~1.0.2", - "escape-html": "~1.0.3", - "parseurl": "~1.3.3", - "send": "0.18.0" - } - }, - "servify": { - "version": "0.1.12", - "resolved": "https://registry.npmjs.org/servify/-/servify-0.1.12.tgz", - "integrity": "sha512-/xE6GvsKKqyo1BAY+KxOWXcLpPsUUyji7Qg3bVD7hh1eRze5bR1uYiuDA/k3Gof1s9BTzQZEJK8sNcNGFIzeWw==", - "dev": true, - "requires": { - "body-parser": "^1.16.0", - "cors": "^2.8.1", - "express": "^4.14.0", - "request": "^2.79.0", - "xhr": "^2.3.3" - } - }, - "set-blocking": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/set-blocking/-/set-blocking-2.0.0.tgz", - "integrity": "sha512-KiKBS8AnWGEyLzofFfmvKwpdPzqiy16LvQfK3yv/fVH7Bj13/wl3JSR1J+rfgRE9q7xUJK4qvgS8raSOeLUehw==", - "dev": true - }, - "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==" - }, - "setprototypeof": { - "version": "1.2.0", - "resolved": "https://registry.npmjs.org/setprototypeof/-/setprototypeof-1.2.0.tgz", - "integrity": "sha512-E5LDX7Wrp85Kil5bhZv46j8jOeboKq5JMmYM3gVGdGH8xFpPWXUMsNrlODCrkoxMEeNi/XZIwuRvY4XNwYMJpw==", - "dev": true - }, - "sha.js": { - "version": "2.4.11", - "resolved": "https://registry.npmjs.org/sha.js/-/sha.js-2.4.11.tgz", - "integrity": "sha512-QMEp5B7cftE7APOjk5Y6xgrbWu+WkLVQwk8JNjZ8nKRciZaByEW6MubieAiToS7+dwvrjGhH8jRXz3MVd0AYqQ==", - "requires": { - "inherits": "^2.0.1", - "safe-buffer": "^5.0.1" - } - }, - "sha1": { - "version": "1.1.1", - "resolved": "https://registry.npmjs.org/sha1/-/sha1-1.1.1.tgz", - "integrity": "sha512-dZBS6OrMjtgVkopB1Gmo4RQCDKiZsqcpAQpkV/aaj+FCrCg8r4I4qMkDPQjBgLIxlmu9k4nUbWq6ohXahOneYA==", - "dev": true, - "requires": { - "charenc": ">= 0.0.1", - "crypt": ">= 0.0.1" - } - }, - "shebang-command": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/shebang-command/-/shebang-command-2.0.0.tgz", - "integrity": "sha512-kHxr2zZpYtdmrN1qDjrrX/Z1rR1kG8Dx+gkpK1G4eXmvXswmcE1hTWBWYUzlraYw1/yZp6YuDY77YtvbN0dmDA==", - "dev": true, - "requires": { - "shebang-regex": "^3.0.0" - } - }, - "shebang-regex": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/shebang-regex/-/shebang-regex-3.0.0.tgz", - "integrity": "sha512-7++dFhtcx3353uBaq8DDR4NuxBetBzC7ZQOhmTQInHEd6bSrXdiEyzCvG07Z44UYdLShWUyXt5M/yhz8ekcb1A==", - "dev": true - }, - "shelljs": { - "version": "0.8.5", - "resolved": "https://registry.npmjs.org/shelljs/-/shelljs-0.8.5.tgz", - "integrity": "sha512-TiwcRcrkhHvbrZbnRcFYMLl30Dfov3HKqzp5tO5b4pt6G/SezKcYhmDg15zXVBswHmctSAQKznqNW2LO5tTDow==", - "dev": true, - "requires": { - "glob": "^7.0.0", - "interpret": "^1.0.0", - "rechoir": "^0.6.2" - } - }, - "side-channel": { - "version": "1.0.4", - "resolved": "https://registry.npmjs.org/side-channel/-/side-channel-1.0.4.tgz", - "integrity": "sha512-q5XPytqFEIKHkGdiMIrY10mvLRvnQh42/+GoBlFW3b2LXLE2xxJpZFdm94we0BaoV3RwJyGqg5wS7epxTv0Zvw==", - "dev": true, - "requires": { - "call-bind": "^1.0.0", - "get-intrinsic": "^1.0.2", - "object-inspect": "^1.9.0" - } - }, - "simple-concat": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/simple-concat/-/simple-concat-1.0.1.tgz", - "integrity": "sha512-cSFtAPtRhljv69IK0hTVZQ+OfE9nePi/rtJmw5UjHeVyVroEqJXP1sFztKUy1qU+xvz3u/sfYJLa947b7nAN2Q==", - "dev": true - }, - "simple-get": { - "version": "2.8.2", - "resolved": "https://registry.npmjs.org/simple-get/-/simple-get-2.8.2.tgz", - "integrity": "sha512-Ijd/rV5o+mSBBs4F/x9oDPtTx9Zb6X9brmnXvMW4J7IR15ngi9q5xxqWBKU744jTZiaXtxaPL7uHG6vtN8kUkw==", - "dev": true, - "requires": { - "decompress-response": "^3.3.0", - "once": "^1.3.1", - "simple-concat": "^1.0.0" - } - }, - "slash": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/slash/-/slash-3.0.0.tgz", - "integrity": "sha512-g9Q1haeby36OSStwb4ntCGGGaKsaVSjQ68fBxoQcutl5fS1vuY18H3wSt3jFyFtrkx+Kz0V1G85A4MyAdDMi2Q==", - "dev": true - }, - "slice-ansi": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/slice-ansi/-/slice-ansi-4.0.0.tgz", - "integrity": "sha512-qMCMfhY040cVHT43K9BFygqYbUPFZKHOg7K73mtTWJRb8pyP3fzf4Ixd5SzdEJQ6MRUg/WBnOLxghZtKKurENQ==", - "dev": true, - "requires": { - "ansi-styles": "^4.0.0", - "astral-regex": "^2.0.0", - "is-fullwidth-code-point": "^3.0.0" - }, - "dependencies": { - "is-fullwidth-code-point": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/is-fullwidth-code-point/-/is-fullwidth-code-point-3.0.0.tgz", - "integrity": "sha512-zymm5+u+sCsSWyD9qNaejV3DFvhCKclKdizYaJUuHA83RLjb7nSuGnddCHGv0hk+KY7BMAlsWeK4Ueg6EV6XQg==", - "dev": true - } - } - }, - "solc": { - "version": "0.6.12", - "resolved": "https://registry.npmjs.org/solc/-/solc-0.6.12.tgz", - "integrity": "sha512-Lm0Ql2G9Qc7yPP2Ba+WNmzw2jwsrd3u4PobHYlSOxaut3TtUbj9+5ZrT6f4DUpNPEoBaFUOEg9Op9C0mk7ge9g==", - "dev": true, - "requires": { - "command-exists": "^1.2.8", - "commander": "3.0.2", - "fs-extra": "^0.30.0", - "js-sha3": "0.8.0", - "memorystream": "^0.3.1", - "require-from-string": "^2.0.0", - "semver": "^5.5.0", - "tmp": "0.0.33" - }, - "dependencies": { - "fs-extra": { - "version": "0.30.0", - "resolved": "https://registry.npmjs.org/fs-extra/-/fs-extra-0.30.0.tgz", - "integrity": "sha512-UvSPKyhMn6LEd/WpUaV9C9t3zATuqoqfWc3QdPhPLb58prN9tqYPlPWi8Krxi44loBoUzlobqZ3+8tGpxxSzwA==", - "dev": true, - "requires": { - "graceful-fs": "^4.1.2", - "jsonfile": "^2.1.0", - "klaw": "^1.0.0", - "path-is-absolute": "^1.0.0", - "rimraf": "^2.2.8" - } - }, - "jsonfile": { - "version": "2.4.0", - "resolved": "https://registry.npmjs.org/jsonfile/-/jsonfile-2.4.0.tgz", - "integrity": "sha512-PKllAqbgLgxHaj8TElYymKCAgrASebJrWpTnEkOaTowt23VKXXN0sUeriJ+eh7y6ufb/CC5ap11pz71/cM0hUw==", - "dev": true, - "requires": { - "graceful-fs": "^4.1.6" - } - }, - "rimraf": { - "version": "2.7.1", - "resolved": "https://registry.npmjs.org/rimraf/-/rimraf-2.7.1.tgz", - "integrity": "sha512-uWjbaKIK3T1OSVptzX7Nl6PvQ3qAGtKEtVRjRuazjfL3Bx5eI409VZSqgND+4UNnmzLVdPj9FqFJNPqBZFve4w==", - "dev": true, - "requires": { - "glob": "^7.1.3" - } - }, - "semver": { - "version": "5.7.2", - "resolved": "https://registry.npmjs.org/semver/-/semver-5.7.2.tgz", - "integrity": "sha512-cBznnQ9KjJqU67B52RMC65CMarK2600WFnbkcaiwWq3xy/5haFJlshgnpjovMVJ+Hff49d8GEn0b87C5pDQ10g==", - "dev": true - } - } - }, - "solhint": { - "version": "3.4.1", - "resolved": "https://registry.npmjs.org/solhint/-/solhint-3.4.1.tgz", - "integrity": "sha512-pzZn2RlZhws1XwvLPVSsxfHrwsteFf5eySOhpAytzXwKQYbTCJV6z8EevYDiSVKMpWrvbKpEtJ055CuEmzp4Xg==", - "dev": true, - "requires": { - "@solidity-parser/parser": "^0.16.0", - "ajv": "^6.12.6", - "antlr4": "^4.11.0", - "ast-parents": "^0.0.1", - "chalk": "^4.1.2", - "commander": "^10.0.0", - "cosmiconfig": "^8.0.0", - "fast-diff": "^1.2.0", - "glob": "^8.0.3", - "ignore": "^5.2.4", - "js-yaml": "^4.1.0", - "lodash": "^4.17.21", - "pluralize": "^8.0.0", - "prettier": "^2.8.3", - "semver": "^6.3.0", - "strip-ansi": "^6.0.1", - "table": "^6.8.1", - "text-table": "^0.2.0" - }, - "dependencies": { - "@solidity-parser/parser": { - "version": "0.16.1", - "resolved": "https://registry.npmjs.org/@solidity-parser/parser/-/parser-0.16.1.tgz", - "integrity": "sha512-PdhRFNhbTtu3x8Axm0uYpqOy/lODYQK+MlYSgqIsq2L8SFYEHJPHNUiOTAJbDGzNjjr1/n9AcIayxafR/fWmYw==", - "dev": true, - "requires": { - "antlr4ts": "^0.5.0-alpha.4" - } - }, - "argparse": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/argparse/-/argparse-2.0.1.tgz", - "integrity": "sha512-8+9WqebbFzpX9OR+Wa6O29asIogeRMzcGtAINdpMHHyAg10f05aSFVBbcEqGf/PXw1EjAZ+q2/bEBg3DvurK3Q==", - "dev": true - }, - "brace-expansion": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-2.0.1.tgz", - "integrity": "sha512-XnAIvQ8eM+kC6aULx6wuQiwVsnzsi9d3WxzV3FpWTGA19F621kwdbsAcFKXgKUHZWsy+mY6iL1sHTxWEFCytDA==", - "dev": true, - "requires": { - "balanced-match": "^1.0.0" - } - }, - "commander": { - "version": "10.0.1", - "resolved": "https://registry.npmjs.org/commander/-/commander-10.0.1.tgz", - "integrity": "sha512-y4Mg2tXshplEbSGzx7amzPwKKOCGuoSRP/CjEdwwk0FOGlUbq6lKuoyDZTNZkmxHdJtp54hdfY/JUrdL7Xfdug==", - "dev": true - }, - "glob": { - "version": "8.1.0", - "resolved": "https://registry.npmjs.org/glob/-/glob-8.1.0.tgz", - "integrity": "sha512-r8hpEjiQEYlF2QU0df3dS+nxxSIreXQS1qRhMJM0Q5NDdR386C7jb7Hwwod8Fgiuex+k0GFjgft18yvxm5XoCQ==", - "dev": true, - "requires": { - "fs.realpath": "^1.0.0", - "inflight": "^1.0.4", - "inherits": "2", - "minimatch": "^5.0.1", - "once": "^1.3.0" - } - }, - "js-yaml": { - "version": "4.1.0", - "resolved": "https://registry.npmjs.org/js-yaml/-/js-yaml-4.1.0.tgz", - "integrity": "sha512-wpxZs9NoxZaJESJGIZTyDEaYpl0FKSA+FB9aJiyemKhMwkxQg63h4T1KJgUGHpTqPDNRcmmYLugrRjJlBtWvRA==", - "dev": true, - "requires": { - "argparse": "^2.0.1" - } - }, - "minimatch": { - "version": "5.1.6", - "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-5.1.6.tgz", - "integrity": "sha512-lKwV/1brpG6mBUFHtb7NUmtABCb2WZZmm2wNiOA5hAb8VdCS4B3dtMWyvcoViccwAW/COERjXLt0zP1zXUN26g==", - "dev": true, - "requires": { - "brace-expansion": "^2.0.1" - } - } - } - }, - "solidity-comments-extractor": { - "version": "0.0.7", - "resolved": "https://registry.npmjs.org/solidity-comments-extractor/-/solidity-comments-extractor-0.0.7.tgz", - "integrity": "sha512-wciNMLg/Irp8OKGrh3S2tfvZiZ0NEyILfcRCXCD4mp7SgK/i9gzLfhY2hY7VMCQJ3kH9UB9BzNdibIVMchzyYw==", - "dev": true - }, - "solidity-coverage": { - "version": "0.7.22", - "resolved": "https://registry.npmjs.org/solidity-coverage/-/solidity-coverage-0.7.22.tgz", - "integrity": "sha512-I6Zd5tsFY+gmj1FDIp6w7OrUePx6ZpMgKQZg7dWgPaQHePLi3Jk+iJ8lwZxsWEoNy2Lcv91rMxATWHqRaFdQpw==", - "dev": true, - "requires": { - "@solidity-parser/parser": "^0.14.0", - "@truffle/provider": "^0.2.24", - "chalk": "^2.4.2", - "death": "^1.1.0", - "detect-port": "^1.3.0", - "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", - "node-emoji": "^1.10.0", - "pify": "^4.0.1", - "recursive-readdir": "^2.2.2", - "sc-istanbul": "^0.4.5", - "semver": "^7.3.4", - "shelljs": "^0.8.3", - "web3-utils": "^1.3.0" - }, - "dependencies": { - "ansi-styles": { - "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, - "requires": { - "color-convert": "^1.9.0" - } - }, - "chalk": { - "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, - "requires": { - "ansi-styles": "^3.2.1", - "escape-string-regexp": "^1.0.5", - "supports-color": "^5.3.0" - } - }, - "color-convert": { - "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, - "requires": { - "color-name": "1.1.3" - } - }, - "color-name": { - "version": "1.1.3", - "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.3.tgz", - "integrity": "sha512-72fSenhMw2HZMTVHeCA9KCmpEIbzWiQsjN+BHcBbS9vr1mtt+vJjPdksIBNUmKAW8TFUDPJK5SUU3QhE9NEXDw==", - "dev": true - }, - "escape-string-regexp": { - "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 - }, - "fs-extra": { - "version": "8.1.0", - "resolved": "https://registry.npmjs.org/fs-extra/-/fs-extra-8.1.0.tgz", - "integrity": "sha512-yhlQgA6mnOJUKOsRUFsgJdQCvkKhcz8tlZG5HBQfReYZy46OwLcY+Zia0mtdHsOo9y/hP+CxMN0TU9QxoOtG4g==", - "dev": true, - "requires": { - "graceful-fs": "^4.2.0", - "jsonfile": "^4.0.0", - "universalify": "^0.1.0" - } - }, - "globby": { - "version": "10.0.2", - "resolved": "https://registry.npmjs.org/globby/-/globby-10.0.2.tgz", - "integrity": "sha512-7dUi7RvCoT/xast/o/dLN53oqND4yk0nsHkhRgn9w65C4PofCLOoJ39iSOg+qVDdWQPIEj+eszMHQ+aLVwwQSg==", - "dev": true, - "requires": { - "@types/glob": "^7.1.1", - "array-union": "^2.1.0", - "dir-glob": "^3.0.1", - "fast-glob": "^3.0.3", - "glob": "^7.1.3", - "ignore": "^5.1.1", - "merge2": "^1.2.3", - "slash": "^3.0.0" - } - }, - "has-flag": { - "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 - }, - "lru-cache": { - "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, - "requires": { - "yallist": "^4.0.0" - } - }, - "semver": { - "version": "7.5.4", - "resolved": "https://registry.npmjs.org/semver/-/semver-7.5.4.tgz", - "integrity": "sha512-1bCSESV6Pv+i21Hvpxp3Dx+pSD8lIPt8uVjRrxAUt/nbswYc+tK6Y2btiULjd4+fnq15PX+nqQDC7Oft7WkwcA==", - "dev": true, - "requires": { - "lru-cache": "^6.0.0" - } - }, - "supports-color": { - "version": "5.5.0", - "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-5.5.0.tgz", - "integrity": "sha512-QjVjwdXIt408MIiAqCX4oUKsgU2EqAGzs2Ppkm4aQYbjm+ZEWEcW4SfFNTr4uMNZma0ey4f5lgLrkB0aX0QMow==", - "dev": true, - "requires": { - "has-flag": "^3.0.0" - } - }, - "yallist": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/yallist/-/yallist-4.0.0.tgz", - "integrity": "sha512-3wdGidZyq5PB084XLES5TpOSRA3wjXAlIWMhum2kRcv/41Sn2emQ0dycQW4uZXLejwKvg6EsvbdlVL+FYEct7A==", - "dev": true - } - } - }, - "source-map": { - "version": "0.2.0", - "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.2.0.tgz", - "integrity": "sha512-CBdZ2oa/BHhS4xj5DlhjWNHcan57/5YuvfdLf17iVmIpd9KRm+DFLmC6nBNj+6Ua7Kt3TmOjDpQT1aTYOQtoUA==", - "dev": true, - "optional": true, - "requires": { - "amdefine": ">=0.0.4" - } - }, - "source-map-support": { - "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, - "requires": { - "buffer-from": "^1.0.0", - "source-map": "^0.6.0" - }, - "dependencies": { - "source-map": { - "version": "0.6.1", - "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.6.1.tgz", - "integrity": "sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g==", - "dev": true - } - } - }, - "spdx-correct": { - "version": "3.2.0", - "resolved": "https://registry.npmjs.org/spdx-correct/-/spdx-correct-3.2.0.tgz", - "integrity": "sha512-kN9dJbvnySHULIluDHy32WHRUu3Og7B9sbY7tsFLctQkIqnMh3hErYgdMjTYuqmcXX+lK5T1lnUt3G7zNswmZA==", - "dev": true, - "requires": { - "spdx-expression-parse": "^3.0.0", - "spdx-license-ids": "^3.0.0" - } - }, - "spdx-exceptions": { - "version": "2.3.0", - "resolved": "https://registry.npmjs.org/spdx-exceptions/-/spdx-exceptions-2.3.0.tgz", - "integrity": "sha512-/tTrYOC7PPI1nUAgx34hUpqXuyJG+DTHJTnIULG4rDygi4xu/tfgmq1e1cIRwRzwZgo4NLySi+ricLkZkw4i5A==", - "dev": true - }, - "spdx-expression-parse": { - "version": "3.0.1", - "resolved": "https://registry.npmjs.org/spdx-expression-parse/-/spdx-expression-parse-3.0.1.tgz", - "integrity": "sha512-cbqHunsQWnJNE6KhVSMsMeH5H/L9EpymbzqTQ3uLwNCLZ1Q481oWaofqH7nO6V07xlXwY6PhQdQ2IedWx/ZK4Q==", - "dev": true, - "requires": { - "spdx-exceptions": "^2.1.0", - "spdx-license-ids": "^3.0.0" - } - }, - "spdx-license-ids": { - "version": "3.0.13", - "resolved": "https://registry.npmjs.org/spdx-license-ids/-/spdx-license-ids-3.0.13.tgz", - "integrity": "sha512-XkD+zwiqXHikFZm4AX/7JSCXA98U5Db4AFd5XUg/+9UNtnH75+Z9KxtpYiJZx36mUDVOwH83pl7yvCer6ewM3w==", - "dev": true - }, - "sprintf-js": { - "version": "1.0.3", - "resolved": "https://registry.npmjs.org/sprintf-js/-/sprintf-js-1.0.3.tgz", - "integrity": "sha512-D9cPgkvLlV3t3IzL0D0YLvGA9Ahk4PcvVwUbN0dSGr1aP0Nrt4AEnTUbuGvquEC0mA64Gqt1fzirlRs5ibXx8g==", - "dev": true - }, - "sshpk": { - "version": "1.17.0", - "resolved": "https://registry.npmjs.org/sshpk/-/sshpk-1.17.0.tgz", - "integrity": "sha512-/9HIEs1ZXGhSPE8X6Ccm7Nam1z8KcoCqPdI7ecm1N33EzAetWahvQWVqLZtaZQ+IDKX4IyA2o0gBzqIMkAagHQ==", - "dev": true, - "requires": { - "asn1": "~0.2.3", - "assert-plus": "^1.0.0", - "bcrypt-pbkdf": "^1.0.0", - "dashdash": "^1.12.0", - "ecc-jsbn": "~0.1.1", - "getpass": "^0.1.1", - "jsbn": "~0.1.0", - "safer-buffer": "^2.0.2", - "tweetnacl": "~0.14.0" - }, - "dependencies": { - "tweetnacl": { - "version": "0.14.5", - "resolved": "https://registry.npmjs.org/tweetnacl/-/tweetnacl-0.14.5.tgz", - "integrity": "sha512-KXXFFdAbFXY4geFIwoyNK+f5Z1b7swfXABfL7HXCmoIWMKU3dmS26672A4EeQtDzLKy7SXmfBu51JolvEKwtGA==", - "dev": true - } - } - }, - "stacktrace-parser": { - "version": "0.1.10", - "resolved": "https://registry.npmjs.org/stacktrace-parser/-/stacktrace-parser-0.1.10.tgz", - "integrity": "sha512-KJP1OCML99+8fhOHxwwzyWrlUuVX5GQ0ZpJTd1DFXhdkrvg1szxfHhawXUZ3g9TkXORQd4/WG68jMlQZ2p8wlg==", - "dev": true, - "requires": { - "type-fest": "^0.7.1" - }, - "dependencies": { - "type-fest": { - "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 - } - } - }, - "statuses": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/statuses/-/statuses-2.0.1.tgz", - "integrity": "sha512-RwNA9Z/7PrK06rYLIzFMlaF+l73iwpzsqRIFgbMLbTcLD6cOao82TaWefPXQvB2fOC4AjuYSEndS7N/mTCbkdQ==", - "dev": true - }, - "stealthy-require": { - "version": "1.1.1", - "resolved": "https://registry.npmjs.org/stealthy-require/-/stealthy-require-1.1.1.tgz", - "integrity": "sha512-ZnWpYnYugiOVEY5GkcuJK1io5V8QmNYChG62gSit9pQVGErXtrKuPC55ITaVSukmMta5qpMU7vqLt2Lnni4f/g==", - "dev": true - }, - "strict-uri-encode": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/strict-uri-encode/-/strict-uri-encode-1.1.0.tgz", - "integrity": "sha512-R3f198pcvnB+5IpnBlRkphuE9n46WyVl8I39W/ZUTZLz4nqSP/oLYUrcnJrw462Ds8he4YKMov2efsTIw1BDGQ==", - "dev": true - }, - "string_decoder": { - "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==", - "requires": { - "safe-buffer": "~5.2.0" - }, - "dependencies": { - "safe-buffer": { - "version": "5.2.1", - "resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.2.1.tgz", - "integrity": "sha512-rp3So07KcdmmKbGvgaNxQSJr7bGVSVk5S9Eq1F+ppbRo70+YeaDxkw5Dd8NPN+GD6bjnYm2VuPuCXmpuYvmCXQ==" - } - } - }, - "string-width": { - "version": "2.1.1", - "resolved": "https://registry.npmjs.org/string-width/-/string-width-2.1.1.tgz", - "integrity": "sha512-nOqH59deCq9SRHlxq1Aw85Jnt4w6KvLKqWVik6oA9ZklXLNIOlqg4F2yrT1MVaTjAqvVwdfeZ7w7aCvJD7ugkw==", - "dev": true, - "requires": { - "is-fullwidth-code-point": "^2.0.0", - "strip-ansi": "^4.0.0" - }, - "dependencies": { - "ansi-regex": { - "version": "3.0.1", - "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-3.0.1.tgz", - "integrity": "sha512-+O9Jct8wf++lXxxFc4hc8LsjaSq0HFzzL7cVsw8pRDIPdjKD2mT4ytDZlLuSBZ4cLKZFXIrMGO7DbQCtMJJMKw==", - "dev": true - }, - "strip-ansi": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-4.0.0.tgz", - "integrity": "sha512-4XaJ2zQdCzROZDivEVIDPkcQn8LMFSa8kj8Gxb/Lnwzv9A8VctNZ+lfivC/sV3ivW8ElJTERXZoPBRrZKkNKow==", - "dev": true, - "requires": { - "ansi-regex": "^3.0.0" - } - } - } - }, - "string.prototype.trim": { - "version": "1.2.7", - "resolved": "https://registry.npmjs.org/string.prototype.trim/-/string.prototype.trim-1.2.7.tgz", - "integrity": "sha512-p6TmeT1T3411M8Cgg9wBTMRtY2q9+PNy9EV1i2lIXUN/btt763oIfxwN3RR8VU6wHX8j/1CFy0L+YuThm6bgOg==", - "dev": true, - "requires": { - "call-bind": "^1.0.2", - "define-properties": "^1.1.4", - "es-abstract": "^1.20.4" - } - }, - "string.prototype.trimend": { - "version": "1.0.6", - "resolved": "https://registry.npmjs.org/string.prototype.trimend/-/string.prototype.trimend-1.0.6.tgz", - "integrity": "sha512-JySq+4mrPf9EsDBEDYMOb/lM7XQLulwg5R/m1r0PXEFqrV0qHvl58sdTilSXtKOflCsK2E8jxf+GKC0T07RWwQ==", - "dev": true, - "requires": { - "call-bind": "^1.0.2", - "define-properties": "^1.1.4", - "es-abstract": "^1.20.4" - } - }, - "string.prototype.trimstart": { - "version": "1.0.6", - "resolved": "https://registry.npmjs.org/string.prototype.trimstart/-/string.prototype.trimstart-1.0.6.tgz", - "integrity": "sha512-omqjMDaY92pbn5HOX7f9IccLA+U1tA9GvtU4JrodiXFfYB7jPzzHpRzpglLAjtUV6bB557zwClJezTqnAiYnQA==", - "dev": true, - "requires": { - "call-bind": "^1.0.2", - "define-properties": "^1.1.4", - "es-abstract": "^1.20.4" - } - }, - "strip-ansi": { - "version": "6.0.1", - "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-6.0.1.tgz", - "integrity": "sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A==", - "dev": true, - "requires": { - "ansi-regex": "^5.0.1" - } - }, - "strip-bom": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/strip-bom/-/strip-bom-3.0.0.tgz", - "integrity": "sha512-vavAMRXOgBVNF6nyEEmL3DBK19iRpDcoIwW+swQ+CbGiu7lju6t+JklA1MHweoWtadgt4ISVUsXLyDq34ddcwA==", - "dev": true - }, - "strip-hex-prefix": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/strip-hex-prefix/-/strip-hex-prefix-1.0.0.tgz", - "integrity": "sha512-q8d4ue7JGEiVcypji1bALTos+0pWtyGlivAWyPuTkHzuTCJqrK9sWxYQZUq6Nq3cuyv3bm734IhHvHtGGURU6A==", - "requires": { - "is-hex-prefixed": "1.0.0" - } - }, - "strip-json-comments": { - "version": "3.1.1", - "resolved": "https://registry.npmjs.org/strip-json-comments/-/strip-json-comments-3.1.1.tgz", - "integrity": "sha512-6fPc+R4ihwqP6N/aIv2f1gMH8lOVtWQHoqC4yK6oSDVVocumAsfCqjkXnqiYMhmMwS/mEHLp7Vehlt3ql6lEig==", - "dev": true - }, - "supports-color": { - "version": "7.2.0", - "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.2.0.tgz", - "integrity": "sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==", - "requires": { - "has-flag": "^4.0.0" - } - }, - "supports-preserve-symlinks-flag": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/supports-preserve-symlinks-flag/-/supports-preserve-symlinks-flag-1.0.0.tgz", - "integrity": "sha512-ot0WnXS9fgdkgIcePe6RHNk1WA8+muPa6cSjeR3V8K27q9BB1rTE3R1p7Hv0z1ZyAc8s6Vvv8DIyWf681MAt0w==", - "dev": true - }, - "swarm-js": { - "version": "0.1.42", - "resolved": "https://registry.npmjs.org/swarm-js/-/swarm-js-0.1.42.tgz", - "integrity": "sha512-BV7c/dVlA3R6ya1lMlSSNPLYrntt0LUq4YMgy3iwpCIc6rZnS5W2wUoctarZ5pXlpKtxDDf9hNziEkcfrxdhqQ==", - "dev": true, - "requires": { - "bluebird": "^3.5.0", - "buffer": "^5.0.5", - "eth-lib": "^0.1.26", - "fs-extra": "^4.0.2", - "got": "^11.8.5", - "mime-types": "^2.1.16", - "mkdirp-promise": "^5.0.1", - "mock-fs": "^4.1.0", - "setimmediate": "^1.0.5", - "tar": "^4.0.2", - "xhr-request": "^1.0.1" - }, - "dependencies": { - "@sindresorhus/is": { - "version": "4.6.0", - "resolved": "https://registry.npmjs.org/@sindresorhus/is/-/is-4.6.0.tgz", - "integrity": "sha512-t09vSN3MdfsyCHoFcTRCH/iUtG7OJ0CsjzB8cjAmKc/va/kIgeDI/TxsigdncE/4be734m0cvIYwNaV4i2XqAw==", - "dev": true - }, - "@szmarczak/http-timer": { - "version": "4.0.6", - "resolved": "https://registry.npmjs.org/@szmarczak/http-timer/-/http-timer-4.0.6.tgz", - "integrity": "sha512-4BAffykYOgO+5nzBWYwE3W90sBgLJoUPRWWcL8wlyiM8IB8ipJz3UMJ9KXQd1RKQXpKp8Tutn80HZtWsu2u76w==", - "dev": true, - "requires": { - "defer-to-connect": "^2.0.0" - } - }, - "buffer": { - "version": "5.7.1", - "resolved": "https://registry.npmjs.org/buffer/-/buffer-5.7.1.tgz", - "integrity": "sha512-EHcyIPBQ4BSGlvjB16k5KgAJ27CIsHY/2JBmCRReo48y9rQ3MaUzWX3KVlBa4U7MyX02HdVj0K7C3WaB3ju7FQ==", - "dev": true, - "requires": { - "base64-js": "^1.3.1", - "ieee754": "^1.1.13" - } - }, - "cacheable-lookup": { - "version": "5.0.4", - "resolved": "https://registry.npmjs.org/cacheable-lookup/-/cacheable-lookup-5.0.4.tgz", - "integrity": "sha512-2/kNscPhpcxrOigMZzbiWF7dz8ilhb/nIHU3EyZiXWXpeq/au8qJ8VhdftMkty3n7Gj6HIGalQG8oiBNB3AJgA==", - "dev": true - }, - "cacheable-request": { - "version": "7.0.4", - "resolved": "https://registry.npmjs.org/cacheable-request/-/cacheable-request-7.0.4.tgz", - "integrity": "sha512-v+p6ongsrp0yTGbJXjgxPow2+DL93DASP4kXCDKb8/bwRtt9OEF3whggkkDkGNzgcWy2XaF4a8nZglC7uElscg==", - "dev": true, - "requires": { - "clone-response": "^1.0.2", - "get-stream": "^5.1.0", - "http-cache-semantics": "^4.0.0", - "keyv": "^4.0.0", - "lowercase-keys": "^2.0.0", - "normalize-url": "^6.0.1", - "responselike": "^2.0.0" - } - }, - "decompress-response": { - "version": "6.0.0", - "resolved": "https://registry.npmjs.org/decompress-response/-/decompress-response-6.0.0.tgz", - "integrity": "sha512-aW35yZM6Bb/4oJlZncMH2LCoZtJXTRxES17vE3hoRiowU2kWHaJKFkSBDnDR+cm9J+9QhXmREyIfv0pji9ejCQ==", - "dev": true, - "requires": { - "mimic-response": "^3.1.0" - } - }, - "defer-to-connect": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/defer-to-connect/-/defer-to-connect-2.0.1.tgz", - "integrity": "sha512-4tvttepXG1VaYGrRibk5EwJd1t4udunSOVMdLSAL6mId1ix438oPwPZMALY41FCijukO1L0twNcGsdzS7dHgDg==", - "dev": true - }, - "fs-extra": { - "version": "4.0.3", - "resolved": "https://registry.npmjs.org/fs-extra/-/fs-extra-4.0.3.tgz", - "integrity": "sha512-q6rbdDd1o2mAnQreO7YADIxf/Whx4AHBiRf6d+/cVT8h44ss+lHgxf1FemcqDnQt9X3ct4McHr+JMGlYSsK7Cg==", - "dev": true, - "requires": { - "graceful-fs": "^4.1.2", - "jsonfile": "^4.0.0", - "universalify": "^0.1.0" - } - }, - "get-stream": { - "version": "5.2.0", - "resolved": "https://registry.npmjs.org/get-stream/-/get-stream-5.2.0.tgz", - "integrity": "sha512-nBF+F1rAZVCu/p7rjzgA+Yb4lfYXrpl7a6VmJrU8wF9I1CKvP/QwPNZHnOlwbTkY6dvtFIzFMSyQXbLoTQPRpA==", - "dev": true, - "requires": { - "pump": "^3.0.0" - } - }, - "got": { - "version": "11.8.6", - "resolved": "https://registry.npmjs.org/got/-/got-11.8.6.tgz", - "integrity": "sha512-6tfZ91bOr7bOXnK7PRDCGBLa1H4U080YHNaAQ2KsMGlLEzRbk44nsZF2E1IeRc3vtJHPVbKCYgdFbaGO2ljd8g==", - "dev": true, - "requires": { - "@sindresorhus/is": "^4.0.0", - "@szmarczak/http-timer": "^4.0.5", - "@types/cacheable-request": "^6.0.1", - "@types/responselike": "^1.0.0", - "cacheable-lookup": "^5.0.3", - "cacheable-request": "^7.0.2", - "decompress-response": "^6.0.0", - "http2-wrapper": "^1.0.0-beta.5.2", - "lowercase-keys": "^2.0.0", - "p-cancelable": "^2.0.0", - "responselike": "^2.0.0" - } - }, - "http2-wrapper": { - "version": "1.0.3", - "resolved": "https://registry.npmjs.org/http2-wrapper/-/http2-wrapper-1.0.3.tgz", - "integrity": "sha512-V+23sDMr12Wnz7iTcDeJr3O6AIxlnvT/bmaAAAP/Xda35C90p9599p0F1eHR/N1KILWSoWVAiOMFjBBXaXSMxg==", - "dev": true, - "requires": { - "quick-lru": "^5.1.1", - "resolve-alpn": "^1.0.0" - } - }, - "json-buffer": { - "version": "3.0.1", - "resolved": "https://registry.npmjs.org/json-buffer/-/json-buffer-3.0.1.tgz", - "integrity": "sha512-4bV5BfR2mqfQTJm+V5tPPdf+ZpuhiIvTuAB5g8kcrXOZpTT/QwwVRWBywX1ozr6lEuPdbHxwaJlm9G6mI2sfSQ==", - "dev": true - }, - "keyv": { - "version": "4.5.3", - "resolved": "https://registry.npmjs.org/keyv/-/keyv-4.5.3.tgz", - "integrity": "sha512-QCiSav9WaX1PgETJ+SpNnx2PRRapJ/oRSXM4VO5OGYGSjrxbKPVFVhB3l2OCbLCk329N8qyAtsJjSjvVBWzEug==", - "dev": true, - "requires": { - "json-buffer": "3.0.1" - } - }, - "lowercase-keys": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/lowercase-keys/-/lowercase-keys-2.0.0.tgz", - "integrity": "sha512-tqNXrS78oMOE73NMxK4EMLQsQowWf8jKooH9g7xPavRT706R6bkQJ6DY2Te7QukaZsulxa30wQ7bk0pm4XiHmA==", - "dev": true - }, - "mimic-response": { - "version": "3.1.0", - "resolved": "https://registry.npmjs.org/mimic-response/-/mimic-response-3.1.0.tgz", - "integrity": "sha512-z0yWI+4FDrrweS8Zmt4Ej5HdJmky15+L2e6Wgn3+iK5fWzb6T3fhNFq2+MeTRb064c6Wr4N/wv0DzQTjNzHNGQ==", - "dev": true - }, - "normalize-url": { - "version": "6.1.0", - "resolved": "https://registry.npmjs.org/normalize-url/-/normalize-url-6.1.0.tgz", - "integrity": "sha512-DlL+XwOy3NxAQ8xuC0okPgK46iuVNAK01YN7RueYBqqFeGsBjV9XmCAzAdgt+667bCl5kPh9EqKKDwnaPG1I7A==", - "dev": true - }, - "p-cancelable": { - "version": "2.1.1", - "resolved": "https://registry.npmjs.org/p-cancelable/-/p-cancelable-2.1.1.tgz", - "integrity": "sha512-BZOr3nRQHOntUjTrH8+Lh54smKHoHyur8We1V8DSMVrl5A2malOOwuJRnKRDjSnkoeBh4at6BwEnb5I7Jl31wg==", - "dev": true - }, - "responselike": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/responselike/-/responselike-2.0.1.tgz", - "integrity": "sha512-4gl03wn3hj1HP3yzgdI7d3lCkF95F21Pz4BPGvKHinyQzALR5CapwC8yIi0Rh58DEMQ/SguC03wFj2k0M/mHhw==", - "dev": true, - "requires": { - "lowercase-keys": "^2.0.0" - } - } - } - }, - "sync-request": { - "version": "6.1.0", - "resolved": "https://registry.npmjs.org/sync-request/-/sync-request-6.1.0.tgz", - "integrity": "sha512-8fjNkrNlNCrVc/av+Jn+xxqfCjYaBoHqCsDz6mt030UMxJGr+GSfCV1dQt2gRtlL63+VPidwDVLr7V2OcTSdRw==", - "dev": true, - "requires": { - "http-response-object": "^3.0.1", - "sync-rpc": "^1.2.1", - "then-request": "^6.0.0" - } - }, - "sync-rpc": { - "version": "1.3.6", - "resolved": "https://registry.npmjs.org/sync-rpc/-/sync-rpc-1.3.6.tgz", - "integrity": "sha512-J8jTXuZzRlvU7HemDgHi3pGnh/rkoqR/OZSjhTyyZrEkkYQbk7Z33AXp37mkPfPpfdOuj7Ex3H/TJM1z48uPQw==", - "dev": true, - "requires": { - "get-port": "^3.1.0" - } - }, - "table": { - "version": "6.8.1", - "resolved": "https://registry.npmjs.org/table/-/table-6.8.1.tgz", - "integrity": "sha512-Y4X9zqrCftUhMeH2EptSSERdVKt/nEdijTOacGD/97EKjhQ/Qs8RTlEGABSJNNN8lac9kheH+af7yAkEWlgneA==", - "dev": true, - "requires": { - "ajv": "^8.0.1", - "lodash.truncate": "^4.4.2", - "slice-ansi": "^4.0.0", - "string-width": "^4.2.3", - "strip-ansi": "^6.0.1" - }, - "dependencies": { - "ajv": { - "version": "8.12.0", - "resolved": "https://registry.npmjs.org/ajv/-/ajv-8.12.0.tgz", - "integrity": "sha512-sRu1kpcO9yLtYxBKvqfTeh9KzZEwO3STyX1HT+4CaDzC6HpTGYhIhPIzj9XuKU7KYDwnaeh5hcOwjy1QuJzBPA==", - "dev": true, - "requires": { - "fast-deep-equal": "^3.1.1", - "json-schema-traverse": "^1.0.0", - "require-from-string": "^2.0.2", - "uri-js": "^4.2.2" - } - }, - "is-fullwidth-code-point": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/is-fullwidth-code-point/-/is-fullwidth-code-point-3.0.0.tgz", - "integrity": "sha512-zymm5+u+sCsSWyD9qNaejV3DFvhCKclKdizYaJUuHA83RLjb7nSuGnddCHGv0hk+KY7BMAlsWeK4Ueg6EV6XQg==", - "dev": true - }, - "json-schema-traverse": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/json-schema-traverse/-/json-schema-traverse-1.0.0.tgz", - "integrity": "sha512-NM8/P9n3XjXhIZn1lLhkFaACTOURQXjWhV4BA/RnOv8xvgqtqpAX9IO4mRQxSx1Rlo4tqzeqb0sOlruaOy3dug==", - "dev": true - }, - "string-width": { - "version": "4.2.3", - "resolved": "https://registry.npmjs.org/string-width/-/string-width-4.2.3.tgz", - "integrity": "sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g==", - "dev": true, - "requires": { - "emoji-regex": "^8.0.0", - "is-fullwidth-code-point": "^3.0.0", - "strip-ansi": "^6.0.1" - } - } - } - }, - "tar": { - "version": "4.4.19", - "resolved": "https://registry.npmjs.org/tar/-/tar-4.4.19.tgz", - "integrity": "sha512-a20gEsvHnWe0ygBY8JbxoM4w3SJdhc7ZAuxkLqh+nvNQN2IOt0B5lLgM490X5Hl8FF0dl0tOf2ewFYAlIFgzVA==", - "dev": true, - "requires": { - "chownr": "^1.1.4", - "fs-minipass": "^1.2.7", - "minipass": "^2.9.0", - "minizlib": "^1.3.3", - "mkdirp": "^0.5.5", - "safe-buffer": "^5.2.1", - "yallist": "^3.1.1" - }, - "dependencies": { - "safe-buffer": { - "version": "5.2.1", - "resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.2.1.tgz", - "integrity": "sha512-rp3So07KcdmmKbGvgaNxQSJr7bGVSVk5S9Eq1F+ppbRo70+YeaDxkw5Dd8NPN+GD6bjnYm2VuPuCXmpuYvmCXQ==", - "dev": true - } - } - }, - "test-value": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/test-value/-/test-value-2.1.0.tgz", - "integrity": "sha512-+1epbAxtKeXttkGFMTX9H42oqzOTufR1ceCF+GYA5aOmvaPq9wd4PUS8329fn2RRLGNeUkgRLnVpycjx8DsO2w==", - "dev": true, - "requires": { - "array-back": "^1.0.3", - "typical": "^2.6.0" - }, - "dependencies": { - "array-back": { - "version": "1.0.4", - "resolved": "https://registry.npmjs.org/array-back/-/array-back-1.0.4.tgz", - "integrity": "sha512-1WxbZvrmyhkNoeYcizokbmh5oiOCIfyvGtcqbK3Ls1v1fKcquzxnQSceOx6tzq7jmai2kFLWIpGND2cLhH6TPw==", - "dev": true, - "requires": { - "typical": "^2.6.0" - } - } - } - }, - "testrpc": { - "version": "0.0.1", - "resolved": "https://registry.npmjs.org/testrpc/-/testrpc-0.0.1.tgz", - "integrity": "sha512-afH1hO+SQ/VPlmaLUFj2636QMeDvPCeQMc/9RBMW0IfjNe9gFD9Ra3ShqYkB7py0do1ZcCna/9acHyzTJ+GcNA==", - "dev": true - }, - "text-table": { - "version": "0.2.0", - "resolved": "https://registry.npmjs.org/text-table/-/text-table-0.2.0.tgz", - "integrity": "sha512-N+8UisAXDGk8PFXP4HAzVR9nbfmVJ3zYLAWiTIoqC5v5isinhr+r5uaO8+7r3BMfuNIufIsA7RdpVgacC2cSpw==", - "dev": true - }, - "then-request": { - "version": "6.0.2", - "resolved": "https://registry.npmjs.org/then-request/-/then-request-6.0.2.tgz", - "integrity": "sha512-3ZBiG7JvP3wbDzA9iNY5zJQcHL4jn/0BWtXIkagfz7QgOL/LqjCEOBQuJNZfu0XYnv5JhKh+cDxCPM4ILrqruA==", - "dev": true, - "requires": { - "@types/concat-stream": "^1.6.0", - "@types/form-data": "0.0.33", - "@types/node": "^8.0.0", - "@types/qs": "^6.2.31", - "caseless": "~0.12.0", - "concat-stream": "^1.6.0", - "form-data": "^2.2.0", - "http-basic": "^8.1.1", - "http-response-object": "^3.0.1", - "promise": "^8.0.0", - "qs": "^6.4.0" - }, - "dependencies": { - "@types/node": { - "version": "8.10.66", - "resolved": "https://registry.npmjs.org/@types/node/-/node-8.10.66.tgz", - "integrity": "sha512-tktOkFUA4kXx2hhhrB8bIFb5TbwzS4uOhKEmwiD+NoiL0qtP2OQ9mFldbgD4dV1djrlBYP6eBuQZiWjuHUpqFw==", - "dev": true - }, - "form-data": { - "version": "2.5.1", - "resolved": "https://registry.npmjs.org/form-data/-/form-data-2.5.1.tgz", - "integrity": "sha512-m21N3WOmEEURgk6B9GLOE4RuWOFf28Lhh9qGYeNlGq4VDXUlJy2th2slBNU8Gp8EzloYZOibZJ7t5ecIrFSjVA==", - "dev": true, - "requires": { - "asynckit": "^0.4.0", - "combined-stream": "^1.0.6", - "mime-types": "^2.1.12" - } - } - } - }, - "timed-out": { - "version": "4.0.1", - "resolved": "https://registry.npmjs.org/timed-out/-/timed-out-4.0.1.tgz", - "integrity": "sha512-G7r3AhovYtr5YKOWQkta8RKAPb+J9IsO4uVmzjl8AZwfhs8UcUwTiD6gcJYSgOtzyjvQKrKYn41syHbUWMkafA==", - "dev": true - }, - "tmp": { - "version": "0.0.33", - "resolved": "https://registry.npmjs.org/tmp/-/tmp-0.0.33.tgz", - "integrity": "sha512-jRCJlojKnZ3addtTOjdIqoRuPEKBvNXcGYqzO6zWZX8KfKEpnGY5jfggJQ3EjKuu8D4bJRr0y+cYJFmYbImXGw==", - "dev": true, - "requires": { - "os-tmpdir": "~1.0.2" - } - }, - "to-readable-stream": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/to-readable-stream/-/to-readable-stream-1.0.0.tgz", - "integrity": "sha512-Iq25XBt6zD5npPhlLVXGFN3/gyR2/qODcKNNyTMd4vbm39HUaOiAM4PMq0eMVC/Tkxz+Zjdsc55g9yyz+Yq00Q==", - "dev": true - }, - "to-regex-range": { - "version": "5.0.1", - "resolved": "https://registry.npmjs.org/to-regex-range/-/to-regex-range-5.0.1.tgz", - "integrity": "sha512-65P7iz6X5yEr1cwcgvQxbbIw7Uk3gOy5dIdtZ4rDveLqhrdJP+Li/Hx6tyK0NEb+2GCyneCMJiGqrADCSNk8sQ==", - "dev": true, - "requires": { - "is-number": "^7.0.0" - } - }, - "toidentifier": { - "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 - }, - "tough-cookie": { - "version": "2.5.0", - "resolved": "https://registry.npmjs.org/tough-cookie/-/tough-cookie-2.5.0.tgz", - "integrity": "sha512-nlLsUzgm1kfLXSXfRZMc1KLAugd4hqJHDTvc2hDIwS3mZAfMEuMbc03SujMF+GEcpaX/qboeycw6iO8JwVv2+g==", - "dev": true, - "requires": { - "psl": "^1.1.28", - "punycode": "^2.1.1" - }, - "dependencies": { - "punycode": { - "version": "2.3.0", - "resolved": "https://registry.npmjs.org/punycode/-/punycode-2.3.0.tgz", - "integrity": "sha512-rRV+zQD8tVFys26lAGR9WUuS4iUAngJScM+ZRSKtvl5tKeZ2t5bvdNFdNHBW9FWR4guGHlgmsZ1G7BSm2wTbuA==", - "dev": true - } - } - }, - "tr46": { - "version": "0.0.3", - "resolved": "https://registry.npmjs.org/tr46/-/tr46-0.0.3.tgz", - "integrity": "sha512-N3WMsuqV66lT30CrXNbEjx4GEwlow3v6rr4mCcv6prnfwhS01rkgyFdjPNBYd9br7LpXV1+Emh01fHnq2Gdgrw==" - }, - "treeify": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/treeify/-/treeify-1.1.0.tgz", - "integrity": "sha512-1m4RA7xVAJrSGrrXGs0L3YTwyvBs2S8PbRHaLZAkFw7JR8oIFwYtysxlBZhYIa7xSyiYJKZ3iGrrk55cGA3i9A==" - }, - "ts-essentials": { - "version": "7.0.3", - "resolved": "https://registry.npmjs.org/ts-essentials/-/ts-essentials-7.0.3.tgz", - "integrity": "sha512-8+gr5+lqO3G84KdiTSMRLtuyJ+nTBVRKuCrK4lidMPdVeEp0uqC875uE5NMcaA7YYMN7XsNiFQuMvasF8HT/xQ==", - "dev": true, - "requires": {} - }, - "ts-generator": { - "version": "0.1.1", - "resolved": "https://registry.npmjs.org/ts-generator/-/ts-generator-0.1.1.tgz", - "integrity": "sha512-N+ahhZxTLYu1HNTQetwWcx3so8hcYbkKBHTr4b4/YgObFTIKkOSSsaa+nal12w8mfrJAyzJfETXawbNjSfP2gQ==", - "dev": true, - "requires": { - "@types/mkdirp": "^0.5.2", - "@types/prettier": "^2.1.1", - "@types/resolve": "^0.0.8", - "chalk": "^2.4.1", - "glob": "^7.1.2", - "mkdirp": "^0.5.1", - "prettier": "^2.1.2", - "resolve": "^1.8.1", - "ts-essentials": "^1.0.0" - }, - "dependencies": { - "ansi-styles": { - "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, - "requires": { - "color-convert": "^1.9.0" - } - }, - "chalk": { - "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, - "requires": { - "ansi-styles": "^3.2.1", - "escape-string-regexp": "^1.0.5", - "supports-color": "^5.3.0" - } - }, - "color-convert": { - "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, - "requires": { - "color-name": "1.1.3" - } - }, - "color-name": { - "version": "1.1.3", - "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.3.tgz", - "integrity": "sha512-72fSenhMw2HZMTVHeCA9KCmpEIbzWiQsjN+BHcBbS9vr1mtt+vJjPdksIBNUmKAW8TFUDPJK5SUU3QhE9NEXDw==", - "dev": true - }, - "escape-string-regexp": { - "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 - }, - "has-flag": { - "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 - }, - "supports-color": { - "version": "5.5.0", - "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-5.5.0.tgz", - "integrity": "sha512-QjVjwdXIt408MIiAqCX4oUKsgU2EqAGzs2Ppkm4aQYbjm+ZEWEcW4SfFNTr4uMNZma0ey4f5lgLrkB0aX0QMow==", - "dev": true, - "requires": { - "has-flag": "^3.0.0" - } - }, - "ts-essentials": { - "version": "1.0.4", - "resolved": "https://registry.npmjs.org/ts-essentials/-/ts-essentials-1.0.4.tgz", - "integrity": "sha512-q3N1xS4vZpRouhYHDPwO0bDW3EZ6SK9CrrDHxi/D6BPReSjpVgWIOpLS2o0gSBZm+7q/wyKp6RVM1AeeW7uyfQ==", - "dev": true - } - } - }, - "ts-node": { - "version": "10.9.1", - "resolved": "https://registry.npmjs.org/ts-node/-/ts-node-10.9.1.tgz", - "integrity": "sha512-NtVysVPkxxrwFGUUxGYhfux8k78pQB3JqYBXlLRZgdGUqTO5wU/UyHop5p70iEbGhB7q5KmiZiU0Y3KlJrScEw==", - "dev": true, - "requires": { - "@cspotcode/source-map-support": "^0.8.0", - "@tsconfig/node10": "^1.0.7", - "@tsconfig/node12": "^1.0.7", - "@tsconfig/node14": "^1.0.0", - "@tsconfig/node16": "^1.0.2", - "acorn": "^8.4.1", - "acorn-walk": "^8.1.1", - "arg": "^4.1.0", - "create-require": "^1.1.0", - "diff": "^4.0.1", - "make-error": "^1.1.1", - "v8-compile-cache-lib": "^3.0.1", - "yn": "3.1.1" - }, - "dependencies": { - "acorn": { - "version": "8.10.0", - "resolved": "https://registry.npmjs.org/acorn/-/acorn-8.10.0.tgz", - "integrity": "sha512-F0SAmZ8iUtS//m8DmCTA0jlh6TDKkHQyK6xc6V4KDTyZKA9dnvX9/3sRTVQrWm79glUAZbnmmNcdYwUIHWVybw==", - "dev": true - }, - "diff": { - "version": "4.0.2", - "resolved": "https://registry.npmjs.org/diff/-/diff-4.0.2.tgz", - "integrity": "sha512-58lmxKSA4BNyLz+HHMUzlOEpg09FV+ev6ZMe3vJihgdxzgcwZ8VoEEPmALCZG9LmqfVoNMMKpttIYTVG6uDY7A==", - "dev": true - } - } - }, - "tsconfig-paths": { - "version": "3.14.2", - "resolved": "https://registry.npmjs.org/tsconfig-paths/-/tsconfig-paths-3.14.2.tgz", - "integrity": "sha512-o/9iXgCYc5L/JxCHPe3Hvh8Q/2xm5Z+p18PESBU6Ff33695QnCHBEjcytY2q19ua7Mbl/DavtBOLq+oG0RCL+g==", - "dev": true, - "requires": { - "@types/json5": "^0.0.29", - "json5": "^1.0.2", - "minimist": "^1.2.6", - "strip-bom": "^3.0.0" - } - }, - "tslib": { - "version": "1.14.1", - "resolved": "https://registry.npmjs.org/tslib/-/tslib-1.14.1.tgz", - "integrity": "sha512-Xni35NKzjgMrwevysHTCArtLDpPvye8zV/0E4EyYn43P7/7qvQwPh9BGkHewbMulVntbigmcT7rdX3BNo9wRJg==", - "dev": true - }, - "tsort": { - "version": "0.0.1", - "resolved": "https://registry.npmjs.org/tsort/-/tsort-0.0.1.tgz", - "integrity": "sha512-Tyrf5mxF8Ofs1tNoxA13lFeZ2Zrbd6cKbuH3V+MQ5sb6DtBj5FjrXVsRWT8YvNAQTqNoz66dz1WsbigI22aEnw==", - "dev": true - }, - "tsutils": { - "version": "3.21.0", - "resolved": "https://registry.npmjs.org/tsutils/-/tsutils-3.21.0.tgz", - "integrity": "sha512-mHKK3iUXL+3UF6xL5k0PEhKRUBKPBCv/+RkEOpjRWxxx27KKRBmmA60A9pgOUvMi8GKhRMPEmjBRPzs2W7O1OA==", - "dev": true, - "requires": { - "tslib": "^1.8.1" - } - }, - "tunnel-agent": { - "version": "0.6.0", - "resolved": "https://registry.npmjs.org/tunnel-agent/-/tunnel-agent-0.6.0.tgz", - "integrity": "sha512-McnNiV1l8RYeY8tBgEpuodCC1mLUdbSN+CYBL7kJsJNInOP8UjDDEwdk6Mw60vdLLrr5NHKZhMAOSrR2NZuQ+w==", - "dev": true, - "requires": { - "safe-buffer": "^5.0.1" - } - }, - "tweetnacl": { - "version": "1.0.3", - "resolved": "https://registry.npmjs.org/tweetnacl/-/tweetnacl-1.0.3.tgz", - "integrity": "sha512-6rt+RN7aOi1nGMyC4Xa5DdYiukl2UWCbcJft7YhxReBGQD7OAM8Pbxw6YMo4r2diNEA8FEmu32YOn9rhaiE5yw==", - "dev": true - }, - "tweetnacl-util": { - "version": "0.15.1", - "resolved": "https://registry.npmjs.org/tweetnacl-util/-/tweetnacl-util-0.15.1.tgz", - "integrity": "sha512-RKJBIj8lySrShN4w6i/BonWp2Z/uxwC3h4y7xsRrpP59ZboCd0GpEVsOnMDYLMmKBpYhb5TgHzZXy7wTfYFBRw==", - "dev": true - }, - "type": { - "version": "1.2.0", - "resolved": "https://registry.npmjs.org/type/-/type-1.2.0.tgz", - "integrity": "sha512-+5nt5AAniqsCnu2cEQQdpzCAh33kVx8n0VoFidKpB1dVVLAN/F+bgVOqOJqOnEnrhp222clB5p3vUlD+1QAnfg==", - "dev": true - }, - "type-check": { - "version": "0.4.0", - "resolved": "https://registry.npmjs.org/type-check/-/type-check-0.4.0.tgz", - "integrity": "sha512-XleUoc9uwGXqjWwXaUTZAmzMcFZ5858QA2vvx1Ur5xIcixXIP+8LnFDgRplU30us6teqdlskFfu+ae4K79Ooew==", - "dev": true, - "requires": { - "prelude-ls": "^1.2.1" - } - }, - "type-detect": { - "version": "4.0.8", - "resolved": "https://registry.npmjs.org/type-detect/-/type-detect-4.0.8.tgz", - "integrity": "sha512-0fr/mIH1dlO+x7TlcMy+bIDqKPsw/70tVyeHW787goQjhmqaZe10uwLujubK9q9Lg6Fiho1KUKDYz0Z7k7g5/g==" - }, - "type-fest": { - "version": "0.20.2", - "resolved": "https://registry.npmjs.org/type-fest/-/type-fest-0.20.2.tgz", - "integrity": "sha512-Ne+eE4r0/iWnpAxD852z3A+N0Bt5RN//NjJwRd2VFHEmrywxf5vsZlh4R6lixl6B+wz/8d+maTSAkN1FIkI3LQ==", - "dev": true - }, - "type-is": { - "version": "1.6.18", - "resolved": "https://registry.npmjs.org/type-is/-/type-is-1.6.18.tgz", - "integrity": "sha512-TkRKr9sUTxEH8MdfuCSP7VizJyzRNMjj2J2do2Jr3Kym598JVdEksuzPQCnlFPW4ky9Q+iA+ma9BGm06XQBy8g==", - "dev": true, - "requires": { - "media-typer": "0.3.0", - "mime-types": "~2.1.24" - } - }, - "typechain": { - "version": "5.2.0", - "resolved": "https://registry.npmjs.org/typechain/-/typechain-5.2.0.tgz", - "integrity": "sha512-0INirvQ+P+MwJOeMct+WLkUE4zov06QxC96D+i3uGFEHoiSkZN70MKDQsaj8zkL86wQwByJReI2e7fOUwECFuw==", - "dev": true, - "requires": { - "@types/prettier": "^2.1.1", - "command-line-args": "^4.0.7", - "debug": "^4.1.1", - "fs-extra": "^7.0.0", - "glob": "^7.1.6", - "js-sha3": "^0.8.0", - "lodash": "^4.17.15", - "mkdirp": "^1.0.4", - "prettier": "^2.1.2", - "ts-essentials": "^7.0.1" - }, - "dependencies": { - "mkdirp": { - "version": "1.0.4", - "resolved": "https://registry.npmjs.org/mkdirp/-/mkdirp-1.0.4.tgz", - "integrity": "sha512-vVqVZQyf3WLx2Shd0qJ9xuvqgAyKPLAiqITEtqW0oIUjzo3PePDd6fW9iFz30ef7Ysp/oiWqbhszeGWW2T6Gzw==", - "dev": true - } - } - }, - "typed-array-buffer": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/typed-array-buffer/-/typed-array-buffer-1.0.0.tgz", - "integrity": "sha512-Y8KTSIglk9OZEr8zywiIHG/kmQ7KWyjseXs1CbSo8vC42w7hg2HgYTxSWwP0+is7bWDc1H+Fo026CpHFwm8tkw==", - "dev": true, - "requires": { - "call-bind": "^1.0.2", - "get-intrinsic": "^1.2.1", - "is-typed-array": "^1.1.10" - } - }, - "typed-array-byte-length": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/typed-array-byte-length/-/typed-array-byte-length-1.0.0.tgz", - "integrity": "sha512-Or/+kvLxNpeQ9DtSydonMxCx+9ZXOswtwJn17SNLvhptaXYDJvkFFP5zbfU/uLmvnBJlI4yrnXRxpdWH/M5tNA==", - "dev": true, - "requires": { - "call-bind": "^1.0.2", - "for-each": "^0.3.3", - "has-proto": "^1.0.1", - "is-typed-array": "^1.1.10" - } - }, - "typed-array-byte-offset": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/typed-array-byte-offset/-/typed-array-byte-offset-1.0.0.tgz", - "integrity": "sha512-RD97prjEt9EL8YgAgpOkf3O4IF9lhJFr9g0htQkm0rchFp/Vx7LW5Q8fSXXub7BXAODyUQohRMyOc3faCPd0hg==", - "dev": true, - "requires": { - "available-typed-arrays": "^1.0.5", - "call-bind": "^1.0.2", - "for-each": "^0.3.3", - "has-proto": "^1.0.1", - "is-typed-array": "^1.1.10" - } - }, - "typed-array-length": { - "version": "1.0.4", - "resolved": "https://registry.npmjs.org/typed-array-length/-/typed-array-length-1.0.4.tgz", - "integrity": "sha512-KjZypGq+I/H7HI5HlOoGHkWUUGq+Q0TPhQurLbyrVrvnKTBgzLhIJ7j6J/XTQOi0d1RjyZ0wdas8bKs2p0x3Ng==", - "dev": true, - "requires": { - "call-bind": "^1.0.2", - "for-each": "^0.3.3", - "is-typed-array": "^1.1.9" - } - }, - "typedarray": { - "version": "0.0.6", - "resolved": "https://registry.npmjs.org/typedarray/-/typedarray-0.0.6.tgz", - "integrity": "sha512-/aCDEGatGvZ2BIk+HmLf4ifCJFwvKFNb9/JeZPMulfgFracn9QFcAf5GO8B/mweUjSoblS5In0cWhqpfs/5PQA==", - "dev": true - }, - "typedarray-to-buffer": { - "version": "3.1.5", - "resolved": "https://registry.npmjs.org/typedarray-to-buffer/-/typedarray-to-buffer-3.1.5.tgz", - "integrity": "sha512-zdu8XMNEDepKKR+XYOXAVPtWui0ly0NtohUscw+UmaHiAWT8hrV1rr//H6V+0DvJ3OQ19S979M0laLfX8rm82Q==", - "dev": true, - "requires": { - "is-typedarray": "^1.0.0" - } - }, - "typescript": { - "version": "4.9.5", - "resolved": "https://registry.npmjs.org/typescript/-/typescript-4.9.5.tgz", - "integrity": "sha512-1FXk9E2Hm+QzZQ7z+McJiHL4NW1F2EzMu9Nq9i3zAaGqibafqYwCVU6WyWAuyQRRzOlxou8xZSyXLEN8oKj24g==", - "dev": true - }, - "typical": { - "version": "2.6.1", - "resolved": "https://registry.npmjs.org/typical/-/typical-2.6.1.tgz", - "integrity": "sha512-ofhi8kjIje6npGozTip9Fr8iecmYfEbS06i0JnIg+rh51KakryWF4+jX8lLKZVhy6N+ID45WYSFCxPOdTWCzNg==", - "dev": true - }, - "uglify-js": { - "version": "3.17.4", - "resolved": "https://registry.npmjs.org/uglify-js/-/uglify-js-3.17.4.tgz", - "integrity": "sha512-T9q82TJI9e/C1TAxYvfb16xO120tMVFZrGA3f9/P4424DNu6ypK103y0GPFVa17yotwSyZW5iYXgjYHkGrJW/g==", - "dev": true, - "optional": true - }, - "ultron": { - "version": "1.1.1", - "resolved": "https://registry.npmjs.org/ultron/-/ultron-1.1.1.tgz", - "integrity": "sha512-UIEXBNeYmKptWH6z8ZnqTeS8fV74zG0/eRU9VGkpzz+LIJNs8W/zM/L+7ctCkRrgbNnnR0xxw4bKOr0cW0N0Og==", - "dev": true - }, - "unbox-primitive": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/unbox-primitive/-/unbox-primitive-1.0.2.tgz", - "integrity": "sha512-61pPlCD9h51VoreyJ0BReideM3MDKMKnh6+V9L08331ipq6Q8OFXZYiqP6n/tbHx4s5I9uRhcye6BrbkizkBDw==", - "dev": true, - "requires": { - "call-bind": "^1.0.2", - "has-bigints": "^1.0.2", - "has-symbols": "^1.0.3", - "which-boxed-primitive": "^1.0.2" - } - }, - "undici": { - "version": "5.26.3", - "resolved": "https://registry.npmjs.org/undici/-/undici-5.26.3.tgz", - "integrity": "sha512-H7n2zmKEWgOllKkIUkLvFmsJQj062lSm3uA4EYApG8gLuiOM0/go9bIoC3HVaSnfg4xunowDE2i9p8drkXuvDw==", - "dev": true, - "requires": { - "@fastify/busboy": "^2.0.0" - } - }, - "universalify": { - "version": "0.1.2", - "resolved": "https://registry.npmjs.org/universalify/-/universalify-0.1.2.tgz", - "integrity": "sha512-rBJeI5CXAlmy1pV+617WB9J63U6XcazHHF2f2dbJix4XzpUF0RS3Zbj0FGIOCAva5P/d/GBOYaACQ1w+0azUkg==", - "dev": true - }, - "unpipe": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/unpipe/-/unpipe-1.0.0.tgz", - "integrity": "sha512-pjy2bYhSsufwWlKwPc+l3cN7+wuJlK6uz0YdJEOlQDbl6jo/YlPi4mb8agUkVC8BF7V8NuzeyPNqRksA3hztKQ==", - "dev": true - }, - "uri-js": { - "version": "4.4.1", - "resolved": "https://registry.npmjs.org/uri-js/-/uri-js-4.4.1.tgz", - "integrity": "sha512-7rKUyy33Q1yc98pQ1DAmLtwX109F7TIfWlW1Ydo8Wl1ii1SeHieeh0HHfPeL2fMXK6z0s8ecKs9frCuLJvndBg==", - "dev": true, - "requires": { - "punycode": "^2.1.0" - } - }, - "url": { - "version": "0.11.1", - "resolved": "https://registry.npmjs.org/url/-/url-0.11.1.tgz", - "integrity": "sha512-rWS3H04/+mzzJkv0eZ7vEDGiQbgquI1fGfOad6zKvgYQi1SzMmhl7c/DdRGxhaWrVH6z0qWITo8rpnxK/RfEhA==", - "dev": true, - "requires": { - "punycode": "^1.4.1", - "qs": "^6.11.0" - }, - "dependencies": { - "punycode": { - "version": "1.4.1", - "resolved": "https://registry.npmjs.org/punycode/-/punycode-1.4.1.tgz", - "integrity": "sha512-jmYNElW7yvO7TV33CjSmvSiE2yco3bV2czu/OzDKdMNVZQWfxCblURLhf+47syQRBntjfLdd/H0egrzIG+oaFQ==", - "dev": true - }, - "qs": { - "version": "6.11.2", - "resolved": "https://registry.npmjs.org/qs/-/qs-6.11.2.tgz", - "integrity": "sha512-tDNIz22aBzCDxLtVH++VnTfzxlfeK5CbqohpSqpJgj1Wg/cQbStNAz3NuqCs5vV+pjBsK4x4pN9HlVh7rcYRiA==", - "dev": true, - "requires": { - "side-channel": "^1.0.4" - } - } - } - }, - "url-parse-lax": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/url-parse-lax/-/url-parse-lax-3.0.0.tgz", - "integrity": "sha512-NjFKA0DidqPa5ciFcSrXnAltTtzz84ogy+NebPvfEgAck0+TNg4UJ4IN+fB7zRZfbgUf0syOo9MDxFkDSMuFaQ==", - "dev": true, - "requires": { - "prepend-http": "^2.0.0" - } - }, - "url-set-query": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/url-set-query/-/url-set-query-1.0.0.tgz", - "integrity": "sha512-3AChu4NiXquPfeckE5R5cGdiHCMWJx1dwCWOmWIL4KHAziJNOFIYJlpGFeKDvwLPHovZRCxK3cYlwzqI9Vp+Gg==", - "dev": true - }, - "utf-8-validate": { - "version": "5.0.10", - "resolved": "https://registry.npmjs.org/utf-8-validate/-/utf-8-validate-5.0.10.tgz", - "integrity": "sha512-Z6czzLq4u8fPOyx7TU6X3dvUZVvoJmxSQ+IcrlmagKhilxlhZgxPK6C5Jqbkw1IDUmFTM+cz9QDnnLTwDz/2gQ==", - "devOptional": true, - "requires": { - "node-gyp-build": "^4.3.0" - } - }, - "utf8": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/utf8/-/utf8-3.0.0.tgz", - "integrity": "sha512-E8VjFIQ/TyQgp+TZfS6l8yp/xWppSAHzidGiRrqe4bK4XP9pTRyKFgGJpO3SN7zdX4DeomTrwaseCHovfpFcqQ==" - }, - "util": { - "version": "0.12.5", - "resolved": "https://registry.npmjs.org/util/-/util-0.12.5.tgz", - "integrity": "sha512-kZf/K6hEIrWHI6XqOFUiiMa+79wE/D8Q+NCNAWclkyg3b4d2k7s0QGepNjiABc+aR3N1PAyHL7p6UcLY6LmrnA==", - "dev": true, - "requires": { - "inherits": "^2.0.3", - "is-arguments": "^1.0.4", - "is-generator-function": "^1.0.7", - "is-typed-array": "^1.1.3", - "which-typed-array": "^1.1.2" - } - }, - "util-deprecate": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/util-deprecate/-/util-deprecate-1.0.2.tgz", - "integrity": "sha512-EPD5q1uXyFxJpCrLnCc1nHnq3gOa6DZBocAIiI2TaSCA7VCJ1UJDMagCzIkXNsUYfD1daK//LTEQ8xiIbrHtcw==" - }, - "utils-merge": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/utils-merge/-/utils-merge-1.0.1.tgz", - "integrity": "sha512-pMZTvIkT1d+TFGvDOqodOclx0QWkkgi6Tdoa8gC8ffGAAqz9pzPTZWAybbsHHoED/ztMtkv/VoYTYyShUn81hA==", - "dev": true - }, - "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 - }, - "v8-compile-cache": { - "version": "2.3.0", - "resolved": "https://registry.npmjs.org/v8-compile-cache/-/v8-compile-cache-2.3.0.tgz", - "integrity": "sha512-l8lCEmLcLYZh4nbunNZvQCJc5pv7+RCwa8q/LdUx8u7lsWvPDKmpodJAJNwkAhJC//dFY48KuIEmjtd4RViDrA==", - "dev": true - }, - "v8-compile-cache-lib": { - "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==", - "dev": true - }, - "validate-npm-package-license": { - "version": "3.0.4", - "resolved": "https://registry.npmjs.org/validate-npm-package-license/-/validate-npm-package-license-3.0.4.tgz", - "integrity": "sha512-DpKm2Ui/xN7/HQKCtpZxoRWBhZ9Z0kqtygG8XCgNQ8ZlDnxuQmWhj566j8fN4Cu3/JmbhsDo7fcAJq4s9h27Ew==", - "dev": true, - "requires": { - "spdx-correct": "^3.0.0", - "spdx-expression-parse": "^3.0.0" - } - }, - "varint": { - "version": "5.0.2", - "resolved": "https://registry.npmjs.org/varint/-/varint-5.0.2.tgz", - "integrity": "sha512-lKxKYG6H03yCZUpAGOPOsMcGxd1RHCu1iKvEHYDPmTyq2HueGhD73ssNBqqQWfvYs04G9iUFRvmAVLW20Jw6ow==", - "dev": true - }, - "vary": { - "version": "1.1.2", - "resolved": "https://registry.npmjs.org/vary/-/vary-1.1.2.tgz", - "integrity": "sha512-BNGbWLfd0eUPabhkXUVm0j8uuvREyTh5ovRa/dyow/BqAbZJyC+5fU+IzQOzmAKzYqYRAISoRhdQr3eIZ/PXqg==", - "dev": true - }, - "verror": { - "version": "1.10.0", - "resolved": "https://registry.npmjs.org/verror/-/verror-1.10.0.tgz", - "integrity": "sha512-ZZKSmDAEFOijERBLkmYfJ+vmk3w+7hOLYDNkRCuRuMJGEmqYNCNLyBBFwWKVMhfwaEF3WOd0Zlw86U/WC/+nYw==", - "dev": true, - "requires": { - "assert-plus": "^1.0.0", - "core-util-is": "1.0.2", - "extsprintf": "^1.2.0" - } - }, - "web3": { - "version": "1.7.4", - "resolved": "https://registry.npmjs.org/web3/-/web3-1.7.4.tgz", - "integrity": "sha512-iFGK5jO32vnXM/ASaJBaI0+gVR6uHozvYdxkdhaeOCD6HIQ4iIXadbO2atVpE9oc/H8l2MovJ4LtPhG7lIBN8A==", - "dev": true, - "requires": { - "web3-bzz": "1.7.4", - "web3-core": "1.7.4", - "web3-eth": "1.7.4", - "web3-eth-personal": "1.7.4", - "web3-net": "1.7.4", - "web3-shh": "1.7.4", - "web3-utils": "1.7.4" - }, - "dependencies": { - "ethereum-cryptography": { - "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, - "requires": { - "@types/pbkdf2": "^3.0.0", - "@types/secp256k1": "^4.0.1", - "blakejs": "^1.1.0", - "browserify-aes": "^1.2.0", - "bs58check": "^2.1.2", - "create-hash": "^1.2.0", - "create-hmac": "^1.1.7", - "hash.js": "^1.1.7", - "keccak": "^3.0.0", - "pbkdf2": "^3.0.17", - "randombytes": "^2.1.0", - "safe-buffer": "^5.1.2", - "scrypt-js": "^3.0.0", - "secp256k1": "^4.0.1", - "setimmediate": "^1.0.5" - } - }, - "ethereumjs-util": { - "version": "7.1.5", - "resolved": "https://registry.npmjs.org/ethereumjs-util/-/ethereumjs-util-7.1.5.tgz", - "integrity": "sha512-SDl5kKrQAudFBUe5OJM9Ac6WmMyYmXX/6sTmLZ3ffG2eY6ZIGBes3pEDxNN6V72WyOw4CPD5RomKdsa8DAAwLg==", - "dev": true, - "requires": { - "@types/bn.js": "^5.1.0", - "bn.js": "^5.1.2", - "create-hash": "^1.1.2", - "ethereum-cryptography": "^0.1.3", - "rlp": "^2.2.4" - } - }, - "web3-utils": { - "version": "1.7.4", - "resolved": "https://registry.npmjs.org/web3-utils/-/web3-utils-1.7.4.tgz", - "integrity": "sha512-acBdm6Evd0TEZRnChM/MCvGsMwYKmSh7OaUfNf5OKG0CIeGWD/6gqLOWIwmwSnre/2WrA1nKGId5uW2e5EfluA==", - "dev": true, - "requires": { - "bn.js": "^5.2.1", - "ethereum-bloom-filters": "^1.0.6", - "ethereumjs-util": "^7.1.0", - "ethjs-unit": "0.1.6", - "number-to-bn": "1.7.0", - "randombytes": "^2.1.0", - "utf8": "3.0.0" - } - } - } - }, - "web3-bzz": { - "version": "1.7.4", - "resolved": "https://registry.npmjs.org/web3-bzz/-/web3-bzz-1.7.4.tgz", - "integrity": "sha512-w9zRhyEqTK/yi0LGRHjZMcPCfP24LBjYXI/9YxFw9VqsIZ9/G0CRCnUt12lUx0A56LRAMpF7iQ8eA73aBcO29Q==", - "dev": true, - "requires": { - "@types/node": "^12.12.6", - "got": "9.6.0", - "swarm-js": "^0.1.40" - } - }, - "web3-core": { - "version": "1.7.4", - "resolved": "https://registry.npmjs.org/web3-core/-/web3-core-1.7.4.tgz", - "integrity": "sha512-L0DCPlIh9bgIED37tYbe7bsWrddoXYc897ANGvTJ6MFkSNGiMwDkTLWSgYd9Mf8qu8b4iuPqXZHMwIo4atoh7Q==", - "dev": true, - "requires": { - "@types/bn.js": "^5.1.0", - "@types/node": "^12.12.6", - "bignumber.js": "^9.0.0", - "web3-core-helpers": "1.7.4", - "web3-core-method": "1.7.4", - "web3-core-requestmanager": "1.7.4", - "web3-utils": "1.7.4" - }, - "dependencies": { - "ethereum-cryptography": { - "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, - "requires": { - "@types/pbkdf2": "^3.0.0", - "@types/secp256k1": "^4.0.1", - "blakejs": "^1.1.0", - "browserify-aes": "^1.2.0", - "bs58check": "^2.1.2", - "create-hash": "^1.2.0", - "create-hmac": "^1.1.7", - "hash.js": "^1.1.7", - "keccak": "^3.0.0", - "pbkdf2": "^3.0.17", - "randombytes": "^2.1.0", - "safe-buffer": "^5.1.2", - "scrypt-js": "^3.0.0", - "secp256k1": "^4.0.1", - "setimmediate": "^1.0.5" - } - }, - "ethereumjs-util": { - "version": "7.1.5", - "resolved": "https://registry.npmjs.org/ethereumjs-util/-/ethereumjs-util-7.1.5.tgz", - "integrity": "sha512-SDl5kKrQAudFBUe5OJM9Ac6WmMyYmXX/6sTmLZ3ffG2eY6ZIGBes3pEDxNN6V72WyOw4CPD5RomKdsa8DAAwLg==", - "dev": true, - "requires": { - "@types/bn.js": "^5.1.0", - "bn.js": "^5.1.2", - "create-hash": "^1.1.2", - "ethereum-cryptography": "^0.1.3", - "rlp": "^2.2.4" - } - }, - "web3-utils": { - "version": "1.7.4", - "resolved": "https://registry.npmjs.org/web3-utils/-/web3-utils-1.7.4.tgz", - "integrity": "sha512-acBdm6Evd0TEZRnChM/MCvGsMwYKmSh7OaUfNf5OKG0CIeGWD/6gqLOWIwmwSnre/2WrA1nKGId5uW2e5EfluA==", - "dev": true, - "requires": { - "bn.js": "^5.2.1", - "ethereum-bloom-filters": "^1.0.6", - "ethereumjs-util": "^7.1.0", - "ethjs-unit": "0.1.6", - "number-to-bn": "1.7.0", - "randombytes": "^2.1.0", - "utf8": "3.0.0" - } - } - } - }, - "web3-core-helpers": { - "version": "1.7.4", - "resolved": "https://registry.npmjs.org/web3-core-helpers/-/web3-core-helpers-1.7.4.tgz", - "integrity": "sha512-F8PH11qIkE/LpK4/h1fF/lGYgt4B6doeMi8rukeV/s4ivseZHHslv1L6aaijLX/g/j4PsFmR42byynBI/MIzFg==", - "dev": true, - "requires": { - "web3-eth-iban": "1.7.4", - "web3-utils": "1.7.4" - }, - "dependencies": { - "ethereum-cryptography": { - "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, - "requires": { - "@types/pbkdf2": "^3.0.0", - "@types/secp256k1": "^4.0.1", - "blakejs": "^1.1.0", - "browserify-aes": "^1.2.0", - "bs58check": "^2.1.2", - "create-hash": "^1.2.0", - "create-hmac": "^1.1.7", - "hash.js": "^1.1.7", - "keccak": "^3.0.0", - "pbkdf2": "^3.0.17", - "randombytes": "^2.1.0", - "safe-buffer": "^5.1.2", - "scrypt-js": "^3.0.0", - "secp256k1": "^4.0.1", - "setimmediate": "^1.0.5" - } - }, - "ethereumjs-util": { - "version": "7.1.5", - "resolved": "https://registry.npmjs.org/ethereumjs-util/-/ethereumjs-util-7.1.5.tgz", - "integrity": "sha512-SDl5kKrQAudFBUe5OJM9Ac6WmMyYmXX/6sTmLZ3ffG2eY6ZIGBes3pEDxNN6V72WyOw4CPD5RomKdsa8DAAwLg==", - "dev": true, - "requires": { - "@types/bn.js": "^5.1.0", - "bn.js": "^5.1.2", - "create-hash": "^1.1.2", - "ethereum-cryptography": "^0.1.3", - "rlp": "^2.2.4" - } - }, - "web3-utils": { - "version": "1.7.4", - "resolved": "https://registry.npmjs.org/web3-utils/-/web3-utils-1.7.4.tgz", - "integrity": "sha512-acBdm6Evd0TEZRnChM/MCvGsMwYKmSh7OaUfNf5OKG0CIeGWD/6gqLOWIwmwSnre/2WrA1nKGId5uW2e5EfluA==", - "dev": true, - "requires": { - "bn.js": "^5.2.1", - "ethereum-bloom-filters": "^1.0.6", - "ethereumjs-util": "^7.1.0", - "ethjs-unit": "0.1.6", - "number-to-bn": "1.7.0", - "randombytes": "^2.1.0", - "utf8": "3.0.0" - } - } - } - }, - "web3-core-method": { - "version": "1.7.4", - "resolved": "https://registry.npmjs.org/web3-core-method/-/web3-core-method-1.7.4.tgz", - "integrity": "sha512-56K7pq+8lZRkxJyzf5MHQPI9/VL3IJLoy4L/+q8HRdZJ3CkB1DkXYaXGU2PeylG1GosGiSzgIfu1ljqS7CP9xQ==", - "dev": true, - "requires": { - "@ethersproject/transactions": "^5.6.2", - "web3-core-helpers": "1.7.4", - "web3-core-promievent": "1.7.4", - "web3-core-subscriptions": "1.7.4", - "web3-utils": "1.7.4" - }, - "dependencies": { - "ethereum-cryptography": { - "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, - "requires": { - "@types/pbkdf2": "^3.0.0", - "@types/secp256k1": "^4.0.1", - "blakejs": "^1.1.0", - "browserify-aes": "^1.2.0", - "bs58check": "^2.1.2", - "create-hash": "^1.2.0", - "create-hmac": "^1.1.7", - "hash.js": "^1.1.7", - "keccak": "^3.0.0", - "pbkdf2": "^3.0.17", - "randombytes": "^2.1.0", - "safe-buffer": "^5.1.2", - "scrypt-js": "^3.0.0", - "secp256k1": "^4.0.1", - "setimmediate": "^1.0.5" - } - }, - "ethereumjs-util": { - "version": "7.1.5", - "resolved": "https://registry.npmjs.org/ethereumjs-util/-/ethereumjs-util-7.1.5.tgz", - "integrity": "sha512-SDl5kKrQAudFBUe5OJM9Ac6WmMyYmXX/6sTmLZ3ffG2eY6ZIGBes3pEDxNN6V72WyOw4CPD5RomKdsa8DAAwLg==", - "dev": true, - "requires": { - "@types/bn.js": "^5.1.0", - "bn.js": "^5.1.2", - "create-hash": "^1.1.2", - "ethereum-cryptography": "^0.1.3", - "rlp": "^2.2.4" - } - }, - "web3-utils": { - "version": "1.7.4", - "resolved": "https://registry.npmjs.org/web3-utils/-/web3-utils-1.7.4.tgz", - "integrity": "sha512-acBdm6Evd0TEZRnChM/MCvGsMwYKmSh7OaUfNf5OKG0CIeGWD/6gqLOWIwmwSnre/2WrA1nKGId5uW2e5EfluA==", - "dev": true, - "requires": { - "bn.js": "^5.2.1", - "ethereum-bloom-filters": "^1.0.6", - "ethereumjs-util": "^7.1.0", - "ethjs-unit": "0.1.6", - "number-to-bn": "1.7.0", - "randombytes": "^2.1.0", - "utf8": "3.0.0" - } - } - } - }, - "web3-core-promievent": { - "version": "1.7.4", - "resolved": "https://registry.npmjs.org/web3-core-promievent/-/web3-core-promievent-1.7.4.tgz", - "integrity": "sha512-o4uxwXKDldN7ER7VUvDfWsqTx9nQSP1aDssi1XYXeYC2xJbVo0n+z6ryKtmcoWoRdRj7uSpVzal3nEmlr480mA==", - "dev": true, - "requires": { - "eventemitter3": "4.0.4" - } - }, - "web3-core-requestmanager": { - "version": "1.7.4", - "resolved": "https://registry.npmjs.org/web3-core-requestmanager/-/web3-core-requestmanager-1.7.4.tgz", - "integrity": "sha512-IuXdAm65BQtPL4aI6LZJJOrKAs0SM5IK2Cqo2/lMNvVMT9Kssq6qOk68Uf7EBDH0rPuINi+ReLP+uH+0g3AnPA==", - "dev": true, - "requires": { - "util": "^0.12.0", - "web3-core-helpers": "1.7.4", - "web3-providers-http": "1.7.4", - "web3-providers-ipc": "1.7.4", - "web3-providers-ws": "1.7.4" - } - }, - "web3-core-subscriptions": { - "version": "1.7.4", - "resolved": "https://registry.npmjs.org/web3-core-subscriptions/-/web3-core-subscriptions-1.7.4.tgz", - "integrity": "sha512-VJvKWaXRyxk2nFWumOR94ut9xvjzMrRtS38c4qj8WBIRSsugrZr5lqUwgndtj0qx4F+50JhnU++QEqUEAtKm3g==", - "dev": true, - "requires": { - "eventemitter3": "4.0.4", - "web3-core-helpers": "1.7.4" - } - }, - "web3-eth": { - "version": "1.7.4", - "resolved": "https://registry.npmjs.org/web3-eth/-/web3-eth-1.7.4.tgz", - "integrity": "sha512-JG0tTMv0Ijj039emXNHi07jLb0OiWSA9O24MRSk5vToTQyDNXihdF2oyq85LfHuF690lXZaAXrjhtLNlYqb7Ug==", - "dev": true, - "requires": { - "web3-core": "1.7.4", - "web3-core-helpers": "1.7.4", - "web3-core-method": "1.7.4", - "web3-core-subscriptions": "1.7.4", - "web3-eth-abi": "1.7.4", - "web3-eth-accounts": "1.7.4", - "web3-eth-contract": "1.7.4", - "web3-eth-ens": "1.7.4", - "web3-eth-iban": "1.7.4", - "web3-eth-personal": "1.7.4", - "web3-net": "1.7.4", - "web3-utils": "1.7.4" - }, - "dependencies": { - "ethereum-cryptography": { - "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, - "requires": { - "@types/pbkdf2": "^3.0.0", - "@types/secp256k1": "^4.0.1", - "blakejs": "^1.1.0", - "browserify-aes": "^1.2.0", - "bs58check": "^2.1.2", - "create-hash": "^1.2.0", - "create-hmac": "^1.1.7", - "hash.js": "^1.1.7", - "keccak": "^3.0.0", - "pbkdf2": "^3.0.17", - "randombytes": "^2.1.0", - "safe-buffer": "^5.1.2", - "scrypt-js": "^3.0.0", - "secp256k1": "^4.0.1", - "setimmediate": "^1.0.5" - } - }, - "ethereumjs-util": { - "version": "7.1.5", - "resolved": "https://registry.npmjs.org/ethereumjs-util/-/ethereumjs-util-7.1.5.tgz", - "integrity": "sha512-SDl5kKrQAudFBUe5OJM9Ac6WmMyYmXX/6sTmLZ3ffG2eY6ZIGBes3pEDxNN6V72WyOw4CPD5RomKdsa8DAAwLg==", - "dev": true, - "requires": { - "@types/bn.js": "^5.1.0", - "bn.js": "^5.1.2", - "create-hash": "^1.1.2", - "ethereum-cryptography": "^0.1.3", - "rlp": "^2.2.4" - } - }, - "web3-utils": { - "version": "1.7.4", - "resolved": "https://registry.npmjs.org/web3-utils/-/web3-utils-1.7.4.tgz", - "integrity": "sha512-acBdm6Evd0TEZRnChM/MCvGsMwYKmSh7OaUfNf5OKG0CIeGWD/6gqLOWIwmwSnre/2WrA1nKGId5uW2e5EfluA==", - "dev": true, - "requires": { - "bn.js": "^5.2.1", - "ethereum-bloom-filters": "^1.0.6", - "ethereumjs-util": "^7.1.0", - "ethjs-unit": "0.1.6", - "number-to-bn": "1.7.0", - "randombytes": "^2.1.0", - "utf8": "3.0.0" - } - } - } - }, - "web3-eth-abi": { - "version": "1.7.4", - "resolved": "https://registry.npmjs.org/web3-eth-abi/-/web3-eth-abi-1.7.4.tgz", - "integrity": "sha512-eMZr8zgTbqyL9MCTCAvb67RbVyN5ZX7DvA0jbLOqRWCiw+KlJKTGnymKO6jPE8n5yjk4w01e165Qb11hTDwHgg==", - "dev": true, - "requires": { - "@ethersproject/abi": "^5.6.3", - "web3-utils": "1.7.4" - }, - "dependencies": { - "ethereum-cryptography": { - "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, - "requires": { - "@types/pbkdf2": "^3.0.0", - "@types/secp256k1": "^4.0.1", - "blakejs": "^1.1.0", - "browserify-aes": "^1.2.0", - "bs58check": "^2.1.2", - "create-hash": "^1.2.0", - "create-hmac": "^1.1.7", - "hash.js": "^1.1.7", - "keccak": "^3.0.0", - "pbkdf2": "^3.0.17", - "randombytes": "^2.1.0", - "safe-buffer": "^5.1.2", - "scrypt-js": "^3.0.0", - "secp256k1": "^4.0.1", - "setimmediate": "^1.0.5" - } - }, - "ethereumjs-util": { - "version": "7.1.5", - "resolved": "https://registry.npmjs.org/ethereumjs-util/-/ethereumjs-util-7.1.5.tgz", - "integrity": "sha512-SDl5kKrQAudFBUe5OJM9Ac6WmMyYmXX/6sTmLZ3ffG2eY6ZIGBes3pEDxNN6V72WyOw4CPD5RomKdsa8DAAwLg==", - "dev": true, - "requires": { - "@types/bn.js": "^5.1.0", - "bn.js": "^5.1.2", - "create-hash": "^1.1.2", - "ethereum-cryptography": "^0.1.3", - "rlp": "^2.2.4" - } - }, - "web3-utils": { - "version": "1.7.4", - "resolved": "https://registry.npmjs.org/web3-utils/-/web3-utils-1.7.4.tgz", - "integrity": "sha512-acBdm6Evd0TEZRnChM/MCvGsMwYKmSh7OaUfNf5OKG0CIeGWD/6gqLOWIwmwSnre/2WrA1nKGId5uW2e5EfluA==", - "dev": true, - "requires": { - "bn.js": "^5.2.1", - "ethereum-bloom-filters": "^1.0.6", - "ethereumjs-util": "^7.1.0", - "ethjs-unit": "0.1.6", - "number-to-bn": "1.7.0", - "randombytes": "^2.1.0", - "utf8": "3.0.0" - } - } - } - }, - "web3-eth-accounts": { - "version": "1.7.4", - "resolved": "https://registry.npmjs.org/web3-eth-accounts/-/web3-eth-accounts-1.7.4.tgz", - "integrity": "sha512-Y9vYLRKP7VU7Cgq6wG1jFaG2k3/eIuiTKAG8RAuQnb6Cd9k5BRqTm5uPIiSo0AP/u11jDomZ8j7+WEgkU9+Btw==", - "dev": true, - "requires": { - "@ethereumjs/common": "^2.5.0", - "@ethereumjs/tx": "^3.3.2", - "crypto-browserify": "3.12.0", - "eth-lib": "0.2.8", - "ethereumjs-util": "^7.0.10", - "scrypt-js": "^3.0.1", - "uuid": "3.3.2", - "web3-core": "1.7.4", - "web3-core-helpers": "1.7.4", - "web3-core-method": "1.7.4", - "web3-utils": "1.7.4" - }, - "dependencies": { - "eth-lib": { - "version": "0.2.8", - "resolved": "https://registry.npmjs.org/eth-lib/-/eth-lib-0.2.8.tgz", - "integrity": "sha512-ArJ7x1WcWOlSpzdoTBX8vkwlkSQ85CjjifSZtV4co64vWxSV8geWfPI9x4SVYu3DSxnX4yWFVTtGL+j9DUFLNw==", - "dev": true, - "requires": { - "bn.js": "^4.11.6", - "elliptic": "^6.4.0", - "xhr-request-promise": "^0.1.2" - }, - "dependencies": { - "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==", - "dev": true - } - } - }, - "ethereum-cryptography": { - "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, - "requires": { - "@types/pbkdf2": "^3.0.0", - "@types/secp256k1": "^4.0.1", - "blakejs": "^1.1.0", - "browserify-aes": "^1.2.0", - "bs58check": "^2.1.2", - "create-hash": "^1.2.0", - "create-hmac": "^1.1.7", - "hash.js": "^1.1.7", - "keccak": "^3.0.0", - "pbkdf2": "^3.0.17", - "randombytes": "^2.1.0", - "safe-buffer": "^5.1.2", - "scrypt-js": "^3.0.0", - "secp256k1": "^4.0.1", - "setimmediate": "^1.0.5" - } - }, - "ethereumjs-util": { - "version": "7.1.5", - "resolved": "https://registry.npmjs.org/ethereumjs-util/-/ethereumjs-util-7.1.5.tgz", - "integrity": "sha512-SDl5kKrQAudFBUe5OJM9Ac6WmMyYmXX/6sTmLZ3ffG2eY6ZIGBes3pEDxNN6V72WyOw4CPD5RomKdsa8DAAwLg==", - "dev": true, - "requires": { - "@types/bn.js": "^5.1.0", - "bn.js": "^5.1.2", - "create-hash": "^1.1.2", - "ethereum-cryptography": "^0.1.3", - "rlp": "^2.2.4" - } - }, - "uuid": { - "version": "3.3.2", - "resolved": "https://registry.npmjs.org/uuid/-/uuid-3.3.2.tgz", - "integrity": "sha512-yXJmeNaw3DnnKAOKJE51sL/ZaYfWJRl1pK9dr19YFCu0ObS231AB1/LbqTKRAQ5kw8A90rA6fr4riOUpTZvQZA==", - "dev": true - }, - "web3-utils": { - "version": "1.7.4", - "resolved": "https://registry.npmjs.org/web3-utils/-/web3-utils-1.7.4.tgz", - "integrity": "sha512-acBdm6Evd0TEZRnChM/MCvGsMwYKmSh7OaUfNf5OKG0CIeGWD/6gqLOWIwmwSnre/2WrA1nKGId5uW2e5EfluA==", - "dev": true, - "requires": { - "bn.js": "^5.2.1", - "ethereum-bloom-filters": "^1.0.6", - "ethereumjs-util": "^7.1.0", - "ethjs-unit": "0.1.6", - "number-to-bn": "1.7.0", - "randombytes": "^2.1.0", - "utf8": "3.0.0" - } - } - } - }, - "web3-eth-contract": { - "version": "1.7.4", - "resolved": "https://registry.npmjs.org/web3-eth-contract/-/web3-eth-contract-1.7.4.tgz", - "integrity": "sha512-ZgSZMDVI1pE9uMQpK0T0HDT2oewHcfTCv0osEqf5qyn5KrcQDg1GT96/+S0dfqZ4HKj4lzS5O0rFyQiLPQ8LzQ==", - "dev": true, - "requires": { - "@types/bn.js": "^5.1.0", - "web3-core": "1.7.4", - "web3-core-helpers": "1.7.4", - "web3-core-method": "1.7.4", - "web3-core-promievent": "1.7.4", - "web3-core-subscriptions": "1.7.4", - "web3-eth-abi": "1.7.4", - "web3-utils": "1.7.4" - }, - "dependencies": { - "ethereum-cryptography": { - "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, - "requires": { - "@types/pbkdf2": "^3.0.0", - "@types/secp256k1": "^4.0.1", - "blakejs": "^1.1.0", - "browserify-aes": "^1.2.0", - "bs58check": "^2.1.2", - "create-hash": "^1.2.0", - "create-hmac": "^1.1.7", - "hash.js": "^1.1.7", - "keccak": "^3.0.0", - "pbkdf2": "^3.0.17", - "randombytes": "^2.1.0", - "safe-buffer": "^5.1.2", - "scrypt-js": "^3.0.0", - "secp256k1": "^4.0.1", - "setimmediate": "^1.0.5" - } - }, - "ethereumjs-util": { - "version": "7.1.5", - "resolved": "https://registry.npmjs.org/ethereumjs-util/-/ethereumjs-util-7.1.5.tgz", - "integrity": "sha512-SDl5kKrQAudFBUe5OJM9Ac6WmMyYmXX/6sTmLZ3ffG2eY6ZIGBes3pEDxNN6V72WyOw4CPD5RomKdsa8DAAwLg==", - "dev": true, - "requires": { - "@types/bn.js": "^5.1.0", - "bn.js": "^5.1.2", - "create-hash": "^1.1.2", - "ethereum-cryptography": "^0.1.3", - "rlp": "^2.2.4" - } - }, - "web3-utils": { - "version": "1.7.4", - "resolved": "https://registry.npmjs.org/web3-utils/-/web3-utils-1.7.4.tgz", - "integrity": "sha512-acBdm6Evd0TEZRnChM/MCvGsMwYKmSh7OaUfNf5OKG0CIeGWD/6gqLOWIwmwSnre/2WrA1nKGId5uW2e5EfluA==", - "dev": true, - "requires": { - "bn.js": "^5.2.1", - "ethereum-bloom-filters": "^1.0.6", - "ethereumjs-util": "^7.1.0", - "ethjs-unit": "0.1.6", - "number-to-bn": "1.7.0", - "randombytes": "^2.1.0", - "utf8": "3.0.0" - } - } - } - }, - "web3-eth-ens": { - "version": "1.7.4", - "resolved": "https://registry.npmjs.org/web3-eth-ens/-/web3-eth-ens-1.7.4.tgz", - "integrity": "sha512-Gw5CVU1+bFXP5RVXTCqJOmHn71X2ghNk9VcEH+9PchLr0PrKbHTA3hySpsPco1WJAyK4t8SNQVlNr3+bJ6/WZA==", - "dev": true, - "requires": { - "content-hash": "^2.5.2", - "eth-ens-namehash": "2.0.8", - "web3-core": "1.7.4", - "web3-core-helpers": "1.7.4", - "web3-core-promievent": "1.7.4", - "web3-eth-abi": "1.7.4", - "web3-eth-contract": "1.7.4", - "web3-utils": "1.7.4" - }, - "dependencies": { - "ethereum-cryptography": { - "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, - "requires": { - "@types/pbkdf2": "^3.0.0", - "@types/secp256k1": "^4.0.1", - "blakejs": "^1.1.0", - "browserify-aes": "^1.2.0", - "bs58check": "^2.1.2", - "create-hash": "^1.2.0", - "create-hmac": "^1.1.7", - "hash.js": "^1.1.7", - "keccak": "^3.0.0", - "pbkdf2": "^3.0.17", - "randombytes": "^2.1.0", - "safe-buffer": "^5.1.2", - "scrypt-js": "^3.0.0", - "secp256k1": "^4.0.1", - "setimmediate": "^1.0.5" - } - }, - "ethereumjs-util": { - "version": "7.1.5", - "resolved": "https://registry.npmjs.org/ethereumjs-util/-/ethereumjs-util-7.1.5.tgz", - "integrity": "sha512-SDl5kKrQAudFBUe5OJM9Ac6WmMyYmXX/6sTmLZ3ffG2eY6ZIGBes3pEDxNN6V72WyOw4CPD5RomKdsa8DAAwLg==", - "dev": true, - "requires": { - "@types/bn.js": "^5.1.0", - "bn.js": "^5.1.2", - "create-hash": "^1.1.2", - "ethereum-cryptography": "^0.1.3", - "rlp": "^2.2.4" - } - }, - "web3-utils": { - "version": "1.7.4", - "resolved": "https://registry.npmjs.org/web3-utils/-/web3-utils-1.7.4.tgz", - "integrity": "sha512-acBdm6Evd0TEZRnChM/MCvGsMwYKmSh7OaUfNf5OKG0CIeGWD/6gqLOWIwmwSnre/2WrA1nKGId5uW2e5EfluA==", - "dev": true, - "requires": { - "bn.js": "^5.2.1", - "ethereum-bloom-filters": "^1.0.6", - "ethereumjs-util": "^7.1.0", - "ethjs-unit": "0.1.6", - "number-to-bn": "1.7.0", - "randombytes": "^2.1.0", - "utf8": "3.0.0" - } - } - } - }, - "web3-eth-iban": { - "version": "1.7.4", - "resolved": "https://registry.npmjs.org/web3-eth-iban/-/web3-eth-iban-1.7.4.tgz", - "integrity": "sha512-XyrsgWlZQMv5gRcjXMsNvAoCRvV5wN7YCfFV5+tHUCqN8g9T/o4XUS20vDWD0k4HNiAcWGFqT1nrls02MGZ08w==", - "dev": true, - "requires": { - "bn.js": "^5.2.1", - "web3-utils": "1.7.4" - }, - "dependencies": { - "ethereum-cryptography": { - "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, - "requires": { - "@types/pbkdf2": "^3.0.0", - "@types/secp256k1": "^4.0.1", - "blakejs": "^1.1.0", - "browserify-aes": "^1.2.0", - "bs58check": "^2.1.2", - "create-hash": "^1.2.0", - "create-hmac": "^1.1.7", - "hash.js": "^1.1.7", - "keccak": "^3.0.0", - "pbkdf2": "^3.0.17", - "randombytes": "^2.1.0", - "safe-buffer": "^5.1.2", - "scrypt-js": "^3.0.0", - "secp256k1": "^4.0.1", - "setimmediate": "^1.0.5" - } - }, - "ethereumjs-util": { - "version": "7.1.5", - "resolved": "https://registry.npmjs.org/ethereumjs-util/-/ethereumjs-util-7.1.5.tgz", - "integrity": "sha512-SDl5kKrQAudFBUe5OJM9Ac6WmMyYmXX/6sTmLZ3ffG2eY6ZIGBes3pEDxNN6V72WyOw4CPD5RomKdsa8DAAwLg==", - "dev": true, - "requires": { - "@types/bn.js": "^5.1.0", - "bn.js": "^5.1.2", - "create-hash": "^1.1.2", - "ethereum-cryptography": "^0.1.3", - "rlp": "^2.2.4" - } - }, - "web3-utils": { - "version": "1.7.4", - "resolved": "https://registry.npmjs.org/web3-utils/-/web3-utils-1.7.4.tgz", - "integrity": "sha512-acBdm6Evd0TEZRnChM/MCvGsMwYKmSh7OaUfNf5OKG0CIeGWD/6gqLOWIwmwSnre/2WrA1nKGId5uW2e5EfluA==", - "dev": true, - "requires": { - "bn.js": "^5.2.1", - "ethereum-bloom-filters": "^1.0.6", - "ethereumjs-util": "^7.1.0", - "ethjs-unit": "0.1.6", - "number-to-bn": "1.7.0", - "randombytes": "^2.1.0", - "utf8": "3.0.0" - } - } - } - }, - "web3-eth-personal": { - "version": "1.7.4", - "resolved": "https://registry.npmjs.org/web3-eth-personal/-/web3-eth-personal-1.7.4.tgz", - "integrity": "sha512-O10C1Hln5wvLQsDhlhmV58RhXo+GPZ5+W76frSsyIrkJWLtYQTCr5WxHtRC9sMD1idXLqODKKgI2DL+7xeZ0/g==", - "dev": true, - "requires": { - "@types/node": "^12.12.6", - "web3-core": "1.7.4", - "web3-core-helpers": "1.7.4", - "web3-core-method": "1.7.4", - "web3-net": "1.7.4", - "web3-utils": "1.7.4" - }, - "dependencies": { - "ethereum-cryptography": { - "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, - "requires": { - "@types/pbkdf2": "^3.0.0", - "@types/secp256k1": "^4.0.1", - "blakejs": "^1.1.0", - "browserify-aes": "^1.2.0", - "bs58check": "^2.1.2", - "create-hash": "^1.2.0", - "create-hmac": "^1.1.7", - "hash.js": "^1.1.7", - "keccak": "^3.0.0", - "pbkdf2": "^3.0.17", - "randombytes": "^2.1.0", - "safe-buffer": "^5.1.2", - "scrypt-js": "^3.0.0", - "secp256k1": "^4.0.1", - "setimmediate": "^1.0.5" - } - }, - "ethereumjs-util": { - "version": "7.1.5", - "resolved": "https://registry.npmjs.org/ethereumjs-util/-/ethereumjs-util-7.1.5.tgz", - "integrity": "sha512-SDl5kKrQAudFBUe5OJM9Ac6WmMyYmXX/6sTmLZ3ffG2eY6ZIGBes3pEDxNN6V72WyOw4CPD5RomKdsa8DAAwLg==", - "dev": true, - "requires": { - "@types/bn.js": "^5.1.0", - "bn.js": "^5.1.2", - "create-hash": "^1.1.2", - "ethereum-cryptography": "^0.1.3", - "rlp": "^2.2.4" - } - }, - "web3-utils": { - "version": "1.7.4", - "resolved": "https://registry.npmjs.org/web3-utils/-/web3-utils-1.7.4.tgz", - "integrity": "sha512-acBdm6Evd0TEZRnChM/MCvGsMwYKmSh7OaUfNf5OKG0CIeGWD/6gqLOWIwmwSnre/2WrA1nKGId5uW2e5EfluA==", - "dev": true, - "requires": { - "bn.js": "^5.2.1", - "ethereum-bloom-filters": "^1.0.6", - "ethereumjs-util": "^7.1.0", - "ethjs-unit": "0.1.6", - "number-to-bn": "1.7.0", - "randombytes": "^2.1.0", - "utf8": "3.0.0" - } - } - } - }, - "web3-net": { - "version": "1.7.4", - "resolved": "https://registry.npmjs.org/web3-net/-/web3-net-1.7.4.tgz", - "integrity": "sha512-d2Gj+DIARHvwIdmxFQ4PwAAXZVxYCR2lET0cxz4KXbE5Og3DNjJi+MoPkX+WqoUXqimu/EOd4Cd+7gefqVAFDg==", - "dev": true, - "requires": { - "web3-core": "1.7.4", - "web3-core-method": "1.7.4", - "web3-utils": "1.7.4" - }, - "dependencies": { - "ethereum-cryptography": { - "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, - "requires": { - "@types/pbkdf2": "^3.0.0", - "@types/secp256k1": "^4.0.1", - "blakejs": "^1.1.0", - "browserify-aes": "^1.2.0", - "bs58check": "^2.1.2", - "create-hash": "^1.2.0", - "create-hmac": "^1.1.7", - "hash.js": "^1.1.7", - "keccak": "^3.0.0", - "pbkdf2": "^3.0.17", - "randombytes": "^2.1.0", - "safe-buffer": "^5.1.2", - "scrypt-js": "^3.0.0", - "secp256k1": "^4.0.1", - "setimmediate": "^1.0.5" - } - }, - "ethereumjs-util": { - "version": "7.1.5", - "resolved": "https://registry.npmjs.org/ethereumjs-util/-/ethereumjs-util-7.1.5.tgz", - "integrity": "sha512-SDl5kKrQAudFBUe5OJM9Ac6WmMyYmXX/6sTmLZ3ffG2eY6ZIGBes3pEDxNN6V72WyOw4CPD5RomKdsa8DAAwLg==", - "dev": true, - "requires": { - "@types/bn.js": "^5.1.0", - "bn.js": "^5.1.2", - "create-hash": "^1.1.2", - "ethereum-cryptography": "^0.1.3", - "rlp": "^2.2.4" - } - }, - "web3-utils": { - "version": "1.7.4", - "resolved": "https://registry.npmjs.org/web3-utils/-/web3-utils-1.7.4.tgz", - "integrity": "sha512-acBdm6Evd0TEZRnChM/MCvGsMwYKmSh7OaUfNf5OKG0CIeGWD/6gqLOWIwmwSnre/2WrA1nKGId5uW2e5EfluA==", - "dev": true, - "requires": { - "bn.js": "^5.2.1", - "ethereum-bloom-filters": "^1.0.6", - "ethereumjs-util": "^7.1.0", - "ethjs-unit": "0.1.6", - "number-to-bn": "1.7.0", - "randombytes": "^2.1.0", - "utf8": "3.0.0" - } - } + "utf8": "3.0.0" + }, + "engines": { + "node": ">=8.0.0" } }, - "web3-providers-http": { + "node_modules/web3-providers-http": { "version": "1.7.4", "resolved": "https://registry.npmjs.org/web3-providers-http/-/web3-providers-http-1.7.4.tgz", "integrity": "sha512-AU+/S+49rcogUER99TlhW+UBMk0N2DxvN54CJ2pK7alc2TQ7+cprNPLHJu4KREe8ndV0fT6JtWUfOMyTvl+FRA==", "dev": true, - "requires": { + "dependencies": { "web3-core-helpers": "1.7.4", "xhr2-cookies": "1.1.0" + }, + "engines": { + "node": ">=8.0.0" } }, - "web3-providers-ipc": { + "node_modules/web3-providers-ipc": { "version": "1.7.4", "resolved": "https://registry.npmjs.org/web3-providers-ipc/-/web3-providers-ipc-1.7.4.tgz", "integrity": "sha512-jhArOZ235dZy8fS8090t60nTxbd1ap92ibQw5xIrAQ9m7LcZKNfmLAQUVsD+3dTFvadRMi6z1vCO7zRi84gWHw==", "dev": true, - "requires": { + "dependencies": { "oboe": "2.1.5", "web3-core-helpers": "1.7.4" + }, + "engines": { + "node": ">=8.0.0" } }, - "web3-providers-ws": { + "node_modules/web3-providers-ws": { "version": "1.7.4", "resolved": "https://registry.npmjs.org/web3-providers-ws/-/web3-providers-ws-1.7.4.tgz", "integrity": "sha512-g72X77nrcHMFU8hRzQJzfgi/072n8dHwRCoTw+WQrGp+XCQ71fsk2qIu3Tp+nlp5BPn8bRudQbPblVm2uT4myQ==", "dev": true, - "requires": { + "dependencies": { "eventemitter3": "4.0.4", "web3-core-helpers": "1.7.4", "websocket": "^1.0.32" + }, + "engines": { + "node": ">=8.0.0" } }, - "web3-shh": { + "node_modules/web3-shh": { "version": "1.7.4", "resolved": "https://registry.npmjs.org/web3-shh/-/web3-shh-1.7.4.tgz", "integrity": "sha512-mlSZxSYcMkuMCxqhTYnZkUdahZ11h+bBv/8TlkXp/IHpEe4/Gg+KAbmfudakq3EzG/04z70XQmPgWcUPrsEJ+A==", "dev": true, - "requires": { + "hasInstallScript": true, + "dependencies": { "web3-core": "1.7.4", "web3-core-method": "1.7.4", "web3-core-subscriptions": "1.7.4", "web3-net": "1.7.4" + }, + "engines": { + "node": ">=8.0.0" } }, - "web3-utils": { + "node_modules/web3-utils": { "version": "1.10.0", "resolved": "https://registry.npmjs.org/web3-utils/-/web3-utils-1.10.0.tgz", "integrity": "sha512-kSaCM0uMcZTNUSmn5vMEhlo02RObGNRRCkdX0V9UTAU0+lrvn0HSaudyCo6CQzuXUsnuY2ERJGCGPfeWmv19Rg==", - "requires": { + "dependencies": { "bn.js": "^5.2.1", "ethereum-bloom-filters": "^1.0.6", "ethereumjs-util": "^7.1.0", @@ -43692,54 +23687,39 @@ "randombytes": "^2.1.0", "utf8": "3.0.0" }, + "engines": { + "node": ">=8.0.0" + } + }, + "node_modules/web3/node_modules/web3-utils": { + "version": "1.7.4", + "resolved": "https://registry.npmjs.org/web3-utils/-/web3-utils-1.7.4.tgz", + "integrity": "sha512-acBdm6Evd0TEZRnChM/MCvGsMwYKmSh7OaUfNf5OKG0CIeGWD/6gqLOWIwmwSnre/2WrA1nKGId5uW2e5EfluA==", + "dev": true, "dependencies": { - "ethereum-cryptography": { - "version": "0.1.3", - "resolved": "https://registry.npmjs.org/ethereum-cryptography/-/ethereum-cryptography-0.1.3.tgz", - "integrity": "sha512-w8/4x1SGGzc+tO97TASLja6SLd3fRIK2tLVcV2Gx4IB21hE19atll5Cq9o3d0ZmAYC/8aw0ipieTSiekAea4SQ==", - "requires": { - "@types/pbkdf2": "^3.0.0", - "@types/secp256k1": "^4.0.1", - "blakejs": "^1.1.0", - "browserify-aes": "^1.2.0", - "bs58check": "^2.1.2", - "create-hash": "^1.2.0", - "create-hmac": "^1.1.7", - "hash.js": "^1.1.7", - "keccak": "^3.0.0", - "pbkdf2": "^3.0.17", - "randombytes": "^2.1.0", - "safe-buffer": "^5.1.2", - "scrypt-js": "^3.0.0", - "secp256k1": "^4.0.1", - "setimmediate": "^1.0.5" - } - }, - "ethereumjs-util": { - "version": "7.1.5", - "resolved": "https://registry.npmjs.org/ethereumjs-util/-/ethereumjs-util-7.1.5.tgz", - "integrity": "sha512-SDl5kKrQAudFBUe5OJM9Ac6WmMyYmXX/6sTmLZ3ffG2eY6ZIGBes3pEDxNN6V72WyOw4CPD5RomKdsa8DAAwLg==", - "requires": { - "@types/bn.js": "^5.1.0", - "bn.js": "^5.1.2", - "create-hash": "^1.1.2", - "ethereum-cryptography": "^0.1.3", - "rlp": "^2.2.4" - } - } + "bn.js": "^5.2.1", + "ethereum-bloom-filters": "^1.0.6", + "ethereumjs-util": "^7.1.0", + "ethjs-unit": "0.1.6", + "number-to-bn": "1.7.0", + "randombytes": "^2.1.0", + "utf8": "3.0.0" + }, + "engines": { + "node": ">=8.0.0" } }, - "webidl-conversions": { + "node_modules/webidl-conversions": { "version": "3.0.1", "resolved": "https://registry.npmjs.org/webidl-conversions/-/webidl-conversions-3.0.1.tgz", "integrity": "sha512-2JAn3z8AR6rjK8Sm8orRC0h/bcl/DqL7tRPdGZ4I1CjdF+EaMLmYxBHyXuKL849eucPFhvBoxMsflfOb8kxaeQ==" }, - "websocket": { + "node_modules/websocket": { "version": "1.0.34", "resolved": "https://registry.npmjs.org/websocket/-/websocket-1.0.34.tgz", "integrity": "sha512-PRDso2sGwF6kM75QykIesBijKSVceR6jL2G8NGYyq2XrItNC2P5/qL5XeR056GhA+Ly7JMFvJb9I312mJfmqnQ==", "dev": true, - "requires": { + "dependencies": { "bufferutil": "^4.0.1", "debug": "^2.2.0", "es5-ext": "^0.10.50", @@ -43747,167 +23727,216 @@ "utf-8-validate": "^5.0.2", "yaeti": "^0.0.6" }, + "engines": { + "node": ">=4.0.0" + } + }, + "node_modules/websocket/node_modules/debug": { + "version": "2.6.9", + "resolved": "https://registry.npmjs.org/debug/-/debug-2.6.9.tgz", + "integrity": "sha512-bC7ElrdJaJnPbAP+1EotYvqZsb3ecl5wi6Bfi6BJTUcNowp6cvspg0jXznRTKDjm/E7AdgFBVeAPVMNcKGsHMA==", + "dev": true, "dependencies": { - "debug": { - "version": "2.6.9", - "resolved": "https://registry.npmjs.org/debug/-/debug-2.6.9.tgz", - "integrity": "sha512-bC7ElrdJaJnPbAP+1EotYvqZsb3ecl5wi6Bfi6BJTUcNowp6cvspg0jXznRTKDjm/E7AdgFBVeAPVMNcKGsHMA==", - "dev": true, - "requires": { - "ms": "2.0.0" - } - }, - "ms": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/ms/-/ms-2.0.0.tgz", - "integrity": "sha512-Tpp60P6IUJDTuOq/5Z8cdskzJujfwqfOTkrwIwj7IRISpnkJnT6SyJ4PCPnGMoFjC9ddhal5KVIYtAt97ix05A==", - "dev": true - } + "ms": "2.0.0" } }, - "whatwg-url": { + "node_modules/websocket/node_modules/ms": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/ms/-/ms-2.0.0.tgz", + "integrity": "sha512-Tpp60P6IUJDTuOq/5Z8cdskzJujfwqfOTkrwIwj7IRISpnkJnT6SyJ4PCPnGMoFjC9ddhal5KVIYtAt97ix05A==", + "dev": true + }, + "node_modules/whatwg-url": { "version": "5.0.0", "resolved": "https://registry.npmjs.org/whatwg-url/-/whatwg-url-5.0.0.tgz", "integrity": "sha512-saE57nupxk6v3HY35+jzBwYa0rKSy0XR8JSxZPwgLr7ys0IBzhGviA1/TUGJLmSVqs8pb9AnvICXEuOHLprYTw==", - "requires": { + "dependencies": { "tr46": "~0.0.3", "webidl-conversions": "^3.0.0" } }, - "which": { + "node_modules/which": { "version": "2.0.2", "resolved": "https://registry.npmjs.org/which/-/which-2.0.2.tgz", "integrity": "sha512-BLI3Tl1TW3Pvl70l3yq3Y64i+awpwXqsGBYWkkqMtnbXgrMD+yj7rhW0kuEDxzJaYXGjEW5ogapKNMEKNMjibA==", "dev": true, - "requires": { + "dependencies": { "isexe": "^2.0.0" + }, + "bin": { + "node-which": "bin/node-which" + }, + "engines": { + "node": ">= 8" } }, - "which-boxed-primitive": { + "node_modules/which-boxed-primitive": { "version": "1.0.2", "resolved": "https://registry.npmjs.org/which-boxed-primitive/-/which-boxed-primitive-1.0.2.tgz", "integrity": "sha512-bwZdv0AKLpplFY2KZRX6TvyuN7ojjr7lwkg6ml0roIy9YeuSr7JS372qlNW18UQYzgYK9ziGcerWqZOmEn9VNg==", "dev": true, - "requires": { + "dependencies": { "is-bigint": "^1.0.1", "is-boolean-object": "^1.1.0", "is-number-object": "^1.0.4", "is-string": "^1.0.5", "is-symbol": "^1.0.3" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" } }, - "which-module": { + "node_modules/which-module": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/which-module/-/which-module-1.0.0.tgz", "integrity": "sha512-F6+WgncZi/mJDrammbTuHe1q0R5hOXv/mBaiNA2TCNT/LTHusX0V+CJnj9XT8ki5ln2UZyyddDgHfCzyrOH7MQ==", "dev": true }, - "which-typed-array": { + "node_modules/which-typed-array": { "version": "1.1.11", "resolved": "https://registry.npmjs.org/which-typed-array/-/which-typed-array-1.1.11.tgz", "integrity": "sha512-qe9UWWpkeG5yzZ0tNYxDmd7vo58HDBc39mZ0xWWpolAGADdFOzkfamWLDxkOWcvHQKVmdTyQdLD4NOfjLWTKew==", "dev": true, - "requires": { + "dependencies": { "available-typed-arrays": "^1.0.5", "call-bind": "^1.0.2", "for-each": "^0.3.3", "gopd": "^1.0.1", "has-tostringtag": "^1.0.0" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" } }, - "wide-align": { + "node_modules/wide-align": { "version": "1.1.3", "resolved": "https://registry.npmjs.org/wide-align/-/wide-align-1.1.3.tgz", "integrity": "sha512-QGkOQc8XL6Bt5PwnsExKBPuMKBxnGxWWW3fU55Xt4feHozMUhdUMaBCk290qpm/wG5u/RSKzwdAC4i51YigihA==", "dev": true, - "requires": { + "dependencies": { "string-width": "^1.0.2 || 2" } }, - "window-size": { + "node_modules/window-size": { "version": "0.2.0", "resolved": "https://registry.npmjs.org/window-size/-/window-size-0.2.0.tgz", "integrity": "sha512-UD7d8HFA2+PZsbKyaOCEy8gMh1oDtHgJh1LfgjQ4zVXmYjAT/kvz3PueITKuqDiIXQe7yzpPnxX3lNc+AhQMyw==", - "dev": true + "dev": true, + "bin": { + "window-size": "cli.js" + }, + "engines": { + "node": ">= 0.10.0" + } }, - "word-wrap": { + "node_modules/word-wrap": { "version": "1.2.4", "resolved": "https://registry.npmjs.org/word-wrap/-/word-wrap-1.2.4.tgz", "integrity": "sha512-2V81OA4ugVo5pRo46hAoD2ivUJx8jXmWXfUkY4KFNw0hEptvN0QfH3K4nHiwzGeKl5rFKedV48QVoqYavy4YpA==", - "dev": true + "dev": true, + "engines": { + "node": ">=0.10.0" + } }, - "wordwrap": { + "node_modules/wordwrap": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/wordwrap/-/wordwrap-1.0.0.tgz", "integrity": "sha512-gvVzJFlPycKc5dZN4yPkP8w7Dc37BtP1yczEneOb4uq34pXZcvrtRTmWV8W+Ume+XCxKgbjM+nevkyFPMybd4Q==", "dev": true }, - "workerpool": { + "node_modules/workerpool": { "version": "6.2.1", "resolved": "https://registry.npmjs.org/workerpool/-/workerpool-6.2.1.tgz", "integrity": "sha512-ILEIE97kDZvF9Wb9f6h5aXK4swSlKGUcOEGiIYb2OOu/IrDU9iwj0fD//SsA6E5ibwJxpEvhullJY4Sl4GcpAw==", "dev": true }, - "wrap-ansi": { + "node_modules/wrap-ansi": { "version": "7.0.0", "resolved": "https://registry.npmjs.org/wrap-ansi/-/wrap-ansi-7.0.0.tgz", "integrity": "sha512-YVGIj2kamLSTxw6NsZjoBxfSwsn0ycdesmc4p+Q21c5zPuZ1pl+NfxVdxPtdHvmNVOQ6XSYG4AUtyt/Fi7D16Q==", "dev": true, - "requires": { + "dependencies": { "ansi-styles": "^4.0.0", "string-width": "^4.1.0", "strip-ansi": "^6.0.0" }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/chalk/wrap-ansi?sponsor=1" + } + }, + "node_modules/wrap-ansi/node_modules/is-fullwidth-code-point": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/is-fullwidth-code-point/-/is-fullwidth-code-point-3.0.0.tgz", + "integrity": "sha512-zymm5+u+sCsSWyD9qNaejV3DFvhCKclKdizYaJUuHA83RLjb7nSuGnddCHGv0hk+KY7BMAlsWeK4Ueg6EV6XQg==", + "dev": true, + "engines": { + "node": ">=8" + } + }, + "node_modules/wrap-ansi/node_modules/string-width": { + "version": "4.2.3", + "resolved": "https://registry.npmjs.org/string-width/-/string-width-4.2.3.tgz", + "integrity": "sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g==", + "dev": true, "dependencies": { - "is-fullwidth-code-point": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/is-fullwidth-code-point/-/is-fullwidth-code-point-3.0.0.tgz", - "integrity": "sha512-zymm5+u+sCsSWyD9qNaejV3DFvhCKclKdizYaJUuHA83RLjb7nSuGnddCHGv0hk+KY7BMAlsWeK4Ueg6EV6XQg==", - "dev": true - }, - "string-width": { - "version": "4.2.3", - "resolved": "https://registry.npmjs.org/string-width/-/string-width-4.2.3.tgz", - "integrity": "sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g==", - "dev": true, - "requires": { - "emoji-regex": "^8.0.0", - "is-fullwidth-code-point": "^3.0.0", - "strip-ansi": "^6.0.1" - } - } + "emoji-regex": "^8.0.0", + "is-fullwidth-code-point": "^3.0.0", + "strip-ansi": "^6.0.1" + }, + "engines": { + "node": ">=8" } }, - "wrappy": { + "node_modules/wrappy": { "version": "1.0.2", "resolved": "https://registry.npmjs.org/wrappy/-/wrappy-1.0.2.tgz", "integrity": "sha512-l4Sp/DRseor9wL6EvV2+TuQn63dMkPjZ/sp9XkghTEbV9KlPS1xUsZ3u7/IQO4wxtcFB4bgpQPRcR3QCvezPcQ==", "dev": true }, - "ws": { + "node_modules/ws": { "version": "7.4.6", "resolved": "https://registry.npmjs.org/ws/-/ws-7.4.6.tgz", "integrity": "sha512-YmhHDO4MzaDLB+M9ym/mDA5z0naX8j7SIlT8f8z+I0VtzsRbekxEutHSme7NPS2qE8StCYQNUnfWdXta/Yu85A==", - "requires": {} + "engines": { + "node": ">=8.3.0" + }, + "peerDependencies": { + "bufferutil": "^4.0.1", + "utf-8-validate": "^5.0.2" + }, + "peerDependenciesMeta": { + "bufferutil": { + "optional": true + }, + "utf-8-validate": { + "optional": true + } + } }, - "xhr": { + "node_modules/xhr": { "version": "2.6.0", "resolved": "https://registry.npmjs.org/xhr/-/xhr-2.6.0.tgz", "integrity": "sha512-/eCGLb5rxjx5e3mF1A7s+pLlR6CGyqWN91fv1JgER5mVWg1MZmlhBvy9kjcsOdRk8RrIujotWyJamfyrp+WIcA==", "dev": true, - "requires": { + "dependencies": { "global": "~4.4.0", "is-function": "^1.0.1", "parse-headers": "^2.0.0", "xtend": "^4.0.0" } }, - "xhr-request": { + "node_modules/xhr-request": { "version": "1.1.0", "resolved": "https://registry.npmjs.org/xhr-request/-/xhr-request-1.1.0.tgz", "integrity": "sha512-Y7qzEaR3FDtL3fP30k9wO/e+FBnBByZeybKOhASsGP30NIkRAAkKD/sCnLvgEfAIEC1rcmK7YG8f4oEnIrrWzA==", "dev": true, - "requires": { + "dependencies": { "buffer-to-arraybuffer": "^0.0.5", "object-assign": "^4.1.1", "query-string": "^5.0.1", @@ -43917,66 +23946,81 @@ "xhr": "^2.0.4" } }, - "xhr-request-promise": { + "node_modules/xhr-request-promise": { "version": "0.1.3", "resolved": "https://registry.npmjs.org/xhr-request-promise/-/xhr-request-promise-0.1.3.tgz", "integrity": "sha512-YUBytBsuwgitWtdRzXDDkWAXzhdGB8bYm0sSzMPZT7Z2MBjMSTHFsyCT1yCRATY+XC69DUrQraRAEgcoCRaIPg==", "dev": true, - "requires": { + "dependencies": { "xhr-request": "^1.1.0" } }, - "xhr2-cookies": { + "node_modules/xhr2-cookies": { "version": "1.1.0", "resolved": "https://registry.npmjs.org/xhr2-cookies/-/xhr2-cookies-1.1.0.tgz", "integrity": "sha512-hjXUA6q+jl/bd8ADHcVfFsSPIf+tyLIjuO9TwJC9WI6JP2zKcS7C+p56I9kCLLsaCiNT035iYvEUUzdEFj/8+g==", "dev": true, - "requires": { + "dependencies": { "cookiejar": "^2.1.1" } }, - "xmlhttprequest": { + "node_modules/xmlhttprequest": { "version": "1.8.0", "resolved": "https://registry.npmjs.org/xmlhttprequest/-/xmlhttprequest-1.8.0.tgz", "integrity": "sha512-58Im/U0mlVBLM38NdZjHyhuMtCqa61469k2YP/AaPbvCoV9aQGUpbJBj1QRm2ytRiVQBD/fsw7L2bJGDVQswBA==", - "dev": true + "dev": true, + "engines": { + "node": ">=0.4.0" + } }, - "xtend": { + "node_modules/xtend": { "version": "4.0.2", "resolved": "https://registry.npmjs.org/xtend/-/xtend-4.0.2.tgz", "integrity": "sha512-LKYU1iAXJXUgAXn9URjiu+MWhyUXHsvfp7mcuYm9dSUKK0/CjtrUwFAxD82/mCWbtLsGjFIad0wIsod4zrTAEQ==", - "dev": true + "dev": true, + "engines": { + "node": ">=0.4" + } }, - "y18n": { + "node_modules/y18n": { "version": "5.0.8", "resolved": "https://registry.npmjs.org/y18n/-/y18n-5.0.8.tgz", "integrity": "sha512-0pfFzegeDWJHJIAmTLRP2DwHjdF5s7jo9tuztdQxAhINCdvS+3nGINqPd00AphqJR/0LhANUS6/+7SCb98YOfA==", - "dev": true + "dev": true, + "engines": { + "node": ">=10" + } }, - "yaeti": { + "node_modules/yaeti": { "version": "0.0.6", "resolved": "https://registry.npmjs.org/yaeti/-/yaeti-0.0.6.tgz", "integrity": "sha512-MvQa//+KcZCUkBTIC9blM+CU9J2GzuTytsOUwf2lidtvkx/6gnEp1QvJv34t9vdjhFmha/mUiNDbN0D0mJWdug==", - "dev": true + "dev": true, + "engines": { + "node": ">=0.10.32" + } }, - "yallist": { + "node_modules/yallist": { "version": "3.1.1", "resolved": "https://registry.npmjs.org/yallist/-/yallist-3.1.1.tgz", "integrity": "sha512-a4UGQaWPH59mOXUYnAG2ewncQS4i4F43Tv3JoAM+s2VDAmS9NsK8GpDMLrCHPksFT7h3K6TOoUNn2pb7RoXx4g==", "dev": true }, - "yaml": { + "node_modules/yaml": { "version": "1.10.2", "resolved": "https://registry.npmjs.org/yaml/-/yaml-1.10.2.tgz", "integrity": "sha512-r3vXyErRCYJ7wg28yvBY5VSoAF8ZvlcW9/BwUzEtUsjvX/DKs24dIkuwjtuprwJJHsbyUbLApepYTR1BN4uHrg==", - "dev": true + "dev": true, + "engines": { + "node": ">= 6" + } }, - "yargs": { + "node_modules/yargs": { "version": "16.2.0", "resolved": "https://registry.npmjs.org/yargs/-/yargs-16.2.0.tgz", "integrity": "sha512-D1mvvtDG0L5ft/jGWkLpG1+m0eQxOfaBvTNELraWj22wSVUMWxZUvYgJYcKh6jGGIkJFhH4IZPQhR4TKpc8mBw==", "dev": true, - "requires": { + "dependencies": { "cliui": "^7.0.2", "escalade": "^3.1.1", "get-caller-file": "^2.0.5", @@ -43985,55 +24029,77 @@ "y18n": "^5.0.5", "yargs-parser": "^20.2.2" }, - "dependencies": { - "is-fullwidth-code-point": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/is-fullwidth-code-point/-/is-fullwidth-code-point-3.0.0.tgz", - "integrity": "sha512-zymm5+u+sCsSWyD9qNaejV3DFvhCKclKdizYaJUuHA83RLjb7nSuGnddCHGv0hk+KY7BMAlsWeK4Ueg6EV6XQg==", - "dev": true - }, - "string-width": { - "version": "4.2.3", - "resolved": "https://registry.npmjs.org/string-width/-/string-width-4.2.3.tgz", - "integrity": "sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g==", - "dev": true, - "requires": { - "emoji-regex": "^8.0.0", - "is-fullwidth-code-point": "^3.0.0", - "strip-ansi": "^6.0.1" - } - } + "engines": { + "node": ">=10" } }, - "yargs-parser": { + "node_modules/yargs-parser": { "version": "20.2.4", "resolved": "https://registry.npmjs.org/yargs-parser/-/yargs-parser-20.2.4.tgz", "integrity": "sha512-WOkpgNhPTlE73h4VFAFsOnomJVaovO8VqLDzy5saChRBFQFBoMYirowyW+Q9HB4HFF4Z7VZTiG3iSzJJA29yRA==", - "dev": true + "dev": true, + "engines": { + "node": ">=10" + } }, - "yargs-unparser": { + "node_modules/yargs-unparser": { "version": "2.0.0", "resolved": "https://registry.npmjs.org/yargs-unparser/-/yargs-unparser-2.0.0.tgz", "integrity": "sha512-7pRTIA9Qc1caZ0bZ6RYRGbHJthJWuakf+WmHK0rVeLkNrrGhfoabBNdue6kdINI6r4if7ocq9aD/n7xwKOdzOA==", "dev": true, - "requires": { + "dependencies": { "camelcase": "^6.0.0", "decamelize": "^4.0.0", "flat": "^5.0.2", "is-plain-obj": "^2.1.0" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/yargs/node_modules/is-fullwidth-code-point": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/is-fullwidth-code-point/-/is-fullwidth-code-point-3.0.0.tgz", + "integrity": "sha512-zymm5+u+sCsSWyD9qNaejV3DFvhCKclKdizYaJUuHA83RLjb7nSuGnddCHGv0hk+KY7BMAlsWeK4Ueg6EV6XQg==", + "dev": true, + "engines": { + "node": ">=8" + } + }, + "node_modules/yargs/node_modules/string-width": { + "version": "4.2.3", + "resolved": "https://registry.npmjs.org/string-width/-/string-width-4.2.3.tgz", + "integrity": "sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g==", + "dev": true, + "dependencies": { + "emoji-regex": "^8.0.0", + "is-fullwidth-code-point": "^3.0.0", + "strip-ansi": "^6.0.1" + }, + "engines": { + "node": ">=8" } }, - "yn": { + "node_modules/yn": { "version": "3.1.1", "resolved": "https://registry.npmjs.org/yn/-/yn-3.1.1.tgz", "integrity": "sha512-Ux4ygGWsu2c7isFWe8Yu1YluJmqVhxqK2cLXNQA5AcC3QfbGNpM7fu0Y8b/z16pXLnFxZYvWhd3fhBY9DLmC6Q==", - "dev": true + "dev": true, + "engines": { + "node": ">=6" + } }, - "yocto-queue": { + "node_modules/yocto-queue": { "version": "0.1.0", "resolved": "https://registry.npmjs.org/yocto-queue/-/yocto-queue-0.1.0.tgz", "integrity": "sha512-rVksvsnNCdJ/ohGc6xgPwyN8eheCxsiLM8mxuE/t/mOVqJewPuO1miLpTHQiRgTKCLexL4MeAFVagts7HmNZ2Q==", - "dev": true + "dev": true, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } } } } From 6a5257d701b0446ff6278b718b28b198b014c04b Mon Sep 17 00:00:00 2001 From: TheDZhon Date: Wed, 17 Apr 2024 08:30:16 +0000 Subject: [PATCH 069/148] fix: Make storage-layout up to date --- .storage-layout | 74 +++++++++++++++++++++++++++++++++++++++++++++---- 1 file changed, 68 insertions(+), 6 deletions(-) diff --git a/.storage-layout b/.storage-layout index 328c7a31..e5a497a7 100644 --- a/.storage-layout +++ b/.storage-layout @@ -33,6 +33,19 @@ | balanceOf | mapping(address => uint256) | 1 | 0 | 32 | contracts/token/ERC20Bridged.sol:ERC20Bridged | | allowance | mapping(address => mapping(address => uint256)) | 2 | 0 | 32 | contracts/token/ERC20Bridged.sol:ERC20Bridged | +======================= +➡ ERC20BridgedPermit +======================= + +| Name | Type | Slot | Offset | Bytes | Contract | +|------------------|-------------------------------------------------|------|--------|-------|-----------------------------------------------------------| +| totalSupply | uint256 | 0 | 0 | 32 | contracts/token/ERC20BridgedPermit.sol:ERC20BridgedPermit | +| balanceOf | mapping(address => uint256) | 1 | 0 | 32 | contracts/token/ERC20BridgedPermit.sol:ERC20BridgedPermit | +| allowance | mapping(address => mapping(address => uint256)) | 2 | 0 | 32 | contracts/token/ERC20BridgedPermit.sol:ERC20BridgedPermit | +| _nameFallback | string | 3 | 0 | 32 | contracts/token/ERC20BridgedPermit.sol:ERC20BridgedPermit | +| _versionFallback | string | 4 | 0 | 32 | contracts/token/ERC20BridgedPermit.sol:ERC20BridgedPermit | +| noncesByAddress | mapping(address => uint256) | 5 | 0 | 32 | contracts/token/ERC20BridgedPermit.sol:ERC20BridgedPermit | + ======================= ➡ ERC20Core ======================= @@ -51,20 +64,44 @@ |------|------|------|--------|-------|----------| ======================= -➡ L1ERC20TokenBridge +➡ ERC20RebasableBridged ======================= -| Name | Type | Slot | Offset | Bytes | Contract | -|--------|---------------------------------------------------|------|--------|-------|--------------------------------------------------------------| -| _roles | mapping(bytes32 => struct AccessControl.RoleData) | 0 | 0 | 32 | contracts/optimism/L1ERC20TokenBridge.sol:L1ERC20TokenBridge | +| Name | Type | Slot | Offset | Bytes | Contract | +|------|------|------|--------|-------|----------| + +======================= +➡ ERC20RebasableBridgedPermit +======================= + +| Name | Type | Slot | Offset | Bytes | Contract | +|------------------|-----------------------------|------|--------|-------|-----------------------------------------------------------------------------| +| _nameFallback | string | 0 | 0 | 32 | contracts/token/ERC20RebasableBridgedPermit.sol:ERC20RebasableBridgedPermit | +| _versionFallback | string | 1 | 0 | 32 | contracts/token/ERC20RebasableBridgedPermit.sol:ERC20RebasableBridgedPermit | +| noncesByAddress | mapping(address => uint256) | 2 | 0 | 32 | contracts/token/ERC20RebasableBridgedPermit.sol:ERC20RebasableBridgedPermit | ======================= -➡ L2ERC20TokenBridge +➡ L1LidoTokensBridge ======================= | Name | Type | Slot | Offset | Bytes | Contract | |--------|---------------------------------------------------|------|--------|-------|--------------------------------------------------------------| -| _roles | mapping(bytes32 => struct AccessControl.RoleData) | 0 | 0 | 32 | contracts/optimism/L2ERC20TokenBridge.sol:L2ERC20TokenBridge | +| _roles | mapping(bytes32 => struct AccessControl.RoleData) | 0 | 0 | 32 | contracts/optimism/L1LidoTokensBridge.sol:L1LidoTokensBridge | + +======================= +➡ L2ERC20ExtendedTokensBridge +======================= + +| Name | Type | Slot | Offset | Bytes | Contract | +|--------|---------------------------------------------------|------|--------|-------|--------------------------------------------------------------------------------| +| _roles | mapping(bytes32 => struct AccessControl.RoleData) | 0 | 0 | 32 | contracts/optimism/L2ERC20ExtendedTokensBridge.sol:L2ERC20ExtendedTokensBridge | + +======================= +➡ OpStackTokenRatePusher +======================= + +| Name | Type | Slot | Offset | Bytes | Contract | +|------|------|------|--------|-------|----------| ======================= ➡ OssifiableProxy @@ -72,3 +109,28 @@ | Name | Type | Slot | Offset | Bytes | Contract | |------|------|------|--------|-------|----------| + +======================= +➡ RebasableAndNonRebasableTokens +======================= + +| Name | Type | Slot | Offset | Bytes | Contract | +|------|------|------|--------|-------|----------| + +======================= +➡ TokenRateNotifier +======================= + +| Name | Type | Slot | Offset | Bytes | Contract | +|-----------|-----------|------|--------|-------|--------------------------------------------------------| +| _owner | address | 0 | 0 | 20 | contracts/lido/TokenRateNotifier.sol:TokenRateNotifier | +| observers | address[] | 1 | 0 | 32 | contracts/lido/TokenRateNotifier.sol:TokenRateNotifier | + +======================= +➡ TokenRateOracle +======================= + +| Name | Type | Slot | Offset | Bytes | Contract | +|-----------------|---------|------|--------|-------|--------------------------------------------------------| +| tokenRate | uint256 | 0 | 0 | 32 | contracts/optimism/TokenRateOracle.sol:TokenRateOracle | +| rateL1Timestamp | uint256 | 1 | 0 | 32 | contracts/optimism/TokenRateOracle.sol:TokenRateOracle | From ea6fcd7c2c2b7775bc1f5e9f4aa3c697b998c1e5 Mon Sep 17 00:00:00 2001 From: Eugene M Date: Wed, 17 Apr 2024 11:41:52 +0300 Subject: [PATCH 070/148] fix: update npm locks --- package-lock.json | 5043 +++++++++++++++++++-------------------------- package.json | 4 +- 2 files changed, 2167 insertions(+), 2880 deletions(-) diff --git a/package-lock.json b/package-lock.json index 89ad2558..eb162453 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1,12 +1,12 @@ { - "name": "lido-l2", - "version": "1.0.0", + "name": "lido-l2-optimism", + "version": "2.0.0", "lockfileVersion": 3, "requires": true, "packages": { "": { - "name": "lido-l2", - "version": "1.0.0", + "name": "lido-l2-optimism", + "version": "2.0.0", "license": "ISC", "dependencies": { "@eth-optimism/sdk": "3.2.3", @@ -69,23 +69,24 @@ } }, "node_modules/@babel/helper-validator-identifier": { - "version": "7.22.5", - "resolved": "https://registry.npmjs.org/@babel/helper-validator-identifier/-/helper-validator-identifier-7.22.5.tgz", - "integrity": "sha512-aJXu+6lErq8ltp+JhkJUfk1MTGyuA4v7f3pA+BJ5HLfNC6nAQ0Cpi9uOquUj8Hehg0aUiHzWQbOVJGao6ztBAQ==", + "version": "7.22.20", + "resolved": "https://registry.npmjs.org/@babel/helper-validator-identifier/-/helper-validator-identifier-7.22.20.tgz", + "integrity": "sha512-Y4OZ+ytlatR8AI+8KZfKuL5urKp7qey08ha31L8b3BwewJAoJamTzyvxPR/5D+KkdJCGPq/+8TukHBlY10FX9A==", "dev": true, "engines": { "node": ">=6.9.0" } }, "node_modules/@babel/highlight": { - "version": "7.22.5", - "resolved": "https://registry.npmjs.org/@babel/highlight/-/highlight-7.22.5.tgz", - "integrity": "sha512-BSKlD1hgnedS5XRnGOljZawtag7H1yPfQp0tdNJCHoH6AZ+Pcm9VvkrK59/Yy593Ypg0zMxH2BxD1VPYUQ7UIw==", + "version": "7.24.2", + "resolved": "https://registry.npmjs.org/@babel/highlight/-/highlight-7.24.2.tgz", + "integrity": "sha512-Yac1ao4flkTxTteCDZLEvdxg2fZfz1v8M4QpaGypq/WPDqg3ijHYbDfs+LG5hvzSoqaSZ9/Z9lKSP3CjZjv+pA==", "dev": true, "dependencies": { - "@babel/helper-validator-identifier": "^7.22.5", - "chalk": "^2.0.0", - "js-tokens": "^4.0.0" + "@babel/helper-validator-identifier": "^7.22.20", + "chalk": "^2.4.2", + "js-tokens": "^4.0.0", + "picocolors": "^1.0.0" }, "engines": { "node": ">=6.9.0" @@ -162,32 +163,6 @@ "node": ">=4" } }, - "node_modules/@chainsafe/as-sha256": { - "version": "0.3.1", - "resolved": "https://registry.npmjs.org/@chainsafe/as-sha256/-/as-sha256-0.3.1.tgz", - "integrity": "sha512-hldFFYuf49ed7DAakWVXSJODuq3pzJEguD8tQ7h+sGkM18vja+OFoJI9krnGmgzyuZC2ETX0NOIcCTy31v2Mtg==", - "dev": true - }, - "node_modules/@chainsafe/persistent-merkle-tree": { - "version": "0.4.2", - "resolved": "https://registry.npmjs.org/@chainsafe/persistent-merkle-tree/-/persistent-merkle-tree-0.4.2.tgz", - "integrity": "sha512-lLO3ihKPngXLTus/L7WHKaw9PnNJWizlOF1H9NNzHP6Xvh82vzg9F2bzkXhYIFshMZ2gTCEz8tq6STe7r5NDfQ==", - "dev": true, - "dependencies": { - "@chainsafe/as-sha256": "^0.3.1" - } - }, - "node_modules/@chainsafe/ssz": { - "version": "0.9.4", - "resolved": "https://registry.npmjs.org/@chainsafe/ssz/-/ssz-0.9.4.tgz", - "integrity": "sha512-77Qtg2N1ayqs4Bg/wvnWfg5Bta7iy7IRh8XqXh7oNMeP2HBbBwx8m6yTpA8p0EHItWPEBkgZd5S5/LSlp3GXuQ==", - "dev": true, - "dependencies": { - "@chainsafe/as-sha256": "^0.3.1", - "@chainsafe/persistent-merkle-tree": "^0.4.2", - "case": "^1.6.3" - } - }, "node_modules/@cspotcode/source-map-support": { "version": "0.8.1", "resolved": "https://registry.npmjs.org/@cspotcode/source-map-support/-/source-map-support-0.8.1.tgz", @@ -532,36 +507,6 @@ "ethers": "^5" } }, - "node_modules/@eth-optimism/sdk/node_modules/lru-cache": { - "version": "6.0.0", - "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-6.0.0.tgz", - "integrity": "sha512-Jo6dJ04CmSjuznwJSS3pUeWmd/H0ffTlkXXgwZi+eq1UCmqQwCh+eLsYOYCwY991i2Fah4h1BEMCx4qThGbsiA==", - "dependencies": { - "yallist": "^4.0.0" - }, - "engines": { - "node": ">=10" - } - }, - "node_modules/@eth-optimism/sdk/node_modules/semver": { - "version": "7.6.0", - "resolved": "https://registry.npmjs.org/semver/-/semver-7.6.0.tgz", - "integrity": "sha512-EnwXhrlwXMk9gKu5/flx5sv/an57AkRplG3hTK68W7FRDN+k+OWBj65M7719OkA82XLBxrcX0KSHj+X5COhOVg==", - "dependencies": { - "lru-cache": "^6.0.0" - }, - "bin": { - "semver": "bin/semver.js" - }, - "engines": { - "node": ">=10" - } - }, - "node_modules/@eth-optimism/sdk/node_modules/yallist": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/yallist/-/yallist-4.0.0.tgz", - "integrity": "sha512-3wdGidZyq5PB084XLES5TpOSRA3wjXAlIWMhum2kRcv/41Sn2emQ0dycQW4uZXLejwKvg6EsvbdlVL+FYEct7A==" - }, "node_modules/@ethereum-waffle/chai": { "version": "3.4.4", "resolved": "https://registry.npmjs.org/@ethereum-waffle/chai/-/chai-3.4.4.tgz", @@ -690,6 +635,17 @@ "ethereumjs-util": "^7.1.5" } }, + "node_modules/@ethereumjs/rlp": { + "version": "4.0.1", + "resolved": "https://registry.npmjs.org/@ethereumjs/rlp/-/rlp-4.0.1.tgz", + "integrity": "sha512-tqsQiBQDQdmPWE1xkkBq4rlSW5QZpLOUJ5RJh2/9fug+q9tnUhuZoVLk7s0scUIKTOzEtR72DFBXI4WiZcMpvw==", + "bin": { + "rlp": "bin/rlp" + }, + "engines": { + "node": ">=14" + } + }, "node_modules/@ethereumjs/tx": { "version": "3.5.2", "resolved": "https://registry.npmjs.org/@ethereumjs/tx/-/tx-3.5.2.tgz", @@ -700,6 +656,41 @@ "ethereumjs-util": "^7.1.5" } }, + "node_modules/@ethereumjs/util": { + "version": "8.1.0", + "resolved": "https://registry.npmjs.org/@ethereumjs/util/-/util-8.1.0.tgz", + "integrity": "sha512-zQ0IqbdX8FZ9aw11vP+dZkKDkS+kgIvQPHnSAXzP9pLu+Rfu3D3XEeLbicvoXJTYnhZiPmsZUxgdzXwNKxRPbA==", + "dependencies": { + "@ethereumjs/rlp": "^4.0.1", + "ethereum-cryptography": "^2.0.0", + "micro-ftch": "^0.3.1" + }, + "engines": { + "node": ">=14" + } + }, + "node_modules/@ethereumjs/util/node_modules/@noble/hashes": { + "version": "1.3.3", + "resolved": "https://registry.npmjs.org/@noble/hashes/-/hashes-1.3.3.tgz", + "integrity": "sha512-V7/fPHgl+jsVPXqqeOzT8egNj2iBIVt+ECeMMG8TdcnTikP3oaBtUVqpT/gYCR68aEBJSF+XbYUxStjbFMqIIA==", + "engines": { + "node": ">= 16" + }, + "funding": { + "url": "https://paulmillr.com/funding/" + } + }, + "node_modules/@ethereumjs/util/node_modules/ethereum-cryptography": { + "version": "2.1.3", + "resolved": "https://registry.npmjs.org/ethereum-cryptography/-/ethereum-cryptography-2.1.3.tgz", + "integrity": "sha512-BlwbIL7/P45W8FGW2r7LGuvoEZ+7PWsniMvQ4p5s2xCyw9tmaDlpfsN9HjAucbF+t/qpVHwZUisgfK24TCW8aA==", + "dependencies": { + "@noble/curves": "1.3.0", + "@noble/hashes": "1.3.3", + "@scure/bip32": "1.3.3", + "@scure/bip39": "1.2.2" + } + }, "node_modules/@ethersproject/abi": { "version": "5.7.0", "resolved": "https://registry.npmjs.org/@ethersproject/abi/-/abi-5.7.0.tgz", @@ -1372,9 +1363,9 @@ } }, "node_modules/@fastify/busboy": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/@fastify/busboy/-/busboy-2.0.0.tgz", - "integrity": "sha512-JUFJad5lv7jxj926GPgymrWQxxjPYuJNiNjNMzqT+HiuP6Vl3dk5xzG+8sTX96np0ZAluvaMzPsjhHZ5rNuNQQ==", + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/@fastify/busboy/-/busboy-2.1.1.tgz", + "integrity": "sha512-vBZP4NlzfOlerQTnba4aqZoMhE/a9HY7HRqoOPaETQcSQuWEIyZMHGfVu6w9wGtGK5fED5qRs2DteVCjOH60sA==", "dev": true, "engines": { "node": ">=14" @@ -1401,9 +1392,9 @@ "dev": true }, "node_modules/@jridgewell/resolve-uri": { - "version": "3.1.1", - "resolved": "https://registry.npmjs.org/@jridgewell/resolve-uri/-/resolve-uri-3.1.1.tgz", - "integrity": "sha512-dSYZh7HhCDtCKm4QakX0xFpsRDqjjtZf/kjI/v3T3Nwt5r8/qz/M19F9ySyOqU94SXBmeG9ttTul+YnR4LOxFA==", + "version": "3.1.2", + "resolved": "https://registry.npmjs.org/@jridgewell/resolve-uri/-/resolve-uri-3.1.2.tgz", + "integrity": "sha512-bRISgCIjP20/tbWSPWMEi54QVPRZExkuD9lJL+UIxUKtwVJA8wW1Trb1jMs1RFXo1CBTNZ/5hpC9QvmKWdopKw==", "dev": true, "engines": { "node": ">=6.0.0" @@ -1465,29 +1456,6 @@ "integrity": "sha512-c98Bf3tPniI+scsdk237ku1Dc3ujXQTSgyiPUDEOe7tRkhrqridvh8klBv0HCEso1OLOYcHuCv/cS6DNxKH+ZA==", "dev": true }, - "node_modules/@metamask/eth-sig-util/node_modules/ethereum-cryptography": { - "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", - "blakejs": "^1.1.0", - "browserify-aes": "^1.2.0", - "bs58check": "^2.1.2", - "create-hash": "^1.2.0", - "create-hmac": "^1.1.7", - "hash.js": "^1.1.7", - "keccak": "^3.0.0", - "pbkdf2": "^3.0.17", - "randombytes": "^2.1.0", - "safe-buffer": "^5.1.2", - "scrypt-js": "^3.0.0", - "secp256k1": "^4.0.1", - "setimmediate": "^1.0.5" - } - }, "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", @@ -1503,17 +1471,38 @@ "rlp": "^2.2.3" } }, + "node_modules/@noble/curves": { + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/@noble/curves/-/curves-1.3.0.tgz", + "integrity": "sha512-t01iSXPuN+Eqzb4eBX0S5oubSqXbK/xXa1Ne18Hj8f9pStxztHCE2gfboSp/dZRLSqfuLpRK2nDXDK+W9puocA==", + "dependencies": { + "@noble/hashes": "1.3.3" + }, + "funding": { + "url": "https://paulmillr.com/funding/" + } + }, + "node_modules/@noble/curves/node_modules/@noble/hashes": { + "version": "1.3.3", + "resolved": "https://registry.npmjs.org/@noble/hashes/-/hashes-1.3.3.tgz", + "integrity": "sha512-V7/fPHgl+jsVPXqqeOzT8egNj2iBIVt+ECeMMG8TdcnTikP3oaBtUVqpT/gYCR68aEBJSF+XbYUxStjbFMqIIA==", + "engines": { + "node": ">= 16" + }, + "funding": { + "url": "https://paulmillr.com/funding/" + } + }, "node_modules/@noble/hashes": { - "version": "1.2.0", - "resolved": "https://registry.npmjs.org/@noble/hashes/-/hashes-1.2.0.tgz", - "integrity": "sha512-FZfhjEDbT5GRswV3C6uvLPHMiVD6lQBmpoX5+eSiPaMTXte/IKqI5dykDxzZB/WBeK/CDuQRBWarPdi3FNY2zQ==", - "dev": true, - "funding": [ - { - "type": "individual", - "url": "https://paulmillr.com/funding/" - } - ] + "version": "1.4.0", + "resolved": "https://registry.npmjs.org/@noble/hashes/-/hashes-1.4.0.tgz", + "integrity": "sha512-V1JJ1WTRUqHHrOSh597hURcMqVKVGL/ea3kv0gSnEdsEZ0/+VyPghM1lMNGc00z7CIQorSvbKpuJkxvuHbvdbg==", + "engines": { + "node": ">= 16" + }, + "funding": { + "url": "https://paulmillr.com/funding/" + } }, "node_modules/@noble/secp256k1": { "version": "1.7.1", @@ -1562,415 +1551,199 @@ "node": ">= 8" } }, - "node_modules/@nomicfoundation/ethereumjs-block": { - "version": "5.0.1", - "resolved": "https://registry.npmjs.org/@nomicfoundation/ethereumjs-block/-/ethereumjs-block-5.0.1.tgz", - "integrity": "sha512-u1Yioemi6Ckj3xspygu/SfFvm8vZEO8/Yx5a1QLzi6nVU0jz3Pg2OmHKJ5w+D9Ogk1vhwRiqEBAqcb0GVhCyHw==", + "node_modules/@nomicfoundation/edr": { + "version": "0.3.5", + "resolved": "https://registry.npmjs.org/@nomicfoundation/edr/-/edr-0.3.5.tgz", + "integrity": "sha512-dPSM9DuI1sr71gqWUMgLo8MjHQWO4+WNDm3iWaT6P4vUFJReZX5qwA5X+3UwIPBry8GvNY084u7yWUvB3/8rqA==", "dev": true, - "dependencies": { - "@nomicfoundation/ethereumjs-common": "4.0.1", - "@nomicfoundation/ethereumjs-rlp": "5.0.1", - "@nomicfoundation/ethereumjs-trie": "6.0.1", - "@nomicfoundation/ethereumjs-tx": "5.0.1", - "@nomicfoundation/ethereumjs-util": "9.0.1", - "ethereum-cryptography": "0.1.3", - "ethers": "^5.7.1" + "engines": { + "node": ">= 18" }, + "optionalDependencies": { + "@nomicfoundation/edr-darwin-arm64": "0.3.5", + "@nomicfoundation/edr-darwin-x64": "0.3.5", + "@nomicfoundation/edr-linux-arm64-gnu": "0.3.5", + "@nomicfoundation/edr-linux-arm64-musl": "0.3.5", + "@nomicfoundation/edr-linux-x64-gnu": "0.3.5", + "@nomicfoundation/edr-linux-x64-musl": "0.3.5", + "@nomicfoundation/edr-win32-x64-msvc": "0.3.5" + } + }, + "node_modules/@nomicfoundation/edr-darwin-arm64": { + "version": "0.3.5", + "resolved": "https://registry.npmjs.org/@nomicfoundation/edr-darwin-arm64/-/edr-darwin-arm64-0.3.5.tgz", + "integrity": "sha512-gIXUIiPMUy6roLHpNlxf15DumU7/YhffUf7XIB+WUjMecaySfTGyZsTGnCMJZqrDyiYqWPyPKwCV/2u/jqFAUg==", + "cpu": [ + "arm64" + ], + "dev": true, + "optional": true, + "os": [ + "darwin" + ], "engines": { - "node": ">=14" + "node": ">= 18" } }, - "node_modules/@nomicfoundation/ethereumjs-block/node_modules/ethereum-cryptography": { - "version": "0.1.3", - "resolved": "https://registry.npmjs.org/ethereum-cryptography/-/ethereum-cryptography-0.1.3.tgz", - "integrity": "sha512-w8/4x1SGGzc+tO97TASLja6SLd3fRIK2tLVcV2Gx4IB21hE19atll5Cq9o3d0ZmAYC/8aw0ipieTSiekAea4SQ==", + "node_modules/@nomicfoundation/edr-darwin-x64": { + "version": "0.3.5", + "resolved": "https://registry.npmjs.org/@nomicfoundation/edr-darwin-x64/-/edr-darwin-x64-0.3.5.tgz", + "integrity": "sha512-0MrpOCXUK8gmplpYZ2Cy0holHEylvWoNeecFcrP2WJ5DLQzrB23U5JU2MvUzOJ7aL76Za1VXNBWi/UeTWdHM+w==", + "cpu": [ + "x64" + ], "dev": true, - "dependencies": { - "@types/pbkdf2": "^3.0.0", - "@types/secp256k1": "^4.0.1", - "blakejs": "^1.1.0", - "browserify-aes": "^1.2.0", - "bs58check": "^2.1.2", - "create-hash": "^1.2.0", - "create-hmac": "^1.1.7", - "hash.js": "^1.1.7", - "keccak": "^3.0.0", - "pbkdf2": "^3.0.17", - "randombytes": "^2.1.0", - "safe-buffer": "^5.1.2", - "scrypt-js": "^3.0.0", - "secp256k1": "^4.0.1", - "setimmediate": "^1.0.5" + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": ">= 18" } }, - "node_modules/@nomicfoundation/ethereumjs-blockchain": { - "version": "7.0.1", - "resolved": "https://registry.npmjs.org/@nomicfoundation/ethereumjs-blockchain/-/ethereumjs-blockchain-7.0.1.tgz", - "integrity": "sha512-NhzndlGg829XXbqJEYrF1VeZhAwSPgsK/OB7TVrdzft3y918hW5KNd7gIZ85sn6peDZOdjBsAXIpXZ38oBYE5A==", - "dev": true, - "dependencies": { - "@nomicfoundation/ethereumjs-block": "5.0.1", - "@nomicfoundation/ethereumjs-common": "4.0.1", - "@nomicfoundation/ethereumjs-ethash": "3.0.1", - "@nomicfoundation/ethereumjs-rlp": "5.0.1", - "@nomicfoundation/ethereumjs-trie": "6.0.1", - "@nomicfoundation/ethereumjs-tx": "5.0.1", - "@nomicfoundation/ethereumjs-util": "9.0.1", - "abstract-level": "^1.0.3", - "debug": "^4.3.3", - "ethereum-cryptography": "0.1.3", - "level": "^8.0.0", - "lru-cache": "^5.1.1", - "memory-level": "^1.0.0" - }, + "node_modules/@nomicfoundation/edr-linux-arm64-gnu": { + "version": "0.3.5", + "resolved": "https://registry.npmjs.org/@nomicfoundation/edr-linux-arm64-gnu/-/edr-linux-arm64-gnu-0.3.5.tgz", + "integrity": "sha512-aw9f7AZMiY1dZFNePJGKho2k+nEgFgzUAyyukiKfSqUIMXoFXMf1U3Ujv848czrSq9c5XGcdDa2xnEf3daU3xg==", + "cpu": [ + "arm64" + ], + "dev": true, + "optional": true, + "os": [ + "linux" + ], "engines": { - "node": ">=14" + "node": ">= 18" } }, - "node_modules/@nomicfoundation/ethereumjs-blockchain/node_modules/ethereum-cryptography": { - "version": "0.1.3", - "resolved": "https://registry.npmjs.org/ethereum-cryptography/-/ethereum-cryptography-0.1.3.tgz", - "integrity": "sha512-w8/4x1SGGzc+tO97TASLja6SLd3fRIK2tLVcV2Gx4IB21hE19atll5Cq9o3d0ZmAYC/8aw0ipieTSiekAea4SQ==", + "node_modules/@nomicfoundation/edr-linux-arm64-musl": { + "version": "0.3.5", + "resolved": "https://registry.npmjs.org/@nomicfoundation/edr-linux-arm64-musl/-/edr-linux-arm64-musl-0.3.5.tgz", + "integrity": "sha512-cVFRQjyABBlsbDj+XTczYBfrCHprZ6YNzN8gGGSqAh+UGIJkAIRomK6ar27GyJLNx3HkgbuDoi/9kA0zOo/95w==", + "cpu": [ + "arm64" + ], "dev": true, - "dependencies": { - "@types/pbkdf2": "^3.0.0", - "@types/secp256k1": "^4.0.1", - "blakejs": "^1.1.0", - "browserify-aes": "^1.2.0", - "bs58check": "^2.1.2", - "create-hash": "^1.2.0", - "create-hmac": "^1.1.7", - "hash.js": "^1.1.7", - "keccak": "^3.0.0", - "pbkdf2": "^3.0.17", - "randombytes": "^2.1.0", - "safe-buffer": "^5.1.2", - "scrypt-js": "^3.0.0", - "secp256k1": "^4.0.1", - "setimmediate": "^1.0.5" + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">= 18" } }, - "node_modules/@nomicfoundation/ethereumjs-common": { - "version": "4.0.1", - "resolved": "https://registry.npmjs.org/@nomicfoundation/ethereumjs-common/-/ethereumjs-common-4.0.1.tgz", - "integrity": "sha512-OBErlkfp54GpeiE06brBW/TTbtbuBJV5YI5Nz/aB2evTDo+KawyEzPjBlSr84z/8MFfj8wS2wxzQX1o32cev5g==", + "node_modules/@nomicfoundation/edr-linux-x64-gnu": { + "version": "0.3.5", + "resolved": "https://registry.npmjs.org/@nomicfoundation/edr-linux-x64-gnu/-/edr-linux-x64-gnu-0.3.5.tgz", + "integrity": "sha512-CjOg85DfR1Vt0fQWn5U0qi26DATK9tVzo3YOZEyI0JBsnqvk43fUTPv3uUAWBrPIRg5O5kOc9xG13hSpCBBxBg==", + "cpu": [ + "x64" + ], "dev": true, - "dependencies": { - "@nomicfoundation/ethereumjs-util": "9.0.1", - "crc-32": "^1.2.0" + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">= 18" } }, - "node_modules/@nomicfoundation/ethereumjs-ethash": { - "version": "3.0.1", - "resolved": "https://registry.npmjs.org/@nomicfoundation/ethereumjs-ethash/-/ethereumjs-ethash-3.0.1.tgz", - "integrity": "sha512-KDjGIB5igzWOp8Ik5I6QiRH5DH+XgILlplsHR7TEuWANZA759G6krQ6o8bvj+tRUz08YygMQu/sGd9mJ1DYT8w==", + "node_modules/@nomicfoundation/edr-linux-x64-musl": { + "version": "0.3.5", + "resolved": "https://registry.npmjs.org/@nomicfoundation/edr-linux-x64-musl/-/edr-linux-x64-musl-0.3.5.tgz", + "integrity": "sha512-hvX8bBGpBydAVevzK8jsu2FlqVZK1RrCyTX6wGHnltgMuBaoGLHYtNHiFpteOaJw2byYMiORc2bvj+98LhJ0Ew==", + "cpu": [ + "x64" + ], "dev": true, - "dependencies": { - "@nomicfoundation/ethereumjs-block": "5.0.1", - "@nomicfoundation/ethereumjs-rlp": "5.0.1", - "@nomicfoundation/ethereumjs-util": "9.0.1", - "abstract-level": "^1.0.3", - "bigint-crypto-utils": "^3.0.23", - "ethereum-cryptography": "0.1.3" - }, + "optional": true, + "os": [ + "linux" + ], "engines": { - "node": ">=14" + "node": ">= 18" } }, - "node_modules/@nomicfoundation/ethereumjs-ethash/node_modules/ethereum-cryptography": { - "version": "0.1.3", - "resolved": "https://registry.npmjs.org/ethereum-cryptography/-/ethereum-cryptography-0.1.3.tgz", - "integrity": "sha512-w8/4x1SGGzc+tO97TASLja6SLd3fRIK2tLVcV2Gx4IB21hE19atll5Cq9o3d0ZmAYC/8aw0ipieTSiekAea4SQ==", + "node_modules/@nomicfoundation/edr-win32-x64-msvc": { + "version": "0.3.5", + "resolved": "https://registry.npmjs.org/@nomicfoundation/edr-win32-x64-msvc/-/edr-win32-x64-msvc-0.3.5.tgz", + "integrity": "sha512-IJXjW13DY5UPsx/eG5DGfXtJ7Ydwrvw/BTZ2Y93lRLHzszVpSmeVmlxjZP5IW2afTSgMLaAAsqNw4NhppRGN8A==", + "cpu": [ + "x64" + ], "dev": true, - "dependencies": { - "@types/pbkdf2": "^3.0.0", - "@types/secp256k1": "^4.0.1", - "blakejs": "^1.1.0", - "browserify-aes": "^1.2.0", - "bs58check": "^2.1.2", - "create-hash": "^1.2.0", - "create-hmac": "^1.1.7", - "hash.js": "^1.1.7", - "keccak": "^3.0.0", - "pbkdf2": "^3.0.17", - "randombytes": "^2.1.0", - "safe-buffer": "^5.1.2", - "scrypt-js": "^3.0.0", - "secp256k1": "^4.0.1", - "setimmediate": "^1.0.5" + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": ">= 18" } }, - "node_modules/@nomicfoundation/ethereumjs-evm": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/@nomicfoundation/ethereumjs-evm/-/ethereumjs-evm-2.0.1.tgz", - "integrity": "sha512-oL8vJcnk0Bx/onl+TgQOQ1t/534GKFaEG17fZmwtPFeH8S5soiBYPCLUrvANOl4sCp9elYxIMzIiTtMtNNN8EQ==", + "node_modules/@nomicfoundation/ethereumjs-common": { + "version": "4.0.4", + "resolved": "https://registry.npmjs.org/@nomicfoundation/ethereumjs-common/-/ethereumjs-common-4.0.4.tgz", + "integrity": "sha512-9Rgb658lcWsjiicr5GzNCjI1llow/7r0k50dLL95OJ+6iZJcVbi15r3Y0xh2cIO+zgX0WIHcbzIu6FeQf9KPrg==", "dev": true, "dependencies": { - "@ethersproject/providers": "^5.7.1", - "@nomicfoundation/ethereumjs-common": "4.0.1", - "@nomicfoundation/ethereumjs-tx": "5.0.1", - "@nomicfoundation/ethereumjs-util": "9.0.1", - "debug": "^4.3.3", - "ethereum-cryptography": "0.1.3", - "mcl-wasm": "^0.7.1", - "rustbn.js": "~0.2.0" + "@nomicfoundation/ethereumjs-util": "9.0.4" + } + }, + "node_modules/@nomicfoundation/ethereumjs-rlp": { + "version": "5.0.4", + "resolved": "https://registry.npmjs.org/@nomicfoundation/ethereumjs-rlp/-/ethereumjs-rlp-5.0.4.tgz", + "integrity": "sha512-8H1S3s8F6QueOc/X92SdrA4RDenpiAEqMg5vJH99kcQaCy/a3Q6fgseo75mgWlbanGJXSlAPtnCeG9jvfTYXlw==", + "dev": true, + "bin": { + "rlp": "bin/rlp.cjs" }, "engines": { - "node": ">=14" + "node": ">=18" } }, - "node_modules/@nomicfoundation/ethereumjs-evm/node_modules/ethereum-cryptography": { - "version": "0.1.3", - "resolved": "https://registry.npmjs.org/ethereum-cryptography/-/ethereum-cryptography-0.1.3.tgz", - "integrity": "sha512-w8/4x1SGGzc+tO97TASLja6SLd3fRIK2tLVcV2Gx4IB21hE19atll5Cq9o3d0ZmAYC/8aw0ipieTSiekAea4SQ==", + "node_modules/@nomicfoundation/ethereumjs-tx": { + "version": "5.0.4", + "resolved": "https://registry.npmjs.org/@nomicfoundation/ethereumjs-tx/-/ethereumjs-tx-5.0.4.tgz", + "integrity": "sha512-Xjv8wAKJGMrP1f0n2PeyfFCCojHd7iS3s/Ab7qzF1S64kxZ8Z22LCMynArYsVqiFx6rzYy548HNVEyI+AYN/kw==", "dev": true, "dependencies": { - "@types/pbkdf2": "^3.0.0", - "@types/secp256k1": "^4.0.1", - "blakejs": "^1.1.0", - "browserify-aes": "^1.2.0", - "bs58check": "^2.1.2", - "create-hash": "^1.2.0", - "create-hmac": "^1.1.7", - "hash.js": "^1.1.7", - "keccak": "^3.0.0", - "pbkdf2": "^3.0.17", - "randombytes": "^2.1.0", - "safe-buffer": "^5.1.2", - "scrypt-js": "^3.0.0", - "secp256k1": "^4.0.1", - "setimmediate": "^1.0.5" - } - }, - "node_modules/@nomicfoundation/ethereumjs-rlp": { - "version": "5.0.1", - "resolved": "https://registry.npmjs.org/@nomicfoundation/ethereumjs-rlp/-/ethereumjs-rlp-5.0.1.tgz", - "integrity": "sha512-xtxrMGa8kP4zF5ApBQBtjlSbN5E2HI8m8FYgVSYAnO6ssUoY5pVPGy2H8+xdf/bmMa22Ce8nWMH3aEW8CcqMeQ==", - "dev": true, - "bin": { - "rlp": "bin/rlp" + "@nomicfoundation/ethereumjs-common": "4.0.4", + "@nomicfoundation/ethereumjs-rlp": "5.0.4", + "@nomicfoundation/ethereumjs-util": "9.0.4", + "ethereum-cryptography": "0.1.3" }, "engines": { - "node": ">=14" - } - }, - "node_modules/@nomicfoundation/ethereumjs-statemanager": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/@nomicfoundation/ethereumjs-statemanager/-/ethereumjs-statemanager-2.0.1.tgz", - "integrity": "sha512-B5ApMOnlruVOR7gisBaYwFX+L/AP7i/2oAahatssjPIBVDF6wTX1K7Qpa39E/nzsH8iYuL3krkYeUFIdO3EMUQ==", - "dev": true, - "dependencies": { - "@nomicfoundation/ethereumjs-common": "4.0.1", - "@nomicfoundation/ethereumjs-rlp": "5.0.1", - "debug": "^4.3.3", - "ethereum-cryptography": "0.1.3", - "ethers": "^5.7.1", - "js-sdsl": "^4.1.4" - } - }, - "node_modules/@nomicfoundation/ethereumjs-statemanager/node_modules/ethereum-cryptography": { - "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", - "blakejs": "^1.1.0", - "browserify-aes": "^1.2.0", - "bs58check": "^2.1.2", - "create-hash": "^1.2.0", - "create-hmac": "^1.1.7", - "hash.js": "^1.1.7", - "keccak": "^3.0.0", - "pbkdf2": "^3.0.17", - "randombytes": "^2.1.0", - "safe-buffer": "^5.1.2", - "scrypt-js": "^3.0.0", - "secp256k1": "^4.0.1", - "setimmediate": "^1.0.5" - } - }, - "node_modules/@nomicfoundation/ethereumjs-trie": { - "version": "6.0.1", - "resolved": "https://registry.npmjs.org/@nomicfoundation/ethereumjs-trie/-/ethereumjs-trie-6.0.1.tgz", - "integrity": "sha512-A64It/IMpDVODzCgxDgAAla8jNjNtsoQZIzZUfIV5AY6Coi4nvn7+VReBn5itlxMiL2yaTlQr9TRWp3CSI6VoA==", - "dev": true, - "dependencies": { - "@nomicfoundation/ethereumjs-rlp": "5.0.1", - "@nomicfoundation/ethereumjs-util": "9.0.1", - "@types/readable-stream": "^2.3.13", - "ethereum-cryptography": "0.1.3", - "readable-stream": "^3.6.0" + "node": ">=18" }, - "engines": { - "node": ">=14" - } - }, - "node_modules/@nomicfoundation/ethereumjs-trie/node_modules/ethereum-cryptography": { - "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", - "blakejs": "^1.1.0", - "browserify-aes": "^1.2.0", - "bs58check": "^2.1.2", - "create-hash": "^1.2.0", - "create-hmac": "^1.1.7", - "hash.js": "^1.1.7", - "keccak": "^3.0.0", - "pbkdf2": "^3.0.17", - "randombytes": "^2.1.0", - "safe-buffer": "^5.1.2", - "scrypt-js": "^3.0.0", - "secp256k1": "^4.0.1", - "setimmediate": "^1.0.5" - } - }, - "node_modules/@nomicfoundation/ethereumjs-tx": { - "version": "5.0.1", - "resolved": "https://registry.npmjs.org/@nomicfoundation/ethereumjs-tx/-/ethereumjs-tx-5.0.1.tgz", - "integrity": "sha512-0HwxUF2u2hrsIM1fsasjXvlbDOq1ZHFV2dd1yGq8CA+MEYhaxZr8OTScpVkkxqMwBcc5y83FyPl0J9MZn3kY0w==", - "dev": true, - "dependencies": { - "@chainsafe/ssz": "^0.9.2", - "@ethersproject/providers": "^5.7.2", - "@nomicfoundation/ethereumjs-common": "4.0.1", - "@nomicfoundation/ethereumjs-rlp": "5.0.1", - "@nomicfoundation/ethereumjs-util": "9.0.1", - "ethereum-cryptography": "0.1.3" + "peerDependencies": { + "c-kzg": "^2.1.2" }, - "engines": { - "node": ">=14" - } - }, - "node_modules/@nomicfoundation/ethereumjs-tx/node_modules/ethereum-cryptography": { - "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", - "blakejs": "^1.1.0", - "browserify-aes": "^1.2.0", - "bs58check": "^2.1.2", - "create-hash": "^1.2.0", - "create-hmac": "^1.1.7", - "hash.js": "^1.1.7", - "keccak": "^3.0.0", - "pbkdf2": "^3.0.17", - "randombytes": "^2.1.0", - "safe-buffer": "^5.1.2", - "scrypt-js": "^3.0.0", - "secp256k1": "^4.0.1", - "setimmediate": "^1.0.5" + "peerDependenciesMeta": { + "c-kzg": { + "optional": true + } } }, "node_modules/@nomicfoundation/ethereumjs-util": { - "version": "9.0.1", - "resolved": "https://registry.npmjs.org/@nomicfoundation/ethereumjs-util/-/ethereumjs-util-9.0.1.tgz", - "integrity": "sha512-TwbhOWQ8QoSCFhV/DDfSmyfFIHjPjFBj957219+V3jTZYZ2rf9PmDtNOeZWAE3p3vlp8xb02XGpd0v6nTUPbsA==", + "version": "9.0.4", + "resolved": "https://registry.npmjs.org/@nomicfoundation/ethereumjs-util/-/ethereumjs-util-9.0.4.tgz", + "integrity": "sha512-sLOzjnSrlx9Bb9EFNtHzK/FJFsfg2re6bsGqinFinH1gCqVfz9YYlXiMWwDM4C/L4ywuHFCYwfKTVr/QHQcU0Q==", "dev": true, "dependencies": { - "@chainsafe/ssz": "^0.10.0", - "@nomicfoundation/ethereumjs-rlp": "5.0.1", + "@nomicfoundation/ethereumjs-rlp": "5.0.4", "ethereum-cryptography": "0.1.3" }, "engines": { - "node": ">=14" - } - }, - "node_modules/@nomicfoundation/ethereumjs-util/node_modules/@chainsafe/persistent-merkle-tree": { - "version": "0.5.0", - "resolved": "https://registry.npmjs.org/@chainsafe/persistent-merkle-tree/-/persistent-merkle-tree-0.5.0.tgz", - "integrity": "sha512-l0V1b5clxA3iwQLXP40zYjyZYospQLZXzBVIhhr9kDg/1qHZfzzHw0jj4VPBijfYCArZDlPkRi1wZaV2POKeuw==", - "dev": true, - "dependencies": { - "@chainsafe/as-sha256": "^0.3.1" - } - }, - "node_modules/@nomicfoundation/ethereumjs-util/node_modules/@chainsafe/ssz": { - "version": "0.10.2", - "resolved": "https://registry.npmjs.org/@chainsafe/ssz/-/ssz-0.10.2.tgz", - "integrity": "sha512-/NL3Lh8K+0q7A3LsiFq09YXS9fPE+ead2rr7vM2QK8PLzrNsw3uqrif9bpRX5UxgeRjM+vYi+boCM3+GM4ovXg==", - "dev": true, - "dependencies": { - "@chainsafe/as-sha256": "^0.3.1", - "@chainsafe/persistent-merkle-tree": "^0.5.0" - } - }, - "node_modules/@nomicfoundation/ethereumjs-util/node_modules/ethereum-cryptography": { - "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", - "blakejs": "^1.1.0", - "browserify-aes": "^1.2.0", - "bs58check": "^2.1.2", - "create-hash": "^1.2.0", - "create-hmac": "^1.1.7", - "hash.js": "^1.1.7", - "keccak": "^3.0.0", - "pbkdf2": "^3.0.17", - "randombytes": "^2.1.0", - "safe-buffer": "^5.1.2", - "scrypt-js": "^3.0.0", - "secp256k1": "^4.0.1", - "setimmediate": "^1.0.5" - } - }, - "node_modules/@nomicfoundation/ethereumjs-vm": { - "version": "7.0.1", - "resolved": "https://registry.npmjs.org/@nomicfoundation/ethereumjs-vm/-/ethereumjs-vm-7.0.1.tgz", - "integrity": "sha512-rArhyn0jPsS/D+ApFsz3yVJMQ29+pVzNZ0VJgkzAZ+7FqXSRtThl1C1prhmlVr3YNUlfpZ69Ak+RUT4g7VoOuQ==", - "dev": true, - "dependencies": { - "@nomicfoundation/ethereumjs-block": "5.0.1", - "@nomicfoundation/ethereumjs-blockchain": "7.0.1", - "@nomicfoundation/ethereumjs-common": "4.0.1", - "@nomicfoundation/ethereumjs-evm": "2.0.1", - "@nomicfoundation/ethereumjs-rlp": "5.0.1", - "@nomicfoundation/ethereumjs-statemanager": "2.0.1", - "@nomicfoundation/ethereumjs-trie": "6.0.1", - "@nomicfoundation/ethereumjs-tx": "5.0.1", - "@nomicfoundation/ethereumjs-util": "9.0.1", - "debug": "^4.3.3", - "ethereum-cryptography": "0.1.3", - "mcl-wasm": "^0.7.1", - "rustbn.js": "~0.2.0" + "node": ">=18" }, - "engines": { - "node": ">=14" - } - }, - "node_modules/@nomicfoundation/ethereumjs-vm/node_modules/ethereum-cryptography": { - "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", - "blakejs": "^1.1.0", - "browserify-aes": "^1.2.0", - "bs58check": "^2.1.2", - "create-hash": "^1.2.0", - "create-hmac": "^1.1.7", - "hash.js": "^1.1.7", - "keccak": "^3.0.0", - "pbkdf2": "^3.0.17", - "randombytes": "^2.1.0", - "safe-buffer": "^5.1.2", - "scrypt-js": "^3.0.0", - "secp256k1": "^4.0.1", - "setimmediate": "^1.0.5" + "peerDependencies": { + "c-kzg": "^2.1.2" + }, + "peerDependenciesMeta": { + "c-kzg": { + "optional": true + } } }, "node_modules/@nomicfoundation/solidity-analyzer": { @@ -2010,67 +1783,211 @@ "node": ">= 10" } }, - "node_modules/@nomiclabs/hardhat-ethers": { - "version": "2.2.3", - "resolved": "https://registry.npmjs.org/@nomiclabs/hardhat-ethers/-/hardhat-ethers-2.2.3.tgz", - "integrity": "sha512-YhzPdzb612X591FOe68q+qXVXGG2ANZRvDo0RRUtimev85rCrAlv/TLMEZw5c+kq9AbzocLTVX/h2jVIFPL9Xg==", - "dev": true, - "peerDependencies": { - "ethers": "^5.0.0", - "hardhat": "^2.0.0" - } - }, - "node_modules/@nomiclabs/hardhat-etherscan": { - "version": "3.1.8", - "resolved": "https://registry.npmjs.org/@nomiclabs/hardhat-etherscan/-/hardhat-etherscan-3.1.8.tgz", - "integrity": "sha512-v5F6IzQhrsjHh6kQz4uNrym49brK9K5bYCq2zQZ729RYRaifI9hHbtmK+KkIVevfhut7huQFEQ77JLRMAzWYjQ==", - "deprecated": "The @nomiclabs/hardhat-etherscan package is deprecated, please use @nomicfoundation/hardhat-verify instead", + "node_modules/@nomicfoundation/solidity-analyzer-darwin-x64": { + "version": "0.1.1", + "resolved": "https://registry.npmjs.org/@nomicfoundation/solidity-analyzer-darwin-x64/-/solidity-analyzer-darwin-x64-0.1.1.tgz", + "integrity": "sha512-XhQG4BaJE6cIbjAVtzGOGbK3sn1BO9W29uhk9J8y8fZF1DYz0Doj8QDMfpMu+A6TjPDs61lbsmeYodIDnfveSA==", + "cpu": [ + "x64" + ], "dev": true, - "dependencies": { - "@ethersproject/abi": "^5.1.2", - "@ethersproject/address": "^5.0.2", - "cbor": "^8.1.0", - "chalk": "^2.4.2", - "debug": "^4.1.1", - "fs-extra": "^7.0.1", - "lodash": "^4.17.11", - "semver": "^6.3.0", - "table": "^6.8.0", - "undici": "^5.14.0" - }, - "peerDependencies": { - "hardhat": "^2.0.4" + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": ">= 10" } }, - "node_modules/@nomiclabs/hardhat-etherscan/node_modules/ansi-styles": { - "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==", + "node_modules/@nomicfoundation/solidity-analyzer-freebsd-x64": { + "version": "0.1.1", + "resolved": "https://registry.npmjs.org/@nomicfoundation/solidity-analyzer-freebsd-x64/-/solidity-analyzer-freebsd-x64-0.1.1.tgz", + "integrity": "sha512-GHF1VKRdHW3G8CndkwdaeLkVBi5A9u2jwtlS7SLhBc8b5U/GcoL39Q+1CSO3hYqePNP+eV5YI7Zgm0ea6kMHoA==", + "cpu": [ + "x64" + ], "dev": true, - "dependencies": { - "color-convert": "^1.9.0" - }, + "optional": true, + "os": [ + "freebsd" + ], "engines": { - "node": ">=4" + "node": ">= 10" } }, - "node_modules/@nomiclabs/hardhat-etherscan/node_modules/chalk": { - "version": "2.4.2", - "resolved": "https://registry.npmjs.org/chalk/-/chalk-2.4.2.tgz", - "integrity": "sha512-Mti+f9lpJNcwF4tWV8/OrTTtF1gZi+f8FqlyAdouralcFWFQWF2+NgCHShjkCb+IFBLq9buZwE1xckQU4peSuQ==", + "node_modules/@nomicfoundation/solidity-analyzer-linux-arm64-gnu": { + "version": "0.1.1", + "resolved": "https://registry.npmjs.org/@nomicfoundation/solidity-analyzer-linux-arm64-gnu/-/solidity-analyzer-linux-arm64-gnu-0.1.1.tgz", + "integrity": "sha512-g4Cv2fO37ZsUENQ2vwPnZc2zRenHyAxHcyBjKcjaSmmkKrFr64yvzeNO8S3GBFCo90rfochLs99wFVGT/0owpg==", + "cpu": [ + "arm64" + ], "dev": true, - "dependencies": { - "ansi-styles": "^3.2.1", - "escape-string-regexp": "^1.0.5", - "supports-color": "^5.3.0" - }, + "optional": true, + "os": [ + "linux" + ], "engines": { - "node": ">=4" + "node": ">= 10" } }, - "node_modules/@nomiclabs/hardhat-etherscan/node_modules/color-convert": { - "version": "1.9.3", - "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-1.9.3.tgz", + "node_modules/@nomicfoundation/solidity-analyzer-linux-arm64-musl": { + "version": "0.1.1", + "resolved": "https://registry.npmjs.org/@nomicfoundation/solidity-analyzer-linux-arm64-musl/-/solidity-analyzer-linux-arm64-musl-0.1.1.tgz", + "integrity": "sha512-WJ3CE5Oek25OGE3WwzK7oaopY8xMw9Lhb0mlYuJl/maZVo+WtP36XoQTb7bW/i8aAdHW5Z+BqrHMux23pvxG3w==", + "cpu": [ + "arm64" + ], + "dev": true, + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">= 10" + } + }, + "node_modules/@nomicfoundation/solidity-analyzer-linux-x64-gnu": { + "version": "0.1.1", + "resolved": "https://registry.npmjs.org/@nomicfoundation/solidity-analyzer-linux-x64-gnu/-/solidity-analyzer-linux-x64-gnu-0.1.1.tgz", + "integrity": "sha512-5WN7leSr5fkUBBjE4f3wKENUy9HQStu7HmWqbtknfXkkil+eNWiBV275IOlpXku7v3uLsXTOKpnnGHJYI2qsdA==", + "cpu": [ + "x64" + ], + "dev": true, + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">= 10" + } + }, + "node_modules/@nomicfoundation/solidity-analyzer-linux-x64-musl": { + "version": "0.1.1", + "resolved": "https://registry.npmjs.org/@nomicfoundation/solidity-analyzer-linux-x64-musl/-/solidity-analyzer-linux-x64-musl-0.1.1.tgz", + "integrity": "sha512-KdYMkJOq0SYPQMmErv/63CwGwMm5XHenEna9X9aB8mQmhDBrYrlAOSsIPgFCUSL0hjxE3xHP65/EPXR/InD2+w==", + "cpu": [ + "x64" + ], + "dev": true, + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">= 10" + } + }, + "node_modules/@nomicfoundation/solidity-analyzer-win32-arm64-msvc": { + "version": "0.1.1", + "resolved": "https://registry.npmjs.org/@nomicfoundation/solidity-analyzer-win32-arm64-msvc/-/solidity-analyzer-win32-arm64-msvc-0.1.1.tgz", + "integrity": "sha512-VFZASBfl4qiBYwW5xeY20exWhmv6ww9sWu/krWSesv3q5hA0o1JuzmPHR4LPN6SUZj5vcqci0O6JOL8BPw+APg==", + "cpu": [ + "arm64" + ], + "dev": true, + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": ">= 10" + } + }, + "node_modules/@nomicfoundation/solidity-analyzer-win32-ia32-msvc": { + "version": "0.1.1", + "resolved": "https://registry.npmjs.org/@nomicfoundation/solidity-analyzer-win32-ia32-msvc/-/solidity-analyzer-win32-ia32-msvc-0.1.1.tgz", + "integrity": "sha512-JnFkYuyCSA70j6Si6cS1A9Gh1aHTEb8kOTBApp/c7NRTFGNMH8eaInKlyuuiIbvYFhlXW4LicqyYuWNNq9hkpQ==", + "cpu": [ + "ia32" + ], + "dev": true, + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": ">= 10" + } + }, + "node_modules/@nomicfoundation/solidity-analyzer-win32-x64-msvc": { + "version": "0.1.1", + "resolved": "https://registry.npmjs.org/@nomicfoundation/solidity-analyzer-win32-x64-msvc/-/solidity-analyzer-win32-x64-msvc-0.1.1.tgz", + "integrity": "sha512-HrVJr6+WjIXGnw3Q9u6KQcbZCtk0caVWhCdFADySvRyUxJ8PnzlaP+MhwNE8oyT8OZ6ejHBRrrgjSqDCFXGirw==", + "cpu": [ + "x64" + ], + "dev": true, + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": ">= 10" + } + }, + "node_modules/@nomiclabs/hardhat-ethers": { + "version": "2.2.3", + "resolved": "https://registry.npmjs.org/@nomiclabs/hardhat-ethers/-/hardhat-ethers-2.2.3.tgz", + "integrity": "sha512-YhzPdzb612X591FOe68q+qXVXGG2ANZRvDo0RRUtimev85rCrAlv/TLMEZw5c+kq9AbzocLTVX/h2jVIFPL9Xg==", + "dev": true, + "peerDependencies": { + "ethers": "^5.0.0", + "hardhat": "^2.0.0" + } + }, + "node_modules/@nomiclabs/hardhat-etherscan": { + "version": "3.1.8", + "resolved": "https://registry.npmjs.org/@nomiclabs/hardhat-etherscan/-/hardhat-etherscan-3.1.8.tgz", + "integrity": "sha512-v5F6IzQhrsjHh6kQz4uNrym49brK9K5bYCq2zQZ729RYRaifI9hHbtmK+KkIVevfhut7huQFEQ77JLRMAzWYjQ==", + "deprecated": "The @nomiclabs/hardhat-etherscan package is deprecated, please use @nomicfoundation/hardhat-verify instead", + "dev": true, + "dependencies": { + "@ethersproject/abi": "^5.1.2", + "@ethersproject/address": "^5.0.2", + "cbor": "^8.1.0", + "chalk": "^2.4.2", + "debug": "^4.1.1", + "fs-extra": "^7.0.1", + "lodash": "^4.17.11", + "semver": "^6.3.0", + "table": "^6.8.0", + "undici": "^5.14.0" + }, + "peerDependencies": { + "hardhat": "^2.0.4" + } + }, + "node_modules/@nomiclabs/hardhat-etherscan/node_modules/ansi-styles": { + "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" + }, + "engines": { + "node": ">=4" + } + }, + "node_modules/@nomiclabs/hardhat-etherscan/node_modules/chalk": { + "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", + "supports-color": "^5.3.0" + }, + "engines": { + "node": ">=4" + } + }, + "node_modules/@nomiclabs/hardhat-etherscan/node_modules/color-convert": { + "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": { @@ -2101,6 +2018,15 @@ "node": ">=4" } }, + "node_modules/@nomiclabs/hardhat-etherscan/node_modules/semver": { + "version": "6.3.1", + "resolved": "https://registry.npmjs.org/semver/-/semver-6.3.1.tgz", + "integrity": "sha512-BR7VvDCVHO+q2xBEWskxS6DJE1qRnb7DxzUrogb71CWoSficBxYsiAGd+Kl0mmq/MprG9yArRkyrQxTO6XjMzA==", + "dev": true, + "bin": { + "semver": "bin/semver.js" + } + }, "node_modules/@nomiclabs/hardhat-etherscan/node_modules/supports-color": { "version": "5.5.0", "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-5.5.0.tgz", @@ -2219,48 +2145,58 @@ } }, "node_modules/@scure/base": { - "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", - "url": "https://paulmillr.com/funding/" - } - ] + "version": "1.1.6", + "resolved": "https://registry.npmjs.org/@scure/base/-/base-1.1.6.tgz", + "integrity": "sha512-ok9AWwhcgYuGG3Zfhyqg+zwl+Wn5uE+dwC0NV/2qQkx4dABbb/bx96vWu8NSj+BNjjSjno+JRYRjle1jV08k3g==", + "funding": { + "url": "https://paulmillr.com/funding/" + } }, "node_modules/@scure/bip32": { - "version": "1.1.5", - "resolved": "https://registry.npmjs.org/@scure/bip32/-/bip32-1.1.5.tgz", - "integrity": "sha512-XyNh1rB0SkEqd3tXcXMi+Xe1fvg+kUIcoRIEujP1Jgv7DqW2r9lg3Ah0NkFaCs9sTkQAQA8kw7xiRXzENi9Rtw==", - "dev": true, - "funding": [ - { - "type": "individual", - "url": "https://paulmillr.com/funding/" - } - ], + "version": "1.3.3", + "resolved": "https://registry.npmjs.org/@scure/bip32/-/bip32-1.3.3.tgz", + "integrity": "sha512-LJaN3HwRbfQK0X1xFSi0Q9amqOgzQnnDngIt+ZlsBC3Bm7/nE7K0kwshZHyaru79yIVRv/e1mQAjZyuZG6jOFQ==", "dependencies": { - "@noble/hashes": "~1.2.0", - "@noble/secp256k1": "~1.7.0", - "@scure/base": "~1.1.0" + "@noble/curves": "~1.3.0", + "@noble/hashes": "~1.3.2", + "@scure/base": "~1.1.4" + }, + "funding": { + "url": "https://paulmillr.com/funding/" + } + }, + "node_modules/@scure/bip32/node_modules/@noble/hashes": { + "version": "1.3.3", + "resolved": "https://registry.npmjs.org/@noble/hashes/-/hashes-1.3.3.tgz", + "integrity": "sha512-V7/fPHgl+jsVPXqqeOzT8egNj2iBIVt+ECeMMG8TdcnTikP3oaBtUVqpT/gYCR68aEBJSF+XbYUxStjbFMqIIA==", + "engines": { + "node": ">= 16" + }, + "funding": { + "url": "https://paulmillr.com/funding/" } }, "node_modules/@scure/bip39": { - "version": "1.1.1", - "resolved": "https://registry.npmjs.org/@scure/bip39/-/bip39-1.1.1.tgz", - "integrity": "sha512-t+wDck2rVkh65Hmv280fYdVdY25J9YeEUIgn2LG1WM6gxFkGzcksoDiUkWVpVp3Oex9xGC68JU2dSbUfwZ2jPg==", - "dev": true, - "funding": [ - { - "type": "individual", - "url": "https://paulmillr.com/funding/" - } - ], + "version": "1.2.2", + "resolved": "https://registry.npmjs.org/@scure/bip39/-/bip39-1.2.2.tgz", + "integrity": "sha512-HYf9TUXG80beW+hGAt3TRM8wU6pQoYur9iNypTROm42dorCGmLnFe3eWjz3gOq6G62H2WRh0FCzAR1PI+29zIA==", "dependencies": { - "@noble/hashes": "~1.2.0", - "@scure/base": "~1.1.0" + "@noble/hashes": "~1.3.2", + "@scure/base": "~1.1.4" + }, + "funding": { + "url": "https://paulmillr.com/funding/" + } + }, + "node_modules/@scure/bip39/node_modules/@noble/hashes": { + "version": "1.3.3", + "resolved": "https://registry.npmjs.org/@noble/hashes/-/hashes-1.3.3.tgz", + "integrity": "sha512-V7/fPHgl+jsVPXqqeOzT8egNj2iBIVt+ECeMMG8TdcnTikP3oaBtUVqpT/gYCR68aEBJSF+XbYUxStjbFMqIIA==", + "engines": { + "node": ">= 16" + }, + "funding": { + "url": "https://paulmillr.com/funding/" } }, "node_modules/@sentry/core": { @@ -2399,17 +2335,22 @@ "version": "0.1.1", "resolved": "https://registry.npmjs.org/@truffle/error/-/error-0.1.1.tgz", "integrity": "sha512-sE7c9IHIGdbK4YayH4BC8i8qMjoAOeg6nUXUDZZp8wlU21/EMpaG+CLx+KqcIPyR+GSWIW3Dm0PXkr2nlggFDA==", + "deprecated": "Package no longer supported. Contact Support at https://www.npmjs.com/support for more info.", "dev": true }, "node_modules/@truffle/interface-adapter": { - "version": "0.5.35", - "resolved": "https://registry.npmjs.org/@truffle/interface-adapter/-/interface-adapter-0.5.35.tgz", - "integrity": "sha512-B5gtJnvsum5j2do393n0UfCT8MklrlAZxuqvEFBeMM9UKnreYct0/D368FVMlZwWo1N50HgGeZ0hlpSJqR/nvg==", + "version": "0.5.37", + "resolved": "https://registry.npmjs.org/@truffle/interface-adapter/-/interface-adapter-0.5.37.tgz", + "integrity": "sha512-lPH9MDgU+7sNDlJSClwyOwPCfuOimqsCx0HfGkznL3mcFRymc1pukAR1k17zn7ErHqBwJjiKAZ6Ri72KkS+IWw==", + "deprecated": "Package no longer supported. Contact Support at https://www.npmjs.com/support for more info.", "dev": true, "dependencies": { "bn.js": "^5.1.3", "ethers": "^4.0.32", "web3": "1.10.0" + }, + "engines": { + "node": "^16.20 || ^18.16 || >=20" } }, "node_modules/@truffle/interface-adapter/node_modules/@ethereumjs/common": { @@ -2617,21 +2558,6 @@ "integrity": "sha512-GII20kjaPX0zJ8wzkTbNDYMY7msuZcTWk8S5UOh6806Jq/wz1J8/bnr8uGU0DAUmYDjj2Mr4X1cW8v/GLYnR+g==", "dev": true }, - "node_modules/@truffle/interface-adapter/node_modules/json-buffer": { - "version": "3.0.1", - "resolved": "https://registry.npmjs.org/json-buffer/-/json-buffer-3.0.1.tgz", - "integrity": "sha512-4bV5BfR2mqfQTJm+V5tPPdf+ZpuhiIvTuAB5g8kcrXOZpTT/QwwVRWBywX1ozr6lEuPdbHxwaJlm9G6mI2sfSQ==", - "dev": true - }, - "node_modules/@truffle/interface-adapter/node_modules/keyv": { - "version": "4.5.3", - "resolved": "https://registry.npmjs.org/keyv/-/keyv-4.5.3.tgz", - "integrity": "sha512-QCiSav9WaX1PgETJ+SpNnx2PRRapJ/oRSXM4VO5OGYGSjrxbKPVFVhB3l2OCbLCk329N8qyAtsJjSjvVBWzEug==", - "dev": true, - "dependencies": { - "json-buffer": "3.0.1" - } - }, "node_modules/@truffle/interface-adapter/node_modules/lowercase-keys": { "version": "3.0.0", "resolved": "https://registry.npmjs.org/lowercase-keys/-/lowercase-keys-3.0.0.tgz", @@ -2903,10 +2829,14 @@ "dev": true }, "node_modules/@truffle/interface-adapter/node_modules/web3-eth-accounts/node_modules/uuid": { - "version": "9.0.0", - "resolved": "https://registry.npmjs.org/uuid/-/uuid-9.0.0.tgz", - "integrity": "sha512-MXcSTerfPa4uqyzStbRoTgt5XIe3x5+42+q1sDuy3R5MDk66URdLMOZe5aPX/SQd+kuYAh0FdP/pO28IkQyTeg==", + "version": "9.0.1", + "resolved": "https://registry.npmjs.org/uuid/-/uuid-9.0.1.tgz", + "integrity": "sha512-b+1eJOlsR9K8HJpow9Ok3fiWOWSIcIzXodvv0rQjVoOVNpWMpxf1wZNpt4y9h10odCNrqnYp1OBzRktckBe3sA==", "dev": true, + "funding": [ + "https://github.com/sponsors/broofa", + "https://github.com/sponsors/ctavan" + ], "bin": { "uuid": "dist/bin/uuid" } @@ -3051,10 +2981,29 @@ "node": ">=8.0.0" } }, + "node_modules/@truffle/interface-adapter/node_modules/web3-utils": { + "version": "1.10.0", + "resolved": "https://registry.npmjs.org/web3-utils/-/web3-utils-1.10.0.tgz", + "integrity": "sha512-kSaCM0uMcZTNUSmn5vMEhlo02RObGNRRCkdX0V9UTAU0+lrvn0HSaudyCo6CQzuXUsnuY2ERJGCGPfeWmv19Rg==", + "dev": true, + "dependencies": { + "bn.js": "^5.2.1", + "ethereum-bloom-filters": "^1.0.6", + "ethereumjs-util": "^7.1.0", + "ethjs-unit": "0.1.6", + "number-to-bn": "1.7.0", + "randombytes": "^2.1.0", + "utf8": "3.0.0" + }, + "engines": { + "node": ">=8.0.0" + } + }, "node_modules/@truffle/provider": { "version": "0.2.64", "resolved": "https://registry.npmjs.org/@truffle/provider/-/provider-0.2.64.tgz", "integrity": "sha512-ZwPsofw4EsCq/2h0t73SPnnFezu4YQWBmK4FxFaOUX0F+o8NsZuHKyfJzuZwyZbiktYmefM3yD9rM0Dj4BhNbw==", + "deprecated": "Package no longer supported. Contact Support at https://www.npmjs.com/support for more info.", "dev": true, "dependencies": { "@truffle/error": "^0.1.1", @@ -3064,9 +3013,9 @@ } }, "node_modules/@tsconfig/node10": { - "version": "1.0.9", - "resolved": "https://registry.npmjs.org/@tsconfig/node10/-/node10-1.0.9.tgz", - "integrity": "sha512-jNsYVVxU8v5g43Erja32laIDHXeoNvFEpX33OK4d6hljo3jDhCBDhx5dhCCTMWUojscpAagGiRkBKxpdl9fxqA==", + "version": "1.0.11", + "resolved": "https://registry.npmjs.org/@tsconfig/node10/-/node10-1.0.11.tgz", + "integrity": "sha512-DcRjDCujK/kCk/cUe8Xz8ZSpm8mS3mNNpta+jGCA6USEDfktlNvm1+IuZ9eTcDbNk41BHwpHHeW+N1lKCz4zOw==", "dev": true }, "node_modules/@tsconfig/node12": { @@ -3147,18 +3096,19 @@ } }, "node_modules/@typechain/hardhat/node_modules/universalify": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/universalify/-/universalify-2.0.0.tgz", - "integrity": "sha512-hAZsKq7Yy11Zu1DE0OzWjw7nnLZmJZYTDZZyEFHZdUhV8FkH5MCfoU1XMaxXovpyW5nq5scPqq0ZDP9Zyl04oQ==", + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/universalify/-/universalify-2.0.1.tgz", + "integrity": "sha512-gptHNQghINnc/vTGIk0SOFGFNXw7JVrlRUtConJRlvaw6DuX0wO5Jeko9sWrMBhh+PsYAZ7oXAiOnf/UKogyiw==", "dev": true, "engines": { "node": ">= 10.0.0" } }, "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==", + "version": "5.1.5", + "resolved": "https://registry.npmjs.org/@types/bn.js/-/bn.js-5.1.5.tgz", + "integrity": "sha512-V46N0zwKRF5Q00AZ6hWtN0T8gGmDUaUzLWQvHFo5yThtVwK/VCenFY3wXVbOvNfajEpsTfQM4IN9k/d6gUVX3A==", + "dev": true, "dependencies": { "@types/node": "*" } @@ -3176,9 +3126,9 @@ } }, "node_modules/@types/chai": { - "version": "4.3.5", - "resolved": "https://registry.npmjs.org/@types/chai/-/chai-4.3.5.tgz", - "integrity": "sha512-mEo1sAde+UCE6b2hxn332f1g1E8WfYRu6p5SvTKr2ZKC1f7gFJXk4h5PyGP9Dt6gCaG8y8XhwnXWC6Iy2cmBng==", + "version": "4.3.14", + "resolved": "https://registry.npmjs.org/@types/chai/-/chai-4.3.14.tgz", + "integrity": "sha512-Wj71sXE4Q4AkGdG9Tvq1u/fquNz9EdG4LIJMwVVII7ashjD/8cf8fyIfJAjRr6YcsXnSE8cOGQPq1gqeR8z+3w==", "dev": true }, "node_modules/@types/concat-stream": { @@ -3210,15 +3160,15 @@ } }, "node_modules/@types/http-cache-semantics": { - "version": "4.0.1", - "resolved": "https://registry.npmjs.org/@types/http-cache-semantics/-/http-cache-semantics-4.0.1.tgz", - "integrity": "sha512-SZs7ekbP8CN0txVG2xVRH6EgKmEm31BOxA07vkFaETzZz1xh+cbt8BcI0slpymvwhx5dlFnQG2rTlPVQn+iRPQ==", + "version": "4.0.4", + "resolved": "https://registry.npmjs.org/@types/http-cache-semantics/-/http-cache-semantics-4.0.4.tgz", + "integrity": "sha512-1m0bIFVc7eJWyve9S0RnuRgcQqF/Xd5QsUZAZeQFr1Q3/p9JWoQQEqmVy+DPTNpGXwhgIetAoYF8JSc33q29QA==", "dev": true }, "node_modules/@types/json-schema": { - "version": "7.0.12", - "resolved": "https://registry.npmjs.org/@types/json-schema/-/json-schema-7.0.12.tgz", - "integrity": "sha512-Hr5Jfhc9eYOQNPYO5WLDq/n4jqijdHNlDXjuAQkkt+mWdQR+XJToOHrsD4cPaMXpn6KO7y2+wM8AZEs8VpBLVA==", + "version": "7.0.15", + "resolved": "https://registry.npmjs.org/@types/json-schema/-/json-schema-7.0.15.tgz", + "integrity": "sha512-5+fP8P8MFNC+AyZCDxrB2pkZFPGzqQWUzpSeuuVLvm8VMcorNYavBqoFcxK8bQz4Qsbn4oUEEem4wDLfcysGHA==", "dev": true }, "node_modules/@types/json5": { @@ -3266,22 +3216,24 @@ "node_modules/@types/node": { "version": "12.20.55", "resolved": "https://registry.npmjs.org/@types/node/-/node-12.20.55.tgz", - "integrity": "sha512-J8xLz7q2OFulZ2cyGTLE1TbbZcjpno7FaN6zdJNrgAdrJ+DZzh/uFR6YrTb4C+nXakvud8Q4+rbhoIWlYQbUFQ==" + "integrity": "sha512-J8xLz7q2OFulZ2cyGTLE1TbbZcjpno7FaN6zdJNrgAdrJ+DZzh/uFR6YrTb4C+nXakvud8Q4+rbhoIWlYQbUFQ==", + "dev": true }, "node_modules/@types/node-fetch": { - "version": "2.6.4", - "resolved": "https://registry.npmjs.org/@types/node-fetch/-/node-fetch-2.6.4.tgz", - "integrity": "sha512-1ZX9fcN4Rvkvgv4E6PAY5WXUFWFcRWxZa3EW83UjycOB9ljJCedb2CupIP4RZMEwF/M3eTcCihbBRgwtGbg5Rg==", + "version": "2.6.11", + "resolved": "https://registry.npmjs.org/@types/node-fetch/-/node-fetch-2.6.11.tgz", + "integrity": "sha512-24xFj9R5+rfQJLRyM56qh+wnVSYhyXC2tkoBndtY0U+vubqNsYXGjufB2nn8Q6gt0LrARwL6UBtMCSVCwl4B1g==", "dev": true, "dependencies": { "@types/node": "*", - "form-data": "^3.0.0" + "form-data": "^4.0.0" } }, "node_modules/@types/pbkdf2": { - "version": "3.1.0", - "resolved": "https://registry.npmjs.org/@types/pbkdf2/-/pbkdf2-3.1.0.tgz", - "integrity": "sha512-Cf63Rv7jCQ0LaL8tNXmEyqTHuIJxRdlS5vMh1mj5voN4+QFhVZnlZruezqpWYDiJ8UTzhP0VmeLXCmBk66YrMQ==", + "version": "3.1.2", + "resolved": "https://registry.npmjs.org/@types/pbkdf2/-/pbkdf2-3.1.2.tgz", + "integrity": "sha512-uRwJqmiXmh9++aSu1VNEn3iIxWOhd8AHXNSdlaLfdAAdSTY9jYVeGWnzejM3dvrkbqE3/hyQkQQ29IFATEGlew==", + "dev": true, "dependencies": { "@types/node": "*" } @@ -3293,21 +3245,11 @@ "dev": true }, "node_modules/@types/qs": { - "version": "6.9.7", - "resolved": "https://registry.npmjs.org/@types/qs/-/qs-6.9.7.tgz", - "integrity": "sha512-FGa1F62FT09qcrueBA6qYTrJPVDzah9a+493+o2PCXsesWHIn27G98TsSMs3WPNbZIEj4+VJf6saSFpvD+3Zsw==", + "version": "6.9.15", + "resolved": "https://registry.npmjs.org/@types/qs/-/qs-6.9.15.tgz", + "integrity": "sha512-uXHQKES6DQKKCLh441Xv/dwxOq1TVS3JPUMlEqoEglvlhR6Mxnlew/Xq/LRVHpLyk7iK3zODe1qYHIMltO7XGg==", "dev": true }, - "node_modules/@types/readable-stream": { - "version": "2.3.15", - "resolved": "https://registry.npmjs.org/@types/readable-stream/-/readable-stream-2.3.15.tgz", - "integrity": "sha512-oM5JSKQCcICF1wvGgmecmHldZ48OZamtMxcGGVICOJA8o8cahXC1zEVAif8iwoc5j8etxFaRFnf095+CDsuoFQ==", - "dev": true, - "dependencies": { - "@types/node": "*", - "safe-buffer": "~5.1.1" - } - }, "node_modules/@types/resolve": { "version": "0.0.8", "resolved": "https://registry.npmjs.org/@types/resolve/-/resolve-0.0.8.tgz", @@ -3318,26 +3260,27 @@ } }, "node_modules/@types/responselike": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/@types/responselike/-/responselike-1.0.0.tgz", - "integrity": "sha512-85Y2BjiufFzaMIlvJDvTTB8Fxl2xfLo4HgmHzVBz08w4wDePCTjYw66PdrolO0kzli3yam/YCgRufyo1DdQVTA==", + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/@types/responselike/-/responselike-1.0.3.tgz", + "integrity": "sha512-H/+L+UkTV33uf49PH5pCAUBVPNj2nDBXTN+qS1dOwyyg24l3CcicicCA7ca+HMvJBZcFgl5r8e+RR6elsb4Lyw==", "dev": true, "dependencies": { "@types/node": "*" } }, "node_modules/@types/secp256k1": { - "version": "4.0.3", - "resolved": "https://registry.npmjs.org/@types/secp256k1/-/secp256k1-4.0.3.tgz", - "integrity": "sha512-Da66lEIFeIz9ltsdMZcpQvmrmmoqrfju8pm1BH8WbYjZSwUgCwXLb9C+9XYogwBITnbsSaMdVPb2ekf7TV+03w==", + "version": "4.0.6", + "resolved": "https://registry.npmjs.org/@types/secp256k1/-/secp256k1-4.0.6.tgz", + "integrity": "sha512-hHxJU6PAEUn0TP4S/ZOzuTUvJWuZ6eIKeNKb5RBpODvSl6hp1Wrw4s7ATY50rklRCScUDpHzVA/DQdSjJ3UoYQ==", + "dev": true, "dependencies": { "@types/node": "*" } }, "node_modules/@types/sinon": { - "version": "10.0.15", - "resolved": "https://registry.npmjs.org/@types/sinon/-/sinon-10.0.15.tgz", - "integrity": "sha512-3lrFNQG0Kr2LDzvjyjB6AMJk4ge+8iYhQfdnSwIwlG88FUOV43kPcQqDZkDa/h3WSZy6i8Fr0BSjfQtB1B3xuQ==", + "version": "17.0.3", + "resolved": "https://registry.npmjs.org/@types/sinon/-/sinon-17.0.3.tgz", + "integrity": "sha512-j3uovdn8ewky9kRBG19bOwaZbexJu/XjtkHyjvUgt4xfPFz18dcORIMqnYh66Fx3Powhcr85NT5+er3+oViapw==", "dev": true, "peer": true, "dependencies": { @@ -3345,9 +3288,9 @@ } }, "node_modules/@types/sinon-chai": { - "version": "3.2.9", - "resolved": "https://registry.npmjs.org/@types/sinon-chai/-/sinon-chai-3.2.9.tgz", - "integrity": "sha512-/19t63pFYU0ikrdbXKBWj9PCdnKyTd0Qkz0X91Ta081cYsq90OxYdcWwK/dwEoDa6dtXgj2HJfmzgq+QZTHdmQ==", + "version": "3.2.12", + "resolved": "https://registry.npmjs.org/@types/sinon-chai/-/sinon-chai-3.2.12.tgz", + "integrity": "sha512-9y0Gflk3b0+NhQZ/oxGtaAJDvRywCa5sIyaVnounqLvmf93yBF4EgIRspePtkMs3Tr844nCclYMlcCNmLCvjuQ==", "dev": true, "peer": true, "dependencies": { @@ -3356,9 +3299,9 @@ } }, "node_modules/@types/sinonjs__fake-timers": { - "version": "8.1.2", - "resolved": "https://registry.npmjs.org/@types/sinonjs__fake-timers/-/sinonjs__fake-timers-8.1.2.tgz", - "integrity": "sha512-9GcLXF0/v3t80caGs5p2rRfkB+a8VBGLJZVih6CNFkx8IZ994wiKKLSRs9nuFwk1HevWs/1mnUmkApGrSGsShA==", + "version": "8.1.5", + "resolved": "https://registry.npmjs.org/@types/sinonjs__fake-timers/-/sinonjs__fake-timers-8.1.5.tgz", + "integrity": "sha512-mQkU2jY8jJEF7YHjHvsQO8+3ughTL1mcnn96igfhONmR+fUPSKIkefQYpSe8bsly2Ep7oQbn/6VG5/9/0qcArQ==", "dev": true, "peer": true }, @@ -3394,43 +3337,10 @@ } } }, - "node_modules/@typescript-eslint/eslint-plugin/node_modules/lru-cache": { - "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" - }, - "engines": { - "node": ">=10" - } - }, - "node_modules/@typescript-eslint/eslint-plugin/node_modules/semver": { - "version": "7.5.4", - "resolved": "https://registry.npmjs.org/semver/-/semver-7.5.4.tgz", - "integrity": "sha512-1bCSESV6Pv+i21Hvpxp3Dx+pSD8lIPt8uVjRrxAUt/nbswYc+tK6Y2btiULjd4+fnq15PX+nqQDC7Oft7WkwcA==", - "dev": true, - "dependencies": { - "lru-cache": "^6.0.0" - }, - "bin": { - "semver": "bin/semver.js" - }, - "engines": { - "node": ">=10" - } - }, - "node_modules/@typescript-eslint/eslint-plugin/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/@typescript-eslint/experimental-utils": { - "version": "4.33.0", - "resolved": "https://registry.npmjs.org/@typescript-eslint/experimental-utils/-/experimental-utils-4.33.0.tgz", - "integrity": "sha512-zeQjOoES5JFjTnAhI5QY7ZviczMzDptls15GFsI6jyUOq0kOf9+WonkhtlIhh0RgHRnqj5gdNxW5j1EvAyYg6Q==", + "node_modules/@typescript-eslint/experimental-utils": { + "version": "4.33.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/experimental-utils/-/experimental-utils-4.33.0.tgz", + "integrity": "sha512-zeQjOoES5JFjTnAhI5QY7ZviczMzDptls15GFsI6jyUOq0kOf9+WonkhtlIhh0RgHRnqj5gdNxW5j1EvAyYg6Q==", "dev": true, "dependencies": { "@types/json-schema": "^7.0.7", @@ -3535,39 +3445,6 @@ } } }, - "node_modules/@typescript-eslint/typescript-estree/node_modules/lru-cache": { - "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" - }, - "engines": { - "node": ">=10" - } - }, - "node_modules/@typescript-eslint/typescript-estree/node_modules/semver": { - "version": "7.5.4", - "resolved": "https://registry.npmjs.org/semver/-/semver-7.5.4.tgz", - "integrity": "sha512-1bCSESV6Pv+i21Hvpxp3Dx+pSD8lIPt8uVjRrxAUt/nbswYc+tK6Y2btiULjd4+fnq15PX+nqQDC7Oft7WkwcA==", - "dev": true, - "dependencies": { - "lru-cache": "^6.0.0" - }, - "bin": { - "semver": "bin/semver.js" - }, - "engines": { - "node": ">=10" - } - }, - "node_modules/@typescript-eslint/typescript-estree/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/@typescript-eslint/visitor-keys": { "version": "4.33.0", "resolved": "https://registry.npmjs.org/@typescript-eslint/visitor-keys/-/visitor-keys-4.33.0.tgz", @@ -3597,42 +3474,12 @@ "integrity": "sha512-LEyx4aLEC3x6T0UguF6YILf+ntvmOaWsVfENmIW0E9H09vKlLDGelMjjSm0jkDHALj8A8quZ/HapKNigzwge+Q==", "dev": true }, - "node_modules/abort-controller": { - "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" - }, - "engines": { - "node": ">=6.5" - } - }, "node_modules/abortcontroller-polyfill": { "version": "1.7.5", "resolved": "https://registry.npmjs.org/abortcontroller-polyfill/-/abortcontroller-polyfill-1.7.5.tgz", "integrity": "sha512-JMJ5soJWP18htbbxJjG7bG6yuI6pRhgJ0scHHTfkUjf6wjP912xZWvM+A4sJK3gqd9E8fcPbDnOefbA9Th/FIQ==", "dev": true }, - "node_modules/abstract-level": { - "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", - "is-buffer": "^2.0.5", - "level-supports": "^4.0.0", - "level-transcoder": "^1.0.1", - "module-error": "^1.0.1", - "queue-microtask": "^1.2.3" - }, - "engines": { - "node": ">=12" - } - }, "node_modules/accepts": { "version": "1.3.8", "resolved": "https://registry.npmjs.org/accepts/-/accepts-1.3.8.tgz", @@ -3668,9 +3515,9 @@ } }, "node_modules/acorn-walk": { - "version": "8.2.0", - "resolved": "https://registry.npmjs.org/acorn-walk/-/acorn-walk-8.2.0.tgz", - "integrity": "sha512-k+iyHEuPgSw6SbuDpGQM+06HQUa04DZ3o+F6CSzXMvvI5KMvnaEqXe+YVe555R9nn6GPt404fos4wcgpw12SDA==", + "version": "8.3.2", + "resolved": "https://registry.npmjs.org/acorn-walk/-/acorn-walk-8.3.2.tgz", + "integrity": "sha512-cjkyv4OtNCIeqhHrfS81QWXoCBPExR/J62oyEqepVw8WaQeSqpW2uhuLPh1m9eWhDuOo/jUXVTlifvesOWp/4A==", "dev": true, "engines": { "node": ">=0.4.0" @@ -3750,6 +3597,15 @@ "node": ">=0.4.2" } }, + "node_modules/ansi-align": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/ansi-align/-/ansi-align-3.0.1.tgz", + "integrity": "sha512-IOfwwBF5iczOjp/WeY4YxyjqAFMQoZufdQWDd19SEExbVLNXqvpzSJ/M7Za4/sCPmQ0+GRquoA7bGcINcxew6w==", + "dev": true, + "dependencies": { + "string-width": "^4.1.0" + } + }, "node_modules/ansi-colors": { "version": "4.1.3", "resolved": "https://registry.npmjs.org/ansi-colors/-/ansi-colors-4.1.3.tgz", @@ -3810,9 +3666,9 @@ } }, "node_modules/antlr4": { - "version": "4.13.0", - "resolved": "https://registry.npmjs.org/antlr4/-/antlr4-4.13.0.tgz", - "integrity": "sha512-zooUbt+UscjnWyOrsuY/tVFL4rwrAGwOivpQmvmUDE22hy/lUA467Rc1rcixyRwcRUIXFYBwv7+dClDSHdmmew==", + "version": "4.13.1", + "resolved": "https://registry.npmjs.org/antlr4/-/antlr4-4.13.1.tgz", + "integrity": "sha512-kiXTspaRYvnIArgE97z5YVVf/cDVQABr3abFRR6mE7yesLMkgu4ujuyV/sgxafQ8wgve0DJQUJ38Z8tkgA2izA==", "dev": true, "engines": { "node": ">=16" @@ -3865,13 +3721,16 @@ } }, "node_modules/array-buffer-byte-length": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/array-buffer-byte-length/-/array-buffer-byte-length-1.0.0.tgz", - "integrity": "sha512-LPuwb2P+NrQw3XhxGc36+XSvuBPopovXYTR9Ew++Du9Yb/bx5AzBfrIsBoj0EZUifjQU+sHL21sseZ3jerWO/A==", + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/array-buffer-byte-length/-/array-buffer-byte-length-1.0.1.tgz", + "integrity": "sha512-ahC5W1xgou+KTXix4sAO8Ki12Q+jf4i0+tmk3sC+zgcynshkHxzpXdImBehiUYKKKDwvfFiJl1tZt6ewscS1Mg==", "dev": true, "dependencies": { - "call-bind": "^1.0.2", - "is-array-buffer": "^3.0.1" + "call-bind": "^1.0.5", + "is-array-buffer": "^3.0.4" + }, + "engines": { + "node": ">= 0.4" }, "funding": { "url": "https://github.com/sponsors/ljharb" @@ -3884,15 +3743,16 @@ "dev": true }, "node_modules/array-includes": { - "version": "3.1.6", - "resolved": "https://registry.npmjs.org/array-includes/-/array-includes-3.1.6.tgz", - "integrity": "sha512-sgTbLvL6cNnw24FnbaDyjmvddQ2ML8arZsgaJhoABMoplz/4QRhtrYS+alr1BUM1Bwp6dhx8vVCBSLG+StwOFw==", + "version": "3.1.8", + "resolved": "https://registry.npmjs.org/array-includes/-/array-includes-3.1.8.tgz", + "integrity": "sha512-itaWrbYbqpGXkGhZPGUulwnhVf5Hpy1xiCFsGqyIGglbBxmG5vSjxQen3/WGOjPpNEv1RtBLKxbmVXm8HpJStQ==", "dev": true, "dependencies": { - "call-bind": "^1.0.2", - "define-properties": "^1.1.4", - "es-abstract": "^1.20.4", - "get-intrinsic": "^1.1.3", + "call-bind": "^1.0.7", + "define-properties": "^1.2.1", + "es-abstract": "^1.23.2", + "es-object-atoms": "^1.0.0", + "get-intrinsic": "^1.2.4", "is-string": "^1.0.7" }, "engines": { @@ -3920,16 +3780,18 @@ "node": ">=0.10.0" } }, - "node_modules/array.prototype.flat": { - "version": "1.3.1", - "resolved": "https://registry.npmjs.org/array.prototype.flat/-/array.prototype.flat-1.3.1.tgz", - "integrity": "sha512-roTU0KWIOmJ4DRLmwKd19Otg0/mT3qPNt0Qb3GWW8iObuZXxrjB/pzn0R3hqpRSWg4HCwqx+0vwOnWnvlOyeIA==", + "node_modules/array.prototype.findlastindex": { + "version": "1.2.5", + "resolved": "https://registry.npmjs.org/array.prototype.findlastindex/-/array.prototype.findlastindex-1.2.5.tgz", + "integrity": "sha512-zfETvRFA8o7EiNn++N5f/kaCw221hrpGsDmcpndVupkPzEc1Wuf3VgC0qby1BbHs7f5DVYjgtEU2LLh5bqeGfQ==", "dev": true, "dependencies": { - "call-bind": "^1.0.2", - "define-properties": "^1.1.4", - "es-abstract": "^1.20.4", - "es-shim-unscopables": "^1.0.0" + "call-bind": "^1.0.7", + "define-properties": "^1.2.1", + "es-abstract": "^1.23.2", + "es-errors": "^1.3.0", + "es-object-atoms": "^1.0.0", + "es-shim-unscopables": "^1.0.2" }, "engines": { "node": ">= 0.4" @@ -3938,15 +3800,15 @@ "url": "https://github.com/sponsors/ljharb" } }, - "node_modules/array.prototype.flatmap": { - "version": "1.3.1", - "resolved": "https://registry.npmjs.org/array.prototype.flatmap/-/array.prototype.flatmap-1.3.1.tgz", - "integrity": "sha512-8UGn9O1FDVvMNB0UlLv4voxRMze7+FpHyF5mSMRjWHUMlpoDViniy05870VlxhfgTnLbpuwTzvD76MTtWxB/mQ==", + "node_modules/array.prototype.flat": { + "version": "1.3.2", + "resolved": "https://registry.npmjs.org/array.prototype.flat/-/array.prototype.flat-1.3.2.tgz", + "integrity": "sha512-djYB+Zx2vLewY8RWlNCUdHjDXs2XOgm602S9E7P/UpHgfeHL00cRiIF+IN/G/aUJ7kGPb6yO/ErDI5V2s8iycA==", "dev": true, "dependencies": { "call-bind": "^1.0.2", - "define-properties": "^1.1.4", - "es-abstract": "^1.20.4", + "define-properties": "^1.2.0", + "es-abstract": "^1.22.1", "es-shim-unscopables": "^1.0.0" }, "engines": { @@ -3956,17 +3818,16 @@ "url": "https://github.com/sponsors/ljharb" } }, - "node_modules/array.prototype.reduce": { - "version": "1.0.5", - "resolved": "https://registry.npmjs.org/array.prototype.reduce/-/array.prototype.reduce-1.0.5.tgz", - "integrity": "sha512-kDdugMl7id9COE8R7MHF5jWk7Dqt/fs4Pv+JXoICnYwqpjjjbUurz6w5fT5IG6brLdJhv6/VoHB0H7oyIBXd+Q==", + "node_modules/array.prototype.flatmap": { + "version": "1.3.2", + "resolved": "https://registry.npmjs.org/array.prototype.flatmap/-/array.prototype.flatmap-1.3.2.tgz", + "integrity": "sha512-Ewyx0c9PmpcsByhSW4r+9zDU7sGjFc86qf/kKtuSCRdhfbk0SNLLkaT5qvcHnRGgc5NP/ly/y+qkXkqONX54CQ==", "dev": true, "dependencies": { "call-bind": "^1.0.2", - "define-properties": "^1.1.4", - "es-abstract": "^1.20.4", - "es-array-method-boxes-properly": "^1.0.0", - "is-string": "^1.0.7" + "define-properties": "^1.2.0", + "es-abstract": "^1.22.1", + "es-shim-unscopables": "^1.0.0" }, "engines": { "node": ">= 0.4" @@ -3976,16 +3837,18 @@ } }, "node_modules/arraybuffer.prototype.slice": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/arraybuffer.prototype.slice/-/arraybuffer.prototype.slice-1.0.1.tgz", - "integrity": "sha512-09x0ZWFEjj4WD8PDbykUwo3t9arLn8NIzmmYEJFpYekOAQjpkGSyrQhNoRTcwwcFRu+ycWF78QZ63oWTqSjBcw==", + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/arraybuffer.prototype.slice/-/arraybuffer.prototype.slice-1.0.3.tgz", + "integrity": "sha512-bMxMKAjg13EBSVscxTaYA4mRc5t1UAXa2kXiGTNfZ079HIWXEkKmkgFrh/nJqamaLSrXO5H4WFFkPEaLJWbs3A==", "dev": true, "dependencies": { - "array-buffer-byte-length": "^1.0.0", - "call-bind": "^1.0.2", - "define-properties": "^1.2.0", - "get-intrinsic": "^1.2.1", - "is-array-buffer": "^3.0.2", + "array-buffer-byte-length": "^1.0.1", + "call-bind": "^1.0.5", + "define-properties": "^1.2.1", + "es-abstract": "^1.22.3", + "es-errors": "^1.2.1", + "get-intrinsic": "^1.2.3", + "is-array-buffer": "^3.0.4", "is-shared-array-buffer": "^1.0.2" }, "engines": { @@ -4011,15 +3874,14 @@ } }, "node_modules/asn1.js": { - "version": "5.4.1", - "resolved": "https://registry.npmjs.org/asn1.js/-/asn1.js-5.4.1.tgz", - "integrity": "sha512-+I//4cYPccV8LdmBLiX8CYvf9Sp3vQsrqu2QNXRcrbiWvcx/UdlFiqUJJzxRQxgsZmvhXhn4cSKeSmoFjVdupA==", + "version": "4.10.1", + "resolved": "https://registry.npmjs.org/asn1.js/-/asn1.js-4.10.1.tgz", + "integrity": "sha512-p32cOF5q0Zqs9uBiONKYLm6BClCoBCM5O9JfeUSlnQLBTxYdTK+pW+nXflm8UkKd2UYlEbYz5qEi0JuZR9ckSw==", "dev": true, "dependencies": { "bn.js": "^4.0.0", "inherits": "^2.0.1", - "minimalistic-assert": "^1.0.0", - "safer-buffer": "^2.1.0" + "minimalistic-assert": "^1.0.0" } }, "node_modules/asn1.js/node_modules/bn.js": { @@ -4088,10 +3950,13 @@ } }, "node_modules/available-typed-arrays": { - "version": "1.0.5", - "resolved": "https://registry.npmjs.org/available-typed-arrays/-/available-typed-arrays-1.0.5.tgz", - "integrity": "sha512-DMD0KiN46eipeziST1LPP/STfDU0sufISXmjSgvVsoU2tqxctQeASejWcfNtxYKqETM1UxQ8sp2OrSBWpHY6sw==", + "version": "1.0.7", + "resolved": "https://registry.npmjs.org/available-typed-arrays/-/available-typed-arrays-1.0.7.tgz", + "integrity": "sha512-wvUjBtSGN7+7SjNpq/9M2Tg350UZD3q62IFZLbRAR1bSMlCo1ZaeW+BJ+D090e4hIIZLBcTDWe4Mh4jvUDajzQ==", "dev": true, + "dependencies": { + "possible-typed-array-names": "^1.0.0" + }, "engines": { "node": ">= 0.4" }, @@ -4114,6 +3979,17 @@ "integrity": "sha512-NmWvPnx0F1SfrQbYwOi7OeaNGokp9XhzNioJ/CSBs8Qa4vxug81mhJEAVZwxXuBmYB5KDRfMq/F3RR0BIU7sWg==", "dev": true }, + "node_modules/axios": { + "version": "1.6.8", + "resolved": "https://registry.npmjs.org/axios/-/axios-1.6.8.tgz", + "integrity": "sha512-v/ZHtJDU39mDpyBoFVkETcd/uNdxrWRrg3bKpOKzXFA6Bvqopts6ALSMU3y6ijYxbw2B+wPrIv46egTzJXCLGQ==", + "dev": true, + "dependencies": { + "follow-redirects": "^1.15.6", + "form-data": "^4.0.0", + "proxy-from-env": "^1.1.0" + } + }, "node_modules/balanced-match": { "version": "1.0.2", "resolved": "https://registry.npmjs.org/balanced-match/-/balanced-match-1.0.2.tgz", @@ -4124,6 +4000,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" } @@ -4167,36 +4044,31 @@ "resolved": "https://registry.npmjs.org/bech32/-/bech32-1.1.4.tgz", "integrity": "sha512-s0IrSOzLlbvX7yp4WBfPITzpAU8sqQcpsmwXDiKwrG4r491vwCO/XpejasRNl0piBMe/DvP4Tz0mIS/X1DPJBQ==" }, - "node_modules/bigint-crypto-utils": { - "version": "3.3.0", - "resolved": "https://registry.npmjs.org/bigint-crypto-utils/-/bigint-crypto-utils-3.3.0.tgz", - "integrity": "sha512-jOTSb+drvEDxEq6OuUybOAv/xxoh3cuYRUIPyu8sSHQNKM303UQ2R1DAo45o1AkcIXw6fzbaFI1+xGGdaXs2lg==", - "dev": true, - "engines": { - "node": ">=14.0.0" - } - }, "node_modules/bignumber.js": { - "version": "9.1.1", - "resolved": "https://registry.npmjs.org/bignumber.js/-/bignumber.js-9.1.1.tgz", - "integrity": "sha512-pHm4LsMJ6lzgNGVfZHjMoO8sdoRhOzOH4MLmY65Jg70bpxCKu5iOHNJyfF6OyvYw7t8Fpf35RuzUyqnQsj8Vig==", + "version": "9.1.2", + "resolved": "https://registry.npmjs.org/bignumber.js/-/bignumber.js-9.1.2.tgz", + "integrity": "sha512-2/mKyZH9K85bzOEfhXDBFZTGd1CTs+5IHpeFQo9luiBG7hghdC851Pj2WAhb6E3R6b9tZj/XKhbg4fum+Kepug==", "engines": { "node": "*" } }, "node_modules/binary-extensions": { - "version": "2.2.0", - "resolved": "https://registry.npmjs.org/binary-extensions/-/binary-extensions-2.2.0.tgz", - "integrity": "sha512-jDctJ/IVQbZoJykoeHbhXpOlNBqGNcwXJKJog42E5HDPUwQTSdjCHdihjj0DlnheQ7blbT6dHOafNAiS8ooQKA==", + "version": "2.3.0", + "resolved": "https://registry.npmjs.org/binary-extensions/-/binary-extensions-2.3.0.tgz", + "integrity": "sha512-Ceh+7ox5qe7LJuLHoY0feh3pHuUDHAcRUeyL2VYghZwfpkNIy/+8Ocg0a3UuSoYzavmylwuLWQOf3hl0jjMMIw==", "dev": true, "engines": { "node": ">=8" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" } }, "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", @@ -4263,6 +4135,28 @@ "url": "https://github.com/sponsors/ljharb" } }, + "node_modules/boxen": { + "version": "5.1.2", + "resolved": "https://registry.npmjs.org/boxen/-/boxen-5.1.2.tgz", + "integrity": "sha512-9gYgQKXx+1nP8mP7CzFyaUARhg7D3n1dF/FnErWmu9l6JvGpNUN278h0aSb+QjoiKSWG+iZ3uHrcqk0qrY9RQQ==", + "dev": true, + "dependencies": { + "ansi-align": "^3.0.0", + "camelcase": "^6.2.0", + "chalk": "^4.1.0", + "cli-boxes": "^2.2.1", + "string-width": "^4.2.2", + "type-fest": "^0.20.2", + "widest-line": "^3.1.0", + "wrap-ansi": "^7.0.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, "node_modules/brace-expansion": { "version": "1.1.11", "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.11.tgz", @@ -4290,18 +4184,6 @@ "resolved": "https://registry.npmjs.org/brorand/-/brorand-1.1.0.tgz", "integrity": "sha512-cKV8tMCEpQs4hK/ik71d6LrPOnpkpGBR0wzxqr68g2m/LB2GxVYQroAjMJZRVM1Y4BCjCKc3vAamxSzOY2RP+w==" }, - "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", - "module-error": "^1.0.2", - "run-parallel-limit": "^1.1.0" - } - }, "node_modules/browser-stdout": { "version": "1.3.1", "resolved": "https://registry.npmjs.org/browser-stdout/-/browser-stdout-1.3.1.tgz", @@ -4312,6 +4194,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", @@ -4355,49 +4238,107 @@ } }, "node_modules/browserify-sign": { - "version": "4.2.2", - "resolved": "https://registry.npmjs.org/browserify-sign/-/browserify-sign-4.2.2.tgz", - "integrity": "sha512-1rudGyeYY42Dk6texmv7c4VcQ0EsvVbLwZkA+AQB7SxvXxmcD93jcHie8bzecJ+ChDlmAm2Qyu0+Ccg5uhZXCg==", + "version": "4.2.3", + "resolved": "https://registry.npmjs.org/browserify-sign/-/browserify-sign-4.2.3.tgz", + "integrity": "sha512-JWCZW6SKhfhjJxO8Tyiiy+XYB7cqd2S5/+WeYHsKdNKFlCBhKbblba1A/HN/90YwtxKc8tCErjffZl++UNmGiw==", "dev": true, "dependencies": { "bn.js": "^5.2.1", "browserify-rsa": "^4.1.0", "create-hash": "^1.2.0", "create-hmac": "^1.1.7", - "elliptic": "^6.5.4", + "elliptic": "^6.5.5", + "hash-base": "~3.0", "inherits": "^2.0.4", - "parse-asn1": "^5.1.6", - "readable-stream": "^3.6.2", + "parse-asn1": "^5.1.7", + "readable-stream": "^2.3.8", "safe-buffer": "^5.2.1" }, "engines": { - "node": ">= 4" + "node": ">= 0.12" } }, - "node_modules/browserify-sign/node_modules/safe-buffer": { - "version": "5.2.1", - "resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.2.1.tgz", - "integrity": "sha512-rp3So07KcdmmKbGvgaNxQSJr7bGVSVk5S9Eq1F+ppbRo70+YeaDxkw5Dd8NPN+GD6bjnYm2VuPuCXmpuYvmCXQ==", + "node_modules/browserify-sign/node_modules/elliptic": { + "version": "6.5.5", + "resolved": "https://registry.npmjs.org/elliptic/-/elliptic-6.5.5.tgz", + "integrity": "sha512-7EjbcmUm17NQFu4Pmgmq2olYMj8nwMnpcddByChSUjArp8F5DQWcIcpriwO4ZToLNAJig0yiyjswfyGNje/ixw==", "dev": true, - "funding": [ - { - "type": "github", - "url": "https://github.com/sponsors/feross" - }, - { - "type": "patreon", - "url": "https://www.patreon.com/feross" - }, - { - "type": "consulting", - "url": "https://feross.org/support" - } - ] + "dependencies": { + "bn.js": "^4.11.9", + "brorand": "^1.1.0", + "hash.js": "^1.0.0", + "hmac-drbg": "^1.0.1", + "inherits": "^2.0.4", + "minimalistic-assert": "^1.0.1", + "minimalistic-crypto-utils": "^1.0.1" + } + }, + "node_modules/browserify-sign/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==", + "dev": true + }, + "node_modules/browserify-sign/node_modules/hash-base": { + "version": "3.0.4", + "resolved": "https://registry.npmjs.org/hash-base/-/hash-base-3.0.4.tgz", + "integrity": "sha512-EeeoJKjTyt868liAlVmcv2ZsUfGHlE3Q+BICOXcZiwN3osr5Q/zFGYmTJpoIzuaSTAwndFy+GqhEwlU4L3j4Ow==", + "dev": true, + "dependencies": { + "inherits": "^2.0.1", + "safe-buffer": "^5.0.1" + }, + "engines": { + "node": ">=4" + } + }, + "node_modules/browserify-sign/node_modules/isarray": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/isarray/-/isarray-1.0.0.tgz", + "integrity": "sha512-VLghIWNM6ELQzo7zwmcg0NmTVyWKYjvIeM83yjp0wRDTmUnrM678fQbcKBo6n2CJEF0szoG//ytg+TKla89ALQ==", + "dev": true + }, + "node_modules/browserify-sign/node_modules/readable-stream": { + "version": "2.3.8", + "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-2.3.8.tgz", + "integrity": "sha512-8p0AUk4XODgIewSi0l8Epjs+EVnWiK7NoDIEGU0HhE7+ZyY8D1IMY7odu5lRrFXGg71L15KG8QrPmum45RTtdA==", + "dev": true, + "dependencies": { + "core-util-is": "~1.0.0", + "inherits": "~2.0.3", + "isarray": "~1.0.0", + "process-nextick-args": "~2.0.0", + "safe-buffer": "~5.1.1", + "string_decoder": "~1.1.1", + "util-deprecate": "~1.0.1" + } + }, + "node_modules/browserify-sign/node_modules/readable-stream/node_modules/safe-buffer": { + "version": "5.1.2", + "resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.1.2.tgz", + "integrity": "sha512-Gd2UZBJDkXlY7GbJxfsE8/nvKkUEU1G38c1siN6QP6a9PT9MmHB8GnpscSmMJSoF8LOIrt8ud/wPtojys4G6+g==", + "dev": true + }, + "node_modules/browserify-sign/node_modules/string_decoder": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/string_decoder/-/string_decoder-1.1.1.tgz", + "integrity": "sha512-n/ShnvDi6FHbbVfviro+WojiFzv+s8MPMHBczVePfUpDJLwoLT0ht1l4YwBCbi8pJAveEEdnkHyPyTP/mzRfwg==", + "dev": true, + "dependencies": { + "safe-buffer": "~5.1.0" + } + }, + "node_modules/browserify-sign/node_modules/string_decoder/node_modules/safe-buffer": { + "version": "5.1.2", + "resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.1.2.tgz", + "integrity": "sha512-Gd2UZBJDkXlY7GbJxfsE8/nvKkUEU1G38c1siN6QP6a9PT9MmHB8GnpscSmMJSoF8LOIrt8ud/wPtojys4G6+g==", + "dev": true }, "node_modules/bs58": { "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" } @@ -4406,6 +4347,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", @@ -4455,12 +4397,13 @@ "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/bufferutil": { - "version": "4.0.7", - "resolved": "https://registry.npmjs.org/bufferutil/-/bufferutil-4.0.7.tgz", - "integrity": "sha512-kukuqc39WOHtdxtw4UScxF/WVnMFVSQVKhtx3AjZJzhd0RGZZldcrfSEbVsWWe6KNH253574cq5F+wpv0G9pJw==", + "version": "4.0.8", + "resolved": "https://registry.npmjs.org/bufferutil/-/bufferutil-4.0.8.tgz", + "integrity": "sha512-4T53u4PdgsXqKaIctwF8ifXlRTTmEPJ8iEPWFdGZvcf7sbwYo6FKFEX9eNNAnzFZ7EzJAQ3CJeOtCRA4rDp7Pw==", "devOptional": true, "hasInstallScript": true, "dependencies": { @@ -4471,11 +4414,11 @@ } }, "node_modules/bufio": { - "version": "1.2.0", - "resolved": "https://registry.npmjs.org/bufio/-/bufio-1.2.0.tgz", - "integrity": "sha512-UlFk8z/PwdhYQTXSQQagwGAdtRI83gib2n4uy4rQnenxUM2yQi8lBDzF230BNk+3wAoZDxYRoBwVVUPgHa9MCA==", + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/bufio/-/bufio-1.2.1.tgz", + "integrity": "sha512-9oR3zNdupcg/Ge2sSHQF3GX+kmvL/fTPvD0nd5AGLq8SjUYnTz+SlFjK/GXidndbZtIj+pVKXiWeR9w6e9wKCA==", "engines": { - "node": ">=8.0.0" + "node": ">=14.0.0" } }, "node_modules/bytes": { @@ -4529,6 +4472,21 @@ "url": "https://github.com/sponsors/sindresorhus" } }, + "node_modules/cacheable-request/node_modules/json-buffer": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/json-buffer/-/json-buffer-3.0.0.tgz", + "integrity": "sha512-CuUqjv0FUZIdXkHPI8MezCnFCdaTAacej1TZYulLoAg1h/PhwkdXFN4V/gzY4g+fMBCOV2xF+rp7t2XD2ns/NQ==", + "dev": true + }, + "node_modules/cacheable-request/node_modules/keyv": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/keyv/-/keyv-3.1.0.tgz", + "integrity": "sha512-9ykJ/46SN/9KPM/sichzQ7OvXyGDYKGTaDlKMGCAlg2UK8KRy4jb0d8sFc+0Tt0YYnThq8X2RZgCg74RPxgcVA==", + "dev": true, + "dependencies": { + "json-buffer": "3.0.0" + } + }, "node_modules/cacheable-request/node_modules/lowercase-keys": { "version": "2.0.0", "resolved": "https://registry.npmjs.org/lowercase-keys/-/lowercase-keys-2.0.0.tgz", @@ -4539,16 +4497,22 @@ } }, "node_modules/call-bind": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/call-bind/-/call-bind-1.0.2.tgz", - "integrity": "sha512-7O+FbCihrB5WGbFYesctwmTKae6rOiIzmz1icreWJ+0aA7LJfuqhEso2T9ncpcFtzMQtzXf2QGGueWJGTYsqrA==", + "version": "1.0.7", + "resolved": "https://registry.npmjs.org/call-bind/-/call-bind-1.0.7.tgz", + "integrity": "sha512-GHTSNSYICQ7scH7sZ+M2rFopRoLh8t2bLSW6BbgrtLsahOIB5iyAVJf9GjWK3cYTDaMj4XdBpM1cA6pIS0Kv2w==", "dev": true, "dependencies": { - "function-bind": "^1.1.1", - "get-intrinsic": "^1.0.2" + "es-define-property": "^1.0.0", + "es-errors": "^1.3.0", + "function-bind": "^1.1.2", + "get-intrinsic": "^1.2.4", + "set-function-length": "^1.2.1" }, - "funding": { - "url": "https://github.com/sponsors/ljharb" + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" } }, "node_modules/callsites": { @@ -4572,30 +4536,12 @@ "url": "https://github.com/sponsors/sindresorhus" } }, - "node_modules/case": { - "version": "1.6.3", - "resolved": "https://registry.npmjs.org/case/-/case-1.6.3.tgz", - "integrity": "sha512-mzDSXIPaFwVDvZAHqZ9VlbyF4yyXRuX6IvB06WvPYkqJVO24kX1PPhv9bfpKNFZyxYFmmgo03HUiD8iklmJYRQ==", - "dev": true, - "engines": { - "node": ">= 0.8.0" - } - }, "node_modules/caseless": { "version": "0.12.0", "resolved": "https://registry.npmjs.org/caseless/-/caseless-0.12.0.tgz", "integrity": "sha512-4tYFyifaFfGacoiObjJegolkwSU4xQNGbVgUiNYVUxbQ2x2lUsFvY4hVgVzGiIe6WLOPqycWXA40l+PWsxthUw==", "dev": true }, - "node_modules/catering": { - "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" - } - }, "node_modules/cbor": { "version": "8.1.0", "resolved": "https://registry.npmjs.org/cbor/-/cbor-8.1.0.tgz", @@ -4661,16 +4607,10 @@ } }, "node_modules/chokidar": { - "version": "3.5.3", - "resolved": "https://registry.npmjs.org/chokidar/-/chokidar-3.5.3.tgz", - "integrity": "sha512-Dr3sfKRP6oTcjf2JmUmFJfeVMvXBdegxB0iVQ5eb2V10uFJUCAS8OByZdVAyVb8xXNz3GjjTgj9kLWsZTqE6kw==", + "version": "3.6.0", + "resolved": "https://registry.npmjs.org/chokidar/-/chokidar-3.6.0.tgz", + "integrity": "sha512-7VT13fmjotKpGipCW9JEQAusEPE+Ei8nl6/g4FBAmIm0GOOLMua9NDDo/DWp0ZAxCr3cPq5ZpBqmPAQgDda2Pw==", "dev": true, - "funding": [ - { - "type": "individual", - "url": "https://paulmillr.com/funding/" - } - ], "dependencies": { "anymatch": "~3.1.2", "braces": "~3.0.2", @@ -4683,6 +4623,9 @@ "engines": { "node": ">= 8.10.0" }, + "funding": { + "url": "https://paulmillr.com/funding/" + }, "optionalDependencies": { "fsevents": "~2.3.2" } @@ -4756,6 +4699,7 @@ "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" @@ -4767,23 +4711,6 @@ "integrity": "sha512-rhjH9AG1fvabIDoGRVH587413LPjTZgmDF9fOFCbFJQV4yuocX1mHxxvXI4g3cGwbVY9wAYIoKlg1N79frJKQw==", "dev": true }, - "node_modules/classic-level": { - "version": "1.3.0", - "resolved": "https://registry.npmjs.org/classic-level/-/classic-level-1.3.0.tgz", - "integrity": "sha512-iwFAJQYtqRTRM0F6L8h4JCt00ZSGdOyqh7yVrhhjrOpFhmBjNlRUey64MCiyo6UmQHMJ+No3c81nujPv+n9yrg==", - "dev": true, - "hasInstallScript": true, - "dependencies": { - "abstract-level": "^1.0.2", - "catering": "^2.1.0", - "module-error": "^1.0.1", - "napi-macros": "^2.2.2", - "node-gyp-build": "^4.3.0" - }, - "engines": { - "node": ">=12" - } - }, "node_modules/clean-stack": { "version": "2.2.0", "resolved": "https://registry.npmjs.org/clean-stack/-/clean-stack-2.2.0.tgz", @@ -4793,6 +4720,18 @@ "node": ">=6" } }, + "node_modules/cli-boxes": { + "version": "2.2.1", + "resolved": "https://registry.npmjs.org/cli-boxes/-/cli-boxes-2.2.1.tgz", + "integrity": "sha512-y4coMcylgSCdVinjiDBuR8PCC2bLjyGTwEmPb9NHR/QaNU6EUOXcTY/s6VjGMD6ENSEaeQYHCY0GNGS5jfMwPw==", + "dev": true, + "engines": { + "node": ">=6" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, "node_modules/cli-table3": { "version": "0.5.1", "resolved": "https://registry.npmjs.org/cli-table3/-/cli-table3-0.5.1.tgz", @@ -4809,38 +4748,58 @@ "colors": "^1.1.2" } }, - "node_modules/cliui": { - "version": "7.0.4", - "resolved": "https://registry.npmjs.org/cliui/-/cliui-7.0.4.tgz", - "integrity": "sha512-OcRE68cOsVMXp1Yvonl/fzkQOyjLSu/8bhPDfQt0e0/Eb283TKP20Fs2MqoPsr9SwA595rRCA+QMzYc9nBP+JQ==", + "node_modules/cli-table3/node_modules/ansi-regex": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-3.0.1.tgz", + "integrity": "sha512-+O9Jct8wf++lXxxFc4hc8LsjaSq0HFzzL7cVsw8pRDIPdjKD2mT4ytDZlLuSBZ4cLKZFXIrMGO7DbQCtMJJMKw==", "dev": true, - "dependencies": { - "string-width": "^4.2.0", - "strip-ansi": "^6.0.0", - "wrap-ansi": "^7.0.0" + "engines": { + "node": ">=4" } }, - "node_modules/cliui/node_modules/is-fullwidth-code-point": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/is-fullwidth-code-point/-/is-fullwidth-code-point-3.0.0.tgz", - "integrity": "sha512-zymm5+u+sCsSWyD9qNaejV3DFvhCKclKdizYaJUuHA83RLjb7nSuGnddCHGv0hk+KY7BMAlsWeK4Ueg6EV6XQg==", + "node_modules/cli-table3/node_modules/is-fullwidth-code-point": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/is-fullwidth-code-point/-/is-fullwidth-code-point-2.0.0.tgz", + "integrity": "sha512-VHskAKYM8RfSFXwee5t5cbN5PZeq1Wrh6qd5bkyiXIf6UQcN6w/A0eXM9r6t8d+GYOh+o6ZhiEnb88LN/Y8m2w==", "dev": true, "engines": { - "node": ">=8" + "node": ">=4" } }, - "node_modules/cliui/node_modules/string-width": { - "version": "4.2.3", - "resolved": "https://registry.npmjs.org/string-width/-/string-width-4.2.3.tgz", - "integrity": "sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g==", + "node_modules/cli-table3/node_modules/string-width": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/string-width/-/string-width-2.1.1.tgz", + "integrity": "sha512-nOqH59deCq9SRHlxq1Aw85Jnt4w6KvLKqWVik6oA9ZklXLNIOlqg4F2yrT1MVaTjAqvVwdfeZ7w7aCvJD7ugkw==", "dev": true, "dependencies": { - "emoji-regex": "^8.0.0", - "is-fullwidth-code-point": "^3.0.0", - "strip-ansi": "^6.0.1" + "is-fullwidth-code-point": "^2.0.0", + "strip-ansi": "^4.0.0" }, "engines": { - "node": ">=8" + "node": ">=4" + } + }, + "node_modules/cli-table3/node_modules/strip-ansi": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-4.0.0.tgz", + "integrity": "sha512-4XaJ2zQdCzROZDivEVIDPkcQn8LMFSa8kj8Gxb/Lnwzv9A8VctNZ+lfivC/sV3ivW8ElJTERXZoPBRrZKkNKow==", + "dev": true, + "dependencies": { + "ansi-regex": "^3.0.0" + }, + "engines": { + "node": ">=4" + } + }, + "node_modules/cliui": { + "version": "7.0.4", + "resolved": "https://registry.npmjs.org/cliui/-/cliui-7.0.4.tgz", + "integrity": "sha512-OcRE68cOsVMXp1Yvonl/fzkQOyjLSu/8bhPDfQt0e0/Eb283TKP20Fs2MqoPsr9SwA595rRCA+QMzYc9nBP+JQ==", + "dev": true, + "dependencies": { + "string-width": "^4.2.0", + "strip-ansi": "^6.0.0", + "wrap-ansi": "^7.0.0" } }, "node_modules/clone-response": { @@ -4969,6 +4928,12 @@ "util-deprecate": "~1.0.1" } }, + "node_modules/concat-stream/node_modules/safe-buffer": { + "version": "5.1.2", + "resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.1.2.tgz", + "integrity": "sha512-Gd2UZBJDkXlY7GbJxfsE8/nvKkUEU1G38c1siN6QP6a9PT9MmHB8GnpscSmMJSoF8LOIrt8ud/wPtojys4G6+g==", + "dev": true + }, "node_modules/concat-stream/node_modules/string_decoder": { "version": "1.1.1", "resolved": "https://registry.npmjs.org/string_decoder/-/string_decoder-1.1.1.tgz", @@ -4990,26 +4955,6 @@ "node": ">= 0.6" } }, - "node_modules/content-disposition/node_modules/safe-buffer": { - "version": "5.2.1", - "resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.2.1.tgz", - "integrity": "sha512-rp3So07KcdmmKbGvgaNxQSJr7bGVSVk5S9Eq1F+ppbRo70+YeaDxkw5Dd8NPN+GD6bjnYm2VuPuCXmpuYvmCXQ==", - "dev": true, - "funding": [ - { - "type": "github", - "url": "https://github.com/sponsors/feross" - }, - { - "type": "patreon", - "url": "https://www.patreon.com/feross" - }, - { - "type": "consulting", - "url": "https://feross.org/support" - } - ] - }, "node_modules/content-hash": { "version": "2.5.2", "resolved": "https://registry.npmjs.org/content-hash/-/content-hash-2.5.2.tgz", @@ -5071,14 +5016,14 @@ } }, "node_modules/cosmiconfig": { - "version": "8.2.0", - "resolved": "https://registry.npmjs.org/cosmiconfig/-/cosmiconfig-8.2.0.tgz", - "integrity": "sha512-3rTMnFJA1tCOPwRxtgF4wd7Ab2qvDbL8jX+3smjIbS4HlZBagTlpERbdN7iAbWlrfxE3M8c27kTwTawQ7st+OQ==", + "version": "8.3.6", + "resolved": "https://registry.npmjs.org/cosmiconfig/-/cosmiconfig-8.3.6.tgz", + "integrity": "sha512-kcZ6+W5QzcJ3P1Mt+83OUv/oHFqZHIx8DuxG6eZ5RGMERoLqp4BuGjhHLYGK+Kf5XVkQvqBSmAy/nGWN3qDgEA==", "dev": true, "dependencies": { - "import-fresh": "^3.2.1", + "import-fresh": "^3.3.0", "js-yaml": "^4.1.0", - "parse-json": "^5.0.0", + "parse-json": "^5.2.0", "path-type": "^4.0.0" }, "engines": { @@ -5086,6 +5031,14 @@ }, "funding": { "url": "https://github.com/sponsors/d-fischer" + }, + "peerDependencies": { + "typescript": ">=4.9.5" + }, + "peerDependenciesMeta": { + "typescript": { + "optional": true + } } }, "node_modules/cosmiconfig/node_modules/argparse": { @@ -5138,6 +5091,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", @@ -5150,6 +5104,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", @@ -5225,13 +5180,16 @@ "integrity": "sha512-KALDyEYgpY+Rlob/iriUtjV6d5Eq+Y191A5g4UqLAi8CyGP9N1+FdVbkc1SxKc2r4YAYqG8JzO2KGL+AizD70Q==" }, "node_modules/d": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/d/-/d-1.0.1.tgz", - "integrity": "sha512-m62ShEObQ39CfralilEQRjH6oAMtNCV1xJyEx5LpRYUVN+EviphDgUc/F3hnYbADmkiNs67Y+3ylmlG7Lnu+FA==", + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/d/-/d-1.0.2.tgz", + "integrity": "sha512-MOqHvMWF9/9MX6nza0KgvFH4HpMU0EF5uUDXqX/BtxtU8NfB0QzRtJ8Oe/6SuS4kbhyzVJwjd97EA4PKrzJ8bw==", "dev": true, "dependencies": { - "es5-ext": "^0.10.50", - "type": "^1.0.1" + "es5-ext": "^0.10.64", + "type": "^2.7.2" + }, + "engines": { + "node": ">=0.12" } }, "node_modules/dashdash": { @@ -5246,6 +5204,57 @@ "node": ">=0.10" } }, + "node_modules/data-view-buffer": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/data-view-buffer/-/data-view-buffer-1.0.1.tgz", + "integrity": "sha512-0lht7OugA5x3iJLOWFhWK/5ehONdprk0ISXqVFn/NFrDu+cuc8iADFrGQz5BnRK7LLU3JmkbXSxaqX+/mXYtUA==", + "dev": true, + "dependencies": { + "call-bind": "^1.0.6", + "es-errors": "^1.3.0", + "is-data-view": "^1.0.1" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/data-view-byte-length": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/data-view-byte-length/-/data-view-byte-length-1.0.1.tgz", + "integrity": "sha512-4J7wRJD3ABAzr8wP+OcIcqq2dlUKp4DVflx++hs5h5ZKydWMI6/D/fAot+yh6g2tHh8fLFTvNOaVN357NvSrOQ==", + "dev": true, + "dependencies": { + "call-bind": "^1.0.7", + "es-errors": "^1.3.0", + "is-data-view": "^1.0.1" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/data-view-byte-offset": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/data-view-byte-offset/-/data-view-byte-offset-1.0.0.tgz", + "integrity": "sha512-t/Ygsytq+R995EJ5PZlD4Cu56sWa8InXySaViRzw9apusqsOO2bQP+SbYzAhR0pFKoB+43lYy8rWban9JSuXnA==", + "dev": true, + "dependencies": { + "call-bind": "^1.0.6", + "es-errors": "^1.3.0", + "is-data-view": "^1.0.1" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, "node_modules/death": { "version": "1.1.0", "resolved": "https://registry.npmjs.org/death/-/death-1.1.0.tgz", @@ -5325,12 +5334,30 @@ "integrity": "sha512-0ISdNousHvZT2EiFlZeZAHBUvSxmKswVCEf8hW7KWgG4a8MVEu/3Vb6uWYozkjylyCxe0JBIiRB1jV45S70WVQ==", "dev": true }, + "node_modules/define-data-property": { + "version": "1.1.4", + "resolved": "https://registry.npmjs.org/define-data-property/-/define-data-property-1.1.4.tgz", + "integrity": "sha512-rBMvIzlpA8v6E+SJZoo++HAYqsLrkg7MSfIinMPFhmkorw7X+dOXVJQs+QT69zGkzMyfDnIMN2Wid1+NbL3T+A==", + "dev": true, + "dependencies": { + "es-define-property": "^1.0.0", + "es-errors": "^1.3.0", + "gopd": "^1.0.1" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, "node_modules/define-properties": { - "version": "1.2.0", - "resolved": "https://registry.npmjs.org/define-properties/-/define-properties-1.2.0.tgz", - "integrity": "sha512-xvqAVKGfT1+UAvPwKTVw/njhdQ8ZhXK4lI0bCIuCMrp2up9nPnaDftrLtmpTazqd1o+UY4zgzU+avtMbDP+ldA==", + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/define-properties/-/define-properties-1.2.1.tgz", + "integrity": "sha512-8QmQKqEASLd5nx0U1B1okLElbUuuttJ/AnYmRXbbbGDWh6uS208EjD4Xqq/I9wK7u0v6O08XhTWnt5XtEbR6Dg==", "dev": true, "dependencies": { + "define-data-property": "^1.0.1", "has-property-descriptors": "^1.0.0", "object-keys": "^1.1.1" }, @@ -5524,12 +5551,13 @@ } }, "node_modules/enquirer": { - "version": "2.3.6", - "resolved": "https://registry.npmjs.org/enquirer/-/enquirer-2.3.6.tgz", - "integrity": "sha512-yjNnPr315/FjS4zIsUxYguYUPP2e1NK4d7E7ZOLiyYCcbFBiTMyID+2wvm2w6+pZ/odMA7cRkjhsPbltwBOrLg==", + "version": "2.4.1", + "resolved": "https://registry.npmjs.org/enquirer/-/enquirer-2.4.1.tgz", + "integrity": "sha512-rRqJg/6gd538VHvR3PSrdRBb/1Vy2YfzHqzvbhGIQpDRKIa4FgV/54b5Q1xYSxOOwKvjXweS26E0Q+nAMwp2pQ==", "dev": true, "dependencies": { - "ansi-colors": "^4.1.1" + "ansi-colors": "^4.1.1", + "strip-ansi": "^6.0.1" }, "engines": { "node": ">=8.6" @@ -5554,50 +5582,57 @@ } }, "node_modules/es-abstract": { - "version": "1.22.1", - "resolved": "https://registry.npmjs.org/es-abstract/-/es-abstract-1.22.1.tgz", - "integrity": "sha512-ioRRcXMO6OFyRpyzV3kE1IIBd4WG5/kltnzdxSCqoP8CMGs/Li+M1uF5o7lOkZVFjDs+NLesthnF66Pg/0q0Lw==", - "dev": true, - "dependencies": { - "array-buffer-byte-length": "^1.0.0", - "arraybuffer.prototype.slice": "^1.0.1", - "available-typed-arrays": "^1.0.5", - "call-bind": "^1.0.2", - "es-set-tostringtag": "^2.0.1", + "version": "1.23.3", + "resolved": "https://registry.npmjs.org/es-abstract/-/es-abstract-1.23.3.tgz", + "integrity": "sha512-e+HfNH61Bj1X9/jLc5v1owaLYuHdeHHSQlkhCBiTK8rBvKaULl/beGMxwrMXjpYrv4pz22BlY570vVePA2ho4A==", + "dev": true, + "dependencies": { + "array-buffer-byte-length": "^1.0.1", + "arraybuffer.prototype.slice": "^1.0.3", + "available-typed-arrays": "^1.0.7", + "call-bind": "^1.0.7", + "data-view-buffer": "^1.0.1", + "data-view-byte-length": "^1.0.1", + "data-view-byte-offset": "^1.0.0", + "es-define-property": "^1.0.0", + "es-errors": "^1.3.0", + "es-object-atoms": "^1.0.0", + "es-set-tostringtag": "^2.0.3", "es-to-primitive": "^1.2.1", - "function.prototype.name": "^1.1.5", - "get-intrinsic": "^1.2.1", - "get-symbol-description": "^1.0.0", + "function.prototype.name": "^1.1.6", + "get-intrinsic": "^1.2.4", + "get-symbol-description": "^1.0.2", "globalthis": "^1.0.3", "gopd": "^1.0.1", - "has": "^1.0.3", - "has-property-descriptors": "^1.0.0", - "has-proto": "^1.0.1", + "has-property-descriptors": "^1.0.2", + "has-proto": "^1.0.3", "has-symbols": "^1.0.3", - "internal-slot": "^1.0.5", - "is-array-buffer": "^3.0.2", + "hasown": "^2.0.2", + "internal-slot": "^1.0.7", + "is-array-buffer": "^3.0.4", "is-callable": "^1.2.7", - "is-negative-zero": "^2.0.2", + "is-data-view": "^1.0.1", + "is-negative-zero": "^2.0.3", "is-regex": "^1.1.4", - "is-shared-array-buffer": "^1.0.2", + "is-shared-array-buffer": "^1.0.3", "is-string": "^1.0.7", - "is-typed-array": "^1.1.10", + "is-typed-array": "^1.1.13", "is-weakref": "^1.0.2", - "object-inspect": "^1.12.3", + "object-inspect": "^1.13.1", "object-keys": "^1.1.1", - "object.assign": "^4.1.4", - "regexp.prototype.flags": "^1.5.0", - "safe-array-concat": "^1.0.0", - "safe-regex-test": "^1.0.0", - "string.prototype.trim": "^1.2.7", - "string.prototype.trimend": "^1.0.6", - "string.prototype.trimstart": "^1.0.6", - "typed-array-buffer": "^1.0.0", - "typed-array-byte-length": "^1.0.0", - "typed-array-byte-offset": "^1.0.0", - "typed-array-length": "^1.0.4", + "object.assign": "^4.1.5", + "regexp.prototype.flags": "^1.5.2", + "safe-array-concat": "^1.1.2", + "safe-regex-test": "^1.0.3", + "string.prototype.trim": "^1.2.9", + "string.prototype.trimend": "^1.0.8", + "string.prototype.trimstart": "^1.0.8", + "typed-array-buffer": "^1.0.2", + "typed-array-byte-length": "^1.0.1", + "typed-array-byte-offset": "^1.0.2", + "typed-array-length": "^1.0.6", "unbox-primitive": "^1.0.2", - "which-typed-array": "^1.1.10" + "which-typed-array": "^1.1.15" }, "engines": { "node": ">= 0.4" @@ -5606,33 +5641,60 @@ "url": "https://github.com/sponsors/ljharb" } }, - "node_modules/es-array-method-boxes-properly": { + "node_modules/es-define-property": { "version": "1.0.0", - "resolved": "https://registry.npmjs.org/es-array-method-boxes-properly/-/es-array-method-boxes-properly-1.0.0.tgz", - "integrity": "sha512-wd6JXUmyHmt8T5a2xreUwKcGPq6f1f+WwIJkijUqiGcJz1qqnZgP6XIK+QyIWU5lT7imeNxUll48bziG+TSYcA==", - "dev": true + "resolved": "https://registry.npmjs.org/es-define-property/-/es-define-property-1.0.0.tgz", + "integrity": "sha512-jxayLKShrEqqzJ0eumQbVhTYQM27CfT1T35+gCgDFoL82JLsXqTJ76zv6A0YLOgEnLUMvLzsDsGIrl8NFpT2gQ==", + "dev": true, + "dependencies": { + "get-intrinsic": "^1.2.4" + }, + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/es-errors": { + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/es-errors/-/es-errors-1.3.0.tgz", + "integrity": "sha512-Zf5H2Kxt2xjTvbJvP2ZWLEICxA6j+hAmMzIlypy4xcBg1vKVnx89Wy0GbS+kf5cwCVFFzdCFh2XSCFNULS6csw==", + "dev": true, + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/es-object-atoms": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/es-object-atoms/-/es-object-atoms-1.0.0.tgz", + "integrity": "sha512-MZ4iQ6JwHOBQjahnjwaC1ZtIBH+2ohjamzAO3oaHcXYup7qxjF2fixyH+Q71voWHeOkI2q/TnJao/KfXYIZWbw==", + "dev": true, + "dependencies": { + "es-errors": "^1.3.0" + }, + "engines": { + "node": ">= 0.4" + } }, "node_modules/es-set-tostringtag": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/es-set-tostringtag/-/es-set-tostringtag-2.0.1.tgz", - "integrity": "sha512-g3OMbtlwY3QewlqAiMLI47KywjWZoEytKr8pf6iTC8uJq5bIAH52Z9pnQ8pVL6whrCto53JZDuUIsifGeLorTg==", + "version": "2.0.3", + "resolved": "https://registry.npmjs.org/es-set-tostringtag/-/es-set-tostringtag-2.0.3.tgz", + "integrity": "sha512-3T8uNMC3OQTHkFUsFq8r/BwAXLHvU/9O9mE0fBc/MY5iq/8H7ncvO947LmYA6ldWw9Uh8Yhf25zu6n7nML5QWQ==", "dev": true, "dependencies": { - "get-intrinsic": "^1.1.3", - "has": "^1.0.3", - "has-tostringtag": "^1.0.0" + "get-intrinsic": "^1.2.4", + "has-tostringtag": "^1.0.2", + "hasown": "^2.0.1" }, "engines": { "node": ">= 0.4" } }, "node_modules/es-shim-unscopables": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/es-shim-unscopables/-/es-shim-unscopables-1.0.0.tgz", - "integrity": "sha512-Jm6GPcCdC30eMLbZ2x8z2WuRwAws3zTBBKuusffYVUrNj/GVSUAZ+xKMaUpfNDR5IbyNA5LJbaecoUVbmUcB1w==", + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/es-shim-unscopables/-/es-shim-unscopables-1.0.2.tgz", + "integrity": "sha512-J3yBRXCzDu4ULnQwxyToo/OjdMx6akgVC7K6few0a7F/0wLtmKKN7I73AH5T2836UuXRqN7Qg+IIUw/+YJksRw==", "dev": true, "dependencies": { - "has": "^1.0.3" + "hasown": "^2.0.0" } }, "node_modules/es-to-primitive": { @@ -5653,14 +5715,15 @@ } }, "node_modules/es5-ext": { - "version": "0.10.62", - "resolved": "https://registry.npmjs.org/es5-ext/-/es5-ext-0.10.62.tgz", - "integrity": "sha512-BHLqn0klhEpnOKSrzn/Xsz2UIW8j+cGmo9JLzr8BiUapV8hPL9+FliFqjwr9ngW7jWdnxv6eO+/LqyhJVqgrjA==", + "version": "0.10.64", + "resolved": "https://registry.npmjs.org/es5-ext/-/es5-ext-0.10.64.tgz", + "integrity": "sha512-p2snDhiLaXe6dahss1LddxqEm+SkuDvV8dnIQG0MWjyHpcMNfXKPE+/Cc0y+PhxJX3A4xGNeFCj5oc0BUh6deg==", "dev": true, "hasInstallScript": true, "dependencies": { "es6-iterator": "^2.0.3", "es6-symbol": "^3.1.3", + "esniff": "^2.0.1", "next-tick": "^1.1.0" }, "engines": { @@ -5685,19 +5748,22 @@ "dev": true }, "node_modules/es6-symbol": { - "version": "3.1.3", - "resolved": "https://registry.npmjs.org/es6-symbol/-/es6-symbol-3.1.3.tgz", - "integrity": "sha512-NJ6Yn3FuDinBaBRWl/q5X/s4koRHBrgKAu+yGI6JCBeiu3qrcbJhwT2GeR/EXVfylRk8dpQVJoLEFhK+Mu31NA==", + "version": "3.1.4", + "resolved": "https://registry.npmjs.org/es6-symbol/-/es6-symbol-3.1.4.tgz", + "integrity": "sha512-U9bFFjX8tFiATgtkJ1zg25+KviIXpgRvRHS8sau3GfhVzThRQrOeksPeT0BWW2MNZs1OEWJ1DPXOQMn0KKRkvg==", "dev": true, "dependencies": { - "d": "^1.0.1", - "ext": "^1.1.2" + "d": "^1.0.2", + "ext": "^1.7.0" + }, + "engines": { + "node": ">=0.12" } }, "node_modules/escalade": { - "version": "3.1.1", - "resolved": "https://registry.npmjs.org/escalade/-/escalade-3.1.1.tgz", - "integrity": "sha512-k0er2gUkLf8O0zKJiAhmkTnJlTvINGv7ygDNPbeIsX/TJjGJZHuh9B2UxbsaEkmlEo9MfhrSzmhIlhRlI2GXnw==", + "version": "3.1.2", + "resolved": "https://registry.npmjs.org/escalade/-/escalade-3.1.2.tgz", + "integrity": "sha512-ErCHMCae19vR8vQGe50xIsVomy19rg6gFu3+r3jkEO46suLMWBksvVyoGgQV+jOfl84ZSOSlmv6Gxa89PmTGmA==", "dev": true, "engines": { "node": ">=6" @@ -5874,9 +5940,9 @@ } }, "node_modules/eslint-config-prettier": { - "version": "8.8.0", - "resolved": "https://registry.npmjs.org/eslint-config-prettier/-/eslint-config-prettier-8.8.0.tgz", - "integrity": "sha512-wLbQiFre3tdGgpDv67NQKnJuTlcUVYHas3k+DZCc2U2BadthoEY4B7hLPvAxaqdyOGCzuLfii2fqGph10va7oA==", + "version": "8.10.0", + "resolved": "https://registry.npmjs.org/eslint-config-prettier/-/eslint-config-prettier-8.10.0.tgz", + "integrity": "sha512-SM8AMJdeQqRYT9O9zguiruQZaN7+z+E4eAP9oiLNGKMtomwaB1E9dcgUD6ZAn/eQAb52USbvezbiljfZUhbJcg==", "dev": true, "bin": { "eslint-config-prettier": "bin/cli.js" @@ -5903,1071 +5969,543 @@ "type": "consulting", "url": "https://feross.org/support" } - ], - "peerDependencies": { - "eslint": "^7.12.1", - "eslint-plugin-import": "^2.22.1", - "eslint-plugin-node": "^11.1.0", - "eslint-plugin-promise": "^4.2.1 || ^5.0.0" - } - }, - "node_modules/eslint-import-resolver-node": { - "version": "0.3.7", - "resolved": "https://registry.npmjs.org/eslint-import-resolver-node/-/eslint-import-resolver-node-0.3.7.tgz", - "integrity": "sha512-gozW2blMLJCeFpBwugLTGyvVjNoeo1knonXAcatC6bjPBZitotxdWf7Gimr25N4c0AAOo4eOUfaG82IJPDpqCA==", - "dev": true, - "dependencies": { - "debug": "^3.2.7", - "is-core-module": "^2.11.0", - "resolve": "^1.22.1" - } - }, - "node_modules/eslint-import-resolver-node/node_modules/debug": { - "version": "3.2.7", - "resolved": "https://registry.npmjs.org/debug/-/debug-3.2.7.tgz", - "integrity": "sha512-CFjzYYAi4ThfiQvizrFQevTTXHtnCqWfe7x1AhgEscTz6ZbLbfoLRLPugTQyBth6f8ZERVUSyWHFD/7Wu4t1XQ==", - "dev": true, - "dependencies": { - "ms": "^2.1.1" - } - }, - "node_modules/eslint-module-utils": { - "version": "2.8.0", - "resolved": "https://registry.npmjs.org/eslint-module-utils/-/eslint-module-utils-2.8.0.tgz", - "integrity": "sha512-aWajIYfsqCKRDgUfjEXNN/JlrzauMuSEy5sbd7WXbtW3EH6A6MpwEh42c7qD+MqQo9QMJ6fWLAeIJynx0g6OAw==", - "dev": true, - "dependencies": { - "debug": "^3.2.7" - }, - "engines": { - "node": ">=4" - }, - "peerDependenciesMeta": { - "eslint": { - "optional": true - } - } - }, - "node_modules/eslint-module-utils/node_modules/debug": { - "version": "3.2.7", - "resolved": "https://registry.npmjs.org/debug/-/debug-3.2.7.tgz", - "integrity": "sha512-CFjzYYAi4ThfiQvizrFQevTTXHtnCqWfe7x1AhgEscTz6ZbLbfoLRLPugTQyBth6f8ZERVUSyWHFD/7Wu4t1XQ==", - "dev": true, - "dependencies": { - "ms": "^2.1.1" - } - }, - "node_modules/eslint-plugin-es": { - "version": "3.0.1", - "resolved": "https://registry.npmjs.org/eslint-plugin-es/-/eslint-plugin-es-3.0.1.tgz", - "integrity": "sha512-GUmAsJaN4Fc7Gbtl8uOBlayo2DqhwWvEzykMHSCZHU3XdJ+NSzzZcVhXh3VxX5icqQ+oQdIEawXX8xkR3mIFmQ==", - "dev": true, - "dependencies": { - "eslint-utils": "^2.0.0", - "regexpp": "^3.0.0" - }, - "engines": { - "node": ">=8.10.0" - }, - "funding": { - "url": "https://github.com/sponsors/mysticatea" - }, - "peerDependencies": { - "eslint": ">=4.19.1" - } - }, - "node_modules/eslint-plugin-es/node_modules/eslint-utils": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/eslint-utils/-/eslint-utils-2.1.0.tgz", - "integrity": "sha512-w94dQYoauyvlDc43XnGB8lU3Zt713vNChgt4EWwhXAP2XkBvndfxF0AgIqKOOasjPIPzj9JqgwkwbCYD0/V3Zg==", - "dev": true, - "dependencies": { - "eslint-visitor-keys": "^1.1.0" - }, - "engines": { - "node": ">=6" - }, - "funding": { - "url": "https://github.com/sponsors/mysticatea" - } - }, - "node_modules/eslint-plugin-es/node_modules/eslint-visitor-keys": { - "version": "1.3.0", - "resolved": "https://registry.npmjs.org/eslint-visitor-keys/-/eslint-visitor-keys-1.3.0.tgz", - "integrity": "sha512-6J72N8UNa462wa/KFODt/PJ3IU60SDpC3QXC1Hjc1BXXpfL2C9R5+AU7jhe0F6GREqVMh4Juu+NY7xn+6dipUQ==", - "dev": true, - "engines": { - "node": ">=4" - } - }, - "node_modules/eslint-plugin-import": { - "version": "2.27.5", - "resolved": "https://registry.npmjs.org/eslint-plugin-import/-/eslint-plugin-import-2.27.5.tgz", - "integrity": "sha512-LmEt3GVofgiGuiE+ORpnvP+kAm3h6MLZJ4Q5HCyHADofsb4VzXFsRiWj3c0OFiV+3DWFh0qg3v9gcPlfc3zRow==", - "dev": true, - "dependencies": { - "array-includes": "^3.1.6", - "array.prototype.flat": "^1.3.1", - "array.prototype.flatmap": "^1.3.1", - "debug": "^3.2.7", - "doctrine": "^2.1.0", - "eslint-import-resolver-node": "^0.3.7", - "eslint-module-utils": "^2.7.4", - "has": "^1.0.3", - "is-core-module": "^2.11.0", - "is-glob": "^4.0.3", - "minimatch": "^3.1.2", - "object.values": "^1.1.6", - "resolve": "^1.22.1", - "semver": "^6.3.0", - "tsconfig-paths": "^3.14.1" - }, - "engines": { - "node": ">=4" - }, - "peerDependencies": { - "eslint": "^2 || ^3 || ^4 || ^5 || ^6 || ^7.2.0 || ^8" - } - }, - "node_modules/eslint-plugin-import/node_modules/debug": { - "version": "3.2.7", - "resolved": "https://registry.npmjs.org/debug/-/debug-3.2.7.tgz", - "integrity": "sha512-CFjzYYAi4ThfiQvizrFQevTTXHtnCqWfe7x1AhgEscTz6ZbLbfoLRLPugTQyBth6f8ZERVUSyWHFD/7Wu4t1XQ==", - "dev": true, - "dependencies": { - "ms": "^2.1.1" - } - }, - "node_modules/eslint-plugin-import/node_modules/doctrine": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/doctrine/-/doctrine-2.1.0.tgz", - "integrity": "sha512-35mSku4ZXK0vfCuHEDAwt55dg2jNajHZ1odvF+8SSr82EsZY4QmXfuWso8oEd8zRhVObSN18aM0CjSdoBX7zIw==", - "dev": true, - "dependencies": { - "esutils": "^2.0.2" - }, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/eslint-plugin-node": { - "version": "11.1.0", - "resolved": "https://registry.npmjs.org/eslint-plugin-node/-/eslint-plugin-node-11.1.0.tgz", - "integrity": "sha512-oUwtPJ1W0SKD0Tr+wqu92c5xuCeQqB3hSCHasn/ZgjFdA9iDGNkNf2Zi9ztY7X+hNuMib23LNGRm6+uN+KLE3g==", - "dev": true, - "dependencies": { - "eslint-plugin-es": "^3.0.0", - "eslint-utils": "^2.0.0", - "ignore": "^5.1.1", - "minimatch": "^3.0.4", - "resolve": "^1.10.1", - "semver": "^6.1.0" - }, - "engines": { - "node": ">=8.10.0" - }, - "peerDependencies": { - "eslint": ">=5.16.0" - } - }, - "node_modules/eslint-plugin-node/node_modules/eslint-utils": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/eslint-utils/-/eslint-utils-2.1.0.tgz", - "integrity": "sha512-w94dQYoauyvlDc43XnGB8lU3Zt713vNChgt4EWwhXAP2XkBvndfxF0AgIqKOOasjPIPzj9JqgwkwbCYD0/V3Zg==", - "dev": true, - "dependencies": { - "eslint-visitor-keys": "^1.1.0" - }, - "engines": { - "node": ">=6" - }, - "funding": { - "url": "https://github.com/sponsors/mysticatea" - } - }, - "node_modules/eslint-plugin-node/node_modules/eslint-visitor-keys": { - "version": "1.3.0", - "resolved": "https://registry.npmjs.org/eslint-visitor-keys/-/eslint-visitor-keys-1.3.0.tgz", - "integrity": "sha512-6J72N8UNa462wa/KFODt/PJ3IU60SDpC3QXC1Hjc1BXXpfL2C9R5+AU7jhe0F6GREqVMh4Juu+NY7xn+6dipUQ==", - "dev": true, - "engines": { - "node": ">=4" - } - }, - "node_modules/eslint-plugin-prettier": { - "version": "3.4.1", - "resolved": "https://registry.npmjs.org/eslint-plugin-prettier/-/eslint-plugin-prettier-3.4.1.tgz", - "integrity": "sha512-htg25EUYUeIhKHXjOinK4BgCcDwtLHjqaxCDsMy5nbnUMkKFvIhMVCp+5GFUXQ4Nr8lBsPqtGAqBenbpFqAA2g==", - "dev": true, - "dependencies": { - "prettier-linter-helpers": "^1.0.0" - }, - "engines": { - "node": ">=6.0.0" - }, - "peerDependencies": { - "eslint": ">=5.0.0", - "prettier": ">=1.13.0" - }, - "peerDependenciesMeta": { - "eslint-config-prettier": { - "optional": true - } - } - }, - "node_modules/eslint-plugin-promise": { - "version": "5.2.0", - "resolved": "https://registry.npmjs.org/eslint-plugin-promise/-/eslint-plugin-promise-5.2.0.tgz", - "integrity": "sha512-SftLb1pUG01QYq2A/hGAWfDRXqYD82zE7j7TopDOyNdU+7SvvoXREls/+PRTY17vUXzXnZA/zfnyKgRH6x4JJw==", - "dev": true, - "engines": { - "node": "^10.12.0 || >=12.0.0" - }, - "peerDependencies": { - "eslint": "^7.0.0" - } - }, - "node_modules/eslint-scope": { - "version": "5.1.1", - "resolved": "https://registry.npmjs.org/eslint-scope/-/eslint-scope-5.1.1.tgz", - "integrity": "sha512-2NxwbF/hZ0KpepYN0cNbo+FN6XoK7GaHlQhgx/hIZl6Va0bF45RQOOwhLIy8lQDbuCiadSLCBnH2CFYquit5bw==", - "dev": true, - "dependencies": { - "esrecurse": "^4.3.0", - "estraverse": "^4.1.1" - }, - "engines": { - "node": ">=8.0.0" - } - }, - "node_modules/eslint-utils": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/eslint-utils/-/eslint-utils-3.0.0.tgz", - "integrity": "sha512-uuQC43IGctw68pJA1RgbQS8/NP7rch6Cwd4j3ZBtgo4/8Flj4eGE7ZYSZRN3iq5pVUv6GPdW5Z1RFleo84uLDA==", - "dev": true, - "dependencies": { - "eslint-visitor-keys": "^2.0.0" - }, - "engines": { - "node": "^10.0.0 || ^12.0.0 || >= 14.0.0" - }, - "funding": { - "url": "https://github.com/sponsors/mysticatea" - }, - "peerDependencies": { - "eslint": ">=5" - } - }, - "node_modules/eslint-visitor-keys": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/eslint-visitor-keys/-/eslint-visitor-keys-2.1.0.tgz", - "integrity": "sha512-0rSmRBzXgDzIsD6mGdJgevzgezI534Cer5L/vyMX0kHzT/jiB43jRhd9YUlMGYLQy2zprNmoT8qasCGtY+QaKw==", - "dev": true, - "engines": { - "node": ">=10" - } - }, - "node_modules/eslint/node_modules/eslint-utils": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/eslint-utils/-/eslint-utils-2.1.0.tgz", - "integrity": "sha512-w94dQYoauyvlDc43XnGB8lU3Zt713vNChgt4EWwhXAP2XkBvndfxF0AgIqKOOasjPIPzj9JqgwkwbCYD0/V3Zg==", - "dev": true, - "dependencies": { - "eslint-visitor-keys": "^1.1.0" - }, - "engines": { - "node": ">=6" - }, - "funding": { - "url": "https://github.com/sponsors/mysticatea" - } - }, - "node_modules/eslint/node_modules/eslint-utils/node_modules/eslint-visitor-keys": { - "version": "1.3.0", - "resolved": "https://registry.npmjs.org/eslint-visitor-keys/-/eslint-visitor-keys-1.3.0.tgz", - "integrity": "sha512-6J72N8UNa462wa/KFODt/PJ3IU60SDpC3QXC1Hjc1BXXpfL2C9R5+AU7jhe0F6GREqVMh4Juu+NY7xn+6dipUQ==", - "dev": true, - "engines": { - "node": ">=4" - } - }, - "node_modules/eslint/node_modules/ignore": { - "version": "4.0.6", - "resolved": "https://registry.npmjs.org/ignore/-/ignore-4.0.6.tgz", - "integrity": "sha512-cyFDKrqc/YdcWFniJhzI42+AzS+gNwmUzOSFcRCQYwySuBBBy/KjuxWLZ/FHEH6Moq1NizMOBWyTcv8O4OZIMg==", - "dev": true, - "engines": { - "node": ">= 4" - } - }, - "node_modules/eslint/node_modules/lru-cache": { - "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" - }, - "engines": { - "node": ">=10" - } - }, - "node_modules/eslint/node_modules/semver": { - "version": "7.5.4", - "resolved": "https://registry.npmjs.org/semver/-/semver-7.5.4.tgz", - "integrity": "sha512-1bCSESV6Pv+i21Hvpxp3Dx+pSD8lIPt8uVjRrxAUt/nbswYc+tK6Y2btiULjd4+fnq15PX+nqQDC7Oft7WkwcA==", - "dev": true, - "dependencies": { - "lru-cache": "^6.0.0" - }, - "bin": { - "semver": "bin/semver.js" - }, - "engines": { - "node": ">=10" - } - }, - "node_modules/eslint/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/espree": { - "version": "7.3.1", - "resolved": "https://registry.npmjs.org/espree/-/espree-7.3.1.tgz", - "integrity": "sha512-v3JCNCE64umkFpmkFGqzVKsOT0tN1Zr+ueqLZfpV1Ob8e+CEgPWa+OxCoGH3tnhimMKIaBm4m/vaRpJ/krRz2g==", - "dev": true, - "dependencies": { - "acorn": "^7.4.0", - "acorn-jsx": "^5.3.1", - "eslint-visitor-keys": "^1.3.0" - }, - "engines": { - "node": "^10.12.0 || >=12.0.0" - } - }, - "node_modules/espree/node_modules/eslint-visitor-keys": { - "version": "1.3.0", - "resolved": "https://registry.npmjs.org/eslint-visitor-keys/-/eslint-visitor-keys-1.3.0.tgz", - "integrity": "sha512-6J72N8UNa462wa/KFODt/PJ3IU60SDpC3QXC1Hjc1BXXpfL2C9R5+AU7jhe0F6GREqVMh4Juu+NY7xn+6dipUQ==", - "dev": true, - "engines": { - "node": ">=4" - } - }, - "node_modules/esprima": { - "version": "4.0.1", - "resolved": "https://registry.npmjs.org/esprima/-/esprima-4.0.1.tgz", - "integrity": "sha512-eGuFFw7Upda+g4p+QHvnW0RyTX/SVeJBDM/gCtMARO0cLuT2HcEKnTPvhjV6aGeqrCB/sbNop0Kszm0jsaWU4A==", - "dev": true, - "bin": { - "esparse": "bin/esparse.js", - "esvalidate": "bin/esvalidate.js" - }, - "engines": { - "node": ">=4" - } - }, - "node_modules/esquery": { - "version": "1.5.0", - "resolved": "https://registry.npmjs.org/esquery/-/esquery-1.5.0.tgz", - "integrity": "sha512-YQLXUplAwJgCydQ78IMJywZCceoqk1oH01OERdSAJc/7U2AylwjhSCLDEtqwg811idIS/9fIU5GjG73IgjKMVg==", - "dev": true, - "dependencies": { - "estraverse": "^5.1.0" - }, - "engines": { - "node": ">=0.10" - } - }, - "node_modules/esquery/node_modules/estraverse": { - "version": "5.3.0", - "resolved": "https://registry.npmjs.org/estraverse/-/estraverse-5.3.0.tgz", - "integrity": "sha512-MMdARuVEQziNTeJD8DgMqmhwR11BRQ/cBP+pLtYdSTnf3MIO8fFeiINEbX36ZdNlfU/7A9f3gUw49B3oQsvwBA==", - "dev": true, - "engines": { - "node": ">=4.0" - } - }, - "node_modules/esrecurse": { - "version": "4.3.0", - "resolved": "https://registry.npmjs.org/esrecurse/-/esrecurse-4.3.0.tgz", - "integrity": "sha512-KmfKL3b6G+RXvP8N1vr3Tq1kL/oCFgn2NYXEtqP8/L3pKapUA4G8cFVaoF3SU323CD4XypR/ffioHmkti6/Tag==", - "dev": true, - "dependencies": { - "estraverse": "^5.2.0" - }, - "engines": { - "node": ">=4.0" - } - }, - "node_modules/esrecurse/node_modules/estraverse": { - "version": "5.3.0", - "resolved": "https://registry.npmjs.org/estraverse/-/estraverse-5.3.0.tgz", - "integrity": "sha512-MMdARuVEQziNTeJD8DgMqmhwR11BRQ/cBP+pLtYdSTnf3MIO8fFeiINEbX36ZdNlfU/7A9f3gUw49B3oQsvwBA==", - "dev": true, - "engines": { - "node": ">=4.0" - } - }, - "node_modules/estraverse": { - "version": "4.3.0", - "resolved": "https://registry.npmjs.org/estraverse/-/estraverse-4.3.0.tgz", - "integrity": "sha512-39nnKffWz8xN1BU/2c79n9nB9HDzo0niYUqx6xyqUnyoAnQyyWpOTdZEeiCch8BBu515t4wp9ZmgVfVhn9EBpw==", - "dev": true, - "engines": { - "node": ">=4.0" - } - }, - "node_modules/esutils": { - "version": "2.0.3", - "resolved": "https://registry.npmjs.org/esutils/-/esutils-2.0.3.tgz", - "integrity": "sha512-kVscqXk4OCp68SZ0dkgEKVi6/8ij300KBWTJq32P/dYeWTSwK41WyTxalN1eRmA5Z9UU/LX9D7FWSmV9SAYx6g==", - "dev": true, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/etag": { - "version": "1.8.1", - "resolved": "https://registry.npmjs.org/etag/-/etag-1.8.1.tgz", - "integrity": "sha512-aIL5Fx7mawVa300al2BnEE4iNvo1qETxLrPI/o05L7z6go7fCw1J6EQmbK4FmJ2AS7kgVF/KEZWufBfdClMcPg==", - "dev": true, - "engines": { - "node": ">= 0.6" - } - }, - "node_modules/eth-ens-namehash": { - "version": "2.0.8", - "resolved": "https://registry.npmjs.org/eth-ens-namehash/-/eth-ens-namehash-2.0.8.tgz", - "integrity": "sha512-VWEI1+KJfz4Km//dadyvBBoBeSQ0MHTXPvr8UIXiLW6IanxvAV+DmlZAijZwAyggqGUfwQBeHf7tc9wzc1piSw==", - "dev": true, - "dependencies": { - "idna-uts46-hx": "^2.3.1", - "js-sha3": "^0.5.7" - } - }, - "node_modules/eth-ens-namehash/node_modules/js-sha3": { - "version": "0.5.7", - "resolved": "https://registry.npmjs.org/js-sha3/-/js-sha3-0.5.7.tgz", - "integrity": "sha512-GII20kjaPX0zJ8wzkTbNDYMY7msuZcTWk8S5UOh6806Jq/wz1J8/bnr8uGU0DAUmYDjj2Mr4X1cW8v/GLYnR+g==", - "dev": true - }, - "node_modules/eth-gas-reporter": { - "version": "0.2.25", - "resolved": "https://registry.npmjs.org/eth-gas-reporter/-/eth-gas-reporter-0.2.25.tgz", - "integrity": "sha512-1fRgyE4xUB8SoqLgN3eDfpDfwEfRxh2Sz1b7wzFbyQA+9TekMmvSjjoRu9SKcSVyK+vLkLIsVbJDsTWjw195OQ==", - "dev": true, - "dependencies": { - "@ethersproject/abi": "^5.0.0-beta.146", - "@solidity-parser/parser": "^0.14.0", - "cli-table3": "^0.5.0", - "colors": "1.4.0", - "ethereum-cryptography": "^1.0.3", - "ethers": "^4.0.40", - "fs-readdir-recursive": "^1.1.0", - "lodash": "^4.17.14", - "markdown-table": "^1.1.3", - "mocha": "^7.1.1", - "req-cwd": "^2.0.0", - "request": "^2.88.0", - "request-promise-native": "^1.0.5", - "sha1": "^1.1.1", - "sync-request": "^6.0.0" - }, - "peerDependencies": { - "@codechecks/client": "^0.1.0" - }, - "peerDependenciesMeta": { - "@codechecks/client": { - "optional": true - } + ], + "peerDependencies": { + "eslint": "^7.12.1", + "eslint-plugin-import": "^2.22.1", + "eslint-plugin-node": "^11.1.0", + "eslint-plugin-promise": "^4.2.1 || ^5.0.0" } }, - "node_modules/eth-gas-reporter/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==", + "node_modules/eslint-import-resolver-node": { + "version": "0.3.9", + "resolved": "https://registry.npmjs.org/eslint-import-resolver-node/-/eslint-import-resolver-node-0.3.9.tgz", + "integrity": "sha512-WFj2isz22JahUv+B788TlO3N6zL3nNJGU8CcZbPZvVEkBPaJdCV4vy5wyghty5ROFbCRnm132v8BScu5/1BQ8g==", "dev": true, - "engines": { - "node": ">=6" + "dependencies": { + "debug": "^3.2.7", + "is-core-module": "^2.13.0", + "resolve": "^1.22.4" } }, - "node_modules/eth-gas-reporter/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==", + "node_modules/eslint-import-resolver-node/node_modules/debug": { + "version": "3.2.7", + "resolved": "https://registry.npmjs.org/debug/-/debug-3.2.7.tgz", + "integrity": "sha512-CFjzYYAi4ThfiQvizrFQevTTXHtnCqWfe7x1AhgEscTz6ZbLbfoLRLPugTQyBth6f8ZERVUSyWHFD/7Wu4t1XQ==", "dev": true, - "engines": { - "node": ">=6" + "dependencies": { + "ms": "^2.1.1" } }, - "node_modules/eth-gas-reporter/node_modules/ansi-styles": { - "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==", + "node_modules/eslint-module-utils": { + "version": "2.8.1", + "resolved": "https://registry.npmjs.org/eslint-module-utils/-/eslint-module-utils-2.8.1.tgz", + "integrity": "sha512-rXDXR3h7cs7dy9RNpUlQf80nX31XWJEyGq1tRMo+6GsO5VmTe4UTwtmonAD4ZkAsrfMVDA2wlGJ3790Ys+D49Q==", "dev": true, "dependencies": { - "color-convert": "^1.9.0" + "debug": "^3.2.7" }, "engines": { "node": ">=4" + }, + "peerDependenciesMeta": { + "eslint": { + "optional": true + } } }, - "node_modules/eth-gas-reporter/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==", - "dev": true - }, - "node_modules/eth-gas-reporter/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/eth-gas-reporter/node_modules/chalk": { - "version": "2.4.2", - "resolved": "https://registry.npmjs.org/chalk/-/chalk-2.4.2.tgz", - "integrity": "sha512-Mti+f9lpJNcwF4tWV8/OrTTtF1gZi+f8FqlyAdouralcFWFQWF2+NgCHShjkCb+IFBLq9buZwE1xckQU4peSuQ==", + "node_modules/eslint-module-utils/node_modules/debug": { + "version": "3.2.7", + "resolved": "https://registry.npmjs.org/debug/-/debug-3.2.7.tgz", + "integrity": "sha512-CFjzYYAi4ThfiQvizrFQevTTXHtnCqWfe7x1AhgEscTz6ZbLbfoLRLPugTQyBth6f8ZERVUSyWHFD/7Wu4t1XQ==", "dev": true, "dependencies": { - "ansi-styles": "^3.2.1", - "escape-string-regexp": "^1.0.5", - "supports-color": "^5.3.0" - }, - "engines": { - "node": ">=4" + "ms": "^2.1.1" } }, - "node_modules/eth-gas-reporter/node_modules/chalk/node_modules/supports-color": { - "version": "5.5.0", - "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-5.5.0.tgz", - "integrity": "sha512-QjVjwdXIt408MIiAqCX4oUKsgU2EqAGzs2Ppkm4aQYbjm+ZEWEcW4SfFNTr4uMNZma0ey4f5lgLrkB0aX0QMow==", + "node_modules/eslint-plugin-es": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/eslint-plugin-es/-/eslint-plugin-es-3.0.1.tgz", + "integrity": "sha512-GUmAsJaN4Fc7Gbtl8uOBlayo2DqhwWvEzykMHSCZHU3XdJ+NSzzZcVhXh3VxX5icqQ+oQdIEawXX8xkR3mIFmQ==", "dev": true, "dependencies": { - "has-flag": "^3.0.0" + "eslint-utils": "^2.0.0", + "regexpp": "^3.0.0" }, "engines": { - "node": ">=4" + "node": ">=8.10.0" + }, + "funding": { + "url": "https://github.com/sponsors/mysticatea" + }, + "peerDependencies": { + "eslint": ">=4.19.1" } }, - "node_modules/eth-gas-reporter/node_modules/chokidar": { - "version": "3.3.0", - "resolved": "https://registry.npmjs.org/chokidar/-/chokidar-3.3.0.tgz", - "integrity": "sha512-dGmKLDdT3Gdl7fBUe8XK+gAtGmzy5Fn0XkkWQuYxGIgWVPPse2CxFA5mtrlD0TOHaHjEUqkWNyP1XdHoJES/4A==", + "node_modules/eslint-plugin-es/node_modules/eslint-utils": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/eslint-utils/-/eslint-utils-2.1.0.tgz", + "integrity": "sha512-w94dQYoauyvlDc43XnGB8lU3Zt713vNChgt4EWwhXAP2XkBvndfxF0AgIqKOOasjPIPzj9JqgwkwbCYD0/V3Zg==", "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" + "eslint-visitor-keys": "^1.1.0" }, "engines": { - "node": ">= 8.10.0" + "node": ">=6" }, - "optionalDependencies": { - "fsevents": "~2.1.1" + "funding": { + "url": "https://github.com/sponsors/mysticatea" } }, - "node_modules/eth-gas-reporter/node_modules/cliui": { - "version": "5.0.0", - "resolved": "https://registry.npmjs.org/cliui/-/cliui-5.0.0.tgz", - "integrity": "sha512-PYeGSEmmHM6zvoef2w8TPzlrnNpXIjTipYK780YswmIP9vjxmd6Y2a3CB2Ks6/AU8NHjZugXvo8w3oWM2qnwXA==", + "node_modules/eslint-plugin-es/node_modules/eslint-visitor-keys": { + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/eslint-visitor-keys/-/eslint-visitor-keys-1.3.0.tgz", + "integrity": "sha512-6J72N8UNa462wa/KFODt/PJ3IU60SDpC3QXC1Hjc1BXXpfL2C9R5+AU7jhe0F6GREqVMh4Juu+NY7xn+6dipUQ==", "dev": true, - "dependencies": { - "string-width": "^3.1.0", - "strip-ansi": "^5.2.0", - "wrap-ansi": "^5.1.0" + "engines": { + "node": ">=4" } }, - "node_modules/eth-gas-reporter/node_modules/color-convert": { - "version": "1.9.3", - "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-1.9.3.tgz", - "integrity": "sha512-QfAUtd+vFdAtFQcC8CCyYt1fYWxSqAiK2cSD6zDB8N3cpsEBAvRxp9zOGg6G/SHHJYAT88/az/IuDGALsNVbGg==", + "node_modules/eslint-plugin-import": { + "version": "2.29.1", + "resolved": "https://registry.npmjs.org/eslint-plugin-import/-/eslint-plugin-import-2.29.1.tgz", + "integrity": "sha512-BbPC0cuExzhiMo4Ff1BTVwHpjjv28C5R+btTOGaCRC7UEz801up0JadwkeSk5Ued6TG34uaczuVuH6qyy5YUxw==", "dev": true, "dependencies": { - "color-name": "1.1.3" + "array-includes": "^3.1.7", + "array.prototype.findlastindex": "^1.2.3", + "array.prototype.flat": "^1.3.2", + "array.prototype.flatmap": "^1.3.2", + "debug": "^3.2.7", + "doctrine": "^2.1.0", + "eslint-import-resolver-node": "^0.3.9", + "eslint-module-utils": "^2.8.0", + "hasown": "^2.0.0", + "is-core-module": "^2.13.1", + "is-glob": "^4.0.3", + "minimatch": "^3.1.2", + "object.fromentries": "^2.0.7", + "object.groupby": "^1.0.1", + "object.values": "^1.1.7", + "semver": "^6.3.1", + "tsconfig-paths": "^3.15.0" + }, + "engines": { + "node": ">=4" + }, + "peerDependencies": { + "eslint": "^2 || ^3 || ^4 || ^5 || ^6 || ^7.2.0 || ^8" } }, - "node_modules/eth-gas-reporter/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==", - "dev": true - }, - "node_modules/eth-gas-reporter/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)", + "node_modules/eslint-plugin-import/node_modules/debug": { + "version": "3.2.7", + "resolved": "https://registry.npmjs.org/debug/-/debug-3.2.7.tgz", + "integrity": "sha512-CFjzYYAi4ThfiQvizrFQevTTXHtnCqWfe7x1AhgEscTz6ZbLbfoLRLPugTQyBth6f8ZERVUSyWHFD/7Wu4t1XQ==", "dev": true, "dependencies": { "ms": "^2.1.1" } }, - "node_modules/eth-gas-reporter/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==", + "node_modules/eslint-plugin-import/node_modules/doctrine": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/doctrine/-/doctrine-2.1.0.tgz", + "integrity": "sha512-35mSku4ZXK0vfCuHEDAwt55dg2jNajHZ1odvF+8SSr82EsZY4QmXfuWso8oEd8zRhVObSN18aM0CjSdoBX7zIw==", "dev": true, + "dependencies": { + "esutils": "^2.0.2" + }, "engines": { "node": ">=0.10.0" } }, - "node_modules/eth-gas-reporter/node_modules/diff": { - "version": "3.5.0", - "resolved": "https://registry.npmjs.org/diff/-/diff-3.5.0.tgz", - "integrity": "sha512-A46qtFgd+g7pDZinpnwiRJtxbC1hpgf0uzP3iG89scHk0AUC7A1TGxf5OiiOUv/JMZR8GOt8hL900hV0bOy5xA==", + "node_modules/eslint-plugin-import/node_modules/semver": { + "version": "6.3.1", + "resolved": "https://registry.npmjs.org/semver/-/semver-6.3.1.tgz", + "integrity": "sha512-BR7VvDCVHO+q2xBEWskxS6DJE1qRnb7DxzUrogb71CWoSficBxYsiAGd+Kl0mmq/MprG9yArRkyrQxTO6XjMzA==", "dev": true, - "engines": { - "node": ">=0.3.1" + "bin": { + "semver": "bin/semver.js" } }, - "node_modules/eth-gas-reporter/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==", - "dev": true - }, - "node_modules/eth-gas-reporter/node_modules/escape-string-regexp": { - "version": "1.0.5", - "resolved": "https://registry.npmjs.org/escape-string-regexp/-/escape-string-regexp-1.0.5.tgz", - "integrity": "sha512-vbRorB5FUQWvla16U8R/qgaFIya2qGzwDrNmCZuYKrbdSUMG6I1ZCGQRefkRVhuOkIGVne7BQ35DSfo1qvJqFg==", + "node_modules/eslint-plugin-node": { + "version": "11.1.0", + "resolved": "https://registry.npmjs.org/eslint-plugin-node/-/eslint-plugin-node-11.1.0.tgz", + "integrity": "sha512-oUwtPJ1W0SKD0Tr+wqu92c5xuCeQqB3hSCHasn/ZgjFdA9iDGNkNf2Zi9ztY7X+hNuMib23LNGRm6+uN+KLE3g==", "dev": true, + "dependencies": { + "eslint-plugin-es": "^3.0.0", + "eslint-utils": "^2.0.0", + "ignore": "^5.1.1", + "minimatch": "^3.0.4", + "resolve": "^1.10.1", + "semver": "^6.1.0" + }, "engines": { - "node": ">=0.8.0" + "node": ">=8.10.0" + }, + "peerDependencies": { + "eslint": ">=5.16.0" } }, - "node_modules/eth-gas-reporter/node_modules/ethers": { - "version": "4.0.49", - "resolved": "https://registry.npmjs.org/ethers/-/ethers-4.0.49.tgz", - "integrity": "sha512-kPltTvWiyu+OktYy1IStSO16i2e7cS9D9OxZ81q2UUaiNPVrm/RTcbxamCXF9VUSKzJIdJV68EAIhTEVBalRWg==", + "node_modules/eslint-plugin-node/node_modules/eslint-utils": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/eslint-utils/-/eslint-utils-2.1.0.tgz", + "integrity": "sha512-w94dQYoauyvlDc43XnGB8lU3Zt713vNChgt4EWwhXAP2XkBvndfxF0AgIqKOOasjPIPzj9JqgwkwbCYD0/V3Zg==", "dev": true, "dependencies": { - "aes-js": "3.0.0", - "bn.js": "^4.11.9", - "elliptic": "6.5.4", - "hash.js": "1.1.3", - "js-sha3": "0.5.7", - "scrypt-js": "2.0.4", - "setimmediate": "1.0.4", - "uuid": "2.0.1", - "xmlhttprequest": "1.8.0" + "eslint-visitor-keys": "^1.1.0" + }, + "engines": { + "node": ">=6" + }, + "funding": { + "url": "https://github.com/sponsors/mysticatea" } }, - "node_modules/eth-gas-reporter/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==", + "node_modules/eslint-plugin-node/node_modules/eslint-visitor-keys": { + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/eslint-visitor-keys/-/eslint-visitor-keys-1.3.0.tgz", + "integrity": "sha512-6J72N8UNa462wa/KFODt/PJ3IU60SDpC3QXC1Hjc1BXXpfL2C9R5+AU7jhe0F6GREqVMh4Juu+NY7xn+6dipUQ==", "dev": true, - "dependencies": { - "locate-path": "^3.0.0" - }, "engines": { - "node": ">=6" + "node": ">=4" } }, - "node_modules/eth-gas-reporter/node_modules/flat": { - "version": "4.1.1", - "resolved": "https://registry.npmjs.org/flat/-/flat-4.1.1.tgz", - "integrity": "sha512-FmTtBsHskrU6FJ2VxCnsDb84wu9zhmO3cUX2kGFb5tuwhfXxGciiT0oRY+cck35QmG+NmGh5eLz6lLCpWTqwpA==", + "node_modules/eslint-plugin-node/node_modules/semver": { + "version": "6.3.1", + "resolved": "https://registry.npmjs.org/semver/-/semver-6.3.1.tgz", + "integrity": "sha512-BR7VvDCVHO+q2xBEWskxS6DJE1qRnb7DxzUrogb71CWoSficBxYsiAGd+Kl0mmq/MprG9yArRkyrQxTO6XjMzA==", "dev": true, - "dependencies": { - "is-buffer": "~2.0.3" - }, "bin": { - "flat": "cli.js" + "semver": "bin/semver.js" } }, - "node_modules/eth-gas-reporter/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\"", + "node_modules/eslint-plugin-prettier": { + "version": "3.4.1", + "resolved": "https://registry.npmjs.org/eslint-plugin-prettier/-/eslint-plugin-prettier-3.4.1.tgz", + "integrity": "sha512-htg25EUYUeIhKHXjOinK4BgCcDwtLHjqaxCDsMy5nbnUMkKFvIhMVCp+5GFUXQ4Nr8lBsPqtGAqBenbpFqAA2g==", "dev": true, - "hasInstallScript": true, - "optional": true, - "os": [ - "darwin" - ], + "dependencies": { + "prettier-linter-helpers": "^1.0.0" + }, "engines": { - "node": "^8.16.0 || ^10.6.0 || >=11.0.0" + "node": ">=6.0.0" + }, + "peerDependencies": { + "eslint": ">=5.0.0", + "prettier": ">=1.13.0" + }, + "peerDependenciesMeta": { + "eslint-config-prettier": { + "optional": true + } } }, - "node_modules/eth-gas-reporter/node_modules/glob": { - "version": "7.1.3", - "resolved": "https://registry.npmjs.org/glob/-/glob-7.1.3.tgz", - "integrity": "sha512-vcfuiIxogLV4DlGBHIUOwI0IbrJ8HWPc4MU7HzviGeNho/UJDfi6B5p3sHeWIQ0KGIU0Jpxi5ZHxemQfLkkAwQ==", + "node_modules/eslint-plugin-promise": { + "version": "5.2.0", + "resolved": "https://registry.npmjs.org/eslint-plugin-promise/-/eslint-plugin-promise-5.2.0.tgz", + "integrity": "sha512-SftLb1pUG01QYq2A/hGAWfDRXqYD82zE7j7TopDOyNdU+7SvvoXREls/+PRTY17vUXzXnZA/zfnyKgRH6x4JJw==", "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": "^10.12.0 || >=12.0.0" + }, + "peerDependencies": { + "eslint": "^7.0.0" } }, - "node_modules/eth-gas-reporter/node_modules/has-flag": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-3.0.0.tgz", - "integrity": "sha512-sKJf1+ceQBr4SMkvQnBDNDtf4TXpVhVGateu0t918bl30FnbE2m4vNLX+VWe/dpjlb+HugGYzW7uQXH98HPEYw==", + "node_modules/eslint-scope": { + "version": "5.1.1", + "resolved": "https://registry.npmjs.org/eslint-scope/-/eslint-scope-5.1.1.tgz", + "integrity": "sha512-2NxwbF/hZ0KpepYN0cNbo+FN6XoK7GaHlQhgx/hIZl6Va0bF45RQOOwhLIy8lQDbuCiadSLCBnH2CFYquit5bw==", "dev": true, + "dependencies": { + "esrecurse": "^4.3.0", + "estraverse": "^4.1.1" + }, "engines": { - "node": ">=4" + "node": ">=8.0.0" } }, - "node_modules/eth-gas-reporter/node_modules/hash.js": { - "version": "1.1.3", - "resolved": "https://registry.npmjs.org/hash.js/-/hash.js-1.1.3.tgz", - "integrity": "sha512-/UETyP0W22QILqS+6HowevwhEFJ3MBJnwTf75Qob9Wz9t0DPuisL8kW8YZMK62dHAKE1c1p+gY1TtOLY+USEHA==", + "node_modules/eslint-utils": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/eslint-utils/-/eslint-utils-3.0.0.tgz", + "integrity": "sha512-uuQC43IGctw68pJA1RgbQS8/NP7rch6Cwd4j3ZBtgo4/8Flj4eGE7ZYSZRN3iq5pVUv6GPdW5Z1RFleo84uLDA==", "dev": true, "dependencies": { - "inherits": "^2.0.3", - "minimalistic-assert": "^1.0.0" + "eslint-visitor-keys": "^2.0.0" + }, + "engines": { + "node": "^10.0.0 || ^12.0.0 || >= 14.0.0" + }, + "funding": { + "url": "https://github.com/sponsors/mysticatea" + }, + "peerDependencies": { + "eslint": ">=5" } }, - "node_modules/eth-gas-reporter/node_modules/js-sha3": { - "version": "0.5.7", - "resolved": "https://registry.npmjs.org/js-sha3/-/js-sha3-0.5.7.tgz", - "integrity": "sha512-GII20kjaPX0zJ8wzkTbNDYMY7msuZcTWk8S5UOh6806Jq/wz1J8/bnr8uGU0DAUmYDjj2Mr4X1cW8v/GLYnR+g==", - "dev": true - }, - "node_modules/eth-gas-reporter/node_modules/js-yaml": { - "version": "3.13.1", - "resolved": "https://registry.npmjs.org/js-yaml/-/js-yaml-3.13.1.tgz", - "integrity": "sha512-YfbcO7jXDdyj0DGxYVSlSeQNHbD7XPWvrVWeVUujrQEoZzWJIRrCPoyk6kL6IAjAG2IolMK4T0hNUe0HOUs5Jw==", + "node_modules/eslint-visitor-keys": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/eslint-visitor-keys/-/eslint-visitor-keys-2.1.0.tgz", + "integrity": "sha512-0rSmRBzXgDzIsD6mGdJgevzgezI534Cer5L/vyMX0kHzT/jiB43jRhd9YUlMGYLQy2zprNmoT8qasCGtY+QaKw==", "dev": true, - "dependencies": { - "argparse": "^1.0.7", - "esprima": "^4.0.0" - }, - "bin": { - "js-yaml": "bin/js-yaml.js" + "engines": { + "node": ">=10" } }, - "node_modules/eth-gas-reporter/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==", + "node_modules/eslint/node_modules/eslint-utils": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/eslint-utils/-/eslint-utils-2.1.0.tgz", + "integrity": "sha512-w94dQYoauyvlDc43XnGB8lU3Zt713vNChgt4EWwhXAP2XkBvndfxF0AgIqKOOasjPIPzj9JqgwkwbCYD0/V3Zg==", "dev": true, "dependencies": { - "p-locate": "^3.0.0", - "path-exists": "^3.0.0" + "eslint-visitor-keys": "^1.1.0" }, "engines": { "node": ">=6" + }, + "funding": { + "url": "https://github.com/sponsors/mysticatea" } }, - "node_modules/eth-gas-reporter/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==", + "node_modules/eslint/node_modules/eslint-utils/node_modules/eslint-visitor-keys": { + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/eslint-visitor-keys/-/eslint-visitor-keys-1.3.0.tgz", + "integrity": "sha512-6J72N8UNa462wa/KFODt/PJ3IU60SDpC3QXC1Hjc1BXXpfL2C9R5+AU7jhe0F6GREqVMh4Juu+NY7xn+6dipUQ==", "dev": true, - "dependencies": { - "chalk": "^2.4.2" - }, "engines": { - "node": ">=8" + "node": ">=4" } }, - "node_modules/eth-gas-reporter/node_modules/minimatch": { - "version": "3.0.4", - "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.0.4.tgz", - "integrity": "sha512-yJHVQEhyqPLUTgt9B83PXu6W3rx4MvvHvSUvToogpwoGDOUQ+yDrR0HRot+yOCdCO7u4hX3pWft6kWBBcqh0UA==", + "node_modules/eslint/node_modules/ignore": { + "version": "4.0.6", + "resolved": "https://registry.npmjs.org/ignore/-/ignore-4.0.6.tgz", + "integrity": "sha512-cyFDKrqc/YdcWFniJhzI42+AzS+gNwmUzOSFcRCQYwySuBBBy/KjuxWLZ/FHEH6Moq1NizMOBWyTcv8O4OZIMg==", "dev": true, - "dependencies": { - "brace-expansion": "^1.1.7" - }, "engines": { - "node": "*" + "node": ">= 4" } }, - "node_modules/eth-gas-reporter/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==", + "node_modules/esniff": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/esniff/-/esniff-2.0.1.tgz", + "integrity": "sha512-kTUIGKQ/mDPFoJ0oVfcmyJn4iBDRptjNVIzwIFR7tqWXdVI9xfA2RMwY/gbSpJG3lkdWNEjLap/NqVHZiJsdfg==", "dev": true, "dependencies": { - "minimist": "^1.2.5" + "d": "^1.0.1", + "es5-ext": "^0.10.62", + "event-emitter": "^0.3.5", + "type": "^2.7.2" }, - "bin": { - "mkdirp": "bin/cmd.js" + "engines": { + "node": ">=0.10" } }, - "node_modules/eth-gas-reporter/node_modules/mocha": { - "version": "7.2.0", - "resolved": "https://registry.npmjs.org/mocha/-/mocha-7.2.0.tgz", - "integrity": "sha512-O9CIypScywTVpNaRrCAgoUnJgozpIofjKUYmJhiCIJMiuYnLI6otcb1/kpW9/n/tJODHGZ7i8aLQoDVsMtOKQQ==", + "node_modules/espree": { + "version": "7.3.1", + "resolved": "https://registry.npmjs.org/espree/-/espree-7.3.1.tgz", + "integrity": "sha512-v3JCNCE64umkFpmkFGqzVKsOT0tN1Zr+ueqLZfpV1Ob8e+CEgPWa+OxCoGH3tnhimMKIaBm4m/vaRpJ/krRz2g==", "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" + "acorn": "^7.4.0", + "acorn-jsx": "^5.3.1", + "eslint-visitor-keys": "^1.3.0" }, "engines": { - "node": ">= 8.10.0" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/mochajs" + "node": "^10.12.0 || >=12.0.0" } }, - "node_modules/eth-gas-reporter/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/eth-gas-reporter/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==", + "node_modules/espree/node_modules/eslint-visitor-keys": { + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/eslint-visitor-keys/-/eslint-visitor-keys-1.3.0.tgz", + "integrity": "sha512-6J72N8UNa462wa/KFODt/PJ3IU60SDpC3QXC1Hjc1BXXpfL2C9R5+AU7jhe0F6GREqVMh4Juu+NY7xn+6dipUQ==", "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": ">=4" } }, - "node_modules/eth-gas-reporter/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==", + "node_modules/esprima": { + "version": "4.0.1", + "resolved": "https://registry.npmjs.org/esprima/-/esprima-4.0.1.tgz", + "integrity": "sha512-eGuFFw7Upda+g4p+QHvnW0RyTX/SVeJBDM/gCtMARO0cLuT2HcEKnTPvhjV6aGeqrCB/sbNop0Kszm0jsaWU4A==", "dev": true, - "dependencies": { - "p-try": "^2.0.0" + "bin": { + "esparse": "bin/esparse.js", + "esvalidate": "bin/esvalidate.js" }, "engines": { - "node": ">=6" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" + "node": ">=4" } }, - "node_modules/eth-gas-reporter/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==", + "node_modules/esquery": { + "version": "1.5.0", + "resolved": "https://registry.npmjs.org/esquery/-/esquery-1.5.0.tgz", + "integrity": "sha512-YQLXUplAwJgCydQ78IMJywZCceoqk1oH01OERdSAJc/7U2AylwjhSCLDEtqwg811idIS/9fIU5GjG73IgjKMVg==", "dev": true, "dependencies": { - "p-limit": "^2.0.0" + "estraverse": "^5.1.0" }, "engines": { - "node": ">=6" + "node": ">=0.10" } }, - "node_modules/eth-gas-reporter/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==", + "node_modules/esquery/node_modules/estraverse": { + "version": "5.3.0", + "resolved": "https://registry.npmjs.org/estraverse/-/estraverse-5.3.0.tgz", + "integrity": "sha512-MMdARuVEQziNTeJD8DgMqmhwR11BRQ/cBP+pLtYdSTnf3MIO8fFeiINEbX36ZdNlfU/7A9f3gUw49B3oQsvwBA==", "dev": true, "engines": { - "node": ">=6" + "node": ">=4.0" } }, - "node_modules/eth-gas-reporter/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==", + "node_modules/esrecurse": { + "version": "4.3.0", + "resolved": "https://registry.npmjs.org/esrecurse/-/esrecurse-4.3.0.tgz", + "integrity": "sha512-KmfKL3b6G+RXvP8N1vr3Tq1kL/oCFgn2NYXEtqP8/L3pKapUA4G8cFVaoF3SU323CD4XypR/ffioHmkti6/Tag==", "dev": true, "dependencies": { - "picomatch": "^2.0.4" + "estraverse": "^5.2.0" }, "engines": { - "node": ">= 8" + "node": ">=4.0" } }, - "node_modules/eth-gas-reporter/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/eth-gas-reporter/node_modules/scrypt-js": { - "version": "2.0.4", - "resolved": "https://registry.npmjs.org/scrypt-js/-/scrypt-js-2.0.4.tgz", - "integrity": "sha512-4KsaGcPnuhtCZQCxFxN3GVYIhKFPTdLd8PLC552XwbMndtD0cjRFAhDuuydXQ0h08ZfPgzqe6EKHozpuH74iDw==", - "dev": true - }, - "node_modules/eth-gas-reporter/node_modules/setimmediate": { - "version": "1.0.4", - "resolved": "https://registry.npmjs.org/setimmediate/-/setimmediate-1.0.4.tgz", - "integrity": "sha512-/TjEmXQVEzdod/FFskf3o7oOAsGhHf2j1dZqRFbDzq4F3mvvxflIIi4Hd3bLQE9y/CpwqfSQam5JakI/mi3Pog==", - "dev": true - }, - "node_modules/eth-gas-reporter/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==", + "node_modules/esrecurse/node_modules/estraverse": { + "version": "5.3.0", + "resolved": "https://registry.npmjs.org/estraverse/-/estraverse-5.3.0.tgz", + "integrity": "sha512-MMdARuVEQziNTeJD8DgMqmhwR11BRQ/cBP+pLtYdSTnf3MIO8fFeiINEbX36ZdNlfU/7A9f3gUw49B3oQsvwBA==", "dev": true, - "dependencies": { - "emoji-regex": "^7.0.1", - "is-fullwidth-code-point": "^2.0.0", - "strip-ansi": "^5.1.0" - }, "engines": { - "node": ">=6" + "node": ">=4.0" } }, - "node_modules/eth-gas-reporter/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==", + "node_modules/estraverse": { + "version": "4.3.0", + "resolved": "https://registry.npmjs.org/estraverse/-/estraverse-4.3.0.tgz", + "integrity": "sha512-39nnKffWz8xN1BU/2c79n9nB9HDzo0niYUqx6xyqUnyoAnQyyWpOTdZEeiCch8BBu515t4wp9ZmgVfVhn9EBpw==", "dev": true, - "dependencies": { - "ansi-regex": "^4.1.0" - }, "engines": { - "node": ">=6" + "node": ">=4.0" } }, - "node_modules/eth-gas-reporter/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==", + "node_modules/esutils": { + "version": "2.0.3", + "resolved": "https://registry.npmjs.org/esutils/-/esutils-2.0.3.tgz", + "integrity": "sha512-kVscqXk4OCp68SZ0dkgEKVi6/8ij300KBWTJq32P/dYeWTSwK41WyTxalN1eRmA5Z9UU/LX9D7FWSmV9SAYx6g==", "dev": true, "engines": { "node": ">=0.10.0" } }, - "node_modules/eth-gas-reporter/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==", + "node_modules/etag": { + "version": "1.8.1", + "resolved": "https://registry.npmjs.org/etag/-/etag-1.8.1.tgz", + "integrity": "sha512-aIL5Fx7mawVa300al2BnEE4iNvo1qETxLrPI/o05L7z6go7fCw1J6EQmbK4FmJ2AS7kgVF/KEZWufBfdClMcPg==", "dev": true, - "dependencies": { - "has-flag": "^3.0.0" - }, "engines": { - "node": ">=6" + "node": ">= 0.6" } }, - "node_modules/eth-gas-reporter/node_modules/uuid": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/uuid/-/uuid-2.0.1.tgz", - "integrity": "sha512-nWg9+Oa3qD2CQzHIP4qKUqwNfzKn8P0LtFhotaCTFchsV7ZfDhAybeip/HZVeMIpZi9JgY1E3nUlwaCmZT1sEg==", - "deprecated": "Please upgrade to version 7 or higher. Older versions may use Math.random() in certain circumstances, which is known to be problematic. See https://v8.dev/blog/math-random for details.", - "dev": true - }, - "node_modules/eth-gas-reporter/node_modules/which": { - "version": "1.3.1", - "resolved": "https://registry.npmjs.org/which/-/which-1.3.1.tgz", - "integrity": "sha512-HxJdYWq1MTIQbJ3nw0cqssHoTNU267KlrDuGZ1WYlxDStUtKUhOaJmh112/TZmHxxUfuJqPXSOm7tDyas0OSIQ==", + "node_modules/eth-ens-namehash": { + "version": "2.0.8", + "resolved": "https://registry.npmjs.org/eth-ens-namehash/-/eth-ens-namehash-2.0.8.tgz", + "integrity": "sha512-VWEI1+KJfz4Km//dadyvBBoBeSQ0MHTXPvr8UIXiLW6IanxvAV+DmlZAijZwAyggqGUfwQBeHf7tc9wzc1piSw==", "dev": true, "dependencies": { - "isexe": "^2.0.0" - }, - "bin": { - "which": "bin/which" + "idna-uts46-hx": "^2.3.1", + "js-sha3": "^0.5.7" } }, - "node_modules/eth-gas-reporter/node_modules/which-module": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/which-module/-/which-module-2.0.1.tgz", - "integrity": "sha512-iBdZ57RDvnOR9AGBhML2vFZf7h8vmBjhoaZqODJBFWHVtKkDmKuHai3cx5PgVMrX5YDNp27AofYbAwctSS+vhQ==", + "node_modules/eth-ens-namehash/node_modules/js-sha3": { + "version": "0.5.7", + "resolved": "https://registry.npmjs.org/js-sha3/-/js-sha3-0.5.7.tgz", + "integrity": "sha512-GII20kjaPX0zJ8wzkTbNDYMY7msuZcTWk8S5UOh6806Jq/wz1J8/bnr8uGU0DAUmYDjj2Mr4X1cW8v/GLYnR+g==", "dev": true }, - "node_modules/eth-gas-reporter/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==", + "node_modules/eth-gas-reporter": { + "version": "0.2.27", + "resolved": "https://registry.npmjs.org/eth-gas-reporter/-/eth-gas-reporter-0.2.27.tgz", + "integrity": "sha512-femhvoAM7wL0GcI8ozTdxfuBtBFJ9qsyIAsmKVjlWAHUbdnnXHt+lKzz/kmldM5lA9jLuNHGwuIxorNpLbR1Zw==", "dev": true, "dependencies": { - "ansi-styles": "^3.2.0", - "string-width": "^3.0.0", - "strip-ansi": "^5.0.0" + "@solidity-parser/parser": "^0.14.0", + "axios": "^1.5.1", + "cli-table3": "^0.5.0", + "colors": "1.4.0", + "ethereum-cryptography": "^1.0.3", + "ethers": "^5.7.2", + "fs-readdir-recursive": "^1.1.0", + "lodash": "^4.17.14", + "markdown-table": "^1.1.3", + "mocha": "^10.2.0", + "req-cwd": "^2.0.0", + "sha1": "^1.1.1", + "sync-request": "^6.0.0" }, - "engines": { - "node": ">=6" + "peerDependencies": { + "@codechecks/client": "^0.1.0" + }, + "peerDependenciesMeta": { + "@codechecks/client": { + "optional": true + } } }, - "node_modules/eth-gas-reporter/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/eth-gas-reporter/node_modules/@noble/hashes": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/@noble/hashes/-/hashes-1.2.0.tgz", + "integrity": "sha512-FZfhjEDbT5GRswV3C6uvLPHMiVD6lQBmpoX5+eSiPaMTXte/IKqI5dykDxzZB/WBeK/CDuQRBWarPdi3FNY2zQ==", + "dev": true, + "funding": [ + { + "type": "individual", + "url": "https://paulmillr.com/funding/" + } + ] }, - "node_modules/eth-gas-reporter/node_modules/yargs": { - "version": "13.3.2", - "resolved": "https://registry.npmjs.org/yargs/-/yargs-13.3.2.tgz", - "integrity": "sha512-AX3Zw5iPruN5ie6xGRIDgqkT+ZhnRlZMLMHAs8tg7nRruy2Nb+i5o9bwghAogtM08q1dpr2LVoS8KSTMYpWXUw==", + "node_modules/eth-gas-reporter/node_modules/@scure/bip32": { + "version": "1.1.5", + "resolved": "https://registry.npmjs.org/@scure/bip32/-/bip32-1.1.5.tgz", + "integrity": "sha512-XyNh1rB0SkEqd3tXcXMi+Xe1fvg+kUIcoRIEujP1Jgv7DqW2r9lg3Ah0NkFaCs9sTkQAQA8kw7xiRXzENi9Rtw==", "dev": true, + "funding": [ + { + "type": "individual", + "url": "https://paulmillr.com/funding/" + } + ], "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" + "@noble/hashes": "~1.2.0", + "@noble/secp256k1": "~1.7.0", + "@scure/base": "~1.1.0" } }, - "node_modules/eth-gas-reporter/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==", + "node_modules/eth-gas-reporter/node_modules/@scure/bip39": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/@scure/bip39/-/bip39-1.1.1.tgz", + "integrity": "sha512-t+wDck2rVkh65Hmv280fYdVdY25J9YeEUIgn2LG1WM6gxFkGzcksoDiUkWVpVp3Oex9xGC68JU2dSbUfwZ2jPg==", "dev": true, + "funding": [ + { + "type": "individual", + "url": "https://paulmillr.com/funding/" + } + ], "dependencies": { - "camelcase": "^5.0.0", - "decamelize": "^1.2.0" + "@noble/hashes": "~1.2.0", + "@scure/base": "~1.1.0" } }, - "node_modules/eth-gas-reporter/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==", + "node_modules/eth-gas-reporter/node_modules/ethereum-cryptography": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/ethereum-cryptography/-/ethereum-cryptography-1.2.0.tgz", + "integrity": "sha512-6yFQC9b5ug6/17CQpCyE3k9eKBMdhyVjzUy1WkiuY/E4vj/SXDBbCw8QEIaXqf0Mf2SnY6RmpDcwlUmBSS0EJw==", "dev": true, "dependencies": { - "flat": "^4.1.0", - "lodash": "^4.17.15", - "yargs": "^13.3.0" - }, - "engines": { - "node": ">=6" + "@noble/hashes": "1.2.0", + "@noble/secp256k1": "1.7.1", + "@scure/bip32": "1.1.5", + "@scure/bip39": "1.1.1" } }, "node_modules/eth-lib": { @@ -6990,6 +6528,12 @@ "integrity": "sha512-c98Bf3tPniI+scsdk237ku1Dc3ujXQTSgyiPUDEOe7tRkhrqridvh8klBv0HCEso1OLOYcHuCv/cS6DNxKH+ZA==", "dev": true }, + "node_modules/eth-lib/node_modules/safe-buffer": { + "version": "5.1.2", + "resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.1.2.tgz", + "integrity": "sha512-Gd2UZBJDkXlY7GbJxfsE8/nvKkUEU1G38c1siN6QP6a9PT9MmHB8GnpscSmMJSoF8LOIrt8ud/wPtojys4G6+g==", + "dev": true + }, "node_modules/eth-lib/node_modules/ws": { "version": "3.3.3", "resolved": "https://registry.npmjs.org/ws/-/ws-3.3.3.tgz", @@ -7002,23 +6546,34 @@ } }, "node_modules/ethereum-bloom-filters": { - "version": "1.0.10", - "resolved": "https://registry.npmjs.org/ethereum-bloom-filters/-/ethereum-bloom-filters-1.0.10.tgz", - "integrity": "sha512-rxJ5OFN3RwjQxDcFP2Z5+Q9ho4eIdEmSc2ht0fCu8Se9nbXjZ7/031uXoUYJ87KHCOdVeiUuwSnoS7hmYAGVHA==", + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/ethereum-bloom-filters/-/ethereum-bloom-filters-1.1.0.tgz", + "integrity": "sha512-J1gDRkLpuGNvWYzWslBQR9cDV4nd4kfvVTE/Wy4Kkm4yb3EYRSlyi0eB/inTsSTTVyA0+HyzHgbr95Fn/Z1fSw==", "dependencies": { - "js-sha3": "^0.8.0" + "@noble/hashes": "^1.4.0" } }, "node_modules/ethereum-cryptography": { - "version": "1.2.0", - "resolved": "https://registry.npmjs.org/ethereum-cryptography/-/ethereum-cryptography-1.2.0.tgz", - "integrity": "sha512-6yFQC9b5ug6/17CQpCyE3k9eKBMdhyVjzUy1WkiuY/E4vj/SXDBbCw8QEIaXqf0Mf2SnY6RmpDcwlUmBSS0EJw==", + "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": { - "@noble/hashes": "1.2.0", - "@noble/secp256k1": "1.7.1", - "@scure/bip32": "1.1.5", - "@scure/bip39": "1.1.1" + "@types/pbkdf2": "^3.0.0", + "@types/secp256k1": "^4.0.1", + "blakejs": "^1.1.0", + "browserify-aes": "^1.2.0", + "bs58check": "^2.1.2", + "create-hash": "^1.2.0", + "create-hmac": "^1.1.7", + "hash.js": "^1.1.7", + "keccak": "^3.0.0", + "pbkdf2": "^3.0.17", + "randombytes": "^2.1.0", + "safe-buffer": "^5.1.2", + "scrypt-js": "^3.0.0", + "secp256k1": "^4.0.1", + "setimmediate": "^1.0.5" } }, "node_modules/ethereum-waffle": { @@ -7065,29 +6620,6 @@ "integrity": "sha512-c98Bf3tPniI+scsdk237ku1Dc3ujXQTSgyiPUDEOe7tRkhrqridvh8klBv0HCEso1OLOYcHuCv/cS6DNxKH+ZA==", "dev": true }, - "node_modules/ethereumjs-abi/node_modules/ethereum-cryptography": { - "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", - "blakejs": "^1.1.0", - "browserify-aes": "^1.2.0", - "bs58check": "^2.1.2", - "create-hash": "^1.2.0", - "create-hmac": "^1.1.7", - "hash.js": "^1.1.7", - "keccak": "^3.0.0", - "pbkdf2": "^3.0.17", - "randombytes": "^2.1.0", - "safe-buffer": "^5.1.2", - "scrypt-js": "^3.0.0", - "secp256k1": "^4.0.1", - "setimmediate": "^1.0.5" - } - }, "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", @@ -7107,6 +6639,7 @@ "version": "7.1.5", "resolved": "https://registry.npmjs.org/ethereumjs-util/-/ethereumjs-util-7.1.5.tgz", "integrity": "sha512-SDl5kKrQAudFBUe5OJM9Ac6WmMyYmXX/6sTmLZ3ffG2eY6ZIGBes3pEDxNN6V72WyOw4CPD5RomKdsa8DAAwLg==", + "dev": true, "dependencies": { "@types/bn.js": "^5.1.0", "bn.js": "^5.1.2", @@ -7118,28 +6651,6 @@ "node": ">=10.0.0" } }, - "node_modules/ethereumjs-util/node_modules/ethereum-cryptography": { - "version": "0.1.3", - "resolved": "https://registry.npmjs.org/ethereum-cryptography/-/ethereum-cryptography-0.1.3.tgz", - "integrity": "sha512-w8/4x1SGGzc+tO97TASLja6SLd3fRIK2tLVcV2Gx4IB21hE19atll5Cq9o3d0ZmAYC/8aw0ipieTSiekAea4SQ==", - "dependencies": { - "@types/pbkdf2": "^3.0.0", - "@types/secp256k1": "^4.0.1", - "blakejs": "^1.1.0", - "browserify-aes": "^1.2.0", - "bs58check": "^2.1.2", - "create-hash": "^1.2.0", - "create-hmac": "^1.1.7", - "hash.js": "^1.1.7", - "keccak": "^3.0.0", - "pbkdf2": "^3.0.17", - "randombytes": "^2.1.0", - "safe-buffer": "^5.1.2", - "scrypt-js": "^3.0.0", - "secp256k1": "^4.0.1", - "setimmediate": "^1.0.5" - } - }, "node_modules/ethers": { "version": "5.7.2", "resolved": "https://registry.npmjs.org/ethers/-/ethers-5.7.2.tgz", @@ -7219,13 +6730,14 @@ "npm": ">=3" } }, - "node_modules/event-target-shim": { - "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==", + "node_modules/event-emitter": { + "version": "0.3.5", + "resolved": "https://registry.npmjs.org/event-emitter/-/event-emitter-0.3.5.tgz", + "integrity": "sha512-D9rRn9y7kLPnJ+hMq7S/nhvoKwwvVJahBi2BPmx3bvbsEdK3W9ii8cBSGjP+72/LnM4n6fo3+dkCX5FeTQruXA==", "dev": true, - "engines": { - "node": ">=6" + "dependencies": { + "d": "1", + "es5-ext": "~0.10.14" } }, "node_modules/eventemitter3": { @@ -7238,23 +6750,24 @@ "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" } }, "node_modules/express": { - "version": "4.18.2", - "resolved": "https://registry.npmjs.org/express/-/express-4.18.2.tgz", - "integrity": "sha512-5/PsL6iGPdfQ/lKM1UuielYgv3BUoJfz1aUwU9vHZ+J7gyvwdQXFEBIEIaxeGf0GIcreATNyBExtalisDbuMqQ==", + "version": "4.19.2", + "resolved": "https://registry.npmjs.org/express/-/express-4.19.2.tgz", + "integrity": "sha512-5T6nhjsT+EOMzuck8JjBHARTHfMht0POzlA60WV2pMD3gyXw2LZnZ+ueGdNxG+0calOJcWKbpFcuzLZ91YWq9Q==", "dev": true, "dependencies": { "accepts": "~1.3.8", "array-flatten": "1.1.1", - "body-parser": "1.20.1", + "body-parser": "1.20.2", "content-disposition": "0.5.4", "content-type": "~1.0.4", - "cookie": "0.5.0", + "cookie": "0.6.0", "cookie-signature": "1.0.6", "debug": "2.6.9", "depd": "2.0.0", @@ -7285,34 +6798,10 @@ "node": ">= 0.10.0" } }, - "node_modules/express/node_modules/body-parser": { - "version": "1.20.1", - "resolved": "https://registry.npmjs.org/body-parser/-/body-parser-1.20.1.tgz", - "integrity": "sha512-jWi7abTbYwajOytWCQc37VulmWiRae5RyTpaCyDcS5/lMdtwSz5lOpDE67srw/HYe35f1z3fDQw+3txg7gNtWw==", - "dev": true, - "dependencies": { - "bytes": "3.1.2", - "content-type": "~1.0.4", - "debug": "2.6.9", - "depd": "2.0.0", - "destroy": "1.2.0", - "http-errors": "2.0.0", - "iconv-lite": "0.4.24", - "on-finished": "2.4.1", - "qs": "6.11.0", - "raw-body": "2.5.1", - "type-is": "~1.6.18", - "unpipe": "1.0.0" - }, - "engines": { - "node": ">= 0.8", - "npm": "1.2.8000 || >= 1.4.16" - } - }, "node_modules/express/node_modules/cookie": { - "version": "0.5.0", - "resolved": "https://registry.npmjs.org/cookie/-/cookie-0.5.0.tgz", - "integrity": "sha512-YZ3GUyn/o8gfKJlnlX7g7xq4gyO6OSuhGPKaaGssGB2qgDUS0gPgtTvoyZLTt9Ab6dC4hfc9dV5arkvc/OCmrw==", + "version": "0.6.0", + "resolved": "https://registry.npmjs.org/cookie/-/cookie-0.6.0.tgz", + "integrity": "sha512-U71cyTamuh1CRNCfpGY6to28lxvNwPG4Guz/EVjgf3Jmzv0vlDp1atT9eS5dDjMYHucpHbWns6Lwf3BKz6svdw==", "dev": true, "engines": { "node": ">= 0.6" @@ -7348,41 +6837,6 @@ "url": "https://github.com/sponsors/ljharb" } }, - "node_modules/express/node_modules/raw-body": { - "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", - "iconv-lite": "0.4.24", - "unpipe": "1.0.0" - }, - "engines": { - "node": ">= 0.8" - } - }, - "node_modules/express/node_modules/safe-buffer": { - "version": "5.2.1", - "resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.2.1.tgz", - "integrity": "sha512-rp3So07KcdmmKbGvgaNxQSJr7bGVSVk5S9Eq1F+ppbRo70+YeaDxkw5Dd8NPN+GD6bjnYm2VuPuCXmpuYvmCXQ==", - "dev": true, - "funding": [ - { - "type": "github", - "url": "https://github.com/sponsors/feross" - }, - { - "type": "patreon", - "url": "https://www.patreon.com/feross" - }, - { - "type": "consulting", - "url": "https://feross.org/support" - } - ] - }, "node_modules/ext": { "version": "1.7.0", "resolved": "https://registry.npmjs.org/ext/-/ext-1.7.0.tgz", @@ -7392,12 +6846,6 @@ "type": "^2.7.2" } }, - "node_modules/ext/node_modules/type": { - "version": "2.7.2", - "resolved": "https://registry.npmjs.org/type/-/type-2.7.2.tgz", - "integrity": "sha512-dzlvlNlt6AXU7EBSfpAscydQ7gXB+pPGsPnfJnZpiNJBDj7IaJzQlBZYGdEi4R9HmPdBv2XmWJ6YUtoTa7lmCw==", - "dev": true - }, "node_modules/extend": { "version": "3.0.2", "resolved": "https://registry.npmjs.org/extend/-/extend-3.0.2.tgz", @@ -7426,9 +6874,9 @@ "dev": true }, "node_modules/fast-glob": { - "version": "3.3.0", - "resolved": "https://registry.npmjs.org/fast-glob/-/fast-glob-3.3.0.tgz", - "integrity": "sha512-ChDuvbOypPuNjO8yIDf36x7BlZX1smcUMTTcyoIjycexOxd6DFsKsg21qVBzEmr3G7fUKIRy2/psii+CIUt7FA==", + "version": "3.3.2", + "resolved": "https://registry.npmjs.org/fast-glob/-/fast-glob-3.3.2.tgz", + "integrity": "sha512-oX2ruAFQwf/Orj8m737Y5adxDQO0LAB7/S5MnxCdTNDd4p6BsyIVsv9JQsATbTSq8KHRpLwIHbVlUNatxd+1Ow==", "dev": true, "dependencies": { "@nodelib/fs.stat": "^2.0.2", @@ -7454,9 +6902,9 @@ "dev": true }, "node_modules/fastq": { - "version": "1.15.0", - "resolved": "https://registry.npmjs.org/fastq/-/fastq-1.15.0.tgz", - "integrity": "sha512-wBrocU2LCXXa+lWBt8RoIRD89Fi8OdABODa/kEnyeyjS5aZO5/GNvI5sEINADqP/h8M29UHTHUb53sUu5Ihqdw==", + "version": "1.17.1", + "resolved": "https://registry.npmjs.org/fastq/-/fastq-1.17.1.tgz", + "integrity": "sha512-sRVD3lWVIXWg6By68ZN7vho9a1pQcN/WBFaAAsDDFzlJjvoGx0P8z7V1t72grFJfJhu3YPZBuu25f7Kaw2jN1w==", "dev": true, "dependencies": { "reusify": "^1.0.4" @@ -7575,12 +7023,13 @@ } }, "node_modules/flat-cache": { - "version": "3.0.4", - "resolved": "https://registry.npmjs.org/flat-cache/-/flat-cache-3.0.4.tgz", - "integrity": "sha512-dm9s5Pw7Jc0GvMYbshN6zchCA9RgQlzzEZX3vylR9IqFfS8XciblUXOKfW6SiuJ0e13eDYZoZV5wdrev7P3Nwg==", + "version": "3.2.0", + "resolved": "https://registry.npmjs.org/flat-cache/-/flat-cache-3.2.0.tgz", + "integrity": "sha512-CYcENa+FtcUKLmhhqyctpclsq7QF38pKjZHsGNiSQF5r4FtoKDWabFDl3hzaEQMvT1LHEysw5twgLvpYYb4vbw==", "dev": true, "dependencies": { - "flatted": "^3.1.0", + "flatted": "^3.2.9", + "keyv": "^4.5.3", "rimraf": "^3.0.2" }, "engines": { @@ -7588,15 +7037,15 @@ } }, "node_modules/flatted": { - "version": "3.2.7", - "resolved": "https://registry.npmjs.org/flatted/-/flatted-3.2.7.tgz", - "integrity": "sha512-5nqDSxl8nn5BSNxyR3n4I6eDmbolI6WT+QqR547RwxQapgjQBmtktdP+HTBb/a/zLsbzERTONyUB5pefh5TtjQ==", + "version": "3.3.1", + "resolved": "https://registry.npmjs.org/flatted/-/flatted-3.3.1.tgz", + "integrity": "sha512-X8cqMLLie7KsNUDSdzeN8FYK9rEt4Dt67OsG/DNGnYTSDBG4uFAJFBnUeiV+zCVAvwFy56IjM9sH51jVaEhNxw==", "dev": true }, "node_modules/follow-redirects": { - "version": "1.15.2", - "resolved": "https://registry.npmjs.org/follow-redirects/-/follow-redirects-1.15.2.tgz", - "integrity": "sha512-VQLG33o04KaQ8uYi2tVNbdrWp1QWxNNea+nmIB4EVM28v0hmP17z7aG1+wAkNzVq4KeXTq3221ye5qTJP91JwA==", + "version": "1.15.6", + "resolved": "https://registry.npmjs.org/follow-redirects/-/follow-redirects-1.15.6.tgz", + "integrity": "sha512-wWN62YITEaOpSK584EZXJafH1AGpO8RVgElfkuXbTOrPX4fIfOyEpW/CsiNd8JdYrAoOvafRTOEnvsO++qCqFA==", "dev": true, "funding": [ { @@ -7632,9 +7081,9 @@ } }, "node_modules/form-data": { - "version": "3.0.1", - "resolved": "https://registry.npmjs.org/form-data/-/form-data-3.0.1.tgz", - "integrity": "sha512-RHkBKtLWUVwd7SqRIvCZMEvAMoGUp0XU+seQiZejj0COz3RI3hWP4sCv3gZWWLjJTd7rGwcsF5eKZGii0r/hbg==", + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/form-data/-/form-data-4.0.0.tgz", + "integrity": "sha512-ETEklSGi5t0QMZuiXoA/Q6vcnxcLQP5vdugSpuAyi6SVGi2clPPp+xgEhuMaHC+zGgn31Kd235W35f7Hykkaww==", "dev": true, "dependencies": { "asynckit": "^0.4.0", @@ -7711,9 +7160,9 @@ "dev": true }, "node_modules/fsevents": { - "version": "2.3.2", - "resolved": "https://registry.npmjs.org/fsevents/-/fsevents-2.3.2.tgz", - "integrity": "sha512-xiqMQR4xAeHTuB9uWm+fFRcIOgKBMiOBP+eXiyT7jsgVCq1bkVygt00oASowB7EdtpOHaaPgKt812P9ab+DDKA==", + "version": "2.3.3", + "resolved": "https://registry.npmjs.org/fsevents/-/fsevents-2.3.3.tgz", + "integrity": "sha512-5xoDfX+fL7faATnagmWPpbFtwh/R77WmMMqqHGS65C3vvB0YHrgF+B1YmZ3441tMj5n63k0212XNoJwzlhffQw==", "dev": true, "hasInstallScript": true, "optional": true, @@ -7725,21 +7174,24 @@ } }, "node_modules/function-bind": { - "version": "1.1.1", - "resolved": "https://registry.npmjs.org/function-bind/-/function-bind-1.1.1.tgz", - "integrity": "sha512-yIovAzMX49sF8Yl58fSCWJ5svSLuaibPxXQJFLmBObTuCr0Mf1KiPopGM9NiFjiYBCbfaa2Fh6breQ6ANVTI0A==", - "dev": true + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/function-bind/-/function-bind-1.1.2.tgz", + "integrity": "sha512-7XHNxH7qX9xG5mIwxkhumTox/MIRNcOgDrxWsMt2pAr23WHp6MrRlN7FBSFpCpr+oVO0F744iUgR82nJMfG2SA==", + "dev": true, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } }, "node_modules/function.prototype.name": { - "version": "1.1.5", - "resolved": "https://registry.npmjs.org/function.prototype.name/-/function.prototype.name-1.1.5.tgz", - "integrity": "sha512-uN7m/BzVKQnCUF/iW8jYea67v++2u7m5UgENbHRtdDVclOUP+FMPlCNdmk0h/ysGyo2tavMJEDqJAkJdRa1vMA==", + "version": "1.1.6", + "resolved": "https://registry.npmjs.org/function.prototype.name/-/function.prototype.name-1.1.6.tgz", + "integrity": "sha512-Z5kx79swU5P27WEayXM1tBi5Ze/lbIyiNgU3qyXUOf9b2rgXYyF9Dy9Cx+IQv/Lc8WCG6L82zwUPpSS9hGehIg==", "dev": true, "dependencies": { "call-bind": "^1.0.2", - "define-properties": "^1.1.3", - "es-abstract": "^1.19.0", - "functions-have-names": "^1.2.2" + "define-properties": "^1.2.0", + "es-abstract": "^1.22.1", + "functions-have-names": "^1.2.3" }, "engines": { "node": ">= 0.4" @@ -16959,15 +16411,19 @@ } }, "node_modules/get-intrinsic": { - "version": "1.2.1", - "resolved": "https://registry.npmjs.org/get-intrinsic/-/get-intrinsic-1.2.1.tgz", - "integrity": "sha512-2DcsyfABl+gVHEfCOaTrWgyt+tb6MSEGmKq+kI5HwLbIYgjgmMcV8KQ41uaKz1xxUcn9tJtgFbQUEVcEbd0FYw==", + "version": "1.2.4", + "resolved": "https://registry.npmjs.org/get-intrinsic/-/get-intrinsic-1.2.4.tgz", + "integrity": "sha512-5uYhsJH8VJBTv7oslg4BznJYhDoRI6waYCxMmCdnTrcCrHA/fCFKoTFz2JKKE0HdDFUF7/oQuhzumXJK7paBRQ==", "dev": true, "dependencies": { - "function-bind": "^1.1.1", - "has": "^1.0.3", + "es-errors": "^1.3.0", + "function-bind": "^1.1.2", "has-proto": "^1.0.1", - "has-symbols": "^1.0.3" + "has-symbols": "^1.0.3", + "hasown": "^2.0.0" + }, + "engines": { + "node": ">= 0.4" }, "funding": { "url": "https://github.com/sponsors/ljharb" @@ -16995,13 +16451,14 @@ } }, "node_modules/get-symbol-description": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/get-symbol-description/-/get-symbol-description-1.0.0.tgz", - "integrity": "sha512-2EmdH1YvIQiZpltCNgkuiUnyukzxM/R6NDJX31Ke3BG1Nq5b0S2PhX59UKi9vZpPDQVdqn+1IcaAwnzTT5vCjw==", + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/get-symbol-description/-/get-symbol-description-1.0.2.tgz", + "integrity": "sha512-g0QYk1dZBxGwk+Ngc+ltRH2IBp2f7zBkBMBJZCDerh6EhlhSR6+9irMCuT/09zD6qkarHUSn529sK/yL4S27mg==", "dev": true, "dependencies": { - "call-bind": "^1.0.2", - "get-intrinsic": "^1.1.1" + "call-bind": "^1.0.5", + "es-errors": "^1.3.0", + "get-intrinsic": "^1.2.4" }, "engines": { "node": ">= 0.4" @@ -17184,9 +16641,9 @@ } }, "node_modules/globals": { - "version": "13.20.0", - "resolved": "https://registry.npmjs.org/globals/-/globals-13.20.0.tgz", - "integrity": "sha512-Qg5QtVkCy/kv3FUSlu4ukeZDVf9ee0iXLAUYX13gbR17bnejFTzr4iS9bY7kwCf1NztRNm1t91fjOiyx4CSwPQ==", + "version": "13.24.0", + "resolved": "https://registry.npmjs.org/globals/-/globals-13.24.0.tgz", + "integrity": "sha512-AhO5QUcj8llrbG09iWhPU2B204J1xnPeL8kQmVorSsy+Sjj1sk8gIyh6cUocGmH4L0UuhAJy+hJMRA4mgA4mFQ==", "dev": true, "dependencies": { "type-fest": "^0.20.2" @@ -17273,23 +16730,14 @@ "integrity": "sha512-RbJ5/jmFcNNCcDV5o9eTnBLJ/HszWV0P73bc+Ff4nS/rJj+YaS6IGyiOL0VoBYX+l1Wrl3k63h/KrH+nhJ0XvQ==", "dev": true }, - "node_modules/growl": { - "version": "1.10.5", - "resolved": "https://registry.npmjs.org/growl/-/growl-1.10.5.tgz", - "integrity": "sha512-qBr4OuELkhPenW6goKVXiv47US3clb3/IbuWF9KNKEijAy9oeHxU9IgzjvJhHkUzhaj7rOUD7+YGWqUjLp5oSA==", - "dev": true, - "engines": { - "node": ">=4.x" - } - }, "node_modules/handlebars": { - "version": "4.7.7", - "resolved": "https://registry.npmjs.org/handlebars/-/handlebars-4.7.7.tgz", - "integrity": "sha512-aAcXm5OAfE/8IXkcZvCepKU3VzW1/39Fb5ZuqMtgI/hT8X2YgoMvBY5dLhq/cpOvw7Lk1nK/UF71aLG/ZnVYRA==", + "version": "4.7.8", + "resolved": "https://registry.npmjs.org/handlebars/-/handlebars-4.7.8.tgz", + "integrity": "sha512-vafaFqs8MZkRrSX7sFVUdo3ap/eNiLnb4IakshzvP56X5Nr1iGKAIqdX6tMlm6HcNRIkr6AxO5jFEoJzzpT8aQ==", "dev": true, "dependencies": { "minimist": "^1.2.5", - "neo-async": "^2.6.0", + "neo-async": "^2.6.2", "source-map": "^0.6.1", "wordwrap": "^1.0.0" }, @@ -17336,31 +16784,25 @@ } }, "node_modules/hardhat": { - "version": "2.17.0", - "resolved": "https://registry.npmjs.org/hardhat/-/hardhat-2.17.0.tgz", - "integrity": "sha512-CaEGa13tkJNe2/rdaBiive4pmdNShwxvdWVhr1zfb6aVpRhQt9VNO0l/UIBt/zzajz38ZFjvhfM2bj8LDXo9gw==", + "version": "2.22.2", + "resolved": "https://registry.npmjs.org/hardhat/-/hardhat-2.22.2.tgz", + "integrity": "sha512-0xZ7MdCZ5sJem4MrvpQWLR3R3zGDoHw5lsR+pBFimqwagimIOn3bWuZv69KA+veXClwI1s/zpqgwPwiFrd4Dxw==", "dev": true, "dependencies": { "@ethersproject/abi": "^5.1.2", "@metamask/eth-sig-util": "^4.0.0", - "@nomicfoundation/ethereumjs-block": "5.0.1", - "@nomicfoundation/ethereumjs-blockchain": "7.0.1", - "@nomicfoundation/ethereumjs-common": "4.0.1", - "@nomicfoundation/ethereumjs-evm": "2.0.1", - "@nomicfoundation/ethereumjs-rlp": "5.0.1", - "@nomicfoundation/ethereumjs-statemanager": "2.0.1", - "@nomicfoundation/ethereumjs-trie": "6.0.1", - "@nomicfoundation/ethereumjs-tx": "5.0.1", - "@nomicfoundation/ethereumjs-util": "9.0.1", - "@nomicfoundation/ethereumjs-vm": "7.0.1", + "@nomicfoundation/edr": "^0.3.1", + "@nomicfoundation/ethereumjs-common": "4.0.4", + "@nomicfoundation/ethereumjs-tx": "5.0.4", + "@nomicfoundation/ethereumjs-util": "9.0.4", "@nomicfoundation/solidity-analyzer": "^0.1.0", "@sentry/node": "^5.18.1", "@types/bn.js": "^5.1.0", "@types/lru-cache": "^5.1.0", - "abort-controller": "^3.0.0", "adm-zip": "^0.4.16", "aggregate-error": "^3.0.0", "ansi-escapes": "^4.3.0", + "boxen": "^5.1.2", "chalk": "^2.4.2", "chokidar": "^3.4.0", "ci-info": "^2.0.0", @@ -17394,9 +16836,6 @@ "bin": { "hardhat": "internal/cli/bootstrap.js" }, - "engines": { - "node": ">=16.0.0" - }, "peerDependencies": { "ts-node": "*", "typescript": "*" @@ -17411,17 +16850,62 @@ } }, "node_modules/hardhat-gas-reporter": { - "version": "1.0.9", - "resolved": "https://registry.npmjs.org/hardhat-gas-reporter/-/hardhat-gas-reporter-1.0.9.tgz", - "integrity": "sha512-INN26G3EW43adGKBNzYWOlI3+rlLnasXTwW79YNnUhXPDa+yHESgt639dJEs37gCjhkbNKcRRJnomXEuMFBXJg==", + "version": "1.0.10", + "resolved": "https://registry.npmjs.org/hardhat-gas-reporter/-/hardhat-gas-reporter-1.0.10.tgz", + "integrity": "sha512-02N4+So/fZrzJ88ci54GqwVA3Zrf0C9duuTyGt0CFRIh/CdNwbnTgkXkRfojOMLBQ+6t+lBIkgbsOtqMvNwikA==", + "dev": true, + "dependencies": { + "array-uniq": "1.0.3", + "eth-gas-reporter": "^0.2.25", + "sha1": "^1.1.1" + }, + "peerDependencies": { + "hardhat": "^2.0.2" + } + }, + "node_modules/hardhat/node_modules/@noble/hashes": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/@noble/hashes/-/hashes-1.2.0.tgz", + "integrity": "sha512-FZfhjEDbT5GRswV3C6uvLPHMiVD6lQBmpoX5+eSiPaMTXte/IKqI5dykDxzZB/WBeK/CDuQRBWarPdi3FNY2zQ==", + "dev": true, + "funding": [ + { + "type": "individual", + "url": "https://paulmillr.com/funding/" + } + ] + }, + "node_modules/hardhat/node_modules/@scure/bip32": { + "version": "1.1.5", + "resolved": "https://registry.npmjs.org/@scure/bip32/-/bip32-1.1.5.tgz", + "integrity": "sha512-XyNh1rB0SkEqd3tXcXMi+Xe1fvg+kUIcoRIEujP1Jgv7DqW2r9lg3Ah0NkFaCs9sTkQAQA8kw7xiRXzENi9Rtw==", + "dev": true, + "funding": [ + { + "type": "individual", + "url": "https://paulmillr.com/funding/" + } + ], + "dependencies": { + "@noble/hashes": "~1.2.0", + "@noble/secp256k1": "~1.7.0", + "@scure/base": "~1.1.0" + } + }, + "node_modules/hardhat/node_modules/@scure/bip39": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/@scure/bip39/-/bip39-1.1.1.tgz", + "integrity": "sha512-t+wDck2rVkh65Hmv280fYdVdY25J9YeEUIgn2LG1WM6gxFkGzcksoDiUkWVpVp3Oex9xGC68JU2dSbUfwZ2jPg==", "dev": true, + "funding": [ + { + "type": "individual", + "url": "https://paulmillr.com/funding/" + } + ], "dependencies": { - "array-uniq": "1.0.3", - "eth-gas-reporter": "^0.2.25", - "sha1": "^1.1.1" - }, - "peerDependencies": { - "hardhat": "^2.0.2" + "@noble/hashes": "~1.2.0", + "@scure/base": "~1.1.0" } }, "node_modules/hardhat/node_modules/ansi-styles": { @@ -17474,6 +16958,18 @@ "node": ">=0.8.0" } }, + "node_modules/hardhat/node_modules/ethereum-cryptography": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/ethereum-cryptography/-/ethereum-cryptography-1.2.0.tgz", + "integrity": "sha512-6yFQC9b5ug6/17CQpCyE3k9eKBMdhyVjzUy1WkiuY/E4vj/SXDBbCw8QEIaXqf0Mf2SnY6RmpDcwlUmBSS0EJw==", + "dev": true, + "dependencies": { + "@noble/hashes": "1.2.0", + "@noble/secp256k1": "1.7.1", + "@scure/bip32": "1.1.5", + "@scure/bip39": "1.1.1" + } + }, "node_modules/hardhat/node_modules/has-flag": { "version": "3.0.0", "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-3.0.0.tgz", @@ -17516,6 +17012,15 @@ "rimraf": "bin.js" } }, + "node_modules/hardhat/node_modules/semver": { + "version": "6.3.1", + "resolved": "https://registry.npmjs.org/semver/-/semver-6.3.1.tgz", + "integrity": "sha512-BR7VvDCVHO+q2xBEWskxS6DJE1qRnb7DxzUrogb71CWoSficBxYsiAGd+Kl0mmq/MprG9yArRkyrQxTO6XjMzA==", + "dev": true, + "bin": { + "semver": "bin/semver.js" + } + }, "node_modules/hardhat/node_modules/solc": { "version": "0.7.3", "resolved": "https://registry.npmjs.org/solc/-/solc-0.7.3.tgz", @@ -17573,18 +17078,6 @@ "node": ">=4" } }, - "node_modules/has": { - "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" - }, - "engines": { - "node": ">= 0.4.0" - } - }, "node_modules/has-bigints": { "version": "1.0.2", "resolved": "https://registry.npmjs.org/has-bigints/-/has-bigints-1.0.2.tgz", @@ -17603,21 +17096,21 @@ } }, "node_modules/has-property-descriptors": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/has-property-descriptors/-/has-property-descriptors-1.0.0.tgz", - "integrity": "sha512-62DVLZGoiEBDHQyqG4w9xCuZ7eJEwNmJRWw2VY84Oedb7WFcA27fiEVe8oUQx9hAUJ4ekurquucTGwsyO1XGdQ==", + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/has-property-descriptors/-/has-property-descriptors-1.0.2.tgz", + "integrity": "sha512-55JNKuIW+vq4Ke1BjOTjM2YctQIvCT7GFzHwmfZPGo5wnrgkid0YQtnAleFSqumZm4az3n2BS+erby5ipJdgrg==", "dev": true, "dependencies": { - "get-intrinsic": "^1.1.1" + "es-define-property": "^1.0.0" }, "funding": { "url": "https://github.com/sponsors/ljharb" } }, "node_modules/has-proto": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/has-proto/-/has-proto-1.0.1.tgz", - "integrity": "sha512-7qE+iP+O+bgF9clE5+UoBFzE65mlBiVj3tKCrlNQ0Ogwm0BjpT/gK4SlLYDMybDh5I3TCTKnPPa0oMG7JDYrhg==", + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/has-proto/-/has-proto-1.0.3.tgz", + "integrity": "sha512-SJ1amZAJUiZS+PhsVLf5tGydlaVB8EdFpaSO4gmiUKUOxk8qzn5AIy4ZeJUmh22znIdk/uMAUT2pl3FxzVUH+Q==", "dev": true, "engines": { "node": ">= 0.4" @@ -17639,12 +17132,12 @@ } }, "node_modules/has-tostringtag": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/has-tostringtag/-/has-tostringtag-1.0.0.tgz", - "integrity": "sha512-kFjcSNhnlGV1kyoGk7OXKSawH5JOb/LzUc5w9B02hOTO0dfFRjbHQKvg1d6cf3HbeUmtU9VbbV3qzZ2Teh97WQ==", + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/has-tostringtag/-/has-tostringtag-1.0.2.tgz", + "integrity": "sha512-NqADB8VjPFLM2V0VvHUewwwsw0ZWBaIdgo+ieHtK3hasLz4qeCRjYcqfB6AQrBggRKppKF8L52/VqdVsO47Dlw==", "dev": true, "dependencies": { - "has-symbols": "^1.0.2" + "has-symbols": "^1.0.3" }, "engines": { "node": ">= 0.4" @@ -17657,6 +17150,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", @@ -17666,25 +17160,6 @@ "node": ">=4" } }, - "node_modules/hash-base/node_modules/safe-buffer": { - "version": "5.2.1", - "resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.2.1.tgz", - "integrity": "sha512-rp3So07KcdmmKbGvgaNxQSJr7bGVSVk5S9Eq1F+ppbRo70+YeaDxkw5Dd8NPN+GD6bjnYm2VuPuCXmpuYvmCXQ==", - "funding": [ - { - "type": "github", - "url": "https://github.com/sponsors/feross" - }, - { - "type": "patreon", - "url": "https://www.patreon.com/feross" - }, - { - "type": "consulting", - "url": "https://feross.org/support" - } - ] - }, "node_modules/hash.js": { "version": "1.1.7", "resolved": "https://registry.npmjs.org/hash.js/-/hash.js-1.1.7.tgz", @@ -17694,6 +17169,18 @@ "minimalistic-assert": "^1.0.1" } }, + "node_modules/hasown": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/hasown/-/hasown-2.0.2.tgz", + "integrity": "sha512-0hJU9SCPvmMzIBdZFqNPXWa6dqh7WdH0cII9y+CyS8rG3nL48Bclra9HmKhVVUHyPWNH5Y7xDwAB7bfgSjkUMQ==", + "dev": true, + "dependencies": { + "function-bind": "^1.1.2" + }, + "engines": { + "node": ">= 0.4" + } + }, "node_modules/he": { "version": "1.2.0", "resolved": "https://registry.npmjs.org/he/-/he-1.2.0.tgz", @@ -17793,9 +17280,9 @@ } }, "node_modules/http2-wrapper": { - "version": "2.2.0", - "resolved": "https://registry.npmjs.org/http2-wrapper/-/http2-wrapper-2.2.0.tgz", - "integrity": "sha512-kZB0wxMo0sh1PehyjJUWRFEd99KC5TLjZ2cULC4f9iqJBAmKQQXEICjxl5iPJRwP40dpeHFqqhm7tYCvODpqpQ==", + "version": "2.2.1", + "resolved": "https://registry.npmjs.org/http2-wrapper/-/http2-wrapper-2.2.1.tgz", + "integrity": "sha512-V5nVw1PAOgfI3Lmeaj2Exmeg7fenjhRUgz1lPSezy1CuhPYbgQtbQj4jZfEAEMlaL+vupsvhjqCyjzob0yxsmQ==", "dev": true, "dependencies": { "quick-lru": "^5.1.1", @@ -17862,18 +17349,18 @@ ] }, "node_modules/ignore": { - "version": "5.2.4", - "resolved": "https://registry.npmjs.org/ignore/-/ignore-5.2.4.tgz", - "integrity": "sha512-MAb38BcSbH0eHNBxn7ql2NH/kX33OkB3lZ1BNdh7ENeRChHTYsTvWrMubiIAMNS2llXEEgZ1MUOBtXChP3kaFQ==", + "version": "5.3.1", + "resolved": "https://registry.npmjs.org/ignore/-/ignore-5.3.1.tgz", + "integrity": "sha512-5Fytz/IraMjqpwfd34ke28PTVMjZjJG2MPn5t7OE4eUCUNf8BAa7b5WUS9/Qvr6mwOQS7Mk6vdsMno5he+T8Xw==", "dev": true, "engines": { "node": ">= 4" } }, "node_modules/immutable": { - "version": "4.3.1", - "resolved": "https://registry.npmjs.org/immutable/-/immutable-4.3.1.tgz", - "integrity": "sha512-lj9cnmB/kVS0QHsJnYKD1uo3o39nrbKxszjnqS9Fr6NB7bZzW45U6WSGBPKXDL/CvDKqDNPA4r3DoDQ8GTxo2A==", + "version": "4.3.5", + "resolved": "https://registry.npmjs.org/immutable/-/immutable-4.3.5.tgz", + "integrity": "sha512-8eabxkth9gZatlwl5TBuJnCsoTADlL6ftEr7A4qgdaTsPyreilDSnUk57SO+jfKcNtxPa22U5KK6DSeAYhpBJw==", "dev": true }, "node_modules/import-fresh": { @@ -17932,13 +17419,13 @@ "dev": true }, "node_modules/internal-slot": { - "version": "1.0.5", - "resolved": "https://registry.npmjs.org/internal-slot/-/internal-slot-1.0.5.tgz", - "integrity": "sha512-Y+R5hJrzs52QCG2laLn4udYVnxsfny9CpOhNhUvk/SSSVyF6T27FzRbF0sroPidSu3X8oEAkOn2K804mjpt6UQ==", + "version": "1.0.7", + "resolved": "https://registry.npmjs.org/internal-slot/-/internal-slot-1.0.7.tgz", + "integrity": "sha512-NGnrKwXzSms2qUUih/ILZ5JBqNTSa1+ZmP6flaIp6KmSElgE9qdndzS3cqjrDovwFdmwsGsLdeFgB6suw+1e9g==", "dev": true, "dependencies": { - "get-intrinsic": "^1.2.0", - "has": "^1.0.3", + "es-errors": "^1.3.0", + "hasown": "^2.0.0", "side-channel": "^1.0.4" }, "engines": { @@ -17998,14 +17485,16 @@ } }, "node_modules/is-array-buffer": { - "version": "3.0.2", - "resolved": "https://registry.npmjs.org/is-array-buffer/-/is-array-buffer-3.0.2.tgz", - "integrity": "sha512-y+FyyR/w8vfIRq4eQcM1EYgSTnmHXPqaF+IgzgraytCFq5Xh8lllDVmAZolPJiZttZLeFSINPYMaEJ7/vWUa1w==", + "version": "3.0.4", + "resolved": "https://registry.npmjs.org/is-array-buffer/-/is-array-buffer-3.0.4.tgz", + "integrity": "sha512-wcjaerHw0ydZwfhiKbXJWLDY8A7yV7KhjQOpb83hGgGfId/aQa4TOvwyzn2PuswW2gPCYEL/nEAiSVpdOj1lXw==", "dev": true, "dependencies": { "call-bind": "^1.0.2", - "get-intrinsic": "^1.2.0", - "is-typed-array": "^1.1.10" + "get-intrinsic": "^1.2.1" + }, + "engines": { + "node": ">= 0.4" }, "funding": { "url": "https://github.com/sponsors/ljharb" @@ -18057,29 +17546,6 @@ "url": "https://github.com/sponsors/ljharb" } }, - "node_modules/is-buffer": { - "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", - "url": "https://github.com/sponsors/feross" - }, - { - "type": "patreon", - "url": "https://www.patreon.com/feross" - }, - { - "type": "consulting", - "url": "https://feross.org/support" - } - ], - "engines": { - "node": ">=4" - } - }, "node_modules/is-callable": { "version": "1.2.7", "resolved": "https://registry.npmjs.org/is-callable/-/is-callable-1.2.7.tgz", @@ -18105,12 +17571,27 @@ } }, "node_modules/is-core-module": { - "version": "2.12.1", - "resolved": "https://registry.npmjs.org/is-core-module/-/is-core-module-2.12.1.tgz", - "integrity": "sha512-Q4ZuBAe2FUsKtyQJoQHlvP8OvBERxO3jEmy1I7hcRXcJBGGHFh/aJBswbXuS9sgrDH2QUO8ilkwNPHvHMd8clg==", + "version": "2.13.1", + "resolved": "https://registry.npmjs.org/is-core-module/-/is-core-module-2.13.1.tgz", + "integrity": "sha512-hHrIjvZsftOsvKSn2TRYl63zvxsgE0K+0mYMoH6gD4omR5IWB2KynivBQczo3+wF1cCkjzvptnI9Q0sPU66ilw==", "dev": true, "dependencies": { - "has": "^1.0.3" + "hasown": "^2.0.0" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/is-data-view": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/is-data-view/-/is-data-view-1.0.1.tgz", + "integrity": "sha512-AHkaJrsUVW6wq6JS8y3JnM/GJF/9cf+k20+iDzlSaJrinEo5+7vRiteOSwBhHRiAyQATN1AmY4hwzxJKPmYf+w==", + "dev": true, + "dependencies": { + "is-typed-array": "^1.1.13" + }, + "engines": { + "node": ">= 0.4" }, "funding": { "url": "https://github.com/sponsors/ljharb" @@ -18156,12 +17637,12 @@ } }, "node_modules/is-fullwidth-code-point": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/is-fullwidth-code-point/-/is-fullwidth-code-point-2.0.0.tgz", - "integrity": "sha512-VHskAKYM8RfSFXwee5t5cbN5PZeq1Wrh6qd5bkyiXIf6UQcN6w/A0eXM9r6t8d+GYOh+o6ZhiEnb88LN/Y8m2w==", + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/is-fullwidth-code-point/-/is-fullwidth-code-point-3.0.0.tgz", + "integrity": "sha512-zymm5+u+sCsSWyD9qNaejV3DFvhCKclKdizYaJUuHA83RLjb7nSuGnddCHGv0hk+KY7BMAlsWeK4Ueg6EV6XQg==", "dev": true, "engines": { - "node": ">=4" + "node": ">=8" } }, "node_modules/is-function": { @@ -18207,9 +17688,9 @@ } }, "node_modules/is-negative-zero": { - "version": "2.0.2", - "resolved": "https://registry.npmjs.org/is-negative-zero/-/is-negative-zero-2.0.2.tgz", - "integrity": "sha512-dqJvarLawXsFbNDeJW7zAz8ItJ9cd28YufuuFzh0G8pNHjJMnY08Dv7sYX2uF5UpQOwieAeOExEYAWWfu7ZZUA==", + "version": "2.0.3", + "resolved": "https://registry.npmjs.org/is-negative-zero/-/is-negative-zero-2.0.3.tgz", + "integrity": "sha512-5KoIu2Ngpyek75jXodFvnafB6DJgr3u8uuK0LEZJjrU19DrMD3EVERaR8sjz8CCGgpZvxPl9SuE1GMVPFHx1mw==", "dev": true, "engines": { "node": ">= 0.4" @@ -18268,12 +17749,15 @@ } }, "node_modules/is-shared-array-buffer": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/is-shared-array-buffer/-/is-shared-array-buffer-1.0.2.tgz", - "integrity": "sha512-sqN2UDu1/0y6uvXyStCOzyhAjCSlHceFoMKJW8W9EU9cvic/QdsZ0kEU93HEy3IUEFZIiH/3w+AH/UQbPHNdhA==", + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/is-shared-array-buffer/-/is-shared-array-buffer-1.0.3.tgz", + "integrity": "sha512-nA2hv5XIhLR3uVzDDfCIknerhx8XUKnstuOERPNNIinXG7v9u+ohXF67vxm4TPTEPU6lm61ZkwP3c9PCB97rhg==", "dev": true, "dependencies": { - "call-bind": "^1.0.2" + "call-bind": "^1.0.7" + }, + "engines": { + "node": ">= 0.4" }, "funding": { "url": "https://github.com/sponsors/ljharb" @@ -18310,12 +17794,12 @@ } }, "node_modules/is-typed-array": { - "version": "1.1.12", - "resolved": "https://registry.npmjs.org/is-typed-array/-/is-typed-array-1.1.12.tgz", - "integrity": "sha512-Z14TF2JNG8Lss5/HMqt0//T9JeHXttXy5pH/DBU4vi98ozO2btxzq9MwYDZYnKwU8nRsz/+GVFVRDq3DkVuSPg==", + "version": "1.1.13", + "resolved": "https://registry.npmjs.org/is-typed-array/-/is-typed-array-1.1.13.tgz", + "integrity": "sha512-uZ25/bUAlUY5fR4OKT4rZQEBrzQWYV9ZJYGGsUmEJ6thodVJ1HX64ePQ6Z0qPWP+m+Uq6e9UugrE38jeYsDSMw==", "dev": true, "dependencies": { - "which-typed-array": "^1.1.11" + "which-typed-array": "^1.1.14" }, "engines": { "node": ">= 0.4" @@ -18396,16 +17880,6 @@ "integrity": "sha512-Yljz7ffyPbrLpLngrMtZ7NduUgVvi6wG9RJ9IUcyCd59YQ911PBJphODUcbOVbqYfxe1wuYf/LJ8PauMRwsM/g==", "dev": true }, - "node_modules/js-sdsl": { - "version": "4.4.1", - "resolved": "https://registry.npmjs.org/js-sdsl/-/js-sdsl-4.4.1.tgz", - "integrity": "sha512-6Gsx8R0RucyePbWqPssR8DyfuXmLBooYN5cZFZKjHGnQuaf7pEzhtpceagJxVu4LqhYY5EYA7nko3FmeHZ1KbA==", - "dev": true, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/js-sdsl" - } - }, "node_modules/js-sha3": { "version": "0.8.0", "resolved": "https://registry.npmjs.org/js-sha3/-/js-sha3-0.8.0.tgz", @@ -18437,9 +17911,9 @@ "dev": true }, "node_modules/json-buffer": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/json-buffer/-/json-buffer-3.0.0.tgz", - "integrity": "sha512-CuUqjv0FUZIdXkHPI8MezCnFCdaTAacej1TZYulLoAg1h/PhwkdXFN4V/gzY4g+fMBCOV2xF+rp7t2XD2ns/NQ==", + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/json-buffer/-/json-buffer-3.0.1.tgz", + "integrity": "sha512-4bV5BfR2mqfQTJm+V5tPPdf+ZpuhiIvTuAB5g8kcrXOZpTT/QwwVRWBywX1ozr6lEuPdbHxwaJlm9G6mI2sfSQ==", "dev": true }, "node_modules/json-parse-even-better-errors": { @@ -18518,9 +17992,9 @@ } }, "node_modules/keccak": { - "version": "3.0.3", - "resolved": "https://registry.npmjs.org/keccak/-/keccak-3.0.3.tgz", - "integrity": "sha512-JZrLIAJWuZxKbCilMpNz5Vj7Vtb4scDG3dMXLOsbzBmQGyjwE61BbW7bJkfKKCShXiQZt3T6sBgALRtmd+nZaQ==", + "version": "3.0.4", + "resolved": "https://registry.npmjs.org/keccak/-/keccak-3.0.4.tgz", + "integrity": "sha512-3vKuW0jV8J3XNTzvfyicFR5qvxrSAGl7KIhvgOu5cmWwM7tZRj3fMbj/pfIf4be7aznbc+prBWGjywox/g2Y6Q==", "hasInstallScript": true, "dependencies": { "node-addon-api": "^2.0.0", @@ -18542,12 +18016,12 @@ } }, "node_modules/keyv": { - "version": "3.1.0", - "resolved": "https://registry.npmjs.org/keyv/-/keyv-3.1.0.tgz", - "integrity": "sha512-9ykJ/46SN/9KPM/sichzQ7OvXyGDYKGTaDlKMGCAlg2UK8KRy4jb0d8sFc+0Tt0YYnThq8X2RZgCg74RPxgcVA==", + "version": "4.5.4", + "resolved": "https://registry.npmjs.org/keyv/-/keyv-4.5.4.tgz", + "integrity": "sha512-oxVHkHR/EJf2CNXnWxRLW6mg7JyCCUcG0DtEGmL2ctUo1PNTin1PUil+r/+4r5MpVgC/fn1kjsx7mjSujKqIpw==", "dev": true, "dependencies": { - "json-buffer": "3.0.0" + "json-buffer": "3.0.1" } }, "node_modules/kind-of": { @@ -18589,45 +18063,6 @@ "node": ">=0.10.0" } }, - "node_modules/level": { - "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" - }, - "engines": { - "node": ">=12" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/level" - } - }, - "node_modules/level-supports": { - "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" - } - }, - "node_modules/level-transcoder": { - "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" - }, - "engines": { - "node": ">=12" - } - }, "node_modules/levn": { "version": "0.4.1", "resolved": "https://registry.npmjs.org/levn/-/levn-0.4.1.tgz", @@ -18749,11 +18184,11 @@ } }, "node_modules/loupe": { - "version": "2.3.6", - "resolved": "https://registry.npmjs.org/loupe/-/loupe-2.3.6.tgz", - "integrity": "sha512-RaPMZKiMy8/JruncMU5Bt6na1eftNoo++R4Y+N2FrxkDVTrGvcyzFTsaGif4QTeKESheMGegbhw6iUAq+5A8zA==", + "version": "2.3.7", + "resolved": "https://registry.npmjs.org/loupe/-/loupe-2.3.7.tgz", + "integrity": "sha512-zSMINGVYkdpYSOBmLi0D1Uo7JU9nVdQKrHxC8eYlV+9YKK9WePqAlL7lSlorG/U2Fw1w0hTBmaa/jrQ3UbPHtA==", "dependencies": { - "get-func-name": "^2.0.0" + "get-func-name": "^2.0.1" } }, "node_modules/lowercase-keys": { @@ -18772,12 +18207,14 @@ "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, + "version": "6.0.0", + "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-6.0.0.tgz", + "integrity": "sha512-Jo6dJ04CmSjuznwJSS3pUeWmd/H0ffTlkXXgwZi+eq1UCmqQwCh+eLsYOYCwY991i2Fah4h1BEMCx4qThGbsiA==", "dependencies": { - "yallist": "^3.0.2" + "yallist": "^4.0.0" + }, + "engines": { + "node": ">=10" } }, "node_modules/make-error": { @@ -18792,19 +18229,11 @@ "integrity": "sha512-1RUZVgQlpJSPWYbFSpmudq5nHY1doEIv89gBtF0s4gW1GF2XorxcA/70M5vq7rLv0a6mhOUccRsqkwhwLCIQ2Q==", "dev": true }, - "node_modules/mcl-wasm": { - "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" - } - }, "node_modules/md5.js": { "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", @@ -18820,20 +18249,6 @@ "node": ">= 0.6" } }, - "node_modules/memory-level": { - "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", - "module-error": "^1.0.1" - }, - "engines": { - "node": ">=12" - } - }, "node_modules/memorystream": { "version": "0.3.1", "resolved": "https://registry.npmjs.org/memorystream/-/memorystream-0.3.1.tgz", @@ -18882,6 +18297,11 @@ "node": ">= 0.6" } }, + "node_modules/micro-ftch": { + "version": "0.3.1", + "resolved": "https://registry.npmjs.org/micro-ftch/-/micro-ftch-0.3.1.tgz", + "integrity": "sha512-/0LLxhzP0tfiR5hcQebtudP56gUurs2CLkGarnCiB/OqEyUFQ6U3paQi/tgLv0hBJYt2rnr9MNpxz4fiiugstg==" + }, "node_modules/micromatch": { "version": "4.0.5", "resolved": "https://registry.npmjs.org/micromatch/-/micromatch-4.0.5.tgz", @@ -19006,6 +18426,12 @@ "yallist": "^3.0.0" } }, + "node_modules/minipass/node_modules/yallist": { + "version": "3.1.1", + "resolved": "https://registry.npmjs.org/yallist/-/yallist-3.1.1.tgz", + "integrity": "sha512-a4UGQaWPH59mOXUYnAG2ewncQS4i4F43Tv3JoAM+s2VDAmS9NsK8GpDMLrCHPksFT7h3K6TOoUNn2pb7RoXx4g==", + "dev": true + }, "node_modules/minizlib": { "version": "1.3.3", "resolved": "https://registry.npmjs.org/minizlib/-/minizlib-1.3.3.tgz", @@ -19050,9 +18476,9 @@ } }, "node_modules/mocha": { - "version": "10.2.0", - "resolved": "https://registry.npmjs.org/mocha/-/mocha-10.2.0.tgz", - "integrity": "sha512-IDY7fl/BecMwFHzoqF2sg/SHHANeBoMMXFlS9r0OXKDssYE1M5O43wUY/9BVPeIvfH2zmEbBfseqN9gBQZzXkg==", + "version": "10.4.0", + "resolved": "https://registry.npmjs.org/mocha/-/mocha-10.4.0.tgz", + "integrity": "sha512-eqhGB8JKapEYcC4ytX/xrzKforgEc3j1pGlAXVy3eRwrtAy5/nIfT1SvgGzfN0XZZxeLq0aQWkOUAmqIJiv+bA==", "dev": true, "dependencies": { "ansi-colors": "4.1.1", @@ -19062,13 +18488,12 @@ "diff": "5.0.0", "escape-string-regexp": "4.0.0", "find-up": "5.0.0", - "glob": "7.2.0", + "glob": "8.1.0", "he": "1.2.0", "js-yaml": "4.1.0", "log-symbols": "4.1.0", "minimatch": "5.0.1", "ms": "2.1.3", - "nanoid": "3.3.3", "serialize-javascript": "6.0.0", "strip-json-comments": "3.1.1", "supports-color": "8.1.1", @@ -19083,10 +18508,6 @@ }, "engines": { "node": ">= 14.0.0" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/mochajs" } }, "node_modules/mocha/node_modules/ansi-colors": { @@ -19113,6 +18534,33 @@ "balanced-match": "^1.0.0" } }, + "node_modules/mocha/node_modules/chokidar": { + "version": "3.5.3", + "resolved": "https://registry.npmjs.org/chokidar/-/chokidar-3.5.3.tgz", + "integrity": "sha512-Dr3sfKRP6oTcjf2JmUmFJfeVMvXBdegxB0iVQ5eb2V10uFJUCAS8OByZdVAyVb8xXNz3GjjTgj9kLWsZTqE6kw==", + "dev": true, + "funding": [ + { + "type": "individual", + "url": "https://paulmillr.com/funding/" + } + ], + "dependencies": { + "anymatch": "~3.1.2", + "braces": "~3.0.2", + "glob-parent": "~5.1.2", + "is-binary-path": "~2.1.0", + "is-glob": "~4.0.1", + "normalize-path": "~3.0.0", + "readdirp": "~3.6.0" + }, + "engines": { + "node": ">= 8.10.0" + }, + "optionalDependencies": { + "fsevents": "~2.3.2" + } + }, "node_modules/mocha/node_modules/find-up": { "version": "5.0.0", "resolved": "https://registry.npmjs.org/find-up/-/find-up-5.0.0.tgz", @@ -19123,10 +18571,29 @@ "path-exists": "^4.0.0" }, "engines": { - "node": ">=10" + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/mocha/node_modules/glob": { + "version": "8.1.0", + "resolved": "https://registry.npmjs.org/glob/-/glob-8.1.0.tgz", + "integrity": "sha512-r8hpEjiQEYlF2QU0df3dS+nxxSIreXQS1qRhMJM0Q5NDdR386C7jb7Hwwod8Fgiuex+k0GFjgft18yvxm5XoCQ==", + "dev": true, + "dependencies": { + "fs.realpath": "^1.0.0", + "inflight": "^1.0.4", + "inherits": "2", + "minimatch": "^5.0.1", + "once": "^1.3.0" + }, + "engines": { + "node": ">=12" }, "funding": { - "url": "https://github.com/sponsors/sindresorhus" + "url": "https://github.com/sponsors/isaacs" } }, "node_modules/mocha/node_modules/js-yaml": { @@ -19234,15 +18701,6 @@ "integrity": "sha512-qYvlv/exQ4+svI3UOvPUpLDF0OMX5euvUH0Ny4N5QyRyhNdgAgUrVH3iUINSzEPLvx0kbo/Bp28GJKIqvE7URw==", "dev": true }, - "node_modules/module-error": { - "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" - } - }, "node_modules/ms": { "version": "2.1.2", "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.2.tgz", @@ -19346,24 +18804,6 @@ "integrity": "sha512-9MqxMH/BSJC7dnLsEMPyfN5Dvoo49IsPFYMcHw3Bcfc2kN0lpHRBSzlMSVx4HGyJ7s9B31CyBTVehWJoQ8Ctew==", "dev": true }, - "node_modules/nanoid": { - "version": "3.3.3", - "resolved": "https://registry.npmjs.org/nanoid/-/nanoid-3.3.3.tgz", - "integrity": "sha512-p1sjXuopFs0xg+fPASzQ28agW1oHD7xDsd9Xkf3T15H3c/cifrFHVwrh74PdoklAPi+i7MdRsE47vm2r6JoB+w==", - "dev": true, - "bin": { - "nanoid": "bin/nanoid.cjs" - }, - "engines": { - "node": "^10 || ^12 || ^13.7 || ^14 || >=15.0.1" - } - }, - "node_modules/napi-macros": { - "version": "2.2.2", - "resolved": "https://registry.npmjs.org/napi-macros/-/napi-macros-2.2.2.tgz", - "integrity": "sha512-hmEVtAGYzVQpCKdbQea4skABsdXW4RUh5t5mJ2zzqowJS2OyXZTU1KhDVFhx+NlWZ4ap9mqR9TcDO3LTTttd+g==", - "dev": true - }, "node_modules/natural-compare": { "version": "1.4.0", "resolved": "https://registry.npmjs.org/natural-compare/-/natural-compare-1.4.0.tgz", @@ -19411,29 +18851,10 @@ "lodash": "^4.17.21" } }, - "node_modules/node-environment-flags": { - "version": "1.0.6", - "resolved": "https://registry.npmjs.org/node-environment-flags/-/node-environment-flags-1.0.6.tgz", - "integrity": "sha512-5Evy2epuL+6TM0lCQGpFIj6KwiEsGh1SrHUhTbNX+sLbBtjidPZFAnVK9y5yU1+h//RitLbRHTIMyxQPtxMdHw==", - "dev": true, - "dependencies": { - "object.getownpropertydescriptors": "^2.0.3", - "semver": "^5.7.0" - } - }, - "node_modules/node-environment-flags/node_modules/semver": { - "version": "5.7.2", - "resolved": "https://registry.npmjs.org/semver/-/semver-5.7.2.tgz", - "integrity": "sha512-cBznnQ9KjJqU67B52RMC65CMarK2600WFnbkcaiwWq3xy/5haFJlshgnpjovMVJ+Hff49d8GEn0b87C5pDQ10g==", - "dev": true, - "bin": { - "semver": "bin/semver" - } - }, "node_modules/node-fetch": { - "version": "2.6.12", - "resolved": "https://registry.npmjs.org/node-fetch/-/node-fetch-2.6.12.tgz", - "integrity": "sha512-C/fGU2E8ToujUivIO0H+tpQ6HWo4eEmchoPIoXtxCrVghxdKq+QOHqEZW7tuP3KlV3bC8FRMO5nMCC7Zm1VP6g==", + "version": "2.7.0", + "resolved": "https://registry.npmjs.org/node-fetch/-/node-fetch-2.7.0.tgz", + "integrity": "sha512-c4FRfUm/dbcWZ7U+1Wq0AwCyFL+3nt2bEw05wfxSz+DWpWsitgmSgYmy2dQdWyKC1694ELPqMs/YzUSNozLt8A==", "dependencies": { "whatwg-url": "^5.0.0" }, @@ -19450,9 +18871,9 @@ } }, "node_modules/node-gyp-build": { - "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==", + "version": "4.8.0", + "resolved": "https://registry.npmjs.org/node-gyp-build/-/node-gyp-build-4.8.0.tgz", + "integrity": "sha512-u6fs2AEUljNho3EYTJNBfImO5QTo/J/1Etd+NVdCj7qWKUSN/bSLkZwhDv7I+w/MSC6qJ4cknepkAYykDdK8og==", "bin": { "node-gyp-build": "bin.js", "node-gyp-build-optional": "optional.js", @@ -19565,9 +18986,9 @@ } }, "node_modules/object-inspect": { - "version": "1.12.3", - "resolved": "https://registry.npmjs.org/object-inspect/-/object-inspect-1.12.3.tgz", - "integrity": "sha512-geUvdk7c+eizMNUDkRpW1wJwgfOiOeHbxBR/hLXK1aT6zmVSO0jsQcs7fj6MGw89jC/cjGfLcNOrtMYtGqm81g==", + "version": "1.13.1", + "resolved": "https://registry.npmjs.org/object-inspect/-/object-inspect-1.13.1.tgz", + "integrity": "sha512-5qoj1RUiKOMsCCNLV1CBiPYE10sziTsnmNxkAI/rZhiD63CF7IqdFGC/XzjWjpSgLf0LxXX3bDFIh0E18f6UhQ==", "dev": true, "funding": { "url": "https://github.com/sponsors/ljharb" @@ -19583,13 +19004,13 @@ } }, "node_modules/object.assign": { - "version": "4.1.4", - "resolved": "https://registry.npmjs.org/object.assign/-/object.assign-4.1.4.tgz", - "integrity": "sha512-1mxKf0e58bvyjSCtKYY4sRe9itRk3PJpquJOjeIkz885CczcI4IvJJDLPS72oowuSh+pBxUFROpX+TU++hxhZQ==", + "version": "4.1.5", + "resolved": "https://registry.npmjs.org/object.assign/-/object.assign-4.1.5.tgz", + "integrity": "sha512-byy+U7gp+FVwmyzKPYhW2h5l3crpmGsxl7X2s8y43IgxvG4g3QZ6CffDtsNQy1WsmZpQbO+ybo0AlW7TY6DcBQ==", "dev": true, "dependencies": { - "call-bind": "^1.0.2", - "define-properties": "^1.1.4", + "call-bind": "^1.0.5", + "define-properties": "^1.2.1", "has-symbols": "^1.0.3", "object-keys": "^1.1.1" }, @@ -19600,34 +19021,47 @@ "url": "https://github.com/sponsors/ljharb" } }, - "node_modules/object.getownpropertydescriptors": { - "version": "2.1.6", - "resolved": "https://registry.npmjs.org/object.getownpropertydescriptors/-/object.getownpropertydescriptors-2.1.6.tgz", - "integrity": "sha512-lq+61g26E/BgHv0ZTFgRvi7NMEPuAxLkFU7rukXjc/AlwH4Am5xXVnIXy3un1bg/JPbXHrixRkK1itUzzPiIjQ==", + "node_modules/object.fromentries": { + "version": "2.0.8", + "resolved": "https://registry.npmjs.org/object.fromentries/-/object.fromentries-2.0.8.tgz", + "integrity": "sha512-k6E21FzySsSK5a21KRADBd/NGneRegFO5pLHfdQLpRDETUNJueLXs3WCzyQ3tFRDYgbq3KHGXfTbi2bs8WQ6rQ==", "dev": true, "dependencies": { - "array.prototype.reduce": "^1.0.5", - "call-bind": "^1.0.2", - "define-properties": "^1.2.0", - "es-abstract": "^1.21.2", - "safe-array-concat": "^1.0.0" + "call-bind": "^1.0.7", + "define-properties": "^1.2.1", + "es-abstract": "^1.23.2", + "es-object-atoms": "^1.0.0" }, "engines": { - "node": ">= 0.8" + "node": ">= 0.4" }, "funding": { "url": "https://github.com/sponsors/ljharb" } }, + "node_modules/object.groupby": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/object.groupby/-/object.groupby-1.0.3.tgz", + "integrity": "sha512-+Lhy3TQTuzXI5hevh8sBGqbmurHbbIjAi0Z4S63nthVLmLxfbj4T54a4CfZrXIrt9iP4mVAPYMo/v99taj3wjQ==", + "dev": true, + "dependencies": { + "call-bind": "^1.0.7", + "define-properties": "^1.2.1", + "es-abstract": "^1.23.2" + }, + "engines": { + "node": ">= 0.4" + } + }, "node_modules/object.values": { - "version": "1.1.6", - "resolved": "https://registry.npmjs.org/object.values/-/object.values-1.1.6.tgz", - "integrity": "sha512-FVVTkD1vENCsAcwNs9k6jea2uHC/X0+JcjG8YA60FN5CMaJmG95wT9jek/xX9nornqGRrBkKtzuAu2wuHpKqvw==", + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/object.values/-/object.values-1.2.0.tgz", + "integrity": "sha512-yBYjY9QX2hnRmZHAjG/f13MzmBzxzYgQhFrke06TTyKY5zSTEqkOeukBzIdVA3j3ulu8Qa3MbVFShV7T2RmGtQ==", "dev": true, "dependencies": { - "call-bind": "^1.0.2", - "define-properties": "^1.1.4", - "es-abstract": "^1.20.4" + "call-bind": "^1.0.7", + "define-properties": "^1.2.1", + "es-object-atoms": "^1.0.0" }, "engines": { "node": ">= 0.4" @@ -19796,16 +19230,33 @@ } }, "node_modules/parse-asn1": { - "version": "5.1.6", - "resolved": "https://registry.npmjs.org/parse-asn1/-/parse-asn1-5.1.6.tgz", - "integrity": "sha512-RnZRo1EPU6JBnra2vGHj0yhp6ebyjBZpmUCLHWiFhxlzvBCCpAuZ7elsBp1PVAbQN0/04VD/19rfzlBSwLstMw==", + "version": "5.1.7", + "resolved": "https://registry.npmjs.org/parse-asn1/-/parse-asn1-5.1.7.tgz", + "integrity": "sha512-CTM5kuWR3sx9IFamcl5ErfPl6ea/N8IYwiJ+vpeB2g+1iknv7zBl5uPwbMbRVznRVbrNY6lGuDoE5b30grmbqg==", "dev": true, "dependencies": { - "asn1.js": "^5.2.0", - "browserify-aes": "^1.0.0", - "evp_bytestokey": "^1.0.0", - "pbkdf2": "^3.0.3", - "safe-buffer": "^5.1.1" + "asn1.js": "^4.10.1", + "browserify-aes": "^1.2.0", + "evp_bytestokey": "^1.0.3", + "hash-base": "~3.0", + "pbkdf2": "^3.1.2", + "safe-buffer": "^5.2.1" + }, + "engines": { + "node": ">= 0.10" + } + }, + "node_modules/parse-asn1/node_modules/hash-base": { + "version": "3.0.4", + "resolved": "https://registry.npmjs.org/hash-base/-/hash-base-3.0.4.tgz", + "integrity": "sha512-EeeoJKjTyt868liAlVmcv2ZsUfGHlE3Q+BICOXcZiwN3osr5Q/zFGYmTJpoIzuaSTAwndFy+GqhEwlU4L3j4Ow==", + "dev": true, + "dependencies": { + "inherits": "^2.0.1", + "safe-buffer": "^5.0.1" + }, + "engines": { + "node": ">=4" } }, "node_modules/parse-cache-control": { @@ -19980,9 +19431,9 @@ } }, "node_modules/patch-package/node_modules/universalify": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/universalify/-/universalify-2.0.0.tgz", - "integrity": "sha512-hAZsKq7Yy11Zu1DE0OzWjw7nnLZmJZYTDZZyEFHZdUhV8FkH5MCfoU1XMaxXovpyW5nq5scPqq0ZDP9Zyl04oQ==", + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/universalify/-/universalify-2.0.1.tgz", + "integrity": "sha512-gptHNQghINnc/vTGIk0SOFGFNXw7JVrlRUtConJRlvaw6DuX0wO5Jeko9sWrMBhh+PsYAZ7oXAiOnf/UKogyiw==", "dev": true, "engines": { "node": ">= 10.0.0" @@ -20066,6 +19517,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", @@ -20083,6 +19535,12 @@ "integrity": "sha512-7EAHlyLHI56VEIdK57uwHdHKIaAGbnXPiw0yWbarQZOKaKpvUIgW0jWRVLiatnM+XXlSwsanIBH/hzGMJulMow==", "dev": true }, + "node_modules/picocolors": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/picocolors/-/picocolors-1.0.0.tgz", + "integrity": "sha512-1fygroTLlHu66zi26VoTDv8yRgm0Fccecssto+MhsZ0D/DGW2sm8E8AjW7NU5VVTRt5GxbeZ5qBuJr+HyLYkjQ==", + "dev": true + }, "node_modules/picomatch": { "version": "2.3.1", "resolved": "https://registry.npmjs.org/picomatch/-/picomatch-2.3.1.tgz", @@ -20134,6 +19592,15 @@ "node": ">=4" } }, + "node_modules/possible-typed-array-names": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/possible-typed-array-names/-/possible-typed-array-names-1.0.0.tgz", + "integrity": "sha512-d7Uw+eZoloe0EHDIYoe+bQ5WXnGMOpmiZFTuMWCwpjzzkL2nTjcKiAk4hh8TjnGye2TwWOk3UXucZ+3rbmBa8Q==", + "dev": true, + "engines": { + "node": ">= 0.4" + } + }, "node_modules/postinstall-postinstall": { "version": "2.1.0", "resolved": "https://registry.npmjs.org/postinstall-postinstall/-/postinstall-postinstall-2.1.0.tgz", @@ -20187,62 +19654,26 @@ } }, "node_modules/prettier-plugin-solidity": { - "version": "1.1.3", - "resolved": "https://registry.npmjs.org/prettier-plugin-solidity/-/prettier-plugin-solidity-1.1.3.tgz", - "integrity": "sha512-fQ9yucPi2sBbA2U2Xjh6m4isUTJ7S7QLc/XDDsktqqxYfTwdYKJ0EnnywXHwCGAaYbQNK+HIYPL1OemxuMsgeg==", + "version": "1.3.1", + "resolved": "https://registry.npmjs.org/prettier-plugin-solidity/-/prettier-plugin-solidity-1.3.1.tgz", + "integrity": "sha512-MN4OP5I2gHAzHZG1wcuJl0FsLS3c4Cc5494bbg+6oQWBPuEamjwDvmGfFMZ6NFzsh3Efd9UUxeT7ImgjNH4ozA==", "dev": true, "dependencies": { - "@solidity-parser/parser": "^0.16.0", - "semver": "^7.3.8", - "solidity-comments-extractor": "^0.0.7" + "@solidity-parser/parser": "^0.17.0", + "semver": "^7.5.4", + "solidity-comments-extractor": "^0.0.8" }, "engines": { - "node": ">=12" + "node": ">=16" }, "peerDependencies": { - "prettier": ">=2.3.0 || >=3.0.0-alpha.0" + "prettier": ">=2.3.0" } }, "node_modules/prettier-plugin-solidity/node_modules/@solidity-parser/parser": { - "version": "0.16.1", - "resolved": "https://registry.npmjs.org/@solidity-parser/parser/-/parser-0.16.1.tgz", - "integrity": "sha512-PdhRFNhbTtu3x8Axm0uYpqOy/lODYQK+MlYSgqIsq2L8SFYEHJPHNUiOTAJbDGzNjjr1/n9AcIayxafR/fWmYw==", - "dev": true, - "dependencies": { - "antlr4ts": "^0.5.0-alpha.4" - } - }, - "node_modules/prettier-plugin-solidity/node_modules/lru-cache": { - "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" - }, - "engines": { - "node": ">=10" - } - }, - "node_modules/prettier-plugin-solidity/node_modules/semver": { - "version": "7.5.4", - "resolved": "https://registry.npmjs.org/semver/-/semver-7.5.4.tgz", - "integrity": "sha512-1bCSESV6Pv+i21Hvpxp3Dx+pSD8lIPt8uVjRrxAUt/nbswYc+tK6Y2btiULjd4+fnq15PX+nqQDC7Oft7WkwcA==", - "dev": true, - "dependencies": { - "lru-cache": "^6.0.0" - }, - "bin": { - "semver": "bin/semver.js" - }, - "engines": { - "node": ">=10" - } - }, - "node_modules/prettier-plugin-solidity/node_modules/yallist": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/yallist/-/yallist-4.0.0.tgz", - "integrity": "sha512-3wdGidZyq5PB084XLES5TpOSRA3wjXAlIWMhum2kRcv/41Sn2emQ0dycQW4uZXLejwKvg6EsvbdlVL+FYEct7A==", + "version": "0.17.0", + "resolved": "https://registry.npmjs.org/@solidity-parser/parser/-/parser-0.17.0.tgz", + "integrity": "sha512-Nko8R0/kUo391jsEHHxrGM07QFdnPGvlmox4rmH0kNiNAashItAilhy4Mv4pK5gQmW5f4sXAF58fwJbmlkGcVw==", "dev": true }, "node_modules/process": { @@ -20291,6 +19722,12 @@ "node": ">= 0.10" } }, + "node_modules/proxy-from-env": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/proxy-from-env/-/proxy-from-env-1.1.0.tgz", + "integrity": "sha512-D+zkORCbA9f1tdWRK0RaCR3GPv50cMxcrz4X8k5LTSUD1Dkw47mKJEZQNunItRTkWwgtaUSo1RVFRIG9ZXiFYg==", + "dev": true + }, "node_modules/psl": { "version": "1.9.0", "resolved": "https://registry.npmjs.org/psl/-/psl-1.9.0.tgz", @@ -20558,14 +19995,15 @@ } }, "node_modules/regexp.prototype.flags": { - "version": "1.5.0", - "resolved": "https://registry.npmjs.org/regexp.prototype.flags/-/regexp.prototype.flags-1.5.0.tgz", - "integrity": "sha512-0SutC3pNudRKgquxGoRGIz946MZVHqbNfPjBdxeOhBrdgDKlRoXmYLQN9xRbrR09ZXWeGAdPuif7egofn6v5LA==", + "version": "1.5.2", + "resolved": "https://registry.npmjs.org/regexp.prototype.flags/-/regexp.prototype.flags-1.5.2.tgz", + "integrity": "sha512-NcDiDkTLuPR+++OCKB0nWafEmhg/Da8aUPLPMQbK+bxKKCm1/S5he+AqYa4PlMCVBalb4/yxIRub6qkEx5yJbw==", "dev": true, "dependencies": { - "call-bind": "^1.0.2", - "define-properties": "^1.2.0", - "functions-have-names": "^1.2.3" + "call-bind": "^1.0.6", + "define-properties": "^1.2.1", + "es-errors": "^1.3.0", + "set-function-name": "^2.0.1" }, "engines": { "node": ">= 0.4" @@ -20651,39 +20089,6 @@ "node": ">= 6" } }, - "node_modules/request-promise-core": { - "version": "1.1.4", - "resolved": "https://registry.npmjs.org/request-promise-core/-/request-promise-core-1.1.4.tgz", - "integrity": "sha512-TTbAfBBRdWD7aNNOoVOBH4pN/KigV6LyapYNNlAPA8JwbovRti1E88m3sYAwsLi5ryhPKsE9APwnjFTgdUjTpw==", - "dev": true, - "dependencies": { - "lodash": "^4.17.19" - }, - "engines": { - "node": ">=0.10.0" - }, - "peerDependencies": { - "request": "^2.34" - } - }, - "node_modules/request-promise-native": { - "version": "1.0.9", - "resolved": "https://registry.npmjs.org/request-promise-native/-/request-promise-native-1.0.9.tgz", - "integrity": "sha512-wcW+sIUiWnKgNY0dqCpOZkUbF/I+YPi+f09JZIDa39Ec+q82CpSYniDp+ISgTTbKmnpJWASeJBPZmoxH84wt3g==", - "deprecated": "request-promise-native has been deprecated because it extends the now deprecated request package, see https://github.com/request/request/issues/3142", - "dev": true, - "dependencies": { - "request-promise-core": "1.1.4", - "stealthy-require": "^1.1.1", - "tough-cookie": "^2.3.3" - }, - "engines": { - "node": ">=0.12.0" - }, - "peerDependencies": { - "request": "^2.34" - } - }, "node_modules/request/node_modules/form-data": { "version": "2.3.3", "resolved": "https://registry.npmjs.org/form-data/-/form-data-2.3.3.tgz", @@ -20733,12 +20138,12 @@ "dev": true }, "node_modules/resolve": { - "version": "1.22.2", - "resolved": "https://registry.npmjs.org/resolve/-/resolve-1.22.2.tgz", - "integrity": "sha512-Sb+mjNHOULsBv818T40qSPeRiuWLyaGMa5ewydRLFimneixmVy2zdivRl+AF6jaYPC8ERxGDmFSiqui6SfPd+g==", + "version": "1.22.8", + "resolved": "https://registry.npmjs.org/resolve/-/resolve-1.22.8.tgz", + "integrity": "sha512-oKWePCxqpd6FlLvGV1VU0x7bkPmmCNolxzjMf4NczoDnQcIWrAF+cPtZn5i6n+RfD2d9i0tzpKnG6Yk168yIyw==", "dev": true, "dependencies": { - "is-core-module": "^2.11.0", + "is-core-module": "^2.13.0", "path-parse": "^1.0.7", "supports-preserve-symlinks-flag": "^1.0.0" }, @@ -20802,6 +20207,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" @@ -20841,43 +20247,14 @@ "queue-microtask": "^1.2.2" } }, - "node_modules/run-parallel-limit": { - "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", - "url": "https://github.com/sponsors/feross" - }, - { - "type": "patreon", - "url": "https://www.patreon.com/feross" - }, - { - "type": "consulting", - "url": "https://feross.org/support" - } - ], - "dependencies": { - "queue-microtask": "^1.2.2" - } - }, - "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==", - "dev": true - }, "node_modules/safe-array-concat": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/safe-array-concat/-/safe-array-concat-1.0.0.tgz", - "integrity": "sha512-9dVEFruWIsnie89yym+xWTAYASdpw3CJV7Li/6zBewGf9z2i1j31rP6jnY0pHEO4QZh6N0K11bFjWmdR8UGdPQ==", + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/safe-array-concat/-/safe-array-concat-1.1.2.tgz", + "integrity": "sha512-vj6RsCsWBCf19jIeHEfkRMw8DPiBb+DMXklQ/1SGDHOMlHdPUkZXFQ2YdplS23zESTijAcurb1aSgJA3AgMu1Q==", "dev": true, "dependencies": { - "call-bind": "^1.0.2", - "get-intrinsic": "^1.2.0", + "call-bind": "^1.0.7", + "get-intrinsic": "^1.2.4", "has-symbols": "^1.0.3", "isarray": "^2.0.5" }, @@ -20889,20 +20266,37 @@ } }, "node_modules/safe-buffer": { - "version": "5.1.2", - "resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.1.2.tgz", - "integrity": "sha512-Gd2UZBJDkXlY7GbJxfsE8/nvKkUEU1G38c1siN6QP6a9PT9MmHB8GnpscSmMJSoF8LOIrt8ud/wPtojys4G6+g==" + "version": "5.2.1", + "resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.2.1.tgz", + "integrity": "sha512-rp3So07KcdmmKbGvgaNxQSJr7bGVSVk5S9Eq1F+ppbRo70+YeaDxkw5Dd8NPN+GD6bjnYm2VuPuCXmpuYvmCXQ==", + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/feross" + }, + { + "type": "patreon", + "url": "https://www.patreon.com/feross" + }, + { + "type": "consulting", + "url": "https://feross.org/support" + } + ] }, "node_modules/safe-regex-test": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/safe-regex-test/-/safe-regex-test-1.0.0.tgz", - "integrity": "sha512-JBUUzyOgEwXQY1NuPtvcj/qcBDbDmEvWufhlnXZIm75DEHp+afM1r1ujJpJsV/gSM4t59tpDyPi1sd6ZaPFfsA==", + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/safe-regex-test/-/safe-regex-test-1.0.3.tgz", + "integrity": "sha512-CdASjNJPvRa7roO6Ra/gLYBTzYzzPyyBXxIMdGW3USQLyjWEls2RgW5UBTXaQVp+OrpeCK3bLem8smtmheoRuw==", "dev": true, "dependencies": { - "call-bind": "^1.0.2", - "get-intrinsic": "^1.1.3", + "call-bind": "^1.0.6", + "es-errors": "^1.3.0", "is-regex": "^1.1.4" }, + "engines": { + "node": ">= 0.4" + }, "funding": { "url": "https://github.com/sponsors/ljharb" } @@ -21015,6 +20409,7 @@ "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", @@ -21026,12 +20421,17 @@ } }, "node_modules/semver": { - "version": "6.3.1", - "resolved": "https://registry.npmjs.org/semver/-/semver-6.3.1.tgz", - "integrity": "sha512-BR7VvDCVHO+q2xBEWskxS6DJE1qRnb7DxzUrogb71CWoSficBxYsiAGd+Kl0mmq/MprG9yArRkyrQxTO6XjMzA==", - "dev": true, + "version": "7.6.0", + "resolved": "https://registry.npmjs.org/semver/-/semver-7.6.0.tgz", + "integrity": "sha512-EnwXhrlwXMk9gKu5/flx5sv/an57AkRplG3hTK68W7FRDN+k+OWBj65M7719OkA82XLBxrcX0KSHj+X5COhOVg==", + "dependencies": { + "lru-cache": "^6.0.0" + }, "bin": { "semver": "bin/semver.js" + }, + "engines": { + "node": ">=10" } }, "node_modules/send": { @@ -21125,10 +20525,43 @@ "integrity": "sha512-KiKBS8AnWGEyLzofFfmvKwpdPzqiy16LvQfK3yv/fVH7Bj13/wl3JSR1J+rfgRE9q7xUJK4qvgS8raSOeLUehw==", "dev": true }, + "node_modules/set-function-length": { + "version": "1.2.2", + "resolved": "https://registry.npmjs.org/set-function-length/-/set-function-length-1.2.2.tgz", + "integrity": "sha512-pgRc4hJ4/sNjWCSS9AmnS40x3bNMDTknHgL5UaMBTMyJnU90EgWh1Rz+MC9eFu4BuN/UwZjKQuY/1v3rM7HMfg==", + "dev": true, + "dependencies": { + "define-data-property": "^1.1.4", + "es-errors": "^1.3.0", + "function-bind": "^1.1.2", + "get-intrinsic": "^1.2.4", + "gopd": "^1.0.1", + "has-property-descriptors": "^1.0.2" + }, + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/set-function-name": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/set-function-name/-/set-function-name-2.0.2.tgz", + "integrity": "sha512-7PGFlmtwsEADb0WYyvCMa1t+yke6daIG4Wirafur5kcf+MhUnPms1UeR0CKQdTZD81yESwMHbtn+TR+dMviakQ==", + "dev": true, + "dependencies": { + "define-data-property": "^1.1.4", + "es-errors": "^1.3.0", + "functions-have-names": "^1.2.3", + "has-property-descriptors": "^1.0.2" + }, + "engines": { + "node": ">= 0.4" + } + }, "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", @@ -21140,6 +20573,7 @@ "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" @@ -21200,14 +20634,18 @@ } }, "node_modules/side-channel": { - "version": "1.0.4", - "resolved": "https://registry.npmjs.org/side-channel/-/side-channel-1.0.4.tgz", - "integrity": "sha512-q5XPytqFEIKHkGdiMIrY10mvLRvnQh42/+GoBlFW3b2LXLE2xxJpZFdm94we0BaoV3RwJyGqg5wS7epxTv0Zvw==", + "version": "1.0.6", + "resolved": "https://registry.npmjs.org/side-channel/-/side-channel-1.0.6.tgz", + "integrity": "sha512-fDW/EZ6Q9RiO8eFG8Hj+7u/oW+XrPTIChwCOM2+th2A6OblDtYYIpve9m+KvI9Z4C9qSEXlaGR6bTEYHReuglA==", "dev": true, "dependencies": { - "call-bind": "^1.0.0", - "get-intrinsic": "^1.0.2", - "object-inspect": "^1.9.0" + "call-bind": "^1.0.7", + "es-errors": "^1.3.0", + "get-intrinsic": "^1.2.4", + "object-inspect": "^1.13.1" + }, + "engines": { + "node": ">= 0.4" }, "funding": { "url": "https://github.com/sponsors/ljharb" @@ -21270,15 +20708,6 @@ "url": "https://github.com/chalk/slice-ansi?sponsor=1" } }, - "node_modules/slice-ansi/node_modules/is-fullwidth-code-point": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/is-fullwidth-code-point/-/is-fullwidth-code-point-3.0.0.tgz", - "integrity": "sha512-zymm5+u+sCsSWyD9qNaejV3DFvhCKclKdizYaJUuHA83RLjb7nSuGnddCHGv0hk+KY7BMAlsWeK4Ueg6EV6XQg==", - "dev": true, - "engines": { - "node": ">=8" - } - }, "node_modules/solc": { "version": "0.6.12", "resolved": "https://registry.npmjs.org/solc/-/solc-0.6.12.tgz", @@ -21345,9 +20774,9 @@ } }, "node_modules/solhint": { - "version": "3.4.1", - "resolved": "https://registry.npmjs.org/solhint/-/solhint-3.4.1.tgz", - "integrity": "sha512-pzZn2RlZhws1XwvLPVSsxfHrwsteFf5eySOhpAytzXwKQYbTCJV6z8EevYDiSVKMpWrvbKpEtJ055CuEmzp4Xg==", + "version": "3.6.2", + "resolved": "https://registry.npmjs.org/solhint/-/solhint-3.6.2.tgz", + "integrity": "sha512-85EeLbmkcPwD+3JR7aEMKsVC9YrRSxd4qkXuMzrlf7+z2Eqdfm1wHWq1ffTuo5aDhoZxp2I9yF3QkxZOxOL7aQ==", "dev": true, "dependencies": { "@solidity-parser/parser": "^0.16.0", @@ -21363,7 +20792,7 @@ "js-yaml": "^4.1.0", "lodash": "^4.17.21", "pluralize": "^8.0.0", - "semver": "^6.3.0", + "semver": "^7.5.2", "strip-ansi": "^6.0.1", "table": "^6.8.1", "text-table": "^0.2.0" @@ -21376,9 +20805,9 @@ } }, "node_modules/solhint/node_modules/@solidity-parser/parser": { - "version": "0.16.1", - "resolved": "https://registry.npmjs.org/@solidity-parser/parser/-/parser-0.16.1.tgz", - "integrity": "sha512-PdhRFNhbTtu3x8Axm0uYpqOy/lODYQK+MlYSgqIsq2L8SFYEHJPHNUiOTAJbDGzNjjr1/n9AcIayxafR/fWmYw==", + "version": "0.16.2", + "resolved": "https://registry.npmjs.org/@solidity-parser/parser/-/parser-0.16.2.tgz", + "integrity": "sha512-PI9NfoA3P8XK2VBkK5oIfRgKDsicwDZfkVq9ZTBCQYGOP1N2owgY2dyLGyU5/J/hQs8KRk55kdmvTLjy3Mu3vg==", "dev": true, "dependencies": { "antlr4ts": "^0.5.0-alpha.4" @@ -21452,9 +20881,9 @@ } }, "node_modules/solidity-comments-extractor": { - "version": "0.0.7", - "resolved": "https://registry.npmjs.org/solidity-comments-extractor/-/solidity-comments-extractor-0.0.7.tgz", - "integrity": "sha512-wciNMLg/Irp8OKGrh3S2tfvZiZ0NEyILfcRCXCD4mp7SgK/i9gzLfhY2hY7VMCQJ3kH9UB9BzNdibIVMchzyYw==", + "version": "0.0.8", + "resolved": "https://registry.npmjs.org/solidity-comments-extractor/-/solidity-comments-extractor-0.0.8.tgz", + "integrity": "sha512-htM7Vn6LhHreR+EglVMd2s+sZhcXAirB1Zlyrv5zBuTxieCvjfnRpd7iZk75m/u6NOlEyQ94C6TWbBn2cY7w8g==", "dev": true }, "node_modules/solidity-coverage": { @@ -21578,33 +21007,6 @@ "node": ">=4" } }, - "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", - "integrity": "sha512-Jo6dJ04CmSjuznwJSS3pUeWmd/H0ffTlkXXgwZi+eq1UCmqQwCh+eLsYOYCwY991i2Fah4h1BEMCx4qThGbsiA==", - "dev": true, - "dependencies": { - "yallist": "^4.0.0" - }, - "engines": { - "node": ">=10" - } - }, - "node_modules/solidity-coverage/node_modules/semver": { - "version": "7.5.4", - "resolved": "https://registry.npmjs.org/semver/-/semver-7.5.4.tgz", - "integrity": "sha512-1bCSESV6Pv+i21Hvpxp3Dx+pSD8lIPt8uVjRrxAUt/nbswYc+tK6Y2btiULjd4+fnq15PX+nqQDC7Oft7WkwcA==", - "dev": true, - "dependencies": { - "lru-cache": "^6.0.0" - }, - "bin": { - "semver": "bin/semver.js" - }, - "engines": { - "node": ">=10" - } - }, "node_modules/solidity-coverage/node_modules/supports-color": { "version": "5.5.0", "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-5.5.0.tgz", @@ -21617,12 +21019,6 @@ "node": ">=4" } }, - "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/source-map": { "version": "0.2.0", "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.2.0.tgz", @@ -21666,9 +21062,9 @@ } }, "node_modules/spdx-exceptions": { - "version": "2.3.0", - "resolved": "https://registry.npmjs.org/spdx-exceptions/-/spdx-exceptions-2.3.0.tgz", - "integrity": "sha512-/tTrYOC7PPI1nUAgx34hUpqXuyJG+DTHJTnIULG4rDygi4xu/tfgmq1e1cIRwRzwZgo4NLySi+ricLkZkw4i5A==", + "version": "2.5.0", + "resolved": "https://registry.npmjs.org/spdx-exceptions/-/spdx-exceptions-2.5.0.tgz", + "integrity": "sha512-PiU42r+xO4UbUS1buo3LPJkjlO7430Xn5SVAhdpzzsPHsjbYVflnnFdATgabnLude+Cqu25p6N+g2lw/PFsa4w==", "dev": true }, "node_modules/spdx-expression-parse": { @@ -21682,9 +21078,9 @@ } }, "node_modules/spdx-license-ids": { - "version": "3.0.13", - "resolved": "https://registry.npmjs.org/spdx-license-ids/-/spdx-license-ids-3.0.13.tgz", - "integrity": "sha512-XkD+zwiqXHikFZm4AX/7JSCXA98U5Db4AFd5XUg/+9UNtnH75+Z9KxtpYiJZx36mUDVOwH83pl7yvCer6ewM3w==", + "version": "3.0.17", + "resolved": "https://registry.npmjs.org/spdx-license-ids/-/spdx-license-ids-3.0.17.tgz", + "integrity": "sha512-sh8PWc/ftMqAAdFiBu6Fy6JUOYjqDJBJvIhpfDMyHrr0Rbp5liZqd4TjtQ/RgfLjKFZb+LMx5hpml5qOWy0qvg==", "dev": true }, "node_modules/sprintf-js": { @@ -21694,9 +21090,9 @@ "dev": true }, "node_modules/sshpk": { - "version": "1.17.0", - "resolved": "https://registry.npmjs.org/sshpk/-/sshpk-1.17.0.tgz", - "integrity": "sha512-/9HIEs1ZXGhSPE8X6Ccm7Nam1z8KcoCqPdI7ecm1N33EzAetWahvQWVqLZtaZQ+IDKX4IyA2o0gBzqIMkAagHQ==", + "version": "1.18.0", + "resolved": "https://registry.npmjs.org/sshpk/-/sshpk-1.18.0.tgz", + "integrity": "sha512-2p2KJZTSqQ/I3+HX42EpYOa2l3f8Erv8MWKsy2I9uf4wA7yFIkXRffYdsx86y6z4vHtV8u7g+pPlr8/4ouAxsQ==", "dev": true, "dependencies": { "asn1": "~0.2.3", @@ -21754,15 +21150,6 @@ "node": ">= 0.8" } }, - "node_modules/stealthy-require": { - "version": "1.1.1", - "resolved": "https://registry.npmjs.org/stealthy-require/-/stealthy-require-1.1.1.tgz", - "integrity": "sha512-ZnWpYnYugiOVEY5GkcuJK1io5V8QmNYChG62gSit9pQVGErXtrKuPC55ITaVSukmMta5qpMU7vqLt2Lnni4f/g==", - "dev": true, - "engines": { - "node": ">=0.10.0" - } - }, "node_modules/strict-uri-encode": { "version": "1.1.0", "resolved": "https://registry.npmjs.org/strict-uri-encode/-/strict-uri-encode-1.1.0.tgz", @@ -21780,68 +21167,30 @@ "safe-buffer": "~5.2.0" } }, - "node_modules/string_decoder/node_modules/safe-buffer": { - "version": "5.2.1", - "resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.2.1.tgz", - "integrity": "sha512-rp3So07KcdmmKbGvgaNxQSJr7bGVSVk5S9Eq1F+ppbRo70+YeaDxkw5Dd8NPN+GD6bjnYm2VuPuCXmpuYvmCXQ==", - "funding": [ - { - "type": "github", - "url": "https://github.com/sponsors/feross" - }, - { - "type": "patreon", - "url": "https://www.patreon.com/feross" - }, - { - "type": "consulting", - "url": "https://feross.org/support" - } - ] - }, "node_modules/string-width": { - "version": "2.1.1", - "resolved": "https://registry.npmjs.org/string-width/-/string-width-2.1.1.tgz", - "integrity": "sha512-nOqH59deCq9SRHlxq1Aw85Jnt4w6KvLKqWVik6oA9ZklXLNIOlqg4F2yrT1MVaTjAqvVwdfeZ7w7aCvJD7ugkw==", - "dev": true, - "dependencies": { - "is-fullwidth-code-point": "^2.0.0", - "strip-ansi": "^4.0.0" - }, - "engines": { - "node": ">=4" - } - }, - "node_modules/string-width/node_modules/ansi-regex": { - "version": "3.0.1", - "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-3.0.1.tgz", - "integrity": "sha512-+O9Jct8wf++lXxxFc4hc8LsjaSq0HFzzL7cVsw8pRDIPdjKD2mT4ytDZlLuSBZ4cLKZFXIrMGO7DbQCtMJJMKw==", - "dev": true, - "engines": { - "node": ">=4" - } - }, - "node_modules/string-width/node_modules/strip-ansi": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-4.0.0.tgz", - "integrity": "sha512-4XaJ2zQdCzROZDivEVIDPkcQn8LMFSa8kj8Gxb/Lnwzv9A8VctNZ+lfivC/sV3ivW8ElJTERXZoPBRrZKkNKow==", + "version": "4.2.3", + "resolved": "https://registry.npmjs.org/string-width/-/string-width-4.2.3.tgz", + "integrity": "sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g==", "dev": true, "dependencies": { - "ansi-regex": "^3.0.0" + "emoji-regex": "^8.0.0", + "is-fullwidth-code-point": "^3.0.0", + "strip-ansi": "^6.0.1" }, "engines": { - "node": ">=4" + "node": ">=8" } }, "node_modules/string.prototype.trim": { - "version": "1.2.7", - "resolved": "https://registry.npmjs.org/string.prototype.trim/-/string.prototype.trim-1.2.7.tgz", - "integrity": "sha512-p6TmeT1T3411M8Cgg9wBTMRtY2q9+PNy9EV1i2lIXUN/btt763oIfxwN3RR8VU6wHX8j/1CFy0L+YuThm6bgOg==", + "version": "1.2.9", + "resolved": "https://registry.npmjs.org/string.prototype.trim/-/string.prototype.trim-1.2.9.tgz", + "integrity": "sha512-klHuCNxiMZ8MlsOihJhJEBJAiMVqU3Z2nEXWfWnIqjN0gEFS9J9+IxKozWWtQGcgoa1WUZzLjKPTr4ZHNFTFxw==", "dev": true, "dependencies": { - "call-bind": "^1.0.2", - "define-properties": "^1.1.4", - "es-abstract": "^1.20.4" + "call-bind": "^1.0.7", + "define-properties": "^1.2.1", + "es-abstract": "^1.23.0", + "es-object-atoms": "^1.0.0" }, "engines": { "node": ">= 0.4" @@ -21851,28 +21200,31 @@ } }, "node_modules/string.prototype.trimend": { - "version": "1.0.6", - "resolved": "https://registry.npmjs.org/string.prototype.trimend/-/string.prototype.trimend-1.0.6.tgz", - "integrity": "sha512-JySq+4mrPf9EsDBEDYMOb/lM7XQLulwg5R/m1r0PXEFqrV0qHvl58sdTilSXtKOflCsK2E8jxf+GKC0T07RWwQ==", + "version": "1.0.8", + "resolved": "https://registry.npmjs.org/string.prototype.trimend/-/string.prototype.trimend-1.0.8.tgz", + "integrity": "sha512-p73uL5VCHCO2BZZ6krwwQE3kCzM7NKmis8S//xEC6fQonchbum4eP6kR4DLEjQFO3Wnj3Fuo8NM0kOSjVdHjZQ==", "dev": true, "dependencies": { - "call-bind": "^1.0.2", - "define-properties": "^1.1.4", - "es-abstract": "^1.20.4" + "call-bind": "^1.0.7", + "define-properties": "^1.2.1", + "es-object-atoms": "^1.0.0" }, "funding": { "url": "https://github.com/sponsors/ljharb" } }, "node_modules/string.prototype.trimstart": { - "version": "1.0.6", - "resolved": "https://registry.npmjs.org/string.prototype.trimstart/-/string.prototype.trimstart-1.0.6.tgz", - "integrity": "sha512-omqjMDaY92pbn5HOX7f9IccLA+U1tA9GvtU4JrodiXFfYB7jPzzHpRzpglLAjtUV6bB557zwClJezTqnAiYnQA==", + "version": "1.0.8", + "resolved": "https://registry.npmjs.org/string.prototype.trimstart/-/string.prototype.trimstart-1.0.8.tgz", + "integrity": "sha512-UXSH262CSZY1tfu3G3Secr6uGLCFVPMhIqHjlgCUtCCcgihYc/xKs9djMTMUOb2j1mVSeU8EU6NWc/iQKU6Gfg==", "dev": true, "dependencies": { - "call-bind": "^1.0.2", - "define-properties": "^1.1.4", - "es-abstract": "^1.20.4" + "call-bind": "^1.0.7", + "define-properties": "^1.2.1", + "es-object-atoms": "^1.0.0" + }, + "engines": { + "node": ">= 0.4" }, "funding": { "url": "https://github.com/sponsors/ljharb" @@ -22128,21 +21480,6 @@ "node": ">=10.19.0" } }, - "node_modules/swarm-js/node_modules/json-buffer": { - "version": "3.0.1", - "resolved": "https://registry.npmjs.org/json-buffer/-/json-buffer-3.0.1.tgz", - "integrity": "sha512-4bV5BfR2mqfQTJm+V5tPPdf+ZpuhiIvTuAB5g8kcrXOZpTT/QwwVRWBywX1ozr6lEuPdbHxwaJlm9G6mI2sfSQ==", - "dev": true - }, - "node_modules/swarm-js/node_modules/keyv": { - "version": "4.5.3", - "resolved": "https://registry.npmjs.org/keyv/-/keyv-4.5.3.tgz", - "integrity": "sha512-QCiSav9WaX1PgETJ+SpNnx2PRRapJ/oRSXM4VO5OGYGSjrxbKPVFVhB3l2OCbLCk329N8qyAtsJjSjvVBWzEug==", - "dev": true, - "dependencies": { - "json-buffer": "3.0.1" - } - }, "node_modules/swarm-js/node_modules/lowercase-keys": { "version": "2.0.0", "resolved": "https://registry.npmjs.org/lowercase-keys/-/lowercase-keys-2.0.0.tgz", @@ -22221,9 +21558,9 @@ } }, "node_modules/table": { - "version": "6.8.1", - "resolved": "https://registry.npmjs.org/table/-/table-6.8.1.tgz", - "integrity": "sha512-Y4X9zqrCftUhMeH2EptSSERdVKt/nEdijTOacGD/97EKjhQ/Qs8RTlEGABSJNNN8lac9kheH+af7yAkEWlgneA==", + "version": "6.8.2", + "resolved": "https://registry.npmjs.org/table/-/table-6.8.2.tgz", + "integrity": "sha512-w2sfv80nrAh2VCbqR5AK27wswXhqcck2AhfnNW76beQXskGZ1V12GwS//yYVa3d3fcvAip2OUnbDAjW2k3v9fA==", "dev": true, "dependencies": { "ajv": "^8.0.1", @@ -22252,35 +21589,12 @@ "url": "https://github.com/sponsors/epoberezkin" } }, - "node_modules/table/node_modules/is-fullwidth-code-point": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/is-fullwidth-code-point/-/is-fullwidth-code-point-3.0.0.tgz", - "integrity": "sha512-zymm5+u+sCsSWyD9qNaejV3DFvhCKclKdizYaJUuHA83RLjb7nSuGnddCHGv0hk+KY7BMAlsWeK4Ueg6EV6XQg==", - "dev": true, - "engines": { - "node": ">=8" - } - }, "node_modules/table/node_modules/json-schema-traverse": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/json-schema-traverse/-/json-schema-traverse-1.0.0.tgz", "integrity": "sha512-NM8/P9n3XjXhIZn1lLhkFaACTOURQXjWhV4BA/RnOv8xvgqtqpAX9IO4mRQxSx1Rlo4tqzeqb0sOlruaOy3dug==", "dev": true }, - "node_modules/table/node_modules/string-width": { - "version": "4.2.3", - "resolved": "https://registry.npmjs.org/string-width/-/string-width-4.2.3.tgz", - "integrity": "sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g==", - "dev": true, - "dependencies": { - "emoji-regex": "^8.0.0", - "is-fullwidth-code-point": "^3.0.0", - "strip-ansi": "^6.0.1" - }, - "engines": { - "node": ">=8" - } - }, "node_modules/tar": { "version": "4.4.19", "resolved": "https://registry.npmjs.org/tar/-/tar-4.4.19.tgz", @@ -22299,25 +21613,11 @@ "node": ">=4.5" } }, - "node_modules/tar/node_modules/safe-buffer": { - "version": "5.2.1", - "resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.2.1.tgz", - "integrity": "sha512-rp3So07KcdmmKbGvgaNxQSJr7bGVSVk5S9Eq1F+ppbRo70+YeaDxkw5Dd8NPN+GD6bjnYm2VuPuCXmpuYvmCXQ==", - "dev": true, - "funding": [ - { - "type": "github", - "url": "https://github.com/sponsors/feross" - }, - { - "type": "patreon", - "url": "https://www.patreon.com/feross" - }, - { - "type": "consulting", - "url": "https://feross.org/support" - } - ] + "node_modules/tar/node_modules/yallist": { + "version": "3.1.1", + "resolved": "https://registry.npmjs.org/yallist/-/yallist-3.1.1.tgz", + "integrity": "sha512-a4UGQaWPH59mOXUYnAG2ewncQS4i4F43Tv3JoAM+s2VDAmS9NsK8GpDMLrCHPksFT7h3K6TOoUNn2pb7RoXx4g==", + "dev": true }, "node_modules/test-value": { "version": "2.1.0", @@ -22464,9 +21764,9 @@ } }, "node_modules/tough-cookie/node_modules/punycode": { - "version": "2.3.0", - "resolved": "https://registry.npmjs.org/punycode/-/punycode-2.3.0.tgz", - "integrity": "sha512-rRV+zQD8tVFys26lAGR9WUuS4iUAngJScM+ZRSKtvl5tKeZ2t5bvdNFdNHBW9FWR4guGHlgmsZ1G7BSm2wTbuA==", + "version": "2.3.1", + "resolved": "https://registry.npmjs.org/punycode/-/punycode-2.3.1.tgz", + "integrity": "sha512-vYt7UD1U9Wg6138shLtLOvdAu+8DsC/ilFtEVHcH+wydcSpNE20AfSOduf6MkRFahL5FY7X1oU7nKVZFtfq8Fg==", "dev": true, "engines": { "node": ">=6" @@ -22592,9 +21892,9 @@ "dev": true }, "node_modules/ts-node": { - "version": "10.9.1", - "resolved": "https://registry.npmjs.org/ts-node/-/ts-node-10.9.1.tgz", - "integrity": "sha512-NtVysVPkxxrwFGUUxGYhfux8k78pQB3JqYBXlLRZgdGUqTO5wU/UyHop5p70iEbGhB7q5KmiZiU0Y3KlJrScEw==", + "version": "10.9.2", + "resolved": "https://registry.npmjs.org/ts-node/-/ts-node-10.9.2.tgz", + "integrity": "sha512-f0FFpIdcHgn8zcPSbf1dRevwt047YMnaiJM3u2w2RewrB+fob/zePZcrOyQoLMMO7aBIddLcQIEK5dYjkLnGrQ==", "dev": true, "dependencies": { "@cspotcode/source-map-support": "^0.8.0", @@ -22635,9 +21935,9 @@ } }, "node_modules/ts-node/node_modules/acorn": { - "version": "8.10.0", - "resolved": "https://registry.npmjs.org/acorn/-/acorn-8.10.0.tgz", - "integrity": "sha512-F0SAmZ8iUtS//m8DmCTA0jlh6TDKkHQyK6xc6V4KDTyZKA9dnvX9/3sRTVQrWm79glUAZbnmmNcdYwUIHWVybw==", + "version": "8.11.3", + "resolved": "https://registry.npmjs.org/acorn/-/acorn-8.11.3.tgz", + "integrity": "sha512-Y9rRfJG5jcKOE0CLisYbojUjIrIEE7AGMzA/Sm4BslANhbS+cDMpgBdcPT91oJ7OuJ9hYJBx59RjbhxVnrF8Xg==", "dev": true, "bin": { "acorn": "bin/acorn" @@ -22656,9 +21956,9 @@ } }, "node_modules/tsconfig-paths": { - "version": "3.14.2", - "resolved": "https://registry.npmjs.org/tsconfig-paths/-/tsconfig-paths-3.14.2.tgz", - "integrity": "sha512-o/9iXgCYc5L/JxCHPe3Hvh8Q/2xm5Z+p18PESBU6Ff33695QnCHBEjcytY2q19ua7Mbl/DavtBOLq+oG0RCL+g==", + "version": "3.15.0", + "resolved": "https://registry.npmjs.org/tsconfig-paths/-/tsconfig-paths-3.15.0.tgz", + "integrity": "sha512-2Ac2RgzDe/cn48GvOe3M+o82pEFewD3UPbyoUHHdKasHwJKjds4fLXWf/Ux5kATBKN20oaFGu+jbElp1pos0mg==", "dev": true, "dependencies": { "@types/json5": "^0.0.29", @@ -22719,9 +22019,9 @@ "dev": true }, "node_modules/type": { - "version": "1.2.0", - "resolved": "https://registry.npmjs.org/type/-/type-1.2.0.tgz", - "integrity": "sha512-+5nt5AAniqsCnu2cEQQdpzCAh33kVx8n0VoFidKpB1dVVLAN/F+bgVOqOJqOnEnrhp222clB5p3vUlD+1QAnfg==", + "version": "2.7.2", + "resolved": "https://registry.npmjs.org/type/-/type-2.7.2.tgz", + "integrity": "sha512-dzlvlNlt6AXU7EBSfpAscydQ7gXB+pPGsPnfJnZpiNJBDj7IaJzQlBZYGdEi4R9HmPdBv2XmWJ6YUtoTa7lmCw==", "dev": true }, "node_modules/type-check": { @@ -22806,29 +22106,30 @@ } }, "node_modules/typed-array-buffer": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/typed-array-buffer/-/typed-array-buffer-1.0.0.tgz", - "integrity": "sha512-Y8KTSIglk9OZEr8zywiIHG/kmQ7KWyjseXs1CbSo8vC42w7hg2HgYTxSWwP0+is7bWDc1H+Fo026CpHFwm8tkw==", + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/typed-array-buffer/-/typed-array-buffer-1.0.2.tgz", + "integrity": "sha512-gEymJYKZtKXzzBzM4jqa9w6Q1Jjm7x2d+sh19AdsD4wqnMPDYyvwpsIc2Q/835kHuo3BEQ7CjelGhfTsoBb2MQ==", "dev": true, "dependencies": { - "call-bind": "^1.0.2", - "get-intrinsic": "^1.2.1", - "is-typed-array": "^1.1.10" + "call-bind": "^1.0.7", + "es-errors": "^1.3.0", + "is-typed-array": "^1.1.13" }, "engines": { "node": ">= 0.4" } }, "node_modules/typed-array-byte-length": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/typed-array-byte-length/-/typed-array-byte-length-1.0.0.tgz", - "integrity": "sha512-Or/+kvLxNpeQ9DtSydonMxCx+9ZXOswtwJn17SNLvhptaXYDJvkFFP5zbfU/uLmvnBJlI4yrnXRxpdWH/M5tNA==", + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/typed-array-byte-length/-/typed-array-byte-length-1.0.1.tgz", + "integrity": "sha512-3iMJ9q0ao7WE9tWcaYKIptkNBuOIcZCCT0d4MRvuuH88fEoEH62IuQe0OtraD3ebQEoTRk8XCBoknUNc1Y67pw==", "dev": true, "dependencies": { - "call-bind": "^1.0.2", + "call-bind": "^1.0.7", "for-each": "^0.3.3", - "has-proto": "^1.0.1", - "is-typed-array": "^1.1.10" + "gopd": "^1.0.1", + "has-proto": "^1.0.3", + "is-typed-array": "^1.1.13" }, "engines": { "node": ">= 0.4" @@ -22838,16 +22139,17 @@ } }, "node_modules/typed-array-byte-offset": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/typed-array-byte-offset/-/typed-array-byte-offset-1.0.0.tgz", - "integrity": "sha512-RD97prjEt9EL8YgAgpOkf3O4IF9lhJFr9g0htQkm0rchFp/Vx7LW5Q8fSXXub7BXAODyUQohRMyOc3faCPd0hg==", + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/typed-array-byte-offset/-/typed-array-byte-offset-1.0.2.tgz", + "integrity": "sha512-Ous0vodHa56FviZucS2E63zkgtgrACj7omjwd/8lTEMEPFFyjfixMZ1ZXenpgCFBBt4EC1J2XsyVS2gkG0eTFA==", "dev": true, "dependencies": { - "available-typed-arrays": "^1.0.5", - "call-bind": "^1.0.2", + "available-typed-arrays": "^1.0.7", + "call-bind": "^1.0.7", "for-each": "^0.3.3", - "has-proto": "^1.0.1", - "is-typed-array": "^1.1.10" + "gopd": "^1.0.1", + "has-proto": "^1.0.3", + "is-typed-array": "^1.1.13" }, "engines": { "node": ">= 0.4" @@ -22857,14 +22159,20 @@ } }, "node_modules/typed-array-length": { - "version": "1.0.4", - "resolved": "https://registry.npmjs.org/typed-array-length/-/typed-array-length-1.0.4.tgz", - "integrity": "sha512-KjZypGq+I/H7HI5HlOoGHkWUUGq+Q0TPhQurLbyrVrvnKTBgzLhIJ7j6J/XTQOi0d1RjyZ0wdas8bKs2p0x3Ng==", + "version": "1.0.6", + "resolved": "https://registry.npmjs.org/typed-array-length/-/typed-array-length-1.0.6.tgz", + "integrity": "sha512-/OxDN6OtAk5KBpGb28T+HZc2M+ADtvRxXrKKbUwtsLgdoxgX13hyy7ek6bFRl5+aBs2yZzB0c4CnQfAtVypW/g==", "dev": true, "dependencies": { - "call-bind": "^1.0.2", + "call-bind": "^1.0.7", "for-each": "^0.3.3", - "is-typed-array": "^1.1.9" + "gopd": "^1.0.1", + "has-proto": "^1.0.3", + "is-typed-array": "^1.1.13", + "possible-typed-array-names": "^1.0.0" + }, + "engines": { + "node": ">= 0.4" }, "funding": { "url": "https://github.com/sponsors/ljharb" @@ -22939,9 +22247,9 @@ } }, "node_modules/undici": { - "version": "5.26.3", - "resolved": "https://registry.npmjs.org/undici/-/undici-5.26.3.tgz", - "integrity": "sha512-H7n2zmKEWgOllKkIUkLvFmsJQj062lSm3uA4EYApG8gLuiOM0/go9bIoC3HVaSnfg4xunowDE2i9p8drkXuvDw==", + "version": "5.28.4", + "resolved": "https://registry.npmjs.org/undici/-/undici-5.28.4.tgz", + "integrity": "sha512-72RFADWFqKmUb2hmmvNODKL3p9hcB6Gt2DOQMis1SEBaV6a4MH8soBvzg+95CYhCKPFedut2JY9bMfrDl9D23g==", "dev": true, "dependencies": { "@fastify/busboy": "^2.0.0" @@ -22978,13 +22286,13 @@ } }, "node_modules/url": { - "version": "0.11.1", - "resolved": "https://registry.npmjs.org/url/-/url-0.11.1.tgz", - "integrity": "sha512-rWS3H04/+mzzJkv0eZ7vEDGiQbgquI1fGfOad6zKvgYQi1SzMmhl7c/DdRGxhaWrVH6z0qWITo8rpnxK/RfEhA==", + "version": "0.11.3", + "resolved": "https://registry.npmjs.org/url/-/url-0.11.3.tgz", + "integrity": "sha512-6hxOLGfZASQK/cijlZnZJTq8OXAkt/3YGfQX45vvMYXpZoo8NdWZcY73K108Jf759lS1Bv/8wXnHDTSz17dSRw==", "dev": true, "dependencies": { "punycode": "^1.4.1", - "qs": "^6.11.0" + "qs": "^6.11.2" } }, "node_modules/url-parse-lax": { @@ -23012,12 +22320,12 @@ "dev": true }, "node_modules/url/node_modules/qs": { - "version": "6.11.2", - "resolved": "https://registry.npmjs.org/qs/-/qs-6.11.2.tgz", - "integrity": "sha512-tDNIz22aBzCDxLtVH++VnTfzxlfeK5CbqohpSqpJgj1Wg/cQbStNAz3NuqCs5vV+pjBsK4x4pN9HlVh7rcYRiA==", + "version": "6.12.1", + "resolved": "https://registry.npmjs.org/qs/-/qs-6.12.1.tgz", + "integrity": "sha512-zWmv4RSuB9r2mYQw3zxQuHWeU+42aKi1wWig/j4ele4ygELZ7PEO6MM7rim9oAQH2A5MWfsAVf/jPvTPgCbvUQ==", "dev": true, "dependencies": { - "side-channel": "^1.0.4" + "side-channel": "^1.0.6" }, "engines": { "node": ">=0.6" @@ -23081,9 +22389,9 @@ } }, "node_modules/v8-compile-cache": { - "version": "2.3.0", - "resolved": "https://registry.npmjs.org/v8-compile-cache/-/v8-compile-cache-2.3.0.tgz", - "integrity": "sha512-l8lCEmLcLYZh4nbunNZvQCJc5pv7+RCwa8q/LdUx8u7lsWvPDKmpodJAJNwkAhJC//dFY48KuIEmjtd4RViDrA==", + "version": "2.4.0", + "resolved": "https://registry.npmjs.org/v8-compile-cache/-/v8-compile-cache-2.4.0.tgz", + "integrity": "sha512-ocyWc3bAHBB/guyqJQVI5o4BZkPhznPYUG2ea80Gond/BgNWpap8TOmLSeeQG7bnh2KMISxskdADG59j7zruhw==", "dev": true }, "node_modules/v8-compile-cache-lib": { @@ -23675,13 +22983,14 @@ } }, "node_modules/web3-utils": { - "version": "1.10.0", - "resolved": "https://registry.npmjs.org/web3-utils/-/web3-utils-1.10.0.tgz", - "integrity": "sha512-kSaCM0uMcZTNUSmn5vMEhlo02RObGNRRCkdX0V9UTAU0+lrvn0HSaudyCo6CQzuXUsnuY2ERJGCGPfeWmv19Rg==", + "version": "1.10.4", + "resolved": "https://registry.npmjs.org/web3-utils/-/web3-utils-1.10.4.tgz", + "integrity": "sha512-tsu8FiKJLk2PzhDl9fXbGUWTkkVXYhtTA+SmEFkKft+9BgwLxfCRpU96sWv7ICC8zixBNd3JURVoiR3dUXgP8A==", "dependencies": { + "@ethereumjs/util": "^8.1.0", "bn.js": "^5.2.1", "ethereum-bloom-filters": "^1.0.6", - "ethereumjs-util": "^7.1.0", + "ethereum-cryptography": "^2.1.2", "ethjs-unit": "0.1.6", "number-to-bn": "1.7.0", "randombytes": "^2.1.0", @@ -23691,6 +23000,28 @@ "node": ">=8.0.0" } }, + "node_modules/web3-utils/node_modules/@noble/hashes": { + "version": "1.3.3", + "resolved": "https://registry.npmjs.org/@noble/hashes/-/hashes-1.3.3.tgz", + "integrity": "sha512-V7/fPHgl+jsVPXqqeOzT8egNj2iBIVt+ECeMMG8TdcnTikP3oaBtUVqpT/gYCR68aEBJSF+XbYUxStjbFMqIIA==", + "engines": { + "node": ">= 16" + }, + "funding": { + "url": "https://paulmillr.com/funding/" + } + }, + "node_modules/web3-utils/node_modules/ethereum-cryptography": { + "version": "2.1.3", + "resolved": "https://registry.npmjs.org/ethereum-cryptography/-/ethereum-cryptography-2.1.3.tgz", + "integrity": "sha512-BlwbIL7/P45W8FGW2r7LGuvoEZ+7PWsniMvQ4p5s2xCyw9tmaDlpfsN9HjAucbF+t/qpVHwZUisgfK24TCW8aA==", + "dependencies": { + "@noble/curves": "1.3.0", + "@noble/hashes": "1.3.3", + "@scure/bip32": "1.3.3", + "@scure/bip39": "1.2.2" + } + }, "node_modules/web3/node_modules/web3-utils": { "version": "1.7.4", "resolved": "https://registry.npmjs.org/web3-utils/-/web3-utils-1.7.4.tgz", @@ -23793,16 +23124,16 @@ "dev": true }, "node_modules/which-typed-array": { - "version": "1.1.11", - "resolved": "https://registry.npmjs.org/which-typed-array/-/which-typed-array-1.1.11.tgz", - "integrity": "sha512-qe9UWWpkeG5yzZ0tNYxDmd7vo58HDBc39mZ0xWWpolAGADdFOzkfamWLDxkOWcvHQKVmdTyQdLD4NOfjLWTKew==", + "version": "1.1.15", + "resolved": "https://registry.npmjs.org/which-typed-array/-/which-typed-array-1.1.15.tgz", + "integrity": "sha512-oV0jmFtUky6CXfkqehVvBP/LSWJ2sy4vWMioiENyJLePrBO/yKyV9OyJySfAKosh+RYkIl5zJCNZ8/4JncrpdA==", "dev": true, "dependencies": { - "available-typed-arrays": "^1.0.5", - "call-bind": "^1.0.2", + "available-typed-arrays": "^1.0.7", + "call-bind": "^1.0.7", "for-each": "^0.3.3", "gopd": "^1.0.1", - "has-tostringtag": "^1.0.0" + "has-tostringtag": "^1.0.2" }, "engines": { "node": ">= 0.4" @@ -23811,13 +23142,16 @@ "url": "https://github.com/sponsors/ljharb" } }, - "node_modules/wide-align": { - "version": "1.1.3", - "resolved": "https://registry.npmjs.org/wide-align/-/wide-align-1.1.3.tgz", - "integrity": "sha512-QGkOQc8XL6Bt5PwnsExKBPuMKBxnGxWWW3fU55Xt4feHozMUhdUMaBCk290qpm/wG5u/RSKzwdAC4i51YigihA==", + "node_modules/widest-line": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/widest-line/-/widest-line-3.1.0.tgz", + "integrity": "sha512-NsmoXalsWVDMGupxZ5R08ka9flZjjiLvHVAWYOKtiKM8ujtZWr9cRffak+uSE48+Ob8ObalXpwyeUiyDD6QFgg==", "dev": true, "dependencies": { - "string-width": "^1.0.2 || 2" + "string-width": "^4.0.0" + }, + "engines": { + "node": ">=8" } }, "node_modules/window-size": { @@ -23833,9 +23167,9 @@ } }, "node_modules/word-wrap": { - "version": "1.2.4", - "resolved": "https://registry.npmjs.org/word-wrap/-/word-wrap-1.2.4.tgz", - "integrity": "sha512-2V81OA4ugVo5pRo46hAoD2ivUJx8jXmWXfUkY4KFNw0hEptvN0QfH3K4nHiwzGeKl5rFKedV48QVoqYavy4YpA==", + "version": "1.2.5", + "resolved": "https://registry.npmjs.org/word-wrap/-/word-wrap-1.2.5.tgz", + "integrity": "sha512-BN22B5eaMMI9UMtjrGd5g5eCYPpCPDUy0FJXbYsaT5zYxjFOckS53SQDE3pWkVoWpHXVb3BrYcEN4Twa55B5cA==", "dev": true, "engines": { "node": ">=0.10.0" @@ -23870,29 +23204,6 @@ "url": "https://github.com/chalk/wrap-ansi?sponsor=1" } }, - "node_modules/wrap-ansi/node_modules/is-fullwidth-code-point": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/is-fullwidth-code-point/-/is-fullwidth-code-point-3.0.0.tgz", - "integrity": "sha512-zymm5+u+sCsSWyD9qNaejV3DFvhCKclKdizYaJUuHA83RLjb7nSuGnddCHGv0hk+KY7BMAlsWeK4Ueg6EV6XQg==", - "dev": true, - "engines": { - "node": ">=8" - } - }, - "node_modules/wrap-ansi/node_modules/string-width": { - "version": "4.2.3", - "resolved": "https://registry.npmjs.org/string-width/-/string-width-4.2.3.tgz", - "integrity": "sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g==", - "dev": true, - "dependencies": { - "emoji-regex": "^8.0.0", - "is-fullwidth-code-point": "^3.0.0", - "strip-ansi": "^6.0.1" - }, - "engines": { - "node": ">=8" - } - }, "node_modules/wrappy": { "version": "1.0.2", "resolved": "https://registry.npmjs.org/wrappy/-/wrappy-1.0.2.tgz", @@ -24001,10 +23312,9 @@ } }, "node_modules/yallist": { - "version": "3.1.1", - "resolved": "https://registry.npmjs.org/yallist/-/yallist-3.1.1.tgz", - "integrity": "sha512-a4UGQaWPH59mOXUYnAG2ewncQS4i4F43Tv3JoAM+s2VDAmS9NsK8GpDMLrCHPksFT7h3K6TOoUNn2pb7RoXx4g==", - "dev": true + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/yallist/-/yallist-4.0.0.tgz", + "integrity": "sha512-3wdGidZyq5PB084XLES5TpOSRA3wjXAlIWMhum2kRcv/41Sn2emQ0dycQW4uZXLejwKvg6EsvbdlVL+FYEct7A==" }, "node_modules/yaml": { "version": "1.10.2", @@ -24057,29 +23367,6 @@ "node": ">=10" } }, - "node_modules/yargs/node_modules/is-fullwidth-code-point": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/is-fullwidth-code-point/-/is-fullwidth-code-point-3.0.0.tgz", - "integrity": "sha512-zymm5+u+sCsSWyD9qNaejV3DFvhCKclKdizYaJUuHA83RLjb7nSuGnddCHGv0hk+KY7BMAlsWeK4Ueg6EV6XQg==", - "dev": true, - "engines": { - "node": ">=8" - } - }, - "node_modules/yargs/node_modules/string-width": { - "version": "4.2.3", - "resolved": "https://registry.npmjs.org/string-width/-/string-width-4.2.3.tgz", - "integrity": "sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g==", - "dev": true, - "dependencies": { - "emoji-regex": "^8.0.0", - "is-fullwidth-code-point": "^3.0.0", - "strip-ansi": "^6.0.1" - }, - "engines": { - "node": ">=8" - } - }, "node_modules/yn": { "version": "3.1.1", "resolved": "https://registry.npmjs.org/yn/-/yn-3.1.1.tgz", diff --git a/package.json b/package.json index 790e94fe..929b7a73 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { - "name": "lido-l2", - "version": "1.0.0", + "name": "lido-l2-optimism", + "version": "2.0.0", "description": "", "main": "index.js", "scripts": { From 0ef8c8691e1752f44ec3e3cdf182d9c21bc924aa Mon Sep 17 00:00:00 2001 From: Anton Kovalchuk Date: Wed, 17 Apr 2024 21:22:28 +0200 Subject: [PATCH 071/148] use unstrucured storage in PermitExtension, add initializers to permit tokens, tests --- contracts/token/ERC20Bridged.sol | 16 +-- contracts/token/ERC20BridgedPermit.sol | 9 ++ contracts/token/ERC20Metadata.sol | 2 +- contracts/token/ERC20RebasableBridged.sol | 16 +-- .../token/ERC20RebasableBridgedPermit.sol | 9 ++ contracts/token/PermitExtension.sol | 104 ++++++++++++++---- test/token/ERC20Bridged.unit.test.ts | 6 +- test/token/ERC20Permit.unit.test.ts | 2 + test/token/ERC20Rebasable.unit.test.ts | 6 +- utils/optimism/deploymentAllFromScratch.ts | 4 +- .../deploymentBridgesAndRebasableToken.ts | 4 +- .../optimism/deploymentNewImplementations.ts | 2 +- 12 files changed, 133 insertions(+), 47 deletions(-) diff --git a/contracts/token/ERC20Bridged.sol b/contracts/token/ERC20Bridged.sol index dee94ec0..a2f872ca 100644 --- a/contracts/token/ERC20Bridged.sol +++ b/contracts/token/ERC20Bridged.sol @@ -43,14 +43,6 @@ contract ERC20Bridged is IERC20Bridged, ERC20Core, ERC20Metadata { bridge = bridge_; } - /// @notice Sets the name and the symbol of the tokens if they both are empty - /// @param name_ The name of the token - /// @param symbol_ The symbol of the token - function initialize(string memory name_, string memory symbol_) external { - _setERC20MetadataName(name_); - _setERC20MetadataSymbol(symbol_); - } - /// @inheritdoc IERC20Bridged function bridgeMint(address account_, uint256 amount_) external onlyBridge { _mint(account_, amount_); @@ -61,6 +53,14 @@ contract ERC20Bridged is IERC20Bridged, ERC20Core, ERC20Metadata { _burn(account_, amount_); } + /// @notice Sets the name and the symbol of the tokens if they both are empty + /// @param name_ The name of the token + /// @param symbol_ The symbol of the token + function initializeERC20Metadata(string memory name_, string memory symbol_) public { + _setERC20MetadataName(name_); + _setERC20MetadataSymbol(symbol_); + } + /// @dev Validates that sender of the transaction is the bridge modifier onlyBridge() { if (msg.sender != bridge) { diff --git a/contracts/token/ERC20BridgedPermit.sol b/contracts/token/ERC20BridgedPermit.sol index d7eca099..55df9245 100644 --- a/contracts/token/ERC20BridgedPermit.sol +++ b/contracts/token/ERC20BridgedPermit.sol @@ -26,6 +26,15 @@ contract ERC20BridgedPermit is ERC20Bridged, PermitExtension { { } + /// @notice Sets the name, the symbol and the version of the tokens if they are empty + /// @param name_ The name of the token + /// @param symbol_ The symbol of the token + /// @param version_ The version of the token + function initialize(string memory name_, string memory symbol_, string memory version_) external { + initializeERC20Metadata(name_, symbol_); + initializeEIP5267Metadata(name_, version_); + } + /// @inheritdoc PermitExtension function _permitAccepted(address owner_, address spender_, uint256 amount_) internal override { _approve(owner_, spender_, amount_); diff --git a/contracts/token/ERC20Metadata.sol b/contracts/token/ERC20Metadata.sol index e3781f26..0e689466 100644 --- a/contracts/token/ERC20Metadata.sol +++ b/contracts/token/ERC20Metadata.sol @@ -74,7 +74,7 @@ contract ERC20Metadata is IERC20Metadata { _loadDynamicMetadata().symbol = symbol_; } - /// @dev Returns the reference to the slot with DynamicMetadta struct + /// @dev Returns the reference to the slot with DynamicMetadata struct function _loadDynamicMetadata() private pure diff --git a/contracts/token/ERC20RebasableBridged.sol b/contracts/token/ERC20RebasableBridged.sol index 4de21b66..9b26a7e6 100644 --- a/contracts/token/ERC20RebasableBridged.sol +++ b/contracts/token/ERC20RebasableBridged.sol @@ -72,14 +72,6 @@ contract ERC20RebasableBridged is IERC20, IERC20Wrapper, IERC20BridgedShares, ER L2_ERC20_TOKEN_BRIDGE = l2ERC20TokenBridge_; } - /// @notice Sets the name and the symbol of the tokens if they both are empty - /// @param name_ The name of the token - /// @param symbol_ The symbol of the token - function initialize(string memory name_, string memory symbol_) external { - _setERC20MetadataName(name_); - _setERC20MetadataSymbol(symbol_); - } - /// @inheritdoc IERC20Wrapper function wrap(uint256 sharesAmount_) external returns (uint256) { if (sharesAmount_ == 0) revert ErrorZeroSharesWrap(); @@ -178,6 +170,14 @@ contract ERC20RebasableBridged is IERC20, IERC20Wrapper, IERC20BridgedShares, ER return true; } + /// @notice Sets the name and the symbol of the tokens if they both are empty + /// @param name_ The name of the token + /// @param symbol_ The symbol of the token + function initializeERC20Metadata(string memory name_, string memory symbol_) public { + _setERC20MetadataName(name_); + _setERC20MetadataSymbol(symbol_); + } + function _getTokenAllowance() internal pure returns (mapping(address => mapping(address => uint256)) storage) { return TOKEN_ALLOWANCE_POSITION.storageMapAddressMapAddressUint256(); } diff --git a/contracts/token/ERC20RebasableBridgedPermit.sol b/contracts/token/ERC20RebasableBridgedPermit.sol index 6b9be86d..2f781333 100644 --- a/contracts/token/ERC20RebasableBridgedPermit.sol +++ b/contracts/token/ERC20RebasableBridgedPermit.sol @@ -30,6 +30,15 @@ contract ERC20RebasableBridgedPermit is ERC20RebasableBridged, PermitExtension { { } + /// @notice Sets the name, the symbol and the version of the tokens if they are empty + /// @param name_ The name of the token + /// @param symbol_ The symbol of the token + /// @param version_ The version of the token + function initialize(string memory name_, string memory symbol_, string memory version_) external { + initializeERC20Metadata(name_, symbol_); + initializeEIP5267Metadata(name_, version_); + } + /// @inheritdoc PermitExtension function _permitAccepted(address owner_, address spender_, uint256 amount_) internal override { _approve(owner_, spender_, amount_); diff --git a/contracts/token/PermitExtension.sol b/contracts/token/PermitExtension.sol index 40cd3617..3850c42d 100644 --- a/contracts/token/PermitExtension.sol +++ b/contracts/token/PermitExtension.sol @@ -3,39 +3,49 @@ pragma solidity 0.8.10; -import {UnstructuredStorage} from "./UnstructuredStorage.sol"; -import {EIP712} from "@openzeppelin/contracts-v4.9/utils/cryptography/EIP712.sol"; -import {IERC2612} from "@openzeppelin/contracts-v4.9/interfaces/IERC2612.sol"; +import {UnstructuredRefStorage} from "./UnstructuredRefStorage.sol"; +import {EIP712} from "@openzeppelin/contracts/utils/cryptography/draft-EIP712.sol"; +import {IERC2612} from "@openzeppelin/contracts/interfaces/draft-IERC2612.sol"; import {SignatureChecker} from "../lib/SignatureChecker.sol"; +/// @author arwer13, kovalgek abstract contract PermitExtension is IERC2612, EIP712 { - using UnstructuredStorage for bytes32; + using UnstructuredRefStorage for bytes32; - /// @dev Nonces for ERC-2612 (Permit) - mapping(address => uint256) internal noncesByAddress; + /// @dev Stores the dynamic metadata of the PermitExtension. Allows safely use of this + /// contract with upgradable proxies + struct EIP5267Metadata { + string name; + string version; + } - // TODO: outline structured storage used because at least EIP712 uses it + /// @dev Location of the slot with EIP5267Metadata + bytes32 private constant EIP5267_METADATA_SLOT = keccak256("PermitExtension.eip5267MetadataSlot"); + /// @dev user shares slot position. + bytes32 internal constant NONCE_BY_ADDRESS_POSITION = keccak256("PermitExtension.NONCE_BY_ADDRESS_POSITION"); /// @dev Typehash constant for ERC-2612 (Permit) - /// /// keccak256("Permit(address owner,address spender,uint256 value,uint256 nonce,uint256 deadline)") - /// bytes32 internal constant PERMIT_TYPEHASH = 0x6e71edae12b1b97f4d1f60370fef10105fa2faae0126114a169c64845d6126c9; /// @param name_ The name of the token /// @param version_ The current major version of the signing domain (aka token version) - constructor( - string memory name_, - string memory version_ - ) EIP712(name_, version_) - { + constructor(string memory name_, string memory version_) EIP712(name_, version_) { + initializeEIP5267Metadata(name_, version_); + } + + /// @notice Sets the name and the version of the tokens if they both are empty + /// @param name_ The name of the token + /// @param version_ The version of the token + function initializeEIP5267Metadata(string memory name_, string memory version_) public { + _setEIP5267MetadataName(name_); + _setEIP5267MetadataVersion(version_); } /// @dev Sets `value` as the allowance of `spender` over ``owner``'s tokens, /// given ``owner``'s signed approval. - /// Emits an {Approval} event. /// /// Requirements: /// @@ -65,7 +75,6 @@ abstract contract PermitExtension is IERC2612, EIP712 { _permitAccepted(_owner, _spender, _value); } - /// @dev Returns the current nonce for `owner`. This value must be /// included whenever a signature is generated for {permit}. /// @@ -73,7 +82,7 @@ abstract contract PermitExtension is IERC2612, EIP712 { /// prevents a signature from being used multiple times. /// function nonces(address owner) external view returns (uint256) { - return noncesByAddress[owner]; + return _getNonceByAddress()[owner]; } /// @dev Returns the domain separator used in the encoding of the signature for {permit}, as defined by {EIP712}. @@ -82,16 +91,73 @@ abstract contract PermitExtension is IERC2612, EIP712 { return _domainSeparatorV4(); } + /// @dev EIP-5267. Returns the fields and values that describe the domain separator + /// used by this contract for EIP-712 signature. + function eip712Domain() + public + view + virtual + returns ( + bytes1 fields, + string memory name, + string memory version, + uint256 chainId, + address verifyingContract, + bytes32 salt, + uint256[] memory extensions + ) + { + return ( + hex"0f", // 01111 + _loadEIP5267Metadata().name, + _loadEIP5267Metadata().version, + block.chainid, + address(this), + bytes32(0), + new uint256[](0) + ); + } /// @dev "Consume a nonce": return the current value and increment. function _useNonce(address _owner) internal returns (uint256 current) { - current = noncesByAddress[_owner]; - noncesByAddress[_owner] = current + 1; + current = _getNonceByAddress()[_owner]; + _getNonceByAddress()[_owner] = current + 1; + } + + /// @notice Nonces for ERC-2612 (Permit) + function _getNonceByAddress() internal pure returns (mapping(address => uint256) storage) { + return NONCE_BY_ADDRESS_POSITION.storageMapAddressAddressUint256(); } /// @dev Override this function in the inherited contract to invoke the approve() function of ERC20. function _permitAccepted(address owner_, address spender_, uint256 amount_) internal virtual; + /// @dev Returns the reference to the slot with EIP5267Metadata struct + function _loadEIP5267Metadata() private pure returns (EIP5267Metadata storage r) { + bytes32 slot = EIP5267_METADATA_SLOT; + assembly { + r.slot := slot + } + } + + /// @dev Sets the name of the token. Might be called only when the name is empty + function _setEIP5267MetadataName(string memory name_) internal { + if (bytes(_loadEIP5267Metadata().name).length > 0) { + revert ErrorEIP5267NameAlreadySet(); + } + _loadEIP5267Metadata().name = name_; + } + + /// @dev Sets the version of the token. Might be called only when the version is empty + function _setEIP5267MetadataVersion(string memory version_) internal { + if (bytes(_loadEIP5267Metadata().version).length > 0) { + revert ErrorEIP5267VersionAlreadySet(); + } + _loadEIP5267Metadata().version = version_; + } + error ErrorInvalidSignature(); error ErrorDeadlineExpired(); + error ErrorEIP5267NameAlreadySet(); + error ErrorEIP5267VersionAlreadySet(); } diff --git a/test/token/ERC20Bridged.unit.test.ts b/test/token/ERC20Bridged.unit.test.ts index a3359635..58ffee0c 100644 --- a/test/token/ERC20Bridged.unit.test.ts +++ b/test/token/ERC20Bridged.unit.test.ts @@ -27,7 +27,7 @@ unit("ERC20Bridged", ctxFactory) owner.address ); await assert.revertsWith( - erc20BridgedImpl.initialize("New Name", ""), + erc20BridgedImpl.initializeERC20Metadata("New Name", ""), "ErrorNameAlreadySet()" ); }) @@ -43,7 +43,7 @@ unit("ERC20Bridged", ctxFactory) owner.address ); await assert.revertsWith( - erc20BridgedImpl.initialize("", "New Symbol"), + erc20BridgedImpl.initializeERC20Metadata("", "New Symbol"), "ErrorSymbolAlreadySet()" ); }) @@ -449,7 +449,7 @@ async function ctxFactory() { const l2TokensProxy = await new OssifiableProxy__factory(deployer).deploy( l2TokenImpl.address, deployer.address, - ERC20Bridged__factory.createInterface().encodeFunctionData("initialize", [ + ERC20Bridged__factory.createInterface().encodeFunctionData("initializeERC20Metadata", [ name, symbol, ]) diff --git a/test/token/ERC20Permit.unit.test.ts b/test/token/ERC20Permit.unit.test.ts index 20e2ae7f..b0ad2c47 100644 --- a/test/token/ERC20Permit.unit.test.ts +++ b/test/token/ERC20Permit.unit.test.ts @@ -420,6 +420,7 @@ async function tokenProxied( ERC20RebasableBridgedPermit__factory.createInterface().encodeFunctionData("initialize", [ name, symbol, + SIGNING_DOMAIN_VERSION ]) ); @@ -449,6 +450,7 @@ async function tokenProxied( ERC20BridgedPermit__factory.createInterface().encodeFunctionData("initialize", [ name, symbol, + SIGNING_DOMAIN_VERSION ]) ); diff --git a/test/token/ERC20Rebasable.unit.test.ts b/test/token/ERC20Rebasable.unit.test.ts index e71c6c73..b403180e 100644 --- a/test/token/ERC20Rebasable.unit.test.ts +++ b/test/token/ERC20Rebasable.unit.test.ts @@ -59,7 +59,7 @@ unit("ERC20RebasableBridged", ctxFactory) owner.address ); await assert.revertsWith( - rebasableTokenImpl.initialize("New Name", ""), + rebasableTokenImpl.initializeERC20Metadata("New Name", ""), "ErrorNameAlreadySet()" ); }) @@ -90,7 +90,7 @@ unit("ERC20RebasableBridged", ctxFactory) owner.address ); await assert.revertsWith( - rebasableTokenImpl.initialize("", "New Symbol"), + rebasableTokenImpl.initializeERC20Metadata("", "New Symbol"), "ErrorSymbolAlreadySet()" ); }) @@ -877,7 +877,7 @@ async function ctxFactory() { const l2TokensProxy = await new OssifiableProxy__factory(deployer).deploy( rebasableTokenImpl.address, deployer.address, - ERC20RebasableBridged__factory.createInterface().encodeFunctionData("initialize", [ + ERC20RebasableBridged__factory.createInterface().encodeFunctionData("initializeERC20Metadata", [ name, symbol, ]) diff --git a/utils/optimism/deploymentAllFromScratch.ts b/utils/optimism/deploymentAllFromScratch.ts index 6fdfaad4..f1aac90c 100644 --- a/utils/optimism/deploymentAllFromScratch.ts +++ b/utils/optimism/deploymentAllFromScratch.ts @@ -229,7 +229,7 @@ export default function deploymentAll( expectedL2TokenImplAddress, l2Params.admins.proxy, ERC20Bridged__factory.createInterface().encodeFunctionData( - "initialize", + "initializeERC20Metadata", [l2TokenName, l2TokenSymbol] ), options?.overrides, @@ -257,7 +257,7 @@ export default function deploymentAll( expectedL2TokenRebasableImplAddress, l2Params.admins.proxy, ERC20RebasableBridged__factory.createInterface().encodeFunctionData( - "initialize", + "initializeERC20Metadata", [l2TokenRebasableName, l2TokenRebasableSymbol] ), options?.overrides, diff --git a/utils/optimism/deploymentBridgesAndRebasableToken.ts b/utils/optimism/deploymentBridgesAndRebasableToken.ts index f5ad4bc4..2a61dd39 100644 --- a/utils/optimism/deploymentBridgesAndRebasableToken.ts +++ b/utils/optimism/deploymentBridgesAndRebasableToken.ts @@ -185,7 +185,7 @@ export default function deployment( expectedL2TokenImplAddress, l2Params.admins.proxy, ERC20Bridged__factory.createInterface().encodeFunctionData( - "initialize", + "initializeERC20Metadata", [l2TokenName, l2TokenSymbol] ), options?.overrides, @@ -213,7 +213,7 @@ export default function deployment( expectedL2TokenRebasableImplAddress, l2Params.admins.proxy, ERC20RebasableBridged__factory.createInterface().encodeFunctionData( - "initialize", + "initializeERC20Metadata", [l2TokenRebasableName, l2TokenRebasableSymbol] ), options?.overrides, diff --git a/utils/optimism/deploymentNewImplementations.ts b/utils/optimism/deploymentNewImplementations.ts index 776fe8df..d49e5d00 100644 --- a/utils/optimism/deploymentNewImplementations.ts +++ b/utils/optimism/deploymentNewImplementations.ts @@ -188,7 +188,7 @@ export default function deploymentNewImplementations( expectedL2TokenRebasableImplAddress, l2Params.admins.proxy, ERC20RebasableBridged__factory.createInterface().encodeFunctionData( - "initialize", + "initializeERC20Metadata", [l2TokenRebasableName, l2TokenRebasableSymbol] ), options?.overrides, From e2cf92f22b4fcac908faf1bcb83523ae74b1292b Mon Sep 17 00:00:00 2001 From: Anton Kovalchuk Date: Wed, 17 Apr 2024 21:42:09 +0200 Subject: [PATCH 072/148] remove OZ 4.9 --- package-lock.json | 15 ++++----------- package.json | 1 - 2 files changed, 4 insertions(+), 12 deletions(-) diff --git a/package-lock.json b/package-lock.json index eb162453..e116e721 100644 --- a/package-lock.json +++ b/package-lock.json @@ -13,7 +13,6 @@ "@ethersproject/providers": "^5.6.8", "@lidofinance/evm-script-decoder": "^0.2.2", "@openzeppelin/contracts": "4.6.0", - "@openzeppelin/contracts-v4.9": "npm:@openzeppelin/contracts@4.9.6", "chalk": "4.1.2" }, "devDependencies": { @@ -2057,12 +2056,6 @@ "resolved": "https://registry.npmjs.org/@openzeppelin/contracts/-/contracts-4.6.0.tgz", "integrity": "sha512-8vi4d50NNya/bQqCmaVzvHNmwHvS0OBKb7HNtuNwEE3scXWrP31fKQoGxNMT+KbzmrNZzatE3QK5p2gFONI/hg==" }, - "node_modules/@openzeppelin/contracts-v4.9": { - "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/@resolver-engine/core": { "version": "0.3.3", "resolved": "https://registry.npmjs.org/@resolver-engine/core/-/core-0.3.3.tgz", @@ -16784,14 +16777,14 @@ } }, "node_modules/hardhat": { - "version": "2.22.2", - "resolved": "https://registry.npmjs.org/hardhat/-/hardhat-2.22.2.tgz", - "integrity": "sha512-0xZ7MdCZ5sJem4MrvpQWLR3R3zGDoHw5lsR+pBFimqwagimIOn3bWuZv69KA+veXClwI1s/zpqgwPwiFrd4Dxw==", + "version": "2.22.3", + "resolved": "https://registry.npmjs.org/hardhat/-/hardhat-2.22.3.tgz", + "integrity": "sha512-k8JV2ECWNchD6ahkg2BR5wKVxY0OiKot7fuxiIpRK0frRqyOljcR2vKwgWSLw6YIeDcNNA4xybj7Og7NSxr2hA==", "dev": true, "dependencies": { "@ethersproject/abi": "^5.1.2", "@metamask/eth-sig-util": "^4.0.0", - "@nomicfoundation/edr": "^0.3.1", + "@nomicfoundation/edr": "^0.3.5", "@nomicfoundation/ethereumjs-common": "4.0.4", "@nomicfoundation/ethereumjs-tx": "5.0.4", "@nomicfoundation/ethereumjs-util": "9.0.4", diff --git a/package.json b/package.json index 929b7a73..06cba522 100644 --- a/package.json +++ b/package.json @@ -64,7 +64,6 @@ "@ethersproject/providers": "^5.6.8", "@lidofinance/evm-script-decoder": "^0.2.2", "@openzeppelin/contracts": "4.6.0", - "@openzeppelin/contracts-v4.9": "npm:@openzeppelin/contracts@4.9.6", "chalk": "4.1.2" } } From fa0fc29a284bbb2dffbb1ebac7b2fa00cfc705a8 Mon Sep 17 00:00:00 2001 From: Anton Kovalchuk Date: Wed, 17 Apr 2024 22:14:54 +0200 Subject: [PATCH 073/148] remove unused contract --- contracts/BridgeableTokens.sol | 49 ---------------------------------- contracts/optimism/README.md | 8 +++--- 2 files changed, 4 insertions(+), 53 deletions(-) delete mode 100644 contracts/BridgeableTokens.sol diff --git a/contracts/BridgeableTokens.sol b/contracts/BridgeableTokens.sol deleted file mode 100644 index 52ec31af..00000000 --- a/contracts/BridgeableTokens.sol +++ /dev/null @@ -1,49 +0,0 @@ -// SPDX-FileCopyrightText: 2022 Lido -// SPDX-License-Identifier: GPL-3.0 - -pragma solidity 0.8.10; - -/// @author psirex -/// @notice Contains the logic for validation of tokens used in the bridging process -contract BridgeableTokens { - /// @notice Address of the bridged token in the L1 chain - address public immutable l1Token; - - /// @notice Address of the token minted on the L2 chain when token bridged - address public immutable l2Token; - - /// @param l1Token_ Address of the bridged token in the L1 chain - /// @param l2Token_ Address of the token minted on the L2 chain when token bridged - constructor(address l1Token_, address l2Token_) { - l1Token = l1Token_; - l2Token = l2Token_; - } - - /// @dev Validates that passed l1Token_ is supported by the bridge - modifier onlySupportedL1Token(address l1Token_) { - if (l1Token_ != l1Token) { - revert ErrorUnsupportedL1Token(); - } - _; - } - - /// @dev Validates that passed l2Token_ is supported by the bridge - modifier onlySupportedL2Token(address l2Token_) { - if (l2Token_ != l2Token) { - revert ErrorUnsupportedL2Token(); - } - _; - } - - /// @dev validates that account_ is not zero address - modifier onlyNonZeroAccount(address account_) { - if (account_ == address(0)) { - revert ErrorAccountIsZeroAddress(); - } - _; - } - - error ErrorUnsupportedL1Token(); - error ErrorUnsupportedL2Token(); - error ErrorAccountIsZeroAddress(); -} diff --git a/contracts/optimism/README.md b/contracts/optimism/README.md index 6b853ba3..0ae17d5f 100644 --- a/contracts/optimism/README.md +++ b/contracts/optimism/README.md @@ -39,7 +39,7 @@ A high-level overview of the proposed solution might be found in the below diagr ![](https://i.imgur.com/yAF9gbl.png) - [**`BridgingManager`**](#BridgingManager) - contains administrative methods to retrieve and control the state of the bridging process. -- [**`BridgeableTokens`**](#BridgeableTokens) - contains the logic for validation of tokens used in the bridging process. +- [**`RebasableAndNonRebasableTokens`**](#RebasableAndNonRebasableTokens) - contains the logic for validation of tokens used in the bridging process. - [**`CrossDomainEnabled`**](#CrossDomainEnabled) - helper contract for contracts performing cross-domain communications - [**`L1ERC20ExtendedTokensBridge`**](#L1ERC20ExtendedTokensBridge) - Ethereum's counterpart of the bridge to bridge registered ERC20 compatible tokens between Ethereum and Optimism chains. - [**`L2ERC20ExtendedTokensBridge`**](#L2ERC20ExtendedTokensBridge) - Optimism's counterpart of the bridge to bridge registered ERC20 compatible tokens between Ethereum and Optimism chains @@ -165,7 +165,7 @@ Validates that deposits are enabled. Reverts with the error `ErrorDepositsDisabl Validates that withdrawals are enabled. Reverts with the error `ErrorWithdrawalsDisabled()` when called on contract with disabled withdrawals. -## BridgeableTokens +## RebasableAndNonRebasableTokens Contains the logic for validation of tokens used in the bridging process @@ -219,7 +219,7 @@ Enforces that the modified function is only callable by a specific cross-domain ## `L1ERC20ExtendedTokensBridge` **Implements:** [`IL1ERC20Bridge`](https://github.com/ethereum-optimism/optimism/blob/develop/packages/contracts/contracts/L1/messaging/IL1ERC20Bridge.sol) -**Inherits:** [`BridgingManager`](#BridgingManager) [`BridgeableTokens`](#BridgeableTokens) [`CrossDomainEnabled`](#CrossDomainEnabled) +**Inherits:** [`BridgingManager`](#BridgingManager) [`RebasableAndNonRebasableTokens`](#RebasableAndNonRebasableTokens) [`CrossDomainEnabled`](#CrossDomainEnabled) The L1 Standard bridge is a contract that locks bridged token on L1 side, send deposit messages on L2 side and finalize token withdrawals from L2. @@ -303,7 +303,7 @@ Performs the logic for deposits by informing the L2 Deposited Token contract of ## `L2ERC20ExtendedTokensBridge` **Implements:** [`IL2ERC20Bridge`](https://github.com/ethereum-optimism/optimism/blob/develop/packages/contracts/contracts/L2/messaging/IL2ERC20Bridge.sol) -**Extends** [`BridgingManager`](#BridgingManager) [`BridgeableTokens`](#BridgeableTokens) [`CrossDomainEnabled`](#CrossDomainEnabled) +**Extends** [`BridgingManager`](#BridgingManager) [`RebasableAndNonRebasableTokens`](#RebasableAndNonRebasableTokens) [`CrossDomainEnabled`](#CrossDomainEnabled) The L2 token bridge is a contract that works with the L1 Token bridge to enable ERC20 token bridging between L1 and L2. This contract acts as a minter for new tokens when it hears about deposits into the L1 token bridge. This contract also acts as a burner of the tokens intended for withdrawal, informing the L1 bridge to release L1 funds. From 429aa7ed39b9b19db755f469952675493c4b4e1f Mon Sep 17 00:00:00 2001 From: Anton Kovalchuk Date: Wed, 17 Apr 2024 23:07:41 +0200 Subject: [PATCH 074/148] refactor bridges --- .../optimism/L1ERC20ExtendedTokensBridge.sol | 7 ++-- .../optimism/L2ERC20ExtendedTokensBridge.sol | 14 +++---- .../RebasableAndNonRebasableTokens.sol | 36 ++++++---------- test/optimism/L1LidoTokensBridge.unit.test.ts | 42 +++++++++---------- .../L2ERC20ExtendedTokensBridge.unit.test.ts | 16 +++---- 5 files changed, 50 insertions(+), 65 deletions(-) diff --git a/contracts/optimism/L1ERC20ExtendedTokensBridge.sol b/contracts/optimism/L1ERC20ExtendedTokensBridge.sol index 8bdaf2d5..90c6eca2 100644 --- a/contracts/optimism/L1ERC20ExtendedTokensBridge.sol +++ b/contracts/optimism/L1ERC20ExtendedTokensBridge.sol @@ -119,7 +119,7 @@ abstract contract L1ERC20ExtendedTokensBridge is onlyFromCrossDomainAccount(L2_TOKEN_BRIDGE) onlySupportedL1L2TokensPair(l1Token_, l2Token_) { - uint256 amountToWithdraw = (_isRebasable(l1Token_) && amount_ != 0) ? + uint256 amountToWithdraw = (l1Token_ == L1_TOKEN_REBASABLE && amount_ != 0) ? IERC20Wrapper(L1_TOKEN_NON_REBASABLE).unwrap(amount_) : amount_; IERC20(l1Token_).safeTransfer(to_, amountToWithdraw); @@ -167,8 +167,8 @@ abstract contract L1ERC20ExtendedTokensBridge is ) internal returns (uint256) { if (amount_ != 0) { IERC20(l1Token_).safeTransferFrom(from_, address(this), amount_); - if(_isRebasable(l1Token_)) { - if(!IERC20(l1Token_).approve(L1_TOKEN_NON_REBASABLE, amount_)) revert ErrorRebasableTokenApprove(); + if(l1Token_ == L1_TOKEN_REBASABLE) { + IERC20(l1Token_).safeIncreaseAllowance(L1_TOKEN_NON_REBASABLE, amount_); return IERC20Wrapper(L1_TOKEN_NON_REBASABLE).wrap(amount_); } } @@ -176,5 +176,4 @@ abstract contract L1ERC20ExtendedTokensBridge is } error ErrorSenderNotEOA(); - error ErrorRebasableTokenApprove(); } diff --git a/contracts/optimism/L2ERC20ExtendedTokensBridge.sol b/contracts/optimism/L2ERC20ExtendedTokensBridge.sol index ccba2bdd..13253ab0 100644 --- a/contracts/optimism/L2ERC20ExtendedTokensBridge.sol +++ b/contracts/optimism/L2ERC20ExtendedTokensBridge.sol @@ -74,7 +74,7 @@ contract L2ERC20ExtendedTokensBridge is revert ErrorSenderNotEOA(); } _withdrawTo(l2Token_, msg.sender, msg.sender, amount_, l1Gas_, data_); - emit WithdrawalInitiated(_l1Token(l2Token_), l2Token_, msg.sender, msg.sender, amount_, data_); + emit WithdrawalInitiated(_getL1Token(l2Token_), l2Token_, msg.sender, msg.sender, amount_, data_); } /// @inheritdoc IL2ERC20Bridge @@ -89,7 +89,7 @@ contract L2ERC20ExtendedTokensBridge is onlySupportedL2Token(l2Token_) { _withdrawTo(l2Token_, msg.sender, to_, amount_, l1Gas_, data_); - emit WithdrawalInitiated(_l1Token(l2Token_), l2Token_, msg.sender, to_, amount_, data_); + emit WithdrawalInitiated(_getL1Token(l2Token_), l2Token_, msg.sender, to_, amount_, data_); } /// @inheritdoc IL2ERC20Bridge @@ -110,7 +110,7 @@ contract L2ERC20ExtendedTokensBridge is ITokenRateUpdatable tokenRateOracle = ERC20RebasableBridged(L2_TOKEN_REBASABLE).TOKEN_RATE_ORACLE(); tokenRateOracle.updateRate(depositData.rate, depositData.timestamp); - uint256 depositedAmount = _mintTokens(l1Token_, l2Token_, to_, amount_); + uint256 depositedAmount = _mintTokens(l2Token_, to_, amount_); emit DepositFinalized(l1Token_, l2Token_, from_, to_, depositedAmount, depositData.data); } @@ -136,24 +136,22 @@ contract L2ERC20ExtendedTokensBridge is bytes memory message = abi.encodeWithSelector( IL1ERC20Bridge.finalizeERC20Withdrawal.selector, - _l1Token(l2Token_), l2Token_, from_, to_, amountToWithdraw, data_ + _getL1Token(l2Token_), l2Token_, from_, to_, amountToWithdraw, data_ ); sendCrossDomainMessage(L1_TOKEN_BRIDGE, l1Gas_, message); } /// @dev Mints tokens. - /// @param l1Token_ Address of L1 token for which deposit is finalizing. /// @param l2Token_ Address of L2 token for which deposit is finalizing. /// @param to_ Account that token mints for. /// @param amount_ Amount of token or shares to mint. /// @return returns amount of minted tokens. function _mintTokens( - address l1Token_, address l2Token_, address to_, uint256 amount_ ) internal returns (uint256) { - if(_isRebasable(l1Token_)) { + if(l2Token_ == L2_TOKEN_REBASABLE) { ERC20RebasableBridged(l2Token_).bridgeMintShares(to_, amount_); return ERC20RebasableBridged(l2Token_).getTokensByShares(amount_); } @@ -172,7 +170,7 @@ contract L2ERC20ExtendedTokensBridge is address from_, uint256 amount_ ) internal returns (uint256) { - if(_isRebasable(l2Token_)) { + if(l2Token_ == L2_TOKEN_REBASABLE) { uint256 shares = ERC20RebasableBridged(l2Token_).getSharesByTokens(amount_); ERC20RebasableBridged(l2Token_).bridgeBurnShares(from_, shares); return shares; diff --git a/contracts/optimism/RebasableAndNonRebasableTokens.sol b/contracts/optimism/RebasableAndNonRebasableTokens.sol index 21491d19..61ead668 100644 --- a/contracts/optimism/RebasableAndNonRebasableTokens.sol +++ b/contracts/optimism/RebasableAndNonRebasableTokens.sol @@ -3,8 +3,6 @@ pragma solidity 0.8.10; -import {UnstructuredRefStorage} from "../token/UnstructuredRefStorage.sol"; - /// @author psirex, kovalgek /// @notice Contains the logic for validation of tokens used in the bridging process contract RebasableAndNonRebasableTokens { @@ -39,28 +37,16 @@ contract RebasableAndNonRebasableTokens { /// @dev Validates that passed l1Token_ and l2Token_ tokens pair is supported by the bridge. modifier onlySupportedL1L2TokensPair(address l1Token_, address l2Token_) { - if (l1Token_ != L1_TOKEN_NON_REBASABLE && l1Token_ != L1_TOKEN_REBASABLE) { - revert ErrorUnsupportedL1Token(); - } - if (l2Token_ != L2_TOKEN_NON_REBASABLE && l2Token_ != L2_TOKEN_REBASABLE) { - revert ErrorUnsupportedL2Token(); - } if (!_isSupportedL1L2TokensPair(l1Token_, l2Token_)) { - revert ErrorUnsupportedL1L2TokensPair(); + revert ErrorUnsupportedL1L2TokensPair(l1Token_, l2Token_); } _; } - function _isSupportedL1L2TokensPair(address l1Token_, address l2Token_) internal view returns (bool) { - bool isNonRebasablePair = l1Token_ == L1_TOKEN_NON_REBASABLE && l2Token_ == L2_TOKEN_NON_REBASABLE; - bool isRebasablePair = l1Token_ == L1_TOKEN_REBASABLE && l2Token_ == L2_TOKEN_REBASABLE; - return isNonRebasablePair || isRebasablePair; - } - /// @dev Validates that passed l1Token_ is supported by the bridge modifier onlySupportedL1Token(address l1Token_) { if (l1Token_ != L1_TOKEN_NON_REBASABLE && l1Token_ != L1_TOKEN_REBASABLE) { - revert ErrorUnsupportedL1Token(); + revert ErrorUnsupportedL1Token(l1Token_); } _; } @@ -68,7 +54,7 @@ contract RebasableAndNonRebasableTokens { /// @dev Validates that passed l2Token_ is supported by the bridge modifier onlySupportedL2Token(address l2Token_) { if (l2Token_ != L2_TOKEN_NON_REBASABLE && l2Token_ != L2_TOKEN_REBASABLE) { - revert ErrorUnsupportedL2Token(); + revert ErrorUnsupportedL2Token(l2Token_); } _; } @@ -81,16 +67,18 @@ contract RebasableAndNonRebasableTokens { _; } - function _isRebasable(address token_) internal view returns (bool) { - return token_ == L1_TOKEN_REBASABLE || token_ == L2_TOKEN_REBASABLE; + function _isSupportedL1L2TokensPair(address l1Token_, address l2Token_) internal view returns (bool) { + bool isNonRebasablePair = l1Token_ == L1_TOKEN_NON_REBASABLE && l2Token_ == L2_TOKEN_NON_REBASABLE; + bool isRebasablePair = l1Token_ == L1_TOKEN_REBASABLE && l2Token_ == L2_TOKEN_REBASABLE; + return isNonRebasablePair || isRebasablePair; } - function _l1Token(address l2Token_) internal view returns (address) { - return _isRebasable(l2Token_) ? L1_TOKEN_REBASABLE : L1_TOKEN_NON_REBASABLE; + function _getL1Token(address l2Token_) internal view returns (address) { + return (l2Token_ == L2_TOKEN_REBASABLE) ? L1_TOKEN_REBASABLE : L1_TOKEN_NON_REBASABLE; } - error ErrorUnsupportedL1Token(); - error ErrorUnsupportedL2Token(); - error ErrorUnsupportedL1L2TokensPair(); + error ErrorUnsupportedL1Token(address l1Token); + error ErrorUnsupportedL2Token(address l2Token); + error ErrorUnsupportedL1L2TokensPair(address l1Token, address l2Token); error ErrorAccountIsZeroAddress(); } diff --git a/test/optimism/L1LidoTokensBridge.unit.test.ts b/test/optimism/L1LidoTokensBridge.unit.test.ts index df1aaae0..5aff89e4 100644 --- a/test/optimism/L1LidoTokensBridge.unit.test.ts +++ b/test/optimism/L1LidoTokensBridge.unit.test.ts @@ -63,7 +63,7 @@ unit("Optimism :: L1LidoTokensBridge", ctxFactory) wei`1 gwei`, "0x" ), - "ErrorUnsupportedL1Token()" + "ErrorUnsupportedL1L2TokensPair(\""+ctx.accounts.stranger.address+"\", \""+ctx.stubs.l2TokenNonRebasable.address+"\")" ); await assert.revertsWith( ctx.l1TokenBridge.depositERC20( @@ -73,7 +73,7 @@ unit("Optimism :: L1LidoTokensBridge", ctxFactory) wei`1 gwei`, "0x" ), - "ErrorUnsupportedL1Token()" + "ErrorUnsupportedL1L2TokensPair(\""+ctx.accounts.stranger.address+"\", \""+ctx.stubs.l2TokenRebasable.address+"\")" ); }) @@ -86,7 +86,7 @@ unit("Optimism :: L1LidoTokensBridge", ctxFactory) wei`1 gwei`, "0x" ), - "ErrorUnsupportedL2Token()" + "ErrorUnsupportedL1L2TokensPair(\""+ctx.stubs.l1TokenNonRebasable.address+"\", \""+ctx.accounts.stranger.address+"\")" ); await assert.revertsWith( ctx.l1TokenBridge.depositERC20( @@ -96,7 +96,7 @@ unit("Optimism :: L1LidoTokensBridge", ctxFactory) wei`1 gwei`, "0x" ), - "ErrorUnsupportedL2Token()" + "ErrorUnsupportedL1L2TokensPair(\""+ctx.stubs.l1TokenRebasable.address+"\", \""+ctx.accounts.stranger.address+"\")" ); }) @@ -109,7 +109,7 @@ unit("Optimism :: L1LidoTokensBridge", ctxFactory) wei`1 gwei`, "0x" ), - "ErrorUnsupportedL1L2TokensPair()" + "ErrorUnsupportedL1L2TokensPair(\""+ctx.stubs.l1TokenRebasable.address+"\", \""+ctx.stubs.l2TokenNonRebasable.address+"\")" ); await assert.revertsWith( ctx.l1TokenBridge.depositERC20( @@ -119,7 +119,7 @@ unit("Optimism :: L1LidoTokensBridge", ctxFactory) wei`1 gwei`, "0x" ), - "ErrorUnsupportedL1L2TokensPair()" + "ErrorUnsupportedL1L2TokensPair(\""+ctx.stubs.l1TokenNonRebasable.address+"\", \""+ctx.stubs.l2TokenRebasable.address+"\")" ); }) @@ -335,7 +335,7 @@ unit("Optimism :: L1LidoTokensBridge", ctxFactory) wei`1 gwei`, "0x" ), - "ErrorUnsupportedL1Token()" + "ErrorUnsupportedL1L2TokensPair(\""+stranger.address+"\", \""+l2TokenNonRebasable.address+"\")" ); await assert.revertsWith( l1TokenBridge.depositERC20To( @@ -346,38 +346,38 @@ unit("Optimism :: L1LidoTokensBridge", ctxFactory) wei`1 gwei`, "0x" ), - "ErrorUnsupportedL1Token()" + "ErrorUnsupportedL1L2TokensPair(\""+stranger.address+"\", \""+l2TokenRebasable.address+"\")" ); }) .test("depositERC20To() :: wrong l2Token address", async (ctx) => { const { l1TokenBridge, - stubs: { l2TokenNonRebasable, l2TokenRebasable }, + stubs: { l1TokenNonRebasable, l1TokenRebasable }, accounts: { recipient, stranger }, } = ctx; await assert.revertsWith( l1TokenBridge.depositERC20To( + l1TokenNonRebasable.address, stranger.address, - l2TokenNonRebasable.address, recipient.address, wei`1 ether`, wei`1 gwei`, "0x" ), - "ErrorUnsupportedL1Token()" + "ErrorUnsupportedL1L2TokensPair(\""+l1TokenNonRebasable.address+"\", \""+stranger.address+"\")" ); await assert.revertsWith( l1TokenBridge.depositERC20To( + l1TokenRebasable.address, stranger.address, - l2TokenRebasable.address, recipient.address, wei`1 ether`, wei`1 gwei`, "0x" ), - "ErrorUnsupportedL1Token()" + "ErrorUnsupportedL1L2TokensPair(\""+l1TokenRebasable.address+"\", \""+stranger.address+"\")" ); }) @@ -397,7 +397,7 @@ unit("Optimism :: L1LidoTokensBridge", ctxFactory) wei`1 gwei`, "0x" ), - "ErrorUnsupportedL1L2TokensPair()" + "ErrorUnsupportedL1L2TokensPair(\""+l1TokenNonRebasable.address+"\", \""+l2TokenRebasable.address+"\")" ); await assert.revertsWith( l1TokenBridge.depositERC20To( @@ -408,7 +408,7 @@ unit("Optimism :: L1LidoTokensBridge", ctxFactory) wei`1 gwei`, "0x" ), - "ErrorUnsupportedL1L2TokensPair()" + "ErrorUnsupportedL1L2TokensPair(\""+l1TokenRebasable.address+"\", \""+l2TokenNonRebasable.address+"\")" ); }) @@ -641,7 +641,7 @@ unit("Optimism :: L1LidoTokensBridge", ctxFactory) wei`1 ether`, "0x" ), - "ErrorUnsupportedL1Token()" + "ErrorUnsupportedL1L2TokensPair(\""+stranger.address+"\", \""+l2TokenNonRebasable.address+"\")" ); await assert.revertsWith( @@ -655,7 +655,7 @@ unit("Optimism :: L1LidoTokensBridge", ctxFactory) wei`1 ether`, "0x" ), - "ErrorUnsupportedL1Token()" + "ErrorUnsupportedL1L2TokensPair(\""+stranger.address+"\", \""+l2TokenRebasable.address+"\")" ); }) @@ -679,7 +679,7 @@ unit("Optimism :: L1LidoTokensBridge", ctxFactory) wei`1 ether`, "0x" ), - "ErrorUnsupportedL2Token()" + "ErrorUnsupportedL1L2TokensPair(\""+l1TokenNonRebasable.address+"\", \""+stranger.address+"\")" ); await assert.revertsWith( @@ -693,7 +693,7 @@ unit("Optimism :: L1LidoTokensBridge", ctxFactory) wei`1 ether`, "0x" ), - "ErrorUnsupportedL2Token()" + "ErrorUnsupportedL1L2TokensPair(\""+l1TokenRebasable.address+"\", \""+stranger.address+"\")" ); }) @@ -716,7 +716,7 @@ unit("Optimism :: L1LidoTokensBridge", ctxFactory) wei`1 ether`, "0x" ), - "ErrorUnsupportedL1L2TokensPair()" + "ErrorUnsupportedL1L2TokensPair(\""+l1TokenNonRebasable.address+"\", \""+l2TokenRebasable.address+"\")" ); await assert.revertsWith( @@ -730,7 +730,7 @@ unit("Optimism :: L1LidoTokensBridge", ctxFactory) wei`1 ether`, "0x" ), - "ErrorUnsupportedL1L2TokensPair()" + "ErrorUnsupportedL1L2TokensPair(\""+l1TokenRebasable.address+"\", \""+l2TokenNonRebasable.address+"\")" ); }) diff --git a/test/optimism/L2ERC20ExtendedTokensBridge.unit.test.ts b/test/optimism/L2ERC20ExtendedTokensBridge.unit.test.ts index ed800439..d8d4f9f9 100644 --- a/test/optimism/L2ERC20ExtendedTokensBridge.unit.test.ts +++ b/test/optimism/L2ERC20ExtendedTokensBridge.unit.test.ts @@ -67,7 +67,7 @@ unit("Optimism:: L2ERC20ExtendedTokensBridge", ctxFactory) } = ctx; await assert.revertsWith( l2TokenBridge.withdraw(stranger.address, wei`1 ether`, wei`1 gwei`, "0x"), - "ErrorUnsupportedL2Token()" + "ErrorUnsupportedL2Token(\""+stranger.address+"\")" ); }) @@ -402,7 +402,7 @@ unit("Optimism:: L2ERC20ExtendedTokensBridge", ctxFactory) wei`1 gwei`, "0x" ), - "ErrorUnsupportedL2Token()" + "ErrorUnsupportedL2Token(\""+stranger.address+"\")" ); }) @@ -724,7 +724,7 @@ unit("Optimism:: L2ERC20ExtendedTokensBridge", ctxFactory) wei`1 ether`, "0x" ), - "ErrorUnsupportedL1Token()" + "ErrorUnsupportedL1L2TokensPair(\""+stranger.address+"\", \""+l2TokenNonRebasable.address+"\")" ); await assert.revertsWith( l2TokenBridge @@ -737,7 +737,7 @@ unit("Optimism:: L2ERC20ExtendedTokensBridge", ctxFactory) wei`1 ether`, "0x" ), - "ErrorUnsupportedL1Token()" + "ErrorUnsupportedL1L2TokensPair(\""+stranger.address+"\", \""+l2TokenRebasable.address+"\")" ); }) @@ -759,7 +759,7 @@ unit("Optimism:: L2ERC20ExtendedTokensBridge", ctxFactory) wei`1 ether`, "0x" ), - "ErrorUnsupportedL2Token()" + "ErrorUnsupportedL1L2TokensPair(\""+l1TokenNonRebasable.address+"\", \""+stranger.address+"\")" ); await assert.revertsWith( l2TokenBridge @@ -772,7 +772,7 @@ unit("Optimism:: L2ERC20ExtendedTokensBridge", ctxFactory) wei`1 ether`, "0x" ), - "ErrorUnsupportedL2Token()" + "ErrorUnsupportedL1L2TokensPair(\""+l1TokenRebasable.address+"\", \""+stranger.address+"\")" ); }) @@ -794,7 +794,7 @@ unit("Optimism:: L2ERC20ExtendedTokensBridge", ctxFactory) wei`1 ether`, "0x" ), - "ErrorUnsupportedL1L2TokensPair()" + "ErrorUnsupportedL1L2TokensPair(\""+l1TokenNonRebasable.address+"\", \""+l2TokenRebasable.address+"\")" ); await assert.revertsWith( l2TokenBridge @@ -807,7 +807,7 @@ unit("Optimism:: L2ERC20ExtendedTokensBridge", ctxFactory) wei`1 ether`, "0x" ), - "ErrorUnsupportedL1L2TokensPair()" + "ErrorUnsupportedL1L2TokensPair(\""+l1TokenRebasable.address+"\", \""+l2TokenNonRebasable.address+"\")" ); }) From 68066b921f95a9858c87b4204c34ed6a2a9ea9a9 Mon Sep 17 00:00:00 2001 From: Anton Kovalchuk Date: Wed, 17 Apr 2024 23:08:04 +0200 Subject: [PATCH 075/148] remove unused function --- contracts/lib/ECDSA.sol | 15 --------------- 1 file changed, 15 deletions(-) diff --git a/contracts/lib/ECDSA.sol b/contracts/lib/ECDSA.sol index 0f694d8d..e3ce787b 100644 --- a/contracts/lib/ECDSA.sol +++ b/contracts/lib/ECDSA.sol @@ -3,7 +3,6 @@ // Extracted from: // https://github.com/OpenZeppelin/openzeppelin-contracts/blob/v3.4.0/contracts/cryptography/ECDSA.sol#L53 -// https://github.com/OpenZeppelin/openzeppelin-contracts/blob/541e821/contracts/utils/cryptography/ECDSA.sol#L112 pragma solidity 0.8.10; @@ -40,18 +39,4 @@ library ECDSA { return signer; } - - /** - * @dev Overload of `recover` that receives the `r` and `vs` short-signature fields separately. - * See https://eips.ethereum.org/EIPS/eip-2098[EIP-2098 short signatures] - */ - function recover(bytes32 hash, bytes32 r, bytes32 vs) internal pure returns (address) { - bytes32 s; - uint8 v; - assembly { - s := and(vs, 0x7fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff) - v := add(shr(255, vs), 27) - } - return recover(hash, v, r, s); - } } From 84ed66501a6c3742cdbaf87edc62f787f39abed2 Mon Sep 17 00:00:00 2001 From: Anton Kovalchuk Date: Wed, 17 Apr 2024 23:08:46 +0200 Subject: [PATCH 076/148] set default value in config example --- .env.example | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/.env.example b/.env.example index 73d274db..8f5446e9 100644 --- a/.env.example +++ b/.env.example @@ -34,7 +34,7 @@ L1_OP_STACK_TOKEN_RATE_PUSHER= # Default is: 300_000. # This value was calculated by formula: # l2GasLimit = (gas cost of L2Bridge.finalizeDeposit() + OptimismPortal.minimumGasLimit(depositData.length)) * 1.5 -L2_GAS_LIMIT_FOR_PUSHING_TOKEN_RATE= +L2_GAS_LIMIT_FOR_PUSHING_TOKEN_RATE=300000 # A time period when token rate can be considered outdated. TOKEN_RATE_OUTDATED_DELAY=86400 # default is 86400 (24 hours) @@ -112,4 +112,4 @@ TESTING_OPT_GOV_BRIDGE_EXECUTOR= # ############################ TESTING_PRIVATE_KEY= -TESTING_OPT_LDO_HOLDER_PRIVATE_KEY= \ No newline at end of file +TESTING_OPT_LDO_HOLDER_PRIVATE_KEY= From 58fb55f10580dc01239b05c986892fbf038adec1 Mon Sep 17 00:00:00 2001 From: Anton Kovalchuk Date: Thu, 18 Apr 2024 01:45:43 +0200 Subject: [PATCH 077/148] use unstructured storage in token rate oracle --- contracts/optimism/TokenRateOracle.sol | 63 +++++++++++++++++--------- 1 file changed, 42 insertions(+), 21 deletions(-) diff --git a/contracts/optimism/TokenRateOracle.sol b/contracts/optimism/TokenRateOracle.sol index 164807b4..de40eadc 100644 --- a/contracts/optimism/TokenRateOracle.sol +++ b/contracts/optimism/TokenRateOracle.sol @@ -13,6 +13,19 @@ interface ITokenRateOracle is ITokenRateUpdatable, IChainlinkAggregatorInterface /// @notice Oracle for storing token rate. contract TokenRateOracle is CrossDomainEnabled, ITokenRateOracle { + /// @dev Stores the dynamic data of the oracle. Allows safely use of this + /// contract with upgradable proxies + struct TokenRateData { + /// @notice wstETH/stETH token rate. + uint192 tokenRate; + /// @notice L1 time when token rate was pushed. + uint64 rateL1Timestamp; + } // occupy a single slot + + /// @dev Location of the slot with TokenRateData + bytes32 private constant TOKEN_RATE_DATA_SLOT = + keccak256("TokenRateOracle.TOKEN_RATE_DATA_SLOT"); + /// @notice A bridge which can update oracle. address public immutable L2_ERC20_TOKEN_BRIDGE; @@ -25,16 +38,12 @@ contract TokenRateOracle is CrossDomainEnabled, ITokenRateOracle { /// @notice Decimals of the oracle response. uint8 public constant DECIMALS = 18; + /// @notice The minimum value that the token rate can be uint256 public constant MIN_TOKEN_RATE = 1_000_000_000_000_000; // 0.001 + /// @notice The maximum value that the token rate can be. uint256 public constant MAX_TOKEN_RATE = 1_000_000_000_000_000_000_000; // 1000 - /// @notice wstETH/stETH token rate. - uint256 public tokenRate; - - /// @notice L1 time when token rate was pushed. - uint256 public rateL1Timestamp; - /// @param messenger_ L2 messenger address being used for cross-chain communications /// @param l2ERC20TokenBridge_ the bridge address that has a right to updates oracle. /// @param l1TokenRatePusher_ An address of account on L1 that can update token rate. @@ -58,20 +67,20 @@ contract TokenRateOracle is CrossDomainEnabled, ITokenRateOracle { uint256 updatedAt_, uint80 answeredInRound_ ) { - uint80 roundId = uint80(rateL1Timestamp); + uint80 roundId = uint80(_loadTokenRateData().rateL1Timestamp); return ( roundId, - int256(tokenRate), - rateL1Timestamp, - rateL1Timestamp, + int256(uint(_loadTokenRateData().tokenRate)), + _loadTokenRateData().rateL1Timestamp, + _loadTokenRateData().rateL1Timestamp, roundId ); } /// @inheritdoc IChainlinkAggregatorInterface function latestAnswer() external view returns (int256) { - return int256(tokenRate); + return int256(uint(_loadTokenRateData().tokenRate)); } /// @inheritdoc IChainlinkAggregatorInterface @@ -82,12 +91,12 @@ contract TokenRateOracle is CrossDomainEnabled, ITokenRateOracle { /// @inheritdoc ITokenRateUpdatable function updateRate(uint256 tokenRate_, uint256 rateL1Timestamp_) external { - if (_isNoRightsToCall(msg.sender)) { + if (!_isAuthorized(msg.sender)) { revert ErrorNoRights(msg.sender); } - if (rateL1Timestamp_ < rateL1Timestamp) { - emit NewTokenRateOutdated(tokenRate_, rateL1Timestamp, rateL1Timestamp_); + if (rateL1Timestamp_ < _loadTokenRateData().rateL1Timestamp) { + emit NewTokenRateOutdated(tokenRate_, _loadTokenRateData().rateL1Timestamp, rateL1Timestamp_); return; } @@ -99,26 +108,38 @@ contract TokenRateOracle is CrossDomainEnabled, ITokenRateOracle { revert ErrorTokenRateIsOutOfRange(tokenRate_, rateL1Timestamp_); } - if (tokenRate_ == tokenRate && rateL1Timestamp_ == rateL1Timestamp) { + if (tokenRate_ == _loadTokenRateData().tokenRate && rateL1Timestamp_ == _loadTokenRateData().rateL1Timestamp) { return; } - tokenRate = tokenRate_; - rateL1Timestamp = rateL1Timestamp_; + _loadTokenRateData().tokenRate = uint192(tokenRate_); + _loadTokenRateData().rateL1Timestamp = uint64(rateL1Timestamp_); - emit RateUpdated(tokenRate, rateL1Timestamp); + emit RateUpdated(_loadTokenRateData().tokenRate, _loadTokenRateData().rateL1Timestamp); } /// @notice Returns flag that shows that token rate can be considered outdated. function isLikelyOutdated() external view returns (bool) { - return block.timestamp - rateL1Timestamp > TOKEN_RATE_OUTDATED_DELAY; + return block.timestamp - _loadTokenRateData().rateL1Timestamp > TOKEN_RATE_OUTDATED_DELAY; } - function _isNoRightsToCall(address caller_) internal view returns (bool) { + function _isAuthorized(address caller_) internal view returns (bool) { bool isCalledFromL1TokenRatePusher = caller_ == address(MESSENGER) && MESSENGER.xDomainMessageSender() == L1_TOKEN_RATE_PUSHER; bool isCalledFromERC20TokenRateBridge = caller_ == L2_ERC20_TOKEN_BRIDGE; - return !isCalledFromL1TokenRatePusher && !isCalledFromERC20TokenRateBridge; + return isCalledFromL1TokenRatePusher || isCalledFromERC20TokenRateBridge; + } + + /// @dev Returns the reference to the slot with TokenRateData struct + function _loadTokenRateData() + private + pure + returns (TokenRateData storage r) + { + bytes32 slot = TOKEN_RATE_DATA_SLOT; + assembly { + r.slot := slot + } } event RateUpdated(uint256 tokenRate_, uint256 rateL1Timestamp_); From 4ac5b442d7b90d9b61f4ca9d6eb8103d168d1333 Mon Sep 17 00:00:00 2001 From: Anton Kovalchuk Date: Thu, 18 Apr 2024 18:06:47 +0200 Subject: [PATCH 078/148] add transferShares and transferSharesFrom to rebasable token, unit tests --- contracts/token/ERC20RebasableBridged.sol | 59 +++- test/token/ERC20Rebasable.unit.test.ts | 342 +++++++++++++++++++--- 2 files changed, 354 insertions(+), 47 deletions(-) diff --git a/contracts/token/ERC20RebasableBridged.sol b/contracts/token/ERC20RebasableBridged.sol index 9b26a7e6..3bcc0d82 100644 --- a/contracts/token/ERC20RebasableBridged.sol +++ b/contracts/token/ERC20RebasableBridged.sol @@ -145,10 +145,7 @@ contract ERC20RebasableBridged is IERC20, IERC20Wrapper, IERC20BridgedShares, ER } /// @inheritdoc IERC20 - function approve(address spender_, uint256 amount_) - external - returns (bool) - { + function approve(address spender_, uint256 amount_) external returns (bool) { _approve(msg.sender, spender_, amount_); return true; } @@ -160,16 +157,56 @@ contract ERC20RebasableBridged is IERC20, IERC20Wrapper, IERC20BridgedShares, ER } /// @inheritdoc IERC20 - function transferFrom( - address from_, - address to_, - uint256 amount_ - ) external returns (bool) { + function transferFrom(address from_, address to_, uint256 amount_) external returns (bool) { _spendAllowance(from_, msg.sender, amount_); _transfer(from_, to_, amount_); return true; } + /// @notice Moves `sharesAmount_` token shares from the caller's account to the `recipient_` account. + /// + /// @return amount of transferred tokens. + /// Emits a `TransferShares` event. + /// Emits a `Transfer` event. + /// + /// Requirements: + /// + /// - `recipient_` cannot be the zero address. + /// - the caller must have at least `sharesAmount_` shares. + /// + /// @dev The `sharesAmount_` argument is the amount of shares, not tokens. + /// + function transferShares(address recipient_, uint256 sharesAmount_) external returns (uint256) { + _transferShares(msg.sender, recipient_, sharesAmount_); + uint256 tokensAmount = _getTokensByShares(sharesAmount_); + _emitTransferEvents(msg.sender, recipient_, tokensAmount, sharesAmount_); + return tokensAmount; + } + + /// @notice Moves `sharesAmount_` token shares from the `sender_` account to the `_recipient` account. + /// + /// @return amount of transferred tokens. + /// Emits a `TransferShares` event. + /// Emits a `Transfer` event. + /// + /// Requirements: + /// + /// - `sender_` and `_recipient` cannot be the zero addresses. + /// - `sender_` must have at least `sharesAmount_` shares. + /// - the caller must have allowance for `sender_`'s tokens of at least `_getTokensByShares(sharesAmount_)`. + /// + /// @dev The `_sharesAmount` argument is the amount of shares, not tokens. + /// + function transferSharesFrom( + address sender_, address recipient_, uint256 sharesAmount_ + ) external returns (uint256) { + uint256 tokensAmount = _getTokensByShares(sharesAmount_); + _spendAllowance(sender_, msg.sender, tokensAmount); + _transferShares(sender_, recipient_, sharesAmount_); + _emitTransferEvents(sender_, recipient_, tokensAmount, sharesAmount_); + return tokensAmount; + } + /// @notice Sets the name and the symbol of the tokens if they both are empty /// @param name_ The name of the token /// @param symbol_ The symbol of the token @@ -202,9 +239,7 @@ contract ERC20RebasableBridged is IERC20, IERC20Wrapper, IERC20BridgedShares, ER /// @param to_ An address of the recipient of the tokens /// @param amount_ An amount of tokens to transfer function _transfer( - address from_, - address to_, - uint256 amount_ + address from_, address to_, uint256 amount_ ) internal onlyNonZeroAccount(from_) onlyNonZeroAccount(to_) { uint256 sharesToTransfer = _getSharesByTokens(amount_); _transferShares(from_, to_, sharesToTransfer); diff --git a/test/token/ERC20Rebasable.unit.test.ts b/test/token/ERC20Rebasable.unit.test.ts index b403180e..ef4ff443 100644 --- a/test/token/ERC20Rebasable.unit.test.ts +++ b/test/token/ERC20Rebasable.unit.test.ts @@ -554,14 +554,137 @@ unit("ERC20RebasableBridged", ctxFactory) assert.equalBN(await rebasableProxied.totalSupply(), premintTokens); }) - .test("transferFrom() :: happy path", async (ctx) => { + + .test("transferShares() :: sender is zero address", async (ctx) => { + const { rebasableProxied } = ctx.contracts; + + const { + accounts: { zero, recipient }, + } = ctx; + await assert.revertsWith( + rebasableProxied.connect(zero).transferShares(recipient.address, wei`1 ether`), + "ErrorAccountIsZeroAddress()" + ); + }) + + .test("transferShares() :: recipient is zero address", async (ctx) => { + const { zero, holder } = ctx.accounts; + await assert.revertsWith( + ctx.contracts.rebasableProxied.connect(holder).transferShares(zero.address, wei`1 ether`), + "ErrorAccountIsZeroAddress()" + ); + }) + + .test("transferShares() :: zero balance", async (ctx) => { + const { rebasableProxied } = ctx.contracts; + const { premintTokens } = ctx.constants; + const { recipient, holder } = ctx.accounts; + + // validate balance before transfer + assert.equalBN(await rebasableProxied.balanceOf(holder.address), premintTokens); + + // transfer tokens + await rebasableProxied.connect(holder).transferShares(recipient.address, "0"); + + // validate balance stays same + assert.equalBN(await rebasableProxied.balanceOf(holder.address), premintTokens); + }) + + .test("transferShares() :: not enough balance", async (ctx) => { + const { rebasableProxied } = ctx.contracts; + const { premintTokens } = ctx.constants; + const { recipient, holder } = ctx.accounts; + + // validate balance before transfer + assert.equalBN(await rebasableProxied.balanceOf(holder.address), premintTokens); + + const amount = premintTokens.add(wei`1 ether`); + + // transfer tokens + await assert.revertsWith( + rebasableProxied.connect(holder).transferShares(recipient.address, amount), + "ErrorNotEnoughBalance()" + ); + }) + + .test("transferShares() :: happy path", async (ctx) => { + const { rebasableProxied } = ctx.contracts; + const { premintTokens } = ctx.constants; + const { recipient, holder } = ctx.accounts; + + // validate balance before transfer + assert.equalBN(await rebasableProxied.balanceOf(holder.address), premintTokens); + + // + const sharesToTransfer = wei`1 ether`; + const tokensToTransfer = await rebasableProxied.getTokensByShares(sharesToTransfer); + + // transfer tokens + const tx = await rebasableProxied + .connect(holder) + .transferShares(recipient.address, sharesToTransfer); + + // validate Transfer event was emitted + await assert.emits(rebasableProxied, tx, "Transfer", [ + holder.address, + recipient.address, + tokensToTransfer, + ]); + + await assert.emits(rebasableProxied, tx, "TransferShares", [ + holder.address, + recipient.address, + sharesToTransfer, + ]); + + // validate balance was updated + assert.equalBN( + await rebasableProxied.balanceOf(holder.address), + premintTokens.sub(tokensToTransfer) + ); + + // validate total supply stays same + assert.equalBN(await rebasableProxied.totalSupply(), premintTokens); + }) + + .test("transferFrom() :: not enough allowance", async (ctx) => { const { rebasableProxied } = ctx.contracts; const { premintTokens } = ctx.constants; const { recipient, holder, spender } = ctx.accounts; - const initialAllowance = wei`2 ether`; + const initialAllowance = wei`0.9 ether`; - // holder sets allowance for spender + // set allowance + await rebasableProxied.approve(recipient.address, initialAllowance); + + // validate allowance is set + assert.equalBN( + await rebasableProxied.allowance(holder.address, recipient.address), + initialAllowance + ); + + // validate balance before transfer + assert.equalBN(await rebasableProxied.balanceOf(holder.address), premintTokens); + + const amount = wei`1 ether`; + + // transfer tokens + await assert.revertsWith( + rebasableProxied + .connect(spender) + .transferFrom(holder.address, recipient.address, amount), + "ErrorNotEnoughAllowance()" + ); + }) + + .test("transferFrom() :: max allowance", async (ctx) => { + const { rebasableProxied } = ctx.contracts; + const { premintTokens } = ctx.constants; + const { recipient, holder, spender } = ctx.accounts; + + const initialAllowance = hre.ethers.constants.MaxUint256; + + // set allowance await rebasableProxied.approve(spender.address, initialAllowance); // validate allowance is set @@ -573,74 +696,174 @@ unit("ERC20RebasableBridged", ctxFactory) // validate balance before transfer assert.equalBN(await rebasableProxied.balanceOf(holder.address), premintTokens); - const amount = wei`1 ether`; + const tokensAmount = wei`1 ether`; + const sharesAmount = await rebasableProxied.getSharesByTokens(tokensAmount); + + const holderBalanceBefore = await rebasableProxied.balanceOf(holder.address); + const recepientBalanceBefore = await rebasableProxied.balanceOf(recipient.address); + + // transfer tokens + const tx = await rebasableProxied + .connect(spender) + .transferFrom(holder.address, recipient.address, tokensAmount); + + // validate Approval event was not emitted + await assert.notEmits(rebasableProxied, tx, "Approval"); + + // validate Transfer event was emitted + await assert.emits(rebasableProxied, tx, "Transfer", [ + holder.address, + recipient.address, + tokensAmount, + ]); + + await assert.emits(rebasableProxied, tx, "TransferShares", [ + holder.address, + recipient.address, + sharesAmount, + ]); + + // validate allowance wasn't changed + assert.equalBN( + await rebasableProxied.allowance(holder.address, spender.address), + initialAllowance + ); + + // validate holder balance updated + assert.equalBN( + await rebasableProxied.balanceOf(holder.address), + holderBalanceBefore.sub(tokensAmount) + ); + + // validate recipient balance updated + const recipientBalanceAfter = await rebasableProxied.balanceOf(recipient.address); + const balanceDelta = recipientBalanceAfter.sub(recepientBalanceBefore); + const oneTwoWei = wei.toBigNumber(tokensAmount).sub(balanceDelta); + assert.isTrue(oneTwoWei.gte(0) && oneTwoWei.lte(2)); + }) + + .test("transferFrom() :: happy path", async (ctx) => { + const { rebasableProxied } = ctx.contracts; + const { premintTokens } = ctx.constants; + const { recipient, holder, spender } = ctx.accounts; + + const tokensAmountToApprove = wei`2 ether`; + const tokensAmountToTransfer = wei`1 ether`; + const sharesAmountToApprove = await rebasableProxied.getSharesByTokens(tokensAmountToApprove); + const sharesAmountToTransfer = await rebasableProxied.getSharesByTokens(tokensAmountToTransfer); + + // holder sets allowance for spender + await rebasableProxied.approve(spender.address, tokensAmountToApprove); + + // validate allowance is set + assert.equalBN( + await rebasableProxied.allowance(holder.address, spender.address), + tokensAmountToApprove + ); + + // validate balance before transfer + assert.equalBN(await rebasableProxied.balanceOf(holder.address), premintTokens); + const recepientBalanceBefore = await rebasableProxied.balanceOf(recipient.address); const holderBalanceBefore = await rebasableProxied.balanceOf(holder.address); // transfer tokens const tx = await rebasableProxied .connect(spender) - .transferFrom(holder.address, recipient.address, amount); + .transferFrom(holder.address, recipient.address, tokensAmountToTransfer); // validate Approval event was emitted await assert.emits(rebasableProxied, tx, "Approval", [ holder.address, spender.address, - wei.toBigNumber(initialAllowance).sub(amount), + wei.toBigNumber(tokensAmountToApprove).sub(tokensAmountToTransfer), ]); // validate Transfer event was emitted await assert.emits(rebasableProxied, tx, "Transfer", [ holder.address, recipient.address, - amount, + tokensAmountToTransfer, ]); + await assert.emits(rebasableProxied, tx, "TransferShares", [ + holder.address, + recipient.address, + sharesAmountToTransfer, + ]); + // validate allowance updated assert.equalBN( await rebasableProxied.allowance(holder.address, spender.address), - wei.toBigNumber(initialAllowance).sub(amount) + wei.toBigNumber(tokensAmountToApprove).sub(tokensAmountToTransfer) ); // validate holder balance updated assert.equalBN( await rebasableProxied.balanceOf(holder.address), - holderBalanceBefore.sub(amount) + holderBalanceBefore.sub(tokensAmountToTransfer) ); - const recipientBalance = await rebasableProxied.balanceOf(recipient.address); - // validate recipient balance updated - assert.equalBN(BigNumber.from(amount).sub(recipientBalance), "1"); + const recipientBalanceAfter = await rebasableProxied.balanceOf(recipient.address); + const balanceDelta = recipientBalanceAfter.sub(recepientBalanceBefore); + const oneTwoWei = wei.toBigNumber(tokensAmountToTransfer).sub(balanceDelta); + assert.isTrue(oneTwoWei.gte(0) && oneTwoWei.lte(2)); }) - .test("transferFrom() :: max allowance", async (ctx) => { + .test("transferSharesFrom() :: not enough allowance", async (ctx) => { + const { rebasableProxied } = ctx.contracts; + const { recipient, holder, spender } = ctx.accounts; + + const sharesAmount = wei`1 ether`; + const tokenAllowance = await rebasableProxied.getTokensByShares(wei.toBigNumber(sharesAmount).sub(1000)); + + // set allowance + await rebasableProxied.approve(recipient.address, tokenAllowance); + + // validate allowance is set + assert.equalBN( + await rebasableProxied.allowance(holder.address, recipient.address), + tokenAllowance + ); + + // transfer tokens + await assert.revertsWith( + rebasableProxied + .connect(spender) + .transferSharesFrom(holder.address, recipient.address, sharesAmount), + "ErrorNotEnoughAllowance()" + ); + }) + + .test("transferSharesFrom() :: max allowance", async (ctx) => { const { rebasableProxied } = ctx.contracts; const { premintTokens } = ctx.constants; const { recipient, holder, spender } = ctx.accounts; - const initialAllowance = hre.ethers.constants.MaxUint256; + const tokenAllowance = hre.ethers.constants.MaxUint256; // set allowance - await rebasableProxied.approve(spender.address, initialAllowance); + await rebasableProxied.approve(spender.address, tokenAllowance); // validate allowance is set assert.equalBN( await rebasableProxied.allowance(holder.address, spender.address), - initialAllowance + tokenAllowance ); // validate balance before transfer assert.equalBN(await rebasableProxied.balanceOf(holder.address), premintTokens); - const amount = wei`1 ether`; + const sharesAmount = wei`1 ether`; + const tokensAmount = await rebasableProxied.getTokensByShares(sharesAmount); const holderBalanceBefore = await rebasableProxied.balanceOf(holder.address); // transfer tokens const tx = await rebasableProxied .connect(spender) - .transferFrom(holder.address, recipient.address, amount); + .transferSharesFrom(holder.address, recipient.address, sharesAmount); // validate Approval event was not emitted await assert.notEmits(rebasableProxied, tx, "Approval"); @@ -649,53 +872,102 @@ unit("ERC20RebasableBridged", ctxFactory) await assert.emits(rebasableProxied, tx, "Transfer", [ holder.address, recipient.address, - amount, + tokensAmount, + ]); + + await assert.emits(rebasableProxied, tx, "TransferShares", [ + holder.address, + recipient.address, + sharesAmount, ]); // validate allowance wasn't changed assert.equalBN( await rebasableProxied.allowance(holder.address, spender.address), - initialAllowance + tokenAllowance ); // validate holder balance updated assert.equalBN( await rebasableProxied.balanceOf(holder.address), - holderBalanceBefore.sub(amount) + holderBalanceBefore.sub(tokensAmount) ); // validate recipient balance updated - const recipientBalance = await rebasableProxied.balanceOf(recipient.address); - assert.equalBN(BigNumber.from(amount).sub(recipientBalance), "1"); + assert.equalBN( + await rebasableProxied.balanceOf(recipient.address), + tokensAmount + ); }) - .test("transferFrom() :: not enough allowance", async (ctx) => { + .test("transferSharesFrom() :: happy path", async (ctx) => { const { rebasableProxied } = ctx.contracts; const { premintTokens } = ctx.constants; const { recipient, holder, spender } = ctx.accounts; - const initialAllowance = wei`0.9 ether`; + const sharesAmountToApprove = wei`2 ether`; + const sharesAmountToTransfer = wei`1 ether`; - // set allowance - await rebasableProxied.approve(recipient.address, initialAllowance); + const tokensAmountToApprove = await rebasableProxied.getTokensByShares(sharesAmountToApprove); + const tokensAmountToTransfer = await rebasableProxied.getTokensByShares(sharesAmountToTransfer); + + + // holder sets allowance for spender + await rebasableProxied.approve(spender.address, tokensAmountToApprove); // validate allowance is set assert.equalBN( - await rebasableProxied.allowance(holder.address, recipient.address), - initialAllowance + await rebasableProxied.allowance(holder.address, spender.address), + tokensAmountToApprove ); // validate balance before transfer assert.equalBN(await rebasableProxied.balanceOf(holder.address), premintTokens); - const amount = wei`1 ether`; + + const holderBalanceBefore = await rebasableProxied.balanceOf(holder.address); // transfer tokens - await assert.revertsWith( - rebasableProxied - .connect(spender) - .transferFrom(holder.address, recipient.address, amount), - "ErrorNotEnoughAllowance()" + const tx = await rebasableProxied + .connect(spender) + .transferSharesFrom(holder.address, recipient.address, sharesAmountToTransfer); + + // validate Approval event was emitted + await assert.emits(rebasableProxied, tx, "Approval", [ + holder.address, + spender.address, + tokensAmountToApprove.sub(tokensAmountToTransfer), + ]); + + // validate Transfer event was emitted + await assert.emits(rebasableProxied, tx, "Transfer", [ + holder.address, + recipient.address, + tokensAmountToTransfer, + ]); + + await assert.emits(rebasableProxied, tx, "TransferShares", [ + holder.address, + recipient.address, + sharesAmountToTransfer, + ]); + + // validate allowance updated + assert.equalBN( + await rebasableProxied.allowance(holder.address, spender.address), + tokensAmountToApprove.sub(tokensAmountToTransfer) + ); + + // validate holder balance updated + assert.equalBN( + await rebasableProxied.balanceOf(holder.address), + holderBalanceBefore.sub(tokensAmountToTransfer) + ); + + // validate recipient balance updated + assert.equalBN( + await rebasableProxied.balanceOf(recipient.address), + tokensAmountToTransfer ); }) From 1cf43295f8fe5e5e0be03cc6f22c1bb45ba21a83 Mon Sep 17 00:00:00 2001 From: Anton Kovalchuk Date: Sun, 21 Apr 2024 15:12:12 +0400 Subject: [PATCH 079/148] add Versioned contract --- .../{token => lib}/UnstructuredRefStorage.sol | 0 .../{token => lib}/UnstructuredStorage.sol | 0 contracts/token/ERC20Bridged.sol | 2 +- contracts/token/ERC20BridgedPermit.sol | 26 ++++++-- contracts/token/ERC20Metadata.sol | 9 --- contracts/token/ERC20RebasableBridged.sol | 20 +++---- .../token/ERC20RebasableBridgedPermit.sol | 8 ++- contracts/token/PermitExtension.sol | 14 +---- contracts/utils/Versioned.sol | 60 +++++++++++++++++++ 9 files changed, 101 insertions(+), 38 deletions(-) rename contracts/{token => lib}/UnstructuredRefStorage.sol (100%) rename contracts/{token => lib}/UnstructuredStorage.sol (100%) create mode 100644 contracts/utils/Versioned.sol diff --git a/contracts/token/UnstructuredRefStorage.sol b/contracts/lib/UnstructuredRefStorage.sol similarity index 100% rename from contracts/token/UnstructuredRefStorage.sol rename to contracts/lib/UnstructuredRefStorage.sol diff --git a/contracts/token/UnstructuredStorage.sol b/contracts/lib/UnstructuredStorage.sol similarity index 100% rename from contracts/token/UnstructuredStorage.sol rename to contracts/lib/UnstructuredStorage.sol diff --git a/contracts/token/ERC20Bridged.sol b/contracts/token/ERC20Bridged.sol index a2f872ca..a8546c4b 100644 --- a/contracts/token/ERC20Bridged.sol +++ b/contracts/token/ERC20Bridged.sol @@ -56,7 +56,7 @@ contract ERC20Bridged is IERC20Bridged, ERC20Core, ERC20Metadata { /// @notice Sets the name and the symbol of the tokens if they both are empty /// @param name_ The name of the token /// @param symbol_ The symbol of the token - function initializeERC20Metadata(string memory name_, string memory symbol_) public { + function _initializeERC20Metadata(string memory name_, string memory symbol_) internal { _setERC20MetadataName(name_); _setERC20MetadataSymbol(symbol_); } diff --git a/contracts/token/ERC20BridgedPermit.sol b/contracts/token/ERC20BridgedPermit.sol index 55df9245..454ff169 100644 --- a/contracts/token/ERC20BridgedPermit.sol +++ b/contracts/token/ERC20BridgedPermit.sol @@ -5,9 +5,10 @@ pragma solidity 0.8.10; import {ERC20Bridged} from "./ERC20Bridged.sol"; import {PermitExtension} from "./PermitExtension.sol"; +import {Versioned} from "../utils/Versioned.sol"; /// @author kovalgek -contract ERC20BridgedPermit is ERC20Bridged, PermitExtension { +contract ERC20BridgedPermit is ERC20Bridged, PermitExtension, Versioned { /// @param name_ The name of the token /// @param symbol_ The symbol of the token @@ -26,13 +27,30 @@ contract ERC20BridgedPermit is ERC20Bridged, PermitExtension { { } - /// @notice Sets the name, the symbol and the version of the tokens if they are empty + /// @notice Initializes the contract from scratch. /// @param name_ The name of the token /// @param symbol_ The symbol of the token /// @param version_ The version of the token function initialize(string memory name_, string memory symbol_, string memory version_) external { - initializeERC20Metadata(name_, symbol_); - initializeEIP5267Metadata(name_, version_); + _initialize_v2(name_, symbol_, version_); + } + + /// @notice A function to finalize upgrade to v2 (from v1). + function finalizeUpgrade_v2(string memory name_, string memory version_) external { + // name and symbol from ERCMetadata already set up in storage, then it is defenetly was v1 + if (bytes(name()).length > 0 && bytes(symbol()).length > 0) { + _updateContractVersion(2); + _initializeEIP5267Metadata(name_, version_); + } else { + // metadata is epmty, incorrect function to call + // error? + } + } + + function _initialize_v2(string memory name_, string memory symbol_, string memory version_) internal { + _updateContractVersion(2); + _initializeERC20Metadata(name_, symbol_); + _initializeEIP5267Metadata(name_, version_); } /// @inheritdoc PermitExtension diff --git a/contracts/token/ERC20Metadata.sol b/contracts/token/ERC20Metadata.sol index 0e689466..465130a8 100644 --- a/contracts/token/ERC20Metadata.sol +++ b/contracts/token/ERC20Metadata.sol @@ -60,17 +60,11 @@ contract ERC20Metadata is IERC20Metadata { /// @dev Sets the name of the token. Might be called only when the name is empty function _setERC20MetadataName(string memory name_) internal { - if (bytes(name()).length > 0) { - revert ErrorNameAlreadySet(); - } _loadDynamicMetadata().name = name_; } /// @dev Sets the symbol of the token. Might be called only when the symbol is empty function _setERC20MetadataSymbol(string memory symbol_) internal { - if (bytes(symbol()).length > 0) { - revert ErrorSymbolAlreadySet(); - } _loadDynamicMetadata().symbol = symbol_; } @@ -85,7 +79,4 @@ contract ERC20Metadata is IERC20Metadata { r.slot := slot } } - - error ErrorNameAlreadySet(); - error ErrorSymbolAlreadySet(); } diff --git a/contracts/token/ERC20RebasableBridged.sol b/contracts/token/ERC20RebasableBridged.sol index 3bcc0d82..3bb6b4a1 100644 --- a/contracts/token/ERC20RebasableBridged.sol +++ b/contracts/token/ERC20RebasableBridged.sol @@ -8,8 +8,8 @@ import {SafeERC20} from "@openzeppelin/contracts/token/ERC20/utils/SafeERC20.sol import {IERC20Wrapper} from "./interfaces/IERC20Wrapper.sol"; import {ITokenRateOracle} from "../optimism/TokenRateOracle.sol"; import {ERC20Metadata} from "./ERC20Metadata.sol"; -import {UnstructuredRefStorage} from "./UnstructuredRefStorage.sol"; -import {UnstructuredStorage} from "./UnstructuredStorage.sol"; +import {UnstructuredRefStorage} from "../lib/UnstructuredRefStorage.sol"; +import {UnstructuredStorage} from "../lib/UnstructuredStorage.sol"; /// @author kovalgek /// @notice Extends the ERC20 functionality that allows the bridge to mint/burn shares @@ -207,14 +207,6 @@ contract ERC20RebasableBridged is IERC20, IERC20Wrapper, IERC20BridgedShares, ER return tokensAmount; } - /// @notice Sets the name and the symbol of the tokens if they both are empty - /// @param name_ The name of the token - /// @param symbol_ The symbol of the token - function initializeERC20Metadata(string memory name_, string memory symbol_) public { - _setERC20MetadataName(name_); - _setERC20MetadataSymbol(symbol_); - } - function _getTokenAllowance() internal pure returns (mapping(address => mapping(address => uint256)) storage) { return TOKEN_ALLOWANCE_POSITION.storageMapAddressMapAddressUint256(); } @@ -371,6 +363,14 @@ contract ERC20RebasableBridged is IERC20, IERC20Wrapper, IERC20BridgedShares, ER emit TransferShares(_from, _to, _sharesAmount); } + /// @notice Sets the name and the symbol of the tokens if they both are empty + /// @param name_ The name of the token + /// @param symbol_ The symbol of the token + function _initializeERC20Metadata(string memory name_, string memory symbol_) internal { + _setERC20MetadataName(name_); + _setERC20MetadataSymbol(symbol_); + } + /// @dev validates that account_ is not zero address modifier onlyNonZeroAccount(address account_) { if (account_ == address(0)) { diff --git a/contracts/token/ERC20RebasableBridgedPermit.sol b/contracts/token/ERC20RebasableBridgedPermit.sol index 2f781333..db0fb10e 100644 --- a/contracts/token/ERC20RebasableBridgedPermit.sol +++ b/contracts/token/ERC20RebasableBridgedPermit.sol @@ -5,9 +5,10 @@ pragma solidity 0.8.10; import {ERC20RebasableBridged} from "./ERC20RebasableBridged.sol"; import {PermitExtension} from "./PermitExtension.sol"; +import {Versioned} from "../utils/Versioned.sol"; /// @author kovalgek -contract ERC20RebasableBridgedPermit is ERC20RebasableBridged, PermitExtension { +contract ERC20RebasableBridgedPermit is ERC20RebasableBridged, PermitExtension, Versioned { /// @param name_ The name of the token /// @param symbol_ The symbol of the token @@ -35,8 +36,9 @@ contract ERC20RebasableBridgedPermit is ERC20RebasableBridged, PermitExtension { /// @param symbol_ The symbol of the token /// @param version_ The version of the token function initialize(string memory name_, string memory symbol_, string memory version_) external { - initializeERC20Metadata(name_, symbol_); - initializeEIP5267Metadata(name_, version_); + _initializeContractVersionTo(1); + _initializeERC20Metadata(name_, symbol_); + _initializeEIP5267Metadata(name_, version_); } /// @inheritdoc PermitExtension diff --git a/contracts/token/PermitExtension.sol b/contracts/token/PermitExtension.sol index 3850c42d..8dc9b6a6 100644 --- a/contracts/token/PermitExtension.sol +++ b/contracts/token/PermitExtension.sol @@ -3,7 +3,7 @@ pragma solidity 0.8.10; -import {UnstructuredRefStorage} from "./UnstructuredRefStorage.sol"; +import {UnstructuredRefStorage} from "../lib//UnstructuredRefStorage.sol"; import {EIP712} from "@openzeppelin/contracts/utils/cryptography/draft-EIP712.sol"; import {IERC2612} from "@openzeppelin/contracts/interfaces/draft-IERC2612.sol"; import {SignatureChecker} from "../lib/SignatureChecker.sol"; @@ -33,13 +33,13 @@ abstract contract PermitExtension is IERC2612, EIP712 { /// @param name_ The name of the token /// @param version_ The current major version of the signing domain (aka token version) constructor(string memory name_, string memory version_) EIP712(name_, version_) { - initializeEIP5267Metadata(name_, version_); + _initializeEIP5267Metadata(name_, version_); } /// @notice Sets the name and the version of the tokens if they both are empty /// @param name_ The name of the token /// @param version_ The version of the token - function initializeEIP5267Metadata(string memory name_, string memory version_) public { + function _initializeEIP5267Metadata(string memory name_, string memory version_) internal { _setEIP5267MetadataName(name_); _setEIP5267MetadataVersion(version_); } @@ -142,22 +142,14 @@ abstract contract PermitExtension is IERC2612, EIP712 { /// @dev Sets the name of the token. Might be called only when the name is empty function _setEIP5267MetadataName(string memory name_) internal { - if (bytes(_loadEIP5267Metadata().name).length > 0) { - revert ErrorEIP5267NameAlreadySet(); - } _loadEIP5267Metadata().name = name_; } /// @dev Sets the version of the token. Might be called only when the version is empty function _setEIP5267MetadataVersion(string memory version_) internal { - if (bytes(_loadEIP5267Metadata().version).length > 0) { - revert ErrorEIP5267VersionAlreadySet(); - } _loadEIP5267Metadata().version = version_; } error ErrorInvalidSignature(); error ErrorDeadlineExpired(); - error ErrorEIP5267NameAlreadySet(); - error ErrorEIP5267VersionAlreadySet(); } diff --git a/contracts/utils/Versioned.sol b/contracts/utils/Versioned.sol new file mode 100644 index 00000000..d00ea2c5 --- /dev/null +++ b/contracts/utils/Versioned.sol @@ -0,0 +1,60 @@ +// SPDX-FileCopyrightText: 2022 Lido +// SPDX-License-Identifier: GPL-3.0 + +pragma solidity 0.8.10; + +import {UnstructuredStorage} from "../lib//UnstructuredStorage.sol"; + +contract Versioned { + using UnstructuredStorage for bytes32; + + event ContractVersionSet(uint256 version); + + error NonZeroContractVersionOnInit(); + error InvalidContractVersionIncrement(); + error UnexpectedContractVersion(uint256 expected, uint256 received); + + /// @dev Storage slot: uint256 version + /// Version of the initialized contract storage. + /// The version stored in CONTRACT_VERSION_POSITION equals to: + /// - 0 right after the deployment, before an initializer is invoked (and only at that moment); + /// - N after calling initialize(), where N is the initially deployed contract version; + /// - N after upgrading contract by calling finalizeUpgrade_vN(). + bytes32 internal constant CONTRACT_VERSION_POSITION = keccak256("lido.Versioned.contractVersion"); + + uint256 internal constant PETRIFIED_VERSION_MARK = type(uint256).max; + + constructor() { + // lock version in the implementation's storage to prevent initialization + CONTRACT_VERSION_POSITION.setStorageUint256(PETRIFIED_VERSION_MARK); + } + + /// @notice Returns the current contract version. + function getContractVersion() public view returns (uint256) { + return CONTRACT_VERSION_POSITION.getStorageUint256(); + } + + function _checkContractVersion(uint256 version) internal view { + uint256 expectedVersion = getContractVersion(); + if (version != expectedVersion) { + revert UnexpectedContractVersion(expectedVersion, version); + } + } + + /// @dev Sets the contract version to N. Should be called from the initialize() function. + function _initializeContractVersionTo(uint256 version) internal { + if (getContractVersion() != 0) revert NonZeroContractVersionOnInit(); + _setContractVersion(version); + } + + /// @dev Updates the contract version. Should be called from a finalizeUpgrade_vN() function. + function _updateContractVersion(uint256 newVersion) internal { + if (newVersion != getContractVersion() + 1) revert InvalidContractVersionIncrement(); + _setContractVersion(newVersion); + } + + function _setContractVersion(uint256 version) private { + CONTRACT_VERSION_POSITION.setStorageUint256(version); + emit ContractVersionSet(version); + } +} From 16b648414b3ab966a84b58a3869f4a0a4ae866a9 Mon Sep 17 00:00:00 2001 From: Anton Kovalchuk Date: Sun, 21 Apr 2024 16:08:20 +0400 Subject: [PATCH 080/148] disable initializers --- contracts/token/ERC20BridgedPermit.sol | 28 +++++++++++-------- .../token/ERC20RebasableBridgedPermit.sol | 6 ++-- 2 files changed, 20 insertions(+), 14 deletions(-) diff --git a/contracts/token/ERC20BridgedPermit.sol b/contracts/token/ERC20BridgedPermit.sol index 454ff169..5f8b8a4a 100644 --- a/contracts/token/ERC20BridgedPermit.sol +++ b/contracts/token/ERC20BridgedPermit.sol @@ -6,9 +6,10 @@ pragma solidity 0.8.10; import {ERC20Bridged} from "./ERC20Bridged.sol"; import {PermitExtension} from "./PermitExtension.sol"; import {Versioned} from "../utils/Versioned.sol"; +import {Initializable} from "@openzeppelin/contracts/proxy/utils/Initializable.sol"; /// @author kovalgek -contract ERC20BridgedPermit is ERC20Bridged, PermitExtension, Versioned { +contract ERC20BridgedPermit is ERC20Bridged, PermitExtension, Versioned, Initializable { /// @param name_ The name of the token /// @param symbol_ The symbol of the token @@ -25,31 +26,32 @@ contract ERC20BridgedPermit is ERC20Bridged, PermitExtension, Versioned { ERC20Bridged(name_, symbol_, decimals_, bridge_) PermitExtension(name_, version_) { + _disableInitializers(); } /// @notice Initializes the contract from scratch. /// @param name_ The name of the token /// @param symbol_ The symbol of the token /// @param version_ The version of the token - function initialize(string memory name_, string memory symbol_, string memory version_) external { - _initialize_v2(name_, symbol_, version_); + function initialize(string memory name_, string memory symbol_, string memory version_) external initializer { + _initializeERC20Metadata(name_, symbol_); + _initialize_v2(name_, version_); } /// @notice A function to finalize upgrade to v2 (from v1). function finalizeUpgrade_v2(string memory name_, string memory version_) external { - // name and symbol from ERCMetadata already set up in storage, then it is defenetly was v1 - if (bytes(name()).length > 0 && bytes(symbol()).length > 0) { - _updateContractVersion(2); - _initializeEIP5267Metadata(name_, version_); - } else { - // metadata is epmty, incorrect function to call - // error? + _checkContractVersion(0); + + // check if name and symbol from ERCMetadata wasn't set up in storage, + // then current contract is not in v1 + if (bytes(name()).length == 0 || bytes(symbol()).length == 0) { + revert ErrorFailedToUpgrade(); } + _initialize_v2(name_, version_); } - function _initialize_v2(string memory name_, string memory symbol_, string memory version_) internal { + function _initialize_v2(string memory name_, string memory version_) internal { _updateContractVersion(2); - _initializeERC20Metadata(name_, symbol_); _initializeEIP5267Metadata(name_, version_); } @@ -57,4 +59,6 @@ contract ERC20BridgedPermit is ERC20Bridged, PermitExtension, Versioned { function _permitAccepted(address owner_, address spender_, uint256 amount_) internal override { _approve(owner_, spender_, amount_); } + + error ErrorFailedToUpgrade(); } diff --git a/contracts/token/ERC20RebasableBridgedPermit.sol b/contracts/token/ERC20RebasableBridgedPermit.sol index db0fb10e..0e4698d5 100644 --- a/contracts/token/ERC20RebasableBridgedPermit.sol +++ b/contracts/token/ERC20RebasableBridgedPermit.sol @@ -6,9 +6,10 @@ pragma solidity 0.8.10; import {ERC20RebasableBridged} from "./ERC20RebasableBridged.sol"; import {PermitExtension} from "./PermitExtension.sol"; import {Versioned} from "../utils/Versioned.sol"; +import {Initializable} from "@openzeppelin/contracts/proxy/utils/Initializable.sol"; /// @author kovalgek -contract ERC20RebasableBridgedPermit is ERC20RebasableBridged, PermitExtension, Versioned { +contract ERC20RebasableBridgedPermit is ERC20RebasableBridged, PermitExtension, Versioned, Initializable { /// @param name_ The name of the token /// @param symbol_ The symbol of the token @@ -29,13 +30,14 @@ contract ERC20RebasableBridgedPermit is ERC20RebasableBridged, PermitExtension, ERC20RebasableBridged(name_, symbol_, decimals_, tokenToWrapFrom_, tokenRateOracle_, bridge_) PermitExtension(name_, version_) { + _disableInitializers(); } /// @notice Sets the name, the symbol and the version of the tokens if they are empty /// @param name_ The name of the token /// @param symbol_ The symbol of the token /// @param version_ The version of the token - function initialize(string memory name_, string memory symbol_, string memory version_) external { + function initialize(string memory name_, string memory symbol_, string memory version_) external initializer { _initializeContractVersionTo(1); _initializeERC20Metadata(name_, symbol_); _initializeEIP5267Metadata(name_, version_); From b6dd6a210feacbd9098314224c06767298f7da2a Mon Sep 17 00:00:00 2001 From: Anton Kovalchuk Date: Sun, 21 Apr 2024 19:38:45 +0400 Subject: [PATCH 081/148] disable initializers for bridges --- contracts/BridgingManager.sol | 2 +- contracts/optimism/L1LidoTokensBridge.sol | 16 +++++++++++++++- .../optimism/L2ERC20ExtendedTokensBridge.sol | 13 +++++++++++-- contracts/token/ERC20RebasableBridgedPermit.sol | 2 +- 4 files changed, 28 insertions(+), 5 deletions(-) diff --git a/contracts/BridgingManager.sol b/contracts/BridgingManager.sol index abffef4d..132c5df7 100644 --- a/contracts/BridgingManager.sol +++ b/contracts/BridgingManager.sol @@ -34,7 +34,7 @@ contract BridgingManager is AccessControl { /// @notice Initializes the contract to grant DEFAULT_ADMIN_ROLE to the admin_ address /// @dev This method might be called only once /// @param admin_ Address of the account to grant the DEFAULT_ADMIN_ROLE - function initialize(address admin_) external { + function _initialize(address admin_) internal { State storage s = _loadState(); if (s.isInitialized) { revert ErrorAlreadyInitialized(); diff --git a/contracts/optimism/L1LidoTokensBridge.sol b/contracts/optimism/L1LidoTokensBridge.sol index d749e034..49ce988c 100644 --- a/contracts/optimism/L1LidoTokensBridge.sol +++ b/contracts/optimism/L1LidoTokensBridge.sol @@ -4,6 +4,7 @@ pragma solidity 0.8.10; import {L1ERC20ExtendedTokensBridge} from "./L1ERC20ExtendedTokensBridge.sol"; +import {Initializable} from "@openzeppelin/contracts/proxy/utils/Initializable.sol"; /// @author kovalgek /// @notice A subset of wstETH token interface of core LIDO protocol. @@ -15,8 +16,14 @@ interface IERC20WstETH { /// @author kovalgek /// @notice Hides wstETH concept from other contracts to keep `L1ERC20ExtendedTokensBridge` reusable. -contract L1LidoTokensBridge is L1ERC20ExtendedTokensBridge { +contract L1LidoTokensBridge is L1ERC20ExtendedTokensBridge, Initializable { + /// @param messenger_ L1 messenger address being used for cross-chain communications + /// @param l2TokenBridge_ Address of the corresponding L2 bridge + /// @param l1TokenNonRebasable_ Address of the bridged token in the L1 chain + /// @param l1TokenRebasable_ Address of the bridged token in the L1 chain + /// @param l2TokenNonRebasable_ Address of the token minted on the L2 chain when token bridged + /// @param l2TokenRebasable_ Address of the token minted on the L2 chain when token bridged constructor( address messenger_, address l2TokenBridge_, @@ -32,6 +39,13 @@ contract L1LidoTokensBridge is L1ERC20ExtendedTokensBridge { l2TokenNonRebasable_, l2TokenRebasable_ ) { + _disableInitializers(); + } + + /// @notice Initializes the contract from scratch. + /// @param admin_ Address of the account to grant the DEFAULT_ADMIN_ROLE + function initialize(address admin_) external initializer { + _initialize(admin_); } function tokenRate() override internal view returns (uint256) { diff --git a/contracts/optimism/L2ERC20ExtendedTokensBridge.sol b/contracts/optimism/L2ERC20ExtendedTokensBridge.sol index 13253ab0..75dcef66 100644 --- a/contracts/optimism/L2ERC20ExtendedTokensBridge.sol +++ b/contracts/optimism/L2ERC20ExtendedTokensBridge.sol @@ -16,6 +16,7 @@ import {BridgingManager} from "../BridgingManager.sol"; import {RebasableAndNonRebasableTokens} from "./RebasableAndNonRebasableTokens.sol"; import {CrossDomainEnabled} from "./CrossDomainEnabled.sol"; import {DepositDataCodec} from "../lib/DepositDataCodec.sol"; +import {Initializable} from "@openzeppelin/contracts/proxy/utils/Initializable.sol"; /// @author psirex, kovalgek /// @notice The L2 token bridge works with the L1 token bridge to enable ERC20 token bridging @@ -27,7 +28,8 @@ contract L2ERC20ExtendedTokensBridge is IL2ERC20Bridge, BridgingManager, RebasableAndNonRebasableTokens, - CrossDomainEnabled + CrossDomainEnabled, + Initializable { using SafeERC20 for IERC20; @@ -46,13 +48,20 @@ contract L2ERC20ExtendedTokensBridge is address l1TokenRebasable_, address l2TokenNonRebasable_, address l2TokenRebasable_ - ) CrossDomainEnabled(messenger_) RebasableAndNonRebasableTokens( + ) CrossDomainEnabled(messenger_) RebasableAndNonRebasableTokens ( l1TokenNonRebasable_, l1TokenRebasable_, l2TokenNonRebasable_, l2TokenRebasable_ ) { L1_TOKEN_BRIDGE = l1TokenBridge_; + _disableInitializers(); + } + + /// @notice Initializes the contract from scratch. + /// @param admin_ Address of the account to grant the DEFAULT_ADMIN_ROLE + function initialize(address admin_) external initializer { + _initialize(admin_); } /// @inheritdoc IL2ERC20Bridge diff --git a/contracts/token/ERC20RebasableBridgedPermit.sol b/contracts/token/ERC20RebasableBridgedPermit.sol index 0e4698d5..bfd2dcb6 100644 --- a/contracts/token/ERC20RebasableBridgedPermit.sol +++ b/contracts/token/ERC20RebasableBridgedPermit.sol @@ -33,7 +33,7 @@ contract ERC20RebasableBridgedPermit is ERC20RebasableBridged, PermitExtension, _disableInitializers(); } - /// @notice Sets the name, the symbol and the version of the tokens if they are empty + /// @notice Initializes the contract from scratch. /// @param name_ The name of the token /// @param symbol_ The symbol of the token /// @param version_ The version of the token From f310a3e3cea73d42b5272588767cce877f34a4f5 Mon Sep 17 00:00:00 2001 From: Anton Kovalchuk Date: Sun, 21 Apr 2024 20:18:47 +0400 Subject: [PATCH 082/148] rename variables --- .../optimism/L1ERC20ExtendedTokensBridge.sol | 30 +++++++++---------- .../optimism/L2ERC20ExtendedTokensBridge.sol | 9 +++--- contracts/optimism/README.md | 4 +-- .../RebasableAndNonRebasableTokens.sol | 12 ++------ contracts/token/ERC20RebasableBridged.sol | 1 - 5 files changed, 25 insertions(+), 31 deletions(-) diff --git a/contracts/optimism/L1ERC20ExtendedTokensBridge.sol b/contracts/optimism/L1ERC20ExtendedTokensBridge.sol index 90c6eca2..40cc03b9 100644 --- a/contracts/optimism/L1ERC20ExtendedTokensBridge.sol +++ b/contracts/optimism/L1ERC20ExtendedTokensBridge.sol @@ -73,11 +73,7 @@ abstract contract L1ERC20ExtendedTokensBridge is if (Address.isContract(msg.sender)) { revert ErrorSenderNotEOA(); } - bytes memory encodedDepositData = DepositDataCodec.encodeDepositData(DepositDataCodec.DepositData({ - rate: uint96(tokenRate()), - timestamp: uint40(block.timestamp), - data: data_ - })); + bytes memory encodedDepositData = _encodeInputDepositData(data_); _depositERC20To(l1Token_, l2Token_, msg.sender, msg.sender, amount_, l2Gas_, encodedDepositData); emit ERC20DepositInitiated(l1Token_, l2Token_, msg.sender, msg.sender, amount_, encodedDepositData); } @@ -96,11 +92,7 @@ abstract contract L1ERC20ExtendedTokensBridge is onlyNonZeroAccount(to_) onlySupportedL1L2TokensPair(l1Token_, l2Token_) { - bytes memory encodedDepositData = DepositDataCodec.encodeDepositData(DepositDataCodec.DepositData({ - rate: uint96(tokenRate()), - timestamp: uint40(block.timestamp), - data: data_ - })); + bytes memory encodedDepositData = _encodeInputDepositData(data_); _depositERC20To(l1Token_, l2Token_, msg.sender, to_, amount_, l2Gas_, encodedDepositData); emit ERC20DepositInitiated(l1Token_, l2Token_, msg.sender, to_, amount_, encodedDepositData); } @@ -119,11 +111,11 @@ abstract contract L1ERC20ExtendedTokensBridge is onlyFromCrossDomainAccount(L2_TOKEN_BRIDGE) onlySupportedL1L2TokensPair(l1Token_, l2Token_) { - uint256 amountToWithdraw = (l1Token_ == L1_TOKEN_REBASABLE && amount_ != 0) ? + uint256 withdrawnL1TokenAmount = (l1Token_ == L1_TOKEN_REBASABLE && amount_ != 0) ? IERC20Wrapper(L1_TOKEN_NON_REBASABLE).unwrap(amount_) : amount_; - IERC20(l1Token_).safeTransfer(to_, amountToWithdraw); - emit ERC20WithdrawalFinalized(l1Token_, l2Token_, from_, to_, amountToWithdraw, data_); + IERC20(l1Token_).safeTransfer(to_, withdrawnL1TokenAmount); + emit ERC20WithdrawalFinalized(l1Token_, l2Token_, from_, to_, withdrawnL1TokenAmount, data_); } /// @dev Performs the logic for deposits by informing the L2 token bridge contract @@ -145,11 +137,11 @@ abstract contract L1ERC20ExtendedTokensBridge is uint32 l2Gas_, bytes memory encodedDepositData_ ) internal { - uint256 amountToDeposit = _transferToBridge(l1Token_, from_, amount_); + uint256 nonRebaseableAmountToDeposit = _transferToBridge(l1Token_, from_, amount_); bytes memory message = abi.encodeWithSelector( IL2ERC20Bridge.finalizeDeposit.selector, - l1Token_, l2Token_, from_, to_, amountToDeposit, encodedDepositData_ + l1Token_, l2Token_, from_, to_, nonRebaseableAmountToDeposit, encodedDepositData_ ); sendCrossDomainMessage(L2_TOKEN_BRIDGE, l2Gas_, message); @@ -175,5 +167,13 @@ abstract contract L1ERC20ExtendedTokensBridge is return amount_; } + function _encodeInputDepositData(bytes calldata data_) internal view returns (bytes memory) { + return DepositDataCodec.encodeDepositData(DepositDataCodec.DepositData({ + rate: uint96(tokenRate()), + timestamp: uint40(block.timestamp), + data: data_ + })); + } + error ErrorSenderNotEOA(); } diff --git a/contracts/optimism/L2ERC20ExtendedTokensBridge.sol b/contracts/optimism/L2ERC20ExtendedTokensBridge.sol index 75dcef66..baf7146f 100644 --- a/contracts/optimism/L2ERC20ExtendedTokensBridge.sol +++ b/contracts/optimism/L2ERC20ExtendedTokensBridge.sol @@ -95,6 +95,7 @@ contract L2ERC20ExtendedTokensBridge is bytes calldata data_ ) external whenWithdrawalsEnabled + onlyNonZeroAccount(to_) onlySupportedL2Token(l2Token_) { _withdrawTo(l2Token_, msg.sender, to_, amount_, l1Gas_, data_); @@ -119,8 +120,8 @@ contract L2ERC20ExtendedTokensBridge is ITokenRateUpdatable tokenRateOracle = ERC20RebasableBridged(L2_TOKEN_REBASABLE).TOKEN_RATE_ORACLE(); tokenRateOracle.updateRate(depositData.rate, depositData.timestamp); - uint256 depositedAmount = _mintTokens(l2Token_, to_, amount_); - emit DepositFinalized(l1Token_, l2Token_, from_, to_, depositedAmount, depositData.data); + uint256 depositedL2TokenAmount = _mintTokens(l2Token_, to_, amount_); + emit DepositFinalized(l1Token_, l2Token_, from_, to_, depositedL2TokenAmount, depositData.data); } /// @notice Performs the logic for withdrawals by burning the token and informing @@ -141,11 +142,11 @@ contract L2ERC20ExtendedTokensBridge is uint32 l1Gas_, bytes calldata data_ ) internal { - uint256 amountToWithdraw = _burnTokens(l2Token_, from_, amount_); + uint256 nonRebaseableAmountToWithdraw = _burnTokens(l2Token_, from_, amount_); bytes memory message = abi.encodeWithSelector( IL1ERC20Bridge.finalizeERC20Withdrawal.selector, - _getL1Token(l2Token_), l2Token_, from_, to_, amountToWithdraw, data_ + _getL1Token(l2Token_), l2Token_, from_, to_, nonRebaseableAmountToWithdraw, data_ ); sendCrossDomainMessage(L1_TOKEN_BRIDGE, l1Gas_, message); } diff --git a/contracts/optimism/README.md b/contracts/optimism/README.md index 0ae17d5f..ff229220 100644 --- a/contracts/optimism/README.md +++ b/contracts/optimism/README.md @@ -178,9 +178,9 @@ The contract keeps the addresses of L1/L2 tokens used in the bridging: ### Modifiers -#### `onlySupportedL1Token(address l1Token_)` +#### `onlySupportedL1L2TokensPair(address l1Token_, address l2Token_)` -Validates that passed `l1Token_` is supported by the bridge. Reverts with error `ErrorUnsupportedL1Token()` when addresses mismatch. +Validates that passed `l1Token_` and `l2Token_` are supported by the bridge. Reverts with error `ErrorUnsupportedL1L2TokensPair()` when addresses mismatch. #### `onlySupportedL2Token(address l2Token_)` diff --git a/contracts/optimism/RebasableAndNonRebasableTokens.sol b/contracts/optimism/RebasableAndNonRebasableTokens.sol index 61ead668..6e35fbc2 100644 --- a/contracts/optimism/RebasableAndNonRebasableTokens.sol +++ b/contracts/optimism/RebasableAndNonRebasableTokens.sol @@ -43,14 +43,6 @@ contract RebasableAndNonRebasableTokens { _; } - /// @dev Validates that passed l1Token_ is supported by the bridge - modifier onlySupportedL1Token(address l1Token_) { - if (l1Token_ != L1_TOKEN_NON_REBASABLE && l1Token_ != L1_TOKEN_REBASABLE) { - revert ErrorUnsupportedL1Token(l1Token_); - } - _; - } - /// @dev Validates that passed l2Token_ is supported by the bridge modifier onlySupportedL2Token(address l2Token_) { if (l2Token_ != L2_TOKEN_NON_REBASABLE && l2Token_ != L2_TOKEN_REBASABLE) { @@ -74,7 +66,9 @@ contract RebasableAndNonRebasableTokens { } function _getL1Token(address l2Token_) internal view returns (address) { - return (l2Token_ == L2_TOKEN_REBASABLE) ? L1_TOKEN_REBASABLE : L1_TOKEN_NON_REBASABLE; + if (l2Token_ == L2_TOKEN_NON_REBASABLE) { return L1_TOKEN_NON_REBASABLE; } + if (l2Token_ == L2_TOKEN_REBASABLE) { return L1_TOKEN_REBASABLE; } + revert ErrorUnsupportedL2Token(l2Token_); } error ErrorUnsupportedL1Token(address l1Token); diff --git a/contracts/token/ERC20RebasableBridged.sol b/contracts/token/ERC20RebasableBridged.sol index 3bb6b4a1..0f0d5d0a 100644 --- a/contracts/token/ERC20RebasableBridged.sol +++ b/contracts/token/ERC20RebasableBridged.sol @@ -342,7 +342,6 @@ contract ERC20RebasableBridged is IERC20, IERC20Wrapper, IERC20BridgedShares, ER address recipient_, uint256 sharesAmount_ ) internal onlyNonZeroAccount(sender_) onlyNonZeroAccount(recipient_) { - if (recipient_ == address(this)) revert ErrorTrasferToRebasableContract(); uint256 currentSenderShares = _getShares()[sender_]; From 1b8cd59f2d29792b779ef97be08a8828ee5507bf Mon Sep 17 00:00:00 2001 From: Anton Kovalchuk Date: Sun, 21 Apr 2024 22:15:09 +0400 Subject: [PATCH 083/148] fix tests --- contracts/BridgingManager.sol | 5 - contracts/token/ERC20BridgedPermit.sol | 2 +- contracts/utils/Versioned.sol | 2 +- .../optimism.integration.test.ts | 4 +- .../BridgingManager.unit.test.ts | 126 +++++++++----- .../L2ERC20ExtendedTokensBridge.unit.test.ts | 5 +- test/optimism/managing-proxy.e2e.test.ts | 4 +- test/token/ERC20Bridged.unit.test.ts | 75 +++++---- test/token/ERC20Rebasable.unit.test.ts | 156 ++++++++++-------- utils/optimism/deploymentAllFromScratch.ts | 31 ++-- .../deploymentBridgesAndRebasableToken.ts | 12 +- .../optimism/deploymentNewImplementations.ts | 10 +- utils/optimism/testing.ts | 8 +- 13 files changed, 251 insertions(+), 189 deletions(-) diff --git a/contracts/BridgingManager.sol b/contracts/BridgingManager.sol index 132c5df7..573e9000 100644 --- a/contracts/BridgingManager.sol +++ b/contracts/BridgingManager.sol @@ -35,12 +35,7 @@ contract BridgingManager is AccessControl { /// @dev This method might be called only once /// @param admin_ Address of the account to grant the DEFAULT_ADMIN_ROLE function _initialize(address admin_) internal { - State storage s = _loadState(); - if (s.isInitialized) { - revert ErrorAlreadyInitialized(); - } _setupRole(DEFAULT_ADMIN_ROLE, admin_); - s.isInitialized = true; emit Initialized(admin_); } diff --git a/contracts/token/ERC20BridgedPermit.sol b/contracts/token/ERC20BridgedPermit.sol index 5f8b8a4a..9bf241d5 100644 --- a/contracts/token/ERC20BridgedPermit.sol +++ b/contracts/token/ERC20BridgedPermit.sol @@ -51,7 +51,7 @@ contract ERC20BridgedPermit is ERC20Bridged, PermitExtension, Versioned, Initial } function _initialize_v2(string memory name_, string memory version_) internal { - _updateContractVersion(2); + _setContractVersion(2); _initializeEIP5267Metadata(name_, version_); } diff --git a/contracts/utils/Versioned.sol b/contracts/utils/Versioned.sol index d00ea2c5..59c16c7a 100644 --- a/contracts/utils/Versioned.sol +++ b/contracts/utils/Versioned.sol @@ -53,7 +53,7 @@ contract Versioned { _setContractVersion(newVersion); } - function _setContractVersion(uint256 version) private { + function _setContractVersion(uint256 version) internal { CONTRACT_VERSION_POSITION.setStorageUint256(version); emit ContractVersionSet(version); } diff --git a/test/bridge-executor/optimism.integration.test.ts b/test/bridge-executor/optimism.integration.test.ts index ee290cb6..655168ad 100644 --- a/test/bridge-executor/optimism.integration.test.ts +++ b/test/bridge-executor/optimism.integration.test.ts @@ -4,7 +4,7 @@ import { L2ERC20ExtendedTokensBridge__factory, OssifiableProxy__factory, OptimismBridgeExecutor__factory, - ERC20Bridged__factory, + ERC20BridgedPermit__factory, ERC20WrapperStub__factory, } from "../../typechain"; import { wei } from "../../utils/wei"; @@ -262,7 +262,7 @@ async function ctxFactory() { await optDeployScript.run(); - const l2Token = ERC20Bridged__factory.connect( + const l2Token = ERC20BridgedPermit__factory.connect( optDeployScript.tokenProxyAddress, l2Deployer ); diff --git a/test/bridging-manager/BridgingManager.unit.test.ts b/test/bridging-manager/BridgingManager.unit.test.ts index a74566bb..03f49ab4 100644 --- a/test/bridging-manager/BridgingManager.unit.test.ts +++ b/test/bridging-manager/BridgingManager.unit.test.ts @@ -2,9 +2,14 @@ import hre from "hardhat"; import { BridgingManager__factory, OssifiableProxy__factory, + L1LidoTokensBridge__factory, + ERC20WrapperStub__factory, + ERC20BridgedStub__factory, + CrossDomainMessengerStub__factory, } from "../../typechain"; import { assert } from "chai"; import { unit } from "../../utils/testing"; +import { wei } from "../../utils/wei"; unit("BridgingManager", ctxFactory) .test("isInitialized() :: on uninitialized contract", async (ctx) => { @@ -19,36 +24,36 @@ unit("BridgingManager", ctxFactory) assert.isFalse(await ctx.bridgingManagerRaw.isWithdrawalsEnabled()); }) - .test("initialize() :: on uninitialized contract", async (ctx) => { - const { - bridgingManagerRaw, - roles: { DEFAULT_ADMIN_ROLE }, - accounts: { stranger }, - } = ctx; - // validate that bridgingManager is not initialized - assert.isFalse(await bridgingManagerRaw.isInitialized()); - - // validate that stranger has no DEFAULT_ADMIN_ROLE - assert.isFalse( - await bridgingManagerRaw.hasRole(DEFAULT_ADMIN_ROLE, stranger.address) - ); - // initialize() might be called by anyone - await bridgingManagerRaw.connect(stranger).initialize(stranger.address); - - // validate that isInitialized() is true - assert.isTrue(await bridgingManagerRaw.isInitialized()); - - // validate that stranger has DEFAULT_ADMIN_RULE - assert.isTrue( - await bridgingManagerRaw.hasRole(DEFAULT_ADMIN_ROLE, stranger.address) - ); - - // validate that initialize() might not be called second time - await assert.revertsWith( - bridgingManagerRaw.connect(stranger).initialize(stranger.address), - "ErrorAlreadyInitialized()" - ); - }) +// .test("initialize() :: on uninitialized contract", async (ctx) => { +// const { +// bridgingManagerRaw, +// roles: { DEFAULT_ADMIN_ROLE }, +// accounts: { stranger }, +// } = ctx; +// // validate that bridgingManager is not initialized +// assert.isFalse(await bridgingManagerRaw.isInitialized()); + +// // validate that stranger has no DEFAULT_ADMIN_ROLE +// assert.isFalse( +// await bridgingManagerRaw.hasRole(DEFAULT_ADMIN_ROLE, stranger.address) +// ); +// // initialize() might be called by anyone +// await bridgingManagerRaw.connect(stranger).initialize(stranger.address); + +// // validate that isInitialized() is true +// assert.isTrue(await bridgingManagerRaw.isInitialized()); + +// // validate that stranger has DEFAULT_ADMIN_RULE +// assert.isTrue( +// await bridgingManagerRaw.hasRole(DEFAULT_ADMIN_ROLE, stranger.address) +// ); + +// // validate that initialize() might not be called second time +// await assert.revertsWith( +// bridgingManagerRaw.connect(stranger).initialize(stranger.address), +// "ErrorAlreadyInitialized()" +// ); +// }) .test("enableDeposits() :: role is not granted", async (ctx) => { const { @@ -302,19 +307,58 @@ async function ctxFactory() { depositsDisabler, withdrawalsEnabler, withdrawalsDisabler, + l2TokenBridgeEOA ] = await hre.ethers.getSigners(); - const bridgingManagerImpl = await new BridgingManager__factory( +// const bridgingManagerImpl = await new BridgingManager__factory( +// deployer +// ).deploy(); + +const l1TokenRebasableStub = await new ERC20BridgedStub__factory(deployer).deploy( + "L1 Token Rebasable", + "L1R" +); + +const l1TokenNonRebasableStub = await new ERC20WrapperStub__factory(deployer).deploy( + l1TokenRebasableStub.address, + "L1 Token Non Rebasable", + "L1NR" +); + +const l2TokenNonRebasableStub = await new ERC20BridgedStub__factory(deployer).deploy( + "L2 Token Non Rebasable", + "L2NR" +); + +const l2TokenRebasableStub = await new ERC20WrapperStub__factory(deployer).deploy( + l2TokenNonRebasableStub.address, + "L2 Token Rebasable", + "L2R" +); + +const l1MessengerStub = await new CrossDomainMessengerStub__factory( + deployer +).deploy({ value: wei.toBigNumber(wei`1 ether`) }); + + const l1TokenBridgeImpl = await new L1LidoTokensBridge__factory( deployer - ).deploy(); +).deploy( + l1MessengerStub.address, + l2TokenBridgeEOA.address, + l1TokenNonRebasableStub.address, + l1TokenRebasableStub.address, + l2TokenNonRebasableStub.address, + l2TokenRebasableStub.address +); + const pureOssifiableProxy = await new OssifiableProxy__factory( deployer - ).deploy(bridgingManagerImpl.address, deployer.address, "0x"); + ).deploy(l1TokenBridgeImpl.address, deployer.address, "0x"); const initializedOssifiableProxy = await new OssifiableProxy__factory( deployer - ).deploy(bridgingManagerImpl.address, deployer.address, "0x"); + ).deploy(l1TokenBridgeImpl.address, deployer.address, "0x"); - const bridgingManager = BridgingManager__factory.connect( + const bridgingManager = L1LidoTokensBridge__factory.connect( initializedOssifiableProxy.address, deployer ); @@ -327,11 +371,11 @@ async function ctxFactory() { WITHDRAWALS_ENABLER_ROLE, WITHDRAWALS_DISABLER_ROLE, ] = await Promise.all([ - await bridgingManagerImpl.DEFAULT_ADMIN_ROLE(), - await bridgingManagerImpl.DEPOSITS_ENABLER_ROLE(), - await bridgingManagerImpl.DEPOSITS_DISABLER_ROLE(), - await bridgingManagerImpl.WITHDRAWALS_ENABLER_ROLE(), - await bridgingManagerImpl.WITHDRAWALS_DISABLER_ROLE(), + await l1TokenBridgeImpl.DEFAULT_ADMIN_ROLE(), + await l1TokenBridgeImpl.DEPOSITS_ENABLER_ROLE(), + await l1TokenBridgeImpl.DEPOSITS_DISABLER_ROLE(), + await l1TokenBridgeImpl.WITHDRAWALS_ENABLER_ROLE(), + await l1TokenBridgeImpl.WITHDRAWALS_DISABLER_ROLE(), ]); await bridgingManager.grantRole( DEPOSITS_ENABLER_ROLE, @@ -367,7 +411,7 @@ async function ctxFactory() { withdrawalsDisabler, }, bridgingManager, - bridgingManagerRaw: BridgingManager__factory.connect( + bridgingManagerRaw: L1LidoTokensBridge__factory.connect( pureOssifiableProxy.address, deployer ), diff --git a/test/optimism/L2ERC20ExtendedTokensBridge.unit.test.ts b/test/optimism/L2ERC20ExtendedTokensBridge.unit.test.ts index d8d4f9f9..86121871 100644 --- a/test/optimism/L2ERC20ExtendedTokensBridge.unit.test.ts +++ b/test/optimism/L2ERC20ExtendedTokensBridge.unit.test.ts @@ -3,7 +3,7 @@ import { ERC20BridgedStub__factory, ERC20WrapperStub__factory, TokenRateOracle__factory, - ERC20RebasableBridged__factory, + ERC20RebasableBridgedPermit__factory, L1LidoTokensBridge__factory, L2ERC20ExtendedTokensBridge__factory, OssifiableProxy__factory, @@ -1018,9 +1018,10 @@ async function ctxFactory() { 86400 ); - const l2TokenRebasableStub = await new ERC20RebasableBridged__factory(deployer).deploy( + const l2TokenRebasableStub = await new ERC20RebasableBridgedPermit__factory(deployer).deploy( "L2 Token Rebasable", "L2R", + "1", decimals, l2TokenNonRebasableStub.address, tokenRateOracle.address, diff --git a/test/optimism/managing-proxy.e2e.test.ts b/test/optimism/managing-proxy.e2e.test.ts index 20ff14af..bed76248 100644 --- a/test/optimism/managing-proxy.e2e.test.ts +++ b/test/optimism/managing-proxy.e2e.test.ts @@ -2,7 +2,7 @@ import { assert } from "chai"; import { TransactionResponse } from "@ethersproject/providers"; import { - ERC20Bridged__factory, + ERC20BridgedPermit__factory, GovBridgeExecutor__factory, OssifiableProxy__factory, L2ERC20ExtendedTokensBridge__factory, @@ -200,7 +200,7 @@ async function ctxFactory() { l1Tester, l2Tester, l1LDOHolder, - l2Token: ERC20Bridged__factory.connect( + l2Token: ERC20BridgedPermit__factory.connect( E2E_TEST_CONTRACTS.l2.l2Token, l2Tester ), diff --git a/test/token/ERC20Bridged.unit.test.ts b/test/token/ERC20Bridged.unit.test.ts index 58ffee0c..b49a1c91 100644 --- a/test/token/ERC20Bridged.unit.test.ts +++ b/test/token/ERC20Bridged.unit.test.ts @@ -1,7 +1,7 @@ import { assert } from "chai"; import hre from "hardhat"; import { - ERC20Bridged__factory, + ERC20BridgedPermit__factory, OssifiableProxy__factory, } from "../../typechain"; import { unit } from "../../utils/testing"; @@ -16,37 +16,39 @@ unit("ERC20Bridged", ctxFactory) assert.equalBN(await ctx.erc20Bridged.totalSupply(), ctx.constants.premint); }) - .test("initialize() :: name already set", async (ctx) => { - const { deployer, owner } = ctx.accounts; - - // deploy new implementation - const erc20BridgedImpl = await new ERC20Bridged__factory(deployer).deploy( - "Name", - "", - 9, - owner.address - ); - await assert.revertsWith( - erc20BridgedImpl.initializeERC20Metadata("New Name", ""), - "ErrorNameAlreadySet()" - ); - }) - - .test("initialize() :: symbol already set", async (ctx) => { - const { deployer, owner } = ctx.accounts; - - // deploy new implementation - const erc20BridgedImpl = await new ERC20Bridged__factory(deployer).deploy( - "", - "Symbol", - 9, - owner.address - ); - await assert.revertsWith( - erc20BridgedImpl.initializeERC20Metadata("", "New Symbol"), - "ErrorSymbolAlreadySet()" - ); - }) +// .test("initialize() :: name already set", async (ctx) => { +// const { deployer, owner } = ctx.accounts; + +// // deploy new implementation +// const erc20BridgedImpl = await new ERC20BridgedPermit__factory(deployer).deploy( +// "Name", +// "", +// "", +// 9, +// owner.address +// ); +// await assert.revertsWith( +// erc20BridgedImpl.initialize("New Name", "", ""), +// "ErrorNameAlreadySet()" +// ); +// }) + +// .test("initialize() :: symbol already set", async (ctx) => { +// const { deployer, owner } = ctx.accounts; + +// // deploy new implementation +// const erc20BridgedImpl = await new ERC20BridgedPermit__factory(deployer).deploy( +// "", +// "Symbol", +// "", +// 9, +// owner.address +// ); +// await assert.revertsWith( +// erc20BridgedImpl.initialize("", "New Symbol", ""), +// "ErrorSymbolAlreadySet()" +// ); +// }) .test("approve()", async (ctx) => { const { erc20Bridged } = ctx; @@ -429,12 +431,14 @@ async function ctxFactory() { const name = "ERC20 Test Token"; const symbol = "ERC20"; const decimals = 18; + const version = "1"; const premint = wei`100 ether`; const [deployer, owner, recipient, spender, holder, stranger] = await hre.ethers.getSigners(); - const l2TokenImpl = await new ERC20Bridged__factory(deployer).deploy( + const l2TokenImpl = await new ERC20BridgedPermit__factory(deployer).deploy( name, symbol, + version, decimals, owner.address ); @@ -449,13 +453,14 @@ async function ctxFactory() { const l2TokensProxy = await new OssifiableProxy__factory(deployer).deploy( l2TokenImpl.address, deployer.address, - ERC20Bridged__factory.createInterface().encodeFunctionData("initializeERC20Metadata", [ + ERC20BridgedPermit__factory.createInterface().encodeFunctionData("initialize", [ name, symbol, + version ]) ); - const erc20BridgedProxied = ERC20Bridged__factory.connect( + const erc20BridgedProxied = ERC20BridgedPermit__factory.connect( l2TokensProxy.address, holder ); diff --git a/test/token/ERC20Rebasable.unit.test.ts b/test/token/ERC20Rebasable.unit.test.ts index ef4ff443..88f1a2a7 100644 --- a/test/token/ERC20Rebasable.unit.test.ts +++ b/test/token/ERC20Rebasable.unit.test.ts @@ -4,9 +4,9 @@ import { unit } from "../../utils/testing"; import { wei } from "../../utils/wei"; import { - ERC20Bridged__factory, + ERC20BridgedPermit__factory, TokenRateOracle__factory, - ERC20RebasableBridged__factory, + ERC20RebasableBridgedPermit__factory, OssifiableProxy__factory, CrossDomainMessengerStub__factory } from "../../typechain"; @@ -32,68 +32,72 @@ unit("ERC20RebasableBridged", ctxFactory) assert.equal(await ctx.contracts.rebasableProxied.symbol(), ctx.constants.symbol) ) - .test("initialize() :: name already set", async (ctx) => { - const { deployer, owner, zero } = ctx.accounts; - const { decimalsToSet } = ctx.constants; - - // deploy new implementation - const wrappedToken = await new ERC20Bridged__factory(deployer).deploy( - "WsETH Test Token", - "WsETH", - decimalsToSet, - owner.address - ); - - const tokenRateOracle = await new TokenRateOracle__factory(deployer).deploy( - zero.address, - owner.address, - zero.address, - 86400 - ); - const rebasableTokenImpl = await new ERC20RebasableBridged__factory(deployer).deploy( - "name", - "", - 10, - wrappedToken.address, - tokenRateOracle.address, - owner.address - ); - await assert.revertsWith( - rebasableTokenImpl.initializeERC20Metadata("New Name", ""), - "ErrorNameAlreadySet()" - ); - }) - - .test("initialize() :: symbol already set", async (ctx) => { - const { deployer, owner, zero } = ctx.accounts; - const { decimalsToSet } = ctx.constants; - - // deploy new implementation - const wrappedToken = await new ERC20Bridged__factory(deployer).deploy( - "WsETH Test Token", - "WsETH", - decimalsToSet, - owner.address - ); - const tokenRateOracle = await new TokenRateOracle__factory(deployer).deploy( - zero.address, - owner.address, - zero.address, - 86400 - ); - const rebasableTokenImpl = await new ERC20RebasableBridged__factory(deployer).deploy( - "", - "symbol", - 10, - wrappedToken.address, - tokenRateOracle.address, - owner.address - ); - await assert.revertsWith( - rebasableTokenImpl.initializeERC20Metadata("", "New Symbol"), - "ErrorSymbolAlreadySet()" - ); - }) +// .test("initialize() :: name already set", async (ctx) => { +// const { deployer, owner, zero } = ctx.accounts; +// const { decimalsToSet } = ctx.constants; + +// // deploy new implementation +// const wrappedToken = await new ERC20BridgedPermit__factory(deployer).deploy( +// "WsETH Test Token", +// "WsETH", +// "1", +// decimalsToSet, +// owner.address +// ); + +// const tokenRateOracle = await new TokenRateOracle__factory(deployer).deploy( +// zero.address, +// owner.address, +// zero.address, +// 86400 +// ); +// const rebasableTokenImpl = await new ERC20RebasableBridgedPermit__factory(deployer).deploy( +// "name", +// "", +// "", +// 10, +// wrappedToken.address, +// tokenRateOracle.address, +// owner.address +// ); +// await assert.revertsWith( +// rebasableTokenImpl.initialize("New Name", "", ""), +// "ErrorNameAlreadySet()" +// ); +// }) + +// .test("initialize() :: symbol already set", async (ctx) => { +// const { deployer, owner, zero } = ctx.accounts; +// const { decimalsToSet } = ctx.constants; + +// // deploy new implementation +// const wrappedToken = await new ERC20BridgedPermit__factory(deployer).deploy( +// "WsETH Test Token", +// "WsETH", +// "1", +// decimalsToSet, +// owner.address +// ); +// const tokenRateOracle = await new TokenRateOracle__factory(deployer).deploy( +// zero.address, +// owner.address, +// zero.address, +// 86400 +// ); +// const rebasableTokenImpl = await new ERC20RebasableBridgedPermit__factory(deployer).deploy( +// "", +// "symbol", +// "1", +// 10, +// wrappedToken.address, +// tokenRateOracle.address, +// owner.address +// ); +// await assert.revertsWith( +// rebasableTokenImpl.initialize("", "New Symbol", "1"), +// "ErrorSymbolAlreadySet()" +// ); +// }) .test("decimals() :: has the same value as is in constructor", async (ctx) => assert.equal(await ctx.contracts.rebasableProxied.decimals(), ctx.constants.decimalsToSet) @@ -116,9 +120,10 @@ unit("ERC20RebasableBridged", ctxFactory) const { decimalsToSet } = ctx.constants; // deploy new implementation to test initial oracle state - const wrappedToken = await new ERC20Bridged__factory(deployer).deploy( + const wrappedToken = await new ERC20BridgedPermit__factory(deployer).deploy( "WsETH Test Token", "WsETH", + "1", decimalsToSet, owner.address ); @@ -128,9 +133,10 @@ unit("ERC20RebasableBridged", ctxFactory) zero.address, 86400 ); - const rebasableProxied = await new ERC20RebasableBridged__factory(deployer).deploy( + const rebasableProxied = await new ERC20RebasableBridgedPermit__factory(deployer).deploy( "", "symbol", + "1", 10, wrappedToken.address, tokenRateOracle.address, @@ -290,9 +296,10 @@ unit("ERC20RebasableBridged", ctxFactory) const { decimalsToSet } = ctx.constants; // deploy new implementation to test initial oracle state - const wrappedToken = await new ERC20Bridged__factory(deployer).deploy( + const wrappedToken = await new ERC20BridgedPermit__factory(deployer).deploy( "WsETH Test Token", "WsETH", + "1", decimalsToSet, owner.address ); @@ -302,9 +309,10 @@ unit("ERC20RebasableBridged", ctxFactory) zero.address, 86400 ); - const rebasableProxied = await new ERC20RebasableBridged__factory(deployer).deploy( + const rebasableProxied = await new ERC20RebasableBridgedPermit__factory(deployer).deploy( "", "symbol", + "1", 10, wrappedToken.address, tokenRateOracle.address, @@ -1098,6 +1106,7 @@ unit("ERC20RebasableBridged", ctxFactory) async function ctxFactory() { const name = "StETH Test Token"; const symbol = "StETH"; + const version = "1"; const decimalsToSet = 18; const decimals = BigNumber.from(10).pow(decimalsToSet); const rate = BigNumber.from('12').pow(decimalsToSet - 1); @@ -1120,9 +1129,10 @@ async function ctxFactory() { ] = await hre.ethers.getSigners(); const zero = await hre.ethers.getSigner(hre.ethers.constants.AddressZero); - const wrappedToken = await new ERC20Bridged__factory(deployer).deploy( + const wrappedToken = await new ERC20BridgedPermit__factory(deployer).deploy( "WsETH Test Token", "WsETH", + version, decimalsToSet, owner.address ); @@ -1132,9 +1142,10 @@ async function ctxFactory() { zero.address, 86400 ); - const rebasableTokenImpl = await new ERC20RebasableBridged__factory(deployer).deploy( + const rebasableTokenImpl = await new ERC20RebasableBridgedPermit__factory(deployer).deploy( name, symbol, + version, decimalsToSet, wrappedToken.address, tokenRateOracle.address, @@ -1149,13 +1160,14 @@ async function ctxFactory() { const l2TokensProxy = await new OssifiableProxy__factory(deployer).deploy( rebasableTokenImpl.address, deployer.address, - ERC20RebasableBridged__factory.createInterface().encodeFunctionData("initializeERC20Metadata", [ + ERC20RebasableBridgedPermit__factory.createInterface().encodeFunctionData("initialize", [ name, symbol, + version ]) ); - const rebasableProxied = ERC20RebasableBridged__factory.connect( + const rebasableProxied = ERC20RebasableBridgedPermit__factory.connect( l2TokensProxy.address, holder ); diff --git a/utils/optimism/deploymentAllFromScratch.ts b/utils/optimism/deploymentAllFromScratch.ts index f1aac90c..d8de76e8 100644 --- a/utils/optimism/deploymentAllFromScratch.ts +++ b/utils/optimism/deploymentAllFromScratch.ts @@ -5,8 +5,8 @@ import { OptDeploymentOptions, DeployScriptParams } from "./types"; import network, { NetworkName } from "../network"; import { DeployScript, Logger } from "../deployment/DeployScript"; import { - ERC20Bridged__factory, - ERC20RebasableBridged__factory, + ERC20BridgedPermit__factory, + ERC20RebasableBridgedPermit__factory, IERC20Metadata__factory, L1LidoTokensBridge__factory, L2ERC20ExtendedTokensBridge__factory, @@ -21,11 +21,13 @@ interface OptL1DeployScriptParams extends DeployScriptParams { interface OptL2DeployScriptParams extends DeployScriptParams { l2Token?: { name?: string; - symbol?: string + symbol?: string; + version?: string; }; l2TokenRebasable?: { name?: string; - symbol?: string + symbol?: string; + version?: string; }; } @@ -191,10 +193,11 @@ export default function deploymentAll( l1TokenRebasable, l1Params.deployer ); - const [decimals, l2TokenName, l2TokenSymbol, l2TokenRebasableName, l2TokenRebasableSymbol] = await Promise.all([ + const [decimals, l2TokenName, l2TokenSymbol, l2TokenVersion, l2TokenRebasableName, l2TokenRebasableSymbol] = await Promise.all([ l1TokenInfo.decimals(), l2Params.l2Token?.name ?? l1TokenInfo.name(), l2Params.l2Token?.symbol ?? l1TokenInfo.symbol(), + l2Params.l2Token?.version ?? "1", l2Params.l2TokenRebasable?.name ?? l1TokenRebasableInfo.name(), l2Params.l2TokenRebasable?.symbol ?? l1TokenRebasableInfo.symbol(), ]); @@ -212,10 +215,11 @@ export default function deploymentAll( options?.logger ) .addStep({ - factory: ERC20Bridged__factory, + factory: ERC20BridgedPermit__factory, args: [ l2TokenName, l2TokenSymbol, + l2TokenVersion, decimals, expectedL2TokenBridgeProxyAddress, options?.overrides, @@ -228,9 +232,9 @@ export default function deploymentAll( args: [ expectedL2TokenImplAddress, l2Params.admins.proxy, - ERC20Bridged__factory.createInterface().encodeFunctionData( - "initializeERC20Metadata", - [l2TokenName, l2TokenSymbol] + ERC20BridgedPermit__factory.createInterface().encodeFunctionData( + "initialize", + [l2TokenName, l2TokenSymbol, l2TokenVersion] ), options?.overrides, ], @@ -238,10 +242,11 @@ export default function deploymentAll( assert.equal(c.address, expectedL2TokenProxyAddress), }) .addStep({ - factory: ERC20RebasableBridged__factory, + factory: ERC20RebasableBridgedPermit__factory, args: [ l2TokenRebasableName, l2TokenRebasableSymbol, + l2TokenVersion, decimals, expectedL2TokenProxyAddress, expectedL2TokenRateOracleProxyAddress, @@ -256,9 +261,9 @@ export default function deploymentAll( args: [ expectedL2TokenRebasableImplAddress, l2Params.admins.proxy, - ERC20RebasableBridged__factory.createInterface().encodeFunctionData( - "initializeERC20Metadata", - [l2TokenRebasableName, l2TokenRebasableSymbol] + ERC20RebasableBridgedPermit__factory.createInterface().encodeFunctionData( + "initialize", + [l2TokenRebasableName, l2TokenRebasableSymbol, l2TokenVersion] ), options?.overrides, ], diff --git a/utils/optimism/deploymentBridgesAndRebasableToken.ts b/utils/optimism/deploymentBridgesAndRebasableToken.ts index 2a61dd39..7526ebf1 100644 --- a/utils/optimism/deploymentBridgesAndRebasableToken.ts +++ b/utils/optimism/deploymentBridgesAndRebasableToken.ts @@ -5,8 +5,8 @@ import { CommonOptions } from "./types"; import network, { NetworkName } from "../network"; import { DeployScript, Logger } from "../deployment/DeployScript"; import { - ERC20Bridged__factory, - ERC20RebasableBridged__factory, + ERC20BridgedPermit__factory, + ERC20RebasableBridgedPermit__factory, IERC20Metadata__factory, L1LidoTokensBridge__factory, L2ERC20ExtendedTokensBridge__factory, @@ -168,7 +168,7 @@ export default function deployment( options?.logger ) .addStep({ - factory: ERC20Bridged__factory, + factory: ERC20BridgedPermit__factory, args: [ l2TokenName, l2TokenSymbol, @@ -184,7 +184,7 @@ export default function deployment( args: [ expectedL2TokenImplAddress, l2Params.admins.proxy, - ERC20Bridged__factory.createInterface().encodeFunctionData( + ERC20BridgedPermit__factory.createInterface().encodeFunctionData( "initializeERC20Metadata", [l2TokenName, l2TokenSymbol] ), @@ -194,7 +194,7 @@ export default function deployment( assert.equal(c.address, expectedL2TokenProxyAddress), }) .addStep({ - factory: ERC20RebasableBridged__factory, + factory: ERC20RebasableBridgedPermit__factory, args: [ l2TokenRebasableName, l2TokenRebasableSymbol, @@ -212,7 +212,7 @@ export default function deployment( args: [ expectedL2TokenRebasableImplAddress, l2Params.admins.proxy, - ERC20RebasableBridged__factory.createInterface().encodeFunctionData( + ERC20RebasableBridgedPermit__factory.createInterface().encodeFunctionData( "initializeERC20Metadata", [l2TokenRebasableName, l2TokenRebasableSymbol] ), diff --git a/utils/optimism/deploymentNewImplementations.ts b/utils/optimism/deploymentNewImplementations.ts index d49e5d00..c29bb8ab 100644 --- a/utils/optimism/deploymentNewImplementations.ts +++ b/utils/optimism/deploymentNewImplementations.ts @@ -5,8 +5,8 @@ import { OptDeploymentOptions, DeployScriptParams } from "./types"; import network, { NetworkName } from "../network"; import { DeployScript, Logger } from "../deployment/DeployScript"; import { - ERC20Bridged__factory, - ERC20RebasableBridged__factory, + ERC20BridgedPermit__factory, + ERC20RebasableBridgedPermit__factory, IERC20Metadata__factory, L1LidoTokensBridge__factory, L2ERC20ExtendedTokensBridge__factory, @@ -157,7 +157,7 @@ export default function deploymentNewImplementations( options?.logger ) .addStep({ - factory: ERC20Bridged__factory, + factory: ERC20BridgedPermit__factory, args: [ l2TokenName, l2TokenSymbol, @@ -169,7 +169,7 @@ export default function deploymentNewImplementations( assert.equal(c.address, expectedL2TokenImplAddress), }) .addStep({ - factory: ERC20RebasableBridged__factory, + factory: ERC20RebasableBridgedPermit__factory, args: [ l2TokenRebasableName, l2TokenRebasableSymbol, @@ -187,7 +187,7 @@ export default function deploymentNewImplementations( args: [ expectedL2TokenRebasableImplAddress, l2Params.admins.proxy, - ERC20RebasableBridged__factory.createInterface().encodeFunctionData( + ERC20RebasableBridgedPermit__factory.createInterface().encodeFunctionData( "initializeERC20Metadata", [l2TokenRebasableName, l2TokenRebasableSymbol] ), diff --git a/utils/optimism/testing.ts b/utils/optimism/testing.ts index af114de0..d2a0973b 100644 --- a/utils/optimism/testing.ts +++ b/utils/optimism/testing.ts @@ -7,14 +7,14 @@ import { IERC20__factory, L1LidoTokensBridge, L2ERC20ExtendedTokensBridge, - ERC20Bridged__factory, + ERC20BridgedPermit__factory, ERC20BridgedStub__factory, ERC20WrapperStub__factory, TokenRateOracle__factory, L1LidoTokensBridge__factory, L2ERC20ExtendedTokensBridge__factory, CrossDomainMessengerStub__factory, - ERC20RebasableBridged__factory, + ERC20RebasableBridgedPermit__factory, } from "../../typechain"; import addresses from "./addresses"; import contracts from "./contracts"; @@ -276,11 +276,11 @@ function connectBridgeContracts( addresses.l2ERC20ExtendedTokensBridge, optSignerOrProvider ); - const l2Token = ERC20Bridged__factory.connect( + const l2Token = ERC20BridgedPermit__factory.connect( addresses.l2Token, optSignerOrProvider ); - const l2TokenRebasable = ERC20RebasableBridged__factory.connect( + const l2TokenRebasable = ERC20RebasableBridgedPermit__factory.connect( addresses.l2TokenRebasable, optSignerOrProvider ); From 0e222ec44014c3a2337c7af3faef08a0d13fc218 Mon Sep 17 00:00:00 2001 From: Anton Kovalchuk Date: Mon, 22 Apr 2024 01:03:40 +0400 Subject: [PATCH 084/148] change filters for updating token rate --- contracts/optimism/TokenRateOracle.sol | 133 ++++++++++++++++++------- 1 file changed, 96 insertions(+), 37 deletions(-) diff --git a/contracts/optimism/TokenRateOracle.sol b/contracts/optimism/TokenRateOracle.sol index de40eadc..ddb1a187 100644 --- a/contracts/optimism/TokenRateOracle.sol +++ b/contracts/optimism/TokenRateOracle.sol @@ -6,12 +6,15 @@ pragma solidity 0.8.10; import {ITokenRateUpdatable} from "./interfaces/ITokenRateUpdatable.sol"; import {IChainlinkAggregatorInterface} from "./interfaces/IChainlinkAggregatorInterface.sol"; import {CrossDomainEnabled} from "./CrossDomainEnabled.sol"; +import {Initializable} from "@openzeppelin/contracts/proxy/utils/Initializable.sol"; +import {Versioned} from "../utils/Versioned.sol"; interface ITokenRateOracle is ITokenRateUpdatable, IChainlinkAggregatorInterface {} /// @author kovalgek /// @notice Oracle for storing token rate. -contract TokenRateOracle is CrossDomainEnabled, ITokenRateOracle { +/// @dev Token rate updates can be delivered from two sources: L1 token rate pusher and L2 bridge. +contract TokenRateOracle is CrossDomainEnabled, ITokenRateOracle, Versioned, Initializable { /// @dev Stores the dynamic data of the oracle. Allows safely use of this /// contract with upgradable proxies @@ -35,14 +38,20 @@ contract TokenRateOracle is CrossDomainEnabled, ITokenRateOracle { /// @notice A time period when token rate can be considered outdated. uint256 public immutable TOKEN_RATE_OUTDATED_DELAY; + /// @notice A time period when token rate can be considered outdated. + uint256 public constant MAX_ALLOWED_L1_L2_TIME_DIFFERENCE = 86400; + + /// @notice Number of seconds in one day. + uint256 public constant ONE_DAY_SECONDS = 86400; + + /// @notice Allowed token rate deviation per day in basic points. + uint256 public constant TOKEN_RATE_DEVIATION = 500; + /// @notice Decimals of the oracle response. uint8 public constant DECIMALS = 18; - /// @notice The minimum value that the token rate can be - uint256 public constant MIN_TOKEN_RATE = 1_000_000_000_000_000; // 0.001 - - /// @notice The maximum value that the token rate can be. - uint256 public constant MAX_TOKEN_RATE = 1_000_000_000_000_000_000_000; // 1000 + /// @notice Basic point scale. + uint256 private constant BASIS_POINT_SCALE = 1e4; /// @param messenger_ L2 messenger address being used for cross-chain communications /// @param l2ERC20TokenBridge_ the bridge address that has a right to updates oracle. @@ -57,6 +66,13 @@ contract TokenRateOracle is CrossDomainEnabled, ITokenRateOracle { L2_ERC20_TOKEN_BRIDGE = l2ERC20TokenBridge_; L1_TOKEN_RATE_PUSHER = l1TokenRatePusher_; TOKEN_RATE_OUTDATED_DELAY = tokenRateOutdatedDelay_; + + _disableInitializers(); + } + + function initialize(uint256 tokenRate_, uint256 rateL1Timestamp_) external initializer { + _initializeContractVersionTo(1); + _updateRate(tokenRate_, rateL1Timestamp_); } /// @inheritdoc IChainlinkAggregatorInterface @@ -67,20 +83,20 @@ contract TokenRateOracle is CrossDomainEnabled, ITokenRateOracle { uint256 updatedAt_, uint80 answeredInRound_ ) { - uint80 roundId = uint80(_loadTokenRateData().rateL1Timestamp); + uint80 roundId = uint80(_rateL1Timestamp()); return ( roundId, - int256(uint(_loadTokenRateData().tokenRate)), - _loadTokenRateData().rateL1Timestamp, - _loadTokenRateData().rateL1Timestamp, + int256(uint256(_tokenRate())), + _rateL1Timestamp(), + _rateL1Timestamp(), roundId ); } /// @inheritdoc IChainlinkAggregatorInterface function latestAnswer() external view returns (int256) { - return int256(uint(_loadTokenRateData().tokenRate)); + return int256(uint256(_tokenRate())); } /// @inheritdoc IChainlinkAggregatorInterface @@ -89,45 +105,41 @@ contract TokenRateOracle is CrossDomainEnabled, ITokenRateOracle { } /// @inheritdoc ITokenRateUpdatable - function updateRate(uint256 tokenRate_, uint256 rateL1Timestamp_) external { + function updateRate(uint256 tokenRate_, uint256 rateL1Timestamp_) external onlyAuthorized { + _updateRate(tokenRate_, rateL1Timestamp_); + } - if (!_isAuthorized(msg.sender)) { - revert ErrorNoRights(msg.sender); - } + /// @notice Returns flag that shows that token rate can be considered outdated. + function isLikelyOutdated() external view returns (bool) { + return block.timestamp - _rateL1Timestamp() > TOKEN_RATE_OUTDATED_DELAY; + } - if (rateL1Timestamp_ < _loadTokenRateData().rateL1Timestamp) { - emit NewTokenRateOutdated(tokenRate_, _loadTokenRateData().rateL1Timestamp, rateL1Timestamp_); + function _updateRate(uint256 tokenRate_, uint256 rateL1Timestamp_) internal { + if (rateL1Timestamp_ < _rateL1Timestamp()) { + emit ATryToUpdateTokenRateWithOutdatedTime(tokenRate_, _rateL1Timestamp(), rateL1Timestamp_); return; } + if (rateL1Timestamp_ - block.timestamp > MAX_ALLOWED_L1_L2_TIME_DIFFERENCE) { + revert ErrorInvalidTime(tokenRate_, rateL1Timestamp_); + } + if (rateL1Timestamp_ > block.timestamp) { - revert ErrorL1TimestampInFuture(tokenRate_, rateL1Timestamp_); + emit TokenRateL1TimestampAheadOfL2Time(); } - if (tokenRate_ < MIN_TOKEN_RATE || tokenRate_ > MAX_TOKEN_RATE) { + if (!_isTokenRateWithinAllowedRange(tokenRate_, rateL1Timestamp_)) { revert ErrorTokenRateIsOutOfRange(tokenRate_, rateL1Timestamp_); } - if (tokenRate_ == _loadTokenRateData().tokenRate && rateL1Timestamp_ == _loadTokenRateData().rateL1Timestamp) { + if (tokenRate_ == _tokenRate() && rateL1Timestamp_ == _rateL1Timestamp()) { return; } - _loadTokenRateData().tokenRate = uint192(tokenRate_); - _loadTokenRateData().rateL1Timestamp = uint64(rateL1Timestamp_); - - emit RateUpdated(_loadTokenRateData().tokenRate, _loadTokenRateData().rateL1Timestamp); - } - - /// @notice Returns flag that shows that token rate can be considered outdated. - function isLikelyOutdated() external view returns (bool) { - return block.timestamp - _loadTokenRateData().rateL1Timestamp > TOKEN_RATE_OUTDATED_DELAY; - } + _setTokenRate(uint192(tokenRate_)); + _setRateL1Timestamp(uint64(rateL1Timestamp_)); - function _isAuthorized(address caller_) internal view returns (bool) { - bool isCalledFromL1TokenRatePusher = caller_ == address(MESSENGER) && - MESSENGER.xDomainMessageSender() == L1_TOKEN_RATE_PUSHER; - bool isCalledFromERC20TokenRateBridge = caller_ == L2_ERC20_TOKEN_BRIDGE; - return isCalledFromL1TokenRatePusher || isCalledFromERC20TokenRateBridge; + emit RateUpdated(_tokenRate(), _rateL1Timestamp()); } /// @dev Returns the reference to the slot with TokenRateData struct @@ -142,10 +154,57 @@ contract TokenRateOracle is CrossDomainEnabled, ITokenRateOracle { } } + function _isTokenRateWithinAllowedRange(uint256 tokenRate_, uint256 rateL1Timestamp_) internal view returns (bool) { + uint256 rateL1TimestampDiff = rateL1Timestamp_ - _rateL1Timestamp(); + uint256 roundedUpNumberOfDays = rateL1TimestampDiff / ONE_DAY_SECONDS + 1; + uint256 allowedTokenRateDeviation = roundedUpNumberOfDays * TOKEN_RATE_DEVIATION; + uint256 topTokenRateLimitFactor = 1 + allowedTokenRateDeviation / BASIS_POINT_SCALE; + uint256 bottomTokenRateLimitFactor = + allowedTokenRateDeviation <= BASIS_POINT_SCALE ? + (1 - allowedTokenRateDeviation / BASIS_POINT_SCALE) : 0; + + return tokenRate_ <= _tokenRate() * topTokenRateLimitFactor && + tokenRate_ >= _tokenRate() * bottomTokenRateLimitFactor; + } + + function _isAuthorized(address caller_) internal view returns (bool) { + if(caller_ == address(MESSENGER) && MESSENGER.xDomainMessageSender() == L1_TOKEN_RATE_PUSHER) { + return true; + } + if(caller_ == L2_ERC20_TOKEN_BRIDGE) { + return true; + } + return false; + } + + function _tokenRate() private view returns (uint192) { + return _loadTokenRateData().tokenRate; + } + + function _rateL1Timestamp() private view returns (uint64) { + return _loadTokenRateData().rateL1Timestamp; + } + + function _setTokenRate(uint192 tokenRate_) internal { + _loadTokenRateData().tokenRate = tokenRate_; + } + + function _setRateL1Timestamp(uint64 rateL1Timestamp_) internal { + _loadTokenRateData().rateL1Timestamp = rateL1Timestamp_; + } + + modifier onlyAuthorized() { + if(!_isAuthorized(msg.sender)) { + revert ErrorNoRights(msg.sender); + } + _; + } + event RateUpdated(uint256 tokenRate_, uint256 rateL1Timestamp_); - event NewTokenRateOutdated(uint256 tokenRate_, uint256 rateL1Timestamp_, uint256 newTateL1Timestamp_); + event ATryToUpdateTokenRateWithOutdatedTime(uint256 tokenRate_, uint256 rateL1Timestamp_, uint256 newTateL1Timestamp_); + event TokenRateL1TimestampAheadOfL2Time(); error ErrorNoRights(address caller); - error ErrorL1TimestampInFuture(uint256 tokenRate_, uint256 rateL1Timestamp_); + error ErrorInvalidTime(uint256 tokenRate_, uint256 rateL1Timestamp_); error ErrorTokenRateIsOutOfRange(uint256 tokenRate_, uint256 rateL1Timestamp_); } From 4d1676727296ec3549015c1336f0cfbdcba30666 Mon Sep 17 00:00:00 2001 From: Anton Kovalchuk Date: Mon, 22 Apr 2024 18:06:39 +0400 Subject: [PATCH 085/148] fix initilization --- contracts/BridgingManager.sol | 13 +- contracts/optimism/L1LidoTokensBridge.sol | 16 +- .../optimism/L2ERC20ExtendedTokensBridge.sol | 16 +- contracts/stubs/BridgingManagerStub.sol | 13 ++ contracts/token/ERC20BridgedPermit.sol | 18 +- contracts/token/ERC20Metadata.sol | 4 + .../token/ERC20RebasableBridgedPermit.sol | 6 +- contracts/utils/Versioned.sol | 4 +- .../BridgingManager.unit.test.ts | 126 ++++-------- ...est.ts => ERC20BridgedPermit.unit.test.ts} | 131 ++++++++---- ... ERC20RebasableBridgedPermit.unit.test.ts} | 186 ++++++++++-------- 11 files changed, 299 insertions(+), 234 deletions(-) create mode 100644 contracts/stubs/BridgingManagerStub.sol rename test/token/{ERC20Bridged.unit.test.ts => ERC20BridgedPermit.unit.test.ts} (80%) rename test/token/{ERC20Rebasable.unit.test.ts => ERC20RebasableBridgedPermit.unit.test.ts} (91%) diff --git a/contracts/BridgingManager.sol b/contracts/BridgingManager.sol index 573e9000..4a2cffd4 100644 --- a/contracts/BridgingManager.sol +++ b/contracts/BridgingManager.sol @@ -13,7 +13,7 @@ contract BridgingManager is AccessControl { /// @param isDepositsEnabled Stores the state of the deposits /// @param isWithdrawalsEnabled Stores the state of the withdrawals struct State { - bool isInitialized; + bool isInitialized; /// @dev DEPRECATED since bridges have their own code for versioning. bool isDepositsEnabled; bool isWithdrawalsEnabled; } @@ -35,7 +35,12 @@ contract BridgingManager is AccessControl { /// @dev This method might be called only once /// @param admin_ Address of the account to grant the DEFAULT_ADMIN_ROLE function _initialize(address admin_) internal { + State storage s = _loadState(); + if (s.isInitialized) { + revert ErrorAlreadyInitialized(); + } _setupRole(DEFAULT_ADMIN_ROLE, admin_); + s.isInitialized = true; emit Initialized(admin_); } @@ -92,6 +97,11 @@ contract BridgingManager is AccessControl { emit WithdrawalsDisabled(msg.sender); } + function _isBridgingManagerInitialized() internal view returns (bool) { + State storage s = _loadState(); + return s.isInitialized; + } + /// @dev Returns the reference to the slot with State struct function _loadState() private pure returns (State storage r) { bytes32 slot = STATE_SLOT; @@ -127,4 +137,5 @@ contract BridgingManager is AccessControl { error ErrorWithdrawalsEnabled(); error ErrorWithdrawalsDisabled(); error ErrorAlreadyInitialized(); + error ErrorBridgingManagerWasInitialized(); } diff --git a/contracts/optimism/L1LidoTokensBridge.sol b/contracts/optimism/L1LidoTokensBridge.sol index 49ce988c..2f975994 100644 --- a/contracts/optimism/L1LidoTokensBridge.sol +++ b/contracts/optimism/L1LidoTokensBridge.sol @@ -4,7 +4,7 @@ pragma solidity 0.8.10; import {L1ERC20ExtendedTokensBridge} from "./L1ERC20ExtendedTokensBridge.sol"; -import {Initializable} from "@openzeppelin/contracts/proxy/utils/Initializable.sol"; +import {Versioned} from "../utils/Versioned.sol"; /// @author kovalgek /// @notice A subset of wstETH token interface of core LIDO protocol. @@ -16,7 +16,7 @@ interface IERC20WstETH { /// @author kovalgek /// @notice Hides wstETH concept from other contracts to keep `L1ERC20ExtendedTokensBridge` reusable. -contract L1LidoTokensBridge is L1ERC20ExtendedTokensBridge, Initializable { +contract L1LidoTokensBridge is L1ERC20ExtendedTokensBridge, Versioned { /// @param messenger_ L1 messenger address being used for cross-chain communications /// @param l2TokenBridge_ Address of the corresponding L2 bridge @@ -39,15 +39,23 @@ contract L1LidoTokensBridge is L1ERC20ExtendedTokensBridge, Initializable { l2TokenNonRebasable_, l2TokenRebasable_ ) { - _disableInitializers(); } /// @notice Initializes the contract from scratch. /// @param admin_ Address of the account to grant the DEFAULT_ADMIN_ROLE - function initialize(address admin_) external initializer { + function initialize(address admin_) external { + _initializeContractVersionTo(2); _initialize(admin_); } + /// @notice A function to finalize upgrade to v2 (from v1). + function finalizeUpgrade_v2() external { + if(!_isBridgingManagerInitialized()) { + revert ErrorBridgingManagerWasInitialized(); + } + _initializeContractVersionTo(2); + } + function tokenRate() override internal view returns (uint256) { return IERC20WstETH(L1_TOKEN_NON_REBASABLE).stEthPerToken(); } diff --git a/contracts/optimism/L2ERC20ExtendedTokensBridge.sol b/contracts/optimism/L2ERC20ExtendedTokensBridge.sol index baf7146f..23542a70 100644 --- a/contracts/optimism/L2ERC20ExtendedTokensBridge.sol +++ b/contracts/optimism/L2ERC20ExtendedTokensBridge.sol @@ -16,7 +16,7 @@ import {BridgingManager} from "../BridgingManager.sol"; import {RebasableAndNonRebasableTokens} from "./RebasableAndNonRebasableTokens.sol"; import {CrossDomainEnabled} from "./CrossDomainEnabled.sol"; import {DepositDataCodec} from "../lib/DepositDataCodec.sol"; -import {Initializable} from "@openzeppelin/contracts/proxy/utils/Initializable.sol"; +import {Versioned} from "../utils/Versioned.sol"; /// @author psirex, kovalgek /// @notice The L2 token bridge works with the L1 token bridge to enable ERC20 token bridging @@ -29,7 +29,7 @@ contract L2ERC20ExtendedTokensBridge is BridgingManager, RebasableAndNonRebasableTokens, CrossDomainEnabled, - Initializable + Versioned { using SafeERC20 for IERC20; @@ -55,15 +55,23 @@ contract L2ERC20ExtendedTokensBridge is l2TokenRebasable_ ) { L1_TOKEN_BRIDGE = l1TokenBridge_; - _disableInitializers(); } /// @notice Initializes the contract from scratch. /// @param admin_ Address of the account to grant the DEFAULT_ADMIN_ROLE - function initialize(address admin_) external initializer { + function initialize(address admin_) external { + _initializeContractVersionTo(2); _initialize(admin_); } + /// @notice A function to finalize upgrade to v2 (from v1). + function finalizeUpgrade_v2() external { + if(!_isBridgingManagerInitialized()) { + revert ErrorBridgingManagerWasInitialized(); + } + _initializeContractVersionTo(2); + } + /// @inheritdoc IL2ERC20Bridge function l1TokenBridge() external view returns (address) { return L1_TOKEN_BRIDGE; diff --git a/contracts/stubs/BridgingManagerStub.sol b/contracts/stubs/BridgingManagerStub.sol new file mode 100644 index 00000000..3b02242c --- /dev/null +++ b/contracts/stubs/BridgingManagerStub.sol @@ -0,0 +1,13 @@ +// SPDX-FileCopyrightText: 2024 Lido +// SPDX-License-Identifier: GPL-3.0 + +import {BridgingManager} from "../BridgingManager.sol"; + +pragma solidity 0.8.10; + +/// @dev For testing purposes. +contract BridgingManagerStub is BridgingManager { + function initialize(address admin_) external { + _initialize(admin_); + } +} diff --git a/contracts/token/ERC20BridgedPermit.sol b/contracts/token/ERC20BridgedPermit.sol index 9bf241d5..b4318164 100644 --- a/contracts/token/ERC20BridgedPermit.sol +++ b/contracts/token/ERC20BridgedPermit.sol @@ -6,10 +6,9 @@ pragma solidity 0.8.10; import {ERC20Bridged} from "./ERC20Bridged.sol"; import {PermitExtension} from "./PermitExtension.sol"; import {Versioned} from "../utils/Versioned.sol"; -import {Initializable} from "@openzeppelin/contracts/proxy/utils/Initializable.sol"; /// @author kovalgek -contract ERC20BridgedPermit is ERC20Bridged, PermitExtension, Versioned, Initializable { +contract ERC20BridgedPermit is ERC20Bridged, PermitExtension, Versioned { /// @param name_ The name of the token /// @param symbol_ The symbol of the token @@ -26,32 +25,27 @@ contract ERC20BridgedPermit is ERC20Bridged, PermitExtension, Versioned, Initial ERC20Bridged(name_, symbol_, decimals_, bridge_) PermitExtension(name_, version_) { - _disableInitializers(); } /// @notice Initializes the contract from scratch. /// @param name_ The name of the token /// @param symbol_ The symbol of the token /// @param version_ The version of the token - function initialize(string memory name_, string memory symbol_, string memory version_) external initializer { + function initialize(string memory name_, string memory symbol_, string memory version_) external { _initializeERC20Metadata(name_, symbol_); _initialize_v2(name_, version_); } /// @notice A function to finalize upgrade to v2 (from v1). function finalizeUpgrade_v2(string memory name_, string memory version_) external { - _checkContractVersion(0); - - // check if name and symbol from ERCMetadata wasn't set up in storage, - // then current contract is not in v1 - if (bytes(name()).length == 0 || bytes(symbol()).length == 0) { - revert ErrorFailedToUpgrade(); + if (!_isMetadataInitialized()) { + revert ErrorMetadataNotInitialized(); } _initialize_v2(name_, version_); } function _initialize_v2(string memory name_, string memory version_) internal { - _setContractVersion(2); + _initializeContractVersionTo(2); _initializeEIP5267Metadata(name_, version_); } @@ -60,5 +54,5 @@ contract ERC20BridgedPermit is ERC20Bridged, PermitExtension, Versioned, Initial _approve(owner_, spender_, amount_); } - error ErrorFailedToUpgrade(); + error ErrorMetadataNotInitialized(); } diff --git a/contracts/token/ERC20Metadata.sol b/contracts/token/ERC20Metadata.sol index 465130a8..16bf3e88 100644 --- a/contracts/token/ERC20Metadata.sol +++ b/contracts/token/ERC20Metadata.sol @@ -68,6 +68,10 @@ contract ERC20Metadata is IERC20Metadata { _loadDynamicMetadata().symbol = symbol_; } + function _isMetadataInitialized() internal view returns (bool) { + return bytes(name()).length != 0 && bytes(symbol()).length != 0; + } + /// @dev Returns the reference to the slot with DynamicMetadata struct function _loadDynamicMetadata() private diff --git a/contracts/token/ERC20RebasableBridgedPermit.sol b/contracts/token/ERC20RebasableBridgedPermit.sol index bfd2dcb6..1e2ee825 100644 --- a/contracts/token/ERC20RebasableBridgedPermit.sol +++ b/contracts/token/ERC20RebasableBridgedPermit.sol @@ -6,10 +6,9 @@ pragma solidity 0.8.10; import {ERC20RebasableBridged} from "./ERC20RebasableBridged.sol"; import {PermitExtension} from "./PermitExtension.sol"; import {Versioned} from "../utils/Versioned.sol"; -import {Initializable} from "@openzeppelin/contracts/proxy/utils/Initializable.sol"; /// @author kovalgek -contract ERC20RebasableBridgedPermit is ERC20RebasableBridged, PermitExtension, Versioned, Initializable { +contract ERC20RebasableBridgedPermit is ERC20RebasableBridged, PermitExtension, Versioned { /// @param name_ The name of the token /// @param symbol_ The symbol of the token @@ -30,14 +29,13 @@ contract ERC20RebasableBridgedPermit is ERC20RebasableBridged, PermitExtension, ERC20RebasableBridged(name_, symbol_, decimals_, tokenToWrapFrom_, tokenRateOracle_, bridge_) PermitExtension(name_, version_) { - _disableInitializers(); } /// @notice Initializes the contract from scratch. /// @param name_ The name of the token /// @param symbol_ The symbol of the token /// @param version_ The version of the token - function initialize(string memory name_, string memory symbol_, string memory version_) external initializer { + function initialize(string memory name_, string memory symbol_, string memory version_) external { _initializeContractVersionTo(1); _initializeERC20Metadata(name_, symbol_); _initializeEIP5267Metadata(name_, version_); diff --git a/contracts/utils/Versioned.sol b/contracts/utils/Versioned.sol index 59c16c7a..12e689f4 100644 --- a/contracts/utils/Versioned.sol +++ b/contracts/utils/Versioned.sol @@ -5,6 +5,8 @@ pragma solidity 0.8.10; import {UnstructuredStorage} from "../lib//UnstructuredStorage.sol"; +/// @dev A copy of Versioned.sol contract from Lido Core Protocol +/// https://github.com/lidofinance/lido-dao/blob/master/contracts/0.8.9/utils/Versioned.sol contract Versioned { using UnstructuredStorage for bytes32; @@ -53,7 +55,7 @@ contract Versioned { _setContractVersion(newVersion); } - function _setContractVersion(uint256 version) internal { + function _setContractVersion(uint256 version) private { CONTRACT_VERSION_POSITION.setStorageUint256(version); emit ContractVersionSet(version); } diff --git a/test/bridging-manager/BridgingManager.unit.test.ts b/test/bridging-manager/BridgingManager.unit.test.ts index 03f49ab4..d2afc53d 100644 --- a/test/bridging-manager/BridgingManager.unit.test.ts +++ b/test/bridging-manager/BridgingManager.unit.test.ts @@ -1,15 +1,10 @@ import hre from "hardhat"; import { - BridgingManager__factory, + BridgingManagerStub__factory, OssifiableProxy__factory, - L1LidoTokensBridge__factory, - ERC20WrapperStub__factory, - ERC20BridgedStub__factory, - CrossDomainMessengerStub__factory, } from "../../typechain"; import { assert } from "chai"; import { unit } from "../../utils/testing"; -import { wei } from "../../utils/wei"; unit("BridgingManager", ctxFactory) .test("isInitialized() :: on uninitialized contract", async (ctx) => { @@ -24,36 +19,36 @@ unit("BridgingManager", ctxFactory) assert.isFalse(await ctx.bridgingManagerRaw.isWithdrawalsEnabled()); }) -// .test("initialize() :: on uninitialized contract", async (ctx) => { -// const { -// bridgingManagerRaw, -// roles: { DEFAULT_ADMIN_ROLE }, -// accounts: { stranger }, -// } = ctx; -// // validate that bridgingManager is not initialized -// assert.isFalse(await bridgingManagerRaw.isInitialized()); - -// // validate that stranger has no DEFAULT_ADMIN_ROLE -// assert.isFalse( -// await bridgingManagerRaw.hasRole(DEFAULT_ADMIN_ROLE, stranger.address) -// ); -// // initialize() might be called by anyone -// await bridgingManagerRaw.connect(stranger).initialize(stranger.address); - -// // validate that isInitialized() is true -// assert.isTrue(await bridgingManagerRaw.isInitialized()); - -// // validate that stranger has DEFAULT_ADMIN_RULE -// assert.isTrue( -// await bridgingManagerRaw.hasRole(DEFAULT_ADMIN_ROLE, stranger.address) -// ); - -// // validate that initialize() might not be called second time -// await assert.revertsWith( -// bridgingManagerRaw.connect(stranger).initialize(stranger.address), -// "ErrorAlreadyInitialized()" -// ); -// }) + .test("initialize() :: on uninitialized contract", async (ctx) => { + const { + bridgingManagerRaw, + roles: { DEFAULT_ADMIN_ROLE }, + accounts: { stranger }, + } = ctx; + // validate that bridgingManager is not initialized + assert.isFalse(await bridgingManagerRaw.isInitialized()); + + // validate that stranger has no DEFAULT_ADMIN_ROLE + assert.isFalse( + await bridgingManagerRaw.hasRole(DEFAULT_ADMIN_ROLE, stranger.address) + ); + // initialize() might be called by anyone + await bridgingManagerRaw.connect(stranger).initialize(stranger.address); + + // validate that isInitialized() is true + assert.isTrue(await bridgingManagerRaw.isInitialized()); + + // validate that stranger has DEFAULT_ADMIN_RULE + assert.isTrue( + await bridgingManagerRaw.hasRole(DEFAULT_ADMIN_ROLE, stranger.address) + ); + + // validate that initialize() might not be called second time + await assert.revertsWith( + bridgingManagerRaw.connect(stranger).initialize(stranger.address), + "ErrorAlreadyInitialized()" + ); + }) .test("enableDeposits() :: role is not granted", async (ctx) => { const { @@ -310,55 +305,18 @@ async function ctxFactory() { l2TokenBridgeEOA ] = await hre.ethers.getSigners(); -// const bridgingManagerImpl = await new BridgingManager__factory( -// deployer -// ).deploy(); - -const l1TokenRebasableStub = await new ERC20BridgedStub__factory(deployer).deploy( - "L1 Token Rebasable", - "L1R" -); - -const l1TokenNonRebasableStub = await new ERC20WrapperStub__factory(deployer).deploy( - l1TokenRebasableStub.address, - "L1 Token Non Rebasable", - "L1NR" -); - -const l2TokenNonRebasableStub = await new ERC20BridgedStub__factory(deployer).deploy( - "L2 Token Non Rebasable", - "L2NR" -); - -const l2TokenRebasableStub = await new ERC20WrapperStub__factory(deployer).deploy( - l2TokenNonRebasableStub.address, - "L2 Token Rebasable", - "L2R" -); - -const l1MessengerStub = await new CrossDomainMessengerStub__factory( - deployer -).deploy({ value: wei.toBigNumber(wei`1 ether`) }); - - const l1TokenBridgeImpl = await new L1LidoTokensBridge__factory( + const bridgingManagerImpl = await new BridgingManagerStub__factory( deployer -).deploy( - l1MessengerStub.address, - l2TokenBridgeEOA.address, - l1TokenNonRebasableStub.address, - l1TokenRebasableStub.address, - l2TokenNonRebasableStub.address, - l2TokenRebasableStub.address -); + ).deploy(); const pureOssifiableProxy = await new OssifiableProxy__factory( deployer - ).deploy(l1TokenBridgeImpl.address, deployer.address, "0x"); + ).deploy(bridgingManagerImpl.address, deployer.address, "0x"); const initializedOssifiableProxy = await new OssifiableProxy__factory( deployer - ).deploy(l1TokenBridgeImpl.address, deployer.address, "0x"); + ).deploy(bridgingManagerImpl.address, deployer.address, "0x"); - const bridgingManager = L1LidoTokensBridge__factory.connect( + const bridgingManager = BridgingManagerStub__factory.connect( initializedOssifiableProxy.address, deployer ); @@ -371,11 +329,11 @@ const l1MessengerStub = await new CrossDomainMessengerStub__factory( WITHDRAWALS_ENABLER_ROLE, WITHDRAWALS_DISABLER_ROLE, ] = await Promise.all([ - await l1TokenBridgeImpl.DEFAULT_ADMIN_ROLE(), - await l1TokenBridgeImpl.DEPOSITS_ENABLER_ROLE(), - await l1TokenBridgeImpl.DEPOSITS_DISABLER_ROLE(), - await l1TokenBridgeImpl.WITHDRAWALS_ENABLER_ROLE(), - await l1TokenBridgeImpl.WITHDRAWALS_DISABLER_ROLE(), + await bridgingManagerImpl.DEFAULT_ADMIN_ROLE(), + await bridgingManagerImpl.DEPOSITS_ENABLER_ROLE(), + await bridgingManagerImpl.DEPOSITS_DISABLER_ROLE(), + await bridgingManagerImpl.WITHDRAWALS_ENABLER_ROLE(), + await bridgingManagerImpl.WITHDRAWALS_DISABLER_ROLE(), ]); await bridgingManager.grantRole( DEPOSITS_ENABLER_ROLE, @@ -411,7 +369,7 @@ const l1MessengerStub = await new CrossDomainMessengerStub__factory( withdrawalsDisabler, }, bridgingManager, - bridgingManagerRaw: L1LidoTokensBridge__factory.connect( + bridgingManagerRaw: BridgingManagerStub__factory.connect( pureOssifiableProxy.address, deployer ), diff --git a/test/token/ERC20Bridged.unit.test.ts b/test/token/ERC20BridgedPermit.unit.test.ts similarity index 80% rename from test/token/ERC20Bridged.unit.test.ts rename to test/token/ERC20BridgedPermit.unit.test.ts index b49a1c91..60399871 100644 --- a/test/token/ERC20Bridged.unit.test.ts +++ b/test/token/ERC20BridgedPermit.unit.test.ts @@ -7,48 +7,101 @@ import { import { unit } from "../../utils/testing"; import { wei } from "../../utils/wei"; -unit("ERC20Bridged", ctxFactory) - .test("bridge()", async (ctx) => { - assert.equal(await ctx.erc20Bridged.bridge(), ctx.accounts.owner.address); +unit("ERC20BridgedPermit", ctxFactory) + .test("initial state", async (ctx) => { + const { erc20Bridged } = ctx; + const { decimals, name, symbol, version, premint } = ctx.constants; + const { owner } = ctx.accounts; + const [,eip712Name,eip712Version,,,,] = await erc20Bridged.eip712Domain(); + + assert.equal(eip712Name, name); + assert.equal(eip712Version, version); + assert.equal(await erc20Bridged.name(), name); + assert.equal(await erc20Bridged.symbol(), symbol); + assert.equalBN(await erc20Bridged.decimals(), decimals); + assert.equal(await erc20Bridged.bridge(), owner.address); + assert.equalBN(await erc20Bridged.totalSupply(), premint); }) - .test("totalSupply()", async (ctx) => { - assert.equalBN(await ctx.erc20Bridged.totalSupply(), ctx.constants.premint); + .test("initialize() :: petrified", async (ctx) => { + const { deployer, owner } = ctx.accounts; + + // deploy new implementation + const erc20BridgedImpl = await new ERC20BridgedPermit__factory(deployer).deploy( + "name", + "symbol", + "version", + 9, + owner.address + ); + + const petrifiedVersionMark = hre.ethers.constants.MaxUint256; + assert.equalBN(await erc20BridgedImpl.getContractVersion(), petrifiedVersionMark); + + await assert.revertsWith( + erc20BridgedImpl.initialize("name", "symbol", "version"), + "NonZeroContractVersionOnInit()" + ); + }) + + .test("initialize() :: reinit", async (ctx) => { + const { deployer, owner, holder } = ctx.accounts; + const { name, symbol, version } = ctx.constants; + + // deploy new implementation + const l2TokenImpl = await new ERC20BridgedPermit__factory(deployer).deploy( + "", + "Symbol", + "", + 9, + owner.address + ); + + const l2TokensProxy = await new OssifiableProxy__factory(deployer).deploy( + l2TokenImpl.address, + deployer.address, + ERC20BridgedPermit__factory.createInterface().encodeFunctionData("initialize", [ + name, + symbol, + version + ]) + ); + + const erc20BridgedProxied = ERC20BridgedPermit__factory.connect( + l2TokensProxy.address, + holder + ); + + assert.equalBN(await erc20BridgedProxied.getContractVersion(), 2); + + await assert.revertsWith( + erc20BridgedProxied.initialize(name, symbol, version), + "NonZeroContractVersionOnInit()" + ); }) -// .test("initialize() :: name already set", async (ctx) => { -// const { deployer, owner } = ctx.accounts; - -// // deploy new implementation -// const erc20BridgedImpl = await new ERC20BridgedPermit__factory(deployer).deploy( -// "Name", -// "", -// "", -// 9, -// owner.address -// ); -// await assert.revertsWith( -// erc20BridgedImpl.initialize("New Name", "", ""), -// "ErrorNameAlreadySet()" -// ); -// }) - -// .test("initialize() :: symbol already set", async (ctx) => { -// const { deployer, owner } = ctx.accounts; - -// // deploy new implementation -// const erc20BridgedImpl = await new ERC20BridgedPermit__factory(deployer).deploy( -// "", -// "Symbol", -// "", -// 9, -// owner.address -// ); -// await assert.revertsWith( -// erc20BridgedImpl.initialize("", "New Symbol", ""), -// "ErrorSymbolAlreadySet()" -// ); -// }) + .test("finalizeUpgrade_v2() :: metadata uninitialized", async (ctx) => { + const { deployer, owner } = ctx.accounts; + const { name, version } = ctx.constants; + + // deploy new implementation + const l2TokenImpl = await new ERC20BridgedPermit__factory(deployer).deploy( + "", + "Symbol", + "", + 9, + owner.address + ); + + await assert.revertsWith(new OssifiableProxy__factory(deployer).deploy( + l2TokenImpl.address, + deployer.address, + ERC20BridgedPermit__factory.createInterface().encodeFunctionData("finalizeUpgrade_v2", [ + name, + version + ]) + ), "ErrorMetadataNotInitialized()"); + }) .test("approve()", async (ctx) => { const { erc20Bridged } = ctx; @@ -469,7 +522,7 @@ async function ctxFactory() { return { accounts: { deployer, owner, recipient, spender, holder, zero, stranger }, - constants: { name, symbol, decimals, premint }, + constants: { name, symbol, version, decimals, premint }, erc20Bridged: erc20BridgedProxied, }; } diff --git a/test/token/ERC20Rebasable.unit.test.ts b/test/token/ERC20RebasableBridgedPermit.unit.test.ts similarity index 91% rename from test/token/ERC20Rebasable.unit.test.ts rename to test/token/ERC20RebasableBridgedPermit.unit.test.ts index 88f1a2a7..9d7d4a1f 100644 --- a/test/token/ERC20Rebasable.unit.test.ts +++ b/test/token/ERC20RebasableBridgedPermit.unit.test.ts @@ -1,103 +1,119 @@ import hre from "hardhat"; import { assert } from "chai"; +import { BigNumber } from "ethers"; import { unit } from "../../utils/testing"; import { wei } from "../../utils/wei"; - import { ERC20BridgedPermit__factory, TokenRateOracle__factory, ERC20RebasableBridgedPermit__factory, - OssifiableProxy__factory, - CrossDomainMessengerStub__factory + OssifiableProxy__factory } from "../../typechain"; -import { BigNumber } from "ethers"; -unit("ERC20RebasableBridged", ctxFactory) - - .test("wrappedToken() :: has the same address is in constructor", async (ctx) => { - const { rebasableProxied, wrappedToken } = ctx.contracts; - assert.equal(await rebasableProxied.TOKEN_TO_WRAP_FROM(), wrappedToken.address) +unit("ERC20RebasableBridgedPermit", ctxFactory) + + .test("initial state", async (ctx) => { + const { rebasableProxied, wrappedToken, tokenRateOracle } = ctx.contracts; + const { owner } = ctx.accounts; + const { decimalsToSet, name, symbol, version } = ctx.constants; + const [,eip712Name,eip712Version,,,,] = await rebasableProxied.eip712Domain(); + assert.equal(eip712Name, name); + assert.equal(eip712Version, version); + assert.equal(await rebasableProxied.name(), name); + assert.equal(await rebasableProxied.symbol(), symbol) + assert.equalBN(await rebasableProxied.decimals(), decimalsToSet) + assert.equal(await rebasableProxied.TOKEN_TO_WRAP_FROM(), wrappedToken.address); + assert.equal(await rebasableProxied.TOKEN_RATE_ORACLE(), tokenRateOracle.address); + assert.equal(await rebasableProxied.L2_ERC20_TOKEN_BRIDGE(), owner.address); }) - .test("tokenRateOracle() :: has the same address is in constructor", async (ctx) => { - const { rebasableProxied, tokenRateOracle } = ctx.contracts; - assert.equal(await rebasableProxied.TOKEN_RATE_ORACLE(), tokenRateOracle.address) + .test("initialize() :: failed to init implementation", async (ctx) => { + const { deployer, owner, zero } = ctx.accounts; + const { decimalsToSet } = ctx.constants; + + // deploy new implementation + const wrappedToken = await new ERC20BridgedPermit__factory(deployer).deploy( + "WsETH Test Token", + "WsETH", + "1", + decimalsToSet, + owner.address + ); + const tokenRateOracle = await new TokenRateOracle__factory(deployer).deploy( + zero.address, + owner.address, + zero.address, + 86400 + ); + const rebasableTokenImpl = await new ERC20RebasableBridgedPermit__factory(deployer).deploy( + "stETH Test Token", + "stETH", + "1", + 10, + wrappedToken.address, + tokenRateOracle.address, + owner.address + ); + + const petrifiedVersionMark = hre.ethers.constants.MaxUint256; + assert.equalBN(await rebasableTokenImpl.getContractVersion(), petrifiedVersionMark); + + await assert.revertsWith( + rebasableTokenImpl.initialize("name", "symbol", "version"), + "NonZeroContractVersionOnInit()" + ); }) - .test("name() :: has the same value is in constructor", async (ctx) => - assert.equal(await ctx.contracts.rebasableProxied.name(), ctx.constants.name) - ) + .test("initialize() :: reinitilization", async (ctx) => { + const { deployer, owner, zero, holder } = ctx.accounts; + const { decimalsToSet, name, symbol, version } = ctx.constants; - .test("symbol() :: has the same value is in constructor", async (ctx) => - assert.equal(await ctx.contracts.rebasableProxied.symbol(), ctx.constants.symbol) - ) + // deploy new implementation + const wrappedToken = await new ERC20BridgedPermit__factory(deployer).deploy( + "WsETH Test Token", + "WsETH", + "1", + decimalsToSet, + owner.address + ); + const tokenRateOracle = await new TokenRateOracle__factory(deployer).deploy( + zero.address, + owner.address, + zero.address, + 86400 + ); + const rebasableTokenImpl = await new ERC20RebasableBridgedPermit__factory(deployer).deploy( + "", + "symbol", + "1", + 10, + wrappedToken.address, + tokenRateOracle.address, + owner.address + ); -// .test("initialize() :: name already set", async (ctx) => { -// const { deployer, owner, zero } = ctx.accounts; -// const { decimalsToSet } = ctx.constants; - -// // deploy new implementation -// const wrappedToken = await new ERC20BridgedPermit__factory(deployer).deploy( -// "WsETH Test Token", -// "WsETH", -// "1", -// decimalsToSet, -// owner.address -// ); - -// const tokenRateOracle = await new TokenRateOracle__factory(deployer).deploy( -// zero.address, -// owner.address, -// zero.address, -// 86400 -// ); -// const rebasableTokenImpl = await new ERC20RebasableBridgedPermit__factory(deployer).deploy( -// "name", -// "", -// "", -// 10, -// wrappedToken.address, -// tokenRateOracle.address, -// owner.address -// ); -// await assert.revertsWith( -// rebasableTokenImpl.initialize("New Name", "", ""), -// "ErrorNameAlreadySet()" -// ); -// }) - -// .test("initialize() :: symbol already set", async (ctx) => { -// const { deployer, owner, zero } = ctx.accounts; -// const { decimalsToSet } = ctx.constants; - -// // deploy new implementation -// const wrappedToken = await new ERC20BridgedPermit__factory(deployer).deploy( -// "WsETH Test Token", -// "WsETH", -// "1", -// decimalsToSet, -// owner.address -// ); -// const tokenRateOracle = await new TokenRateOracle__factory(deployer).deploy( -// zero.address, -// owner.address, -// zero.address, -// 86400 -// ); -// const rebasableTokenImpl = await new ERC20RebasableBridgedPermit__factory(deployer).deploy( -// "", -// "symbol", -// "1", -// 10, -// wrappedToken.address, -// tokenRateOracle.address, -// owner.address -// ); -// await assert.revertsWith( -// rebasableTokenImpl.initialize("", "New Symbol", "1"), -// "ErrorSymbolAlreadySet()" -// ); -// }) + const l2TokensProxy = await new OssifiableProxy__factory(deployer).deploy( + rebasableTokenImpl.address, + deployer.address, + ERC20RebasableBridgedPermit__factory.createInterface().encodeFunctionData("initialize", [ + name, + symbol, + version + ]) + ); + + const rebasableProxied = ERC20RebasableBridgedPermit__factory.connect( + l2TokensProxy.address, + holder + ); + + assert.equalBN(await rebasableProxied.getContractVersion(), 1); + + await assert.revertsWith( + rebasableProxied.initialize(name, symbol, version), + "NonZeroContractVersionOnInit()" + ); + }) .test("decimals() :: has the same value as is in constructor", async (ctx) => assert.equal(await ctx.contracts.rebasableProxied.decimals(), ctx.constants.decimalsToSet) @@ -1177,7 +1193,7 @@ async function ctxFactory() { return { accounts: { deployer, owner, recipient, spender, holder, stranger, zero, user1, user2 }, - constants: { name, symbol, decimalsToSet, decimals, premintShares, premintTokens, rate, blockTimestamp }, + constants: { name, symbol, version, decimalsToSet, decimals, premintShares, premintTokens, rate, blockTimestamp }, contracts: { rebasableProxied, wrappedToken, tokenRateOracle } }; } From fad1a4e7b8b52b75a03352a43d98b768bf8534ae Mon Sep 17 00:00:00 2001 From: Anton Kovalchuk Date: Mon, 22 Apr 2024 22:01:17 +0400 Subject: [PATCH 086/148] fix token rate oracle according to feedback --- contracts/optimism/TokenRateOracle.sol | 163 +++++++++++++------------ 1 file changed, 87 insertions(+), 76 deletions(-) diff --git a/contracts/optimism/TokenRateOracle.sol b/contracts/optimism/TokenRateOracle.sol index ddb1a187..31483913 100644 --- a/contracts/optimism/TokenRateOracle.sol +++ b/contracts/optimism/TokenRateOracle.sol @@ -6,7 +6,6 @@ pragma solidity 0.8.10; import {ITokenRateUpdatable} from "./interfaces/ITokenRateUpdatable.sol"; import {IChainlinkAggregatorInterface} from "./interfaces/IChainlinkAggregatorInterface.sol"; import {CrossDomainEnabled} from "./CrossDomainEnabled.sol"; -import {Initializable} from "@openzeppelin/contracts/proxy/utils/Initializable.sol"; import {Versioned} from "../utils/Versioned.sol"; interface ITokenRateOracle is ITokenRateUpdatable, IChainlinkAggregatorInterface {} @@ -14,7 +13,7 @@ interface ITokenRateOracle is ITokenRateUpdatable, IChainlinkAggregatorInterface /// @author kovalgek /// @notice Oracle for storing token rate. /// @dev Token rate updates can be delivered from two sources: L1 token rate pusher and L2 bridge. -contract TokenRateOracle is CrossDomainEnabled, ITokenRateOracle, Versioned, Initializable { +contract TokenRateOracle is CrossDomainEnabled, ITokenRateOracle, Versioned { /// @dev Stores the dynamic data of the oracle. Allows safely use of this /// contract with upgradable proxies @@ -38,15 +37,16 @@ contract TokenRateOracle is CrossDomainEnabled, ITokenRateOracle, Versioned, Ini /// @notice A time period when token rate can be considered outdated. uint256 public immutable TOKEN_RATE_OUTDATED_DELAY; - /// @notice A time period when token rate can be considered outdated. - uint256 public constant MAX_ALLOWED_L1_L2_TIME_DIFFERENCE = 86400; + /// @notice A time difference between received l1Timestamp and L2 block.timestamp + /// when token rate can be considered outdated. + uint256 public immutable MAX_ALLOWED_L2_TO_L1_CLOCK_LAG; + + /// @notice Allowed token rate deviation per day in basic points. + uint256 public immutable MAX_ALLOWED_TOKEN_RATE_DEVIATION_PER_DAY; /// @notice Number of seconds in one day. uint256 public constant ONE_DAY_SECONDS = 86400; - /// @notice Allowed token rate deviation per day in basic points. - uint256 public constant TOKEN_RATE_DEVIATION = 500; - /// @notice Decimals of the oracle response. uint8 public constant DECIMALS = 18; @@ -57,22 +57,27 @@ contract TokenRateOracle is CrossDomainEnabled, ITokenRateOracle, Versioned, Ini /// @param l2ERC20TokenBridge_ the bridge address that has a right to updates oracle. /// @param l1TokenRatePusher_ An address of account on L1 that can update token rate. /// @param tokenRateOutdatedDelay_ time period when token rate can be considered outdated. + /// @param maxAllowedL2ToL1ClockLag_ A time difference between received l1Timestamp and L2 block.timestamp + /// when token rate can be considered outdated. + /// @param maxAllowedTokenRateDeviationPerDay_ Allowed token rate deviation per day in basic points. constructor( address messenger_, address l2ERC20TokenBridge_, address l1TokenRatePusher_, - uint256 tokenRateOutdatedDelay_ + uint256 tokenRateOutdatedDelay_, + uint256 maxAllowedL2ToL1ClockLag_, + uint256 maxAllowedTokenRateDeviationPerDay_ ) CrossDomainEnabled(messenger_) { L2_ERC20_TOKEN_BRIDGE = l2ERC20TokenBridge_; L1_TOKEN_RATE_PUSHER = l1TokenRatePusher_; TOKEN_RATE_OUTDATED_DELAY = tokenRateOutdatedDelay_; - - _disableInitializers(); + MAX_ALLOWED_L2_TO_L1_CLOCK_LAG = maxAllowedL2ToL1ClockLag_; + MAX_ALLOWED_TOKEN_RATE_DEVIATION_PER_DAY = maxAllowedTokenRateDeviationPerDay_; } - function initialize(uint256 tokenRate_, uint256 rateL1Timestamp_) external initializer { + function initialize(uint256 tokenRate_, uint256 rateL1Timestamp_) external { _initializeContractVersionTo(1); - _updateRate(tokenRate_, rateL1Timestamp_); + _setTokenRateAndL1Timestamp(uint192(tokenRate_), uint64(rateL1Timestamp_)); } /// @inheritdoc IChainlinkAggregatorInterface @@ -83,20 +88,20 @@ contract TokenRateOracle is CrossDomainEnabled, ITokenRateOracle, Versioned, Ini uint256 updatedAt_, uint80 answeredInRound_ ) { - uint80 roundId = uint80(_rateL1Timestamp()); + uint80 roundId = uint80(_getRateL1Timestamp()); return ( roundId, - int256(uint256(_tokenRate())), - _rateL1Timestamp(), - _rateL1Timestamp(), + int256(uint256(_getTokenRate())), + _getRateL1Timestamp(), + _getRateL1Timestamp(), roundId ); } /// @inheritdoc IChainlinkAggregatorInterface function latestAnswer() external view returns (int256) { - return int256(uint256(_tokenRate())); + return int256(uint256(_getTokenRate())); } /// @inheritdoc IChainlinkAggregatorInterface @@ -105,106 +110,112 @@ contract TokenRateOracle is CrossDomainEnabled, ITokenRateOracle, Versioned, Ini } /// @inheritdoc ITokenRateUpdatable - function updateRate(uint256 tokenRate_, uint256 rateL1Timestamp_) external onlyAuthorized { - _updateRate(tokenRate_, rateL1Timestamp_); - } - - /// @notice Returns flag that shows that token rate can be considered outdated. - function isLikelyOutdated() external view returns (bool) { - return block.timestamp - _rateL1Timestamp() > TOKEN_RATE_OUTDATED_DELAY; - } - - function _updateRate(uint256 tokenRate_, uint256 rateL1Timestamp_) internal { - if (rateL1Timestamp_ < _rateL1Timestamp()) { - emit ATryToUpdateTokenRateWithOutdatedTime(tokenRate_, _rateL1Timestamp(), rateL1Timestamp_); - return; - } + function updateRate(uint256 tokenRate_, uint256 rateL1Timestamp_) external onlyBridgeOrTokenRatePusher { - if (rateL1Timestamp_ - block.timestamp > MAX_ALLOWED_L1_L2_TIME_DIFFERENCE) { - revert ErrorInvalidTime(tokenRate_, rateL1Timestamp_); - } - - if (rateL1Timestamp_ > block.timestamp) { - emit TokenRateL1TimestampAheadOfL2Time(); + /// @dev checks if the time difference between L1 and L2 exceeds the configurable threshold + if (rateL1Timestamp_ - block.timestamp > MAX_ALLOWED_L2_TO_L1_CLOCK_LAG) { + revert ErrorL1TimestampExceededAllowedClockLag(tokenRate_, rateL1Timestamp_); } + /// @dev allow token rate to be within some configurable range that depens on time it wasn't updated. if (!_isTokenRateWithinAllowedRange(tokenRate_, rateL1Timestamp_)) { revert ErrorTokenRateIsOutOfRange(tokenRate_, rateL1Timestamp_); } - if (tokenRate_ == _tokenRate() && rateL1Timestamp_ == _rateL1Timestamp()) { + /// @dev use only the more actual token rate + if (rateL1Timestamp_ <= _getRateL1Timestamp()) { + emit DormantTokenRateUpdateIgnored(tokenRate_, _getRateL1Timestamp(), rateL1Timestamp_); return; } - _setTokenRate(uint192(tokenRate_)); - _setRateL1Timestamp(uint64(rateL1Timestamp_)); + /// @dev notify that there is a differnce L1 and L2 time. + if (rateL1Timestamp_ > block.timestamp) { + emit TokenRateL1TimestampAheadOfL2Time(); + } - emit RateUpdated(_tokenRate(), _rateL1Timestamp()); + _setTokenRateAndL1Timestamp(uint192(tokenRate_), uint64(rateL1Timestamp_)); + emit RateUpdated(_getTokenRate(), _getRateL1Timestamp()); } - /// @dev Returns the reference to the slot with TokenRateData struct - function _loadTokenRateData() - private - pure - returns (TokenRateData storage r) - { - bytes32 slot = TOKEN_RATE_DATA_SLOT; - assembly { - r.slot := slot - } + /// @notice Returns flag that shows that token rate can be considered outdated. + function isLikelyOutdated() external view returns (bool) { + return block.timestamp - _getRateL1Timestamp() > TOKEN_RATE_OUTDATED_DELAY; } - function _isTokenRateWithinAllowedRange(uint256 tokenRate_, uint256 rateL1Timestamp_) internal view returns (bool) { - uint256 rateL1TimestampDiff = rateL1Timestamp_ - _rateL1Timestamp(); + /// @dev Allow tokenRate deviation from the previous value to be + /// ±`MAX_ALLOWED_TOKEN_RATE_DEVIATION_PER_DAY`% per day. + function _isTokenRateWithinAllowedRange( + uint256 newTokenRate_, uint256 newRateL1Timestamp_ + ) internal view returns (bool) { + uint256 rateL1TimestampDiff = newRateL1Timestamp_ - _getRateL1Timestamp(); uint256 roundedUpNumberOfDays = rateL1TimestampDiff / ONE_DAY_SECONDS + 1; - uint256 allowedTokenRateDeviation = roundedUpNumberOfDays * TOKEN_RATE_DEVIATION; - uint256 topTokenRateLimitFactor = 1 + allowedTokenRateDeviation / BASIS_POINT_SCALE; - uint256 bottomTokenRateLimitFactor = - allowedTokenRateDeviation <= BASIS_POINT_SCALE ? - (1 - allowedTokenRateDeviation / BASIS_POINT_SCALE) : 0; - - return tokenRate_ <= _tokenRate() * topTokenRateLimitFactor && - tokenRate_ >= _tokenRate() * bottomTokenRateLimitFactor; + uint256 allowedTokenRateDeviation = roundedUpNumberOfDays * MAX_ALLOWED_TOKEN_RATE_DEVIATION_PER_DAY; + uint256 topTokenRateLimit = _getTokenRate() * (BASIS_POINT_SCALE + allowedTokenRateDeviation) / + BASIS_POINT_SCALE; + uint256 bottomTokenRateLimit = 0; + if(allowedTokenRateDeviation <= BASIS_POINT_SCALE) { + bottomTokenRateLimit = (_getTokenRate() * (BASIS_POINT_SCALE - allowedTokenRateDeviation) / + BASIS_POINT_SCALE); + } + + return newTokenRate_ <= topTokenRateLimit && + newTokenRate_ >= bottomTokenRateLimit; } - function _isAuthorized(address caller_) internal view returns (bool) { - if(caller_ == address(MESSENGER) && MESSENGER.xDomainMessageSender() == L1_TOKEN_RATE_PUSHER) { + function _isCallerBridgeOrMessegerWithTokenRatePusher(address caller_) internal view returns (bool) { + if(caller_ == L2_ERC20_TOKEN_BRIDGE) { return true; } - if(caller_ == L2_ERC20_TOKEN_BRIDGE) { + if(caller_ == address(MESSENGER) && MESSENGER.xDomainMessageSender() == L1_TOKEN_RATE_PUSHER) { return true; } return false; } - function _tokenRate() private view returns (uint192) { + function _getTokenRate() private view returns (uint192) { return _loadTokenRateData().tokenRate; } - function _rateL1Timestamp() private view returns (uint64) { + function _getRateL1Timestamp() private view returns (uint64) { return _loadTokenRateData().rateL1Timestamp; } - function _setTokenRate(uint192 tokenRate_) internal { + function _setTokenRateAndL1Timestamp(uint192 tokenRate_, uint64 rateL1Timestamp_) internal { _loadTokenRateData().tokenRate = tokenRate_; + _loadTokenRateData().rateL1Timestamp = rateL1Timestamp_; } - function _setRateL1Timestamp(uint64 rateL1Timestamp_) internal { - _loadTokenRateData().rateL1Timestamp = rateL1Timestamp_; + /// @dev Returns the reference to the slot with TokenRateData struct + function _loadTokenRateData() + private + pure + returns (TokenRateData storage r) + { + bytes32 slot = TOKEN_RATE_DATA_SLOT; + assembly { + r.slot := slot + } } - modifier onlyAuthorized() { - if(!_isAuthorized(msg.sender)) { - revert ErrorNoRights(msg.sender); + modifier onlyBridgeOrTokenRatePusher() { + if(!_isCallerBridgeOrMessegerWithTokenRatePusher(msg.sender)) { + revert ErrorNotBridgeOrTokenRatePusher(); } _; } - event RateUpdated(uint256 tokenRate_, uint256 rateL1Timestamp_); - event ATryToUpdateTokenRateWithOutdatedTime(uint256 tokenRate_, uint256 rateL1Timestamp_, uint256 newTateL1Timestamp_); + event RateUpdated( + uint256 tokenRate_, + uint256 indexed rateL1Timestamp_ + ); + event DormantTokenRateUpdateIgnored( + uint256 tokenRate_, + uint256 indexed currentRateL1Timestamp_, + uint256 indexed newRateL1Timestamp_ + ); event TokenRateL1TimestampAheadOfL2Time(); - error ErrorNoRights(address caller); - error ErrorInvalidTime(uint256 tokenRate_, uint256 rateL1Timestamp_); + error ErrorNotBridgeOrTokenRatePusher(); + error ErrorL1TimestampExceededAllowedClockLag(uint256 tokenRate_, uint256 rateL1Timestamp_); error ErrorTokenRateIsOutOfRange(uint256 tokenRate_, uint256 rateL1Timestamp_); } From 3f2ef873244b83971208efb95da3cf672f16f4d8 Mon Sep 17 00:00:00 2001 From: Anton Kovalchuk Date: Tue, 23 Apr 2024 01:06:45 +0400 Subject: [PATCH 087/148] fix token rate uptading conditions, add tests for initializer --- contracts/optimism/TokenRateOracle.sol | 20 +- .../L2ERC20ExtendedTokensBridge.unit.test.ts | 4 +- test/optimism/TokenRateOracle.unit.test.ts | 241 +++++++++++++----- test/token/ERC20BridgedPermit.unit.test.ts | 2 +- test/token/ERC20Permit.unit.test.ts | 4 +- .../ERC20RebasableBridgedPermit.unit.test.ts | 22 +- 6 files changed, 210 insertions(+), 83 deletions(-) diff --git a/contracts/optimism/TokenRateOracle.sol b/contracts/optimism/TokenRateOracle.sol index 31483913..a71afc3d 100644 --- a/contracts/optimism/TokenRateOracle.sol +++ b/contracts/optimism/TokenRateOracle.sol @@ -113,24 +113,25 @@ contract TokenRateOracle is CrossDomainEnabled, ITokenRateOracle, Versioned { function updateRate(uint256 tokenRate_, uint256 rateL1Timestamp_) external onlyBridgeOrTokenRatePusher { /// @dev checks if the time difference between L1 and L2 exceeds the configurable threshold - if (rateL1Timestamp_ - block.timestamp > MAX_ALLOWED_L2_TO_L1_CLOCK_LAG) { + if (rateL1Timestamp_ > block.timestamp && + rateL1Timestamp_ - block.timestamp > MAX_ALLOWED_L2_TO_L1_CLOCK_LAG) { revert ErrorL1TimestampExceededAllowedClockLag(tokenRate_, rateL1Timestamp_); } - /// @dev allow token rate to be within some configurable range that depens on time it wasn't updated. - if (!_isTokenRateWithinAllowedRange(tokenRate_, rateL1Timestamp_)) { - revert ErrorTokenRateIsOutOfRange(tokenRate_, rateL1Timestamp_); - } - /// @dev use only the more actual token rate if (rateL1Timestamp_ <= _getRateL1Timestamp()) { emit DormantTokenRateUpdateIgnored(tokenRate_, _getRateL1Timestamp(), rateL1Timestamp_); return; } + /// @dev allow token rate to be within some configurable range that depens on time it wasn't updated. + if (!_isTokenRateWithinAllowedRange(tokenRate_, rateL1Timestamp_)) { + revert ErrorTokenRateIsOutOfRange(tokenRate_, rateL1Timestamp_); + } + /// @dev notify that there is a differnce L1 and L2 time. if (rateL1Timestamp_ > block.timestamp) { - emit TokenRateL1TimestampAheadOfL2Time(); + emit TokenRateL1TimestampAheadOfL2Time(tokenRate_, rateL1Timestamp_); } _setTokenRateAndL1Timestamp(uint192(tokenRate_), uint64(rateL1Timestamp_)); @@ -213,7 +214,10 @@ contract TokenRateOracle is CrossDomainEnabled, ITokenRateOracle, Versioned { uint256 indexed currentRateL1Timestamp_, uint256 indexed newRateL1Timestamp_ ); - event TokenRateL1TimestampAheadOfL2Time(); + event TokenRateL1TimestampAheadOfL2Time( + uint256 tokenRate_, + uint256 indexed rateL1Timestamp_ + ); error ErrorNotBridgeOrTokenRatePusher(); error ErrorL1TimestampExceededAllowedClockLag(uint256 tokenRate_, uint256 rateL1Timestamp_); diff --git a/test/optimism/L2ERC20ExtendedTokensBridge.unit.test.ts b/test/optimism/L2ERC20ExtendedTokensBridge.unit.test.ts index 86121871..4873f6ff 100644 --- a/test/optimism/L2ERC20ExtendedTokensBridge.unit.test.ts +++ b/test/optimism/L2ERC20ExtendedTokensBridge.unit.test.ts @@ -1015,7 +1015,9 @@ async function ctxFactory() { l2MessengerStub.address, l2TokenBridgeProxyAddress, l1TokenBridgeEOA.address, - 86400 + 86400, + 86400, + 500 ); const l2TokenRebasableStub = await new ERC20RebasableBridgedPermit__factory(deployer).deploy( diff --git a/test/optimism/TokenRateOracle.unit.test.ts b/test/optimism/TokenRateOracle.unit.test.ts index b309ef77..0e0cc461 100644 --- a/test/optimism/TokenRateOracle.unit.test.ts +++ b/test/optimism/TokenRateOracle.unit.test.ts @@ -4,22 +4,82 @@ import { BigNumber } from "ethers"; import testing, { unit } from "../../utils/testing"; import { TokenRateOracle__factory, - CrossDomainMessengerStub__factory + CrossDomainMessengerStub__factory, + OssifiableProxy__factory } from "../../typechain"; import { wei } from "../../utils/wei"; unit("TokenRateOracle", ctxFactory) + .test("initialize() :: petrified version", async (ctx) => { + + const { deployer, bridge, l1TokenBridgeEOA } = ctx.accounts; + const { l2MessengerStub } = ctx.contracts; + + const tokenRateOracleImpl = await new TokenRateOracle__factory(deployer).deploy( + l2MessengerStub.address, + bridge.address, + l1TokenBridgeEOA.address, + 86400, + 86400, + 500 + ); + + const petrifiedVersionMark = hre.ethers.constants.MaxUint256; + assert.equalBN(await tokenRateOracleImpl.getContractVersion(), petrifiedVersionMark); + + await assert.revertsWith( + tokenRateOracleImpl.initialize(1,2), + "NonZeroContractVersionOnInit()" + ); + }) + + .test("initialize() :: re-initialization", async (ctx) => { + + const { deployer, bridge, l1TokenBridgeEOA } = ctx.accounts; + const { l2MessengerStub } = ctx.contracts; + + const tokenRateOracleImpl = await new TokenRateOracle__factory(deployer).deploy( + l2MessengerStub.address, + bridge.address, + l1TokenBridgeEOA.address, + 86400, + 86400, + 500 + ); + + const tokenRateOracleProxy = await new OssifiableProxy__factory( + deployer + ).deploy( + tokenRateOracleImpl.address, + deployer.address, + tokenRateOracleImpl.interface.encodeFunctionData("initialize", [1,2]) + ); + + const tokenRateOracle = TokenRateOracle__factory.connect( + tokenRateOracleProxy.address, + deployer + ); + + assert.equalBN(await tokenRateOracle.getContractVersion(), 1); + + await assert.revertsWith( + tokenRateOracle.initialize(2,3), + "NonZeroContractVersionOnInit()" + ); + }) + .test("state after init", async (ctx) => { const { tokenRateOracle, l2MessengerStub } = ctx.contracts; const { bridge, l1TokenBridgeEOA } = ctx.accounts; + const { tokenRateCorrect, blockTimestamp } = ctx.constants; assert.equal(await tokenRateOracle.MESSENGER(), l2MessengerStub.address); assert.equal(await tokenRateOracle.L2_ERC20_TOKEN_BRIDGE(), bridge.address); assert.equal(await tokenRateOracle.L1_TOKEN_RATE_PUSHER(), l1TokenBridgeEOA.address); assert.equalBN(await tokenRateOracle.TOKEN_RATE_OUTDATED_DELAY(), 86400); - assert.equalBN(await tokenRateOracle.latestAnswer(), 0); + assert.equalBN(await tokenRateOracle.latestAnswer(), tokenRateCorrect); const { roundId_, @@ -29,85 +89,104 @@ unit("TokenRateOracle", ctxFactory) answeredInRound_ } = await tokenRateOracle.latestRoundData(); - assert.equalBN(roundId_, 0); - assert.equalBN(answer_, 0); - assert.equalBN(startedAt_, 0); - assert.equalBN(updatedAt_, 0); - assert.equalBN(answeredInRound_, 0); + assert.equalBN(roundId_, blockTimestamp); + assert.equalBN(answer_, tokenRateCorrect); + assert.equalBN(startedAt_, blockTimestamp); + assert.equalBN(updatedAt_, blockTimestamp); + assert.equalBN(answeredInRound_, blockTimestamp); assert.equalBN(await tokenRateOracle.decimals(), 18); }) .test("updateRate() :: called by non-bridge account", async (ctx) => { const { tokenRateOracle } = ctx.contracts; const { stranger } = ctx.accounts; - await assert.revertsWith(tokenRateOracle.connect(stranger).updateRate(10, 40), "ErrorNoRights(\""+stranger.address+"\")"); + await assert.revertsWith(tokenRateOracle.connect(stranger).updateRate(10, 40), "ErrorNotBridgeOrTokenRatePusher()"); }) .test("updateRate() :: called by messenger with incorrect cross-domain sender", async (ctx) => { const { tokenRateOracle, l2MessengerStub } = ctx.contracts; const { stranger, l2MessengerStubEOA } = ctx.accounts; await l2MessengerStub.setXDomainMessageSender(stranger.address); - await assert.revertsWith(tokenRateOracle.connect(l2MessengerStubEOA).updateRate(10, 40), "ErrorNoRights(\""+l2MessengerStubEOA._address+"\")"); + await assert.revertsWith(tokenRateOracle.connect(l2MessengerStubEOA).updateRate(10, 40), "ErrorNotBridgeOrTokenRatePusher()"); }) - .test("updateRate() :: incorrect time", async (ctx) => { + .test("updateRate() :: L1 time exceeded allowed L2 clock lag", async (ctx) => { const { tokenRateOracle } = ctx.contracts; const { bridge } = ctx.accounts; - const { tokenRateCorrect } = ctx.constants; - - const tx0 = await tokenRateOracle.connect(bridge).updateRate(tokenRateCorrect, 1000); - const tx1 = await tokenRateOracle.connect(bridge).updateRate(tokenRateCorrect, 20); + const { tokenRateCorrect, blockTimestamp } = ctx.constants; - await assert.emits(tokenRateOracle, tx1, "NewTokenRateOutdated", [tokenRateCorrect, 1000, 20]); + const exceededTime = blockTimestamp+86400+40; + await assert.revertsWith( + tokenRateOracle.connect(bridge).updateRate(tokenRateCorrect, exceededTime), + "ErrorL1TimestampExceededAllowedClockLag("+tokenRateCorrect+", "+exceededTime+")" + ) }) - .test("updateRate() :: time in future", async (ctx) => { + .test("updateRate() :: received token rate is in the past or same time", async (ctx) => { const { tokenRateOracle } = ctx.contracts; const { bridge } = ctx.accounts; const { tokenRateCorrect, blockTimestamp } = ctx.constants; - const timeInFuture = blockTimestamp + 100000; - await assert.revertsWith( - tokenRateOracle.connect(bridge).updateRate(tokenRateCorrect, timeInFuture), - "ErrorL1TimestampInFuture("+tokenRateCorrect+", "+timeInFuture+")" - ); - }) + const tx0 = await tokenRateOracle.connect(bridge).updateRate(tokenRateCorrect, blockTimestamp); - .test("updateRate() :: rate is out of range", async (ctx) => { - const { tokenRateOracle } = ctx.contracts; - const { bridge } = ctx.accounts; - const { tokenRateTooBig, tokenRateTooSmall, blockTimestamp } = ctx.constants; + await assert.emits(tokenRateOracle, tx0, "DormantTokenRateUpdateIgnored", [ + tokenRateCorrect, + blockTimestamp, + blockTimestamp, + ]); - await assert.revertsWith( - tokenRateOracle.connect(bridge).updateRate(tokenRateTooBig, blockTimestamp), - "ErrorTokenRateIsOutOfRange("+tokenRateTooBig+", "+blockTimestamp+")" - ); - await assert.revertsWith( - tokenRateOracle.connect(bridge).updateRate(tokenRateTooSmall, blockTimestamp), - "ErrorTokenRateIsOutOfRange("+tokenRateTooSmall+", "+blockTimestamp+")" - ); + const timeInPast = blockTimestamp-1000; + const tx1 = await tokenRateOracle.connect(bridge).updateRate(tokenRateCorrect, timeInPast); + + await assert.emits(tokenRateOracle, tx1, "DormantTokenRateUpdateIgnored", [ + tokenRateCorrect, + blockTimestamp, + timeInPast, + ]); }) - .test("updateRate() :: don't update state if values are the same", async (ctx) => { + .test("updateRate() :: ErrorTokenRateIsOutOfRange", async (ctx) => { const { tokenRateOracle } = ctx.contracts; const { bridge } = ctx.accounts; - const { tokenRateCorrect } = ctx.constants; + const { tokenRateCorrect, blockTimestamp } = ctx.constants; - const tx1 = await tokenRateOracle.connect(bridge).updateRate(tokenRateCorrect, 1000); - await assert.emits(tokenRateOracle, tx1, "RateUpdated", [tokenRateCorrect, 1000]); + const tokenRateTooBig = tokenRateCorrect.mul(BigNumber.from('2')); + const tokenRateTooSmall = tokenRateCorrect.div(BigNumber.from('2')); - const tx2 = await tokenRateOracle.connect(bridge).updateRate(tokenRateCorrect, 1000); - await assert.notEmits(tokenRateOracle, tx2, "RateUpdated"); + var blockTimestampForNextUpdate = blockTimestamp + 1000; + await assert.revertsWith( + tokenRateOracle.connect(bridge).updateRate(tokenRateTooBig, blockTimestampForNextUpdate), + "ErrorTokenRateIsOutOfRange("+tokenRateTooBig+", "+blockTimestampForNextUpdate+")" + ) + + blockTimestampForNextUpdate += 1000; + await assert.revertsWith( + tokenRateOracle.connect(bridge).updateRate(tokenRateTooSmall, blockTimestampForNextUpdate), + "ErrorTokenRateIsOutOfRange("+tokenRateTooSmall+", "+blockTimestampForNextUpdate+")" + ) }) .test("updateRate() :: happy path called by bridge", async (ctx) => { const { tokenRateOracle } = ctx.contracts; const { bridge } = ctx.accounts; - const { tokenRateCorrect, blockTimestamp } = ctx.constants; + const { blockTimestamp } = ctx.constants; - await tokenRateOracle.connect(bridge).updateRate(tokenRateCorrect, blockTimestamp); + const decimalsBN = BigNumber.from(10).pow(18-2); + const newTokenRateCorrect = BigNumber.from('125').mul(decimalsBN); + const blockTimestampInFuture = blockTimestamp + 1000; + const tx = await tokenRateOracle.connect(bridge).updateRate(newTokenRateCorrect, blockTimestampInFuture); - assert.equalBN(await tokenRateOracle.latestAnswer(), tokenRateCorrect); + await assert.emits(tokenRateOracle, tx, "TokenRateL1TimestampAheadOfL2Time", [ + newTokenRateCorrect, + blockTimestampInFuture + ]); + + await assert.emits(tokenRateOracle, tx, "RateUpdated", [ + newTokenRateCorrect, + blockTimestampInFuture + ]); + + assert.equalBN(await tokenRateOracle.latestAnswer(), newTokenRateCorrect); const { roundId_, @@ -117,24 +196,37 @@ unit("TokenRateOracle", ctxFactory) answeredInRound_ } = await tokenRateOracle.latestRoundData(); - assert.equalBN(roundId_, blockTimestamp); - assert.equalBN(answer_, tokenRateCorrect); - assert.equalBN(startedAt_, blockTimestamp); - assert.equalBN(updatedAt_, blockTimestamp); - assert.equalBN(answeredInRound_, blockTimestamp); + assert.equalBN(roundId_, blockTimestampInFuture); + assert.equalBN(answer_, newTokenRateCorrect); + assert.equalBN(startedAt_, blockTimestampInFuture); + assert.equalBN(updatedAt_, blockTimestampInFuture); + assert.equalBN(answeredInRound_, blockTimestampInFuture); assert.equalBN(await tokenRateOracle.decimals(), 18); }) .test("updateRate() :: happy path called by messenger with correct cross-domain sender", async (ctx) => { const { tokenRateOracle, l2MessengerStub } = ctx.contracts; const { l2MessengerStubEOA, l1TokenBridgeEOA } = ctx.accounts; - const { tokenRateCorrect, blockTimestamp } = ctx.constants; + const { blockTimestamp } = ctx.constants; await l2MessengerStub.setXDomainMessageSender(l1TokenBridgeEOA.address); - await tokenRateOracle.connect(l2MessengerStubEOA).updateRate(tokenRateCorrect, blockTimestamp); + const decimalsBN = BigNumber.from(10).pow(18-2); + const newTokenRateCorrect = BigNumber.from('125').mul(decimalsBN); + const blockTimestampInFuture = blockTimestamp + 1000; + const tx = await tokenRateOracle.connect(l2MessengerStubEOA).updateRate(newTokenRateCorrect, blockTimestampInFuture); - assert.equalBN(await tokenRateOracle.latestAnswer(), tokenRateCorrect); + await assert.emits(tokenRateOracle, tx, "TokenRateL1TimestampAheadOfL2Time", [ + newTokenRateCorrect, + blockTimestampInFuture + ]); + + await assert.emits(tokenRateOracle, tx, "RateUpdated", [ + newTokenRateCorrect, + blockTimestampInFuture + ]); + + assert.equalBN(await tokenRateOracle.latestAnswer(), newTokenRateCorrect); const { roundId_, @@ -144,17 +236,26 @@ unit("TokenRateOracle", ctxFactory) answeredInRound_ } = await tokenRateOracle.latestRoundData(); - assert.equalBN(roundId_, blockTimestamp); - assert.equalBN(answer_, tokenRateCorrect); - assert.equalBN(startedAt_, blockTimestamp); - assert.equalBN(updatedAt_, blockTimestamp); - assert.equalBN(answeredInRound_, blockTimestamp); + assert.equalBN(roundId_, blockTimestampInFuture); + assert.equalBN(answer_, newTokenRateCorrect); + assert.equalBN(startedAt_, blockTimestampInFuture); + assert.equalBN(updatedAt_, blockTimestampInFuture); + assert.equalBN(answeredInRound_, blockTimestampInFuture); assert.equalBN(await tokenRateOracle.decimals(), 18); }) .run(); async function ctxFactory() { + const decimals = 18; + const decimalsBN = BigNumber.from(10).pow(decimals-1); + const tokenRateCorrect = BigNumber.from('12').mul(decimalsBN); + const tokenRateTooBig = BigNumber.from('2000').pow(decimals); + const tokenRateTooSmall = BigNumber.from('1').pow(decimals-3); + + const provider = await hre.ethers.provider; + const blockNumber = await provider.getBlockNumber(); + const blockTimestamp = (await provider.getBlock(blockNumber)).timestamp; const [deployer, bridge, stranger, l1TokenBridgeEOA] = await hre.ethers.getSigners(); @@ -163,22 +264,30 @@ async function ctxFactory() { ).deploy({ value: wei.toBigNumber(wei`1 ether`) }); const l2MessengerStubEOA = await testing.impersonate(l2MessengerStub.address); - const tokenRateOracle = await new TokenRateOracle__factory(deployer).deploy( + const tokenRateOracleImpl = await new TokenRateOracle__factory(deployer).deploy( l2MessengerStub.address, bridge.address, l1TokenBridgeEOA.address, - 86400 + 86400, + 86400, + 500 ); - const decimals = 18; - const decimalsBN = BigNumber.from(10).pow(decimals); - const tokenRateCorrect = BigNumber.from('12').pow(decimals - 1); - const tokenRateTooBig = BigNumber.from('2000').pow(decimals); - const tokenRateTooSmall = BigNumber.from('1').pow(decimals-3); + const tokenRateOracleProxy = await new OssifiableProxy__factory( + deployer + ).deploy( + tokenRateOracleImpl.address, + deployer.address, + tokenRateOracleImpl.interface.encodeFunctionData("initialize", [ + tokenRateCorrect, + blockTimestamp + ]) + ); - const provider = await hre.ethers.provider; - const blockNumber = await provider.getBlockNumber(); - const blockTimestamp = (await provider.getBlock(blockNumber)).timestamp; + const tokenRateOracle = TokenRateOracle__factory.connect( + tokenRateOracleProxy.address, + deployer + ); return { accounts: { deployer, bridge, stranger, l1TokenBridgeEOA, l2MessengerStubEOA }, diff --git a/test/token/ERC20BridgedPermit.unit.test.ts b/test/token/ERC20BridgedPermit.unit.test.ts index 60399871..f9884ca1 100644 --- a/test/token/ERC20BridgedPermit.unit.test.ts +++ b/test/token/ERC20BridgedPermit.unit.test.ts @@ -44,7 +44,7 @@ unit("ERC20BridgedPermit", ctxFactory) ); }) - .test("initialize() :: reinit", async (ctx) => { + .test("initialize() :: re-initialization", async (ctx) => { const { deployer, owner, holder } = ctx.accounts; const { name, symbol, version } = ctx.constants; diff --git a/test/token/ERC20Permit.unit.test.ts b/test/token/ERC20Permit.unit.test.ts index b0ad2c47..3b54abb4 100644 --- a/test/token/ERC20Permit.unit.test.ts +++ b/test/token/ERC20Permit.unit.test.ts @@ -402,7 +402,9 @@ async function tokenProxied( hre.ethers.constants.AddressZero, owner.address, hre.ethers.constants.AddressZero, - 86400 + 86400, + 86400, + 500 ); const rebasableTokenImpl = await new ERC20RebasableBridgedPermit__factory(deployer).deploy( name, diff --git a/test/token/ERC20RebasableBridgedPermit.unit.test.ts b/test/token/ERC20RebasableBridgedPermit.unit.test.ts index 9d7d4a1f..c6e719a7 100644 --- a/test/token/ERC20RebasableBridgedPermit.unit.test.ts +++ b/test/token/ERC20RebasableBridgedPermit.unit.test.ts @@ -27,7 +27,7 @@ unit("ERC20RebasableBridgedPermit", ctxFactory) assert.equal(await rebasableProxied.L2_ERC20_TOKEN_BRIDGE(), owner.address); }) - .test("initialize() :: failed to init implementation", async (ctx) => { + .test("initialize() :: petrified version", async (ctx) => { const { deployer, owner, zero } = ctx.accounts; const { decimalsToSet } = ctx.constants; @@ -43,7 +43,9 @@ unit("ERC20RebasableBridgedPermit", ctxFactory) zero.address, owner.address, zero.address, - 86400 + 86400, + 86400, + 500 ); const rebasableTokenImpl = await new ERC20RebasableBridgedPermit__factory(deployer).deploy( "stETH Test Token", @@ -80,7 +82,9 @@ unit("ERC20RebasableBridgedPermit", ctxFactory) zero.address, owner.address, zero.address, - 86400 + 86400, + 86400, + 500 ); const rebasableTokenImpl = await new ERC20RebasableBridgedPermit__factory(deployer).deploy( "", @@ -147,7 +151,9 @@ unit("ERC20RebasableBridgedPermit", ctxFactory) zero.address, owner.address, zero.address, - 86400 + 86400, + 86400, + 500 ); const rebasableProxied = await new ERC20RebasableBridgedPermit__factory(deployer).deploy( "", @@ -323,7 +329,9 @@ unit("ERC20RebasableBridgedPermit", ctxFactory) zero.address, owner.address, zero.address, - 86400 + 86400, + 86400, + 500 ); const rebasableProxied = await new ERC20RebasableBridgedPermit__factory(deployer).deploy( "", @@ -1156,7 +1164,9 @@ async function ctxFactory() { zero.address, owner.address, zero.address, - 86400 + 86400, + 86400, + 500 ); const rebasableTokenImpl = await new ERC20RebasableBridgedPermit__factory(deployer).deploy( name, From c10f8644a0a1e30553689d19e7e4d37826c16710 Mon Sep 17 00:00:00 2001 From: Anton Kovalchuk Date: Tue, 23 Apr 2024 20:23:41 +0400 Subject: [PATCH 088/148] update scripts and tests --- contracts/stubs/ERC20WrapperStub.sol | 5 +- scripts/optimism/deploy-new-impls.ts | 19 +- scripts/optimism/deploy-oracle.ts | 11 +- .../optimism.integration.test.ts | 10 +- test/optimism/L1LidoTokensBridge.unit.test.ts | 6 +- .../L2ERC20ExtendedTokensBridge.unit.test.ts | 30 +- .../OpStackTokenRatePusher.unit.test.ts | 6 +- test/optimism/TokenRateNotifier.unit.test.ts | 5 +- test/optimism/TokenRateOracle.unit.test.ts | 516 +++++----- .../bridging-rebasable.integration.test.ts | 889 +++++++++--------- .../pushingTokenRate.integration.test.ts | 33 +- test/token/ERC20BridgedPermit.unit.test.ts | 46 +- test/token/ERC20Permit.unit.test.ts | 33 +- .../ERC20RebasableBridgedPermit.unit.test.ts | 73 +- utils/optimism/deploymentAllFromScratch.ts | 16 +- .../deploymentBridgesAndRebasableToken.ts | 6 +- .../optimism/deploymentNewImplementations.ts | 36 +- utils/optimism/deploymentOracle.ts | 24 +- utils/optimism/testing.ts | 11 +- utils/testing/contractsFactory.ts | 150 +++ utils/testing/index.ts | 2 +- 21 files changed, 1096 insertions(+), 831 deletions(-) create mode 100644 utils/testing/contractsFactory.ts diff --git a/contracts/stubs/ERC20WrapperStub.sol b/contracts/stubs/ERC20WrapperStub.sol index fc9ab194..167d796b 100644 --- a/contracts/stubs/ERC20WrapperStub.sol +++ b/contracts/stubs/ERC20WrapperStub.sol @@ -16,12 +16,11 @@ contract ERC20WrapperStub is IERC20Wrapper, IERC20WstETH, ERC20 { address public bridge; uint256 public tokensRate; - constructor(IERC20 stETH_, string memory name_, string memory symbol_) + constructor(IERC20 stETH_, string memory name_, string memory symbol_, uint256 tokensRate_) ERC20(name_, symbol_) { stETH = stETH_; - - tokensRate = 2 * 10 **18; + tokensRate = tokensRate_; _mint(msg.sender, 1000000 * 10**18); } diff --git a/scripts/optimism/deploy-new-impls.ts b/scripts/optimism/deploy-new-impls.ts index b28ea202..bd6104c3 100644 --- a/scripts/optimism/deploy-new-impls.ts +++ b/scripts/optimism/deploy-new-impls.ts @@ -4,6 +4,7 @@ import network from "../../utils/network"; import deployment from "../../utils/deployment"; import deploymentNewImplementations from "../../utils/optimism/deploymentNewImplementations"; +import { BigNumber } from "ethers"; async function main() { const networkName = env.network(); @@ -47,8 +48,22 @@ async function main() { contractsShift: 0, tokenBridgeProxyAddress: deploymentConfig.l2TokenBridge, tokenProxyAddress: deploymentConfig.l2Token, - tokenRateOracleProxyAddress: deploymentConfig.l2TokenRateOracle, - tokenRateOracleRateOutdatedDelay: deploymentConfig.tokenRateOutdatedDelay, + tokenRateOracle: { + proxyAddress: deploymentConfig.l2TokenRateOracle, + rateOutdatedDelay: BigNumber.from(deploymentConfig.tokenRateOutdatedDelay), + maxAllowedL2ToL1ClockLag: BigNumber.from(86400), + maxAllowedTokenRateDeviationPerDay: BigNumber.from(500) + }, + token: { + name: "name", + symbol: "symbol", + version: "1" + }, + tokenRebasable: { + name: "name", + symbol: "symbol", + version: "1" + } } ); diff --git a/scripts/optimism/deploy-oracle.ts b/scripts/optimism/deploy-oracle.ts index 08edc448..e3717606 100644 --- a/scripts/optimism/deploy-oracle.ts +++ b/scripts/optimism/deploy-oracle.ts @@ -19,6 +19,9 @@ async function main() { } ); + const blockNumber = await optDeployer.provider.getBlockNumber(); + const blockTimestamp = (await optDeployer.provider.getBlock(blockNumber)).timestamp; + const deploymentConfig = deployment.loadMultiChainDeploymentConfig(); const [l1DeployScript, l2DeployScript] = await optimism @@ -41,7 +44,13 @@ async function main() { proxy: deploymentConfig.l2.proxyAdmin, bridge: optDeployer.address, }, - contractsShift: 0 + contractsShift: 0, + tokenRateOracle: { + maxAllowedL2ToL1ClockLag: 86400, + maxAllowedTokenRateDeviationPerDay: 500, + tokenRate: 1164454276599657236, + l1Timestamp: blockTimestamp + } } ); diff --git a/test/bridge-executor/optimism.integration.test.ts b/test/bridge-executor/optimism.integration.test.ts index 655168ad..189bc740 100644 --- a/test/bridge-executor/optimism.integration.test.ts +++ b/test/bridge-executor/optimism.integration.test.ts @@ -1,4 +1,5 @@ import { assert } from "chai"; +import { BigNumber } from 'ethers' import { ERC20BridgedStub__factory, L2ERC20ExtendedTokensBridge__factory, @@ -216,7 +217,8 @@ async function ctxFactory() { const l1TokenRebasable = await new ERC20WrapperStub__factory(l1Deployer).deploy( l1Token.address, "Test Token", - "TT" + "TT", + BigNumber.from('1164454276599657236') ); const optAddresses = optimism.addresses(networkName); @@ -256,7 +258,11 @@ async function ctxFactory() { proxy: govBridgeExecutor.address, bridge: govBridgeExecutor.address, }, - contractsShift: 0 + contractsShift: 0, + tokenRateOracle: { + tokenRate:10, + l1Timestamp:2 + } } ); diff --git a/test/optimism/L1LidoTokensBridge.unit.test.ts b/test/optimism/L1LidoTokensBridge.unit.test.ts index 5aff89e4..e95e8491 100644 --- a/test/optimism/L1LidoTokensBridge.unit.test.ts +++ b/test/optimism/L1LidoTokensBridge.unit.test.ts @@ -985,7 +985,8 @@ async function ctxFactory() { const l1TokenNonRebasableStub = await new ERC20WrapperStub__factory(deployer).deploy( l1TokenRebasableStub.address, "L1 Token Non Rebasable", - "L1NR" + "L1NR", + BigNumber.from('1164454276599657236') ); const l2TokenNonRebasableStub = await new ERC20BridgedStub__factory(deployer).deploy( @@ -996,7 +997,8 @@ async function ctxFactory() { const l2TokenRebasableStub = await new ERC20WrapperStub__factory(deployer).deploy( l2TokenNonRebasableStub.address, "L2 Token Rebasable", - "L2R" + "L2R", + BigNumber.from('1164454276599657236') ); const emptyContract = await new EmptyContractStub__factory(deployer).deploy({ diff --git a/test/optimism/L2ERC20ExtendedTokensBridge.unit.test.ts b/test/optimism/L2ERC20ExtendedTokensBridge.unit.test.ts index 4873f6ff..72fa414b 100644 --- a/test/optimism/L2ERC20ExtendedTokensBridge.unit.test.ts +++ b/test/optimism/L2ERC20ExtendedTokensBridge.unit.test.ts @@ -972,7 +972,7 @@ async function ctxFactory() { const decimals = 18; const decimalsBN = BigNumber.from(10).pow(decimals); - const exchangeRate = BigNumber.from('12').pow(decimals - 1); + const exchangeRate = BigNumber.from('1164454276599657236') const l2MessengerStub = await new CrossDomainMessengerStub__factory( deployer @@ -992,8 +992,9 @@ async function ctxFactory() { , , , + , l2TokenBridgeProxyAddress - ] = await predictAddresses(deployer, 7); + ] = await predictAddresses(deployer, 8); const l1TokenRebasableStub = await new ERC20BridgedStub__factory(deployer).deploy( "L1 Token Rebasable", @@ -1003,7 +1004,8 @@ async function ctxFactory() { const l1TokenNonRebasableStub = await new ERC20WrapperStub__factory(deployer).deploy( l1TokenRebasableStub.address, "L1 Token Non Rebasable", - "L1NR" + "L1NR", + exchangeRate ); const l2TokenNonRebasableStub = await new ERC20BridgedStub__factory(deployer).deploy( @@ -1011,7 +1013,7 @@ async function ctxFactory() { "L2NR" ); - const tokenRateOracle = await new TokenRateOracle__factory(deployer).deploy( + const tokenRateOracleImpl = await new TokenRateOracle__factory(deployer).deploy( l2MessengerStub.address, l2TokenBridgeProxyAddress, l1TokenBridgeEOA.address, @@ -1020,6 +1022,26 @@ async function ctxFactory() { 500 ); + const provider = await hre.ethers.provider; + const blockNumber = await provider.getBlockNumber(); + const blockTimestamp = (await provider.getBlock(blockNumber)).timestamp; + const tokenRateOracleProxy = await new OssifiableProxy__factory( + deployer + ).deploy( + tokenRateOracleImpl.address, + deployer.address, + tokenRateOracleImpl.interface.encodeFunctionData("initialize", [ + exchangeRate, + blockTimestamp + ]) + ); + + const tokenRateOracle = TokenRateOracle__factory.connect( + tokenRateOracleProxy.address, + deployer + ); + + const l2TokenRebasableStub = await new ERC20RebasableBridgedPermit__factory(deployer).deploy( "L2 Token Rebasable", "L2R", diff --git a/test/optimism/OpStackTokenRatePusher.unit.test.ts b/test/optimism/OpStackTokenRatePusher.unit.test.ts index cf7a99f6..78102c7a 100644 --- a/test/optimism/OpStackTokenRatePusher.unit.test.ts +++ b/test/optimism/OpStackTokenRatePusher.unit.test.ts @@ -1,9 +1,8 @@ import { ethers } from "hardhat"; import { assert } from "chai"; -import { utils } from 'ethers' +import { utils, BigNumber } from 'ethers' import { unit } from "../../utils/testing"; import { wei } from "../../utils/wei"; - import { OpStackTokenRatePusher__factory, CrossDomainMessengerStub__factory, @@ -68,7 +67,8 @@ async function ctxFactory() { const l1TokenNonRebasableStub = await new ERC20WrapperStub__factory(deployer).deploy( l1TokenRebasableStub.address, "L1 Token Non Rebasable", - "L1NR" + "L1NR", + BigNumber.from('1164454276599657236') ); const l1MessengerStub = await new CrossDomainMessengerStub__factory( diff --git a/test/optimism/TokenRateNotifier.unit.test.ts b/test/optimism/TokenRateNotifier.unit.test.ts index c2f3864c..32f50460 100644 --- a/test/optimism/TokenRateNotifier.unit.test.ts +++ b/test/optimism/TokenRateNotifier.unit.test.ts @@ -1,6 +1,6 @@ import { ethers } from "hardhat"; import { assert } from "chai"; -import { utils } from 'ethers' +import { utils, BigNumber } from 'ethers' import { unit } from "../../utils/testing"; import { wei } from "../../utils/wei"; import { SignerWithAddress } from "@nomiclabs/hardhat-ethers/signers"; @@ -256,7 +256,8 @@ async function getOpStackTokenRatePusher( const l1TokenNonRebasableStub = await new ERC20WrapperStub__factory(deployer).deploy( l1TokenRebasableStub.address, "L1 Token Non Rebasable", - "L1NR" + "L1NR", + BigNumber.from('1164454276599657236') ); const opStackTokenRatePusher = await new OpStackTokenRatePusher__factory(deployer).deploy( diff --git a/test/optimism/TokenRateOracle.unit.test.ts b/test/optimism/TokenRateOracle.unit.test.ts index 0e0cc461..dbc5a141 100644 --- a/test/optimism/TokenRateOracle.unit.test.ts +++ b/test/optimism/TokenRateOracle.unit.test.ts @@ -2,260 +2,240 @@ import hre from "hardhat"; import { assert } from "chai"; import { BigNumber } from "ethers"; import testing, { unit } from "../../utils/testing"; +import { wei } from "../../utils/wei"; +import { tokenRateOracleUnderProxy } from "../../utils/testing/contractsFactory"; import { TokenRateOracle__factory, - CrossDomainMessengerStub__factory, - OssifiableProxy__factory + CrossDomainMessengerStub__factory } from "../../typechain"; -import { wei } from "../../utils/wei"; unit("TokenRateOracle", ctxFactory) - - .test("initialize() :: petrified version", async (ctx) => { - - const { deployer, bridge, l1TokenBridgeEOA } = ctx.accounts; - const { l2MessengerStub } = ctx.contracts; - - const tokenRateOracleImpl = await new TokenRateOracle__factory(deployer).deploy( - l2MessengerStub.address, - bridge.address, - l1TokenBridgeEOA.address, - 86400, - 86400, - 500 - ); - - const petrifiedVersionMark = hre.ethers.constants.MaxUint256; - assert.equalBN(await tokenRateOracleImpl.getContractVersion(), petrifiedVersionMark); - - await assert.revertsWith( - tokenRateOracleImpl.initialize(1,2), - "NonZeroContractVersionOnInit()" - ); - }) - - .test("initialize() :: re-initialization", async (ctx) => { - - const { deployer, bridge, l1TokenBridgeEOA } = ctx.accounts; - const { l2MessengerStub } = ctx.contracts; - - const tokenRateOracleImpl = await new TokenRateOracle__factory(deployer).deploy( - l2MessengerStub.address, - bridge.address, - l1TokenBridgeEOA.address, - 86400, - 86400, - 500 - ); - - const tokenRateOracleProxy = await new OssifiableProxy__factory( - deployer - ).deploy( - tokenRateOracleImpl.address, - deployer.address, - tokenRateOracleImpl.interface.encodeFunctionData("initialize", [1,2]) - ); - - const tokenRateOracle = TokenRateOracle__factory.connect( - tokenRateOracleProxy.address, - deployer - ); - - assert.equalBN(await tokenRateOracle.getContractVersion(), 1); - - await assert.revertsWith( - tokenRateOracle.initialize(2,3), - "NonZeroContractVersionOnInit()" - ); - }) - - .test("state after init", async (ctx) => { - const { tokenRateOracle, l2MessengerStub } = ctx.contracts; - const { bridge, l1TokenBridgeEOA } = ctx.accounts; - const { tokenRateCorrect, blockTimestamp } = ctx.constants; - - assert.equal(await tokenRateOracle.MESSENGER(), l2MessengerStub.address); - assert.equal(await tokenRateOracle.L2_ERC20_TOKEN_BRIDGE(), bridge.address); - assert.equal(await tokenRateOracle.L1_TOKEN_RATE_PUSHER(), l1TokenBridgeEOA.address); - assert.equalBN(await tokenRateOracle.TOKEN_RATE_OUTDATED_DELAY(), 86400); - - assert.equalBN(await tokenRateOracle.latestAnswer(), tokenRateCorrect); - - const { - roundId_, - answer_, - startedAt_, - updatedAt_, - answeredInRound_ - } = await tokenRateOracle.latestRoundData(); - - assert.equalBN(roundId_, blockTimestamp); - assert.equalBN(answer_, tokenRateCorrect); - assert.equalBN(startedAt_, blockTimestamp); - assert.equalBN(updatedAt_, blockTimestamp); - assert.equalBN(answeredInRound_, blockTimestamp); - assert.equalBN(await tokenRateOracle.decimals(), 18); - }) - - .test("updateRate() :: called by non-bridge account", async (ctx) => { - const { tokenRateOracle } = ctx.contracts; - const { stranger } = ctx.accounts; - await assert.revertsWith(tokenRateOracle.connect(stranger).updateRate(10, 40), "ErrorNotBridgeOrTokenRatePusher()"); - }) - - .test("updateRate() :: called by messenger with incorrect cross-domain sender", async (ctx) => { - const { tokenRateOracle, l2MessengerStub } = ctx.contracts; - const { stranger, l2MessengerStubEOA } = ctx.accounts; - await l2MessengerStub.setXDomainMessageSender(stranger.address); - await assert.revertsWith(tokenRateOracle.connect(l2MessengerStubEOA).updateRate(10, 40), "ErrorNotBridgeOrTokenRatePusher()"); - }) - - .test("updateRate() :: L1 time exceeded allowed L2 clock lag", async (ctx) => { - const { tokenRateOracle } = ctx.contracts; - const { bridge } = ctx.accounts; - const { tokenRateCorrect, blockTimestamp } = ctx.constants; - - const exceededTime = blockTimestamp+86400+40; - await assert.revertsWith( - tokenRateOracle.connect(bridge).updateRate(tokenRateCorrect, exceededTime), - "ErrorL1TimestampExceededAllowedClockLag("+tokenRateCorrect+", "+exceededTime+")" - ) - }) - - .test("updateRate() :: received token rate is in the past or same time", async (ctx) => { - const { tokenRateOracle } = ctx.contracts; - const { bridge } = ctx.accounts; - const { tokenRateCorrect, blockTimestamp } = ctx.constants; - - const tx0 = await tokenRateOracle.connect(bridge).updateRate(tokenRateCorrect, blockTimestamp); - - await assert.emits(tokenRateOracle, tx0, "DormantTokenRateUpdateIgnored", [ - tokenRateCorrect, - blockTimestamp, - blockTimestamp, - ]); - - const timeInPast = blockTimestamp-1000; - const tx1 = await tokenRateOracle.connect(bridge).updateRate(tokenRateCorrect, timeInPast); - - await assert.emits(tokenRateOracle, tx1, "DormantTokenRateUpdateIgnored", [ - tokenRateCorrect, - blockTimestamp, - timeInPast, - ]); - }) - - .test("updateRate() :: ErrorTokenRateIsOutOfRange", async (ctx) => { - const { tokenRateOracle } = ctx.contracts; - const { bridge } = ctx.accounts; - const { tokenRateCorrect, blockTimestamp } = ctx.constants; - - const tokenRateTooBig = tokenRateCorrect.mul(BigNumber.from('2')); - const tokenRateTooSmall = tokenRateCorrect.div(BigNumber.from('2')); - - var blockTimestampForNextUpdate = blockTimestamp + 1000; - await assert.revertsWith( - tokenRateOracle.connect(bridge).updateRate(tokenRateTooBig, blockTimestampForNextUpdate), - "ErrorTokenRateIsOutOfRange("+tokenRateTooBig+", "+blockTimestampForNextUpdate+")" - ) - - blockTimestampForNextUpdate += 1000; - await assert.revertsWith( - tokenRateOracle.connect(bridge).updateRate(tokenRateTooSmall, blockTimestampForNextUpdate), - "ErrorTokenRateIsOutOfRange("+tokenRateTooSmall+", "+blockTimestampForNextUpdate+")" - ) - }) - - .test("updateRate() :: happy path called by bridge", async (ctx) => { - const { tokenRateOracle } = ctx.contracts; - const { bridge } = ctx.accounts; - const { blockTimestamp } = ctx.constants; - - const decimalsBN = BigNumber.from(10).pow(18-2); - const newTokenRateCorrect = BigNumber.from('125').mul(decimalsBN); - const blockTimestampInFuture = blockTimestamp + 1000; - const tx = await tokenRateOracle.connect(bridge).updateRate(newTokenRateCorrect, blockTimestampInFuture); - - await assert.emits(tokenRateOracle, tx, "TokenRateL1TimestampAheadOfL2Time", [ - newTokenRateCorrect, - blockTimestampInFuture - ]); - - await assert.emits(tokenRateOracle, tx, "RateUpdated", [ - newTokenRateCorrect, - blockTimestampInFuture - ]); - - assert.equalBN(await tokenRateOracle.latestAnswer(), newTokenRateCorrect); - - const { - roundId_, - answer_, - startedAt_, - updatedAt_, - answeredInRound_ - } = await tokenRateOracle.latestRoundData(); - - assert.equalBN(roundId_, blockTimestampInFuture); - assert.equalBN(answer_, newTokenRateCorrect); - assert.equalBN(startedAt_, blockTimestampInFuture); - assert.equalBN(updatedAt_, blockTimestampInFuture); - assert.equalBN(answeredInRound_, blockTimestampInFuture); - assert.equalBN(await tokenRateOracle.decimals(), 18); - }) - - .test("updateRate() :: happy path called by messenger with correct cross-domain sender", async (ctx) => { - const { tokenRateOracle, l2MessengerStub } = ctx.contracts; - const { l2MessengerStubEOA, l1TokenBridgeEOA } = ctx.accounts; - const { blockTimestamp } = ctx.constants; - - await l2MessengerStub.setXDomainMessageSender(l1TokenBridgeEOA.address); - - const decimalsBN = BigNumber.from(10).pow(18-2); - const newTokenRateCorrect = BigNumber.from('125').mul(decimalsBN); - const blockTimestampInFuture = blockTimestamp + 1000; - const tx = await tokenRateOracle.connect(l2MessengerStubEOA).updateRate(newTokenRateCorrect, blockTimestampInFuture); - - await assert.emits(tokenRateOracle, tx, "TokenRateL1TimestampAheadOfL2Time", [ - newTokenRateCorrect, - blockTimestampInFuture - ]); - - await assert.emits(tokenRateOracle, tx, "RateUpdated", [ - newTokenRateCorrect, - blockTimestampInFuture - ]); - - assert.equalBN(await tokenRateOracle.latestAnswer(), newTokenRateCorrect); - - const { - roundId_, - answer_, - startedAt_, - updatedAt_, - answeredInRound_ - } = await tokenRateOracle.latestRoundData(); - - assert.equalBN(roundId_, blockTimestampInFuture); - assert.equalBN(answer_, newTokenRateCorrect); - assert.equalBN(startedAt_, blockTimestampInFuture); - assert.equalBN(updatedAt_, blockTimestampInFuture); - assert.equalBN(answeredInRound_, blockTimestampInFuture); - assert.equalBN(await tokenRateOracle.decimals(), 18); - }) - - .run(); + .test("initialize() :: petrified version", async (ctx) => { + const { deployer, bridge, l1TokenBridgeEOA } = ctx.accounts; + const { l2MessengerStub } = ctx.contracts; + + const tokenRateOracleImpl = await new TokenRateOracle__factory(deployer).deploy( + l2MessengerStub.address, + bridge.address, + l1TokenBridgeEOA.address, + 86400, + 86400, + 500 + ); + + const petrifiedVersionMark = hre.ethers.constants.MaxUint256; + assert.equalBN(await tokenRateOracleImpl.getContractVersion(), petrifiedVersionMark); + + await assert.revertsWith( + tokenRateOracleImpl.initialize(1, 2), + "NonZeroContractVersionOnInit()" + ); + }) + + .test("initialize() :: re-initialization", async (ctx) => { + const { tokenRateOracle } = ctx.contracts; + + assert.equalBN(await tokenRateOracle.getContractVersion(), 1); + + await assert.revertsWith( + tokenRateOracle.initialize(2, 3), + "NonZeroContractVersionOnInit()" + ); + }) + + .test("state after init", async (ctx) => { + const { tokenRateOracle, l2MessengerStub } = ctx.contracts; + const { bridge, l1TokenBridgeEOA } = ctx.accounts; + const { tokenRate, blockTimestamp } = ctx.constants; + + assert.equal(await tokenRateOracle.MESSENGER(), l2MessengerStub.address); + assert.equal(await tokenRateOracle.L2_ERC20_TOKEN_BRIDGE(), bridge.address); + assert.equal(await tokenRateOracle.L1_TOKEN_RATE_PUSHER(), l1TokenBridgeEOA.address); + assert.equalBN(await tokenRateOracle.TOKEN_RATE_OUTDATED_DELAY(), 86400); + + assert.equalBN(await tokenRateOracle.latestAnswer(), tokenRate); + + const { + roundId_, + answer_, + startedAt_, + updatedAt_, + answeredInRound_ + } = await tokenRateOracle.latestRoundData(); + + assert.equalBN(roundId_, blockTimestamp); + assert.equalBN(answer_, tokenRate); + assert.equalBN(startedAt_, blockTimestamp); + assert.equalBN(updatedAt_, blockTimestamp); + assert.equalBN(answeredInRound_, blockTimestamp); + assert.equalBN(await tokenRateOracle.decimals(), 18); + }) + + .test("updateRate() :: called by non-bridge account", async (ctx) => { + const { tokenRateOracle } = ctx.contracts; + const { stranger } = ctx.accounts; + await assert.revertsWith( + tokenRateOracle.connect(stranger).updateRate(10, 40), + "ErrorNotBridgeOrTokenRatePusher()" + ); + }) + + .test("updateRate() :: called by messenger with incorrect cross-domain sender", async (ctx) => { + const { tokenRateOracle, l2MessengerStub } = ctx.contracts; + const { stranger, l2MessengerStubEOA } = ctx.accounts; + await l2MessengerStub.setXDomainMessageSender(stranger.address); + await assert.revertsWith(tokenRateOracle.connect( + l2MessengerStubEOA).updateRate(10, 40), + "ErrorNotBridgeOrTokenRatePusher()" + ); + }) + + .test("updateRate() :: L1 time exceeded allowed L2 clock lag", async (ctx) => { + const { tokenRateOracle } = ctx.contracts; + const { bridge } = ctx.accounts; + const { tokenRate, blockTimestamp } = ctx.constants; + + const exceededTime = blockTimestamp.add(86400).add(40); // more than one day + await assert.revertsWith( + tokenRateOracle.connect(bridge).updateRate(tokenRate, exceededTime), + "ErrorL1TimestampExceededAllowedClockLag(" + tokenRate + ", " + exceededTime + ")" + ) + }) + + .test("updateRate() :: received token rate is in the past or same time", async (ctx) => { + const { tokenRateOracle } = ctx.contracts; + const { bridge } = ctx.accounts; + const { tokenRate, blockTimestamp } = ctx.constants; + + const tx0 = await tokenRateOracle + .connect(bridge) + .updateRate(tokenRate, blockTimestamp); + + await assert.emits(tokenRateOracle, tx0, "DormantTokenRateUpdateIgnored", [ + tokenRate, + blockTimestamp, + blockTimestamp, + ]); + + const timeInPast = blockTimestamp.sub(1000); + const tx1 = await tokenRateOracle + .connect(bridge) + .updateRate(tokenRate, timeInPast); + + await assert.emits(tokenRateOracle, tx1, "DormantTokenRateUpdateIgnored", [ + tokenRate, + blockTimestamp, + timeInPast, + ]); + }) + + .test("updateRate() :: token rate is out of range", async (ctx) => { + const { tokenRateOracle } = ctx.contracts; + const { bridge } = ctx.accounts; + const { tokenRate, blockTimestamp } = ctx.constants; + + const tokenRateTooBig = tokenRate.mul(BigNumber.from('106')).div(BigNumber.from('100')); // 106% + const tokenRateTooSmall = tokenRate.mul(BigNumber.from('94')).div(BigNumber.from('100')); // 94% + + var blockTimestampForNextUpdate = blockTimestamp.add(1000); + await assert.revertsWith( + tokenRateOracle.connect(bridge).updateRate(tokenRateTooBig, blockTimestampForNextUpdate), + "ErrorTokenRateIsOutOfRange(" + tokenRateTooBig + ", " + blockTimestampForNextUpdate + ")" + ) + + blockTimestampForNextUpdate = blockTimestampForNextUpdate.add(1000); + await assert.revertsWith( + tokenRateOracle.connect(bridge).updateRate(tokenRateTooSmall, blockTimestampForNextUpdate), + "ErrorTokenRateIsOutOfRange(" + tokenRateTooSmall + ", " + blockTimestampForNextUpdate + ")" + ) + }) + + .test("updateRate() :: happy path called by bridge", async (ctx) => { + const { tokenRateOracle } = ctx.contracts; + const { bridge } = ctx.accounts; + const { tokenRate, blockTimestamp } = ctx.constants; + + const newTokenRate = tokenRate.mul(BigNumber.from('104')).div(BigNumber.from('100')); // 104% + + const blockTimestampInFuture = blockTimestamp.add(1000); + const tx = await tokenRateOracle.connect(bridge).updateRate(newTokenRate, blockTimestampInFuture); + + await assert.emits(tokenRateOracle, tx, "TokenRateL1TimestampAheadOfL2Time", [ + newTokenRate, + blockTimestampInFuture + ]); + + await assert.emits(tokenRateOracle, tx, "RateUpdated", [ + newTokenRate, + blockTimestampInFuture + ]); + + assert.equalBN(await tokenRateOracle.latestAnswer(), newTokenRate); + + const { + roundId_, + answer_, + startedAt_, + updatedAt_, + answeredInRound_ + } = await tokenRateOracle.latestRoundData(); + + assert.equalBN(roundId_, blockTimestampInFuture); + assert.equalBN(answer_, newTokenRate); + assert.equalBN(startedAt_, blockTimestampInFuture); + assert.equalBN(updatedAt_, blockTimestampInFuture); + assert.equalBN(answeredInRound_, blockTimestampInFuture); + assert.equalBN(await tokenRateOracle.decimals(), 18); + }) + + .test("updateRate() :: happy path called by messenger with correct cross-domain sender", async (ctx) => { + const { tokenRateOracle, l2MessengerStub } = ctx.contracts; + const { l2MessengerStubEOA, l1TokenBridgeEOA } = ctx.accounts; + const { tokenRate, blockTimestamp } = ctx.constants; + + await l2MessengerStub.setXDomainMessageSender(l1TokenBridgeEOA.address); + + const newTokenRate = tokenRate.mul(BigNumber.from('104')).div(BigNumber.from('100')); // 104% + + const blockTimestampInFuture = blockTimestamp.add(1000); + const tx = await tokenRateOracle.connect(l2MessengerStubEOA).updateRate(newTokenRate, blockTimestampInFuture); + + await assert.emits(tokenRateOracle, tx, "TokenRateL1TimestampAheadOfL2Time", [ + newTokenRate, + blockTimestampInFuture + ]); + + await assert.emits(tokenRateOracle, tx, "RateUpdated", [ + newTokenRate, + blockTimestampInFuture + ]); + + assert.equalBN(await tokenRateOracle.latestAnswer(), newTokenRate); + + const { + roundId_, + answer_, + startedAt_, + updatedAt_, + answeredInRound_ + } = await tokenRateOracle.latestRoundData(); + + assert.equalBN(roundId_, blockTimestampInFuture); + assert.equalBN(answer_, newTokenRate); + assert.equalBN(startedAt_, blockTimestampInFuture); + assert.equalBN(updatedAt_, blockTimestampInFuture); + assert.equalBN(answeredInRound_, blockTimestampInFuture); + assert.equalBN(await tokenRateOracle.decimals(), 18); + }) + + .run(); async function ctxFactory() { - const decimals = 18; - const decimalsBN = BigNumber.from(10).pow(decimals-1); - const tokenRateCorrect = BigNumber.from('12').mul(decimalsBN); - const tokenRateTooBig = BigNumber.from('2000').pow(decimals); - const tokenRateTooSmall = BigNumber.from('1').pow(decimals-3); - - const provider = await hre.ethers.provider; - const blockNumber = await provider.getBlockNumber(); - const blockTimestamp = (await provider.getBlock(blockNumber)).timestamp; + const tokenRate = BigNumber.from('1164454276599657236'); // value taken from real contact on 23.04.24 + const tokenRateOutdatedDelay = BigNumber.from(86400); // 1 day + const maxAllowedL2ToL1ClockLag = BigNumber.from(86400); // 1 day + const maxAllowedTokenRateDeviationPerDay = BigNumber.from(500); // 5% + const blockTimestamp = await getBlockTimestamp(0); const [deployer, bridge, stranger, l1TokenBridgeEOA] = await hre.ethers.getSigners(); @@ -264,34 +244,30 @@ async function ctxFactory() { ).deploy({ value: wei.toBigNumber(wei`1 ether`) }); const l2MessengerStubEOA = await testing.impersonate(l2MessengerStub.address); - const tokenRateOracleImpl = await new TokenRateOracle__factory(deployer).deploy( + const tokenRateOracle = await tokenRateOracleUnderProxy( + deployer, l2MessengerStub.address, bridge.address, l1TokenBridgeEOA.address, - 86400, - 86400, - 500 - ); - - const tokenRateOracleProxy = await new OssifiableProxy__factory( - deployer - ).deploy( - tokenRateOracleImpl.address, - deployer.address, - tokenRateOracleImpl.interface.encodeFunctionData("initialize", [ - tokenRateCorrect, - blockTimestamp - ]) - ); - - const tokenRateOracle = TokenRateOracle__factory.connect( - tokenRateOracleProxy.address, - deployer + tokenRateOutdatedDelay, + maxAllowedL2ToL1ClockLag, + maxAllowedTokenRateDeviationPerDay, + tokenRate, + blockTimestamp ); return { - accounts: { deployer, bridge, stranger, l1TokenBridgeEOA, l2MessengerStubEOA }, - contracts: { tokenRateOracle, l2MessengerStub }, - constants: { tokenRateCorrect, tokenRateTooBig, tokenRateTooSmall, blockTimestamp } + accounts: { deployer, bridge, stranger, l1TokenBridgeEOA, l2MessengerStubEOA }, + contracts: { tokenRateOracle, l2MessengerStub }, + constants: { + tokenRate, blockTimestamp, tokenRateOutdatedDelay, + maxAllowedL2ToL1ClockLag, maxAllowedTokenRateDeviationPerDay + } }; } + +async function getBlockTimestamp(shift: number) { + const provider = await hre.ethers.provider; + const blockNumber = await provider.getBlockNumber(); + return BigNumber.from((await provider.getBlock(blockNumber)).timestamp + shift); +} diff --git a/test/optimism/bridging-rebasable.integration.test.ts b/test/optimism/bridging-rebasable.integration.test.ts index 905b6b36..74d97794 100644 --- a/test/optimism/bridging-rebasable.integration.test.ts +++ b/test/optimism/bridging-rebasable.integration.test.ts @@ -7,6 +7,7 @@ import testing, { scenario } from "../../utils/testing"; import { ethers } from "hardhat"; import { JsonRpcProvider } from "@ethersproject/providers"; import { ERC20WrapperStub } from "../../typechain"; +import { BigNumber } from 'ethers' scenario("Optimism :: Bridging rebasable token integration test", ctxFactory) .after(async (ctx) => { @@ -305,6 +306,8 @@ scenario("Optimism :: Bridging rebasable token integration test", ctxFactory) ] ); + console.log("tokenHolderA.address=",tokenHolderA.address); + const messageNonce = await l1CrossDomainMessenger.messageNonce(); await assert.emits(l1CrossDomainMessenger, tx, "SentMessage", [ @@ -315,448 +318,448 @@ scenario("Optimism :: Bridging rebasable token integration test", ctxFactory) 200_000, ]); - assert.equalBN( - await l1Token.balanceOf(l1LidoTokensBridge.address), - l1ERC20ExtendedTokensBridgeBalanceBefore.add(depositAmountNonRebasable) - ); - - assert.equalBN( - await l1TokenRebasable.balanceOf(tokenHolderA.address), // stETH - tokenHolderABalanceBefore.sub(depositAmountRebasable) - ); - }) - - .step("Finalize deposit on L2", async (ctx) => { - const { - l1Token, - l1TokenRebasable, - l2TokenRebasable, - l1LidoTokensBridge, - l2CrossDomainMessenger, - l2ERC20ExtendedTokensBridge, - l2Provider - } = ctx; - const { depositAmountNonRebasable, depositAmountRebasable } = ctx.common; - - const { accountA: tokenHolderA, l1CrossDomainMessengerAliased } = - ctx.accounts; - - const tokenHolderABalanceBefore = await l2TokenRebasable.balanceOf( - tokenHolderA.address - ); - - const l2TokenRebasableTotalSupplyBefore = await l2TokenRebasable.totalSupply(); - const dataToReceive = await packedTokenRateAndTimestamp(l2Provider, l1Token); - - const tx = await l2CrossDomainMessenger - .connect(l1CrossDomainMessengerAliased) - .relayMessage( - 1, - l1LidoTokensBridge.address, - l2ERC20ExtendedTokensBridge.address, - 0, - 300_000, - l2ERC20ExtendedTokensBridge.interface.encodeFunctionData("finalizeDeposit", [ - l1TokenRebasable.address, - l2TokenRebasable.address, - tokenHolderA.address, - tokenHolderA.address, - depositAmountNonRebasable, - dataToReceive, - ]), - { gasLimit: 5_000_000 } - ); - - await assert.emits(l2ERC20ExtendedTokensBridge, tx, "DepositFinalized", [ - l1TokenRebasable.address, - l2TokenRebasable.address, - tokenHolderA.address, - tokenHolderA.address, - depositAmountRebasable, - "0x", - ]); - - assert.equalBN( - await l2TokenRebasable.balanceOf(tokenHolderA.address), - tokenHolderABalanceBefore.add(depositAmountRebasable) - ); - assert.equalBN( - await l2TokenRebasable.totalSupply(), - l2TokenRebasableTotalSupplyBefore.add(depositAmountRebasable) - ); - }) - - .step("L2 -> L1 withdrawal via withdraw()", async (ctx) => { - const { accountA: tokenHolderA } = ctx.accounts; - const { withdrawalAmountRebasable } = ctx.common; - const { - l1TokenRebasable, - l2TokenRebasable, - l2ERC20ExtendedTokensBridge - } = ctx; - - const tokenHolderABalanceBefore = await l2TokenRebasable.balanceOf( - tokenHolderA.address - ); - const l2TotalSupplyBefore = await l2TokenRebasable.totalSupply(); - - const tx = await l2ERC20ExtendedTokensBridge - .connect(tokenHolderA.l2Signer) - .withdraw( - l2TokenRebasable.address, - withdrawalAmountRebasable, - 0, - "0x" - ); - - await assert.emits(l2ERC20ExtendedTokensBridge, tx, "WithdrawalInitiated", [ - l1TokenRebasable.address, - l2TokenRebasable.address, - tokenHolderA.address, - tokenHolderA.address, - withdrawalAmountRebasable, - "0x", - ]); - - assert.equalBN( - await l2TokenRebasable.balanceOf(tokenHolderA.address), - tokenHolderABalanceBefore.sub(withdrawalAmountRebasable) - ); - assert.equalBN( - await l2TokenRebasable.totalSupply(), - l2TotalSupplyBefore.sub(withdrawalAmountRebasable) - ); - }) - - .step("Finalize withdrawal on L1", async (ctx) => { - const { - l1Token, - l1TokenRebasable, - l1CrossDomainMessenger, - l1LidoTokensBridge, - l2CrossDomainMessenger, - l2TokenRebasable, - l2ERC20ExtendedTokensBridge, - } = ctx; - const { accountA: tokenHolderA, l1Stranger } = ctx.accounts; - const { withdrawalAmountNonRebasable, withdrawalAmountRebasable } = ctx.common; + // assert.equalBN( + // await l1Token.balanceOf(l1LidoTokensBridge.address), + // l1ERC20ExtendedTokensBridgeBalanceBefore.add(depositAmountNonRebasable) + // ); - const tokenHolderABalanceBefore = await l1TokenRebasable.balanceOf( - tokenHolderA.address - ); - const l1ERC20ExtendedTokensBridgeBalanceBefore = await l1Token.balanceOf( - l1LidoTokensBridge.address - ); - - await l1CrossDomainMessenger - .connect(l1Stranger) - .setXDomainMessageSender(l2ERC20ExtendedTokensBridge.address); - - const tx = await l1CrossDomainMessenger - .connect(l1Stranger) - .relayMessage( - l1LidoTokensBridge.address, - l2CrossDomainMessenger.address, - l1LidoTokensBridge.interface.encodeFunctionData( - "finalizeERC20Withdrawal", - [ - l1TokenRebasable.address, - l2TokenRebasable.address, - tokenHolderA.address, - tokenHolderA.address, - withdrawalAmountNonRebasable, - "0x", - ] - ), - 0 - ); - - await assert.emits(l1LidoTokensBridge, tx, "ERC20WithdrawalFinalized", [ - l1TokenRebasable.address, - l2TokenRebasable.address, - tokenHolderA.address, - tokenHolderA.address, - withdrawalAmountRebasable, - "0x", - ]); - - assert.equalBN( - await l1Token.balanceOf(l1LidoTokensBridge.address), - l1ERC20ExtendedTokensBridgeBalanceBefore.sub(withdrawalAmountNonRebasable) - ); - - assert.equalBN( - await l1TokenRebasable.balanceOf(tokenHolderA.address), - tokenHolderABalanceBefore.add(withdrawalAmountRebasable) - ); + // assert.equalBN( + // await l1TokenRebasable.balanceOf(tokenHolderA.address), // stETH + // tokenHolderABalanceBefore.sub(depositAmountRebasable) + // ); }) - - .step("L1 -> L2 deposit via depositERC20To()", async (ctx) => { - - const { - l1Token, - l1TokenRebasable, - l1LidoTokensBridge, - l2TokenRebasable, - l1CrossDomainMessenger, - l2ERC20ExtendedTokensBridge, - l1Provider - } = ctx; - const { accountA: tokenHolderA, accountB: tokenHolderB } = ctx.accounts; - assert.notEqual(tokenHolderA.address, tokenHolderB.address); - - const { exchangeRate } = ctx.common; - const depositAmountNonRebasable = wei`0.03 ether`; - const depositAmountRebasable = wei.toBigNumber(depositAmountNonRebasable).mul(exchangeRate); - - await l1TokenRebasable - .connect(tokenHolderA.l1Signer) - .approve(l1LidoTokensBridge.address, depositAmountRebasable); - - const tokenHolderABalanceBefore = await l1TokenRebasable.balanceOf( - tokenHolderA.address - ); - const l1ERC20ExtendedTokensBridgeBalanceBefore = await l1Token.balanceOf( - l1LidoTokensBridge.address - ); - - const tx = await l1LidoTokensBridge - .connect(tokenHolderA.l1Signer) - .depositERC20To( - l1TokenRebasable.address, - l2TokenRebasable.address, - tokenHolderB.address, - depositAmountRebasable, - 200_000, - "0x" - ); - - const dataToSend = await packedTokenRateAndTimestamp(l1Provider, l1Token); - - await assert.emits(l1LidoTokensBridge, tx, "ERC20DepositInitiated", [ - l1TokenRebasable.address, - l2TokenRebasable.address, - tokenHolderA.address, - tokenHolderB.address, - depositAmountRebasable, - dataToSend, - ]); - - const l2DepositCalldata = l2ERC20ExtendedTokensBridge.interface.encodeFunctionData( - "finalizeDeposit", - [ - l1TokenRebasable.address, - l2TokenRebasable.address, - tokenHolderA.address, - tokenHolderB.address, - depositAmountNonRebasable, - dataToSend, - ] - ); - - const messageNonce = await l1CrossDomainMessenger.messageNonce(); - - await assert.emits(l1CrossDomainMessenger, tx, "SentMessage", [ - l2ERC20ExtendedTokensBridge.address, - l1LidoTokensBridge.address, - l2DepositCalldata, - messageNonce, - 200_000, - ]); - - assert.equalBN( - await l1Token.balanceOf(l1LidoTokensBridge.address), - l1ERC20ExtendedTokensBridgeBalanceBefore.add(depositAmountNonRebasable) - ); - - assert.equalBN( - await l1TokenRebasable.balanceOf(tokenHolderA.address), // stETH - tokenHolderABalanceBefore.sub(depositAmountRebasable) - ); - }) - - .step("Finalize deposit on L2", async (ctx) => { - const { - l1Token, - l1TokenRebasable, - l1LidoTokensBridge, - l2TokenRebasable, - l2CrossDomainMessenger, - l2ERC20ExtendedTokensBridge, - l2Provider - } = ctx; - - const { - accountA: tokenHolderA, - accountB: tokenHolderB, - l1CrossDomainMessengerAliased, - } = ctx.accounts; - - const { exchangeRate } = ctx.common; - - const depositAmountNonRebasable = wei`0.03 ether`; - const depositAmountRebasable = wei.toBigNumber(depositAmountNonRebasable).mul(exchangeRate); - - const dataToReceive = await packedTokenRateAndTimestamp(l2Provider, l1Token); - - const l2TokenRebasableTotalSupplyBefore = await l2TokenRebasable.totalSupply(); - - const tokenHolderBBalanceBefore = await l2TokenRebasable.balanceOf( - tokenHolderB.address - ); - - const tx = await l2CrossDomainMessenger - .connect(l1CrossDomainMessengerAliased) - .relayMessage( - 1, - l1LidoTokensBridge.address, - l2ERC20ExtendedTokensBridge.address, - 0, - 300_000, - l2ERC20ExtendedTokensBridge.interface.encodeFunctionData("finalizeDeposit", [ - l1TokenRebasable.address, - l2TokenRebasable.address, - tokenHolderA.address, - tokenHolderB.address, - depositAmountNonRebasable, - dataToReceive, - ]), - { gasLimit: 5_000_000 } - ); - - await assert.emits(l2ERC20ExtendedTokensBridge, tx, "DepositFinalized", [ - l1TokenRebasable.address, - l2TokenRebasable.address, - tokenHolderA.address, - tokenHolderB.address, - depositAmountRebasable, - "0x", - ]); - - assert.equalBN( - await l2TokenRebasable.balanceOf(tokenHolderB.address), - tokenHolderBBalanceBefore.add(depositAmountRebasable) - ); - - assert.equalBN( - await l2TokenRebasable.totalSupply(), - l2TokenRebasableTotalSupplyBefore.add(depositAmountRebasable) - ); - }) - - .step("L2 -> L1 withdrawal via withdrawTo()", async (ctx) => { - const { l1TokenRebasable, l2TokenRebasable, l2ERC20ExtendedTokensBridge } = ctx; - const { accountA: tokenHolderA, accountB: tokenHolderB } = ctx.accounts; - - const { exchangeRate } = ctx.common; - const withdrawalAmountNonRebasable = wei`0.03 ether`; - const withdrawalAmountRebasable = wei.toBigNumber(withdrawalAmountNonRebasable).mul(exchangeRate); - - const tokenHolderBBalanceBefore = await l2TokenRebasable.balanceOf( - tokenHolderB.address - ); - const l2TotalSupplyBefore = await l2TokenRebasable.totalSupply(); - - const tx = await l2ERC20ExtendedTokensBridge - .connect(tokenHolderB.l2Signer) - .withdrawTo( - l2TokenRebasable.address, - tokenHolderA.address, - withdrawalAmountRebasable, - 0, - "0x" - ); - - await assert.emits(l2ERC20ExtendedTokensBridge, tx, "WithdrawalInitiated", [ - l1TokenRebasable.address, - l2TokenRebasable.address, - tokenHolderB.address, - tokenHolderA.address, - withdrawalAmountRebasable, - "0x", - ]); - - assert.equalBN( - await l2TokenRebasable.balanceOf(tokenHolderB.address), - tokenHolderBBalanceBefore.sub(withdrawalAmountRebasable) - ); - - assert.equalBN( - await l2TokenRebasable.totalSupply(), - l2TotalSupplyBefore.sub(withdrawalAmountRebasable) - ); - }) - - .step("Finalize withdrawal on L1", async (ctx) => { - const { - l1Token, - l1TokenRebasable, - l1CrossDomainMessenger, - l1LidoTokensBridge, - l2CrossDomainMessenger, - l2TokenRebasable, - l2ERC20ExtendedTokensBridge, - } = ctx; - const { - accountA: tokenHolderA, - accountB: tokenHolderB, - l1Stranger, - } = ctx.accounts; - - const { exchangeRate } = ctx.common; - const withdrawalAmountNonRebasable = wei`0.03 ether`; - const withdrawalAmountRebasable = wei.toBigNumber(withdrawalAmountNonRebasable).mul(exchangeRate); - - const tokenHolderABalanceBefore = await l1TokenRebasable.balanceOf( - tokenHolderA.address - ); - const l1ERC20ExtendedTokensBridgeBalanceBefore = await l1Token.balanceOf( - l1LidoTokensBridge.address - ); - - await l1CrossDomainMessenger - .connect(l1Stranger) - .setXDomainMessageSender(l2ERC20ExtendedTokensBridge.address); - - const tx = await l1CrossDomainMessenger - .connect(l1Stranger) - .relayMessage( - l1LidoTokensBridge.address, - l2CrossDomainMessenger.address, - l1LidoTokensBridge.interface.encodeFunctionData( - "finalizeERC20Withdrawal", - [ - l1TokenRebasable.address, - l2TokenRebasable.address, - tokenHolderB.address, - tokenHolderA.address, - withdrawalAmountNonRebasable, - "0x", - ] - ), - 0 - ); - - await assert.emits(l1LidoTokensBridge, tx, "ERC20WithdrawalFinalized", [ - l1TokenRebasable.address, - l2TokenRebasable.address, - tokenHolderB.address, - tokenHolderA.address, - withdrawalAmountRebasable, - "0x", - ]); - - assert.equalBN( - await l1Token.balanceOf(l1LidoTokensBridge.address), - l1ERC20ExtendedTokensBridgeBalanceBefore.sub(withdrawalAmountNonRebasable) - ); - - assert.equalBN( - await l1TokenRebasable.balanceOf(tokenHolderA.address), - tokenHolderABalanceBefore.add(withdrawalAmountRebasable) - ); - }) +// .step("Finalize deposit on L2", async (ctx) => { +// const { +// l1Token, +// l1TokenRebasable, +// l2TokenRebasable, +// l1LidoTokensBridge, +// l2CrossDomainMessenger, +// l2ERC20ExtendedTokensBridge, +// l2Provider +// } = ctx; +// const { depositAmountNonRebasable, depositAmountRebasable } = ctx.common; + +// const { accountA: tokenHolderA, l1CrossDomainMessengerAliased } = +// ctx.accounts; + +// const tokenHolderABalanceBefore = await l2TokenRebasable.balanceOf( +// tokenHolderA.address +// ); + +// const l2TokenRebasableTotalSupplyBefore = await l2TokenRebasable.totalSupply(); +// const dataToReceive = await packedTokenRateAndTimestamp(l2Provider, l1Token); + +// const tx = await l2CrossDomainMessenger +// .connect(l1CrossDomainMessengerAliased) +// .relayMessage( +// 1, +// l1LidoTokensBridge.address, +// l2ERC20ExtendedTokensBridge.address, +// 0, +// 300_000, +// l2ERC20ExtendedTokensBridge.interface.encodeFunctionData("finalizeDeposit", [ +// l1TokenRebasable.address, +// l2TokenRebasable.address, +// tokenHolderA.address, +// tokenHolderA.address, +// depositAmountNonRebasable, +// dataToReceive, +// ]), +// { gasLimit: 5_000_000 } +// ); + +// await assert.emits(l2ERC20ExtendedTokensBridge, tx, "DepositFinalized", [ +// l1TokenRebasable.address, +// l2TokenRebasable.address, +// tokenHolderA.address, +// tokenHolderA.address, +// depositAmountRebasable, +// "0x", +// ]); + +// assert.equalBN( +// await l2TokenRebasable.balanceOf(tokenHolderA.address), +// tokenHolderABalanceBefore.add(depositAmountRebasable) +// ); +// assert.equalBN( +// await l2TokenRebasable.totalSupply(), +// l2TokenRebasableTotalSupplyBefore.add(depositAmountRebasable) +// ); +// }) + +// .step("L2 -> L1 withdrawal via withdraw()", async (ctx) => { +// const { accountA: tokenHolderA } = ctx.accounts; +// const { withdrawalAmountRebasable } = ctx.common; +// const { +// l1TokenRebasable, +// l2TokenRebasable, +// l2ERC20ExtendedTokensBridge +// } = ctx; + +// const tokenHolderABalanceBefore = await l2TokenRebasable.balanceOf( +// tokenHolderA.address +// ); +// const l2TotalSupplyBefore = await l2TokenRebasable.totalSupply(); + +// const tx = await l2ERC20ExtendedTokensBridge +// .connect(tokenHolderA.l2Signer) +// .withdraw( +// l2TokenRebasable.address, +// withdrawalAmountRebasable, +// 0, +// "0x" +// ); + +// await assert.emits(l2ERC20ExtendedTokensBridge, tx, "WithdrawalInitiated", [ +// l1TokenRebasable.address, +// l2TokenRebasable.address, +// tokenHolderA.address, +// tokenHolderA.address, +// withdrawalAmountRebasable, +// "0x", +// ]); + +// assert.equalBN( +// await l2TokenRebasable.balanceOf(tokenHolderA.address), +// tokenHolderABalanceBefore.sub(withdrawalAmountRebasable) +// ); +// assert.equalBN( +// await l2TokenRebasable.totalSupply(), +// l2TotalSupplyBefore.sub(withdrawalAmountRebasable) +// ); +// }) + +// .step("Finalize withdrawal on L1", async (ctx) => { +// const { +// l1Token, +// l1TokenRebasable, +// l1CrossDomainMessenger, +// l1LidoTokensBridge, +// l2CrossDomainMessenger, +// l2TokenRebasable, +// l2ERC20ExtendedTokensBridge, +// } = ctx; +// const { accountA: tokenHolderA, l1Stranger } = ctx.accounts; +// const { withdrawalAmountNonRebasable, withdrawalAmountRebasable } = ctx.common; + +// const tokenHolderABalanceBefore = await l1TokenRebasable.balanceOf( +// tokenHolderA.address +// ); +// const l1ERC20ExtendedTokensBridgeBalanceBefore = await l1Token.balanceOf( +// l1LidoTokensBridge.address +// ); + +// await l1CrossDomainMessenger +// .connect(l1Stranger) +// .setXDomainMessageSender(l2ERC20ExtendedTokensBridge.address); + +// const tx = await l1CrossDomainMessenger +// .connect(l1Stranger) +// .relayMessage( +// l1LidoTokensBridge.address, +// l2CrossDomainMessenger.address, +// l1LidoTokensBridge.interface.encodeFunctionData( +// "finalizeERC20Withdrawal", +// [ +// l1TokenRebasable.address, +// l2TokenRebasable.address, +// tokenHolderA.address, +// tokenHolderA.address, +// withdrawalAmountNonRebasable, +// "0x", +// ] +// ), +// 0 +// ); + +// await assert.emits(l1LidoTokensBridge, tx, "ERC20WithdrawalFinalized", [ +// l1TokenRebasable.address, +// l2TokenRebasable.address, +// tokenHolderA.address, +// tokenHolderA.address, +// withdrawalAmountRebasable, +// "0x", +// ]); + +// assert.equalBN( +// await l1Token.balanceOf(l1LidoTokensBridge.address), +// l1ERC20ExtendedTokensBridgeBalanceBefore.sub(withdrawalAmountNonRebasable) +// ); + +// assert.equalBN( +// await l1TokenRebasable.balanceOf(tokenHolderA.address), +// tokenHolderABalanceBefore.add(withdrawalAmountRebasable) +// ); +// }) + + +// .step("L1 -> L2 deposit via depositERC20To()", async (ctx) => { + +// const { +// l1Token, +// l1TokenRebasable, +// l1LidoTokensBridge, +// l2TokenRebasable, +// l1CrossDomainMessenger, +// l2ERC20ExtendedTokensBridge, +// l1Provider +// } = ctx; +// const { accountA: tokenHolderA, accountB: tokenHolderB } = ctx.accounts; +// assert.notEqual(tokenHolderA.address, tokenHolderB.address); + +// const { exchangeRate } = ctx.common; +// const depositAmountNonRebasable = wei`0.03 ether`; +// const depositAmountRebasable = wei.toBigNumber(depositAmountNonRebasable).mul(exchangeRate); + +// await l1TokenRebasable +// .connect(tokenHolderA.l1Signer) +// .approve(l1LidoTokensBridge.address, depositAmountRebasable); + +// const tokenHolderABalanceBefore = await l1TokenRebasable.balanceOf( +// tokenHolderA.address +// ); +// const l1ERC20ExtendedTokensBridgeBalanceBefore = await l1Token.balanceOf( +// l1LidoTokensBridge.address +// ); + +// const tx = await l1LidoTokensBridge +// .connect(tokenHolderA.l1Signer) +// .depositERC20To( +// l1TokenRebasable.address, +// l2TokenRebasable.address, +// tokenHolderB.address, +// depositAmountRebasable, +// 200_000, +// "0x" +// ); + +// const dataToSend = await packedTokenRateAndTimestamp(l1Provider, l1Token); + +// await assert.emits(l1LidoTokensBridge, tx, "ERC20DepositInitiated", [ +// l1TokenRebasable.address, +// l2TokenRebasable.address, +// tokenHolderA.address, +// tokenHolderB.address, +// depositAmountRebasable, +// dataToSend, +// ]); + +// const l2DepositCalldata = l2ERC20ExtendedTokensBridge.interface.encodeFunctionData( +// "finalizeDeposit", +// [ +// l1TokenRebasable.address, +// l2TokenRebasable.address, +// tokenHolderA.address, +// tokenHolderB.address, +// depositAmountNonRebasable, +// dataToSend, +// ] +// ); + +// const messageNonce = await l1CrossDomainMessenger.messageNonce(); + +// await assert.emits(l1CrossDomainMessenger, tx, "SentMessage", [ +// l2ERC20ExtendedTokensBridge.address, +// l1LidoTokensBridge.address, +// l2DepositCalldata, +// messageNonce, +// 200_000, +// ]); + +// assert.equalBN( +// await l1Token.balanceOf(l1LidoTokensBridge.address), +// l1ERC20ExtendedTokensBridgeBalanceBefore.add(depositAmountNonRebasable) +// ); + +// assert.equalBN( +// await l1TokenRebasable.balanceOf(tokenHolderA.address), // stETH +// tokenHolderABalanceBefore.sub(depositAmountRebasable) +// ); +// }) + +// .step("Finalize deposit on L2", async (ctx) => { +// const { +// l1Token, +// l1TokenRebasable, +// l1LidoTokensBridge, +// l2TokenRebasable, +// l2CrossDomainMessenger, +// l2ERC20ExtendedTokensBridge, +// l2Provider +// } = ctx; + +// const { +// accountA: tokenHolderA, +// accountB: tokenHolderB, +// l1CrossDomainMessengerAliased, +// } = ctx.accounts; + +// const { exchangeRate } = ctx.common; + +// const depositAmountNonRebasable = wei`0.03 ether`; +// const depositAmountRebasable = wei.toBigNumber(depositAmountNonRebasable).mul(exchangeRate); + +// const dataToReceive = await packedTokenRateAndTimestamp(l2Provider, l1Token); + +// const l2TokenRebasableTotalSupplyBefore = await l2TokenRebasable.totalSupply(); + +// const tokenHolderBBalanceBefore = await l2TokenRebasable.balanceOf( +// tokenHolderB.address +// ); + +// const tx = await l2CrossDomainMessenger +// .connect(l1CrossDomainMessengerAliased) +// .relayMessage( +// 1, +// l1LidoTokensBridge.address, +// l2ERC20ExtendedTokensBridge.address, +// 0, +// 300_000, +// l2ERC20ExtendedTokensBridge.interface.encodeFunctionData("finalizeDeposit", [ +// l1TokenRebasable.address, +// l2TokenRebasable.address, +// tokenHolderA.address, +// tokenHolderB.address, +// depositAmountNonRebasable, +// dataToReceive, +// ]), +// { gasLimit: 5_000_000 } +// ); + +// await assert.emits(l2ERC20ExtendedTokensBridge, tx, "DepositFinalized", [ +// l1TokenRebasable.address, +// l2TokenRebasable.address, +// tokenHolderA.address, +// tokenHolderB.address, +// depositAmountRebasable, +// "0x", +// ]); + +// assert.equalBN( +// await l2TokenRebasable.balanceOf(tokenHolderB.address), +// tokenHolderBBalanceBefore.add(depositAmountRebasable) +// ); + +// assert.equalBN( +// await l2TokenRebasable.totalSupply(), +// l2TokenRebasableTotalSupplyBefore.add(depositAmountRebasable) +// ); +// }) + +// .step("L2 -> L1 withdrawal via withdrawTo()", async (ctx) => { +// const { l1TokenRebasable, l2TokenRebasable, l2ERC20ExtendedTokensBridge } = ctx; +// const { accountA: tokenHolderA, accountB: tokenHolderB } = ctx.accounts; + +// const { exchangeRate } = ctx.common; +// const withdrawalAmountNonRebasable = wei`0.03 ether`; +// const withdrawalAmountRebasable = wei.toBigNumber(withdrawalAmountNonRebasable).mul(exchangeRate); + +// const tokenHolderBBalanceBefore = await l2TokenRebasable.balanceOf( +// tokenHolderB.address +// ); +// const l2TotalSupplyBefore = await l2TokenRebasable.totalSupply(); + +// const tx = await l2ERC20ExtendedTokensBridge +// .connect(tokenHolderB.l2Signer) +// .withdrawTo( +// l2TokenRebasable.address, +// tokenHolderA.address, +// withdrawalAmountRebasable, +// 0, +// "0x" +// ); + +// await assert.emits(l2ERC20ExtendedTokensBridge, tx, "WithdrawalInitiated", [ +// l1TokenRebasable.address, +// l2TokenRebasable.address, +// tokenHolderB.address, +// tokenHolderA.address, +// withdrawalAmountRebasable, +// "0x", +// ]); + +// assert.equalBN( +// await l2TokenRebasable.balanceOf(tokenHolderB.address), +// tokenHolderBBalanceBefore.sub(withdrawalAmountRebasable) +// ); + +// assert.equalBN( +// await l2TokenRebasable.totalSupply(), +// l2TotalSupplyBefore.sub(withdrawalAmountRebasable) +// ); +// }) + +// .step("Finalize withdrawal on L1", async (ctx) => { +// const { +// l1Token, +// l1TokenRebasable, +// l1CrossDomainMessenger, +// l1LidoTokensBridge, +// l2CrossDomainMessenger, +// l2TokenRebasable, +// l2ERC20ExtendedTokensBridge, +// } = ctx; +// const { +// accountA: tokenHolderA, +// accountB: tokenHolderB, +// l1Stranger, +// } = ctx.accounts; + +// const { exchangeRate } = ctx.common; +// const withdrawalAmountNonRebasable = wei`0.03 ether`; +// const withdrawalAmountRebasable = wei.toBigNumber(withdrawalAmountNonRebasable).mul(exchangeRate); + +// const tokenHolderABalanceBefore = await l1TokenRebasable.balanceOf( +// tokenHolderA.address +// ); +// const l1ERC20ExtendedTokensBridgeBalanceBefore = await l1Token.balanceOf( +// l1LidoTokensBridge.address +// ); + +// await l1CrossDomainMessenger +// .connect(l1Stranger) +// .setXDomainMessageSender(l2ERC20ExtendedTokensBridge.address); + +// const tx = await l1CrossDomainMessenger +// .connect(l1Stranger) +// .relayMessage( +// l1LidoTokensBridge.address, +// l2CrossDomainMessenger.address, +// l1LidoTokensBridge.interface.encodeFunctionData( +// "finalizeERC20Withdrawal", +// [ +// l1TokenRebasable.address, +// l2TokenRebasable.address, +// tokenHolderB.address, +// tokenHolderA.address, +// withdrawalAmountNonRebasable, +// "0x", +// ] +// ), +// 0 +// ); + +// await assert.emits(l1LidoTokensBridge, tx, "ERC20WithdrawalFinalized", [ +// l1TokenRebasable.address, +// l2TokenRebasable.address, +// tokenHolderB.address, +// tokenHolderA.address, +// withdrawalAmountRebasable, +// "0x", +// ]); + +// assert.equalBN( +// await l1Token.balanceOf(l1LidoTokensBridge.address), +// l1ERC20ExtendedTokensBridgeBalanceBefore.sub(withdrawalAmountNonRebasable) +// ); + +// assert.equalBN( +// await l1TokenRebasable.balanceOf(tokenHolderA.address), +// tokenHolderABalanceBefore.add(withdrawalAmountRebasable) +// ); +// }) .run(); @@ -779,12 +782,14 @@ async function ctxFactory() { const accountA = testing.accounts.accountA(l1Provider, l2Provider); const accountB = testing.accounts.accountB(l1Provider, l2Provider); - const exchangeRate = 2; + const exchangeRate = BigNumber.from('1164454276599657236'); const depositAmountNonRebasable = wei`0.15 ether`; - const depositAmountRebasable = wei.toBigNumber(depositAmountNonRebasable).mul(exchangeRate); + const depositAmountRebasable = wei.toBigNumber(depositAmountNonRebasable).mul(exchangeRate) + .div(BigNumber.from('1000000000000000000')); const withdrawalAmountNonRebasable = wei`0.05 ether`; - const withdrawalAmountRebasable = wei.toBigNumber(withdrawalAmountNonRebasable).mul(exchangeRate); + const withdrawalAmountRebasable = wei.toBigNumber(withdrawalAmountNonRebasable).mul(exchangeRate) + .div(BigNumber.from('1000000000000000000')); await testing.setBalance( await contracts.l1TokensHolder.getAddress(), diff --git a/test/optimism/pushingTokenRate.integration.test.ts b/test/optimism/pushingTokenRate.integration.test.ts index f5d9606c..ff9adae3 100644 --- a/test/optimism/pushingTokenRate.integration.test.ts +++ b/test/optimism/pushingTokenRate.integration.test.ts @@ -7,7 +7,6 @@ import network from "../../utils/network"; import testing, { scenario } from "../../utils/testing"; import deploymentOracle from "../../utils/optimism/deploymentOracle"; import { getBridgeExecutorParams } from "../../utils/bridge-executor"; -import { JsonRpcProvider } from "@ethersproject/providers"; import { BigNumber } from "ethers"; import { ERC20BridgedStub__factory, @@ -37,8 +36,11 @@ scenario("Optimism :: Token Rate Oracle integration test", ctxFactory) .connect(account.l1Signer) .handlePostTokenRebase(1, 2, 3, 4, 5, 6, 7); + const blockNumber = await l1Provider.getBlockNumber(); + const blockTimestamp = BigNumber.from((await l1Provider.getBlock(blockNumber)).timestamp); + const messageNonce = await l1CrossDomainMessenger.messageNonce(); - const [stEthPerTokenStr, blockTimestampStr] = await tokenRateAndTimestamp(l1Provider, tokenRate); + const [stEthPerTokenStr, blockTimestampStr] = await tokenRateAndTimestamp(tokenRate, blockTimestamp); const l2Calldata = tokenRateOracle.interface.encodeFunctionData( "updateRate", [ @@ -70,8 +72,11 @@ scenario("Optimism :: Token Rate Oracle integration test", ctxFactory) .connect(account.l1Signer) .setXDomainMessageSender(opTokenRatePusher); + const blockNumber = await l1Provider.getBlockNumber(); + const blockTimestamp = BigNumber.from((await l1Provider.getBlock(blockNumber)).timestamp); + const tokenRate = await l1Token.stEthPerToken(); - const [stEthPerTokenStr, blockTimestampStr] = await tokenRateAndTimestamp(l1Provider, tokenRate); + const [stEthPerTokenStr, blockTimestampStr] = await tokenRateAndTimestamp(tokenRate, blockTimestamp); const tx = await ctx.l2CrossDomainMessenger .connect(ctx.accounts.l1CrossDomainMessengerAliased) @@ -113,6 +118,12 @@ async function ctxFactory() { const l1Deployer = testing.accounts.deployer(l1Provider); const l2Deployer = testing.accounts.deployer(l2Provider); + const blockNumber = await l2Provider.getBlockNumber(); + const blockTimestamp = BigNumber.from((await l2Provider.getBlock(blockNumber)).timestamp); + + const blockTimestampInPast = BigNumber.from((await l2Provider.getBlock(blockNumber)).timestamp).sub(86400); + const tokenRate = BigNumber.from('1164454276599657236'); + const optContracts = optimism.contracts(networkName, { forking: true }); const l2CrossDomainMessenger = optContracts.L2CrossDomainMessenger; const testingOnDeployedContracts = testing.env.USE_DEPLOYED_CONTRACTS(false); @@ -137,7 +148,8 @@ async function ctxFactory() { const l1Token = await new ERC20WrapperStub__factory(l1Deployer).deploy( l1TokenRebasable.address, "Test Token", - "TT" + "TT", + tokenRate ); const [ethDeployScript, optDeployScript] = await deploymentOracle( networkName @@ -159,7 +171,13 @@ async function ctxFactory() { proxy: govBridgeExecutor.address, bridge: govBridgeExecutor.address, }, - contractsShift: 0 + contractsShift: 0, + tokenRateOracle: { + maxAllowedL2ToL1ClockLag: BigNumber.from(86400), + maxAllowedTokenRateDeviationPerDay: BigNumber.from(500), + tokenRate: tokenRate, + l1Timestamp: BigNumber.from(blockTimestampInPast) + } } ); @@ -201,6 +219,7 @@ async function ctxFactory() { l2CrossDomainMessenger, l1Token, l1Provider, + blockTimestamp, accounts: { accountA, l1CrossDomainMessengerAliased @@ -208,9 +227,7 @@ async function ctxFactory() { }; } -async function tokenRateAndTimestamp(provider: JsonRpcProvider, tokenRate: BigNumber) { - const blockNumber = await provider.getBlockNumber(); - const blockTimestamp = (await provider.getBlock(blockNumber)).timestamp; +async function tokenRateAndTimestamp(tokenRate: BigNumber, blockTimestamp: BigNumber) { const stEthPerTokenStr = ethers.utils.hexZeroPad(tokenRate.toHexString(), 12); const blockTimestampStr = ethers.utils.hexZeroPad(ethers.utils.hexlify(blockTimestamp), 5); return [stEthPerTokenStr, blockTimestampStr]; diff --git a/test/token/ERC20BridgedPermit.unit.test.ts b/test/token/ERC20BridgedPermit.unit.test.ts index f9884ca1..ddd0082c 100644 --- a/test/token/ERC20BridgedPermit.unit.test.ts +++ b/test/token/ERC20BridgedPermit.unit.test.ts @@ -6,6 +6,8 @@ import { } from "../../typechain"; import { unit } from "../../utils/testing"; import { wei } from "../../utils/wei"; +import { erc20BridgedPermitUnderProxy } from "../../utils/testing/contractsFactory"; +import { BigNumber } from "ethers"; unit("ERC20BridgedPermit", ctxFactory) .test("initial state", async (ctx) => { @@ -50,9 +52,9 @@ unit("ERC20BridgedPermit", ctxFactory) // deploy new implementation const l2TokenImpl = await new ERC20BridgedPermit__factory(deployer).deploy( - "", - "Symbol", - "", + "wstETH", + "wst", + "1", 9, owner.address ); @@ -88,7 +90,7 @@ unit("ERC20BridgedPermit", ctxFactory) const l2TokenImpl = await new ERC20BridgedPermit__factory(deployer).deploy( "", "Symbol", - "", + "1", 9, owner.address ); @@ -483,18 +485,11 @@ unit("ERC20BridgedPermit", ctxFactory) async function ctxFactory() { const name = "ERC20 Test Token"; const symbol = "ERC20"; - const decimals = 18; const version = "1"; + const decimals = BigNumber.from('18'); const premint = wei`100 ether`; - const [deployer, owner, recipient, spender, holder, stranger] = - await hre.ethers.getSigners(); - const l2TokenImpl = await new ERC20BridgedPermit__factory(deployer).deploy( - name, - symbol, - version, - decimals, - owner.address - ); + + const [deployer, owner, recipient, spender, holder, stranger] = await hre.ethers.getSigners(); await hre.network.provider.request({ method: "hardhat_impersonateAccount", @@ -503,20 +498,15 @@ async function ctxFactory() { const zero = await hre.ethers.getSigner(hre.ethers.constants.AddressZero); - const l2TokensProxy = await new OssifiableProxy__factory(deployer).deploy( - l2TokenImpl.address, - deployer.address, - ERC20BridgedPermit__factory.createInterface().encodeFunctionData("initialize", [ - name, - symbol, - version - ]) - ); - - const erc20BridgedProxied = ERC20BridgedPermit__factory.connect( - l2TokensProxy.address, - holder - ); + const erc20BridgedProxied = await erc20BridgedPermitUnderProxy( + deployer, + holder, + name, + symbol, + version, + decimals, + owner.address + ) await erc20BridgedProxied.connect(owner).bridgeMint(holder.address, premint); diff --git a/test/token/ERC20Permit.unit.test.ts b/test/token/ERC20Permit.unit.test.ts index 3b54abb4..5ed23cb1 100644 --- a/test/token/ERC20Permit.unit.test.ts +++ b/test/token/ERC20Permit.unit.test.ts @@ -328,9 +328,9 @@ function ctxFactoryFactory( return async () => { const decimalsToSet = 18; const decimals = BigNumber.from(10).pow(decimalsToSet); - const rate = BigNumber.from('12').pow(decimalsToSet - 1); + const tokenRate = BigNumber.from('1164454276599657236'); const premintShares = wei.toBigNumber(wei`100 ether`); - const premintTokens = BigNumber.from(rate).mul(premintShares).div(decimals); + const premintTokens = tokenRate.mul(premintShares).div(decimals); const [ deployer, @@ -354,7 +354,7 @@ function ctxFactoryFactory( name, symbol, decimalsToSet, - rate, + tokenRate, isRebasable, owner, deployer, @@ -365,7 +365,7 @@ function ctxFactoryFactory( return { accounts: { deployer, owner, recipient, spender, holder, stranger, zero, user1, user2 }, - constants: { name, symbol, decimalsToSet, decimals, premintShares, premintTokens, rate }, + constants: { name, symbol, decimalsToSet, decimals, premintShares, premintTokens, tokenRate }, contracts: { rebasableProxied }, permitParams: { owner: alice, @@ -383,7 +383,7 @@ async function tokenProxied( name: string, symbol: string, decimalsToSet: number, - rate: BigNumber, + tokenRate: BigNumber, isRebasable: boolean, owner: SignerWithAddress, deployer: SignerWithAddress, @@ -398,7 +398,7 @@ async function tokenProxied( decimalsToSet, owner.address ); - const tokenRateOracle = await new TokenRateOracle__factory(deployer).deploy( + const tokenRateOracleImpl = await new TokenRateOracle__factory(deployer).deploy( hre.ethers.constants.AddressZero, owner.address, hre.ethers.constants.AddressZero, @@ -406,6 +406,26 @@ async function tokenProxied( 86400, 500 ); + const provider = await hre.ethers.provider; + const blockNumber = await provider.getBlockNumber(); + const blockTimestamp = (await provider.getBlock(blockNumber)).timestamp; + + const tokenRateOracleProxy = await new OssifiableProxy__factory( + deployer + ).deploy( + tokenRateOracleImpl.address, + deployer.address, + tokenRateOracleImpl.interface.encodeFunctionData("initialize", [ + tokenRate, + blockTimestamp + ]) + ); + + const tokenRateOracle = TokenRateOracle__factory.connect( + tokenRateOracleProxy.address, + deployer + ); + const rebasableTokenImpl = await new ERC20RebasableBridgedPermit__factory(deployer).deploy( name, symbol, @@ -431,7 +451,6 @@ async function tokenProxied( holder ); - await tokenRateOracle.connect(owner).updateRate(rate, 1000); const premintShares = wei.toBigNumber(wei`100 ether`); await rebasableProxied.connect(owner).bridgeMintShares(holder.address, premintShares); diff --git a/test/token/ERC20RebasableBridgedPermit.unit.test.ts b/test/token/ERC20RebasableBridgedPermit.unit.test.ts index c6e719a7..7ac3aba3 100644 --- a/test/token/ERC20RebasableBridgedPermit.unit.test.ts +++ b/test/token/ERC20RebasableBridgedPermit.unit.test.ts @@ -10,6 +10,8 @@ import { OssifiableProxy__factory } from "../../typechain"; +import { tokenRateOracleUnderProxy } from "../../utils/testing/contractsFactory"; + unit("ERC20RebasableBridgedPermit", ctxFactory) .test("initial state", async (ctx) => { @@ -183,18 +185,18 @@ unit("ERC20RebasableBridgedPermit", ctxFactory) const { rebasableProxied, wrappedToken, tokenRateOracle } = ctx.contracts; const {user1, user2, owner, zero } = ctx.accounts; - const { rate, decimals, premintShares } = ctx.constants; + const { tokenRate, decimals, premintShares } = ctx.constants; - await tokenRateOracle.connect(owner).updateRate(rate, 1000); + await tokenRateOracle.connect(owner).updateRate(tokenRate, 1000); - const totalSupply = rate.mul(premintShares).div(decimals); + const totalSupply = tokenRate.mul(premintShares).div(decimals); assert.equalBN(await rebasableProxied.getTotalShares(), premintShares); assert.equalBN(await rebasableProxied.totalSupply(), totalSupply); // user1 const user1Shares = wei`100 ether`; - const user1Tokens = rate.mul(user1Shares).div(decimals); + const user1Tokens = tokenRate.mul(user1Shares).div(decimals); assert.equalBN(await rebasableProxied.sharesOf(user1.address), 0); assert.equalBN(await rebasableProxied.balanceOf(user1.address), 0); @@ -221,7 +223,7 @@ unit("ERC20RebasableBridgedPermit", ctxFactory) assert.equalBN(await rebasableProxied.balanceOf(user2.address), 0); const user2Shares = wei`50 ether`; - const user2Tokens = rate.mul(user2Shares).div(decimals); + const user2Tokens = tokenRate.mul(user2Shares).div(decimals); await wrappedToken.connect(owner).bridgeMint(user2.address, user2Tokens); await wrappedToken.connect(user2).approve(rebasableProxied.address, user2Shares); @@ -251,9 +253,9 @@ unit("ERC20RebasableBridgedPermit", ctxFactory) const { rebasableProxied, wrappedToken } = ctx.contracts; const {user1, user2, owner } = ctx.accounts; - const { rate, decimals, premintShares } = ctx.constants; + const { tokenRate, decimals, premintShares } = ctx.constants; - const totalSupply = BigNumber.from(rate).mul(premintShares).div(decimals); + const totalSupply = BigNumber.from(tokenRate).mul(premintShares).div(decimals); assert.equalBN(await rebasableProxied.getTotalShares(), premintShares); assert.equalBN(await rebasableProxied.totalSupply(), totalSupply); @@ -265,10 +267,10 @@ unit("ERC20RebasableBridgedPermit", ctxFactory) const user1SharesToWrap = wei`100 ether`; const user1SharesToUnwrap = wei`59 ether`; - const user1TokensToUnwrap = rate.mul(user1SharesToUnwrap).div(decimals); + const user1TokensToUnwrap = tokenRate.mul(user1SharesToUnwrap).div(decimals); const user1Shares = BigNumber.from(user1SharesToWrap).sub(user1SharesToUnwrap); - const user1Tokens = BigNumber.from(rate).mul(user1Shares).div(decimals); + const user1Tokens = BigNumber.from(tokenRate).mul(user1Shares).div(decimals); await wrappedToken.connect(owner).bridgeMint(user1.address, user1SharesToWrap); await wrappedToken.connect(user1).approve(rebasableProxied.address, user1SharesToWrap); @@ -288,10 +290,10 @@ unit("ERC20RebasableBridgedPermit", ctxFactory) // user2 const user2SharesToWrap = wei`145 ether`; const user2SharesToUnwrap = wei`14 ether`; - const user2TokensToUnwrap = rate.mul(user2SharesToUnwrap).div(decimals); + const user2TokensToUnwrap = tokenRate.mul(user2SharesToUnwrap).div(decimals); const user2Shares = BigNumber.from(user2SharesToWrap).sub(user2SharesToUnwrap); - const user2Tokens = BigNumber.from(rate).mul(user2Shares).div(decimals); + const user2Tokens = BigNumber.from(tokenRate).mul(user2Shares).div(decimals); assert.equalBN(await rebasableProxied.sharesOf(user2.address), 0); assert.equalBN(await rebasableProxied.balanceOf(user2.address), 0); @@ -360,14 +362,14 @@ unit("ERC20RebasableBridgedPermit", ctxFactory) const { rebasableProxied } = ctx.contracts; const {user1, user2, owner, zero } = ctx.accounts; - const { rate, decimals, premintShares, premintTokens } = ctx.constants; + const { tokenRate, decimals, premintShares, premintTokens } = ctx.constants; assert.equalBN(await rebasableProxied.getTotalShares(), premintShares); assert.equalBN(await rebasableProxied.totalSupply(), premintTokens); // user1 const user1SharesToMint = wei`44 ether`; - const user1TokensMinted = rate.mul(user1SharesToMint).div(decimals); + const user1TokensMinted = tokenRate.mul(user1SharesToMint).div(decimals); assert.equalBN(await rebasableProxied.sharesOf(user1.address), 0); assert.equalBN(await rebasableProxied.balanceOf(user1.address), 0); @@ -385,7 +387,7 @@ unit("ERC20RebasableBridgedPermit", ctxFactory) // // user2 const user2SharesToMint = wei`75 ether`; - const user2TokensMinted = rate.mul(user2SharesToMint).div(decimals); + const user2TokensMinted = tokenRate.mul(user2SharesToMint).div(decimals); assert.equalBN(await rebasableProxied.sharesOf(user2.address), 0); assert.equalBN(await rebasableProxied.balanceOf(user2.address), 0); @@ -406,17 +408,17 @@ unit("ERC20RebasableBridgedPermit", ctxFactory) const { rebasableProxied } = ctx.contracts; const {user1, user2, owner } = ctx.accounts; - const { rate, decimals, premintShares, premintTokens } = ctx.constants; + const { tokenRate, decimals, premintShares, premintTokens } = ctx.constants; assert.equalBN(await rebasableProxied.getTotalShares(), premintShares); assert.equalBN(await rebasableProxied.totalSupply(), premintTokens); // user1 const user1SharesToMint = wei`12 ether`; - const user1TokensMinted = rate.mul(user1SharesToMint).div(decimals); + const user1TokensMinted = tokenRate.mul(user1SharesToMint).div(decimals); const user1SharesToBurn = wei`4 ether`; - const user1TokensBurned = rate.mul(user1SharesToBurn).div(decimals); + const user1TokensBurned = tokenRate.mul(user1SharesToBurn).div(decimals); const user1Shares = BigNumber.from(user1SharesToMint).sub(user1SharesToBurn); const user1Tokens = user1TokensMinted.sub(user1TokensBurned); @@ -438,10 +440,10 @@ unit("ERC20RebasableBridgedPermit", ctxFactory) // // user2 const user2SharesToMint = wei`64 ether`; - const user2TokensMinted = rate.mul(user2SharesToMint).div(decimals); + const user2TokensMinted = tokenRate.mul(user2SharesToMint).div(decimals); const user2SharesToBurn = wei`22 ether`; - const user2TokensBurned = rate.mul(user2SharesToBurn).div(decimals); + const user2TokensBurned = tokenRate.mul(user2SharesToBurn).div(decimals); const user2Shares = BigNumber.from(user2SharesToMint).sub(user2SharesToBurn); const user2Tokens = user2TokensMinted.sub(user2TokensBurned); @@ -1133,9 +1135,10 @@ async function ctxFactory() { const version = "1"; const decimalsToSet = 18; const decimals = BigNumber.from(10).pow(decimalsToSet); - const rate = BigNumber.from('12').pow(decimalsToSet - 1); + const tokenRate = BigNumber.from('1164454276599657236'); + const premintShares = wei.toBigNumber(wei`100 ether`); - const premintTokens = BigNumber.from(rate).mul(premintShares).div(decimals); + const premintTokens = BigNumber.from(tokenRate).mul(premintShares).div(decimals); const provider = await hre.ethers.provider; const blockNumber = await provider.getBlockNumber(); @@ -1160,14 +1163,19 @@ async function ctxFactory() { decimalsToSet, owner.address ); - const tokenRateOracle = await new TokenRateOracle__factory(deployer).deploy( + + const tokenRateOracle = await tokenRateOracleUnderProxy( + deployer, zero.address, owner.address, zero.address, - 86400, - 86400, - 500 - ); + BigNumber.from('86400'), + BigNumber.from('86400'), + BigNumber.from('500'), + tokenRate, + BigNumber.from(blockTimestamp) + ) + const rebasableTokenImpl = await new ERC20RebasableBridgedPermit__factory(deployer).deploy( name, symbol, @@ -1178,11 +1186,6 @@ async function ctxFactory() { owner.address ); - await hre.network.provider.request({ - method: "hardhat_impersonateAccount", - params: [hre.ethers.constants.AddressZero], - }); - const l2TokensProxy = await new OssifiableProxy__factory(deployer).deploy( rebasableTokenImpl.address, deployer.address, @@ -1198,12 +1201,16 @@ async function ctxFactory() { holder ); - await tokenRateOracle.connect(owner).updateRate(rate, blockTimestamp - 1000); await rebasableProxied.connect(owner).bridgeMintShares(holder.address, premintShares); + await hre.network.provider.request({ + method: "hardhat_impersonateAccount", + params: [hre.ethers.constants.AddressZero], + }); + return { accounts: { deployer, owner, recipient, spender, holder, stranger, zero, user1, user2 }, - constants: { name, symbol, version, decimalsToSet, decimals, premintShares, premintTokens, rate, blockTimestamp }, + constants: { name, symbol, version, decimalsToSet, decimals, premintShares, premintTokens, tokenRate, blockTimestamp }, contracts: { rebasableProxied, wrappedToken, tokenRateOracle } }; } diff --git a/utils/optimism/deploymentAllFromScratch.ts b/utils/optimism/deploymentAllFromScratch.ts index d8de76e8..26a22170 100644 --- a/utils/optimism/deploymentAllFromScratch.ts +++ b/utils/optimism/deploymentAllFromScratch.ts @@ -1,5 +1,5 @@ import { assert } from "chai"; -import { Wallet } from "ethers"; +import { BigNumber, Wallet } from "ethers"; import addresses from "./addresses"; import { OptDeploymentOptions, DeployScriptParams } from "./types"; import network, { NetworkName } from "../network"; @@ -29,6 +29,10 @@ interface OptL2DeployScriptParams extends DeployScriptParams { symbol?: string; version?: string; }; + tokenRateOracle: { + tokenRate: BigNumber; + l1Timestamp: BigNumber; + } } export class L1DeployAllScript extends DeployScript { @@ -303,6 +307,8 @@ export default function deploymentAll( expectedL2TokenBridgeProxyAddress, expectedL1OpStackTokenRatePusherImplAddress, 86400, + 86400, + 500, options?.overrides, ], afterDeploy: (c) => @@ -313,7 +319,13 @@ export default function deploymentAll( args: [ expectedL2TokenRateOracleImplAddress, l2Params.admins.proxy, - [], + TokenRateOracle__factory.createInterface().encodeFunctionData( + "initialize", + [ + l2Params.tokenRateOracle.tokenRate, + l2Params.tokenRateOracle.l1Timestamp + ] + ), options?.overrides, ], afterDeploy: (c) => diff --git a/utils/optimism/deploymentBridgesAndRebasableToken.ts b/utils/optimism/deploymentBridgesAndRebasableToken.ts index 7526ebf1..6d9d9dc0 100644 --- a/utils/optimism/deploymentBridgesAndRebasableToken.ts +++ b/utils/optimism/deploymentBridgesAndRebasableToken.ts @@ -156,6 +156,7 @@ export default function deployment( l2Params.l2TokenRebasable?.name ?? l1TokenRebasableInfo.name(), l2Params.l2TokenRebasable?.symbol ?? l1TokenRebasableInfo.symbol(), ]); + const l2TokenVersion = "1"; const l2DeployScript = new BridgeL2DeployScript( l2Params.deployer, @@ -172,6 +173,7 @@ export default function deployment( args: [ l2TokenName, l2TokenSymbol, + l2TokenVersion, decimals, expectedL2TokenBridgeProxyAddress, options?.overrides, @@ -185,8 +187,8 @@ export default function deployment( expectedL2TokenImplAddress, l2Params.admins.proxy, ERC20BridgedPermit__factory.createInterface().encodeFunctionData( - "initializeERC20Metadata", - [l2TokenName, l2TokenSymbol] + "initialize", + [l2TokenName, l2TokenSymbol, l2TokenVersion] ), options?.overrides, ], diff --git a/utils/optimism/deploymentNewImplementations.ts b/utils/optimism/deploymentNewImplementations.ts index c29bb8ab..6ffa9e18 100644 --- a/utils/optimism/deploymentNewImplementations.ts +++ b/utils/optimism/deploymentNewImplementations.ts @@ -1,5 +1,5 @@ import { assert } from "chai"; -import { Wallet } from "ethers"; +import { BigNumber, Wallet } from "ethers"; import addresses from "./addresses"; import { OptDeploymentOptions, DeployScriptParams } from "./types"; import network, { NetworkName } from "../network"; @@ -30,15 +30,21 @@ interface OptL1DeployScriptParams extends DeployScriptParams { interface OptL2DeployScriptParams extends DeployScriptParams { tokenBridgeProxyAddress: string; tokenProxyAddress: string; - tokenRateOracleProxyAddress: string; - tokenRateOracleRateOutdatedDelay: number; - token?: { - name?: string; - symbol?: string + tokenRateOracle: { + proxyAddress: string; + rateOutdatedDelay: BigNumber; + maxAllowedL2ToL1ClockLag: BigNumber; + maxAllowedTokenRateDeviationPerDay: BigNumber; + } + token: { + name: string; + symbol: string; + version: string; }; - tokenRebasable?: { - name?: string; - symbol?: string + tokenRebasable: { + name: string; + symbol: string; + version: string; }; } @@ -161,6 +167,7 @@ export default function deploymentNewImplementations( args: [ l2TokenName, l2TokenSymbol, + l2Params.token.version, decimals, l2Params.tokenBridgeProxyAddress, options?.overrides, @@ -173,9 +180,10 @@ export default function deploymentNewImplementations( args: [ l2TokenRebasableName, l2TokenRebasableSymbol, + l2Params.tokenRebasable.version, decimals, l2Params.tokenProxyAddress, - l2Params.tokenRateOracleProxyAddress, + l2Params.tokenRateOracle.proxyAddress, l2Params.tokenBridgeProxyAddress, options?.overrides, ], @@ -188,8 +196,8 @@ export default function deploymentNewImplementations( expectedL2TokenRebasableImplAddress, l2Params.admins.proxy, ERC20RebasableBridgedPermit__factory.createInterface().encodeFunctionData( - "initializeERC20Metadata", - [l2TokenRebasableName, l2TokenRebasableSymbol] + "initialize", + [l2TokenRebasableName, l2TokenRebasableSymbol, l2Params.tokenRebasable.version] ), options?.overrides, ], @@ -216,7 +224,9 @@ export default function deploymentNewImplementations( optAddresses.L2CrossDomainMessenger, l2Params.tokenBridgeProxyAddress, l1Params.opStackTokenRatePusherImplAddress, - l2Params.tokenRateOracleRateOutdatedDelay, + l2Params.tokenRateOracle.rateOutdatedDelay, + l2Params.tokenRateOracle.maxAllowedL2ToL1ClockLag, + l2Params.tokenRateOracle.maxAllowedTokenRateDeviationPerDay, options?.overrides, ], afterDeploy: (c) => diff --git a/utils/optimism/deploymentOracle.ts b/utils/optimism/deploymentOracle.ts index 5be8d466..432133c3 100644 --- a/utils/optimism/deploymentOracle.ts +++ b/utils/optimism/deploymentOracle.ts @@ -1,5 +1,5 @@ import { assert } from "chai"; -import { Wallet } from "ethers"; +import { BigNumber, Wallet } from "ethers"; import { ethers } from "hardhat"; import addresses from "./addresses"; import { DeployScriptParams, OptDeploymentOptions } from "./types"; @@ -13,6 +13,16 @@ import { } from "../../typechain"; interface OptDeployScriptParams extends DeployScriptParams {} + +interface OptL2DeployScriptParams extends DeployScriptParams { + tokenRateOracle: { + maxAllowedL2ToL1ClockLag: BigNumber; + maxAllowedTokenRateDeviationPerDay: BigNumber; + tokenRate: BigNumber; + l1Timestamp: BigNumber; + } +} + export class OracleL1DeployScript extends DeployScript { constructor( deployer: Wallet, @@ -56,7 +66,7 @@ export default function deploymentOracle( l2GasLimitForPushingTokenRate: number, tokenRateOutdatedDelay: number, l1Params: OptDeployScriptParams, - l2Params: OptDeployScriptParams, + l2Params: OptL2DeployScriptParams, ): Promise<[OracleL1DeployScript, OracleL2DeployScript]> { const [ @@ -110,6 +120,8 @@ export default function deploymentOracle( ethers.constants.AddressZero, expectedL1OpStackTokenRatePusherImplAddress, tokenRateOutdatedDelay, + l2Params.tokenRateOracle.maxAllowedL2ToL1ClockLag, + l2Params.tokenRateOracle.maxAllowedTokenRateDeviationPerDay, options?.overrides, ], afterDeploy: (c) => @@ -120,7 +132,13 @@ export default function deploymentOracle( args: [ expectedL2TokenRateOracleImplAddress, l2Params.admins.proxy, - [], + TokenRateOracle__factory.createInterface().encodeFunctionData( + "initialize", + [ + l2Params.tokenRateOracle.tokenRate, + l2Params.tokenRateOracle.l1Timestamp + ] + ), options?.overrides, ], afterDeploy: (c) => diff --git a/utils/optimism/testing.ts b/utils/optimism/testing.ts index d2a0973b..618ad76c 100644 --- a/utils/optimism/testing.ts +++ b/utils/optimism/testing.ts @@ -1,6 +1,6 @@ import { Signer } from "ethers"; import { JsonRpcProvider } from "@ethersproject/providers"; - +import { BigNumber } from "ethers"; import { IERC20, ERC20Bridged, @@ -194,7 +194,8 @@ async function deployTestBridge( const l1Token = await new ERC20WrapperStub__factory(ethDeployer).deploy( l1TokenRebasable.address, "Test Token", - "TT" + "TT", + BigNumber.from('1164454276599657236') ); const [ethDeployScript, optDeployScript] = await deploymentAll( @@ -210,7 +211,11 @@ async function deployTestBridge( { deployer: optDeployer, admins: { proxy: optDeployer.address, bridge: optDeployer.address }, - contractsShift: 0 + contractsShift: 0, + tokenRateOracle: { + tokenRate: BigNumber.from('1164454276599657236'), + l1Timestamp: BigNumber.from('1000') + } } ); diff --git a/utils/testing/contractsFactory.ts b/utils/testing/contractsFactory.ts new file mode 100644 index 00000000..26070252 --- /dev/null +++ b/utils/testing/contractsFactory.ts @@ -0,0 +1,150 @@ +import { BigNumber } from "ethers"; +import { SignerWithAddress } from "@nomiclabs/hardhat-ethers/signers"; +import { + ERC20BridgedPermit__factory, + TokenRateOracle__factory, + ERC20RebasableBridgedPermit__factory, + OssifiableProxy__factory +} from "../../typechain"; + +export async function erc20BridgedPermitUnderProxy( + deployer: SignerWithAddress, + holder: SignerWithAddress, + name: string, + symbol: string, + version: string, + decimals: BigNumber, + bridge: string +) { + const erc20BridgedPermitImpl = await new ERC20BridgedPermit__factory(deployer).deploy( + name, + symbol, + version, + decimals, + bridge + ); + + const erc20BridgedPermitProxy = await new OssifiableProxy__factory(deployer).deploy( + erc20BridgedPermitImpl.address, + deployer.address, + ERC20BridgedPermit__factory.createInterface().encodeFunctionData("initialize", [ + name, + symbol, + version + ]) + ); + + const erc20BridgedPermit = ERC20BridgedPermit__factory.connect( + erc20BridgedPermitProxy.address, + holder + ); + + return erc20BridgedPermit; +} + +export async function tokenRateOracleUnderProxy( + deployer: SignerWithAddress, + messenger: string, + l2ERC20TokenBridge: string, + l1TokenRatePusher: string, + tokenRateOutdatedDelay: BigNumber, + maxAllowedL2ToL1ClockLag: BigNumber, + maxAllowedTokenRateDeviationPerDay: BigNumber, + tokenRate: BigNumber, + blockTimestamp: BigNumber +) { + const tokenRateOracleImpl = await new TokenRateOracle__factory(deployer).deploy( + messenger, + l2ERC20TokenBridge, + l1TokenRatePusher, + tokenRateOutdatedDelay, + maxAllowedL2ToL1ClockLag, + maxAllowedTokenRateDeviationPerDay + ); + const tokenRateOracleProxy = await new OssifiableProxy__factory( + deployer + ).deploy( + tokenRateOracleImpl.address, + deployer.address, + tokenRateOracleImpl.interface.encodeFunctionData("initialize", [ + tokenRate, + blockTimestamp + ]) + ); + const tokenRateOracle = TokenRateOracle__factory.connect( + tokenRateOracleProxy.address, + deployer + ); + return tokenRateOracle; +} + +export async function erc20RebasableBridgedPermitUnderProxy( + deployer: SignerWithAddress, + + erc20BridgedPermitName: string, + erc20BridgedPermitSymbol: string, + erc20BridgedPermitVersion: string, + + erc20RebasableBridgedPermitName: string, + erc20RebasableBridgedPermitSymbol: string, + erc20RebasableBridgedPermitVersion: string, + + decimals: BigNumber, + bridge: string, + + messenger: string, + l1TokenRatePusher: string, + tokenRateOutdatedDelay: BigNumber, + maxAllowedL2ToL1ClockLag: BigNumber, + maxAllowedTokenRateDeviationPerDay: BigNumber, + tokenRate: BigNumber, + blockTimestamp: BigNumber +) { + const erc20BridgedPermit = await erc20BridgedPermitUnderProxy( + deployer, + erc20BridgedPermitName, + erc20BridgedPermitSymbol, + erc20BridgedPermitVersion, + decimals, + bridge + ); + + const tokenRateOracle = await tokenRateOracleUnderProxy( + deployer, + messenger, + bridge, + l1TokenRatePusher, + tokenRateOutdatedDelay, + maxAllowedL2ToL1ClockLag, + maxAllowedTokenRateDeviationPerDay, + tokenRate, + BigNumber.from(blockTimestamp) + ) + + const erc20RebasableBridgedPermitImpl = await new ERC20RebasableBridgedPermit__factory(deployer).deploy( + erc20RebasableBridgedPermitName, + erc20RebasableBridgedPermitSymbol, + erc20RebasableBridgedPermitVersion, + decimals, + erc20BridgedPermit.address, + tokenRateOracle.address, + bridge + ); + + const erc20RebasableBridgedPermitProxy = await new OssifiableProxy__factory(deployer).deploy( + erc20RebasableBridgedPermitImpl.address, + deployer.address, + ERC20RebasableBridgedPermit__factory.createInterface().encodeFunctionData("initialize", [ + erc20RebasableBridgedPermitName, + erc20RebasableBridgedPermitSymbol, + erc20RebasableBridgedPermitVersion + ]) + ); + + const erc20RebasableBridgedPermit = ERC20RebasableBridgedPermit__factory.connect( + erc20RebasableBridgedPermitProxy.address, + deployer + ); + + return { tokenRateOracle, erc20BridgedPermit, erc20RebasableBridgedPermit }; +} diff --git a/utils/testing/index.ts b/utils/testing/index.ts index 3df84575..6033d5cc 100644 --- a/utils/testing/index.ts +++ b/utils/testing/index.ts @@ -11,5 +11,5 @@ export default { env, accounts, impersonate, - setBalance, + setBalance }; From 8518ca123e5a3ed541fd7d101a0f2cb0fa602b02 Mon Sep 17 00:00:00 2001 From: TheDZhon Date: Tue, 23 Apr 2024 16:58:32 +0000 Subject: [PATCH 089/148] fix: Make storage-layout up to date --- .storage-layout | 40 ++++++++++++++++------------------------ 1 file changed, 16 insertions(+), 24 deletions(-) diff --git a/.storage-layout b/.storage-layout index e5a497a7..50deee78 100644 --- a/.storage-layout +++ b/.storage-layout @@ -1,13 +1,6 @@ 👁👁 STORAGE LAYOUT snapshot 👁👁 ======================= -======================= -➡ BridgeableTokens -======================= - -| Name | Type | Slot | Offset | Bytes | Contract | -|------|------|------|--------|-------|----------| - ======================= ➡ BridgingManager ======================= @@ -37,14 +30,11 @@ ➡ ERC20BridgedPermit ======================= -| Name | Type | Slot | Offset | Bytes | Contract | -|------------------|-------------------------------------------------|------|--------|-------|-----------------------------------------------------------| -| totalSupply | uint256 | 0 | 0 | 32 | contracts/token/ERC20BridgedPermit.sol:ERC20BridgedPermit | -| balanceOf | mapping(address => uint256) | 1 | 0 | 32 | contracts/token/ERC20BridgedPermit.sol:ERC20BridgedPermit | -| allowance | mapping(address => mapping(address => uint256)) | 2 | 0 | 32 | contracts/token/ERC20BridgedPermit.sol:ERC20BridgedPermit | -| _nameFallback | string | 3 | 0 | 32 | contracts/token/ERC20BridgedPermit.sol:ERC20BridgedPermit | -| _versionFallback | string | 4 | 0 | 32 | contracts/token/ERC20BridgedPermit.sol:ERC20BridgedPermit | -| noncesByAddress | mapping(address => uint256) | 5 | 0 | 32 | contracts/token/ERC20BridgedPermit.sol:ERC20BridgedPermit | +| Name | Type | Slot | Offset | Bytes | Contract | +|-------------|-------------------------------------------------|------|--------|-------|-----------------------------------------------------------| +| totalSupply | uint256 | 0 | 0 | 32 | contracts/token/ERC20BridgedPermit.sol:ERC20BridgedPermit | +| balanceOf | mapping(address => uint256) | 1 | 0 | 32 | contracts/token/ERC20BridgedPermit.sol:ERC20BridgedPermit | +| allowance | mapping(address => mapping(address => uint256)) | 2 | 0 | 32 | contracts/token/ERC20BridgedPermit.sol:ERC20BridgedPermit | ======================= ➡ ERC20Core @@ -74,11 +64,8 @@ ➡ ERC20RebasableBridgedPermit ======================= -| Name | Type | Slot | Offset | Bytes | Contract | -|------------------|-----------------------------|------|--------|-------|-----------------------------------------------------------------------------| -| _nameFallback | string | 0 | 0 | 32 | contracts/token/ERC20RebasableBridgedPermit.sol:ERC20RebasableBridgedPermit | -| _versionFallback | string | 1 | 0 | 32 | contracts/token/ERC20RebasableBridgedPermit.sol:ERC20RebasableBridgedPermit | -| noncesByAddress | mapping(address => uint256) | 2 | 0 | 32 | contracts/token/ERC20RebasableBridgedPermit.sol:ERC20RebasableBridgedPermit | +| Name | Type | Slot | Offset | Bytes | Contract | +|------|------|------|--------|-------|----------| ======================= ➡ L1LidoTokensBridge @@ -130,7 +117,12 @@ ➡ TokenRateOracle ======================= -| Name | Type | Slot | Offset | Bytes | Contract | -|-----------------|---------|------|--------|-------|--------------------------------------------------------| -| tokenRate | uint256 | 0 | 0 | 32 | contracts/optimism/TokenRateOracle.sol:TokenRateOracle | -| rateL1Timestamp | uint256 | 1 | 0 | 32 | contracts/optimism/TokenRateOracle.sol:TokenRateOracle | +| Name | Type | Slot | Offset | Bytes | Contract | +|------|------|------|--------|-------|----------| + +======================= +➡ Versioned +======================= + +| Name | Type | Slot | Offset | Bytes | Contract | +|------|------|------|--------|-------|----------| From c3e8b9e42725d01c0d6b78a182c21e534db3957c Mon Sep 17 00:00:00 2001 From: Anton Kovalchuk Date: Tue, 23 Apr 2024 23:55:47 +0400 Subject: [PATCH 090/148] small fixes and init tests --- contracts/BridgingManager.sol | 7 +- contracts/lib/ECDSA.sol | 5 +- contracts/optimism/L1LidoTokensBridge.sol | 4 +- .../optimism/L2ERC20ExtendedTokensBridge.sol | 4 +- contracts/optimism/TokenRateOracle.sol | 29 +- contracts/stubs/BridgingManagerStub.sol | 2 +- .../stubs/ERC20BridgedWithInitializerStub.sol | 20 + contracts/token/ERC20BridgedPermit.sol | 4 +- .../BridgingManager.unit.test.ts | 3 +- test/optimism/L1LidoTokensBridge.unit.test.ts | 2192 ++++++++------- .../L2ERC20ExtendedTokensBridge.unit.test.ts | 2390 +++++++++-------- .../OpStackTokenRatePusher.unit.test.ts | 94 +- test/optimism/TokenRateNotifier.unit.test.ts | 542 ++-- test/optimism/TokenRateOracle.unit.test.ts | 525 ++-- .../bridging-rebasable.integration.test.ts | 928 +++---- test/optimism/bridging.integration.test.ts | 20 +- test/optimism/deposit-gas-estimation.test.ts | 40 +- test/optimism/managing-proxy.e2e.test.ts | 6 +- test/optimism/pushingTokenRate.e2e.test.ts | 134 +- .../pushingTokenRate.integration.test.ts | 420 +-- test/token/ERC20BridgedPermit.unit.test.ts | 100 +- test/token/ERC20Permit.unit.test.ts | 927 ++++--- .../ERC20RebasableBridgedPermit.unit.test.ts | 405 ++- utils/optimism/deploymentAllFromScratch.ts | 596 ++-- .../deploymentBridgesAndRebasableToken.ts | 86 +- .../optimism/deploymentNewImplementations.ts | 414 +-- utils/optimism/deploymentOracle.ts | 242 +- utils/testing/contractsFactory.ts | 91 +- 28 files changed, 5281 insertions(+), 4949 deletions(-) create mode 100644 contracts/stubs/ERC20BridgedWithInitializerStub.sol diff --git a/contracts/BridgingManager.sol b/contracts/BridgingManager.sol index 4a2cffd4..c302ae1d 100644 --- a/contracts/BridgingManager.sol +++ b/contracts/BridgingManager.sol @@ -13,7 +13,8 @@ contract BridgingManager is AccessControl { /// @param isDepositsEnabled Stores the state of the deposits /// @param isWithdrawalsEnabled Stores the state of the withdrawals struct State { - bool isInitialized; /// @dev DEPRECATED since bridges have their own code for versioning. + /// @dev DEPRECATED since v2 as bridges have their own code for initialization and storage versioning. + bool isInitialized; bool isDepositsEnabled; bool isWithdrawalsEnabled; } @@ -34,7 +35,7 @@ contract BridgingManager is AccessControl { /// @notice Initializes the contract to grant DEFAULT_ADMIN_ROLE to the admin_ address /// @dev This method might be called only once /// @param admin_ Address of the account to grant the DEFAULT_ADMIN_ROLE - function _initialize(address admin_) internal { + function _initializeBridgingManager(address admin_) internal { State storage s = _loadState(); if (s.isInitialized) { revert ErrorAlreadyInitialized(); @@ -137,5 +138,5 @@ contract BridgingManager is AccessControl { error ErrorWithdrawalsEnabled(); error ErrorWithdrawalsDisabled(); error ErrorAlreadyInitialized(); - error ErrorBridgingManagerWasInitialized(); + error ErrorBridgingManagerIsNotInitialized(); } diff --git a/contracts/lib/ECDSA.sol b/contracts/lib/ECDSA.sol index e3ce787b..98598c78 100644 --- a/contracts/lib/ECDSA.sol +++ b/contracts/lib/ECDSA.sol @@ -1,8 +1,9 @@ // SPDX-FileCopyrightText: 2024 Lido // SPDX-License-Identifier: MIT -// Extracted from: -// https://github.com/OpenZeppelin/openzeppelin-contracts/blob/v3.4.0/contracts/cryptography/ECDSA.sol#L53 +/// @dev Extracted from: +/// https://github.com/OpenZeppelin/openzeppelin-contracts/blob/v3.4.0/contracts/cryptography/ECDSA.sol#L53 +/// Also it is used in Lido Core Protocol. pragma solidity 0.8.10; diff --git a/contracts/optimism/L1LidoTokensBridge.sol b/contracts/optimism/L1LidoTokensBridge.sol index 2f975994..3023be37 100644 --- a/contracts/optimism/L1LidoTokensBridge.sol +++ b/contracts/optimism/L1LidoTokensBridge.sol @@ -45,13 +45,13 @@ contract L1LidoTokensBridge is L1ERC20ExtendedTokensBridge, Versioned { /// @param admin_ Address of the account to grant the DEFAULT_ADMIN_ROLE function initialize(address admin_) external { _initializeContractVersionTo(2); - _initialize(admin_); + _initializeBridgingManager(admin_); } /// @notice A function to finalize upgrade to v2 (from v1). function finalizeUpgrade_v2() external { if(!_isBridgingManagerInitialized()) { - revert ErrorBridgingManagerWasInitialized(); + revert ErrorBridgingManagerIsNotInitialized(); } _initializeContractVersionTo(2); } diff --git a/contracts/optimism/L2ERC20ExtendedTokensBridge.sol b/contracts/optimism/L2ERC20ExtendedTokensBridge.sol index 23542a70..1b41f6aa 100644 --- a/contracts/optimism/L2ERC20ExtendedTokensBridge.sol +++ b/contracts/optimism/L2ERC20ExtendedTokensBridge.sol @@ -61,13 +61,13 @@ contract L2ERC20ExtendedTokensBridge is /// @param admin_ Address of the account to grant the DEFAULT_ADMIN_ROLE function initialize(address admin_) external { _initializeContractVersionTo(2); - _initialize(admin_); + _initializeBridgingManager(admin_); } /// @notice A function to finalize upgrade to v2 (from v1). function finalizeUpgrade_v2() external { if(!_isBridgingManagerInitialized()) { - revert ErrorBridgingManagerWasInitialized(); + revert ErrorBridgingManagerIsNotInitialized(); } _initializeContractVersionTo(2); } diff --git a/contracts/optimism/TokenRateOracle.sol b/contracts/optimism/TokenRateOracle.sol index a71afc3d..b4de7dba 100644 --- a/contracts/optimism/TokenRateOracle.sol +++ b/contracts/optimism/TokenRateOracle.sol @@ -112,15 +112,14 @@ contract TokenRateOracle is CrossDomainEnabled, ITokenRateOracle, Versioned { /// @inheritdoc ITokenRateUpdatable function updateRate(uint256 tokenRate_, uint256 rateL1Timestamp_) external onlyBridgeOrTokenRatePusher { - /// @dev checks if the time difference between L1 and L2 exceeds the configurable threshold - if (rateL1Timestamp_ > block.timestamp && - rateL1Timestamp_ - block.timestamp > MAX_ALLOWED_L2_TO_L1_CLOCK_LAG) { + /// @dev checks if the clock lag (i.e, time difference) between L1 and L2 exceeds the configurable threshold + if (rateL1Timestamp_ > block.timestamp + MAX_ALLOWED_L2_TO_L1_CLOCK_LAG) { revert ErrorL1TimestampExceededAllowedClockLag(tokenRate_, rateL1Timestamp_); } /// @dev use only the more actual token rate if (rateL1Timestamp_ <= _getRateL1Timestamp()) { - emit DormantTokenRateUpdateIgnored(tokenRate_, _getRateL1Timestamp(), rateL1Timestamp_); + emit DormantTokenRateUpdateIgnored(tokenRate_, rateL1Timestamp_, _getRateL1Timestamp()); return; } @@ -131,7 +130,7 @@ contract TokenRateOracle is CrossDomainEnabled, ITokenRateOracle, Versioned { /// @dev notify that there is a differnce L1 and L2 time. if (rateL1Timestamp_ > block.timestamp) { - emit TokenRateL1TimestampAheadOfL2Time(tokenRate_, rateL1Timestamp_); + emit TokenRateL1TimestampIsInFuture(tokenRate_, rateL1Timestamp_); } _setTokenRateAndL1Timestamp(uint192(tokenRate_), uint64(rateL1Timestamp_)); @@ -140,11 +139,11 @@ contract TokenRateOracle is CrossDomainEnabled, ITokenRateOracle, Versioned { /// @notice Returns flag that shows that token rate can be considered outdated. function isLikelyOutdated() external view returns (bool) { - return block.timestamp - _getRateL1Timestamp() > TOKEN_RATE_OUTDATED_DELAY; + return block.timestamp > _getRateL1Timestamp() + TOKEN_RATE_OUTDATED_DELAY; } /// @dev Allow tokenRate deviation from the previous value to be - /// ±`MAX_ALLOWED_TOKEN_RATE_DEVIATION_PER_DAY`% per day. + /// ±`MAX_ALLOWED_TOKEN_RATE_DEVIATION_PER_DAY` BP per day. function _isTokenRateWithinAllowedRange( uint256 newTokenRate_, uint256 newRateL1Timestamp_ ) internal view returns (bool) { @@ -173,6 +172,11 @@ contract TokenRateOracle is CrossDomainEnabled, ITokenRateOracle, Versioned { return false; } + function _setTokenRateAndL1Timestamp(uint192 tokenRate_, uint64 rateL1Timestamp_) internal { + _loadTokenRateData().tokenRate = tokenRate_; + _loadTokenRateData().rateL1Timestamp = rateL1Timestamp_; + } + function _getTokenRate() private view returns (uint192) { return _loadTokenRateData().tokenRate; } @@ -181,11 +185,6 @@ contract TokenRateOracle is CrossDomainEnabled, ITokenRateOracle, Versioned { return _loadTokenRateData().rateL1Timestamp; } - function _setTokenRateAndL1Timestamp(uint192 tokenRate_, uint64 rateL1Timestamp_) internal { - _loadTokenRateData().tokenRate = tokenRate_; - _loadTokenRateData().rateL1Timestamp = rateL1Timestamp_; - } - /// @dev Returns the reference to the slot with TokenRateData struct function _loadTokenRateData() private @@ -211,10 +210,10 @@ contract TokenRateOracle is CrossDomainEnabled, ITokenRateOracle, Versioned { ); event DormantTokenRateUpdateIgnored( uint256 tokenRate_, - uint256 indexed currentRateL1Timestamp_, - uint256 indexed newRateL1Timestamp_ + uint256 indexed newRateL1Timestamp_, + uint256 indexed currentRateL1Timestamp_ ); - event TokenRateL1TimestampAheadOfL2Time( + event TokenRateL1TimestampIsInFuture( uint256 tokenRate_, uint256 indexed rateL1Timestamp_ ); diff --git a/contracts/stubs/BridgingManagerStub.sol b/contracts/stubs/BridgingManagerStub.sol index 3b02242c..4e534317 100644 --- a/contracts/stubs/BridgingManagerStub.sol +++ b/contracts/stubs/BridgingManagerStub.sol @@ -8,6 +8,6 @@ pragma solidity 0.8.10; /// @dev For testing purposes. contract BridgingManagerStub is BridgingManager { function initialize(address admin_) external { - _initialize(admin_); + _initializeBridgingManager(admin_); } } diff --git a/contracts/stubs/ERC20BridgedWithInitializerStub.sol b/contracts/stubs/ERC20BridgedWithInitializerStub.sol new file mode 100644 index 00000000..c4f79773 --- /dev/null +++ b/contracts/stubs/ERC20BridgedWithInitializerStub.sol @@ -0,0 +1,20 @@ +// SPDX-FileCopyrightText: 2024 Lido +// SPDX-License-Identifier: GPL-3.0 + +pragma solidity 0.8.10; + +import {ERC20Bridged} from "../token/ERC20Bridged.sol"; + +/// @dev For testing purposes. +contract ERC20BridgedWithInitializerStub is ERC20Bridged { + constructor( + string memory name_, + string memory symbol_, + uint8 decimals_, + address bridge_ + ) ERC20Bridged(name_, symbol_, decimals_, bridge_) {} + + function initializeERC20Metadata(string memory name_, string memory symbol_) external { + _initializeERC20Metadata(name_, symbol_); + } +} diff --git a/contracts/token/ERC20BridgedPermit.sol b/contracts/token/ERC20BridgedPermit.sol index b4318164..161d8d52 100644 --- a/contracts/token/ERC20BridgedPermit.sol +++ b/contracts/token/ERC20BridgedPermit.sol @@ -39,7 +39,7 @@ contract ERC20BridgedPermit is ERC20Bridged, PermitExtension, Versioned { /// @notice A function to finalize upgrade to v2 (from v1). function finalizeUpgrade_v2(string memory name_, string memory version_) external { if (!_isMetadataInitialized()) { - revert ErrorMetadataNotInitialized(); + revert ErrorMetadataIsNotInitialized(); } _initialize_v2(name_, version_); } @@ -54,5 +54,5 @@ contract ERC20BridgedPermit is ERC20Bridged, PermitExtension, Versioned { _approve(owner_, spender_, amount_); } - error ErrorMetadataNotInitialized(); + error ErrorMetadataIsNotInitialized(); } diff --git a/test/bridging-manager/BridgingManager.unit.test.ts b/test/bridging-manager/BridgingManager.unit.test.ts index d2afc53d..5584b50f 100644 --- a/test/bridging-manager/BridgingManager.unit.test.ts +++ b/test/bridging-manager/BridgingManager.unit.test.ts @@ -301,8 +301,7 @@ async function ctxFactory() { depositsEnabler, depositsDisabler, withdrawalsEnabler, - withdrawalsDisabler, - l2TokenBridgeEOA + withdrawalsDisabler ] = await hre.ethers.getSigners(); const bridgingManagerImpl = await new BridgingManagerStub__factory( diff --git a/test/optimism/L1LidoTokensBridge.unit.test.ts b/test/optimism/L1LidoTokensBridge.unit.test.ts index e95e8491..8871e68c 100644 --- a/test/optimism/L1LidoTokensBridge.unit.test.ts +++ b/test/optimism/L1LidoTokensBridge.unit.test.ts @@ -2,1083 +2,1211 @@ import { assert } from "chai"; import hre, { ethers } from "hardhat"; import { BigNumber } from "ethers"; import { - ERC20BridgedStub__factory, - ERC20WrapperStub__factory, - L1LidoTokensBridge__factory, - L2ERC20ExtendedTokensBridge__factory, - OssifiableProxy__factory, - EmptyContractStub__factory, - ERC20WrapperStub + ERC20BridgedStub__factory, + ERC20WrapperStub__factory, + L1LidoTokensBridge__factory, + BridgingManagerStub__factory, + L2ERC20ExtendedTokensBridge__factory, + OssifiableProxy__factory, + EmptyContractStub__factory, + ERC20WrapperStub } from "../../typechain"; import { JsonRpcProvider } from "@ethersproject/providers"; import { CrossDomainMessengerStub__factory } from "../../typechain/factories/CrossDomainMessengerStub__factory"; import testing, { unit } from "../../utils/testing"; import { wei } from "../../utils/wei"; +import { SignerWithAddress } from "@nomiclabs/hardhat-ethers/signers"; unit("Optimism :: L1LidoTokensBridge", ctxFactory) - .test("initial state", async (ctx) => { - assert.equal(await ctx.l1TokenBridge.l2TokenBridge(), ctx.accounts.l2TokenBridgeEOA.address); - assert.equal(await ctx.l1TokenBridge.MESSENGER(), ctx.accounts.l1MessengerStubAsEOA._address); - assert.equal(await ctx.l1TokenBridge.L1_TOKEN_NON_REBASABLE(), ctx.stubs.l1TokenNonRebasable.address); - assert.equal(await ctx.l1TokenBridge.L1_TOKEN_REBASABLE(), ctx.stubs.l1TokenRebasable.address); - assert.equal(await ctx.l1TokenBridge.L2_TOKEN_NON_REBASABLE(), ctx.stubs.l2TokenNonRebasable.address); - assert.equal(await ctx.l1TokenBridge.L2_TOKEN_REBASABLE(), ctx.stubs.l2TokenRebasable.address); - }) - - .test("depositERC20() :: deposits disabled", async (ctx) => { - await ctx.l1TokenBridge.disableDeposits(); - - assert.isFalse(await ctx.l1TokenBridge.isDepositsEnabled()); - - await assert.revertsWith( - ctx.l1TokenBridge.depositERC20( - ctx.stubs.l1TokenNonRebasable.address, - ctx.stubs.l2TokenNonRebasable.address, - wei`1 ether`, - wei`1 gwei`, - "0x" - ), - "ErrorDepositsDisabled()" - ); - - await assert.revertsWith( - ctx.l1TokenBridge.depositERC20( - ctx.stubs.l1TokenRebasable.address, - ctx.stubs.l2TokenRebasable.address, - wei`1 ether`, - wei`1 gwei`, - "0x" - ), - "ErrorDepositsDisabled()" - ); - }) - - .test("depositERC20() :: wrong l1Token address", async (ctx) => { - await assert.revertsWith( - ctx.l1TokenBridge.depositERC20( - ctx.accounts.stranger.address, - ctx.stubs.l2TokenNonRebasable.address, - wei`1 ether`, - wei`1 gwei`, - "0x" - ), - "ErrorUnsupportedL1L2TokensPair(\""+ctx.accounts.stranger.address+"\", \""+ctx.stubs.l2TokenNonRebasable.address+"\")" - ); - await assert.revertsWith( - ctx.l1TokenBridge.depositERC20( - ctx.accounts.stranger.address, - ctx.stubs.l2TokenRebasable.address, - wei`1 ether`, - wei`1 gwei`, - "0x" - ), - "ErrorUnsupportedL1L2TokensPair(\""+ctx.accounts.stranger.address+"\", \""+ctx.stubs.l2TokenRebasable.address+"\")" - ); - }) - - .test("depositERC20() :: wrong l2Token address", async (ctx) => { - await assert.revertsWith( - ctx.l1TokenBridge.depositERC20( - ctx.stubs.l1TokenNonRebasable.address, - ctx.accounts.stranger.address, - wei`1 ether`, - wei`1 gwei`, - "0x" - ), - "ErrorUnsupportedL1L2TokensPair(\""+ctx.stubs.l1TokenNonRebasable.address+"\", \""+ctx.accounts.stranger.address+"\")" - ); - await assert.revertsWith( - ctx.l1TokenBridge.depositERC20( - ctx.stubs.l1TokenRebasable.address, - ctx.accounts.stranger.address, - wei`1 ether`, - wei`1 gwei`, - "0x" - ), - "ErrorUnsupportedL1L2TokensPair(\""+ctx.stubs.l1TokenRebasable.address+"\", \""+ctx.accounts.stranger.address+"\")" - ); - }) - - .test("depositERC20() :: wrong tokens combination", async (ctx) => { - await assert.revertsWith( - ctx.l1TokenBridge.depositERC20( - ctx.stubs.l1TokenRebasable.address, - ctx.stubs.l2TokenNonRebasable.address, - wei`1 ether`, - wei`1 gwei`, - "0x" - ), - "ErrorUnsupportedL1L2TokensPair(\""+ctx.stubs.l1TokenRebasable.address+"\", \""+ctx.stubs.l2TokenNonRebasable.address+"\")" - ); - await assert.revertsWith( - ctx.l1TokenBridge.depositERC20( - ctx.stubs.l1TokenNonRebasable.address, - ctx.stubs.l2TokenRebasable.address, - wei`1 ether`, - wei`1 gwei`, - "0x" - ), - "ErrorUnsupportedL1L2TokensPair(\""+ctx.stubs.l1TokenNonRebasable.address+"\", \""+ctx.stubs.l2TokenRebasable.address+"\")" - ); - }) - - .test("depositERC20() :: not from EOA", async (ctx) => { - await assert.revertsWith( - ctx.l1TokenBridge - .connect(ctx.accounts.emptyContractAsEOA) - .depositERC20( - ctx.stubs.l1TokenNonRebasable.address, - ctx.stubs.l2TokenNonRebasable.address, - wei`1 ether`, - wei`1 gwei`, - "0x" - ), - "ErrorSenderNotEOA()" - ); - await assert.revertsWith( - ctx.l1TokenBridge - .connect(ctx.accounts.emptyContractAsEOA) - .depositERC20( - ctx.stubs.l1TokenRebasable.address, - ctx.stubs.l2TokenRebasable.address, - wei`1 ether`, - wei`1 gwei`, - "0x" - ), - "ErrorSenderNotEOA()" - ); - }) - - .test("depositERC20() :: non-rebasable token flow", async (ctx) => { - const { - l1TokenBridge, - accounts: { deployer, l2TokenBridgeEOA }, - stubs: { l1TokenNonRebasable, l2TokenNonRebasable, l1Messenger }, - } = ctx; - - const l2Gas = wei`0.99 wei`; - const amount = wei`1 ether`; - const data = "0xdeadbeaf"; - - await l1TokenNonRebasable.approve(l1TokenBridge.address, amount); - - const deployerBalanceBefore = await l1TokenNonRebasable.balanceOf(deployer.address); - const bridgeBalanceBefore = await l1TokenNonRebasable.balanceOf(l1TokenBridge.address); - - const tx = await l1TokenBridge.depositERC20( - l1TokenNonRebasable.address, - l2TokenNonRebasable.address, - amount, - l2Gas, - data - ); + .test("initial state", async (ctx) => { + assert.equal(await ctx.l1TokenBridge.l2TokenBridge(), ctx.accounts.l2TokenBridgeEOA.address); + assert.equal(await ctx.l1TokenBridge.MESSENGER(), ctx.accounts.l1MessengerStubAsEOA._address); + assert.equal(await ctx.l1TokenBridge.L1_TOKEN_NON_REBASABLE(), ctx.stubs.l1TokenNonRebasable.address); + assert.equal(await ctx.l1TokenBridge.L1_TOKEN_REBASABLE(), ctx.stubs.l1TokenRebasable.address); + assert.equal(await ctx.l1TokenBridge.L2_TOKEN_NON_REBASABLE(), ctx.stubs.l2TokenNonRebasable.address); + assert.equal(await ctx.l1TokenBridge.L2_TOKEN_REBASABLE(), ctx.stubs.l2TokenRebasable.address); + }) - const packedTokenRateAndTimestampData = await packedTokenRateAndTimestamp(ctx.provider, l1TokenNonRebasable); - const dataToReceive = ethers.utils.hexConcat([packedTokenRateAndTimestampData, data]); + .test("initialize() :: petrified", async (ctx) => { + const { deployer, l2TokenBridgeEOA } = ctx.accounts; - await assert.emits(l1TokenBridge, tx, "ERC20DepositInitiated", [ - l1TokenNonRebasable.address, - l2TokenNonRebasable.address, - deployer.address, - deployer.address, - amount, - dataToReceive, - ]); - - await assert.emits(l1Messenger, tx, "SentMessage", [ - l2TokenBridgeEOA.address, - l1TokenBridge.address, - L2ERC20ExtendedTokensBridge__factory.createInterface().encodeFunctionData( - "finalizeDeposit", - [ - l1TokenNonRebasable.address, - l2TokenNonRebasable.address, - deployer.address, - deployer.address, - amount, - dataToReceive, - ] - ), - 1, // message nonce - l2Gas, - ]); - - assert.equalBN( - await l1TokenNonRebasable.balanceOf(deployer.address), - deployerBalanceBefore.sub(amount) - ); - - assert.equalBN( - await l1TokenNonRebasable.balanceOf(l1TokenBridge.address), - bridgeBalanceBefore.add(amount) - ); - }) - - .test("depositERC20() :: rebasable token flow", async (ctx) => { - const { - l1TokenBridge, - accounts: { deployer, l2TokenBridgeEOA }, - stubs: { l1TokenRebasable, l2TokenRebasable, l1TokenNonRebasable, l1Messenger }, - } = ctx; - - const l2Gas = wei`0.99 wei`; - const amount = wei`1 ether`; - const data = "0xdeadbeaf"; - const rate = await l1TokenNonRebasable.stEthPerToken(); - const decimalsStr = await l1TokenNonRebasable.decimals(); - const decimals = BigNumber.from(10).pow(decimalsStr); - - const amountWrapped = (wei.toBigNumber(amount)).mul(BigNumber.from(decimals)).div(rate); - const deployerBalanceBefore = await l1TokenRebasable.balanceOf(deployer.address); - const bridgeBalanceBefore = await l1TokenNonRebasable.balanceOf(l1TokenBridge.address); - - await l1TokenRebasable.approve(l1TokenBridge.address, amount); - - const tx = await l1TokenBridge.depositERC20( - l1TokenRebasable.address, - l2TokenRebasable.address, - amount, - l2Gas, - data - ); + const l1LidoTokensBridgeImpl = await getL1LidoTokensBridgeImpl(deployer, l2TokenBridgeEOA); - const packedTokenRateAndTimestampData = await packedTokenRateAndTimestamp(ctx.provider, l1TokenNonRebasable); - const dataToReceive = ethers.utils.hexConcat([packedTokenRateAndTimestampData, data]); + const petrifiedVersionMark = hre.ethers.constants.MaxUint256; + assert.equalBN(await l1LidoTokensBridgeImpl.getContractVersion(), petrifiedVersionMark); - await assert.emits(l1TokenBridge, tx, "ERC20DepositInitiated", [ - l1TokenRebasable.address, - l2TokenRebasable.address, - deployer.address, - deployer.address, - amount, - dataToReceive, - ]); - - await assert.emits(l1Messenger, tx, "SentMessage", [ - l2TokenBridgeEOA.address, - l1TokenBridge.address, - L2ERC20ExtendedTokensBridge__factory.createInterface().encodeFunctionData( - "finalizeDeposit", - [ - l1TokenRebasable.address, - l2TokenRebasable.address, - deployer.address, - deployer.address, - amountWrapped, - dataToReceive, - ] - ), - 1, // message nonce - l2Gas, - ]); - - assert.equalBN( - await l1TokenRebasable.balanceOf(deployer.address), - deployerBalanceBefore.sub(amount) - ); - - assert.equalBN( - await l1TokenNonRebasable.balanceOf(l1TokenBridge.address), - bridgeBalanceBefore.add(amountWrapped) - ); - }) - - .test("depositERC20To() :: deposits disabled", async (ctx) => { - const { - l1TokenBridge, - stubs: { l1TokenNonRebasable, l2TokenNonRebasable, l1TokenRebasable, l2TokenRebasable }, - accounts: { recipient }, - } = ctx; - await l1TokenBridge.disableDeposits(); - - assert.isFalse(await l1TokenBridge.isDepositsEnabled()); - - await assert.revertsWith( - l1TokenBridge.depositERC20To( - l1TokenNonRebasable.address, - l2TokenNonRebasable.address, - recipient.address, - wei`1 ether`, - wei`1 gwei`, - "0x" - ), - "ErrorDepositsDisabled()" - ); - - await assert.revertsWith( - l1TokenBridge.depositERC20To( - l1TokenRebasable.address, - l2TokenRebasable.address, - recipient.address, - wei`1 ether`, - wei`1 gwei`, - "0x" - ), - "ErrorDepositsDisabled()" - ); - }) - - .test("depositERC20To() :: wrong l1Token address", async (ctx) => { - const { - l1TokenBridge, - stubs: { l2TokenNonRebasable, l2TokenRebasable }, - accounts: { recipient, stranger }, - } = ctx; - - await assert.revertsWith( - l1TokenBridge.depositERC20To( - stranger.address, - l2TokenNonRebasable.address, - recipient.address, - wei`1 ether`, - wei`1 gwei`, - "0x" - ), - "ErrorUnsupportedL1L2TokensPair(\""+stranger.address+"\", \""+l2TokenNonRebasable.address+"\")" - ); - await assert.revertsWith( - l1TokenBridge.depositERC20To( - stranger.address, - l2TokenRebasable.address, - recipient.address, - wei`1 ether`, - wei`1 gwei`, - "0x" - ), - "ErrorUnsupportedL1L2TokensPair(\""+stranger.address+"\", \""+l2TokenRebasable.address+"\")" - ); - }) - - .test("depositERC20To() :: wrong l2Token address", async (ctx) => { - const { - l1TokenBridge, - stubs: { l1TokenNonRebasable, l1TokenRebasable }, - accounts: { recipient, stranger }, - } = ctx; - - await assert.revertsWith( - l1TokenBridge.depositERC20To( - l1TokenNonRebasable.address, - stranger.address, - recipient.address, - wei`1 ether`, - wei`1 gwei`, - "0x" - ), - "ErrorUnsupportedL1L2TokensPair(\""+l1TokenNonRebasable.address+"\", \""+stranger.address+"\")" - ); - await assert.revertsWith( - l1TokenBridge.depositERC20To( - l1TokenRebasable.address, - stranger.address, - recipient.address, - wei`1 ether`, - wei`1 gwei`, - "0x" - ), - "ErrorUnsupportedL1L2TokensPair(\""+l1TokenRebasable.address+"\", \""+stranger.address+"\")" - ); - }) - - .test("depositERC20To() :: wrong tokens combination", async (ctx) => { - const { - l1TokenBridge, - stubs: { l1TokenNonRebasable, l1TokenRebasable, l2TokenNonRebasable, l2TokenRebasable }, - accounts: { recipient }, - } = ctx; - - await assert.revertsWith( - l1TokenBridge.depositERC20To( - l1TokenNonRebasable.address, - l2TokenRebasable.address, - recipient.address, - wei`1 ether`, - wei`1 gwei`, - "0x" - ), - "ErrorUnsupportedL1L2TokensPair(\""+l1TokenNonRebasable.address+"\", \""+l2TokenRebasable.address+"\")" - ); - await assert.revertsWith( - l1TokenBridge.depositERC20To( - l1TokenRebasable.address, - l2TokenNonRebasable.address, - recipient.address, - wei`1 ether`, - wei`1 gwei`, - "0x" - ), - "ErrorUnsupportedL1L2TokensPair(\""+l1TokenRebasable.address+"\", \""+l2TokenNonRebasable.address+"\")" - ); - }) - - .test("depositERC20To() :: recipient is zero address", async (ctx) => { - const { - l1TokenBridge, - stubs: { l1TokenNonRebasable, l2TokenNonRebasable, l1TokenRebasable, l2TokenRebasable } - } = ctx; - - await assert.revertsWith( - l1TokenBridge.depositERC20To( - l1TokenNonRebasable.address, - l2TokenNonRebasable.address, - ethers.constants.AddressZero, - wei`1 ether`, - wei`1 gwei`, - "0x" - ), - "ErrorAccountIsZeroAddress()" - ); - await assert.revertsWith( - l1TokenBridge.depositERC20To( - l1TokenRebasable.address, - l2TokenRebasable.address, - ethers.constants.AddressZero, - wei`1 ether`, - wei`1 gwei`, - "0x" - ), - "ErrorAccountIsZeroAddress()" - ); - }) - - .test("depositERC20To() :: non-rebasable token flow", async (ctx) => { - const { - l1TokenBridge, - accounts: { deployer, l2TokenBridgeEOA, recipient }, - stubs: { l1TokenNonRebasable, l2TokenNonRebasable, l1Messenger }, - } = ctx; - - const l2Gas = wei`0.99 wei`; - const amount = wei`1 ether`; - const data = "0x"; - - await l1TokenNonRebasable.approve(l1TokenBridge.address, amount); - - const deployerBalanceBefore = await l1TokenNonRebasable.balanceOf(deployer.address); - const bridgeBalanceBefore = await l1TokenNonRebasable.balanceOf(l1TokenBridge.address); - - const tx = await l1TokenBridge.depositERC20To( - l1TokenNonRebasable.address, - l2TokenNonRebasable.address, - recipient.address, - amount, - l2Gas, - data - ); + await assert.revertsWith( + l1LidoTokensBridgeImpl.initialize(deployer.address), + "NonZeroContractVersionOnInit()" + ); + }) - const packedTokenRateAndTimestampData = await packedTokenRateAndTimestamp(ctx.provider, l1TokenNonRebasable); - const dataToReceive = ethers.utils.hexConcat([packedTokenRateAndTimestampData, data]); + .test("initialize() :: don't allow to initialize twice", async (ctx) => { + const { deployer, l2TokenBridgeEOA } = ctx.accounts; - await assert.emits(l1TokenBridge, tx, "ERC20DepositInitiated", [ - l1TokenNonRebasable.address, - l2TokenNonRebasable.address, - deployer.address, - recipient.address, - amount, - dataToReceive, - ]); - - await assert.emits(l1Messenger, tx, "SentMessage", [ - l2TokenBridgeEOA.address, - l1TokenBridge.address, - L2ERC20ExtendedTokensBridge__factory.createInterface().encodeFunctionData( - "finalizeDeposit", - [ - l1TokenNonRebasable.address, - l2TokenNonRebasable.address, - deployer.address, - recipient.address, - amount, - dataToReceive, - ] - ), - 1, // message nonce - l2Gas, - ]); - - assert.equalBN( - await l1TokenNonRebasable.balanceOf(deployer.address), - deployerBalanceBefore.sub(amount) - ); - - assert.equalBN( - await l1TokenNonRebasable.balanceOf(l1TokenBridge.address), - bridgeBalanceBefore.add(amount) - ); - }) - - .test("depositERC20To() :: rebasable token flow", async (ctx) => { - const { - l1TokenBridge, - accounts: { deployer, l2TokenBridgeEOA, recipient }, - stubs: { l1TokenNonRebasable, l2TokenNonRebasable, l1TokenRebasable, l2TokenRebasable, l1Messenger }, - } = ctx; - - const l2Gas = wei`0.99 wei`; - const amount = wei`1 ether`; - const data = "0x"; - - const rate = await l1TokenNonRebasable.stEthPerToken(); - const decimalsStr = await l1TokenNonRebasable.decimals(); - const decimals = BigNumber.from(10).pow(decimalsStr); - - const amountWrapped = (wei.toBigNumber(amount)).mul(BigNumber.from(decimals)).div(rate); - - await l1TokenRebasable.approve(l1TokenBridge.address, amount); - - const deployerBalanceBefore = await l1TokenRebasable.balanceOf(deployer.address); - const bridgeBalanceBefore = await l1TokenNonRebasable.balanceOf(l1TokenBridge.address); - - const tx = await l1TokenBridge.depositERC20To( - l1TokenRebasable.address, - l2TokenRebasable.address, - recipient.address, - amount, - l2Gas, - data - ); + const l1LidoTokensBridgeImpl = await getL1LidoTokensBridgeImpl(deployer, l2TokenBridgeEOA); - const packedTokenRateAndTimestampData = await packedTokenRateAndTimestamp(ctx.provider, l1TokenNonRebasable); - const dataToReceive = ethers.utils.hexConcat([packedTokenRateAndTimestampData, data]); + const l1TokenBridgeProxy = await new OssifiableProxy__factory( + deployer + ).deploy( + l1LidoTokensBridgeImpl.address, + deployer.address, + l1LidoTokensBridgeImpl.interface.encodeFunctionData("initialize", [ + deployer.address + ]) + ); - await assert.emits(l1TokenBridge, tx, "ERC20DepositInitiated", [ - l1TokenRebasable.address, - l2TokenRebasable.address, - deployer.address, - recipient.address, - amount, - dataToReceive, - ]); - - await assert.emits(l1Messenger, tx, "SentMessage", [ - l2TokenBridgeEOA.address, - l1TokenBridge.address, - L2ERC20ExtendedTokensBridge__factory.createInterface().encodeFunctionData( - "finalizeDeposit", - [ - l1TokenRebasable.address, - l2TokenRebasable.address, - deployer.address, - recipient.address, - amountWrapped, - dataToReceive, - ] - ), - 1, // message nonce - l2Gas, - ]); - - assert.equalBN( - await l1TokenRebasable.balanceOf(deployer.address), - deployerBalanceBefore.sub(amount) - ); - - assert.equalBN( - await l1TokenNonRebasable.balanceOf(l1TokenBridge.address), - bridgeBalanceBefore.add(amountWrapped) - ); - }) - - .test( - "finalizeERC20Withdrawal() :: withdrawals are disabled", - async (ctx) => { - const { - l1TokenBridge, - stubs: { l1TokenNonRebasable, l2TokenNonRebasable, l1TokenRebasable, l2TokenRebasable }, - accounts: { deployer, recipient, l2TokenBridgeEOA }, - } = ctx; - await l1TokenBridge.disableWithdrawals(); - - assert.isFalse(await l1TokenBridge.isWithdrawalsEnabled()); - - await assert.revertsWith( - l1TokenBridge - .connect(l2TokenBridgeEOA) - .finalizeERC20Withdrawal( - l1TokenNonRebasable.address, - l2TokenNonRebasable.address, - deployer.address, - recipient.address, - wei`1 ether`, - "0x" - ), - "ErrorWithdrawalsDisabled()" - ); - await assert.revertsWith( - l1TokenBridge - .connect(l2TokenBridgeEOA) - .finalizeERC20Withdrawal( - l1TokenRebasable.address, - l2TokenRebasable.address, - deployer.address, - recipient.address, - wei`1 ether`, - "0x" - ), - "ErrorWithdrawalsDisabled()" - ); - } - ) - - .test("finalizeERC20Withdrawal() :: wrong l1Token", async (ctx) => { - const { - l1TokenBridge, - stubs: { l2TokenNonRebasable, l2TokenRebasable, l1Messenger }, - accounts: { deployer, recipient, l1MessengerStubAsEOA, stranger, l2TokenBridgeEOA }, - } = ctx; - - await l1Messenger.setXDomainMessageSender(l2TokenBridgeEOA.address); - - await assert.revertsWith( - l1TokenBridge - .connect(l1MessengerStubAsEOA) - .finalizeERC20Withdrawal( - stranger.address, - l2TokenNonRebasable.address, - deployer.address, - recipient.address, - wei`1 ether`, - "0x" - ), - "ErrorUnsupportedL1L2TokensPair(\""+stranger.address+"\", \""+l2TokenNonRebasable.address+"\")" - ); - - await assert.revertsWith( - l1TokenBridge - .connect(l1MessengerStubAsEOA) - .finalizeERC20Withdrawal( - stranger.address, - l2TokenRebasable.address, - deployer.address, - recipient.address, - wei`1 ether`, - "0x" - ), - "ErrorUnsupportedL1L2TokensPair(\""+stranger.address+"\", \""+l2TokenRebasable.address+"\")" - ); - }) - - .test("finalizeERC20Withdrawal() :: wrong l2Token", async (ctx) => { - const { - l1TokenBridge, - stubs: { l1TokenNonRebasable, l1TokenRebasable, l1Messenger }, - accounts: { deployer, recipient, l2TokenBridgeEOA, l1MessengerStubAsEOA, stranger }, - } = ctx; - - await l1Messenger.setXDomainMessageSender(l2TokenBridgeEOA.address); - - await assert.revertsWith( - l1TokenBridge - .connect(l1MessengerStubAsEOA) - .finalizeERC20Withdrawal( - l1TokenNonRebasable.address, - stranger.address, - deployer.address, - recipient.address, - wei`1 ether`, - "0x" - ), - "ErrorUnsupportedL1L2TokensPair(\""+l1TokenNonRebasable.address+"\", \""+stranger.address+"\")" - ); - - await assert.revertsWith( - l1TokenBridge - .connect(l1MessengerStubAsEOA) - .finalizeERC20Withdrawal( - l1TokenRebasable.address, - stranger.address, - deployer.address, - recipient.address, - wei`1 ether`, - "0x" - ), - "ErrorUnsupportedL1L2TokensPair(\""+l1TokenRebasable.address+"\", \""+stranger.address+"\")" - ); - }) - - .test("finalizeERC20Withdrawal() :: wrong token combination", async (ctx) => { - const { - l1TokenBridge, - stubs: { l1TokenNonRebasable, l1TokenRebasable, l2TokenNonRebasable, l2TokenRebasable, l1Messenger }, - accounts: { deployer, recipient, l2TokenBridgeEOA, l1MessengerStubAsEOA }, - } = ctx; - await l1Messenger.setXDomainMessageSender(l2TokenBridgeEOA.address); - - await assert.revertsWith( - l1TokenBridge - .connect(l1MessengerStubAsEOA) - .finalizeERC20Withdrawal( - l1TokenNonRebasable.address, - l2TokenRebasable.address, - deployer.address, - recipient.address, - wei`1 ether`, - "0x" - ), - "ErrorUnsupportedL1L2TokensPair(\""+l1TokenNonRebasable.address+"\", \""+l2TokenRebasable.address+"\")" - ); - - await assert.revertsWith( - l1TokenBridge - .connect(l1MessengerStubAsEOA) - .finalizeERC20Withdrawal( - l1TokenRebasable.address, - l2TokenNonRebasable.address, - deployer.address, - recipient.address, - wei`1 ether`, - "0x" - ), - "ErrorUnsupportedL1L2TokensPair(\""+l1TokenRebasable.address+"\", \""+l2TokenNonRebasable.address+"\")" - ); - }) - - .test("finalizeERC20Withdrawal() :: unauthorized messenger", async (ctx) => { - const { - l1TokenBridge, - stubs: { l1TokenNonRebasable, l2TokenNonRebasable, l1TokenRebasable, l2TokenRebasable }, - accounts: { deployer, recipient, stranger }, - } = ctx; - - await assert.revertsWith( - l1TokenBridge - .connect(stranger) - .finalizeERC20Withdrawal( - l1TokenNonRebasable.address, - l2TokenNonRebasable.address, - deployer.address, - recipient.address, - wei`1 ether`, - "0x" - ), - "ErrorUnauthorizedMessenger()" - ); - await assert.revertsWith( - l1TokenBridge - .connect(stranger) - .finalizeERC20Withdrawal( - l1TokenRebasable.address, - l2TokenRebasable.address, - deployer.address, - recipient.address, - wei`1 ether`, - "0x" - ), - "ErrorUnauthorizedMessenger()" - ); - }) - - .test("finalizeERC20Withdrawal() :: wrong cross domain sender", async (ctx) => { - const { - l1TokenBridge, - stubs: { l1TokenNonRebasable, l2TokenNonRebasable, l1TokenRebasable, l2TokenRebasable, l1Messenger }, - accounts: { deployer, recipient, stranger, l1MessengerStubAsEOA }, - } = ctx; - - await l1Messenger.setXDomainMessageSender(stranger.address); - - await assert.revertsWith( - l1TokenBridge - .connect(l1MessengerStubAsEOA) - .finalizeERC20Withdrawal( - l1TokenNonRebasable.address, - l2TokenNonRebasable.address, - deployer.address, - recipient.address, - wei`1 ether`, - "0x" - ), - "ErrorWrongCrossDomainSender()" - ); - await assert.revertsWith( - l1TokenBridge - .connect(l1MessengerStubAsEOA) - .finalizeERC20Withdrawal( - l1TokenRebasable.address, - l2TokenRebasable.address, - deployer.address, - recipient.address, - wei`1 ether`, - "0x" - ), - "ErrorWrongCrossDomainSender()" - ); - }) - - .test("finalizeERC20Withdrawal() :: non-rebasable token flow", async (ctx) => { - const { - l1TokenBridge, - stubs: { l1TokenNonRebasable, l2TokenNonRebasable, l1Messenger }, - accounts: { deployer, recipient, l1MessengerStubAsEOA, l2TokenBridgeEOA }, - } = ctx; - - await l1Messenger.setXDomainMessageSender(l2TokenBridgeEOA.address); - - const amount = wei`1 ether`; - const data = "0xdeadbeaf"; - const bridgeBalanceBefore = await l1TokenNonRebasable.balanceOf(l1TokenBridge.address); - - const tx = await l1TokenBridge - .connect(l1MessengerStubAsEOA) - .finalizeERC20Withdrawal( - l1TokenNonRebasable.address, - l2TokenNonRebasable.address, - deployer.address, - recipient.address, - amount, - data - ); - - await assert.emits(l1TokenBridge, tx, "ERC20WithdrawalFinalized", [ - l1TokenNonRebasable.address, - l2TokenNonRebasable.address, - deployer.address, - recipient.address, - amount, - data, - ]); - - assert.equalBN(await l1TokenNonRebasable.balanceOf(recipient.address), amount); - assert.equalBN( - await l1TokenNonRebasable.balanceOf(l1TokenBridge.address), - bridgeBalanceBefore.sub(amount) - ); - }) - - .test("finalizeERC20Withdrawal() :: rebasable token flow", async (ctx) => { - const { - l1TokenBridge, - stubs: { l1TokenRebasable, l2TokenRebasable, l1TokenNonRebasable, l2TokenNonRebasable, l1Messenger }, - accounts: { deployer, recipient, l1MessengerStubAsEOA, l2TokenBridgeEOA }, - } = ctx; - - await l1Messenger.setXDomainMessageSender(l2TokenBridgeEOA.address); - await l1TokenRebasable.transfer(l1TokenNonRebasable.address, wei`100 ether`); - - const amount = wei`1 ether`; - const data = "0xdeadbeaf"; - const rate = await l1TokenNonRebasable.stEthPerToken(); - const decimalsStr = await l1TokenNonRebasable.decimals(); - const decimals = BigNumber.from(10).pow(decimalsStr); - const amountUnwrapped = (wei.toBigNumber(amount)).mul(rate).div(BigNumber.from(decimals)); - const bridgeBalanceBefore = await l1TokenRebasable.balanceOf(l1TokenBridge.address); - - const tx = await l1TokenBridge - .connect(l1MessengerStubAsEOA) - .finalizeERC20Withdrawal( - l1TokenRebasable.address, - l2TokenRebasable.address, - deployer.address, - recipient.address, - amount, - data - ); - - await assert.emits(l1TokenBridge, tx, "ERC20WithdrawalFinalized", [ - l1TokenRebasable.address, - l2TokenRebasable.address, - deployer.address, - recipient.address, - amountUnwrapped, - data, - ]); - - assert.equalBN(await l1TokenRebasable.balanceOf(recipient.address), amountUnwrapped); - assert.equalBN( - await l1TokenNonRebasable.balanceOf(l1TokenBridge.address), - bridgeBalanceBefore.sub(amount) - ); - }) - - .test("finalizeERC20Withdrawal() :: zero amount of rebasable token", async (ctx) => { - const { - l1TokenBridge, - stubs: { l1TokenRebasable, l2TokenRebasable, l1Messenger }, - accounts: { deployer, recipient, l1MessengerStubAsEOA, l2TokenBridgeEOA }, - } = ctx; - - await l1Messenger.setXDomainMessageSender(l2TokenBridgeEOA.address); - - const data = "0xdeadbeaf"; - const recipientBalanceBefore = await l1TokenRebasable.balanceOf(recipient.address); - const bridgeBalanceBefore = await l1TokenRebasable.balanceOf(l1TokenBridge.address); - - const tx = await l1TokenBridge - .connect(l1MessengerStubAsEOA) - .finalizeERC20Withdrawal( - l1TokenRebasable.address, - l2TokenRebasable.address, - deployer.address, - recipient.address, - 0, - data - ); - - await assert.emits(l1TokenBridge, tx, "ERC20WithdrawalFinalized", [ - l1TokenRebasable.address, - l2TokenRebasable.address, - deployer.address, - recipient.address, - 0, - data, - ]); - - assert.equalBN(await l1TokenRebasable.balanceOf(recipient.address), recipientBalanceBefore); - assert.equalBN(await l1TokenRebasable.balanceOf(l1TokenBridge.address), bridgeBalanceBefore); - }) - - .test("finalizeERC20Withdrawal() :: zero amount of non-rebasable token", async (ctx) => { - const { - l1TokenBridge, - stubs: { l1TokenNonRebasable, l2TokenNonRebasable, l1Messenger }, - accounts: { deployer, recipient, l1MessengerStubAsEOA, l2TokenBridgeEOA }, - } = ctx; - - await l1Messenger.setXDomainMessageSender(l2TokenBridgeEOA.address); - - const data = "0xdeadbeaf"; - const recipientBalanceBefore = await l1TokenNonRebasable.balanceOf(recipient.address); - const bridgeBalanceBefore = await l1TokenNonRebasable.balanceOf(l1TokenBridge.address); - - const tx = await l1TokenBridge - .connect(l1MessengerStubAsEOA) - .finalizeERC20Withdrawal( - l1TokenNonRebasable.address, - l2TokenNonRebasable.address, - deployer.address, - recipient.address, - 0, - data - ); - - await assert.emits(l1TokenBridge, tx, "ERC20WithdrawalFinalized", [ - l1TokenNonRebasable.address, - l2TokenNonRebasable.address, - deployer.address, - recipient.address, - 0, - data, - ]); + const l1TokenBridge = L1LidoTokensBridge__factory.connect( + l1TokenBridgeProxy.address, + deployer + ); + + assert.equalBN(await l1TokenBridge.getContractVersion(), 2); + + await assert.revertsWith( + l1TokenBridge.initialize(deployer.address), + "NonZeroContractVersionOnInit()" + ); + }) + + .test("finalizeUpgrade_v2() :: bridging manager uninitialized", async (ctx) => { + const { deployer, l2TokenBridgeEOA } = ctx.accounts; + + const l1LidoTokensBridgeImpl = await getL1LidoTokensBridgeImpl(deployer, l2TokenBridgeEOA); + + await assert.revertsWith(new OssifiableProxy__factory(deployer).deploy( + l1LidoTokensBridgeImpl.address, + deployer.address, + L1LidoTokensBridge__factory.createInterface().encodeFunctionData("finalizeUpgrade_v2") + ), "ErrorBridgingManagerIsNotInitialized()"); + }) + + .test("finalizeUpgrade_v2() :: bridging manager initialized", async (ctx) => { + const { deployer, l2TokenBridgeEOA } = ctx.accounts; + + const bridgingManagerImpl = await new BridgingManagerStub__factory(deployer).deploy(); + const proxy = await new OssifiableProxy__factory(deployer).deploy( + bridgingManagerImpl.address, + deployer.address, + BridgingManagerStub__factory.createInterface().encodeFunctionData("initialize", [ + deployer.address + ]) + ); - assert.equalBN(await l1TokenNonRebasable.balanceOf(recipient.address), recipientBalanceBefore); - assert.equalBN(await l1TokenNonRebasable.balanceOf(l1TokenBridge.address), bridgeBalanceBefore); - }) + const l1LidoTokensBridgeImpl = await getL1LidoTokensBridgeImpl(deployer, l2TokenBridgeEOA); + await proxy.proxy__upgradeToAndCall( + l1LidoTokensBridgeImpl.address, + L1LidoTokensBridge__factory.createInterface().encodeFunctionData("finalizeUpgrade_v2"), + false + ); - .run(); + const l1LidoTokensBridgeProxied = L1LidoTokensBridge__factory.connect( + proxy.address, + deployer + ); -async function ctxFactory() { - const [deployer, l2TokenBridgeEOA, stranger, recipient] = - await hre.ethers.getSigners(); + assert.equalBN(await l1LidoTokensBridgeProxied.getContractVersion(), 2); + }) - const provider = await hre.ethers.provider; + .test("depositERC20() :: deposits disabled", async (ctx) => { + await ctx.l1TokenBridge.disableDeposits(); - const l1MessengerStub = await new CrossDomainMessengerStub__factory( - deployer - ).deploy({ value: wei.toBigNumber(wei`1 ether`) }); + assert.isFalse(await ctx.l1TokenBridge.isDepositsEnabled()); - const l1TokenRebasableStub = await new ERC20BridgedStub__factory(deployer).deploy( - "L1 Token Rebasable", - "L1R" + await assert.revertsWith( + ctx.l1TokenBridge.depositERC20( + ctx.stubs.l1TokenNonRebasable.address, + ctx.stubs.l2TokenNonRebasable.address, + wei`1 ether`, + wei`1 gwei`, + "0x" + ), + "ErrorDepositsDisabled()" ); - const l1TokenNonRebasableStub = await new ERC20WrapperStub__factory(deployer).deploy( - l1TokenRebasableStub.address, - "L1 Token Non Rebasable", - "L1NR", - BigNumber.from('1164454276599657236') + await assert.revertsWith( + ctx.l1TokenBridge.depositERC20( + ctx.stubs.l1TokenRebasable.address, + ctx.stubs.l2TokenRebasable.address, + wei`1 ether`, + wei`1 gwei`, + "0x" + ), + "ErrorDepositsDisabled()" + ); + }) + + .test("depositERC20() :: wrong l1Token address", async (ctx) => { + await assert.revertsWith( + ctx.l1TokenBridge.depositERC20( + ctx.accounts.stranger.address, + ctx.stubs.l2TokenNonRebasable.address, + wei`1 ether`, + wei`1 gwei`, + "0x" + ), + "ErrorUnsupportedL1L2TokensPair(\"" + ctx.accounts.stranger.address + "\", \"" + ctx.stubs.l2TokenNonRebasable.address + "\")" + ); + await assert.revertsWith( + ctx.l1TokenBridge.depositERC20( + ctx.accounts.stranger.address, + ctx.stubs.l2TokenRebasable.address, + wei`1 ether`, + wei`1 gwei`, + "0x" + ), + "ErrorUnsupportedL1L2TokensPair(\"" + ctx.accounts.stranger.address + "\", \"" + ctx.stubs.l2TokenRebasable.address + "\")" + ); + }) + + .test("depositERC20() :: wrong l2Token address", async (ctx) => { + await assert.revertsWith( + ctx.l1TokenBridge.depositERC20( + ctx.stubs.l1TokenNonRebasable.address, + ctx.accounts.stranger.address, + wei`1 ether`, + wei`1 gwei`, + "0x" + ), + "ErrorUnsupportedL1L2TokensPair(\"" + ctx.stubs.l1TokenNonRebasable.address + "\", \"" + ctx.accounts.stranger.address + "\")" + ); + await assert.revertsWith( + ctx.l1TokenBridge.depositERC20( + ctx.stubs.l1TokenRebasable.address, + ctx.accounts.stranger.address, + wei`1 ether`, + wei`1 gwei`, + "0x" + ), + "ErrorUnsupportedL1L2TokensPair(\"" + ctx.stubs.l1TokenRebasable.address + "\", \"" + ctx.accounts.stranger.address + "\")" + ); + }) + + .test("depositERC20() :: wrong tokens combination", async (ctx) => { + await assert.revertsWith( + ctx.l1TokenBridge.depositERC20( + ctx.stubs.l1TokenRebasable.address, + ctx.stubs.l2TokenNonRebasable.address, + wei`1 ether`, + wei`1 gwei`, + "0x" + ), + "ErrorUnsupportedL1L2TokensPair(\"" + ctx.stubs.l1TokenRebasable.address + "\", \"" + ctx.stubs.l2TokenNonRebasable.address + "\")" ); + await assert.revertsWith( + ctx.l1TokenBridge.depositERC20( + ctx.stubs.l1TokenNonRebasable.address, + ctx.stubs.l2TokenRebasable.address, + wei`1 ether`, + wei`1 gwei`, + "0x" + ), + "ErrorUnsupportedL1L2TokensPair(\"" + ctx.stubs.l1TokenNonRebasable.address + "\", \"" + ctx.stubs.l2TokenRebasable.address + "\")" + ); + }) + + .test("depositERC20() :: not from EOA", async (ctx) => { + await assert.revertsWith( + ctx.l1TokenBridge + .connect(ctx.accounts.emptyContractAsEOA) + .depositERC20( + ctx.stubs.l1TokenNonRebasable.address, + ctx.stubs.l2TokenNonRebasable.address, + wei`1 ether`, + wei`1 gwei`, + "0x" + ), + "ErrorSenderNotEOA()" + ); + await assert.revertsWith( + ctx.l1TokenBridge + .connect(ctx.accounts.emptyContractAsEOA) + .depositERC20( + ctx.stubs.l1TokenRebasable.address, + ctx.stubs.l2TokenRebasable.address, + wei`1 ether`, + wei`1 gwei`, + "0x" + ), + "ErrorSenderNotEOA()" + ); + }) + + .test("depositERC20() :: non-rebasable token flow", async (ctx) => { + const { + l1TokenBridge, + accounts: { deployer, l2TokenBridgeEOA }, + stubs: { l1TokenNonRebasable, l2TokenNonRebasable, l1Messenger }, + } = ctx; + + const l2Gas = wei`0.99 wei`; + const amount = wei`1 ether`; + const data = "0xdeadbeaf"; + + await l1TokenNonRebasable.approve(l1TokenBridge.address, amount); + + const deployerBalanceBefore = await l1TokenNonRebasable.balanceOf(deployer.address); + const bridgeBalanceBefore = await l1TokenNonRebasable.balanceOf(l1TokenBridge.address); + + const tx = await l1TokenBridge.depositERC20( + l1TokenNonRebasable.address, + l2TokenNonRebasable.address, + amount, + l2Gas, + data + ); + + const packedTokenRateAndTimestampData = await packedTokenRateAndTimestamp(ctx.provider, l1TokenNonRebasable); + const dataToReceive = ethers.utils.hexConcat([packedTokenRateAndTimestampData, data]); - const l2TokenNonRebasableStub = await new ERC20BridgedStub__factory(deployer).deploy( - "L2 Token Non Rebasable", - "L2NR" + await assert.emits(l1TokenBridge, tx, "ERC20DepositInitiated", [ + l1TokenNonRebasable.address, + l2TokenNonRebasable.address, + deployer.address, + deployer.address, + amount, + dataToReceive, + ]); + + await assert.emits(l1Messenger, tx, "SentMessage", [ + l2TokenBridgeEOA.address, + l1TokenBridge.address, + L2ERC20ExtendedTokensBridge__factory.createInterface().encodeFunctionData( + "finalizeDeposit", + [ + l1TokenNonRebasable.address, + l2TokenNonRebasable.address, + deployer.address, + deployer.address, + amount, + dataToReceive, + ] + ), + 1, // message nonce + l2Gas, + ]); + + assert.equalBN( + await l1TokenNonRebasable.balanceOf(deployer.address), + deployerBalanceBefore.sub(amount) ); - const l2TokenRebasableStub = await new ERC20WrapperStub__factory(deployer).deploy( - l2TokenNonRebasableStub.address, - "L2 Token Rebasable", - "L2R", - BigNumber.from('1164454276599657236') + assert.equalBN( + await l1TokenNonRebasable.balanceOf(l1TokenBridge.address), + bridgeBalanceBefore.add(amount) + ); + }) + + .test("depositERC20() :: rebasable token flow", async (ctx) => { + const { + l1TokenBridge, + accounts: { deployer, l2TokenBridgeEOA }, + stubs: { l1TokenRebasable, l2TokenRebasable, l1TokenNonRebasable, l1Messenger }, + } = ctx; + + const l2Gas = wei`0.99 wei`; + const amount = wei`1 ether`; + const data = "0xdeadbeaf"; + const rate = await l1TokenNonRebasable.stEthPerToken(); + const decimalsStr = await l1TokenNonRebasable.decimals(); + const decimals = BigNumber.from(10).pow(decimalsStr); + + const amountWrapped = (wei.toBigNumber(amount)).mul(BigNumber.from(decimals)).div(rate); + const deployerBalanceBefore = await l1TokenRebasable.balanceOf(deployer.address); + const bridgeBalanceBefore = await l1TokenNonRebasable.balanceOf(l1TokenBridge.address); + + await l1TokenRebasable.approve(l1TokenBridge.address, amount); + + const tx = await l1TokenBridge.depositERC20( + l1TokenRebasable.address, + l2TokenRebasable.address, + amount, + l2Gas, + data ); - const emptyContract = await new EmptyContractStub__factory(deployer).deploy({ - value: wei.toBigNumber(wei`1 ether`), - }); - const emptyContractAsEOA = await testing.impersonate(emptyContract.address); + const packedTokenRateAndTimestampData = await packedTokenRateAndTimestamp(ctx.provider, l1TokenNonRebasable); + const dataToReceive = ethers.utils.hexConcat([packedTokenRateAndTimestampData, data]); - const l1MessengerStubAsEOA = await testing.impersonate( - l1MessengerStub.address + await assert.emits(l1TokenBridge, tx, "ERC20DepositInitiated", [ + l1TokenRebasable.address, + l2TokenRebasable.address, + deployer.address, + deployer.address, + amount, + dataToReceive, + ]); + + await assert.emits(l1Messenger, tx, "SentMessage", [ + l2TokenBridgeEOA.address, + l1TokenBridge.address, + L2ERC20ExtendedTokensBridge__factory.createInterface().encodeFunctionData( + "finalizeDeposit", + [ + l1TokenRebasable.address, + l2TokenRebasable.address, + deployer.address, + deployer.address, + amountWrapped, + dataToReceive, + ] + ), + 1, // message nonce + l2Gas, + ]); + + assert.equalBN( + await l1TokenRebasable.balanceOf(deployer.address), + deployerBalanceBefore.sub(amount) ); - const l1TokenBridgeImpl = await new L1LidoTokensBridge__factory( - deployer - ).deploy( - l1MessengerStub.address, - l2TokenBridgeEOA.address, - l1TokenNonRebasableStub.address, - l1TokenRebasableStub.address, - l2TokenNonRebasableStub.address, - l2TokenRebasableStub.address + assert.equalBN( + await l1TokenNonRebasable.balanceOf(l1TokenBridge.address), + bridgeBalanceBefore.add(amountWrapped) + ); + }) + + .test("depositERC20To() :: deposits disabled", async (ctx) => { + const { + l1TokenBridge, + stubs: { l1TokenNonRebasable, l2TokenNonRebasable, l1TokenRebasable, l2TokenRebasable }, + accounts: { recipient }, + } = ctx; + await l1TokenBridge.disableDeposits(); + + assert.isFalse(await l1TokenBridge.isDepositsEnabled()); + + await assert.revertsWith( + l1TokenBridge.depositERC20To( + l1TokenNonRebasable.address, + l2TokenNonRebasable.address, + recipient.address, + wei`1 ether`, + wei`1 gwei`, + "0x" + ), + "ErrorDepositsDisabled()" ); - const l1TokenBridgeProxy = await new OssifiableProxy__factory( - deployer - ).deploy( - l1TokenBridgeImpl.address, - deployer.address, - l1TokenBridgeImpl.interface.encodeFunctionData("initialize", [ - deployer.address - ]) + await assert.revertsWith( + l1TokenBridge.depositERC20To( + l1TokenRebasable.address, + l2TokenRebasable.address, + recipient.address, + wei`1 ether`, + wei`1 gwei`, + "0x" + ), + "ErrorDepositsDisabled()" + ); + }) + + .test("depositERC20To() :: wrong l1Token address", async (ctx) => { + const { + l1TokenBridge, + stubs: { l2TokenNonRebasable, l2TokenRebasable }, + accounts: { recipient, stranger }, + } = ctx; + + await assert.revertsWith( + l1TokenBridge.depositERC20To( + stranger.address, + l2TokenNonRebasable.address, + recipient.address, + wei`1 ether`, + wei`1 gwei`, + "0x" + ), + "ErrorUnsupportedL1L2TokensPair(\"" + stranger.address + "\", \"" + l2TokenNonRebasable.address + "\")" + ); + await assert.revertsWith( + l1TokenBridge.depositERC20To( + stranger.address, + l2TokenRebasable.address, + recipient.address, + wei`1 ether`, + wei`1 gwei`, + "0x" + ), + "ErrorUnsupportedL1L2TokensPair(\"" + stranger.address + "\", \"" + l2TokenRebasable.address + "\")" + ); + }) + + .test("depositERC20To() :: wrong l2Token address", async (ctx) => { + const { + l1TokenBridge, + stubs: { l1TokenNonRebasable, l1TokenRebasable }, + accounts: { recipient, stranger }, + } = ctx; + + await assert.revertsWith( + l1TokenBridge.depositERC20To( + l1TokenNonRebasable.address, + stranger.address, + recipient.address, + wei`1 ether`, + wei`1 gwei`, + "0x" + ), + "ErrorUnsupportedL1L2TokensPair(\"" + l1TokenNonRebasable.address + "\", \"" + stranger.address + "\")" + ); + await assert.revertsWith( + l1TokenBridge.depositERC20To( + l1TokenRebasable.address, + stranger.address, + recipient.address, + wei`1 ether`, + wei`1 gwei`, + "0x" + ), + "ErrorUnsupportedL1L2TokensPair(\"" + l1TokenRebasable.address + "\", \"" + stranger.address + "\")" + ); + }) + + .test("depositERC20To() :: wrong tokens combination", async (ctx) => { + const { + l1TokenBridge, + stubs: { l1TokenNonRebasable, l1TokenRebasable, l2TokenNonRebasable, l2TokenRebasable }, + accounts: { recipient }, + } = ctx; + + await assert.revertsWith( + l1TokenBridge.depositERC20To( + l1TokenNonRebasable.address, + l2TokenRebasable.address, + recipient.address, + wei`1 ether`, + wei`1 gwei`, + "0x" + ), + "ErrorUnsupportedL1L2TokensPair(\"" + l1TokenNonRebasable.address + "\", \"" + l2TokenRebasable.address + "\")" + ); + await assert.revertsWith( + l1TokenBridge.depositERC20To( + l1TokenRebasable.address, + l2TokenNonRebasable.address, + recipient.address, + wei`1 ether`, + wei`1 gwei`, + "0x" + ), + "ErrorUnsupportedL1L2TokensPair(\"" + l1TokenRebasable.address + "\", \"" + l2TokenNonRebasable.address + "\")" + ); + }) + + .test("depositERC20To() :: recipient is zero address", async (ctx) => { + const { + l1TokenBridge, + stubs: { l1TokenNonRebasable, l2TokenNonRebasable, l1TokenRebasable, l2TokenRebasable } + } = ctx; + + await assert.revertsWith( + l1TokenBridge.depositERC20To( + l1TokenNonRebasable.address, + l2TokenNonRebasable.address, + ethers.constants.AddressZero, + wei`1 ether`, + wei`1 gwei`, + "0x" + ), + "ErrorAccountIsZeroAddress()" + ); + await assert.revertsWith( + l1TokenBridge.depositERC20To( + l1TokenRebasable.address, + l2TokenRebasable.address, + ethers.constants.AddressZero, + wei`1 ether`, + wei`1 gwei`, + "0x" + ), + "ErrorAccountIsZeroAddress()" + ); + }) + + .test("depositERC20To() :: non-rebasable token flow", async (ctx) => { + const { + l1TokenBridge, + accounts: { deployer, l2TokenBridgeEOA, recipient }, + stubs: { l1TokenNonRebasable, l2TokenNonRebasable, l1Messenger }, + } = ctx; + + const l2Gas = wei`0.99 wei`; + const amount = wei`1 ether`; + const data = "0x"; + + await l1TokenNonRebasable.approve(l1TokenBridge.address, amount); + + const deployerBalanceBefore = await l1TokenNonRebasable.balanceOf(deployer.address); + const bridgeBalanceBefore = await l1TokenNonRebasable.balanceOf(l1TokenBridge.address); + + const tx = await l1TokenBridge.depositERC20To( + l1TokenNonRebasable.address, + l2TokenNonRebasable.address, + recipient.address, + amount, + l2Gas, + data ); - const l1TokenBridge = L1LidoTokensBridge__factory.connect( - l1TokenBridgeProxy.address, - deployer + const packedTokenRateAndTimestampData = await packedTokenRateAndTimestamp(ctx.provider, l1TokenNonRebasable); + const dataToReceive = ethers.utils.hexConcat([packedTokenRateAndTimestampData, data]); + + await assert.emits(l1TokenBridge, tx, "ERC20DepositInitiated", [ + l1TokenNonRebasable.address, + l2TokenNonRebasable.address, + deployer.address, + recipient.address, + amount, + dataToReceive, + ]); + + await assert.emits(l1Messenger, tx, "SentMessage", [ + l2TokenBridgeEOA.address, + l1TokenBridge.address, + L2ERC20ExtendedTokensBridge__factory.createInterface().encodeFunctionData( + "finalizeDeposit", + [ + l1TokenNonRebasable.address, + l2TokenNonRebasable.address, + deployer.address, + recipient.address, + amount, + dataToReceive, + ] + ), + 1, // message nonce + l2Gas, + ]); + + assert.equalBN( + await l1TokenNonRebasable.balanceOf(deployer.address), + deployerBalanceBefore.sub(amount) ); - await l1TokenNonRebasableStub.transfer(l1TokenBridge.address, wei`100 ether`); - await l1TokenRebasableStub.transfer(l1TokenBridge.address, wei`100 ether`); + assert.equalBN( + await l1TokenNonRebasable.balanceOf(l1TokenBridge.address), + bridgeBalanceBefore.add(amount) + ); + }) + + .test("depositERC20To() :: rebasable token flow", async (ctx) => { + const { + l1TokenBridge, + accounts: { deployer, l2TokenBridgeEOA, recipient }, + stubs: { l1TokenNonRebasable, l2TokenNonRebasable, l1TokenRebasable, l2TokenRebasable, l1Messenger }, + } = ctx; + + const l2Gas = wei`0.99 wei`; + const amount = wei`1 ether`; + const data = "0x"; + + const rate = await l1TokenNonRebasable.stEthPerToken(); + const decimalsStr = await l1TokenNonRebasable.decimals(); + const decimals = BigNumber.from(10).pow(decimalsStr); + + const amountWrapped = (wei.toBigNumber(amount)).mul(BigNumber.from(decimals)).div(rate); - const roles = await Promise.all([ - l1TokenBridge.DEPOSITS_ENABLER_ROLE(), - l1TokenBridge.DEPOSITS_DISABLER_ROLE(), - l1TokenBridge.WITHDRAWALS_ENABLER_ROLE(), - l1TokenBridge.WITHDRAWALS_DISABLER_ROLE(), + await l1TokenRebasable.approve(l1TokenBridge.address, amount); + + const deployerBalanceBefore = await l1TokenRebasable.balanceOf(deployer.address); + const bridgeBalanceBefore = await l1TokenNonRebasable.balanceOf(l1TokenBridge.address); + + const tx = await l1TokenBridge.depositERC20To( + l1TokenRebasable.address, + l2TokenRebasable.address, + recipient.address, + amount, + l2Gas, + data + ); + + const packedTokenRateAndTimestampData = await packedTokenRateAndTimestamp(ctx.provider, l1TokenNonRebasable); + const dataToReceive = ethers.utils.hexConcat([packedTokenRateAndTimestampData, data]); + + await assert.emits(l1TokenBridge, tx, "ERC20DepositInitiated", [ + l1TokenRebasable.address, + l2TokenRebasable.address, + deployer.address, + recipient.address, + amount, + dataToReceive, ]); - for (const role of roles) { - await l1TokenBridge.grantRole(role, deployer.address); - } + await assert.emits(l1Messenger, tx, "SentMessage", [ + l2TokenBridgeEOA.address, + l1TokenBridge.address, + L2ERC20ExtendedTokensBridge__factory.createInterface().encodeFunctionData( + "finalizeDeposit", + [ + l1TokenRebasable.address, + l2TokenRebasable.address, + deployer.address, + recipient.address, + amountWrapped, + dataToReceive, + ] + ), + 1, // message nonce + l2Gas, + ]); - await l1TokenBridge.enableDeposits(); - await l1TokenBridge.enableWithdrawals(); - - return { - provider: provider, - accounts: { - deployer, - stranger, - l2TokenBridgeEOA, - emptyContractAsEOA, - recipient, - l1MessengerStubAsEOA, - }, - stubs: { - l1TokenNonRebasable: l1TokenNonRebasableStub, - l1TokenRebasable: l1TokenRebasableStub, - l2TokenNonRebasable: l2TokenNonRebasableStub, - l2TokenRebasable: l2TokenRebasableStub, - l1Messenger: l1MessengerStub, - }, + assert.equalBN( + await l1TokenRebasable.balanceOf(deployer.address), + deployerBalanceBefore.sub(amount) + ); + + assert.equalBN( + await l1TokenNonRebasable.balanceOf(l1TokenBridge.address), + bridgeBalanceBefore.add(amountWrapped) + ); + }) + + .test( + "finalizeERC20Withdrawal() :: withdrawals are disabled", + async (ctx) => { + const { l1TokenBridge, - }; + stubs: { l1TokenNonRebasable, l2TokenNonRebasable, l1TokenRebasable, l2TokenRebasable }, + accounts: { deployer, recipient, l2TokenBridgeEOA }, + } = ctx; + await l1TokenBridge.disableWithdrawals(); + + assert.isFalse(await l1TokenBridge.isWithdrawalsEnabled()); + + await assert.revertsWith( + l1TokenBridge + .connect(l2TokenBridgeEOA) + .finalizeERC20Withdrawal( + l1TokenNonRebasable.address, + l2TokenNonRebasable.address, + deployer.address, + recipient.address, + wei`1 ether`, + "0x" + ), + "ErrorWithdrawalsDisabled()" + ); + await assert.revertsWith( + l1TokenBridge + .connect(l2TokenBridgeEOA) + .finalizeERC20Withdrawal( + l1TokenRebasable.address, + l2TokenRebasable.address, + deployer.address, + recipient.address, + wei`1 ether`, + "0x" + ), + "ErrorWithdrawalsDisabled()" + ); + } + ) + + .test("finalizeERC20Withdrawal() :: wrong l1Token", async (ctx) => { + const { + l1TokenBridge, + stubs: { l2TokenNonRebasable, l2TokenRebasable, l1Messenger }, + accounts: { deployer, recipient, l1MessengerStubAsEOA, stranger, l2TokenBridgeEOA }, + } = ctx; + + await l1Messenger.setXDomainMessageSender(l2TokenBridgeEOA.address); + + await assert.revertsWith( + l1TokenBridge + .connect(l1MessengerStubAsEOA) + .finalizeERC20Withdrawal( + stranger.address, + l2TokenNonRebasable.address, + deployer.address, + recipient.address, + wei`1 ether`, + "0x" + ), + "ErrorUnsupportedL1L2TokensPair(\"" + stranger.address + "\", \"" + l2TokenNonRebasable.address + "\")" + ); + + await assert.revertsWith( + l1TokenBridge + .connect(l1MessengerStubAsEOA) + .finalizeERC20Withdrawal( + stranger.address, + l2TokenRebasable.address, + deployer.address, + recipient.address, + wei`1 ether`, + "0x" + ), + "ErrorUnsupportedL1L2TokensPair(\"" + stranger.address + "\", \"" + l2TokenRebasable.address + "\")" + ); + }) + + .test("finalizeERC20Withdrawal() :: wrong l2Token", async (ctx) => { + const { + l1TokenBridge, + stubs: { l1TokenNonRebasable, l1TokenRebasable, l1Messenger }, + accounts: { deployer, recipient, l2TokenBridgeEOA, l1MessengerStubAsEOA, stranger }, + } = ctx; + + await l1Messenger.setXDomainMessageSender(l2TokenBridgeEOA.address); + + await assert.revertsWith( + l1TokenBridge + .connect(l1MessengerStubAsEOA) + .finalizeERC20Withdrawal( + l1TokenNonRebasable.address, + stranger.address, + deployer.address, + recipient.address, + wei`1 ether`, + "0x" + ), + "ErrorUnsupportedL1L2TokensPair(\"" + l1TokenNonRebasable.address + "\", \"" + stranger.address + "\")" + ); + + await assert.revertsWith( + l1TokenBridge + .connect(l1MessengerStubAsEOA) + .finalizeERC20Withdrawal( + l1TokenRebasable.address, + stranger.address, + deployer.address, + recipient.address, + wei`1 ether`, + "0x" + ), + "ErrorUnsupportedL1L2TokensPair(\"" + l1TokenRebasable.address + "\", \"" + stranger.address + "\")" + ); + }) + + .test("finalizeERC20Withdrawal() :: wrong token combination", async (ctx) => { + const { + l1TokenBridge, + stubs: { l1TokenNonRebasable, l1TokenRebasable, l2TokenNonRebasable, l2TokenRebasable, l1Messenger }, + accounts: { deployer, recipient, l2TokenBridgeEOA, l1MessengerStubAsEOA }, + } = ctx; + await l1Messenger.setXDomainMessageSender(l2TokenBridgeEOA.address); + + await assert.revertsWith( + l1TokenBridge + .connect(l1MessengerStubAsEOA) + .finalizeERC20Withdrawal( + l1TokenNonRebasable.address, + l2TokenRebasable.address, + deployer.address, + recipient.address, + wei`1 ether`, + "0x" + ), + "ErrorUnsupportedL1L2TokensPair(\"" + l1TokenNonRebasable.address + "\", \"" + l2TokenRebasable.address + "\")" + ); + + await assert.revertsWith( + l1TokenBridge + .connect(l1MessengerStubAsEOA) + .finalizeERC20Withdrawal( + l1TokenRebasable.address, + l2TokenNonRebasable.address, + deployer.address, + recipient.address, + wei`1 ether`, + "0x" + ), + "ErrorUnsupportedL1L2TokensPair(\"" + l1TokenRebasable.address + "\", \"" + l2TokenNonRebasable.address + "\")" + ); + }) + + .test("finalizeERC20Withdrawal() :: unauthorized messenger", async (ctx) => { + const { + l1TokenBridge, + stubs: { l1TokenNonRebasable, l2TokenNonRebasable, l1TokenRebasable, l2TokenRebasable }, + accounts: { deployer, recipient, stranger }, + } = ctx; + + await assert.revertsWith( + l1TokenBridge + .connect(stranger) + .finalizeERC20Withdrawal( + l1TokenNonRebasable.address, + l2TokenNonRebasable.address, + deployer.address, + recipient.address, + wei`1 ether`, + "0x" + ), + "ErrorUnauthorizedMessenger()" + ); + await assert.revertsWith( + l1TokenBridge + .connect(stranger) + .finalizeERC20Withdrawal( + l1TokenRebasable.address, + l2TokenRebasable.address, + deployer.address, + recipient.address, + wei`1 ether`, + "0x" + ), + "ErrorUnauthorizedMessenger()" + ); + }) + + .test("finalizeERC20Withdrawal() :: wrong cross domain sender", async (ctx) => { + const { + l1TokenBridge, + stubs: { l1TokenNonRebasable, l2TokenNonRebasable, l1TokenRebasable, l2TokenRebasable, l1Messenger }, + accounts: { deployer, recipient, stranger, l1MessengerStubAsEOA }, + } = ctx; + + await l1Messenger.setXDomainMessageSender(stranger.address); + + await assert.revertsWith( + l1TokenBridge + .connect(l1MessengerStubAsEOA) + .finalizeERC20Withdrawal( + l1TokenNonRebasable.address, + l2TokenNonRebasable.address, + deployer.address, + recipient.address, + wei`1 ether`, + "0x" + ), + "ErrorWrongCrossDomainSender()" + ); + await assert.revertsWith( + l1TokenBridge + .connect(l1MessengerStubAsEOA) + .finalizeERC20Withdrawal( + l1TokenRebasable.address, + l2TokenRebasable.address, + deployer.address, + recipient.address, + wei`1 ether`, + "0x" + ), + "ErrorWrongCrossDomainSender()" + ); + }) + + .test("finalizeERC20Withdrawal() :: non-rebasable token flow", async (ctx) => { + const { + l1TokenBridge, + stubs: { l1TokenNonRebasable, l2TokenNonRebasable, l1Messenger }, + accounts: { deployer, recipient, l1MessengerStubAsEOA, l2TokenBridgeEOA }, + } = ctx; + + await l1Messenger.setXDomainMessageSender(l2TokenBridgeEOA.address); + + const amount = wei`1 ether`; + const data = "0xdeadbeaf"; + const bridgeBalanceBefore = await l1TokenNonRebasable.balanceOf(l1TokenBridge.address); + + const tx = await l1TokenBridge + .connect(l1MessengerStubAsEOA) + .finalizeERC20Withdrawal( + l1TokenNonRebasable.address, + l2TokenNonRebasable.address, + deployer.address, + recipient.address, + amount, + data + ); + + await assert.emits(l1TokenBridge, tx, "ERC20WithdrawalFinalized", [ + l1TokenNonRebasable.address, + l2TokenNonRebasable.address, + deployer.address, + recipient.address, + amount, + data, + ]); + + assert.equalBN(await l1TokenNonRebasable.balanceOf(recipient.address), amount); + assert.equalBN( + await l1TokenNonRebasable.balanceOf(l1TokenBridge.address), + bridgeBalanceBefore.sub(amount) + ); + }) + + .test("finalizeERC20Withdrawal() :: rebasable token flow", async (ctx) => { + const { + l1TokenBridge, + stubs: { l1TokenRebasable, l2TokenRebasable, l1TokenNonRebasable, l2TokenNonRebasable, l1Messenger }, + accounts: { deployer, recipient, l1MessengerStubAsEOA, l2TokenBridgeEOA }, + } = ctx; + + await l1Messenger.setXDomainMessageSender(l2TokenBridgeEOA.address); + await l1TokenRebasable.transfer(l1TokenNonRebasable.address, wei`100 ether`); + + const amount = wei`1 ether`; + const data = "0xdeadbeaf"; + const rate = await l1TokenNonRebasable.stEthPerToken(); + const decimalsStr = await l1TokenNonRebasable.decimals(); + const decimals = BigNumber.from(10).pow(decimalsStr); + const amountUnwrapped = (wei.toBigNumber(amount)).mul(rate).div(BigNumber.from(decimals)); + const bridgeBalanceBefore = await l1TokenRebasable.balanceOf(l1TokenBridge.address); + + const tx = await l1TokenBridge + .connect(l1MessengerStubAsEOA) + .finalizeERC20Withdrawal( + l1TokenRebasable.address, + l2TokenRebasable.address, + deployer.address, + recipient.address, + amount, + data + ); + + await assert.emits(l1TokenBridge, tx, "ERC20WithdrawalFinalized", [ + l1TokenRebasable.address, + l2TokenRebasable.address, + deployer.address, + recipient.address, + amountUnwrapped, + data, + ]); + + assert.equalBN(await l1TokenRebasable.balanceOf(recipient.address), amountUnwrapped); + assert.equalBN( + await l1TokenNonRebasable.balanceOf(l1TokenBridge.address), + bridgeBalanceBefore.sub(amount) + ); + }) + + .test("finalizeERC20Withdrawal() :: zero amount of rebasable token", async (ctx) => { + const { + l1TokenBridge, + stubs: { l1TokenRebasable, l2TokenRebasable, l1Messenger }, + accounts: { deployer, recipient, l1MessengerStubAsEOA, l2TokenBridgeEOA }, + } = ctx; + + await l1Messenger.setXDomainMessageSender(l2TokenBridgeEOA.address); + + const data = "0xdeadbeaf"; + const recipientBalanceBefore = await l1TokenRebasable.balanceOf(recipient.address); + const bridgeBalanceBefore = await l1TokenRebasable.balanceOf(l1TokenBridge.address); + + const tx = await l1TokenBridge + .connect(l1MessengerStubAsEOA) + .finalizeERC20Withdrawal( + l1TokenRebasable.address, + l2TokenRebasable.address, + deployer.address, + recipient.address, + 0, + data + ); + + await assert.emits(l1TokenBridge, tx, "ERC20WithdrawalFinalized", [ + l1TokenRebasable.address, + l2TokenRebasable.address, + deployer.address, + recipient.address, + 0, + data, + ]); + + assert.equalBN(await l1TokenRebasable.balanceOf(recipient.address), recipientBalanceBefore); + assert.equalBN(await l1TokenRebasable.balanceOf(l1TokenBridge.address), bridgeBalanceBefore); + }) + + .test("finalizeERC20Withdrawal() :: zero amount of non-rebasable token", async (ctx) => { + const { + l1TokenBridge, + stubs: { l1TokenNonRebasable, l2TokenNonRebasable, l1Messenger }, + accounts: { deployer, recipient, l1MessengerStubAsEOA, l2TokenBridgeEOA }, + } = ctx; + + await l1Messenger.setXDomainMessageSender(l2TokenBridgeEOA.address); + + const data = "0xdeadbeaf"; + const recipientBalanceBefore = await l1TokenNonRebasable.balanceOf(recipient.address); + const bridgeBalanceBefore = await l1TokenNonRebasable.balanceOf(l1TokenBridge.address); + + const tx = await l1TokenBridge + .connect(l1MessengerStubAsEOA) + .finalizeERC20Withdrawal( + l1TokenNonRebasable.address, + l2TokenNonRebasable.address, + deployer.address, + recipient.address, + 0, + data + ); + + await assert.emits(l1TokenBridge, tx, "ERC20WithdrawalFinalized", [ + l1TokenNonRebasable.address, + l2TokenNonRebasable.address, + deployer.address, + recipient.address, + 0, + data, + ]); + + assert.equalBN(await l1TokenNonRebasable.balanceOf(recipient.address), recipientBalanceBefore); + assert.equalBN(await l1TokenNonRebasable.balanceOf(l1TokenBridge.address), bridgeBalanceBefore); + }) + + .run(); + +async function ctxFactory() { + const [deployer, l2TokenBridgeEOA, stranger, recipient] = + await hre.ethers.getSigners(); + + const provider = await hre.ethers.provider; + const tokenRate = BigNumber.from('1164454276599657236'); + + const l1MessengerStub = await new CrossDomainMessengerStub__factory( + deployer + ).deploy({ value: wei.toBigNumber(wei`1 ether`) }); + + const l1TokenRebasableStub = await new ERC20BridgedStub__factory(deployer).deploy( + "L1 Token Rebasable", + "L1R" + ); + + const l1TokenNonRebasableStub = await new ERC20WrapperStub__factory(deployer).deploy( + l1TokenRebasableStub.address, + "L1 Token Non Rebasable", + "L1NR", + tokenRate + ); + + const l2TokenNonRebasableStub = await new ERC20BridgedStub__factory(deployer).deploy( + "L2 Token Non Rebasable", + "L2NR" + ); + + const l2TokenRebasableStub = await new ERC20WrapperStub__factory(deployer).deploy( + l2TokenNonRebasableStub.address, + "L2 Token Rebasable", + "L2R", + tokenRate + ); + + const emptyContract = await new EmptyContractStub__factory(deployer).deploy({ + value: wei.toBigNumber(wei`1 ether`), + }); + const emptyContractAsEOA = await testing.impersonate(emptyContract.address); + + const l1MessengerStubAsEOA = await testing.impersonate( + l1MessengerStub.address + ); + + const l1TokenBridgeImpl = await new L1LidoTokensBridge__factory( + deployer + ).deploy( + l1MessengerStub.address, + l2TokenBridgeEOA.address, + l1TokenNonRebasableStub.address, + l1TokenRebasableStub.address, + l2TokenNonRebasableStub.address, + l2TokenRebasableStub.address + ); + + const l1TokenBridgeProxy = await new OssifiableProxy__factory( + deployer + ).deploy( + l1TokenBridgeImpl.address, + deployer.address, + l1TokenBridgeImpl.interface.encodeFunctionData("initialize", [ + deployer.address + ]) + ); + + const l1TokenBridge = L1LidoTokensBridge__factory.connect( + l1TokenBridgeProxy.address, + deployer + ); + + await l1TokenNonRebasableStub.transfer(l1TokenBridge.address, wei`100 ether`); + await l1TokenRebasableStub.transfer(l1TokenBridge.address, wei`100 ether`); + + const roles = await Promise.all([ + l1TokenBridge.DEPOSITS_ENABLER_ROLE(), + l1TokenBridge.DEPOSITS_DISABLER_ROLE(), + l1TokenBridge.WITHDRAWALS_ENABLER_ROLE(), + l1TokenBridge.WITHDRAWALS_DISABLER_ROLE(), + ]); + + for (const role of roles) { + await l1TokenBridge.grantRole(role, deployer.address); + } + + await l1TokenBridge.enableDeposits(); + await l1TokenBridge.enableWithdrawals(); + + return { + provider: provider, + accounts: { + deployer, + stranger, + l2TokenBridgeEOA, + emptyContractAsEOA, + recipient, + l1MessengerStubAsEOA, + }, + stubs: { + l1TokenNonRebasable: l1TokenNonRebasableStub, + l1TokenRebasable: l1TokenRebasableStub, + l2TokenNonRebasable: l2TokenNonRebasableStub, + l2TokenRebasable: l2TokenRebasableStub, + l1Messenger: l1MessengerStub, + }, + l1TokenBridge, + }; } async function packedTokenRateAndTimestamp(l1Provider: JsonRpcProvider, l1Token: ERC20WrapperStub) { - const stEthPerToken = await l1Token.stEthPerToken(); - const blockNumber = await l1Provider.getBlockNumber(); - const blockTimestamp = (await l1Provider.getBlock(blockNumber)).timestamp; - const stEthPerTokenStr = ethers.utils.hexZeroPad(stEthPerToken.toHexString(), 12); - const blockTimestampStr = ethers.utils.hexZeroPad(ethers.utils.hexlify(blockTimestamp), 5); - return ethers.utils.hexConcat([stEthPerTokenStr, blockTimestampStr]); + const stEthPerToken = await l1Token.stEthPerToken(); + const blockNumber = await l1Provider.getBlockNumber(); + const blockTimestamp = (await l1Provider.getBlock(blockNumber)).timestamp; + const stEthPerTokenStr = ethers.utils.hexZeroPad(stEthPerToken.toHexString(), 12); + const blockTimestampStr = ethers.utils.hexZeroPad(ethers.utils.hexlify(blockTimestamp), 5); + return ethers.utils.hexConcat([stEthPerTokenStr, blockTimestampStr]); +} + +async function getL1LidoTokensBridgeImpl(deployer: SignerWithAddress, l2TokenBridgeEOA: SignerWithAddress) { + const tokenRate = BigNumber.from('1164454276599657236'); + + const l1MessengerStub = await new CrossDomainMessengerStub__factory( + deployer + ).deploy({ value: wei.toBigNumber(wei`1 ether`) }); + + const l1TokenRebasableStub = await new ERC20BridgedStub__factory(deployer).deploy( + "L1 Token Rebasable", + "L1R" + ); + + const l1TokenNonRebasableStub = await new ERC20WrapperStub__factory(deployer).deploy( + l1TokenRebasableStub.address, + "L1 Token Non Rebasable", + "L1NR", + tokenRate + ); + + const l2TokenNonRebasableStub = await new ERC20BridgedStub__factory(deployer).deploy( + "L2 Token Non Rebasable", + "L2NR" + ); + + const l2TokenRebasableStub = await new ERC20WrapperStub__factory(deployer).deploy( + l2TokenNonRebasableStub.address, + "L2 Token Rebasable", + "L2R", + tokenRate + ); + + const l1TokenBridgeImpl = await new L1LidoTokensBridge__factory( + deployer + ).deploy( + l1MessengerStub.address, + l2TokenBridgeEOA.address, + l1TokenNonRebasableStub.address, + l1TokenRebasableStub.address, + l2TokenNonRebasableStub.address, + l2TokenRebasableStub.address + ); + return l1TokenBridgeImpl; } diff --git a/test/optimism/L2ERC20ExtendedTokensBridge.unit.test.ts b/test/optimism/L2ERC20ExtendedTokensBridge.unit.test.ts index 72fa414b..a3a671ed 100644 --- a/test/optimism/L2ERC20ExtendedTokensBridge.unit.test.ts +++ b/test/optimism/L2ERC20ExtendedTokensBridge.unit.test.ts @@ -1,1157 +1,1325 @@ import hre, { ethers } from "hardhat"; -import { - ERC20BridgedStub__factory, - ERC20WrapperStub__factory, - TokenRateOracle__factory, - ERC20RebasableBridgedPermit__factory, - L1LidoTokensBridge__factory, - L2ERC20ExtendedTokensBridge__factory, - OssifiableProxy__factory, - EmptyContractStub__factory, - CrossDomainMessengerStub__factory, - L2ERC20ExtendedTokensBridge -} from "../../typechain"; -import testing, { unit } from "../../utils/testing"; -import { wei } from "../../utils/wei"; +import { BigNumber } from "ethers"; +import { getContractAddress } from "ethers/lib/utils"; import { assert } from "chai"; import { SignerWithAddress } from "@nomiclabs/hardhat-ethers/signers"; -import { getContractAddress } from "ethers/lib/utils"; import { JsonRpcProvider } from "@ethersproject/providers"; -import { BigNumber } from "ethers"; +import testing, { unit } from "../../utils/testing"; +import { wei } from "../../utils/wei"; +import { + ERC20BridgedStub__factory, + ERC20WrapperStub__factory, + TokenRateOracle__factory, + ERC20RebasableBridgedPermit__factory, + L1LidoTokensBridge__factory, + L2ERC20ExtendedTokensBridge__factory, + OssifiableProxy__factory, + EmptyContractStub__factory, + CrossDomainMessengerStub__factory, + BridgingManagerStub__factory +} from "../../typechain"; unit("Optimism:: L2ERC20ExtendedTokensBridge", ctxFactory) - .test("initial state", async (ctx) => { - assert.equal(await ctx.l2TokenBridge.l1TokenBridge(), ctx.accounts.l1TokenBridgeEOA.address); - assert.equal(await ctx.l2TokenBridge.MESSENGER(), ctx.accounts.l2MessengerStubEOA._address); - assert.equal(await ctx.l2TokenBridge.L1_TOKEN_NON_REBASABLE(), ctx.stubs.l1TokenNonRebasable.address); - assert.equal(await ctx.l2TokenBridge.L1_TOKEN_REBASABLE(), ctx.stubs.l1TokenRebasable.address); - assert.equal(await ctx.l2TokenBridge.L2_TOKEN_NON_REBASABLE(), ctx.stubs.l2TokenNonRebasable.address); - assert.equal(await ctx.l2TokenBridge.L2_TOKEN_REBASABLE(), ctx.stubs.l2TokenRebasable.address); - }) - - .test("withdraw() :: withdrawals disabled", async (ctx) => { - const { - l2TokenBridge, - stubs: { l2TokenNonRebasable, l2TokenRebasable }, - } = ctx; - - await ctx.l2TokenBridge.disableWithdrawals(); - - assert.isFalse(await ctx.l2TokenBridge.isWithdrawalsEnabled()); - - await assert.revertsWith( - l2TokenBridge.withdraw( - l2TokenNonRebasable.address, - wei`1 ether`, - wei`1 gwei`, - "0x" - ), - "ErrorWithdrawalsDisabled()" - ); - - await assert.revertsWith( - l2TokenBridge.withdraw( - l2TokenRebasable.address, - wei`1 ether`, - wei`1 gwei`, - "0x" - ), - "ErrorWithdrawalsDisabled()" - ); - }) - - .test("withdraw() :: unsupported l2Token", async (ctx) => { - const { - l2TokenBridge, - accounts: { stranger }, - } = ctx; - await assert.revertsWith( - l2TokenBridge.withdraw(stranger.address, wei`1 ether`, wei`1 gwei`, "0x"), - "ErrorUnsupportedL2Token(\""+stranger.address+"\")" - ); - }) - - .test("withdraw() :: not from EOA", async (ctx) => { - const { - l2TokenBridge, - accounts: { emptyContractEOA }, - stubs: { l2TokenRebasable, l2TokenNonRebasable }, - } = ctx; - - await assert.revertsWith( - l2TokenBridge - .connect(emptyContractEOA) - .withdraw( - l2TokenNonRebasable.address, - wei`1 ether`, - wei`1 gwei`, - "0x" - ), - "ErrorSenderNotEOA()" - ); - await assert.revertsWith( - l2TokenBridge - .connect(emptyContractEOA) - .withdraw( - l2TokenRebasable.address, - wei`1 ether`, - wei`1 gwei`, - "0x" - ), - "ErrorSenderNotEOA()" - ); - }) - - .test("withdraw() :: non-rebasable token flow", async (ctx) => { - const { - l2TokenBridge, - accounts: { deployer, l1TokenBridgeEOA }, - stubs: { - l2Messenger, - l1TokenNonRebasable, - l2TokenNonRebasable, - }, - } = ctx; - - const deployerBalanceBefore = await l2TokenNonRebasable.balanceOf(deployer.address); - const totalSupplyBefore = await l2TokenNonRebasable.totalSupply(); - - const amount = wei`1 ether`; - const l1Gas = wei`1 wei`; - const data = "0xdeadbeaf"; - - const tx = await l2TokenBridge.withdraw( - l2TokenNonRebasable.address, - amount, - l1Gas, - data - ); - - await assert.emits(l2TokenBridge, tx, "WithdrawalInitiated", [ - l1TokenNonRebasable.address, - l2TokenNonRebasable.address, - deployer.address, - deployer.address, - amount, - data, - ]); - - await assert.emits(l2Messenger, tx, "SentMessage", [ - l1TokenBridgeEOA.address, - l2TokenBridge.address, - L1LidoTokensBridge__factory.createInterface().encodeFunctionData( - "finalizeERC20Withdrawal", - [ - l1TokenNonRebasable.address, - l2TokenNonRebasable.address, - deployer.address, - deployer.address, - amount, - data, - ] - ), - 1, // message nonce - l1Gas, - ]); - - assert.equalBN( - await l2TokenNonRebasable.balanceOf(deployer.address), - deployerBalanceBefore.sub(amount) - ); - - assert.equalBN( - await l2TokenNonRebasable.totalSupply(), - totalSupplyBefore.sub(amount) - ); - }) - - .test("withdraw() :: rebasable token flow", async (ctx) => { - const { - l2TokenBridge, - accounts: { deployer, l1TokenBridgeEOA, l2MessengerStubEOA, recipient }, - stubs: { - l2Messenger, - l1TokenRebasable, - l2TokenRebasable - }, - } = ctx; - - const amountToDeposit = wei`1 ether`; - const amountToWithdraw = wei.toBigNumber(amountToDeposit).mul(ctx.exchangeRate).div(ctx.decimalsBN); - const l1Gas = wei`1 wei`; - const data = "0xdeadbeaf"; - const provider = await hre.ethers.provider; - const packedTokenRateAndTimestampData = await packedTokenRateAndTimestamp(provider, ctx.exchangeRate); - - const tx1 = await l2TokenBridge - .connect(l2MessengerStubEOA) - .finalizeDeposit( - l1TokenRebasable.address, - l2TokenRebasable.address, - deployer.address, - recipient.address, - amountToDeposit, - packedTokenRateAndTimestampData - ); - - const recipientBalanceBefore = await l2TokenRebasable.balanceOf(recipient.address); - const totalSupplyBefore = await l2TokenRebasable.totalSupply(); - - const tx = await l2TokenBridge.connect(recipient).withdraw( - l2TokenRebasable.address, - amountToWithdraw, - l1Gas, - data - ); - - await assert.emits(l2TokenBridge, tx, "WithdrawalInitiated", [ - l1TokenRebasable.address, - l2TokenRebasable.address, - recipient.address, - recipient.address, - amountToWithdraw, - data, - ]); - - await assert.emits(l2Messenger, tx, "SentMessage", [ - l1TokenBridgeEOA.address, - l2TokenBridge.address, - L1LidoTokensBridge__factory.createInterface().encodeFunctionData( - "finalizeERC20Withdrawal", - [ - l1TokenRebasable.address, - l2TokenRebasable.address, - recipient.address, - recipient.address, - amountToDeposit, - data, - ] - ), - 1, // message nonce - l1Gas, - ]); - - assert.equalBN( - await l2TokenRebasable.balanceOf(deployer.address), - recipientBalanceBefore.sub(amountToWithdraw) - ); - - assert.equalBN( - await l2TokenRebasable.totalSupply(), - totalSupplyBefore.sub(amountToWithdraw) - ); - }) - - .test("withdraw() :: zero rebasable tokens", async (ctx) => { - const { - l2TokenBridge, - accounts: { deployer, l1TokenBridgeEOA, recipient }, - stubs: { - l2Messenger, - l1TokenRebasable, - l2TokenRebasable - }, - } = ctx; - - await pushTokenRate(ctx); - - const l1Gas = wei`1 wei`; - const data = "0xdeadbeaf"; - const recipientBalanceBefore = await l2TokenRebasable.balanceOf(recipient.address); - const totalSupplyBefore = await l2TokenRebasable.totalSupply(); - - const tx = await l2TokenBridge - .connect(recipient) - .withdraw( - l2TokenRebasable.address, - 0, - l1Gas, - data); - - await assert.emits(l2TokenBridge, tx, "WithdrawalInitiated", [ - l1TokenRebasable.address, - l2TokenRebasable.address, - recipient.address, - recipient.address, - 0, - data, - ]); - - await assert.emits(l2Messenger, tx, "SentMessage", [ - l1TokenBridgeEOA.address, - l2TokenBridge.address, - L1LidoTokensBridge__factory.createInterface().encodeFunctionData( - "finalizeERC20Withdrawal", - [ - l1TokenRebasable.address, - l2TokenRebasable.address, - recipient.address, - recipient.address, - 0, - data, - ] - ), - 1, // message nonce - l1Gas, - ]); - - assert.equalBN(await l2TokenRebasable.balanceOf(deployer.address), recipientBalanceBefore); - assert.equalBN(await l2TokenRebasable.totalSupply(), totalSupplyBefore); - }) - - .test("withdraw() :: zero non-rebasable tokens", async (ctx) => { - const { - l2TokenBridge, - accounts: { deployer, l1TokenBridgeEOA, recipient }, - stubs: { - l2Messenger, - l1TokenNonRebasable, - l2TokenNonRebasable - }, - } = ctx; - - await pushTokenRate(ctx); - - const l1Gas = wei`1 wei`; - const data = "0xdeadbeaf"; - const recipientBalanceBefore = await l2TokenNonRebasable.balanceOf(recipient.address); - const totalSupplyBefore = await l2TokenNonRebasable.totalSupply(); - - const tx = await l2TokenBridge - .connect(recipient) - .withdraw( - l2TokenNonRebasable.address, - 0, - l1Gas, - data); - - await assert.emits(l2TokenBridge, tx, "WithdrawalInitiated", [ - l1TokenNonRebasable.address, - l2TokenNonRebasable.address, - recipient.address, - recipient.address, - 0, - data, - ]); - - await assert.emits(l2Messenger, tx, "SentMessage", [ - l1TokenBridgeEOA.address, - l2TokenBridge.address, - L1LidoTokensBridge__factory.createInterface().encodeFunctionData( - "finalizeERC20Withdrawal", - [ - l1TokenNonRebasable.address, - l2TokenNonRebasable.address, - recipient.address, - recipient.address, - 0, - data, - ] - ), - 1, // message nonce - l1Gas, - ]); - - assert.equalBN(await l2TokenNonRebasable.balanceOf(recipient.address), recipientBalanceBefore); - assert.equalBN(await l2TokenNonRebasable.totalSupply(), totalSupplyBefore); - }) - - .test("withdrawTo() :: withdrawals disabled", async (ctx) => { - const { - l2TokenBridge, - stubs: { l2TokenNonRebasable, l2TokenRebasable }, - accounts: { recipient }, - } = ctx; - - await ctx.l2TokenBridge.disableWithdrawals(); - - assert.isFalse(await ctx.l2TokenBridge.isWithdrawalsEnabled()); - - await assert.revertsWith( - l2TokenBridge.withdrawTo( - l2TokenNonRebasable.address, - recipient.address, - wei`1 ether`, - wei`1 gwei`, - "0x" - ), - "ErrorWithdrawalsDisabled()" - ); - await assert.revertsWith( - l2TokenBridge.withdrawTo( - l2TokenRebasable.address, - recipient.address, - wei`1 ether`, - wei`1 gwei`, - "0x" - ), - "ErrorWithdrawalsDisabled()" - ); - }) - - .test("withdrawTo() :: unsupported l2Token", async (ctx) => { - const { - l2TokenBridge, - accounts: { stranger, recipient }, - } = ctx; - await assert.revertsWith( - l2TokenBridge.withdrawTo( - stranger.address, - recipient.address, - wei`1 ether`, - wei`1 gwei`, - "0x" - ), - "ErrorUnsupportedL2Token(\""+stranger.address+"\")" - ); - }) - - .test("withdrawTo() :: non rebasable token flow", async (ctx) => { - const { - l2TokenBridge, - accounts: { deployer, recipient, l1TokenBridgeEOA }, - stubs: { - l2Messenger: l2MessengerStub, - l1TokenNonRebasable, - l2TokenNonRebasable - }, - } = ctx; - - const deployerBalanceBefore = await l2TokenNonRebasable.balanceOf(deployer.address); - const totalSupplyBefore = await l2TokenNonRebasable.totalSupply(); - - const amount = wei`1 ether`; - const l1Gas = wei`1 wei`; - const data = "0xdeadbeaf"; - - const tx = await l2TokenBridge.withdrawTo( - l2TokenNonRebasable.address, - recipient.address, - amount, - l1Gas, - data - ); - - await assert.emits(l2TokenBridge, tx, "WithdrawalInitiated", [ - l1TokenNonRebasable.address, - l2TokenNonRebasable.address, - deployer.address, - recipient.address, - amount, - data, - ]); - - await assert.emits(l2MessengerStub, tx, "SentMessage", [ - l1TokenBridgeEOA.address, - l2TokenBridge.address, - L1LidoTokensBridge__factory.createInterface().encodeFunctionData( - "finalizeERC20Withdrawal", - [ - l1TokenNonRebasable.address, - l2TokenNonRebasable.address, - deployer.address, - recipient.address, - amount, - data, - ] - ), - 1, // message nonce - l1Gas, - ]); - - assert.equalBN( - await l2TokenNonRebasable.balanceOf(deployer.address), - deployerBalanceBefore.sub(amount) - ); - - assert.equalBN( - await l2TokenNonRebasable.totalSupply(), - totalSupplyBefore.sub(amount) - ); - }) - - .test("withdrawTo() :: rebasable token flow", async (ctx) => { - - const { - l2TokenBridge, - accounts: { deployer, l1TokenBridgeEOA, l2MessengerStubEOA, recipient }, - stubs: { - l2Messenger, - l1TokenNonRebasable, - l2TokenNonRebasable, - l1TokenRebasable, - l2TokenRebasable - }, - } = ctx; - - const amountToDeposit = wei`1 ether`; - const amountToWithdraw = wei.toBigNumber(amountToDeposit).mul(ctx.exchangeRate).div(ctx.decimalsBN); - const l1Gas = wei`1 wei`; - const data = "0xdeadbeaf"; - const provider = await hre.ethers.provider; - const packedTokenRateAndTimestampData = await packedTokenRateAndTimestamp(provider, ctx.exchangeRate); - - const tx1 = await l2TokenBridge - .connect(l2MessengerStubEOA) - .finalizeDeposit( - l1TokenRebasable.address, - l2TokenRebasable.address, - deployer.address, - deployer.address, - amountToDeposit, - packedTokenRateAndTimestampData - ); - - const deployerBalanceBefore = await l2TokenRebasable.balanceOf(deployer.address); - const totalSupplyBefore = await l2TokenRebasable.totalSupply(); - - const tx = await l2TokenBridge.connect(deployer).withdrawTo( - l2TokenRebasable.address, - recipient.address, - amountToWithdraw, - l1Gas, - data - ); - - await assert.emits(l2TokenBridge, tx, "WithdrawalInitiated", [ - l1TokenRebasable.address, - l2TokenRebasable.address, - deployer.address, - recipient.address, - amountToWithdraw, - data, - ]); - - await assert.emits(l2Messenger, tx, "SentMessage", [ - l1TokenBridgeEOA.address, - l2TokenBridge.address, - L1LidoTokensBridge__factory.createInterface().encodeFunctionData( - "finalizeERC20Withdrawal", - [ - l1TokenRebasable.address, - l2TokenRebasable.address, - deployer.address, - recipient.address, - amountToDeposit, - data, - ] - ), - 1, // message nonce - l1Gas, - ]); - - assert.equalBN( - await l2TokenRebasable.balanceOf(recipient.address), - deployerBalanceBefore.sub(amountToWithdraw) - ); - - assert.equalBN( - await l2TokenRebasable.totalSupply(), - totalSupplyBefore.sub(amountToWithdraw) - ); - }) - - .test("withdrawTo() :: zero rebasable tokens", async (ctx) => { - const { - l2TokenBridge, - accounts: { deployer, l1TokenBridgeEOA, recipient }, - stubs: { - l2Messenger, - l1TokenRebasable, - l2TokenRebasable - }, - } = ctx; - - await pushTokenRate(ctx); - - const l1Gas = wei`1 wei`; - const data = "0xdeadbeaf"; - const recipientBalanceBefore = await l2TokenRebasable.balanceOf(recipient.address); - const totalSupplyBefore = await l2TokenRebasable.totalSupply(); - - const tx = await l2TokenBridge - .connect(recipient) - .withdrawTo( - l2TokenRebasable.address, - recipient.address, - 0, - l1Gas, - data); - - await assert.emits(l2TokenBridge, tx, "WithdrawalInitiated", [ - l1TokenRebasable.address, - l2TokenRebasable.address, - recipient.address, - recipient.address, - 0, - data, - ]); - - await assert.emits(l2Messenger, tx, "SentMessage", [ - l1TokenBridgeEOA.address, - l2TokenBridge.address, - L1LidoTokensBridge__factory.createInterface().encodeFunctionData( - "finalizeERC20Withdrawal", - [ - l1TokenRebasable.address, - l2TokenRebasable.address, - recipient.address, - recipient.address, - 0, - data, - ] - ), - 1, // message nonce - l1Gas, - ]); - - assert.equalBN(await l2TokenRebasable.balanceOf(deployer.address), recipientBalanceBefore); - assert.equalBN(await l2TokenRebasable.totalSupply(), totalSupplyBefore); - }) - - .test("withdrawTo() :: zero non-rebasable tokens", async (ctx) => { - const { - l2TokenBridge, - accounts: { deployer, l1TokenBridgeEOA, recipient }, - stubs: { - l2Messenger, - l1TokenNonRebasable, - l2TokenNonRebasable - }, - } = ctx; - - await pushTokenRate(ctx); - - const l1Gas = wei`1 wei`; - const data = "0xdeadbeaf"; - const recipientBalanceBefore = await l2TokenNonRebasable.balanceOf(recipient.address); - const totalSupplyBefore = await l2TokenNonRebasable.totalSupply(); - - const tx = await l2TokenBridge - .connect(recipient) - .withdrawTo( - l2TokenNonRebasable.address, - recipient.address, - 0, - l1Gas, - data); - - await assert.emits(l2TokenBridge, tx, "WithdrawalInitiated", [ - l1TokenNonRebasable.address, - l2TokenNonRebasable.address, - recipient.address, - recipient.address, - 0, - data, - ]); - - await assert.emits(l2Messenger, tx, "SentMessage", [ - l1TokenBridgeEOA.address, - l2TokenBridge.address, - L1LidoTokensBridge__factory.createInterface().encodeFunctionData( - "finalizeERC20Withdrawal", - [ - l1TokenNonRebasable.address, - l2TokenNonRebasable.address, - recipient.address, - recipient.address, - 0, - data, - ] - ), - 1, // message nonce - l1Gas, - ]); - - assert.equalBN(await l2TokenNonRebasable.balanceOf(recipient.address), recipientBalanceBefore); - assert.equalBN(await l2TokenNonRebasable.totalSupply(), totalSupplyBefore); - }) - - .test("finalizeDeposit() :: deposits disabled", async (ctx) => { - const { - l2TokenBridge, - accounts: { l2MessengerStubEOA, deployer, recipient }, - stubs: { l1TokenNonRebasable, l2TokenNonRebasable, l1TokenRebasable, l2TokenRebasable }, - } = ctx; - - await l2TokenBridge.disableDeposits(); - - assert.isFalse(await l2TokenBridge.isDepositsEnabled()); - - await assert.revertsWith( - l2TokenBridge - .connect(l2MessengerStubEOA) - .finalizeDeposit( - l1TokenNonRebasable.address, - l2TokenNonRebasable.address, - deployer.address, - recipient.address, - wei`1 ether`, - "0x" - ), - "ErrorDepositsDisabled()" - ); - await assert.revertsWith( - l2TokenBridge - .connect(l2MessengerStubEOA) - .finalizeDeposit( - l1TokenRebasable.address, - l2TokenRebasable.address, - deployer.address, - recipient.address, - wei`1 ether`, - "0x" - ), - "ErrorDepositsDisabled()" - ); - }) - - .test("finalizeDeposit() :: unsupported l1Token", async (ctx) => { - const { - l2TokenBridge, - accounts: { l2MessengerStubEOA, deployer, recipient, stranger }, - stubs: { l2TokenNonRebasable, l2TokenRebasable }, - } = ctx; - - await assert.revertsWith( - l2TokenBridge - .connect(l2MessengerStubEOA) - .finalizeDeposit( - stranger.address, - l2TokenNonRebasable.address, - deployer.address, - recipient.address, - wei`1 ether`, - "0x" - ), - "ErrorUnsupportedL1L2TokensPair(\""+stranger.address+"\", \""+l2TokenNonRebasable.address+"\")" - ); - await assert.revertsWith( - l2TokenBridge - .connect(l2MessengerStubEOA) - .finalizeDeposit( - stranger.address, - l2TokenRebasable.address, - deployer.address, - recipient.address, - wei`1 ether`, - "0x" - ), - "ErrorUnsupportedL1L2TokensPair(\""+stranger.address+"\", \""+l2TokenRebasable.address+"\")" - ); - }) - - .test("finalizeDeposit() :: unsupported l2Token", async (ctx) => { - const { - l2TokenBridge, - accounts: { l2MessengerStubEOA, deployer, recipient, stranger }, - stubs: { l1TokenNonRebasable, l1TokenRebasable }, - } = ctx; - - await assert.revertsWith( - l2TokenBridge - .connect(l2MessengerStubEOA) - .finalizeDeposit( - l1TokenNonRebasable.address, - stranger.address, - deployer.address, - recipient.address, - wei`1 ether`, - "0x" - ), - "ErrorUnsupportedL1L2TokensPair(\""+l1TokenNonRebasable.address+"\", \""+stranger.address+"\")" - ); - await assert.revertsWith( - l2TokenBridge - .connect(l2MessengerStubEOA) - .finalizeDeposit( - l1TokenRebasable.address, - stranger.address, - deployer.address, - recipient.address, - wei`1 ether`, - "0x" - ), - "ErrorUnsupportedL1L2TokensPair(\""+l1TokenRebasable.address+"\", \""+stranger.address+"\")" - ); - }) - - .test("finalizeDeposit() :: unsupported tokens combination", async (ctx) => { - const { - l2TokenBridge, - accounts: { l2MessengerStubEOA, deployer, recipient, stranger }, - stubs: { l1TokenNonRebasable, l1TokenRebasable, l2TokenNonRebasable, l2TokenRebasable }, - } = ctx; - - await assert.revertsWith( - l2TokenBridge - .connect(l2MessengerStubEOA) - .finalizeDeposit( - l1TokenNonRebasable.address, - l2TokenRebasable.address, - deployer.address, - recipient.address, - wei`1 ether`, - "0x" - ), - "ErrorUnsupportedL1L2TokensPair(\""+l1TokenNonRebasable.address+"\", \""+l2TokenRebasable.address+"\")" - ); - await assert.revertsWith( - l2TokenBridge - .connect(l2MessengerStubEOA) - .finalizeDeposit( - l1TokenRebasable.address, - l2TokenNonRebasable.address, - deployer.address, - recipient.address, - wei`1 ether`, - "0x" - ), - "ErrorUnsupportedL1L2TokensPair(\""+l1TokenRebasable.address+"\", \""+l2TokenNonRebasable.address+"\")" - ); - }) - - .test("finalizeDeposit() :: unauthorized messenger", async (ctx) => { - const { - l2TokenBridge, - stubs: { l1TokenNonRebasable, l2TokenNonRebasable, l1TokenRebasable, l2TokenRebasable }, - accounts: { deployer, recipient, stranger }, - } = ctx; - - await assert.revertsWith( - l2TokenBridge - .connect(stranger) - .finalizeDeposit( - l1TokenNonRebasable.address, - l2TokenNonRebasable.address, - deployer.address, - recipient.address, - wei`1 ether`, - "0x" - ), - "ErrorUnauthorizedMessenger()" - ); - await assert.revertsWith( - l2TokenBridge - .connect(stranger) - .finalizeDeposit( - l1TokenRebasable.address, - l2TokenRebasable.address, - deployer.address, - recipient.address, - wei`1 ether`, - "0x" - ), - "ErrorUnauthorizedMessenger()" - ); - }) - - .test("finalizeDeposit() :: wrong cross domain sender", async (ctx) => { - const { - l2TokenBridge, - stubs: { l1TokenNonRebasable, l2TokenNonRebasable, l1TokenRebasable, l2TokenRebasable, l2Messenger }, - accounts: { deployer, recipient, stranger, l2MessengerStubEOA }, - } = ctx; - - await l2Messenger.setXDomainMessageSender(stranger.address); - - await assert.revertsWith( - l2TokenBridge - .connect(l2MessengerStubEOA) - .finalizeDeposit( - l1TokenNonRebasable.address, - l2TokenNonRebasable.address, - deployer.address, - recipient.address, - wei`1 ether`, - "0x" - ), - "ErrorWrongCrossDomainSender()" - ); - - await assert.revertsWith( - l2TokenBridge - .connect(l2MessengerStubEOA) - .finalizeDeposit( - l1TokenRebasable.address, - l2TokenRebasable.address, - deployer.address, - recipient.address, - wei`1 ether`, - "0x" - ), - "ErrorWrongCrossDomainSender()" - ); - }) - - .test("finalizeDeposit() :: non-rebasable token flow", async (ctx) => { - const { - l2TokenBridge, - stubs: { l1TokenNonRebasable, l2TokenNonRebasable, l2Messenger }, - accounts: { deployer, recipient, l2MessengerStubEOA, l1TokenBridgeEOA }, - } = ctx; - - await l2Messenger.setXDomainMessageSender(l1TokenBridgeEOA.address); - - const totalSupplyBefore = await l2TokenNonRebasable.totalSupply(); - - const amount = wei`1 ether`; - const data = "0xdeadbeaf"; - const provider = await hre.ethers.provider; - const packedTokenRateAndTimestampData = await packedTokenRateAndTimestamp(provider, ctx.exchangeRate); - const dataToReceive = ethers.utils.hexConcat([packedTokenRateAndTimestampData, data]); - - const tx = await l2TokenBridge - .connect(l2MessengerStubEOA) - .finalizeDeposit( - l1TokenNonRebasable.address, - l2TokenNonRebasable.address, - deployer.address, - recipient.address, - amount, - dataToReceive - ); - - await assert.emits(l2TokenBridge, tx, "DepositFinalized", [ - l1TokenNonRebasable.address, - l2TokenNonRebasable.address, - deployer.address, - recipient.address, - amount, - data, - ]); - - assert.equalBN(await l2TokenNonRebasable.balanceOf(recipient.address), amount); - assert.equalBN(await l2TokenNonRebasable.totalSupply(), totalSupplyBefore.add(amount)); - }) - - .test("finalizeDeposit() :: rebasable token flow", async (ctx) => { - const { - l2TokenBridge, - stubs: { l1TokenRebasable, l2TokenRebasable, l2Messenger }, - accounts: { deployer, recipient, l2MessengerStubEOA, l1TokenBridgeEOA }, - } = ctx; - - await l2Messenger.setXDomainMessageSender(l1TokenBridgeEOA.address); - - const amountToDeposit = wei`1 ether`; - const amountToEmit = wei.toBigNumber(amountToDeposit).mul(ctx.exchangeRate).div(ctx.decimalsBN); - const data = "0xdeadbeaf"; - const provider = await hre.ethers.provider; - const packedTokenRateAndTimestampData = await packedTokenRateAndTimestamp(provider, ctx.exchangeRate); - const dataToReceive = ethers.utils.hexConcat([packedTokenRateAndTimestampData, data]); - - const tx = await l2TokenBridge - .connect(l2MessengerStubEOA) - .finalizeDeposit( - l1TokenRebasable.address, - l2TokenRebasable.address, - deployer.address, - recipient.address, - amountToDeposit, - dataToReceive - ); - - await assert.emits(l2TokenBridge, tx, "DepositFinalized", [ - l1TokenRebasable.address, - l2TokenRebasable.address, - deployer.address, - recipient.address, - amountToEmit, - data, - ]); - - assert.equalBN(await l2TokenRebasable.balanceOf(recipient.address), amountToEmit); - }) - - .run(); + .test("initial state", async (ctx) => { + assert.equal(await ctx.l2TokenBridge.l1TokenBridge(), ctx.accounts.l1TokenBridgeEOA.address); + assert.equal(await ctx.l2TokenBridge.MESSENGER(), ctx.accounts.l2MessengerStubEOA._address); + assert.equal(await ctx.l2TokenBridge.L1_TOKEN_NON_REBASABLE(), ctx.stubs.l1TokenNonRebasable.address); + assert.equal(await ctx.l2TokenBridge.L1_TOKEN_REBASABLE(), ctx.stubs.l1TokenRebasable.address); + assert.equal(await ctx.l2TokenBridge.L2_TOKEN_NON_REBASABLE(), ctx.stubs.l2TokenNonRebasable.address); + assert.equal(await ctx.l2TokenBridge.L2_TOKEN_REBASABLE(), ctx.stubs.l2TokenRebasable.address); + }) + + .test("initialize() :: petrified", async (ctx) => { + const { deployer, l1TokenBridgeEOA } = ctx.accounts; + + const l2TokenBridgeImpl = await getL2TokenBridgeImpl(deployer, l1TokenBridgeEOA); + + const petrifiedVersionMark = hre.ethers.constants.MaxUint256; + assert.equalBN(await l2TokenBridgeImpl.getContractVersion(), petrifiedVersionMark); + + await assert.revertsWith( + l2TokenBridgeImpl.initialize(deployer.address), + "NonZeroContractVersionOnInit()" + ); + }) -async function ctxFactory() { - const [deployer, stranger, recipient, l1TokenBridgeEOA] = - await hre.ethers.getSigners(); - - const decimals = 18; - const decimalsBN = BigNumber.from(10).pow(decimals); - const exchangeRate = BigNumber.from('1164454276599657236') - - const l2MessengerStub = await new CrossDomainMessengerStub__factory( - deployer - ).deploy({ value: wei.toBigNumber(wei`1 ether`) }); - const l2MessengerStubEOA = await testing.impersonate(l2MessengerStub.address); - await l2MessengerStub.setXDomainMessageSender(l1TokenBridgeEOA.address); - - const emptyContract = await new EmptyContractStub__factory(deployer).deploy({ - value: wei.toBigNumber(wei`1 ether`), - }); - const emptyContractEOA = await testing.impersonate(emptyContract.address); - - const [ - , - , - , - , - , - , - , - l2TokenBridgeProxyAddress - ] = await predictAddresses(deployer, 8); - - const l1TokenRebasableStub = await new ERC20BridgedStub__factory(deployer).deploy( - "L1 Token Rebasable", - "L1R" + .test("initialize() :: don't allow to initialize twice", async (ctx) => { + const { deployer, l1TokenBridgeEOA } = ctx.accounts; + + const l1LidoTokensBridgeImpl = await getL2TokenBridgeImpl(deployer, l1TokenBridgeEOA); + + const l1TokenBridgeProxy = await new OssifiableProxy__factory( + deployer + ).deploy( + l1LidoTokensBridgeImpl.address, + deployer.address, + l1LidoTokensBridgeImpl.interface.encodeFunctionData("initialize", [ + deployer.address + ]) + ); + + const l1TokenBridge = L1LidoTokensBridge__factory.connect( + l1TokenBridgeProxy.address, + deployer + ); + + assert.equalBN(await l1TokenBridge.getContractVersion(), 2); + + await assert.revertsWith( + l1TokenBridge.initialize(deployer.address), + "NonZeroContractVersionOnInit()" + ); + }) + + .test("finalizeUpgrade_v2() :: bridging manager uninitialized", async (ctx) => { + const { deployer, l1TokenBridgeEOA } = ctx.accounts; + + const l1LidoTokensBridgeImpl = await getL2TokenBridgeImpl(deployer, l1TokenBridgeEOA); + + await assert.revertsWith(new OssifiableProxy__factory(deployer).deploy( + l1LidoTokensBridgeImpl.address, + deployer.address, + L1LidoTokensBridge__factory.createInterface().encodeFunctionData("finalizeUpgrade_v2") + ), "ErrorBridgingManagerIsNotInitialized()"); + }) + + .test("finalizeUpgrade_v2() :: bridging manager initialized", async (ctx) => { + const { deployer, l1TokenBridgeEOA } = ctx.accounts; + + const bridgingManagerImpl = await new BridgingManagerStub__factory(deployer).deploy(); + const proxy = await new OssifiableProxy__factory(deployer).deploy( + bridgingManagerImpl.address, + deployer.address, + BridgingManagerStub__factory.createInterface().encodeFunctionData("initialize", [ + deployer.address + ]) ); - const l1TokenNonRebasableStub = await new ERC20WrapperStub__factory(deployer).deploy( - l1TokenRebasableStub.address, - "L1 Token Non Rebasable", - "L1NR", - exchangeRate + const l1LidoTokensBridgeImpl = await getL2TokenBridgeImpl(deployer, l1TokenBridgeEOA); + await proxy.proxy__upgradeToAndCall( + l1LidoTokensBridgeImpl.address, + L1LidoTokensBridge__factory.createInterface().encodeFunctionData("finalizeUpgrade_v2"), + false ); - const l2TokenNonRebasableStub = await new ERC20BridgedStub__factory(deployer).deploy( - "L2 Token Non Rebasable", - "L2NR" + const l1LidoTokensBridgeProxied = L1LidoTokensBridge__factory.connect( + proxy.address, + deployer + ); + + assert.equalBN(await l1LidoTokensBridgeProxied.getContractVersion(), 2); + }) + + .test("withdraw() :: withdrawals disabled", async (ctx) => { + const { + l2TokenBridge, + stubs: { l2TokenNonRebasable, l2TokenRebasable }, + } = ctx; + + await ctx.l2TokenBridge.disableWithdrawals(); + + assert.isFalse(await ctx.l2TokenBridge.isWithdrawalsEnabled()); + + await assert.revertsWith( + l2TokenBridge.withdraw( + l2TokenNonRebasable.address, + wei`1 ether`, + wei`1 gwei`, + "0x" + ), + "ErrorWithdrawalsDisabled()" ); - const tokenRateOracleImpl = await new TokenRateOracle__factory(deployer).deploy( - l2MessengerStub.address, - l2TokenBridgeProxyAddress, - l1TokenBridgeEOA.address, - 86400, - 86400, - 500 + await assert.revertsWith( + l2TokenBridge.withdraw( + l2TokenRebasable.address, + wei`1 ether`, + wei`1 gwei`, + "0x" + ), + "ErrorWithdrawalsDisabled()" + ); + }) + + .test("withdraw() :: unsupported l2Token", async (ctx) => { + const { + l2TokenBridge, + accounts: { stranger }, + } = ctx; + await assert.revertsWith( + l2TokenBridge.withdraw(stranger.address, wei`1 ether`, wei`1 gwei`, "0x"), + "ErrorUnsupportedL2Token(\"" + stranger.address + "\")" + ); + }) + + .test("withdraw() :: not from EOA", async (ctx) => { + const { + l2TokenBridge, + accounts: { emptyContractEOA }, + stubs: { l2TokenRebasable, l2TokenNonRebasable }, + } = ctx; + + await assert.revertsWith( + l2TokenBridge + .connect(emptyContractEOA) + .withdraw( + l2TokenNonRebasable.address, + wei`1 ether`, + wei`1 gwei`, + "0x" + ), + "ErrorSenderNotEOA()" + ); + await assert.revertsWith( + l2TokenBridge + .connect(emptyContractEOA) + .withdraw( + l2TokenRebasable.address, + wei`1 ether`, + wei`1 gwei`, + "0x" + ), + "ErrorSenderNotEOA()" ); + }) + + .test("withdraw() :: non-rebasable token flow", async (ctx) => { + const { + l2TokenBridge, + accounts: { deployer, l1TokenBridgeEOA }, + stubs: { + l2Messenger, + l1TokenNonRebasable, + l2TokenNonRebasable, + }, + } = ctx; + + const deployerBalanceBefore = await l2TokenNonRebasable.balanceOf(deployer.address); + const totalSupplyBefore = await l2TokenNonRebasable.totalSupply(); + + const amount = wei`1 ether`; + const l1Gas = wei`1 wei`; + const data = "0xdeadbeaf"; + + const tx = await l2TokenBridge.withdraw( + l2TokenNonRebasable.address, + amount, + l1Gas, + data + ); + + await assert.emits(l2TokenBridge, tx, "WithdrawalInitiated", [ + l1TokenNonRebasable.address, + l2TokenNonRebasable.address, + deployer.address, + deployer.address, + amount, + data, + ]); + await assert.emits(l2Messenger, tx, "SentMessage", [ + l1TokenBridgeEOA.address, + l2TokenBridge.address, + L1LidoTokensBridge__factory.createInterface().encodeFunctionData( + "finalizeERC20Withdrawal", + [ + l1TokenNonRebasable.address, + l2TokenNonRebasable.address, + deployer.address, + deployer.address, + amount, + data, + ] + ), + 1, // message nonce + l1Gas, + ]); + + assert.equalBN( + await l2TokenNonRebasable.balanceOf(deployer.address), + deployerBalanceBefore.sub(amount) + ); + + assert.equalBN( + await l2TokenNonRebasable.totalSupply(), + totalSupplyBefore.sub(amount) + ); + }) + + .test("withdraw() :: rebasable token flow", async (ctx) => { + const { + l2TokenBridge, + accounts: { deployer, l1TokenBridgeEOA, l2MessengerStubEOA, recipient }, + stubs: { + l2Messenger, + l1TokenRebasable, + l2TokenRebasable + }, + } = ctx; + + const amountToDeposit = wei`1 ether`; + const amountToWithdraw = wei.toBigNumber(amountToDeposit).mul(ctx.exchangeRate).div(ctx.decimalsBN); + const l1Gas = wei`1 wei`; + const data = "0xdeadbeaf"; const provider = await hre.ethers.provider; - const blockNumber = await provider.getBlockNumber(); - const blockTimestamp = (await provider.getBlock(blockNumber)).timestamp; - const tokenRateOracleProxy = await new OssifiableProxy__factory( - deployer - ).deploy( - tokenRateOracleImpl.address, + const packedTokenRateAndTimestampData = await packedTokenRateAndTimestamp(provider, ctx.exchangeRate); + + const tx1 = await l2TokenBridge + .connect(l2MessengerStubEOA) + .finalizeDeposit( + l1TokenRebasable.address, + l2TokenRebasable.address, deployer.address, - tokenRateOracleImpl.interface.encodeFunctionData("initialize", [ - exchangeRate, - blockTimestamp - ]) + recipient.address, + amountToDeposit, + packedTokenRateAndTimestampData + ); + + const recipientBalanceBefore = await l2TokenRebasable.balanceOf(recipient.address); + const totalSupplyBefore = await l2TokenRebasable.totalSupply(); + + const tx = await l2TokenBridge.connect(recipient).withdraw( + l2TokenRebasable.address, + amountToWithdraw, + l1Gas, + data + ); + + await assert.emits(l2TokenBridge, tx, "WithdrawalInitiated", [ + l1TokenRebasable.address, + l2TokenRebasable.address, + recipient.address, + recipient.address, + amountToWithdraw, + data, + ]); + + await assert.emits(l2Messenger, tx, "SentMessage", [ + l1TokenBridgeEOA.address, + l2TokenBridge.address, + L1LidoTokensBridge__factory.createInterface().encodeFunctionData( + "finalizeERC20Withdrawal", + [ + l1TokenRebasable.address, + l2TokenRebasable.address, + recipient.address, + recipient.address, + amountToDeposit, + data, + ] + ), + 1, // message nonce + l1Gas, + ]); + + assert.equalBN( + await l2TokenRebasable.balanceOf(deployer.address), + recipientBalanceBefore.sub(amountToWithdraw) ); - const tokenRateOracle = TokenRateOracle__factory.connect( - tokenRateOracleProxy.address, - deployer + assert.equalBN( + await l2TokenRebasable.totalSupply(), + totalSupplyBefore.sub(amountToWithdraw) ); + }) + + .test("withdraw() :: zero rebasable tokens", async (ctx) => { + const { + l2TokenBridge, + accounts: { deployer, l1TokenBridgeEOA, recipient }, + stubs: { + l2Messenger, + l1TokenRebasable, + l2TokenRebasable + }, + } = ctx; + + await pushTokenRate(ctx); + + const l1Gas = wei`1 wei`; + const data = "0xdeadbeaf"; + const recipientBalanceBefore = await l2TokenRebasable.balanceOf(recipient.address); + const totalSupplyBefore = await l2TokenRebasable.totalSupply(); + + const tx = await l2TokenBridge + .connect(recipient) + .withdraw( + l2TokenRebasable.address, + 0, + l1Gas, + data); + + await assert.emits(l2TokenBridge, tx, "WithdrawalInitiated", [ + l1TokenRebasable.address, + l2TokenRebasable.address, + recipient.address, + recipient.address, + 0, + data, + ]); + await assert.emits(l2Messenger, tx, "SentMessage", [ + l1TokenBridgeEOA.address, + l2TokenBridge.address, + L1LidoTokensBridge__factory.createInterface().encodeFunctionData( + "finalizeERC20Withdrawal", + [ + l1TokenRebasable.address, + l2TokenRebasable.address, + recipient.address, + recipient.address, + 0, + data, + ] + ), + 1, // message nonce + l1Gas, + ]); + + assert.equalBN(await l2TokenRebasable.balanceOf(deployer.address), recipientBalanceBefore); + assert.equalBN(await l2TokenRebasable.totalSupply(), totalSupplyBefore); + }) + + .test("withdraw() :: zero non-rebasable tokens", async (ctx) => { + const { + l2TokenBridge, + accounts: { deployer, l1TokenBridgeEOA, recipient }, + stubs: { + l2Messenger, + l1TokenNonRebasable, + l2TokenNonRebasable + }, + } = ctx; + + await pushTokenRate(ctx); + + const l1Gas = wei`1 wei`; + const data = "0xdeadbeaf"; + const recipientBalanceBefore = await l2TokenNonRebasable.balanceOf(recipient.address); + const totalSupplyBefore = await l2TokenNonRebasable.totalSupply(); + + const tx = await l2TokenBridge + .connect(recipient) + .withdraw( + l2TokenNonRebasable.address, + 0, + l1Gas, + data); + + await assert.emits(l2TokenBridge, tx, "WithdrawalInitiated", [ + l1TokenNonRebasable.address, + l2TokenNonRebasable.address, + recipient.address, + recipient.address, + 0, + data, + ]); - const l2TokenRebasableStub = await new ERC20RebasableBridgedPermit__factory(deployer).deploy( - "L2 Token Rebasable", - "L2R", - "1", - decimals, - l2TokenNonRebasableStub.address, - tokenRateOracle.address, - l2TokenBridgeProxyAddress + await assert.emits(l2Messenger, tx, "SentMessage", [ + l1TokenBridgeEOA.address, + l2TokenBridge.address, + L1LidoTokensBridge__factory.createInterface().encodeFunctionData( + "finalizeERC20Withdrawal", + [ + l1TokenNonRebasable.address, + l2TokenNonRebasable.address, + recipient.address, + recipient.address, + 0, + data, + ] + ), + 1, // message nonce + l1Gas, + ]); + + assert.equalBN(await l2TokenNonRebasable.balanceOf(recipient.address), recipientBalanceBefore); + assert.equalBN(await l2TokenNonRebasable.totalSupply(), totalSupplyBefore); + }) + + .test("withdrawTo() :: withdrawals disabled", async (ctx) => { + const { + l2TokenBridge, + stubs: { l2TokenNonRebasable, l2TokenRebasable }, + accounts: { recipient }, + } = ctx; + + await ctx.l2TokenBridge.disableWithdrawals(); + + assert.isFalse(await ctx.l2TokenBridge.isWithdrawalsEnabled()); + + await assert.revertsWith( + l2TokenBridge.withdrawTo( + l2TokenNonRebasable.address, + recipient.address, + wei`1 ether`, + wei`1 gwei`, + "0x" + ), + "ErrorWithdrawalsDisabled()" + ); + await assert.revertsWith( + l2TokenBridge.withdrawTo( + l2TokenRebasable.address, + recipient.address, + wei`1 ether`, + wei`1 gwei`, + "0x" + ), + "ErrorWithdrawalsDisabled()" + ); + }) + + .test("withdrawTo() :: unsupported l2Token", async (ctx) => { + const { + l2TokenBridge, + accounts: { stranger, recipient }, + } = ctx; + await assert.revertsWith( + l2TokenBridge.withdrawTo( + stranger.address, + recipient.address, + wei`1 ether`, + wei`1 gwei`, + "0x" + ), + "ErrorUnsupportedL2Token(\"" + stranger.address + "\")" + ); + }) + + .test("withdrawTo() :: non rebasable token flow", async (ctx) => { + const { + l2TokenBridge, + accounts: { deployer, recipient, l1TokenBridgeEOA }, + stubs: { + l2Messenger: l2MessengerStub, + l1TokenNonRebasable, + l2TokenNonRebasable + }, + } = ctx; + + const deployerBalanceBefore = await l2TokenNonRebasable.balanceOf(deployer.address); + const totalSupplyBefore = await l2TokenNonRebasable.totalSupply(); + + const amount = wei`1 ether`; + const l1Gas = wei`1 wei`; + const data = "0xdeadbeaf"; + + const tx = await l2TokenBridge.withdrawTo( + l2TokenNonRebasable.address, + recipient.address, + amount, + l1Gas, + data ); - const l2TokenBridgeImpl = await new L2ERC20ExtendedTokensBridge__factory( - deployer - ).deploy( - l2MessengerStub.address, - l1TokenBridgeEOA.address, - l1TokenNonRebasableStub.address, - l1TokenRebasableStub.address, - l2TokenNonRebasableStub.address, - l2TokenRebasableStub.address + await assert.emits(l2TokenBridge, tx, "WithdrawalInitiated", [ + l1TokenNonRebasable.address, + l2TokenNonRebasable.address, + deployer.address, + recipient.address, + amount, + data, + ]); + + await assert.emits(l2MessengerStub, tx, "SentMessage", [ + l1TokenBridgeEOA.address, + l2TokenBridge.address, + L1LidoTokensBridge__factory.createInterface().encodeFunctionData( + "finalizeERC20Withdrawal", + [ + l1TokenNonRebasable.address, + l2TokenNonRebasable.address, + deployer.address, + recipient.address, + amount, + data, + ] + ), + 1, // message nonce + l1Gas, + ]); + + assert.equalBN( + await l2TokenNonRebasable.balanceOf(deployer.address), + deployerBalanceBefore.sub(amount) ); - const l2TokenBridgeProxy = await new OssifiableProxy__factory( - deployer - ).deploy( - l2TokenBridgeImpl.address, + assert.equalBN( + await l2TokenNonRebasable.totalSupply(), + totalSupplyBefore.sub(amount) + ); + }) + + .test("withdrawTo() :: rebasable token flow", async (ctx) => { + + const { + l2TokenBridge, + accounts: { deployer, l1TokenBridgeEOA, l2MessengerStubEOA, recipient }, + stubs: { + l2Messenger, + l1TokenNonRebasable, + l2TokenNonRebasable, + l1TokenRebasable, + l2TokenRebasable + }, + } = ctx; + + const amountToDeposit = wei`1 ether`; + const amountToWithdraw = wei.toBigNumber(amountToDeposit).mul(ctx.exchangeRate).div(ctx.decimalsBN); + const l1Gas = wei`1 wei`; + const data = "0xdeadbeaf"; + const provider = await hre.ethers.provider; + const packedTokenRateAndTimestampData = await packedTokenRateAndTimestamp(provider, ctx.exchangeRate); + + const tx1 = await l2TokenBridge + .connect(l2MessengerStubEOA) + .finalizeDeposit( + l1TokenRebasable.address, + l2TokenRebasable.address, + deployer.address, deployer.address, - l2TokenBridgeImpl.interface.encodeFunctionData("initialize", [ - deployer.address, - ]) + amountToDeposit, + packedTokenRateAndTimestampData + ); + + const deployerBalanceBefore = await l2TokenRebasable.balanceOf(deployer.address); + const totalSupplyBefore = await l2TokenRebasable.totalSupply(); + + const tx = await l2TokenBridge.connect(deployer).withdrawTo( + l2TokenRebasable.address, + recipient.address, + amountToWithdraw, + l1Gas, + data ); - const l2TokenBridge = L2ERC20ExtendedTokensBridge__factory.connect( - l2TokenBridgeProxy.address, - deployer + await assert.emits(l2TokenBridge, tx, "WithdrawalInitiated", [ + l1TokenRebasable.address, + l2TokenRebasable.address, + deployer.address, + recipient.address, + amountToWithdraw, + data, + ]); + + await assert.emits(l2Messenger, tx, "SentMessage", [ + l1TokenBridgeEOA.address, + l2TokenBridge.address, + L1LidoTokensBridge__factory.createInterface().encodeFunctionData( + "finalizeERC20Withdrawal", + [ + l1TokenRebasable.address, + l2TokenRebasable.address, + deployer.address, + recipient.address, + amountToDeposit, + data, + ] + ), + 1, // message nonce + l1Gas, + ]); + + assert.equalBN( + await l2TokenRebasable.balanceOf(recipient.address), + deployerBalanceBefore.sub(amountToWithdraw) + ); + + assert.equalBN( + await l2TokenRebasable.totalSupply(), + totalSupplyBefore.sub(amountToWithdraw) + ); + }) + + .test("withdrawTo() :: zero rebasable tokens", async (ctx) => { + const { + l2TokenBridge, + accounts: { deployer, l1TokenBridgeEOA, recipient }, + stubs: { + l2Messenger, + l1TokenRebasable, + l2TokenRebasable + }, + } = ctx; + + await pushTokenRate(ctx); + + const l1Gas = wei`1 wei`; + const data = "0xdeadbeaf"; + const recipientBalanceBefore = await l2TokenRebasable.balanceOf(recipient.address); + const totalSupplyBefore = await l2TokenRebasable.totalSupply(); + + const tx = await l2TokenBridge + .connect(recipient) + .withdrawTo( + l2TokenRebasable.address, + recipient.address, + 0, + l1Gas, + data); + + await assert.emits(l2TokenBridge, tx, "WithdrawalInitiated", [ + l1TokenRebasable.address, + l2TokenRebasable.address, + recipient.address, + recipient.address, + 0, + data, + ]); + + await assert.emits(l2Messenger, tx, "SentMessage", [ + l1TokenBridgeEOA.address, + l2TokenBridge.address, + L1LidoTokensBridge__factory.createInterface().encodeFunctionData( + "finalizeERC20Withdrawal", + [ + l1TokenRebasable.address, + l2TokenRebasable.address, + recipient.address, + recipient.address, + 0, + data, + ] + ), + 1, // message nonce + l1Gas, + ]); + + assert.equalBN(await l2TokenRebasable.balanceOf(deployer.address), recipientBalanceBefore); + assert.equalBN(await l2TokenRebasable.totalSupply(), totalSupplyBefore); + }) + + .test("withdrawTo() :: zero non-rebasable tokens", async (ctx) => { + const { + l2TokenBridge, + accounts: { deployer, l1TokenBridgeEOA, recipient }, + stubs: { + l2Messenger, + l1TokenNonRebasable, + l2TokenNonRebasable + }, + } = ctx; + + await pushTokenRate(ctx); + + const l1Gas = wei`1 wei`; + const data = "0xdeadbeaf"; + const recipientBalanceBefore = await l2TokenNonRebasable.balanceOf(recipient.address); + const totalSupplyBefore = await l2TokenNonRebasable.totalSupply(); + + const tx = await l2TokenBridge + .connect(recipient) + .withdrawTo( + l2TokenNonRebasable.address, + recipient.address, + 0, + l1Gas, + data); + + await assert.emits(l2TokenBridge, tx, "WithdrawalInitiated", [ + l1TokenNonRebasable.address, + l2TokenNonRebasable.address, + recipient.address, + recipient.address, + 0, + data, + ]); + + await assert.emits(l2Messenger, tx, "SentMessage", [ + l1TokenBridgeEOA.address, + l2TokenBridge.address, + L1LidoTokensBridge__factory.createInterface().encodeFunctionData( + "finalizeERC20Withdrawal", + [ + l1TokenNonRebasable.address, + l2TokenNonRebasable.address, + recipient.address, + recipient.address, + 0, + data, + ] + ), + 1, // message nonce + l1Gas, + ]); + + assert.equalBN(await l2TokenNonRebasable.balanceOf(recipient.address), recipientBalanceBefore); + assert.equalBN(await l2TokenNonRebasable.totalSupply(), totalSupplyBefore); + }) + + .test("finalizeDeposit() :: deposits disabled", async (ctx) => { + const { + l2TokenBridge, + accounts: { l2MessengerStubEOA, deployer, recipient }, + stubs: { l1TokenNonRebasable, l2TokenNonRebasable, l1TokenRebasable, l2TokenRebasable }, + } = ctx; + + await l2TokenBridge.disableDeposits(); + + assert.isFalse(await l2TokenBridge.isDepositsEnabled()); + + await assert.revertsWith( + l2TokenBridge + .connect(l2MessengerStubEOA) + .finalizeDeposit( + l1TokenNonRebasable.address, + l2TokenNonRebasable.address, + deployer.address, + recipient.address, + wei`1 ether`, + "0x" + ), + "ErrorDepositsDisabled()" + ); + await assert.revertsWith( + l2TokenBridge + .connect(l2MessengerStubEOA) + .finalizeDeposit( + l1TokenRebasable.address, + l2TokenRebasable.address, + deployer.address, + recipient.address, + wei`1 ether`, + "0x" + ), + "ErrorDepositsDisabled()" + ); + }) + + .test("finalizeDeposit() :: unsupported l1Token", async (ctx) => { + const { + l2TokenBridge, + accounts: { l2MessengerStubEOA, deployer, recipient, stranger }, + stubs: { l2TokenNonRebasable, l2TokenRebasable }, + } = ctx; + + await assert.revertsWith( + l2TokenBridge + .connect(l2MessengerStubEOA) + .finalizeDeposit( + stranger.address, + l2TokenNonRebasable.address, + deployer.address, + recipient.address, + wei`1 ether`, + "0x" + ), + "ErrorUnsupportedL1L2TokensPair(\"" + stranger.address + "\", \"" + l2TokenNonRebasable.address + "\")" + ); + await assert.revertsWith( + l2TokenBridge + .connect(l2MessengerStubEOA) + .finalizeDeposit( + stranger.address, + l2TokenRebasable.address, + deployer.address, + recipient.address, + wei`1 ether`, + "0x" + ), + "ErrorUnsupportedL1L2TokensPair(\"" + stranger.address + "\", \"" + l2TokenRebasable.address + "\")" + ); + }) + + .test("finalizeDeposit() :: unsupported l2Token", async (ctx) => { + const { + l2TokenBridge, + accounts: { l2MessengerStubEOA, deployer, recipient, stranger }, + stubs: { l1TokenNonRebasable, l1TokenRebasable }, + } = ctx; + + await assert.revertsWith( + l2TokenBridge + .connect(l2MessengerStubEOA) + .finalizeDeposit( + l1TokenNonRebasable.address, + stranger.address, + deployer.address, + recipient.address, + wei`1 ether`, + "0x" + ), + "ErrorUnsupportedL1L2TokensPair(\"" + l1TokenNonRebasable.address + "\", \"" + stranger.address + "\")" + ); + await assert.revertsWith( + l2TokenBridge + .connect(l2MessengerStubEOA) + .finalizeDeposit( + l1TokenRebasable.address, + stranger.address, + deployer.address, + recipient.address, + wei`1 ether`, + "0x" + ), + "ErrorUnsupportedL1L2TokensPair(\"" + l1TokenRebasable.address + "\", \"" + stranger.address + "\")" + ); + }) + + .test("finalizeDeposit() :: unsupported tokens combination", async (ctx) => { + const { + l2TokenBridge, + accounts: { l2MessengerStubEOA, deployer, recipient, stranger }, + stubs: { l1TokenNonRebasable, l1TokenRebasable, l2TokenNonRebasable, l2TokenRebasable }, + } = ctx; + + await assert.revertsWith( + l2TokenBridge + .connect(l2MessengerStubEOA) + .finalizeDeposit( + l1TokenNonRebasable.address, + l2TokenRebasable.address, + deployer.address, + recipient.address, + wei`1 ether`, + "0x" + ), + "ErrorUnsupportedL1L2TokensPair(\"" + l1TokenNonRebasable.address + "\", \"" + l2TokenRebasable.address + "\")" + ); + await assert.revertsWith( + l2TokenBridge + .connect(l2MessengerStubEOA) + .finalizeDeposit( + l1TokenRebasable.address, + l2TokenNonRebasable.address, + deployer.address, + recipient.address, + wei`1 ether`, + "0x" + ), + "ErrorUnsupportedL1L2TokensPair(\"" + l1TokenRebasable.address + "\", \"" + l2TokenNonRebasable.address + "\")" + ); + }) + + .test("finalizeDeposit() :: unauthorized messenger", async (ctx) => { + const { + l2TokenBridge, + stubs: { l1TokenNonRebasable, l2TokenNonRebasable, l1TokenRebasable, l2TokenRebasable }, + accounts: { deployer, recipient, stranger }, + } = ctx; + + await assert.revertsWith( + l2TokenBridge + .connect(stranger) + .finalizeDeposit( + l1TokenNonRebasable.address, + l2TokenNonRebasable.address, + deployer.address, + recipient.address, + wei`1 ether`, + "0x" + ), + "ErrorUnauthorizedMessenger()" + ); + await assert.revertsWith( + l2TokenBridge + .connect(stranger) + .finalizeDeposit( + l1TokenRebasable.address, + l2TokenRebasable.address, + deployer.address, + recipient.address, + wei`1 ether`, + "0x" + ), + "ErrorUnauthorizedMessenger()" + ); + }) + + .test("finalizeDeposit() :: wrong cross domain sender", async (ctx) => { + const { + l2TokenBridge, + stubs: { l1TokenNonRebasable, l2TokenNonRebasable, l1TokenRebasable, l2TokenRebasable, l2Messenger }, + accounts: { deployer, recipient, stranger, l2MessengerStubEOA }, + } = ctx; + + await l2Messenger.setXDomainMessageSender(stranger.address); + + await assert.revertsWith( + l2TokenBridge + .connect(l2MessengerStubEOA) + .finalizeDeposit( + l1TokenNonRebasable.address, + l2TokenNonRebasable.address, + deployer.address, + recipient.address, + wei`1 ether`, + "0x" + ), + "ErrorWrongCrossDomainSender()" + ); + + await assert.revertsWith( + l2TokenBridge + .connect(l2MessengerStubEOA) + .finalizeDeposit( + l1TokenRebasable.address, + l2TokenRebasable.address, + deployer.address, + recipient.address, + wei`1 ether`, + "0x" + ), + "ErrorWrongCrossDomainSender()" ); + }) - const roles = await Promise.all([ - l2TokenBridge.DEPOSITS_ENABLER_ROLE(), - l2TokenBridge.DEPOSITS_DISABLER_ROLE(), - l2TokenBridge.WITHDRAWALS_ENABLER_ROLE(), - l2TokenBridge.WITHDRAWALS_DISABLER_ROLE(), + .test("finalizeDeposit() :: non-rebasable token flow", async (ctx) => { + const { + l2TokenBridge, + stubs: { l1TokenNonRebasable, l2TokenNonRebasable, l2Messenger }, + accounts: { deployer, recipient, l2MessengerStubEOA, l1TokenBridgeEOA }, + } = ctx; + + await l2Messenger.setXDomainMessageSender(l1TokenBridgeEOA.address); + + const totalSupplyBefore = await l2TokenNonRebasable.totalSupply(); + + const amount = wei`1 ether`; + const data = "0xdeadbeaf"; + const provider = await hre.ethers.provider; + const packedTokenRateAndTimestampData = await packedTokenRateAndTimestamp(provider, ctx.exchangeRate); + const dataToReceive = ethers.utils.hexConcat([packedTokenRateAndTimestampData, data]); + + const tx = await l2TokenBridge + .connect(l2MessengerStubEOA) + .finalizeDeposit( + l1TokenNonRebasable.address, + l2TokenNonRebasable.address, + deployer.address, + recipient.address, + amount, + dataToReceive + ); + + await assert.emits(l2TokenBridge, tx, "DepositFinalized", [ + l1TokenNonRebasable.address, + l2TokenNonRebasable.address, + deployer.address, + recipient.address, + amount, + data, ]); - for (const role of roles) { - await l2TokenBridge.grantRole(role, deployer.address); - } - - await l2TokenBridge.enableDeposits(); - await l2TokenBridge.enableWithdrawals(); - - return { - stubs: { - l1TokenNonRebasable: l1TokenNonRebasableStub, - l1TokenRebasable: l1TokenRebasableStub, - l2TokenNonRebasable: l2TokenNonRebasableStub, - l2TokenRebasable: l2TokenRebasableStub, - l2Messenger: l2MessengerStub, - }, - accounts: { - deployer, - stranger, - recipient, - l2MessengerStubEOA, - emptyContractEOA, - l1TokenBridgeEOA, - }, - l2TokenBridge, - exchangeRate, - decimalsBN - }; + assert.equalBN(await l2TokenNonRebasable.balanceOf(recipient.address), amount); + assert.equalBN(await l2TokenNonRebasable.totalSupply(), totalSupplyBefore.add(amount)); + }) + + .test("finalizeDeposit() :: rebasable token flow", async (ctx) => { + const { + l2TokenBridge, + stubs: { l1TokenRebasable, l2TokenRebasable, l2Messenger }, + accounts: { deployer, recipient, l2MessengerStubEOA, l1TokenBridgeEOA }, + } = ctx; + + await l2Messenger.setXDomainMessageSender(l1TokenBridgeEOA.address); + + const amountToDeposit = wei`1 ether`; + const amountToEmit = wei.toBigNumber(amountToDeposit).mul(ctx.exchangeRate).div(ctx.decimalsBN); + const data = "0xdeadbeaf"; + const provider = await hre.ethers.provider; + const packedTokenRateAndTimestampData = await packedTokenRateAndTimestamp(provider, ctx.exchangeRate); + const dataToReceive = ethers.utils.hexConcat([packedTokenRateAndTimestampData, data]); + + const tx = await l2TokenBridge + .connect(l2MessengerStubEOA) + .finalizeDeposit( + l1TokenRebasable.address, + l2TokenRebasable.address, + deployer.address, + recipient.address, + amountToDeposit, + dataToReceive + ); + + await assert.emits(l2TokenBridge, tx, "DepositFinalized", [ + l1TokenRebasable.address, + l2TokenRebasable.address, + deployer.address, + recipient.address, + amountToEmit, + data, + ]); + + assert.equalBN(await l2TokenRebasable.balanceOf(recipient.address), amountToEmit); + }) + + .run(); + +async function ctxFactory() { + const [deployer, stranger, recipient, l1TokenBridgeEOA] = + await hre.ethers.getSigners(); + + const decimals = 18; + const decimalsBN = BigNumber.from(10).pow(decimals); + const exchangeRate = BigNumber.from('1164454276599657236') + + const l2MessengerStub = await new CrossDomainMessengerStub__factory( + deployer + ).deploy({ value: wei.toBigNumber(wei`1 ether`) }); + const l2MessengerStubEOA = await testing.impersonate(l2MessengerStub.address); + await l2MessengerStub.setXDomainMessageSender(l1TokenBridgeEOA.address); + + const emptyContract = await new EmptyContractStub__factory(deployer).deploy({ + value: wei.toBigNumber(wei`1 ether`), + }); + const emptyContractEOA = await testing.impersonate(emptyContract.address); + + const [ + , + , + , + , + , + , + , + l2TokenBridgeProxyAddress + ] = await predictAddresses(deployer, 8); + + const l1TokenRebasableStub = await new ERC20BridgedStub__factory(deployer).deploy( + "L1 Token Rebasable", + "L1R" + ); + + const l1TokenNonRebasableStub = await new ERC20WrapperStub__factory(deployer).deploy( + l1TokenRebasableStub.address, + "L1 Token Non Rebasable", + "L1NR", + exchangeRate + ); + + const l2TokenNonRebasableStub = await new ERC20BridgedStub__factory(deployer).deploy( + "L2 Token Non Rebasable", + "L2NR" + ); + + const tokenRateOracleImpl = await new TokenRateOracle__factory(deployer).deploy( + l2MessengerStub.address, + l2TokenBridgeProxyAddress, + l1TokenBridgeEOA.address, + 86400, + 86400, + 500 + ); + + const provider = await hre.ethers.provider; + const blockNumber = await provider.getBlockNumber(); + const blockTimestamp = (await provider.getBlock(blockNumber)).timestamp; + const tokenRateOracleProxy = await new OssifiableProxy__factory( + deployer + ).deploy( + tokenRateOracleImpl.address, + deployer.address, + tokenRateOracleImpl.interface.encodeFunctionData("initialize", [ + exchangeRate, + blockTimestamp + ]) + ); + + const tokenRateOracle = TokenRateOracle__factory.connect( + tokenRateOracleProxy.address, + deployer + ); + + const l2TokenRebasableStub = await new ERC20RebasableBridgedPermit__factory(deployer).deploy( + "L2 Token Rebasable", + "L2R", + "1", + decimals, + l2TokenNonRebasableStub.address, + tokenRateOracle.address, + l2TokenBridgeProxyAddress + ); + + const l2TokenBridgeImpl = await new L2ERC20ExtendedTokensBridge__factory( + deployer + ).deploy( + l2MessengerStub.address, + l1TokenBridgeEOA.address, + l1TokenNonRebasableStub.address, + l1TokenRebasableStub.address, + l2TokenNonRebasableStub.address, + l2TokenRebasableStub.address + ); + + const l2TokenBridgeProxy = await new OssifiableProxy__factory( + deployer + ).deploy( + l2TokenBridgeImpl.address, + deployer.address, + l2TokenBridgeImpl.interface.encodeFunctionData("initialize", [ + deployer.address, + ]) + ); + + const l2TokenBridge = L2ERC20ExtendedTokensBridge__factory.connect( + l2TokenBridgeProxy.address, + deployer + ); + + const roles = await Promise.all([ + l2TokenBridge.DEPOSITS_ENABLER_ROLE(), + l2TokenBridge.DEPOSITS_DISABLER_ROLE(), + l2TokenBridge.WITHDRAWALS_ENABLER_ROLE(), + l2TokenBridge.WITHDRAWALS_DISABLER_ROLE(), + ]); + + for (const role of roles) { + await l2TokenBridge.grantRole(role, deployer.address); + } + + await l2TokenBridge.enableDeposits(); + await l2TokenBridge.enableWithdrawals(); + + return { + stubs: { + l1TokenNonRebasable: l1TokenNonRebasableStub, + l1TokenRebasable: l1TokenRebasableStub, + l2TokenNonRebasable: l2TokenNonRebasableStub, + l2TokenRebasable: l2TokenRebasableStub, + l2Messenger: l2MessengerStub, + }, + accounts: { + deployer, + stranger, + recipient, + l2MessengerStubEOA, + emptyContractEOA, + l1TokenBridgeEOA, + }, + l2TokenBridge, + exchangeRate, + decimalsBN + }; } async function predictAddresses(account: SignerWithAddress, txsCount: number) { - const currentNonce = await account.getTransactionCount(); - - const res: string[] = []; - for (let i = 0; i < txsCount; ++i) { - res.push( - getContractAddress({ - from: account.address, - nonce: currentNonce + i, - }) - ); - } - return res; + const currentNonce = await account.getTransactionCount(); + + const res: string[] = []; + for (let i = 0; i < txsCount; ++i) { + res.push( + getContractAddress({ + from: account.address, + nonce: currentNonce + i, + }) + ); + } + return res; } async function packedTokenRateAndTimestamp(provider: JsonRpcProvider, tokenRate: BigNumber) { - const blockNumber = await provider.getBlockNumber(); - const blockTimestamp = (await provider.getBlock(blockNumber)).timestamp; - const stEthPerTokenStr = ethers.utils.hexZeroPad(tokenRate.toHexString(), 12); - const blockTimestampStr = ethers.utils.hexZeroPad(ethers.utils.hexlify(blockTimestamp), 5); - return ethers.utils.hexConcat([stEthPerTokenStr, blockTimestampStr]); + const blockNumber = await provider.getBlockNumber(); + const blockTimestamp = (await provider.getBlock(blockNumber)).timestamp; + const stEthPerTokenStr = ethers.utils.hexZeroPad(tokenRate.toHexString(), 12); + const blockTimestampStr = ethers.utils.hexZeroPad(ethers.utils.hexlify(blockTimestamp), 5); + return ethers.utils.hexConcat([stEthPerTokenStr, blockTimestampStr]); } type ContextType = Awaited> async function pushTokenRate(ctx: ContextType) { - const provider = await hre.ethers.provider; - - const packedTokenRateAndTimestampData = await packedTokenRateAndTimestamp(provider, ctx.exchangeRate); + const provider = await hre.ethers.provider; + + const packedTokenRateAndTimestampData = await packedTokenRateAndTimestamp(provider, ctx.exchangeRate); + + await ctx.l2TokenBridge + .connect(ctx.accounts.l2MessengerStubEOA) + .finalizeDeposit( + ctx.stubs.l1TokenRebasable.address, + ctx.stubs.l2TokenRebasable.address, + ctx.accounts.deployer.address, + ctx.accounts.deployer.address, + 0, + packedTokenRateAndTimestampData + ); +} - await ctx.l2TokenBridge - .connect(ctx.accounts.l2MessengerStubEOA) - .finalizeDeposit( - ctx.stubs.l1TokenRebasable.address, - ctx.stubs.l2TokenRebasable.address, - ctx.accounts.deployer.address, - ctx.accounts.deployer.address, - 0, - packedTokenRateAndTimestampData - ); +async function getL2TokenBridgeImpl(deployer: SignerWithAddress, l1TokenBridgeEOA: SignerWithAddress) { + const decimals = 18; + const exchangeRate = BigNumber.from('1164454276599657236') + + const l2MessengerStub = await new CrossDomainMessengerStub__factory( + deployer + ).deploy({ value: wei.toBigNumber(wei`1 ether`) }); + await l2MessengerStub.setXDomainMessageSender(l1TokenBridgeEOA.address); + + const [ + , + , + , + , + , + , + , + l2TokenBridgeProxyAddress + ] = await predictAddresses(deployer, 8); + + const l1TokenRebasableStub = await new ERC20BridgedStub__factory(deployer).deploy( + "L1 Token Rebasable", + "L1R" + ); + + const l1TokenNonRebasableStub = await new ERC20WrapperStub__factory(deployer).deploy( + l1TokenRebasableStub.address, + "L1 Token Non Rebasable", + "L1NR", + exchangeRate + ); + + const l2TokenNonRebasableStub = await new ERC20BridgedStub__factory(deployer).deploy( + "L2 Token Non Rebasable", + "L2NR" + ); + + const tokenRateOracleImpl = await new TokenRateOracle__factory(deployer).deploy( + l2MessengerStub.address, + l2TokenBridgeProxyAddress, + l1TokenBridgeEOA.address, + 86400, + 86400, + 500 + ); + + const provider = await hre.ethers.provider; + const blockNumber = await provider.getBlockNumber(); + const blockTimestamp = (await provider.getBlock(blockNumber)).timestamp; + const tokenRateOracleProxy = await new OssifiableProxy__factory( + deployer + ).deploy( + tokenRateOracleImpl.address, + deployer.address, + tokenRateOracleImpl.interface.encodeFunctionData("initialize", [ + exchangeRate, + blockTimestamp + ]) + ); + + const tokenRateOracle = TokenRateOracle__factory.connect( + tokenRateOracleProxy.address, + deployer + ); + + const l2TokenRebasableStub = await new ERC20RebasableBridgedPermit__factory(deployer).deploy( + "L2 Token Rebasable", + "L2R", + "1", + decimals, + l2TokenNonRebasableStub.address, + tokenRateOracle.address, + l2TokenBridgeProxyAddress + ); + + const l2TokenBridgeImpl = await new L2ERC20ExtendedTokensBridge__factory( + deployer + ).deploy( + l2MessengerStub.address, + l1TokenBridgeEOA.address, + l1TokenNonRebasableStub.address, + l1TokenRebasableStub.address, + l2TokenNonRebasableStub.address, + l2TokenRebasableStub.address + ); + return l2TokenBridgeImpl; } diff --git a/test/optimism/OpStackTokenRatePusher.unit.test.ts b/test/optimism/OpStackTokenRatePusher.unit.test.ts index 78102c7a..c024bb4a 100644 --- a/test/optimism/OpStackTokenRatePusher.unit.test.ts +++ b/test/optimism/OpStackTokenRatePusher.unit.test.ts @@ -4,12 +4,12 @@ import { utils, BigNumber } from 'ethers' import { unit } from "../../utils/testing"; import { wei } from "../../utils/wei"; import { - OpStackTokenRatePusher__factory, - CrossDomainMessengerStub__factory, - ERC20BridgedStub__factory, - ERC20WrapperStub__factory, - ITokenRateOracle__factory, - ITokenRatePusher__factory + OpStackTokenRatePusher__factory, + CrossDomainMessengerStub__factory, + ERC20BridgedStub__factory, + ERC20WrapperStub__factory, + ITokenRateOracle__factory, + ITokenRatePusher__factory } from "../../typechain"; unit("OpStackTokenRatePusher", ctxFactory) @@ -39,7 +39,7 @@ unit("OpStackTokenRatePusher", ctxFactory) const blockNumber = await provider.getBlockNumber(); const blockTimestamp = (await provider.getBlock(blockNumber)).timestamp; - await assert.emits(l1MessengerStub , tx, "SentMessage", [ + await assert.emits(l1MessengerStub, tx, "SentMessage", [ tokenRateOracle.address, opStackTokenRatePusher.address, ITokenRateOracle__factory.createInterface().encodeFunctionData( @@ -57,46 +57,48 @@ unit("OpStackTokenRatePusher", ctxFactory) .run(); async function ctxFactory() { - const [deployer, bridge, stranger, tokenRateOracle, l1TokenBridgeEOA] = await ethers.getSigners(); - - const l1TokenRebasableStub = await new ERC20BridgedStub__factory(deployer).deploy( - "L1 Token Rebasable", - "L1R" - ); - - const l1TokenNonRebasableStub = await new ERC20WrapperStub__factory(deployer).deploy( - l1TokenRebasableStub.address, - "L1 Token Non Rebasable", - "L1NR", - BigNumber.from('1164454276599657236') - ); - - const l1MessengerStub = await new CrossDomainMessengerStub__factory( - deployer - ).deploy({ value: wei.toBigNumber(wei`1 ether`) }); - await l1MessengerStub.setXDomainMessageSender(l1TokenBridgeEOA.address); - - const l2GasLimitForPushingTokenRate = 123; - - const opStackTokenRatePusher = await new OpStackTokenRatePusher__factory(deployer).deploy( - l1MessengerStub.address, - l1TokenNonRebasableStub.address, - tokenRateOracle.address, - l2GasLimitForPushingTokenRate - ); - - return { - accounts: { deployer, bridge, stranger, tokenRateOracle }, - contracts: { opStackTokenRatePusher, l1MessengerStub, l1TokenNonRebasableStub }, - constants: { l2GasLimitForPushingTokenRate } - }; + const [deployer, bridge, stranger, tokenRateOracle, l1TokenBridgeEOA] = await ethers.getSigners(); + + const tokenRate = BigNumber.from('1164454276599657236'); + + const l1TokenRebasableStub = await new ERC20BridgedStub__factory(deployer).deploy( + "L1 Token Rebasable", + "L1R" + ); + + const l1TokenNonRebasableStub = await new ERC20WrapperStub__factory(deployer).deploy( + l1TokenRebasableStub.address, + "L1 Token Non Rebasable", + "L1NR", + tokenRate + ); + + const l1MessengerStub = await new CrossDomainMessengerStub__factory( + deployer + ).deploy({ value: wei.toBigNumber(wei`1 ether`) }); + await l1MessengerStub.setXDomainMessageSender(l1TokenBridgeEOA.address); + + const l2GasLimitForPushingTokenRate = 123; + + const opStackTokenRatePusher = await new OpStackTokenRatePusher__factory(deployer).deploy( + l1MessengerStub.address, + l1TokenNonRebasableStub.address, + tokenRateOracle.address, + l2GasLimitForPushingTokenRate + ); + + return { + accounts: { deployer, bridge, stranger, tokenRateOracle }, + contracts: { opStackTokenRatePusher, l1MessengerStub, l1TokenNonRebasableStub }, + constants: { l2GasLimitForPushingTokenRate } + }; } export function getInterfaceID(contractInterface: utils.Interface) { - let interfaceID = ethers.constants.Zero; - const functions: string[] = Object.keys(contractInterface.functions); - for (let i = 0; i < functions.length; i++) { - interfaceID = interfaceID.xor(contractInterface.getSighash(functions[i])); - } - return interfaceID; + let interfaceID = ethers.constants.Zero; + const functions: string[] = Object.keys(contractInterface.functions); + for (let i = 0; i < functions.length; i++) { + interfaceID = interfaceID.xor(contractInterface.getSighash(functions[i])); + } + return interfaceID; } diff --git a/test/optimism/TokenRateNotifier.unit.test.ts b/test/optimism/TokenRateNotifier.unit.test.ts index 32f50460..075f956c 100644 --- a/test/optimism/TokenRateNotifier.unit.test.ts +++ b/test/optimism/TokenRateNotifier.unit.test.ts @@ -5,295 +5,297 @@ import { unit } from "../../utils/testing"; import { wei } from "../../utils/wei"; import { SignerWithAddress } from "@nomiclabs/hardhat-ethers/signers"; import { - TokenRateNotifier__factory, - ITokenRatePusher__factory, - OpStackTokenRatePusher__factory, - ITokenRateOracle__factory, - ERC20BridgedStub__factory, - ERC20WrapperStub__factory, - CrossDomainMessengerStub__factory, - OpStackTokenRatePusherWithSomeErrorStub__factory, - OpStackTokenRatePusherWithOutOfGasErrorStub__factory + TokenRateNotifier__factory, + ITokenRatePusher__factory, + OpStackTokenRatePusher__factory, + ITokenRateOracle__factory, + ERC20BridgedStub__factory, + ERC20WrapperStub__factory, + CrossDomainMessengerStub__factory, + OpStackTokenRatePusherWithSomeErrorStub__factory, + OpStackTokenRatePusherWithOutOfGasErrorStub__factory } from "../../typechain"; unit("TokenRateNotifier", ctxFactory) - .test("deploy with zero address owner", async (ctx) => { - const { deployer } = ctx.accounts; - - await assert.revertsWith( - new TokenRateNotifier__factory(deployer).deploy(ethers.constants.AddressZero), - "ErrorZeroAddressOwner()" - ); - }) - - .test("initial state", async (ctx) => { - const { tokenRateNotifier } = ctx.contracts; - - assert.equalBN(await tokenRateNotifier.MAX_OBSERVERS_COUNT(), 32); - const iTokenRateObserver = getInterfaceID(ITokenRatePusher__factory.createInterface()); - assert.equal(await tokenRateNotifier.REQUIRED_INTERFACE(), iTokenRateObserver._hex); - assert.equalBN(await tokenRateNotifier.observersLength(), 0); - }) - - .test("addObserver() :: not the owner", async (ctx) => { - const { tokenRateNotifier } = ctx.contracts; - const { stranger } = ctx.accounts; - - await assert.revertsWith( - tokenRateNotifier - .connect(stranger) - .addObserver(ethers.constants.AddressZero), - "Ownable: caller is not the owner" - ); - }) - - .test("addObserver() :: revert on adding zero address observer", async (ctx) => { - const { tokenRateNotifier } = ctx.contracts; - - await assert.revertsWith( - tokenRateNotifier - .connect(ctx.accounts.owner) - .addObserver(ethers.constants.AddressZero), - "ErrorZeroAddressObserver()" - ); - }) - - .test("addObserver() :: revert on adding observer with bad interface", async (ctx) => { - const { tokenRateNotifier } = ctx.contracts; - const { deployer } = ctx.accounts; - - const observer = await new TokenRateNotifier__factory(deployer).deploy(deployer.address); - await assert.revertsWith( - tokenRateNotifier - .connect(ctx.accounts.owner) - .addObserver(observer.address), - "ErrorBadObserverInterface()" - ); - }) - - .test("addObserver() :: revert on adding too many observers", async (ctx) => { - const { tokenRateNotifier, opStackTokenRatePusher } = ctx.contracts; - const { deployer, owner, tokenRateOracle } = ctx.accounts; - const { l2GasLimitForPushingTokenRate } = ctx.constants; - - assert.equalBN(await tokenRateNotifier.observersLength(), 0); - const maxObservers = await tokenRateNotifier.MAX_OBSERVERS_COUNT(); - for (let i = 0; i < maxObservers.toNumber(); i++) { - - const { - opStackTokenRatePusher - } = await getOpStackTokenRatePusher(deployer, owner, tokenRateOracle, l2GasLimitForPushingTokenRate); - - await tokenRateNotifier - .connect(ctx.accounts.owner) - .addObserver(opStackTokenRatePusher.address); - } - assert.equalBN(await tokenRateNotifier.observersLength(), maxObservers); - - await assert.revertsWith( - tokenRateNotifier - .connect(ctx.accounts.owner) - .addObserver(opStackTokenRatePusher.address), - "ErrorMaxObserversCountExceeded()" - ); - }) - - .test("addObserver() :: revert on adding the same observer twice", async (ctx) => { - const { tokenRateNotifier, opStackTokenRatePusher } = ctx.contracts; - - await tokenRateNotifier - .connect(ctx.accounts.owner) - .addObserver(opStackTokenRatePusher.address); - - await assert.revertsWith( - tokenRateNotifier - .connect(ctx.accounts.owner) - .addObserver(opStackTokenRatePusher.address), - "ErrorAddExistedObserver()" - ); - }) - - .test("addObserver() :: happy path of adding observer", async (ctx) => { - const { tokenRateNotifier, opStackTokenRatePusher } = ctx.contracts; - - assert.equalBN(await tokenRateNotifier.observersLength(), 0); - const tx = await tokenRateNotifier - .connect(ctx.accounts.owner) - .addObserver(opStackTokenRatePusher.address); - assert.equalBN(await tokenRateNotifier.observersLength(), 1); - - await assert.emits(tokenRateNotifier, tx, "ObserverAdded", [opStackTokenRatePusher.address]); - }) - - .test("removeObserver() :: revert on calling by not the owner", async (ctx) => { - const { tokenRateNotifier } = ctx.contracts; - const { stranger } = ctx.accounts; - - await assert.revertsWith( - tokenRateNotifier - .connect(stranger) - .removeObserver(ethers.constants.AddressZero), - "Ownable: caller is not the owner" - ); - }) - - .test("removeObserver() :: revert on removing non-added observer", async (ctx) => { - const { tokenRateNotifier, opStackTokenRatePusher } = ctx.contracts; - - assert.equalBN(await tokenRateNotifier.observersLength(), 0); - - await assert.revertsWith( - tokenRateNotifier - .connect(ctx.accounts.owner) - .removeObserver(opStackTokenRatePusher.address), - "ErrorNoObserverToRemove()" - ); - }) - - .test("removeObserver() :: happy path of removing observer", async (ctx) => { - const { tokenRateNotifier, opStackTokenRatePusher } = ctx.contracts; - - assert.equalBN(await tokenRateNotifier.observersLength(), 0); - - await tokenRateNotifier - .connect(ctx.accounts.owner) - .addObserver(opStackTokenRatePusher.address); - - assert.equalBN(await tokenRateNotifier.observersLength(), 1); - - const tx = await tokenRateNotifier - .connect(ctx.accounts.owner) - .removeObserver(opStackTokenRatePusher.address); - await assert.emits(tokenRateNotifier, tx, "ObserverRemoved", [opStackTokenRatePusher.address]); - - assert.equalBN(await tokenRateNotifier.observersLength(), 0); - }) - - .test("handlePostTokenRebase() :: failed with some error", async (ctx) => { - const { tokenRateNotifier } = ctx.contracts; - const { deployer } = ctx.accounts; - - const observer = await new OpStackTokenRatePusherWithSomeErrorStub__factory(deployer).deploy(); - await tokenRateNotifier - .connect(ctx.accounts.owner) - .addObserver(observer.address); - - const tx = await tokenRateNotifier.handlePostTokenRebase(1, 2, 3, 4, 5, 6, 7); - - await assert.emits(tokenRateNotifier, tx, "PushTokenRateFailed", [observer.address, "0x332e27d2"]); - }) - - .test("handlePostTokenRebase() :: revert when observer has out of gas error", async (ctx) => { - const { tokenRateNotifier } = ctx.contracts; - const { deployer } = ctx.accounts; - - const observer = await new OpStackTokenRatePusherWithOutOfGasErrorStub__factory(deployer).deploy(); - await tokenRateNotifier - .connect(ctx.accounts.owner) - .addObserver(observer.address); - - await assert.revertsWith( - tokenRateNotifier.handlePostTokenRebase(1, 2, 3, 4, 5, 6, 7), - "ErrorTokenRateNotifierRevertedWithNoData()" - ); - }) - - .test("handlePostTokenRebase() :: happy path of handling token rebase", async (ctx) => { - const { - tokenRateNotifier, - l1MessengerStub, - opStackTokenRatePusher, - l1TokenNonRebasableStub - } = ctx.contracts; - const { tokenRateOracle } = ctx.accounts; - const { l2GasLimitForPushingTokenRate } = ctx.constants; - - let tokenRate = await l1TokenNonRebasableStub.stEthPerToken(); - await tokenRateNotifier - .connect(ctx.accounts.owner) - .addObserver(opStackTokenRatePusher.address); - let tx = await tokenRateNotifier.handlePostTokenRebase(1, 2, 3, 4, 5, 6, 7); - - const provider = await ethers.provider; - const blockNumber = await provider.getBlockNumber(); - const blockTimestamp = (await provider.getBlock(blockNumber)).timestamp; - - await assert.emits(l1MessengerStub, tx, "SentMessage", [ - tokenRateOracle.address, - opStackTokenRatePusher.address, - ITokenRateOracle__factory.createInterface().encodeFunctionData( - "updateRate", - [ - tokenRate, - blockTimestamp - ] - ), - 1, - l2GasLimitForPushingTokenRate, - ]); - }) - - .run(); + .test("deploy with zero address owner", async (ctx) => { + const { deployer } = ctx.accounts; -async function getOpStackTokenRatePusher( - deployer: SignerWithAddress, - owner: SignerWithAddress, - tokenRateOracle: SignerWithAddress, - l2GasLimitForPushingTokenRate: number) { + await assert.revertsWith( + new TokenRateNotifier__factory(deployer).deploy(ethers.constants.AddressZero), + "ErrorZeroAddressOwner()" + ); + }) + + .test("initial state", async (ctx) => { + const { tokenRateNotifier } = ctx.contracts; + + assert.equalBN(await tokenRateNotifier.MAX_OBSERVERS_COUNT(), 32); + const iTokenRateObserver = getInterfaceID(ITokenRatePusher__factory.createInterface()); + assert.equal(await tokenRateNotifier.REQUIRED_INTERFACE(), iTokenRateObserver._hex); + assert.equalBN(await tokenRateNotifier.observersLength(), 0); + }) + + .test("addObserver() :: not the owner", async (ctx) => { + const { tokenRateNotifier } = ctx.contracts; + const { stranger } = ctx.accounts; + + await assert.revertsWith( + tokenRateNotifier + .connect(stranger) + .addObserver(ethers.constants.AddressZero), + "Ownable: caller is not the owner" + ); + }) + + .test("addObserver() :: revert on adding zero address observer", async (ctx) => { + const { tokenRateNotifier } = ctx.contracts; + + await assert.revertsWith( + tokenRateNotifier + .connect(ctx.accounts.owner) + .addObserver(ethers.constants.AddressZero), + "ErrorZeroAddressObserver()" + ); + }) + + .test("addObserver() :: revert on adding observer with bad interface", async (ctx) => { + const { tokenRateNotifier } = ctx.contracts; + const { deployer } = ctx.accounts; + + const observer = await new TokenRateNotifier__factory(deployer).deploy(deployer.address); + await assert.revertsWith( + tokenRateNotifier + .connect(ctx.accounts.owner) + .addObserver(observer.address), + "ErrorBadObserverInterface()" + ); + }) + + .test("addObserver() :: revert on adding too many observers", async (ctx) => { + const { tokenRateNotifier, opStackTokenRatePusher } = ctx.contracts; + const { deployer, owner, tokenRateOracle } = ctx.accounts; + const { l2GasLimitForPushingTokenRate } = ctx.constants; - const tokenRateNotifier = await new TokenRateNotifier__factory(deployer).deploy(owner.address); + assert.equalBN(await tokenRateNotifier.observersLength(), 0); + const maxObservers = await tokenRateNotifier.MAX_OBSERVERS_COUNT(); + for (let i = 0; i < maxObservers.toNumber(); i++) { - const l1MessengerStub = await new CrossDomainMessengerStub__factory( - deployer - ).deploy({ value: wei.toBigNumber(wei`1 ether`) }); + const { + opStackTokenRatePusher + } = await getOpStackTokenRatePusher(deployer, owner, tokenRateOracle, l2GasLimitForPushingTokenRate); + + await tokenRateNotifier + .connect(ctx.accounts.owner) + .addObserver(opStackTokenRatePusher.address); + } + assert.equalBN(await tokenRateNotifier.observersLength(), maxObservers); - const l1TokenRebasableStub = await new ERC20BridgedStub__factory(deployer).deploy( - "L1 Token Rebasable", - "L1R" + await assert.revertsWith( + tokenRateNotifier + .connect(ctx.accounts.owner) + .addObserver(opStackTokenRatePusher.address), + "ErrorMaxObserversCountExceeded()" ); + }) - const l1TokenNonRebasableStub = await new ERC20WrapperStub__factory(deployer).deploy( - l1TokenRebasableStub.address, - "L1 Token Non Rebasable", - "L1NR", - BigNumber.from('1164454276599657236') + .test("addObserver() :: revert on adding the same observer twice", async (ctx) => { + const { tokenRateNotifier, opStackTokenRatePusher } = ctx.contracts; + + await tokenRateNotifier + .connect(ctx.accounts.owner) + .addObserver(opStackTokenRatePusher.address); + + await assert.revertsWith( + tokenRateNotifier + .connect(ctx.accounts.owner) + .addObserver(opStackTokenRatePusher.address), + "ErrorAddExistedObserver()" ); + }) + + .test("addObserver() :: happy path of adding observer", async (ctx) => { + const { tokenRateNotifier, opStackTokenRatePusher } = ctx.contracts; + + assert.equalBN(await tokenRateNotifier.observersLength(), 0); + const tx = await tokenRateNotifier + .connect(ctx.accounts.owner) + .addObserver(opStackTokenRatePusher.address); + assert.equalBN(await tokenRateNotifier.observersLength(), 1); + + await assert.emits(tokenRateNotifier, tx, "ObserverAdded", [opStackTokenRatePusher.address]); + }) - const opStackTokenRatePusher = await new OpStackTokenRatePusher__factory(deployer).deploy( - l1MessengerStub.address, - l1TokenNonRebasableStub.address, - tokenRateOracle.address, - l2GasLimitForPushingTokenRate + .test("removeObserver() :: revert on calling by not the owner", async (ctx) => { + const { tokenRateNotifier } = ctx.contracts; + const { stranger } = ctx.accounts; + + await assert.revertsWith( + tokenRateNotifier + .connect(stranger) + .removeObserver(ethers.constants.AddressZero), + "Ownable: caller is not the owner" ); + }) - return {tokenRateNotifier, opStackTokenRatePusher, l1MessengerStub, l1TokenNonRebasableStub} -} + .test("removeObserver() :: revert on removing non-added observer", async (ctx) => { + const { tokenRateNotifier, opStackTokenRatePusher } = ctx.contracts; -async function ctxFactory() { - const [deployer, owner, stranger, tokenRateOracle] = await ethers.getSigners(); + assert.equalBN(await tokenRateNotifier.observersLength(), 0); + + await assert.revertsWith( + tokenRateNotifier + .connect(ctx.accounts.owner) + .removeObserver(opStackTokenRatePusher.address), + "ErrorNoObserverToRemove()" + ); + }) - const l2GasLimitForPushingTokenRate = 123; + .test("removeObserver() :: happy path of removing observer", async (ctx) => { + const { tokenRateNotifier, opStackTokenRatePusher } = ctx.contracts; + assert.equalBN(await tokenRateNotifier.observersLength(), 0); + + await tokenRateNotifier + .connect(ctx.accounts.owner) + .addObserver(opStackTokenRatePusher.address); + + assert.equalBN(await tokenRateNotifier.observersLength(), 1); + + const tx = await tokenRateNotifier + .connect(ctx.accounts.owner) + .removeObserver(opStackTokenRatePusher.address); + await assert.emits(tokenRateNotifier, tx, "ObserverRemoved", [opStackTokenRatePusher.address]); + + assert.equalBN(await tokenRateNotifier.observersLength(), 0); + }) + + .test("handlePostTokenRebase() :: failed with some error", async (ctx) => { + const { tokenRateNotifier } = ctx.contracts; + const { deployer } = ctx.accounts; + + const observer = await new OpStackTokenRatePusherWithSomeErrorStub__factory(deployer).deploy(); + await tokenRateNotifier + .connect(ctx.accounts.owner) + .addObserver(observer.address); + + const tx = await tokenRateNotifier.handlePostTokenRebase(1, 2, 3, 4, 5, 6, 7); + + await assert.emits(tokenRateNotifier, tx, "PushTokenRateFailed", [observer.address, "0x332e27d2"]); + }) + + .test("handlePostTokenRebase() :: revert when observer has out of gas error", async (ctx) => { + const { tokenRateNotifier } = ctx.contracts; + const { deployer } = ctx.accounts; + + const observer = await new OpStackTokenRatePusherWithOutOfGasErrorStub__factory(deployer).deploy(); + await tokenRateNotifier + .connect(ctx.accounts.owner) + .addObserver(observer.address); + + await assert.revertsWith( + tokenRateNotifier.handlePostTokenRebase(1, 2, 3, 4, 5, 6, 7), + "ErrorTokenRateNotifierRevertedWithNoData()" + ); + }) + + .test("handlePostTokenRebase() :: happy path of handling token rebase", async (ctx) => { const { - tokenRateNotifier, - opStackTokenRatePusher, - l1MessengerStub, - l1TokenNonRebasableStub - } = await getOpStackTokenRatePusher(deployer, owner, tokenRateOracle, l2GasLimitForPushingTokenRate); - - return { - accounts: { deployer, owner, stranger, tokenRateOracle }, - contracts: { tokenRateNotifier, opStackTokenRatePusher, l1MessengerStub, l1TokenNonRebasableStub }, - constants: { l2GasLimitForPushingTokenRate } - }; + tokenRateNotifier, + l1MessengerStub, + opStackTokenRatePusher, + l1TokenNonRebasableStub + } = ctx.contracts; + const { tokenRateOracle } = ctx.accounts; + const { l2GasLimitForPushingTokenRate } = ctx.constants; + + let tokenRate = await l1TokenNonRebasableStub.stEthPerToken(); + await tokenRateNotifier + .connect(ctx.accounts.owner) + .addObserver(opStackTokenRatePusher.address); + let tx = await tokenRateNotifier.handlePostTokenRebase(1, 2, 3, 4, 5, 6, 7); + + const provider = await ethers.provider; + const blockNumber = await provider.getBlockNumber(); + const blockTimestamp = (await provider.getBlock(blockNumber)).timestamp; + + await assert.emits(l1MessengerStub, tx, "SentMessage", [ + tokenRateOracle.address, + opStackTokenRatePusher.address, + ITokenRateOracle__factory.createInterface().encodeFunctionData( + "updateRate", + [ + tokenRate, + blockTimestamp + ] + ), + 1, + l2GasLimitForPushingTokenRate, + ]); + }) + + .run(); + +async function getOpStackTokenRatePusher( + deployer: SignerWithAddress, + owner: SignerWithAddress, + tokenRateOracle: SignerWithAddress, + l2GasLimitForPushingTokenRate: number) { + + const tokenRate = BigNumber.from('1164454276599657236'); + + const tokenRateNotifier = await new TokenRateNotifier__factory(deployer).deploy(owner.address); + + const l1MessengerStub = await new CrossDomainMessengerStub__factory( + deployer + ).deploy({ value: wei.toBigNumber(wei`1 ether`) }); + + const l1TokenRebasableStub = await new ERC20BridgedStub__factory(deployer).deploy( + "L1 Token Rebasable", + "L1R" + ); + + const l1TokenNonRebasableStub = await new ERC20WrapperStub__factory(deployer).deploy( + l1TokenRebasableStub.address, + "L1 Token Non Rebasable", + "L1NR", + tokenRate + ); + + const opStackTokenRatePusher = await new OpStackTokenRatePusher__factory(deployer).deploy( + l1MessengerStub.address, + l1TokenNonRebasableStub.address, + tokenRateOracle.address, + l2GasLimitForPushingTokenRate + ); + + return { tokenRateNotifier, opStackTokenRatePusher, l1MessengerStub, l1TokenNonRebasableStub } +} + +async function ctxFactory() { + const [deployer, owner, stranger, tokenRateOracle] = await ethers.getSigners(); + + const l2GasLimitForPushingTokenRate = 300_000; + + const { + tokenRateNotifier, + opStackTokenRatePusher, + l1MessengerStub, + l1TokenNonRebasableStub + } = await getOpStackTokenRatePusher(deployer, owner, tokenRateOracle, l2GasLimitForPushingTokenRate); + + return { + accounts: { deployer, owner, stranger, tokenRateOracle }, + contracts: { tokenRateNotifier, opStackTokenRatePusher, l1MessengerStub, l1TokenNonRebasableStub }, + constants: { l2GasLimitForPushingTokenRate } + }; } export function getInterfaceID(contractInterface: utils.Interface) { - let interfaceID = ethers.constants.Zero; - const functions: string[] = Object.keys(contractInterface.functions); - for (let i = 0; i < functions.length; i++) { - interfaceID = interfaceID.xor(contractInterface.getSighash(functions[i])); - } - return interfaceID; + let interfaceID = ethers.constants.Zero; + const functions: string[] = Object.keys(contractInterface.functions); + for (let i = 0; i < functions.length; i++) { + interfaceID = interfaceID.xor(contractInterface.getSighash(functions[i])); + } + return interfaceID; } diff --git a/test/optimism/TokenRateOracle.unit.test.ts b/test/optimism/TokenRateOracle.unit.test.ts index dbc5a141..d14de325 100644 --- a/test/optimism/TokenRateOracle.unit.test.ts +++ b/test/optimism/TokenRateOracle.unit.test.ts @@ -1,273 +1,280 @@ import hre from "hardhat"; import { assert } from "chai"; import { BigNumber } from "ethers"; -import testing, { unit } from "../../utils/testing"; import { wei } from "../../utils/wei"; +import testing, { unit } from "../../utils/testing"; import { tokenRateOracleUnderProxy } from "../../utils/testing/contractsFactory"; -import { - TokenRateOracle__factory, - CrossDomainMessengerStub__factory -} from "../../typechain"; +import { TokenRateOracle__factory, CrossDomainMessengerStub__factory } from "../../typechain"; unit("TokenRateOracle", ctxFactory) - .test("initialize() :: petrified version", async (ctx) => { - const { deployer, bridge, l1TokenBridgeEOA } = ctx.accounts; - const { l2MessengerStub } = ctx.contracts; - - const tokenRateOracleImpl = await new TokenRateOracle__factory(deployer).deploy( - l2MessengerStub.address, - bridge.address, - l1TokenBridgeEOA.address, - 86400, - 86400, - 500 - ); - - const petrifiedVersionMark = hre.ethers.constants.MaxUint256; - assert.equalBN(await tokenRateOracleImpl.getContractVersion(), petrifiedVersionMark); - - await assert.revertsWith( - tokenRateOracleImpl.initialize(1, 2), - "NonZeroContractVersionOnInit()" - ); - }) - - .test("initialize() :: re-initialization", async (ctx) => { - const { tokenRateOracle } = ctx.contracts; - - assert.equalBN(await tokenRateOracle.getContractVersion(), 1); - - await assert.revertsWith( - tokenRateOracle.initialize(2, 3), - "NonZeroContractVersionOnInit()" - ); - }) - - .test("state after init", async (ctx) => { - const { tokenRateOracle, l2MessengerStub } = ctx.contracts; - const { bridge, l1TokenBridgeEOA } = ctx.accounts; - const { tokenRate, blockTimestamp } = ctx.constants; - - assert.equal(await tokenRateOracle.MESSENGER(), l2MessengerStub.address); - assert.equal(await tokenRateOracle.L2_ERC20_TOKEN_BRIDGE(), bridge.address); - assert.equal(await tokenRateOracle.L1_TOKEN_RATE_PUSHER(), l1TokenBridgeEOA.address); - assert.equalBN(await tokenRateOracle.TOKEN_RATE_OUTDATED_DELAY(), 86400); - - assert.equalBN(await tokenRateOracle.latestAnswer(), tokenRate); - - const { - roundId_, - answer_, - startedAt_, - updatedAt_, - answeredInRound_ - } = await tokenRateOracle.latestRoundData(); - - assert.equalBN(roundId_, blockTimestamp); - assert.equalBN(answer_, tokenRate); - assert.equalBN(startedAt_, blockTimestamp); - assert.equalBN(updatedAt_, blockTimestamp); - assert.equalBN(answeredInRound_, blockTimestamp); - assert.equalBN(await tokenRateOracle.decimals(), 18); - }) - - .test("updateRate() :: called by non-bridge account", async (ctx) => { - const { tokenRateOracle } = ctx.contracts; - const { stranger } = ctx.accounts; - await assert.revertsWith( - tokenRateOracle.connect(stranger).updateRate(10, 40), - "ErrorNotBridgeOrTokenRatePusher()" - ); - }) - - .test("updateRate() :: called by messenger with incorrect cross-domain sender", async (ctx) => { - const { tokenRateOracle, l2MessengerStub } = ctx.contracts; - const { stranger, l2MessengerStubEOA } = ctx.accounts; - await l2MessengerStub.setXDomainMessageSender(stranger.address); - await assert.revertsWith(tokenRateOracle.connect( - l2MessengerStubEOA).updateRate(10, 40), - "ErrorNotBridgeOrTokenRatePusher()" - ); - }) - - .test("updateRate() :: L1 time exceeded allowed L2 clock lag", async (ctx) => { - const { tokenRateOracle } = ctx.contracts; - const { bridge } = ctx.accounts; - const { tokenRate, blockTimestamp } = ctx.constants; - - const exceededTime = blockTimestamp.add(86400).add(40); // more than one day - await assert.revertsWith( - tokenRateOracle.connect(bridge).updateRate(tokenRate, exceededTime), - "ErrorL1TimestampExceededAllowedClockLag(" + tokenRate + ", " + exceededTime + ")" - ) - }) - - .test("updateRate() :: received token rate is in the past or same time", async (ctx) => { - const { tokenRateOracle } = ctx.contracts; - const { bridge } = ctx.accounts; - const { tokenRate, blockTimestamp } = ctx.constants; - - const tx0 = await tokenRateOracle - .connect(bridge) - .updateRate(tokenRate, blockTimestamp); - - await assert.emits(tokenRateOracle, tx0, "DormantTokenRateUpdateIgnored", [ - tokenRate, - blockTimestamp, - blockTimestamp, - ]); - - const timeInPast = blockTimestamp.sub(1000); - const tx1 = await tokenRateOracle - .connect(bridge) - .updateRate(tokenRate, timeInPast); - - await assert.emits(tokenRateOracle, tx1, "DormantTokenRateUpdateIgnored", [ - tokenRate, - blockTimestamp, - timeInPast, - ]); - }) - - .test("updateRate() :: token rate is out of range", async (ctx) => { - const { tokenRateOracle } = ctx.contracts; - const { bridge } = ctx.accounts; - const { tokenRate, blockTimestamp } = ctx.constants; - - const tokenRateTooBig = tokenRate.mul(BigNumber.from('106')).div(BigNumber.from('100')); // 106% - const tokenRateTooSmall = tokenRate.mul(BigNumber.from('94')).div(BigNumber.from('100')); // 94% - - var blockTimestampForNextUpdate = blockTimestamp.add(1000); - await assert.revertsWith( - tokenRateOracle.connect(bridge).updateRate(tokenRateTooBig, blockTimestampForNextUpdate), - "ErrorTokenRateIsOutOfRange(" + tokenRateTooBig + ", " + blockTimestampForNextUpdate + ")" - ) - - blockTimestampForNextUpdate = blockTimestampForNextUpdate.add(1000); - await assert.revertsWith( - tokenRateOracle.connect(bridge).updateRate(tokenRateTooSmall, blockTimestampForNextUpdate), - "ErrorTokenRateIsOutOfRange(" + tokenRateTooSmall + ", " + blockTimestampForNextUpdate + ")" - ) - }) - - .test("updateRate() :: happy path called by bridge", async (ctx) => { - const { tokenRateOracle } = ctx.contracts; - const { bridge } = ctx.accounts; - const { tokenRate, blockTimestamp } = ctx.constants; - - const newTokenRate = tokenRate.mul(BigNumber.from('104')).div(BigNumber.from('100')); // 104% - - const blockTimestampInFuture = blockTimestamp.add(1000); - const tx = await tokenRateOracle.connect(bridge).updateRate(newTokenRate, blockTimestampInFuture); - - await assert.emits(tokenRateOracle, tx, "TokenRateL1TimestampAheadOfL2Time", [ - newTokenRate, - blockTimestampInFuture - ]); - - await assert.emits(tokenRateOracle, tx, "RateUpdated", [ - newTokenRate, - blockTimestampInFuture - ]); - - assert.equalBN(await tokenRateOracle.latestAnswer(), newTokenRate); - - const { - roundId_, - answer_, - startedAt_, - updatedAt_, - answeredInRound_ - } = await tokenRateOracle.latestRoundData(); - - assert.equalBN(roundId_, blockTimestampInFuture); - assert.equalBN(answer_, newTokenRate); - assert.equalBN(startedAt_, blockTimestampInFuture); - assert.equalBN(updatedAt_, blockTimestampInFuture); - assert.equalBN(answeredInRound_, blockTimestampInFuture); - assert.equalBN(await tokenRateOracle.decimals(), 18); - }) - - .test("updateRate() :: happy path called by messenger with correct cross-domain sender", async (ctx) => { - const { tokenRateOracle, l2MessengerStub } = ctx.contracts; - const { l2MessengerStubEOA, l1TokenBridgeEOA } = ctx.accounts; - const { tokenRate, blockTimestamp } = ctx.constants; - - await l2MessengerStub.setXDomainMessageSender(l1TokenBridgeEOA.address); - - const newTokenRate = tokenRate.mul(BigNumber.from('104')).div(BigNumber.from('100')); // 104% - - const blockTimestampInFuture = blockTimestamp.add(1000); - const tx = await tokenRateOracle.connect(l2MessengerStubEOA).updateRate(newTokenRate, blockTimestampInFuture); - - await assert.emits(tokenRateOracle, tx, "TokenRateL1TimestampAheadOfL2Time", [ - newTokenRate, - blockTimestampInFuture - ]); - - await assert.emits(tokenRateOracle, tx, "RateUpdated", [ - newTokenRate, - blockTimestampInFuture - ]); - - assert.equalBN(await tokenRateOracle.latestAnswer(), newTokenRate); - - const { - roundId_, - answer_, - startedAt_, - updatedAt_, - answeredInRound_ - } = await tokenRateOracle.latestRoundData(); - - assert.equalBN(roundId_, blockTimestampInFuture); - assert.equalBN(answer_, newTokenRate); - assert.equalBN(startedAt_, blockTimestampInFuture); - assert.equalBN(updatedAt_, blockTimestampInFuture); - assert.equalBN(answeredInRound_, blockTimestampInFuture); - assert.equalBN(await tokenRateOracle.decimals(), 18); - }) - - .run(); + .test("state after init", async (ctx) => { + const { tokenRateOracle, l2MessengerStub } = ctx.contracts; + const { bridge, l1TokenBridgeEOA } = ctx.accounts; + const { + tokenRate, + blockTimestamp, + tokenRateOutdatedDelay, + maxAllowedL2ToL1ClockLag, + maxAllowedTokenRateDeviationPerDay + } = ctx.constants; + + assert.equal(await tokenRateOracle.MESSENGER(), l2MessengerStub.address); + assert.equal(await tokenRateOracle.L2_ERC20_TOKEN_BRIDGE(), bridge.address); + assert.equal(await tokenRateOracle.L1_TOKEN_RATE_PUSHER(), l1TokenBridgeEOA.address); + assert.equalBN(await tokenRateOracle.TOKEN_RATE_OUTDATED_DELAY(), tokenRateOutdatedDelay); + assert.equalBN(await tokenRateOracle.MAX_ALLOWED_L2_TO_L1_CLOCK_LAG(), maxAllowedL2ToL1ClockLag); + assert.equalBN(await tokenRateOracle.MAX_ALLOWED_TOKEN_RATE_DEVIATION_PER_DAY(), maxAllowedTokenRateDeviationPerDay); + + assert.equalBN(await tokenRateOracle.latestAnswer(), tokenRate); + + const { + roundId_, + answer_, + startedAt_, + updatedAt_, + answeredInRound_ + } = await tokenRateOracle.latestRoundData(); + + assert.equalBN(roundId_, blockTimestamp); + assert.equalBN(answer_, tokenRate); + assert.equalBN(startedAt_, blockTimestamp); + assert.equalBN(updatedAt_, blockTimestamp); + assert.equalBN(answeredInRound_, blockTimestamp); + assert.equalBN(await tokenRateOracle.decimals(), 18); + }) + + .test("initialize() :: petrified version", async (ctx) => { + const { deployer, bridge, l1TokenBridgeEOA } = ctx.accounts; + const { l2MessengerStub } = ctx.contracts; + + const tokenRateOracleImpl = await new TokenRateOracle__factory(deployer).deploy( + l2MessengerStub.address, + bridge.address, + l1TokenBridgeEOA.address, + 86400, + 86400, + 500 + ); -async function ctxFactory() { - const tokenRate = BigNumber.from('1164454276599657236'); // value taken from real contact on 23.04.24 - const tokenRateOutdatedDelay = BigNumber.from(86400); // 1 day - const maxAllowedL2ToL1ClockLag = BigNumber.from(86400); // 1 day - const maxAllowedTokenRateDeviationPerDay = BigNumber.from(500); // 5% - const blockTimestamp = await getBlockTimestamp(0); - - const [deployer, bridge, stranger, l1TokenBridgeEOA] = await hre.ethers.getSigners(); - - const l2MessengerStub = await new CrossDomainMessengerStub__factory( - deployer - ).deploy({ value: wei.toBigNumber(wei`1 ether`) }); - const l2MessengerStubEOA = await testing.impersonate(l2MessengerStub.address); - - const tokenRateOracle = await tokenRateOracleUnderProxy( - deployer, - l2MessengerStub.address, - bridge.address, - l1TokenBridgeEOA.address, - tokenRateOutdatedDelay, - maxAllowedL2ToL1ClockLag, - maxAllowedTokenRateDeviationPerDay, - tokenRate, - blockTimestamp + const petrifiedVersionMark = hre.ethers.constants.MaxUint256; + assert.equalBN(await tokenRateOracleImpl.getContractVersion(), petrifiedVersionMark); + + await assert.revertsWith( + tokenRateOracleImpl.initialize(1, 2), + "NonZeroContractVersionOnInit()" + ); + }) + + .test("initialize() :: don't allow to initialize twice", async (ctx) => { + const { tokenRateOracle } = ctx.contracts; + + assert.equalBN(await tokenRateOracle.getContractVersion(), 1); + + await assert.revertsWith( + tokenRateOracle.initialize(2, 3), + "NonZeroContractVersionOnInit()" ); + }) + + .test("updateRate() :: called by non-bridge account", async (ctx) => { + const { tokenRateOracle } = ctx.contracts; + const { stranger } = ctx.accounts; + await assert.revertsWith( + tokenRateOracle.connect(stranger).updateRate(10, 40), + "ErrorNotBridgeOrTokenRatePusher()" + ); + }) + + .test("updateRate() :: called by messenger with incorrect cross-domain sender", async (ctx) => { + const { tokenRateOracle, l2MessengerStub } = ctx.contracts; + const { stranger, l2MessengerStubEOA } = ctx.accounts; + await l2MessengerStub.setXDomainMessageSender(stranger.address); + await assert.revertsWith( + tokenRateOracle.connect(l2MessengerStubEOA).updateRate(10, 40), + "ErrorNotBridgeOrTokenRatePusher()" + ); + }) + + .test("updateRate() :: L1 time exceeded allowed L2 clock lag", async (ctx) => { + const { tokenRateOracle } = ctx.contracts; + const { bridge } = ctx.accounts; + const { tokenRate, blockTimestamp } = ctx.constants; + + const exceededTime = blockTimestamp.add(86400).add(40); // more than one day + await assert.revertsWith( + tokenRateOracle.connect(bridge).updateRate(tokenRate, exceededTime), + "ErrorL1TimestampExceededAllowedClockLag(" + tokenRate + ", " + exceededTime + ")" + ) + }) + + .test("updateRate() :: received token rate is in the past or same time", async (ctx) => { + const { tokenRateOracle } = ctx.contracts; + const { bridge } = ctx.accounts; + const { tokenRate, blockTimestamp } = ctx.constants; + + const tx0 = await tokenRateOracle + .connect(bridge) + .updateRate(tokenRate, blockTimestamp); + + await assert.emits(tokenRateOracle, tx0, "DormantTokenRateUpdateIgnored", [ + tokenRate, + blockTimestamp, + blockTimestamp, + ]); + await assert.notEmits(tokenRateOracle, tx0, "RateUpdated"); + + const timeInPast = blockTimestamp.sub(1000); + const tx1 = await tokenRateOracle + .connect(bridge) + .updateRate(tokenRate, timeInPast); + + await assert.emits(tokenRateOracle, tx1, "DormantTokenRateUpdateIgnored", [ + tokenRate, + timeInPast, + blockTimestamp, + ]); + await assert.notEmits(tokenRateOracle, tx1, "RateUpdated"); + }) + + .test("updateRate() :: token rate is out of range", async (ctx) => { + const { tokenRateOracle } = ctx.contracts; + const { bridge } = ctx.accounts; + const { tokenRate, blockTimestamp } = ctx.constants; + + const tokenRateTooBig = tokenRate.mul(BigNumber.from('106')).div(BigNumber.from('100')); // 106% + const tokenRateTooSmall = tokenRate.mul(BigNumber.from('94')).div(BigNumber.from('100')); // 94% + + var blockTimestampForNextUpdate = blockTimestamp.add(1000); + await assert.revertsWith( + tokenRateOracle.connect(bridge).updateRate(tokenRateTooBig, blockTimestampForNextUpdate), + "ErrorTokenRateIsOutOfRange(" + tokenRateTooBig + ", " + blockTimestampForNextUpdate + ")" + ) + + blockTimestampForNextUpdate = blockTimestampForNextUpdate.add(1000); + await assert.revertsWith( + tokenRateOracle.connect(bridge).updateRate(tokenRateTooSmall, blockTimestampForNextUpdate), + "ErrorTokenRateIsOutOfRange(" + tokenRateTooSmall + ", " + blockTimestampForNextUpdate + ")" + ) + }) + + .test("updateRate() :: happy path called by bridge", async (ctx) => { + const { tokenRateOracle } = ctx.contracts; + const { bridge } = ctx.accounts; + const { tokenRate, blockTimestamp } = ctx.constants; + + const newTokenRate = tokenRate.mul(BigNumber.from('104')).div(BigNumber.from('100')); // 104% + + const blockTimestampInFuture = blockTimestamp.add(1000); + const tx = await tokenRateOracle.connect(bridge).updateRate(newTokenRate, blockTimestampInFuture); + + await assert.emits(tokenRateOracle, tx, "TokenRateL1TimestampIsInFuture", [ + newTokenRate, + blockTimestampInFuture + ]); + + await assert.emits(tokenRateOracle, tx, "RateUpdated", [ + newTokenRate, + blockTimestampInFuture + ]); + + assert.equalBN(await tokenRateOracle.latestAnswer(), newTokenRate); + + const { + roundId_, + answer_, + startedAt_, + updatedAt_, + answeredInRound_ + } = await tokenRateOracle.latestRoundData(); + + assert.equalBN(roundId_, blockTimestampInFuture); + assert.equalBN(answer_, newTokenRate); + assert.equalBN(startedAt_, blockTimestampInFuture); + assert.equalBN(updatedAt_, blockTimestampInFuture); + assert.equalBN(answeredInRound_, blockTimestampInFuture); + assert.equalBN(await tokenRateOracle.decimals(), 18); + }) + + .test("updateRate() :: happy path called by messenger with correct cross-domain sender", async (ctx) => { + const { tokenRateOracle, l2MessengerStub } = ctx.contracts; + const { l2MessengerStubEOA, l1TokenBridgeEOA } = ctx.accounts; + const { tokenRate, blockTimestamp } = ctx.constants; + + await l2MessengerStub.setXDomainMessageSender(l1TokenBridgeEOA.address); + + const newTokenRate = tokenRate.mul(BigNumber.from('104')).div(BigNumber.from('100')); // 104% + + const blockTimestampInFuture = blockTimestamp.add(1000); + const tx = await tokenRateOracle.connect(l2MessengerStubEOA).updateRate(newTokenRate, blockTimestampInFuture); + + await assert.emits(tokenRateOracle, tx, "TokenRateL1TimestampIsInFuture", [ + newTokenRate, + blockTimestampInFuture + ]); + + await assert.emits(tokenRateOracle, tx, "RateUpdated", [ + newTokenRate, + blockTimestampInFuture + ]); + + assert.equalBN(await tokenRateOracle.latestAnswer(), newTokenRate); + + const { + roundId_, + answer_, + startedAt_, + updatedAt_, + answeredInRound_ + } = await tokenRateOracle.latestRoundData(); + + assert.equalBN(roundId_, blockTimestampInFuture); + assert.equalBN(answer_, newTokenRate); + assert.equalBN(startedAt_, blockTimestampInFuture); + assert.equalBN(updatedAt_, blockTimestampInFuture); + assert.equalBN(answeredInRound_, blockTimestampInFuture); + assert.equalBN(await tokenRateOracle.decimals(), 18); + }) + + .run(); - return { - accounts: { deployer, bridge, stranger, l1TokenBridgeEOA, l2MessengerStubEOA }, - contracts: { tokenRateOracle, l2MessengerStub }, - constants: { - tokenRate, blockTimestamp, tokenRateOutdatedDelay, - maxAllowedL2ToL1ClockLag, maxAllowedTokenRateDeviationPerDay - } - }; +async function ctxFactory() { + const tokenRate = BigNumber.from('1164454276599657236'); // value taken from real contact on 23.04.24 + const tokenRateOutdatedDelay = BigNumber.from(86400); // 1 day + const maxAllowedL2ToL1ClockLag = BigNumber.from(86400); // 1 day + const maxAllowedTokenRateDeviationPerDay = BigNumber.from(500); // 5% + const blockTimestamp = await getBlockTimestamp(0); + + const [deployer, bridge, stranger, l1TokenBridgeEOA] = await hre.ethers.getSigners(); + + const l2MessengerStub = await new CrossDomainMessengerStub__factory( + deployer + ).deploy({ value: wei.toBigNumber(wei`1 ether`) }); + const l2MessengerStubEOA = await testing.impersonate(l2MessengerStub.address); + + const tokenRateOracle = await tokenRateOracleUnderProxy( + deployer, + l2MessengerStub.address, + bridge.address, + l1TokenBridgeEOA.address, + tokenRateOutdatedDelay, + maxAllowedL2ToL1ClockLag, + maxAllowedTokenRateDeviationPerDay, + tokenRate, + blockTimestamp + ); + + return { + accounts: { deployer, bridge, stranger, l1TokenBridgeEOA, l2MessengerStubEOA }, + contracts: { tokenRateOracle, l2MessengerStub }, + constants: { + tokenRate, blockTimestamp, tokenRateOutdatedDelay, + maxAllowedL2ToL1ClockLag, maxAllowedTokenRateDeviationPerDay + } + }; } async function getBlockTimestamp(shift: number) { - const provider = await hre.ethers.provider; - const blockNumber = await provider.getBlockNumber(); - return BigNumber.from((await provider.getBlock(blockNumber)).timestamp + shift); + const provider = await hre.ethers.provider; + const blockNumber = await provider.getBlockNumber(); + return BigNumber.from((await provider.getBlock(blockNumber)).timestamp + shift); } diff --git a/test/optimism/bridging-rebasable.integration.test.ts b/test/optimism/bridging-rebasable.integration.test.ts index 74d97794..b28ae6a9 100644 --- a/test/optimism/bridging-rebasable.integration.test.ts +++ b/test/optimism/bridging-rebasable.integration.test.ts @@ -76,37 +76,37 @@ scenario("Optimism :: Bridging rebasable token integration test", ctxFactory) .step("Set up Token Rate Oracle by pushing first rate", async (ctx) => { const { - l1Token, - l1TokenRebasable, - l2TokenRebasable, - l1LidoTokensBridge, - l2CrossDomainMessenger, - l2ERC20ExtendedTokensBridge, - l2Provider - } = ctx; + l1Token, + l1TokenRebasable, + l2TokenRebasable, + l1LidoTokensBridge, + l2CrossDomainMessenger, + l2ERC20ExtendedTokensBridge, + l2Provider + } = ctx; const { accountA: tokenHolderA, l1CrossDomainMessengerAliased } = ctx.accounts; const dataToReceive = await packedTokenRateAndTimestamp(l2Provider, l1Token); const tx = await l2CrossDomainMessenger - .connect(l1CrossDomainMessengerAliased) - .relayMessage( - 1, - l1LidoTokensBridge.address, - l2ERC20ExtendedTokensBridge.address, - 0, - 300_000, - l2ERC20ExtendedTokensBridge.interface.encodeFunctionData("finalizeDeposit", [ - l1TokenRebasable.address, - l2TokenRebasable.address, - tokenHolderA.address, - tokenHolderA.address, + .connect(l1CrossDomainMessengerAliased) + .relayMessage( + 1, + l1LidoTokensBridge.address, + l2ERC20ExtendedTokensBridge.address, 0, - dataToReceive, - ]), - { gasLimit: 5_000_000 } - ); + 300_000, + l2ERC20ExtendedTokensBridge.interface.encodeFunctionData("finalizeDeposit", [ + l1TokenRebasable.address, + l2TokenRebasable.address, + tokenHolderA.address, + tokenHolderA.address, + 0, + dataToReceive, + ]), + { gasLimit: 5_000_000 } + ); }) .step("L1 -> L2 deposit zero tokens via depositERC20() method", async (ctx) => { @@ -131,7 +131,7 @@ scenario("Optimism :: Bridging rebasable token integration test", ctxFactory) ); const l1ERC20ExtendedTokensBridgeBalanceBefore = await l1TokenRebasable.balanceOf( - l1LidoTokensBridge.address + l1LidoTokensBridge.address ); const tx = await l1LidoTokensBridge @@ -270,7 +270,7 @@ scenario("Optimism :: Bridging rebasable token integration test", ctxFactory) ); const l1ERC20ExtendedTokensBridgeBalanceBefore = await l1TokenRebasable.balanceOf( - l1LidoTokensBridge.address + l1LidoTokensBridge.address ); const tx = await l1LidoTokensBridge @@ -306,7 +306,7 @@ scenario("Optimism :: Bridging rebasable token integration test", ctxFactory) ] ); - console.log("tokenHolderA.address=",tokenHolderA.address); + console.log("tokenHolderA.address=", tokenHolderA.address); const messageNonce = await l1CrossDomainMessenger.messageNonce(); @@ -329,437 +329,437 @@ scenario("Optimism :: Bridging rebasable token integration test", ctxFactory) // ); }) -// .step("Finalize deposit on L2", async (ctx) => { -// const { -// l1Token, -// l1TokenRebasable, -// l2TokenRebasable, -// l1LidoTokensBridge, -// l2CrossDomainMessenger, -// l2ERC20ExtendedTokensBridge, -// l2Provider -// } = ctx; -// const { depositAmountNonRebasable, depositAmountRebasable } = ctx.common; - -// const { accountA: tokenHolderA, l1CrossDomainMessengerAliased } = -// ctx.accounts; - -// const tokenHolderABalanceBefore = await l2TokenRebasable.balanceOf( -// tokenHolderA.address -// ); - -// const l2TokenRebasableTotalSupplyBefore = await l2TokenRebasable.totalSupply(); -// const dataToReceive = await packedTokenRateAndTimestamp(l2Provider, l1Token); - -// const tx = await l2CrossDomainMessenger -// .connect(l1CrossDomainMessengerAliased) -// .relayMessage( -// 1, -// l1LidoTokensBridge.address, -// l2ERC20ExtendedTokensBridge.address, -// 0, -// 300_000, -// l2ERC20ExtendedTokensBridge.interface.encodeFunctionData("finalizeDeposit", [ -// l1TokenRebasable.address, -// l2TokenRebasable.address, -// tokenHolderA.address, -// tokenHolderA.address, -// depositAmountNonRebasable, -// dataToReceive, -// ]), -// { gasLimit: 5_000_000 } -// ); - -// await assert.emits(l2ERC20ExtendedTokensBridge, tx, "DepositFinalized", [ -// l1TokenRebasable.address, -// l2TokenRebasable.address, -// tokenHolderA.address, -// tokenHolderA.address, -// depositAmountRebasable, -// "0x", -// ]); - -// assert.equalBN( -// await l2TokenRebasable.balanceOf(tokenHolderA.address), -// tokenHolderABalanceBefore.add(depositAmountRebasable) -// ); -// assert.equalBN( -// await l2TokenRebasable.totalSupply(), -// l2TokenRebasableTotalSupplyBefore.add(depositAmountRebasable) -// ); -// }) - -// .step("L2 -> L1 withdrawal via withdraw()", async (ctx) => { -// const { accountA: tokenHolderA } = ctx.accounts; -// const { withdrawalAmountRebasable } = ctx.common; -// const { -// l1TokenRebasable, -// l2TokenRebasable, -// l2ERC20ExtendedTokensBridge -// } = ctx; - -// const tokenHolderABalanceBefore = await l2TokenRebasable.balanceOf( -// tokenHolderA.address -// ); -// const l2TotalSupplyBefore = await l2TokenRebasable.totalSupply(); - -// const tx = await l2ERC20ExtendedTokensBridge -// .connect(tokenHolderA.l2Signer) -// .withdraw( -// l2TokenRebasable.address, -// withdrawalAmountRebasable, -// 0, -// "0x" -// ); - -// await assert.emits(l2ERC20ExtendedTokensBridge, tx, "WithdrawalInitiated", [ -// l1TokenRebasable.address, -// l2TokenRebasable.address, -// tokenHolderA.address, -// tokenHolderA.address, -// withdrawalAmountRebasable, -// "0x", -// ]); - -// assert.equalBN( -// await l2TokenRebasable.balanceOf(tokenHolderA.address), -// tokenHolderABalanceBefore.sub(withdrawalAmountRebasable) -// ); -// assert.equalBN( -// await l2TokenRebasable.totalSupply(), -// l2TotalSupplyBefore.sub(withdrawalAmountRebasable) -// ); -// }) - -// .step("Finalize withdrawal on L1", async (ctx) => { -// const { -// l1Token, -// l1TokenRebasable, -// l1CrossDomainMessenger, -// l1LidoTokensBridge, -// l2CrossDomainMessenger, -// l2TokenRebasable, -// l2ERC20ExtendedTokensBridge, -// } = ctx; -// const { accountA: tokenHolderA, l1Stranger } = ctx.accounts; -// const { withdrawalAmountNonRebasable, withdrawalAmountRebasable } = ctx.common; - -// const tokenHolderABalanceBefore = await l1TokenRebasable.balanceOf( -// tokenHolderA.address -// ); -// const l1ERC20ExtendedTokensBridgeBalanceBefore = await l1Token.balanceOf( -// l1LidoTokensBridge.address -// ); - -// await l1CrossDomainMessenger -// .connect(l1Stranger) -// .setXDomainMessageSender(l2ERC20ExtendedTokensBridge.address); - -// const tx = await l1CrossDomainMessenger -// .connect(l1Stranger) -// .relayMessage( -// l1LidoTokensBridge.address, -// l2CrossDomainMessenger.address, -// l1LidoTokensBridge.interface.encodeFunctionData( -// "finalizeERC20Withdrawal", -// [ -// l1TokenRebasable.address, -// l2TokenRebasable.address, -// tokenHolderA.address, -// tokenHolderA.address, -// withdrawalAmountNonRebasable, -// "0x", -// ] -// ), -// 0 -// ); - -// await assert.emits(l1LidoTokensBridge, tx, "ERC20WithdrawalFinalized", [ -// l1TokenRebasable.address, -// l2TokenRebasable.address, -// tokenHolderA.address, -// tokenHolderA.address, -// withdrawalAmountRebasable, -// "0x", -// ]); - -// assert.equalBN( -// await l1Token.balanceOf(l1LidoTokensBridge.address), -// l1ERC20ExtendedTokensBridgeBalanceBefore.sub(withdrawalAmountNonRebasable) -// ); - -// assert.equalBN( -// await l1TokenRebasable.balanceOf(tokenHolderA.address), -// tokenHolderABalanceBefore.add(withdrawalAmountRebasable) -// ); -// }) - - -// .step("L1 -> L2 deposit via depositERC20To()", async (ctx) => { - -// const { -// l1Token, -// l1TokenRebasable, -// l1LidoTokensBridge, -// l2TokenRebasable, -// l1CrossDomainMessenger, -// l2ERC20ExtendedTokensBridge, -// l1Provider -// } = ctx; -// const { accountA: tokenHolderA, accountB: tokenHolderB } = ctx.accounts; -// assert.notEqual(tokenHolderA.address, tokenHolderB.address); - -// const { exchangeRate } = ctx.common; -// const depositAmountNonRebasable = wei`0.03 ether`; -// const depositAmountRebasable = wei.toBigNumber(depositAmountNonRebasable).mul(exchangeRate); - -// await l1TokenRebasable -// .connect(tokenHolderA.l1Signer) -// .approve(l1LidoTokensBridge.address, depositAmountRebasable); - -// const tokenHolderABalanceBefore = await l1TokenRebasable.balanceOf( -// tokenHolderA.address -// ); -// const l1ERC20ExtendedTokensBridgeBalanceBefore = await l1Token.balanceOf( -// l1LidoTokensBridge.address -// ); - -// const tx = await l1LidoTokensBridge -// .connect(tokenHolderA.l1Signer) -// .depositERC20To( -// l1TokenRebasable.address, -// l2TokenRebasable.address, -// tokenHolderB.address, -// depositAmountRebasable, -// 200_000, -// "0x" -// ); - -// const dataToSend = await packedTokenRateAndTimestamp(l1Provider, l1Token); - -// await assert.emits(l1LidoTokensBridge, tx, "ERC20DepositInitiated", [ -// l1TokenRebasable.address, -// l2TokenRebasable.address, -// tokenHolderA.address, -// tokenHolderB.address, -// depositAmountRebasable, -// dataToSend, -// ]); - -// const l2DepositCalldata = l2ERC20ExtendedTokensBridge.interface.encodeFunctionData( -// "finalizeDeposit", -// [ -// l1TokenRebasable.address, -// l2TokenRebasable.address, -// tokenHolderA.address, -// tokenHolderB.address, -// depositAmountNonRebasable, -// dataToSend, -// ] -// ); - -// const messageNonce = await l1CrossDomainMessenger.messageNonce(); - -// await assert.emits(l1CrossDomainMessenger, tx, "SentMessage", [ -// l2ERC20ExtendedTokensBridge.address, -// l1LidoTokensBridge.address, -// l2DepositCalldata, -// messageNonce, -// 200_000, -// ]); - -// assert.equalBN( -// await l1Token.balanceOf(l1LidoTokensBridge.address), -// l1ERC20ExtendedTokensBridgeBalanceBefore.add(depositAmountNonRebasable) -// ); - -// assert.equalBN( -// await l1TokenRebasable.balanceOf(tokenHolderA.address), // stETH -// tokenHolderABalanceBefore.sub(depositAmountRebasable) -// ); -// }) - -// .step("Finalize deposit on L2", async (ctx) => { -// const { -// l1Token, -// l1TokenRebasable, -// l1LidoTokensBridge, -// l2TokenRebasable, -// l2CrossDomainMessenger, -// l2ERC20ExtendedTokensBridge, -// l2Provider -// } = ctx; - -// const { -// accountA: tokenHolderA, -// accountB: tokenHolderB, -// l1CrossDomainMessengerAliased, -// } = ctx.accounts; - -// const { exchangeRate } = ctx.common; - -// const depositAmountNonRebasable = wei`0.03 ether`; -// const depositAmountRebasable = wei.toBigNumber(depositAmountNonRebasable).mul(exchangeRate); - -// const dataToReceive = await packedTokenRateAndTimestamp(l2Provider, l1Token); - -// const l2TokenRebasableTotalSupplyBefore = await l2TokenRebasable.totalSupply(); - -// const tokenHolderBBalanceBefore = await l2TokenRebasable.balanceOf( -// tokenHolderB.address -// ); - -// const tx = await l2CrossDomainMessenger -// .connect(l1CrossDomainMessengerAliased) -// .relayMessage( -// 1, -// l1LidoTokensBridge.address, -// l2ERC20ExtendedTokensBridge.address, -// 0, -// 300_000, -// l2ERC20ExtendedTokensBridge.interface.encodeFunctionData("finalizeDeposit", [ -// l1TokenRebasable.address, -// l2TokenRebasable.address, -// tokenHolderA.address, -// tokenHolderB.address, -// depositAmountNonRebasable, -// dataToReceive, -// ]), -// { gasLimit: 5_000_000 } -// ); - -// await assert.emits(l2ERC20ExtendedTokensBridge, tx, "DepositFinalized", [ -// l1TokenRebasable.address, -// l2TokenRebasable.address, -// tokenHolderA.address, -// tokenHolderB.address, -// depositAmountRebasable, -// "0x", -// ]); - -// assert.equalBN( -// await l2TokenRebasable.balanceOf(tokenHolderB.address), -// tokenHolderBBalanceBefore.add(depositAmountRebasable) -// ); - -// assert.equalBN( -// await l2TokenRebasable.totalSupply(), -// l2TokenRebasableTotalSupplyBefore.add(depositAmountRebasable) -// ); -// }) - -// .step("L2 -> L1 withdrawal via withdrawTo()", async (ctx) => { -// const { l1TokenRebasable, l2TokenRebasable, l2ERC20ExtendedTokensBridge } = ctx; -// const { accountA: tokenHolderA, accountB: tokenHolderB } = ctx.accounts; - -// const { exchangeRate } = ctx.common; -// const withdrawalAmountNonRebasable = wei`0.03 ether`; -// const withdrawalAmountRebasable = wei.toBigNumber(withdrawalAmountNonRebasable).mul(exchangeRate); - -// const tokenHolderBBalanceBefore = await l2TokenRebasable.balanceOf( -// tokenHolderB.address -// ); -// const l2TotalSupplyBefore = await l2TokenRebasable.totalSupply(); - -// const tx = await l2ERC20ExtendedTokensBridge -// .connect(tokenHolderB.l2Signer) -// .withdrawTo( -// l2TokenRebasable.address, -// tokenHolderA.address, -// withdrawalAmountRebasable, -// 0, -// "0x" -// ); - -// await assert.emits(l2ERC20ExtendedTokensBridge, tx, "WithdrawalInitiated", [ -// l1TokenRebasable.address, -// l2TokenRebasable.address, -// tokenHolderB.address, -// tokenHolderA.address, -// withdrawalAmountRebasable, -// "0x", -// ]); - -// assert.equalBN( -// await l2TokenRebasable.balanceOf(tokenHolderB.address), -// tokenHolderBBalanceBefore.sub(withdrawalAmountRebasable) -// ); - -// assert.equalBN( -// await l2TokenRebasable.totalSupply(), -// l2TotalSupplyBefore.sub(withdrawalAmountRebasable) -// ); -// }) - -// .step("Finalize withdrawal on L1", async (ctx) => { -// const { -// l1Token, -// l1TokenRebasable, -// l1CrossDomainMessenger, -// l1LidoTokensBridge, -// l2CrossDomainMessenger, -// l2TokenRebasable, -// l2ERC20ExtendedTokensBridge, -// } = ctx; -// const { -// accountA: tokenHolderA, -// accountB: tokenHolderB, -// l1Stranger, -// } = ctx.accounts; - -// const { exchangeRate } = ctx.common; -// const withdrawalAmountNonRebasable = wei`0.03 ether`; -// const withdrawalAmountRebasable = wei.toBigNumber(withdrawalAmountNonRebasable).mul(exchangeRate); - -// const tokenHolderABalanceBefore = await l1TokenRebasable.balanceOf( -// tokenHolderA.address -// ); -// const l1ERC20ExtendedTokensBridgeBalanceBefore = await l1Token.balanceOf( -// l1LidoTokensBridge.address -// ); - -// await l1CrossDomainMessenger -// .connect(l1Stranger) -// .setXDomainMessageSender(l2ERC20ExtendedTokensBridge.address); - -// const tx = await l1CrossDomainMessenger -// .connect(l1Stranger) -// .relayMessage( -// l1LidoTokensBridge.address, -// l2CrossDomainMessenger.address, -// l1LidoTokensBridge.interface.encodeFunctionData( -// "finalizeERC20Withdrawal", -// [ -// l1TokenRebasable.address, -// l2TokenRebasable.address, -// tokenHolderB.address, -// tokenHolderA.address, -// withdrawalAmountNonRebasable, -// "0x", -// ] -// ), -// 0 -// ); - -// await assert.emits(l1LidoTokensBridge, tx, "ERC20WithdrawalFinalized", [ -// l1TokenRebasable.address, -// l2TokenRebasable.address, -// tokenHolderB.address, -// tokenHolderA.address, -// withdrawalAmountRebasable, -// "0x", -// ]); - -// assert.equalBN( -// await l1Token.balanceOf(l1LidoTokensBridge.address), -// l1ERC20ExtendedTokensBridgeBalanceBefore.sub(withdrawalAmountNonRebasable) -// ); - -// assert.equalBN( -// await l1TokenRebasable.balanceOf(tokenHolderA.address), -// tokenHolderABalanceBefore.add(withdrawalAmountRebasable) -// ); -// }) + // .step("Finalize deposit on L2", async (ctx) => { + // const { + // l1Token, + // l1TokenRebasable, + // l2TokenRebasable, + // l1LidoTokensBridge, + // l2CrossDomainMessenger, + // l2ERC20ExtendedTokensBridge, + // l2Provider + // } = ctx; + // const { depositAmountNonRebasable, depositAmountRebasable } = ctx.common; + + // const { accountA: tokenHolderA, l1CrossDomainMessengerAliased } = + // ctx.accounts; + + // const tokenHolderABalanceBefore = await l2TokenRebasable.balanceOf( + // tokenHolderA.address + // ); + + // const l2TokenRebasableTotalSupplyBefore = await l2TokenRebasable.totalSupply(); + // const dataToReceive = await packedTokenRateAndTimestamp(l2Provider, l1Token); + + // const tx = await l2CrossDomainMessenger + // .connect(l1CrossDomainMessengerAliased) + // .relayMessage( + // 1, + // l1LidoTokensBridge.address, + // l2ERC20ExtendedTokensBridge.address, + // 0, + // 300_000, + // l2ERC20ExtendedTokensBridge.interface.encodeFunctionData("finalizeDeposit", [ + // l1TokenRebasable.address, + // l2TokenRebasable.address, + // tokenHolderA.address, + // tokenHolderA.address, + // depositAmountNonRebasable, + // dataToReceive, + // ]), + // { gasLimit: 5_000_000 } + // ); + + // await assert.emits(l2ERC20ExtendedTokensBridge, tx, "DepositFinalized", [ + // l1TokenRebasable.address, + // l2TokenRebasable.address, + // tokenHolderA.address, + // tokenHolderA.address, + // depositAmountRebasable, + // "0x", + // ]); + + // assert.equalBN( + // await l2TokenRebasable.balanceOf(tokenHolderA.address), + // tokenHolderABalanceBefore.add(depositAmountRebasable) + // ); + // assert.equalBN( + // await l2TokenRebasable.totalSupply(), + // l2TokenRebasableTotalSupplyBefore.add(depositAmountRebasable) + // ); + // }) + + // .step("L2 -> L1 withdrawal via withdraw()", async (ctx) => { + // const { accountA: tokenHolderA } = ctx.accounts; + // const { withdrawalAmountRebasable } = ctx.common; + // const { + // l1TokenRebasable, + // l2TokenRebasable, + // l2ERC20ExtendedTokensBridge + // } = ctx; + + // const tokenHolderABalanceBefore = await l2TokenRebasable.balanceOf( + // tokenHolderA.address + // ); + // const l2TotalSupplyBefore = await l2TokenRebasable.totalSupply(); + + // const tx = await l2ERC20ExtendedTokensBridge + // .connect(tokenHolderA.l2Signer) + // .withdraw( + // l2TokenRebasable.address, + // withdrawalAmountRebasable, + // 0, + // "0x" + // ); + + // await assert.emits(l2ERC20ExtendedTokensBridge, tx, "WithdrawalInitiated", [ + // l1TokenRebasable.address, + // l2TokenRebasable.address, + // tokenHolderA.address, + // tokenHolderA.address, + // withdrawalAmountRebasable, + // "0x", + // ]); + + // assert.equalBN( + // await l2TokenRebasable.balanceOf(tokenHolderA.address), + // tokenHolderABalanceBefore.sub(withdrawalAmountRebasable) + // ); + // assert.equalBN( + // await l2TokenRebasable.totalSupply(), + // l2TotalSupplyBefore.sub(withdrawalAmountRebasable) + // ); + // }) + + // .step("Finalize withdrawal on L1", async (ctx) => { + // const { + // l1Token, + // l1TokenRebasable, + // l1CrossDomainMessenger, + // l1LidoTokensBridge, + // l2CrossDomainMessenger, + // l2TokenRebasable, + // l2ERC20ExtendedTokensBridge, + // } = ctx; + // const { accountA: tokenHolderA, l1Stranger } = ctx.accounts; + // const { withdrawalAmountNonRebasable, withdrawalAmountRebasable } = ctx.common; + + // const tokenHolderABalanceBefore = await l1TokenRebasable.balanceOf( + // tokenHolderA.address + // ); + // const l1ERC20ExtendedTokensBridgeBalanceBefore = await l1Token.balanceOf( + // l1LidoTokensBridge.address + // ); + + // await l1CrossDomainMessenger + // .connect(l1Stranger) + // .setXDomainMessageSender(l2ERC20ExtendedTokensBridge.address); + + // const tx = await l1CrossDomainMessenger + // .connect(l1Stranger) + // .relayMessage( + // l1LidoTokensBridge.address, + // l2CrossDomainMessenger.address, + // l1LidoTokensBridge.interface.encodeFunctionData( + // "finalizeERC20Withdrawal", + // [ + // l1TokenRebasable.address, + // l2TokenRebasable.address, + // tokenHolderA.address, + // tokenHolderA.address, + // withdrawalAmountNonRebasable, + // "0x", + // ] + // ), + // 0 + // ); + + // await assert.emits(l1LidoTokensBridge, tx, "ERC20WithdrawalFinalized", [ + // l1TokenRebasable.address, + // l2TokenRebasable.address, + // tokenHolderA.address, + // tokenHolderA.address, + // withdrawalAmountRebasable, + // "0x", + // ]); + + // assert.equalBN( + // await l1Token.balanceOf(l1LidoTokensBridge.address), + // l1ERC20ExtendedTokensBridgeBalanceBefore.sub(withdrawalAmountNonRebasable) + // ); + + // assert.equalBN( + // await l1TokenRebasable.balanceOf(tokenHolderA.address), + // tokenHolderABalanceBefore.add(withdrawalAmountRebasable) + // ); + // }) + + + // .step("L1 -> L2 deposit via depositERC20To()", async (ctx) => { + + // const { + // l1Token, + // l1TokenRebasable, + // l1LidoTokensBridge, + // l2TokenRebasable, + // l1CrossDomainMessenger, + // l2ERC20ExtendedTokensBridge, + // l1Provider + // } = ctx; + // const { accountA: tokenHolderA, accountB: tokenHolderB } = ctx.accounts; + // assert.notEqual(tokenHolderA.address, tokenHolderB.address); + + // const { exchangeRate } = ctx.common; + // const depositAmountNonRebasable = wei`0.03 ether`; + // const depositAmountRebasable = wei.toBigNumber(depositAmountNonRebasable).mul(exchangeRate); + + // await l1TokenRebasable + // .connect(tokenHolderA.l1Signer) + // .approve(l1LidoTokensBridge.address, depositAmountRebasable); + + // const tokenHolderABalanceBefore = await l1TokenRebasable.balanceOf( + // tokenHolderA.address + // ); + // const l1ERC20ExtendedTokensBridgeBalanceBefore = await l1Token.balanceOf( + // l1LidoTokensBridge.address + // ); + + // const tx = await l1LidoTokensBridge + // .connect(tokenHolderA.l1Signer) + // .depositERC20To( + // l1TokenRebasable.address, + // l2TokenRebasable.address, + // tokenHolderB.address, + // depositAmountRebasable, + // 200_000, + // "0x" + // ); + + // const dataToSend = await packedTokenRateAndTimestamp(l1Provider, l1Token); + + // await assert.emits(l1LidoTokensBridge, tx, "ERC20DepositInitiated", [ + // l1TokenRebasable.address, + // l2TokenRebasable.address, + // tokenHolderA.address, + // tokenHolderB.address, + // depositAmountRebasable, + // dataToSend, + // ]); + + // const l2DepositCalldata = l2ERC20ExtendedTokensBridge.interface.encodeFunctionData( + // "finalizeDeposit", + // [ + // l1TokenRebasable.address, + // l2TokenRebasable.address, + // tokenHolderA.address, + // tokenHolderB.address, + // depositAmountNonRebasable, + // dataToSend, + // ] + // ); + + // const messageNonce = await l1CrossDomainMessenger.messageNonce(); + + // await assert.emits(l1CrossDomainMessenger, tx, "SentMessage", [ + // l2ERC20ExtendedTokensBridge.address, + // l1LidoTokensBridge.address, + // l2DepositCalldata, + // messageNonce, + // 200_000, + // ]); + + // assert.equalBN( + // await l1Token.balanceOf(l1LidoTokensBridge.address), + // l1ERC20ExtendedTokensBridgeBalanceBefore.add(depositAmountNonRebasable) + // ); + + // assert.equalBN( + // await l1TokenRebasable.balanceOf(tokenHolderA.address), // stETH + // tokenHolderABalanceBefore.sub(depositAmountRebasable) + // ); + // }) + + // .step("Finalize deposit on L2", async (ctx) => { + // const { + // l1Token, + // l1TokenRebasable, + // l1LidoTokensBridge, + // l2TokenRebasable, + // l2CrossDomainMessenger, + // l2ERC20ExtendedTokensBridge, + // l2Provider + // } = ctx; + + // const { + // accountA: tokenHolderA, + // accountB: tokenHolderB, + // l1CrossDomainMessengerAliased, + // } = ctx.accounts; + + // const { exchangeRate } = ctx.common; + + // const depositAmountNonRebasable = wei`0.03 ether`; + // const depositAmountRebasable = wei.toBigNumber(depositAmountNonRebasable).mul(exchangeRate); + + // const dataToReceive = await packedTokenRateAndTimestamp(l2Provider, l1Token); + + // const l2TokenRebasableTotalSupplyBefore = await l2TokenRebasable.totalSupply(); + + // const tokenHolderBBalanceBefore = await l2TokenRebasable.balanceOf( + // tokenHolderB.address + // ); + + // const tx = await l2CrossDomainMessenger + // .connect(l1CrossDomainMessengerAliased) + // .relayMessage( + // 1, + // l1LidoTokensBridge.address, + // l2ERC20ExtendedTokensBridge.address, + // 0, + // 300_000, + // l2ERC20ExtendedTokensBridge.interface.encodeFunctionData("finalizeDeposit", [ + // l1TokenRebasable.address, + // l2TokenRebasable.address, + // tokenHolderA.address, + // tokenHolderB.address, + // depositAmountNonRebasable, + // dataToReceive, + // ]), + // { gasLimit: 5_000_000 } + // ); + + // await assert.emits(l2ERC20ExtendedTokensBridge, tx, "DepositFinalized", [ + // l1TokenRebasable.address, + // l2TokenRebasable.address, + // tokenHolderA.address, + // tokenHolderB.address, + // depositAmountRebasable, + // "0x", + // ]); + + // assert.equalBN( + // await l2TokenRebasable.balanceOf(tokenHolderB.address), + // tokenHolderBBalanceBefore.add(depositAmountRebasable) + // ); + + // assert.equalBN( + // await l2TokenRebasable.totalSupply(), + // l2TokenRebasableTotalSupplyBefore.add(depositAmountRebasable) + // ); + // }) + + // .step("L2 -> L1 withdrawal via withdrawTo()", async (ctx) => { + // const { l1TokenRebasable, l2TokenRebasable, l2ERC20ExtendedTokensBridge } = ctx; + // const { accountA: tokenHolderA, accountB: tokenHolderB } = ctx.accounts; + + // const { exchangeRate } = ctx.common; + // const withdrawalAmountNonRebasable = wei`0.03 ether`; + // const withdrawalAmountRebasable = wei.toBigNumber(withdrawalAmountNonRebasable).mul(exchangeRate); + + // const tokenHolderBBalanceBefore = await l2TokenRebasable.balanceOf( + // tokenHolderB.address + // ); + // const l2TotalSupplyBefore = await l2TokenRebasable.totalSupply(); + + // const tx = await l2ERC20ExtendedTokensBridge + // .connect(tokenHolderB.l2Signer) + // .withdrawTo( + // l2TokenRebasable.address, + // tokenHolderA.address, + // withdrawalAmountRebasable, + // 0, + // "0x" + // ); + + // await assert.emits(l2ERC20ExtendedTokensBridge, tx, "WithdrawalInitiated", [ + // l1TokenRebasable.address, + // l2TokenRebasable.address, + // tokenHolderB.address, + // tokenHolderA.address, + // withdrawalAmountRebasable, + // "0x", + // ]); + + // assert.equalBN( + // await l2TokenRebasable.balanceOf(tokenHolderB.address), + // tokenHolderBBalanceBefore.sub(withdrawalAmountRebasable) + // ); + + // assert.equalBN( + // await l2TokenRebasable.totalSupply(), + // l2TotalSupplyBefore.sub(withdrawalAmountRebasable) + // ); + // }) + + // .step("Finalize withdrawal on L1", async (ctx) => { + // const { + // l1Token, + // l1TokenRebasable, + // l1CrossDomainMessenger, + // l1LidoTokensBridge, + // l2CrossDomainMessenger, + // l2TokenRebasable, + // l2ERC20ExtendedTokensBridge, + // } = ctx; + // const { + // accountA: tokenHolderA, + // accountB: tokenHolderB, + // l1Stranger, + // } = ctx.accounts; + + // const { exchangeRate } = ctx.common; + // const withdrawalAmountNonRebasable = wei`0.03 ether`; + // const withdrawalAmountRebasable = wei.toBigNumber(withdrawalAmountNonRebasable).mul(exchangeRate); + + // const tokenHolderABalanceBefore = await l1TokenRebasable.balanceOf( + // tokenHolderA.address + // ); + // const l1ERC20ExtendedTokensBridgeBalanceBefore = await l1Token.balanceOf( + // l1LidoTokensBridge.address + // ); + + // await l1CrossDomainMessenger + // .connect(l1Stranger) + // .setXDomainMessageSender(l2ERC20ExtendedTokensBridge.address); + + // const tx = await l1CrossDomainMessenger + // .connect(l1Stranger) + // .relayMessage( + // l1LidoTokensBridge.address, + // l2CrossDomainMessenger.address, + // l1LidoTokensBridge.interface.encodeFunctionData( + // "finalizeERC20Withdrawal", + // [ + // l1TokenRebasable.address, + // l2TokenRebasable.address, + // tokenHolderB.address, + // tokenHolderA.address, + // withdrawalAmountNonRebasable, + // "0x", + // ] + // ), + // 0 + // ); + + // await assert.emits(l1LidoTokensBridge, tx, "ERC20WithdrawalFinalized", [ + // l1TokenRebasable.address, + // l2TokenRebasable.address, + // tokenHolderB.address, + // tokenHolderA.address, + // withdrawalAmountRebasable, + // "0x", + // ]); + + // assert.equalBN( + // await l1Token.balanceOf(l1LidoTokensBridge.address), + // l1ERC20ExtendedTokensBridgeBalanceBefore.sub(withdrawalAmountNonRebasable) + // ); + + // assert.equalBN( + // await l1TokenRebasable.balanceOf(tokenHolderA.address), + // tokenHolderABalanceBefore.add(withdrawalAmountRebasable) + // ); + // }) .run(); @@ -851,10 +851,10 @@ async function ctxFactory() { } async function packedTokenRateAndTimestamp(l1Provider: JsonRpcProvider, l1Token: ERC20WrapperStub) { - const stEthPerToken = await l1Token.stEthPerToken(); - const blockNumber = await l1Provider.getBlockNumber(); - const blockTimestamp = (await l1Provider.getBlock(blockNumber)).timestamp; - const stEthPerTokenStr = ethers.utils.hexZeroPad(stEthPerToken.toHexString(), 12); - const blockTimestampStr = ethers.utils.hexZeroPad(ethers.utils.hexlify(blockTimestamp), 5); - return ethers.utils.hexConcat([stEthPerTokenStr, blockTimestampStr]); + const stEthPerToken = await l1Token.stEthPerToken(); + const blockNumber = await l1Provider.getBlockNumber(); + const blockTimestamp = (await l1Provider.getBlock(blockNumber)).timestamp; + const stEthPerTokenStr = ethers.utils.hexZeroPad(stEthPerToken.toHexString(), 12); + const blockTimestampStr = ethers.utils.hexZeroPad(ethers.utils.hexlify(blockTimestamp), 5); + return ethers.utils.hexConcat([stEthPerTokenStr, blockTimestampStr]); } diff --git a/test/optimism/bridging.integration.test.ts b/test/optimism/bridging.integration.test.ts index 9ec87c7d..e640718d 100644 --- a/test/optimism/bridging.integration.test.ts +++ b/test/optimism/bridging.integration.test.ts @@ -91,7 +91,7 @@ scenario("Optimism :: Bridging non-rebasable token integration test", ctxFactory tokenHolderA.address ); const l1ERC20ExtendedTokensBridgeBalanceBefore = await l1Token.balanceOf( - l1LidoTokensBridge.address + l1LidoTokensBridge.address ); const tx = await l1LidoTokensBridge @@ -251,7 +251,7 @@ scenario("Optimism :: Bridging non-rebasable token integration test", ctxFactory tokenHolderA.address ); const l1ERC20ExtendedTokensBridgeBalanceBefore = await l1Token.balanceOf( - l1LidoTokensBridge.address + l1LidoTokensBridge.address ); await l1CrossDomainMessenger @@ -318,7 +318,7 @@ scenario("Optimism :: Bridging non-rebasable token integration test", ctxFactory tokenHolderA.address ); const l1ERC20ExtendedTokensBridgeBalanceBefore = await l1Token.balanceOf( - l1LidoTokensBridge.address + l1LidoTokensBridge.address ); const tx = await l1LidoTokensBridge @@ -496,7 +496,7 @@ scenario("Optimism :: Bridging non-rebasable token integration test", ctxFactory tokenHolderA.address ); const l1ERC20ExtendedTokensBridgeBalanceBefore = await l1Token.balanceOf( - l1LidoTokensBridge.address + l1LidoTokensBridge.address ); await l1CrossDomainMessenger @@ -623,10 +623,10 @@ async function ctxFactory() { } async function packedTokenRateAndTimestamp(l1Provider: JsonRpcProvider, l1Token: ERC20WrapperStub) { - const stEthPerToken = await l1Token.stEthPerToken(); - const blockNumber = await l1Provider.getBlockNumber(); - const blockTimestamp = (await l1Provider.getBlock(blockNumber)).timestamp; - const stEthPerTokenStr = ethers.utils.hexZeroPad(stEthPerToken.toHexString(), 12); - const blockTimestampStr = ethers.utils.hexZeroPad(ethers.utils.hexlify(blockTimestamp), 5); - return ethers.utils.hexConcat([stEthPerTokenStr, blockTimestampStr]); + const stEthPerToken = await l1Token.stEthPerToken(); + const blockNumber = await l1Provider.getBlockNumber(); + const blockTimestamp = (await l1Provider.getBlock(blockNumber)).timestamp; + const stEthPerTokenStr = ethers.utils.hexZeroPad(stEthPerToken.toHexString(), 12); + const blockTimestampStr = ethers.utils.hexZeroPad(ethers.utils.hexlify(blockTimestamp), 5); + return ethers.utils.hexConcat([stEthPerTokenStr, blockTimestampStr]); } diff --git a/test/optimism/deposit-gas-estimation.test.ts b/test/optimism/deposit-gas-estimation.test.ts index e6d4857e..e110a17e 100644 --- a/test/optimism/deposit-gas-estimation.test.ts +++ b/test/optimism/deposit-gas-estimation.test.ts @@ -92,24 +92,24 @@ scenario("Optimism :: Bridging integration test", ctxFactory) const tokenHolderABalanceBefore = await l1Token.balanceOf( tokenHolderA.address ); - console.log("tokenHolderABalanceBefore=",tokenHolderABalanceBefore); + console.log("tokenHolderABalanceBefore=", tokenHolderABalanceBefore); const l1ERC20ExtendedTokensBridgeBalanceBefore = await l1TokenRebasable.balanceOf( - l1LidoTokensBridge.address + l1LidoTokensBridge.address ); const tx0 = await l1LidoTokensBridge - .connect(tokenHolderA.l1Signer) - .depositERC20( - l1Token.address, - l2Token.address, - 10, - 200_000, - "0x" - ); + .connect(tokenHolderA.l1Signer) + .depositERC20( + l1Token.address, + l2Token.address, + 10, + 200_000, + "0x" + ); const receipt0 = await tx0.wait(); - console.log("l1Token gasUsed=",receipt0.gasUsed); + console.log("l1Token gasUsed=", receipt0.gasUsed); const tx1 = await l1LidoTokensBridge .connect(tokenHolderA.l1Signer) @@ -121,11 +121,11 @@ scenario("Optimism :: Bridging integration test", ctxFactory) "0x" ); - const receipt1 = await tx1.wait(); - console.log("l1TokenRebasable gasUsed=",receipt1.gasUsed); + const receipt1 = await tx1.wait(); + console.log("l1TokenRebasable gasUsed=", receipt1.gasUsed); - const gasDifference = receipt1.gasUsed.sub(receipt0.gasUsed); - console.log("gasUsed difference=", gasDifference); + const gasDifference = receipt1.gasUsed.sub(receipt0.gasUsed); + console.log("gasUsed difference=", gasDifference); }) @@ -134,7 +134,7 @@ scenario("Optimism :: Bridging integration test", ctxFactory) async function ctxFactory() { const networkName = env.network("TESTING_OPT_NETWORK", "mainnet"); - console.log("networkName=",networkName); + console.log("networkName=", networkName); const { l1Provider, @@ -172,8 +172,8 @@ async function ctxFactory() { ); await contracts.l1Token - .connect(contracts.l1TokensHolder) - .transfer(accountA.l1Signer.address, depositAmount); + .connect(contracts.l1TokensHolder) + .transfer(accountA.l1Signer.address, depositAmount); await contracts.l1TokenRebasable .connect(contracts.l1TokensHolder) @@ -184,8 +184,8 @@ async function ctxFactory() { l2Provider ); - console.log("l1CrossDomainMessengerAliased=",l1CrossDomainMessengerAliased); - console.log("contracts.l1CrossDomainMessenger.address=",contracts.l1CrossDomainMessenger.address); + console.log("l1CrossDomainMessengerAliased=", l1CrossDomainMessengerAliased); + console.log("contracts.l1CrossDomainMessenger.address=", contracts.l1CrossDomainMessenger.address); await testing.setBalance( await l1CrossDomainMessengerAliased.getAddress(), diff --git a/test/optimism/managing-proxy.e2e.test.ts b/test/optimism/managing-proxy.e2e.test.ts index bed76248..8c5ddcc2 100644 --- a/test/optimism/managing-proxy.e2e.test.ts +++ b/test/optimism/managing-proxy.e2e.test.ts @@ -40,9 +40,9 @@ scenario( ["proxy__upgradeTo(address)"], [ "0x" + - ctx.proxyToOssify.interface - .encodeFunctionData("proxy__upgradeTo", [ctx.l2Token.address]) - .substring(10), + ctx.proxyToOssify.interface + .encodeFunctionData("proxy__upgradeTo", [ctx.l2Token.address]) + .substring(10), ], [false], ]); diff --git a/test/optimism/pushingTokenRate.e2e.test.ts b/test/optimism/pushingTokenRate.e2e.test.ts index 06fb2fb5..99f36d93 100644 --- a/test/optimism/pushingTokenRate.e2e.test.ts +++ b/test/optimism/pushingTokenRate.e2e.test.ts @@ -3,94 +3,94 @@ import env from "../../utils/env"; import network, { SignerOrProvider } from "../../utils/network"; import testingUtils, { scenario } from "../../utils/testing"; import { - ERC20WrapperStub__factory, - TokenRateNotifier__factory, - TokenRateOracle__factory + ERC20WrapperStub__factory, + TokenRateNotifier__factory, + TokenRateOracle__factory } from "../../typechain"; scenario("Optimism :: Push token rate to Oracle E2E test", ctxFactory) - .step("Push Token Rate", async (ctx) => { - await ctx.tokenRateNotifier - .connect(ctx.l1Tester) - .handlePostTokenRebase(1, 2, 3, 4, 5, 6, 7); - }) + .step("Push Token Rate", async (ctx) => { + await ctx.tokenRateNotifier + .connect(ctx.l1Tester) + .handlePostTokenRebase(1, 2, 3, 4, 5, 6, 7); + }) - .step("Receive token rate", async (ctx) => { - const tokenRate = await ctx.l1Token.stEthPerToken(); + .step("Receive token rate", async (ctx) => { + const tokenRate = await ctx.l1Token.stEthPerToken(); - const answer = await ctx.tokenRateOracle.latestAnswer(); - assert.equalBN(answer, tokenRate); + const answer = await ctx.tokenRateOracle.latestAnswer(); + assert.equalBN(answer, tokenRate); - const [ - , - latestRoundDataAnswer, - , - , - ] = await ctx.tokenRateOracle.latestRoundData(); - assert.equalBN(latestRoundDataAnswer, tokenRate); - }) + const [ + , + latestRoundDataAnswer, + , + , + ] = await ctx.tokenRateOracle.latestRoundData(); + assert.equalBN(latestRoundDataAnswer, tokenRate); + }) - .run(); + .run(); async function ctxFactory() { - const testingSetup = await getE2ETestSetup(); + const testingSetup = await getE2ETestSetup(); - return { - l1Tester: testingSetup.l1Tester, - l2Tester: testingSetup.l2Tester, - l1Provider: testingSetup.l1Provider, - l2Provider: testingSetup.l2Provider, - l1Token: testingSetup.l1Token, - tokenRateNotifier: testingSetup.tokenRateNotifier, - tokenRateOracle: testingSetup.tokenRateOracle - }; + return { + l1Tester: testingSetup.l1Tester, + l2Tester: testingSetup.l2Tester, + l1Provider: testingSetup.l1Provider, + l2Provider: testingSetup.l2Provider, + l1Token: testingSetup.l1Token, + tokenRateNotifier: testingSetup.tokenRateNotifier, + tokenRateOracle: testingSetup.tokenRateOracle + }; } async function getE2ETestSetup() { - const testerPrivateKey = testingUtils.env.TESTING_PRIVATE_KEY(); - const networkName = env.network("TESTING_OPT_NETWORK", "sepolia"); + const testerPrivateKey = testingUtils.env.TESTING_PRIVATE_KEY(); + const networkName = env.network("TESTING_OPT_NETWORK", "sepolia"); - const ethOptNetworks = network.multichain(["eth", "opt"], networkName); + const ethOptNetworks = network.multichain(["eth", "opt"], networkName); - const [ethProvider, optProvider] = ethOptNetworks.getProviders({ - forking: false, - }); - const [l1Tester, l2Tester] = ethOptNetworks.getSigners(testerPrivateKey, { - forking: false, - }); + const [ethProvider, optProvider] = ethOptNetworks.getProviders({ + forking: false, + }); + const [l1Tester, l2Tester] = ethOptNetworks.getSigners(testerPrivateKey, { + forking: false, + }); - const contracts = await loadDeployedContracts(l1Tester, l2Tester); + const contracts = await loadDeployedContracts(l1Tester, l2Tester); - // await printLoadedTestConfig(networkName, bridgeContracts, l1Tester); + // await printLoadedTestConfig(networkName, bridgeContracts, l1Tester); - return { - l1Tester, - l2Tester, - l1Provider: ethProvider, - l2Provider: optProvider, - ...contracts, - }; + return { + l1Tester, + l2Tester, + l1Provider: ethProvider, + l2Provider: optProvider, + ...contracts, + }; } async function loadDeployedContracts( - l1SignerOrProvider: SignerOrProvider, - l2SignerOrProvider: SignerOrProvider + l1SignerOrProvider: SignerOrProvider, + l2SignerOrProvider: SignerOrProvider ) { - return { - l1Token: ERC20WrapperStub__factory.connect( - testingUtils.env.OPT_L1_TOKEN(), - l1SignerOrProvider - ), - tokenRateNotifier: TokenRateNotifier__factory.connect( - testingUtils.env.OPT_L1_TOKEN_RATE_NOTIFIER(), - l1SignerOrProvider - ), - tokenRateOracle: TokenRateOracle__factory.connect( - testingUtils.env.OPT_L2_TOKEN_RATE_ORACLE(), - l2SignerOrProvider - ), - l1SignerOrProvider, - l2SignerOrProvider - }; + return { + l1Token: ERC20WrapperStub__factory.connect( + testingUtils.env.OPT_L1_TOKEN(), + l1SignerOrProvider + ), + tokenRateNotifier: TokenRateNotifier__factory.connect( + testingUtils.env.OPT_L1_TOKEN_RATE_NOTIFIER(), + l1SignerOrProvider + ), + tokenRateOracle: TokenRateOracle__factory.connect( + testingUtils.env.OPT_L2_TOKEN_RATE_ORACLE(), + l2SignerOrProvider + ), + l1SignerOrProvider, + l2SignerOrProvider + }; } diff --git a/test/optimism/pushingTokenRate.integration.test.ts b/test/optimism/pushingTokenRate.integration.test.ts index ff9adae3..b96fe79f 100644 --- a/test/optimism/pushingTokenRate.integration.test.ts +++ b/test/optimism/pushingTokenRate.integration.test.ts @@ -9,226 +9,226 @@ import deploymentOracle from "../../utils/optimism/deploymentOracle"; import { getBridgeExecutorParams } from "../../utils/bridge-executor"; import { BigNumber } from "ethers"; import { - ERC20BridgedStub__factory, - ERC20WrapperStub__factory, - OptimismBridgeExecutor__factory, - TokenRateNotifier__factory, - TokenRateOracle__factory + ERC20BridgedStub__factory, + ERC20WrapperStub__factory, + OptimismBridgeExecutor__factory, + TokenRateNotifier__factory, + TokenRateOracle__factory } from "../../typechain"; scenario("Optimism :: Token Rate Oracle integration test", ctxFactory) - .step("Push Token Rate", async (ctx) => { - const { - tokenRateNotifier, - tokenRateOracle, - opTokenRatePusher, - l1CrossDomainMessenger, - l1Token, - l1Provider - } = ctx; - - const tokenRate = await l1Token.stEthPerToken(); - - const account = ctx.accounts.accountA; - - const tx = await tokenRateNotifier - .connect(account.l1Signer) - .handlePostTokenRebase(1, 2, 3, 4, 5, 6, 7); - - const blockNumber = await l1Provider.getBlockNumber(); - const blockTimestamp = BigNumber.from((await l1Provider.getBlock(blockNumber)).timestamp); - - const messageNonce = await l1CrossDomainMessenger.messageNonce(); - const [stEthPerTokenStr, blockTimestampStr] = await tokenRateAndTimestamp(tokenRate, blockTimestamp); - const l2Calldata = tokenRateOracle.interface.encodeFunctionData( - "updateRate", - [ - stEthPerTokenStr, - blockTimestampStr - ] - ); - - await assert.emits(l1CrossDomainMessenger, tx, "SentMessage", [ - tokenRateOracle.address, - opTokenRatePusher, - l2Calldata, - messageNonce, - 1000, - ]); - }) - - .step("Finalize pushing rate", async (ctx) => { - const { - opTokenRatePusher, - tokenRateOracle, - l1Token, - l1Provider, - l1CrossDomainMessenger - } = ctx; - - const account = ctx.accounts.accountA; - await l1CrossDomainMessenger - .connect(account.l1Signer) - .setXDomainMessageSender(opTokenRatePusher); - - const blockNumber = await l1Provider.getBlockNumber(); - const blockTimestamp = BigNumber.from((await l1Provider.getBlock(blockNumber)).timestamp); - - const tokenRate = await l1Token.stEthPerToken(); - const [stEthPerTokenStr, blockTimestampStr] = await tokenRateAndTimestamp(tokenRate, blockTimestamp); - - const tx = await ctx.l2CrossDomainMessenger - .connect(ctx.accounts.l1CrossDomainMessengerAliased) - .relayMessage( - 1, - opTokenRatePusher, - tokenRateOracle.address, - 0, - 300_000, - tokenRateOracle.interface.encodeFunctionData("updateRate", [ - stEthPerTokenStr, - blockTimestampStr - ]), - { gasLimit: 5_000_000 } - ); - - const answer = await tokenRateOracle.latestAnswer(); - assert.equalBN(answer, tokenRate); - - const [ - , - tokenRateAnswer, - , - updatedAt, - - ] = await tokenRateOracle.latestRoundData(); - - assert.equalBN(tokenRateAnswer, tokenRate); - assert.equalBN(updatedAt, blockTimestampStr); - }) - - .run(); - -async function ctxFactory() { - const networkName = env.network("TESTING_OPT_NETWORK", "mainnet"); - const [l1Provider, l2Provider] = network - .multichain(["eth", "opt"], networkName) - .getProviders({ forking: true }); - const l1Deployer = testing.accounts.deployer(l1Provider); - const l2Deployer = testing.accounts.deployer(l2Provider); - - const blockNumber = await l2Provider.getBlockNumber(); - const blockTimestamp = BigNumber.from((await l2Provider.getBlock(blockNumber)).timestamp); - - const blockTimestampInPast = BigNumber.from((await l2Provider.getBlock(blockNumber)).timestamp).sub(86400); - const tokenRate = BigNumber.from('1164454276599657236'); - - const optContracts = optimism.contracts(networkName, { forking: true }); - const l2CrossDomainMessenger = optContracts.L2CrossDomainMessenger; - const testingOnDeployedContracts = testing.env.USE_DEPLOYED_CONTRACTS(false); - const optAddresses = optimism.addresses(networkName); - - const govBridgeExecutor = testingOnDeployedContracts - ? OptimismBridgeExecutor__factory.connect( - testing.env.OPT_GOV_BRIDGE_EXECUTOR(), - l2Provider - ) - : await new OptimismBridgeExecutor__factory(l2Deployer).deploy( - optAddresses.L2CrossDomainMessenger, - l1Deployer.address, - ...getBridgeExecutorParams(), - l2Deployer.address - ); - - const l1TokenRebasable = await new ERC20BridgedStub__factory(l1Deployer).deploy( - "Test Token Rebasable", - "TTR" - ); - const l1Token = await new ERC20WrapperStub__factory(l1Deployer).deploy( - l1TokenRebasable.address, - "Test Token", - "TT", - tokenRate + .step("Push Token Rate", async (ctx) => { + const { + tokenRateNotifier, + tokenRateOracle, + opTokenRatePusher, + l1CrossDomainMessenger, + l1Token, + l1Provider + } = ctx; + + const tokenRate = await l1Token.stEthPerToken(); + + const account = ctx.accounts.accountA; + + const tx = await tokenRateNotifier + .connect(account.l1Signer) + .handlePostTokenRebase(1, 2, 3, 4, 5, 6, 7); + + const blockNumber = await l1Provider.getBlockNumber(); + const blockTimestamp = BigNumber.from((await l1Provider.getBlock(blockNumber)).timestamp); + + const messageNonce = await l1CrossDomainMessenger.messageNonce(); + const [stEthPerTokenStr, blockTimestampStr] = await tokenRateAndTimestamp(tokenRate, blockTimestamp); + const l2Calldata = tokenRateOracle.interface.encodeFunctionData( + "updateRate", + [ + stEthPerTokenStr, + blockTimestampStr + ] ); - const [ethDeployScript, optDeployScript] = await deploymentOracle( - networkName - ).oracleDeployScript( - l1Token.address, - 1000, - 86400, - { - deployer: l1Deployer, - admins: { - proxy: l1Deployer.address, - bridge: l1Deployer.address - }, - contractsShift: 0 - }, - { - deployer: l2Deployer, - admins: { - proxy: govBridgeExecutor.address, - bridge: govBridgeExecutor.address, - }, - contractsShift: 0, - tokenRateOracle: { - maxAllowedL2ToL1ClockLag: BigNumber.from(86400), - maxAllowedTokenRateDeviationPerDay: BigNumber.from(500), - tokenRate: tokenRate, - l1Timestamp: BigNumber.from(blockTimestampInPast) - } - } - ); - - await ethDeployScript.run(); - await optDeployScript.run(); - await optimism.testing(networkName).stubL1CrossChainMessengerContract(); + await assert.emits(l1CrossDomainMessenger, tx, "SentMessage", [ + tokenRateOracle.address, + opTokenRatePusher, + l2Calldata, + messageNonce, + 1000, + ]); + }) + + .step("Finalize pushing rate", async (ctx) => { + const { + opTokenRatePusher, + tokenRateOracle, + l1Token, + l1Provider, + l1CrossDomainMessenger + } = ctx; + + const account = ctx.accounts.accountA; + await l1CrossDomainMessenger + .connect(account.l1Signer) + .setXDomainMessageSender(opTokenRatePusher); + + const blockNumber = await l1Provider.getBlockNumber(); + const blockTimestamp = BigNumber.from((await l1Provider.getBlock(blockNumber)).timestamp); + + const tokenRate = await l1Token.stEthPerToken(); + const [stEthPerTokenStr, blockTimestampStr] = await tokenRateAndTimestamp(tokenRate, blockTimestamp); + + const tx = await ctx.l2CrossDomainMessenger + .connect(ctx.accounts.l1CrossDomainMessengerAliased) + .relayMessage( + 1, + opTokenRatePusher, + tokenRateOracle.address, + 0, + 300_000, + tokenRateOracle.interface.encodeFunctionData("updateRate", [ + stEthPerTokenStr, + blockTimestampStr + ]), + { gasLimit: 5_000_000 } + ); + + const answer = await tokenRateOracle.latestAnswer(); + assert.equalBN(answer, tokenRate); + + const [ + , + tokenRateAnswer, + , + updatedAt, + + ] = await tokenRateOracle.latestRoundData(); + + assert.equalBN(tokenRateAnswer, tokenRate); + assert.equalBN(updatedAt, blockTimestampStr); + }) + + .run(); - const l1CrossDomainMessengerAliased = await testing.impersonate( - testing.accounts.applyL1ToL2Alias(optContracts.L1CrossDomainMessengerStub.address), - l2Provider - ); - await testing.setBalance( - await l1CrossDomainMessengerAliased.getAddress(), - wei.toBigNumber(wei`1 ether`), - l2Provider - ); - - const tokenRateNotifier = TokenRateNotifier__factory.connect( - ethDeployScript.tokenRateNotifierImplAddress, - l1Provider - ); - await tokenRateNotifier - .connect(l1Deployer) - .addObserver(ethDeployScript.opStackTokenRatePusherImplAddress); - const tokenRateOracle = TokenRateOracle__factory.connect( - optDeployScript.tokenRateOracleProxyAddress, - l2Provider +async function ctxFactory() { + const networkName = env.network("TESTING_OPT_NETWORK", "mainnet"); + const [l1Provider, l2Provider] = network + .multichain(["eth", "opt"], networkName) + .getProviders({ forking: true }); + const l1Deployer = testing.accounts.deployer(l1Provider); + const l2Deployer = testing.accounts.deployer(l2Provider); + + const blockNumber = await l2Provider.getBlockNumber(); + const blockTimestamp = BigNumber.from((await l2Provider.getBlock(blockNumber)).timestamp); + + const blockTimestampInPast = BigNumber.from((await l2Provider.getBlock(blockNumber)).timestamp).sub(86400); + const tokenRate = BigNumber.from('1164454276599657236'); + + const optContracts = optimism.contracts(networkName, { forking: true }); + const l2CrossDomainMessenger = optContracts.L2CrossDomainMessenger; + const testingOnDeployedContracts = testing.env.USE_DEPLOYED_CONTRACTS(false); + const optAddresses = optimism.addresses(networkName); + + const govBridgeExecutor = testingOnDeployedContracts + ? OptimismBridgeExecutor__factory.connect( + testing.env.OPT_GOV_BRIDGE_EXECUTOR(), + l2Provider + ) + : await new OptimismBridgeExecutor__factory(l2Deployer).deploy( + optAddresses.L2CrossDomainMessenger, + l1Deployer.address, + ...getBridgeExecutorParams(), + l2Deployer.address ); - const accountA = testing.accounts.accountA(l1Provider, l2Provider); - const l1CrossDomainMessenger = optContracts.L1CrossDomainMessengerStub; - - return { - tokenRateNotifier, - tokenRateOracle, - opTokenRatePusher: ethDeployScript.opStackTokenRatePusherImplAddress, - l1CrossDomainMessenger, - l2CrossDomainMessenger, - l1Token, - l1Provider, - blockTimestamp, - accounts: { - accountA, - l1CrossDomainMessengerAliased - } - }; + const l1TokenRebasable = await new ERC20BridgedStub__factory(l1Deployer).deploy( + "Test Token Rebasable", + "TTR" + ); + const l1Token = await new ERC20WrapperStub__factory(l1Deployer).deploy( + l1TokenRebasable.address, + "Test Token", + "TT", + tokenRate + ); + const [ethDeployScript, optDeployScript] = await deploymentOracle( + networkName + ).oracleDeployScript( + l1Token.address, + 1000, + 86400, + { + deployer: l1Deployer, + admins: { + proxy: l1Deployer.address, + bridge: l1Deployer.address + }, + contractsShift: 0 + }, + { + deployer: l2Deployer, + admins: { + proxy: govBridgeExecutor.address, + bridge: govBridgeExecutor.address, + }, + contractsShift: 0, + tokenRateOracle: { + maxAllowedL2ToL1ClockLag: BigNumber.from(86400), + maxAllowedTokenRateDeviationPerDay: BigNumber.from(500), + tokenRate: tokenRate, + l1Timestamp: BigNumber.from(blockTimestampInPast) + } + } + ); + + await ethDeployScript.run(); + await optDeployScript.run(); + + await optimism.testing(networkName).stubL1CrossChainMessengerContract(); + + const l1CrossDomainMessengerAliased = await testing.impersonate( + testing.accounts.applyL1ToL2Alias(optContracts.L1CrossDomainMessengerStub.address), + l2Provider + ); + await testing.setBalance( + await l1CrossDomainMessengerAliased.getAddress(), + wei.toBigNumber(wei`1 ether`), + l2Provider + ); + + const tokenRateNotifier = TokenRateNotifier__factory.connect( + ethDeployScript.tokenRateNotifierImplAddress, + l1Provider + ); + await tokenRateNotifier + .connect(l1Deployer) + .addObserver(ethDeployScript.opStackTokenRatePusherImplAddress); + const tokenRateOracle = TokenRateOracle__factory.connect( + optDeployScript.tokenRateOracleProxyAddress, + l2Provider + ); + + const accountA = testing.accounts.accountA(l1Provider, l2Provider); + const l1CrossDomainMessenger = optContracts.L1CrossDomainMessengerStub; + + return { + tokenRateNotifier, + tokenRateOracle, + opTokenRatePusher: ethDeployScript.opStackTokenRatePusherImplAddress, + l1CrossDomainMessenger, + l2CrossDomainMessenger, + l1Token, + l1Provider, + blockTimestamp, + accounts: { + accountA, + l1CrossDomainMessengerAliased + } + }; } async function tokenRateAndTimestamp(tokenRate: BigNumber, blockTimestamp: BigNumber) { - const stEthPerTokenStr = ethers.utils.hexZeroPad(tokenRate.toHexString(), 12); - const blockTimestampStr = ethers.utils.hexZeroPad(ethers.utils.hexlify(blockTimestamp), 5); - return [stEthPerTokenStr, blockTimestampStr]; + const stEthPerTokenStr = ethers.utils.hexZeroPad(tokenRate.toHexString(), 12); + const blockTimestampStr = ethers.utils.hexZeroPad(ethers.utils.hexlify(blockTimestamp), 5); + return [stEthPerTokenStr, blockTimestampStr]; } diff --git a/test/token/ERC20BridgedPermit.unit.test.ts b/test/token/ERC20BridgedPermit.unit.test.ts index ddd0082c..53405b1d 100644 --- a/test/token/ERC20BridgedPermit.unit.test.ts +++ b/test/token/ERC20BridgedPermit.unit.test.ts @@ -1,20 +1,21 @@ import { assert } from "chai"; import hre from "hardhat"; +import { BigNumber } from "ethers"; +import { unit } from "../../utils/testing"; +import { wei } from "../../utils/wei"; +import { erc20BridgedPermitUnderProxy } from "../../utils/testing/contractsFactory"; import { ERC20BridgedPermit__factory, + ERC20BridgedWithInitializerStub__factory, OssifiableProxy__factory, } from "../../typechain"; -import { unit } from "../../utils/testing"; -import { wei } from "../../utils/wei"; -import { erc20BridgedPermitUnderProxy } from "../../utils/testing/contractsFactory"; -import { BigNumber } from "ethers"; unit("ERC20BridgedPermit", ctxFactory) .test("initial state", async (ctx) => { const { erc20Bridged } = ctx; const { decimals, name, symbol, version, premint } = ctx.constants; const { owner } = ctx.accounts; - const [,eip712Name,eip712Version,,,,] = await erc20Bridged.eip712Domain(); + const [, eip712Name, eip712Version, , , ,] = await erc20Bridged.eip712Domain(); assert.equal(eip712Name, name); assert.equal(eip712Version, version); @@ -41,12 +42,12 @@ unit("ERC20BridgedPermit", ctxFactory) assert.equalBN(await erc20BridgedImpl.getContractVersion(), petrifiedVersionMark); await assert.revertsWith( - erc20BridgedImpl.initialize("name", "symbol", "version"), + erc20BridgedImpl.initialize("name", "symbol", "version"), "NonZeroContractVersionOnInit()" ); }) - .test("initialize() :: re-initialization", async (ctx) => { + .test("initialize() :: don't allow to initialize twice", async (ctx) => { const { deployer, owner, holder } = ctx.accounts; const { name, symbol, version } = ctx.constants; @@ -60,24 +61,24 @@ unit("ERC20BridgedPermit", ctxFactory) ); const l2TokensProxy = await new OssifiableProxy__factory(deployer).deploy( - l2TokenImpl.address, - deployer.address, - ERC20BridgedPermit__factory.createInterface().encodeFunctionData("initialize", [ - name, - symbol, - version - ]) - ); + l2TokenImpl.address, + deployer.address, + ERC20BridgedPermit__factory.createInterface().encodeFunctionData("initialize", [ + name, + symbol, + version + ]) + ); - const erc20BridgedProxied = ERC20BridgedPermit__factory.connect( - l2TokensProxy.address, - holder - ); + const erc20BridgedProxied = ERC20BridgedPermit__factory.connect( + l2TokensProxy.address, + holder + ); assert.equalBN(await erc20BridgedProxied.getContractVersion(), 2); await assert.revertsWith( - erc20BridgedProxied.initialize(name, symbol, version), + erc20BridgedProxied.initialize(name, symbol, version), "NonZeroContractVersionOnInit()" ); }) @@ -96,13 +97,58 @@ unit("ERC20BridgedPermit", ctxFactory) ); await assert.revertsWith(new OssifiableProxy__factory(deployer).deploy( - l2TokenImpl.address, - deployer.address, - ERC20BridgedPermit__factory.createInterface().encodeFunctionData("finalizeUpgrade_v2", [ - name, - version - ]) - ), "ErrorMetadataNotInitialized()"); + l2TokenImpl.address, + deployer.address, + ERC20BridgedPermit__factory.createInterface().encodeFunctionData("finalizeUpgrade_v2", [ + name, + version + ]) + ), "ErrorMetadataIsNotInitialized()"); + }) + + .test("finalizeUpgrade_v2() :: metadata initialized", async (ctx) => { + const { deployer, owner } = ctx.accounts; + const { name, version } = ctx.constants; + + const l2TokenOldImpl = await new ERC20BridgedWithInitializerStub__factory(deployer).deploy( + "name", + "symbol", + 18, + owner.address + ); + + const l2TokenProxy = await new OssifiableProxy__factory(deployer).deploy( + l2TokenOldImpl.address, + deployer.address, + ERC20BridgedWithInitializerStub__factory.createInterface().encodeFunctionData("initializeERC20Metadata", [ + "name", + "symbol" + ]) + ); + + const erc20BridgedProxied = ERC20BridgedPermit__factory.connect( + l2TokenProxy.address, + owner + ); + + const l2TokenImpl = await new ERC20BridgedPermit__factory(deployer).deploy( + "name", + "symbol", + "1", + 18, + owner.address + ); + + await l2TokenProxy.proxy__upgradeToAndCall( + l2TokenImpl.address, + ERC20BridgedPermit__factory.createInterface().encodeFunctionData("finalizeUpgrade_v2", [ + name, + version + ]), + false + ); + + assert.equalBN(await erc20BridgedProxied.getContractVersion(), 2); }) .test("approve()", async (ctx) => { diff --git a/test/token/ERC20Permit.unit.test.ts b/test/token/ERC20Permit.unit.test.ts index 5ed23cb1..928db576 100644 --- a/test/token/ERC20Permit.unit.test.ts +++ b/test/token/ERC20Permit.unit.test.ts @@ -8,11 +8,11 @@ import testing from "../../utils/testing"; import { SignerWithAddress } from "@nomiclabs/hardhat-ethers/signers"; import { - TokenRateOracle__factory, - OssifiableProxy__factory, - ERC20RebasableBridgedPermit__factory, - ERC1271PermitSignerMock__factory, - ERC20BridgedPermit__factory, + TokenRateOracle__factory, + OssifiableProxy__factory, + ERC20RebasableBridgedPermit__factory, + ERC1271PermitSignerMock__factory, + ERC20BridgedPermit__factory, } from "../../typechain"; @@ -23,506 +23,505 @@ const MAX_UINT256 = '0xfffffffffffffffffffffffffffffffffffffffffffffffffffffffff // derived from mnemonic: want believe mosquito cat design route voice cause gold benefit gospel bulk often attitude rural const ACCOUNTS_AND_KEYS = [ - { - address: '0xF4C028683CAd61ff284d265bC0F77EDd67B4e65A', - privateKey: '0x5f7edf5892efb4a5cd75dedd496598f48e579b562a70eb1360474cc83a982987', - }, - { - address: '0x7F94c1F9e4BfFccc8Cd79195554E0d83a0a5c5f2', - privateKey: '0x3fe2f6bd9dbc7d507a6cb95ec36a36787706617e34385292b66c74cd39874605', - }, + { + address: '0xF4C028683CAd61ff284d265bC0F77EDd67B4e65A', + privateKey: '0x5f7edf5892efb4a5cd75dedd496598f48e579b562a70eb1360474cc83a982987', + }, + { + address: '0x7F94c1F9e4BfFccc8Cd79195554E0d83a0a5c5f2', + privateKey: '0x3fe2f6bd9dbc7d507a6cb95ec36a36787706617e34385292b66c74cd39874605', + }, ] function getChainId() { - return hre.network.config.chainId as number; + return hre.network.config.chainId as number; } const getAccountsEOA = async () => { - return { - alice: ACCOUNTS_AND_KEYS[0], - bob: ACCOUNTS_AND_KEYS[1], - } + return { + alice: ACCOUNTS_AND_KEYS[0], + bob: ACCOUNTS_AND_KEYS[1], + } } const getAccountsEIP1271 = async () => { - const deployer = (await hre.ethers.getSigners())[0] - const alice = await new ERC1271PermitSignerMock__factory(deployer).deploy() - const bob = await new ERC1271PermitSignerMock__factory(deployer).deploy() - return { alice, bob } + const deployer = (await hre.ethers.getSigners())[0] + const alice = await new ERC1271PermitSignerMock__factory(deployer).deploy() + const bob = await new ERC1271PermitSignerMock__factory(deployer).deploy() + return { alice, bob } } function permitTestsSuit(unitInstance: UnitTest) { - unitInstance - - // .test("wrappedToken() :: has the same address is in constructor", async (ctx) => { - // const { rebasableProxied, wrappedToken } = ctx.contracts; - // assert.equal(await rebasableProxied.TOKEN_TO_WRAP_FROM(), wrappedToken.address) - // }) - - .test('eip712Domain() is correct', async (ctx) => { - const token = ctx.contracts.rebasableProxied - const [, name, version, chainId, verifyingContract, ,] = await token.eip712Domain() - - assert.equal(name, ctx.constants.name) - assert.equal(version, SIGNING_DOMAIN_VERSION) - assert.isDefined(hre.network.config.chainId) - assert.equal(chainId.toNumber(), getChainId()) - assert.equal(verifyingContract, token.address) - }) - - .test('DOMAIN_SEPARATOR() is correct', async (ctx) => { - const token = ctx.contracts.rebasableProxied - const domainSeparator = makeDomainSeparator(ctx.constants.name, SIGNING_DOMAIN_VERSION, getChainId(), token.address) - assert.equal(await ctx.contracts.rebasableProxied.DOMAIN_SEPARATOR(), domainSeparator) - }) - - .test('grants allowance when a valid permit is given', async (ctx) => { - const token = ctx.contracts.rebasableProxied - - const { owner, spender, deadline } = ctx.permitParams - let { value } = ctx.permitParams - // create a signed permit to grant Bob permission to spend Alice's funds - // on behalf, and sign with Alice's key - let nonce = 0 - const charlie = ctx.accounts.user2 - - let { v, r, s } = await signPermit(owner.address, owner, spender.address, value, deadline, nonce, ctx.domainSeparator) - - // check that the allowance is initially zero - assert.equalBN(await token.allowance(owner.address, spender.address), BigNumber.from(0)) - // check that the next nonce expected is zero - assert.equalBN(await token.nonces(owner.address), BigNumber.from(0)) - // check domain separator - assert.equal(await token.DOMAIN_SEPARATOR(), ctx.domainSeparator) - - // a third-party, Charlie (not Alice) submits the permit - // TODO: handle unpredictable gas limit somehow better than setting it to a random constant - const tx = await token.connect(charlie) - .permit(owner.address, spender.address, value, deadline, v, r, s, { gasLimit: '0xffffff' }) - - // check that allowance is updated - assert.equalBN(await token.allowance(owner.address, spender.address), BigNumber.from(value)) - await assert.emits(token, tx, 'Approval', [owner.address, spender.address, value]) - assert.equalBN(await token.nonces(owner.address), BigNumber.from(1)) - - - // increment nonce - nonce = 1 - value = 4e5 - ; ({ v, r, s } = await signPermit(owner.address, owner, spender.address, value, deadline, nonce, ctx.domainSeparator)) - - // submit the permit - const tx2 = await token.connect(charlie).permit(owner.address, spender.address, value, deadline, v, r, s) - - // check that allowance is updated - assert.equalBN(await token.allowance(owner.address, spender.address), BigNumber.from(value)) - assert.emits(token, tx2, 'Approval', [owner.address, spender.address, BigNumber.from(value)]) - assert.equalBN(await token.nonces(owner.address), BigNumber.from(2)) - }) - - - .test('reverts if the signature does not match given parameters', async (ctx) => { - const { owner, spender, value, nonce, deadline } = ctx.permitParams - const token = ctx.contracts.rebasableProxied - const charlie = ctx.accounts.user2 - - // create a signed permit - const { v, r, s } = await signPermit(owner.address, owner, spender.address, value, deadline, nonce, ctx.domainSeparator) - - // try to cheat by claiming the approved amount + 1 - await assert.revertsWith( - token.connect(charlie).permit( - owner.address, - spender.address, - value + 1, // pass more than signed value - deadline, - v, - r, - s, - ), - 'ErrorInvalidSignature()' - ) - - // check that msg is incorrect even if claim the approved amount - 1 - await assert.revertsWith( - token.connect(charlie).permit( - owner.address, - spender.address, - value - 1, // pass less than signed - deadline, - v, - r, - s, - ), - 'ErrorInvalidSignature()' - ) - }) - - .test('reverts if the signature is not signed with the right key', async (ctx) => { - const { owner, spender, value, nonce, deadline } = ctx.permitParams - const token = ctx.contracts.rebasableProxied - const spenderSigner = await hre.ethers.getSigner(spender.address) - const charlie = ctx.accounts.user2 - - // create a signed permit to grant Bob permission to spend - // Alice's funds on behalf, but sign with Bob's key instead of Alice's - const { v, r, s } = await signPermit(owner.address, spender, spender.address, value, deadline, nonce, ctx.domainSeparator) - - // try to cheat by submitting the permit that is signed by a - // wrong person - await assert.revertsWith( - token.connect(charlie).permit(owner.address, spender.address, value, deadline, v, r, s), - 'ErrorInvalidSignature()' - ) - - await testing.impersonate(spender.address) - await testing.setBalance(spender.address, wei.toBigNumber(wei`10 ether`)) - - // even Bob himself can't call permit with the invalid sig - await assert.revertsWith( - token.connect(spenderSigner).permit(owner.address, spender.address, value, deadline, v, r, s), - 'ErrorInvalidSignature()' - ) - }) - - .test('reverts if the permit is expired', async (ctx) => { - const token = ctx.contracts.rebasableProxied - const { owner, spender, value, nonce } = ctx.permitParams - const charlie = ctx.accounts.user2 - - // create a signed permit that already invalid - const deadline = ((await hre.ethers.provider.getBlock('latest')).timestamp - 1).toString() - const { v, r, s } = await signPermit(owner.address, owner, spender.address, value, deadline, nonce, ctx.domainSeparator) - - // try to submit the permit that is expired - await assert.revertsWith( - token.connect(charlie).permit(owner.address, spender.address, value, deadline, v, r, s, { gasLimit: '0xffffff' }), - 'ErrorDeadlineExpired()' - ) - - { - // create a signed permit that valid for 1 minute (approximately) - const deadline1min = ((await hre.ethers.provider.getBlock('latest')).timestamp + 60).toString() - const { v, r, s } = await signPermit(owner.address, owner, spender.address, value, deadline1min, nonce, ctx.domainSeparator) - const tx = await token.connect(charlie).permit(owner.address, spender.address, value, deadline1min, v, r, s) - - assert.equalBN(await token.nonces(owner.address), BigNumber.from(1)) - assert.emits(token, tx, 'Approval', [owner, spender, BigNumber.from(value)]) - } - }) - - .test('reverts if the nonce given does not match the next nonce expected', async (ctx) => { - const token = ctx.contracts.rebasableProxied - const { owner, spender, value, deadline } = ctx.permitParams - const charlie = ctx.accounts.user2 - const nonce = 1 - // create a signed permit - const { v, r, s } = await signPermit(owner.address, owner, spender.address, value, deadline, nonce, ctx.domainSeparator) - // check that the next nonce expected is 0, not 1 - assert.equalBN(await token.nonces(owner.address), BigNumber.from(0)) - - // try to submit the permit - await assert.revertsWith( - token.connect(charlie).permit(owner.address, spender.address, value, deadline, v, r, s), - 'ErrorInvalidSignature()' - ) - }) - - .test('reverts if the permit has already been used', async (ctx) => { - const token = ctx.contracts.rebasableProxied - const { owner, spender, value, nonce, deadline } = ctx.permitParams - const charlie = ctx.accounts.user2 - // create a signed permit - const { v, r, s } = await signPermit(owner.address, owner, spender.address, value, deadline, nonce, ctx.domainSeparator) - - // submit the permit - await token.connect(charlie).permit(owner.address, spender.address, value, deadline, v, r, s) - - // try to submit the permit again - await assert.revertsWith( - token.connect(charlie).permit(owner.address, spender.address, value, deadline, v, r, s), - 'ErrorInvalidSignature()' - ) - - await testing.impersonate(owner.address) - await testing.setBalance(owner.address, wei.toBigNumber(wei`10 ether`)) - - // try to submit the permit again from Alice herself - await assert.revertsWith( - token.connect(await hre.ethers.getSigner(owner.address)).permit(owner.address, spender.address, value, deadline, v, r, s), - 'ErrorInvalidSignature()' - ) - }) - - .test('reverts if the permit has a nonce that has already been used by the signer', async (ctx) => { - const token = ctx.contracts.rebasableProxied - const { owner, spender, value, nonce, deadline } = ctx.permitParams - const charlie = ctx.accounts.user2 - // create a signed permit - const permit = await signPermit(owner.address, owner, spender.address, value, deadline, nonce, ctx.domainSeparator) - - // submit the permit - await token.connect(charlie).permit(owner.address, spender.address, value, deadline, permit.v, permit.r, permit.s) - - // create another signed permit with the same nonce, but - // with different parameters - const permit2 = await signPermit(owner.address, owner, spender.address, 1e6, deadline, nonce, ctx.domainSeparator) - - // try to submit the permit again - await assert.revertsWith( - token.connect(charlie).permit(owner.address, spender.address, 1e6, deadline, permit2.v, permit2.r, permit2.s), - 'ErrorInvalidSignature()' - ) - }) - - .test('reverts if the permit includes invalid approval parameters', async (ctx) => { - const token = ctx.contracts.rebasableProxied - const { owner, value, nonce, deadline } = ctx.permitParams - const charlie = ctx.accounts.user2 - // create a signed permit that attempts to grant allowance to the - // zero address - const spender = hre.ethers.constants.AddressZero - const { v, r, s } = await signPermit(owner.address, owner, spender, value, deadline, nonce, ctx.domainSeparator) - - // try to submit the permit with invalid approval parameters - await assert.revertsWith( - token.connect(charlie).permit(owner.address, spender, value, deadline, v, r, s), - 'ErrorAccountIsZeroAddress()' - ) - }) - - .test('reverts if the permit is not for an approval', async (ctx) => { - const token = ctx.contracts.rebasableProxied - const charlie = ctx.accounts.user2 - const { owner: from, spender: to, value, deadline: validBefore } = ctx.permitParams - // create a signed permit for a transfer - const validAfter = '0' - const nonce = '0x1234567890abcdef1234567890abcdef1234567890abcdef1234567890abcdef' - const digest = calculateTransferAuthorizationDigest( - from.address, - to.address, - value, - validAfter, - validBefore, - nonce, - ctx.domainSeparator - ) - const { v, r, s } = await signEOAorEIP1271(digest, from) - - // try to submit the transfer permit - await assert.revertsWith( - token.connect(charlie).permit(from.address, to.address, value, validBefore, v, r, s), - 'ErrorInvalidSignature()' - ) - }) - - .run(); + unitInstance + + // .test("wrappedToken() :: has the same address is in constructor", async (ctx) => { + // const { rebasableProxied, wrappedToken } = ctx.contracts; + // assert.equal(await rebasableProxied.TOKEN_TO_WRAP_FROM(), wrappedToken.address) + // }) + + .test('eip712Domain() is correct', async (ctx) => { + const token = ctx.contracts.rebasableProxied + const [, name, version, chainId, verifyingContract, ,] = await token.eip712Domain() + + assert.equal(name, ctx.constants.name) + assert.equal(version, SIGNING_DOMAIN_VERSION) + assert.isDefined(hre.network.config.chainId) + assert.equal(chainId.toNumber(), getChainId()) + assert.equal(verifyingContract, token.address) + }) + + .test('DOMAIN_SEPARATOR() is correct', async (ctx) => { + const token = ctx.contracts.rebasableProxied + const domainSeparator = makeDomainSeparator(ctx.constants.name, SIGNING_DOMAIN_VERSION, getChainId(), token.address) + assert.equal(await ctx.contracts.rebasableProxied.DOMAIN_SEPARATOR(), domainSeparator) + }) + + .test('grants allowance when a valid permit is given', async (ctx) => { + const token = ctx.contracts.rebasableProxied + + const { owner, spender, deadline } = ctx.permitParams + let { value } = ctx.permitParams + // create a signed permit to grant Bob permission to spend Alice's funds + // on behalf, and sign with Alice's key + let nonce = 0 + const charlie = ctx.accounts.user2 + + let { v, r, s } = await signPermit(owner.address, owner, spender.address, value, deadline, nonce, ctx.domainSeparator) + + // check that the allowance is initially zero + assert.equalBN(await token.allowance(owner.address, spender.address), BigNumber.from(0)) + // check that the next nonce expected is zero + assert.equalBN(await token.nonces(owner.address), BigNumber.from(0)) + // check domain separator + assert.equal(await token.DOMAIN_SEPARATOR(), ctx.domainSeparator) + + // a third-party, Charlie (not Alice) submits the permit + // TODO: handle unpredictable gas limit somehow better than setting it to a random constant + const tx = await token.connect(charlie) + .permit(owner.address, spender.address, value, deadline, v, r, s, { gasLimit: '0xffffff' }) + + // check that allowance is updated + assert.equalBN(await token.allowance(owner.address, spender.address), BigNumber.from(value)) + await assert.emits(token, tx, 'Approval', [owner.address, spender.address, value]) + assert.equalBN(await token.nonces(owner.address), BigNumber.from(1)) + + + // increment nonce + nonce = 1 + value = 4e5 + ; ({ v, r, s } = await signPermit(owner.address, owner, spender.address, value, deadline, nonce, ctx.domainSeparator)) + + // submit the permit + const tx2 = await token.connect(charlie).permit(owner.address, spender.address, value, deadline, v, r, s) + + // check that allowance is updated + assert.equalBN(await token.allowance(owner.address, spender.address), BigNumber.from(value)) + assert.emits(token, tx2, 'Approval', [owner.address, spender.address, BigNumber.from(value)]) + assert.equalBN(await token.nonces(owner.address), BigNumber.from(2)) + }) + + + .test('reverts if the signature does not match given parameters', async (ctx) => { + const { owner, spender, value, nonce, deadline } = ctx.permitParams + const token = ctx.contracts.rebasableProxied + const charlie = ctx.accounts.user2 + + // create a signed permit + const { v, r, s } = await signPermit(owner.address, owner, spender.address, value, deadline, nonce, ctx.domainSeparator) + + // try to cheat by claiming the approved amount + 1 + await assert.revertsWith( + token.connect(charlie).permit( + owner.address, + spender.address, + value + 1, // pass more than signed value + deadline, + v, + r, + s, + ), + 'ErrorInvalidSignature()' + ) + + // check that msg is incorrect even if claim the approved amount - 1 + await assert.revertsWith( + token.connect(charlie).permit( + owner.address, + spender.address, + value - 1, // pass less than signed + deadline, + v, + r, + s, + ), + 'ErrorInvalidSignature()' + ) + }) + + .test('reverts if the signature is not signed with the right key', async (ctx) => { + const { owner, spender, value, nonce, deadline } = ctx.permitParams + const token = ctx.contracts.rebasableProxied + const spenderSigner = await hre.ethers.getSigner(spender.address) + const charlie = ctx.accounts.user2 + + // create a signed permit to grant Bob permission to spend + // Alice's funds on behalf, but sign with Bob's key instead of Alice's + const { v, r, s } = await signPermit(owner.address, spender, spender.address, value, deadline, nonce, ctx.domainSeparator) + + // try to cheat by submitting the permit that is signed by a + // wrong person + await assert.revertsWith( + token.connect(charlie).permit(owner.address, spender.address, value, deadline, v, r, s), + 'ErrorInvalidSignature()' + ) + + await testing.impersonate(spender.address) + await testing.setBalance(spender.address, wei.toBigNumber(wei`10 ether`)) + + // even Bob himself can't call permit with the invalid sig + await assert.revertsWith( + token.connect(spenderSigner).permit(owner.address, spender.address, value, deadline, v, r, s), + 'ErrorInvalidSignature()' + ) + }) + + .test('reverts if the permit is expired', async (ctx) => { + const token = ctx.contracts.rebasableProxied + const { owner, spender, value, nonce } = ctx.permitParams + const charlie = ctx.accounts.user2 + + // create a signed permit that already invalid + const deadline = ((await hre.ethers.provider.getBlock('latest')).timestamp - 1).toString() + const { v, r, s } = await signPermit(owner.address, owner, spender.address, value, deadline, nonce, ctx.domainSeparator) + + // try to submit the permit that is expired + await assert.revertsWith( + token.connect(charlie).permit(owner.address, spender.address, value, deadline, v, r, s, { gasLimit: '0xffffff' }), + 'ErrorDeadlineExpired()' + ) + + { + // create a signed permit that valid for 1 minute (approximately) + const deadline1min = ((await hre.ethers.provider.getBlock('latest')).timestamp + 60).toString() + const { v, r, s } = await signPermit(owner.address, owner, spender.address, value, deadline1min, nonce, ctx.domainSeparator) + const tx = await token.connect(charlie).permit(owner.address, spender.address, value, deadline1min, v, r, s) + + assert.equalBN(await token.nonces(owner.address), BigNumber.from(1)) + assert.emits(token, tx, 'Approval', [owner, spender, BigNumber.from(value)]) + } + }) + + .test('reverts if the nonce given does not match the next nonce expected', async (ctx) => { + const token = ctx.contracts.rebasableProxied + const { owner, spender, value, deadline } = ctx.permitParams + const charlie = ctx.accounts.user2 + const nonce = 1 + // create a signed permit + const { v, r, s } = await signPermit(owner.address, owner, spender.address, value, deadline, nonce, ctx.domainSeparator) + // check that the next nonce expected is 0, not 1 + assert.equalBN(await token.nonces(owner.address), BigNumber.from(0)) + + // try to submit the permit + await assert.revertsWith( + token.connect(charlie).permit(owner.address, spender.address, value, deadline, v, r, s), + 'ErrorInvalidSignature()' + ) + }) + + .test('reverts if the permit has already been used', async (ctx) => { + const token = ctx.contracts.rebasableProxied + const { owner, spender, value, nonce, deadline } = ctx.permitParams + const charlie = ctx.accounts.user2 + // create a signed permit + const { v, r, s } = await signPermit(owner.address, owner, spender.address, value, deadline, nonce, ctx.domainSeparator) + + // submit the permit + await token.connect(charlie).permit(owner.address, spender.address, value, deadline, v, r, s) + + // try to submit the permit again + await assert.revertsWith( + token.connect(charlie).permit(owner.address, spender.address, value, deadline, v, r, s), + 'ErrorInvalidSignature()' + ) + + await testing.impersonate(owner.address) + await testing.setBalance(owner.address, wei.toBigNumber(wei`10 ether`)) + + // try to submit the permit again from Alice herself + await assert.revertsWith( + token.connect(await hre.ethers.getSigner(owner.address)).permit(owner.address, spender.address, value, deadline, v, r, s), + 'ErrorInvalidSignature()' + ) + }) + + .test('reverts if the permit has a nonce that has already been used by the signer', async (ctx) => { + const token = ctx.contracts.rebasableProxied + const { owner, spender, value, nonce, deadline } = ctx.permitParams + const charlie = ctx.accounts.user2 + // create a signed permit + const permit = await signPermit(owner.address, owner, spender.address, value, deadline, nonce, ctx.domainSeparator) + + // submit the permit + await token.connect(charlie).permit(owner.address, spender.address, value, deadline, permit.v, permit.r, permit.s) + + // create another signed permit with the same nonce, but + // with different parameters + const permit2 = await signPermit(owner.address, owner, spender.address, 1e6, deadline, nonce, ctx.domainSeparator) + + // try to submit the permit again + await assert.revertsWith( + token.connect(charlie).permit(owner.address, spender.address, 1e6, deadline, permit2.v, permit2.r, permit2.s), + 'ErrorInvalidSignature()' + ) + }) + + .test('reverts if the permit includes invalid approval parameters', async (ctx) => { + const token = ctx.contracts.rebasableProxied + const { owner, value, nonce, deadline } = ctx.permitParams + const charlie = ctx.accounts.user2 + // create a signed permit that attempts to grant allowance to the + // zero address + const spender = hre.ethers.constants.AddressZero + const { v, r, s } = await signPermit(owner.address, owner, spender, value, deadline, nonce, ctx.domainSeparator) + + // try to submit the permit with invalid approval parameters + await assert.revertsWith( + token.connect(charlie).permit(owner.address, spender, value, deadline, v, r, s), + 'ErrorAccountIsZeroAddress()' + ) + }) + + .test('reverts if the permit is not for an approval', async (ctx) => { + const token = ctx.contracts.rebasableProxied + const charlie = ctx.accounts.user2 + const { owner: from, spender: to, value, deadline: validBefore } = ctx.permitParams + // create a signed permit for a transfer + const validAfter = '0' + const nonce = '0x1234567890abcdef1234567890abcdef1234567890abcdef1234567890abcdef' + const digest = calculateTransferAuthorizationDigest( + from.address, + to.address, + value, + validAfter, + validBefore, + nonce, + ctx.domainSeparator + ) + const { v, r, s } = await signEOAorEIP1271(digest, from) + + // try to submit the transfer permit + await assert.revertsWith( + token.connect(charlie).permit(from.address, to.address, value, validBefore, v, r, s), + 'ErrorInvalidSignature()' + ) + }) + + .run(); } function ctxFactoryFactory( - name: string, - symbol: string, - isRebasable: boolean, - signingAccountsFuncFactory: typeof getAccountsEIP1271 | typeof getAccountsEOA + name: string, + symbol: string, + isRebasable: boolean, + signingAccountsFuncFactory: typeof getAccountsEIP1271 | typeof getAccountsEOA ) { - return async () => { - const decimalsToSet = 18; - const decimals = BigNumber.from(10).pow(decimalsToSet); - const tokenRate = BigNumber.from('1164454276599657236'); - const premintShares = wei.toBigNumber(wei`100 ether`); - const premintTokens = tokenRate.mul(premintShares).div(decimals); - - const [ - deployer, - owner, - recipient, - spender, - holder, - stranger, - user1, - user2, - ] = await hre.ethers.getSigners(); - - await hre.network.provider.request({ - method: "hardhat_impersonateAccount", - params: [hre.ethers.constants.AddressZero], - }); - - const zero = await hre.ethers.getSigner(hre.ethers.constants.AddressZero); - - const rebasableProxied = await tokenProxied( - name, - symbol, - decimalsToSet, - tokenRate, - isRebasable, - owner, - deployer, - holder - ); - - const { alice, bob } = await signingAccountsFuncFactory(); - - return { - accounts: { deployer, owner, recipient, spender, holder, stranger, zero, user1, user2 }, - constants: { name, symbol, decimalsToSet, decimals, premintShares, premintTokens, tokenRate }, - contracts: { rebasableProxied }, - permitParams: { - owner: alice, - spender: bob, - value: 6e6, - nonce: 0, - deadline: MAX_UINT256, - }, - domainSeparator: makeDomainSeparator(name, SIGNING_DOMAIN_VERSION, getChainId(), rebasableProxied.address), - }; - } + return async () => { + const decimalsToSet = 18; + const decimals = BigNumber.from(10).pow(decimalsToSet); + const tokenRate = BigNumber.from('1164454276599657236'); + const premintShares = wei.toBigNumber(wei`100 ether`); + const premintTokens = tokenRate.mul(premintShares).div(decimals); + + const [ + deployer, + owner, + recipient, + spender, + holder, + stranger, + user1, + user2, + ] = await hre.ethers.getSigners(); + + await hre.network.provider.request({ + method: "hardhat_impersonateAccount", + params: [hre.ethers.constants.AddressZero], + }); + + const zero = await hre.ethers.getSigner(hre.ethers.constants.AddressZero); + + const rebasableProxied = await tokenProxied( + name, + symbol, + decimalsToSet, + tokenRate, + isRebasable, + owner, + deployer, + holder + ); + + const { alice, bob } = await signingAccountsFuncFactory(); + + return { + accounts: { deployer, owner, recipient, spender, holder, stranger, zero, user1, user2 }, + constants: { name, symbol, decimalsToSet, decimals, premintShares, premintTokens, tokenRate }, + contracts: { rebasableProxied }, + permitParams: { + owner: alice, + spender: bob, + value: 6e6, + nonce: 0, + deadline: MAX_UINT256, + }, + domainSeparator: makeDomainSeparator(name, SIGNING_DOMAIN_VERSION, getChainId(), rebasableProxied.address), + }; + } } async function tokenProxied( - name: string, - symbol: string, - decimalsToSet: number, - tokenRate: BigNumber, - isRebasable: boolean, - owner: SignerWithAddress, - deployer: SignerWithAddress, - holder: SignerWithAddress) { - - if (isRebasable) { - - const wrappedToken = await new ERC20BridgedPermit__factory(deployer).deploy( - "WstETH Test Token", - "WstETH", - SIGNING_DOMAIN_VERSION, - decimalsToSet, - owner.address - ); - const tokenRateOracleImpl = await new TokenRateOracle__factory(deployer).deploy( - hre.ethers.constants.AddressZero, - owner.address, - hre.ethers.constants.AddressZero, - 86400, - 86400, - 500 - ); - const provider = await hre.ethers.provider; - const blockNumber = await provider.getBlockNumber(); - const blockTimestamp = (await provider.getBlock(blockNumber)).timestamp; - - const tokenRateOracleProxy = await new OssifiableProxy__factory( - deployer - ).deploy( - tokenRateOracleImpl.address, - deployer.address, - tokenRateOracleImpl.interface.encodeFunctionData("initialize", [ - tokenRate, - blockTimestamp - ]) - ); - - const tokenRateOracle = TokenRateOracle__factory.connect( - tokenRateOracleProxy.address, - deployer - ); - - const rebasableTokenImpl = await new ERC20RebasableBridgedPermit__factory(deployer).deploy( - name, - symbol, - SIGNING_DOMAIN_VERSION, - decimalsToSet, - wrappedToken.address, - tokenRateOracle.address, - owner.address - ); - - const l2TokensProxy = await new OssifiableProxy__factory(deployer).deploy( - rebasableTokenImpl.address, - deployer.address, - ERC20RebasableBridgedPermit__factory.createInterface().encodeFunctionData("initialize", [ - name, - symbol, - SIGNING_DOMAIN_VERSION - ]) - ); - - const rebasableProxied = ERC20RebasableBridgedPermit__factory.connect( - l2TokensProxy.address, - holder - ); - - const premintShares = wei.toBigNumber(wei`100 ether`); - await rebasableProxied.connect(owner).bridgeMintShares(holder.address, premintShares); - - return rebasableProxied; - } - + name: string, + symbol: string, + decimalsToSet: number, + tokenRate: BigNumber, + isRebasable: boolean, + owner: SignerWithAddress, + deployer: SignerWithAddress, + holder: SignerWithAddress) { + + if (isRebasable) { const wrappedToken = await new ERC20BridgedPermit__factory(deployer).deploy( - name, - symbol, - SIGNING_DOMAIN_VERSION, - decimalsToSet, - owner.address + "WstETH Test Token", + "WstETH", + SIGNING_DOMAIN_VERSION, + decimalsToSet, + owner.address + ); + const tokenRateOracleImpl = await new TokenRateOracle__factory(deployer).deploy( + hre.ethers.constants.AddressZero, + owner.address, + hre.ethers.constants.AddressZero, + 86400, + 86400, + 500 + ); + const provider = await hre.ethers.provider; + const blockNumber = await provider.getBlockNumber(); + const blockTimestamp = (await provider.getBlock(blockNumber)).timestamp; + + const tokenRateOracleProxy = await new OssifiableProxy__factory( + deployer + ).deploy( + tokenRateOracleImpl.address, + deployer.address, + tokenRateOracleImpl.interface.encodeFunctionData("initialize", [ + tokenRate, + blockTimestamp + ]) + ); + + const tokenRateOracle = TokenRateOracle__factory.connect( + tokenRateOracleProxy.address, + deployer + ); + + const rebasableTokenImpl = await new ERC20RebasableBridgedPermit__factory(deployer).deploy( + name, + symbol, + SIGNING_DOMAIN_VERSION, + decimalsToSet, + wrappedToken.address, + tokenRateOracle.address, + owner.address ); const l2TokensProxy = await new OssifiableProxy__factory(deployer).deploy( - wrappedToken.address, - deployer.address, - ERC20BridgedPermit__factory.createInterface().encodeFunctionData("initialize", [ - name, - symbol, - SIGNING_DOMAIN_VERSION - ]) + rebasableTokenImpl.address, + deployer.address, + ERC20RebasableBridgedPermit__factory.createInterface().encodeFunctionData("initialize", [ + name, + symbol, + SIGNING_DOMAIN_VERSION + ]) ); - const nonRebasableProxied = ERC20BridgedPermit__factory.connect( - l2TokensProxy.address, - holder + const rebasableProxied = ERC20RebasableBridgedPermit__factory.connect( + l2TokensProxy.address, + holder ); - return nonRebasableProxied; + const premintShares = wei.toBigNumber(wei`100 ether`); + await rebasableProxied.connect(owner).bridgeMintShares(holder.address, premintShares); + + return rebasableProxied; + } + + const wrappedToken = await new ERC20BridgedPermit__factory(deployer).deploy( + name, + symbol, + SIGNING_DOMAIN_VERSION, + decimalsToSet, + owner.address + ); + + const l2TokensProxy = await new OssifiableProxy__factory(deployer).deploy( + wrappedToken.address, + deployer.address, + ERC20BridgedPermit__factory.createInterface().encodeFunctionData("initialize", [ + name, + symbol, + SIGNING_DOMAIN_VERSION + ]) + ); + + const nonRebasableProxied = ERC20BridgedPermit__factory.connect( + l2TokensProxy.address, + holder + ); + + return nonRebasableProxied; } permitTestsSuit( - unit("ERC20RebasableBridgedPermit with EIP1271 (contract) signing", - ctxFactoryFactory( - "Liquid staked Ether 2.0", - "stETH", - true, - getAccountsEIP1271 - ) + unit("ERC20RebasableBridgedPermit with EIP1271 (contract) signing", + ctxFactoryFactory( + "Liquid staked Ether 2.0", + "stETH", + true, + getAccountsEIP1271 ) + ) ); permitTestsSuit( - unit("ERC20RebasableBridgedPermit with ECDSA (EOA) signing", - ctxFactoryFactory( - "Liquid staked Ether 2.0", - "stETH", - true, - getAccountsEOA - ) + unit("ERC20RebasableBridgedPermit with ECDSA (EOA) signing", + ctxFactoryFactory( + "Liquid staked Ether 2.0", + "stETH", + true, + getAccountsEOA ) + ) ); permitTestsSuit( - unit("ERC20BridgedPermit with EIP1271 (contract) signing", - ctxFactoryFactory( - "Wrapped liquid staked Ether 2.0", - "wstETH", - false, - getAccountsEIP1271 - ) + unit("ERC20BridgedPermit with EIP1271 (contract) signing", + ctxFactoryFactory( + "Wrapped liquid staked Ether 2.0", + "wstETH", + false, + getAccountsEIP1271 ) + ) ); permitTestsSuit( - unit("ERC20BridgedPermit with ECDSA (EOA) signing", - ctxFactoryFactory( - "Wrapped liquid staked Ether 2.0", - "WstETH", - false, - getAccountsEOA - ) + unit("ERC20BridgedPermit with ECDSA (EOA) signing", + ctxFactoryFactory( + "Wrapped liquid staked Ether 2.0", + "WstETH", + false, + getAccountsEOA ) + ) ); diff --git a/test/token/ERC20RebasableBridgedPermit.unit.test.ts b/test/token/ERC20RebasableBridgedPermit.unit.test.ts index 7ac3aba3..92a0d192 100644 --- a/test/token/ERC20RebasableBridgedPermit.unit.test.ts +++ b/test/token/ERC20RebasableBridgedPermit.unit.test.ts @@ -4,50 +4,52 @@ import { BigNumber } from "ethers"; import { unit } from "../../utils/testing"; import { wei } from "../../utils/wei"; import { - ERC20BridgedPermit__factory, - TokenRateOracle__factory, - ERC20RebasableBridgedPermit__factory, - OssifiableProxy__factory + erc20RebasableBridgedPermitUnderProxy, + tokenRateOracleUnderProxy +} from "../../utils/testing/contractsFactory"; +import { + OssifiableProxy__factory, + ERC20BridgedPermit__factory, + ERC20RebasableBridgedPermit__factory, + TokenRateOracle__factory } from "../../typechain"; -import { tokenRateOracleUnderProxy } from "../../utils/testing/contractsFactory"; - unit("ERC20RebasableBridgedPermit", ctxFactory) .test("initial state", async (ctx) => { - const { rebasableProxied, wrappedToken, tokenRateOracle } = ctx.contracts; - const { owner } = ctx.accounts; - const { decimalsToSet, name, symbol, version } = ctx.constants; - const [,eip712Name,eip712Version,,,,] = await rebasableProxied.eip712Domain(); - assert.equal(eip712Name, name); - assert.equal(eip712Version, version); - assert.equal(await rebasableProxied.name(), name); - assert.equal(await rebasableProxied.symbol(), symbol) - assert.equalBN(await rebasableProxied.decimals(), decimalsToSet) - assert.equal(await rebasableProxied.TOKEN_TO_WRAP_FROM(), wrappedToken.address); - assert.equal(await rebasableProxied.TOKEN_RATE_ORACLE(), tokenRateOracle.address); - assert.equal(await rebasableProxied.L2_ERC20_TOKEN_BRIDGE(), owner.address); + const { rebasableProxied, wrappedToken, tokenRateOracle } = ctx.contracts; + const { name, symbol, version, decimals } = ctx.constants; + const { owner } = ctx.accounts; + const [, eip712Name, eip712Version, , , ,] = await rebasableProxied.eip712Domain(); + assert.equal(eip712Name, name); + assert.equal(eip712Version, version); + assert.equal(await rebasableProxied.name(), name); + assert.equal(await rebasableProxied.symbol(), symbol) + assert.equalBN(await rebasableProxied.decimals(), decimals) + assert.equal(await rebasableProxied.TOKEN_TO_WRAP_FROM(), wrappedToken.address); + assert.equal(await rebasableProxied.TOKEN_RATE_ORACLE(), tokenRateOracle.address); + assert.equal(await rebasableProxied.L2_ERC20_TOKEN_BRIDGE(), owner.address); }) .test("initialize() :: petrified version", async (ctx) => { const { deployer, owner, zero } = ctx.accounts; - const { decimalsToSet } = ctx.constants; + const { decimals } = ctx.constants; // deploy new implementation const wrappedToken = await new ERC20BridgedPermit__factory(deployer).deploy( - "WsETH Test Token", - "WsETH", - "1", - decimalsToSet, - owner.address + "WsETH Test Token", + "WsETH", + "1", + decimals, + owner.address ); const tokenRateOracle = await new TokenRateOracle__factory(deployer).deploy( - zero.address, - owner.address, - zero.address, - 86400, - 86400, - 500 + zero.address, + owner.address, + zero.address, + 86400, + 86400, + 500 ); const rebasableTokenImpl = await new ERC20RebasableBridgedPermit__factory(deployer).deploy( "stETH Test Token", @@ -68,25 +70,25 @@ unit("ERC20RebasableBridgedPermit", ctxFactory) ); }) - .test("initialize() :: reinitilization", async (ctx) => { + .test("initialize() :: don't allow to initialize twice", async (ctx) => { const { deployer, owner, zero, holder } = ctx.accounts; - const { decimalsToSet, name, symbol, version } = ctx.constants; + const { decimals, name, symbol, version } = ctx.constants; // deploy new implementation const wrappedToken = await new ERC20BridgedPermit__factory(deployer).deploy( - "WsETH Test Token", - "WsETH", - "1", - decimalsToSet, - owner.address + "WsETH Test Token", + "WsETH", + "1", + decimals, + owner.address ); const tokenRateOracle = await new TokenRateOracle__factory(deployer).deploy( - zero.address, - owner.address, - zero.address, - 86400, - 86400, - 500 + zero.address, + owner.address, + zero.address, + 86400, + 86400, + 500 ); const rebasableTokenImpl = await new ERC20RebasableBridgedPermit__factory(deployer).deploy( "", @@ -122,7 +124,7 @@ unit("ERC20RebasableBridgedPermit", ctxFactory) }) .test("decimals() :: has the same value as is in constructor", async (ctx) => - assert.equal(await ctx.contracts.rebasableProxied.decimals(), ctx.constants.decimalsToSet) + assert.equalBN(await ctx.contracts.rebasableProxied.decimals(), ctx.constants.decimals) ) .test("getTotalShares() :: returns preminted amount", async (ctx) => { @@ -139,39 +141,39 @@ unit("ERC20RebasableBridgedPermit", ctxFactory) .test("wrap() :: wrong oracle update time", async (ctx) => { const { deployer, user1, owner, zero } = ctx.accounts; - const { decimalsToSet } = ctx.constants; + const { decimals } = ctx.constants; // deploy new implementation to test initial oracle state const wrappedToken = await new ERC20BridgedPermit__factory(deployer).deploy( - "WsETH Test Token", - "WsETH", - "1", - decimalsToSet, - owner.address + "WsETH Test Token", + "WsETH", + "1", + decimals, + owner.address ); const tokenRateOracle = await new TokenRateOracle__factory(deployer).deploy( - zero.address, - owner.address, - zero.address, - 86400, - 86400, - 500 + zero.address, + owner.address, + zero.address, + 86400, + 86400, + 500 ); const rebasableProxied = await new ERC20RebasableBridgedPermit__factory(deployer).deploy( - "", - "symbol", - "1", - 10, - wrappedToken.address, - tokenRateOracle.address, - owner.address + "", + "symbol", + "1", + 10, + wrappedToken.address, + tokenRateOracle.address, + owner.address ); await wrappedToken.connect(owner).bridgeMint(user1.address, 1000); await wrappedToken.connect(user1).approve(rebasableProxied.address, 1000); await assert.revertsWith(rebasableProxied.connect(user1).wrap(5), "ErrorWrongOracleUpdateTime()"); -}) + }) .test("wrap() :: when no balance", async (ctx) => { const { rebasableProxied, wrappedToken } = ctx.contracts; @@ -184,19 +186,19 @@ unit("ERC20RebasableBridgedPermit", ctxFactory) .test("wrap() :: happy path", async (ctx) => { const { rebasableProxied, wrappedToken, tokenRateOracle } = ctx.contracts; - const {user1, user2, owner, zero } = ctx.accounts; - const { tokenRate, decimals, premintShares } = ctx.constants; + const { user1, user2, owner, zero } = ctx.accounts; + const { tokenRate, tenPowDecimals, premintShares } = ctx.constants; await tokenRateOracle.connect(owner).updateRate(tokenRate, 1000); - const totalSupply = tokenRate.mul(premintShares).div(decimals); + const totalSupply = tokenRate.mul(premintShares).div(tenPowDecimals); assert.equalBN(await rebasableProxied.getTotalShares(), premintShares); assert.equalBN(await rebasableProxied.totalSupply(), totalSupply); // user1 const user1Shares = wei`100 ether`; - const user1Tokens = tokenRate.mul(user1Shares).div(decimals); + const user1Tokens = tokenRate.mul(user1Shares).div(tenPowDecimals); assert.equalBN(await rebasableProxied.sharesOf(user1.address), 0); assert.equalBN(await rebasableProxied.balanceOf(user1.address), 0); @@ -223,7 +225,7 @@ unit("ERC20RebasableBridgedPermit", ctxFactory) assert.equalBN(await rebasableProxied.balanceOf(user2.address), 0); const user2Shares = wei`50 ether`; - const user2Tokens = tokenRate.mul(user2Shares).div(decimals); + const user2Tokens = tokenRate.mul(user2Shares).div(tenPowDecimals); await wrappedToken.connect(owner).bridgeMint(user2.address, user2Tokens); await wrappedToken.connect(user2).approve(rebasableProxied.address, user2Shares); @@ -252,10 +254,10 @@ unit("ERC20RebasableBridgedPermit", ctxFactory) .test("unwrap() :: happy path", async (ctx) => { const { rebasableProxied, wrappedToken } = ctx.contracts; - const {user1, user2, owner } = ctx.accounts; - const { tokenRate, decimals, premintShares } = ctx.constants; + const { user1, user2, owner } = ctx.accounts; + const { tokenRate, tenPowDecimals, premintShares } = ctx.constants; - const totalSupply = BigNumber.from(tokenRate).mul(premintShares).div(decimals); + const totalSupply = BigNumber.from(tokenRate).mul(premintShares).div(tenPowDecimals); assert.equalBN(await rebasableProxied.getTotalShares(), premintShares); assert.equalBN(await rebasableProxied.totalSupply(), totalSupply); @@ -267,10 +269,10 @@ unit("ERC20RebasableBridgedPermit", ctxFactory) const user1SharesToWrap = wei`100 ether`; const user1SharesToUnwrap = wei`59 ether`; - const user1TokensToUnwrap = tokenRate.mul(user1SharesToUnwrap).div(decimals); + const user1TokensToUnwrap = tokenRate.mul(user1SharesToUnwrap).div(tenPowDecimals); const user1Shares = BigNumber.from(user1SharesToWrap).sub(user1SharesToUnwrap); - const user1Tokens = BigNumber.from(tokenRate).mul(user1Shares).div(decimals); + const user1Tokens = BigNumber.from(tokenRate).mul(user1Shares).div(tenPowDecimals); await wrappedToken.connect(owner).bridgeMint(user1.address, user1SharesToWrap); await wrappedToken.connect(user1).approve(rebasableProxied.address, user1SharesToWrap); @@ -290,10 +292,10 @@ unit("ERC20RebasableBridgedPermit", ctxFactory) // user2 const user2SharesToWrap = wei`145 ether`; const user2SharesToUnwrap = wei`14 ether`; - const user2TokensToUnwrap = tokenRate.mul(user2SharesToUnwrap).div(decimals); + const user2TokensToUnwrap = tokenRate.mul(user2SharesToUnwrap).div(tenPowDecimals); const user2Shares = BigNumber.from(user2SharesToWrap).sub(user2SharesToUnwrap); - const user2Tokens = BigNumber.from(tokenRate).mul(user2Shares).div(decimals); + const user2Tokens = BigNumber.from(tokenRate).mul(user2Shares).div(tenPowDecimals); assert.equalBN(await rebasableProxied.sharesOf(user2.address), 0); assert.equalBN(await rebasableProxied.balanceOf(user2.address), 0); @@ -317,32 +319,32 @@ unit("ERC20RebasableBridgedPermit", ctxFactory) .test("unwrap() :: with wrong oracle update time", async (ctx) => { const { deployer, user1, owner, zero } = ctx.accounts; - const { decimalsToSet } = ctx.constants; + const { decimals } = ctx.constants; // deploy new implementation to test initial oracle state const wrappedToken = await new ERC20BridgedPermit__factory(deployer).deploy( - "WsETH Test Token", - "WsETH", - "1", - decimalsToSet, - owner.address + "WsETH Test Token", + "WsETH", + "1", + decimals, + owner.address ); const tokenRateOracle = await new TokenRateOracle__factory(deployer).deploy( - zero.address, - owner.address, - zero.address, - 86400, - 86400, - 500 + zero.address, + owner.address, + zero.address, + 86400, + 86400, + 500 ); const rebasableProxied = await new ERC20RebasableBridgedPermit__factory(deployer).deploy( - "", - "symbol", - "1", - 10, - wrappedToken.address, - tokenRateOracle.address, - owner.address + "", + "symbol", + "1", + 10, + wrappedToken.address, + tokenRateOracle.address, + owner.address ); await wrappedToken.connect(owner).bridgeMint(user1.address, 1000); @@ -361,15 +363,15 @@ unit("ERC20RebasableBridgedPermit", ctxFactory) .test("bridgeMintShares() :: happy path", async (ctx) => { const { rebasableProxied } = ctx.contracts; - const {user1, user2, owner, zero } = ctx.accounts; - const { tokenRate, decimals, premintShares, premintTokens } = ctx.constants; + const { user1, user2, owner, zero } = ctx.accounts; + const { tokenRate, tenPowDecimals, premintShares, premintTokens } = ctx.constants; assert.equalBN(await rebasableProxied.getTotalShares(), premintShares); assert.equalBN(await rebasableProxied.totalSupply(), premintTokens); // user1 const user1SharesToMint = wei`44 ether`; - const user1TokensMinted = tokenRate.mul(user1SharesToMint).div(decimals); + const user1TokensMinted = tokenRate.mul(user1SharesToMint).div(tenPowDecimals); assert.equalBN(await rebasableProxied.sharesOf(user1.address), 0); assert.equalBN(await rebasableProxied.balanceOf(user1.address), 0); @@ -387,7 +389,7 @@ unit("ERC20RebasableBridgedPermit", ctxFactory) // // user2 const user2SharesToMint = wei`75 ether`; - const user2TokensMinted = tokenRate.mul(user2SharesToMint).div(decimals); + const user2TokensMinted = tokenRate.mul(user2SharesToMint).div(tenPowDecimals); assert.equalBN(await rebasableProxied.sharesOf(user2.address), 0); assert.equalBN(await rebasableProxied.balanceOf(user2.address), 0); @@ -407,18 +409,18 @@ unit("ERC20RebasableBridgedPermit", ctxFactory) .test("bridgeBurnShares() :: happy path", async (ctx) => { const { rebasableProxied } = ctx.contracts; - const {user1, user2, owner } = ctx.accounts; - const { tokenRate, decimals, premintShares, premintTokens } = ctx.constants; + const { user1, user2, owner } = ctx.accounts; + const { tokenRate, tenPowDecimals, premintShares, premintTokens } = ctx.constants; assert.equalBN(await rebasableProxied.getTotalShares(), premintShares); assert.equalBN(await rebasableProxied.totalSupply(), premintTokens); // user1 const user1SharesToMint = wei`12 ether`; - const user1TokensMinted = tokenRate.mul(user1SharesToMint).div(decimals); + const user1TokensMinted = tokenRate.mul(user1SharesToMint).div(tenPowDecimals); const user1SharesToBurn = wei`4 ether`; - const user1TokensBurned = tokenRate.mul(user1SharesToBurn).div(decimals); + const user1TokensBurned = tokenRate.mul(user1SharesToBurn).div(tenPowDecimals); const user1Shares = BigNumber.from(user1SharesToMint).sub(user1SharesToBurn); const user1Tokens = user1TokensMinted.sub(user1TokensBurned); @@ -440,10 +442,10 @@ unit("ERC20RebasableBridgedPermit", ctxFactory) // // user2 const user2SharesToMint = wei`64 ether`; - const user2TokensMinted = tokenRate.mul(user2SharesToMint).div(decimals); + const user2TokensMinted = tokenRate.mul(user2SharesToMint).div(tenPowDecimals); const user2SharesToBurn = wei`22 ether`; - const user2TokensBurned = tokenRate.mul(user2SharesToBurn).div(decimals); + const user2TokensBurned = tokenRate.mul(user2SharesToBurn).div(tenPowDecimals); const user2Shares = BigNumber.from(user2SharesToMint).sub(user2SharesToBurn); const user2Tokens = user2TokensMinted.sub(user2TokensBurned); @@ -504,8 +506,8 @@ unit("ERC20RebasableBridgedPermit", ctxFactory) accounts: { zero, recipient }, } = ctx; await assert.revertsWith( - rebasableProxied.connect(zero).transfer(recipient.address, wei`1 ether`), - "ErrorAccountIsZeroAddress()" + rebasableProxied.connect(zero).transfer(recipient.address, wei`1 ether`), + "ErrorAccountIsZeroAddress()" ); }) @@ -544,7 +546,7 @@ unit("ERC20RebasableBridgedPermit", ctxFactory) // transfer tokens await assert.revertsWith( - rebasableProxied.connect(holder).transfer(recipient.address, amount), + rebasableProxied.connect(holder).transfer(recipient.address, amount), "ErrorNotEnoughBalance()" ); }) @@ -596,8 +598,8 @@ unit("ERC20RebasableBridgedPermit", ctxFactory) accounts: { zero, recipient }, } = ctx; await assert.revertsWith( - rebasableProxied.connect(zero).transferShares(recipient.address, wei`1 ether`), - "ErrorAccountIsZeroAddress()" + rebasableProxied.connect(zero).transferShares(recipient.address, wei`1 ether`), + "ErrorAccountIsZeroAddress()" ); }) @@ -636,7 +638,7 @@ unit("ERC20RebasableBridgedPermit", ctxFactory) // transfer tokens await assert.revertsWith( - rebasableProxied.connect(holder).transferShares(recipient.address, amount), + rebasableProxied.connect(holder).transferShares(recipient.address, amount), "ErrorNotEnoughBalance()" ); }) @@ -704,7 +706,7 @@ unit("ERC20RebasableBridgedPermit", ctxFactory) // transfer tokens await assert.revertsWith( - rebasableProxied + rebasableProxied .connect(spender) .transferFrom(holder.address, recipient.address, amount), "ErrorNotEnoughAllowance()" @@ -752,10 +754,10 @@ unit("ERC20RebasableBridgedPermit", ctxFactory) ]); await assert.emits(rebasableProxied, tx, "TransferShares", [ - holder.address, - recipient.address, - sharesAmount, - ]); + holder.address, + recipient.address, + sharesAmount, + ]); // validate allowance wasn't changed assert.equalBN( @@ -821,10 +823,10 @@ unit("ERC20RebasableBridgedPermit", ctxFactory) ]); await assert.emits(rebasableProxied, tx, "TransferShares", [ - holder.address, - recipient.address, - sharesAmountToTransfer, - ]); + holder.address, + recipient.address, + sharesAmountToTransfer, + ]); // validate allowance updated assert.equalBN( @@ -863,7 +865,7 @@ unit("ERC20RebasableBridgedPermit", ctxFactory) // transfer tokens await assert.revertsWith( - rebasableProxied + rebasableProxied .connect(spender) .transferSharesFrom(holder.address, recipient.address, sharesAmount), "ErrorNotEnoughAllowance()" @@ -1010,7 +1012,7 @@ unit("ERC20RebasableBridgedPermit", ctxFactory) const { stranger } = ctx.accounts; await assert.revertsWith( - rebasableProxied + rebasableProxied .connect(stranger) .bridgeMintShares(stranger.address, wei`1000 ether`), "ErrorNotBridge()" @@ -1067,7 +1069,7 @@ unit("ERC20RebasableBridgedPermit", ctxFactory) const { holder, stranger } = ctx.accounts; await assert.revertsWith( - rebasableProxied.connect(stranger).bridgeBurnShares(holder.address, wei`100 ether`), + rebasableProxied.connect(stranger).bridgeBurnShares(holder.address, wei`100 ether`), "ErrorNotBridge()" ); }) @@ -1080,7 +1082,7 @@ unit("ERC20RebasableBridgedPermit", ctxFactory) assert.equalBN(await rebasableProxied.balanceOf(stranger.address), 0); await assert.revertsWith( - rebasableProxied.connect(owner).bridgeBurnShares(stranger.address, wei`100 ether`), + rebasableProxied.connect(owner).bridgeBurnShares(stranger.address, wei`100 ether`), "ErrorNotEnoughBalance()" ); }) @@ -1130,87 +1132,78 @@ unit("ERC20RebasableBridgedPermit", ctxFactory) .run(); async function ctxFactory() { - const name = "StETH Test Token"; - const symbol = "StETH"; - const version = "1"; - const decimalsToSet = 18; - const decimals = BigNumber.from(10).pow(decimalsToSet); - const tokenRate = BigNumber.from('1164454276599657236'); - - const premintShares = wei.toBigNumber(wei`100 ether`); - const premintTokens = BigNumber.from(tokenRate).mul(premintShares).div(decimals); - - const provider = await hre.ethers.provider; - const blockNumber = await provider.getBlockNumber(); - const blockTimestamp = (await provider.getBlock(blockNumber)).timestamp; - - const [ - deployer, - owner, - recipient, - spender, - holder, - stranger, - user1, - user2 - ] = await hre.ethers.getSigners(); - const zero = await hre.ethers.getSigner(hre.ethers.constants.AddressZero); - - const wrappedToken = await new ERC20BridgedPermit__factory(deployer).deploy( - "WsETH Test Token", - "WsETH", - version, - decimalsToSet, - owner.address - ); - - const tokenRateOracle = await tokenRateOracleUnderProxy( - deployer, - zero.address, - owner.address, - zero.address, - BigNumber.from('86400'), - BigNumber.from('86400'), - BigNumber.from('500'), - tokenRate, - BigNumber.from(blockTimestamp) - ) - - const rebasableTokenImpl = await new ERC20RebasableBridgedPermit__factory(deployer).deploy( - name, - symbol, - version, - decimalsToSet, - wrappedToken.address, - tokenRateOracle.address, - owner.address - ); - - const l2TokensProxy = await new OssifiableProxy__factory(deployer).deploy( - rebasableTokenImpl.address, - deployer.address, - ERC20RebasableBridgedPermit__factory.createInterface().encodeFunctionData("initialize", [ - name, - symbol, - version - ]) - ); - - const rebasableProxied = ERC20RebasableBridgedPermit__factory.connect( - l2TokensProxy.address, - holder - ); - - await rebasableProxied.connect(owner).bridgeMintShares(holder.address, premintShares); - - await hre.network.provider.request({ - method: "hardhat_impersonateAccount", - params: [hre.ethers.constants.AddressZero], - }); + const name = "StETH Test Token"; + const symbol = "StETH"; + const version = "1"; + const decimals = BigNumber.from('18'); + const tenPowDecimals = BigNumber.from('10').pow(decimals); + const tokenRate = BigNumber.from('1164454276599657236'); // value taken from real contact on 23.04.24 + const tokenRateOutdatedDelay = BigNumber.from(86400); // 1 day + const maxAllowedL2ToL1ClockLag = BigNumber.from(86400); // 1 day + const maxAllowedTokenRateDeviationPerDay = BigNumber.from(500); // 5% + + const premintShares = wei.toBigNumber(wei`100 ether`); + const premintTokens = BigNumber.from(tokenRate).mul(premintShares).div(tenPowDecimals); + + const provider = await hre.ethers.provider; + const blockNumber = await provider.getBlockNumber(); + const blockTimestamp = BigNumber.from((await provider.getBlock(blockNumber)).timestamp); + + const [ + deployer, + owner, + recipient, + spender, + holder, + stranger, + user1, + user2 + ] = await hre.ethers.getSigners(); + + const zero = await hre.ethers.getSigner(hre.ethers.constants.AddressZero); + + const wrappedToken = await new ERC20BridgedPermit__factory(deployer).deploy( + "WsETH Test Token", + "WsETH", + version, + decimals, + owner.address + ); + + const tokenRateOracle = await tokenRateOracleUnderProxy( + deployer, + zero.address, + owner.address, + zero.address, + tokenRateOutdatedDelay, + maxAllowedL2ToL1ClockLag, + maxAllowedTokenRateDeviationPerDay, + tokenRate, + blockTimestamp + ) - return { - accounts: { deployer, owner, recipient, spender, holder, stranger, zero, user1, user2 }, - constants: { name, symbol, version, decimalsToSet, decimals, premintShares, premintTokens, tokenRate, blockTimestamp }, - contracts: { rebasableProxied, wrappedToken, tokenRateOracle } - }; + const rebasableProxied = await erc20RebasableBridgedPermitUnderProxy( + deployer, + holder, + name, + symbol, + version, + decimals, + tokenRateOracle, + wrappedToken, + owner.address, + ); + + await rebasableProxied.connect(owner).bridgeMintShares(holder.address, premintShares); + + await hre.network.provider.request({ + method: "hardhat_impersonateAccount", + params: [hre.ethers.constants.AddressZero], + }); + + return { + accounts: { deployer, owner, recipient, spender, holder, stranger, zero, user1, user2 }, + constants: { name, symbol, version, decimals, tenPowDecimals, premintShares, premintTokens, tokenRate, blockTimestamp }, + contracts: { rebasableProxied, wrappedToken, tokenRateOracle } + }; } diff --git a/utils/optimism/deploymentAllFromScratch.ts b/utils/optimism/deploymentAllFromScratch.ts index 26a22170..cc4ecd63 100644 --- a/utils/optimism/deploymentAllFromScratch.ts +++ b/utils/optimism/deploymentAllFromScratch.ts @@ -5,92 +5,92 @@ import { OptDeploymentOptions, DeployScriptParams } from "./types"; import network, { NetworkName } from "../network"; import { DeployScript, Logger } from "../deployment/DeployScript"; import { - ERC20BridgedPermit__factory, - ERC20RebasableBridgedPermit__factory, - IERC20Metadata__factory, - L1LidoTokensBridge__factory, - L2ERC20ExtendedTokensBridge__factory, - OssifiableProxy__factory, - TokenRateOracle__factory, - TokenRateNotifier__factory, - OpStackTokenRatePusher__factory + ERC20BridgedPermit__factory, + ERC20RebasableBridgedPermit__factory, + IERC20Metadata__factory, + L1LidoTokensBridge__factory, + L2ERC20ExtendedTokensBridge__factory, + OssifiableProxy__factory, + TokenRateOracle__factory, + TokenRateNotifier__factory, + OpStackTokenRatePusher__factory } from "../../typechain"; interface OptL1DeployScriptParams extends DeployScriptParams { } interface OptL2DeployScriptParams extends DeployScriptParams { - l2Token?: { - name?: string; - symbol?: string; - version?: string; - }; - l2TokenRebasable?: { - name?: string; - symbol?: string; - version?: string; - }; - tokenRateOracle: { - tokenRate: BigNumber; - l1Timestamp: BigNumber; - } + l2Token?: { + name?: string; + symbol?: string; + version?: string; + }; + l2TokenRebasable?: { + name?: string; + symbol?: string; + version?: string; + }; + tokenRateOracle: { + tokenRate: BigNumber; + l1Timestamp: BigNumber; + } } export class L1DeployAllScript extends DeployScript { - constructor( - deployer: Wallet, - bridgeImplAddress: string, - bridgeProxyAddress: string, - tokenRateNotifierImplAddress: string, - opStackTokenRatePusherImplAddress: string, - logger?: Logger - ) { - super(deployer, logger); - this.bridgeImplAddress = bridgeImplAddress; - this.bridgeProxyAddress = bridgeProxyAddress; - this.tokenRateNotifierImplAddress = tokenRateNotifierImplAddress; - this.opStackTokenRatePusherImplAddress = opStackTokenRatePusherImplAddress; - } + constructor( + deployer: Wallet, + bridgeImplAddress: string, + bridgeProxyAddress: string, + tokenRateNotifierImplAddress: string, + opStackTokenRatePusherImplAddress: string, + logger?: Logger + ) { + super(deployer, logger); + this.bridgeImplAddress = bridgeImplAddress; + this.bridgeProxyAddress = bridgeProxyAddress; + this.tokenRateNotifierImplAddress = tokenRateNotifierImplAddress; + this.opStackTokenRatePusherImplAddress = opStackTokenRatePusherImplAddress; + } - public bridgeImplAddress: string; - public bridgeProxyAddress: string; - public tokenRateNotifierImplAddress: string; - public opStackTokenRatePusherImplAddress: string; + public bridgeImplAddress: string; + public bridgeProxyAddress: string; + public tokenRateNotifierImplAddress: string; + public opStackTokenRatePusherImplAddress: string; } export class L2DeployAllScript extends DeployScript { - constructor( - deployer: Wallet, - tokenImplAddress: string, - tokenProxyAddress: string, - tokenRebasableImplAddress: string, - tokenRebasableProxyAddress: string, - tokenBridgeImplAddress: string, - tokenBridgeProxyAddress: string, - tokenRateOracleImplAddress: string, - tokenRateOracleProxyAddress: string, - logger?: Logger - ) { - super(deployer, logger); - this.tokenImplAddress = tokenImplAddress; - this.tokenProxyAddress = tokenProxyAddress; - this.tokenRebasableImplAddress = tokenRebasableImplAddress; - this.tokenRebasableProxyAddress = tokenRebasableProxyAddress; - this.tokenBridgeImplAddress = tokenBridgeImplAddress; - this.tokenBridgeProxyAddress = tokenBridgeProxyAddress; - this.tokenRateOracleImplAddress = tokenRateOracleImplAddress; - this.tokenRateOracleProxyAddress = tokenRateOracleProxyAddress; - } + constructor( + deployer: Wallet, + tokenImplAddress: string, + tokenProxyAddress: string, + tokenRebasableImplAddress: string, + tokenRebasableProxyAddress: string, + tokenBridgeImplAddress: string, + tokenBridgeProxyAddress: string, + tokenRateOracleImplAddress: string, + tokenRateOracleProxyAddress: string, + logger?: Logger + ) { + super(deployer, logger); + this.tokenImplAddress = tokenImplAddress; + this.tokenProxyAddress = tokenProxyAddress; + this.tokenRebasableImplAddress = tokenRebasableImplAddress; + this.tokenRebasableProxyAddress = tokenRebasableProxyAddress; + this.tokenBridgeImplAddress = tokenBridgeImplAddress; + this.tokenBridgeProxyAddress = tokenBridgeProxyAddress; + this.tokenRateOracleImplAddress = tokenRateOracleImplAddress; + this.tokenRateOracleProxyAddress = tokenRateOracleProxyAddress; + } - public tokenImplAddress: string; - public tokenProxyAddress: string; - public tokenRebasableImplAddress: string; - public tokenRebasableProxyAddress: string; - public tokenBridgeImplAddress: string; - public tokenBridgeProxyAddress: string; - public tokenRateOracleImplAddress: string; - public tokenRateOracleProxyAddress: string; + public tokenImplAddress: string; + public tokenProxyAddress: string; + public tokenRebasableImplAddress: string; + public tokenRebasableProxyAddress: string; + public tokenBridgeImplAddress: string; + public tokenBridgeProxyAddress: string; + public tokenRateOracleImplAddress: string; + public tokenRateOracleProxyAddress: string; } /// deploys from scratch @@ -100,239 +100,239 @@ export class L2DeployAllScript extends DeployScript { /// - bridgeL2 /// - Oracle export default function deploymentAll( - networkName: NetworkName, - options: OptDeploymentOptions = {} + networkName: NetworkName, + options: OptDeploymentOptions = {} ) { - const optAddresses = addresses(networkName, options); - return { - async deployAllScript( - l1Token: string, - l1TokenRebasable: string, - l1Params: OptL1DeployScriptParams, - l2Params: OptL2DeployScriptParams, - ): Promise<[L1DeployAllScript, L2DeployAllScript]> { + const optAddresses = addresses(networkName, options); + return { + async deployAllScript( + l1Token: string, + l1TokenRebasable: string, + l1Params: OptL1DeployScriptParams, + l2Params: OptL2DeployScriptParams, + ): Promise<[L1DeployAllScript, L2DeployAllScript]> { - const [ - expectedL1TokenBridgeImplAddress, - expectedL1TokenBridgeProxyAddress, - expectedL1TokenRateNotifierImplAddress, - expectedL1OpStackTokenRatePusherImplAddress, - ] = await network.predictAddresses(l1Params.deployer, l1Params.contractsShift + 4); + const [ + expectedL1TokenBridgeImplAddress, + expectedL1TokenBridgeProxyAddress, + expectedL1TokenRateNotifierImplAddress, + expectedL1OpStackTokenRatePusherImplAddress, + ] = await network.predictAddresses(l1Params.deployer, l1Params.contractsShift + 4); - const [ - expectedL2TokenImplAddress, - expectedL2TokenProxyAddress, - expectedL2TokenRebasableImplAddress, - expectedL2TokenRebasableProxyAddress, - expectedL2TokenBridgeImplAddress, - expectedL2TokenBridgeProxyAddress, - expectedL2TokenRateOracleImplAddress, - expectedL2TokenRateOracleProxyAddress - ] = await network.predictAddresses(l2Params.deployer, l2Params.contractsShift + 8); + const [ + expectedL2TokenImplAddress, + expectedL2TokenProxyAddress, + expectedL2TokenRebasableImplAddress, + expectedL2TokenRebasableProxyAddress, + expectedL2TokenBridgeImplAddress, + expectedL2TokenBridgeProxyAddress, + expectedL2TokenRateOracleImplAddress, + expectedL2TokenRateOracleProxyAddress + ] = await network.predictAddresses(l2Params.deployer, l2Params.contractsShift + 8); - const l1DeployScript = new L1DeployAllScript( - l1Params.deployer, - expectedL1TokenBridgeImplAddress, - expectedL1TokenBridgeProxyAddress, - expectedL1TokenRateNotifierImplAddress, - expectedL1OpStackTokenRatePusherImplAddress, - options?.logger - ) - .addStep({ - factory: L1LidoTokensBridge__factory, - args: [ - optAddresses.L1CrossDomainMessenger, - expectedL2TokenBridgeProxyAddress, - l1Token, - l1TokenRebasable, - expectedL2TokenProxyAddress, - expectedL2TokenRebasableProxyAddress, - options?.overrides, - ], - afterDeploy: (c) => - assert.equal(c.address, expectedL1TokenBridgeImplAddress), - }) - .addStep({ - factory: OssifiableProxy__factory, - args: [ - expectedL1TokenBridgeImplAddress, - l1Params.admins.proxy, - L1LidoTokensBridge__factory.createInterface().encodeFunctionData( - "initialize", - [l1Params.admins.bridge] - ), - options?.overrides, - ], - afterDeploy: (c) => - assert.equal(c.address, expectedL1TokenBridgeProxyAddress), - }) - .addStep({ - factory: TokenRateNotifier__factory, - args: [ - l1Params.deployer.address, - options?.overrides, - ], - afterDeploy: (c) => - assert.equal(c.address, expectedL1TokenRateNotifierImplAddress), - }) - .addStep({ - factory: OpStackTokenRatePusher__factory, - args: [ - optAddresses.L1CrossDomainMessenger, - l1Token, - expectedL2TokenRateOracleProxyAddress, - 1000, - options?.overrides, - ], - afterDeploy: (c) => - assert.equal(c.address, expectedL1OpStackTokenRatePusherImplAddress), - }); + const l1DeployScript = new L1DeployAllScript( + l1Params.deployer, + expectedL1TokenBridgeImplAddress, + expectedL1TokenBridgeProxyAddress, + expectedL1TokenRateNotifierImplAddress, + expectedL1OpStackTokenRatePusherImplAddress, + options?.logger + ) + .addStep({ + factory: L1LidoTokensBridge__factory, + args: [ + optAddresses.L1CrossDomainMessenger, + expectedL2TokenBridgeProxyAddress, + l1Token, + l1TokenRebasable, + expectedL2TokenProxyAddress, + expectedL2TokenRebasableProxyAddress, + options?.overrides, + ], + afterDeploy: (c) => + assert.equal(c.address, expectedL1TokenBridgeImplAddress), + }) + .addStep({ + factory: OssifiableProxy__factory, + args: [ + expectedL1TokenBridgeImplAddress, + l1Params.admins.proxy, + L1LidoTokensBridge__factory.createInterface().encodeFunctionData( + "initialize", + [l1Params.admins.bridge] + ), + options?.overrides, + ], + afterDeploy: (c) => + assert.equal(c.address, expectedL1TokenBridgeProxyAddress), + }) + .addStep({ + factory: TokenRateNotifier__factory, + args: [ + l1Params.deployer.address, + options?.overrides, + ], + afterDeploy: (c) => + assert.equal(c.address, expectedL1TokenRateNotifierImplAddress), + }) + .addStep({ + factory: OpStackTokenRatePusher__factory, + args: [ + optAddresses.L1CrossDomainMessenger, + l1Token, + expectedL2TokenRateOracleProxyAddress, + 1000, + options?.overrides, + ], + afterDeploy: (c) => + assert.equal(c.address, expectedL1OpStackTokenRatePusherImplAddress), + }); - const l1TokenInfo = IERC20Metadata__factory.connect( - l1Token, - l1Params.deployer - ); + const l1TokenInfo = IERC20Metadata__factory.connect( + l1Token, + l1Params.deployer + ); - const l1TokenRebasableInfo = IERC20Metadata__factory.connect( - l1TokenRebasable, - l1Params.deployer - ); - const [decimals, l2TokenName, l2TokenSymbol, l2TokenVersion, l2TokenRebasableName, l2TokenRebasableSymbol] = await Promise.all([ - l1TokenInfo.decimals(), - l2Params.l2Token?.name ?? l1TokenInfo.name(), - l2Params.l2Token?.symbol ?? l1TokenInfo.symbol(), - l2Params.l2Token?.version ?? "1", - l2Params.l2TokenRebasable?.name ?? l1TokenRebasableInfo.name(), - l2Params.l2TokenRebasable?.symbol ?? l1TokenRebasableInfo.symbol(), - ]); + const l1TokenRebasableInfo = IERC20Metadata__factory.connect( + l1TokenRebasable, + l1Params.deployer + ); + const [decimals, l2TokenName, l2TokenSymbol, l2TokenVersion, l2TokenRebasableName, l2TokenRebasableSymbol] = await Promise.all([ + l1TokenInfo.decimals(), + l2Params.l2Token?.name ?? l1TokenInfo.name(), + l2Params.l2Token?.symbol ?? l1TokenInfo.symbol(), + l2Params.l2Token?.version ?? "1", + l2Params.l2TokenRebasable?.name ?? l1TokenRebasableInfo.name(), + l2Params.l2TokenRebasable?.symbol ?? l1TokenRebasableInfo.symbol(), + ]); - const l2DeployScript = new L2DeployAllScript( - l2Params.deployer, - expectedL2TokenImplAddress, - expectedL2TokenProxyAddress, - expectedL2TokenRebasableImplAddress, - expectedL2TokenRebasableProxyAddress, - expectedL2TokenBridgeImplAddress, - expectedL2TokenBridgeProxyAddress, - expectedL2TokenRateOracleImplAddress, - expectedL2TokenRateOracleProxyAddress, - options?.logger - ) - .addStep({ - factory: ERC20BridgedPermit__factory, - args: [ - l2TokenName, - l2TokenSymbol, - l2TokenVersion, - decimals, - expectedL2TokenBridgeProxyAddress, - options?.overrides, - ], - afterDeploy: (c) => - assert.equal(c.address, expectedL2TokenImplAddress), - }) - .addStep({ - factory: OssifiableProxy__factory, - args: [ - expectedL2TokenImplAddress, - l2Params.admins.proxy, - ERC20BridgedPermit__factory.createInterface().encodeFunctionData( - "initialize", - [l2TokenName, l2TokenSymbol, l2TokenVersion] - ), - options?.overrides, - ], - afterDeploy: (c) => - assert.equal(c.address, expectedL2TokenProxyAddress), - }) - .addStep({ - factory: ERC20RebasableBridgedPermit__factory, - args: [ - l2TokenRebasableName, - l2TokenRebasableSymbol, - l2TokenVersion, - decimals, - expectedL2TokenProxyAddress, - expectedL2TokenRateOracleProxyAddress, - expectedL2TokenBridgeProxyAddress, - options?.overrides, - ], - afterDeploy: (c) => - assert.equal(c.address, expectedL2TokenRebasableImplAddress), - }) - .addStep({ - factory: OssifiableProxy__factory, - args: [ - expectedL2TokenRebasableImplAddress, - l2Params.admins.proxy, - ERC20RebasableBridgedPermit__factory.createInterface().encodeFunctionData( - "initialize", - [l2TokenRebasableName, l2TokenRebasableSymbol, l2TokenVersion] - ), - options?.overrides, - ], - afterDeploy: (c) => - assert.equal(c.address, expectedL2TokenRebasableProxyAddress), - }) - .addStep({ - factory: L2ERC20ExtendedTokensBridge__factory, - args: [ - optAddresses.L2CrossDomainMessenger, - expectedL1TokenBridgeProxyAddress, - l1Token, - l1TokenRebasable, - expectedL2TokenProxyAddress, - expectedL2TokenRebasableProxyAddress, - options?.overrides, - ], - afterDeploy: (c) => - assert.equal(c.address, expectedL2TokenBridgeImplAddress), - }) - .addStep({ - factory: OssifiableProxy__factory, - args: [ - expectedL2TokenBridgeImplAddress, - l2Params.admins.proxy, - L2ERC20ExtendedTokensBridge__factory.createInterface().encodeFunctionData( - "initialize", - [l2Params.admins.bridge] - ), - options?.overrides, - ], - }) - .addStep({ - factory: TokenRateOracle__factory, - args: [ - optAddresses.L2CrossDomainMessenger, - expectedL2TokenBridgeProxyAddress, - expectedL1OpStackTokenRatePusherImplAddress, - 86400, - 86400, - 500, - options?.overrides, - ], - afterDeploy: (c) => - assert.equal(c.address, expectedL2TokenRateOracleImplAddress), - }) - .addStep({ - factory: OssifiableProxy__factory, - args: [ - expectedL2TokenRateOracleImplAddress, - l2Params.admins.proxy, - TokenRateOracle__factory.createInterface().encodeFunctionData( - "initialize", - [ - l2Params.tokenRateOracle.tokenRate, - l2Params.tokenRateOracle.l1Timestamp - ] - ), - options?.overrides, - ], - afterDeploy: (c) => - assert.equal(c.address, expectedL2TokenRateOracleProxyAddress), - }); + const l2DeployScript = new L2DeployAllScript( + l2Params.deployer, + expectedL2TokenImplAddress, + expectedL2TokenProxyAddress, + expectedL2TokenRebasableImplAddress, + expectedL2TokenRebasableProxyAddress, + expectedL2TokenBridgeImplAddress, + expectedL2TokenBridgeProxyAddress, + expectedL2TokenRateOracleImplAddress, + expectedL2TokenRateOracleProxyAddress, + options?.logger + ) + .addStep({ + factory: ERC20BridgedPermit__factory, + args: [ + l2TokenName, + l2TokenSymbol, + l2TokenVersion, + decimals, + expectedL2TokenBridgeProxyAddress, + options?.overrides, + ], + afterDeploy: (c) => + assert.equal(c.address, expectedL2TokenImplAddress), + }) + .addStep({ + factory: OssifiableProxy__factory, + args: [ + expectedL2TokenImplAddress, + l2Params.admins.proxy, + ERC20BridgedPermit__factory.createInterface().encodeFunctionData( + "initialize", + [l2TokenName, l2TokenSymbol, l2TokenVersion] + ), + options?.overrides, + ], + afterDeploy: (c) => + assert.equal(c.address, expectedL2TokenProxyAddress), + }) + .addStep({ + factory: ERC20RebasableBridgedPermit__factory, + args: [ + l2TokenRebasableName, + l2TokenRebasableSymbol, + l2TokenVersion, + decimals, + expectedL2TokenProxyAddress, + expectedL2TokenRateOracleProxyAddress, + expectedL2TokenBridgeProxyAddress, + options?.overrides, + ], + afterDeploy: (c) => + assert.equal(c.address, expectedL2TokenRebasableImplAddress), + }) + .addStep({ + factory: OssifiableProxy__factory, + args: [ + expectedL2TokenRebasableImplAddress, + l2Params.admins.proxy, + ERC20RebasableBridgedPermit__factory.createInterface().encodeFunctionData( + "initialize", + [l2TokenRebasableName, l2TokenRebasableSymbol, l2TokenVersion] + ), + options?.overrides, + ], + afterDeploy: (c) => + assert.equal(c.address, expectedL2TokenRebasableProxyAddress), + }) + .addStep({ + factory: L2ERC20ExtendedTokensBridge__factory, + args: [ + optAddresses.L2CrossDomainMessenger, + expectedL1TokenBridgeProxyAddress, + l1Token, + l1TokenRebasable, + expectedL2TokenProxyAddress, + expectedL2TokenRebasableProxyAddress, + options?.overrides, + ], + afterDeploy: (c) => + assert.equal(c.address, expectedL2TokenBridgeImplAddress), + }) + .addStep({ + factory: OssifiableProxy__factory, + args: [ + expectedL2TokenBridgeImplAddress, + l2Params.admins.proxy, + L2ERC20ExtendedTokensBridge__factory.createInterface().encodeFunctionData( + "initialize", + [l2Params.admins.bridge] + ), + options?.overrides, + ], + }) + .addStep({ + factory: TokenRateOracle__factory, + args: [ + optAddresses.L2CrossDomainMessenger, + expectedL2TokenBridgeProxyAddress, + expectedL1OpStackTokenRatePusherImplAddress, + 86400, + 86400, + 500, + options?.overrides, + ], + afterDeploy: (c) => + assert.equal(c.address, expectedL2TokenRateOracleImplAddress), + }) + .addStep({ + factory: OssifiableProxy__factory, + args: [ + expectedL2TokenRateOracleImplAddress, + l2Params.admins.proxy, + TokenRateOracle__factory.createInterface().encodeFunctionData( + "initialize", + [ + l2Params.tokenRateOracle.tokenRate, + l2Params.tokenRateOracle.l1Timestamp + ] + ), + options?.overrides, + ], + afterDeploy: (c) => + assert.equal(c.address, expectedL2TokenRateOracleProxyAddress), + }); - return [l1DeployScript as L1DeployAllScript, l2DeployScript as L2DeployAllScript]; - }, - }; + return [l1DeployScript as L1DeployAllScript, l2DeployScript as L2DeployAllScript]; + }, + }; } diff --git a/utils/optimism/deploymentBridgesAndRebasableToken.ts b/utils/optimism/deploymentBridgesAndRebasableToken.ts index 6d9d9dc0..e77c2d0c 100644 --- a/utils/optimism/deploymentBridgesAndRebasableToken.ts +++ b/utils/optimism/deploymentBridgesAndRebasableToken.ts @@ -5,13 +5,13 @@ import { CommonOptions } from "./types"; import network, { NetworkName } from "../network"; import { DeployScript, Logger } from "../deployment/DeployScript"; import { - ERC20BridgedPermit__factory, - ERC20RebasableBridgedPermit__factory, - IERC20Metadata__factory, - L1LidoTokensBridge__factory, - L2ERC20ExtendedTokensBridge__factory, - OssifiableProxy__factory, - } from "../../typechain"; + ERC20BridgedPermit__factory, + ERC20RebasableBridgedPermit__factory, + IERC20Metadata__factory, + L1LidoTokensBridge__factory, + L2ERC20ExtendedTokensBridge__factory, + OssifiableProxy__factory, +} from "../../typechain"; interface OptL1DeployScriptParams { deployer: Wallet; @@ -31,48 +31,48 @@ interface OptDeploymentOptions extends CommonOptions { export class BridgeL1DeployScript extends DeployScript { - constructor( - deployer: Wallet, - bridgeImplAddress: string, - bridgeProxyAddress: string, - logger?: Logger - ) { - super(deployer, logger); - this.bridgeImplAddress = bridgeImplAddress; - this.bridgeProxyAddress = bridgeProxyAddress; - } + constructor( + deployer: Wallet, + bridgeImplAddress: string, + bridgeProxyAddress: string, + logger?: Logger + ) { + super(deployer, logger); + this.bridgeImplAddress = bridgeImplAddress; + this.bridgeProxyAddress = bridgeProxyAddress; + } - public bridgeImplAddress: string; - public bridgeProxyAddress: string; + public bridgeImplAddress: string; + public bridgeProxyAddress: string; } export class BridgeL2DeployScript extends DeployScript { - constructor( - deployer: Wallet, - tokenImplAddress: string, - tokenProxyAddress: string, - tokenRebasableImplAddress: string, - tokenRebasableProxyAddress: string, - tokenBridgeImplAddress: string, - tokenBridgeProxyAddress: string, - logger?: Logger - ) { - super(deployer, logger); - this.tokenImplAddress = tokenImplAddress; - this.tokenProxyAddress = tokenProxyAddress; - this.tokenRebasableImplAddress = tokenRebasableImplAddress; - this.tokenRebasableProxyAddress = tokenRebasableProxyAddress; - this.tokenBridgeImplAddress = tokenBridgeImplAddress; - this.tokenBridgeProxyAddress = tokenBridgeProxyAddress; - } + constructor( + deployer: Wallet, + tokenImplAddress: string, + tokenProxyAddress: string, + tokenRebasableImplAddress: string, + tokenRebasableProxyAddress: string, + tokenBridgeImplAddress: string, + tokenBridgeProxyAddress: string, + logger?: Logger + ) { + super(deployer, logger); + this.tokenImplAddress = tokenImplAddress; + this.tokenProxyAddress = tokenProxyAddress; + this.tokenRebasableImplAddress = tokenRebasableImplAddress; + this.tokenRebasableProxyAddress = tokenRebasableProxyAddress; + this.tokenBridgeImplAddress = tokenBridgeImplAddress; + this.tokenBridgeProxyAddress = tokenBridgeProxyAddress; + } - public tokenImplAddress: string; - public tokenProxyAddress: string; - public tokenRebasableImplAddress: string; - public tokenRebasableProxyAddress: string; - public tokenBridgeImplAddress: string; - public tokenBridgeProxyAddress: string; + public tokenImplAddress: string; + public tokenProxyAddress: string; + public tokenRebasableImplAddress: string; + public tokenRebasableProxyAddress: string; + public tokenBridgeImplAddress: string; + public tokenBridgeProxyAddress: string; } /// deploy Oracle first diff --git a/utils/optimism/deploymentNewImplementations.ts b/utils/optimism/deploymentNewImplementations.ts index 6ffa9e18..d2080086 100644 --- a/utils/optimism/deploymentNewImplementations.ts +++ b/utils/optimism/deploymentNewImplementations.ts @@ -5,87 +5,87 @@ import { OptDeploymentOptions, DeployScriptParams } from "./types"; import network, { NetworkName } from "../network"; import { DeployScript, Logger } from "../deployment/DeployScript"; import { - ERC20BridgedPermit__factory, - ERC20RebasableBridgedPermit__factory, - IERC20Metadata__factory, - L1LidoTokensBridge__factory, - L2ERC20ExtendedTokensBridge__factory, - OssifiableProxy__factory, - TokenRateOracle__factory + ERC20BridgedPermit__factory, + ERC20RebasableBridgedPermit__factory, + IERC20Metadata__factory, + L1LidoTokensBridge__factory, + L2ERC20ExtendedTokensBridge__factory, + OssifiableProxy__factory, + TokenRateOracle__factory } from "../../typechain"; interface OptL1DeployScriptParams extends DeployScriptParams { - tokenProxyAddress: string; - tokenRebasableProxyAddress: string; - opStackTokenRatePusherImplAddress: string; - tokenBridgeProxyAddress: string; - deployer: Wallet; - admins: { - proxy: string; - bridge: string - }; - contractsShift: number; + tokenProxyAddress: string; + tokenRebasableProxyAddress: string; + opStackTokenRatePusherImplAddress: string; + tokenBridgeProxyAddress: string; + deployer: Wallet; + admins: { + proxy: string; + bridge: string + }; + contractsShift: number; } interface OptL2DeployScriptParams extends DeployScriptParams { - tokenBridgeProxyAddress: string; - tokenProxyAddress: string; - tokenRateOracle: { - proxyAddress: string; - rateOutdatedDelay: BigNumber; - maxAllowedL2ToL1ClockLag: BigNumber; - maxAllowedTokenRateDeviationPerDay: BigNumber; - } - token: { - name: string; - symbol: string; - version: string; - }; - tokenRebasable: { - name: string; - symbol: string; - version: string; - }; + tokenBridgeProxyAddress: string; + tokenProxyAddress: string; + tokenRateOracle: { + proxyAddress: string; + rateOutdatedDelay: BigNumber; + maxAllowedL2ToL1ClockLag: BigNumber; + maxAllowedTokenRateDeviationPerDay: BigNumber; + } + token: { + name: string; + symbol: string; + version: string; + }; + tokenRebasable: { + name: string; + symbol: string; + version: string; + }; } export class BridgeL1DeployScript extends DeployScript { - constructor( - deployer: Wallet, - bridgeImplAddress: string, - logger?: Logger - ) { - super(deployer, logger); - this.bridgeImplAddress = bridgeImplAddress; - } + constructor( + deployer: Wallet, + bridgeImplAddress: string, + logger?: Logger + ) { + super(deployer, logger); + this.bridgeImplAddress = bridgeImplAddress; + } - public bridgeImplAddress: string; + public bridgeImplAddress: string; } export class BridgeL2DeployScript extends DeployScript { - constructor( - deployer: Wallet, - tokenImplAddress: string, - tokenRebasableImplAddress: string, - tokenRebasableProxyAddress: string, - tokenBridgeImplAddress: string, - tokenRateOracleImplAddress: string, - logger?: Logger - ) { - super(deployer, logger); - this.tokenImplAddress = tokenImplAddress; - this.tokenRebasableImplAddress = tokenRebasableImplAddress; - this.tokenRebasableProxyAddress = tokenRebasableProxyAddress; - this.tokenBridgeImplAddress = tokenBridgeImplAddress; - this.tokenRateOracleImplAddress = tokenRateOracleImplAddress; - } - - public tokenImplAddress: string; - public tokenRebasableImplAddress: string; - public tokenRebasableProxyAddress: string; - public tokenBridgeImplAddress: string; - public tokenRateOracleImplAddress: string; + constructor( + deployer: Wallet, + tokenImplAddress: string, + tokenRebasableImplAddress: string, + tokenRebasableProxyAddress: string, + tokenBridgeImplAddress: string, + tokenRateOracleImplAddress: string, + logger?: Logger + ) { + super(deployer, logger); + this.tokenImplAddress = tokenImplAddress; + this.tokenRebasableImplAddress = tokenRebasableImplAddress; + this.tokenRebasableProxyAddress = tokenRebasableProxyAddress; + this.tokenBridgeImplAddress = tokenBridgeImplAddress; + this.tokenRateOracleImplAddress = tokenRateOracleImplAddress; + } + + public tokenImplAddress: string; + public tokenRebasableImplAddress: string; + public tokenRebasableProxyAddress: string; + public tokenBridgeImplAddress: string; + public tokenRateOracleImplAddress: string; } /// deploys @@ -94,146 +94,146 @@ export class BridgeL2DeployScript extends DeployScript { /// - RebasableToken(stETH) Impl and Proxy (because it was never deployed before) /// - Non-rebasable token (wstETH) new Impl with Permissions export default function deploymentNewImplementations( - networkName: NetworkName, - options: OptDeploymentOptions = {} + networkName: NetworkName, + options: OptDeploymentOptions = {} ) { - const optAddresses = addresses(networkName, options); - return { - async deployScript( - l1Params: OptL1DeployScriptParams, - l2Params: OptL2DeployScriptParams, - ): Promise<[BridgeL1DeployScript, BridgeL2DeployScript]> { - - const [ - expectedL1TokenBridgeImplAddress, - ] = await network.predictAddresses(l1Params.deployer, l1Params.contractsShift + 1); - - const [ - expectedL2TokenImplAddress, - expectedL2TokenRebasableImplAddress, - expectedL2TokenRebasableProxyAddress, - expectedL2TokenBridgeImplAddress, - expectedL2TokenRateOracleImplAddress - ] = await network.predictAddresses(l2Params.deployer, l2Params.contractsShift + 5); - - const l1DeployScript = new BridgeL1DeployScript( - l1Params.deployer, - expectedL1TokenBridgeImplAddress, - options?.logger - ) - .addStep({ - factory: L1LidoTokensBridge__factory, - args: [ - optAddresses.L1CrossDomainMessenger, - l2Params.tokenBridgeProxyAddress, - l1Params.tokenProxyAddress, - l1Params.tokenRebasableProxyAddress, - l2Params.tokenProxyAddress, - expectedL2TokenRebasableProxyAddress, - options?.overrides, - ], - afterDeploy: (c) => - assert.equal(c.address, expectedL1TokenBridgeImplAddress), - }); - - const l1TokenInfo = IERC20Metadata__factory.connect( - l1Params.tokenProxyAddress, - l1Params.deployer - ); - - const l1TokenRebasableInfo = IERC20Metadata__factory.connect( - l1Params.tokenRebasableProxyAddress, - l1Params.deployer - ); - const [decimals, l2TokenName, l2TokenSymbol, l2TokenRebasableName, l2TokenRebasableSymbol] = await Promise.all([ - l1TokenInfo.decimals(), - l2Params.token?.name ?? l1TokenInfo.name(), - l2Params.token?.symbol ?? l1TokenInfo.symbol(), - l2Params.tokenRebasable?.name ?? l1TokenRebasableInfo.name(), - l2Params.tokenRebasable?.symbol ?? l1TokenRebasableInfo.symbol(), - ]); - - const l2DeployScript = new BridgeL2DeployScript( - l2Params.deployer, - expectedL2TokenImplAddress, - expectedL2TokenRebasableImplAddress, - expectedL2TokenRebasableProxyAddress, - expectedL2TokenBridgeImplAddress, - expectedL2TokenRateOracleImplAddress, - options?.logger - ) - .addStep({ - factory: ERC20BridgedPermit__factory, - args: [ - l2TokenName, - l2TokenSymbol, - l2Params.token.version, - decimals, - l2Params.tokenBridgeProxyAddress, - options?.overrides, - ], - afterDeploy: (c) => - assert.equal(c.address, expectedL2TokenImplAddress), - }) - .addStep({ - factory: ERC20RebasableBridgedPermit__factory, - args: [ - l2TokenRebasableName, - l2TokenRebasableSymbol, - l2Params.tokenRebasable.version, - decimals, - l2Params.tokenProxyAddress, - l2Params.tokenRateOracle.proxyAddress, - l2Params.tokenBridgeProxyAddress, - options?.overrides, - ], - afterDeploy: (c) => - assert.equal(c.address, expectedL2TokenRebasableImplAddress), - }) - .addStep({ - factory: OssifiableProxy__factory, - args: [ - expectedL2TokenRebasableImplAddress, - l2Params.admins.proxy, - ERC20RebasableBridgedPermit__factory.createInterface().encodeFunctionData( - "initialize", - [l2TokenRebasableName, l2TokenRebasableSymbol, l2Params.tokenRebasable.version] - ), - options?.overrides, - ], - afterDeploy: (c) => - assert.equal(c.address, expectedL2TokenRebasableProxyAddress), - }) - .addStep({ - factory: L2ERC20ExtendedTokensBridge__factory, - args: [ - optAddresses.L2CrossDomainMessenger, - l1Params.tokenBridgeProxyAddress, - l1Params.tokenProxyAddress, - l1Params.tokenRebasableProxyAddress, - l2Params.tokenProxyAddress, - expectedL2TokenRebasableProxyAddress, - options?.overrides, - ], - afterDeploy: (c) => - assert.equal(c.address, expectedL2TokenBridgeImplAddress), - }) - .addStep({ - factory: TokenRateOracle__factory, - args: [ - optAddresses.L2CrossDomainMessenger, - l2Params.tokenBridgeProxyAddress, - l1Params.opStackTokenRatePusherImplAddress, - l2Params.tokenRateOracle.rateOutdatedDelay, - l2Params.tokenRateOracle.maxAllowedL2ToL1ClockLag, - l2Params.tokenRateOracle.maxAllowedTokenRateDeviationPerDay, - options?.overrides, - ], - afterDeploy: (c) => - assert.equal(c.address, expectedL2TokenRateOracleImplAddress), - }); - - return [l1DeployScript as BridgeL1DeployScript, l2DeployScript as BridgeL2DeployScript]; - }, - }; + const optAddresses = addresses(networkName, options); + return { + async deployScript( + l1Params: OptL1DeployScriptParams, + l2Params: OptL2DeployScriptParams, + ): Promise<[BridgeL1DeployScript, BridgeL2DeployScript]> { + + const [ + expectedL1TokenBridgeImplAddress, + ] = await network.predictAddresses(l1Params.deployer, l1Params.contractsShift + 1); + + const [ + expectedL2TokenImplAddress, + expectedL2TokenRebasableImplAddress, + expectedL2TokenRebasableProxyAddress, + expectedL2TokenBridgeImplAddress, + expectedL2TokenRateOracleImplAddress + ] = await network.predictAddresses(l2Params.deployer, l2Params.contractsShift + 5); + + const l1DeployScript = new BridgeL1DeployScript( + l1Params.deployer, + expectedL1TokenBridgeImplAddress, + options?.logger + ) + .addStep({ + factory: L1LidoTokensBridge__factory, + args: [ + optAddresses.L1CrossDomainMessenger, + l2Params.tokenBridgeProxyAddress, + l1Params.tokenProxyAddress, + l1Params.tokenRebasableProxyAddress, + l2Params.tokenProxyAddress, + expectedL2TokenRebasableProxyAddress, + options?.overrides, + ], + afterDeploy: (c) => + assert.equal(c.address, expectedL1TokenBridgeImplAddress), + }); + + const l1TokenInfo = IERC20Metadata__factory.connect( + l1Params.tokenProxyAddress, + l1Params.deployer + ); + + const l1TokenRebasableInfo = IERC20Metadata__factory.connect( + l1Params.tokenRebasableProxyAddress, + l1Params.deployer + ); + const [decimals, l2TokenName, l2TokenSymbol, l2TokenRebasableName, l2TokenRebasableSymbol] = await Promise.all([ + l1TokenInfo.decimals(), + l2Params.token?.name ?? l1TokenInfo.name(), + l2Params.token?.symbol ?? l1TokenInfo.symbol(), + l2Params.tokenRebasable?.name ?? l1TokenRebasableInfo.name(), + l2Params.tokenRebasable?.symbol ?? l1TokenRebasableInfo.symbol(), + ]); + + const l2DeployScript = new BridgeL2DeployScript( + l2Params.deployer, + expectedL2TokenImplAddress, + expectedL2TokenRebasableImplAddress, + expectedL2TokenRebasableProxyAddress, + expectedL2TokenBridgeImplAddress, + expectedL2TokenRateOracleImplAddress, + options?.logger + ) + .addStep({ + factory: ERC20BridgedPermit__factory, + args: [ + l2TokenName, + l2TokenSymbol, + l2Params.token.version, + decimals, + l2Params.tokenBridgeProxyAddress, + options?.overrides, + ], + afterDeploy: (c) => + assert.equal(c.address, expectedL2TokenImplAddress), + }) + .addStep({ + factory: ERC20RebasableBridgedPermit__factory, + args: [ + l2TokenRebasableName, + l2TokenRebasableSymbol, + l2Params.tokenRebasable.version, + decimals, + l2Params.tokenProxyAddress, + l2Params.tokenRateOracle.proxyAddress, + l2Params.tokenBridgeProxyAddress, + options?.overrides, + ], + afterDeploy: (c) => + assert.equal(c.address, expectedL2TokenRebasableImplAddress), + }) + .addStep({ + factory: OssifiableProxy__factory, + args: [ + expectedL2TokenRebasableImplAddress, + l2Params.admins.proxy, + ERC20RebasableBridgedPermit__factory.createInterface().encodeFunctionData( + "initialize", + [l2TokenRebasableName, l2TokenRebasableSymbol, l2Params.tokenRebasable.version] + ), + options?.overrides, + ], + afterDeploy: (c) => + assert.equal(c.address, expectedL2TokenRebasableProxyAddress), + }) + .addStep({ + factory: L2ERC20ExtendedTokensBridge__factory, + args: [ + optAddresses.L2CrossDomainMessenger, + l1Params.tokenBridgeProxyAddress, + l1Params.tokenProxyAddress, + l1Params.tokenRebasableProxyAddress, + l2Params.tokenProxyAddress, + expectedL2TokenRebasableProxyAddress, + options?.overrides, + ], + afterDeploy: (c) => + assert.equal(c.address, expectedL2TokenBridgeImplAddress), + }) + .addStep({ + factory: TokenRateOracle__factory, + args: [ + optAddresses.L2CrossDomainMessenger, + l2Params.tokenBridgeProxyAddress, + l1Params.opStackTokenRatePusherImplAddress, + l2Params.tokenRateOracle.rateOutdatedDelay, + l2Params.tokenRateOracle.maxAllowedL2ToL1ClockLag, + l2Params.tokenRateOracle.maxAllowedTokenRateDeviationPerDay, + options?.overrides, + ], + afterDeploy: (c) => + assert.equal(c.address, expectedL2TokenRateOracleImplAddress), + }); + + return [l1DeployScript as BridgeL1DeployScript, l2DeployScript as BridgeL2DeployScript]; + }, + }; } diff --git a/utils/optimism/deploymentOracle.ts b/utils/optimism/deploymentOracle.ts index 432133c3..8536e140 100644 --- a/utils/optimism/deploymentOracle.ts +++ b/utils/optimism/deploymentOracle.ts @@ -6,146 +6,146 @@ import { DeployScriptParams, OptDeploymentOptions } from "./types"; import network, { NetworkName } from "../network"; import { DeployScript, Logger } from "../deployment/DeployScript"; import { - OssifiableProxy__factory, - TokenRateOracle__factory, - TokenRateNotifier__factory, - OpStackTokenRatePusher__factory + OssifiableProxy__factory, + TokenRateOracle__factory, + TokenRateNotifier__factory, + OpStackTokenRatePusher__factory } from "../../typechain"; -interface OptDeployScriptParams extends DeployScriptParams {} +interface OptDeployScriptParams extends DeployScriptParams { } interface OptL2DeployScriptParams extends DeployScriptParams { - tokenRateOracle: { - maxAllowedL2ToL1ClockLag: BigNumber; - maxAllowedTokenRateDeviationPerDay: BigNumber; - tokenRate: BigNumber; - l1Timestamp: BigNumber; - } + tokenRateOracle: { + maxAllowedL2ToL1ClockLag: BigNumber; + maxAllowedTokenRateDeviationPerDay: BigNumber; + tokenRate: BigNumber; + l1Timestamp: BigNumber; + } } export class OracleL1DeployScript extends DeployScript { - constructor( - deployer: Wallet, - tokenRateNotifierImplAddress: string, - opStackTokenRatePusherImplAddress: string, - logger?: Logger - ) { - super(deployer, logger); - this.tokenRateNotifierImplAddress = tokenRateNotifierImplAddress; - this.opStackTokenRatePusherImplAddress = opStackTokenRatePusherImplAddress; - } + constructor( + deployer: Wallet, + tokenRateNotifierImplAddress: string, + opStackTokenRatePusherImplAddress: string, + logger?: Logger + ) { + super(deployer, logger); + this.tokenRateNotifierImplAddress = tokenRateNotifierImplAddress; + this.opStackTokenRatePusherImplAddress = opStackTokenRatePusherImplAddress; + } - public tokenRateNotifierImplAddress: string; - public opStackTokenRatePusherImplAddress: string; + public tokenRateNotifierImplAddress: string; + public opStackTokenRatePusherImplAddress: string; } export class OracleL2DeployScript extends DeployScript { - constructor( - deployer: Wallet, - tokenRateOracleImplAddress: string, - tokenRateOracleProxyAddress: string, - logger?: Logger - ) { - super(deployer, logger); - this.tokenRateOracleImplAddress = tokenRateOracleImplAddress; - this.tokenRateOracleProxyAddress = tokenRateOracleProxyAddress; - } + constructor( + deployer: Wallet, + tokenRateOracleImplAddress: string, + tokenRateOracleProxyAddress: string, + logger?: Logger + ) { + super(deployer, logger); + this.tokenRateOracleImplAddress = tokenRateOracleImplAddress; + this.tokenRateOracleProxyAddress = tokenRateOracleProxyAddress; + } - public tokenRateOracleImplAddress: string; - public tokenRateOracleProxyAddress: string; + public tokenRateOracleImplAddress: string; + public tokenRateOracleProxyAddress: string; } export default function deploymentOracle( - networkName: NetworkName, - options: OptDeploymentOptions = {} + networkName: NetworkName, + options: OptDeploymentOptions = {} ) { - const optAddresses = addresses(networkName, options); - return { - async oracleDeployScript( - l1Token: string, - l2GasLimitForPushingTokenRate: number, - tokenRateOutdatedDelay: number, - l1Params: OptDeployScriptParams, - l2Params: OptL2DeployScriptParams, - ): Promise<[OracleL1DeployScript, OracleL2DeployScript]> { + const optAddresses = addresses(networkName, options); + return { + async oracleDeployScript( + l1Token: string, + l2GasLimitForPushingTokenRate: number, + tokenRateOutdatedDelay: number, + l1Params: OptDeployScriptParams, + l2Params: OptL2DeployScriptParams, + ): Promise<[OracleL1DeployScript, OracleL2DeployScript]> { - const [ - expectedL1TokenRateNotifierImplAddress, - expectedL1OpStackTokenRatePusherImplAddress, - ] = await network.predictAddresses(l1Params.deployer, 2); + const [ + expectedL1TokenRateNotifierImplAddress, + expectedL1OpStackTokenRatePusherImplAddress, + ] = await network.predictAddresses(l1Params.deployer, 2); - const [ - expectedL2TokenRateOracleImplAddress, - expectedL2TokenRateOracleProxyAddress - ] = await network.predictAddresses(l2Params.deployer, 2); + const [ + expectedL2TokenRateOracleImplAddress, + expectedL2TokenRateOracleProxyAddress + ] = await network.predictAddresses(l2Params.deployer, 2); - const l1DeployScript = new OracleL1DeployScript( - l1Params.deployer, - expectedL1TokenRateNotifierImplAddress, - expectedL1OpStackTokenRatePusherImplAddress, - options?.logger - ) - .addStep({ - factory: TokenRateNotifier__factory, - args: [ - l1Params.deployer.address, - options?.overrides, - ], - afterDeploy: (c) => - assert.equal(c.address, expectedL1TokenRateNotifierImplAddress), - }) - .addStep({ - factory: OpStackTokenRatePusher__factory, - args: [ - optAddresses.L1CrossDomainMessenger, - l1Token, - expectedL2TokenRateOracleProxyAddress, - l2GasLimitForPushingTokenRate, - options?.overrides, - ], - afterDeploy: (c) => - assert.equal(c.address, expectedL1OpStackTokenRatePusherImplAddress), - }); + const l1DeployScript = new OracleL1DeployScript( + l1Params.deployer, + expectedL1TokenRateNotifierImplAddress, + expectedL1OpStackTokenRatePusherImplAddress, + options?.logger + ) + .addStep({ + factory: TokenRateNotifier__factory, + args: [ + l1Params.deployer.address, + options?.overrides, + ], + afterDeploy: (c) => + assert.equal(c.address, expectedL1TokenRateNotifierImplAddress), + }) + .addStep({ + factory: OpStackTokenRatePusher__factory, + args: [ + optAddresses.L1CrossDomainMessenger, + l1Token, + expectedL2TokenRateOracleProxyAddress, + l2GasLimitForPushingTokenRate, + options?.overrides, + ], + afterDeploy: (c) => + assert.equal(c.address, expectedL1OpStackTokenRatePusherImplAddress), + }); - const l2DeployScript = new OracleL2DeployScript( - l2Params.deployer, - expectedL2TokenRateOracleImplAddress, - expectedL2TokenRateOracleProxyAddress, - options?.logger - ) - .addStep({ - factory: TokenRateOracle__factory, - args: [ - optAddresses.L2CrossDomainMessenger, - ethers.constants.AddressZero, - expectedL1OpStackTokenRatePusherImplAddress, - tokenRateOutdatedDelay, - l2Params.tokenRateOracle.maxAllowedL2ToL1ClockLag, - l2Params.tokenRateOracle.maxAllowedTokenRateDeviationPerDay, - options?.overrides, - ], - afterDeploy: (c) => - assert.equal(c.address, expectedL2TokenRateOracleImplAddress), - }) - .addStep({ - factory: OssifiableProxy__factory, - args: [ - expectedL2TokenRateOracleImplAddress, - l2Params.admins.proxy, - TokenRateOracle__factory.createInterface().encodeFunctionData( - "initialize", - [ - l2Params.tokenRateOracle.tokenRate, - l2Params.tokenRateOracle.l1Timestamp - ] - ), - options?.overrides, - ], - afterDeploy: (c) => - assert.equal(c.address, expectedL2TokenRateOracleProxyAddress), - }); + const l2DeployScript = new OracleL2DeployScript( + l2Params.deployer, + expectedL2TokenRateOracleImplAddress, + expectedL2TokenRateOracleProxyAddress, + options?.logger + ) + .addStep({ + factory: TokenRateOracle__factory, + args: [ + optAddresses.L2CrossDomainMessenger, + ethers.constants.AddressZero, + expectedL1OpStackTokenRatePusherImplAddress, + tokenRateOutdatedDelay, + l2Params.tokenRateOracle.maxAllowedL2ToL1ClockLag, + l2Params.tokenRateOracle.maxAllowedTokenRateDeviationPerDay, + options?.overrides, + ], + afterDeploy: (c) => + assert.equal(c.address, expectedL2TokenRateOracleImplAddress), + }) + .addStep({ + factory: OssifiableProxy__factory, + args: [ + expectedL2TokenRateOracleImplAddress, + l2Params.admins.proxy, + TokenRateOracle__factory.createInterface().encodeFunctionData( + "initialize", + [ + l2Params.tokenRateOracle.tokenRate, + l2Params.tokenRateOracle.l1Timestamp + ] + ), + options?.overrides, + ], + afterDeploy: (c) => + assert.equal(c.address, expectedL2TokenRateOracleProxyAddress), + }); - return [l1DeployScript as OracleL1DeployScript, l2DeployScript as OracleL2DeployScript]; - }, - }; + return [l1DeployScript as OracleL1DeployScript, l2DeployScript as OracleL2DeployScript]; + }, + }; } diff --git a/utils/testing/contractsFactory.ts b/utils/testing/contractsFactory.ts index 26070252..3e49a940 100644 --- a/utils/testing/contractsFactory.ts +++ b/utils/testing/contractsFactory.ts @@ -4,7 +4,9 @@ import { ERC20BridgedPermit__factory, TokenRateOracle__factory, ERC20RebasableBridgedPermit__factory, - OssifiableProxy__factory + OssifiableProxy__factory, + TokenRateOracle, + ERC20BridgedPermit } from "../../typechain"; export async function erc20BridgedPermitUnderProxy( @@ -22,34 +24,34 @@ export async function erc20BridgedPermitUnderProxy( version, decimals, bridge - ); + ); - const erc20BridgedPermitProxy = await new OssifiableProxy__factory(deployer).deploy( + const erc20BridgedPermitProxy = await new OssifiableProxy__factory(deployer).deploy( erc20BridgedPermitImpl.address, deployer.address, ERC20BridgedPermit__factory.createInterface().encodeFunctionData("initialize", [ - name, - symbol, - version + name, + symbol, + version ]) - ); + ); - const erc20BridgedPermit = ERC20BridgedPermit__factory.connect( + return ERC20BridgedPermit__factory.connect( erc20BridgedPermitProxy.address, holder - ); - - return erc20BridgedPermit; + ); } export async function tokenRateOracleUnderProxy( deployer: SignerWithAddress, + messenger: string, l2ERC20TokenBridge: string, l1TokenRatePusher: string, tokenRateOutdatedDelay: BigNumber, maxAllowedL2ToL1ClockLag: BigNumber, maxAllowedTokenRateDeviationPerDay: BigNumber, + tokenRate: BigNumber, blockTimestamp: BigNumber ) { @@ -71,60 +73,27 @@ export async function tokenRateOracleUnderProxy( blockTimestamp ]) ); - const tokenRateOracle = TokenRateOracle__factory.connect( + return TokenRateOracle__factory.connect( tokenRateOracleProxy.address, deployer ); - return tokenRateOracle; } export async function erc20RebasableBridgedPermitUnderProxy( deployer: SignerWithAddress, - - erc20BridgedPermitName: string, - erc20BridgedPermitSymbol: string, - erc20BridgedPermitVersion: string, - - erc20RebasableBridgedPermitName: string, - erc20RebasableBridgedPermitSymbol: string, - erc20RebasableBridgedPermitVersion: string, - + holder: SignerWithAddress, + name: string, + symbol: string, + version: string, decimals: BigNumber, - bridge: string, - - messenger: string, - l1TokenRatePusher: string, - tokenRateOutdatedDelay: BigNumber, - maxAllowedL2ToL1ClockLag: BigNumber, - maxAllowedTokenRateDeviationPerDay: BigNumber, - tokenRate: BigNumber, - blockTimestamp: BigNumber + tokenRateOracle: TokenRateOracle, + erc20BridgedPermit: ERC20BridgedPermit, + bridge: string ) { - const erc20BridgedPermit = await erc20BridgedPermitUnderProxy( - deployer, - erc20BridgedPermitName, - erc20BridgedPermitSymbol, - erc20BridgedPermitVersion, - decimals, - bridge - ); - - const tokenRateOracle = await tokenRateOracleUnderProxy( - deployer, - messenger, - bridge, - l1TokenRatePusher, - tokenRateOutdatedDelay, - maxAllowedL2ToL1ClockLag, - maxAllowedTokenRateDeviationPerDay, - tokenRate, - BigNumber.from(blockTimestamp) - ) - const erc20RebasableBridgedPermitImpl = await new ERC20RebasableBridgedPermit__factory(deployer).deploy( - erc20RebasableBridgedPermitName, - erc20RebasableBridgedPermitSymbol, - erc20RebasableBridgedPermitVersion, + name, + symbol, + version, decimals, erc20BridgedPermit.address, tokenRateOracle.address, @@ -135,16 +104,14 @@ export async function erc20RebasableBridgedPermitUnderProxy( erc20RebasableBridgedPermitImpl.address, deployer.address, ERC20RebasableBridgedPermit__factory.createInterface().encodeFunctionData("initialize", [ - erc20RebasableBridgedPermitName, - erc20RebasableBridgedPermitSymbol, - erc20RebasableBridgedPermitVersion + name, + symbol, + version, ]) ); - const erc20RebasableBridgedPermit = ERC20RebasableBridgedPermit__factory.connect( + return ERC20RebasableBridgedPermit__factory.connect( erc20RebasableBridgedPermitProxy.address, - deployer + holder ); - - return { tokenRateOracle, erc20BridgedPermit, erc20RebasableBridgedPermit }; } From 792071cdeaf61de927cc144e8c1c02d5f5996a01 Mon Sep 17 00:00:00 2001 From: Anton Kovalchuk Date: Wed, 24 Apr 2024 18:13:49 +0400 Subject: [PATCH 091/148] update comments, change functions order, emit additional event when burn shares --- contracts/BridgingManager.sol | 4 +- contracts/lib/DepositDataCodec.sol | 1 - contracts/lib/ECDSA.sol | 6 +-- contracts/lib/SignatureChecker.sol | 4 +- contracts/lib/UnstructuredRefStorage.sol | 4 +- contracts/lib/UnstructuredStorage.sol | 4 +- contracts/lido/TokenRateNotifier.sol | 7 ++-- .../optimism/L1ERC20ExtendedTokensBridge.sol | 12 +++--- contracts/optimism/L1LidoTokensBridge.sol | 2 +- .../optimism/L2ERC20ExtendedTokensBridge.sol | 5 +-- contracts/optimism/OpStackTokenRatePusher.sol | 2 +- .../RebasableAndNonRebasableTokens.sol | 24 +++++------ contracts/optimism/TokenRateOracle.sol | 11 +++-- contracts/token/ERC20Bridged.sol | 4 +- contracts/token/ERC20BridgedPermit.sol | 1 + contracts/token/ERC20RebasableBridged.sol | 7 ++-- .../token/ERC20RebasableBridgedPermit.sol | 1 + contracts/token/PermitExtension.sol | 40 +++++++++---------- contracts/utils/Versioned.sol | 4 +- .../ERC20RebasableBridgedPermit.unit.test.ts | 10 ++++- 20 files changed, 81 insertions(+), 72 deletions(-) diff --git a/contracts/BridgingManager.sol b/contracts/BridgingManager.sol index c302ae1d..1af3f449 100644 --- a/contracts/BridgingManager.sol +++ b/contracts/BridgingManager.sol @@ -1,11 +1,11 @@ -// SPDX-FileCopyrightText: 2022 Lido +// SPDX-FileCopyrightText: 2024 Lido // SPDX-License-Identifier: GPL-3.0 pragma solidity 0.8.10; import {AccessControl} from "@openzeppelin/contracts/access/AccessControl.sol"; -/// @author psirex +/// @author psirex, kovalgek /// @notice Contains administrative methods to retrieve and control the state of the bridging contract BridgingManager is AccessControl { /// @dev Stores the state of the bridging diff --git a/contracts/lib/DepositDataCodec.sol b/contracts/lib/DepositDataCodec.sol index af8a9910..46647d3d 100644 --- a/contracts/lib/DepositDataCodec.sol +++ b/contracts/lib/DepositDataCodec.sol @@ -26,7 +26,6 @@ library DepositDataCodec { } function decodeDepositData(bytes calldata buffer) internal pure returns (DepositData memory) { - if (buffer.length < RATE_FIELD_SIZE + TIMESTAMP_FIELD_SIZE) { revert ErrorDepositDataLength(); } diff --git a/contracts/lib/ECDSA.sol b/contracts/lib/ECDSA.sol index 98598c78..daa0f927 100644 --- a/contracts/lib/ECDSA.sol +++ b/contracts/lib/ECDSA.sol @@ -1,12 +1,10 @@ // SPDX-FileCopyrightText: 2024 Lido // SPDX-License-Identifier: MIT -/// @dev Extracted from: -/// https://github.com/OpenZeppelin/openzeppelin-contracts/blob/v3.4.0/contracts/cryptography/ECDSA.sol#L53 -/// Also it is used in Lido Core Protocol. - pragma solidity 0.8.10; +/// @dev Extracted from: https://github.com/OpenZeppelin/openzeppelin-contracts/blob/v3.4.0/contracts/cryptography/ECDSA.sol#L53 +/// Also it is used in Lido on Ethereum protocol: https://github.com/lidofinance/lido-dao/blob/master/contracts/common/lib/ECDSA.sol library ECDSA { /** * @dev Returns the address that signed a hashed message (`hash`). diff --git a/contracts/lib/SignatureChecker.sol b/contracts/lib/SignatureChecker.sol index 183e0266..269d4e63 100644 --- a/contracts/lib/SignatureChecker.sol +++ b/contracts/lib/SignatureChecker.sol @@ -5,8 +5,8 @@ pragma solidity 0.8.10; import {ECDSA} from "./ECDSA.sol"; -/// @dev A copy of SignatureUtils.sol contract from Lido Core Protocol -/// https://github.com/lidofinance/lido-dao/blob/master/contracts/common/lib/SignatureUtils.sol +/// @dev A copy of SignatureUtils.sol library from Lido on Ethereum protocol. +/// https://github.com/lidofinance/lido-dao/blob/master/contracts/common/lib/SignatureUtils.sol library SignatureChecker { /** * @dev The selector of the ERC1271's `isValidSignature(bytes32 hash, bytes signature)` function, diff --git a/contracts/lib/UnstructuredRefStorage.sol b/contracts/lib/UnstructuredRefStorage.sol index f4657639..28a80c9b 100644 --- a/contracts/lib/UnstructuredRefStorage.sol +++ b/contracts/lib/UnstructuredRefStorage.sol @@ -3,6 +3,8 @@ pragma solidity 0.8.10; +/// @dev A copy of UnstructuredRefStorage.sol library from Lido on Ethereum protocol. +/// https://github.com/lidofinance/lido-dao/blob/master/contracts/0.8.9/lib/UnstructuredRefStorage.sol library UnstructuredRefStorage { function storageMapAddressMapAddressUint256(bytes32 _position) internal pure returns ( mapping(address => mapping(address => uint256)) storage result @@ -15,4 +17,4 @@ library UnstructuredRefStorage { ) { assembly { result.slot := _position } } -} \ No newline at end of file +} diff --git a/contracts/lib/UnstructuredStorage.sol b/contracts/lib/UnstructuredStorage.sol index 058d1ed3..994b4e34 100644 --- a/contracts/lib/UnstructuredStorage.sol +++ b/contracts/lib/UnstructuredStorage.sol @@ -3,6 +3,8 @@ pragma solidity 0.8.10; +/// @dev A copy of UnstructuredStorage.sol library from Lido on Ethereum protocol. +/// https://github.com/lidofinance/lido-dao/blob/master/contracts/0.8.9/lib/UnstructuredStorage.sol library UnstructuredStorage { function getStorageBool(bytes32 position) internal view returns (bool data) { assembly { data := sload(position) } @@ -35,4 +37,4 @@ library UnstructuredStorage { function setStorageUint256(bytes32 position, uint256 data) internal { assembly { sstore(position, data) } } -} \ No newline at end of file +} diff --git a/contracts/lido/TokenRateNotifier.sol b/contracts/lido/TokenRateNotifier.sol index 24ca4c31..13704ef1 100644 --- a/contracts/lido/TokenRateNotifier.sol +++ b/contracts/lido/TokenRateNotifier.sol @@ -3,9 +3,9 @@ pragma solidity 0.8.10; -import {ITokenRatePusher} from "./interfaces/ITokenRatePusher.sol"; import {Ownable} from "@openzeppelin/contracts/access/Ownable.sol"; import {ERC165Checker} from "@openzeppelin/contracts/utils/introspection/ERC165Checker.sol"; +import {ITokenRatePusher} from "./interfaces/ITokenRatePusher.sol"; /// @notice An interface to subscribe on the `stETH` token rebases (defined in the `Lido` core contract) interface IPostTokenRebaseReceiver { @@ -23,7 +23,7 @@ interface IPostTokenRebaseReceiver { } /// @author kovalgek -/// @notice Notifies all observers when rebase event occures. +/// @notice Notifies all `observers` when rebase event occures. contract TokenRateNotifier is Ownable, IPostTokenRebaseReceiver { using ERC165Checker for address; @@ -70,7 +70,6 @@ contract TokenRateNotifier is Ownable, IPostTokenRebaseReceiver { /// @notice Remove a observer at the given `observer_` position /// @param observer_ observer remove position function removeObserver(address observer_) external onlyOwner { - uint256 observerIndexToRemove = _observerIndex(observer_); if (observerIndexToRemove == INDEX_NOT_FOUND) { @@ -114,7 +113,7 @@ contract TokenRateNotifier is Ownable, IPostTokenRebaseReceiver { } /// @notice Observer length - /// @return Added observers count + /// @return Added `observers` count function observersLength() external view returns (uint256) { return observers.length; } diff --git a/contracts/optimism/L1ERC20ExtendedTokensBridge.sol b/contracts/optimism/L1ERC20ExtendedTokensBridge.sol index 40cc03b9..ba674b6e 100644 --- a/contracts/optimism/L1ERC20ExtendedTokensBridge.sol +++ b/contracts/optimism/L1ERC20ExtendedTokensBridge.sol @@ -50,9 +50,6 @@ abstract contract L1ERC20ExtendedTokensBridge is L2_TOKEN_BRIDGE = l2TokenBridge_; } - /// @notice required to abstact a way token rate is requested. - function tokenRate() virtual internal view returns (uint256); - /// @inheritdoc IL1ERC20Bridge function l2TokenBridge() external view returns (address) { return L2_TOKEN_BRIDGE; @@ -118,7 +115,7 @@ abstract contract L1ERC20ExtendedTokensBridge is emit ERC20WithdrawalFinalized(l1Token_, l2Token_, from_, to_, withdrawnL1TokenAmount, data_); } - /// @dev Performs the logic for deposits by informing the L2 token bridge contract + /// @notice Performs the logic for deposits by informing the L2 token bridge contract /// of the deposit and calling safeTransferFrom to lock the L1 funds. /// @param l1Token_ Address of the L1 ERC20 we are depositing /// @param l2Token_ Address of the L1 respective L2 ERC20 @@ -147,7 +144,7 @@ abstract contract L1ERC20ExtendedTokensBridge is sendCrossDomainMessage(L2_TOKEN_BRIDGE, l2Gas_, message); } - /// @dev Transfers tokens to the bridge and wraps if needed. + /// @notice Transfers tokens to the bridge and wraps if needed. /// @param l1Token_ Address of the L1 ERC20 we are depositing. /// @param from_ Account to pull the deposit from on L1. /// @param amount_ Amount of the ERC20 to deposit. @@ -169,11 +166,14 @@ abstract contract L1ERC20ExtendedTokensBridge is function _encodeInputDepositData(bytes calldata data_) internal view returns (bytes memory) { return DepositDataCodec.encodeDepositData(DepositDataCodec.DepositData({ - rate: uint96(tokenRate()), + rate: uint96(_tokenRate()), timestamp: uint40(block.timestamp), data: data_ })); } + /// @notice required to abstact a way token rate is requested. + function _tokenRate() virtual internal view returns (uint256); + error ErrorSenderNotEOA(); } diff --git a/contracts/optimism/L1LidoTokensBridge.sol b/contracts/optimism/L1LidoTokensBridge.sol index 3023be37..49941b7c 100644 --- a/contracts/optimism/L1LidoTokensBridge.sol +++ b/contracts/optimism/L1LidoTokensBridge.sol @@ -56,7 +56,7 @@ contract L1LidoTokensBridge is L1ERC20ExtendedTokensBridge, Versioned { _initializeContractVersionTo(2); } - function tokenRate() override internal view returns (uint256) { + function _tokenRate() override internal view returns (uint256) { return IERC20WstETH(L1_TOKEN_NON_REBASABLE).stEthPerToken(); } } diff --git a/contracts/optimism/L2ERC20ExtendedTokensBridge.sol b/contracts/optimism/L2ERC20ExtendedTokensBridge.sol index 1b41f6aa..55b38bc3 100644 --- a/contracts/optimism/L2ERC20ExtendedTokensBridge.sol +++ b/contracts/optimism/L2ERC20ExtendedTokensBridge.sol @@ -10,7 +10,6 @@ import {IL1ERC20Bridge} from "./interfaces/IL1ERC20Bridge.sol"; import {IL2ERC20Bridge} from "./interfaces/IL2ERC20Bridge.sol"; import {IERC20Bridged} from "../token/ERC20Bridged.sol"; import {ITokenRateUpdatable} from "../optimism/interfaces/ITokenRateUpdatable.sol"; -import {IERC20Wrapper} from "../token/interfaces/IERC20Wrapper.sol"; import {ERC20RebasableBridged} from "../token/ERC20RebasableBridged.sol"; import {BridgingManager} from "../BridgingManager.sol"; import {RebasableAndNonRebasableTokens} from "./RebasableAndNonRebasableTokens.sol"; @@ -159,7 +158,7 @@ contract L2ERC20ExtendedTokensBridge is sendCrossDomainMessage(L1_TOKEN_BRIDGE, l1Gas_, message); } - /// @dev Mints tokens. + /// @notice Mints tokens and returns amount of minted tokens. /// @param l2Token_ Address of L2 token for which deposit is finalizing. /// @param to_ Account that token mints for. /// @param amount_ Amount of token or shares to mint. @@ -178,7 +177,7 @@ contract L2ERC20ExtendedTokensBridge is return amount_; } - /// @dev Burns tokens + /// @notice Burns tokens and returns amount of non-rebasable token to withdraw. /// @param l2Token_ Address of L2 token where withdrawal was initiated. /// @param from_ Account which tokens are burns. /// @param amount_ Amount of token to burn. diff --git a/contracts/optimism/OpStackTokenRatePusher.sol b/contracts/optimism/OpStackTokenRatePusher.sol index 52479a22..c2ee66ed 100644 --- a/contracts/optimism/OpStackTokenRatePusher.sol +++ b/contracts/optimism/OpStackTokenRatePusher.sol @@ -3,11 +3,11 @@ pragma solidity 0.8.10; +import {ERC165} from "@openzeppelin/contracts/utils/introspection/ERC165.sol"; import {CrossDomainEnabled} from "./CrossDomainEnabled.sol"; import {ITokenRatePusher} from "../lido/interfaces/ITokenRatePusher.sol"; import {IERC20WstETH} from "./L1LidoTokensBridge.sol"; import {ITokenRateUpdatable} from "../optimism/interfaces/ITokenRateUpdatable.sol"; -import {ERC165} from "@openzeppelin/contracts/utils/introspection/ERC165.sol"; /// @author kovalgek /// @notice Pushes token rate to L2 Oracle. diff --git a/contracts/optimism/RebasableAndNonRebasableTokens.sol b/contracts/optimism/RebasableAndNonRebasableTokens.sol index 6e35fbc2..40a8b9f8 100644 --- a/contracts/optimism/RebasableAndNonRebasableTokens.sol +++ b/contracts/optimism/RebasableAndNonRebasableTokens.sol @@ -35,6 +35,18 @@ contract RebasableAndNonRebasableTokens { L2_TOKEN_REBASABLE = l2TokenRebasable_; } + function _isSupportedL1L2TokensPair(address l1Token_, address l2Token_) internal view returns (bool) { + bool isNonRebasablePair = l1Token_ == L1_TOKEN_NON_REBASABLE && l2Token_ == L2_TOKEN_NON_REBASABLE; + bool isRebasablePair = l1Token_ == L1_TOKEN_REBASABLE && l2Token_ == L2_TOKEN_REBASABLE; + return isNonRebasablePair || isRebasablePair; + } + + function _getL1Token(address l2Token_) internal view returns (address) { + if (l2Token_ == L2_TOKEN_NON_REBASABLE) { return L1_TOKEN_NON_REBASABLE; } + if (l2Token_ == L2_TOKEN_REBASABLE) { return L1_TOKEN_REBASABLE; } + revert ErrorUnsupportedL2Token(l2Token_); + } + /// @dev Validates that passed l1Token_ and l2Token_ tokens pair is supported by the bridge. modifier onlySupportedL1L2TokensPair(address l1Token_, address l2Token_) { if (!_isSupportedL1L2TokensPair(l1Token_, l2Token_)) { @@ -59,18 +71,6 @@ contract RebasableAndNonRebasableTokens { _; } - function _isSupportedL1L2TokensPair(address l1Token_, address l2Token_) internal view returns (bool) { - bool isNonRebasablePair = l1Token_ == L1_TOKEN_NON_REBASABLE && l2Token_ == L2_TOKEN_NON_REBASABLE; - bool isRebasablePair = l1Token_ == L1_TOKEN_REBASABLE && l2Token_ == L2_TOKEN_REBASABLE; - return isNonRebasablePair || isRebasablePair; - } - - function _getL1Token(address l2Token_) internal view returns (address) { - if (l2Token_ == L2_TOKEN_NON_REBASABLE) { return L1_TOKEN_NON_REBASABLE; } - if (l2Token_ == L2_TOKEN_REBASABLE) { return L1_TOKEN_REBASABLE; } - revert ErrorUnsupportedL2Token(l2Token_); - } - error ErrorUnsupportedL1Token(address l1Token); error ErrorUnsupportedL2Token(address l2Token); error ErrorUnsupportedL1L2TokensPair(address l1Token, address l2Token); diff --git a/contracts/optimism/TokenRateOracle.sol b/contracts/optimism/TokenRateOracle.sol index b4de7dba..c71e506a 100644 --- a/contracts/optimism/TokenRateOracle.sol +++ b/contracts/optimism/TokenRateOracle.sol @@ -24,10 +24,6 @@ contract TokenRateOracle is CrossDomainEnabled, ITokenRateOracle, Versioned { uint64 rateL1Timestamp; } // occupy a single slot - /// @dev Location of the slot with TokenRateData - bytes32 private constant TOKEN_RATE_DATA_SLOT = - keccak256("TokenRateOracle.TOKEN_RATE_DATA_SLOT"); - /// @notice A bridge which can update oracle. address public immutable L2_ERC20_TOKEN_BRIDGE; @@ -53,6 +49,9 @@ contract TokenRateOracle is CrossDomainEnabled, ITokenRateOracle, Versioned { /// @notice Basic point scale. uint256 private constant BASIS_POINT_SCALE = 1e4; + /// @dev Location of the slot with TokenRateData + bytes32 private constant TOKEN_RATE_DATA_SLOT = keccak256("TokenRateOracle.TOKEN_RATE_DATA_SLOT"); + /// @param messenger_ L2 messenger address being used for cross-chain communications /// @param l2ERC20TokenBridge_ the bridge address that has a right to updates oracle. /// @param l1TokenRatePusher_ An address of account on L1 that can update token rate. @@ -142,8 +141,8 @@ contract TokenRateOracle is CrossDomainEnabled, ITokenRateOracle, Versioned { return block.timestamp > _getRateL1Timestamp() + TOKEN_RATE_OUTDATED_DELAY; } - /// @dev Allow tokenRate deviation from the previous value to be - /// ±`MAX_ALLOWED_TOKEN_RATE_DEVIATION_PER_DAY` BP per day. + /// @notice Allow tokenRate deviation from the previous value to be + /// ±`MAX_ALLOWED_TOKEN_RATE_DEVIATION_PER_DAY` BP per day. function _isTokenRateWithinAllowedRange( uint256 newTokenRate_, uint256 newRateL1Timestamp_ ) internal view returns (bool) { diff --git a/contracts/token/ERC20Bridged.sol b/contracts/token/ERC20Bridged.sol index a8546c4b..ec1f1bfd 100644 --- a/contracts/token/ERC20Bridged.sol +++ b/contracts/token/ERC20Bridged.sol @@ -13,12 +13,12 @@ interface IERC20Bridged is IERC20 { /// @notice Returns bridge which can mint and burn tokens on L2 function bridge() external view returns (address); - /// @notice Creates amount_ tokens and assigns them to account_, increasing the total supply + /// @notice Creates `amount_` tokens and assigns them to `account_`, increasing the total supply /// @param account_ An address of the account to mint tokens /// @param amount_ An amount of tokens to mint function bridgeMint(address account_, uint256 amount_) external; - /// @notice Destroys amount_ tokens from account_, reducing the total supply + /// @notice Destroys `amount_` tokens from `account_`, reducing the total supply /// @param account_ An address of the account to burn tokens /// @param amount_ An amount of tokens to burn function bridgeBurn(address account_, uint256 amount_) external; diff --git a/contracts/token/ERC20BridgedPermit.sol b/contracts/token/ERC20BridgedPermit.sol index 161d8d52..a9fad757 100644 --- a/contracts/token/ERC20BridgedPermit.sol +++ b/contracts/token/ERC20BridgedPermit.sol @@ -8,6 +8,7 @@ import {PermitExtension} from "./PermitExtension.sol"; import {Versioned} from "../utils/Versioned.sol"; /// @author kovalgek +/// @notice extends ERC20Bridged functionality that allows to use permits and versioning. contract ERC20BridgedPermit is ERC20Bridged, PermitExtension, Versioned { /// @param name_ The name of the token diff --git a/contracts/token/ERC20RebasableBridged.sol b/contracts/token/ERC20RebasableBridged.sol index 0f0d5d0a..2abaaff1 100644 --- a/contracts/token/ERC20RebasableBridged.sol +++ b/contracts/token/ERC20RebasableBridged.sol @@ -306,7 +306,7 @@ contract ERC20RebasableBridged is IERC20, IERC20Wrapper, IERC20BridgedShares, ER return (uint256(answer), uint256(rateDecimals)); } - /// @dev Creates amount_ shares and assigns them to account_, increasing the total shares supply + /// @dev Creates `amount_` shares and assigns them to `account_`, increasing the total shares supply /// @param recipient_ An address of the account to mint shares /// @param amount_ An amount of shares to mint function _mintShares( @@ -319,7 +319,7 @@ contract ERC20RebasableBridged is IERC20, IERC20Wrapper, IERC20BridgedShares, ER _emitTransferEvents(address(0), recipient_, tokensAmount ,amount_); } - /// @dev Destroys amount_ shares from account_, reducing the total shares supply. + /// @dev Destroys `amount_` shares from `account_`, reducing the total shares supply. /// @param account_ An address of the account to mint shares /// @param amount_ An amount of shares to mint function _burnShares( @@ -330,7 +330,8 @@ contract ERC20RebasableBridged is IERC20, IERC20Wrapper, IERC20BridgedShares, ER if (accountShares < amount_) revert ErrorNotEnoughBalance(); _setTotalShares(_getTotalShares() - amount_); _getShares()[account_] = accountShares - amount_; - emit Transfer(account_, address(0), amount_); + uint256 tokensAmount = _getTokensByShares(amount_); + _emitTransferEvents(account_, address(0), tokensAmount ,amount_); } /// @dev Moves `sharesAmount_` shares from `sender_` to `recipient_`. diff --git a/contracts/token/ERC20RebasableBridgedPermit.sol b/contracts/token/ERC20RebasableBridgedPermit.sol index 1e2ee825..4891a53c 100644 --- a/contracts/token/ERC20RebasableBridgedPermit.sol +++ b/contracts/token/ERC20RebasableBridgedPermit.sol @@ -8,6 +8,7 @@ import {PermitExtension} from "./PermitExtension.sol"; import {Versioned} from "../utils/Versioned.sol"; /// @author kovalgek +/// @notice extends ERC20RebasableBridged functionality that allows to use permits and versioning. contract ERC20RebasableBridgedPermit is ERC20RebasableBridged, PermitExtension, Versioned { /// @param name_ The name of the token diff --git a/contracts/token/PermitExtension.sol b/contracts/token/PermitExtension.sol index 8dc9b6a6..31d1ef12 100644 --- a/contracts/token/PermitExtension.sol +++ b/contracts/token/PermitExtension.sol @@ -3,9 +3,9 @@ pragma solidity 0.8.10; -import {UnstructuredRefStorage} from "../lib//UnstructuredRefStorage.sol"; import {EIP712} from "@openzeppelin/contracts/utils/cryptography/draft-EIP712.sol"; import {IERC2612} from "@openzeppelin/contracts/interfaces/draft-IERC2612.sol"; +import {UnstructuredRefStorage} from "../lib//UnstructuredRefStorage.sol"; import {SignatureChecker} from "../lib/SignatureChecker.sol"; /// @author arwer13, kovalgek @@ -19,9 +19,6 @@ abstract contract PermitExtension is IERC2612, EIP712 { string version; } - /// @dev Location of the slot with EIP5267Metadata - bytes32 private constant EIP5267_METADATA_SLOT = keccak256("PermitExtension.eip5267MetadataSlot"); - /// @dev user shares slot position. bytes32 internal constant NONCE_BY_ADDRESS_POSITION = keccak256("PermitExtension.NONCE_BY_ADDRESS_POSITION"); @@ -30,20 +27,15 @@ abstract contract PermitExtension is IERC2612, EIP712 { bytes32 internal constant PERMIT_TYPEHASH = 0x6e71edae12b1b97f4d1f60370fef10105fa2faae0126114a169c64845d6126c9; + /// @dev Location of the slot with EIP5267Metadata + bytes32 private constant EIP5267_METADATA_SLOT = keccak256("PermitExtension.eip5267MetadataSlot"); + /// @param name_ The name of the token /// @param version_ The current major version of the signing domain (aka token version) constructor(string memory name_, string memory version_) EIP712(name_, version_) { _initializeEIP5267Metadata(name_, version_); } - /// @notice Sets the name and the version of the tokens if they both are empty - /// @param name_ The name of the token - /// @param version_ The version of the token - function _initializeEIP5267Metadata(string memory name_, string memory version_) internal { - _setEIP5267MetadataName(name_); - _setEIP5267MetadataVersion(version_); - } - /// @dev Sets `value` as the allowance of `spender` over ``owner``'s tokens, /// given ``owner``'s signed approval. /// @@ -118,6 +110,14 @@ abstract contract PermitExtension is IERC2612, EIP712 { ); } + /// @notice Sets the name and the version of the tokens if they both are empty + /// @param name_ The name of the token + /// @param version_ The version of the token + function _initializeEIP5267Metadata(string memory name_, string memory version_) internal { + _setEIP5267MetadataName(name_); + _setEIP5267MetadataVersion(version_); + } + /// @dev "Consume a nonce": return the current value and increment. function _useNonce(address _owner) internal returns (uint256 current) { current = _getNonceByAddress()[_owner]; @@ -132,14 +132,6 @@ abstract contract PermitExtension is IERC2612, EIP712 { /// @dev Override this function in the inherited contract to invoke the approve() function of ERC20. function _permitAccepted(address owner_, address spender_, uint256 amount_) internal virtual; - /// @dev Returns the reference to the slot with EIP5267Metadata struct - function _loadEIP5267Metadata() private pure returns (EIP5267Metadata storage r) { - bytes32 slot = EIP5267_METADATA_SLOT; - assembly { - r.slot := slot - } - } - /// @dev Sets the name of the token. Might be called only when the name is empty function _setEIP5267MetadataName(string memory name_) internal { _loadEIP5267Metadata().name = name_; @@ -150,6 +142,14 @@ abstract contract PermitExtension is IERC2612, EIP712 { _loadEIP5267Metadata().version = version_; } + /// @dev Returns the reference to the slot with EIP5267Metadata struct + function _loadEIP5267Metadata() private pure returns (EIP5267Metadata storage r) { + bytes32 slot = EIP5267_METADATA_SLOT; + assembly { + r.slot := slot + } + } + error ErrorInvalidSignature(); error ErrorDeadlineExpired(); } diff --git a/contracts/utils/Versioned.sol b/contracts/utils/Versioned.sol index 12e689f4..b47e6901 100644 --- a/contracts/utils/Versioned.sol +++ b/contracts/utils/Versioned.sol @@ -5,8 +5,8 @@ pragma solidity 0.8.10; import {UnstructuredStorage} from "../lib//UnstructuredStorage.sol"; -/// @dev A copy of Versioned.sol contract from Lido Core Protocol -/// https://github.com/lidofinance/lido-dao/blob/master/contracts/0.8.9/utils/Versioned.sol +/// @dev A copy of Versioned.sol contract from Lido on Ethereum protocol +/// https://github.com/lidofinance/lido-dao/blob/master/contracts/0.8.9/utils/Versioned.sol contract Versioned { using UnstructuredStorage for bytes32; diff --git a/test/token/ERC20RebasableBridgedPermit.unit.test.ts b/test/token/ERC20RebasableBridgedPermit.unit.test.ts index 92a0d192..66663379 100644 --- a/test/token/ERC20RebasableBridgedPermit.unit.test.ts +++ b/test/token/ERC20RebasableBridgedPermit.unit.test.ts @@ -1105,11 +1105,19 @@ unit("ERC20RebasableBridgedPermit", ctxFactory) .connect(owner) .bridgeBurnShares(holder.address, burnAmount); + const burnTokenAmount = await rebasableProxied.getTokensByShares(burnAmount); + // validate Transfer event was emitted await assert.emits(rebasableProxied, tx, "Transfer", [ holder.address, hre.ethers.constants.AddressZero, - burnAmount, + burnTokenAmount, + ]); + + await assert.emits(rebasableProxied, tx, "TransferShares", [ + holder.address, + hre.ethers.constants.AddressZero, + burnAmount ]); const expectedBalanceAndTotalSupply = premintShares From 695fa42e2ab2b56d494d741832ca8b376a7be22d Mon Sep 17 00:00:00 2001 From: Anton Kovalchuk Date: Thu, 25 Apr 2024 00:44:52 +0400 Subject: [PATCH 092/148] add integration tests for zero and 1wei amount of token --- .editorconfig | 2 +- ...ridging-non-rebasable.integration.test.ts} | 0 .../bridging-rebasable.integration.test.ts | 1440 ++++++++--------- utils/testing/scenario.ts | 2 +- 4 files changed, 656 insertions(+), 788 deletions(-) rename test/optimism/{bridging.integration.test.ts => bridging-non-rebasable.integration.test.ts} (100%) diff --git a/.editorconfig b/.editorconfig index 99a93420..865fbb3b 100644 --- a/.editorconfig +++ b/.editorconfig @@ -10,6 +10,6 @@ charset = utf-8 indent_style = space indent_size = 4 -[*.{js,yml,json,cjs}] +[*.{js,ts,yml,json,cjs}] indent_size = 2 max_line_length = 120 diff --git a/test/optimism/bridging.integration.test.ts b/test/optimism/bridging-non-rebasable.integration.test.ts similarity index 100% rename from test/optimism/bridging.integration.test.ts rename to test/optimism/bridging-non-rebasable.integration.test.ts diff --git a/test/optimism/bridging-rebasable.integration.test.ts b/test/optimism/bridging-rebasable.integration.test.ts index b28ae6a9..69bceca5 100644 --- a/test/optimism/bridging-rebasable.integration.test.ts +++ b/test/optimism/bridging-rebasable.integration.test.ts @@ -1,853 +1,674 @@ import { assert } from "chai"; - +import { ethers } from "hardhat"; +import { BigNumber } from 'ethers' +import { JsonRpcProvider } from "@ethersproject/providers"; import env from "../../utils/env"; import { wei } from "../../utils/wei"; import optimism from "../../utils/optimism"; import testing, { scenario } from "../../utils/testing"; -import { ethers } from "hardhat"; -import { JsonRpcProvider } from "@ethersproject/providers"; import { ERC20WrapperStub } from "../../typechain"; -import { BigNumber } from 'ethers' +import { ScenarioTest } from "../../utils/testing"; + +type ContextType = Awaited>> + +function bridgingTestsSuit(scenarioInstance: ScenarioTest) { + scenarioInstance + .after(async (ctx) => { + await ctx.l1Provider.send("evm_revert", [ctx.snapshot.l1]); + await ctx.l2Provider.send("evm_revert", [ctx.snapshot.l2]); + }) + + .step("Activate bridging on L1", async (ctx) => { + const { l1LidoTokensBridge } = ctx; + const { l1ERC20ExtendedTokensBridgeAdmin } = ctx.accounts; + + const isDepositsEnabled = await l1LidoTokensBridge.isDepositsEnabled(); + + if (!isDepositsEnabled) { + await l1LidoTokensBridge + .connect(l1ERC20ExtendedTokensBridgeAdmin) + .enableDeposits(); + } else { + console.log("L1 deposits already enabled"); + } + + const isWithdrawalsEnabled = + await l1LidoTokensBridge.isWithdrawalsEnabled(); + + if (!isWithdrawalsEnabled) { + await l1LidoTokensBridge + .connect(l1ERC20ExtendedTokensBridgeAdmin) + .enableWithdrawals(); + } else { + console.log("L1 withdrawals already enabled"); + } + + assert.isTrue(await l1LidoTokensBridge.isDepositsEnabled()); + assert.isTrue(await l1LidoTokensBridge.isWithdrawalsEnabled()); + }) + + .step("Activate bridging on L2", async (ctx) => { + const { l2ERC20ExtendedTokensBridge } = ctx; + const { l2ERC20ExtendedTokensBridgeAdmin } = ctx.accounts; + + const isDepositsEnabled = await l2ERC20ExtendedTokensBridge.isDepositsEnabled(); + + if (!isDepositsEnabled) { + await l2ERC20ExtendedTokensBridge + .connect(l2ERC20ExtendedTokensBridgeAdmin) + .enableDeposits(); + } else { + console.log("L2 deposits already enabled"); + } + + const isWithdrawalsEnabled = + await l2ERC20ExtendedTokensBridge.isWithdrawalsEnabled(); + + if (!isWithdrawalsEnabled) { + await l2ERC20ExtendedTokensBridge + .connect(l2ERC20ExtendedTokensBridgeAdmin) + .enableWithdrawals(); + } else { + console.log("L2 withdrawals already enabled"); + } + + assert.isTrue(await l2ERC20ExtendedTokensBridge.isDepositsEnabled()); + assert.isTrue(await l2ERC20ExtendedTokensBridge.isWithdrawalsEnabled()); + }) + + .step("L1 -> L2 deposit via depositERC20() method", async (ctx) => { + const { + l1Token, + l1TokenRebasable, + l1LidoTokensBridge, + l2TokenRebasable, + l1CrossDomainMessenger, + l2ERC20ExtendedTokensBridge, + l1Provider + } = ctx; + const { accountA: tokenHolderA } = ctx.accounts; + const { depositAmountOfRebasableToken, tokenRate } = ctx.constants; + + const depositAmountNonRebasable = nonRebasableFromRebasable(depositAmountOfRebasableToken, tokenRate); + + await l1TokenRebasable + .connect(tokenHolderA.l1Signer) + .approve(l1LidoTokensBridge.address, depositAmountOfRebasableToken); + + const rebasableTokenHolderBalanceBefore = await l1TokenRebasable.balanceOf(tokenHolderA.address); + const nonRebasableTokenBridgeBalanceBefore = await l1Token.balanceOf(l1LidoTokensBridge.address); + const warappedRebasableTokenBalanceBefore = await l1TokenRebasable.balanceOf(l1Token.address); + + const tx = await l1LidoTokensBridge + .connect(tokenHolderA.l1Signer) + .depositERC20( + l1TokenRebasable.address, + l2TokenRebasable.address, + depositAmountOfRebasableToken, + 200_000, + "0x" + ); -scenario("Optimism :: Bridging rebasable token integration test", ctxFactory) - .after(async (ctx) => { - await ctx.l1Provider.send("evm_revert", [ctx.snapshot.l1]); - await ctx.l2Provider.send("evm_revert", [ctx.snapshot.l2]); - }) - - .step("Activate bridging on L1", async (ctx) => { - const { l1LidoTokensBridge } = ctx; - const { l1ERC20ExtendedTokensBridgeAdmin } = ctx.accounts; - - const isDepositsEnabled = await l1LidoTokensBridge.isDepositsEnabled(); - - if (!isDepositsEnabled) { - await l1LidoTokensBridge - .connect(l1ERC20ExtendedTokensBridgeAdmin) - .enableDeposits(); - } else { - console.log("L1 deposits already enabled"); - } - - const isWithdrawalsEnabled = - await l1LidoTokensBridge.isWithdrawalsEnabled(); - - if (!isWithdrawalsEnabled) { - await l1LidoTokensBridge - .connect(l1ERC20ExtendedTokensBridgeAdmin) - .enableWithdrawals(); - } else { - console.log("L1 withdrawals already enabled"); - } - - assert.isTrue(await l1LidoTokensBridge.isDepositsEnabled()); - assert.isTrue(await l1LidoTokensBridge.isWithdrawalsEnabled()); - }) - - .step("Activate bridging on L2", async (ctx) => { - const { l2ERC20ExtendedTokensBridge } = ctx; - const { l2ERC20ExtendedTokensBridgeAdmin } = ctx.accounts; - - const isDepositsEnabled = await l2ERC20ExtendedTokensBridge.isDepositsEnabled(); - - if (!isDepositsEnabled) { - await l2ERC20ExtendedTokensBridge - .connect(l2ERC20ExtendedTokensBridgeAdmin) - .enableDeposits(); - } else { - console.log("L2 deposits already enabled"); - } - - const isWithdrawalsEnabled = - await l2ERC20ExtendedTokensBridge.isWithdrawalsEnabled(); - - if (!isWithdrawalsEnabled) { - await l2ERC20ExtendedTokensBridge - .connect(l2ERC20ExtendedTokensBridgeAdmin) - .enableWithdrawals(); - } else { - console.log("L2 withdrawals already enabled"); - } - - assert.isTrue(await l2ERC20ExtendedTokensBridge.isDepositsEnabled()); - assert.isTrue(await l2ERC20ExtendedTokensBridge.isWithdrawalsEnabled()); - }) - - .step("Set up Token Rate Oracle by pushing first rate", async (ctx) => { + const dataToSend = await packedTokenRateAndTimestamp(l1Provider, l1Token); - const { - l1Token, - l1TokenRebasable, - l2TokenRebasable, - l1LidoTokensBridge, - l2CrossDomainMessenger, - l2ERC20ExtendedTokensBridge, - l2Provider - } = ctx; - - const { accountA: tokenHolderA, l1CrossDomainMessengerAliased } = - ctx.accounts; - const dataToReceive = await packedTokenRateAndTimestamp(l2Provider, l1Token); + await assert.emits(l1LidoTokensBridge, tx, "ERC20DepositInitiated", [ + l1TokenRebasable.address, + l2TokenRebasable.address, + tokenHolderA.address, + tokenHolderA.address, + depositAmountOfRebasableToken, + dataToSend, + ]); - const tx = await l2CrossDomainMessenger - .connect(l1CrossDomainMessengerAliased) - .relayMessage( - 1, - l1LidoTokensBridge.address, - l2ERC20ExtendedTokensBridge.address, - 0, - 300_000, - l2ERC20ExtendedTokensBridge.interface.encodeFunctionData("finalizeDeposit", [ + const l2DepositCalldata = l2ERC20ExtendedTokensBridge.interface.encodeFunctionData( + "finalizeDeposit", + [ l1TokenRebasable.address, l2TokenRebasable.address, tokenHolderA.address, tokenHolderA.address, - 0, - dataToReceive, - ]), - { gasLimit: 5_000_000 } + depositAmountNonRebasable, + dataToSend, + ] ); - }) - .step("L1 -> L2 deposit zero tokens via depositERC20() method", async (ctx) => { - const { - l1Token, - l1TokenRebasable, - l1LidoTokensBridge, - l2TokenRebasable, - l1CrossDomainMessenger, - l2ERC20ExtendedTokensBridge, - l1Provider - } = ctx; + const messageNonce = await l1CrossDomainMessenger.messageNonce(); - const { accountA: tokenHolderA } = ctx.accounts; + await assert.emits(l1CrossDomainMessenger, tx, "SentMessage", [ + l2ERC20ExtendedTokensBridge.address, + l1LidoTokensBridge.address, + l2DepositCalldata, + messageNonce, + 200_000, + ]); - await l1TokenRebasable - .connect(tokenHolderA.l1Signer) - .approve(l1LidoTokensBridge.address, 0); + const rebasableTokenHolderBalanceAfter = await l1TokenRebasable.balanceOf(tokenHolderA.address); + const nonRebasableTokenBridgeBalanceAfter = await l1Token.balanceOf(l1LidoTokensBridge.address); + const warappedRebasableTokenBalanceAfter = await l1TokenRebasable.balanceOf(l1Token.address); - const tokenHolderABalanceBefore = await l1TokenRebasable.balanceOf( - tokenHolderA.address - ); - const l1ERC20ExtendedTokensBridgeBalanceBefore = await l1TokenRebasable.balanceOf( - l1LidoTokensBridge.address - ); + assert.equalBN( + rebasableTokenHolderBalanceAfter, + rebasableTokenHolderBalanceBefore.sub(depositAmountOfRebasableToken) + ); - const tx = await l1LidoTokensBridge - .connect(tokenHolderA.l1Signer) - .depositERC20( - l1TokenRebasable.address, - l2TokenRebasable.address, - 0, - 200_000, - "0x" + const balanceDelta = nonRebasableTokenBridgeBalanceAfter.sub(nonRebasableTokenBridgeBalanceBefore); + const oneTwoWei = BigNumber.from(depositAmountNonRebasable).sub(balanceDelta); + assert.isTrue(oneTwoWei.gte(0) && oneTwoWei.lte(2)); + + assert.equalBN( + warappedRebasableTokenBalanceAfter, + warappedRebasableTokenBalanceBefore.add(depositAmountOfRebasableToken) ); + }) + + .step("Finalize deposit on L2", async (ctx) => { + const { + l1Token, + l1TokenRebasable, + l2TokenRebasable, + l1LidoTokensBridge, + l2CrossDomainMessenger, + l2ERC20ExtendedTokensBridge, + l2Provider + } = ctx; - const dataToSend = await packedTokenRateAndTimestamp(l1Provider, l1Token); + const { depositAmountOfRebasableToken, tokenRate } = ctx.constants; - await assert.emits(l1LidoTokensBridge, tx, "ERC20DepositInitiated", [ - l1TokenRebasable.address, - l2TokenRebasable.address, - tokenHolderA.address, - tokenHolderA.address, - 0, - dataToSend, - ]); + const depositAmountNonRebasable = nonRebasableFromRebasable(depositAmountOfRebasableToken, tokenRate); + const depositAmountRebasable = rebasableFromNonRebasable(depositAmountNonRebasable, tokenRate); - const l2DepositCalldata = l2ERC20ExtendedTokensBridge.interface.encodeFunctionData( - "finalizeDeposit", - [ + const { accountA: tokenHolderA, l1CrossDomainMessengerAliased } = ctx.accounts; + + const tokenHolderABalanceBefore = await l2TokenRebasable.balanceOf(tokenHolderA.address); + const l2TokenRebasableTotalSupplyBefore = await l2TokenRebasable.totalSupply(); + + const dataToReceive = await packedTokenRateAndTimestamp(l2Provider, l1Token); + + const tx = await l2CrossDomainMessenger + .connect(l1CrossDomainMessengerAliased) + .relayMessage( + 1, + l1LidoTokensBridge.address, + l2ERC20ExtendedTokensBridge.address, + 0, + 300_000, + l2ERC20ExtendedTokensBridge.interface.encodeFunctionData("finalizeDeposit", [ + l1TokenRebasable.address, + l2TokenRebasable.address, + tokenHolderA.address, + tokenHolderA.address, + depositAmountNonRebasable, + dataToReceive, + ]), + { gasLimit: 5_000_000 } + ); + + await assert.emits(l2ERC20ExtendedTokensBridge, tx, "DepositFinalized", [ l1TokenRebasable.address, l2TokenRebasable.address, tokenHolderA.address, tokenHolderA.address, - 0, - dataToSend, - ] - ); + depositAmountRebasable, + "0x", + ]); - const messageNonce = await l1CrossDomainMessenger.messageNonce(); + assert.equalBN( + await l2TokenRebasable.balanceOf(tokenHolderA.address), + tokenHolderABalanceBefore.add(depositAmountRebasable) + ); + assert.equalBN( + await l2TokenRebasable.totalSupply(), + l2TokenRebasableTotalSupplyBefore.add(depositAmountRebasable) + ); + }) + + .step("L2 -> L1 withdrawal via withdraw()", async (ctx) => { + const { accountA: tokenHolderA } = ctx.accounts; + const { + l1TokenRebasable, + l2TokenRebasable, + l2ERC20ExtendedTokensBridge + } = ctx; + const { withdrawalAmountOfRebasableToken } = ctx.constants; + + const tokenHolderABalanceBefore = await l2TokenRebasable.balanceOf(tokenHolderA.address); + const l2TotalSupplyBefore = await l2TokenRebasable.totalSupply(); + + const tx = await l2ERC20ExtendedTokensBridge + .connect(tokenHolderA.l2Signer) + .withdraw( + l2TokenRebasable.address, + withdrawalAmountOfRebasableToken, + 0, + "0x" + ); - await assert.emits(l1CrossDomainMessenger, tx, "SentMessage", [ - l2ERC20ExtendedTokensBridge.address, - l1LidoTokensBridge.address, - l2DepositCalldata, - messageNonce, - 200_000, - ]); + await assert.emits(l2ERC20ExtendedTokensBridge, tx, "WithdrawalInitiated", [ + l1TokenRebasable.address, + l2TokenRebasable.address, + tokenHolderA.address, + tokenHolderA.address, + withdrawalAmountOfRebasableToken, + "0x", + ]); + + const tokenHolderABalanceAfter = await l2TokenRebasable.balanceOf(tokenHolderA.address); + const l2TotalSupplyAfter = await l2TokenRebasable.totalSupply() + + assert.isTrue(almostEqual(tokenHolderABalanceAfter, tokenHolderABalanceBefore.sub(withdrawalAmountOfRebasableToken))); + assert.isTrue(almostEqual(l2TotalSupplyAfter, l2TotalSupplyBefore.sub(withdrawalAmountOfRebasableToken))); + }) + + .step("Finalize withdrawal on L1", async (ctx) => { + const { + l1Token, + l1TokenRebasable, + l1CrossDomainMessenger, + l1LidoTokensBridge, + l2CrossDomainMessenger, + l2TokenRebasable, + l2ERC20ExtendedTokensBridge, + } = ctx; + const { accountA: tokenHolderA, l1Stranger } = ctx.accounts; + const { withdrawalAmountOfRebasableToken, tokenRate } = ctx.constants; + + const withdrawalAmountNonRebasable = nonRebasableFromRebasable(withdrawalAmountOfRebasableToken, tokenRate); + const withdrawalAmountRebasable = rebasableFromNonRebasable(withdrawalAmountNonRebasable, tokenRate); + + const tokenHolderABalanceBefore = await l1TokenRebasable.balanceOf(tokenHolderA.address); + const l1LidoTokensBridgeBalanceBefore = await l1Token.balanceOf(l1LidoTokensBridge.address); + + await l1CrossDomainMessenger + .connect(l1Stranger) + .setXDomainMessageSender(l2ERC20ExtendedTokensBridge.address); + + const tx = await l1CrossDomainMessenger + .connect(l1Stranger) + .relayMessage( + l1LidoTokensBridge.address, + l2CrossDomainMessenger.address, + l1LidoTokensBridge.interface.encodeFunctionData( + "finalizeERC20Withdrawal", + [ + l1TokenRebasable.address, + l2TokenRebasable.address, + tokenHolderA.address, + tokenHolderA.address, + withdrawalAmountNonRebasable, + "0x", + ] + ), + 0 + ); + + await assert.emits(l1LidoTokensBridge, tx, "ERC20WithdrawalFinalized", [ + l1TokenRebasable.address, + l2TokenRebasable.address, + tokenHolderA.address, + tokenHolderA.address, + withdrawalAmountRebasable, + "0x", + ]); - assert.equalBN( - await l1Token.balanceOf(l1LidoTokensBridge.address), - l1ERC20ExtendedTokensBridgeBalanceBefore - ); + const l1LidoTokensBridgeBalanceAfter = await l1Token.balanceOf(l1LidoTokensBridge.address); + const tokenHolderABalanceAfter = await l1TokenRebasable.balanceOf(tokenHolderA.address); - assert.equalBN( - await l1TokenRebasable.balanceOf(tokenHolderA.address), - tokenHolderABalanceBefore - ); - }) + assert.equalBN( + l1LidoTokensBridgeBalanceAfter, + l1LidoTokensBridgeBalanceBefore.sub(withdrawalAmountNonRebasable) + ); - .step("Finalize deposit zero tokens on L2", async (ctx) => { - const { - l1Token, - l1TokenRebasable, - l2TokenRebasable, - l1LidoTokensBridge, - l2CrossDomainMessenger, - l2ERC20ExtendedTokensBridge, - l2Provider - } = ctx; + assert.equalBN( + tokenHolderABalanceAfter, + tokenHolderABalanceBefore.add(withdrawalAmountRebasable) + ); + }) - const dataToReceive = await packedTokenRateAndTimestamp(l2Provider, l1Token); + .step("L1 -> L2 deposit via depositERC20To()", async (ctx) => { - const { accountA: tokenHolderA, l1CrossDomainMessengerAliased } = - ctx.accounts; + const { + l1Token, + l1TokenRebasable, + l1LidoTokensBridge, + l2TokenRebasable, + l1CrossDomainMessenger, + l2ERC20ExtendedTokensBridge, + l1Provider + } = ctx; + const { accountA: tokenHolderA, accountB: tokenHolderB } = ctx.accounts; + assert.notEqual(tokenHolderA.address, tokenHolderB.address); - const tokenHolderABalanceBefore = await l2TokenRebasable.balanceOf( - tokenHolderA.address - ); + const { depositAmountOfRebasableToken, tokenRate } = ctx.constants; - const l2TokenRebasableTotalSupplyBefore = await l2TokenRebasable.totalSupply(); + const depositAmountNonRebasable = nonRebasableFromRebasable(depositAmountOfRebasableToken, tokenRate); - const tx = await l2CrossDomainMessenger - .connect(l1CrossDomainMessengerAliased) - .relayMessage( - 1, - l1LidoTokensBridge.address, - l2ERC20ExtendedTokensBridge.address, - 0, - 300_000, - l2ERC20ExtendedTokensBridge.interface.encodeFunctionData("finalizeDeposit", [ + const rebasableTokenHolderBalanceBefore = await l1TokenRebasable.balanceOf(tokenHolderA.address); + const nonRebasableTokenBridgeBalanceBefore = await l1Token.balanceOf(l1LidoTokensBridge.address); + const warappedRebasableTokenBalanceBefore = await l1TokenRebasable.balanceOf(l1Token.address); + + await l1TokenRebasable + .connect(tokenHolderA.l1Signer) + .approve(l1LidoTokensBridge.address, depositAmountOfRebasableToken); + + const tx = await l1LidoTokensBridge + .connect(tokenHolderA.l1Signer) + .depositERC20To( + l1TokenRebasable.address, + l2TokenRebasable.address, + tokenHolderB.address, + depositAmountOfRebasableToken, + 200_000, + "0x" + ); + + const dataToSend = await packedTokenRateAndTimestamp(l1Provider, l1Token); + + await assert.emits(l1LidoTokensBridge, tx, "ERC20DepositInitiated", [ + l1TokenRebasable.address, + l2TokenRebasable.address, + tokenHolderA.address, + tokenHolderB.address, + depositAmountOfRebasableToken, + dataToSend, + ]); + + const l2DepositCalldata = l2ERC20ExtendedTokensBridge.interface.encodeFunctionData( + "finalizeDeposit", + [ l1TokenRebasable.address, l2TokenRebasable.address, tokenHolderA.address, - tokenHolderA.address, - 0, - dataToReceive, - ]), - { gasLimit: 5_000_000 } + tokenHolderB.address, + depositAmountNonRebasable, + dataToSend, + ] ); - await assert.emits(l2ERC20ExtendedTokensBridge, tx, "DepositFinalized", [ - l1TokenRebasable.address, - l2TokenRebasable.address, - tokenHolderA.address, - tokenHolderA.address, - 0, - "0x", - ]); - - assert.equalBN( - await l2TokenRebasable.balanceOf(tokenHolderA.address), - tokenHolderABalanceBefore - ); - assert.equalBN( - await l2TokenRebasable.totalSupply(), - l2TokenRebasableTotalSupplyBefore - ); - }) + const messageNonce = await l1CrossDomainMessenger.messageNonce(); - .step("L1 -> L2 deposit via depositERC20() method", async (ctx) => { - const { - l1Token, - l1TokenRebasable, - l1LidoTokensBridge, - l2TokenRebasable, - l1CrossDomainMessenger, - l2ERC20ExtendedTokensBridge, - l1Provider - } = ctx; - const { accountA: tokenHolderA } = ctx.accounts; - const { depositAmountNonRebasable, depositAmountRebasable } = ctx.common; + await assert.emits(l1CrossDomainMessenger, tx, "SentMessage", [ + l2ERC20ExtendedTokensBridge.address, + l1LidoTokensBridge.address, + l2DepositCalldata, + messageNonce, + 200_000, + ]); - await l1TokenRebasable - .connect(tokenHolderA.l1Signer) - .approve(l1LidoTokensBridge.address, depositAmountRebasable); + const rebasableTokenHolderBalanceAfter = await l1TokenRebasable.balanceOf(tokenHolderA.address); + const nonRebasableTokenBridgeBalanceAfter = await l1Token.balanceOf(l1LidoTokensBridge.address); + const warappedRebasableTokenBalanceAfter = await l1TokenRebasable.balanceOf(l1Token.address); - const tokenHolderABalanceBefore = await l1TokenRebasable.balanceOf( - tokenHolderA.address - ); + assert.equalBN( + rebasableTokenHolderBalanceAfter, + rebasableTokenHolderBalanceBefore.sub(depositAmountOfRebasableToken) + ); - const l1ERC20ExtendedTokensBridgeBalanceBefore = await l1TokenRebasable.balanceOf( - l1LidoTokensBridge.address - ); + const balanceDelta = nonRebasableTokenBridgeBalanceAfter.sub(nonRebasableTokenBridgeBalanceBefore); + const oneTwoWei = BigNumber.from(depositAmountNonRebasable).sub(balanceDelta); + assert.isTrue(oneTwoWei.gte(0) && oneTwoWei.lte(2)); + + assert.equalBN( + warappedRebasableTokenBalanceAfter, + warappedRebasableTokenBalanceBefore.add(depositAmountOfRebasableToken) + ); + }) + + .step("Finalize deposit on L2", async (ctx) => { + const { + l1Token, + l1TokenRebasable, + l1LidoTokensBridge, + l2TokenRebasable, + l2CrossDomainMessenger, + l2ERC20ExtendedTokensBridge, + l2Provider + } = ctx; + + const { + accountA: tokenHolderA, + accountB: tokenHolderB, + l1CrossDomainMessengerAliased, + } = ctx.accounts; + + const { depositAmountOfRebasableToken, tokenRate } = ctx.constants; + + const depositAmountNonRebasable = nonRebasableFromRebasable(depositAmountOfRebasableToken, tokenRate); + const depositAmountRebasable = rebasableFromNonRebasable(depositAmountNonRebasable, tokenRate); + + const dataToReceive = await packedTokenRateAndTimestamp(l2Provider, l1Token); - const tx = await l1LidoTokensBridge - .connect(tokenHolderA.l1Signer) - .depositERC20( + const l2TokenRebasableTotalSupplyBefore = await l2TokenRebasable.totalSupply(); + + const tokenHolderBBalanceBefore = await l2TokenRebasable.balanceOf( + tokenHolderB.address + ); + + const tx = await l2CrossDomainMessenger + .connect(l1CrossDomainMessengerAliased) + .relayMessage( + 1, + l1LidoTokensBridge.address, + l2ERC20ExtendedTokensBridge.address, + 0, + 300_000, + l2ERC20ExtendedTokensBridge.interface.encodeFunctionData("finalizeDeposit", [ + l1TokenRebasable.address, + l2TokenRebasable.address, + tokenHolderA.address, + tokenHolderB.address, + depositAmountNonRebasable, + dataToReceive, + ]), + { gasLimit: 5_000_000 } + ); + + await assert.emits(l2ERC20ExtendedTokensBridge, tx, "DepositFinalized", [ l1TokenRebasable.address, l2TokenRebasable.address, + tokenHolderA.address, + tokenHolderB.address, depositAmountRebasable, - 200_000, - "0x" + "0x", + ]); + + assert.equalBN( + await l2TokenRebasable.balanceOf(tokenHolderB.address), + tokenHolderBBalanceBefore.add(depositAmountRebasable) + ); + + assert.equalBN( + await l2TokenRebasable.totalSupply(), + l2TokenRebasableTotalSupplyBefore.add(depositAmountRebasable) ); + }) - const dataToSend = await packedTokenRateAndTimestamp(l1Provider, l1Token); + .step("L2 -> L1 withdrawal via withdrawTo()", async (ctx) => { + const { l1TokenRebasable, l2TokenRebasable, l2ERC20ExtendedTokensBridge } = ctx; + const { accountA: tokenHolderA, accountB: tokenHolderB } = ctx.accounts; - await assert.emits(l1LidoTokensBridge, tx, "ERC20DepositInitiated", [ - l1TokenRebasable.address, - l2TokenRebasable.address, - tokenHolderA.address, - tokenHolderA.address, - depositAmountRebasable, - dataToSend, - ]); + const { withdrawalAmountOfRebasableToken } = ctx.constants; - const l2DepositCalldata = l2ERC20ExtendedTokensBridge.interface.encodeFunctionData( - "finalizeDeposit", - [ + const tokenHolderBBalanceBefore = await l2TokenRebasable.balanceOf(tokenHolderB.address); + const l2TotalSupplyBefore = await l2TokenRebasable.totalSupply(); + + const tx = await l2ERC20ExtendedTokensBridge + .connect(tokenHolderB.l2Signer) + .withdrawTo( + l2TokenRebasable.address, + tokenHolderA.address, + withdrawalAmountOfRebasableToken, + 0, + "0x" + ); + + await assert.emits(l2ERC20ExtendedTokensBridge, tx, "WithdrawalInitiated", [ l1TokenRebasable.address, l2TokenRebasable.address, + tokenHolderB.address, tokenHolderA.address, + withdrawalAmountOfRebasableToken, + "0x", + ]); + + const tokenHolderABalanceAfter = await l2TokenRebasable.balanceOf(tokenHolderB.address); + const l2TotalSupplyAfter = await l2TokenRebasable.totalSupply() + + assert.isTrue(almostEqual(tokenHolderABalanceAfter, tokenHolderBBalanceBefore.sub(withdrawalAmountOfRebasableToken))); + assert.isTrue(almostEqual(l2TotalSupplyAfter, l2TotalSupplyBefore.sub(withdrawalAmountOfRebasableToken))); + }) + + .step("Finalize withdrawal on L1", async (ctx) => { + const { + l1Token, + l1TokenRebasable, + l1CrossDomainMessenger, + l1LidoTokensBridge, + l2CrossDomainMessenger, + l2TokenRebasable, + l2ERC20ExtendedTokensBridge, + } = ctx; + const { + accountA: tokenHolderA, + accountB: tokenHolderB, + l1Stranger, + } = ctx.accounts; + + const { withdrawalAmountOfRebasableToken, tokenRate } = ctx.constants; + + const withdrawalAmountNonRebasable = nonRebasableFromRebasable(withdrawalAmountOfRebasableToken, tokenRate); + const withdrawalAmountRebasable = rebasableFromNonRebasable(withdrawalAmountNonRebasable, tokenRate); + + const tokenHolderABalanceBefore = await l1TokenRebasable.balanceOf(tokenHolderA.address); + const l1LidoTokensBridgeBalanceBefore = await l1Token.balanceOf(l1LidoTokensBridge.address); + + await l1CrossDomainMessenger + .connect(l1Stranger) + .setXDomainMessageSender(l2ERC20ExtendedTokensBridge.address); + + const tx = await l1CrossDomainMessenger + .connect(l1Stranger) + .relayMessage( + l1LidoTokensBridge.address, + l2CrossDomainMessenger.address, + l1LidoTokensBridge.interface.encodeFunctionData( + "finalizeERC20Withdrawal", + [ + l1TokenRebasable.address, + l2TokenRebasable.address, + tokenHolderB.address, + tokenHolderA.address, + withdrawalAmountNonRebasable, + "0x", + ] + ), + 0 + ); + + await assert.emits(l1LidoTokensBridge, tx, "ERC20WithdrawalFinalized", [ + l1TokenRebasable.address, + l2TokenRebasable.address, + tokenHolderB.address, tokenHolderA.address, - depositAmountNonRebasable, - dataToSend, - ] - ); + withdrawalAmountRebasable, + "0x", + ]); - console.log("tokenHolderA.address=", tokenHolderA.address); - - const messageNonce = await l1CrossDomainMessenger.messageNonce(); - - await assert.emits(l1CrossDomainMessenger, tx, "SentMessage", [ - l2ERC20ExtendedTokensBridge.address, - l1LidoTokensBridge.address, - l2DepositCalldata, - messageNonce, - 200_000, - ]); - - // assert.equalBN( - // await l1Token.balanceOf(l1LidoTokensBridge.address), - // l1ERC20ExtendedTokensBridgeBalanceBefore.add(depositAmountNonRebasable) - // ); - - // assert.equalBN( - // await l1TokenRebasable.balanceOf(tokenHolderA.address), // stETH - // tokenHolderABalanceBefore.sub(depositAmountRebasable) - // ); - }) - - // .step("Finalize deposit on L2", async (ctx) => { - // const { - // l1Token, - // l1TokenRebasable, - // l2TokenRebasable, - // l1LidoTokensBridge, - // l2CrossDomainMessenger, - // l2ERC20ExtendedTokensBridge, - // l2Provider - // } = ctx; - // const { depositAmountNonRebasable, depositAmountRebasable } = ctx.common; - - // const { accountA: tokenHolderA, l1CrossDomainMessengerAliased } = - // ctx.accounts; - - // const tokenHolderABalanceBefore = await l2TokenRebasable.balanceOf( - // tokenHolderA.address - // ); - - // const l2TokenRebasableTotalSupplyBefore = await l2TokenRebasable.totalSupply(); - // const dataToReceive = await packedTokenRateAndTimestamp(l2Provider, l1Token); - - // const tx = await l2CrossDomainMessenger - // .connect(l1CrossDomainMessengerAliased) - // .relayMessage( - // 1, - // l1LidoTokensBridge.address, - // l2ERC20ExtendedTokensBridge.address, - // 0, - // 300_000, - // l2ERC20ExtendedTokensBridge.interface.encodeFunctionData("finalizeDeposit", [ - // l1TokenRebasable.address, - // l2TokenRebasable.address, - // tokenHolderA.address, - // tokenHolderA.address, - // depositAmountNonRebasable, - // dataToReceive, - // ]), - // { gasLimit: 5_000_000 } - // ); - - // await assert.emits(l2ERC20ExtendedTokensBridge, tx, "DepositFinalized", [ - // l1TokenRebasable.address, - // l2TokenRebasable.address, - // tokenHolderA.address, - // tokenHolderA.address, - // depositAmountRebasable, - // "0x", - // ]); - - // assert.equalBN( - // await l2TokenRebasable.balanceOf(tokenHolderA.address), - // tokenHolderABalanceBefore.add(depositAmountRebasable) - // ); - // assert.equalBN( - // await l2TokenRebasable.totalSupply(), - // l2TokenRebasableTotalSupplyBefore.add(depositAmountRebasable) - // ); - // }) - - // .step("L2 -> L1 withdrawal via withdraw()", async (ctx) => { - // const { accountA: tokenHolderA } = ctx.accounts; - // const { withdrawalAmountRebasable } = ctx.common; - // const { - // l1TokenRebasable, - // l2TokenRebasable, - // l2ERC20ExtendedTokensBridge - // } = ctx; - - // const tokenHolderABalanceBefore = await l2TokenRebasable.balanceOf( - // tokenHolderA.address - // ); - // const l2TotalSupplyBefore = await l2TokenRebasable.totalSupply(); - - // const tx = await l2ERC20ExtendedTokensBridge - // .connect(tokenHolderA.l2Signer) - // .withdraw( - // l2TokenRebasable.address, - // withdrawalAmountRebasable, - // 0, - // "0x" - // ); - - // await assert.emits(l2ERC20ExtendedTokensBridge, tx, "WithdrawalInitiated", [ - // l1TokenRebasable.address, - // l2TokenRebasable.address, - // tokenHolderA.address, - // tokenHolderA.address, - // withdrawalAmountRebasable, - // "0x", - // ]); - - // assert.equalBN( - // await l2TokenRebasable.balanceOf(tokenHolderA.address), - // tokenHolderABalanceBefore.sub(withdrawalAmountRebasable) - // ); - // assert.equalBN( - // await l2TokenRebasable.totalSupply(), - // l2TotalSupplyBefore.sub(withdrawalAmountRebasable) - // ); - // }) - - // .step("Finalize withdrawal on L1", async (ctx) => { - // const { - // l1Token, - // l1TokenRebasable, - // l1CrossDomainMessenger, - // l1LidoTokensBridge, - // l2CrossDomainMessenger, - // l2TokenRebasable, - // l2ERC20ExtendedTokensBridge, - // } = ctx; - // const { accountA: tokenHolderA, l1Stranger } = ctx.accounts; - // const { withdrawalAmountNonRebasable, withdrawalAmountRebasable } = ctx.common; - - // const tokenHolderABalanceBefore = await l1TokenRebasable.balanceOf( - // tokenHolderA.address - // ); - // const l1ERC20ExtendedTokensBridgeBalanceBefore = await l1Token.balanceOf( - // l1LidoTokensBridge.address - // ); - - // await l1CrossDomainMessenger - // .connect(l1Stranger) - // .setXDomainMessageSender(l2ERC20ExtendedTokensBridge.address); - - // const tx = await l1CrossDomainMessenger - // .connect(l1Stranger) - // .relayMessage( - // l1LidoTokensBridge.address, - // l2CrossDomainMessenger.address, - // l1LidoTokensBridge.interface.encodeFunctionData( - // "finalizeERC20Withdrawal", - // [ - // l1TokenRebasable.address, - // l2TokenRebasable.address, - // tokenHolderA.address, - // tokenHolderA.address, - // withdrawalAmountNonRebasable, - // "0x", - // ] - // ), - // 0 - // ); - - // await assert.emits(l1LidoTokensBridge, tx, "ERC20WithdrawalFinalized", [ - // l1TokenRebasable.address, - // l2TokenRebasable.address, - // tokenHolderA.address, - // tokenHolderA.address, - // withdrawalAmountRebasable, - // "0x", - // ]); - - // assert.equalBN( - // await l1Token.balanceOf(l1LidoTokensBridge.address), - // l1ERC20ExtendedTokensBridgeBalanceBefore.sub(withdrawalAmountNonRebasable) - // ); - - // assert.equalBN( - // await l1TokenRebasable.balanceOf(tokenHolderA.address), - // tokenHolderABalanceBefore.add(withdrawalAmountRebasable) - // ); - // }) - - - // .step("L1 -> L2 deposit via depositERC20To()", async (ctx) => { - - // const { - // l1Token, - // l1TokenRebasable, - // l1LidoTokensBridge, - // l2TokenRebasable, - // l1CrossDomainMessenger, - // l2ERC20ExtendedTokensBridge, - // l1Provider - // } = ctx; - // const { accountA: tokenHolderA, accountB: tokenHolderB } = ctx.accounts; - // assert.notEqual(tokenHolderA.address, tokenHolderB.address); - - // const { exchangeRate } = ctx.common; - // const depositAmountNonRebasable = wei`0.03 ether`; - // const depositAmountRebasable = wei.toBigNumber(depositAmountNonRebasable).mul(exchangeRate); - - // await l1TokenRebasable - // .connect(tokenHolderA.l1Signer) - // .approve(l1LidoTokensBridge.address, depositAmountRebasable); - - // const tokenHolderABalanceBefore = await l1TokenRebasable.balanceOf( - // tokenHolderA.address - // ); - // const l1ERC20ExtendedTokensBridgeBalanceBefore = await l1Token.balanceOf( - // l1LidoTokensBridge.address - // ); - - // const tx = await l1LidoTokensBridge - // .connect(tokenHolderA.l1Signer) - // .depositERC20To( - // l1TokenRebasable.address, - // l2TokenRebasable.address, - // tokenHolderB.address, - // depositAmountRebasable, - // 200_000, - // "0x" - // ); - - // const dataToSend = await packedTokenRateAndTimestamp(l1Provider, l1Token); - - // await assert.emits(l1LidoTokensBridge, tx, "ERC20DepositInitiated", [ - // l1TokenRebasable.address, - // l2TokenRebasable.address, - // tokenHolderA.address, - // tokenHolderB.address, - // depositAmountRebasable, - // dataToSend, - // ]); - - // const l2DepositCalldata = l2ERC20ExtendedTokensBridge.interface.encodeFunctionData( - // "finalizeDeposit", - // [ - // l1TokenRebasable.address, - // l2TokenRebasable.address, - // tokenHolderA.address, - // tokenHolderB.address, - // depositAmountNonRebasable, - // dataToSend, - // ] - // ); - - // const messageNonce = await l1CrossDomainMessenger.messageNonce(); - - // await assert.emits(l1CrossDomainMessenger, tx, "SentMessage", [ - // l2ERC20ExtendedTokensBridge.address, - // l1LidoTokensBridge.address, - // l2DepositCalldata, - // messageNonce, - // 200_000, - // ]); - - // assert.equalBN( - // await l1Token.balanceOf(l1LidoTokensBridge.address), - // l1ERC20ExtendedTokensBridgeBalanceBefore.add(depositAmountNonRebasable) - // ); - - // assert.equalBN( - // await l1TokenRebasable.balanceOf(tokenHolderA.address), // stETH - // tokenHolderABalanceBefore.sub(depositAmountRebasable) - // ); - // }) - - // .step("Finalize deposit on L2", async (ctx) => { - // const { - // l1Token, - // l1TokenRebasable, - // l1LidoTokensBridge, - // l2TokenRebasable, - // l2CrossDomainMessenger, - // l2ERC20ExtendedTokensBridge, - // l2Provider - // } = ctx; - - // const { - // accountA: tokenHolderA, - // accountB: tokenHolderB, - // l1CrossDomainMessengerAliased, - // } = ctx.accounts; - - // const { exchangeRate } = ctx.common; - - // const depositAmountNonRebasable = wei`0.03 ether`; - // const depositAmountRebasable = wei.toBigNumber(depositAmountNonRebasable).mul(exchangeRate); - - // const dataToReceive = await packedTokenRateAndTimestamp(l2Provider, l1Token); - - // const l2TokenRebasableTotalSupplyBefore = await l2TokenRebasable.totalSupply(); - - // const tokenHolderBBalanceBefore = await l2TokenRebasable.balanceOf( - // tokenHolderB.address - // ); - - // const tx = await l2CrossDomainMessenger - // .connect(l1CrossDomainMessengerAliased) - // .relayMessage( - // 1, - // l1LidoTokensBridge.address, - // l2ERC20ExtendedTokensBridge.address, - // 0, - // 300_000, - // l2ERC20ExtendedTokensBridge.interface.encodeFunctionData("finalizeDeposit", [ - // l1TokenRebasable.address, - // l2TokenRebasable.address, - // tokenHolderA.address, - // tokenHolderB.address, - // depositAmountNonRebasable, - // dataToReceive, - // ]), - // { gasLimit: 5_000_000 } - // ); - - // await assert.emits(l2ERC20ExtendedTokensBridge, tx, "DepositFinalized", [ - // l1TokenRebasable.address, - // l2TokenRebasable.address, - // tokenHolderA.address, - // tokenHolderB.address, - // depositAmountRebasable, - // "0x", - // ]); - - // assert.equalBN( - // await l2TokenRebasable.balanceOf(tokenHolderB.address), - // tokenHolderBBalanceBefore.add(depositAmountRebasable) - // ); - - // assert.equalBN( - // await l2TokenRebasable.totalSupply(), - // l2TokenRebasableTotalSupplyBefore.add(depositAmountRebasable) - // ); - // }) - - // .step("L2 -> L1 withdrawal via withdrawTo()", async (ctx) => { - // const { l1TokenRebasable, l2TokenRebasable, l2ERC20ExtendedTokensBridge } = ctx; - // const { accountA: tokenHolderA, accountB: tokenHolderB } = ctx.accounts; - - // const { exchangeRate } = ctx.common; - // const withdrawalAmountNonRebasable = wei`0.03 ether`; - // const withdrawalAmountRebasable = wei.toBigNumber(withdrawalAmountNonRebasable).mul(exchangeRate); - - // const tokenHolderBBalanceBefore = await l2TokenRebasable.balanceOf( - // tokenHolderB.address - // ); - // const l2TotalSupplyBefore = await l2TokenRebasable.totalSupply(); - - // const tx = await l2ERC20ExtendedTokensBridge - // .connect(tokenHolderB.l2Signer) - // .withdrawTo( - // l2TokenRebasable.address, - // tokenHolderA.address, - // withdrawalAmountRebasable, - // 0, - // "0x" - // ); - - // await assert.emits(l2ERC20ExtendedTokensBridge, tx, "WithdrawalInitiated", [ - // l1TokenRebasable.address, - // l2TokenRebasable.address, - // tokenHolderB.address, - // tokenHolderA.address, - // withdrawalAmountRebasable, - // "0x", - // ]); - - // assert.equalBN( - // await l2TokenRebasable.balanceOf(tokenHolderB.address), - // tokenHolderBBalanceBefore.sub(withdrawalAmountRebasable) - // ); - - // assert.equalBN( - // await l2TokenRebasable.totalSupply(), - // l2TotalSupplyBefore.sub(withdrawalAmountRebasable) - // ); - // }) - - // .step("Finalize withdrawal on L1", async (ctx) => { - // const { - // l1Token, - // l1TokenRebasable, - // l1CrossDomainMessenger, - // l1LidoTokensBridge, - // l2CrossDomainMessenger, - // l2TokenRebasable, - // l2ERC20ExtendedTokensBridge, - // } = ctx; - // const { - // accountA: tokenHolderA, - // accountB: tokenHolderB, - // l1Stranger, - // } = ctx.accounts; - - // const { exchangeRate } = ctx.common; - // const withdrawalAmountNonRebasable = wei`0.03 ether`; - // const withdrawalAmountRebasable = wei.toBigNumber(withdrawalAmountNonRebasable).mul(exchangeRate); - - // const tokenHolderABalanceBefore = await l1TokenRebasable.balanceOf( - // tokenHolderA.address - // ); - // const l1ERC20ExtendedTokensBridgeBalanceBefore = await l1Token.balanceOf( - // l1LidoTokensBridge.address - // ); - - // await l1CrossDomainMessenger - // .connect(l1Stranger) - // .setXDomainMessageSender(l2ERC20ExtendedTokensBridge.address); - - // const tx = await l1CrossDomainMessenger - // .connect(l1Stranger) - // .relayMessage( - // l1LidoTokensBridge.address, - // l2CrossDomainMessenger.address, - // l1LidoTokensBridge.interface.encodeFunctionData( - // "finalizeERC20Withdrawal", - // [ - // l1TokenRebasable.address, - // l2TokenRebasable.address, - // tokenHolderB.address, - // tokenHolderA.address, - // withdrawalAmountNonRebasable, - // "0x", - // ] - // ), - // 0 - // ); - - // await assert.emits(l1LidoTokensBridge, tx, "ERC20WithdrawalFinalized", [ - // l1TokenRebasable.address, - // l2TokenRebasable.address, - // tokenHolderB.address, - // tokenHolderA.address, - // withdrawalAmountRebasable, - // "0x", - // ]); - - // assert.equalBN( - // await l1Token.balanceOf(l1LidoTokensBridge.address), - // l1ERC20ExtendedTokensBridgeBalanceBefore.sub(withdrawalAmountNonRebasable) - // ); - - // assert.equalBN( - // await l1TokenRebasable.balanceOf(tokenHolderA.address), - // tokenHolderABalanceBefore.add(withdrawalAmountRebasable) - // ); - // }) - - .run(); - -async function ctxFactory() { - const networkName = env.network("TESTING_OPT_NETWORK", "mainnet"); - - const { - l1Provider, - l2Provider, - l1ERC20ExtendedTokensBridgeAdmin, - l2ERC20ExtendedTokensBridgeAdmin, - ...contracts - } = await optimism.testing(networkName).getIntegrationTestSetup(); - - const l1Snapshot = await l1Provider.send("evm_snapshot", []); - const l2Snapshot = await l2Provider.send("evm_snapshot", []); - - await optimism.testing(networkName).stubL1CrossChainMessengerContract(); - - const accountA = testing.accounts.accountA(l1Provider, l2Provider); - const accountB = testing.accounts.accountB(l1Provider, l2Provider); - - const exchangeRate = BigNumber.from('1164454276599657236'); - const depositAmountNonRebasable = wei`0.15 ether`; - const depositAmountRebasable = wei.toBigNumber(depositAmountNonRebasable).mul(exchangeRate) - .div(BigNumber.from('1000000000000000000')); + const l1LidoTokensBridgeBalanceAfter = await l1Token.balanceOf(l1LidoTokensBridge.address); + const tokenHolderABalanceAfter = await l1TokenRebasable.balanceOf(tokenHolderA.address); - const withdrawalAmountNonRebasable = wei`0.05 ether`; - const withdrawalAmountRebasable = wei.toBigNumber(withdrawalAmountNonRebasable).mul(exchangeRate) - .div(BigNumber.from('1000000000000000000')); + assert.equalBN( + l1LidoTokensBridgeBalanceAfter, + l1LidoTokensBridgeBalanceBefore.sub(withdrawalAmountNonRebasable) + ); - await testing.setBalance( - await contracts.l1TokensHolder.getAddress(), - wei.toBigNumber(wei`1 ether`), - l1Provider - ); - - await testing.setBalance( - await l1ERC20ExtendedTokensBridgeAdmin.getAddress(), - wei.toBigNumber(wei`1 ether`), - l1Provider - ); - - await testing.setBalance( - await l2ERC20ExtendedTokensBridgeAdmin.getAddress(), - wei.toBigNumber(wei`1 ether`), - l2Provider - ); - - await contracts.l1TokenRebasable - .connect(contracts.l1TokensHolder) - .transfer(accountA.l1Signer.address, depositAmountRebasable); - - const l1CrossDomainMessengerAliased = await testing.impersonate( - testing.accounts.applyL1ToL2Alias(contracts.l1CrossDomainMessenger.address), - l2Provider - ); - - await testing.setBalance( - await l1CrossDomainMessengerAliased.getAddress(), - wei.toBigNumber(wei`1 ether`), - l2Provider - ); - - return { - l1Provider, - l2Provider, - ...contracts, - accounts: { - accountA, - accountB, - l1Stranger: testing.accounts.stranger(l1Provider), + assert.equalBN( + tokenHolderABalanceAfter, + tokenHolderABalanceBefore.add(withdrawalAmountRebasable) + ); + }) + .run(); +} + +function ctxFactory(depositAmountOfRebasableToken: BigNumber, withdrawalAmountOfRebasableToken: BigNumber) { + return async () => { + const networkName = env.network("TESTING_OPT_NETWORK", "mainnet"); + + const { + l1Provider, + l2Provider, l1ERC20ExtendedTokensBridgeAdmin, l2ERC20ExtendedTokensBridgeAdmin, - l1CrossDomainMessengerAliased, - }, - common: { - depositAmountNonRebasable, - depositAmountRebasable, - withdrawalAmountNonRebasable, - withdrawalAmountRebasable, - exchangeRate, - }, - snapshot: { - l1: l1Snapshot, - l2: l2Snapshot, - }, - }; + ...contracts + } = await optimism.testing(networkName).getIntegrationTestSetup(); + + const l1Snapshot = await l1Provider.send("evm_snapshot", []); + const l2Snapshot = await l2Provider.send("evm_snapshot", []); + + await optimism.testing(networkName).stubL1CrossChainMessengerContract(); + + const accountA = testing.accounts.accountA(l1Provider, l2Provider); + const accountB = testing.accounts.accountB(l1Provider, l2Provider); + + const exchangeRate = BigNumber.from('1164454276599657236'); + const depositAmountRebasable = wei.toBigNumber(wei`0.15 ether`); + + await testing.setBalance( + await contracts.l1TokensHolder.getAddress(), + wei.toBigNumber(wei`1 ether`), + l1Provider + ); + + await testing.setBalance( + await l1ERC20ExtendedTokensBridgeAdmin.getAddress(), + wei.toBigNumber(wei`1 ether`), + l1Provider + ); + + await testing.setBalance( + await l2ERC20ExtendedTokensBridgeAdmin.getAddress(), + wei.toBigNumber(wei`1 ether`), + l2Provider + ); + + await contracts.l1TokenRebasable + .connect(contracts.l1TokensHolder) + .transfer(accountA.l1Signer.address, depositAmountRebasable); + + const l1CrossDomainMessengerAliased = await testing.impersonate( + testing.accounts.applyL1ToL2Alias(contracts.l1CrossDomainMessenger.address), + l2Provider + ); + + await testing.setBalance( + await l1CrossDomainMessengerAliased.getAddress(), + wei.toBigNumber(wei`1 ether`), + l2Provider + ); + + return { + l1Provider, + l2Provider, + ...contracts, + accounts: { + accountA, + accountB, + l1Stranger: testing.accounts.stranger(l1Provider), + l1ERC20ExtendedTokensBridgeAdmin, + l2ERC20ExtendedTokensBridgeAdmin, + l1CrossDomainMessengerAliased, + }, + constants: { + depositAmountOfRebasableToken, + withdrawalAmountOfRebasableToken, + tokenRate: exchangeRate + }, + snapshot: { + l1: l1Snapshot, + l2: l2Snapshot, + }, + }; + } } async function packedTokenRateAndTimestamp(l1Provider: JsonRpcProvider, l1Token: ERC20WrapperStub) { @@ -858,3 +679,50 @@ async function packedTokenRateAndTimestamp(l1Provider: JsonRpcProvider, l1Token: const blockTimestampStr = ethers.utils.hexZeroPad(ethers.utils.hexlify(blockTimestamp), 5); return ethers.utils.hexConcat([stEthPerTokenStr, blockTimestampStr]); } + +function nonRebasableFromRebasable(rebasable: BigNumber, exchangeRate: BigNumber) { + return BigNumber.from(rebasable) + .mul(BigNumber.from('1000000000000000000')) + .div(exchangeRate); +} + +function rebasableFromNonRebasable(nonRebasable: BigNumber, exchangeRate: BigNumber) { + return BigNumber.from(nonRebasable) + .mul(exchangeRate) + .div(BigNumber.from('1000000000000000000')); +} + +bridgingTestsSuit( + scenario( + "Optimism :: Bridging X rebasable token integration test ", + ctxFactory( + wei.toBigNumber(wei`0.001 ether`), + wei.toBigNumber(wei`0.001 ether`) + ) + ) +); + +bridgingTestsSuit( + scenario( + "Optimism :: Bridging Zero rebasable token integration test", + ctxFactory( + BigNumber.from('0'), + BigNumber.from('0') + ) + ) +); + +bridgingTestsSuit( + scenario( + "Optimism :: Bridging 1 wei rebasable token integration test", + ctxFactory( + wei.toBigNumber(wei`1 wei`), + wei.toBigNumber(wei`1 wei`) + ) + ) +); + +function almostEqual(num1: BigNumber, num2: BigNumber) { + const delta = (num1.sub(num2)).abs(); + return delta.lte(BigNumber.from('2')); +} diff --git a/utils/testing/scenario.ts b/utils/testing/scenario.ts index 66998a17..af008bdc 100644 --- a/utils/testing/scenario.ts +++ b/utils/testing/scenario.ts @@ -1,6 +1,6 @@ import { CtxFactory, StepTest, CtxFn } from "./types"; -class ScenarioTest { +export class ScenarioTest { private afterFn?: CtxFn; private beforeFn?: CtxFn; From 689a194ae8fea949811d9e6e5d84918614649674 Mon Sep 17 00:00:00 2001 From: Anton Kovalchuk Date: Mon, 29 Apr 2024 12:20:49 +0400 Subject: [PATCH 093/148] fix critical: mint wstETH and wrap to stETH during stETH deposit and unwrap stETH to wstETH and burn during stETH withdrawal --- .../optimism/L2ERC20ExtendedTokensBridge.sol | 22 +++++++++++++------ 1 file changed, 15 insertions(+), 7 deletions(-) diff --git a/contracts/optimism/L2ERC20ExtendedTokensBridge.sol b/contracts/optimism/L2ERC20ExtendedTokensBridge.sol index 55b38bc3..359e81f3 100644 --- a/contracts/optimism/L2ERC20ExtendedTokensBridge.sol +++ b/contracts/optimism/L2ERC20ExtendedTokensBridge.sol @@ -169,10 +169,15 @@ contract L2ERC20ExtendedTokensBridge is uint256 amount_ ) internal returns (uint256) { if(l2Token_ == L2_TOKEN_REBASABLE) { - ERC20RebasableBridged(l2Token_).bridgeMintShares(to_, amount_); - return ERC20RebasableBridged(l2Token_).getTokensByShares(amount_); + IERC20Bridged(L2_TOKEN_NON_REBASABLE).bridgeMint(address(this), amount_); + IERC20(L2_TOKEN_NON_REBASABLE).safeIncreaseAllowance(l2Token_, amount_); + uint256 rebasableTokensAmount; + if (amount_ != 0) { + rebasableTokensAmount = ERC20RebasableBridged(l2Token_).wrap(amount_); + } + ERC20RebasableBridged(l2Token_).transferShares(to_, amount_); + return rebasableTokensAmount; } - IERC20Bridged(l2Token_).bridgeMint(to_, amount_); return amount_; } @@ -188,11 +193,14 @@ contract L2ERC20ExtendedTokensBridge is uint256 amount_ ) internal returns (uint256) { if(l2Token_ == L2_TOKEN_REBASABLE) { - uint256 shares = ERC20RebasableBridged(l2Token_).getSharesByTokens(amount_); - ERC20RebasableBridged(l2Token_).bridgeBurnShares(from_, shares); - return shares; + ERC20RebasableBridged(l2Token_).transferFrom(from_, address(this), amount_); + uint256 nonRebasableTokensAmount; + if (amount_ != 0) { + nonRebasableTokensAmount = ERC20RebasableBridged(l2Token_).unwrap(amount_); + } + IERC20Bridged(L2_TOKEN_NON_REBASABLE).bridgeBurn(address(this), nonRebasableTokensAmount); + return nonRebasableTokensAmount; } - IERC20Bridged(l2Token_).bridgeBurn(from_, amount_); return amount_; } From 737f9691b0d99b84138f991f2eb328701bac114f Mon Sep 17 00:00:00 2001 From: Anton Kovalchuk Date: Mon, 29 Apr 2024 12:27:05 +0400 Subject: [PATCH 094/148] update tests for wrap/unwrap on L2 during deposit/withdrwal --- .../L2ERC20ExtendedTokensBridge.unit.test.ts | 16 ++- .../bridging-rebasable.integration.test.ts | 118 ++++++++++++------ utils/optimism/testing.ts | 9 +- 3 files changed, 96 insertions(+), 47 deletions(-) diff --git a/test/optimism/L2ERC20ExtendedTokensBridge.unit.test.ts b/test/optimism/L2ERC20ExtendedTokensBridge.unit.test.ts index a3a671ed..ba92c686 100644 --- a/test/optimism/L2ERC20ExtendedTokensBridge.unit.test.ts +++ b/test/optimism/L2ERC20ExtendedTokensBridge.unit.test.ts @@ -278,6 +278,8 @@ unit("Optimism:: L2ERC20ExtendedTokensBridge", ctxFactory) const recipientBalanceBefore = await l2TokenRebasable.balanceOf(recipient.address); const totalSupplyBefore = await l2TokenRebasable.totalSupply(); + await l2TokenRebasable.connect(recipient).approve(l2TokenBridge.address, amountToWithdraw); + const tx = await l2TokenBridge.connect(recipient).withdraw( l2TokenRebasable.address, amountToWithdraw, @@ -565,7 +567,7 @@ unit("Optimism:: L2ERC20ExtendedTokensBridge", ctxFactory) }, } = ctx; - const amountToDeposit = wei`1 ether`; + const amountToDeposit = wei`1 ether`; // shares const amountToWithdraw = wei.toBigNumber(amountToDeposit).mul(ctx.exchangeRate).div(ctx.decimalsBN); const l1Gas = wei`1 wei`; const data = "0xdeadbeaf"; @@ -586,6 +588,8 @@ unit("Optimism:: L2ERC20ExtendedTokensBridge", ctxFactory) const deployerBalanceBefore = await l2TokenRebasable.balanceOf(deployer.address); const totalSupplyBefore = await l2TokenRebasable.totalSupply(); + await l2TokenRebasable.approve(l2TokenBridge.address, amountToWithdraw); + const tx = await l2TokenBridge.connect(deployer).withdrawTo( l2TokenRebasable.address, recipient.address, @@ -1015,8 +1019,8 @@ unit("Optimism:: L2ERC20ExtendedTokensBridge", ctxFactory) await l2Messenger.setXDomainMessageSender(l1TokenBridgeEOA.address); - const amountToDeposit = wei`1 ether`; - const amountToEmit = wei.toBigNumber(amountToDeposit).mul(ctx.exchangeRate).div(ctx.decimalsBN); + const amountOfSharesToDeposit = wei`1 ether`; + const amountOfRebasableToken = wei.toBigNumber(amountOfSharesToDeposit).mul(ctx.exchangeRate).div(ctx.decimalsBN); const data = "0xdeadbeaf"; const provider = await hre.ethers.provider; const packedTokenRateAndTimestampData = await packedTokenRateAndTimestamp(provider, ctx.exchangeRate); @@ -1029,7 +1033,7 @@ unit("Optimism:: L2ERC20ExtendedTokensBridge", ctxFactory) l2TokenRebasable.address, deployer.address, recipient.address, - amountToDeposit, + amountOfSharesToDeposit, dataToReceive ); @@ -1038,11 +1042,11 @@ unit("Optimism:: L2ERC20ExtendedTokensBridge", ctxFactory) l2TokenRebasable.address, deployer.address, recipient.address, - amountToEmit, + amountOfRebasableToken, data, ]); - assert.equalBN(await l2TokenRebasable.balanceOf(recipient.address), amountToEmit); + assert.equalBN(await l2TokenRebasable.balanceOf(recipient.address), amountOfRebasableToken); }) .run(); diff --git a/test/optimism/bridging-rebasable.integration.test.ts b/test/optimism/bridging-rebasable.integration.test.ts index 69bceca5..b03b7056 100644 --- a/test/optimism/bridging-rebasable.integration.test.ts +++ b/test/optimism/bridging-rebasable.integration.test.ts @@ -96,6 +96,9 @@ function bridgingTestsSuit(scenarioInstance: ScenarioTest) { .approve(l1LidoTokensBridge.address, depositAmountOfRebasableToken); const rebasableTokenHolderBalanceBefore = await l1TokenRebasable.balanceOf(tokenHolderA.address); + + ctx.balances.accountABalanceBeforeDeposit = rebasableTokenHolderBalanceBefore; + const nonRebasableTokenBridgeBalanceBefore = await l1Token.balanceOf(l1LidoTokensBridge.address); const warappedRebasableTokenBalanceBefore = await l1TokenRebasable.balanceOf(l1Token.address); @@ -144,20 +147,21 @@ function bridgingTestsSuit(scenarioInstance: ScenarioTest) { const rebasableTokenHolderBalanceAfter = await l1TokenRebasable.balanceOf(tokenHolderA.address); const nonRebasableTokenBridgeBalanceAfter = await l1Token.balanceOf(l1LidoTokensBridge.address); - const warappedRebasableTokenBalanceAfter = await l1TokenRebasable.balanceOf(l1Token.address); - + const wrappedRebasableTokenBalanceAfter = await l1TokenRebasable.balanceOf(l1Token.address); assert.equalBN( rebasableTokenHolderBalanceAfter, rebasableTokenHolderBalanceBefore.sub(depositAmountOfRebasableToken) ); - const balanceDelta = nonRebasableTokenBridgeBalanceAfter.sub(nonRebasableTokenBridgeBalanceBefore); - const oneTwoWei = BigNumber.from(depositAmountNonRebasable).sub(balanceDelta); - assert.isTrue(oneTwoWei.gte(0) && oneTwoWei.lte(2)); + // during wrapping 1-2 wei can be lost + assert.isTrue(almostEqual( + depositAmountNonRebasable, + nonRebasableTokenBridgeBalanceAfter.sub(nonRebasableTokenBridgeBalanceBefore)) + ); assert.equalBN( - warappedRebasableTokenBalanceAfter, + wrappedRebasableTokenBalanceAfter, warappedRebasableTokenBalanceBefore.add(depositAmountOfRebasableToken) ); }) @@ -213,13 +217,16 @@ function bridgingTestsSuit(scenarioInstance: ScenarioTest) { "0x", ]); + const tokenHolderABalanceAfter = await l2TokenRebasable.balanceOf(tokenHolderA.address); + const l2TokenRebasableTotalSupplyAfter = await l2TokenRebasable.totalSupply(); + assert.equalBN( - await l2TokenRebasable.balanceOf(tokenHolderA.address), - tokenHolderABalanceBefore.add(depositAmountRebasable) + tokenHolderABalanceBefore.add(depositAmountRebasable), + tokenHolderABalanceAfter ); assert.equalBN( - await l2TokenRebasable.totalSupply(), - l2TokenRebasableTotalSupplyBefore.add(depositAmountRebasable) + l2TokenRebasableTotalSupplyBefore.add(depositAmountRebasable), + l2TokenRebasableTotalSupplyAfter ); }) @@ -235,6 +242,10 @@ function bridgingTestsSuit(scenarioInstance: ScenarioTest) { const tokenHolderABalanceBefore = await l2TokenRebasable.balanceOf(tokenHolderA.address); const l2TotalSupplyBefore = await l2TokenRebasable.totalSupply(); + await l2TokenRebasable + .connect(tokenHolderA.l2Signer) + .approve(l2ERC20ExtendedTokensBridge.address, withdrawalAmountOfRebasableToken); + const tx = await l2ERC20ExtendedTokensBridge .connect(tokenHolderA.l2Signer) .withdraw( @@ -256,6 +267,7 @@ function bridgingTestsSuit(scenarioInstance: ScenarioTest) { const tokenHolderABalanceAfter = await l2TokenRebasable.balanceOf(tokenHolderA.address); const l2TotalSupplyAfter = await l2TokenRebasable.totalSupply() + // during unwrapping 1-2 wei can be lost assert.isTrue(almostEqual(tokenHolderABalanceAfter, tokenHolderABalanceBefore.sub(withdrawalAmountOfRebasableToken))); assert.isTrue(almostEqual(l2TotalSupplyAfter, l2TotalSupplyBefore.sub(withdrawalAmountOfRebasableToken))); }) @@ -271,7 +283,7 @@ function bridgingTestsSuit(scenarioInstance: ScenarioTest) { l2ERC20ExtendedTokensBridge, } = ctx; const { accountA: tokenHolderA, l1Stranger } = ctx.accounts; - const { withdrawalAmountOfRebasableToken, tokenRate } = ctx.constants; + const { depositAmountOfRebasableToken, withdrawalAmountOfRebasableToken, tokenRate } = ctx.constants; const withdrawalAmountNonRebasable = nonRebasableFromRebasable(withdrawalAmountOfRebasableToken, tokenRate); const withdrawalAmountRebasable = rebasableFromNonRebasable(withdrawalAmountNonRebasable, tokenRate); @@ -323,6 +335,13 @@ function bridgingTestsSuit(scenarioInstance: ScenarioTest) { tokenHolderABalanceAfter, tokenHolderABalanceBefore.add(withdrawalAmountRebasable) ); + + /// check that user balance is correct after depositing and withdrawal. + const deltaDepositWithdrawal = depositAmountOfRebasableToken.sub(withdrawalAmountOfRebasableToken); + assert.isTrue(almostEqual( + ctx.balances.accountABalanceBeforeDeposit, + tokenHolderABalanceAfter.add(deltaDepositWithdrawal)) + ); }) .step("L1 -> L2 deposit via depositERC20To()", async (ctx) => { @@ -340,13 +359,16 @@ function bridgingTestsSuit(scenarioInstance: ScenarioTest) { assert.notEqual(tokenHolderA.address, tokenHolderB.address); const { depositAmountOfRebasableToken, tokenRate } = ctx.constants; - const depositAmountNonRebasable = nonRebasableFromRebasable(depositAmountOfRebasableToken, tokenRate); - const rebasableTokenHolderBalanceBefore = await l1TokenRebasable.balanceOf(tokenHolderA.address); + const rebasableTokenHolderABalanceBefore = await l1TokenRebasable.balanceOf(tokenHolderA.address); const nonRebasableTokenBridgeBalanceBefore = await l1Token.balanceOf(l1LidoTokensBridge.address); const warappedRebasableTokenBalanceBefore = await l1TokenRebasable.balanceOf(l1Token.address); + // save to check balance later + ctx.balances.accountABalanceBeforeDeposit = rebasableTokenHolderABalanceBefore; + ctx.balances.accountBBalanceBeforeDeposit = await l2TokenRebasable.balanceOf(tokenHolderB.address); + await l1TokenRebasable .connect(tokenHolderA.l1Signer) .approve(l1LidoTokensBridge.address, depositAmountOfRebasableToken); @@ -395,18 +417,20 @@ function bridgingTestsSuit(scenarioInstance: ScenarioTest) { 200_000, ]); - const rebasableTokenHolderBalanceAfter = await l1TokenRebasable.balanceOf(tokenHolderA.address); + const rebasableTokenHolderABalanceAfter = await l1TokenRebasable.balanceOf(tokenHolderA.address); const nonRebasableTokenBridgeBalanceAfter = await l1Token.balanceOf(l1LidoTokensBridge.address); const warappedRebasableTokenBalanceAfter = await l1TokenRebasable.balanceOf(l1Token.address); assert.equalBN( - rebasableTokenHolderBalanceAfter, - rebasableTokenHolderBalanceBefore.sub(depositAmountOfRebasableToken) + rebasableTokenHolderABalanceAfter, + rebasableTokenHolderABalanceBefore.sub(depositAmountOfRebasableToken) ); - const balanceDelta = nonRebasableTokenBridgeBalanceAfter.sub(nonRebasableTokenBridgeBalanceBefore); - const oneTwoWei = BigNumber.from(depositAmountNonRebasable).sub(balanceDelta); - assert.isTrue(oneTwoWei.gte(0) && oneTwoWei.lte(2)); + // during wrapping 1-2 wei can be lost + assert.isTrue(almostEqual( + depositAmountNonRebasable, + nonRebasableTokenBridgeBalanceAfter.sub(nonRebasableTokenBridgeBalanceBefore)) + ); assert.equalBN( warappedRebasableTokenBalanceAfter, @@ -439,10 +463,7 @@ function bridgingTestsSuit(scenarioInstance: ScenarioTest) { const dataToReceive = await packedTokenRateAndTimestamp(l2Provider, l1Token); const l2TokenRebasableTotalSupplyBefore = await l2TokenRebasable.totalSupply(); - - const tokenHolderBBalanceBefore = await l2TokenRebasable.balanceOf( - tokenHolderB.address - ); + const tokenHolderBBalanceBefore = await l2TokenRebasable.balanceOf(tokenHolderB.address); const tx = await l2CrossDomainMessenger .connect(l1CrossDomainMessengerAliased) @@ -492,6 +513,10 @@ function bridgingTestsSuit(scenarioInstance: ScenarioTest) { const tokenHolderBBalanceBefore = await l2TokenRebasable.balanceOf(tokenHolderB.address); const l2TotalSupplyBefore = await l2TokenRebasable.totalSupply(); + await l2TokenRebasable + .connect(tokenHolderB.l2Signer) + .approve(l2ERC20ExtendedTokensBridge.address, withdrawalAmountOfRebasableToken); + const tx = await l2ERC20ExtendedTokensBridge .connect(tokenHolderB.l2Signer) .withdrawTo( @@ -534,7 +559,7 @@ function bridgingTestsSuit(scenarioInstance: ScenarioTest) { l1Stranger, } = ctx.accounts; - const { withdrawalAmountOfRebasableToken, tokenRate } = ctx.constants; + const { depositAmountOfRebasableToken, withdrawalAmountOfRebasableToken, tokenRate } = ctx.constants; const withdrawalAmountNonRebasable = nonRebasableFromRebasable(withdrawalAmountOfRebasableToken, tokenRate); const withdrawalAmountRebasable = rebasableFromNonRebasable(withdrawalAmountNonRebasable, tokenRate); @@ -576,6 +601,7 @@ function bridgingTestsSuit(scenarioInstance: ScenarioTest) { const l1LidoTokensBridgeBalanceAfter = await l1Token.balanceOf(l1LidoTokensBridge.address); const tokenHolderABalanceAfter = await l1TokenRebasable.balanceOf(tokenHolderA.address); + const tokenHolderBBalanceAfter = await l2TokenRebasable.balanceOf(tokenHolderB.address); assert.equalBN( l1LidoTokensBridgeBalanceAfter, @@ -586,13 +612,26 @@ function bridgingTestsSuit(scenarioInstance: ScenarioTest) { tokenHolderABalanceAfter, tokenHolderABalanceBefore.add(withdrawalAmountRebasable) ); + + /// check that user balance is correct after depositing and withdrawal. + const deltaDepositWithdrawal = depositAmountOfRebasableToken.sub(withdrawalAmountOfRebasableToken); + assert.isTrue(almostEqual( + ctx.balances.accountABalanceBeforeDeposit, + tokenHolderABalanceAfter.add(deltaDepositWithdrawal)) + ); + assert.isTrue(almostEqual( + ctx.balances.accountBBalanceBeforeDeposit, + tokenHolderBBalanceAfter.sub(deltaDepositWithdrawal)) + ); }) + .run(); } function ctxFactory(depositAmountOfRebasableToken: BigNumber, withdrawalAmountOfRebasableToken: BigNumber) { return async () => { const networkName = env.network("TESTING_OPT_NETWORK", "mainnet"); + const exchangeRate = BigNumber.from('1164454276599657236'); const { l1Provider, @@ -600,7 +639,7 @@ function ctxFactory(depositAmountOfRebasableToken: BigNumber, withdrawalAmountOf l1ERC20ExtendedTokensBridgeAdmin, l2ERC20ExtendedTokensBridgeAdmin, ...contracts - } = await optimism.testing(networkName).getIntegrationTestSetup(); + } = await optimism.testing(networkName).getIntegrationTestSetup(exchangeRate); const l1Snapshot = await l1Provider.send("evm_snapshot", []); const l2Snapshot = await l2Provider.send("evm_snapshot", []); @@ -610,8 +649,6 @@ function ctxFactory(depositAmountOfRebasableToken: BigNumber, withdrawalAmountOf const accountA = testing.accounts.accountA(l1Provider, l2Provider); const accountB = testing.accounts.accountB(l1Provider, l2Provider); - const exchangeRate = BigNumber.from('1164454276599657236'); - const depositAmountRebasable = wei.toBigNumber(wei`0.15 ether`); await testing.setBalance( await contracts.l1TokensHolder.getAddress(), @@ -631,10 +668,6 @@ function ctxFactory(depositAmountOfRebasableToken: BigNumber, withdrawalAmountOf l2Provider ); - await contracts.l1TokenRebasable - .connect(contracts.l1TokensHolder) - .transfer(accountA.l1Signer.address, depositAmountRebasable); - const l1CrossDomainMessengerAliased = await testing.impersonate( testing.accounts.applyL1ToL2Alias(contracts.l1CrossDomainMessenger.address), l2Provider @@ -646,6 +679,13 @@ function ctxFactory(depositAmountOfRebasableToken: BigNumber, withdrawalAmountOf l2Provider ); + await contracts.l1TokenRebasable + .connect(contracts.l1TokensHolder) + .transfer(accountA.l1Signer.address, depositAmountOfRebasableToken.mul(2)); + + var accountABalanceBeforeDeposit = BigNumber.from(0); + var accountBBalanceBeforeDeposit = BigNumber.from(0); + return { l1Provider, l2Provider, @@ -663,6 +703,10 @@ function ctxFactory(depositAmountOfRebasableToken: BigNumber, withdrawalAmountOf withdrawalAmountOfRebasableToken, tokenRate: exchangeRate }, + balances: { + accountABalanceBeforeDeposit, + accountBBalanceBeforeDeposit + }, snapshot: { l1: l1Snapshot, l2: l2Snapshot, @@ -704,20 +748,20 @@ bridgingTestsSuit( bridgingTestsSuit( scenario( - "Optimism :: Bridging Zero rebasable token integration test", + "Optimism :: Bridging 1 wei rebasable token integration test", ctxFactory( - BigNumber.from('0'), - BigNumber.from('0') + wei.toBigNumber(wei`1 wei`), + wei.toBigNumber(wei`1 wei`) ) ) ); bridgingTestsSuit( scenario( - "Optimism :: Bridging 1 wei rebasable token integration test", + "Optimism :: Bridging Zero rebasable token integration test", ctxFactory( - wei.toBigNumber(wei`1 wei`), - wei.toBigNumber(wei`1 wei`) + BigNumber.from('0'), + BigNumber.from('0') ) ) ); diff --git a/utils/optimism/testing.ts b/utils/optimism/testing.ts index 618ad76c..2aa2f18d 100644 --- a/utils/optimism/testing.ts +++ b/utils/optimism/testing.ts @@ -46,7 +46,7 @@ export default function testing(networkName: NetworkName) { ...bridgeContracts, }; }, - async getIntegrationTestSetup() { + async getIntegrationTestSetup(tokenRate: BigNumber) { const hasDeployedContracts = testingUtils.env.USE_DEPLOYED_CONTRACTS(false); @@ -56,7 +56,7 @@ export default function testing(networkName: NetworkName) { const bridgeContracts = hasDeployedContracts ? await loadDeployedBridges(ethProvider, optProvider) - : await deployTestBridge(networkName, ethProvider, optProvider); + : await deployTestBridge(networkName, tokenRate, ethProvider, optProvider); const [l1ERC20ExtendedTokensAdminAddress] = await BridgingManagement.getAdmins(bridgeContracts.l1LidoTokensBridge); @@ -180,6 +180,7 @@ async function loadDeployedBridges( async function deployTestBridge( networkName: NetworkName, + tokenRate: BigNumber, ethProvider: JsonRpcProvider, optProvider: JsonRpcProvider ) { @@ -195,7 +196,7 @@ async function deployTestBridge( l1TokenRebasable.address, "Test Token", "TT", - BigNumber.from('1164454276599657236') + tokenRate ); const [ethDeployScript, optDeployScript] = await deploymentAll( @@ -213,7 +214,7 @@ async function deployTestBridge( admins: { proxy: optDeployer.address, bridge: optDeployer.address }, contractsShift: 0, tokenRateOracle: { - tokenRate: BigNumber.from('1164454276599657236'), + tokenRate: tokenRate, l1Timestamp: BigNumber.from('1000') } } From ad309de2779bafc3607c752ea7020b8b7c8ac008 Mon Sep 17 00:00:00 2001 From: Anton Kovalchuk Date: Mon, 29 Apr 2024 15:26:12 +0400 Subject: [PATCH 095/148] refactor and add new integration tests for non rebasable token --- test/optimism/_launch.test.ts | 3 +- ...bridging-non-rebasable.integration.test.ts | 1160 +++++++++-------- test/optimism/deposit-gas-estimation.test.ts | 4 +- test/token/ERC20Permit.unit.test.ts | 6 - 4 files changed, 618 insertions(+), 555 deletions(-) diff --git a/test/optimism/_launch.test.ts b/test/optimism/_launch.test.ts index 41040dd2..e1f6f80e 100644 --- a/test/optimism/_launch.test.ts +++ b/test/optimism/_launch.test.ts @@ -6,6 +6,7 @@ import optimism from "../../utils/optimism"; import testing, { scenario } from "../../utils/testing"; import { BridgingManagerRole } from "../../utils/bridging-management"; import { L1LidoTokensBridge__factory } from "../../typechain"; +import { BigNumber } from 'ethers' const REVERT = env.bool("REVERT", true); @@ -57,7 +58,7 @@ async function ctxFactory() { const { l1Provider, l2Provider, l1LidoTokensBridge } = await optimism .testing(networkName) - .getIntegrationTestSetup(); + .getIntegrationTestSetup(BigNumber.from('1164454276599657236')); const hasDeployedContracts = testing.env.USE_DEPLOYED_CONTRACTS(false); const l1DevMultisig = hasDeployedContracts diff --git a/test/optimism/bridging-non-rebasable.integration.test.ts b/test/optimism/bridging-non-rebasable.integration.test.ts index e640718d..18730eca 100644 --- a/test/optimism/bridging-non-rebasable.integration.test.ts +++ b/test/optimism/bridging-non-rebasable.integration.test.ts @@ -1,625 +1,661 @@ import { assert } from "chai"; - +import { ethers } from "hardhat"; +import { BigNumber } from 'ethers' import env from "../../utils/env"; import { wei } from "../../utils/wei"; import optimism from "../../utils/optimism"; import testing, { scenario } from "../../utils/testing"; -import { ethers } from "hardhat"; -import { JsonRpcProvider } from "@ethersproject/providers"; +import { ScenarioTest } from "../../utils/testing"; import { ERC20WrapperStub } from "../../typechain"; +import { JsonRpcProvider } from "@ethersproject/providers"; -scenario("Optimism :: Bridging non-rebasable token integration test", ctxFactory) - .after(async (ctx) => { - await ctx.l1Provider.send("evm_revert", [ctx.snapshot.l1]); - await ctx.l2Provider.send("evm_revert", [ctx.snapshot.l2]); - }) - - .step("Activate bridging on L1", async (ctx) => { - const { l1LidoTokensBridge } = ctx; - const { l1ERC20ExtendedTokensBridgeAdmin } = ctx.accounts; - - const isDepositsEnabled = await l1LidoTokensBridge.isDepositsEnabled(); - - if (!isDepositsEnabled) { - await l1LidoTokensBridge - .connect(l1ERC20ExtendedTokensBridgeAdmin) - .enableDeposits(); - } else { - console.log("L1 deposits already enabled"); - } - - const isWithdrawalsEnabled = - await l1LidoTokensBridge.isWithdrawalsEnabled(); - - if (!isWithdrawalsEnabled) { - await l1LidoTokensBridge - .connect(l1ERC20ExtendedTokensBridgeAdmin) - .enableWithdrawals(); - } else { - console.log("L1 withdrawals already enabled"); - } - - assert.isTrue(await l1LidoTokensBridge.isDepositsEnabled()); - assert.isTrue(await l1LidoTokensBridge.isWithdrawalsEnabled()); - }) - - .step("Activate bridging on L2", async (ctx) => { - const { l2ERC20ExtendedTokensBridge } = ctx; - const { l2ERC20ExtendedTokensBridgeAdmin } = ctx.accounts; - - const isDepositsEnabled = await l2ERC20ExtendedTokensBridge.isDepositsEnabled(); - - if (!isDepositsEnabled) { - await l2ERC20ExtendedTokensBridge - .connect(l2ERC20ExtendedTokensBridgeAdmin) - .enableDeposits(); - } else { - console.log("L2 deposits already enabled"); - } - - const isWithdrawalsEnabled = - await l2ERC20ExtendedTokensBridge.isWithdrawalsEnabled(); - - if (!isWithdrawalsEnabled) { - await l2ERC20ExtendedTokensBridge - .connect(l2ERC20ExtendedTokensBridgeAdmin) - .enableWithdrawals(); - } else { - console.log("L2 withdrawals already enabled"); - } - - assert.isTrue(await l2ERC20ExtendedTokensBridge.isDepositsEnabled()); - assert.isTrue(await l2ERC20ExtendedTokensBridge.isWithdrawalsEnabled()); - }) - - .step("L1 -> L2 deposit via depositERC20() method", async (ctx) => { - const { - l1Token, - l1LidoTokensBridge, - l2Token, - l1CrossDomainMessenger, - l2ERC20ExtendedTokensBridge, - } = ctx; - const { accountA: tokenHolderA } = ctx.accounts; - const { depositAmount } = ctx.common; - - await l1Token - .connect(tokenHolderA.l1Signer) - .approve(l1LidoTokensBridge.address, depositAmount); - - const tokenHolderABalanceBefore = await l1Token.balanceOf( - tokenHolderA.address - ); - const l1ERC20ExtendedTokensBridgeBalanceBefore = await l1Token.balanceOf( - l1LidoTokensBridge.address - ); - - const tx = await l1LidoTokensBridge - .connect(tokenHolderA.l1Signer) - .depositERC20( - l1Token.address, - l2Token.address, - depositAmount, - 200_000, - "0x" - ); - - const dataToSend = await packedTokenRateAndTimestamp(ctx.l1Provider, l1Token); +type ContextType = Awaited>> + +function bridgingTestsSuit(scenarioInstance: ScenarioTest) { + scenarioInstance + .after(async (ctx) => { + await ctx.l1Provider.send("evm_revert", [ctx.snapshot.l1]); + await ctx.l2Provider.send("evm_revert", [ctx.snapshot.l2]); + }) + + .step("Activate bridging on L1", async (ctx) => { + const { l1LidoTokensBridge } = ctx; + const { l1ERC20ExtendedTokensBridgeAdmin } = ctx.accounts; + + const isDepositsEnabled = await l1LidoTokensBridge.isDepositsEnabled(); + + if (!isDepositsEnabled) { + await l1LidoTokensBridge + .connect(l1ERC20ExtendedTokensBridgeAdmin) + .enableDeposits(); + } else { + console.log("L1 deposits already enabled"); + } + + const isWithdrawalsEnabled = + await l1LidoTokensBridge.isWithdrawalsEnabled(); + + if (!isWithdrawalsEnabled) { + await l1LidoTokensBridge + .connect(l1ERC20ExtendedTokensBridgeAdmin) + .enableWithdrawals(); + } else { + console.log("L1 withdrawals already enabled"); + } + + assert.isTrue(await l1LidoTokensBridge.isDepositsEnabled()); + assert.isTrue(await l1LidoTokensBridge.isWithdrawalsEnabled()); + }) + + .step("Activate bridging on L2", async (ctx) => { + const { l2ERC20ExtendedTokensBridge } = ctx; + const { l2ERC20ExtendedTokensBridgeAdmin } = ctx.accounts; + + const isDepositsEnabled = await l2ERC20ExtendedTokensBridge.isDepositsEnabled(); + + if (!isDepositsEnabled) { + await l2ERC20ExtendedTokensBridge + .connect(l2ERC20ExtendedTokensBridgeAdmin) + .enableDeposits(); + } else { + console.log("L2 deposits already enabled"); + } + + const isWithdrawalsEnabled = + await l2ERC20ExtendedTokensBridge.isWithdrawalsEnabled(); + + if (!isWithdrawalsEnabled) { + await l2ERC20ExtendedTokensBridge + .connect(l2ERC20ExtendedTokensBridgeAdmin) + .enableWithdrawals(); + } else { + console.log("L2 withdrawals already enabled"); + } + + assert.isTrue(await l2ERC20ExtendedTokensBridge.isDepositsEnabled()); + assert.isTrue(await l2ERC20ExtendedTokensBridge.isWithdrawalsEnabled()); + }) + + .step("L1 -> L2 deposit via depositERC20() method", async (ctx) => { + const { + l1Token, + l1LidoTokensBridge, + l2Token, + l1CrossDomainMessenger, + l2ERC20ExtendedTokensBridge, + } = ctx; + const { accountA: tokenHolderA } = ctx.accounts; + const { depositAmount } = ctx.constants; + + await l1Token + .connect(tokenHolderA.l1Signer) + .approve(l1LidoTokensBridge.address, depositAmount); + + const tokenHolderABalanceBefore = await l1Token.balanceOf(tokenHolderA.address); + const l1ERC20ExtendedTokensBridgeBalanceBefore = await l1Token.balanceOf(l1LidoTokensBridge.address); + + ctx.balances.accountABalanceBeforeDeposit = tokenHolderABalanceBefore; + + const tx = await l1LidoTokensBridge + .connect(tokenHolderA.l1Signer) + .depositERC20( + l1Token.address, + l2Token.address, + depositAmount, + 200_000, + "0x" + ); - await assert.emits(l1LidoTokensBridge, tx, "ERC20DepositInitiated", [ - l1Token.address, - l2Token.address, - tokenHolderA.address, - tokenHolderA.address, - depositAmount, - dataToSend, - ]); + const dataToSend = await packedTokenRateAndTimestamp(ctx.l1Provider, l1Token); - const l2DepositCalldata = l2ERC20ExtendedTokensBridge.interface.encodeFunctionData( - "finalizeDeposit", - [ + await assert.emits(l1LidoTokensBridge, tx, "ERC20DepositInitiated", [ l1Token.address, l2Token.address, tokenHolderA.address, tokenHolderA.address, depositAmount, dataToSend, - ] - ); - - const messageNonce = await l1CrossDomainMessenger.messageNonce(); + ]); - await assert.emits(l1CrossDomainMessenger, tx, "SentMessage", [ - l2ERC20ExtendedTokensBridge.address, - l1LidoTokensBridge.address, - l2DepositCalldata, - messageNonce, - 200_000, - ]); - - assert.equalBN( - await l1Token.balanceOf(l1LidoTokensBridge.address), - l1ERC20ExtendedTokensBridgeBalanceBefore.add(depositAmount) - ); - - assert.equalBN( - await l1Token.balanceOf(tokenHolderA.address), - tokenHolderABalanceBefore.sub(depositAmount) - ); - }) - - .step("Finalize deposit on L2", async (ctx) => { - const { - l1Token, - l2Token, - l1LidoTokensBridge, - l2CrossDomainMessenger, - l2ERC20ExtendedTokensBridge, - } = ctx; - const { depositAmount } = ctx.common; - const { accountA: tokenHolderA, l1CrossDomainMessengerAliased } = - ctx.accounts; - - const tokenHolderABalanceBefore = await l2Token.balanceOf( - tokenHolderA.address - ); - const l2TokenTotalSupplyBefore = await l2Token.totalSupply(); - const dataToReceive = await packedTokenRateAndTimestamp(ctx.l2Provider, l1Token); - - const tx = await l2CrossDomainMessenger - .connect(l1CrossDomainMessengerAliased) - .relayMessage( - 1, - l1LidoTokensBridge.address, - l2ERC20ExtendedTokensBridge.address, - 0, - 300_000, - l2ERC20ExtendedTokensBridge.interface.encodeFunctionData("finalizeDeposit", [ + const l2DepositCalldata = l2ERC20ExtendedTokensBridge.interface.encodeFunctionData( + "finalizeDeposit", + [ l1Token.address, l2Token.address, tokenHolderA.address, tokenHolderA.address, depositAmount, - dataToReceive, - ]), - { gasLimit: 5_000_000 } + dataToSend, + ] ); - await assert.emits(l2ERC20ExtendedTokensBridge, tx, "DepositFinalized", [ - l1Token.address, - l2Token.address, - tokenHolderA.address, - tokenHolderA.address, - depositAmount, - "0x", - ]); - assert.equalBN( - await l2Token.balanceOf(tokenHolderA.address), - tokenHolderABalanceBefore.add(depositAmount) - ); - assert.equalBN( - await l2Token.totalSupply(), - l2TokenTotalSupplyBefore.add(depositAmount) - ); - }) - - .step("L2 -> L1 withdrawal via withdraw()", async (ctx) => { - const { accountA: tokenHolderA } = ctx.accounts; - const { withdrawalAmount } = ctx.common; - const { l1Token, l2Token, l2ERC20ExtendedTokensBridge } = ctx; + const messageNonce = await l1CrossDomainMessenger.messageNonce(); - const tokenHolderABalanceBefore = await l2Token.balanceOf( - tokenHolderA.address - ); - const l2TotalSupplyBefore = await l2Token.totalSupply(); - - const tx = await l2ERC20ExtendedTokensBridge - .connect(tokenHolderA.l2Signer) - .withdraw(l2Token.address, withdrawalAmount, 0, "0x"); - - await assert.emits(l2ERC20ExtendedTokensBridge, tx, "WithdrawalInitiated", [ - l1Token.address, - l2Token.address, - tokenHolderA.address, - tokenHolderA.address, - withdrawalAmount, - "0x", - ]); - assert.equalBN( - await l2Token.balanceOf(tokenHolderA.address), - tokenHolderABalanceBefore.sub(withdrawalAmount) - ); - assert.equalBN( - await l2Token.totalSupply(), - l2TotalSupplyBefore.sub(withdrawalAmount) - ); - }) - - .step("Finalize withdrawal on L1", async (ctx) => { - const { - l1Token, - l1CrossDomainMessenger, - l1LidoTokensBridge, - l2CrossDomainMessenger, - l2Token, - l2ERC20ExtendedTokensBridge, - } = ctx; - const { accountA: tokenHolderA, l1Stranger } = ctx.accounts; - const { withdrawalAmount } = ctx.common; - - const tokenHolderABalanceBefore = await l1Token.balanceOf( - tokenHolderA.address - ); - const l1ERC20ExtendedTokensBridgeBalanceBefore = await l1Token.balanceOf( - l1LidoTokensBridge.address - ); + await assert.emits(l1CrossDomainMessenger, tx, "SentMessage", [ + l2ERC20ExtendedTokensBridge.address, + l1LidoTokensBridge.address, + l2DepositCalldata, + messageNonce, + 200_000, + ]); - await l1CrossDomainMessenger - .connect(l1Stranger) - .setXDomainMessageSender(l2ERC20ExtendedTokensBridge.address); + assert.equalBN( + await l1Token.balanceOf(l1LidoTokensBridge.address), + l1ERC20ExtendedTokensBridgeBalanceBefore.add(depositAmount) + ); - const tx = await l1CrossDomainMessenger - .connect(l1Stranger) - .relayMessage( - l1LidoTokensBridge.address, - l2CrossDomainMessenger.address, - l1LidoTokensBridge.interface.encodeFunctionData( - "finalizeERC20Withdrawal", - [ + assert.equalBN( + await l1Token.balanceOf(tokenHolderA.address), + tokenHolderABalanceBefore.sub(depositAmount) + ); + }) + + .step("Finalize deposit on L2", async (ctx) => { + const { + l1Token, + l2Token, + l1LidoTokensBridge, + l2CrossDomainMessenger, + l2ERC20ExtendedTokensBridge, + } = ctx; + const { depositAmount } = ctx.constants; + + const { accountA: tokenHolderA, l1CrossDomainMessengerAliased } = + ctx.accounts; + + const tokenHolderABalanceBefore = await l2Token.balanceOf(tokenHolderA.address); + const l2TokenTotalSupplyBefore = await l2Token.totalSupply(); + + const dataToReceive = await packedTokenRateAndTimestamp(ctx.l2Provider, l1Token); + + const tx = await l2CrossDomainMessenger + .connect(l1CrossDomainMessengerAliased) + .relayMessage( + 1, + l1LidoTokensBridge.address, + l2ERC20ExtendedTokensBridge.address, + 0, + 300_000, + l2ERC20ExtendedTokensBridge.interface.encodeFunctionData("finalizeDeposit", [ l1Token.address, l2Token.address, tokenHolderA.address, tokenHolderA.address, - withdrawalAmount, - "0x", - ] - ), - 0 + depositAmount, + dataToReceive, + ]), + { gasLimit: 5_000_000 } + ); + + await assert.emits(l2ERC20ExtendedTokensBridge, tx, "DepositFinalized", [ + l1Token.address, + l2Token.address, + tokenHolderA.address, + tokenHolderA.address, + depositAmount, + "0x", + ]); + assert.equalBN( + await l2Token.balanceOf(tokenHolderA.address), + tokenHolderABalanceBefore.add(depositAmount) ); + assert.equalBN( + await l2Token.totalSupply(), + l2TokenTotalSupplyBefore.add(depositAmount) + ); + }) - await assert.emits(l1LidoTokensBridge, tx, "ERC20WithdrawalFinalized", [ - l1Token.address, - l2Token.address, - tokenHolderA.address, - tokenHolderA.address, - withdrawalAmount, - "0x", - ]); - - assert.equalBN( - await l1Token.balanceOf(l1LidoTokensBridge.address), - l1ERC20ExtendedTokensBridgeBalanceBefore.sub(withdrawalAmount) - ); + .step("L2 -> L1 withdrawal via withdraw()", async (ctx) => { + const { accountA: tokenHolderA } = ctx.accounts; + const { withdrawalAmount } = ctx.constants; + const { l1Token, l2Token, l2ERC20ExtendedTokensBridge } = ctx; - assert.equalBN( - await l1Token.balanceOf(tokenHolderA.address), - tokenHolderABalanceBefore.add(withdrawalAmount) - ); - }) + const tokenHolderABalanceBefore = await l2Token.balanceOf(tokenHolderA.address); + const l2TotalSupplyBefore = await l2Token.totalSupply(); - .step("L1 -> L2 deposit via depositERC20To()", async (ctx) => { - const { - l1Token, - l2Token, - l1LidoTokensBridge, - l2ERC20ExtendedTokensBridge, - l1CrossDomainMessenger, - } = ctx; - const { accountA: tokenHolderA, accountB: tokenHolderB } = ctx.accounts; - const { depositAmount } = ctx.common; - - assert.notEqual(tokenHolderA.address, tokenHolderB.address); - - await l1Token - .connect(tokenHolderA.l1Signer) - .approve(l1LidoTokensBridge.address, depositAmount); - - const tokenHolderABalanceBefore = await l1Token.balanceOf( - tokenHolderA.address - ); - const l1ERC20ExtendedTokensBridgeBalanceBefore = await l1Token.balanceOf( - l1LidoTokensBridge.address - ); + const tx = await l2ERC20ExtendedTokensBridge + .connect(tokenHolderA.l2Signer) + .withdraw(l2Token.address, withdrawalAmount, 0, "0x"); - const tx = await l1LidoTokensBridge - .connect(tokenHolderA.l1Signer) - .depositERC20To( + await assert.emits(l2ERC20ExtendedTokensBridge, tx, "WithdrawalInitiated", [ l1Token.address, l2Token.address, - tokenHolderB.address, - depositAmount, - 200_000, - "0x" - ); + tokenHolderA.address, + tokenHolderA.address, + withdrawalAmount, + "0x", + ]); - const dataToSend = await packedTokenRateAndTimestamp(ctx.l1Provider, l1Token); + const tokenHolderABalanceAfter = await l2Token.balanceOf(tokenHolderA.address); + const l2TotalSupplyAfter = await l2Token.totalSupply(); - await assert.emits(l1LidoTokensBridge, tx, "ERC20DepositInitiated", [ - l1Token.address, - l2Token.address, - tokenHolderA.address, - tokenHolderB.address, - depositAmount, - dataToSend, - ]); + assert.equalBN( + tokenHolderABalanceAfter, + tokenHolderABalanceBefore.sub(withdrawalAmount) + ); + assert.equalBN( + l2TotalSupplyAfter, + l2TotalSupplyBefore.sub(withdrawalAmount) + ); + }) + + .step("Finalize withdrawal on L1", async (ctx) => { + const { + l1Token, + l1CrossDomainMessenger, + l1LidoTokensBridge, + l2CrossDomainMessenger, + l2Token, + l2ERC20ExtendedTokensBridge, + } = ctx; + const { accountA: tokenHolderA, l1Stranger } = ctx.accounts; + const { depositAmount, withdrawalAmount } = ctx.constants; + + const tokenHolderABalanceBefore = await l1Token.balanceOf( + tokenHolderA.address + ); + const l1ERC20ExtendedTokensBridgeBalanceBefore = await l1Token.balanceOf( + l1LidoTokensBridge.address + ); - const l2DepositCalldata = l2ERC20ExtendedTokensBridge.interface.encodeFunctionData( - "finalizeDeposit", - [ + await l1CrossDomainMessenger + .connect(l1Stranger) + .setXDomainMessageSender(l2ERC20ExtendedTokensBridge.address); + + const tx = await l1CrossDomainMessenger + .connect(l1Stranger) + .relayMessage( + l1LidoTokensBridge.address, + l2CrossDomainMessenger.address, + l1LidoTokensBridge.interface.encodeFunctionData( + "finalizeERC20Withdrawal", + [ + l1Token.address, + l2Token.address, + tokenHolderA.address, + tokenHolderA.address, + withdrawalAmount, + "0x", + ] + ), + 0 + ); + + await assert.emits(l1LidoTokensBridge, tx, "ERC20WithdrawalFinalized", [ l1Token.address, l2Token.address, tokenHolderA.address, - tokenHolderB.address, - depositAmount, - dataToSend, - ] - ); + tokenHolderA.address, + withdrawalAmount, + "0x", + ]); - const messageNonce = await l1CrossDomainMessenger.messageNonce(); + const l1LidoTokensBridgeBalanceAfter = await l1Token.balanceOf(l1LidoTokensBridge.address); + const tokenHolderABalanceAfter = await l1Token.balanceOf(tokenHolderA.address); - await assert.emits(l1CrossDomainMessenger, tx, "SentMessage", [ - l2ERC20ExtendedTokensBridge.address, - l1LidoTokensBridge.address, - l2DepositCalldata, - messageNonce, - 200_000, - ]); + assert.equalBN( + l1LidoTokensBridgeBalanceAfter, + l1ERC20ExtendedTokensBridgeBalanceBefore.sub(withdrawalAmount) + ); - assert.equalBN( - await l1Token.balanceOf(l1LidoTokensBridge.address), - l1ERC20ExtendedTokensBridgeBalanceBefore.add(depositAmount) - ); + assert.equalBN( + tokenHolderABalanceAfter, + tokenHolderABalanceBefore.add(withdrawalAmount) + ); - assert.equalBN( - await l1Token.balanceOf(tokenHolderA.address), - tokenHolderABalanceBefore.sub(depositAmount) - ); - }) + /// check that user balance is correct after depositing and withdrawal. + const deltaDepositWithdrawal = depositAmount.sub(withdrawalAmount); + assert.equalBN( + ctx.balances.accountABalanceBeforeDeposit, + tokenHolderABalanceAfter.add(deltaDepositWithdrawal) + ); + }) + + .step("L1 -> L2 deposit via depositERC20To()", async (ctx) => { + const { + l1Token, + l2Token, + l1LidoTokensBridge, + l2ERC20ExtendedTokensBridge, + l1CrossDomainMessenger, + } = ctx; + const { accountA: tokenHolderA, accountB: tokenHolderB } = ctx.accounts; + const { depositAmount } = ctx.constants; + + assert.notEqual(tokenHolderA.address, tokenHolderB.address); + + const tokenHolderABalanceBefore = await l1Token.balanceOf(tokenHolderA.address); + const l1ERC20ExtendedTokensBridgeBalanceBefore = await l1Token.balanceOf(l1LidoTokensBridge.address); + + ctx.balances.accountABalanceBeforeDeposit = tokenHolderABalanceBefore; + ctx.balances.accountBBalanceBeforeDeposit = await l2Token.balanceOf(tokenHolderB.address); + + await l1Token + .connect(tokenHolderA.l1Signer) + .approve(l1LidoTokensBridge.address, depositAmount); + + const tx = await l1LidoTokensBridge + .connect(tokenHolderA.l1Signer) + .depositERC20To( + l1Token.address, + l2Token.address, + tokenHolderB.address, + depositAmount, + 200_000, + "0x" + ); - .step("Finalize deposit on L2", async (ctx) => { - const { - l1Token, - l1LidoTokensBridge, - l2Token, - l2CrossDomainMessenger, - l2ERC20ExtendedTokensBridge, - } = ctx; - const { - accountA: tokenHolderA, - accountB: tokenHolderB, - l1CrossDomainMessengerAliased, - } = ctx.accounts; - const { depositAmount } = ctx.common; - - const l2TokenTotalSupplyBefore = await l2Token.totalSupply(); - const tokenHolderBBalanceBefore = await l2Token.balanceOf( - tokenHolderB.address - ); + const dataToSend = await packedTokenRateAndTimestamp(ctx.l1Provider, l1Token); - const dataToReceive = await packedTokenRateAndTimestamp(ctx.l2Provider, l1Token); + await assert.emits(l1LidoTokensBridge, tx, "ERC20DepositInitiated", [ + l1Token.address, + l2Token.address, + tokenHolderA.address, + tokenHolderB.address, + depositAmount, + dataToSend, + ]); - const tx = await l2CrossDomainMessenger - .connect(l1CrossDomainMessengerAliased) - .relayMessage( - 1, - l1LidoTokensBridge.address, - l2ERC20ExtendedTokensBridge.address, - 0, - 300_000, - l2ERC20ExtendedTokensBridge.interface.encodeFunctionData("finalizeDeposit", [ + const l2DepositCalldata = l2ERC20ExtendedTokensBridge.interface.encodeFunctionData( + "finalizeDeposit", + [ l1Token.address, l2Token.address, tokenHolderA.address, tokenHolderB.address, depositAmount, - dataToReceive, - ]), - { gasLimit: 5_000_000 } + dataToSend, + ] ); - await assert.emits(l2ERC20ExtendedTokensBridge, tx, "DepositFinalized", [ - l1Token.address, - l2Token.address, - tokenHolderA.address, - tokenHolderB.address, - depositAmount, - "0x", - ]); - - assert.equalBN( - await l2Token.totalSupply(), - l2TokenTotalSupplyBefore.add(depositAmount) - ); - assert.equalBN( - await l2Token.balanceOf(tokenHolderB.address), - tokenHolderBBalanceBefore.add(depositAmount) - ); - }) + const messageNonce = await l1CrossDomainMessenger.messageNonce(); - .step("L2 -> L1 withdrawal via withdrawTo()", async (ctx) => { - const { l1Token, l2Token, l2ERC20ExtendedTokensBridge } = ctx; - const { accountA: tokenHolderA, accountB: tokenHolderB } = ctx.accounts; - const { withdrawalAmount } = ctx.common; + await assert.emits(l1CrossDomainMessenger, tx, "SentMessage", [ + l2ERC20ExtendedTokensBridge.address, + l1LidoTokensBridge.address, + l2DepositCalldata, + messageNonce, + 200_000, + ]); - const tokenHolderBBalanceBefore = await l2Token.balanceOf( - tokenHolderB.address - ); - const l2TotalSupplyBefore = await l2Token.totalSupply(); + assert.equalBN( + await l1Token.balanceOf(l1LidoTokensBridge.address), + l1ERC20ExtendedTokensBridgeBalanceBefore.add(depositAmount) + ); - const tx = await l2ERC20ExtendedTokensBridge - .connect(tokenHolderB.l2Signer) - .withdrawTo( + assert.equalBN( + await l1Token.balanceOf(tokenHolderA.address), + tokenHolderABalanceBefore.sub(depositAmount) + ); + }) + + .step("Finalize deposit on L2", async (ctx) => { + const { + l1Token, + l1LidoTokensBridge, + l2Token, + l2CrossDomainMessenger, + l2ERC20ExtendedTokensBridge, + } = ctx; + const { + accountA: tokenHolderA, + accountB: tokenHolderB, + l1CrossDomainMessengerAliased, + } = ctx.accounts; + const { depositAmount } = ctx.constants; + + const l2TokenTotalSupplyBefore = await l2Token.totalSupply(); + const tokenHolderBBalanceBefore = await l2Token.balanceOf(tokenHolderB.address); + + const dataToReceive = await packedTokenRateAndTimestamp(ctx.l2Provider, l1Token); + + const tx = await l2CrossDomainMessenger + .connect(l1CrossDomainMessengerAliased) + .relayMessage( + 1, + l1LidoTokensBridge.address, + l2ERC20ExtendedTokensBridge.address, + 0, + 300_000, + l2ERC20ExtendedTokensBridge.interface.encodeFunctionData("finalizeDeposit", [ + l1Token.address, + l2Token.address, + tokenHolderA.address, + tokenHolderB.address, + depositAmount, + dataToReceive, + ]), + { gasLimit: 5_000_000 } + ); + + await assert.emits(l2ERC20ExtendedTokensBridge, tx, "DepositFinalized", [ + l1Token.address, l2Token.address, tokenHolderA.address, + tokenHolderB.address, + depositAmount, + "0x", + ]); + + assert.equalBN( + await l2Token.totalSupply(), + l2TokenTotalSupplyBefore.add(depositAmount) + ); + assert.equalBN( + await l2Token.balanceOf(tokenHolderB.address), + tokenHolderBBalanceBefore.add(depositAmount) + ); + }) + + .step("L2 -> L1 withdrawal via withdrawTo()", async (ctx) => { + const { l1Token, l2Token, l2ERC20ExtendedTokensBridge } = ctx; + const { accountA: tokenHolderA, accountB: tokenHolderB } = ctx.accounts; + const { withdrawalAmount } = ctx.constants; + + const tokenHolderBBalanceBefore = await l2Token.balanceOf( + tokenHolderB.address + ); + const l2TotalSupplyBefore = await l2Token.totalSupply(); + + const tx = await l2ERC20ExtendedTokensBridge + .connect(tokenHolderB.l2Signer) + .withdrawTo( + l2Token.address, + tokenHolderA.address, + withdrawalAmount, + 0, + "0x" + ); + + await assert.emits(l2ERC20ExtendedTokensBridge, tx, "WithdrawalInitiated", [ + l1Token.address, + l2Token.address, + tokenHolderB.address, + tokenHolderA.address, withdrawalAmount, - 0, - "0x" + "0x", + ]); + + assert.equalBN( + await l2Token.balanceOf(tokenHolderB.address), + tokenHolderBBalanceBefore.sub(withdrawalAmount) ); - await assert.emits(l2ERC20ExtendedTokensBridge, tx, "WithdrawalInitiated", [ - l1Token.address, - l2Token.address, - tokenHolderB.address, - tokenHolderA.address, - withdrawalAmount, - "0x", - ]); - - assert.equalBN( - await l2Token.balanceOf(tokenHolderB.address), - tokenHolderBBalanceBefore.sub(withdrawalAmount) - ); + assert.equalBN( + await l2Token.totalSupply(), + l2TotalSupplyBefore.sub(withdrawalAmount) + ); + }) + + .step("Finalize withdrawal on L1", async (ctx) => { + const { + l1Token, + l1CrossDomainMessenger, + l1LidoTokensBridge, + l2CrossDomainMessenger, + l2Token, + l2ERC20ExtendedTokensBridge, + } = ctx; + const { + accountA: tokenHolderA, + accountB: tokenHolderB, + l1Stranger, + } = ctx.accounts; + const { depositAmount, withdrawalAmount } = ctx.constants; + + const tokenHolderABalanceBefore = await l1Token.balanceOf( + tokenHolderA.address + ); + const l1ERC20ExtendedTokensBridgeBalanceBefore = await l1Token.balanceOf( + l1LidoTokensBridge.address + ); - assert.equalBN( - await l2Token.totalSupply(), - l2TotalSupplyBefore.sub(withdrawalAmount) - ); - }) + await l1CrossDomainMessenger + .connect(l1Stranger) + .setXDomainMessageSender(l2ERC20ExtendedTokensBridge.address); + + const tx = await l1CrossDomainMessenger + .connect(l1Stranger) + .relayMessage( + l1LidoTokensBridge.address, + l2CrossDomainMessenger.address, + l1LidoTokensBridge.interface.encodeFunctionData( + "finalizeERC20Withdrawal", + [ + l1Token.address, + l2Token.address, + tokenHolderB.address, + tokenHolderA.address, + withdrawalAmount, + "0x", + ] + ), + 0 + ); + + await assert.emits(l1LidoTokensBridge, tx, "ERC20WithdrawalFinalized", [ + l1Token.address, + l2Token.address, + tokenHolderB.address, + tokenHolderA.address, + withdrawalAmount, + "0x", + ]); + + const l1LidoTokensBridgeBalanceAfter = await l1Token.balanceOf(l1LidoTokensBridge.address); + const tokenHolderABalanceAfter = await l1Token.balanceOf(tokenHolderA.address); + const tokenHolderBBalanceAfter = await l2Token.balanceOf(tokenHolderB.address); + + assert.equalBN( + l1LidoTokensBridgeBalanceAfter, + l1ERC20ExtendedTokensBridgeBalanceBefore.sub(withdrawalAmount) + ); + + assert.equalBN( + tokenHolderABalanceAfter, + tokenHolderABalanceBefore.add(withdrawalAmount) + ); + + /// check that user balance is correct after depositing and withdrawal. + const deltaDepositWithdrawal = depositAmount.sub(withdrawalAmount); + assert.equalBN( + ctx.balances.accountABalanceBeforeDeposit, + tokenHolderABalanceAfter.add(deltaDepositWithdrawal) + ); + assert.equalBN( + ctx.balances.accountBBalanceBeforeDeposit, + tokenHolderBBalanceAfter.sub(deltaDepositWithdrawal) + ); + }) + + .run(); +} + +function ctxFactory(depositAmount: BigNumber, withdrawalAmount: BigNumber) { + return async () => { + const networkName = env.network("TESTING_OPT_NETWORK", "mainnet"); + const tokenRate = BigNumber.from('1164454276599657236'); - .step("Finalize withdrawal on L1", async (ctx) => { - const { - l1Token, - l1CrossDomainMessenger, - l1LidoTokensBridge, - l2CrossDomainMessenger, - l2Token, - l2ERC20ExtendedTokensBridge, - } = ctx; const { - accountA: tokenHolderA, - accountB: tokenHolderB, - l1Stranger, - } = ctx.accounts; - const { withdrawalAmount } = ctx.common; - - const tokenHolderABalanceBefore = await l1Token.balanceOf( - tokenHolderA.address - ); - const l1ERC20ExtendedTokensBridgeBalanceBefore = await l1Token.balanceOf( - l1LidoTokensBridge.address + l1Provider, + l2Provider, + l1ERC20ExtendedTokensBridgeAdmin, + l2ERC20ExtendedTokensBridgeAdmin, + ...contracts + } = await optimism.testing(networkName).getIntegrationTestSetup(tokenRate); + + const l1Snapshot = await l1Provider.send("evm_snapshot", []); + const l2Snapshot = await l2Provider.send("evm_snapshot", []); + + + await optimism.testing(networkName).stubL1CrossChainMessengerContract(); + + const accountA = testing.accounts.accountA(l1Provider, l2Provider); + const accountB = testing.accounts.accountB(l1Provider, l2Provider); + + await testing.setBalance( + await contracts.l1TokensHolder.getAddress(), + wei.toBigNumber(wei`1 ether`), + l1Provider ); - await l1CrossDomainMessenger - .connect(l1Stranger) - .setXDomainMessageSender(l2ERC20ExtendedTokensBridge.address); + await testing.setBalance( + await l1ERC20ExtendedTokensBridgeAdmin.getAddress(), + wei.toBigNumber(wei`1 ether`), + l1Provider + ); - const tx = await l1CrossDomainMessenger - .connect(l1Stranger) - .relayMessage( - l1LidoTokensBridge.address, - l2CrossDomainMessenger.address, - l1LidoTokensBridge.interface.encodeFunctionData( - "finalizeERC20Withdrawal", - [ - l1Token.address, - l2Token.address, - tokenHolderB.address, - tokenHolderA.address, - withdrawalAmount, - "0x", - ] - ), - 0 - ); + await testing.setBalance( + await l2ERC20ExtendedTokensBridgeAdmin.getAddress(), + wei.toBigNumber(wei`1 ether`), + l2Provider + ); - await assert.emits(l1LidoTokensBridge, tx, "ERC20WithdrawalFinalized", [ - l1Token.address, - l2Token.address, - tokenHolderB.address, - tokenHolderA.address, - withdrawalAmount, - "0x", - ]); - - assert.equalBN( - await l1Token.balanceOf(l1LidoTokensBridge.address), - l1ERC20ExtendedTokensBridgeBalanceBefore.sub(withdrawalAmount) + const l1CrossDomainMessengerAliased = await testing.impersonate( + testing.accounts.applyL1ToL2Alias(contracts.l1CrossDomainMessenger.address), + l2Provider ); - assert.equalBN( - await l1Token.balanceOf(tokenHolderA.address), - tokenHolderABalanceBefore.add(withdrawalAmount) + await testing.setBalance( + await l1CrossDomainMessengerAliased.getAddress(), + wei.toBigNumber(wei`1 ether`), + l2Provider ); - }) - - .run(); - -async function ctxFactory() { - const networkName = env.network("TESTING_OPT_NETWORK", "mainnet"); - - const { - l1Provider, - l2Provider, - l1ERC20ExtendedTokensBridgeAdmin, - l2ERC20ExtendedTokensBridgeAdmin, - ...contracts - } = await optimism.testing(networkName).getIntegrationTestSetup(); - - const l1Snapshot = await l1Provider.send("evm_snapshot", []); - const l2Snapshot = await l2Provider.send("evm_snapshot", []); - - await optimism.testing(networkName).stubL1CrossChainMessengerContract(); - - const accountA = testing.accounts.accountA(l1Provider, l2Provider); - const accountB = testing.accounts.accountB(l1Provider, l2Provider); - - const depositAmount = wei`0.15 ether`; - const withdrawalAmount = wei`0.05 ether`; - - await testing.setBalance( - await contracts.l1TokensHolder.getAddress(), - wei.toBigNumber(wei`1 ether`), - l1Provider - ); - - await testing.setBalance( - await l1ERC20ExtendedTokensBridgeAdmin.getAddress(), - wei.toBigNumber(wei`1 ether`), - l1Provider - ); - - await testing.setBalance( - await l2ERC20ExtendedTokensBridgeAdmin.getAddress(), - wei.toBigNumber(wei`1 ether`), - l2Provider - ); - - await contracts.l1Token - .connect(contracts.l1TokensHolder) - .transfer(accountA.l1Signer.address, wei.toBigNumber(depositAmount).mul(2)); - - const l1CrossDomainMessengerAliased = await testing.impersonate( - testing.accounts.applyL1ToL2Alias(contracts.l1CrossDomainMessenger.address), - l2Provider - ); - - await testing.setBalance( - await l1CrossDomainMessengerAliased.getAddress(), - wei.toBigNumber(wei`1 ether`), - l2Provider - ); - - return { - l1Provider, - l2Provider, - ...contracts, - accounts: { - accountA, - accountB, - l1Stranger: testing.accounts.stranger(l1Provider), - l1ERC20ExtendedTokensBridgeAdmin, - l2ERC20ExtendedTokensBridgeAdmin, - l1CrossDomainMessengerAliased, - }, - common: { - depositAmount, - withdrawalAmount, - }, - snapshot: { - l1: l1Snapshot, - l2: l2Snapshot, - }, - }; + + await contracts.l1Token + .connect(contracts.l1TokensHolder) + .transfer(accountA.l1Signer.address, depositAmount.mul(2)); + + var accountABalanceBeforeDeposit = BigNumber.from(0); + var accountBBalanceBeforeDeposit = BigNumber.from(0); + + return { + l1Provider, + l2Provider, + ...contracts, + accounts: { + accountA, + accountB, + l1Stranger: testing.accounts.stranger(l1Provider), + l1ERC20ExtendedTokensBridgeAdmin, + l2ERC20ExtendedTokensBridgeAdmin, + l1CrossDomainMessengerAliased, + }, + constants: { + depositAmount, + withdrawalAmount, + tokenRate + }, + balances: { + accountABalanceBeforeDeposit, + accountBBalanceBeforeDeposit + }, + snapshot: { + l1: l1Snapshot, + l2: l2Snapshot, + }, + }; + } } async function packedTokenRateAndTimestamp(l1Provider: JsonRpcProvider, l1Token: ERC20WrapperStub) { @@ -630,3 +666,33 @@ async function packedTokenRateAndTimestamp(l1Provider: JsonRpcProvider, l1Token: const blockTimestampStr = ethers.utils.hexZeroPad(ethers.utils.hexlify(blockTimestamp), 5); return ethers.utils.hexConcat([stEthPerTokenStr, blockTimestampStr]); } + +bridgingTestsSuit( + scenario( + "Optimism :: Bridging X non-rebasable token integration test", + ctxFactory( + wei.toBigNumber(wei`0.001 ether`), + wei.toBigNumber(wei`0.001 ether`) + ) + ) +); + +bridgingTestsSuit( + scenario( + "Optimism :: Bridging 1 wei non-rebasable token integration test", + ctxFactory( + wei.toBigNumber(wei`1 wei`), + wei.toBigNumber(wei`1 wei`) + ) + ) +); + +bridgingTestsSuit( + scenario( + "Optimism :: Bridging zero non-rebasable token integration test", + ctxFactory( + BigNumber.from('0'), + BigNumber.from('0') + ) + ) +); diff --git a/test/optimism/deposit-gas-estimation.test.ts b/test/optimism/deposit-gas-estimation.test.ts index e110a17e..945e5cbb 100644 --- a/test/optimism/deposit-gas-estimation.test.ts +++ b/test/optimism/deposit-gas-estimation.test.ts @@ -4,6 +4,7 @@ import env from "../../utils/env"; import { wei } from "../../utils/wei"; import optimism from "../../utils/optimism"; import testing, { scenario } from "../../utils/testing"; +import { BigNumber } from 'ethers' scenario("Optimism :: Bridging integration test", ctxFactory) .after(async (ctx) => { @@ -135,6 +136,7 @@ scenario("Optimism :: Bridging integration test", ctxFactory) async function ctxFactory() { const networkName = env.network("TESTING_OPT_NETWORK", "mainnet"); console.log("networkName=", networkName); + const exchangeRate = BigNumber.from('1164454276599657236'); const { l1Provider, @@ -142,7 +144,7 @@ async function ctxFactory() { l1ERC20ExtendedTokensBridgeAdmin, l2ERC20ExtendedTokensBridgeAdmin, ...contracts - } = await optimism.testing(networkName).getIntegrationTestSetup(); + } = await optimism.testing(networkName).getIntegrationTestSetup(exchangeRate); const l1Snapshot = await l1Provider.send("evm_snapshot", []); const l2Snapshot = await l2Provider.send("evm_snapshot", []); diff --git a/test/token/ERC20Permit.unit.test.ts b/test/token/ERC20Permit.unit.test.ts index 928db576..b4a10630 100644 --- a/test/token/ERC20Permit.unit.test.ts +++ b/test/token/ERC20Permit.unit.test.ts @@ -53,12 +53,6 @@ const getAccountsEIP1271 = async () => { function permitTestsSuit(unitInstance: UnitTest) { unitInstance - - // .test("wrappedToken() :: has the same address is in constructor", async (ctx) => { - // const { rebasableProxied, wrappedToken } = ctx.contracts; - // assert.equal(await rebasableProxied.TOKEN_TO_WRAP_FROM(), wrappedToken.address) - // }) - .test('eip712Domain() is correct', async (ctx) => { const token = ctx.contracts.rebasableProxied const [, name, version, chainId, verifyingContract, ,] = await token.eip712Domain() From e4d45cc7b0895ba0e5dbefb89a67716d988dce32 Mon Sep 17 00:00:00 2001 From: Anton Kovalchuk Date: Wed, 1 May 2024 13:21:34 +0400 Subject: [PATCH 096/148] refactor _mintTokens, add new method for rebasable token that can unwrap tokens by bridge only, use it in _burnTokens --- .../optimism/L2ERC20ExtendedTokensBridge.sol | 19 ++++++------- contracts/token/ERC20RebasableBridged.sol | 27 ++++++++++++++----- 2 files changed, 29 insertions(+), 17 deletions(-) diff --git a/contracts/optimism/L2ERC20ExtendedTokensBridge.sol b/contracts/optimism/L2ERC20ExtendedTokensBridge.sol index 359e81f3..93dfa0f8 100644 --- a/contracts/optimism/L2ERC20ExtendedTokensBridge.sol +++ b/contracts/optimism/L2ERC20ExtendedTokensBridge.sol @@ -158,7 +158,7 @@ contract L2ERC20ExtendedTokensBridge is sendCrossDomainMessage(L1_TOKEN_BRIDGE, l1Gas_, message); } - /// @notice Mints tokens and returns amount of minted tokens. + /// @notice Mints tokens, wraps if needed and returns amount of minted tokens. /// @param l2Token_ Address of L2 token for which deposit is finalizing. /// @param to_ Account that token mints for. /// @param amount_ Amount of token or shares to mint. @@ -171,18 +171,16 @@ contract L2ERC20ExtendedTokensBridge is if(l2Token_ == L2_TOKEN_REBASABLE) { IERC20Bridged(L2_TOKEN_NON_REBASABLE).bridgeMint(address(this), amount_); IERC20(L2_TOKEN_NON_REBASABLE).safeIncreaseAllowance(l2Token_, amount_); - uint256 rebasableTokensAmount; if (amount_ != 0) { - rebasableTokensAmount = ERC20RebasableBridged(l2Token_).wrap(amount_); + ERC20RebasableBridged(l2Token_).wrap(amount_); } - ERC20RebasableBridged(l2Token_).transferShares(to_, amount_); - return rebasableTokensAmount; + return ERC20RebasableBridged(l2Token_).transferShares(to_, amount_); } IERC20Bridged(l2Token_).bridgeMint(to_, amount_); return amount_; } - /// @notice Burns tokens and returns amount of non-rebasable token to withdraw. + /// @notice Unwraps if needed, burns tokens and returns amount of non-rebasable token to withdraw. /// @param l2Token_ Address of L2 token where withdrawal was initiated. /// @param from_ Account which tokens are burns. /// @param amount_ Amount of token to burn. @@ -193,13 +191,12 @@ contract L2ERC20ExtendedTokensBridge is uint256 amount_ ) internal returns (uint256) { if(l2Token_ == L2_TOKEN_REBASABLE) { - ERC20RebasableBridged(l2Token_).transferFrom(from_, address(this), amount_); - uint256 nonRebasableTokensAmount; + uint256 nonRebasableTokenAmount; if (amount_ != 0) { - nonRebasableTokensAmount = ERC20RebasableBridged(l2Token_).unwrap(amount_); + nonRebasableTokenAmount = ERC20RebasableBridged(l2Token_).bridgeUnwrap(from_, amount_); } - IERC20Bridged(L2_TOKEN_NON_REBASABLE).bridgeBurn(address(this), nonRebasableTokensAmount); - return nonRebasableTokensAmount; + IERC20Bridged(L2_TOKEN_NON_REBASABLE).bridgeBurn(from_, nonRebasableTokenAmount); + return nonRebasableTokenAmount; } IERC20Bridged(l2Token_).bridgeBurn(from_, amount_); return amount_; diff --git a/contracts/token/ERC20RebasableBridged.sol b/contracts/token/ERC20RebasableBridged.sol index 2abaaff1..dc3c7faa 100644 --- a/contracts/token/ERC20RebasableBridged.sol +++ b/contracts/token/ERC20RebasableBridged.sol @@ -26,6 +26,12 @@ interface IERC20BridgedShares is IERC20 { /// @param account_ An address of the account to burn shares /// @param amount_ An amount of shares to burn function bridgeBurnShares(address account_, uint256 amount_) external; + + /// @notice Exchanges wrapper token to wrappable one. Can be called by bridge only. + /// @param account_ An address of the account to unwrap token for + /// @param amount_ amount of wrapper token to uwrap in exchange for wrappable. + /// @return Amount of wrappable token user receives after unwrap. + function bridgeUnwrap(address account_, uint256 amount_) external returns (uint256); } /// @author kovalgek @@ -84,13 +90,12 @@ contract ERC20RebasableBridged is IERC20, IERC20Wrapper, IERC20BridgedShares, ER /// @inheritdoc IERC20Wrapper function unwrap(uint256 tokenAmount_) external returns (uint256) { - if (tokenAmount_ == 0) revert ErrorZeroTokensUnwrap(); - - uint256 sharesAmount = _getSharesByTokens(tokenAmount_); - _burnShares(msg.sender, sharesAmount); - TOKEN_TO_WRAP_FROM.safeTransfer(msg.sender, sharesAmount); + return _unwrap(msg.sender, tokenAmount_); + } - return sharesAmount; + /// @inheritdoc IERC20BridgedShares + function bridgeUnwrap(address account_, uint256 amount_) external onlyBridge returns (uint256) { + return _unwrap(account_, amount_); } /// @inheritdoc IERC20BridgedShares @@ -371,6 +376,16 @@ contract ERC20RebasableBridged is IERC20, IERC20Wrapper, IERC20BridgedShares, ER _setERC20MetadataSymbol(symbol_); } + function _unwrap(address account_, uint256 tokenAmount_) internal returns (uint256) { + if (tokenAmount_ == 0) revert ErrorZeroTokensUnwrap(); + + uint256 sharesAmount = _getSharesByTokens(tokenAmount_); + _burnShares(account_, sharesAmount); + TOKEN_TO_WRAP_FROM.safeTransfer(account_, sharesAmount); + + return sharesAmount; + } + /// @dev validates that account_ is not zero address modifier onlyNonZeroAccount(address account_) { if (account_ == address(0)) { From ccb34109c79dd9afce36a049122fbfa43966ef86 Mon Sep 17 00:00:00 2001 From: Anton Kovalchuk Date: Wed, 1 May 2024 13:22:56 +0400 Subject: [PATCH 097/148] remove approval before withdrawal in tests, add new tests for bridgeUnwrap method --- .../L2ERC20ExtendedTokensBridge.unit.test.ts | 4 - .../bridging-rebasable.integration.test.ts | 8 -- .../ERC20RebasableBridgedPermit.unit.test.ts | 119 ++++++++++++++++++ 3 files changed, 119 insertions(+), 12 deletions(-) diff --git a/test/optimism/L2ERC20ExtendedTokensBridge.unit.test.ts b/test/optimism/L2ERC20ExtendedTokensBridge.unit.test.ts index ba92c686..b9133d9e 100644 --- a/test/optimism/L2ERC20ExtendedTokensBridge.unit.test.ts +++ b/test/optimism/L2ERC20ExtendedTokensBridge.unit.test.ts @@ -278,8 +278,6 @@ unit("Optimism:: L2ERC20ExtendedTokensBridge", ctxFactory) const recipientBalanceBefore = await l2TokenRebasable.balanceOf(recipient.address); const totalSupplyBefore = await l2TokenRebasable.totalSupply(); - await l2TokenRebasable.connect(recipient).approve(l2TokenBridge.address, amountToWithdraw); - const tx = await l2TokenBridge.connect(recipient).withdraw( l2TokenRebasable.address, amountToWithdraw, @@ -588,8 +586,6 @@ unit("Optimism:: L2ERC20ExtendedTokensBridge", ctxFactory) const deployerBalanceBefore = await l2TokenRebasable.balanceOf(deployer.address); const totalSupplyBefore = await l2TokenRebasable.totalSupply(); - await l2TokenRebasable.approve(l2TokenBridge.address, amountToWithdraw); - const tx = await l2TokenBridge.connect(deployer).withdrawTo( l2TokenRebasable.address, recipient.address, diff --git a/test/optimism/bridging-rebasable.integration.test.ts b/test/optimism/bridging-rebasable.integration.test.ts index b03b7056..50c2a519 100644 --- a/test/optimism/bridging-rebasable.integration.test.ts +++ b/test/optimism/bridging-rebasable.integration.test.ts @@ -242,10 +242,6 @@ function bridgingTestsSuit(scenarioInstance: ScenarioTest) { const tokenHolderABalanceBefore = await l2TokenRebasable.balanceOf(tokenHolderA.address); const l2TotalSupplyBefore = await l2TokenRebasable.totalSupply(); - await l2TokenRebasable - .connect(tokenHolderA.l2Signer) - .approve(l2ERC20ExtendedTokensBridge.address, withdrawalAmountOfRebasableToken); - const tx = await l2ERC20ExtendedTokensBridge .connect(tokenHolderA.l2Signer) .withdraw( @@ -513,10 +509,6 @@ function bridgingTestsSuit(scenarioInstance: ScenarioTest) { const tokenHolderBBalanceBefore = await l2TokenRebasable.balanceOf(tokenHolderB.address); const l2TotalSupplyBefore = await l2TokenRebasable.totalSupply(); - await l2TokenRebasable - .connect(tokenHolderB.l2Signer) - .approve(l2ERC20ExtendedTokensBridge.address, withdrawalAmountOfRebasableToken); - const tx = await l2ERC20ExtendedTokensBridge .connect(tokenHolderB.l2Signer) .withdrawTo( diff --git a/test/token/ERC20RebasableBridgedPermit.unit.test.ts b/test/token/ERC20RebasableBridgedPermit.unit.test.ts index 66663379..ec83aa02 100644 --- a/test/token/ERC20RebasableBridgedPermit.unit.test.ts +++ b/test/token/ERC20RebasableBridgedPermit.unit.test.ts @@ -360,6 +360,125 @@ unit("ERC20RebasableBridgedPermit", ctxFactory) await assert.revertsWith(rebasableProxied.connect(user1).unwrap(wei`4 ether`), "ErrorNotEnoughBalance()"); }) + .test("bridgeUnwrap() :: revert if not bridge", async (ctx) => { + const { rebasableProxied } = ctx.contracts; + const { user1, user2 } = ctx.accounts; + await assert.revertsWith(rebasableProxied.connect(user1).bridgeUnwrap(user2.address, 10), "ErrorNotBridge()"); + }) + + .test("bridgeUnwrap() :: revert if unwrap 0 wstETH", async (ctx) => { + const { rebasableProxied } = ctx.contracts; + const { user1, owner } = ctx.accounts; + await assert.revertsWith(rebasableProxied.connect(owner).bridgeUnwrap(user1.address, 0), "ErrorZeroTokensUnwrap()"); + }) + + .test("bridgeUnwrap() :: when no balance", async (ctx) => { + const { rebasableProxied } = ctx.contracts; + const { user1, owner } = ctx.accounts; + + await assert.revertsWith(rebasableProxied.connect(owner).bridgeUnwrap(user1.address, wei`4 ether`), "ErrorNotEnoughBalance()"); + }) + + .test("bridgeUnwrap() :: with wrong oracle update time", async (ctx) => { + + const { deployer, user1, owner, zero } = ctx.accounts; + const { decimals } = ctx.constants; + + // deploy new implementation to test initial oracle state + const wrappedToken = await new ERC20BridgedPermit__factory(deployer).deploy( + "WsETH Test Token", + "WsETH", + "1", + decimals, + owner.address + ); + const tokenRateOracle = await new TokenRateOracle__factory(deployer).deploy( + zero.address, + owner.address, + zero.address, + 86400, + 86400, + 500 + ); + const rebasableProxied = await new ERC20RebasableBridgedPermit__factory(deployer).deploy( + "", + "symbol", + "1", + 10, + wrappedToken.address, + tokenRateOracle.address, + owner.address + ); + + await wrappedToken.connect(owner).bridgeMint(user1.address, 1000); + await assert.revertsWith(rebasableProxied.connect(owner).bridgeUnwrap(user1.address, 5), "ErrorWrongOracleUpdateTime()"); + }) + + .test("bridgeUnwrap() :: happy path", async (ctx) => { + + const { rebasableProxied, wrappedToken } = ctx.contracts; + const { user1, user2, owner } = ctx.accounts; + const { tokenRate, tenPowDecimals, premintShares } = ctx.constants; + + const totalSupply = BigNumber.from(tokenRate).mul(premintShares).div(tenPowDecimals); + + assert.equalBN(await rebasableProxied.getTotalShares(), premintShares); + assert.equalBN(await rebasableProxied.totalSupply(), totalSupply); + + // user1 + + assert.equalBN(await rebasableProxied.sharesOf(user1.address), 0); + assert.equalBN(await rebasableProxied.balanceOf(user1.address), 0); + + const user1SharesToWrap = wei`100 ether`; + const user1SharesToUnwrap = wei`59 ether`; + const user1TokensToUnwrap = tokenRate.mul(user1SharesToUnwrap).div(tenPowDecimals); + + const user1Shares = BigNumber.from(user1SharesToWrap).sub(user1SharesToUnwrap); + const user1Tokens = BigNumber.from(tokenRate).mul(user1Shares).div(tenPowDecimals); + + await wrappedToken.connect(owner).bridgeMint(user1.address, user1SharesToWrap); + await wrappedToken.connect(user1).approve(rebasableProxied.address, user1SharesToWrap); + + const tx0 = await rebasableProxied.connect(user1).wrap(user1SharesToWrap); + assert.equalBN(await rebasableProxied.connect(owner).callStatic.bridgeUnwrap(user1.address, user1TokensToUnwrap), user1SharesToUnwrap); + const tx = await rebasableProxied.connect(owner).bridgeUnwrap(user1.address, user1TokensToUnwrap); + + assert.equalBN(await rebasableProxied.sharesOf(user1.address), user1Shares); + assert.equalBN(await rebasableProxied.balanceOf(user1.address), user1Tokens); + assert.equalBN(await wrappedToken.balanceOf(rebasableProxied.address), user1Shares); + + // common state changes + assert.equalBN(await rebasableProxied.getTotalShares(), premintShares.add(user1Shares)); + assert.equalBN(await rebasableProxied.totalSupply(), totalSupply.add(user1Tokens)); + + // user2 + const user2SharesToWrap = wei`145 ether`; + const user2SharesToUnwrap = wei`14 ether`; + const user2TokensToUnwrap = tokenRate.mul(user2SharesToUnwrap).div(tenPowDecimals); + + const user2Shares = BigNumber.from(user2SharesToWrap).sub(user2SharesToUnwrap); + const user2Tokens = BigNumber.from(tokenRate).mul(user2Shares).div(tenPowDecimals); + + assert.equalBN(await rebasableProxied.sharesOf(user2.address), 0); + assert.equalBN(await rebasableProxied.balanceOf(user2.address), 0); + + await wrappedToken.connect(owner).bridgeMint(user2.address, user2SharesToWrap); + await wrappedToken.connect(user2).approve(rebasableProxied.address, user2SharesToWrap); + + await rebasableProxied.connect(user2).wrap(user2SharesToWrap); + assert.equalBN(await rebasableProxied.connect(owner).callStatic.bridgeUnwrap(user2.address, user2TokensToUnwrap), user2SharesToUnwrap); + const tx2 = await rebasableProxied.connect(owner).bridgeUnwrap(user2.address, user2TokensToUnwrap); + + assert.equalBN(await rebasableProxied.sharesOf(user2.address), user2Shares); + assert.equalBN(await rebasableProxied.balanceOf(user2.address), user2Tokens); + assert.equalBN(await wrappedToken.balanceOf(rebasableProxied.address), BigNumber.from(user1Shares).add(user2Shares)); + + // common state changes + assert.equalBN(await rebasableProxied.getTotalShares(), premintShares.add(user1Shares).add(user2Shares)); + assert.equalBN(await rebasableProxied.totalSupply(), totalSupply.add(user1Tokens).add(user2Tokens)); + }) + .test("bridgeMintShares() :: happy path", async (ctx) => { const { rebasableProxied } = ctx.contracts; From b552925daa8e75e2862db42d75be974fb2038899 Mon Sep 17 00:00:00 2001 From: Anton Kovalchuk Date: Wed, 1 May 2024 17:29:37 +0400 Subject: [PATCH 098/148] remove bridgeMintShares and bridgeBurnShares methods and rename interface --- contracts/token/ERC20RebasableBridged.sol | 32 +++++------------------ 1 file changed, 6 insertions(+), 26 deletions(-) diff --git a/contracts/token/ERC20RebasableBridged.sol b/contracts/token/ERC20RebasableBridged.sol index dc3c7faa..b21c3381 100644 --- a/contracts/token/ERC20RebasableBridged.sol +++ b/contracts/token/ERC20RebasableBridged.sol @@ -12,21 +12,11 @@ import {UnstructuredRefStorage} from "../lib/UnstructuredRefStorage.sol"; import {UnstructuredStorage} from "../lib/UnstructuredStorage.sol"; /// @author kovalgek -/// @notice Extends the ERC20 functionality that allows the bridge to mint/burn shares -interface IERC20BridgedShares is IERC20 { - /// @notice Returns bridge which can mint and burn shares on L2 +/// @notice Extends the ERC20 functionality that allows the bridge to unwrap token. +interface IBridgeUnwrappable { + /// @notice Returns bridge which can unwrap token on L2. function L2_ERC20_TOKEN_BRIDGE() external view returns (address); - /// @notice Creates amount_ shares and assigns them to account_, increasing the total shares supply - /// @param account_ An address of the account to mint shares - /// @param amount_ An amount of shares to mint - function bridgeMintShares(address account_, uint256 amount_) external; - - /// @notice Destroys amount_ shares from account_, reducing the total shares supply - /// @param account_ An address of the account to burn shares - /// @param amount_ An amount of shares to burn - function bridgeBurnShares(address account_, uint256 amount_) external; - /// @notice Exchanges wrapper token to wrappable one. Can be called by bridge only. /// @param account_ An address of the account to unwrap token for /// @param amount_ amount of wrapper token to uwrap in exchange for wrappable. @@ -36,12 +26,12 @@ interface IERC20BridgedShares is IERC20 { /// @author kovalgek /// @notice Rebasable token that wraps/unwraps non-rebasable token and allow to mint/burn tokens by bridge. -contract ERC20RebasableBridged is IERC20, IERC20Wrapper, IERC20BridgedShares, ERC20Metadata { +contract ERC20RebasableBridged is IERC20, IERC20Wrapper, IBridgeUnwrappable, ERC20Metadata { using SafeERC20 for IERC20; using UnstructuredRefStorage for bytes32; using UnstructuredStorage for bytes32; - /// @inheritdoc IERC20BridgedShares + /// @inheritdoc IBridgeUnwrappable address public immutable L2_ERC20_TOKEN_BRIDGE; /// @notice Contract of non-rebasable token to wrap from. @@ -93,21 +83,11 @@ contract ERC20RebasableBridged is IERC20, IERC20Wrapper, IERC20BridgedShares, ER return _unwrap(msg.sender, tokenAmount_); } - /// @inheritdoc IERC20BridgedShares + /// @inheritdoc IBridgeUnwrappable function bridgeUnwrap(address account_, uint256 amount_) external onlyBridge returns (uint256) { return _unwrap(account_, amount_); } - /// @inheritdoc IERC20BridgedShares - function bridgeMintShares(address account_, uint256 amount_) external onlyBridge { - _mintShares(account_, amount_); - } - - /// @inheritdoc IERC20BridgedShares - function bridgeBurnShares(address account_, uint256 amount_) external onlyBridge { - _burnShares(account_, amount_); - } - /// @inheritdoc IERC20 function allowance(address owner, address spender) external view returns (uint256) { return _getTokenAllowance()[owner][spender]; From 88cf3820c479f3d07bb8ad03442b3f9ecda53ce3 Mon Sep 17 00:00:00 2001 From: Anton Kovalchuk Date: Wed, 1 May 2024 17:32:04 +0400 Subject: [PATCH 099/148] remove tests for bridgeBurnShares and bridgeMintShares --- test/token/ERC20Permit.unit.test.ts | 3 - .../ERC20RebasableBridgedPermit.unit.test.ts | 252 +----------------- 2 files changed, 9 insertions(+), 246 deletions(-) diff --git a/test/token/ERC20Permit.unit.test.ts b/test/token/ERC20Permit.unit.test.ts index b4a10630..9ce73cc4 100644 --- a/test/token/ERC20Permit.unit.test.ts +++ b/test/token/ERC20Permit.unit.test.ts @@ -444,9 +444,6 @@ async function tokenProxied( holder ); - const premintShares = wei.toBigNumber(wei`100 ether`); - await rebasableProxied.connect(owner).bridgeMintShares(holder.address, premintShares); - return rebasableProxied; } diff --git a/test/token/ERC20RebasableBridgedPermit.unit.test.ts b/test/token/ERC20RebasableBridgedPermit.unit.test.ts index ec83aa02..26b3c98d 100644 --- a/test/token/ERC20RebasableBridgedPermit.unit.test.ts +++ b/test/token/ERC20RebasableBridgedPermit.unit.test.ts @@ -214,7 +214,7 @@ unit("ERC20RebasableBridgedPermit", ctxFactory) assert.equalBN(await rebasableProxied.sharesOf(user1.address), user1Shares); assert.equalBN(await rebasableProxied.balanceOf(user1.address), user1Tokens); - assert.equalBN(await wrappedToken.balanceOf(rebasableProxied.address), user1Shares); + assert.equalBN(await wrappedToken.balanceOf(rebasableProxied.address), premintShares.add(user1Shares)); // common state changes assert.equalBN(await rebasableProxied.getTotalShares(), BigNumber.from(premintShares).add(user1Shares)); @@ -238,7 +238,7 @@ unit("ERC20RebasableBridgedPermit", ctxFactory) assert.equalBN(await rebasableProxied.sharesOf(user2.address), user2Shares); assert.equalBN(await rebasableProxied.balanceOf(user2.address), user2Tokens); - assert.equalBN(await wrappedToken.balanceOf(rebasableProxied.address), BigNumber.from(user1Shares).add(user2Shares)); + assert.equalBN(await wrappedToken.balanceOf(rebasableProxied.address), premintShares.add(BigNumber.from(user1Shares).add(user2Shares))); // common state changes assert.equalBN(await rebasableProxied.getTotalShares(), BigNumber.from(premintShares).add(user1Shares).add(user2Shares)); @@ -283,7 +283,7 @@ unit("ERC20RebasableBridgedPermit", ctxFactory) assert.equalBN(await rebasableProxied.sharesOf(user1.address), user1Shares); assert.equalBN(await rebasableProxied.balanceOf(user1.address), user1Tokens); - assert.equalBN(await wrappedToken.balanceOf(rebasableProxied.address), user1Shares); + assert.equalBN(await wrappedToken.balanceOf(rebasableProxied.address), premintShares.add(user1Shares)); // common state changes assert.equalBN(await rebasableProxied.getTotalShares(), premintShares.add(user1Shares)); @@ -309,7 +309,7 @@ unit("ERC20RebasableBridgedPermit", ctxFactory) assert.equalBN(await rebasableProxied.sharesOf(user2.address), user2Shares); assert.equalBN(await rebasableProxied.balanceOf(user2.address), user2Tokens); - assert.equalBN(await wrappedToken.balanceOf(rebasableProxied.address), BigNumber.from(user1Shares).add(user2Shares)); + assert.equalBN(await wrappedToken.balanceOf(rebasableProxied.address), premintShares.add(BigNumber.from(user1Shares).add(user2Shares))); // common state changes assert.equalBN(await rebasableProxied.getTotalShares(), premintShares.add(user1Shares).add(user2Shares)); @@ -446,7 +446,7 @@ unit("ERC20RebasableBridgedPermit", ctxFactory) assert.equalBN(await rebasableProxied.sharesOf(user1.address), user1Shares); assert.equalBN(await rebasableProxied.balanceOf(user1.address), user1Tokens); - assert.equalBN(await wrappedToken.balanceOf(rebasableProxied.address), user1Shares); + assert.equalBN(await wrappedToken.balanceOf(rebasableProxied.address), premintShares.add(user1Shares)); // common state changes assert.equalBN(await rebasableProxied.getTotalShares(), premintShares.add(user1Shares)); @@ -472,118 +472,13 @@ unit("ERC20RebasableBridgedPermit", ctxFactory) assert.equalBN(await rebasableProxied.sharesOf(user2.address), user2Shares); assert.equalBN(await rebasableProxied.balanceOf(user2.address), user2Tokens); - assert.equalBN(await wrappedToken.balanceOf(rebasableProxied.address), BigNumber.from(user1Shares).add(user2Shares)); + assert.equalBN(await wrappedToken.balanceOf(rebasableProxied.address), premintShares.add(BigNumber.from(user1Shares).add(user2Shares))); // common state changes assert.equalBN(await rebasableProxied.getTotalShares(), premintShares.add(user1Shares).add(user2Shares)); assert.equalBN(await rebasableProxied.totalSupply(), totalSupply.add(user1Tokens).add(user2Tokens)); }) - .test("bridgeMintShares() :: happy path", async (ctx) => { - - const { rebasableProxied } = ctx.contracts; - const { user1, user2, owner, zero } = ctx.accounts; - const { tokenRate, tenPowDecimals, premintShares, premintTokens } = ctx.constants; - - assert.equalBN(await rebasableProxied.getTotalShares(), premintShares); - assert.equalBN(await rebasableProxied.totalSupply(), premintTokens); - - // user1 - const user1SharesToMint = wei`44 ether`; - const user1TokensMinted = tokenRate.mul(user1SharesToMint).div(tenPowDecimals); - - assert.equalBN(await rebasableProxied.sharesOf(user1.address), 0); - assert.equalBN(await rebasableProxied.balanceOf(user1.address), 0); - - const tx0 = await rebasableProxied.connect(owner).bridgeMintShares(user1.address, user1SharesToMint); - await assert.emits(rebasableProxied, tx0, "Transfer", [zero.address, user1.address, user1TokensMinted]); - await assert.emits(rebasableProxied, tx0, "TransferShares", [zero.address, user1.address, user1SharesToMint]); - - assert.equalBN(await rebasableProxied.sharesOf(user1.address), user1SharesToMint); - assert.equalBN(await rebasableProxied.balanceOf(user1.address), user1TokensMinted); - - // common state changes - assert.equalBN(await rebasableProxied.getTotalShares(), premintShares.add(user1SharesToMint)); - assert.equalBN(await rebasableProxied.totalSupply(), premintTokens.add(user1TokensMinted)); - - // // user2 - const user2SharesToMint = wei`75 ether`; - const user2TokensMinted = tokenRate.mul(user2SharesToMint).div(tenPowDecimals); - - assert.equalBN(await rebasableProxied.sharesOf(user2.address), 0); - assert.equalBN(await rebasableProxied.balanceOf(user2.address), 0); - - const tx1 = await rebasableProxied.connect(owner).bridgeMintShares(user2.address, user2SharesToMint); - await assert.emits(rebasableProxied, tx1, "Transfer", [zero.address, user2.address, user2TokensMinted]); - await assert.emits(rebasableProxied, tx1, "TransferShares", [zero.address, user2.address, user2SharesToMint]); - - assert.equalBN(await rebasableProxied.sharesOf(user2.address), user2SharesToMint); - assert.equalBN(await rebasableProxied.balanceOf(user2.address), user2TokensMinted); - - // common state changes - assert.equalBN(await rebasableProxied.getTotalShares(), premintShares.add(user1SharesToMint).add(user2SharesToMint)); - assert.equalBN(await rebasableProxied.totalSupply(), premintTokens.add(user1TokensMinted).add(user2TokensMinted)); - }) - - .test("bridgeBurnShares() :: happy path", async (ctx) => { - - const { rebasableProxied } = ctx.contracts; - const { user1, user2, owner } = ctx.accounts; - const { tokenRate, tenPowDecimals, premintShares, premintTokens } = ctx.constants; - - assert.equalBN(await rebasableProxied.getTotalShares(), premintShares); - assert.equalBN(await rebasableProxied.totalSupply(), premintTokens); - - // user1 - const user1SharesToMint = wei`12 ether`; - const user1TokensMinted = tokenRate.mul(user1SharesToMint).div(tenPowDecimals); - - const user1SharesToBurn = wei`4 ether`; - const user1TokensBurned = tokenRate.mul(user1SharesToBurn).div(tenPowDecimals); - - const user1Shares = BigNumber.from(user1SharesToMint).sub(user1SharesToBurn); - const user1Tokens = user1TokensMinted.sub(user1TokensBurned); - - assert.equalBN(await rebasableProxied.sharesOf(user1.address), 0); - assert.equalBN(await rebasableProxied.balanceOf(user1.address), 0); - - await rebasableProxied.connect(owner).bridgeMintShares(user1.address, user1SharesToMint); - assert.equalBN(await rebasableProxied.sharesOf(user1.address), user1SharesToMint); - assert.equalBN(await rebasableProxied.balanceOf(user1.address), user1TokensMinted); - - await rebasableProxied.connect(owner).bridgeBurnShares(user1.address, user1SharesToBurn); - assert.equalBN(await rebasableProxied.sharesOf(user1.address), user1Shares); - assert.equalBN(await rebasableProxied.balanceOf(user1.address), user1Tokens); - - // common state changes - assert.equalBN(await rebasableProxied.getTotalShares(), premintShares.add(user1Shares)); - assert.equalBN(await rebasableProxied.totalSupply(), premintTokens.add(user1Tokens)); - - // // user2 - const user2SharesToMint = wei`64 ether`; - const user2TokensMinted = tokenRate.mul(user2SharesToMint).div(tenPowDecimals); - - const user2SharesToBurn = wei`22 ether`; - const user2TokensBurned = tokenRate.mul(user2SharesToBurn).div(tenPowDecimals); - - const user2Shares = BigNumber.from(user2SharesToMint).sub(user2SharesToBurn); - const user2Tokens = user2TokensMinted.sub(user2TokensBurned); - - assert.equalBN(await rebasableProxied.sharesOf(user2.address), 0); - assert.equalBN(await rebasableProxied.balanceOf(user2.address), 0); - - await rebasableProxied.connect(owner).bridgeMintShares(user2.address, user2SharesToMint); - assert.equalBN(await rebasableProxied.sharesOf(user2.address), user2SharesToMint); - assert.equalBN(await rebasableProxied.balanceOf(user2.address), user2TokensMinted); - await rebasableProxied.connect(owner).bridgeBurnShares(user2.address, user2SharesToBurn); - assert.equalBN(await rebasableProxied.sharesOf(user2.address), user2Shares); - assert.equalBN(await rebasableProxied.balanceOf(user2.address), user2Tokens); - - // common state changes - assert.equalBN(await rebasableProxied.getTotalShares(), premintShares.add(user1Shares).add(user2Shares)); - assert.equalBN(await rebasableProxied.totalSupply(), premintTokens.add(user1Tokens).add(user2Tokens)); - }) - .test("approve() :: happy path", async (ctx) => { const { rebasableProxied } = ctx.contracts; const { holder, spender } = ctx.accounts; @@ -1125,137 +1020,6 @@ unit("ERC20RebasableBridgedPermit", ctxFactory) tokensAmountToTransfer ); }) - - .test("bridgeMint() :: not owner", async (ctx) => { - const { rebasableProxied } = ctx.contracts; - const { stranger } = ctx.accounts; - - await assert.revertsWith( - rebasableProxied - .connect(stranger) - .bridgeMintShares(stranger.address, wei`1000 ether`), - "ErrorNotBridge()" - ); - }) - - .group([wei`1000 ether`, "0"], (mintAmount) => [ - `bridgeMint() :: amount is ${mintAmount} wei`, - async (ctx) => { - const { rebasableProxied } = ctx.contracts; - const { premintShares } = ctx.constants; - const { recipient, owner, zero } = ctx.accounts; - - // validate balance before mint - assert.equalBN(await rebasableProxied.balanceOf(recipient.address), 0); - - // validate total supply before mint - assert.equalBN(await rebasableProxied.getTotalShares(), premintShares); - - // mint tokens - const tx = await rebasableProxied - .connect(owner) - .bridgeMintShares(recipient.address, mintAmount); - - // validate Transfer event was emitted - const mintAmountInTokens = await rebasableProxied.getTokensByShares(mintAmount); - await assert.emits(rebasableProxied, tx, "Transfer", [ - zero.address, - recipient.address, - mintAmountInTokens, - ]); - await assert.emits(rebasableProxied, tx, "TransferShares", [ - zero.address, - recipient.address, - mintAmount, - ]); - - // validate balance was updated - assert.equalBN( - await rebasableProxied.sharesOf(recipient.address), - mintAmount - ); - - // validate total supply was updated - assert.equalBN( - await rebasableProxied.getTotalShares(), - premintShares.add(mintAmount) - ); - }, - ]) - - .test("bridgeBurn() :: not owner", async (ctx) => { - const { rebasableProxied } = ctx.contracts; - const { holder, stranger } = ctx.accounts; - - await assert.revertsWith( - rebasableProxied.connect(stranger).bridgeBurnShares(holder.address, wei`100 ether`), - "ErrorNotBridge()" - ); - }) - - .test("bridgeBurn() :: amount exceeds balance", async (ctx) => { - const { rebasableProxied } = ctx.contracts; - const { owner, stranger } = ctx.accounts; - - // validate stranger has no tokens - assert.equalBN(await rebasableProxied.balanceOf(stranger.address), 0); - - await assert.revertsWith( - rebasableProxied.connect(owner).bridgeBurnShares(stranger.address, wei`100 ether`), - "ErrorNotEnoughBalance()" - ); - }) - - .group([wei`10 ether`, "0"], (burnAmount) => [ - `bridgeBurn() :: amount is ${burnAmount} wei`, - async (ctx) => { - const { rebasableProxied } = ctx.contracts; - const { premintShares } = ctx.constants; - const { owner, holder } = ctx.accounts; - - // validate balance before mint - assert.equalBN(await rebasableProxied.sharesOf(holder.address), premintShares); - - // validate total supply before mint - assert.equalBN(await rebasableProxied.getTotalShares(), premintShares); - - // burn tokens - const tx = await rebasableProxied - .connect(owner) - .bridgeBurnShares(holder.address, burnAmount); - - const burnTokenAmount = await rebasableProxied.getTokensByShares(burnAmount); - - // validate Transfer event was emitted - await assert.emits(rebasableProxied, tx, "Transfer", [ - holder.address, - hre.ethers.constants.AddressZero, - burnTokenAmount, - ]); - - await assert.emits(rebasableProxied, tx, "TransferShares", [ - holder.address, - hre.ethers.constants.AddressZero, - burnAmount - ]); - - const expectedBalanceAndTotalSupply = premintShares - .sub(burnAmount); - - // validate balance was updated - assert.equalBN( - await rebasableProxied.sharesOf(holder.address), - expectedBalanceAndTotalSupply - ); - - // validate total supply was updated - assert.equalBN( - await rebasableProxied.getTotalShares(), - expectedBalanceAndTotalSupply - ); - }, - ]) - .run(); async function ctxFactory() { @@ -1321,7 +1085,9 @@ async function ctxFactory() { owner.address, ); - await rebasableProxied.connect(owner).bridgeMintShares(holder.address, premintShares); + await wrappedToken.connect(owner).bridgeMint(holder.address, premintTokens); + await wrappedToken.connect(holder).approve(rebasableProxied.address, premintShares); + await rebasableProxied.connect(holder).wrap(premintShares); await hre.network.provider.request({ method: "hardhat_impersonateAccount", From d49322a1ccbc35f29c4813a17597d8759aa20bb9 Mon Sep 17 00:00:00 2001 From: Anton Kovalchuk Date: Thu, 2 May 2024 02:44:17 +0200 Subject: [PATCH 100/148] add bridgeWrap, setup infinite allowance l2 bridge for bridged token, refactor finalize deposit --- .../optimism/L2ERC20ExtendedTokensBridge.sol | 34 ++++++------ contracts/token/ERC20RebasableBridged.sol | 53 ++++++++++++------- 2 files changed, 52 insertions(+), 35 deletions(-) diff --git a/contracts/optimism/L2ERC20ExtendedTokensBridge.sol b/contracts/optimism/L2ERC20ExtendedTokensBridge.sol index 93dfa0f8..63e0b48f 100644 --- a/contracts/optimism/L2ERC20ExtendedTokensBridge.sol +++ b/contracts/optimism/L2ERC20ExtendedTokensBridge.sol @@ -59,7 +59,7 @@ contract L2ERC20ExtendedTokensBridge is /// @notice Initializes the contract from scratch. /// @param admin_ Address of the account to grant the DEFAULT_ADMIN_ROLE function initialize(address admin_) external { - _initializeContractVersionTo(2); + _initializeExtendedTokensBridge(); _initializeBridgingManager(admin_); } @@ -68,7 +68,7 @@ contract L2ERC20ExtendedTokensBridge is if(!_isBridgingManagerInitialized()) { revert ErrorBridgingManagerIsNotInitialized(); } - _initializeContractVersionTo(2); + _initializeExtendedTokensBridge(); } /// @inheritdoc IL2ERC20Bridge @@ -131,6 +131,12 @@ contract L2ERC20ExtendedTokensBridge is emit DepositFinalized(l1Token_, l2Token_, from_, to_, depositedL2TokenAmount, depositData.data); } + function _initializeExtendedTokensBridge() internal { + _initializeContractVersionTo(2); + // used for `bridgeWrap` call to succeed in the `_mintTokens` method + IERC20(L2_TOKEN_NON_REBASABLE).safeIncreaseAllowance(L2_TOKEN_REBASABLE, type(uint256).max); + } + /// @notice Performs the logic for withdrawals by burning the token and informing /// the L1 token Gateway of the withdrawal /// @param l2Token_ Address of L2 token where withdrawal was initiated. @@ -168,16 +174,16 @@ contract L2ERC20ExtendedTokensBridge is address to_, uint256 amount_ ) internal returns (uint256) { + uint256 tokenAmount = amount_; if(l2Token_ == L2_TOKEN_REBASABLE) { IERC20Bridged(L2_TOKEN_NON_REBASABLE).bridgeMint(address(this), amount_); - IERC20(L2_TOKEN_NON_REBASABLE).safeIncreaseAllowance(l2Token_, amount_); if (amount_ != 0) { - ERC20RebasableBridged(l2Token_).wrap(amount_); + tokenAmount = ERC20RebasableBridged(l2Token_).bridgeWrap(to_, amount_); } - return ERC20RebasableBridged(l2Token_).transferShares(to_, amount_); + } else { + IERC20Bridged(l2Token_).bridgeMint(to_, amount_); } - IERC20Bridged(l2Token_).bridgeMint(to_, amount_); - return amount_; + return tokenAmount; } /// @notice Unwraps if needed, burns tokens and returns amount of non-rebasable token to withdraw. @@ -190,16 +196,12 @@ contract L2ERC20ExtendedTokensBridge is address from_, uint256 amount_ ) internal returns (uint256) { - if(l2Token_ == L2_TOKEN_REBASABLE) { - uint256 nonRebasableTokenAmount; - if (amount_ != 0) { - nonRebasableTokenAmount = ERC20RebasableBridged(l2Token_).bridgeUnwrap(from_, amount_); - } - IERC20Bridged(L2_TOKEN_NON_REBASABLE).bridgeBurn(from_, nonRebasableTokenAmount); - return nonRebasableTokenAmount; + uint256 nonRebasableTokenAmount = amount_; + if(l2Token_ == L2_TOKEN_REBASABLE && (amount_ != 0)) { + nonRebasableTokenAmount = ERC20RebasableBridged(L2_TOKEN_REBASABLE).bridgeUnwrap(from_, amount_); } - IERC20Bridged(l2Token_).bridgeBurn(from_, amount_); - return amount_; + IERC20Bridged(L2_TOKEN_NON_REBASABLE).bridgeBurn(from_, nonRebasableTokenAmount); + return nonRebasableTokenAmount; } error ErrorSenderNotEOA(); diff --git a/contracts/token/ERC20RebasableBridged.sol b/contracts/token/ERC20RebasableBridged.sol index b21c3381..8ab0d7aa 100644 --- a/contracts/token/ERC20RebasableBridged.sol +++ b/contracts/token/ERC20RebasableBridged.sol @@ -12,26 +12,32 @@ import {UnstructuredRefStorage} from "../lib/UnstructuredRefStorage.sol"; import {UnstructuredStorage} from "../lib/UnstructuredStorage.sol"; /// @author kovalgek -/// @notice Extends the ERC20 functionality that allows the bridge to unwrap token. -interface IBridgeUnwrappable { - /// @notice Returns bridge which can unwrap token on L2. +/// @notice Extends the ERC20 functionality that allows the bridge to wrap/unwrap token. +interface IBridgeWrappable { + /// @notice Returns bridge which can wrap/unwrap token on L2. function L2_ERC20_TOKEN_BRIDGE() external view returns (address); - /// @notice Exchanges wrapper token to wrappable one. Can be called by bridge only. - /// @param account_ An address of the account to unwrap token for - /// @param amount_ amount of wrapper token to uwrap in exchange for wrappable. - /// @return Amount of wrappable token user receives after unwrap. - function bridgeUnwrap(address account_, uint256 amount_) external returns (uint256); + /// @notice Exchanges non-rebaseable token (shares) to rebaseable token. Can be called by bridge only. + /// @param account_ an address of the account to exchange shares for. + /// @param sharesAmount_ amount of non-rebaseable token (shares). + /// @return Amount of rebaseable token. + function bridgeWrap(address account_, uint256 sharesAmount_) external returns (uint256); + + /// @notice Exchanges rebaseable token to non-rebasable (shares). Can be called by bridge only. + /// @param account_ an address of the account to exchange token for. + /// @param tokenAmount_ amount of rebaseable token to uwrap in exchange for non-rebaseable token (shares). + /// @return Amount of non-rebaseable token (shares) user receives after unwrap. + function bridgeUnwrap(address account_, uint256 tokenAmount_) external returns (uint256); } /// @author kovalgek /// @notice Rebasable token that wraps/unwraps non-rebasable token and allow to mint/burn tokens by bridge. -contract ERC20RebasableBridged is IERC20, IERC20Wrapper, IBridgeUnwrappable, ERC20Metadata { +contract ERC20RebasableBridged is IERC20, IERC20Wrapper, IBridgeWrappable, ERC20Metadata { using SafeERC20 for IERC20; using UnstructuredRefStorage for bytes32; using UnstructuredStorage for bytes32; - /// @inheritdoc IBridgeUnwrappable + /// @inheritdoc IBridgeWrappable address public immutable L2_ERC20_TOKEN_BRIDGE; /// @notice Contract of non-rebasable token to wrap from. @@ -70,12 +76,7 @@ contract ERC20RebasableBridged is IERC20, IERC20Wrapper, IBridgeUnwrappable, ERC /// @inheritdoc IERC20Wrapper function wrap(uint256 sharesAmount_) external returns (uint256) { - if (sharesAmount_ == 0) revert ErrorZeroSharesWrap(); - - _mintShares(msg.sender, sharesAmount_); - TOKEN_TO_WRAP_FROM.safeTransferFrom(msg.sender, address(this), sharesAmount_); - - return _getTokensByShares(sharesAmount_); + return _wrap(msg.sender, msg.sender, sharesAmount_); } /// @inheritdoc IERC20Wrapper @@ -83,9 +84,14 @@ contract ERC20RebasableBridged is IERC20, IERC20Wrapper, IBridgeUnwrappable, ERC return _unwrap(msg.sender, tokenAmount_); } - /// @inheritdoc IBridgeUnwrappable - function bridgeUnwrap(address account_, uint256 amount_) external onlyBridge returns (uint256) { - return _unwrap(account_, amount_); + /// @inheritdoc IBridgeWrappable + function bridgeWrap(address account_, uint256 sharesAmount_) external onlyBridge returns (uint256) { + return _wrap(L2_ERC20_TOKEN_BRIDGE, account_, sharesAmount_); + } + + /// @inheritdoc IBridgeWrappable + function bridgeUnwrap(address account_, uint256 tokenAmount_) external onlyBridge returns (uint256) { + return _unwrap(account_, tokenAmount_); } /// @inheritdoc IERC20 @@ -356,6 +362,15 @@ contract ERC20RebasableBridged is IERC20, IERC20Wrapper, IBridgeUnwrappable, ERC _setERC20MetadataSymbol(symbol_); } + function _wrap(address from_, address to_, uint256 sharesAmount_) internal returns (uint256) { + if (sharesAmount_ == 0) revert ErrorZeroSharesWrap(); + + TOKEN_TO_WRAP_FROM.safeTransferFrom(from_, address(this), sharesAmount_); + _mintShares(to_, sharesAmount_); + + return _getTokensByShares(sharesAmount_); + } + function _unwrap(address account_, uint256 tokenAmount_) internal returns (uint256) { if (tokenAmount_ == 0) revert ErrorZeroTokensUnwrap(); From e965a88ac0cfbee10d81c388faae0b8a1f1e5a3c Mon Sep 17 00:00:00 2001 From: Anton Kovalchuk Date: Thu, 2 May 2024 02:59:36 +0200 Subject: [PATCH 101/148] add tests for bridgeWrap --- .../L2ERC20ExtendedTokensBridge.unit.test.ts | 23 +++- .../ERC20RebasableBridgedPermit.unit.test.ts | 122 ++++++++++++++++++ 2 files changed, 139 insertions(+), 6 deletions(-) diff --git a/test/optimism/L2ERC20ExtendedTokensBridge.unit.test.ts b/test/optimism/L2ERC20ExtendedTokensBridge.unit.test.ts index b9133d9e..66d901a0 100644 --- a/test/optimism/L2ERC20ExtendedTokensBridge.unit.test.ts +++ b/test/optimism/L2ERC20ExtendedTokensBridge.unit.test.ts @@ -21,12 +21,23 @@ import { unit("Optimism:: L2ERC20ExtendedTokensBridge", ctxFactory) .test("initial state", async (ctx) => { - assert.equal(await ctx.l2TokenBridge.l1TokenBridge(), ctx.accounts.l1TokenBridgeEOA.address); - assert.equal(await ctx.l2TokenBridge.MESSENGER(), ctx.accounts.l2MessengerStubEOA._address); - assert.equal(await ctx.l2TokenBridge.L1_TOKEN_NON_REBASABLE(), ctx.stubs.l1TokenNonRebasable.address); - assert.equal(await ctx.l2TokenBridge.L1_TOKEN_REBASABLE(), ctx.stubs.l1TokenRebasable.address); - assert.equal(await ctx.l2TokenBridge.L2_TOKEN_NON_REBASABLE(), ctx.stubs.l2TokenNonRebasable.address); - assert.equal(await ctx.l2TokenBridge.L2_TOKEN_REBASABLE(), ctx.stubs.l2TokenRebasable.address); + const { + l2TokenBridge, + accounts: {l1TokenBridgeEOA, l2MessengerStubEOA}, + stubs: { l1TokenNonRebasable, l2TokenNonRebasable, l1TokenRebasable, l2TokenRebasable }, + } = ctx; + + assert.equal(await l2TokenBridge.l1TokenBridge(), l1TokenBridgeEOA.address); + assert.equal(await l2TokenBridge.MESSENGER(), l2MessengerStubEOA._address); + assert.equal(await l2TokenBridge.L1_TOKEN_NON_REBASABLE(), l1TokenNonRebasable.address); + assert.equal(await l2TokenBridge.L1_TOKEN_REBASABLE(), l1TokenRebasable.address); + assert.equal(await l2TokenBridge.L2_TOKEN_NON_REBASABLE(), l2TokenNonRebasable.address); + assert.equal(await l2TokenBridge.L2_TOKEN_REBASABLE(), l2TokenRebasable.address); + + assert.equalBN( + await l2TokenNonRebasable.allowance(l2TokenBridge.address, l2TokenRebasable.address), + hre.ethers.constants.MaxUint256 + ); }) .test("initialize() :: petrified", async (ctx) => { diff --git a/test/token/ERC20RebasableBridgedPermit.unit.test.ts b/test/token/ERC20RebasableBridgedPermit.unit.test.ts index 26b3c98d..17226353 100644 --- a/test/token/ERC20RebasableBridgedPermit.unit.test.ts +++ b/test/token/ERC20RebasableBridgedPermit.unit.test.ts @@ -360,6 +360,128 @@ unit("ERC20RebasableBridgedPermit", ctxFactory) await assert.revertsWith(rebasableProxied.connect(user1).unwrap(wei`4 ether`), "ErrorNotEnoughBalance()"); }) + .test("bridgeWrap() :: revert if not bridge", async (ctx) => { + const { rebasableProxied } = ctx.contracts; + const { user1, user2 } = ctx.accounts; + await assert.revertsWith(rebasableProxied.connect(user1).bridgeWrap(user2.address, 10), "ErrorNotBridge()"); + }) + + .test("bridgeWrap() :: revert if wrap 0 wstETH", async (ctx) => { + const { rebasableProxied } = ctx.contracts; + const { user1, owner } = ctx.accounts; + await assert.revertsWith(rebasableProxied.connect(owner).bridgeWrap(user1.address, 0), "ErrorZeroSharesWrap()"); + }) + + .test("bridgeWrap() :: wrong oracle update time", async (ctx) => { + const { deployer, user1, owner, zero } = ctx.accounts; + const { decimals } = ctx.constants; + + // deploy new implementation to test initial oracle state + const wrappedToken = await new ERC20BridgedPermit__factory(deployer).deploy( + "WsETH Test Token", + "WsETH", + "1", + decimals, + owner.address + ); + const tokenRateOracle = await new TokenRateOracle__factory(deployer).deploy( + zero.address, + owner.address, + zero.address, + 86400, + 86400, + 500 + ); + const rebasableProxied = await new ERC20RebasableBridgedPermit__factory(deployer).deploy( + "", + "symbol", + "1", + 10, + wrappedToken.address, + tokenRateOracle.address, + owner.address + ); + + await wrappedToken.connect(owner).bridgeMint(owner.address, 1000); + await wrappedToken.connect(owner).approve(rebasableProxied.address, 1000); + + await assert.revertsWith( + rebasableProxied.connect(owner).bridgeWrap(user1.address, 5), + "ErrorWrongOracleUpdateTime()" + ); + }) + + .test("bridgeWrap() :: when no balance", async (ctx) => { + const { rebasableProxied, wrappedToken } = ctx.contracts; + const { owner, user1 } = ctx.accounts; + + await wrappedToken.connect(owner).approve(rebasableProxied.address, 1000); + await assert.revertsWith(rebasableProxied.connect(owner).bridgeWrap(user1.address, 2), "ErrorNotEnoughBalance()"); + }) + + .test("bridgeWrap() :: happy path", async (ctx) => { + + const { rebasableProxied, wrappedToken, tokenRateOracle } = ctx.contracts; + const { user1, user2, owner, zero } = ctx.accounts; + const { tokenRate, tenPowDecimals, premintShares } = ctx.constants; + + await wrappedToken.connect(owner).bridgeMint(owner.address, wei`1000 ether`); + await tokenRateOracle.connect(owner).updateRate(tokenRate, 1000); + + const totalSupply = tokenRate.mul(premintShares).div(tenPowDecimals); + + assert.equalBN(await rebasableProxied.getTotalShares(), premintShares); + assert.equalBN(await rebasableProxied.totalSupply(), totalSupply); + + // user1 + const user1Shares = wei`100 ether`; + const user1Tokens = tokenRate.mul(user1Shares).div(tenPowDecimals); + + assert.equalBN(await rebasableProxied.sharesOf(user1.address), 0); + assert.equalBN(await rebasableProxied.balanceOf(user1.address), 0); + + await wrappedToken.connect(owner).bridgeMint(user1.address, user1Tokens); + await wrappedToken.connect(owner).approve(rebasableProxied.address, user1Shares); + + assert.equalBN(await rebasableProxied.connect(owner).callStatic.bridgeWrap(user1.address, user1Shares), user1Tokens); + const tx = await rebasableProxied.connect(owner).bridgeWrap(user1.address, user1Shares); + + await assert.emits(rebasableProxied, tx, "Transfer", [zero.address, user1.address, user1Tokens]); + await assert.emits(rebasableProxied, tx, "TransferShares", [zero.address, user1.address, user1Shares]); + + assert.equalBN(await rebasableProxied.sharesOf(user1.address), user1Shares); + assert.equalBN(await rebasableProxied.balanceOf(user1.address), user1Tokens); + assert.equalBN(await wrappedToken.balanceOf(rebasableProxied.address), premintShares.add(user1Shares)); + + // common state changes + assert.equalBN(await rebasableProxied.getTotalShares(), BigNumber.from(premintShares).add(user1Shares)); + assert.equalBN(await rebasableProxied.totalSupply(), totalSupply.add(user1Tokens)); + + // user2 + assert.equalBN(await rebasableProxied.sharesOf(user2.address), 0); + assert.equalBN(await rebasableProxied.balanceOf(user2.address), 0); + + const user2Shares = wei`50 ether`; + const user2Tokens = tokenRate.mul(user2Shares).div(tenPowDecimals); + + await wrappedToken.connect(owner).bridgeMint(user2.address, user2Tokens); + await wrappedToken.connect(owner).approve(rebasableProxied.address, user2Shares); + + assert.equalBN(await rebasableProxied.connect(owner).callStatic.bridgeWrap(user2.address, user2Shares), user2Tokens); + const tx2 = await rebasableProxied.connect(owner).bridgeWrap(user2.address, user2Shares); + + await assert.emits(rebasableProxied, tx2, "Transfer", [zero.address, user2.address, user2Tokens]); + await assert.emits(rebasableProxied, tx2, "TransferShares", [zero.address, user2.address, user2Shares]); + + assert.equalBN(await rebasableProxied.sharesOf(user2.address), user2Shares); + assert.equalBN(await rebasableProxied.balanceOf(user2.address), user2Tokens); + assert.equalBN(await wrappedToken.balanceOf(rebasableProxied.address), premintShares.add(BigNumber.from(user1Shares).add(user2Shares))); + + // common state changes + assert.equalBN(await rebasableProxied.getTotalShares(), BigNumber.from(premintShares).add(user1Shares).add(user2Shares)); + assert.equalBN(await rebasableProxied.totalSupply(), totalSupply.add(user1Tokens).add(user2Tokens)); + }) + .test("bridgeUnwrap() :: revert if not bridge", async (ctx) => { const { rebasableProxied } = ctx.contracts; const { user1, user2 } = ctx.accounts; From a3abe9115345bec8d004405fbba7568241a10ff7 Mon Sep 17 00:00:00 2001 From: Anton Kovalchuk Date: Thu, 2 May 2024 10:10:28 +0200 Subject: [PATCH 102/148] rename interface --- contracts/token/ERC20RebasableBridged.sol | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/contracts/token/ERC20RebasableBridged.sol b/contracts/token/ERC20RebasableBridged.sol index 8ab0d7aa..3feff0d4 100644 --- a/contracts/token/ERC20RebasableBridged.sol +++ b/contracts/token/ERC20RebasableBridged.sol @@ -13,7 +13,7 @@ import {UnstructuredStorage} from "../lib/UnstructuredStorage.sol"; /// @author kovalgek /// @notice Extends the ERC20 functionality that allows the bridge to wrap/unwrap token. -interface IBridgeWrappable { +interface IBridgeWrapper { /// @notice Returns bridge which can wrap/unwrap token on L2. function L2_ERC20_TOKEN_BRIDGE() external view returns (address); @@ -32,12 +32,12 @@ interface IBridgeWrappable { /// @author kovalgek /// @notice Rebasable token that wraps/unwraps non-rebasable token and allow to mint/burn tokens by bridge. -contract ERC20RebasableBridged is IERC20, IERC20Wrapper, IBridgeWrappable, ERC20Metadata { +contract ERC20RebasableBridged is IERC20, IERC20Wrapper, IBridgeWrapper, ERC20Metadata { using SafeERC20 for IERC20; using UnstructuredRefStorage for bytes32; using UnstructuredStorage for bytes32; - /// @inheritdoc IBridgeWrappable + /// @inheritdoc IBridgeWrapper address public immutable L2_ERC20_TOKEN_BRIDGE; /// @notice Contract of non-rebasable token to wrap from. @@ -84,12 +84,12 @@ contract ERC20RebasableBridged is IERC20, IERC20Wrapper, IBridgeWrappable, ERC20 return _unwrap(msg.sender, tokenAmount_); } - /// @inheritdoc IBridgeWrappable + /// @inheritdoc IBridgeWrapper function bridgeWrap(address account_, uint256 sharesAmount_) external onlyBridge returns (uint256) { return _wrap(L2_ERC20_TOKEN_BRIDGE, account_, sharesAmount_); } - /// @inheritdoc IBridgeWrappable + /// @inheritdoc IBridgeWrapper function bridgeUnwrap(address account_, uint256 tokenAmount_) external onlyBridge returns (uint256) { return _unwrap(account_, tokenAmount_); } From d2bc0a111b58e258996616f9e86cebb013cf8b1e Mon Sep 17 00:00:00 2001 From: Anton Kovalchuk Date: Thu, 2 May 2024 10:11:15 +0200 Subject: [PATCH 103/148] fix comments in few contracts --- contracts/token/ERC20Bridged.sol | 2 +- contracts/token/ERC20BridgedPermit.sol | 2 +- contracts/token/PermitExtension.sol | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/contracts/token/ERC20Bridged.sol b/contracts/token/ERC20Bridged.sol index ec1f1bfd..47969ec8 100644 --- a/contracts/token/ERC20Bridged.sol +++ b/contracts/token/ERC20Bridged.sol @@ -33,7 +33,7 @@ contract ERC20Bridged is IERC20Bridged, ERC20Core, ERC20Metadata { /// @param name_ The name of the token /// @param symbol_ The symbol of the token /// @param decimals_ The decimals places of the token - /// @param bridge_ The bridge address which allowd to mint/burn tokens + /// @param bridge_ The bridge address which allows to mint/burn tokens constructor( string memory name_, string memory symbol_, diff --git a/contracts/token/ERC20BridgedPermit.sol b/contracts/token/ERC20BridgedPermit.sol index a9fad757..2f014c75 100644 --- a/contracts/token/ERC20BridgedPermit.sol +++ b/contracts/token/ERC20BridgedPermit.sol @@ -15,7 +15,7 @@ contract ERC20BridgedPermit is ERC20Bridged, PermitExtension, Versioned { /// @param symbol_ The symbol of the token /// @param version_ The current major version of the signing domain (aka token version) /// @param decimals_ The decimals places of the token - /// @param bridge_ The bridge address which allowd to mint/burn tokens + /// @param bridge_ The bridge address which allows to mint/burn tokens constructor( string memory name_, string memory symbol_, diff --git a/contracts/token/PermitExtension.sol b/contracts/token/PermitExtension.sol index 31d1ef12..ea96ce24 100644 --- a/contracts/token/PermitExtension.sol +++ b/contracts/token/PermitExtension.sol @@ -19,7 +19,7 @@ abstract contract PermitExtension is IERC2612, EIP712 { string version; } - /// @dev user shares slot position. + /// @dev user nonce slot position. bytes32 internal constant NONCE_BY_ADDRESS_POSITION = keccak256("PermitExtension.NONCE_BY_ADDRESS_POSITION"); /// @dev Typehash constant for ERC-2612 (Permit) From 205cd678710bc53d2b1ff5b6ee4eaf7a9c287a3c Mon Sep 17 00:00:00 2001 From: Anton Kovalchuk Date: Wed, 8 May 2024 18:23:09 +0200 Subject: [PATCH 104/148] add unwrapShares method to rebasable roken, add tests --- contracts/token/ERC20RebasableBridged.sol | 11 ++ .../ERC20RebasableBridgedPermit.unit.test.ts | 116 ++++++++++++------ 2 files changed, 91 insertions(+), 36 deletions(-) diff --git a/contracts/token/ERC20RebasableBridged.sol b/contracts/token/ERC20RebasableBridged.sol index 3feff0d4..9fe32537 100644 --- a/contracts/token/ERC20RebasableBridged.sol +++ b/contracts/token/ERC20RebasableBridged.sol @@ -84,6 +84,16 @@ contract ERC20RebasableBridged is IERC20, IERC20Wrapper, IBridgeWrapper, ERC20Me return _unwrap(msg.sender, tokenAmount_); } + /// @notice Exchanges rebaseable token to non-rebasable by providing rebaseable token shares. + /// @param sharesAmount_ amount of rebaseable token shares to unwrap. + /// @return amount of non-rebaseable token user receives after unwrap. + function unwrapShares(uint256 sharesAmount_) external returns (uint256) { + if (sharesAmount_ == 0) revert ErrorZeroSharesUnwrap(); + _burnShares(msg.sender, sharesAmount_); + TOKEN_TO_WRAP_FROM.safeTransfer(msg.sender, sharesAmount_); + return sharesAmount_; + } + /// @inheritdoc IBridgeWrapper function bridgeWrap(address account_, uint256 sharesAmount_) external onlyBridge returns (uint256) { return _wrap(L2_ERC20_TOKEN_BRIDGE, account_, sharesAmount_); @@ -407,6 +417,7 @@ contract ERC20RebasableBridged is IERC20, IERC20Wrapper, IBridgeWrapper, ERC20Me error ErrorZeroSharesWrap(); error ErrorZeroTokensUnwrap(); + error ErrorZeroSharesUnwrap(); error ErrorTokenRateDecimalsIsZero(); error ErrorWrongOracleUpdateTime(); error ErrorTrasferToRebasableContract(); diff --git a/test/token/ERC20RebasableBridgedPermit.unit.test.ts b/test/token/ERC20RebasableBridgedPermit.unit.test.ts index 17226353..6d1b9508 100644 --- a/test/token/ERC20RebasableBridgedPermit.unit.test.ts +++ b/test/token/ERC20RebasableBridgedPermit.unit.test.ts @@ -251,6 +251,50 @@ unit("ERC20RebasableBridgedPermit", ctxFactory) await assert.revertsWith(rebasableProxied.connect(user1).unwrap(0), "ErrorZeroTokensUnwrap()"); }) + .test("unwrap() :: with wrong oracle update time", async (ctx) => { + + const { deployer, user1, owner, zero } = ctx.accounts; + const { decimals } = ctx.constants; + + // deploy new implementation to test initial oracle state + const wrappedToken = await new ERC20BridgedPermit__factory(deployer).deploy( + "WsETH Test Token", + "WsETH", + "1", + decimals, + owner.address + ); + const tokenRateOracle = await new TokenRateOracle__factory(deployer).deploy( + zero.address, + owner.address, + zero.address, + 86400, + 86400, + 500 + ); + const rebasableProxied = await new ERC20RebasableBridgedPermit__factory(deployer).deploy( + "", + "symbol", + "1", + 10, + wrappedToken.address, + tokenRateOracle.address, + owner.address + ); + + await wrappedToken.connect(owner).bridgeMint(user1.address, 1000); + await wrappedToken.connect(user1).approve(rebasableProxied.address, 1000); + + await assert.revertsWith(rebasableProxied.connect(user1).unwrap(5), "ErrorWrongOracleUpdateTime()"); + }) + + .test("unwrap() :: when no balance", async (ctx) => { + const { rebasableProxied } = ctx.contracts; + const { user1 } = ctx.accounts; + + await assert.revertsWith(rebasableProxied.connect(user1).unwrap(wei`4 ether`), "ErrorNotEnoughBalance()"); + }) + .test("unwrap() :: happy path", async (ctx) => { const { rebasableProxied, wrappedToken } = ctx.contracts; @@ -316,48 +360,48 @@ unit("ERC20RebasableBridgedPermit", ctxFactory) assert.equalBN(await rebasableProxied.totalSupply(), totalSupply.add(user1Tokens).add(user2Tokens)); }) - .test("unwrap() :: with wrong oracle update time", async (ctx) => { + .test("unwrapShares() :: revert if unwrap 0 shares", async (ctx) => { + const { rebasableProxied } = ctx.contracts; + const { user1 } = ctx.accounts; + await assert.revertsWith(rebasableProxied.connect(user1).unwrapShares(0), "ErrorZeroSharesUnwrap()"); + }) - const { deployer, user1, owner, zero } = ctx.accounts; - const { decimals } = ctx.constants; + .test("unwrapShares() :: not enough balance", async (ctx) => { + const { rebasableProxied } = ctx.contracts; + const { user1 } = ctx.accounts; + await assert.revertsWith(rebasableProxied.connect(user1).unwrapShares(wei`4 ether`), "ErrorNotEnoughBalance()"); + }) - // deploy new implementation to test initial oracle state - const wrappedToken = await new ERC20BridgedPermit__factory(deployer).deploy( - "WsETH Test Token", - "WsETH", - "1", - decimals, - owner.address - ); - const tokenRateOracle = await new TokenRateOracle__factory(deployer).deploy( - zero.address, - owner.address, - zero.address, - 86400, - 86400, - 500 - ); - const rebasableProxied = await new ERC20RebasableBridgedPermit__factory(deployer).deploy( - "", - "symbol", - "1", - 10, - wrappedToken.address, - tokenRateOracle.address, - owner.address - ); + .test("unwrapShares() :: happy path", async (ctx) => { - await wrappedToken.connect(owner).bridgeMint(user1.address, 1000); - await wrappedToken.connect(user1).approve(rebasableProxied.address, 1000); + const { rebasableProxied, wrappedToken } = ctx.contracts; + const { user1, owner } = ctx.accounts; + const { tokenRate, tenPowDecimals, premintShares } = ctx.constants; - await assert.revertsWith(rebasableProxied.connect(user1).unwrap(5), "ErrorWrongOracleUpdateTime()"); - }) + const totalSupply = BigNumber.from(tokenRate).mul(premintShares).div(tenPowDecimals); - .test("unwrap() :: when no balance", async (ctx) => { - const { rebasableProxied } = ctx.contracts; - const { user1 } = ctx.accounts; + // user1 + const user1SharesToWrap = 10; + const user1SharesToUnwrap = user1SharesToWrap; - await assert.revertsWith(rebasableProxied.connect(user1).unwrap(wei`4 ether`), "ErrorNotEnoughBalance()"); + assert.equalBN(await rebasableProxied.getTotalShares(), premintShares); + assert.equalBN(await rebasableProxied.totalSupply(), totalSupply); + + assert.equalBN(await rebasableProxied.sharesOf(user1.address), 0); + assert.equalBN(await rebasableProxied.balanceOf(user1.address), 0); + + await wrappedToken.connect(owner).bridgeMint(user1.address, user1SharesToWrap); + await wrappedToken.connect(user1).approve(rebasableProxied.address, user1SharesToWrap); + await rebasableProxied.connect(user1).wrap(user1SharesToWrap); + + assert.equalBN(await rebasableProxied.sharesOf(user1.address), user1SharesToWrap); + assert.equalBN(await wrappedToken.balanceOf(rebasableProxied.address), premintShares.add(user1SharesToWrap)); + + await rebasableProxied.connect(user1).unwrapShares(user1SharesToUnwrap); + + assert.equalBN(await rebasableProxied.sharesOf(user1.address), 0); + assert.equalBN(await rebasableProxied.balanceOf(user1.address), 0); + assert.equalBN(await wrappedToken.balanceOf(rebasableProxied.address), premintShares); }) .test("bridgeWrap() :: revert if not bridge", async (ctx) => { From b68c9a55df809f576c0f8ab347b66c4549cb9145 Mon Sep 17 00:00:00 2001 From: Anton Kovalchuk Date: Wed, 8 May 2024 19:33:07 +0200 Subject: [PATCH 105/148] check name and symbol for emptyness during initialization, tests --- contracts/token/ERC20Metadata.sol | 13 +++- test/token/ERC20BridgedPermit.unit.test.ts | 40 ++++++++++- .../ERC20RebasableBridgedPermit.unit.test.ts | 66 +++++++++++++++++-- 3 files changed, 111 insertions(+), 8 deletions(-) diff --git a/contracts/token/ERC20Metadata.sol b/contracts/token/ERC20Metadata.sol index 16bf3e88..9c5526f6 100644 --- a/contracts/token/ERC20Metadata.sol +++ b/contracts/token/ERC20Metadata.sol @@ -58,13 +58,19 @@ contract ERC20Metadata is IERC20Metadata { return _loadDynamicMetadata().symbol; } - /// @dev Sets the name of the token. Might be called only when the name is empty + /// @dev Sets the name of the token. function _setERC20MetadataName(string memory name_) internal { + if (bytes(name_).length == 0) { + revert ErrorNameIsEmpty(); + } _loadDynamicMetadata().name = name_; } - /// @dev Sets the symbol of the token. Might be called only when the symbol is empty + /// @dev Sets the symbol of the token. function _setERC20MetadataSymbol(string memory symbol_) internal { + if (bytes(symbol_).length == 0) { + revert ErrorSymbolIsEmpty(); + } _loadDynamicMetadata().symbol = symbol_; } @@ -83,4 +89,7 @@ contract ERC20Metadata is IERC20Metadata { r.slot := slot } } + + error ErrorNameIsEmpty(); + error ErrorSymbolIsEmpty(); } diff --git a/test/token/ERC20BridgedPermit.unit.test.ts b/test/token/ERC20BridgedPermit.unit.test.ts index 53405b1d..eae4ed6c 100644 --- a/test/token/ERC20BridgedPermit.unit.test.ts +++ b/test/token/ERC20BridgedPermit.unit.test.ts @@ -47,6 +47,44 @@ unit("ERC20BridgedPermit", ctxFactory) ); }) + .test("initialize() :: don't allow to initialize with empty metadata", async (ctx) => { + const { deployer, owner } = ctx.accounts; + const { name, symbol, version } = ctx.constants; + + const l2TokenImpl = await new ERC20BridgedPermit__factory(deployer).deploy( + "wstETH", + "wst", + "1", + 9, + owner.address + ); + + await assert.revertsWith( + new OssifiableProxy__factory(deployer).deploy( + l2TokenImpl.address, + deployer.address, + ERC20BridgedPermit__factory.createInterface().encodeFunctionData("initialize", [ + "", + symbol, + version + ]) + ), + "ErrorNameIsEmpty()" + ); + await assert.revertsWith( + new OssifiableProxy__factory(deployer).deploy( + l2TokenImpl.address, + deployer.address, + ERC20BridgedPermit__factory.createInterface().encodeFunctionData("initialize", [ + name, + "", + version + ]) + ), + "ErrorSymbolIsEmpty()" + ); + }) + .test("initialize() :: don't allow to initialize twice", async (ctx) => { const { deployer, owner, holder } = ctx.accounts; const { name, symbol, version } = ctx.constants; @@ -89,7 +127,7 @@ unit("ERC20BridgedPermit", ctxFactory) // deploy new implementation const l2TokenImpl = await new ERC20BridgedPermit__factory(deployer).deploy( - "", + "name", "Symbol", "1", 9, diff --git a/test/token/ERC20RebasableBridgedPermit.unit.test.ts b/test/token/ERC20RebasableBridgedPermit.unit.test.ts index 6d1b9508..c240e8b8 100644 --- a/test/token/ERC20RebasableBridgedPermit.unit.test.ts +++ b/test/token/ERC20RebasableBridgedPermit.unit.test.ts @@ -70,6 +70,62 @@ unit("ERC20RebasableBridgedPermit", ctxFactory) ); }) + .test("initialize() :: don't allow to initialize with empty metadata", async (ctx) => { + const { deployer, owner, zero } = ctx.accounts; + const { decimals, name, symbol, version } = ctx.constants; + + // deploy new implementation + const wrappedToken = await new ERC20BridgedPermit__factory(deployer).deploy( + "WsETH Test Token", + "WsETH", + "1", + decimals, + owner.address + ); + const tokenRateOracle = await new TokenRateOracle__factory(deployer).deploy( + zero.address, + owner.address, + zero.address, + 86400, + 86400, + 500 + ); + const rebasableTokenImpl = await new ERC20RebasableBridgedPermit__factory(deployer).deploy( + "name", + "symbol", + "1", + 10, + wrappedToken.address, + tokenRateOracle.address, + owner.address + ); + + await assert.revertsWith( + new OssifiableProxy__factory(deployer).deploy( + rebasableTokenImpl.address, + deployer.address, + ERC20RebasableBridgedPermit__factory.createInterface().encodeFunctionData("initialize", [ + "", + symbol, + version + ]) + ), + "ErrorNameIsEmpty()" + ); + await assert.revertsWith( + new OssifiableProxy__factory(deployer).deploy( + rebasableTokenImpl.address, + deployer.address, + ERC20RebasableBridgedPermit__factory.createInterface().encodeFunctionData("initialize", [ + name, + "", + version + ]) + ), + "ErrorSymbolIsEmpty()" + ); + }) + .test("initialize() :: don't allow to initialize twice", async (ctx) => { const { deployer, owner, zero, holder } = ctx.accounts; const { decimals, name, symbol, version } = ctx.constants; @@ -91,7 +147,7 @@ unit("ERC20RebasableBridgedPermit", ctxFactory) 500 ); const rebasableTokenImpl = await new ERC20RebasableBridgedPermit__factory(deployer).deploy( - "", + "name", "symbol", "1", 10, @@ -160,7 +216,7 @@ unit("ERC20RebasableBridgedPermit", ctxFactory) 500 ); const rebasableProxied = await new ERC20RebasableBridgedPermit__factory(deployer).deploy( - "", + "name", "symbol", "1", 10, @@ -273,7 +329,7 @@ unit("ERC20RebasableBridgedPermit", ctxFactory) 500 ); const rebasableProxied = await new ERC20RebasableBridgedPermit__factory(deployer).deploy( - "", + "name", "symbol", "1", 10, @@ -437,7 +493,7 @@ unit("ERC20RebasableBridgedPermit", ctxFactory) 500 ); const rebasableProxied = await new ERC20RebasableBridgedPermit__factory(deployer).deploy( - "", + "name", "symbol", "1", 10, @@ -567,7 +623,7 @@ unit("ERC20RebasableBridgedPermit", ctxFactory) 500 ); const rebasableProxied = await new ERC20RebasableBridgedPermit__factory(deployer).deploy( - "", + "name", "symbol", "1", 10, From 752bb46f6827c704a30427f1d9afd9f30dc5ac4b Mon Sep 17 00:00:00 2001 From: Anton Kovalchuk Date: Wed, 8 May 2024 20:57:13 +0200 Subject: [PATCH 106/148] add comment for CEXes --- contracts/optimism/TokenRateOracle.sol | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/contracts/optimism/TokenRateOracle.sol b/contracts/optimism/TokenRateOracle.sol index c71e506a..ee1876cc 100644 --- a/contracts/optimism/TokenRateOracle.sol +++ b/contracts/optimism/TokenRateOracle.sol @@ -11,7 +11,9 @@ import {Versioned} from "../utils/Versioned.sol"; interface ITokenRateOracle is ITokenRateUpdatable, IChainlinkAggregatorInterface {} /// @author kovalgek -/// @notice Oracle for storing token rate. +/// @notice Oracle for storing and providing token rate. +/// CEXes should fetch the token rate specific to the chain for deposits/withdrawals; +/// otherwise, utilizing the token rate from L1 for L2 transactions might lead to bad debt for the exchange. /// @dev Token rate updates can be delivered from two sources: L1 token rate pusher and L2 bridge. contract TokenRateOracle is CrossDomainEnabled, ITokenRateOracle, Versioned { From f00015b25998ffe4f505cd75679c9c07a2619d21 Mon Sep 17 00:00:00 2001 From: Anton Kovalchuk Date: Wed, 8 May 2024 21:07:29 +0200 Subject: [PATCH 107/148] check maxAllowedTokenRateDeviationPerDay in oracle --- contracts/optimism/TokenRateOracle.sol | 5 +++++ test/optimism/TokenRateOracle.unit.test.ts | 19 +++++++++++++++++++ 2 files changed, 24 insertions(+) diff --git a/contracts/optimism/TokenRateOracle.sol b/contracts/optimism/TokenRateOracle.sol index ee1876cc..c6213821 100644 --- a/contracts/optimism/TokenRateOracle.sol +++ b/contracts/optimism/TokenRateOracle.sol @@ -61,6 +61,7 @@ contract TokenRateOracle is CrossDomainEnabled, ITokenRateOracle, Versioned { /// @param maxAllowedL2ToL1ClockLag_ A time difference between received l1Timestamp and L2 block.timestamp /// when token rate can be considered outdated. /// @param maxAllowedTokenRateDeviationPerDay_ Allowed token rate deviation per day in basic points. + /// Can't be bigger than BASIS_POINT_SCALE. constructor( address messenger_, address l2ERC20TokenBridge_, @@ -69,6 +70,9 @@ contract TokenRateOracle is CrossDomainEnabled, ITokenRateOracle, Versioned { uint256 maxAllowedL2ToL1ClockLag_, uint256 maxAllowedTokenRateDeviationPerDay_ ) CrossDomainEnabled(messenger_) { + if (maxAllowedTokenRateDeviationPerDay_ > BASIS_POINT_SCALE) { + revert ErrorMaxAllowedTokenRateDeviationPerDayBiggerThanBasicPointScale(); + } L2_ERC20_TOKEN_BRIDGE = l2ERC20TokenBridge_; L1_TOKEN_RATE_PUSHER = l1TokenRatePusher_; TOKEN_RATE_OUTDATED_DELAY = tokenRateOutdatedDelay_; @@ -222,4 +226,5 @@ contract TokenRateOracle is CrossDomainEnabled, ITokenRateOracle, Versioned { error ErrorNotBridgeOrTokenRatePusher(); error ErrorL1TimestampExceededAllowedClockLag(uint256 tokenRate_, uint256 rateL1Timestamp_); error ErrorTokenRateIsOutOfRange(uint256 tokenRate_, uint256 rateL1Timestamp_); + error ErrorMaxAllowedTokenRateDeviationPerDayBiggerThanBasicPointScale(); } diff --git a/test/optimism/TokenRateOracle.unit.test.ts b/test/optimism/TokenRateOracle.unit.test.ts index d14de325..7a42d52d 100644 --- a/test/optimism/TokenRateOracle.unit.test.ts +++ b/test/optimism/TokenRateOracle.unit.test.ts @@ -76,6 +76,25 @@ unit("TokenRateOracle", ctxFactory) ); }) + .test("initialize() :: wrong maxAllowedTokenRateDeviationPerDay", async (ctx) => { + const { deployer, bridge, l1TokenBridgeEOA } = ctx.accounts; + const { l2MessengerStub } = ctx.contracts; + + const maxAllowedTokenRateDeviationPerDay = 10001; + + await assert.revertsWith( + new TokenRateOracle__factory(deployer).deploy( + l2MessengerStub.address, + bridge.address, + l1TokenBridgeEOA.address, + 86400, + 86400, + maxAllowedTokenRateDeviationPerDay + ), + "ErrorMaxAllowedTokenRateDeviationPerDayBiggerThanBasicPointScale()" + ); + }) + .test("updateRate() :: called by non-bridge account", async (ctx) => { const { tokenRateOracle } = ctx.contracts; const { stranger } = ctx.accounts; From af2745465e6ae4340cc9230fddb739381effc10f Mon Sep 17 00:00:00 2001 From: Anton Kovalchuk Date: Thu, 9 May 2024 10:19:05 +0200 Subject: [PATCH 108/148] optimisations: skip token rate range if value the same, improve removing observers, save to constant token rate oracle decimals in token --- contracts/lido/TokenRateNotifier.sol | 2 +- contracts/optimism/TokenRateOracle.sol | 2 +- contracts/token/ERC20RebasableBridged.sol | 13 ++++++++----- test/token/ERC20RebasableBridgedPermit.unit.test.ts | 2 +- 4 files changed, 11 insertions(+), 8 deletions(-) diff --git a/contracts/lido/TokenRateNotifier.sol b/contracts/lido/TokenRateNotifier.sol index 13704ef1..1f3d16dc 100644 --- a/contracts/lido/TokenRateNotifier.sol +++ b/contracts/lido/TokenRateNotifier.sol @@ -75,7 +75,7 @@ contract TokenRateNotifier is Ownable, IPostTokenRebaseReceiver { if (observerIndexToRemove == INDEX_NOT_FOUND) { revert ErrorNoObserverToRemove(); } - if (observers.length > 1) { + if (observerIndexToRemove != observers.length - 1) { observers[observerIndexToRemove] = observers[observers.length - 1]; } observers.pop(); diff --git a/contracts/optimism/TokenRateOracle.sol b/contracts/optimism/TokenRateOracle.sol index c6213821..2a8ea2e4 100644 --- a/contracts/optimism/TokenRateOracle.sol +++ b/contracts/optimism/TokenRateOracle.sol @@ -129,7 +129,7 @@ contract TokenRateOracle is CrossDomainEnabled, ITokenRateOracle, Versioned { } /// @dev allow token rate to be within some configurable range that depens on time it wasn't updated. - if (!_isTokenRateWithinAllowedRange(tokenRate_, rateL1Timestamp_)) { + if (tokenRate_ != _getTokenRate() && !_isTokenRateWithinAllowedRange(tokenRate_, rateL1Timestamp_)) { revert ErrorTokenRateIsOutOfRange(tokenRate_, rateL1Timestamp_); } diff --git a/contracts/token/ERC20RebasableBridged.sol b/contracts/token/ERC20RebasableBridged.sol index 9fe32537..14d5c2cb 100644 --- a/contracts/token/ERC20RebasableBridged.sol +++ b/contracts/token/ERC20RebasableBridged.sol @@ -46,6 +46,9 @@ contract ERC20RebasableBridged is IERC20, IERC20Wrapper, IBridgeWrapper, ERC20Me /// @notice Oracle contract used to get token rate for wrapping/unwrapping tokens. ITokenRateOracle public immutable TOKEN_RATE_ORACLE; + /// @notice Decimals of the oracle response. + uint8 public immutable TOKEN_RATE_ORACLE_DECIMALS; + /// @dev token allowance slot position. bytes32 internal constant TOKEN_ALLOWANCE_POSITION = keccak256("ERC20RebasableBridged.TOKEN_ALLOWANCE_POSITION"); @@ -69,8 +72,12 @@ contract ERC20RebasableBridged is IERC20, IERC20Wrapper, IBridgeWrapper, ERC20Me address tokenRateOracle_, address l2ERC20TokenBridge_ ) ERC20Metadata(name_, symbol_, decimals_) { + if (ITokenRateOracle(tokenRateOracle_).decimals() == uint8(0)) { + revert ErrorTokenRateDecimalsIsZero(); + } TOKEN_TO_WRAP_FROM = IERC20(tokenToWrapFrom_); TOKEN_RATE_ORACLE = ITokenRateOracle(tokenRateOracle_); + TOKEN_RATE_ORACLE_DECIMALS = TOKEN_RATE_ORACLE.decimals(); L2_ERC20_TOKEN_BRIDGE = l2ERC20TokenBridge_; } @@ -289,10 +296,6 @@ contract ERC20RebasableBridged is IERC20, IERC20Wrapper, IBridgeWrapper, ERC20Me } function _getTokenRateAndDecimal() internal view returns (uint256, uint256) { - uint8 rateDecimals = TOKEN_RATE_ORACLE.decimals(); - - if (rateDecimals == uint8(0)) revert ErrorTokenRateDecimalsIsZero(); - //slither-disable-next-line unused-return ( /* roundId_ */, @@ -304,7 +307,7 @@ contract ERC20RebasableBridged is IERC20, IERC20Wrapper, IBridgeWrapper, ERC20Me if (updatedAt == 0) revert ErrorWrongOracleUpdateTime(); - return (uint256(answer), uint256(rateDecimals)); + return (uint256(answer), uint256(TOKEN_RATE_ORACLE_DECIMALS)); } /// @dev Creates `amount_` shares and assigns them to `account_`, increasing the total shares supply diff --git a/test/token/ERC20RebasableBridgedPermit.unit.test.ts b/test/token/ERC20RebasableBridgedPermit.unit.test.ts index c240e8b8..7d855d41 100644 --- a/test/token/ERC20RebasableBridgedPermit.unit.test.ts +++ b/test/token/ERC20RebasableBridgedPermit.unit.test.ts @@ -598,7 +598,7 @@ unit("ERC20RebasableBridgedPermit", ctxFactory) const { rebasableProxied } = ctx.contracts; const { user1, owner } = ctx.accounts; - await assert.revertsWith(rebasableProxied.connect(owner).bridgeUnwrap(user1.address, wei`4 ether`), "ErrorNotEnoughBalance()"); + await assert.revertsWith(rebasableProxied.connect(owner).bridgeUnwrap(user1.address, wei`4 ether`), "ErrorNotEnoughBalance()"); }) .test("bridgeUnwrap() :: with wrong oracle update time", async (ctx) => { From ccaa0239fdf825c339fe55110aad8d8c0c9d40ed Mon Sep 17 00:00:00 2001 From: Anton Kovalchuk Date: Thu, 9 May 2024 11:27:38 +0200 Subject: [PATCH 109/148] small improvements: unnecessary spaces, add comments, remove unused functions --- .../optimism/L1ERC20ExtendedTokensBridge.sol | 6 +++++- contracts/optimism/L1LidoTokensBridge.sol | 2 +- .../optimism/L2ERC20ExtendedTokensBridge.sol | 8 ++++---- .../optimism/RebasableAndNonRebasableTokens.sol | 1 - contracts/optimism/TokenRateOracle.sol | 10 +++++----- contracts/token/ERC20Metadata.sol | 1 + contracts/token/ERC20RebasableBridged.sol | 16 +++++++--------- contracts/token/PermitExtension.sol | 2 +- contracts/utils/Versioned.sol | 15 +-------------- 9 files changed, 25 insertions(+), 36 deletions(-) diff --git a/contracts/optimism/L1ERC20ExtendedTokensBridge.sol b/contracts/optimism/L1ERC20ExtendedTokensBridge.sol index ba674b6e..2981577e 100644 --- a/contracts/optimism/L1ERC20ExtendedTokensBridge.sol +++ b/contracts/optimism/L1ERC20ExtendedTokensBridge.sol @@ -156,7 +156,7 @@ abstract contract L1ERC20ExtendedTokensBridge is ) internal returns (uint256) { if (amount_ != 0) { IERC20(l1Token_).safeTransferFrom(from_, address(this), amount_); - if(l1Token_ == L1_TOKEN_REBASABLE) { + if (l1Token_ == L1_TOKEN_REBASABLE) { IERC20(l1Token_).safeIncreaseAllowance(L1_TOKEN_NON_REBASABLE, amount_); return IERC20Wrapper(L1_TOKEN_NON_REBASABLE).wrap(amount_); } @@ -164,6 +164,10 @@ abstract contract L1ERC20ExtendedTokensBridge is return amount_; } + /// @dev Helper that simplifies calling encoding by DepositDataCodec. + /// Encodes token rate, it's L1 timestamp and optional data. + /// @param data_ Optional data to forward to L2. + /// @return encoded data in bytes form. function _encodeInputDepositData(bytes calldata data_) internal view returns (bytes memory) { return DepositDataCodec.encodeDepositData(DepositDataCodec.DepositData({ rate: uint96(_tokenRate()), diff --git a/contracts/optimism/L1LidoTokensBridge.sol b/contracts/optimism/L1LidoTokensBridge.sol index 49941b7c..a331fc87 100644 --- a/contracts/optimism/L1LidoTokensBridge.sol +++ b/contracts/optimism/L1LidoTokensBridge.sol @@ -50,7 +50,7 @@ contract L1LidoTokensBridge is L1ERC20ExtendedTokensBridge, Versioned { /// @notice A function to finalize upgrade to v2 (from v1). function finalizeUpgrade_v2() external { - if(!_isBridgingManagerInitialized()) { + if (!_isBridgingManagerInitialized()) { revert ErrorBridgingManagerIsNotInitialized(); } _initializeContractVersionTo(2); diff --git a/contracts/optimism/L2ERC20ExtendedTokensBridge.sol b/contracts/optimism/L2ERC20ExtendedTokensBridge.sol index 63e0b48f..b89d7e51 100644 --- a/contracts/optimism/L2ERC20ExtendedTokensBridge.sol +++ b/contracts/optimism/L2ERC20ExtendedTokensBridge.sol @@ -65,7 +65,7 @@ contract L2ERC20ExtendedTokensBridge is /// @notice A function to finalize upgrade to v2 (from v1). function finalizeUpgrade_v2() external { - if(!_isBridgingManagerInitialized()) { + if (!_isBridgingManagerInitialized()) { revert ErrorBridgingManagerIsNotInitialized(); } _initializeExtendedTokensBridge(); @@ -119,7 +119,7 @@ contract L2ERC20ExtendedTokensBridge is bytes calldata data_ ) external - whenDepositsEnabled() + whenDepositsEnabled onlySupportedL1L2TokensPair(l1Token_, l2Token_) onlyFromCrossDomainAccount(L1_TOKEN_BRIDGE) { @@ -175,7 +175,7 @@ contract L2ERC20ExtendedTokensBridge is uint256 amount_ ) internal returns (uint256) { uint256 tokenAmount = amount_; - if(l2Token_ == L2_TOKEN_REBASABLE) { + if (l2Token_ == L2_TOKEN_REBASABLE) { IERC20Bridged(L2_TOKEN_NON_REBASABLE).bridgeMint(address(this), amount_); if (amount_ != 0) { tokenAmount = ERC20RebasableBridged(l2Token_).bridgeWrap(to_, amount_); @@ -197,7 +197,7 @@ contract L2ERC20ExtendedTokensBridge is uint256 amount_ ) internal returns (uint256) { uint256 nonRebasableTokenAmount = amount_; - if(l2Token_ == L2_TOKEN_REBASABLE && (amount_ != 0)) { + if (l2Token_ == L2_TOKEN_REBASABLE && (amount_ != 0)) { nonRebasableTokenAmount = ERC20RebasableBridged(L2_TOKEN_REBASABLE).bridgeUnwrap(from_, amount_); } IERC20Bridged(L2_TOKEN_NON_REBASABLE).bridgeBurn(from_, nonRebasableTokenAmount); diff --git a/contracts/optimism/RebasableAndNonRebasableTokens.sol b/contracts/optimism/RebasableAndNonRebasableTokens.sol index 40a8b9f8..a50ff81c 100644 --- a/contracts/optimism/RebasableAndNonRebasableTokens.sol +++ b/contracts/optimism/RebasableAndNonRebasableTokens.sol @@ -71,7 +71,6 @@ contract RebasableAndNonRebasableTokens { _; } - error ErrorUnsupportedL1Token(address l1Token); error ErrorUnsupportedL2Token(address l2Token); error ErrorUnsupportedL1L2TokensPair(address l1Token, address l2Token); error ErrorAccountIsZeroAddress(); diff --git a/contracts/optimism/TokenRateOracle.sol b/contracts/optimism/TokenRateOracle.sol index 2a8ea2e4..9c103d8d 100644 --- a/contracts/optimism/TokenRateOracle.sol +++ b/contracts/optimism/TokenRateOracle.sol @@ -158,7 +158,7 @@ contract TokenRateOracle is CrossDomainEnabled, ITokenRateOracle, Versioned { uint256 topTokenRateLimit = _getTokenRate() * (BASIS_POINT_SCALE + allowedTokenRateDeviation) / BASIS_POINT_SCALE; uint256 bottomTokenRateLimit = 0; - if(allowedTokenRateDeviation <= BASIS_POINT_SCALE) { + if (allowedTokenRateDeviation <= BASIS_POINT_SCALE) { bottomTokenRateLimit = (_getTokenRate() * (BASIS_POINT_SCALE - allowedTokenRateDeviation) / BASIS_POINT_SCALE); } @@ -167,11 +167,11 @@ contract TokenRateOracle is CrossDomainEnabled, ITokenRateOracle, Versioned { newTokenRate_ >= bottomTokenRateLimit; } - function _isCallerBridgeOrMessegerWithTokenRatePusher(address caller_) internal view returns (bool) { - if(caller_ == L2_ERC20_TOKEN_BRIDGE) { + function _isCallerBridgeOrMessengerWithTokenRatePusher(address caller_) internal view returns (bool) { + if (caller_ == L2_ERC20_TOKEN_BRIDGE) { return true; } - if(caller_ == address(MESSENGER) && MESSENGER.xDomainMessageSender() == L1_TOKEN_RATE_PUSHER) { + if (caller_ == address(MESSENGER) && MESSENGER.xDomainMessageSender() == L1_TOKEN_RATE_PUSHER) { return true; } return false; @@ -203,7 +203,7 @@ contract TokenRateOracle is CrossDomainEnabled, ITokenRateOracle, Versioned { } modifier onlyBridgeOrTokenRatePusher() { - if(!_isCallerBridgeOrMessegerWithTokenRatePusher(msg.sender)) { + if (!_isCallerBridgeOrMessengerWithTokenRatePusher(msg.sender)) { revert ErrorNotBridgeOrTokenRatePusher(); } _; diff --git a/contracts/token/ERC20Metadata.sol b/contracts/token/ERC20Metadata.sol index 9c5526f6..7ac0e62a 100644 --- a/contracts/token/ERC20Metadata.sol +++ b/contracts/token/ERC20Metadata.sol @@ -29,6 +29,7 @@ contract ERC20Metadata is IERC20Metadata { } /// @dev Location of the slot with DynamicMetdata + /// The value has a misspelling but without it the contract storage will be broken. bytes32 private constant DYNAMIC_METADATA_SLOT = keccak256("ERC20Metdata.dynamicMetadata"); diff --git a/contracts/token/ERC20RebasableBridged.sol b/contracts/token/ERC20RebasableBridged.sol index 14d5c2cb..fb9327ba 100644 --- a/contracts/token/ERC20RebasableBridged.sol +++ b/contracts/token/ERC20RebasableBridged.sol @@ -243,7 +243,7 @@ contract ERC20RebasableBridged is IERC20, IERC20Wrapper, IBridgeWrapper, ERC20Me ) internal onlyNonZeroAccount(from_) onlyNonZeroAccount(to_) { uint256 sharesToTransfer = _getSharesByTokens(amount_); _transferShares(from_, to_, sharesToTransfer); - _emitTransferEvents(from_, to_, amount_ ,sharesToTransfer); + _emitTransferEvents(from_, to_, amount_, sharesToTransfer); } /// @dev Updates owner_'s allowance for spender_ based on spent amount_. Does not update @@ -286,16 +286,14 @@ contract ERC20RebasableBridged is IERC20, IERC20Wrapper, IBridgeWrapper, ERC20Me } function _getTokensByShares(uint256 sharesAmount_) internal view returns (uint256) { - (uint256 tokensRate, uint256 decimals) = _getTokenRateAndDecimal(); - return (sharesAmount_ * tokensRate) / (10 ** decimals); + return (sharesAmount_ * _getTokenRate()) / (10 ** TOKEN_RATE_ORACLE_DECIMALS); } function _getSharesByTokens(uint256 tokenAmount_) internal view returns (uint256) { - (uint256 tokensRate, uint256 decimals) = _getTokenRateAndDecimal(); - return (tokenAmount_ * (10 ** decimals)) / tokensRate; + return (tokenAmount_ * (10 ** TOKEN_RATE_ORACLE_DECIMALS)) / _getTokenRate(); } - function _getTokenRateAndDecimal() internal view returns (uint256, uint256) { + function _getTokenRate() internal view returns (uint256) { //slither-disable-next-line unused-return ( /* roundId_ */, @@ -307,7 +305,7 @@ contract ERC20RebasableBridged is IERC20, IERC20Wrapper, IBridgeWrapper, ERC20Me if (updatedAt == 0) revert ErrorWrongOracleUpdateTime(); - return (uint256(answer), uint256(TOKEN_RATE_ORACLE_DECIMALS)); + return uint256(answer); } /// @dev Creates `amount_` shares and assigns them to `account_`, increasing the total shares supply @@ -320,7 +318,7 @@ contract ERC20RebasableBridged is IERC20, IERC20Wrapper, IBridgeWrapper, ERC20Me _setTotalShares(_getTotalShares() + amount_); _getShares()[recipient_] = _getShares()[recipient_] + amount_; uint256 tokensAmount = _getTokensByShares(amount_); - _emitTransferEvents(address(0), recipient_, tokensAmount ,amount_); + _emitTransferEvents(address(0), recipient_, tokensAmount, amount_); } /// @dev Destroys `amount_` shares from `account_`, reducing the total shares supply. @@ -335,7 +333,7 @@ contract ERC20RebasableBridged is IERC20, IERC20Wrapper, IBridgeWrapper, ERC20Me _setTotalShares(_getTotalShares() - amount_); _getShares()[account_] = accountShares - amount_; uint256 tokensAmount = _getTokensByShares(amount_); - _emitTransferEvents(account_, address(0), tokensAmount ,amount_); + _emitTransferEvents(account_, address(0), tokensAmount, amount_); } /// @dev Moves `sharesAmount_` shares from `sender_` to `recipient_`. diff --git a/contracts/token/PermitExtension.sol b/contracts/token/PermitExtension.sol index ea96ce24..d0cb90f6 100644 --- a/contracts/token/PermitExtension.sol +++ b/contracts/token/PermitExtension.sol @@ -86,7 +86,7 @@ abstract contract PermitExtension is IERC2612, EIP712 { /// @dev EIP-5267. Returns the fields and values that describe the domain separator /// used by this contract for EIP-712 signature. function eip712Domain() - public + external view virtual returns ( diff --git a/contracts/utils/Versioned.sol b/contracts/utils/Versioned.sol index b47e6901..6fb6b45b 100644 --- a/contracts/utils/Versioned.sol +++ b/contracts/utils/Versioned.sol @@ -3,7 +3,7 @@ pragma solidity 0.8.10; -import {UnstructuredStorage} from "../lib//UnstructuredStorage.sol"; +import {UnstructuredStorage} from "../lib/UnstructuredStorage.sol"; /// @dev A copy of Versioned.sol contract from Lido on Ethereum protocol /// https://github.com/lidofinance/lido-dao/blob/master/contracts/0.8.9/utils/Versioned.sol @@ -36,25 +36,12 @@ contract Versioned { return CONTRACT_VERSION_POSITION.getStorageUint256(); } - function _checkContractVersion(uint256 version) internal view { - uint256 expectedVersion = getContractVersion(); - if (version != expectedVersion) { - revert UnexpectedContractVersion(expectedVersion, version); - } - } - /// @dev Sets the contract version to N. Should be called from the initialize() function. function _initializeContractVersionTo(uint256 version) internal { if (getContractVersion() != 0) revert NonZeroContractVersionOnInit(); _setContractVersion(version); } - /// @dev Updates the contract version. Should be called from a finalizeUpgrade_vN() function. - function _updateContractVersion(uint256 newVersion) internal { - if (newVersion != getContractVersion() + 1) revert InvalidContractVersionIncrement(); - _setContractVersion(newVersion); - } - function _setContractVersion(uint256 version) private { CONTRACT_VERSION_POSITION.setStorageUint256(version); emit ContractVersionSet(version); From 6795332a456d816787f8d1087833b8dde49d51a8 Mon Sep 17 00:00:00 2001 From: Anton Kovalchuk Date: Thu, 9 May 2024 12:29:07 +0200 Subject: [PATCH 110/148] setup token rate oracle decimals in rebasable token constructor --- contracts/token/ERC20RebasableBridged.sol | 7 +++---- contracts/token/ERC20RebasableBridgedPermit.sol | 11 ++++++++++- .../optimism/L2ERC20ExtendedTokensBridge.unit.test.ts | 2 ++ test/token/ERC20Permit.unit.test.ts | 1 + test/token/ERC20RebasableBridgedPermit.unit.test.ts | 7 +++++++ utils/optimism/deploymentAllFromScratch.ts | 1 + utils/testing/contractsFactory.ts | 1 + 7 files changed, 25 insertions(+), 5 deletions(-) diff --git a/contracts/token/ERC20RebasableBridged.sol b/contracts/token/ERC20RebasableBridged.sol index fb9327ba..b1e02f46 100644 --- a/contracts/token/ERC20RebasableBridged.sol +++ b/contracts/token/ERC20RebasableBridged.sol @@ -63,6 +63,7 @@ contract ERC20RebasableBridged is IERC20, IERC20Wrapper, IBridgeWrapper, ERC20Me /// @param decimals_ The decimals places of the token /// @param tokenToWrapFrom_ address of the ERC20 token to wrap /// @param tokenRateOracle_ address of oracle that returns tokens rate + /// @param tokenRateOracleDecimals_ Decimals of the oracle response. /// @param l2ERC20TokenBridge_ The bridge address which allows to mint/burn tokens constructor( string memory name_, @@ -70,14 +71,12 @@ contract ERC20RebasableBridged is IERC20, IERC20Wrapper, IBridgeWrapper, ERC20Me uint8 decimals_, address tokenToWrapFrom_, address tokenRateOracle_, + uint8 tokenRateOracleDecimals_, address l2ERC20TokenBridge_ ) ERC20Metadata(name_, symbol_, decimals_) { - if (ITokenRateOracle(tokenRateOracle_).decimals() == uint8(0)) { - revert ErrorTokenRateDecimalsIsZero(); - } TOKEN_TO_WRAP_FROM = IERC20(tokenToWrapFrom_); TOKEN_RATE_ORACLE = ITokenRateOracle(tokenRateOracle_); - TOKEN_RATE_ORACLE_DECIMALS = TOKEN_RATE_ORACLE.decimals(); + TOKEN_RATE_ORACLE_DECIMALS = tokenRateOracleDecimals_; L2_ERC20_TOKEN_BRIDGE = l2ERC20TokenBridge_; } diff --git a/contracts/token/ERC20RebasableBridgedPermit.sol b/contracts/token/ERC20RebasableBridgedPermit.sol index 4891a53c..8efebd40 100644 --- a/contracts/token/ERC20RebasableBridgedPermit.sol +++ b/contracts/token/ERC20RebasableBridgedPermit.sol @@ -25,9 +25,18 @@ contract ERC20RebasableBridgedPermit is ERC20RebasableBridged, PermitExtension, uint8 decimals_, address tokenToWrapFrom_, address tokenRateOracle_, + uint8 tokenRateOracleDecimals_, address bridge_ ) - ERC20RebasableBridged(name_, symbol_, decimals_, tokenToWrapFrom_, tokenRateOracle_, bridge_) + ERC20RebasableBridged( + name_, + symbol_, + decimals_, + tokenToWrapFrom_, + tokenRateOracle_, + tokenRateOracleDecimals_, + bridge_ + ) PermitExtension(name_, version_) { } diff --git a/test/optimism/L2ERC20ExtendedTokensBridge.unit.test.ts b/test/optimism/L2ERC20ExtendedTokensBridge.unit.test.ts index 66d901a0..56bfd340 100644 --- a/test/optimism/L2ERC20ExtendedTokensBridge.unit.test.ts +++ b/test/optimism/L2ERC20ExtendedTokensBridge.unit.test.ts @@ -1140,6 +1140,7 @@ async function ctxFactory() { decimals, l2TokenNonRebasableStub.address, tokenRateOracle.address, + 18, l2TokenBridgeProxyAddress ); @@ -1319,6 +1320,7 @@ async function getL2TokenBridgeImpl(deployer: SignerWithAddress, l1TokenBridgeEO decimals, l2TokenNonRebasableStub.address, tokenRateOracle.address, + 18, l2TokenBridgeProxyAddress ); diff --git a/test/token/ERC20Permit.unit.test.ts b/test/token/ERC20Permit.unit.test.ts index 9ce73cc4..e81102f3 100644 --- a/test/token/ERC20Permit.unit.test.ts +++ b/test/token/ERC20Permit.unit.test.ts @@ -426,6 +426,7 @@ async function tokenProxied( decimalsToSet, wrappedToken.address, tokenRateOracle.address, + 18, owner.address ); diff --git a/test/token/ERC20RebasableBridgedPermit.unit.test.ts b/test/token/ERC20RebasableBridgedPermit.unit.test.ts index 7d855d41..7c7c91cd 100644 --- a/test/token/ERC20RebasableBridgedPermit.unit.test.ts +++ b/test/token/ERC20RebasableBridgedPermit.unit.test.ts @@ -58,6 +58,7 @@ unit("ERC20RebasableBridgedPermit", ctxFactory) 10, wrappedToken.address, tokenRateOracle.address, + 18, owner.address ); @@ -97,6 +98,7 @@ unit("ERC20RebasableBridgedPermit", ctxFactory) 10, wrappedToken.address, tokenRateOracle.address, + 18, owner.address ); @@ -153,6 +155,7 @@ unit("ERC20RebasableBridgedPermit", ctxFactory) 10, wrappedToken.address, tokenRateOracle.address, + 18, owner.address ); @@ -222,6 +225,7 @@ unit("ERC20RebasableBridgedPermit", ctxFactory) 10, wrappedToken.address, tokenRateOracle.address, + 18, owner.address ); @@ -335,6 +339,7 @@ unit("ERC20RebasableBridgedPermit", ctxFactory) 10, wrappedToken.address, tokenRateOracle.address, + 18, owner.address ); @@ -499,6 +504,7 @@ unit("ERC20RebasableBridgedPermit", ctxFactory) 10, wrappedToken.address, tokenRateOracle.address, + 18, owner.address ); @@ -629,6 +635,7 @@ unit("ERC20RebasableBridgedPermit", ctxFactory) 10, wrappedToken.address, tokenRateOracle.address, + 18, owner.address ); diff --git a/utils/optimism/deploymentAllFromScratch.ts b/utils/optimism/deploymentAllFromScratch.ts index cc4ecd63..4588fd69 100644 --- a/utils/optimism/deploymentAllFromScratch.ts +++ b/utils/optimism/deploymentAllFromScratch.ts @@ -254,6 +254,7 @@ export default function deploymentAll( decimals, expectedL2TokenProxyAddress, expectedL2TokenRateOracleProxyAddress, + decimals, expectedL2TokenBridgeProxyAddress, options?.overrides, ], diff --git a/utils/testing/contractsFactory.ts b/utils/testing/contractsFactory.ts index 3e49a940..b6fb40d9 100644 --- a/utils/testing/contractsFactory.ts +++ b/utils/testing/contractsFactory.ts @@ -97,6 +97,7 @@ export async function erc20RebasableBridgedPermitUnderProxy( decimals, erc20BridgedPermit.address, tokenRateOracle.address, + 18, bridge ); From 560a382162b1fb3d24ed14af0c48200fed8ab478 Mon Sep 17 00:00:00 2001 From: Anton Kovalchuk Date: Thu, 9 May 2024 12:44:46 +0200 Subject: [PATCH 111/148] upgrade comment --- contracts/BridgingManager.sol | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/contracts/BridgingManager.sol b/contracts/BridgingManager.sol index 1af3f449..0588ea60 100644 --- a/contracts/BridgingManager.sol +++ b/contracts/BridgingManager.sol @@ -13,7 +13,9 @@ contract BridgingManager is AccessControl { /// @param isDepositsEnabled Stores the state of the deposits /// @param isWithdrawalsEnabled Stores the state of the withdrawals struct State { - /// @dev DEPRECATED since v2 as bridges have their own code for initialization and storage versioning. + /// @dev This variable is used to determine whether the admin has been initialized or not. + /// At the same time, bridges have their own code for initialization and storage versioning. + /// Therefore, it is recommended to base upgrade logic on new mechanisms since v2. bool isInitialized; bool isDepositsEnabled; bool isWithdrawalsEnabled; From 47c729d3e9c99dbae36ea3acc67fc2a7035cd4d7 Mon Sep 17 00:00:00 2001 From: Anton Kovalchuk Date: Thu, 9 May 2024 13:04:01 +0200 Subject: [PATCH 112/148] check if bridge is zero address --- .../optimism/L1ERC20ExtendedTokensBridge.sol | 4 +++ .../optimism/L2ERC20ExtendedTokensBridge.sol | 4 +++ test/optimism/L1LidoTokensBridge.unit.test.ts | 21 +++++++++++----- .../L2ERC20ExtendedTokensBridge.unit.test.ts | 25 +++++++++++++------ 4 files changed, 40 insertions(+), 14 deletions(-) diff --git a/contracts/optimism/L1ERC20ExtendedTokensBridge.sol b/contracts/optimism/L1ERC20ExtendedTokensBridge.sol index 2981577e..f75046e0 100644 --- a/contracts/optimism/L1ERC20ExtendedTokensBridge.sol +++ b/contracts/optimism/L1ERC20ExtendedTokensBridge.sol @@ -47,6 +47,9 @@ abstract contract L1ERC20ExtendedTokensBridge is l2TokenNonRebasable_, l2TokenRebasable_ ) { + if (l2TokenBridge_ == address(0)) { + revert ErrorZeroAddressL2Bridge(); + } L2_TOKEN_BRIDGE = l2TokenBridge_; } @@ -180,4 +183,5 @@ abstract contract L1ERC20ExtendedTokensBridge is function _tokenRate() virtual internal view returns (uint256); error ErrorSenderNotEOA(); + error ErrorZeroAddressL2Bridge(); } diff --git a/contracts/optimism/L2ERC20ExtendedTokensBridge.sol b/contracts/optimism/L2ERC20ExtendedTokensBridge.sol index b89d7e51..b105ab94 100644 --- a/contracts/optimism/L2ERC20ExtendedTokensBridge.sol +++ b/contracts/optimism/L2ERC20ExtendedTokensBridge.sol @@ -53,6 +53,9 @@ contract L2ERC20ExtendedTokensBridge is l2TokenNonRebasable_, l2TokenRebasable_ ) { + if (l1TokenBridge_ == address(0)) { + revert ErrorZeroAddressL1Bridge(); + } L1_TOKEN_BRIDGE = l1TokenBridge_; } @@ -205,4 +208,5 @@ contract L2ERC20ExtendedTokensBridge is } error ErrorSenderNotEOA(); + error ErrorZeroAddressL1Bridge(); } diff --git a/test/optimism/L1LidoTokensBridge.unit.test.ts b/test/optimism/L1LidoTokensBridge.unit.test.ts index 8871e68c..9febc29d 100644 --- a/test/optimism/L1LidoTokensBridge.unit.test.ts +++ b/test/optimism/L1LidoTokensBridge.unit.test.ts @@ -31,7 +31,7 @@ unit("Optimism :: L1LidoTokensBridge", ctxFactory) .test("initialize() :: petrified", async (ctx) => { const { deployer, l2TokenBridgeEOA } = ctx.accounts; - const l1LidoTokensBridgeImpl = await getL1LidoTokensBridgeImpl(deployer, l2TokenBridgeEOA); + const l1LidoTokensBridgeImpl = await getL1LidoTokensBridgeImpl(deployer, l2TokenBridgeEOA.address); const petrifiedVersionMark = hre.ethers.constants.MaxUint256; assert.equalBN(await l1LidoTokensBridgeImpl.getContractVersion(), petrifiedVersionMark); @@ -42,10 +42,19 @@ unit("Optimism :: L1LidoTokensBridge", ctxFactory) ); }) + .test("initialize() :: zero address L2 bridge", async (ctx) => { + const { deployer } = ctx.accounts; + + await assert.revertsWith( + getL1LidoTokensBridgeImpl(deployer, hre.ethers.constants.AddressZero), + "ErrorZeroAddressL2Bridge()" + ); + }) + .test("initialize() :: don't allow to initialize twice", async (ctx) => { const { deployer, l2TokenBridgeEOA } = ctx.accounts; - const l1LidoTokensBridgeImpl = await getL1LidoTokensBridgeImpl(deployer, l2TokenBridgeEOA); + const l1LidoTokensBridgeImpl = await getL1LidoTokensBridgeImpl(deployer, l2TokenBridgeEOA.address); const l1TokenBridgeProxy = await new OssifiableProxy__factory( deployer @@ -73,7 +82,7 @@ unit("Optimism :: L1LidoTokensBridge", ctxFactory) .test("finalizeUpgrade_v2() :: bridging manager uninitialized", async (ctx) => { const { deployer, l2TokenBridgeEOA } = ctx.accounts; - const l1LidoTokensBridgeImpl = await getL1LidoTokensBridgeImpl(deployer, l2TokenBridgeEOA); + const l1LidoTokensBridgeImpl = await getL1LidoTokensBridgeImpl(deployer, l2TokenBridgeEOA.address); await assert.revertsWith(new OssifiableProxy__factory(deployer).deploy( l1LidoTokensBridgeImpl.address, @@ -94,7 +103,7 @@ unit("Optimism :: L1LidoTokensBridge", ctxFactory) ]) ); - const l1LidoTokensBridgeImpl = await getL1LidoTokensBridgeImpl(deployer, l2TokenBridgeEOA); + const l1LidoTokensBridgeImpl = await getL1LidoTokensBridgeImpl(deployer, l2TokenBridgeEOA.address); await proxy.proxy__upgradeToAndCall( l1LidoTokensBridgeImpl.address, L1LidoTokensBridge__factory.createInterface().encodeFunctionData("finalizeUpgrade_v2"), @@ -1167,7 +1176,7 @@ async function packedTokenRateAndTimestamp(l1Provider: JsonRpcProvider, l1Token: return ethers.utils.hexConcat([stEthPerTokenStr, blockTimestampStr]); } -async function getL1LidoTokensBridgeImpl(deployer: SignerWithAddress, l2TokenBridgeEOA: SignerWithAddress) { +async function getL1LidoTokensBridgeImpl(deployer: SignerWithAddress, l2TokenBridge: string) { const tokenRate = BigNumber.from('1164454276599657236'); const l1MessengerStub = await new CrossDomainMessengerStub__factory( @@ -1202,7 +1211,7 @@ async function getL1LidoTokensBridgeImpl(deployer: SignerWithAddress, l2TokenBri deployer ).deploy( l1MessengerStub.address, - l2TokenBridgeEOA.address, + l2TokenBridge, l1TokenNonRebasableStub.address, l1TokenRebasableStub.address, l2TokenNonRebasableStub.address, diff --git a/test/optimism/L2ERC20ExtendedTokensBridge.unit.test.ts b/test/optimism/L2ERC20ExtendedTokensBridge.unit.test.ts index 56bfd340..794a2288 100644 --- a/test/optimism/L2ERC20ExtendedTokensBridge.unit.test.ts +++ b/test/optimism/L2ERC20ExtendedTokensBridge.unit.test.ts @@ -43,7 +43,7 @@ unit("Optimism:: L2ERC20ExtendedTokensBridge", ctxFactory) .test("initialize() :: petrified", async (ctx) => { const { deployer, l1TokenBridgeEOA } = ctx.accounts; - const l2TokenBridgeImpl = await getL2TokenBridgeImpl(deployer, l1TokenBridgeEOA); + const l2TokenBridgeImpl = await getL2TokenBridgeImpl(deployer, l1TokenBridgeEOA.address); const petrifiedVersionMark = hre.ethers.constants.MaxUint256; assert.equalBN(await l2TokenBridgeImpl.getContractVersion(), petrifiedVersionMark); @@ -54,10 +54,19 @@ unit("Optimism:: L2ERC20ExtendedTokensBridge", ctxFactory) ); }) + .test("initialize() :: zero address L2 bridge", async (ctx) => { + const { deployer } = ctx.accounts; + + await assert.revertsWith( + getL2TokenBridgeImpl(deployer, hre.ethers.constants.AddressZero), + "ErrorZeroAddressL1Bridge()" + ); + }) + .test("initialize() :: don't allow to initialize twice", async (ctx) => { const { deployer, l1TokenBridgeEOA } = ctx.accounts; - const l1LidoTokensBridgeImpl = await getL2TokenBridgeImpl(deployer, l1TokenBridgeEOA); + const l1LidoTokensBridgeImpl = await getL2TokenBridgeImpl(deployer, l1TokenBridgeEOA.address); const l1TokenBridgeProxy = await new OssifiableProxy__factory( deployer @@ -85,7 +94,7 @@ unit("Optimism:: L2ERC20ExtendedTokensBridge", ctxFactory) .test("finalizeUpgrade_v2() :: bridging manager uninitialized", async (ctx) => { const { deployer, l1TokenBridgeEOA } = ctx.accounts; - const l1LidoTokensBridgeImpl = await getL2TokenBridgeImpl(deployer, l1TokenBridgeEOA); + const l1LidoTokensBridgeImpl = await getL2TokenBridgeImpl(deployer, l1TokenBridgeEOA.address); await assert.revertsWith(new OssifiableProxy__factory(deployer).deploy( l1LidoTokensBridgeImpl.address, @@ -106,7 +115,7 @@ unit("Optimism:: L2ERC20ExtendedTokensBridge", ctxFactory) ]) ); - const l1LidoTokensBridgeImpl = await getL2TokenBridgeImpl(deployer, l1TokenBridgeEOA); + const l1LidoTokensBridgeImpl = await getL2TokenBridgeImpl(deployer, l1TokenBridgeEOA.address); await proxy.proxy__upgradeToAndCall( l1LidoTokensBridgeImpl.address, L1LidoTokensBridge__factory.createInterface().encodeFunctionData("finalizeUpgrade_v2"), @@ -1248,14 +1257,14 @@ async function pushTokenRate(ctx: ContextType) { ); } -async function getL2TokenBridgeImpl(deployer: SignerWithAddress, l1TokenBridgeEOA: SignerWithAddress) { +async function getL2TokenBridgeImpl(deployer: SignerWithAddress, l1TokenBridge: string) { const decimals = 18; const exchangeRate = BigNumber.from('1164454276599657236') const l2MessengerStub = await new CrossDomainMessengerStub__factory( deployer ).deploy({ value: wei.toBigNumber(wei`1 ether`) }); - await l2MessengerStub.setXDomainMessageSender(l1TokenBridgeEOA.address); + await l2MessengerStub.setXDomainMessageSender(l1TokenBridge); const [ , @@ -1288,7 +1297,7 @@ async function getL2TokenBridgeImpl(deployer: SignerWithAddress, l1TokenBridgeEO const tokenRateOracleImpl = await new TokenRateOracle__factory(deployer).deploy( l2MessengerStub.address, l2TokenBridgeProxyAddress, - l1TokenBridgeEOA.address, + l1TokenBridge, 86400, 86400, 500 @@ -1328,7 +1337,7 @@ async function getL2TokenBridgeImpl(deployer: SignerWithAddress, l1TokenBridgeEO deployer ).deploy( l2MessengerStub.address, - l1TokenBridgeEOA.address, + l1TokenBridge, l1TokenNonRebasableStub.address, l1TokenRebasableStub.address, l2TokenNonRebasableStub.address, From a5a3da54ae9d7c8e6cbedd0ddf7463f9d44b663b Mon Sep 17 00:00:00 2001 From: Anton Kovalchuk Date: Fri, 10 May 2024 13:17:18 +0200 Subject: [PATCH 113/148] check time and rate in init, fix rate deviation calculation, tests --- contracts/optimism/TokenRateOracle.sol | 18 ++- test/optimism/TokenRateOracle.unit.test.ts | 134 +++++++++++++++++++-- 2 files changed, 136 insertions(+), 16 deletions(-) diff --git a/contracts/optimism/TokenRateOracle.sol b/contracts/optimism/TokenRateOracle.sol index 9c103d8d..4b55af8e 100644 --- a/contracts/optimism/TokenRateOracle.sol +++ b/contracts/optimism/TokenRateOracle.sol @@ -51,6 +51,12 @@ contract TokenRateOracle is CrossDomainEnabled, ITokenRateOracle, Versioned { /// @notice Basic point scale. uint256 private constant BASIS_POINT_SCALE = 1e4; + /// @notice Max allowed token rate value. + uint256 private constant MAX_ALLOWED_TOKEN_RATE = 2*10 ** 18; + + /// @notice Min allowed token rate value. + uint256 private constant MIN_ALLOWED_TOKEN_RATE = 1*10 ** 18; + /// @dev Location of the slot with TokenRateData bytes32 private constant TOKEN_RATE_DATA_SLOT = keccak256("TokenRateOracle.TOKEN_RATE_DATA_SLOT"); @@ -81,6 +87,12 @@ contract TokenRateOracle is CrossDomainEnabled, ITokenRateOracle, Versioned { } function initialize(uint256 tokenRate_, uint256 rateL1Timestamp_) external { + if (tokenRate_ < MIN_ALLOWED_TOKEN_RATE || tokenRate_ > MAX_ALLOWED_TOKEN_RATE) { + revert ErrorTokenRateIsOutOfRange(tokenRate_, rateL1Timestamp_); + } + if (rateL1Timestamp_ > block.timestamp + MAX_ALLOWED_L2_TO_L1_CLOCK_LAG) { + revert ErrorL1TimestampExceededAllowedClockLag(tokenRate_, rateL1Timestamp_); + } _initializeContractVersionTo(1); _setTokenRateAndL1Timestamp(uint192(tokenRate_), uint64(rateL1Timestamp_)); } @@ -134,9 +146,7 @@ contract TokenRateOracle is CrossDomainEnabled, ITokenRateOracle, Versioned { } /// @dev notify that there is a differnce L1 and L2 time. - if (rateL1Timestamp_ > block.timestamp) { - emit TokenRateL1TimestampIsInFuture(tokenRate_, rateL1Timestamp_); - } + if (rateL1Timestamp_ > block.timestamp) emit TokenRateL1TimestampIsInFuture(tokenRate_, rateL1Timestamp_); _setTokenRateAndL1Timestamp(uint192(tokenRate_), uint64(rateL1Timestamp_)); emit RateUpdated(_getTokenRate(), _getRateL1Timestamp()); @@ -153,7 +163,7 @@ contract TokenRateOracle is CrossDomainEnabled, ITokenRateOracle, Versioned { uint256 newTokenRate_, uint256 newRateL1Timestamp_ ) internal view returns (bool) { uint256 rateL1TimestampDiff = newRateL1Timestamp_ - _getRateL1Timestamp(); - uint256 roundedUpNumberOfDays = rateL1TimestampDiff / ONE_DAY_SECONDS + 1; + uint256 roundedUpNumberOfDays = (rateL1TimestampDiff + ONE_DAY_SECONDS - 1) / ONE_DAY_SECONDS; uint256 allowedTokenRateDeviation = roundedUpNumberOfDays * MAX_ALLOWED_TOKEN_RATE_DEVIATION_PER_DAY; uint256 topTokenRateLimit = _getTokenRate() * (BASIS_POINT_SCALE + allowedTokenRateDeviation) / BASIS_POINT_SCALE; diff --git a/test/optimism/TokenRateOracle.unit.test.ts b/test/optimism/TokenRateOracle.unit.test.ts index 7a42d52d..d5b96080 100644 --- a/test/optimism/TokenRateOracle.unit.test.ts +++ b/test/optimism/TokenRateOracle.unit.test.ts @@ -7,6 +7,7 @@ import { tokenRateOracleUnderProxy } from "../../utils/testing/contractsFactory" import { TokenRateOracle__factory, CrossDomainMessengerStub__factory } from "../../typechain"; unit("TokenRateOracle", ctxFactory) + .test("state after init", async (ctx) => { const { tokenRateOracle, l2MessengerStub } = ctx.contracts; const { bridge, l1TokenBridgeEOA } = ctx.accounts; @@ -46,6 +47,7 @@ unit("TokenRateOracle", ctxFactory) .test("initialize() :: petrified version", async (ctx) => { const { deployer, bridge, l1TokenBridgeEOA } = ctx.accounts; const { l2MessengerStub } = ctx.contracts; + const { tokenRate, blockTimestamp } = ctx.constants; const tokenRateOracleImpl = await new TokenRateOracle__factory(deployer).deploy( l2MessengerStub.address, @@ -60,22 +62,65 @@ unit("TokenRateOracle", ctxFactory) assert.equalBN(await tokenRateOracleImpl.getContractVersion(), petrifiedVersionMark); await assert.revertsWith( - tokenRateOracleImpl.initialize(1, 2), + tokenRateOracleImpl.initialize(tokenRate, blockTimestamp), "NonZeroContractVersionOnInit()" ); }) .test("initialize() :: don't allow to initialize twice", async (ctx) => { const { tokenRateOracle } = ctx.contracts; + const { tokenRate, blockTimestamp } = ctx.constants; assert.equalBN(await tokenRateOracle.getContractVersion(), 1); await assert.revertsWith( - tokenRateOracle.initialize(2, 3), + tokenRateOracle.initialize(tokenRate, blockTimestamp), "NonZeroContractVersionOnInit()" ); }) + .test("initialize() :: token rate is out of range", async (ctx) => { + const { deployer, bridge, l1TokenBridgeEOA } = ctx.accounts; + const { l2MessengerStub } = ctx.contracts; + const { blockTimestamp } = ctx.constants; + + const tokenRateOracleImpl = await new TokenRateOracle__factory(deployer).deploy( + l2MessengerStub.address, + bridge.address, + l1TokenBridgeEOA.address, + 86400, + 86400, + 500 + ); + + await assert.revertsWith( + tokenRateOracleImpl.initialize(10, blockTimestamp), + "ErrorTokenRateIsOutOfRange(" + 10 + ", " + blockTimestamp + ")" + ); + }) + + .test("initialize() :: time is wrong", async (ctx) => { + const { deployer, bridge, l1TokenBridgeEOA } = ctx.accounts; + const { l2MessengerStub } = ctx.contracts; + const { tokenRate, blockTimestamp, maxAllowedL2ToL1ClockLag } = ctx.constants; + + const tokenRateOracleImpl = await new TokenRateOracle__factory(deployer).deploy( + l2MessengerStub.address, + bridge.address, + l1TokenBridgeEOA.address, + 86400, + 86400, + 500 + ); + + const wrongTime = blockTimestamp.add(maxAllowedL2ToL1ClockLag).add(20); + + await assert.revertsWith( + tokenRateOracleImpl.initialize(tokenRate, wrongTime), + "ErrorL1TimestampExceededAllowedClockLag(" + tokenRate + ", " + wrongTime + ")" + ); + }) + .test("initialize() :: wrong maxAllowedTokenRateDeviationPerDay", async (ctx) => { const { deployer, bridge, l1TokenBridgeEOA } = ctx.accounts; const { l2MessengerStub } = ctx.contracts; @@ -117,9 +162,9 @@ unit("TokenRateOracle", ctxFactory) .test("updateRate() :: L1 time exceeded allowed L2 clock lag", async (ctx) => { const { tokenRateOracle } = ctx.contracts; const { bridge } = ctx.accounts; - const { tokenRate, blockTimestamp } = ctx.constants; + const { tokenRate, blockTimestamp, maxAllowedL2ToL1ClockLag } = ctx.constants; - const exceededTime = blockTimestamp.add(86400).add(40); // more than one day + const exceededTime = blockTimestamp.add(maxAllowedL2ToL1ClockLag).add(40); // more than maxAllowedL2ToL1ClockLag await assert.revertsWith( tokenRateOracle.connect(bridge).updateRate(tokenRate, exceededTime), "ErrorL1TimestampExceededAllowedClockLag(" + tokenRate + ", " + exceededTime + ")" @@ -155,25 +200,90 @@ unit("TokenRateOracle", ctxFactory) await assert.notEmits(tokenRateOracle, tx1, "RateUpdated"); }) - .test("updateRate() :: token rate is out of range", async (ctx) => { + .test("updateRate() :: token rate is out of range 1 day", async (ctx) => { const { tokenRateOracle } = ctx.contracts; const { bridge } = ctx.accounts; - const { tokenRate, blockTimestamp } = ctx.constants; + const { tokenRate, blockTimestamp, maxAllowedTokenRateDeviationPerDay } = ctx.constants; - const tokenRateTooBig = tokenRate.mul(BigNumber.from('106')).div(BigNumber.from('100')); // 106% - const tokenRateTooSmall = tokenRate.mul(BigNumber.from('94')).div(BigNumber.from('100')); // 94% + const blockTimestampForNextUpdate = blockTimestamp.add(1000); + const tokenRateTooBig = tokenRate.mul( + BigNumber.from('10000') + .add(maxAllowedTokenRateDeviationPerDay) + .add(100) + ) + .div(BigNumber.from('10000')); // 1% more than allowed + const tokenRateTooSmall = tokenRate.mul( + BigNumber.from('10000') + .sub(maxAllowedTokenRateDeviationPerDay) + .sub(100) + ) + .div(BigNumber.from('10000')); // 1% less than allowed + + const tokenRateAllowed = tokenRate.mul( + BigNumber.from('10000') + .add(maxAllowedTokenRateDeviationPerDay) + .sub(100) + ) + .div(BigNumber.from('10000')); // allowed within one day + + await tokenRateOracle.connect(bridge).updateRate(tokenRate, blockTimestamp); - var blockTimestampForNextUpdate = blockTimestamp.add(1000); await assert.revertsWith( tokenRateOracle.connect(bridge).updateRate(tokenRateTooBig, blockTimestampForNextUpdate), "ErrorTokenRateIsOutOfRange(" + tokenRateTooBig + ", " + blockTimestampForNextUpdate + ")" - ) + ); - blockTimestampForNextUpdate = blockTimestampForNextUpdate.add(1000); await assert.revertsWith( tokenRateOracle.connect(bridge).updateRate(tokenRateTooSmall, blockTimestampForNextUpdate), "ErrorTokenRateIsOutOfRange(" + tokenRateTooSmall + ", " + blockTimestampForNextUpdate + ")" + ); + + await tokenRateOracle.connect(bridge).updateRate(tokenRateAllowed, blockTimestampForNextUpdate); + }) + + .test("updateRate() :: token rate is out of range 2 days", async (ctx) => { + const { tokenRateOracle } = ctx.contracts; + const { bridge } = ctx.accounts; + const { tokenRate, blockTimestamp, maxAllowedTokenRateDeviationPerDay } = ctx.constants; + + const tokenRateFirstUpdate = tokenRate.add(10); + + const tokenRateTooBig = tokenRate.mul( + BigNumber.from('10000') + .add(maxAllowedTokenRateDeviationPerDay.mul(2)) + .add(100) + ) + .div(BigNumber.from('10000')); // 1% more than allowed in 2 days + + const tokenRateTooSmall = tokenRate.mul( + BigNumber.from('10000') + .sub(maxAllowedTokenRateDeviationPerDay.mul(2)) + .sub(100) + ) + .div(BigNumber.from('10000')); // 1% less than allowed in 2 days + + const tokenRateSizeDoesMatterAfterAll = tokenRate.mul( + BigNumber.from('10000') + .add(maxAllowedTokenRateDeviationPerDay.mul(2)) + .sub(100) ) + .div(BigNumber.from('10000')); // allowed within 2 days + + + await tokenRateOracle.connect(bridge).updateRate(tokenRateFirstUpdate, blockTimestamp.add(1000)); + + const blockTimestampMoreThanOneDays = blockTimestamp.add(86400 + 2000); + await assert.revertsWith( + tokenRateOracle.connect(bridge).updateRate(tokenRateTooBig, blockTimestampMoreThanOneDays), + "ErrorTokenRateIsOutOfRange(" + tokenRateTooBig + ", " + blockTimestampMoreThanOneDays + ")" + ); + + await assert.revertsWith( + tokenRateOracle.connect(bridge).updateRate(tokenRateTooSmall, blockTimestampMoreThanOneDays), + "ErrorTokenRateIsOutOfRange(" + tokenRateTooSmall + ", " + blockTimestampMoreThanOneDays + ")" + ); + + await tokenRateOracle.connect(bridge).updateRate(tokenRateSizeDoesMatterAfterAll, blockTimestampMoreThanOneDays); }) .test("updateRate() :: happy path called by bridge", async (ctx) => { @@ -259,7 +369,7 @@ unit("TokenRateOracle", ctxFactory) async function ctxFactory() { const tokenRate = BigNumber.from('1164454276599657236'); // value taken from real contact on 23.04.24 const tokenRateOutdatedDelay = BigNumber.from(86400); // 1 day - const maxAllowedL2ToL1ClockLag = BigNumber.from(86400); // 1 day + const maxAllowedL2ToL1ClockLag = BigNumber.from(86400 * 2); // 2 days const maxAllowedTokenRateDeviationPerDay = BigNumber.from(500); // 5% const blockTimestamp = await getBlockTimestamp(0); From eba565f01520c9fb9340ae536c7caee6129b2ca1 Mon Sep 17 00:00:00 2001 From: Anton Kovalchuk Date: Fri, 10 May 2024 13:41:30 +0200 Subject: [PATCH 114/148] change rate and time restrictions during init --- contracts/optimism/TokenRateOracle.sol | 19 ++++++++------- test/optimism/TokenRateOracle.unit.test.ts | 28 ++++++++++++++++------ 2 files changed, 31 insertions(+), 16 deletions(-) diff --git a/contracts/optimism/TokenRateOracle.sol b/contracts/optimism/TokenRateOracle.sol index 4b55af8e..95e78dd5 100644 --- a/contracts/optimism/TokenRateOracle.sol +++ b/contracts/optimism/TokenRateOracle.sol @@ -48,14 +48,14 @@ contract TokenRateOracle is CrossDomainEnabled, ITokenRateOracle, Versioned { /// @notice Decimals of the oracle response. uint8 public constant DECIMALS = 18; - /// @notice Basic point scale. - uint256 private constant BASIS_POINT_SCALE = 1e4; - /// @notice Max allowed token rate value. - uint256 private constant MAX_ALLOWED_TOKEN_RATE = 2*10 ** 18; + uint256 public constant MAX_ALLOWED_TOKEN_RATE = 2*10 ** 18; /// @notice Min allowed token rate value. - uint256 private constant MIN_ALLOWED_TOKEN_RATE = 1*10 ** 18; + uint256 public constant MIN_ALLOWED_TOKEN_RATE = 1*10 ** 18; + + /// @notice Basic point scale. + uint256 private constant BASIS_POINT_SCALE = 1e4; /// @dev Location of the slot with TokenRateData bytes32 private constant TOKEN_RATE_DATA_SLOT = keccak256("TokenRateOracle.TOKEN_RATE_DATA_SLOT"); @@ -88,10 +88,10 @@ contract TokenRateOracle is CrossDomainEnabled, ITokenRateOracle, Versioned { function initialize(uint256 tokenRate_, uint256 rateL1Timestamp_) external { if (tokenRate_ < MIN_ALLOWED_TOKEN_RATE || tokenRate_ > MAX_ALLOWED_TOKEN_RATE) { - revert ErrorTokenRateIsOutOfRange(tokenRate_, rateL1Timestamp_); + revert ErrorTokenRateInitializationIsOutOfAllowedRange(tokenRate_); } - if (rateL1Timestamp_ > block.timestamp + MAX_ALLOWED_L2_TO_L1_CLOCK_LAG) { - revert ErrorL1TimestampExceededAllowedClockLag(tokenRate_, rateL1Timestamp_); + if (rateL1Timestamp_ < block.timestamp || rateL1Timestamp_ > block.timestamp + MAX_ALLOWED_L2_TO_L1_CLOCK_LAG) { + revert ErrorL1TimestampInitializationIsOutOfAllowedRange(rateL1Timestamp_); } _initializeContractVersionTo(1); _setTokenRateAndL1Timestamp(uint192(tokenRate_), uint64(rateL1Timestamp_)); @@ -172,7 +172,6 @@ contract TokenRateOracle is CrossDomainEnabled, ITokenRateOracle, Versioned { bottomTokenRateLimit = (_getTokenRate() * (BASIS_POINT_SCALE - allowedTokenRateDeviation) / BASIS_POINT_SCALE); } - return newTokenRate_ <= topTokenRateLimit && newTokenRate_ >= bottomTokenRateLimit; } @@ -237,4 +236,6 @@ contract TokenRateOracle is CrossDomainEnabled, ITokenRateOracle, Versioned { error ErrorL1TimestampExceededAllowedClockLag(uint256 tokenRate_, uint256 rateL1Timestamp_); error ErrorTokenRateIsOutOfRange(uint256 tokenRate_, uint256 rateL1Timestamp_); error ErrorMaxAllowedTokenRateDeviationPerDayBiggerThanBasicPointScale(); + error ErrorTokenRateInitializationIsOutOfAllowedRange(uint256 tokenRate_); + error ErrorL1TimestampInitializationIsOutOfAllowedRange(uint256 rateL1Timestamp_); } diff --git a/test/optimism/TokenRateOracle.unit.test.ts b/test/optimism/TokenRateOracle.unit.test.ts index d5b96080..53fac096 100644 --- a/test/optimism/TokenRateOracle.unit.test.ts +++ b/test/optimism/TokenRateOracle.unit.test.ts @@ -93,13 +93,21 @@ unit("TokenRateOracle", ctxFactory) 500 ); + const tokenRateMin = await tokenRateOracleImpl.MIN_ALLOWED_TOKEN_RATE(); + const tokenRateMax = await tokenRateOracleImpl.MAX_ALLOWED_TOKEN_RATE(); + + await assert.revertsWith( + tokenRateOracleImpl.initialize(tokenRateMin.sub(1), blockTimestamp), + "ErrorTokenRateInitializationIsOutOfAllowedRange(" + tokenRateMin.sub(1) + ")" + ); + await assert.revertsWith( - tokenRateOracleImpl.initialize(10, blockTimestamp), - "ErrorTokenRateIsOutOfRange(" + 10 + ", " + blockTimestamp + ")" + tokenRateOracleImpl.initialize(tokenRateMax.add(1), blockTimestamp), + "ErrorTokenRateInitializationIsOutOfAllowedRange(" + tokenRateMax.add(1) + ")" ); }) - .test("initialize() :: time is wrong", async (ctx) => { + .test("initialize() :: time is out of init range", async (ctx) => { const { deployer, bridge, l1TokenBridgeEOA } = ctx.accounts; const { l2MessengerStub } = ctx.contracts; const { tokenRate, blockTimestamp, maxAllowedL2ToL1ClockLag } = ctx.constants; @@ -113,11 +121,17 @@ unit("TokenRateOracle", ctxFactory) 500 ); - const wrongTime = blockTimestamp.add(maxAllowedL2ToL1ClockLag).add(20); + const wrongTimeMax = blockTimestamp.add(maxAllowedL2ToL1ClockLag).add(20); + const wrongTimeMin = blockTimestamp.sub(20); + + await assert.revertsWith( + tokenRateOracleImpl.initialize(tokenRate, wrongTimeMax), + "ErrorL1TimestampInitializationIsOutOfAllowedRange(" + wrongTimeMax + ")" + ); await assert.revertsWith( - tokenRateOracleImpl.initialize(tokenRate, wrongTime), - "ErrorL1TimestampExceededAllowedClockLag(" + tokenRate + ", " + wrongTime + ")" + tokenRateOracleImpl.initialize(tokenRate, wrongTimeMin), + "ErrorL1TimestampInitializationIsOutOfAllowedRange(" + wrongTimeMin + ")" ); }) @@ -371,7 +385,7 @@ async function ctxFactory() { const tokenRateOutdatedDelay = BigNumber.from(86400); // 1 day const maxAllowedL2ToL1ClockLag = BigNumber.from(86400 * 2); // 2 days const maxAllowedTokenRateDeviationPerDay = BigNumber.from(500); // 5% - const blockTimestamp = await getBlockTimestamp(0); + const blockTimestamp = await getBlockTimestamp(10); const [deployer, bridge, stranger, l1TokenBridgeEOA] = await hre.ethers.getSigners(); From 2f895fa614ffde07c4978767d1a3fb7902606756 Mon Sep 17 00:00:00 2001 From: Anton Kovalchuk Date: Sat, 11 May 2024 12:57:47 +0200 Subject: [PATCH 115/148] additional test to prove that it is imposible to init afer upgrade --- test/token/ERC20BridgedPermit.unit.test.ts | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/test/token/ERC20BridgedPermit.unit.test.ts b/test/token/ERC20BridgedPermit.unit.test.ts index eae4ed6c..c0aee526 100644 --- a/test/token/ERC20BridgedPermit.unit.test.ts +++ b/test/token/ERC20BridgedPermit.unit.test.ts @@ -187,6 +187,12 @@ unit("ERC20BridgedPermit", ctxFactory) ); assert.equalBN(await erc20BridgedProxied.getContractVersion(), 2); + + // can't initialize after finalizeUpgrade_v2 + await assert.revertsWith( + erc20BridgedProxied.initialize("name", "symbol", "version"), + "NonZeroContractVersionOnInit()" + ); }) .test("approve()", async (ctx) => { From be90d6b1c4fea53deab26ac4d3dd4ad337095e95 Mon Sep 17 00:00:00 2001 From: Anton Kovalchuk Date: Sat, 11 May 2024 13:41:54 +0200 Subject: [PATCH 116/148] check that withdrawTo doesn't allow to withdraw to stETH on L1 address --- .../optimism/L2ERC20ExtendedTokensBridge.sol | 6 ++++ .../L2ERC20ExtendedTokensBridge.unit.test.ts | 28 ++++++++++++++++++- 2 files changed, 33 insertions(+), 1 deletion(-) diff --git a/contracts/optimism/L2ERC20ExtendedTokensBridge.sol b/contracts/optimism/L2ERC20ExtendedTokensBridge.sol index b105ab94..4c77bf02 100644 --- a/contracts/optimism/L2ERC20ExtendedTokensBridge.sol +++ b/contracts/optimism/L2ERC20ExtendedTokensBridge.sol @@ -108,6 +108,11 @@ contract L2ERC20ExtendedTokensBridge is onlyNonZeroAccount(to_) onlySupportedL2Token(l2Token_) { + /// @dev L1_TOKEN_REBASABLE doesn't allow to transfer to itself. + /// To prevent stucking tokens on L1 bridge this check was added. + if (to_ == L1_TOKEN_REBASABLE) { + revert ErrorTransferToL1TokenRebasableContract(); + } _withdrawTo(l2Token_, msg.sender, to_, amount_, l1Gas_, data_); emit WithdrawalInitiated(_getL1Token(l2Token_), l2Token_, msg.sender, to_, amount_, data_); } @@ -209,4 +214,5 @@ contract L2ERC20ExtendedTokensBridge is error ErrorSenderNotEOA(); error ErrorZeroAddressL1Bridge(); + error ErrorTransferToL1TokenRebasableContract(); } diff --git a/test/optimism/L2ERC20ExtendedTokensBridge.unit.test.ts b/test/optimism/L2ERC20ExtendedTokensBridge.unit.test.ts index 794a2288..afe065cf 100644 --- a/test/optimism/L2ERC20ExtendedTokensBridge.unit.test.ts +++ b/test/optimism/L2ERC20ExtendedTokensBridge.unit.test.ts @@ -23,7 +23,7 @@ unit("Optimism:: L2ERC20ExtendedTokensBridge", ctxFactory) .test("initial state", async (ctx) => { const { l2TokenBridge, - accounts: {l1TokenBridgeEOA, l2MessengerStubEOA}, + accounts: { l1TokenBridgeEOA, l2MessengerStubEOA }, stubs: { l1TokenNonRebasable, l2TokenNonRebasable, l1TokenRebasable, l2TokenRebasable }, } = ctx; @@ -768,6 +768,32 @@ unit("Optimism:: L2ERC20ExtendedTokensBridge", ctxFactory) assert.equalBN(await l2TokenNonRebasable.totalSupply(), totalSupplyBefore); }) + .test("withdrawTo() :: sending to L1 stETH address", async (ctx) => { + const { + l2TokenBridge, + accounts: { recipient }, + stubs: { + l1TokenRebasable, + l2TokenRebasable + }, + } = ctx; + + const l1Gas = wei`1 wei`; + const data = "0xdeadbeaf"; + + await assert.revertsWith( + l2TokenBridge + .connect(recipient) + .withdrawTo( + l2TokenRebasable.address, + l1TokenRebasable.address, + 0, + l1Gas, + data), + "ErrorTransferToL1TokenRebasableContract()" + ); + }) + .test("finalizeDeposit() :: deposits disabled", async (ctx) => { const { l2TokenBridge, From b4970315c2376735fcbe431bfdb80beaa66eb275 Mon Sep 17 00:00:00 2001 From: Anton Kovalchuk Date: Sun, 12 May 2024 21:04:20 +0200 Subject: [PATCH 117/148] remove bottom limit of l2 blocktime in oracle init --- contracts/optimism/TokenRateOracle.sol | 4 ++-- test/optimism/TokenRateOracle.unit.test.ts | 6 ------ 2 files changed, 2 insertions(+), 8 deletions(-) diff --git a/contracts/optimism/TokenRateOracle.sol b/contracts/optimism/TokenRateOracle.sol index 95e78dd5..d5dcaa56 100644 --- a/contracts/optimism/TokenRateOracle.sol +++ b/contracts/optimism/TokenRateOracle.sol @@ -90,7 +90,7 @@ contract TokenRateOracle is CrossDomainEnabled, ITokenRateOracle, Versioned { if (tokenRate_ < MIN_ALLOWED_TOKEN_RATE || tokenRate_ > MAX_ALLOWED_TOKEN_RATE) { revert ErrorTokenRateInitializationIsOutOfAllowedRange(tokenRate_); } - if (rateL1Timestamp_ < block.timestamp || rateL1Timestamp_ > block.timestamp + MAX_ALLOWED_L2_TO_L1_CLOCK_LAG) { + if (rateL1Timestamp_ > block.timestamp + MAX_ALLOWED_L2_TO_L1_CLOCK_LAG) { revert ErrorL1TimestampInitializationIsOutOfAllowedRange(rateL1Timestamp_); } _initializeContractVersionTo(1); @@ -134,7 +134,7 @@ contract TokenRateOracle is CrossDomainEnabled, ITokenRateOracle, Versioned { revert ErrorL1TimestampExceededAllowedClockLag(tokenRate_, rateL1Timestamp_); } - /// @dev use only the more actual token rate + /// @dev Use only the most up-to-date token rate. Reverting should be avoided as it may occur occasionally. if (rateL1Timestamp_ <= _getRateL1Timestamp()) { emit DormantTokenRateUpdateIgnored(tokenRate_, rateL1Timestamp_, _getRateL1Timestamp()); return; diff --git a/test/optimism/TokenRateOracle.unit.test.ts b/test/optimism/TokenRateOracle.unit.test.ts index 53fac096..ba733ca3 100644 --- a/test/optimism/TokenRateOracle.unit.test.ts +++ b/test/optimism/TokenRateOracle.unit.test.ts @@ -122,17 +122,11 @@ unit("TokenRateOracle", ctxFactory) ); const wrongTimeMax = blockTimestamp.add(maxAllowedL2ToL1ClockLag).add(20); - const wrongTimeMin = blockTimestamp.sub(20); await assert.revertsWith( tokenRateOracleImpl.initialize(tokenRate, wrongTimeMax), "ErrorL1TimestampInitializationIsOutOfAllowedRange(" + wrongTimeMax + ")" ); - - await assert.revertsWith( - tokenRateOracleImpl.initialize(tokenRate, wrongTimeMin), - "ErrorL1TimestampInitializationIsOutOfAllowedRange(" + wrongTimeMin + ")" - ); }) .test("initialize() :: wrong maxAllowedTokenRateDeviationPerDay", async (ctx) => { From 4b301e85d252e853e606d4d14199e6aceefe5f87 Mon Sep 17 00:00:00 2001 From: Anton Kovalchuk Date: Wed, 15 May 2024 22:36:51 +0400 Subject: [PATCH 118/148] fix comments, error names, refactor token rate oracle and l2bridge --- .../optimism/L1ERC20ExtendedTokensBridge.sol | 6 +- .../optimism/L2ERC20ExtendedTokensBridge.sol | 24 +++--- contracts/optimism/TokenRateOracle.sol | 76 +++++++++++++------ contracts/token/ERC20Metadata.sol | 2 +- contracts/token/ERC20RebasableBridged.sol | 38 +++++----- .../token/ERC20RebasableBridgedPermit.sol | 2 - .../L2ERC20ExtendedTokensBridge.unit.test.ts | 4 +- test/optimism/TokenRateOracle.unit.test.ts | 35 ++++++++- test/token/ERC20Permit.unit.test.ts | 1 - .../ERC20RebasableBridgedPermit.unit.test.ts | 7 -- utils/optimism/deploymentAllFromScratch.ts | 69 +++++++++-------- utils/testing/contractsFactory.ts | 1 - 12 files changed, 158 insertions(+), 107 deletions(-) diff --git a/contracts/optimism/L1ERC20ExtendedTokensBridge.sol b/contracts/optimism/L1ERC20ExtendedTokensBridge.sol index f75046e0..ae045afb 100644 --- a/contracts/optimism/L1ERC20ExtendedTokensBridge.sol +++ b/contracts/optimism/L1ERC20ExtendedTokensBridge.sol @@ -137,11 +137,11 @@ abstract contract L1ERC20ExtendedTokensBridge is uint32 l2Gas_, bytes memory encodedDepositData_ ) internal { - uint256 nonRebaseableAmountToDeposit = _transferToBridge(l1Token_, from_, amount_); + uint256 nonRebasableAmountToDeposit = _transferToBridge(l1Token_, from_, amount_); bytes memory message = abi.encodeWithSelector( IL2ERC20Bridge.finalizeDeposit.selector, - l1Token_, l2Token_, from_, to_, nonRebaseableAmountToDeposit, encodedDepositData_ + l1Token_, l2Token_, from_, to_, nonRebasableAmountToDeposit, encodedDepositData_ ); sendCrossDomainMessage(L2_TOKEN_BRIDGE, l2Gas_, message); @@ -170,7 +170,7 @@ abstract contract L1ERC20ExtendedTokensBridge is /// @dev Helper that simplifies calling encoding by DepositDataCodec. /// Encodes token rate, it's L1 timestamp and optional data. /// @param data_ Optional data to forward to L2. - /// @return encoded data in bytes form. + /// @return encoded data in the 'wired' bytes form. function _encodeInputDepositData(bytes calldata data_) internal view returns (bytes memory) { return DepositDataCodec.encodeDepositData(DepositDataCodec.DepositData({ rate: uint96(_tokenRate()), diff --git a/contracts/optimism/L2ERC20ExtendedTokensBridge.sol b/contracts/optimism/L2ERC20ExtendedTokensBridge.sol index 4c77bf02..7435204b 100644 --- a/contracts/optimism/L2ERC20ExtendedTokensBridge.sol +++ b/contracts/optimism/L2ERC20ExtendedTokensBridge.sol @@ -110,8 +110,8 @@ contract L2ERC20ExtendedTokensBridge is { /// @dev L1_TOKEN_REBASABLE doesn't allow to transfer to itself. /// To prevent stucking tokens on L1 bridge this check was added. - if (to_ == L1_TOKEN_REBASABLE) { - revert ErrorTransferToL1TokenRebasableContract(); + if (to_ == L1_TOKEN_REBASABLE || to_ == L1_TOKEN_NON_REBASABLE) { + revert ErrorTransferToL1TokenContract(); } _withdrawTo(l2Token_, msg.sender, to_, amount_, l1Gas_, data_); emit WithdrawalInitiated(_getL1Token(l2Token_), l2Token_, msg.sender, to_, amount_, data_); @@ -163,11 +163,11 @@ contract L2ERC20ExtendedTokensBridge is uint32 l1Gas_, bytes calldata data_ ) internal { - uint256 nonRebaseableAmountToWithdraw = _burnTokens(l2Token_, from_, amount_); + uint256 nonRebasableAmountToWithdraw = _burnTokens(l2Token_, from_, amount_); bytes memory message = abi.encodeWithSelector( IL1ERC20Bridge.finalizeERC20Withdrawal.selector, - _getL1Token(l2Token_), l2Token_, from_, to_, nonRebaseableAmountToWithdraw, data_ + _getL1Token(l2Token_), l2Token_, from_, to_, nonRebasableAmountToWithdraw, data_ ); sendCrossDomainMessage(L1_TOKEN_BRIDGE, l1Gas_, message); } @@ -204,15 +204,19 @@ contract L2ERC20ExtendedTokensBridge is address from_, uint256 amount_ ) internal returns (uint256) { - uint256 nonRebasableTokenAmount = amount_; - if (l2Token_ == L2_TOKEN_REBASABLE && (amount_ != 0)) { - nonRebasableTokenAmount = ERC20RebasableBridged(L2_TOKEN_REBASABLE).bridgeUnwrap(from_, amount_); + if (l2Token_ == L2_TOKEN_REBASABLE) { + uint256 nonRebasableTokenAmount = ERC20RebasableBridged(L2_TOKEN_REBASABLE).getSharesByTokens(amount_); + if (amount_ != 0 && nonRebasableTokenAmount != 0) { + ERC20RebasableBridged(L2_TOKEN_REBASABLE).bridgeUnwrap(from_, amount_); + IERC20Bridged(L2_TOKEN_NON_REBASABLE).bridgeBurn(from_, nonRebasableTokenAmount); + } + return nonRebasableTokenAmount; } - IERC20Bridged(L2_TOKEN_NON_REBASABLE).bridgeBurn(from_, nonRebasableTokenAmount); - return nonRebasableTokenAmount; + IERC20Bridged(L2_TOKEN_NON_REBASABLE).bridgeBurn(from_, amount_); + return amount_; } error ErrorSenderNotEOA(); error ErrorZeroAddressL1Bridge(); - error ErrorTransferToL1TokenRebasableContract(); + error ErrorTransferToL1TokenContract(); } diff --git a/contracts/optimism/TokenRateOracle.sol b/contracts/optimism/TokenRateOracle.sol index d5dcaa56..e9f76890 100644 --- a/contracts/optimism/TokenRateOracle.sol +++ b/contracts/optimism/TokenRateOracle.sol @@ -12,8 +12,8 @@ interface ITokenRateOracle is ITokenRateUpdatable, IChainlinkAggregatorInterface /// @author kovalgek /// @notice Oracle for storing and providing token rate. -/// CEXes should fetch the token rate specific to the chain for deposits/withdrawals; -/// otherwise, utilizing the token rate from L1 for L2 transactions might lead to bad debt for the exchange. +/// NB: Cross-chain apps and CEXes should fetch the token rate specific to the chain for deposits/withdrawals +/// and compare against the token rate on L1 being a source of truth; /// @dev Token rate updates can be delivered from two sources: L1 token rate pusher and L2 bridge. contract TokenRateOracle is CrossDomainEnabled, ITokenRateOracle, Versioned { @@ -49,10 +49,10 @@ contract TokenRateOracle is CrossDomainEnabled, ITokenRateOracle, Versioned { uint8 public constant DECIMALS = 18; /// @notice Max allowed token rate value. - uint256 public constant MAX_ALLOWED_TOKEN_RATE = 2*10 ** 18; + uint256 public constant MAX_ALLOWED_TOKEN_RATE = 1*10 ** 20; /// @notice Min allowed token rate value. - uint256 public constant MIN_ALLOWED_TOKEN_RATE = 1*10 ** 18; + uint256 public constant MIN_ALLOWED_TOKEN_RATE = 1*10 ** 16; /// @notice Basic point scale. uint256 private constant BASIS_POINT_SCALE = 1e4; @@ -77,7 +77,7 @@ contract TokenRateOracle is CrossDomainEnabled, ITokenRateOracle, Versioned { uint256 maxAllowedTokenRateDeviationPerDay_ ) CrossDomainEnabled(messenger_) { if (maxAllowedTokenRateDeviationPerDay_ > BASIS_POINT_SCALE) { - revert ErrorMaxAllowedTokenRateDeviationPerDayBiggerThanBasicPointScale(); + revert ErrorMaxTokenRateDeviationIsOutOfRange(); } L2_ERC20_TOKEN_BRIDGE = l2ERC20TokenBridge_; L1_TOKEN_RATE_PUSHER = l1TokenRatePusher_; @@ -105,14 +105,14 @@ contract TokenRateOracle is CrossDomainEnabled, ITokenRateOracle, Versioned { uint256 updatedAt_, uint80 answeredInRound_ ) { - uint80 roundId = uint80(_getRateL1Timestamp()); + uint256 rateL1Timestamp = _getRateL1Timestamp(); return ( - roundId, + uint80(rateL1Timestamp), int256(uint256(_getTokenRate())), - _getRateL1Timestamp(), - _getRateL1Timestamp(), - roundId + rateL1Timestamp, + rateL1Timestamp, + uint80(rateL1Timestamp) ); } @@ -128,6 +128,8 @@ contract TokenRateOracle is CrossDomainEnabled, ITokenRateOracle, Versioned { /// @inheritdoc ITokenRateUpdatable function updateRate(uint256 tokenRate_, uint256 rateL1Timestamp_) external onlyBridgeOrTokenRatePusher { + uint256 currentTokenRate = _getTokenRate(); + uint256 currentRateL1Timestamp = _getRateL1Timestamp(); /// @dev checks if the clock lag (i.e, time difference) between L1 and L2 exceeds the configurable threshold if (rateL1Timestamp_ > block.timestamp + MAX_ALLOWED_L2_TO_L1_CLOCK_LAG) { @@ -135,13 +137,14 @@ contract TokenRateOracle is CrossDomainEnabled, ITokenRateOracle, Versioned { } /// @dev Use only the most up-to-date token rate. Reverting should be avoided as it may occur occasionally. - if (rateL1Timestamp_ <= _getRateL1Timestamp()) { - emit DormantTokenRateUpdateIgnored(tokenRate_, rateL1Timestamp_, _getRateL1Timestamp()); + if (rateL1Timestamp_ <= currentRateL1Timestamp) { + emit DormantTokenRateUpdateIgnored(tokenRate_, rateL1Timestamp_, currentRateL1Timestamp); return; } /// @dev allow token rate to be within some configurable range that depens on time it wasn't updated. - if (tokenRate_ != _getTokenRate() && !_isTokenRateWithinAllowedRange(tokenRate_, rateL1Timestamp_)) { + if (tokenRate_ != currentTokenRate && + !_isTokenRateWithinAllowedRange(currentTokenRate, tokenRate_, currentRateL1Timestamp, rateL1Timestamp_)) { revert ErrorTokenRateIsOutOfRange(tokenRate_, rateL1Timestamp_); } @@ -149,7 +152,7 @@ contract TokenRateOracle is CrossDomainEnabled, ITokenRateOracle, Versioned { if (rateL1Timestamp_ > block.timestamp) emit TokenRateL1TimestampIsInFuture(tokenRate_, rateL1Timestamp_); _setTokenRateAndL1Timestamp(uint192(tokenRate_), uint64(rateL1Timestamp_)); - emit RateUpdated(_getTokenRate(), _getRateL1Timestamp()); + emit RateUpdated(tokenRate_, rateL1Timestamp_); } /// @notice Returns flag that shows that token rate can be considered outdated. @@ -160,20 +163,47 @@ contract TokenRateOracle is CrossDomainEnabled, ITokenRateOracle, Versioned { /// @notice Allow tokenRate deviation from the previous value to be /// ±`MAX_ALLOWED_TOKEN_RATE_DEVIATION_PER_DAY` BP per day. function _isTokenRateWithinAllowedRange( - uint256 newTokenRate_, uint256 newRateL1Timestamp_ + uint256 currentTokenRate_, + uint256 newTokenRate_, + uint256 currentRateL1Timestamp_, + uint256 newRateL1Timestamp_ ) internal view returns (bool) { - uint256 rateL1TimestampDiff = newRateL1Timestamp_ - _getRateL1Timestamp(); + uint256 allowedTokenRateDeviation = _allowedTokenRateDeviation(newRateL1Timestamp_, currentRateL1Timestamp_); + return newTokenRate_ <= _maxTokenRateLimit(currentTokenRate_, allowedTokenRateDeviation) && + newTokenRate_ >= _minTokenRateLimit(currentTokenRate_, allowedTokenRateDeviation); + } + + /// @dev Returns the allowed token deviation depending on the number of days passed since the last update. + function _allowedTokenRateDeviation( + uint256 newRateL1Timestamp_, + uint256 currentRateL1Timestamp_ + ) internal view returns (uint256) { + uint256 rateL1TimestampDiff = newRateL1Timestamp_ - currentRateL1Timestamp_; uint256 roundedUpNumberOfDays = (rateL1TimestampDiff + ONE_DAY_SECONDS - 1) / ONE_DAY_SECONDS; - uint256 allowedTokenRateDeviation = roundedUpNumberOfDays * MAX_ALLOWED_TOKEN_RATE_DEVIATION_PER_DAY; - uint256 topTokenRateLimit = _getTokenRate() * (BASIS_POINT_SCALE + allowedTokenRateDeviation) / + return roundedUpNumberOfDays * MAX_ALLOWED_TOKEN_RATE_DEVIATION_PER_DAY; + } + + /// @dev Returns the maximum allowable value for the token rate. + function _maxTokenRateLimit( + uint256 currentTokenRate, + uint256 allowedTokenRateDeviation + ) internal pure returns (uint256) { + uint256 maxTokenRateLimit = currentTokenRate * (BASIS_POINT_SCALE + allowedTokenRateDeviation) / BASIS_POINT_SCALE; - uint256 bottomTokenRateLimit = 0; + return (maxTokenRateLimit > MAX_ALLOWED_TOKEN_RATE) ? MAX_ALLOWED_TOKEN_RATE : maxTokenRateLimit; + } + + /// @dev Returns the minimum allowable value for the token rate. + function _minTokenRateLimit( + uint256 currentTokenRate, + uint256 allowedTokenRateDeviation + ) internal pure returns (uint256) { + uint256 minTokenRateLimit = MIN_ALLOWED_TOKEN_RATE; if (allowedTokenRateDeviation <= BASIS_POINT_SCALE) { - bottomTokenRateLimit = (_getTokenRate() * (BASIS_POINT_SCALE - allowedTokenRateDeviation) / + minTokenRateLimit = (currentTokenRate * (BASIS_POINT_SCALE - allowedTokenRateDeviation) / BASIS_POINT_SCALE); } - return newTokenRate_ <= topTokenRateLimit && - newTokenRate_ >= bottomTokenRateLimit; + return (minTokenRateLimit < MIN_ALLOWED_TOKEN_RATE) ? MIN_ALLOWED_TOKEN_RATE : minTokenRateLimit; } function _isCallerBridgeOrMessengerWithTokenRatePusher(address caller_) internal view returns (bool) { @@ -235,7 +265,7 @@ contract TokenRateOracle is CrossDomainEnabled, ITokenRateOracle, Versioned { error ErrorNotBridgeOrTokenRatePusher(); error ErrorL1TimestampExceededAllowedClockLag(uint256 tokenRate_, uint256 rateL1Timestamp_); error ErrorTokenRateIsOutOfRange(uint256 tokenRate_, uint256 rateL1Timestamp_); - error ErrorMaxAllowedTokenRateDeviationPerDayBiggerThanBasicPointScale(); + error ErrorMaxTokenRateDeviationIsOutOfRange(); error ErrorTokenRateInitializationIsOutOfAllowedRange(uint256 tokenRate_); error ErrorL1TimestampInitializationIsOutOfAllowedRange(uint256 rateL1Timestamp_); } diff --git a/contracts/token/ERC20Metadata.sol b/contracts/token/ERC20Metadata.sol index 7ac0e62a..db916aed 100644 --- a/contracts/token/ERC20Metadata.sol +++ b/contracts/token/ERC20Metadata.sol @@ -29,7 +29,7 @@ contract ERC20Metadata is IERC20Metadata { } /// @dev Location of the slot with DynamicMetdata - /// The value has a misspelling but without it the contract storage will be broken. + /// The slot's index string has a misspelling, but the contract storage will be broken without it. bytes32 private constant DYNAMIC_METADATA_SLOT = keccak256("ERC20Metdata.dynamicMetadata"); diff --git a/contracts/token/ERC20RebasableBridged.sol b/contracts/token/ERC20RebasableBridged.sol index b1e02f46..7f449b09 100644 --- a/contracts/token/ERC20RebasableBridged.sol +++ b/contracts/token/ERC20RebasableBridged.sol @@ -17,16 +17,16 @@ interface IBridgeWrapper { /// @notice Returns bridge which can wrap/unwrap token on L2. function L2_ERC20_TOKEN_BRIDGE() external view returns (address); - /// @notice Exchanges non-rebaseable token (shares) to rebaseable token. Can be called by bridge only. + /// @notice Exchanges non-rebasable token (shares) to rebasable token. Can be called by bridge only. /// @param account_ an address of the account to exchange shares for. - /// @param sharesAmount_ amount of non-rebaseable token (shares). - /// @return Amount of rebaseable token. + /// @param sharesAmount_ amount of non-rebasable token (shares). + /// @return Amount of rebasable token. function bridgeWrap(address account_, uint256 sharesAmount_) external returns (uint256); - /// @notice Exchanges rebaseable token to non-rebasable (shares). Can be called by bridge only. + /// @notice Exchanges rebasable token to non-rebasable (shares). Can be called by bridge only. /// @param account_ an address of the account to exchange token for. - /// @param tokenAmount_ amount of rebaseable token to uwrap in exchange for non-rebaseable token (shares). - /// @return Amount of non-rebaseable token (shares) user receives after unwrap. + /// @param tokenAmount_ amount of rebasable token to uwrap in exchange for non-rebasable token (shares). + /// @return Amount of non-rebasable token (shares) user receives after unwrap. function bridgeUnwrap(address account_, uint256 tokenAmount_) external returns (uint256); } @@ -63,7 +63,6 @@ contract ERC20RebasableBridged is IERC20, IERC20Wrapper, IBridgeWrapper, ERC20Me /// @param decimals_ The decimals places of the token /// @param tokenToWrapFrom_ address of the ERC20 token to wrap /// @param tokenRateOracle_ address of oracle that returns tokens rate - /// @param tokenRateOracleDecimals_ Decimals of the oracle response. /// @param l2ERC20TokenBridge_ The bridge address which allows to mint/burn tokens constructor( string memory name_, @@ -71,12 +70,11 @@ contract ERC20RebasableBridged is IERC20, IERC20Wrapper, IBridgeWrapper, ERC20Me uint8 decimals_, address tokenToWrapFrom_, address tokenRateOracle_, - uint8 tokenRateOracleDecimals_, address l2ERC20TokenBridge_ ) ERC20Metadata(name_, symbol_, decimals_) { TOKEN_TO_WRAP_FROM = IERC20(tokenToWrapFrom_); TOKEN_RATE_ORACLE = ITokenRateOracle(tokenRateOracle_); - TOKEN_RATE_ORACLE_DECIMALS = tokenRateOracleDecimals_; + TOKEN_RATE_ORACLE_DECIMALS = TOKEN_RATE_ORACLE.decimals(); L2_ERC20_TOKEN_BRIDGE = l2ERC20TokenBridge_; } @@ -90,14 +88,11 @@ contract ERC20RebasableBridged is IERC20, IERC20Wrapper, IBridgeWrapper, ERC20Me return _unwrap(msg.sender, tokenAmount_); } - /// @notice Exchanges rebaseable token to non-rebasable by providing rebaseable token shares. - /// @param sharesAmount_ amount of rebaseable token shares to unwrap. - /// @return amount of non-rebaseable token user receives after unwrap. + /// @notice Exchanges rebasable token to non-rebasable by providing rebasable token shares. + /// @param sharesAmount_ amount of rebasable token shares to unwrap. + /// @return amount of non-rebasable token user receives after unwrap. function unwrapShares(uint256 sharesAmount_) external returns (uint256) { - if (sharesAmount_ == 0) revert ErrorZeroSharesUnwrap(); - _burnShares(msg.sender, sharesAmount_); - TOKEN_TO_WRAP_FROM.safeTransfer(msg.sender, sharesAmount_); - return sharesAmount_; + return _unwrapShares(msg.sender, sharesAmount_); } /// @inheritdoc IBridgeWrapper @@ -383,12 +378,15 @@ contract ERC20RebasableBridged is IERC20, IERC20Wrapper, IBridgeWrapper, ERC20Me function _unwrap(address account_, uint256 tokenAmount_) internal returns (uint256) { if (tokenAmount_ == 0) revert ErrorZeroTokensUnwrap(); - uint256 sharesAmount = _getSharesByTokens(tokenAmount_); - _burnShares(account_, sharesAmount); - TOKEN_TO_WRAP_FROM.safeTransfer(account_, sharesAmount); + return _unwrapShares(account_, sharesAmount); + } - return sharesAmount; + function _unwrapShares(address account_, uint256 sharesAmount_) internal returns (uint256) { + if (sharesAmount_ == 0) revert ErrorZeroSharesUnwrap(); + _burnShares(account_, sharesAmount_); + TOKEN_TO_WRAP_FROM.safeTransfer(account_, sharesAmount_); + return sharesAmount_; } /// @dev validates that account_ is not zero address diff --git a/contracts/token/ERC20RebasableBridgedPermit.sol b/contracts/token/ERC20RebasableBridgedPermit.sol index 8efebd40..1755fd17 100644 --- a/contracts/token/ERC20RebasableBridgedPermit.sol +++ b/contracts/token/ERC20RebasableBridgedPermit.sol @@ -25,7 +25,6 @@ contract ERC20RebasableBridgedPermit is ERC20RebasableBridged, PermitExtension, uint8 decimals_, address tokenToWrapFrom_, address tokenRateOracle_, - uint8 tokenRateOracleDecimals_, address bridge_ ) ERC20RebasableBridged( @@ -34,7 +33,6 @@ contract ERC20RebasableBridgedPermit is ERC20RebasableBridged, PermitExtension, decimals_, tokenToWrapFrom_, tokenRateOracle_, - tokenRateOracleDecimals_, bridge_ ) PermitExtension(name_, version_) diff --git a/test/optimism/L2ERC20ExtendedTokensBridge.unit.test.ts b/test/optimism/L2ERC20ExtendedTokensBridge.unit.test.ts index afe065cf..f3600e8f 100644 --- a/test/optimism/L2ERC20ExtendedTokensBridge.unit.test.ts +++ b/test/optimism/L2ERC20ExtendedTokensBridge.unit.test.ts @@ -790,7 +790,7 @@ unit("Optimism:: L2ERC20ExtendedTokensBridge", ctxFactory) 0, l1Gas, data), - "ErrorTransferToL1TokenRebasableContract()" + "ErrorTransferToL1TokenContract()" ); }) @@ -1175,7 +1175,6 @@ async function ctxFactory() { decimals, l2TokenNonRebasableStub.address, tokenRateOracle.address, - 18, l2TokenBridgeProxyAddress ); @@ -1355,7 +1354,6 @@ async function getL2TokenBridgeImpl(deployer: SignerWithAddress, l1TokenBridge: decimals, l2TokenNonRebasableStub.address, tokenRateOracle.address, - 18, l2TokenBridgeProxyAddress ); diff --git a/test/optimism/TokenRateOracle.unit.test.ts b/test/optimism/TokenRateOracle.unit.test.ts index ba733ca3..4e827687 100644 --- a/test/optimism/TokenRateOracle.unit.test.ts +++ b/test/optimism/TokenRateOracle.unit.test.ts @@ -144,7 +144,7 @@ unit("TokenRateOracle", ctxFactory) 86400, maxAllowedTokenRateDeviationPerDay ), - "ErrorMaxAllowedTokenRateDeviationPerDayBiggerThanBasicPointScale()" + "ErrorMaxTokenRateDeviationIsOutOfRange()" ); }) @@ -294,6 +294,39 @@ unit("TokenRateOracle", ctxFactory) await tokenRateOracle.connect(bridge).updateRate(tokenRateSizeDoesMatterAfterAll, blockTimestampMoreThanOneDays); }) + .test("updateRate() :: token rate limits", async (ctx) => { + const { deployer, bridge, l1TokenBridgeEOA } = ctx.accounts; + const { tokenRate, blockTimestamp } = ctx.constants; + + const tokenRateOutdatedDelay = BigNumber.from(86400); // 1 day + const maxAllowedL2ToL1ClockLag = BigNumber.from(86400 * 2); // 2 days + const maxAllowedTokenRateDeviationPerDay = BigNumber.from(10000); // 100% + + const l2MessengerStub = await new CrossDomainMessengerStub__factory( + deployer + ).deploy({ value: wei.toBigNumber(wei`1 ether`) }); + + const tokenRateOracle = await tokenRateOracleUnderProxy( + deployer, + l2MessengerStub.address, + bridge.address, + l1TokenBridgeEOA.address, + tokenRateOutdatedDelay, + maxAllowedL2ToL1ClockLag, + maxAllowedTokenRateDeviationPerDay, + tokenRate, + BigNumber.from(0) + ); + + const maxAllowedTokenRate = await tokenRateOracle.MAX_ALLOWED_TOKEN_RATE(); + await tokenRateOracle.connect(bridge).updateRate(maxAllowedTokenRate, blockTimestamp.add(1000)); + assert.equalBN(await tokenRateOracle.latestAnswer(), maxAllowedTokenRate); + + const minAllowedTokenRate = await tokenRateOracle.MIN_ALLOWED_TOKEN_RATE(); + await tokenRateOracle.connect(bridge).updateRate(minAllowedTokenRate, blockTimestamp.add(2000)); + assert.equalBN(await tokenRateOracle.latestAnswer(), minAllowedTokenRate); + }) + .test("updateRate() :: happy path called by bridge", async (ctx) => { const { tokenRateOracle } = ctx.contracts; const { bridge } = ctx.accounts; diff --git a/test/token/ERC20Permit.unit.test.ts b/test/token/ERC20Permit.unit.test.ts index e81102f3..9ce73cc4 100644 --- a/test/token/ERC20Permit.unit.test.ts +++ b/test/token/ERC20Permit.unit.test.ts @@ -426,7 +426,6 @@ async function tokenProxied( decimalsToSet, wrappedToken.address, tokenRateOracle.address, - 18, owner.address ); diff --git a/test/token/ERC20RebasableBridgedPermit.unit.test.ts b/test/token/ERC20RebasableBridgedPermit.unit.test.ts index 7c7c91cd..7d855d41 100644 --- a/test/token/ERC20RebasableBridgedPermit.unit.test.ts +++ b/test/token/ERC20RebasableBridgedPermit.unit.test.ts @@ -58,7 +58,6 @@ unit("ERC20RebasableBridgedPermit", ctxFactory) 10, wrappedToken.address, tokenRateOracle.address, - 18, owner.address ); @@ -98,7 +97,6 @@ unit("ERC20RebasableBridgedPermit", ctxFactory) 10, wrappedToken.address, tokenRateOracle.address, - 18, owner.address ); @@ -155,7 +153,6 @@ unit("ERC20RebasableBridgedPermit", ctxFactory) 10, wrappedToken.address, tokenRateOracle.address, - 18, owner.address ); @@ -225,7 +222,6 @@ unit("ERC20RebasableBridgedPermit", ctxFactory) 10, wrappedToken.address, tokenRateOracle.address, - 18, owner.address ); @@ -339,7 +335,6 @@ unit("ERC20RebasableBridgedPermit", ctxFactory) 10, wrappedToken.address, tokenRateOracle.address, - 18, owner.address ); @@ -504,7 +499,6 @@ unit("ERC20RebasableBridgedPermit", ctxFactory) 10, wrappedToken.address, tokenRateOracle.address, - 18, owner.address ); @@ -635,7 +629,6 @@ unit("ERC20RebasableBridgedPermit", ctxFactory) 10, wrappedToken.address, tokenRateOracle.address, - 18, owner.address ); diff --git a/utils/optimism/deploymentAllFromScratch.ts b/utils/optimism/deploymentAllFromScratch.ts index 4588fd69..e89e78d6 100644 --- a/utils/optimism/deploymentAllFromScratch.ts +++ b/utils/optimism/deploymentAllFromScratch.ts @@ -120,14 +120,14 @@ export default function deploymentAll( ] = await network.predictAddresses(l1Params.deployer, l1Params.contractsShift + 4); const [ + expectedL2TokenRateOracleImplAddress, + expectedL2TokenRateOracleProxyAddress, expectedL2TokenImplAddress, expectedL2TokenProxyAddress, expectedL2TokenRebasableImplAddress, expectedL2TokenRebasableProxyAddress, expectedL2TokenBridgeImplAddress, - expectedL2TokenBridgeProxyAddress, - expectedL2TokenRateOracleImplAddress, - expectedL2TokenRateOracleProxyAddress + expectedL2TokenBridgeProxyAddress ] = await network.predictAddresses(l2Params.deployer, l2Params.contractsShift + 8); const l1DeployScript = new L1DeployAllScript( @@ -218,6 +218,37 @@ export default function deploymentAll( expectedL2TokenRateOracleProxyAddress, options?.logger ) + .addStep({ + factory: TokenRateOracle__factory, + args: [ + optAddresses.L2CrossDomainMessenger, + expectedL2TokenBridgeProxyAddress, + expectedL1OpStackTokenRatePusherImplAddress, + 86400, + 86400, + 500, + options?.overrides, + ], + afterDeploy: (c) => + assert.equal(c.address, expectedL2TokenRateOracleImplAddress), + }) + .addStep({ + factory: OssifiableProxy__factory, + args: [ + expectedL2TokenRateOracleImplAddress, + l2Params.admins.proxy, + TokenRateOracle__factory.createInterface().encodeFunctionData( + "initialize", + [ + l2Params.tokenRateOracle.tokenRate, + l2Params.tokenRateOracle.l1Timestamp + ] + ), + options?.overrides, + ], + afterDeploy: (c) => + assert.equal(c.address, expectedL2TokenRateOracleProxyAddress), + }) .addStep({ factory: ERC20BridgedPermit__factory, args: [ @@ -254,7 +285,6 @@ export default function deploymentAll( decimals, expectedL2TokenProxyAddress, expectedL2TokenRateOracleProxyAddress, - decimals, expectedL2TokenBridgeProxyAddress, options?.overrides, ], @@ -300,37 +330,6 @@ export default function deploymentAll( ), options?.overrides, ], - }) - .addStep({ - factory: TokenRateOracle__factory, - args: [ - optAddresses.L2CrossDomainMessenger, - expectedL2TokenBridgeProxyAddress, - expectedL1OpStackTokenRatePusherImplAddress, - 86400, - 86400, - 500, - options?.overrides, - ], - afterDeploy: (c) => - assert.equal(c.address, expectedL2TokenRateOracleImplAddress), - }) - .addStep({ - factory: OssifiableProxy__factory, - args: [ - expectedL2TokenRateOracleImplAddress, - l2Params.admins.proxy, - TokenRateOracle__factory.createInterface().encodeFunctionData( - "initialize", - [ - l2Params.tokenRateOracle.tokenRate, - l2Params.tokenRateOracle.l1Timestamp - ] - ), - options?.overrides, - ], - afterDeploy: (c) => - assert.equal(c.address, expectedL2TokenRateOracleProxyAddress), }); return [l1DeployScript as L1DeployAllScript, l2DeployScript as L2DeployAllScript]; diff --git a/utils/testing/contractsFactory.ts b/utils/testing/contractsFactory.ts index b6fb40d9..3e49a940 100644 --- a/utils/testing/contractsFactory.ts +++ b/utils/testing/contractsFactory.ts @@ -97,7 +97,6 @@ export async function erc20RebasableBridgedPermitUnderProxy( decimals, erc20BridgedPermit.address, tokenRateOracle.address, - 18, bridge ); From d48ce20f2fc3f9a4c084c4c42238d21defa4da90 Mon Sep 17 00:00:00 2001 From: Anton Kovalchuk Date: Wed, 15 May 2024 22:44:25 +0400 Subject: [PATCH 119/148] remove token rate from event about wrong l1 time --- contracts/optimism/TokenRateOracle.sol | 3 +-- test/optimism/TokenRateOracle.unit.test.ts | 2 -- 2 files changed, 1 insertion(+), 4 deletions(-) diff --git a/contracts/optimism/TokenRateOracle.sol b/contracts/optimism/TokenRateOracle.sol index e9f76890..c50f0427 100644 --- a/contracts/optimism/TokenRateOracle.sol +++ b/contracts/optimism/TokenRateOracle.sol @@ -138,7 +138,7 @@ contract TokenRateOracle is CrossDomainEnabled, ITokenRateOracle, Versioned { /// @dev Use only the most up-to-date token rate. Reverting should be avoided as it may occur occasionally. if (rateL1Timestamp_ <= currentRateL1Timestamp) { - emit DormantTokenRateUpdateIgnored(tokenRate_, rateL1Timestamp_, currentRateL1Timestamp); + emit DormantTokenRateUpdateIgnored(rateL1Timestamp_, currentRateL1Timestamp); return; } @@ -253,7 +253,6 @@ contract TokenRateOracle is CrossDomainEnabled, ITokenRateOracle, Versioned { uint256 indexed rateL1Timestamp_ ); event DormantTokenRateUpdateIgnored( - uint256 tokenRate_, uint256 indexed newRateL1Timestamp_, uint256 indexed currentRateL1Timestamp_ ); diff --git a/test/optimism/TokenRateOracle.unit.test.ts b/test/optimism/TokenRateOracle.unit.test.ts index 4e827687..f34767b7 100644 --- a/test/optimism/TokenRateOracle.unit.test.ts +++ b/test/optimism/TokenRateOracle.unit.test.ts @@ -189,7 +189,6 @@ unit("TokenRateOracle", ctxFactory) .updateRate(tokenRate, blockTimestamp); await assert.emits(tokenRateOracle, tx0, "DormantTokenRateUpdateIgnored", [ - tokenRate, blockTimestamp, blockTimestamp, ]); @@ -201,7 +200,6 @@ unit("TokenRateOracle", ctxFactory) .updateRate(tokenRate, timeInPast); await assert.emits(tokenRateOracle, tx1, "DormantTokenRateUpdateIgnored", [ - tokenRate, timeInPast, blockTimestamp, ]); From 5c48830cbbde31e9ae153c605007cb39d096489c Mon Sep 17 00:00:00 2001 From: Anton Kovalchuk Date: Thu, 16 May 2024 16:58:36 +0400 Subject: [PATCH 120/148] update comment --- contracts/optimism/L2ERC20ExtendedTokensBridge.sol | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/contracts/optimism/L2ERC20ExtendedTokensBridge.sol b/contracts/optimism/L2ERC20ExtendedTokensBridge.sol index 7435204b..15b9869c 100644 --- a/contracts/optimism/L2ERC20ExtendedTokensBridge.sol +++ b/contracts/optimism/L2ERC20ExtendedTokensBridge.sol @@ -108,8 +108,9 @@ contract L2ERC20ExtendedTokensBridge is onlyNonZeroAccount(to_) onlySupportedL2Token(l2Token_) { - /// @dev L1_TOKEN_REBASABLE doesn't allow to transfer to itself. - /// To prevent stucking tokens on L1 bridge this check was added. + /// @dev L1_TOKEN_REBASABLE does not allow transfers to itself. + /// Additionally, sending funds to L1_TOKEN_NON_REBASABLE would lock these funds permanently, + /// as it is non-upgradeable. To prevent stucking tokens on L1 bridge or token this check was added. if (to_ == L1_TOKEN_REBASABLE || to_ == L1_TOKEN_NON_REBASABLE) { revert ErrorTransferToL1TokenContract(); } From 34ac185bb8ea1bfc4ca79943314af463d377c397 Mon Sep 17 00:00:00 2001 From: Anton Kovalchuk Date: Thu, 16 May 2024 17:02:32 +0400 Subject: [PATCH 121/148] use min/max functions --- contracts/optimism/TokenRateOracle.sol | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/contracts/optimism/TokenRateOracle.sol b/contracts/optimism/TokenRateOracle.sol index c50f0427..542c6c09 100644 --- a/contracts/optimism/TokenRateOracle.sol +++ b/contracts/optimism/TokenRateOracle.sol @@ -7,6 +7,7 @@ import {ITokenRateUpdatable} from "./interfaces/ITokenRateUpdatable.sol"; import {IChainlinkAggregatorInterface} from "./interfaces/IChainlinkAggregatorInterface.sol"; import {CrossDomainEnabled} from "./CrossDomainEnabled.sol"; import {Versioned} from "../utils/Versioned.sol"; +import {Math} from "@openzeppelin/contracts/utils/math/Math.sol"; interface ITokenRateOracle is ITokenRateUpdatable, IChainlinkAggregatorInterface {} @@ -190,7 +191,7 @@ contract TokenRateOracle is CrossDomainEnabled, ITokenRateOracle, Versioned { ) internal pure returns (uint256) { uint256 maxTokenRateLimit = currentTokenRate * (BASIS_POINT_SCALE + allowedTokenRateDeviation) / BASIS_POINT_SCALE; - return (maxTokenRateLimit > MAX_ALLOWED_TOKEN_RATE) ? MAX_ALLOWED_TOKEN_RATE : maxTokenRateLimit; + return Math.min(maxTokenRateLimit, MAX_ALLOWED_TOKEN_RATE); } /// @dev Returns the minimum allowable value for the token rate. @@ -203,7 +204,7 @@ contract TokenRateOracle is CrossDomainEnabled, ITokenRateOracle, Versioned { minTokenRateLimit = (currentTokenRate * (BASIS_POINT_SCALE - allowedTokenRateDeviation) / BASIS_POINT_SCALE); } - return (minTokenRateLimit < MIN_ALLOWED_TOKEN_RATE) ? MIN_ALLOWED_TOKEN_RATE : minTokenRateLimit; + return Math.max(minTokenRateLimit, MIN_ALLOWED_TOKEN_RATE); } function _isCallerBridgeOrMessengerWithTokenRatePusher(address caller_) internal view returns (bool) { From 64c8b98c090d54f6910c7ebbe21718da00e57867 Mon Sep 17 00:00:00 2001 From: Anton Kovalchuk Date: Thu, 16 May 2024 17:37:43 +0400 Subject: [PATCH 122/148] don't allow to call initialize on prev deployed contract --- contracts/token/ERC20BridgedPermit.sol | 4 ++ test/token/ERC20BridgedPermit.unit.test.ts | 46 ++++++++++++++++++++-- 2 files changed, 47 insertions(+), 3 deletions(-) diff --git a/contracts/token/ERC20BridgedPermit.sol b/contracts/token/ERC20BridgedPermit.sol index 2f014c75..87536453 100644 --- a/contracts/token/ERC20BridgedPermit.sol +++ b/contracts/token/ERC20BridgedPermit.sol @@ -33,6 +33,9 @@ contract ERC20BridgedPermit is ERC20Bridged, PermitExtension, Versioned { /// @param symbol_ The symbol of the token /// @param version_ The version of the token function initialize(string memory name_, string memory symbol_, string memory version_) external { + if (_isMetadataInitialized()) { + revert ErrorMetadataIsAlreadyInitialized(); + } _initializeERC20Metadata(name_, symbol_); _initialize_v2(name_, version_); } @@ -56,4 +59,5 @@ contract ERC20BridgedPermit is ERC20Bridged, PermitExtension, Versioned { } error ErrorMetadataIsNotInitialized(); + error ErrorMetadataIsAlreadyInitialized(); } diff --git a/test/token/ERC20BridgedPermit.unit.test.ts b/test/token/ERC20BridgedPermit.unit.test.ts index c0aee526..04322a96 100644 --- a/test/token/ERC20BridgedPermit.unit.test.ts +++ b/test/token/ERC20BridgedPermit.unit.test.ts @@ -41,9 +41,10 @@ unit("ERC20BridgedPermit", ctxFactory) const petrifiedVersionMark = hre.ethers.constants.MaxUint256; assert.equalBN(await erc20BridgedImpl.getContractVersion(), petrifiedVersionMark); + // an early check of metadata won't allow to see NonZeroContractVersionOnInit() error await assert.revertsWith( erc20BridgedImpl.initialize("name", "symbol", "version"), - "NonZeroContractVersionOnInit()" + "ErrorMetadataIsAlreadyInitialized()" ); }) @@ -117,7 +118,7 @@ unit("ERC20BridgedPermit", ctxFactory) await assert.revertsWith( erc20BridgedProxied.initialize(name, symbol, version), - "NonZeroContractVersionOnInit()" + "ErrorMetadataIsAlreadyInitialized()" ); }) @@ -191,10 +192,49 @@ unit("ERC20BridgedPermit", ctxFactory) // can't initialize after finalizeUpgrade_v2 await assert.revertsWith( erc20BridgedProxied.initialize("name", "symbol", "version"), - "NonZeroContractVersionOnInit()" + "ErrorMetadataIsAlreadyInitialized()" ); }) + .test("initialize() :: ins't allowed to call instead of finalizeUpgrade_v2()", async (ctx) => { + const { deployer, owner } = ctx.accounts; + const { name, symbol, version } = ctx.constants; + + const l2TokenOldImpl = await new ERC20BridgedWithInitializerStub__factory(deployer).deploy( + "name", + "symbol", + 18, + owner.address + ); + + const l2TokenProxy = await new OssifiableProxy__factory(deployer).deploy( + l2TokenOldImpl.address, + deployer.address, + ERC20BridgedWithInitializerStub__factory.createInterface().encodeFunctionData("initializeERC20Metadata", [ + "name", + "symbol" + ]) + ); + + const l2TokenImpl = await new ERC20BridgedPermit__factory(deployer).deploy( + "name", + "symbol", + "1", + 18, + owner.address + ); + + await assert.revertsWith(l2TokenProxy.proxy__upgradeToAndCall( + l2TokenImpl.address, + ERC20BridgedPermit__factory.createInterface().encodeFunctionData("initialize", [ + name, + symbol, + version + ]), + false + ), "ErrorMetadataIsAlreadyInitialized()"); + }) + .test("approve()", async (ctx) => { const { erc20Bridged } = ctx; const { holder, spender } = ctx.accounts; From c95fdad3c2d2f8ba4124b13037c4d4a03700de8d Mon Sep 17 00:00:00 2001 From: Eugene M Date: Thu, 16 May 2024 16:51:21 +0300 Subject: [PATCH 123/148] draft: change rate reporting approach --- contracts/lib/DepositDataCodec.sol | 4 +- .../optimism/L1ERC20ExtendedTokensBridge.sol | 7 +- contracts/optimism/L1LidoTokensBridge.sol | 44 +++++++-- contracts/optimism/OpStackTokenRatePusher.sol | 25 +++--- contracts/optimism/TokenRateOracle.sol | 90 +++++++++++-------- .../interfaces/ITokenRateUpdatable.sol | 4 +- contracts/stubs/ERC20WrapperStub.sol | 4 +- 7 files changed, 116 insertions(+), 62 deletions(-) diff --git a/contracts/lib/DepositDataCodec.sol b/contracts/lib/DepositDataCodec.sol index 46647d3d..81d3a3fb 100644 --- a/contracts/lib/DepositDataCodec.sol +++ b/contracts/lib/DepositDataCodec.sol @@ -7,11 +7,11 @@ pragma solidity 0.8.10; /// @notice encodes and decodes DepositData for crosschain transfering. library DepositDataCodec { - uint8 internal constant RATE_FIELD_SIZE = 12; + uint8 internal constant RATE_FIELD_SIZE = 16; uint8 internal constant TIMESTAMP_FIELD_SIZE = 5; struct DepositData { - uint96 rate; + uint128 rate; uint40 timestamp; bytes data; } diff --git a/contracts/optimism/L1ERC20ExtendedTokensBridge.sol b/contracts/optimism/L1ERC20ExtendedTokensBridge.sol index ae045afb..0a08b65a 100644 --- a/contracts/optimism/L1ERC20ExtendedTokensBridge.sol +++ b/contracts/optimism/L1ERC20ExtendedTokensBridge.sol @@ -172,15 +172,16 @@ abstract contract L1ERC20ExtendedTokensBridge is /// @param data_ Optional data to forward to L2. /// @return encoded data in the 'wired' bytes form. function _encodeInputDepositData(bytes calldata data_) internal view returns (bytes memory) { + (uint256 timestamp, uint256 rate) = tokenRate(); return DepositDataCodec.encodeDepositData(DepositDataCodec.DepositData({ - rate: uint96(_tokenRate()), - timestamp: uint40(block.timestamp), + rate: uint128(rate), + timestamp: uint40(timestamp), data: data_ })); } /// @notice required to abstact a way token rate is requested. - function _tokenRate() virtual internal view returns (uint256); + function tokenRate() virtual public view returns (uint256 rate, uint256 updateTimestamp); error ErrorSenderNotEOA(); error ErrorZeroAddressL2Bridge(); diff --git a/contracts/optimism/L1LidoTokensBridge.sol b/contracts/optimism/L1LidoTokensBridge.sol index a331fc87..e25a4319 100644 --- a/contracts/optimism/L1LidoTokensBridge.sol +++ b/contracts/optimism/L1LidoTokensBridge.sol @@ -9,28 +9,53 @@ import {Versioned} from "../utils/Versioned.sol"; /// @author kovalgek /// @notice A subset of wstETH token interface of core LIDO protocol. interface IERC20WstETH { - /// @notice Get amount of wstETH for a one stETH - /// @return Amount of wstETH for a 1 stETH - function stEthPerToken() external view returns (uint256); + /// @notice Get amount of stETH for the givern amount of wstETH + /// @return Amount of stETH + function getStETHByWstETH(uint256 _wstETHAmount) external view returns (uint256); +} + +/// @author dzhon +/// @notice A subset of AccountingOracle interface of core LIDO protocol. +interface IAccountingOracle { + /// @notice Get timetamp of the Consensus Layer genesis + function GENESIS_TIME() external view returns (uint256); + /// @notice Get seconds per single Consensus Layer slot + function SECONDS_PER_SLOT() external view returns (uint256); + /// @notice Returns the last reference slot for which processing of the report was started + function getLastProcessingRefSlot() external view returns (uint256); } /// @author kovalgek /// @notice Hides wstETH concept from other contracts to keep `L1ERC20ExtendedTokensBridge` reusable. contract L1LidoTokensBridge is L1ERC20ExtendedTokensBridge, Versioned { + /// @notice Timetamp of the Consensus Layer genesis + uint256 public immutable GENESIS_TIME; + + /// @notice Seconds per single Consensus Layer slot + uint256 public immutable SECONDS_PER_SLOT; + + /// @notice Address of the AccountingOracle instance + address public immutable ACCOUNTING_ORACLE; + + /// @notice Token rate decimals to push + uint256 public constant TOKEN_RATE_DECIMALS = 27; + /// @param messenger_ L1 messenger address being used for cross-chain communications /// @param l2TokenBridge_ Address of the corresponding L2 bridge /// @param l1TokenNonRebasable_ Address of the bridged token in the L1 chain /// @param l1TokenRebasable_ Address of the bridged token in the L1 chain /// @param l2TokenNonRebasable_ Address of the token minted on the L2 chain when token bridged /// @param l2TokenRebasable_ Address of the token minted on the L2 chain when token bridged + /// @param accountingOracle_ Address of the AccountingOracle instance to retrieve rate update timestamps constructor( address messenger_, address l2TokenBridge_, address l1TokenNonRebasable_, address l1TokenRebasable_, address l2TokenNonRebasable_, - address l2TokenRebasable_ + address l2TokenRebasable_, + address accountingOracle_ ) L1ERC20ExtendedTokensBridge( messenger_, l2TokenBridge_, @@ -39,6 +64,9 @@ contract L1LidoTokensBridge is L1ERC20ExtendedTokensBridge, Versioned { l2TokenNonRebasable_, l2TokenRebasable_ ) { + ACCOUNTING_ORACLE = accountingOracle_; + GENESIS_TIME = IAccountingOracle(ACCOUNTING_ORACLE).GENESIS_TIME(); + SECONDS_PER_SLOT = IAccountingOracle(ACCOUNTING_ORACLE).SECONDS_PER_SLOT(); } /// @notice Initializes the contract from scratch. @@ -56,7 +84,11 @@ contract L1LidoTokensBridge is L1ERC20ExtendedTokensBridge, Versioned { _initializeContractVersionTo(2); } - function _tokenRate() override internal view returns (uint256) { - return IERC20WstETH(L1_TOKEN_NON_REBASABLE).stEthPerToken(); + function tokenRate() override public view returns (uint256 rate, uint256 updateTimestamp) { + rate = IERC20WstETH(L1_TOKEN_NON_REBASABLE).getStETHByWstETH(10 ** TOKEN_RATE_DECIMALS); + + updateTimestamp = GENESIS_TIME + SECONDS_PER_SLOT * IAccountingOracle( + ACCOUNTING_ORACLE + ).getLastProcessingRefSlot(); } } diff --git a/contracts/optimism/OpStackTokenRatePusher.sol b/contracts/optimism/OpStackTokenRatePusher.sol index c2ee66ed..b1c89a98 100644 --- a/contracts/optimism/OpStackTokenRatePusher.sol +++ b/contracts/optimism/OpStackTokenRatePusher.sol @@ -9,6 +9,13 @@ import {ITokenRatePusher} from "../lido/interfaces/ITokenRatePusher.sol"; import {IERC20WstETH} from "./L1LidoTokensBridge.sol"; import {ITokenRateUpdatable} from "../optimism/interfaces/ITokenRateUpdatable.sol"; +/// @author dzhon +/// @notice Extracts of the L1ERC20ExtendedTokensBridge contract to support token rate retrieving +interface IL1ERC20ExtendedTokensBridge { + /// @notice returns token rate and its update timestamp + function tokenRate() external view returns (uint256 rate, uint256 updateTimestamp); +} + /// @author kovalgek /// @notice Pushes token rate to L2 Oracle. contract OpStackTokenRatePusher is CrossDomainEnabled, ERC165, ITokenRatePusher { @@ -16,8 +23,8 @@ contract OpStackTokenRatePusher is CrossDomainEnabled, ERC165, ITokenRatePusher /// @notice Oracle address on L2 for receiving token rate. address public immutable L2_TOKEN_RATE_ORACLE; - /// @notice Non-rebasable token of Core Lido procotol. - address public immutable WSTETH; + /// @notice L1 token bridge + address public immutable L1_TOKEN_BRIDGE; /// @notice Gas limit for L2 required to finish pushing token rate on L2 side. /// Client pays for gas on L2 by burning it on L1. @@ -27,29 +34,25 @@ contract OpStackTokenRatePusher is CrossDomainEnabled, ERC165, ITokenRatePusher uint32 public immutable L2_GAS_LIMIT_FOR_PUSHING_TOKEN_RATE; /// @param messenger_ L1 messenger address being used for cross-chain communications - /// @param wstEth_ Non-rebasable token of Core Lido procotol. + /// @param l1TokenBridge_ L1 token bridge address /// @param tokenRateOracle_ Oracle address on L2 for receiving token rate. /// @param l2GasLimitForPushingTokenRate_ Gas limit required to complete pushing token rate on L2. constructor( address messenger_, - address wstEth_, + address l1TokenBridge_, address tokenRateOracle_, uint32 l2GasLimitForPushingTokenRate_ ) CrossDomainEnabled(messenger_) { - WSTETH = wstEth_; + L1_TOKEN_BRIDGE = l1TokenBridge_; L2_TOKEN_RATE_ORACLE = tokenRateOracle_; L2_GAS_LIMIT_FOR_PUSHING_TOKEN_RATE = l2GasLimitForPushingTokenRate_; } /// @inheritdoc ITokenRatePusher function pushTokenRate() external { - uint256 tokenRate = IERC20WstETH(WSTETH).stEthPerToken(); + (uint256 rate, uint256 updateTimestamp) = IL1ERC20ExtendedTokensBridge(L1_TOKEN_BRIDGE).tokenRate(); - bytes memory message = abi.encodeWithSelector( - ITokenRateUpdatable.updateRate.selector, - tokenRate, - block.timestamp - ); + bytes memory message = abi.encodeWithSelector(ITokenRateUpdatable.updateRate.selector, rate, updateTimestamp); sendCrossDomainMessage(L2_TOKEN_RATE_ORACLE, L2_GAS_LIMIT_FOR_PUSHING_TOKEN_RATE, message); } diff --git a/contracts/optimism/TokenRateOracle.sol b/contracts/optimism/TokenRateOracle.sol index c50f0427..c92bc406 100644 --- a/contracts/optimism/TokenRateOracle.sol +++ b/contracts/optimism/TokenRateOracle.sol @@ -21,9 +21,11 @@ contract TokenRateOracle is CrossDomainEnabled, ITokenRateOracle, Versioned { /// contract with upgradable proxies struct TokenRateData { /// @notice wstETH/stETH token rate. - uint192 tokenRate; - /// @notice L1 time when token rate was pushed. - uint64 rateL1Timestamp; + uint128 tokenRate; + /// @notice last time when token rate was updated on L1. + uint64 rateUpdateL1Timestamp; + /// @notice last time when token rate was received on L2. + uint64 rateReceiptL2Timestamp; } // occupy a single slot /// @notice A bridge which can update oracle. @@ -46,13 +48,13 @@ contract TokenRateOracle is CrossDomainEnabled, ITokenRateOracle, Versioned { uint256 public constant ONE_DAY_SECONDS = 86400; /// @notice Decimals of the oracle response. - uint8 public constant DECIMALS = 18; + uint8 public constant DECIMALS = 27; /// @notice Max allowed token rate value. - uint256 public constant MAX_ALLOWED_TOKEN_RATE = 1*10 ** 20; + uint256 public constant MAX_ALLOWED_TOKEN_RATE = 10 ** (DECIMALS + 2); /// @notice Min allowed token rate value. - uint256 public constant MIN_ALLOWED_TOKEN_RATE = 1*10 ** 16; + uint256 public constant MIN_ALLOWED_TOKEN_RATE = 10 ** (DECIMALS - 2); /// @notice Basic point scale. uint256 private constant BASIS_POINT_SCALE = 1e4; @@ -94,7 +96,7 @@ contract TokenRateOracle is CrossDomainEnabled, ITokenRateOracle, Versioned { revert ErrorL1TimestampInitializationIsOutOfAllowedRange(rateL1Timestamp_); } _initializeContractVersionTo(1); - _setTokenRateAndL1Timestamp(uint192(tokenRate_), uint64(rateL1Timestamp_)); + _setTokenRateAndTimestamps(uint128(tokenRate_), uint64(rateL1Timestamp_)); } /// @inheritdoc IChainlinkAggregatorInterface @@ -105,20 +107,20 @@ contract TokenRateOracle is CrossDomainEnabled, ITokenRateOracle, Versioned { uint256 updatedAt_, uint80 answeredInRound_ ) { - uint256 rateL1Timestamp = _getRateL1Timestamp(); + TokenRateData memory tokenRateData = _getTokenRateAndTimestamps(); return ( - uint80(rateL1Timestamp), - int256(uint256(_getTokenRate())), - rateL1Timestamp, - rateL1Timestamp, - uint80(rateL1Timestamp) + uint80(tokenRateData.rateUpdateL1Timestamp), + int256(uint256(tokenRateData.tokenRate)), + tokenRateData.rateUpdateL1Timestamp, + tokenRateData.rateReceiptL2Timestamp, + uint80(tokenRateData.rateUpdateL1Timestamp) ); } /// @inheritdoc IChainlinkAggregatorInterface function latestAnswer() external view returns (int256) { - return int256(uint256(_getTokenRate())); + return int256(uint256(_loadTokenRateData().value.tokenRate)); } /// @inheritdoc IChainlinkAggregatorInterface @@ -127,37 +129,52 @@ contract TokenRateOracle is CrossDomainEnabled, ITokenRateOracle, Versioned { } /// @inheritdoc ITokenRateUpdatable - function updateRate(uint256 tokenRate_, uint256 rateL1Timestamp_) external onlyBridgeOrTokenRatePusher { - uint256 currentTokenRate = _getTokenRate(); - uint256 currentRateL1Timestamp = _getRateL1Timestamp(); + function updateRate(uint256 tokenRate_, uint256 rateUpdateL1Timestamp_) external onlyBridgeOrTokenRatePusher { + TokenRateData memory tokenRateData = _getTokenRateAndTimestamps(); /// @dev checks if the clock lag (i.e, time difference) between L1 and L2 exceeds the configurable threshold - if (rateL1Timestamp_ > block.timestamp + MAX_ALLOWED_L2_TO_L1_CLOCK_LAG) { - revert ErrorL1TimestampExceededAllowedClockLag(tokenRate_, rateL1Timestamp_); + if (rateUpdateL1Timestamp_ > block.timestamp + MAX_ALLOWED_L2_TO_L1_CLOCK_LAG) { + revert ErrorL1TimestampExceededAllowedClockLag(tokenRate_, rateUpdateL1Timestamp_); } /// @dev Use only the most up-to-date token rate. Reverting should be avoided as it may occur occasionally. - if (rateL1Timestamp_ <= currentRateL1Timestamp) { - emit DormantTokenRateUpdateIgnored(rateL1Timestamp_, currentRateL1Timestamp); + if (rateUpdateL1Timestamp_ < tokenRateData.rateUpdateL1Timestamp) { + emit DormantTokenRateUpdateIgnored(rateUpdateL1Timestamp_, tokenRateData.rateUpdateL1Timestamp); + return; + } + + /// @dev Bump L2 receipt time, don't touch the rate othwerwise + /// NB: Here we assume that the rate can only be changed together with the token rebase induced + /// by the AccountingOracle report + if (rateUpdateL1Timestamp_ == tokenRateData.rateUpdateL1Timestamp) { + _loadTokenRateData().value.rateReceiptL2Timestamp = uint64(block.timestamp); + emit RateReceiptUpdated(); return; } /// @dev allow token rate to be within some configurable range that depens on time it wasn't updated. - if (tokenRate_ != currentTokenRate && - !_isTokenRateWithinAllowedRange(currentTokenRate, tokenRate_, currentRateL1Timestamp, rateL1Timestamp_)) { - revert ErrorTokenRateIsOutOfRange(tokenRate_, rateL1Timestamp_); + if ((tokenRate_ != tokenRateData.tokenRate) &&!_isTokenRateWithinAllowedRange( + tokenRateData.tokenRate, + tokenRate_, + tokenRateData.rateUpdateL1Timestamp, + rateUpdateL1Timestamp_) + ) { + revert ErrorTokenRateIsOutOfRange(tokenRate_, rateUpdateL1Timestamp_); } /// @dev notify that there is a differnce L1 and L2 time. - if (rateL1Timestamp_ > block.timestamp) emit TokenRateL1TimestampIsInFuture(tokenRate_, rateL1Timestamp_); + if (rateUpdateL1Timestamp_ > block.timestamp) { + emit TokenRateL1TimestampIsInFuture(tokenRate_, rateUpdateL1Timestamp_); + } + + _setTokenRateAndTimestamps(uint128(tokenRate_), uint64(rateUpdateL1Timestamp_)); - _setTokenRateAndL1Timestamp(uint192(tokenRate_), uint64(rateL1Timestamp_)); - emit RateUpdated(tokenRate_, rateL1Timestamp_); + emit RateUpdated(tokenRate_, rateUpdateL1Timestamp_); } /// @notice Returns flag that shows that token rate can be considered outdated. function isLikelyOutdated() external view returns (bool) { - return block.timestamp > _getRateL1Timestamp() + TOKEN_RATE_OUTDATED_DELAY; + return block.timestamp > _loadTokenRateData().value.rateReceiptL2Timestamp + TOKEN_RATE_OUTDATED_DELAY; } /// @notice Allow tokenRate deviation from the previous value to be @@ -216,24 +233,24 @@ contract TokenRateOracle is CrossDomainEnabled, ITokenRateOracle, Versioned { return false; } - function _setTokenRateAndL1Timestamp(uint192 tokenRate_, uint64 rateL1Timestamp_) internal { - _loadTokenRateData().tokenRate = tokenRate_; - _loadTokenRateData().rateL1Timestamp = rateL1Timestamp_; + function _setTokenRateAndTimestamps(uint128 tokenRate_, uint64 rateUpdateL1Timestamp_) internal { + _loadTokenRateData().value = TokenRateData(tokenRate_, rateUpdateL1Timestamp_, uint64(block.timestamp)); } - function _getTokenRate() private view returns (uint192) { - return _loadTokenRateData().tokenRate; + function _getTokenRateAndTimestamps() private view returns (TokenRateData memory tokenRateData) { + tokenRateData = _loadTokenRateData().value; } - function _getRateL1Timestamp() private view returns (uint64) { - return _loadTokenRateData().rateL1Timestamp; + /// @dev Storage helper for TokenRateData + struct StorageTokenRateData { + TokenRateData value; } /// @dev Returns the reference to the slot with TokenRateData struct function _loadTokenRateData() private pure - returns (TokenRateData storage r) + returns (StorageTokenRateData storage r) { bytes32 slot = TOKEN_RATE_DATA_SLOT; assembly { @@ -252,6 +269,7 @@ contract TokenRateOracle is CrossDomainEnabled, ITokenRateOracle, Versioned { uint256 tokenRate_, uint256 indexed rateL1Timestamp_ ); + event RateReceiptUpdated(); event DormantTokenRateUpdateIgnored( uint256 indexed newRateL1Timestamp_, uint256 indexed currentRateL1Timestamp_ diff --git a/contracts/optimism/interfaces/ITokenRateUpdatable.sol b/contracts/optimism/interfaces/ITokenRateUpdatable.sol index c14461ac..06505412 100644 --- a/contracts/optimism/interfaces/ITokenRateUpdatable.sol +++ b/contracts/optimism/interfaces/ITokenRateUpdatable.sol @@ -8,6 +8,6 @@ pragma solidity 0.8.10; interface ITokenRateUpdatable { /// @notice Updates token rate. /// @param tokenRate_ wstETH/stETH token rate. - /// @param rateL1Timestamp_ L1 time when rate was pushed on L1 side. - function updateRate(uint256 tokenRate_, uint256 rateL1Timestamp_) external; + /// @param rateUpdateL1Timestamp_ L1 time when rate was updated on L1 side. + function updateRate(uint256 tokenRate_, uint256 rateUpdateL1Timestamp_) external; } diff --git a/contracts/stubs/ERC20WrapperStub.sol b/contracts/stubs/ERC20WrapperStub.sol index 167d796b..a58bc2bc 100644 --- a/contracts/stubs/ERC20WrapperStub.sol +++ b/contracts/stubs/ERC20WrapperStub.sol @@ -46,7 +46,7 @@ contract ERC20WrapperStub is IERC20Wrapper, IERC20WstETH, ERC20 { return stETHAmount; } - function stEthPerToken() external view returns (uint256) { - return tokensRate; + function getStETHByWstETH(uint256 _wstETHAmount) external view returns (uint256) { + return (tokensRate * 10**27) / _wstETHAmount; } } From 70198757fe19ebdd722fb8198de47e1712293410 Mon Sep 17 00:00:00 2001 From: Anton Kovalchuk Date: Mon, 20 May 2024 22:32:07 +0200 Subject: [PATCH 124/148] split l1 bridge and token pusher connection to be able to deploy oracle part independently --- contracts/lib/DepositDataCodec.sol | 2 +- contracts/lido/stubs/AccountingOracleStub.sol | 32 ++++++++++ .../optimism/L1ERC20ExtendedTokensBridge.sol | 2 +- contracts/optimism/L1LidoTokensBridge.sol | 49 +++------------ contracts/optimism/OpStackTokenRatePusher.sol | 25 +++----- .../TokenRateAndUpdateTimestampProvider.sol | 60 +++++++++++++++++++ contracts/optimism/TokenRateOracle.sol | 29 ++++----- contracts/stubs/ERC20WrapperStub.sol | 11 ++-- 8 files changed, 125 insertions(+), 85 deletions(-) create mode 100644 contracts/lido/stubs/AccountingOracleStub.sol create mode 100644 contracts/optimism/TokenRateAndUpdateTimestampProvider.sol diff --git a/contracts/lib/DepositDataCodec.sol b/contracts/lib/DepositDataCodec.sol index 81d3a3fb..94403871 100644 --- a/contracts/lib/DepositDataCodec.sol +++ b/contracts/lib/DepositDataCodec.sol @@ -31,7 +31,7 @@ library DepositDataCodec { } DepositData memory depositData = DepositData({ - rate: uint96(bytes12(buffer[0:RATE_FIELD_SIZE])), + rate: uint128(bytes16(buffer[0:RATE_FIELD_SIZE])), timestamp: uint40(bytes5(buffer[RATE_FIELD_SIZE:RATE_FIELD_SIZE + TIMESTAMP_FIELD_SIZE])), data: buffer[RATE_FIELD_SIZE + TIMESTAMP_FIELD_SIZE:] }); diff --git a/contracts/lido/stubs/AccountingOracleStub.sol b/contracts/lido/stubs/AccountingOracleStub.sol new file mode 100644 index 00000000..11f58942 --- /dev/null +++ b/contracts/lido/stubs/AccountingOracleStub.sol @@ -0,0 +1,32 @@ +// SPDX-FileCopyrightText: 2024 Lido +// SPDX-License-Identifier: GPL-3.0 + +pragma solidity 0.8.10; + +import {IAccountingOracle} from "../../optimism/TokenRateAndUpdateTimestampProvider.sol"; + +/// @dev For testing purposes. +contract AccountingOracleStub is IAccountingOracle { + + uint256 private immutable genesisTime; + uint256 private immutable secondsPerSlot; + uint256 private immutable lastProcessingRefSlot; + + constructor(uint256 genesisTime_, uint256 secondsPerSlot_, uint256 lastProcessingRefSlot_) { + genesisTime = genesisTime_; + secondsPerSlot = secondsPerSlot_; + lastProcessingRefSlot = lastProcessingRefSlot_; + } + + function GENESIS_TIME() external view returns (uint256) { + return genesisTime; + } + + function SECONDS_PER_SLOT() external view returns (uint256) { + return secondsPerSlot; + } + + function getLastProcessingRefSlot() external view returns (uint256) { + return lastProcessingRefSlot; + } +} diff --git a/contracts/optimism/L1ERC20ExtendedTokensBridge.sol b/contracts/optimism/L1ERC20ExtendedTokensBridge.sol index 0a08b65a..658e335d 100644 --- a/contracts/optimism/L1ERC20ExtendedTokensBridge.sol +++ b/contracts/optimism/L1ERC20ExtendedTokensBridge.sol @@ -172,7 +172,7 @@ abstract contract L1ERC20ExtendedTokensBridge is /// @param data_ Optional data to forward to L2. /// @return encoded data in the 'wired' bytes form. function _encodeInputDepositData(bytes calldata data_) internal view returns (bytes memory) { - (uint256 timestamp, uint256 rate) = tokenRate(); + (uint256 rate, uint256 timestamp) = tokenRate(); return DepositDataCodec.encodeDepositData(DepositDataCodec.DepositData({ rate: uint128(rate), timestamp: uint40(timestamp), diff --git a/contracts/optimism/L1LidoTokensBridge.sol b/contracts/optimism/L1LidoTokensBridge.sol index e25a4319..1232c6e4 100644 --- a/contracts/optimism/L1LidoTokensBridge.sol +++ b/contracts/optimism/L1LidoTokensBridge.sol @@ -5,41 +5,11 @@ pragma solidity 0.8.10; import {L1ERC20ExtendedTokensBridge} from "./L1ERC20ExtendedTokensBridge.sol"; import {Versioned} from "../utils/Versioned.sol"; - -/// @author kovalgek -/// @notice A subset of wstETH token interface of core LIDO protocol. -interface IERC20WstETH { - /// @notice Get amount of stETH for the givern amount of wstETH - /// @return Amount of stETH - function getStETHByWstETH(uint256 _wstETHAmount) external view returns (uint256); -} - -/// @author dzhon -/// @notice A subset of AccountingOracle interface of core LIDO protocol. -interface IAccountingOracle { - /// @notice Get timetamp of the Consensus Layer genesis - function GENESIS_TIME() external view returns (uint256); - /// @notice Get seconds per single Consensus Layer slot - function SECONDS_PER_SLOT() external view returns (uint256); - /// @notice Returns the last reference slot for which processing of the report was started - function getLastProcessingRefSlot() external view returns (uint256); -} +import {TokenRateAndUpdateTimestampProvider} from "./TokenRateAndUpdateTimestampProvider.sol"; /// @author kovalgek /// @notice Hides wstETH concept from other contracts to keep `L1ERC20ExtendedTokensBridge` reusable. -contract L1LidoTokensBridge is L1ERC20ExtendedTokensBridge, Versioned { - - /// @notice Timetamp of the Consensus Layer genesis - uint256 public immutable GENESIS_TIME; - - /// @notice Seconds per single Consensus Layer slot - uint256 public immutable SECONDS_PER_SLOT; - - /// @notice Address of the AccountingOracle instance - address public immutable ACCOUNTING_ORACLE; - - /// @notice Token rate decimals to push - uint256 public constant TOKEN_RATE_DECIMALS = 27; +contract L1LidoTokensBridge is L1ERC20ExtendedTokensBridge, TokenRateAndUpdateTimestampProvider, Versioned { /// @param messenger_ L1 messenger address being used for cross-chain communications /// @param l2TokenBridge_ Address of the corresponding L2 bridge @@ -63,11 +33,10 @@ contract L1LidoTokensBridge is L1ERC20ExtendedTokensBridge, Versioned { l1TokenRebasable_, l2TokenNonRebasable_, l2TokenRebasable_ - ) { - ACCOUNTING_ORACLE = accountingOracle_; - GENESIS_TIME = IAccountingOracle(ACCOUNTING_ORACLE).GENESIS_TIME(); - SECONDS_PER_SLOT = IAccountingOracle(ACCOUNTING_ORACLE).SECONDS_PER_SLOT(); - } + ) TokenRateAndUpdateTimestampProvider( + l1TokenNonRebasable_, + accountingOracle_ + ) {} /// @notice Initializes the contract from scratch. /// @param admin_ Address of the account to grant the DEFAULT_ADMIN_ROLE @@ -85,10 +54,6 @@ contract L1LidoTokensBridge is L1ERC20ExtendedTokensBridge, Versioned { } function tokenRate() override public view returns (uint256 rate, uint256 updateTimestamp) { - rate = IERC20WstETH(L1_TOKEN_NON_REBASABLE).getStETHByWstETH(10 ** TOKEN_RATE_DECIMALS); - - updateTimestamp = GENESIS_TIME + SECONDS_PER_SLOT * IAccountingOracle( - ACCOUNTING_ORACLE - ).getLastProcessingRefSlot(); + return getTokenRateAndUpdateTimestamp(); } } diff --git a/contracts/optimism/OpStackTokenRatePusher.sol b/contracts/optimism/OpStackTokenRatePusher.sol index b1c89a98..c2796f71 100644 --- a/contracts/optimism/OpStackTokenRatePusher.sol +++ b/contracts/optimism/OpStackTokenRatePusher.sol @@ -6,26 +6,16 @@ pragma solidity 0.8.10; import {ERC165} from "@openzeppelin/contracts/utils/introspection/ERC165.sol"; import {CrossDomainEnabled} from "./CrossDomainEnabled.sol"; import {ITokenRatePusher} from "../lido/interfaces/ITokenRatePusher.sol"; -import {IERC20WstETH} from "./L1LidoTokensBridge.sol"; import {ITokenRateUpdatable} from "../optimism/interfaces/ITokenRateUpdatable.sol"; - -/// @author dzhon -/// @notice Extracts of the L1ERC20ExtendedTokensBridge contract to support token rate retrieving -interface IL1ERC20ExtendedTokensBridge { - /// @notice returns token rate and its update timestamp - function tokenRate() external view returns (uint256 rate, uint256 updateTimestamp); -} +import {TokenRateAndUpdateTimestampProvider} from "./TokenRateAndUpdateTimestampProvider.sol"; /// @author kovalgek /// @notice Pushes token rate to L2 Oracle. -contract OpStackTokenRatePusher is CrossDomainEnabled, ERC165, ITokenRatePusher { +contract OpStackTokenRatePusher is ERC165, CrossDomainEnabled, TokenRateAndUpdateTimestampProvider, ITokenRatePusher { /// @notice Oracle address on L2 for receiving token rate. address public immutable L2_TOKEN_RATE_ORACLE; - /// @notice L1 token bridge - address public immutable L1_TOKEN_BRIDGE; - /// @notice Gas limit for L2 required to finish pushing token rate on L2 side. /// Client pays for gas on L2 by burning it on L1. /// Depends linearly on deposit data length and gas used for finalizing deposit on L2. @@ -34,23 +24,24 @@ contract OpStackTokenRatePusher is CrossDomainEnabled, ERC165, ITokenRatePusher uint32 public immutable L2_GAS_LIMIT_FOR_PUSHING_TOKEN_RATE; /// @param messenger_ L1 messenger address being used for cross-chain communications - /// @param l1TokenBridge_ L1 token bridge address + /// @param wstETH_ L1 token bridge address + /// @param accountingOracle_ L1 token bridge address /// @param tokenRateOracle_ Oracle address on L2 for receiving token rate. /// @param l2GasLimitForPushingTokenRate_ Gas limit required to complete pushing token rate on L2. constructor( address messenger_, - address l1TokenBridge_, + address wstETH_, + address accountingOracle_, address tokenRateOracle_, uint32 l2GasLimitForPushingTokenRate_ - ) CrossDomainEnabled(messenger_) { - L1_TOKEN_BRIDGE = l1TokenBridge_; + ) CrossDomainEnabled(messenger_) TokenRateAndUpdateTimestampProvider(wstETH_, accountingOracle_) { L2_TOKEN_RATE_ORACLE = tokenRateOracle_; L2_GAS_LIMIT_FOR_PUSHING_TOKEN_RATE = l2GasLimitForPushingTokenRate_; } /// @inheritdoc ITokenRatePusher function pushTokenRate() external { - (uint256 rate, uint256 updateTimestamp) = IL1ERC20ExtendedTokensBridge(L1_TOKEN_BRIDGE).tokenRate(); + (uint256 rate, uint256 updateTimestamp) = getTokenRateAndUpdateTimestamp(); bytes memory message = abi.encodeWithSelector(ITokenRateUpdatable.updateRate.selector, rate, updateTimestamp); diff --git a/contracts/optimism/TokenRateAndUpdateTimestampProvider.sol b/contracts/optimism/TokenRateAndUpdateTimestampProvider.sol new file mode 100644 index 00000000..53674b42 --- /dev/null +++ b/contracts/optimism/TokenRateAndUpdateTimestampProvider.sol @@ -0,0 +1,60 @@ +// SPDX-FileCopyrightText: 2024 Lido +// SPDX-License-Identifier: GPL-3.0 + +pragma solidity 0.8.10; + +/// @author dzhon +/// @notice A subset of AccountingOracle interface of core LIDO protocol. +interface IAccountingOracle { + /// @notice Get timestamp of the Consensus Layer genesis + function GENESIS_TIME() external view returns (uint256); + /// @notice Get seconds per single Consensus Layer slot + function SECONDS_PER_SLOT() external view returns (uint256); + /// @notice Returns the last reference slot for which processing of the report was started + function getLastProcessingRefSlot() external view returns (uint256); +} + +/// @author kovalgek +/// @notice A subset of wstETH token interface of core LIDO protocol. +interface IERC20WstETH { + /// @notice Get amount of stETH for a given amount of wstETH + /// @param wstETHAmount_ amount of wstETH + /// @return Amount of stETH for a given wstETH amount + function getStETHByWstETH(uint256 wstETHAmount_) external view returns (uint256); +} + +/// @author kovalgek +/// @notice Provides token rate and update timestamp. +abstract contract TokenRateAndUpdateTimestampProvider { + + /// @notice Non-rebasable token of Core Lido procotol. + address public immutable WSTETH; + + /// @notice Address of the AccountingOracle instance + address public immutable ACCOUNTING_ORACLE; + + /// @notice Timetamp of the Consensus Layer genesis + uint256 public immutable GENESIS_TIME; + + /// @notice Seconds per single Consensus Layer slot + uint256 public immutable SECONDS_PER_SLOT; + + /// @notice Token rate decimals to push + uint256 public constant TOKEN_RATE_DECIMALS = 27; + + constructor(address wstETH_, address accountingOracle_) { + WSTETH = wstETH_; + ACCOUNTING_ORACLE = accountingOracle_; + GENESIS_TIME = IAccountingOracle(ACCOUNTING_ORACLE).GENESIS_TIME(); + SECONDS_PER_SLOT = IAccountingOracle(ACCOUNTING_ORACLE).SECONDS_PER_SLOT(); + } + + function getTokenRateAndUpdateTimestamp() internal view returns (uint256 rate, uint256 updateTimestamp) { + rate = IERC20WstETH(WSTETH).getStETHByWstETH(10 ** TOKEN_RATE_DECIMALS); + + /// @dev github.com/ethereum/consensus-specs/blob/dev/specs/bellatrix/beacon-chain.md#compute_timestamp_at_slot + updateTimestamp = GENESIS_TIME + SECONDS_PER_SLOT * IAccountingOracle( + ACCOUNTING_ORACLE + ).getLastProcessingRefSlot(); + } +} diff --git a/contracts/optimism/TokenRateOracle.sol b/contracts/optimism/TokenRateOracle.sol index 13502843..88bede34 100644 --- a/contracts/optimism/TokenRateOracle.sol +++ b/contracts/optimism/TokenRateOracle.sol @@ -26,8 +26,8 @@ contract TokenRateOracle is CrossDomainEnabled, ITokenRateOracle, Versioned { /// @notice last time when token rate was updated on L1. uint64 rateUpdateL1Timestamp; /// @notice last time when token rate was received on L2. - uint64 rateReceiptL2Timestamp; - } // occupy a single slot + uint64 rateReceivedL2Timestamp; + } // occupies a single slot /// @notice A bridge which can update oracle. address public immutable L2_ERC20_TOKEN_BRIDGE; @@ -114,7 +114,7 @@ contract TokenRateOracle is CrossDomainEnabled, ITokenRateOracle, Versioned { uint80(tokenRateData.rateUpdateL1Timestamp), int256(uint256(tokenRateData.tokenRate)), tokenRateData.rateUpdateL1Timestamp, - tokenRateData.rateReceiptL2Timestamp, + tokenRateData.rateReceivedL2Timestamp, uint80(tokenRateData.rateUpdateL1Timestamp) ); } @@ -148,8 +148,8 @@ contract TokenRateOracle is CrossDomainEnabled, ITokenRateOracle, Versioned { /// NB: Here we assume that the rate can only be changed together with the token rebase induced /// by the AccountingOracle report if (rateUpdateL1Timestamp_ == tokenRateData.rateUpdateL1Timestamp) { - _loadTokenRateData().value.rateReceiptL2Timestamp = uint64(block.timestamp); - emit RateReceiptUpdated(); + _loadTokenRateData().value.rateReceivedL2Timestamp = uint64(block.timestamp); + emit RateReceivedUpdated(block.timestamp); return; } @@ -175,7 +175,7 @@ contract TokenRateOracle is CrossDomainEnabled, ITokenRateOracle, Versioned { /// @notice Returns flag that shows that token rate can be considered outdated. function isLikelyOutdated() external view returns (bool) { - return block.timestamp > _loadTokenRateData().value.rateReceiptL2Timestamp + TOKEN_RATE_OUTDATED_DELAY; + return block.timestamp > _loadTokenRateData().value.rateReceivedL2Timestamp + TOKEN_RATE_OUTDATED_DELAY; } /// @notice Allow tokenRate deviation from the previous value to be @@ -266,19 +266,10 @@ contract TokenRateOracle is CrossDomainEnabled, ITokenRateOracle, Versioned { _; } - event RateUpdated( - uint256 tokenRate_, - uint256 indexed rateL1Timestamp_ - ); - event RateReceiptUpdated(); - event DormantTokenRateUpdateIgnored( - uint256 indexed newRateL1Timestamp_, - uint256 indexed currentRateL1Timestamp_ - ); - event TokenRateL1TimestampIsInFuture( - uint256 tokenRate_, - uint256 indexed rateL1Timestamp_ - ); + event RateUpdated(uint256 tokenRate_, uint256 indexed rateL1Timestamp_); + event RateReceivedUpdated(uint256 indexed rateReceivedL2Timestamp); + event DormantTokenRateUpdateIgnored(uint256 indexed newRateL1Timestamp_, uint256 indexed currentRateL1Timestamp_); + event TokenRateL1TimestampIsInFuture(uint256 tokenRate_, uint256 indexed rateL1Timestamp_); error ErrorNotBridgeOrTokenRatePusher(); error ErrorL1TimestampExceededAllowedClockLag(uint256 tokenRate_, uint256 rateL1Timestamp_); diff --git a/contracts/stubs/ERC20WrapperStub.sol b/contracts/stubs/ERC20WrapperStub.sol index a58bc2bc..64c97974 100644 --- a/contracts/stubs/ERC20WrapperStub.sol +++ b/contracts/stubs/ERC20WrapperStub.sol @@ -6,7 +6,7 @@ pragma solidity 0.8.10; import {IERC20Bridged} from "../token/ERC20Bridged.sol"; import {ERC20} from "@openzeppelin/contracts/token/ERC20/ERC20.sol"; import {IERC20} from "@openzeppelin/contracts/token/ERC20/IERC20.sol"; -import {IERC20WstETH} from "../optimism/L1LidoTokensBridge.sol"; +import {IERC20WstETH} from "../optimism/TokenRateAndUpdateTimestampProvider.sol"; import {IERC20Wrapper} from "../token/interfaces/IERC20Wrapper.sol"; /// @dev represents wstETH on L1. For testing purposes. @@ -15,19 +15,20 @@ contract ERC20WrapperStub is IERC20Wrapper, IERC20WstETH, ERC20 { IERC20 public stETH; address public bridge; uint256 public tokensRate; + uint256 private constant DECIMALS = 27; constructor(IERC20 stETH_, string memory name_, string memory symbol_, uint256 tokensRate_) ERC20(name_, symbol_) { stETH = stETH_; tokensRate = tokensRate_; - _mint(msg.sender, 1000000 * 10**18); + _mint(msg.sender, 1000000 * 10**DECIMALS); } function wrap(uint256 _stETHAmount) external returns (uint256) { require(_stETHAmount > 0, "wstETH: can't wrap zero stETH"); - uint256 wstETHAmount = (_stETHAmount * (10 ** uint256(decimals()))) / tokensRate; + uint256 wstETHAmount = (_stETHAmount * (10 ** DECIMALS)) / tokensRate; _mint(msg.sender, wstETHAmount); stETH.transferFrom(msg.sender, address(this), _stETHAmount); @@ -38,7 +39,7 @@ contract ERC20WrapperStub is IERC20Wrapper, IERC20WstETH, ERC20 { function unwrap(uint256 _wstETHAmount) external returns (uint256) { require(_wstETHAmount > 0, "wstETH: zero amount unwrap not allowed"); - uint256 stETHAmount = (_wstETHAmount * tokensRate) / (10 ** uint256(decimals())); + uint256 stETHAmount = (_wstETHAmount * tokensRate) / (10 ** DECIMALS); _burn(msg.sender, _wstETHAmount); stETH.transfer(msg.sender, stETHAmount); @@ -47,6 +48,6 @@ contract ERC20WrapperStub is IERC20Wrapper, IERC20WstETH, ERC20 { } function getStETHByWstETH(uint256 _wstETHAmount) external view returns (uint256) { - return (tokensRate * 10**27) / _wstETHAmount; + return (tokensRate * 10**DECIMALS) / _wstETHAmount; } } From c2d365053fd5d5827bec47ce24e00c1788e1f030 Mon Sep 17 00:00:00 2001 From: Anton Kovalchuk Date: Mon, 20 May 2024 23:46:17 +0200 Subject: [PATCH 125/148] fix tests --- scripts/optimism/deploy-oracle.ts | 3 +- .../optimism.integration.test.ts | 10 +- test/optimism/L1LidoTokensBridge.unit.test.ts | 335 +++++++++++------- .../L2ERC20ExtendedTokensBridge.unit.test.ts | 25 +- .../OpStackTokenRatePusher.unit.test.ts | 59 +-- test/optimism/TokenRateNotifier.unit.test.ts | 98 +++-- test/optimism/TokenRateOracle.unit.test.ts | 174 +++++---- test/optimism/_launch.test.ts | 2 +- ...bridging-non-rebasable.integration.test.ts | 39 +- .../bridging-rebasable.integration.test.ts | 37 +- test/optimism/deposit-gas-estimation.test.ts | 4 +- test/optimism/pushingTokenRate.e2e.test.ts | 2 +- .../pushingTokenRate.integration.test.ts | 79 +++-- test/token/ERC20Permit.unit.test.ts | 2 +- .../ERC20RebasableBridgedPermit.unit.test.ts | 6 +- utils/deployment.ts | 2 + utils/optimism/deploymentAllFromScratch.ts | 3 + utils/optimism/deploymentOracle.ts | 2 + utils/optimism/testing.ts | 13 + utils/testing/contractsFactory.ts | 199 ++++++----- utils/testing/helpers.ts | 96 +++++ 21 files changed, 715 insertions(+), 475 deletions(-) create mode 100644 utils/testing/helpers.ts diff --git a/scripts/optimism/deploy-oracle.ts b/scripts/optimism/deploy-oracle.ts index e3717606..88013720 100644 --- a/scripts/optimism/deploy-oracle.ts +++ b/scripts/optimism/deploy-oracle.ts @@ -28,6 +28,7 @@ async function main() { .deploymentOracle(networkName, { logger: console }) .oracleDeployScript( deploymentConfig.l1Token, + deploymentConfig.accountingOracle, deploymentConfig.l2GasLimitForPushingTokenRate, deploymentConfig.tokenRateOutdatedDelay, { @@ -48,7 +49,7 @@ async function main() { tokenRateOracle: { maxAllowedL2ToL1ClockLag: 86400, maxAllowedTokenRateDeviationPerDay: 500, - tokenRate: 1164454276599657236, + tokenRate: 1164454276599657236000000000, l1Timestamp: blockTimestamp } } diff --git a/test/bridge-executor/optimism.integration.test.ts b/test/bridge-executor/optimism.integration.test.ts index 189bc740..c573d5e2 100644 --- a/test/bridge-executor/optimism.integration.test.ts +++ b/test/bridge-executor/optimism.integration.test.ts @@ -7,6 +7,7 @@ import { OptimismBridgeExecutor__factory, ERC20BridgedPermit__factory, ERC20WrapperStub__factory, + AccountingOracleStub__factory } from "../../typechain"; import { wei } from "../../utils/wei"; import optimism from "../../utils/optimism"; @@ -218,9 +219,11 @@ async function ctxFactory() { l1Token.address, "Test Token", "TT", - BigNumber.from('1164454276599657236') + BigNumber.from('1164454276599657236000000000') ); + const accountingOracle = await new AccountingOracleStub__factory(l1Deployer).deploy(1,2,3); + const optAddresses = optimism.addresses(networkName); const testingOnDeployedContracts = testing.env.USE_DEPLOYED_CONTRACTS(false); @@ -244,6 +247,7 @@ async function ctxFactory() { ).deployAllScript( l1Token.address, l1TokenRebasable.address, + accountingOracle.address, { deployer: l1Deployer, admins: { @@ -260,8 +264,8 @@ async function ctxFactory() { }, contractsShift: 0, tokenRateOracle: { - tokenRate:10, - l1Timestamp:2 + tokenRate: BigNumber.from(10), + l1Timestamp:BigNumber.from(2) } } ); diff --git a/test/optimism/L1LidoTokensBridge.unit.test.ts b/test/optimism/L1LidoTokensBridge.unit.test.ts index 9febc29d..dda49b88 100644 --- a/test/optimism/L1LidoTokensBridge.unit.test.ts +++ b/test/optimism/L1LidoTokensBridge.unit.test.ts @@ -9,13 +9,14 @@ import { L2ERC20ExtendedTokensBridge__factory, OssifiableProxy__factory, EmptyContractStub__factory, - ERC20WrapperStub + AccountingOracleStub__factory, + L1LidoTokensBridge } from "../../typechain"; -import { JsonRpcProvider } from "@ethersproject/providers"; import { CrossDomainMessengerStub__factory } from "../../typechain/factories/CrossDomainMessengerStub__factory"; import testing, { unit } from "../../utils/testing"; import { wei } from "../../utils/wei"; import { SignerWithAddress } from "@nomiclabs/hardhat-ethers/signers"; +import { tokenRateAndTimestampPacked, refSlotTimestamp } from "../../utils/testing/helpers"; unit("Optimism :: L1LidoTokensBridge", ctxFactory) @@ -30,38 +31,76 @@ unit("Optimism :: L1LidoTokensBridge", ctxFactory) .test("initialize() :: petrified", async (ctx) => { const { deployer, l2TokenBridgeEOA } = ctx.accounts; - - const l1LidoTokensBridgeImpl = await getL1LidoTokensBridgeImpl(deployer, l2TokenBridgeEOA.address); + const { + tokenRate, + genesisTime, + secondsPerSlot, + lastProcessingRefSlot + } = ctx.constants; + + const { l1TokenBridgeImpl } = await getL1LidoTokensBridgeImpl( + tokenRate, + genesisTime, + secondsPerSlot, + lastProcessingRefSlot, + deployer, + l2TokenBridgeEOA.address + ); const petrifiedVersionMark = hre.ethers.constants.MaxUint256; - assert.equalBN(await l1LidoTokensBridgeImpl.getContractVersion(), petrifiedVersionMark); + assert.equalBN(await l1TokenBridgeImpl.getContractVersion(), petrifiedVersionMark); await assert.revertsWith( - l1LidoTokensBridgeImpl.initialize(deployer.address), + l1TokenBridgeImpl.initialize(deployer.address), "NonZeroContractVersionOnInit()" ); }) .test("initialize() :: zero address L2 bridge", async (ctx) => { const { deployer } = ctx.accounts; + const { + tokenRate, + genesisTime, + secondsPerSlot, + lastProcessingRefSlot + } = ctx.constants; await assert.revertsWith( - getL1LidoTokensBridgeImpl(deployer, hre.ethers.constants.AddressZero), + getL1LidoTokensBridgeImpl(tokenRate, + genesisTime, + secondsPerSlot, + lastProcessingRefSlot, + deployer, + hre.ethers.constants.AddressZero + ), "ErrorZeroAddressL2Bridge()" ); }) .test("initialize() :: don't allow to initialize twice", async (ctx) => { const { deployer, l2TokenBridgeEOA } = ctx.accounts; - - const l1LidoTokensBridgeImpl = await getL1LidoTokensBridgeImpl(deployer, l2TokenBridgeEOA.address); + const { + tokenRate, + genesisTime, + secondsPerSlot, + lastProcessingRefSlot + } = ctx.constants; + + const { l1TokenBridgeImpl } = await getL1LidoTokensBridgeImpl( + tokenRate, + genesisTime, + secondsPerSlot, + lastProcessingRefSlot, + deployer, + l2TokenBridgeEOA.address + ); const l1TokenBridgeProxy = await new OssifiableProxy__factory( deployer ).deploy( - l1LidoTokensBridgeImpl.address, + l1TokenBridgeImpl.address, deployer.address, - l1LidoTokensBridgeImpl.interface.encodeFunctionData("initialize", [ + l1TokenBridgeImpl.interface.encodeFunctionData("initialize", [ deployer.address ]) ); @@ -81,11 +120,24 @@ unit("Optimism :: L1LidoTokensBridge", ctxFactory) .test("finalizeUpgrade_v2() :: bridging manager uninitialized", async (ctx) => { const { deployer, l2TokenBridgeEOA } = ctx.accounts; - - const l1LidoTokensBridgeImpl = await getL1LidoTokensBridgeImpl(deployer, l2TokenBridgeEOA.address); + const { + tokenRate, + genesisTime, + secondsPerSlot, + lastProcessingRefSlot + } = ctx.constants; + + const { l1TokenBridgeImpl } = await getL1LidoTokensBridgeImpl( + tokenRate, + genesisTime, + secondsPerSlot, + lastProcessingRefSlot, + deployer, + l2TokenBridgeEOA.address + ); await assert.revertsWith(new OssifiableProxy__factory(deployer).deploy( - l1LidoTokensBridgeImpl.address, + l1TokenBridgeImpl.address, deployer.address, L1LidoTokensBridge__factory.createInterface().encodeFunctionData("finalizeUpgrade_v2") ), "ErrorBridgingManagerIsNotInitialized()"); @@ -93,6 +145,12 @@ unit("Optimism :: L1LidoTokensBridge", ctxFactory) .test("finalizeUpgrade_v2() :: bridging manager initialized", async (ctx) => { const { deployer, l2TokenBridgeEOA } = ctx.accounts; + const { + tokenRate, + genesisTime, + secondsPerSlot, + lastProcessingRefSlot + } = ctx.constants; const bridgingManagerImpl = await new BridgingManagerStub__factory(deployer).deploy(); const proxy = await new OssifiableProxy__factory(deployer).deploy( @@ -103,9 +161,17 @@ unit("Optimism :: L1LidoTokensBridge", ctxFactory) ]) ); - const l1LidoTokensBridgeImpl = await getL1LidoTokensBridgeImpl(deployer, l2TokenBridgeEOA.address); + const { l1TokenBridgeImpl } = await getL1LidoTokensBridgeImpl( + tokenRate, + genesisTime, + secondsPerSlot, + lastProcessingRefSlot, + deployer, + l2TokenBridgeEOA.address + ); + await proxy.proxy__upgradeToAndCall( - l1LidoTokensBridgeImpl.address, + l1TokenBridgeImpl.address, L1LidoTokensBridge__factory.createInterface().encodeFunctionData("finalizeUpgrade_v2"), false ); @@ -246,7 +312,8 @@ unit("Optimism :: L1LidoTokensBridge", ctxFactory) const { l1TokenBridge, accounts: { deployer, l2TokenBridgeEOA }, - stubs: { l1TokenNonRebasable, l2TokenNonRebasable, l1Messenger }, + stubs: { l1TokenNonRebasable, l2TokenNonRebasable, l1Messenger, accountingOracle }, + constants: { tokenRate } } = ctx; const l2Gas = wei`0.99 wei`; @@ -266,8 +333,8 @@ unit("Optimism :: L1LidoTokensBridge", ctxFactory) data ); - const packedTokenRateAndTimestampData = await packedTokenRateAndTimestamp(ctx.provider, l1TokenNonRebasable); - const dataToReceive = ethers.utils.hexConcat([packedTokenRateAndTimestampData, data]); + const refSlotTime = await refSlotTimestamp(accountingOracle); + const dataToReceive = await tokenRateAndTimestampPacked(tokenRate, refSlotTime, data); await assert.emits(l1TokenBridge, tx, "ERC20DepositInitiated", [ l1TokenNonRebasable.address, @@ -310,18 +377,15 @@ unit("Optimism :: L1LidoTokensBridge", ctxFactory) .test("depositERC20() :: rebasable token flow", async (ctx) => { const { l1TokenBridge, + constants: { tenPowerDecimals, tokenRate }, accounts: { deployer, l2TokenBridgeEOA }, - stubs: { l1TokenRebasable, l2TokenRebasable, l1TokenNonRebasable, l1Messenger }, + stubs: { l1TokenRebasable, l2TokenRebasable, l1TokenNonRebasable, l1Messenger, accountingOracle }, } = ctx; const l2Gas = wei`0.99 wei`; const amount = wei`1 ether`; const data = "0xdeadbeaf"; - const rate = await l1TokenNonRebasable.stEthPerToken(); - const decimalsStr = await l1TokenNonRebasable.decimals(); - const decimals = BigNumber.from(10).pow(decimalsStr); - - const amountWrapped = (wei.toBigNumber(amount)).mul(BigNumber.from(decimals)).div(rate); + const amountWrapped = (wei.toBigNumber(amount)).mul(tenPowerDecimals).div(tokenRate); const deployerBalanceBefore = await l1TokenRebasable.balanceOf(deployer.address); const bridgeBalanceBefore = await l1TokenNonRebasable.balanceOf(l1TokenBridge.address); @@ -335,8 +399,8 @@ unit("Optimism :: L1LidoTokensBridge", ctxFactory) data ); - const packedTokenRateAndTimestampData = await packedTokenRateAndTimestamp(ctx.provider, l1TokenNonRebasable); - const dataToReceive = ethers.utils.hexConcat([packedTokenRateAndTimestampData, data]); + const refSlotTime = await refSlotTimestamp(accountingOracle); + const dataToReceive = await tokenRateAndTimestampPacked(tokenRate, refSlotTime, data); await assert.emits(l1TokenBridge, tx, "ERC20DepositInitiated", [ l1TokenRebasable.address, @@ -538,7 +602,8 @@ unit("Optimism :: L1LidoTokensBridge", ctxFactory) const { l1TokenBridge, accounts: { deployer, l2TokenBridgeEOA, recipient }, - stubs: { l1TokenNonRebasable, l2TokenNonRebasable, l1Messenger }, + stubs: { l1TokenNonRebasable, l2TokenNonRebasable, l1Messenger, accountingOracle }, + constants: { tokenRate } } = ctx; const l2Gas = wei`0.99 wei`; @@ -559,8 +624,8 @@ unit("Optimism :: L1LidoTokensBridge", ctxFactory) data ); - const packedTokenRateAndTimestampData = await packedTokenRateAndTimestamp(ctx.provider, l1TokenNonRebasable); - const dataToReceive = ethers.utils.hexConcat([packedTokenRateAndTimestampData, data]); + const refSlotTime = await refSlotTimestamp(accountingOracle); + const dataToReceive = await tokenRateAndTimestampPacked(tokenRate, refSlotTime, data); await assert.emits(l1TokenBridge, tx, "ERC20DepositInitiated", [ l1TokenNonRebasable.address, @@ -603,19 +668,16 @@ unit("Optimism :: L1LidoTokensBridge", ctxFactory) .test("depositERC20To() :: rebasable token flow", async (ctx) => { const { l1TokenBridge, + constants: { tenPowerDecimals, tokenRate }, accounts: { deployer, l2TokenBridgeEOA, recipient }, - stubs: { l1TokenNonRebasable, l2TokenNonRebasable, l1TokenRebasable, l2TokenRebasable, l1Messenger }, + stubs: { l1TokenNonRebasable, l1TokenRebasable, l2TokenRebasable, l1Messenger, accountingOracle }, } = ctx; const l2Gas = wei`0.99 wei`; const amount = wei`1 ether`; const data = "0x"; - const rate = await l1TokenNonRebasable.stEthPerToken(); - const decimalsStr = await l1TokenNonRebasable.decimals(); - const decimals = BigNumber.from(10).pow(decimalsStr); - - const amountWrapped = (wei.toBigNumber(amount)).mul(BigNumber.from(decimals)).div(rate); + const amountWrapped = (wei.toBigNumber(amount)).mul(tenPowerDecimals).div(tokenRate); await l1TokenRebasable.approve(l1TokenBridge.address, amount); @@ -631,8 +693,8 @@ unit("Optimism :: L1LidoTokensBridge", ctxFactory) data ); - const packedTokenRateAndTimestampData = await packedTokenRateAndTimestamp(ctx.provider, l1TokenNonRebasable); - const dataToReceive = ethers.utils.hexConcat([packedTokenRateAndTimestampData, data]); + const refSlotTime = await refSlotTimestamp(accountingOracle); + const dataToReceive = await tokenRateAndTimestampPacked(tokenRate, refSlotTime, data); await assert.emits(l1TokenBridge, tx, "ERC20DepositInitiated", [ l1TokenRebasable.address, @@ -941,7 +1003,7 @@ unit("Optimism :: L1LidoTokensBridge", ctxFactory) .test("finalizeERC20Withdrawal() :: rebasable token flow", async (ctx) => { const { l1TokenBridge, - stubs: { l1TokenRebasable, l2TokenRebasable, l1TokenNonRebasable, l2TokenNonRebasable, l1Messenger }, + stubs: { l1TokenRebasable, l2TokenRebasable, l1TokenNonRebasable, l1Messenger }, accounts: { deployer, recipient, l1MessengerStubAsEOA, l2TokenBridgeEOA }, } = ctx; @@ -950,10 +1012,9 @@ unit("Optimism :: L1LidoTokensBridge", ctxFactory) const amount = wei`1 ether`; const data = "0xdeadbeaf"; - const rate = await l1TokenNonRebasable.stEthPerToken(); - const decimalsStr = await l1TokenNonRebasable.decimals(); - const decimals = BigNumber.from(10).pow(decimalsStr); - const amountUnwrapped = (wei.toBigNumber(amount)).mul(rate).div(BigNumber.from(decimals)); + const rate = await l1TokenNonRebasable.getStETHByWstETH(BigNumber.from(10).pow(27)); + const decimals = BigNumber.from(10).pow(27); + const amountUnwrapped = (wei.toBigNumber(amount)).mul(rate).div(decimals); const bridgeBalanceBefore = await l1TokenRebasable.balanceOf(l1TokenBridge.address); const tx = await l1TokenBridge @@ -1060,91 +1121,44 @@ unit("Optimism :: L1LidoTokensBridge", ctxFactory) .run(); async function ctxFactory() { - const [deployer, l2TokenBridgeEOA, stranger, recipient] = - await hre.ethers.getSigners(); + const [deployer, l2TokenBridgeEOA, stranger, recipient] = await hre.ethers.getSigners(); const provider = await hre.ethers.provider; - const tokenRate = BigNumber.from('1164454276599657236'); - - const l1MessengerStub = await new CrossDomainMessengerStub__factory( - deployer - ).deploy({ value: wei.toBigNumber(wei`1 ether`) }); - - const l1TokenRebasableStub = await new ERC20BridgedStub__factory(deployer).deploy( - "L1 Token Rebasable", - "L1R" + const tokenRate = BigNumber.from('1164454276599657236000000000'); + const decimals = 27; + const tenPowerDecimals = BigNumber.from(10).pow(decimals); + const genesisTime = BigNumber.from(1); + const secondsPerSlot = BigNumber.from(2); + const lastProcessingRefSlot = BigNumber.from(3); + + const { + l1MessengerStub, + l1TokenBridgeImpl, + l1TokenNonRebasableStub, + l1TokenRebasableStub, + l2TokenNonRebasableStub, + l2TokenRebasableStub, + accountingOracle + } = await getL1LidoTokensBridgeImpl( + tokenRate, + genesisTime, + secondsPerSlot, + lastProcessingRefSlot, + deployer, + l2TokenBridgeEOA.address ); - const l1TokenNonRebasableStub = await new ERC20WrapperStub__factory(deployer).deploy( - l1TokenRebasableStub.address, - "L1 Token Non Rebasable", - "L1NR", - tokenRate - ); - - const l2TokenNonRebasableStub = await new ERC20BridgedStub__factory(deployer).deploy( - "L2 Token Non Rebasable", - "L2NR" - ); + const l1TokenBridge = await getL1LidoTokensBridgeProxy(deployer, l1TokenBridgeImpl); - const l2TokenRebasableStub = await new ERC20WrapperStub__factory(deployer).deploy( - l2TokenNonRebasableStub.address, - "L2 Token Rebasable", - "L2R", - tokenRate - ); - - const emptyContract = await new EmptyContractStub__factory(deployer).deploy({ - value: wei.toBigNumber(wei`1 ether`), - }); + const emptyContract = await new EmptyContractStub__factory(deployer).deploy({ value: wei.toBigNumber(wei`1 ether`) }); const emptyContractAsEOA = await testing.impersonate(emptyContract.address); - const l1MessengerStubAsEOA = await testing.impersonate( - l1MessengerStub.address - ); - - const l1TokenBridgeImpl = await new L1LidoTokensBridge__factory( - deployer - ).deploy( - l1MessengerStub.address, - l2TokenBridgeEOA.address, - l1TokenNonRebasableStub.address, - l1TokenRebasableStub.address, - l2TokenNonRebasableStub.address, - l2TokenRebasableStub.address - ); - - const l1TokenBridgeProxy = await new OssifiableProxy__factory( - deployer - ).deploy( - l1TokenBridgeImpl.address, - deployer.address, - l1TokenBridgeImpl.interface.encodeFunctionData("initialize", [ - deployer.address - ]) - ); - - const l1TokenBridge = L1LidoTokensBridge__factory.connect( - l1TokenBridgeProxy.address, - deployer - ); + const l1MessengerStubAsEOA = await testing.impersonate(l1MessengerStub.address); await l1TokenNonRebasableStub.transfer(l1TokenBridge.address, wei`100 ether`); await l1TokenRebasableStub.transfer(l1TokenBridge.address, wei`100 ether`); - const roles = await Promise.all([ - l1TokenBridge.DEPOSITS_ENABLER_ROLE(), - l1TokenBridge.DEPOSITS_DISABLER_ROLE(), - l1TokenBridge.WITHDRAWALS_ENABLER_ROLE(), - l1TokenBridge.WITHDRAWALS_DISABLER_ROLE(), - ]); - - for (const role of roles) { - await l1TokenBridge.grantRole(role, deployer.address); - } - - await l1TokenBridge.enableDeposits(); - await l1TokenBridge.enableWithdrawals(); + await setupL1TokenBridge(deployer, l1TokenBridge); return { provider: provider, @@ -1162,26 +1176,30 @@ async function ctxFactory() { l2TokenNonRebasable: l2TokenNonRebasableStub, l2TokenRebasable: l2TokenRebasableStub, l1Messenger: l1MessengerStub, + accountingOracle: accountingOracle + }, + constants: { + decimals, + tenPowerDecimals, + tokenRate, + genesisTime, + secondsPerSlot, + lastProcessingRefSlot }, l1TokenBridge, }; } -async function packedTokenRateAndTimestamp(l1Provider: JsonRpcProvider, l1Token: ERC20WrapperStub) { - const stEthPerToken = await l1Token.stEthPerToken(); - const blockNumber = await l1Provider.getBlockNumber(); - const blockTimestamp = (await l1Provider.getBlock(blockNumber)).timestamp; - const stEthPerTokenStr = ethers.utils.hexZeroPad(stEthPerToken.toHexString(), 12); - const blockTimestampStr = ethers.utils.hexZeroPad(ethers.utils.hexlify(blockTimestamp), 5); - return ethers.utils.hexConcat([stEthPerTokenStr, blockTimestampStr]); -} - -async function getL1LidoTokensBridgeImpl(deployer: SignerWithAddress, l2TokenBridge: string) { - const tokenRate = BigNumber.from('1164454276599657236'); - - const l1MessengerStub = await new CrossDomainMessengerStub__factory( - deployer - ).deploy({ value: wei.toBigNumber(wei`1 ether`) }); +async function getL1LidoTokensBridgeImpl( + tokenRate: BigNumber, + genesisTime: BigNumber, + secondsPerSlot: BigNumber, + lastProcessingRefSlot: BigNumber, + deployer: SignerWithAddress, + l2TokenBridge: string +) { + const l1MessengerStub = await new CrossDomainMessengerStub__factory(deployer) + .deploy({ value: wei.toBigNumber(wei`1 ether`) }); const l1TokenRebasableStub = await new ERC20BridgedStub__factory(deployer).deploy( "L1 Token Rebasable", @@ -1207,6 +1225,11 @@ async function getL1LidoTokensBridgeImpl(deployer: SignerWithAddress, l2TokenBri tokenRate ); + const accountingOracle = await new AccountingOracleStub__factory(deployer).deploy( + genesisTime, + secondsPerSlot, + lastProcessingRefSlot + ); const l1TokenBridgeImpl = await new L1LidoTokensBridge__factory( deployer ).deploy( @@ -1215,7 +1238,51 @@ async function getL1LidoTokensBridgeImpl(deployer: SignerWithAddress, l2TokenBri l1TokenNonRebasableStub.address, l1TokenRebasableStub.address, l2TokenNonRebasableStub.address, - l2TokenRebasableStub.address + l2TokenRebasableStub.address, + accountingOracle.address ); - return l1TokenBridgeImpl; + + return { + l1MessengerStub, + l1TokenBridgeImpl, + l1TokenNonRebasableStub, + l1TokenRebasableStub, + l2TokenNonRebasableStub, + l2TokenRebasableStub, + accountingOracle + }; +} + +async function getL1LidoTokensBridgeProxy(deployer: SignerWithAddress, l1TokenBridgeImpl: L1LidoTokensBridge) { + + const l1TokenBridgeProxy = await new OssifiableProxy__factory( + deployer + ).deploy( + l1TokenBridgeImpl.address, + deployer.address, + l1TokenBridgeImpl.interface.encodeFunctionData("initialize", [ + deployer.address + ]) + ); + + return L1LidoTokensBridge__factory.connect( + l1TokenBridgeProxy.address, + deployer + ); +} + +async function setupL1TokenBridge(deployer: SignerWithAddress, l1TokenBridge: L1LidoTokensBridge) { + const roles = await Promise.all([ + l1TokenBridge.DEPOSITS_ENABLER_ROLE(), + l1TokenBridge.DEPOSITS_DISABLER_ROLE(), + l1TokenBridge.WITHDRAWALS_ENABLER_ROLE(), + l1TokenBridge.WITHDRAWALS_DISABLER_ROLE(), + ]); + + for (const role of roles) { + await l1TokenBridge.grantRole(role, deployer.address); + } + + await l1TokenBridge.enableDeposits(); + await l1TokenBridge.enableWithdrawals(); } diff --git a/test/optimism/L2ERC20ExtendedTokensBridge.unit.test.ts b/test/optimism/L2ERC20ExtendedTokensBridge.unit.test.ts index f3600e8f..3513bdc3 100644 --- a/test/optimism/L2ERC20ExtendedTokensBridge.unit.test.ts +++ b/test/optimism/L2ERC20ExtendedTokensBridge.unit.test.ts @@ -1,6 +1,6 @@ import hre, { ethers } from "hardhat"; import { BigNumber } from "ethers"; -import { getContractAddress } from "ethers/lib/utils"; +import { predictAddresses } from "../../utils/testing/helpers"; import { assert } from "chai"; import { SignerWithAddress } from "@nomiclabs/hardhat-ethers/signers"; import { JsonRpcProvider } from "@ethersproject/providers"; @@ -1097,9 +1097,9 @@ async function ctxFactory() { const [deployer, stranger, recipient, l1TokenBridgeEOA] = await hre.ethers.getSigners(); - const decimals = 18; + const decimals = 27; const decimalsBN = BigNumber.from(10).pow(decimals); - const exchangeRate = BigNumber.from('1164454276599657236') + const exchangeRate = BigNumber.from('1164454276599657236000000000') const l2MessengerStub = await new CrossDomainMessengerStub__factory( deployer @@ -1240,25 +1240,10 @@ async function ctxFactory() { }; } -async function predictAddresses(account: SignerWithAddress, txsCount: number) { - const currentNonce = await account.getTransactionCount(); - - const res: string[] = []; - for (let i = 0; i < txsCount; ++i) { - res.push( - getContractAddress({ - from: account.address, - nonce: currentNonce + i, - }) - ); - } - return res; -} - async function packedTokenRateAndTimestamp(provider: JsonRpcProvider, tokenRate: BigNumber) { const blockNumber = await provider.getBlockNumber(); const blockTimestamp = (await provider.getBlock(blockNumber)).timestamp; - const stEthPerTokenStr = ethers.utils.hexZeroPad(tokenRate.toHexString(), 12); + const stEthPerTokenStr = ethers.utils.hexZeroPad(tokenRate.toHexString(), 16); const blockTimestampStr = ethers.utils.hexZeroPad(ethers.utils.hexlify(blockTimestamp), 5); return ethers.utils.hexConcat([stEthPerTokenStr, blockTimestampStr]); } @@ -1284,7 +1269,7 @@ async function pushTokenRate(ctx: ContextType) { async function getL2TokenBridgeImpl(deployer: SignerWithAddress, l1TokenBridge: string) { const decimals = 18; - const exchangeRate = BigNumber.from('1164454276599657236') + const exchangeRate = BigNumber.from('1164454276599657236000000000') const l2MessengerStub = await new CrossDomainMessengerStub__factory( deployer diff --git a/test/optimism/OpStackTokenRatePusher.unit.test.ts b/test/optimism/OpStackTokenRatePusher.unit.test.ts index c024bb4a..28116e91 100644 --- a/test/optimism/OpStackTokenRatePusher.unit.test.ts +++ b/test/optimism/OpStackTokenRatePusher.unit.test.ts @@ -1,25 +1,31 @@ import { ethers } from "hardhat"; import { assert } from "chai"; -import { utils, BigNumber } from 'ethers' +import { BigNumber } from 'ethers' import { unit } from "../../utils/testing"; import { wei } from "../../utils/wei"; +import { getInterfaceID } from "../../utils/testing/helpers"; import { OpStackTokenRatePusher__factory, CrossDomainMessengerStub__factory, + ITokenRateOracle__factory, + ITokenRatePusher__factory, ERC20BridgedStub__factory, ERC20WrapperStub__factory, - ITokenRateOracle__factory, - ITokenRatePusher__factory + AccountingOracleStub__factory + } from "../../typechain"; unit("OpStackTokenRatePusher", ctxFactory) .test("initial state", async (ctx) => { const { tokenRateOracle } = ctx.accounts; - const { opStackTokenRatePusher, l1MessengerStub, l1TokenNonRebasableStub } = ctx.contracts; + const { opStackTokenRatePusher, l1MessengerStub, accountingOracle } = ctx.contracts; + const { genesisTime, secondsPerSlot } = ctx.constants; assert.equal(await opStackTokenRatePusher.MESSENGER(), l1MessengerStub.address); - assert.equal(await opStackTokenRatePusher.WSTETH(), l1TokenNonRebasableStub.address); + assert.equalBN(await opStackTokenRatePusher.GENESIS_TIME(), genesisTime); + assert.equalBN(await opStackTokenRatePusher.SECONDS_PER_SLOT(), secondsPerSlot); + assert.equal(await opStackTokenRatePusher.ACCOUNTING_ORACLE(), accountingOracle.address); assert.equal(await opStackTokenRatePusher.L2_TOKEN_RATE_ORACLE(), tokenRateOracle.address); assert.equalBN(await opStackTokenRatePusher.L2_GAS_LIMIT_FOR_PUSHING_TOKEN_RATE(), 123); const iTokenRatePusher = getInterfaceID(ITokenRatePusher__factory.createInterface()); @@ -28,17 +34,11 @@ unit("OpStackTokenRatePusher", ctxFactory) .test("pushTokenRate() :: success", async (ctx) => { const { tokenRateOracle } = ctx.accounts; - const { l2GasLimitForPushingTokenRate } = ctx.constants; - const { opStackTokenRatePusher, l1MessengerStub, l1TokenNonRebasableStub } = ctx.contracts; - - let tokenRate = await l1TokenNonRebasableStub.stEthPerToken(); + const { l2GasLimitForPushingTokenRate, tokenRate, updateRateTime } = ctx.constants; + const { opStackTokenRatePusher, l1MessengerStub } = ctx.contracts; let tx = await opStackTokenRatePusher.pushTokenRate(); - const provider = await ethers.provider; - const blockNumber = await provider.getBlockNumber(); - const blockTimestamp = (await provider.getBlock(blockNumber)).timestamp; - await assert.emits(l1MessengerStub, tx, "SentMessage", [ tokenRateOracle.address, opStackTokenRatePusher.address, @@ -46,7 +46,7 @@ unit("OpStackTokenRatePusher", ctxFactory) "updateRate", [ tokenRate, - blockTimestamp + updateRateTime ] ), 1, @@ -59,7 +59,11 @@ unit("OpStackTokenRatePusher", ctxFactory) async function ctxFactory() { const [deployer, bridge, stranger, tokenRateOracle, l1TokenBridgeEOA] = await ethers.getSigners(); - const tokenRate = BigNumber.from('1164454276599657236'); + const tokenRate = BigNumber.from('1164454276599657236000000000'); + const genesisTime = BigNumber.from(1); + const secondsPerSlot = BigNumber.from(2); + const lastProcessingRefSlot = BigNumber.from(3); + const updateRateTime = genesisTime.add(secondsPerSlot.mul(lastProcessingRefSlot)); const l1TokenRebasableStub = await new ERC20BridgedStub__factory(deployer).deploy( "L1 Token Rebasable", @@ -73,9 +77,14 @@ async function ctxFactory() { tokenRate ); - const l1MessengerStub = await new CrossDomainMessengerStub__factory( - deployer - ).deploy({ value: wei.toBigNumber(wei`1 ether`) }); + const accountingOracle = await new AccountingOracleStub__factory(deployer).deploy( + genesisTime, + secondsPerSlot, + lastProcessingRefSlot + ); + + const l1MessengerStub = await new CrossDomainMessengerStub__factory(deployer) + .deploy({ value: wei.toBigNumber(wei`1 ether`) }); await l1MessengerStub.setXDomainMessageSender(l1TokenBridgeEOA.address); const l2GasLimitForPushingTokenRate = 123; @@ -83,22 +92,14 @@ async function ctxFactory() { const opStackTokenRatePusher = await new OpStackTokenRatePusher__factory(deployer).deploy( l1MessengerStub.address, l1TokenNonRebasableStub.address, + accountingOracle.address, tokenRateOracle.address, l2GasLimitForPushingTokenRate ); return { accounts: { deployer, bridge, stranger, tokenRateOracle }, - contracts: { opStackTokenRatePusher, l1MessengerStub, l1TokenNonRebasableStub }, - constants: { l2GasLimitForPushingTokenRate } + contracts: { opStackTokenRatePusher, l1MessengerStub, l1TokenNonRebasableStub, accountingOracle }, + constants: { l2GasLimitForPushingTokenRate, tokenRate, updateRateTime, genesisTime, secondsPerSlot, lastProcessingRefSlot } }; } - -export function getInterfaceID(contractInterface: utils.Interface) { - let interfaceID = ethers.constants.Zero; - const functions: string[] = Object.keys(contractInterface.functions); - for (let i = 0; i < functions.length; i++) { - interfaceID = interfaceID.xor(contractInterface.getSighash(functions[i])); - } - return interfaceID; -} diff --git a/test/optimism/TokenRateNotifier.unit.test.ts b/test/optimism/TokenRateNotifier.unit.test.ts index 075f956c..d2d305cb 100644 --- a/test/optimism/TokenRateNotifier.unit.test.ts +++ b/test/optimism/TokenRateNotifier.unit.test.ts @@ -1,19 +1,21 @@ import { ethers } from "hardhat"; import { assert } from "chai"; -import { utils, BigNumber } from 'ethers' +import { BigNumber } from 'ethers' import { unit } from "../../utils/testing"; import { wei } from "../../utils/wei"; +import { getInterfaceID } from "../../utils/testing/helpers"; import { SignerWithAddress } from "@nomiclabs/hardhat-ethers/signers"; import { TokenRateNotifier__factory, ITokenRatePusher__factory, OpStackTokenRatePusher__factory, ITokenRateOracle__factory, - ERC20BridgedStub__factory, - ERC20WrapperStub__factory, CrossDomainMessengerStub__factory, OpStackTokenRatePusherWithSomeErrorStub__factory, - OpStackTokenRatePusherWithOutOfGasErrorStub__factory + OpStackTokenRatePusherWithOutOfGasErrorStub__factory, + ERC20BridgedStub__factory, + ERC20WrapperStub__factory, + AccountingOracleStub__factory } from "../../typechain"; unit("TokenRateNotifier", ctxFactory) @@ -75,7 +77,7 @@ unit("TokenRateNotifier", ctxFactory) .test("addObserver() :: revert on adding too many observers", async (ctx) => { const { tokenRateNotifier, opStackTokenRatePusher } = ctx.contracts; const { deployer, owner, tokenRateOracle } = ctx.accounts; - const { l2GasLimitForPushingTokenRate } = ctx.constants; + const { l2GasLimitForPushingTokenRate, tokenRate, genesisTime, secondsPerSlot, lastProcessingRefSlot } = ctx.constants; assert.equalBN(await tokenRateNotifier.observersLength(), 0); const maxObservers = await tokenRateNotifier.MAX_OBSERVERS_COUNT(); @@ -83,7 +85,17 @@ unit("TokenRateNotifier", ctxFactory) const { opStackTokenRatePusher - } = await getOpStackTokenRatePusher(deployer, owner, tokenRateOracle, l2GasLimitForPushingTokenRate); + } = await getOpStackTokenRatePusher + ( + tokenRate, + genesisTime, + secondsPerSlot, + lastProcessingRefSlot, + deployer, + owner, + tokenRateOracle, + l2GasLimitForPushingTokenRate + ); await tokenRateNotifier .connect(ctx.accounts.owner) @@ -203,22 +215,18 @@ unit("TokenRateNotifier", ctxFactory) const { tokenRateNotifier, l1MessengerStub, - opStackTokenRatePusher, - l1TokenNonRebasableStub + opStackTokenRatePusher } = ctx.contracts; const { tokenRateOracle } = ctx.accounts; - const { l2GasLimitForPushingTokenRate } = ctx.constants; + const { l2GasLimitForPushingTokenRate, tokenRate, genesisTime, secondsPerSlot, lastProcessingRefSlot } = ctx.constants; + + const updateRateTime = genesisTime.add(secondsPerSlot.mul(lastProcessingRefSlot)); - let tokenRate = await l1TokenNonRebasableStub.stEthPerToken(); await tokenRateNotifier .connect(ctx.accounts.owner) .addObserver(opStackTokenRatePusher.address); let tx = await tokenRateNotifier.handlePostTokenRebase(1, 2, 3, 4, 5, 6, 7); - const provider = await ethers.provider; - const blockNumber = await provider.getBlockNumber(); - const blockTimestamp = (await provider.getBlock(blockNumber)).timestamp; - await assert.emits(l1MessengerStub, tx, "SentMessage", [ tokenRateOracle.address, opStackTokenRatePusher.address, @@ -226,7 +234,7 @@ unit("TokenRateNotifier", ctxFactory) "updateRate", [ tokenRate, - blockTimestamp + updateRateTime ] ), 1, @@ -237,18 +245,20 @@ unit("TokenRateNotifier", ctxFactory) .run(); async function getOpStackTokenRatePusher( + tokenRate: BigNumber, + genesisTime: BigNumber, + secondsPerSlot: BigNumber, + lastProcessingRefSlot: BigNumber, deployer: SignerWithAddress, owner: SignerWithAddress, tokenRateOracle: SignerWithAddress, l2GasLimitForPushingTokenRate: number) { - const tokenRate = BigNumber.from('1164454276599657236'); - const tokenRateNotifier = await new TokenRateNotifier__factory(deployer).deploy(owner.address); - const l1MessengerStub = await new CrossDomainMessengerStub__factory( - deployer - ).deploy({ value: wei.toBigNumber(wei`1 ether`) }); + const l1MessengerStub = await new CrossDomainMessengerStub__factory(deployer) + .deploy({ value: wei.toBigNumber(wei`1 ether`) }); + const l1TokenRebasableStub = await new ERC20BridgedStub__factory(deployer).deploy( "L1 Token Rebasable", @@ -262,40 +272,56 @@ async function getOpStackTokenRatePusher( tokenRate ); + const accountingOracle = await new AccountingOracleStub__factory(deployer).deploy( + genesisTime, + secondsPerSlot, + lastProcessingRefSlot + ); + const opStackTokenRatePusher = await new OpStackTokenRatePusher__factory(deployer).deploy( l1MessengerStub.address, l1TokenNonRebasableStub.address, + accountingOracle.address, tokenRateOracle.address, l2GasLimitForPushingTokenRate ); - return { tokenRateNotifier, opStackTokenRatePusher, l1MessengerStub, l1TokenNonRebasableStub } + return { + tokenRateNotifier, + l1TokenNonRebasableStub, + opStackTokenRatePusher, + accountingOracle, + l1MessengerStub, + tokenRate + }; } async function ctxFactory() { const [deployer, owner, stranger, tokenRateOracle] = await ethers.getSigners(); - + const tokenRate = BigNumber.from('1164454276599657236000000000'); const l2GasLimitForPushingTokenRate = 300_000; + const genesisTime = BigNumber.from(1); + const secondsPerSlot = BigNumber.from(2); + const lastProcessingRefSlot = BigNumber.from(3); const { tokenRateNotifier, opStackTokenRatePusher, - l1MessengerStub, - l1TokenNonRebasableStub - } = await getOpStackTokenRatePusher(deployer, owner, tokenRateOracle, l2GasLimitForPushingTokenRate); + l1MessengerStub + } = await getOpStackTokenRatePusher( + tokenRate, + genesisTime, + secondsPerSlot, + lastProcessingRefSlot, + deployer, + owner, + tokenRateOracle, + l2GasLimitForPushingTokenRate + ); return { accounts: { deployer, owner, stranger, tokenRateOracle }, - contracts: { tokenRateNotifier, opStackTokenRatePusher, l1MessengerStub, l1TokenNonRebasableStub }, - constants: { l2GasLimitForPushingTokenRate } + contracts: { tokenRateNotifier, opStackTokenRatePusher, l1MessengerStub }, + constants: { l2GasLimitForPushingTokenRate, tokenRate, genesisTime, secondsPerSlot, lastProcessingRefSlot } }; } - -export function getInterfaceID(contractInterface: utils.Interface) { - let interfaceID = ethers.constants.Zero; - const functions: string[] = Object.keys(contractInterface.functions); - for (let i = 0; i < functions.length; i++) { - interfaceID = interfaceID.xor(contractInterface.getSighash(functions[i])); - } - return interfaceID; -} diff --git a/test/optimism/TokenRateOracle.unit.test.ts b/test/optimism/TokenRateOracle.unit.test.ts index f34767b7..8d9b5cf9 100644 --- a/test/optimism/TokenRateOracle.unit.test.ts +++ b/test/optimism/TokenRateOracle.unit.test.ts @@ -4,16 +4,18 @@ import { BigNumber } from "ethers"; import { wei } from "../../utils/wei"; import testing, { unit } from "../../utils/testing"; import { tokenRateOracleUnderProxy } from "../../utils/testing/contractsFactory"; +import { getContractTransactionTimestamp, getBlockTimestamp } from "../../utils/testing/helpers"; import { TokenRateOracle__factory, CrossDomainMessengerStub__factory } from "../../typechain"; unit("TokenRateOracle", ctxFactory) - .test("state after init", async (ctx) => { const { tokenRateOracle, l2MessengerStub } = ctx.contracts; const { bridge, l1TokenBridgeEOA } = ctx.accounts; const { tokenRate, - blockTimestamp, + decimals, + rateL1Timestamp, + blockTimestampOfDeployment, tokenRateOutdatedDelay, maxAllowedL2ToL1ClockLag, maxAllowedTokenRateDeviationPerDay @@ -25,7 +27,6 @@ unit("TokenRateOracle", ctxFactory) assert.equalBN(await tokenRateOracle.TOKEN_RATE_OUTDATED_DELAY(), tokenRateOutdatedDelay); assert.equalBN(await tokenRateOracle.MAX_ALLOWED_L2_TO_L1_CLOCK_LAG(), maxAllowedL2ToL1ClockLag); assert.equalBN(await tokenRateOracle.MAX_ALLOWED_TOKEN_RATE_DEVIATION_PER_DAY(), maxAllowedTokenRateDeviationPerDay); - assert.equalBN(await tokenRateOracle.latestAnswer(), tokenRate); const { @@ -36,18 +37,19 @@ unit("TokenRateOracle", ctxFactory) answeredInRound_ } = await tokenRateOracle.latestRoundData(); - assert.equalBN(roundId_, blockTimestamp); + assert.equalBN(roundId_, rateL1Timestamp); assert.equalBN(answer_, tokenRate); - assert.equalBN(startedAt_, blockTimestamp); - assert.equalBN(updatedAt_, blockTimestamp); - assert.equalBN(answeredInRound_, blockTimestamp); - assert.equalBN(await tokenRateOracle.decimals(), 18); + assert.equalBN(startedAt_, rateL1Timestamp); + assert.equalBN(updatedAt_, blockTimestampOfDeployment); + assert.equalBN(answeredInRound_, rateL1Timestamp); + + assert.equalBN(await tokenRateOracle.decimals(), decimals); }) .test("initialize() :: petrified version", async (ctx) => { const { deployer, bridge, l1TokenBridgeEOA } = ctx.accounts; const { l2MessengerStub } = ctx.contracts; - const { tokenRate, blockTimestamp } = ctx.constants; + const { tokenRate, blockTimestampOfDeployment } = ctx.constants; const tokenRateOracleImpl = await new TokenRateOracle__factory(deployer).deploy( l2MessengerStub.address, @@ -62,19 +64,19 @@ unit("TokenRateOracle", ctxFactory) assert.equalBN(await tokenRateOracleImpl.getContractVersion(), petrifiedVersionMark); await assert.revertsWith( - tokenRateOracleImpl.initialize(tokenRate, blockTimestamp), + tokenRateOracleImpl.initialize(tokenRate, blockTimestampOfDeployment), "NonZeroContractVersionOnInit()" ); }) .test("initialize() :: don't allow to initialize twice", async (ctx) => { const { tokenRateOracle } = ctx.contracts; - const { tokenRate, blockTimestamp } = ctx.constants; + const { tokenRate, blockTimestampOfDeployment } = ctx.constants; assert.equalBN(await tokenRateOracle.getContractVersion(), 1); await assert.revertsWith( - tokenRateOracle.initialize(tokenRate, blockTimestamp), + tokenRateOracle.initialize(tokenRate, blockTimestampOfDeployment), "NonZeroContractVersionOnInit()" ); }) @@ -82,7 +84,7 @@ unit("TokenRateOracle", ctxFactory) .test("initialize() :: token rate is out of range", async (ctx) => { const { deployer, bridge, l1TokenBridgeEOA } = ctx.accounts; const { l2MessengerStub } = ctx.contracts; - const { blockTimestamp } = ctx.constants; + const { blockTimestampOfDeployment } = ctx.constants; const tokenRateOracleImpl = await new TokenRateOracle__factory(deployer).deploy( l2MessengerStub.address, @@ -97,12 +99,12 @@ unit("TokenRateOracle", ctxFactory) const tokenRateMax = await tokenRateOracleImpl.MAX_ALLOWED_TOKEN_RATE(); await assert.revertsWith( - tokenRateOracleImpl.initialize(tokenRateMin.sub(1), blockTimestamp), + tokenRateOracleImpl.initialize(tokenRateMin.sub(1), blockTimestampOfDeployment), "ErrorTokenRateInitializationIsOutOfAllowedRange(" + tokenRateMin.sub(1) + ")" ); await assert.revertsWith( - tokenRateOracleImpl.initialize(tokenRateMax.add(1), blockTimestamp), + tokenRateOracleImpl.initialize(tokenRateMax.add(1), blockTimestampOfDeployment), "ErrorTokenRateInitializationIsOutOfAllowedRange(" + tokenRateMax.add(1) + ")" ); }) @@ -110,7 +112,7 @@ unit("TokenRateOracle", ctxFactory) .test("initialize() :: time is out of init range", async (ctx) => { const { deployer, bridge, l1TokenBridgeEOA } = ctx.accounts; const { l2MessengerStub } = ctx.contracts; - const { tokenRate, blockTimestamp, maxAllowedL2ToL1ClockLag } = ctx.constants; + const { tokenRate, blockTimestampOfDeployment, maxAllowedL2ToL1ClockLag } = ctx.constants; const tokenRateOracleImpl = await new TokenRateOracle__factory(deployer).deploy( l2MessengerStub.address, @@ -121,7 +123,7 @@ unit("TokenRateOracle", ctxFactory) 500 ); - const wrongTimeMax = blockTimestamp.add(maxAllowedL2ToL1ClockLag).add(20); + const wrongTimeMax = blockTimestampOfDeployment.add(maxAllowedL2ToL1ClockLag).add(20); await assert.revertsWith( tokenRateOracleImpl.initialize(tokenRate, wrongTimeMax), @@ -170,48 +172,63 @@ unit("TokenRateOracle", ctxFactory) .test("updateRate() :: L1 time exceeded allowed L2 clock lag", async (ctx) => { const { tokenRateOracle } = ctx.contracts; const { bridge } = ctx.accounts; - const { tokenRate, blockTimestamp, maxAllowedL2ToL1ClockLag } = ctx.constants; + const { tokenRate, blockTimestampOfDeployment, maxAllowedL2ToL1ClockLag } = ctx.constants; - const exceededTime = blockTimestamp.add(maxAllowedL2ToL1ClockLag).add(40); // more than maxAllowedL2ToL1ClockLag + const exceededTime = blockTimestampOfDeployment.add(maxAllowedL2ToL1ClockLag).add(40); // more than maxAllowedL2ToL1ClockLag await assert.revertsWith( tokenRateOracle.connect(bridge).updateRate(tokenRate, exceededTime), "ErrorL1TimestampExceededAllowedClockLag(" + tokenRate + ", " + exceededTime + ")" ) }) - .test("updateRate() :: received token rate is in the past or same time", async (ctx) => { + .test("updateRate() :: received token rate has l1 time in the past", async (ctx) => { const { tokenRateOracle } = ctx.contracts; const { bridge } = ctx.accounts; - const { tokenRate, blockTimestamp } = ctx.constants; + const { tokenRate, rateL1Timestamp } = ctx.constants; + + const rateL1TimestampInPast = rateL1Timestamp.sub(100); const tx0 = await tokenRateOracle .connect(bridge) - .updateRate(tokenRate, blockTimestamp); + .updateRate(tokenRate, rateL1Timestamp.sub(100)); await assert.emits(tokenRateOracle, tx0, "DormantTokenRateUpdateIgnored", [ - blockTimestamp, - blockTimestamp, + rateL1TimestampInPast, + rateL1Timestamp, ]); await assert.notEmits(tokenRateOracle, tx0, "RateUpdated"); + }) - const timeInPast = blockTimestamp.sub(1000); - const tx1 = await tokenRateOracle + .test("updateRate() :: received token rate has the same l1 time", async (ctx) => { + const { tokenRateOracle } = ctx.contracts; + const { bridge } = ctx.accounts; + const { tokenRate, rateL1Timestamp } = ctx.constants; + + const tx = await tokenRateOracle .connect(bridge) - .updateRate(tokenRate, timeInPast); + .updateRate(tokenRate, rateL1Timestamp); - await assert.emits(tokenRateOracle, tx1, "DormantTokenRateUpdateIgnored", [ - timeInPast, - blockTimestamp, - ]); - await assert.notEmits(tokenRateOracle, tx1, "RateUpdated"); + const updatedAt = await getContractTransactionTimestamp(ctx.provider, tx); + + await assert.emits(tokenRateOracle, tx, "RateReceivedUpdated", [updatedAt]); + + const { + roundId_, + answer_, + startedAt_, + updatedAt_, + answeredInRound_ + } = await tokenRateOracle.latestRoundData(); + + assert.equalBN(updatedAt_, updatedAt); }) .test("updateRate() :: token rate is out of range 1 day", async (ctx) => { const { tokenRateOracle } = ctx.contracts; const { bridge } = ctx.accounts; - const { tokenRate, blockTimestamp, maxAllowedTokenRateDeviationPerDay } = ctx.constants; + const { tokenRate, blockTimestampOfDeployment, maxAllowedTokenRateDeviationPerDay } = ctx.constants; - const blockTimestampForNextUpdate = blockTimestamp.add(1000); + const blockTimestampForNextUpdate = blockTimestampOfDeployment.add(1000); const tokenRateTooBig = tokenRate.mul( BigNumber.from('10000') .add(maxAllowedTokenRateDeviationPerDay) @@ -232,7 +249,7 @@ unit("TokenRateOracle", ctxFactory) ) .div(BigNumber.from('10000')); // allowed within one day - await tokenRateOracle.connect(bridge).updateRate(tokenRate, blockTimestamp); + await tokenRateOracle.connect(bridge).updateRate(tokenRate, blockTimestampOfDeployment); await assert.revertsWith( tokenRateOracle.connect(bridge).updateRate(tokenRateTooBig, blockTimestampForNextUpdate), @@ -250,7 +267,7 @@ unit("TokenRateOracle", ctxFactory) .test("updateRate() :: token rate is out of range 2 days", async (ctx) => { const { tokenRateOracle } = ctx.contracts; const { bridge } = ctx.accounts; - const { tokenRate, blockTimestamp, maxAllowedTokenRateDeviationPerDay } = ctx.constants; + const { tokenRate, blockTimestampOfDeployment, maxAllowedTokenRateDeviationPerDay } = ctx.constants; const tokenRateFirstUpdate = tokenRate.add(10); @@ -276,9 +293,9 @@ unit("TokenRateOracle", ctxFactory) .div(BigNumber.from('10000')); // allowed within 2 days - await tokenRateOracle.connect(bridge).updateRate(tokenRateFirstUpdate, blockTimestamp.add(1000)); + await tokenRateOracle.connect(bridge).updateRate(tokenRateFirstUpdate, blockTimestampOfDeployment.add(1000)); - const blockTimestampMoreThanOneDays = blockTimestamp.add(86400 + 2000); + const blockTimestampMoreThanOneDays = blockTimestampOfDeployment.add(86400 + 2000); await assert.revertsWith( tokenRateOracle.connect(bridge).updateRate(tokenRateTooBig, blockTimestampMoreThanOneDays), "ErrorTokenRateIsOutOfRange(" + tokenRateTooBig + ", " + blockTimestampMoreThanOneDays + ")" @@ -294,7 +311,7 @@ unit("TokenRateOracle", ctxFactory) .test("updateRate() :: token rate limits", async (ctx) => { const { deployer, bridge, l1TokenBridgeEOA } = ctx.accounts; - const { tokenRate, blockTimestamp } = ctx.constants; + const { tokenRate } = ctx.constants; const tokenRateOutdatedDelay = BigNumber.from(86400); // 1 day const maxAllowedL2ToL1ClockLag = BigNumber.from(86400 * 2); // 2 days @@ -304,7 +321,7 @@ unit("TokenRateOracle", ctxFactory) deployer ).deploy({ value: wei.toBigNumber(wei`1 ether`) }); - const tokenRateOracle = await tokenRateOracleUnderProxy( + const {tokenRateOracle, blockTimestampOfDeployment } = await tokenRateOracleUnderProxy( deployer, l2MessengerStub.address, bridge.address, @@ -317,22 +334,22 @@ unit("TokenRateOracle", ctxFactory) ); const maxAllowedTokenRate = await tokenRateOracle.MAX_ALLOWED_TOKEN_RATE(); - await tokenRateOracle.connect(bridge).updateRate(maxAllowedTokenRate, blockTimestamp.add(1000)); + await tokenRateOracle.connect(bridge).updateRate(maxAllowedTokenRate, blockTimestampOfDeployment.add(1000)); assert.equalBN(await tokenRateOracle.latestAnswer(), maxAllowedTokenRate); const minAllowedTokenRate = await tokenRateOracle.MIN_ALLOWED_TOKEN_RATE(); - await tokenRateOracle.connect(bridge).updateRate(minAllowedTokenRate, blockTimestamp.add(2000)); + await tokenRateOracle.connect(bridge).updateRate(minAllowedTokenRate, blockTimestampOfDeployment.add(2000)); assert.equalBN(await tokenRateOracle.latestAnswer(), minAllowedTokenRate); }) .test("updateRate() :: happy path called by bridge", async (ctx) => { const { tokenRateOracle } = ctx.contracts; const { bridge } = ctx.accounts; - const { tokenRate, blockTimestamp } = ctx.constants; + const { tokenRate, blockTimestampOfDeployment } = ctx.constants; const newTokenRate = tokenRate.mul(BigNumber.from('104')).div(BigNumber.from('100')); // 104% - const blockTimestampInFuture = blockTimestamp.add(1000); + const blockTimestampInFuture = blockTimestampOfDeployment.add(1000); const tx = await tokenRateOracle.connect(bridge).updateRate(newTokenRate, blockTimestampInFuture); await assert.emits(tokenRateOracle, tx, "TokenRateL1TimestampIsInFuture", [ @@ -355,24 +372,25 @@ unit("TokenRateOracle", ctxFactory) answeredInRound_ } = await tokenRateOracle.latestRoundData(); + const updatedAt = await getContractTransactionTimestamp(ctx.provider, tx); + assert.equalBN(roundId_, blockTimestampInFuture); assert.equalBN(answer_, newTokenRate); assert.equalBN(startedAt_, blockTimestampInFuture); - assert.equalBN(updatedAt_, blockTimestampInFuture); + assert.equalBN(updatedAt_, updatedAt); assert.equalBN(answeredInRound_, blockTimestampInFuture); - assert.equalBN(await tokenRateOracle.decimals(), 18); }) .test("updateRate() :: happy path called by messenger with correct cross-domain sender", async (ctx) => { const { tokenRateOracle, l2MessengerStub } = ctx.contracts; const { l2MessengerStubEOA, l1TokenBridgeEOA } = ctx.accounts; - const { tokenRate, blockTimestamp } = ctx.constants; + const { tokenRate, blockTimestampOfDeployment } = ctx.constants; await l2MessengerStub.setXDomainMessageSender(l1TokenBridgeEOA.address); const newTokenRate = tokenRate.mul(BigNumber.from('104')).div(BigNumber.from('100')); // 104% - const blockTimestampInFuture = blockTimestamp.add(1000); + const blockTimestampInFuture = blockTimestampOfDeployment.add(1000); const tx = await tokenRateOracle.connect(l2MessengerStubEOA).updateRate(newTokenRate, blockTimestampInFuture); await assert.emits(tokenRateOracle, tx, "TokenRateL1TimestampIsInFuture", [ @@ -395,31 +413,34 @@ unit("TokenRateOracle", ctxFactory) answeredInRound_ } = await tokenRateOracle.latestRoundData(); + const updatedAt = await getContractTransactionTimestamp(ctx.provider, tx); + assert.equalBN(roundId_, blockTimestampInFuture); assert.equalBN(answer_, newTokenRate); assert.equalBN(startedAt_, blockTimestampInFuture); - assert.equalBN(updatedAt_, blockTimestampInFuture); + assert.equalBN(updatedAt_, updatedAt); assert.equalBN(answeredInRound_, blockTimestampInFuture); - assert.equalBN(await tokenRateOracle.decimals(), 18); }) .run(); async function ctxFactory() { - const tokenRate = BigNumber.from('1164454276599657236'); // value taken from real contact on 23.04.24 - const tokenRateOutdatedDelay = BigNumber.from(86400); // 1 day - const maxAllowedL2ToL1ClockLag = BigNumber.from(86400 * 2); // 2 days - const maxAllowedTokenRateDeviationPerDay = BigNumber.from(500); // 5% - const blockTimestamp = await getBlockTimestamp(10); - const [deployer, bridge, stranger, l1TokenBridgeEOA] = await hre.ethers.getSigners(); - const l2MessengerStub = await new CrossDomainMessengerStub__factory( - deployer - ).deploy({ value: wei.toBigNumber(wei`1 ether`) }); + const decimals = 27; + const provider = await hre.ethers.provider; + const tokenRate = BigNumber.from('1164454276599657236000000000'); // value taken from real contact on 23.04.24 + const tokenRateOutdatedDelay = BigNumber.from(86400); // 1 day + const maxAllowedL2ToL1ClockLag = BigNumber.from(86400 * 2); // 2 days + const maxAllowedTokenRateDeviationPerDay = BigNumber.from(500); // 5% + + const l2MessengerStub = await new CrossDomainMessengerStub__factory(deployer) + .deploy({ value: wei.toBigNumber(wei`1 ether`) }); const l2MessengerStubEOA = await testing.impersonate(l2MessengerStub.address); - const tokenRateOracle = await tokenRateOracleUnderProxy( + const rateL1Timestamp = await getBlockTimestamp(provider, 0); + + const { tokenRateOracle, blockTimestampOfDeployment } = await tokenRateOracleUnderProxy( deployer, l2MessengerStub.address, bridge.address, @@ -428,21 +449,30 @@ async function ctxFactory() { maxAllowedL2ToL1ClockLag, maxAllowedTokenRateDeviationPerDay, tokenRate, - blockTimestamp + rateL1Timestamp ); return { - accounts: { deployer, bridge, stranger, l1TokenBridgeEOA, l2MessengerStubEOA }, - contracts: { tokenRateOracle, l2MessengerStub }, + accounts: { + deployer, + bridge, + stranger, + l1TokenBridgeEOA, + l2MessengerStubEOA + }, + contracts: { + tokenRateOracle, + l2MessengerStub + }, constants: { - tokenRate, blockTimestamp, tokenRateOutdatedDelay, - maxAllowedL2ToL1ClockLag, maxAllowedTokenRateDeviationPerDay - } + tokenRate, + decimals, + rateL1Timestamp, + blockTimestampOfDeployment, + tokenRateOutdatedDelay, + maxAllowedL2ToL1ClockLag, + maxAllowedTokenRateDeviationPerDay + }, + provider }; } - -async function getBlockTimestamp(shift: number) { - const provider = await hre.ethers.provider; - const blockNumber = await provider.getBlockNumber(); - return BigNumber.from((await provider.getBlock(blockNumber)).timestamp + shift); -} diff --git a/test/optimism/_launch.test.ts b/test/optimism/_launch.test.ts index e1f6f80e..e64f0d33 100644 --- a/test/optimism/_launch.test.ts +++ b/test/optimism/_launch.test.ts @@ -58,7 +58,7 @@ async function ctxFactory() { const { l1Provider, l2Provider, l1LidoTokensBridge } = await optimism .testing(networkName) - .getIntegrationTestSetup(BigNumber.from('1164454276599657236')); + .getIntegrationTestSetup(BigNumber.from('1164454276599657236000000000')); const hasDeployedContracts = testing.env.USE_DEPLOYED_CONTRACTS(false); const l1DevMultisig = hasDeployedContracts diff --git a/test/optimism/bridging-non-rebasable.integration.test.ts b/test/optimism/bridging-non-rebasable.integration.test.ts index 18730eca..43f2fc4d 100644 --- a/test/optimism/bridging-non-rebasable.integration.test.ts +++ b/test/optimism/bridging-non-rebasable.integration.test.ts @@ -1,13 +1,11 @@ import { assert } from "chai"; -import { ethers } from "hardhat"; import { BigNumber } from 'ethers' import env from "../../utils/env"; import { wei } from "../../utils/wei"; import optimism from "../../utils/optimism"; import testing, { scenario } from "../../utils/testing"; import { ScenarioTest } from "../../utils/testing"; -import { ERC20WrapperStub } from "../../typechain"; -import { JsonRpcProvider } from "@ethersproject/providers"; +import { tokenRateAndTimestampPacked, refSlotTimestamp } from "../../utils/testing/helpers"; type ContextType = Awaited>> @@ -83,9 +81,10 @@ function bridgingTestsSuit(scenarioInstance: ScenarioTest) { l2Token, l1CrossDomainMessenger, l2ERC20ExtendedTokensBridge, + accountingOracle } = ctx; const { accountA: tokenHolderA } = ctx.accounts; - const { depositAmount } = ctx.constants; + const { depositAmount, tokenRate } = ctx.constants; await l1Token .connect(tokenHolderA.l1Signer) @@ -106,7 +105,8 @@ function bridgingTestsSuit(scenarioInstance: ScenarioTest) { "0x" ); - const dataToSend = await packedTokenRateAndTimestamp(ctx.l1Provider, l1Token); + const refSlotTime = await refSlotTimestamp(accountingOracle); + const dataToSend = await tokenRateAndTimestampPacked(tokenRate, refSlotTime, "0x"); await assert.emits(l1LidoTokensBridge, tx, "ERC20DepositInitiated", [ l1Token.address, @@ -157,8 +157,9 @@ function bridgingTestsSuit(scenarioInstance: ScenarioTest) { l1LidoTokensBridge, l2CrossDomainMessenger, l2ERC20ExtendedTokensBridge, + accountingOracle } = ctx; - const { depositAmount } = ctx.constants; + const { depositAmount, tokenRate } = ctx.constants; const { accountA: tokenHolderA, l1CrossDomainMessengerAliased } = ctx.accounts; @@ -166,7 +167,8 @@ function bridgingTestsSuit(scenarioInstance: ScenarioTest) { const tokenHolderABalanceBefore = await l2Token.balanceOf(tokenHolderA.address); const l2TokenTotalSupplyBefore = await l2Token.totalSupply(); - const dataToReceive = await packedTokenRateAndTimestamp(ctx.l2Provider, l1Token); + const refSlotTime = await refSlotTimestamp(accountingOracle); + const dataToReceive = await tokenRateAndTimestampPacked(tokenRate, refSlotTime, "0x"); const tx = await l2CrossDomainMessenger .connect(l1CrossDomainMessengerAliased) @@ -318,9 +320,10 @@ function bridgingTestsSuit(scenarioInstance: ScenarioTest) { l1LidoTokensBridge, l2ERC20ExtendedTokensBridge, l1CrossDomainMessenger, + accountingOracle } = ctx; const { accountA: tokenHolderA, accountB: tokenHolderB } = ctx.accounts; - const { depositAmount } = ctx.constants; + const { depositAmount, tokenRate } = ctx.constants; assert.notEqual(tokenHolderA.address, tokenHolderB.address); @@ -345,7 +348,8 @@ function bridgingTestsSuit(scenarioInstance: ScenarioTest) { "0x" ); - const dataToSend = await packedTokenRateAndTimestamp(ctx.l1Provider, l1Token); + const refSlotTime = await refSlotTimestamp(accountingOracle); + const dataToSend = await tokenRateAndTimestampPacked(tokenRate, refSlotTime, "0x"); await assert.emits(l1LidoTokensBridge, tx, "ERC20DepositInitiated", [ l1Token.address, @@ -396,18 +400,20 @@ function bridgingTestsSuit(scenarioInstance: ScenarioTest) { l2Token, l2CrossDomainMessenger, l2ERC20ExtendedTokensBridge, + accountingOracle } = ctx; const { accountA: tokenHolderA, accountB: tokenHolderB, l1CrossDomainMessengerAliased, } = ctx.accounts; - const { depositAmount } = ctx.constants; + const { depositAmount, tokenRate } = ctx.constants; const l2TokenTotalSupplyBefore = await l2Token.totalSupply(); const tokenHolderBBalanceBefore = await l2Token.balanceOf(tokenHolderB.address); - const dataToReceive = await packedTokenRateAndTimestamp(ctx.l2Provider, l1Token); + const refSlotTime = await refSlotTimestamp(accountingOracle); + const dataToReceive = await tokenRateAndTimestampPacked(tokenRate, refSlotTime, "0x"); const tx = await l2CrossDomainMessenger .connect(l1CrossDomainMessengerAliased) @@ -574,7 +580,7 @@ function bridgingTestsSuit(scenarioInstance: ScenarioTest) { function ctxFactory(depositAmount: BigNumber, withdrawalAmount: BigNumber) { return async () => { const networkName = env.network("TESTING_OPT_NETWORK", "mainnet"); - const tokenRate = BigNumber.from('1164454276599657236'); + const tokenRate = BigNumber.from('1164454276599657236000000000'); const { l1Provider, @@ -658,15 +664,6 @@ function ctxFactory(depositAmount: BigNumber, withdrawalAmount: BigNumber) { } } -async function packedTokenRateAndTimestamp(l1Provider: JsonRpcProvider, l1Token: ERC20WrapperStub) { - const stEthPerToken = await l1Token.stEthPerToken(); - const blockNumber = await l1Provider.getBlockNumber(); - const blockTimestamp = (await l1Provider.getBlock(blockNumber)).timestamp; - const stEthPerTokenStr = ethers.utils.hexZeroPad(stEthPerToken.toHexString(), 12); - const blockTimestampStr = ethers.utils.hexZeroPad(ethers.utils.hexlify(blockTimestamp), 5); - return ethers.utils.hexConcat([stEthPerTokenStr, blockTimestampStr]); -} - bridgingTestsSuit( scenario( "Optimism :: Bridging X non-rebasable token integration test", diff --git a/test/optimism/bridging-rebasable.integration.test.ts b/test/optimism/bridging-rebasable.integration.test.ts index 50c2a519..cf41ef23 100644 --- a/test/optimism/bridging-rebasable.integration.test.ts +++ b/test/optimism/bridging-rebasable.integration.test.ts @@ -1,13 +1,11 @@ import { assert } from "chai"; -import { ethers } from "hardhat"; import { BigNumber } from 'ethers' -import { JsonRpcProvider } from "@ethersproject/providers"; import env from "../../utils/env"; import { wei } from "../../utils/wei"; import optimism from "../../utils/optimism"; import testing, { scenario } from "../../utils/testing"; -import { ERC20WrapperStub } from "../../typechain"; import { ScenarioTest } from "../../utils/testing"; +import { tokenRateAndTimestampPacked, refSlotTimestamp } from "../../utils/testing/helpers"; type ContextType = Awaited>> @@ -84,7 +82,8 @@ function bridgingTestsSuit(scenarioInstance: ScenarioTest) { l2TokenRebasable, l1CrossDomainMessenger, l2ERC20ExtendedTokensBridge, - l1Provider + l1Provider, + accountingOracle } = ctx; const { accountA: tokenHolderA } = ctx.accounts; const { depositAmountOfRebasableToken, tokenRate } = ctx.constants; @@ -112,7 +111,8 @@ function bridgingTestsSuit(scenarioInstance: ScenarioTest) { "0x" ); - const dataToSend = await packedTokenRateAndTimestamp(l1Provider, l1Token); + const refSlotTime = await refSlotTimestamp(accountingOracle); + const dataToSend = await tokenRateAndTimestampPacked(tokenRate, refSlotTime, "0x"); await assert.emits(l1LidoTokensBridge, tx, "ERC20DepositInitiated", [ l1TokenRebasable.address, @@ -170,6 +170,7 @@ function bridgingTestsSuit(scenarioInstance: ScenarioTest) { const { l1Token, l1TokenRebasable, + accountingOracle, l2TokenRebasable, l1LidoTokensBridge, l2CrossDomainMessenger, @@ -187,7 +188,8 @@ function bridgingTestsSuit(scenarioInstance: ScenarioTest) { const tokenHolderABalanceBefore = await l2TokenRebasable.balanceOf(tokenHolderA.address); const l2TokenRebasableTotalSupplyBefore = await l2TokenRebasable.totalSupply(); - const dataToReceive = await packedTokenRateAndTimestamp(l2Provider, l1Token); + const refSlotTime = await refSlotTimestamp(accountingOracle); + const dataToReceive = await tokenRateAndTimestampPacked(tokenRate, refSlotTime, "0x"); const tx = await l2CrossDomainMessenger .connect(l1CrossDomainMessengerAliased) @@ -345,6 +347,7 @@ function bridgingTestsSuit(scenarioInstance: ScenarioTest) { const { l1Token, l1TokenRebasable, + accountingOracle, l1LidoTokensBridge, l2TokenRebasable, l1CrossDomainMessenger, @@ -380,7 +383,8 @@ function bridgingTestsSuit(scenarioInstance: ScenarioTest) { "0x" ); - const dataToSend = await packedTokenRateAndTimestamp(l1Provider, l1Token); + const refSlotTime = await refSlotTimestamp(accountingOracle); + const dataToSend = await tokenRateAndTimestampPacked(tokenRate, refSlotTime, "0x"); await assert.emits(l1LidoTokensBridge, tx, "ERC20DepositInitiated", [ l1TokenRebasable.address, @@ -438,6 +442,7 @@ function bridgingTestsSuit(scenarioInstance: ScenarioTest) { const { l1Token, l1TokenRebasable, + accountingOracle, l1LidoTokensBridge, l2TokenRebasable, l2CrossDomainMessenger, @@ -456,7 +461,8 @@ function bridgingTestsSuit(scenarioInstance: ScenarioTest) { const depositAmountNonRebasable = nonRebasableFromRebasable(depositAmountOfRebasableToken, tokenRate); const depositAmountRebasable = rebasableFromNonRebasable(depositAmountNonRebasable, tokenRate); - const dataToReceive = await packedTokenRateAndTimestamp(l2Provider, l1Token); + const refSlotTime = await refSlotTimestamp(accountingOracle); + const dataToReceive = await tokenRateAndTimestampPacked(tokenRate, refSlotTime, "0x"); const l2TokenRebasableTotalSupplyBefore = await l2TokenRebasable.totalSupply(); const tokenHolderBBalanceBefore = await l2TokenRebasable.balanceOf(tokenHolderB.address); @@ -623,7 +629,7 @@ function bridgingTestsSuit(scenarioInstance: ScenarioTest) { function ctxFactory(depositAmountOfRebasableToken: BigNumber, withdrawalAmountOfRebasableToken: BigNumber) { return async () => { const networkName = env.network("TESTING_OPT_NETWORK", "mainnet"); - const exchangeRate = BigNumber.from('1164454276599657236'); + const exchangeRate = BigNumber.from('1164454276599657236000000000'); const { l1Provider, @@ -707,25 +713,16 @@ function ctxFactory(depositAmountOfRebasableToken: BigNumber, withdrawalAmountOf } } -async function packedTokenRateAndTimestamp(l1Provider: JsonRpcProvider, l1Token: ERC20WrapperStub) { - const stEthPerToken = await l1Token.stEthPerToken(); - const blockNumber = await l1Provider.getBlockNumber(); - const blockTimestamp = (await l1Provider.getBlock(blockNumber)).timestamp; - const stEthPerTokenStr = ethers.utils.hexZeroPad(stEthPerToken.toHexString(), 12); - const blockTimestampStr = ethers.utils.hexZeroPad(ethers.utils.hexlify(blockTimestamp), 5); - return ethers.utils.hexConcat([stEthPerTokenStr, blockTimestampStr]); -} - function nonRebasableFromRebasable(rebasable: BigNumber, exchangeRate: BigNumber) { return BigNumber.from(rebasable) - .mul(BigNumber.from('1000000000000000000')) + .mul(BigNumber.from('1000000000000000000000000000')) .div(exchangeRate); } function rebasableFromNonRebasable(nonRebasable: BigNumber, exchangeRate: BigNumber) { return BigNumber.from(nonRebasable) .mul(exchangeRate) - .div(BigNumber.from('1000000000000000000')); + .div(BigNumber.from('1000000000000000000000000000')); } bridgingTestsSuit( diff --git a/test/optimism/deposit-gas-estimation.test.ts b/test/optimism/deposit-gas-estimation.test.ts index 945e5cbb..8994aa95 100644 --- a/test/optimism/deposit-gas-estimation.test.ts +++ b/test/optimism/deposit-gas-estimation.test.ts @@ -80,7 +80,7 @@ scenario("Optimism :: Bridging integration test", ctxFactory) } = ctx; const { accountA: tokenHolderA } = ctx.accounts; - const stEthPerToken = await l1Token.stEthPerToken(); + const stEthPerToken = await l1Token.getStETHByWstETH(BigNumber.from(10).pow(27)); await l1TokenRebasable .connect(tokenHolderA.l1Signer) @@ -136,7 +136,7 @@ scenario("Optimism :: Bridging integration test", ctxFactory) async function ctxFactory() { const networkName = env.network("TESTING_OPT_NETWORK", "mainnet"); console.log("networkName=", networkName); - const exchangeRate = BigNumber.from('1164454276599657236'); + const exchangeRate = BigNumber.from('1164454276599657236000000000'); const { l1Provider, diff --git a/test/optimism/pushingTokenRate.e2e.test.ts b/test/optimism/pushingTokenRate.e2e.test.ts index 99f36d93..a7cb3cc7 100644 --- a/test/optimism/pushingTokenRate.e2e.test.ts +++ b/test/optimism/pushingTokenRate.e2e.test.ts @@ -17,7 +17,7 @@ scenario("Optimism :: Push token rate to Oracle E2E test", ctxFactory) }) .step("Receive token rate", async (ctx) => { - const tokenRate = await ctx.l1Token.stEthPerToken(); + const tokenRate = await ctx.l1Token.getStETHByWstETH(BigNumber.from(10).pow(27)); const answer = await ctx.tokenRateOracle.latestAnswer(); assert.equalBN(answer, tokenRate); diff --git a/test/optimism/pushingTokenRate.integration.test.ts b/test/optimism/pushingTokenRate.integration.test.ts index b96fe79f..48b1ed85 100644 --- a/test/optimism/pushingTokenRate.integration.test.ts +++ b/test/optimism/pushingTokenRate.integration.test.ts @@ -1,5 +1,4 @@ import { assert } from "chai"; -import { ethers } from "hardhat"; import env from "../../utils/env"; import { wei } from "../../utils/wei"; import optimism from "../../utils/optimism"; @@ -7,13 +6,16 @@ import network from "../../utils/network"; import testing, { scenario } from "../../utils/testing"; import deploymentOracle from "../../utils/optimism/deploymentOracle"; import { getBridgeExecutorParams } from "../../utils/bridge-executor"; +import { tokenRateAndTimestamp } from "../../utils/testing/helpers"; import { BigNumber } from "ethers"; +import { getBlockTimestamp } from "../../utils/testing/helpers"; import { ERC20BridgedStub__factory, ERC20WrapperStub__factory, OptimismBridgeExecutor__factory, TokenRateNotifier__factory, - TokenRateOracle__factory + TokenRateOracle__factory, + AccountingOracleStub__factory } from "../../typechain"; scenario("Optimism :: Token Rate Oracle integration test", ctxFactory) @@ -24,23 +26,23 @@ scenario("Optimism :: Token Rate Oracle integration test", ctxFactory) tokenRateOracle, opTokenRatePusher, l1CrossDomainMessenger, - l1Token, - l1Provider + genesisTime, + secondsPerSlot, + lastProcessingRefSlot, + tokenRate } = ctx; - const tokenRate = await l1Token.stEthPerToken(); - const account = ctx.accounts.accountA; const tx = await tokenRateNotifier .connect(account.l1Signer) .handlePostTokenRebase(1, 2, 3, 4, 5, 6, 7); - const blockNumber = await l1Provider.getBlockNumber(); - const blockTimestamp = BigNumber.from((await l1Provider.getBlock(blockNumber)).timestamp); - const messageNonce = await l1CrossDomainMessenger.messageNonce(); - const [stEthPerTokenStr, blockTimestampStr] = await tokenRateAndTimestamp(tokenRate, blockTimestamp); + + const updateRateTime = genesisTime.add(secondsPerSlot.mul(lastProcessingRefSlot)); + const [stEthPerTokenStr, blockTimestampStr] = await tokenRateAndTimestamp(tokenRate, updateRateTime); + const l2Calldata = tokenRateOracle.interface.encodeFunctionData( "updateRate", [ @@ -62,9 +64,8 @@ scenario("Optimism :: Token Rate Oracle integration test", ctxFactory) const { opTokenRatePusher, tokenRateOracle, - l1Token, - l1Provider, - l1CrossDomainMessenger + l1CrossDomainMessenger, + genesisTime, secondsPerSlot, lastProcessingRefSlot, tokenRate } = ctx; const account = ctx.accounts.accountA; @@ -72,11 +73,8 @@ scenario("Optimism :: Token Rate Oracle integration test", ctxFactory) .connect(account.l1Signer) .setXDomainMessageSender(opTokenRatePusher); - const blockNumber = await l1Provider.getBlockNumber(); - const blockTimestamp = BigNumber.from((await l1Provider.getBlock(blockNumber)).timestamp); - - const tokenRate = await l1Token.stEthPerToken(); - const [stEthPerTokenStr, blockTimestampStr] = await tokenRateAndTimestamp(tokenRate, blockTimestamp); + const updateRateTime = genesisTime.add(secondsPerSlot.mul(lastProcessingRefSlot)); + const [stEthPerTokenStr, blockTimestampStr] = await tokenRateAndTimestamp(tokenRate, updateRateTime); const tx = await ctx.l2CrossDomainMessenger .connect(ctx.accounts.l1CrossDomainMessengerAliased) @@ -99,18 +97,23 @@ scenario("Optimism :: Token Rate Oracle integration test", ctxFactory) const [ , tokenRateAnswer, + startedAt_, , - updatedAt, - ] = await tokenRateOracle.latestRoundData(); assert.equalBN(tokenRateAnswer, tokenRate); - assert.equalBN(updatedAt, blockTimestampStr); + assert.equalBN(startedAt_, updateRateTime); }) .run(); async function ctxFactory() { + const l2GasLimitForPushingTokenRate = 1000; + const tokenRateOutdatedDelay = 86400; + const maxAllowedL2ToL1ClockLag = BigNumber.from(86400); + const maxAllowedTokenRateDeviationPerDay = BigNumber.from(500); + const tokenRate = BigNumber.from('1164454276599657236000000000'); + const networkName = env.network("TESTING_OPT_NETWORK", "mainnet"); const [l1Provider, l2Provider] = network .multichain(["eth", "opt"], networkName) @@ -118,11 +121,12 @@ async function ctxFactory() { const l1Deployer = testing.accounts.deployer(l1Provider); const l2Deployer = testing.accounts.deployer(l2Provider); - const blockNumber = await l2Provider.getBlockNumber(); - const blockTimestamp = BigNumber.from((await l2Provider.getBlock(blockNumber)).timestamp); + const blockTimestamp = await getBlockTimestamp(l1Provider, 0); + const blockTimestampInPast = await getBlockTimestamp(l1Provider, -86400); - const blockTimestampInPast = BigNumber.from((await l2Provider.getBlock(blockNumber)).timestamp).sub(86400); - const tokenRate = BigNumber.from('1164454276599657236'); + const genesisTime = blockTimestamp; + const secondsPerSlot = BigNumber.from(10); + const lastProcessingRefSlot = BigNumber.from(20); const optContracts = optimism.contracts(networkName, { forking: true }); const l2CrossDomainMessenger = optContracts.L2CrossDomainMessenger; @@ -151,12 +155,20 @@ async function ctxFactory() { "TT", tokenRate ); + + const accountingOracle = await new AccountingOracleStub__factory(l1Deployer).deploy( + genesisTime, + secondsPerSlot, + lastProcessingRefSlot + ); + const [ethDeployScript, optDeployScript] = await deploymentOracle( networkName ).oracleDeployScript( l1Token.address, - 1000, - 86400, + accountingOracle.address, + l2GasLimitForPushingTokenRate, + tokenRateOutdatedDelay, { deployer: l1Deployer, admins: { @@ -173,8 +185,8 @@ async function ctxFactory() { }, contractsShift: 0, tokenRateOracle: { - maxAllowedL2ToL1ClockLag: BigNumber.from(86400), - maxAllowedTokenRateDeviationPerDay: BigNumber.from(500), + maxAllowedL2ToL1ClockLag: maxAllowedL2ToL1ClockLag, + maxAllowedTokenRateDeviationPerDay: maxAllowedTokenRateDeviationPerDay, tokenRate: tokenRate, l1Timestamp: BigNumber.from(blockTimestampInPast) } @@ -218,17 +230,14 @@ async function ctxFactory() { l1CrossDomainMessenger, l2CrossDomainMessenger, l1Token, + accountingOracle, l1Provider, blockTimestamp, + tokenRate, + genesisTime, secondsPerSlot, lastProcessingRefSlot, accounts: { accountA, l1CrossDomainMessengerAliased } }; } - -async function tokenRateAndTimestamp(tokenRate: BigNumber, blockTimestamp: BigNumber) { - const stEthPerTokenStr = ethers.utils.hexZeroPad(tokenRate.toHexString(), 12); - const blockTimestampStr = ethers.utils.hexZeroPad(ethers.utils.hexlify(blockTimestamp), 5); - return [stEthPerTokenStr, blockTimestampStr]; -} diff --git a/test/token/ERC20Permit.unit.test.ts b/test/token/ERC20Permit.unit.test.ts index 9ce73cc4..02fecb57 100644 --- a/test/token/ERC20Permit.unit.test.ts +++ b/test/token/ERC20Permit.unit.test.ts @@ -322,7 +322,7 @@ function ctxFactoryFactory( return async () => { const decimalsToSet = 18; const decimals = BigNumber.from(10).pow(decimalsToSet); - const tokenRate = BigNumber.from('1164454276599657236'); + const tokenRate = BigNumber.from('1164454276599657236000000000'); const premintShares = wei.toBigNumber(wei`100 ether`); const premintTokens = tokenRate.mul(premintShares).div(decimals); diff --git a/test/token/ERC20RebasableBridgedPermit.unit.test.ts b/test/token/ERC20RebasableBridgedPermit.unit.test.ts index 7d855d41..f5b54e78 100644 --- a/test/token/ERC20RebasableBridgedPermit.unit.test.ts +++ b/test/token/ERC20RebasableBridgedPermit.unit.test.ts @@ -1248,9 +1248,9 @@ async function ctxFactory() { const name = "StETH Test Token"; const symbol = "StETH"; const version = "1"; - const decimals = BigNumber.from('18'); + const decimals = BigNumber.from('27'); const tenPowDecimals = BigNumber.from('10').pow(decimals); - const tokenRate = BigNumber.from('1164454276599657236'); // value taken from real contact on 23.04.24 + const tokenRate = BigNumber.from('1164454276599657236000000000'); // value taken from real contact on 23.04.24 const tokenRateOutdatedDelay = BigNumber.from(86400); // 1 day const maxAllowedL2ToL1ClockLag = BigNumber.from(86400); // 1 day const maxAllowedTokenRateDeviationPerDay = BigNumber.from(500); // 5% @@ -1283,7 +1283,7 @@ async function ctxFactory() { owner.address ); - const tokenRateOracle = await tokenRateOracleUnderProxy( + const { tokenRateOracle } = await tokenRateOracleUnderProxy( deployer, zero.address, owner.address, diff --git a/utils/deployment.ts b/utils/deployment.ts index d9037d42..ea444a39 100644 --- a/utils/deployment.ts +++ b/utils/deployment.ts @@ -12,6 +12,7 @@ interface ChainDeploymentConfig extends BridgingManagerSetupConfig { interface MultiChainDeploymentConfig { l1Token: string; l1RebasableToken: string; + accountingOracle: string; l1OpStackTokenRatePusher: string; l2GasLimitForPushingTokenRate: number; tokenRateOutdatedDelay: number; @@ -28,6 +29,7 @@ export function loadMultiChainDeploymentConfig(): MultiChainDeploymentConfig { return { l1Token: env.address("TOKEN"), l1RebasableToken: env.address("REBASABLE_TOKEN"), + accountingOracle: env.address("ACCOUNTING_ORACLE"), l1OpStackTokenRatePusher: env.address("L1_OP_STACK_TOKEN_RATE_PUSHER"), l2GasLimitForPushingTokenRate: Number(env.string("L2_GAS_LIMIT_FOR_PUSHING_TOKEN_RATE")), tokenRateOutdatedDelay: Number(env.string("TOKEN_RATE_OUTDATED_DELAY")), diff --git a/utils/optimism/deploymentAllFromScratch.ts b/utils/optimism/deploymentAllFromScratch.ts index e89e78d6..14ba6fa5 100644 --- a/utils/optimism/deploymentAllFromScratch.ts +++ b/utils/optimism/deploymentAllFromScratch.ts @@ -108,6 +108,7 @@ export default function deploymentAll( async deployAllScript( l1Token: string, l1TokenRebasable: string, + accountingOracle: string, l1Params: OptL1DeployScriptParams, l2Params: OptL2DeployScriptParams, ): Promise<[L1DeployAllScript, L2DeployAllScript]> { @@ -147,6 +148,7 @@ export default function deploymentAll( l1TokenRebasable, expectedL2TokenProxyAddress, expectedL2TokenRebasableProxyAddress, + accountingOracle, options?.overrides, ], afterDeploy: (c) => @@ -180,6 +182,7 @@ export default function deploymentAll( args: [ optAddresses.L1CrossDomainMessenger, l1Token, + accountingOracle, expectedL2TokenRateOracleProxyAddress, 1000, options?.overrides, diff --git a/utils/optimism/deploymentOracle.ts b/utils/optimism/deploymentOracle.ts index 8536e140..f58fccc9 100644 --- a/utils/optimism/deploymentOracle.ts +++ b/utils/optimism/deploymentOracle.ts @@ -63,6 +63,7 @@ export default function deploymentOracle( return { async oracleDeployScript( l1Token: string, + accountingOracle: string, l2GasLimitForPushingTokenRate: number, tokenRateOutdatedDelay: number, l1Params: OptDeployScriptParams, @@ -99,6 +100,7 @@ export default function deploymentOracle( args: [ optAddresses.L1CrossDomainMessenger, l1Token, + accountingOracle, expectedL2TokenRateOracleProxyAddress, l2GasLimitForPushingTokenRate, options?.overrides, diff --git a/utils/optimism/testing.ts b/utils/optimism/testing.ts index 2aa2f18d..fe2a2a58 100644 --- a/utils/optimism/testing.ts +++ b/utils/optimism/testing.ts @@ -15,6 +15,7 @@ import { L2ERC20ExtendedTokensBridge__factory, CrossDomainMessengerStub__factory, ERC20RebasableBridgedPermit__factory, + AccountingOracleStub__factory } from "../../typechain"; import addresses from "./addresses"; import contracts from "./contracts"; @@ -163,6 +164,10 @@ async function loadDeployedBridges( testingUtils.env.OPT_L1_REBASABLE_TOKEN(), l1SignerOrProvider ), + accountingOracle: AccountingOracleStub__factory.connect( + testingUtils.env.OPT_L1_REBASABLE_TOKEN(), + l1SignerOrProvider + ), ...connectBridgeContracts( { @@ -199,11 +204,18 @@ async function deployTestBridge( tokenRate ); + const accountingOracle = await new AccountingOracleStub__factory(ethDeployer).deploy( + 1, + 2, + 3 + ); + const [ethDeployScript, optDeployScript] = await deploymentAll( networkName ).deployAllScript( l1Token.address, l1TokenRebasable.address, + accountingOracle.address, { deployer: ethDeployer, admins: { proxy: ethDeployer.address, bridge: ethDeployer.address }, @@ -248,6 +260,7 @@ async function deployTestBridge( return { l1Token: l1Token.connect(ethProvider), l1TokenRebasable: l1TokenRebasable.connect(ethProvider), + accountingOracle: accountingOracle.connect(ethProvider), ...connectBridgeContracts( { tokenRateOracle: optDeployScript.tokenRateOracleProxyAddress, diff --git a/utils/testing/contractsFactory.ts b/utils/testing/contractsFactory.ts index 3e49a940..bea28998 100644 --- a/utils/testing/contractsFactory.ts +++ b/utils/testing/contractsFactory.ts @@ -1,117 +1,124 @@ +import hre from "hardhat"; import { BigNumber } from "ethers"; import { SignerWithAddress } from "@nomiclabs/hardhat-ethers/signers"; import { - ERC20BridgedPermit__factory, - TokenRateOracle__factory, - ERC20RebasableBridgedPermit__factory, - OssifiableProxy__factory, - TokenRateOracle, - ERC20BridgedPermit + ERC20BridgedPermit__factory, + TokenRateOracle__factory, + ERC20RebasableBridgedPermit__factory, + OssifiableProxy__factory, + TokenRateOracle, + ERC20BridgedPermit } from "../../typechain"; export async function erc20BridgedPermitUnderProxy( - deployer: SignerWithAddress, - holder: SignerWithAddress, - name: string, - symbol: string, - version: string, - decimals: BigNumber, - bridge: string + deployer: SignerWithAddress, + holder: SignerWithAddress, + name: string, + symbol: string, + version: string, + decimals: BigNumber, + bridge: string ) { - const erc20BridgedPermitImpl = await new ERC20BridgedPermit__factory(deployer).deploy( - name, - symbol, - version, - decimals, - bridge - ); + const erc20BridgedPermitImpl = await new ERC20BridgedPermit__factory(deployer).deploy( + name, + symbol, + version, + decimals, + bridge + ); - const erc20BridgedPermitProxy = await new OssifiableProxy__factory(deployer).deploy( - erc20BridgedPermitImpl.address, - deployer.address, - ERC20BridgedPermit__factory.createInterface().encodeFunctionData("initialize", [ - name, - symbol, - version - ]) - ); + const erc20BridgedPermitProxy = await new OssifiableProxy__factory(deployer).deploy( + erc20BridgedPermitImpl.address, + deployer.address, + ERC20BridgedPermit__factory.createInterface().encodeFunctionData("initialize", [ + name, + symbol, + version + ]) + ); - return ERC20BridgedPermit__factory.connect( - erc20BridgedPermitProxy.address, - holder - ); + return ERC20BridgedPermit__factory.connect( + erc20BridgedPermitProxy.address, + holder + ); } export async function tokenRateOracleUnderProxy( - deployer: SignerWithAddress, + deployer: SignerWithAddress, + messenger: string, + l2ERC20TokenBridge: string, + l1TokenRatePusher: string, + tokenRateOutdatedDelay: BigNumber, + maxAllowedL2ToL1ClockLag: BigNumber, + maxAllowedTokenRateDeviationPerDay: BigNumber, + tokenRate: BigNumber, + rateL1Timestamp: BigNumber +) { + const tokenRateOracleImpl = await new TokenRateOracle__factory(deployer).deploy( + messenger, + l2ERC20TokenBridge, + l1TokenRatePusher, + tokenRateOutdatedDelay, + maxAllowedL2ToL1ClockLag, + maxAllowedTokenRateDeviationPerDay + ); - messenger: string, - l2ERC20TokenBridge: string, - l1TokenRatePusher: string, - tokenRateOutdatedDelay: BigNumber, - maxAllowedL2ToL1ClockLag: BigNumber, - maxAllowedTokenRateDeviationPerDay: BigNumber, + const tokenRateOracleProxy = new OssifiableProxy__factory(deployer); - tokenRate: BigNumber, - blockTimestamp: BigNumber -) { - const tokenRateOracleImpl = await new TokenRateOracle__factory(deployer).deploy( - messenger, - l2ERC20TokenBridge, - l1TokenRatePusher, - tokenRateOutdatedDelay, - maxAllowedL2ToL1ClockLag, - maxAllowedTokenRateDeviationPerDay - ); - const tokenRateOracleProxy = await new OssifiableProxy__factory( - deployer - ).deploy( - tokenRateOracleImpl.address, - deployer.address, - tokenRateOracleImpl.interface.encodeFunctionData("initialize", [ - tokenRate, - blockTimestamp - ]) - ); - return TokenRateOracle__factory.connect( - tokenRateOracleProxy.address, - deployer - ); + const unsignedTx = tokenRateOracleProxy.getDeployTransaction(tokenRateOracleImpl.address, + deployer.address, + tokenRateOracleImpl.interface.encodeFunctionData("initialize", [ + tokenRate, + rateL1Timestamp + ])); + + const response = await deployer.sendTransaction(unsignedTx); + const provider = await hre.ethers.provider; + const contractReceipt = await response.wait(); + const blockTimestampOfDeployment = BigNumber.from((await provider.getBlock(contractReceipt.blockNumber)).timestamp); + const txResponse = await response.wait(); + + const tokenRateOracle = TokenRateOracle__factory.connect( + txResponse.contractAddress, + deployer + ); + + return { tokenRateOracle, blockTimestampOfDeployment }; } export async function erc20RebasableBridgedPermitUnderProxy( - deployer: SignerWithAddress, - holder: SignerWithAddress, - name: string, - symbol: string, - version: string, - decimals: BigNumber, - tokenRateOracle: TokenRateOracle, - erc20BridgedPermit: ERC20BridgedPermit, - bridge: string + deployer: SignerWithAddress, + holder: SignerWithAddress, + name: string, + symbol: string, + version: string, + decimals: BigNumber, + tokenRateOracle: TokenRateOracle, + erc20BridgedPermit: ERC20BridgedPermit, + bridge: string ) { - const erc20RebasableBridgedPermitImpl = await new ERC20RebasableBridgedPermit__factory(deployer).deploy( - name, - symbol, - version, - decimals, - erc20BridgedPermit.address, - tokenRateOracle.address, - bridge - ); + const erc20RebasableBridgedPermitImpl = await new ERC20RebasableBridgedPermit__factory(deployer).deploy( + name, + symbol, + version, + decimals, + erc20BridgedPermit.address, + tokenRateOracle.address, + bridge + ); - const erc20RebasableBridgedPermitProxy = await new OssifiableProxy__factory(deployer).deploy( - erc20RebasableBridgedPermitImpl.address, - deployer.address, - ERC20RebasableBridgedPermit__factory.createInterface().encodeFunctionData("initialize", [ - name, - symbol, - version, - ]) - ); + const erc20RebasableBridgedPermitProxy = await new OssifiableProxy__factory(deployer).deploy( + erc20RebasableBridgedPermitImpl.address, + deployer.address, + ERC20RebasableBridgedPermit__factory.createInterface().encodeFunctionData("initialize", [ + name, + symbol, + version, + ]) + ); - return ERC20RebasableBridgedPermit__factory.connect( - erc20RebasableBridgedPermitProxy.address, - holder - ); + return ERC20RebasableBridgedPermit__factory.connect( + erc20RebasableBridgedPermitProxy.address, + holder + ); } diff --git a/utils/testing/helpers.ts b/utils/testing/helpers.ts new file mode 100644 index 00000000..f8fabeba --- /dev/null +++ b/utils/testing/helpers.ts @@ -0,0 +1,96 @@ +import { utils, BigNumber, ethers } from "ethers"; +import { ContractTransaction } from "@ethersproject/contracts"; +import { JsonRpcProvider } from "@ethersproject/providers"; +import { ERC20WrapperStub, AccountingOracleStub } from "../../typechain"; +import { SignerWithAddress } from "@nomiclabs/hardhat-ethers/signers"; +import { getContractAddress } from "ethers/lib/utils"; + +export async function getContractTransactionTimestamp(provider: JsonRpcProvider, tx: ContractTransaction) { + const contractReceipt = await tx.wait(); + return BigNumber.from((await provider.getBlock(contractReceipt.blockNumber)).timestamp); +} + +export async function getBlockTimestamp(provider: JsonRpcProvider, secondsToShift: number) { + const blockNumber = await provider.getBlockNumber(); + return BigNumber.from((await provider.getBlock(blockNumber)).timestamp + secondsToShift); +} + +export function getInterfaceID(contractInterface: utils.Interface) { + let interfaceID = ethers.constants.Zero; + const functions: string[] = Object.keys(contractInterface.functions); + for (let i = 0; i < functions.length; i++) { + interfaceID = interfaceID.xor(contractInterface.getSighash(functions[i])); + } + return interfaceID; +} + +export async function packedTokenRateAndTimestamp(provider: JsonRpcProvider, l1Token: ERC20WrapperStub) { + const stEthPerToken = await l1Token.getStETHByWstETH(BigNumber.from(10).pow(27)); + const stEthPerTokenStr = ethers.utils.hexZeroPad(stEthPerToken.toHexString(), 16); + + const blockNumber = await provider.getBlockNumber(); + const blockTimestamp = (await provider.getBlock(blockNumber)).timestamp; + const blockTimestampStr = ethers.utils.hexZeroPad(ethers.utils.hexlify(blockTimestamp), 5); + + return ethers.utils.hexConcat([stEthPerTokenStr, blockTimestampStr]); +} + +async function packedTokenRateAndTimestamp2(provider: JsonRpcProvider, tokenRate: BigNumber) { + const stEthPerTokenStr = ethers.utils.hexZeroPad(tokenRate.toHexString(), 16); + + const blockNumber = await provider.getBlockNumber(); + const blockTimestamp = (await provider.getBlock(blockNumber)).timestamp; + const blockTimestampStr = ethers.utils.hexZeroPad(ethers.utils.hexlify(blockTimestamp), 5); + + return ethers.utils.hexConcat([stEthPerTokenStr, blockTimestampStr]); +} + +export async function packedTokenRateAndTimestampForL1Bridge( + l1Token: ERC20WrapperStub, + accountingOracle: AccountingOracleStub +) { + const stEthPerToken = await l1Token.getStETHByWstETH(BigNumber.from(10).pow(27)); + const stEthPerTokenStr = ethers.utils.hexZeroPad(stEthPerToken.toHexString(), 16); + + const genesisTime = await accountingOracle.GENESIS_TIME(); + const secondsPerSlot = await accountingOracle.SECONDS_PER_SLOT(); + const lastProcessingRefSlot = await accountingOracle.lastProcessingRefSlot(); + const refSlotTimestamp = genesisTime.add(secondsPerSlot.mul(lastProcessingRefSlot)); + const blockTimestampStr = ethers.utils.hexZeroPad(ethers.utils.hexlify(refSlotTimestamp), 5); + + return ethers.utils.hexConcat([stEthPerTokenStr, blockTimestampStr]); +} + +export async function tokenRateAndTimestamp(tokenRate: BigNumber, blockTimestamp: BigNumber) { + const stEthPerTokenStr = ethers.utils.hexZeroPad(tokenRate.toHexString(), 16); + const blockTimestampStr = ethers.utils.hexZeroPad(ethers.utils.hexlify(blockTimestamp), 5); + return [stEthPerTokenStr, blockTimestampStr]; +} + +export async function tokenRateAndTimestampPacked(tokenRate: BigNumber, blockTimestamp: BigNumber, data: string) { + const stEthPerTokenStr = ethers.utils.hexZeroPad(tokenRate.toHexString(), 16); + const blockTimestampStr = ethers.utils.hexZeroPad(ethers.utils.hexlify(blockTimestamp), 5); + return ethers.utils.hexConcat([stEthPerTokenStr, blockTimestampStr, data]); +} + +export async function refSlotTimestamp(accountingOracle: AccountingOracleStub) { + const genesisTime = await accountingOracle.GENESIS_TIME(); + const secondsPerSlot = await accountingOracle.SECONDS_PER_SLOT(); + const lastProcessingRefSlot = await accountingOracle.getLastProcessingRefSlot(); + return genesisTime.add(secondsPerSlot.mul(lastProcessingRefSlot)); +} + +export async function predictAddresses(account: SignerWithAddress, txsCount: number) { + const currentNonce = await account.getTransactionCount(); + + const res: string[] = []; + for (let i = 0; i < txsCount; ++i) { + res.push( + getContractAddress({ + from: account.address, + nonce: currentNonce + i, + }) + ); + } + return res; +} From e55068e1fbab6173fa282785a4891cce9b1f971f Mon Sep 17 00:00:00 2001 From: Anton Kovalchuk Date: Tue, 21 May 2024 01:22:28 +0200 Subject: [PATCH 126/148] refactor scripts --- .../L2ERC20ExtendedTokensBridge.unit.test.ts | 189 +++++++++--------- .../OpStackTokenRatePusher.unit.test.ts | 18 +- test/optimism/TokenRateNotifier.unit.test.ts | 83 ++++---- test/optimism/TokenRateOracle.unit.test.ts | 9 +- .../pushingTokenRate.integration.test.ts | 14 +- test/token/ERC20BridgedPermit.unit.test.ts | 22 +- .../ERC20RebasableBridgedPermit.unit.test.ts | 59 ++++-- utils/testing/helpers.ts | 73 ++----- 8 files changed, 238 insertions(+), 229 deletions(-) diff --git a/test/optimism/L2ERC20ExtendedTokensBridge.unit.test.ts b/test/optimism/L2ERC20ExtendedTokensBridge.unit.test.ts index 3513bdc3..04edcee3 100644 --- a/test/optimism/L2ERC20ExtendedTokensBridge.unit.test.ts +++ b/test/optimism/L2ERC20ExtendedTokensBridge.unit.test.ts @@ -1,9 +1,8 @@ -import hre, { ethers } from "hardhat"; +import hre from "hardhat"; import { BigNumber } from "ethers"; -import { predictAddresses } from "../../utils/testing/helpers"; import { assert } from "chai"; import { SignerWithAddress } from "@nomiclabs/hardhat-ethers/signers"; -import { JsonRpcProvider } from "@ethersproject/providers"; +import { tokenRateAndTimestampPacked, getBlockTimestamp, predictAddresses } from "../../utils/testing/helpers"; import testing, { unit } from "../../utils/testing"; import { wei } from "../../utils/wei"; import { @@ -22,9 +21,8 @@ import { unit("Optimism:: L2ERC20ExtendedTokensBridge", ctxFactory) .test("initial state", async (ctx) => { const { - l2TokenBridge, accounts: { l1TokenBridgeEOA, l2MessengerStubEOA }, - stubs: { l1TokenNonRebasable, l2TokenNonRebasable, l1TokenRebasable, l2TokenRebasable }, + contracts: { l2TokenBridge, l1TokenNonRebasable, l2TokenNonRebasable, l1TokenRebasable, l2TokenRebasable }, } = ctx; assert.equal(await l2TokenBridge.l1TokenBridge(), l1TokenBridgeEOA.address); @@ -132,13 +130,12 @@ unit("Optimism:: L2ERC20ExtendedTokensBridge", ctxFactory) .test("withdraw() :: withdrawals disabled", async (ctx) => { const { - l2TokenBridge, - stubs: { l2TokenNonRebasable, l2TokenRebasable }, + contracts: { l2TokenBridge, l2TokenNonRebasable, l2TokenRebasable }, } = ctx; - await ctx.l2TokenBridge.disableWithdrawals(); + await l2TokenBridge.disableWithdrawals(); - assert.isFalse(await ctx.l2TokenBridge.isWithdrawalsEnabled()); + assert.isFalse(await l2TokenBridge.isWithdrawalsEnabled()); await assert.revertsWith( l2TokenBridge.withdraw( @@ -163,7 +160,7 @@ unit("Optimism:: L2ERC20ExtendedTokensBridge", ctxFactory) .test("withdraw() :: unsupported l2Token", async (ctx) => { const { - l2TokenBridge, + contracts: { l2TokenBridge }, accounts: { stranger }, } = ctx; await assert.revertsWith( @@ -174,9 +171,8 @@ unit("Optimism:: L2ERC20ExtendedTokensBridge", ctxFactory) .test("withdraw() :: not from EOA", async (ctx) => { const { - l2TokenBridge, accounts: { emptyContractEOA }, - stubs: { l2TokenRebasable, l2TokenNonRebasable }, + contracts: { l2TokenBridge, l2TokenRebasable, l2TokenNonRebasable }, } = ctx; await assert.revertsWith( @@ -205,9 +201,9 @@ unit("Optimism:: L2ERC20ExtendedTokensBridge", ctxFactory) .test("withdraw() :: non-rebasable token flow", async (ctx) => { const { - l2TokenBridge, accounts: { deployer, l1TokenBridgeEOA }, - stubs: { + contracts: { + l2TokenBridge, l2Messenger, l1TokenNonRebasable, l2TokenNonRebasable, @@ -268,23 +264,28 @@ unit("Optimism:: L2ERC20ExtendedTokensBridge", ctxFactory) .test("withdraw() :: rebasable token flow", async (ctx) => { const { - l2TokenBridge, accounts: { deployer, l1TokenBridgeEOA, l2MessengerStubEOA, recipient }, - stubs: { + contracts: { + l2TokenBridge, l2Messenger, l1TokenRebasable, l2TokenRebasable }, + constants: { exchangeRate, decimalsBN } } = ctx; const amountToDeposit = wei`1 ether`; - const amountToWithdraw = wei.toBigNumber(amountToDeposit).mul(ctx.exchangeRate).div(ctx.decimalsBN); + const amountToWithdraw = wei.toBigNumber(amountToDeposit).mul(exchangeRate).div(decimalsBN); const l1Gas = wei`1 wei`; const data = "0xdeadbeaf"; - const provider = await hre.ethers.provider; - const packedTokenRateAndTimestampData = await packedTokenRateAndTimestamp(provider, ctx.exchangeRate); + const currentBlockTimestamp = await getBlockTimestamp(ctx.provider, 0); + const packedTokenRateAndTimestampData = await tokenRateAndTimestampPacked( + exchangeRate, + currentBlockTimestamp, + data + ); - const tx1 = await l2TokenBridge + await l2TokenBridge .connect(l2MessengerStubEOA) .finalizeDeposit( l1TokenRebasable.address, @@ -345,9 +346,9 @@ unit("Optimism:: L2ERC20ExtendedTokensBridge", ctxFactory) .test("withdraw() :: zero rebasable tokens", async (ctx) => { const { - l2TokenBridge, accounts: { deployer, l1TokenBridgeEOA, recipient }, - stubs: { + contracts: { + l2TokenBridge, l2Messenger, l1TokenRebasable, l2TokenRebasable @@ -402,9 +403,9 @@ unit("Optimism:: L2ERC20ExtendedTokensBridge", ctxFactory) .test("withdraw() :: zero non-rebasable tokens", async (ctx) => { const { - l2TokenBridge, - accounts: { deployer, l1TokenBridgeEOA, recipient }, - stubs: { + accounts: { l1TokenBridgeEOA, recipient }, + contracts: { + l2TokenBridge, l2Messenger, l1TokenNonRebasable, l2TokenNonRebasable @@ -459,14 +460,13 @@ unit("Optimism:: L2ERC20ExtendedTokensBridge", ctxFactory) .test("withdrawTo() :: withdrawals disabled", async (ctx) => { const { - l2TokenBridge, - stubs: { l2TokenNonRebasable, l2TokenRebasable }, + contracts: { l2TokenBridge, l2TokenNonRebasable, l2TokenRebasable }, accounts: { recipient }, } = ctx; - await ctx.l2TokenBridge.disableWithdrawals(); + await l2TokenBridge.disableWithdrawals(); - assert.isFalse(await ctx.l2TokenBridge.isWithdrawalsEnabled()); + assert.isFalse(await l2TokenBridge.isWithdrawalsEnabled()); await assert.revertsWith( l2TokenBridge.withdrawTo( @@ -492,7 +492,7 @@ unit("Optimism:: L2ERC20ExtendedTokensBridge", ctxFactory) .test("withdrawTo() :: unsupported l2Token", async (ctx) => { const { - l2TokenBridge, + contracts: { l2TokenBridge }, accounts: { stranger, recipient }, } = ctx; await assert.revertsWith( @@ -509,9 +509,9 @@ unit("Optimism:: L2ERC20ExtendedTokensBridge", ctxFactory) .test("withdrawTo() :: non rebasable token flow", async (ctx) => { const { - l2TokenBridge, accounts: { deployer, recipient, l1TokenBridgeEOA }, - stubs: { + contracts: { + l2TokenBridge, l2Messenger: l2MessengerStub, l1TokenNonRebasable, l2TokenNonRebasable @@ -574,25 +574,28 @@ unit("Optimism:: L2ERC20ExtendedTokensBridge", ctxFactory) .test("withdrawTo() :: rebasable token flow", async (ctx) => { const { - l2TokenBridge, accounts: { deployer, l1TokenBridgeEOA, l2MessengerStubEOA, recipient }, - stubs: { + contracts: { + l2TokenBridge, l2Messenger, - l1TokenNonRebasable, - l2TokenNonRebasable, l1TokenRebasable, l2TokenRebasable }, + constants: { exchangeRate, decimalsBN } } = ctx; const amountToDeposit = wei`1 ether`; // shares - const amountToWithdraw = wei.toBigNumber(amountToDeposit).mul(ctx.exchangeRate).div(ctx.decimalsBN); + const amountToWithdraw = wei.toBigNumber(amountToDeposit).mul(exchangeRate).div(decimalsBN); const l1Gas = wei`1 wei`; const data = "0xdeadbeaf"; - const provider = await hre.ethers.provider; - const packedTokenRateAndTimestampData = await packedTokenRateAndTimestamp(provider, ctx.exchangeRate); + const currentBlockTimestamp = await getBlockTimestamp(ctx.provider, 0); + const packedTokenRateAndTimestampData = await tokenRateAndTimestampPacked( + exchangeRate, + currentBlockTimestamp, + data + ); - const tx1 = await l2TokenBridge + await l2TokenBridge .connect(l2MessengerStubEOA) .finalizeDeposit( l1TokenRebasable.address, @@ -654,9 +657,9 @@ unit("Optimism:: L2ERC20ExtendedTokensBridge", ctxFactory) .test("withdrawTo() :: zero rebasable tokens", async (ctx) => { const { - l2TokenBridge, accounts: { deployer, l1TokenBridgeEOA, recipient }, - stubs: { + contracts: { + l2TokenBridge, l2Messenger, l1TokenRebasable, l2TokenRebasable @@ -712,9 +715,9 @@ unit("Optimism:: L2ERC20ExtendedTokensBridge", ctxFactory) .test("withdrawTo() :: zero non-rebasable tokens", async (ctx) => { const { - l2TokenBridge, - accounts: { deployer, l1TokenBridgeEOA, recipient }, - stubs: { + accounts: { l1TokenBridgeEOA, recipient }, + contracts: { + l2TokenBridge, l2Messenger, l1TokenNonRebasable, l2TokenNonRebasable @@ -770,9 +773,9 @@ unit("Optimism:: L2ERC20ExtendedTokensBridge", ctxFactory) .test("withdrawTo() :: sending to L1 stETH address", async (ctx) => { const { - l2TokenBridge, accounts: { recipient }, - stubs: { + contracts: { + l2TokenBridge, l1TokenRebasable, l2TokenRebasable }, @@ -796,9 +799,8 @@ unit("Optimism:: L2ERC20ExtendedTokensBridge", ctxFactory) .test("finalizeDeposit() :: deposits disabled", async (ctx) => { const { - l2TokenBridge, accounts: { l2MessengerStubEOA, deployer, recipient }, - stubs: { l1TokenNonRebasable, l2TokenNonRebasable, l1TokenRebasable, l2TokenRebasable }, + contracts: { l2TokenBridge, l1TokenNonRebasable, l2TokenNonRebasable, l1TokenRebasable, l2TokenRebasable }, } = ctx; await l2TokenBridge.disableDeposits(); @@ -835,9 +837,8 @@ unit("Optimism:: L2ERC20ExtendedTokensBridge", ctxFactory) .test("finalizeDeposit() :: unsupported l1Token", async (ctx) => { const { - l2TokenBridge, accounts: { l2MessengerStubEOA, deployer, recipient, stranger }, - stubs: { l2TokenNonRebasable, l2TokenRebasable }, + contracts: { l2TokenBridge, l2TokenNonRebasable, l2TokenRebasable }, } = ctx; await assert.revertsWith( @@ -870,9 +871,8 @@ unit("Optimism:: L2ERC20ExtendedTokensBridge", ctxFactory) .test("finalizeDeposit() :: unsupported l2Token", async (ctx) => { const { - l2TokenBridge, accounts: { l2MessengerStubEOA, deployer, recipient, stranger }, - stubs: { l1TokenNonRebasable, l1TokenRebasable }, + contracts: { l2TokenBridge, l1TokenNonRebasable, l1TokenRebasable }, } = ctx; await assert.revertsWith( @@ -905,9 +905,8 @@ unit("Optimism:: L2ERC20ExtendedTokensBridge", ctxFactory) .test("finalizeDeposit() :: unsupported tokens combination", async (ctx) => { const { - l2TokenBridge, - accounts: { l2MessengerStubEOA, deployer, recipient, stranger }, - stubs: { l1TokenNonRebasable, l1TokenRebasable, l2TokenNonRebasable, l2TokenRebasable }, + accounts: { l2MessengerStubEOA, deployer, recipient }, + contracts: { l2TokenBridge, l1TokenNonRebasable, l1TokenRebasable, l2TokenNonRebasable, l2TokenRebasable }, } = ctx; await assert.revertsWith( @@ -940,8 +939,7 @@ unit("Optimism:: L2ERC20ExtendedTokensBridge", ctxFactory) .test("finalizeDeposit() :: unauthorized messenger", async (ctx) => { const { - l2TokenBridge, - stubs: { l1TokenNonRebasable, l2TokenNonRebasable, l1TokenRebasable, l2TokenRebasable }, + contracts: { l2TokenBridge, l1TokenNonRebasable, l2TokenNonRebasable, l1TokenRebasable, l2TokenRebasable }, accounts: { deployer, recipient, stranger }, } = ctx; @@ -975,8 +973,7 @@ unit("Optimism:: L2ERC20ExtendedTokensBridge", ctxFactory) .test("finalizeDeposit() :: wrong cross domain sender", async (ctx) => { const { - l2TokenBridge, - stubs: { l1TokenNonRebasable, l2TokenNonRebasable, l1TokenRebasable, l2TokenRebasable, l2Messenger }, + contracts: { l2TokenBridge, l1TokenNonRebasable, l2TokenNonRebasable, l1TokenRebasable, l2TokenRebasable, l2Messenger }, accounts: { deployer, recipient, stranger, l2MessengerStubEOA }, } = ctx; @@ -1013,9 +1010,9 @@ unit("Optimism:: L2ERC20ExtendedTokensBridge", ctxFactory) .test("finalizeDeposit() :: non-rebasable token flow", async (ctx) => { const { - l2TokenBridge, - stubs: { l1TokenNonRebasable, l2TokenNonRebasable, l2Messenger }, + contracts: { l2TokenBridge, l1TokenNonRebasable, l2TokenNonRebasable, l2Messenger }, accounts: { deployer, recipient, l2MessengerStubEOA, l1TokenBridgeEOA }, + constants: { exchangeRate } } = ctx; await l2Messenger.setXDomainMessageSender(l1TokenBridgeEOA.address); @@ -1024,9 +1021,13 @@ unit("Optimism:: L2ERC20ExtendedTokensBridge", ctxFactory) const amount = wei`1 ether`; const data = "0xdeadbeaf"; - const provider = await hre.ethers.provider; - const packedTokenRateAndTimestampData = await packedTokenRateAndTimestamp(provider, ctx.exchangeRate); - const dataToReceive = ethers.utils.hexConcat([packedTokenRateAndTimestampData, data]); + const currentBlockTimestamp = await getBlockTimestamp(ctx.provider, 0); + const dataToReceive = await tokenRateAndTimestampPacked( + exchangeRate, + currentBlockTimestamp, + data + ); + const tx = await l2TokenBridge .connect(l2MessengerStubEOA) @@ -1054,19 +1055,22 @@ unit("Optimism:: L2ERC20ExtendedTokensBridge", ctxFactory) .test("finalizeDeposit() :: rebasable token flow", async (ctx) => { const { - l2TokenBridge, - stubs: { l1TokenRebasable, l2TokenRebasable, l2Messenger }, + contracts: { l2TokenBridge, l1TokenRebasable, l2TokenRebasable, l2Messenger }, accounts: { deployer, recipient, l2MessengerStubEOA, l1TokenBridgeEOA }, + constants: {exchangeRate, decimalsBN} } = ctx; await l2Messenger.setXDomainMessageSender(l1TokenBridgeEOA.address); const amountOfSharesToDeposit = wei`1 ether`; - const amountOfRebasableToken = wei.toBigNumber(amountOfSharesToDeposit).mul(ctx.exchangeRate).div(ctx.decimalsBN); + const amountOfRebasableToken = wei.toBigNumber(amountOfSharesToDeposit).mul(exchangeRate).div(decimalsBN); const data = "0xdeadbeaf"; - const provider = await hre.ethers.provider; - const packedTokenRateAndTimestampData = await packedTokenRateAndTimestamp(provider, ctx.exchangeRate); - const dataToReceive = ethers.utils.hexConcat([packedTokenRateAndTimestampData, data]); + const currentBlockTimestamp = await getBlockTimestamp(ctx.provider, 0); + const dataToReceive = await tokenRateAndTimestampPacked( + exchangeRate, + currentBlockTimestamp, + data + ); const tx = await l2TokenBridge .connect(l2MessengerStubEOA) @@ -1219,12 +1223,9 @@ async function ctxFactory() { await l2TokenBridge.enableWithdrawals(); return { - stubs: { - l1TokenNonRebasable: l1TokenNonRebasableStub, - l1TokenRebasable: l1TokenRebasableStub, - l2TokenNonRebasable: l2TokenNonRebasableStub, - l2TokenRebasable: l2TokenRebasableStub, - l2Messenger: l2MessengerStub, + constants: { + exchangeRate, + decimalsBN }, accounts: { deployer, @@ -1234,32 +1235,34 @@ async function ctxFactory() { emptyContractEOA, l1TokenBridgeEOA, }, - l2TokenBridge, - exchangeRate, - decimalsBN + contracts: { + l1TokenNonRebasable: l1TokenNonRebasableStub, + l1TokenRebasable: l1TokenRebasableStub, + l2TokenNonRebasable: l2TokenNonRebasableStub, + l2TokenRebasable: l2TokenRebasableStub, + l2Messenger: l2MessengerStub, + l2TokenBridge: l2TokenBridge + }, + provider }; } -async function packedTokenRateAndTimestamp(provider: JsonRpcProvider, tokenRate: BigNumber) { - const blockNumber = await provider.getBlockNumber(); - const blockTimestamp = (await provider.getBlock(blockNumber)).timestamp; - const stEthPerTokenStr = ethers.utils.hexZeroPad(tokenRate.toHexString(), 16); - const blockTimestampStr = ethers.utils.hexZeroPad(ethers.utils.hexlify(blockTimestamp), 5); - return ethers.utils.hexConcat([stEthPerTokenStr, blockTimestampStr]); -} - type ContextType = Awaited> async function pushTokenRate(ctx: ContextType) { - const provider = await hre.ethers.provider; - const packedTokenRateAndTimestampData = await packedTokenRateAndTimestamp(provider, ctx.exchangeRate); + const currentBlockTimestamp = await getBlockTimestamp(ctx.provider, 0); + const packedTokenRateAndTimestampData = await tokenRateAndTimestampPacked( + ctx.constants.exchangeRate, + currentBlockTimestamp, + "0x" + ); - await ctx.l2TokenBridge + await ctx.contracts.l2TokenBridge .connect(ctx.accounts.l2MessengerStubEOA) .finalizeDeposit( - ctx.stubs.l1TokenRebasable.address, - ctx.stubs.l2TokenRebasable.address, + ctx.contracts.l1TokenRebasable.address, + ctx.contracts.l2TokenRebasable.address, ctx.accounts.deployer.address, ctx.accounts.deployer.address, 0, diff --git a/test/optimism/OpStackTokenRatePusher.unit.test.ts b/test/optimism/OpStackTokenRatePusher.unit.test.ts index 28116e91..5b6ff04d 100644 --- a/test/optimism/OpStackTokenRatePusher.unit.test.ts +++ b/test/optimism/OpStackTokenRatePusher.unit.test.ts @@ -57,14 +57,24 @@ unit("OpStackTokenRatePusher", ctxFactory) .run(); async function ctxFactory() { + /// --------------------------- + /// constants + /// --------------------------- const [deployer, bridge, stranger, tokenRateOracle, l1TokenBridgeEOA] = await ethers.getSigners(); - const tokenRate = BigNumber.from('1164454276599657236000000000'); const genesisTime = BigNumber.from(1); const secondsPerSlot = BigNumber.from(2); const lastProcessingRefSlot = BigNumber.from(3); const updateRateTime = genesisTime.add(secondsPerSlot.mul(lastProcessingRefSlot)); + const l2GasLimitForPushingTokenRate = 123; + + const l1MessengerStub = await new CrossDomainMessengerStub__factory(deployer) + .deploy({ value: wei.toBigNumber(wei`1 ether`) }); + await l1MessengerStub.setXDomainMessageSender(l1TokenBridgeEOA.address); + /// --------------------------- + /// contracts + /// --------------------------- const l1TokenRebasableStub = await new ERC20BridgedStub__factory(deployer).deploy( "L1 Token Rebasable", "L1R" @@ -83,12 +93,6 @@ async function ctxFactory() { lastProcessingRefSlot ); - const l1MessengerStub = await new CrossDomainMessengerStub__factory(deployer) - .deploy({ value: wei.toBigNumber(wei`1 ether`) }); - await l1MessengerStub.setXDomainMessageSender(l1TokenBridgeEOA.address); - - const l2GasLimitForPushingTokenRate = 123; - const opStackTokenRatePusher = await new OpStackTokenRatePusher__factory(deployer).deploy( l1MessengerStub.address, l1TokenNonRebasableStub.address, diff --git a/test/optimism/TokenRateNotifier.unit.test.ts b/test/optimism/TokenRateNotifier.unit.test.ts index d2d305cb..07bf0e59 100644 --- a/test/optimism/TokenRateNotifier.unit.test.ts +++ b/test/optimism/TokenRateNotifier.unit.test.ts @@ -85,17 +85,16 @@ unit("TokenRateNotifier", ctxFactory) const { opStackTokenRatePusher - } = await getOpStackTokenRatePusher - ( - tokenRate, - genesisTime, - secondsPerSlot, - lastProcessingRefSlot, - deployer, - owner, - tokenRateOracle, - l2GasLimitForPushingTokenRate - ); + } = await createContracts( + tokenRate, + genesisTime, + secondsPerSlot, + lastProcessingRefSlot, + deployer, + owner, + tokenRateOracle, + l2GasLimitForPushingTokenRate + ); await tokenRateNotifier .connect(ctx.accounts.owner) @@ -244,7 +243,37 @@ unit("TokenRateNotifier", ctxFactory) .run(); -async function getOpStackTokenRatePusher( +async function ctxFactory() { + const [deployer, owner, stranger, tokenRateOracle] = await ethers.getSigners(); + const tokenRate = BigNumber.from('1164454276599657236000000000'); + const l2GasLimitForPushingTokenRate = 300_000; + const genesisTime = BigNumber.from(1); + const secondsPerSlot = BigNumber.from(2); + const lastProcessingRefSlot = BigNumber.from(3); + + const { + tokenRateNotifier, + opStackTokenRatePusher, + l1MessengerStub + } = await createContracts( + tokenRate, + genesisTime, + secondsPerSlot, + lastProcessingRefSlot, + deployer, + owner, + tokenRateOracle, + l2GasLimitForPushingTokenRate + ); + + return { + accounts: { deployer, owner, stranger, tokenRateOracle }, + contracts: { tokenRateNotifier, opStackTokenRatePusher, l1MessengerStub }, + constants: { l2GasLimitForPushingTokenRate, tokenRate, genesisTime, secondsPerSlot, lastProcessingRefSlot } + }; +} + +async function createContracts( tokenRate: BigNumber, genesisTime: BigNumber, secondsPerSlot: BigNumber, @@ -295,33 +324,3 @@ async function getOpStackTokenRatePusher( tokenRate }; } - -async function ctxFactory() { - const [deployer, owner, stranger, tokenRateOracle] = await ethers.getSigners(); - const tokenRate = BigNumber.from('1164454276599657236000000000'); - const l2GasLimitForPushingTokenRate = 300_000; - const genesisTime = BigNumber.from(1); - const secondsPerSlot = BigNumber.from(2); - const lastProcessingRefSlot = BigNumber.from(3); - - const { - tokenRateNotifier, - opStackTokenRatePusher, - l1MessengerStub - } = await getOpStackTokenRatePusher( - tokenRate, - genesisTime, - secondsPerSlot, - lastProcessingRefSlot, - deployer, - owner, - tokenRateOracle, - l2GasLimitForPushingTokenRate - ); - - return { - accounts: { deployer, owner, stranger, tokenRateOracle }, - contracts: { tokenRateNotifier, opStackTokenRatePusher, l1MessengerStub }, - constants: { l2GasLimitForPushingTokenRate, tokenRate, genesisTime, secondsPerSlot, lastProcessingRefSlot } - }; -} diff --git a/test/optimism/TokenRateOracle.unit.test.ts b/test/optimism/TokenRateOracle.unit.test.ts index 8d9b5cf9..650802fc 100644 --- a/test/optimism/TokenRateOracle.unit.test.ts +++ b/test/optimism/TokenRateOracle.unit.test.ts @@ -425,14 +425,16 @@ unit("TokenRateOracle", ctxFactory) .run(); async function ctxFactory() { - const [deployer, bridge, stranger, l1TokenBridgeEOA] = await hre.ethers.getSigners(); - + /// --------------------------- + /// constants + /// --------------------------- const decimals = 27; const provider = await hre.ethers.provider; const tokenRate = BigNumber.from('1164454276599657236000000000'); // value taken from real contact on 23.04.24 const tokenRateOutdatedDelay = BigNumber.from(86400); // 1 day const maxAllowedL2ToL1ClockLag = BigNumber.from(86400 * 2); // 2 days const maxAllowedTokenRateDeviationPerDay = BigNumber.from(500); // 5% + const [deployer, bridge, stranger, l1TokenBridgeEOA] = await hre.ethers.getSigners(); const l2MessengerStub = await new CrossDomainMessengerStub__factory(deployer) .deploy({ value: wei.toBigNumber(wei`1 ether`) }); @@ -440,6 +442,9 @@ async function ctxFactory() { const rateL1Timestamp = await getBlockTimestamp(provider, 0); + /// --------------------------- + /// contracts + /// --------------------------- const { tokenRateOracle, blockTimestampOfDeployment } = await tokenRateOracleUnderProxy( deployer, l2MessengerStub.address, diff --git a/test/optimism/pushingTokenRate.integration.test.ts b/test/optimism/pushingTokenRate.integration.test.ts index 48b1ed85..8e718d72 100644 --- a/test/optimism/pushingTokenRate.integration.test.ts +++ b/test/optimism/pushingTokenRate.integration.test.ts @@ -6,7 +6,7 @@ import network from "../../utils/network"; import testing, { scenario } from "../../utils/testing"; import deploymentOracle from "../../utils/optimism/deploymentOracle"; import { getBridgeExecutorParams } from "../../utils/bridge-executor"; -import { tokenRateAndTimestamp } from "../../utils/testing/helpers"; +import { tokenRateAndTimestampPacked } from "../../utils/testing/helpers"; import { BigNumber } from "ethers"; import { getBlockTimestamp } from "../../utils/testing/helpers"; import { @@ -41,13 +41,12 @@ scenario("Optimism :: Token Rate Oracle integration test", ctxFactory) const messageNonce = await l1CrossDomainMessenger.messageNonce(); const updateRateTime = genesisTime.add(secondsPerSlot.mul(lastProcessingRefSlot)); - const [stEthPerTokenStr, blockTimestampStr] = await tokenRateAndTimestamp(tokenRate, updateRateTime); const l2Calldata = tokenRateOracle.interface.encodeFunctionData( "updateRate", [ - stEthPerTokenStr, - blockTimestampStr + tokenRate, + updateRateTime ] ); @@ -74,9 +73,8 @@ scenario("Optimism :: Token Rate Oracle integration test", ctxFactory) .setXDomainMessageSender(opTokenRatePusher); const updateRateTime = genesisTime.add(secondsPerSlot.mul(lastProcessingRefSlot)); - const [stEthPerTokenStr, blockTimestampStr] = await tokenRateAndTimestamp(tokenRate, updateRateTime); - const tx = await ctx.l2CrossDomainMessenger + await ctx.l2CrossDomainMessenger .connect(ctx.accounts.l1CrossDomainMessengerAliased) .relayMessage( 1, @@ -85,8 +83,8 @@ scenario("Optimism :: Token Rate Oracle integration test", ctxFactory) 0, 300_000, tokenRateOracle.interface.encodeFunctionData("updateRate", [ - stEthPerTokenStr, - blockTimestampStr + tokenRate, + updateRateTime ]), { gasLimit: 5_000_000 } ); diff --git a/test/token/ERC20BridgedPermit.unit.test.ts b/test/token/ERC20BridgedPermit.unit.test.ts index 04322a96..b619564d 100644 --- a/test/token/ERC20BridgedPermit.unit.test.ts +++ b/test/token/ERC20BridgedPermit.unit.test.ts @@ -613,21 +613,21 @@ unit("ERC20BridgedPermit", ctxFactory) .run(); async function ctxFactory() { + /// --------------------------- + /// constants + /// --------------------------- const name = "ERC20 Test Token"; const symbol = "ERC20"; const version = "1"; - const decimals = BigNumber.from('18'); + const decimals = BigNumber.from(18); const premint = wei`100 ether`; const [deployer, owner, recipient, spender, holder, stranger] = await hre.ethers.getSigners(); - - await hre.network.provider.request({ - method: "hardhat_impersonateAccount", - params: [hre.ethers.constants.AddressZero], - }); - const zero = await hre.ethers.getSigner(hre.ethers.constants.AddressZero); + /// --------------------------- + /// contracts + /// --------------------------- const erc20BridgedProxied = await erc20BridgedPermitUnderProxy( deployer, holder, @@ -638,8 +638,16 @@ async function ctxFactory() { owner.address ) + /// --------------------------- + /// setup + /// --------------------------- await erc20BridgedProxied.connect(owner).bridgeMint(holder.address, premint); + await hre.network.provider.request({ + method: "hardhat_impersonateAccount", + params: [hre.ethers.constants.AddressZero], + }); + return { accounts: { deployer, owner, recipient, spender, holder, zero, stranger }, constants: { name, symbol, version, decimals, premint }, diff --git a/test/token/ERC20RebasableBridgedPermit.unit.test.ts b/test/token/ERC20RebasableBridgedPermit.unit.test.ts index f5b54e78..70418123 100644 --- a/test/token/ERC20RebasableBridgedPermit.unit.test.ts +++ b/test/token/ERC20RebasableBridgedPermit.unit.test.ts @@ -3,6 +3,7 @@ import { assert } from "chai"; import { BigNumber } from "ethers"; import { unit } from "../../utils/testing"; import { wei } from "../../utils/wei"; +import { getBlockTimestamp } from "../../utils/testing/helpers"; import { erc20RebasableBridgedPermitUnderProxy, tokenRateOracleUnderProxy @@ -1245,22 +1246,22 @@ unit("ERC20RebasableBridgedPermit", ctxFactory) .run(); async function ctxFactory() { + /// --------------------------- + /// constants + /// --------------------------- const name = "StETH Test Token"; const symbol = "StETH"; const version = "1"; - const decimals = BigNumber.from('27'); - const tenPowDecimals = BigNumber.from('10').pow(decimals); - const tokenRate = BigNumber.from('1164454276599657236000000000'); // value taken from real contact on 23.04.24 - const tokenRateOutdatedDelay = BigNumber.from(86400); // 1 day - const maxAllowedL2ToL1ClockLag = BigNumber.from(86400); // 1 day - const maxAllowedTokenRateDeviationPerDay = BigNumber.from(500); // 5% - + const decimals = BigNumber.from(27); + const tenPowDecimals = BigNumber.from(10).pow(decimals); + const tokenRate = BigNumber.from('1164454276599657236000000000'); // value taken from real contact on 23.04.24 + const tokenRateOutdatedDelay = BigNumber.from(86400); // 1 day + const maxAllowedL2ToL1ClockLag = BigNumber.from(86400); // 1 day + const maxAllowedTokenRateDeviationPerDay = BigNumber.from(500); // 5% const premintShares = wei.toBigNumber(wei`100 ether`); - const premintTokens = BigNumber.from(tokenRate).mul(premintShares).div(tenPowDecimals); - + const premintTokens = tokenRate.mul(premintShares).div(tenPowDecimals); const provider = await hre.ethers.provider; - const blockNumber = await provider.getBlockNumber(); - const blockTimestamp = BigNumber.from((await provider.getBlock(blockNumber)).timestamp); + const blockTimestamp = await getBlockTimestamp(provider, 0); const [ deployer, @@ -1275,6 +1276,9 @@ async function ctxFactory() { const zero = await hre.ethers.getSigner(hre.ethers.constants.AddressZero); + /// --------------------------- + /// contracts + /// --------------------------- const wrappedToken = await new ERC20BridgedPermit__factory(deployer).deploy( "WsETH Test Token", "WsETH", @@ -1307,6 +1311,9 @@ async function ctxFactory() { owner.address, ); + /// --------------------------- + /// setup + /// --------------------------- await wrappedToken.connect(owner).bridgeMint(holder.address, premintTokens); await wrappedToken.connect(holder).approve(rebasableProxied.address, premintShares); await rebasableProxied.connect(holder).wrap(premintShares); @@ -1317,8 +1324,32 @@ async function ctxFactory() { }); return { - accounts: { deployer, owner, recipient, spender, holder, stranger, zero, user1, user2 }, - constants: { name, symbol, version, decimals, tenPowDecimals, premintShares, premintTokens, tokenRate, blockTimestamp }, - contracts: { rebasableProxied, wrappedToken, tokenRateOracle } + accounts: { + deployer, + owner, + recipient, + spender, + holder, + stranger, + zero, + user1, + user2 + }, + constants: { + name, + symbol, + version, + decimals, + tenPowDecimals, + premintShares, + premintTokens, + tokenRate, + blockTimestamp + }, + contracts: { + rebasableProxied, + wrappedToken, + tokenRateOracle + } }; } diff --git a/utils/testing/helpers.ts b/utils/testing/helpers.ts index f8fabeba..b39e9494 100644 --- a/utils/testing/helpers.ts +++ b/utils/testing/helpers.ts @@ -1,7 +1,7 @@ import { utils, BigNumber, ethers } from "ethers"; import { ContractTransaction } from "@ethersproject/contracts"; import { JsonRpcProvider } from "@ethersproject/providers"; -import { ERC20WrapperStub, AccountingOracleStub } from "../../typechain"; +import { AccountingOracleStub } from "../../typechain"; import { SignerWithAddress } from "@nomiclabs/hardhat-ethers/signers"; import { getContractAddress } from "ethers/lib/utils"; @@ -15,62 +15,14 @@ export async function getBlockTimestamp(provider: JsonRpcProvider, secondsToShif return BigNumber.from((await provider.getBlock(blockNumber)).timestamp + secondsToShift); } -export function getInterfaceID(contractInterface: utils.Interface) { - let interfaceID = ethers.constants.Zero; - const functions: string[] = Object.keys(contractInterface.functions); - for (let i = 0; i < functions.length; i++) { - interfaceID = interfaceID.xor(contractInterface.getSighash(functions[i])); - } - return interfaceID; -} - -export async function packedTokenRateAndTimestamp(provider: JsonRpcProvider, l1Token: ERC20WrapperStub) { - const stEthPerToken = await l1Token.getStETHByWstETH(BigNumber.from(10).pow(27)); - const stEthPerTokenStr = ethers.utils.hexZeroPad(stEthPerToken.toHexString(), 16); - - const blockNumber = await provider.getBlockNumber(); - const blockTimestamp = (await provider.getBlock(blockNumber)).timestamp; - const blockTimestampStr = ethers.utils.hexZeroPad(ethers.utils.hexlify(blockTimestamp), 5); - - return ethers.utils.hexConcat([stEthPerTokenStr, blockTimestampStr]); -} - -async function packedTokenRateAndTimestamp2(provider: JsonRpcProvider, tokenRate: BigNumber) { - const stEthPerTokenStr = ethers.utils.hexZeroPad(tokenRate.toHexString(), 16); - - const blockNumber = await provider.getBlockNumber(); - const blockTimestamp = (await provider.getBlock(blockNumber)).timestamp; - const blockTimestampStr = ethers.utils.hexZeroPad(ethers.utils.hexlify(blockTimestamp), 5); - - return ethers.utils.hexConcat([stEthPerTokenStr, blockTimestampStr]); -} - -export async function packedTokenRateAndTimestampForL1Bridge( - l1Token: ERC20WrapperStub, - accountingOracle: AccountingOracleStub -) { - const stEthPerToken = await l1Token.getStETHByWstETH(BigNumber.from(10).pow(27)); - const stEthPerTokenStr = ethers.utils.hexZeroPad(stEthPerToken.toHexString(), 16); - - const genesisTime = await accountingOracle.GENESIS_TIME(); - const secondsPerSlot = await accountingOracle.SECONDS_PER_SLOT(); - const lastProcessingRefSlot = await accountingOracle.lastProcessingRefSlot(); - const refSlotTimestamp = genesisTime.add(secondsPerSlot.mul(lastProcessingRefSlot)); - const blockTimestampStr = ethers.utils.hexZeroPad(ethers.utils.hexlify(refSlotTimestamp), 5); - - return ethers.utils.hexConcat([stEthPerTokenStr, blockTimestampStr]); -} - -export async function tokenRateAndTimestamp(tokenRate: BigNumber, blockTimestamp: BigNumber) { - const stEthPerTokenStr = ethers.utils.hexZeroPad(tokenRate.toHexString(), 16); - const blockTimestampStr = ethers.utils.hexZeroPad(ethers.utils.hexlify(blockTimestamp), 5); - return [stEthPerTokenStr, blockTimestampStr]; -} - export async function tokenRateAndTimestampPacked(tokenRate: BigNumber, blockTimestamp: BigNumber, data: string) { - const stEthPerTokenStr = ethers.utils.hexZeroPad(tokenRate.toHexString(), 16); - const blockTimestampStr = ethers.utils.hexZeroPad(ethers.utils.hexlify(blockTimestamp), 5); - return ethers.utils.hexConcat([stEthPerTokenStr, blockTimestampStr, data]); + return ethers.utils.hexConcat( + [ + ethers.utils.hexZeroPad(tokenRate.toHexString(), 16), + ethers.utils.hexZeroPad(blockTimestamp.toHexString(), 5), + data + ] + ); } export async function refSlotTimestamp(accountingOracle: AccountingOracleStub) { @@ -80,6 +32,15 @@ export async function refSlotTimestamp(accountingOracle: AccountingOracleStub) { return genesisTime.add(secondsPerSlot.mul(lastProcessingRefSlot)); } +export function getInterfaceID(contractInterface: utils.Interface) { + let interfaceID = ethers.constants.Zero; + const functions: string[] = Object.keys(contractInterface.functions); + for (let i = 0; i < functions.length; i++) { + interfaceID = interfaceID.xor(contractInterface.getSighash(functions[i])); + } + return interfaceID; +} + export async function predictAddresses(account: SignerWithAddress, txsCount: number) { const currentNonce = await account.getTransactionCount(); From a9e4df1818defbb361cc03aa8d3f783e3db4aec8 Mon Sep 17 00:00:00 2001 From: Anton Kovalchuk Date: Wed, 22 May 2024 14:01:05 +0200 Subject: [PATCH 127/148] add tests for large amount of tokens --- contracts/stubs/ERC20BridgedStub.sol | 2 +- contracts/stubs/ERC20WrapperStub.sol | 45 ++++- test/optimism/L1LidoTokensBridge.unit.test.ts | 51 +++-- .../L2ERC20ExtendedTokensBridge.unit.test.ts | 117 ++++++++--- .../OpStackTokenRatePusher.unit.test.ts | 11 +- test/optimism/TokenRateNotifier.unit.test.ts | 43 +++- ...bridging-non-rebasable.integration.test.ts | 9 +- .../bridging-rebasable.integration.test.ts | 190 ++++++++++++++---- .../pushingTokenRate.integration.test.ts | 10 +- utils/optimism/testing.ts | 24 ++- utils/testing/helpers.ts | 35 ++++ 11 files changed, 417 insertions(+), 120 deletions(-) diff --git a/contracts/stubs/ERC20BridgedStub.sol b/contracts/stubs/ERC20BridgedStub.sol index fd0b33f8..af5995fd 100644 --- a/contracts/stubs/ERC20BridgedStub.sol +++ b/contracts/stubs/ERC20BridgedStub.sol @@ -13,7 +13,7 @@ contract ERC20BridgedStub is IERC20Bridged, ERC20 { constructor(string memory name_, string memory symbol_) ERC20(name_, symbol_) { - _mint(msg.sender, 1000000 * 10**18); + _mint(msg.sender, 1000000 * 10**40); } function setBridge(address bridge_) external { diff --git a/contracts/stubs/ERC20WrapperStub.sol b/contracts/stubs/ERC20WrapperStub.sol index 64c97974..df4dd1fb 100644 --- a/contracts/stubs/ERC20WrapperStub.sol +++ b/contracts/stubs/ERC20WrapperStub.sol @@ -14,21 +14,27 @@ contract ERC20WrapperStub is IERC20Wrapper, IERC20WstETH, ERC20 { IERC20 public stETH; address public bridge; - uint256 public tokensRate; - uint256 private constant DECIMALS = 27; + uint256 private immutable TOTAL_POOLED_ETHER; + uint256 private immutable TOTAL_SHARES; + uint256 private decimalsShift = 0; - constructor(IERC20 stETH_, string memory name_, string memory symbol_, uint256 tokensRate_) - ERC20(name_, symbol_) - { + constructor( + IERC20 stETH_, + string memory name_, + string memory symbol_, + uint256 totalPooledEther_, + uint256 totalShares_ + ) ERC20(name_, symbol_) { stETH = stETH_; - tokensRate = tokensRate_; - _mint(msg.sender, 1000000 * 10**DECIMALS); + TOTAL_POOLED_ETHER = totalPooledEther_; + TOTAL_SHARES = totalShares_; + _mint(msg.sender, 1000000 * 10**40); } function wrap(uint256 _stETHAmount) external returns (uint256) { require(_stETHAmount > 0, "wstETH: can't wrap zero stETH"); - uint256 wstETHAmount = (_stETHAmount * (10 ** DECIMALS)) / tokensRate; + uint256 wstETHAmount = _getSharesByPooledEth(_stETHAmount); _mint(msg.sender, wstETHAmount); stETH.transferFrom(msg.sender, address(this), _stETHAmount); @@ -39,7 +45,7 @@ contract ERC20WrapperStub is IERC20Wrapper, IERC20WstETH, ERC20 { function unwrap(uint256 _wstETHAmount) external returns (uint256) { require(_wstETHAmount > 0, "wstETH: zero amount unwrap not allowed"); - uint256 stETHAmount = (_wstETHAmount * tokensRate) / (10 ** DECIMALS); + uint256 stETHAmount = _getPooledEthByShares(_wstETHAmount); _burn(msg.sender, _wstETHAmount); stETH.transfer(msg.sender, stETHAmount); @@ -47,7 +53,24 @@ contract ERC20WrapperStub is IERC20Wrapper, IERC20WstETH, ERC20 { return stETHAmount; } - function getStETHByWstETH(uint256 _wstETHAmount) external view returns (uint256) { - return (tokensRate * 10**DECIMALS) / _wstETHAmount; + function getStETHByWstETH(uint256 wstETHAmount_) external view returns (uint256) { + uint256 wstETHAmount = wstETHAmount_ - decimalsShift; + return _getPooledEthByShares(wstETHAmount); + } + + function _getPooledEthByShares(uint256 sharesAmount_) internal view returns (uint256) { + return sharesAmount_ * TOTAL_POOLED_ETHER / TOTAL_SHARES; + } + + function getWstETHByStETH(uint256 stETHAmount_) external view returns (uint256) { + return _getSharesByPooledEth(stETHAmount_); + } + + function _getSharesByPooledEth(uint256 ethAmount_) internal view returns (uint256) { + return ethAmount_ * TOTAL_SHARES / TOTAL_POOLED_ETHER; + } + + function setDecimalsShift(uint256 decimalsShift_) external { + decimalsShift = decimalsShift_; } } diff --git a/test/optimism/L1LidoTokensBridge.unit.test.ts b/test/optimism/L1LidoTokensBridge.unit.test.ts index dda49b88..1453a10b 100644 --- a/test/optimism/L1LidoTokensBridge.unit.test.ts +++ b/test/optimism/L1LidoTokensBridge.unit.test.ts @@ -16,7 +16,7 @@ import { CrossDomainMessengerStub__factory } from "../../typechain/factories/Cro import testing, { unit } from "../../utils/testing"; import { wei } from "../../utils/wei"; import { SignerWithAddress } from "@nomiclabs/hardhat-ethers/signers"; -import { tokenRateAndTimestampPacked, refSlotTimestamp } from "../../utils/testing/helpers"; +import { tokenRateAndTimestampPacked, refSlotTimestamp, getExchangeRate } from "../../utils/testing/helpers"; unit("Optimism :: L1LidoTokensBridge", ctxFactory) @@ -32,14 +32,16 @@ unit("Optimism :: L1LidoTokensBridge", ctxFactory) .test("initialize() :: petrified", async (ctx) => { const { deployer, l2TokenBridgeEOA } = ctx.accounts; const { - tokenRate, + totalPooledEther, + totalShares, genesisTime, secondsPerSlot, lastProcessingRefSlot } = ctx.constants; const { l1TokenBridgeImpl } = await getL1LidoTokensBridgeImpl( - tokenRate, + totalPooledEther, + totalShares, genesisTime, secondsPerSlot, lastProcessingRefSlot, @@ -59,14 +61,17 @@ unit("Optimism :: L1LidoTokensBridge", ctxFactory) .test("initialize() :: zero address L2 bridge", async (ctx) => { const { deployer } = ctx.accounts; const { - tokenRate, + totalPooledEther, + totalShares, genesisTime, secondsPerSlot, lastProcessingRefSlot } = ctx.constants; await assert.revertsWith( - getL1LidoTokensBridgeImpl(tokenRate, + getL1LidoTokensBridgeImpl( + totalPooledEther, + totalShares, genesisTime, secondsPerSlot, lastProcessingRefSlot, @@ -80,14 +85,16 @@ unit("Optimism :: L1LidoTokensBridge", ctxFactory) .test("initialize() :: don't allow to initialize twice", async (ctx) => { const { deployer, l2TokenBridgeEOA } = ctx.accounts; const { - tokenRate, + totalPooledEther, + totalShares, genesisTime, secondsPerSlot, lastProcessingRefSlot } = ctx.constants; const { l1TokenBridgeImpl } = await getL1LidoTokensBridgeImpl( - tokenRate, + totalPooledEther, + totalShares, genesisTime, secondsPerSlot, lastProcessingRefSlot, @@ -121,14 +128,16 @@ unit("Optimism :: L1LidoTokensBridge", ctxFactory) .test("finalizeUpgrade_v2() :: bridging manager uninitialized", async (ctx) => { const { deployer, l2TokenBridgeEOA } = ctx.accounts; const { - tokenRate, + totalPooledEther, + totalShares, genesisTime, secondsPerSlot, lastProcessingRefSlot } = ctx.constants; const { l1TokenBridgeImpl } = await getL1LidoTokensBridgeImpl( - tokenRate, + totalPooledEther, + totalShares, genesisTime, secondsPerSlot, lastProcessingRefSlot, @@ -146,7 +155,8 @@ unit("Optimism :: L1LidoTokensBridge", ctxFactory) .test("finalizeUpgrade_v2() :: bridging manager initialized", async (ctx) => { const { deployer, l2TokenBridgeEOA } = ctx.accounts; const { - tokenRate, + totalPooledEther, + totalShares, genesisTime, secondsPerSlot, lastProcessingRefSlot @@ -162,7 +172,8 @@ unit("Optimism :: L1LidoTokensBridge", ctxFactory) ); const { l1TokenBridgeImpl } = await getL1LidoTokensBridgeImpl( - tokenRate, + totalPooledEther, + totalShares, genesisTime, secondsPerSlot, lastProcessingRefSlot, @@ -1124,8 +1135,10 @@ async function ctxFactory() { const [deployer, l2TokenBridgeEOA, stranger, recipient] = await hre.ethers.getSigners(); const provider = await hre.ethers.provider; - const tokenRate = BigNumber.from('1164454276599657236000000000'); - const decimals = 27; + const decimals = BigNumber.from(27); + const totalPooledEther = BigNumber.from('9309904612343950493629678'); + const totalShares = BigNumber.from('7975822843597609202337218'); + const tokenRate = getExchangeRate(decimals, totalPooledEther, totalShares); const tenPowerDecimals = BigNumber.from(10).pow(decimals); const genesisTime = BigNumber.from(1); const secondsPerSlot = BigNumber.from(2); @@ -1140,7 +1153,8 @@ async function ctxFactory() { l2TokenRebasableStub, accountingOracle } = await getL1LidoTokensBridgeImpl( - tokenRate, + totalPooledEther, + totalShares, genesisTime, secondsPerSlot, lastProcessingRefSlot, @@ -1181,7 +1195,9 @@ async function ctxFactory() { constants: { decimals, tenPowerDecimals, + totalPooledEther, tokenRate, + totalShares, genesisTime, secondsPerSlot, lastProcessingRefSlot @@ -1191,7 +1207,8 @@ async function ctxFactory() { } async function getL1LidoTokensBridgeImpl( - tokenRate: BigNumber, + totalPooledEther: BigNumber, + totalShares: BigNumber, genesisTime: BigNumber, secondsPerSlot: BigNumber, lastProcessingRefSlot: BigNumber, @@ -1210,7 +1227,7 @@ async function getL1LidoTokensBridgeImpl( l1TokenRebasableStub.address, "L1 Token Non Rebasable", "L1NR", - tokenRate + totalPooledEther, totalShares ); const l2TokenNonRebasableStub = await new ERC20BridgedStub__factory(deployer).deploy( @@ -1222,7 +1239,7 @@ async function getL1LidoTokensBridgeImpl( l2TokenNonRebasableStub.address, "L2 Token Rebasable", "L2R", - tokenRate + totalPooledEther, totalShares ); const accountingOracle = await new AccountingOracleStub__factory(deployer).deploy( diff --git a/test/optimism/L2ERC20ExtendedTokensBridge.unit.test.ts b/test/optimism/L2ERC20ExtendedTokensBridge.unit.test.ts index 04edcee3..7d84b88f 100644 --- a/test/optimism/L2ERC20ExtendedTokensBridge.unit.test.ts +++ b/test/optimism/L2ERC20ExtendedTokensBridge.unit.test.ts @@ -2,7 +2,16 @@ import hre from "hardhat"; import { BigNumber } from "ethers"; import { assert } from "chai"; import { SignerWithAddress } from "@nomiclabs/hardhat-ethers/signers"; -import { tokenRateAndTimestampPacked, getBlockTimestamp, predictAddresses } from "../../utils/testing/helpers"; +import { + tokenRateAndTimestampPacked, + getBlockTimestamp, + predictAddresses, + getExchangeRate, + nonRebasableFromRebasableL1, + nonRebasableFromRebasableL2, + rebasableFromNonRebasableL1, + rebasableFromNonRebasableL2 +} from "../../utils/testing/helpers"; import testing, { unit } from "../../utils/testing"; import { wei } from "../../utils/wei"; import { @@ -271,11 +280,23 @@ unit("Optimism:: L2ERC20ExtendedTokensBridge", ctxFactory) l1TokenRebasable, l2TokenRebasable }, - constants: { exchangeRate, decimalsBN } + constants: { exchangeRate, tokenRateDecimals } } = ctx; - const amountToDeposit = wei`1 ether`; - const amountToWithdraw = wei.toBigNumber(amountToDeposit).mul(exchangeRate).div(decimalsBN); + const amountToDepositNonRebasable = wei`1 ether`; + + const amountToWithdrawRebasable = rebasableFromNonRebasableL2( + wei.toBigNumber(amountToDepositNonRebasable), + tokenRateDecimals, + exchangeRate + ); + + const amountReceivedWithdrawNonRebasable = nonRebasableFromRebasableL2( + amountToWithdrawRebasable, + tokenRateDecimals, + exchangeRate + ); + const l1Gas = wei`1 wei`; const data = "0xdeadbeaf"; const currentBlockTimestamp = await getBlockTimestamp(ctx.provider, 0); @@ -292,7 +313,7 @@ unit("Optimism:: L2ERC20ExtendedTokensBridge", ctxFactory) l2TokenRebasable.address, deployer.address, recipient.address, - amountToDeposit, + amountToDepositNonRebasable, packedTokenRateAndTimestampData ); @@ -301,7 +322,7 @@ unit("Optimism:: L2ERC20ExtendedTokensBridge", ctxFactory) const tx = await l2TokenBridge.connect(recipient).withdraw( l2TokenRebasable.address, - amountToWithdraw, + amountToWithdrawRebasable, l1Gas, data ); @@ -311,7 +332,7 @@ unit("Optimism:: L2ERC20ExtendedTokensBridge", ctxFactory) l2TokenRebasable.address, recipient.address, recipient.address, - amountToWithdraw, + amountToWithdrawRebasable, data, ]); @@ -325,7 +346,7 @@ unit("Optimism:: L2ERC20ExtendedTokensBridge", ctxFactory) l2TokenRebasable.address, recipient.address, recipient.address, - amountToDeposit, + amountReceivedWithdrawNonRebasable, data, ] ), @@ -333,15 +354,24 @@ unit("Optimism:: L2ERC20ExtendedTokensBridge", ctxFactory) l1Gas, ]); + console.log("amountToWithdraw=",amountToWithdrawRebasable); + + console.log("recipientBalanceBefore=",recipientBalanceBefore); + console.log("after=",await l2TokenRebasable.balanceOf(deployer.address)); + assert.equalBN( await l2TokenRebasable.balanceOf(deployer.address), - recipientBalanceBefore.sub(amountToWithdraw) + recipientBalanceBefore.sub(amountToWithdrawRebasable) ); - assert.equalBN( + console.log("await l2TokenRebasable.totalSupply()=",await l2TokenRebasable.totalSupply()); + console.log("totalSupplyBefore=",totalSupplyBefore); + // console.log("amountToWithdraw=",amountToWithdraw); + + assert.isTrue(almostEqual( await l2TokenRebasable.totalSupply(), - totalSupplyBefore.sub(amountToWithdraw) - ); + totalSupplyBefore.sub(amountToWithdrawRebasable) + )); }) .test("withdraw() :: zero rebasable tokens", async (ctx) => { @@ -581,11 +611,21 @@ unit("Optimism:: L2ERC20ExtendedTokensBridge", ctxFactory) l1TokenRebasable, l2TokenRebasable }, - constants: { exchangeRate, decimalsBN } + constants: { exchangeRate, tokenRateDecimals } } = ctx; - const amountToDeposit = wei`1 ether`; // shares - const amountToWithdraw = wei.toBigNumber(amountToDeposit).mul(exchangeRate).div(decimalsBN); + const amountToDepositNonRebasable = wei`1 ether`; // shares + const amountToWithdraw = rebasableFromNonRebasableL2( + wei.toBigNumber(amountToDepositNonRebasable), + tokenRateDecimals, + exchangeRate + ); + const amountReceivedWithdrawNonRebasable = nonRebasableFromRebasableL2( + amountToWithdraw, + tokenRateDecimals, + exchangeRate + ); + const l1Gas = wei`1 wei`; const data = "0xdeadbeaf"; const currentBlockTimestamp = await getBlockTimestamp(ctx.provider, 0); @@ -602,7 +642,7 @@ unit("Optimism:: L2ERC20ExtendedTokensBridge", ctxFactory) l2TokenRebasable.address, deployer.address, deployer.address, - amountToDeposit, + amountToDepositNonRebasable, packedTokenRateAndTimestampData ); @@ -636,7 +676,7 @@ unit("Optimism:: L2ERC20ExtendedTokensBridge", ctxFactory) l2TokenRebasable.address, deployer.address, recipient.address, - amountToDeposit, + amountReceivedWithdrawNonRebasable, data, ] ), @@ -649,9 +689,9 @@ unit("Optimism:: L2ERC20ExtendedTokensBridge", ctxFactory) deployerBalanceBefore.sub(amountToWithdraw) ); - assert.equalBN( + assert.isTrue(almostEqual( await l2TokenRebasable.totalSupply(), - totalSupplyBefore.sub(amountToWithdraw) + totalSupplyBefore.sub(amountToWithdraw)) ); }) @@ -1057,13 +1097,18 @@ unit("Optimism:: L2ERC20ExtendedTokensBridge", ctxFactory) const { contracts: { l2TokenBridge, l1TokenRebasable, l2TokenRebasable, l2Messenger }, accounts: { deployer, recipient, l2MessengerStubEOA, l1TokenBridgeEOA }, - constants: {exchangeRate, decimalsBN} + constants: { exchangeRate, tokenRateDecimals } } = ctx; await l2Messenger.setXDomainMessageSender(l1TokenBridgeEOA.address); const amountOfSharesToDeposit = wei`1 ether`; - const amountOfRebasableToken = wei.toBigNumber(amountOfSharesToDeposit).mul(exchangeRate).div(decimalsBN); + const amountOfRebasableToken = rebasableFromNonRebasableL2( + wei.toBigNumber(amountOfSharesToDeposit), + tokenRateDecimals, + exchangeRate + ); + const data = "0xdeadbeaf"; const currentBlockTimestamp = await getBlockTimestamp(ctx.provider, 0); const dataToReceive = await tokenRateAndTimestampPacked( @@ -1098,12 +1143,13 @@ unit("Optimism:: L2ERC20ExtendedTokensBridge", ctxFactory) .run(); async function ctxFactory() { - const [deployer, stranger, recipient, l1TokenBridgeEOA] = - await hre.ethers.getSigners(); + const [deployer, stranger, recipient, l1TokenBridgeEOA] = await hre.ethers.getSigners(); - const decimals = 27; - const decimalsBN = BigNumber.from(10).pow(decimals); - const exchangeRate = BigNumber.from('1164454276599657236000000000') + const tokenDecimals = 18; + const tokenRateDecimals = BigNumber.from(27); + const totalPooledEther = BigNumber.from('9309904612343950493629678'); + const totalShares = BigNumber.from('7975822843597609202337218'); + const exchangeRate = getExchangeRate(tokenRateDecimals, totalPooledEther, totalShares); const l2MessengerStub = await new CrossDomainMessengerStub__factory( deployer @@ -1136,7 +1182,8 @@ async function ctxFactory() { l1TokenRebasableStub.address, "L1 Token Non Rebasable", "L1NR", - exchangeRate + totalPooledEther, + totalShares ); const l2TokenNonRebasableStub = await new ERC20BridgedStub__factory(deployer).deploy( @@ -1176,7 +1223,7 @@ async function ctxFactory() { "L2 Token Rebasable", "L2R", "1", - decimals, + tokenDecimals, l2TokenNonRebasableStub.address, tokenRateOracle.address, l2TokenBridgeProxyAddress @@ -1225,7 +1272,7 @@ async function ctxFactory() { return { constants: { exchangeRate, - decimalsBN + tokenRateDecimals }, accounts: { deployer, @@ -1272,7 +1319,10 @@ async function pushTokenRate(ctx: ContextType) { async function getL2TokenBridgeImpl(deployer: SignerWithAddress, l1TokenBridge: string) { const decimals = 18; - const exchangeRate = BigNumber.from('1164454276599657236000000000') + const tokenRateDecimals = BigNumber.from(27); + const totalPooledEther = BigNumber.from('9309904612343950493629678'); + const totalShares = BigNumber.from('7975822843597609202337218'); + const exchangeRate = getExchangeRate(tokenRateDecimals, totalPooledEther, totalShares); const l2MessengerStub = await new CrossDomainMessengerStub__factory( deployer @@ -1299,7 +1349,7 @@ async function getL2TokenBridgeImpl(deployer: SignerWithAddress, l1TokenBridge: l1TokenRebasableStub.address, "L1 Token Non Rebasable", "L1NR", - exchangeRate + totalPooledEther, totalShares ); const l2TokenNonRebasableStub = await new ERC20BridgedStub__factory(deployer).deploy( @@ -1357,3 +1407,8 @@ async function getL2TokenBridgeImpl(deployer: SignerWithAddress, l1TokenBridge: ); return l2TokenBridgeImpl; } + +function almostEqual(num1: BigNumber, num2: BigNumber) { + const delta = (num1.sub(num2)).abs(); + return delta.lte(BigNumber.from('2')); +} diff --git a/test/optimism/OpStackTokenRatePusher.unit.test.ts b/test/optimism/OpStackTokenRatePusher.unit.test.ts index 5b6ff04d..c3c3ae86 100644 --- a/test/optimism/OpStackTokenRatePusher.unit.test.ts +++ b/test/optimism/OpStackTokenRatePusher.unit.test.ts @@ -3,7 +3,7 @@ import { assert } from "chai"; import { BigNumber } from 'ethers' import { unit } from "../../utils/testing"; import { wei } from "../../utils/wei"; -import { getInterfaceID } from "../../utils/testing/helpers"; +import { getInterfaceID, getExchangeRate } from "../../utils/testing/helpers"; import { OpStackTokenRatePusher__factory, CrossDomainMessengerStub__factory, @@ -61,7 +61,11 @@ async function ctxFactory() { /// constants /// --------------------------- const [deployer, bridge, stranger, tokenRateOracle, l1TokenBridgeEOA] = await ethers.getSigners(); - const tokenRate = BigNumber.from('1164454276599657236000000000'); + const totalPooledEther = BigNumber.from('9309904612343950493629678'); + const totalShares = BigNumber.from('7975822843597609202337218'); + const tokenRateDecimals = BigNumber.from(27); + const tokenRate = getExchangeRate(tokenRateDecimals, totalPooledEther, totalShares); + const genesisTime = BigNumber.from(1); const secondsPerSlot = BigNumber.from(2); const lastProcessingRefSlot = BigNumber.from(3); @@ -84,7 +88,8 @@ async function ctxFactory() { l1TokenRebasableStub.address, "L1 Token Non Rebasable", "L1NR", - tokenRate + totalPooledEther, + totalShares ); const accountingOracle = await new AccountingOracleStub__factory(deployer).deploy( diff --git a/test/optimism/TokenRateNotifier.unit.test.ts b/test/optimism/TokenRateNotifier.unit.test.ts index 07bf0e59..cd19b9aa 100644 --- a/test/optimism/TokenRateNotifier.unit.test.ts +++ b/test/optimism/TokenRateNotifier.unit.test.ts @@ -3,7 +3,7 @@ import { assert } from "chai"; import { BigNumber } from 'ethers' import { unit } from "../../utils/testing"; import { wei } from "../../utils/wei"; -import { getInterfaceID } from "../../utils/testing/helpers"; +import { getInterfaceID, getExchangeRate } from "../../utils/testing/helpers"; import { SignerWithAddress } from "@nomiclabs/hardhat-ethers/signers"; import { TokenRateNotifier__factory, @@ -77,7 +77,7 @@ unit("TokenRateNotifier", ctxFactory) .test("addObserver() :: revert on adding too many observers", async (ctx) => { const { tokenRateNotifier, opStackTokenRatePusher } = ctx.contracts; const { deployer, owner, tokenRateOracle } = ctx.accounts; - const { l2GasLimitForPushingTokenRate, tokenRate, genesisTime, secondsPerSlot, lastProcessingRefSlot } = ctx.constants; + const { l2GasLimitForPushingTokenRate, tokenRate, totalPooledEther, totalShares, genesisTime, secondsPerSlot, lastProcessingRefSlot } = ctx.constants; assert.equalBN(await tokenRateNotifier.observersLength(), 0); const maxObservers = await tokenRateNotifier.MAX_OBSERVERS_COUNT(); @@ -86,6 +86,8 @@ unit("TokenRateNotifier", ctxFactory) const { opStackTokenRatePusher } = await createContracts( + totalPooledEther, + totalShares, tokenRate, genesisTime, secondsPerSlot, @@ -245,7 +247,10 @@ unit("TokenRateNotifier", ctxFactory) async function ctxFactory() { const [deployer, owner, stranger, tokenRateOracle] = await ethers.getSigners(); - const tokenRate = BigNumber.from('1164454276599657236000000000'); + const totalPooledEther = BigNumber.from('9309904612343950493629678'); + const totalShares = BigNumber.from('7975822843597609202337218'); + const tokenRateDecimals = BigNumber.from(27); + const tokenRate = getExchangeRate(tokenRateDecimals, totalPooledEther, totalShares); const l2GasLimitForPushingTokenRate = 300_000; const genesisTime = BigNumber.from(1); const secondsPerSlot = BigNumber.from(2); @@ -256,6 +261,8 @@ async function ctxFactory() { opStackTokenRatePusher, l1MessengerStub } = await createContracts( + totalPooledEther, + totalShares, tokenRate, genesisTime, secondsPerSlot, @@ -267,13 +274,32 @@ async function ctxFactory() { ); return { - accounts: { deployer, owner, stranger, tokenRateOracle }, - contracts: { tokenRateNotifier, opStackTokenRatePusher, l1MessengerStub }, - constants: { l2GasLimitForPushingTokenRate, tokenRate, genesisTime, secondsPerSlot, lastProcessingRefSlot } + accounts: { + deployer, + owner, + stranger, + tokenRateOracle + }, + contracts: { + tokenRateNotifier, + opStackTokenRatePusher, + l1MessengerStub + }, + constants: { + l2GasLimitForPushingTokenRate, + tokenRate, + totalPooledEther, + totalShares, + genesisTime, + secondsPerSlot, + lastProcessingRefSlot + } }; } async function createContracts( + totalPooledEther: BigNumber, + totalShares: BigNumber, tokenRate: BigNumber, genesisTime: BigNumber, secondsPerSlot: BigNumber, @@ -298,7 +324,8 @@ async function createContracts( l1TokenRebasableStub.address, "L1 Token Non Rebasable", "L1NR", - tokenRate + totalPooledEther, + totalShares ); const accountingOracle = await new AccountingOracleStub__factory(deployer).deploy( @@ -321,6 +348,8 @@ async function createContracts( opStackTokenRatePusher, accountingOracle, l1MessengerStub, + totalPooledEther, + totalShares, tokenRate }; } diff --git a/test/optimism/bridging-non-rebasable.integration.test.ts b/test/optimism/bridging-non-rebasable.integration.test.ts index 43f2fc4d..428a25b9 100644 --- a/test/optimism/bridging-non-rebasable.integration.test.ts +++ b/test/optimism/bridging-non-rebasable.integration.test.ts @@ -5,7 +5,7 @@ import { wei } from "../../utils/wei"; import optimism from "../../utils/optimism"; import testing, { scenario } from "../../utils/testing"; import { ScenarioTest } from "../../utils/testing"; -import { tokenRateAndTimestampPacked, refSlotTimestamp } from "../../utils/testing/helpers"; +import { tokenRateAndTimestampPacked, refSlotTimestamp, getExchangeRate } from "../../utils/testing/helpers"; type ContextType = Awaited>> @@ -580,7 +580,10 @@ function bridgingTestsSuit(scenarioInstance: ScenarioTest) { function ctxFactory(depositAmount: BigNumber, withdrawalAmount: BigNumber) { return async () => { const networkName = env.network("TESTING_OPT_NETWORK", "mainnet"); - const tokenRate = BigNumber.from('1164454276599657236000000000'); + const tokenRateDecimals = BigNumber.from(27); + const totalPooledEther = BigNumber.from('9309904612343950493629678'); + const totalShares = BigNumber.from('7975822843597609202337218'); + const tokenRate = getExchangeRate(tokenRateDecimals, totalPooledEther, totalShares); const { l1Provider, @@ -588,7 +591,7 @@ function ctxFactory(depositAmount: BigNumber, withdrawalAmount: BigNumber) { l1ERC20ExtendedTokensBridgeAdmin, l2ERC20ExtendedTokensBridgeAdmin, ...contracts - } = await optimism.testing(networkName).getIntegrationTestSetup(tokenRate); + } = await optimism.testing(networkName).getIntegrationTestSetup(totalPooledEther, totalShares); const l1Snapshot = await l1Provider.send("evm_snapshot", []); const l2Snapshot = await l2Provider.send("evm_snapshot", []); diff --git a/test/optimism/bridging-rebasable.integration.test.ts b/test/optimism/bridging-rebasable.integration.test.ts index cf41ef23..06738f20 100644 --- a/test/optimism/bridging-rebasable.integration.test.ts +++ b/test/optimism/bridging-rebasable.integration.test.ts @@ -78,29 +78,35 @@ function bridgingTestsSuit(scenarioInstance: ScenarioTest) { const { l1Token, l1TokenRebasable, - l1LidoTokensBridge, l2TokenRebasable, + l1LidoTokensBridge, l1CrossDomainMessenger, l2ERC20ExtendedTokensBridge, - l1Provider, accountingOracle } = ctx; const { accountA: tokenHolderA } = ctx.accounts; const { depositAmountOfRebasableToken, tokenRate } = ctx.constants; - const depositAmountNonRebasable = nonRebasableFromRebasable(depositAmountOfRebasableToken, tokenRate); + /// warp L1 + const depositAmountNonRebasable = nonRebasableFromRebasableL1( + depositAmountOfRebasableToken, + ctx.constants.totalPooledEther, + ctx.constants.totalShares + ); + + console.log("depositAmountOfRebasableToken=",depositAmountOfRebasableToken); + console.log("depositAmountNonRebasable=",depositAmountNonRebasable); await l1TokenRebasable .connect(tokenHolderA.l1Signer) .approve(l1LidoTokensBridge.address, depositAmountOfRebasableToken); const rebasableTokenHolderBalanceBefore = await l1TokenRebasable.balanceOf(tokenHolderA.address); - - ctx.balances.accountABalanceBeforeDeposit = rebasableTokenHolderBalanceBefore; - const nonRebasableTokenBridgeBalanceBefore = await l1Token.balanceOf(l1LidoTokensBridge.address); const warappedRebasableTokenBalanceBefore = await l1TokenRebasable.balanceOf(l1Token.address); + ctx.balances.accountABalanceBeforeDeposit = rebasableTokenHolderBalanceBefore; + const tx = await l1LidoTokensBridge .connect(tokenHolderA.l1Signer) .depositERC20( @@ -168,20 +174,33 @@ function bridgingTestsSuit(scenarioInstance: ScenarioTest) { .step("Finalize deposit on L2", async (ctx) => { const { - l1Token, l1TokenRebasable, accountingOracle, l2TokenRebasable, l1LidoTokensBridge, l2CrossDomainMessenger, - l2ERC20ExtendedTokensBridge, - l2Provider + l2ERC20ExtendedTokensBridge } = ctx; const { depositAmountOfRebasableToken, tokenRate } = ctx.constants; - const depositAmountNonRebasable = nonRebasableFromRebasable(depositAmountOfRebasableToken, tokenRate); - const depositAmountRebasable = rebasableFromNonRebasable(depositAmountNonRebasable, tokenRate); + // first wrap on L1 + const depositAmountNonRebasable = nonRebasableFromRebasableL1( + depositAmountOfRebasableToken, + ctx.constants.totalPooledEther, + ctx.constants.totalShares + ); + // second wrap on L2 + const depositAmountRebasable = rebasableFromNonRebasableL2( + depositAmountNonRebasable, + ctx.constants.tokenRateDecimals, + ctx.constants.tokenRate + ); + + console.log("depositAmountOfRebasableToken=",depositAmountOfRebasableToken); + console.log("depositAmountNonRebasable=",depositAmountNonRebasable); + console.log("depositAmountRebasable=",depositAmountRebasable); + const { accountA: tokenHolderA, l1CrossDomainMessengerAliased } = ctx.accounts; @@ -191,6 +210,7 @@ function bridgingTestsSuit(scenarioInstance: ScenarioTest) { const refSlotTime = await refSlotTimestamp(accountingOracle); const dataToReceive = await tokenRateAndTimestampPacked(tokenRate, refSlotTime, "0x"); + const tx = await l2CrossDomainMessenger .connect(l1CrossDomainMessengerAliased) .relayMessage( @@ -222,6 +242,11 @@ function bridgingTestsSuit(scenarioInstance: ScenarioTest) { const tokenHolderABalanceAfter = await l2TokenRebasable.balanceOf(tokenHolderA.address); const l2TokenRebasableTotalSupplyAfter = await l2TokenRebasable.totalSupply(); + // console.log("tokenHolderABalanceBefore=",tokenHolderABalanceBefore); + // console.log("depositAmountRebasable=",depositAmountRebasable); + // console.log("tokenHolderABalanceAfter=",tokenHolderABalanceAfter); + // console.log("tokenHolderABalanceBefore.add(depositAmountRebasable)=",tokenHolderABalanceBefore.add(depositAmountRebasable)); + assert.equalBN( tokenHolderABalanceBefore.add(depositAmountRebasable), tokenHolderABalanceAfter @@ -244,6 +269,9 @@ function bridgingTestsSuit(scenarioInstance: ScenarioTest) { const tokenHolderABalanceBefore = await l2TokenRebasable.balanceOf(tokenHolderA.address); const l2TotalSupplyBefore = await l2TokenRebasable.totalSupply(); + console.log("withdrawalAmountOfRebasableToken=",withdrawalAmountOfRebasableToken); + //console.log("tokenHolderABalanceBefore=",tokenHolderABalanceBefore); + const tx = await l2ERC20ExtendedTokensBridge .connect(tokenHolderA.l2Signer) .withdraw( @@ -283,8 +311,22 @@ function bridgingTestsSuit(scenarioInstance: ScenarioTest) { const { accountA: tokenHolderA, l1Stranger } = ctx.accounts; const { depositAmountOfRebasableToken, withdrawalAmountOfRebasableToken, tokenRate } = ctx.constants; - const withdrawalAmountNonRebasable = nonRebasableFromRebasable(withdrawalAmountOfRebasableToken, tokenRate); - const withdrawalAmountRebasable = rebasableFromNonRebasable(withdrawalAmountNonRebasable, tokenRate); + // unwrap on L2 + const withdrawalAmountNonRebasable = nonRebasableFromRebasableL2( + withdrawalAmountOfRebasableToken, + ctx.constants.tokenRateDecimals, + tokenRate); + + // unwrap on L1: loosing 1-2 wei + const withdrawalAmountRebasable = rebasableFromNonRebasableL1( + withdrawalAmountNonRebasable, + ctx.constants.totalPooledEther, + ctx.constants.totalShares + ); + + console.log("withdrawalAmountOfRebasableToken=",withdrawalAmountOfRebasableToken); + console.log("withdrawalAmountNonRebasable=",withdrawalAmountNonRebasable); + console.log("withdrawalAmountRebasable=",withdrawalAmountRebasable); const tokenHolderABalanceBefore = await l1TokenRebasable.balanceOf(tokenHolderA.address); const l1LidoTokensBridgeBalanceBefore = await l1Token.balanceOf(l1LidoTokensBridge.address); @@ -352,13 +394,18 @@ function bridgingTestsSuit(scenarioInstance: ScenarioTest) { l2TokenRebasable, l1CrossDomainMessenger, l2ERC20ExtendedTokensBridge, - l1Provider } = ctx; const { accountA: tokenHolderA, accountB: tokenHolderB } = ctx.accounts; assert.notEqual(tokenHolderA.address, tokenHolderB.address); const { depositAmountOfRebasableToken, tokenRate } = ctx.constants; - const depositAmountNonRebasable = nonRebasableFromRebasable(depositAmountOfRebasableToken, tokenRate); + + // wrap on L1 + const depositAmountNonRebasable = nonRebasableFromRebasableL1( + depositAmountOfRebasableToken, + ctx.constants.totalPooledEther, + ctx.constants.totalShares + ); const rebasableTokenHolderABalanceBefore = await l1TokenRebasable.balanceOf(tokenHolderA.address); const nonRebasableTokenBridgeBalanceBefore = await l1Token.balanceOf(l1LidoTokensBridge.address); @@ -440,14 +487,12 @@ function bridgingTestsSuit(scenarioInstance: ScenarioTest) { .step("Finalize deposit on L2", async (ctx) => { const { - l1Token, l1TokenRebasable, accountingOracle, l1LidoTokensBridge, l2TokenRebasable, l2CrossDomainMessenger, - l2ERC20ExtendedTokensBridge, - l2Provider + l2ERC20ExtendedTokensBridge } = ctx; const { @@ -458,8 +503,18 @@ function bridgingTestsSuit(scenarioInstance: ScenarioTest) { const { depositAmountOfRebasableToken, tokenRate } = ctx.constants; - const depositAmountNonRebasable = nonRebasableFromRebasable(depositAmountOfRebasableToken, tokenRate); - const depositAmountRebasable = rebasableFromNonRebasable(depositAmountNonRebasable, tokenRate); + // wrap on L1 + const depositAmountNonRebasable = nonRebasableFromRebasableL1( + depositAmountOfRebasableToken, + ctx.constants.totalPooledEther, + ctx.constants.totalShares + ); + // wrap on L2: loosing 1-2 wei for big numbers + const depositAmountRebasable = rebasableFromNonRebasableL2( + depositAmountNonRebasable, + ctx.constants.tokenRateDecimals, + ctx.constants.tokenRate + ); const refSlotTime = await refSlotTimestamp(accountingOracle); const dataToReceive = await tokenRateAndTimestampPacked(tokenRate, refSlotTime, "0x"); @@ -559,8 +614,18 @@ function bridgingTestsSuit(scenarioInstance: ScenarioTest) { const { depositAmountOfRebasableToken, withdrawalAmountOfRebasableToken, tokenRate } = ctx.constants; - const withdrawalAmountNonRebasable = nonRebasableFromRebasable(withdrawalAmountOfRebasableToken, tokenRate); - const withdrawalAmountRebasable = rebasableFromNonRebasable(withdrawalAmountNonRebasable, tokenRate); + // unwrap on L2 + const withdrawalAmountNonRebasable = nonRebasableFromRebasableL2( + withdrawalAmountOfRebasableToken, + ctx.constants.tokenRateDecimals, + tokenRate); + + // unwrap on L1: loosing 1-2 wei + const withdrawalAmountRebasable = rebasableFromNonRebasableL1( + withdrawalAmountNonRebasable, + ctx.constants.totalPooledEther, + ctx.constants.totalShares + ); const tokenHolderABalanceBefore = await l1TokenRebasable.balanceOf(tokenHolderA.address); const l1LidoTokensBridgeBalanceBefore = await l1Token.balanceOf(l1LidoTokensBridge.address); @@ -626,10 +691,17 @@ function bridgingTestsSuit(scenarioInstance: ScenarioTest) { .run(); } -function ctxFactory(depositAmountOfRebasableToken: BigNumber, withdrawalAmountOfRebasableToken: BigNumber) { +function ctxFactory( + totalPooledEther: BigNumber, + totalShares: BigNumber, + tokenRateDecimals: BigNumber, + depositAmountOfRebasableToken: BigNumber, + withdrawalAmountOfRebasableToken: BigNumber +) { return async () => { const networkName = env.network("TESTING_OPT_NETWORK", "mainnet"); - const exchangeRate = BigNumber.from('1164454276599657236000000000'); + + const exchangeRate = getExchangeRate(tokenRateDecimals, totalPooledEther, totalShares); const { l1Provider, @@ -637,7 +709,7 @@ function ctxFactory(depositAmountOfRebasableToken: BigNumber, withdrawalAmountOf l1ERC20ExtendedTokensBridgeAdmin, l2ERC20ExtendedTokensBridgeAdmin, ...contracts - } = await optimism.testing(networkName).getIntegrationTestSetup(exchangeRate); + } = await optimism.testing(networkName).getIntegrationTestSetup(totalPooledEther, totalShares); const l1Snapshot = await l1Provider.send("evm_snapshot", []); const l2Snapshot = await l2Provider.send("evm_snapshot", []); @@ -647,7 +719,6 @@ function ctxFactory(depositAmountOfRebasableToken: BigNumber, withdrawalAmountOf const accountA = testing.accounts.accountA(l1Provider, l2Provider); const accountB = testing.accounts.accountB(l1Provider, l2Provider); - await testing.setBalance( await contracts.l1TokensHolder.getAddress(), wei.toBigNumber(wei`1 ether`), @@ -699,7 +770,10 @@ function ctxFactory(depositAmountOfRebasableToken: BigNumber, withdrawalAmountOf constants: { depositAmountOfRebasableToken, withdrawalAmountOfRebasableToken, - tokenRate: exchangeRate + tokenRate: exchangeRate, + totalPooledEther, + totalShares, + tokenRateDecimals, }, balances: { accountABalanceBeforeDeposit, @@ -713,22 +787,48 @@ function ctxFactory(depositAmountOfRebasableToken: BigNumber, withdrawalAmountOf } } -function nonRebasableFromRebasable(rebasable: BigNumber, exchangeRate: BigNumber) { - return BigNumber.from(rebasable) - .mul(BigNumber.from('1000000000000000000000000000')) +function getExchangeRate(decimals: BigNumber, totalPooledEther: BigNumber, totalShares: BigNumber) { + return (BigNumber.from(10).pow(decimals)).mul(totalPooledEther).div(totalShares); +} + +/// token / rate +function nonRebasableFromRebasableL1(rebasable: BigNumber, totalPooledEther: BigNumber, totalShares: BigNumber) { + return rebasable + .mul(totalShares) + .div(totalPooledEther); +} + +/// token * rate +function rebasableFromNonRebasableL1(rebasable: BigNumber, totalPooledEther: BigNumber, totalShares: BigNumber) { + return rebasable + .mul(totalPooledEther) + .div(totalShares); +} + +function nonRebasableFromRebasableL2(rebasable: BigNumber, decimals: BigNumber, exchangeRate: BigNumber) { + return rebasable + .mul(BigNumber.from(10).pow(decimals)) .div(exchangeRate); } -function rebasableFromNonRebasable(nonRebasable: BigNumber, exchangeRate: BigNumber) { - return BigNumber.from(nonRebasable) +function rebasableFromNonRebasableL2(nonRebasable: BigNumber, decimals: BigNumber, exchangeRate: BigNumber) { + return nonRebasable .mul(exchangeRate) - .div(BigNumber.from('1000000000000000000000000000')); + .div(BigNumber.from(10).pow(decimals)); +} + +function almostEqual(num1: BigNumber, num2: BigNumber) { + const delta = (num1.sub(num2)).abs(); + return delta.lte(BigNumber.from('2')); } bridgingTestsSuit( scenario( "Optimism :: Bridging X rebasable token integration test ", ctxFactory( + BigNumber.from('9309904612343950493629678'), + BigNumber.from('7975822843597609202337218'), + BigNumber.from(27), wei.toBigNumber(wei`0.001 ether`), wei.toBigNumber(wei`0.001 ether`) ) @@ -739,6 +839,9 @@ bridgingTestsSuit( scenario( "Optimism :: Bridging 1 wei rebasable token integration test", ctxFactory( + BigNumber.from('9309904612343950493629678'), + BigNumber.from('7975822843597609202337218'), + BigNumber.from(27), wei.toBigNumber(wei`1 wei`), wei.toBigNumber(wei`1 wei`) ) @@ -749,13 +852,26 @@ bridgingTestsSuit( scenario( "Optimism :: Bridging Zero rebasable token integration test", ctxFactory( + BigNumber.from('9309904612343950493629678'), + BigNumber.from('7975822843597609202337218'), + BigNumber.from(27), BigNumber.from('0'), BigNumber.from('0') ) ) ); -function almostEqual(num1: BigNumber, num2: BigNumber) { - const delta = (num1.sub(num2)).abs(); - return delta.lte(BigNumber.from('2')); -} +bridgingTestsSuit( + scenario( + "Optimism :: Bridging Big rebasable token integration test", + ctxFactory( + BigNumber.from('9309904612343950493629678'), + BigNumber.from('7975822843597609202337218'), + BigNumber.from(27), + BigNumber.from(10).pow(27), + BigNumber.from(10).pow(27).sub(2) // correct big number because during rounding and loosing 1-2 wei + /// user can't withdraw the same amount + ) + ) +); + diff --git a/test/optimism/pushingTokenRate.integration.test.ts b/test/optimism/pushingTokenRate.integration.test.ts index 8e718d72..c579b481 100644 --- a/test/optimism/pushingTokenRate.integration.test.ts +++ b/test/optimism/pushingTokenRate.integration.test.ts @@ -6,7 +6,7 @@ import network from "../../utils/network"; import testing, { scenario } from "../../utils/testing"; import deploymentOracle from "../../utils/optimism/deploymentOracle"; import { getBridgeExecutorParams } from "../../utils/bridge-executor"; -import { tokenRateAndTimestampPacked } from "../../utils/testing/helpers"; +import { getExchangeRate } from "../../utils/testing/helpers"; import { BigNumber } from "ethers"; import { getBlockTimestamp } from "../../utils/testing/helpers"; import { @@ -110,7 +110,10 @@ async function ctxFactory() { const tokenRateOutdatedDelay = 86400; const maxAllowedL2ToL1ClockLag = BigNumber.from(86400); const maxAllowedTokenRateDeviationPerDay = BigNumber.from(500); - const tokenRate = BigNumber.from('1164454276599657236000000000'); + const totalPooledEther = BigNumber.from('9309904612343950493629678'); + const totalShares = BigNumber.from('7975822843597609202337218'); + const tokenRateDecimals = BigNumber.from(27); + const tokenRate = getExchangeRate(tokenRateDecimals, totalPooledEther, totalShares); const networkName = env.network("TESTING_OPT_NETWORK", "mainnet"); const [l1Provider, l2Provider] = network @@ -151,7 +154,8 @@ async function ctxFactory() { l1TokenRebasable.address, "Test Token", "TT", - tokenRate + totalPooledEther, + totalShares ); const accountingOracle = await new AccountingOracleStub__factory(l1Deployer).deploy( diff --git a/utils/optimism/testing.ts b/utils/optimism/testing.ts index fe2a2a58..ca288469 100644 --- a/utils/optimism/testing.ts +++ b/utils/optimism/testing.ts @@ -47,7 +47,10 @@ export default function testing(networkName: NetworkName) { ...bridgeContracts, }; }, - async getIntegrationTestSetup(tokenRate: BigNumber) { + async getIntegrationTestSetup( + totalPooledEther: BigNumber, + totalShares: BigNumber + ) { const hasDeployedContracts = testingUtils.env.USE_DEPLOYED_CONTRACTS(false); @@ -57,7 +60,7 @@ export default function testing(networkName: NetworkName) { const bridgeContracts = hasDeployedContracts ? await loadDeployedBridges(ethProvider, optProvider) - : await deployTestBridge(networkName, tokenRate, ethProvider, optProvider); + : await deployTestBridge(networkName, totalPooledEther, totalShares, ethProvider, optProvider); const [l1ERC20ExtendedTokensAdminAddress] = await BridgingManagement.getAdmins(bridgeContracts.l1LidoTokensBridge); @@ -185,7 +188,8 @@ async function loadDeployedBridges( async function deployTestBridge( networkName: NetworkName, - tokenRate: BigNumber, + totalPooledEther: BigNumber, + totalShares: BigNumber, ethProvider: JsonRpcProvider, optProvider: JsonRpcProvider ) { @@ -201,13 +205,19 @@ async function deployTestBridge( l1TokenRebasable.address, "Test Token", "TT", - tokenRate + totalPooledEther, + totalShares ); + const tokenRate = BigNumber.from(10).pow(27).mul(totalPooledEther).div(totalShares); + const genesisTime = 1; + const secondsPerSlot = 2; + const lastProcessingRefSlot = 3; + const accountingOracle = await new AccountingOracleStub__factory(ethDeployer).deploy( - 1, - 2, - 3 + genesisTime, + secondsPerSlot, + lastProcessingRefSlot ); const [ethDeployScript, optDeployScript] = await deploymentAll( diff --git a/utils/testing/helpers.ts b/utils/testing/helpers.ts index b39e9494..6fd5de4b 100644 --- a/utils/testing/helpers.ts +++ b/utils/testing/helpers.ts @@ -55,3 +55,38 @@ export async function predictAddresses(account: SignerWithAddress, txsCount: num } return res; } + +export function getExchangeRate(decimals: BigNumber, totalPooledEther: BigNumber, totalShares: BigNumber) { + return (BigNumber.from(10).pow(decimals)).mul(totalPooledEther).div(totalShares); +} + +/// token / rate +export function nonRebasableFromRebasableL1(rebasable: BigNumber, totalPooledEther: BigNumber, totalShares: BigNumber) { + return rebasable + .mul(totalShares) + .div(totalPooledEther); +} + +/// token * rate +export function rebasableFromNonRebasableL1(rebasable: BigNumber, totalPooledEther: BigNumber, totalShares: BigNumber) { + return rebasable + .mul(totalPooledEther) + .div(totalShares); +} + +export function nonRebasableFromRebasableL2(rebasable: BigNumber, decimals: BigNumber, exchangeRate: BigNumber) { + return rebasable + .mul(BigNumber.from(10).pow(decimals)) + .div(exchangeRate); +} + +export function rebasableFromNonRebasableL2(nonRebasable: BigNumber, decimals: BigNumber, exchangeRate: BigNumber) { + return nonRebasable + .mul(exchangeRate) + .div(BigNumber.from(10).pow(decimals)); +} + +export function almostEqual(num1: BigNumber, num2: BigNumber) { + const delta = (num1.sub(num2)).abs(); + return delta.lte(BigNumber.from('2')); +} From 6c5f357260531e92a63bdcdfccc7a63d8806b84b Mon Sep 17 00:00:00 2001 From: Anton Kovalchuk Date: Wed, 22 May 2024 14:47:46 +0200 Subject: [PATCH 128/148] add logs to scipts --- contracts/optimism/TokenRateOracle.sol | 2 +- .../L2ERC20ExtendedTokensBridge.unit.test.ts | 17 +++-- .../bridging-rebasable.integration.test.ts | 74 +++++++------------ 3 files changed, 36 insertions(+), 57 deletions(-) diff --git a/contracts/optimism/TokenRateOracle.sol b/contracts/optimism/TokenRateOracle.sol index 88bede34..11753472 100644 --- a/contracts/optimism/TokenRateOracle.sol +++ b/contracts/optimism/TokenRateOracle.sol @@ -154,7 +154,7 @@ contract TokenRateOracle is CrossDomainEnabled, ITokenRateOracle, Versioned { } /// @dev allow token rate to be within some configurable range that depens on time it wasn't updated. - if ((tokenRate_ != tokenRateData.tokenRate) &&!_isTokenRateWithinAllowedRange( + if ((tokenRate_ != tokenRateData.tokenRate) && !_isTokenRateWithinAllowedRange( tokenRateData.tokenRate, tokenRate_, tokenRateData.rateUpdateL1Timestamp, diff --git a/test/optimism/L2ERC20ExtendedTokensBridge.unit.test.ts b/test/optimism/L2ERC20ExtendedTokensBridge.unit.test.ts index 7d84b88f..c87d9aad 100644 --- a/test/optimism/L2ERC20ExtendedTokensBridge.unit.test.ts +++ b/test/optimism/L2ERC20ExtendedTokensBridge.unit.test.ts @@ -285,18 +285,25 @@ unit("Optimism:: L2ERC20ExtendedTokensBridge", ctxFactory) const amountToDepositNonRebasable = wei`1 ether`; + // wrap on L2 const amountToWithdrawRebasable = rebasableFromNonRebasableL2( wei.toBigNumber(amountToDepositNonRebasable), tokenRateDecimals, exchangeRate ); + // unwrap on L2 const amountReceivedWithdrawNonRebasable = nonRebasableFromRebasableL2( amountToWithdrawRebasable, tokenRateDecimals, exchangeRate ); + console.log("input: amountToDepositNonRebasable=",amountToDepositNonRebasable); + console.log("wrap on L2: amountToWithdrawRebasable=",amountToWithdrawRebasable); + console.log("unwrap on L2: amountReceivedWithdrawNonRebasable=",amountReceivedWithdrawNonRebasable); + + const l1Gas = wei`1 wei`; const data = "0xdeadbeaf"; const currentBlockTimestamp = await getBlockTimestamp(ctx.provider, 0); @@ -354,20 +361,14 @@ unit("Optimism:: L2ERC20ExtendedTokensBridge", ctxFactory) l1Gas, ]); - console.log("amountToWithdraw=",amountToWithdrawRebasable); - - console.log("recipientBalanceBefore=",recipientBalanceBefore); - console.log("after=",await l2TokenRebasable.balanceOf(deployer.address)); + console.log("rebasable on L2 recipientBalanceBefore=",recipientBalanceBefore); + console.log("rebasable on L2 recipientBalanceAfter=",await l2TokenRebasable.balanceOf(recipient.address)); assert.equalBN( await l2TokenRebasable.balanceOf(deployer.address), recipientBalanceBefore.sub(amountToWithdrawRebasable) ); - console.log("await l2TokenRebasable.totalSupply()=",await l2TokenRebasable.totalSupply()); - console.log("totalSupplyBefore=",totalSupplyBefore); - // console.log("amountToWithdraw=",amountToWithdraw); - assert.isTrue(almostEqual( await l2TokenRebasable.totalSupply(), totalSupplyBefore.sub(amountToWithdrawRebasable) diff --git a/test/optimism/bridging-rebasable.integration.test.ts b/test/optimism/bridging-rebasable.integration.test.ts index 06738f20..a6656157 100644 --- a/test/optimism/bridging-rebasable.integration.test.ts +++ b/test/optimism/bridging-rebasable.integration.test.ts @@ -5,7 +5,15 @@ import { wei } from "../../utils/wei"; import optimism from "../../utils/optimism"; import testing, { scenario } from "../../utils/testing"; import { ScenarioTest } from "../../utils/testing"; -import { tokenRateAndTimestampPacked, refSlotTimestamp } from "../../utils/testing/helpers"; +import { + tokenRateAndTimestampPacked, + refSlotTimestamp, + nonRebasableFromRebasableL1, + nonRebasableFromRebasableL2, + rebasableFromNonRebasableL1, + rebasableFromNonRebasableL2, + getExchangeRate +} from "../../utils/testing/helpers"; type ContextType = Awaited>> @@ -95,7 +103,7 @@ function bridgingTestsSuit(scenarioInstance: ScenarioTest) { ); console.log("depositAmountOfRebasableToken=",depositAmountOfRebasableToken); - console.log("depositAmountNonRebasable=",depositAmountNonRebasable); + console.log("warp L1: depositAmountNonRebasable=",depositAmountNonRebasable); await l1TokenRebasable .connect(tokenHolderA.l1Signer) @@ -197,10 +205,9 @@ function bridgingTestsSuit(scenarioInstance: ScenarioTest) { ctx.constants.tokenRate ); - console.log("depositAmountOfRebasableToken=",depositAmountOfRebasableToken); - console.log("depositAmountNonRebasable=",depositAmountNonRebasable); - console.log("depositAmountRebasable=",depositAmountRebasable); - + console.log("input: depositAmountOfRebasableToken=",depositAmountOfRebasableToken); + console.log("wrap on L1: depositAmountNonRebasable=",depositAmountNonRebasable); + console.log("wrap on L2: depositAmountRebasable=",depositAmountRebasable); const { accountA: tokenHolderA, l1CrossDomainMessengerAliased } = ctx.accounts; @@ -210,7 +217,6 @@ function bridgingTestsSuit(scenarioInstance: ScenarioTest) { const refSlotTime = await refSlotTimestamp(accountingOracle); const dataToReceive = await tokenRateAndTimestampPacked(tokenRate, refSlotTime, "0x"); - const tx = await l2CrossDomainMessenger .connect(l1CrossDomainMessengerAliased) .relayMessage( @@ -242,10 +248,9 @@ function bridgingTestsSuit(scenarioInstance: ScenarioTest) { const tokenHolderABalanceAfter = await l2TokenRebasable.balanceOf(tokenHolderA.address); const l2TokenRebasableTotalSupplyAfter = await l2TokenRebasable.totalSupply(); - // console.log("tokenHolderABalanceBefore=",tokenHolderABalanceBefore); - // console.log("depositAmountRebasable=",depositAmountRebasable); - // console.log("tokenHolderABalanceAfter=",tokenHolderABalanceAfter); - // console.log("tokenHolderABalanceBefore.add(depositAmountRebasable)=",tokenHolderABalanceBefore.add(depositAmountRebasable)); + console.log("rebasable on L2 tokenHolderABalanceBefore=",tokenHolderABalanceBefore); + console.log("rebasable on L2 tokenHolderABalanceAfter=",tokenHolderABalanceAfter); + console.log("diff on L2 tokenHolderABalance=",tokenHolderABalanceBefore.add(depositAmountRebasable).sub(tokenHolderABalanceAfter)); assert.equalBN( tokenHolderABalanceBefore.add(depositAmountRebasable), @@ -269,8 +274,7 @@ function bridgingTestsSuit(scenarioInstance: ScenarioTest) { const tokenHolderABalanceBefore = await l2TokenRebasable.balanceOf(tokenHolderA.address); const l2TotalSupplyBefore = await l2TokenRebasable.totalSupply(); - console.log("withdrawalAmountOfRebasableToken=",withdrawalAmountOfRebasableToken); - //console.log("tokenHolderABalanceBefore=",tokenHolderABalanceBefore); + console.log("input: withdrawalAmountOfRebasableToken=",withdrawalAmountOfRebasableToken); const tx = await l2ERC20ExtendedTokensBridge .connect(tokenHolderA.l2Signer) @@ -324,9 +328,9 @@ function bridgingTestsSuit(scenarioInstance: ScenarioTest) { ctx.constants.totalShares ); - console.log("withdrawalAmountOfRebasableToken=",withdrawalAmountOfRebasableToken); - console.log("withdrawalAmountNonRebasable=",withdrawalAmountNonRebasable); - console.log("withdrawalAmountRebasable=",withdrawalAmountRebasable); + console.log("input: withdrawalAmountOfRebasableToken=",withdrawalAmountOfRebasableToken); + console.log("unwrap on L2: withdrawalAmountNonRebasable=",withdrawalAmountNonRebasable); + console.log("unwrap on L1: withdrawalAmountRebasable=",withdrawalAmountRebasable); const tokenHolderABalanceBefore = await l1TokenRebasable.balanceOf(tokenHolderA.address); const l1LidoTokensBridgeBalanceBefore = await l1Token.balanceOf(l1LidoTokensBridge.address); @@ -366,6 +370,10 @@ function bridgingTestsSuit(scenarioInstance: ScenarioTest) { const l1LidoTokensBridgeBalanceAfter = await l1Token.balanceOf(l1LidoTokensBridge.address); const tokenHolderABalanceAfter = await l1TokenRebasable.balanceOf(tokenHolderA.address); + console.log("rebasable on L1 tokenHolderABalanceBefore=",tokenHolderABalanceBefore); + console.log("rebasable on L1 tokenHolderABalanceAfter=",tokenHolderABalanceAfter); + console.log("diff on L1 tokenHolderA=",tokenHolderABalanceAfter.sub(tokenHolderABalanceBefore.add(withdrawalAmountRebasable))); + assert.equalBN( l1LidoTokensBridgeBalanceAfter, l1LidoTokensBridgeBalanceBefore.sub(withdrawalAmountNonRebasable) @@ -787,36 +795,6 @@ function ctxFactory( } } -function getExchangeRate(decimals: BigNumber, totalPooledEther: BigNumber, totalShares: BigNumber) { - return (BigNumber.from(10).pow(decimals)).mul(totalPooledEther).div(totalShares); -} - -/// token / rate -function nonRebasableFromRebasableL1(rebasable: BigNumber, totalPooledEther: BigNumber, totalShares: BigNumber) { - return rebasable - .mul(totalShares) - .div(totalPooledEther); -} - -/// token * rate -function rebasableFromNonRebasableL1(rebasable: BigNumber, totalPooledEther: BigNumber, totalShares: BigNumber) { - return rebasable - .mul(totalPooledEther) - .div(totalShares); -} - -function nonRebasableFromRebasableL2(rebasable: BigNumber, decimals: BigNumber, exchangeRate: BigNumber) { - return rebasable - .mul(BigNumber.from(10).pow(decimals)) - .div(exchangeRate); -} - -function rebasableFromNonRebasableL2(nonRebasable: BigNumber, decimals: BigNumber, exchangeRate: BigNumber) { - return nonRebasable - .mul(exchangeRate) - .div(BigNumber.from(10).pow(decimals)); -} - function almostEqual(num1: BigNumber, num2: BigNumber) { const delta = (num1.sub(num2)).abs(); return delta.lte(BigNumber.from('2')); @@ -869,8 +847,8 @@ bridgingTestsSuit( BigNumber.from('7975822843597609202337218'), BigNumber.from(27), BigNumber.from(10).pow(27), - BigNumber.from(10).pow(27).sub(2) // correct big number because during rounding and loosing 1-2 wei - /// user can't withdraw the same amount + BigNumber.from(10).pow(27).sub(2) // correct big number because during rounding looses 1-2 wei + /// Thus, user can't withdraw the same amount ) ) ); From e6888cc6daaf59845356ce9b7cc54389d252894d Mon Sep 17 00:00:00 2001 From: Anton Kovalchuk Date: Wed, 22 May 2024 16:23:46 +0200 Subject: [PATCH 129/148] emit correct amount of token during unwrap and bridgeUnwrap --- contracts/token/ERC20RebasableBridged.sol | 12 +++++------- .../ERC20RebasableBridgedPermit.unit.test.ts | 19 +++++++++++++++++++ 2 files changed, 24 insertions(+), 7 deletions(-) diff --git a/contracts/token/ERC20RebasableBridged.sol b/contracts/token/ERC20RebasableBridged.sol index 7f449b09..ea4b05e0 100644 --- a/contracts/token/ERC20RebasableBridged.sol +++ b/contracts/token/ERC20RebasableBridged.sol @@ -92,7 +92,8 @@ contract ERC20RebasableBridged is IERC20, IERC20Wrapper, IBridgeWrapper, ERC20Me /// @param sharesAmount_ amount of rebasable token shares to unwrap. /// @return amount of non-rebasable token user receives after unwrap. function unwrapShares(uint256 sharesAmount_) external returns (uint256) { - return _unwrapShares(msg.sender, sharesAmount_); + uint256 tokenAmount = _getTokensByShares(sharesAmount_); + return _unwrapShares(msg.sender, sharesAmount_, tokenAmount); } /// @inheritdoc IBridgeWrapper @@ -326,8 +327,6 @@ contract ERC20RebasableBridged is IERC20, IERC20Wrapper, IBridgeWrapper, ERC20Me if (accountShares < amount_) revert ErrorNotEnoughBalance(); _setTotalShares(_getTotalShares() - amount_); _getShares()[account_] = accountShares - amount_; - uint256 tokensAmount = _getTokensByShares(amount_); - _emitTransferEvents(account_, address(0), tokensAmount, amount_); } /// @dev Moves `sharesAmount_` shares from `sender_` to `recipient_`. @@ -369,22 +368,21 @@ contract ERC20RebasableBridged is IERC20, IERC20Wrapper, IBridgeWrapper, ERC20Me function _wrap(address from_, address to_, uint256 sharesAmount_) internal returns (uint256) { if (sharesAmount_ == 0) revert ErrorZeroSharesWrap(); - TOKEN_TO_WRAP_FROM.safeTransferFrom(from_, address(this), sharesAmount_); _mintShares(to_, sharesAmount_); - return _getTokensByShares(sharesAmount_); } function _unwrap(address account_, uint256 tokenAmount_) internal returns (uint256) { if (tokenAmount_ == 0) revert ErrorZeroTokensUnwrap(); uint256 sharesAmount = _getSharesByTokens(tokenAmount_); - return _unwrapShares(account_, sharesAmount); + return _unwrapShares(account_, sharesAmount, tokenAmount_); } - function _unwrapShares(address account_, uint256 sharesAmount_) internal returns (uint256) { + function _unwrapShares(address account_, uint256 sharesAmount_, uint256 tokenAmount_) internal returns (uint256) { if (sharesAmount_ == 0) revert ErrorZeroSharesUnwrap(); _burnShares(account_, sharesAmount_); + _emitTransferEvents(account_, address(0), tokenAmount_, sharesAmount_); TOKEN_TO_WRAP_FROM.safeTransfer(account_, sharesAmount_); return sharesAmount_; } diff --git a/test/token/ERC20RebasableBridgedPermit.unit.test.ts b/test/token/ERC20RebasableBridgedPermit.unit.test.ts index 70418123..f065635a 100644 --- a/test/token/ERC20RebasableBridgedPermit.unit.test.ts +++ b/test/token/ERC20RebasableBridgedPermit.unit.test.ts @@ -352,6 +352,25 @@ unit("ERC20RebasableBridgedPermit", ctxFactory) await assert.revertsWith(rebasableProxied.connect(user1).unwrap(wei`4 ether`), "ErrorNotEnoughBalance()"); }) + .test("unwrap() :: events", async (ctx) => { + const { rebasableProxied, wrappedToken } = ctx.contracts; + const { user1, owner, zero } = ctx.accounts; + const { tokenRate, tenPowDecimals } = ctx.constants; + + const user1SharesToWrap = BigNumber.from(10).pow(30) ; + const user1TokensToUnwrap = BigNumber.from('764035550674393190'); + const user1SharesToUnwrap = (user1TokensToUnwrap).mul(tenPowDecimals).div(BigNumber.from(tokenRate)); + + await wrappedToken.connect(owner).bridgeMint(user1.address, user1SharesToWrap); + await wrappedToken.connect(user1).approve(rebasableProxied.address, user1SharesToWrap); + await rebasableProxied.connect(user1).wrap(user1SharesToWrap); + + const tx = await rebasableProxied.connect(user1).unwrap(user1TokensToUnwrap); + + await assert.emits(rebasableProxied, tx, "Transfer", [user1.address, zero.address, user1TokensToUnwrap]); + await assert.emits(rebasableProxied, tx, "TransferShares", [user1.address, zero.address, user1SharesToUnwrap]); + }) + .test("unwrap() :: happy path", async (ctx) => { const { rebasableProxied, wrappedToken } = ctx.contracts; From 5ea0159f7f494f683005f0382870c99da5f791f8 Mon Sep 17 00:00:00 2001 From: Anton Kovalchuk Date: Wed, 22 May 2024 20:01:59 +0200 Subject: [PATCH 130/148] emit correct events, fix comment, remove unsued code --- contracts/BridgingManager.sol | 2 +- contracts/lido/TokenRateNotifier.sol | 6 ++++-- contracts/optimism/L2ERC20ExtendedTokensBridge.sol | 4 ++-- contracts/optimism/interfaces/IL2ERC20Bridge.sol | 13 ++----------- contracts/token/ERC20Core.sol | 3 +-- contracts/token/ERC20RebasableBridged.sol | 13 ++++++------- 6 files changed, 16 insertions(+), 25 deletions(-) diff --git a/contracts/BridgingManager.sol b/contracts/BridgingManager.sol index 0588ea60..a61eca89 100644 --- a/contracts/BridgingManager.sol +++ b/contracts/BridgingManager.sol @@ -42,7 +42,7 @@ contract BridgingManager is AccessControl { if (s.isInitialized) { revert ErrorAlreadyInitialized(); } - _setupRole(DEFAULT_ADMIN_ROLE, admin_); + _grantRole(DEFAULT_ADMIN_ROLE, admin_); s.isInitialized = true; emit Initialized(admin_); } diff --git a/contracts/lido/TokenRateNotifier.sol b/contracts/lido/TokenRateNotifier.sol index 1f3d16dc..587bb45d 100644 --- a/contracts/lido/TokenRateNotifier.sol +++ b/contracts/lido/TokenRateNotifier.sol @@ -94,7 +94,8 @@ contract TokenRateNotifier is Ownable, IPostTokenRebaseReceiver { uint256, /* postTotalEther */ uint256 /* sharesMintedAsFees */ ) external { - for (uint256 obIndex = 0; obIndex < observers.length; obIndex++) { + uint256 cachedObserversLength = observers.length; + for (uint256 obIndex = 0; obIndex < cachedObserversLength; obIndex++) { // solhint-disable-next-line no-empty-blocks try ITokenRatePusher(observers[obIndex]).pushTokenRate() {} catch (bytes memory lowLevelRevertData) { @@ -121,7 +122,8 @@ contract TokenRateNotifier is Ownable, IPostTokenRebaseReceiver { /// @notice `observer_` index in `observers` array. /// @return An index of `observer_` or `INDEX_NOT_FOUND` if it wasn't found. function _observerIndex(address observer_) internal view returns (uint256) { - for (uint256 obIndex = 0; obIndex < observers.length; obIndex++) { + uint256 cachedObserversLength = observers.length; + for (uint256 obIndex = 0; obIndex < cachedObserversLength; obIndex++) { if (observers[obIndex] == observer_) { return obIndex; } diff --git a/contracts/optimism/L2ERC20ExtendedTokensBridge.sol b/contracts/optimism/L2ERC20ExtendedTokensBridge.sol index 15b9869c..9c2fca4f 100644 --- a/contracts/optimism/L2ERC20ExtendedTokensBridge.sol +++ b/contracts/optimism/L2ERC20ExtendedTokensBridge.sol @@ -129,8 +129,8 @@ contract L2ERC20ExtendedTokensBridge is ) external whenDepositsEnabled - onlySupportedL1L2TokensPair(l1Token_, l2Token_) onlyFromCrossDomainAccount(L1_TOKEN_BRIDGE) + onlySupportedL1L2TokensPair(l1Token_, l2Token_) { DepositDataCodec.DepositData memory depositData = DepositDataCodec.decodeDepositData(data_); ITokenRateUpdatable tokenRateOracle = ERC20RebasableBridged(L2_TOKEN_REBASABLE).TOKEN_RATE_ORACLE(); @@ -152,7 +152,7 @@ contract L2ERC20ExtendedTokensBridge is /// @param from_ Account to pull the withdrawal from on L2 /// @param to_ Account to give the withdrawal to on L1 /// @param amount_ Amount of the token to withdraw - /// @param l1Gas_ Unused, but included for potential forward compatibility considerations + /// @param l1Gas_ Minimum gas limit to use for the transaction. /// @param data_ Optional data to forward to L1. This data is provided /// solely as a convenience for external contracts. Aside from enforcing a maximum /// length, these contracts provide no guarantees about its content diff --git a/contracts/optimism/interfaces/IL2ERC20Bridge.sol b/contracts/optimism/interfaces/IL2ERC20Bridge.sol index 448dfb8b..a0907ee1 100644 --- a/contracts/optimism/interfaces/IL2ERC20Bridge.sol +++ b/contracts/optimism/interfaces/IL2ERC20Bridge.sol @@ -26,22 +26,13 @@ interface IL2ERC20Bridge { bytes _data ); - event DepositFailed( - address indexed _l1Token, - address indexed _l2Token, - address indexed _from, - address _to, - uint256 _amount, - bytes _data - ); - /// @notice Returns the address of the corresponding L1 bridge contract function l1TokenBridge() external returns (address); /// @notice Initiates a withdraw of some tokens to the caller's account on L1 /// @param l2Token_ Address of L2 token where withdrawal was initiated. /// @param amount_ Amount of the token to withdraw. - /// @param l1Gas_ Unused, but included for potential forward compatibility considerations. + /// @param l1Gas_ Minimum gas limit to use for the transaction. /// @param data_ Optional data to forward to L1. This data is provided /// solely as a convenience for external contracts. Aside from enforcing a maximum /// length, these contracts provide no guarantees about its content. @@ -56,7 +47,7 @@ interface IL2ERC20Bridge { /// @param l2Token_ Address of L2 token where withdrawal is initiated. /// @param to_ L1 adress to credit the withdrawal to. /// @param amount_ Amount of the token to withdraw. - /// @param l1Gas_ Unused, but included for potential forward compatibility considerations. + /// @param l1Gas_ Minimum gas limit to use for the transaction. /// @param data_ Optional data to forward to L1. This data is provided /// solely as a convenience for external contracts. Aside from enforcing a maximum /// length, these contracts provide no guarantees about its content. diff --git a/contracts/token/ERC20Core.sol b/contracts/token/ERC20Core.sol index 354c28bd..f5588a33 100644 --- a/contracts/token/ERC20Core.sol +++ b/contracts/token/ERC20Core.sol @@ -24,6 +24,7 @@ contract ERC20Core is IERC20 { returns (bool) { _approve(msg.sender, spender_, amount_); + emit Approval(msg.sender, spender_, amount_); return true; } @@ -90,7 +91,6 @@ contract ERC20Core is IERC20 { uint256 amount_ ) internal virtual onlyNonZeroAccount(owner_) onlyNonZeroAccount(spender_) { allowance[owner_][spender_] = amount_; - emit Approval(owner_, spender_, amount_); } /// @dev Creates amount_ tokens and assigns them to account_, increasing the total supply @@ -142,5 +142,4 @@ contract ERC20Core is IERC20 { error ErrorNotEnoughBalance(); error ErrorNotEnoughAllowance(); error ErrorAccountIsZeroAddress(); - error ErrorDecreasedAllowanceBelowZero(); } diff --git a/contracts/token/ERC20RebasableBridged.sol b/contracts/token/ERC20RebasableBridged.sol index ea4b05e0..5c108d11 100644 --- a/contracts/token/ERC20RebasableBridged.sol +++ b/contracts/token/ERC20RebasableBridged.sol @@ -150,6 +150,7 @@ contract ERC20RebasableBridged is IERC20, IERC20Wrapper, IBridgeWrapper, ERC20Me /// @inheritdoc IERC20 function approve(address spender_, uint256 amount_) external returns (bool) { _approve(msg.sender, spender_, amount_); + emit Approval(msg.sender, spender_, amount_); return true; } @@ -273,7 +274,6 @@ contract ERC20RebasableBridged is IERC20, IERC20Wrapper, IBridgeWrapper, ERC20Me uint256 amount_ ) internal virtual onlyNonZeroAccount(owner_) onlyNonZeroAccount(spender_) { _getTokenAllowance()[owner_][spender_] = amount_; - emit Approval(owner_, spender_, amount_); } function _sharesOf(address account_) internal view returns (uint256) { @@ -312,8 +312,6 @@ contract ERC20RebasableBridged is IERC20, IERC20Wrapper, IBridgeWrapper, ERC20Me ) internal onlyNonZeroAccount(recipient_) { _setTotalShares(_getTotalShares() + amount_); _getShares()[recipient_] = _getShares()[recipient_] + amount_; - uint256 tokensAmount = _getTokensByShares(amount_); - _emitTransferEvents(address(0), recipient_, tokensAmount, amount_); } /// @dev Destroys `amount_` shares from `account_`, reducing the total shares supply. @@ -338,7 +336,7 @@ contract ERC20RebasableBridged is IERC20, IERC20Wrapper, IBridgeWrapper, ERC20Me address recipient_, uint256 sharesAmount_ ) internal onlyNonZeroAccount(sender_) onlyNonZeroAccount(recipient_) { - if (recipient_ == address(this)) revert ErrorTrasferToRebasableContract(); + if (recipient_ == address(this)) revert ErrorTransferToRebasableContract(); uint256 currentSenderShares = _getShares()[sender_]; if (sharesAmount_ > currentSenderShares) revert ErrorNotEnoughBalance(); @@ -370,7 +368,9 @@ contract ERC20RebasableBridged is IERC20, IERC20Wrapper, IBridgeWrapper, ERC20Me if (sharesAmount_ == 0) revert ErrorZeroSharesWrap(); TOKEN_TO_WRAP_FROM.safeTransferFrom(from_, address(this), sharesAmount_); _mintShares(to_, sharesAmount_); - return _getTokensByShares(sharesAmount_); + uint256 tokensAmount = _getTokensByShares(sharesAmount_); + _emitTransferEvents(address(0), to_, tokensAmount, sharesAmount_); + return tokensAmount; } function _unwrap(address account_, uint256 tokenAmount_) internal returns (uint256) { @@ -416,10 +416,9 @@ contract ERC20RebasableBridged is IERC20, IERC20Wrapper, IBridgeWrapper, ERC20Me error ErrorZeroSharesUnwrap(); error ErrorTokenRateDecimalsIsZero(); error ErrorWrongOracleUpdateTime(); - error ErrorTrasferToRebasableContract(); + error ErrorTransferToRebasableContract(); error ErrorNotEnoughBalance(); error ErrorNotEnoughAllowance(); error ErrorAccountIsZeroAddress(); - error ErrorDecreasedAllowanceBelowZero(); error ErrorNotBridge(); } From e137a559bc892b4c5b951efcfd791123035c9b9e Mon Sep 17 00:00:00 2001 From: Eugene Mamin Date: Wed, 22 May 2024 23:35:54 +0300 Subject: [PATCH 131/148] fix: change event name for rate timestamp update --- contracts/optimism/TokenRateOracle.sol | 4 ++-- test/optimism/TokenRateOracle.unit.test.ts | 2 +- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/contracts/optimism/TokenRateOracle.sol b/contracts/optimism/TokenRateOracle.sol index 11753472..5b3cea05 100644 --- a/contracts/optimism/TokenRateOracle.sol +++ b/contracts/optimism/TokenRateOracle.sol @@ -149,7 +149,7 @@ contract TokenRateOracle is CrossDomainEnabled, ITokenRateOracle, Versioned { /// by the AccountingOracle report if (rateUpdateL1Timestamp_ == tokenRateData.rateUpdateL1Timestamp) { _loadTokenRateData().value.rateReceivedL2Timestamp = uint64(block.timestamp); - emit RateReceivedUpdated(block.timestamp); + emit RateReceivedTimestampUpdated(block.timestamp); return; } @@ -267,7 +267,7 @@ contract TokenRateOracle is CrossDomainEnabled, ITokenRateOracle, Versioned { } event RateUpdated(uint256 tokenRate_, uint256 indexed rateL1Timestamp_); - event RateReceivedUpdated(uint256 indexed rateReceivedL2Timestamp); + event RateReceivedTimestampUpdated(uint256 indexed rateReceivedL2Timestamp); event DormantTokenRateUpdateIgnored(uint256 indexed newRateL1Timestamp_, uint256 indexed currentRateL1Timestamp_); event TokenRateL1TimestampIsInFuture(uint256 tokenRate_, uint256 indexed rateL1Timestamp_); diff --git a/test/optimism/TokenRateOracle.unit.test.ts b/test/optimism/TokenRateOracle.unit.test.ts index 650802fc..6ccc4574 100644 --- a/test/optimism/TokenRateOracle.unit.test.ts +++ b/test/optimism/TokenRateOracle.unit.test.ts @@ -210,7 +210,7 @@ unit("TokenRateOracle", ctxFactory) const updatedAt = await getContractTransactionTimestamp(ctx.provider, tx); - await assert.emits(tokenRateOracle, tx, "RateReceivedUpdated", [updatedAt]); + await assert.emits(tokenRateOracle, tx, "RateReceivedTimestampUpdated", [updatedAt]); const { roundId_, From 7369c2e13c36270eff23c0e863967be6b3d4bad6 Mon Sep 17 00:00:00 2001 From: Anton Kovalchuk Date: Thu, 23 May 2024 12:15:26 +0200 Subject: [PATCH 132/148] additional checks in constructors --- contracts/optimism/CrossDomainEnabled.sol | 4 + contracts/optimism/OpStackTokenRatePusher.sol | 5 + .../RebasableAndNonRebasableTokens.sol | 16 ++ .../TokenRateAndUpdateTimestampProvider.sol | 9 + contracts/optimism/TokenRateOracle.sol | 8 + contracts/token/ERC20Bridged.sol | 4 + contracts/token/ERC20BridgedPermit.sol | 1 + contracts/token/ERC20Metadata.sol | 4 + contracts/token/ERC20RebasableBridged.sol | 12 ++ .../token/ERC20RebasableBridgedPermit.sol | 1 + test/optimism/L1LidoTokensBridge.unit.test.ts | 91 ++++++++++ .../L2ERC20ExtendedTokensBridge.unit.test.ts | 74 ++++++++ .../OpStackTokenRatePusher.unit.test.ts | 55 +++++- test/optimism/TokenRateOracle.unit.test.ts | 43 ++++- test/token/ERC20BridgedPermit.unit.test.ts | 32 +++- .../ERC20RebasableBridgedPermit.unit.test.ts | 164 +++++++++++++----- 16 files changed, 470 insertions(+), 53 deletions(-) diff --git a/contracts/optimism/CrossDomainEnabled.sol b/contracts/optimism/CrossDomainEnabled.sol index 23935681..2103470a 100644 --- a/contracts/optimism/CrossDomainEnabled.sol +++ b/contracts/optimism/CrossDomainEnabled.sol @@ -12,6 +12,9 @@ contract CrossDomainEnabled { /// @param messenger_ Address of the CrossDomainMessenger on the current layer constructor(address messenger_) { + if (messenger_ == address(0)) { + revert ErrorZeroAddressMessenger(); + } MESSENGER = ICrossDomainMessenger(messenger_); } @@ -41,6 +44,7 @@ contract CrossDomainEnabled { _; } + error ErrorZeroAddressMessenger(); error ErrorUnauthorizedMessenger(); error ErrorWrongCrossDomainSender(); } diff --git a/contracts/optimism/OpStackTokenRatePusher.sol b/contracts/optimism/OpStackTokenRatePusher.sol index c2796f71..596156b3 100644 --- a/contracts/optimism/OpStackTokenRatePusher.sol +++ b/contracts/optimism/OpStackTokenRatePusher.sol @@ -35,6 +35,9 @@ contract OpStackTokenRatePusher is ERC165, CrossDomainEnabled, TokenRateAndUpdat address tokenRateOracle_, uint32 l2GasLimitForPushingTokenRate_ ) CrossDomainEnabled(messenger_) TokenRateAndUpdateTimestampProvider(wstETH_, accountingOracle_) { + if (tokenRateOracle_ == address(0)) { + revert ErrorZeroAddressTokenRateOracle(); + } L2_TOKEN_RATE_ORACLE = tokenRateOracle_; L2_GAS_LIMIT_FOR_PUSHING_TOKEN_RATE = l2GasLimitForPushingTokenRate_; } @@ -55,4 +58,6 @@ contract OpStackTokenRatePusher is ERC165, CrossDomainEnabled, TokenRateAndUpdat || super.supportsInterface(_interfaceId) ); } + + error ErrorZeroAddressTokenRateOracle(); } diff --git a/contracts/optimism/RebasableAndNonRebasableTokens.sol b/contracts/optimism/RebasableAndNonRebasableTokens.sol index a50ff81c..bc4cfcc9 100644 --- a/contracts/optimism/RebasableAndNonRebasableTokens.sol +++ b/contracts/optimism/RebasableAndNonRebasableTokens.sol @@ -29,6 +29,18 @@ contract RebasableAndNonRebasableTokens { address l2TokenNonRebasable_, address l2TokenRebasable_ ) { + if (l1TokenNonRebasable_ == address(0)) { + revert ErrorZeroAddressL1TokenNonRebasable(); + } + if (l1TokenRebasable_ == address(0)) { + revert ErrorZeroAddressL1TokenRebasable(); + } + if (l2TokenNonRebasable_ == address(0)) { + revert ErrorZeroAddressL2TokenNonRebasable(); + } + if (l2TokenRebasable_ == address(0)) { + revert ErrorZeroAddressL2TokenRebasable(); + } L1_TOKEN_NON_REBASABLE = l1TokenNonRebasable_; L1_TOKEN_REBASABLE = l1TokenRebasable_; L2_TOKEN_NON_REBASABLE = l2TokenNonRebasable_; @@ -71,6 +83,10 @@ contract RebasableAndNonRebasableTokens { _; } + error ErrorZeroAddressL1TokenNonRebasable(); + error ErrorZeroAddressL1TokenRebasable(); + error ErrorZeroAddressL2TokenNonRebasable(); + error ErrorZeroAddressL2TokenRebasable(); error ErrorUnsupportedL2Token(address l2Token); error ErrorUnsupportedL1L2TokensPair(address l1Token, address l2Token); error ErrorAccountIsZeroAddress(); diff --git a/contracts/optimism/TokenRateAndUpdateTimestampProvider.sol b/contracts/optimism/TokenRateAndUpdateTimestampProvider.sol index 53674b42..33334c62 100644 --- a/contracts/optimism/TokenRateAndUpdateTimestampProvider.sol +++ b/contracts/optimism/TokenRateAndUpdateTimestampProvider.sol @@ -43,6 +43,12 @@ abstract contract TokenRateAndUpdateTimestampProvider { uint256 public constant TOKEN_RATE_DECIMALS = 27; constructor(address wstETH_, address accountingOracle_) { + if (wstETH_ == address(0)) { + revert ErrorZeroAddressWstEth(); + } + if (accountingOracle_ == address(0)) { + revert ErrorZeroAddressAccountingOracle(); + } WSTETH = wstETH_; ACCOUNTING_ORACLE = accountingOracle_; GENESIS_TIME = IAccountingOracle(ACCOUNTING_ORACLE).GENESIS_TIME(); @@ -57,4 +63,7 @@ abstract contract TokenRateAndUpdateTimestampProvider { ACCOUNTING_ORACLE ).getLastProcessingRefSlot(); } + + error ErrorZeroAddressWstEth(); + error ErrorZeroAddressAccountingOracle(); } diff --git a/contracts/optimism/TokenRateOracle.sol b/contracts/optimism/TokenRateOracle.sol index 11753472..3de656e5 100644 --- a/contracts/optimism/TokenRateOracle.sol +++ b/contracts/optimism/TokenRateOracle.sol @@ -79,6 +79,12 @@ contract TokenRateOracle is CrossDomainEnabled, ITokenRateOracle, Versioned { uint256 maxAllowedL2ToL1ClockLag_, uint256 maxAllowedTokenRateDeviationPerDay_ ) CrossDomainEnabled(messenger_) { + if (l2ERC20TokenBridge_ == address(0)) { + revert ErrorZeroAddressL2ERC20TokenBridge(); + } + if (l1TokenRatePusher_ == address(0)) { + revert ErrorZeroAddressL1TokenRatePusher(); + } if (maxAllowedTokenRateDeviationPerDay_ > BASIS_POINT_SCALE) { revert ErrorMaxTokenRateDeviationIsOutOfRange(); } @@ -271,6 +277,8 @@ contract TokenRateOracle is CrossDomainEnabled, ITokenRateOracle, Versioned { event DormantTokenRateUpdateIgnored(uint256 indexed newRateL1Timestamp_, uint256 indexed currentRateL1Timestamp_); event TokenRateL1TimestampIsInFuture(uint256 tokenRate_, uint256 indexed rateL1Timestamp_); + error ErrorZeroAddressL2ERC20TokenBridge(); + error ErrorZeroAddressL1TokenRatePusher(); error ErrorNotBridgeOrTokenRatePusher(); error ErrorL1TimestampExceededAllowedClockLag(uint256 tokenRate_, uint256 rateL1Timestamp_); error ErrorTokenRateIsOutOfRange(uint256 tokenRate_, uint256 rateL1Timestamp_); diff --git a/contracts/token/ERC20Bridged.sol b/contracts/token/ERC20Bridged.sol index 47969ec8..7ec7e011 100644 --- a/contracts/token/ERC20Bridged.sol +++ b/contracts/token/ERC20Bridged.sol @@ -40,6 +40,9 @@ contract ERC20Bridged is IERC20Bridged, ERC20Core, ERC20Metadata { uint8 decimals_, address bridge_ ) ERC20Metadata(name_, symbol_, decimals_) { + if (bridge_ == address(0)) { + revert ErrorZeroAddressBridge(); + } bridge = bridge_; } @@ -69,5 +72,6 @@ contract ERC20Bridged is IERC20Bridged, ERC20Core, ERC20Metadata { _; } + error ErrorZeroAddressBridge(); error ErrorNotBridge(); } diff --git a/contracts/token/ERC20BridgedPermit.sol b/contracts/token/ERC20BridgedPermit.sol index 87536453..e856b507 100644 --- a/contracts/token/ERC20BridgedPermit.sol +++ b/contracts/token/ERC20BridgedPermit.sol @@ -56,6 +56,7 @@ contract ERC20BridgedPermit is ERC20Bridged, PermitExtension, Versioned { /// @inheritdoc PermitExtension function _permitAccepted(address owner_, address spender_, uint256 amount_) internal override { _approve(owner_, spender_, amount_); + emit Approval(owner_, spender_, amount_); } error ErrorMetadataIsNotInitialized(); diff --git a/contracts/token/ERC20Metadata.sol b/contracts/token/ERC20Metadata.sol index db916aed..b86c700f 100644 --- a/contracts/token/ERC20Metadata.sol +++ b/contracts/token/ERC20Metadata.sol @@ -44,6 +44,9 @@ contract ERC20Metadata is IERC20Metadata { string memory symbol_, uint8 decimals_ ) { + if (decimals_ == 0) { + revert ErrorZeroDecimals(); + } decimals = decimals_; _setERC20MetadataName(name_); _setERC20MetadataSymbol(symbol_); @@ -91,6 +94,7 @@ contract ERC20Metadata is IERC20Metadata { } } + error ErrorZeroDecimals(); error ErrorNameIsEmpty(); error ErrorSymbolIsEmpty(); } diff --git a/contracts/token/ERC20RebasableBridged.sol b/contracts/token/ERC20RebasableBridged.sol index 5c108d11..4637a061 100644 --- a/contracts/token/ERC20RebasableBridged.sol +++ b/contracts/token/ERC20RebasableBridged.sol @@ -72,6 +72,15 @@ contract ERC20RebasableBridged is IERC20, IERC20Wrapper, IBridgeWrapper, ERC20Me address tokenRateOracle_, address l2ERC20TokenBridge_ ) ERC20Metadata(name_, symbol_, decimals_) { + if (tokenToWrapFrom_ == address(0)) { + revert ErrorZeroAddressTokenToWrapFrom(); + } + if (tokenRateOracle_ == address(0)) { + revert ErrorZeroAddressTokenRateOracle(); + } + if (l2ERC20TokenBridge_ == address(0)) { + revert ErrorZeroAddressL2ERC20TokenBridge(); + } TOKEN_TO_WRAP_FROM = IERC20(tokenToWrapFrom_); TOKEN_RATE_ORACLE = ITokenRateOracle(tokenRateOracle_); TOKEN_RATE_ORACLE_DECIMALS = TOKEN_RATE_ORACLE.decimals(); @@ -411,6 +420,9 @@ contract ERC20RebasableBridged is IERC20, IERC20Wrapper, IBridgeWrapper, ERC20Me uint256 sharesValue ); + error ErrorZeroAddressTokenToWrapFrom(); + error ErrorZeroAddressTokenRateOracle(); + error ErrorZeroAddressL2ERC20TokenBridge(); error ErrorZeroSharesWrap(); error ErrorZeroTokensUnwrap(); error ErrorZeroSharesUnwrap(); diff --git a/contracts/token/ERC20RebasableBridgedPermit.sol b/contracts/token/ERC20RebasableBridgedPermit.sol index 1755fd17..40d75443 100644 --- a/contracts/token/ERC20RebasableBridgedPermit.sol +++ b/contracts/token/ERC20RebasableBridgedPermit.sol @@ -52,5 +52,6 @@ contract ERC20RebasableBridgedPermit is ERC20RebasableBridged, PermitExtension, /// @inheritdoc PermitExtension function _permitAccepted(address owner_, address spender_, uint256 amount_) internal override { _approve(owner_, spender_, amount_); + emit Approval(owner_, spender_, amount_); } } diff --git a/test/optimism/L1LidoTokensBridge.unit.test.ts b/test/optimism/L1LidoTokensBridge.unit.test.ts index 1453a10b..2b5aac66 100644 --- a/test/optimism/L1LidoTokensBridge.unit.test.ts +++ b/test/optimism/L1LidoTokensBridge.unit.test.ts @@ -29,6 +29,95 @@ unit("Optimism :: L1LidoTokensBridge", ctxFactory) assert.equal(await ctx.l1TokenBridge.L2_TOKEN_REBASABLE(), ctx.stubs.l2TokenRebasable.address); }) + .test("constructor() :: zero params", async (ctx) => { + + const { deployer, stranger, zero } = ctx.accounts; + + await assert.revertsWith(new L1LidoTokensBridge__factory( + deployer + ).deploy( + zero.address, + stranger.address, + stranger.address, + stranger.address, + stranger.address, + stranger.address, + stranger.address + ), "ErrorZeroAddressMessenger()"); + + await assert.revertsWith(new L1LidoTokensBridge__factory( + deployer + ).deploy( + stranger.address, + zero.address, + stranger.address, + stranger.address, + stranger.address, + stranger.address, + stranger.address + ), "ErrorZeroAddressL2Bridge()"); + + await assert.revertsWith(new L1LidoTokensBridge__factory( + deployer + ).deploy( + stranger.address, + stranger.address, + zero.address, + stranger.address, + stranger.address, + stranger.address, + stranger.address + ), "ErrorZeroAddressL1TokenNonRebasable()"); + + await assert.revertsWith(new L1LidoTokensBridge__factory( + deployer + ).deploy( + stranger.address, + stranger.address, + stranger.address, + zero.address, + stranger.address, + stranger.address, + stranger.address + ), "ErrorZeroAddressL1TokenRebasable()"); + + await assert.revertsWith(new L1LidoTokensBridge__factory( + deployer + ).deploy( + stranger.address, + stranger.address, + stranger.address, + stranger.address, + zero.address, + stranger.address, + stranger.address + ), "ErrorZeroAddressL2TokenNonRebasable()"); + + await assert.revertsWith(new L1LidoTokensBridge__factory( + deployer + ).deploy( + stranger.address, + stranger.address, + stranger.address, + stranger.address, + stranger.address, + zero.address, + stranger.address + ), "ErrorZeroAddressL2TokenRebasable()"); + + await assert.revertsWith(new L1LidoTokensBridge__factory( + deployer + ).deploy( + stranger.address, + stranger.address, + stranger.address, + stranger.address, + stranger.address, + stranger.address, + zero.address, + ), "ErrorZeroAddressAccountingOracle()"); + }) + .test("initialize() :: petrified", async (ctx) => { const { deployer, l2TokenBridgeEOA } = ctx.accounts; const { @@ -1133,6 +1222,7 @@ unit("Optimism :: L1LidoTokensBridge", ctxFactory) async function ctxFactory() { const [deployer, l2TokenBridgeEOA, stranger, recipient] = await hre.ethers.getSigners(); + const zero = await hre.ethers.getSigner(hre.ethers.constants.AddressZero); const provider = await hre.ethers.provider; const decimals = BigNumber.from(27); @@ -1183,6 +1273,7 @@ async function ctxFactory() { emptyContractAsEOA, recipient, l1MessengerStubAsEOA, + zero }, stubs: { l1TokenNonRebasable: l1TokenNonRebasableStub, diff --git a/test/optimism/L2ERC20ExtendedTokensBridge.unit.test.ts b/test/optimism/L2ERC20ExtendedTokensBridge.unit.test.ts index c87d9aad..954a97df 100644 --- a/test/optimism/L2ERC20ExtendedTokensBridge.unit.test.ts +++ b/test/optimism/L2ERC20ExtendedTokensBridge.unit.test.ts @@ -28,6 +28,78 @@ import { } from "../../typechain"; unit("Optimism:: L2ERC20ExtendedTokensBridge", ctxFactory) + +.test("constructor() :: zero params", async (ctx) => { + + const { deployer, stranger, zero } = ctx.accounts; + + await assert.revertsWith(new L2ERC20ExtendedTokensBridge__factory( + deployer + ).deploy( + zero.address, + stranger.address, + stranger.address, + stranger.address, + stranger.address, + stranger.address + ), "ErrorZeroAddressMessenger()"); + + await assert.revertsWith(new L2ERC20ExtendedTokensBridge__factory( + deployer + ).deploy( + stranger.address, + zero.address, + stranger.address, + stranger.address, + stranger.address, + stranger.address + ), "ErrorZeroAddressL1Bridge()"); + + await assert.revertsWith(new L2ERC20ExtendedTokensBridge__factory( + deployer + ).deploy( + stranger.address, + stranger.address, + zero.address, + stranger.address, + stranger.address, + stranger.address + ), "ErrorZeroAddressL1TokenNonRebasable()"); + + await assert.revertsWith(new L2ERC20ExtendedTokensBridge__factory( + deployer + ).deploy( + stranger.address, + stranger.address, + stranger.address, + zero.address, + stranger.address, + stranger.address + ), "ErrorZeroAddressL1TokenRebasable()"); + + await assert.revertsWith(new L2ERC20ExtendedTokensBridge__factory( + deployer + ).deploy( + stranger.address, + stranger.address, + stranger.address, + stranger.address, + zero.address, + stranger.address, + ), "ErrorZeroAddressL2TokenNonRebasable()"); + + await assert.revertsWith(new L2ERC20ExtendedTokensBridge__factory( + deployer + ).deploy( + stranger.address, + stranger.address, + stranger.address, + stranger.address, + stranger.address, + zero.address, + ), "ErrorZeroAddressL2TokenRebasable()"); +}) + .test("initial state", async (ctx) => { const { accounts: { l1TokenBridgeEOA, l2MessengerStubEOA }, @@ -1145,6 +1217,7 @@ unit("Optimism:: L2ERC20ExtendedTokensBridge", ctxFactory) async function ctxFactory() { const [deployer, stranger, recipient, l1TokenBridgeEOA] = await hre.ethers.getSigners(); + const zero = await hre.ethers.getSigner(hre.ethers.constants.AddressZero); const tokenDecimals = 18; const tokenRateDecimals = BigNumber.from(27); @@ -1278,6 +1351,7 @@ async function ctxFactory() { accounts: { deployer, stranger, + zero, recipient, l2MessengerStubEOA, emptyContractEOA, diff --git a/test/optimism/OpStackTokenRatePusher.unit.test.ts b/test/optimism/OpStackTokenRatePusher.unit.test.ts index c3c3ae86..b32d690f 100644 --- a/test/optimism/OpStackTokenRatePusher.unit.test.ts +++ b/test/optimism/OpStackTokenRatePusher.unit.test.ts @@ -1,4 +1,4 @@ -import { ethers } from "hardhat"; +import hre, { ethers } from "hardhat"; import { assert } from "chai"; import { BigNumber } from 'ethers' import { unit } from "../../utils/testing"; @@ -17,6 +17,53 @@ import { unit("OpStackTokenRatePusher", ctxFactory) + .test("constructor() :: zero params", async (ctx) => { + + const { deployer, stranger, zero } = ctx.accounts; + + const accountingOracle = await new AccountingOracleStub__factory(deployer).deploy(1,2,3); + + await assert.revertsWith(new OpStackTokenRatePusher__factory( + deployer + ).deploy( + zero.address, + stranger.address, + accountingOracle.address, + stranger.address, + 10 + ), "ErrorZeroAddressMessenger()"); + + await assert.revertsWith(new OpStackTokenRatePusher__factory( + deployer + ).deploy( + stranger.address, + zero.address, + accountingOracle.address, + stranger.address, + 10 + ), "ErrorZeroAddressWstEth()"); + + await assert.revertsWith(new OpStackTokenRatePusher__factory( + deployer + ).deploy( + stranger.address, + stranger.address, + zero.address, + stranger.address, + 10 + ), "ErrorZeroAddressAccountingOracle()"); + + await assert.revertsWith(new OpStackTokenRatePusher__factory( + deployer + ).deploy( + stranger.address, + stranger.address, + accountingOracle.address, + zero.address, + 10 + ), "ErrorZeroAddressTokenRateOracle()"); + }) + .test("initial state", async (ctx) => { const { tokenRateOracle } = ctx.accounts; const { opStackTokenRatePusher, l1MessengerStub, accountingOracle } = ctx.contracts; @@ -61,6 +108,8 @@ async function ctxFactory() { /// constants /// --------------------------- const [deployer, bridge, stranger, tokenRateOracle, l1TokenBridgeEOA] = await ethers.getSigners(); + const zero = await hre.ethers.getSigner(hre.ethers.constants.AddressZero); + const totalPooledEther = BigNumber.from('9309904612343950493629678'); const totalShares = BigNumber.from('7975822843597609202337218'); const tokenRateDecimals = BigNumber.from(27); @@ -107,8 +156,8 @@ async function ctxFactory() { ); return { - accounts: { deployer, bridge, stranger, tokenRateOracle }, + accounts: { deployer, bridge, stranger, zero, tokenRateOracle }, contracts: { opStackTokenRatePusher, l1MessengerStub, l1TokenNonRebasableStub, accountingOracle }, - constants: { l2GasLimitForPushingTokenRate, tokenRate, updateRateTime, genesisTime, secondsPerSlot, lastProcessingRefSlot } + constants: { l2GasLimitForPushingTokenRate, tokenRate, updateRateTime, genesisTime, secondsPerSlot, lastProcessingRefSlot } }; } diff --git a/test/optimism/TokenRateOracle.unit.test.ts b/test/optimism/TokenRateOracle.unit.test.ts index 650802fc..af01d3e2 100644 --- a/test/optimism/TokenRateOracle.unit.test.ts +++ b/test/optimism/TokenRateOracle.unit.test.ts @@ -8,6 +8,45 @@ import { getContractTransactionTimestamp, getBlockTimestamp } from "../../utils/ import { TokenRateOracle__factory, CrossDomainMessengerStub__factory } from "../../typechain"; unit("TokenRateOracle", ctxFactory) + + .test("constructor() :: zero params", async (ctx) => { + + const { deployer, stranger, zero } = ctx.accounts; + + await assert.revertsWith(new TokenRateOracle__factory( + deployer + ).deploy( + zero.address, + stranger.address, + stranger.address, + 0, + 0, + 0 + ), "ErrorZeroAddressMessenger()"); + + await assert.revertsWith(new TokenRateOracle__factory( + deployer + ).deploy( + stranger.address, + zero.address, + stranger.address, + 0, + 0, + 0 + ), "ErrorZeroAddressL2ERC20TokenBridge()"); + + await assert.revertsWith(new TokenRateOracle__factory( + deployer + ).deploy( + stranger.address, + stranger.address, + zero.address, + 0, + 0, + 0 + ), "ErrorZeroAddressL1TokenRatePusher()"); + }) + .test("state after init", async (ctx) => { const { tokenRateOracle, l2MessengerStub } = ctx.contracts; const { bridge, l1TokenBridgeEOA } = ctx.accounts; @@ -321,7 +360,7 @@ unit("TokenRateOracle", ctxFactory) deployer ).deploy({ value: wei.toBigNumber(wei`1 ether`) }); - const {tokenRateOracle, blockTimestampOfDeployment } = await tokenRateOracleUnderProxy( + const { tokenRateOracle, blockTimestampOfDeployment } = await tokenRateOracleUnderProxy( deployer, l2MessengerStub.address, bridge.address, @@ -435,6 +474,7 @@ async function ctxFactory() { const maxAllowedL2ToL1ClockLag = BigNumber.from(86400 * 2); // 2 days const maxAllowedTokenRateDeviationPerDay = BigNumber.from(500); // 5% const [deployer, bridge, stranger, l1TokenBridgeEOA] = await hre.ethers.getSigners(); + const zero = await hre.ethers.getSigner(hre.ethers.constants.AddressZero); const l2MessengerStub = await new CrossDomainMessengerStub__factory(deployer) .deploy({ value: wei.toBigNumber(wei`1 ether`) }); @@ -461,6 +501,7 @@ async function ctxFactory() { accounts: { deployer, bridge, + zero, stranger, l1TokenBridgeEOA, l2MessengerStubEOA diff --git a/test/token/ERC20BridgedPermit.unit.test.ts b/test/token/ERC20BridgedPermit.unit.test.ts index b619564d..98877f73 100644 --- a/test/token/ERC20BridgedPermit.unit.test.ts +++ b/test/token/ERC20BridgedPermit.unit.test.ts @@ -11,6 +11,31 @@ import { } from "../../typechain"; unit("ERC20BridgedPermit", ctxFactory) + + .test("constructor() :: zero params", async (ctx) => { + const { deployer, stranger, zero } = ctx.accounts; + + await assert.revertsWith(new ERC20BridgedPermit__factory( + deployer + ).deploy( + "name", + "symbol", + "version", + 0, + stranger.address + ), "ErrorZeroDecimals()"); + + await assert.revertsWith(new ERC20BridgedPermit__factory( + deployer + ).deploy( + "name", + "symbol", + "version", + 18, + zero.address + ), "ErrorZeroAddressBridge()"); + }) + .test("initial state", async (ctx) => { const { erc20Bridged } = ctx; const { decimals, name, symbol, version, premint } = ctx.constants; @@ -379,13 +404,6 @@ unit("ERC20BridgedPermit", ctxFactory) .connect(spender) .transferFrom(holder.address, recipient.address, amount); - // validate Approval event was emitted - await assert.emits(erc20Bridged, tx, "Approval", [ - holder.address, - spender.address, - wei.toBigNumber(initialAllowance).sub(amount), - ]); - // validate Transfer event was emitted await assert.emits(erc20Bridged, tx, "Transfer", [ holder.address, diff --git a/test/token/ERC20RebasableBridgedPermit.unit.test.ts b/test/token/ERC20RebasableBridgedPermit.unit.test.ts index f065635a..7acb0a61 100644 --- a/test/token/ERC20RebasableBridgedPermit.unit.test.ts +++ b/test/token/ERC20RebasableBridgedPermit.unit.test.ts @@ -17,6 +17,80 @@ import { unit("ERC20RebasableBridgedPermit", ctxFactory) + .test("constructor() :: zero params", async (ctx) => { + const { + deployer, + stranger, + zero, + owner, + messenger, + l1TokenRatePusher + } = ctx.accounts; + + const { + tokenRateOutdatedDelay, + maxAllowedL2ToL1ClockLag, + maxAllowedTokenRateDeviationPerDay + } = ctx.constants; + + const tokenRateOracle = await new TokenRateOracle__factory(deployer).deploy( + messenger.address, + owner.address, + l1TokenRatePusher.address, + tokenRateOutdatedDelay, + maxAllowedL2ToL1ClockLag, + maxAllowedTokenRateDeviationPerDay + ); + + await assert.revertsWith(new ERC20RebasableBridgedPermit__factory( + deployer + ).deploy( + "name", + "symbol", + "version", + 0, + stranger.address, + tokenRateOracle.address, + stranger.address + ), "ErrorZeroDecimals()"); + + await assert.revertsWith(new ERC20RebasableBridgedPermit__factory( + deployer + ).deploy( + "name", + "symbol", + "version", + 18, + zero.address, + tokenRateOracle.address, + stranger.address, + ), "ErrorZeroAddressTokenToWrapFrom()"); + + await assert.revertsWith(new ERC20RebasableBridgedPermit__factory( + deployer + ).deploy( + "name", + "symbol", + "version", + 18, + stranger.address, + zero.address, + stranger.address, + ), "ErrorZeroAddressTokenRateOracle()"); + + await assert.revertsWith(new ERC20RebasableBridgedPermit__factory( + deployer + ).deploy( + "name", + "symbol", + "version", + 18, + stranger.address, + tokenRateOracle.address, + zero.address, + ), "ErrorZeroAddressL2ERC20TokenBridge()"); + }) + .test("initial state", async (ctx) => { const { rebasableProxied, wrappedToken, tokenRateOracle } = ctx.contracts; const { name, symbol, version, decimals } = ctx.constants; @@ -33,7 +107,7 @@ unit("ERC20RebasableBridgedPermit", ctxFactory) }) .test("initialize() :: petrified version", async (ctx) => { - const { deployer, owner, zero } = ctx.accounts; + const { deployer, owner, zero, messenger, l1TokenRatePusher } = ctx.accounts; const { decimals } = ctx.constants; // deploy new implementation @@ -45,9 +119,9 @@ unit("ERC20RebasableBridgedPermit", ctxFactory) owner.address ); const tokenRateOracle = await new TokenRateOracle__factory(deployer).deploy( - zero.address, + messenger.address, owner.address, - zero.address, + l1TokenRatePusher.address, 86400, 86400, 500 @@ -72,7 +146,7 @@ unit("ERC20RebasableBridgedPermit", ctxFactory) }) .test("initialize() :: don't allow to initialize with empty metadata", async (ctx) => { - const { deployer, owner, zero } = ctx.accounts; + const { deployer, owner, zero, messenger, l1TokenRatePusher } = ctx.accounts; const { decimals, name, symbol, version } = ctx.constants; // deploy new implementation @@ -84,9 +158,9 @@ unit("ERC20RebasableBridgedPermit", ctxFactory) owner.address ); const tokenRateOracle = await new TokenRateOracle__factory(deployer).deploy( - zero.address, + messenger.address, owner.address, - zero.address, + l1TokenRatePusher.address, 86400, 86400, 500 @@ -128,7 +202,7 @@ unit("ERC20RebasableBridgedPermit", ctxFactory) }) .test("initialize() :: don't allow to initialize twice", async (ctx) => { - const { deployer, owner, zero, holder } = ctx.accounts; + const { deployer, owner, zero, holder, messenger, l1TokenRatePusher } = ctx.accounts; const { decimals, name, symbol, version } = ctx.constants; // deploy new implementation @@ -140,9 +214,9 @@ unit("ERC20RebasableBridgedPermit", ctxFactory) owner.address ); const tokenRateOracle = await new TokenRateOracle__factory(deployer).deploy( - zero.address, + messenger.address, owner.address, - zero.address, + l1TokenRatePusher.address, 86400, 86400, 500 @@ -197,7 +271,7 @@ unit("ERC20RebasableBridgedPermit", ctxFactory) .test("wrap() :: wrong oracle update time", async (ctx) => { - const { deployer, user1, owner, zero } = ctx.accounts; + const { deployer, user1, owner, zero, messenger, l1TokenRatePusher } = ctx.accounts; const { decimals } = ctx.constants; // deploy new implementation to test initial oracle state @@ -209,9 +283,9 @@ unit("ERC20RebasableBridgedPermit", ctxFactory) owner.address ); const tokenRateOracle = await new TokenRateOracle__factory(deployer).deploy( - zero.address, + messenger.address, owner.address, - zero.address, + l1TokenRatePusher.address, 86400, 86400, 500 @@ -310,7 +384,7 @@ unit("ERC20RebasableBridgedPermit", ctxFactory) .test("unwrap() :: with wrong oracle update time", async (ctx) => { - const { deployer, user1, owner, zero } = ctx.accounts; + const { deployer, user1, owner, zero, messenger, l1TokenRatePusher } = ctx.accounts; const { decimals } = ctx.constants; // deploy new implementation to test initial oracle state @@ -322,9 +396,9 @@ unit("ERC20RebasableBridgedPermit", ctxFactory) owner.address ); const tokenRateOracle = await new TokenRateOracle__factory(deployer).deploy( - zero.address, + messenger.address, owner.address, - zero.address, + l1TokenRatePusher.address, 86400, 86400, 500 @@ -357,7 +431,7 @@ unit("ERC20RebasableBridgedPermit", ctxFactory) const { user1, owner, zero } = ctx.accounts; const { tokenRate, tenPowDecimals } = ctx.constants; - const user1SharesToWrap = BigNumber.from(10).pow(30) ; + const user1SharesToWrap = BigNumber.from(10).pow(30); const user1TokensToUnwrap = BigNumber.from('764035550674393190'); const user1SharesToUnwrap = (user1TokensToUnwrap).mul(tenPowDecimals).div(BigNumber.from(tokenRate)); @@ -493,7 +567,7 @@ unit("ERC20RebasableBridgedPermit", ctxFactory) }) .test("bridgeWrap() :: wrong oracle update time", async (ctx) => { - const { deployer, user1, owner, zero } = ctx.accounts; + const { deployer, user1, owner, zero, messenger, l1TokenRatePusher } = ctx.accounts; const { decimals } = ctx.constants; // deploy new implementation to test initial oracle state @@ -505,9 +579,9 @@ unit("ERC20RebasableBridgedPermit", ctxFactory) owner.address ); const tokenRateOracle = await new TokenRateOracle__factory(deployer).deploy( - zero.address, + messenger.address, owner.address, - zero.address, + l1TokenRatePusher.address, 86400, 86400, 500 @@ -623,7 +697,7 @@ unit("ERC20RebasableBridgedPermit", ctxFactory) .test("bridgeUnwrap() :: with wrong oracle update time", async (ctx) => { - const { deployer, user1, owner, zero } = ctx.accounts; + const { deployer, user1, owner, zero, messenger, l1TokenRatePusher } = ctx.accounts; const { decimals } = ctx.constants; // deploy new implementation to test initial oracle state @@ -635,9 +709,9 @@ unit("ERC20RebasableBridgedPermit", ctxFactory) owner.address ); const tokenRateOracle = await new TokenRateOracle__factory(deployer).deploy( - zero.address, + messenger.address, owner.address, - zero.address, + l1TokenRatePusher.address, 86400, 86400, 500 @@ -721,6 +795,20 @@ unit("ERC20RebasableBridgedPermit", ctxFactory) assert.equalBN(await rebasableProxied.totalSupply(), totalSupply.add(user1Tokens).add(user2Tokens)); }) + .test("approve() :: events", async (ctx) => { + const { rebasableProxied } = ctx.contracts; + const { holder, spender } = ctx.accounts; + const amount = wei`1 ether`; + + const tx = await rebasableProxied.approve(spender.address, amount); + await assert.emits(rebasableProxied, tx, "Approval", [ + holder.address, + spender.address, + amount, + ]); + + + }) .test("approve() :: happy path", async (ctx) => { const { rebasableProxied } = ctx.contracts; const { holder, spender } = ctx.accounts; @@ -1041,7 +1129,6 @@ unit("ERC20RebasableBridgedPermit", ctxFactory) const tokensAmountToApprove = wei`2 ether`; const tokensAmountToTransfer = wei`1 ether`; - const sharesAmountToApprove = await rebasableProxied.getSharesByTokens(tokensAmountToApprove); const sharesAmountToTransfer = await rebasableProxied.getSharesByTokens(tokensAmountToTransfer); // holder sets allowance for spender @@ -1064,13 +1151,6 @@ unit("ERC20RebasableBridgedPermit", ctxFactory) .connect(spender) .transferFrom(holder.address, recipient.address, tokensAmountToTransfer); - // validate Approval event was emitted - await assert.emits(rebasableProxied, tx, "Approval", [ - holder.address, - spender.address, - wei.toBigNumber(tokensAmountToApprove).sub(tokensAmountToTransfer), - ]); - // validate Transfer event was emitted await assert.emits(rebasableProxied, tx, "Transfer", [ holder.address, @@ -1224,13 +1304,6 @@ unit("ERC20RebasableBridgedPermit", ctxFactory) .connect(spender) .transferSharesFrom(holder.address, recipient.address, sharesAmountToTransfer); - // validate Approval event was emitted - await assert.emits(rebasableProxied, tx, "Approval", [ - holder.address, - spender.address, - tokensAmountToApprove.sub(tokensAmountToTransfer), - ]); - // validate Transfer event was emitted await assert.emits(rebasableProxied, tx, "Transfer", [ holder.address, @@ -1290,7 +1363,9 @@ async function ctxFactory() { holder, stranger, user1, - user2 + user2, + messenger, + l1TokenRatePusher ] = await hre.ethers.getSigners(); const zero = await hre.ethers.getSigner(hre.ethers.constants.AddressZero); @@ -1308,9 +1383,9 @@ async function ctxFactory() { const { tokenRateOracle } = await tokenRateOracleUnderProxy( deployer, - zero.address, + messenger.address, owner.address, - zero.address, + l1TokenRatePusher.address, tokenRateOutdatedDelay, maxAllowedL2ToL1ClockLag, maxAllowedTokenRateDeviationPerDay, @@ -1352,7 +1427,9 @@ async function ctxFactory() { stranger, zero, user1, - user2 + user2, + messenger, + l1TokenRatePusher }, constants: { name, @@ -1363,7 +1440,10 @@ async function ctxFactory() { premintShares, premintTokens, tokenRate, - blockTimestamp + blockTimestamp, + tokenRateOutdatedDelay, + maxAllowedL2ToL1ClockLag, + maxAllowedTokenRateDeviationPerDay }, contracts: { rebasableProxied, From e2ec2b4cccbdf92963735e43675064c895e6ca23 Mon Sep 17 00:00:00 2001 From: Anton Kovalchuk Date: Thu, 23 May 2024 17:34:40 +0200 Subject: [PATCH 133/148] add new permit method with signature --- contracts/lib/SignatureChecker.sol | 64 ------- contracts/token/PermitExtension.sol | 71 +++++--- .../L2ERC20ExtendedTokensBridge.unit.test.ts | 9 - .../pushingTokenRate.integration.test.ts | 4 + test/token/ERC20Permit.unit.test.ts | 158 +++++++++++++++--- utils/optimism/deploymentOracle.ts | 3 +- 6 files changed, 188 insertions(+), 121 deletions(-) delete mode 100644 contracts/lib/SignatureChecker.sol diff --git a/contracts/lib/SignatureChecker.sol b/contracts/lib/SignatureChecker.sol deleted file mode 100644 index 269d4e63..00000000 --- a/contracts/lib/SignatureChecker.sol +++ /dev/null @@ -1,64 +0,0 @@ -// SPDX-FileCopyrightText: 2024 Lido -// SPDX-License-Identifier: MIT - -pragma solidity 0.8.10; - -import {ECDSA} from "./ECDSA.sol"; - -/// @dev A copy of SignatureUtils.sol library from Lido on Ethereum protocol. -/// https://github.com/lidofinance/lido-dao/blob/master/contracts/common/lib/SignatureUtils.sol -library SignatureChecker { - /** - * @dev The selector of the ERC1271's `isValidSignature(bytes32 hash, bytes signature)` function, - * serving at the same time as the magic value that the function should return upon success. - * - * See https://eips.ethereum.org/EIPS/eip-1271. - * - * bytes4(keccak256("isValidSignature(bytes32,bytes)") - */ - bytes4 internal constant ERC1271_IS_VALID_SIGNATURE_SELECTOR = 0x1626ba7e; - - /** - * @dev Checks signature validity. - * - * If the signer address doesn't contain any code, assumes that the address is externally owned - * and the signature is a ECDSA signature generated using its private key. Otherwise, issues a - * static call to the signer address to check the signature validity using the ERC-1271 standard. - */ - function isValidSignature( - address signer, - bytes32 msgHash, - uint8 v, - bytes32 r, - bytes32 s - ) internal view returns (bool) { - if (_hasCode(signer)) { - bytes memory sig = abi.encodePacked(r, s, v); - // Solidity <0.5 generates a regular CALL instruction even if the function being called - // is marked as `view`, and the only way to perform a STATICCALL is to use assembly - bytes memory data = abi.encodeWithSelector(ERC1271_IS_VALID_SIGNATURE_SELECTOR, msgHash, sig); - bytes32 retval; - /// @solidity memory-safe-assembly - assembly { - // allocate memory for storing the return value - let outDataOffset := mload(0x40) - mstore(0x40, add(outDataOffset, 32)) - // issue a static call and load the result if the call succeeded - let success := staticcall(gas(), signer, add(data, 32), mload(data), outDataOffset, 32) - if and(eq(success, 1), eq(returndatasize(), 32)) { - retval := mload(outDataOffset) - } - } - return retval == bytes32(ERC1271_IS_VALID_SIGNATURE_SELECTOR); - } else { - return ECDSA.recover(msgHash, v, r, s) == signer; - } - } - - function _hasCode(address addr) internal view returns (bool) { - uint256 size; - /// @solidity memory-safe-assembly - assembly { size := extcodesize(addr) } - return size > 0; - } -} diff --git a/contracts/token/PermitExtension.sol b/contracts/token/PermitExtension.sol index d0cb90f6..5f07831b 100644 --- a/contracts/token/PermitExtension.sol +++ b/contracts/token/PermitExtension.sol @@ -3,10 +3,10 @@ pragma solidity 0.8.10; -import {EIP712} from "@openzeppelin/contracts/utils/cryptography/draft-EIP712.sol"; import {IERC2612} from "@openzeppelin/contracts/interfaces/draft-IERC2612.sol"; +import {EIP712} from "@openzeppelin/contracts/utils/cryptography/draft-EIP712.sol"; +import {SignatureChecker} from "@openzeppelin/contracts/utils/cryptography/SignatureChecker.sol"; import {UnstructuredRefStorage} from "../lib//UnstructuredRefStorage.sol"; -import {SignatureChecker} from "../lib/SignatureChecker.sol"; /// @author arwer13, kovalgek abstract contract PermitExtension is IERC2612, EIP712 { @@ -36,35 +36,64 @@ abstract contract PermitExtension is IERC2612, EIP712 { _initializeEIP5267Metadata(name_, version_); } - /// @dev Sets `value` as the allowance of `spender` over ``owner``'s tokens, - /// given ``owner``'s signed approval. - /// - /// Requirements: - /// - /// - `spender` cannot be the zero address. - /// - `deadline` must be a timestamp in the future. - /// - `v`, `r` and `s` must be a valid `secp256k1` signature from `owner` - /// over the EIP712-formatted function arguments. - /// - the signature must use ``owner``'s current nonce (see {nonces}). - /// + /// @notice Sets `value_` as the allowance of `spender_` over `owner_`'s tokens, given `owner_`'s signed approval. + /// @param owner_ Token owner's address (Authorizer). Cannot be the zero address. + /// @param spender_ An address of the tokens spender. Cannot be the zero address. + /// @param value_ An amount of tokens to allow to spend. + /// @param deadline_ The time at which the signature expires (unix time). Must be a timestamp in the future. + /// @param v_, r_, s_ must be a valid `secp256k1` signature from `owner` + /// over the EIP712-formatted function arguments. + /// The signature must use ``owner``'s current nonce (see {nonces}). + function permit( + address owner_, + address spender_, + uint256 value_, + uint256 deadline_, + uint8 v_, + bytes32 r_, + bytes32 s_ + ) external { + _permit(owner_, spender_, value_, deadline_, abi.encodePacked(r_, s_, v_)); + } + + /// @notice Sets `value_` as the allowance of `spender_` over `owner_`'s tokens, given `owner_`'s signed approval. + /// @param owner_ Token owner's address (Authorizer). Cannot be the zero address. + /// @param spender_ An address of the tokens spender. Cannot be the zero address. + /// @param value_ An amount of tokens to allow to spend. + /// @param deadline_ The time at which the signature expires (unix time). Must be a timestamp in the future. + /// @param signature_ Unstructured bytes signature signed by an EOA wallet or a contract wallet. function permit( - address _owner, address _spender, uint256 _value, uint256 _deadline, uint8 _v, bytes32 _r, bytes32 _s + address owner_, + address spender_, + uint value_, + uint deadline_, + bytes memory signature_ ) external { - if (block.timestamp > _deadline) { + _permit(owner_, spender_, value_, deadline_, signature_); + } + + function _permit( + address owner_, + address spender_, + uint value_, + uint deadline_, + bytes memory signature_ + ) internal { + if (block.timestamp > deadline_) { revert ErrorDeadlineExpired(); } - bytes32 structHash = keccak256( - abi.encode(PERMIT_TYPEHASH, _owner, _spender, _value, _useNonce(_owner), _deadline) + bytes32 hash = _hashTypedDataV4( + keccak256( + abi.encode(PERMIT_TYPEHASH, owner_, spender_, value_, _useNonce(owner_), deadline_) + ) ); - bytes32 hash = _hashTypedDataV4(structHash); - - if (!SignatureChecker.isValidSignature(_owner, hash, _v, _r, _s)) { + if (!SignatureChecker.isValidSignatureNow(owner_, hash, signature_)) { revert ErrorInvalidSignature(); } - _permitAccepted(_owner, _spender, _value); + _permitAccepted(owner_, spender_, value_); } /// @dev Returns the current nonce for `owner`. This value must be diff --git a/test/optimism/L2ERC20ExtendedTokensBridge.unit.test.ts b/test/optimism/L2ERC20ExtendedTokensBridge.unit.test.ts index 954a97df..0812c202 100644 --- a/test/optimism/L2ERC20ExtendedTokensBridge.unit.test.ts +++ b/test/optimism/L2ERC20ExtendedTokensBridge.unit.test.ts @@ -133,15 +133,6 @@ unit("Optimism:: L2ERC20ExtendedTokensBridge", ctxFactory) ); }) - .test("initialize() :: zero address L2 bridge", async (ctx) => { - const { deployer } = ctx.accounts; - - await assert.revertsWith( - getL2TokenBridgeImpl(deployer, hre.ethers.constants.AddressZero), - "ErrorZeroAddressL1Bridge()" - ); - }) - .test("initialize() :: don't allow to initialize twice", async (ctx) => { const { deployer, l1TokenBridgeEOA } = ctx.accounts; diff --git a/test/optimism/pushingTokenRate.integration.test.ts b/test/optimism/pushingTokenRate.integration.test.ts index c579b481..c354e7aa 100644 --- a/test/optimism/pushingTokenRate.integration.test.ts +++ b/test/optimism/pushingTokenRate.integration.test.ts @@ -1,3 +1,4 @@ +import hre from "hardhat"; import { assert } from "chai"; import env from "../../utils/env"; import { wei } from "../../utils/wei"; @@ -164,10 +165,13 @@ async function ctxFactory() { lastProcessingRefSlot ); + const [l2ERC20TokenBridge] = await hre.ethers.getSigners(); + const [ethDeployScript, optDeployScript] = await deploymentOracle( networkName ).oracleDeployScript( l1Token.address, + l2ERC20TokenBridge.address, accountingOracle.address, l2GasLimitForPushingTokenRate, tokenRateOutdatedDelay, diff --git a/test/token/ERC20Permit.unit.test.ts b/test/token/ERC20Permit.unit.test.ts index 02fecb57..77f2ca11 100644 --- a/test/token/ERC20Permit.unit.test.ts +++ b/test/token/ERC20Permit.unit.test.ts @@ -1,4 +1,4 @@ -import hre from "hardhat"; +import hre, { ethers } from "hardhat"; import { assert } from "chai"; import { BigNumber } from "ethers"; import { unit, UnitTest } from "../../utils/testing"; @@ -6,6 +6,7 @@ import { wei } from "../../utils/wei"; import { makeDomainSeparator, signPermit, calculateTransferAuthorizationDigest, signEOAorEIP1271 } from "../../utils/testing/permit-helpers"; import testing from "../../utils/testing"; import { SignerWithAddress } from "@nomiclabs/hardhat-ethers/signers"; +import { BaseContract, Overrides } from "@ethersproject/contracts"; import { TokenRateOracle__factory, @@ -13,6 +14,7 @@ import { ERC20RebasableBridgedPermit__factory, ERC1271PermitSignerMock__factory, ERC20BridgedPermit__factory, + } from "../../typechain"; @@ -91,8 +93,17 @@ function permitTestsSuit(unitInstance: UnitTest) { // a third-party, Charlie (not Alice) submits the permit // TODO: handle unpredictable gas limit somehow better than setting it to a random constant - const tx = await token.connect(charlie) - .permit(owner.address, spender.address, value, deadline, v, r, s, { gasLimit: '0xffffff' }) + const tx = await permit( + token, + charlie, + owner.address, + spender.address, + value, + deadline, + v, r, s, + ctx.constants.usePermitMethodWithSignature, + { gasLimit: '0xffffff' } + ); // check that allowance is updated assert.equalBN(await token.allowance(owner.address, spender.address), BigNumber.from(value)) @@ -106,7 +117,16 @@ function permitTestsSuit(unitInstance: UnitTest) { ; ({ v, r, s } = await signPermit(owner.address, owner, spender.address, value, deadline, nonce, ctx.domainSeparator)) // submit the permit - const tx2 = await token.connect(charlie).permit(owner.address, spender.address, value, deadline, v, r, s) + const tx2 = await permit( + token, + charlie, + owner.address, + spender.address, + value, + deadline, + v, r, s, + ctx.constants.usePermitMethodWithSignature + ); // check that allowance is updated assert.equalBN(await token.allowance(owner.address, spender.address), BigNumber.from(value)) @@ -125,7 +145,8 @@ function permitTestsSuit(unitInstance: UnitTest) { // try to cheat by claiming the approved amount + 1 await assert.revertsWith( - token.connect(charlie).permit( + permit(token, + charlie, owner.address, spender.address, value + 1, // pass more than signed value @@ -133,13 +154,16 @@ function permitTestsSuit(unitInstance: UnitTest) { v, r, s, + ctx.constants.usePermitMethodWithSignature ), 'ErrorInvalidSignature()' ) // check that msg is incorrect even if claim the approved amount - 1 await assert.revertsWith( - token.connect(charlie).permit( + permit( + token, + charlie, owner.address, spender.address, value - 1, // pass less than signed @@ -147,6 +171,7 @@ function permitTestsSuit(unitInstance: UnitTest) { v, r, s, + ctx.constants.usePermitMethodWithSignature ), 'ErrorInvalidSignature()' ) @@ -165,7 +190,7 @@ function permitTestsSuit(unitInstance: UnitTest) { // try to cheat by submitting the permit that is signed by a // wrong person await assert.revertsWith( - token.connect(charlie).permit(owner.address, spender.address, value, deadline, v, r, s), + permit(token, charlie, owner.address, spender.address, value, deadline, v, r, s, ctx.constants.usePermitMethodWithSignature), 'ErrorInvalidSignature()' ) @@ -174,7 +199,7 @@ function permitTestsSuit(unitInstance: UnitTest) { // even Bob himself can't call permit with the invalid sig await assert.revertsWith( - token.connect(spenderSigner).permit(owner.address, spender.address, value, deadline, v, r, s), + permit(token, spenderSigner, owner.address, spender.address, value, deadline, v, r, s, ctx.constants.usePermitMethodWithSignature), 'ErrorInvalidSignature()' ) }) @@ -190,7 +215,7 @@ function permitTestsSuit(unitInstance: UnitTest) { // try to submit the permit that is expired await assert.revertsWith( - token.connect(charlie).permit(owner.address, spender.address, value, deadline, v, r, s, { gasLimit: '0xffffff' }), + permit(token, charlie, owner.address, spender.address, value, deadline, v, r, s, ctx.constants.usePermitMethodWithSignature, { gasLimit: '0xffffff' }), 'ErrorDeadlineExpired()' ) @@ -198,7 +223,7 @@ function permitTestsSuit(unitInstance: UnitTest) { // create a signed permit that valid for 1 minute (approximately) const deadline1min = ((await hre.ethers.provider.getBlock('latest')).timestamp + 60).toString() const { v, r, s } = await signPermit(owner.address, owner, spender.address, value, deadline1min, nonce, ctx.domainSeparator) - const tx = await token.connect(charlie).permit(owner.address, spender.address, value, deadline1min, v, r, s) + const tx = await permit(token, charlie, owner.address, spender.address, value, deadline1min, v, r, s, ctx.constants.usePermitMethodWithSignature) assert.equalBN(await token.nonces(owner.address), BigNumber.from(1)) assert.emits(token, tx, 'Approval', [owner, spender, BigNumber.from(value)]) @@ -217,7 +242,7 @@ function permitTestsSuit(unitInstance: UnitTest) { // try to submit the permit await assert.revertsWith( - token.connect(charlie).permit(owner.address, spender.address, value, deadline, v, r, s), + permit(token, charlie, owner.address, spender.address, value, deadline, v, r, s, ctx.constants.usePermitMethodWithSignature), 'ErrorInvalidSignature()' ) }) @@ -230,11 +255,11 @@ function permitTestsSuit(unitInstance: UnitTest) { const { v, r, s } = await signPermit(owner.address, owner, spender.address, value, deadline, nonce, ctx.domainSeparator) // submit the permit - await token.connect(charlie).permit(owner.address, spender.address, value, deadline, v, r, s) + await permit(token, charlie, owner.address, spender.address, value, deadline, v, r, s, ctx.constants.usePermitMethodWithSignature) // try to submit the permit again await assert.revertsWith( - token.connect(charlie).permit(owner.address, spender.address, value, deadline, v, r, s), + permit(token, charlie, owner.address, spender.address, value, deadline, v, r, s, ctx.constants.usePermitMethodWithSignature), 'ErrorInvalidSignature()' ) @@ -243,7 +268,7 @@ function permitTestsSuit(unitInstance: UnitTest) { // try to submit the permit again from Alice herself await assert.revertsWith( - token.connect(await hre.ethers.getSigner(owner.address)).permit(owner.address, spender.address, value, deadline, v, r, s), + permit(token, await hre.ethers.getSigner(owner.address), owner.address, spender.address, value, deadline, v, r, s, ctx.constants.usePermitMethodWithSignature), 'ErrorInvalidSignature()' ) }) @@ -253,10 +278,10 @@ function permitTestsSuit(unitInstance: UnitTest) { const { owner, spender, value, nonce, deadline } = ctx.permitParams const charlie = ctx.accounts.user2 // create a signed permit - const permit = await signPermit(owner.address, owner, spender.address, value, deadline, nonce, ctx.domainSeparator) + const permit0 = await signPermit(owner.address, owner, spender.address, value, deadline, nonce, ctx.domainSeparator) // submit the permit - await token.connect(charlie).permit(owner.address, spender.address, value, deadline, permit.v, permit.r, permit.s) + await permit(token, charlie, owner.address, spender.address, value, deadline, permit0.v, permit0.r, permit0.s, ctx.constants.usePermitMethodWithSignature) // create another signed permit with the same nonce, but // with different parameters @@ -264,7 +289,7 @@ function permitTestsSuit(unitInstance: UnitTest) { // try to submit the permit again await assert.revertsWith( - token.connect(charlie).permit(owner.address, spender.address, 1e6, deadline, permit2.v, permit2.r, permit2.s), + permit(token, charlie, owner.address, spender.address, 1e6, deadline, permit2.v, permit2.r, permit2.s, ctx.constants.usePermitMethodWithSignature), 'ErrorInvalidSignature()' ) }) @@ -280,7 +305,7 @@ function permitTestsSuit(unitInstance: UnitTest) { // try to submit the permit with invalid approval parameters await assert.revertsWith( - token.connect(charlie).permit(owner.address, spender, value, deadline, v, r, s), + permit(token, charlie, owner.address, spender, value, deadline, v, r, s, ctx.constants.usePermitMethodWithSignature), 'ErrorAccountIsZeroAddress()' ) }) @@ -305,7 +330,7 @@ function permitTestsSuit(unitInstance: UnitTest) { // try to submit the transfer permit await assert.revertsWith( - token.connect(charlie).permit(from.address, to.address, value, validBefore, v, r, s), + permit(token, charlie, from.address, to.address, value, validBefore, v, r, s, ctx.constants.usePermitMethodWithSignature), 'ErrorInvalidSignature()' ) }) @@ -317,6 +342,7 @@ function ctxFactoryFactory( name: string, symbol: string, isRebasable: boolean, + usePermitMethodWithSignature: boolean, signingAccountsFuncFactory: typeof getAccountsEIP1271 | typeof getAccountsEOA ) { return async () => { @@ -335,6 +361,8 @@ function ctxFactoryFactory( stranger, user1, user2, + messenger, + l1TokenRatePusher ] = await hre.ethers.getSigners(); await hre.network.provider.request({ @@ -350,6 +378,8 @@ function ctxFactoryFactory( decimalsToSet, tokenRate, isRebasable, + messenger.address, + l1TokenRatePusher.address, owner, deployer, holder @@ -359,7 +389,7 @@ function ctxFactoryFactory( return { accounts: { deployer, owner, recipient, spender, holder, stranger, zero, user1, user2 }, - constants: { name, symbol, decimalsToSet, decimals, premintShares, premintTokens, tokenRate }, + constants: { name, symbol, decimalsToSet, decimals, premintShares, premintTokens, tokenRate, usePermitMethodWithSignature }, contracts: { rebasableProxied }, permitParams: { owner: alice, @@ -379,6 +409,8 @@ async function tokenProxied( decimalsToSet: number, tokenRate: BigNumber, isRebasable: boolean, + messenger: string, + l1TokenRatePusher: string, owner: SignerWithAddress, deployer: SignerWithAddress, holder: SignerWithAddress) { @@ -392,9 +424,9 @@ async function tokenProxied( owner.address ); const tokenRateOracleImpl = await new TokenRateOracle__factory(deployer).deploy( - hre.ethers.constants.AddressZero, + messenger, owner.address, - hre.ethers.constants.AddressZero, + l1TokenRatePusher, 86400, 86400, 500 @@ -473,46 +505,120 @@ async function tokenProxied( return nonRebasableProxied; } +/// permit with signature parameter permitTestsSuit( - unit("ERC20RebasableBridgedPermit with EIP1271 (contract) signing", + unit("ERC20RebasableBridgedPermit with EIP1271 (contract) signing. Uses permit function with signature parameter.", ctxFactoryFactory( "Liquid staked Ether 2.0", "stETH", true, + true, getAccountsEIP1271 ) ) ); permitTestsSuit( - unit("ERC20RebasableBridgedPermit with ECDSA (EOA) signing", + unit("ERC20RebasableBridgedPermit with ECDSA (EOA) signing. Uses permit function with signature parameter.", ctxFactoryFactory( "Liquid staked Ether 2.0", "stETH", true, + true, getAccountsEOA ) ) ); permitTestsSuit( - unit("ERC20BridgedPermit with EIP1271 (contract) signing", + unit("ERC20BridgedPermit with EIP1271 (contract) signing. Uses permit function with signature parameter.", ctxFactoryFactory( "Wrapped liquid staked Ether 2.0", "wstETH", false, + true, getAccountsEIP1271 ) ) ); permitTestsSuit( - unit("ERC20BridgedPermit with ECDSA (EOA) signing", + unit("ERC20BridgedPermit with ECDSA (EOA) signing. Uses permit function with signature parameter.", ctxFactoryFactory( "Wrapped liquid staked Ether 2.0", "WstETH", false, + true, getAccountsEOA ) ) ); + +/// permit with v,r,s params +permitTestsSuit( + unit("ERC20RebasableBridgedPermit with EIP1271 (contract) signing. Uses permit function with v,r,s params", + ctxFactoryFactory( + "Liquid staked Ether 2.0", + "stETH", + true, + false, + getAccountsEIP1271 + ) + ) +); + +permitTestsSuit( + unit("ERC20RebasableBridgedPermit with ECDSA (EOA) signing. Uses permit function with v,r,s params", + ctxFactoryFactory( + "Liquid staked Ether 2.0", + "stETH", + true, + false, + getAccountsEOA + ) + ) +); + +permitTestsSuit( + unit("ERC20BridgedPermit with EIP1271 (contract) signing. Uses permit function with v,r,s params", + ctxFactoryFactory( + "Wrapped liquid staked Ether 2.0", + "wstETH", + false, + false, + getAccountsEIP1271 + ) + ) +); + +permitTestsSuit( + unit("ERC20BridgedPermit with ECDSA (EOA) signing. Uses permit function with v,r,s params", + ctxFactoryFactory( + "Wrapped liquid staked Ether 2.0", + "WstETH", + false, + false, + getAccountsEOA + ) + ) +); + +async function permit( + token: BaseContract, + signer: SignerWithAddress, + owner: string, + spender: string, + value: number, + deadline: string, + v: string | number, + r: string, + s: string, + functionWithSignature: boolean, + overrides: Overrides & { from?: string | Promise} = {}) { + if(functionWithSignature) { + const signature = ethers.utils.solidityPack(["bytes32", "bytes32", "uint8"], [r, s, v]) + return await token.connect(signer)["permit(address,address,uint256,uint256,bytes)"](owner, spender, value, deadline, signature, overrides); + } else { + return await token.connect(signer)["permit(address,address,uint256,uint256,uint8,bytes32,bytes32)"](owner, spender, value, deadline, v, r, s, overrides); + } +} diff --git a/utils/optimism/deploymentOracle.ts b/utils/optimism/deploymentOracle.ts index f58fccc9..3c8da04d 100644 --- a/utils/optimism/deploymentOracle.ts +++ b/utils/optimism/deploymentOracle.ts @@ -63,6 +63,7 @@ export default function deploymentOracle( return { async oracleDeployScript( l1Token: string, + l2ERC20TokenBridge: string, accountingOracle: string, l2GasLimitForPushingTokenRate: number, tokenRateOutdatedDelay: number, @@ -119,7 +120,7 @@ export default function deploymentOracle( factory: TokenRateOracle__factory, args: [ optAddresses.L2CrossDomainMessenger, - ethers.constants.AddressZero, + l2ERC20TokenBridge, expectedL1OpStackTokenRatePusherImplAddress, tokenRateOutdatedDelay, l2Params.tokenRateOracle.maxAllowedL2ToL1ClockLag, From 895886858219112e864c205c6b0049890e1797d0 Mon Sep 17 00:00:00 2001 From: Anton Kovalchuk Date: Mon, 27 May 2024 11:42:17 +0200 Subject: [PATCH 134/148] remove unused library --- contracts/lib/ECDSA.sol | 41 ----------------------------------------- 1 file changed, 41 deletions(-) delete mode 100644 contracts/lib/ECDSA.sol diff --git a/contracts/lib/ECDSA.sol b/contracts/lib/ECDSA.sol deleted file mode 100644 index daa0f927..00000000 --- a/contracts/lib/ECDSA.sol +++ /dev/null @@ -1,41 +0,0 @@ -// SPDX-FileCopyrightText: 2024 Lido -// SPDX-License-Identifier: MIT - -pragma solidity 0.8.10; - -/// @dev Extracted from: https://github.com/OpenZeppelin/openzeppelin-contracts/blob/v3.4.0/contracts/cryptography/ECDSA.sol#L53 -/// Also it is used in Lido on Ethereum protocol: https://github.com/lidofinance/lido-dao/blob/master/contracts/common/lib/ECDSA.sol -library ECDSA { - /** - * @dev Returns the address that signed a hashed message (`hash`). - * This address can then be used for verification purposes. - * Receives the `v`, `r` and `s` signature fields separately. - * - * The `ecrecover` EVM opcode allows for malleable (non-unique) signatures: - * this function rejects them by requiring the `s` value to be in the lower - * half order, and the `v` value to be either 27 or 28. - * - * IMPORTANT: `hash` _must_ be the result of a hash operation for the - * verification to be secure: it is possible to craft signatures that - * recover to arbitrary addresses for non-hashed data. - */ - function recover(bytes32 hash, uint8 v, bytes32 r, bytes32 s) internal pure returns (address) - { - // EIP-2 still allows signature malleability for ecrecover(). Remove this possibility and make the signature - // unique. Appendix F in the Ethereum Yellow paper (https://ethereum.github.io/yellowpaper/paper.pdf), defines - // the valid range for s in (281): 0 < s < secp256k1n ÷ 2 + 1, and for v in (282): v ∈ {27, 28}. Most - // signatures from current libraries generate a unique signature with an s-value in the lower half order. - // - // If your library generates malleable signatures, such as s-values in the upper range, calculate a new s-value - // with 0xFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFEBAAEDCE6AF48A03BBFD25E8CD0364141 - s1 and flip v from 27 to 28 or - // vice versa. If your library also generates signatures with 0/1 for v instead 27/28, add 27 to v to accept - // these malleable signatures as well. - require(uint256(s) <= 0x7FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF5D576E7357A4501DDFE92F46681B20A0, "ECDSA: invalid signature 's' value"); - - // If the signature is valid (and not malleable), return the signer address - address signer = ecrecover(hash, v, r, s); - require(signer != address(0), "ECDSA: invalid signature"); - - return signer; - } -} From 5022c9646518aebe3699728178d76cddeccca3e3 Mon Sep 17 00:00:00 2001 From: Anton Kovalchuk Date: Mon, 27 May 2024 17:52:22 +0200 Subject: [PATCH 135/148] revert approve event changes --- contracts/token/ERC20Core.sol | 2 +- contracts/token/ERC20RebasableBridged.sol | 2 +- test/token/ERC20BridgedPermit.unit.test.ts | 7 +++++++ .../ERC20RebasableBridgedPermit.unit.test.ts | 17 +++++++++++++++-- 4 files changed, 24 insertions(+), 4 deletions(-) diff --git a/contracts/token/ERC20Core.sol b/contracts/token/ERC20Core.sol index f5588a33..db8aafb8 100644 --- a/contracts/token/ERC20Core.sol +++ b/contracts/token/ERC20Core.sol @@ -24,7 +24,6 @@ contract ERC20Core is IERC20 { returns (bool) { _approve(msg.sender, spender_, amount_); - emit Approval(msg.sender, spender_, amount_); return true; } @@ -91,6 +90,7 @@ contract ERC20Core is IERC20 { uint256 amount_ ) internal virtual onlyNonZeroAccount(owner_) onlyNonZeroAccount(spender_) { allowance[owner_][spender_] = amount_; + emit Approval(owner_, spender_, amount_); } /// @dev Creates amount_ tokens and assigns them to account_, increasing the total supply diff --git a/contracts/token/ERC20RebasableBridged.sol b/contracts/token/ERC20RebasableBridged.sol index 4637a061..230bfd5c 100644 --- a/contracts/token/ERC20RebasableBridged.sol +++ b/contracts/token/ERC20RebasableBridged.sol @@ -159,7 +159,6 @@ contract ERC20RebasableBridged is IERC20, IERC20Wrapper, IBridgeWrapper, ERC20Me /// @inheritdoc IERC20 function approve(address spender_, uint256 amount_) external returns (bool) { _approve(msg.sender, spender_, amount_); - emit Approval(msg.sender, spender_, amount_); return true; } @@ -283,6 +282,7 @@ contract ERC20RebasableBridged is IERC20, IERC20Wrapper, IBridgeWrapper, ERC20Me uint256 amount_ ) internal virtual onlyNonZeroAccount(owner_) onlyNonZeroAccount(spender_) { _getTokenAllowance()[owner_][spender_] = amount_; + emit Approval(owner_, spender_, amount_); } function _sharesOf(address account_) internal view returns (uint256) { diff --git a/test/token/ERC20BridgedPermit.unit.test.ts b/test/token/ERC20BridgedPermit.unit.test.ts index 98877f73..2062600a 100644 --- a/test/token/ERC20BridgedPermit.unit.test.ts +++ b/test/token/ERC20BridgedPermit.unit.test.ts @@ -404,6 +404,13 @@ unit("ERC20BridgedPermit", ctxFactory) .connect(spender) .transferFrom(holder.address, recipient.address, amount); + // validate Approval event was emitted + await assert.emits(erc20Bridged, tx, "Approval", [ + holder.address, + spender.address, + wei.toBigNumber(initialAllowance).sub(amount), + ]); + // validate Transfer event was emitted await assert.emits(erc20Bridged, tx, "Transfer", [ holder.address, diff --git a/test/token/ERC20RebasableBridgedPermit.unit.test.ts b/test/token/ERC20RebasableBridgedPermit.unit.test.ts index 7acb0a61..d678adf5 100644 --- a/test/token/ERC20RebasableBridgedPermit.unit.test.ts +++ b/test/token/ERC20RebasableBridgedPermit.unit.test.ts @@ -806,9 +806,8 @@ unit("ERC20RebasableBridgedPermit", ctxFactory) spender.address, amount, ]); - - }) + .test("approve() :: happy path", async (ctx) => { const { rebasableProxied } = ctx.contracts; const { holder, spender } = ctx.accounts; @@ -1151,6 +1150,13 @@ unit("ERC20RebasableBridgedPermit", ctxFactory) .connect(spender) .transferFrom(holder.address, recipient.address, tokensAmountToTransfer); + // validate Approval event was emitted + await assert.emits(rebasableProxied, tx, "Approval", [ + holder.address, + spender.address, + wei.toBigNumber(tokensAmountToApprove).sub(tokensAmountToTransfer), + ]); + // validate Transfer event was emitted await assert.emits(rebasableProxied, tx, "Transfer", [ holder.address, @@ -1304,6 +1310,13 @@ unit("ERC20RebasableBridgedPermit", ctxFactory) .connect(spender) .transferSharesFrom(holder.address, recipient.address, sharesAmountToTransfer); + // validate Approval event was emitted + await assert.emits(rebasableProxied, tx, "Approval", [ + holder.address, + spender.address, + tokensAmountToApprove.sub(tokensAmountToTransfer), + ]); + // validate Transfer event was emitted await assert.emits(rebasableProxied, tx, "Transfer", [ holder.address, From 074dee0643617054bc18597ca8f7aa63548409a6 Mon Sep 17 00:00:00 2001 From: Anton Kovalchuk Date: Mon, 27 May 2024 21:04:14 +0200 Subject: [PATCH 136/148] draft --- contracts/optimism/TokenRateOracle.sol | 144 +++++++++++++++++++------ 1 file changed, 114 insertions(+), 30 deletions(-) diff --git a/contracts/optimism/TokenRateOracle.sol b/contracts/optimism/TokenRateOracle.sol index 602f4872..512bfeb8 100644 --- a/contracts/optimism/TokenRateOracle.sol +++ b/contracts/optimism/TokenRateOracle.sol @@ -8,6 +8,8 @@ import {IChainlinkAggregatorInterface} from "./interfaces/IChainlinkAggregatorIn import {CrossDomainEnabled} from "./CrossDomainEnabled.sol"; import {Versioned} from "../utils/Versioned.sol"; import {Math} from "@openzeppelin/contracts/utils/math/Math.sol"; +import {AccessControl} from "@openzeppelin/contracts/access/AccessControl.sol"; +import {UnstructuredStorage} from "../lib/UnstructuredStorage.sol"; interface ITokenRateOracle is ITokenRateUpdatable, IChainlinkAggregatorInterface {} @@ -16,7 +18,7 @@ interface ITokenRateOracle is ITokenRateUpdatable, IChainlinkAggregatorInterface /// NB: Cross-chain apps and CEXes should fetch the token rate specific to the chain for deposits/withdrawals /// and compare against the token rate on L1 being a source of truth; /// @dev Token rate updates can be delivered from two sources: L1 token rate pusher and L2 bridge. -contract TokenRateOracle is CrossDomainEnabled, ITokenRateOracle, Versioned { +contract TokenRateOracle is ITokenRateOracle, CrossDomainEnabled, AccessControl, Versioned { /// @dev Stores the dynamic data of the oracle. Allows safely use of this /// contract with upgradable proxies @@ -61,7 +63,17 @@ contract TokenRateOracle is CrossDomainEnabled, ITokenRateOracle, Versioned { uint256 private constant BASIS_POINT_SCALE = 1e4; /// @dev Location of the slot with TokenRateData - bytes32 private constant TOKEN_RATE_DATA_SLOT = keccak256("TokenRateOracle.TOKEN_RATE_DATA_SLOT"); + bytes32 private constant TOKEN_RATES_COUNT_POSITION = keccak256("TokenRateOracle.TOKEN_RATES_COUNT_POSITION"); + + bytes32 private constant TOKEN_RATES_DATA_SLOT = keccak256("TokenRateOracle.TOKEN_RATES_DATA_SLOT"); + + bytes32 private constant PAUSE_TOKEN_RATE_UPDATES = keccak256("TokenRateOracle.PAUSE_TOKEN_RATE_UPDATES"); + + /// @dev Role granting the permission to pause updating rate. + bytes32 public constant RATE_UPDATE_DISABLER_ROLE = keccak256("TokenRateOracle.RATE_UPDATE_DISABLER_ROLE"); + + /// @dev Role granting the permission to unpause updating rate. + bytes32 private constant RATE_UPDATE_ENABLER_ROLE = keccak256("TokenRateOracle.RATE_UPDATE_ENABLER_ROLE"); /// @param messenger_ L2 messenger address being used for cross-chain communications /// @param l2ERC20TokenBridge_ the bridge address that has a right to updates oracle. @@ -95,7 +107,10 @@ contract TokenRateOracle is CrossDomainEnabled, ITokenRateOracle, Versioned { MAX_ALLOWED_TOKEN_RATE_DEVIATION_PER_DAY = maxAllowedTokenRateDeviationPerDay_; } - function initialize(uint256 tokenRate_, uint256 rateL1Timestamp_) external { + function initialize(address admin_, uint256 tokenRate_, uint256 rateL1Timestamp_) external { + if (admin_ == address(0)) { + revert ErrorZeroAddressAdmin(); + } if (tokenRate_ < MIN_ALLOWED_TOKEN_RATE || tokenRate_ > MAX_ALLOWED_TOKEN_RATE) { revert ErrorTokenRateInitializationIsOutOfAllowedRange(tokenRate_); } @@ -103,7 +118,37 @@ contract TokenRateOracle is CrossDomainEnabled, ITokenRateOracle, Versioned { revert ErrorL1TimestampInitializationIsOutOfAllowedRange(rateL1Timestamp_); } _initializeContractVersionTo(1); - _setTokenRateAndTimestamps(uint128(tokenRate_), uint64(rateL1Timestamp_)); + _grantRole(DEFAULT_ADMIN_ROLE, admin_); + _addTokenRate(tokenRate_, rateL1Timestamp_, block.timestamp); + } + + function pause(uint256 oldRateUpdateL1Timestamp_, uint256 rateIndexHint_) external onlyRole(RATE_UPDATE_DISABLER_ROLE) { + PAUSE_TOKEN_RATE_UPDATES.setStorageBool(true); + + TokenRateData storage tokenRateData = _getLastTokenRate(); + + if(tokenRateData.rateUpdateL1Timestamp == oldRateUpdateL1Timestamp_) { + _addTokenRate(tokenRateData.tokenRate, tokenRateData.rateUpdateL1Timestamp, block.timestamp); + emit RateUpdated(tokenRateData.tokenRate, tokenRateData.rateUpdateL1Timestamp); + return; + } + + uint256 tokenRatesCount = _getTokenRatesCount(); + for (uint256 idx = tokenRatesCount - 1; idx >= rateIndexHint_; idx--) { + if (_getTokenRateByIndex(idx).rateUpdateL1Timestamp == oldRateUpdateL1Timestamp_ && + _getTokenRateByIndex(idx).rateReceivedL2Timestamp > block.timestamp - 86000*3 ) { + _addTokenRate(tokenRateData.tokenRate, tokenRateData.rateUpdateL1Timestamp, block.timestamp); + emit RateUpdated(tokenRateData.tokenRate, tokenRateData.rateUpdateL1Timestamp); + return; + } + } + } + + function unpause(uint256 tokenRate_, uint256 rateUpdateL1Timestamp_) external onlyRole(RATE_UPDATE_ENABLER_ROLE) { + PAUSE_TOKEN_RATE_UPDATES.setStorageBool(false); + + _addTokenRate(tokenRate_, rateUpdateL1Timestamp_, block.timestamp); + emit RateUpdated(tokenRate_, rateUpdateL1Timestamp_); } /// @inheritdoc IChainlinkAggregatorInterface @@ -114,7 +159,7 @@ contract TokenRateOracle is CrossDomainEnabled, ITokenRateOracle, Versioned { uint256 updatedAt_, uint80 answeredInRound_ ) { - TokenRateData memory tokenRateData = _getTokenRateAndTimestamps(); + TokenRateData memory tokenRateData = _getLastTokenRate(); return ( uint80(tokenRateData.rateUpdateL1Timestamp), @@ -127,7 +172,8 @@ contract TokenRateOracle is CrossDomainEnabled, ITokenRateOracle, Versioned { /// @inheritdoc IChainlinkAggregatorInterface function latestAnswer() external view returns (int256) { - return int256(uint256(_loadTokenRateData().value.tokenRate)); + TokenRateData memory tokenRateData = _getLastTokenRate(); + return int256(uint256(tokenRateData.tokenRate)); } /// @inheritdoc IChainlinkAggregatorInterface @@ -137,7 +183,13 @@ contract TokenRateOracle is CrossDomainEnabled, ITokenRateOracle, Versioned { /// @inheritdoc ITokenRateUpdatable function updateRate(uint256 tokenRate_, uint256 rateUpdateL1Timestamp_) external onlyBridgeOrTokenRatePusher { - TokenRateData memory tokenRateData = _getTokenRateAndTimestamps(); + + if(PAUSE_TOKEN_RATE_UPDATES.getStorageBool()) { + //revert; + return; + } + + TokenRateData memory tokenRateData = _getLastTokenRate(); /// @dev checks if the clock lag (i.e, time difference) between L1 and L2 exceeds the configurable threshold if (rateUpdateL1Timestamp_ > block.timestamp + MAX_ALLOWED_L2_TO_L1_CLOCK_LAG) { @@ -154,7 +206,7 @@ contract TokenRateOracle is CrossDomainEnabled, ITokenRateOracle, Versioned { /// NB: Here we assume that the rate can only be changed together with the token rebase induced /// by the AccountingOracle report if (rateUpdateL1Timestamp_ == tokenRateData.rateUpdateL1Timestamp) { - _loadTokenRateData().value.rateReceivedL2Timestamp = uint64(block.timestamp); + _addTokenRate(tokenRateData.tokenRate, rateUpdateL1Timestamp_, block.timestamp); emit RateReceivedTimestampUpdated(block.timestamp); return; } @@ -174,8 +226,7 @@ contract TokenRateOracle is CrossDomainEnabled, ITokenRateOracle, Versioned { emit TokenRateL1TimestampIsInFuture(tokenRate_, rateUpdateL1Timestamp_); } - _setTokenRateAndTimestamps(uint128(tokenRate_), uint64(rateUpdateL1Timestamp_)); - + _addTokenRate(tokenRate_, rateUpdateL1Timestamp_, block.timestamp); emit RateUpdated(tokenRate_, rateUpdateL1Timestamp_); } @@ -240,30 +291,30 @@ contract TokenRateOracle is CrossDomainEnabled, ITokenRateOracle, Versioned { return false; } - function _setTokenRateAndTimestamps(uint128 tokenRate_, uint64 rateUpdateL1Timestamp_) internal { - _loadTokenRateData().value = TokenRateData(tokenRate_, rateUpdateL1Timestamp_, uint64(block.timestamp)); - } + // function _setTokenRateAndTimestamps(uint128 tokenRate_, uint64 rateUpdateL1Timestamp_) internal { + // _loadTokenRateData().value = TokenRateData(tokenRate_, rateUpdateL1Timestamp_, uint64(block.timestamp)); + // } - function _getTokenRateAndTimestamps() private view returns (TokenRateData memory tokenRateData) { - tokenRateData = _loadTokenRateData().value; - } + // function _getTokenRateAndTimestamps() private view returns (TokenRateData memory tokenRateData) { + // tokenRateData = _loadTokenRateData().value; + // } - /// @dev Storage helper for TokenRateData - struct StorageTokenRateData { - TokenRateData value; - } + // /// @dev Storage helper for TokenRateData + // struct StorageTokenRateData { + // TokenRateData value; + // } /// @dev Returns the reference to the slot with TokenRateData struct - function _loadTokenRateData() - private - pure - returns (StorageTokenRateData storage r) - { - bytes32 slot = TOKEN_RATE_DATA_SLOT; - assembly { - r.slot := slot - } - } + // function _loadTokenRateData() + // private + // pure + // returns (StorageTokenRateData storage r) + // { + // bytes32 slot = TOKEN_RATE_DATA_SLOT; + // assembly { + // r.slot := slot + // } + // } modifier onlyBridgeOrTokenRatePusher() { if (!_isCallerBridgeOrMessengerWithTokenRatePusher(msg.sender)) { @@ -272,11 +323,44 @@ contract TokenRateOracle is CrossDomainEnabled, ITokenRateOracle, Versioned { _; } + function _addTokenRate( + uint128 tokenRate_, uint64 rateUpdateL1Timestamp_, uint64 rateReceivedL2Timestamp_ + ) internal { + uint256 newTokenRateIndex = _getTokenRatesCount(); + TokenRateData storage newTokenRateData = _getTokenRateByIndex(newTokenRateIndex); + newTokenRateData.tokenRate = tokenRate_; + newTokenRateData.rateUpdateL1Timestamps = rateUpdateL1Timestamp_; + newTokenRateData.rateReceivedL2Timestamp = rateReceivedL2Timestamp_; + TOKEN_RATES_COUNT_POSITION.setStorageUint256(newTokenRateIndex + 1); + } + + function _getLastTokenRate() internal view returns (TokenRateData storage) { + uint256 tokenRatesCount = _getTokenRatesCount(); + return _getTokenRateByIndex(tokenRatesCount - 1); + } + + function _getTokenRateByIndex(uint256 _tokenRateIndex) internal view returns (TokenRateData storage) { + mapping(uint256 => TokenRateData) storage _tokenRates = _getStorageTokenRatesMapping(); + return _tokenRates[_tokenRateIndex]; + } + + function _getStorageTokenRatesMapping() internal pure returns (mapping(uint256 => TokenRateData) storage result) { + bytes32 position = TOKEN_RATES_DATA_SLOT; + assembly { + result.slot := position + } + } + + function _getTokenRatesCount() public view returns (uint256) { + return TOKEN_RATES_COUNT_POSITION.getStorageUint256(); + } + event RateUpdated(uint256 tokenRate_, uint256 indexed rateL1Timestamp_); event RateReceivedTimestampUpdated(uint256 indexed rateReceivedL2Timestamp); event DormantTokenRateUpdateIgnored(uint256 indexed newRateL1Timestamp_, uint256 indexed currentRateL1Timestamp_); event TokenRateL1TimestampIsInFuture(uint256 tokenRate_, uint256 indexed rateL1Timestamp_); + error ErrorZeroAddressAdmin(); error ErrorZeroAddressL2ERC20TokenBridge(); error ErrorZeroAddressL1TokenRatePusher(); error ErrorNotBridgeOrTokenRatePusher(); From 5ce97495f942b61dca2854abeed75d41d7c87525 Mon Sep 17 00:00:00 2001 From: Anton Kovalchuk Date: Tue, 28 May 2024 00:13:27 +0200 Subject: [PATCH 137/148] update pause/unpause logic --- contracts/optimism/TokenRateOracle.sol | 151 +++++++++++++------------ 1 file changed, 77 insertions(+), 74 deletions(-) diff --git a/contracts/optimism/TokenRateOracle.sol b/contracts/optimism/TokenRateOracle.sol index 512bfeb8..653be140 100644 --- a/contracts/optimism/TokenRateOracle.sol +++ b/contracts/optimism/TokenRateOracle.sol @@ -20,6 +20,8 @@ interface ITokenRateOracle is ITokenRateUpdatable, IChainlinkAggregatorInterface /// @dev Token rate updates can be delivered from two sources: L1 token rate pusher and L2 bridge. contract TokenRateOracle is ITokenRateOracle, CrossDomainEnabled, AccessControl, Versioned { + using UnstructuredStorage for bytes32; + /// @dev Stores the dynamic data of the oracle. Allows safely use of this /// contract with upgradable proxies struct TokenRateData { @@ -62,13 +64,15 @@ contract TokenRateOracle is ITokenRateOracle, CrossDomainEnabled, AccessControl, /// @notice Basic point scale. uint256 private constant BASIS_POINT_SCALE = 1e4; - /// @dev Location of the slot with TokenRateData - bytes32 private constant TOKEN_RATES_COUNT_POSITION = keccak256("TokenRateOracle.TOKEN_RATES_COUNT_POSITION"); - - bytes32 private constant TOKEN_RATES_DATA_SLOT = keccak256("TokenRateOracle.TOKEN_RATES_DATA_SLOT"); + /// @notice Max delta time for last updates to pause updating rate. + uint256 private constant MAX_DELTA_TIME_TO_PAUSE_TOKEN_RATE_UPDATES = 86400 * 3; + /// @notice Pause flag for updates slot position. bytes32 private constant PAUSE_TOKEN_RATE_UPDATES = keccak256("TokenRateOracle.PAUSE_TOKEN_RATE_UPDATES"); + /// @notice Token rates array slot position. + bytes32 private constant TOKEN_RATES_DATA_SLOT = keccak256("TokenRateOracle.TOKEN_RATES_DATA_SLOT"); + /// @dev Role granting the permission to pause updating rate. bytes32 public constant RATE_UPDATE_DISABLER_ROLE = keccak256("TokenRateOracle.RATE_UPDATE_DISABLER_ROLE"); @@ -119,35 +123,40 @@ contract TokenRateOracle is ITokenRateOracle, CrossDomainEnabled, AccessControl, } _initializeContractVersionTo(1); _grantRole(DEFAULT_ADMIN_ROLE, admin_); - _addTokenRate(tokenRate_, rateL1Timestamp_, block.timestamp); + _addTokenRate(uint128(tokenRate_), uint64(rateL1Timestamp_), uint64(block.timestamp)); } function pause(uint256 oldRateUpdateL1Timestamp_, uint256 rateIndexHint_) external onlyRole(RATE_UPDATE_DISABLER_ROLE) { - PAUSE_TOKEN_RATE_UPDATES.setStorageBool(true); + uint256 tokenRatesLength = _getStorageTokenRates().length; + if (tokenRatesLength == 0) { + revert ErrorNoTokenRateUpdates(); + } - TokenRateData storage tokenRateData = _getLastTokenRate(); + if (rateIndexHint_ >= tokenRatesLength) { + revert ErrorWrongIndex(); + } - if(tokenRateData.rateUpdateL1Timestamp == oldRateUpdateL1Timestamp_) { - _addTokenRate(tokenRateData.tokenRate, tokenRateData.rateUpdateL1Timestamp, block.timestamp); - emit RateUpdated(tokenRateData.tokenRate, tokenRateData.rateUpdateL1Timestamp); + TokenRateData storage lastTokenRateData = _getStorageTokenRates()[tokenRatesLength - 1]; + if (lastTokenRateData.rateUpdateL1Timestamp == oldRateUpdateL1Timestamp_) { + PAUSE_TOKEN_RATE_UPDATES.setStorageBool(true); + emit TokenRateUpdatesPaused(lastTokenRateData.tokenRate, lastTokenRateData.rateUpdateL1Timestamp); return; } - uint256 tokenRatesCount = _getTokenRatesCount(); - for (uint256 idx = tokenRatesCount - 1; idx >= rateIndexHint_; idx--) { - if (_getTokenRateByIndex(idx).rateUpdateL1Timestamp == oldRateUpdateL1Timestamp_ && - _getTokenRateByIndex(idx).rateReceivedL2Timestamp > block.timestamp - 86000*3 ) { - _addTokenRate(tokenRateData.tokenRate, tokenRateData.rateUpdateL1Timestamp, block.timestamp); - emit RateUpdated(tokenRateData.tokenRate, tokenRateData.rateUpdateL1Timestamp); - return; - } + TokenRateData storage tokenRateData = _getStorageTokenRates()[rateIndexHint_]; + if (tokenRateData.rateReceivedL2Timestamp > block.timestamp - MAX_DELTA_TIME_TO_PAUSE_TOKEN_RATE_UPDATES) { + _removeLastElements(tokenRatesLength - rateIndexHint_ - 1); + PAUSE_TOKEN_RATE_UPDATES.setStorageBool(true); + emit TokenRateUpdatesPaused(tokenRateData.tokenRate, tokenRateData.rateUpdateL1Timestamp); + } else { + revert ErrorLastTokenRateUpdateTooOld(); } } function unpause(uint256 tokenRate_, uint256 rateUpdateL1Timestamp_) external onlyRole(RATE_UPDATE_ENABLER_ROLE) { + _addTokenRate(uint128(tokenRate_), uint64(rateUpdateL1Timestamp_), uint64(block.timestamp)); PAUSE_TOKEN_RATE_UPDATES.setStorageBool(false); - - _addTokenRate(tokenRate_, rateUpdateL1Timestamp_, block.timestamp); + emit TokenRateUpdatesUnpaused(tokenRate_, rateUpdateL1Timestamp_); emit RateUpdated(tokenRate_, rateUpdateL1Timestamp_); } @@ -182,12 +191,9 @@ contract TokenRateOracle is ITokenRateOracle, CrossDomainEnabled, AccessControl, } /// @inheritdoc ITokenRateUpdatable - function updateRate(uint256 tokenRate_, uint256 rateUpdateL1Timestamp_) external onlyBridgeOrTokenRatePusher { - - if(PAUSE_TOKEN_RATE_UPDATES.getStorageBool()) { - //revert; - return; - } + function updateRate( + uint256 tokenRate_, uint256 rateUpdateL1Timestamp_ + ) external onlyBridgeOrTokenRatePusher whenNotPaused { TokenRateData memory tokenRateData = _getLastTokenRate(); @@ -206,7 +212,7 @@ contract TokenRateOracle is ITokenRateOracle, CrossDomainEnabled, AccessControl, /// NB: Here we assume that the rate can only be changed together with the token rebase induced /// by the AccountingOracle report if (rateUpdateL1Timestamp_ == tokenRateData.rateUpdateL1Timestamp) { - _addTokenRate(tokenRateData.tokenRate, rateUpdateL1Timestamp_, block.timestamp); + tokenRateData.rateReceivedL2Timestamp = uint64(block.timestamp); emit RateReceivedTimestampUpdated(block.timestamp); return; } @@ -226,13 +232,13 @@ contract TokenRateOracle is ITokenRateOracle, CrossDomainEnabled, AccessControl, emit TokenRateL1TimestampIsInFuture(tokenRate_, rateUpdateL1Timestamp_); } - _addTokenRate(tokenRate_, rateUpdateL1Timestamp_, block.timestamp); + _addTokenRate(uint128(tokenRate_), uint64(rateUpdateL1Timestamp_), uint64(block.timestamp)); emit RateUpdated(tokenRate_, rateUpdateL1Timestamp_); } /// @notice Returns flag that shows that token rate can be considered outdated. function isLikelyOutdated() external view returns (bool) { - return block.timestamp > _loadTokenRateData().value.rateReceivedL2Timestamp + TOKEN_RATE_OUTDATED_DELAY; + return block.timestamp > _getLastTokenRate().rateReceivedL2Timestamp + TOKEN_RATE_OUTDATED_DELAY; } /// @notice Allow tokenRate deviation from the previous value to be @@ -291,76 +297,73 @@ contract TokenRateOracle is ITokenRateOracle, CrossDomainEnabled, AccessControl, return false; } - // function _setTokenRateAndTimestamps(uint128 tokenRate_, uint64 rateUpdateL1Timestamp_) internal { - // _loadTokenRateData().value = TokenRateData(tokenRate_, rateUpdateL1Timestamp_, uint64(block.timestamp)); - // } - - // function _getTokenRateAndTimestamps() private view returns (TokenRateData memory tokenRateData) { - // tokenRateData = _loadTokenRateData().value; - // } - - // /// @dev Storage helper for TokenRateData - // struct StorageTokenRateData { - // TokenRateData value; - // } - - /// @dev Returns the reference to the slot with TokenRateData struct - // function _loadTokenRateData() - // private - // pure - // returns (StorageTokenRateData storage r) - // { - // bytes32 slot = TOKEN_RATE_DATA_SLOT; - // assembly { - // r.slot := slot - // } - // } - - modifier onlyBridgeOrTokenRatePusher() { - if (!_isCallerBridgeOrMessengerWithTokenRatePusher(msg.sender)) { - revert ErrorNotBridgeOrTokenRatePusher(); - } - _; - } - function _addTokenRate( uint128 tokenRate_, uint64 rateUpdateL1Timestamp_, uint64 rateReceivedL2Timestamp_ ) internal { - uint256 newTokenRateIndex = _getTokenRatesCount(); - TokenRateData storage newTokenRateData = _getTokenRateByIndex(newTokenRateIndex); - newTokenRateData.tokenRate = tokenRate_; - newTokenRateData.rateUpdateL1Timestamps = rateUpdateL1Timestamp_; - newTokenRateData.rateReceivedL2Timestamp = rateReceivedL2Timestamp_; - TOKEN_RATES_COUNT_POSITION.setStorageUint256(newTokenRateIndex + 1); + _getStorageTokenRates().push(TokenRateData({ + tokenRate: tokenRate_, + rateUpdateL1Timestamp: rateUpdateL1Timestamp_, + rateReceivedL2Timestamp: rateReceivedL2Timestamp_ + })); } function _getLastTokenRate() internal view returns (TokenRateData storage) { - uint256 tokenRatesCount = _getTokenRatesCount(); - return _getTokenRateByIndex(tokenRatesCount - 1); + return _getTokenRateByIndex(_getStorageTokenRates().length - 1); } function _getTokenRateByIndex(uint256 _tokenRateIndex) internal view returns (TokenRateData storage) { - mapping(uint256 => TokenRateData) storage _tokenRates = _getStorageTokenRatesMapping(); - return _tokenRates[_tokenRateIndex]; + return _getStorageTokenRates()[_tokenRateIndex]; } - function _getStorageTokenRatesMapping() internal pure returns (mapping(uint256 => TokenRateData) storage result) { + function _getStorageTokenRates() internal pure returns (TokenRateData [] storage result) { bytes32 position = TOKEN_RATES_DATA_SLOT; assembly { result.slot := position } } - function _getTokenRatesCount() public view returns (uint256) { - return TOKEN_RATES_COUNT_POSITION.getStorageUint256(); + function _removeLastElements(uint256 removeElementsCount_) internal { + uint256 removeElementsCount = removeElementsCount_; + while (removeElementsCount > 0) { + removeElementsCount -= 1; + _getStorageTokenRates().pop(); + } + } + + function _setPause(bool pause) internal { + PAUSE_TOKEN_RATE_UPDATES.setStorageBool(pause); + } + + function _isPaused() internal view returns (bool) { + return PAUSE_TOKEN_RATE_UPDATES.getStorageBool(); + } + + modifier whenNotPaused() { + if (PAUSE_TOKEN_RATE_UPDATES.getStorageBool()) { + revert ErrorRateUpdatePaused(); + } + _; + } + + modifier onlyBridgeOrTokenRatePusher() { + if (!_isCallerBridgeOrMessengerWithTokenRatePusher(msg.sender)) { + revert ErrorNotBridgeOrTokenRatePusher(); + } + _; } event RateUpdated(uint256 tokenRate_, uint256 indexed rateL1Timestamp_); event RateReceivedTimestampUpdated(uint256 indexed rateReceivedL2Timestamp); event DormantTokenRateUpdateIgnored(uint256 indexed newRateL1Timestamp_, uint256 indexed currentRateL1Timestamp_); event TokenRateL1TimestampIsInFuture(uint256 tokenRate_, uint256 indexed rateL1Timestamp_); + event TokenRateUpdatesPaused(uint256 tokenRate_, uint256 indexed rateL1Timestamp_); + event TokenRateUpdatesUnpaused(uint256 tokenRate_, uint256 indexed rateL1Timestamp_); error ErrorZeroAddressAdmin(); + error ErrorRateUpdatePaused(); + error ErrorNoTokenRateUpdates(); + error ErrorWrongIndex(); + error ErrorLastTokenRateUpdateTooOld(); error ErrorZeroAddressL2ERC20TokenBridge(); error ErrorZeroAddressL1TokenRatePusher(); error ErrorNotBridgeOrTokenRatePusher(); From 6ddbdb5fcf201db22449940fa4744b4075e57a58 Mon Sep 17 00:00:00 2001 From: Anton Kovalchuk Date: Tue, 28 May 2024 10:34:35 +0200 Subject: [PATCH 138/148] update comments, add external methods --- contracts/optimism/TokenRateOracle.sol | 80 ++++++++++++++++---------- 1 file changed, 50 insertions(+), 30 deletions(-) diff --git a/contracts/optimism/TokenRateOracle.sol b/contracts/optimism/TokenRateOracle.sol index 653be140..f213689b 100644 --- a/contracts/optimism/TokenRateOracle.sol +++ b/contracts/optimism/TokenRateOracle.sol @@ -22,8 +22,7 @@ contract TokenRateOracle is ITokenRateOracle, CrossDomainEnabled, AccessControl, using UnstructuredStorage for bytes32; - /// @dev Stores the dynamic data of the oracle. Allows safely use of this - /// contract with upgradable proxies + /// @dev Uses to store historical data of rate and times. struct TokenRateData { /// @notice wstETH/stETH token rate. uint128 tokenRate; @@ -67,7 +66,7 @@ contract TokenRateOracle is ITokenRateOracle, CrossDomainEnabled, AccessControl, /// @notice Max delta time for last updates to pause updating rate. uint256 private constant MAX_DELTA_TIME_TO_PAUSE_TOKEN_RATE_UPDATES = 86400 * 3; - /// @notice Pause flag for updates slot position. + /// @notice Flag to pause token rate updates slot position. bytes32 private constant PAUSE_TOKEN_RATE_UPDATES = keccak256("TokenRateOracle.PAUSE_TOKEN_RATE_UPDATES"); /// @notice Token rates array slot position. @@ -111,55 +110,76 @@ contract TokenRateOracle is ITokenRateOracle, CrossDomainEnabled, AccessControl, MAX_ALLOWED_TOKEN_RATE_DEVIATION_PER_DAY = maxAllowedTokenRateDeviationPerDay_; } - function initialize(address admin_, uint256 tokenRate_, uint256 rateL1Timestamp_) external { + /// @notice Initializes the contract from scratch. + /// @param admin_ Address of the account to grant the DEFAULT_ADMIN_ROLE + /// @param tokenRate_ wstETH/stETH token rate. + /// @param rateUpdateL1Timestamp_ L1 time when rate was updated on L1 side. + function initialize(address admin_, uint256 tokenRate_, uint256 rateUpdateL1Timestamp_) external { if (admin_ == address(0)) { revert ErrorZeroAddressAdmin(); } if (tokenRate_ < MIN_ALLOWED_TOKEN_RATE || tokenRate_ > MAX_ALLOWED_TOKEN_RATE) { revert ErrorTokenRateInitializationIsOutOfAllowedRange(tokenRate_); } - if (rateL1Timestamp_ > block.timestamp + MAX_ALLOWED_L2_TO_L1_CLOCK_LAG) { - revert ErrorL1TimestampInitializationIsOutOfAllowedRange(rateL1Timestamp_); + if (rateUpdateL1Timestamp_ > block.timestamp + MAX_ALLOWED_L2_TO_L1_CLOCK_LAG) { + revert ErrorL1TimestampInitializationIsOutOfAllowedRange(rateUpdateL1Timestamp_); } _initializeContractVersionTo(1); _grantRole(DEFAULT_ADMIN_ROLE, admin_); - _addTokenRate(uint128(tokenRate_), uint64(rateL1Timestamp_), uint64(block.timestamp)); + _addTokenRate(uint128(tokenRate_), uint64(rateUpdateL1Timestamp_), uint64(block.timestamp)); } - function pause(uint256 oldRateUpdateL1Timestamp_, uint256 rateIndexHint_) external onlyRole(RATE_UPDATE_DISABLER_ROLE) { + /// @notice Pauses token rate updates. Should be called by DAO or multisig only. + /// @param rateIndex_ The index of the token rate that applies after the pause. + /// Token Rate can't be older then MAX_DELTA_TIME_TO_PAUSE_TOKEN_RATE_UPDATES. + function pauseTokenRateUpdates(uint256 rateIndex_) external onlyRole(RATE_UPDATE_DISABLER_ROLE) { uint256 tokenRatesLength = _getStorageTokenRates().length; if (tokenRatesLength == 0) { revert ErrorNoTokenRateUpdates(); } - - if (rateIndexHint_ >= tokenRatesLength) { - revert ErrorWrongIndex(); - } - - TokenRateData storage lastTokenRateData = _getStorageTokenRates()[tokenRatesLength - 1]; - if (lastTokenRateData.rateUpdateL1Timestamp == oldRateUpdateL1Timestamp_) { - PAUSE_TOKEN_RATE_UPDATES.setStorageBool(true); - emit TokenRateUpdatesPaused(lastTokenRateData.tokenRate, lastTokenRateData.rateUpdateL1Timestamp); - return; + if (rateIndex_ >= tokenRatesLength) { + revert ErrorWrongTokenRateIndex(); } - TokenRateData storage tokenRateData = _getStorageTokenRates()[rateIndexHint_]; + TokenRateData storage tokenRateData = _getStorageTokenRates()[rateIndex_]; if (tokenRateData.rateReceivedL2Timestamp > block.timestamp - MAX_DELTA_TIME_TO_PAUSE_TOKEN_RATE_UPDATES) { - _removeLastElements(tokenRatesLength - rateIndexHint_ - 1); + _removeLastElements(tokenRatesLength - rateIndex_ - 1); PAUSE_TOKEN_RATE_UPDATES.setStorageBool(true); emit TokenRateUpdatesPaused(tokenRateData.tokenRate, tokenRateData.rateUpdateL1Timestamp); } else { - revert ErrorLastTokenRateUpdateTooOld(); + revert ErrorTokenRateUpdateTooOld(); } } - function unpause(uint256 tokenRate_, uint256 rateUpdateL1Timestamp_) external onlyRole(RATE_UPDATE_ENABLER_ROLE) { + /// @notice Unpauses token rate updates applying provided token rate. Should be called by DAO only. + /// @param tokenRate_ a new token rate that applies after unpausing. + /// @param rateUpdateL1Timestamp_ L1 time when rate was updated on L1 side. + function unpauseTokenRateUpdates( + uint256 tokenRate_, + uint256 rateUpdateL1Timestamp_ + ) external onlyRole(RATE_UPDATE_ENABLER_ROLE) { _addTokenRate(uint128(tokenRate_), uint64(rateUpdateL1Timestamp_), uint64(block.timestamp)); PAUSE_TOKEN_RATE_UPDATES.setStorageBool(false); emit TokenRateUpdatesUnpaused(tokenRate_, rateUpdateL1Timestamp_); emit RateUpdated(tokenRate_, rateUpdateL1Timestamp_); } + /// @notice Shows that token rate updates are paused or not. + function isTokenRateUpdatesPaused() external view returns (bool) { + return _isPaused(); + } + + /// @notice Returns token rate data by index. + /// @param tokenRateIndex_ an index of token rate data. + function getTokenRateByIndex(uint256 tokenRateIndex_) external view returns (TokenRateData memory) { + return _getTokenRateByIndex(tokenRateIndex_); + } + + /// @notice Returns token rates data length. + function getTokenRatesLength() external view returns (uint256) { + return _getStorageTokenRates().length; + } + /// @inheritdoc IChainlinkAggregatorInterface function latestRoundData() external view returns ( uint80 roundId_, @@ -311,8 +331,8 @@ contract TokenRateOracle is ITokenRateOracle, CrossDomainEnabled, AccessControl, return _getTokenRateByIndex(_getStorageTokenRates().length - 1); } - function _getTokenRateByIndex(uint256 _tokenRateIndex) internal view returns (TokenRateData storage) { - return _getStorageTokenRates()[_tokenRateIndex]; + function _getTokenRateByIndex(uint256 tokenRateIndex_) internal view returns (TokenRateData storage) { + return _getStorageTokenRates()[tokenRateIndex_]; } function _getStorageTokenRates() internal pure returns (TokenRateData [] storage result) { @@ -322,10 +342,10 @@ contract TokenRateOracle is ITokenRateOracle, CrossDomainEnabled, AccessControl, } } - function _removeLastElements(uint256 removeElementsCount_) internal { - uint256 removeElementsCount = removeElementsCount_; - while (removeElementsCount > 0) { - removeElementsCount -= 1; + function _removeLastElements(uint256 numberOfElementsToRemove_) internal { + uint256 numberOfElementsToRemove = numberOfElementsToRemove_; + while (numberOfElementsToRemove > 0) { + numberOfElementsToRemove -= 1; _getStorageTokenRates().pop(); } } @@ -362,8 +382,8 @@ contract TokenRateOracle is ITokenRateOracle, CrossDomainEnabled, AccessControl, error ErrorZeroAddressAdmin(); error ErrorRateUpdatePaused(); error ErrorNoTokenRateUpdates(); - error ErrorWrongIndex(); - error ErrorLastTokenRateUpdateTooOld(); + error ErrorWrongTokenRateIndex(); + error ErrorTokenRateUpdateTooOld(); error ErrorZeroAddressL2ERC20TokenBridge(); error ErrorZeroAddressL1TokenRatePusher(); error ErrorNotBridgeOrTokenRatePusher(); From ee950e456d7694eb1366007a5cb0304b735ab6ed Mon Sep 17 00:00:00 2001 From: Anton Kovalchuk Date: Tue, 28 May 2024 18:33:13 +0200 Subject: [PATCH 139/148] add additional checks --- contracts/optimism/TokenRateOracle.sol | 48 ++++++++++++----------- contracts/token/ERC20RebasableBridged.sol | 14 +------ 2 files changed, 26 insertions(+), 36 deletions(-) diff --git a/contracts/optimism/TokenRateOracle.sol b/contracts/optimism/TokenRateOracle.sol index f213689b..20cf034f 100644 --- a/contracts/optimism/TokenRateOracle.sol +++ b/contracts/optimism/TokenRateOracle.sol @@ -129,26 +129,18 @@ contract TokenRateOracle is ITokenRateOracle, CrossDomainEnabled, AccessControl, _addTokenRate(uint128(tokenRate_), uint64(rateUpdateL1Timestamp_), uint64(block.timestamp)); } - /// @notice Pauses token rate updates. Should be called by DAO or multisig only. - /// @param rateIndex_ The index of the token rate that applies after the pause. - /// Token Rate can't be older then MAX_DELTA_TIME_TO_PAUSE_TOKEN_RATE_UPDATES. - function pauseTokenRateUpdates(uint256 rateIndex_) external onlyRole(RATE_UPDATE_DISABLER_ROLE) { - uint256 tokenRatesLength = _getStorageTokenRates().length; - if (tokenRatesLength == 0) { - revert ErrorNoTokenRateUpdates(); - } - if (rateIndex_ >= tokenRatesLength) { - revert ErrorWrongTokenRateIndex(); - } - - TokenRateData storage tokenRateData = _getStorageTokenRates()[rateIndex_]; - if (tokenRateData.rateReceivedL2Timestamp > block.timestamp - MAX_DELTA_TIME_TO_PAUSE_TOKEN_RATE_UPDATES) { - _removeLastElements(tokenRatesLength - rateIndex_ - 1); - PAUSE_TOKEN_RATE_UPDATES.setStorageBool(true); - emit TokenRateUpdatesPaused(tokenRateData.tokenRate, tokenRateData.rateUpdateL1Timestamp); - } else { + /// @notice Pauses token rate updates and setup old rate provided by tokenRateIndex_. + /// Should be called by DAO or multisig only. + /// @param tokenRateIndex_ The index of the token rate that applies after the pause. + /// Token Rate can't be received older then MAX_DELTA_TIME_TO_PAUSE_TOKEN_RATE_UPDATES. + function pauseTokenRateUpdates(uint256 tokenRateIndex_) external onlyRole(RATE_UPDATE_DISABLER_ROLE) { + TokenRateData memory tokenRateData = _getTokenRateByIndex(tokenRateIndex_); + if (tokenRateData.rateReceivedL2Timestamp < block.timestamp - MAX_DELTA_TIME_TO_PAUSE_TOKEN_RATE_UPDATES) { revert ErrorTokenRateUpdateTooOld(); } + _removeElementsAfterIndex(tokenRateIndex_); + PAUSE_TOKEN_RATE_UPDATES.setStorageBool(true); + emit TokenRateUpdatesPaused(tokenRateData.tokenRate, tokenRateData.rateUpdateL1Timestamp); } /// @notice Unpauses token rate updates applying provided token rate. Should be called by DAO only. @@ -189,7 +181,6 @@ contract TokenRateOracle is ITokenRateOracle, CrossDomainEnabled, AccessControl, uint80 answeredInRound_ ) { TokenRateData memory tokenRateData = _getLastTokenRate(); - return ( uint80(tokenRateData.rateUpdateL1Timestamp), int256(uint256(tokenRateData.tokenRate)), @@ -215,7 +206,7 @@ contract TokenRateOracle is ITokenRateOracle, CrossDomainEnabled, AccessControl, uint256 tokenRate_, uint256 rateUpdateL1Timestamp_ ) external onlyBridgeOrTokenRatePusher whenNotPaused { - TokenRateData memory tokenRateData = _getLastTokenRate(); + TokenRateData storage tokenRateData = _getLastTokenRate(); /// @dev checks if the clock lag (i.e, time difference) between L1 and L2 exceeds the configurable threshold if (rateUpdateL1Timestamp_ > block.timestamp + MAX_ALLOWED_L2_TO_L1_CLOCK_LAG) { @@ -328,10 +319,16 @@ contract TokenRateOracle is ITokenRateOracle, CrossDomainEnabled, AccessControl, } function _getLastTokenRate() internal view returns (TokenRateData storage) { + if (_getStorageTokenRates().length == 0) { + revert ErrorNoTokenRateUpdates(); + } return _getTokenRateByIndex(_getStorageTokenRates().length - 1); } function _getTokenRateByIndex(uint256 tokenRateIndex_) internal view returns (TokenRateData storage) { + if (tokenRateIndex_ >= _getStorageTokenRates().length) { + revert ErrorWrongTokenRateIndex(); + } return _getStorageTokenRates()[tokenRateIndex_]; } @@ -342,8 +339,13 @@ contract TokenRateOracle is ITokenRateOracle, CrossDomainEnabled, AccessControl, } } - function _removeLastElements(uint256 numberOfElementsToRemove_) internal { - uint256 numberOfElementsToRemove = numberOfElementsToRemove_; + function _removeElementsAfterIndex(uint256 tokenRateIndex_) internal { + uint256 tokenRatesLength = _getStorageTokenRates().length; + if (tokenRateIndex_ >= tokenRatesLength) { + revert ErrorWrongTokenRateIndex(); + } + + uint256 numberOfElementsToRemove = tokenRatesLength - tokenRateIndex_ - 1; while (numberOfElementsToRemove > 0) { numberOfElementsToRemove -= 1; _getStorageTokenRates().pop(); @@ -359,7 +361,7 @@ contract TokenRateOracle is ITokenRateOracle, CrossDomainEnabled, AccessControl, } modifier whenNotPaused() { - if (PAUSE_TOKEN_RATE_UPDATES.getStorageBool()) { + if (_isPaused()) { revert ErrorRateUpdatePaused(); } _; diff --git a/contracts/token/ERC20RebasableBridged.sol b/contracts/token/ERC20RebasableBridged.sol index 230bfd5c..69f9ee2f 100644 --- a/contracts/token/ERC20RebasableBridged.sol +++ b/contracts/token/ERC20RebasableBridged.sol @@ -298,18 +298,7 @@ contract ERC20RebasableBridged is IERC20, IERC20Wrapper, IBridgeWrapper, ERC20Me } function _getTokenRate() internal view returns (uint256) { - //slither-disable-next-line unused-return - ( - /* roundId_ */, - int256 answer, - /* startedAt_ */, - uint256 updatedAt, - /* answeredInRound_ */ - ) = TOKEN_RATE_ORACLE.latestRoundData(); - - if (updatedAt == 0) revert ErrorWrongOracleUpdateTime(); - - return uint256(answer); + return uint256(TOKEN_RATE_ORACLE.latestAnswer()); } /// @dev Creates `amount_` shares and assigns them to `account_`, increasing the total shares supply @@ -427,7 +416,6 @@ contract ERC20RebasableBridged is IERC20, IERC20Wrapper, IBridgeWrapper, ERC20Me error ErrorZeroTokensUnwrap(); error ErrorZeroSharesUnwrap(); error ErrorTokenRateDecimalsIsZero(); - error ErrorWrongOracleUpdateTime(); error ErrorTransferToRebasableContract(); error ErrorNotEnoughBalance(); error ErrorNotEnoughAllowance(); From c4c14f14987adadbc3c3ef365a136f6ea95e0d22 Mon Sep 17 00:00:00 2001 From: Anton Kovalchuk Date: Tue, 28 May 2024 18:47:06 +0200 Subject: [PATCH 140/148] fix tests --- .../L2ERC20ExtendedTokensBridge.unit.test.ts | 2 ++ test/optimism/TokenRateOracle.unit.test.ts | 11 ++++++----- test/token/ERC20Permit.unit.test.ts | 1 + .../ERC20RebasableBridgedPermit.unit.test.ts | 16 ++++++++-------- utils/optimism/deploymentAllFromScratch.ts | 1 + utils/optimism/deploymentOracle.ts | 1 + utils/testing/contractsFactory.ts | 1 + 7 files changed, 20 insertions(+), 13 deletions(-) diff --git a/test/optimism/L2ERC20ExtendedTokensBridge.unit.test.ts b/test/optimism/L2ERC20ExtendedTokensBridge.unit.test.ts index 0812c202..a6d187b8 100644 --- a/test/optimism/L2ERC20ExtendedTokensBridge.unit.test.ts +++ b/test/optimism/L2ERC20ExtendedTokensBridge.unit.test.ts @@ -1274,6 +1274,7 @@ async function ctxFactory() { tokenRateOracleImpl.address, deployer.address, tokenRateOracleImpl.interface.encodeFunctionData("initialize", [ + deployer.address, exchangeRate, blockTimestamp ]) @@ -1441,6 +1442,7 @@ async function getL2TokenBridgeImpl(deployer: SignerWithAddress, l1TokenBridge: tokenRateOracleImpl.address, deployer.address, tokenRateOracleImpl.interface.encodeFunctionData("initialize", [ + deployer.address, exchangeRate, blockTimestamp ]) diff --git a/test/optimism/TokenRateOracle.unit.test.ts b/test/optimism/TokenRateOracle.unit.test.ts index 6084e792..5d6bd036 100644 --- a/test/optimism/TokenRateOracle.unit.test.ts +++ b/test/optimism/TokenRateOracle.unit.test.ts @@ -103,19 +103,20 @@ unit("TokenRateOracle", ctxFactory) assert.equalBN(await tokenRateOracleImpl.getContractVersion(), petrifiedVersionMark); await assert.revertsWith( - tokenRateOracleImpl.initialize(tokenRate, blockTimestampOfDeployment), + tokenRateOracleImpl.initialize(deployer.address, tokenRate, blockTimestampOfDeployment), "NonZeroContractVersionOnInit()" ); }) .test("initialize() :: don't allow to initialize twice", async (ctx) => { + const { deployer } = ctx.accounts; const { tokenRateOracle } = ctx.contracts; const { tokenRate, blockTimestampOfDeployment } = ctx.constants; assert.equalBN(await tokenRateOracle.getContractVersion(), 1); await assert.revertsWith( - tokenRateOracle.initialize(tokenRate, blockTimestampOfDeployment), + tokenRateOracle.initialize(deployer.address, tokenRate, blockTimestampOfDeployment), "NonZeroContractVersionOnInit()" ); }) @@ -138,12 +139,12 @@ unit("TokenRateOracle", ctxFactory) const tokenRateMax = await tokenRateOracleImpl.MAX_ALLOWED_TOKEN_RATE(); await assert.revertsWith( - tokenRateOracleImpl.initialize(tokenRateMin.sub(1), blockTimestampOfDeployment), + tokenRateOracleImpl.initialize(deployer.address, tokenRateMin.sub(1), blockTimestampOfDeployment), "ErrorTokenRateInitializationIsOutOfAllowedRange(" + tokenRateMin.sub(1) + ")" ); await assert.revertsWith( - tokenRateOracleImpl.initialize(tokenRateMax.add(1), blockTimestampOfDeployment), + tokenRateOracleImpl.initialize(deployer.address, tokenRateMax.add(1), blockTimestampOfDeployment), "ErrorTokenRateInitializationIsOutOfAllowedRange(" + tokenRateMax.add(1) + ")" ); }) @@ -165,7 +166,7 @@ unit("TokenRateOracle", ctxFactory) const wrongTimeMax = blockTimestampOfDeployment.add(maxAllowedL2ToL1ClockLag).add(20); await assert.revertsWith( - tokenRateOracleImpl.initialize(tokenRate, wrongTimeMax), + tokenRateOracleImpl.initialize(deployer.address, tokenRate, wrongTimeMax), "ErrorL1TimestampInitializationIsOutOfAllowedRange(" + wrongTimeMax + ")" ); }) diff --git a/test/token/ERC20Permit.unit.test.ts b/test/token/ERC20Permit.unit.test.ts index 77f2ca11..3cd5ef85 100644 --- a/test/token/ERC20Permit.unit.test.ts +++ b/test/token/ERC20Permit.unit.test.ts @@ -441,6 +441,7 @@ async function tokenProxied( tokenRateOracleImpl.address, deployer.address, tokenRateOracleImpl.interface.encodeFunctionData("initialize", [ + deployer.address, tokenRate, blockTimestamp ]) diff --git a/test/token/ERC20RebasableBridgedPermit.unit.test.ts b/test/token/ERC20RebasableBridgedPermit.unit.test.ts index d678adf5..844ea066 100644 --- a/test/token/ERC20RebasableBridgedPermit.unit.test.ts +++ b/test/token/ERC20RebasableBridgedPermit.unit.test.ts @@ -16,7 +16,6 @@ import { } from "../../typechain"; unit("ERC20RebasableBridgedPermit", ctxFactory) - .test("constructor() :: zero params", async (ctx) => { const { deployer, @@ -272,7 +271,7 @@ unit("ERC20RebasableBridgedPermit", ctxFactory) .test("wrap() :: wrong oracle update time", async (ctx) => { const { deployer, user1, owner, zero, messenger, l1TokenRatePusher } = ctx.accounts; - const { decimals } = ctx.constants; + const { decimals, tokenRate } = ctx.constants; // deploy new implementation to test initial oracle state const wrappedToken = await new ERC20BridgedPermit__factory(deployer).deploy( @@ -282,7 +281,7 @@ unit("ERC20RebasableBridgedPermit", ctxFactory) decimals, owner.address ); - const tokenRateOracle = await new TokenRateOracle__factory(deployer).deploy( + const tokenRateOracleImpl = await new TokenRateOracle__factory(deployer).deploy( messenger.address, owner.address, l1TokenRatePusher.address, @@ -290,20 +289,21 @@ unit("ERC20RebasableBridgedPermit", ctxFactory) 86400, 500 ); + const rebasableProxied = await new ERC20RebasableBridgedPermit__factory(deployer).deploy( "name", "symbol", "1", 10, wrappedToken.address, - tokenRateOracle.address, + tokenRateOracleImpl.address, owner.address ); await wrappedToken.connect(owner).bridgeMint(user1.address, 1000); await wrappedToken.connect(user1).approve(rebasableProxied.address, 1000); - await assert.revertsWith(rebasableProxied.connect(user1).wrap(5), "ErrorWrongOracleUpdateTime()"); + await assert.revertsWith(rebasableProxied.connect(user1).wrap(5), "ErrorNoTokenRateUpdates()"); }) .test("wrap() :: when no balance", async (ctx) => { @@ -416,7 +416,7 @@ unit("ERC20RebasableBridgedPermit", ctxFactory) await wrappedToken.connect(owner).bridgeMint(user1.address, 1000); await wrappedToken.connect(user1).approve(rebasableProxied.address, 1000); - await assert.revertsWith(rebasableProxied.connect(user1).unwrap(5), "ErrorWrongOracleUpdateTime()"); + await assert.revertsWith(rebasableProxied.connect(user1).unwrap(5), "ErrorNoTokenRateUpdates()"); }) .test("unwrap() :: when no balance", async (ctx) => { @@ -601,7 +601,7 @@ unit("ERC20RebasableBridgedPermit", ctxFactory) await assert.revertsWith( rebasableProxied.connect(owner).bridgeWrap(user1.address, 5), - "ErrorWrongOracleUpdateTime()" + "ErrorNoTokenRateUpdates()" ); }) @@ -727,7 +727,7 @@ unit("ERC20RebasableBridgedPermit", ctxFactory) ); await wrappedToken.connect(owner).bridgeMint(user1.address, 1000); - await assert.revertsWith(rebasableProxied.connect(owner).bridgeUnwrap(user1.address, 5), "ErrorWrongOracleUpdateTime()"); + await assert.revertsWith(rebasableProxied.connect(owner).bridgeUnwrap(user1.address, 5), "ErrorNoTokenRateUpdates()"); }) .test("bridgeUnwrap() :: happy path", async (ctx) => { diff --git a/utils/optimism/deploymentAllFromScratch.ts b/utils/optimism/deploymentAllFromScratch.ts index 14ba6fa5..dc6c0032 100644 --- a/utils/optimism/deploymentAllFromScratch.ts +++ b/utils/optimism/deploymentAllFromScratch.ts @@ -243,6 +243,7 @@ export default function deploymentAll( TokenRateOracle__factory.createInterface().encodeFunctionData( "initialize", [ + l2Params.admins.bridge, l2Params.tokenRateOracle.tokenRate, l2Params.tokenRateOracle.l1Timestamp ] diff --git a/utils/optimism/deploymentOracle.ts b/utils/optimism/deploymentOracle.ts index 3c8da04d..58a3b03d 100644 --- a/utils/optimism/deploymentOracle.ts +++ b/utils/optimism/deploymentOracle.ts @@ -138,6 +138,7 @@ export default function deploymentOracle( TokenRateOracle__factory.createInterface().encodeFunctionData( "initialize", [ + l2Params.admins.bridge, l2Params.tokenRateOracle.tokenRate, l2Params.tokenRateOracle.l1Timestamp ] diff --git a/utils/testing/contractsFactory.ts b/utils/testing/contractsFactory.ts index bea28998..e842e1b0 100644 --- a/utils/testing/contractsFactory.ts +++ b/utils/testing/contractsFactory.ts @@ -68,6 +68,7 @@ export async function tokenRateOracleUnderProxy( const unsignedTx = tokenRateOracleProxy.getDeployTransaction(tokenRateOracleImpl.address, deployer.address, tokenRateOracleImpl.interface.encodeFunctionData("initialize", [ + deployer.address, tokenRate, rateL1Timestamp ])); From 48f3106753af233814e3c7f066e3d8c4b0c672cd Mon Sep 17 00:00:00 2001 From: Anton Kovalchuk Date: Tue, 28 May 2024 19:04:28 +0200 Subject: [PATCH 141/148] return instead of revert when updates are paused --- contracts/optimism/TokenRateOracle.sol | 18 ++++++++---------- 1 file changed, 8 insertions(+), 10 deletions(-) diff --git a/contracts/optimism/TokenRateOracle.sol b/contracts/optimism/TokenRateOracle.sol index 20cf034f..69025962 100644 --- a/contracts/optimism/TokenRateOracle.sol +++ b/contracts/optimism/TokenRateOracle.sol @@ -203,8 +203,13 @@ contract TokenRateOracle is ITokenRateOracle, CrossDomainEnabled, AccessControl, /// @inheritdoc ITokenRateUpdatable function updateRate( - uint256 tokenRate_, uint256 rateUpdateL1Timestamp_ - ) external onlyBridgeOrTokenRatePusher whenNotPaused { + uint256 tokenRate_, + uint256 rateUpdateL1Timestamp_ + ) external onlyBridgeOrTokenRatePusher { + if (_isPaused()) { + emit TokenRateUpdateAttemptDuringPause(tokenRate_, rateUpdateL1Timestamp_); + return; + } TokenRateData storage tokenRateData = _getLastTokenRate(); @@ -360,13 +365,6 @@ contract TokenRateOracle is ITokenRateOracle, CrossDomainEnabled, AccessControl, return PAUSE_TOKEN_RATE_UPDATES.getStorageBool(); } - modifier whenNotPaused() { - if (_isPaused()) { - revert ErrorRateUpdatePaused(); - } - _; - } - modifier onlyBridgeOrTokenRatePusher() { if (!_isCallerBridgeOrMessengerWithTokenRatePusher(msg.sender)) { revert ErrorNotBridgeOrTokenRatePusher(); @@ -380,9 +378,9 @@ contract TokenRateOracle is ITokenRateOracle, CrossDomainEnabled, AccessControl, event TokenRateL1TimestampIsInFuture(uint256 tokenRate_, uint256 indexed rateL1Timestamp_); event TokenRateUpdatesPaused(uint256 tokenRate_, uint256 indexed rateL1Timestamp_); event TokenRateUpdatesUnpaused(uint256 tokenRate_, uint256 indexed rateL1Timestamp_); + event TokenRateUpdateAttemptDuringPause(uint256 tokenRate_, uint256 indexed rateL1Timestamp_); error ErrorZeroAddressAdmin(); - error ErrorRateUpdatePaused(); error ErrorNoTokenRateUpdates(); error ErrorWrongTokenRateIndex(); error ErrorTokenRateUpdateTooOld(); From 5bc7a43cced309915f724489d0b6a793658b2da5 Mon Sep 17 00:00:00 2001 From: Anton Kovalchuk Date: Wed, 29 May 2024 11:49:15 +0200 Subject: [PATCH 142/148] add tests, small improvements in pause --- contracts/optimism/TokenRateOracle.sol | 58 +++--- .../L2ERC20ExtendedTokensBridge.unit.test.ts | 6 +- test/optimism/TokenRateOracle.unit.test.ts | 187 +++++++++++++++++- test/token/ERC20Permit.unit.test.ts | 3 +- .../ERC20RebasableBridgedPermit.unit.test.ts | 25 ++- utils/testing/contractsFactory.ts | 4 +- 6 files changed, 242 insertions(+), 41 deletions(-) diff --git a/contracts/optimism/TokenRateOracle.sol b/contracts/optimism/TokenRateOracle.sol index 69025962..d22eedc1 100644 --- a/contracts/optimism/TokenRateOracle.sol +++ b/contracts/optimism/TokenRateOracle.sol @@ -48,6 +48,9 @@ contract TokenRateOracle is ITokenRateOracle, CrossDomainEnabled, AccessControl, /// @notice Allowed token rate deviation per day in basic points. uint256 public immutable MAX_ALLOWED_TOKEN_RATE_DEVIATION_PER_DAY; + /// @notice Max delta time for last updates to pause updating rate. + uint256 public immutable MAX_DELTA_TIME_TO_PAUSE_TOKEN_RATE_UPDATES; + /// @notice Number of seconds in one day. uint256 public constant ONE_DAY_SECONDS = 86400; @@ -60,24 +63,21 @@ contract TokenRateOracle is ITokenRateOracle, CrossDomainEnabled, AccessControl, /// @notice Min allowed token rate value. uint256 public constant MIN_ALLOWED_TOKEN_RATE = 10 ** (DECIMALS - 2); + /// @dev Role granting the permission to pause updating rate. + bytes32 public constant RATE_UPDATE_DISABLER_ROLE = keccak256("TokenRateOracle.RATE_UPDATE_DISABLER_ROLE"); + + /// @dev Role granting the permission to unpause updating rate. + bytes32 public constant RATE_UPDATE_ENABLER_ROLE = keccak256("TokenRateOracle.RATE_UPDATE_ENABLER_ROLE"); + /// @notice Basic point scale. uint256 private constant BASIS_POINT_SCALE = 1e4; - /// @notice Max delta time for last updates to pause updating rate. - uint256 private constant MAX_DELTA_TIME_TO_PAUSE_TOKEN_RATE_UPDATES = 86400 * 3; - /// @notice Flag to pause token rate updates slot position. bytes32 private constant PAUSE_TOKEN_RATE_UPDATES = keccak256("TokenRateOracle.PAUSE_TOKEN_RATE_UPDATES"); /// @notice Token rates array slot position. bytes32 private constant TOKEN_RATES_DATA_SLOT = keccak256("TokenRateOracle.TOKEN_RATES_DATA_SLOT"); - /// @dev Role granting the permission to pause updating rate. - bytes32 public constant RATE_UPDATE_DISABLER_ROLE = keccak256("TokenRateOracle.RATE_UPDATE_DISABLER_ROLE"); - - /// @dev Role granting the permission to unpause updating rate. - bytes32 private constant RATE_UPDATE_ENABLER_ROLE = keccak256("TokenRateOracle.RATE_UPDATE_ENABLER_ROLE"); - /// @param messenger_ L2 messenger address being used for cross-chain communications /// @param l2ERC20TokenBridge_ the bridge address that has a right to updates oracle. /// @param l1TokenRatePusher_ An address of account on L1 that can update token rate. @@ -86,13 +86,16 @@ contract TokenRateOracle is ITokenRateOracle, CrossDomainEnabled, AccessControl, /// when token rate can be considered outdated. /// @param maxAllowedTokenRateDeviationPerDay_ Allowed token rate deviation per day in basic points. /// Can't be bigger than BASIS_POINT_SCALE. + /// @param maxDeltaTimeToPausetokenRateUpdates_ Maximum delta time between current time and last received + /// token rate update that is allowed to set during pause. constructor( address messenger_, address l2ERC20TokenBridge_, address l1TokenRatePusher_, uint256 tokenRateOutdatedDelay_, uint256 maxAllowedL2ToL1ClockLag_, - uint256 maxAllowedTokenRateDeviationPerDay_ + uint256 maxAllowedTokenRateDeviationPerDay_, + uint256 maxDeltaTimeToPausetokenRateUpdates_ ) CrossDomainEnabled(messenger_) { if (l2ERC20TokenBridge_ == address(0)) { revert ErrorZeroAddressL2ERC20TokenBridge(); @@ -108,11 +111,12 @@ contract TokenRateOracle is ITokenRateOracle, CrossDomainEnabled, AccessControl, TOKEN_RATE_OUTDATED_DELAY = tokenRateOutdatedDelay_; MAX_ALLOWED_L2_TO_L1_CLOCK_LAG = maxAllowedL2ToL1ClockLag_; MAX_ALLOWED_TOKEN_RATE_DEVIATION_PER_DAY = maxAllowedTokenRateDeviationPerDay_; + MAX_DELTA_TIME_TO_PAUSE_TOKEN_RATE_UPDATES = maxDeltaTimeToPausetokenRateUpdates_; } /// @notice Initializes the contract from scratch. /// @param admin_ Address of the account to grant the DEFAULT_ADMIN_ROLE - /// @param tokenRate_ wstETH/stETH token rate. + /// @param tokenRate_ wstETH/stETH token rate, uses 10**DECIMALS precision. /// @param rateUpdateL1Timestamp_ L1 time when rate was updated on L1 side. function initialize(address admin_, uint256 tokenRate_, uint256 rateUpdateL1Timestamp_) external { if (admin_ == address(0)) { @@ -129,18 +133,24 @@ contract TokenRateOracle is ITokenRateOracle, CrossDomainEnabled, AccessControl, _addTokenRate(uint128(tokenRate_), uint64(rateUpdateL1Timestamp_), uint64(block.timestamp)); } - /// @notice Pauses token rate updates and setup old rate provided by tokenRateIndex_. - /// Should be called by DAO or multisig only. + /// @notice Pauses token rate updates and sets the old rate provided by tokenRateIndex_. + /// Should be called by DAO or emergency breaks only. /// @param tokenRateIndex_ The index of the token rate that applies after the pause. - /// Token Rate can't be received older then MAX_DELTA_TIME_TO_PAUSE_TOKEN_RATE_UPDATES. + /// Token Rate can't be received older then MAX_DELTA_TIME_TO_PAUSE_TOKEN_RATE_UPDATES + /// except only if the passed index is the latest one. function pauseTokenRateUpdates(uint256 tokenRateIndex_) external onlyRole(RATE_UPDATE_DISABLER_ROLE) { + if (_isPaused()) { + revert ErrorAlreadyPaused(); + } TokenRateData memory tokenRateData = _getTokenRateByIndex(tokenRateIndex_); - if (tokenRateData.rateReceivedL2Timestamp < block.timestamp - MAX_DELTA_TIME_TO_PAUSE_TOKEN_RATE_UPDATES) { + if (tokenRateIndex_ != _getStorageTokenRates().length - 1 && + tokenRateData.rateReceivedL2Timestamp < block.timestamp - MAX_DELTA_TIME_TO_PAUSE_TOKEN_RATE_UPDATES) { revert ErrorTokenRateUpdateTooOld(); } _removeElementsAfterIndex(tokenRateIndex_); PAUSE_TOKEN_RATE_UPDATES.setStorageBool(true); emit TokenRateUpdatesPaused(tokenRateData.tokenRate, tokenRateData.rateUpdateL1Timestamp); + emit RateUpdated(tokenRateData.tokenRate, tokenRateData.rateUpdateL1Timestamp); } /// @notice Unpauses token rate updates applying provided token rate. Should be called by DAO only. @@ -150,6 +160,9 @@ contract TokenRateOracle is ITokenRateOracle, CrossDomainEnabled, AccessControl, uint256 tokenRate_, uint256 rateUpdateL1Timestamp_ ) external onlyRole(RATE_UPDATE_ENABLER_ROLE) { + if (!_isPaused()) { + revert ErrorAlreadyUnpaused(); + } _addTokenRate(uint128(tokenRate_), uint64(rateUpdateL1Timestamp_), uint64(block.timestamp)); PAUSE_TOKEN_RATE_UPDATES.setStorageBool(false); emit TokenRateUpdatesUnpaused(tokenRate_, rateUpdateL1Timestamp_); @@ -346,14 +359,11 @@ contract TokenRateOracle is ITokenRateOracle, CrossDomainEnabled, AccessControl, function _removeElementsAfterIndex(uint256 tokenRateIndex_) internal { uint256 tokenRatesLength = _getStorageTokenRates().length; - if (tokenRateIndex_ >= tokenRatesLength) { - revert ErrorWrongTokenRateIndex(); - } - - uint256 numberOfElementsToRemove = tokenRatesLength - tokenRateIndex_ - 1; - while (numberOfElementsToRemove > 0) { - numberOfElementsToRemove -= 1; - _getStorageTokenRates().pop(); + if (tokenRateIndex_ < tokenRatesLength) { + uint256 numberOfElementsToRemove = tokenRatesLength - tokenRateIndex_ - 1; + for (uint256 i = 0; i < numberOfElementsToRemove; i++) { + _getStorageTokenRates().pop(); + } } } @@ -384,6 +394,8 @@ contract TokenRateOracle is ITokenRateOracle, CrossDomainEnabled, AccessControl, error ErrorNoTokenRateUpdates(); error ErrorWrongTokenRateIndex(); error ErrorTokenRateUpdateTooOld(); + error ErrorAlreadyPaused(); + error ErrorAlreadyUnpaused(); error ErrorZeroAddressL2ERC20TokenBridge(); error ErrorZeroAddressL1TokenRatePusher(); error ErrorNotBridgeOrTokenRatePusher(); diff --git a/test/optimism/L2ERC20ExtendedTokensBridge.unit.test.ts b/test/optimism/L2ERC20ExtendedTokensBridge.unit.test.ts index a6d187b8..a9365cd7 100644 --- a/test/optimism/L2ERC20ExtendedTokensBridge.unit.test.ts +++ b/test/optimism/L2ERC20ExtendedTokensBridge.unit.test.ts @@ -1262,7 +1262,8 @@ async function ctxFactory() { l1TokenBridgeEOA.address, 86400, 86400, - 500 + 500, + 86400*3 ); const provider = await hre.ethers.provider; @@ -1430,7 +1431,8 @@ async function getL2TokenBridgeImpl(deployer: SignerWithAddress, l1TokenBridge: l1TokenBridge, 86400, 86400, - 500 + 500, + 86400*3 ); const provider = await hre.ethers.provider; diff --git a/test/optimism/TokenRateOracle.unit.test.ts b/test/optimism/TokenRateOracle.unit.test.ts index 5d6bd036..c4fbdc82 100644 --- a/test/optimism/TokenRateOracle.unit.test.ts +++ b/test/optimism/TokenRateOracle.unit.test.ts @@ -21,6 +21,7 @@ unit("TokenRateOracle", ctxFactory) stranger.address, 0, 0, + 0, 0 ), "ErrorZeroAddressMessenger()"); @@ -32,6 +33,7 @@ unit("TokenRateOracle", ctxFactory) stranger.address, 0, 0, + 0, 0 ), "ErrorZeroAddressL2ERC20TokenBridge()"); @@ -43,6 +45,7 @@ unit("TokenRateOracle", ctxFactory) zero.address, 0, 0, + 0, 0 ), "ErrorZeroAddressL1TokenRatePusher()"); }) @@ -96,7 +99,8 @@ unit("TokenRateOracle", ctxFactory) l1TokenBridgeEOA.address, 86400, 86400, - 500 + 500, + 86400*3 ); const petrifiedVersionMark = hre.ethers.constants.MaxUint256; @@ -132,7 +136,8 @@ unit("TokenRateOracle", ctxFactory) l1TokenBridgeEOA.address, 86400, 86400, - 500 + 500, + 86400*3 ); const tokenRateMin = await tokenRateOracleImpl.MIN_ALLOWED_TOKEN_RATE(); @@ -160,7 +165,8 @@ unit("TokenRateOracle", ctxFactory) l1TokenBridgeEOA.address, 86400, 86400, - 500 + 500, + 86400*3 ); const wrongTimeMax = blockTimestampOfDeployment.add(maxAllowedL2ToL1ClockLag).add(20); @@ -184,7 +190,8 @@ unit("TokenRateOracle", ctxFactory) l1TokenBridgeEOA.address, 86400, 86400, - maxAllowedTokenRateDeviationPerDay + maxAllowedTokenRateDeviationPerDay, + 86400*3 ), "ErrorMaxTokenRateDeviationIsOutOfRange()" ); @@ -369,6 +376,7 @@ unit("TokenRateOracle", ctxFactory) tokenRateOutdatedDelay, maxAllowedL2ToL1ClockLag, maxAllowedTokenRateDeviationPerDay, + BigNumber.from(86400*3), tokenRate, BigNumber.from(0) ); @@ -462,6 +470,171 @@ unit("TokenRateOracle", ctxFactory) assert.equalBN(answeredInRound_, blockTimestampInFuture); }) + // correct index + // too old time + // success + state and events + check event when update + .test("pauseTokenRateUpdates() :: wrong role", async (ctx) => { + const { tokenRateOracle } = ctx.contracts; + const { deployer } = ctx.accounts; + + const disablerRole = await tokenRateOracle.RATE_UPDATE_DISABLER_ROLE(); + const account = deployer.address.toLowerCase(); + + await assert.revertsWith( + tokenRateOracle.pauseTokenRateUpdates(1), + "AccessControl: account " + account + " is missing role " + disablerRole + ); + }) + + .test("pauseTokenRateUpdates() :: wrong index", async (ctx) => { + const { tokenRateOracle } = ctx.contracts; + const { multisig } = ctx.accounts; + + const disablerRole = await tokenRateOracle.RATE_UPDATE_DISABLER_ROLE(); + await tokenRateOracle.grantRole(disablerRole, multisig.address); + + await assert.revertsWith( + tokenRateOracle.connect(multisig).pauseTokenRateUpdates(1), + "ErrorWrongTokenRateIndex()" + ); + }) + + .test("pauseTokenRateUpdates() :: old rate", async (ctx) => { + + const { multisig, bridge, deployer, l1TokenBridgeEOA } = ctx.accounts; + const { tokenRate, tokenRateOutdatedDelay, maxAllowedL2ToL1ClockLag, maxAllowedTokenRateDeviationPerDay } = ctx.constants; + const { l2MessengerStub } = ctx.contracts; + + /// create new Oracle with 0 maxDeltaTimeToPausetokenRateUpdates + const maxDeltaTimeToPausetokenRateUpdates = BigNumber.from(0); + const { tokenRateOracle, blockTimestampOfDeployment } = await tokenRateOracleUnderProxy( + deployer, + l2MessengerStub.address, + bridge.address, + l1TokenBridgeEOA.address, + tokenRateOutdatedDelay, + maxAllowedL2ToL1ClockLag, + maxAllowedTokenRateDeviationPerDay, + maxDeltaTimeToPausetokenRateUpdates, + tokenRate, + BigNumber.from(0) + ); + + const disablerRole = await tokenRateOracle.RATE_UPDATE_DISABLER_ROLE(); + await tokenRateOracle.grantRole(disablerRole, multisig.address); + + await tokenRateOracle.connect(bridge).updateRate(tokenRate, blockTimestampOfDeployment.add(1000)); + + await assert.revertsWith( + tokenRateOracle.connect(multisig).pauseTokenRateUpdates(0), + "ErrorTokenRateUpdateTooOld()" + ); + }) + + .test("pauseTokenRateUpdates() :: success", async (ctx) => { + const { tokenRateOracle } = ctx.contracts; + const { multisig, bridge } = ctx.accounts; + const { tokenRate, blockTimestampOfDeployment } = ctx.constants; + + const disablerRole = await tokenRateOracle.RATE_UPDATE_DISABLER_ROLE(); + await tokenRateOracle.grantRole(disablerRole, multisig.address); + + const tokenRate0 = tokenRate.add(100); + const tokenRate1 = tokenRate.add(200); + const tokenRate2 = tokenRate.add(300); + const tokenRate3 = tokenRate.add(300); + + const blockTimestampOfDeployment0 = blockTimestampOfDeployment.add(1000); + const blockTimestampOfDeployment1 = blockTimestampOfDeployment.add(2000); + const blockTimestampOfDeployment2 = blockTimestampOfDeployment.add(3000); + const blockTimestampOfDeployment3 = blockTimestampOfDeployment.add(4000); + + const tx0 = await tokenRateOracle.connect(bridge).updateRate(tokenRate0, blockTimestampOfDeployment0); + await assert.emits(tokenRateOracle, tx0, "RateUpdated", [tokenRate0, blockTimestampOfDeployment0]); + + const tx1 = await tokenRateOracle.connect(bridge).updateRate(tokenRate1, blockTimestampOfDeployment1); + await assert.emits(tokenRateOracle, tx1, "RateUpdated", [tokenRate1, blockTimestampOfDeployment1]); + + const tx2 = await tokenRateOracle.connect(bridge).updateRate(tokenRate2, blockTimestampOfDeployment2); + await assert.emits(tokenRateOracle, tx2, "RateUpdated", [tokenRate2, blockTimestampOfDeployment2]); + + assert.equalBN(await tokenRateOracle.getTokenRatesLength(), 4); + assert.isFalse(await tokenRateOracle.isTokenRateUpdatesPaused()); + + const pauseTx = await tokenRateOracle.connect(multisig).pauseTokenRateUpdates(1); + + await assert.emits(tokenRateOracle, pauseTx, "TokenRateUpdatesPaused", [ + tokenRate0, + blockTimestampOfDeployment0 + ]); + + assert.equalBN(await tokenRateOracle.getTokenRatesLength(), 2); + assert.isTrue(await tokenRateOracle.isTokenRateUpdatesPaused()); + + const tx3 = await tokenRateOracle.connect(bridge).updateRate(tokenRate3, blockTimestampOfDeployment3); + await assert.emits( + tokenRateOracle, + tx3, + "TokenRateUpdateAttemptDuringPause", + [tokenRate3, blockTimestampOfDeployment3] + ); + }) + + // unpause + // wrong role + // success + state and events + updates goes as before + .test("unpauseTokenRateUpdates() :: wrong role", async (ctx) => { + const { tokenRateOracle } = ctx.contracts; + const { deployer } = ctx.accounts; + const { tokenRate, blockTimestampOfDeployment } = ctx.constants; + + const enablerRole = await tokenRateOracle.RATE_UPDATE_ENABLER_ROLE(); + const account = deployer.address.toLowerCase(); + + await assert.revertsWith( + tokenRateOracle.unpauseTokenRateUpdates(tokenRate, blockTimestampOfDeployment.add(1000)), + "AccessControl: account " + account + " is missing role " + enablerRole + ); + }) + + .test("unpauseTokenRateUpdates() :: success", async (ctx) => { + const { tokenRateOracle } = ctx.contracts; + const { multisig, bridge } = ctx.accounts; + const { tokenRate, blockTimestampOfDeployment } = ctx.constants; + + const disablerRole = await tokenRateOracle.RATE_UPDATE_DISABLER_ROLE(); + await tokenRateOracle.grantRole(disablerRole, multisig.address); + + const enablerRole = await tokenRateOracle.RATE_UPDATE_ENABLER_ROLE(); + await tokenRateOracle.grantRole(enablerRole, multisig.address); + + await tokenRateOracle.connect(bridge).updateRate(tokenRate, blockTimestampOfDeployment.add(1000)); + assert.isFalse(await tokenRateOracle.isTokenRateUpdatesPaused()); + await tokenRateOracle.connect(multisig).pauseTokenRateUpdates(0); + assert.isTrue(await tokenRateOracle.isTokenRateUpdatesPaused()); + const unpauseTx = await tokenRateOracle.connect(multisig).unpauseTokenRateUpdates(tokenRate, blockTimestampOfDeployment); + assert.isFalse(await tokenRateOracle.isTokenRateUpdatesPaused()); + + await assert.emits( + tokenRateOracle, + unpauseTx, + "TokenRateUpdatesUnpaused", + [tokenRate, blockTimestampOfDeployment] + ); + await assert.emits(tokenRateOracle, + unpauseTx, + "RateUpdated", + [tokenRate, blockTimestampOfDeployment] + ); + + const tx = await tokenRateOracle.connect(bridge).updateRate(tokenRate.add(100), blockTimestampOfDeployment.add(1000)); + await assert.emits(tokenRateOracle, + tx, + "RateUpdated", + [tokenRate.add(100), blockTimestampOfDeployment.add(1000)] + ); + }) + .run(); async function ctxFactory() { @@ -474,7 +647,7 @@ async function ctxFactory() { const tokenRateOutdatedDelay = BigNumber.from(86400); // 1 day const maxAllowedL2ToL1ClockLag = BigNumber.from(86400 * 2); // 2 days const maxAllowedTokenRateDeviationPerDay = BigNumber.from(500); // 5% - const [deployer, bridge, stranger, l1TokenBridgeEOA] = await hre.ethers.getSigners(); + const [deployer, bridge, stranger, l1TokenBridgeEOA, multisig] = await hre.ethers.getSigners(); const zero = await hre.ethers.getSigner(hre.ethers.constants.AddressZero); const l2MessengerStub = await new CrossDomainMessengerStub__factory(deployer) @@ -494,6 +667,7 @@ async function ctxFactory() { tokenRateOutdatedDelay, maxAllowedL2ToL1ClockLag, maxAllowedTokenRateDeviationPerDay, + BigNumber.from(86400*3), tokenRate, rateL1Timestamp ); @@ -505,7 +679,8 @@ async function ctxFactory() { zero, stranger, l1TokenBridgeEOA, - l2MessengerStubEOA + l2MessengerStubEOA, + multisig }, contracts: { tokenRateOracle, diff --git a/test/token/ERC20Permit.unit.test.ts b/test/token/ERC20Permit.unit.test.ts index 3cd5ef85..a590ea54 100644 --- a/test/token/ERC20Permit.unit.test.ts +++ b/test/token/ERC20Permit.unit.test.ts @@ -429,7 +429,8 @@ async function tokenProxied( l1TokenRatePusher, 86400, 86400, - 500 + 500, + 86400*3 ); const provider = await hre.ethers.provider; const blockNumber = await provider.getBlockNumber(); diff --git a/test/token/ERC20RebasableBridgedPermit.unit.test.ts b/test/token/ERC20RebasableBridgedPermit.unit.test.ts index 844ea066..db00660b 100644 --- a/test/token/ERC20RebasableBridgedPermit.unit.test.ts +++ b/test/token/ERC20RebasableBridgedPermit.unit.test.ts @@ -38,7 +38,8 @@ unit("ERC20RebasableBridgedPermit", ctxFactory) l1TokenRatePusher.address, tokenRateOutdatedDelay, maxAllowedL2ToL1ClockLag, - maxAllowedTokenRateDeviationPerDay + maxAllowedTokenRateDeviationPerDay, + 86400*3 ); await assert.revertsWith(new ERC20RebasableBridgedPermit__factory( @@ -123,7 +124,8 @@ unit("ERC20RebasableBridgedPermit", ctxFactory) l1TokenRatePusher.address, 86400, 86400, - 500 + 500, + 86400*3 ); const rebasableTokenImpl = await new ERC20RebasableBridgedPermit__factory(deployer).deploy( "stETH Test Token", @@ -162,7 +164,8 @@ unit("ERC20RebasableBridgedPermit", ctxFactory) l1TokenRatePusher.address, 86400, 86400, - 500 + 500, + 86400*3 ); const rebasableTokenImpl = await new ERC20RebasableBridgedPermit__factory(deployer).deploy( "name", @@ -218,7 +221,8 @@ unit("ERC20RebasableBridgedPermit", ctxFactory) l1TokenRatePusher.address, 86400, 86400, - 500 + 500, + 86400*3 ); const rebasableTokenImpl = await new ERC20RebasableBridgedPermit__factory(deployer).deploy( "name", @@ -287,7 +291,8 @@ unit("ERC20RebasableBridgedPermit", ctxFactory) l1TokenRatePusher.address, 86400, 86400, - 500 + 500, + 86400*3 ); const rebasableProxied = await new ERC20RebasableBridgedPermit__factory(deployer).deploy( @@ -401,7 +406,8 @@ unit("ERC20RebasableBridgedPermit", ctxFactory) l1TokenRatePusher.address, 86400, 86400, - 500 + 500, + 86400*3 ); const rebasableProxied = await new ERC20RebasableBridgedPermit__factory(deployer).deploy( "name", @@ -584,7 +590,8 @@ unit("ERC20RebasableBridgedPermit", ctxFactory) l1TokenRatePusher.address, 86400, 86400, - 500 + 500, + 86400*3 ); const rebasableProxied = await new ERC20RebasableBridgedPermit__factory(deployer).deploy( "name", @@ -714,7 +721,8 @@ unit("ERC20RebasableBridgedPermit", ctxFactory) l1TokenRatePusher.address, 86400, 86400, - 500 + 500, + 86400*3 ); const rebasableProxied = await new ERC20RebasableBridgedPermit__factory(deployer).deploy( "name", @@ -1402,6 +1410,7 @@ async function ctxFactory() { tokenRateOutdatedDelay, maxAllowedL2ToL1ClockLag, maxAllowedTokenRateDeviationPerDay, + BigNumber.from(86400*3), tokenRate, blockTimestamp ) diff --git a/utils/testing/contractsFactory.ts b/utils/testing/contractsFactory.ts index e842e1b0..7daf7e00 100644 --- a/utils/testing/contractsFactory.ts +++ b/utils/testing/contractsFactory.ts @@ -51,6 +51,7 @@ export async function tokenRateOracleUnderProxy( tokenRateOutdatedDelay: BigNumber, maxAllowedL2ToL1ClockLag: BigNumber, maxAllowedTokenRateDeviationPerDay: BigNumber, + maxDeltaTimeToPausetokenRateUpdates: BigNumber, tokenRate: BigNumber, rateL1Timestamp: BigNumber ) { @@ -60,7 +61,8 @@ export async function tokenRateOracleUnderProxy( l1TokenRatePusher, tokenRateOutdatedDelay, maxAllowedL2ToL1ClockLag, - maxAllowedTokenRateDeviationPerDay + maxAllowedTokenRateDeviationPerDay, + maxDeltaTimeToPausetokenRateUpdates ); const tokenRateOracleProxy = new OssifiableProxy__factory(deployer); From e54f586662fd592967ed6aca0c3f08e377ea0c6f Mon Sep 17 00:00:00 2001 From: Anton Kovalchuk Date: Thu, 30 May 2024 23:50:06 +0200 Subject: [PATCH 143/148] rename constants, add comments, don't allow often rate updates --- contracts/optimism/TokenRateOracle.sol | 127 +++++---- .../L2ERC20ExtendedTokensBridge.unit.test.ts | 28 +- test/optimism/TokenRateOracle.unit.test.ts | 207 +++++++++----- .../pushingTokenRate.integration.test.ts | 6 +- test/token/ERC20Permit.unit.test.ts | 3 +- .../ERC20RebasableBridgedPermit.unit.test.ts | 166 +----------- utils/optimism/deploymentAllFromScratch.ts | 174 ++++++------ .../deploymentBridgesAndRebasableToken.ts | 256 ------------------ utils/optimism/deploymentOracle.ts | 14 +- utils/optimism/index.ts | 2 - utils/optimism/testing.ts | 36 ++- utils/testing/contractsFactory.ts | 6 +- 12 files changed, 376 insertions(+), 649 deletions(-) delete mode 100644 utils/optimism/deploymentBridgesAndRebasableToken.ts diff --git a/contracts/optimism/TokenRateOracle.sol b/contracts/optimism/TokenRateOracle.sol index d22eedc1..0b784466 100644 --- a/contracts/optimism/TokenRateOracle.sol +++ b/contracts/optimism/TokenRateOracle.sol @@ -46,22 +46,24 @@ contract TokenRateOracle is ITokenRateOracle, CrossDomainEnabled, AccessControl, uint256 public immutable MAX_ALLOWED_L2_TO_L1_CLOCK_LAG; /// @notice Allowed token rate deviation per day in basic points. - uint256 public immutable MAX_ALLOWED_TOKEN_RATE_DEVIATION_PER_DAY; + uint256 public immutable MAX_ALLOWED_TOKEN_RATE_DEVIATION_PER_DAY_BP; - /// @notice Max delta time for last updates to pause updating rate. - uint256 public immutable MAX_DELTA_TIME_TO_PAUSE_TOKEN_RATE_UPDATES; + /// @notice The maximum allowed time difference between the current time and the last received + /// token rate update that can be set during a pause. This is required to limit the pause role + /// and prevent potential economic attacks. + uint256 public immutable OLDEST_RATE_ALLOWED_IN_PAUSE_TIME_SPAN; - /// @notice Number of seconds in one day. - uint256 public constant ONE_DAY_SECONDS = 86400; + /// @notice The maximum delta time that is allowed between two L1 timestamps of token rate updates. + uint256 public immutable MAX_ALLOWED_TIME_BETWEEEN_TOKEN_RATE_UPDATES; /// @notice Decimals of the oracle response. uint8 public constant DECIMALS = 27; - /// @notice Max allowed token rate value. - uint256 public constant MAX_ALLOWED_TOKEN_RATE = 10 ** (DECIMALS + 2); + /// @notice Max sane token rate value. + uint256 public constant MAX_SANE_TOKEN_RATE = 10 ** (DECIMALS + 2); - /// @notice Min allowed token rate value. - uint256 public constant MIN_ALLOWED_TOKEN_RATE = 10 ** (DECIMALS - 2); + /// @notice Min sane token rate value. + uint256 public constant MIN_SANE_TOKEN_RATE = 10 ** (DECIMALS - 2); /// @dev Role granting the permission to pause updating rate. bytes32 public constant RATE_UPDATE_DISABLER_ROLE = keccak256("TokenRateOracle.RATE_UPDATE_DISABLER_ROLE"); @@ -72,8 +74,11 @@ contract TokenRateOracle is ITokenRateOracle, CrossDomainEnabled, AccessControl, /// @notice Basic point scale. uint256 private constant BASIS_POINT_SCALE = 1e4; + /// @notice Number of seconds in one day. + uint256 private constant ONE_DAY_SECONDS = 86400; + /// @notice Flag to pause token rate updates slot position. - bytes32 private constant PAUSE_TOKEN_RATE_UPDATES = keccak256("TokenRateOracle.PAUSE_TOKEN_RATE_UPDATES"); + bytes32 private constant PAUSE_TOKEN_RATE_UPDATES_SLOT = keccak256("TokenRateOracle.PAUSE_TOKEN_RATE_UPDATES_SLOT"); /// @notice Token rates array slot position. bytes32 private constant TOKEN_RATES_DATA_SLOT = keccak256("TokenRateOracle.TOKEN_RATES_DATA_SLOT"); @@ -84,18 +89,21 @@ contract TokenRateOracle is ITokenRateOracle, CrossDomainEnabled, AccessControl, /// @param tokenRateOutdatedDelay_ time period when token rate can be considered outdated. /// @param maxAllowedL2ToL1ClockLag_ A time difference between received l1Timestamp and L2 block.timestamp /// when token rate can be considered outdated. - /// @param maxAllowedTokenRateDeviationPerDay_ Allowed token rate deviation per day in basic points. + /// @param maxAllowedTokenRateDeviationPerDayBp_ Allowed token rate deviation per day in basic points. /// Can't be bigger than BASIS_POINT_SCALE. - /// @param maxDeltaTimeToPausetokenRateUpdates_ Maximum delta time between current time and last received - /// token rate update that is allowed to set during pause. + /// @param oldestRateAllowedInPauseTimeSpan_ Maximum allowed time difference between the current time + /// and the last received token rate update that can be set during a pause. + /// @param maxAllowedTimeBetweenTokenRateUpdates_ he maximum delta time that is allowed between two + /// L1 timestamps of token rate updates. constructor( address messenger_, address l2ERC20TokenBridge_, address l1TokenRatePusher_, uint256 tokenRateOutdatedDelay_, uint256 maxAllowedL2ToL1ClockLag_, - uint256 maxAllowedTokenRateDeviationPerDay_, - uint256 maxDeltaTimeToPausetokenRateUpdates_ + uint256 maxAllowedTokenRateDeviationPerDayBp_, + uint256 oldestRateAllowedInPauseTimeSpan_, + uint256 maxAllowedTimeBetweenTokenRateUpdates_ ) CrossDomainEnabled(messenger_) { if (l2ERC20TokenBridge_ == address(0)) { revert ErrorZeroAddressL2ERC20TokenBridge(); @@ -103,15 +111,16 @@ contract TokenRateOracle is ITokenRateOracle, CrossDomainEnabled, AccessControl, if (l1TokenRatePusher_ == address(0)) { revert ErrorZeroAddressL1TokenRatePusher(); } - if (maxAllowedTokenRateDeviationPerDay_ > BASIS_POINT_SCALE) { + if (maxAllowedTokenRateDeviationPerDayBp_ > BASIS_POINT_SCALE) { revert ErrorMaxTokenRateDeviationIsOutOfRange(); } L2_ERC20_TOKEN_BRIDGE = l2ERC20TokenBridge_; L1_TOKEN_RATE_PUSHER = l1TokenRatePusher_; TOKEN_RATE_OUTDATED_DELAY = tokenRateOutdatedDelay_; MAX_ALLOWED_L2_TO_L1_CLOCK_LAG = maxAllowedL2ToL1ClockLag_; - MAX_ALLOWED_TOKEN_RATE_DEVIATION_PER_DAY = maxAllowedTokenRateDeviationPerDay_; - MAX_DELTA_TIME_TO_PAUSE_TOKEN_RATE_UPDATES = maxDeltaTimeToPausetokenRateUpdates_; + MAX_ALLOWED_TOKEN_RATE_DEVIATION_PER_DAY_BP = maxAllowedTokenRateDeviationPerDayBp_; + OLDEST_RATE_ALLOWED_IN_PAUSE_TIME_SPAN = oldestRateAllowedInPauseTimeSpan_; + MAX_ALLOWED_TIME_BETWEEEN_TOKEN_RATE_UPDATES = maxAllowedTimeBetweenTokenRateUpdates_; } /// @notice Initializes the contract from scratch. @@ -119,24 +128,24 @@ contract TokenRateOracle is ITokenRateOracle, CrossDomainEnabled, AccessControl, /// @param tokenRate_ wstETH/stETH token rate, uses 10**DECIMALS precision. /// @param rateUpdateL1Timestamp_ L1 time when rate was updated on L1 side. function initialize(address admin_, uint256 tokenRate_, uint256 rateUpdateL1Timestamp_) external { + _initializeContractVersionTo(1); if (admin_ == address(0)) { revert ErrorZeroAddressAdmin(); } - if (tokenRate_ < MIN_ALLOWED_TOKEN_RATE || tokenRate_ > MAX_ALLOWED_TOKEN_RATE) { - revert ErrorTokenRateInitializationIsOutOfAllowedRange(tokenRate_); + if (tokenRate_ < MIN_SANE_TOKEN_RATE || tokenRate_ > MAX_SANE_TOKEN_RATE) { + revert ErrorTokenRateInitializationIsOutOfSaneRange(tokenRate_); } if (rateUpdateL1Timestamp_ > block.timestamp + MAX_ALLOWED_L2_TO_L1_CLOCK_LAG) { revert ErrorL1TimestampInitializationIsOutOfAllowedRange(rateUpdateL1Timestamp_); } - _initializeContractVersionTo(1); _grantRole(DEFAULT_ADMIN_ROLE, admin_); - _addTokenRate(uint128(tokenRate_), uint64(rateUpdateL1Timestamp_), uint64(block.timestamp)); + _addTokenRate(tokenRate_, rateUpdateL1Timestamp_, block.timestamp); } /// @notice Pauses token rate updates and sets the old rate provided by tokenRateIndex_. /// Should be called by DAO or emergency breaks only. /// @param tokenRateIndex_ The index of the token rate that applies after the pause. - /// Token Rate can't be received older then MAX_DELTA_TIME_TO_PAUSE_TOKEN_RATE_UPDATES + /// Token Rate can't be received older then OLDEST_RATE_ALLOWED_IN_PAUSE_TIME_SPAN /// except only if the passed index is the latest one. function pauseTokenRateUpdates(uint256 tokenRateIndex_) external onlyRole(RATE_UPDATE_DISABLER_ROLE) { if (_isPaused()) { @@ -144,28 +153,28 @@ contract TokenRateOracle is ITokenRateOracle, CrossDomainEnabled, AccessControl, } TokenRateData memory tokenRateData = _getTokenRateByIndex(tokenRateIndex_); if (tokenRateIndex_ != _getStorageTokenRates().length - 1 && - tokenRateData.rateReceivedL2Timestamp < block.timestamp - MAX_DELTA_TIME_TO_PAUSE_TOKEN_RATE_UPDATES) { + tokenRateData.rateReceivedL2Timestamp < block.timestamp - OLDEST_RATE_ALLOWED_IN_PAUSE_TIME_SPAN) { revert ErrorTokenRateUpdateTooOld(); } _removeElementsAfterIndex(tokenRateIndex_); - PAUSE_TOKEN_RATE_UPDATES.setStorageBool(true); + _setPause(true); emit TokenRateUpdatesPaused(tokenRateData.tokenRate, tokenRateData.rateUpdateL1Timestamp); emit RateUpdated(tokenRateData.tokenRate, tokenRateData.rateUpdateL1Timestamp); } - /// @notice Unpauses token rate updates applying provided token rate. Should be called by DAO only. + /// @notice Resume token rate updates applying provided token rate. /// @param tokenRate_ a new token rate that applies after unpausing. /// @param rateUpdateL1Timestamp_ L1 time when rate was updated on L1 side. - function unpauseTokenRateUpdates( + function resumeTokenRateUpdates( uint256 tokenRate_, uint256 rateUpdateL1Timestamp_ ) external onlyRole(RATE_UPDATE_ENABLER_ROLE) { if (!_isPaused()) { - revert ErrorAlreadyUnpaused(); + revert ErrorAlreadyResumed(); } - _addTokenRate(uint128(tokenRate_), uint64(rateUpdateL1Timestamp_), uint64(block.timestamp)); - PAUSE_TOKEN_RATE_UPDATES.setStorageBool(false); - emit TokenRateUpdatesUnpaused(tokenRate_, rateUpdateL1Timestamp_); + _addTokenRate(tokenRate_, rateUpdateL1Timestamp_, block.timestamp); + _setPause(false); + emit TokenRateUpdatesResumed(tokenRate_, rateUpdateL1Timestamp_); emit RateUpdated(tokenRate_, rateUpdateL1Timestamp_); } @@ -246,6 +255,13 @@ contract TokenRateOracle is ITokenRateOracle, CrossDomainEnabled, AccessControl, return; } + /// @dev This condition was made under the assumption that the L1 timestamps can be hacked. + /// Normally L1 timestamps (oracle reports) can't be less than 24 hours. + if (rateUpdateL1Timestamp_ < tokenRateData.rateUpdateL1Timestamp + MAX_ALLOWED_TIME_BETWEEEN_TOKEN_RATE_UPDATES) { + emit UpdateRateIsTooOften(); + return; + } + /// @dev allow token rate to be within some configurable range that depens on time it wasn't updated. if ((tokenRate_ != tokenRateData.tokenRate) && !_isTokenRateWithinAllowedRange( tokenRateData.tokenRate, @@ -261,13 +277,14 @@ contract TokenRateOracle is ITokenRateOracle, CrossDomainEnabled, AccessControl, emit TokenRateL1TimestampIsInFuture(tokenRate_, rateUpdateL1Timestamp_); } - _addTokenRate(uint128(tokenRate_), uint64(rateUpdateL1Timestamp_), uint64(block.timestamp)); + _addTokenRate(tokenRate_, rateUpdateL1Timestamp_, block.timestamp); emit RateUpdated(tokenRate_, rateUpdateL1Timestamp_); } /// @notice Returns flag that shows that token rate can be considered outdated. function isLikelyOutdated() external view returns (bool) { - return block.timestamp > _getLastTokenRate().rateReceivedL2Timestamp + TOKEN_RATE_OUTDATED_DELAY; + return (block.timestamp > _getLastTokenRate().rateReceivedL2Timestamp + TOKEN_RATE_OUTDATED_DELAY) || + _isPaused(); } /// @notice Allow tokenRate deviation from the previous value to be @@ -290,7 +307,7 @@ contract TokenRateOracle is ITokenRateOracle, CrossDomainEnabled, AccessControl, ) internal view returns (uint256) { uint256 rateL1TimestampDiff = newRateL1Timestamp_ - currentRateL1Timestamp_; uint256 roundedUpNumberOfDays = (rateL1TimestampDiff + ONE_DAY_SECONDS - 1) / ONE_DAY_SECONDS; - return roundedUpNumberOfDays * MAX_ALLOWED_TOKEN_RATE_DEVIATION_PER_DAY; + return roundedUpNumberOfDays * MAX_ALLOWED_TOKEN_RATE_DEVIATION_PER_DAY_BP; } /// @dev Returns the maximum allowable value for the token rate. @@ -300,7 +317,7 @@ contract TokenRateOracle is ITokenRateOracle, CrossDomainEnabled, AccessControl, ) internal pure returns (uint256) { uint256 maxTokenRateLimit = currentTokenRate * (BASIS_POINT_SCALE + allowedTokenRateDeviation) / BASIS_POINT_SCALE; - return Math.min(maxTokenRateLimit, MAX_ALLOWED_TOKEN_RATE); + return Math.min(maxTokenRateLimit, MAX_SANE_TOKEN_RATE); } /// @dev Returns the minimum allowable value for the token rate. @@ -308,12 +325,12 @@ contract TokenRateOracle is ITokenRateOracle, CrossDomainEnabled, AccessControl, uint256 currentTokenRate, uint256 allowedTokenRateDeviation ) internal pure returns (uint256) { - uint256 minTokenRateLimit = MIN_ALLOWED_TOKEN_RATE; + uint256 minTokenRateLimit = MIN_SANE_TOKEN_RATE; if (allowedTokenRateDeviation <= BASIS_POINT_SCALE) { minTokenRateLimit = (currentTokenRate * (BASIS_POINT_SCALE - allowedTokenRateDeviation) / BASIS_POINT_SCALE); } - return Math.max(minTokenRateLimit, MIN_ALLOWED_TOKEN_RATE); + return Math.max(minTokenRateLimit, MIN_SANE_TOKEN_RATE); } function _isCallerBridgeOrMessengerWithTokenRatePusher(address caller_) internal view returns (bool) { @@ -327,19 +344,16 @@ contract TokenRateOracle is ITokenRateOracle, CrossDomainEnabled, AccessControl, } function _addTokenRate( - uint128 tokenRate_, uint64 rateUpdateL1Timestamp_, uint64 rateReceivedL2Timestamp_ + uint256 tokenRate_, uint256 rateUpdateL1Timestamp_, uint256 rateReceivedL2Timestamp_ ) internal { _getStorageTokenRates().push(TokenRateData({ - tokenRate: tokenRate_, - rateUpdateL1Timestamp: rateUpdateL1Timestamp_, - rateReceivedL2Timestamp: rateReceivedL2Timestamp_ + tokenRate: uint128(tokenRate_), + rateUpdateL1Timestamp: uint64(rateUpdateL1Timestamp_), + rateReceivedL2Timestamp: uint64(rateReceivedL2Timestamp_) })); } function _getLastTokenRate() internal view returns (TokenRateData storage) { - if (_getStorageTokenRates().length == 0) { - revert ErrorNoTokenRateUpdates(); - } return _getTokenRateByIndex(_getStorageTokenRates().length - 1); } @@ -357,22 +371,25 @@ contract TokenRateOracle is ITokenRateOracle, CrossDomainEnabled, AccessControl, } } + /// @dev tokenRateIndex_ is limited by time in the past and the number of elements also has restrictions. + /// Therefore, this loop can't consume a lot of gas. function _removeElementsAfterIndex(uint256 tokenRateIndex_) internal { uint256 tokenRatesLength = _getStorageTokenRates().length; - if (tokenRateIndex_ < tokenRatesLength) { - uint256 numberOfElementsToRemove = tokenRatesLength - tokenRateIndex_ - 1; - for (uint256 i = 0; i < numberOfElementsToRemove; i++) { - _getStorageTokenRates().pop(); - } + if (tokenRateIndex_ >= tokenRatesLength) { + return; + } + uint256 numberOfElementsToRemove = tokenRatesLength - tokenRateIndex_ - 1; + for (uint256 i = 0; i < numberOfElementsToRemove; i++) { + _getStorageTokenRates().pop(); } } function _setPause(bool pause) internal { - PAUSE_TOKEN_RATE_UPDATES.setStorageBool(pause); + PAUSE_TOKEN_RATE_UPDATES_SLOT.setStorageBool(pause); } function _isPaused() internal view returns (bool) { - return PAUSE_TOKEN_RATE_UPDATES.getStorageBool(); + return PAUSE_TOKEN_RATE_UPDATES_SLOT.getStorageBool(); } modifier onlyBridgeOrTokenRatePusher() { @@ -387,21 +404,21 @@ contract TokenRateOracle is ITokenRateOracle, CrossDomainEnabled, AccessControl, event DormantTokenRateUpdateIgnored(uint256 indexed newRateL1Timestamp_, uint256 indexed currentRateL1Timestamp_); event TokenRateL1TimestampIsInFuture(uint256 tokenRate_, uint256 indexed rateL1Timestamp_); event TokenRateUpdatesPaused(uint256 tokenRate_, uint256 indexed rateL1Timestamp_); - event TokenRateUpdatesUnpaused(uint256 tokenRate_, uint256 indexed rateL1Timestamp_); + event TokenRateUpdatesResumed(uint256 tokenRate_, uint256 indexed rateL1Timestamp_); event TokenRateUpdateAttemptDuringPause(uint256 tokenRate_, uint256 indexed rateL1Timestamp_); + event UpdateRateIsTooOften(); error ErrorZeroAddressAdmin(); - error ErrorNoTokenRateUpdates(); error ErrorWrongTokenRateIndex(); error ErrorTokenRateUpdateTooOld(); error ErrorAlreadyPaused(); - error ErrorAlreadyUnpaused(); + error ErrorAlreadyResumed(); error ErrorZeroAddressL2ERC20TokenBridge(); error ErrorZeroAddressL1TokenRatePusher(); error ErrorNotBridgeOrTokenRatePusher(); error ErrorL1TimestampExceededAllowedClockLag(uint256 tokenRate_, uint256 rateL1Timestamp_); error ErrorTokenRateIsOutOfRange(uint256 tokenRate_, uint256 rateL1Timestamp_); error ErrorMaxTokenRateDeviationIsOutOfRange(); - error ErrorTokenRateInitializationIsOutOfAllowedRange(uint256 tokenRate_); + error ErrorTokenRateInitializationIsOutOfSaneRange(uint256 tokenRate_); error ErrorL1TimestampInitializationIsOutOfAllowedRange(uint256 rateL1Timestamp_); } diff --git a/test/optimism/L2ERC20ExtendedTokensBridge.unit.test.ts b/test/optimism/L2ERC20ExtendedTokensBridge.unit.test.ts index a9365cd7..a00f1130 100644 --- a/test/optimism/L2ERC20ExtendedTokensBridge.unit.test.ts +++ b/test/optimism/L2ERC20ExtendedTokensBridge.unit.test.ts @@ -1215,6 +1215,11 @@ async function ctxFactory() { const totalPooledEther = BigNumber.from('9309904612343950493629678'); const totalShares = BigNumber.from('7975822843597609202337218'); const exchangeRate = getExchangeRate(tokenRateDecimals, totalPooledEther, totalShares); + const tokenRateOutdatedDelay = 86400; + const maxAllowedL2ToL1ClockLag = BigNumber.from(86400); + const maxAllowedTokenRateDeviationPerDay = BigNumber.from(500); + const oldestRateAllowedInPauseTimeSpan = BigNumber.from(86400*3); + const maxAllowedTimeBetweenTokenRateUpdates = BigNumber.from(3600); const l2MessengerStub = await new CrossDomainMessengerStub__factory( deployer @@ -1260,10 +1265,11 @@ async function ctxFactory() { l2MessengerStub.address, l2TokenBridgeProxyAddress, l1TokenBridgeEOA.address, - 86400, - 86400, - 500, - 86400*3 + tokenRateOutdatedDelay, + maxAllowedL2ToL1ClockLag, + maxAllowedTokenRateDeviationPerDay, + oldestRateAllowedInPauseTimeSpan, + maxAllowedTimeBetweenTokenRateUpdates ); const provider = await hre.ethers.provider; @@ -1391,6 +1397,11 @@ async function getL2TokenBridgeImpl(deployer: SignerWithAddress, l1TokenBridge: const totalPooledEther = BigNumber.from('9309904612343950493629678'); const totalShares = BigNumber.from('7975822843597609202337218'); const exchangeRate = getExchangeRate(tokenRateDecimals, totalPooledEther, totalShares); + const tokenRateOutdatedDelay = 86400; + const maxAllowedL2ToL1ClockLag = BigNumber.from(86400); + const maxAllowedTokenRateDeviationPerDay = BigNumber.from(500); + const oldestRateAllowedInPauseTimeSpan = BigNumber.from(86400*3); + const maxAllowedTimeBetweenTokenRateUpdates = BigNumber.from(3600); const l2MessengerStub = await new CrossDomainMessengerStub__factory( deployer @@ -1429,10 +1440,11 @@ async function getL2TokenBridgeImpl(deployer: SignerWithAddress, l1TokenBridge: l2MessengerStub.address, l2TokenBridgeProxyAddress, l1TokenBridge, - 86400, - 86400, - 500, - 86400*3 + tokenRateOutdatedDelay, + maxAllowedL2ToL1ClockLag, + maxAllowedTokenRateDeviationPerDay, + oldestRateAllowedInPauseTimeSpan, + maxAllowedTimeBetweenTokenRateUpdates ); const provider = await hre.ethers.provider; diff --git a/test/optimism/TokenRateOracle.unit.test.ts b/test/optimism/TokenRateOracle.unit.test.ts index c4fbdc82..5579b589 100644 --- a/test/optimism/TokenRateOracle.unit.test.ts +++ b/test/optimism/TokenRateOracle.unit.test.ts @@ -22,6 +22,7 @@ unit("TokenRateOracle", ctxFactory) 0, 0, 0, + 0, 0 ), "ErrorZeroAddressMessenger()"); @@ -34,6 +35,7 @@ unit("TokenRateOracle", ctxFactory) 0, 0, 0, + 0, 0 ), "ErrorZeroAddressL2ERC20TokenBridge()"); @@ -46,6 +48,7 @@ unit("TokenRateOracle", ctxFactory) 0, 0, 0, + 0, 0 ), "ErrorZeroAddressL1TokenRatePusher()"); }) @@ -68,7 +71,7 @@ unit("TokenRateOracle", ctxFactory) assert.equal(await tokenRateOracle.L1_TOKEN_RATE_PUSHER(), l1TokenBridgeEOA.address); assert.equalBN(await tokenRateOracle.TOKEN_RATE_OUTDATED_DELAY(), tokenRateOutdatedDelay); assert.equalBN(await tokenRateOracle.MAX_ALLOWED_L2_TO_L1_CLOCK_LAG(), maxAllowedL2ToL1ClockLag); - assert.equalBN(await tokenRateOracle.MAX_ALLOWED_TOKEN_RATE_DEVIATION_PER_DAY(), maxAllowedTokenRateDeviationPerDay); + assert.equalBN(await tokenRateOracle.MAX_ALLOWED_TOKEN_RATE_DEVIATION_PER_DAY_BP(), maxAllowedTokenRateDeviationPerDay); assert.equalBN(await tokenRateOracle.latestAnswer(), tokenRate); const { @@ -100,7 +103,8 @@ unit("TokenRateOracle", ctxFactory) 86400, 86400, 500, - 86400*3 + 86400*3, + 3600 ); const petrifiedVersionMark = hre.ethers.constants.MaxUint256; @@ -128,51 +132,82 @@ unit("TokenRateOracle", ctxFactory) .test("initialize() :: token rate is out of range", async (ctx) => { const { deployer, bridge, l1TokenBridgeEOA } = ctx.accounts; const { l2MessengerStub } = ctx.contracts; - const { blockTimestampOfDeployment } = ctx.constants; - - const tokenRateOracleImpl = await new TokenRateOracle__factory(deployer).deploy( - l2MessengerStub.address, - bridge.address, - l1TokenBridgeEOA.address, - 86400, - 86400, - 500, - 86400*3 - ); + const { + blockTimestampOfDeployment, + tokenRateOutdatedDelay, + maxAllowedL2ToL1ClockLag, + maxAllowedTokenRateDeviationPerDay, + oldestRateAllowedInPauseTimeSpan, + maxAllowedTimeBetweenTokenRateUpdates + } = ctx.constants; - const tokenRateMin = await tokenRateOracleImpl.MIN_ALLOWED_TOKEN_RATE(); - const tokenRateMax = await tokenRateOracleImpl.MAX_ALLOWED_TOKEN_RATE(); + const tokenRateMin = BigNumber.from(10).pow(27-2); + const tokenRateMax = BigNumber.from(10).pow(27+2); await assert.revertsWith( - tokenRateOracleImpl.initialize(deployer.address, tokenRateMin.sub(1), blockTimestampOfDeployment), - "ErrorTokenRateInitializationIsOutOfAllowedRange(" + tokenRateMin.sub(1) + ")" + tokenRateOracleUnderProxy( + deployer, + l2MessengerStub.address, + bridge.address, + l1TokenBridgeEOA.address, + tokenRateOutdatedDelay, + maxAllowedL2ToL1ClockLag, + maxAllowedTokenRateDeviationPerDay, + oldestRateAllowedInPauseTimeSpan, + maxAllowedTimeBetweenTokenRateUpdates, + tokenRateMin.sub(1), + blockTimestampOfDeployment + ), + "ErrorTokenRateInitializationIsOutOfSaneRange(" + tokenRateMin.sub(1) + ")" ); await assert.revertsWith( - tokenRateOracleImpl.initialize(deployer.address, tokenRateMax.add(1), blockTimestampOfDeployment), - "ErrorTokenRateInitializationIsOutOfAllowedRange(" + tokenRateMax.add(1) + ")" + tokenRateOracleUnderProxy( + deployer, + l2MessengerStub.address, + bridge.address, + l1TokenBridgeEOA.address, + tokenRateOutdatedDelay, + maxAllowedL2ToL1ClockLag, + maxAllowedTokenRateDeviationPerDay, + oldestRateAllowedInPauseTimeSpan, + maxAllowedTimeBetweenTokenRateUpdates, + tokenRateMax.add(1), + blockTimestampOfDeployment + ), + "ErrorTokenRateInitializationIsOutOfSaneRange(" + tokenRateMax.add(1) + ")" ); }) .test("initialize() :: time is out of init range", async (ctx) => { const { deployer, bridge, l1TokenBridgeEOA } = ctx.accounts; const { l2MessengerStub } = ctx.contracts; - const { tokenRate, blockTimestampOfDeployment, maxAllowedL2ToL1ClockLag } = ctx.constants; - - const tokenRateOracleImpl = await new TokenRateOracle__factory(deployer).deploy( - l2MessengerStub.address, - bridge.address, - l1TokenBridgeEOA.address, - 86400, - 86400, - 500, - 86400*3 - ); + const { + tokenRate, + blockTimestampOfDeployment, + tokenRateOutdatedDelay, + maxAllowedL2ToL1ClockLag, + maxAllowedTokenRateDeviationPerDay, + oldestRateAllowedInPauseTimeSpan, + maxAllowedTimeBetweenTokenRateUpdates + } = ctx.constants; const wrongTimeMax = blockTimestampOfDeployment.add(maxAllowedL2ToL1ClockLag).add(20); await assert.revertsWith( - tokenRateOracleImpl.initialize(deployer.address, tokenRate, wrongTimeMax), + tokenRateOracleUnderProxy( + deployer, + l2MessengerStub.address, + bridge.address, + l1TokenBridgeEOA.address, + tokenRateOutdatedDelay, + maxAllowedL2ToL1ClockLag, + maxAllowedTokenRateDeviationPerDay, + oldestRateAllowedInPauseTimeSpan, + maxAllowedTimeBetweenTokenRateUpdates, + tokenRate, + wrongTimeMax + ), "ErrorL1TimestampInitializationIsOutOfAllowedRange(" + wrongTimeMax + ")" ); }) @@ -191,7 +226,8 @@ unit("TokenRateOracle", ctxFactory) 86400, 86400, maxAllowedTokenRateDeviationPerDay, - 86400*3 + 86400*3, + 3600 ), "ErrorMaxTokenRateDeviationIsOutOfRange()" ); @@ -237,7 +273,7 @@ unit("TokenRateOracle", ctxFactory) const tx0 = await tokenRateOracle .connect(bridge) - .updateRate(tokenRate, rateL1Timestamp.sub(100)); + .updateRate(tokenRate, rateL1TimestampInPast); await assert.emits(tokenRateOracle, tx0, "DormantTokenRateUpdateIgnored", [ rateL1TimestampInPast, @@ -270,12 +306,32 @@ unit("TokenRateOracle", ctxFactory) assert.equalBN(updatedAt_, updatedAt); }) + .test("updateRate() :: token rate updates too often", async (ctx) => { + const { tokenRateOracle } = ctx.contracts; + const { bridge } = ctx.accounts; + const { tokenRate, rateL1Timestamp, maxAllowedTimeBetweenTokenRateUpdates } = ctx.constants; + + const rateL1TimestampWithinTooOften = rateL1Timestamp.add(maxAllowedTimeBetweenTokenRateUpdates).sub(1); + + const tx0 = await tokenRateOracle + .connect(bridge) + .updateRate(tokenRate, rateL1TimestampWithinTooOften); + + await assert.emits(tokenRateOracle, tx0, "UpdateRateIsTooOften"); + await assert.notEmits(tokenRateOracle, tx0, "RateUpdated"); + }) + .test("updateRate() :: token rate is out of range 1 day", async (ctx) => { const { tokenRateOracle } = ctx.contracts; const { bridge } = ctx.accounts; - const { tokenRate, blockTimestampOfDeployment, maxAllowedTokenRateDeviationPerDay } = ctx.constants; + const { + tokenRate, + blockTimestampOfDeployment, + maxAllowedTokenRateDeviationPerDay, + maxAllowedTimeBetweenTokenRateUpdates + } = ctx.constants; - const blockTimestampForNextUpdate = blockTimestampOfDeployment.add(1000); + const blockTimestampForNextUpdate = blockTimestampOfDeployment.add(maxAllowedTimeBetweenTokenRateUpdates).add(1); const tokenRateTooBig = tokenRate.mul( BigNumber.from('10000') .add(maxAllowedTokenRateDeviationPerDay) @@ -358,7 +414,7 @@ unit("TokenRateOracle", ctxFactory) .test("updateRate() :: token rate limits", async (ctx) => { const { deployer, bridge, l1TokenBridgeEOA } = ctx.accounts; - const { tokenRate } = ctx.constants; + const { tokenRate, maxAllowedTimeBetweenTokenRateUpdates } = ctx.constants; const tokenRateOutdatedDelay = BigNumber.from(86400); // 1 day const maxAllowedL2ToL1ClockLag = BigNumber.from(86400 * 2); // 2 days @@ -377,27 +433,28 @@ unit("TokenRateOracle", ctxFactory) maxAllowedL2ToL1ClockLag, maxAllowedTokenRateDeviationPerDay, BigNumber.from(86400*3), + BigNumber.from(3600), tokenRate, BigNumber.from(0) ); - const maxAllowedTokenRate = await tokenRateOracle.MAX_ALLOWED_TOKEN_RATE(); - await tokenRateOracle.connect(bridge).updateRate(maxAllowedTokenRate, blockTimestampOfDeployment.add(1000)); + const maxAllowedTokenRate = await tokenRateOracle.MAX_SANE_TOKEN_RATE(); + await tokenRateOracle.connect(bridge).updateRate(maxAllowedTokenRate, blockTimestampOfDeployment.add(maxAllowedTimeBetweenTokenRateUpdates).add(1)); assert.equalBN(await tokenRateOracle.latestAnswer(), maxAllowedTokenRate); - const minAllowedTokenRate = await tokenRateOracle.MIN_ALLOWED_TOKEN_RATE(); - await tokenRateOracle.connect(bridge).updateRate(minAllowedTokenRate, blockTimestampOfDeployment.add(2000)); + const minAllowedTokenRate = await tokenRateOracle.MIN_SANE_TOKEN_RATE(); + await tokenRateOracle.connect(bridge).updateRate(minAllowedTokenRate, blockTimestampOfDeployment.add(maxAllowedTimeBetweenTokenRateUpdates.mul(2)).add(1)); assert.equalBN(await tokenRateOracle.latestAnswer(), minAllowedTokenRate); }) .test("updateRate() :: happy path called by bridge", async (ctx) => { const { tokenRateOracle } = ctx.contracts; const { bridge } = ctx.accounts; - const { tokenRate, blockTimestampOfDeployment } = ctx.constants; + const { tokenRate, blockTimestampOfDeployment, maxAllowedTimeBetweenTokenRateUpdates } = ctx.constants; const newTokenRate = tokenRate.mul(BigNumber.from('104')).div(BigNumber.from('100')); // 104% - const blockTimestampInFuture = blockTimestampOfDeployment.add(1000); + const blockTimestampInFuture = blockTimestampOfDeployment.add(maxAllowedTimeBetweenTokenRateUpdates).add(1); const tx = await tokenRateOracle.connect(bridge).updateRate(newTokenRate, blockTimestampInFuture); await assert.emits(tokenRateOracle, tx, "TokenRateL1TimestampIsInFuture", [ @@ -432,13 +489,13 @@ unit("TokenRateOracle", ctxFactory) .test("updateRate() :: happy path called by messenger with correct cross-domain sender", async (ctx) => { const { tokenRateOracle, l2MessengerStub } = ctx.contracts; const { l2MessengerStubEOA, l1TokenBridgeEOA } = ctx.accounts; - const { tokenRate, blockTimestampOfDeployment } = ctx.constants; + const { tokenRate, blockTimestampOfDeployment, maxAllowedTimeBetweenTokenRateUpdates } = ctx.constants; await l2MessengerStub.setXDomainMessageSender(l1TokenBridgeEOA.address); const newTokenRate = tokenRate.mul(BigNumber.from('104')).div(BigNumber.from('100')); // 104% - const blockTimestampInFuture = blockTimestampOfDeployment.add(1000); + const blockTimestampInFuture = blockTimestampOfDeployment.add(maxAllowedTimeBetweenTokenRateUpdates).add(1); const tx = await tokenRateOracle.connect(l2MessengerStubEOA).updateRate(newTokenRate, blockTimestampInFuture); await assert.emits(tokenRateOracle, tx, "TokenRateL1TimestampIsInFuture", [ @@ -470,9 +527,6 @@ unit("TokenRateOracle", ctxFactory) assert.equalBN(answeredInRound_, blockTimestampInFuture); }) - // correct index - // too old time - // success + state and events + check event when update .test("pauseTokenRateUpdates() :: wrong role", async (ctx) => { const { tokenRateOracle } = ctx.contracts; const { deployer } = ctx.accounts; @@ -505,8 +559,8 @@ unit("TokenRateOracle", ctxFactory) const { tokenRate, tokenRateOutdatedDelay, maxAllowedL2ToL1ClockLag, maxAllowedTokenRateDeviationPerDay } = ctx.constants; const { l2MessengerStub } = ctx.contracts; - /// create new Oracle with 0 maxDeltaTimeToPausetokenRateUpdates - const maxDeltaTimeToPausetokenRateUpdates = BigNumber.from(0); + /// create new Oracle with 0 maxDeltaTimeToPauseTokenRateUpdates + const maxDeltaTimeToPauseTokenRateUpdates = BigNumber.from(0); const { tokenRateOracle, blockTimestampOfDeployment } = await tokenRateOracleUnderProxy( deployer, l2MessengerStub.address, @@ -515,9 +569,10 @@ unit("TokenRateOracle", ctxFactory) tokenRateOutdatedDelay, maxAllowedL2ToL1ClockLag, maxAllowedTokenRateDeviationPerDay, - maxDeltaTimeToPausetokenRateUpdates, + maxDeltaTimeToPauseTokenRateUpdates, + BigNumber.from(3600), tokenRate, - BigNumber.from(0) + BigNumber.from(0), ); const disablerRole = await tokenRateOracle.RATE_UPDATE_DISABLER_ROLE(); @@ -534,7 +589,7 @@ unit("TokenRateOracle", ctxFactory) .test("pauseTokenRateUpdates() :: success", async (ctx) => { const { tokenRateOracle } = ctx.contracts; const { multisig, bridge } = ctx.accounts; - const { tokenRate, blockTimestampOfDeployment } = ctx.constants; + const { tokenRate, blockTimestampOfDeployment, maxAllowedTimeBetweenTokenRateUpdates } = ctx.constants; const disablerRole = await tokenRateOracle.RATE_UPDATE_DISABLER_ROLE(); await tokenRateOracle.grantRole(disablerRole, multisig.address); @@ -544,10 +599,10 @@ unit("TokenRateOracle", ctxFactory) const tokenRate2 = tokenRate.add(300); const tokenRate3 = tokenRate.add(300); - const blockTimestampOfDeployment0 = blockTimestampOfDeployment.add(1000); - const blockTimestampOfDeployment1 = blockTimestampOfDeployment.add(2000); - const blockTimestampOfDeployment2 = blockTimestampOfDeployment.add(3000); - const blockTimestampOfDeployment3 = blockTimestampOfDeployment.add(4000); + const blockTimestampOfDeployment0 = blockTimestampOfDeployment.add(maxAllowedTimeBetweenTokenRateUpdates).add(1); + const blockTimestampOfDeployment1 = blockTimestampOfDeployment.add(maxAllowedTimeBetweenTokenRateUpdates.mul(2)).add(1); + const blockTimestampOfDeployment2 = blockTimestampOfDeployment.add(maxAllowedTimeBetweenTokenRateUpdates.mul(3)).add(1); + const blockTimestampOfDeployment3 = blockTimestampOfDeployment.add(maxAllowedTimeBetweenTokenRateUpdates.mul(4)).add(1); const tx0 = await tokenRateOracle.connect(bridge).updateRate(tokenRate0, blockTimestampOfDeployment0); await assert.emits(tokenRateOracle, tx0, "RateUpdated", [tokenRate0, blockTimestampOfDeployment0]); @@ -580,10 +635,7 @@ unit("TokenRateOracle", ctxFactory) ); }) - // unpause - // wrong role - // success + state and events + updates goes as before - .test("unpauseTokenRateUpdates() :: wrong role", async (ctx) => { + .test("resumeTokenRateUpdates() :: wrong role", async (ctx) => { const { tokenRateOracle } = ctx.contracts; const { deployer } = ctx.accounts; const { tokenRate, blockTimestampOfDeployment } = ctx.constants; @@ -592,15 +644,15 @@ unit("TokenRateOracle", ctxFactory) const account = deployer.address.toLowerCase(); await assert.revertsWith( - tokenRateOracle.unpauseTokenRateUpdates(tokenRate, blockTimestampOfDeployment.add(1000)), + tokenRateOracle.resumeTokenRateUpdates(tokenRate, blockTimestampOfDeployment.add(1000)), "AccessControl: account " + account + " is missing role " + enablerRole ); }) - .test("unpauseTokenRateUpdates() :: success", async (ctx) => { + .test("resumeTokenRateUpdates() :: success", async (ctx) => { const { tokenRateOracle } = ctx.contracts; const { multisig, bridge } = ctx.accounts; - const { tokenRate, blockTimestampOfDeployment } = ctx.constants; + const { tokenRate, blockTimestampOfDeployment, maxAllowedTimeBetweenTokenRateUpdates } = ctx.constants; const disablerRole = await tokenRateOracle.RATE_UPDATE_DISABLER_ROLE(); await tokenRateOracle.grantRole(disablerRole, multisig.address); @@ -608,17 +660,17 @@ unit("TokenRateOracle", ctxFactory) const enablerRole = await tokenRateOracle.RATE_UPDATE_ENABLER_ROLE(); await tokenRateOracle.grantRole(enablerRole, multisig.address); - await tokenRateOracle.connect(bridge).updateRate(tokenRate, blockTimestampOfDeployment.add(1000)); + await tokenRateOracle.connect(bridge).updateRate(tokenRate, blockTimestampOfDeployment.add(maxAllowedTimeBetweenTokenRateUpdates).add(1)); assert.isFalse(await tokenRateOracle.isTokenRateUpdatesPaused()); await tokenRateOracle.connect(multisig).pauseTokenRateUpdates(0); assert.isTrue(await tokenRateOracle.isTokenRateUpdatesPaused()); - const unpauseTx = await tokenRateOracle.connect(multisig).unpauseTokenRateUpdates(tokenRate, blockTimestampOfDeployment); + const unpauseTx = await tokenRateOracle.connect(multisig).resumeTokenRateUpdates(tokenRate, blockTimestampOfDeployment); assert.isFalse(await tokenRateOracle.isTokenRateUpdatesPaused()); await assert.emits( tokenRateOracle, unpauseTx, - "TokenRateUpdatesUnpaused", + "TokenRateUpdatesResumed", [tokenRate, blockTimestampOfDeployment] ); await assert.emits(tokenRateOracle, @@ -627,11 +679,12 @@ unit("TokenRateOracle", ctxFactory) [tokenRate, blockTimestampOfDeployment] ); - const tx = await tokenRateOracle.connect(bridge).updateRate(tokenRate.add(100), blockTimestampOfDeployment.add(1000)); + const newTime = blockTimestampOfDeployment.add(maxAllowedTimeBetweenTokenRateUpdates.mul(2)).add(1); + const tx = await tokenRateOracle.connect(bridge).updateRate(tokenRate.add(100), newTime); await assert.emits(tokenRateOracle, tx, "RateUpdated", - [tokenRate.add(100), blockTimestampOfDeployment.add(1000)] + [tokenRate.add(100), newTime] ); }) @@ -643,10 +696,13 @@ async function ctxFactory() { /// --------------------------- const decimals = 27; const provider = await hre.ethers.provider; - const tokenRate = BigNumber.from('1164454276599657236000000000'); // value taken from real contact on 23.04.24 - const tokenRateOutdatedDelay = BigNumber.from(86400); // 1 day - const maxAllowedL2ToL1ClockLag = BigNumber.from(86400 * 2); // 2 days - const maxAllowedTokenRateDeviationPerDay = BigNumber.from(500); // 5% + const tokenRate = BigNumber.from('1164454276599657236000000000'); // value taken from real contact on 23.04.24 + const tokenRateOutdatedDelay = BigNumber.from(86400); // 1 day + const maxAllowedL2ToL1ClockLag = BigNumber.from(86400 * 2); // 2 days + const maxAllowedTokenRateDeviationPerDay = BigNumber.from(500); // 5% + const oldestRateAllowedInPauseTimeSpan = BigNumber.from(86400*3); // 3 days + const maxAllowedTimeBetweenTokenRateUpdates = BigNumber.from(3600); // 1 hour + const [deployer, bridge, stranger, l1TokenBridgeEOA, multisig] = await hre.ethers.getSigners(); const zero = await hre.ethers.getSigner(hre.ethers.constants.AddressZero); @@ -667,7 +723,8 @@ async function ctxFactory() { tokenRateOutdatedDelay, maxAllowedL2ToL1ClockLag, maxAllowedTokenRateDeviationPerDay, - BigNumber.from(86400*3), + oldestRateAllowedInPauseTimeSpan, + maxAllowedTimeBetweenTokenRateUpdates, tokenRate, rateL1Timestamp ); @@ -693,7 +750,9 @@ async function ctxFactory() { blockTimestampOfDeployment, tokenRateOutdatedDelay, maxAllowedL2ToL1ClockLag, - maxAllowedTokenRateDeviationPerDay + maxAllowedTokenRateDeviationPerDay, + oldestRateAllowedInPauseTimeSpan, + maxAllowedTimeBetweenTokenRateUpdates }, provider }; diff --git a/test/optimism/pushingTokenRate.integration.test.ts b/test/optimism/pushingTokenRate.integration.test.ts index c354e7aa..cab7d3da 100644 --- a/test/optimism/pushingTokenRate.integration.test.ts +++ b/test/optimism/pushingTokenRate.integration.test.ts @@ -111,6 +111,8 @@ async function ctxFactory() { const tokenRateOutdatedDelay = 86400; const maxAllowedL2ToL1ClockLag = BigNumber.from(86400); const maxAllowedTokenRateDeviationPerDay = BigNumber.from(500); + const oldestRateAllowedInPauseTimeSpan = BigNumber.from(86400*3); + const maxAllowedTimeBetweenTokenRateUpdates = BigNumber.from(3600); const totalPooledEther = BigNumber.from('9309904612343950493629678'); const totalShares = BigNumber.from('7975822843597609202337218'); const tokenRateDecimals = BigNumber.from(27); @@ -192,7 +194,9 @@ async function ctxFactory() { contractsShift: 0, tokenRateOracle: { maxAllowedL2ToL1ClockLag: maxAllowedL2ToL1ClockLag, - maxAllowedTokenRateDeviationPerDay: maxAllowedTokenRateDeviationPerDay, + maxAllowedTokenRateDeviationPerDayBp: maxAllowedTokenRateDeviationPerDay, + oldestRateAllowedInPauseTimeSpan: oldestRateAllowedInPauseTimeSpan, + maxAllowedTimeBetweenTokenRateUpdates: maxAllowedTimeBetweenTokenRateUpdates, tokenRate: tokenRate, l1Timestamp: BigNumber.from(blockTimestampInPast) } diff --git a/test/token/ERC20Permit.unit.test.ts b/test/token/ERC20Permit.unit.test.ts index a590ea54..9ce167a6 100644 --- a/test/token/ERC20Permit.unit.test.ts +++ b/test/token/ERC20Permit.unit.test.ts @@ -430,7 +430,8 @@ async function tokenProxied( 86400, 86400, 500, - 86400*3 + 86400*3, + 3600 ); const provider = await hre.ethers.provider; const blockNumber = await provider.getBlockNumber(); diff --git a/test/token/ERC20RebasableBridgedPermit.unit.test.ts b/test/token/ERC20RebasableBridgedPermit.unit.test.ts index db00660b..75e3fdea 100644 --- a/test/token/ERC20RebasableBridgedPermit.unit.test.ts +++ b/test/token/ERC20RebasableBridgedPermit.unit.test.ts @@ -39,7 +39,8 @@ unit("ERC20RebasableBridgedPermit", ctxFactory) tokenRateOutdatedDelay, maxAllowedL2ToL1ClockLag, maxAllowedTokenRateDeviationPerDay, - 86400*3 + BigNumber.from(86400*3), + BigNumber.from(3600) ); await assert.revertsWith(new ERC20RebasableBridgedPermit__factory( @@ -125,7 +126,8 @@ unit("ERC20RebasableBridgedPermit", ctxFactory) 86400, 86400, 500, - 86400*3 + 86400*3, + 3600 ); const rebasableTokenImpl = await new ERC20RebasableBridgedPermit__factory(deployer).deploy( "stETH Test Token", @@ -165,7 +167,8 @@ unit("ERC20RebasableBridgedPermit", ctxFactory) 86400, 86400, 500, - 86400*3 + 86400*3, + 3600 ); const rebasableTokenImpl = await new ERC20RebasableBridgedPermit__factory(deployer).deploy( "name", @@ -222,7 +225,8 @@ unit("ERC20RebasableBridgedPermit", ctxFactory) 86400, 86400, 500, - 86400*3 + 86400*3, + 3600 ); const rebasableTokenImpl = await new ERC20RebasableBridgedPermit__factory(deployer).deploy( "name", @@ -272,45 +276,6 @@ unit("ERC20RebasableBridgedPermit", ctxFactory) await assert.revertsWith(rebasableProxied.connect(user1).wrap(0), "ErrorZeroSharesWrap()"); }) - .test("wrap() :: wrong oracle update time", async (ctx) => { - - const { deployer, user1, owner, zero, messenger, l1TokenRatePusher } = ctx.accounts; - const { decimals, tokenRate } = ctx.constants; - - // deploy new implementation to test initial oracle state - const wrappedToken = await new ERC20BridgedPermit__factory(deployer).deploy( - "WsETH Test Token", - "WsETH", - "1", - decimals, - owner.address - ); - const tokenRateOracleImpl = await new TokenRateOracle__factory(deployer).deploy( - messenger.address, - owner.address, - l1TokenRatePusher.address, - 86400, - 86400, - 500, - 86400*3 - ); - - const rebasableProxied = await new ERC20RebasableBridgedPermit__factory(deployer).deploy( - "name", - "symbol", - "1", - 10, - wrappedToken.address, - tokenRateOracleImpl.address, - owner.address - ); - - await wrappedToken.connect(owner).bridgeMint(user1.address, 1000); - await wrappedToken.connect(user1).approve(rebasableProxied.address, 1000); - - await assert.revertsWith(rebasableProxied.connect(user1).wrap(5), "ErrorNoTokenRateUpdates()"); - }) - .test("wrap() :: when no balance", async (ctx) => { const { rebasableProxied, wrappedToken } = ctx.contracts; const { user1 } = ctx.accounts; @@ -387,44 +352,6 @@ unit("ERC20RebasableBridgedPermit", ctxFactory) await assert.revertsWith(rebasableProxied.connect(user1).unwrap(0), "ErrorZeroTokensUnwrap()"); }) - .test("unwrap() :: with wrong oracle update time", async (ctx) => { - - const { deployer, user1, owner, zero, messenger, l1TokenRatePusher } = ctx.accounts; - const { decimals } = ctx.constants; - - // deploy new implementation to test initial oracle state - const wrappedToken = await new ERC20BridgedPermit__factory(deployer).deploy( - "WsETH Test Token", - "WsETH", - "1", - decimals, - owner.address - ); - const tokenRateOracle = await new TokenRateOracle__factory(deployer).deploy( - messenger.address, - owner.address, - l1TokenRatePusher.address, - 86400, - 86400, - 500, - 86400*3 - ); - const rebasableProxied = await new ERC20RebasableBridgedPermit__factory(deployer).deploy( - "name", - "symbol", - "1", - 10, - wrappedToken.address, - tokenRateOracle.address, - owner.address - ); - - await wrappedToken.connect(owner).bridgeMint(user1.address, 1000); - await wrappedToken.connect(user1).approve(rebasableProxied.address, 1000); - - await assert.revertsWith(rebasableProxied.connect(user1).unwrap(5), "ErrorNoTokenRateUpdates()"); - }) - .test("unwrap() :: when no balance", async (ctx) => { const { rebasableProxied } = ctx.contracts; const { user1 } = ctx.accounts; @@ -572,46 +499,6 @@ unit("ERC20RebasableBridgedPermit", ctxFactory) await assert.revertsWith(rebasableProxied.connect(owner).bridgeWrap(user1.address, 0), "ErrorZeroSharesWrap()"); }) - .test("bridgeWrap() :: wrong oracle update time", async (ctx) => { - const { deployer, user1, owner, zero, messenger, l1TokenRatePusher } = ctx.accounts; - const { decimals } = ctx.constants; - - // deploy new implementation to test initial oracle state - const wrappedToken = await new ERC20BridgedPermit__factory(deployer).deploy( - "WsETH Test Token", - "WsETH", - "1", - decimals, - owner.address - ); - const tokenRateOracle = await new TokenRateOracle__factory(deployer).deploy( - messenger.address, - owner.address, - l1TokenRatePusher.address, - 86400, - 86400, - 500, - 86400*3 - ); - const rebasableProxied = await new ERC20RebasableBridgedPermit__factory(deployer).deploy( - "name", - "symbol", - "1", - 10, - wrappedToken.address, - tokenRateOracle.address, - owner.address - ); - - await wrappedToken.connect(owner).bridgeMint(owner.address, 1000); - await wrappedToken.connect(owner).approve(rebasableProxied.address, 1000); - - await assert.revertsWith( - rebasableProxied.connect(owner).bridgeWrap(user1.address, 5), - "ErrorNoTokenRateUpdates()" - ); - }) - .test("bridgeWrap() :: when no balance", async (ctx) => { const { rebasableProxied, wrappedToken } = ctx.contracts; const { owner, user1 } = ctx.accounts; @@ -702,42 +589,6 @@ unit("ERC20RebasableBridgedPermit", ctxFactory) await assert.revertsWith(rebasableProxied.connect(owner).bridgeUnwrap(user1.address, wei`4 ether`), "ErrorNotEnoughBalance()"); }) - .test("bridgeUnwrap() :: with wrong oracle update time", async (ctx) => { - - const { deployer, user1, owner, zero, messenger, l1TokenRatePusher } = ctx.accounts; - const { decimals } = ctx.constants; - - // deploy new implementation to test initial oracle state - const wrappedToken = await new ERC20BridgedPermit__factory(deployer).deploy( - "WsETH Test Token", - "WsETH", - "1", - decimals, - owner.address - ); - const tokenRateOracle = await new TokenRateOracle__factory(deployer).deploy( - messenger.address, - owner.address, - l1TokenRatePusher.address, - 86400, - 86400, - 500, - 86400*3 - ); - const rebasableProxied = await new ERC20RebasableBridgedPermit__factory(deployer).deploy( - "name", - "symbol", - "1", - 10, - wrappedToken.address, - tokenRateOracle.address, - owner.address - ); - - await wrappedToken.connect(owner).bridgeMint(user1.address, 1000); - await assert.revertsWith(rebasableProxied.connect(owner).bridgeUnwrap(user1.address, 5), "ErrorNoTokenRateUpdates()"); - }) - .test("bridgeUnwrap() :: happy path", async (ctx) => { const { rebasableProxied, wrappedToken } = ctx.contracts; @@ -1411,6 +1262,7 @@ async function ctxFactory() { maxAllowedL2ToL1ClockLag, maxAllowedTokenRateDeviationPerDay, BigNumber.from(86400*3), + BigNumber.from(3600), tokenRate, blockTimestamp ) diff --git a/utils/optimism/deploymentAllFromScratch.ts b/utils/optimism/deploymentAllFromScratch.ts index dc6c0032..39457f3e 100644 --- a/utils/optimism/deploymentAllFromScratch.ts +++ b/utils/optimism/deploymentAllFromScratch.ts @@ -17,19 +17,30 @@ import { } from "../../typechain"; interface OptL1DeployScriptParams extends DeployScriptParams { + l1Token: string; + l1TokenRebasable: string; + accountingOracle: string; + l2GasLimitForPushingTokenRate: number; } interface OptL2DeployScriptParams extends DeployScriptParams { - l2Token?: { - name?: string; - symbol?: string; - version?: string; + l2TokenNonRebasable: { + name: string; + symbol: string; + version: string; + decimals: number; }; - l2TokenRebasable?: { - name?: string; - symbol?: string; - version?: string; + l2TokenRebasable: { + name: string; + symbol: string; + version: string; + decimals: number; }; tokenRateOracle: { + tokenRateOutdatedDelay: BigNumber; + maxAllowedL2ToL1ClockLag: BigNumber; + maxAllowedTokenRateDeviationPerDayBp: BigNumber; + oldestRateAllowedInPauseTimeSpan: BigNumber; + maxAllowedTimeBetweenTokenRateUpdates: BigNumber; tokenRate: BigNumber; l1Timestamp: BigNumber; } @@ -93,12 +104,16 @@ export class L2DeployAllScript extends DeployScript { public tokenRateOracleProxyAddress: string; } -/// deploys from scratch -/// - wstETH on L2 -/// - stETH on L2 -/// - bridgeL1 -/// - bridgeL2 -/// - Oracle +/// Deploy all from scratch +/// L1 part +/// L1LidoTokensBridge + Proxy +/// TokenRateNotifier +/// OpStackTokenRatePusher +/// L2 part +/// TokenRateOracle + Proxy +/// ERC20BridgedPermit + Proxy +/// ERC20RebasableBridgedPermit + Proxy +/// L2ERC20ExtendedTokensBridge + Proxy export default function deploymentAll( networkName: NetworkName, options: OptDeploymentOptions = {} @@ -106,9 +121,6 @@ export default function deploymentAll( const optAddresses = addresses(networkName, options); return { async deployAllScript( - l1Token: string, - l1TokenRebasable: string, - accountingOracle: string, l1Params: OptL1DeployScriptParams, l2Params: OptL2DeployScriptParams, ): Promise<[L1DeployAllScript, L2DeployAllScript]> { @@ -144,11 +156,11 @@ export default function deploymentAll( args: [ optAddresses.L1CrossDomainMessenger, expectedL2TokenBridgeProxyAddress, - l1Token, - l1TokenRebasable, + l1Params.l1Token, + l1Params.l1TokenRebasable, expectedL2TokenProxyAddress, expectedL2TokenRebasableProxyAddress, - accountingOracle, + l1Params.accountingOracle, options?.overrides, ], afterDeploy: (c) => @@ -181,34 +193,16 @@ export default function deploymentAll( factory: OpStackTokenRatePusher__factory, args: [ optAddresses.L1CrossDomainMessenger, - l1Token, - accountingOracle, + l1Params.l1Token, + l1Params.accountingOracle, expectedL2TokenRateOracleProxyAddress, - 1000, + l1Params.l2GasLimitForPushingTokenRate, options?.overrides, ], afterDeploy: (c) => assert.equal(c.address, expectedL1OpStackTokenRatePusherImplAddress), }); - const l1TokenInfo = IERC20Metadata__factory.connect( - l1Token, - l1Params.deployer - ); - - const l1TokenRebasableInfo = IERC20Metadata__factory.connect( - l1TokenRebasable, - l1Params.deployer - ); - const [decimals, l2TokenName, l2TokenSymbol, l2TokenVersion, l2TokenRebasableName, l2TokenRebasableSymbol] = await Promise.all([ - l1TokenInfo.decimals(), - l2Params.l2Token?.name ?? l1TokenInfo.name(), - l2Params.l2Token?.symbol ?? l1TokenInfo.symbol(), - l2Params.l2Token?.version ?? "1", - l2Params.l2TokenRebasable?.name ?? l1TokenRebasableInfo.name(), - l2Params.l2TokenRebasable?.symbol ?? l1TokenRebasableInfo.symbol(), - ]); - const l2DeployScript = new L2DeployAllScript( l2Params.deployer, expectedL2TokenImplAddress, @@ -221,45 +215,47 @@ export default function deploymentAll( expectedL2TokenRateOracleProxyAddress, options?.logger ) - .addStep({ - factory: TokenRateOracle__factory, - args: [ - optAddresses.L2CrossDomainMessenger, - expectedL2TokenBridgeProxyAddress, - expectedL1OpStackTokenRatePusherImplAddress, - 86400, - 86400, - 500, - options?.overrides, - ], - afterDeploy: (c) => - assert.equal(c.address, expectedL2TokenRateOracleImplAddress), - }) - .addStep({ - factory: OssifiableProxy__factory, - args: [ - expectedL2TokenRateOracleImplAddress, - l2Params.admins.proxy, - TokenRateOracle__factory.createInterface().encodeFunctionData( - "initialize", - [ - l2Params.admins.bridge, - l2Params.tokenRateOracle.tokenRate, - l2Params.tokenRateOracle.l1Timestamp - ] - ), - options?.overrides, - ], - afterDeploy: (c) => - assert.equal(c.address, expectedL2TokenRateOracleProxyAddress), - }) + .addStep({ + factory: TokenRateOracle__factory, + args: [ + optAddresses.L2CrossDomainMessenger, + expectedL2TokenBridgeProxyAddress, + expectedL1OpStackTokenRatePusherImplAddress, + l2Params.tokenRateOracle.tokenRateOutdatedDelay, + l2Params.tokenRateOracle.maxAllowedL2ToL1ClockLag, + l2Params.tokenRateOracle.maxAllowedTokenRateDeviationPerDayBp, + l2Params.tokenRateOracle.oldestRateAllowedInPauseTimeSpan, + l2Params.tokenRateOracle.maxAllowedTimeBetweenTokenRateUpdates, + options?.overrides, + ], + afterDeploy: (c) => + assert.equal(c.address, expectedL2TokenRateOracleImplAddress), + }) + .addStep({ + factory: OssifiableProxy__factory, + args: [ + expectedL2TokenRateOracleImplAddress, + l2Params.admins.proxy, + TokenRateOracle__factory.createInterface().encodeFunctionData( + "initialize", + [ + l2Params.admins.bridge, + l2Params.tokenRateOracle.tokenRate, + l2Params.tokenRateOracle.l1Timestamp + ] + ), + options?.overrides, + ], + afterDeploy: (c) => + assert.equal(c.address, expectedL2TokenRateOracleProxyAddress), + }) .addStep({ factory: ERC20BridgedPermit__factory, args: [ - l2TokenName, - l2TokenSymbol, - l2TokenVersion, - decimals, + l2Params.l2TokenNonRebasable.name, + l2Params.l2TokenNonRebasable.symbol, + l2Params.l2TokenNonRebasable.version, + l2Params.l2TokenNonRebasable.decimals, expectedL2TokenBridgeProxyAddress, options?.overrides, ], @@ -273,7 +269,11 @@ export default function deploymentAll( l2Params.admins.proxy, ERC20BridgedPermit__factory.createInterface().encodeFunctionData( "initialize", - [l2TokenName, l2TokenSymbol, l2TokenVersion] + [ + l2Params.l2TokenNonRebasable.name, + l2Params.l2TokenNonRebasable.symbol, + l2Params.l2TokenNonRebasable.version + ] ), options?.overrides, ], @@ -283,10 +283,10 @@ export default function deploymentAll( .addStep({ factory: ERC20RebasableBridgedPermit__factory, args: [ - l2TokenRebasableName, - l2TokenRebasableSymbol, - l2TokenVersion, - decimals, + l2Params.l2TokenRebasable.name, + l2Params.l2TokenRebasable.symbol, + l2Params.l2TokenRebasable.version, + l2Params.l2TokenRebasable.decimals, expectedL2TokenProxyAddress, expectedL2TokenRateOracleProxyAddress, expectedL2TokenBridgeProxyAddress, @@ -302,7 +302,11 @@ export default function deploymentAll( l2Params.admins.proxy, ERC20RebasableBridgedPermit__factory.createInterface().encodeFunctionData( "initialize", - [l2TokenRebasableName, l2TokenRebasableSymbol, l2TokenVersion] + [ + l2Params.l2TokenRebasable.name, + l2Params.l2TokenRebasable.symbol, + l2Params.l2TokenRebasable.version + ] ), options?.overrides, ], @@ -314,8 +318,8 @@ export default function deploymentAll( args: [ optAddresses.L2CrossDomainMessenger, expectedL1TokenBridgeProxyAddress, - l1Token, - l1TokenRebasable, + l1Params.l1Token, + l1Params.l1TokenRebasable, expectedL2TokenProxyAddress, expectedL2TokenRebasableProxyAddress, options?.overrides, diff --git a/utils/optimism/deploymentBridgesAndRebasableToken.ts b/utils/optimism/deploymentBridgesAndRebasableToken.ts deleted file mode 100644 index e77c2d0c..00000000 --- a/utils/optimism/deploymentBridgesAndRebasableToken.ts +++ /dev/null @@ -1,256 +0,0 @@ -import { assert } from "chai"; -import { Overrides, Wallet } from "ethers"; -import addresses from "./addresses"; -import { CommonOptions } from "./types"; -import network, { NetworkName } from "../network"; -import { DeployScript, Logger } from "../deployment/DeployScript"; -import { - ERC20BridgedPermit__factory, - ERC20RebasableBridgedPermit__factory, - IERC20Metadata__factory, - L1LidoTokensBridge__factory, - L2ERC20ExtendedTokensBridge__factory, - OssifiableProxy__factory, -} from "../../typechain"; - -interface OptL1DeployScriptParams { - deployer: Wallet; - admins: { proxy: string; bridge: string }; - contractsShift: number; -} - -interface OptL2DeployScriptParams extends OptL1DeployScriptParams { - l2Token?: { name?: string; symbol?: string }; - l2TokenRebasable?: { name?: string; symbol?: string }; -} - -interface OptDeploymentOptions extends CommonOptions { - logger?: Logger; - overrides?: Overrides; -} - -export class BridgeL1DeployScript extends DeployScript { - - constructor( - deployer: Wallet, - bridgeImplAddress: string, - bridgeProxyAddress: string, - logger?: Logger - ) { - super(deployer, logger); - this.bridgeImplAddress = bridgeImplAddress; - this.bridgeProxyAddress = bridgeProxyAddress; - } - - public bridgeImplAddress: string; - public bridgeProxyAddress: string; -} - -export class BridgeL2DeployScript extends DeployScript { - - constructor( - deployer: Wallet, - tokenImplAddress: string, - tokenProxyAddress: string, - tokenRebasableImplAddress: string, - tokenRebasableProxyAddress: string, - tokenBridgeImplAddress: string, - tokenBridgeProxyAddress: string, - logger?: Logger - ) { - super(deployer, logger); - this.tokenImplAddress = tokenImplAddress; - this.tokenProxyAddress = tokenProxyAddress; - this.tokenRebasableImplAddress = tokenRebasableImplAddress; - this.tokenRebasableProxyAddress = tokenRebasableProxyAddress; - this.tokenBridgeImplAddress = tokenBridgeImplAddress; - this.tokenBridgeProxyAddress = tokenBridgeProxyAddress; - } - - public tokenImplAddress: string; - public tokenProxyAddress: string; - public tokenRebasableImplAddress: string; - public tokenRebasableProxyAddress: string; - public tokenBridgeImplAddress: string; - public tokenBridgeProxyAddress: string; -} - -/// deploy Oracle first -/// deploys from scratch wstETH on L2, stETH on L2, bridgeL1, bridgeL2 -export default function deployment( - networkName: NetworkName, - options: OptDeploymentOptions = {} -) { - const optAddresses = addresses(networkName, options); - return { - async erc20TokenBridgeDeployScript( - l1Token: string, - l1TokenRebasable: string, - l2TokenRateOracle: string, - l1Params: OptL1DeployScriptParams, - l2Params: OptL2DeployScriptParams, - ): Promise<[BridgeL1DeployScript, BridgeL2DeployScript]> { - - const [ - expectedL1TokenBridgeImplAddress, - expectedL1TokenBridgeProxyAddress, - ] = await network.predictAddresses(l1Params.deployer, l1Params.contractsShift + 2); - - const [ - expectedL2TokenImplAddress, - expectedL2TokenProxyAddress, - expectedL2TokenRebasableImplAddress, - expectedL2TokenRebasableProxyAddress, - expectedL2TokenBridgeImplAddress, - expectedL2TokenBridgeProxyAddress, - ] = await network.predictAddresses(l2Params.deployer, l2Params.contractsShift + 6); - - const l1DeployScript = new BridgeL1DeployScript( - l1Params.deployer, - expectedL1TokenBridgeImplAddress, - expectedL1TokenBridgeProxyAddress, - options?.logger - ) - .addStep({ - factory: L1LidoTokensBridge__factory, - args: [ - optAddresses.L1CrossDomainMessenger, - expectedL2TokenBridgeProxyAddress, - l1Token, - l1TokenRebasable, - expectedL2TokenProxyAddress, - expectedL2TokenRebasableProxyAddress, - options?.overrides, - ], - afterDeploy: (c) => - assert.equal(c.address, expectedL1TokenBridgeImplAddress), - }) - .addStep({ - factory: OssifiableProxy__factory, - args: [ - expectedL1TokenBridgeImplAddress, - l1Params.admins.proxy, - L1LidoTokensBridge__factory.createInterface().encodeFunctionData( - "initialize", - [l1Params.admins.bridge] - ), - options?.overrides, - ], - afterDeploy: (c) => - assert.equal(c.address, expectedL1TokenBridgeProxyAddress), - }); - - const l1TokenInfo = IERC20Metadata__factory.connect( - l1Token, - l1Params.deployer - ); - - const l1TokenRebasableInfo = IERC20Metadata__factory.connect( - l1TokenRebasable, - l1Params.deployer - ); - const [decimals, l2TokenName, l2TokenSymbol, l2TokenRebasableName, l2TokenRebasableSymbol] = await Promise.all([ - l1TokenInfo.decimals(), - l2Params.l2Token?.name ?? l1TokenInfo.name(), - l2Params.l2Token?.symbol ?? l1TokenInfo.symbol(), - l2Params.l2TokenRebasable?.name ?? l1TokenRebasableInfo.name(), - l2Params.l2TokenRebasable?.symbol ?? l1TokenRebasableInfo.symbol(), - ]); - const l2TokenVersion = "1"; - - const l2DeployScript = new BridgeL2DeployScript( - l2Params.deployer, - expectedL2TokenImplAddress, - expectedL2TokenProxyAddress, - expectedL2TokenRebasableImplAddress, - expectedL2TokenRebasableProxyAddress, - expectedL2TokenBridgeImplAddress, - expectedL2TokenBridgeProxyAddress, - options?.logger - ) - .addStep({ - factory: ERC20BridgedPermit__factory, - args: [ - l2TokenName, - l2TokenSymbol, - l2TokenVersion, - decimals, - expectedL2TokenBridgeProxyAddress, - options?.overrides, - ], - afterDeploy: (c) => - assert.equal(c.address, expectedL2TokenImplAddress), - }) - .addStep({ - factory: OssifiableProxy__factory, - args: [ - expectedL2TokenImplAddress, - l2Params.admins.proxy, - ERC20BridgedPermit__factory.createInterface().encodeFunctionData( - "initialize", - [l2TokenName, l2TokenSymbol, l2TokenVersion] - ), - options?.overrides, - ], - afterDeploy: (c) => - assert.equal(c.address, expectedL2TokenProxyAddress), - }) - .addStep({ - factory: ERC20RebasableBridgedPermit__factory, - args: [ - l2TokenRebasableName, - l2TokenRebasableSymbol, - decimals, - expectedL2TokenProxyAddress, - l2TokenRateOracle, - expectedL2TokenBridgeProxyAddress, - options?.overrides, - ], - afterDeploy: (c) => - assert.equal(c.address, expectedL2TokenRebasableImplAddress), - }) - .addStep({ - factory: OssifiableProxy__factory, - args: [ - expectedL2TokenRebasableImplAddress, - l2Params.admins.proxy, - ERC20RebasableBridgedPermit__factory.createInterface().encodeFunctionData( - "initializeERC20Metadata", - [l2TokenRebasableName, l2TokenRebasableSymbol] - ), - options?.overrides, - ], - afterDeploy: (c) => - assert.equal(c.address, expectedL2TokenRebasableProxyAddress), - }) - .addStep({ - factory: L2ERC20ExtendedTokensBridge__factory, - args: [ - optAddresses.L2CrossDomainMessenger, - expectedL1TokenBridgeProxyAddress, - l1Token, - l1TokenRebasable, - expectedL2TokenProxyAddress, - expectedL2TokenRebasableProxyAddress, - options?.overrides, - ], - afterDeploy: (c) => - assert.equal(c.address, expectedL2TokenBridgeImplAddress), - }) - .addStep({ - factory: OssifiableProxy__factory, - args: [ - expectedL2TokenBridgeImplAddress, - l2Params.admins.proxy, - L2ERC20ExtendedTokensBridge__factory.createInterface().encodeFunctionData( - "initialize", - [l2Params.admins.bridge] - ), - options?.overrides, - ], - }); - - return [l1DeployScript as BridgeL1DeployScript, l2DeployScript as BridgeL2DeployScript]; - }, - }; -} diff --git a/utils/optimism/deploymentOracle.ts b/utils/optimism/deploymentOracle.ts index 58a3b03d..d715ddfe 100644 --- a/utils/optimism/deploymentOracle.ts +++ b/utils/optimism/deploymentOracle.ts @@ -17,7 +17,9 @@ interface OptDeployScriptParams extends DeployScriptParams { } interface OptL2DeployScriptParams extends DeployScriptParams { tokenRateOracle: { maxAllowedL2ToL1ClockLag: BigNumber; - maxAllowedTokenRateDeviationPerDay: BigNumber; + maxAllowedTokenRateDeviationPerDayBp: BigNumber; + oldestRateAllowedInPauseTimeSpan: BigNumber; + maxAllowedTimeBetweenTokenRateUpdates: BigNumber; tokenRate: BigNumber; l1Timestamp: BigNumber; } @@ -55,6 +57,12 @@ export class OracleL2DeployScript extends DeployScript { public tokenRateOracleProxyAddress: string; } +/// Deploy Oracle + L1 part to push rate +/// L1 part +/// TokenRateNotifier +/// OpStackTokenRatePusher +/// L2 part +/// TokenRateOracle + proxy export default function deploymentOracle( networkName: NetworkName, options: OptDeploymentOptions = {} @@ -124,7 +132,9 @@ export default function deploymentOracle( expectedL1OpStackTokenRatePusherImplAddress, tokenRateOutdatedDelay, l2Params.tokenRateOracle.maxAllowedL2ToL1ClockLag, - l2Params.tokenRateOracle.maxAllowedTokenRateDeviationPerDay, + l2Params.tokenRateOracle.maxAllowedTokenRateDeviationPerDayBp, + l2Params.tokenRateOracle.oldestRateAllowedInPauseTimeSpan, + l2Params.tokenRateOracle.maxAllowedTimeBetweenTokenRateUpdates, options?.overrides, ], afterDeploy: (c) => diff --git a/utils/optimism/index.ts b/utils/optimism/index.ts index 9b00eed7..d9732d61 100644 --- a/utils/optimism/index.ts +++ b/utils/optimism/index.ts @@ -1,6 +1,5 @@ import addresses from "./addresses"; import contracts from "./contracts"; -import deployment from "./deploymentBridgesAndRebasableToken"; import deploymentOracle from "./deploymentOracle"; import testing from "./testing"; import messaging from "./messaging"; @@ -10,7 +9,6 @@ export default { addresses, contracts, messaging, - deployment, deploymentOracle }; diff --git a/utils/optimism/testing.ts b/utils/optimism/testing.ts index ca288469..d2c5a0ee 100644 --- a/utils/optimism/testing.ts +++ b/utils/optimism/testing.ts @@ -201,9 +201,9 @@ async function deployTestBridge( "TTR" ); - const l1Token = await new ERC20WrapperStub__factory(ethDeployer).deploy( + const l1TokenNonRebasable = await new ERC20WrapperStub__factory(ethDeployer).deploy( l1TokenRebasable.address, - "Test Token", + "Test Non Rebasable Token", "TT", totalPooledEther, totalShares @@ -220,13 +220,20 @@ async function deployTestBridge( lastProcessingRefSlot ); + const maxAllowedL2ToL1ClockLag = BigNumber.from(86400); + const maxAllowedTokenRateDeviationPerDay = BigNumber.from(500); + const oldestRateAllowedInPauseTimeSpan = BigNumber.from(86400*3); + const maxAllowedTimeBetweenTokenRateUpdates = BigNumber.from(3600); + const tokenRateOutdatedDelay = BigNumber.from(86400); + const [ethDeployScript, optDeployScript] = await deploymentAll( networkName ).deployAllScript( - l1Token.address, - l1TokenRebasable.address, - accountingOracle.address, { + l1Token: l1TokenNonRebasable.address, + l1TokenRebasable: l1TokenRebasable.address, + accountingOracle: accountingOracle.address, + l2GasLimitForPushingTokenRate: 300_000, deployer: ethDeployer, admins: { proxy: ethDeployer.address, bridge: ethDeployer.address }, contractsShift: 0 @@ -236,8 +243,25 @@ async function deployTestBridge( admins: { proxy: optDeployer.address, bridge: optDeployer.address }, contractsShift: 0, tokenRateOracle: { + tokenRateOutdatedDelay: tokenRateOutdatedDelay, + maxAllowedL2ToL1ClockLag: maxAllowedL2ToL1ClockLag, + maxAllowedTokenRateDeviationPerDayBp: maxAllowedTokenRateDeviationPerDay, + oldestRateAllowedInPauseTimeSpan: oldestRateAllowedInPauseTimeSpan, + maxAllowedTimeBetweenTokenRateUpdates: maxAllowedTimeBetweenTokenRateUpdates, tokenRate: tokenRate, l1Timestamp: BigNumber.from('1000') + }, + l2TokenNonRebasable: { + name: "wstETH", + symbol: "WST", + version: "1", + decimals: 18 + }, + l2TokenRebasable: { + name: "stETH", + symbol: "ST", + version: "1", + decimals: 18 } } ); @@ -268,7 +292,7 @@ async function deployTestBridge( }); return { - l1Token: l1Token.connect(ethProvider), + l1Token: l1TokenNonRebasable.connect(ethProvider), l1TokenRebasable: l1TokenRebasable.connect(ethProvider), accountingOracle: accountingOracle.connect(ethProvider), ...connectBridgeContracts( diff --git a/utils/testing/contractsFactory.ts b/utils/testing/contractsFactory.ts index 7daf7e00..8d377eea 100644 --- a/utils/testing/contractsFactory.ts +++ b/utils/testing/contractsFactory.ts @@ -51,7 +51,8 @@ export async function tokenRateOracleUnderProxy( tokenRateOutdatedDelay: BigNumber, maxAllowedL2ToL1ClockLag: BigNumber, maxAllowedTokenRateDeviationPerDay: BigNumber, - maxDeltaTimeToPausetokenRateUpdates: BigNumber, + oldestRateAllowedInPauseTimeSpan: BigNumber, + maxAllowedTimeBetweenTokenRateUpdates: BigNumber, tokenRate: BigNumber, rateL1Timestamp: BigNumber ) { @@ -62,7 +63,8 @@ export async function tokenRateOracleUnderProxy( tokenRateOutdatedDelay, maxAllowedL2ToL1ClockLag, maxAllowedTokenRateDeviationPerDay, - maxDeltaTimeToPausetokenRateUpdates + oldestRateAllowedInPauseTimeSpan, + maxAllowedTimeBetweenTokenRateUpdates ); const tokenRateOracleProxy = new OssifiableProxy__factory(deployer); From a1f258791e3273d0662fdfdffe6403df2cedd7e5 Mon Sep 17 00:00:00 2001 From: Anton Kovalchuk Date: Fri, 31 May 2024 00:07:40 +0200 Subject: [PATCH 144/148] add tests for double pause/unpause --- contracts/optimism/TokenRateOracle.sol | 1 - test/optimism/TokenRateOracle.unit.test.ts | 29 ++++++++++++++++++++++ 2 files changed, 29 insertions(+), 1 deletion(-) diff --git a/contracts/optimism/TokenRateOracle.sol b/contracts/optimism/TokenRateOracle.sol index 0b784466..2a17b894 100644 --- a/contracts/optimism/TokenRateOracle.sol +++ b/contracts/optimism/TokenRateOracle.sol @@ -256,7 +256,6 @@ contract TokenRateOracle is ITokenRateOracle, CrossDomainEnabled, AccessControl, } /// @dev This condition was made under the assumption that the L1 timestamps can be hacked. - /// Normally L1 timestamps (oracle reports) can't be less than 24 hours. if (rateUpdateL1Timestamp_ < tokenRateData.rateUpdateL1Timestamp + MAX_ALLOWED_TIME_BETWEEEN_TOKEN_RATE_UPDATES) { emit UpdateRateIsTooOften(); return; diff --git a/test/optimism/TokenRateOracle.unit.test.ts b/test/optimism/TokenRateOracle.unit.test.ts index 5579b589..7bda3e58 100644 --- a/test/optimism/TokenRateOracle.unit.test.ts +++ b/test/optimism/TokenRateOracle.unit.test.ts @@ -540,6 +540,21 @@ unit("TokenRateOracle", ctxFactory) ); }) + .test("pauseTokenRateUpdates() :: double pause", async (ctx) => { + const { tokenRateOracle } = ctx.contracts; + const { multisig } = ctx.accounts; + + const disablerRole = await tokenRateOracle.RATE_UPDATE_DISABLER_ROLE(); + await tokenRateOracle.grantRole(disablerRole, multisig.address); + + await tokenRateOracle.connect(multisig).pauseTokenRateUpdates(0); + + await assert.revertsWith( + tokenRateOracle.connect(multisig).pauseTokenRateUpdates(1), + "ErrorAlreadyPaused()" + ); + }) + .test("pauseTokenRateUpdates() :: wrong index", async (ctx) => { const { tokenRateOracle } = ctx.contracts; const { multisig } = ctx.accounts; @@ -649,6 +664,20 @@ unit("TokenRateOracle", ctxFactory) ); }) + .test("resumeTokenRateUpdates() :: unpause when unpaused", async (ctx) => { + const { tokenRateOracle } = ctx.contracts; + const { multisig } = ctx.accounts; + const { tokenRate, blockTimestampOfDeployment } = ctx.constants; + + const enablerRole = await tokenRateOracle.RATE_UPDATE_ENABLER_ROLE(); + await tokenRateOracle.grantRole(enablerRole, multisig.address); + + await assert.revertsWith( + tokenRateOracle.connect(multisig).resumeTokenRateUpdates(tokenRate, blockTimestampOfDeployment), + "ErrorAlreadyResumed()" + ); + }) + .test("resumeTokenRateUpdates() :: success", async (ctx) => { const { tokenRateOracle } = ctx.contracts; const { multisig, bridge } = ctx.accounts; From b68fbafb9b796335585b1c3da1531ad82af02921 Mon Sep 17 00:00:00 2001 From: Anton Kovalchuk Date: Fri, 31 May 2024 16:06:52 +0200 Subject: [PATCH 145/148] fix comments, small refactoring, rename events, constants --- contracts/BridgingManager.sol | 2 +- .../optimism/L1ERC20ExtendedTokensBridge.sol | 4 +- contracts/optimism/L1LidoTokensBridge.sol | 4 +- .../optimism/L2ERC20ExtendedTokensBridge.sol | 51 ++++---- contracts/optimism/OpStackTokenRatePusher.sol | 10 +- .../TokenRateAndUpdateTimestampProvider.sol | 6 +- contracts/optimism/TokenRateOracle.sol | 116 ++++++++++-------- .../interfaces/ITokenRateUpdatable.sol | 4 +- contracts/token/ERC20BridgedPermit.sol | 1 - .../token/ERC20RebasableBridgedPermit.sol | 1 - contracts/token/PermitExtension.sol | 10 +- contracts/utils/Versioned.sol | 13 ++ .../OpStackTokenRatePusher.unit.test.ts | 2 +- test/optimism/TokenRateOracle.unit.test.ts | 11 +- 14 files changed, 128 insertions(+), 107 deletions(-) diff --git a/contracts/BridgingManager.sol b/contracts/BridgingManager.sol index a61eca89..224c331f 100644 --- a/contracts/BridgingManager.sol +++ b/contracts/BridgingManager.sol @@ -13,7 +13,7 @@ contract BridgingManager is AccessControl { /// @param isDepositsEnabled Stores the state of the deposits /// @param isWithdrawalsEnabled Stores the state of the withdrawals struct State { - /// @dev This variable is used to determine whether the admin has been initialized or not. + /// @dev This variable is used to determine whether the contract has been initialized or not. /// At the same time, bridges have their own code for initialization and storage versioning. /// Therefore, it is recommended to base upgrade logic on new mechanisms since v2. bool isInitialized; diff --git a/contracts/optimism/L1ERC20ExtendedTokensBridge.sol b/contracts/optimism/L1ERC20ExtendedTokensBridge.sol index 658e335d..e4e1a381 100644 --- a/contracts/optimism/L1ERC20ExtendedTokensBridge.sol +++ b/contracts/optimism/L1ERC20ExtendedTokensBridge.sol @@ -172,7 +172,7 @@ abstract contract L1ERC20ExtendedTokensBridge is /// @param data_ Optional data to forward to L2. /// @return encoded data in the 'wired' bytes form. function _encodeInputDepositData(bytes calldata data_) internal view returns (bytes memory) { - (uint256 rate, uint256 timestamp) = tokenRate(); + (uint256 rate, uint256 timestamp) = _tokenRate(); return DepositDataCodec.encodeDepositData(DepositDataCodec.DepositData({ rate: uint128(rate), timestamp: uint40(timestamp), @@ -181,7 +181,7 @@ abstract contract L1ERC20ExtendedTokensBridge is } /// @notice required to abstact a way token rate is requested. - function tokenRate() virtual public view returns (uint256 rate, uint256 updateTimestamp); + function _tokenRate() virtual internal view returns (uint256 rate_, uint256 updateTimestamp_); error ErrorSenderNotEOA(); error ErrorZeroAddressL2Bridge(); diff --git a/contracts/optimism/L1LidoTokensBridge.sol b/contracts/optimism/L1LidoTokensBridge.sol index 1232c6e4..50fd83aa 100644 --- a/contracts/optimism/L1LidoTokensBridge.sol +++ b/contracts/optimism/L1LidoTokensBridge.sol @@ -53,7 +53,7 @@ contract L1LidoTokensBridge is L1ERC20ExtendedTokensBridge, TokenRateAndUpdateTi _initializeContractVersionTo(2); } - function tokenRate() override public view returns (uint256 rate, uint256 updateTimestamp) { - return getTokenRateAndUpdateTimestamp(); + function _tokenRate() override internal view returns (uint256 rate, uint256 updateTimestamp) { + return _getTokenRateAndUpdateTimestamp(); } } diff --git a/contracts/optimism/L2ERC20ExtendedTokensBridge.sol b/contracts/optimism/L2ERC20ExtendedTokensBridge.sol index 9c2fca4f..e3fed342 100644 --- a/contracts/optimism/L2ERC20ExtendedTokensBridge.sol +++ b/contracts/optimism/L2ERC20ExtendedTokensBridge.sol @@ -108,12 +108,6 @@ contract L2ERC20ExtendedTokensBridge is onlyNonZeroAccount(to_) onlySupportedL2Token(l2Token_) { - /// @dev L1_TOKEN_REBASABLE does not allow transfers to itself. - /// Additionally, sending funds to L1_TOKEN_NON_REBASABLE would lock these funds permanently, - /// as it is non-upgradeable. To prevent stucking tokens on L1 bridge or token this check was added. - if (to_ == L1_TOKEN_REBASABLE || to_ == L1_TOKEN_NON_REBASABLE) { - revert ErrorTransferToL1TokenContract(); - } _withdrawTo(l2Token_, msg.sender, to_, amount_, l1Gas_, data_); emit WithdrawalInitiated(_getL1Token(l2Token_), l2Token_, msg.sender, to_, amount_, data_); } @@ -147,15 +141,17 @@ contract L2ERC20ExtendedTokensBridge is } /// @notice Performs the logic for withdrawals by burning the token and informing - /// the L1 token Gateway of the withdrawal + /// the L1 token Gateway of the withdrawal. This function does not allow sending to token addresses. + /// L1_TOKEN_REBASABLE does not allow transfers to itself. Additionally, sending funds to + /// L1_TOKEN_NON_REBASABLE would lock these funds permanently, as it is non-upgradeable. /// @param l2Token_ Address of L2 token where withdrawal was initiated. /// @param from_ Account to pull the withdrawal from on L2 - /// @param to_ Account to give the withdrawal to on L1 + /// @param to_ Account to give the withdrawal to on L1. /// @param amount_ Amount of the token to withdraw /// @param l1Gas_ Minimum gas limit to use for the transaction. /// @param data_ Optional data to forward to L1. This data is provided - /// solely as a convenience for external contracts. Aside from enforcing a maximum - /// length, these contracts provide no guarantees about its content + /// solely as a convenience for external contracts. Aside from enforcing a maximum + /// length, these contracts provide no guarantees about its content function _withdrawTo( address l2Token_, address from_, @@ -164,6 +160,10 @@ contract L2ERC20ExtendedTokensBridge is uint32 l1Gas_, bytes calldata data_ ) internal { + if (to_ == L1_TOKEN_REBASABLE || to_ == L1_TOKEN_NON_REBASABLE) { + revert ErrorTransferToL1TokenContract(); + } + uint256 nonRebasableAmountToWithdraw = _burnTokens(l2Token_, from_, amount_); bytes memory message = abi.encodeWithSelector( @@ -176,23 +176,22 @@ contract L2ERC20ExtendedTokensBridge is /// @notice Mints tokens, wraps if needed and returns amount of minted tokens. /// @param l2Token_ Address of L2 token for which deposit is finalizing. /// @param to_ Account that token mints for. - /// @param amount_ Amount of token or shares to mint. + /// @param nonRebasableTokenAmount_ Amount of non-rebasable token. /// @return returns amount of minted tokens. function _mintTokens( address l2Token_, address to_, - uint256 amount_ + uint256 nonRebasableTokenAmount_ ) internal returns (uint256) { - uint256 tokenAmount = amount_; + if (nonRebasableTokenAmount_ == 0) { + return 0; + } if (l2Token_ == L2_TOKEN_REBASABLE) { - IERC20Bridged(L2_TOKEN_NON_REBASABLE).bridgeMint(address(this), amount_); - if (amount_ != 0) { - tokenAmount = ERC20RebasableBridged(l2Token_).bridgeWrap(to_, amount_); - } - } else { - IERC20Bridged(l2Token_).bridgeMint(to_, amount_); + IERC20Bridged(L2_TOKEN_NON_REBASABLE).bridgeMint(address(this), nonRebasableTokenAmount_); + return ERC20RebasableBridged(l2Token_).bridgeWrap(to_, nonRebasableTokenAmount_); } - return tokenAmount; + IERC20Bridged(l2Token_).bridgeMint(to_, nonRebasableTokenAmount_); + return nonRebasableTokenAmount_; } /// @notice Unwraps if needed, burns tokens and returns amount of non-rebasable token to withdraw. @@ -205,16 +204,20 @@ contract L2ERC20ExtendedTokensBridge is address from_, uint256 amount_ ) internal returns (uint256) { + if (amount_ == 0) { + return 0; + } + uint256 nonRebasableTokenAmount = amount_; if (l2Token_ == L2_TOKEN_REBASABLE) { - uint256 nonRebasableTokenAmount = ERC20RebasableBridged(L2_TOKEN_REBASABLE).getSharesByTokens(amount_); - if (amount_ != 0 && nonRebasableTokenAmount != 0) { + nonRebasableTokenAmount = ERC20RebasableBridged(L2_TOKEN_REBASABLE).getSharesByTokens(amount_); + if (nonRebasableTokenAmount != 0) { ERC20RebasableBridged(L2_TOKEN_REBASABLE).bridgeUnwrap(from_, amount_); IERC20Bridged(L2_TOKEN_NON_REBASABLE).bridgeBurn(from_, nonRebasableTokenAmount); } return nonRebasableTokenAmount; } - IERC20Bridged(L2_TOKEN_NON_REBASABLE).bridgeBurn(from_, amount_); - return amount_; + IERC20Bridged(L2_TOKEN_NON_REBASABLE).bridgeBurn(from_, nonRebasableTokenAmount); + return nonRebasableTokenAmount; } error ErrorSenderNotEOA(); diff --git a/contracts/optimism/OpStackTokenRatePusher.sol b/contracts/optimism/OpStackTokenRatePusher.sol index 596156b3..412f61b8 100644 --- a/contracts/optimism/OpStackTokenRatePusher.sol +++ b/contracts/optimism/OpStackTokenRatePusher.sol @@ -11,7 +11,7 @@ import {TokenRateAndUpdateTimestampProvider} from "./TokenRateAndUpdateTimestamp /// @author kovalgek /// @notice Pushes token rate to L2 Oracle. -contract OpStackTokenRatePusher is ERC165, CrossDomainEnabled, TokenRateAndUpdateTimestampProvider, ITokenRatePusher { +contract OpStackTokenRatePusher is ITokenRatePusher, CrossDomainEnabled, TokenRateAndUpdateTimestampProvider, ERC165 { /// @notice Oracle address on L2 for receiving token rate. address public immutable L2_TOKEN_RATE_ORACLE; @@ -23,9 +23,9 @@ contract OpStackTokenRatePusher is ERC165, CrossDomainEnabled, TokenRateAndUpdat /// (gas cost of L2Bridge.finalizeDeposit() + OptimismPortal.minimumGasLimit(depositData.length)) * 1.5 uint32 public immutable L2_GAS_LIMIT_FOR_PUSHING_TOKEN_RATE; - /// @param messenger_ L1 messenger address being used for cross-chain communications - /// @param wstETH_ L1 token bridge address - /// @param accountingOracle_ L1 token bridge address + /// @param messenger_ L1 messenger address being used for cross-chain communications. + /// @param wstETH_ L1 token bridge address. + /// @param accountingOracle_ Address of the AccountingOracle instance to retrieve rate update timestamps. /// @param tokenRateOracle_ Oracle address on L2 for receiving token rate. /// @param l2GasLimitForPushingTokenRate_ Gas limit required to complete pushing token rate on L2. constructor( @@ -44,7 +44,7 @@ contract OpStackTokenRatePusher is ERC165, CrossDomainEnabled, TokenRateAndUpdat /// @inheritdoc ITokenRatePusher function pushTokenRate() external { - (uint256 rate, uint256 updateTimestamp) = getTokenRateAndUpdateTimestamp(); + (uint256 rate, uint256 updateTimestamp) = _getTokenRateAndUpdateTimestamp(); bytes memory message = abi.encodeWithSelector(ITokenRateUpdatable.updateRate.selector, rate, updateTimestamp); diff --git a/contracts/optimism/TokenRateAndUpdateTimestampProvider.sol b/contracts/optimism/TokenRateAndUpdateTimestampProvider.sol index 33334c62..2ffba15c 100644 --- a/contracts/optimism/TokenRateAndUpdateTimestampProvider.sol +++ b/contracts/optimism/TokenRateAndUpdateTimestampProvider.sol @@ -44,7 +44,7 @@ abstract contract TokenRateAndUpdateTimestampProvider { constructor(address wstETH_, address accountingOracle_) { if (wstETH_ == address(0)) { - revert ErrorZeroAddressWstEth(); + revert ErrorZeroAddressWstETH(); } if (accountingOracle_ == address(0)) { revert ErrorZeroAddressAccountingOracle(); @@ -55,7 +55,7 @@ abstract contract TokenRateAndUpdateTimestampProvider { SECONDS_PER_SLOT = IAccountingOracle(ACCOUNTING_ORACLE).SECONDS_PER_SLOT(); } - function getTokenRateAndUpdateTimestamp() internal view returns (uint256 rate, uint256 updateTimestamp) { + function _getTokenRateAndUpdateTimestamp() internal view returns (uint256 rate, uint256 updateTimestamp) { rate = IERC20WstETH(WSTETH).getStETHByWstETH(10 ** TOKEN_RATE_DECIMALS); /// @dev github.com/ethereum/consensus-specs/blob/dev/specs/bellatrix/beacon-chain.md#compute_timestamp_at_slot @@ -64,6 +64,6 @@ abstract contract TokenRateAndUpdateTimestampProvider { ).getLastProcessingRefSlot(); } - error ErrorZeroAddressWstEth(); + error ErrorZeroAddressWstETH(); error ErrorZeroAddressAccountingOracle(); } diff --git a/contracts/optimism/TokenRateOracle.sol b/contracts/optimism/TokenRateOracle.sol index 2a17b894..be73b316 100644 --- a/contracts/optimism/TokenRateOracle.sol +++ b/contracts/optimism/TokenRateOracle.sol @@ -16,18 +16,19 @@ interface ITokenRateOracle is ITokenRateUpdatable, IChainlinkAggregatorInterface /// @author kovalgek /// @notice Oracle for storing and providing token rate. /// NB: Cross-chain apps and CEXes should fetch the token rate specific to the chain for deposits/withdrawals -/// and compare against the token rate on L1 being a source of truth; +/// and compare against the token rate on L1 being an ultimate source of truth; +/// If the L1 rate differs, it can be pushed permissionlessly via OpStackTokenRatePusher. /// @dev Token rate updates can be delivered from two sources: L1 token rate pusher and L2 bridge. contract TokenRateOracle is ITokenRateOracle, CrossDomainEnabled, AccessControl, Versioned { using UnstructuredStorage for bytes32; - /// @dev Uses to store historical data of rate and times. + /// @dev Used to store historical data of rate and times. struct TokenRateData { /// @notice wstETH/stETH token rate. uint128 tokenRate; /// @notice last time when token rate was updated on L1. - uint64 rateUpdateL1Timestamp; + uint64 rateUpdatedL1Timestamp; /// @notice last time when token rate was received on L2. uint64 rateReceivedL2Timestamp; } // occupies a single slot @@ -50,11 +51,12 @@ contract TokenRateOracle is ITokenRateOracle, CrossDomainEnabled, AccessControl, /// @notice The maximum allowed time difference between the current time and the last received /// token rate update that can be set during a pause. This is required to limit the pause role - /// and prevent potential economic attacks. + /// and mitigate potential economic attacks. + /// See the 'pauseTokenRateUpdates()' method uint256 public immutable OLDEST_RATE_ALLOWED_IN_PAUSE_TIME_SPAN; /// @notice The maximum delta time that is allowed between two L1 timestamps of token rate updates. - uint256 public immutable MAX_ALLOWED_TIME_BETWEEEN_TOKEN_RATE_UPDATES; + uint256 public immutable MAX_ALLOWED_TIME_BETWEEN_TOKEN_RATE_UPDATES; /// @notice Decimals of the oracle response. uint8 public constant DECIMALS = 27; @@ -68,8 +70,8 @@ contract TokenRateOracle is ITokenRateOracle, CrossDomainEnabled, AccessControl, /// @dev Role granting the permission to pause updating rate. bytes32 public constant RATE_UPDATE_DISABLER_ROLE = keccak256("TokenRateOracle.RATE_UPDATE_DISABLER_ROLE"); - /// @dev Role granting the permission to unpause updating rate. - bytes32 public constant RATE_UPDATE_ENABLER_ROLE = keccak256("TokenRateOracle.RATE_UPDATE_ENABLER_ROLE"); + /// @dev Role granting the permission to resume updating rate. + bytes32 public constant RATE_UPDATE_ENABLER_ROLE = keccak256("TokenRafteOracle.RATE_UPDATE_ENABLER_ROLE"); /// @notice Basic point scale. uint256 private constant BASIS_POINT_SCALE = 1e4; @@ -93,7 +95,7 @@ contract TokenRateOracle is ITokenRateOracle, CrossDomainEnabled, AccessControl, /// Can't be bigger than BASIS_POINT_SCALE. /// @param oldestRateAllowedInPauseTimeSpan_ Maximum allowed time difference between the current time /// and the last received token rate update that can be set during a pause. - /// @param maxAllowedTimeBetweenTokenRateUpdates_ he maximum delta time that is allowed between two + /// @param maxAllowedTimeBetweenTokenRateUpdates_ the maximum delta time that is allowed between two /// L1 timestamps of token rate updates. constructor( address messenger_, @@ -111,7 +113,7 @@ contract TokenRateOracle is ITokenRateOracle, CrossDomainEnabled, AccessControl, if (l1TokenRatePusher_ == address(0)) { revert ErrorZeroAddressL1TokenRatePusher(); } - if (maxAllowedTokenRateDeviationPerDayBp_ > BASIS_POINT_SCALE) { + if (maxAllowedTokenRateDeviationPerDayBp_ == 0 || maxAllowedTokenRateDeviationPerDayBp_ > BASIS_POINT_SCALE) { revert ErrorMaxTokenRateDeviationIsOutOfRange(); } L2_ERC20_TOKEN_BRIDGE = l2ERC20TokenBridge_; @@ -120,30 +122,30 @@ contract TokenRateOracle is ITokenRateOracle, CrossDomainEnabled, AccessControl, MAX_ALLOWED_L2_TO_L1_CLOCK_LAG = maxAllowedL2ToL1ClockLag_; MAX_ALLOWED_TOKEN_RATE_DEVIATION_PER_DAY_BP = maxAllowedTokenRateDeviationPerDayBp_; OLDEST_RATE_ALLOWED_IN_PAUSE_TIME_SPAN = oldestRateAllowedInPauseTimeSpan_; - MAX_ALLOWED_TIME_BETWEEEN_TOKEN_RATE_UPDATES = maxAllowedTimeBetweenTokenRateUpdates_; + MAX_ALLOWED_TIME_BETWEEN_TOKEN_RATE_UPDATES = maxAllowedTimeBetweenTokenRateUpdates_; } /// @notice Initializes the contract from scratch. /// @param admin_ Address of the account to grant the DEFAULT_ADMIN_ROLE /// @param tokenRate_ wstETH/stETH token rate, uses 10**DECIMALS precision. - /// @param rateUpdateL1Timestamp_ L1 time when rate was updated on L1 side. - function initialize(address admin_, uint256 tokenRate_, uint256 rateUpdateL1Timestamp_) external { + /// @param rateUpdatedL1Timestamp_ L1 time when rate was updated on L1 side. + function initialize(address admin_, uint256 tokenRate_, uint256 rateUpdatedL1Timestamp_) external { _initializeContractVersionTo(1); if (admin_ == address(0)) { revert ErrorZeroAddressAdmin(); } if (tokenRate_ < MIN_SANE_TOKEN_RATE || tokenRate_ > MAX_SANE_TOKEN_RATE) { - revert ErrorTokenRateInitializationIsOutOfSaneRange(tokenRate_); + revert ErrorTokenRateIsOutOfSaneRange(tokenRate_); } - if (rateUpdateL1Timestamp_ > block.timestamp + MAX_ALLOWED_L2_TO_L1_CLOCK_LAG) { - revert ErrorL1TimestampInitializationIsOutOfAllowedRange(rateUpdateL1Timestamp_); + if (rateUpdatedL1Timestamp_ > block.timestamp + MAX_ALLOWED_L2_TO_L1_CLOCK_LAG) { + revert ErrorL1TimestampExceededMaxAllowedClockLag(rateUpdatedL1Timestamp_); } _grantRole(DEFAULT_ADMIN_ROLE, admin_); - _addTokenRate(tokenRate_, rateUpdateL1Timestamp_, block.timestamp); + _addTokenRate(tokenRate_, rateUpdatedL1Timestamp_, block.timestamp); } /// @notice Pauses token rate updates and sets the old rate provided by tokenRateIndex_. - /// Should be called by DAO or emergency breaks only. + /// Should be called by DAO or emergency brakes only. /// @param tokenRateIndex_ The index of the token rate that applies after the pause. /// Token Rate can't be received older then OLDEST_RATE_ALLOWED_IN_PAUSE_TIME_SPAN /// except only if the passed index is the latest one. @@ -158,24 +160,30 @@ contract TokenRateOracle is ITokenRateOracle, CrossDomainEnabled, AccessControl, } _removeElementsAfterIndex(tokenRateIndex_); _setPause(true); - emit TokenRateUpdatesPaused(tokenRateData.tokenRate, tokenRateData.rateUpdateL1Timestamp); - emit RateUpdated(tokenRateData.tokenRate, tokenRateData.rateUpdateL1Timestamp); + emit TokenRateUpdatesPaused(tokenRateData.tokenRate, tokenRateData.rateUpdatedL1Timestamp); + emit RateUpdated(tokenRateData.tokenRate, tokenRateData.rateUpdatedL1Timestamp); } /// @notice Resume token rate updates applying provided token rate. - /// @param tokenRate_ a new token rate that applies after unpausing. - /// @param rateUpdateL1Timestamp_ L1 time when rate was updated on L1 side. + /// @param tokenRate_ a new token rate that applies after resuming. + /// @param rateUpdatedL1Timestamp_ L1 time when rate was updated on L1 side. function resumeTokenRateUpdates( uint256 tokenRate_, - uint256 rateUpdateL1Timestamp_ + uint256 rateUpdatedL1Timestamp_ ) external onlyRole(RATE_UPDATE_ENABLER_ROLE) { if (!_isPaused()) { revert ErrorAlreadyResumed(); } - _addTokenRate(tokenRate_, rateUpdateL1Timestamp_, block.timestamp); + if (tokenRate_ < MIN_SANE_TOKEN_RATE || tokenRate_ > MAX_SANE_TOKEN_RATE) { + revert ErrorTokenRateIsOutOfSaneRange(tokenRate_); + } + if (rateUpdatedL1Timestamp_ > block.timestamp + MAX_ALLOWED_L2_TO_L1_CLOCK_LAG) { + revert ErrorL1TimestampExceededMaxAllowedClockLag(rateUpdatedL1Timestamp_); + } + _addTokenRate(tokenRate_, rateUpdatedL1Timestamp_, block.timestamp); _setPause(false); - emit TokenRateUpdatesResumed(tokenRate_, rateUpdateL1Timestamp_); - emit RateUpdated(tokenRate_, rateUpdateL1Timestamp_); + emit TokenRateUpdatesResumed(tokenRate_, rateUpdatedL1Timestamp_); + emit RateUpdated(tokenRate_, rateUpdatedL1Timestamp_); } /// @notice Shows that token rate updates are paused or not. @@ -204,11 +212,11 @@ contract TokenRateOracle is ITokenRateOracle, CrossDomainEnabled, AccessControl, ) { TokenRateData memory tokenRateData = _getLastTokenRate(); return ( - uint80(tokenRateData.rateUpdateL1Timestamp), + uint80(tokenRateData.rateUpdatedL1Timestamp), int256(uint256(tokenRateData.tokenRate)), - tokenRateData.rateUpdateL1Timestamp, + tokenRateData.rateUpdatedL1Timestamp, tokenRateData.rateReceivedL2Timestamp, - uint80(tokenRateData.rateUpdateL1Timestamp) + uint80(tokenRateData.rateUpdatedL1Timestamp) ); } @@ -226,58 +234,58 @@ contract TokenRateOracle is ITokenRateOracle, CrossDomainEnabled, AccessControl, /// @inheritdoc ITokenRateUpdatable function updateRate( uint256 tokenRate_, - uint256 rateUpdateL1Timestamp_ + uint256 rateUpdatedL1Timestamp_ ) external onlyBridgeOrTokenRatePusher { if (_isPaused()) { - emit TokenRateUpdateAttemptDuringPause(tokenRate_, rateUpdateL1Timestamp_); + emit TokenRateUpdateAttemptDuringPause(); return; } TokenRateData storage tokenRateData = _getLastTokenRate(); /// @dev checks if the clock lag (i.e, time difference) between L1 and L2 exceeds the configurable threshold - if (rateUpdateL1Timestamp_ > block.timestamp + MAX_ALLOWED_L2_TO_L1_CLOCK_LAG) { - revert ErrorL1TimestampExceededAllowedClockLag(tokenRate_, rateUpdateL1Timestamp_); + if (rateUpdatedL1Timestamp_ > block.timestamp + MAX_ALLOWED_L2_TO_L1_CLOCK_LAG) { + revert ErrorL1TimestampExceededAllowedClockLag(tokenRate_, rateUpdatedL1Timestamp_); } /// @dev Use only the most up-to-date token rate. Reverting should be avoided as it may occur occasionally. - if (rateUpdateL1Timestamp_ < tokenRateData.rateUpdateL1Timestamp) { - emit DormantTokenRateUpdateIgnored(rateUpdateL1Timestamp_, tokenRateData.rateUpdateL1Timestamp); + if (rateUpdatedL1Timestamp_ < tokenRateData.rateUpdatedL1Timestamp) { + emit DormantTokenRateUpdateIgnored(rateUpdatedL1Timestamp_, tokenRateData.rateUpdatedL1Timestamp); return; } /// @dev Bump L2 receipt time, don't touch the rate othwerwise /// NB: Here we assume that the rate can only be changed together with the token rebase induced /// by the AccountingOracle report - if (rateUpdateL1Timestamp_ == tokenRateData.rateUpdateL1Timestamp) { + if (rateUpdatedL1Timestamp_ == tokenRateData.rateUpdatedL1Timestamp) { tokenRateData.rateReceivedL2Timestamp = uint64(block.timestamp); emit RateReceivedTimestampUpdated(block.timestamp); return; } /// @dev This condition was made under the assumption that the L1 timestamps can be hacked. - if (rateUpdateL1Timestamp_ < tokenRateData.rateUpdateL1Timestamp + MAX_ALLOWED_TIME_BETWEEEN_TOKEN_RATE_UPDATES) { - emit UpdateRateIsTooOften(); + if (rateUpdatedL1Timestamp_ < tokenRateData.rateUpdatedL1Timestamp + MAX_ALLOWED_TIME_BETWEEN_TOKEN_RATE_UPDATES) { + emit UpdateRateIsTooOften(rateUpdatedL1Timestamp_, tokenRateData.rateUpdatedL1Timestamp); return; } /// @dev allow token rate to be within some configurable range that depens on time it wasn't updated. - if ((tokenRate_ != tokenRateData.tokenRate) && !_isTokenRateWithinAllowedRange( - tokenRateData.tokenRate, - tokenRate_, - tokenRateData.rateUpdateL1Timestamp, - rateUpdateL1Timestamp_) - ) { - revert ErrorTokenRateIsOutOfRange(tokenRate_, rateUpdateL1Timestamp_); + if (!_isTokenRateWithinAllowedRange( + tokenRateData.tokenRate, + tokenRate_, + tokenRateData.rateUpdatedL1Timestamp, + rateUpdatedL1Timestamp_) + ) { + revert ErrorTokenRateIsOutOfRange(tokenRate_, rateUpdatedL1Timestamp_); } /// @dev notify that there is a differnce L1 and L2 time. - if (rateUpdateL1Timestamp_ > block.timestamp) { - emit TokenRateL1TimestampIsInFuture(tokenRate_, rateUpdateL1Timestamp_); + if (rateUpdatedL1Timestamp_ > block.timestamp) { + emit TokenRateL1TimestampIsInFuture(tokenRate_, rateUpdatedL1Timestamp_); } - _addTokenRate(tokenRate_, rateUpdateL1Timestamp_, block.timestamp); - emit RateUpdated(tokenRate_, rateUpdateL1Timestamp_); + _addTokenRate(tokenRate_, rateUpdatedL1Timestamp_, block.timestamp); + emit RateUpdated(tokenRate_, rateUpdatedL1Timestamp_); } /// @notice Returns flag that shows that token rate can be considered outdated. @@ -343,11 +351,11 @@ contract TokenRateOracle is ITokenRateOracle, CrossDomainEnabled, AccessControl, } function _addTokenRate( - uint256 tokenRate_, uint256 rateUpdateL1Timestamp_, uint256 rateReceivedL2Timestamp_ + uint256 tokenRate_, uint256 rateUpdatedL1Timestamp_, uint256 rateReceivedL2Timestamp_ ) internal { _getStorageTokenRates().push(TokenRateData({ tokenRate: uint128(tokenRate_), - rateUpdateL1Timestamp: uint64(rateUpdateL1Timestamp_), + rateUpdatedL1Timestamp: uint64(rateUpdatedL1Timestamp_), rateReceivedL2Timestamp: uint64(rateReceivedL2Timestamp_) })); } @@ -404,8 +412,8 @@ contract TokenRateOracle is ITokenRateOracle, CrossDomainEnabled, AccessControl, event TokenRateL1TimestampIsInFuture(uint256 tokenRate_, uint256 indexed rateL1Timestamp_); event TokenRateUpdatesPaused(uint256 tokenRate_, uint256 indexed rateL1Timestamp_); event TokenRateUpdatesResumed(uint256 tokenRate_, uint256 indexed rateL1Timestamp_); - event TokenRateUpdateAttemptDuringPause(uint256 tokenRate_, uint256 indexed rateL1Timestamp_); - event UpdateRateIsTooOften(); + event TokenRateUpdateAttemptDuringPause(); + event UpdateRateIsTooOften(uint256 indexed newRateL1Timestamp_, uint256 indexed currentRateL1Timestamp_); error ErrorZeroAddressAdmin(); error ErrorWrongTokenRateIndex(); @@ -418,6 +426,6 @@ contract TokenRateOracle is ITokenRateOracle, CrossDomainEnabled, AccessControl, error ErrorL1TimestampExceededAllowedClockLag(uint256 tokenRate_, uint256 rateL1Timestamp_); error ErrorTokenRateIsOutOfRange(uint256 tokenRate_, uint256 rateL1Timestamp_); error ErrorMaxTokenRateDeviationIsOutOfRange(); - error ErrorTokenRateInitializationIsOutOfSaneRange(uint256 tokenRate_); - error ErrorL1TimestampInitializationIsOutOfAllowedRange(uint256 rateL1Timestamp_); + error ErrorTokenRateIsOutOfSaneRange(uint256 tokenRate_); + error ErrorL1TimestampExceededMaxAllowedClockLag(uint256 rateL1Timestamp_); } diff --git a/contracts/optimism/interfaces/ITokenRateUpdatable.sol b/contracts/optimism/interfaces/ITokenRateUpdatable.sol index 06505412..433396de 100644 --- a/contracts/optimism/interfaces/ITokenRateUpdatable.sol +++ b/contracts/optimism/interfaces/ITokenRateUpdatable.sol @@ -8,6 +8,6 @@ pragma solidity 0.8.10; interface ITokenRateUpdatable { /// @notice Updates token rate. /// @param tokenRate_ wstETH/stETH token rate. - /// @param rateUpdateL1Timestamp_ L1 time when rate was updated on L1 side. - function updateRate(uint256 tokenRate_, uint256 rateUpdateL1Timestamp_) external; + /// @param rateUpdatedL1Timestamp_ L1 time when rate was updated on L1 side. + function updateRate(uint256 tokenRate_, uint256 rateUpdatedL1Timestamp_) external; } diff --git a/contracts/token/ERC20BridgedPermit.sol b/contracts/token/ERC20BridgedPermit.sol index e856b507..87536453 100644 --- a/contracts/token/ERC20BridgedPermit.sol +++ b/contracts/token/ERC20BridgedPermit.sol @@ -56,7 +56,6 @@ contract ERC20BridgedPermit is ERC20Bridged, PermitExtension, Versioned { /// @inheritdoc PermitExtension function _permitAccepted(address owner_, address spender_, uint256 amount_) internal override { _approve(owner_, spender_, amount_); - emit Approval(owner_, spender_, amount_); } error ErrorMetadataIsNotInitialized(); diff --git a/contracts/token/ERC20RebasableBridgedPermit.sol b/contracts/token/ERC20RebasableBridgedPermit.sol index 40d75443..1755fd17 100644 --- a/contracts/token/ERC20RebasableBridgedPermit.sol +++ b/contracts/token/ERC20RebasableBridgedPermit.sol @@ -52,6 +52,5 @@ contract ERC20RebasableBridgedPermit is ERC20RebasableBridged, PermitExtension, /// @inheritdoc PermitExtension function _permitAccepted(address owner_, address spender_, uint256 amount_) internal override { _approve(owner_, spender_, amount_); - emit Approval(owner_, spender_, amount_); } } diff --git a/contracts/token/PermitExtension.sol b/contracts/token/PermitExtension.sol index 5f07831b..2dc6dc58 100644 --- a/contracts/token/PermitExtension.sol +++ b/contracts/token/PermitExtension.sol @@ -65,9 +65,9 @@ abstract contract PermitExtension is IERC2612, EIP712 { function permit( address owner_, address spender_, - uint value_, - uint deadline_, - bytes memory signature_ + uint256 value_, + uint256 deadline_, + bytes calldata signature_ ) external { _permit(owner_, spender_, value_, deadline_, signature_); } @@ -75,8 +75,8 @@ abstract contract PermitExtension is IERC2612, EIP712 { function _permit( address owner_, address spender_, - uint value_, - uint deadline_, + uint256 value_, + uint256 deadline_, bytes memory signature_ ) internal { if (block.timestamp > deadline_) { diff --git a/contracts/utils/Versioned.sol b/contracts/utils/Versioned.sol index 6fb6b45b..48e1b0a1 100644 --- a/contracts/utils/Versioned.sol +++ b/contracts/utils/Versioned.sol @@ -36,12 +36,25 @@ contract Versioned { return CONTRACT_VERSION_POSITION.getStorageUint256(); } + function _checkContractVersion(uint256 version) internal view { + uint256 expectedVersion = getContractVersion(); + if (version != expectedVersion) { + revert UnexpectedContractVersion(expectedVersion, version); + } + } + /// @dev Sets the contract version to N. Should be called from the initialize() function. function _initializeContractVersionTo(uint256 version) internal { if (getContractVersion() != 0) revert NonZeroContractVersionOnInit(); _setContractVersion(version); } + /// @dev Updates the contract version. Should be called from a finalizeUpgrade_vN() function. + function _updateContractVersion(uint256 newVersion) internal { + if (newVersion != getContractVersion() + 1) revert InvalidContractVersionIncrement(); + _setContractVersion(newVersion); + } + function _setContractVersion(uint256 version) private { CONTRACT_VERSION_POSITION.setStorageUint256(version); emit ContractVersionSet(version); diff --git a/test/optimism/OpStackTokenRatePusher.unit.test.ts b/test/optimism/OpStackTokenRatePusher.unit.test.ts index b32d690f..76fec554 100644 --- a/test/optimism/OpStackTokenRatePusher.unit.test.ts +++ b/test/optimism/OpStackTokenRatePusher.unit.test.ts @@ -41,7 +41,7 @@ unit("OpStackTokenRatePusher", ctxFactory) accountingOracle.address, stranger.address, 10 - ), "ErrorZeroAddressWstEth()"); + ), "ErrorZeroAddressWstETH()"); await assert.revertsWith(new OpStackTokenRatePusher__factory( deployer diff --git a/test/optimism/TokenRateOracle.unit.test.ts b/test/optimism/TokenRateOracle.unit.test.ts index 7bda3e58..c54fc3cc 100644 --- a/test/optimism/TokenRateOracle.unit.test.ts +++ b/test/optimism/TokenRateOracle.unit.test.ts @@ -158,7 +158,7 @@ unit("TokenRateOracle", ctxFactory) tokenRateMin.sub(1), blockTimestampOfDeployment ), - "ErrorTokenRateInitializationIsOutOfSaneRange(" + tokenRateMin.sub(1) + ")" + "ErrorTokenRateIsOutOfSaneRange(" + tokenRateMin.sub(1) + ")" ); await assert.revertsWith( @@ -175,7 +175,7 @@ unit("TokenRateOracle", ctxFactory) tokenRateMax.add(1), blockTimestampOfDeployment ), - "ErrorTokenRateInitializationIsOutOfSaneRange(" + tokenRateMax.add(1) + ")" + "ErrorTokenRateIsOutOfSaneRange(" + tokenRateMax.add(1) + ")" ); }) @@ -208,7 +208,7 @@ unit("TokenRateOracle", ctxFactory) tokenRate, wrongTimeMax ), - "ErrorL1TimestampInitializationIsOutOfAllowedRange(" + wrongTimeMax + ")" + "ErrorL1TimestampExceededMaxAllowedClockLag(" + wrongTimeMax + ")" ); }) @@ -317,7 +317,7 @@ unit("TokenRateOracle", ctxFactory) .connect(bridge) .updateRate(tokenRate, rateL1TimestampWithinTooOften); - await assert.emits(tokenRateOracle, tx0, "UpdateRateIsTooOften"); + await assert.emits(tokenRateOracle, tx0, "UpdateRateIsTooOften", [rateL1TimestampWithinTooOften, rateL1Timestamp]); await assert.notEmits(tokenRateOracle, tx0, "RateUpdated"); }) @@ -645,8 +645,7 @@ unit("TokenRateOracle", ctxFactory) await assert.emits( tokenRateOracle, tx3, - "TokenRateUpdateAttemptDuringPause", - [tokenRate3, blockTimestampOfDeployment3] + "TokenRateUpdateAttemptDuringPause" ); }) From 6fba907bb44c080041f9fe887bcf002216ee9219 Mon Sep 17 00:00:00 2001 From: Anton Kovalchuk Date: Thu, 6 Jun 2024 13:41:31 +0200 Subject: [PATCH 146/148] check admin address during initialization in bridges, fix indentation, tests --- contracts/BridgingManager.sol | 4 +++ contracts/token/ERC20RebasableBridged.sol | 2 +- test/optimism/L1LidoTokensBridge.unit.test.ts | 31 +++++++++++++++++++ .../L2ERC20ExtendedTokensBridge.unit.test.ts | 15 +++++++++ 4 files changed, 51 insertions(+), 1 deletion(-) diff --git a/contracts/BridgingManager.sol b/contracts/BridgingManager.sol index 224c331f..bb844aff 100644 --- a/contracts/BridgingManager.sol +++ b/contracts/BridgingManager.sol @@ -38,6 +38,9 @@ contract BridgingManager is AccessControl { /// @dev This method might be called only once /// @param admin_ Address of the account to grant the DEFAULT_ADMIN_ROLE function _initializeBridgingManager(address admin_) internal { + if (admin_ == address(0)) { + revert ErrorZeroAddressAdmin(); + } State storage s = _loadState(); if (s.isInitialized) { revert ErrorAlreadyInitialized(); @@ -135,6 +138,7 @@ contract BridgingManager is AccessControl { event WithdrawalsDisabled(address indexed disabler); event Initialized(address indexed admin); + error ErrorZeroAddressAdmin(); error ErrorDepositsEnabled(); error ErrorDepositsDisabled(); error ErrorWithdrawalsEnabled(); diff --git a/contracts/token/ERC20RebasableBridged.sol b/contracts/token/ERC20RebasableBridged.sol index 69f9ee2f..f2adfe12 100644 --- a/contracts/token/ERC20RebasableBridged.sol +++ b/contracts/token/ERC20RebasableBridged.sol @@ -362,7 +362,7 @@ contract ERC20RebasableBridged is IERC20, IERC20Wrapper, IBridgeWrapper, ERC20Me _setERC20MetadataSymbol(symbol_); } - function _wrap(address from_, address to_, uint256 sharesAmount_) internal returns (uint256) { + function _wrap(address from_, address to_, uint256 sharesAmount_) internal returns (uint256) { if (sharesAmount_ == 0) revert ErrorZeroSharesWrap(); TOKEN_TO_WRAP_FROM.safeTransferFrom(from_, address(this), sharesAmount_); _mintShares(to_, sharesAmount_); diff --git a/test/optimism/L1LidoTokensBridge.unit.test.ts b/test/optimism/L1LidoTokensBridge.unit.test.ts index 2b5aac66..4ddf4304 100644 --- a/test/optimism/L1LidoTokensBridge.unit.test.ts +++ b/test/optimism/L1LidoTokensBridge.unit.test.ts @@ -171,6 +171,37 @@ unit("Optimism :: L1LidoTokensBridge", ctxFactory) ); }) + .test("initialize() :: revert when admin is zero", async (ctx) => { + const { deployer, l2TokenBridgeEOA, zero } = ctx.accounts; + const { + totalPooledEther, + totalShares, + genesisTime, + secondsPerSlot, + lastProcessingRefSlot + } = ctx.constants; + + const { l1TokenBridgeImpl } = await getL1LidoTokensBridgeImpl( + totalPooledEther, + totalShares, + genesisTime, + secondsPerSlot, + lastProcessingRefSlot, + deployer, + l2TokenBridgeEOA.address + ); + + await assert.revertsWith(new OssifiableProxy__factory( + deployer + ).deploy( + l1TokenBridgeImpl.address, + deployer.address, + l1TokenBridgeImpl.interface.encodeFunctionData("initialize", [ + zero.address + ]) + ), "ErrorZeroAddressAdmin()"); + }) + .test("initialize() :: don't allow to initialize twice", async (ctx) => { const { deployer, l2TokenBridgeEOA } = ctx.accounts; const { diff --git a/test/optimism/L2ERC20ExtendedTokensBridge.unit.test.ts b/test/optimism/L2ERC20ExtendedTokensBridge.unit.test.ts index a00f1130..c857a22e 100644 --- a/test/optimism/L2ERC20ExtendedTokensBridge.unit.test.ts +++ b/test/optimism/L2ERC20ExtendedTokensBridge.unit.test.ts @@ -161,6 +161,21 @@ unit("Optimism:: L2ERC20ExtendedTokensBridge", ctxFactory) ); }) + .test("initialize() :: revert when admin is zero", async (ctx) => { + const { deployer, l1TokenBridgeEOA, zero } = ctx.accounts; + const l2TokenBridgeImpl = await getL2TokenBridgeImpl(deployer, l1TokenBridgeEOA.address); + + await assert.revertsWith(new OssifiableProxy__factory( + deployer + ).deploy( + l2TokenBridgeImpl.address, + deployer.address, + l2TokenBridgeImpl.interface.encodeFunctionData("initialize", [ + zero.address + ]) + ), "ErrorZeroAddressAdmin()"); + }) + .test("finalizeUpgrade_v2() :: bridging manager uninitialized", async (ctx) => { const { deployer, l1TokenBridgeEOA } = ctx.accounts; From baf9859fa3fccb9ccea4f6ad94d1fcdcf8493695 Mon Sep 17 00:00:00 2001 From: Anton Kovalchuk Date: Tue, 11 Jun 2024 15:43:40 +0200 Subject: [PATCH 147/148] update upgrade/deploy scripts --- package.json | 20 +- scripts/optimism/deploy-new-impls.ts | 88 ----- scripts/optimism/deploy-oracle.ts | 85 ---- scripts/optimism/deploy-scratch.ts | 98 +++++ scripts/optimism/upgrade.ts | 109 ++++++ .../optimism.integration.test.ts | 48 ++- ...bridging-non-rebasable.integration.test.ts | 2 +- .../bridging-rebasable.integration.test.ts | 11 +- test/optimism/pushingTokenRate.e2e.test.ts | 2 +- utils/deployment.ts | 66 +++- utils/optimism/deployment.ts | 369 ++++++++++++++++++ .../optimism/deploymentNewImplementations.ts | 239 ------------ utils/optimism/index.ts | 4 +- utils/optimism/testing.ts | 38 +- utils/optimism/upgrade.ts | 329 ++++++++++++++++ utils/testing/env.ts | 27 +- 16 files changed, 1046 insertions(+), 489 deletions(-) delete mode 100644 scripts/optimism/deploy-new-impls.ts delete mode 100644 scripts/optimism/deploy-oracle.ts create mode 100644 scripts/optimism/deploy-scratch.ts create mode 100644 scripts/optimism/upgrade.ts create mode 100644 utils/optimism/deployment.ts delete mode 100644 utils/optimism/deploymentNewImplementations.ts create mode 100644 utils/optimism/upgrade.ts diff --git a/package.json b/package.json index 06cba522..a4560476 100644 --- a/package.json +++ b/package.json @@ -7,21 +7,19 @@ "compile": "hardhat compile", "compile:force": "hardhat compile --force", "coverage": "hardhat coverage --testfiles './test/**/*.unit.test.ts'", - "test:e2e": "hardhat test ./test/**/*.e2e.test.ts", - "test:unit": "hardhat test ./test/**/*.unit.test.ts", - "test:integration": "hardhat test ./test/**/*.integration.test.ts", "fork:eth:mainnet": "hardhat node:fork eth_mainnet 8545", "fork:eth:sepolia": "hardhat node:fork eth_sepolia 8545", "fork:opt:sepolia": "hardhat node:fork opt_sepolia 9545", "fork:opt:mainnet": "hardhat node:fork opt_mainnet 9545", - "optimism:deploy": "ts-node --files ./scripts/optimism/deploy-bridge.ts", - "optimism:finalize-message": "ts-node --files ./scripts/optimism/finalize-message.ts", - "optimism:test:e2e": "hardhat test ./test/optimism/*.e2e.test.ts", - "optimism:test:unit": "hardhat test ./test/optimism/*.unit.test.ts", - "optimism:test:integration": "hardhat test ./test/optimism/*.integration.test.ts", - "optimism:test:acceptance": "hardhat test ./test/optimism/*.acceptance.test.ts", - "optimism:test:executor": "hardhat test ./test/bridge-executor/optimism.integration.test.ts", - "optimism:test:launch": "REVERT=false hardhat test ./test/optimism/{_launch.test.ts,bridging.integration.test.ts}" + "deploy": "ts-node --files ./scripts/optimism/deploy-scratch.ts", + "upgrade": "ts-node --files ./scripts/optimism/upgrade.ts", + "finalize-message": "ts-node --files ./scripts/optimism/finalize-message.ts", + "test:e2e": "hardhat test ./test/optimism/*.e2e.test.ts", + "test:unit": "hardhat test ./test/optimism/*.unit.test.ts", + "test:integration": "hardhat test ./test/optimism/*.integration.test.ts", + "test:acceptance": "hardhat test ./test/optimism/*.acceptance.test.ts", + "test:executor": "hardhat test ./test/bridge-executor/optimism.integration.test.ts", + "test:launch": "REVERT=false hardhat test ./test/optimism/{_launch.test.ts,bridging.integration.test.ts}" }, "keywords": [], "author": "", diff --git a/scripts/optimism/deploy-new-impls.ts b/scripts/optimism/deploy-new-impls.ts deleted file mode 100644 index bd6104c3..00000000 --- a/scripts/optimism/deploy-new-impls.ts +++ /dev/null @@ -1,88 +0,0 @@ -import env from "../../utils/env"; -import prompt from "../../utils/prompt"; -import network from "../../utils/network"; -import deployment from "../../utils/deployment"; - -import deploymentNewImplementations from "../../utils/optimism/deploymentNewImplementations"; -import { BigNumber } from "ethers"; - -async function main() { - const networkName = env.network(); - const ethOptNetwork = network.multichain(["eth", "opt"], networkName); - - const [ethDeployer] = ethOptNetwork.getSigners(env.privateKey(), { - forking: env.forking(), - }); - const [, optDeployer] = ethOptNetwork.getSigners( - env.string("OPT_DEPLOYER_PRIVATE_KEY"), - { - forking: env.forking(), - } - ); - - const deploymentConfig = deployment.loadMultiChainDeploymentConfig(); - - const [l1DeployScript, l2DeployScript] = await deploymentNewImplementations( - networkName, - { logger: console } - ) - .deployScript( - { - deployer: ethDeployer, - admins: { - proxy: deploymentConfig.l1.proxyAdmin, - bridge: ethDeployer.address - }, - contractsShift: 0, - tokenProxyAddress: deploymentConfig.l1Token, - tokenRebasableProxyAddress: deploymentConfig.l1RebasableToken, - opStackTokenRatePusherImplAddress: deploymentConfig.l1OpStackTokenRatePusher, - tokenBridgeProxyAddress: deploymentConfig.l1TokenBridge, - }, - { - deployer: optDeployer, - admins: { - proxy: deploymentConfig.l2.proxyAdmin, - bridge: optDeployer.address, - }, - contractsShift: 0, - tokenBridgeProxyAddress: deploymentConfig.l2TokenBridge, - tokenProxyAddress: deploymentConfig.l2Token, - tokenRateOracle: { - proxyAddress: deploymentConfig.l2TokenRateOracle, - rateOutdatedDelay: BigNumber.from(deploymentConfig.tokenRateOutdatedDelay), - maxAllowedL2ToL1ClockLag: BigNumber.from(86400), - maxAllowedTokenRateDeviationPerDay: BigNumber.from(500) - }, - token: { - name: "name", - symbol: "symbol", - version: "1" - }, - tokenRebasable: { - name: "name", - symbol: "symbol", - version: "1" - } - } - ); - - await deployment.printMultiChainDeploymentConfig( - "Deploy new implementations: bridges, wstETH, stETH", - ethDeployer, - optDeployer, - deploymentConfig, - l1DeployScript, - l2DeployScript - ); - - await prompt.proceed(); - - await l1DeployScript.run(); - await l2DeployScript.run(); -} - -main().catch((error) => { - console.error(error); - process.exitCode = 1; -}); diff --git a/scripts/optimism/deploy-oracle.ts b/scripts/optimism/deploy-oracle.ts deleted file mode 100644 index 88013720..00000000 --- a/scripts/optimism/deploy-oracle.ts +++ /dev/null @@ -1,85 +0,0 @@ -import env from "../../utils/env"; -import prompt from "../../utils/prompt"; -import network from "../../utils/network"; -import optimism from "../../utils/optimism"; -import deployment from "../../utils/deployment"; -import { TokenRateNotifier__factory } from "../../typechain"; - -async function main() { - const networkName = env.network(); - const ethOptNetwork = network.multichain(["eth", "opt"], networkName); - - const [ethDeployer] = ethOptNetwork.getSigners(env.privateKey(), { - forking: env.forking(), - }); - const [, optDeployer] = ethOptNetwork.getSigners( - env.string("OPT_DEPLOYER_PRIVATE_KEY"), - { - forking: env.forking(), - } - ); - - const blockNumber = await optDeployer.provider.getBlockNumber(); - const blockTimestamp = (await optDeployer.provider.getBlock(blockNumber)).timestamp; - - const deploymentConfig = deployment.loadMultiChainDeploymentConfig(); - - const [l1DeployScript, l2DeployScript] = await optimism - .deploymentOracle(networkName, { logger: console }) - .oracleDeployScript( - deploymentConfig.l1Token, - deploymentConfig.accountingOracle, - deploymentConfig.l2GasLimitForPushingTokenRate, - deploymentConfig.tokenRateOutdatedDelay, - { - deployer: ethDeployer, - admins: { - proxy: deploymentConfig.l1.proxyAdmin, - bridge: ethDeployer.address, - }, - contractsShift: 0 - }, - { - deployer: optDeployer, - admins: { - proxy: deploymentConfig.l2.proxyAdmin, - bridge: optDeployer.address, - }, - contractsShift: 0, - tokenRateOracle: { - maxAllowedL2ToL1ClockLag: 86400, - maxAllowedTokenRateDeviationPerDay: 500, - tokenRate: 1164454276599657236000000000, - l1Timestamp: blockTimestamp - } - } - ); - - await deployment.printMultiChainDeploymentConfig( - "Deploy Token Rate Oracle", - ethDeployer, - optDeployer, - deploymentConfig, - l1DeployScript, - l2DeployScript - ); - - await prompt.proceed(); - - await l1DeployScript.run(); - await l2DeployScript.run(); - - /// setup by adding observer - const tokenRateNotifier = TokenRateNotifier__factory.connect( - l1DeployScript.tokenRateNotifierImplAddress, - ethDeployer - ); - await tokenRateNotifier - .connect(ethDeployer) - .addObserver(l1DeployScript.opStackTokenRatePusherImplAddress); -} - -main().catch((error) => { - console.error(error); - process.exitCode = 1; -}); diff --git a/scripts/optimism/deploy-scratch.ts b/scripts/optimism/deploy-scratch.ts new file mode 100644 index 00000000..165155ec --- /dev/null +++ b/scripts/optimism/deploy-scratch.ts @@ -0,0 +1,98 @@ +import env from "../../utils/env"; +import prompt from "../../utils/prompt"; +import network from "../../utils/network"; +import deployment from "../../utils/deployment"; +import { BridgingManagement } from "../../utils/bridging-management"; +import deploymentAllFromScratch from "../../utils/optimism/deployment"; + +async function main() { + const networkName = env.network(); + const ethOptNetwork = network.multichain(["eth", "opt"], networkName); + + const [ethDeployer] = ethOptNetwork.getSigners(env.privateKey(), { + forking: env.forking(), + }); + const [, optDeployer] = ethOptNetwork.getSigners( + env.string("OPT_DEPLOYER_PRIVATE_KEY"), + { + forking: env.forking(), + } + ); + + const deploymentConfig = deployment.loadMultiChainDeploymentConfig(); + + const [l1DeployScript, l2DeployScript] = await deploymentAllFromScratch (networkName, { logger: console }) + .deployAllScript( + { + l1TokenNonRebasable: deploymentConfig.l1TokenNonRebasable, + l1TokenRebasable: deploymentConfig.l1RebasableToken, + accountingOracle: deploymentConfig.accountingOracle, + l2GasLimitForPushingTokenRate: deploymentConfig.l2GasLimitForPushingTokenRate, + + deployer: ethDeployer, + admins: { + proxy: deploymentConfig.l1.proxyAdmin, + bridge: ethDeployer.address + }, + contractsShift: 0, + }, + { + tokenRateOracle: { + tokenRateOutdatedDelay: deploymentConfig.tokenRateOutdatedDelay, + maxAllowedL2ToL1ClockLag: deploymentConfig.maxAllowedL2ToL1ClockLag, + maxAllowedTokenRateDeviationPerDayBp: deploymentConfig.maxAllowedTokenRateDeviationPerDayBp, + oldestRateAllowedInPauseTimeSpan: deploymentConfig.oldestRateAllowedInPauseTimeSpan, + maxAllowedTimeBetweenTokenRateUpdates: deploymentConfig.maxAllowedTimeBetweenTokenRateUpdates, + tokenRate: deploymentConfig.tokenRateValue, + l1Timestamp: deploymentConfig.tokenRateL1Timestamp + }, + l2TokenNonRebasable: { + version: "1" + }, + l2TokenRebasable: { + version: "1" + }, + + deployer: optDeployer, + admins: { + proxy: deploymentConfig.l2.proxyAdmin, + bridge: optDeployer.address, + }, + contractsShift: 0, + } + ); + + await deployment.printMultiChainDeploymentConfig( + "Deploy Optimism Bridge", + ethDeployer, + optDeployer, + deploymentConfig, + l1DeployScript, + l2DeployScript + ); + + await prompt.proceed(); + + await l1DeployScript.run(); + await l2DeployScript.run(); + + const l1BridgingManagement = new BridgingManagement( + l1DeployScript.bridgeProxyAddress, + ethDeployer, + { logger: console } + ); + + const l2BridgingManagement = new BridgingManagement( + l2DeployScript.tokenBridgeProxyAddress, + optDeployer, + { logger: console } + ); + + await l1BridgingManagement.setup(deploymentConfig.l1); + await l2BridgingManagement.setup(deploymentConfig.l2); +} + +main().catch((error) => { + console.error(error); + process.exitCode = 1; +}); diff --git a/scripts/optimism/upgrade.ts b/scripts/optimism/upgrade.ts new file mode 100644 index 00000000..c4d969b7 --- /dev/null +++ b/scripts/optimism/upgrade.ts @@ -0,0 +1,109 @@ +import env from "../../utils/env"; +import prompt from "../../utils/prompt"; +import network from "../../utils/network"; +import deployment from "../../utils/deployment"; +import { BridgingManagement } from "../../utils/bridging-management"; +import upgrade from "../../utils/optimism/upgrade"; + +async function main() { + const networkName = env.network(); + const ethOptNetwork = network.multichain(["eth", "opt"], networkName); + + const [ethDeployer] = ethOptNetwork.getSigners(env.privateKey(), { + forking: env.forking(), + }); + const [, optDeployer] = ethOptNetwork.getSigners( + env.string("OPT_DEPLOYER_PRIVATE_KEY"), + { + forking: env.forking(), + } + ); + + const deploymentConfig = deployment.loadMultiChainDeploymentConfig(); + + const [l1DeployScript, l2DeployScript] = await upgrade (networkName, { logger: console }) + .upgradeScript( + { + l1TokenNonRebasable: deploymentConfig.l1TokenNonRebasable, + l1TokenRebasable: deploymentConfig.l1RebasableToken, + accountingOracle: deploymentConfig.accountingOracle, + l2GasLimitForPushingTokenRate: deploymentConfig.l2GasLimitForPushingTokenRate, + + l1TokenBridge: deploymentConfig.l1TokenBridge, + + deployer: ethDeployer, + admins: { + proxy: deploymentConfig.l1.proxyAdmin, + bridge: ethDeployer.address + }, + contractsShift: 0, + }, + { + tokenRateOracle: { + constructor: { + tokenRateOutdatedDelay: deploymentConfig.tokenRateOutdatedDelay, + maxAllowedL2ToL1ClockLag: deploymentConfig.maxAllowedL2ToL1ClockLag, + maxAllowedTokenRateDeviationPerDayBp: deploymentConfig.maxAllowedTokenRateDeviationPerDayBp, + oldestRateAllowedInPauseTimeSpan: deploymentConfig.oldestRateAllowedInPauseTimeSpan, + maxAllowedTimeBetweenTokenRateUpdates: deploymentConfig.maxAllowedTimeBetweenTokenRateUpdates + }, + initialize: { + tokenRate: deploymentConfig.tokenRateValue, + l1Timestamp: deploymentConfig.tokenRateL1Timestamp + } + }, + + l2TokenBridge: deploymentConfig.l2TokenBridge, + + l2TokenNonRebasable: { + address: deploymentConfig.l2TokenNonRebasable, + version: "1" + }, + + l2TokenRebasable: { + version: "1" + }, + + deployer: optDeployer, + admins: { + proxy: deploymentConfig.l2.proxyAdmin, + bridge: optDeployer.address, + }, + contractsShift: 0, + } + ); + + await deployment.printMultiChainDeploymentConfig( + "Upgrade Optimism Bridge", + ethDeployer, + optDeployer, + deploymentConfig, + l1DeployScript, + l2DeployScript + ); + + await prompt.proceed(); + + await l1DeployScript.run(); + await l2DeployScript.run(); + + const l1BridgingManagement = new BridgingManagement( + l1DeployScript.bridgeProxyAddress, + ethDeployer, + { logger: console } + ); + + const l2BridgingManagement = new BridgingManagement( + l2DeployScript.tokenBridgeProxyAddress, + optDeployer, + { logger: console } + ); + + await l1BridgingManagement.setup(deploymentConfig.l1); + await l2BridgingManagement.setup(deploymentConfig.l2); +} + +main().catch((error) => { + console.error(error); + process.exitCode = 1; +}); diff --git a/test/bridge-executor/optimism.integration.test.ts b/test/bridge-executor/optimism.integration.test.ts index c573d5e2..7159ce59 100644 --- a/test/bridge-executor/optimism.integration.test.ts +++ b/test/bridge-executor/optimism.integration.test.ts @@ -13,11 +13,12 @@ import { wei } from "../../utils/wei"; import optimism from "../../utils/optimism"; import testing, { scenario } from "../../utils/testing"; import { BridgingManagerRole } from "../../utils/bridging-management"; +import { getExchangeRate } from "../../utils/testing/helpers"; import env from "../../utils/env"; import network from "../../utils/network"; import { getBridgeExecutorParams } from "../../utils/bridge-executor"; -import deploymentAll from "../../utils/optimism/deploymentAllFromScratch"; +import deploymentAll from "../../utils/optimism/deployment"; scenario("Optimism :: Bridge Executor integration test", ctxFactory) .step("Activate L2 bridge", async (ctx) => { @@ -204,6 +205,14 @@ async function ctxFactory() { .multichain(["eth", "opt"], networkName) .getProviders({ forking: true }); + const tokenRateDecimals = BigNumber.from(27); + const totalPooledEther = BigNumber.from('9309904612343950493629678'); + const totalShares = BigNumber.from('7975822843597609202337218'); + const maxAllowedL2ToL1ClockLag = BigNumber.from(86400); + const maxAllowedTokenRateDeviationPerDay = BigNumber.from(500); + const oldestRateAllowedInPauseTimeSpan = BigNumber.from(86400*3); + const maxAllowedTimeBetweenTokenRateUpdates = BigNumber.from(3600); + const exchangeRate = getExchangeRate(tokenRateDecimals, totalPooledEther, totalShares); const l1Deployer = testing.accounts.deployer(l1Provider); const l2Deployer = testing.accounts.deployer(l2Provider); @@ -219,7 +228,8 @@ async function ctxFactory() { l1Token.address, "Test Token", "TT", - BigNumber.from('1164454276599657236000000000') + totalPooledEther, + totalShares ); const accountingOracle = await new AccountingOracleStub__factory(l1Deployer).deploy(1,2,3); @@ -245,28 +255,46 @@ async function ctxFactory() { const [, optDeployScript] = await deploymentAll( networkName ).deployAllScript( - l1Token.address, - l1TokenRebasable.address, - accountingOracle.address, { + l1TokenNonRebasable: l1Token.address, + l1TokenRebasable: l1TokenRebasable.address, + accountingOracle: accountingOracle.address, + l2GasLimitForPushingTokenRate: BigNumber.from(300_000), deployer: l1Deployer, admins: { proxy: l1Deployer.address, bridge: l1Deployer.address }, - contractsShift: 0 + contractsShift: 0, }, { + tokenRateOracle: { + tokenRateOutdatedDelay: BigNumber.from(1000), + maxAllowedL2ToL1ClockLag: maxAllowedL2ToL1ClockLag, + maxAllowedTokenRateDeviationPerDayBp: maxAllowedTokenRateDeviationPerDay, + oldestRateAllowedInPauseTimeSpan: oldestRateAllowedInPauseTimeSpan, + maxAllowedTimeBetweenTokenRateUpdates: maxAllowedTimeBetweenTokenRateUpdates, + tokenRate: exchangeRate, + l1Timestamp: BigNumber.from('1000') + }, + l2TokenNonRebasable: { + name: "wstETH", + symbol: "WST", + version: "1", + decimals: 18 + }, + l2TokenRebasable: { + name: "stETH", + symbol: "ST", + version: "1", + decimals: 18 + }, deployer: l2Deployer, admins: { proxy: govBridgeExecutor.address, bridge: govBridgeExecutor.address, }, contractsShift: 0, - tokenRateOracle: { - tokenRate: BigNumber.from(10), - l1Timestamp:BigNumber.from(2) - } } ); diff --git a/test/optimism/bridging-non-rebasable.integration.test.ts b/test/optimism/bridging-non-rebasable.integration.test.ts index 428a25b9..2026f7a4 100644 --- a/test/optimism/bridging-non-rebasable.integration.test.ts +++ b/test/optimism/bridging-non-rebasable.integration.test.ts @@ -583,7 +583,6 @@ function ctxFactory(depositAmount: BigNumber, withdrawalAmount: BigNumber) { const tokenRateDecimals = BigNumber.from(27); const totalPooledEther = BigNumber.from('9309904612343950493629678'); const totalShares = BigNumber.from('7975822843597609202337218'); - const tokenRate = getExchangeRate(tokenRateDecimals, totalPooledEther, totalShares); const { l1Provider, @@ -596,6 +595,7 @@ function ctxFactory(depositAmount: BigNumber, withdrawalAmount: BigNumber) { const l1Snapshot = await l1Provider.send("evm_snapshot", []); const l2Snapshot = await l2Provider.send("evm_snapshot", []); + const tokenRate = await contracts.l1Token.getStETHByWstETH(BigNumber.from(10).pow(tokenRateDecimals)); await optimism.testing(networkName).stubL1CrossChainMessengerContract(); diff --git a/test/optimism/bridging-rebasable.integration.test.ts b/test/optimism/bridging-rebasable.integration.test.ts index a6656157..dab782f9 100644 --- a/test/optimism/bridging-rebasable.integration.test.ts +++ b/test/optimism/bridging-rebasable.integration.test.ts @@ -11,8 +11,7 @@ import { nonRebasableFromRebasableL1, nonRebasableFromRebasableL2, rebasableFromNonRebasableL1, - rebasableFromNonRebasableL2, - getExchangeRate + rebasableFromNonRebasableL2 } from "../../utils/testing/helpers"; type ContextType = Awaited>> @@ -95,7 +94,7 @@ function bridgingTestsSuit(scenarioInstance: ScenarioTest) { const { accountA: tokenHolderA } = ctx.accounts; const { depositAmountOfRebasableToken, tokenRate } = ctx.constants; - /// warp L1 + /// wrap L1 const depositAmountNonRebasable = nonRebasableFromRebasableL1( depositAmountOfRebasableToken, ctx.constants.totalPooledEther, @@ -103,7 +102,7 @@ function bridgingTestsSuit(scenarioInstance: ScenarioTest) { ); console.log("depositAmountOfRebasableToken=",depositAmountOfRebasableToken); - console.log("warp L1: depositAmountNonRebasable=",depositAmountNonRebasable); + console.log("wrap L1: depositAmountNonRebasable=",depositAmountNonRebasable); await l1TokenRebasable .connect(tokenHolderA.l1Signer) @@ -709,8 +708,6 @@ function ctxFactory( return async () => { const networkName = env.network("TESTING_OPT_NETWORK", "mainnet"); - const exchangeRate = getExchangeRate(tokenRateDecimals, totalPooledEther, totalShares); - const { l1Provider, l2Provider, @@ -722,6 +719,8 @@ function ctxFactory( const l1Snapshot = await l1Provider.send("evm_snapshot", []); const l2Snapshot = await l2Provider.send("evm_snapshot", []); + const exchangeRate = await contracts.l1Token.getStETHByWstETH(BigNumber.from(10).pow(tokenRateDecimals)); + await optimism.testing(networkName).stubL1CrossChainMessengerContract(); const accountA = testing.accounts.accountA(l1Provider, l2Provider); diff --git a/test/optimism/pushingTokenRate.e2e.test.ts b/test/optimism/pushingTokenRate.e2e.test.ts index a7cb3cc7..9fa24bcc 100644 --- a/test/optimism/pushingTokenRate.e2e.test.ts +++ b/test/optimism/pushingTokenRate.e2e.test.ts @@ -79,7 +79,7 @@ async function loadDeployedContracts( ) { return { l1Token: ERC20WrapperStub__factory.connect( - testingUtils.env.OPT_L1_TOKEN(), + testingUtils.env.OPT_L1_NON_REBASABLE_TOKEN(), l1SignerOrProvider ), tokenRateNotifier: TokenRateNotifier__factory.connect( diff --git a/utils/deployment.ts b/utils/deployment.ts index ea444a39..d9fc7406 100644 --- a/utils/deployment.ts +++ b/utils/deployment.ts @@ -1,5 +1,5 @@ import chalk from "chalk"; -import { Wallet } from "ethers"; +import { BigNumber, Wallet } from "ethers"; import env from "./env"; import { DeployScript } from "./deployment/DeployScript"; @@ -10,16 +10,29 @@ interface ChainDeploymentConfig extends BridgingManagerSetupConfig { } interface MultiChainDeploymentConfig { - l1Token: string; + /// L1 + l1TokenNonRebasable: string; l1RebasableToken: string; accountingOracle: string; - l1OpStackTokenRatePusher: string; - l2GasLimitForPushingTokenRate: number; - tokenRateOutdatedDelay: number; + l2GasLimitForPushingTokenRate: BigNumber; l1TokenBridge: string; + + /// L2 + /// Oracle + tokenRateOutdatedDelay: BigNumber; + maxAllowedL2ToL1ClockLag: BigNumber; + maxAllowedTokenRateDeviationPerDayBp: BigNumber; + oldestRateAllowedInPauseTimeSpan: BigNumber; + maxAllowedTimeBetweenTokenRateUpdates: BigNumber; + tokenRateValue: BigNumber; + tokenRateL1Timestamp: BigNumber; + + /// wstETH address to upgrade + l2TokenNonRebasable: string; + + /// bridge l2TokenBridge: string; - l2Token: string; - l2TokenRateOracle: string; + govBridgeExecutor: string; l1: ChainDeploymentConfig; l2: ChainDeploymentConfig; @@ -27,16 +40,26 @@ interface MultiChainDeploymentConfig { export function loadMultiChainDeploymentConfig(): MultiChainDeploymentConfig { return { - l1Token: env.address("TOKEN"), - l1RebasableToken: env.address("REBASABLE_TOKEN"), + /// L1 Part + l1TokenNonRebasable: env.address("L1_NON_REBASABLE_TOKEN"), + l1RebasableToken: env.address("L1_REBASABLE_TOKEN"), accountingOracle: env.address("ACCOUNTING_ORACLE"), - l1OpStackTokenRatePusher: env.address("L1_OP_STACK_TOKEN_RATE_PUSHER"), - l2GasLimitForPushingTokenRate: Number(env.string("L2_GAS_LIMIT_FOR_PUSHING_TOKEN_RATE")), - tokenRateOutdatedDelay: Number(env.string("TOKEN_RATE_OUTDATED_DELAY")), + l2GasLimitForPushingTokenRate: BigNumber.from(env.string("L2_GAS_LIMIT_FOR_PUSHING_TOKEN_RATE")), l1TokenBridge: env.address("L1_TOKEN_BRIDGE"), + + /// L2 Part + /// TokenRateOracle + tokenRateOutdatedDelay: BigNumber.from(env.string("TOKEN_RATE_OUTDATED_DELAY")), + maxAllowedL2ToL1ClockLag: BigNumber.from(env.string("MAX_ALLOWED_L2_TO_L1_CLOCK_LAG")), + maxAllowedTokenRateDeviationPerDayBp: BigNumber.from(env.string("MAX_ALLOWED_TOKEN_RATE_DEVIATION_PER_DAY_BP")), + oldestRateAllowedInPauseTimeSpan: BigNumber.from(env.string("OLDEST_RATE_ALLOWED_IN_PAUSE_TIME_SPAN")), + maxAllowedTimeBetweenTokenRateUpdates: BigNumber.from(env.string("MAX_ALLOWED_TIME_BETWEEN_TOKEN_RATE_UPDATES")), + tokenRateValue: BigNumber.from(env.string("TOKEN_RATE")), + tokenRateL1Timestamp: BigNumber.from(env.string("TOKEN_RATE_L1_TIMESTAMP")), + + l2TokenNonRebasable: env.address("L2_TOKEN_NON_REBASABLE"), l2TokenBridge: env.address("L2_TOKEN_BRIDGE"), - l2Token: env.address("L2_TOKEN"), - l2TokenRateOracle: env.address("L2_TOKEN_RATE_ORACLE"), + govBridgeExecutor: env.address("GOV_BRIDGE_EXECUTOR"), l1: { proxyAdmin: env.address("L1_PROXY_ADMIN"), @@ -61,6 +84,12 @@ export function loadMultiChainDeploymentConfig(): MultiChainDeploymentConfig { }; } +export async function printDeploymentConfig() { + const pad = " ".repeat(4); + console.log(`${pad}· Network: ${env.string("NETWORK")}`); + console.log(`${pad}· Forking: ${env.bool("FORKING")}`); +} + export async function printMultiChainDeploymentConfig( title: string, l1Deployer: Wallet, @@ -69,8 +98,13 @@ export async function printMultiChainDeploymentConfig( l1DeployScript: DeployScript, l2DeployScript: DeployScript ) { - const { l1Token, l1RebasableToken, l1, l2 } = deploymentParams; - console.log(chalk.bold(`${title} :: ${chalk.underline(l1Token)} :: ${chalk.underline(l1RebasableToken)}\n`)); + const { l1TokenNonRebasable, l1RebasableToken, l1, l2 } = deploymentParams; + console.log(chalk.bold(`${title} :: ${chalk.underline(l1TokenNonRebasable)} :: ${chalk.underline(l1RebasableToken)}\n`)); + + console.log(chalk.bold(" · Deployment Params:")); + await printDeploymentConfig(); + console.log(); + console.log(chalk.bold(" · L1 Deployment Params:")); await printChainDeploymentConfig(l1Deployer, l1); console.log(); diff --git a/utils/optimism/deployment.ts b/utils/optimism/deployment.ts new file mode 100644 index 00000000..3464ed08 --- /dev/null +++ b/utils/optimism/deployment.ts @@ -0,0 +1,369 @@ +import { assert } from "chai"; +import { BigNumber, Wallet } from "ethers"; +import addresses from "./addresses"; +import { OptDeploymentOptions, DeployScriptParams } from "./types"; +import network, { NetworkName } from "../network"; +import { DeployScript, Logger } from "../deployment/DeployScript"; +import { + ERC20BridgedPermit__factory, + ERC20RebasableBridgedPermit__factory, + L1LidoTokensBridge__factory, + L2ERC20ExtendedTokensBridge__factory, + OssifiableProxy__factory, + TokenRateOracle__factory, + TokenRateNotifier__factory, + OpStackTokenRatePusher__factory, + IERC20Metadata__factory +} from "../../typechain"; + +interface OptL1DeployScriptParams extends DeployScriptParams { + l1TokenNonRebasable: string; + l1TokenRebasable: string; + accountingOracle: string; + l2GasLimitForPushingTokenRate: BigNumber; +} + +interface OptL2DeployScriptParams extends DeployScriptParams { + l2TokenNonRebasable: { + name?: string; + symbol?: string; + version: string; + decimals?: number; + }; + l2TokenRebasable: { + name?: string; + symbol?: string; + version: string; + decimals?: number; + }; + tokenRateOracle: { + tokenRateOutdatedDelay: BigNumber; + maxAllowedL2ToL1ClockLag: BigNumber; + maxAllowedTokenRateDeviationPerDayBp: BigNumber; + oldestRateAllowedInPauseTimeSpan: BigNumber; + maxAllowedTimeBetweenTokenRateUpdates: BigNumber; + tokenRate: BigNumber; + l1Timestamp: BigNumber; + } +} + +export class L1DeployAllScript extends DeployScript { + + constructor( + deployer: Wallet, + bridgeImplAddress: string, + bridgeProxyAddress: string, + tokenRateNotifierImplAddress: string, + opStackTokenRatePusherImplAddress: string, + logger?: Logger + ) { + super(deployer, logger); + this.bridgeImplAddress = bridgeImplAddress; + this.bridgeProxyAddress = bridgeProxyAddress; + this.tokenRateNotifierImplAddress = tokenRateNotifierImplAddress; + this.opStackTokenRatePusherImplAddress = opStackTokenRatePusherImplAddress; + } + + public bridgeImplAddress: string; + public bridgeProxyAddress: string; + public tokenRateNotifierImplAddress: string; + public opStackTokenRatePusherImplAddress: string; +} + +export class L2DeployAllScript extends DeployScript { + + constructor( + deployer: Wallet, + tokenImplAddress: string, + tokenProxyAddress: string, + tokenRebasableImplAddress: string, + tokenRebasableProxyAddress: string, + tokenBridgeImplAddress: string, + tokenBridgeProxyAddress: string, + tokenRateOracleImplAddress: string, + tokenRateOracleProxyAddress: string, + logger?: Logger + ) { + super(deployer, logger); + this.tokenImplAddress = tokenImplAddress; + this.tokenProxyAddress = tokenProxyAddress; + this.tokenRebasableImplAddress = tokenRebasableImplAddress; + this.tokenRebasableProxyAddress = tokenRebasableProxyAddress; + this.tokenBridgeImplAddress = tokenBridgeImplAddress; + this.tokenBridgeProxyAddress = tokenBridgeProxyAddress; + this.tokenRateOracleImplAddress = tokenRateOracleImplAddress; + this.tokenRateOracleProxyAddress = tokenRateOracleProxyAddress; + } + + public tokenImplAddress: string; + public tokenProxyAddress: string; + public tokenRebasableImplAddress: string; + public tokenRebasableProxyAddress: string; + public tokenBridgeImplAddress: string; + public tokenBridgeProxyAddress: string; + public tokenRateOracleImplAddress: string; + public tokenRateOracleProxyAddress: string; +} + +/// Deploy all from scratch +/// L1 part +/// L1LidoTokensBridge + Proxy +/// TokenRateNotifier +/// OpStackTokenRatePusher +/// L2 part +/// TokenRateOracle + Proxy +/// ERC20BridgedPermit + Proxy +/// ERC20RebasableBridgedPermit + Proxy +/// L2ERC20ExtendedTokensBridge + Proxy +export default function deploymentAll( + networkName: NetworkName, + options: OptDeploymentOptions = {} +) { + const optAddresses = addresses(networkName, options); + return { + async deployAllScript( + l1Params: OptL1DeployScriptParams, + l2Params: OptL2DeployScriptParams, + ): Promise<[L1DeployAllScript, L2DeployAllScript]> { + + const [ + expectedL1TokenBridgeImplAddress, + expectedL1TokenBridgeProxyAddress, + expectedL1TokenRateNotifierImplAddress, + expectedL1OpStackTokenRatePusherImplAddress, + ] = await network.predictAddresses(l1Params.deployer, l1Params.contractsShift + 4); + + const [ + expectedL2TokenRateOracleImplAddress, + expectedL2TokenRateOracleProxyAddress, + expectedL2TokenImplAddress, + expectedL2TokenProxyAddress, + expectedL2TokenRebasableImplAddress, + expectedL2TokenRebasableProxyAddress, + expectedL2TokenBridgeImplAddress, + expectedL2TokenBridgeProxyAddress + ] = await network.predictAddresses(l2Params.deployer, l2Params.contractsShift + 8); + + const l1DeployScript = new L1DeployAllScript( + l1Params.deployer, + expectedL1TokenBridgeImplAddress, + expectedL1TokenBridgeProxyAddress, + expectedL1TokenRateNotifierImplAddress, + expectedL1OpStackTokenRatePusherImplAddress, + options?.logger + ) + .addStep({ + factory: L1LidoTokensBridge__factory, + args: [ + optAddresses.L1CrossDomainMessenger, + expectedL2TokenBridgeProxyAddress, + l1Params.l1TokenNonRebasable, + l1Params.l1TokenRebasable, + expectedL2TokenProxyAddress, + expectedL2TokenRebasableProxyAddress, + l1Params.accountingOracle, + options?.overrides, + ], + afterDeploy: (c) => + assert.equal(c.address, expectedL1TokenBridgeImplAddress), + }) + .addStep({ + factory: OssifiableProxy__factory, + args: [ + expectedL1TokenBridgeImplAddress, + l1Params.admins.proxy, + L1LidoTokensBridge__factory.createInterface().encodeFunctionData( + "initialize", + [l1Params.admins.bridge] + ), + options?.overrides, + ], + afterDeploy: (c) => + assert.equal(c.address, expectedL1TokenBridgeProxyAddress), + }) + .addStep({ + factory: TokenRateNotifier__factory, + args: [ + l1Params.deployer.address, + options?.overrides, + ], + afterDeploy: (c) => + assert.equal(c.address, expectedL1TokenRateNotifierImplAddress), + }) + .addStep({ + factory: OpStackTokenRatePusher__factory, + args: [ + optAddresses.L1CrossDomainMessenger, + l1Params.l1TokenNonRebasable, + l1Params.accountingOracle, + expectedL2TokenRateOracleProxyAddress, + l1Params.l2GasLimitForPushingTokenRate, + options?.overrides, + ], + afterDeploy: (c) => + assert.equal(c.address, expectedL1OpStackTokenRatePusherImplAddress), + }); + + const l1TokenNonRebasableInfo = IERC20Metadata__factory.connect( + l1Params.l1TokenNonRebasable, + l1Params.deployer + ); + + const l1TokenRebasableInfo = IERC20Metadata__factory.connect( + l1Params.l1TokenRebasable, + l1Params.deployer + ); + + const [ + l2TokenNonRebasableDecimals, l2TokenNonRebasableName, l2TokenNonRebasableSymbol, + l2TokenRebasableDecimals, l2TokenRebasableName, l2TokenRebasableSymbol + ] = await Promise.all([ + l1TokenNonRebasableInfo.decimals(), + l2Params.l2TokenNonRebasable?.name ?? l1TokenNonRebasableInfo.name(), + l2Params.l2TokenNonRebasable?.symbol ?? l1TokenNonRebasableInfo.symbol(), + l1TokenRebasableInfo.decimals(), + l2Params.l2TokenRebasable?.name ?? l1TokenRebasableInfo.name(), + l2Params.l2TokenRebasable?.symbol ?? l1TokenRebasableInfo.symbol(), + ]); + + const l2DeployScript = new L2DeployAllScript( + l2Params.deployer, + expectedL2TokenImplAddress, + expectedL2TokenProxyAddress, + expectedL2TokenRebasableImplAddress, + expectedL2TokenRebasableProxyAddress, + expectedL2TokenBridgeImplAddress, + expectedL2TokenBridgeProxyAddress, + expectedL2TokenRateOracleImplAddress, + expectedL2TokenRateOracleProxyAddress, + options?.logger + ) + .addStep({ + factory: TokenRateOracle__factory, + args: [ + optAddresses.L2CrossDomainMessenger, + expectedL2TokenBridgeProxyAddress, + expectedL1OpStackTokenRatePusherImplAddress, + l2Params.tokenRateOracle.tokenRateOutdatedDelay, + l2Params.tokenRateOracle.maxAllowedL2ToL1ClockLag, + l2Params.tokenRateOracle.maxAllowedTokenRateDeviationPerDayBp, + l2Params.tokenRateOracle.oldestRateAllowedInPauseTimeSpan, + l2Params.tokenRateOracle.maxAllowedTimeBetweenTokenRateUpdates, + options?.overrides, + ], + afterDeploy: (c) => + assert.equal(c.address, expectedL2TokenRateOracleImplAddress), + }) + .addStep({ + factory: OssifiableProxy__factory, + args: [ + expectedL2TokenRateOracleImplAddress, + l2Params.admins.proxy, + TokenRateOracle__factory.createInterface().encodeFunctionData( + "initialize", + [ + l2Params.admins.bridge, + l2Params.tokenRateOracle.tokenRate, + l2Params.tokenRateOracle.l1Timestamp + ] + ), + options?.overrides, + ], + afterDeploy: (c) => + assert.equal(c.address, expectedL2TokenRateOracleProxyAddress), + }) + .addStep({ + factory: ERC20BridgedPermit__factory, + args: [ + l2TokenNonRebasableName, + l2TokenNonRebasableSymbol, + l2Params.l2TokenNonRebasable.version, + l2TokenNonRebasableDecimals, + expectedL2TokenBridgeProxyAddress, + options?.overrides, + ], + afterDeploy: (c) => + assert.equal(c.address, expectedL2TokenImplAddress), + }) + .addStep({ + factory: OssifiableProxy__factory, + args: [ + expectedL2TokenImplAddress, + l2Params.admins.proxy, + ERC20BridgedPermit__factory.createInterface().encodeFunctionData( + "initialize", + [ + l2TokenNonRebasableName, + l2TokenNonRebasableSymbol, + l2Params.l2TokenNonRebasable.version + ] + ), + options?.overrides, + ], + afterDeploy: (c) => + assert.equal(c.address, expectedL2TokenProxyAddress), + }) + .addStep({ + factory: ERC20RebasableBridgedPermit__factory, + args: [ + l2TokenRebasableName, + l2TokenRebasableSymbol, + l2Params.l2TokenRebasable.version, + l2TokenRebasableDecimals, + expectedL2TokenProxyAddress, + expectedL2TokenRateOracleProxyAddress, + expectedL2TokenBridgeProxyAddress, + options?.overrides, + ], + afterDeploy: (c) => + assert.equal(c.address, expectedL2TokenRebasableImplAddress), + }) + .addStep({ + factory: OssifiableProxy__factory, + args: [ + expectedL2TokenRebasableImplAddress, + l2Params.admins.proxy, + ERC20RebasableBridgedPermit__factory.createInterface().encodeFunctionData( + "initialize", + [ + l2TokenRebasableName, + l2TokenRebasableSymbol, + l2Params.l2TokenRebasable.version + ] + ), + options?.overrides, + ], + afterDeploy: (c) => + assert.equal(c.address, expectedL2TokenRebasableProxyAddress), + }) + .addStep({ + factory: L2ERC20ExtendedTokensBridge__factory, + args: [ + optAddresses.L2CrossDomainMessenger, + expectedL1TokenBridgeProxyAddress, + l1Params.l1TokenNonRebasable, + l1Params.l1TokenRebasable, + expectedL2TokenProxyAddress, + expectedL2TokenRebasableProxyAddress, + options?.overrides, + ], + afterDeploy: (c) => + assert.equal(c.address, expectedL2TokenBridgeImplAddress), + }) + .addStep({ + factory: OssifiableProxy__factory, + args: [ + expectedL2TokenBridgeImplAddress, + l2Params.admins.proxy, + L2ERC20ExtendedTokensBridge__factory.createInterface().encodeFunctionData( + "initialize", + [l2Params.admins.bridge] + ), + options?.overrides, + ], + }); + + return [l1DeployScript as L1DeployAllScript, l2DeployScript as L2DeployAllScript]; + }, + }; +} diff --git a/utils/optimism/deploymentNewImplementations.ts b/utils/optimism/deploymentNewImplementations.ts deleted file mode 100644 index d2080086..00000000 --- a/utils/optimism/deploymentNewImplementations.ts +++ /dev/null @@ -1,239 +0,0 @@ -import { assert } from "chai"; -import { BigNumber, Wallet } from "ethers"; -import addresses from "./addresses"; -import { OptDeploymentOptions, DeployScriptParams } from "./types"; -import network, { NetworkName } from "../network"; -import { DeployScript, Logger } from "../deployment/DeployScript"; -import { - ERC20BridgedPermit__factory, - ERC20RebasableBridgedPermit__factory, - IERC20Metadata__factory, - L1LidoTokensBridge__factory, - L2ERC20ExtendedTokensBridge__factory, - OssifiableProxy__factory, - TokenRateOracle__factory -} from "../../typechain"; - -interface OptL1DeployScriptParams extends DeployScriptParams { - tokenProxyAddress: string; - tokenRebasableProxyAddress: string; - opStackTokenRatePusherImplAddress: string; - tokenBridgeProxyAddress: string; - deployer: Wallet; - admins: { - proxy: string; - bridge: string - }; - contractsShift: number; -} - -interface OptL2DeployScriptParams extends DeployScriptParams { - tokenBridgeProxyAddress: string; - tokenProxyAddress: string; - tokenRateOracle: { - proxyAddress: string; - rateOutdatedDelay: BigNumber; - maxAllowedL2ToL1ClockLag: BigNumber; - maxAllowedTokenRateDeviationPerDay: BigNumber; - } - token: { - name: string; - symbol: string; - version: string; - }; - tokenRebasable: { - name: string; - symbol: string; - version: string; - }; -} - -export class BridgeL1DeployScript extends DeployScript { - - constructor( - deployer: Wallet, - bridgeImplAddress: string, - logger?: Logger - ) { - super(deployer, logger); - this.bridgeImplAddress = bridgeImplAddress; - } - - public bridgeImplAddress: string; -} - -export class BridgeL2DeployScript extends DeployScript { - - constructor( - deployer: Wallet, - tokenImplAddress: string, - tokenRebasableImplAddress: string, - tokenRebasableProxyAddress: string, - tokenBridgeImplAddress: string, - tokenRateOracleImplAddress: string, - logger?: Logger - ) { - super(deployer, logger); - this.tokenImplAddress = tokenImplAddress; - this.tokenRebasableImplAddress = tokenRebasableImplAddress; - this.tokenRebasableProxyAddress = tokenRebasableProxyAddress; - this.tokenBridgeImplAddress = tokenBridgeImplAddress; - this.tokenRateOracleImplAddress = tokenRateOracleImplAddress; - } - - public tokenImplAddress: string; - public tokenRebasableImplAddress: string; - public tokenRebasableProxyAddress: string; - public tokenBridgeImplAddress: string; - public tokenRateOracleImplAddress: string; -} - -/// deploys -/// - new L1Bridge Impl -/// - new L2Bridge Impl -/// - RebasableToken(stETH) Impl and Proxy (because it was never deployed before) -/// - Non-rebasable token (wstETH) new Impl with Permissions -export default function deploymentNewImplementations( - networkName: NetworkName, - options: OptDeploymentOptions = {} -) { - const optAddresses = addresses(networkName, options); - return { - async deployScript( - l1Params: OptL1DeployScriptParams, - l2Params: OptL2DeployScriptParams, - ): Promise<[BridgeL1DeployScript, BridgeL2DeployScript]> { - - const [ - expectedL1TokenBridgeImplAddress, - ] = await network.predictAddresses(l1Params.deployer, l1Params.contractsShift + 1); - - const [ - expectedL2TokenImplAddress, - expectedL2TokenRebasableImplAddress, - expectedL2TokenRebasableProxyAddress, - expectedL2TokenBridgeImplAddress, - expectedL2TokenRateOracleImplAddress - ] = await network.predictAddresses(l2Params.deployer, l2Params.contractsShift + 5); - - const l1DeployScript = new BridgeL1DeployScript( - l1Params.deployer, - expectedL1TokenBridgeImplAddress, - options?.logger - ) - .addStep({ - factory: L1LidoTokensBridge__factory, - args: [ - optAddresses.L1CrossDomainMessenger, - l2Params.tokenBridgeProxyAddress, - l1Params.tokenProxyAddress, - l1Params.tokenRebasableProxyAddress, - l2Params.tokenProxyAddress, - expectedL2TokenRebasableProxyAddress, - options?.overrides, - ], - afterDeploy: (c) => - assert.equal(c.address, expectedL1TokenBridgeImplAddress), - }); - - const l1TokenInfo = IERC20Metadata__factory.connect( - l1Params.tokenProxyAddress, - l1Params.deployer - ); - - const l1TokenRebasableInfo = IERC20Metadata__factory.connect( - l1Params.tokenRebasableProxyAddress, - l1Params.deployer - ); - const [decimals, l2TokenName, l2TokenSymbol, l2TokenRebasableName, l2TokenRebasableSymbol] = await Promise.all([ - l1TokenInfo.decimals(), - l2Params.token?.name ?? l1TokenInfo.name(), - l2Params.token?.symbol ?? l1TokenInfo.symbol(), - l2Params.tokenRebasable?.name ?? l1TokenRebasableInfo.name(), - l2Params.tokenRebasable?.symbol ?? l1TokenRebasableInfo.symbol(), - ]); - - const l2DeployScript = new BridgeL2DeployScript( - l2Params.deployer, - expectedL2TokenImplAddress, - expectedL2TokenRebasableImplAddress, - expectedL2TokenRebasableProxyAddress, - expectedL2TokenBridgeImplAddress, - expectedL2TokenRateOracleImplAddress, - options?.logger - ) - .addStep({ - factory: ERC20BridgedPermit__factory, - args: [ - l2TokenName, - l2TokenSymbol, - l2Params.token.version, - decimals, - l2Params.tokenBridgeProxyAddress, - options?.overrides, - ], - afterDeploy: (c) => - assert.equal(c.address, expectedL2TokenImplAddress), - }) - .addStep({ - factory: ERC20RebasableBridgedPermit__factory, - args: [ - l2TokenRebasableName, - l2TokenRebasableSymbol, - l2Params.tokenRebasable.version, - decimals, - l2Params.tokenProxyAddress, - l2Params.tokenRateOracle.proxyAddress, - l2Params.tokenBridgeProxyAddress, - options?.overrides, - ], - afterDeploy: (c) => - assert.equal(c.address, expectedL2TokenRebasableImplAddress), - }) - .addStep({ - factory: OssifiableProxy__factory, - args: [ - expectedL2TokenRebasableImplAddress, - l2Params.admins.proxy, - ERC20RebasableBridgedPermit__factory.createInterface().encodeFunctionData( - "initialize", - [l2TokenRebasableName, l2TokenRebasableSymbol, l2Params.tokenRebasable.version] - ), - options?.overrides, - ], - afterDeploy: (c) => - assert.equal(c.address, expectedL2TokenRebasableProxyAddress), - }) - .addStep({ - factory: L2ERC20ExtendedTokensBridge__factory, - args: [ - optAddresses.L2CrossDomainMessenger, - l1Params.tokenBridgeProxyAddress, - l1Params.tokenProxyAddress, - l1Params.tokenRebasableProxyAddress, - l2Params.tokenProxyAddress, - expectedL2TokenRebasableProxyAddress, - options?.overrides, - ], - afterDeploy: (c) => - assert.equal(c.address, expectedL2TokenBridgeImplAddress), - }) - .addStep({ - factory: TokenRateOracle__factory, - args: [ - optAddresses.L2CrossDomainMessenger, - l2Params.tokenBridgeProxyAddress, - l1Params.opStackTokenRatePusherImplAddress, - l2Params.tokenRateOracle.rateOutdatedDelay, - l2Params.tokenRateOracle.maxAllowedL2ToL1ClockLag, - l2Params.tokenRateOracle.maxAllowedTokenRateDeviationPerDay, - options?.overrides, - ], - afterDeploy: (c) => - assert.equal(c.address, expectedL2TokenRateOracleImplAddress), - }); - - return [l1DeployScript as BridgeL1DeployScript, l2DeployScript as BridgeL2DeployScript]; - }, - }; -} diff --git a/utils/optimism/index.ts b/utils/optimism/index.ts index d9732d61..330f3644 100644 --- a/utils/optimism/index.ts +++ b/utils/optimism/index.ts @@ -1,6 +1,5 @@ import addresses from "./addresses"; import contracts from "./contracts"; -import deploymentOracle from "./deploymentOracle"; import testing from "./testing"; import messaging from "./messaging"; @@ -8,7 +7,6 @@ export default { testing, addresses, contracts, - messaging, - deploymentOracle + messaging }; diff --git a/utils/optimism/testing.ts b/utils/optimism/testing.ts index d2c5a0ee..f27e1f8b 100644 --- a/utils/optimism/testing.ts +++ b/utils/optimism/testing.ts @@ -15,11 +15,12 @@ import { L2ERC20ExtendedTokensBridge__factory, CrossDomainMessengerStub__factory, ERC20RebasableBridgedPermit__factory, - AccountingOracleStub__factory + AccountingOracleStub__factory, + IAccountingOracle__factory, } from "../../typechain"; import addresses from "./addresses"; import contracts from "./contracts"; -import deploymentAll from "./deploymentAllFromScratch"; +import deploymentAll from "./deployment"; import testingUtils from "../testing"; import { BridgingManagement } from "../bridging-management"; import network, { NetworkName, SignerOrProvider } from "../network"; @@ -160,7 +161,7 @@ async function loadDeployedBridges( ) { return { l1Token: ERC20WrapperStub__factory.connect( - testingUtils.env.OPT_L1_TOKEN(), + testingUtils.env.OPT_L1_NON_REBASABLE_TOKEN(), l1SignerOrProvider ), l1TokenRebasable: IERC20__factory.connect( @@ -168,14 +169,14 @@ async function loadDeployedBridges( l1SignerOrProvider ), accountingOracle: AccountingOracleStub__factory.connect( - testingUtils.env.OPT_L1_REBASABLE_TOKEN(), + testingUtils.env.OPT_L1_ACCOUNTING_ORACLE(), l1SignerOrProvider ), ...connectBridgeContracts( { tokenRateOracle: testingUtils.env.OPT_L2_TOKEN_RATE_ORACLE(), - l2Token: testingUtils.env.OPT_L2_TOKEN(), + l2Token: testingUtils.env.OPT_L2_NON_REBASABLE_TOKEN(), l2TokenRebasable: testingUtils.env.OPT_L2_REBASABLE_TOKEN(), l1LidoTokensBridge: testingUtils.env.OPT_L1_ERC20_TOKEN_BRIDGE(), l2ERC20ExtendedTokensBridge: testingUtils.env.OPT_L2_ERC20_TOKEN_BRIDGE(), @@ -193,6 +194,18 @@ async function deployTestBridge( ethProvider: JsonRpcProvider, optProvider: JsonRpcProvider ) { + const tokenRate = BigNumber.from(10).pow(27) + .mul(totalPooledEther) + .div(totalShares); + const genesisTime = 1; + const secondsPerSlot = 2; + const lastProcessingRefSlot = 3; + const maxAllowedL2ToL1ClockLag = BigNumber.from(86400); + const maxAllowedTokenRateDeviationPerDay = BigNumber.from(500); + const oldestRateAllowedInPauseTimeSpan = BigNumber.from(86400*3); + const maxAllowedTimeBetweenTokenRateUpdates = BigNumber.from(3600); + const tokenRateOutdatedDelay = BigNumber.from(86400); + const ethDeployer = testingUtils.accounts.deployer(ethProvider); const optDeployer = testingUtils.accounts.deployer(optProvider); @@ -209,31 +222,20 @@ async function deployTestBridge( totalShares ); - const tokenRate = BigNumber.from(10).pow(27).mul(totalPooledEther).div(totalShares); - const genesisTime = 1; - const secondsPerSlot = 2; - const lastProcessingRefSlot = 3; - const accountingOracle = await new AccountingOracleStub__factory(ethDeployer).deploy( genesisTime, secondsPerSlot, lastProcessingRefSlot ); - const maxAllowedL2ToL1ClockLag = BigNumber.from(86400); - const maxAllowedTokenRateDeviationPerDay = BigNumber.from(500); - const oldestRateAllowedInPauseTimeSpan = BigNumber.from(86400*3); - const maxAllowedTimeBetweenTokenRateUpdates = BigNumber.from(3600); - const tokenRateOutdatedDelay = BigNumber.from(86400); - const [ethDeployScript, optDeployScript] = await deploymentAll( networkName ).deployAllScript( { - l1Token: l1TokenNonRebasable.address, + l1TokenNonRebasable: l1TokenNonRebasable.address, l1TokenRebasable: l1TokenRebasable.address, accountingOracle: accountingOracle.address, - l2GasLimitForPushingTokenRate: 300_000, + l2GasLimitForPushingTokenRate: BigNumber.from(300_000), deployer: ethDeployer, admins: { proxy: ethDeployer.address, bridge: ethDeployer.address }, contractsShift: 0 diff --git a/utils/optimism/upgrade.ts b/utils/optimism/upgrade.ts new file mode 100644 index 00000000..a01cb864 --- /dev/null +++ b/utils/optimism/upgrade.ts @@ -0,0 +1,329 @@ +import { assert } from "chai"; +import { BigNumber, Wallet } from "ethers"; +import addresses from "./addresses"; +import { OptDeploymentOptions, DeployScriptParams } from "./types"; +import network, { NetworkName } from "../network"; +import { DeployScript, Logger } from "../deployment/DeployScript"; +import { + ERC20BridgedPermit__factory, + ERC20RebasableBridgedPermit__factory, + L1LidoTokensBridge__factory, + L2ERC20ExtendedTokensBridge__factory, + OssifiableProxy__factory, + TokenRateOracle__factory, + TokenRateNotifier__factory, + OpStackTokenRatePusher__factory, + IERC20Metadata__factory +} from "../../typechain"; + +interface OptL1UpgradeScriptParams extends DeployScriptParams { + l1TokenNonRebasable: string; + l1TokenRebasable: string; + accountingOracle: string; + l2GasLimitForPushingTokenRate: BigNumber; + l1TokenBridge: string; +} + +interface OptL2UpgradeScriptParams extends DeployScriptParams { + l2TokenBridge: string; + l2TokenNonRebasable: { + address: string; + name?: string; + symbol?: string; + version: string; + decimals?: BigNumber; + }; + l2TokenRebasable: { + name?: string; + symbol?: string; + version: string; + decimals?: BigNumber; + }; + tokenRateOracle: { + constructor: { + tokenRateOutdatedDelay: BigNumber; + maxAllowedL2ToL1ClockLag: BigNumber; + maxAllowedTokenRateDeviationPerDayBp: BigNumber; + oldestRateAllowedInPauseTimeSpan: BigNumber; + maxAllowedTimeBetweenTokenRateUpdates: BigNumber; + } + initialize: { + tokenRate: BigNumber; + l1Timestamp: BigNumber; + } + } +} + +export class L1UpgradeScript extends DeployScript { + + constructor( + deployer: Wallet, + bridgeProxyAddress: string, + bridgeImplAddress: string, + tokenRateNotifierImplAddress: string, + opStackTokenRatePusherImplAddress: string, + logger?: Logger + ) { + super(deployer, logger); + this.bridgeProxyAddress = bridgeProxyAddress; + this.bridgeImplAddress = bridgeImplAddress; + this.tokenRateNotifierImplAddress = tokenRateNotifierImplAddress; + this.opStackTokenRatePusherImplAddress = opStackTokenRatePusherImplAddress; + } + + public bridgeProxyAddress: string; + public bridgeImplAddress: string; + public tokenRateNotifierImplAddress: string; + public opStackTokenRatePusherImplAddress: string; +} + +export class L2UpgradeScript extends DeployScript { + + constructor( + deployer: Wallet, + tokenImplAddress: string, + tokenRebasableImplAddress: string, + tokenRebasableProxyAddress: string, + tokenBridgeProxyAddress: string, + tokenBridgeImplAddress: string, + tokenRateOracleImplAddress: string, + tokenRateOracleProxyAddress: string, + logger?: Logger + ) { + super(deployer, logger); + this.tokenImplAddress = tokenImplAddress; + this.tokenRebasableImplAddress = tokenRebasableImplAddress; + this.tokenRebasableProxyAddress = tokenRebasableProxyAddress; + this.tokenBridgeProxyAddress = tokenBridgeProxyAddress; + this.tokenBridgeImplAddress = tokenBridgeImplAddress; + this.tokenRateOracleImplAddress = tokenRateOracleImplAddress; + this.tokenRateOracleProxyAddress = tokenRateOracleProxyAddress; + } + + public tokenImplAddress: string; + public tokenRebasableImplAddress: string; + public tokenRebasableProxyAddress: string; + public tokenBridgeProxyAddress: string; + public tokenBridgeImplAddress: string; + public tokenRateOracleImplAddress: string; + public tokenRateOracleProxyAddress: string; +} + + +/// L1 part +/// new TokenRateNotifier Impl +/// new OpStackTokenRatePusher Impl +/// new L1Bridge Impl +/// L2 part +/// TokenRateOracle + proxy +/// new L2Bridge Impl +/// RebasableToken(stETH) Impl and Proxy (because it was never deployed before) +/// Non-rebasable token (wstETH) new Impl with Permissions + +export default function upgrade( + networkName: NetworkName, + options: OptDeploymentOptions = {} +) { + const optAddresses = addresses(networkName, options); + return { + async upgradeScript( + l1Params: OptL1UpgradeScriptParams, + l2Params: OptL2UpgradeScriptParams, + ): Promise<[L1UpgradeScript, L2UpgradeScript]> { + + const [ + expectedL1TokenBridgeImplAddress, + expectedL1TokenRateNotifierImplAddress, + expectedL1OpStackTokenRatePusherImplAddress, + ] = await network.predictAddresses(l1Params.deployer, l1Params.contractsShift + 3); + + const [ + // Oracle + Proxy + expectedL2TokenRateOracleImplAddress, + expectedL2TokenRateOracleProxyAddress, + // wstETH Impl + expectedL2TokenImplAddress, + // stETH Impl + Proxy + expectedL2TokenRebasableImplAddress, + expectedL2TokenRebasableProxyAddress, + // L2Bridge Impl + expectedL2TokenBridgeImplAddress + ] = await network.predictAddresses(l2Params.deployer, l2Params.contractsShift + 6); + + const l1UpgradeScript = new L1UpgradeScript( + l1Params.deployer, + l1Params.l1TokenBridge, + expectedL1TokenBridgeImplAddress, + expectedL1TokenRateNotifierImplAddress, + expectedL1OpStackTokenRatePusherImplAddress, + options?.logger + ) + .addStep({ + factory: L1LidoTokensBridge__factory, + args: [ + optAddresses.L1CrossDomainMessenger, + l2Params.l2TokenBridge, + l1Params.l1TokenNonRebasable, + l1Params.l1TokenRebasable, + l2Params.l2TokenNonRebasable.address, + expectedL2TokenRebasableProxyAddress, + l1Params.accountingOracle, + options?.overrides, + ], + afterDeploy: (c) => + assert.equal(c.address, expectedL1TokenBridgeImplAddress), + }) + .addStep({ + factory: TokenRateNotifier__factory, + args: [ + l1Params.deployer.address, + options?.overrides, + ], + afterDeploy: (c) => + assert.equal(c.address, expectedL1TokenRateNotifierImplAddress), + }) + .addStep({ + factory: OpStackTokenRatePusher__factory, + args: [ + optAddresses.L1CrossDomainMessenger, + l1Params.l1TokenNonRebasable, + l1Params.accountingOracle, + expectedL2TokenRateOracleProxyAddress, + l1Params.l2GasLimitForPushingTokenRate, + options?.overrides, + ], + afterDeploy: (c) => + assert.equal(c.address, expectedL1OpStackTokenRatePusherImplAddress), + }); + + const l1TokenNonRebasableInfo = IERC20Metadata__factory.connect( + l1Params.l1TokenNonRebasable, + l1Params.deployer + ); + + const l1TokenRebasableInfo = IERC20Metadata__factory.connect( + l1Params.l1TokenRebasable, + l1Params.deployer + ); + const [ + l2TokenNonRebasableDecimals, l2TokenNonRebasableName, l2TokenNonRebasableSymbol, + l2TokenRebasableDecimals, l2TokenRebasableName, l2TokenRebasableSymbol + ] = await Promise.all([ + l1TokenNonRebasableInfo.decimals(), + l2Params.l2TokenNonRebasable?.name ?? l1TokenNonRebasableInfo.name(), + l2Params.l2TokenNonRebasable?.symbol ?? l1TokenNonRebasableInfo.symbol(), + l1TokenRebasableInfo.decimals(), + l2Params.l2TokenRebasable?.name ?? l1TokenRebasableInfo.name(), + l2Params.l2TokenRebasable?.symbol ?? l1TokenRebasableInfo.symbol(), + ]); + + const l2UpgradeScript = new L2UpgradeScript( + l2Params.deployer, + expectedL2TokenImplAddress, + expectedL2TokenRebasableImplAddress, + expectedL2TokenRebasableProxyAddress, + l2Params.l2TokenBridge, + expectedL2TokenBridgeImplAddress, + expectedL2TokenRateOracleImplAddress, + expectedL2TokenRateOracleProxyAddress, + options?.logger + ) + .addStep({ + factory: TokenRateOracle__factory, + args: [ + optAddresses.L2CrossDomainMessenger, + l2Params.l2TokenBridge, + expectedL1OpStackTokenRatePusherImplAddress, + l2Params.tokenRateOracle.constructor.tokenRateOutdatedDelay, + l2Params.tokenRateOracle.constructor.maxAllowedL2ToL1ClockLag, + l2Params.tokenRateOracle.constructor.maxAllowedTokenRateDeviationPerDayBp, + l2Params.tokenRateOracle.constructor.oldestRateAllowedInPauseTimeSpan, + l2Params.tokenRateOracle.constructor.maxAllowedTimeBetweenTokenRateUpdates, + options?.overrides, + ], + afterDeploy: (c) => + assert.equal(c.address, expectedL2TokenRateOracleImplAddress), + }) + .addStep({ + factory: OssifiableProxy__factory, + args: [ + expectedL2TokenRateOracleImplAddress, + l2Params.admins.proxy, + TokenRateOracle__factory.createInterface().encodeFunctionData( + "initialize", + [ + l2Params.admins.bridge, + l2Params.tokenRateOracle.initialize.tokenRate, + l2Params.tokenRateOracle.initialize.l1Timestamp + ] + ), + options?.overrides, + ], + afterDeploy: (c) => + assert.equal(c.address, expectedL2TokenRateOracleProxyAddress), + }) + .addStep({ + factory: ERC20BridgedPermit__factory, + args: [ + l2TokenNonRebasableName, + l2TokenNonRebasableSymbol, + l2Params.l2TokenNonRebasable.version, + l2TokenNonRebasableDecimals, + l2Params.l2TokenBridge, + options?.overrides, + ], + afterDeploy: (c) => + assert.equal(c.address, expectedL2TokenImplAddress), + }) + .addStep({ + factory: ERC20RebasableBridgedPermit__factory, + args: [ + l2TokenRebasableName, + l2TokenRebasableSymbol, + l2Params.l2TokenRebasable.version, + l2TokenRebasableDecimals, + l2Params.l2TokenNonRebasable.address, + expectedL2TokenRateOracleProxyAddress, + l2Params.l2TokenBridge, + options?.overrides, + ], + afterDeploy: (c) => + assert.equal(c.address, expectedL2TokenRebasableImplAddress), + }) + .addStep({ + factory: OssifiableProxy__factory, + args: [ + expectedL2TokenRebasableImplAddress, + l2Params.admins.proxy, + ERC20RebasableBridgedPermit__factory.createInterface().encodeFunctionData( + "initialize", + [ + l2TokenRebasableName, + l2TokenRebasableSymbol, + l2Params.l2TokenRebasable.version + ] + ), + options?.overrides, + ], + afterDeploy: (c) => + assert.equal(c.address, expectedL2TokenRebasableProxyAddress), + }) + .addStep({ + factory: L2ERC20ExtendedTokensBridge__factory, + args: [ + optAddresses.L2CrossDomainMessenger, + l1Params.l1TokenBridge, + l1Params.l1TokenNonRebasable, + l1Params.l1TokenRebasable, + l2Params.l2TokenNonRebasable.address, + expectedL2TokenRebasableProxyAddress, + options?.overrides, + ], + afterDeploy: (c) => + assert.equal(c.address, expectedL2TokenBridgeImplAddress), + }); + + return [l1UpgradeScript as L1UpgradeScript, l2UpgradeScript as L2UpgradeScript]; + }, + }; +} diff --git a/utils/testing/env.ts b/utils/testing/env.ts index 218ca7df..bebddeb4 100644 --- a/utils/testing/env.ts +++ b/utils/testing/env.ts @@ -4,30 +4,35 @@ export default { USE_DEPLOYED_CONTRACTS(defaultValue: boolean = false) { return env.bool("TESTING_USE_DEPLOYED_CONTRACTS", defaultValue); }, - OPT_L1_TOKEN() { - return env.address("TESTING_OPT_L1_TOKEN"); + OPT_L1_NON_REBASABLE_TOKEN() { + return env.address("TESTING_OPT_L1_NON_REBASABLE_TOKEN"); }, - OPT_L2_TOKEN() { - return env.address("TESTING_OPT_L2_TOKEN"); + OPT_L1_REBASABLE_TOKEN() { + return env.address("TESTING_OPT_L1_REBASABLE_TOKEN"); }, - OPT_L1_TOKEN_RATE_NOTIFIER() { - return env.address("TESTING_OPT_L1_TOKEN_RATE_NOTIFIER"); + OPT_L1_ACCOUNTING_ORACLE() { + return env.address("TESTING_OPT_L1_ACCOUNTING_ORACLE"); }, + OPT_L1_ERC20_TOKEN_BRIDGE() { + return env.address("TESTING_OPT_L1_ERC20_TOKEN_BRIDGE"); + }, + OPT_L2_TOKEN_RATE_ORACLE() { return env.address("TESTING_OPT_L2_TOKEN_RATE_ORACLE"); }, - OPT_L1_REBASABLE_TOKEN() { - return env.address("TESTING_OPT_L1_REBASABLE_TOKEN"); + OPT_L2_NON_REBASABLE_TOKEN() { + return env.address("TESTING_OPT_L2_NON_REBASABLE_TOKEN"); }, OPT_L2_REBASABLE_TOKEN() { return env.address("TESTING_OPT_L2_REBASABLE_TOKEN"); }, - OPT_L1_ERC20_TOKEN_BRIDGE() { - return env.address("TESTING_OPT_L1_ERC20_TOKEN_BRIDGE"); - }, OPT_L2_ERC20_TOKEN_BRIDGE() { return env.address("TESTING_OPT_L2_ERC20_TOKEN_BRIDGE"); }, + + OPT_L1_TOKEN_RATE_NOTIFIER() { + return env.address("TESTING_OPT_L1_TOKEN_RATE_NOTIFIER"); + }, OPT_GOV_BRIDGE_EXECUTOR() { return env.address("TESTING_OPT_GOV_BRIDGE_EXECUTOR"); }, From 12333ef11a9d374b62a49ecde0af3db3488a3162 Mon Sep 17 00:00:00 2001 From: Anton Kovalchuk Date: Fri, 14 Jun 2024 17:14:04 +0200 Subject: [PATCH 148/148] add permission to handle toekn rate report by token rate notifier --- contracts/lido/TokenRateNotifier.sol | 15 +- scripts/optimism/deploy-bridge.ts | 83 ----- scripts/optimism/deploy-scratch.ts | 5 +- test/optimism/TokenRateNotifier.unit.test.ts | 60 ++- .../pushingTokenRate.integration.test.ts | 16 +- utils/deployment.ts | 2 + utils/optimism/deployment.ts | 2 + utils/optimism/deploymentAllFromScratch.ts | 346 ------------------ utils/optimism/deploymentOracle.ts | 5 +- utils/optimism/testing.ts | 1 + utils/optimism/upgrade.ts | 2 + 11 files changed, 82 insertions(+), 455 deletions(-) delete mode 100644 scripts/optimism/deploy-bridge.ts delete mode 100644 utils/optimism/deploymentAllFromScratch.ts diff --git a/contracts/lido/TokenRateNotifier.sol b/contracts/lido/TokenRateNotifier.sol index 587bb45d..96f1c718 100644 --- a/contracts/lido/TokenRateNotifier.sol +++ b/contracts/lido/TokenRateNotifier.sol @@ -27,6 +27,9 @@ interface IPostTokenRebaseReceiver { contract TokenRateNotifier is Ownable, IPostTokenRebaseReceiver { using ERC165Checker for address; + /// @notice Address of the contract that is allowed to call handlePostTokenRebase. + address public immutable AUTHORIZED_REBASE_CALLER; + /// @notice Maximum amount of observers to be supported. uint256 public constant MAX_OBSERVERS_COUNT = 32; @@ -40,11 +43,16 @@ contract TokenRateNotifier is Ownable, IPostTokenRebaseReceiver { address[] public observers; /// @param initialOwner_ initial owner - constructor(address initialOwner_) { + /// @param authorizedRebaseCaller_ Address of the contract that is allowed to call handlePostTokenRebase. + constructor(address initialOwner_, address authorizedRebaseCaller_) { if (initialOwner_ == address(0)) { revert ErrorZeroAddressOwner(); } + if (authorizedRebaseCaller_ == address(0)) { + revert ErrorZeroAddressCaller(); + } _transferOwnership(initialOwner_); + AUTHORIZED_REBASE_CALLER = authorizedRebaseCaller_; } /// @notice Add a `observer_` to the back of array @@ -94,6 +102,9 @@ contract TokenRateNotifier is Ownable, IPostTokenRebaseReceiver { uint256, /* postTotalEther */ uint256 /* sharesMintedAsFees */ ) external { + if (msg.sender != AUTHORIZED_REBASE_CALLER) { + revert ErrorNotAuthorizedRebaseCaller(); + } uint256 cachedObserversLength = observers.length; for (uint256 obIndex = 0; obIndex < cachedObserversLength; obIndex++) { // solhint-disable-next-line no-empty-blocks @@ -141,5 +152,7 @@ contract TokenRateNotifier is Ownable, IPostTokenRebaseReceiver { error ErrorMaxObserversCountExceeded(); error ErrorNoObserverToRemove(); error ErrorZeroAddressOwner(); + error ErrorZeroAddressCaller(); + error ErrorNotAuthorizedRebaseCaller(); error ErrorAddExistedObserver(); } diff --git a/scripts/optimism/deploy-bridge.ts b/scripts/optimism/deploy-bridge.ts deleted file mode 100644 index 57538f49..00000000 --- a/scripts/optimism/deploy-bridge.ts +++ /dev/null @@ -1,83 +0,0 @@ -import env from "../../utils/env"; -import prompt from "../../utils/prompt"; -import network from "../../utils/network"; -import optimism from "../../utils/optimism"; -import deployment from "../../utils/deployment"; -import { BridgingManagement } from "../../utils/bridging-management"; - -async function main() { - const networkName = env.network(); - const ethOptNetwork = network.multichain(["eth", "opt"], networkName); - - const [ethDeployer] = ethOptNetwork.getSigners(env.privateKey(), { - forking: env.forking(), - }); - const [, optDeployer] = ethOptNetwork.getSigners( - env.string("OPT_DEPLOYER_PRIVATE_KEY"), - { - forking: env.forking(), - } - ); - - const deploymentConfig = deployment.loadMultiChainDeploymentConfig(); - - const [l1DeployScript, l2DeployScript] = await optimism - .deployment(networkName, { logger: console }) - .erc20TokenBridgeDeployScript( - deploymentConfig.l1Token, - deploymentConfig.l1RebasableToken, - deploymentConfig.l2TokenRateOracle, - { - deployer: ethDeployer, - admins: { - proxy: deploymentConfig.l1.proxyAdmin, - bridge: ethDeployer.address - }, - contractsShift: 0 - }, - { - deployer: optDeployer, - admins: { - proxy: deploymentConfig.l2.proxyAdmin, - bridge: optDeployer.address, - }, - contractsShift: 0 - } - ); - - await deployment.printMultiChainDeploymentConfig( - "Deploy Optimism Bridge", - ethDeployer, - optDeployer, - deploymentConfig, - l1DeployScript, - l2DeployScript - ); - - await prompt.proceed(); - - await l1DeployScript.run(); - await l2DeployScript.run(); - - const l1ERC20ExtendedTokensBridgeProxyDeployStepIndex = 1; - const l1BridgingManagement = new BridgingManagement( - l1DeployScript.getContractAddress(l1ERC20ExtendedTokensBridgeProxyDeployStepIndex), - ethDeployer, - { logger: console } - ); - - const l2ERC20ExtendedTokensBridgeProxyDeployStepIndex = 5; - const l2BridgingManagement = new BridgingManagement( - l2DeployScript.getContractAddress(l2ERC20ExtendedTokensBridgeProxyDeployStepIndex), - optDeployer, - { logger: console } - ); - - await l1BridgingManagement.setup(deploymentConfig.l1); - await l2BridgingManagement.setup(deploymentConfig.l2); -} - -main().catch((error) => { - console.error(error); - process.exitCode = 1; -}); diff --git a/scripts/optimism/deploy-scratch.ts b/scripts/optimism/deploy-scratch.ts index 165155ec..3583379f 100644 --- a/scripts/optimism/deploy-scratch.ts +++ b/scripts/optimism/deploy-scratch.ts @@ -3,7 +3,7 @@ import prompt from "../../utils/prompt"; import network from "../../utils/network"; import deployment from "../../utils/deployment"; import { BridgingManagement } from "../../utils/bridging-management"; -import deploymentAllFromScratch from "../../utils/optimism/deployment"; +import deploymentAll from "../../utils/optimism/deployment"; async function main() { const networkName = env.network(); @@ -21,13 +21,14 @@ async function main() { const deploymentConfig = deployment.loadMultiChainDeploymentConfig(); - const [l1DeployScript, l2DeployScript] = await deploymentAllFromScratch (networkName, { logger: console }) + const [l1DeployScript, l2DeployScript] = await deploymentAll (networkName, { logger: console }) .deployAllScript( { l1TokenNonRebasable: deploymentConfig.l1TokenNonRebasable, l1TokenRebasable: deploymentConfig.l1RebasableToken, accountingOracle: deploymentConfig.accountingOracle, l2GasLimitForPushingTokenRate: deploymentConfig.l2GasLimitForPushingTokenRate, + l1AuthorizedRebaseCaller: deploymentConfig.l1AuthorizedRebaseCaller, deployer: ethDeployer, admins: { diff --git a/test/optimism/TokenRateNotifier.unit.test.ts b/test/optimism/TokenRateNotifier.unit.test.ts index cd19b9aa..e0d7b38b 100644 --- a/test/optimism/TokenRateNotifier.unit.test.ts +++ b/test/optimism/TokenRateNotifier.unit.test.ts @@ -21,14 +21,23 @@ import { unit("TokenRateNotifier", ctxFactory) .test("deploy with zero address owner", async (ctx) => { - const { deployer } = ctx.accounts; + const { deployer, l1AuthorizedRebaseCaller } = ctx.accounts; await assert.revertsWith( - new TokenRateNotifier__factory(deployer).deploy(ethers.constants.AddressZero), + new TokenRateNotifier__factory(deployer).deploy(ethers.constants.AddressZero, l1AuthorizedRebaseCaller.address), "ErrorZeroAddressOwner()" ); }) + .test("deploy with zero address rebase caller", async (ctx) => { + const { deployer } = ctx.accounts; + + await assert.revertsWith( + new TokenRateNotifier__factory(deployer).deploy(deployer.address, ethers.constants.AddressZero), + "ErrorZeroAddressCaller()" + ); + }) + .test("initial state", async (ctx) => { const { tokenRateNotifier } = ctx.contracts; @@ -63,9 +72,9 @@ unit("TokenRateNotifier", ctxFactory) .test("addObserver() :: revert on adding observer with bad interface", async (ctx) => { const { tokenRateNotifier } = ctx.contracts; - const { deployer } = ctx.accounts; + const { deployer, l1AuthorizedRebaseCaller } = ctx.accounts; - const observer = await new TokenRateNotifier__factory(deployer).deploy(deployer.address); + const observer = await new TokenRateNotifier__factory(deployer).deploy(deployer.address, l1AuthorizedRebaseCaller.address); await assert.revertsWith( tokenRateNotifier .connect(ctx.accounts.owner) @@ -76,7 +85,7 @@ unit("TokenRateNotifier", ctxFactory) .test("addObserver() :: revert on adding too many observers", async (ctx) => { const { tokenRateNotifier, opStackTokenRatePusher } = ctx.contracts; - const { deployer, owner, tokenRateOracle } = ctx.accounts; + const { deployer, owner, tokenRateOracle, l1AuthorizedRebaseCaller } = ctx.accounts; const { l2GasLimitForPushingTokenRate, tokenRate, totalPooledEther, totalShares, genesisTime, secondsPerSlot, lastProcessingRefSlot } = ctx.constants; assert.equalBN(await tokenRateNotifier.observersLength(), 0); @@ -95,7 +104,8 @@ unit("TokenRateNotifier", ctxFactory) deployer, owner, tokenRateOracle, - l2GasLimitForPushingTokenRate + l2GasLimitForPushingTokenRate, + l1AuthorizedRebaseCaller ); await tokenRateNotifier @@ -183,23 +193,33 @@ unit("TokenRateNotifier", ctxFactory) assert.equalBN(await tokenRateNotifier.observersLength(), 0); }) + .test("handlePostTokenRebase() :: unauthorized caller", async (ctx) => { + const { tokenRateNotifier } = ctx.contracts; + const { stranger } = ctx.accounts; + + await assert.revertsWith( + tokenRateNotifier.connect(stranger).handlePostTokenRebase(1, 2, 3, 4, 5, 6, 7), + "ErrorNotAuthorizedRebaseCaller()" + ); + }) + .test("handlePostTokenRebase() :: failed with some error", async (ctx) => { const { tokenRateNotifier } = ctx.contracts; - const { deployer } = ctx.accounts; + const { deployer, l1AuthorizedRebaseCaller } = ctx.accounts; const observer = await new OpStackTokenRatePusherWithSomeErrorStub__factory(deployer).deploy(); await tokenRateNotifier .connect(ctx.accounts.owner) .addObserver(observer.address); - const tx = await tokenRateNotifier.handlePostTokenRebase(1, 2, 3, 4, 5, 6, 7); + const tx = await tokenRateNotifier.connect(l1AuthorizedRebaseCaller).handlePostTokenRebase(1, 2, 3, 4, 5, 6, 7); await assert.emits(tokenRateNotifier, tx, "PushTokenRateFailed", [observer.address, "0x332e27d2"]); }) .test("handlePostTokenRebase() :: revert when observer has out of gas error", async (ctx) => { const { tokenRateNotifier } = ctx.contracts; - const { deployer } = ctx.accounts; + const { deployer, l1AuthorizedRebaseCaller } = ctx.accounts; const observer = await new OpStackTokenRatePusherWithOutOfGasErrorStub__factory(deployer).deploy(); await tokenRateNotifier @@ -207,7 +227,7 @@ unit("TokenRateNotifier", ctxFactory) .addObserver(observer.address); await assert.revertsWith( - tokenRateNotifier.handlePostTokenRebase(1, 2, 3, 4, 5, 6, 7), + tokenRateNotifier.connect(l1AuthorizedRebaseCaller).handlePostTokenRebase(1, 2, 3, 4, 5, 6, 7), "ErrorTokenRateNotifierRevertedWithNoData()" ); }) @@ -218,7 +238,7 @@ unit("TokenRateNotifier", ctxFactory) l1MessengerStub, opStackTokenRatePusher } = ctx.contracts; - const { tokenRateOracle } = ctx.accounts; + const { tokenRateOracle, l1AuthorizedRebaseCaller } = ctx.accounts; const { l2GasLimitForPushingTokenRate, tokenRate, genesisTime, secondsPerSlot, lastProcessingRefSlot } = ctx.constants; const updateRateTime = genesisTime.add(secondsPerSlot.mul(lastProcessingRefSlot)); @@ -226,7 +246,7 @@ unit("TokenRateNotifier", ctxFactory) await tokenRateNotifier .connect(ctx.accounts.owner) .addObserver(opStackTokenRatePusher.address); - let tx = await tokenRateNotifier.handlePostTokenRebase(1, 2, 3, 4, 5, 6, 7); + let tx = await tokenRateNotifier.connect(l1AuthorizedRebaseCaller).handlePostTokenRebase(1, 2, 3, 4, 5, 6, 7); await assert.emits(l1MessengerStub, tx, "SentMessage", [ tokenRateOracle.address, @@ -246,7 +266,7 @@ unit("TokenRateNotifier", ctxFactory) .run(); async function ctxFactory() { - const [deployer, owner, stranger, tokenRateOracle] = await ethers.getSigners(); + const [deployer, owner, stranger, tokenRateOracle, l1AuthorizedRebaseCaller] = await ethers.getSigners(); const totalPooledEther = BigNumber.from('9309904612343950493629678'); const totalShares = BigNumber.from('7975822843597609202337218'); const tokenRateDecimals = BigNumber.from(27); @@ -270,7 +290,8 @@ async function ctxFactory() { deployer, owner, tokenRateOracle, - l2GasLimitForPushingTokenRate + l2GasLimitForPushingTokenRate, + l1AuthorizedRebaseCaller ); return { @@ -278,7 +299,8 @@ async function ctxFactory() { deployer, owner, stranger, - tokenRateOracle + tokenRateOracle, + l1AuthorizedRebaseCaller }, contracts: { tokenRateNotifier, @@ -307,9 +329,13 @@ async function createContracts( deployer: SignerWithAddress, owner: SignerWithAddress, tokenRateOracle: SignerWithAddress, - l2GasLimitForPushingTokenRate: number) { + l2GasLimitForPushingTokenRate: number, + l1AuthorizedRebaseCaller: SignerWithAddress) { - const tokenRateNotifier = await new TokenRateNotifier__factory(deployer).deploy(owner.address); + const tokenRateNotifier = await new TokenRateNotifier__factory(deployer).deploy( + owner.address, + l1AuthorizedRebaseCaller.address + ); const l1MessengerStub = await new CrossDomainMessengerStub__factory(deployer) .deploy({ value: wei.toBigNumber(wei`1 ether`) }); diff --git a/test/optimism/pushingTokenRate.integration.test.ts b/test/optimism/pushingTokenRate.integration.test.ts index cab7d3da..cf6fc902 100644 --- a/test/optimism/pushingTokenRate.integration.test.ts +++ b/test/optimism/pushingTokenRate.integration.test.ts @@ -16,7 +16,8 @@ import { OptimismBridgeExecutor__factory, TokenRateNotifier__factory, TokenRateOracle__factory, - AccountingOracleStub__factory + AccountingOracleStub__factory, + EmptyContractStub__factory } from "../../typechain"; scenario("Optimism :: Token Rate Oracle integration test", ctxFactory) @@ -30,13 +31,12 @@ scenario("Optimism :: Token Rate Oracle integration test", ctxFactory) genesisTime, secondsPerSlot, lastProcessingRefSlot, - tokenRate + tokenRate, + l1AuthorizedRebaseCaller } = ctx; - const account = ctx.accounts.accountA; - const tx = await tokenRateNotifier - .connect(account.l1Signer) + .connect(l1AuthorizedRebaseCaller) .handlePostTokenRebase(1, 2, 3, 4, 5, 6, 7); const messageNonce = await l1CrossDomainMessenger.messageNonce(); @@ -169,6 +169,10 @@ async function ctxFactory() { const [l2ERC20TokenBridge] = await hre.ethers.getSigners(); + const l1AuthorizedRebaseCaller = await new EmptyContractStub__factory(l1Deployer).deploy({ value: 10000000 }); + const l1AuthorizedRebaseCallerAsEOA = await testing.impersonate(l1AuthorizedRebaseCaller.address); + await testing.setBalance(l1AuthorizedRebaseCaller.address, wei.toBigNumber(wei`1 ether`)); + const [ethDeployScript, optDeployScript] = await deploymentOracle( networkName ).oracleDeployScript( @@ -178,6 +182,7 @@ async function ctxFactory() { l2GasLimitForPushingTokenRate, tokenRateOutdatedDelay, { + l1AuthorizedRebaseCaller: l1AuthorizedRebaseCaller.address, deployer: l1Deployer, admins: { proxy: l1Deployer.address, @@ -245,6 +250,7 @@ async function ctxFactory() { blockTimestamp, tokenRate, genesisTime, secondsPerSlot, lastProcessingRefSlot, + l1AuthorizedRebaseCaller: l1AuthorizedRebaseCallerAsEOA, accounts: { accountA, l1CrossDomainMessengerAliased diff --git a/utils/deployment.ts b/utils/deployment.ts index d9fc7406..58021774 100644 --- a/utils/deployment.ts +++ b/utils/deployment.ts @@ -16,6 +16,7 @@ interface MultiChainDeploymentConfig { accountingOracle: string; l2GasLimitForPushingTokenRate: BigNumber; l1TokenBridge: string; + l1AuthorizedRebaseCaller: string; /// L2 /// Oracle @@ -46,6 +47,7 @@ export function loadMultiChainDeploymentConfig(): MultiChainDeploymentConfig { accountingOracle: env.address("ACCOUNTING_ORACLE"), l2GasLimitForPushingTokenRate: BigNumber.from(env.string("L2_GAS_LIMIT_FOR_PUSHING_TOKEN_RATE")), l1TokenBridge: env.address("L1_TOKEN_BRIDGE"), + l1AuthorizedRebaseCaller: env.address("L1_AUTHORIZED_REBASE_CALLER"), /// L2 Part /// TokenRateOracle diff --git a/utils/optimism/deployment.ts b/utils/optimism/deployment.ts index 3464ed08..c652cdda 100644 --- a/utils/optimism/deployment.ts +++ b/utils/optimism/deployment.ts @@ -21,6 +21,7 @@ interface OptL1DeployScriptParams extends DeployScriptParams { l1TokenRebasable: string; accountingOracle: string; l2GasLimitForPushingTokenRate: BigNumber; + l1AuthorizedRebaseCaller: string; } interface OptL2DeployScriptParams extends DeployScriptParams { @@ -185,6 +186,7 @@ export default function deploymentAll( factory: TokenRateNotifier__factory, args: [ l1Params.deployer.address, + l1Params.l1AuthorizedRebaseCaller, options?.overrides, ], afterDeploy: (c) => diff --git a/utils/optimism/deploymentAllFromScratch.ts b/utils/optimism/deploymentAllFromScratch.ts deleted file mode 100644 index 39457f3e..00000000 --- a/utils/optimism/deploymentAllFromScratch.ts +++ /dev/null @@ -1,346 +0,0 @@ -import { assert } from "chai"; -import { BigNumber, Wallet } from "ethers"; -import addresses from "./addresses"; -import { OptDeploymentOptions, DeployScriptParams } from "./types"; -import network, { NetworkName } from "../network"; -import { DeployScript, Logger } from "../deployment/DeployScript"; -import { - ERC20BridgedPermit__factory, - ERC20RebasableBridgedPermit__factory, - IERC20Metadata__factory, - L1LidoTokensBridge__factory, - L2ERC20ExtendedTokensBridge__factory, - OssifiableProxy__factory, - TokenRateOracle__factory, - TokenRateNotifier__factory, - OpStackTokenRatePusher__factory -} from "../../typechain"; - -interface OptL1DeployScriptParams extends DeployScriptParams { - l1Token: string; - l1TokenRebasable: string; - accountingOracle: string; - l2GasLimitForPushingTokenRate: number; -} -interface OptL2DeployScriptParams extends DeployScriptParams { - l2TokenNonRebasable: { - name: string; - symbol: string; - version: string; - decimals: number; - }; - l2TokenRebasable: { - name: string; - symbol: string; - version: string; - decimals: number; - }; - tokenRateOracle: { - tokenRateOutdatedDelay: BigNumber; - maxAllowedL2ToL1ClockLag: BigNumber; - maxAllowedTokenRateDeviationPerDayBp: BigNumber; - oldestRateAllowedInPauseTimeSpan: BigNumber; - maxAllowedTimeBetweenTokenRateUpdates: BigNumber; - tokenRate: BigNumber; - l1Timestamp: BigNumber; - } -} - -export class L1DeployAllScript extends DeployScript { - - constructor( - deployer: Wallet, - bridgeImplAddress: string, - bridgeProxyAddress: string, - tokenRateNotifierImplAddress: string, - opStackTokenRatePusherImplAddress: string, - logger?: Logger - ) { - super(deployer, logger); - this.bridgeImplAddress = bridgeImplAddress; - this.bridgeProxyAddress = bridgeProxyAddress; - this.tokenRateNotifierImplAddress = tokenRateNotifierImplAddress; - this.opStackTokenRatePusherImplAddress = opStackTokenRatePusherImplAddress; - } - - public bridgeImplAddress: string; - public bridgeProxyAddress: string; - public tokenRateNotifierImplAddress: string; - public opStackTokenRatePusherImplAddress: string; -} - -export class L2DeployAllScript extends DeployScript { - - constructor( - deployer: Wallet, - tokenImplAddress: string, - tokenProxyAddress: string, - tokenRebasableImplAddress: string, - tokenRebasableProxyAddress: string, - tokenBridgeImplAddress: string, - tokenBridgeProxyAddress: string, - tokenRateOracleImplAddress: string, - tokenRateOracleProxyAddress: string, - logger?: Logger - ) { - super(deployer, logger); - this.tokenImplAddress = tokenImplAddress; - this.tokenProxyAddress = tokenProxyAddress; - this.tokenRebasableImplAddress = tokenRebasableImplAddress; - this.tokenRebasableProxyAddress = tokenRebasableProxyAddress; - this.tokenBridgeImplAddress = tokenBridgeImplAddress; - this.tokenBridgeProxyAddress = tokenBridgeProxyAddress; - this.tokenRateOracleImplAddress = tokenRateOracleImplAddress; - this.tokenRateOracleProxyAddress = tokenRateOracleProxyAddress; - } - - public tokenImplAddress: string; - public tokenProxyAddress: string; - public tokenRebasableImplAddress: string; - public tokenRebasableProxyAddress: string; - public tokenBridgeImplAddress: string; - public tokenBridgeProxyAddress: string; - public tokenRateOracleImplAddress: string; - public tokenRateOracleProxyAddress: string; -} - -/// Deploy all from scratch -/// L1 part -/// L1LidoTokensBridge + Proxy -/// TokenRateNotifier -/// OpStackTokenRatePusher -/// L2 part -/// TokenRateOracle + Proxy -/// ERC20BridgedPermit + Proxy -/// ERC20RebasableBridgedPermit + Proxy -/// L2ERC20ExtendedTokensBridge + Proxy -export default function deploymentAll( - networkName: NetworkName, - options: OptDeploymentOptions = {} -) { - const optAddresses = addresses(networkName, options); - return { - async deployAllScript( - l1Params: OptL1DeployScriptParams, - l2Params: OptL2DeployScriptParams, - ): Promise<[L1DeployAllScript, L2DeployAllScript]> { - - const [ - expectedL1TokenBridgeImplAddress, - expectedL1TokenBridgeProxyAddress, - expectedL1TokenRateNotifierImplAddress, - expectedL1OpStackTokenRatePusherImplAddress, - ] = await network.predictAddresses(l1Params.deployer, l1Params.contractsShift + 4); - - const [ - expectedL2TokenRateOracleImplAddress, - expectedL2TokenRateOracleProxyAddress, - expectedL2TokenImplAddress, - expectedL2TokenProxyAddress, - expectedL2TokenRebasableImplAddress, - expectedL2TokenRebasableProxyAddress, - expectedL2TokenBridgeImplAddress, - expectedL2TokenBridgeProxyAddress - ] = await network.predictAddresses(l2Params.deployer, l2Params.contractsShift + 8); - - const l1DeployScript = new L1DeployAllScript( - l1Params.deployer, - expectedL1TokenBridgeImplAddress, - expectedL1TokenBridgeProxyAddress, - expectedL1TokenRateNotifierImplAddress, - expectedL1OpStackTokenRatePusherImplAddress, - options?.logger - ) - .addStep({ - factory: L1LidoTokensBridge__factory, - args: [ - optAddresses.L1CrossDomainMessenger, - expectedL2TokenBridgeProxyAddress, - l1Params.l1Token, - l1Params.l1TokenRebasable, - expectedL2TokenProxyAddress, - expectedL2TokenRebasableProxyAddress, - l1Params.accountingOracle, - options?.overrides, - ], - afterDeploy: (c) => - assert.equal(c.address, expectedL1TokenBridgeImplAddress), - }) - .addStep({ - factory: OssifiableProxy__factory, - args: [ - expectedL1TokenBridgeImplAddress, - l1Params.admins.proxy, - L1LidoTokensBridge__factory.createInterface().encodeFunctionData( - "initialize", - [l1Params.admins.bridge] - ), - options?.overrides, - ], - afterDeploy: (c) => - assert.equal(c.address, expectedL1TokenBridgeProxyAddress), - }) - .addStep({ - factory: TokenRateNotifier__factory, - args: [ - l1Params.deployer.address, - options?.overrides, - ], - afterDeploy: (c) => - assert.equal(c.address, expectedL1TokenRateNotifierImplAddress), - }) - .addStep({ - factory: OpStackTokenRatePusher__factory, - args: [ - optAddresses.L1CrossDomainMessenger, - l1Params.l1Token, - l1Params.accountingOracle, - expectedL2TokenRateOracleProxyAddress, - l1Params.l2GasLimitForPushingTokenRate, - options?.overrides, - ], - afterDeploy: (c) => - assert.equal(c.address, expectedL1OpStackTokenRatePusherImplAddress), - }); - - const l2DeployScript = new L2DeployAllScript( - l2Params.deployer, - expectedL2TokenImplAddress, - expectedL2TokenProxyAddress, - expectedL2TokenRebasableImplAddress, - expectedL2TokenRebasableProxyAddress, - expectedL2TokenBridgeImplAddress, - expectedL2TokenBridgeProxyAddress, - expectedL2TokenRateOracleImplAddress, - expectedL2TokenRateOracleProxyAddress, - options?.logger - ) - .addStep({ - factory: TokenRateOracle__factory, - args: [ - optAddresses.L2CrossDomainMessenger, - expectedL2TokenBridgeProxyAddress, - expectedL1OpStackTokenRatePusherImplAddress, - l2Params.tokenRateOracle.tokenRateOutdatedDelay, - l2Params.tokenRateOracle.maxAllowedL2ToL1ClockLag, - l2Params.tokenRateOracle.maxAllowedTokenRateDeviationPerDayBp, - l2Params.tokenRateOracle.oldestRateAllowedInPauseTimeSpan, - l2Params.tokenRateOracle.maxAllowedTimeBetweenTokenRateUpdates, - options?.overrides, - ], - afterDeploy: (c) => - assert.equal(c.address, expectedL2TokenRateOracleImplAddress), - }) - .addStep({ - factory: OssifiableProxy__factory, - args: [ - expectedL2TokenRateOracleImplAddress, - l2Params.admins.proxy, - TokenRateOracle__factory.createInterface().encodeFunctionData( - "initialize", - [ - l2Params.admins.bridge, - l2Params.tokenRateOracle.tokenRate, - l2Params.tokenRateOracle.l1Timestamp - ] - ), - options?.overrides, - ], - afterDeploy: (c) => - assert.equal(c.address, expectedL2TokenRateOracleProxyAddress), - }) - .addStep({ - factory: ERC20BridgedPermit__factory, - args: [ - l2Params.l2TokenNonRebasable.name, - l2Params.l2TokenNonRebasable.symbol, - l2Params.l2TokenNonRebasable.version, - l2Params.l2TokenNonRebasable.decimals, - expectedL2TokenBridgeProxyAddress, - options?.overrides, - ], - afterDeploy: (c) => - assert.equal(c.address, expectedL2TokenImplAddress), - }) - .addStep({ - factory: OssifiableProxy__factory, - args: [ - expectedL2TokenImplAddress, - l2Params.admins.proxy, - ERC20BridgedPermit__factory.createInterface().encodeFunctionData( - "initialize", - [ - l2Params.l2TokenNonRebasable.name, - l2Params.l2TokenNonRebasable.symbol, - l2Params.l2TokenNonRebasable.version - ] - ), - options?.overrides, - ], - afterDeploy: (c) => - assert.equal(c.address, expectedL2TokenProxyAddress), - }) - .addStep({ - factory: ERC20RebasableBridgedPermit__factory, - args: [ - l2Params.l2TokenRebasable.name, - l2Params.l2TokenRebasable.symbol, - l2Params.l2TokenRebasable.version, - l2Params.l2TokenRebasable.decimals, - expectedL2TokenProxyAddress, - expectedL2TokenRateOracleProxyAddress, - expectedL2TokenBridgeProxyAddress, - options?.overrides, - ], - afterDeploy: (c) => - assert.equal(c.address, expectedL2TokenRebasableImplAddress), - }) - .addStep({ - factory: OssifiableProxy__factory, - args: [ - expectedL2TokenRebasableImplAddress, - l2Params.admins.proxy, - ERC20RebasableBridgedPermit__factory.createInterface().encodeFunctionData( - "initialize", - [ - l2Params.l2TokenRebasable.name, - l2Params.l2TokenRebasable.symbol, - l2Params.l2TokenRebasable.version - ] - ), - options?.overrides, - ], - afterDeploy: (c) => - assert.equal(c.address, expectedL2TokenRebasableProxyAddress), - }) - .addStep({ - factory: L2ERC20ExtendedTokensBridge__factory, - args: [ - optAddresses.L2CrossDomainMessenger, - expectedL1TokenBridgeProxyAddress, - l1Params.l1Token, - l1Params.l1TokenRebasable, - expectedL2TokenProxyAddress, - expectedL2TokenRebasableProxyAddress, - options?.overrides, - ], - afterDeploy: (c) => - assert.equal(c.address, expectedL2TokenBridgeImplAddress), - }) - .addStep({ - factory: OssifiableProxy__factory, - args: [ - expectedL2TokenBridgeImplAddress, - l2Params.admins.proxy, - L2ERC20ExtendedTokensBridge__factory.createInterface().encodeFunctionData( - "initialize", - [l2Params.admins.bridge] - ), - options?.overrides, - ], - }); - - return [l1DeployScript as L1DeployAllScript, l2DeployScript as L2DeployAllScript]; - }, - }; -} diff --git a/utils/optimism/deploymentOracle.ts b/utils/optimism/deploymentOracle.ts index d715ddfe..ad2fa31d 100644 --- a/utils/optimism/deploymentOracle.ts +++ b/utils/optimism/deploymentOracle.ts @@ -12,7 +12,9 @@ import { OpStackTokenRatePusher__factory } from "../../typechain"; -interface OptDeployScriptParams extends DeployScriptParams { } +interface OptDeployScriptParams extends DeployScriptParams { + l1AuthorizedRebaseCaller: string; +} interface OptL2DeployScriptParams extends DeployScriptParams { tokenRateOracle: { @@ -99,6 +101,7 @@ export default function deploymentOracle( factory: TokenRateNotifier__factory, args: [ l1Params.deployer.address, + l1Params.l1AuthorizedRebaseCaller, options?.overrides, ], afterDeploy: (c) => diff --git a/utils/optimism/testing.ts b/utils/optimism/testing.ts index f27e1f8b..6f38f783 100644 --- a/utils/optimism/testing.ts +++ b/utils/optimism/testing.ts @@ -236,6 +236,7 @@ async function deployTestBridge( l1TokenRebasable: l1TokenRebasable.address, accountingOracle: accountingOracle.address, l2GasLimitForPushingTokenRate: BigNumber.from(300_000), + l1AuthorizedRebaseCaller: ethDeployer.address, deployer: ethDeployer, admins: { proxy: ethDeployer.address, bridge: ethDeployer.address }, contractsShift: 0 diff --git a/utils/optimism/upgrade.ts b/utils/optimism/upgrade.ts index a01cb864..ceea1f87 100644 --- a/utils/optimism/upgrade.ts +++ b/utils/optimism/upgrade.ts @@ -22,6 +22,7 @@ interface OptL1UpgradeScriptParams extends DeployScriptParams { accountingOracle: string; l2GasLimitForPushingTokenRate: BigNumber; l1TokenBridge: string; + l1AuthorizedRebaseCaller: string; } interface OptL2UpgradeScriptParams extends DeployScriptParams { @@ -177,6 +178,7 @@ export default function upgrade( factory: TokenRateNotifier__factory, args: [ l1Params.deployer.address, + l1Params.l1AuthorizedRebaseCaller, options?.overrides, ], afterDeploy: (c) =>