Contract Source Code:
// SPDX-License-Identifier: MIT
pragma solidity 0.8.19;
import "./managers/FallbackManager.sol";
import "./ICoreStorage.sol";
import "../helpers/Utils.sol";
/// @title Cyan Wallet Core - A Cyan wallet's core features.
/// @author Bulgantamir Gankhuyag - <[email protected]>
/// @author Naranbayar Uuganbayar - <[email protected]>
contract Core is ICoreStorage, IFallbackManager {
struct Call {
address to;
uint256 value;
bytes data;
}
constructor() ICoreStorage(msg.sender) {}
/// @notice Initiates new wallet.
/// @param owner Address of the wallet owner.
function initiate(address owner) external {
require(_owner == address(0x0), "Wallet already initialized.");
require(owner != address(0x0), "Invalid owner address.");
_owner = owner;
emit SetOwner(owner);
}
/// @notice Main transaction handling method of the wallet.
/// Note: All the non-core transactions go through this method.
/// @param to Destination contract address.
/// @param value Native token value of the transaction.
/// @param data Data payload of the transaction.
/// @return Result of the transaction.
function execute(
address to,
uint256 value,
bytes calldata data
) public payable onlyDelegateCall onlyOwner returns (bytes memory) {
require(address(this).balance >= value, "Not enough balance.");
if (data.length == 0) {
return Utils._execute(to, value, data);
}
bytes4 funcHash = Utils.parseFunctionSelector(data);
address module = Core(_this).getModule(to, funcHash);
require(module != address(0x0), "Not supported method.");
(bool success, bytes memory result) = module.delegatecall(
abi.encodeWithSignature("handleTransaction(address,uint256,bytes)", to, value, data)
);
if (!success) {
assembly {
let ptr := mload(0x40)
returndatacopy(ptr, 0, returndatasize())
revert(ptr, returndatasize())
}
}
return result;
}
function executeBatch(Call[] calldata data) external payable onlyDelegateCall onlyOwner {
for (uint8 i = 0; i < data.length; ++i) {
execute(data[i].to, data[i].value, data[i].data);
}
}
/// @inheritdoc IModuleManager
function executeModule(bytes calldata data) external override onlyDelegateCall onlyOperator returns (bytes memory) {
bytes4 funcHash = Utils.parseFunctionSelector(data);
address module = Core(_this).getInternalModule(funcHash);
require(module != address(0x0), "Not supported method.");
(bool success, bytes memory result) = module.delegatecall(data);
if (!success) {
assembly {
let ptr := mload(0x40)
returndatacopy(ptr, 0, returndatasize())
revert(ptr, returndatasize())
}
}
return result;
}
/// @inheritdoc IFallbackManager
function setFallbackHandler(address handler) external override noDelegateCall onlyAdmin {
require(handler != address(0x0), "Invalid handler address.");
_setFallbackHandler(handler);
}
fallback() external payable onlyDelegateCall {
address handler = Core(_this).getFallbackHandler();
assembly {
if iszero(handler) {
return(0, 0)
}
calldatacopy(0, 0, calldatasize())
let success := delegatecall(gas(), handler, 0, calldatasize(), 0, 0)
returndatacopy(0, 0, returndatasize())
if gt(success, 0) {
return(0, returndatasize())
}
revert(0, returndatasize())
}
}
}
// SPDX-License-Identifier: MIT
pragma solidity 0.8.19;
import "./managers/DelegateCallManager.sol";
import "./managers/RoleManager.sol";
import "./managers/ModuleManager.sol";
/// @title Cyan Wallet Core Storage - A Cyan wallet's core storage features.
/// @dev This contract must be the very first parent of the Core contract and Module contracts.
/// @author Bulgantamir Gankhuyag - <[email protected]>
/// @author Naranbayar Uuganbayar - <[email protected]>
abstract contract ICoreStorage is DelegateCallManager, IRoleManager, IModuleManager {
constructor(address admin) IRoleManager(admin) {
require(admin != address(0x0), "Invalid admin address.");
}
/// @inheritdoc IModuleManager
function setModule(
address target,
bytes4 funcHash,
address module
) external override noDelegateCall onlyAdmin {
emit SetModule(target, funcHash, _modules[target][funcHash], module);
_modules[target][funcHash] = module;
}
/// @inheritdoc IModuleManager
function setInternalModule(bytes4 funcHash, address module) external override noDelegateCall onlyAdmin {
emit SetInternalModule(funcHash, _internalModules[funcHash], module);
_internalModules[funcHash] = module;
}
/// @inheritdoc IRoleManager
function getOwner() external view override onlyDelegateCall returns (address) {
return _owner;
}
/// @inheritdoc IRoleManager
function setAdmin(address admin) external override noDelegateCall onlyAdmin {
require(admin != address(0x0), "Invalid admin address.");
_admin = admin;
emit SetAdmin(admin);
}
/// @inheritdoc IRoleManager
function getAdmin() external view override noDelegateCall returns (address) {
return _admin;
}
/// @inheritdoc IRoleManager
function setOperator(address operator, bool isActive) external override noDelegateCall onlyAdmin {
require(operator != address(0x0), "Invalid operator address.");
_operators[operator] = isActive;
emit SetOperator(operator, isActive);
}
/// @inheritdoc IRoleManager
function _checkOnlyAdmin() internal view override {
if (address(this) != _this) {
require(ICoreStorage(_this).getAdmin() == msg.sender, "Caller is not an admin.");
} else {
require(_admin == msg.sender, "Caller is not an admin.");
}
}
/// @inheritdoc IRoleManager
function isOperator(address operator) external view override noDelegateCall returns (bool result) {
return _operators[operator];
}
/// @inheritdoc IRoleManager
function _checkOnlyOperator() internal view override {
require(ICoreStorage(_this).isOperator(msg.sender), "Caller is not an operator.");
}
/// @inheritdoc IRoleManager
function _checkOnlyOwner() internal view override {
require(_owner == msg.sender, "Caller is not an owner.");
}
}
// SPDX-License-Identifier: MIT
pragma solidity 0.8.19;
/// @title Manage the delegatecall to a contract
/// @notice Base contract that provides a modifier for managing delegatecall to methods in a child contract
abstract contract DelegateCallManager {
/// @dev The address of this contract
address payable internal immutable _this;
constructor() {
// Immutables are computed in the init code of the contract, and then inlined into the deployed bytecode.
// In other words, this variable won't change when it's checked at runtime.
_this = payable(address(this));
}
/// @dev Private method is used instead of inlining into modifier because modifiers are copied into each method,
/// and the use of immutable means the address bytes are copied in every place the modifier is used.
function _checkNotDelegateCall() private view {
require(address(this) == _this, "Only direct calls allowed.");
}
/// @dev Private method is used instead of inlining into modifier because modifiers are copied into each method,
/// and the use of immutable means the address bytes are copied in every place the modifier is used.
function _checkOnlyDelegateCall() private view {
require(address(this) != _this, "Cannot be called directly.");
}
/// @notice Prevents delegatecall into the modified method
modifier noDelegateCall() {
_checkNotDelegateCall();
_;
}
/// @notice Prevents non delegatecall into the modified method
modifier onlyDelegateCall() {
_checkOnlyDelegateCall();
_;
}
}
// SPDX-License-Identifier: MIT
pragma solidity 0.8.19;
/// @title Cyan Wallet Fallback Manager - A Cyan wallet's fallback manager.
/// @author Bulgantamir Gankhuyag - <[email protected]>
/// @author Naranbayar Uuganbayar - <[email protected]>
abstract contract IFallbackManager {
// keccak256("core.fallbackHandler.address")
bytes32 internal constant FALLBACK_HANDLER_STORAGE_SLOT =
0x7734d301adfb6b9d8ff43068373ec4ffef29a42d1456fb5e0ba2ebb9f4793edb;
event ChangedFallbackHandler(address handler);
/// @notice Sets the fallback handler.
/// @param handler Address of the fallback handler.
function _setFallbackHandler(address handler) internal {
bytes32 slot = FALLBACK_HANDLER_STORAGE_SLOT;
assembly {
sstore(slot, handler)
}
emit ChangedFallbackHandler(handler);
}
/// @notice Sets the fallback handler.
/// @param handler Address of the fallback handler.
function setFallbackHandler(address handler) external virtual;
/// @notice Returns the fallback handler.
/// @return handler Address of the fallback handler.
function getFallbackHandler() external view returns (address handler) {
bytes32 slot = FALLBACK_HANDLER_STORAGE_SLOT;
assembly {
handler := sload(slot)
}
}
/// @notice Returns an native token balance of the wallet.
/// return native token balance of the wallet.
function getBalance() external view returns (uint256) {
return address(this).balance;
}
/// @notice Allows the wallet to receive native token.
receive() external payable {}
}
// 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 - <[email protected]>
/// @author Naranbayar Uuganbayar - <[email protected]>
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 - <[email protected]>
/// @author Naranbayar Uuganbayar - <[email protected]>
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 - <[email protected]>
/// @author Naranbayar Uuganbayar - <[email protected]>
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 - <[email protected]>
/// @author Naranbayar Uuganbayar - <[email protected]>
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)))
}
}
}