Skip to content

Commit 772c3c2

Browse files
author
Ho
committed
code for priority op queue, include unit-tests
1 parent 16ac1cf commit 772c3c2

File tree

7 files changed

+411
-0
lines changed

7 files changed

+411
-0
lines changed

contracts/Bytes.sol

Lines changed: 105 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,105 @@
1+
// SPDX-License-Identifier: MIT OR Apache-2.0
2+
3+
pragma solidity ^0.8.0;
4+
5+
library Bytes {
6+
7+
function shiftAndReverseBits8(uint8 val, uint8 offset) internal pure returns (uint256 ret) {
8+
uint16 effectLen = offset < 248 ? 8 : 256 - offset;
9+
for(uint16 i = 0; i < effectLen; i++){
10+
if (val & 1 == 1){
11+
ret += (1 << (255-i-offset));
12+
}
13+
val >>=1;
14+
}
15+
}
16+
17+
function shiftAndReverseBits16(uint16 val, uint8 offset) internal pure returns (uint256 ret) {
18+
uint16 effectLen = offset < 240 ? 16 : 256 - offset;
19+
for(uint16 i = 0; i < effectLen; i++){
20+
if (val & 1 == 1){
21+
ret += (1 << (255-i-offset));
22+
}
23+
val >>=1;
24+
}
25+
}
26+
27+
function shiftAndReverseBits32(uint32 val, uint8 offset) internal pure returns (uint256 ret) {
28+
uint16 effectLen = offset < 224 ? 32 : 256 - offset;
29+
for(uint16 i = 0; i < effectLen; i++){
30+
if (val & 1 == 1){
31+
ret += (1 << (255-i-offset));
32+
}
33+
val >>=1;
34+
}
35+
}
36+
37+
function shiftAndReverseBits64(uint64 val, uint8 offset) internal pure returns (uint256 ret) {
38+
uint16 effectLen = offset < 192 ? 64 : 256 - offset;
39+
for(uint16 i = 0; i < effectLen; i++){
40+
if (val & 1 == 1){
41+
ret += (1 << (255-i-offset));
42+
}
43+
val >>=1;
44+
}
45+
}
46+
47+
function shiftAndReverseBits128(uint128 val, uint8 offset) internal pure returns (uint256 ret) {
48+
uint16 effectLen = offset < 128 ? 128 : 256 - offset;
49+
for(uint16 i = 0; i < effectLen; i++){
50+
if (val & 1 == 1){
51+
ret += (1 << (255-i-offset));
52+
}
53+
val >>=1;
54+
}
55+
}
56+
57+
function shiftAndReverseBits(uint256 val, uint8 offset) internal pure returns (uint256 ret) {
58+
for(uint16 i = 0; i < 256 - offset; i++){
59+
if (val & 1 == 1){
60+
ret += (1 << (255-i-offset));
61+
}
62+
val >>=1;
63+
}
64+
}
65+
66+
//see https://ethereum.stackexchange.com/questions/83626/how-to-reverse-byte-order-in-uint256-or-bytes32
67+
function swapBytes(uint256 input) internal pure returns (uint256 v) {
68+
v = input;
69+
70+
// swap bytes
71+
v = ((v & 0xFF00FF00FF00FF00FF00FF00FF00FF00FF00FF00FF00FF00FF00FF00FF00FF00) >> 8) |
72+
((v & 0x00FF00FF00FF00FF00FF00FF00FF00FF00FF00FF00FF00FF00FF00FF00FF00FF) << 8);
73+
74+
// swap 2-byte long pairs
75+
v = ((v & 0xFFFF0000FFFF0000FFFF0000FFFF0000FFFF0000FFFF0000FFFF0000FFFF0000) >> 16) |
76+
((v & 0x0000FFFF0000FFFF0000FFFF0000FFFF0000FFFF0000FFFF0000FFFF0000FFFF) << 16);
77+
78+
// swap 4-byte long pairs
79+
v = ((v & 0xFFFFFFFF00000000FFFFFFFF00000000FFFFFFFF00000000FFFFFFFF00000000) >> 32) |
80+
((v & 0x00000000FFFFFFFF00000000FFFFFFFF00000000FFFFFFFF00000000FFFFFFFF) << 32);
81+
82+
// swap 8-byte long pairs
83+
v = ((v & 0xFFFFFFFFFFFFFFFF0000000000000000FFFFFFFFFFFFFFFF0000000000000000) >> 64) |
84+
((v & 0x0000000000000000FFFFFFFFFFFFFFFF0000000000000000FFFFFFFFFFFFFFFF) << 64);
85+
86+
// swap 16-byte long pairs
87+
v = (v >> 128) | (v << 128);
88+
}
89+
90+
// See comment at the top of this file for explanation of how this function works.
91+
// NOTE: theoretically possible overflow of (_start + 0x2)
92+
function bytesToUInt16(bytes memory _bytes, uint256 _start) internal pure returns (uint16 r) {
93+
uint256 offset = _start + 0x2;
94+
require(_bytes.length >= offset, "T");
95+
assembly {
96+
r := mload(add(_bytes, offset))
97+
}
98+
}
99+
100+
// NOTE: theoretically possible overflow of (_offset + 2)
101+
function readUInt16(bytes memory _data, uint256 _offset) internal pure returns (uint256 newOffset, uint16 r) {
102+
newOffset = _offset + 2;
103+
r = bytesToUInt16(_data, _offset);
104+
}
105+
}

