Contract Name:
FallbackHandler
Contract Source Code:
// SPDX-License-Identifier: MIT
// OpenZeppelin Contracts (last updated v4.5.0) (token/ERC1155/IERC1155Receiver.sol)
pragma solidity ^0.8.0;
import "../../utils/introspection/IERC165.sol";
/**
* @dev _Available since v3.1._
*/
interface IERC1155Receiver is IERC165 {
/**
* @dev Handles the receipt of a single ERC1155 token type. This function is
* called at the end of a `safeTransferFrom` after the balance has been updated.
*
* NOTE: To accept the transfer, this must return
* `bytes4(keccak256("onERC1155Received(address,address,uint256,uint256,bytes)"))`
* (i.e. 0xf23a6e61, or its own function selector).
*
* @param operator The address which initiated the transfer (i.e. msg.sender)
* @param from The address which previously owned the token
* @param id The ID of the token being transferred
* @param value The amount of tokens being transferred
* @param data Additional data with no specified format
* @return `bytes4(keccak256("onERC1155Received(address,address,uint256,uint256,bytes)"))` if transfer is allowed
*/
function onERC1155Received(
address operator,
address from,
uint256 id,
uint256 value,
bytes calldata data
) external returns (bytes4);
/**
* @dev Handles the receipt of a multiple ERC1155 token types. This function
* is called at the end of a `safeBatchTransferFrom` after the balances have
* been updated.
*
* NOTE: To accept the transfer(s), this must return
* `bytes4(keccak256("onERC1155BatchReceived(address,address,uint256[],uint256[],bytes)"))`
* (i.e. 0xbc197c81, or its own function selector).
*
* @param operator The address which initiated the batch transfer (i.e. msg.sender)
* @param from The address which previously owned the token
* @param ids An array containing ids of each token being transferred (order and length must match values array)
* @param values An array containing amounts of each token being transferred (order and length must match ids array)
* @param data Additional data with no specified format
* @return `bytes4(keccak256("onERC1155BatchReceived(address,address,uint256[],uint256[],bytes)"))` if transfer is allowed
*/
function onERC1155BatchReceived(
address operator,
address from,
uint256[] calldata ids,
uint256[] calldata values,
bytes calldata data
) external returns (bytes4);
}
// SPDX-License-Identifier: MIT
// OpenZeppelin Contracts (last updated v4.6.0) (token/ERC721/IERC721Receiver.sol)
pragma solidity ^0.8.0;
/**
* @title ERC721 token receiver interface
* @dev Interface for any contract that wants to support safeTransfers
* from ERC721 asset contracts.
*/
interface IERC721Receiver {
/**
* @dev Whenever an {IERC721} `tokenId` token is transferred to this contract via {IERC721-safeTransferFrom}
* by `operator` from `from`, this function is called.
*
* It must return its Solidity selector to confirm the token transfer.
* If any other value is returned or the interface is not implemented by the recipient, the transfer will be reverted.
*
* The selector can be obtained in Solidity with `IERC721Receiver.onERC721Received.selector`.
*/
function onERC721Received(
address operator,
address from,
uint256 tokenId,
bytes calldata data
) external returns (bytes4);
}
// SPDX-License-Identifier: MIT
// OpenZeppelin Contracts v4.4.1 (utils/introspection/IERC165.sol)
pragma solidity ^0.8.0;
/**
* @dev Interface of the ERC165 standard, as defined in the
* https://eips.ethereum.org/EIPS/eip-165[EIP].
*
* Implementers can declare support of contract interfaces, which can then be
* queried by others ({ERC165Checker}).
*
* For an implementation, see {ERC165}.
*/
interface IERC165 {
/**
* @dev Returns true if this contract implements the interface defined by
* `interfaceId`. See the corresponding
* https://eips.ethereum.org/EIPS/eip-165#how-interfaces-are-identified[EIP section]
* to learn more about how these ids are created.
*
* This function call must use less than 30 000 gas.
*/
function supportsInterface(bytes4 interfaceId) external view returns (bool);
}
// SPDX-License-Identifier: MIT
pragma solidity 0.8.19;
import { RoleManagerStorage } from "./managers/RoleManager.sol";
import { ModuleManagerStorage } from "./managers/ModuleManager.sol";
/// @title Cyan Wallet Core Storage - A Cyan wallet's core storage.
/// @dev This contract only needed if the Module wants to access main storage of the wallet.
/// Must be the very first parent of the Module contract.
/// @author Bulgantamir Gankhuyag - <bulgaa@usecyan.com>
/// @author Naranbayar Uuganbayar - <naba@usecyan.com>
abstract contract CoreStorage is RoleManagerStorage, ModuleManagerStorage {
}
// SPDX-License-Identifier: MIT
pragma solidity 0.8.19;
import "@openzeppelin/contracts/token/ERC721/IERC721Receiver.sol";
import "@openzeppelin/contracts/token/ERC1155/IERC1155Receiver.sol";
import "../helpers/Utils.sol";
import "./CoreStorage.sol";
import "./Lockers.sol" as Lockers;
/// @title Cyan Wallet Fallback Handler - A Cyan wallet's fallback handler.
/// @author Bulgantamir Gankhuyag - <bulgaa@usecyan.com>
/// @author Naranbayar Uuganbayar - <naba@usecyan.com>
contract FallbackHandler is CoreStorage, IERC721Receiver, IERC1155Receiver {
// bytes4(keccak256("isValidSignature(bytes32,bytes)"));
bytes4 internal constant ERC1271_MAGIC_VALUE = 0x1626ba7e;
// bytes4(keccak256("supportsInterface(bytes4)"))
bytes4 private constant ERC165_INTERFACE = 0x01ffc9a7;
// bytes4(keccak256("onERC1155Received(address,address,uint256,uint256,bytes)"));
bytes4 private constant ERC1155_RECEIVED = 0xf23a6e61;
// bytes4(keccak256("onERC1155BatchReceived(address,address,uint256[],uint256[],bytes)"));
bytes4 private constant ERC1155_BATCH_RECEIVED = 0xbc197c81;
// bytes4(keccak256("onERC721Received(address,address,uint256,bytes)"));
bytes4 private constant ERC721_RECEIVED = 0x150b7a02;
event EthTransferred(address indexed receiver, uint256 value);
/// @notice Allows the wallet to receive an ERC721 tokens.
function onERC721Received(
address,
address,
uint256,
bytes calldata
) external pure returns (bytes4) {
return ERC721_RECEIVED;
}
/// @notice Allows the wallet to receive an ERC1155 token.
function onERC1155Received(
address,
address,
uint256,
uint256,
bytes calldata
) external pure returns (bytes4) {
return ERC1155_RECEIVED;
}
/// @notice Allows the wallet to receive an ERC1155 tokens.
function onERC1155BatchReceived(
address,
address,
uint256[] calldata,
uint256[] calldata,
bytes calldata
) external pure returns (bytes4) {
return ERC1155_BATCH_RECEIVED;
}
function supportsInterface(bytes4 interfaceId) external view virtual override returns (bool) {
return
interfaceId == type(IERC1155Receiver).interfaceId ||
interfaceId == type(IERC721Receiver).interfaceId ||
interfaceId == type(IERC165).interfaceId;
}
/// @notice Return whether the signature provided is valid for the provided data.
/// @param data Data signed on the behalf of the wallet.
/// @param signature Signature byte array associated with the data.
/// @return magicValue Returns a magic value (0x1626ba7e) if the given signature is correct.
function isValidSignature(bytes32 data, bytes calldata signature) external view returns (bytes4 magicValue) {
require(signature.length == 65, "Invalid signature length.");
address signer = Utils.recoverSigner(data, signature);
require(signer == _owner, "Invalid signer.");
return ERC1271_MAGIC_VALUE;
}
function isLockedERC721(address collection, uint256 tokenId) external view returns (bool) {
return Lockers.isLockedERC721(collection, tokenId);
}
function isLockedByCyanPlanERC721(address collection, uint256 tokenId) external view returns (bool) {
return Lockers.isLockedByCyanPlanERC721(collection, tokenId);
}
function isLockedByApePlan(address collection, uint256 tokenId) external view returns (bool) {
return Lockers.isLockedByApePlan(collection, tokenId);
}
function getLockedERC1155Amount(address collection, uint256 tokenId) external view returns (uint256) {
return Lockers.getLockedERC1155Amount(collection, tokenId);
}
function getApeLockState(address collection, uint256 tokenId) external view returns (uint8) {
return Lockers.getApeLockState(collection, tokenId);
}
}
// SPDX-License-Identifier: MIT
pragma solidity 0.8.19;
// keccak256("wallet.YugaModule.lockedApe")
bytes32 constant APE_PLAN_LOCKER_SLOT = 0x010881fa8a1edce184936a8e4e08060bba49cb5145c9b396e6e80c0c6b0e1269;
// keccak256("wallet.ERC721Module.lockedERC721")
bytes32 constant CYAN_PLAN_LOCKER_SLOT_ERC721 = 0x25888debd3e1e584ccaebe1162c7763ec457a94078c5d0d9a1d32a926ff9973c;
// keccak256("wallet.ERC1155Module.lockedERC1155")
bytes32 constant CYAN_PLAN_LOCKER_SLOT_ERC1155 = 0xdcc609ac7fc3b6a216ce1445788736c9dbe88a58b25a13af71623e6da931efa0;
// keccak256("wallet.CryptoPunksModule.lockedCryptoPunks")
bytes32 constant CRYPTO_PUNKS_PLAN_LOCKER_SLOT = 0x67ae504a494a1bd5120fdcd8b3565de046d61ac7bb95311090f1976ec179a99a;
struct ApePlanLocker {
/// @notice Map of the locked tokens.
/// Note: Collection Address => Token ID => Lock state
mapping(address => mapping(uint256 => uint8)) tokens;
}
struct CyanPlanLockerERC721 {
/// @notice Locked tokens count of the collection.
/// Note: Collection Address => Number of locked tokens
mapping(address => uint256) count;
/// @notice Map of the locked tokens.
/// Note: Collection Address => Token ID => isLocked
mapping(address => mapping(uint256 => bool)) tokens;
}
struct CyanPlanLockerCryptoPunks {
/// @notice Locked tokens count of the CryptoPunks.
/// Note: Number of locked tokens
uint256 count;
/// @notice Map of the locked tokens.
/// Note: CryptoPunk index => isLocked
mapping(uint256 => bool) tokens;
}
struct CyanPlanLockerERC1155 {
/// @notice Map of the locked ERC1155 tokens.
/// Note: Collection Address => Token ID => amount
mapping(address => mapping(uint256 => uint256)) tokens;
}
/// @notice Checks whether the NFT is locked or not. This method checks both ERC721 lock and ApePlan lock.
/// @param collection Collection address.
/// @param tokenId Token ID.
/// @return isLocked Whether the token is locked or not.
function isLockedERC721(address collection, uint256 tokenId) view returns (bool) {
return isLockedByCyanPlanERC721(collection, tokenId) || isLockedByApePlan(collection, tokenId);
}
/// @notice Checks whether the ERC721 token is locked or not.
/// @param collection Collection address.
/// @param tokenId Token ID.
/// @return isLocked Whether the token is locked or not.
function isLockedByCyanPlanERC721(address collection, uint256 tokenId) view returns (bool) {
return getCyanPlanLockerERC721().tokens[collection][tokenId];
}
/// @notice Checks whether the CryptoPunks token is locked or not.
/// @param tokenId Token ID.
/// @return isLocked Whether the token is locked or not.
function isLockedByCryptoPunkPlan(uint256 tokenId) view returns (bool) {
return getCyanPlanLockerCryptoPunks().tokens[tokenId];
}
/// @notice Checks whether the BAYC, MAYC or BAKC token is locked or not.
/// @param collection Ape collection address.
/// @param tokenId Token ID.
/// @return isLocked Whether the token is ape locked or not.
function isLockedByApePlan(address collection, uint256 tokenId) view returns (bool) {
return getApePlanLocker().tokens[collection][tokenId] != 0;
}
/// @notice Returns amount of locked ERC1155Token items.
/// @param collection Collection address.
/// @param tokenId Token ID.
/// @return isLocked Whether the token is locked or not.
function getLockedERC1155Amount(address collection, uint256 tokenId) view returns (uint256) {
return getCyanPlanLockerERC1155().tokens[collection][tokenId];
}
/// @notice Returns ape lock state.
/// @param collection Ape collection address.
/// @param tokenId Token ID.
/// @return Ape locks state.
function getApeLockState(address collection, uint256 tokenId) view returns (uint8) {
return getApePlanLocker().tokens[collection][tokenId];
}
/// @dev Returns the map of the locked ERC721 tokens.
/// @return result CyanPlanLockerERC721 struct of the locked tokens.
/// Note: Collection Address => Token ID => isLocked
function getCyanPlanLockerERC721() pure returns (CyanPlanLockerERC721 storage result) {
assembly {
result.slot := CYAN_PLAN_LOCKER_SLOT_ERC721
}
}
/// @dev Returns the map of the locked ERC1155 tokens.
/// @return result CyanPlanERC1155Locker struct of the locked tokens.
/// Note: Collection Address => Token ID => locked amount
function getCyanPlanLockerERC1155() pure returns (CyanPlanLockerERC1155 storage result) {
assembly {
result.slot := CYAN_PLAN_LOCKER_SLOT_ERC1155
}
}
/// @dev Returns the map of the locked Crypto Punks.
/// @return result CryptoPunksPlanLocker struct of the locked tokens.
/// Note: CryptoPunk index => isLocked
function getCyanPlanLockerCryptoPunks() pure returns (CyanPlanLockerCryptoPunks storage result) {
assembly {
result.slot := CRYPTO_PUNKS_PLAN_LOCKER_SLOT
}
}
/// @dev Returns the map of the locked tokens.
/// @return result ApePlanLocker struct of the locked tokens.
/// Note: Collection Address => Token ID => Lock state
function getApePlanLocker() pure returns (ApePlanLocker storage result) {
assembly {
result.slot := APE_PLAN_LOCKER_SLOT
}
}
// SPDX-License-Identifier: MIT
pragma solidity 0.8.19;
/// @title Cyan Wallet Module Manager Storage - A Cyan wallet's module manager's storage.
/// @author Bulgantamir Gankhuyag - <bulgaa@usecyan.com>
/// @author Naranbayar Uuganbayar - <naba@usecyan.com>
abstract contract ModuleManagerStorage {
/// @notice Storing allowed contract methods.
/// Note: Target Contract Address => Sighash of method => Module address
mapping(address => mapping(bytes4 => address)) internal _modules;
/// @notice Storing internally allowed module methods.
/// Note: Sighash of module method => Module address
mapping(bytes4 => address) internal _internalModules;
}
/// @title Cyan Wallet Module Manager - A Cyan wallet's module manager's functionalities.
/// @author Bulgantamir Gankhuyag - <bulgaa@usecyan.com>
/// @author Naranbayar Uuganbayar - <naba@usecyan.com>
abstract contract IModuleManager is ModuleManagerStorage {
event SetModule(address target, bytes4 funcHash, address oldModule, address newModule);
event SetInternalModule(bytes4 funcHash, address oldModule, address newModule);
/// @notice Sets the handler module of the target's function.
/// @param target Address of the target contract.
/// @param funcHash Sighash of the target contract's method.
/// @param module Address of the handler module.
function setModule(
address target,
bytes4 funcHash,
address module
) external virtual;
/// @notice Returns a handling module of the target function.
/// @param target Address of the target contract.
/// @param funcHash Sighash of the target contract's method.
/// @return module Handler module.
function getModule(address target, bytes4 funcHash) external view returns (address) {
return _modules[target][funcHash];
}
/// @notice Sets the internal handler module of the function.
/// @param funcHash Sighash of the module method.
/// @param module Address of the handler module.
function setInternalModule(bytes4 funcHash, address module) external virtual;
/// @notice Returns an internal handling module of the given function.
/// @param funcHash Sighash of the module's method.
/// @return module Handler module.
function getInternalModule(bytes4 funcHash) external view returns (address) {
return _internalModules[funcHash];
}
/// @notice Used to call module functions on the wallet.
/// Usually used to call locking function of the module on the wallet.
/// @param data Data payload of the transaction.
/// @return Result of the execution.
function executeModule(bytes memory data) external virtual returns (bytes memory);
}
// SPDX-License-Identifier: MIT
pragma solidity 0.8.19;
/// @title Cyan Wallet Role Manager - A Cyan wallet's role manager's storage.
/// @author Bulgantamir Gankhuyag - <bulgaa@usecyan.com>
/// @author Naranbayar Uuganbayar - <naba@usecyan.com>
abstract contract RoleManagerStorage {
address[3] internal _deprecatedOperators; // Deprecated
address internal _admin;
address internal _owner;
mapping(address => bool) internal _operators;
}
/// @title Cyan Wallet Role Manager - A Cyan wallet's role manager's functionalities.
/// @author Bulgantamir Gankhuyag - <bulgaa@usecyan.com>
/// @author Naranbayar Uuganbayar - <naba@usecyan.com>
abstract contract IRoleManager is RoleManagerStorage {
event SetOwner(address owner);
event SetAdmin(address admin);
event SetOperator(address operator, bool isActive);
modifier onlyOperator() {
_checkOnlyOperator();
_;
}
modifier onlyAdmin() {
_checkOnlyAdmin();
_;
}
modifier onlyOwner() {
_checkOnlyOwner();
_;
}
constructor(address admin) {
require(admin != address(0x0), "Invalid admin address.");
_admin = admin;
}
/// @notice Returns current owner of the wallet.
/// @return Address of the current owner.
function getOwner() external view virtual returns (address);
/// @notice Changes the current admin.
/// @param admin New admin address.
function setAdmin(address admin) external virtual;
/// @notice Returns current admin of the core contract.
/// @return Address of the current admin.
function getAdmin() external view virtual returns (address);
/// @notice Sets the operator status.
/// @param operator Operator address.
/// @param isActive Is active or not.
function setOperator(address operator, bool isActive) external virtual;
/// @notice Checks whether the given address is an operator.
/// @param operator Address that will be checked.
/// @return result Boolean result.
function isOperator(address operator) external view virtual returns (bool result);
/// @notice Checks whether the message sender is an operator.
function _checkOnlyOperator() internal view virtual;
/// @notice Checks whether the message sender is an admin.
function _checkOnlyAdmin() internal view virtual;
/// @notice Checks whether the message sender is an owner.
function _checkOnlyOwner() internal view virtual;
}
// SPDX-License-Identifier: MIT
pragma solidity 0.8.19;
library Utils {
/// @notice Executes a transaction to the given address.
/// @param to Target address.
/// @param value Native token value to be sent to the address.
/// @param data Data to be sent to the address.
/// @return result Result of the transaciton.
function _execute(
address to,
uint256 value,
bytes memory data
) internal returns (bytes memory result) {
assembly {
let success := call(gas(), to, value, add(data, 0x20), mload(data), 0, 0)
mstore(result, returndatasize())
returndatacopy(add(result, 0x20), 0, returndatasize())
if eq(success, 0) {
revert(add(result, 0x20), returndatasize())
}
}
}
/// @notice Recover signer address from signature.
/// @param signedHash Arbitrary length data signed on the behalf of the wallet.
/// @param signature Signature byte array associated with signedHash.
/// @return Recovered signer address.
function recoverSigner(bytes32 signedHash, bytes memory signature) internal pure returns (address) {
uint8 v;
bytes32 r;
bytes32 s;
// we jump 32 (0x20) as the first slot of bytes contains the length
// we jump 65 (0x41) per signature
// for v we load 32 bytes ending with v (the first 31 come from s) then apply a mask
assembly {
r := mload(add(signature, 0x20))
s := mload(add(signature, 0x40))
v := byte(0, mload(add(signature, 0x60)))
}
require(v == 27 || v == 28, "Bad v value in signature.");
address recoveredAddress = ecrecover(signedHash, v, r, s);
require(recoveredAddress != address(0), "ecrecover returned 0.");
return recoveredAddress;
}
/// @notice Helper method to parse the function selector from data.
/// @param data Any data to be parsed, mostly calldata of transaction.
/// @return result Parsed function sighash.
function parseFunctionSelector(bytes memory data) internal pure returns (bytes4 result) {
require(data.length >= 4, "Invalid data.");
assembly {
result := mload(add(data, 0x20))
}
}
/// @notice Parse uint256 from given data.
/// @param data Any data to be parsed, mostly calldata of transaction.
/// @param position Position in the data.
/// @return result Uint256 parsed from given data.
function getUint256At(bytes memory data, uint8 position) internal pure returns (uint256 result) {
assembly {
result := mload(add(data, add(position, 0x20)))
}
}
}