diff --git a/README.md b/README.md index 21ffe194..01aca983 100644 --- a/README.md +++ b/README.md @@ -1,12 +1,12 @@ # witnet-solidity-bridge -Open repository containing the smart contracts composing the **Witnet Solidity Bridge** framework. This framework enables Solidity developers and smart contracts operating in a long range of EVM-compatible chains to interact with the [Witnet Oracle Blockchain](https://witnet.io) for retrieving and aggregating offchain public data, and randomness. +Solidity source code of the smart contracts composing the **Witnet EVM Bridge** framework. This framework enables smart contracts operating in a long range of EVM-compatible chains to interact with the [Witnet Oracle Blockchain](https://witnet.io) for retrieving and aggregating offchain public data, or as an entropy source for randomness generation. ## Install the package `$ pnpm install` -## Deploy the whole WSB framework on a new chain +## Deploying the Witnet EVM Bridge on a new chain ### Pre-assessment @@ -29,7 +29,7 @@ Should any artifact require customized contract implementations: `$ pnpm run migrate :` -## Upgrade WSB components on an existing chain +## Upgrding the Witnet EVM Bridge on an existing chain When modifying the existing source code, or the contents of `settings/artifacts` or `settings/specs`, you may need to upgrade some of the artifacts on certain networks. Just add the `--artifacts` parameter and a comma-separated list of the artifacts you need to upgrade. For instance: @@ -44,8 +44,43 @@ Reasons for an upgrade to fail: - You're attempting to upgrade a contract with the same implementation logic as it currently has. - The parameters passed to the upgrade call, as specified in `settings/specs` are not accepted for some reason (see actual revert message for further info). -## Exported assets - -- [Deployed addresses.]("./migrations/addresses.json") -- [Supported chains.]("./settings/networks/index.js") +## Package exported modules + +### `require("witnet-solidity-bridge")` +Javacript methods and resources: + +- List of supported EVM ecosystems: + - `supportedEcosystems()` +- List of supported EVM chains: + - `supportedNetworks()` +- WEB addresses at a given chain: + - `getAddresses(network)` +- WEB artifacts: + - `assets.WitnetOracle` + - `assets.WitnetPriceFeeds` + - `assets.WitnetPriceRouteSolver` + - `assets.WitnetRequest` + - `assets.WitnetRequestBytecodes` + - `assets.WitnetRequestFactory` + - `assets.WitnetRequestTemplate` + - `assets.WitnetUpgrableBase` + +### `require("witnet-solidity-bridge/utils")` + +Javascript utils methods: + +- `fromAscii(str)` +- `getRealmNetworkFromArgs()` +- `getRealmNetworkFromString()` +- `getWitnetArtifactsFromArgs()` +- `getWitnetRequestMethodString(method)` +- `isDryRun(network)` +- `isNullAddress(addr)` +- `padLeft(str, char, size)` +- `prompt(text)` +- `readJsonFromFile(filename)` +- `overwriteJsonFile(filname, extra)` +- `traceHeader(header)` +- `traceTx(tx)` +- `traceVerify(network, verifyArgs)` diff --git a/contracts/apps/WitnetFeeds.sol b/contracts/WitnetFeeds.sol similarity index 88% rename from contracts/apps/WitnetFeeds.sol rename to contracts/WitnetFeeds.sol index 97462e64..67e97247 100644 --- a/contracts/apps/WitnetFeeds.sol +++ b/contracts/WitnetFeeds.sol @@ -2,9 +2,9 @@ pragma solidity >=0.7.0 <0.9.0; -import "../interfaces/IFeeds.sol"; -import "../interfaces/IWitnetFeeds.sol"; -import "../interfaces/IWitnetFeedsAdmin.sol"; +import "./interfaces/IFeeds.sol"; +import "./interfaces/IWitnetFeeds.sol"; +import "./interfaces/IWitnetFeedsAdmin.sol"; import "ado-contracts/contracts/interfaces/IERC2362.sol"; diff --git a/contracts/WitnetPriceFeeds.sol b/contracts/WitnetPriceFeeds.sol index 709f2958..cc9c83e4 100644 --- a/contracts/WitnetPriceFeeds.sol +++ b/contracts/WitnetPriceFeeds.sol @@ -2,7 +2,7 @@ pragma solidity >=0.7.0 <0.9.0; -import "./apps/WitnetFeeds.sol"; +import "./WitnetFeeds.sol"; import "./interfaces/IWitnetPriceFeeds.sol"; import "./interfaces/IWitnetPriceSolverDeployer.sol"; diff --git a/contracts/WitnetRandomness.sol b/contracts/WitnetRandomness.sol new file mode 100644 index 00000000..1066cd7c --- /dev/null +++ b/contracts/WitnetRandomness.sol @@ -0,0 +1,17 @@ +// SPDX-License-Identifier: MIT + +pragma solidity >=0.8.0 <0.9.0; + +import "./interfaces/IWitnetOracleEvents.sol"; +import "./interfaces/IWitnetRandomness.sol"; +import "./interfaces/IWitnetRandomnessEvents.sol"; + +abstract contract WitnetRandomness + is + IWitnetOracleEvents, + IWitnetRandomness, + IWitnetRandomnessEvents +{ + function class() virtual external view returns (string memory); + function specs() virtual external view returns (bytes4); +} diff --git a/contracts/apps/UsingWitnet.sol b/contracts/apps/UsingWitnet.sol index 7f829491..0c1a21cb 100644 --- a/contracts/apps/UsingWitnet.sol +++ b/contracts/apps/UsingWitnet.sol @@ -23,7 +23,7 @@ abstract contract UsingWitnet /// @dev as to deal with volatility of evmGasPrice and evmWitPrice during the live time of /// @dev a data request (since being posted until a result gets reported back), at both the EVM and /// @dev the Witnet blockchain levels, respectivelly. - uint16 private __witnetBaseFeeOverheadPercentage; + uint16 internal __witnetBaseFeeOverheadPercentage; /// @param _wrb Address of the WitnetOracle contract. constructor(WitnetOracle _wrb) { @@ -71,7 +71,7 @@ abstract contract UsingWitnet returns (uint256) { return ( - (100 + _witnetBaseFeeOverheadPercentage()) + (100 + __witnetBaseFeeOverheadPercentage) * __witnet.estimateBaseFee(tx.gasprice, _resultMaxSize) ) / 100; } @@ -89,12 +89,4 @@ abstract contract UsingWitnet { return __witnet.getQueryResultError(_witnetQueryId); } - - function _witnetBaseFeeOverheadPercentage() virtual internal view returns (uint16) { - return __witnetBaseFeeOverheadPercentage; - } - - function __witnetSetBaseFeeOverheadPercentage(uint16 _baseFeeOverheadPercentage) virtual internal { - __witnetBaseFeeOverheadPercentage = _baseFeeOverheadPercentage; - } } diff --git a/contracts/apps/UsingWitnetRandomness.sol b/contracts/apps/UsingWitnetRandomness.sol index 61e20d0b..b26bbd11 100644 --- a/contracts/apps/UsingWitnetRandomness.sol +++ b/contracts/apps/UsingWitnetRandomness.sol @@ -3,97 +3,28 @@ pragma solidity >=0.7.0 <0.9.0; pragma experimental ABIEncoderV2; -import "./WitnetConsumer.sol"; -import "../WitnetRequest.sol"; +import "../WitnetRandomness.sol"; +/// @title The UsingWitnetRandomness contract +/// @dev Contracts willing to interact with WitnetRandomness appliance should just inherit from this contract. +/// @author The Witnet Foundation. abstract contract UsingWitnetRandomness is - WitnetConsumer + IWitnetOracleEvents, + IWitnetRandomnessEvents { - using Witnet for bytes; - using WitnetCBOR for WitnetCBOR.CBOR; - using WitnetV2 for bytes32; - using WitnetV2 for WitnetV2.RadonSLA; - - bytes32 internal immutable __witnetRandomnessRadHash; - - /// @param _wrb Address of the WitnetOracle contract. - /// @param _baseFeeOverheadPercentage Percentage over base fee to pay as on every data request. - /// @param _callbackGasLimit Maximum gas to be spent by the IWitnetConsumer's callback methods. - constructor( - WitnetOracle _wrb, - uint16 _baseFeeOverheadPercentage, - uint24 _callbackGasLimit - ) - UsingWitnet(_wrb) - WitnetConsumer(_callbackGasLimit) - { - // On-chain building of the Witnet Randomness Request: - { - WitnetRequestFactory _factory = witnet().factory(); - WitnetRequestBytecodes _registry = witnet().registry(); - // Build own Witnet Randomness Request: - bytes32[] memory _retrievals = new bytes32[](1); - _retrievals[0] = _registry.verifyRadonRetrieval( - Witnet.RadonDataRequestMethods.Rng, - "", // no url - "", // no body - new string[2][](0), // no headers - hex"80" // no retrieval script - ); - Witnet.RadonFilter[] memory _filters; - bytes32 _aggregator = _registry.verifyRadonReducer(Witnet.RadonReducer({ - opcode: Witnet.RadonReducerOpcodes.Mode, - filters: _filters // no filters - })); - bytes32 _tally = _registry.verifyRadonReducer(Witnet.RadonReducer({ - opcode: Witnet.RadonReducerOpcodes.ConcatenateAndHash, - filters: _filters // no filters - })); - WitnetRequestTemplate _template = WitnetRequestTemplate(_factory.buildRequestTemplate( - _retrievals, - _aggregator, - _tally, - 32 // 256 bits of pure entropy ;-) - )); - __witnetRandomnessRadHash = WitnetRequest( - _template.buildRequest(new string[][](_retrievals.length)) - ).radHash(); - } - __witnetSetBaseFeeOverheadPercentage(_baseFeeOverheadPercentage); - } - - function _witnetEstimateEvmReward() virtual override internal view returns (uint256) { - return _witnetEstimateEvmReward(32); - } - - function _witnetRandomUniformUint32(uint32 _range, uint256 _nonce, bytes32 _seed) internal pure returns (uint32) { - uint256 _number = uint256( - keccak256( - abi.encode(_seed, _nonce) - ) - ) & uint256(2 ** 224 - 1); - return uint32((_number * _range) >> 224); - } - - function _witnetReadRandomizeFromResultValue(WitnetCBOR.CBOR calldata cborValue) internal pure returns (bytes32) { - return cborValue.readBytes().toBytes32(); - } - - function __witnetRandomize(uint256 _witnetEvmReward) virtual internal returns (uint256) { - return __witnetRandomize(_witnetEvmReward, __witnetDefaultSLA); - } - - function __witnetRandomize( - uint256 _witnetEvmReward, - WitnetV2.RadonSLA memory _witnetQuerySLA - ) - virtual internal - returns (uint256 _randomizeId) - { - return __witnet.postRequest{value: _witnetEvmReward}( - __witnetRandomnessRadHash, - _witnetQuerySLA + WitnetOracle immutable public witnet; + WitnetRandomness immutable public __RNG; + + constructor(WitnetRandomness _witnetRandomness) { + require( + address(_witnetRandomness).code.length > 0 + && _witnetRandomness.specs() == type(WitnetRandomness).interfaceId, + "UsingWitnetRandomness: uncompliant WitnetRandomness appliance" ); + __RNG = _witnetRandomness; + witnet = __RNG.witnet(); } + } + diff --git a/contracts/apps/UsingWitnetRequest.sol b/contracts/apps/UsingWitnetRequest.sol index 70a2c20e..cf8541f0 100644 --- a/contracts/apps/UsingWitnetRequest.sol +++ b/contracts/apps/UsingWitnetRequest.sol @@ -27,7 +27,7 @@ abstract contract UsingWitnetRequest dataRequest = _witnetRequest; __witnetQueryResultMaxSize = _witnetRequest.resultDataMaxSize(); __witnetRequestRadHash = _witnetRequest.radHash(); - __witnetSetBaseFeeOverheadPercentage(_baseFeeOverheadPercentage); + __witnetBaseFeeOverheadPercentage = _baseFeeOverheadPercentage; } function _witnetEstimateEvmReward() diff --git a/contracts/apps/UsingWitnetRequestTemplate.sol b/contracts/apps/UsingWitnetRequestTemplate.sol index 5785e8e3..1823bdce 100644 --- a/contracts/apps/UsingWitnetRequestTemplate.sol +++ b/contracts/apps/UsingWitnetRequestTemplate.sol @@ -25,7 +25,7 @@ abstract contract UsingWitnetRequestTemplate ); dataRequestTemplate = _witnetRequestTemplate; __witnetQueryResultMaxSize = _witnetRequestTemplate.resultDataMaxSize(); - __witnetSetBaseFeeOverheadPercentage(_baseFeeOverheadPercentage); + __witnetBaseFeeOverheadPercentage = _baseFeeOverheadPercentage; } function _witnetBuildRadHash(string[][] memory _witnetRequestArgs) diff --git a/contracts/apps/WitnetConsumer.sol b/contracts/apps/WitnetConsumer.sol index 60c8b30c..fecb4aad 100644 --- a/contracts/apps/WitnetConsumer.sol +++ b/contracts/apps/WitnetConsumer.sol @@ -43,7 +43,7 @@ abstract contract WitnetConsumer function _witnetEstimateEvmReward() virtual internal view returns (uint256) { return ( - (100 + _witnetBaseFeeOverheadPercentage()) + (100 + __witnetBaseFeeOverheadPercentage) * __witnet.estimateBaseFeeWithCallback( tx.gasprice, _witnetCallbackGasLimit() @@ -67,7 +67,7 @@ abstract contract WitnetConsumer returns (uint256) { return ( - (100 + _witnetBaseFeeOverheadPercentage()) + (100 + __witnetBaseFeeOverheadPercentage) * __witnet.estimateBaseFeeWithCallback( tx.gasprice, _callbackGasLimit diff --git a/contracts/apps/WitnetRandomnessRequestConsumer.sol b/contracts/apps/WitnetRandomnessRequestConsumer.sol new file mode 100644 index 00000000..d7c1d1fe --- /dev/null +++ b/contracts/apps/WitnetRandomnessRequestConsumer.sol @@ -0,0 +1,96 @@ +// SPDX-License-Identifier: MIT + +pragma solidity >=0.7.0 <0.9.0; +pragma experimental ABIEncoderV2; + +import "./WitnetConsumer.sol"; +import "../WitnetRequest.sol"; + +abstract contract WitnetRandomnessRequestConsumer + is + WitnetConsumer +{ + using Witnet for bytes; + using WitnetCBOR for WitnetCBOR.CBOR; + using WitnetV2 for bytes32; + using WitnetV2 for WitnetV2.RadonSLA; + + bytes32 internal immutable __witnetRandomnessRadHash; + + /// @param _wrb Address of the WitnetOracle contract. + /// @param _baseFeeOverheadPercentage Percentage over base fee to pay as on every data request. + /// @param _callbackGasLimit Maximum gas to be spent by the IWitnetConsumer's callback methods. + constructor( + WitnetOracle _wrb, + uint16 _baseFeeOverheadPercentage, + uint24 _callbackGasLimit + ) + UsingWitnet(_wrb) + WitnetConsumer(_callbackGasLimit) + { + // On-chain building of the Witnet Randomness Request: + { + WitnetRequestBytecodes _registry = witnet().registry(); + // Build own Witnet Randomness Request: + bytes32[] memory _retrievals = new bytes32[](1); + _retrievals[0] = _registry.verifyRadonRetrieval( + Witnet.RadonDataRequestMethods.RNG, + "", // no url + "", // no body + new string[2][](0), // no headers + hex"80" // no retrieval script + ); + Witnet.RadonFilter[] memory _filters; + bytes32 _aggregator = _registry.verifyRadonReducer(Witnet.RadonReducer({ + opcode: Witnet.RadonReducerOpcodes.Mode, + filters: _filters // no filters + })); + bytes32 _tally = _registry.verifyRadonReducer(Witnet.RadonReducer({ + opcode: Witnet.RadonReducerOpcodes.ConcatenateAndHash, + filters: _filters // no filters + })); + __witnetRandomnessRadHash = _registry.verifyRadonRequest( + _retrievals, + _aggregator, + _tally, + 32, // 256 bits of pure entropy ;-) + new string[][](_retrievals.length) + ); + } + __witnetBaseFeeOverheadPercentage = _baseFeeOverheadPercentage; + } + + function _witnetEstimateEvmReward() virtual override internal view returns (uint256) { + return _witnetEstimateEvmReward(32); + } + + function _witnetRandomUniformUint32(uint32 _range, uint256 _nonce, bytes32 _seed) internal pure returns (uint32) { + uint256 _number = uint256( + keccak256( + abi.encode(_seed, _nonce) + ) + ) & uint256(2 ** 224 - 1); + return uint32((_number * _range) >> 224); + } + + function _witnetReadRandomizeFromResultValue(WitnetCBOR.CBOR calldata cborValue) internal pure returns (bytes32) { + return cborValue.readBytes().toBytes32(); + } + + function __witnetRandomize(uint256 _witnetEvmReward) virtual internal returns (uint256) { + return __witnetRandomize(_witnetEvmReward, __witnetDefaultSLA); + } + + function __witnetRandomize( + uint256 _witnetEvmReward, + WitnetV2.RadonSLA memory _witnetQuerySLA + ) + virtual internal + returns (uint256 _randomizeId) + { + return __witnet.postRequest{value: _witnetEvmReward}( + __witnetRandomnessRadHash, + _witnetQuerySLA + ); + } +} diff --git a/contracts/apps/WitnetRandomnessV2.sol b/contracts/apps/WitnetRandomnessV2.sol new file mode 100644 index 00000000..80e119d8 --- /dev/null +++ b/contracts/apps/WitnetRandomnessV2.sol @@ -0,0 +1,534 @@ +// SPDX-License-Identifier: MIT + +pragma solidity >=0.8.0 <0.9.0; + +import "../WitnetRandomness.sol"; +import "../apps/UsingWitnet.sol"; +import "../interfaces/IWitnetRandomnessAdmin.sol"; +import "../patterns/Ownable2Step.sol"; + +/// @title WitnetRandomnessV2: Unmalleable and provably-fair randomness generation based on the Witnet Oracle v2.*. +/// @author The Witnet Foundation. +contract WitnetRandomnessV2 + is + Ownable2Step, + UsingWitnet, + WitnetRandomness, + IWitnetRandomnessAdmin +{ + using Witnet for bytes; + using Witnet for Witnet.Result; + using WitnetV2 for WitnetV2.RadonSLA; + + struct Randomize { + uint256 witnetQueryId; + uint256 prevBlock; + uint256 nextBlock; + } + + struct Storage { + uint256 lastRandomizeBlock; + mapping (uint256 => Randomize) randomize_; + } + + /// @notice Unique identifier of the RNG data request used on the Witnet Oracle blockchain for solving randomness. + /// @dev Can be used to track all randomness requests solved so far on the Witnet Oracle blockchain. + bytes32 immutable public override witnetRadHash; + + constructor( + WitnetOracle _witnet + ) + Ownable(address(msg.sender)) + UsingWitnet(_witnet) + { + _require( + address(_witnet) == address(0) + || _witnet.specs() == type(IWitnetOracle).interfaceId, + "uncompliant WitnetOracle" + ); + WitnetRequestBytecodes _registry = witnet().registry(); + { + // Build own Witnet Randomness Request: + bytes32[] memory _retrievals = new bytes32[](1); + _retrievals[0] = _registry.verifyRadonRetrieval( + Witnet.RadonDataRequestMethods.RNG, + "", // no request url + "", // no request body + new string[2][](0), // no request headers + hex"80" // no request Radon script + ); + Witnet.RadonFilter[] memory _filters; + bytes32 _aggregator = _registry.verifyRadonReducer(Witnet.RadonReducer({ + opcode: Witnet.RadonReducerOpcodes.Mode, + filters: _filters // no filters + })); + bytes32 _tally = _registry.verifyRadonReducer(Witnet.RadonReducer({ + opcode: Witnet.RadonReducerOpcodes.ConcatenateAndHash, + filters: _filters // no filters + })); + witnetRadHash = _registry.verifyRadonRequest( + _retrievals, + _aggregator, + _tally, + 32, // 256 bits of pure entropy ;-) + new string[][](_retrievals.length) + ); + } + } + + receive() virtual external payable { + _revert("no transfers accepted"); + } + + fallback() virtual external payable { + _revert(string(abi.encodePacked( + "not implemented: 0x", + Witnet.toHexString(uint8(bytes1(msg.sig))), + Witnet.toHexString(uint8(bytes1(msg.sig << 8))), + Witnet.toHexString(uint8(bytes1(msg.sig << 16))), + Witnet.toHexString(uint8(bytes1(msg.sig << 24))) + ))); + } + + function class() virtual override public pure returns (string memory) { + return type(WitnetRandomnessV2).name; + } + + function specs() virtual override external pure returns (bytes4) { + return type(WitnetRandomness).interfaceId; + } + + function witnet() override (IWitnetRandomness, UsingWitnet) + public view returns (WitnetOracle) + { + return UsingWitnet.witnet(); + } + + + /// =============================================================================================================== + /// --- 'IWitnetRandomness' implementation ------------------------------------------------------------------------ + + /// Returns amount of wei required to be paid as a fee when requesting randomization with a + /// transaction gas price as the one given. + function estimateRandomizeFee(uint256 _evmGasPrice) + public view + virtual override + returns (uint256) + { + return ( + (100 + __witnetBaseFeeOverheadPercentage) + * __witnet.estimateBaseFee( + _evmGasPrice, + witnetRadHash + ) + ) / 100; + } + + /// @notice Retrieves the result of keccak256-hashing the given block number with the randomness value + /// @notice generated by the Witnet Oracle blockchain in response to the first non-errored randomize request solved + /// @notice after such block number. + /// @dev Reverts if: + /// @dev i. no `randomize()` was requested on neither the given block, nor afterwards. + /// @dev ii. the first non-errored `randomize()` request found on or after the given block is not solved yet. + /// @dev iii. all `randomize()` requests that took place on or after the given block were solved with errors. + /// @param _blockNumber Block number from which the search will start + function fetchRandomnessAfter(uint256 _blockNumber) + public view + virtual override + returns (bytes32) + { + return keccak256( + abi.encode( + _blockNumber, + _fetchRandomnessAfter(_blockNumber) + ) + ); + } + + function _fetchRandomnessAfter(uint256 _blockNumber) + virtual internal view + returns (bytes32) + { + if (__storage().randomize_[_blockNumber].witnetQueryId == 0) { + _blockNumber = getRandomizeNextBlock(_blockNumber); + } + + Randomize storage __randomize = __storage().randomize_[_blockNumber]; + uint256 _witnetQueryId = __randomize.witnetQueryId; + _require( + _witnetQueryId != 0, + "not randomized" + ); + + WitnetV2.ResponseStatus _status = __witnet.getQueryResponseStatus(_witnetQueryId); + if (_status == WitnetV2.ResponseStatus.Ready) { + return ( + __witnet.getQueryResultCborBytes(_witnetQueryId) + .toWitnetResult() + .asBytes32() + ); + } else if (_status == WitnetV2.ResponseStatus.Error) { + uint256 _nextRandomizeBlock = __randomize.nextBlock; + _require( + _nextRandomizeBlock != 0, + "faulty randomize" + ); + return _fetchRandomnessAfter(_nextRandomizeBlock); + + } else { + _revert("pending randomize"); + } + } + + /// @notice Retrieves the actual random value, unique hash and timestamp of the witnessing commit/reveal act that took + /// @notice place in the Witnet Oracle blockchain in response to the first non-errored randomize request + /// @notice solved after the given block number. + /// @dev Reverts if: + /// @dev i. no `randomize()` was requested on neither the given block, nor afterwards. + /// @dev ii. the first non-errored `randomize()` request found on or after the given block is not solved yet. + /// @dev iii. all `randomize()` requests that took place on or after the given block were solved with errors. + /// @param _blockNumber Block number from which the search will start. + /// @return _witnetResultRandomness Random value provided by the Witnet blockchain and used for solving randomness after given block. + /// @return _witnetResultTimestamp Timestamp at which the randomness value was generated by the Witnet blockchain. + /// @return _witnetResultTallyHash Hash of the witnessing commit/reveal act that took place on the Witnet blockchain. + /// @return _witnetResultFinalityBlock EVM block number from which the provided randomness can be considered to be final. + function fetchRandomnessAfterProof(uint256 _blockNumber) + virtual override + public view + returns ( + bytes32 _witnetResultRandomness, + uint64 _witnetResultTimestamp, + bytes32 _witnetResultTallyHash, + uint256 _witnetResultFinalityBlock + ) + { + if (__storage().randomize_[_blockNumber].witnetQueryId == 0) { + _blockNumber = getRandomizeNextBlock(_blockNumber); + } + + Randomize storage __randomize = __storage().randomize_[_blockNumber]; + uint256 _witnetQueryId = __randomize.witnetQueryId; + _require( + _witnetQueryId != 0, + "not randomized" + ); + + WitnetV2.ResponseStatus _status = __witnet.getQueryResponseStatus(_witnetQueryId); + if (_status == WitnetV2.ResponseStatus.Ready) { + WitnetV2.Response memory _witnetQueryResponse = __witnet.getQueryResponse(_witnetQueryId); + _witnetResultTimestamp = _witnetQueryResponse.resultTimestamp; + _witnetResultTallyHash = _witnetQueryResponse.resultTallyHash; + _witnetResultFinalityBlock = _witnetQueryResponse.finality; + _witnetResultRandomness = _witnetQueryResponse.resultCborBytes.toWitnetResult().asBytes32(); + + } else if (_status == WitnetV2.ResponseStatus.Error) { + uint256 _nextRandomizeBlock = __randomize.nextBlock; + _require( + _nextRandomizeBlock != 0, + "faulty randomize" + ); + return fetchRandomnessAfterProof(_nextRandomizeBlock); + + } else { + _revert("pending randomize"); + } + } + + /// @notice Returns last block number on which a randomize was requested. + function getLastRandomizeBlock() + virtual override + external view + returns (uint256) + { + return __storage().lastRandomizeBlock; + } + + /// @notice Retrieves metadata related to the randomize request that got posted to the + /// @notice Witnet Oracle contract on the given block number. + /// @dev Returns zero values if no randomize request was actually posted on the given block. + /// @return _witnetQueryId Identifier of the underlying Witnet query created on the given block number. + /// @return _prevRandomizeBlock Block number in which a randomize request got posted just before this one. 0 if none. + /// @return _nextRandomizeBlock Block number in which a randomize request got posted just after this one, 0 if none. + function getRandomizeData(uint256 _blockNumber) + external view + virtual override + returns ( + uint256 _witnetQueryId, + uint256 _prevRandomizeBlock, + uint256 _nextRandomizeBlock + ) + { + Randomize storage __randomize = __storage().randomize_[_blockNumber]; + _witnetQueryId = __randomize.witnetQueryId; + _prevRandomizeBlock = __randomize.prevBlock; + _nextRandomizeBlock = __randomize.nextBlock; + } + + /// @notice Returns the number of the next block in which a randomize request was posted after the given one. + /// @param _blockNumber Block number from which the search will start. + /// @return Number of the first block found after the given one, or `0` otherwise. + function getRandomizeNextBlock(uint256 _blockNumber) + public view + virtual override + returns (uint256) + { + return ((__storage().randomize_[_blockNumber].witnetQueryId != 0) + ? __storage().randomize_[_blockNumber].nextBlock + // start search from the latest block + : _searchNextBlock(_blockNumber, __storage().lastRandomizeBlock) + ); + } + + /// @notice Returns the number of the previous block in which a randomize request was posted before the given one. + /// @param _blockNumber Block number from which the search will start. Cannot be zero. + /// @return First block found before the given one, or `0` otherwise. + function getRandomizePrevBlock(uint256 _blockNumber) + public view + virtual override + returns (uint256) + { + assert(_blockNumber > 0); + uint256 _latest = __storage().lastRandomizeBlock; + return ((_blockNumber > _latest) + ? _latest + // start search from the latest block + : _searchPrevBlock(_blockNumber, __storage().randomize_[_latest].prevBlock) + ); + } + + /// @notice Returns status of the first non-errored randomize request posted on or after the given block number. + /// @dev Possible values: + /// @dev - 0 -> Void: no randomize request was actually posted on or after the given block number. + /// @dev - 1 -> Awaiting: a randomize request was found but it's not yet solved by the Witnet blockchain. + /// @dev - 2 -> Ready: a successfull randomize value was reported and ready to be read. + /// @dev - 3 -> Error: all randomize requests after the given block were solved with errors. + /// @dev - 4 -> Finalizing: a randomize resolution has been reported from the Witnet blockchain, but it's not yet final. + function getRandomizeStatus(uint256 _blockNumber) + virtual override + public view + returns (WitnetV2.ResponseStatus) + { + if (__storage().randomize_[_blockNumber].witnetQueryId == 0) { + _blockNumber = getRandomizeNextBlock(_blockNumber); + } + uint256 _witnetQueryId = __storage().randomize_[_blockNumber].witnetQueryId; + if (_witnetQueryId == 0) { + return WitnetV2.ResponseStatus.Void; + + } else { + WitnetV2.ResponseStatus _status = __witnet.getQueryResponseStatus(_witnetQueryId); + if (_status == WitnetV2.ResponseStatus.Error) { + uint256 _nextRandomizeBlock = __storage().randomize_[_blockNumber].nextBlock; + if (_nextRandomizeBlock != 0) { + return getRandomizeStatus(_nextRandomizeBlock); + } else { + return WitnetV2.ResponseStatus.Error; + } + } else { + return _status; + } + } + } + + /// @notice Returns `true` only if a successfull resolution from the Witnet blockchain is found for the first + /// @notice non-errored randomize request posted on or after the given block number. + function isRandomized(uint256 _blockNumber) + public view + virtual override + returns (bool) + { + return ( + getRandomizeStatus(_blockNumber) == WitnetV2.ResponseStatus.Ready + ); + } + + /// @notice Generates a pseudo-random number uniformly distributed within the range [0 .. _range), by using + /// @notice the given `nonce` and the randomness returned by `getRandomnessAfter(blockNumber)`. + /// @dev Fails under same conditions as `getRandomnessAfter(uint256)` does. + /// @param _range Range within which the uniformly-distributed random number will be generated. + /// @param _nonce Nonce value enabling multiple random numbers from the same randomness value. + /// @param _blockNumber Block number from which the search for the first randomize request solved aftewards will start. + function random(uint32 _range, uint256 _nonce, uint256 _blockNumber) + external view + virtual override + returns (uint32) + { + return WitnetV2.randomUniformUint32( + _range, + _nonce, + keccak256( + abi.encode( + msg.sender, + fetchRandomnessAfter(_blockNumber) + ) + ) + ); + } + + /// @notice Requests the Witnet oracle to generate an EVM-agnostic and trustless source of randomness. + /// @dev Only one randomness request per block will be actually posted to the Witnet Oracle. + /// @return _evmRandomizeFee Funds actually paid as randomize fee. + function randomize() + external payable + virtual override + returns (uint256 _evmRandomizeFee) + { + if (__storage().lastRandomizeBlock < block.number) { + _evmRandomizeFee = msg.value; + // Post the Witnet Randomness request: + uint _witnetQueryId = __witnet.postRequest{ + value: _evmRandomizeFee + }( + witnetRadHash, + __witnetDefaultSLA + ); + // Keep Randomize data in storage: + Randomize storage __randomize = __storage().randomize_[block.number]; + __randomize.witnetQueryId = _witnetQueryId; + // Update block links: + uint256 _prevBlock = __storage().lastRandomizeBlock; + __randomize.prevBlock = _prevBlock; + __storage().randomize_[_prevBlock].nextBlock = block.number; + __storage().lastRandomizeBlock = block.number; + // Throw event: + emit Randomizing( + block.number, + tx.gasprice, + _evmRandomizeFee, + _witnetQueryId, + __witnetDefaultSLA + ); + } + // Transfer back unused funds: + if (_evmRandomizeFee < msg.value) { + payable(msg.sender).transfer(msg.value - _evmRandomizeFee); + } + } + + /// @notice Returns the SLA parameters required for the Witnet Oracle blockchain to fulfill + /// @notice when solving randomness requests: + /// @notice - number of witnessing nodes contributing to randomness generation + /// @notice - reward in $nanoWIT received by every contributing node in the Witnet blockchain + function witnetQuerySLA() + virtual override + external view + returns (WitnetV2.RadonSLA memory) + { + return __witnetDefaultSLA; + } + + + /// =============================================================================================================== + /// --- 'IWitnetRandomnessAdmin' implementation ------------------------------------------------------------------- + + function acceptOwnership() + virtual override (IWitnetRandomnessAdmin, Ownable2Step) + public + { + Ownable2Step.acceptOwnership(); + } + + function baseFeeOverheadPercentage() + virtual override + external view + returns (uint16) + { + return __witnetBaseFeeOverheadPercentage; + } + + function owner() + virtual override (IWitnetRandomnessAdmin, Ownable) + public view + returns (address) + { + return Ownable.owner(); + } + + function pendingOwner() + virtual override (IWitnetRandomnessAdmin, Ownable2Step) + public view + returns (address) + { + return Ownable2Step.pendingOwner(); + } + + function transferOwnership(address _newOwner) + virtual override (IWitnetRandomnessAdmin, Ownable2Step) + public + onlyOwner + { + Ownable.transferOwnership(_newOwner); + } + + function settleBaseFeeOverheadPercentage(uint16 _baseFeeOverheadPercentage) + virtual override + external + onlyOwner + { + __witnetBaseFeeOverheadPercentage = _baseFeeOverheadPercentage; + } + + function settleWitnetQuerySLA(WitnetV2.RadonSLA calldata _witnetQuerySLA) + virtual override + external + onlyOwner + { + _require( + _witnetQuerySLA.isValid(), + "invalid SLA" + ); + __witnetDefaultSLA = _witnetQuerySLA; + } + + + // ================================================================================================================ + // --- Internal methods ------------------------------------------------------------------------------------------- + + function _require( + bool _condition, + string memory _message + ) + internal pure + { + if (!_condition) { + _revert(_message); + } + } + + function _revert(string memory _message) + internal pure + { + revert( + string(abi.encodePacked( + class(), + ": ", + _message + )) + ); + } + + /// @dev Recursively searches for the number of the first block after the given one in which a Witnet + /// @dev randomness request was posted. Returns 0 if none found. + function _searchNextBlock(uint256 _target, uint256 _latest) internal view returns (uint256) { + return ((_target >= _latest) + ? __storage().randomize_[_latest].nextBlock + : _searchNextBlock(_target, __storage().randomize_[_latest].prevBlock) + ); + } + + /// @dev Recursively searches for the number of the first block before the given one in which a Witnet + /// @dev randomness request was posted. Returns 0 if none found. + function _searchPrevBlock(uint256 _target, uint256 _latest) internal view returns (uint256) { + return ((_target > _latest) + ? _latest + : _searchPrevBlock(_target, __storage().randomize_[_latest].prevBlock) + ); + } + + function __storage() internal pure returns (Storage storage _ptr) { + bytes32 _slothash = keccak256(bytes("io.witnet.apps.randomness.v20")); + assembly { + _ptr.slot := _slothash + } + } +} diff --git a/contracts/core/WitnetUpgradableBase.sol b/contracts/core/WitnetUpgradableBase.sol index 57b31627..5c056b7f 100644 --- a/contracts/core/WitnetUpgradableBase.sol +++ b/contracts/core/WitnetUpgradableBase.sol @@ -35,9 +35,13 @@ abstract contract WitnetUpgradableBase /// @dev Reverts if proxy delegatecalls to unexistent method. fallback() virtual external { - revert("WitnetUpgradableBase: not implemented"); + _revert("not implemented"); } + + function class() virtual public view returns (string memory) { + return type(WitnetUpgradableBase).name; + } // ================================================================================================================ // --- Overrides 'Proxiable' -------------------------------------------------------------------------------------- @@ -60,6 +64,29 @@ abstract contract WitnetUpgradableBase // ================================================================================================================ // --- Internal methods ------------------------------------------------------------------------------------------- + function _require( + bool _condition, + string memory _message + ) + internal view + { + if (!_condition) { + _revert(_message); + } + } + + function _revert(string memory _message) + internal view + { + revert( + string(abi.encodePacked( + class(), + ": ", + _message + )) + ); + } + /// Converts bytes32 into string. function _toString(bytes32 _bytes32) internal pure diff --git a/contracts/core/customs/WitnetOracleTrustableObscuro.sol b/contracts/core/customs/WitnetOracleTrustableObscuro.sol index 06a3cb1a..34418f09 100644 --- a/contracts/core/customs/WitnetOracleTrustableObscuro.sol +++ b/contracts/core/customs/WitnetOracleTrustableObscuro.sol @@ -16,7 +16,7 @@ contract WitnetOracleTrustableObscuro is WitnetOracleTrustableDefault { - function class() virtual override external view returns (string memory) { + function class() virtual override public view returns (string memory) { return type(WitnetOracleTrustableObscuro).name; } diff --git a/contracts/core/customs/WitnetOracleTrustableOvm2.sol b/contracts/core/customs/WitnetOracleTrustableOvm2.sol index f74e4ba9..eedb7001 100644 --- a/contracts/core/customs/WitnetOracleTrustableOvm2.sol +++ b/contracts/core/customs/WitnetOracleTrustableOvm2.sol @@ -23,7 +23,7 @@ contract WitnetOracleTrustableOvm2 { using WitnetV2 for WitnetV2.RadonSLA; - function class() virtual override external view returns (string memory) { + function class() virtual override public view returns (string memory) { return type(WitnetOracleTrustableOvm2).name; } diff --git a/contracts/core/customs/WitnetOracleTrustableReef.sol b/contracts/core/customs/WitnetOracleTrustableReef.sol index 4d51180f..220d1f53 100644 --- a/contracts/core/customs/WitnetOracleTrustableReef.sol +++ b/contracts/core/customs/WitnetOracleTrustableReef.sol @@ -17,7 +17,7 @@ contract WitnetOracleTrustableReef is WitnetOracleTrustableDefault { - function class() virtual override external view returns (string memory) { + function class() virtual override public view returns (string memory) { return type(WitnetOracleTrustableReef).name; } diff --git a/contracts/core/customs/WitnetRequestBytecodesNoSha256.sol b/contracts/core/customs/WitnetRequestBytecodesNoSha256.sol index dc0c02f0..719f909a 100644 --- a/contracts/core/customs/WitnetRequestBytecodesNoSha256.sol +++ b/contracts/core/customs/WitnetRequestBytecodesNoSha256.sol @@ -9,7 +9,7 @@ contract WitnetRequestBytecodesNoSha256 is WitnetRequestBytecodesDefault { - function class() virtual override external view returns (string memory) { + function class() virtual override public view returns (string memory) { return type(WitnetRequestBytecodesNoSha256).name; } diff --git a/contracts/core/customs/WitnetRequestFactoryCfxCore.sol b/contracts/core/customs/WitnetRequestFactoryCfxCore.sol index 2a820cbd..de13a150 100644 --- a/contracts/core/customs/WitnetRequestFactoryCfxCore.sol +++ b/contracts/core/customs/WitnetRequestFactoryCfxCore.sol @@ -9,7 +9,7 @@ contract WitnetRequestFactoryCfxCore is WitnetRequestFactoryDefault { - function class() virtual override external view returns (string memory) { + function class() virtual override public view returns (string memory) { return type(WitnetRequestFactoryCfxCore).name; } diff --git a/contracts/core/defaults/WitnetOracleTrustableBase.sol b/contracts/core/defaults/WitnetOracleTrustableBase.sol index 813a8466..a1ec3d52 100644 --- a/contracts/core/defaults/WitnetOracleTrustableBase.sol +++ b/contracts/core/defaults/WitnetOracleTrustableBase.sol @@ -39,30 +39,35 @@ abstract contract WitnetOracleTrustableBase WitnetRequestFactory immutable private __factory; modifier checkCallbackRecipient(address _addr, uint24 _callbackGasLimit) { - require( + _require( _addr.code.length > 0 && IWitnetConsumer(_addr).reportableFrom(address(this)) && _callbackGasLimit > 0, - "WitnetOracle: invalid callback" + "invalid callback" ); _; } modifier checkReward(uint256 _baseFee) { - require( + _require( _getMsgValue() >= _baseFee, - "WitnetOracle: insufficient reward" - ); _; + "insufficient reward" + ); + _require( + _getMsgValue() <= _baseFee * 10, + "too much reward" + ); + _; } modifier checkSLA(WitnetV2.RadonSLA calldata sla) { - require( + _require( WitnetV2.isValid(sla), - "WitnetOracle: invalid SLA" + "invalid SLA" ); _; } /// Asserts the given query is currently in the given status. modifier inStatus(uint256 _queryId, WitnetV2.QueryStatus _status) { if (WitnetOracleDataLib.seekQueryStatus(_queryId) != _status) { - revert(WitnetOracleDataLib.notInStatusRevertMessage(_status)); + _revert(WitnetOracleDataLib.notInStatusRevertMessage(_status)); } else { _; } @@ -70,17 +75,17 @@ abstract contract WitnetOracleTrustableBase /// Asserts the caller actually posted the referred query. modifier onlyRequester(uint256 _queryId) { - require( + _require( msg.sender == WitnetOracleDataLib.seekQueryRequest(_queryId).requester, - "WitnetOracle: not the requester" + "not the requester" ); _; } /// Asserts the caller is authorized as a reporter modifier onlyReporters { - require( + _require( __storage().reporters[msg.sender], - "WitnetOracle: unauthorized reporter" + "unauthorized reporter" ); _; } @@ -105,7 +110,7 @@ abstract contract WitnetOracleTrustableBase } receive() external payable { - revert("WitnetOracle: no transfers accepted"); + _revert("no transfers accepted"); } /// @dev Provide backwards compatibility for dapps bound to versions <= 0.6.1 @@ -114,8 +119,8 @@ abstract contract WitnetOracleTrustableBase /* solhint-disable payable-fallback */ /* solhint-disable no-complex-fallback */ fallback() override external { - revert(string(abi.encodePacked( - "WitnetOracle: not implemented: 0x", + _revert(string(abi.encodePacked( + "not implemented: 0x", Witnet.toHexString(uint8(bytes1(msg.sig))), Witnet.toHexString(uint8(bytes1(msg.sig << 8))), Witnet.toHexString(uint8(bytes1(msg.sig << 16))), @@ -127,6 +132,14 @@ abstract contract WitnetOracleTrustableBase return bytes4(keccak256(abi.encode(address(this), block.chainid))); } + function class() + public view + virtual override(WitnetOracle, WitnetUpgradableBase) + returns (string memory) + { + return type(WitnetOracleTrustableBase).name; + } + function factory() virtual override public view returns (WitnetRequestFactory) { return __factory; } @@ -167,9 +180,9 @@ abstract contract WitnetOracleTrustableBase _newReporters = abi.decode(_newReportersRaw, (address[])); } else { // only owner can initialize: - require( + _require( msg.sender == _owner, - "WitnetOracle: not the owner" + "not the owner" ); // get reporters from _initData _newReporters = abi.decode(_initData, (address[])); @@ -179,22 +192,22 @@ abstract contract WitnetOracleTrustableBase __proxiable().codehash != bytes32(0) && __proxiable().codehash == codehash() ) { - revert("WitnetOracle: already upgraded"); + _revert("already upgraded"); } __proxiable().codehash = codehash(); - require( + _require( address(__factory).code.length > 0, - "WitnetOracle: inexistent factory" + "inexistent factory" ); - require( + _require( __factory.specs() == type(IWitnetRequestFactory).interfaceId, - "WitnetOracle: uncompliant factory" + "uncompliant factory" ); - require( + _require( address(__factory.witnet()) == address(this) && address(__factory.registry()) == address(registry), - "WitnetOracle: discordant factory" + "discordant factory" ); // Set reporters, if any @@ -226,9 +239,9 @@ abstract contract WitnetOracleTrustableBase returns (uint256) { uint16 _resultMaxSize = registry.lookupRadonRequestResultMaxSize(radHash); - require( + _require( _resultMaxSize > 0, - "WitnetOracleTrustableDefault: invalid RAD" + "invalid RAD" ); return estimateBaseFee( gasPrice, @@ -259,7 +272,16 @@ abstract contract WitnetOracleTrustableBase { return __storage().queries[_witnetQueryId]; } - + + /// @notice Gets the current EVM reward the report can claim, if not done yet. + function getQueryEvmReward(uint256 _witnetQueryId) + external view + virtual override + returns (uint256) + { + return __storage().queries[_witnetQueryId].request.evmReward; + } + /// @notice Retrieves the RAD hash and SLA parameters of the given query. /// @param _witnetQueryId The unique query identifier. function getQueryRequest(uint256 _witnetQueryId) @@ -276,7 +298,7 @@ abstract contract WitnetOracleTrustableBase function getQueryResponse(uint256 _witnetQueryId) public view virtual override - returns (WitnetV2.Response memory _response) + returns (WitnetV2.Response memory) { return WitnetOracleDataLib.seekQueryResponse(_witnetQueryId); } @@ -294,6 +316,16 @@ abstract contract WitnetOracleTrustableBase return WitnetOracleDataLib.seekQueryResponseStatus(_witnetQueryId); } + /// @notice Retrieves the CBOR-encoded buffer containing the Witnet-provided result to the given query. + /// @param _witnetQueryId The unique query identifier. + function getQueryResultCborBytes(uint256 _witnetQueryId) + external view + virtual override + returns (bytes memory) + { + return WitnetOracleDataLib.seekQueryResponse(_witnetQueryId).resultCborBytes; + } + /// @notice Gets error code identifying some possible failure on the resolution of the given query. /// @param _witnetQueryId The unique query identifier. function getQueryResultError(uint256 _witnetQueryId) @@ -538,9 +570,9 @@ abstract contract WitnetOracleTrustableBase returns (uint256) { // results cannot be empty: - require( + _require( _witnetQueryResultCborBytes.length != 0, - "WitnetOracleTrustableDefault: result cannot be empty" + "result cannot be empty" ); // do actual report and return reward transfered to the reproter: // solhint-disable not-rely-on-time @@ -575,15 +607,15 @@ abstract contract WitnetOracleTrustableBase returns (uint256) { // validate timestamp - require( + _require( _witnetQueryResultTimestamp > 0 && _witnetQueryResultTimestamp <= block.timestamp, - "WitnetOracleTrustableDefault: bad timestamp" + "bad timestamp" ); // results cannot be empty - require( + _require( _witnetQueryResultCborBytes.length != 0, - "WitnetOracleTrustableDefault: result cannot be empty" + "result cannot be empty" ); // do actual report and return reward transfered to the reproter: return __reportResultAndReward( @@ -623,7 +655,10 @@ abstract contract WitnetOracleTrustableBase ) { emit BatchReportError( _batchResults[_i].queryId, - "WitnetOracle: invalid report data" + string(abi.encodePacked( + class(), + "invalid report data" + )) ); } else { _batchReward += __reportResult( @@ -704,7 +739,7 @@ abstract contract WitnetOracleTrustableBase { _witnetQueryId = ++ __storage().nonce; //__newQueryId(_radHash, _packedSLA); WitnetV2.Request storage __request = WitnetOracleDataLib.seekQueryRequest(_witnetQueryId); - require(__request.requester == address(0), "WitnetOracle: already posted"); + _require(__request.requester == address(0), "already posted"); { __request.requester = msg.sender; __request.gasCallback = _callbackGasLimit; diff --git a/contracts/core/defaults/WitnetOracleTrustableDefault.sol b/contracts/core/defaults/WitnetOracleTrustableDefault.sol index 137d49ac..46ca011a 100644 --- a/contracts/core/defaults/WitnetOracleTrustableDefault.sol +++ b/contracts/core/defaults/WitnetOracleTrustableDefault.sol @@ -16,7 +16,7 @@ contract WitnetOracleTrustableDefault is WitnetOracleTrustableBase { - function class() virtual override external view returns (string memory) { + function class() virtual override public view returns (string memory) { return type(WitnetOracleTrustableDefault).name; } diff --git a/contracts/core/defaults/WitnetPriceFeedsDefault.sol b/contracts/core/defaults/WitnetPriceFeedsDefault.sol index 35433fac..63dbd04b 100644 --- a/contracts/core/defaults/WitnetPriceFeedsDefault.sol +++ b/contracts/core/defaults/WitnetPriceFeedsDefault.sol @@ -26,7 +26,7 @@ contract WitnetPriceFeedsDefault using WitnetV2 for WitnetV2.Response; using WitnetV2 for WitnetV2.RadonSLA; - function class() virtual override external view returns (string memory) { + function class() virtual override(WitnetFeeds, WitnetUpgradableBase) public view returns (string memory) { return type(WitnetPriceFeedsDefault).name; } @@ -332,6 +332,14 @@ contract WitnetPriceFeedsDefault Ownable2Step.acceptOwnership(); } + function baseFeeOverheadPercentage() + virtual override + external view + returns (uint16) + { + return __baseFeeOverheadPercentage; + } + function pendingOwner() virtual override (IWitnetFeedsAdmin, Ownable2Step) public view diff --git a/contracts/core/defaults/WitnetRequestBytecodesDefault.sol b/contracts/core/defaults/WitnetRequestBytecodesDefault.sol index 8938b9cd..07c41458 100644 --- a/contracts/core/defaults/WitnetRequestBytecodesDefault.sol +++ b/contracts/core/defaults/WitnetRequestBytecodesDefault.sol @@ -29,7 +29,11 @@ contract WitnetRequestBytecodesDefault using WitnetEncodingLib for Witnet.RadonSLA; using WitnetEncodingLib for Witnet.RadonDataTypes; - function class() virtual override external view returns (string memory) { + function class() + public view + virtual override(WitnetRequestBytecodes, WitnetUpgradableBase) + returns (string memory) + { return type(WitnetRequestBytecodesDefault).name; } diff --git a/contracts/core/defaults/WitnetRequestFactoryDefault.sol b/contracts/core/defaults/WitnetRequestFactoryDefault.sol index 38b577ae..b5e94849 100644 --- a/contracts/core/defaults/WitnetRequestFactoryDefault.sol +++ b/contracts/core/defaults/WitnetRequestFactoryDefault.sol @@ -188,8 +188,8 @@ contract WitnetRequestFactoryDefault } function class() - virtual override(WitnetRequestFactory, WitnetRequestTemplate) - external view + virtual override(WitnetRequestFactory, WitnetRequestTemplate, WitnetUpgradableBase) + public view returns (string memory) { if ( diff --git a/contracts/data/WitnetOracleDataLib.sol b/contracts/data/WitnetOracleDataLib.sol index d0300aab..a76370a1 100644 --- a/contracts/data/WitnetOracleDataLib.sol +++ b/contracts/data/WitnetOracleDataLib.sol @@ -70,11 +70,16 @@ library WitnetOracleDataLib { WitnetV2.QueryStatus _queryStatus = seekQueryStatus(queryId); if (_queryStatus == WitnetV2.QueryStatus.Finalized) { bytes storage __cborValues = data().queries[queryId].response.resultCborBytes; - // determine whether reported result is an error by peeking the first byte - return (__cborValues[0] == bytes1(0xd8) - ? WitnetV2.ResponseStatus.Error - : WitnetV2.ResponseStatus.Ready - ); + if (__cborValues.length > 0) { + // determine whether stored result is an error by peeking the first byte + return (__cborValues[0] == bytes1(0xd8) + ? WitnetV2.ResponseStatus.Error + : WitnetV2.ResponseStatus.Ready + ); + } else { + // the result is final but delivered to the requesting address + return WitnetV2.ResponseStatus.Delivered; + } } else if (_queryStatus == WitnetV2.QueryStatus.Posted) { return WitnetV2.ResponseStatus.Awaiting; } else if (_queryStatus == WitnetV2.QueryStatus.Reported) { @@ -112,13 +117,13 @@ library WitnetOracleDataLib { function notInStatusRevertMessage(WitnetV2.QueryStatus self) public pure returns (string memory) { if (self == WitnetV2.QueryStatus.Posted) { - return "WitnetOracle: query not in Posted status"; + return "query not in Posted status"; } else if (self == WitnetV2.QueryStatus.Reported) { - return "WitnetOracle: query not in Reported status"; + return "query not in Reported status"; } else if (self == WitnetV2.QueryStatus.Finalized) { - return "WitnetOracle: query not in Finalized status"; + return "query not in Finalized status"; } else { - return "WitnetOracle: bad mood"; + return "bad mood"; } } } diff --git a/contracts/interfaces/IWitnetFeedsAdmin.sol b/contracts/interfaces/IWitnetFeedsAdmin.sol index 990f2fbf..bc4c8107 100644 --- a/contracts/interfaces/IWitnetFeedsAdmin.sol +++ b/contracts/interfaces/IWitnetFeedsAdmin.sol @@ -7,6 +7,7 @@ import "../WitnetRequest.sol"; interface IWitnetFeedsAdmin { function acceptOwnership() external; + function baseFeeOverheadPercentage() external view returns (uint16); function deleteFeed(string calldata caption) external; function deleteFeeds() external; function owner() external view returns (address); diff --git a/contracts/interfaces/IWitnetOracle.sol b/contracts/interfaces/IWitnetOracle.sol index 7de5552f..b9825eb5 100644 --- a/contracts/interfaces/IWitnetOracle.sol +++ b/contracts/interfaces/IWitnetOracle.sol @@ -32,6 +32,9 @@ interface IWitnetOracle { /// @notice Gets the whole Query data contents, if any, no matter its current status. function getQuery(uint256 queryId) external view returns (WitnetV2.Query memory); + /// @notice Gets the current EVM reward the report can claim, if not done yet. + function getQueryEvmReward(uint256 queryId) external view returns (uint256); + /// @notice Retrieves the RAD hash and SLA parameters of the given query. /// @param queryId The unique query identifier. function getQueryRequest(uint256 queryId) external view returns (WitnetV2.Request memory); @@ -48,6 +51,10 @@ interface IWitnetOracle { /// @param queryId The unique query identifier. function getQueryResponseStatus(uint256 queryId) external view returns (WitnetV2.ResponseStatus); + /// @notice Retrieves the CBOR-encoded buffer containing the Witnet-provided result to the given query. + /// @param queryId The unique query identifier. + function getQueryResultCborBytes(uint256 queryId) external view returns (bytes memory); + /// @notice Gets error code identifying some possible failure on the resolution of the given query. /// @param queryId The unique query identifier. function getQueryResultError(uint256 queryId) external view returns (Witnet.ResultError memory); diff --git a/contracts/interfaces/IWitnetOracleEvents.sol b/contracts/interfaces/IWitnetOracleEvents.sol index 3ff8ffb1..81da5ac9 100644 --- a/contracts/interfaces/IWitnetOracleEvents.sol +++ b/contracts/interfaces/IWitnetOracleEvents.sol @@ -7,27 +7,37 @@ interface IWitnetOracleEvents { /// Emitted every time a new query containing some verified data request is posted to the WRB. event WitnetQuery( - uint256 indexed id, + uint256 id, uint256 evmReward, WitnetV2.RadonSLA witnetSLA ); /// Emitted when a query with no callback gets reported into the WRB. - event WitnetQueryResponse(uint256 indexed id, uint256 evmGasPrice); + event WitnetQueryResponse( + uint256 id, + uint256 evmGasPrice + ); /// Emitted when a query with a callback gets successfully reported into the WRB. - event WitnetQueryResponseDelivered(uint256 indexed id, uint256 evmGasPrice, uint256 evmCallbackGas); + event WitnetQueryResponseDelivered( + uint256 id, + uint256 evmGasPrice, + uint256 evmCallbackGas + ); /// Emitted when a query with a callback cannot get reported into the WRB. event WitnetQueryResponseDeliveryFailed( - uint256 indexed id, - bytes resultCborBytes, - uint256 evmGasPrice, - uint256 evmCallbackGas, - string evmCallbackRevertReason - ); + uint256 id, + bytes resultCborBytes, + uint256 evmGasPrice, + uint256 evmCallbackActualGas, + string evmCallbackRevertReason + ); /// Emitted when the reward of some not-yet reported query is upgraded. - event WitnetQueryRewardUpgraded(uint256 indexed id, uint256 evmReward); + event WitnetQueryRewardUpgraded( + uint256 id, + uint256 evmReward + ); } diff --git a/contracts/interfaces/IWitnetRandomness.sol b/contracts/interfaces/IWitnetRandomness.sol new file mode 100644 index 00000000..70018866 --- /dev/null +++ b/contracts/interfaces/IWitnetRandomness.sol @@ -0,0 +1,108 @@ +// SPDX-License-Identifier: MIT + +pragma solidity >=0.7.0 <0.9.0; +pragma experimental ABIEncoderV2; + +import "../WitnetOracle.sol"; + +/// @title The Witnet Randomness generator interface. +/// @author Witnet Foundation. +interface IWitnetRandomness { + + /// @notice Returns amount of wei required to be paid as a fee when requesting randomization with a + /// transaction gas price as the one given. + function estimateRandomizeFee(uint256 evmGasPrice) external view returns (uint256); + + /// @notice Retrieves the result of keccak256-hashing the given block number with the randomness value + /// @notice generated by the Witnet Oracle blockchain in response to the first non-errored randomize request solved + /// @notice after such block number. + /// @dev Reverts if: + /// @dev i. no `randomize()` was requested on neither the given block, nor afterwards. + /// @dev ii. the first non-errored `randomize()` request found on or after the given block is not solved yet. + /// @dev iii. all `randomize()` requests that took place on or after the given block were solved with errors. + /// @param blockNumber Block number from which the search will start. + function fetchRandomnessAfter(uint256 blockNumber) external view returns (bytes32); + + /// @notice Retrieves the actual random value, unique hash and timestamp of the witnessing commit/reveal act that took + /// @notice place in the Witnet Oracle blockchain in response to the first non-errored randomize request + /// @notice solved after the given block number. + /// @dev Reverts if: + /// @dev i. no `randomize()` was requested on neither the given block, nor afterwards. + /// @dev ii. the first non-errored `randomize()` request found on or after the given block is not solved yet. + /// @dev iii. all `randomize()` requests that took place on or after the given block were solved with errors. + /// @param blockNumber Block number from which the search will start. + /// @return witnetResultRandomness Random value provided by the Witnet blockchain and used for solving randomness after given block. + /// @return witnetResultTimestamp Timestamp at which the randomness value was generated by the Witnet blockchain. + /// @return witnetResultTallyHash Hash of the witnessing commit/reveal act that took place on the Witnet blockchain. + /// @return witnetResultFinalityBlock EVM block number from which the provided randomness can be considered to be final. + function fetchRandomnessAfterProof(uint256 blockNumber) external view returns ( + bytes32 witnetResultRandomness, + uint64 witnetResultTimestamp, + bytes32 witnetResultTallyHash, + uint256 witnetResultFinalityBlock + ); + + /// @notice Returns last block number on which a randomize was requested. + function getLastRandomizeBlock() external view returns (uint256); + + /// @notice Retrieves metadata related to the randomize request that got posted to the + /// @notice Witnet Oracle contract on the given block number. + /// @dev Returns zero values if no randomize request was actually posted on the given block. + /// @return witnetQueryId Identifier of the underlying Witnet query created on the given block number. + /// @return prevRandomizeBlock Block number in which a randomize request got posted just before this one. 0 if none. + /// @return nextRandomizeBlock Block number in which a randomize request got posted just after this one, 0 if none. + function getRandomizeData(uint256 blockNumber) external view returns ( + uint256 witnetQueryId, + uint256 prevRandomizeBlock, + uint256 nextRandomizeBlock + ); + + /// @notice Returns the number of the next block in which a randomize request was posted after the given one. + /// @param blockNumber Block number from which the search will start. + /// @return Number of the first block found after the given one, or `0` otherwise. + function getRandomizeNextBlock(uint256 blockNumber) external view returns (uint256); + + /// @notice Returns the number of the previous block in which a randomize request was posted before the given one. + /// @param blockNumber Block number from which the search will start. + /// @return First block found before the given one, or `0` otherwise. + function getRandomizePrevBlock(uint256 blockNumber) external view returns (uint256); + + /// @notice Gets current status of the first non-errored randomize request posted on or after the given block number. + /// @dev Possible values: + /// @dev - 0 -> Void: no randomize request was actually posted on or after the given block number. + /// @dev - 1 -> Awaiting: a randomize request was found but it's not yet solved by the Witnet blockchain. + /// @dev - 2 -> Ready: a successfull randomize value was reported and ready to be read. + /// @dev - 3 -> Error: all randomize resolutions after the given block were solved with errors. + /// @dev - 4 -> Finalizing: a randomize resolution has been reported from the Witnet blockchain, but it's not yet final. + function getRandomizeStatus(uint256 blockNumber) external view returns (WitnetV2.ResponseStatus); + + /// @notice Returns `true` only if a successfull resolution from the Witnet blockchain is found for the first + /// @notice non-errored randomize request posted on or after the given block number. + function isRandomized(uint256 blockNumber) external view returns (bool); + + /// @notice Generates a pseudo-random number uniformly distributed within the range [0 .. _range), by using + /// @notice the given `nonce` and the randomness returned by `getRandomnessAfter(blockNumber)`. + /// @dev Fails under same conditions as `getRandomnessAfter(uint256)` does. + /// @param range Range within which the uniformly-distributed random number will be generated. + /// @param nonce Nonce value enabling multiple random numbers from the same randomness value. + /// @param blockNumber Block number from which the search for the first randomize request solved aftewards will start. + function random(uint32 range, uint256 nonce, uint256 blockNumber) external view returns (uint32); + + /// @notice Requests the Witnet oracle to generate an EVM-agnostic and trustless source of randomness. + /// @dev Only one randomness request per block will be actually posted to the Witnet Oracle. + /// @dev Unused funds will be transfered back to the `msg.sender`. + /// @return Funds actually paid as randomize fee. + function randomize() external payable returns (uint256); + + /// @notice Returns address of the Witnet Oracle bridging contract being used for solving randomness requests. + function witnet() external view returns (WitnetOracle); + + /// @notice Returns the SLA parameters required for the Witnet Oracle blockchain to fulfill + /// @notice when solving randomness requests: + /// @notice - number of witnessing nodes contributing to randomness generation + /// @notice - reward in $nanoWIT received per witnessing node in the Witnet blockchain + function witnetQuerySLA() external view returns (WitnetV2.RadonSLA memory); + + /// @notice Returns the unique identifier of the Witnet-compliant data request being used for solving randomness. + function witnetRadHash() external view returns (bytes32); +} diff --git a/contracts/interfaces/IWitnetRandomnessAdmin.sol b/contracts/interfaces/IWitnetRandomnessAdmin.sol new file mode 100644 index 00000000..51999352 --- /dev/null +++ b/contracts/interfaces/IWitnetRandomnessAdmin.sol @@ -0,0 +1,15 @@ +// SPDX-License-Identifier: MIT + +pragma solidity >=0.8.0 <0.9.0; + +import "../libs/WitnetV2.sol"; + +interface IWitnetRandomnessAdmin { + function acceptOwnership() external; + function baseFeeOverheadPercentage() external view returns (uint16); + function owner() external view returns (address); + function pendingOwner() external returns (address); + function transferOwnership(address) external; + function settleBaseFeeOverheadPercentage(uint16) external; + function settleWitnetQuerySLA(WitnetV2.RadonSLA calldata) external; +} \ No newline at end of file diff --git a/contracts/interfaces/IWitnetRandomnessEvents.sol b/contracts/interfaces/IWitnetRandomnessEvents.sol new file mode 100644 index 00000000..456fcedb --- /dev/null +++ b/contracts/interfaces/IWitnetRandomnessEvents.sol @@ -0,0 +1,18 @@ +// SPDX-License-Identifier: MIT + +pragma solidity >=0.7.0 <0.9.0; +pragma experimental ABIEncoderV2; + +import "../libs/WitnetV2.sol"; + +/// @title The Witnet Randomness generator interface. +/// @author Witnet Foundation. +interface IWitnetRandomnessEvents { + event Randomizing( + uint256 blockNumber, + uint256 evmTxGasPrice, + uint256 evmRandomizeFee, + uint256 witnetQueryId, + WitnetV2.RadonSLA witnetQuerySLA + ); +} diff --git a/contracts/libs/Witnet.sol b/contracts/libs/Witnet.sol index 8fabc32a..a31618bc 100644 --- a/contracts/libs/Witnet.sol +++ b/contracts/libs/Witnet.sol @@ -280,7 +280,7 @@ library Witnet { enum RadonDataRequestMethods { /* 0 */ Unknown, /* 1 */ HttpGet, - /* 2 */ Rng, + /* 2 */ RNG, /* 3 */ HttpPost, /* 4 */ HttpHead } @@ -537,53 +537,7 @@ library Witnet { return (res, true); } } - - - // /// =============================================================================================================== - // /// --- 'Witnet.Request' helper methods --------------------------------------------------------------------------- - - // function packRequesterCallbackGasLimit(address requester, uint96 callbackGasLimit) internal pure returns (bytes32) { - // return bytes32(uint(bytes32(bytes20(requester))) | callbackGasLimit); - // } - - // function unpackRequester(Request storage self) internal view returns (address) { - // return address(bytes20(self.fromCallbackGas)); - // } - - // function unpackCallbackGasLimit(Request storage self) internal view returns (uint96) { - // return uint96(uint(self.fromCallbackGas)); - // } - - // function unpackRequesterAndCallbackGasLimit(Request storage self) internal view returns (address, uint96) { - // bytes32 _packed = self.fromCallbackGas; - // return (address(bytes20(_packed)), uint96(uint(_packed))); - // } - - // /// =============================================================================================================== - // /// --- 'Witnet.Response' helper methods -------------------------------------------------------------------------- - - // function packReporterEvmFinalityBlock(address reporter, uint256 evmFinalityBlock) internal pure returns (bytes32) { - // return bytes32(uint(bytes32(bytes20(reporter))) << 96 | uint96(evmFinalityBlock)); - // } - - // function unpackWitnetReporter(Response storage self) internal view returns (address) { - // return address(bytes20(self.fromFinality)); - // } - - // function unpackEvmFinalityBlock(Response storage self) internal view returns (uint256) { - // return uint(uint96(uint(self.fromFinality))); - // } - - // function unpackEvmFinalityBlock(bytes32 fromFinality) internal pure returns (uint256) { - // return uint(uint96(uint(fromFinality))); - // } - - // function unpackWitnetReporterAndEvmFinalityBlock(Response storage self) internal view returns (address, uint256) { - // bytes32 _packed = self.fromFinality; - // return (address(bytes20(_packed)), uint(uint96(uint(_packed)))); - // } - /// =============================================================================================================== /// --- 'Witnet.Result' helper methods ---------------------------------------------------------------------------- diff --git a/contracts/libs/WitnetEncodingLib.sol b/contracts/libs/WitnetEncodingLib.sol index b05156d8..6967464b 100644 --- a/contracts/libs/WitnetEncodingLib.sol +++ b/contracts/libs/WitnetEncodingLib.sol @@ -333,7 +333,7 @@ library WitnetEncodingLib { || method == Witnet.RadonDataRequestMethods.HttpPost || method == Witnet.RadonDataRequestMethods.HttpHead ) - || method == Witnet.RadonDataRequestMethods.Rng + || method == Witnet.RadonDataRequestMethods.RNG && bytes(url).length == 0 && headers.length == 0 && script.length >= 1 diff --git a/contracts/libs/WitnetV2.sol b/contracts/libs/WitnetV2.sol index 681ddba9..6d8cd7d1 100644 --- a/contracts/libs/WitnetV2.sol +++ b/contracts/libs/WitnetV2.sol @@ -45,7 +45,8 @@ library WitnetV2 { Awaiting, Ready, Error, - Finalizing + Finalizing, + Delivered } struct RadonSLA { @@ -90,40 +91,21 @@ library WitnetV2 { return self.witnessingFeeNanoWit * (self.committeeSize + 3); } - uint256 internal constant _WITNET_GENESIS_TIMESTAMP = 1602666045; - uint256 internal constant _WITNET_GENESIS_EPOCH_SECONDS = 45; - - uint256 internal constant _WITNET_2_0_EPOCH = 1234567; - uint256 internal constant _WITNET_2_0_EPOCH_SECONDS = 30; - uint256 internal constant _WITNET_2_0_TIMESTAMP = _WITNET_GENESIS_TIMESTAMP + _WITNET_2_0_EPOCH * _WITNET_GENESIS_EPOCH_SECONDS; - - function timestampToWitnetEpoch(uint _timestamp) internal pure returns (uint) { - if (_timestamp > _WITNET_2_0_TIMESTAMP ) { - return ( - _WITNET_2_0_EPOCH + ( - _timestamp - _WITNET_2_0_TIMESTAMP - ) / _WITNET_2_0_EPOCH_SECONDS - ); - } else if (_timestamp > _WITNET_GENESIS_TIMESTAMP) { - return ( - 1 + ( - _timestamp - _WITNET_GENESIS_TIMESTAMP - ) / _WITNET_GENESIS_EPOCH_SECONDS - ); - } else { - return 0; - } - } - function witnetEpochToTimestamp(uint _epoch) internal pure returns (uint) { - if (_epoch >= _WITNET_2_0_EPOCH) { - return ( - _WITNET_2_0_TIMESTAMP + ( - _epoch - _WITNET_2_0_EPOCH - ) * _WITNET_2_0_EPOCH_SECONDS - ); - } else { - return (_WITNET_GENESIS_TIMESTAMP + _epoch * _WITNET_GENESIS_EPOCH_SECONDS); - } + /// =============================================================================================================== + /// --- P-RNG generators ------------------------------------------------------------------------------------------ + + /// Generates a pseudo-random uint32 number uniformly distributed within the range `[0 .. range)`, based on + /// the given `nonce` and `seed` values. + function randomUniformUint32(uint32 range, uint256 nonce, bytes32 seed) + internal pure + returns (uint32) + { + uint256 _number = uint256( + keccak256( + abi.encode(seed, nonce) + ) + ) & uint256(2 ** 224 - 1); + return uint32((_number * range) >> 224); } -} \ No newline at end of file +} diff --git a/contracts/mocks/WitnetMockedOracle.sol b/contracts/mocks/WitnetMockedOracle.sol index fc92265d..39701641 100644 --- a/contracts/mocks/WitnetMockedOracle.sol +++ b/contracts/mocks/WitnetMockedOracle.sol @@ -8,6 +8,7 @@ import "./WitnetMockedRequestFactory.sol"; import "../core/defaults/WitnetOracleTrustableDefault.sol"; import "./WitnetMockedPriceFeeds.sol"; +import "./WitnetMockedRandomness.sol"; /// @title Mocked implementation of `WitnetOracle`. /// @dev TO BE USED ONLY ON DEVELOPMENT ENVIRONMENTS. diff --git a/contracts/mocks/WitnetMockedRandomness.sol b/contracts/mocks/WitnetMockedRandomness.sol new file mode 100644 index 00000000..b8e9d451 --- /dev/null +++ b/contracts/mocks/WitnetMockedRandomness.sol @@ -0,0 +1,18 @@ +// SPDX-License-Identifier: MIT + +pragma solidity >=0.7.0 <0.9.0; +pragma experimental ABIEncoderV2; + +import "./WitnetMockedOracle.sol"; +import "../apps/WitnetRandomnessV2.sol"; + +/// @title Mocked implementation of `WitnetRandomness`. +/// @dev TO BE USED ONLY ON DEVELOPMENT ENVIRONMENTS. +/// @dev ON SUPPORTED TESTNETS AND MAINNETS, PLEASE USE +/// @dev THE `WitnetRandomness` CONTRACT ADDRESS PROVIDED +/// @dev BY THE WITNET FOUNDATION. +contract WitnetMockedRandomness is WitnetRandomnessV2 { + constructor(WitnetMockedOracle _wrb) + WitnetRandomnessV2(_wrb) + {} +} diff --git a/migrations/addresses.json b/migrations/addresses.json index f786dbe6..2e597cfb 100644 --- a/migrations/addresses.json +++ b/migrations/addresses.json @@ -18,14 +18,15 @@ }, "optimism:sepolia": { "WitnetProxy": "0x58Bd0091748d0438f379bbf7D8BfF3a624CEbc3F", - "WitnetErrorsLib": "0x19900f2432ffC755224bd9e5411c5B8E07883eC8", + "WitnetErrorsLib": "0x4f2F381Ed2020095F1a1B5a0BDe5AB30da916BC9", "WitnetEncodingLib": "0xf321bcD29CFc6134c9Bf42F759f428F3A6010919", "WitnetPriceFeedsLib": "0x85aa4A0fDa112c47d4216448EE4D49Afd072675e", - "WitnetOracleDataLib": "0xBeFdE8285b613b17CE94705b53A57E5509D40397", - "WitnetOracleTrustableOvm2": "0x62cc7Ed0d00C7d34775A24D545Edf50721710962", - "WitnetPriceFeedsDefault": "0xF80CA682c967D8155de7ed9C9355dF661B08e5aD", - "WitnetRequestBytecodesDefault": "0x65fB7A0C98719e967546317222dfF90A73C42296", - "WitnetRequestFactoryDefault": "0x23873d132e4Ebc34C19F405cf2bFe31dA21630c2" + "WitnetOracleDataLib": "0x25d57Cf8a047B14172Ba2a929C1441E1A77c3f9D", + "WitnetOracleTrustableOvm2": "0xeE04B260D4aBE8ABb2BB174D33b2C8731d518DdE", + "WitnetPriceFeedsDefault": "0xa07f16b1312d68bcDed1AD1164F644b309F96d06", + "WitnetRandomnessV2": "0x3C027d97b18aE66a8EE4AeDD745f0224BFbf5B40", + "WitnetRequestBytecodesDefault": "0x2D8BCBC4F8c97CC227e770d95d19914324baBF2A", + "WitnetRequestFactoryDefault": "0x3D551165020a4014A8d5b9E4b73D2b3Dbe401546" }, "ten:testnet": { "WitnetProxy": "0x4563C77E85c1374754ffec3e6220d13Ddc8620eF", @@ -33,8 +34,8 @@ "WitnetEncodingLib": "0xf321bcD29CFc6134c9Bf42F759f428F3A6010919", "WitnetPriceFeedsLib": "0x85aa4A0fDa112c47d4216448EE4D49Afd072675e", "WitnetOracleDataLib": "0xBeFdE8285b613b17CE94705b53A57E5509D40397", - "WitnetOracleTrustableObscuro": "0x24988fCB6bb6bCB5933CEeB9a729807A4d8C260E", - "WitnetPriceFeedsDefault": "0x1DbD1691979335481E8376ccB3300725a7bD7f1d", + "WitnetOracleTrustableObscuro": "", + "WitnetPriceFeedsDefault": "", "WitnetRequestBytecodesDefault": "0x05e81E99ba36dC08079193D0Fad4Eb579E43ECE7", "WitnetRequestFactoryDefault": "0x8C4ed866d3418c05CA0325524624EC6CE52ADC44" } diff --git a/migrations/constructorArgs.json b/migrations/constructorArgs.json index bcf32d97..4bb2b6ca 100644 --- a/migrations/constructorArgs.json +++ b/migrations/constructorArgs.json @@ -6,11 +6,11 @@ "WitnetPriceFeedsDefault": "" }, "optimism:sepolia": { - "WitnetRequestBytecodesDefault": "0000000000000000000000000000000000000000000000000000000000000001322e302e372d3264386665303400000000000000000000000000000000000000", - "WitnetRequestFactoryDefault": "00000000000000000000000077703ae126b971c9946d562f41dd47071da00777000000000000000000000000000b61fe075f545fd37767f403916582759000000000000000000000000000000000000000000000000000000000000000000001322e302e372d3264386665303400000000000000000000000000000000000000", - "WitnetOracleTrustableOvm2": "000000000000000000000000000db36997af1f02209a6f995883b9b699900000000000000000000000000000000b61fe075f545fd37767f403916582759000000000000000000000000000000000000000000000000000000000000000000001322e302e372d3264386665303400000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000e3aa000000000000000000000000000000000000000000000000000000000000fef90000000000000000000000000000000000000000000000000000000000010faa0000000000000000000000000000000000000000000000000000000000004e20", - "WitnetPriceFeedsDefault": "00000000000000000000000077703ae126b971c9946d562f41dd47071da007770000000000000000000000000000000000000000000000000000000000000001322e302e372d3264386665303400000000000000000000000000000000000000", - "WitnetOracleTrustableObscuro": "" + "WitnetRandomnessV2": "00000000000000000000000077703ae126b971c9946d562f41dd47071da00777", + "WitnetRequestBytecodesDefault": "0000000000000000000000000000000000000000000000000000000000000001322e302e382d3465626434366300000000000000000000000000000000000000", + "WitnetRequestFactoryDefault": "00000000000000000000000077703ae126b971c9946d562f41dd47071da00777000000000000000000000000000b61fe075f545fd37767f403916582759000000000000000000000000000000000000000000000000000000000000000000001322e302e382d3465626434366300000000000000000000000000000000000000", + "WitnetOracleTrustableOvm2": "000000000000000000000000000db36997af1f02209a6f995883b9b699900000000000000000000000000000000b61fe075f545fd37767f403916582759000000000000000000000000000000000000000000000000000000000000000000001322e302e382d3465626434366300000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000e3aa000000000000000000000000000000000000000000000000000000000000fef90000000000000000000000000000000000000000000000000000000000010faa0000000000000000000000000000000000000000000000000000000000004e20", + "WitnetPriceFeedsDefault": "00000000000000000000000077703ae126b971c9946d562f41dd47071da007770000000000000000000000000000000000000000000000000000000000000001322e302e382d3465626434366300000000000000000000000000000000000000" }, "ten:testnet": { "WitnetRequestBytecodesDefault": "0000000000000000000000000000000000000000000000000000000000000001322e302e372d3033346663383100000000000000000000000000000000000000", diff --git a/migrations/scripts/3_core.js b/migrations/scripts/3_core.js index b2d001c7..c8605747 100644 --- a/migrations/scripts/3_core.js +++ b/migrations/scripts/3_core.js @@ -13,7 +13,9 @@ module.exports = async function (_, network, [, from]) { const specs = settings.getSpecs(network) const targets = settings.getArtifacts(network) - // Deploy/upgrade WitnetRequestBytecodes target implementation, if required + // ========================================================================== + // --- WitnetRequestBytecodes core implementation --------------------------- + await deploy({ network, targets, @@ -30,7 +32,9 @@ module.exports = async function (_, network, [, from]) { }, }) - // Deploy/upgrade WitnetRequestFactory target implementation, if required + // ========================================================================== + // --- WitnetRequestFactory core implementation ----------------------------- + await deploy({ network, targets, @@ -49,7 +53,9 @@ module.exports = async function (_, network, [, from]) { }, }) - // Deploy/upgrade WitnetOracle target implementation, if required + // ========================================================================== + // --- WitnetOracle core implementation --------------------------------- + await deploy({ network, targets, @@ -68,7 +74,9 @@ module.exports = async function (_, network, [, from]) { }, }) - // Deploy/upgrade WitnetPriceFeeds target implementation, if required + // ========================================================================== + // --- WitnetPriceFeeds core implementation --------------------------------- + await deploy({ network, targets, diff --git a/migrations/scripts/5_apps.js b/migrations/scripts/5_apps.js new file mode 100644 index 00000000..676f8c9f --- /dev/null +++ b/migrations/scripts/5_apps.js @@ -0,0 +1,126 @@ +const ethUtils = require("ethereumjs-util") +const settings = require("../../settings") +const utils = require("../../src/utils") + +const WitnetDeployer = artifacts.require("WitnetDeployer") + +module.exports = async function (_, network, [, from]) { + const specs = settings.getSpecs(network) + const targets = settings.getArtifacts(network) + + // Community appliances built on top of the Witnet Oracle are meant to be immutable, + // and therefore not upgradable. Appliances can only be deployed + // once all core Witnet Oracle artifacts get deployed and initialized. + + // ========================================================================== + // --- WitnetRandomnessV2 -------------------------------------------------- + + await deploy({ + network, + targets, + from: utils.isDryRun(network) ? from : specs.WitnetRandomness.from || from, + key: "WitnetRandomness", + specs: specs.WitnetRandomness, + intrinsics: { + types: ["address"], + values: [ + /* _witnet */ await determineProxyAddr(from, specs.WitnetOracle?.vanity || 3), + ], + }, + }) +} + +async function deploy (target) { + const { from, key, intrinsics, network, specs, targets } = target + const { libs, immutables, vanity } = specs + const salt = vanity ? "0x" + utils.padLeft(vanity.toString(16), "0", 64) : "0x0" + + const addresses = await utils.readJsonFromFile("./migrations/addresses.json") + if (!addresses[network]) addresses[network] = {} + + const selection = utils.getWitnetArtifactsFromArgs() + const artifact = artifacts.require(key) + const contract = artifacts.require(targets[key]) + if ( + addresses[network][targets[key]] === "" || + selection.includes(key) || + (libs && selection.filter(item => libs.includes(item)).length > 0) || + (!utils.isNullAddress(addresses[network][targets[key]]) && ( + await web3.eth.getCode(addresses[network][targets[key]])).length < 3 + ) + ) { + utils.traceHeader(`Deploying '${key}'...`) + console.info(" ", "> account: ", from) + console.info(" ", "> balance: ", web3.utils.fromWei(await web3.eth.getBalance(from), "ether"), "ETH") + const deployer = await WitnetDeployer.deployed() + let { types, values } = intrinsics + if (immutables?.types) types = [...types, ...immutables.types] + if (immutables?.values) values = [...values, ...immutables.values] + const constructorArgs = web3.eth.abi.encodeParameters(types, values) + if (constructorArgs.length > 2) { + console.info(" ", "> constructor types:", JSON.stringify(types)) + console.info(" ", "> constructor args: ", constructorArgs.slice(2)) + } + const bytecode = link(contract.toJSON().bytecode, libs, targets) + if (bytecode.indexOf("__") > -1) { + console.info(bytecode) + console.info("Error: Cannot deploy due to some missing libs") + process.exit(1) + } + const initCode = bytecode + constructorArgs.slice(2) + const addr = await deployer.determineAddr.call(initCode, salt, { from }) + const tx = await deployer.deploy(initCode, salt || "0x0", { from }) + utils.traceTx(tx) + if ((await web3.eth.getCode(addr)).length > 3) { + addresses[network][targets[key]] = addr + } else { + console.info(`Error: Contract was not deployed on expected address: ${addr}`) + process.exit(1) + } + // save addresses file if required + if (!utils.isDryRun(network)) { + await utils.overwriteJsonFile("./migrations/addresses.json", addresses) + const args = await utils.readJsonFromFile("./migrations/constructorArgs.json") + if (!args[network]) args[network] = {} + args[network][targets[key]] = constructorArgs.slice(2) + await utils.overwriteJsonFile("./migrations/constructorArgs.json", args) + } + } else if (addresses[network][key]) { + utils.traceHeader(`Skipped '${key}'`) + } + if (!utils.isNullAddress(addresses[network][targets[key]])) { + artifact.address = addresses[network][targets[key]] + contract.address = addresses[network][targets[key]] + for (const index in libs) { + const libname = libs[index] + const lib = artifacts.require(libname) + contract.link(lib) + console.info(" ", "> external library: ", `${libname}@${lib.address}`) + }; + const appliance = await artifact.deployed() + console.info(" ", "> appliance address: ", appliance.address) + console.info(" ", "> appliance class: ", await appliance.class({ from })) + console.info(" ", "> appliance codehash:", web3.utils.soliditySha3(await web3.eth.getCode(appliance.address))) + console.info(" ", "> appliance specs: ", await appliance.specs({ from })) + console.info() + } + return contract +} + +async function determineProxyAddr (from, nonce) { + const salt = nonce ? "0x" + ethUtils.setLengthLeft(ethUtils.toBuffer(nonce), 32).toString("hex") : "0x0" + const deployer = await WitnetDeployer.deployed() + return await deployer.determineProxyAddr.call(salt, { from }) +} + +function link (bytecode, libs, targets) { + if (libs && Array.isArray(libs) && libs.length > 0) { + for (const index in libs) { + const key = targets[libs[index]] + const lib = artifacts.require(key) + bytecode = bytecode.replaceAll(`__${key}${"_".repeat(38 - key.length)}`, lib.address.slice(2)) + console.info(" ", `> linked library: ${key} => ${lib.address}`) + } + } + return bytecode +} diff --git a/package.json b/package.json index 5d17885c..1e250eaa 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "witnet-solidity-bridge", - "version": "2.0.7", + "version": "2.0.8", "description": "Witnet Solidity Bridge contracts for EVM-compatible chains", "author": "Witnet Foundation ", "license": "MIT", diff --git a/scripts/vanity2gen.js b/scripts/vanity2gen.js index 4b3847d7..c05cb8ec 100644 --- a/scripts/vanity2gen.js +++ b/scripts/vanity2gen.js @@ -1,4 +1,3 @@ -const { assert } = require("chai") const create2 = require("./eth-create2") const fs = require("fs") const utils = require("../src/utils") @@ -8,7 +7,6 @@ const addresses = require("../migrations/addresses") module.exports = async function () { let artifact let count = 0 - let ecosystem = "default" let from let hits = 10 let offset = 0 @@ -23,10 +21,14 @@ module.exports = async function () { artifact = artifacts.require(args[index + 1]) } else if (argv === "--prefix") { prefix = args[index + 1].toLowerCase() - assert(web3.utils.isHexStrict(prefix), "--prefix: invalid hex string") + if (!web3.utils.isHexStrict(prefix)) { + throw new Error("--prefix: invalid hex string") + } } else if (argv === "--suffix") { suffix = args[index + 1].toLowerCase() - assert(web3.utils.isHexStrict(suffix), "--suffix: invalid hex string") + if (!web3.utils.isHexStrict(suffix)) { + throw new Error("--suffix: invalid hex string") + } } else if (argv === "--hits") { hits = parseInt(args[index + 1]) } else if (argv === "--network") { @@ -34,14 +36,16 @@ module.exports = async function () { } else if (argv === "--hexArgs") { hexArgs = args[index + 1].toLowerCase() if (hexArgs.startsWith("0x")) hexArgs = hexArgs.slice(2) - assert(web3.utils.isHexStrict("0x" + hexArgs), "--hexArgs: invalid hex string") + if (!web3.utils.isHexStrict("0x" + hexArgs)) { + throw new Error("--hexArgs: invalid hex string") + } } else if (argv === "--from") { from = args[index + 1] } return argv }) try { - from = from || addresses[ecosystem][network].WitnetDeployer + from = from || addresses[network]?.WitnetDeployer || addresses.default.WitnetDeployer } catch { console.error(`WitnetDeployer must have been previously deployed on network '${network}'.\n`) console.info("Usage:\n") diff --git a/scripts/verify-apps.js b/scripts/verify-apps.js new file mode 100644 index 00000000..cac4e373 --- /dev/null +++ b/scripts/verify-apps.js @@ -0,0 +1,28 @@ +#!/usr/bin/env node + +const settings = require("../settings") +const utils = require("../src/utils") + +if (process.argv.length < 3) { + console.error("\nUsage:\n\n$ node ./scripts/verify-apps.js : ...OPTIONAL_ARGS\n") + process.exit(0) +} + +const network = process.argv[2].toLowerCase().replaceAll(".", ":") + +const header = network.toUpperCase() + " APPS" +console.info() +console.info(header) +console.info("=".repeat(header.length)) +console.info() + +const artifacts = settings.getArtifacts(network) +const apps = [ + artifacts.WitnetRandomness, +] +const constructorArgs = require("../migrations/constructorArgs.json") +for (const index in apps) { + utils.traceVerify(network, `${apps[index]} --forceConstructorArgs string:${ + constructorArgs[network][apps[index]] + }`) +} diff --git a/scripts/verify-core.js b/scripts/verify-core.js index 797a8509..d0451de4 100644 --- a/scripts/verify-core.js +++ b/scripts/verify-core.js @@ -3,7 +3,7 @@ const utils = require("../src/utils") if (process.argv.length < 3) { - console.error("\nUsage:\n\n$ node ./scripts/verify-proxies.js : ...OPTIONAL_ARGS\n") + console.error("\nUsage:\n\n$ node ./scripts/verify-core.js : ...OPTIONAL_ARGS\n") process.exit(0) } diff --git a/scripts/verify-impls.js b/scripts/verify-impls.js index c888ea61..eccb7290 100644 --- a/scripts/verify-impls.js +++ b/scripts/verify-impls.js @@ -4,7 +4,7 @@ const settings = require("../settings") const utils = require("../src/utils") if (process.argv.length < 3) { - console.error("\nUsage:\n\n$ node ./scripts/verify-proxies.js : ...OPTIONAL_ARGS\n") + console.error("\nUsage:\n\n$ node ./scripts/verify-impls.js : ...OPTIONAL_ARGS\n") process.exit(0) } diff --git a/scripts/verify-libs.js b/scripts/verify-libs.js index 79e79aa8..a35c4d61 100644 --- a/scripts/verify-libs.js +++ b/scripts/verify-libs.js @@ -4,7 +4,7 @@ const settings = require("../settings") const utils = require("../src/utils") if (process.argv.length < 3) { - console.error("\nUsage:\n\n$ node ./scripts/verify-proxies.js : ...OPTIONAL_ARGS\n") + console.error("\nUsage:\n\n$ node ./scripts/verify-libs.js : ...OPTIONAL_ARGS\n") process.exit(0) } diff --git a/settings/artifacts.js b/settings/artifacts.js index eef7d170..a4778c97 100644 --- a/settings/artifacts.js +++ b/settings/artifacts.js @@ -2,6 +2,7 @@ module.exports = { default: { WitnetOracle: "WitnetOracleTrustableDefault", WitnetPriceFeeds: "WitnetPriceFeedsDefault", + WitnetRandomness: "WitnetRandomnessV2", WitnetRequestBytecodes: "WitnetRequestBytecodesDefault", WitnetRequestFactory: "WitnetRequestFactoryDefault", WitnetEncodingLib: "WitnetEncodingLib", diff --git a/settings/networks.js b/settings/networks.js index deb29af1..60140a84 100644 --- a/settings/networks.js +++ b/settings/networks.js @@ -294,10 +294,11 @@ module.exports = { "optimism:sepolia": { port: 8503, network_id: 11155420, + confirmations: 2, verify: { - apiKey: process.env.ETHERSCAN_API_KEY, - apiUrl: "https://optimism-sepolia.blockscout.com/api", - explorerUrl: "https://optimism-sepolia.blockscout.com/", + apiKey: process.env.ETHERSCAN_OPTIMISM_API_KEY, + apiUrl: "https://api-sepolia-optimistic.etherscan.io/api", + explorerUrl: "https://sepolia-optimism.etherscan.io/address", }, }, "optimism:mainnet": { diff --git a/settings/specs.js b/settings/specs.js index 95163c99..fd106e11 100644 --- a/settings/specs.js +++ b/settings/specs.js @@ -18,6 +18,9 @@ module.exports = { libs: ["WitnetPriceFeedsLib"], vanity: 1865150170, // 0x1111AbA2164AcdC6D291b08DfB374280035E1111 }, + WitnetRandomness: { + from: "0xF121b71715E71DDeD592F1125a06D4ED06F0694D", + }, WitnetRequestBytecodes: { libs: ["WitnetEncodingLib"], vanity: 6765579443, // 0x000B61Fe075F545fd37767f40391658275900000 diff --git a/src/index.js b/src/index.js index 1f475f7d..a69152a1 100644 --- a/src/index.js +++ b/src/index.js @@ -11,8 +11,9 @@ module.exports = { addresses[net], ) return { - WitnetPriceFeeds: merged?.WitnetPriceFeeds, WitnetOracle: merged?.WitnetOracle, + WitnetPriceFeeds: merged?.WitnetPriceFeeds, + WitnetRandomnessV2: merged?.WitnetRandomnessV2, } } else { return {} @@ -33,6 +34,7 @@ module.exports = { WitnetOracle: require("../artifacts/contracts/WitnetOracle.sol/WitnetOracle.json"), WitnetPriceFeeds: require("../artifacts/contracts/WitnetPriceFeeds.sol/WitnetPriceFeeds.json"), WitnetPriceRouteSolver: require("../artifacts/contracts/interfaces/IWitnetPriceSolver.sol/IWitnetPriceSolver.json"), + WitnetRandomness: require("../artifacts/contracts/WitnetRandomness.sol/WitnetRandomness.json"), WitnetRequest: require("../artifacts/contracts/WitnetRequest.sol/WitnetRequest.json"), WitnetRequestBytecodes: require("../artifacts/contracts/WitnetRequestBytecodes.sol/WitnetRequestBytecodes.json"), WitnetRequestFactory: require("../artifacts/contracts/WitnetRequestFactory.sol/WitnetRequestFactory.json"), diff --git a/test/witnet_bytecodes.test.js b/test/witnet_bytecodes.test.js index 825fc3b7..b1f21438 100644 --- a/test/witnet_bytecodes.test.js +++ b/test/witnet_bytecodes.test.js @@ -106,7 +106,7 @@ contract("WitnetRequestBytecodes", (accounts) => { let btcUsdPriceFeedHash context("verifyRadonRetrieval(..)", async () => { - context("WitnetV2.RadonDataRequestMethods.Rng", async () => { + context("WitnetV2.RadonDataRequestMethods.RNG", async () => { it("emits appropiate single event when verifying randomness data source for the first time", async () => { const tx = await bytecodes.verifyRadonRetrieval( 2, // requestMethod