Skip to content

Commit

Permalink
PR fixes: rename tokens, add sanity check to oracle update, add unit …
Browse files Browse the repository at this point in the history
…tests, fix comments
  • Loading branch information
kovalgek committed Apr 16, 2024
1 parent b62fbee commit f387de6
Show file tree
Hide file tree
Showing 17 changed files with 519 additions and 142 deletions.
12 changes: 8 additions & 4 deletions contracts/optimism/L1ERC20ExtendedTokensBridge.sol
Original file line number Diff line number Diff line change
Expand Up @@ -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);
Expand All @@ -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_,
Expand All @@ -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_,
Expand Down
24 changes: 18 additions & 6 deletions contracts/optimism/L2ERC20ExtendedTokensBridge.sol
Original file line number Diff line number Diff line change
Expand Up @@ -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";
Expand Down Expand Up @@ -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_);
Expand All @@ -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
Expand All @@ -140,29 +141,40 @@ 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_,
address to_,
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;
}

Expand Down
19 changes: 17 additions & 2 deletions contracts/optimism/TokenRateOracle.sol
Original file line number Diff line number Diff line change
Expand Up @@ -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;

Expand Down Expand Up @@ -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) {
Expand All @@ -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_);
}
Original file line number Diff line number Diff line change
Expand Up @@ -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_.
Expand All @@ -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.
Expand Down
7 changes: 2 additions & 5 deletions contracts/token/ERC20BridgedPermit.sol
Original file line number Diff line number Diff line change
Expand Up @@ -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_);
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -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";
Expand All @@ -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;

Expand All @@ -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_;
}
Expand All @@ -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_);
}
Expand All @@ -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;
}
Expand Down Expand Up @@ -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));
}
Expand Down Expand Up @@ -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();
}
Original file line number Diff line number Diff line change
Expand Up @@ -3,38 +3,35 @@

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(
string memory name_,
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_);
}
}
2 changes: 1 addition & 1 deletion contracts/token/PermitExtension.sol
Original file line number Diff line number Diff line change
Expand Up @@ -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();
Expand Down
Loading

0 comments on commit f387de6

Please sign in to comment.