编译器
0.8.24+commit.e11b9ed9
文件 1 的 46:AccessControl.sol
pragma solidity ^0.8.20;
import {IAccessControl} from "./IAccessControl.sol";
import {Context} from "../utils/Context.sol";
import {ERC165} from "../utils/introspection/ERC165.sol";
abstract contract AccessControl is Context, IAccessControl, ERC165 {
struct RoleData {
mapping(address account => bool) hasRole;
bytes32 adminRole;
}
mapping(bytes32 role => RoleData) private _roles;
bytes32 public constant DEFAULT_ADMIN_ROLE = 0x00;
modifier onlyRole(bytes32 role) {
_checkRole(role);
_;
}
function supportsInterface(bytes4 interfaceId) public view virtual override returns (bool) {
return interfaceId == type(IAccessControl).interfaceId || super.supportsInterface(interfaceId);
}
function hasRole(bytes32 role, address account) public view virtual returns (bool) {
return _roles[role].hasRole[account];
}
function _checkRole(bytes32 role) internal view virtual {
_checkRole(role, _msgSender());
}
function _checkRole(bytes32 role, address account) internal view virtual {
if (!hasRole(role, account)) {
revert AccessControlUnauthorizedAccount(account, role);
}
}
function getRoleAdmin(bytes32 role) public view virtual returns (bytes32) {
return _roles[role].adminRole;
}
function grantRole(bytes32 role, address account) public virtual onlyRole(getRoleAdmin(role)) {
_grantRole(role, account);
}
function revokeRole(bytes32 role, address account) public virtual onlyRole(getRoleAdmin(role)) {
_revokeRole(role, account);
}
function renounceRole(bytes32 role, address callerConfirmation) public virtual {
if (callerConfirmation != _msgSender()) {
revert AccessControlBadConfirmation();
}
_revokeRole(role, callerConfirmation);
}
function _setRoleAdmin(bytes32 role, bytes32 adminRole) internal virtual {
bytes32 previousAdminRole = getRoleAdmin(role);
_roles[role].adminRole = adminRole;
emit RoleAdminChanged(role, previousAdminRole, adminRole);
}
function _grantRole(bytes32 role, address account) internal virtual returns (bool) {
if (!hasRole(role, account)) {
_roles[role].hasRole[account] = true;
emit RoleGranted(role, account, _msgSender());
return true;
} else {
return false;
}
}
function _revokeRole(bytes32 role, address account) internal virtual returns (bool) {
if (hasRole(role, account)) {
_roles[role].hasRole[account] = false;
emit RoleRevoked(role, account, _msgSender());
return true;
} else {
return false;
}
}
}
文件 2 的 46:Address.sol
pragma solidity ^0.8.20;
library Address {
error AddressInsufficientBalance(address account);
error AddressEmptyCode(address target);
error FailedInnerCall();
function sendValue(address payable recipient, uint256 amount) internal {
if (address(this).balance < amount) {
revert AddressInsufficientBalance(address(this));
}
(bool success, ) = recipient.call{value: amount}("");
if (!success) {
revert FailedInnerCall();
}
}
function functionCall(address target, bytes memory data) internal returns (bytes memory) {
return functionCallWithValue(target, data, 0);
}
function functionCallWithValue(address target, bytes memory data, uint256 value) internal returns (bytes memory) {
if (address(this).balance < value) {
revert AddressInsufficientBalance(address(this));
}
(bool success, bytes memory returndata) = target.call{value: value}(data);
return verifyCallResultFromTarget(target, success, returndata);
}
function functionStaticCall(address target, bytes memory data) internal view returns (bytes memory) {
(bool success, bytes memory returndata) = target.staticcall(data);
return verifyCallResultFromTarget(target, success, returndata);
}
function functionDelegateCall(address target, bytes memory data) internal returns (bytes memory) {
(bool success, bytes memory returndata) = target.delegatecall(data);
return verifyCallResultFromTarget(target, success, returndata);
}
function verifyCallResultFromTarget(
address target,
bool success,
bytes memory returndata
) internal view returns (bytes memory) {
if (!success) {
_revert(returndata);
} else {
if (returndata.length == 0 && target.code.length == 0) {
revert AddressEmptyCode(target);
}
return returndata;
}
}
function verifyCallResult(bool success, bytes memory returndata) internal pure returns (bytes memory) {
if (!success) {
_revert(returndata);
} else {
return returndata;
}
}
function _revert(bytes memory returndata) private pure {
if (returndata.length > 0) {
assembly {
let returndata_size := mload(returndata)
revert(add(32, returndata), returndata_size)
}
} else {
revert FailedInnerCall();
}
}
}
文件 3 的 46:Arrays.sol
pragma solidity ^0.8.20;
import {StorageSlot} from "./StorageSlot.sol";
import {Math} from "./math/Math.sol";
library Arrays {
using StorageSlot for bytes32;
function findUpperBound(uint256[] storage array, uint256 element) internal view returns (uint256) {
uint256 low = 0;
uint256 high = array.length;
if (high == 0) {
return 0;
}
while (low < high) {
uint256 mid = Math.average(low, high);
if (unsafeAccess(array, mid).value > element) {
high = mid;
} else {
low = mid + 1;
}
}
if (low > 0 && unsafeAccess(array, low - 1).value == element) {
return low - 1;
} else {
return low;
}
}
function unsafeAccess(address[] storage arr, uint256 pos) internal pure returns (StorageSlot.AddressSlot storage) {
bytes32 slot;
assembly {
mstore(0, arr.slot)
slot := add(keccak256(0, 0x20), pos)
}
return slot.getAddressSlot();
}
function unsafeAccess(bytes32[] storage arr, uint256 pos) internal pure returns (StorageSlot.Bytes32Slot storage) {
bytes32 slot;
assembly {
mstore(0, arr.slot)
slot := add(keccak256(0, 0x20), pos)
}
return slot.getBytes32Slot();
}
function unsafeAccess(uint256[] storage arr, uint256 pos) internal pure returns (StorageSlot.Uint256Slot storage) {
bytes32 slot;
assembly {
mstore(0, arr.slot)
slot := add(keccak256(0, 0x20), pos)
}
return slot.getUint256Slot();
}
function unsafeMemoryAccess(uint256[] memory arr, uint256 pos) internal pure returns (uint256 res) {
assembly {
res := mload(add(add(arr, 0x20), mul(pos, 0x20)))
}
}
function unsafeMemoryAccess(address[] memory arr, uint256 pos) internal pure returns (address res) {
assembly {
res := mload(add(add(arr, 0x20), mul(pos, 0x20)))
}
}
}
文件 4 的 46:Context.sol
pragma solidity ^0.8.20;
abstract contract Context {
function _msgSender() internal view virtual returns (address) {
return msg.sender;
}
function _msgData() internal view virtual returns (bytes calldata) {
return msg.data;
}
function _contextSuffixLength() internal view virtual returns (uint256) {
return 0;
}
}
文件 5 的 46:ERC1155.sol
pragma solidity ^0.8.20;
import {IERC1155} from "./IERC1155.sol";
import {IERC1155Receiver} from "./IERC1155Receiver.sol";
import {IERC1155MetadataURI} from "./extensions/IERC1155MetadataURI.sol";
import {Context} from "../../utils/Context.sol";
import {IERC165, ERC165} from "../../utils/introspection/ERC165.sol";
import {Arrays} from "../../utils/Arrays.sol";
import {IERC1155Errors} from "../../interfaces/draft-IERC6093.sol";
abstract contract ERC1155 is Context, ERC165, IERC1155, IERC1155MetadataURI, IERC1155Errors {
using Arrays for uint256[];
using Arrays for address[];
mapping(uint256 id => mapping(address account => uint256)) private _balances;
mapping(address account => mapping(address operator => bool)) private _operatorApprovals;
string private _uri;
constructor(string memory uri_) {
_setURI(uri_);
}
function supportsInterface(bytes4 interfaceId) public view virtual override(ERC165, IERC165) returns (bool) {
return
interfaceId == type(IERC1155).interfaceId ||
interfaceId == type(IERC1155MetadataURI).interfaceId ||
super.supportsInterface(interfaceId);
}
function uri(uint256 ) public view virtual returns (string memory) {
return _uri;
}
function balanceOf(address account, uint256 id) public view virtual returns (uint256) {
return _balances[id][account];
}
function balanceOfBatch(
address[] memory accounts,
uint256[] memory ids
) public view virtual returns (uint256[] memory) {
if (accounts.length != ids.length) {
revert ERC1155InvalidArrayLength(ids.length, accounts.length);
}
uint256[] memory batchBalances = new uint256[](accounts.length);
for (uint256 i = 0; i < accounts.length; ++i) {
batchBalances[i] = balanceOf(accounts.unsafeMemoryAccess(i), ids.unsafeMemoryAccess(i));
}
return batchBalances;
}
function setApprovalForAll(address operator, bool approved) public virtual {
_setApprovalForAll(_msgSender(), operator, approved);
}
function isApprovedForAll(address account, address operator) public view virtual returns (bool) {
return _operatorApprovals[account][operator];
}
function safeTransferFrom(address from, address to, uint256 id, uint256 value, bytes memory data) public virtual {
address sender = _msgSender();
if (from != sender && !isApprovedForAll(from, sender)) {
revert ERC1155MissingApprovalForAll(sender, from);
}
_safeTransferFrom(from, to, id, value, data);
}
function safeBatchTransferFrom(
address from,
address to,
uint256[] memory ids,
uint256[] memory values,
bytes memory data
) public virtual {
address sender = _msgSender();
if (from != sender && !isApprovedForAll(from, sender)) {
revert ERC1155MissingApprovalForAll(sender, from);
}
_safeBatchTransferFrom(from, to, ids, values, data);
}
function _update(address from, address to, uint256[] memory ids, uint256[] memory values) internal virtual {
if (ids.length != values.length) {
revert ERC1155InvalidArrayLength(ids.length, values.length);
}
address operator = _msgSender();
for (uint256 i = 0; i < ids.length; ++i) {
uint256 id = ids.unsafeMemoryAccess(i);
uint256 value = values.unsafeMemoryAccess(i);
if (from != address(0)) {
uint256 fromBalance = _balances[id][from];
if (fromBalance < value) {
revert ERC1155InsufficientBalance(from, fromBalance, value, id);
}
unchecked {
_balances[id][from] = fromBalance - value;
}
}
if (to != address(0)) {
_balances[id][to] += value;
}
}
if (ids.length == 1) {
uint256 id = ids.unsafeMemoryAccess(0);
uint256 value = values.unsafeMemoryAccess(0);
emit TransferSingle(operator, from, to, id, value);
} else {
emit TransferBatch(operator, from, to, ids, values);
}
}
function _updateWithAcceptanceCheck(
address from,
address to,
uint256[] memory ids,
uint256[] memory values,
bytes memory data
) internal virtual {
_update(from, to, ids, values);
if (to != address(0)) {
address operator = _msgSender();
if (ids.length == 1) {
uint256 id = ids.unsafeMemoryAccess(0);
uint256 value = values.unsafeMemoryAccess(0);
_doSafeTransferAcceptanceCheck(operator, from, to, id, value, data);
} else {
_doSafeBatchTransferAcceptanceCheck(operator, from, to, ids, values, data);
}
}
}
function _safeTransferFrom(address from, address to, uint256 id, uint256 value, bytes memory data) internal {
if (to == address(0)) {
revert ERC1155InvalidReceiver(address(0));
}
if (from == address(0)) {
revert ERC1155InvalidSender(address(0));
}
(uint256[] memory ids, uint256[] memory values) = _asSingletonArrays(id, value);
_updateWithAcceptanceCheck(from, to, ids, values, data);
}
function _safeBatchTransferFrom(
address from,
address to,
uint256[] memory ids,
uint256[] memory values,
bytes memory data
) internal {
if (to == address(0)) {
revert ERC1155InvalidReceiver(address(0));
}
if (from == address(0)) {
revert ERC1155InvalidSender(address(0));
}
_updateWithAcceptanceCheck(from, to, ids, values, data);
}
function _setURI(string memory newuri) internal virtual {
_uri = newuri;
}
function _mint(address to, uint256 id, uint256 value, bytes memory data) internal {
if (to == address(0)) {
revert ERC1155InvalidReceiver(address(0));
}
(uint256[] memory ids, uint256[] memory values) = _asSingletonArrays(id, value);
_updateWithAcceptanceCheck(address(0), to, ids, values, data);
}
function _mintBatch(address to, uint256[] memory ids, uint256[] memory values, bytes memory data) internal {
if (to == address(0)) {
revert ERC1155InvalidReceiver(address(0));
}
_updateWithAcceptanceCheck(address(0), to, ids, values, data);
}
function _burn(address from, uint256 id, uint256 value) internal {
if (from == address(0)) {
revert ERC1155InvalidSender(address(0));
}
(uint256[] memory ids, uint256[] memory values) = _asSingletonArrays(id, value);
_updateWithAcceptanceCheck(from, address(0), ids, values, "");
}
function _burnBatch(address from, uint256[] memory ids, uint256[] memory values) internal {
if (from == address(0)) {
revert ERC1155InvalidSender(address(0));
}
_updateWithAcceptanceCheck(from, address(0), ids, values, "");
}
function _setApprovalForAll(address owner, address operator, bool approved) internal virtual {
if (operator == address(0)) {
revert ERC1155InvalidOperator(address(0));
}
_operatorApprovals[owner][operator] = approved;
emit ApprovalForAll(owner, operator, approved);
}
function _doSafeTransferAcceptanceCheck(
address operator,
address from,
address to,
uint256 id,
uint256 value,
bytes memory data
) private {
if (to.code.length > 0) {
try IERC1155Receiver(to).onERC1155Received(operator, from, id, value, data) returns (bytes4 response) {
if (response != IERC1155Receiver.onERC1155Received.selector) {
revert ERC1155InvalidReceiver(to);
}
} catch (bytes memory reason) {
if (reason.length == 0) {
revert ERC1155InvalidReceiver(to);
} else {
assembly {
revert(add(32, reason), mload(reason))
}
}
}
}
}
function _doSafeBatchTransferAcceptanceCheck(
address operator,
address from,
address to,
uint256[] memory ids,
uint256[] memory values,
bytes memory data
) private {
if (to.code.length > 0) {
try IERC1155Receiver(to).onERC1155BatchReceived(operator, from, ids, values, data) returns (
bytes4 response
) {
if (response != IERC1155Receiver.onERC1155BatchReceived.selector) {
revert ERC1155InvalidReceiver(to);
}
} catch (bytes memory reason) {
if (reason.length == 0) {
revert ERC1155InvalidReceiver(to);
} else {
assembly {
revert(add(32, reason), mload(reason))
}
}
}
}
}
function _asSingletonArrays(
uint256 element1,
uint256 element2
) private pure returns (uint256[] memory array1, uint256[] memory array2) {
assembly {
array1 := mload(0x40)
mstore(array1, 1)
mstore(add(array1, 0x20), element1)
array2 := add(array1, 0x40)
mstore(array2, 1)
mstore(add(array2, 0x20), element2)
mstore(0x40, add(array2, 0x40))
}
}
}
文件 6 的 46:ERC1155Burnable.sol
pragma solidity ^0.8.20;
import {ERC1155} from "../ERC1155.sol";
abstract contract ERC1155Burnable is ERC1155 {
function burn(address account, uint256 id, uint256 value) public virtual {
if (account != _msgSender() && !isApprovedForAll(account, _msgSender())) {
revert ERC1155MissingApprovalForAll(_msgSender(), account);
}
_burn(account, id, value);
}
function burnBatch(address account, uint256[] memory ids, uint256[] memory values) public virtual {
if (account != _msgSender() && !isApprovedForAll(account, _msgSender())) {
revert ERC1155MissingApprovalForAll(_msgSender(), account);
}
_burnBatch(account, ids, values);
}
}
文件 7 的 46:ERC1155Holder.sol
pragma solidity ^0.8.20;
import {IERC165, ERC165} from "../../../utils/introspection/ERC165.sol";
import {IERC1155Receiver} from "../IERC1155Receiver.sol";
abstract contract ERC1155Holder is ERC165, IERC1155Receiver {
function supportsInterface(bytes4 interfaceId) public view virtual override(ERC165, IERC165) returns (bool) {
return interfaceId == type(IERC1155Receiver).interfaceId || super.supportsInterface(interfaceId);
}
function onERC1155Received(
address,
address,
uint256,
uint256,
bytes memory
) public virtual override returns (bytes4) {
return this.onERC1155Received.selector;
}
function onERC1155BatchReceived(
address,
address,
uint256[] memory,
uint256[] memory,
bytes memory
) public virtual override returns (bytes4) {
return this.onERC1155BatchReceived.selector;
}
}
文件 8 的 46:ERC1155Supply.sol
pragma solidity ^0.8.20;
import {ERC1155} from "../ERC1155.sol";
abstract contract ERC1155Supply is ERC1155 {
mapping(uint256 id => uint256) private _totalSupply;
uint256 private _totalSupplyAll;
function totalSupply(uint256 id) public view virtual returns (uint256) {
return _totalSupply[id];
}
function totalSupply() public view virtual returns (uint256) {
return _totalSupplyAll;
}
function exists(uint256 id) public view virtual returns (bool) {
return totalSupply(id) > 0;
}
function _update(
address from,
address to,
uint256[] memory ids,
uint256[] memory values
) internal virtual override {
super._update(from, to, ids, values);
if (from == address(0)) {
uint256 totalMintValue = 0;
for (uint256 i = 0; i < ids.length; ++i) {
uint256 value = values[i];
_totalSupply[ids[i]] += value;
totalMintValue += value;
}
_totalSupplyAll += totalMintValue;
}
if (to == address(0)) {
uint256 totalBurnValue = 0;
for (uint256 i = 0; i < ids.length; ++i) {
uint256 value = values[i];
unchecked {
_totalSupply[ids[i]] -= value;
totalBurnValue += value;
}
}
unchecked {
_totalSupplyAll -= totalBurnValue;
}
}
}
}
文件 9 的 46:ERC165.sol
pragma solidity ^0.8.20;
import {IERC165} from "./IERC165.sol";
abstract contract ERC165 is IERC165 {
function supportsInterface(bytes4 interfaceId) public view virtual returns (bool) {
return interfaceId == type(IERC165).interfaceId;
}
}
文件 10 的 46:ERC2981.sol
pragma solidity ^0.8.20;
import {IERC2981} from "../../interfaces/IERC2981.sol";
import {IERC165, ERC165} from "../../utils/introspection/ERC165.sol";
abstract contract ERC2981 is IERC2981, ERC165 {
struct RoyaltyInfo {
address receiver;
uint96 royaltyFraction;
}
RoyaltyInfo private _defaultRoyaltyInfo;
mapping(uint256 tokenId => RoyaltyInfo) private _tokenRoyaltyInfo;
error ERC2981InvalidDefaultRoyalty(uint256 numerator, uint256 denominator);
error ERC2981InvalidDefaultRoyaltyReceiver(address receiver);
error ERC2981InvalidTokenRoyalty(uint256 tokenId, uint256 numerator, uint256 denominator);
error ERC2981InvalidTokenRoyaltyReceiver(uint256 tokenId, address receiver);
function supportsInterface(bytes4 interfaceId) public view virtual override(IERC165, ERC165) returns (bool) {
return interfaceId == type(IERC2981).interfaceId || super.supportsInterface(interfaceId);
}
function royaltyInfo(uint256 tokenId, uint256 salePrice) public view virtual returns (address, uint256) {
RoyaltyInfo memory royalty = _tokenRoyaltyInfo[tokenId];
if (royalty.receiver == address(0)) {
royalty = _defaultRoyaltyInfo;
}
uint256 royaltyAmount = (salePrice * royalty.royaltyFraction) / _feeDenominator();
return (royalty.receiver, royaltyAmount);
}
function _feeDenominator() internal pure virtual returns (uint96) {
return 10000;
}
function _setDefaultRoyalty(address receiver, uint96 feeNumerator) internal virtual {
uint256 denominator = _feeDenominator();
if (feeNumerator > denominator) {
revert ERC2981InvalidDefaultRoyalty(feeNumerator, denominator);
}
if (receiver == address(0)) {
revert ERC2981InvalidDefaultRoyaltyReceiver(address(0));
}
_defaultRoyaltyInfo = RoyaltyInfo(receiver, feeNumerator);
}
function _deleteDefaultRoyalty() internal virtual {
delete _defaultRoyaltyInfo;
}
function _setTokenRoyalty(uint256 tokenId, address receiver, uint96 feeNumerator) internal virtual {
uint256 denominator = _feeDenominator();
if (feeNumerator > denominator) {
revert ERC2981InvalidTokenRoyalty(tokenId, feeNumerator, denominator);
}
if (receiver == address(0)) {
revert ERC2981InvalidTokenRoyaltyReceiver(tokenId, address(0));
}
_tokenRoyaltyInfo[tokenId] = RoyaltyInfo(receiver, feeNumerator);
}
function _resetTokenRoyalty(uint256 tokenId) internal virtual {
delete _tokenRoyaltyInfo[tokenId];
}
}
文件 11 的 46:ERC721.sol
pragma solidity ^0.8.20;
import {IERC721} from "./IERC721.sol";
import {IERC721Receiver} from "./IERC721Receiver.sol";
import {IERC721Metadata} from "./extensions/IERC721Metadata.sol";
import {Context} from "../../utils/Context.sol";
import {Strings} from "../../utils/Strings.sol";
import {IERC165, ERC165} from "../../utils/introspection/ERC165.sol";
import {IERC721Errors} from "../../interfaces/draft-IERC6093.sol";
abstract contract ERC721 is Context, ERC165, IERC721, IERC721Metadata, IERC721Errors {
using Strings for uint256;
string private _name;
string private _symbol;
mapping(uint256 tokenId => address) private _owners;
mapping(address owner => uint256) private _balances;
mapping(uint256 tokenId => address) private _tokenApprovals;
mapping(address owner => mapping(address operator => bool)) private _operatorApprovals;
constructor(string memory name_, string memory symbol_) {
_name = name_;
_symbol = symbol_;
}
function supportsInterface(bytes4 interfaceId) public view virtual override(ERC165, IERC165) returns (bool) {
return
interfaceId == type(IERC721).interfaceId ||
interfaceId == type(IERC721Metadata).interfaceId ||
super.supportsInterface(interfaceId);
}
function balanceOf(address owner) public view virtual returns (uint256) {
if (owner == address(0)) {
revert ERC721InvalidOwner(address(0));
}
return _balances[owner];
}
function ownerOf(uint256 tokenId) public view virtual returns (address) {
return _requireOwned(tokenId);
}
function name() public view virtual returns (string memory) {
return _name;
}
function symbol() public view virtual returns (string memory) {
return _symbol;
}
function tokenURI(uint256 tokenId) public view virtual returns (string memory) {
_requireOwned(tokenId);
string memory baseURI = _baseURI();
return bytes(baseURI).length > 0 ? string.concat(baseURI, tokenId.toString()) : "";
}
function _baseURI() internal view virtual returns (string memory) {
return "";
}
function approve(address to, uint256 tokenId) public virtual {
_approve(to, tokenId, _msgSender());
}
function getApproved(uint256 tokenId) public view virtual returns (address) {
_requireOwned(tokenId);
return _getApproved(tokenId);
}
function setApprovalForAll(address operator, bool approved) public virtual {
_setApprovalForAll(_msgSender(), operator, approved);
}
function isApprovedForAll(address owner, address operator) public view virtual returns (bool) {
return _operatorApprovals[owner][operator];
}
function transferFrom(address from, address to, uint256 tokenId) public virtual {
if (to == address(0)) {
revert ERC721InvalidReceiver(address(0));
}
address previousOwner = _update(to, tokenId, _msgSender());
if (previousOwner != from) {
revert ERC721IncorrectOwner(from, tokenId, previousOwner);
}
}
function safeTransferFrom(address from, address to, uint256 tokenId) public {
safeTransferFrom(from, to, tokenId, "");
}
function safeTransferFrom(address from, address to, uint256 tokenId, bytes memory data) public virtual {
transferFrom(from, to, tokenId);
_checkOnERC721Received(from, to, tokenId, data);
}
function _ownerOf(uint256 tokenId) internal view virtual returns (address) {
return _owners[tokenId];
}
function _getApproved(uint256 tokenId) internal view virtual returns (address) {
return _tokenApprovals[tokenId];
}
function _isAuthorized(address owner, address spender, uint256 tokenId) internal view virtual returns (bool) {
return
spender != address(0) &&
(owner == spender || isApprovedForAll(owner, spender) || _getApproved(tokenId) == spender);
}
function _checkAuthorized(address owner, address spender, uint256 tokenId) internal view virtual {
if (!_isAuthorized(owner, spender, tokenId)) {
if (owner == address(0)) {
revert ERC721NonexistentToken(tokenId);
} else {
revert ERC721InsufficientApproval(spender, tokenId);
}
}
}
function _increaseBalance(address account, uint128 value) internal virtual {
unchecked {
_balances[account] += value;
}
}
function _update(address to, uint256 tokenId, address auth) internal virtual returns (address) {
address from = _ownerOf(tokenId);
if (auth != address(0)) {
_checkAuthorized(from, auth, tokenId);
}
if (from != address(0)) {
_approve(address(0), tokenId, address(0), false);
unchecked {
_balances[from] -= 1;
}
}
if (to != address(0)) {
unchecked {
_balances[to] += 1;
}
}
_owners[tokenId] = to;
emit Transfer(from, to, tokenId);
return from;
}
function _mint(address to, uint256 tokenId) internal {
if (to == address(0)) {
revert ERC721InvalidReceiver(address(0));
}
address previousOwner = _update(to, tokenId, address(0));
if (previousOwner != address(0)) {
revert ERC721InvalidSender(address(0));
}
}
function _safeMint(address to, uint256 tokenId) internal {
_safeMint(to, tokenId, "");
}
function _safeMint(address to, uint256 tokenId, bytes memory data) internal virtual {
_mint(to, tokenId);
_checkOnERC721Received(address(0), to, tokenId, data);
}
function _burn(uint256 tokenId) internal {
address previousOwner = _update(address(0), tokenId, address(0));
if (previousOwner == address(0)) {
revert ERC721NonexistentToken(tokenId);
}
}
function _transfer(address from, address to, uint256 tokenId) internal {
if (to == address(0)) {
revert ERC721InvalidReceiver(address(0));
}
address previousOwner = _update(to, tokenId, address(0));
if (previousOwner == address(0)) {
revert ERC721NonexistentToken(tokenId);
} else if (previousOwner != from) {
revert ERC721IncorrectOwner(from, tokenId, previousOwner);
}
}
function _safeTransfer(address from, address to, uint256 tokenId) internal {
_safeTransfer(from, to, tokenId, "");
}
function _safeTransfer(address from, address to, uint256 tokenId, bytes memory data) internal virtual {
_transfer(from, to, tokenId);
_checkOnERC721Received(from, to, tokenId, data);
}
function _approve(address to, uint256 tokenId, address auth) internal {
_approve(to, tokenId, auth, true);
}
function _approve(address to, uint256 tokenId, address auth, bool emitEvent) internal virtual {
if (emitEvent || auth != address(0)) {
address owner = _requireOwned(tokenId);
if (auth != address(0) && owner != auth && !isApprovedForAll(owner, auth)) {
revert ERC721InvalidApprover(auth);
}
if (emitEvent) {
emit Approval(owner, to, tokenId);
}
}
_tokenApprovals[tokenId] = to;
}
function _setApprovalForAll(address owner, address operator, bool approved) internal virtual {
if (operator == address(0)) {
revert ERC721InvalidOperator(operator);
}
_operatorApprovals[owner][operator] = approved;
emit ApprovalForAll(owner, operator, approved);
}
function _requireOwned(uint256 tokenId) internal view returns (address) {
address owner = _ownerOf(tokenId);
if (owner == address(0)) {
revert ERC721NonexistentToken(tokenId);
}
return owner;
}
function _checkOnERC721Received(address from, address to, uint256 tokenId, bytes memory data) private {
if (to.code.length > 0) {
try IERC721Receiver(to).onERC721Received(_msgSender(), from, tokenId, data) returns (bytes4 retval) {
if (retval != IERC721Receiver.onERC721Received.selector) {
revert ERC721InvalidReceiver(to);
}
} catch (bytes memory reason) {
if (reason.length == 0) {
revert ERC721InvalidReceiver(to);
} else {
assembly {
revert(add(32, reason), mload(reason))
}
}
}
}
}
}
文件 12 的 46:ERC721Burnable.sol
pragma solidity ^0.8.20;
import {ERC721} from "../ERC721.sol";
import {Context} from "../../../utils/Context.sol";
abstract contract ERC721Burnable is Context, ERC721 {
function burn(uint256 tokenId) public virtual {
_update(address(0), tokenId, _msgSender());
}
}
文件 13 的 46:ERC721Enumerable.sol
pragma solidity ^0.8.20;
import {ERC721} from "../ERC721.sol";
import {IERC721Enumerable} from "./IERC721Enumerable.sol";
import {IERC165} from "../../../utils/introspection/ERC165.sol";
abstract contract ERC721Enumerable is ERC721, IERC721Enumerable {
mapping(address owner => mapping(uint256 index => uint256)) private _ownedTokens;
mapping(uint256 tokenId => uint256) private _ownedTokensIndex;
uint256[] private _allTokens;
mapping(uint256 tokenId => uint256) private _allTokensIndex;
error ERC721OutOfBoundsIndex(address owner, uint256 index);
error ERC721EnumerableForbiddenBatchMint();
function supportsInterface(bytes4 interfaceId) public view virtual override(IERC165, ERC721) returns (bool) {
return interfaceId == type(IERC721Enumerable).interfaceId || super.supportsInterface(interfaceId);
}
function tokenOfOwnerByIndex(address owner, uint256 index) public view virtual returns (uint256) {
if (index >= balanceOf(owner)) {
revert ERC721OutOfBoundsIndex(owner, index);
}
return _ownedTokens[owner][index];
}
function totalSupply() public view virtual returns (uint256) {
return _allTokens.length;
}
function tokenByIndex(uint256 index) public view virtual returns (uint256) {
if (index >= totalSupply()) {
revert ERC721OutOfBoundsIndex(address(0), index);
}
return _allTokens[index];
}
function _update(address to, uint256 tokenId, address auth) internal virtual override returns (address) {
address previousOwner = super._update(to, tokenId, auth);
if (previousOwner == address(0)) {
_addTokenToAllTokensEnumeration(tokenId);
} else if (previousOwner != to) {
_removeTokenFromOwnerEnumeration(previousOwner, tokenId);
}
if (to == address(0)) {
_removeTokenFromAllTokensEnumeration(tokenId);
} else if (previousOwner != to) {
_addTokenToOwnerEnumeration(to, tokenId);
}
return previousOwner;
}
function _addTokenToOwnerEnumeration(address to, uint256 tokenId) private {
uint256 length = balanceOf(to) - 1;
_ownedTokens[to][length] = tokenId;
_ownedTokensIndex[tokenId] = length;
}
function _addTokenToAllTokensEnumeration(uint256 tokenId) private {
_allTokensIndex[tokenId] = _allTokens.length;
_allTokens.push(tokenId);
}
function _removeTokenFromOwnerEnumeration(address from, uint256 tokenId) private {
uint256 lastTokenIndex = balanceOf(from);
uint256 tokenIndex = _ownedTokensIndex[tokenId];
if (tokenIndex != lastTokenIndex) {
uint256 lastTokenId = _ownedTokens[from][lastTokenIndex];
_ownedTokens[from][tokenIndex] = lastTokenId;
_ownedTokensIndex[lastTokenId] = tokenIndex;
}
delete _ownedTokensIndex[tokenId];
delete _ownedTokens[from][lastTokenIndex];
}
function _removeTokenFromAllTokensEnumeration(uint256 tokenId) private {
uint256 lastTokenIndex = _allTokens.length - 1;
uint256 tokenIndex = _allTokensIndex[tokenId];
uint256 lastTokenId = _allTokens[lastTokenIndex];
_allTokens[tokenIndex] = lastTokenId;
_allTokensIndex[lastTokenId] = tokenIndex;
delete _allTokensIndex[tokenId];
_allTokens.pop();
}
function _increaseBalance(address account, uint128 amount) internal virtual override {
if (amount > 0) {
revert ERC721EnumerableForbiddenBatchMint();
}
super._increaseBalance(account, amount);
}
}
文件 14 的 46:ERC721Pausable.sol
pragma solidity ^0.8.20;
import {ERC721} from "../ERC721.sol";
import {Pausable} from "../../../utils/Pausable.sol";
abstract contract ERC721Pausable is ERC721, Pausable {
function _update(
address to,
uint256 tokenId,
address auth
) internal virtual override whenNotPaused returns (address) {
return super._update(to, tokenId, auth);
}
}
文件 15 的 46:IAccessControl.sol
pragma solidity ^0.8.20;
interface IAccessControl {
error AccessControlUnauthorizedAccount(address account, bytes32 neededRole);
error AccessControlBadConfirmation();
event RoleAdminChanged(bytes32 indexed role, bytes32 indexed previousAdminRole, bytes32 indexed newAdminRole);
event RoleGranted(bytes32 indexed role, address indexed account, address indexed sender);
event RoleRevoked(bytes32 indexed role, address indexed account, address indexed sender);
function hasRole(bytes32 role, address account) external view returns (bool);
function getRoleAdmin(bytes32 role) external view returns (bytes32);
function grantRole(bytes32 role, address account) external;
function revokeRole(bytes32 role, address account) external;
function renounceRole(bytes32 role, address callerConfirmation) external;
}
文件 16 的 46:IERC1155.sol
pragma solidity ^0.8.20;
import {IERC165} from "../../utils/introspection/IERC165.sol";
interface IERC1155 is IERC165 {
event TransferSingle(address indexed operator, address indexed from, address indexed to, uint256 id, uint256 value);
event TransferBatch(
address indexed operator,
address indexed from,
address indexed to,
uint256[] ids,
uint256[] values
);
event ApprovalForAll(address indexed account, address indexed operator, bool approved);
event URI(string value, uint256 indexed id);
function balanceOf(address account, uint256 id) external view returns (uint256);
function balanceOfBatch(
address[] calldata accounts,
uint256[] calldata ids
) external view returns (uint256[] memory);
function setApprovalForAll(address operator, bool approved) external;
function isApprovedForAll(address account, address operator) external view returns (bool);
function safeTransferFrom(address from, address to, uint256 id, uint256 value, bytes calldata data) external;
function safeBatchTransferFrom(
address from,
address to,
uint256[] calldata ids,
uint256[] calldata values,
bytes calldata data
) external;
}
文件 17 的 46:IERC1155MetadataURI.sol
pragma solidity ^0.8.20;
import {IERC1155} from "../IERC1155.sol";
interface IERC1155MetadataURI is IERC1155 {
function uri(uint256 id) external view returns (string memory);
}
文件 18 的 46:IERC1155Receiver.sol
pragma solidity ^0.8.20;
import {IERC165} from "../../utils/introspection/IERC165.sol";
interface IERC1155Receiver is IERC165 {
function onERC1155Received(
address operator,
address from,
uint256 id,
uint256 value,
bytes calldata data
) external returns (bytes4);
function onERC1155BatchReceived(
address operator,
address from,
uint256[] calldata ids,
uint256[] calldata values,
bytes calldata data
) external returns (bytes4);
}
文件 19 的 46:IERC165.sol
pragma solidity ^0.8.20;
interface IERC165 {
function supportsInterface(bytes4 interfaceId) external view returns (bool);
}
文件 20 的 46:IERC20.sol
pragma solidity ^0.8.20;
interface IERC20 {
event Transfer(address indexed from, address indexed to, uint256 value);
event Approval(address indexed owner, address indexed spender, uint256 value);
function totalSupply() external view returns (uint256);
function balanceOf(address account) external view returns (uint256);
function transfer(address to, uint256 value) external returns (bool);
function allowance(address owner, address spender) external view returns (uint256);
function approve(address spender, uint256 value) external returns (bool);
function transferFrom(address from, address to, uint256 value) external returns (bool);
}
文件 21 的 46:IERC20Metadata.sol
pragma solidity ^0.8.20;
import {IERC20} from "../IERC20.sol";
interface IERC20Metadata is IERC20 {
function name() external view returns (string memory);
function symbol() external view returns (string memory);
function decimals() external view returns (uint8);
}
文件 22 的 46:IERC20Permit.sol
pragma solidity ^0.8.20;
interface IERC20Permit {
function permit(
address owner,
address spender,
uint256 value,
uint256 deadline,
uint8 v,
bytes32 r,
bytes32 s
) external;
function nonces(address owner) external view returns (uint256);
function DOMAIN_SEPARATOR() external view returns (bytes32);
}
文件 23 的 46:IERC2981.sol
pragma solidity ^0.8.20;
import {IERC165} from "../utils/introspection/IERC165.sol";
interface IERC2981 is IERC165 {
function royaltyInfo(
uint256 tokenId,
uint256 salePrice
) external view returns (address receiver, uint256 royaltyAmount);
}
文件 24 的 46:IERC721.sol
pragma solidity ^0.8.20;
import {IERC165} from "../../utils/introspection/IERC165.sol";
interface IERC721 is IERC165 {
event Transfer(address indexed from, address indexed to, uint256 indexed tokenId);
event Approval(address indexed owner, address indexed approved, uint256 indexed tokenId);
event ApprovalForAll(address indexed owner, address indexed operator, bool approved);
function balanceOf(address owner) external view returns (uint256 balance);
function ownerOf(uint256 tokenId) external view returns (address owner);
function safeTransferFrom(address from, address to, uint256 tokenId, bytes calldata data) external;
function safeTransferFrom(address from, address to, uint256 tokenId) external;
function transferFrom(address from, address to, uint256 tokenId) external;
function approve(address to, uint256 tokenId) external;
function setApprovalForAll(address operator, bool approved) external;
function getApproved(uint256 tokenId) external view returns (address operator);
function isApprovedForAll(address owner, address operator) external view returns (bool);
}
文件 25 的 46:IERC721Enumerable.sol
pragma solidity ^0.8.20;
import {IERC721} from "../IERC721.sol";
interface IERC721Enumerable is IERC721 {
function totalSupply() external view returns (uint256);
function tokenOfOwnerByIndex(address owner, uint256 index) external view returns (uint256);
function tokenByIndex(uint256 index) external view returns (uint256);
}
文件 26 的 46:IERC721Metadata.sol
pragma solidity ^0.8.20;
import {IERC721} from "../IERC721.sol";
interface IERC721Metadata is IERC721 {
function name() external view returns (string memory);
function symbol() external view returns (string memory);
function tokenURI(uint256 tokenId) external view returns (string memory);
}
文件 27 的 46:IERC721Receiver.sol
pragma solidity ^0.8.20;
interface IERC721Receiver {
function onERC721Received(
address operator,
address from,
uint256 tokenId,
bytes calldata data
) external returns (bytes4);
}
文件 28 的 46:IPermissionCallable.sol
pragma solidity ^0.8.18;
interface IPermissionCallable {
function permissionedCall(bytes calldata call) external payable returns (bytes memory res);
function supportsPermissionedCallSelector(bytes4 selector) external view returns (bool supported);
}
文件 29 的 46:ManaSystem.sol
pragma solidity 0.8.24;
import "@openzeppelin/contracts/access/AccessControl.sol";
contract ManaSystem is AccessControl {
bytes32 public constant MINTER_ROLE = keccak256("MINTER_ROLE");
bytes32 public constant SPENDER_ROLE = keccak256("SPENDER_ROLE");
mapping(address => uint256) public userMana;
mapping(address => uint256) public lifetimeMana;
event ManaAdded(address indexed user, uint256 amount, uint256 newTotal, uint256 newLifetime);
event ManaSpent(address indexed user, uint256 amount, uint256 newTotal);
constructor() {
_grantRole(DEFAULT_ADMIN_ROLE, msg.sender);
}
function addMana(address user, uint256 amount) external onlyRole(MINTER_ROLE) {
userMana[user] += amount;
lifetimeMana[user] += amount;
emit ManaAdded(user, amount, userMana[user], lifetimeMana[user]);
}
function spendMana(address user, uint256 amount) external onlyRole(SPENDER_ROLE) {
require(userMana[user] >= amount, "Insufficient mana");
userMana[user] -= amount;
emit ManaSpent(user, amount, userMana[user]);
}
function getMana(address user) external view returns (uint256) {
return userMana[user];
}
function getLifetimeMana(address user) external view returns (uint256) {
return lifetimeMana[user];
}
}
文件 30 的 46:Math.sol
pragma solidity ^0.8.20;
library Math {
error MathOverflowedMulDiv();
enum Rounding {
Floor,
Ceil,
Trunc,
Expand
}
function tryAdd(uint256 a, uint256 b) internal pure returns (bool, uint256) {
unchecked {
uint256 c = a + b;
if (c < a) return (false, 0);
return (true, c);
}
}
function trySub(uint256 a, uint256 b) internal pure returns (bool, uint256) {
unchecked {
if (b > a) return (false, 0);
return (true, a - b);
}
}
function tryMul(uint256 a, uint256 b) internal pure returns (bool, uint256) {
unchecked {
if (a == 0) return (true, 0);
uint256 c = a * b;
if (c / a != b) return (false, 0);
return (true, c);
}
}
function tryDiv(uint256 a, uint256 b) internal pure returns (bool, uint256) {
unchecked {
if (b == 0) return (false, 0);
return (true, a / b);
}
}
function tryMod(uint256 a, uint256 b) internal pure returns (bool, uint256) {
unchecked {
if (b == 0) return (false, 0);
return (true, a % b);
}
}
function max(uint256 a, uint256 b) internal pure returns (uint256) {
return a > b ? a : b;
}
function min(uint256 a, uint256 b) internal pure returns (uint256) {
return a < b ? a : b;
}
function average(uint256 a, uint256 b) internal pure returns (uint256) {
return (a & b) + (a ^ b) / 2;
}
function ceilDiv(uint256 a, uint256 b) internal pure returns (uint256) {
if (b == 0) {
return a / b;
}
return a == 0 ? 0 : (a - 1) / b + 1;
}
function mulDiv(uint256 x, uint256 y, uint256 denominator) internal pure returns (uint256 result) {
unchecked {
uint256 prod0 = x * y;
uint256 prod1;
assembly {
let mm := mulmod(x, y, not(0))
prod1 := sub(sub(mm, prod0), lt(mm, prod0))
}
if (prod1 == 0) {
return prod0 / denominator;
}
if (denominator <= prod1) {
revert MathOverflowedMulDiv();
}
uint256 remainder;
assembly {
remainder := mulmod(x, y, denominator)
prod1 := sub(prod1, gt(remainder, prod0))
prod0 := sub(prod0, remainder)
}
uint256 twos = denominator & (0 - denominator);
assembly {
denominator := div(denominator, twos)
prod0 := div(prod0, twos)
twos := add(div(sub(0, twos), twos), 1)
}
prod0 |= prod1 * twos;
uint256 inverse = (3 * denominator) ^ 2;
inverse *= 2 - denominator * inverse;
inverse *= 2 - denominator * inverse;
inverse *= 2 - denominator * inverse;
inverse *= 2 - denominator * inverse;
inverse *= 2 - denominator * inverse;
inverse *= 2 - denominator * inverse;
result = prod0 * inverse;
return result;
}
}
function mulDiv(uint256 x, uint256 y, uint256 denominator, Rounding rounding) internal pure returns (uint256) {
uint256 result = mulDiv(x, y, denominator);
if (unsignedRoundsUp(rounding) && mulmod(x, y, denominator) > 0) {
result += 1;
}
return result;
}
function sqrt(uint256 a) internal pure returns (uint256) {
if (a == 0) {
return 0;
}
uint256 result = 1 << (log2(a) >> 1);
unchecked {
result = (result + a / result) >> 1;
result = (result + a / result) >> 1;
result = (result + a / result) >> 1;
result = (result + a / result) >> 1;
result = (result + a / result) >> 1;
result = (result + a / result) >> 1;
result = (result + a / result) >> 1;
return min(result, a / result);
}
}
function sqrt(uint256 a, Rounding rounding) internal pure returns (uint256) {
unchecked {
uint256 result = sqrt(a);
return result + (unsignedRoundsUp(rounding) && result * result < a ? 1 : 0);
}
}
function log2(uint256 value) internal pure returns (uint256) {
uint256 result = 0;
unchecked {
if (value >> 128 > 0) {
value >>= 128;
result += 128;
}
if (value >> 64 > 0) {
value >>= 64;
result += 64;
}
if (value >> 32 > 0) {
value >>= 32;
result += 32;
}
if (value >> 16 > 0) {
value >>= 16;
result += 16;
}
if (value >> 8 > 0) {
value >>= 8;
result += 8;
}
if (value >> 4 > 0) {
value >>= 4;
result += 4;
}
if (value >> 2 > 0) {
value >>= 2;
result += 2;
}
if (value >> 1 > 0) {
result += 1;
}
}
return result;
}
function log2(uint256 value, Rounding rounding) internal pure returns (uint256) {
unchecked {
uint256 result = log2(value);
return result + (unsignedRoundsUp(rounding) && 1 << result < value ? 1 : 0);
}
}
function log10(uint256 value) internal pure returns (uint256) {
uint256 result = 0;
unchecked {
if (value >= 10 ** 64) {
value /= 10 ** 64;
result += 64;
}
if (value >= 10 ** 32) {
value /= 10 ** 32;
result += 32;
}
if (value >= 10 ** 16) {
value /= 10 ** 16;
result += 16;
}
if (value >= 10 ** 8) {
value /= 10 ** 8;
result += 8;
}
if (value >= 10 ** 4) {
value /= 10 ** 4;
result += 4;
}
if (value >= 10 ** 2) {
value /= 10 ** 2;
result += 2;
}
if (value >= 10 ** 1) {
result += 1;
}
}
return result;
}
function log10(uint256 value, Rounding rounding) internal pure returns (uint256) {
unchecked {
uint256 result = log10(value);
return result + (unsignedRoundsUp(rounding) && 10 ** result < value ? 1 : 0);
}
}
function log256(uint256 value) internal pure returns (uint256) {
uint256 result = 0;
unchecked {
if (value >> 128 > 0) {
value >>= 128;
result += 16;
}
if (value >> 64 > 0) {
value >>= 64;
result += 8;
}
if (value >> 32 > 0) {
value >>= 32;
result += 4;
}
if (value >> 16 > 0) {
value >>= 16;
result += 2;
}
if (value >> 8 > 0) {
result += 1;
}
}
return result;
}
function log256(uint256 value, Rounding rounding) internal pure returns (uint256) {
unchecked {
uint256 result = log256(value);
return result + (unsignedRoundsUp(rounding) && 1 << (result << 3) < value ? 1 : 0);
}
}
function unsignedRoundsUp(Rounding rounding) internal pure returns (bool) {
return uint8(rounding) % 2 == 1;
}
}
文件 31 的 46:Multicall.sol
pragma solidity ^0.8.20;
import {Address} from "./Address.sol";
import {Context} from "./Context.sol";
abstract contract Multicall is Context {
function multicall(bytes[] calldata data) external virtual returns (bytes[] memory results) {
bytes memory context = msg.sender == _msgSender()
? new bytes(0)
: msg.data[msg.data.length - _contextSuffixLength():];
results = new bytes[](data.length);
for (uint256 i = 0; i < data.length; i++) {
results[i] = Address.functionDelegateCall(address(this), bytes.concat(data[i], context));
}
return results;
}
}
文件 32 的 46:Oracle.sol
pragma solidity 0.8.24;
import "@openzeppelin/contracts/access/Ownable.sol";
import "@openzeppelin/contracts/utils/math/Math.sol";
import "@openzeppelin/contracts/token/ERC20/extensions/IERC20Metadata.sol";
interface IOffchainOracle {
function getRateToEth(address srcToken, bool useSrcWrappers) external view returns (uint256 weightedRate);
}
contract Oracle is Ownable {
using Math for uint256;
address public constant DEAD_ADDRESS = 0x000000000000000000000000000000000000dEaD;
uint256 private constant BASIS_POINTS = 10000;
IOffchainOracle public immutable offchainOracle;
address public daoAddress;
struct PaymentTokenConfig {
uint256 burnBasisPoints;
uint256 daoBasisPoints;
uint256 partnerBasisPoints;
address partnerWallet;
bool isStablecoin;
bool isWhitelisted;
}
mapping(address => PaymentTokenConfig) public tokenConfigs;
event TokenConfigUpdated(
address indexed tokenAddress,
uint256 burnBasisPoints,
uint256 daoBasisPoints,
uint256 partnerBasisPoints,
address partnerWallet,
bool isStablecoin,
bool isWhitelisted
);
constructor(address _offchainOracle) Ownable(msg.sender) {
offchainOracle = IOffchainOracle(_offchainOracle);
daoAddress = msg.sender;
}
function isTokenWhitelisted(address paymentToken) external view returns (bool) {
return tokenConfigs[paymentToken].isWhitelisted;
}
function calculateTokenAmount(
address paymentToken,
uint256 assetNativeCost,
uint256 quantity
) public view returns (uint256 tokenPriceNative, uint256 tokenAmount) {
uint256 decimals = IERC20Metadata(paymentToken).decimals();
tokenPriceNative = getPriceInNative(paymentToken);
tokenAmount = (assetNativeCost * quantity * 10 ** decimals * 10 ** 18) / tokenPriceNative;
tokenAmount = (tokenAmount + 10 ** 18 - 1) / 10 ** 18;
}
function getPriceInNative(address token) public view returns (uint256) {
uint256 rate = offchainOracle.getRateToEth(token, true);
uint8 tokenDecimals = IERC20Metadata(token).decimals();
if (tokenDecimals < 18) {
rate = rate / 10 ** (18 - tokenDecimals);
} else if (tokenDecimals > 18) {
rate = rate * 10 ** (tokenDecimals - 18);
}
return rate;
}
function setTokenConfig(
address paymentToken,
uint256 burnBasisPoints,
uint256 daoBasisPoints,
uint256 partnerBasisPoints,
address partnerWallet,
bool isStablecoin,
bool isWhitelisted
) external onlyOwner {
require(burnBasisPoints + daoBasisPoints + partnerBasisPoints == BASIS_POINTS, "Basis points must add up to 10000");
tokenConfigs[paymentToken] = PaymentTokenConfig(
burnBasisPoints,
daoBasisPoints,
partnerBasisPoints,
partnerWallet,
isStablecoin,
isWhitelisted
);
emit TokenConfigUpdated(
paymentToken,
burnBasisPoints,
daoBasisPoints,
partnerBasisPoints,
partnerWallet,
isStablecoin,
isWhitelisted
);
}
function calculateTokenDistribution(
address paymentToken,
uint256 totalAmount
) external view returns (uint256 burnAmount, uint256 daoAmount, uint256 partnerAmount, address partnerWallet) {
PaymentTokenConfig memory config = tokenConfigs[paymentToken];
require(config.isWhitelisted, "Payment token not whitelisted");
burnAmount = (totalAmount * config.burnBasisPoints) / BASIS_POINTS;
daoAmount = (totalAmount * config.daoBasisPoints) / BASIS_POINTS;
partnerAmount = (totalAmount * config.partnerBasisPoints) / BASIS_POINTS;
partnerWallet = config.partnerWallet;
}
function setDaoAddress(address _daoAddress) external onlyOwner {
require(_daoAddress != address(0), "Invalid DAO address");
daoAddress = _daoAddress;
}
}
文件 33 的 46:Ownable.sol
pragma solidity ^0.8.20;
import {Context} from "../utils/Context.sol";
abstract contract Ownable is Context {
address private _owner;
error OwnableUnauthorizedAccount(address account);
error OwnableInvalidOwner(address owner);
event OwnershipTransferred(address indexed previousOwner, address indexed newOwner);
constructor(address initialOwner) {
if (initialOwner == address(0)) {
revert OwnableInvalidOwner(address(0));
}
_transferOwnership(initialOwner);
}
modifier onlyOwner() {
_checkOwner();
_;
}
function owner() public view virtual returns (address) {
return _owner;
}
function _checkOwner() internal view virtual {
if (owner() != _msgSender()) {
revert OwnableUnauthorizedAccount(_msgSender());
}
}
function renounceOwnership() public virtual onlyOwner {
_transferOwnership(address(0));
}
function transferOwnership(address newOwner) public virtual onlyOwner {
if (newOwner == address(0)) {
revert OwnableInvalidOwner(address(0));
}
_transferOwnership(newOwner);
}
function _transferOwnership(address newOwner) internal virtual {
address oldOwner = _owner;
_owner = newOwner;
emit OwnershipTransferred(oldOwner, newOwner);
}
}
文件 34 的 46:Pausable.sol
pragma solidity ^0.8.20;
import {Context} from "../utils/Context.sol";
abstract contract Pausable is Context {
bool private _paused;
event Paused(address account);
event Unpaused(address account);
error EnforcedPause();
error ExpectedPause();
constructor() {
_paused = false;
}
modifier whenNotPaused() {
_requireNotPaused();
_;
}
modifier whenPaused() {
_requirePaused();
_;
}
function paused() public view virtual returns (bool) {
return _paused;
}
function _requireNotPaused() internal view virtual {
if (paused()) {
revert EnforcedPause();
}
}
function _requirePaused() internal view virtual {
if (!paused()) {
revert ExpectedPause();
}
}
function _pause() internal virtual whenNotPaused {
_paused = true;
emit Paused(_msgSender());
}
function _unpause() internal virtual whenPaused {
_paused = false;
emit Unpaused(_msgSender());
}
}
文件 35 的 46:PermissionCallable.sol
pragma solidity ^0.8.23;
import {Address} from "openzeppelin-contracts/contracts/utils/Address.sol";
import {Multicall} from "openzeppelin-contracts/contracts/utils/Multicall.sol";
import {IPermissionCallable} from "./IPermissionCallable.sol";
abstract contract PermissionCallable is IPermissionCallable {
error NotPermissionCallable(bytes4 selector);
function permissionedCall(bytes calldata call) external payable returns (bytes memory res) {
if (!supportsPermissionedCallSelector(bytes4(call))) revert NotPermissionCallable(bytes4(call));
return Address.functionDelegateCall(address(this), call);
}
function supportsPermissionedCallSelector(bytes4 selector) public view virtual returns (bool);
}
文件 36 的 46:ReentrancyGuard.sol
pragma solidity ^0.8.20;
abstract contract ReentrancyGuard {
uint256 private constant NOT_ENTERED = 1;
uint256 private constant ENTERED = 2;
uint256 private _status;
error ReentrancyGuardReentrantCall();
constructor() {
_status = NOT_ENTERED;
}
modifier nonReentrant() {
_nonReentrantBefore();
_;
_nonReentrantAfter();
}
function _nonReentrantBefore() private {
if (_status == ENTERED) {
revert ReentrancyGuardReentrantCall();
}
_status = ENTERED;
}
function _nonReentrantAfter() private {
_status = NOT_ENTERED;
}
function _reentrancyGuardEntered() internal view returns (bool) {
return _status == ENTERED;
}
}
文件 37 的 46:SafeERC20.sol
pragma solidity ^0.8.20;
import {IERC20} from "../IERC20.sol";
import {IERC20Permit} from "../extensions/IERC20Permit.sol";
import {Address} from "../../../utils/Address.sol";
library SafeERC20 {
using Address for address;
error SafeERC20FailedOperation(address token);
error SafeERC20FailedDecreaseAllowance(address spender, uint256 currentAllowance, uint256 requestedDecrease);
function safeTransfer(IERC20 token, address to, uint256 value) internal {
_callOptionalReturn(token, abi.encodeCall(token.transfer, (to, value)));
}
function safeTransferFrom(IERC20 token, address from, address to, uint256 value) internal {
_callOptionalReturn(token, abi.encodeCall(token.transferFrom, (from, to, value)));
}
function safeIncreaseAllowance(IERC20 token, address spender, uint256 value) internal {
uint256 oldAllowance = token.allowance(address(this), spender);
forceApprove(token, spender, oldAllowance + value);
}
function safeDecreaseAllowance(IERC20 token, address spender, uint256 requestedDecrease) internal {
unchecked {
uint256 currentAllowance = token.allowance(address(this), spender);
if (currentAllowance < requestedDecrease) {
revert SafeERC20FailedDecreaseAllowance(spender, currentAllowance, requestedDecrease);
}
forceApprove(token, spender, currentAllowance - requestedDecrease);
}
}
function forceApprove(IERC20 token, address spender, uint256 value) internal {
bytes memory approvalCall = abi.encodeCall(token.approve, (spender, value));
if (!_callOptionalReturnBool(token, approvalCall)) {
_callOptionalReturn(token, abi.encodeCall(token.approve, (spender, 0)));
_callOptionalReturn(token, approvalCall);
}
}
function _callOptionalReturn(IERC20 token, bytes memory data) private {
bytes memory returndata = address(token).functionCall(data);
if (returndata.length != 0 && !abi.decode(returndata, (bool))) {
revert SafeERC20FailedOperation(address(token));
}
}
function _callOptionalReturnBool(IERC20 token, bytes memory data) private returns (bool) {
(bool success, bytes memory returndata) = address(token).call(data);
return success && (returndata.length == 0 || abi.decode(returndata, (bool))) && address(token).code.length > 0;
}
}
文件 38 的 46:SignedMath.sol
pragma solidity ^0.8.20;
library SignedMath {
function max(int256 a, int256 b) internal pure returns (int256) {
return a > b ? a : b;
}
function min(int256 a, int256 b) internal pure returns (int256) {
return a < b ? a : b;
}
function average(int256 a, int256 b) internal pure returns (int256) {
int256 x = (a & b) + ((a ^ b) >> 1);
return x + (int256(uint256(x) >> 255) & (a ^ b));
}
function abs(int256 n) internal pure returns (uint256) {
unchecked {
return uint256(n >= 0 ? n : -n);
}
}
}
文件 39 的 46:StorageSlot.sol
pragma solidity ^0.8.20;
library StorageSlot {
struct AddressSlot {
address value;
}
struct BooleanSlot {
bool value;
}
struct Bytes32Slot {
bytes32 value;
}
struct Uint256Slot {
uint256 value;
}
struct StringSlot {
string value;
}
struct BytesSlot {
bytes value;
}
function getAddressSlot(bytes32 slot) internal pure returns (AddressSlot storage r) {
assembly {
r.slot := slot
}
}
function getBooleanSlot(bytes32 slot) internal pure returns (BooleanSlot storage r) {
assembly {
r.slot := slot
}
}
function getBytes32Slot(bytes32 slot) internal pure returns (Bytes32Slot storage r) {
assembly {
r.slot := slot
}
}
function getUint256Slot(bytes32 slot) internal pure returns (Uint256Slot storage r) {
assembly {
r.slot := slot
}
}
function getStringSlot(bytes32 slot) internal pure returns (StringSlot storage r) {
assembly {
r.slot := slot
}
}
function getStringSlot(string storage store) internal pure returns (StringSlot storage r) {
assembly {
r.slot := store.slot
}
}
function getBytesSlot(bytes32 slot) internal pure returns (BytesSlot storage r) {
assembly {
r.slot := slot
}
}
function getBytesSlot(bytes storage store) internal pure returns (BytesSlot storage r) {
assembly {
r.slot := store.slot
}
}
}
文件 40 的 46:Strings.sol
pragma solidity ^0.8.20;
import {Math} from "./math/Math.sol";
import {SignedMath} from "./math/SignedMath.sol";
library Strings {
bytes16 private constant HEX_DIGITS = "0123456789abcdef";
uint8 private constant ADDRESS_LENGTH = 20;
error StringsInsufficientHexLength(uint256 value, uint256 length);
function toString(uint256 value) internal pure returns (string memory) {
unchecked {
uint256 length = Math.log10(value) + 1;
string memory buffer = new string(length);
uint256 ptr;
assembly {
ptr := add(buffer, add(32, length))
}
while (true) {
ptr--;
assembly {
mstore8(ptr, byte(mod(value, 10), HEX_DIGITS))
}
value /= 10;
if (value == 0) break;
}
return buffer;
}
}
function toStringSigned(int256 value) internal pure returns (string memory) {
return string.concat(value < 0 ? "-" : "", toString(SignedMath.abs(value)));
}
function toHexString(uint256 value) internal pure returns (string memory) {
unchecked {
return toHexString(value, Math.log256(value) + 1);
}
}
function toHexString(uint256 value, uint256 length) internal pure returns (string memory) {
uint256 localValue = value;
bytes memory buffer = new bytes(2 * length + 2);
buffer[0] = "0";
buffer[1] = "x";
for (uint256 i = 2 * length + 1; i > 1; --i) {
buffer[i] = HEX_DIGITS[localValue & 0xf];
localValue >>= 4;
}
if (localValue != 0) {
revert StringsInsufficientHexLength(value, length);
}
return string(buffer);
}
function toHexString(address addr) internal pure returns (string memory) {
return toHexString(uint256(uint160(addr)), ADDRESS_LENGTH);
}
function equal(string memory a, string memory b) internal pure returns (bool) {
return bytes(a).length == bytes(b).length && keccak256(bytes(a)) == keccak256(bytes(b));
}
}
文件 41 的 46:TokiemonBattles.sol
pragma solidity 0.8.24;
import "@openzeppelin/contracts/access/AccessControl.sol";
import "@openzeppelin/contracts/utils/math/Math.sol";
import "@openzeppelin/contracts/token/ERC20/IERC20.sol";
import "@openzeppelin/contracts/token/ERC20/utils/SafeERC20.sol";
import "./TokiemonNFT.sol";
import "./ManaSystem.sol";
import "./TokiemonEquipment.sol";
import "./TokiemonEnergy.sol";
import "./Oracle.sol";
contract TokiemonBattles is AccessControl {
using SafeERC20 for IERC20;
TokiemonNFT public tokiemonNFT;
ManaSystem public manaSystem;
TokiemonEquipment public tokiemonEquipment;
TokiemonEnergy public tokiemonEnergy;
Oracle public oracle;
bytes32 public constant MINTER_ROLE = keccak256("MINTER_ROLE");
uint256 public freeTier = 4;
uint256 public battleCount = 60000;
uint256 public nextBattlePackId;
uint256 public criticalEnergyThreshold;
int256 public criticalEnergyProbability;
EnergyThreshold[] public energyThresholds;
RewardConfig public rewardConfig;
DailyBattleSettings public dailyBattleSettings;
BattleSettings public battleSettings;
mapping(uint256 => BattlePack) public battlePacks;
mapping(uint256 => Battle) public battles;
mapping(uint256 => uint256) public purchasedBattles;
mapping(uint256 => uint256) public lastBattleTimestamp;
mapping(uint256 => uint256) public availableBattles;
mapping(uint256 => uint256) public regenSurplus;
mapping(address => uint256) public lastDailyResetForFreeTierByAddress;
mapping(address => uint256) public dailyFreeTierBattlesUsedByAddress;
mapping(uint256 => uint256) public tierManaPerBattle;
event BattleComplete(
uint256 battleId,
uint256 indexed winner,
uint256 indexed challenger,
uint256 indexed opponent,
address challengerAddress,
address opponentAddress,
AttackStyle challengerStyle,
AttackStyle opponentStyle,
uint256 challengerTotalLevel,
uint256 opponentTotalLevel,
uint8[3] challengerSkillLevels,
uint8[3] opponentSkillLevels
);
event BattlePackPurchased(uint256 tokenId, uint256 numberOfBattles);
struct RewardConfig {
uint256 minReward;
uint256 maxReward;
uint256 maxLevel;
uint256 freeTierMultiplier;
uint256 bonusMultiplier;
uint256 idleMultiplier;
}
struct DailyBattleSettings {
uint256 freeMaxDailyBattles;
uint256 freeBattleRegenerationTime;
uint256 standardMaxDailyBattles;
uint256 standardBattleRegenerationTime;
uint256 maxFreeTierDailyBattlesPerAddress;
}
struct BattleSettings {
bool equipmentOn;
bool energyOn;
uint8 energyLossChallenger;
uint8 energyLossOpponent;
}
struct BattlePack {
uint256 price;
uint256 numberOfBattles;
bool active;
}
struct Battle {
uint256 winner;
uint256 challenger;
uint256 opponent;
address challengerAddress;
address opponentAddress;
AttackStyle challengerStyle;
AttackStyle opponentStyle;
uint256 challengerTotalLevel;
uint256 opponentTotalLevel;
uint8[3] challengerSkillLevels;
uint8[3] opponentSkillLevels;
}
struct BattleComputationData {
uint8[3] challengerSkillLevels;
uint8[3] opponentSkillLevels;
uint256 challengerTotalLevel;
uint256 opponentTotalLevel;
uint256 challengerStyleLevel;
uint256 opponentStyleLevel;
TokiemonNFT.Rarity challengerRarity;
TokiemonNFT.Rarity opponentRarity;
AttackStyle challengerStyle;
AttackStyle opponentStyle;
TokiemonEquipment.ItemEffect challengerEquipmentEffect;
TokiemonEquipment.ItemEffect opponentEquipmentEffect;
TokiemonEnergy.TokiemonEnergyStatus challengerEnergyStatus;
}
struct EnergyThreshold {
uint256 threshold;
int256 adjustment;
}
enum AttackStyle {
Attack,
Defense,
Magic
}
constructor(address _tokiemonNFT, address _manaSystem, address _tokiemonEquipment, address _tokiemonEnergy, address _oracle) {
tokiemonNFT = TokiemonNFT(_tokiemonNFT);
manaSystem = ManaSystem(_manaSystem);
tokiemonEquipment = TokiemonEquipment(_tokiemonEquipment);
tokiemonEnergy = TokiemonEnergy(_tokiemonEnergy);
oracle = Oracle(_oracle);
_grantRole(DEFAULT_ADMIN_ROLE, msg.sender);
_grantRole(MINTER_ROLE, msg.sender);
rewardConfig = RewardConfig({
minReward: 22,
maxReward: 222,
maxLevel: 297,
freeTierMultiplier: 100,
bonusMultiplier: 100,
idleMultiplier: 20
});
dailyBattleSettings = DailyBattleSettings({
freeMaxDailyBattles: 2,
freeBattleRegenerationTime: 12 hours,
standardMaxDailyBattles: 4,
standardBattleRegenerationTime: 6 hours,
maxFreeTierDailyBattlesPerAddress: 5
});
battleSettings = BattleSettings({equipmentOn: true, energyOn: true, energyLossChallenger: 10, energyLossOpponent: 3});
energyThresholds.push(EnergyThreshold(25, -10));
energyThresholds.push(EnergyThreshold(50, -5));
energyThresholds.push(EnergyThreshold(80, 0));
energyThresholds.push(EnergyThreshold(100, 5));
criticalEnergyThreshold = 10;
criticalEnergyProbability = 25;
tierManaPerBattle[1] = 5;
tierManaPerBattle[2] = 7;
tierManaPerBattle[3] = 10;
}
function getTotalBattlesLeft(uint256 tokenId) public view returns (uint256) {
return getAvailableBattles(tokenId) + purchasedBattles[tokenId];
}
function getBattleResult(uint256 battleId) external view returns (Battle memory) {
return battles[battleId];
}
function getBattlesLeftBreakdown(uint256 tokenId) public view returns (uint256, uint256) {
return (purchasedBattles[tokenId], getAvailableBattles(tokenId));
}
function getAvailableBattles(uint256 tokenId) public view returns (uint256) {
(, , uint256 purchaseTier, ) = tokiemonNFT.getTokiemonData(tokenId);
uint256 maxDailyBattles = (purchaseTier == freeTier)
? dailyBattleSettings.freeMaxDailyBattles
: dailyBattleSettings.standardMaxDailyBattles;
uint256 regenerationTime = (purchaseTier == freeTier)
? dailyBattleSettings.freeBattleRegenerationTime
: dailyBattleSettings.standardBattleRegenerationTime;
uint256 elapsedTime = block.timestamp - lastBattleTimestamp[tokenId];
uint256 regeneratedBattles = (elapsedTime + regenSurplus[tokenId]) / regenerationTime;
return Math.min(availableBattles[tokenId] + regeneratedBattles, maxDailyBattles);
}
function getNextBattleRegenerationTime(uint256 tokenId) public view returns (uint256) {
(, , uint256 purchaseTier, ) = tokiemonNFT.getTokiemonData(tokenId);
uint256 maxDailyBattles = (purchaseTier == freeTier)
? dailyBattleSettings.freeMaxDailyBattles
: dailyBattleSettings.standardMaxDailyBattles;
uint256 regenerationTime = (purchaseTier == freeTier)
? dailyBattleSettings.freeBattleRegenerationTime
: dailyBattleSettings.standardBattleRegenerationTime;
if (getAvailableBattles(tokenId) >= maxDailyBattles) {
return 0;
}
uint256 elapsedTime = block.timestamp - lastBattleTimestamp[tokenId];
uint256 timeUntilNextBattle = regenerationTime - ((elapsedTime + regenSurplus[tokenId]) % regenerationTime);
return block.timestamp + timeUntilNextBattle;
}
function getRemainingDailyFreeTierBattles(address player) public view returns (uint256) {
uint256 currentDay = block.timestamp / 1 days;
uint256 lastResetDay = lastDailyResetForFreeTierByAddress[player] / 1 days;
if (currentDay > lastResetDay) {
return dailyBattleSettings.maxFreeTierDailyBattlesPerAddress;
}
return dailyBattleSettings.maxFreeTierDailyBattlesPerAddress - dailyFreeTierBattlesUsedByAddress[player];
}
function getEnergyProbabilityImpact(uint256 currentEnergy) public view returns (int256) {
if (currentEnergy <= criticalEnergyThreshold) {
return -9975;
}
int256 energyProbabilityImpact = energyThresholds[energyThresholds.length - 1].adjustment;
for (uint256 i = 0; i < energyThresholds.length; i++) {
if (currentEnergy <= energyThresholds[i].threshold) {
energyProbabilityImpact = energyThresholds[i].adjustment;
break;
}
}
return energyProbabilityImpact;
}
function calculateBattleReward(uint256 winnerTokenId, uint256 loserTokenId, uint256 challengerTokenId) public view returns (uint256) {
(, uint256 totalLevel, ) = getSkillInfo(loserTokenId);
(, , uint256 purchaseTier, ) = tokiemonNFT.getTokiemonData(winnerTokenId);
uint256 reward = totalLevel +
rewardConfig.minReward +
((rewardConfig.maxReward - rewardConfig.minReward) * (Math.log2(totalLevel) - 1)) /
(Math.log2(rewardConfig.maxLevel) - 1);
reward = Math.min(Math.max(reward, rewardConfig.minReward), rewardConfig.maxReward + totalLevel);
bool isFreeTier = purchaseTier == freeTier;
if (isFreeTier) {
reward = (reward * rewardConfig.freeTierMultiplier) / 100;
}
if (winnerTokenId != challengerTokenId) {
reward = (reward * rewardConfig.idleMultiplier) / 100;
}
return (reward * rewardConfig.bonusMultiplier) / 100;
}
function getSkillInfo(uint256 tokenId) public view returns (uint8[3] memory skillLevels, uint256 totalLevel, AttackStyle bestSkill) {
TokiemonNFT.Skill memory attackSkill = tokiemonNFT.getTokiemonSkill(tokenId, uint256(AttackStyle.Attack));
TokiemonNFT.Skill memory defenseSkill = tokiemonNFT.getTokiemonSkill(tokenId, uint256(AttackStyle.Defense));
TokiemonNFT.Skill memory magicSkill = tokiemonNFT.getTokiemonSkill(tokenId, uint256(AttackStyle.Magic));
skillLevels = [attackSkill.level, defenseSkill.level, magicSkill.level];
totalLevel = uint256(attackSkill.level) + uint256(defenseSkill.level) + uint256(magicSkill.level);
if (attackSkill.level >= defenseSkill.level && attackSkill.level >= magicSkill.level) {
bestSkill = AttackStyle.Attack;
} else if (defenseSkill.level >= magicSkill.level) {
bestSkill = AttackStyle.Defense;
} else {
bestSkill = AttackStyle.Magic;
}
}
function buyBattles(uint256 _tokenId, uint256 _packId) external payable {
BattlePack memory pack = battlePacks[_packId];
require(pack.numberOfBattles > 0, "Invalid battle pack");
require(pack.active, "Battle pack is not active");
require(msg.value >= pack.price, "Insufficient payment");
_processBattlePurchase(_tokenId, _packId);
}
function buyBattlesWithERC20(uint256 _tokenId, uint256 _packId, address paymentToken, uint256 maxPaymentTokenAmount) external {
BattlePack memory pack = battlePacks[_packId];
require(pack.numberOfBattles > 0, "Invalid battle pack");
require(pack.active, "Battle pack is not active");
require(oracle.isTokenWhitelisted(paymentToken), "Payment token not whitelisted");
(, uint256 tokenAmount) = oracle.calculateTokenAmount(paymentToken, pack.price, 1);
require(tokenAmount <= maxPaymentTokenAmount, "Slippage: amount exceeds maximum");
(uint256 burnAmount, uint256 daoAmount, uint256 partnerAmount, address partnerWallet) = oracle.calculateTokenDistribution(
paymentToken,
tokenAmount
);
transferTokens(IERC20(paymentToken), burnAmount, daoAmount, partnerAmount, partnerWallet);
_processBattlePurchase(_tokenId, _packId);
}
function trainBatch(uint256[] calldata tokenIds) external {
uint256 totalMana = 0;
for (uint256 i = 0; i < tokenIds.length; i++) {
uint256 tokenId = tokenIds[i];
require(tokiemonNFT.ownerOf(tokenId) == msg.sender, "You don't own the Tokiemon");
(, , uint256 purchaseTier, ) = tokiemonNFT.getTokiemonData(tokenId);
require(purchaseTier != 4, "Free tier Tokiemon cannot train");
uint256 manaPerBattle = tierManaPerBattle[purchaseTier];
_updateAvailableBattles(tokenId);
uint256 availableBattlesCount = getAvailableBattles(tokenId);
totalMana += manaPerBattle * availableBattlesCount;
availableBattles[tokenId] = 0;
}
manaSystem.addMana(msg.sender, totalMana);
}
function battle(uint256 challengerTokenId, uint256 opponentTokenId, AttackStyle challengerStyle) external {
(, , uint256 purchaseTier, ) = tokiemonNFT.getTokiemonData(challengerTokenId);
bool isFreeTier = purchaseTier == freeTier;
require(tokiemonNFT.ownerOf(challengerTokenId) == msg.sender, "You don't own the challenger Tokiemon");
require(tokiemonNFT.ownerOf(opponentTokenId) != address(0), "Opponent Tokiemon doesn't exist");
require(challengerTokenId != opponentTokenId, "Don't hurt yourself!");
require(tokiemonNFT.ownerOf(opponentTokenId) != msg.sender, "Cannot battle against your own Tokiemon");
if (lastBattleTimestamp[challengerTokenId] == 0) {
lastBattleTimestamp[challengerTokenId] = block.timestamp - 24 hours;
}
_updateAvailableBattles(challengerTokenId);
uint256 battlesLeft = getTotalBattlesLeft(challengerTokenId);
require(battlesLeft > 0, "No battles left");
bool usingRegeneratedBattle = availableBattles[challengerTokenId] > 0;
if (isFreeTier && usingRegeneratedBattle) {
_checkAndUpdateDailyFreeTierLimit(msg.sender);
require(
dailyFreeTierBattlesUsedByAddress[msg.sender] < dailyBattleSettings.maxFreeTierDailyBattlesPerAddress ||
purchasedBattles[challengerTokenId] > 0,
"Daily free tier battle limit reached"
);
}
if (usingRegeneratedBattle) {
availableBattles[challengerTokenId]--;
if (isFreeTier) {
dailyFreeTierBattlesUsedByAddress[msg.sender]++;
}
} else {
purchasedBattles[challengerTokenId]--;
}
(, , AttackStyle opponentStyle) = getSkillInfo(opponentTokenId);
uint256 winner = generateWinner(challengerTokenId, opponentTokenId, challengerStyle);
uint256 loser = winner == challengerTokenId ? opponentTokenId : challengerTokenId;
uint256 reward = calculateBattleReward(winner, loser, challengerTokenId);
manaSystem.addMana(tokiemonNFT.ownerOf(winner), reward);
if (battleSettings.energyOn) {
if (loser == challengerTokenId) {
tokiemonEnergy.decreaseEnergy(challengerTokenId, battleSettings.energyLossChallenger);
} else {
tokiemonEnergy.decreaseEnergy(opponentTokenId, battleSettings.energyLossOpponent);
}
}
_createBattleAndEmitEvent(challengerTokenId, opponentTokenId, challengerStyle, opponentStyle, winner);
}
function generateWinner(
uint256 challengerTokenId,
uint256 opponentTokenId,
AttackStyle challengerStyle
) private view returns (uint256 winner) {
uint256 winProbability = calculateWinProbability(challengerTokenId, opponentTokenId, challengerStyle);
uint256 randomNumber = uint256(
keccak256(abi.encodePacked(block.timestamp, block.prevrandao, msg.sender, challengerTokenId, opponentTokenId))
) % 10000;
winner = randomNumber < winProbability ? challengerTokenId : opponentTokenId;
return winner;
}
function calculateWinProbability(
uint256 challengerTokenId,
uint256 opponentTokenId,
AttackStyle challengerStyle
) public view returns (uint256) {
BattleComputationData memory data = _initializeBattleData(challengerTokenId, opponentTokenId, challengerStyle);
int256 winProbability = 5000;
winProbability = _calculateStyleMatchup(winProbability, data.challengerStyle, data.opponentStyle);
winProbability = _calculateRarityDifferential(winProbability, data.challengerRarity, data.opponentRarity);
winProbability = _calculateLevelDifferentials(
winProbability,
data.challengerStyleLevel,
data.opponentStyleLevel,
data.challengerTotalLevel,
data.opponentTotalLevel
);
if (battleSettings.equipmentOn) {
winProbability = _applyEquipmentImpact(winProbability, data.challengerEquipmentEffect, data.opponentEquipmentEffect);
}
if (battleSettings.energyOn) {
winProbability = _applyEnergyImpact(winProbability, data.challengerEnergyStatus);
}
if (winProbability < 0) {
winProbability = 0;
}
return uint256(Math.max(Math.min(9975, uint256(winProbability)), 25));
}
function _initializeBattleData(
uint256 challengerTokenId,
uint256 opponentTokenId,
AttackStyle challengerStyle
) private view returns (BattleComputationData memory data) {
(data.challengerSkillLevels, data.challengerTotalLevel, ) = getSkillInfo(challengerTokenId);
(data.opponentSkillLevels, data.opponentTotalLevel, data.opponentStyle) = getSkillInfo(opponentTokenId);
data.challengerStyleLevel = data.challengerSkillLevels[uint256(challengerStyle)];
data.opponentStyleLevel = data.opponentSkillLevels[uint256(data.opponentStyle)];
data.challengerStyle = challengerStyle;
(, , , data.challengerRarity) = tokiemonNFT.getTokiemonData(challengerTokenId);
(, , , data.opponentRarity) = tokiemonNFT.getTokiemonData(opponentTokenId);
if (battleSettings.equipmentOn) {
data.challengerEquipmentEffect = tokiemonEquipment.getTotalEquipmentEffect(challengerTokenId);
data.opponentEquipmentEffect = tokiemonEquipment.getTotalEquipmentEffect(opponentTokenId);
data = _applyEquipmentBonuses(data);
}
if (battleSettings.energyOn) {
data.challengerEnergyStatus = tokiemonEnergy.getTokiemonEnergyStatus(challengerTokenId);
data = _applyEnergyBonuses(data);
}
return data;
}
function _applyEquipmentBonuses(BattleComputationData memory data) private pure returns (BattleComputationData memory) {
data.challengerTotalLevel +=
data.challengerEquipmentEffect.attackBonus +
data.challengerEquipmentEffect.defenseBonus +
data.challengerEquipmentEffect.magicBonus;
data.opponentTotalLevel +=
data.opponentEquipmentEffect.attackBonus +
data.opponentEquipmentEffect.defenseBonus +
data.opponentEquipmentEffect.magicBonus;
data.challengerStyleLevel += data.challengerStyle == AttackStyle.Attack
? data.challengerEquipmentEffect.attackBonus
: data.challengerStyle == AttackStyle.Defense
? data.challengerEquipmentEffect.defenseBonus
: data.challengerEquipmentEffect.magicBonus;
data.opponentStyleLevel += data.opponentStyle == AttackStyle.Attack
? data.opponentEquipmentEffect.attackBonus
: data.opponentStyle == AttackStyle.Defense
? data.opponentEquipmentEffect.defenseBonus
: data.opponentEquipmentEffect.magicBonus;
return data;
}
function _applyEnergyBonuses(BattleComputationData memory data) private view returns (BattleComputationData memory) {
if (
data.challengerEnergyStatus.skillImpactExpiry > block.timestamp &&
data.challengerEnergyStatus.skillId == uint256(data.challengerStyle)
) {
if (data.challengerEnergyStatus.skillImpactIncrease) {
data.challengerStyleLevel += data.challengerEnergyStatus.skillImpactAmount;
} else {
data.challengerStyleLevel -= data.challengerEnergyStatus.skillImpactAmount;
}
}
return data;
}
function _calculateStyleMatchup(
int256 winProbability,
AttackStyle challengerStyle,
AttackStyle opponentStyle
) private pure returns (int256) {
if (
(challengerStyle == AttackStyle.Attack && opponentStyle == AttackStyle.Magic) ||
(challengerStyle == AttackStyle.Defense && opponentStyle == AttackStyle.Attack) ||
(challengerStyle == AttackStyle.Magic && opponentStyle == AttackStyle.Defense)
) {
return winProbability + 500;
} else if (challengerStyle != opponentStyle) {
return winProbability - 500;
}
return winProbability;
}
function _calculateRarityDifferential(
int256 winProbability,
TokiemonNFT.Rarity challengerRarity,
TokiemonNFT.Rarity opponentRarity
) private pure returns (int256) {
return winProbability + (int256(uint256(challengerRarity)) - int256(uint256(opponentRarity))) * 300;
}
function _calculateLevelDifferentials(
int256 winProbability,
uint256 challengerStyleLevel,
uint256 opponentStyleLevel,
uint256 challengerTotalLevel,
uint256 opponentTotalLevel
) private pure returns (int256) {
int256 styleLevelDiff = int256(challengerStyleLevel) - int256(opponentStyleLevel);
int256 totalLevelDiff = int256(challengerTotalLevel) - int256(opponentTotalLevel);
return winProbability + (styleLevelDiff * 200) + (totalLevelDiff * 75);
}
function _applyEquipmentImpact(
int256 winProbability,
TokiemonEquipment.ItemEffect memory challengerEquipmentEffect,
TokiemonEquipment.ItemEffect memory opponentEquipmentEffect
) private pure returns (int256) {
int256 equipmentDiff = int256(challengerEquipmentEffect.overallBonus) - int256(opponentEquipmentEffect.overallBonus);
return (winProbability * (100 + equipmentDiff)) / 100;
}
function _applyEnergyImpact(
int256 winProbability,
TokiemonEnergy.TokiemonEnergyStatus memory challengerEnergyStatus
) private view returns (int256) {
if (challengerEnergyStatus.currentEnergy <= criticalEnergyThreshold) {
return criticalEnergyProbability;
}
int256 energyAdjustment = energyThresholds[energyThresholds.length - 1].adjustment;
for (uint256 i = 0; i < energyThresholds.length; i++) {
if (challengerEnergyStatus.currentEnergy <= energyThresholds[i].threshold) {
energyAdjustment = energyThresholds[i].adjustment;
break;
}
}
return (winProbability * (100 + energyAdjustment)) / 100;
}
function _createBattleAndEmitEvent(
uint256 challengerTokenId,
uint256 opponentTokenId,
AttackStyle challengerStyle,
AttackStyle opponentStyle,
uint256 winner
) private {
(uint8[3] memory challengerSkillLevels, uint256 challengerTotalLevel, ) = getSkillInfo(challengerTokenId);
(uint8[3] memory opponentSkillLevels, uint256 opponentTotalLevel, ) = getSkillInfo(opponentTokenId);
address opponentAddress = tokiemonNFT.ownerOf(opponentTokenId);
battles[battleCount] = Battle(
winner,
challengerTokenId,
opponentTokenId,
msg.sender,
opponentAddress,
challengerStyle,
opponentStyle,
challengerTotalLevel,
opponentTotalLevel,
challengerSkillLevels,
opponentSkillLevels
);
emit BattleComplete(
battleCount,
winner,
challengerTokenId,
opponentTokenId,
msg.sender,
opponentAddress,
challengerStyle,
opponentStyle,
challengerTotalLevel,
opponentTotalLevel,
challengerSkillLevels,
opponentSkillLevels
);
battleCount++;
}
function _updateAvailableBattles(uint256 tokenId) private {
(, , uint256 purchaseTier, ) = tokiemonNFT.getTokiemonData(tokenId);
uint256 maxDailyBattles = (purchaseTier == freeTier)
? dailyBattleSettings.freeMaxDailyBattles
: dailyBattleSettings.standardMaxDailyBattles;
uint256 regenerationTime = (purchaseTier == freeTier)
? dailyBattleSettings.freeBattleRegenerationTime
: dailyBattleSettings.standardBattleRegenerationTime;
uint256 elapsedTime = block.timestamp - lastBattleTimestamp[tokenId];
uint256 totalTime = elapsedTime + regenSurplus[tokenId];
uint256 regeneratedBattles = totalTime / regenerationTime;
availableBattles[tokenId] = Math.min(regeneratedBattles + availableBattles[tokenId], maxDailyBattles);
regenSurplus[tokenId] = totalTime % regenerationTime;
lastBattleTimestamp[tokenId] = block.timestamp;
}
function _checkAndUpdateDailyFreeTierLimit(address player) private {
uint256 currentDay = block.timestamp / 1 days;
uint256 lastResetDay = lastDailyResetForFreeTierByAddress[player] / 1 days;
if (currentDay > lastResetDay) {
dailyFreeTierBattlesUsedByAddress[player] = 0;
lastDailyResetForFreeTierByAddress[player] = block.timestamp - (block.timestamp % 1 days);
}
}
function calculateTokenAmount(
address paymentToken,
uint256 packId
) public view returns (uint256 tokenPriceNative, uint256 tokenAmount) {
BattlePack memory pack = battlePacks[packId];
require(pack.active, "Battle pack is not active");
require(pack.numberOfBattles > 0, "Invalid battle pack");
return oracle.calculateTokenAmount(paymentToken, pack.price, 1);
}
function _processBattlePurchase(uint256 _tokenId, uint256 _packId) internal {
BattlePack memory pack = battlePacks[_packId];
purchasedBattles[_tokenId] += pack.numberOfBattles;
emit BattlePackPurchased(_tokenId, pack.numberOfBattles);
}
function transferTokens(IERC20 token, uint256 burnAmount, uint256 daoAmount, uint256 partnerAmount, address partnerWallet) private {
if (burnAmount > 0) {
token.safeTransferFrom(msg.sender, oracle.DEAD_ADDRESS(), burnAmount);
}
if (daoAmount > 0) {
token.safeTransferFrom(msg.sender, oracle.daoAddress(), daoAmount);
}
if (partnerAmount > 0 && partnerWallet != address(0)) {
token.safeTransferFrom(msg.sender, partnerWallet, partnerAmount);
}
}
function addBattlePack(uint256 _price, uint256 _battles) external onlyRole(DEFAULT_ADMIN_ROLE) {
battlePacks[nextBattlePackId] = BattlePack(_price, _battles, true);
nextBattlePackId++;
}
function removeBattlePack(uint256 _packId) external onlyRole(DEFAULT_ADMIN_ROLE) {
require(battlePacks[_packId].price > 0, "Pack does not exist");
battlePacks[_packId].active = false;
}
function updateBattlePack(uint256 _packId, uint256 _price, uint256 _battles, bool _active) external onlyRole(DEFAULT_ADMIN_ROLE) {
require(battlePacks[_packId].price > 0, "Pack does not exist");
battlePacks[_packId] = BattlePack(_price, _battles, _active);
}
function addBattlesToToken(uint256 tokenId, uint256 amount) external onlyRole(MINTER_ROLE) {
purchasedBattles[tokenId] += amount;
}
function setRewardConfig(
uint256 _minReward,
uint256 _maxReward,
uint256 _maxLevel,
uint256 _freeTierMultiplier,
uint256 _bonusMultiplier,
uint256 _idleMultiplier
) external onlyRole(DEFAULT_ADMIN_ROLE) {
require(_minReward < _maxReward, "Min reward must be less than max reward");
require(_maxLevel > 1, "Max level must be greater than 1");
rewardConfig = RewardConfig({
minReward: _minReward,
maxReward: _maxReward,
maxLevel: _maxLevel,
freeTierMultiplier: _freeTierMultiplier,
bonusMultiplier: _bonusMultiplier,
idleMultiplier: _idleMultiplier
});
}
function setDailyBattleSettings(
uint256 _freeMaxDailyBattles,
uint256 _freeBattleRegenerationTime,
uint256 _standardMaxDailyBattles,
uint256 _standardBattleRegenerationTime,
uint256 _maxFreeTierDailyBattlesPerAddress
) external onlyRole(DEFAULT_ADMIN_ROLE) {
dailyBattleSettings = DailyBattleSettings({
freeMaxDailyBattles: _freeMaxDailyBattles,
freeBattleRegenerationTime: _freeBattleRegenerationTime,
standardMaxDailyBattles: _standardMaxDailyBattles,
standardBattleRegenerationTime: _standardBattleRegenerationTime,
maxFreeTierDailyBattlesPerAddress: _maxFreeTierDailyBattlesPerAddress
});
}
function setBattleSettings(
bool _equipmentOn,
bool _energyOn,
uint8 _energyLossChallenger,
uint8 _energyLossOpponent
) external onlyRole(DEFAULT_ADMIN_ROLE) {
battleSettings = BattleSettings({
equipmentOn: _equipmentOn,
energyOn: _energyOn,
energyLossChallenger: _energyLossChallenger,
energyLossOpponent: _energyLossOpponent
});
}
function setEnergyThresholds(EnergyThreshold[] calldata _newThresholds) external onlyRole(DEFAULT_ADMIN_ROLE) {
require(_newThresholds.length > 0, "Must provide at least one threshold");
require(_newThresholds[_newThresholds.length - 1].threshold == 100, "Last threshold must be 100");
for (uint256 i = 1; i < _newThresholds.length; i++) {
require(_newThresholds[i - 1].threshold < _newThresholds[i].threshold, "Thresholds must be in ascending order");
}
delete energyThresholds;
for (uint256 i = 0; i < _newThresholds.length; i++) {
energyThresholds.push(_newThresholds[i]);
}
}
function setCriticalEnergySettings(uint256 _threshold, int256 _probability) external onlyRole(DEFAULT_ADMIN_ROLE) {
require(_threshold < energyThresholds[0].threshold, "Critical threshold must be less than first regular threshold");
require(_probability >= 0 && _probability <= 9975, "Invalid critical energy probability");
criticalEnergyThreshold = _threshold;
criticalEnergyProbability = _probability;
}
function setOracle(address _oracle) external onlyRole(DEFAULT_ADMIN_ROLE) {
oracle = Oracle(_oracle);
}
function setTierManaPerBattle(uint256 tier, uint256 manaAmount) external onlyRole(DEFAULT_ADMIN_ROLE) {
tierManaPerBattle[tier] = manaAmount;
}
function withdrawFunds(address payable _to) external onlyRole(DEFAULT_ADMIN_ROLE) {
uint256 balance = address(this).balance;
require(balance > 0, "No funds to withdraw");
(bool sent, ) = _to.call{value: balance}("");
require(sent, "Failed to send Ether");
}
}
文件 42 的 46:TokiemonEnergy.sol
pragma solidity 0.8.24;
import {PermissionCallable} from "./permissions/PermissionCallable.sol";
import "@openzeppelin/contracts/token/ERC721/IERC721.sol";
import "@openzeppelin/contracts/token/ERC1155/IERC1155.sol";
import "@openzeppelin/contracts/token/ERC1155/utils/ERC1155Holder.sol";
import "@openzeppelin/contracts/access/AccessControl.sol";
import "@openzeppelin/contracts/utils/ReentrancyGuard.sol";
import "./TokiemonNFT.sol";
import "./TokiemonItems.sol";
contract TokiemonEnergy is ERC1155Holder, AccessControl, ReentrancyGuard, PermissionCallable {
TokiemonNFT public tokiemonNFT;
TokiemonItems public tokiemonItems;
bytes32 public constant MANAGER_ROLE = keccak256("MANAGER_ROLE");
uint8 public minEnergy;
uint8 public maxEnergy;
uint256 public energyDecayInterval;
uint8 public energyDecayAmount;
uint256 public petCooldown;
uint8 public petEnergyBoost;
uint8 public startingEnergy;
mapping(uint256 => uint8) public tokiemonEnergies;
mapping(uint256 => uint256) public maxEnergyEffectExpiry;
mapping(uint256 => uint256) public lastEnergyUpdateTime;
mapping(uint256 => ItemEffect) public consumableEffects;
mapping(uint256 => uint256) public lastPetTime;
mapping(uint256 => ActiveSkillImpact) public activeSkillImpacts;
struct ItemEffect {
uint8 energyBoost;
uint256 energyDuration;
SkillEffect skillEffect;
}
struct SkillEffect {
uint8 impactAmount;
uint256 impactDuration;
bool increase;
uint256 skillId;
}
struct ActiveSkillImpact {
uint8 impactAmount;
uint256 impactExpiry;
bool increase;
uint256 skillId;
}
struct TokiemonEnergyStatus {
uint256 nextPetTime;
uint8 currentEnergy;
uint256 maxEnergyEffectExpiryTime;
uint256 skillId;
uint8 skillImpactAmount;
uint256 skillImpactExpiry;
bool skillImpactIncrease;
}
event EnergyUpdated(uint256 indexed tokenId, uint8 oldEnergy, uint8 newEnergy, string reason);
event ItemConsumed(uint256 indexed tokenId, uint256 itemId, uint8 energyBoost, uint256 energyDuration);
event TokiemonPetted(uint256 indexed tokenId, uint8 energyBoost);
event SkillImpactApplied(uint256 indexed tokenId, uint256 skillId, uint8 impactAmount, uint256 impactExpiry, bool increase);
constructor(address _tokiemonNFT, address _tokiemonItems) {
tokiemonNFT = TokiemonNFT(_tokiemonNFT);
tokiemonItems = TokiemonItems(_tokiemonItems);
_grantRole(DEFAULT_ADMIN_ROLE, msg.sender);
_grantRole(MANAGER_ROLE, msg.sender);
minEnergy = 1;
maxEnergy = 100;
energyDecayInterval = 1 hours;
energyDecayAmount = 1;
petCooldown = 4 hours;
petEnergyBoost = 10;
startingEnergy = 80;
}
function getTokiemonEnergyStatus(uint256 tokenId) public view returns (TokiemonEnergyStatus memory) {
require(tokiemonNFT.ownerOf(tokenId) != address(0), "Tokiemon does not exist");
uint256 nextPetTime;
if (lastPetTime[tokenId] == 0) {
nextPetTime = 0;
} else {
uint256 calculatedNextPetTime = lastPetTime[tokenId] + petCooldown;
nextPetTime = calculatedNextPetTime > block.timestamp ? calculatedNextPetTime : block.timestamp;
}
uint8 currentEnergy = getEnergy(tokenId);
uint256 expiryTime = maxEnergyEffectExpiry[tokenId];
ActiveSkillImpact memory skillImpact = activeSkillImpacts[tokenId];
return
TokiemonEnergyStatus(
nextPetTime,
currentEnergy,
expiryTime,
skillImpact.skillId,
skillImpact.impactAmount,
skillImpact.impactExpiry,
skillImpact.increase
);
}
function getBulkTokiemonEnergyStatus(uint256[] calldata tokenIds) external view returns (TokiemonEnergyStatus[] memory) {
TokiemonEnergyStatus[] memory statuses = new TokiemonEnergyStatus[](tokenIds.length);
for (uint256 i = 0; i < tokenIds.length; i++) {
statuses[i] = getTokiemonEnergyStatus(tokenIds[i]);
}
return statuses;
}
function getConsumableEffectData(
uint256 itemId
) external view returns (uint8 energyBoost, uint256 energyDuration, SkillEffect memory skillEffect) {
ItemEffect memory effect = consumableEffects[itemId];
return (effect.energyBoost, effect.energyDuration, effect.skillEffect);
}
function getEnergy(uint256 tokenId) public view returns (uint8) {
require(tokiemonNFT.ownerOf(tokenId) != address(0), "Tokiemon does not exist");
uint8 currentEnergy = tokiemonEnergies[tokenId];
if (currentEnergy == 0) {
return startingEnergy;
}
uint256 timePassedSinceUpdate = block.timestamp - lastEnergyUpdateTime[tokenId];
uint256 maxEnergyExpiry = maxEnergyEffectExpiry[tokenId];
if (block.timestamp <= maxEnergyExpiry) {
return maxEnergy;
}
if (maxEnergyExpiry > lastEnergyUpdateTime[tokenId]) {
timePassedSinceUpdate = block.timestamp - maxEnergyExpiry;
currentEnergy = maxEnergy;
}
uint256 decayPeriods = timePassedSinceUpdate / energyDecayInterval;
if (decayPeriods > 0) {
uint8 totalDecay = uint8(decayPeriods) * energyDecayAmount;
return currentEnergy > totalDecay ? currentEnergy - totalDecay : minEnergy;
}
return currentEnergy;
}
function getNextPetTime(uint256 tokenId) public view returns (uint256) {
require(tokiemonNFT.ownerOf(tokenId) != address(0), "Tokiemon does not exist");
if (lastPetTime[tokenId] == 0) {
return 0;
}
uint256 nextPetTime = lastPetTime[tokenId] + petCooldown;
return nextPetTime > block.timestamp ? nextPetTime : block.timestamp;
}
function consume(uint256 tokenId, uint256 itemId) external nonReentrant {
_validateConsumption(tokenId, itemId);
_spendItem(msg.sender, itemId);
ItemEffect memory effect = consumableEffects[itemId];
uint8 currentEnergy = getEnergy(tokenId);
_applyAndUpdateEnergyEffect(tokenId, currentEnergy, effect);
_applySkillEffect(tokenId, effect.skillEffect);
emit ItemConsumed(tokenId, itemId, effect.energyBoost, effect.energyDuration);
}
function petTokiemon(uint256 tokenId) external nonReentrant {
_petTokiemon(tokenId);
}
function bulkPetTokiemon(uint256[] calldata tokenIds) external nonReentrant {
uint256 length = tokenIds.length;
for (uint256 i = 0; i < length; i++) {
_petTokiemon(tokenIds[i]);
}
}
function _petTokiemon(uint256 tokenId) internal {
require(tokiemonNFT.ownerOf(tokenId) == msg.sender, "Not the owner of the Tokiemon");
require(block.timestamp >= lastPetTime[tokenId] + petCooldown, "Tokiemon was pet too recently");
uint8 currentEnergy = getEnergy(tokenId);
require(currentEnergy < maxEnergy, "Tokiemon is already at max energy");
uint8 oldEnergy = currentEnergy;
uint8 newEnergy = currentEnergy + petEnergyBoost > maxEnergy ? maxEnergy : currentEnergy + petEnergyBoost;
tokiemonEnergies[tokenId] = newEnergy;
lastEnergyUpdateTime[tokenId] = block.timestamp;
lastPetTime[tokenId] = block.timestamp;
emit TokiemonPetted(tokenId, newEnergy - oldEnergy);
emit EnergyUpdated(tokenId, oldEnergy, newEnergy, "Petting");
}
function _setConsumableEffect(
uint256 itemId,
uint8 energyBoost,
uint256 energyDuration,
bool hasSkillImpact,
SkillEffect memory skillEffect
) internal {
require(energyBoost == 0 || energyDuration == 0, "Cannot set both energyBoost and energyDuration");
consumableEffects[itemId] = ItemEffect(energyBoost, energyDuration, hasSkillImpact ? skillEffect : SkillEffect(0, 0, false, 0));
}
function _validateConsumption(uint256 tokenId, uint256 itemId) internal view {
require(tokiemonNFT.ownerOf(tokenId) == msg.sender, "Not the owner of the Tokiemon");
require(
consumableEffects[itemId].energyBoost > 0 ||
consumableEffects[itemId].energyDuration > 0 ||
consumableEffects[itemId].skillEffect.impactAmount > 0,
"Item is not consumable"
);
require(tokiemonItems.balanceOf(msg.sender, itemId) > 0, "You don't have this item");
}
function _applyAndUpdateEnergyEffect(uint256 tokenId, uint8 currentEnergy, ItemEffect memory effect) internal returns (uint8) {
uint8 newEnergy = currentEnergy;
if (effect.energyDuration > 0) {
maxEnergyEffectExpiry[tokenId] = block.timestamp + effect.energyDuration;
newEnergy = maxEnergy;
} else if (effect.energyBoost > 0) {
newEnergy = uint8(min(uint256(currentEnergy) + effect.energyBoost, maxEnergy));
}
tokiemonEnergies[tokenId] = newEnergy;
lastEnergyUpdateTime[tokenId] = block.timestamp;
emit EnergyUpdated(tokenId, currentEnergy, newEnergy, "Item consumption");
return newEnergy;
}
function _applySkillEffect(uint256 tokenId, SkillEffect memory skillEffect) internal {
if (skillEffect.impactAmount > 0) {
activeSkillImpacts[tokenId] = ActiveSkillImpact({
skillId: skillEffect.skillId,
impactAmount: skillEffect.impactAmount,
impactExpiry: block.timestamp + skillEffect.impactDuration,
increase: skillEffect.increase
});
emit SkillImpactApplied(
tokenId,
skillEffect.skillId,
skillEffect.impactAmount,
block.timestamp + skillEffect.impactDuration,
skillEffect.increase
);
}
}
function _spendItem(address owner, uint256 itemId) internal {
tokiemonItems.spendItem(owner, itemId, 1);
}
function min(uint256 a, uint256 b) internal pure returns (uint256) {
return a < b ? a : b;
}
function setEnergy(uint256 tokenId, uint8 newEnergy) external onlyRole(MANAGER_ROLE) {
require(newEnergy >= minEnergy && newEnergy <= maxEnergy, "Invalid energy value");
require(tokiemonNFT.ownerOf(tokenId) != address(0), "Tokiemon does not exist");
tokiemonEnergies[tokenId] = newEnergy;
lastEnergyUpdateTime[tokenId] = block.timestamp;
emit EnergyUpdated(tokenId, 0, newEnergy, "Manual set");
}
function decreaseEnergy(uint256 tokenId, uint8 amount) external onlyRole(MANAGER_ROLE) {
require(tokiemonNFT.ownerOf(tokenId) != address(0), "Tokiemon does not exist");
require(amount <= maxEnergy, "Amount must be less than or equal to max energy");
if (amount == 0) {
return;
}
uint8 currentEnergy = getEnergy(tokenId);
uint8 newEnergy = currentEnergy > amount ? currentEnergy - amount : minEnergy;
tokiemonEnergies[tokenId] = newEnergy;
lastEnergyUpdateTime[tokenId] = block.timestamp;
emit EnergyUpdated(tokenId, currentEnergy, newEnergy, "Energy decreased");
}
function setConsumableEffect(
uint256 itemId,
uint8 energyBoost,
uint256 energyDuration,
bool hasSkillImpact,
SkillEffect memory skillEffect
) external onlyRole(DEFAULT_ADMIN_ROLE) {
_setConsumableEffect(itemId, energyBoost, energyDuration, hasSkillImpact, skillEffect);
}
function setEnergyParameters(
uint8 _minEnergy,
uint8 _maxEnergy,
uint256 _energyDecayInterval,
uint8 _energyDecayAmount,
uint256 _petCooldown,
uint8 _petEnergyBoost,
uint8 _startingEnergy
) external onlyRole(DEFAULT_ADMIN_ROLE) {
require(_minEnergy < _maxEnergy, "Invalid energy range");
require(_startingEnergy >= _minEnergy && _startingEnergy <= _maxEnergy, "Invalid starting energy");
minEnergy = _minEnergy;
maxEnergy = _maxEnergy;
energyDecayInterval = _energyDecayInterval;
energyDecayAmount = _energyDecayAmount;
petCooldown = _petCooldown;
petEnergyBoost = _petEnergyBoost;
startingEnergy = _startingEnergy;
}
function bulkSetConsumableEffects(
uint256[] calldata itemIds,
uint8[] calldata energyBoosts,
uint256[] calldata energyDurations,
bool[] calldata hasSkillImpact,
SkillEffect[] calldata skillEffects
) external onlyRole(DEFAULT_ADMIN_ROLE) {
require(
itemIds.length == energyBoosts.length &&
itemIds.length == energyDurations.length &&
itemIds.length == hasSkillImpact.length &&
itemIds.length == skillEffects.length,
"Array lengths must match"
);
for (uint256 i = 0; i < itemIds.length; i++) {
_setConsumableEffect(itemIds[i], energyBoosts[i], energyDurations[i], hasSkillImpact[i], skillEffects[i]);
}
}
function supportsInterface(bytes4 interfaceId) public view virtual override(ERC1155Holder, AccessControl) returns (bool) {
return super.supportsInterface(interfaceId);
}
function supportsPermissionedCallSelector(bytes4 ) public pure override returns (bool) {
return true;
}
}
文件 43 的 46:TokiemonEquipment.sol
pragma solidity 0.8.24;
import {PermissionCallable} from "./permissions/PermissionCallable.sol";
import "@openzeppelin/contracts/token/ERC721/IERC721.sol";
import "@openzeppelin/contracts/token/ERC1155/IERC1155.sol";
import "@openzeppelin/contracts/token/ERC1155/utils/ERC1155Holder.sol";
import "@openzeppelin/contracts/access/AccessControl.sol";
import "@openzeppelin/contracts/utils/ReentrancyGuard.sol";
import "./TokiemonNFT.sol";
import "./TokiemonItems.sol";
contract TokiemonEquipment is ERC1155Holder, AccessControl, ReentrancyGuard, PermissionCallable {
TokiemonNFT public tokiemonNFT;
ITokiemonItems public tokiemonItems;
uint256 public tokiemonSkillCount = 3;
mapping(uint256 => mapping(Slot => uint256)) public equippedItems;
mapping(uint256 => ItemConfig) public itemConfigs;
enum Slot {
None,
Head,
Weapon,
Torso,
Offhand,
Legs,
Special,
Mystical,
Amazing,
Superior
}
struct ItemConfig {
Slot primarySlot;
Slot secondarySlot;
SkillRequirement[5] skillRequirements;
ItemEffect effect;
uint8 skillRequirementCount;
}
struct SkillRequirement {
uint256 skillId;
uint8 skillLevel;
bool isOverallSkill;
}
struct ItemEffect {
uint256 attackBonus;
uint256 defenseBonus;
uint256 magicBonus;
uint256 overallBonus;
}
event ItemConfigured(
uint256 itemId,
Slot primarySlot,
Slot secondarySlot,
SkillRequirement[] skillRequirements,
uint256 attackBonus,
uint256 defenseBonus,
uint256 magicBonus,
uint256 overallBonus
);
event TokiemonEquipmentUpdated(uint256 indexed tokenId, uint256[9] equippedItems);
constructor(address _tokiemonNFT, address _tokiemonItems) {
tokiemonNFT = TokiemonNFT(_tokiemonNFT);
tokiemonItems = ITokiemonItems(_tokiemonItems);
_grantRole(DEFAULT_ADMIN_ROLE, msg.sender);
}
function getEquippedItem(uint256 tokenId, Slot slot) external view returns (uint256) {
return equippedItems[tokenId][slot];
}
function getAllEquippedItems(uint256 tokenId) external view returns (uint256[9] memory) {
uint256[9] memory items;
for (uint256 i = 1; i <= 9; i++) {
items[i - 1] = equippedItems[tokenId][Slot(i)];
}
return items;
}
function getTotalEquipmentEffect(uint256 tokenId) public view returns (ItemEffect memory) {
ItemEffect memory totalEffect;
for (uint256 i = 1; i <= uint256(Slot.Superior); i++) {
uint256 itemId = equippedItems[tokenId][Slot(i)];
if (itemId != 0) {
ItemConfig memory config = itemConfigs[itemId];
totalEffect.attackBonus += config.effect.attackBonus;
totalEffect.defenseBonus += config.effect.defenseBonus;
totalEffect.magicBonus += config.effect.magicBonus;
totalEffect.overallBonus += config.effect.overallBonus;
}
}
return totalEffect;
}
function getItemConfigData(
uint256 itemId
)
external
view
returns (
Slot primarySlot,
Slot secondarySlot,
SkillRequirement[5] memory skillRequirements,
ItemEffect memory effect,
uint8 skillRequirementCount
)
{
ItemConfig memory config = itemConfigs[itemId];
return (config.primarySlot, config.secondarySlot, config.skillRequirements, config.effect, config.skillRequirementCount);
}
function equipItem(uint256 tokenId, uint256 itemId) external nonReentrant {
require(tokiemonNFT.ownerOf(tokenId) == msg.sender, "Not the owner of the Tokiemon");
_equipItem(tokenId, itemId);
_emitEquipmentUpdate(tokenId);
}
function unequipItem(uint256 tokenId, Slot slot) external nonReentrant {
require(tokiemonNFT.ownerOf(tokenId) == msg.sender, "Not the owner of the Tokiemon");
_unequipItem(tokenId, slot);
_emitEquipmentUpdate(tokenId);
}
function bulkEquipItems(uint256 tokenId, uint256[] calldata itemIds) external nonReentrant {
require(tokiemonNFT.ownerOf(tokenId) == msg.sender, "Not the owner of the Tokiemon");
require(itemIds.length > 0, "No items to equip");
for (uint256 i = 0; i < itemIds.length; i++) {
_equipItem(tokenId, itemIds[i]);
}
_emitEquipmentUpdate(tokenId);
}
function bulkUnequipItems(uint256 tokenId, Slot[] calldata slots) external nonReentrant {
require(tokiemonNFT.ownerOf(tokenId) == msg.sender, "Not the owner of the Tokiemon");
require(slots.length > 0, "No slots to unequip");
for (uint256 i = 0; i < slots.length; i++) {
_unequipItem(tokenId, slots[i]);
}
_emitEquipmentUpdate(tokenId);
}
function updateAllEquipment(uint256 tokenId, uint256[9] calldata itemIds) external nonReentrant {
require(tokiemonNFT.ownerOf(tokenId) == msg.sender, "Not the owner of the Tokiemon");
for (uint256 i = 0; i < 9; i++) {
Slot slot = Slot(i + 1);
uint256 itemId = itemIds[i];
if (itemId == 0) {
if (equippedItems[tokenId][slot] != 0) {
_unequipItem(tokenId, slot);
}
} else {
_equipItem(tokenId, itemId);
}
}
_emitEquipmentUpdate(tokenId);
}
function _equipItem(uint256 tokenId, uint256 itemId) internal returns (Slot) {
ItemConfig memory config = itemConfigs[itemId];
require(config.primarySlot != Slot.None, "Item is not equippable");
uint256 totalSkillLevel = 0;
TokiemonNFT.Skill[3] memory skills;
for (uint256 i = 0; i < tokiemonSkillCount; i++) {
skills[i] = tokiemonNFT.getTokiemonSkill(tokenId, i);
totalSkillLevel += skills[i].level;
}
for (uint8 i = 0; i < config.skillRequirementCount; i++) {
SkillRequirement memory req = config.skillRequirements[i];
if (req.isOverallSkill) {
require(totalSkillLevel >= req.skillLevel, "Tokiemon does not meet the required overall skill level");
} else {
require(req.skillId < tokiemonSkillCount, "Invalid skill ID");
require(skills[req.skillId].level >= req.skillLevel, "Tokiemon does not meet the required skill level");
}
}
bool alreadyEquipped = equippedItems[tokenId][config.primarySlot] == itemId &&
(config.secondarySlot == Slot.None || equippedItems[tokenId][config.secondarySlot] == itemId);
if (alreadyEquipped) {
return config.primarySlot;
}
uint256[] memory itemIds = new uint256[](1);
itemIds[0] = itemId;
uint256[] memory amounts = new uint256[](1);
amounts[0] = 1;
tokiemonItems.whitelistTransfer(msg.sender, address(this), itemIds, amounts);
if (equippedItems[tokenId][config.primarySlot] != 0) {
_unequipItem(tokenId, config.primarySlot);
}
if (config.secondarySlot != Slot.None && equippedItems[tokenId][config.secondarySlot] != 0) {
_unequipItem(tokenId, config.secondarySlot);
}
equippedItems[tokenId][config.primarySlot] = itemId;
if (config.secondarySlot != Slot.None) {
equippedItems[tokenId][config.secondarySlot] = itemId;
}
return config.primarySlot;
}
function _unequipItem(uint256 tokenId, Slot slot) internal returns (uint256) {
uint256 itemId = equippedItems[tokenId][slot];
require(itemId != 0, "No item equipped in this slot");
tokiemonItems.safeTransferFrom(address(this), tokiemonNFT.ownerOf(tokenId), itemId, 1, "");
ItemConfig memory config = itemConfigs[itemId];
delete equippedItems[tokenId][slot];
if (config.primarySlot != slot && equippedItems[tokenId][config.primarySlot] == itemId) {
delete equippedItems[tokenId][config.primarySlot];
}
if (config.secondarySlot != Slot.None && config.secondarySlot != slot && equippedItems[tokenId][config.secondarySlot] == itemId) {
delete equippedItems[tokenId][config.secondarySlot];
}
return itemId;
}
function _emitEquipmentUpdate(uint256 tokenId) internal {
uint256[9] memory items;
for (uint256 i = 1; i <= 9; i++) {
items[i - 1] = equippedItems[tokenId][Slot(i)];
}
emit TokiemonEquipmentUpdated(tokenId, items);
}
function setTokiemonSkillCount(uint256 newCount) external onlyRole(DEFAULT_ADMIN_ROLE) {
tokiemonSkillCount = newCount;
}
function configureItem(
uint256 itemId,
Slot primarySlot,
Slot secondarySlot,
SkillRequirement[] memory skillRequirements,
ItemEffect memory effect
) public onlyRole(DEFAULT_ADMIN_ROLE) {
_configureItem(itemId, primarySlot, secondarySlot, skillRequirements, effect);
}
function configureMultipleItems(
uint256[] calldata itemIds,
Slot[] calldata primarySlots,
Slot[] calldata secondarySlots,
SkillRequirement[][] calldata skillRequirements,
ItemEffect[] calldata effects
) external onlyRole(DEFAULT_ADMIN_ROLE) {
require(
itemIds.length == primarySlots.length &&
itemIds.length == secondarySlots.length &&
itemIds.length == skillRequirements.length &&
itemIds.length == effects.length,
"Input arrays must have the same length"
);
for (uint256 i = 0; i < itemIds.length; i++) {
_configureItem(itemIds[i], primarySlots[i], secondarySlots[i], skillRequirements[i], effects[i]);
}
}
function _configureItem(
uint256 itemId,
Slot primarySlot,
Slot secondarySlot,
SkillRequirement[] memory skillRequirements,
ItemEffect memory effect
) internal {
require(primarySlot != Slot.None, "Primary slot cannot be None");
require(skillRequirements.length <= 5, "Too many skill requirements");
ItemConfig storage config = itemConfigs[itemId];
config.primarySlot = primarySlot;
config.secondarySlot = secondarySlot;
config.effect = effect;
config.skillRequirementCount = uint8(skillRequirements.length);
for (uint8 i = 0; i < skillRequirements.length; i++) {
config.skillRequirements[i] = skillRequirements[i];
}
emit ItemConfigured(
itemId,
primarySlot,
secondarySlot,
skillRequirements,
effect.attackBonus,
effect.defenseBonus,
effect.magicBonus,
effect.overallBonus
);
}
function emergencyWithdraw(
uint256[] calldata ids,
uint256[] calldata amounts,
address recipient
) external onlyRole(DEFAULT_ADMIN_ROLE) {
require(ids.length == amounts.length, "Mismatched ids and amounts");
require(recipient != address(0), "Invalid recipient");
tokiemonItems.safeBatchTransferFrom(address(this), recipient, ids, amounts, "");
}
function onERC1155Received(address, address, uint256, uint256, bytes memory) public virtual override returns (bytes4) {
return this.onERC1155Received.selector;
}
function onERC1155BatchReceived(
address,
address,
uint256[] memory,
uint256[] memory,
bytes memory
) public virtual override returns (bytes4) {
return this.onERC1155BatchReceived.selector;
}
function supportsInterface(bytes4 interfaceId) public view virtual override(ERC1155Holder, AccessControl) returns (bool) {
return super.supportsInterface(interfaceId);
}
function supportsPermissionedCallSelector(bytes4 ) public pure override returns (bool) {
return true;
}
}
文件 44 的 46:TokiemonItems.sol
pragma solidity 0.8.24;
import {PermissionCallable} from "./permissions/PermissionCallable.sol";
import "@openzeppelin/contracts/token/ERC1155/ERC1155.sol";
import "@openzeppelin/contracts/access/AccessControl.sol";
import "@openzeppelin/contracts/token/ERC1155/extensions/ERC1155Burnable.sol";
import "@openzeppelin/contracts/token/ERC1155/extensions/ERC1155Supply.sol";
interface ITokiemonItems is IERC1155 {
function whitelistTransfer(address from, address to, uint256[] memory ids, uint256[] memory amounts) external;
}
contract TokiemonItems is ERC1155, AccessControl, ERC1155Burnable, ERC1155Supply, PermissionCallable {
bytes32 public constant URI_SETTER_ROLE = keccak256("URI_SETTER_ROLE");
bytes32 public constant MINTER_ROLE = keccak256("MINTER_ROLE");
bytes32 public constant SPENDER_ROLE = keccak256("SPENDER_ROLE");
bytes32 public constant EQUIPMENT_ROLE = keccak256("EQUIPMENT_ROLE");
uint256 currentHighestId;
mapping(uint256 => ItemConfig) public itemConfigs;
mapping(address => bool) public whitelistedContracts;
enum TransferRestriction {
Transferable,
NonTransferable,
WhitelistOnly
}
enum ItemType {
Spendable,
Consumable,
Equipable,
Collectible,
Quest,
Other
}
struct ItemConfig {
TransferRestriction transferRestriction;
ItemType itemType;
uint256 maxSupply;
}
constructor() ERC1155("https://api.tokiemon.io/items/{id}") {
_grantRole(DEFAULT_ADMIN_ROLE, msg.sender);
_grantRole(MINTER_ROLE, msg.sender);
}
function getItemBalances(address account) public view returns (uint256[] memory ids, uint256[] memory balances) {
uint256 count = 0;
for (uint256 i = 1; i <= currentHighestId; i++) {
if (balanceOf(account, i) > 0) {
count++;
}
}
ids = new uint256[](count);
balances = new uint256[](count);
uint256 index = 0;
for (uint256 i = 1; i <= currentHighestId; i++) {
uint256 balance = balanceOf(account, i);
if (balance > 0) {
ids[index] = i;
balances[index] = balance;
index++;
}
}
return (ids, balances);
}
function isAvailable(uint256 id) external view returns (bool) {
ItemConfig memory config = itemConfigs[id];
if (config.maxSupply == 0) {
return true;
}
return totalSupply(id) < config.maxSupply;
}
function getItemConfigData(uint256 id) external view returns (
TransferRestriction transferRestriction,
ItemType itemType,
uint256 maxSupply
) {
ItemConfig memory config = itemConfigs[id];
return (config.transferRestriction, config.itemType, config.maxSupply);
}
function mint(address account, uint256 id, uint256 amount, bytes memory data) public onlyRole(MINTER_ROLE) {
ItemConfig memory config = itemConfigs[id];
if (config.maxSupply > 0) {
require(totalSupply(id) + amount <= config.maxSupply, "Exceeds maximum supply");
}
_mint(account, id, amount, data);
}
function mintBatch(address to, uint256[] memory ids, uint256[] memory amounts, bytes memory data) public onlyRole(MINTER_ROLE) {
for (uint256 i = 0; i < ids.length; i++) {
ItemConfig memory config = itemConfigs[ids[i]];
if (config.maxSupply > 0) {
require(totalSupply(ids[i]) + amounts[i] <= config.maxSupply, "Exceeds maximum supply");
}
}
_mintBatch(to, ids, amounts, data);
}
function spendItem(address from, uint256 id, uint256 amount) public onlyRole(SPENDER_ROLE) {
_burn(from, id, amount);
}
function spendBatchItems(address from, uint256[] memory ids, uint256[] memory amounts) public onlyRole(SPENDER_ROLE) {
_burnBatch(from, ids, amounts);
}
function whitelistTransfer(address from, address to, uint256[] memory ids, uint256[] memory amounts) public onlyRole(EQUIPMENT_ROLE) {
_update(from, to, ids, amounts);
}
function setURI(string memory newuri) public onlyRole(URI_SETTER_ROLE) {
_setURI(newuri);
}
function setItemConfig(
uint256 id,
TransferRestriction restriction,
uint256 maxSupply,
ItemType itemType
) public onlyRole(DEFAULT_ADMIN_ROLE) {
_setAndUpdateHighestId(id, restriction, maxSupply, itemType);
}
function setBulkItemConfigs(
uint256[] memory ids,
TransferRestriction[] memory restrictions,
uint256[] memory maxSupplies,
ItemType[] memory itemTypes
) public onlyRole(DEFAULT_ADMIN_ROLE) {
require(
ids.length == restrictions.length && ids.length == maxSupplies.length && ids.length == itemTypes.length,
"Input arrays must have the same length"
);
for (uint256 i = 0; i < ids.length; i++) {
_setAndUpdateHighestId(ids[i], restrictions[i], maxSupplies[i], itemTypes[i]);
}
}
function _setAndUpdateHighestId(uint256 id, TransferRestriction restriction, uint256 maxSupply, ItemType itemType) internal {
itemConfigs[id] = ItemConfig(restriction, itemType, maxSupply);
if (id > currentHighestId) {
currentHighestId = id;
}
}
function setWhitelistedContract(address contractAddress, bool isWhitelisted) public onlyRole(DEFAULT_ADMIN_ROLE) {
whitelistedContracts[contractAddress] = isWhitelisted;
}
function _update(address from, address to, uint256[] memory ids, uint256[] memory values) internal override(ERC1155, ERC1155Supply) {
for (uint256 i = 0; i < ids.length; i++) {
ItemConfig memory config = itemConfigs[ids[i]];
if (from != address(0) && to != address(0)) {
if (config.transferRestriction == TransferRestriction.NonTransferable) {
revert("Item is not transferable");
} else if (config.transferRestriction == TransferRestriction.WhitelistOnly) {
require(whitelistedContracts[from] || whitelistedContracts[to], "Either sender or recipient must be whitelisted");
}
}
}
super._update(from, to, ids, values);
}
function supportsInterface(bytes4 interfaceId) public view override(ERC1155, AccessControl) returns (bool) {
return super.supportsInterface(interfaceId);
}
function supportsPermissionedCallSelector(bytes4 ) public pure override returns (bool) {
return true;
}
}
文件 45 的 46:TokiemonNFT.sol
pragma solidity 0.8.24;
import "@openzeppelin/contracts/token/ERC721/ERC721.sol";
import "@openzeppelin/contracts/token/common/ERC2981.sol";
import "@openzeppelin/contracts/token/ERC721/extensions/ERC721Enumerable.sol";
import "@openzeppelin/contracts/token/ERC721/extensions/ERC721Pausable.sol";
import "@openzeppelin/contracts/token/ERC721/extensions/ERC721Burnable.sol";
import "@openzeppelin/contracts/access/AccessControl.sol";
import "@openzeppelin/contracts/utils/ReentrancyGuard.sol";
import "@openzeppelin/contracts/utils/Strings.sol";
import "./ManaSystem.sol";
contract TokiemonNFT is ERC721, ERC721Enumerable, ERC721Pausable, AccessControl, ERC721Burnable, ERC2981, ReentrancyGuard {
using Strings for uint256;
uint16 public constant RARITY_PRECISION = 10000;
uint96 public constant DEFAULT_ROYALTY_PERCENTAGE = 250;
bytes32 public constant PAUSER_ROLE = keccak256("PAUSER_ROLE");
bytes32 public constant MINTER_ROLE = keccak256("MINTER_ROLE");
uint256 private _nextTokenId;
uint256[] public activeTiers;
ManaSystem public manaSystem;
address public royaltyReceiver;
struct TokiemonData {
string communityId;
string name;
uint256 purchaseTier;
Rarity rarity;
mapping(uint256 => Skill) skills;
}
mapping(uint256 => TokiemonData) private _tokiemonData;
mapping(uint256 => uint8) public skillMaxLevels;
mapping(uint256 => RarityProbabilities) public tierToRarityProbabilities;
enum Rarity {
Common,
Uncommon,
Rare,
Epic,
Legendary
}
struct RarityProbabilities {
uint16 common;
uint16 uncommon;
uint16 rare;
uint16 epic;
uint16 legendary;
}
struct Skill {
uint8 level;
uint32 manaPerLevelMultiplier;
uint32 manaUntilNextLevel;
uint256 cumulativeMana;
}
event SkillManaApplied(uint256 indexed tokenId, uint256 indexed skillId, uint256 manaAmount, uint8 newLevel, uint256 newMana);
event TokiemonSkillsUpdated(uint256 indexed tokenId, uint256[] skillIds, Skill[] updatedSkills);
event NameChanged(uint256 indexed tokenId, string newName);
event TokiemonMinted(
uint256 indexed tokenId,
address indexed to,
string communityId,
uint256 purchaseTier,
Rarity rarity,
address paymentToken
);
constructor(address defaultAdmin, address pauser, address minter) ERC721("Tokiemon", "TOKIE") {
_grantRole(DEFAULT_ADMIN_ROLE, defaultAdmin);
_grantRole(PAUSER_ROLE, pauser);
_grantRole(MINTER_ROLE, minter);
royaltyReceiver = defaultAdmin;
_setDefaultRoyalty(royaltyReceiver, DEFAULT_ROYALTY_PERCENTAGE);
addRarityProbability(1, RarityProbabilities(6200, 3000, 500, 250, 50));
addRarityProbability(2, RarityProbabilities(3000, 3800, 2000, 1000, 200));
addRarityProbability(3, RarityProbabilities(500, 1500, 5000, 2500, 500));
skillMaxLevels[0] = 99;
skillMaxLevels[1] = 99;
skillMaxLevels[2] = 99;
}
function _baseURI() internal pure override returns (string memory) {
return "https://api.tokiemon.io/tokiemon/";
}
function getActiveTiers() public view returns (uint256[] memory) {
return activeTiers;
}
function getRarity(uint256 tokenId) public view returns (Rarity) {
_requireOwned(tokenId);
return _tokiemonData[tokenId].rarity;
}
function getTierRarityProbabilities(uint256 tier) public view returns (uint16, uint16, uint16, uint16, uint16) {
RarityProbabilities memory probs = tierToRarityProbabilities[tier];
return (probs.common, probs.uncommon, probs.rare, probs.epic, probs.legendary);
}
function _tierExists(uint256 tier) private view returns (bool) {
return tierToRarityProbabilities[tier].common != 0;
}
function getTokiemonSkill(uint256 tokenId, uint256 skillId) public view returns (Skill memory) {
_requireOwned(tokenId);
return _tokiemonData[tokenId].skills[skillId];
}
function getTokiemonData(
uint256 tokenId
) public view returns (string memory community, string memory name, uint256 purchaseTier, Rarity rarity) {
_requireOwned(tokenId);
TokiemonData storage data = _tokiemonData[tokenId];
return (data.communityId, data.name, data.purchaseTier, data.rarity);
}
function tokenURI(uint256 tokenId) public view override returns (string memory) {
_requireOwned(tokenId);
string memory baseURI = _baseURI();
return bytes(baseURI).length > 0 ? string(abi.encodePacked(baseURI, tokenId.toString())) : "";
}
function safeMint(
address to,
uint256 tier,
string memory communityId,
address paymentToken,
Skill[] memory initialSkills
) public onlyRole(MINTER_ROLE) returns (uint256) {
require(_tierExists(tier), "Invalid tier");
uint256 tokenId = _nextTokenId++;
TokiemonData storage newTokiemon = _tokiemonData[tokenId];
newTokiemon.communityId = communityId;
newTokiemon.name = string(abi.encodePacked("Tokiemon #", tokenId.toString()));
newTokiemon.purchaseTier = tier;
newTokiemon.rarity = _determineRarity(tier);
for (uint256 i = 0; i < initialSkills.length; i++) {
newTokiemon.skills[i] = initialSkills[i];
}
_safeMint(to, tokenId);
emit TokiemonMinted(tokenId, to, communityId, tier, newTokiemon.rarity, paymentToken);
return tokenId;
}
function _determineRarity(uint256 purchaseTier) private view returns (Rarity) {
RarityProbabilities memory probs = tierToRarityProbabilities[purchaseTier];
uint256 randomNumber = uint256(keccak256(abi.encodePacked(block.timestamp, block.prevrandao, _nextTokenId, msg.sender))) %
RARITY_PRECISION;
uint256 cumulativeProbability = 0;
if (randomNumber < (cumulativeProbability += probs.common)) return Rarity.Common;
if (randomNumber < (cumulativeProbability += probs.uncommon)) return Rarity.Uncommon;
if (randomNumber < (cumulativeProbability += probs.rare)) return Rarity.Rare;
if (randomNumber < (cumulativeProbability += probs.epic)) return Rarity.Epic;
return Rarity.Legendary;
}
function levelUpSkill(uint256 tokenId, uint256 skillId, uint256 manaAmount) public nonReentrant {
require(msg.sender == ownerOf(tokenId), "Only owner can level up skills");
require(manaSystem.getMana(msg.sender) >= manaAmount, "Insufficient mana");
Skill storage skill = _tokiemonData[tokenId].skills[skillId];
require(skill.level < skillMaxLevels[skillId], "Skill already at maximum level");
manaSystem.spendMana(msg.sender, manaAmount);
uint8 newLevel = skill.level;
skill.cumulativeMana += manaAmount;
uint256 surplusMana = calculateAnyLevelCost(tokenId, skillId, newLevel + 1) - skill.manaUntilNextLevel;
uint256 manaAvailable = manaAmount + surplusMana;
while (manaAvailable >= calculateAnyLevelCost(tokenId, skillId, newLevel + 1) && newLevel < skillMaxLevels[skillId]) {
uint256 levelCost = calculateAnyLevelCost(tokenId, skillId, newLevel + 1);
manaAvailable -= levelCost;
newLevel++;
}
skill.level = newLevel;
skill.manaUntilNextLevel = uint32(calculateAnyLevelCost(tokenId, skillId, newLevel + 1) - manaAvailable);
emit SkillManaApplied(tokenId, skillId, manaAmount, newLevel, skill.manaUntilNextLevel);
}
function calculateAnyLevelCost(uint256 tokenId, uint256 skillId, uint8 level) public view returns (uint256) {
Skill memory skill = _tokiemonData[tokenId].skills[skillId];
return uint256(skill.manaPerLevelMultiplier) * uint256(level);
}
function getCumulativeManaRequiredToLevelUp(uint256 tokenId, uint256 skillId, uint8 level) public view returns (uint256) {
uint256 totalMana = 0;
Skill memory skill = _tokiemonData[tokenId].skills[skillId];
for (uint8 i = skill.level + 1; i <= level; i++) {
totalMana += i * skill.manaPerLevelMultiplier;
}
return totalMana;
}
function getNextLevelDelta(uint256 tokenId, uint256 skillId) public view returns (uint32) {
Skill memory skill = _tokiemonData[tokenId].skills[skillId];
return skill.manaUntilNextLevel;
}
function changeName(uint256 tokenId, string memory newName) public {
require(ownerOf(tokenId) == msg.sender, "Only token owner can change name");
_tokiemonData[tokenId].name = newName;
emit NameChanged(tokenId, newName);
}
function addRarityProbability(uint256 tier, RarityProbabilities memory probs) public onlyRole(DEFAULT_ADMIN_ROLE) {
require(!_tierExists(tier), "Tier already exists");
require(
probs.common + probs.uncommon + probs.rare + probs.epic + probs.legendary == RARITY_PRECISION,
"Probabilities must sum to 10000"
);
tierToRarityProbabilities[tier] = probs;
activeTiers.push(tier);
}
function updateRarityProbability(uint256 tier, RarityProbabilities memory probs) public onlyRole(DEFAULT_ADMIN_ROLE) {
require(_tierExists(tier), "Tier does not exist");
require(
probs.common + probs.uncommon + probs.rare + probs.epic + probs.legendary == RARITY_PRECISION,
"Probabilities must sum to 10000"
);
tierToRarityProbabilities[tier] = probs;
}
function removeRarityProbability(uint256 tier) public onlyRole(DEFAULT_ADMIN_ROLE) {
require(_tierExists(tier), "Tier does not exist");
for (uint256 i = 0; i < activeTiers.length; i++) {
if (activeTiers[i] == tier) {
activeTiers[i] = activeTiers[activeTiers.length - 1];
activeTiers.pop();
break;
}
}
delete tierToRarityProbabilities[tier];
}
function pause() public onlyRole(PAUSER_ROLE) {
_pause();
}
function unpause() public onlyRole(PAUSER_ROLE) {
_unpause();
}
function setManaSystem(address _manaSystem) external onlyRole(DEFAULT_ADMIN_ROLE) {
manaSystem = ManaSystem(_manaSystem);
}
function updateSkillMaxLevel(uint256 skillId, uint8 newMaxLevel) external onlyRole(DEFAULT_ADMIN_ROLE) {
skillMaxLevels[skillId] = newMaxLevel;
}
function setRoyaltyInfo(address receiver, uint96 feeNumerator) external onlyRole(DEFAULT_ADMIN_ROLE) {
_setDefaultRoyalty(receiver, feeNumerator);
royaltyReceiver = receiver;
}
function updateTokiemonSkills(
uint256 tokenId,
uint256[] memory skillIds,
Skill[] memory updatedSkills
) external onlyRole(DEFAULT_ADMIN_ROLE) {
require(skillIds.length == updatedSkills.length, "Skill IDs and updated skills must have the same length");
_requireOwned(tokenId);
for (uint256 i = 0; i < skillIds.length; i++) {
uint256 skillId = skillIds[i];
Skill memory updatedSkill = updatedSkills[i];
require(updatedSkill.level <= skillMaxLevels[skillId], "Skill level exceeds maximum");
_tokiemonData[tokenId].skills[skillId] = updatedSkill;
}
emit TokiemonSkillsUpdated(tokenId, skillIds, updatedSkills);
}
function _update(
address to,
uint256 tokenId,
address auth
) internal override(ERC721, ERC721Enumerable, ERC721Pausable) returns (address) {
return super._update(to, tokenId, auth);
}
function _increaseBalance(address account, uint128 value) internal override(ERC721, ERC721Enumerable) {
super._increaseBalance(account, value);
}
function supportsInterface(bytes4 interfaceId) public view override(ERC721, ERC721Enumerable, AccessControl, ERC2981) returns (bool) {
return super.supportsInterface(interfaceId);
}
}
文件 46 的 46:draft-IERC6093.sol
pragma solidity ^0.8.20;
interface IERC20Errors {
error ERC20InsufficientBalance(address sender, uint256 balance, uint256 needed);
error ERC20InvalidSender(address sender);
error ERC20InvalidReceiver(address receiver);
error ERC20InsufficientAllowance(address spender, uint256 allowance, uint256 needed);
error ERC20InvalidApprover(address approver);
error ERC20InvalidSpender(address spender);
}
interface IERC721Errors {
error ERC721InvalidOwner(address owner);
error ERC721NonexistentToken(uint256 tokenId);
error ERC721IncorrectOwner(address sender, uint256 tokenId, address owner);
error ERC721InvalidSender(address sender);
error ERC721InvalidReceiver(address receiver);
error ERC721InsufficientApproval(address operator, uint256 tokenId);
error ERC721InvalidApprover(address approver);
error ERC721InvalidOperator(address operator);
}
interface IERC1155Errors {
error ERC1155InsufficientBalance(address sender, uint256 balance, uint256 needed, uint256 tokenId);
error ERC1155InvalidSender(address sender);
error ERC1155InvalidReceiver(address receiver);
error ERC1155MissingApprovalForAll(address operator, address owner);
error ERC1155InvalidApprover(address approver);
error ERC1155InvalidOperator(address operator);
error ERC1155InvalidArrayLength(uint256 idsLength, uint256 valuesLength);
}
{
"compilationTarget": {
"src/TokiemonBattles.sol": "TokiemonBattles"
},
"evmVersion": "paris",
"libraries": {},
"metadata": {
"bytecodeHash": "ipfs"
},
"optimizer": {
"enabled": true,
"runs": 200
},
"remappings": []
}
[{"inputs":[{"internalType":"address","name":"_tokiemonNFT","type":"address"},{"internalType":"address","name":"_manaSystem","type":"address"},{"internalType":"address","name":"_tokiemonEquipment","type":"address"},{"internalType":"address","name":"_tokiemonEnergy","type":"address"},{"internalType":"address","name":"_oracle","type":"address"}],"stateMutability":"nonpayable","type":"constructor"},{"inputs":[],"name":"AccessControlBadConfirmation","type":"error"},{"inputs":[{"internalType":"address","name":"account","type":"address"},{"internalType":"bytes32","name":"neededRole","type":"bytes32"}],"name":"AccessControlUnauthorizedAccount","type":"error"},{"inputs":[{"internalType":"address","name":"target","type":"address"}],"name":"AddressEmptyCode","type":"error"},{"inputs":[{"internalType":"address","name":"account","type":"address"}],"name":"AddressInsufficientBalance","type":"error"},{"inputs":[],"name":"FailedInnerCall","type":"error"},{"inputs":[{"internalType":"address","name":"token","type":"address"}],"name":"SafeERC20FailedOperation","type":"error"},{"anonymous":false,"inputs":[{"indexed":false,"internalType":"uint256","name":"battleId","type":"uint256"},{"indexed":true,"internalType":"uint256","name":"winner","type":"uint256"},{"indexed":true,"internalType":"uint256","name":"challenger","type":"uint256"},{"indexed":true,"internalType":"uint256","name":"opponent","type":"uint256"},{"indexed":false,"internalType":"address","name":"challengerAddress","type":"address"},{"indexed":false,"internalType":"address","name":"opponentAddress","type":"address"},{"indexed":false,"internalType":"enum TokiemonBattles.AttackStyle","name":"challengerStyle","type":"uint8"},{"indexed":false,"internalType":"enum TokiemonBattles.AttackStyle","name":"opponentStyle","type":"uint8"},{"indexed":false,"internalType":"uint256","name":"challengerTotalLevel","type":"uint256"},{"indexed":false,"internalType":"uint256","name":"opponentTotalLevel","type":"uint256"},{"indexed":false,"internalType":"uint8[3]","name":"challengerSkillLevels","type":"uint8[3]"},{"indexed":false,"internalType":"uint8[3]","name":"opponentSkillLevels","type":"uint8[3]"}],"name":"BattleComplete","type":"event"},{"anonymous":false,"inputs":[{"indexed":false,"internalType":"uint256","name":"tokenId","type":"uint256"},{"indexed":false,"internalType":"uint256","name":"numberOfBattles","type":"uint256"}],"name":"BattlePackPurchased","type":"event"},{"anonymous":false,"inputs":[{"indexed":true,"internalType":"bytes32","name":"role","type":"bytes32"},{"indexed":true,"internalType":"bytes32","name":"previousAdminRole","type":"bytes32"},{"indexed":true,"internalType":"bytes32","name":"newAdminRole","type":"bytes32"}],"name":"RoleAdminChanged","type":"event"},{"anonymous":false,"inputs":[{"indexed":true,"internalType":"bytes32","name":"role","type":"bytes32"},{"indexed":true,"internalType":"address","name":"account","type":"address"},{"indexed":true,"internalType":"address","name":"sender","type":"address"}],"name":"RoleGranted","type":"event"},{"anonymous":false,"inputs":[{"indexed":true,"internalType":"bytes32","name":"role","type":"bytes32"},{"indexed":true,"internalType":"address","name":"account","type":"address"},{"indexed":true,"internalType":"address","name":"sender","type":"address"}],"name":"RoleRevoked","type":"event"},{"inputs":[],"name":"DEFAULT_ADMIN_ROLE","outputs":[{"internalType":"bytes32","name":"","type":"bytes32"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"MINTER_ROLE","outputs":[{"internalType":"bytes32","name":"","type":"bytes32"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"uint256","name":"_price","type":"uint256"},{"internalType":"uint256","name":"_battles","type":"uint256"}],"name":"addBattlePack","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"uint256","name":"tokenId","type":"uint256"},{"internalType":"uint256","name":"amount","type":"uint256"}],"name":"addBattlesToToken","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"uint256","name":"","type":"uint256"}],"name":"availableBattles","outputs":[{"internalType":"uint256","name":"","type":"uint256"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"uint256","name":"challengerTokenId","type":"uint256"},{"internalType":"uint256","name":"opponentTokenId","type":"uint256"},{"internalType":"enum TokiemonBattles.AttackStyle","name":"challengerStyle","type":"uint8"}],"name":"battle","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[],"name":"battleCount","outputs":[{"internalType":"uint256","name":"","type":"uint256"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"uint256","name":"","type":"uint256"}],"name":"battlePacks","outputs":[{"internalType":"uint256","name":"price","type":"uint256"},{"internalType":"uint256","name":"numberOfBattles","type":"uint256"},{"internalType":"bool","name":"active","type":"bool"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"battleSettings","outputs":[{"internalType":"bool","name":"equipmentOn","type":"bool"},{"internalType":"bool","name":"energyOn","type":"bool"},{"internalType":"uint8","name":"energyLossChallenger","type":"uint8"},{"internalType":"uint8","name":"energyLossOpponent","type":"uint8"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"uint256","name":"","type":"uint256"}],"name":"battles","outputs":[{"internalType":"uint256","name":"winner","type":"uint256"},{"internalType":"uint256","name":"challenger","type":"uint256"},{"internalType":"uint256","name":"opponent","type":"uint256"},{"internalType":"address","name":"challengerAddress","type":"address"},{"internalType":"address","name":"opponentAddress","type":"address"},{"internalType":"enum TokiemonBattles.AttackStyle","name":"challengerStyle","type":"uint8"},{"internalType":"enum TokiemonBattles.AttackStyle","name":"opponentStyle","type":"uint8"},{"internalType":"uint256","name":"challengerTotalLevel","type":"uint256"},{"internalType":"uint256","name":"opponentTotalLevel","type":"uint256"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"uint256","name":"_tokenId","type":"uint256"},{"internalType":"uint256","name":"_packId","type":"uint256"}],"name":"buyBattles","outputs":[],"stateMutability":"payable","type":"function"},{"inputs":[{"internalType":"uint256","name":"_tokenId","type":"uint256"},{"internalType":"uint256","name":"_packId","type":"uint256"},{"internalType":"address","name":"paymentToken","type":"address"},{"internalType":"uint256","name":"maxPaymentTokenAmount","type":"uint256"}],"name":"buyBattlesWithERC20","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"uint256","name":"winnerTokenId","type":"uint256"},{"internalType":"uint256","name":"loserTokenId","type":"uint256"},{"internalType":"uint256","name":"challengerTokenId","type":"uint256"}],"name":"calculateBattleReward","outputs":[{"internalType":"uint256","name":"","type":"uint256"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"address","name":"paymentToken","type":"address"},{"internalType":"uint256","name":"packId","type":"uint256"}],"name":"calculateTokenAmount","outputs":[{"internalType":"uint256","name":"tokenPriceNative","type":"uint256"},{"internalType":"uint256","name":"tokenAmount","type":"uint256"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"uint256","name":"challengerTokenId","type":"uint256"},{"internalType":"uint256","name":"opponentTokenId","type":"uint256"},{"internalType":"enum TokiemonBattles.AttackStyle","name":"challengerStyle","type":"uint8"}],"name":"calculateWinProbability","outputs":[{"internalType":"uint256","name":"","type":"uint256"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"criticalEnergyProbability","outputs":[{"internalType":"int256","name":"","type":"int256"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"criticalEnergyThreshold","outputs":[{"internalType":"uint256","name":"","type":"uint256"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"dailyBattleSettings","outputs":[{"internalType":"uint256","name":"freeMaxDailyBattles","type":"uint256"},{"internalType":"uint256","name":"freeBattleRegenerationTime","type":"uint256"},{"internalType":"uint256","name":"standardMaxDailyBattles","type":"uint256"},{"internalType":"uint256","name":"standardBattleRegenerationTime","type":"uint256"},{"internalType":"uint256","name":"maxFreeTierDailyBattlesPerAddress","type":"uint256"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"address","name":"","type":"address"}],"name":"dailyFreeTierBattlesUsedByAddress","outputs":[{"internalType":"uint256","name":"","type":"uint256"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"uint256","name":"","type":"uint256"}],"name":"energyThresholds","outputs":[{"internalType":"uint256","name":"threshold","type":"uint256"},{"internalType":"int256","name":"adjustment","type":"int256"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"freeTier","outputs":[{"internalType":"uint256","name":"","type":"uint256"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"uint256","name":"tokenId","type":"uint256"}],"name":"getAvailableBattles","outputs":[{"internalType":"uint256","name":"","type":"uint256"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"uint256","name":"battleId","type":"uint256"}],"name":"getBattleResult","outputs":[{"components":[{"internalType":"uint256","name":"winner","type":"uint256"},{"internalType":"uint256","name":"challenger","type":"uint256"},{"internalType":"uint256","name":"opponent","type":"uint256"},{"internalType":"address","name":"challengerAddress","type":"address"},{"internalType":"address","name":"opponentAddress","type":"address"},{"internalType":"enum TokiemonBattles.AttackStyle","name":"challengerStyle","type":"uint8"},{"internalType":"enum TokiemonBattles.AttackStyle","name":"opponentStyle","type":"uint8"},{"internalType":"uint256","name":"challengerTotalLevel","type":"uint256"},{"internalType":"uint256","name":"opponentTotalLevel","type":"uint256"},{"internalType":"uint8[3]","name":"challengerSkillLevels","type":"uint8[3]"},{"internalType":"uint8[3]","name":"opponentSkillLevels","type":"uint8[3]"}],"internalType":"struct TokiemonBattles.Battle","name":"","type":"tuple"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"uint256","name":"tokenId","type":"uint256"}],"name":"getBattlesLeftBreakdown","outputs":[{"internalType":"uint256","name":"","type":"uint256"},{"internalType":"uint256","name":"","type":"uint256"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"uint256","name":"currentEnergy","type":"uint256"}],"name":"getEnergyProbabilityImpact","outputs":[{"internalType":"int256","name":"","type":"int256"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"uint256","name":"tokenId","type":"uint256"}],"name":"getNextBattleRegenerationTime","outputs":[{"internalType":"uint256","name":"","type":"uint256"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"address","name":"player","type":"address"}],"name":"getRemainingDailyFreeTierBattles","outputs":[{"internalType":"uint256","name":"","type":"uint256"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"bytes32","name":"role","type":"bytes32"}],"name":"getRoleAdmin","outputs":[{"internalType":"bytes32","name":"","type":"bytes32"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"uint256","name":"tokenId","type":"uint256"}],"name":"getSkillInfo","outputs":[{"internalType":"uint8[3]","name":"skillLevels","type":"uint8[3]"},{"internalType":"uint256","name":"totalLevel","type":"uint256"},{"internalType":"enum TokiemonBattles.AttackStyle","name":"bestSkill","type":"uint8"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"uint256","name":"tokenId","type":"uint256"}],"name":"getTotalBattlesLeft","outputs":[{"internalType":"uint256","name":"","type":"uint256"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"bytes32","name":"role","type":"bytes32"},{"internalType":"address","name":"account","type":"address"}],"name":"grantRole","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"bytes32","name":"role","type":"bytes32"},{"internalType":"address","name":"account","type":"address"}],"name":"hasRole","outputs":[{"internalType":"bool","name":"","type":"bool"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"uint256","name":"","type":"uint256"}],"name":"lastBattleTimestamp","outputs":[{"internalType":"uint256","name":"","type":"uint256"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"address","name":"","type":"address"}],"name":"lastDailyResetForFreeTierByAddress","outputs":[{"internalType":"uint256","name":"","type":"uint256"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"manaSystem","outputs":[{"internalType":"contract ManaSystem","name":"","type":"address"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"nextBattlePackId","outputs":[{"internalType":"uint256","name":"","type":"uint256"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"oracle","outputs":[{"internalType":"contract Oracle","name":"","type":"address"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"uint256","name":"","type":"uint256"}],"name":"purchasedBattles","outputs":[{"internalType":"uint256","name":"","type":"uint256"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"uint256","name":"","type":"uint256"}],"name":"regenSurplus","outputs":[{"internalType":"uint256","name":"","type":"uint256"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"uint256","name":"_packId","type":"uint256"}],"name":"removeBattlePack","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"bytes32","name":"role","type":"bytes32"},{"internalType":"address","name":"callerConfirmation","type":"address"}],"name":"renounceRole","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"bytes32","name":"role","type":"bytes32"},{"internalType":"address","name":"account","type":"address"}],"name":"revokeRole","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[],"name":"rewardConfig","outputs":[{"internalType":"uint256","name":"minReward","type":"uint256"},{"internalType":"uint256","name":"maxReward","type":"uint256"},{"internalType":"uint256","name":"maxLevel","type":"uint256"},{"internalType":"uint256","name":"freeTierMultiplier","type":"uint256"},{"internalType":"uint256","name":"bonusMultiplier","type":"uint256"},{"internalType":"uint256","name":"idleMultiplier","type":"uint256"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"bool","name":"_equipmentOn","type":"bool"},{"internalType":"bool","name":"_energyOn","type":"bool"},{"internalType":"uint8","name":"_energyLossChallenger","type":"uint8"},{"internalType":"uint8","name":"_energyLossOpponent","type":"uint8"}],"name":"setBattleSettings","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"uint256","name":"_threshold","type":"uint256"},{"internalType":"int256","name":"_probability","type":"int256"}],"name":"setCriticalEnergySettings","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"uint256","name":"_freeMaxDailyBattles","type":"uint256"},{"internalType":"uint256","name":"_freeBattleRegenerationTime","type":"uint256"},{"internalType":"uint256","name":"_standardMaxDailyBattles","type":"uint256"},{"internalType":"uint256","name":"_standardBattleRegenerationTime","type":"uint256"},{"internalType":"uint256","name":"_maxFreeTierDailyBattlesPerAddress","type":"uint256"}],"name":"setDailyBattleSettings","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"components":[{"internalType":"uint256","name":"threshold","type":"uint256"},{"internalType":"int256","name":"adjustment","type":"int256"}],"internalType":"struct TokiemonBattles.EnergyThreshold[]","name":"_newThresholds","type":"tuple[]"}],"name":"setEnergyThresholds","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"address","name":"_oracle","type":"address"}],"name":"setOracle","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"uint256","name":"_minReward","type":"uint256"},{"internalType":"uint256","name":"_maxReward","type":"uint256"},{"internalType":"uint256","name":"_maxLevel","type":"uint256"},{"internalType":"uint256","name":"_freeTierMultiplier","type":"uint256"},{"internalType":"uint256","name":"_bonusMultiplier","type":"uint256"},{"internalType":"uint256","name":"_idleMultiplier","type":"uint256"}],"name":"setRewardConfig","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"uint256","name":"tier","type":"uint256"},{"internalType":"uint256","name":"manaAmount","type":"uint256"}],"name":"setTierManaPerBattle","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"bytes4","name":"interfaceId","type":"bytes4"}],"name":"supportsInterface","outputs":[{"internalType":"bool","name":"","type":"bool"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"uint256","name":"","type":"uint256"}],"name":"tierManaPerBattle","outputs":[{"internalType":"uint256","name":"","type":"uint256"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"tokiemonEnergy","outputs":[{"internalType":"contract TokiemonEnergy","name":"","type":"address"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"tokiemonEquipment","outputs":[{"internalType":"contract TokiemonEquipment","name":"","type":"address"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"tokiemonNFT","outputs":[{"internalType":"contract TokiemonNFT","name":"","type":"address"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"uint256[]","name":"tokenIds","type":"uint256[]"}],"name":"trainBatch","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"uint256","name":"_packId","type":"uint256"},{"internalType":"uint256","name":"_price","type":"uint256"},{"internalType":"uint256","name":"_battles","type":"uint256"},{"internalType":"bool","name":"_active","type":"bool"}],"name":"updateBattlePack","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"address payable","name":"_to","type":"address"}],"name":"withdrawFunds","outputs":[],"stateMutability":"nonpayable","type":"function"}]