账户
0xa7...b0ee
0xa7...b0EE

0xa7...b0EE

$500
此合同的源代码已经过验证!
合同元数据
编译器
0.8.24+commit.e11b9ed9
语言
Solidity
合同源代码
文件 1 的 2:DungeonConfig.sol
// SPDX-License-Identifier: MIT
//
// DungeonConfig by DungeonMaster/@DungeonSpawner

pragma solidity ^0.8.0;

import "./Ownable.sol";

interface IDungeon {
    function ownerOf(uint256 tokenId) external view returns (address owner);
}

interface IDungeonRewards {
    function getStakedTokens(
        address owner
    )
        external
        view
        returns (uint256[] memory dungeons, uint256[] memory avatars);
}

contract DungeonConfig is Ownable {
    /*///////////////////////////////////////////////////////////////
                                 EVENTS
    //////////////////////////////////////////////////////////////*/
    event AssignPerms(
        address indexed from,
        address indexed to,
        uint256 indexed tokenId
    );
    event AssignGlobalPerms(address indexed from, address indexed to);
    event RevokeGlobalPerms(address indexed from, address indexed to);

    /*///////////////////////////////////////////////////////////////
                          METADATA STORAGE/LOGIC
    //////////////////////////////////////////////////////////////*/
    address public dungeonAddress;
    address public dungeonRewardsAddress;

    struct Permission {
        address grantedBy; // The address that granted the permission
        address grantedTo; // The address that received the permission
    }
    struct GlobalPermission {
        address grantedTo;
        address grantedBy;
    }

    struct FloorData {
        string txId; // transaction ID for the floor data
        uint256 version; // current version of the floor data (every resetAllFloors increments the version #, outdating previous entries)
    }

    struct DungeonData {
        bool locked;
        uint256 trialTimeout;
        bool randomizeLayout; // randomize layout indicates each floor after floor 1 has a random layout/environment
        string passwordHash; // DungeonMaster notes: not really secure, of course, since it's publically available, but the best we have
        string ownersMessage;
        string gameMode; // if starts with https:, NFT will redirect here after loading up (use for streaming via rumble, 3D client, etc)
    }

    // Mapping that tells us which version of the dungeon data we're dealing with 
    // (reseting all floors increments the version # -- saves us gas from having to clear all the existing dungeon floor data)
    mapping(uint256 => uint256) private tokenVersions;

    mapping(uint256 => DungeonData) public dungeons;
    // Mapping from tokenId to a mapping of floorNumber to FloorData
    mapping(uint256 => mapping(uint256 => FloorData)) public dungeonFloors;

    mapping(uint256 => Permission) public tokenPermissions;
    // Mapping to store global permissions
    mapping(address => GlobalPermission) public globalPermissions;

    string public BASE_CODE_TXID = ""; // base code txid

    constructor(address _dungeonAddress, address _dungeonRewardsAddress) {
        dungeonAddress = _dungeonAddress;
        dungeonRewardsAddress = _dungeonRewardsAddress;
    }

    modifier onlyOwnerOrStaker(uint256 tokenId) {
        IDungeon dungeon = IDungeon(dungeonAddress);
        address owner = dungeon.ownerOf(tokenId);
        bool isStaker = owner == dungeonRewardsAddress &&
            isTokenStakedByAddress(tokenId, msg.sender);

        require(
            owner == msg.sender || isStaker,
            "Caller is not owner or staker"
        );
        _;
    }

    function isTokenStakedByAddress(
        uint256 tokenId,
        address addressToCheck
    ) private view returns (bool) {
        IDungeonRewards rewards = IDungeonRewards(dungeonRewardsAddress);
        (uint256[] memory stakedDungeons, ) = rewards.getStakedTokens(
            addressToCheck
        );
        for (uint i = 0; i < stakedDungeons.length; i++) {
            if (stakedDungeons[i] == tokenId) {
                return true;
            }
        }
        return false;
    }

    modifier isPermitted(uint256 tokenId) {
        IDungeon dungeon = IDungeon(dungeonAddress);
        address owner = dungeon.ownerOf(tokenId);

        // First, check if msg.sender is the owner. If true, no further checks needed.
        if (
            owner == msg.sender ||
            (owner == dungeonRewardsAddress &&
                isTokenStakedByAddress(tokenId, msg.sender))
        ) {
            _;
            return;
        }

        // Global Permissions Check
        GlobalPermission memory globalPerm = globalPermissions[msg.sender];
        if (globalPerm.grantedTo == msg.sender) {
            bool isGlobalGrantorOwnerOrStaker = globalPerm.grantedBy == owner ||
                (owner == dungeonRewardsAddress &&
                    isTokenStakedByAddress(tokenId, globalPerm.grantedBy));

            if (isGlobalGrantorOwnerOrStaker) {
                _;
                return;
            }
        }

        // Only proceed to check permissions if the sender is not the owner or a staker.
        Permission memory permission = tokenPermissions[tokenId];
        bool hasValidPermission = permission.grantedTo == msg.sender &&
            (permission.grantedBy == owner ||
                (owner == dungeonRewardsAddress &&
                    isTokenStakedByAddress(tokenId, permission.grantedBy)));

        require(hasValidPermission, "Not authorized");

        _;
    }

    function writeDungeonConfig(
        uint256 tokenId,
        bool locked,
        uint256 trialTimeout,
        bool randomizeLayout,
        string memory passwordHash,
        string memory ownersMessage,
        string memory gameMode
    ) public isPermitted(tokenId) {
        DungeonData storage dungeon = dungeons[tokenId];
        dungeon.locked = locked;
        dungeon.trialTimeout = trialTimeout;
        dungeon.randomizeLayout = randomizeLayout;
        dungeon.passwordHash = passwordHash;
        dungeon.ownersMessage = ownersMessage;
        dungeon.gameMode = gameMode;
    }

    function readDungeonConfig(
        uint256 tokenId
    )
        public
        view
        returns (
            bool locked,
            uint256 trialTimeout,
            bool randomizeLayout,
            string memory passwordHash,
            string memory ownersMessage,
            string memory gameMode,
            string memory codeTxId
        )
    {
        DungeonData memory dungeon = dungeons[tokenId];
        return (
            dungeon.locked,
            dungeon.trialTimeout,
            dungeon.randomizeLayout,
            dungeon.passwordHash,
            dungeon.ownersMessage,
            dungeon.gameMode,
            BASE_CODE_TXID
        );
    }

    function writeFloorData(
        uint256 tokenId,
        uint256 floorNumber,
        string memory txId
    ) public isPermitted(tokenId) {
        // Store the transaction ID for the specific floor
        dungeonFloors[tokenId][floorNumber] = FloorData(txId, tokenVersions[tokenId]);
    }

    function readFloorData(
        uint256 tokenId,
        uint256 floorNumber
    ) public view returns (string memory) {
         FloorData memory floorData = dungeonFloors[tokenId][floorNumber];
        // Return the transaction ID for the specific floor
        if(floorData.version == tokenVersions[tokenId]) {
            return floorData.txId;
        } else {
            return ""; // Indicates no data for the current version
        }        
    }

    function resetAllFloors(uint256 tokenId) public isPermitted(tokenId) { 
        tokenVersions[tokenId] += 1; // Increment the version to "reset" the data
    }

    function assignPermission(
        uint256 tokenId,
        address to
    ) public onlyOwnerOrStaker(tokenId) {
        tokenPermissions[tokenId] = Permission(msg.sender, to);

        emit AssignPerms(msg.sender, to, tokenId);
    }

    // Assign global permission
    function assignGlobalPermission(address to) public {
        globalPermissions[to] = GlobalPermission({
            grantedTo: to,
            grantedBy: msg.sender
        });

        emit AssignGlobalPerms(msg.sender, to);
    }

    // Optional: Function to revoke global permission
    function revokeGlobalPermission(address to) public {
        require(
            globalPermissions[to].grantedBy == msg.sender,
            "Not authorized to revoke"
        );
        delete globalPermissions[to];
        emit RevokeGlobalPerms(msg.sender, to);
    }

    // set the ethscription txid of the base codebase
    function setBaseCodeTxid(string memory baseCodeTxid) public onlyOwner {
        BASE_CODE_TXID = baseCodeTxid;
    }

    // read the ethscription txid of the base codebase
    function getBaseCodeTxid() public view returns (string memory) {
        return BASE_CODE_TXID;
    }

}
合同源代码
文件 2 的 2:Ownable.sol
// SPDX-License-Identifier: AGPL-3.0-only
pragma solidity ^0.8.10;

