簡體   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