简体   繁体   English

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

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

This is my 1st time asking a question on stackoverflow so i hope i provided all the information needed here这是我第一次在 stackoverflow 上提问,所以我希望我在这里提供了所有需要的信息

So i wrote a solidity smart contract to stake nfts, the staking part is working well but the unstaking part is not, im trying to write the unstaking function so that the owner will only be able to unstake their nfts if a certain time has passed, this time is a uint48 value stored in a solidity Struct of the stakeNft:所以我写了一个可靠的智能合约来质押 nfts,质押部分运行良好但解除质押部分不是,我试图编写解除质押功能,以便所有者只有在一定时间过去后才能解除质押他们的 nfts,这次是一个 uint48 值,存储在 stakeNft 的一个solidity Struct 中:

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

This is the staking function:这是质押功能:

    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)
            });
        }
    }

And this is the Unstaking function:这是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]);
            
        }
    }

This is the full code:这是完整的代码:

// 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;
    }
}

after i run it on ganache-cli and do the steps required to initialize the contracts i stake one nft在我在 ganache-cli 上运行它并执行初始化合约所需的步骤后,我质押了一个 nft

then after sometime i do another transaction on my local blockchain to update the block.timestamp value and try to unstake然后一段时间后,我在本地区块链上进行另一笔交易以更新 block.timestamp 值并尝试取消抵押

when i try to unstake before the time passes the reverted timeError returns the value of the timestamps of the corresponding stake, but its not the right value because its always changing everytime i run the unstake function, and its always equal to the block.timestamp value当我尝试在时间过去之前取消抵押时,还原的 timeError 返回相应抵押的时间戳的值,但它不是正确的值,因为每次我运行取消抵押函数时它总是在变化,并且它总是等于 block.timestamp 值

this time stamp value is fetched using a function called stakeStamp, the stakeStamp function always returns the right value from the struct, but whenever i use it in the unstake function it returns the block.timestamp value instead of the saved timeestamp in the struct这个时间戳值是使用一个名为 stakeStamp 的函数获取的,stakeStamp 函数总是从结构中返回正确的值,但是每当我在 unstake 函数中使用它时,它都会返回 block.timestamp 值而不是结构中保存的时间戳

this is the stakeStamp function:这是 stakeStamp 函数:

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

u can check how i used it in the unstake function up above in the 3rd code block你可以在第三个代码块的上面的 unstake 函数中检查我是如何使用它的

i hope i provided good information on the problem.我希望我提供了有关该问题的良好信息。

First of all, I think you should make some automated tests in order to identify what is causing this timestamp bug.首先,我认为您应该进行一些自动化测试,以确定导致此时间戳错误的原因。 I've made some for you already that I will be appending to this answer.我已经为你做了一些,我将附加到这个答案中。

There are some things that could be better in your contract.在你的合同中有些事情可能会更好。

For example, in line 99 (at least in my editor after some linting)例如,在第 99 行(至少在我的编辑器中经过一些 linting)

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

block.timestamp is a value that keeps growing. block.timestamp是一个不断增长的值。 It's the timestamp of the EVM, so it's increasing by the seconds.它是 EVM 的时间戳,所以它以秒为单位增加。 checking block.timestamp < timeStamp + 60 will throw error after 60 seconds of calling stake() .检查block.timestamp < timeStamp + 60将在调用stake() 60 秒后抛出错误。 Maybe you should rewrite it to也许您应该将其重写为

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

As the blocking timers seem to be small, I assume you are not using a testing environment and testing the contract on Remix or something else.由于阻塞计时器似乎很小,我假设您没有使用测试环境并在 Remix 或其他东西上测试合约。

I've made some testing on your contract using Hardhat and made a gist that you can access.我已经使用 Hardhat 对您的合同进行了一些测试,并制作了一个您可以访问的要点。 Take a look:看一看:

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

It basically tests if the Vault can stake and unstake.它基本上测试了 Vault 是否可以质押和取消质押。

Regarding to your question in this point:关于你在这一点上的问题:

when i try to unstake before the time passes the reverted timeError returns the value of the timestamps of the corresponding stake, but its not the right value because its always changing everytime i run the unstake function, and its always equal to the block.timestamp value当我尝试在时间过去之前取消抵押时,还原的 timeError 返回相应抵押的时间戳的值,但它不是正确的值,因为每次我运行取消抵押函数时它总是在变化,并且它总是等于 block.timestamp 值

You stake to a specific block.timestamp in the moment the function was called.在调用函数的那一刻,您将抵押到特定的block.timestamp It is static, doesn't change.它是静态的,不会改变。 I quite doesn't understand what you mean there.我完全不明白你的意思。 Also, timeStamp returned from the stakeStamp() is always different from block.timestmap .此外,从timeStamp stakeStamp()返回的 timeStamp 始终与block.timestmap不同。 More specifically, it's always lower than block.timestmap since the intention is to set it stake() and check it on unstake()更具体地说,它总是低于block.timestmap ,因为目的是设置它stake()并在unstake()上检查它

Also, is there any specific reason to use uint48 as the timestamp?此外,是否有任何特定原因使用uint48作为时间戳? Given that it is stated in the Solidity documentation that it is an uint256 I don't know if this cast has any unwanted behavior.鉴于 Solidity 文档中声明它是一个uint256 ,我不知道这个演员表是否有任何不需要的行为。 Probably not, given that the timestamp is in seconds and it would perfectly fit in a uint48 .可能不会,因为时间戳以秒为单位,并且完全适合uint48

Import safeTransferFrom function from IERC721.从 IERC721 导入safeTransferFrom函数。 use safeTransferFrom function in stake and _unstakeMany functions.在 Stake 和_unstakeMany函数中使用safeTransferFrom函数。 if you dont get solution , just ping me in twitter(@bhargav_dasappa).如果您没有得到解决方案,只需在推特上联系我(@bhargav_dasappa)。

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

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