contracts/Operations.sol

Lines changed: 110 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,110 @@
1+
// SPDX-License-Identifier: MIT OR Apache-2.0
2+
3+
pragma solidity ^0.8.0;
4+
pragma experimental ABIEncoderV2;
5+
6+
import "./Bytes.sol";
7+
import "./Utils.sol";
8+
import "hardhat/console.sol"; //for debugging
9+
10+
/// @title Fluidex operations tools
11+
library Operations {
12+
13+
/// @notice Config parameters, generated from circuit parameters
14+
uint8 constant BALANCE_BITS = 3;
15+
uint8 constant ACCOUNT_BITS = 4;
16+
uint8 constant ORDER_BITS = 4;
17+
uint constant TX_PUBDATA_BYTES = 33;
18+
19+
/// @dev Expected average period of block creation
20+
uint256 internal constant BLOCK_PERIOD = 15 seconds;
21+
22+
/// @dev Expiration delta for priority request to be satisfied (in seconds)
23+
/// @dev NOTE: Priority expiration should be > (EXPECT_VERIFICATION_IN * BLOCK_PERIOD)
24+
/// @dev otherwise incorrect block with priority op could not be reverted.
25+
uint256 internal constant PRIORITY_EXPIRATION_PERIOD = 7 days;
26+
27+
/// @dev Expiration delta for priority request to be satisfied (in ETH blocks)
28+
uint256 internal constant PRIORITY_EXPIRATION = PRIORITY_EXPIRATION_PERIOD / BLOCK_PERIOD;
29+
30+
/// @notice Fluidex circuit operation type
31+
enum OpType {
32+
Deposit,
33+
Registry
34+
}
35+
36+
function hashPubData(bytes calldata _public_data, uint pos) internal pure returns (bytes20){
37+
return Utils.hashBytesToBytes20(_public_data[pos:pos+TX_PUBDATA_BYTES]);
38+
}
39+
40+
// Registry L2key pubdata
41+
struct Registry {
42+
uint32 accountId;
43+
bytes32 l2key;
44+
}
45+
46+
// Deposit pubdata
47+
struct Deposit {
48+
uint32 accountId;
49+
uint16 tokenId;
50+
uint128 amount;
51+
}
52+
53+
function scaleTokenValueToAmount(uint value, uint8 scale) internal pure returns (uint128) {
54+
require(scale > 0, "Known token must has a scaling");
55+
return uint128(value / (10 ** scale));
56+
}
57+
58+
function hashOpDataFromBuf(bytes memory buf) internal pure returns (bytes20){
59+
bytes memory truncatedBuf = new bytes(TX_PUBDATA_BYTES);
60+
for(uint i = 0; i < TX_PUBDATA_BYTES; i++){
61+
truncatedBuf[i] = buf[i];
62+
}
63+
64+
return Utils.hashBytesToBytes20(truncatedBuf);
65+
}
66+
67+
/// Serialize registry pubdata
68+
function writeRegistryPubdataForPriorityQueue(Registry memory op) internal pure returns (bytes20) {
69+
70+
uint256 encoded_1 = 0;
71+
uint8 offset = 0;
72+
encoded_1 += Bytes.shiftAndReverseBits8(uint8(1), offset); //100 in bits
73+
offset += 3;
74+
encoded_1 += Bytes.shiftAndReverseBits32(op.accountId, offset);
75+
offset += ACCOUNT_BITS;
76+
uint8 sign = op.l2key[31] & 0x80 != 0 ? 1 : 0;
77+
encoded_1 += Bytes.shiftAndReverseBits8(sign, offset);
78+
offset += 1;
79+
uint256 ay = uint256(op.l2key);
80+
ay -= (uint8(op.l2key[31]) - uint8(op.l2key[31] & 0x7f));
81+
//notice babyjub consider the read bytes as LITTLE-endian integer
82+
ay = Bytes.swapBytes(ay);
83+
encoded_1 += Bytes.shiftAndReverseBits(ay, offset);
84+
//calc the resident of bits in ay
85+
ay >>= (256 - offset);
86+
uint256 encoded_2 = Bytes.shiftAndReverseBits(ay, 0);
87+
88+
return hashOpDataFromBuf(abi.encodePacked(encoded_1, encoded_2));
89+
}
90+
91+
/// Serialize deposit pubdata
92+
function writeDepositPubdataForPriorityQueue(Deposit memory op) internal pure returns (bytes20) {
93+
uint256 encoded_1 = 0;
94+
uint8 offset = 0;
95+
encoded_1 += Bytes.shiftAndReverseBits8(uint8(0), offset); //000 in bits
96+
offset += 3;
97+
encoded_1 += Bytes.shiftAndReverseBits32(op.accountId, offset);
98+
offset += ACCOUNT_BITS;
99+
//according to the encoding scheme we need to encode account id twice
100+
encoded_1 += Bytes.shiftAndReverseBits32(op.accountId, offset);
101+
offset += ACCOUNT_BITS;
102+
encoded_1 += Bytes.shiftAndReverseBits16(op.tokenId, offset);
103+
offset += BALANCE_BITS;
104+
assert(offset <= 128);
105+
106+
encoded_1 += Bytes.shiftAndReverseBits128(op.amount, offset);
107+
108+
return hashOpDataFromBuf(abi.encodePacked(encoded_1, uint256(0)));
109+
}
110+
}

