From 639b9e6e00a1dae19ed57622cf309ef537f7b0b1 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Guillermo=20D=C3=ADaz?= Date: Wed, 20 Mar 2024 17:48:52 +0100 Subject: [PATCH] feat: WitnetPriceFeesBypassV20 --- .../impls/apps/WitnetPriceFeedsBypassV20.sol | 334 ++++++++++++++++++ contracts/interfaces/V2/IWitnetPriceFeeds.sol | 1 + contracts/libs/Witnet.sol | 24 ++ migrations/witnet.addresses.json | 3 +- migrations/witnet.settings.js | 1 + 5 files changed, 362 insertions(+), 1 deletion(-) create mode 100644 contracts/impls/apps/WitnetPriceFeedsBypassV20.sol diff --git a/contracts/impls/apps/WitnetPriceFeedsBypassV20.sol b/contracts/impls/apps/WitnetPriceFeedsBypassV20.sol new file mode 100644 index 000000000..e3884027c --- /dev/null +++ b/contracts/impls/apps/WitnetPriceFeedsBypassV20.sol @@ -0,0 +1,334 @@ +// SPDX-License-Identifier: MIT + +pragma solidity >=0.8.0 <0.9.0; + +import "../../impls/WitnetUpgradableBase.sol"; +import "../../interfaces/V2/IWitnetPriceFeeds.sol"; + +abstract contract WitnetPriceFeedsV07 { + function supportedFeeds() virtual external view returns (bytes4[] memory, string[] memory, bytes32[] memory); +} + +abstract contract WitnetPriceFeedsV20 { + function isUpgradableFrom(address) virtual external view returns (bool); + function latestUpdateResponseStatus(bytes4) virtual external view returns (WitnetV2.ResponseStatus); + function owner() virtual external view returns (address); + function requestUpdate(bytes4) virtual external payable returns (uint256); + function specs() virtual external view returns (bytes4); +} + +/// @title Witnet Price Feeds surrogate bypass implementation to V2.0 +/// @author The Witnet Foundation +contract WitnetPriceFeedsBypassV20 + is + WitnetUpgradableBase +{ + using Witnet for bytes4; + WitnetPriceFeedsV20 immutable public surrogate; + + constructor ( + WitnetPriceFeedsV20 _surrogate, + bool _upgradable, + bytes32 _versionTag + ) + WitnetUpgradableBase( + _upgradable, + _versionTag, + "io.witnet.proxiable.router" + ) + { + _require( + address(_surrogate).code.length > 0 + && _surrogate.specs() == type(IWitnetPriceFeeds).interfaceId, + "uncompliant surrogate" + ); + surrogate = _surrogate; + } + + // solhint-disable-next-line payable-fallback + fallback() virtual override external { /* solhint-disable no-complex-fallback */ + address _surrogate = address(surrogate); + assembly { /* solhint-disable avoid-low-level-calls */ + // Gas optimized surrogate call to the 'surrogate' immutable contract. + // Note: `msg.data`, `msg.sender` and `msg.value` will be passed over + // to actual implementation of `msg.sig` within `implementation` contract. + let ptr := mload(0x40) + calldatacopy(ptr, 0, calldatasize()) + let result := call(gas(), _surrogate, 0, ptr, calldatasize(), 0, 0) + let size := returndatasize() + returndatacopy(ptr, 0, size) + switch result + case 0 { + // pass back revert message: + revert(ptr, size) + } + default { + // pass back same data as returned by 'implementation' contract: + return(ptr, size) + } + } + } + + function class() public pure returns (string memory) { + return type(WitnetPriceFeedsBypassV20).name; + } + + + // ================================================================================================================ + // --- Overrides 'Upgradeable' ------------------------------------------------------------------------------------- + + function owner() public view override returns (address) { + return surrogate.owner(); + } + + + // ================================================================================================================ + // --- Overrides 'Upgradeable' ------------------------------------------------------------------------------------ + + /// @notice Re-initialize contract's storage context upon a new upgrade from a proxy. + /// @dev Must fail when trying to upgrade to same logic contract more than once. + function initialize(bytes memory) + public override + onlyDelegateCalls // => we don't want the logic base contract to be ever initialized + { + if ( + __proxiable().proxy == address(0) + && __proxiable().implementation == address(0) + ) { + // a proxy is being initialized for the first time... + __proxiable().proxy = address(this); + _transferOwnership(msg.sender); + } else { + // only the owner can initialize: + if (msg.sender != owner()) { + _revert("not the owner"); + } + } + + // Check that new implentation hasn't actually been initialized already: + _require( + __proxiable().implementation != base(), + "already initialized" + ); + + (bytes4[] memory _id4s,,) = WitnetPriceFeedsV07(address(this)).supportedFeeds(); + for (uint _ix = 0; _ix < _id4s.length; _ix ++) { + // Check that all supported price feeds under current implementation + // are actually supported under the surrogate immutable instance, + // and that none of them is currently in pending status: + WitnetV2.ResponseStatus _status = surrogate.latestUpdateResponseStatus(_id4s[_ix]); + _require( + _status == WitnetV2.ResponseStatus.Ready + || _status == WitnetV2.ResponseStatus.Error + || _status == WitnetV2.ResponseStatus.Delivered, + string(abi.encodePacked( + "unconsolidated feed: 0x", + _id4s[_ix].toHexString() + )) + ); + } + + // Set new implementation as initialized: + __proxiable().implementation = base(); + + // Emit event: + emit Upgraded(msg.sender, base(), codehash(), version()); + } + + function isUpgradableFrom(address _from) external view override returns (bool) { + return surrogate.isUpgradableFrom(_from); + } + + + // ================================================================================================================ + // --- Partial interception of 'IWitnetFeeds' --------------------------------------------------------------------- + + function estimateUpdateBaseFee(bytes4, uint256 _gasPrice, uint256) public view returns (uint256) { + return abi.decode( + _staticcall(abi.encodeWithSignature( + "estimateUpdateBaseFee(uint256)", + _gasPrice + )), + (uint256) + ); + } + + function estimateUpdateBaseFee(bytes4, uint256 _gasPrice, uint256, bytes32) external view returns (uint256) { + return abi.decode( + _staticcall(abi.encodeWithSignature( + "estimateUpdateBaseFee(uint256)", + _gasPrice + )), + (uint256) + ); + } + + function latestResponse(bytes4 _feedId) public view returns (Witnet.Response memory) { + WitnetV2.Response memory _responseV2 = abi.decode( + _staticcall(abi.encodeWithSignature( + "lastValidResponse(bytes4)", + _feedId + )), + (WitnetV2.Response) + ); + return Witnet.Response({ + reporter: _responseV2.reporter, + timestamp: uint256(_responseV2.resultTimestamp), + drTxHash: _responseV2.resultTallyHash, + cborBytes: _responseV2.resultCborBytes + }); + } + + function latestResult(bytes4 _feedId) public view returns (Witnet.Result memory) { + return Witnet.resultFromCborBytes( + latestResponse(_feedId).cborBytes + ); + } + + function latestUpdateRequest(bytes4 _feedId) external view returns (Witnet.Request memory) { + WitnetV2.Request memory _requestV2 = abi.decode( + _staticcall(abi.encodeWithSignature( + "latestUpdateRequest(bytes4)", + _feedId + )), + (WitnetV2.Request) + ); + return Witnet.Request({ + addr: address(0), + slaHash: abi.decode(abi.encode(_requestV2.witnetSLA), (bytes32)), + radHash: _requestV2.witnetRAD, + gasprice: tx.gasprice, + reward: uint256(_requestV2.evmReward) + }); + } + + function latestUpdateResponse(bytes4 _feedId) external view returns (Witnet.Response memory) { + WitnetV2.Response memory _responseV2 = abi.decode( + _staticcall(abi.encodeWithSignature( + "latestUpdateResponse(bytes4)", + _feedId + )), + (WitnetV2.Response) + ); + return Witnet.Response({ + reporter: _responseV2.reporter, + timestamp: uint256(_responseV2.resultTimestamp), + drTxHash: bytes32(_responseV2.resultTallyHash), + cborBytes: _responseV2.resultCborBytes + }); + } + + function latestUpdateResultStatus(bytes4 _feedId) external view returns (Witnet.ResultStatus) { + WitnetV2.ResponseStatus _status = abi.decode( + _staticcall(abi.encodeWithSignature( + "latestUpdateResponseStatus(bytes4)", + _feedId + )), + (WitnetV2.ResponseStatus) + ); + if (_status == WitnetV2.ResponseStatus.Finalizing) { + return Witnet.ResultStatus.Awaiting; + } else { + return Witnet.ResultStatus(uint8(_status)); + } + } + + function lookupBytecode(bytes4 _feedId) external view returns (bytes memory) { + return abi.decode( + _staticcall(abi.encodeWithSignature( + "lookupWitnetBytecode(bytes4)", + _feedId + )), + ((bytes)) + ); + } + + function lookupRadHash(bytes4 _feedId) external view returns (bytes32) { + return abi.decode( + _staticcall(abi.encodeWithSignature( + "lookupWitnetRadHash(bytes4)", + _feedId + )), + (bytes32) + ); + } + + function requestUpdate(bytes4 _feedId) external payable returns (uint256 _usedFunds) { + _usedFunds = surrogate.requestUpdate{ + value: msg.value + }( + _feedId + ); + if (_usedFunds < msg.value) { + // transfer back unused funds: + payable(msg.sender).transfer(msg.value - _usedFunds); + } + } + + function requestUpdate(bytes4, bytes32) external payable returns (uint256) { + _revert("deprecated"); + } + + + // ================================================================================================================ + // --- Partial interception of 'IWitnetFeedsAdmin' ---------------------------------------------------------------- + + function settleDefaultRadonSLA(Witnet.RadonSLA calldata _slaV1) external { + _require( + Witnet.isValid(_slaV1), + "invalid SLA" + ); + __call(abi.encodeWithSignature( + "settleDefaultRadonSLA((uint8,uint64))", + WitnetV2.RadonSLA({ + committeeSize: _slaV1.numWitnesses, + witnessingFeeNanoWit: _slaV1.witnessCollateral / _slaV1.numWitnesses + }) + )); + } + + + // ================================================================================================================ + // --- Internal methods ------------------------------------------------------------------------------------------- + + function _require(bool _condition, string memory _message) internal pure { + if (!_condition) { + _revert(_message); + } + } + + function _revert(string memory _reason) internal pure { + revert( + string(abi.encodePacked( + class(), + ": ", + _reason + )) + ); + } + + function _staticcall(bytes memory _encodedCall) internal view returns (bytes memory _returnData) { + bool _success; + (_success, _returnData) = address(surrogate).staticcall(_encodedCall); + _require( + _success, + string(abi.encodePacked( + "cannot surrogate static call: 0x", + msg.sig.toHexString() + )) + ); + } + + function __call(bytes memory _encodedCall) internal returns (bytes memory _returnData) { + bool _success; + (_success, _returnData) = address(surrogate).call(_encodedCall); + _require( + _success, + string(abi.encodePacked( + "cannot surrogate call: 0x", + msg.sig.toHexString() + )) + ); + } + +} \ No newline at end of file diff --git a/contracts/interfaces/V2/IWitnetPriceFeeds.sol b/contracts/interfaces/V2/IWitnetPriceFeeds.sol index e221c6c3e..0b4b7e12c 100644 --- a/contracts/interfaces/V2/IWitnetPriceFeeds.sol +++ b/contracts/interfaces/V2/IWitnetPriceFeeds.sol @@ -5,6 +5,7 @@ pragma solidity >=0.8.0 <0.9.0; import "./IWitnetPriceSolver.sol"; interface IWitnetPriceFeeds { + /// ====================================================================================================== /// --- IFeeds extension --------------------------------------------------------------------------------- diff --git a/contracts/libs/Witnet.sol b/contracts/libs/Witnet.sol index 290b28eab..075d7da20 100644 --- a/contracts/libs/Witnet.sol +++ b/contracts/libs/Witnet.sol @@ -390,6 +390,17 @@ library Witnet { && a.minerCommitRevealFee >= b.minerCommitRevealFee ); } + + function isValid(RadonSLA calldata sla) internal pure returns (bool) { + return ( + sla.numWitnesses > 0 + && sla.numWitnesses <= 127 + && sla.minConsensusPercentage >= 51 + && sla.witnessReward > 0 + && sla.witnessCollateral > 20 * 10 ** 9 + && sla.witnessCollateral / sla.witnessReward <= 125 + ); + } /// =============================================================================================================== @@ -501,6 +512,19 @@ library Witnet { } + /// =============================================================================================================== + /// --- 'bytes4' helper methods ----------------------------------------------------------------------------------- + + function toHexString(bytes4 word) internal pure returns (string memory) { + return string(abi.encodePacked( + toHexString(uint8(bytes1(word))), + toHexString(uint8(bytes1(word << 8))), + toHexString(uint8(bytes1(word << 16))), + toHexString(uint8(bytes1(word << 24))) + )); + } + + /// =============================================================================================================== /// --- 'string' helper methods ----------------------------------------------------------------------------------- diff --git a/migrations/witnet.addresses.json b/migrations/witnet.addresses.json index fa4381472..aa2a24069 100644 --- a/migrations/witnet.addresses.json +++ b/migrations/witnet.addresses.json @@ -780,7 +780,8 @@ "WitnetRequestBoardImplementation": "0x307b1a85dC9BE441Cd7e4931266Dd6a24C4A98d2", "WitnetRequestBoardTrustableOvm2": "0xDb248F9D510aF57A6f7d009712e45f6cDDEA776d", "WitnetRequestFactoryImplementation": "0xc228d93908bd3E9E9015f83f125d1514803eDC06", - "WitnetOracleV20": "0x77703aE126B971c9946d562F41Dd47071dA00777" + "WitnetOracleV20": "0x77703aE126B971c9946d562F41Dd47071dA00777", + "WitnetPriceFeedsV20": "0x1111AbA2164AcdC6D291b08DfB374280035E1111" }, "optimism.mainnet": { "WitnetLib": "0x1D9c4a8f8B7b5F9B8e2641D81927f8F8Cc7fF079", diff --git a/migrations/witnet.settings.js b/migrations/witnet.settings.js index 02e56b7d4..2dcd62081 100644 --- a/migrations/witnet.settings.js +++ b/migrations/witnet.settings.js @@ -21,6 +21,7 @@ module.exports = { }, optimism: { WitnetRequestBoard: "WitnetRequestBoardBypassV20:WitnetRequestBoardTrustableOvm2", + WitnetPriceFeeds: "WitnetPriceFeedsBypassV20:", }, "polygon.zkevm.goerli": { WitnetBytecodes: "WitnetBytecodesNoSha256",