繁体   English   中英

在solidity Struct中保存block.timestamp,但函数返回当前时间的时间戳

[英]Getting saved block.timestamp in solidity Struct, but function is returning timestamp of current time

这是我第一次在 stackoverflow 上提问,所以我希望我在这里提供了所有需要的信息

所以我写了一个可靠的智能合约来质押 nfts,质押部分运行良好但解除质押部分不是,我试图编写解除质押功能,以便所有者只有在一定时间过去后才能解除质押他们的 nfts,这次是一个 uint48 值,存储在 stakeNft 的一个solidity Struct 中:

    struct Stake {
        uint24 tokenId;
        uint48 timestamp; <------
        address owner;
    }

这是质押功能:

    function stake(uint256[] calldata tokenIds) external {
        IERC721N nft = IERC721N(NftAddress);
        uint256 tokenId;
        totalStaked += tokenIds.length;
        for (uint256 i = 0; i < tokenIds.length; i++) {
            tokenId = tokenIds[i];
            require(nft.ownerOf(tokenId) == msg.sender, "not your token");
            require(vault[tokenId].tokenId == 0, "already staked");

            nft.transferFrom(msg.sender, address(this), tokenId);
            emit BlockStaked(msg.sender, tokenId, block.timestamp);

            vault[tokenId] = Stake({
                owner: msg.sender,
                tokenId: uint24(tokenId),
                timestamp: uint48(block.timestamp)
            });
        }
    }

这是Unstaking功能:

    function _unstakeMany(address account, uint256[] calldata tokenIds)
        internal
    {
        IERC721N nft = IERC721N(NftAddress);
        // uint256 tokenId;
        Stake memory staked;
        totalStaked -= tokenIds.length;
        for (uint256 i = 0; i < tokenIds.length; i++) {
            // tokenId = tokenIds[i];
            staked = vault[tokenIds[i]];
            uint256 timeStamp = stakeStamp(tokenIds[i]);
            require(staked.owner == msg.sender, "not an owner");
            if(block.timestamp < timeStamp + 60){
                revert timeError(timeStamp, tokenIds[i]);
            }
            delete vault[tokenIds[i]];
            emit BlockUnstaked(account, tokenIds[i], block.timestamp);
            nft.transferFrom(address(this), account, tokenIds[i]);
            
        }
    }

这是完整的代码:

// SPDX-License-Identifier: MIT LICENSE

pragma solidity ^0.8.9;

import "@openzeppelin/contracts/access/Ownable.sol";
import "@openzeppelin/contracts/token/ERC721/IERC721Receiver.sol";
import "@openzeppelin/contracts/token/ERC721/IERC721.sol";
import "@openzeppelin/contracts/token/ERC20/IERC20.sol";
import "@openzeppelin/contracts/utils/cryptography/ECDSA.sol";
import "@openzeppelin/contracts/utils/math/SafeMath.sol";


interface IERC20N is IERC20 {
    function mint(address to, uint256 amount) external;
}

interface IERC721N is IERC721 {
    function totalSupply() external view returns (uint256);
}

