-
Notifications
You must be signed in to change notification settings - Fork 2
/
BlobStorageManager.sol
179 lines (146 loc) · 6.03 KB
/
BlobStorageManager.sol
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.0;
import "@openzeppelin/contracts/access/Ownable.sol";
enum DecodeType {
RawData,
PaddingPer31Bytes
}
interface IEthStorageContract {
function putBlob(bytes32 key, uint256 blobIdx, uint256 length) external payable;
function get(bytes32 key, DecodeType decodeType, uint256 off, uint256 len) external view returns (bytes memory);
function remove(bytes32 key) external;
function hash(bytes32 key) external view returns (bytes24);
function size(bytes32 key) external view returns (uint256);
function upfrontPayment() external view returns (uint256);
}
contract BlobStorageManager is Ownable {
uint32 public maxChunkSize;
IEthStorageContract public storageContract;
mapping(bytes32 => mapping(uint256 => bytes32)) internal keyToChunks;
constructor(uint32 size, address storageAddress) {
maxChunkSize = size;
storageContract = IEthStorageContract(storageAddress);
}
function setStorageContract(address storageAddress) public onlyOwner {
storageContract = IEthStorageContract(storageAddress);
}
function setMaxChunkSize(uint32 size) public onlyOwner {
maxChunkSize = size;
}
function isSupportBlob() view public returns (bool) {
return address(storageContract) != address(0) && upfrontPayment() >= 0;
}
function upfrontPayment() public view returns (uint256) {
return storageContract.upfrontPayment();
}
function _countChunksFromBlob(bytes32 key) internal view returns (uint256) {
uint256 chunkId = 0;
while (true) {
bytes32 chunkKey = keyToChunks[key][chunkId];
if (chunkKey == bytes32(0)) {
break;
}
chunkId++;
}
return chunkId;
}
function _chunkSizeFromBlob(bytes32 key, uint256 chunkId) internal view returns (uint256, bool) {
if (chunkId >= _countChunksFromBlob(key)) {
return (0, false);
}
uint256 length = storageContract.size(keyToChunks[key][chunkId]);
return (length, true);
}
function _sizeFromBlob(bytes32 key) internal view returns (uint256, uint256) {
uint256 chunkNum = _countChunksFromBlob(key);
uint256 size = 0;
for (uint256 chunkId = 0; chunkId < chunkNum; chunkId++) {
size += storageContract.size(keyToChunks[key][chunkId]);
}
return (size, chunkNum);
}
function _getChunkFromBlob(bytes32 key, uint256 chunkId) internal view returns (bytes memory, bool) {
(uint256 length,) = _chunkSizeFromBlob(key, chunkId);
if (length < 1) {
return (new bytes(0), false);
}
bytes memory data = storageContract.get(keyToChunks[key][chunkId], DecodeType.PaddingPer31Bytes, 0, length);
return (data, true);
}
function _getFromBlob(bytes32 key) internal view returns (bytes memory, bool) {
(uint256 fileSize, uint256 chunkNum) = _sizeFromBlob(key);
if (chunkNum == 0) {
return (new bytes(0), false);
}
bytes memory concatenatedData = new bytes(fileSize);
uint256 offset = 0;
for (uint256 chunkId = 0; chunkId < chunkNum; chunkId++) {
bytes32 chunkKey = keyToChunks[key][chunkId];
uint256 length = storageContract.size(chunkKey);
storageContract.get(chunkKey, DecodeType.PaddingPer31Bytes, 0, length);
assembly {
returndatacopy(add(add(concatenatedData, offset), 0x20), 0x40, length)
}
offset += length;
}
return (concatenatedData, true);
}
function _removeChunkFromBlob(bytes32 key, uint256 chunkId) internal returns (bool) {
bytes32 chunkKey = keyToChunks[key][chunkId];
if (chunkKey == bytes32(0)) {
return false;
}
if (keyToChunks[key][chunkId + 1] != bytes32(0)) {
// only the last chunk can be removed
return false;
}
// TODO The current version does not support the delete
// storageContract.remove(keyToChunks[key][chunkId]);
keyToChunks[key][chunkId] = bytes32(0);
return true;
}
function _removeFromBlob(bytes32 key, uint256 chunkId) internal returns (uint256) {
while (true) {
bytes32 chunkKey = keyToChunks[key][chunkId];
if (chunkKey == bytes32(0)) {
break;
}
// TODO The current version does not support the delete
// storageContract.remove(keyToChunks[key][chunkId]);
keyToChunks[key][chunkId] = bytes32(0);
chunkId++;
}
return chunkId;
}
function _preparePutFromBlob(bytes32 key, uint256 chunkId) private {
bytes32 chunkKey = keyToChunks[key][chunkId];
if (chunkKey == bytes32(0)) {
require(chunkId == 0 || keyToChunks[key][chunkId - 1] != bytes32(0), "must replace or append");
} else {
// TODO The current version does not support the delete
// storageContract.remove(keyToChunks[key][chunkId]);
}
}
function _putChunks(
bytes32 key,
uint256[] memory chunkIds,
uint256[] memory sizes
) internal {
uint256 length = chunkIds.length;
uint256 cost = storageContract.upfrontPayment();
require(msg.value >= cost * length, "insufficient balance");
for (uint8 i = 0; i < length; i++) {
require(0 < sizes[i] && sizes[i] <= maxChunkSize, "invalid chunk length");
_preparePutFromBlob(key, chunkIds[i]);
bytes32 chunkKey = keccak256(abi.encode(msg.sender, key, chunkIds[i]));
storageContract.putBlob{value : cost}(chunkKey, i, sizes[i]);
keyToChunks[key][chunkIds[i]] = chunkKey;
}
}
function _getChunkHashFromBlob(bytes32 key, uint256 chunkId) public view returns (bytes32) {
if (chunkId >= _countChunksFromBlob(key)) {
return bytes32(0);
}
return storageContract.hash(keyToChunks[key][chunkId]);
}
}