diff --git a/README.md b/README.md index 3dd667a..00f5521 100644 --- a/README.md +++ b/README.md @@ -1,5 +1,8 @@ # EthStorage Decentralized Storage Contracts V1 +## Style Guide +Smart contracts should be written according to this [STYLE_GUIDE.md](https://github.com/ethstorage/optimism/blob/develop/packages/contracts-bedrock/STYLE_GUIDE.md) + ## How to verify the contract - Implementation: npx hardhat verify --network sepolia diff --git a/contracts/DecentralizedKV.sol b/contracts/DecentralizedKV.sol index 191fe68..e755215 100644 --- a/contracts/DecentralizedKV.sol +++ b/contracts/DecentralizedKV.sol @@ -2,94 +2,125 @@ pragma solidity ^0.8.0; import "@openzeppelin/contracts-upgradeable/access/OwnableUpgradeable.sol"; -import "./MerkleLib.sol"; -import "./BinaryRelated.sol"; +import "./libraries/MerkleLib.sol"; +import "./libraries/BinaryRelated.sol"; +/// @custom:upgradeable +/// @title DecentralizedKV +/// @notice The DecentralizedKV is a top base contract for the EthStorage contract. It provides the +/// basic key-value store functionalities. contract DecentralizedKV is OwnableUpgradeable { - event Remove(uint256 indexed kvIdx, uint256 indexed kvEntryCount); + /// @notice Represents the metadata of the key-value . + /// @custom:field kvIdx Internal address seeking. + /// @custom:field kvSize BLOB size. + /// @custom:field hash Commitment. + struct PhyAddr { + uint40 kvIdx; + uint24 kvSize; + bytes24 hash; + } + /// @notice Enum representing different decoding types when getting key-value. + /// @custom:field RawData Don't do any decoding. + /// @custom:field PaddingPer31Bytes Will remove the padding byte every 31 bytes. enum DecodeType { RawData, PaddingPer31Bytes } - uint256 public immutable storageCost; // Upfront storage cost (pre-dcf) - // Discounted cash flow factor in seconds - // E.g., 0.85 yearly discount in second = 0.9999999948465585 = 340282365167313208607671216367074279424 in Q128.128 - uint256 public immutable dcfFactor; - uint256 public immutable startTime; - uint256 public immutable maxKvSize; + /// @notice Upfront storage cost (pre-dcf) + uint256 internal immutable STORAGE_COST; + + /// @notice Discounted cash flow factor in seconds + /// E.g., 0.85 yearly discount in second = 0.9999999948465585 = 340282365167313208607671216367074279424 in Q128.128 + uint256 internal immutable DCF_FACTOR; + /// @notice The start time of the storage payment + uint256 internal immutable START_TIME; + + /// @notice Maximum size of a single key-value pair + uint256 internal immutable MAX_KV_SIZE; + + /// @custom:legacy /// @custom:spacer storageCost, dcfFactor, startTime, maxKvSize /// @notice Spacer for backwards compatibility. - uint256[4] public kvSpacers; - - uint40 public kvEntryCount; // number of entries in the store + uint256[4] private kvSpacers; - struct PhyAddr { - /* Internal address seeking */ - uint40 kvIdx; - /* BLOB size */ - uint24 kvSize; - /* Commitment */ - bytes24 hash; - } + /// @notice The number of entries in the store + uint40 public kvEntryCount; - /* skey - PhyAddr */ + /// @notice skey and PhyAddr mapping mapping(bytes32 => PhyAddr) internal kvMap; - /* index - skey, reverse lookup */ + + /// @notice index and skey mapping, reverse lookup mapping(uint256 => bytes32) internal idxMap; + /// @notice Emitted when a key-value is removed. + /// @param kvIdx The removed key-value index. + /// @param kvEntryCount The key-value entry count after removing the kvIdx. + event Remove(uint256 indexed kvIdx, uint256 indexed kvEntryCount); + // TODO: Reserve extra slots (to a total of 50?) in the storage layout for future upgrades + /// @notice Constructs the DecentralizedKV contract. Initializes the immutables. constructor(uint256 _maxKvSize, uint256 _startTime, uint256 _storageCost, uint256 _dcfFactor) { - maxKvSize = _maxKvSize; - startTime = _startTime; - storageCost = _storageCost; - dcfFactor = _dcfFactor; + MAX_KV_SIZE = _maxKvSize; + START_TIME = _startTime; + STORAGE_COST = _storageCost; + DCF_FACTOR = _dcfFactor; } - function __init_KV(address _owner) public onlyInitializing { + /// @notice Initializer. + /// @param _owner The contract owner. + function __init_KV(address _owner) internal onlyInitializing { __Context_init(); __Ownable_init(_owner); kvEntryCount = 0; } - function pow(uint256 fp, uint256 n) internal pure returns (uint256) { - return BinaryRelated.pow(fp, n); + /// @notice Pow function in Q128. + function _pow(uint256 _fp, uint256 _n) internal pure returns (uint256) { + return BinaryRelated.pow(_fp, _n); } - // Evaluate payment from [t0, t1) seconds - function _paymentInInterval(uint256 x, uint256 t0, uint256 t1) internal view returns (uint256) { - return (x * (pow(dcfFactor, t0) - pow(dcfFactor, t1))) >> 128; + /// @notice Evaluate payment from [t0, t1) seconds + function _paymentInInterval(uint256 _x, uint256 _t0, uint256 _t1) internal view returns (uint256) { + return (_x * (_pow(DCF_FACTOR, _t0) - _pow(DCF_FACTOR, _t1))) >> 128; } - // Evaluate payment from [t0, \inf). - function _paymentInf(uint256 x, uint256 t0) internal view returns (uint256) { - return (x * pow(dcfFactor, t0)) >> 128; + /// @notice Evaluate payment from [t0, \inf). + function _paymentInf(uint256 _x, uint256 _t0) internal view returns (uint256) { + return (_x * _pow(DCF_FACTOR, _t0)) >> 128; } - // Evaluate payment from timestamp [fromTs, toTs) - function _paymentIn(uint256 x, uint256 fromTs, uint256 toTs) internal view returns (uint256) { - return _paymentInInterval(x, fromTs - startTime, toTs - startTime); + /// @notice Evaluate payment from timestamp [fromTs, toTs) + function _paymentIn(uint256 _x, uint256 _fromTs, uint256 _toTs) internal view returns (uint256) { + return _paymentInInterval(_x, _fromTs - START_TIME, _toTs - START_TIME); } - function _upfrontPayment(uint256 ts) internal view returns (uint256) { - return _paymentInf(storageCost, ts - startTime); + /// @notice Evaluate payment given the timestamp. + function _upfrontPayment(uint256 _ts) internal view returns (uint256) { + return _paymentInf(STORAGE_COST, _ts - START_TIME); } - // Evaluate the storage cost of a single put(). + /// @notice Evaluate the storage cost of a single put(). function upfrontPayment() public view virtual returns (uint256) { return _upfrontPayment(block.timestamp); } + /// @notice Checks before appending the key-value. function _prepareAppend() internal virtual { - require(msg.value >= upfrontPayment(), "not enough payment"); + require(msg.value >= upfrontPayment(), "DecentralizedKV: not enough payment"); } - function _putInternal(bytes32 key, bytes32 dataHash, uint256 length) internal returns (uint256) { - require(length <= maxKvSize, "data too large"); - bytes32 skey = keccak256(abi.encode(msg.sender, key)); + /// @notice Called by public put method. + /// @param _key Key of the data. + /// @param _dataHash Hash of the data. + /// @param _length Length of the data. + /// @return The index of the key-value. + function _putInternal(bytes32 _key, bytes32 _dataHash, uint256 _length) internal returns (uint256) { + require(_length <= MAX_KV_SIZE, "DecentralizedKV: data too large"); + bytes32 skey = keccak256(abi.encode(msg.sender, _key)); PhyAddr memory paddr = kvMap[skey]; if (paddr.hash == 0) { @@ -99,57 +130,65 @@ contract DecentralizedKV is OwnableUpgradeable { idxMap[paddr.kvIdx] = skey; kvEntryCount = kvEntryCount + 1; } - paddr.kvSize = uint24(length); - paddr.hash = bytes24(dataHash); + paddr.kvSize = uint24(_length); + paddr.hash = bytes24(_dataHash); kvMap[skey] = paddr; return paddr.kvIdx; } - // Return the size of the keyed value - function size(bytes32 key) public view returns (uint256) { - bytes32 skey = keccak256(abi.encode(msg.sender, key)); + /// @notice Return the size of the keyed value. + function size(bytes32 _key) public view returns (uint256) { + bytes32 skey = keccak256(abi.encode(msg.sender, _key)); return kvMap[skey].kvSize; } - // Return the dataHash of the keyed value - function hash(bytes32 key) public view returns (bytes24) { - bytes32 skey = keccak256(abi.encode(msg.sender, key)); + /// @notice Return the dataHash of the keyed value. + function hash(bytes32 _key) public view returns (bytes24) { + bytes32 skey = keccak256(abi.encode(msg.sender, _key)); return kvMap[skey].hash; } - // Exist - function exist(bytes32 key) public view returns (bool) { - bytes32 skey = keccak256(abi.encode(msg.sender, key)); + /// @notice Check if the key-value exists. + function exist(bytes32 _key) public view returns (bool) { + bytes32 skey = keccak256(abi.encode(msg.sender, _key)); return kvMap[skey].hash != 0; } - // Return the keyed data given off and len. This function can be only called in JSON-RPC context of ES L2 node. + // @notice Return the keyed data given off and len. This function can be only called in JSON-RPC context of ES L2 node. + /// @param _key Key of the data. + /// @param _decodeType Type of decoding. + /// @param _off Offset of the data. + /// @param _len Length of the data. + /// @return The data. function get( - bytes32 key, - DecodeType decodeType, - uint256 off, - uint256 len + bytes32 _key, + DecodeType _decodeType, + uint256 _off, + uint256 _len ) public view virtual returns (bytes memory) { - require(len > 0, "data len should be non zero"); + require(_len > 0, "DecentralizedKV: data len should be non zero"); - bytes32 skey = keccak256(abi.encode(msg.sender, key)); + bytes32 skey = keccak256(abi.encode(msg.sender, _key)); PhyAddr memory paddr = kvMap[skey]; - require(paddr.hash != 0, "data not exist"); - if (decodeType == DecodeType.PaddingPer31Bytes) { + require(paddr.hash != 0, "DecentralizedKV: data not exist"); + if (_decodeType == DecodeType.PaddingPer31Bytes) { // kvSize is the actual data size that dApp contract stores - require((paddr.kvSize >= off + len) && (off + len <= maxKvSize - 4096), "beyond the range of kvSize"); + require( + (paddr.kvSize >= _off + _len) && (_off + _len <= MAX_KV_SIZE - 4096), + "DecentralizedKV: beyond the range of kvSize" + ); } else { // maxKvSize is blob size - require(maxKvSize >= off + len, "beyond the range of maxKvSize"); + require(MAX_KV_SIZE >= _off + _len, "DecentralizedKV: beyond the range of maxKvSize"); } - bytes memory input = abi.encode(paddr.kvIdx, decodeType, off, len, paddr.hash); - bytes memory output = new bytes(len); + bytes memory input = abi.encode(paddr.kvIdx, _decodeType, _off, _len, paddr.hash); + bytes memory output = new bytes(_len); uint256 retSize = 0; assembly { - if iszero(staticcall(not(0), 0x33301, add(input, 0x20), 0xa0, add(output, 0x20), len)) { + if iszero(staticcall(not(0), 0x33301, add(input, 0x20), 0xa0, add(output, 0x20), _len)) { revert(0, 0) } retSize := returndatasize() @@ -157,28 +196,34 @@ contract DecentralizedKV is OwnableUpgradeable { // If this function is called in a regular L1 node, there will no code in 0x33301, // and it will simply return immediately instead of revert - require(retSize > 0, "get() must be called on ES node"); + require(retSize > 0, "DecentralizedKV: get() must be called on ES node"); return output; } - // Remove an existing KV pair to a recipient. Refund the cost accordingly. - function removeTo(bytes32 key, address to) public virtual { - require(false, "removeTo() unimplemented"); + /// @notice Remove an existing KV pair to a recipient. Refund the cost accordingly. + /// @param _key Key of the data. + /// @param _to The recipient address. + function removeTo(bytes32 _key, address _to) public virtual { + revert("DecentralizedKV: removeTo() unimplemented"); } - // Remove an existing KV pair. Refund the cost accordingly. - function remove(bytes32 key) public { - removeTo(key, msg.sender); + /// @notice Remove an existing KV pair. Refund the cost accordingly. + /// @param _key Key of the data. + function remove(bytes32 _key) public { + removeTo(_key, msg.sender); } - function getKvMetas(uint256[] memory kvIndices) public view virtual returns (bytes32[] memory) { - bytes32[] memory res = new bytes32[](kvIndices.length); + /// @notice Get the metadata of the key-value. + /// @param _kvIndices The indices of the key-value. + /// @return The metadatas of the key-value. + function getKvMetas(uint256[] memory _kvIndices) public view virtual returns (bytes32[] memory) { + bytes32[] memory res = new bytes32[](_kvIndices.length); - for (uint256 i = 0; i < kvIndices.length; i++) { - PhyAddr memory paddr = kvMap[idxMap[kvIndices[i]]]; + for (uint256 i = 0; i < _kvIndices.length; i++) { + PhyAddr memory paddr = kvMap[idxMap[_kvIndices[i]]]; - res[i] |= bytes32(uint256(kvIndices[i])) << 216; + res[i] |= bytes32(uint256(_kvIndices[i])) << 216; res[i] |= bytes32(uint256(paddr.kvSize)) << 192; res[i] |= bytes32(paddr.hash) >> 64; } @@ -187,7 +232,27 @@ contract DecentralizedKV is OwnableUpgradeable { } /// @notice This is for compatibility with earlier versions and can be removed in the future. - function lastKvIndex() public view returns (uint40) { + function lastKvIdx() public view returns (uint40) { return kvEntryCount; } + + /// @notice Getter for STORAGE_COST + function storageCost() public view returns (uint256) { + return STORAGE_COST; + } + + /// @notice Getter for DCF_FACTOR + function dcfFactor() public view returns (uint256) { + return DCF_FACTOR; + } + + /// @notice Getter for START_TIME + function startTime() public view returns (uint256) { + return START_TIME; + } + + /// @notice Getter for MAX_KV_SIZE + function maxKvSize() public view returns (uint256) { + return MAX_KV_SIZE; + } } diff --git a/contracts/EthStorageContract.sol b/contracts/EthStorageContract.sol index d6cc8c9..576b3b1 100644 --- a/contracts/EthStorageContract.sol +++ b/contracts/EthStorageContract.sol @@ -2,27 +2,47 @@ pragma solidity ^0.8.0; import "./StorageContract.sol"; -import "./Decoder.sol"; -import "./BinaryRelated.sol"; +import "./zk-verify/Decoder.sol"; +import "./libraries//BinaryRelated.sol"; +/// @custom:proxied +/// @title EthStorageContract +/// @notice EthStorage Contract that using EIP-4844 BLOB contract EthStorageContract is StorageContract, Decoder { - uint256 constant modulusBls = 0x73eda753299d7d483339d80809a1d80553bda402fffe5bfeffffffff00000001; - uint256 constant ruBls = 0x564c0a11a0f704f4fc3e8acfe0f8245f0ad1347b378fbf96e206da11a5d36306; - uint256 constant ruBn254 = 0x931d596de2fd10f01ddd073fd5a90a976f169c76f039bb91c4775720042d43a; - uint256 constant modulusBn254 = 0x30644e72e131a029b85045b68181585d2833e84879b9709143e1f593f0000001; - uint256 constant fieldElementsPerBlob = 0x1000; + /// @notice The modulus for the BLS curve + uint256 internal constant MODULUS_BLS = 0x73eda753299d7d483339d80809a1d80553bda402fffe5bfeffffffff00000001; + + /// @notice The root of unity of BLS curve + uint256 internal constant RU_BLS = 0x564c0a11a0f704f4fc3e8acfe0f8245f0ad1347b378fbf96e206da11a5d36306; + + /// @notice The root of unity for the BN254 curve + uint256 constant RU_BN254 = 0x931d596de2fd10f01ddd073fd5a90a976f169c76f039bb91c4775720042d43a; + + /// @notice The modulus for the BN254 curve + uint256 constant MODULUS_BN254 = 0x30644e72e131a029b85045b68181585d2833e84879b9709143e1f593f0000001; + + /// @notice The field elements per BLOB + uint256 constant FIELD_ELEMENTS_PER_BLOB = 0x1000; // TODO: Reserve extra slots (to a total of 50?) in the storage layout for future upgrades + /// @notice Emitted when a BLOB is appended. + /// @param kvIdx The index of the KV pair + /// @param kvSize The size of the KV pair + /// @param dataHash The hash of the data event PutBlob(uint256 indexed kvIdx, uint256 indexed kvSize, bytes32 indexed dataHash); + /// @notice Constructs the EthStorageContract contract. constructor( Config memory _config, uint256 _startTime, uint256 _storageCost, uint256 _dcfFactor - ) StorageContract(_config, _startTime, _storageCost, _dcfFactor) {} + ) StorageContract(_config, _startTime, _storageCost, _dcfFactor) { + //initialize(0, 0, 0, address(0x0), msg.sender); + } + /// @notice Initialize the contract function initialize( uint256 _minimumDiff, uint256 _prepaidAmount, @@ -33,7 +53,12 @@ contract EthStorageContract is StorageContract, Decoder { __init_storage(_minimumDiff, _prepaidAmount, _nonceLimit, _treasury, _owner); } - function modExp(uint256 _b, uint256 _e, uint256 _m) internal view returns (uint256 result) { + /// @notice Performs modular exponentiation, which is a type of exponentiation performed over a modulus. + /// @param _b The base + /// @param _e The exponent + /// @param _m The modulus + /// @return result_ The result of the modular exponentiation + function _modExp(uint256 _b, uint256 _e, uint256 _m) internal view returns (uint256 result_) { assembly { // Free memory pointer let pointer := mload(0x40) @@ -53,143 +78,179 @@ contract EthStorageContract is StorageContract, Decoder { revert(0, 0) } - result := mload(0x0) + result_ := mload(0x0) // Clear memory or exclude the memory mstore(0x40, add(pointer, 0xc0)) } } - function pointEvaluation(bytes memory input) internal view returns (uint256 versionedHash, uint256 x, uint256 y) { + /// @notice Perform point evaluation + /// @param _input The input data + /// @return versionedHash_ The versioned hash + /// @return x_ The x coordinate + /// @return y_ The y coordinate + function _pointEvaluation( + bytes memory _input + ) internal view returns (uint256 versionedHash_, uint256 x_, uint256 y_) { assembly { - versionedHash := mload(add(input, 0x20)) - x := mload(add(input, 0x40)) - y := mload(add(input, 0x60)) + versionedHash_ := mload(add(_input, 0x20)) + x_ := mload(add(_input, 0x40)) + y_ := mload(add(_input, 0x60)) // Call the precompiled contract 0x0a = point evaluation, reuse scratch to get the results - if iszero(staticcall(not(0), 0x0a, add(input, 0x20), 0xc0, 0x0, 0x40)) { + if iszero(staticcall(not(0), 0x0a, add(_input, 0x20), 0xc0, 0x0, 0x40)) { revert(0, 0) } // Check the results - if iszero(eq(mload(0x0), fieldElementsPerBlob)) { + if iszero(eq(mload(0x0), FIELD_ELEMENTS_PER_BLOB)) { revert(0, 0) } - if iszero(eq(mload(0x20), modulusBls)) { + if iszero(eq(mload(0x20), MODULUS_BLS)) { revert(0, 0) } } } + /// @notice Verify the mask is correct + /// @param _proof The ZK proof + /// @param _encodingKey The encoding key + /// @param _sampleIdxInKv The sample index in the KV + /// @param _mask The mask of the sample + /// @return The result of the verification function decodeSample( - Proof memory proof, - uint256 encodingKey, - uint256 sampleIdxInKv, - uint256 mask + Proof memory _proof, + uint256 _encodingKey, + uint256 _sampleIdxInKv, + uint256 _mask ) public view returns (bool) { - uint256 xBn254 = modExp(ruBn254, sampleIdxInKv, modulusBn254); + uint256 xBn254 = _modExp(RU_BN254, _sampleIdxInKv, MODULUS_BN254); uint256[] memory input = new uint256[](3); // TODO: simple hash to curve mapping - input[0] = encodingKey % modulusBn254; + input[0] = _encodingKey % MODULUS_BN254; input[1] = xBn254; - input[2] = mask; - return (verifyDecoding(input, proof) == 0); + input[2] = _mask; + return (verifyDecoding(input, _proof) == 0); } + /// @notice Check the decoded data is included in the BLOB corresponding to on-chain datahashes + /// @param _dataHash The data hash + /// @param _sampleIdxInKv The sample index in the KV + /// @param _decodedData The decoded sample + /// @param _peInput The point evaluation input + /// @return The result of the check function checkInclusive( - bytes32 dataHash, - uint256 sampleIdxInKv, - uint256 decodedData, - bytes memory peInput + bytes32 _dataHash, + uint256 _sampleIdxInKv, + uint256 _decodedData, + bytes memory _peInput ) public view returns (bool) { - if (dataHash == 0x0) { - return decodedData == 0; + if (_dataHash == 0x0) { + return _decodedData == 0; } // peInput includes an input point that comes from bit reversed sampleIdxInKv - uint256 sampleIdxInKvRev = BinaryRelated.reverseBits(12, sampleIdxInKv); - uint256 xBls = modExp(ruBls, sampleIdxInKvRev, modulusBls); - (uint256 versionedHash, uint256 evalX, uint256 evalY) = pointEvaluation(peInput); - if (evalX != xBls || bytes24(bytes32(versionedHash)) != dataHash) { + uint256 sampleIdxInKvRev = BinaryRelated.reverseBits(12, _sampleIdxInKv); + uint256 xBls = _modExp(RU_BLS, sampleIdxInKvRev, MODULUS_BLS); + (uint256 versionedHash, uint256 evalX, uint256 evalY) = _pointEvaluation(_peInput); + if (evalX != xBls || bytes24(bytes32(versionedHash)) != _dataHash) { return false; } - return evalY == decodedData; + return evalY == _decodedData; } - /* - * Decode the sample and check the decoded sample is included in the BLOB corresponding to on-chain datahashes. - */ + /// @notice Decode the sample and check the decoded sample is included in the BLOB corresponding to on-chain datahashes + /// @param _kvIdx The index of the KV pair + /// @param _sampleIdxInKv The sample index in the KV + /// @param _miner The miner address + /// @param _encodedData The encoded sample data + /// @param _mask The mask of the sample + /// @param _inclusiveProof The inclusive proof + /// @param _decodeProof The decode proof + /// @return The result of the check function decodeAndCheckInclusive( - uint256 kvIdx, - uint256 sampleIdxInKv, - address miner, - bytes32 encodedData, - uint256 mask, - bytes calldata inclusiveProof, - bytes calldata decodeProof + uint256 _kvIdx, + uint256 _sampleIdxInKv, + address _miner, + bytes32 _encodedData, + uint256 _mask, + bytes calldata _inclusiveProof, + bytes calldata _decodeProof ) public view virtual returns (bool) { - PhyAddr memory kvInfo = kvMap[idxMap[kvIdx]]; - Proof memory proof = abi.decode(decodeProof, (Proof)); + PhyAddr memory kvInfo = kvMap[idxMap[_kvIdx]]; + Proof memory proof = abi.decode(_decodeProof, (Proof)); // BLOB decoding check - if (!decodeSample(proof, uint256(keccak256(abi.encode(kvInfo.hash, miner, kvIdx))), sampleIdxInKv, mask)) { + if (!decodeSample(proof, uint256(keccak256(abi.encode(kvInfo.hash, _miner, _kvIdx))), _sampleIdxInKv, _mask)) { return false; } // Inclusive proof of decodedData = mask ^ encodedData - return checkInclusive(kvInfo.hash, sampleIdxInKv, mask ^ uint256(encodedData), inclusiveProof); + return checkInclusive(kvInfo.hash, _sampleIdxInKv, _mask ^ uint256(_encodedData), _inclusiveProof); } - function getSampleIdx(uint256 rows, uint256 startShardId, bytes32 hash0) public view returns (uint256, uint256) { - uint256 parent = uint256(hash0) % rows; - uint256 sampleIdx = parent + (startShardId << (shardEntryBits + sampleLenBits)); - uint256 kvIdx = sampleIdx >> sampleLenBits; - uint256 sampleIdxInKv = sampleIdx % (1 << sampleLenBits); + /// @notice Get the sample index + /// @param _rows The sample count per shard + /// @param _startShardId The start shard ID + /// @param _hash0 The hash0 + /// @return The key index contains the sample + /// @return The sample index in the key + function getSampleIdx(uint256 _rows, uint256 _startShardId, bytes32 _hash0) public view returns (uint256, uint256) { + uint256 parent = uint256(_hash0) % _rows; + uint256 sampleIdx = parent + (_startShardId << (SHARD_ENTRY_BITS + SAMPLE_LEN_BITS)); + uint256 kvIdx = sampleIdx >> SAMPLE_LEN_BITS; + uint256 sampleIdxInKv = sampleIdx % (1 << SAMPLE_LEN_BITS); return (kvIdx, sampleIdxInKv); } + /// @inheritdoc StorageContract function verifySamples( - uint256 startShardId, - bytes32 hash0, - address miner, - bytes32[] memory encodedSamples, - uint256[] memory masks, - bytes[] calldata inclusiveProofs, - bytes[] calldata decodeProof + uint256 _startShardId, + bytes32 _hash0, + address _miner, + bytes32[] memory _encodedSamples, + uint256[] memory _masks, + bytes[] calldata _inclusiveProofs, + bytes[] calldata _decodeProof ) public view virtual override returns (bytes32) { - require(encodedSamples.length == randomChecks, "data length mismatch"); - require(masks.length == randomChecks, "masks length mismatch"); - require(inclusiveProofs.length == randomChecks, "proof length mismatch"); - require(decodeProof.length == randomChecks, "decodeProof length mismatch"); + require(_encodedSamples.length == RANDOM_CHECKS, "EthStorageContract: data length mismatch"); + require(_masks.length == RANDOM_CHECKS, "EthStorageContract: masks length mismatch"); + require(_inclusiveProofs.length == RANDOM_CHECKS, "EthStorageContract: proof length mismatch"); + require(_decodeProof.length == RANDOM_CHECKS, "EthStorageContract: decodeProof length mismatch"); // calculate the number of samples range of the sample check - uint256 rows = 1 << (shardEntryBits + sampleLenBits); + uint256 rows = 1 << (SHARD_ENTRY_BITS + SAMPLE_LEN_BITS); - for (uint256 i = 0; i < randomChecks; i++) { - (uint256 kvIdx, uint256 sampleIdxInKv) = getSampleIdx(rows, startShardId, hash0); + for (uint256 i = 0; i < RANDOM_CHECKS; i++) { + (uint256 kvIdx, uint256 sampleIdxInKv) = getSampleIdx(rows, _startShardId, _hash0); require( decodeAndCheckInclusive( kvIdx, sampleIdxInKv, - miner, - encodedSamples[i], - masks[i], - inclusiveProofs[i], - decodeProof[i] + _miner, + _encodedSamples[i], + _masks[i], + _inclusiveProofs[i], + _decodeProof[i] ), - "invalid samples" + "EthStorageContract: invalid samples" ); - hash0 = keccak256(abi.encode(hash0, encodedSamples[i])); + _hash0 = keccak256(abi.encode(_hash0, _encodedSamples[i])); } - return hash0; + return _hash0; } - // Write a large value to KV store. If the KV pair exists, overrides it. Otherwise, will append the KV to the KV array. - function putBlob(bytes32 key, uint256 blobIdx, uint256 length) public payable virtual { - bytes32 dataHash = blobhash(blobIdx); - require(dataHash != 0, "failed to get blob hash"); - uint256 kvIdx = _putInternal(key, dataHash, length); + /// @notice Write a large value to KV store. If the KV pair exists, overrides it. + /// Otherwise, will append the KV to the KV array. + /// @param _key The key of the KV pair + /// @param _blobIdx The index of the blob + /// @param _length The length of the blob + function putBlob(bytes32 _key, uint256 _blobIdx, uint256 _length) public payable virtual { + bytes32 dataHash = blobhash(_blobIdx); + require(dataHash != 0, "EthStorageContract: failed to get blob hash"); + uint256 kvIdx = _putInternal(_key, dataHash, _length); - emit PutBlob(kvIdx, length, dataHash); + emit PutBlob(kvIdx, _length, dataHash); } } diff --git a/contracts/EthStorageContract2.sol b/contracts/EthStorageContract2.sol index ed7be31..6ff4894 100644 --- a/contracts/EthStorageContract2.sol +++ b/contracts/EthStorageContract2.sol @@ -2,11 +2,13 @@ pragma solidity ^0.8.0; import "./EthStorageContract.sol"; -import "./Decoder2.sol"; - +import "./zk-verify/Decoder2.sol"; +/// @custom:proxied +/// @title EthStorageContract2 +/// @notice EthStorage Contract that verifies two sample decodings using only one zk proof contract EthStorageContract2 is EthStorageContract, Decoder2 { - + /// @notice Constructs the EthStorageContract2 contract. constructor( Config memory _config, uint256 _startTime, @@ -14,88 +16,122 @@ contract EthStorageContract2 is EthStorageContract, Decoder2 { uint256 _dcfFactor ) EthStorageContract(_config, _startTime, _storageCost, _dcfFactor) {} - function getEncodingKey(uint256 kvIdx, address miner) internal view returns (uint256) { - return uint256(keccak256(abi.encode(kvMap[idxMap[kvIdx]].hash, miner, kvIdx))) % modulusBn254; + /// @notice Compute the encoding key using the kvIdx and miner address + function _getEncodingKey(uint256 _kvIdx, address _miner) internal view returns (uint256) { + return uint256(keccak256(abi.encode(kvMap[idxMap[_kvIdx]].hash, _miner, _kvIdx))) % MODULUS_BN254; } - function getXIn(uint256 sampleIdx) internal view returns (uint256) { - return modExp(ruBn254, sampleIdx, modulusBn254); + /// @notice Compute the input X for inclusion proof using the sample index + function _getXIn(uint256 _sampleIdx) internal view returns (uint256) { + return _modExp(RU_BN254, _sampleIdx, MODULUS_BN254); } - function _decodeSample(bytes calldata decodeProof, uint[6] memory _pubSignals) internal view returns (bool) { - (uint[2] memory pA, uint[2][2] memory pB, uint[2] memory pC) = abi.decode(decodeProof, (uint[2], uint[2][2], uint[2])); - // verifyProof uses the opcode 'return', so if we call verifyProof directly, it will lead to a compiler warning about 'unreachable code' - // and causes the caller function return directly + /// @notice Verify the zk proof for two sample decoding + /// @param _decodeProof The zk proof for multiple sample decoding + /// @param _pubSignals The public signals for the zk proof + /// @return true if the proof is valid, false otherwise + function _decodeSample(bytes calldata _decodeProof, uint[6] memory _pubSignals) internal view returns (bool) { + (uint[2] memory pA, uint[2][2] memory pB, uint[2] memory pC) = abi.decode( + _decodeProof, + (uint[2], uint[2][2], uint[2]) + ); + // verifyProof uses the opcode 'return', so if we call verifyProof directly, it will lead to a compiler warning about 'unreachable code' + // and causes the caller function return directly return this.verifyProof(pA, pB, pC, _pubSignals); } + /// @notice Verify the masks using the zk proof + /// @param _masks The masks for the samples + /// @param _kvIdxs The kvIdxs that contain the samples + /// @param _sampleIdxs The sampleIdxs in the kvIdxs + /// @param _miner The miner address + /// @param _decodeProof The zk proof for two sample decoding + /// @return true if the proof is valid, false otherwise function decodeSample( - uint256[] memory masks, - uint256[] memory kvIdxs, - uint256[] memory sampleIdxs, - address miner, - bytes calldata decodeProof + uint256[] memory _masks, + uint256[] memory _kvIdxs, + uint256[] memory _sampleIdxs, + address _miner, + bytes calldata _decodeProof ) public view returns (bool) { - return _decodeSample(decodeProof, [ - getEncodingKey(kvIdxs[0], miner), - getEncodingKey(kvIdxs[1], miner), - getXIn(sampleIdxs[0]), - getXIn(sampleIdxs[1]), - masks[0], - masks[1] - ]); + return + _decodeSample( + _decodeProof, + [ + _getEncodingKey(_kvIdxs[0], _miner), + _getEncodingKey(_kvIdxs[1], _miner), + _getXIn(_sampleIdxs[0]), + _getXIn(_sampleIdxs[1]), + _masks[0], + _masks[1] + ] + ); } + /// @notice Check the sample is included in the kvIdx + /// @param _startShardId The start shard id + /// @param _rows The number of samples per shard + /// @param _hash0 The hash0 value + /// @param _encodedSample The encoded sample data + /// @param _mask The mask for the sample + /// @param _inclusiveProof The zk proof for the sample inclusion + /// @return The next hash0 value + /// @return The kvIdx that contains the sample + /// @return The sampleIdx in the kvIdx function _checkSample( - uint256 startShardId, - uint256 rows, - bytes32 hash0, - bytes32 encodedSample, - uint256 mask, - bytes calldata inclusiveProof + uint256 _startShardId, + uint256 _rows, + bytes32 _hash0, + bytes32 _encodedSample, + uint256 _mask, + bytes calldata _inclusiveProof ) internal view returns (bytes32, uint256, uint256) { - (uint256 kvIdx, uint256 sampleIdxInKv) = getSampleIdx(rows, startShardId, hash0); - + (uint256 kvIdx, uint256 sampleIdxInKv) = getSampleIdx(_rows, _startShardId, _hash0); + PhyAddr memory kvInfo = kvMap[idxMap[kvIdx]]; require( - checkInclusive(kvInfo.hash, sampleIdxInKv, mask ^ uint256(encodedSample), inclusiveProof), - "invalid samples" + checkInclusive(kvInfo.hash, sampleIdxInKv, _mask ^ uint256(_encodedSample), _inclusiveProof), + "EthStorageContract2: invalid samples" ); - hash0 = keccak256(abi.encode(hash0, encodedSample)); - return (hash0, kvIdx, sampleIdxInKv); + _hash0 = keccak256(abi.encode(_hash0, _encodedSample)); + return (_hash0, kvIdx, sampleIdxInKv); } + /// @inheritdoc EthStorageContract function verifySamples( - uint256 startShardId, - bytes32 hash0, - address miner, - bytes32[] memory encodedSamples, - uint256[] memory masks, - bytes[] calldata inclusiveProofs, - bytes[] calldata decodeProof + uint256 _startShardId, + bytes32 _hash0, + address _miner, + bytes32[] memory _encodedSamples, + uint256[] memory _masks, + bytes[] calldata _inclusiveProofs, + bytes[] calldata _decodeProof ) public view virtual override returns (bytes32) { - require(encodedSamples.length == randomChecks, "data length mismatch"); - require(masks.length == randomChecks, "masks length mismatch"); - require(inclusiveProofs.length == randomChecks, "proof length mismatch"); - require(decodeProof.length == 1, "decodeProof length mismatch"); + require(_encodedSamples.length == RANDOM_CHECKS, "EthStorageContract2: data length mismatch"); + require(_masks.length == RANDOM_CHECKS, "EthStorageContract2: masks length mismatch"); + require(_inclusiveProofs.length == RANDOM_CHECKS, "EthStorageContract2: proof length mismatch"); + require(_decodeProof.length == 1, "EthStorageContract2: decodeProof length mismatch"); // calculate the number of samples range of the sample check - uint256 rows = 1 << (shardEntryBits + sampleLenBits); + uint256 rows = 1 << (SHARD_ENTRY_BITS + SAMPLE_LEN_BITS); - uint[] memory kvIdxs = new uint[](randomChecks); - uint[] memory sampleIdxs = new uint[](randomChecks); - for (uint256 i = 0; i < randomChecks; i++) { - (hash0, kvIdxs[i], sampleIdxs[i]) = _checkSample( - startShardId, - rows, - hash0, - encodedSamples[i], - masks[i], - inclusiveProofs[i] + uint[] memory kvIdxs = new uint[](RANDOM_CHECKS); + uint[] memory sampleIdxs = new uint[](RANDOM_CHECKS); + for (uint256 i = 0; i < RANDOM_CHECKS; i++) { + (_hash0, kvIdxs[i], sampleIdxs[i]) = _checkSample( + _startShardId, + rows, + _hash0, + _encodedSamples[i], + _masks[i], + _inclusiveProofs[i] ); } - require(decodeSample(masks, kvIdxs, sampleIdxs, miner, decodeProof[0]), "decode failed"); - return hash0; + require( + decodeSample(_masks, kvIdxs, sampleIdxs, _miner, _decodeProof[0]), + "EthStorageContract2: decode failed" + ); + return _hash0; } } diff --git a/contracts/EthStorageContractL2.sol b/contracts/EthStorageContractL2.sol index 8a2538c..a3017b8 100644 --- a/contracts/EthStorageContractL2.sol +++ b/contracts/EthStorageContractL2.sol @@ -3,17 +3,31 @@ pragma solidity ^0.8.0; import "./EthStorageContract2.sol"; +/// @title IL1Block +/// @notice Interface for L1Block contract. interface IL1Block { + /// @notice Get the blockhash of an L1 history block number. + /// @param _historyNumber The L1 history block number. + /// @return The blockhash of the L1 history block number. function blockHash(uint256 _historyNumber) external view returns (bytes32); + /// @notice Get the current L1 block number. + /// @return The current L1 block number. function number() external view returns (uint64); + /// @notice Get the current L1 block timestamp. + /// @return The current L1 block timestamp. function timestamp() external view returns (uint64); } +/// @custom:proxied +/// @title EthStorageContractL2 +/// @notice EthStorage contract that will be deployed on L2, and uses L1Block contract to mine. contract EthStorageContractL2 is EthStorageContract2 { - IL1Block public constant l1Block = IL1Block(0x4200000000000000000000000000000000000015); + /// @notice The precompile contract address for L1Block. + IL1Block internal constant L1_BLOCK = IL1Block(0x4200000000000000000000000000000000000015); + /// @notice Constructs the EthStorageContractL2 contract. constructor( Config memory _config, uint256 _startTime, @@ -21,47 +35,55 @@ contract EthStorageContractL2 is EthStorageContract2 { uint256 _dcfFactor ) EthStorageContract2(_config, _startTime, _storageCost, _dcfFactor) {} - function getRandao(uint256 l1BlockNumber, bytes calldata headerRlpBytes) internal view returns (bytes32) { - bytes32 bh = l1Block.blockHash(l1BlockNumber); - require(bh != bytes32(0), "failed to obtain blockhash"); + /// @notice Get the randao value from the L1 blockhash. + function _getRandao(uint256 _l1BlockNumber, bytes calldata _headerRlpBytes) internal view returns (bytes32) { + bytes32 bh = L1_BLOCK.blockHash(_l1BlockNumber); + require(bh != bytes32(0), "EthStorageContractL2: failed to obtain blockhash"); - return RandaoLib.verifyHeaderAndGetRandao(bh, headerRlpBytes); + return RandaoLib.verifyHeaderAndGetRandao(bh, _headerRlpBytes); } - /// @dev We are still using L1 block number, timestamp, and blockhash to mine. - /// @param blockNumber L1 blocknumber. - /// @param randaoProof L1 block header RLP bytes. + /// @notice We are still using L1 block number, timestamp, and blockhash to mine eventhough we are on L2. + /// @param _blockNumber L1 blocknumber. + /// @param _shardId Shard ID. + /// @param _miner Miner address. + /// @param _nonce Nonce. + /// @param _encodedSamples Encoded samples. + /// @param _masks Sample masks. + /// @param _randaoProof L1 block header RLP bytes. + /// @param _inclusiveProofs Sample inclusive proofs. + /// @param _decodeProof Mask decode proof. function _mine( - uint256 blockNumber, - uint256 shardId, - address miner, - uint256 nonce, - bytes32[] memory encodedSamples, - uint256[] memory masks, - bytes calldata randaoProof, - bytes[] calldata inclusiveProofs, - bytes[] calldata decodeProof + uint256 _blockNumber, + uint256 _shardId, + address _miner, + uint256 _nonce, + bytes32[] memory _encodedSamples, + uint256[] memory _masks, + bytes calldata _randaoProof, + bytes[] calldata _inclusiveProofs, + bytes[] calldata _decodeProof ) internal override { // Obtain the blockhash of the block number of recent blocks - require(l1Block.number() - blockNumber <= maxL1MiningDrift, "block number too old"); + require(L1_BLOCK.number() - _blockNumber <= MAX_L1_MINING_DRIFT, "EthStorageContractL2: block number too old"); // To avoid stack too deep, we resue the hash0 instead of using randao - bytes32 hash0 = getRandao(blockNumber, randaoProof); + bytes32 hash0 = _getRandao(_blockNumber, _randaoProof); // Estimate block timestamp - uint256 mineTs = l1Block.timestamp() - (l1Block.number() - blockNumber) * 12; + uint256 mineTs = L1_BLOCK.timestamp() - (L1_BLOCK.number() - _blockNumber) * 12; // Given a blockhash and a miner, we only allow sampling up to nonce limit times. - require(nonce < nonceLimit, "nonce too big"); + require(_nonce < nonceLimit, "EthStorageContractL2: nonce too big"); // Check if the data matches the hash in metadata and obtain the solution hash. - hash0 = keccak256(abi.encode(miner, hash0, nonce)); - hash0 = verifySamples(shardId, hash0, miner, encodedSamples, masks, inclusiveProofs, decodeProof); + hash0 = keccak256(abi.encode(_miner, hash0, _nonce)); + hash0 = verifySamples(_shardId, hash0, _miner, _encodedSamples, _masks, _inclusiveProofs, _decodeProof); // Check difficulty - uint256 diff = _calculateDiffAndInitHashSingleShard(shardId, mineTs); + uint256 diff = _calculateDiffAndInitHashSingleShard(_shardId, mineTs); uint256 required = uint256(2 ** 256 - 1) / diff; - require(uint256(hash0) <= required, "diff not match"); + require(uint256(hash0) <= required, "EthStorageContractL2: diff not match"); - _rewardMiner(shardId, miner, mineTs, diff); + _rewardMiner(_shardId, _miner, mineTs, diff); } } diff --git a/contracts/MiningLib.sol b/contracts/MiningLib.sol deleted file mode 100644 index c9e13cd..0000000 --- a/contracts/MiningLib.sol +++ /dev/null @@ -1,44 +0,0 @@ -// SPDX-License-Identifier: MIT -pragma solidity ^0.8.0; - -library MiningLib { - struct MiningInfo { - uint256 lastMineTime; - uint256 difficulty; - uint256 blockMined; - } - - function expectedDiff( - MiningInfo storage info, - uint256 mineTime, - uint256 cutoff, - uint256 diffAdjDivisor, - uint256 minDiff - ) internal view returns (uint256) { - // Check if the diff matches - // Use modified ETH diff algorithm - uint256 interval = mineTime - info.lastMineTime; - uint256 diff = info.difficulty; - if (interval < cutoff) { - diff = diff + ((1 - interval / cutoff) * diff) / diffAdjDivisor; - if (diff < minDiff) { - diff = minDiff; - } - } else { - uint256 dec = ((interval / cutoff - 1) * diff) / diffAdjDivisor; - if (dec + minDiff > diff) { - diff = minDiff; - } else { - diff = diff - dec; - } - } - return diff; - } - - function update(MiningInfo storage info, uint256 mineTime, uint256 diff) internal { - // A block is mined! - info.blockMined = info.blockMined + 1; - info.difficulty = diff; - info.lastMineTime = mineTime; - } -} diff --git a/contracts/RandaoLib.sol b/contracts/RandaoLib.sol deleted file mode 100644 index a7823b2..0000000 --- a/contracts/RandaoLib.sol +++ /dev/null @@ -1,32 +0,0 @@ -// SPDX-License-Identifier: MIT -pragma solidity ^0.8.0; - -import "./RLPReader.sol"; - -library RandaoLib { - using RLPReader for RLPReader.RLPItem; - using RLPReader for RLPReader.Iterator; - using RLPReader for bytes; - - function getRandaoFromHeader(RLPReader.RLPItem memory item) pure internal returns (bytes32) { - RLPReader.Iterator memory iterator = item.iterator(); - // mixDigest is at item 13 (0-base index) - for (uint256 i = 0; i < 13; i++) { - iterator.next(); - } - - return bytes32(iterator.next().toUint()); - } - - function verifyHeaderAndGetRandao(bytes32 headerHash, bytes memory headerRlpBytes) pure internal returns (bytes32) { - RLPReader.RLPItem memory item = headerRlpBytes.toRlpItem(); - require(headerHash == item.rlpBytesKeccak256(), "header hash mismatch"); - return getRandaoFromHeader(item); - } - - function verifyHistoricalRandao(uint256 blockNumber, bytes memory headerRlpBytes) view internal returns (bytes32) { - bytes32 bh = blockhash(blockNumber); - require(bh != bytes32(0), "failed to obtain blockhash"); - return verifyHeaderAndGetRandao(bh, headerRlpBytes); - } -} \ No newline at end of file diff --git a/contracts/StorageContract.sol b/contracts/StorageContract.sol index 01898ed..dff4f47 100644 --- a/contracts/StorageContract.sol +++ b/contracts/StorageContract.sol @@ -2,53 +2,104 @@ pragma solidity ^0.8.0; import "./DecentralizedKV.sol"; -import "./MiningLib.sol"; -import "./RandaoLib.sol"; +import "./libraries/MiningLib.sol"; +import "./libraries/RandaoLib.sol"; -/* - * EthStorage L1 Contract with Decentralized KV Interface and Proof of Storage Verification. - */ +/// @custom:upgradeable +/// @title StorageContract +/// @notice EthStorage L1 Contract with Decentralized KV Interface and Proof of Storage Verification abstract contract StorageContract is DecentralizedKV { + /// @notice Represents the configuration of the storage contract. + /// @custom:field maxKvSizeBits Maximum size of a single key-value pair. + /// @custom:field shardSizeBits Storage shard size. + /// @custom:field randomChecks Number of random checks when mining + /// @custom:field cutoff Cutoff time for difficulty adjustment. + /// @custom:field diffAdjDivisor Difficulty adjustment divisor. + /// @custom:field treasuryShare Treasury share in basis points. 10000 = 1.0 struct Config { uint256 maxKvSizeBits; uint256 shardSizeBits; uint256 randomChecks; uint256 cutoff; uint256 diffAdjDivisor; - uint256 treasuryShare; // 10000 = 1.0 + uint256 treasuryShare; } - uint256 public constant sampleSizeBits = 5; // 32 bytes per sample - uint8 public constant maxL1MiningDrift = 64; // 64 blocks + /// @notice 32 bytes per sample + uint256 internal constant SAMPLE_SIZE_BITS = 5; - uint256 public immutable maxKvSizeBits; - uint256 public immutable shardSizeBits; - uint256 public immutable shardEntryBits; - uint256 public immutable sampleLenBits; - uint256 public immutable randomChecks; - uint256 public immutable cutoff; - uint256 public immutable diffAdjDivisor; - uint256 public immutable treasuryShare; // 10000 = 1.0 + /// @notice 64 blocks + uint8 internal constant MAX_L1_MINING_DRIFT = 64; + + /// @notice Maximum size of a single key-value pair + uint256 internal immutable MAX_KV_SIZE_BITS; + + /// @notice Storage shard size + uint256 internal immutable SHARD_SIZE_BITS; + + /// @notice Key-value count per shard + uint256 internal immutable SHARD_ENTRY_BITS; + + /// @notice Sample count per key-value pair + uint256 internal immutable SAMPLE_LEN_BITS; + + /// @notice Number of random checks when mining + uint256 internal immutable RANDOM_CHECKS; + + /// @notice Cutoff time for difficulty adjustment + uint256 internal immutable CUTOFF; + + /// @notice Difficulty adjustment divisor + uint256 internal immutable DIFF_ADJ_DIVISOR; + + /// @notice Treasury share in basis points. + uint256 internal immutable TREASURY_SHARE; /// @custom:spacer maxKvSizeBits, shardSizeBits, shardEntryBits, sampleLenBits, randomChecks /// @notice Spacer for backwards compatibility. - uint256[5] public storSpacers1; + uint256[5] private storSpacers1; + /// @notice Minimum difficulty uint256 public minimumDiff; /// @custom:spacer cutoff, diffAdjDivisor, treasuryShare /// @notice Spacer for backwards compatibility. - uint256[3] public storSpacers2; + uint256[3] private storSpacers2; + /// @notice Prepaid amount for the last shard uint256 public prepaidAmount; + /// @notice Mining infomation for each shard mapping(uint256 => MiningLib.MiningInfo) public infos; - uint256 public nonceLimit; // maximum nonce per block + + /// @notice Maximum nonce per block + uint256 public nonceLimit; + + /// @notice Treasury address address public treasury; + + /// @notice uint256 public prepaidLastMineTime; // TODO: Reserve extra slots (to a total of 50?) in the storage layout for future upgrades + /// @notice Emitted when a block is mined. + /// @param shardId The shard id of the mined block. + /// @param difficulty The difficulty of the mined block. + /// @param blockMined The block number of the mined block. + /// @param lastMineTime The last mine time of the shard. + /// @param miner The miner of the block. + /// @param minerReward The reward of the miner. + event MinedBlock( + uint256 indexed shardId, + uint256 indexed difficulty, + uint256 indexed blockMined, + uint256 lastMineTime, + address miner, + uint256 minerReward + ); + + /// @notice Constructs the StorageContract contract. Initializes the storage config. constructor( Config memory _config, uint256 _startTime, @@ -56,20 +107,26 @@ abstract contract StorageContract is DecentralizedKV { uint256 _dcfFactor ) DecentralizedKV(1 << _config.maxKvSizeBits, _startTime, _storageCost, _dcfFactor) { /* Assumptions */ - require(_config.shardSizeBits >= _config.maxKvSizeBits, "shardSize too small"); - require(_config.maxKvSizeBits >= sampleSizeBits, "maxKvSize too small"); - require(_config.randomChecks > 0, "At least one checkpoint needed"); - - maxKvSizeBits = _config.maxKvSizeBits; - shardSizeBits = _config.shardSizeBits; - shardEntryBits = _config.shardSizeBits - _config.maxKvSizeBits; - sampleLenBits = _config.maxKvSizeBits - sampleSizeBits; - randomChecks = _config.randomChecks; - cutoff = _config.cutoff; - diffAdjDivisor = _config.diffAdjDivisor; - treasuryShare = _config.treasuryShare; + require(_config.shardSizeBits >= _config.maxKvSizeBits, "StorageContract: shardSize too small"); + require(_config.maxKvSizeBits >= SAMPLE_SIZE_BITS, "StorageContract: maxKvSize too small"); + require(_config.randomChecks > 0, "StorageContract: at least one checkpoint needed"); + + MAX_KV_SIZE_BITS = _config.maxKvSizeBits; + SHARD_SIZE_BITS = _config.shardSizeBits; + SHARD_ENTRY_BITS = _config.shardSizeBits - _config.maxKvSizeBits; + SAMPLE_LEN_BITS = _config.maxKvSizeBits - SAMPLE_SIZE_BITS; + RANDOM_CHECKS = _config.randomChecks; + CUTOFF = _config.cutoff; + DIFF_ADJ_DIVISOR = _config.diffAdjDivisor; + TREASURY_SHARE = _config.treasuryShare; } + /// @notice Initializer. + /// @param _minimumDiff The minimum difficulty. + /// @param _prepaidAmount The prepaid amount for the last shard. + /// @param _nonceLimit The maximum nonce per block. + /// @param _treasury The treasury address. + /// @param _owner The contract owner. function __init_storage( uint256 _minimumDiff, uint256 _prepaidAmount, @@ -83,44 +140,37 @@ abstract contract StorageContract is DecentralizedKV { prepaidAmount = _prepaidAmount; nonceLimit = _nonceLimit; treasury = _treasury; - prepaidLastMineTime = startTime; + prepaidLastMineTime = START_TIME; // make sure shard0 is ready to mine and pay correctly - infos[0].lastMineTime = startTime; + infos[0].lastMineTime = START_TIME; } - event MinedBlock( - uint256 indexed shardId, - uint256 indexed difficulty, - uint256 indexed blockMined, - uint256 lastMineTime, - address miner, - uint256 minerReward - ); - + /// @notice People can sent ETH to the contract. function sendValue() public payable {} - function _prepareAppendWithTimestamp(uint256 timestamp) internal { + /// @notice Checks the payment using the last mine time. + function _prepareAppendWithTimestamp(uint256 _timestamp) internal { uint256 totalEntries = kvEntryCount + 1; // include the one to be put - uint256 shardId = kvEntryCount >> shardEntryBits; // shard id of the new KV - if ((totalEntries % (1 << shardEntryBits)) == 1) { + uint256 shardId = kvEntryCount >> SHARD_ENTRY_BITS; // shard id of the new KV + if ((totalEntries % (1 << SHARD_ENTRY_BITS)) == 1) { // Open a new shard if the KV is the first one of the shard // and mark the shard is ready to mine. // (TODO): Setup shard difficulty as current difficulty / factor? if (shardId != 0) { // shard0 is already opened in constructor - infos[shardId].lastMineTime = timestamp; + infos[shardId].lastMineTime = _timestamp; } } - require(msg.value >= _upfrontPayment(infos[shardId].lastMineTime), "not enough payment"); + require(msg.value >= _upfrontPayment(infos[shardId].lastMineTime), "StorageContract: not enough payment"); } - // Upfront payment for the next insertion + /// @notice Upfront payment for the next insertion function upfrontPayment() public view virtual override returns (uint256) { uint256 totalEntries = kvEntryCount + 1; // include the one to be put - uint256 shardId = kvEntryCount >> shardEntryBits; // shard id of the new KV + uint256 shardId = kvEntryCount >> SHARD_ENTRY_BITS; // shard id of the new KV // shard0 is already opened in constructor - if ((totalEntries % (1 << shardEntryBits)) == 1 && shardId != 0) { + if ((totalEntries % (1 << SHARD_ENTRY_BITS)) == 1 && shardId != 0) { // Open a new shard if the KV is the first one of the shard // and mark the shard is ready to mine. // (TODO): Setup shard difficulty as current difficulty / factor? @@ -130,145 +180,249 @@ abstract contract StorageContract is DecentralizedKV { } } + /// @inheritdoc DecentralizedKV function _prepareAppend() internal virtual override { return _prepareAppendWithTimestamp(block.timestamp); } - /* - * Verify the samples of the BLOBs by the miner (storage provider) including - * - decode the samples - * - check the inclusive of the samples - * - calculate the final hash using - */ + /// @notice Verify the samples of the BLOBs by the miner (storage provider) including + /// - decode the samples + /// - check the inclusive of the samples + /// - calculate the final hash using + /// @param _startShardId The shard id of the start shard. + /// @param _hash0 The hash0 of the mining block. + /// @param _miner The miner address. + /// @param _encodedSamples The encoded samples. + /// @param _masks The masks of the samples. + /// @param _inclusiveProofs The inclusive proofs of the samples. + /// @param _decodeProof The decode proof of the samples. + /// @return The mining result. function verifySamples( - uint256 startShardId, - bytes32 hash0, - address miner, - bytes32[] memory encodedSamples, - uint256[] memory masks, - bytes[] calldata inclusiveProofs, - bytes[] calldata decodeProof + uint256 _startShardId, + bytes32 _hash0, + address _miner, + bytes32[] memory _encodedSamples, + uint256[] memory _masks, + bytes[] calldata _inclusiveProofs, + bytes[] calldata _decodeProof ) public view virtual returns (bytes32); - // Obtain the difficulty of the shard + /// @notice Obtain the difficulty of the shard + /// @param _shardId The shard id. + /// @param _minedTs The mined timestamp. + /// @return diff_ The difficulty of the shard. function _calculateDiffAndInitHashSingleShard( - uint256 shardId, - uint256 minedTs - ) internal view returns (uint256 diff) { - MiningLib.MiningInfo storage info = infos[shardId]; - require(minedTs >= info.lastMineTime, "minedTs too small"); - diff = MiningLib.expectedDiff(info, minedTs, cutoff, diffAdjDivisor, minimumDiff); + uint256 _shardId, + uint256 _minedTs + ) internal view returns (uint256 diff_) { + MiningLib.MiningInfo storage info = infos[_shardId]; + require(_minedTs >= info.lastMineTime, "StorageContract: minedTs too small"); + diff_ = MiningLib.expectedDiff(info, _minedTs, CUTOFF, DIFF_ADJ_DIVISOR, minimumDiff); } - function _rewardMiner(uint256 shardId, address miner, uint256 minedTs, uint256 diff) internal { + /// @notice Reward the miner + /// @param _shardId The shard id. + /// @param _miner The miner address. + /// @param _minedTs The mined timestamp. + /// @param _diff The difficulty of the shard. + function _rewardMiner(uint256 _shardId, address _miner, uint256 _minedTs, uint256 _diff) internal { // Mining is successful. // Send reward to coinbase and miner. - (bool updatePrepaidTime, uint256 treasuryReward, uint256 minerReward) = _miningReward(shardId, minedTs); + (bool updatePrepaidTime, uint256 treasuryReward, uint256 minerReward) = _miningReward(_shardId, _minedTs); if (updatePrepaidTime) { - prepaidLastMineTime = minedTs; + prepaidLastMineTime = _minedTs; } // Update mining info. - MiningLib.update(infos[shardId], minedTs, diff); + MiningLib.update(infos[_shardId], _minedTs, _diff); - require(treasuryReward + minerReward <= address(this).balance, "not enough balance"); + require(treasuryReward + minerReward <= address(this).balance, "StorageContract: not enough balance"); // TODO: avoid reentrancy attack payable(treasury).transfer(treasuryReward); - payable(miner).transfer(minerReward); - emit MinedBlock(shardId, diff, infos[shardId].blockMined, minedTs, miner, minerReward); + payable(_miner).transfer(minerReward); + emit MinedBlock(_shardId, _diff, infos[_shardId].blockMined, _minedTs, _miner, minerReward); } - function _miningReward(uint256 shardId, uint256 minedTs) internal view returns (bool, uint256, uint256) { - MiningLib.MiningInfo storage info = infos[shardId]; - uint256 lastShardIdx = kvEntryCount > 0 ? (kvEntryCount - 1) >> shardEntryBits : 0; + /// @notice Calculate the mining reward + /// @param _shardId The shard id. + /// @param _minedTs The mined timestamp. + /// @return updatePrepaidTime Whether to update the prepaid time. + /// @return treasuryReward The treasury reward. + /// @return minerReward The miner reward. + function _miningReward(uint256 _shardId, uint256 _minedTs) internal view returns (bool, uint256, uint256) { + MiningLib.MiningInfo storage info = infos[_shardId]; + uint256 lastShardIdx = kvEntryCount > 0 ? (kvEntryCount - 1) >> SHARD_ENTRY_BITS : 0; uint256 reward = 0; bool updatePrepaidTime = false; - if (shardId < lastShardIdx) { - reward = _paymentIn(storageCost << shardEntryBits, info.lastMineTime, minedTs); - } else if (shardId == lastShardIdx) { - reward = _paymentIn(storageCost * (kvEntryCount % (1 << shardEntryBits)), info.lastMineTime, minedTs); + if (_shardId < lastShardIdx) { + reward = _paymentIn(STORAGE_COST << SHARD_ENTRY_BITS, info.lastMineTime, _minedTs); + } else if (_shardId == lastShardIdx) { + reward = _paymentIn(STORAGE_COST * (kvEntryCount % (1 << SHARD_ENTRY_BITS)), info.lastMineTime, _minedTs); // Additional prepaid for the last shard - if (prepaidLastMineTime < minedTs) { - reward += _paymentIn(prepaidAmount, prepaidLastMineTime, minedTs); + if (prepaidLastMineTime < _minedTs) { + reward += _paymentIn(prepaidAmount, prepaidLastMineTime, _minedTs); updatePrepaidTime = true; } } - uint256 treasuryReward = (reward * treasuryShare) / 10000; + uint256 treasuryReward = (reward * TREASURY_SHARE) / 10000; uint256 minerReward = reward - treasuryReward; return (updatePrepaidTime, treasuryReward, minerReward); } - function miningReward(uint256 shardId, uint256 blockNumber) public view returns (uint256) { - uint256 minedTs = block.timestamp - (block.number - blockNumber) * 12; - (, , uint256 minerReward) = _miningReward(shardId, minedTs); + /// @notice Get the mining reward. + /// @param _shardId The shard id. + /// @param _blockNumber The block number. + /// @return The mining reward. + function miningReward(uint256 _shardId, uint256 _blockNumber) public view returns (uint256) { + uint256 minedTs = block.timestamp - (block.number - _blockNumber) * 12; + (, , uint256 minerReward) = _miningReward(_shardId, minedTs); return minerReward; } + /// @notice Mine a block. + /// @param _blockNumber The block number. + /// @param _shardId The shard id. + /// @param _miner The miner address. + /// @param _nonce The nonce. + /// @param _encodedSamples The encoded samples. + /// @param _masks The masks of the samples. + /// @param _randaoProof The randao proof. + /// @param _inclusiveProofs The inclusive proofs. + /// @param _decodeProof The decode proof. function mine( - uint256 blockNumber, - uint256 shardId, - address miner, - uint256 nonce, - bytes32[] memory encodedSamples, - uint256[] memory masks, - bytes calldata randaoProof, - bytes[] calldata inclusiveProofs, - bytes[] calldata decodeProof + uint256 _blockNumber, + uint256 _shardId, + address _miner, + uint256 _nonce, + bytes32[] memory _encodedSamples, + uint256[] memory _masks, + bytes calldata _randaoProof, + bytes[] calldata _inclusiveProofs, + bytes[] calldata _decodeProof ) public virtual { - return - _mine(blockNumber, shardId, miner, nonce, encodedSamples, masks, randaoProof, inclusiveProofs, decodeProof); + _mine( + _blockNumber, + _shardId, + _miner, + _nonce, + _encodedSamples, + _masks, + _randaoProof, + _inclusiveProofs, + _decodeProof + ); } + /// @notice Set the nonce limit. function setNonceLimit(uint256 _nonceLimit) public onlyOwner { nonceLimit = _nonceLimit; } + /// @notice Set the prepaid amount. function setPrepaidAmount(uint256 _prepaidAmount) public onlyOwner { prepaidAmount = _prepaidAmount; } + /// @notice Set the treasury address. function setMinimumDiff(uint256 _minimumDiff) public onlyOwner { minimumDiff = _minimumDiff; } - /* - * On-chain verification of storage proof of sufficient sampling. - * On-chain verifier will go same routine as off-chain data host, will check the encoded samples by decoding - * to decoded one. The decoded samples will be used to perform inclusive check with on-chain datahashes. - * The encoded samples will be used to calculate the solution hash, and if the hash passes the difficulty check, - * the miner, or say the storage provider, shall be rewarded by the token number from out economic models - */ + /// @notice On-chain verification of storage proof of sufficient sampling. + /// On-chain verifier will go same routine as off-chain data host, will check the encoded samples by decoding + /// to decoded one. The decoded samples will be used to perform inclusive check with on-chain datahashes. + /// The encoded samples will be used to calculate the solution hash, and if the hash passes the difficulty check, + /// the miner, or say the storage provider, shall be rewarded by the token number from out economic models + /// @param _blockNumber The block number. + /// @param _shardId The shard id. + /// @param _miner The miner address. + /// @param _nonce The nonce. + /// @param _encodedSamples The encoded samples. + /// @param _masks The masks of the samples. + /// @param _randaoProof The randao proof. + /// @param _inclusiveProofs The inclusive proofs. + /// @param _decodeProof The decode proof. function _mine( - uint256 blockNumber, - uint256 shardId, - address miner, - uint256 nonce, - bytes32[] memory encodedSamples, - uint256[] memory masks, - bytes calldata randaoProof, - bytes[] calldata inclusiveProofs, - bytes[] calldata decodeProof + uint256 _blockNumber, + uint256 _shardId, + address _miner, + uint256 _nonce, + bytes32[] memory _encodedSamples, + uint256[] memory _masks, + bytes calldata _randaoProof, + bytes[] calldata _inclusiveProofs, + bytes[] calldata _decodeProof ) internal virtual { // Obtain the blockhash of the block number of recent blocks - require(block.number - blockNumber <= maxL1MiningDrift, "block number too old"); + require(block.number - _blockNumber <= MAX_L1_MINING_DRIFT, "StorageContract: block number too old"); // To avoid stack too deep, we resue the hash0 instead of using randao - bytes32 hash0 = RandaoLib.verifyHistoricalRandao(blockNumber, randaoProof); + bytes32 hash0 = RandaoLib.verifyHistoricalRandao(_blockNumber, _randaoProof); // Estimate block timestamp - uint256 mineTs = block.timestamp - (block.number - blockNumber) * 12; + uint256 mineTs = block.timestamp - (block.number - _blockNumber) * 12; // Given a blockhash and a miner, we only allow sampling up to nonce limit times. - require(nonce < nonceLimit, "nonce too big"); + require(_nonce < nonceLimit, "StorageContract: nonce too big"); // Check if the data matches the hash in metadata and obtain the solution hash. - hash0 = keccak256(abi.encode(miner, hash0, nonce)); - hash0 = verifySamples(shardId, hash0, miner, encodedSamples, masks, inclusiveProofs, decodeProof); + hash0 = keccak256(abi.encode(_miner, hash0, _nonce)); + hash0 = verifySamples(_shardId, hash0, _miner, _encodedSamples, _masks, _inclusiveProofs, _decodeProof); // Check difficulty - uint256 diff = _calculateDiffAndInitHashSingleShard(shardId, mineTs); + uint256 diff = _calculateDiffAndInitHashSingleShard(_shardId, mineTs); uint256 required = uint256(2 ** 256 - 1) / diff; - require(uint256(hash0) <= required, "diff not match"); + require(uint256(hash0) <= required, "StorageContract: diff not match"); + + _rewardMiner(_shardId, _miner, mineTs, diff); + } + + /// @notice Return the sample size bits. + function sampleSizeBits() public pure returns (uint256) { + return SAMPLE_SIZE_BITS; + } + + /// @notice Return the max L1 mining drift. + function maxL1MiningDrift() public pure returns (uint8) { + return MAX_L1_MINING_DRIFT; + } + + /// @notice Return the max kv size bits. + function maxKvSizeBits() public view returns (uint256) { + return MAX_KV_SIZE_BITS; + } + + /// @notice Return the shard size bits. + function shardSizeBits() public view returns (uint256) { + return SHARD_SIZE_BITS; + } + + /// @notice Return the shard entry bits. + function shardEntryBits() public view returns (uint256) { + return SHARD_ENTRY_BITS; + } + + /// @notice Return the sample len bits. + function sampleLenBits() public view returns (uint256) { + return SAMPLE_LEN_BITS; + } + + /// @notice Return the random checks. + function randomChecks() public view returns (uint256) { + return RANDOM_CHECKS; + } + + /// @notice Return the cutoff. + function cutoff() public view returns (uint256) { + return CUTOFF; + } + + /// @notice Return the diff adj divisor. + function diffAdjDivisor() public view returns (uint256) { + return DIFF_ADJ_DIVISOR; + } - _rewardMiner(shardId, miner, mineTs, diff); + /// @notice Return the treasury share. + function treasuryShare() public view returns (uint256) { + return TREASURY_SHARE; } } diff --git a/contracts/BinaryRelated.sol b/contracts/libraries/BinaryRelated.sol similarity index 100% rename from contracts/BinaryRelated.sol rename to contracts/libraries/BinaryRelated.sol diff --git a/contracts/MerkleLib.sol b/contracts/libraries/MerkleLib.sol similarity index 100% rename from contracts/MerkleLib.sol rename to contracts/libraries/MerkleLib.sol diff --git a/contracts/libraries/MiningLib.sol b/contracts/libraries/MiningLib.sol new file mode 100644 index 0000000..03795d5 --- /dev/null +++ b/contracts/libraries/MiningLib.sol @@ -0,0 +1,61 @@ +// SPDX-License-Identifier: MIT +pragma solidity ^0.8.0; + +/// @title MiningLib +/// @notice Handles mining difficulty calculation and mining info update +library MiningLib { + /// @notice MiningInfo represents the mining information of a shard + /// @custom:field lastMineTime The last time a block was mined + /// @custom:field difficulty The current difficulty of the shard + /// @custom:field blockMined The number of blocks mined + struct MiningInfo { + uint256 lastMineTime; + uint256 difficulty; + uint256 blockMined; + } + + /// @notice Calculate the expected difficulty of the next block + /// @param _info The mining information of the shard + /// @param _mineTime The mined time of the next block + /// @param _cutoff Cutoff time for difficulty adjustment + /// @param _diffAdjDivisor The divisor to adjust the difficulty + /// @param _minDiff The minimum difficulty + /// @return The expected difficulty of the next block + function expectedDiff( + MiningInfo storage _info, + uint256 _mineTime, + uint256 _cutoff, + uint256 _diffAdjDivisor, + uint256 _minDiff + ) internal view returns (uint256) { + // Check if the diff matches + // Use modified ETH diff algorithm + uint256 interval = _mineTime - _info.lastMineTime; + uint256 diff = _info.difficulty; + if (interval < _cutoff) { + diff = diff + ((1 - interval / _cutoff) * diff) / _diffAdjDivisor; + if (diff < _minDiff) { + diff = _minDiff; + } + } else { + uint256 dec = ((interval / _cutoff - 1) * diff) / _diffAdjDivisor; + if (dec + _minDiff > diff) { + diff = _minDiff; + } else { + diff = diff - dec; + } + } + return diff; + } + + /// @notice Update the mining information of the shard + /// @param _info The mining information of the shard + /// @param _mineTime The mined time of the next block + /// @param _diff The difficulty of the next block + function update(MiningInfo storage _info, uint256 _mineTime, uint256 _diff) internal { + // A block is mined! + _info.blockMined = _info.blockMined + 1; + _info.difficulty = _diff; + _info.lastMineTime = _mineTime; + } +} diff --git a/contracts/RLPReader.sol b/contracts/libraries/RLPReader.sol similarity index 100% rename from contracts/RLPReader.sol rename to contracts/libraries/RLPReader.sol diff --git a/contracts/libraries/RandaoLib.sol b/contracts/libraries/RandaoLib.sol new file mode 100644 index 0000000..fd5438e --- /dev/null +++ b/contracts/libraries/RandaoLib.sol @@ -0,0 +1,51 @@ +// SPDX-License-Identifier: MIT +pragma solidity ^0.8.0; + +import "./RLPReader.sol"; + +/// @title RandaoLib +/// @notice Handles Randao related operations +library RandaoLib { + using RLPReader for RLPReader.RLPItem; + using RLPReader for RLPReader.Iterator; + using RLPReader for bytes; + + /// @notice Get the Randao mixDigest from the header + /// @param _item The RLP data of the header + /// @return The Randao mixDigest + function getRandaoFromHeader(RLPReader.RLPItem memory _item) internal pure returns (bytes32) { + RLPReader.Iterator memory iterator = _item.iterator(); + // mixDigest is at item 13 (0-base index) + for (uint256 i = 0; i < 13; i++) { + iterator.next(); + } + + return bytes32(iterator.next().toUint()); + } + + /// @notice Verify the header hash and get the Randao mixDigest + /// @param _headerHash The hash of the header + /// @param _headerRlpBytes The RLP data of the header + /// @return The Randao mixDigest + function verifyHeaderAndGetRandao( + bytes32 _headerHash, + bytes memory _headerRlpBytes + ) internal pure returns (bytes32) { + RLPReader.RLPItem memory item = _headerRlpBytes.toRlpItem(); + require(_headerHash == item.rlpBytesKeccak256(), "RandaoLib: header hash mismatch"); + return getRandaoFromHeader(item); + } + + /// @notice Get the historical Randao mixDigest by block number + /// @param _blockNumber The block number + /// @param _headerRlpBytes The RLP data of the header + /// @return The Randao mixDigest + function verifyHistoricalRandao( + uint256 _blockNumber, + bytes memory _headerRlpBytes + ) internal view returns (bytes32) { + bytes32 bh = blockhash(_blockNumber); + require(bh != bytes32(0), "RandaoLib: failed to obtain blockhash"); + return verifyHeaderAndGetRandao(bh, _headerRlpBytes); + } +} diff --git a/contracts/TestDecentralizedKV.sol b/contracts/test/TestDecentralizedKV.sol similarity index 98% rename from contracts/TestDecentralizedKV.sol rename to contracts/test/TestDecentralizedKV.sol index e5f93a7..08a415b 100644 --- a/contracts/TestDecentralizedKV.sol +++ b/contracts/test/TestDecentralizedKV.sol @@ -1,7 +1,7 @@ // SPDX-License-Identifier: MIT pragma solidity ^0.8.0; -import "./DecentralizedKV.sol"; +import "../DecentralizedKV.sol"; contract TestDecentralizedKV is DecentralizedKV { uint256 public currentTimestamp; diff --git a/contracts/TestEthStorageContract.sol b/contracts/test/TestEthStorageContract.sol similarity index 85% rename from contracts/TestEthStorageContract.sol rename to contracts/test/TestEthStorageContract.sol index 9483c77..0a1bdf9 100644 --- a/contracts/TestEthStorageContract.sol +++ b/contracts/test/TestEthStorageContract.sol @@ -1,8 +1,8 @@ // SPDX-License-Identifier: MIT pragma solidity ^0.8.0; -import "./EthStorageContract.sol"; -import "./MerkleLib.sol"; +import "../EthStorageContract.sol"; +import "../libraries/MerkleLib.sol"; contract TestEthStorageContract is EthStorageContract { uint256 public currentTimestamp; @@ -50,9 +50,7 @@ contract TestEthStorageContract is EthStorageContract { Proof memory proof = abi.decode(decodeProof, (Proof)); // BLOB decoding check - if ( - !decodeSample(proof, uint256(keccak256(abi.encode(kvInfo.hash, miner, kvIdx))), sampleIdxInKv, mask) - ) { + if (!decodeSample(proof, uint256(keccak256(abi.encode(kvInfo.hash, miner, kvIdx))), sampleIdxInKv, mask)) { return false; } @@ -68,12 +66,12 @@ contract TestEthStorageContract is EthStorageContract { function getSampleIdx(uint256 startShardId, bytes32 hash0) public view returns (uint256, uint256, uint256) { // calculate the number of samples range of the sample check - uint256 rows = 1 << (shardEntryBits + sampleLenBits); // kvNumbersPerShard * smapleNumersPerKV + uint256 rows = 1 << (SHARD_ENTRY_BITS + SAMPLE_LEN_BITS); // kvNumbersPerShard * smapleNumersPerKV uint256 parent = uint256(hash0) % rows; - uint256 sampleIdx = parent + (startShardId << (shardEntryBits + sampleLenBits)); - uint256 kvIdx = sampleIdx >> sampleLenBits; - uint256 sampleIdxInKv = sampleIdx % (1 << sampleLenBits); + uint256 sampleIdx = parent + (startShardId << (SHARD_ENTRY_BITS + SAMPLE_LEN_BITS)); + uint256 kvIdx = sampleIdx >> SAMPLE_LEN_BITS; + uint256 sampleIdxInKv = sampleIdx % (1 << SAMPLE_LEN_BITS); return (sampleIdx, kvIdx, sampleIdxInKv); } @@ -134,7 +132,18 @@ contract TestEthStorageContract is EthStorageContract { bytes[] calldata inclusiveProofs, bytes[] calldata decodeProof ) public virtual override { - return _mineWithoutDiffCompare(blockNumber, shardId, miner, nonce, encodedSamples, masks, randaoProof, inclusiveProofs, decodeProof); + return + _mineWithoutDiffCompare( + blockNumber, + shardId, + miner, + nonce, + encodedSamples, + masks, + randaoProof, + inclusiveProofs, + decodeProof + ); } function _mineWithFixedHash0( @@ -171,6 +180,7 @@ contract TestEthStorageContract is EthStorageContract { bytes[] calldata inclusiveProofs, bytes[] calldata decodeProof ) public virtual { - return _mineWithFixedHash0(initHash0, shardId, miner, nonce, encodedSamples, masks, inclusiveProofs, decodeProof); + return + _mineWithFixedHash0(initHash0, shardId, miner, nonce, encodedSamples, masks, inclusiveProofs, decodeProof); } } diff --git a/contracts/TestEthStorageContractKZG.sol b/contracts/test/TestEthStorageContractKZG.sol similarity index 84% rename from contracts/TestEthStorageContractKZG.sol rename to contracts/test/TestEthStorageContractKZG.sol index aafd6fd..eb8d048 100644 --- a/contracts/TestEthStorageContractKZG.sol +++ b/contracts/test/TestEthStorageContractKZG.sol @@ -1,10 +1,9 @@ // SPDX-License-Identifier: MIT pragma solidity ^0.8.0; -import "./EthStorageContract2.sol"; +import "../EthStorageContract2.sol"; contract TestEthStorageContractKZG is EthStorageContract2 { - constructor( Config memory _config, uint256 _startTime, @@ -15,14 +14,14 @@ contract TestEthStorageContractKZG is EthStorageContract2 { // a test only method to upload multiple blobs in one tx function putBlobs(bytes32[] memory keys) public payable { for (uint256 i = 0; i < keys.length; i++) { - putBlob(keys[i], i, maxKvSize); + putBlob(keys[i], i, MAX_KV_SIZE); } } function putBlobs(uint256 num) public payable { for (uint256 i = 0; i < num; i++) { bytes32 key = keccak256(abi.encode(block.number, i)); - putBlob(key, 0, maxKvSize); + putBlob(key, 0, MAX_KV_SIZE); } } } diff --git a/contracts/TestMerkleLib.sol b/contracts/test/TestMerkleLib.sol similarity index 97% rename from contracts/TestMerkleLib.sol rename to contracts/test/TestMerkleLib.sol index b967d2f..e2b5fec 100644 --- a/contracts/TestMerkleLib.sol +++ b/contracts/test/TestMerkleLib.sol @@ -1,7 +1,7 @@ // SPDX-License-Identifier: MIT pragma solidity ^0.8.0; -import "./MerkleLib.sol"; +import "../libraries/MerkleLib.sol"; contract TestMerkleLib { function merkleRoot(bytes memory data, uint256 chunkSize, uint256 nChunkBits) public pure returns (bytes32) { diff --git a/contracts/TestRandao.sol b/contracts/test/TestRandao.sol similarity index 92% rename from contracts/TestRandao.sol rename to contracts/test/TestRandao.sol index da195a8..8e4aeef 100644 --- a/contracts/TestRandao.sol +++ b/contracts/test/TestRandao.sol @@ -1,7 +1,7 @@ // SPDX-License-Identifier: MIT pragma solidity ^0.8.0; -import "./RandaoLib.sol"; +import "../libraries/RandaoLib.sol"; contract TestRandao { function verifyHeaderAndGetRandao(bytes32 headerHash, bytes memory headerRlpBytes) public pure returns (bytes32) { @@ -11,4 +11,4 @@ contract TestRandao { function verifyHistoricalRandao(uint256 blockNumber, bytes memory proof) public view returns (bytes32) { return RandaoLib.verifyHistoricalRandao(blockNumber, proof); } -} \ No newline at end of file +} diff --git a/contracts/Decoder.sol b/contracts/zk-verify/Decoder.sol similarity index 100% rename from contracts/Decoder.sol rename to contracts/zk-verify/Decoder.sol diff --git a/contracts/Decoder2.sol b/contracts/zk-verify/Decoder2.sol similarity index 100% rename from contracts/Decoder2.sol rename to contracts/zk-verify/Decoder2.sol diff --git a/scripts/deployL2.js b/scripts/deployL2.js index c21f5da..0f368c2 100644 --- a/scripts/deployL2.js +++ b/scripts/deployL2.js @@ -51,7 +51,7 @@ async function deployContract() { console.log("storage impl address is ", impl); const data = implContract.interface.encodeFunctionData("initialize", [ - 4718592000, // minimumDiff 5 * 3 * 3600 * 1024 * 1024 / 12 = 4718592000 for 5 replicas that can have 1M IOs in one epoch + 94371840, // minimumDiff 0.1 * 3 * 3600 * 1024 * 1024 / 12 = 94371840 for 0.1 replicas that can have 1M IOs in one epoch 3145728000000000000000n, // prepaidAmount - 50% * 2^39 / 131072 * 1500000Gwei, it also means 3145 ETH for half of the shard 1048576, // nonceLimit 1024 * 1024 = 1M samples and finish sampling in 1.3s with IO rate 6144 MB/s: 4k * 2(random checks) / 6144 = 1.3s treasuryAddress, // treasury @@ -85,7 +85,7 @@ async function deployContract() { } async function updateContract() { - const StorageContract = await hre.ethers.getContractFactory("TestEthStorageContractKZG"); + const StorageContract = await hre.ethers.getContractFactory("EthStorageContractL2"); // get start time const ethStorage = StorageContract.attach(storageContractProxy); diff --git a/test/decentralized-kv-test.js b/test/decentralized-kv-test.js index c05e41b..2a789ee 100644 --- a/test/decentralized-kv-test.js +++ b/test/decentralized-kv-test.js @@ -55,12 +55,12 @@ describe("DecentralizedKV Test", function () { await kv.initialize(ownerAddr); expect(await kv.upfrontPayment()).to.equal("1000000000000000000"); - await expect(kv.put(key1, "0x11223344")).to.be.revertedWith("not enough payment"); + await expect(kv.put(key1, "0x11223344")).to.be.revertedWith("DecentralizedKV: not enough payment"); await expect( kv.put(key1, "0x11223344", { value: "900000000000000000", }) - ).to.be.revertedWith("not enough payment"); + ).to.be.revertedWith("DecentralizedKV: not enough payment"); await kv.put(key1, "0x11223344", { value: ethers.utils.parseEther("1.0"), }); @@ -91,12 +91,12 @@ describe("DecentralizedKV Test", function () { await kv.initialize(ownerAddr); expect(await kv.upfrontPayment()).to.equal("1000000000000000000"); - await expect(kv.put(key1, "0x11223344")).to.be.revertedWith("not enough payment"); + await expect(kv.put(key1, "0x11223344")).to.be.revertedWith("DecentralizedKV: not enough payment"); await expect( kv.put(key1, "0x11223344", { value: "900000000000000000", }) - ).to.be.revertedWith("not enough payment"); + ).to.be.revertedWith("DecentralizedKV: not enough payment"); await kv.put(key1, "0x11223344", { value: ethers.utils.parseEther("1.0"), });