contract Vault is Ownable, IERC721Receiver {
    using SafeMath for uint256;
    uint256 public totalStaked;
    
    // struct to store a stake's token, owner, and earning values
    struct Stake {
        uint24 tokenId;
        uint48 timestamp;
        address owner;
    }

    event BlockStaked(address owner, uint256 tokenId, uint256 value);
    event BlockUnstaked(address owner, uint256 tokenId, uint256 value);
    event Claimed(address owner, uint256 amount);

    // maps tokenId to stake
    mapping(uint256 => Stake) public vault;

    // initialising Nft cotract and coin contract
    address public NftAddress;
    address public TokenAddress;

    // IERC721N nft1 = IERC721N(NftAddress);
    // IERC20N token = IERC20N(TokenAddress);

    error timeError(uint256 timeleft, uint256 tokenId);
    // error timeError(uint256 timeleft, uint256 blockStamp, uint256 tokenId);

    constructor() {}

    function setNftAddress(address _address) public onlyOwner {
        NftAddress = _address;
    }

    function setTokenAddress(address _address) public onlyOwner {
        TokenAddress = _address;
    }

    function stake(uint256[] calldata tokenIds) external {
        IERC721N nft = IERC721N(NftAddress);
        uint256 tokenId;
        totalStaked += tokenIds.length;
        for (uint256 i = 0; i < tokenIds.length; i++) {
            tokenId = tokenIds[i];
            require(nft.ownerOf(tokenId) == msg.sender, "not your token");
            require(vault[tokenId].tokenId == 0, "already staked");

            nft.transferFrom(msg.sender, address(this), tokenId);
            emit BlockStaked(msg.sender, tokenId, block.timestamp);

            vault[tokenId] = Stake({
                owner: msg.sender,
                tokenId: uint24(tokenId),
                timestamp: uint48(block.timestamp)
            });
        }
    }


    uint256 public TIMe;



    function _unstakeMany(address account, uint256[] calldata tokenIds)
        internal
    {
        // IERC721N nft = IERC721N(NftAddress);
        // uint256 tokenId;
        Stake memory staked;
        totalStaked -= tokenIds.length;
        for (uint256 i = 0; i < tokenIds.length; i++) {
            // tokenId = tokenIds[i];
            staked = vault[tokenIds[i]];
            uint256 timeStamp = stakeStamp(tokenIds[i]);
            require(staked.owner == msg.sender, "not an owner");
            if(block.timestamp < timeStamp + 60){
                revert timeError(timeStamp, tokenIds[i]);
            }
            delete vault[tokenIds[i]];
            emit BlockUnstaked(account, tokenIds[i], block.timestamp);
            // nft.transferFrom(address(this), account, tokenIds[i]);
            
        }
    }

    function blockStamp() public view returns(uint256){
        return block.timestamp;
    }

    function stakeStamp(uint256 id) public view returns(uint256){
        return vault[id].timestamp;
    }


    function unstake(uint256[] calldata tokenIds) external {
        _claim(msg.sender, tokenIds, true);
    }

    function _claim(
        address account,
        uint256[] calldata tokenIds,
        bool _unstake
    ) internal {
        uint256 tokenId;
        uint256 earned = 0;
        IERC20N token = IERC20N(TokenAddress);

        for (uint256 i = 0; i < tokenIds.length; i++) {
            tokenId = tokenIds[i];

            Stake memory staked = vault[tokenId];
            require(staked.owner == account, "not an owner");

            uint256 stakedAt = staked.timestamp;

            vault[tokenId] = Stake({
                owner: account,
                tokenId: uint24(tokenId),
                timestamp: uint48(block.timestamp)
            });

            if (block.timestamp - stakedAt > 300) {
                earned += 1000 ether;
            }
        }
        if (earned > 0) {
            token.mint(msg.sender, earned);
        }
        if (_unstake) {
            _unstakeMany(account, tokenIds);
        }
        emit Claimed(account, earned);
    }

    function timeFromStaked(uint256[] calldata tokenIds)
        public
        view
        returns (uint256[] memory)
    {
        uint256[] memory list = new uint256[](tokenIds.length);

        for (uint256 i = 0; i < tokenIds.length; i++) {
            uint256 tokenId = tokenIds[i];
            Stake memory staked = vault[tokenId];
            uint256 stakedAt = staked.timestamp;
            list[i] = uint48(block.timestamp) - stakedAt;
        }
        return list;
    }

    // should never be used inside of transaction because of gas fee
    function balanceOf(address account) public view returns (uint256) {
        IERC721N nft = IERC721N(NftAddress);
        uint256 balance = 0;
        uint256 supply = nft.totalSupply();
        for (uint256 i = 1; i <= supply; i++) {
            if (vault[i].owner == account) {
                balance += 1;
            }
        }
        return balance;
    }

    // should never be used inside of transaction because of gas fee
    function tokensOfOwner(address account)
        public
        view
        returns (uint256[] memory ownerTokens)
    {
        IERC721N nft = IERC721N(NftAddress);
        uint256 supply = nft.totalSupply();
        uint256[] memory tmp = new uint256[](supply);

        uint256 index = 0;
        for (uint256 tokenId = 1; tokenId <= supply; tokenId++) {
            if (vault[tokenId].owner == account) {
                tmp[index] = vault[tokenId].tokenId;
                index += 1;
            }
        }

        uint256[] memory tokens = new uint256[](index);
        for (uint256 i = 0; i < index; i++) {
            tokens[i] = tmp[i];
        }

        return tokens;
    }

    function onERC721Received(
        address,
        address from,
        uint256,
        bytes calldata
    ) external pure override returns (bytes4) {
        require(from == address(0x0), "Cannot send nfts to Vault directly");
        return IERC721Receiver.onERC721Received.selector;
    }
}

