{"content":{"title":"Solidity实战系列 - (NFT - 质押 - 锁仓 - ERC1155 - 兑换)","body":"这一多月写了一份NFT合约，合约的代码上线至币安主网链，分别是ERC1155类型的NFT合约，质押合约，以及兑换合约。合约地址如下：NanoGen半同质化代币合约(\\\r\n0x33e63F9A797A10733e3FB0d1C34C199f7640BFc8)；PledgeNano质押合约(0x3Ad056433E8Ea143ea03fDeb225f33A115f09549)；Exchange兑换合约(0x5883f32cc7151BCc016075448B6534bc1f506235)。\r\n\r\n大家可以打开浏览器的源码，我们来分析下源码。\r\n\r\n首先，NanoGen合约属于ERC1155类型的NFT合约，该合约的主要功能是Manage方法，通过这个方法，我们可以看到代码中同样是ERC1155类型的NFT代币Next被销毁，作为ERC20代币类型的NT被划转到质押合约中，而Nano的数量进行了增加，但是并没有实质上的被铸造出来。这个方法仅仅作为管理员管控代币分配的入口。\r\n\r\n```\r\nnextGenContract.burn(msg.sender, nextGenId, amount); \r\nNanoAmount += amount * 100;\r\nNanoAllAmounts += amount * 100;\r\nntCoinContract.transferFrom(msg.sender, address(nanoStakingContract), amount * 10000 * 1000000000000000000);\r\n```\r\n\r\n其次，合约中有一个购买的方法PurchaseNano，提供给用户去购买合约中的Nano。用户通过输入要购买的Nano的数量，合约自动计算需要多少BUSD代币可以来购买对标的Nano。\r\n\r\n```\r\nbusdContract.transferFrom(msg.sender, address(this), needMoney); \r\n_mint(msg.sender, nanoGenId, _buyNanoAmount, \"\");\r\nNanoAmount -= _buyNanoAmount;\r\n```\r\n\r\n最后，合约中提供了一个GiveNano的方法来给市场钱包地址直接铸造Nano。\r\n\r\n```\r\n_mint(_marketAccount, nanoGenId, _amount, \"\");\r\nNanoAmount -= _amount;\r\n```\r\n\r\n其余的方法，都是设置更新合约地址，Nano价格，查看剩余Nano数量的一些方法。看完这个合约之后，如果有什么疑惑的，可以打在评论区，看到会回复哦。\r\n\r\n接下来是质押合约。\r\n\r\n质押合约的逻辑比较复杂，也是比较头疼的地方，同样的，我们看着合约的源码来学习一下。这份合约主要是实现用户可以将购买的Nano进行质押后，得到项目方的NT代币，然后合约将NT代币进行锁仓，并自动释放处理。在释放的过程中，如果该用户有下级绑定了它并且也进行了质押操作，那么该用户可以立即释放一定数量的NT代币，当剩余释放的数量不满足一定的条件时，又是做额外的处理。\r\n\r\n在这份合约中，我们首先看自动释放的逻辑accumulateRealease2方法，在这个方法中，我们判断了一系列的逻辑让其满足时间不超过我们设定好的质押周期，并且判断用户剩余可释放的NT的数量，并且该方法按照一定的方式一直在获取daspassed的值。\r\n\r\n```\r\n        uint256 daysPassed = (block.timestamp - userLockInfo.releaseStartTime) / releaseInterval;\r\n        uint256 accumulatedRelease = 0;\r\n\r\n        if (daysPassed < lockDuration && userLockInfo.norelease >= fixSpeedAmount) {\r\n            accumulatedRelease = daysPassed * releaseNtTokenPerDay * userLockInfo.ntAmounts / 100 / 10**18 + userLockInfo.speedCount * fixSpeedAmount;\r\n        }\r\n\r\n        if(daysPassed < lockDuration && userLockInfo.norelease < fixSpeedAmount && isSpeedCountMarked[_player][_stakeId][userLockInfo.speedCount] == false) {\r\n            accumulatedRelease = daysPassed * releaseNtTokenPerDay * userLockInfo.ntAmounts / 100 / 10**18;\r\n        }\r\n\r\n        if(daysPassed < lockDuration && userLockInfo.norelease < fixSpeedAmount && isSpeedCountMarked[_player][_stakeId][userLockInfo.speedCount] == true) {\r\n            accumulatedRelease = userLockInfo.ntAmounts;\r\n        }\r\n```\r\n\r\n有了自动计算的方法后，我们看绑定的方法bind。这个方法看着比较简单，但是里面有一个逻辑我们要注意的是绑定的状态。具体我们可以详细看一下每一个代码的步骤的含义。\r\n\r\n```\r\n        if(msg.sender != _child) {\r\n            revert ErrorMsg(\"Only the child can call this method\");\r\n        }\r\n\r\n        if(_parent == _child) {\r\n            revert ErrorMsg(\"Parent and child cannot be the same address\");\r\n        }\r\n\r\n        if(_parent == address(0x0) || _child == address(0x0)) {\r\n            revert ErrorMsg(\"account isn't zero address\");\r\n        }\r\n\r\n        if(hasParent[_child] == true) {\r\n            revert ErrorMsg(\"_child only had one parent just\");\r\n        }\r\n\r\n        if(pchasbindornot[_parent][_child] == true || pchasbindornot[_child][_parent] == true) {\r\n            revert ErrorMsg(\"these two accounts had binded before\");\r\n        }\r\n\r\n        address[] storage children = parentToChilds[_parent];\r\n\r\n        for (uint256 i = 0; i < children.length; i++) {\r\n            if(children[i] == _child) {\r\n                revert ErrorMsg(\"Child already exists in parent's array\");\r\n            }\r\n        }\r\n\r\n        parentToChilds[_parent].push(_child);\r\n\r\n        childToParent[_child] = _parent;\r\n\r\n        hasParent[_child] = true;\r\n\r\n        pchasbindornot[_parent][_child] = true;\r\n        pchasbindornot[_child][_parent] = true;\r\n```\r\n\r\n绑定方法完成后，我们看子级为用户加速的方法speedForParent。在该方法中，我们要判断父子之间是否有绑定，子是否有铸造，只有满足了这两个条件，父才能立即释放NT。\r\n\r\n```\r\n if (hasParent[msg.sender] == true && isStaked[_parent] == true) {\r\n            uint256[] memory parentStakeIds = accountHasStakeIdAmounts[_parent];\r\n            bool accelerateParent = false; \r\n\r\n            for (uint256 i = 0; i < parentStakeIds.length; i++) {\r\n                uint256 parentStakeId = parentStakeIds[i];\r\n                \r\n                LockInfo storage parentLock = stakes[_parent][parentStakeId];\r\n\r\n                uint256 totalSpeedCount = parentLock.speedCount + _amount;\r\n                uint256 canSpeedCount = parentLock.ntAmounts / fixSpeedAmount;\r\n\r\n                (,,,,,,uint256 norelease,,,,) = getUserStakeInfo(_parent, parentStakeId);\r\n\r\n                if (norelease > 0) {\r\n                    if (norelease >= fixSpeedAmount * _amount) {\r\n                        parentLock.hasreleaseall += fixSpeedAmount * _amount;\r\n                        parentLock.norelease = parentLock.ntAmounts - parentLock.hasreleaseall;\r\n                    } else {\r\n                        parentLock.hasreleaseall = parentLock.norelease;\r\n                        parentLock.norelease = 0;\r\n                    }\r\n                    \r\n                    if(totalSpeedCount > canSpeedCount) {\r\n                        parentLock.speedCount = canSpeedCount;\r\n                    }else {\r\n                        parentLock.speedCount += _amount;\r\n                    }\r\n\r\n                    isSpeedCountMarked[_parent][parentStakeId][parentLock.speedCount] = true;\r\n                    accelerateParent = true;\r\n                    break;\r\n                }\r\n            }\r\n        }      \r\n```\r\n\r\n当这些方法实现之后，我们就可以来写质押的方法NanoStaking，通过这个方法，用户可以将Nano质押在合约中。\r\n\r\nclaimNtToken方法可以让用户领取属于自己的NT代币。\r\n\r\n这份合约的复杂点比较多，功能需求模块细节也比较多，可以好好的精读一下这份合约，好好揣清里面的逻辑，对solidity合约开发来说，会是比较大的提升。\r\n\r\n最后，我们看一下兑换合约Exchange。该合约的功能比较简单。只是当时由于NanoGen合约自带的安全转账方法无法满足业务需求，故直接采用其setApproveForAll方法。通过这个方法给兑换合约授权，而后通过一个指定的approver来给用户转Nano代币，具体的实现方法是flash。\r\n\r\n```\r\n ntCoinContract.transferFrom(msg.sender, address(this), ntReserve);\r\n nanoGenContract.safeTransferFrom(approver, msg.sender,nanoGenId, _nanoAmount,\"\");\r\n```\r\n\r\n好了，这就是我这一个多月以来编写的三份合约。大家可以好好学习下里面的代码编写方式，逻辑，以及代币之间的转让等，希望大家能有收获。\r\n\r\n有任何问题，评论区见！"},"author":{"user":"https://learnblockchain.cn/people/4335","address":null},"history":null,"timestamp":1700033931,"version":1}