diff --git a/solidity/src/Hyperlane.sol b/solidity/src/Hyperlane.sol index 1cd58de5..2f7ea3dd 100644 --- a/solidity/src/Hyperlane.sol +++ b/solidity/src/Hyperlane.sol @@ -11,9 +11,11 @@ contract Hyperlane is IHyperlane { address[] public _validators; - function parseAndVerifyHyMsg( - bytes calldata encodedHyMsg - ) + constructor(address[] memory validators) { + _validators = validators; + } + + function parseAndVerifyHyMsg(bytes calldata encodedHyMsg) public view returns (HyMsg memory hyMsg, bool valid, string memory reason) @@ -22,9 +24,7 @@ contract Hyperlane is IHyperlane { (valid, reason) = verifyHyMsg(hyMsg); } - function verifyHyMsg( - HyMsg memory hyMsg - ) public view returns (bool valid, string memory reason) { + function verifyHyMsg(HyMsg memory hyMsg) public view returns (bool valid, string memory reason) { // TODO: fetch validators from calldata/storage address[] memory validators = _validators; @@ -34,19 +34,12 @@ contract Hyperlane is IHyperlane { // We're using a fixed point number transformation with 1 decimal to deal with rounding. // we check that we have will be able to reach a quorum with the current signatures - if ( - (((validators.length * 10) / 3) * 2) / 10 + 1 > - hyMsg.signatures.length - ) { + if ((((validators.length * 10) / 3) * 2) / 10 + 1 > hyMsg.signatures.length) { return (false, "no quorum"); } // Verify signatures - (bool signaturesValid, string memory invalidReason) = verifySignatures( - hyMsg.hash, - hyMsg.signatures, - validators - ); + (bool signaturesValid, string memory invalidReason) = verifySignatures(hyMsg.hash, hyMsg.signatures, validators); if (!signaturesValid) { return (false, invalidReason); } @@ -54,36 +47,28 @@ contract Hyperlane is IHyperlane { return (true, ""); } - function verifySignatures( - bytes32 hash, - Signature[] memory signatures, - address[] memory validators - ) public pure returns (bool valid, string memory reason) { + function verifySignatures(bytes32 hash, Signature[] memory signatures, address[] memory validators) + public + pure + returns (bool valid, string memory reason) + { uint8 lastIndex = 0; // TODO: break on quorum - for (uint i = 0; i < signatures.length; i++) { + for (uint256 i = 0; i < signatures.length; i++) { Signature memory sig = signatures[i]; - require( - i == 0 || sig.validatorIndex > lastIndex, - "signature indices must be ascending" - ); + require(i == 0 || sig.validatorIndex > lastIndex, "signature indices must be ascending"); lastIndex = sig.validatorIndex; - if ( - ecrecover(hash, sig.v, sig.r, sig.s) != - validators[sig.validatorIndex] - ) { + if (ecrecover(hash, sig.v, sig.r, sig.s) != validators[sig.validatorIndex]) { return (false, "HyMsg signature invalid"); } } return (true, ""); } - function parseHyMsg( - bytes calldata encodedHyMsg - ) public pure returns (HyMsg memory hyMsg) { - uint index = 0; + function parseHyMsg(bytes calldata encodedHyMsg) public pure returns (HyMsg memory hyMsg) { + uint256 index = 0; hyMsg.version = encodedHyMsg.toUint8(index); index += 1; @@ -94,7 +79,7 @@ contract Hyperlane is IHyperlane { index += 1; hyMsg.signatures = new Signature[](signersLen); - for (uint i = 0; i < signersLen; i++) { + for (uint256 i = 0; i < signersLen; i++) { hyMsg.signatures[i].validatorIndex = encodedHyMsg.toUint8(index); index += 1; @@ -107,10 +92,7 @@ contract Hyperlane is IHyperlane { } // Hash the body - bytes memory body = encodedHyMsg.slice( - index, - encodedHyMsg.length - index - ); + bytes memory body = encodedHyMsg.slice(index, encodedHyMsg.length - index); hyMsg.hash = keccak256(abi.encodePacked(keccak256(body))); // Parse the rest of the message diff --git a/solidity/src/Pragma.sol b/solidity/src/Pragma.sol index 573fda4b..1ed60929 100644 --- a/solidity/src/Pragma.sol +++ b/solidity/src/Pragma.sol @@ -13,62 +13,61 @@ import "./libraries/ErrorsLib.sol"; /// @notice The Pragma contract. contract Pragma is IPragma, PragmaDecoder { /* STORAGE */ - uint public validTimePeriodSeconds; - uint public singleUpdateFeeInWei; + uint256 public validTimePeriodSeconds; + uint256 public singleUpdateFeeInWei; mapping(bytes32 => uint64) public latestDataInfoPublishTime; constructor( address _hyperlane, uint16[] memory _dataSourceEmitterChainIds, bytes32[] memory _dataSourceEmitterAddresses, - uint _validTimePeriodSeconds, - uint _singleUpdateFeeInWei - ) - PragmaDecoder( - _hyperlane, - _dataSourceEmitterChainIds, - _dataSourceEmitterAddresses - ) - { + uint256 _validTimePeriodSeconds, + uint256 _singleUpdateFeeInWei + ) PragmaDecoder(_hyperlane, _dataSourceEmitterChainIds, _dataSourceEmitterAddresses) { validTimePeriodSeconds = _validTimePeriodSeconds; singleUpdateFeeInWei = _singleUpdateFeeInWei; } /// @inheritdoc IPragma function updateDataFeeds(bytes[] calldata updateData) external payable { - uint totalNumUpdates = 0; - uint len = updateData.length; - for (uint i = 0; i < len; ) { + uint256 totalNumUpdates = 0; + uint256 len = updateData.length; + for (uint256 i = 0; i < len;) { totalNumUpdates += updateDataInfoFromUpdate(updateData[i]); unchecked { i++; } } - uint requiredFee = getTotalFee(totalNumUpdates); + uint256 requiredFee = getTotalFee(totalNumUpdates); if (msg.value < requiredFee) { revert ErrorsLib.InsufficientFee(); } } /// @inheritdoc IPragma - function getUpdateFee( - bytes[] calldata updateData - ) external view returns (uint feeAmount) { + function getUpdateFee(bytes[] calldata updateData) external view returns (uint256 feeAmount) { // Get the update fee. } - function getTotalFee( - uint totalNumUpdates - ) private view returns (uint requiredFee) { + function getTotalFee(uint256 totalNumUpdates) private view returns (uint256 requiredFee) { return totalNumUpdates * singleUpdateFeeInWei; } - function getPriceNoOlderThan( - bytes32 id, - uint age - ) external view returns (DataFeed memory data) { - // Get the price no older than. + function getPriceUnsafe(bytes32 id) private view returns (DataFeed memory) { + DataFeed memory data = _latestPriceInfo[id]; + if (data.publishTime == 0) { + revert ErrorsLib.DataNotFound(); + } + return data; + } + + function getPriceNoOlderThan(bytes32 id, uint256 age) external view returns (DataFeed memory data) { + data = getPriceUnsafe(id); + + if (diff(block.timestamp, data.publishTime) > age) { + revert ErrorsLib.DataStale(); + } } /// @inheritdoc IPragma @@ -76,11 +75,19 @@ contract Pragma is IPragma, PragmaDecoder { return (latestDataInfoPublishTime[id] != 0); } - function getValidTimePeriod() public view returns (uint) { + function getValidTimePeriod() public view returns (uint256) { return validTimePeriodSeconds; } function version() public pure returns (string memory) { return "1.0.0"; } + + function diff(uint256 x, uint256 y) internal pure returns (uint256) { + if (x > y) { + return x - y; + } else { + return y - x; + } + } } diff --git a/solidity/src/PragmaDecoder.sol b/solidity/src/PragmaDecoder.sol index a810fb2c..87a46263 100644 --- a/solidity/src/PragmaDecoder.sol +++ b/solidity/src/PragmaDecoder.sol @@ -28,77 +28,56 @@ contract PragmaDecoder { ) { hyperlane = IHyperlane(payable(_hyperlane)); - for (uint i = 0; i < _dataSourceEmitterChainIds.length; i++) { - _isValidDataSource[ - keccak256( - abi.encodePacked( - _dataSourceEmitterChainIds[i], - _dataSourceEmitterAddresses[i] - ) - ) - ] = true; + for (uint256 i = 0; i < _dataSourceEmitterChainIds.length; i++) { + _isValidDataSource[keccak256( + abi.encodePacked(_dataSourceEmitterChainIds[i], _dataSourceEmitterAddresses[i]) + )] = true; } } // TODO: set valid data sources - function isValidDataSource( - uint16 chainId, - bytes32 emitterAddress - ) public view returns (bool) { - return - _isValidDataSource[ - keccak256(abi.encodePacked(chainId, emitterAddress)) - ]; + function isValidDataSource(uint16 chainId, bytes32 emitterAddress) public view returns (bool) { + return _isValidDataSource[keccak256(abi.encodePacked(chainId, emitterAddress))]; } - function parseAndVerifyHyMsg( - bytes calldata encodedHyMsg - ) internal view returns (HyMsg memory hyMsg) { + function parseAndVerifyHyMsg(bytes calldata encodedHyMsg) internal view returns (HyMsg memory hyMsg) { { bool valid; - (hyMsg, valid, ) = hyperlane.parseAndVerifyHyMsg(encodedHyMsg); + (hyMsg, valid,) = hyperlane.parseAndVerifyHyMsg(encodedHyMsg); if (!valid) revert ErrorsLib.InvalidHyperlaneCheckpointRoot(); } - if (!isValidDataSource(hyMsg.emitterChainId, hyMsg.emitterAddress)) + if (!isValidDataSource(hyMsg.emitterChainId, hyMsg.emitterAddress)) { revert ErrorsLib.InvalidUpdateDataSource(); + } } - function extractMetadataFromheader( - bytes calldata updateData - ) internal pure returns (uint encodedOffset) { + function extractMetadataFromheader(bytes calldata updateData) internal pure returns (uint256 encodedOffset) { unchecked { encodedOffset = 0; { - uint8 majorVersion = UnsafeCalldataBytesLib.toUint8( - updateData, - encodedOffset - ); + uint8 majorVersion = UnsafeCalldataBytesLib.toUint8(updateData, encodedOffset); encodedOffset += 1; - if (majorVersion != ConstantsLib.MAJOR_VERSION) + if (majorVersion != ConstantsLib.MAJOR_VERSION) { revert ErrorsLib.InvalidVersion(); + } - uint8 minorVersion = UnsafeCalldataBytesLib.toUint8( - updateData, - encodedOffset - ); + uint8 minorVersion = UnsafeCalldataBytesLib.toUint8(updateData, encodedOffset); encodedOffset += 1; // Minor versions are forward compatible, so we only check // that the minor version is not less than the minimum allowed - if (minorVersion < ConstantsLib.MINIMUM_ALLOWED_MINOR_VERSION) + if (minorVersion < ConstantsLib.MINIMUM_ALLOWED_MINOR_VERSION) { revert ErrorsLib.InvalidVersion(); + } // This field ensure that we can add headers in the future // without breaking the contract (future compatibility) - uint8 trailingHeaderSize = UnsafeCalldataBytesLib.toUint8( - updateData, - encodedOffset - ); + uint8 trailingHeaderSize = UnsafeCalldataBytesLib.toUint8(updateData, encodedOffset); encodedOffset += 1; // We use another encodedOffset for the trailing header and in the end add the @@ -112,30 +91,19 @@ contract PragmaDecoder { encodedOffset += trailingHeaderSize; } - if (updateData.length < encodedOffset) + if (updateData.length < encodedOffset) { revert ErrorsLib.InvalidUpdateData(); + } } } - function extractCheckpointRootAndNumUpdates( - bytes calldata updateData, - uint encodedOffset - ) + function extractCheckpointRootAndNumUpdates(bytes calldata updateData, uint256 encodedOffset) internal view - returns ( - uint offset, - bytes32 checkpointRoot, - uint8 numUpdates, - bytes calldata encoded - ) + returns (uint256 offset, bytes32 checkpointRoot, uint8 numUpdates, bytes calldata encoded) { unchecked { - encoded = UnsafeCalldataBytesLib.slice( - updateData, - encodedOffset, - updateData.length - encodedOffset - ); + encoded = UnsafeCalldataBytesLib.slice(updateData, encodedOffset, updateData.length - encodedOffset); offset = 0; uint16 hyMsgSize = UnsafeCalldataBytesLib.toUint16(encoded, offset); @@ -144,26 +112,22 @@ contract PragmaDecoder { { bytes memory encodedPayload; { - HyMsg memory hyMsg = parseAndVerifyHyMsg( - UnsafeCalldataBytesLib.slice(encoded, offset, hyMsgSize) - ); + HyMsg memory hyMsg = parseAndVerifyHyMsg(UnsafeCalldataBytesLib.slice(encoded, offset, hyMsgSize)); offset += hyMsgSize; encodedPayload = hyMsg.payload; } - uint payloadOffset = 0; + uint256 payloadOffset = 0; { - checkpointRoot = UnsafeBytesLib.toBytes32( - encodedPayload, - payloadOffset - ); + checkpointRoot = UnsafeBytesLib.toBytes32(encodedPayload, payloadOffset); payloadOffset += 32; // We don't check equality to enable future compatibility. - if (payloadOffset > encodedPayload.length) + if (payloadOffset > encodedPayload.length) { revert ErrorsLib.InvalidUpdateData(); + } } } @@ -172,34 +136,18 @@ contract PragmaDecoder { } } - function extractDataInfoFromUpdate( - bytes calldata encoded, - uint offset, - bytes32 checkpointRoot - ) + function extractDataInfoFromUpdate(bytes calldata encoded, uint256 offset, bytes32 checkpointRoot) internal pure - returns ( - uint endOffset, - DataFeed memory dataFeed, - bytes32 dataId, - uint64 publishTime - ) + returns (uint256 endOffset, DataFeed memory dataFeed, bytes32 dataId, uint64 publishTime) { unchecked { bytes calldata encodedUpdate; - uint16 updateSize = UnsafeCalldataBytesLib.toUint16( - encoded, - offset - ); + uint16 updateSize = UnsafeCalldataBytesLib.toUint16(encoded, offset); offset += 2; { - encodedUpdate = UnsafeCalldataBytesLib.slice( - encoded, - offset, - updateSize - ); + encodedUpdate = UnsafeCalldataBytesLib.slice(encoded, offset, updateSize); offset += updateSize; } @@ -213,24 +161,16 @@ contract PragmaDecoder { if (!valid) revert ErrorsLib.InvalidHyperlaneCheckpointRoot(); - DataFeedType dataFeedType = DataFeedType( - UnsafeCalldataBytesLib.toUint8(encodedUpdate, 0) - ); + DataFeedType dataFeedType = DataFeedType(UnsafeCalldataBytesLib.toUint8(encodedUpdate, 0)); if (dataFeedType == DataFeedType.SpotMedian) { - (dataFeed, dataId, publishTime) = parseSpotMedianDataFeed( - encodedUpdate, - 1 - ); + (dataFeed, dataId, publishTime) = parseSpotMedianDataFeed(encodedUpdate, 1); } else { revert ErrorsLib.InvalidDataFeedType(); } } } - function parseSpotMedianDataFeed( - bytes calldata encodedDataFeed, - uint offset - ) + function parseSpotMedianDataFeed(bytes calldata encodedDataFeed, uint256 offset) private pure returns (DataFeed memory dataFeedInfo, bytes32 dataId, uint64 publishTime) @@ -240,33 +180,22 @@ contract PragmaDecoder { } } - function updateDataInfoFromUpdate( - bytes calldata updateData - ) internal returns (uint8 numUpdates) { + function updateDataInfoFromUpdate(bytes calldata updateData) internal returns (uint8 numUpdates) { // Extract header metadata - uint encodedOffset = extractMetadataFromheader(updateData); + uint256 encodedOffset = extractMetadataFromheader(updateData); // Extract merkle root and number of updates from update data. - uint offset; + uint256 offset; bytes32 checkpointRoot; bytes calldata encoded; - ( - offset, - checkpointRoot, - numUpdates, - encoded - ) = extractCheckpointRootAndNumUpdates(updateData, encodedOffset); + (offset, checkpointRoot, numUpdates, encoded) = extractCheckpointRootAndNumUpdates(updateData, encodedOffset); unchecked { - for (uint i = 0; i < numUpdates; i++) { + for (uint256 i = 0; i < numUpdates; i++) { DataFeed memory dataFeed; bytes32 dataId; - (offset, dataFeed, dataId,) = extractDataInfoFromUpdate( - encoded, - offset, - checkpointRoot - ); + (offset, dataFeed, dataId,) = extractDataInfoFromUpdate(encoded, offset, checkpointRoot); updateLatestDataInfoIfNecessary(dataId, dataFeed); } } @@ -276,19 +205,11 @@ contract PragmaDecoder { if (offset != encoded.length) revert ErrorsLib.InvalidUpdateData(); } - function updateLatestDataInfoIfNecessary( - bytes32 dataId, - DataFeed memory info - ) internal { + function updateLatestDataInfoIfNecessary(bytes32 dataId, DataFeed memory info) internal { uint64 latestPublishTime = _latestPriceInfo[dataId].publishTime; if (info.publishTime > latestPublishTime) { _latestPriceInfo[dataId] = info; - emit EventsLib.DataFeedUpdate( - dataId, - info.publishTime, - info.numSourcesAggregated, - info.value - ); + emit EventsLib.DataFeedUpdate(dataId, info.publishTime, info.numSourcesAggregated, info.value); } } } diff --git a/solidity/src/interfaces/IHyperlane.sol b/solidity/src/interfaces/IHyperlane.sol index 1b6d23fc..96c1d50d 100644 --- a/solidity/src/interfaces/IHyperlane.sol +++ b/solidity/src/interfaces/IHyperlane.sol @@ -24,13 +24,20 @@ struct HyMsg { /// @author Pragma Labs /// @custom:contact security@pragma.build interface IHyperlane { - /// @notice Parses and verifies a Hyperlane message. + /// @notice Parses and verifies the signature of an Hyperlane message. /// @dev message should be encoded following the specs (TODO: add docs) /// @param encodedHyMsg The encoded Hyperlane message. /// @return hyMsg The parsed Hyperlane message. /// @return valid Whether the message is valid. /// @return reason The reason the message is invalid. - function parseAndVerifyHyMsg( - bytes calldata encodedHyMsg - ) external view returns (HyMsg memory hyMsg, bool valid, string memory reason); -} \ No newline at end of file + function parseAndVerifyHyMsg(bytes calldata encodedHyMsg) + external + view + returns (HyMsg memory hyMsg, bool valid, string memory reason); + + /// @notice Parses an Hyperlane message. + /// @dev message should be encoded following the specs (TODO: add docs) + /// @param encodedHyMsg The encoded Hyperlane message. + /// @return hyMsg The parsed Hyperlane message. + function parseHyMsg(bytes calldata encodedHyMsg) external pure returns (HyMsg memory hyMsg); +} diff --git a/solidity/src/interfaces/IPragma.sol b/solidity/src/interfaces/IPragma.sol index 34b40052..5ef17b05 100644 --- a/solidity/src/interfaces/IPragma.sol +++ b/solidity/src/interfaces/IPragma.sol @@ -34,17 +34,12 @@ interface IPragma { /// @notice Returns the required fee to update an array of price updates. /// @param updateData Array of price update data. /// @return feeAmount The required fee in Wei. - function getUpdateFee( - bytes[] calldata updateData - ) external view returns (uint feeAmount); + function getUpdateFee(bytes[] calldata updateData) external view returns (uint256 feeAmount); /// @notice Returns the data that is no older than `age` seconds of the current time. /// @dev Reverts if the data wasn't updated sufficiently recently. /// @return data - please read the documentation of DataFeed to understand how to use this safely. - function getPriceNoOlderThan( - bytes32 id, - uint age - ) external view returns (DataFeed memory data); + function getPriceNoOlderThan(bytes32 id, uint256 age) external view returns (DataFeed memory data); /// @notice Checks if a data feed exists. /// @param id The data feed ID. diff --git a/solidity/src/libraries/BytesLib.sol b/solidity/src/libraries/BytesLib.sol index 71277b6d..4e4718a9 100644 --- a/solidity/src/libraries/BytesLib.sol +++ b/solidity/src/libraries/BytesLib.sol @@ -9,10 +9,7 @@ pragma solidity >=0.8.0 <0.9.0; library BytesLib { - function concat( - bytes memory _preBytes, - bytes memory _postBytes - ) internal pure returns (bytes memory) { + function concat(bytes memory _preBytes, bytes memory _postBytes) internal pure returns (bytes memory) { bytes memory tempBytes; assembly { @@ -60,14 +57,10 @@ library BytesLib { // length of the arrays. end := add(mc, length) - for { - let cc := add(_postBytes, 0x20) - } lt(mc, end) { + for { let cc := add(_postBytes, 0x20) } lt(mc, end) { mc := add(mc, 0x20) cc := add(cc, 0x20) - } { - mstore(mc, mload(cc)) - } + } { mstore(mc, mload(cc)) } // Update the free-memory pointer by padding our last write location // to 32 bytes: add 31 bytes to the end of tempBytes to move to the @@ -86,10 +79,7 @@ library BytesLib { return tempBytes; } - function concatStorage( - bytes storage _preBytes, - bytes memory _postBytes - ) internal { + function concatStorage(bytes storage _preBytes, bytes memory _postBytes) internal { assembly { // Read the first 32 bytes of _preBytes storage, which is the length // of the array. (We don't need to use the offset into the slot @@ -102,10 +92,7 @@ library BytesLib { // If the slot is even, bitwise and the slot with 255 and divide by // two to get the length. If the slot is odd, bitwise and the slot // with -1 and divide by two. - let slength := div( - and(fslot, sub(mul(0x100, iszero(and(fslot, 1))), 1)), - 2 - ) + let slength := div(and(fslot, sub(mul(0x100, iszero(and(fslot, 1))), 1)), 2) let mlength := mload(_postBytes) let newlength := add(slength, mlength) // slength can contain both the length and contents of the array @@ -170,10 +157,7 @@ library BytesLib { sstore( sc, add( - and( - fslot, - 0xffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff00 - ), + and(fslot, 0xffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff00), and(mload(mc), mask) ) ) @@ -184,9 +168,7 @@ library BytesLib { } lt(mc, end) { sc := add(sc, 1) mc := add(mc, 0x20) - } { - sstore(sc, mload(mc)) - } + } { sstore(sc, mload(mc)) } mask := exp(0x100, sub(mc, end)) @@ -218,9 +200,7 @@ library BytesLib { } lt(mc, end) { sc := add(sc, 1) mc := add(mc, 0x20) - } { - sstore(sc, mload(mc)) - } + } { sstore(sc, mload(mc)) } mask := exp(0x100, sub(mc, end)) @@ -229,11 +209,7 @@ library BytesLib { } } - function slice( - bytes memory _bytes, - uint256 _start, - uint256 _length - ) internal pure returns (bytes memory) { + function slice(bytes memory _bytes, uint256 _start, uint256 _length) internal pure returns (bytes memory) { require(_length + 31 >= _length, "slice_overflow"); require(_bytes.length >= _start + _length, "slice_outOfBounds"); @@ -260,28 +236,17 @@ library BytesLib { // because when slicing multiples of 32 bytes (lengthmod == 0) // the following copy loop was copying the origin's length // and then ending prematurely not copying everything it should. - let mc := add( - add(tempBytes, lengthmod), - mul(0x20, iszero(lengthmod)) - ) + let mc := add(add(tempBytes, lengthmod), mul(0x20, iszero(lengthmod))) let end := add(mc, _length) for { // The multiplication in the next line has the same exact purpose // as the one above. - let cc := add( - add( - add(_bytes, lengthmod), - mul(0x20, iszero(lengthmod)) - ), - _start - ) + let cc := add(add(add(_bytes, lengthmod), mul(0x20, iszero(lengthmod))), _start) } lt(mc, end) { mc := add(mc, 0x20) cc := add(cc, 0x20) - } { - mstore(mc, mload(cc)) - } + } { mstore(mc, mload(cc)) } mstore(tempBytes, _length) @@ -303,27 +268,18 @@ library BytesLib { return tempBytes; } - function toAddress( - bytes memory _bytes, - uint256 _start - ) internal pure returns (address) { + function toAddress(bytes memory _bytes, uint256 _start) internal pure returns (address) { require(_bytes.length >= _start + 20, "toAddress_outOfBounds"); address tempAddress; assembly { - tempAddress := div( - mload(add(add(_bytes, 0x20), _start)), - 0x1000000000000000000000000 - ) + tempAddress := div(mload(add(add(_bytes, 0x20), _start)), 0x1000000000000000000000000) } return tempAddress; } - function toUint8( - bytes memory _bytes, - uint256 _start - ) internal pure returns (uint8) { + function toUint8(bytes memory _bytes, uint256 _start) internal pure returns (uint8) { require(_bytes.length >= _start + 1, "toUint8_outOfBounds"); uint8 tempUint; @@ -334,10 +290,7 @@ library BytesLib { return tempUint; } - function toUint16( - bytes memory _bytes, - uint256 _start - ) internal pure returns (uint16) { + function toUint16(bytes memory _bytes, uint256 _start) internal pure returns (uint16) { require(_bytes.length >= _start + 2, "toUint16_outOfBounds"); uint16 tempUint; @@ -348,10 +301,7 @@ library BytesLib { return tempUint; } - function toUint32( - bytes memory _bytes, - uint256 _start - ) internal pure returns (uint32) { + function toUint32(bytes memory _bytes, uint256 _start) internal pure returns (uint32) { require(_bytes.length >= _start + 4, "toUint32_outOfBounds"); uint32 tempUint; @@ -362,10 +312,7 @@ library BytesLib { return tempUint; } - function toUint64( - bytes memory _bytes, - uint256 _start - ) internal pure returns (uint64) { + function toUint64(bytes memory _bytes, uint256 _start) internal pure returns (uint64) { require(_bytes.length >= _start + 8, "toUint64_outOfBounds"); uint64 tempUint; @@ -376,10 +323,7 @@ library BytesLib { return tempUint; } - function toUint96( - bytes memory _bytes, - uint256 _start - ) internal pure returns (uint96) { + function toUint96(bytes memory _bytes, uint256 _start) internal pure returns (uint96) { require(_bytes.length >= _start + 12, "toUint96_outOfBounds"); uint96 tempUint; @@ -390,10 +334,7 @@ library BytesLib { return tempUint; } - function toUint128( - bytes memory _bytes, - uint256 _start - ) internal pure returns (uint128) { + function toUint128(bytes memory _bytes, uint256 _start) internal pure returns (uint128) { require(_bytes.length >= _start + 16, "toUint128_outOfBounds"); uint128 tempUint; @@ -404,10 +345,7 @@ library BytesLib { return tempUint; } - function toUint256( - bytes memory _bytes, - uint256 _start - ) internal pure returns (uint256) { + function toUint256(bytes memory _bytes, uint256 _start) internal pure returns (uint256) { require(_bytes.length >= _start + 32, "toUint256_outOfBounds"); uint256 tempUint; @@ -418,10 +356,7 @@ library BytesLib { return tempUint; } - function toBytes32( - bytes memory _bytes, - uint256 _start - ) internal pure returns (bytes32) { + function toBytes32(bytes memory _bytes, uint256 _start) internal pure returns (bytes32) { require(_bytes.length >= _start + 32, "toBytes32_outOfBounds"); bytes32 tempBytes32; @@ -432,10 +367,7 @@ library BytesLib { return tempBytes32; } - function equal( - bytes memory _preBytes, - bytes memory _postBytes - ) internal pure returns (bool) { + function equal(bytes memory _preBytes, bytes memory _postBytes) internal pure returns (bool) { bool success = true; assembly { @@ -453,11 +385,10 @@ library BytesLib { let mc := add(_preBytes, 0x20) let end := add(mc, length) - for { - let cc := add(_postBytes, 0x20) - // the next line is the loop condition: - // while(uint256(mc < end) + cb == 2) - } eq(add(lt(mc, end), cb), 2) { + for { let cc := add(_postBytes, 0x20) } + // the next line is the loop condition: + // while(uint256(mc < end) + cb == 2) + eq(add(lt(mc, end), cb), 2) { mc := add(mc, 0x20) cc := add(cc, 0x20) } { @@ -478,20 +409,14 @@ library BytesLib { return success; } - function equalStorage( - bytes storage _preBytes, - bytes memory _postBytes - ) internal view returns (bool) { + function equalStorage(bytes storage _preBytes, bytes memory _postBytes) internal view returns (bool) { bool success = true; assembly { // we know _preBytes_offset is 0 let fslot := sload(_preBytes.slot) // Decode the length of the stored array like in concatStorage(). - let slength := div( - and(fslot, sub(mul(0x100, iszero(and(fslot, 1))), 1)), - 2 - ) + let slength := div(and(fslot, sub(mul(0x100, iszero(and(fslot, 1))), 1)), 2) let mlength := mload(_postBytes) // if lengths don't match the arrays are not equal @@ -527,9 +452,7 @@ library BytesLib { // the next line is the loop condition: // while(uint256(mc < end) + cb == 2) - for { - - } eq(add(lt(mc, end), cb), 2) { + for {} eq(add(lt(mc, end), cb), 2) { sc := add(sc, 1) mc := add(mc, 0x20) } { diff --git a/solidity/src/libraries/ConstantsLib.sol b/solidity/src/libraries/ConstantsLib.sol index 20391728..6b1fb928 100644 --- a/solidity/src/libraries/ConstantsLib.sol +++ b/solidity/src/libraries/ConstantsLib.sol @@ -10,4 +10,4 @@ library ConstantsLib { uint8 internal constant MINIMUM_ALLOWED_MINOR_VERSION = 0; /// @dev The current major version. uint8 internal constant MAJOR_VERSION = 1; -} \ No newline at end of file +} diff --git a/solidity/src/libraries/ErrorsLib.sol b/solidity/src/libraries/ErrorsLib.sol index 46a4aef9..4bc27993 100644 --- a/solidity/src/libraries/ErrorsLib.sol +++ b/solidity/src/libraries/ErrorsLib.sol @@ -24,4 +24,10 @@ library ErrorsLib { // Data feed type is not supported. // TODO: add signature error InvalidDataFeedType(); -} \ No newline at end of file + // Data feed is not found. + // TODO: add signature + error DataNotFound(); + // Data feed is stale. (e.g., not updated for a long time) + // TODO: add signature + error DataStale(); +} diff --git a/solidity/src/libraries/EventsLib.sol b/solidity/src/libraries/EventsLib.sol index 5e6c8377..798a5e53 100644 --- a/solidity/src/libraries/EventsLib.sol +++ b/solidity/src/libraries/EventsLib.sol @@ -10,10 +10,5 @@ library EventsLib { /// @param feedId Pragma Feed ID. /// @param publishTime Unix timestamp of the update. /// @param value New value of the data feed. - event DataFeedUpdate( - bytes32 indexed feedId, - uint64 publishTime, - uint32 numSourcesAggregated, - int64 value - ); -} \ No newline at end of file + event DataFeedUpdate(bytes32 indexed feedId, uint64 publishTime, uint32 numSourcesAggregated, int64 value); +} diff --git a/solidity/src/libraries/MerkleTree.sol b/solidity/src/libraries/MerkleTree.sol index c87fdf2c..b8751fed 100644 --- a/solidity/src/libraries/MerkleTree.sol +++ b/solidity/src/libraries/MerkleTree.sol @@ -8,7 +8,6 @@ import "./UnsafeCalldataBytesLib.sol"; * @dev This library provides methods to construct and verify Merkle Tree proofs efficiently. * */ - library MerkleTree { uint8 constant MERKLE_LEAF_PREFIX = 0; uint8 constant MERKLE_NODE_PREFIX = 1; @@ -26,10 +25,7 @@ library MerkleTree { return hash(abi.encodePacked(MERKLE_LEAF_PREFIX, data)); } - function nodeHash( - bytes32 childA, - bytes32 childB - ) internal pure returns (bytes32) { + function nodeHash(bytes32 childA, bytes32 childB) internal pure returns (bytes32) { if (childA > childB) { (childA, childB) = (childB, childA); } @@ -44,31 +40,22 @@ library MerkleTree { /// `proofOffset` parameters. It is the caller's responsibility to ensure /// that the `encodedProof` is long enough to contain the proof and the /// `proofOffset` is not out of bound. - function isProofValid( - bytes calldata encodedProof, - uint proofOffset, - bytes32 root, - bytes calldata leafData - ) internal pure returns (bool valid, uint endOffset) { + function isProofValid(bytes calldata encodedProof, uint256 proofOffset, bytes32 root, bytes calldata leafData) + internal + pure + returns (bool valid, uint256 endOffset) + { unchecked { bytes32 currentDigest = MerkleTree.leafHash(leafData); - uint8 proofSize = UnsafeCalldataBytesLib.toUint8( - encodedProof, - proofOffset - ); + uint8 proofSize = UnsafeCalldataBytesLib.toUint8(encodedProof, proofOffset); proofOffset += 1; - for (uint i = 0; i < proofSize; i++) { - bytes32 siblingDigest = bytes32( - UnsafeCalldataBytesLib.toBytes32(encodedProof, proofOffset) - ); + for (uint256 i = 0; i < proofSize; i++) { + bytes32 siblingDigest = bytes32(UnsafeCalldataBytesLib.toBytes32(encodedProof, proofOffset)); proofOffset += 20; - currentDigest = MerkleTree.nodeHash( - currentDigest, - siblingDigest - ); + currentDigest = MerkleTree.nodeHash(currentDigest, siblingDigest); } valid = currentDigest == root; @@ -84,10 +71,11 @@ library MerkleTree { /// messages as leafs (in the same given order) and returns the root digest /// and the proofs for each message. If the number of messages is not a power /// of 2, the tree is padded with empty messages. - function constructProofs( - bytes[] memory messages, - uint8 depth - ) internal pure returns (bytes32 root, bytes[] memory proofs) { + function constructProofs(bytes[] memory messages, uint8 depth) + internal + pure + returns (bytes32 root, bytes[] memory proofs) + { require((1 << depth) >= messages.length, "depth too small"); bytes32[] memory tree = new bytes32[]((1 << (depth + 1))); @@ -104,7 +92,7 @@ library MerkleTree { // Filling the leaf hashes bytes32 cachedEmptyLeafHash = emptyLeafHash(); - for (uint i = 0; i < (1 << depth); i++) { + for (uint256 i = 0; i < (1 << depth); i++) { if (i < messages.length) { tree[(1 << depth) + i] = leafHash(messages[i]); } else { @@ -113,11 +101,11 @@ library MerkleTree { } // Filling the node hashes from bottom to top - for (uint k = depth; k > 0; k--) { - uint level = k - 1; - uint levelNumNodes = (1 << level); - for (uint i = 0; i < levelNumNodes; i++) { - uint id = (1 << level) + i; + for (uint256 k = depth; k > 0; k--) { + uint256 level = k - 1; + uint256 levelNumNodes = (1 << level); + for (uint256 i = 0; i < levelNumNodes; i++) { + uint256 id = (1 << level) + i; tree[id] = nodeHash(tree[id * 2], tree[id * 2 + 1]); } } @@ -126,11 +114,11 @@ library MerkleTree { proofs = new bytes[](messages.length); - for (uint i = 0; i < messages.length; i++) { + for (uint256 i = 0; i < messages.length; i++) { // depth is the number of sibling nodes in the path from the leaf to the root proofs[i] = abi.encodePacked(depth); - uint idx = (1 << depth) + i; + uint256 idx = (1 << depth) + i; // This loop iterates through the leaf and its parents // and keeps adding the sibling of the current node to the proof. diff --git a/solidity/src/libraries/UnsafeBytesLib.sol b/solidity/src/libraries/UnsafeBytesLib.sol index 2c6fdad6..720288f2 100644 --- a/solidity/src/libraries/UnsafeBytesLib.sol +++ b/solidity/src/libraries/UnsafeBytesLib.sol @@ -12,10 +12,7 @@ pragma solidity >=0.8.0 <0.9.0; library UnsafeBytesLib { - function concat( - bytes memory _preBytes, - bytes memory _postBytes - ) internal pure returns (bytes memory) { + function concat(bytes memory _preBytes, bytes memory _postBytes) internal pure returns (bytes memory) { bytes memory tempBytes; assembly { @@ -63,14 +60,10 @@ library UnsafeBytesLib { // length of the arrays. end := add(mc, length) - for { - let cc := add(_postBytes, 0x20) - } lt(mc, end) { + for { let cc := add(_postBytes, 0x20) } lt(mc, end) { mc := add(mc, 0x20) cc := add(cc, 0x20) - } { - mstore(mc, mload(cc)) - } + } { mstore(mc, mload(cc)) } // Update the free-memory pointer by padding our last write location // to 32 bytes: add 31 bytes to the end of tempBytes to move to the @@ -89,10 +82,7 @@ library UnsafeBytesLib { return tempBytes; } - function concatStorage( - bytes storage _preBytes, - bytes memory _postBytes - ) internal { + function concatStorage(bytes storage _preBytes, bytes memory _postBytes) internal { assembly { // Read the first 32 bytes of _preBytes storage, which is the length // of the array. (We don't need to use the offset into the slot @@ -105,10 +95,7 @@ library UnsafeBytesLib { // If the slot is even, bitwise and the slot with 255 and divide by // two to get the length. If the slot is odd, bitwise and the slot // with -1 and divide by two. - let slength := div( - and(fslot, sub(mul(0x100, iszero(and(fslot, 1))), 1)), - 2 - ) + let slength := div(and(fslot, sub(mul(0x100, iszero(and(fslot, 1))), 1)), 2) let mlength := mload(_postBytes) let newlength := add(slength, mlength) // slength can contain both the length and contents of the array @@ -173,10 +160,7 @@ library UnsafeBytesLib { sstore( sc, add( - and( - fslot, - 0xffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff00 - ), + and(fslot, 0xffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff00), and(mload(mc), mask) ) ) @@ -187,9 +171,7 @@ library UnsafeBytesLib { } lt(mc, end) { sc := add(sc, 1) mc := add(mc, 0x20) - } { - sstore(sc, mload(mc)) - } + } { sstore(sc, mload(mc)) } mask := exp(0x100, sub(mc, end)) @@ -221,9 +203,7 @@ library UnsafeBytesLib { } lt(mc, end) { sc := add(sc, 1) mc := add(mc, 0x20) - } { - sstore(sc, mload(mc)) - } + } { sstore(sc, mload(mc)) } mask := exp(0x100, sub(mc, end)) @@ -232,11 +212,7 @@ library UnsafeBytesLib { } } - function slice( - bytes memory _bytes, - uint256 _start, - uint256 _length - ) internal pure returns (bytes memory) { + function slice(bytes memory _bytes, uint256 _start, uint256 _length) internal pure returns (bytes memory) { bytes memory tempBytes; assembly { @@ -260,28 +236,17 @@ library UnsafeBytesLib { // because when slicing multiples of 32 bytes (lengthmod == 0) // the following copy loop was copying the origin's length // and then ending prematurely not copying everything it should. - let mc := add( - add(tempBytes, lengthmod), - mul(0x20, iszero(lengthmod)) - ) + let mc := add(add(tempBytes, lengthmod), mul(0x20, iszero(lengthmod))) let end := add(mc, _length) for { // The multiplication in the next line has the same exact purpose // as the one above. - let cc := add( - add( - add(_bytes, lengthmod), - mul(0x20, iszero(lengthmod)) - ), - _start - ) + let cc := add(add(add(_bytes, lengthmod), mul(0x20, iszero(lengthmod))), _start) } lt(mc, end) { mc := add(mc, 0x20) cc := add(cc, 0x20) - } { - mstore(mc, mload(cc)) - } + } { mstore(mc, mload(cc)) } mstore(tempBytes, _length) @@ -303,26 +268,17 @@ library UnsafeBytesLib { return tempBytes; } - function toAddress( - bytes memory _bytes, - uint256 _start - ) internal pure returns (address) { + function toAddress(bytes memory _bytes, uint256 _start) internal pure returns (address) { address tempAddress; assembly { - tempAddress := div( - mload(add(add(_bytes, 0x20), _start)), - 0x1000000000000000000000000 - ) + tempAddress := div(mload(add(add(_bytes, 0x20), _start)), 0x1000000000000000000000000) } return tempAddress; } - function toUint8( - bytes memory _bytes, - uint256 _start - ) internal pure returns (uint8) { + function toUint8(bytes memory _bytes, uint256 _start) internal pure returns (uint8) { uint8 tempUint; assembly { @@ -332,10 +288,7 @@ library UnsafeBytesLib { return tempUint; } - function toUint16( - bytes memory _bytes, - uint256 _start - ) internal pure returns (uint16) { + function toUint16(bytes memory _bytes, uint256 _start) internal pure returns (uint16) { uint16 tempUint; assembly { @@ -345,10 +298,7 @@ library UnsafeBytesLib { return tempUint; } - function toUint32( - bytes memory _bytes, - uint256 _start - ) internal pure returns (uint32) { + function toUint32(bytes memory _bytes, uint256 _start) internal pure returns (uint32) { uint32 tempUint; assembly { @@ -358,10 +308,7 @@ library UnsafeBytesLib { return tempUint; } - function toUint64( - bytes memory _bytes, - uint256 _start - ) internal pure returns (uint64) { + function toUint64(bytes memory _bytes, uint256 _start) internal pure returns (uint64) { uint64 tempUint; assembly { @@ -371,10 +318,7 @@ library UnsafeBytesLib { return tempUint; } - function toUint96( - bytes memory _bytes, - uint256 _start - ) internal pure returns (uint96) { + function toUint96(bytes memory _bytes, uint256 _start) internal pure returns (uint96) { uint96 tempUint; assembly { @@ -384,10 +328,7 @@ library UnsafeBytesLib { return tempUint; } - function toUint128( - bytes memory _bytes, - uint256 _start - ) internal pure returns (uint128) { + function toUint128(bytes memory _bytes, uint256 _start) internal pure returns (uint128) { uint128 tempUint; assembly { @@ -397,10 +338,7 @@ library UnsafeBytesLib { return tempUint; } - function toUint256( - bytes memory _bytes, - uint256 _start - ) internal pure returns (uint256) { + function toUint256(bytes memory _bytes, uint256 _start) internal pure returns (uint256) { uint256 tempUint; assembly { @@ -410,10 +348,7 @@ library UnsafeBytesLib { return tempUint; } - function toBytes32( - bytes memory _bytes, - uint256 _start - ) internal pure returns (bytes32) { + function toBytes32(bytes memory _bytes, uint256 _start) internal pure returns (bytes32) { bytes32 tempBytes32; assembly { @@ -423,10 +358,7 @@ library UnsafeBytesLib { return tempBytes32; } - function equal( - bytes memory _preBytes, - bytes memory _postBytes - ) internal pure returns (bool) { + function equal(bytes memory _preBytes, bytes memory _postBytes) internal pure returns (bool) { bool success = true; assembly { @@ -444,11 +376,10 @@ library UnsafeBytesLib { let mc := add(_preBytes, 0x20) let end := add(mc, length) - for { - let cc := add(_postBytes, 0x20) - // the next line is the loop condition: - // while(uint256(mc < end) + cb == 2) - } eq(add(lt(mc, end), cb), 2) { + for { let cc := add(_postBytes, 0x20) } + // the next line is the loop condition: + // while(uint256(mc < end) + cb == 2) + eq(add(lt(mc, end), cb), 2) { mc := add(mc, 0x20) cc := add(cc, 0x20) } { @@ -469,20 +400,14 @@ library UnsafeBytesLib { return success; } - function equalStorage( - bytes storage _preBytes, - bytes memory _postBytes - ) internal view returns (bool) { + function equalStorage(bytes storage _preBytes, bytes memory _postBytes) internal view returns (bool) { bool success = true; assembly { // we know _preBytes_offset is 0 let fslot := sload(_preBytes.slot) // Decode the length of the stored array like in concatStorage(). - let slength := div( - and(fslot, sub(mul(0x100, iszero(and(fslot, 1))), 1)), - 2 - ) + let slength := div(and(fslot, sub(mul(0x100, iszero(and(fslot, 1))), 1)), 2) let mlength := mload(_postBytes) // if lengths don't match the arrays are not equal @@ -518,9 +443,7 @@ library UnsafeBytesLib { // the next line is the loop condition: // while(uint256(mc < end) + cb == 2) - for { - - } eq(add(lt(mc, end), cb), 2) { + for {} eq(add(lt(mc, end), cb), 2) { sc := add(sc, 1) mc := add(mc, 0x20) } { diff --git a/solidity/src/libraries/UnsafeCalldataBytesLib.sol b/solidity/src/libraries/UnsafeCalldataBytesLib.sol index 487d11e3..efc3dc45 100644 --- a/solidity/src/libraries/UnsafeCalldataBytesLib.sol +++ b/solidity/src/libraries/UnsafeCalldataBytesLib.sol @@ -12,25 +12,15 @@ pragma solidity >=0.8.0 <0.9.0; library UnsafeCalldataBytesLib { - function slice( - bytes calldata _bytes, - uint256 _start, - uint256 _length - ) internal pure returns (bytes calldata) { + function slice(bytes calldata _bytes, uint256 _start, uint256 _length) internal pure returns (bytes calldata) { return _bytes[_start:_start + _length]; } - function sliceFrom( - bytes calldata _bytes, - uint256 _start - ) internal pure returns (bytes calldata) { + function sliceFrom(bytes calldata _bytes, uint256 _start) internal pure returns (bytes calldata) { return _bytes[_start:_bytes.length]; } - function toAddress( - bytes calldata _bytes, - uint256 _start - ) internal pure returns (address) { + function toAddress(bytes calldata _bytes, uint256 _start) internal pure returns (address) { address tempAddress; assembly { @@ -40,10 +30,7 @@ library UnsafeCalldataBytesLib { return tempAddress; } - function toUint8( - bytes calldata _bytes, - uint256 _start - ) internal pure returns (uint8) { + function toUint8(bytes calldata _bytes, uint256 _start) internal pure returns (uint8) { uint8 tempUint; assembly { @@ -53,10 +40,7 @@ library UnsafeCalldataBytesLib { return tempUint; } - function toUint16( - bytes calldata _bytes, - uint256 _start - ) internal pure returns (uint16) { + function toUint16(bytes calldata _bytes, uint256 _start) internal pure returns (uint16) { uint16 tempUint; assembly { @@ -66,10 +50,7 @@ library UnsafeCalldataBytesLib { return tempUint; } - function toUint32( - bytes calldata _bytes, - uint256 _start - ) internal pure returns (uint32) { + function toUint32(bytes calldata _bytes, uint256 _start) internal pure returns (uint32) { uint32 tempUint; assembly { @@ -79,10 +60,7 @@ library UnsafeCalldataBytesLib { return tempUint; } - function toUint64( - bytes calldata _bytes, - uint256 _start - ) internal pure returns (uint64) { + function toUint64(bytes calldata _bytes, uint256 _start) internal pure returns (uint64) { uint64 tempUint; assembly { @@ -92,10 +70,7 @@ library UnsafeCalldataBytesLib { return tempUint; } - function toUint96( - bytes calldata _bytes, - uint256 _start - ) internal pure returns (uint96) { + function toUint96(bytes calldata _bytes, uint256 _start) internal pure returns (uint96) { uint96 tempUint; assembly { @@ -105,10 +80,7 @@ library UnsafeCalldataBytesLib { return tempUint; } - function toUint128( - bytes calldata _bytes, - uint256 _start - ) internal pure returns (uint128) { + function toUint128(bytes calldata _bytes, uint256 _start) internal pure returns (uint128) { uint128 tempUint; assembly { @@ -118,10 +90,7 @@ library UnsafeCalldataBytesLib { return tempUint; } - function toUint256( - bytes calldata _bytes, - uint256 _start - ) internal pure returns (uint256) { + function toUint256(bytes calldata _bytes, uint256 _start) internal pure returns (uint256) { uint256 tempUint; assembly { @@ -131,10 +100,7 @@ library UnsafeCalldataBytesLib { return tempUint; } - function toBytes32( - bytes calldata _bytes, - uint256 _start - ) internal pure returns (bytes32) { + function toBytes32(bytes calldata _bytes, uint256 _start) internal pure returns (bytes32) { bytes32 tempBytes32; assembly { diff --git a/solidity/test/Pragma.t.sol b/solidity/test/Pragma.t.sol index 0e49cfa2..2168689b 100644 --- a/solidity/test/Pragma.t.sol +++ b/solidity/test/Pragma.t.sol @@ -2,12 +2,16 @@ pragma solidity ^0.8.13; import {Test, console} from "forge-std/Test.sol"; -import {Pragma} from "../src/Pragma.sol"; +import {IPragma} from "../src/interfaces/IPragma.sol"; +import "./utils/PragmaTestUtils.t.sol"; -contract PragmaT is Test { - Pragma public pragma_; +contract PragmaTest is Test, PragmaTestUtils { + IPragma public pragma_; + + uint8 constant NUM_VALIDATORS = 10; function setUp() public { - // pragma_ = new Pragma(); + // TODO: setup hyperlane + pragma_ = IPragma(setUpPragma(address(0))); } } diff --git a/solidity/test/utils/HyperlaneTestUtils.t.sol b/solidity/test/utils/HyperlaneTestUtils.t.sol new file mode 100644 index 00000000..03ba014d --- /dev/null +++ b/solidity/test/utils/HyperlaneTestUtils.t.sol @@ -0,0 +1,75 @@ +// SPDX-License-Identifier: Apache 2 + +pragma solidity ^0.8.0; + +import "../../src/Hyperlane.sol"; +import {IHyperlane, HyMsg, Signature} from "../../src/interfaces/IHyperlane.sol"; +import "forge-std/Test.sol"; + +abstract contract HyperlaneTestUtils is Test { + uint256[] currentSigners; + uint16 constant CHAIN_ID = 2; // Ethereum + address hyperlaneAddr; + + function setUpHyperlane(uint8 numValidators) public returns (address) { + address[] memory initSigners = new address[](numValidators); + currentSigners = new uint256[](numValidators); + + for (uint256 i = 0; i < numValidators; ++i) { + currentSigners[i] = i + 1; + initSigners[i] = vm.addr(currentSigners[i]); // i+1 is the private key for the i-th signer. + } + + Hyperlane hyperlane_ = new Hyperlane(initSigners); + return address(hyperlane_); + } + + function isNotMatch(bytes memory a, bytes memory b) public pure returns (bool) { + return keccak256(a) != keccak256(b); + } + + function generateUpdateData( + uint64 timestamp, + uint16 emitterChainId, + bytes32 emitterAddress, + bytes memory payload, + uint8 numSigners + ) public view returns (bytes memory updateData) { + // TODO: generate update data + } +} + +contract HyperlaneTestUtilsTest is Test, HyperlaneTestUtils { + uint32 constant TEST_UPDATE_TIMESTAMP = 112; + uint16 constant TEST_EMITTER_CHAIN_ID = 7; + bytes32 constant TEST_EMITTER_ADDR = 0x0000000000000000000000000000000000000000000000000000000000000bad; + bytes constant TEST_PAYLOAD = hex"deadbeaf"; + uint8 constant TEST_NUM_SIGNERS = 4; + + function assertHyMsgMatchesTestValues(HyMsg memory hyMsg, bool valid, string memory reason, bytes memory updateData) + private + view + { + assertTrue(valid); + assertEq(reason, ""); + assertEq(hyMsg.timestamp, TEST_UPDATE_TIMESTAMP); + assertEq(hyMsg.emitterChainId, TEST_EMITTER_CHAIN_ID); + assertEq(hyMsg.emitterAddress, TEST_EMITTER_ADDR); + assertEq(hyMsg.payload, TEST_PAYLOAD); + // parseAndVerifyHyMsg() returns an empty signatures array for gas savings since it's not used + // after its been verified. parseHyMsg() returns the full signatures array. + hyMsg = IHyperlane(hyperlaneAddr).parseHyMsg(updateData); + assertEq(hyMsg.signatures.length, TEST_NUM_SIGNERS); + } + + function testGenerateUpdateDataWorks() public { + IHyperlane hyperlane = IHyperlane(setUpHyperlane(5)); + + bytes memory updateData = generateUpdateData( + TEST_UPDATE_TIMESTAMP, TEST_EMITTER_CHAIN_ID, TEST_EMITTER_ADDR, TEST_PAYLOAD, TEST_NUM_SIGNERS + ); + + (HyMsg memory hyMsg, bool valid, string memory reason) = hyperlane.parseAndVerifyHyMsg(updateData); + assertHyMsgMatchesTestValues(hyMsg, valid, reason, updateData); + } +} diff --git a/solidity/test/utils/PragmaTestUtils.t.sol b/solidity/test/utils/PragmaTestUtils.t.sol new file mode 100644 index 00000000..77a70513 --- /dev/null +++ b/solidity/test/utils/PragmaTestUtils.t.sol @@ -0,0 +1,110 @@ +// SPDX-License-Identifier: Apache 2 + +pragma solidity ^0.8.0; + +import "forge-std/Test.sol"; +import "../../src/Pragma.sol"; +import "../../src/libraries/MerkleTree.sol"; +import "./RandTestUtils.t.sol"; +import "./HyperlaneTestUtils.t.sol"; +import {DataFeedType} from "../../src/interfaces/IPragma.sol"; + +abstract contract PragmaTestUtils is Test, RandTestUtils, HyperlaneTestUtils { + uint16 constant SOURCE_EMITTER_CHAIN_ID = 0x1; + bytes32 constant SOURCE_EMITTER_ADDRESS = 0x03dA250675D8c2BB7cef7E1b7FDFe17aA4D5752Ed82A9333e4F9a12b22E521aa; + + uint256 constant SINGLE_UPDATE_FEE_IN_WEI = 1; + uint256 constant VALID_TIME_PERIOD_IN_SECONDS = 60; + + function setUpPragma(address hyperlane) public returns (address) { + uint16[] memory emitterChainIds = new uint16[](1); + bytes32[] memory emitterAddresses = new bytes32[](1); + + emitterChainIds[0] = SOURCE_EMITTER_CHAIN_ID; + emitterAddresses[0] = SOURCE_EMITTER_ADDRESS; + + Pragma pragma_ = new Pragma( + hyperlane, emitterChainIds, emitterAddresses, VALID_TIME_PERIOD_IN_SECONDS, SINGLE_UPDATE_FEE_IN_WEI + ); + + return address(pragma_); + } + + function singleUpdateFeeInWei() public pure returns (uint256) { + return SINGLE_UPDATE_FEE_IN_WEI; + } + + // Utilities to help generating data feed messages and Hyperlane Checkpoints for them + + struct DataFeedMessage { + bytes32 dataId; + int64 value; + int32 expo; + uint64 publishTime; + uint64 prevPublishTime; + } + + struct MerkleUpdateConfig { + uint8 depth; + uint8 numSigners; + uint16 source_chain_id; + bytes32 source_emitter_address; + bool brokenSignature; + } + + function encodeDataFeedMessages(DataFeedMessage[] memory dataFeedMessages) + internal + pure + returns (bytes[] memory encodedDataFeedMessages) + { + encodedDataFeedMessages = new bytes[](dataFeedMessages.length); + + for (uint256 i = 0; i < dataFeedMessages.length; i++) { + encodedDataFeedMessages[i] = abi.encodePacked( + uint8(DataFeedType.SpotMedian), + dataFeedMessages[i].dataId, + dataFeedMessages[i].value, + dataFeedMessages[i].expo, + dataFeedMessages[i].publishTime, + dataFeedMessages[i].prevPublishTime + ); + } + } + + function generateHyMerkleUpdateWithSource( + DataFeedMessage[] memory dataFeedMessages, + MerkleUpdateConfig memory config + ) internal returns (bytes memory hyMerkleUpdateData) { + bytes[] memory encodedDataFeedMessages = encodeDataFeedMessages(dataFeedMessages); + + (bytes32 rootDigest, bytes[] memory proofs) = MerkleTree.constructProofs(encodedDataFeedMessages, config.depth); + + bytes memory hyperlanePayload = abi.encodePacked(rootDigest); + + bytes memory updateData = generateUpdateData( + 0, config.source_chain_id, config.source_emitter_address, hyperlanePayload, config.numSigners + ); + + if (config.brokenSignature) { + uint256 mutPos = getRandUint() % updateData.length; + + // mutate the random position by 1 bit + updateData[mutPos] = bytes1(uint8(updateData[mutPos]) ^ 1); + } + + hyMerkleUpdateData = abi.encodePacked( + uint8(1), // major version + uint8(0), // minor version + uint8(0), // trailing header size + uint16(updateData.length), + updateData, + uint8(dataFeedMessages.length) + ); + + for (uint256 i = 0; i < dataFeedMessages.length; i++) { + hyMerkleUpdateData = abi.encodePacked( + hyMerkleUpdateData, uint16(encodedDataFeedMessages[i].length), encodedDataFeedMessages[i], proofs[i] + ); + } + } +} diff --git a/solidity/test/utils/RandTestUtils.t.sol b/solidity/test/utils/RandTestUtils.t.sol new file mode 100644 index 00000000..8c25640d --- /dev/null +++ b/solidity/test/utils/RandTestUtils.t.sol @@ -0,0 +1,48 @@ +// SPDX-License-Identifier: Apache 2 + +pragma solidity ^0.8.0; + +import "forge-std/Test.sol"; + +contract RandTestUtils is Test { + uint256 randSeed; + + function setRandSeed(uint256 seed) internal { + randSeed = seed; + } + + function getRandBytes32() internal returns (bytes32) { + unchecked { + randSeed++; + } + return keccak256(abi.encode(randSeed)); + } + + function getRandUint() internal returns (uint256) { + return uint256(getRandBytes32()); + } + + function getRandUint64() internal returns (uint64) { + return uint64(getRandUint()); + } + + function getRandInt64() internal returns (int64) { + return int64(getRandUint64()); + } + + function getRandUint32() internal returns (uint32) { + return uint32(getRandUint()); + } + + function getRandInt32() internal returns (int32) { + return int32(getRandUint32()); + } + + function getRandUint8() internal returns (uint8) { + return uint8(getRandUint()); + } + + function getRandInt8() internal returns (int8) { + return int8(getRandUint8()); + } +}