在我在 ganache-cli 上运行它并执行初始化合约所需的步骤后,我质押了一个 nft

然后一段时间后,我在本地区块链上进行另一笔交易以更新 block.timestamp 值并尝试取消抵押

当我尝试在时间过去之前取消抵押时,还原的 timeError 返回相应抵押的时间戳的值,但它不是正确的值,因为每次我运行取消抵押函数时它总是在变化,并且它总是等于 block.timestamp 值

这个时间戳值是使用一个名为 stakeStamp 的函数获取的,stakeStamp 函数总是从结构中返回正确的值,但是每当我在 unstake 函数中使用它时,它都会返回 block.timestamp 值而不是结构中保存的时间戳

这是 stakeStamp 函数:

    function stakeStamp(uint256 id) public view returns(uint256){
        return vault[id].timestamp;
    }

你可以在第三个代码块的上面的 unstake 函数中检查我是如何使用它的

我希望我提供了有关该问题的良好信息。

首先,我认为您应该进行一些自动化测试,以确定导致此时间戳错误的原因。 我已经为你做了一些,我将附加到这个答案中。

在你的合同中有些事情可能会更好。

例如,在第 99 行(至少在我的编辑器中经过一些 linting)

            if(block.timestamp < timeStamp + 60){
                revert timeError(timeStamp, tokenIds[i]);
            }

block.timestamp是一个不断增长的值。 它是 EVM 的时间戳,所以它以秒为单位增加。 检查block.timestamp < timeStamp + 60将在调用stake() 60 秒后抛出错误。 也许您应该将其重写为

            require(block.timestamp > timeStamp + 60,"not past due time");

由于阻塞计时器似乎很小,我假设您没有使用测试环境并在 Remix 或其他东西上测试合约。

我已经使用 Hardhat 对您的合同进行了一些测试,并制作了一个您可以访问的要点。 看一看:

https://remix.ethereum.org/#version=soljson-v0.8.7+commit.e28d00a7.js&optimize=false&runs=200&gist=3ad1a14ab5ad4ec5aca16ae6414ffb67

它基本上测试了 Vault 是否可以质押和取消质押。

关于你在这一点上的问题:

当我尝试在时间过去之前取消抵押时,还原的 timeError 返回相应抵押的时间戳的值,但它不是正确的值,因为每次我运行取消抵押函数时它总是在变化,并且它总是等于 block.timestamp 值

在调用函数的那一刻,您将抵押到特定的block.timestamp 它是静态的,不会改变。 我完全不明白你的意思。 此外,从timeStamp stakeStamp()返回的 timeStamp 始终与block.timestmap不同。 更具体地说,它总是低于block.timestmap ,因为目的是设置它stake()并在unstake()上检查它

此外,是否有任何特定原因使用uint48作为时间戳? 鉴于 Solidity 文档中声明它是一个uint256 ,我不知道这个演员表是否有任何不需要的行为。 可能不会,因为时间戳以秒为单位,并且完全适合uint48

从 IERC721 导入safeTransferFrom函数。 在 Stake 和_unstakeMany函数中使用safeTransferFrom函数。 如果您没有得到解决方案,只需在推特上联系我(@bhargav_dasappa)。

暂无
暂无

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM