diff --git a/SSQ.md b/SSQ.md new file mode 100644 index 000000000..8eac92801 --- /dev/null +++ b/SSQ.md @@ -0,0 +1,91 @@ +# SimpleSquanch (SSQ) + +The squanchy alternative to SSQ inspired by the [Varint serializer of Protocol Buffer](https://developers.google.com/protocol-buffers/docs/encoding#varints) and @metachris's [minimalistic approach](https://github.com/metachris/binary-serializer#base-128-varints). It is also an alternative to [Solidity's encodePacked()](https://docs.soliditylang.org/en/latest/abi-spec.html?highlight=encode#non-standard-packed-mode) which requires fixed-length fields and at most 1 variable-length field to decode without ambiguity. + +## What is it? + +A serialization scheme supporting simple binary fields of variable length. + + +### Features +- Space-efficient +- Varints do not require a fixed length. + + +### How does it work? +The delimitation of the fields relies on the MSB of each byte which is used as a flag to signal whether there are more bytes to process. + +For example: +- If the payload fits in 7 bits (=1 byte without MSB), then the MSB is set to 0 +- If the payload needs 2 or more bytes, each byte's MSB is set to 0 except the last one (from right to left). + + +### Limitations +- No support for complex types such as arrays, mappings or nesting. +- No concern with data types (signed/unsigned integers, strings etc), everything is bytes. + + +### Differences + +In SSQ, the MSB flag has a different meaning: + - In SSQ, the MSB flag means "there are more bytes after this one", assuming that they are processed from least to most significant (right to left). + - In Protobuf and Metachris serializer, it is assumed that they are processed from the most to least significant (left to right) instead. + - The rationale for SSQ is that it appeared to make an humble decoding implementation more straightforward. + +#### Differences with Solidity abi.encodePacked() +- The only way to decode packed bytes is by knowing the fixed length of each field which is fine at one point in time, but over time the required length for a field may change. Dynamic fields lead to ambiguities. +- SSQ is not affected by the above limitation, it supports dynamic fields of arbitrary length. + +#### Differences with ProtoBuf +- SSQ lacks 98% of most of ProtoBuf's features: no (proto) schema, no nesting, no optional or repeating field, no string... +- Protobuf encodes the Varint's LSB first. +- Optimized for speed, closer to the network wire order + +#### Differences with Metachris binary serializer +- SSQ supports only Varints, not packages. + + +### Simple Specifications + +Each byte uses 7 bits for the payload storage, the MSB being reserved as a "more bytes after this?" flag. +``` +Bit Values: [ x | 64 | 32 | 16 | 8 | 4 | 2 | 1 ] + | + + last-varint-byte indicator (0=last, 1=next-also-length-byte) +``` + +Therefore we can store: +- in 1 byte: any value between 0x00..0x7F +- in 2 bytes: any value between 0x00..0x3FFF +... + +There is a loss of 1 bit of storage but it is compensated by not having to either: +- reserve additional space for a Length field (to allow for variable length) +- agree on a fixed length now while attempting to predict the future possible range of values for the field. + + +### Specifications as code + +👉 [Collab Python notebook](https://colab.research.google.com/drive/1QmRpkwmUYXBH1RPu6TTX1ZmTuZ5c1EZu#scrollTo=wvQJVfJwqOvj) + + +### Encoding examples + +``` + input | [ varint byte 1 ] | [ varint byte 0 ] | [ output ] +---------------+----------------------+---------------------+-------------- + 1 = 0x0001 | | [ 0 0 0 0 0 0 0 1 ] | 0x0001 + 127 = 0x007F | | [ 0 1 1 1 1 1 1 1 ] | 0x007F + 128 = 0x0080 | [ 0 0 0 0 0 0 0 1 ] | [ 1 0 0 0 0 0 0 0 ] | 0x0180 + 255 = 0x00FF | [ 0 0 0 0 0 0 0 1 ] | [ 1 1 1 1 1 1 1 1 ] | 0x01FF + 256 = 0x0100 | [ 0 0 0 0 0 0 1 0 ] | [ 1 0 0 0 0 0 0 0 ] | 0x0280 +16383 = 0x3FFF | [ 0 1 1 1 1 1 1 1 ] | [ 1 1 1 1 1 1 1 1 ] | 0x7FFF + ... + + input | [ varint byte 2 ] | [ varint byte 1 ] | [ varint byte 0 ] | [ output ] +---------------+----------------------+---------------------+---------------------+------------ +16484 = 0x4000 | [ 0 0 0 0 0 0 0 1 ] | [ 1 0 0 0 0 0 0 0 ] | [ 1 0 0 0 0 0 0 0 ] | 0x018080 + +``` + + diff --git a/contracts/deploy/00-home-chain-arbitration.ts b/contracts/deploy/00-home-chain-arbitration.ts index bcb27b7be..3e0052bcf 100644 --- a/contracts/deploy/00-home-chain-arbitration.ts +++ b/contracts/deploy/00-home-chain-arbitration.ts @@ -27,9 +27,17 @@ const deployArbitration: DeployFunction = async (hre: HardhatRuntimeEnvironment) from: deployer, log: true, }); + + const SSQLibrary = await deploy("SSQ", { + from: deployer, + log: true, + }); const disputeKit = await deploy("DisputeKitClassic", { from: deployer, + libraries: { + SSQ: SSQLibrary.address + }, args: [deployer, AddressZero, rng.address], log: true, }); @@ -58,6 +66,7 @@ const deployArbitration: DeployFunction = async (hre: HardhatRuntimeEnvironment) from: deployer, libraries: { SortitionSumTreeFactory: sortitionSumTreeLibrary.address, + SSQ: SSQLibrary.address }, args: [deployer, pnk, AddressZero, disputeKit.address, false, minStake, alpha, feeForJuror, 3, [0, 0, 0, 0], 3], log: true, diff --git a/contracts/src/arbitration/KlerosCore.sol b/contracts/src/arbitration/KlerosCore.sol index a339ea9e6..0de334d5f 100644 --- a/contracts/src/arbitration/KlerosCore.sol +++ b/contracts/src/arbitration/KlerosCore.sol @@ -13,6 +13,7 @@ pragma solidity ^0.8; import "@openzeppelin/contracts/token/ERC20/IERC20.sol"; import "./IArbitrator.sol"; import "./IDisputeKit.sol"; +import {SSQ} from "../libraries/SSQ.sol"; import {SortitionSumTreeFactory} from "../data-structures/SortitionSumTreeFactory.sol"; /** @@ -20,6 +21,7 @@ import {SortitionSumTreeFactory} from "../data-structures/SortitionSumTreeFactor * Core arbitrator contract for Kleros v2. */ contract KlerosCore is IArbitrator { + using SSQ for bytes32; // Use library functions for deserialization to reduce L1 calldata costs on Optimistic Rollups. using SortitionSumTreeFactory for SortitionSumTreeFactory.SortitionSumTrees; // Use library functions for sortition sum trees. // ************************************* // @@ -341,6 +343,16 @@ contract KlerosCore is IArbitrator { // * State Modifiers * // // ************************************* // + /** @dev Sets the caller's stake in a subcourt by passing serialized args to reduce L1 calldata gas costs on optimistic rollups. + * @param _args The SSQ serialized arguments. + */ + function setStake(bytes32 _args) external{ + uint256 subcourtID; + uint256 stake; + (subcourtID, _args) = _args.unsquanchUint256(); + (stake, _args) = _args.unsquanchUint256(); + require(setStakeForAccount(msg.sender, uint96(subcourtID), stake, 0), "Staking failed"); + } /** @dev Sets the caller's stake in a subcourt. * @param _subcourtID The ID of the subcourt. * @param _stake The new stake. @@ -349,19 +361,48 @@ contract KlerosCore is IArbitrator { require(setStakeForAccount(msg.sender, _subcourtID, _stake, 0), "Staking failed"); } + /** @dev Creates a dispute by calling _creatDispute() with serialized args to reduce L1 calldata costs with optimistic rollups. + * @param _args The SSQ serialized arguments passed to _createDispute(...) + * @return disputeID The ID of the created dispute. + */ + function createDispute(bytes32 _args) + external + payable + returns (uint256 disputeID) + { + uint numberOfChoices; + bytes memory extraData; + (numberOfChoices, _args) = _args.unsquanchUint256(); + (extraData, _args) = _args.unsquanchBytesLeftPadded(); + return _createDispute(numberOfChoices, extraData); + } + /** @dev Creates a dispute. Must be called by the arbitrable contract. * @param _numberOfChoices Number of choices for the jurors to choose from. * @param _extraData Additional info about the dispute. We use it to pass the ID of the dispute's subcourt (first 32 bytes), * the minimum number of jurors required (next 32 bytes) and the ID of the specific dispute kit (last 32 bytes). * @return disputeID The ID of the created dispute. */ - function createDispute(uint256 _numberOfChoices, bytes memory _extraData) + function createDispute(uint256 _numberOfChoices, bytes calldata _extraData) external - payable override + payable returns (uint256 disputeID) { require(msg.value >= arbitrationCost(_extraData), "Not enough ETH to cover arbitration cost."); + return _createDispute(_numberOfChoices, _extraData); + } + + /** @dev Creates a dispute. Must be called by the arbitrable contract. + * @param _numberOfChoices Number of choices for the jurors to choose from. + * @param _extraData Additional info about the dispute. We use it to pass the ID of the dispute's subcourt (first 32 bytes), + * the minimum number of jurors required (next 32 bytes) and the ID of the specific dispute kit (last 32 bytes). + * @return disputeID The ID of the created dispute. + */ + function _createDispute(uint256 _numberOfChoices, bytes memory _extraData) + internal + returns (uint256 disputeID) + { (uint96 subcourtID, , uint8 disputeKitID) = extraDataToSubcourtIDMinJurorsDisputeKit(_extraData); uint256 bitToCheck = 1 << disputeKitID; // Get the bit that corresponds with dispute kit's ID. diff --git a/contracts/src/arbitration/dispute-kits/DisputeKitClassic.sol b/contracts/src/arbitration/dispute-kits/DisputeKitClassic.sol index 43e666c04..c8d10d1eb 100644 --- a/contracts/src/arbitration/dispute-kits/DisputeKitClassic.sol +++ b/contracts/src/arbitration/dispute-kits/DisputeKitClassic.sol @@ -13,6 +13,7 @@ pragma solidity ^0.8; import "./BaseDisputeKit.sol"; import "../../rng/RNG.sol"; import "../../evidence/IEvidence.sol"; +import {SSQ} from "../../libraries/SSQ.sol"; /** * @title DisputeKitClassic @@ -25,6 +26,7 @@ import "../../evidence/IEvidence.sol"; * - phase management: Generating->Drawing->Resolving->Generating in coordination with KlerosCore to freeze staking. */ contract DisputeKitClassic is BaseDisputeKit, IEvidence { + using SSQ for bytes32; // Use library functions for deserialization to reduce L1 calldata costs on Optimistic Rollups. // ************************************* // // * Structs * // // ************************************* // @@ -195,6 +197,22 @@ contract DisputeKitClassic is BaseDisputeKit, IEvidence { round.votes.push(Vote({account: drawnAddress, commit: bytes32(0), choice: 0, voted: false})); } + /** @dev Sets the caller's commit by passing serialized arguments + * `O(n)` where + * `n` is the number of votes. + * @param _args The SSQ serialized arguments + * @param _commit The commit. + */ + function castCommit( + bytes32 _args, bytes32 _commit + ) external { + uint256[] memory voteIDs; + uint256 disputeID; + ( disputeID, _args )= _args.unsquanchUint256(); + (voteIDs, _args) = _args.unsquanchUint256Array(); + + _castCommit(disputeID, voteIDs, _commit); + } /** @dev Sets the caller's commit for the specified votes. * `O(n)` where @@ -205,9 +223,24 @@ contract DisputeKitClassic is BaseDisputeKit, IEvidence { */ function castCommit( uint256 _disputeID, - uint256[] calldata _voteIDs, + uint256[] memory _voteIDs, bytes32 _commit ) external { + _castCommit(_disputeID, _voteIDs, _commit); + } + + /** @dev Sets the caller's commit for the specified votes. + * `O(n)` where + * `n` is the number of votes. + * @param _disputeID The ID of the dispute. + * @param _voteIDs The IDs of the votes. + * @param _commit The commit. + */ + function _castCommit( + uint256 _disputeID, + uint256[] memory _voteIDs, + bytes32 _commit + ) internal { require( core.getCurrentPeriod(_disputeID) == KlerosCore.Period.commit, "The dispute should be in Commit period." @@ -226,6 +259,25 @@ contract DisputeKitClassic is BaseDisputeKit, IEvidence { if (round.totalCommitted == round.votes.length) core.passPeriod(_disputeID); } + /** @dev Sets the caller's choices by passing serialzed arguments to reduce L1 calldata gas costs on optimistic rollups. + * `O(n)` where + * `n` is the number of votes. + * @param _args The SSQ serialized args. + * @param _salt The salt for the commit if the votes were hidden. + */ + function castVote( + bytes32 _args, uint256 _salt + ) external { + uint256[] memory voteIDs; + uint256 disputeID; + uint256 choice; + ( disputeID, _args )= _args.unsquanchUint256(); + ( voteIDs, _args ) = _args.unsquanchUint256Array(); + ( choice, _args )= _args.unsquanchUint256(); + + _castVote(disputeID, voteIDs, choice, _salt); + } + /** @dev Sets the caller's choices for the specified votes. * `O(n)` where * `n` is the number of votes. @@ -236,10 +288,26 @@ contract DisputeKitClassic is BaseDisputeKit, IEvidence { */ function castVote( uint256 _disputeID, - uint256[] calldata _voteIDs, + uint256[] memory _voteIDs, uint256 _choice, uint256 _salt ) external { + _castVote(_disputeID, _voteIDs, _choice, _salt); + } + /** @dev Sets the caller's choices for the specified votes. + * `O(n)` where + * `n` is the number of votes. + * @param _disputeID The ID of the dispute. + * @param _voteIDs The IDs of the votes. + * @param _choice The choice. + * @param _salt The salt for the commit if the votes were hidden. + */ + function _castVote( + uint256 _disputeID, + uint256[] memory _voteIDs, + uint256 _choice, + uint256 _salt + ) internal { require(core.getCurrentPeriod(_disputeID) == KlerosCore.Period.vote, "The dispute should be in Vote period."); require(_voteIDs.length > 0, "No voteID provided"); @@ -282,12 +350,33 @@ contract DisputeKitClassic is BaseDisputeKit, IEvidence { if (round.totalVoted == round.votes.length) core.passPeriod(_disputeID); } + /** @dev Manages contributions and appeals by passing SSQ serialized args to reduce L1 calldata gas cost on optimistic rollups. + * Note that the surplus deposit will be reimbursed. + * @param _args The SSQ serialized arguments. + */ + function fundAppeal(bytes32 _args) external payable { + uint256 disputeID; + uint256 choice; + ( disputeID, _args )= _args.unsquanchUint256(); + ( choice, _args )= _args.unsquanchUint256(); + + _fundAppeal(disputeID, choice); + } + /** @dev Manages contributions, and appeals a dispute if at least two choices are fully funded. * Note that the surplus deposit will be reimbursed. * @param _disputeID Index of the dispute in Kleros Core contract. * @param _choice A choice that receives funding. */ function fundAppeal(uint256 _disputeID, uint256 _choice) external payable { + _fundAppeal(_disputeID, _choice); + } + /** @dev Manages contributions, and appeals a dispute if at least two choices are fully funded. + * Note that the surplus deposit will be reimbursed. + * @param _disputeID Index of the dispute in Kleros Core contract. + * @param _choice A choice that receives funding. + */ + function _fundAppeal(uint256 _disputeID, uint256 _choice) internal { Dispute storage dispute = disputes[coreDisputeIDToLocal[_disputeID]]; require(_choice <= dispute.numberOfChoices, "There is no such ruling to fund."); @@ -341,6 +430,23 @@ contract DisputeKitClassic is BaseDisputeKit, IEvidence { if (msg.value > contribution) payable(msg.sender).send(msg.value - contribution); } + /** @dev Withdraw any reimbursable fees or rewards by passing serialized args to reduce L1 calldata gas costs on optimistic rollups + * @param _args The SSQ serialized arguments. + * @return amount The withdrawn amount. + */ + function withdrawFeesAndRewards(bytes32 _args) external returns(uint256 amount) { + uint256 disputeID; + uint256 beneficiary; + uint256 round; + uint256 choice; + ( disputeID, _args )= _args.unsquanchUint256(); + ( beneficiary, _args )= _args.unsquanchUint256(); + ( round, _args )= _args.unsquanchUint256(); + ( choice, _args )= _args.unsquanchUint256(); + + return _withdrawFeesAndRewards(disputeID, payable(address(uint160(beneficiary))), round, choice); + } + /** @dev Allows those contributors who attempted to fund an appeal round to withdraw any reimbursable fees or rewards after the dispute gets resolved. * @param _disputeID Index of the dispute in Kleros Core contract. * @param _beneficiary The address whose rewards to withdraw. @@ -354,6 +460,21 @@ contract DisputeKitClassic is BaseDisputeKit, IEvidence { uint256 _round, uint256 _choice ) external returns (uint256 amount) { + return _withdrawFeesAndRewards(_disputeID, _beneficiary, _round, _choice); + } + /** @dev Allows those contributors who attempted to fund an appeal round to withdraw any reimbursable fees or rewards after the dispute gets resolved. + * @param _disputeID Index of the dispute in Kleros Core contract. + * @param _beneficiary The address whose rewards to withdraw. + * @param _round The round the caller wants to withdraw from. + * @param _choice The ruling option that the caller wants to withdraw from. + * @return amount The withdrawn amount. + */ + function _withdrawFeesAndRewards( + uint256 _disputeID, + address payable _beneficiary, + uint256 _round, + uint256 _choice + ) internal returns (uint256 amount) { require(core.isRuled(_disputeID), "Dispute should be resolved."); Dispute storage dispute = disputes[coreDisputeIDToLocal[_disputeID]]; diff --git a/contracts/src/libraries/SSQ.sol b/contracts/src/libraries/SSQ.sol new file mode 100644 index 000000000..2126f74ec --- /dev/null +++ b/contracts/src/libraries/SSQ.sol @@ -0,0 +1,217 @@ +// SPDX-License-Identifier: MIT + +/** + * @authors: [@shotaronowhere] + * @reviewers: [] + * @auditors: [] + * @bounties: [] + * @deployments: [] + */ + +pragma solidity ^0.8.0; + +/** + * @title SSQ - SimpleSquanch + * @dev A library of functions to serialize data + */ +library SSQ { + + /** + * @dev Serializes data by encoding input with SSQ. + * @param _input Input to be serialized. + * @param _result SSQ Serialized 'Squanched' result. + */ + function squanchClassic(bytes32[] calldata _input) public pure returns (bytes32[] memory _result){ + + assembly { + _result := mload(0x40) // free memory pointer + let lengthInput := calldataload(0x24) // skip 4 bytes function selector + let _cursor := add(_result,0x3F) // skip length and starting from right to left + for { let i := 0} lt(i, lengthInput) { i:= add(i, 1)}{ + + let _encodedInputSlot := encode(calldataload(add(mul(0x20,i),0x44))) + + for { let j := 0} lt(j,0x20) {j:=add(j,1) }{ + + mstore8(_cursor,_encodedInputSlot) + _encodedInputSlot := shr(8,_encodedInputSlot) + if eq(_encodedInputSlot, 0){ + j := 0x20 + } + // shift cursor to next 32bytes + switch mod(sub(_cursor,_result),0x20) + case 0{ + _cursor := add(_cursor,0x3F) + } + default{ + _cursor := sub(_cursor,1) + } + } + } + + switch mod(sub(_cursor,_result),0x20) + case 0x1F{ + mstore(_result,sub(div(sub(_cursor,_result),0x20),1)) // store length + // align cursor to end on a multiple of 32 bytes + _cursor := sub(_cursor,0x1F) + mstore(0x40, _cursor) + } + default{ + mstore(_result,div(sub(_cursor,_result),0x20)) // store length + // align cursor to end on a multiple of 32 bytes + _cursor := add(_cursor,sub(0x20, mod(sub(_cursor,_result),0x20))) + mstore(0x40, _cursor) + } + /** + * @dev Serializes data with SSQ. + * @param _inputSlot Input 32 bytes to be serialized. + * @param _resultSlot Serialized 'Squanched' result. + */ + function encode(_inputSlot) -> _resultSlot { + let i := 0 + for { } gt(_inputSlot,0x7F) {i := add(i,1)} { + _resultSlot := add(_resultSlot,shl(mul(8,i),add(0x80,and(_inputSlot, 0x7F)))) + _inputSlot := shr(7,_inputSlot) + } + _resultSlot := add(_resultSlot,shl(mul(8,i),and(_inputSlot, 0x7F))) + } + } + } + + /** + * @dev Deserializes data and left pads with zeros. + * @param encoded Input to be deserialized. + * @param decodedBytes Left zero padded bytes array containing decoded values. + * @param remainder The remaining encoded byte32. + */ + function unsquanchBytesLeftPadded(bytes32 encoded) public pure returns (bytes memory decodedBytes, bytes32 remainder){ + + assembly { + + decodedBytes := mload(0x40) + let cursor := decodedBytes + // to use the first decoded bytes32 as the output array length + let flagIsFirst := 1 + let length := 0 + let decoded := 0 + let decodedIndex := 0 + + for { let j := 0 } lt(j,0x20) {j := add(j,1)} { + decoded := add(decoded,shl(mul(7,decodedIndex),and(encoded, 0x7F))) + switch and(encoded,0x80) + case 0{ + if flagIsFirst{ + length := decoded + flagIsFirst := 0 + } + mstore(cursor,decoded) + if eq(sub(cursor, decodedBytes), length){ + j := 0x20 + } + cursor := add(cursor,0x20) + decoded := 0 + decodedIndex := 0 + } + default{ + decodedIndex := add(decodedIndex, 1) + } + encoded := shr(8,encoded) + } + remainder := encoded + mstore(0x40, cursor) + } + } + + + /** + * @dev Deserializes encoded data to a single uint256 + * @param encoded Input to be deserialized. + * @param decodedUint256 Decoded uint256 + * @param remainder The remaining encoded byte32. + */ + function unsquanchUint256(bytes32 encoded) public pure returns (uint256 decodedUint256, bytes32 remainder){ + assembly { + let decodedIndex := 0 + for { let j := 0 } lt(j,0x20) {j := add(j,1)} { + decodedUint256 := add(decodedUint256,shl(mul(7,decodedIndex),and(encoded, 0x7F))) + if eq(and(encoded,0x80),0) { j := 0x20 } + decodedIndex := add(decodedIndex, 1) + encoded := shr(8,encoded) + } + remainder := encoded + } + } + + /** + * @dev Deserializes data and left pads with zeros. + * @param encoded Input to be deserialized. + * @param decodedUint256Array Left zero padded bytes array containing decoded values. + * @param remainder The remaining encoded byte32. + */ + function unsquanchUint256Array(bytes32 encoded) public pure returns (uint256[] memory decodedUint256Array, bytes32 remainder){ + + assembly { + + decodedUint256Array := mload(0x40) + let cursor := decodedUint256Array + // to use the first decoded bytes32 as the output array length + let flagIsFirst := 1 + let length := 0 + let decoded := 0 + let decodedIndex := 0 + + for { let j := 0 } lt(j,0x20) {j := add(j,1)} { + decoded := add(decoded,shl(mul(7,decodedIndex),and(encoded, 0x7F))) + switch and(encoded,0x80) + case 0{ + if flagIsFirst{ + length := decoded + flagIsFirst := 0 + } + mstore(cursor,decoded) + if eq(div(sub(cursor, decodedUint256Array),0x20), length){ + j := 0x20 + } + cursor := add(cursor,0x20) + decoded := 0 + decodedIndex := 0 + } + default{ + decodedIndex := add(decodedIndex, 1) + } + encoded := shr(8,encoded) + } + mstore(0x40, cursor) + } + remainder = encoded; + } + + /** + * @dev Serializes data by encoding input with SSQ. + * @param _input Input to be serialized. + * @param _result Serialized 'Squanched' result. + */ + function encode(bytes32 _input) public pure returns (bytes32 _result){ + assembly { + let i := 0 + for { } gt(_input,0x7F) {i := add(i,1)} { + _result := add(_result,shl(mul(8,i),add(0x80,and(_input, 0x7F)))) + _input := shr(7,_input) + } + _result := add(_result,shl(mul(8,i),and(_input, 0x7F))) + } + } + /** + * @dev Deserializes data by reversing SSQ + * @param _input Input to be deserialized. + * @param _result Deserialized 'Unsquanched' result. + */ + function decode(bytes32 _input) public pure returns (bytes32 _result){ + assembly { + for { let i := 0x0 } gt(_input,0) {i := add(i,0x1)} { + _result := add(_result,shl(mul(7,i),and(_input, 0x7F))) + _input := shr(8,_input) + } + } + } +}