error NotOwner();

// https://github.com/m1guelpf/erc721-drop/blob/main/src/LilOwnable.sol
abstract contract Ownable {
    address internal _owner;

    event OwnershipTransferred(
        address indexed previousOwner,
        address indexed newOwner
    );

    modifier onlyOwner() {
        require(_owner == msg.sender);
        _;
    }

    constructor() {
        _owner = msg.sender;
    }

    function owner() external view returns (address) {
        return _owner;
    }

    function transferOwnership(address _newOwner) external {
        if (msg.sender != _owner) revert NotOwner();

        _owner = _newOwner;
    }

    function renounceOwnership() public {
        if (msg.sender != _owner) revert NotOwner();

        _owner = address(0);
    }

    function supportsInterface(bytes4 interfaceId)
        public
        pure
        virtual
        returns (bool)
    {
        return interfaceId == 0x7f5828d0; // ERC165 Interface ID for ERC173
    }
}
设置
{
  "compilationTarget": {
    "DungeonConfig.sol": "DungeonConfig"
  },
  "evmVersion": "shanghai",
  "libraries": {},
  "metadata": {
    "bytecodeHash": "ipfs"
  },
  "optimizer": {
    "enabled": false,
    "runs": 200
  },
  "remappings": []
}
ABI
[{"inputs":[{"internalType":"address","name":"_dungeonAddress","type":"address"},{"internalType":"address","name":"_dungeonRewardsAddress","type":"address"}],"stateMutability":"nonpayable","type":"constructor"},{"inputs":[],"name":"NotOwner","type":"error"},{"anonymous":false,"inputs":[{"indexed":true,"internalType":"address","name":"from","type":"address"},{"indexed":true,"internalType":"address","name":"to","type":"address"}],"name":"AssignGlobalPerms","type":"event"},{"anonymous":false,"inputs":[{"indexed":true,"internalType":"address","name":"from","type":"address"},{"indexed":true,"internalType":"address","name":"to","type":"address"},{"indexed":true,"internalType":"uint256","name":"tokenId","type":"uint256"}],"name":"AssignPerms","type":"event"},{"anonymous":false,"inputs":[{"indexed":true,"internalType":"address","name":"previousOwner","type":"address"},{"indexed":true,"internalType":"address","name":"newOwner","type":"address"}],"name":"OwnershipTransferred","type":"event"},{"anonymous":false,"inputs":[{"indexed":true,"internalType":"address","name":"from","type":"address"},{"indexed":true,"internalType":"address","name":"to","type":"address"}],"name":"RevokeGlobalPerms","type":"event"},{"inputs":[],"name":"BASE_CODE_TXID","outputs":[{"internalType":"string","name":"","type":"string"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"address","name":"to","type":"address"}],"name":"assignGlobalPermission","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"uint256","name":"tokenId","type":"uint256"},{"internalType":"address","name":"to","type":"address"}],"name":"assignPermission","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[],"name":"dungeonAddress","outputs":[{"internalType":"address","name":"","type":"address"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"uint256","name":"","type":"uint256"},{"internalType":"uint256","name":"","type":"uint256"}],"name":"dungeonFloors","outputs":[{"internalType":"string","name":"txId","type":"string"},{"internalType":"uint256","name":"version","type":"uint256"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"dungeonRewardsAddress","outputs":[{"internalType":"address","name":"","type":"address"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"uint256","name":"","type":"uint256"}],"name":"dungeons","outputs":[{"internalType":"bool","name":"locked","type":"bool"},{"internalType":"uint256","name":"trialTimeout","type":"uint256"},{"internalType":"bool","name":"randomizeLayout","type":"bool"},{"internalType":"string","name":"passwordHash","type":"string"},{"internalType":"string","name":"ownersMessage","type":"string"},{"internalType":"string","name":"gameMode","type":"string"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"getBaseCodeTxid","outputs":[{"internalType":"string","name":"","type":"string"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"address","name":"","type":"address"}],"name":"globalPermissions","outputs":[{"internalType":"address","name":"grantedTo","type":"address"},{"internalType":"address","name":"grantedBy","type":"address"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"owner","outputs":[{"internalType":"address","name":"","type":"address"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"uint256","name":"tokenId","type":"uint256"}],"name":"readDungeonConfig","outputs":[{"internalType":"bool","name":"locked","type":"bool"},{"internalType":"uint256","name":"trialTimeout","type":"uint256"},{"internalType":"bool","name":"randomizeLayout","type":"bool"},{"internalType":"string","name":"passwordHash","type":"string"},{"internalType":"string","name":"ownersMessage","type":"string"},{"internalType":"string","name":"gameMode","type":"string"},{"internalType":"string","name":"codeTxId","type":"string"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"uint256","name":"tokenId","type":"uint256"},{"internalType":"uint256","name":"floorNumber","type":"uint256"}],"name":"readFloorData","outputs":[{"internalType":"string","name":"","type":"string"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"renounceOwnership","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"uint256","name":"tokenId","type":"uint256"}],"name":"resetAllFloors","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"address","name":"to","type":"address"}],"name":"revokeGlobalPermission","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"string","name":"baseCodeTxid","type":"string"}],"name":"setBaseCodeTxid","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"bytes4","name":"interfaceId","type":"bytes4"}],"name":"supportsInterface","outputs":[{"internalType":"bool","name":"","type":"bool"}],"stateMutability":"pure","type":"function"},{"inputs":[{"internalType":"uint256","name":"","type":"uint256"}],"name":"tokenPermissions","outputs":[{"internalType":"address","name":"grantedBy","type":"address"},{"internalType":"address","name":"grantedTo","type":"address"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"address","name":"_newOwner","type":"address"}],"name":"transferOwnership","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"uint256","name":"tokenId","type":"uint256"},{"internalType":"bool","name":"locked","type":"bool"},{"internalType":"uint256","name":"trialTimeout","type":"uint256"},{"internalType":"bool","name":"randomizeLayout","type":"bool"},{"internalType":"string","name":"passwordHash","type":"string"},{"internalType":"string","name":"ownersMessage","type":"string"},{"internalType":"string","name":"gameMode","type":"string"}],"name":"writeDungeonConfig","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"uint256","name":"tokenId","type":"uint256"},{"internalType":"uint256","name":"floorNumber","type":"uint256"},{"internalType":"string","name":"txId","type":"string"}],"name":"writeFloorData","outputs":[],"stateMutability":"nonpayable","type":"function"}]