diff --git a/contracts/dividend.sol b/contracts/dividend.sol index 20c9d56..b0f9b7e 100644 --- a/contracts/dividend.sol +++ b/contracts/dividend.sol @@ -243,7 +243,7 @@ contract DividendContract is Initializable, UUPSUpgradeable, ReentrancyGuardUpgr // stake tokens to next cycle function stake(uint256 amount) external nonReentrant { - require(amount > 0, "Cannot stake 0"); + require(amount >0, "Cannot stake 0 DMC"); require(IERC20(stakingToken).transferFrom(msg.sender, address(this), amount), "Stake failed"); console.log("user stake ===> amount %d, cycle %d, user %s", amount, currentCycleIndex, msg.sender); @@ -262,6 +262,7 @@ contract DividendContract is Initializable, UUPSUpgradeable, ReentrancyGuardUpgr // update the total staked amount of the contract totalStaked += amount; + //TODO: update current cycle's total staked? // emit the stake event emit Stake(msg.sender, amount); diff --git a/contracts/dividend2.sol b/contracts/dividend2.sol deleted file mode 100644 index ece4fda..0000000 --- a/contracts/dividend2.sol +++ /dev/null @@ -1,219 +0,0 @@ - -import "@openzeppelin/contracts/token/ERC20/IERC20.sol"; - -import "hardhat/console.sol"; - - -// 这里打算做一个更简单的逻辑:一旦有抵押变动,这个用户在这一轮里就不能提取收益 -contract Dividend2 { - IERC20 dmcToken; - uint256 minCycleBlock; - uint256 startBlock; - - uint256 lastActiveCycle; - - struct TokenIncome { - address token; - uint256 amount; - } - - struct DividendInfo { - uint256 startBlock; - uint256 totalDeposits; - TokenIncome[] incomes; - } - - struct UserStackLog { - uint256 cycleNumber; - uint256 amount; - } - - mapping(uint256 => DividendInfo) dividends; - uint256 lastCycleNumber; - - uint256 totalDeposits; - - mapping(address => UserStackLog[]) userStackLog; - - constructor(address _dmcToken, uint256 _minCycleBlock) { - dmcToken = IERC20(_dmcToken); - minCycleBlock = _minCycleBlock; - startBlock = block.number; - } - - // TODO:可能改成uint256[] cycleNumbers,返回uint256[]更好,考虑到withdraw的大多数调用情况应该是一次提取多个周期 - function _getStack(address user, uint256 cycleNumber) internal view returns (uint256) { - UserStackLog[] memory logs = userStackLog[user]; - if (logs.length == 0) { - return 0; - } - for (uint i = logs.length-1; ; i--) { - if (logs[i].cycleNumber <= cycleNumber) { - return logs[i].amount; - } - if (i == 0) { - break; - } - } - - return 0; - } - - // 质押 - function stake(uint256 amount) public { - dmcToken.transferFrom(msg.sender, address(this), amount); - uint256 nextCycle = lastCycleNumber + 1; - - UserStackLog[] storage logs = userStackLog[msg.sender]; - - if (logs.length == 0) { - logs.push(UserStackLog(nextCycle, amount)); - } else { - UserStackLog storage lastLog = logs[logs.length-1]; - if (lastLog.cycleNumber == nextCycle) { - lastLog.amount += amount; - } else { - logs.push(UserStackLog(nextCycle, lastLog.amount + amount)); - } - } - - totalDeposits += amount; - console.log("user %s add deposit %d, his deposit %d", msg.sender, amount, logs[logs.length-1].amount); - console.log("total deposti %d", totalDeposits); - } - - // 提取 - function unstake(uint256 amount) public { - UserStackLog[] storage logs = userStackLog[msg.sender]; - require(logs.length > 0, "no deposit"); - - UserStackLog storage lastLog = logs[logs.length-1]; - require(lastLog.amount >= amount, "not enough deposit"); - - console.log("user %s will unstack %d, his deposit %d", msg.sender, amount, lastLog.amount); - - dmcToken.transfer(msg.sender, amount); - uint256 nextCycle = lastCycleNumber + 1; - - // addAmount可以从last - lastlast的差值得到,就不需要再存一份了 - if (lastLog.cycleNumber == nextCycle) { - uint256 addAmount = 0; - if (logs.length > 1) { - console.log("found prev log cycle %d, amount %d", logs[logs.length-2].cycleNumber, logs[logs.length-2].amount); - if (logs[logs.length-2].amount < lastLog.amount) { - addAmount = lastLog.amount - logs[logs.length-2].amount; - } - } else { - addAmount = lastLog.amount; - } - // amount = 45, addAmount = 20 - if (addAmount >= amount) { - lastLog.amount -= amount; - } else { - // diff = 25 - uint256 diff = amount - addAmount; - console.log("amount %d, addAmount %d, diff %d", amount, addAmount, diff); - - // 从上一个周期扣amount的差值, 能走到这里,一定说明至少有两个周期,否则不会出现lastLog.amount >= amount 且 lastLog.addAmount < amount的情况 - UserStackLog memory lastLastLog = logs[logs.length-2]; - - // 假设当前是周期3,用户在周期1存了50,周期3存了20,又提取了45 - // 这里的Log要从[{2, 50}, {4, 70}]变成[{2, 50}, {3, 25}, {4, 25}] - if (lastLastLog.cycleNumber != nextCycle - 1) { - // 4 -> 3 - console.log("change last log cycle %d -> %d", lastLog.cycleNumber, nextCycle - 1); - lastLog.cycleNumber = nextCycle - 1; - // 70 -> 25 = 50 - 25 - console.log("change last log amount %d -> %d", lastLog.amount, lastLog.amount - diff); - lastLog.amount = lastLastLog.amount - diff; - console.log("push new log (%d, %d)", nextCycle, lastLog.amount); - logs.push(UserStackLog(nextCycle, lastLog.amount)); - } else { - // 假设用户在周期2存了50,周期3存了20,又提取了45 - // 这里的原log为[{3, 50}, {4, 70}],变成[{3, 25}, {4, 25}] - lastLastLog.amount -= diff; - lastLog.amount -= amount; - } - console.log("diff %d", diff); - console.log("change cycle %d total deposit %d -> %d", lastLog.cycleNumber, dividends[lastLog.cycleNumber].totalDeposits, dividends[lastLog.cycleNumber].totalDeposits - diff); - dividends[lastLog.cycleNumber].totalDeposits -= diff; - } - } else { - logs.push(UserStackLog(nextCycle, lastLog.amount - amount)); - } - - totalDeposits -= amount; - - console.log("user %s unstake %d, his deposit %d", msg.sender, amount, logs[logs.length-1].amount); - console.log("total deposti %d", totalDeposits); - } - - function _settleCycle() internal { - DividendInfo memory lastInfo = dividends[lastCycleNumber]; - if (lastInfo.startBlock + minCycleBlock > block.number) { - return; - } - - lastCycleNumber += 1; - - console.log("enter cycle %d, total deposit %d", lastCycleNumber, totalDeposits); - DividendInfo storage newInfo = dividends[lastCycleNumber]; - newInfo.startBlock = block.number; - newInfo.totalDeposits = totalDeposits; - - if (lastInfo.totalDeposits == 0) { - // 没人质押,分红滚入下一期 - newInfo.incomes = lastInfo.incomes; - } - } - - // 简单起见,先只做erc20的 - function deposit(uint256 amount, address token) public { - _settleCycle(); - - // 当amount为0时,强制结算上一个周期 - if (amount == 0) { - return; - } - - IERC20(token).transferFrom(msg.sender, address(this), amount); - uint256 cycleNumber = lastCycleNumber; - TokenIncome[] storage incomes = dividends[cycleNumber].incomes; - if (incomes.length == 0) { - dividends[cycleNumber].incomes.push(TokenIncome(token, amount)); - } else { - bool found = false; - for (uint i = 0; i < dividends[cycleNumber].incomes.length; i++) { - if (dividends[cycleNumber].incomes[i].token == token) { - dividends[cycleNumber].incomes[i].amount += amount; - found = true; - break; - } - } - - if (!found) { - dividends[cycleNumber].incomes.push(TokenIncome(token, amount)); - } - } - - } - - function withdrawDevidends(uint256[] calldata cycleNumbers, address[] calldata tokens) public { - for (uint i = 0; i < cycleNumbers.length; i++) { - uint256 cycleNumber = cycleNumbers[i]; - DividendInfo memory info = dividends[cycleNumber]; - require(info.totalDeposits > 0, "no dividend"); - console.log("%s withdraw %d", msg.sender, cycleNumbers[i]); - - uint256 userStack = _getStack(msg.sender, cycleNumber); - console.log("get cycle %d stack %d/%d", cycleNumber, userStack, info.totalDeposits); - require(userStack > 0, "cannot withdraw"); - for (uint256 i = 0; i < info.incomes.length; i++) { - uint256 amount = info.incomes[i].amount * userStack / info.totalDeposits; - console.log("total %d, withdraw %d", info.incomes[i].amount, amount); - IERC20(info.incomes[i].token).transfer(msg.sender, amount); - } - } - - } -} \ No newline at end of file diff --git a/contracts/dmc.sol b/contracts/dmc.sol deleted file mode 100644 index a607f86..0000000 --- a/contracts/dmc.sol +++ /dev/null @@ -1,40 +0,0 @@ -// SPDX-License-Identifier: MIT -pragma solidity ^0.8.0; - -import "@openzeppelin/contracts/token/ERC20/ERC20.sol"; -import "@openzeppelin/contracts/access/Ownable.sol"; - -contract DMCToken is ERC20, Ownable { - uint256 maxSupply; - mapping (address => bool) allow_minter; - - modifier onlyMinter() { - require(allow_minter[msg.sender], "mint not allowed"); - _; - } - - constructor(uint256 _maxSupply, address[] memory initAddress, uint[] memory initAmount) ERC20("Datamall Chain Token", "DMC") Ownable(msg.sender) { - maxSupply = _maxSupply; - - for (uint i = 0; i < initAddress.length; i++) { - _mint(initAddress[i], initAmount[i]); - } - } - - function enableMinter(address[] calldata addresses) public onlyOwner { - for (uint i = 0; i < addresses.length; i++) { - allow_minter[addresses[i]] = true; - } - } - - function disableMinter(address[] calldata addresses) public onlyOwner { - for (uint i = 0; i < addresses.length; i++) { - allow_minter[addresses[i]] = false; - } - } - - function mint(address to, uint256 amount) public onlyMinter { - require(totalSupply() + amount <= maxSupply, "max supply exceeded"); - _mint(to, amount); - } -} \ No newline at end of file diff --git a/contracts/dmc2.sol b/contracts/dmc2.sol index beee72f..7b9f82e 100644 --- a/contracts/dmc2.sol +++ b/contracts/dmc2.sol @@ -6,6 +6,7 @@ import "@openzeppelin/contracts/access/Ownable.sol"; contract DMC2 is ERC20Burnable, Ownable { mapping (address => bool) allow_minter; + bool can_change_minter = true; modifier onlyMinter() { require(allow_minter[msg.sender], "mint not allowed"); @@ -20,14 +21,20 @@ contract DMC2 is ERC20Burnable, Ownable { } _mint(address(this), _totalSupply - totalInited); } + + function disableMinterChange() public onlyOwner { + can_change_minter = false; + } function enableMinter(address[] calldata addresses) public onlyOwner { + require(can_change_minter, "mint change not allowed"); for (uint i = 0; i < addresses.length; i++) { allow_minter[addresses[i]] = true; } } function disableMinter(address[] calldata addresses) public onlyOwner { + require(can_change_minter, "mint change not allowed"); for (uint i = 0; i < addresses.length; i++) { allow_minter[addresses[i]] = false; } diff --git a/contracts/exchange.sol b/contracts/exchange.sol deleted file mode 100644 index 6fbd52b..0000000 --- a/contracts/exchange.sol +++ /dev/null @@ -1,68 +0,0 @@ -// SPDX-License-Identifier: MIT -pragma solidity ^0.8.0; - -import "@openzeppelin/contracts-upgradeable/proxy/utils/Initializable.sol"; -import "@openzeppelin/contracts-upgradeable/proxy/utils/UUPSUpgradeable.sol"; -import "@openzeppelin/contracts-upgradeable/access/OwnableUpgradeable.sol"; -import "@openzeppelin/contracts/utils/cryptography/MerkleProof.sol"; - -import "./dmc.sol"; -import "./gwt.sol"; - -contract Exchange is Initializable, UUPSUpgradeable, OwnableUpgradeable { - DMCToken dmcToken; - GWTToken gwtToken; - mapping (bytes32 => uint256) _mintDMC; - - address mintAdmin; - function initialize(address _dmcToken, address _gwtToken) public initializer { - __ExchangeUpgradable_init(_dmcToken, _gwtToken); - } - - function __ExchangeUpgradable_init(address _dmcToken, address _gwtToken) internal onlyInitializing { - __UUPSUpgradeable_init(); - __Ownable_init(msg.sender); - dmcToken = DMCToken(_dmcToken); - gwtToken = GWTToken(_gwtToken); - mintAdmin = msg.sender; - } - - function _authorizeUpgrade(address newImplementation) internal virtual override onlyOwner { - - } - - function setMintAdmin(address _mintAdmin) public onlyOwner { - mintAdmin = _mintAdmin; - } - - function allowMintDMC(address[] calldata mintAddr, string[] calldata cookie, uint256[] calldata amount) public { - require(msg.sender == mintAdmin, "not mint admin"); - for (uint i = 0; i < mintAddr.length; i++) { - _mintDMC[keccak256(abi.encode(mintAddr[i], cookie[i]))] = amount[i]; - } - } - - function gwtRate() public pure returns(uint256) { - // 1 : 210 - return 210; - } - - function exchangeGWT(uint256 amount) public { - uint256 gwtAmount = amount * gwtRate(); - dmcToken.transferFrom(msg.sender, address(this), amount); - gwtToken.mint(msg.sender, gwtAmount); - } - - function exchangeDMC(uint256 amount) public { - uint256 dmcAmount = amount / gwtRate(); - gwtToken.burnFrom(msg.sender, amount); - dmcToken.transfer(msg.sender, dmcAmount); - } - - function mintDMC(string calldata cookie) public { - bytes32 cookieHash = keccak256(abi.encode(msg.sender, cookie)); - require(_mintDMC[cookieHash] > 0, "cannot mint"); - dmcToken.mint(msg.sender, _mintDMC[cookieHash]); - _mintDMC[cookieHash] = 0; - } -} \ No newline at end of file diff --git a/contracts/exchange2.sol b/contracts/exchange2.sol index 267396e..517ffc0 100644 --- a/contracts/exchange2.sol +++ b/contracts/exchange2.sol @@ -15,6 +15,7 @@ contract Exchange2 is Initializable, UUPSUpgradeable, OwnableUpgradeable { address gwtToken; address fundationIncome; + bool enable_dmc_mint = false; // 当前周期,也可以看做周期总数 uint256 current_circle; uint256 current_mine_circle_start; @@ -40,6 +41,8 @@ contract Exchange2 is Initializable, UUPSUpgradeable, OwnableUpgradeable { uint256 adjust_period; uint256 initial_dmc_balance; + mapping(address=>bool) is_free_minted; + event newCycle(uint256 cycle_number, uint256 dmc_balance, uint256 start_time); event gwtRateChanged(uint256 new_rate, uint256 old_rate); event DMCMinted(address user, uint256 amount, uint256 remain); @@ -48,6 +51,7 @@ contract Exchange2 is Initializable, UUPSUpgradeable, OwnableUpgradeable { __UUPSUpgradeable_init(); __Ownable_init(msg.sender); __Exchange2Upgradable_init(_dmcToken, _gwtToken, _fundationIncome, _min_circle_time); + free_mint_balance = 210*5000*10**18; } function __Exchange2Upgradable_init(address _dmcToken, address _gwtToken, address _fundationIncome, uint256 _min_circle_time) public onlyInitializing { @@ -155,22 +159,58 @@ contract Exchange2 is Initializable, UUPSUpgradeable, OwnableUpgradeable { is_empty = true; } - emit DMCMinted(msg.sender, real_amount, remain_dmc_balance); - return (real_amount, is_empty); } + function addFreeMintBalance(uint256 amount) public onlyOwner { + require(amount > 0, "amount must be greater than 0"); + GWTToken2(gwtToken).transferFrom(msg.sender, address(this), amount); + } + + function freeMintGWT() public { + //每个地址只能free mint一次 + //每次成功得到210个GWT + require(free_mint_balance > 0, "no free mint balance"); + require(!is_free_minted[msg.sender], "already free minted"); + + GWTToken2(gwtToken).transferFrom(this, msg.sender, 210*10**18); + is_free_minted[msg.sender] = true; + } + function DMCtoGWT(uint256 amount) public { DMC2(dmcToken).burnFrom(msg.sender, amount); GWTToken2(gwtToken).mint(msg.sender, amount * dmc2gwt_rate * 6 / 5); } + function eanbleDMCMint() public onlyOwner { + enable_dmc_mint = true; + } + + function addFreeDMCTestMintBalance(uint256 amount) public onlyOwner { + require(!enable_dmc_mint, "dmc mint is enabled,test end"); + require(amount > 0, "amount must be greater than 0"); + DMC2(dmcToken).transferFrom(msg.sender, address(this), amount); + } + + function GWTToDMCForTest(uint256 ammount) public { + require(!enable_dmc_mint, "dmc mint is enabled,test end"); + require(ammount > 0, "ammount must be greater than 0"); + require(ammount % 210 == 0, "ammount must be multiple of 210"); + + dmc_ammount = ammount / 210; + DMC2(dmcToken).transferFrom(this, msg.sender, dmc_ammount); + } + function GWTtoDMC(uint256 amount) public { + require(enable_dmc_mint, "dmc mint not enabled"); + adjustExchangeRate(); uint256 dmc_count = amount / dmc2gwt_rate; console.log("exchange dmc %d from amount %d, rate %d", dmc_count, amount, dmc2gwt_rate); (uint256 real_dmc_amount, bool is_empty) = _decreaseDMCBalance(dmc_count); + require(real_dmc_amount > 0, "no dmc balance in current circle"); + uint256 real_gwt_amount = amount; if (is_empty) { //current_finish_time = block.timestamp; @@ -180,6 +220,7 @@ contract Exchange2 is Initializable, UUPSUpgradeable, OwnableUpgradeable { //不用立刻转给分红合约,而是等积累一下 GWTToken2(gwtToken).transferFrom(msg.sender, address(this), real_gwt_amount); DMC2(dmcToken).mint(msg.sender, real_dmc_amount); + emit DMCMinted(msg.sender, real_dmc_amount, remain_dmc_balance); } // 手工将累积的收入打给分红合约 diff --git a/contracts/gwt.sol b/contracts/gwt.sol deleted file mode 100644 index 8bbe7e0..0000000 --- a/contracts/gwt.sol +++ /dev/null @@ -1,58 +0,0 @@ -// SPDX-License-Identifier: MIT -pragma solidity ^0.8.0; - -import "@openzeppelin/contracts/token/ERC20/extensions/ERC20Burnable.sol"; -import "@openzeppelin/contracts/access/Ownable.sol"; -import "./dmc.sol"; - -contract GWTToken is ERC20Burnable, Ownable { - mapping (address => bool) allow_transfer; - mapping (address => bool) allow_minter; - - constructor() ERC20("Gb storage per Week Token", "GWT") Ownable(msg.sender) { - // enable mint and burn - allow_transfer[address(0)] = true; - } - - modifier onlyMinter() { - require(allow_minter[msg.sender], "mint not allowed"); - _; - } - - modifier canTransfer(address sender, address to) { - require(allow_transfer[sender] || allow_transfer[to], "transfer not allowed"); - _; - } - - function enableMinter(address[] calldata addresses) public onlyOwner { - for (uint i = 0; i < addresses.length; i++) { - allow_minter[addresses[i]] = true; - } - } - - function disableMinter(address[] calldata addresses) public onlyOwner { - for (uint i = 0; i < addresses.length; i++) { - allow_minter[addresses[i]] = false; - } - } - - function enableTransfer(address[] calldata addresses) public onlyOwner { - for (uint i = 0; i < addresses.length; i++) { - allow_transfer[addresses[i]] = true; - } - } - - function disableTransfer(address[] calldata addresses) public onlyOwner { - for (uint i = 0; i < addresses.length; i++) { - allow_transfer[addresses[i]] = false; - } - } - - function mint(address to, uint256 amount) public onlyMinter { - _mint(to, amount); - } - - function _update(address sender, address to, uint256 amount) internal override canTransfer(sender, to) { - super._update(sender, to, amount); - } -} diff --git a/contracts/gwt2.sol b/contracts/gwt2.sol index e449320..1bc7b26 100644 --- a/contracts/gwt2.sol +++ b/contracts/gwt2.sol @@ -6,9 +6,11 @@ import "@openzeppelin/contracts/access/Ownable.sol"; contract GWTToken2 is ERC20, Ownable { mapping (address => bool) allow_minter; + bool can_change_minter = true; constructor() ERC20("Gb storage per Week Token", "GWT") Ownable(msg.sender) { - + //给owner分配5000万Token + _mint(msg.sender, 50000000 * 10 ** uint(decimals())); } modifier onlyMinter() { @@ -16,13 +18,19 @@ contract GWTToken2 is ERC20, Ownable { _; } + function disableMinterChange() public onlyOwner { + can_change_minter = false; + } + function enableMinter(address[] calldata addresses) public onlyOwner { + require(can_change_minter, "mint change not allowed"); for (uint i = 0; i < addresses.length; i++) { allow_minter[addresses[i]] = true; } } function disableMinter(address[] calldata addresses) public onlyOwner { + require(can_change_minter, "mint change not allowed"); for (uint i = 0; i < addresses.length; i++) { allow_minter[addresses[i]] = false; } diff --git "a/doc/dmc20 \344\273\213\347\273\215.md" "b/doc/dmc20 \344\273\213\347\273\215.md" new file mode 100644 index 0000000..572cde2 --- /dev/null +++ "b/doc/dmc20 \344\273\213\347\273\215.md" @@ -0,0 +1,158 @@ +# DMC20介绍 + +## Token的基本概念与时间线 +DMC20由一系列Token组成,充分利用了L2的技术和比特币Ordinal协议。 +DMC(2.0):发行在 X Layer(由OKX构建的ETH L2)上的标准ERC20 Token,总量为7.9亿,其中50%可挖。,可以通过 X Layer的L2<->L1桥转换到ETH主链上,进而可以使用整个ETH生态。DMC2.0主合约部署在L2上,手续费使用OKB支付,比L1便宜很多。今天的DMC(1.0)将能通过中心化跨链桥转换成DMC2.0. DMC2.0的合约为Token Holder提供了更多的权益,其中最重要 +GWT(Gb per Week Token) : DMC体系的存储稳定币,代表1GB数据保存1周,无发行上限。首发在 XLayer上,未来计划在所有的EVM兼容链上发行。我们会和合作伙伴一起构建大量的垮链桥,以帮助GWT在所有的链之间流通。所有的存储挖矿的核心逻辑都是质押GWT获得有效空间,保存数据提交存储证明后获得有效存储算力,根据当前链的总存储算力的增长情况的到算力奖励的GWT。GWT我们希望能有更大量的交易,其价格也应该更稳定。 +DMCs:基于Ordinal 协议发行在BTC上,总量2.1亿。DMCs的目标是将更有价值的数据铭刻在BTC网络上,其经济模型独立与EVM兼容链体系。(已基本完成实现,具体实现本文未介绍) + + +### 时间线 +第一期:在XLayer上线4个关键合约,完成DMC20的体系的框架构建。(本周) +- DMC20主合约(ERC20) +- GWT@ETH合约(ERC20) +- DMC<->GWT合约,又可看作DMC20的挖矿合约。这个合约里包含早期的,测试用的限量free mint,用户可以花费很少的手续费得到GWT。 +- DMC分红合约,展现DMC的关键权益 +(上述均为有限可升级合约,当关闭升级能力后,这些合约的实现都会稳定下来不可修改) + +第二期:如果第一期的合约没有重大问题会部署2个重要的合约 (2周后) +- DMC1.0 -> DMC2.0 中心化跨链桥 +- 公共数据合约以及新版本的s.dmctech.io + +第三期:前两期都是做加法,这一期对现有DMC的挖矿生态有影响,需要慎重的计划时间点 (时间未定,6月底7月初?) +- 私有数据存储挖矿,用户可用通过质押GWT并出售自己的闲置空间获得GWT收入和GWT算力奖励。也可通过支付GWT获得备份空间,并得到GWT算力奖励。 + + +## DMC20<->GWT的兑换 +DMC20体系将Token分成了两大类。一大类是算力稳定币,我们计划发行3种:存储稳定币GWT,传输稳定币:MST,计算稳定币待定。另一大类是生态权益币DMC。 +该体系让我们能拥有面向未来的可扩展能力,同时也能继承今天的DMC共识,并通过分离 算力稳定币的挖矿和DMC的挖矿,实现更简单可靠的经济模型和更高水平的博弈。 + + +### 用GWT兑换未分配的DMC20 + +继承自DMC1.0的白皮书,待挖矿(待释放的)DMC20有一个确定的释放曲线: +``` +从2024年X月开始一共有420个周期,每个周期大概是现实世界的1周。每21个周期进行一次难度调整。难度调整后的周期释放的Token是上一个周期的80%。总释放是5*0.79 = 3.95亿DMC +第1-21周,每周最多释放768242枚DMC。随后的第21-42周,每周最多释放768242*0.8枚DMC +``` +每周开始,系统都会计算一次DMC:GWT的比例。在这个周期里,任何人都可以用这个比例将GWT换成DMC,直到换完。下个周期会根据上个周期的DMC兑换情况调整比例: +``` +上个周期的DMC都兑换完成了,则兑换比例提高:需要更多的GWT才能换到1个DMC +上个周期的DMC未兑换完成,则兑换比例下降:需要更少的GWT能换到1个DMC。 +兑换比例的提高和下降,不能超过20%,兑换比例的下限是210(至少需要210个GWT才能换1个DMC) +``` +用户用于兑换DMC的GWT不会销毁,而是成为DMC组织的收入并进入DMC的可分红利润池。 + +### 销毁DMC20得到GWT +用户销毁1个DMC,可以得到 兑换比例*1.2个GWT。 +这种机制形成了一个初看有利可图的套利盘:用户将GWT兑换成DMC,然后再销毁DMC得到GWT。这种套利操作是我刻意引导(鼓励)的早期用户行为。至少可以在挖矿未上线的情况下,用传统的DeFi流动性挖矿逻辑,能让一部分金融爱好者先互相博弈起来。 + + +## 基于DMC20的分红 +上面说到,用GWT兑换DMC的GWT不会被销毁,而是进入分红池。质押DMC并得到分红权是DMC20的一个重要权益,也是对DMC流动性控制的系统性设计。DMC20的分红权益的基本原理是 +``` +任何人都可以给DMC分红池打入收入(支持任何币种)。 +DMC20分红池的主要的收入来源是我们的一些重要的合约,这些合约会将一些行为里设计到的资产固定的作为利润打入分红池。 +根据利润进入分红池的时间,我们可以有不同的分红周期。用户质押DMC后,会按比例获得下一个周期的分红权。 +``` + +每条链上的GWT挖矿合约都会有一个利润池,随后通过基金会中心化机制跨链转移到XLayer上并打入DMC20的分红池。因此DMC20的持有者在质押DMC后,得到的是整个DMC生态利润的分红权 + +## DMC20 流动性挖矿的博弈 +是否需要销毁DMC20来获得GWT?早期GWT挖矿难度低,有更多的GWT质押能更快的获得更多算力,但是早期销毁DMC20得到的GWT相对较少。 +是否需要接触DMC20质押:用户手头的DMC20肯定会去进行质押挖矿(不然就白拿着了),但接触质押需要消耗手续费,而且会损失一个周期的分红权。会让用户犹豫,这种犹豫能潜在的减少DMC20的卖盘。 +是否需要将GWT换成DMC?早期兑换比例低,用更少的GWT就有机会换到DMC,但早期GWT的挖矿难度也低,更多的质押币能更快的获得竞争优势。G + +## GWT的挖矿 + +### 公共数据挖矿 (之前的dmcs的文章里有过介绍,这里不详细讲解) + +我们不提供公共数据挖矿的自动挖矿程序,公共数据挖矿更像是GWT持有者参与的另一个挖矿游戏。 + +从DApp的角度来说,公共数据挖矿的流程是: +1. 安装DApp +2. 在DApp的网页中配置存储空间 +3. 通过质押GWT来获得有效的公共数据存储空间,目前该值为有效空间*16*64,也就是说获得1G的有效公共数据存储空间,需要质押1024个GWT. +4. 浏览公共数据,选中公共数据后尝试SHOW Proof来获得GWT。该GWT一方面来自公告数据的余额,另一方面来自苏案例奖励 +5. SHOW Proof会冻结(质押)一定数量的GWT,数量和SHOW成功后的回报有关。用户可以在SHOW之前通过DAapp的界面来决定质押的数量 + 需要额外冻结的GWT一般与SHOW成功的回报有关。SHWO成功得到的回报越大,则需要冻结的GWT越多。 +6. SHOW Proof会将公共数据的MixHash加入用户的公共数据持有列表,并消耗有效公共数据存储空间。 +7. 用户可以随时释放公共数据来获得更多的有效公共数据存储空间 +8. 通过释放未使用的公共数据存储空间,用户可以拿回质押的GWT + +DApp提供了一系列API,可以让有技术的玩家制定更符合自己需要的策略来自动的玩公共数据存储挖矿,赢得更多的收益。 + + +### 私有数据挖矿(和DMC1.0的机制基本接近,核心区别就是整个流程只使用GWT) + +1. 出售存储资源的用户被称作供应商,可以挂卖单,卖单应详细的描述其存储资源的定义和价格。约定: + +- 存储总大小, +- 存储单价(定价单位从0.125到8,单位是GWT(Gb per Week Token) +- 存储有效时间(系统规定最小有效期为24周), +- 单次购买的最小大小(需小设置为2GB)。 +- 存储质量 + +2. 挂卖单时矿工用GWT缴纳质量保证金,用来在存储出现问题时赔付给用户。 + +``` +保证金总额 = 卖单总空间*有效时间*单价*质保比率 +``` + +系统有要求最小质保比率(0.5),用户可以根据实际的市场竞争调高该保证金。存储价格,质保比率会影响供应方在算力奖励里的比率。 +比如用户有100G空间,挂单24周,价格是2,质保比率是0.5,那么在挂单时,矿工需要准备好 `100*24*2*0.5 = 2400`个GWT的质保金。 + +3. 有需求的用户(需求方)可以选择一个已挂单购买自己需要的空间 + +``` +存储费用=需求空间*剩余有效时间*单价。 +``` +用户并不能选择有效时间。当一个存储订单的剩余有效时间小于系统的最小存储有效时间时,用户无法购买该存储订单。为了降低GAS Fee,用户在购买空间时应设置数据的MixHash. + +4. 已成交订单进入等待(数据)状态。数据交付的流程是需求方首先根据供应方公开的账号信息,通过链下协议将数据传输给供应商。供应商收到数据后调用合约接口确认该数据的MixHash。此后订单进入交付状态。 + +5. 未进入交付状态的订单双方都可以随时取消。取消后,用户会立刻得到退款,矿工则是回复了该存储订单的空间。 + +6. 订单进入交付状态12周后,可以调用“提现操作”,提现操作会让供应方得到收入+算力奖励GWT,让用户得到算力奖励GWT。此时系统会在该过程中抽取一部分交易费用GWT。 + +7. 矿工可以随时减少卖单的有效剩余空间。有效剩余空间减少后,矿工会得到退还的质保金。 + +8. 当矿工发现数据丢失时,可以主动声明数据丢失。此时只会赔付给用户质保金的80%,并拿回剩余的20%。 + +9. 供应方因为正常的技术理由可以申请订单请假(请假的频率基于有效长度有最大限制),订单请假期间其无法发起存储挑战。供应方可以在主动调用合约接口将请假状态的订单恢复正常。 + +10. 存储订单可以协议取消(一方发起,另一方同意),此时双方都会得到剩余交付时间的退款。 + +11. 使用官方的挖矿软件不会碰到其它异常情况,因此本文不讨论存储挑战等情况下的经济模型 + + +## MST的挖矿:传输挖矿 +MST 是 Mb per Sec Token的缩写。代表1秒1MB的数据传输。 + +### 用torrent发布公共数据 +1. 我们可以验证一个种子是否被发布到DMC网络 +2. 用户消耗手续费将种子发布到DMC网络 +3. 对tracker进行改造 + +### BT下载挖矿 +1. 用户发布种子,消耗GWT +2. 用户加速下载,消耗GWT +3. 用户A上传给用户B,用户B构造传输证明给A,随后用户A向Tracker提交传输证明成功,赚取GWT +4. 任何人都可以查询用户在某个文件上是否有足够的余额(每个用户都有通用余额和指定数据的专用余额) +5. 用户通过支付GWT,可以提高指定数据的余额 +6. 用户A通过定义提交种子文件的存储证明,可以得到公共数据挖矿的奖励 + + +## DMC20矿工的基本策略 + +1. 根据自己的存储能力,决定存储的价格和质押率 +2. 根据对挖矿奖励的期望,决定存储的价格和质押率 +3. 适当的挂出卖单,因为每次挂单都有可能真正的锁定GWT质押,因此需要谨慎操作 +4. 手工撤回为成交的卖单,已得到质押GWT的立刻退还 +5. 自动的处理存储订单的交付,也可手工处理黑名单用户的订单。 +6. 理解挖矿规则后,从节约手续费、锁定有利的系统参数、以及及时获得流动性3个角度在合适的时候提现。 +7. 在GWT全链转换的基础上,认证思考如何在不同链上挖矿(寻找挖矿难度更低的链),以及合适转移GWT + + + + diff --git a/doc/lucky mint protocol.md b/doc/lucky mint protocol.md new file mode 100644 index 0000000..bac6616 --- /dev/null +++ b/doc/lucky mint protocol.md @@ -0,0 +1,21 @@ +# lucky mint protocol + +## 摘要 + +1. 在Mint协议中增加了一个关键字“lucky”,用户可以在mint是保存一条自己的message +2. 带有lucky的mint称为lucky mint, lucky mint交易必须进入一个与用户地址相关的特定区块(可以称作幸运区块)才算lucky 生效 +3. lucky生效后,用户得到的奖励是amount * 10 + +## 详细设计 + +幸运区块的确定: +```python +def is_lucky_mint(self, sender_address, block_height): + address_num = btc_address_to_number(sender_address) + if (block_height + address_num) % 8 == 0: + return True + else: + return False +``` + +## diff --git "a/doc/\345\210\206\347\272\242\345\220\210\347\272\246.md" "b/doc/\345\210\206\347\272\242\345\220\210\347\272\246.md" new file mode 100644 index 0000000..049984f --- /dev/null +++ "b/doc/\345\210\206\347\272\242\345\220\210\347\272\246.md" @@ -0,0 +1,79 @@ +分红合约 +## 一些核心设计目标 +1. 手续费平衡,是指人们付出手续费是为了自己的利益,而不是强迫人们为了整体的利益付出手续费 +2. 符合经济学原理,用户可以自由的选择对自己来说利益最大化的操作 +3. 无额外开销,不能架设合约需要被持续的调用 + + +## 实现的思路 + +已结束周期:只能进行分红提现的操作 +当前周期:充值利润进入当前周期,用户不能对当前周期增加质押 +下一个周期:用户对下一个周期可以增加质押和减少质押 +上述概念可以通过cycle array + cycle_id 组合实现。 + +为了不用在每个cycle里保存全部用户质押的总数,可以用一个map(address=>array) g_stack_op_log 来保存特定用户的stack操作日志。改日志记录了用户调整自己质押币的cycle_id. + +## 接口实现 + +``` +func 增加质押(from,blance) { + 用户质押是为了获得更多的分红,用户增加质押后如未做操作,就可以获得后续的分红 + 当前周期的质押比例已经基本确定,但利润未定 +} + +func 减少质押(from,value) { + 从利益的角度,减少质押应该是立刻到账的 + 优先从未生效(下周期)中扣除质押 + 最好能实现从当前周期中减少质押:当前周期用户肯定没有提现,减少质押用户只是损失一些分红权 (当前周期的质押已经固定,不修改为好) +} + +func 分红提现(from,cycle_ids[],token_ids[]) { + 提现必然消耗手续费,所以要允许用户选择“划算的周期进行提现”,以及选择计划提现的tokenid(有一些不值钱的token就不提现了) + 只有已经结束的周期才能进行提现 + 分红提现: 利润 *(当前用户在当前周期的质押总数 / 当前周期的质押总数) +} + + +func 充值利润(from,token_addr,value) { + 向当前周期充值利润。 + 充值时会检测当前周期是否已经结束,如果已经结束则触发“移动到下个周期”,并将利润充值到正确的当前周期 +} + + +private func 得到用户指定周期的质押总数(from,cycle_id) { + op_log = g_stack_op_log[from] + last_op_cycle_id = get_last_op(op_log,cycle_id) + return all_cycles[last_op_cycle_id].staked[from] +} + +private func 移动到下个周期() { + next_cycle = all_cycles[g_current_cycle_id+1] + g_current_cycle_id = g_current_cycle_id + 1 + new_next_cycle = create_new_next_cycle(next_cycle) + all_cycles[g_current_cycle_id+1] = new_next_cycle +} + +``` + + + + +``` +func 增加质押(from,blance) { + 用户质押是为了获得下一个周期(未确认) +} + +func 减少质押(from,value) { + +} + +func 分红提现(from,cycle_ids[]) { + +} + +func 充值利润(from,token_addr,value) { + +} + +``` \ No newline at end of file