contracts/Storage.sol

Lines changed: 74 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,74 @@
1+
// SPDX-License-Identifier: MIT OR Apache-2.0
2+
3+
pragma solidity ^0.8.0;
4+
5+
import "./Operations.sol";
6+
7+
/// @title Fluidex storage contract
8+
contract Storage {
9+
10+
/// @notice First open priority request id
11+
uint64 public firstPriorityRequestId;
12+
13+
/// @notice Total number of requests
14+
uint64 public totalOpenPriorityRequests;
15+
16+
/// @notice Priority Operation container
17+
/// @member hashedPubData Hashed priority operation public data
18+
/// @member expirationBlock Expiration block number (ETH block) for this request (must be satisfied before)
19+
/// @member opType Priority operation type
20+
struct PriorityOperation {
21+
bytes20 hashedPubData;
22+
uint64 expirationBlock;
23+
Operations.OpType opType;
24+
}
25+
26+
/// @dev Priority Requests mapping (request id - operation)
27+
/// @dev Contains op type, pubdata and expiration block of unsatisfied requests.
28+
/// @dev Numbers are in order of requests receiving
29+
mapping(uint64 => PriorityOperation) internal priorityRequests;
30+
31+
/// @notice Verify priorityOp inside the calling public data match the priority queue
32+
/// @dev Calculates expiration block for request, store this request and emit NewPriorityRequest event
33+
/// @param _public_data Calling public_data
34+
/// @param _op_indexs indexs specify which op should be checked
35+
function verifyPriorityOp(bytes calldata _public_data, bytes calldata _op_indexs) internal view returns (bool _ret, uint64 _priorityRequestId){
36+
//_op_indexs is uint16 being encode packed
37+
assert(_op_indexs.length % 2 == 0);
38+
39+
_priorityRequestId = firstPriorityRequestId;
40+
41+
for(uint i = 0; i < _op_indexs.length; i+= 2){
42+
//TODO: with compiler later than 0.8.10 we can use slice
43+
//uint pos = uint16(bytes2(_op_indexs[i:i+2]));
44+
uint pos = uint(uint8(_op_indexs[i])) * 256 + uint(uint8(_op_indexs[i+1]));
45+
assert(pos < _public_data.length);
46+
bytes20 hashedPubdata = priorityRequests[_priorityRequestId].hashedPubData;
47+
if (Operations.hashPubData(_public_data, pos) != hashedPubdata){
48+
return (false, _priorityRequestId);
49+
}
50+
_priorityRequestId++;
51+
}
52+
53+
_ret = true;
54+
}
55+
56+
/// @notice Saves priority request in storage
57+
/// @dev Calculates expiration block for request, store this request and emit NewPriorityRequest event
58+
/// @param _opType Rollup operation type
59+
/// @param _hashedPubData Hashed Operation pubdata
60+
function addPriorityRequest(Operations.OpType _opType, bytes20 _hashedPubData) internal {
61+
// Expiration block is: current block number + priority expiration delta
62+
uint64 expirationBlock = uint64(block.number + Operations.PRIORITY_EXPIRATION);
63+
64+
uint64 nextPriorityRequestId = firstPriorityRequestId + totalOpenPriorityRequests;
65+
66+
priorityRequests[nextPriorityRequestId] = PriorityOperation({
67+
hashedPubData: _hashedPubData,
68+
expirationBlock: expirationBlock,
69+
opType: _opType
70+
});
71+
72+
totalOpenPriorityRequests++;
73+
}
74+
}

