Commit ca1be38b authored by 贾浩@五瓣科技's avatar 贾浩@五瓣科技

init

parents
Pipeline #665 canceled with stages
node_modules
.env
coverage
coverage.json
typechain
typechain-types
# Hardhat files
cache
artifacts
.idea
.history
*.go
\ No newline at end of file
# Contract
```shell
npx hardhat help
npx hardhat test
REPORT_GAS=true npx hardhat test
npx hardhat node
npx hardhat run scripts/deploy.js
```
test token https://holesky.etherscan.io/address/0xffb096e2b90324ffccbaf987bdd724462c0ae18c
// SPDX-License-Identifier: GPL-3.0
pragma solidity ^0.8.20;
import {Ownable} from "@openzeppelin/contracts/access/Ownable.sol";
import {EnumerableSet} from "@openzeppelin/contracts/utils/structs/EnumerableSet.sol";
import {IAddressStorage} from "./interface/IAddressStorage.sol";
contract AddressStorage is IAddressStorage, Ownable {
using EnumerableSet for EnumerableSet.AddressSet;
EnumerableSet.AddressSet private containerAddressSet;
EnumerableSet.AddressSet private nmAddressSet;
event NmAddressChanged(address indexed addr, bool indexed ok, uint256 indexed count);
event ContainerAddressChanged(address indexed addr, bool indexed ok, uint256 indexed count);
constructor() Ownable(msg.sender) {
setContainerAddress(msg.sender, true);
setNmAddress(msg.sender, true);
}
function setNmAddress(address addr, bool ok) public onlyOwner {
if (ok) {
_addNmAddress(addr);
} else {
_delNmAddress(addr);
}
emit NmAddressChanged(addr, ok, nmAddressSet.length());
}
function setContainerAddress(address addr, bool ok) public onlyOwner {
if (ok) {
_addContainerAddress(addr);
} else {
_delContainerAddress(addr);
}
emit ContainerAddressChanged(addr, ok, containerAddressSet.length());
}
function isNmAddress(address addr) public view returns (bool) {
return nmAddressSet.contains(addr);
}
function isContainerAddress(address addr) public view returns (bool) {
return containerAddressSet.contains(addr);
}
function getNmAddresses() public view returns (address[] memory) {
return nmAddressSet.values();
}
function getContainerAddresses() public view returns (address[] memory) {
return containerAddressSet.values();
}
function _addNmAddress(address addr) internal {
bool exist = nmAddressSet.contains(addr);
require(!exist, "Already exist");
nmAddressSet.add(addr);
}
function _delNmAddress(address addr) internal {
bool exist = nmAddressSet.contains(addr);
require(exist, "Not exist");
nmAddressSet.remove(addr);
}
function _addContainerAddress(address addr) internal {
bool exist = containerAddressSet.contains(addr);
require(!exist, "Already exist");
containerAddressSet.add(addr);
}
function _delContainerAddress(address addr) internal {
bool exist = containerAddressSet.contains(addr);
require(exist, "Not exist");
containerAddressSet.remove(addr);
}
}
\ No newline at end of file
// SPDX-License-Identifier: GPL-3.0
pragma solidity ^0.8.20;
import {Ownable} from "@openzeppelin/contracts/access/Ownable.sol";
import {MerkleProof} from "@openzeppelin/contracts/utils/cryptography/MerkleProof.sol";
import {IERC20} from "@openzeppelin/contracts/token/ERC20/IERC20.sol";
import {Pausable} from "@openzeppelin/contracts/utils/Pausable.sol";
import {ReentrancyGuard} from "@openzeppelin/contracts/utils/ReentrancyGuard.sol";
import {IWitness} from "./interface/IWitness.sol";
import {IDistribution} from "./interface/IDistribution.sol";
contract Distribution is IDistribution, Ownable, Pausable, ReentrancyGuard {
constructor() Ownable(msg.sender) {
}
IWitness public witness;
IERC20 public token = IERC20(address(0xfFb096e2B90324FFcCbaf987BdD724462c0aE18c));
mapping(address => uint256) public userClaimed;
event Claimed(address indexed addr, uint256 indexed amount, uint256 indexed totalClaimedAmount);
function setPause() public onlyOwner {
_pause();
}
function setUnPause() public onlyOwner {
_unpause();
}
function withdraw(address addr) public onlyOwner {
token.transfer(addr, token.balanceOf(address(this)));
}
function claim(bytes32[] calldata proof, uint256 userAllAmount) public whenNotPaused nonReentrant {
bytes32 leaf = keccak256(abi.encodePacked(msg.sender, userAllAmount));
require(MerkleProof.verifyCalldata(proof, _merkleRoot(), leaf), "Invalid proof");
uint256 userCanClaimAmount = userAllAmount - userClaimed[msg.sender];
userClaimed[msg.sender] = userAllAmount;
token.transfer(msg.sender, userCanClaimAmount);
emit Claimed(msg.sender, userCanClaimAmount, userAllAmount);
}
function _merkleRoot() internal returns (bytes32){
return witness.getMerkleRoot();
}
}
\ No newline at end of file
// SPDX-License-Identifier: GPL-3.0
pragma solidity ^0.8.20;
import {Ownable} from "@openzeppelin/contracts/access/Ownable.sol";
import {EnumerableSet} from "@openzeppelin/contracts/utils/structs/EnumerableSet.sol";
import {IWitness} from "./interface/IWitness.sol";
contract Witness is IWitness, Ownable {
using EnumerableSet for EnumerableSet.AddressSet;
EnumerableSet.AddressSet private witnessSet;
bytes32 public merkleRoot; // 当前最新的 merkle root
bytes32 public merkleSumRoot; // 当前最新的 merkle sum root
uint256 public merkleRootThreshold = 1; // 多节点提交时,生效阈值
uint256 public dailyMerkleRootSubmitCount = 0; // 当天已提交次数
uint256 public lastMerkleRootUpdate; // 上次 merkle root 更新时间
// 记录每天每个 merkle root 提交次数 2023-01-01 -> hash(merkleRoot, merkleSumRoot) -> 1
mapping(string => mapping(bytes32 => uint256)) public dailyMerkleRootSubmitCountMap;
// 记录每天每个 witness 是否已提交过
mapping(string => mapping(address => bool)) public dailyMerkleRootSubmittedMap;
// 记录每天已验证的 merkle root (已超过生效阈值)
mapping(string => bool) public dailyMerkleRootVerifiedMap;
// 记录每天的 merkle root
mapping(string => bytes32) public dailyMerkleRootMap;
// 记录每天的 merkle sum root
mapping(string => bytes32) public dailyMerkleSumRootMap;
uint256 public dailyDistribution;
constructor() Ownable(msg.sender) {
witnessSet.add(msg.sender); // for test
}
modifier onlyWitness() {
require(witnessSet.contains(msg.sender), "Only witness");
_;
}
event MerkleRootChanged(string date, bytes32 merkleRoot, bytes32 merkleSumRoot);
event MerkleRootThresholdChanged(uint256 merkleRootThreshold);
event MerkleRootSubmitted(address indexed addr, bytes32 merkleRoot, bytes32 merkleSumRoot, uint256 indexed count, uint256 indexed dailySubmitCount);
event DailyDistributionChanged(uint256 dailyDistribution);
event WitnessChanged(address indexed addr, bool indexed ok, uint256 indexed count);
function getMerkleRoot() public view returns (bytes32){
return merkleRoot;
}
function submitMerkleRoot(string calldata _date, bytes32 _merkleRoot, bytes32 _merkleSumRoot) public onlyWitness {
require(dailyMerkleRootSubmitCount <= witnessSet.length(), "Too many submissions");
require(!dailyMerkleRootSubmittedMap[_date][msg.sender], "Already submitted");
// check already verified
if (dailyMerkleRootVerifiedMap[_date]) {
return;
}
require(block.timestamp - lastMerkleRootUpdate >= 3600 * 24, "Too soon");
bytes32 rootHash = keccak256(abi.encodePacked(_merkleRoot, _merkleSumRoot));
dailyMerkleRootSubmitCountMap[_date][rootHash] ++;
dailyMerkleRootSubmittedMap[_date][msg.sender] = true;
dailyMerkleRootSubmitCount ++;
if (dailyMerkleRootSubmitCountMap[_date][rootHash] >= merkleRootThreshold) {
dailyMerkleRootVerifiedMap[_date] = true;
_setMerkleRoot(_date, _merkleRoot, _merkleSumRoot);
lastMerkleRootUpdate = block.timestamp;
dailyMerkleRootSubmitCount = 0;
emit MerkleRootChanged(_date, _merkleRoot, _merkleSumRoot);
}
emit MerkleRootSubmitted(msg.sender, _merkleRoot, _merkleSumRoot, dailyMerkleRootSubmitCountMap[_date][rootHash], dailyMerkleRootSubmitCount);
}
function _setMerkleRoot(string calldata _date, bytes32 _merkleRoot, bytes32 _merkleSumRoot) internal {
merkleRoot = _merkleRoot;
merkleSumRoot = merkleSumRoot;
dailyMerkleRootMap[_date] = _merkleRoot;
dailyMerkleSumRootMap[_date] = _merkleSumRoot;
}
function setDailyDistribution(uint256 _dailyDistribution) public onlyOwner {
dailyDistribution = _dailyDistribution;
emit DailyDistributionChanged(_dailyDistribution);
}
function getDailyDistribution() public view returns (uint256) {
return dailyDistribution;
}
function setRootThreshold(uint256 _merkleRootThreshold) public onlyOwner {
merkleRootThreshold = _merkleRootThreshold;
emit MerkleRootThresholdChanged(_merkleRootThreshold);
}
function setWitness(address addr, bool ok) public onlyOwner {
if (ok) {
_addWitness(addr);
} else {
_delWitness(addr);
}
emit WitnessChanged(addr, ok, witnessSet.length());
}
function _addWitness(address addr) internal {
bool exist = witnessSet.contains(addr);
require(!exist, "Already exist");
witnessSet.add(addr);
}
function _delWitness(address addr) internal {
bool exist = witnessSet.contains(addr);
require(exist, "Not exist");
witnessSet.remove(addr);
}
function getWitnesses() public view returns (address[] memory) {
return witnessSet.values();
}
function isWitness(address addr) public view returns (bool) {
return witnessSet.contains(addr);
}
}
\ No newline at end of file
// SPDX-License-Identifier: GPL-3.0
pragma solidity ^0.8.20;
interface IAddressStorage {
function setNmAddress(address addr, bool ok) external;
function setContainerAddress(address addr, bool ok) external;
function getNmAddresses() external view returns (address[] memory);
function getContainerAddresses() external view returns (address[] memory);
function isNmAddress(address addr) external view returns (bool);
function isContainerAddress(address addr) external view returns (bool);
}
\ No newline at end of file
// SPDX-License-Identifier: GPL-3.0
pragma solidity ^0.8.20;
interface IDistribution {
function claim(bytes32[] calldata proof, uint256 userAllAmount) external;
}
\ No newline at end of file
// SPDX-License-Identifier: GPL-3.0
pragma solidity ^0.8.20;
interface IWitness {
function getMerkleRoot() external returns (bytes32);
function submitMerkleRoot(string calldata _date, bytes32 _merkleRoot, bytes32 _merkleSumRoot) external;
function getDailyDistribution() external view returns (uint256);
function setDailyDistribution(uint256 _dailyDistribution) external;
}
\ No newline at end of file
require("@nomicfoundation/hardhat-toolbox");
require('dotenv').config()
/** @type import('hardhat/config').HardhatUserConfig */
module.exports = {
solidity: {
version: "0.8.20",
settings: {
optimizer: {
enabled: true,
runs: 200
}
}
},
networks: {
testnet: {
url: 'https://1rpc.io/holesky',
accounts: process.env.PRIVATE_KEY ? [process.env.PRIVATE_KEY] : []
}
},
etherscan: {
}
};
This diff is collapsed.
This diff is collapsed.
// We require the Hardhat Runtime Environment explicitly here. This is optional
// but useful for running the script in a standalone fashion through `node <script>`.
//
// You can also run a script with `npx hardhat run <script>`. If you do that, Hardhat
// will compile your contracts, add the Hardhat Runtime Environment's members to the
// global scope, and execute the script.
const hre = require("hardhat");
async function main() {
const Storage = await hre.ethers.getContractFactory("AddressStorage");
const storage = await Storage.deploy();
await storage.waitForDeployment();
console.log(`Storage deployed to ${storage.target}`);
const Witness = await hre.ethers.getContractFactory("Witness");
const witness = await Witness.deploy();
await witness.waitForDeployment();
console.log(`Witness deployed to ${witness.target}`);
// const Distribution = await hre.ethers.getContractFactory("Distribution");
// const distribution = await Distribution.deploy();
// await distribution.waitForDeployment();
// console.log(`Distribution deployed to ${distribution.target}`);
}
// We recommend this pattern to be able to use async/await everywhere
// and properly handle errors.
main().catch((error) => {
console.error(error);
process.exitCode = 1;
});
const {
time,
loadFixture,
} = require("@nomicfoundation/hardhat-toolbox/network-helpers");
const { anyValue } = require("@nomicfoundation/hardhat-chai-matchers/withArgs");
const { expect } = require("chai");
describe("Lock", function () {
// We define a fixture to reuse the same setup in every test.
// We use loadFixture to run this setup once, snapshot that state,
// and reset Hardhat Network to that snapshot in every test.
async function deployOneYearLockFixture() {
const ONE_YEAR_IN_SECS = 365 * 24 * 60 * 60;
const ONE_GWEI = 1_000_000_000;
const lockedAmount = ONE_GWEI;
const unlockTime = (await time.latest()) + ONE_YEAR_IN_SECS;
// Contracts are deployed using the first signer/account by default
const [owner, otherAccount] = await ethers.getSigners();
const Lock = await ethers.getContractFactory("Lock");
const lock = await Lock.deploy(unlockTime, { value: lockedAmount });
return { lock, unlockTime, lockedAmount, owner, otherAccount };
}
describe("Deployment", function () {
it("Should set the right unlockTime", async function () {
const { lock, unlockTime } = await loadFixture(deployOneYearLockFixture);
expect(await lock.unlockTime()).to.equal(unlockTime);
});
it("Should set the right owner", async function () {
const { lock, owner } = await loadFixture(deployOneYearLockFixture);
expect(await lock.owner()).to.equal(owner.address);
});
it("Should receive and store the funds to lock", async function () {
const { lock, lockedAmount } = await loadFixture(
deployOneYearLockFixture
);
expect(await ethers.provider.getBalance(lock.target)).to.equal(
lockedAmount
);
});
it("Should fail if the unlockTime is not in the future", async function () {
// We don't use the fixture here because we want a different deployment
const latestTime = await time.latest();
const Lock = await ethers.getContractFactory("Lock");
await expect(Lock.deploy(latestTime, { value: 1 })).to.be.revertedWith(
"Unlock time should be in the future"
);
});
});
describe("Withdrawals", function () {
describe("Validations", function () {
it("Should revert with the right error if called too soon", async function () {
const { lock } = await loadFixture(deployOneYearLockFixture);
await expect(lock.withdraw()).to.be.revertedWith(
"You can't withdraw yet"
);
});
it("Should revert with the right error if called from another account", async function () {
const { lock, unlockTime, otherAccount } = await loadFixture(
deployOneYearLockFixture
);
// We can increase the time in Hardhat Network
await time.increaseTo(unlockTime);
// We use lock.connect() to send a transaction from another account
await expect(lock.connect(otherAccount).withdraw()).to.be.revertedWith(
"You aren't the owner"
);
});
it("Shouldn't fail if the unlockTime has arrived and the owner calls it", async function () {
const { lock, unlockTime } = await loadFixture(
deployOneYearLockFixture
);
// Transactions are sent using the first signer by default
await time.increaseTo(unlockTime);
await expect(lock.withdraw()).not.to.be.reverted;
});
});
describe("Events", function () {
it("Should emit an event on withdrawals", async function () {
const { lock, unlockTime, lockedAmount } = await loadFixture(
deployOneYearLockFixture
);
await time.increaseTo(unlockTime);
await expect(lock.withdraw())
.to.emit(lock, "Withdrawal")
.withArgs(lockedAmount, anyValue); // We accept any value as `when` arg
});
});
describe("Transfers", function () {
it("Should transfer the funds to the owner", async function () {
const { lock, unlockTime, lockedAmount, owner } = await loadFixture(
deployOneYearLockFixture
);
await time.increaseTo(unlockTime);
await expect(lock.withdraw()).to.changeEtherBalances(
[owner, lock],
[lockedAmount, -lockedAmount]
);
});
});
});
});
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment