Skip to content

Commit

Permalink
simplify oracle, remove warnings
Browse files Browse the repository at this point in the history
  • Loading branch information
kovalgek committed Dec 27, 2023
1 parent 86037dc commit 86788b2
Show file tree
Hide file tree
Showing 12 changed files with 102 additions and 152 deletions.
3 changes: 2 additions & 1 deletion .solhint.json
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,7 @@
"ignoreConstructors": true
}
],
"lido/fixed-compiler-version": "error"
"lido/fixed-compiler-version": "error",
"const-name-snakecase": false
}
}
12 changes: 6 additions & 6 deletions contracts/optimism/DepositDataCodec.sol
Original file line number Diff line number Diff line change
Expand Up @@ -6,8 +6,8 @@ pragma solidity 0.8.10;
contract DepositDataCodec {

struct DepositData {
uint256 rate;
uint256 time;
uint96 rate;
uint40 time;
bytes data;
}

Expand All @@ -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;
Expand Down
11 changes: 5 additions & 6 deletions contracts/optimism/L1ERC20TokenBridge.sol
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down Expand Up @@ -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
Expand Down Expand Up @@ -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_
});

Expand Down
4 changes: 1 addition & 3 deletions contracts/optimism/L2ERC20TokenBridge.sol
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down Expand Up @@ -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_)) {
Expand Down
147 changes: 35 additions & 112 deletions contracts/optimism/TokenRateOracle.sol
Original file line number Diff line number Diff line change
Expand Up @@ -4,155 +4,78 @@
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_,
uint256 startedAt_,
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
);
}

/// @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;
_;
}
}
1 change: 0 additions & 1 deletion contracts/stubs/ERC20Stub.sol
Original file line number Diff line number Diff line change
Expand Up @@ -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 {

Expand Down
2 changes: 0 additions & 2 deletions contracts/stubs/ERC20WrapableStub.sol
Original file line number Diff line number Diff line change
Expand Up @@ -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 {
Expand Down
16 changes: 11 additions & 5 deletions contracts/stubs/TokenRateOracleStub.sol
Original file line number Diff line number Diff line change
Expand Up @@ -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_;
}

Expand All @@ -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_;
Expand Down
2 changes: 0 additions & 2 deletions contracts/token/ERC20Core.sol
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down Expand Up @@ -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_);
}
Expand Down
10 changes: 5 additions & 5 deletions contracts/token/ERC20Rebasable.sol
Original file line number Diff line number Diff line change
Expand Up @@ -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();
Expand All @@ -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.
Expand Down Expand Up @@ -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_);
}
Expand Down
Loading

0 comments on commit 86788b2

Please sign in to comment.