Skip to content

Commit 0274f4f

Browse files
committed
Add first version of the SPVContract
1 parent e4f8a9e commit 0274f4f

File tree

10 files changed

+880
-811
lines changed

10 files changed

+880
-811
lines changed

README.md

Lines changed: 1 addition & 64 deletions
Original file line numberDiff line numberDiff line change
@@ -1,64 +1 @@
1-
# Hardhat template
2-
3-
Template hardhat repository for ad-hoc smart contracts development.
4-
5-
### How to use
6-
7-
The template works out of the box. To clean up the repo, you may need to delete the mock contracts, tests and migration files.
8-
9-
#### Compilation
10-
11-
To compile the contracts, use the next script:
12-
13-
```bash
14-
npm run compile
15-
```
16-
17-
#### Test
18-
19-
To run the tests, execute the following command:
20-
21-
```bash
22-
npm run test
23-
```
24-
25-
Or to see the coverage, run:
26-
27-
```bash
28-
npm run coverage
29-
```
30-
31-
#### Local deployment
32-
33-
To deploy the contracts locally, run the following commands (in the different terminals):
34-
35-
```bash
36-
npm run private-network
37-
npm run deploy-localhost
38-
```
39-
40-
#### Bindings
41-
42-
The command to generate the bindings is as follows:
43-
44-
```bash
45-
npm run generate-types
46-
```
47-
48-
> See the full list of available commands in the `package.json` file.
49-
50-
### Integrated plugins
51-
52-
- Hardhat official `ethers` + `ethers-v6`
53-
- [`Typechain`](https://www.npmjs.com/package/@typechain/hardhat)
54-
- [`hardhat-migrate`](https://www.npmjs.com/package/@solarity/hardhat-migrate), [`hardhat-markup`](https://www.npmjs.com/package/@solarity/hardhat-markup), [`hardhat-gobind`](https://www.npmjs.com/package/@solarity/hardhat-gobind)
55-
- [`hardhat-zkit`](https://www.npmjs.com/package/@solarity/hardhat-zkit), [`chai-zkit`](https://www.npmjs.com/package/@solarity/chai-zkit)
56-
- [`hardhat-contract-sizer`](https://www.npmjs.com/package/hardhat-contract-sizer)
57-
- [`hardhat-gas-reporter`](https://www.npmjs.com/package/hardhat-gas-reporter)
58-
- [`solidity-coverage`](https://www.npmjs.com/package/solidity-coverage)
59-
60-
### Other niceties
61-
62-
- The template comes with presetup `prettier` and `solhint` that lint the project via `husky` before compilation hook.
63-
- The `.env.example` file is provided to check what is required as ENVs
64-
- Preinstalled `@openzeppelin/contracts` and `@solarity/solidity-lib`
1+
# SPV Contracts

contracts/SPVContract.sol

Lines changed: 163 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,163 @@
1+
// SPDX-License-Identifier: MIT
2+
pragma solidity ^0.8.28;
3+
4+
import {Initializable} from "@openzeppelin/contracts-upgradeable/proxy/utils/Initializable.sol";
5+
6+
import {BlockHeader, BlockHeaderData} from "./libs/BlockHeader.sol";
7+
import {BlocksStorage} from "./libs/BlocksStorage.sol";
8+
import {TargetsStorage} from "./libs/targets/TargetsStorage.sol";
9+
import {TargetsHelper} from "./libs/targets/TargetsHelper.sol";
10+
11+
// import "hardhat/console.sol";
12+
13+
contract SPVContract is Initializable {
14+
using BlockHeader for bytes;
15+
using BlocksStorage for BlocksStorage.BlocksData;
16+
using TargetsStorage for TargetsStorage.TargetsData;
17+
18+
bytes32 public constant SPV_CONTRACT_STORAGE_SLOT =
19+
keccak256("spv.contract.spv.contract.storage");
20+
21+
error PrevBlockDoesNotExist(bytes32 prevBlockHash);
22+
error BlockAlreadyExists(bytes32 blockHash);
23+
24+
error InvalidTarget(bytes32 blockTarget, bytes32 networkTarget);
25+
error InvalidBlockHash(bytes32 actualBlockHash, bytes32 blockTarget);
26+
error InvalidBlockTime(uint32 blockTime, uint32 medianTime);
27+
28+
event BlockHeaderAdded(uint256 indexed blockHeight, bytes32 indexed blockHash);
29+
30+
struct SPVContractStorage {
31+
BlocksStorage.BlocksData blocksData;
32+
TargetsStorage.TargetsData targets;
33+
uint256 pendingTargetHeightCount;
34+
}
35+
36+
function __SPVContract_init(
37+
uint256 pendingBlockCount_,
38+
uint256 pendingTargetHeightCount_
39+
) external initializer {
40+
SPVContractStorage storage $ = _getSPVContractStorage();
41+
42+
$.blocksData.initialize(pendingBlockCount_);
43+
$.targets.initialize();
44+
45+
$.pendingTargetHeightCount = pendingTargetHeightCount_;
46+
}
47+
48+
function _getSPVContractStorage() internal pure returns (SPVContractStorage storage _spvs) {
49+
bytes32 slot_ = SPV_CONTRACT_STORAGE_SLOT;
50+
51+
assembly {
52+
_spvs.slot := slot_
53+
}
54+
}
55+
56+
function addBlockHeader(bytes calldata blockHeaderRaw_) external {
57+
SPVContractStorage storage $ = _getSPVContractStorage();
58+
59+
(BlockHeaderData memory blockHeader_, bytes32 blockHash_) = blockHeaderRaw_
60+
.parseBlockHeaderData();
61+
62+
// console.logBytes32(blockHash_);
63+
64+
require(!$.blocksData.blockExists(blockHash_), BlockAlreadyExists(blockHash_));
65+
require(
66+
$.blocksData.blockExists(blockHeader_.prevBlockHash),
67+
PrevBlockDoesNotExist(blockHeader_.prevBlockHash)
68+
);
69+
70+
uint256 blockHeight_ = $.blocksData.getBlockHeight(blockHeader_.prevBlockHash) + 1;
71+
72+
_tryConfirmPendingTarget(blockHeight_);
73+
74+
bytes32 target_ = _getRequiredTarget(blockHeight_, blockHeader_.prevBlockHash);
75+
76+
_validateBlockRules(blockHeader_, blockHash_, target_);
77+
78+
$.blocksData.addBlock(
79+
blockHeader_,
80+
blockHash_,
81+
blockHeight_,
82+
TargetsStorage.getBlockWork(target_)
83+
);
84+
85+
if (TargetsStorage.isTargetAdjustmentBlock(blockHeight_)) {
86+
$.targets.updatePendingTarget(
87+
blockHeight_,
88+
blockHash_,
89+
$.blocksData.getBlockTimeByBlockHeight(
90+
blockHeight_ - TargetsStorage.DIFFICULTY_ADJSTMENT_INTERVAL
91+
),
92+
blockHeader_.time
93+
);
94+
}
95+
96+
emit BlockHeaderAdded(blockHeight_, blockHash_);
97+
}
98+
99+
function _getRequiredTarget(
100+
uint256 blockHeight_,
101+
bytes32 prevBlockHash_
102+
) internal view returns (bytes32 target_) {
103+
SPVContractStorage storage $ = _getSPVContractStorage();
104+
105+
uint256 blockTargetEpoch_ = TargetsStorage.countTargetEpoch(blockHeight_);
106+
bool isPendingTargetRequired_ = blockTargetEpoch_ == $.targets.getPendingEpoch();
107+
108+
if (isPendingTargetRequired_) {
109+
uint256 epochBlockNumber_ = TargetsStorage.getEpochBlockNumber(blockHeight_);
110+
111+
for (uint256 i = 0; i < epochBlockNumber_ - 1; ++i) {
112+
prevBlockHash_ = $.blocksData.getPrevBlockHash(prevBlockHash_);
113+
}
114+
115+
target_ = $.targets.getPendingTarget(prevBlockHash_);
116+
} else {
117+
target_ = $.targets.getTarget(blockTargetEpoch_);
118+
}
119+
120+
assert(target_ > 0);
121+
}
122+
123+
function _tryConfirmPendingTarget(uint256 blockHeight_) internal {
124+
SPVContractStorage storage $ = _getSPVContractStorage();
125+
126+
if (!$.targets.hasPendingTarget() || blockHeight_ < $.pendingTargetHeightCount) {
127+
return;
128+
}
129+
130+
uint256 lastActiveBlockHeight_ = blockHeight_ - $.pendingTargetHeightCount;
131+
132+
if (TargetsStorage.isTargetAdjustmentBlock(lastActiveBlockHeight_)) {
133+
$.targets.confirmPendingTarget(
134+
$.blocksData.getBlockHashByBlockHeight(lastActiveBlockHeight_)
135+
);
136+
}
137+
}
138+
139+
function _validateBlockRules(
140+
BlockHeaderData memory blockHeader_,
141+
bytes32 blockHash_,
142+
bytes32 target_
143+
) internal view {
144+
SPVContractStorage storage $ = _getSPVContractStorage();
145+
146+
bytes32 blockTarget_ = TargetsHelper.bitsToTarget(blockHeader_.bits);
147+
148+
require(target_ == blockTarget_, InvalidTarget(blockTarget_, target_));
149+
require(blockHash_ <= blockTarget_, InvalidBlockHash(blockHash_, blockTarget_));
150+
151+
uint32 medianTime_ = $.blocksData.getMedianTime(blockHeader_.prevBlockHash);
152+
153+
require(blockHeader_.time > medianTime_, InvalidBlockTime(blockHeader_.time, medianTime_));
154+
}
155+
156+
// function test(bytes calldata blockHeaderRaw_) external {
157+
// BlockHeader.BlockHeaderData memory initBlockHeaderData_ = BlockHeader.parseBlockHeaderData(
158+
// blockHeaderRaw_
159+
// );
160+
161+
// console.logBytes32(initBlockHeaderData_.blockHash);
162+
// }
163+
}

contracts/libs/BlockHeader.sol

Lines changed: 66 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,66 @@
1+
// SPDX-License-Identifier: MIT
2+
pragma solidity ^0.8.28;
3+
4+
import {LibBit} from "solady/src/utils/LibBit.sol";
5+
6+
struct BlockHeaderData {
7+
bytes32 prevBlockHash;
8+
bytes32 merkleRoot;
9+
uint32 version;
10+
uint32 time;
11+
uint32 nonce;
12+
bytes4 bits;
13+
}
14+
15+
library BlockHeader {
16+
using LibBit for uint256;
17+
18+
uint256 public constant BLOCK_HEADER_DATA_LENGTH = 80;
19+
20+
error InvalidBlockHeaderDataLength();
21+
22+
function getBlockHeaderHash(bytes calldata blockHeaderRaw_) internal pure returns (bytes32) {
23+
bytes32 rawBlockHash_ = sha256(abi.encode(sha256(blockHeaderRaw_)));
24+
25+
return reverseBlockHash(rawBlockHash_);
26+
}
27+
28+
function parseBlockHeaderData(
29+
bytes calldata blockHeaderRaw_
30+
) internal pure returns (BlockHeaderData memory headerData_, bytes32 blockHash_) {
31+
require(
32+
blockHeaderRaw_.length == BLOCK_HEADER_DATA_LENGTH,
33+
InvalidBlockHeaderDataLength()
34+
);
35+
36+
headerData_ = BlockHeaderData({
37+
version: uint32(_convertToBigEndian(blockHeaderRaw_[0:4])),
38+
prevBlockHash: reverseBlockHash(bytes32(blockHeaderRaw_[4:36])),
39+
merkleRoot: bytes32(blockHeaderRaw_[36:68]),
40+
time: uint32(_convertToBigEndian(blockHeaderRaw_[68:72])),
41+
bits: bytes4(uint32(_convertToBigEndian(blockHeaderRaw_[72:76]))),
42+
nonce: uint32(_convertToBigEndian(blockHeaderRaw_[76:80]))
43+
});
44+
blockHash_ = getBlockHeaderHash(blockHeaderRaw_);
45+
}
46+
47+
function toRawBytes(BlockHeaderData memory headerData_) internal pure returns (bytes memory) {
48+
return
49+
abi.encode(
50+
headerData_.version,
51+
reverseBlockHash(headerData_.prevBlockHash),
52+
headerData_.merkleRoot,
53+
headerData_.time,
54+
headerData_.bits,
55+
headerData_.nonce
56+
);
57+
}
58+
59+
function reverseBlockHash(bytes32 blockHash_) internal pure returns (bytes32) {
60+
return bytes32(uint256(blockHash_).reverseBytes());
61+
}
62+
63+
function _convertToBigEndian(bytes calldata bytesToConvert_) private pure returns (uint256) {
64+
return uint256(bytes32(bytesToConvert_)).reverseBytes();
65+
}
66+
}

0 commit comments

Comments
 (0)