contracts/TestLibs.sol

Lines changed: 42 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,42 @@
1+
// SPDX-License-Identifier: MIT OR Apache-2.0
2+
3+
pragma solidity ^0.8.0;
4+
5+
import "./Operations.sol";
6+
7+
/// @title Unittest contract for libraries
8+
contract TestLibs {
9+
10+
function testBitsOp8(uint8 val, uint8 offset) public pure returns (uint256){
11+
return Bytes.shiftAndReverseBits8(val, offset);
12+
}
13+
14+
function testBitsOp32(uint32 val, uint8 offset) public pure returns (uint256){
15+
return Bytes.shiftAndReverseBits32(val, offset);
16+
}
17+
18+
function testBitsOp256(uint256 val, uint8 offset) public pure returns (uint256){
19+
return Bytes.shiftAndReverseBits(val, offset);
20+
}
21+
22+
function testWriteRegistryPubdata(uint32 accId, bytes32 l2key) public pure returns (bytes20){
23+
24+
Operations.Registry memory op = Operations.Registry({
25+
accountId: accId,
26+
l2key: l2key
27+
});
28+
29+
return Operations.writeRegistryPubdataForPriorityQueue(op);
30+
}
31+
32+
function testWriteDepositPubdata(uint32 accId, uint16 tokenId, uint128 amount) public pure returns (bytes20){
33+
34+
Operations.Deposit memory op = Operations.Deposit({
35+
accountId: accId,
36+
tokenId: tokenId,
37+
amount: amount
38+
});
39+
40+
return Operations.writeDepositPubdataForPriorityQueue(op);
41+
}
42+
}

contracts/Utils.sol

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,9 @@
1+
// SPDX-License-Identifier: MIT OR Apache-2.0
2+
3+
pragma solidity ^0.8.0;
4+
5+
library Utils {
6+
function hashBytesToBytes20(bytes memory _bytes) internal pure returns (bytes20) {
7+
return bytes20(uint160(uint256(keccak256(_bytes))));
8+
}
9+
}

package.json

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -13,6 +13,7 @@
1313
"ethers": "^5.0.24",
1414
"fluidex.js": "git+https://github.com/fluidex/fluidex.js.git#b9224719013c224445fe8ec06f6a05e800b4469e",
1515
"hardhat": "^2.0.6",
16+
"mocha": "^9.1.3",
1617
"prettier": "^2.2.1",
1718
"prettier-plugin-solidity": "^1.0.0-beta.2",
1819
"ts-node": "^10.2.1",

0 commit comments

Comments
 (0)