diff --git a/.env_example b/.env_example index 803455b64..4dc50233d 100644 --- a/.env_example +++ b/.env_example @@ -1 +1,6 @@ +ARBISCAN_API_KEY= +ELASTOS_API_KEY= +ETHERSCAN_API_KEY= +MOONSCAN_API_KEY= +POLYGONSCAN_API_KEY= WITNET_EVM_REALM=default diff --git a/.gitignore b/.gitignore index 777d959ea..8ec0ffeb5 100644 --- a/.gitignore +++ b/.gitignore @@ -13,3 +13,4 @@ yarn-error.log package-lock.json *.tmp +.old \ No newline at end of file diff --git a/README.md b/README.md index 6baeb4572..8af5848c8 100644 --- a/README.md +++ b/README.md @@ -267,9 +267,9 @@ Please, have a look at the [`witnet/truffle-box`](https://github.com/witnet/truf ·······································|···························|·············|·············|·············|··············|·············· | WitnetProxy · upgradeTo · - · - · 129653 · 1 · - │ ·······································|···························|·············|·············|·············|··············|·············· -| WitnetRandomness · clone · - · - · 247765 · 7 · - │ +| WitnetRandomnessProxiable · clone · - · - · 247765 · 7 · - │ ·······································|···························|·············|·············|·············|··············|·············· -| WitnetRandomness · upgradeRandomizeFee · - · - · 28446 · 1 · - │ +| WitnetRandomnessProxiable · upgradeRandomizeFee · - · - · 28446 · 1 · - │ ·······································|···························|·············|·············|·············|··············|·············· | WitnetRequestBoardTrustableBoba · deleteQuery · - · - · 59706 · 3 · - │ ·······································|···························|·············|·············|·············|··············|·············· diff --git a/contracts/UsingWitnetV2.sol b/contracts/UsingWitnetV2.sol new file mode 100644 index 000000000..f1b74a42a --- /dev/null +++ b/contracts/UsingWitnetV2.sol @@ -0,0 +1,95 @@ +// SPDX-License-Identifier: MIT + +pragma solidity >=0.7.0 <0.9.0; +pragma experimental ABIEncoderV2; + +import "./WitnetRequestBoardV2.sol"; + +/// @title The UsingWitnetV2 contract +/// @dev Witnet-aware contracts can inherit from this contract in order to interact with Witnet. +/// @author The Witnet Foundation. +abstract contract UsingWitnetV2 { + + /// @dev Immutable address to the WitnetRequestBoardV2 contract. + WitnetRequestBoardV2 public immutable witnet; + + /// @dev Include an address to specify the WitnetRequestBoard entry point address. + /// @param _wrb The WitnetRequestBoard entry point address. + constructor(WitnetRequestBoardV2 _wrb) + { + require( + _wrb.class() == type(WitnetRequestBoardV2).interfaceId, + "UsingWitnetV2: uncompliant request board" + ); + witnet = _wrb; + } + + /// @dev Provides a convenient way for client contracts extending this to block the execution of the main logic of the + /// @dev contract until a particular data query has been successfully solved and reported by Witnet, + /// @dev either with an error or successfully. + modifier witnetQueryInStatus(bytes32 _queryHash, WitnetV2.QueryStatus _queryStatus) { + require( + witnet.checkQueryStatus(_queryHash) == _queryStatus, + "UsingWitnetV2: unexpected query status"); + _; + } + + /// @notice Returns EVM gas price within the context of current transaction. + function _getTxGasPrice() virtual internal view returns (uint256) { + return tx.gasprice; + } + + /// @notice Estimate the minimum reward in EVM/wei required for posting the described Witnet data request. + /// @param _radHash The hash of the query's data request part (previously registered in `witnet.registry()`). + /// @param _slaParams The query's SLA parameters. + /// @param _witEvmPrice The price of 1 nanoWit in EVM/wei to be used when estimating query rewards. + /// @param _maxEvmGasPrice The maximum EVM gas price willing to pay upon result reporting. + function _witnetEstimateQueryReward( + bytes32 _radHash, + WitnetV2.RadonSLAv2 memory _slaParams, + uint256 _witEvmPrice, + uint256 _maxEvmGasPrice + ) + internal view + returns (uint256) + { + return witnet.estimateQueryReward(_radHash, _slaParams, _witEvmPrice, _maxEvmGasPrice, 0); + } + + /// @notice Post some data request to be eventually solved by the Witnet decentralized oracle network. + /// @dev Enough EVM coins need to be provided as to cover for the implicit cost and bridge rewarding. + /// @param _radHash The hash of the query's data request part (previously registered in `witnet.registry()`). + /// @param _slaParams The query's SLA parameters. + /// @param _witEvmPrice The price of 1 nanoWit in EVM/wei to be used when estimating query rewards. + /// @param _maxEvmGasPrice The maximum EVM gas price willing to pay upon result reporting. + /// @return _queryHash The unique identifier of the new data query. + /// @return _queryReward The actual amount escrowed into the WRB as query reward. + function _witnetPostQuery( + bytes32 _radHash, + WitnetV2.RadonSLAv2 memory _slaParams, + uint256 _witEvmPrice, + uint256 _maxEvmGasPrice + ) + virtual internal + returns (bytes32 _queryHash, uint256 _queryReward) + { + _queryReward = _witnetEstimateQueryReward(_radHash, _slaParams, _witEvmPrice, _maxEvmGasPrice); + require(_queryReward <= msg.value, "UsingWitnetV2: EVM reward too low"); + _queryHash = witnet.postQuery{value: _queryReward}(_radHash, _slaParams); + } + + /// @notice Post some data request to be eventually solved by the Witnet decentralized oracle network. + /// @dev Enough EVM coins need to be provided as to cover for the implicit cost and bridge rewarding. + /// @dev Implicitly sets `tx.gasprice` as the maximum EVM gas price expected to be paid upon result reporting. + /// @param _radHash The hash of the query's data request part (previously registered in `witnet.registry()`). + /// @param _slaParams The query's SLA parameters. + /// @param _witEvmPrice The price of 1 nanoWit in EVM/wei to be used when estimating query rewards. + /// @return _queryHash The unique identifier of the new data query. + /// @return _queryReward The actual amount escrowed into the WRB as query reward. + function _witnetPostQuery(bytes32 _radHash, WitnetV2.RadonSLAv2 memory _slaParams, uint256 _witEvmPrice) + virtual internal + returns (bytes32 _queryHash, uint256 _queryReward) + { + return _witnetPostQuery(_radHash, _slaParams, _witEvmPrice, _getTxGasPrice()); + } +} diff --git a/contracts/WitnetBlocks.sol b/contracts/WitnetBlocks.sol new file mode 100644 index 000000000..214f81c30 --- /dev/null +++ b/contracts/WitnetBlocks.sol @@ -0,0 +1,11 @@ +// SPDX-License-Identifier: MIT + +pragma solidity >=0.7.0 <0.9.0; +pragma experimental ABIEncoderV2; + +import "./interfaces/V2/IWitnetBlocks.sol"; + +abstract contract WitnetBlocks + is + IWitnetBlocks +{} \ No newline at end of file diff --git a/contracts/WitnetPriceFeeds.sol b/contracts/WitnetPriceFeeds.sol index 5e801f364..60fda9612 100644 --- a/contracts/WitnetPriceFeeds.sol +++ b/contracts/WitnetPriceFeeds.sol @@ -4,12 +4,14 @@ pragma solidity >=0.8.0 <0.9.0; import "ado-contracts/contracts/interfaces/IERC2362.sol"; import "./interfaces/V2/IWitnetPriceFeeds.sol"; +import "./interfaces/V2/IWitnetPriceSolverDeployer.sol"; import "./WitnetFeeds.sol"; abstract contract WitnetPriceFeeds is IERC2362, IWitnetPriceFeeds, + IWitnetPriceSolverDeployer, WitnetFeeds { constructor(WitnetRequestBoard _wrb) diff --git a/contracts/WitnetRequestBoardV2.sol b/contracts/WitnetRequestBoardV2.sol new file mode 100644 index 000000000..e2cabd47d --- /dev/null +++ b/contracts/WitnetRequestBoardV2.sol @@ -0,0 +1,36 @@ +// SPDX-License-Identifier: MIT + +pragma solidity >=0.7.0 <0.9.0; +pragma experimental ABIEncoderV2; + +import "./WitnetBlocks.sol"; +import "./WitnetBytecodes.sol"; +import "./WitnetRequestFactory.sol"; + +/// @title Witnet Request Board V2 functionality base contract. +/// @author The Witnet Foundation. +abstract contract WitnetRequestBoardV2 + is + IWitnetRequestBoardV2 +{ + WitnetBlocks immutable public blocks; + WitnetRequestFactory immutable public factory; + WitnetBytecodes immutable public registry; + constructor ( + WitnetBlocks _blocks, + WitnetRequestFactory _factory + ) + { + require( + _blocks.class() == type(WitnetBlocks).interfaceId, + "WitnetRequestBoardV2: uncompliant blocks" + ); + require( + _factory.class() == type(WitnetRequestFactory).interfaceId, + "WitnetRequestBoardV2: uncompliant factory" + ); + blocks = _blocks; + factory = _factory; + registry = _factory.registry(); + } +} \ No newline at end of file diff --git a/contracts/data/WitnetBlocksData.sol b/contracts/data/WitnetBlocksData.sol new file mode 100644 index 000000000..eccd06260 --- /dev/null +++ b/contracts/data/WitnetBlocksData.sol @@ -0,0 +1,47 @@ +// SPDX-License-Identifier: MIT + +pragma solidity >=0.8.0 <0.9.0; + +import "../libs/WitnetV2.sol"; + +/// @title WitnetBlocks data model. +/// @author The Witnet Foundation. +abstract contract WitnetBlocksData { + + bytes32 private constant _WITNET_BLOCKS_DATA_SLOTHASH = + /* keccak256("io.witnet.blocks.data") */ + 0x28b1d7e478138a94698f82768889fd6edf6b777bb6815c200552870d3e78ffb5; + + struct Storage { + uint256 lastBeaconIndex; + uint256 nextBeaconIndex; + uint256 latestBeaconIndex; + mapping (/* beacon index */ uint256 => WitnetV2.Beacon) beacons; + mapping (/* beacon index */ uint256 => uint256) beaconSuccessorOf; + mapping (/* beacon index */ uint256 => BeaconTracks) beaconTracks; + } + + struct BeaconTracks { + mapping (bytes32 => bool) disputed; + bytes32[] queries; + uint256 offset; + } + + // ================================================================================================ + // --- Internal functions ------------------------------------------------------------------------- + + /// @notice Returns storage pointer to where Storage data is located. + function __blocks() + internal pure + returns (Storage storage _ptr) + { + assembly { + _ptr.slot := _WITNET_BLOCKS_DATA_SLOTHASH + } + } + + function __tracks_(uint256 beaconIndex) internal view returns (BeaconTracks storage) { + return __blocks().beaconTracks[beaconIndex]; + } + +} \ No newline at end of file diff --git a/contracts/data/WitnetRequestBoardV2Data.sol b/contracts/data/WitnetRequestBoardV2Data.sol new file mode 100644 index 000000000..4539c9201 --- /dev/null +++ b/contracts/data/WitnetRequestBoardV2Data.sol @@ -0,0 +1,63 @@ +// SPDX-License-Identifier: MIT + +pragma solidity >=0.7.0 <0.9.0; + +import "../libs/WitnetV2.sol"; +import "../interfaces/V2/IWitnetBlocks.sol"; + +/// @title Witnet Request Board base data model. +/// @author The Witnet Foundation. +abstract contract WitnetRequestBoardV2Data { + + bytes32 internal constant _WITNET_BOARD_DATA_SLOTHASH = + /* keccak256("io.witnet.boards.data.v2") */ + 0xfeed002ff8a708dcba69bac2a8e829fd61fee551b9e9fc0317707d989cb0fe53; + + struct Storage { + mapping (address => Escrow) escrows; + mapping (/* queryHash */ bytes32 => WitnetV2.Query) queries; + mapping (/* tallyHash */ bytes32 => Suitor) suitors; + } + + struct Suitor { + uint256 index; + bytes32 queryHash; + } + + struct Escrow { + uint256 atStake; + uint256 balance; + } + + /// Asserts the given query was previously posted but not yet deleted. + modifier queryExists(bytes32 queryHash) { + require(__query_(queryHash).from != address(0), "WitnetRequestBoardV2Data: empty query"); + _; + } + + + // ================================================================================================================ + // --- Internal functions ----------------------------------------------------------------------------------------- + + /// Gets query storage by query id. + function __query_(bytes32 queryHash) internal view returns (WitnetV2.Query storage) { + return __storage().queries[queryHash]; + } + + /// Gets the Witnet.Request part of a given query. + function __request_(bytes32 queryHash) internal view returns (WitnetV2.QueryRequest storage) { + return __query_(queryHash).request; + } + + /// Gets the Witnet.Result part of a given query. + function __report_(bytes32 queryHash) internal view returns (WitnetV2.QueryReport storage) { + return __query_(queryHash).report; + } + + /// Returns storage pointer to contents of 'WitnetBoardState' struct. + function __storage() internal pure returns (Storage storage _ptr) { + assembly { + _ptr.slot := _WITNET_BOARD_DATA_SLOTHASH + } + } +} diff --git a/contracts/impls/WitnetUpgradableBase.sol b/contracts/impls/WitnetUpgradableBase.sol index 5d6bc17a6..89056edd4 100644 --- a/contracts/impls/WitnetUpgradableBase.sol +++ b/contracts/impls/WitnetUpgradableBase.sol @@ -43,6 +43,8 @@ abstract contract WitnetUpgradableBase revert("WitnetUpgradableBase: not implemented"); } + function class() virtual external view returns (bytes4); + // ================================================================================================================ // --- Overrides IERC165 interface -------------------------------------------------------------------------------- diff --git a/contracts/impls/apps/WitnetPriceFeedsUpgradable.sol b/contracts/impls/apps/WitnetPriceFeedsUpgradable.sol index 81980940b..ddba64647 100644 --- a/contracts/impls/apps/WitnetPriceFeedsUpgradable.sol +++ b/contracts/impls/apps/WitnetPriceFeedsUpgradable.sol @@ -3,28 +3,21 @@ pragma solidity >=0.7.0 <0.9.0; pragma experimental ABIEncoderV2; -import "ado-contracts/contracts/interfaces/IERC2362.sol"; - -import "../../WitnetFeeds.sol"; +import "../../WitnetPriceFeeds.sol"; import "../../data/WitnetPriceFeedsData.sol"; import "../../impls/WitnetUpgradableBase.sol"; -import "../../interfaces/V2/IWitnetPriceFeeds.sol"; -import "../../interfaces/V2/IWitnetPriceSolver.sol"; -import "../../libs/Slices.sol"; +import "../../interfaces/V2/IWitnetPriceSolver.sol"; +import "../../libs/WitnetPriceFeedsLib.sol"; /// @title WitnetPriceFeedsUpgradable: ... /// @author Witnet Foundation. contract WitnetPriceFeedsUpgradable is - IERC2362, - IWitnetPriceFeeds, - WitnetFeeds, + WitnetPriceFeeds, WitnetPriceFeedsData, WitnetUpgradableBase { - using Slices for string; - using Slices for Slices.Slice; using Witnet for Witnet.Result; using WitnetV2 for WitnetV2.RadonSLA; @@ -33,11 +26,7 @@ contract WitnetPriceFeedsUpgradable bool _upgradable, bytes32 _version ) - WitnetFeeds( - _wrb, - WitnetV2.RadonDataTypes.Integer, - "Price-" - ) + WitnetPriceFeeds(_wrb) WitnetUpgradableBase( _upgradable, _version, @@ -71,6 +60,10 @@ contract WitnetPriceFeedsUpgradable } } + function class() virtual override external pure returns (bytes4) { + return type(WitnetPriceFeeds).interfaceId; + } + // ================================================================================================================ // --- Overrides 'Upgradeable' ------------------------------------------------------------------------------------- @@ -100,11 +93,12 @@ contract WitnetPriceFeedsUpgradable ); if (__storage().defaultSlaHash == 0) { settleDefaultRadonSLA(WitnetV2.RadonSLA({ - numWitnesses: 7, + numWitnesses: 5, witnessCollateral: 15 * 10 ** 9, - witnessReward: 15* 10 ** 7, + witnessReward: 15 * 10 ** 7, minerCommitRevealFee: 10 ** 7, - minConsensusPercentage: 51 + minConsensusPercentage: 51, + minMinerFee: 0 })); } __proxiable().implementation = base(); @@ -588,6 +582,33 @@ contract WitnetPriceFeedsUpgradable } + // ================================================================================================================ + // --- Implements 'IWitnetPriceSolverDeployer' --------------------------------------------------------------------- + + function deployPriceSolver(bytes calldata initcode, bytes calldata constructorParams) + virtual override + external + onlyOwner + returns (address _solver) + { + _solver = WitnetPriceFeedsLib.deployPriceSolver(initcode, constructorParams); + emit WitnetPriceSolverDeployed( + msg.sender, + _solver, + _solver.codehash, + constructorParams + ); + } + + function determinePriceSolverAddress(bytes calldata initcode, bytes calldata constructorParams) + virtual override + public view + returns (address _address) + { + return WitnetPriceFeedsLib.determinePriceSolverAddress(initcode, constructorParams); + } + + // ================================================================================================================ // --- Implements 'IERC2362' -------------------------------------------------------------------------------------- @@ -641,18 +662,13 @@ contract WitnetPriceFeedsUpgradable function _validateCaption(string calldata caption) internal view returns (uint8) { - require( - bytes6(bytes(caption)) == bytes6(__prefix), - "WitnetPriceFeedsUpgradable: bad caption prefix" - ); - Slices.Slice memory _caption = caption.toSlice(); - Slices.Slice memory _delim = string("-").toSlice(); - string[] memory _parts = new string[](_caption.count(_delim) + 1); - for (uint _ix = 0; _ix < _parts.length; _ix ++) { - _parts[_ix] = _caption.split(_delim).toString(); + try WitnetPriceFeedsLib.validateCaption(__prefix, caption) returns (uint8 _decimals) { + return _decimals; + } catch Error(string memory reason) { + revert(string(abi.encodePacked( + "WitnetPriceFeedsUpgradable: ", + reason + ))); } - (uint _decimals, bool _success) = Witnet.tryUint(_parts[_parts.length - 1]); - require(_success, "WitnetPriceFeedsUpgradable: bad decimals"); - return uint8(_decimals); } } diff --git a/contracts/impls/apps/WitnetPriceRouter.sol b/contracts/impls/apps/WitnetPriceRouter.sol index 48fd600df..65e6348e1 100644 --- a/contracts/impls/apps/WitnetPriceRouter.sol +++ b/contracts/impls/apps/WitnetPriceRouter.sol @@ -39,6 +39,10 @@ contract WitnetPriceRouter ) {} + function class() virtual override external pure returns (bytes4) { + return type(IWitnetPriceRouter).interfaceId; + } + // ================================================================================================================ // --- Overrides 'Upgradeable' ------------------------------------------------------------------------------------- diff --git a/contracts/impls/apps/WitnetRandomnessProxiable.sol b/contracts/impls/apps/WitnetRandomnessProxiable.sol index 23aadab2e..fc29e84fe 100644 --- a/contracts/impls/apps/WitnetRandomnessProxiable.sol +++ b/contracts/impls/apps/WitnetRandomnessProxiable.sol @@ -66,6 +66,10 @@ contract WitnetRandomnessProxiable witnetRandomnessRequest = _request; } + function class() virtual override external pure returns (bytes4) { + return type(WitnetRandomness).interfaceId; + } + /// @notice Initializes a cloned instance. /// @dev Every cloned instance can only get initialized once. function initializeClone(bytes memory _initData) diff --git a/contracts/impls/core/WitnetBlocksDefault.sol b/contracts/impls/core/WitnetBlocksDefault.sol new file mode 100644 index 000000000..50e69d72f --- /dev/null +++ b/contracts/impls/core/WitnetBlocksDefault.sol @@ -0,0 +1,296 @@ +// SPDX-License-Identifier: MIT + +pragma solidity >=0.8.4 <0.9.0; + +import "../WitnetUpgradableBase.sol"; +import "../../WitnetBlocks.sol"; +import "../../data/WitnetBlocksData.sol"; + +/// @title WitnetBlocks decentralized block relaying contract +/// @author The Witnet Foundation +contract WitnetBlocksDefault + is + WitnetBlocks, + WitnetBlocksData, + WitnetUpgradableBase +{ + using WitnetV2 for WitnetV2.Beacon; + + IWitnetRequestBoardV2 immutable public override board; + uint256 immutable internal __genesisIndex; + + uint256 immutable public override ROLLUP_DEFAULT_PENALTY_WEI; + uint256 immutable public override ROLLUP_MAX_GAS; + + constructor( + address _counterFactualAddress, + WitnetV2.Beacon memory _genesis, + uint256 _rollupDefaultPenaltyWei, + uint256 _rollupMaxGas, + bool _upgradable, + bytes32 _versionTag + ) + WitnetUpgradableBase( + _upgradable, + _versionTag, + "io.witnet.proxiable.blocks" + ) + { + assert(_counterFactualAddress != address(0)); + assert(_rollupDefaultPenaltyWei > 0); + assert(_rollupMaxGas > 0); + board = IWitnetRequestBoardV2(_counterFactualAddress); + __genesisIndex = _genesis.index; + __blocks().beacons[__genesisIndex] = _genesis; + __blocks().lastBeaconIndex = _genesis.index; + ROLLUP_DEFAULT_PENALTY_WEI = _rollupDefaultPenaltyWei; + ROLLUP_MAX_GAS = _rollupMaxGas; + } + + + // ================================================================================================================ + // --- 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 initdata) + override public + 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("WitnetBlocksDefault: not the owner"); + } + } + require( + __proxiable().implementation != base(), + "WitnetBlocksDefault: already initialized" + ); + if (initdata.length > 0) { + WitnetV2.Beacon memory _checkpoint = abi.decode(initdata, (WitnetV2.Beacon)); + uint256 _lastBeaconIndex = __blocks().lastBeaconIndex; + uint256 _nextBeaconIndex = __blocks().nextBeaconIndex; + require( + _checkpoint.index > _lastBeaconIndex, + "WitnetBlocksDefault: cannot rollback" + ); + require( + _nextBeaconIndex == 0 || _checkpoint.index < _nextBeaconIndex, + "WitnetBlocksDefault: pending rollups" + ); + __blocks().lastBeaconIndex = _checkpoint.index; + __blocks().beacons[_checkpoint.index] = _checkpoint; + emit FastForward(msg.sender, _checkpoint.index, _lastBeaconIndex); + } + __proxiable().implementation = base(); + emit Upgraded(msg.sender, base(), codehash(), version()); + } + + /// Tells whether provided address could eventually upgrade the contract. + function isUpgradableFrom(address _from) external view override returns (bool) { + return ( + // false if the WRB is intrinsically not upgradable, or `_from` is no owner + isUpgradable() + && _from == owner() + ); + } + + + // ================================================================================================================ + // --- Implementation of 'IWitnetBlocks' -------------------------------------------------------------------------- + + function class() virtual override(WitnetUpgradableBase, IWitnetBlocks) external pure returns (bytes4) { + return type(IWitnetBlocks).interfaceId; + } + + function genesis() + virtual override + external view + returns (WitnetV2.Beacon memory) + { + return __blocks().beacons[__genesisIndex]; + } + + function getBeaconDisputedQueries(uint256 beaconIndex) + virtual override + external view + returns (bytes32[] memory) + { + return __tracks_(beaconIndex).queries; + } + + function getCurrentBeaconIndex() + virtual override + public view + returns (uint256) + { + // solhint-disable not-rely-on-time + return WitnetV2.beaconIndexFromTimestamp(block.timestamp); + } + + function getCurrentEpoch() + virtual override + public view + returns (uint256) + { + // solhint-disable not-rely-on-time + return WitnetV2.epochFromTimestamp(block.timestamp); + } + + function getLastBeacon() + virtual override + external view + returns (WitnetV2.Beacon memory _beacon) + { + return __blocks().beacons[__blocks().lastBeaconIndex]; + } + + function getLastBeaconEpoch() + virtual override + external view + returns (uint256) + { + return getLastBeaconIndex() * 10; + } + + function getLastBeaconIndex() + virtual override + public view + returns (uint256) + { + return __blocks().lastBeaconIndex; + } + + function getNextBeaconIndex() + virtual override + external view + returns (uint256) + { + return __blocks().nextBeaconIndex; + } + + function __insertBeaconSuccessor(uint nextBeaconIndex, uint tallyBeaconIndex) + virtual internal + returns (uint256) + { + uint _currentSuccessor = __blocks().beaconSuccessorOf[nextBeaconIndex]; + if (_currentSuccessor == 0) { + __blocks().beaconSuccessorOf[nextBeaconIndex] = tallyBeaconIndex; + return tallyBeaconIndex; + } else if (_currentSuccessor >= tallyBeaconIndex) { + __blocks().beaconSuccessorOf[tallyBeaconIndex] = _currentSuccessor; + return tallyBeaconIndex; + } else { + return __insertBeaconSuccessor(_currentSuccessor, tallyBeaconIndex); + } + } + + function disputeQuery(bytes32 queryHash, uint256 tallyBeaconIndex) + virtual override + external + { + require( + msg.sender == address(board), + "WitnetBlocksDefault: unauthorized" + ); + if (tallyBeaconIndex > __blocks().lastBeaconIndex) { + if (__blocks().nextBeaconIndex > __blocks().lastBeaconIndex) { + // pending rollup + if (tallyBeaconIndex < __blocks().nextBeaconIndex) { + __blocks().beaconSuccessorOf[tallyBeaconIndex] = __blocks().nextBeaconIndex; + __blocks().nextBeaconIndex = tallyBeaconIndex; + } else if (tallyBeaconIndex > __blocks().nextBeaconIndex) { + __blocks().nextBeaconIndex = __insertBeaconSuccessor(__blocks().nextBeaconIndex, tallyBeaconIndex); + if (tallyBeaconIndex > __blocks().latestBeaconIndex) { + __blocks().latestBeaconIndex = tallyBeaconIndex; + } + } + } else { + // settle next rollup + __blocks().nextBeaconIndex = tallyBeaconIndex; + __blocks().latestBeaconIndex = tallyBeaconIndex; + } + BeaconTracks storage __tracks = __tracks_(tallyBeaconIndex); + if (!__tracks.disputed[queryHash]) { + __tracks.disputed[queryHash] = true; + __tracks.queries.push(queryHash); + } + } else { + revert("WitnetBlocksDefault: too late dispute"); + } + } + + function rollupTallyHashes( + WitnetV2.FastForward[] calldata ffs, + bytes32[] calldata tallyHashes, + uint256 tallyOffset, + uint256 tallyLength + ) + external + // TODO: modifiers + returns (uint256 weiReward) + { + uint _nextBeaconIndex = __blocks().nextBeaconIndex; + uint _lastBeaconIndex = __blocks().lastBeaconIndex; + require( + ffs.length >= 1 + && ffs[ffs.length - 1].next.index <= _nextBeaconIndex, + "WitnetBlocksDefault: misleading rollup" + ); + if (_nextBeaconIndex > _lastBeaconIndex) { + WitnetV2.Beacon memory _lastBeacon = __blocks().beacons[_lastBeaconIndex]; + for (uint _ix = 0; _ix < ffs.length; _ix ++) { + _lastBeacon = _lastBeacon.verifyFastForward(ffs[_ix ++]); + } + emit FastForward( + msg.sender, + _lastBeacon.index, + _lastBeaconIndex + ); + { + __blocks().beaconSuccessorOf[_lastBeaconIndex] = _lastBeacon.index; + _lastBeaconIndex = _lastBeacon.index; + __blocks().lastBeaconIndex = _lastBeaconIndex; + __blocks().beacons[_lastBeaconIndex] = _lastBeacon; + } + } + if (_nextBeaconIndex == _lastBeaconIndex) { + BeaconTracks storage __tracks = __tracks_(_nextBeaconIndex); + require( + tallyOffset == __tracks.offset + && tallyOffset + tallyLength == tallyHashes.length, + "WitnetBlocksDefault: out of range" + ); + require( + WitnetV2.merkle(tallyHashes) == __blocks().beacons[_nextBeaconIndex].ddrTallyRoot, + "WitnetBlocksDefault: invalid tallies" + ); + uint _maxTallyOffset = tallyOffset + tallyLength; + for (; tallyOffset < _maxTallyOffset; tallyOffset ++) { + (bytes32 _queryHash, uint256 _queryStakes) = board.determineQueryTallyHash(tallyHashes[tallyOffset]); + require( + __tracks.disputed[_queryHash], + "WitnetBlocksDefault: already judged" + ); + __tracks.disputed[_queryHash] = false; + weiReward += _queryStakes; + } + if (tallyOffset == tallyHashes.length) { + __blocks().nextBeaconIndex = __blocks().beaconSuccessorOf[_nextBeaconIndex]; + } else { + __tracks.offset = tallyOffset; + } + emit Rollup(msg.sender, _nextBeaconIndex, tallyOffset, weiReward); + } else { + revert("WitnetBlocksDefault: no pending rollup"); + } + } +} diff --git a/contracts/impls/core/WitnetBytecodesDefault.sol b/contracts/impls/core/WitnetBytecodesDefault.sol index aa106d93f..5989d40ab 100644 --- a/contracts/impls/core/WitnetBytecodesDefault.sol +++ b/contracts/impls/core/WitnetBytecodesDefault.sol @@ -48,6 +48,10 @@ contract WitnetBytecodesDefault revert("WitnetBytecodesDefault: no transfers"); } + function class() virtual override external pure returns (bytes4) { + return type(WitnetBytecodes).interfaceId; + } + // ================================================================================================================ // --- Overrides IERC165 interface -------------------------------------------------------------------------------- @@ -170,7 +174,7 @@ contract WitnetBytecodesDefault } function bytecodeOf(bytes32 _radHash, bytes32 _slaHash) - external view + public view returns (bytes memory) { WitnetV2.RadonSLA storage __sla = __database().slas[_slaHash]; @@ -185,6 +189,36 @@ contract WitnetBytecodesDefault ); } + function fetchBytecodeWitFeesOf(bytes32 _radHash, bytes32 _slaHash) + external view returns ( + uint256 _witMinMinerFee, + uint256 _witWitnessingFee, + bytes memory _radSlabytecode + ) + { + _radSlabytecode = bytecodeOf(_radHash, _slaHash); + WitnetV2.RadonSLA memory _sla = __database().slas[_slaHash]; // TODO: _unpackRadonSAL(_slaHash); + _witMinMinerFee = _sla.minMinerFee; + _witWitnessingFee = _sla.numWitnesses * (_sla.witnessReward + _sla.minerCommitRevealFee * 2); + } + + function fetchMaxResultSizeWitRewardOf(bytes32 _radHash, bytes32 _slaHash) + external view + virtual override + returns (uint256 _maxResultSize, uint256 _witReward) + { + _maxResultSize = __requests(_radHash).resultMaxSize; + if (_maxResultSize < 32) _maxResultSize = 32; + WitnetV2.RadonSLA memory _sla = __database().slas[_slaHash]; // TODO: _unpackRadonSLA(_slaHash); + _witReward = ( + _sla.minMinerFee + + _sla.numWitnesses * ( + _sla.witnessReward + + _sla.minerCommitRevealFee * 2 + ) + ); + } + function hashOf( bytes32[] calldata _retrievalsIds, bytes32 _aggregatorId, @@ -517,12 +551,12 @@ contract WitnetBytecodesDefault // Check that at least one source is provided; if (_retrievalsIds.length == 0) { - revert WitnetV2.RadonRequestNoSources(); + revert WitnetEncodingLib.RadonRequestNoSources(); } // Check that number of args arrays matches the number of sources: if ( _retrievalsIds.length != _args.length) { - revert WitnetV2.RadonRequestSourcesArgsMismatch( + revert WitnetEncodingLib.RadonRequestSourcesArgsMismatch( _retrievalsIds.length, _args.length ); @@ -532,7 +566,7 @@ contract WitnetBytecodesDefault WitnetV2.RadonReducer memory _aggregator = __database().reducers[_aggregatorId]; WitnetV2.RadonReducer memory _tally = __database().reducers[_tallyId]; if (_tally.script.length > 0) { - revert WitnetV2.UnsupportedRadonTallyScript(_tallyId); + revert WitnetEncodingLib.UnsupportedRadonTallyScript(_tallyId); } // Check result type consistency among all sources: @@ -544,7 +578,7 @@ contract WitnetBytecodesDefault if (_ix == 0) { _resultDataType = _retrievals[0].resultDataType; } else if (_retrievals[_ix].resultDataType != _resultDataType) { - revert WitnetV2.RadonRequestResultsMismatch( + revert WitnetEncodingLib.RadonRequestResultsMismatch( _ix, uint8(_retrievals[_ix].resultDataType), uint8(_resultDataType) @@ -552,7 +586,7 @@ contract WitnetBytecodesDefault } // check enough args are provided for each source if (_args[_ix].length < uint(_retrievals[_ix].argsCount)) { - revert WitnetV2.RadonRequestMissingArgs( + revert WitnetEncodingLib.RadonRequestMissingArgs( _ix, _retrievals[_ix].argsCount, _args[_ix].length @@ -571,7 +605,7 @@ contract WitnetBytecodesDefault _resultMaxSize ); if (_bytecode.length > 65535) { - revert WitnetV2.RadonRequestTooHeavy(_bytecode, _bytecode.length); + revert WitnetEncodingLib.RadonRequestTooHeavy(_bytecode, _bytecode.length); } // Calculate radhash and add request metadata and rad bytecode to storage: diff --git a/contracts/impls/core/WitnetRequestBoardTrustableDefault.sol b/contracts/impls/core/WitnetRequestBoardTrustableDefault.sol index bde65694f..bf240408a 100644 --- a/contracts/impls/core/WitnetRequestBoardTrustableDefault.sol +++ b/contracts/impls/core/WitnetRequestBoardTrustableDefault.sol @@ -30,7 +30,7 @@ contract WitnetRequestBoardTrustableDefault _factory, _upgradable, _versionTag, - address(0) + IERC20(address(0)) ) { _ESTIMATED_REPORT_RESULT_GAS = _reportResultGasLimit; @@ -84,7 +84,7 @@ contract WitnetRequestBoardTrustableDefault /// Transfers ETHs to given address. /// @param _to Recipient address. /// @param _amount Amount of ETHs to transfer. - function _safeTransferTo(address payable _to, uint256 _amount) + function __safeTransferTo(address payable _to, uint256 _amount) internal virtual override { diff --git a/contracts/impls/core/WitnetRequestBoardTrustlessDefault.sol b/contracts/impls/core/WitnetRequestBoardTrustlessDefault.sol new file mode 100644 index 000000000..9fbd3b25a --- /dev/null +++ b/contracts/impls/core/WitnetRequestBoardTrustlessDefault.sol @@ -0,0 +1,795 @@ +// SPDX-License-Identifier: MIT + +/* solhint-disable var-name-mixedcase */ + +pragma solidity >=0.7.0 <0.9.0; +pragma experimental ABIEncoderV2; + +import "../WitnetUpgradableBase.sol"; +import "../../WitnetRequestBoardV2.sol"; +import "../../data/WitnetRequestBoardV2Data.sol"; +import "../../patterns/Escrowable.sol"; + +import "../../libs/WitnetErrorsLib.sol"; + +/// @title Witnet Request Board "trustless" default implementation contract. +/// @author The Witnet Foundation +contract WitnetRequestBoardTrustlessDefault + is + Escrowable, + WitnetRequestBoardV2, + WitnetRequestBoardV2Data, + WitnetUpgradableBase +{ + using WitnetV2 for WitnetV2.QueryReport; + + bytes4 immutable public override DDR_QUERY_TAG; + uint256 immutable internal _DDR_REPORT_QUERY_GAS_BASE; + + /// Asserts the given query is currently in some specific status. + modifier queryInStatus( + bytes32 queryHash, + WitnetV2.QueryStatus queryStatus + ) + { + require( + checkQueryStatus(queryHash) == queryStatus, + _queryNotInStatusRevertMessage(queryStatus) + ); + _; + } + + modifier stakes(bytes32 queryHash) { + __stake( + msg.sender, + __query_(queryHash).weiStake + ); + _; + } + + constructor( + WitnetBlocks _blocks, + WitnetRequestFactory _factory, + uint256 _reportQueryGasBase, + bool _upgradable, + bytes32 _versionTag + ) + Escrowable(IERC20(address(0))) + WitnetRequestBoardV2(_blocks, _factory) + WitnetUpgradableBase( + _upgradable, + _versionTag, + "io.witnet.proxiable.board" + ) + { + DDR_QUERY_TAG = bytes4(keccak256(abi.encodePacked( + "evm:", + Witnet.toString(block.chainid), + ":", + Witnet.toHexString(address(_blocks.board())) + ))); + _DDR_REPORT_QUERY_GAS_BASE = _reportQueryGasBase; + } + + function DDR_REPORT_QUERY_GAS_BASE() + virtual override + public view + returns (uint256) + { + return _DDR_REPORT_QUERY_GAS_BASE; + } + + function DDR_REPORT_QUERY_MIN_STAKE_WEI() + virtual override + public view + returns (uint256) + { + return DDR_REPORT_QUERY_MIN_STAKE_WEI(_getGasPrice()); + } + + function DDR_REPORT_QUERY_MIN_STAKE_WEI(uint256 evmGasPrice) + virtual override + public view + returns (uint256) + { + // 50% over given gas price + return (blocks.ROLLUP_MAX_GAS() * evmGasPrice * 15) / 10; + } + + + // ================================================================================================================ + // --- Overrides 'Escrowable' ------------------------------------------------------------------------------------- + + receive() external payable virtual override { + __receive(msg.sender, msg.value); + } + + function atStakeBy(address tenant) + public view + virtual override + returns (uint256) + { + return __storage().escrows[tenant].atStake; + } + + function balanceOf(address tenant) + public view + virtual override + returns (uint256) + { + return __storage().escrows[tenant].balance; + } + + function withdraw() + external + virtual override + nonReentrant + returns (uint256 _withdrawn) + { + _withdrawn = balanceOf(msg.sender); + __storage().escrows[msg.sender].balance = 0; + __safeTransferTo(payable(msg.sender), _withdrawn); + } + + function __receive(address from, uint256 value) + virtual override + internal + { + __storage().escrows[from].balance += value; + emit Received(from, value); + } + + function __stake(address from, uint256 value) + virtual override + internal + { + Escrow storage __escrow = __storage().escrows[from]; + require( + __escrow.balance >= value, + "WitnetRequestBoardTrustlessDefault: insufficient balance" + ); + __escrow.balance -= value; + __escrow.atStake += value; + emit Staked(from, value); + } + + function __slash(address from, address to, uint256 value) + virtual override + internal + { + Escrow storage __escrow_from = __storage().escrows[from]; + Escrow storage __escrow_to = __storage().escrows[to]; + require( + __escrow_from.atStake >= value, + "WitnetRequestBoardTrustlessDefault: insufficient stake" + ); + __escrow_from.atStake -= value; + __escrow_to.balance += value; + emit Slashed(from, to, value); + } + + function __unstake(address from, uint256 value) + virtual override + internal + { + Escrow storage __escrow = __storage().escrows[from]; + require( + __escrow.atStake >= value, + "WitnetRequestBoardTrustlessDefault: insufficient stake" + ); + __escrow.atStake -= value; + __escrow.balance += value; + emit Unstaked(from, value); + } + + + // ================================================================================================================ + // --- Overrides 'Payable' ---------------------------------------------------------------------------------------- + + /// Gets current transaction price. + function _getGasPrice() + internal view + virtual override + returns (uint256) + { + return tx.gasprice; + } + + /// Gets current payment value. + function _getMsgValue() + internal view + virtual override + returns (uint256) + { + return msg.value; + } + + /// Transfers ETHs to given address. + /// @param _to Recipient address. + /// @param _amount Amount of ETHs to transfer. + function __safeTransferTo(address payable _to, uint256 _amount) + internal + virtual override + { + payable(_to).transfer(_amount); + emit Transfer(msg.sender, _amount); + } + + + // ================================================================================================================ + // --- 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) + override public + 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("WitnetRequestBoardTrustlessDefault: not the owner"); + } + } + require( + __proxiable().implementation != base(), + "WitnetRequestBoardTrustlessDefault: already initialized" + ); + require( + address(blocks.board()) == address(this), + "WitnetRequestBoardTrustlessDefault: blocks.board() counterfactual mismatch" + ); + __proxiable().implementation = base(); + emit Upgraded(msg.sender, base(), codehash(), version()); + } + + /// Tells whether provided address could eventually upgrade the contract. + function isUpgradableFrom(address _from) external view override returns (bool) { + return ( + // false if the WRB is intrinsically not upgradable, or `_from` is no owner + isUpgradable() + && _from == owner() + ); + } + + + // ================================================================================================================ + // --- Implementation of 'IWitnetRequestBoardV2' ------------------------------------------------------------------ + + function class() + external pure + virtual override(WitnetUpgradableBase, IWitnetRequestBoardV2) + returns (bytes4) + { + return type(IWitnetRequestBoardV2).interfaceId; + } + + function estimateQueryReward( + bytes32 radHash, + WitnetV2.RadonSLAv2 calldata slaParams, + uint256 witEvmPrice, + uint256 evmMaxGasPrice, + uint256 evmCallbackGasLimit + ) + external view + virtual override + returns (uint256) + { + uint _queryMaxResultSize = registry.lookupRadonRequestResultMaxSize(radHash); + uint _queryTotalWits = ( + slaParams.witMinMinerFee * 3 + + slaParams.committeeSize * slaParams.witWitnessReward + ); + return ( + witEvmPrice * _queryTotalWits + + evmMaxGasPrice * ( + _DDR_REPORT_QUERY_GAS_BASE + + _getSaveToStorageCost(2 + _queryMaxResultSize / 32) + + evmCallbackGasLimit + // TODO: + evmClaimQueryGasLimit + ) + ); + } + + function readQueryBridgeData(bytes32 queryHash) + external view + virtual override + queryExists(queryHash) + returns ( + WitnetV2.QueryStatus status, + uint256 weiEvmReward, + uint256 weiEvmStake, + bytes memory radBytecode, + WitnetV2.RadonSLAv2 memory slaParams + ) + { + status = checkQueryStatus(queryHash); + WitnetV2.Query storage __query = __query_(queryHash); + WitnetV2.QueryRequest storage __request = __query.request; + weiEvmReward = __query.weiReward; + weiEvmStake = __query.weiStake; + radBytecode = registry.bytecodeOf(__request.radHash); + slaParams = WitnetV2.toRadonSLAv2(__request.packedSLA); + } + + function readQueryBridgeStatus(bytes32 queryHash) + external view + virtual override + returns ( + WitnetV2.QueryStatus status, + uint256 weiEvmReward + ) + { + status = checkQueryStatus(queryHash); + weiEvmReward = __query_(queryHash).weiReward; + } + + function readQuery(bytes32 queryHash) + external view + virtual override + queryExists(queryHash) + returns (WitnetV2.Query memory) + { + return __query_(queryHash); + } + + function readQueryEvmReward(bytes32 queryHash) + external view + virtual override + queryExists(queryHash) + returns (uint256) + { + return __query_(queryHash).weiReward; + } + + function readQueryCallback(bytes32 queryHash) + external view + virtual override + queryExists(queryHash) + returns (WitnetV2.QueryCallback memory) + { + return __query_(queryHash).callback; + } + + function readQueryRequest(bytes32 queryHash) + external view + virtual override + queryExists(queryHash) + returns (bytes32, WitnetV2.RadonSLAv2 memory sla) + { + return ( + __request_(queryHash).radHash, + WitnetV2.toRadonSLAv2(__request_(queryHash).packedSLA) + ); + } + + function readQueryReport(bytes32 queryHash) + external view + virtual override + queryExists(queryHash) + returns (WitnetV2.QueryReport memory) + { + return __query_(queryHash).report; + } + + function readQueryResult(bytes32 queryHash) + external view + virtual override + returns (Witnet.Result memory) + { + return Witnet.resultFromCborBytes(__report_(queryHash).tallyCborBytes); + } + + function checkQueryStatus(bytes32 queryHash) + public view + virtual override + returns (WitnetV2.QueryStatus) + { + WitnetV2.Query storage __query = __query_(queryHash); + if (__query.reporter != address(0)) { + if (__query.disputes.length > 0) { + // disputed or finalized + if (blocks.getLastBeaconEpoch() > __query.reportEpoch) { + return WitnetV2.QueryStatus.Finalized; + } else { + return WitnetV2.QueryStatus.Disputed; + } + } else { + // reported or finalized + return WitnetV2.checkQueryReportStatus( + __query.reportEpoch, + blocks.getCurrentEpoch() + ); + } + } else if (__query.from != address(0)) { + // posted, delayed or expired + return WitnetV2.checkQueryPostStatus( + __query.postEpoch, + blocks.getCurrentEpoch() + ); + } else { + return WitnetV2.QueryStatus.Void; + } + } + + function checkQueryResultStatus(bytes32 queryHash) + public view + virtual override + returns (Witnet.ResultStatus) + { + WitnetV2.QueryStatus _queryStatus = checkQueryStatus(queryHash); + if (_queryStatus == WitnetV2.QueryStatus.Finalized) { + // determine whether reported result is an error by peeking the first byte + return (__report_(queryHash).tallyCborBytes[0] == bytes1(0xd8) + ? Witnet.ResultStatus.Error + : Witnet.ResultStatus.Ready + ); + } else if (_queryStatus == WitnetV2.QueryStatus.Expired) { + return Witnet.ResultStatus.Expired; + } else if (_queryStatus == WitnetV2.QueryStatus.Void) { + return Witnet.ResultStatus.Void; + } else { + return Witnet.ResultStatus.Awaiting; + } + } + + function checkQueryResultError(bytes32 queryHash) + external view + virtual override + returns (Witnet.ResultError memory) + { + Witnet.ResultStatus _resultStatus = checkQueryResultStatus(queryHash); + if (_resultStatus == Witnet.ResultStatus.Awaiting) { + return Witnet.ResultError({ + code: Witnet.ResultErrorCodes.BoardUnsolvedQuery, + reason: "Witnet: Board: pending query" + }); + } else if (_resultStatus == Witnet.ResultStatus.Expired) { + return Witnet.ResultError({ + code: Witnet.ResultErrorCodes.BoardExpiredQuery, + reason: "Witnet: Board: expired query" + }); + } else if (_resultStatus == Witnet.ResultStatus.Void) { + return Witnet.ResultError({ + code: Witnet.ResultErrorCodes.Unknown, + reason: "Witnet: Board: unknown query" + }); + } else { + try WitnetErrorsLib.resultErrorFromCborBytes(__report_(queryHash).tallyCborBytes) + returns (Witnet.ResultError memory _error) + { + return _error; + } + catch Error(string memory _reason) { + return Witnet.ResultError({ + code: Witnet.ResultErrorCodes.BoardUndecodableError, + reason: string(abi.encodePacked("Witnet: Board: undecodable error: ", _reason)) + }); + } + catch (bytes memory) { + return Witnet.ResultError({ + code: Witnet.ResultErrorCodes.BoardUndecodableError, + reason: "Witnet: Board: undecodable error: assertion failed" + }); + } + } + } + + function postQuery( + bytes32 radHash, + WitnetV2.RadonSLAv2 calldata slaParams + ) + external payable + virtual override + returns (bytes32 _queryHash) + { + return postQuery( + radHash, + slaParams, + IWitnetRequestCallback(address(0)), + 0 + ); + } + + function postQuery( + bytes32 radHash, + WitnetV2.RadonSLAv2 calldata slaParams, + IWitnetRequestCallback queryCallback, + uint256 queryCallbackGas + ) + public payable + virtual override + returns (bytes32 queryHash) + { + require( + WitnetV2.isValid(slaParams), + "WitnetRequestBoardTrustlessDefault: invalid SLA" + ); + bytes32 packedSLA = WitnetV2.pack(slaParams); + uint256 postEpoch = blocks.getCurrentEpoch(); + queryHash = keccak256(abi.encodePacked( + DDR_QUERY_TAG, + radHash, + packedSLA, + postEpoch + )); + WitnetV2.Query storage __query = __storage().queries[queryHash]; + require( + __query.postEpoch == 0, + "WitnetRequestBoardTrustlessDefault: already posted" + ); + __query.postEpoch = postEpoch; + __query.weiReward = msg.value; + __query.weiStake = DDR_REPORT_QUERY_MIN_STAKE_WEI(); + __query.request = WitnetV2.QueryRequest({ + radHash: radHash, + packedSLA: packedSLA + }); + if (address(queryCallback) != address(0)) { + require( + queryCallbackGas >= 50000, + "WitnetRequestBoardTrustlessDefault: insufficient callback gas" + ); + __query.callback = WitnetV2.QueryCallback({ + addr: address(queryCallback), + gas: queryCallbackGas + }); + } + emit PostedQuery( + msg.sender, + queryHash, + address(queryCallback) + ); + } + + function deleteQuery(bytes32 queryHash) + external + virtual override + { + WitnetV2.Query storage __query = __query_(queryHash); + address _requester = __query.from; + address _reporter = __query.reporter; + uint256 _weiReward = __query.weiReward; + uint256 _weiStake = __query.weiStake; + require( + msg.sender == __query.from, + "WitnetRequestBoardTrustlessDefault: not the requester" + ); + WitnetV2.QueryStatus _queryStatus = checkQueryStatus(queryHash); + if ( + _queryStatus == WitnetV2.QueryStatus.Finalized + || _queryStatus == WitnetV2.QueryStatus.Expired + ) { + delete __query.request;//? + delete __query.report;//? + delete __query.callback;//? + delete __query.disputes;//? + delete __storage().queries[queryHash]; + emit DeletedQuery(msg.sender, queryHash); + if (_weiReward > 0) { + if (_queryStatus == WitnetV2.QueryStatus.Expired) { + __safeTransferTo(payable(_requester), _weiReward); + } else { + __receive(_reporter, _weiReward); + } + } + if (_weiStake > 0) { + __unstake(_reporter, _weiStake); + } + } else { + revert("WitnetRequestBoardTrustlessDefault: not in current status"); + } + } + + function reportQuery( + bytes32 queryHash, + bytes calldata relayerSignature, + WitnetV2.QueryReport calldata queryReport + ) + public + virtual override + stakes(queryHash) + { + WitnetV2.Query storage __query = __query_(queryHash); + WitnetV2.QueryStatus _queryStatus = checkQueryStatus(queryHash); + if ( + _queryStatus == WitnetV2.QueryStatus.Delayed + || ( + _queryStatus == WitnetV2.QueryStatus.Posted + && Witnet.recoverAddr(queryHash, relayerSignature) == msg.sender + && msg.sender == queryReport.relayer + ) + ) { + __query.reporter = msg.sender; + } else { + revert("WitnetRequestBoardTrustlessDefault: unauthorized report"); + } + uint _tallyBeaconIndex = WitnetV2.beaconIndexFromEpoch(queryReport.tallyEpoch); + uint _postBeaconIndex = WitnetV2.beaconIndexFromEpoch(__query.postEpoch); + require( + _tallyBeaconIndex >= _postBeaconIndex && _tallyBeaconIndex <= _postBeaconIndex + 1, + "WitnetRequestBoardTrustlessDefault: too late tally" + ); + __query.reportEpoch = blocks.getCurrentEpoch(); + __query.report = queryReport; + WitnetV2.QueryCallback storage __callback = __query.callback; + if (__callback.addr != address(0)) { + IWitnetRequestCallback(__callback.addr).settleWitnetQueryReport{gas: __callback.gas}( + queryHash, + queryReport + ); + } + emit ReportedQuery( + msg.sender, + queryHash, + __callback.addr + ); + } + + function reportQueryBatch( + bytes32[] calldata hashes, + bytes[] calldata signatures, + WitnetV2.QueryReport[] calldata reports + ) + external + virtual override + { + require( + hashes.length == reports.length + && hashes.length == signatures.length, + "WitnetRequestBoardTrustlessDefault: arrays mismatch" + ); + for (uint _ix = 0; _ix < hashes.length; _ix ++) { + WitnetV2.QueryStatus _status = checkQueryStatus(hashes[_ix]); + if (_status == WitnetV2.QueryStatus.Posted || _status == WitnetV2.QueryStatus.Delayed) { + reportQuery(hashes[_ix], signatures[_ix], reports[_ix]); + } + } + } + + function disputeQuery( + bytes32 queryHash, + WitnetV2.QueryReport calldata queryReport + ) + external + virtual override + stakes(queryHash) + { + WitnetV2.QueryStatus queryStatus = checkQueryStatus(queryHash); + if ( + queryStatus == WitnetV2.QueryStatus.Reported + || queryStatus == WitnetV2.QueryStatus.Delayed + || queryStatus == WitnetV2.QueryStatus.Disputed + ) { + WitnetV2.Query storage __query = __query_(queryHash); + uint _tallyBeaconIndex = WitnetV2.beaconIndexFromEpoch(queryReport.tallyEpoch); + uint _postBeaconIndex = WitnetV2.beaconIndexFromEpoch(__query.postEpoch); + require( + _tallyBeaconIndex >= _postBeaconIndex + && _tallyBeaconIndex <= _postBeaconIndex + 1, + "WitnetRequestBoardTrustlessDefault: too late tally" + ); + __query.disputes.push(WitnetV2.QueryDispute({ + disputer: msg.sender, + report: queryReport + })); + if (__query.disputes.length == 1 && __query.reporter != address(0)) { + // upon first dispute, append reporter's tallyHash + __storage().suitors[__query.report.tallyHash(queryHash)] = Suitor({ + index: 0, + queryHash: queryHash + }); + } + bytes32 _tallyHash = queryReport.tallyHash(queryHash); + require( + __storage().suitors[_tallyHash].queryHash == bytes32(0), + "WitnetRequestBoardTrustlessDefault: replayed dispute" + ); + __storage().suitors[_tallyHash] = Suitor({ + index: __query.disputes.length, + queryHash: queryHash + }); + // settle query dispute on WitnetBlocks + blocks.disputeQuery(queryHash, _tallyBeaconIndex); + // emit event + emit DisputedQuery( + msg.sender, + queryHash, + _tallyHash + ); + } else { + revert("WitnetRequestBoardTrustlessDefault: not disputable"); + } + } + + function claimQueryReward(bytes32 queryHash) + external + virtual override + queryInStatus(queryHash, WitnetV2.QueryStatus.Finalized) + returns (uint256 _weiReward) + { + WitnetV2.Query storage __query = __query_(queryHash); + require( + msg.sender == __query.reporter, + "WitnetRequestBoardTrustlessDefault: not the reporter" + ); + _weiReward = __query.weiReward; + if (_weiReward > 0) { + __query.weiReward = 0; + __receive(__query.reporter, _weiReward); + } else { + revert("WitnetRequestBoardTrustlessDefault: already claimed"); + } + uint _weiStake = __query.weiStake; + if (_weiStake > 0) { + __query.weiStake = 0; + __unstake(__query.reporter, _weiStake); + } + } + + function determineQueryTallyHash(bytes32) + virtual override + external + returns ( + bytes32, + uint256 + ) + { + require( + msg.sender == address(blocks), + "WitnetRequestBoardTrustlessDefault: unauthorized" + ); + // TODO + } + + + // ================================================================================================================ + // --- Internal methods ------------------------------------------------------------------------------------------- + + function _getSaveToStorageCost(uint256 words) virtual internal pure returns (uint256) { + return words * 20000; + } + + function _queryNotInStatusRevertMessage(WitnetV2.QueryStatus queryStatus) + internal pure + returns (string memory) + { + string memory _reason; + if (queryStatus == WitnetV2.QueryStatus.Posted) { + _reason = "Posted"; + } else if (queryStatus == WitnetV2.QueryStatus.Reported) { + _reason = "Reported"; + } else if (queryStatus == WitnetV2.QueryStatus.Disputed) { + _reason = "Disputed"; + } else if (queryStatus == WitnetV2.QueryStatus.Expired) { + _reason = "Expired"; + } else if (queryStatus == WitnetV2.QueryStatus.Finalized) { + _reason = "Finalized"; + } else { + _reason = "expected"; + } + return string(abi.encodePacked( + "WitnetRequestBoardTrustlessDefault: not in ", + _reason, + " status" + )); + } + + function _max(uint a, uint b) internal pure returns (uint256) { + unchecked { + if (a >= b) { + return a; + } else { + return b; + } + } + } + +} diff --git a/contracts/impls/core/WitnetRequestFactoryDefault.sol b/contracts/impls/core/WitnetRequestFactoryDefault.sol index 25766113d..a92827b92 100644 --- a/contracts/impls/core/WitnetRequestFactoryDefault.sol +++ b/contracts/impls/core/WitnetRequestFactoryDefault.sol @@ -207,15 +207,15 @@ contract WitnetRequestFactoryDefault _tally, _resultDataMaxSize )); - emit WitnetRequestTemplateBuilt( - _template, - WitnetRequestTemplate(_template).parameterized() - ); } + emit WitnetRequestTemplateBuilt( + _template, + WitnetRequestTemplate(_template).parameterized() + ); } function class() - virtual override(IWitnetRequestFactory, WitnetRequestTemplate) + virtual override(IWitnetRequestFactory, WitnetRequestTemplate, WitnetUpgradableBase) external view returns (bytes4) { diff --git a/contracts/impls/core/customs/WitnetRequestBoardBypass.sol b/contracts/impls/core/customs/WitnetRequestBoardBypass.sol new file mode 100644 index 000000000..023e094f2 --- /dev/null +++ b/contracts/impls/core/customs/WitnetRequestBoardBypass.sol @@ -0,0 +1,138 @@ +// SPDX-License-Identifier: MIT + +pragma solidity >=0.7.0 <0.9.0; +pragma experimental ABIEncoderV2; + +import "../../WitnetUpgradableBase.sol"; +import "../../../WitnetRequestBoard.sol"; +import "../../../data/WitnetBoardData.sol"; + +/// @title Witnet Request Board "trustable" base implementation contract. +/// @notice Contract to bridge requests to Witnet Decentralized Oracle Network. +/// @dev This contract enables posting requests that Witnet bridges will insert into the Witnet network. +/// The result of the requests will be posted back to this contract by the bridge nodes too. +/// @author The Witnet Foundation +contract WitnetRequestBoardBypass + is + WitnetBoardData, + WitnetUpgradableBase +{ + WitnetRequestBoard public immutable bypass; + WitnetRequestBoard public former; + uint public offset; + + constructor( + WitnetRequestBoard _bypass, + bool _upgradable, + bytes32 _versionTag + ) + WitnetUpgradableBase( + _upgradable, + _versionTag, + "io.witnet.proxiable.board" + ) + { + assert(address(_bypass) != address(0)); + bypass = _bypass; + } + + fallback() override external { + address _bypass = address(bypass); + assembly { + let ptr := mload(0x40) + calldatacopy(ptr, 0, calldatasize()) + let result := call(gas(), _bypass, 0, ptr, calldatasize(), 0, 0) + let size := returndatasize() + returndatacopy(ptr, 0, size) + switch result + case 0 { revert(ptr, size) } + default { return(ptr, size) } + } + } + + function class() virtual override external pure returns (bytes4) { + return ( + type(IWitnetRequestBoardDeprecating).interfaceId + ^ type(IWitnetRequestBoardReporter).interfaceId + ^ type(IWitnetRequestBoardRequestor).interfaceId + ^ type(IWitnetRequestBoardView).interfaceId + ); + } + + /// @notice Requests the execution of the given Witnet Data Request in expectation that it will be relayed and solved by the Witnet DON. + /// @notice A reward amount is escrowed by the Witnet Request Board that will be transferred to the reporter who relays back the Witnet-provided + /// @notice result to this request. + /// @dev Fails if: + /// @dev - provided reward is too low. + /// @dev - provided script is zero address. + /// @dev - provided script bytecode is empty. + /// @param addr The address of the IWitnetRequest contract that can provide the actual Data Request bytecode. + function postRequest(IWitnetRequest addr) + external payable + returns (uint256 _queryId) + { + _queryId = offset + bypass.postRequest{value:msg.value, gas: gasleft()}(addr); + } + + /// @notice Requests the execution of the given Witnet Data Request in expectation that it will be relayed and solved by the Witnet DON. + /// @notice A reward amount is escrowed by the Witnet Request Board that will be transferred to the reporter who relays back the Witnet-provided + /// @notice result to this request. + /// @dev Fails if, provided reward is too low. + /// @param radHash The radHash of the Witnet Data Request. + /// @param slaHash The slaHash of the Witnet Data Request. + function postRequest(bytes32 radHash, bytes32 slaHash) + external payable + returns (uint256 _queryId) + { + return offset + bypass.postRequest{value: msg.value, gas: gasleft()}(radHash, slaHash); + } + + + // ================================================================================================================ + // --- Overrides 'Upgradeable' ------------------------------------------------------------------------------------- + + function owner() virtual override public view returns (address) { + return __storage().owner; + } + + /// @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 + { + address _owner = __storage().owner; + if (_owner == address(0)) { + // set owner if none set yet + _transferOwnership(msg.sender); + __proxiable().implementation = base(); + __proxiable().proxy = address(this); + } else { + // only owner can initialize: + require( + msg.sender == _owner, + "WitnetRequestBoardBypass: only the owner" + ); + } + if (__proxiable().implementation == address(0)) { + revert("WitnetRequestBoardBypass: cannot bypass zero address"); + } else if (__proxiable().implementation == base()) { + revert("WitnetRequestBoardBypass: already initialized"); + } else { + former = WitnetRequestBoard(__proxiable().implementation); + offset = __storage().numQueries; + } + __proxiable().implementation == base(); + emit Upgraded(msg.sender, base(), codehash(), version()); + } + + /// Tells whether provided address could eventually upgrade the contract. + function isUpgradableFrom(address _from) external view override returns (bool) { + address _owner = owner(); + return ( + // false if the WRB is intrinsically not upgradable, or `_from` is no owner + isUpgradable() + && _owner == _from + ); + } +} \ No newline at end of file diff --git a/contracts/impls/core/customs/WitnetRequestBoardTrustableBase.sol b/contracts/impls/core/customs/WitnetRequestBoardTrustableBase.sol index f51347bad..dcc08cc6d 100644 --- a/contracts/impls/core/customs/WitnetRequestBoardTrustableBase.sol +++ b/contracts/impls/core/customs/WitnetRequestBoardTrustableBase.sol @@ -31,7 +31,7 @@ abstract contract WitnetRequestBoardTrustableBase WitnetRequestFactory _factory, bool _upgradable, bytes32 _versionTag, - address _currency + IERC20 _currency ) Payable(_currency) WitnetUpgradableBase( @@ -46,6 +46,15 @@ abstract contract WitnetRequestBoardTrustableBase revert("WitnetRequestBoardTrustableBase: no transfers accepted"); } + function class() virtual override external pure returns (bytes4) { + return ( + type(IWitnetRequestBoardDeprecating).interfaceId + ^ type(IWitnetRequestBoardReporter).interfaceId + ^ type(IWitnetRequestBoardRequestor).interfaceId + ^ type(IWitnetRequestBoardView).interfaceId + ); + } + // ================================================================================================================ // --- Overrides IERC165 interface -------------------------------------------------------------------------------- @@ -205,7 +214,7 @@ abstract contract WitnetRequestBoardTrustableBase // This would not be a valid encoding with CBOR and could trigger a reentrancy attack require(_cborBytes.length != 0, "WitnetRequestBoardTrustableDefault: result cannot be empty"); // solhint-disable not-rely-on-time - _safeTransferTo( + __safeTransferTo( payable(msg.sender), __reportResult( _queryId, @@ -243,7 +252,7 @@ abstract contract WitnetRequestBoardTrustableBase // Ensures the result bytes do not have zero length // This would not be a valid encoding with CBOR and could trigger a reentrancy attack require(_cborBytes.length != 0, "WitnetRequestBoardTrustableDefault: result cannot be empty"); - _safeTransferTo( + __safeTransferTo( payable(msg.sender), __reportResult( _queryId, @@ -319,7 +328,7 @@ abstract contract WitnetRequestBoardTrustableBase } // Transfer all successful rewards in one single shot to the authorized reporter, if any: if (_batchReward > 0) { - _safeTransferTo( + __safeTransferTo( payable(msg.sender), _batchReward ); diff --git a/contracts/impls/core/customs/WitnetRequestBoardTrustableBoba.sol b/contracts/impls/core/customs/WitnetRequestBoardTrustableBoba.sol index e6c3a33f4..02be259e4 100644 --- a/contracts/impls/core/customs/WitnetRequestBoardTrustableBoba.sol +++ b/contracts/impls/core/customs/WitnetRequestBoardTrustableBoba.sol @@ -38,7 +38,7 @@ contract WitnetRequestBoardTrustableBoba bytes32 _versionTag, uint256 _layer2ReportResultGasLimit, uint256 _layer2GasPrice, - address _oETH + IERC20 _oETH ) WitnetRequestBoardTrustableBase(_factory, _upgradable, _versionTag, _oETH) { @@ -84,7 +84,7 @@ contract WitnetRequestBoardTrustableBoba /// @dev Updates `lastBalance` value. /// @param _to OVM_ETH recipient account. /// @param _amount Amount of oETHs to transfer. - function _safeTransferTo(address payable _to, uint256 _amount) + function __safeTransferTo(address payable _to, uint256 _amount) internal override { diff --git a/contracts/interfaces/V2/IWitnetBlocks.sol b/contracts/interfaces/V2/IWitnetBlocks.sol new file mode 100644 index 000000000..161670a97 --- /dev/null +++ b/contracts/interfaces/V2/IWitnetBlocks.sol @@ -0,0 +1,34 @@ +// SPDX-License-Identifier: MIT + +pragma solidity >=0.8.0 <0.9.0; + +import "./IWitnetRequestBoardV2.sol"; + +interface IWitnetBlocks { + + event FastForward(address indexed from, uint256 index, uint256 prevIndex); + event Rollup(address indexed from, uint256 index, uint256 tallyCount, uint256 weiReward); + + function ROLLUP_DEFAULT_PENALTY_WEI() external view returns (uint256); + function ROLLUP_MAX_GAS() external view returns (uint256); + + function board() external view returns (IWitnetRequestBoardV2); + function class() external pure returns (bytes4); + function genesis() external view returns (WitnetV2.Beacon memory); + + function getBeaconDisputedQueries(uint256 beaconIndex) external view returns (bytes32[] memory); + function getCurrentBeaconIndex() external view returns (uint256); + function getCurrentEpoch() external view returns (uint256); + function getLastBeacon() external view returns (WitnetV2.Beacon memory); + function getLastBeaconEpoch() external view returns (uint256); + function getLastBeaconIndex() external view returns (uint256); + function getNextBeaconIndex() external view returns (uint256); + + function disputeQuery(bytes32 queryHash, uint256 tallyBeaconIndex) external; + function rollupTallyHashes( + WitnetV2.FastForward[] calldata fastForwards, + bytes32[] calldata tallyHashes, + uint256 tallyOffset, + uint256 tallyLength + ) external returns (uint256 weiReward); +} \ No newline at end of file diff --git a/contracts/interfaces/V2/IWitnetBytecodes.sol b/contracts/interfaces/V2/IWitnetBytecodes.sol index abea432eb..f09f230da 100644 --- a/contracts/interfaces/V2/IWitnetBytecodes.sol +++ b/contracts/interfaces/V2/IWitnetBytecodes.sol @@ -4,11 +4,19 @@ pragma solidity >=0.8.0 <0.9.0; import "../../libs/WitnetV2.sol"; -interface IWitnetBytecodes { +interface IWitnetBytecodes { function bytecodeOf(bytes32 radHash) external view returns (bytes memory); function bytecodeOf(bytes32 radHash, bytes32 slahHash) external view returns (bytes memory); + function fetchBytecodeWitFeesOf(bytes32 radHash, bytes32 slaHash) + external view returns ( + uint256 _witMinMinerFee, + uint256 _witWitnessingFee, + bytes memory _radSlaBytecode + ); + function fetchMaxResultSizeWitRewardOf(bytes32 radHash, bytes32 slaHash) external view returns (uint256, uint256); + function hashOf( bytes32[] calldata sources, bytes32 aggregator, diff --git a/contracts/interfaces/V2/IWitnetPriceSolverDeployer.sol b/contracts/interfaces/V2/IWitnetPriceSolverDeployer.sol new file mode 100644 index 000000000..3d441446b --- /dev/null +++ b/contracts/interfaces/V2/IWitnetPriceSolverDeployer.sol @@ -0,0 +1,9 @@ +// SPDX-License-Identifier: MIT + +pragma solidity >=0.8.0 <0.9.0; + +interface IWitnetPriceSolverDeployer { + event WitnetPriceSolverDeployed(address indexed from, address solver, bytes32 codehash, bytes constructorParams); + function deployPriceSolver(bytes calldata initcode, bytes calldata additionalParams) external returns (address); + function determinePriceSolverAddress(bytes calldata initcode, bytes calldata additionalParams) external view returns (address); +} \ No newline at end of file diff --git a/contracts/interfaces/V2/IWitnetRequestBoardV2.sol b/contracts/interfaces/V2/IWitnetRequestBoardV2.sol new file mode 100644 index 000000000..0e2ec9638 --- /dev/null +++ b/contracts/interfaces/V2/IWitnetRequestBoardV2.sol @@ -0,0 +1,84 @@ +// SPDX-License-Identifier: MIT + +pragma solidity >=0.7.0 <0.9.0; +pragma experimental ABIEncoderV2; + +import "./IWitnetRequestCallback.sol"; + +/// @title Witnet Requestor Interface +/// @notice It defines how to interact with the Witnet Request Board in order to: +/// - request the execution of Witnet Radon scripts (data request); +/// - upgrade the resolution reward of any previously posted request, in case gas price raises in mainnet; +/// - read the result of any previously posted request, eventually reported by the Witnet DON. +/// - remove from storage all data related to past and solved data requests, and results. +/// @author The Witnet Foundation. +interface IWitnetRequestBoardV2 { + + event PostedQuery(address indexed from, bytes32 hash, address callback); + event DeletedQuery(address indexed from, bytes32 hash); + event ReportedQuery(address indexed from, bytes32 hash, address callback); + event DisputedQuery(address indexed from, bytes32 hash, bytes32 tallyHash); + + function DDR_QUERY_TAG() external view returns (bytes4); + function DDR_REPORT_QUERY_GAS_BASE() external view returns (uint256); + function DDR_REPORT_QUERY_MIN_STAKE_WEI() external view returns (uint256); + function DDR_REPORT_QUERY_MIN_STAKE_WEI(uint256 gasPrice) external view returns (uint256); + + function class() external view returns (bytes4); + + function estimateQueryReward( + bytes32 radHash, + WitnetV2.RadonSLAv2 calldata slaParams, + uint256 nanoWitWeiEvmPrice, + uint256 weiEvmMaxGasPrice, + uint256 evmCallbackGasLimit + ) external view returns (uint256); + + function readQueryBridgeData(bytes32 queryHash) + external view returns ( + WitnetV2.QueryStatus status, + uint256 weiEvmReward, + uint256 weiEvmStake, + bytes memory radBytecode, + WitnetV2.RadonSLAv2 memory slaParams + ); + + function readQueryBridgeStatus(bytes32 queryHash) + external view returns ( + WitnetV2.QueryStatus status, + uint256 weiEvmReward + ); + + function readQuery(bytes32 queryHash) external view returns (WitnetV2.Query memory); + function readQueryEvmReward(bytes32 queryHash) external view returns (uint256); + function readQueryCallback(bytes32 queryHash) external view returns (WitnetV2.QueryCallback memory); + function readQueryRequest(bytes32 queryHash) external view returns (bytes32 radHash, WitnetV2.RadonSLAv2 memory sla); + function readQueryReport(bytes32 queryHash) external view returns (WitnetV2.QueryReport memory); + function readQueryResult(bytes32 queryHash) external view returns (Witnet.Result memory); + + function checkQueryStatus(bytes32 queryHash) external view returns (WitnetV2.QueryStatus); + function checkQueryResultStatus(bytes32 queryHash) external view returns (Witnet.ResultStatus); + function checkQueryResultError(bytes32 queryHash) external view returns (Witnet.ResultError memory); + + function postQuery( + bytes32 radHash, + WitnetV2.RadonSLAv2 calldata slaParams + ) external payable returns (bytes32 queryHash); + + function postQuery( + bytes32 radHash, + WitnetV2.RadonSLAv2 calldata slaParams, + IWitnetRequestCallback callback, + uint256 callbackGas + ) external payable returns (bytes32 queryHash); + + function reportQuery(bytes32 queryHash, bytes calldata relayerSignature, WitnetV2.QueryReport memory) external; + function reportQueryBatch(bytes32[] calldata hashes, bytes[] calldata signatures, WitnetV2.QueryReport[] calldata) external; + + function disputeQuery(bytes32 queryHash, WitnetV2.QueryReport memory) external; + + function claimQueryReward(bytes32 queryHash) external returns (uint256); + function deleteQuery(bytes32 queryHash) external; + + function determineQueryTallyHash(bytes32 queryTallyHash) external returns (bytes32 queryHash, uint256 queryStakes); +} \ No newline at end of file diff --git a/contracts/interfaces/V2/IWitnetRequestCallback.sol b/contracts/interfaces/V2/IWitnetRequestCallback.sol new file mode 100644 index 000000000..ab169559d --- /dev/null +++ b/contracts/interfaces/V2/IWitnetRequestCallback.sol @@ -0,0 +1,12 @@ +// SPDX-License-Identifier: MIT + +pragma solidity >=0.8.0 <0.9.0; + +import "../../libs/WitnetV2.sol"; + +interface IWitnetRequestCallback { + function settleWitnetQueryReport( + bytes32 queryHash, + WitnetV2.QueryReport calldata queryReport + ) external; +} \ No newline at end of file diff --git a/contracts/libs/Witnet.sol b/contracts/libs/Witnet.sol index e03d774d9..97f28f287 100644 --- a/contracts/libs/Witnet.sol +++ b/contracts/libs/Witnet.sol @@ -55,7 +55,8 @@ library Witnet { Void, Awaiting, Ready, - Error + Error, + Expired } /// Data struct describing an error when trying to fetch a Witnet-provided result to a Data Request. @@ -124,8 +125,10 @@ library Witnet { InsufficientCommits, /// 0x53: Generic error during tally execution TallyExecution, + /// 0x54: Max result size exceeded + MaxResultSizeExceeded, /// Unallocated - OtherError0x54, OtherError0x55, OtherError0x56, OtherError0x57, OtherError0x58, OtherError0x59, + OtherError0x55, OtherError0x56, OtherError0x57, OtherError0x58, OtherError0x59, OtherError0x5A, OtherError0x5B, OtherError0x5C, OtherError0x5D, OtherError0x5E, OtherError0x5F, /// 0x60: Invalid reveal serialization (malformed reveals are converted to this value) MalformedReveal, @@ -135,7 +138,7 @@ library Witnet { OtherError0x6D, OtherError0x6E,OtherError0x6F, /////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// /// Access errors ===================================================================================================== - /// 0x70: Tried to access a value from an index using an index that is out of bounds + /// 0x70: Tried to access a value from an array using an index that is out of bounds ArrayIndexOutOfBounds, /// 0x71: Tried to access a value from a map using a key that does not exist MapKeyNotFound, @@ -169,9 +172,17 @@ library Witnet { BridgeOversizedResult, /// Unallocated OtherError0xE3, OtherError0xE4, OtherError0xE5, OtherError0xE6, OtherError0xE7, OtherError0xE8, OtherError0xE9, - OtherError0xEA, OtherError0xEB, OtherError0xEC, OtherError0xED, OtherError0xEE, OtherError0xEF, OtherError0xF0, - OtherError0xF1, OtherError0xF2, OtherError0xF3, OtherError0xF4, OtherError0xF5, OtherError0xF6, OtherError0xF7, - OtherError0xF8, OtherError0xF9, OtherError0xFA, OtherError0xFB, OtherError0xFC, OtherError0xFD, OtherError0xFE, + OtherError0xEA, OtherError0xEB, OtherError0xEC, OtherError0xED, OtherError0xEE, OtherError0xEF, + /////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// + /// Request board errors: errors that arise when trying to fetch a result to some previously posted data request ====== + BoardUnsolvedQuery, + BoardUnknownQuery, + BoardExpiredQuery, + BoardUndecodableError, + /////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// + /// Other errors: errors that arise when trying to fetch a result to some previously posted data request ============== + OtherError0xF4, OtherError0xF5, OtherError0xF6, OtherError0xF7, OtherError0xF8, OtherError0xF9, OtherError0xFA, + OtherError0xFB, OtherError0xFC, OtherError0xFD, OtherError0xFE, /////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// /// 0xFF: Some tally error is not intercepted but should UnhandledIntercept @@ -344,6 +355,17 @@ library Witnet { } + /// =============================================================================================================== + /// --- 'address' helper methods ---------------------------------------------------------------------------------- + + function toHexString(address addr) + internal pure + returns (string memory) + { + // TODO + } + + /// =============================================================================================================== /// --- 'bytes' helper methods ------------------------------------------------------------------------------------ @@ -359,6 +381,32 @@ library Witnet { } } + /// Recovers address from hash and signature. + function recoverAddr(bytes32 hash_, bytes memory signature) + internal pure + returns (address) + { + if (signature.length != 65) { + return (address(0)); + } + bytes32 r; + bytes32 s; + uint8 v; + assembly { + r := mload(add(signature, 0x20)) + s := mload(add(signature, 0x40)) + v := byte(0, mload(add(signature, 0x60))) + } + if (uint256(s) > 0x7FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF5D576E7357A4501DDFE92F46681B20A0) { + return address(0); + } + if (v != 27 && v != 28) { + return address(0); + } + return ecrecover(hash_, v, r, s); + } + + /// @dev Transform given bytes into a Witnet.Result instance. /// @param bytecode Raw bytes representing a CBOR-encoded value. /// @return A `Witnet.Result` instance. @@ -499,6 +547,26 @@ library Witnet { } } + /// @notice Convert a `uint` into a string` representing its value. + function toString(uint v) + internal pure + returns (string memory) + { + uint maxlength = 100; + bytes memory reversed = new bytes(maxlength); + uint i = 0; + do { + uint8 remainder = uint8(v % 10); + v = v / 10; + reversed[i ++] = bytes1(48 + remainder); + } while (v != 0); + bytes memory buf = new bytes(i); + for (uint j = 1; j <= i; j ++) { + buf[j - 1] = reversed[i - j]; + } + return string(buf); + } + /// =============================================================================================================== /// --- Witnet library private methods ---------------------------------------------------------------------------- diff --git a/contracts/libs/WitnetBuffer.sol b/contracts/libs/WitnetBuffer.sol index 9c9493765..46a99d953 100644 --- a/contracts/libs/WitnetBuffer.sol +++ b/contracts/libs/WitnetBuffer.sol @@ -217,27 +217,135 @@ library WitnetBuffer { uint32 value = readUint16(buffer); // Get bit at position 0 uint32 sign = value & 0x8000; - // Get bits 1 to 5, then normalize to the [-14, 15] range so as to counterweight the IEEE 754 exponent bias + // Get bits 1 to 5, then normalize to the [-15, 16] range so as to counterweight the IEEE 754 exponent bias int32 exponent = (int32(value & 0x7c00) >> 10) - 15; // Get bits 6 to 15 - int32 significand = int32(value & 0x03ff); - // Add 1024 to the fraction if the exponent is 0 - if (exponent == 15) { - significand |= 0x400; + int32 fraction = int32(value & 0x03ff); + // Add 2^10 to the fraction if exponent is not -15 + if (exponent != -15) { + fraction |= 0x400; + } else if (exponent == 16) { + revert( + string(abi.encodePacked( + "WitnetBuffer.readFloat16: ", + sign != 0 ? "negative" : hex"", + " infinity" + )) + ); } // Compute `2 ^ exponent · (1 + fraction / 1024)` if (exponent >= 0) { - result = ( - int32((int256(1 << uint256(int256(exponent))) + result = int32( + int(1 << uint256(int256(exponent))) * 10000 - * int256(uint256(int256(significand)) | 0x400)) >> 10) + * fraction + ) >> 10; + } else { + result = int32( + int(fraction) + * 10000 + / int(1 << uint(int(- exponent))) + ) >> 10; + } + // Make the result negative if the sign bit is not 0 + if (sign != 0) { + result *= -1; + } + } + + /// @notice Consume the next 4 bytes from the buffer as an IEEE 754-2008 floating point number enclosed into an `int`. + /// @dev Due to the lack of support for floating or fixed point arithmetic in the EVM, this method offsets all values + /// by 9 decimal orders so as to get a fixed precision of 9 decimal positions, which should be OK for most `float32` + /// use cases. In other words, the integer output of this method is 10^9 times the actual value. The input bytes are + /// expected to follow the 64-bit base-2 format (a.k.a. `binary32`) in the IEEE 754-2008 standard. + /// @param buffer An instance of `Buffer`. + /// @return result The `int` value of the next 8 bytes in the buffer counting from the cursor position. + function readFloat32(Buffer memory buffer) + internal pure + returns (int result) + { + uint value = readUint32(buffer); + // Get bit at position 0 + uint sign = value & 0x80000000; + // Get bits 1 to 8, then normalize to the [-127, 128] range so as to counterweight the IEEE 754 exponent bias + int exponent = (int(value & 0x7f800000) >> 23) - 127; + // Get bits 9 to 31 + int fraction = int(value & 0x007fffff); + // Add 2^23 to the fraction if exponent is not -127 + if (exponent != -127) { + fraction |= 0x800000; + } else if (exponent == 128) { + revert( + string(abi.encodePacked( + "WitnetBuffer.readFloat32: ", + sign != 0 ? "negative" : hex"", + " infinity" + )) + ); + } + // Compute `2 ^ exponent · (1 + fraction / 2^23)` + if (exponent >= 0) { + result = ( + int(1 << uint(exponent)) + * (10 ** 9) + * fraction + ) >> 23; + } else { + result = ( + fraction + * (10 ** 9) + / int(1 << uint(-exponent)) + ) >> 23; + } + // Make the result negative if the sign bit is not 0 + if (sign != 0) { + result *= -1; + } + } + + /// @notice Consume the next 8 bytes from the buffer as an IEEE 754-2008 floating point number enclosed into an `int`. + /// @dev Due to the lack of support for floating or fixed point arithmetic in the EVM, this method offsets all values + /// by 15 decimal orders so as to get a fixed precision of 15 decimal positions, which should be OK for most `float64` + /// use cases. In other words, the integer output of this method is 10^15 times the actual value. The input bytes are + /// expected to follow the 64-bit base-2 format (a.k.a. `binary64`) in the IEEE 754-2008 standard. + /// @param buffer An instance of `Buffer`. + /// @return result The `int` value of the next 8 bytes in the buffer counting from the cursor position. + function readFloat64(Buffer memory buffer) + internal pure + returns (int result) + { + uint value = readUint64(buffer); + // Get bit at position 0 + uint sign = value & 0x8000000000000000; + // Get bits 1 to 12, then normalize to the [-1023, 1024] range so as to counterweight the IEEE 754 exponent bias + int exponent = (int(value & 0x7ff0000000000000) >> 52) - 1023; + // Get bits 6 to 15 + int fraction = int(value & 0x000fffffffffffff); + // Add 2^52 to the fraction if exponent is not -1023 + if (exponent != -1023) { + fraction |= 0x10000000000000; + } else if (exponent == 1024) { + revert( + string(abi.encodePacked( + "WitnetBuffer.readFloat64: ", + sign != 0 ? "negative" : hex"", + " infinity" + )) ); + } + // Compute `2 ^ exponent · (1 + fraction / 1024)` + if (exponent >= 0) { + result = ( + int(1 << uint(exponent)) + * (10 ** 15) + * fraction + ) >> 52; } else { - result = (int32( - ((int256(uint256(int256(significand)) | 0x400) * 10000) - / int256(1 << uint256(int256(- exponent)))) - >> 10 - )); + result = ( + fraction + * (10 ** 15) + / int(1 << uint(-exponent)) + ) >> 52; } // Make the result negative if the sign bit is not 0 if (sign != 0) { diff --git a/contracts/libs/WitnetCBOR.sol b/contracts/libs/WitnetCBOR.sol index 2c4f94be2..fa0249ff7 100644 --- a/contracts/libs/WitnetCBOR.sol +++ b/contracts/libs/WitnetCBOR.sol @@ -9,9 +9,6 @@ import "./WitnetBuffer.sol"; /// the gas cost of decoding them into a useful native type. /// @dev Most of the logic has been borrowed from Patrick Gansterer’s cbor.js library: https://github.com/paroga/cbor-js /// @author The Witnet Foundation. -/// -/// TODO: add support for Float32 (majorType = 7, additionalInformation = 26) -/// TODO: add support for Float64 (majorType = 7, additionalInformation = 27) library WitnetCBOR { @@ -157,6 +154,11 @@ library WitnetCBOR { if ( self.majorType == MAJOR_TYPE_INT || self.majorType == MAJOR_TYPE_NEGATIVE_INT + || ( + self.majorType == MAJOR_TYPE_CONTENT_FREE + && self.additionalInformation >= 25 + && self.additionalInformation <= 27 + ) ) { self.buffer.cursor += self.peekLength(); } else if ( @@ -359,6 +361,42 @@ library WitnetCBOR { } } + /// @notice Decode a `CBOR` structure into a `fixed32` value. + /// @dev Due to the lack of support for floating or fixed point arithmetic in the EVM, this method offsets all values + /// by 9 decimal orders so as to get a fixed precision of 9 decimal positions, which should be OK for most `fixed64` + /// use cases. In other words, the output of this method is 10^9 times the actual value, encoded into an `int`. + /// @param cbor An instance of `CBOR`. + /// @return The value represented by the input, as an `int` value. + function readFloat32(CBOR memory cbor) + internal pure + isMajorType(cbor, MAJOR_TYPE_CONTENT_FREE) + returns (int) + { + if (cbor.additionalInformation == 26) { + return cbor.buffer.readFloat32(); + } else { + revert UnsupportedPrimitive(cbor.additionalInformation); + } + } + + /// @notice Decode a `CBOR` structure into a `fixed64` value. + /// @dev Due to the lack of support for floating or fixed point arithmetic in the EVM, this method offsets all values + /// by 15 decimal orders so as to get a fixed precision of 15 decimal positions, which should be OK for most `fixed64` + /// use cases. In other words, the output of this method is 10^15 times the actual value, encoded into an `int`. + /// @param cbor An instance of `CBOR`. + /// @return The value represented by the input, as an `int` value. + function readFloat64(CBOR memory cbor) + internal pure + isMajorType(cbor, MAJOR_TYPE_CONTENT_FREE) + returns (int) + { + if (cbor.additionalInformation == 27) { + return cbor.buffer.readFloat64(); + } else { + revert UnsupportedPrimitive(cbor.additionalInformation); + } + } + /// @notice Decode a `CBOR` structure into a native `int128[]` value whose inner values follow the same convention /// @notice as explained in `decodeFixed16`. /// @param cbor An instance of `CBOR`. diff --git a/contracts/libs/WitnetEncodingLib.sol b/contracts/libs/WitnetEncodingLib.sol index ef867ced5..e0573f223 100644 --- a/contracts/libs/WitnetEncodingLib.sol +++ b/contracts/libs/WitnetEncodingLib.sol @@ -23,6 +23,31 @@ library WitnetEncodingLib { // ff010203050406070101ffffffffffff // 02ff050404000106060707ffffffffff + error RadonFilterMissingArgs(uint8 opcode); + + error RadonRequestNoSources(); + error RadonRequestSourcesArgsMismatch(uint expected, uint actual); + error RadonRequestMissingArgs(uint index, uint expected, uint actual); + error RadonRequestResultsMismatch(uint index, uint8 read, uint8 expected); + error RadonRequestTooHeavy(bytes bytecode, uint weight); + + error RadonSlaNoReward(); + error RadonSlaNoWitnesses(); + error RadonSlaTooManyWitnesses(uint256 numWitnesses); + error RadonSlaConsensusOutOfRange(uint256 percentage); + error RadonSlaLowCollateral(uint256 witnessCollateral); + + error UnsupportedDataRequestMethod(uint8 method, string schema, string body, string[2][] headers); + error UnsupportedRadonDataType(uint8 datatype, uint256 maxlength); + error UnsupportedRadonFilterOpcode(uint8 opcode); + error UnsupportedRadonFilterArgs(uint8 opcode, bytes args); + error UnsupportedRadonReducerOpcode(uint8 opcode); + error UnsupportedRadonReducerScript(uint8 opcode, bytes script, uint256 offset); + error UnsupportedRadonScript(bytes script, uint256 offset); + error UnsupportedRadonScriptOpcode(bytes script, uint256 cursor, uint8 opcode); + error UnsupportedRadonTallyScript(bytes32 hash); + + /// =============================================================================================================== /// --- WitnetLib internal methods -------------------------------------------------------------------------------- @@ -335,7 +360,7 @@ library WitnetEncodingLib { && headers.length == 0 && script.length >= 1 )) { - revert WitnetV2.UnsupportedDataRequestMethod( + revert UnsupportedDataRequestMethod( uint8(method), schema, body, @@ -368,7 +393,7 @@ library WitnetEncodingLib { || dataType == WitnetV2.RadonDataTypes.Array ) { if (/*maxDataSize == 0 ||*/maxDataSize > 2048) { - revert WitnetV2.UnsupportedRadonDataType( + revert UnsupportedRadonDataType( uint8(dataType), maxDataSize ); @@ -381,7 +406,7 @@ library WitnetEncodingLib { ) { return 0; // TBD: size(dataType); } else { - revert WitnetV2.UnsupportedRadonDataType( + revert UnsupportedRadonDataType( uint8(dataType), size(dataType) ); @@ -396,18 +421,18 @@ library WitnetEncodingLib { ) { // check filters that require arguments if (filter.args.length == 0) { - revert WitnetV2.RadonFilterMissingArgs(uint8(filter.opcode)); + revert RadonFilterMissingArgs(uint8(filter.opcode)); } } else if ( filter.opcode == WitnetV2.RadonFilterOpcodes.Mode ) { // check filters that don't require any arguments if (filter.args.length > 0) { - revert WitnetV2.UnsupportedRadonFilterArgs(uint8(filter.opcode), filter.args); + revert UnsupportedRadonFilterArgs(uint8(filter.opcode), filter.args); } } else { // reject unsupported opcodes - revert WitnetV2.UnsupportedRadonFilterOpcode(uint8(filter.opcode)); + revert UnsupportedRadonFilterOpcode(uint8(filter.opcode)); } } @@ -422,14 +447,14 @@ library WitnetEncodingLib { || reducer.opcode == WitnetV2.RadonReducerOpcodes.ConcatenateAndHash || reducer.opcode == WitnetV2.RadonReducerOpcodes.AverageMedian )) { - revert WitnetV2.UnsupportedRadonReducerOpcode(uint8(reducer.opcode)); + revert UnsupportedRadonReducerOpcode(uint8(reducer.opcode)); } for (uint ix = 0; ix < reducer.filters.length; ix ++) { validate(reducer.filters[ix]); } } else { if (uint8(reducer.opcode) != 0xff || reducer.filters.length > 0) { - revert WitnetV2.UnsupportedRadonReducerScript( + revert UnsupportedRadonReducerScript( uint8(reducer.opcode), reducer.script, 0 @@ -442,21 +467,21 @@ library WitnetEncodingLib { public pure { if (sla.witnessReward == 0) { - revert WitnetV2.RadonSlaNoReward(); + revert RadonSlaNoReward(); } if (sla.numWitnesses == 0) { - revert WitnetV2.RadonSlaNoWitnesses(); + revert RadonSlaNoWitnesses(); } else if (sla.numWitnesses > 127) { - revert WitnetV2.RadonSlaTooManyWitnesses(sla.numWitnesses); + revert RadonSlaTooManyWitnesses(sla.numWitnesses); } if ( sla.minConsensusPercentage < 51 || sla.minConsensusPercentage > 99 ) { - revert WitnetV2.RadonSlaConsensusOutOfRange(sla.minConsensusPercentage); + revert RadonSlaConsensusOutOfRange(sla.minConsensusPercentage); } if (sla.witnessCollateral < 10 ** 9) { - revert WitnetV2.RadonSlaLowCollateral(sla.witnessCollateral); + revert RadonSlaLowCollateral(sla.witnessCollateral); } } @@ -517,7 +542,7 @@ library WitnetEncodingLib { : uint8(WITNET_RADON_OPCODES_RESULT_TYPES[opcode]) ); if (dataType > uint8(type(WitnetV2.RadonDataTypes).max)) { - revert WitnetV2.UnsupportedRadonScriptOpcode( + revert UnsupportedRadonScriptOpcode( self.buffer.data, cursor, uint8(opcode) diff --git a/contracts/libs/WitnetErrorsLib.sol b/contracts/libs/WitnetErrorsLib.sol index 45151029a..54c017569 100644 --- a/contracts/libs/WitnetErrorsLib.sol +++ b/contracts/libs/WitnetErrorsLib.sol @@ -9,6 +9,10 @@ import "./Witnet.sol"; /// @author The Witnet Foundation. library WitnetErrorsLib { + using Witnet for uint8; + using Witnet for uint256; + using WitnetCBOR for WitnetCBOR.CBOR; + // ================================================================================================================ // --- Library public methods ------------------------------------------------------------------------------------- @@ -19,8 +23,9 @@ library WitnetErrorsLib { public pure returns (Witnet.ResultError memory _error) { - uint[] memory errors = _errorsFromResult(result); - return _fromErrorCodes(errors); + return _fromErrorArray( + _errorsFromResult(result) + ); } /// @notice Extract error code and description string from given CBOR-encoded value. @@ -30,8 +35,8 @@ library WitnetErrorsLib { public pure returns (Witnet.ResultError memory _error) { - uint[] memory errors = _errorsFromCborBytes(cborBytes); - return _fromErrorCodes(errors); + WitnetCBOR.CBOR[] memory errors = _errorsFromCborBytes(cborBytes); + return _fromErrorArray(errors); } @@ -43,7 +48,7 @@ library WitnetErrorsLib { /// @return The `uint[]` error parameters as decoded from the `Witnet.Result`. function _errorsFromCborBytes(bytes memory cborBytes) private pure - returns(uint[] memory) + returns(WitnetCBOR.CBOR[] memory) { Witnet.Result memory result = Witnet.resultFromCborBytes(cborBytes); return _errorsFromResult(result); @@ -54,197 +59,191 @@ library WitnetErrorsLib { /// @return The `uint[]` error parameters as decoded from the `Witnet.Result`. function _errorsFromResult(Witnet.Result memory result) private pure - returns (uint[] memory) + returns (WitnetCBOR.CBOR[] memory) { require(!result.success, "no errors"); - return Witnet.asUintArray(result); + return result.value.readArray(); } - /// @dev Extract Witnet.ResultErrorCodes and error description from given array of uints. - function _fromErrorCodes(uint[] memory errors) + /// @dev Extract Witnet.ResultErrorCodes and error description from given array of CBOR values. + function _fromErrorArray(WitnetCBOR.CBOR[] memory errors) private pure returns (Witnet.ResultError memory _error) { - if (errors.length == 0) { + if (errors.length < 2) { return Witnet.ResultError({ code: Witnet.ResultErrorCodes.Unknown, reason: "Unknown error: no error code was found." }); } else { - _error.code = Witnet.ResultErrorCodes(errors[0]); + _error.code = Witnet.ResultErrorCodes(errors[0].readUint()); } // switch on _error.code if ( _error.code == Witnet.ResultErrorCodes.SourceScriptNotCBOR - && errors.length >= 2 + && errors.length > 1 ) { _error.reason = string(abi.encodePacked( - "Syntax error: source script #", - Witnet.toString(uint8(errors[1])), - " was not a valid CBOR value" + "Witnet: Radon: invalid CBOR value." )); } else if ( _error.code == Witnet.ResultErrorCodes.SourceScriptNotArray - && errors.length >= 2 + && errors.length > 1 ) { _error.reason = string(abi.encodePacked( - "Syntax error: the CBOR value in script #", - Witnet.toString(uint8(errors[1])), - " was not an array of calls" + "Witnet: Radon: CBOR value expected to be an array of calls." )); } else if ( _error.code == Witnet.ResultErrorCodes.SourceScriptNotRADON - && errors.length >= 2 + && errors.length > 1 ) { _error.reason = string(abi.encodePacked( - "Syntax error: the CBOR value in script #", - Witnet.toString(uint8(errors[1])), - " was not a valid Data Request" + "Witnet: Radon: CBOR value expected to be a data request." )); } else if ( _error.code == Witnet.ResultErrorCodes.RequestTooManySources - && errors.length >= 2 + && errors.length > 1 ) { _error.reason = string(abi.encodePacked( - "Complexity error: the request contained too many sources (", - Witnet.toString(uint8(errors[1])), - ")" + "Witnet: Radon: too many sources." )); } else if ( _error.code == Witnet.ResultErrorCodes.ScriptTooManyCalls - && errors.length >= 4 + && errors.length > 1 ) { _error.reason = string(abi.encodePacked( - "Complexity error: script #", - Witnet.toString(uint8(errors[2])), - " from the ", - _stageName(uint8(errors[1])), - " stage contained too many calls (", - Witnet.toString(uint8(errors[3])), - ")" + "Witnet: Radon: too many calls." )); } else if ( _error.code == Witnet.ResultErrorCodes.UnsupportedOperator - && errors.length >= 5 + && errors.length > 3 ) { _error.reason = string(abi.encodePacked( - "Radon script: opcode 0x", - Witnet.toHexString(uint8(errors[4])), - " found at call #", - Witnet.toString(uint8(errors[3])), - " in script #", - Witnet.toString(uint8(errors[2])), - " from ", - _stageName(uint8(errors[1])), - " stage is not supported" + "Witnet: Radon: unsupported '", + errors[2].readString(), + "' for input type '", + errors[1].readString(), + "'." )); } else if ( _error.code == Witnet.ResultErrorCodes.HTTP - && errors.length >= 3 + && errors.length > 2 ) { _error.reason = string(abi.encodePacked( - "External error: source #", - Witnet.toString(uint8(errors[1])), - " failed with HTTP error code: ", - Witnet.toString(uint8(errors[2] / 100)), - Witnet.toString(uint8(errors[2] % 100 / 10)), - Witnet.toString(uint8(errors[2] % 10)) + "Witnet: Retrieval: HTTP/", + errors[1].readUint().toString(), + " error." )); } else if ( _error.code == Witnet.ResultErrorCodes.RetrievalTimeout - && errors.length >= 2 + && errors.length > 1 ) { _error.reason = string(abi.encodePacked( - "External error: source #", - Witnet.toString(uint8(errors[1])), - " could not be retrieved because of a timeout" + "Witnet: Retrieval: timeout." )); } else if ( _error.code == Witnet.ResultErrorCodes.Underflow - && errors.length >= 5 + && errors.length > 1 ) { _error.reason = string(abi.encodePacked( - "Math error: underflow at opcode 0x", - Witnet.toHexString(uint8(errors[4])), - " found at call #", - Witnet.toString(uint8(errors[3])), - " in script #", - Witnet.toString(uint8(errors[2])), - " from ", - _stageName(uint8(errors[1])), - " stage" + "Witnet: Aggregation: math underflow." )); } else if ( _error.code == Witnet.ResultErrorCodes.Overflow - && errors.length >= 5 + && errors.length > 1 ) { _error.reason = string(abi.encodePacked( - "Math error: overflow at opcode 0x", - Witnet.toHexString(uint8(errors[4])), - " found at call #", - Witnet.toString(uint8(errors[3])), - " in script #", - Witnet.toString(uint8(errors[2])), - " from ", - _stageName(uint8(errors[1])), - " stage" + "Witnet: Aggregation: math overflow." )); } else if ( _error.code == Witnet.ResultErrorCodes.DivisionByZero - && errors.length >= 5 + && errors.length > 1 ) { _error.reason = string(abi.encodePacked( - "Math error: division by zero at opcode 0x", - Witnet.toHexString(uint8(errors[4])), - " found at call #", - Witnet.toString(uint8(errors[3])), - " in script #", - Witnet.toString(uint8(errors[2])), - " from ", - _stageName(uint8(errors[1])), - " stage" + "Witnet: Aggregation: division by zero." )); } else if ( _error.code == Witnet.ResultErrorCodes.BridgeMalformedRequest ) { - _error.reason = "Bridge error: malformed data request cannot be processed"; + _error.reason = "Witnet: Bridge: malformed data request cannot be processed."; } else if ( _error.code == Witnet.ResultErrorCodes.BridgePoorIncentives ) { - _error.reason = "Bridge error: rejected due to poor witnessing incentives"; + _error.reason = "Witnet: Bridge: rejected due to poor witnessing incentives."; } else if ( _error.code == Witnet.ResultErrorCodes.BridgeOversizedResult ) { - _error.reason = "Bridge error: rejected due to poor bridging incentives"; - } else if ( - _error.code == Witnet.ResultErrorCodes.RetrievalTimeout - ) { - _error.reason = "External error: at least one of the sources timed out"; + _error.reason = "Witnet: Bridge: rejected due to poor bridging incentives."; } else if ( _error.code == Witnet.ResultErrorCodes.InsufficientConsensus + && errors.length > 3 ) { - _error.reason = "Insufficient witnessing consensus"; + uint reached = (errors[1].additionalInformation == 25 + ? uint(int(errors[1].readFloat16() / 10 ** 4)) + : uint(int(errors[1].readFloat64() / 10 ** 15)) + ); + uint expected = (errors[2].additionalInformation == 25 + ? uint(int(errors[2].readFloat16() / 10 ** 4)) + : uint(int(errors[2].readFloat64() / 10 ** 15)) + ); + _error.reason = string(abi.encodePacked( + "Witnet: Tally: insufficient consensus: ", + reached.toString(), + "% <= ", + expected.toString(), + "%." + )); } else if ( _error.code == Witnet.ResultErrorCodes.InsufficientCommits ) { - _error.reason = "Insufficient witnessing commits"; + _error.reason = "Witnet: Tally: insufficient commits."; } else if ( _error.code == Witnet.ResultErrorCodes.TallyExecution + && errors.length > 3 ) { - _error.reason = "Tally execution error"; + _error.reason = string(abi.encodePacked( + "Witnet: Tally: execution error: ", + errors[2].readString(), + "." + )); } else if ( _error.code == Witnet.ResultErrorCodes.ArrayIndexOutOfBounds + && errors.length > 2 ) { - _error.reason = "Radon script: tried to access a value from an array with an index out of bounds"; + _error.reason = string(abi.encodePacked( + "Witnet: Aggregation: tried to access a value from an array with an index (", + errors[1].readUint().toString(), + ") out of bounds." + )); } else if ( _error.code == Witnet.ResultErrorCodes.MapKeyNotFound + && errors.length > 2 + ) { + _error.reason = string(abi.encodePacked( + "Witnet: Aggregation: tried to access a value from a map with a key (\"", + errors[1].readString(), + "\") that was not found." + )); + } else if ( + _error.code == Witnet.ResultErrorCodes.NoReveals + ) { + _error.reason = "Witnet: Tally: no reveals."; + } else if ( + _error.code == Witnet.ResultErrorCodes.MalformedReveal + ) { + _error.reason = "Witnet: Tally: malformed reveal."; + } else if ( + _error.code == Witnet.ResultErrorCodes.UnhandledIntercept ) { - _error.reason = "Radon script: tried to access a value from a map with a key that does not exist"; + _error.reason = "Witnet: Tally: unhandled intercept."; } else { _error.reason = string(abi.encodePacked( "Unhandled error: 0x", - Witnet.toHexString(uint8(errors[0])) + Witnet.toHexString(uint8(_error.code)), + errors.length > 2 + ? string(abi.encodePacked(" (", uint(errors.length - 1).toString(), " params).")) + : "." )); } } @@ -257,13 +256,13 @@ library WitnetErrorsLib { returns (string memory) { if (stageIndex == 0) { - return "retrieval"; + return "Retrieval"; } else if (stageIndex == 1) { - return "aggregation"; + return "Aggregation"; } else if (stageIndex == 2) { - return "tally"; + return "Tally"; } else { - return "unknown"; + return "(unknown)"; } } } \ No newline at end of file diff --git a/contracts/libs/WitnetPriceFeedsLib.sol b/contracts/libs/WitnetPriceFeedsLib.sol new file mode 100644 index 000000000..44ed75b09 --- /dev/null +++ b/contracts/libs/WitnetPriceFeedsLib.sol @@ -0,0 +1,96 @@ +// SPDX-License-Identifier: MIT + +pragma solidity >=0.7.0 <0.9.0; +pragma experimental ABIEncoderV2; + +import "../interfaces/V2/IWitnetPriceSolver.sol"; +import "../interfaces/V2/IWitnetPriceSolverDeployer.sol"; + +import "./Slices.sol"; + +/// @title Ancillary external library for WitnetPriceFeeds implementations. +/// @dev Features: +/// @dev - deployment of counter-factual IWitnetPriceSolver instances. +/// @dev - validation of feed caption strings. +/// @author The Witnet Foundation. +library WitnetPriceFeedsLib { + + using Slices for string; + using Slices for Slices.Slice; + + function deployPriceSolver( + bytes calldata initcode, + bytes calldata constructorParams + ) + external + returns (address _solver) + { + _solver = determinePriceSolverAddress(initcode, constructorParams); + if (_solver.code.length == 0) { + bytes memory _bytecode = _completeInitCode(initcode, constructorParams); + address _createdContract; + assembly { + _createdContract := create2( + 0, + add(_bytecode, 0x20), + mload(_bytecode), + 0 + ) + } + assert(_solver == _createdContract); + require( + IWitnetPriceSolver(_solver).class() == type(IWitnetPriceSolver).interfaceId, + "WitnetPriceFeedsLib: uncompliant solver implementation" + ); + } + } + + function determinePriceSolverAddress( + bytes calldata initcode, + bytes calldata constructorParams + ) + public view + returns (address) + { + return address( + uint160(uint(keccak256( + abi.encodePacked( + bytes1(0xff), + address(this), + bytes32(0), + keccak256(_completeInitCode(initcode, constructorParams)) + ) + ))) + ); + } + + function validateCaption(bytes32 prefix, string calldata caption) + external pure + returns (uint8) + { + require( + bytes6(bytes(caption)) == bytes6(prefix), + "WitnetPriceFeedsLib: bad caption prefix" + ); + Slices.Slice memory _caption = caption.toSlice(); + Slices.Slice memory _delim = string("-").toSlice(); + string[] memory _parts = new string[](_caption.count(_delim) + 1); + for (uint _ix = 0; _ix < _parts.length; _ix ++) { + _parts[_ix] = _caption.split(_delim).toString(); + } + (uint _decimals, bool _success) = Witnet.tryUint(_parts[_parts.length - 1]); + require(_success, "WitnetPriceFeedsLib: bad decimals"); + return uint8(_decimals); + } + + function _completeInitCode(bytes calldata initcode, bytes calldata constructorParams) + private pure + returns (bytes memory) + { + return abi.encodePacked( + initcode, + constructorParams + ); + } + +} diff --git a/contracts/libs/WitnetV2.sol b/contracts/libs/WitnetV2.sol index 3282b9b13..cbf904b3a 100644 --- a/contracts/libs/WitnetV2.sol +++ b/contracts/libs/WitnetV2.sol @@ -6,105 +6,66 @@ import "./Witnet.sol"; library WitnetV2 { - error IndexOutOfBounds(uint256 index, uint256 range); - error InsufficientBalance(uint256 weiBalance, uint256 weiExpected); - error InsufficientFee(uint256 weiProvided, uint256 weiExpected); - error Unauthorized(address violator); - - error RadonFilterMissingArgs(uint8 opcode); - - error RadonRequestNoSources(); - error RadonRequestSourcesArgsMismatch(uint expected, uint actual); - error RadonRequestMissingArgs(uint index, uint expected, uint actual); - error RadonRequestResultsMismatch(uint index, uint8 read, uint8 expected); - error RadonRequestTooHeavy(bytes bytecode, uint weight); - - error RadonSlaNoReward(); - error RadonSlaNoWitnesses(); - error RadonSlaTooManyWitnesses(uint256 numWitnesses); - error RadonSlaConsensusOutOfRange(uint256 percentage); - error RadonSlaLowCollateral(uint256 witnessCollateral); - - error UnsupportedDataRequestMethod(uint8 method, string schema, string body, string[2][] headers); - error UnsupportedRadonDataType(uint8 datatype, uint256 maxlength); - error UnsupportedRadonFilterOpcode(uint8 opcode); - error UnsupportedRadonFilterArgs(uint8 opcode, bytes args); - error UnsupportedRadonReducerOpcode(uint8 opcode); - error UnsupportedRadonReducerScript(uint8 opcode, bytes script, uint256 offset); - error UnsupportedRadonScript(bytes script, uint256 offset); - error UnsupportedRadonScriptOpcode(bytes script, uint256 cursor, uint8 opcode); - error UnsupportedRadonTallyScript(bytes32 hash); - - function toEpoch(uint _timestamp) internal pure returns (uint) { - return 1 + (_timestamp - 11111) / 15; - } - - function toTimestamp(uint _epoch) internal pure returns (uint) { - return 111111+ _epoch * 15; - } + uint256 constant _WITNET_BLOCK_TIME_SECS = 45; + uint256 constant _WITNET_INCEPTION_TS = 1602666000; + uint256 constant _WITNET_SUPERBLOCK_EPOCHS = 10; struct Beacon { - uint256 escrow; - uint256 evmBlock; - uint256 gasprice; - address relayer; - address slasher; - uint256 superblockIndex; - uint256 superblockRoot; - } - - enum BeaconStatus { - Idle + uint256 index; + uint256 prevIndex; + bytes32 prevRoot; + bytes32 nextBlsRoot; + bytes32 ddrTallyRoot; } - struct Block { - bytes32 blockHash; - bytes32 drTxsRoot; - bytes32 drTallyTxsRoot; + struct FastForward { + Beacon next; + bytes32[] signatures; } - enum BlockStatus { - Idle + /// Possible status of a WitnetV2 query. + enum QueryStatus { + Void, + Posted, + Reported, + Delayed, + Disputed, + Expired, + Finalized } - struct DrPost { - uint256 block; - DrPostStatus status; - DrPostRequest request; - DrPostResponse response; - } - - /// Data kept in EVM-storage for every Request posted to the Witnet Request Board. - struct DrPostRequest { - uint256 epoch; - address requester; + struct Query { + address from; address reporter; - bytes32 radHash; - bytes32 slaHash; + uint256 postEpoch; + uint256 reportEpoch; uint256 weiReward; + uint256 weiStake; + QueryRequest request; + QueryReport report; + QueryCallback callback; + QueryDispute[] disputes; } - /// Data kept in EVM-storage containing Witnet-provided response metadata and result. - struct DrPostResponse { + struct QueryDispute { address disputer; - address reporter; - uint256 escrowed; - uint256 drCommitTxEpoch; - uint256 drTallyTxEpoch; - bytes32 drTallyTxHash; - bytes drTallyResultCborBytes; + QueryReport report; } - enum DrPostStatus { - Void, - Deleted, - Expired, - Posted, - Disputed, - Reported, - Finalized, - Accepted, - Rejected + struct QueryCallback { + address addr; + uint256 gas; + } + + struct QueryRequest { + bytes32 radHash; + bytes32 packedSLA; + } + + struct QueryReport { + address relayer; + uint256 tallyEpoch; + bytes tallyCborBytes; } struct DataProvider { @@ -192,6 +153,56 @@ library WitnetV2 { uint witnessReward; uint witnessCollateral; uint minerCommitRevealFee; + uint minMinerFee; + } + + struct RadonSLAv2 { + uint8 committeeSize; + uint8 committeeConsensus; + uint8 ratioWitCollateral; + uint8 reserved; + uint64 witWitnessReward; + uint64 witMinMinerFee; + } + + function beaconIndexFromEpoch(uint epoch) internal pure returns (uint256) { + return epoch / 10; + } + + function beaconIndexFromTimestamp(uint ts) internal pure returns (uint256) { + return 1 + epochFromTimestamp(ts) / _WITNET_SUPERBLOCK_EPOCHS; + } + + function checkQueryPostStatus( + uint queryPostEpoch, + uint currentEpoch + ) + internal pure + returns (WitnetV2.QueryStatus) + { + if (currentEpoch > queryPostEpoch + _WITNET_SUPERBLOCK_EPOCHS) { + if (currentEpoch > queryPostEpoch + _WITNET_SUPERBLOCK_EPOCHS * 2) { + return WitnetV2.QueryStatus.Expired; + } else { + return WitnetV2.QueryStatus.Delayed; + } + } else { + return WitnetV2.QueryStatus.Posted; + } + } + + function checkQueryReportStatus( + uint queryReportEpoch, + uint currentEpoch + ) + internal pure + returns (WitnetV2.QueryStatus) + { + if (currentEpoch > queryReportEpoch + _WITNET_SUPERBLOCK_EPOCHS) { + return WitnetV2.QueryStatus.Finalized; + } else { + return WitnetV2.QueryStatus.Reported; + } } /// @notice Returns `true` if all witnessing parameters in `b` have same @@ -206,7 +217,107 @@ library WitnetV2 { && a.witnessReward >= b.witnessReward && a.witnessCollateral >= b.witnessCollateral && a.minerCommitRevealFee >= b.minerCommitRevealFee + && a.minMinerFee >= b.minMinerFee + ); + } + + /// @notice Returns `true` if all witnessing parameters in `b` have same + /// @notice value or greater than the ones in `a`. + function equalOrGreaterThan(RadonSLAv2 memory a, RadonSLAv2 memory b) + internal pure + returns (bool) + { + return ( + a.committeeSize >= b.committeeSize + && a.committeeConsensus >= b.committeeConsensus + // && a.ratioEvmCollateral >= b.ratioEvmCollateral + && a.ratioWitCollateral >= b.ratioWitCollateral + && a.witWitnessReward >= b.witWitnessReward + && a.witMinMinerFee >= b.witMinMinerFee + ); + } + + function epochFromTimestamp(uint ts) internal pure returns (uint256) { + return (ts - _WITNET_INCEPTION_TS) / _WITNET_BLOCK_TIME_SECS; + } + + function isValid(RadonSLAv2 calldata sla) internal pure returns (bool) { + return ( + sla.committeeSize >= 1 + && sla.committeeConsensus >= 51 + && sla.committeeConsensus <= 99 + && sla.ratioWitCollateral >= 1 + && sla.ratioWitCollateral <= 127 + && sla.witWitnessReward >= 1 + && sla.witMinMinerFee >= 1 ); } + function pack(RadonSLAv2 calldata sla) internal pure returns (bytes32) { + return bytes32(abi.encodePacked( + sla.committeeSize, + sla.committeeConsensus, + sla.ratioWitCollateral, + uint8(0), + sla.witWitnessReward, + sla.witMinMinerFee + )); + } + + function tallyHash(QueryReport calldata self, bytes32 queryHash) + internal pure + returns (bytes32) + { + return keccak256(abi.encodePacked( + queryHash, + self.relayer, + self.tallyEpoch, + self.tallyCborBytes + )); + } + + function tallyHash(QueryReport storage self, bytes32 queryHash) + internal view + returns (bytes32) + { + return keccak256(abi.encodePacked( + queryHash, + self.relayer, + self.tallyEpoch, + self.tallyCborBytes + )); + } + + function toRadonSLAv2(bytes32 packed) internal pure returns (RadonSLAv2 memory sla) { + return RadonSLAv2({ + committeeSize: uint8(bytes1(packed)), + committeeConsensus: uint8(bytes1(packed << 8)), + ratioWitCollateral: uint8(bytes1(packed << 16)), + reserved: 0, + witWitnessReward: uint64(bytes8(packed << 32)), + witMinMinerFee: uint64(bytes8(packed << 96)) + }); + } + + function merkle(bytes32[] calldata items) internal pure returns (bytes32) { + // TODO + } + + function merkle(WitnetV2.Beacon memory self) internal pure returns (bytes32) { + // TODO + } + + function verifyFastForward(WitnetV2.Beacon memory self, WitnetV2.FastForward calldata ff) + internal pure + returns (WitnetV2.Beacon memory) + { + require( + self.index == ff.next.prevIndex + && merkle(self) == ff.next.prevRoot, + "WitnetV2: misplaced fastforward" + ); + // TODO verify ff proofs + return ff.next; + } + } \ No newline at end of file diff --git a/contracts/patterns/Escrowable.sol b/contracts/patterns/Escrowable.sol new file mode 100644 index 000000000..27a53651c --- /dev/null +++ b/contracts/patterns/Escrowable.sol @@ -0,0 +1,29 @@ +// SPDX-License-Identifier: MIT + +pragma solidity >=0.8.4 <0.9.0; + +import "./Payable.sol"; + +abstract contract Escrowable + is + Payable +{ + event Staked(address indexed from, uint256 value); + event Slashed(address indexed from, address indexed to, uint256 value); + event Unstaked(address indexed from, uint256 value); + + constructor(IERC20 _currency) + Payable(_currency) + {} + + receive() virtual external payable; + + function atStakeBy(address) virtual external view returns (uint256); + function balanceOf(address) virtual external view returns (uint256); + function withdraw() virtual external returns (uint256); + + function __receive(address from, uint256 value) virtual internal; + function __stake(address from, uint256 value) virtual internal; + function __slash(address from, address to, uint256 value) virtual internal; + function __unstake(address from, uint256 value) virtual internal; +} \ No newline at end of file diff --git a/contracts/patterns/Payable.sol b/contracts/patterns/Payable.sol index 6ff570307..e0336fdb3 100644 --- a/contracts/patterns/Payable.sol +++ b/contracts/patterns/Payable.sol @@ -7,10 +7,10 @@ import "../interfaces/IERC20.sol"; abstract contract Payable { IERC20 public immutable currency; - event Received(address from, uint256 value); - event Transfer(address to, uint256 value); + event Received(address indexed from, uint256 value); + event Transfer(address indexed to, uint256 value); - constructor(address _currency) { + constructor(IERC20 _currency) { currency = IERC20(_currency); } @@ -21,5 +21,5 @@ abstract contract Payable { function _getMsgValue() internal view virtual returns (uint256); /// Perform safe transfer or whatever token is used for paying rewards. - function _safeTransferTo(address payable, uint256) internal virtual; + function __safeTransferTo(address payable, uint256) internal virtual; } diff --git a/migrations/scripts/5_WitnetRequestBoard.js b/migrations/scripts/5_WitnetRequestBoard.js index 7aa939ba0..a8827d66a 100644 --- a/migrations/scripts/5_WitnetRequestBoard.js +++ b/migrations/scripts/5_WitnetRequestBoard.js @@ -81,22 +81,31 @@ module.exports = async function (deployer, network, [, from, reporter]) { let board if (utils.isNullAddress(addresses[ecosystem][network]?.WitnetRequestBoardImplementation)) { - await deployer.link(WitnetErrorsLib, WitnetRequestBoardImplementation) - await deployer.deploy( - WitnetRequestBoardImplementation, - WitnetRequestFactory.address, - /* _isUpgradeable */ true, - /* _versionTag */ utils.fromAscii(version), - ...( - // if defined, use network-specific constructor parameters: - settings.constructorParams[network]?.WitnetRequestBoard || - // otherwise, use ecosystem-specific parameters, if any: - settings.constructorParams[ecosystem]?.WitnetRequestBoard || - // or, default defined parameters for WRBs, if any: - settings.constructorParams?.default?.WitnetRequestBoard - ), - { from } - ) + if (WitnetRequestBoardImplementation.contractName === "WitnetRequestBoardBypass") { + await deployer.deploy( + WitnetRequestBoardImplementation, + addresses[ecosystem][network]?.WitnetRequestBoardBypass, + true, + utils.fromAscii(version) + ) + } else { + await deployer.link(WitnetErrorsLib, WitnetRequestBoardImplementation) + await deployer.deploy( + WitnetRequestBoardImplementation, + WitnetRequestFactory.address, + /* _isUpgradeable */ true, + /* _versionTag */ utils.fromAscii(version), + ...( + // if defined, use network-specific constructor parameters: + settings.constructorParams[network]?.WitnetRequestBoard || + // otherwise, use ecosystem-specific parameters, if any: + settings.constructorParams[ecosystem]?.WitnetRequestBoard || + // or, default defined parameters for WRBs, if any: + settings.constructorParams?.default?.WitnetRequestBoard + ), + { from } + ) + } board = await WitnetRequestBoardImplementation.deployed() addresses[ecosystem][network].WitnetRequestBoardImplementation = board.address if (!isDryRun) { diff --git a/migrations/scripts/7_WitnetPriceFeeds.js b/migrations/scripts/7_WitnetPriceFeeds.js index fc35fc12e..9ff07babb 100644 --- a/migrations/scripts/7_WitnetPriceFeeds.js +++ b/migrations/scripts/7_WitnetPriceFeeds.js @@ -15,6 +15,7 @@ const Create2Factory = artifacts.require("Create2Factory") const WitnetProxy = artifacts.require("WitnetProxy") const WitnetPriceFeeds = artifacts.require("WitnetPriceFeeds") +const WitnetPriceFeedsLib = artifacts.require("WitnetPriceFeedsLib") const WitnetRequestBoard = artifacts.require("WitnetRequestBoard") module.exports = async function (deployer, network, [, from]) { @@ -31,6 +32,21 @@ module.exports = async function (deployer, network, [, from]) { return } + if (utils.isNullAddress(addresses[ecosystem][network]?.WitnetPriceFeedsLib)) { + await deployer.deploy(WitnetPriceFeedsLib, { from }) + const lib = await WitnetPriceFeedsLib.deployed() + addresses[ecosystem][network].WitnetPriceFeedsLib = lib.address + if (!isDryRun) { + utils.saveAddresses(addresses) + } + } else { + const lib = await WitnetPriceFeedsLib.at(addresses[ecosystem][network]?.WitnetPriceFeedsLib) + WitnetPriceFeedsLib.address = lib.address + utils.traceHeader("Skipping 'WitnetPriceFeedsLib'") + console.info(" ", "> library address:", lib.address) + console.info() + } + const artifactNames = merge(settings.artifacts.default, settings.artifacts[ecosystem], settings.artifacts[network]) const WitnetPriceFeedsImplementation = artifacts.require(artifactNames.WitnetPriceFeeds) @@ -85,6 +101,7 @@ module.exports = async function (deployer, network, [, from]) { let router if (utils.isNullAddress(addresses[ecosystem][network]?.WitnetPriceFeedsImplementation)) { + await deployer.link(WitnetPriceFeedsLib, WitnetPriceFeedsImplementation) await deployer.deploy( WitnetPriceFeedsImplementation, WitnetRequestBoard.address, diff --git a/migrations/witnet.addresses.json b/migrations/witnet.addresses.json index b1e3f41c1..2ba3efb9c 100644 --- a/migrations/witnet.addresses.json +++ b/migrations/witnet.addresses.json @@ -41,12 +41,12 @@ "WitnetRequestBoard": "0x777777772C24e6CD34B464D1d71616C444254537", "WitnetRequestFactory": "0x1111111FDE7dC956E3d7922Bc779D9E2349Afb63", "WitnetRequestRandomness": "0xF1Aba51c5097487a62DA9c72fb9Aa7B0c98676C1", - "WitnetErrorsLib": "0x1225A47bC743199dFef9FEEf065b3B76695AaaaC", + "WitnetErrorsLib": "", "WitnetEncodingLib": "0x30e7D86b3DcdC0CC90509b1F7C27eA8e5481FAc5", "WitnetBytecodesImplementation": "0x5832e99368877a63dd1c2cea941C2b43E1F6b16A", - "WitnetPriceFeedsImplementation": "0x0d13c6058DDE86da77565ED6038C065Af71e9208", + "WitnetPriceFeedsImplementation": "", "WitnetRandomnessImplementation": "0xa239729c399c9eBae7fdc188A1Dbb2c4a06Cd4Bb", - "WitnetRequestBoardImplementation": "0x0e4F5763f417BB13AF759da4d36af50d13F02730", + "WitnetRequestBoardImplementation": "", "WitnetRequestFactoryImplementation": "0xc42656C501623859565C6d080a7Feb1f2B72b55a" } }, @@ -182,12 +182,12 @@ "WitnetRandomness": "0x88888885966F8F77cC6E797aE263C4d091e44A55", "WitnetRequestBoard": "0x777777772C24e6CD34B464D1d71616C444254537", "WitnetRequestFactory": "0x1111111FDE7dC956E3d7922Bc779D9E2349Afb63", - "WitnetErrorsLib": "0x2ef492c5300Ba8002ECC1ee44fD758be1d3B6e89", + "WitnetErrorsLib": "", "WitnetEncodingLib": "0x6F68A4d58cEd8e094b42511350527Ed628ACB970", "WitnetBytecodesImplementation": "0x7ab66AB288A143D4e07Aff9b729165bFb71DB73a", - "WitnetPriceFeedsImplementation": "0x2426a2B2328aD372aaB21ACdF104CDc2e6B5e725", + "WitnetPriceFeedsImplementation": "", "WitnetRandomnessImplementation": "0x9597b5708CDB58fF057ca494574951Fc3d9163f7", - "WitnetRequestBoardImplementation": "0x7a56A80A9B169c046EdD1d8f584455a394bc9C71", + "WitnetRequestBoardImplementation": "", "WitnetRequestFactoryImplementation": "0xd8875D7D3087DEec0103d47d4cE0C4a5414874E1", "WitnetRequestRandomness": "0x34fcD46c6D97A534BdC31Eb9d1e5273298CA8034" }, @@ -199,12 +199,12 @@ "WitnetRandomness": "0x88888885966F8F77cC6E797aE263C4d091e44A55", "WitnetRequestBoard": "0x777777772C24e6CD34B464D1d71616C444254537", "WitnetRequestFactory": "0x1111111FDE7dC956E3d7922Bc779D9E2349Afb63", - "WitnetErrorsLib": "0x0Dd81412825b9C3960195ab47F14dFa9Fd70e36e", + "WitnetErrorsLib": "", "WitnetEncodingLib": "0x1225A47bC743199dFef9FEEf065b3B76695AaaaC", "WitnetBytecodesImplementation": "0xF503A52bEcF03d9d5fc85459906a4d280142B1cd", "WitnetPriceRouterImplementation": "0x0Aa147F25CE8BfaA4E6B4B2CCa91f595bD732CD4", "WitnetRandomnessImplementation": "0x92a68143Ee3C2527C2B07e4354efAF89fd75a359", - "WitnetRequestBoardImplementation": "0x0fa3a073f48c985151875A0979A7222615F642E4", + "WitnetRequestBoardImplementation": "", "WitnetRequestFactoryImplementation": "0x49dbaA6eE184Da1bCAba0764B5Daa363d49b3fe5", "WitnetRequestRandomness": "0x217C32Cf5755aB281809f29F21c107cD0E4652B3" } @@ -219,12 +219,12 @@ "WitnetRandomness": "0x88888885966F8F77cC6E797aE263C4d091e44A55", "WitnetRequestBoard": "0x777777772C24e6CD34B464D1d71616C444254537", "WitnetRequestFactory": "0x1111111FDE7dC956E3d7922Bc779D9E2349Afb63", - "WitnetErrorsLib": "0xf6d52770453166de85B6B6260Cf22196bC460E88", + "WitnetErrorsLib": "", "WitnetEncodingLib": "0xB5447342cA17A40e59d410b340ba412E22e36201", "WitnetBytecodesImplementation": "0x705E076F3387cFd59708D8D8508CECe3e1C65C87", - "WitnetPriceFeedsImplementation": "0x7833F9B08F4236D77ec6287d667F533bc2F1c1EE", + "WitnetPriceFeedsImplementation": "", "WitnetRandomnessImplementation": "0x7A6C2Aad6b4b08De09477D0F663b8f90b0db9662", - "WitnetRequestBoardImplementation": "0xb5F3c9Dc6Ca7C1078cE5c51c1cE030D6BEEd57E2", + "WitnetRequestBoardImplementation": "", "WitnetRequestFactoryImplementation": "0x364E2b91a4C7563288C3ccF7256BA172935CC550", "WitnetRequestRandomness": "0x6dc0E2AC80550F901cd4d5F5a4C48333A0ca97Ee" } @@ -311,12 +311,12 @@ "WitnetRequestBoard": "0x777777772C24e6CD34B464D1d71616C444254537", "WitnetRequestFactory": "0x1111111FDE7dC956E3d7922Bc779D9E2349Afb63", "WitnetRequestRandomness": "0x6dc0E2AC80550F901cd4d5F5a4C48333A0ca97Ee", - "WitnetErrorsLib": "0x9F026F081b5E1f60d583CE380f30A0a4eF0AB97a", + "WitnetErrorsLib": "", "WitnetEncodingLib": "0xB5447342cA17A40e59d410b340ba412E22e36201", "WitnetBytecodesImplementation": "0x705E076F3387cFd59708D8D8508CECe3e1C65C87", - "WitnetPriceFeedsImplementation": "0xE7f331AB7DFAbd4F9A06807cd9f16128489C28fC", + "WitnetPriceFeedsImplementation": "", "WitnetRandomnessImplementation": "0x7A6C2Aad6b4b08De09477D0F663b8f90b0db9662", - "WitnetRequestBoardImplementation": "0x2ef492c5300Ba8002ECC1ee44fD758be1d3B6e89", + "WitnetRequestBoardImplementation": "", "WitnetRequestFactoryImplementation": "0x364E2b91a4C7563288C3ccF7256BA172935CC550" }, "metis.mainnet": { @@ -337,12 +337,12 @@ "WitnetRequestBoard": "0x02Cd4089679EAA9431a88170fd784e7dE78A2425", "WitnetRequestFactory": "0x1111111FDE7dC956E3d7922Bc779D9E2349Afb63", "WitnetRequestRandomness": "0xb5F3c9Dc6Ca7C1078cE5c51c1cE030D6BEEd57E2", - "WitnetErrorsLib": "0xf6d52770453166de85B6B6260Cf22196bC460E88", + "WitnetErrorsLib": "0x7a56A80A9B169c046EdD1d8f584455a394bc9C71", "WitnetEncodingLib": "0xB5447342cA17A40e59d410b340ba412E22e36201", + "WitnetPriceFeedsLib": "0x685528FA605c31aE076e1ee1707041B7Cb356573", "WitnetBytecodesImplementation": "0x705E076F3387cFd59708D8D8508CECe3e1C65C87", - "WitnetPriceFeedsImplementation": "", "WitnetRandomnessImplementation": "0x65772461641A4A6E8B10c81bae1a132E04e77262", - "WitnetRequestBoardImplementation": "0xeDA4f244FEe1D6a3EA0fB2c0Fc6b7D1c49fEF01D", + "WitnetRequestBoardImplementation": "0xb6E0e5a64C7c02Fa477A5254dca35ED967570DF5", "WitnetRequestFactoryImplementation": "0x364E2b91a4C7563288C3ccF7256BA172935CC550" }, "moonbeam.moonriver": { @@ -396,13 +396,13 @@ "WitnetRequestBoard": "0x58D8ECe142c60f5707594a7C1D90e46eAE5AF431", "WitnetRequestFactory": "0x1111111FDE7dC956E3d7922Bc779D9E2349Afb63", "WitnetRequestRandomness": "0xCFf84e16db3c705aD3C6e96b5575ab9111C874B9", - "WitnetErrorsLib": "0xDAec3242d9c93256e556067395359e24CB0b73CA", + "WitnetErrorsLib": "0x724d8E0a9B83a3Fc1A807b91392cAF5f42233307", "WitnetEncodingLib": "0x3E4F5AB751F3B7D8953Be20CCc3bD0A7a4dea903", + "WitnetPriceFeedsLib": "0x83587BcfD1f75B5D08c4e27F098F99783cc693cb", "WitnetBytecodesImplementation": "0x1a50d6bc99f9a79e8aECFdE71c5A597ef9012C39", - "WitnetPriceFeedsImplementation": "", "WitnetRandomnessImplementation": "0x24Cc52D0603F161E16c3DB29Da4c2bCc07d17C4b", - "WitnetRequestBoardImplementation": "0x83Bf7353Db9EefEf3169e391b1EAcd2A9b2d191d", - "WitnetRequestFactoryImplementation": "0x4779A692aC089E02FD1301B0b53Fa1a02985a83F" + "WitnetRequestBoardImplementation": "0xe0329cad0306dF321CD331C526E6Ccc67ce5c007", + "WitnetRequestFactoryImplementation": "0x4779A692aC089E02FD1301B0b53Fa1a02985a83F" }, "polygon.mainnet": { "WitnetLib": "0x1D9c4a8f8B7b5F9B8e2641D81927f8F8Cc7fF079", @@ -420,12 +420,12 @@ "WitnetRequestBoard": "0x777777772C24e6CD34B464D1d71616C444254537", "WitnetRequestFactory": "0x1111111FDE7dC956E3d7922Bc779D9E2349Afb63", "WitnetRequestRandomness": "0xC768fD0A0EC5B788524227abEBf4Da021e85908c", - "WitnetErrorsLib": "0xef978B8CdA6464D0fBaCE5FBC8Ed7A7A8976E094", + "WitnetErrorsLib": "", "WitnetEncodingLib": "0x98DbB216138aA6a0b3ff2ae9bBdeC254398E5B2E", "WitnetBytecodesImplementation": "0xFB36b14df6D319A5A7F418C80b0700664A4f9e6a", "WitnetPriceFeedsImplementation": "", "WitnetRandomnessImplementation": "0x3f189fAc162d3CC6d84EF72c8177afAd8f3DBeE1", - "WitnetRequestBoardImplementation": "0x2F0912fa566B5B3215e746Dc108d85fDd4A8113A", + "WitnetRequestBoardImplementation": "", "WitnetRequestFactoryImplementation": "0x17189FaFd8Dda06ccc2086e12A11693ee552B807" } }, @@ -454,12 +454,12 @@ "WitnetRequestBoard": "0x777777772C24e6CD34B464D1d71616C444254537", "WitnetRequestBoardBypassed": "0x0000007F26760C151AC86695D5846D21e7828B67", "WitnetRequestFactory": "0x1111111FDE7dC956E3d7922Bc779D9E2349Afb63", - "WitnetErrorsLib": "0x5c1dD29563203883A5D5C3136783D3119B2C4e57", + "WitnetErrorsLib": "", "WitnetEncodingLib": "0xd0f725Bf11bA75D291506d396FbcbeCAb5384e95", "WitnetBytecodesImplementation": "0x71D5D7F2ed2436062946727DB84E44588F765D02", "WitnetPriceFeedsImplementation": "", "WitnetRandomnessImplementation": "0x3caF71061A07A77347d5c01Fb37b53D5B3865B9A", - "WitnetRequestBoardImplementation": "0xEBef903399a4FD3Ba035c2b9824Bb468bB3d7060", + "WitnetRequestBoardImplementation": "", "WitnetRequestFactoryImplementation": "0xE7f331AB7DFAbd4F9A06807cd9f16128489C28fC", "WitnetRequestRandomness": "0x578F143c36654DD361FdD51F6D2693b4621ac455" } diff --git a/migrations/witnet.settings.js b/migrations/witnet.settings.js index 8c5a02b21..2f267a940 100644 --- a/migrations/witnet.settings.js +++ b/migrations/witnet.settings.js @@ -194,6 +194,17 @@ module.exports = { }, }, boba: { + "boba.bnb.testnet": { + network_id: 9728, + host: "localhost", + port: 8510, + skipDryRun: true, + verify: { + apiUrl: "https://blockexplorer.testnet.bnb.boba.network/api", + browserURL: "https://blockexplorer.testnet.bnb.boba.network/", + apiKey: "MY_API_KEY", + }, + }, "boba.moonbeam.bobabase": { network_id: 1297, host: "localhost", @@ -318,7 +329,7 @@ module.exports = { verify: { apiUrl: "https://esc-testnet.elastos.io/api", browserURL: "https://esc-testnet.elastos.io/address", - apiKey: "MY-API-KEY", + apiKey: "D75NV1MFN41XRSE9SWV9BA3QZKUD1U9RM3", }, }, "elastos.mainnet": { @@ -329,6 +340,7 @@ module.exports = { verify: { apiUrl: "https://esc.elastos.io/api", browserURL: "https://esc.elastos.io/address", + apiKey: "D75NV1MFN41XRSE9SWV9BA3QZKUD1U9RM3", }, }, }, diff --git a/package.json b/package.json index b3641d9b2..dbfa596a0 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "witnet-solidity-bridge", - "version": "0.7.6", + "version": "0.8.1", "description": "Witnet Solidity Bridge contracts for EVM-compatible chains", "main": "", "scripts": { diff --git a/test/TestWitnetBuffer.sol b/test/TestWitnetBuffer.sol index a216c96e9..b0c9c5e74 100644 --- a/test/TestWitnetBuffer.sol +++ b/test/TestWitnetBuffer.sol @@ -83,10 +83,6 @@ contract TestWitnetBuffer { ); } - function testReplaceMissingArgs() external { - // TODO - } - function testReplace() external { string memory input = "\\0\\/image/\\1\\?digest=sha-256"; string[] memory args = new string[](2); @@ -235,6 +231,28 @@ contract TestWitnetBuffer { Assert.equal(uint(actual), uint(expected), "Read Uint64 from a Buffer"); } + function testReadFloat64() external { + WitnetBuffer.Buffer memory buf; + buf = WitnetBuffer.Buffer(hex"3FE051EB851EB852", 0); + Assert.equal( + buf.readFloat64(), + 510000000000000, + "Reading Float64(0.51)" + ); + buf = WitnetBuffer.Buffer(hex"3FE5555555555555", 0); + Assert.equal( + buf.readFloat64(), + 666666666666666, + "Reading Float64(2/3)" + ); + buf = WitnetBuffer.Buffer(hex"400921FB54442D18", 0); + Assert.equal( + buf.readFloat64(), + 3141592653589793, + "Reading Float64(pi)" + ); + } + function testMultipleReadHead() external { uint8 small = 31; uint64 big = 3141592653589793238; diff --git a/test/TestWitnetErrorsLib.sol b/test/TestWitnetErrorsLib.sol index b6239a1b1..486e112d7 100644 --- a/test/TestWitnetErrorsLib.sol +++ b/test/TestWitnetErrorsLib.sol @@ -11,153 +11,154 @@ contract TestWitnetErrorsLib { using Witnet for Witnet.Result; event Log(bytes data, uint256 length); + event Error(Witnet.ResultError log); // Test decoding of `RadonError` error codes function testErrorCodes1() external { - (Witnet.ResultErrorCodes errorCodeEmpty,) = WitnetErrorsLib.resultErrorFromCborBytes(hex"D82780"); - (Witnet.ResultErrorCodes errorCode0x00,) = WitnetErrorsLib.resultErrorFromCborBytes(hex"D8278100"); - (Witnet.ResultErrorCodes errorCode0x01,) = WitnetErrorsLib.resultErrorFromCborBytes(hex"D8278101"); - (Witnet.ResultErrorCodes errorCode0x02,) = WitnetErrorsLib.resultErrorFromCborBytes(hex"D8278102"); - (Witnet.ResultErrorCodes errorCode0x03,) = WitnetErrorsLib.resultErrorFromCborBytes(hex"D8278103"); - (Witnet.ResultErrorCodes errorCode0x10,) = WitnetErrorsLib.resultErrorFromCborBytes(hex"D8278110"); - (Witnet.ResultErrorCodes errorCode0x11,) = WitnetErrorsLib.resultErrorFromCborBytes(hex"D8278111"); - (Witnet.ResultErrorCodes errorCode0x20,) = WitnetErrorsLib.resultErrorFromCborBytes(hex"D827811820"); - (Witnet.ResultErrorCodes errorCode0x30,) = WitnetErrorsLib.resultErrorFromCborBytes(hex"D827811830"); - (Witnet.ResultErrorCodes errorCode0x31,) = WitnetErrorsLib.resultErrorFromCborBytes(hex"D827811831"); - (Witnet.ResultErrorCodes errorCode0x40,) = WitnetErrorsLib.resultErrorFromCborBytes(hex"D827811840"); - (Witnet.ResultErrorCodes errorCode0x41,) = WitnetErrorsLib.resultErrorFromCborBytes(hex"D827811841"); - (Witnet.ResultErrorCodes errorCode0x42,) = WitnetErrorsLib.resultErrorFromCborBytes(hex"D827811842"); - Assert.equal( - uint(errorCodeEmpty), + Witnet.ResultError memory errorEmpty = WitnetErrorsLib.resultErrorFromCborBytes(hex"D82780"); + Witnet.ResultError memory error0x00 = WitnetErrorsLib.resultErrorFromCborBytes(hex"D8278100"); + Witnet.ResultError memory error0x01 = WitnetErrorsLib.resultErrorFromCborBytes(hex"D8278101"); + Witnet.ResultError memory error0x02 = WitnetErrorsLib.resultErrorFromCborBytes(hex"D8278102"); + Witnet.ResultError memory error0x03 = WitnetErrorsLib.resultErrorFromCborBytes(hex"D8278103"); + Witnet.ResultError memory error0x10 = WitnetErrorsLib.resultErrorFromCborBytes(hex"D8278110"); + Witnet.ResultError memory error0x11 = WitnetErrorsLib.resultErrorFromCborBytes(hex"D8278111"); + Witnet.ResultError memory error0x20 = WitnetErrorsLib.resultErrorFromCborBytes(hex"D827811820"); + Witnet.ResultError memory error0x30 = WitnetErrorsLib.resultErrorFromCborBytes(hex"D827811830"); + Witnet.ResultError memory error0x31 = WitnetErrorsLib.resultErrorFromCborBytes(hex"D827811831"); + Witnet.ResultError memory error0x40 = WitnetErrorsLib.resultErrorFromCborBytes(hex"D827811840"); + Witnet.ResultError memory error0x41 = WitnetErrorsLib.resultErrorFromCborBytes(hex"D827811841"); + Witnet.ResultError memory error0x42 = WitnetErrorsLib.resultErrorFromCborBytes(hex"D827811842"); + Assert.equal( + uint(errorEmpty.code), uint(Witnet.ResultErrorCodes.Unknown), "empty error code `[]` should be `Witnet.ResultErrorCodes.Unknown`" ); Assert.equal( - uint(errorCode0x00), + uint(error0x00.code), uint(Witnet.ResultErrorCodes.Unknown), "error code `0x00` should be `Witnet.ResultErrorCodes.Unknown`" ); Assert.equal( - uint(errorCode0x01), + uint(error0x01.code), uint(Witnet.ResultErrorCodes.SourceScriptNotCBOR), "error code `0x01` should be `Witnet.ResultErrorCodes.SourceScriptNotCBOR`" ); Assert.equal( - uint(errorCode0x02), + uint(error0x02.code), uint(Witnet.ResultErrorCodes.SourceScriptNotArray), "error code `0x02` should be `Witnet.ResultErrorCodes.SourceScriptNotArray`" ); Assert.equal( - uint(errorCode0x03), + uint(error0x03.code), uint(Witnet.ResultErrorCodes.SourceScriptNotRADON), "error code `0x03` should be `Witnet.ResultErrorCodes.SourceScriptNotRADON`" ); Assert.equal( - uint(errorCode0x10), + uint(error0x10.code), uint(Witnet.ResultErrorCodes.RequestTooManySources), "error code `0x10` should be `Witnet.ResultErrorCodes.RequestTooManySources`" ); Assert.equal( - uint(errorCode0x11), + uint(error0x11.code), uint(Witnet.ResultErrorCodes.ScriptTooManyCalls), "error code `0x11` should be `Witnet.ResultErrorCodes.ScriptTooManyCalls`" ); Assert.equal( - uint(errorCode0x20), + uint(error0x20.code), uint(Witnet.ResultErrorCodes.UnsupportedOperator), "error code `0x20` should be `Witnet.ResultErrorCodes.UnsupportedOperator`" ); Assert.equal( - uint(errorCode0x30), + uint(error0x30.code), uint(Witnet.ResultErrorCodes.HTTP), "error code `0x30` should be `Witnet.ResultErrorCodes.HTTP`" ); Assert.equal( - uint(errorCode0x31), + uint(error0x31.code), uint(Witnet.ResultErrorCodes.RetrievalTimeout), "Error code 0x31 should be `Witnet.ResultErrorCodes.RetrievalTimeout`" ); Assert.equal( - uint(errorCode0x40), + uint(error0x40.code), uint(Witnet.ResultErrorCodes.Underflow), "error code `0x40` should be `Witnet.ResultErrorCodes.Underflow`" ); Assert.equal( - uint(errorCode0x41), + uint(error0x41.code), uint(Witnet.ResultErrorCodes.Overflow), "error code `0x41` should be `Witnet.ResultErrorCodes.Overflow`" ); Assert.equal( - uint(errorCode0x42), + uint(error0x42.code), uint(Witnet.ResultErrorCodes.DivisionByZero), "Error code #0x42 should be `Witnet.ResultErrorCodes.DivisionByZero`" ); } function testErrorCodes2() external { - (Witnet.ResultErrorCodes errorCode0x50,) = WitnetErrorsLib.resultErrorFromCborBytes(hex"D827811850"); - (Witnet.ResultErrorCodes errorCode0x51,) = WitnetErrorsLib.resultErrorFromCborBytes(hex"D827811851"); - (Witnet.ResultErrorCodes errorCode0x52,) = WitnetErrorsLib.resultErrorFromCborBytes(hex"D827811852"); - (Witnet.ResultErrorCodes errorCode0x53,) = WitnetErrorsLib.resultErrorFromCborBytes(hex"D827811853"); - (Witnet.ResultErrorCodes errorCode0x60,) = WitnetErrorsLib.resultErrorFromCborBytes(hex"D827811860"); - (Witnet.ResultErrorCodes errorCode0x70,) = WitnetErrorsLib.resultErrorFromCborBytes(hex"D827811870"); - (Witnet.ResultErrorCodes errorCode0x71,) = WitnetErrorsLib.resultErrorFromCborBytes(hex"D827811871"); - (Witnet.ResultErrorCodes errorCode0xE0,) = WitnetErrorsLib.resultErrorFromCborBytes(hex"D8278118E0"); - (Witnet.ResultErrorCodes errorCode0xE1,) = WitnetErrorsLib.resultErrorFromCborBytes(hex"D8278118E1"); - (Witnet.ResultErrorCodes errorCode0xE2,) = WitnetErrorsLib.resultErrorFromCborBytes(hex"D8278118E2"); - (Witnet.ResultErrorCodes errorCode0xFF,) = WitnetErrorsLib.resultErrorFromCborBytes(hex"D8278118FF"); - Assert.equal( - uint(errorCode0x50), + Witnet.ResultError memory error0x50 = WitnetErrorsLib.resultErrorFromCborBytes(hex"D827811850"); + Witnet.ResultError memory error0x51 = WitnetErrorsLib.resultErrorFromCborBytes(hex"D827811851"); + Witnet.ResultError memory error0x52 = WitnetErrorsLib.resultErrorFromCborBytes(hex"D827811852"); + Witnet.ResultError memory error0x53 = WitnetErrorsLib.resultErrorFromCborBytes(hex"D827811853"); + Witnet.ResultError memory error0x60 = WitnetErrorsLib.resultErrorFromCborBytes(hex"D827811860"); + Witnet.ResultError memory error0x70 = WitnetErrorsLib.resultErrorFromCborBytes(hex"D827811870"); + Witnet.ResultError memory error0x71 = WitnetErrorsLib.resultErrorFromCborBytes(hex"D827811871"); + Witnet.ResultError memory error0xe0 = WitnetErrorsLib.resultErrorFromCborBytes(hex"D8278118E0"); + Witnet.ResultError memory error0xe1 = WitnetErrorsLib.resultErrorFromCborBytes(hex"D8278118E1"); + Witnet.ResultError memory error0xe2 = WitnetErrorsLib.resultErrorFromCborBytes(hex"D8278118E2"); + Witnet.ResultError memory error0xff = WitnetErrorsLib.resultErrorFromCborBytes(hex"D8278118FF"); + Assert.equal( + uint(error0x50.code), uint(Witnet.ResultErrorCodes.NoReveals), "Error code #0x50 should be `Witnet.ResultErrorCodes.NoReveals`" ); Assert.equal( - uint(errorCode0x51), + uint(error0x51.code), uint(Witnet.ResultErrorCodes.InsufficientConsensus), "Error code #0x51 should be `Witnet.ResultErrorCodes.InsufficientConsensus`" ); Assert.equal( - uint(errorCode0x52), + uint(error0x52.code), uint(Witnet.ResultErrorCodes.InsufficientCommits), "Error code #0x52 should be `Witnet.ResultErrorCodes.InsufficientCommits`" ); Assert.equal( - uint(errorCode0x53), + uint(error0x53.code), uint(Witnet.ResultErrorCodes.TallyExecution), "Error code #0x53 should be `Witnet.ResultErrorCodes.TallyExecution`" ); Assert.equal( - uint(errorCode0x60), + uint(error0x60.code), uint(Witnet.ResultErrorCodes.MalformedReveal), "Error code #0x60 should be `Witnet.ResultErrorCodes.MalformedReveal`" ); Assert.equal( - uint(errorCode0x70), + uint(error0x70.code), uint(Witnet.ResultErrorCodes.ArrayIndexOutOfBounds), "Error code #0x70 should be `Witnet.ResultErrorCodes.ArrayIndexOutOfBounds`" ); Assert.equal( - uint(errorCode0x71), + uint(error0x71.code), uint(Witnet.ResultErrorCodes.MapKeyNotFound), "Error code #0x71 should be `Witnet.ResultErrorCodes.MapKeyNotFound`" ); Assert.equal( - uint(errorCode0xE0), + uint(error0xe0.code), uint(Witnet.ResultErrorCodes.BridgeMalformedRequest), "Error code #0xE0 should be `Witnet.ResultErrorCodes.BridgeMalformedRequest`" ); Assert.equal( - uint(errorCode0xE1), + uint(error0xe1.code), uint(Witnet.ResultErrorCodes.BridgePoorIncentives), "Error code #0xE1 should be `Witnet.ResultErrorCodes.BridgePoorIncentives`" ); Assert.equal( - uint(errorCode0xE2), + uint(error0xe2.code), uint(Witnet.ResultErrorCodes.BridgeOversizedResult), "Error code #0xE2 should be `Witnet.ResultErrorCodes.BridgeOversizedResult`" ); Assert.equal( - uint(errorCode0xFF), + uint(error0xff.code), uint(Witnet.ResultErrorCodes.UnhandledIntercept), "Error code #0xFF should be `Witnet.ResultErrorCodes.UnhandledIntercept`" ); @@ -165,112 +166,145 @@ contract TestWitnetErrorsLib { // Test decoding of `RadonError` error messages function testErrorMessages() external { - (, string memory errorMessageEmpty) = WitnetErrorsLib.resultErrorFromCborBytes(hex"D82780"); - (, string memory errorMessage0x00) = WitnetErrorsLib.resultErrorFromCborBytes(hex"D8278100"); - (, string memory errorMessage0x01) = WitnetErrorsLib.resultErrorFromCborBytes(hex"D827820102"); - (, string memory errorMessage0x02) = WitnetErrorsLib.resultErrorFromCborBytes(hex"D827820203"); - (, string memory errorMessage0x03) = WitnetErrorsLib.resultErrorFromCborBytes(hex"D827820304"); - (, string memory errorMessage0x10) = WitnetErrorsLib.resultErrorFromCborBytes(hex"D827821005"); - (, string memory errorMessage0x11) = WitnetErrorsLib.resultErrorFromCborBytes(hex"D8278411000708"); - (, string memory errorMessage0x20) = WitnetErrorsLib.resultErrorFromCborBytes(hex"D8278518200108090A"); - (, string memory errorMessage0x30) = WitnetErrorsLib.resultErrorFromCborBytes(hex"D82783183008190141"); - (, string memory errorMessage0x31) = WitnetErrorsLib.resultErrorFromCborBytes(hex"D82782183109"); - (, string memory errorMessage0x40) = WitnetErrorsLib.resultErrorFromCborBytes(hex"D82785184002090A0B"); - (, string memory errorMessage0x41) = WitnetErrorsLib.resultErrorFromCborBytes(hex"D827851841000A0B0C"); - (, string memory errorMessage0x42) = WitnetErrorsLib.resultErrorFromCborBytes(hex"D827851842010B0C0D"); - (, string memory errorMessage0xFF) = WitnetErrorsLib.resultErrorFromCborBytes(hex"D8278118FF"); - Assert.equal( - errorMessageEmpty, - "Unknown error: no error code.", + Witnet.ResultError memory errorEmpty = WitnetErrorsLib.resultErrorFromCborBytes(hex"D82780"); + Witnet.ResultError memory error0x00 = WitnetErrorsLib.resultErrorFromCborBytes(hex"D8278100"); + Witnet.ResultError memory error0x01 = WitnetErrorsLib.resultErrorFromCborBytes(hex"D8278101"); + Witnet.ResultError memory error0x02 = WitnetErrorsLib.resultErrorFromCborBytes(hex"D8278102"); + Witnet.ResultError memory error0x03 = WitnetErrorsLib.resultErrorFromCborBytes(hex"D8278103"); + Witnet.ResultError memory error0x10 = WitnetErrorsLib.resultErrorFromCborBytes(hex"D8278110"); + Witnet.ResultError memory error0x11 = WitnetErrorsLib.resultErrorFromCborBytes(hex"D8278111"); + Witnet.ResultError memory error0x20 = WitnetErrorsLib.resultErrorFromCborBytes(hex"D8278418206b5261646f6e537472696e676a496e74656765724164648101"); + Witnet.ResultError memory error0x30 = WitnetErrorsLib.resultErrorFromCborBytes(hex"D827821830190194"); + Witnet.ResultError memory error0x31 = WitnetErrorsLib.resultErrorFromCborBytes(hex"D827811831"); + Witnet.ResultError memory error0x40 = WitnetErrorsLib.resultErrorFromCborBytes(hex"D827811840"); + Witnet.ResultError memory error0x41 = WitnetErrorsLib.resultErrorFromCborBytes(hex"D827811841"); + Witnet.ResultError memory error0x42 = WitnetErrorsLib.resultErrorFromCborBytes(hex"D827811842"); + Assert.equal( + errorEmpty.reason, + "Unknown error: no error code was found.", "Empty error message `[]` should be properly formatted" ); Assert.equal( - errorMessage0x00, - "Unknown error (0x00)", + error0x00.reason, + "Unhandled error: 0x00.", "Error message 0x00 should be properly formatted" ); Assert.equal( - errorMessage0x01, - "Source script #2 was not a valid CBOR value", + error0x01.reason, + "Witnet: Radon: invalid CBOR value.", "Error message for error code `0x01` (`Witnet.ResultErrorCodes.SourceScriptNotCBOR`) should be properly formatted" ); Assert.equal( - errorMessage0x02, - "The CBOR value in script #3 was not an Array of calls", + error0x02.reason, + "Witnet: Radon: CBOR value expected to be an array of calls.", "Error message for error code `0x02` (`Witnet.ResultErrorCodes.SourceScriptNotArray`) should be properly formatted" ); Assert.equal( - errorMessage0x03, - "The CBOR value in script #4 was not a valid Data Request", + error0x03.reason, + "Witnet: Radon: CBOR value expected to be a data request.", "Error message for error code `0x03` (`Witnet.ResultErrorCodes.SourceScriptNotRADON`) should be properly formatted" ); Assert.equal( - errorMessage0x10, - "The request contained too many sources (5)", + error0x10.reason, + "Witnet: Radon: too many sources.", "Error message for error code `0x10` (`Witnet.ResultErrorCodes.RequestTooManySources`) should be properly formatted" ); Assert.equal( - errorMessage0x11, - "Script #7 from the retrieval stage contained too many calls (8)", + error0x11.reason, + "Witnet: Radon: too many calls.", "Error message for error code `0x11` (`Witnet.ResultErrorCodes.ScriptTooManyCalls`) should be properly formatted" ); Assert.equal( - errorMessage0x20, - "Operator code 0x0A found at call #9 in script #8 from aggregation stage is not supported", + error0x20.reason, + "Witnet: Radon: unsupported 'IntegerAdd' for input type 'RadonString'.", "Error message for error code `0x20` (`Witnet.ResultErrorCodes.UnsupportedOperator`) should be properly formatted" ); Assert.equal( - errorMessage0x30, - "Source #8 could not be retrieved. Failed with HTTP error code: 321", + error0x30.reason, + "Witnet: Retrieval: HTTP/404 error.", "Error message for error code `0x30` (`Witnet.ResultErrorCodes.HTTP`) should be properly formatted" ); Assert.equal( - errorMessage0x31, - "Source #9 could not be retrieved because of a timeout", - "Error message for error code `0x31` (`Witnet.ResultErrorCodes.HTTP`) should be properly formatted" + error0x31.reason, + "Witnet: Retrieval: timeout.", + "Error message for error code `0x31` (`Witnet.ResultErrorCodes.RetrievalTimeout`) should be properly formatted" ); Assert.equal( - errorMessage0x40, - "Underflow at operator code 0x0B found at call #10 in script #9 from tally stage", + error0x40.reason, + "Witnet: Aggregation: math underflow.", "Error message for error code `0x40` (`Witnet.ResultErrorCodes.Underflow`) should be properly formatted" ); Assert.equal( - errorMessage0x41, - "Overflow at operator code 0x0C found at call #11 in script #10 from retrieval stage", + error0x41.reason, + "Witnet: Aggregation: math overflow.", "Error message for error code `0x41` (`Witnet.ResultErrorCodes.Overflow`) should be properly formatted" ); Assert.equal( - errorMessage0x42, - "Division by zero at operator code 0x0D found at call #12 in script #11 from aggregation stage", - "Error message for error code `0x42` (`Witnet.ResultErrorCodes.DivisionByZero`) should be properly formatted" - ); - Assert.equal( - errorMessage0xFF, - "Unknown error (0xFF)", - "Error message for an unknown error should be properly formatted" + error0x42.reason, + "Witnet: Aggregation: division by zero.", + "Error message for e rror code `0x42` (`Witnet.ResultErrorCodes.DivisionByZero`) should be properly formatted" ); } function testBridgeErrorMessages() external { - (, string memory errorMessage0xE0) = WitnetErrorsLib.resultErrorFromCborBytes(hex"D8278118E0"); - (, string memory errorMessage0xE1) = WitnetErrorsLib.resultErrorFromCborBytes(hex"D8278118E1"); - (, string memory errorMessage0xE2) = WitnetErrorsLib.resultErrorFromCborBytes(hex"D8278118E2"); + Witnet.ResultError memory error0xe0 = WitnetErrorsLib.resultErrorFromCborBytes(hex"D8278118E0"); + Witnet.ResultError memory error0xe1 = WitnetErrorsLib.resultErrorFromCborBytes(hex"D8278118E1"); + Witnet.ResultError memory error0xe2 = WitnetErrorsLib.resultErrorFromCborBytes(hex"D8278118E2"); Assert.equal( - errorMessage0xE0, - "The structure of the request is invalid and it cannot be parsed", + error0xe0.reason, + "Witnet: Bridge: malformed data request cannot be processed.", "Error message failed (0xE0)" ); Assert.equal( - errorMessage0xE1, - "The request has been rejected by the bridge node due to poor incentives", + error0xe1.reason, + "Witnet: Bridge: rejected due to poor witnessing incentives.", "Error message failed (0xE1)" ); Assert.equal( - errorMessage0xE2, - "The request result length exceeds a bridge contract defined limit", + error0xe2.reason, + "Witnet: Bridge: rejected due to poor bridging incentives.", "Error message failed (0xE2)" ); } + function testTallyErrorMessages() external { + Witnet.ResultError memory error0x50 = WitnetErrorsLib.resultErrorFromCborBytes(hex"D827811850"); + Witnet.ResultError memory error0x51 = WitnetErrorsLib.resultErrorFromCborBytes(hex"D827831851fb400aaaaaaaaaaaabf95260"); + Witnet.ResultError memory error0x51b = WitnetErrorsLib.resultErrorFromCborBytes(hex"D827831851f95220f95260"); + Witnet.ResultError memory error0x52 = WitnetErrorsLib.resultErrorFromCborBytes(hex"D827811852"); + Witnet.ResultError memory error0x60 = WitnetErrorsLib.resultErrorFromCborBytes(hex"D827811860"); + Witnet.ResultError memory error0xff = WitnetErrorsLib.resultErrorFromCborBytes(hex"D8278118ff"); + Assert.equal( + error0x50.reason, + "Witnet: Tally: no reveals.", + "Error message for error code `0x50` (`Witnet.ResultErrorCodes.NoReveals`) should be properly formatted" + ); + Assert.equal( + error0x51.reason, + "Witnet: Tally: insufficient consensus: 3% <= 51%.", + "Error message for error code `0x51` (`Witnet.ResultErrorCodes.InsufficientConsensus`) should be properly formatted" + ); + Assert.equal( + error0x51b.reason, + "Witnet: Tally: insufficient consensus: 49% <= 51%.", + "Error message for error code `0x51` (`Witnet.ResultErrorCodes.InsufficientConsensus`) should be properly formatted" + ); + Assert.equal( + error0x52.reason, + "Witnet: Tally: insufficient commits.", + "Error message for error code `0x52` (`Witnet.ResultErrorCodes.InsufficientCommits`) should be properly formatted" + ); + Assert.equal( + error0x60.reason, + "Witnet: Tally: malformed reveal.", + "Error message for error code `0x60` (`Witnet.ResultErrorCodes.MalformedReveal`) should be properly formatted" + ); + Assert.equal( + error0xff.reason, + "Witnet: Tally: unhandled intercept.", + "Error message for error code `0xFF` (`Witnet.ResultErrorCodes.UnhandledIntercept`) should be properly formatted" + ); + } + }