creation_sourcecode: {"DungeonConfig.sol":{"content":"// SPDX-License-Identifier: MIT\r\n//\r\n// DungeonConfig by DungeonMaster/@DungeonSpawner\r\n\r\npragma solidity ^0.8.0;\r\n\r\nimport \"./Ownable.sol\";\r\n\r\ninterface IDungeon {\r\n    function ownerOf(uint256 tokenId) external view returns (address owner);\r\n}\r\n\r\ninterface IDungeonRewards {\r\n    function getStakedTokens(\r\n        address owner\r\n    )\r\n        external\r\n        view\r\n        returns (uint256[] memory dungeons, uint256[] memory avatars);\r\n}\r\n\r\ncontract DungeonConfig is Ownable {\r\n    /*///////////////////////////////////////////////////////////////\r\n                                 EVENTS\r\n    //////////////////////////////////////////////////////////////*/\r\n    event AssignPerms(\r\n        address indexed from,\r\n        address indexed to,\r\n        uint256 indexed tokenId\r\n    );\r\n    event AssignGlobalPerms(address indexed from, address indexed to);\r\n    event RevokeGlobalPerms(address indexed from, address indexed to);\r\n\r\n    /*///////////////////////////////////////////////////////////////\r\n                          METADATA STORAGE/LOGIC\r\n    //////////////////////////////////////////////////////////////*/\r\n    address public dungeonAddress;\r\n    address public dungeonRewardsAddress;\r\n\r\n    struct Permission {\r\n        address grantedBy; // The address that granted the permission\r\n        address grantedTo; // The address that received the permission\r\n    }\r\n    struct GlobalPermission {\r\n        address grantedTo;\r\n        address grantedBy;\r\n    }\r\n\r\n    struct FloorData {\r\n        string txId; // transaction ID for the floor data\r\n        uint256 version; // current version of the floor data (every resetAllFloors increments the version #, outdating previous entries)\r\n    }\r\n\r\n    struct DungeonData {\r\n        bool locked;\r\n        uint256 trialTimeout;\r\n        bool randomizeLayout; // randomize layout indicates each floor after floor 1 has a random layout/environment\r\n        string passwordHash; // DungeonMaster notes: not really secure, of course, since it\u0027s publically available, but the best we have\r\n        string ownersMessage;\r\n        string gameMode; // if starts with https:, NFT will redirect here after loading up (use for streaming via rumble, 3D client, etc)\r\n    }\r\n\r\n    // Mapping that tells us which version of the dungeon data we\u0027re dealing with \r\n    // (reseting all floors increments the version # -- saves us gas from having to clear all the existing dungeon floor data)\r\n    mapping(uint256 =\u003e uint256) private tokenVersions;\r\n\r\n    mapping(uint256 =\u003e DungeonData) public dungeons;\r\n    // Mapping from tokenId to a mapping of floorNumber to FloorData\r\n    mapping(uint256 =\u003e mapping(uint256 =\u003e FloorData)) public dungeonFloors;\r\n\r\n    mapping(uint256 =\u003e Permission) public tokenPermissions;\r\n    // Mapping to store global permissions\r\n    mapping(address =\u003e GlobalPermission) public globalPermissions;\r\n\r\n    string public BASE_CODE_TXID = \"\"; // base code txid\r\n\r\n    constructor(address _dungeonAddress, address _dungeonRewardsAddress) {\r\n        dungeonAddress = _dungeonAddress;\r\n        dungeonRewardsAddress = _dungeonRewardsAddress;\r\n    }\r\n\r\n    modifier onlyOwnerOrStaker(uint256 tokenId) {\r\n        IDungeon dungeon = IDungeon(dungeonAddress);\r\n        address owner = dungeon.ownerOf(tokenId);\r\n        bool isStaker = owner == dungeonRewardsAddress \u0026\u0026\r\n            isTokenStakedByAddress(tokenId, msg.sender);\r\n\r\n        require(\r\n            owner == msg.sender || isStaker,\r\n            \"Caller is not owner or staker\"\r\n        );\r\n        _;\r\n    }\r\n\r\n    function isTokenStakedByAddress(\r\n        uint256 tokenId,\r\n        address addressToCheck\r\n    ) private view returns (bool) {\r\n        IDungeonRewards rewards = IDungeonRewards(dungeonRewardsAddress);\r\n        (uint256[] memory stakedDungeons, ) = rewards.getStakedTokens(\r\n            addressToCheck\r\n        );\r\n        for (uint i = 0; i \u003c stakedDungeons.length; i++) {\r\n            if (stakedDungeons[i] == tokenId) {\r\n                return true;\r\n            }\r\n        }\r\n        return false;\r\n    }\r\n\r\n    modifier isPermitted(uint256 tokenId) {\r\n        IDungeon dungeon = IDungeon(dungeonAddress);\r\n        address owner = dungeon.ownerOf(tokenId);\r\n\r\n        // First, check if msg.sender is the owner. If true, no further checks needed.\r\n        if (\r\n            owner == msg.sender ||\r\n            (owner == dungeonRewardsAddress \u0026\u0026\r\n                isTokenStakedByAddress(tokenId, msg.sender))\r\n        ) {\r\n            _;\r\n            return;\r\n        }\r\n\r\n        // Global Permissions Check\r\n        GlobalPermission memory globalPerm = globalPermissions[msg.sender];\r\n        if (globalPerm.grantedTo == msg.sender) {\r\n            bool isGlobalGrantorOwnerOrStaker = globalPerm.grantedBy == owner ||\r\n                (owner == dungeonRewardsAddress \u0026\u0026\r\n                    isTokenStakedByAddress(tokenId, globalPerm.grantedBy));\r\n\r\n            if (isGlobalGrantorOwnerOrStaker) {\r\n                _;\r\n                return;\r\n            }\r\n        }\r\n\r\n        // Only proceed to check permissions if the sender is not the owner or a staker.\r\n        Permission memory permission = tokenPermissions[tokenId];\r\n        bool hasValidPermission = permission.grantedTo == msg.sender \u0026\u0026\r\n            (permission.grantedBy == owner ||\r\n                (owner == dungeonRewardsAddress \u0026\u0026\r\n                    isTokenStakedByAddress(tokenId, permission.grantedBy)));\r\n\r\n        require(hasValidPermission, \"Not authorized\");\r\n\r\n        _;\r\n    }\r\n\r\n    function writeDungeonConfig(\r\n        uint256 tokenId,\r\n        bool locked,\r\n        uint256 trialTimeout,\r\n        bool randomizeLayout,\r\n        string memory passwordHash,\r\n        string memory ownersMessage,\r\n        string memory gameMode\r\n    ) public isPermitted(tokenId) {\r\n        DungeonData storage dungeon = dungeons[tokenId];\r\n        dungeon.locked = locked;\r\n        dungeon.trialTimeout = trialTimeout;\r\n        dungeon.randomizeLayout = randomizeLayout;\r\n        dungeon.passwordHash = passwordHash;\r\n        dungeon.ownersMessage = ownersMessage;\r\n        dungeon.gameMode = gameMode;\r\n    }\r\n\r\n    function readDungeonConfig(\r\n        uint256 tokenId\r\n    )\r\n        public\r\n        view\r\n        returns (\r\n            bool locked,\r\n            uint256 trialTimeout,\r\n            bool randomizeLayout,\r\n            string memory passwordHash,\r\n            string memory ownersMessage,\r\n            string memory gameMode,\r\n            string memory codeTxId\r\n        )\r\n    {\r\n        DungeonData memory dungeon = dungeons[tokenId];\r\n        return (\r\n            dungeon.locked,\r\n            dungeon.trialTimeout,\r\n            dungeon.randomizeLayout,\r\n            dungeon.passwordHash,\r\n            dungeon.ownersMessage,\r\n            dungeon.gameMode,\r\n            BASE_CODE_TXID\r\n        );\r\n    }\r\n\r\n    function writeFloorData(\r\n        uint256 tokenId,\r\n        uint256 floorNumber,\r\n        string memory txId\r\n    ) public isPermitted(tokenId) {\r\n        // Store the transaction ID for the specific floor\r\n        dungeonFloors[tokenId][floorNumber] = FloorData(txId, tokenVersions[tokenId]);\r\n    }\r\n\r\n    function readFloorData(\r\n        uint256 tokenId,\r\n        uint256 floorNumber\r\n    ) public view returns (string memory) {\r\n         FloorData memory floorData = dungeonFloors[tokenId][floorNumber];\r\n        // Return the transaction ID for the specific floor\r\n        if(floorData.version == tokenVersions[tokenId]) {\r\n            return floorData.txId;\r\n        } else {\r\n            return \"\"; // Indicates no data for the current version\r\n        }        \r\n    }\r\n\r\n    function resetAllFloors(uint256 tokenId) public isPermitted(tokenId) { \r\n        tokenVersions[tokenId] += 1; // Increment the version to \"reset\" the data\r\n    }\r\n\r\n    function assignPermission(\r\n        uint256 tokenId,\r\n        address to\r\n    ) public onlyOwnerOrStaker(tokenId) {\r\n        tokenPermissions[tokenId] = Permission(msg.sender, to);\r\n\r\n        emit AssignPerms(msg.sender, to, tokenId);\r\n    }\r\n\r\n    // Assign global permission\r\n    function assignGlobalPermission(address to) public {\r\n        globalPermissions[to] = GlobalPermission({\r\n            grantedTo: to,\r\n            grantedBy: msg.sender\r\n        });\r\n\r\n        emit AssignGlobalPerms(msg.sender, to);\r\n    }\r\n\r\n    // Optional: Function to revoke global permission\r\n    function revokeGlobalPermission(address to) public {\r\n        require(\r\n            globalPermissions[to].grantedBy == msg.sender,\r\n            \"Not authorized to revoke\"\r\n        );\r\n        delete globalPermissions[to];\r\n        emit RevokeGlobalPerms(msg.sender, to);\r\n    }\r\n\r\n    // set the ethscription txid of the base codebase\r\n    function setBaseCodeTxid(string memory baseCodeTxid) public onlyOwner {\r\n        BASE_CODE_TXID = baseCodeTxid;\r\n    }\r\n\r\n    // read the ethscription txid of the base codebase\r\n    function getBaseCodeTxid() public view returns (string memory) {\r\n        return BASE_CODE_TXID;\r\n    }\r\n\r\n}\r\n"},"Ownable.sol":{"content":"// SPDX-License-Identifier: AGPL-3.0-only\r\npragma solidity ^0.8.10;\r\n\r\nerror NotOwner();\r\n\r\n// https://github.com/m1guelpf/erc721-drop/blob/main/src/LilOwnable.sol\r\nabstract contract Ownable {\r\n    address internal _owner;\r\n\r\n    event OwnershipTransferred(\r\n        address indexed previousOwner,\r\n        address indexed newOwner\r\n    );\r\n\r\n    modifier onlyOwner() {\r\n        require(_owner == msg.sender);\r\n        _;\r\n    }\r\n\r\n    constructor() {\r\n        _owner = msg.sender;\r\n    }\r\n\r\n    function owner() external view returns (address) {\r\n        return _owner;\r\n    }\r\n\r\n    function transferOwnership(address _newOwner) external {\r\n        if (msg.sender != _owner) revert NotOwner();\r\n\r\n        _owner = _newOwner;\r\n    }\r\n\r\n    function renounceOwnership() public {\r\n        if (msg.sender != _owner) revert NotOwner();\r\n\r\n        _owner = address(0);\r\n    }\r\n\r\n    function supportsInterface(bytes4 interfaceId)\r\n        public\r\n        pure\r\n        virtual\r\n        returns (bool)\r\n    {\r\n        return interfaceId == 0x7f5828d0; // ERC165 Interface ID for ERC173\r\n    }\r\n}\r\n"}}