-
Notifications
You must be signed in to change notification settings - Fork 18
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Merge pull request #638 from iotexproject/minter
smart contract
- Loading branch information
Showing
18 changed files
with
1,479 additions
and
950 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,31 @@ | ||
// SPDX-License-Identifier: MIT | ||
pragma solidity ^0.8.19; | ||
|
||
contract Scrypt { | ||
function hash( | ||
bytes calldata password, | ||
bytes calldata salt, | ||
uint64 N, | ||
uint32 r, | ||
uint32 p, | ||
uint32 keyLen, | ||
uint32 mode | ||
) external view returns (bytes memory retval) { | ||
bytes memory input = abi.encode(password, salt, N, r, p, keyLen, mode); | ||
uint length = input.length; | ||
assembly { | ||
if iszero(staticcall(gas(), 0x8002, add(input, 0x20), length, 0, 0)) { | ||
revert(0x0, 0x0) | ||
} | ||
if iszero(eq(returndatasize(), keyLen)) { | ||
revert(0x0, 0x0) | ||
} | ||
mstore(retval, returndatasize()) // Store the length. | ||
let o := add(retval, 0x20) | ||
returndatacopy(o, 0x00, returndatasize()) // Copy the returndata. | ||
mstore(0x40, add(o, returndatasize())) // Allocate the memory. | ||
} | ||
|
||
return retval; | ||
} | ||
} |
128 changes: 128 additions & 0 deletions
128
smartcontracts/contracts/W3bstreamBlockHeaderValidator.sol
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,128 @@ | ||
// SPDX-License-Identifier: MIT | ||
pragma solidity ^0.8.19; | ||
|
||
import {Ownable} from "@openzeppelin/contracts/access/Ownable.sol"; | ||
import {BlockHeader, IBlockHeaderValidator, IScrypt} from "./interfaces/IBlockHeaderValidator.sol"; | ||
|
||
contract W3bstreamBlockHeaderValidator is IBlockHeaderValidator, Ownable { | ||
event OperatorSet(address operator); | ||
event TargetDurationSet(uint256 duration); | ||
event NBitsSet(uint32 nbits); | ||
|
||
uint32 public constant MAX_EXPONENT = 0x1c; | ||
uint32 public constant UPPER_BOUND = 0xffff00; | ||
uint32 public constant LOWER_BOUND = 0x8000; | ||
// uint256 public constant MAX_TARGET = 0x00000000ffff0000000000000000000000000000000000000000000000000000; | ||
|
||
address public operator; | ||
IScrypt public scrypt; | ||
uint256 public targetDuration; | ||
uint32 public currentNBits; | ||
bool public useAdhocNBits; | ||
|
||
uint256 private _currentTarget; | ||
uint256[10] private _durations; | ||
uint256 private _durationSum; | ||
uint256 private _durationNum; | ||
uint256 private _durationIndex; | ||
|
||
constructor(IScrypt _scrypt) Ownable() { | ||
scrypt = _scrypt; | ||
_setTargetDuration(12); | ||
_setAdhocNBits(0x1c7fffff); | ||
} | ||
|
||
function validate(BlockHeader calldata header) public view returns (bytes memory) { | ||
require(header.nbits == currentNBits, "invalid nbits"); | ||
bytes memory encodedHeader = abi.encodePacked(header.meta, header.prevhash, header.merkleRoot, header.nbits, header.nonce); | ||
bytes memory headerHash = scrypt.hash(encodedHeader, encodedHeader, 1024, 1, 1, 32, 224); | ||
require(headerHash.length == 32, "invalid header hash length"); | ||
require(uint256(bytes32(headerHash)) <= _currentTarget, "invalid proof of work"); | ||
return encodedHeader; | ||
} | ||
|
||
function setOperator(address _operator) public onlyOwner { | ||
operator = _operator; | ||
emit OperatorSet(_operator); | ||
} | ||
|
||
function setTargetDuration(uint256 duration) public onlyOwner { | ||
_setTargetDuration(duration); | ||
} | ||
|
||
function _setTargetDuration(uint256 duration) internal { | ||
targetDuration = duration; | ||
emit TargetDurationSet(duration); | ||
} | ||
|
||
function setAdhocNBits(uint32 nbits) public onlyOwner { | ||
_setAdhocNBits(nbits); | ||
} | ||
|
||
function _setAdhocNBits(uint32 nbits) internal { | ||
if (nbits == 0) { | ||
useAdhocNBits = false; | ||
return; | ||
} | ||
_setNBits(nbits); | ||
useAdhocNBits = true; | ||
} | ||
|
||
function updateDuration(uint256 duration) public { | ||
require(msg.sender == operator, "not operator"); | ||
_durationSum += duration - _durations[_durationIndex]; | ||
_durations[_durationIndex] = duration; | ||
_durationIndex = (_durationIndex + 1) % _durations.length; | ||
if (_durationNum < _durations.length) { | ||
_durationNum++; | ||
return; | ||
} | ||
if (useAdhocNBits) { | ||
return; | ||
} | ||
uint32 nbits = uint32(currentNBits); | ||
uint32 next = _nextNBits(nbits, targetDuration * _durationNum, _durationSum); | ||
if (next != nbits) { | ||
_setNBits(next); | ||
} | ||
} | ||
|
||
function _nextNBits(uint32 nbits, uint256 expectedSum, uint256 sum) internal pure returns (uint32) { | ||
if (sum * 5 > expectedSum * 6) { | ||
(uint32 exponent, uint32 coefficient) = decodeNBits(nbits); | ||
if (coefficient < UPPER_BOUND) { | ||
return (exponent << 24) | uint32(coefficient + 1); | ||
} | ||
if (exponent < MAX_EXPONENT) { | ||
return ((exponent + 1) << 24) | LOWER_BOUND; | ||
} | ||
} else if (expectedSum * 4 > sum * 5) { | ||
(uint32 exponent, uint32 coefficient) = decodeNBits(nbits); | ||
if (coefficient > LOWER_BOUND) { | ||
return (exponent << 24) | uint32(coefficient - 1); | ||
} | ||
if (exponent > 0) { | ||
return ((exponent - 1) << 24) | UPPER_BOUND; | ||
} | ||
} | ||
return nbits; | ||
} | ||
|
||
function _setNBits(uint32 nbits) internal { | ||
uint256 target = nbitsToTarget(nbits); | ||
currentNBits = nbits; | ||
_currentTarget = target; | ||
emit NBitsSet(nbits); | ||
} | ||
|
||
function decodeNBits(uint32 nbits) internal pure returns (uint32, uint32) { | ||
return (nbits >> 24, nbits & 0x00ffffff); | ||
} | ||
|
||
function nbitsToTarget(uint32 nbits) public pure returns (uint256) { | ||
(uint32 exponent, uint256 coefficient) = decodeNBits(nbits); | ||
require(exponent <= MAX_EXPONENT, "invalid nbits"); | ||
require(coefficient >= LOWER_BOUND && coefficient <= UPPER_BOUND, "invalid nbits"); | ||
return coefficient << (8 * (exponent - 3)); | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,82 @@ | ||
// SPDX-License-Identifier: MIT | ||
pragma solidity ^0.8.19; | ||
|
||
import {OwnableUpgradeable} from "@openzeppelin/contracts-upgradeable/access/OwnableUpgradeable.sol"; | ||
import {BlockHeader, IBlockHeaderValidator} from "./interfaces/IBlockHeaderValidator.sol"; | ||
import {ITaskManager, TaskAssignment} from "./interfaces/ITaskManager.sol"; | ||
|
||
interface IDAO { | ||
function tip() external view returns (uint256, bytes32, uint256); | ||
function mint(bytes32 hash, uint256 timestamp) external; | ||
} | ||
|
||
interface IBlockRewardDistributor { | ||
function distribute(address recipient, uint256 amount) external; | ||
} | ||
|
||
struct Sequencer { | ||
address addr; | ||
address operator; | ||
address beneficiary; | ||
} | ||
|
||
contract W3bstreamBlockMinter is OwnableUpgradeable { | ||
event BlockRewardSet(uint256 reward); | ||
event TaskAllowanceSet(uint256 allowance); | ||
event TargetDurationSet(uint256 duration); | ||
event NBitsSet(uint32 nbits); | ||
|
||
IDAO public dao; | ||
ITaskManager public taskManager; | ||
IBlockRewardDistributor public distributor; | ||
IBlockHeaderValidator public headerValidator; | ||
|
||
uint256 public blockReward; | ||
uint256 public taskAllowance; | ||
|
||
function initialize(IDAO _dao, ITaskManager _taskManager, IBlockRewardDistributor _distributor, IBlockHeaderValidator _headerValidator) public initializer { | ||
__Ownable_init(); | ||
dao = _dao; | ||
taskManager = _taskManager; | ||
distributor = _distributor; | ||
headerValidator = _headerValidator; | ||
_setBlockReward(1000000000000000000); | ||
_setTaskAllowance(720); | ||
} | ||
|
||
function mint( | ||
BlockHeader calldata header, | ||
Sequencer calldata coinbase, | ||
TaskAssignment[] calldata assignments | ||
) public { | ||
require(coinbase.operator == msg.sender, "invalid operator"); | ||
(, bytes32 tiphash, uint256 tipTimestamp) = dao.tip(); | ||
require(tipTimestamp != block.number); | ||
require(header.prevhash == tiphash, "invalid prevhash"); | ||
require(header.merkleRoot == keccak256(abi.encode(coinbase.addr, coinbase.operator, coinbase.beneficiary)), "invalid merkle root"); | ||
bytes memory encodedHeader = headerValidator.validate(header); | ||
bytes32 blockHash = keccak256(abi.encode(encodedHeader, assignments)); | ||
taskManager.assign(assignments, coinbase.beneficiary, block.number + taskAllowance); | ||
headerValidator.updateDuration(block.number - tipTimestamp); | ||
dao.mint(blockHash, block.number); | ||
distributor.distribute(coinbase.beneficiary, blockReward); | ||
} | ||
|
||
function setBlockReward(uint256 reward) public onlyOwner { | ||
_setBlockReward(reward); | ||
} | ||
|
||
function _setBlockReward(uint256 reward) internal { | ||
blockReward = reward; | ||
emit BlockRewardSet(reward); | ||
} | ||
|
||
function setTaskAllowance(uint256 allowance) public onlyOwner { | ||
_setTaskAllowance(allowance); | ||
} | ||
|
||
function _setTaskAllowance(uint256 allowance) internal { | ||
taskAllowance = allowance; | ||
emit TaskAllowanceSet(allowance); | ||
} | ||
} |
49 changes: 49 additions & 0 deletions
49
smartcontracts/contracts/W3bstreamBlockRewardDistributor.sol
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,49 @@ | ||
// SPDX-License-Identifier: MIT | ||
pragma solidity ^0.8.19; | ||
|
||
import {OwnableUpgradeable} from "@openzeppelin/contracts-upgradeable/access/OwnableUpgradeable.sol"; | ||
|
||
contract W3bstreamBlockRewardDistributor is OwnableUpgradeable { | ||
event Distributed(address indexed recipient, uint256 amount); | ||
event Withdrawn(uint256 amount); | ||
event OperatorSet(address indexed operator); | ||
event Topup(uint256 amount); | ||
address public operator; | ||
|
||
modifier onlyOperator() { | ||
require(msg.sender == operator, "not operator"); | ||
_; | ||
} | ||
|
||
receive() external payable { | ||
emit Topup(msg.value); | ||
} | ||
|
||
function initialize() public initializer { | ||
__Ownable_init(); | ||
} | ||
|
||
function setOperator(address _operator) public onlyOwner { | ||
operator = _operator; | ||
emit OperatorSet(_operator); | ||
} | ||
|
||
function distribute(address recipient, uint256 amount) public onlyOperator { | ||
if (amount == 0) { | ||
return; | ||
} | ||
if (amount > address(this).balance) { | ||
revert("insufficient balance"); | ||
} | ||
(bool success, ) = recipient.call{value: amount}(""); | ||
require(success, "transfer failed"); | ||
emit Distributed(recipient, amount); | ||
} | ||
|
||
function withdraw(uint256 amount) public onlyOwner { | ||
require(amount <= address(this).balance, "insufficient balance"); | ||
(bool success, ) = msg.sender.call{value: amount}(""); | ||
require(success, "transfer failed"); | ||
emit Withdrawn(amount); | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,34 @@ | ||
// SPDX-License-Identifier: MIT | ||
pragma solidity ^0.8.19; | ||
|
||
import {OwnableUpgradeable} from "@openzeppelin/contracts-upgradeable/access/OwnableUpgradeable.sol"; | ||
|
||
contract W3bstreamDAO is OwnableUpgradeable { | ||
struct Block { | ||
bytes32 hash; | ||
uint256 timestamp; | ||
} | ||
event BlockAdded(uint256 indexed num, bytes32 hash, uint256 timestamp); | ||
|
||
Block[] public blocks; | ||
|
||
function initialize(bytes32 genesis) public initializer { | ||
__Ownable_init(); | ||
_mint(genesis, block.number); | ||
} | ||
|
||
function mint(bytes32 hash, uint256 timestamp) public onlyOwner { | ||
_mint(hash, timestamp); | ||
} | ||
|
||
function tip() public view returns (uint256, bytes32, uint256) { | ||
uint256 blocknum = blocks.length - 1; | ||
Block storage tipblock = blocks[blocknum]; | ||
return (blocknum, tipblock.hash, tipblock.timestamp); | ||
} | ||
|
||
function _mint(bytes32 hash, uint256 timestamp) internal { | ||
blocks.push(Block({timestamp: timestamp, hash: hash})); | ||
emit BlockAdded(blocks.length - 1, hash, timestamp); | ||
} | ||
} |
Oops, something went wrong.