Overview
ETH Balance
0 ETH
ETH Value
$0.00Advanced mode: Intended for advanced users or developers and will display all Internal Transactions including zero value transfers.
Latest 25 internal transactions (View All)
Advanced mode:
| Parent Transaction Hash | Block | From | To | ||||
|---|---|---|---|---|---|---|---|
| 36925133 | 19 days ago | 0 ETH | |||||
| 36925133 | 19 days ago | 0 ETH | |||||
| 35854546 | 32 days ago | 0 ETH | |||||
| 35854546 | 32 days ago | 0 ETH | |||||
| 34269768 | 50 days ago | 0 ETH | |||||
| 34269768 | 50 days ago | 0 ETH | |||||
| 34096651 | 52 days ago | 0 ETH | |||||
| 34096651 | 52 days ago | 0 ETH | |||||
| 32620188 | 69 days ago | 0 ETH | |||||
| 32620188 | 69 days ago | 0 ETH | |||||
| 32619561 | 69 days ago | 0 ETH | |||||
| 32619561 | 69 days ago | 0 ETH | |||||
| 32618594 | 69 days ago | 0 ETH | |||||
| 32618594 | 69 days ago | 0 ETH | |||||
| 32618277 | 69 days ago | 0 ETH | |||||
| 32618277 | 69 days ago | 0 ETH | |||||
| 32354884 | 72 days ago | 0 ETH | |||||
| 32354884 | 72 days ago | 0 ETH | |||||
| 32354332 | 72 days ago | 0 ETH | |||||
| 32354332 | 72 days ago | 0 ETH | |||||
| 32346376 | 72 days ago | 0 ETH | |||||
| 32346376 | 72 days ago | 0 ETH | |||||
| 32346289 | 72 days ago | 0 ETH | |||||
| 32346289 | 72 days ago | 0 ETH | |||||
| 31654732 | 80 days ago | 0 ETH |
Cross-Chain Transactions
Loading...
Loading
Contract Name:
OracleProxy
Compiler Version
v0.8.18+commit.87f61d96
Optimization Enabled:
Yes with 200 runs
Other Settings:
default evmVersion
Contract Source Code (Solidity Standard Json-Input format)
// SPDX-License-Identifier: MIT
/*
Copyright 2023 Wanchain Foundation.
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
*/
// _ _ _
// __ ____ _ _ __ ___| |__ __ _(_)_ __ __| | _____ __
// \ \ /\ / / _` | '_ \ / __| '_ \ / _` | | '_ \@/ _` |/ _ \ \ / /
// \ V V / (_| | | | | (__| | | | (_| | | | | | (_| | __/\ V /
// \_/\_/ \__,_|_| |_|\___|_| |_|\__,_|_|_| |_|\__,_|\___| \_/
//
//
pragma solidity 0.8.18;
import "../components/Owned.sol";
import "./OracleStorage.sol";
import "../components/Proxy.sol";
/**
* @title OracleProxy
* @dev Proxy contract for Oracle functionality
* This contract provides:
* - Upgradeable implementation
* - Storage management
* - Ownership control
*/
contract OracleProxy is OracleStorage, Owned, Proxy {
/**
* @notice Upgrades the OracleDelegate implementation
* @dev Can only be called by the contract owner
* @param impl Address of the new OracleDelegate contract
* Requirements:
* - New implementation address cannot be zero
* - New implementation address must be different from current
* Emits:
* - Upgraded event with the new implementation address
*/
function upgradeTo(address impl) public onlyOwner {
require(impl != address(0), "Cannot upgrade to invalid address");
require(impl != _implementation, "Cannot upgrade to the same implementation");
_implementation = impl;
emit Upgraded(impl);
}
}// SPDX-License-Identifier: MIT
// OpenZeppelin Contracts (last updated v4.9.0) (access/AccessControl.sol)
pragma solidity ^0.8.0;
import "./IAccessControl.sol";
import "../utils/Context.sol";
import "../utils/Strings.sol";
import "../utils/introspection/ERC165.sol";
/**
* @dev Contract module that allows children to implement role-based access
* control mechanisms. This is a lightweight version that doesn't allow enumerating role
* members except through off-chain means by accessing the contract event logs. Some
* applications may benefit from on-chain enumerability, for those cases see
* {AccessControlEnumerable}.
*
* Roles are referred to by their `bytes32` identifier. These should be exposed
* in the external API and be unique. The best way to achieve this is by
* using `public constant` hash digests:
*
* ```solidity
* bytes32 public constant MY_ROLE = keccak256("MY_ROLE");
* ```
*
* Roles can be used to represent a set of permissions. To restrict access to a
* function call, use {hasRole}:
*
* ```solidity
* function foo() public {
* require(hasRole(MY_ROLE, msg.sender));
* ...
* }
* ```
*
* Roles can be granted and revoked dynamically via the {grantRole} and
* {revokeRole} functions. Each role has an associated admin role, and only
* accounts that have a role's admin role can call {grantRole} and {revokeRole}.
*
* By default, the admin role for all roles is `DEFAULT_ADMIN_ROLE`, which means
* that only accounts with this role will be able to grant or revoke other
* roles. More complex role relationships can be created by using
* {_setRoleAdmin}.
*
* WARNING: The `DEFAULT_ADMIN_ROLE` is also its own admin: it has permission to
* grant and revoke this role. Extra precautions should be taken to secure
* accounts that have been granted it. We recommend using {AccessControlDefaultAdminRules}
* to enforce additional security measures for this role.
*/
abstract contract AccessControl is Context, IAccessControl, ERC165 {
struct RoleData {
mapping(address => bool) members;
bytes32 adminRole;
}
mapping(bytes32 => RoleData) private _roles;
bytes32 public constant DEFAULT_ADMIN_ROLE = 0x00;
/**
* @dev Modifier that checks that an account has a specific role. Reverts
* with a standardized message including the required role.
*
* The format of the revert reason is given by the following regular expression:
*
* /^AccessControl: account (0x[0-9a-f]{40}) is missing role (0x[0-9a-f]{64})$/
*
* _Available since v4.1._
*/
modifier onlyRole(bytes32 role) {
_checkRole(role);
_;
}
/**
* @dev See {IERC165-supportsInterface}.
*/
function supportsInterface(bytes4 interfaceId) public view virtual override returns (bool) {
return interfaceId == type(IAccessControl).interfaceId || super.supportsInterface(interfaceId);
}
/**
* @dev Returns `true` if `account` has been granted `role`.
*/
function hasRole(bytes32 role, address account) public view virtual override returns (bool) {
return _roles[role].members[account];
}
/**
* @dev Revert with a standard message if `_msgSender()` is missing `role`.
* Overriding this function changes the behavior of the {onlyRole} modifier.
*
* Format of the revert message is described in {_checkRole}.
*
* _Available since v4.6._
*/
function _checkRole(bytes32 role) internal view virtual {
_checkRole(role, _msgSender());
}
/**
* @dev Revert with a standard message if `account` is missing `role`.
*
* The format of the revert reason is given by the following regular expression:
*
* /^AccessControl: account (0x[0-9a-f]{40}) is missing role (0x[0-9a-f]{64})$/
*/
function _checkRole(bytes32 role, address account) internal view virtual {
if (!hasRole(role, account)) {
revert(
string(
abi.encodePacked(
"AccessControl: account ",
Strings.toHexString(account),
" is missing role ",
Strings.toHexString(uint256(role), 32)
)
)
);
}
}
/**
* @dev Returns the admin role that controls `role`. See {grantRole} and
* {revokeRole}.
*
* To change a role's admin, use {_setRoleAdmin}.
*/
function getRoleAdmin(bytes32 role) public view virtual override returns (bytes32) {
return _roles[role].adminRole;
}
/**
* @dev Grants `role` to `account`.
*
* If `account` had not been already granted `role`, emits a {RoleGranted}
* event.
*
* Requirements:
*
* - the caller must have ``role``'s admin role.
*
* May emit a {RoleGranted} event.
*/
function grantRole(bytes32 role, address account) public virtual override onlyRole(getRoleAdmin(role)) {
_grantRole(role, account);
}
/**
* @dev Revokes `role` from `account`.
*
* If `account` had been granted `role`, emits a {RoleRevoked} event.
*
* Requirements:
*
* - the caller must have ``role``'s admin role.
*
* May emit a {RoleRevoked} event.
*/
function revokeRole(bytes32 role, address account) public virtual override onlyRole(getRoleAdmin(role)) {
_revokeRole(role, account);
}
/**
* @dev Revokes `role` from the calling account.
*
* Roles are often managed via {grantRole} and {revokeRole}: this function's
* purpose is to provide a mechanism for accounts to lose their privileges
* if they are compromised (such as when a trusted device is misplaced).
*
* If the calling account had been revoked `role`, emits a {RoleRevoked}
* event.
*
* Requirements:
*
* - the caller must be `account`.
*
* May emit a {RoleRevoked} event.
*/
function renounceRole(bytes32 role, address account) public virtual override {
require(account == _msgSender(), "AccessControl: can only renounce roles for self");
_revokeRole(role, account);
}
/**
* @dev Grants `role` to `account`.
*
* If `account` had not been already granted `role`, emits a {RoleGranted}
* event. Note that unlike {grantRole}, this function doesn't perform any
* checks on the calling account.
*
* May emit a {RoleGranted} event.
*
* [WARNING]
* ====
* This function should only be called from the constructor when setting
* up the initial roles for the system.
*
* Using this function in any other way is effectively circumventing the admin
* system imposed by {AccessControl}.
* ====
*
* NOTE: This function is deprecated in favor of {_grantRole}.
*/
function _setupRole(bytes32 role, address account) internal virtual {
_grantRole(role, account);
}
/**
* @dev Sets `adminRole` as ``role``'s admin role.
*
* Emits a {RoleAdminChanged} event.
*/
function _setRoleAdmin(bytes32 role, bytes32 adminRole) internal virtual {
bytes32 previousAdminRole = getRoleAdmin(role);
_roles[role].adminRole = adminRole;
emit RoleAdminChanged(role, previousAdminRole, adminRole);
}
/**
* @dev Grants `role` to `account`.
*
* Internal function without access restriction.
*
* May emit a {RoleGranted} event.
*/
function _grantRole(bytes32 role, address account) internal virtual {
if (!hasRole(role, account)) {
_roles[role].members[account] = true;
emit RoleGranted(role, account, _msgSender());
}
}
/**
* @dev Revokes `role` from `account`.
*
* Internal function without access restriction.
*
* May emit a {RoleRevoked} event.
*/
function _revokeRole(bytes32 role, address account) internal virtual {
if (hasRole(role, account)) {
_roles[role].members[account] = false;
emit RoleRevoked(role, account, _msgSender());
}
}
}// SPDX-License-Identifier: MIT
// OpenZeppelin Contracts v4.4.1 (access/IAccessControl.sol)
pragma solidity ^0.8.0;
/**
* @dev External interface of AccessControl declared to support ERC165 detection.
*/
interface IAccessControl {
/**
* @dev Emitted when `newAdminRole` is set as ``role``'s admin role, replacing `previousAdminRole`
*
* `DEFAULT_ADMIN_ROLE` is the starting admin for all roles, despite
* {RoleAdminChanged} not being emitted signaling this.
*
* _Available since v3.1._
*/
event RoleAdminChanged(bytes32 indexed role, bytes32 indexed previousAdminRole, bytes32 indexed newAdminRole);
/**
* @dev Emitted when `account` is granted `role`.
*
* `sender` is the account that originated the contract call, an admin role
* bearer except when using {AccessControl-_setupRole}.
*/
event RoleGranted(bytes32 indexed role, address indexed account, address indexed sender);
/**
* @dev Emitted when `account` is revoked `role`.
*
* `sender` is the account that originated the contract call:
* - if using `revokeRole`, it is the admin role bearer
* - if using `renounceRole`, it is the role bearer (i.e. `account`)
*/
event RoleRevoked(bytes32 indexed role, address indexed account, address indexed sender);
/**
* @dev Returns `true` if `account` has been granted `role`.
*/
function hasRole(bytes32 role, address account) external view returns (bool);
/**
* @dev Returns the admin role that controls `role`. See {grantRole} and
* {revokeRole}.
*
* To change a role's admin, use {AccessControl-_setRoleAdmin}.
*/
function getRoleAdmin(bytes32 role) external view returns (bytes32);
/**
* @dev Grants `role` to `account`.
*
* If `account` had not been already granted `role`, emits a {RoleGranted}
* event.
*
* Requirements:
*
* - the caller must have ``role``'s admin role.
*/
function grantRole(bytes32 role, address account) external;
/**
* @dev Revokes `role` from `account`.
*
* If `account` had been granted `role`, emits a {RoleRevoked} event.
*
* Requirements:
*
* - the caller must have ``role``'s admin role.
*/
function revokeRole(bytes32 role, address account) external;
/**
* @dev Revokes `role` from the calling account.
*
* Roles are often managed via {grantRole} and {revokeRole}: this function's
* purpose is to provide a mechanism for accounts to lose their privileges
* if they are compromised (such as when a trusted device is misplaced).
*
* If the calling account had been granted `role`, emits a {RoleRevoked}
* event.
*
* Requirements:
*
* - the caller must be `account`.
*/
function renounceRole(bytes32 role, address account) external;
}// SPDX-License-Identifier: MIT
// OpenZeppelin Contracts (last updated v4.9.0) (access/Ownable.sol)
pragma solidity ^0.8.0;
import "../utils/Context.sol";
/**
* @dev Contract module which provides a basic access control mechanism, where
* there is an account (an owner) that can be granted exclusive access to
* specific functions.
*
* By default, the owner account will be the one that deploys the contract. This
* can later be changed with {transferOwnership}.
*
* This module is used through inheritance. It will make available the modifier
* `onlyOwner`, which can be applied to your functions to restrict their use to
* the owner.
*/
abstract contract Ownable is Context {
address private _owner;
event OwnershipTransferred(address indexed previousOwner, address indexed newOwner);
/**
* @dev Initializes the contract setting the deployer as the initial owner.
*/
constructor() {
_transferOwnership(_msgSender());
}
/**
* @dev Throws if called by any account other than the owner.
*/
modifier onlyOwner() {
_checkOwner();
_;
}
/**
* @dev Returns the address of the current owner.
*/
function owner() public view virtual returns (address) {
return _owner;
}
/**
* @dev Throws if the sender is not the owner.
*/
function _checkOwner() internal view virtual {
require(owner() == _msgSender(), "Ownable: caller is not the owner");
}
/**
* @dev Leaves the contract without owner. It will not be possible to call
* `onlyOwner` functions. Can only be called by the current owner.
*
* NOTE: Renouncing ownership will leave the contract without an owner,
* thereby disabling any functionality that is only available to the owner.
*/
function renounceOwnership() public virtual onlyOwner {
_transferOwnership(address(0));
}
/**
* @dev Transfers ownership of the contract to a new account (`newOwner`).
* Can only be called by the current owner.
*/
function transferOwnership(address newOwner) public virtual onlyOwner {
require(newOwner != address(0), "Ownable: new owner is the zero address");
_transferOwnership(newOwner);
}
/**
* @dev Transfers ownership of the contract to a new account (`newOwner`).
* Internal function without access restriction.
*/
function _transferOwnership(address newOwner) internal virtual {
address oldOwner = _owner;
_owner = newOwner;
emit OwnershipTransferred(oldOwner, newOwner);
}
}// SPDX-License-Identifier: MIT
// OpenZeppelin Contracts (last updated v4.9.0) (governance/TimelockController.sol)
pragma solidity ^0.8.0;
import "../access/AccessControl.sol";
import "../token/ERC721/IERC721Receiver.sol";
import "../token/ERC1155/IERC1155Receiver.sol";
/**
* @dev Contract module which acts as a timelocked controller. When set as the
* owner of an `Ownable` smart contract, it enforces a timelock on all
* `onlyOwner` maintenance operations. This gives time for users of the
* controlled contract to exit before a potentially dangerous maintenance
* operation is applied.
*
* By default, this contract is self administered, meaning administration tasks
* have to go through the timelock process. The proposer (resp executor) role
* is in charge of proposing (resp executing) operations. A common use case is
* to position this {TimelockController} as the owner of a smart contract, with
* a multisig or a DAO as the sole proposer.
*
* _Available since v3.3._
*/
contract TimelockController is AccessControl, IERC721Receiver, IERC1155Receiver {
bytes32 public constant TIMELOCK_ADMIN_ROLE = keccak256("TIMELOCK_ADMIN_ROLE");
bytes32 public constant PROPOSER_ROLE = keccak256("PROPOSER_ROLE");
bytes32 public constant EXECUTOR_ROLE = keccak256("EXECUTOR_ROLE");
bytes32 public constant CANCELLER_ROLE = keccak256("CANCELLER_ROLE");
uint256 internal constant _DONE_TIMESTAMP = uint256(1);
mapping(bytes32 => uint256) private _timestamps;
uint256 private _minDelay;
/**
* @dev Emitted when a call is scheduled as part of operation `id`.
*/
event CallScheduled(
bytes32 indexed id,
uint256 indexed index,
address target,
uint256 value,
bytes data,
bytes32 predecessor,
uint256 delay
);
/**
* @dev Emitted when a call is performed as part of operation `id`.
*/
event CallExecuted(bytes32 indexed id, uint256 indexed index, address target, uint256 value, bytes data);
/**
* @dev Emitted when new proposal is scheduled with non-zero salt.
*/
event CallSalt(bytes32 indexed id, bytes32 salt);
/**
* @dev Emitted when operation `id` is cancelled.
*/
event Cancelled(bytes32 indexed id);
/**
* @dev Emitted when the minimum delay for future operations is modified.
*/
event MinDelayChange(uint256 oldDuration, uint256 newDuration);
/**
* @dev Initializes the contract with the following parameters:
*
* - `minDelay`: initial minimum delay for operations
* - `proposers`: accounts to be granted proposer and canceller roles
* - `executors`: accounts to be granted executor role
* - `admin`: optional account to be granted admin role; disable with zero address
*
* IMPORTANT: The optional admin can aid with initial configuration of roles after deployment
* without being subject to delay, but this role should be subsequently renounced in favor of
* administration through timelocked proposals. Previous versions of this contract would assign
* this admin to the deployer automatically and should be renounced as well.
*/
constructor(uint256 minDelay, address[] memory proposers, address[] memory executors, address admin) {
_setRoleAdmin(TIMELOCK_ADMIN_ROLE, TIMELOCK_ADMIN_ROLE);
_setRoleAdmin(PROPOSER_ROLE, TIMELOCK_ADMIN_ROLE);
_setRoleAdmin(EXECUTOR_ROLE, TIMELOCK_ADMIN_ROLE);
_setRoleAdmin(CANCELLER_ROLE, TIMELOCK_ADMIN_ROLE);
// self administration
_setupRole(TIMELOCK_ADMIN_ROLE, address(this));
// optional admin
if (admin != address(0)) {
_setupRole(TIMELOCK_ADMIN_ROLE, admin);
}
// register proposers and cancellers
for (uint256 i = 0; i < proposers.length; ++i) {
_setupRole(PROPOSER_ROLE, proposers[i]);
_setupRole(CANCELLER_ROLE, proposers[i]);
}
// register executors
for (uint256 i = 0; i < executors.length; ++i) {
_setupRole(EXECUTOR_ROLE, executors[i]);
}
_minDelay = minDelay;
emit MinDelayChange(0, minDelay);
}
/**
* @dev Modifier to make a function callable only by a certain role. In
* addition to checking the sender's role, `address(0)` 's role is also
* considered. Granting a role to `address(0)` is equivalent to enabling
* this role for everyone.
*/
modifier onlyRoleOrOpenRole(bytes32 role) {
if (!hasRole(role, address(0))) {
_checkRole(role, _msgSender());
}
_;
}
/**
* @dev Contract might receive/hold ETH as part of the maintenance process.
*/
receive() external payable {}
/**
* @dev See {IERC165-supportsInterface}.
*/
function supportsInterface(bytes4 interfaceId) public view virtual override(IERC165, AccessControl) returns (bool) {
return interfaceId == type(IERC1155Receiver).interfaceId || super.supportsInterface(interfaceId);
}
/**
* @dev Returns whether an id correspond to a registered operation. This
* includes both Pending, Ready and Done operations.
*/
function isOperation(bytes32 id) public view virtual returns (bool) {
return getTimestamp(id) > 0;
}
/**
* @dev Returns whether an operation is pending or not. Note that a "pending" operation may also be "ready".
*/
function isOperationPending(bytes32 id) public view virtual returns (bool) {
return getTimestamp(id) > _DONE_TIMESTAMP;
}
/**
* @dev Returns whether an operation is ready for execution. Note that a "ready" operation is also "pending".
*/
function isOperationReady(bytes32 id) public view virtual returns (bool) {
uint256 timestamp = getTimestamp(id);
return timestamp > _DONE_TIMESTAMP && timestamp <= block.timestamp;
}
/**
* @dev Returns whether an operation is done or not.
*/
function isOperationDone(bytes32 id) public view virtual returns (bool) {
return getTimestamp(id) == _DONE_TIMESTAMP;
}
/**
* @dev Returns the timestamp at which an operation becomes ready (0 for
* unset operations, 1 for done operations).
*/
function getTimestamp(bytes32 id) public view virtual returns (uint256) {
return _timestamps[id];
}
/**
* @dev Returns the minimum delay for an operation to become valid.
*
* This value can be changed by executing an operation that calls `updateDelay`.
*/
function getMinDelay() public view virtual returns (uint256) {
return _minDelay;
}
/**
* @dev Returns the identifier of an operation containing a single
* transaction.
*/
function hashOperation(
address target,
uint256 value,
bytes calldata data,
bytes32 predecessor,
bytes32 salt
) public pure virtual returns (bytes32) {
return keccak256(abi.encode(target, value, data, predecessor, salt));
}
/**
* @dev Returns the identifier of an operation containing a batch of
* transactions.
*/
function hashOperationBatch(
address[] calldata targets,
uint256[] calldata values,
bytes[] calldata payloads,
bytes32 predecessor,
bytes32 salt
) public pure virtual returns (bytes32) {
return keccak256(abi.encode(targets, values, payloads, predecessor, salt));
}
/**
* @dev Schedule an operation containing a single transaction.
*
* Emits {CallSalt} if salt is nonzero, and {CallScheduled}.
*
* Requirements:
*
* - the caller must have the 'proposer' role.
*/
function schedule(
address target,
uint256 value,
bytes calldata data,
bytes32 predecessor,
bytes32 salt,
uint256 delay
) public virtual onlyRole(PROPOSER_ROLE) {
bytes32 id = hashOperation(target, value, data, predecessor, salt);
_schedule(id, delay);
emit CallScheduled(id, 0, target, value, data, predecessor, delay);
if (salt != bytes32(0)) {
emit CallSalt(id, salt);
}
}
/**
* @dev Schedule an operation containing a batch of transactions.
*
* Emits {CallSalt} if salt is nonzero, and one {CallScheduled} event per transaction in the batch.
*
* Requirements:
*
* - the caller must have the 'proposer' role.
*/
function scheduleBatch(
address[] calldata targets,
uint256[] calldata values,
bytes[] calldata payloads,
bytes32 predecessor,
bytes32 salt,
uint256 delay
) public virtual onlyRole(PROPOSER_ROLE) {
require(targets.length == values.length, "TimelockController: length mismatch");
require(targets.length == payloads.length, "TimelockController: length mismatch");
bytes32 id = hashOperationBatch(targets, values, payloads, predecessor, salt);
_schedule(id, delay);
for (uint256 i = 0; i < targets.length; ++i) {
emit CallScheduled(id, i, targets[i], values[i], payloads[i], predecessor, delay);
}
if (salt != bytes32(0)) {
emit CallSalt(id, salt);
}
}
/**
* @dev Schedule an operation that is to become valid after a given delay.
*/
function _schedule(bytes32 id, uint256 delay) private {
require(!isOperation(id), "TimelockController: operation already scheduled");
require(delay >= getMinDelay(), "TimelockController: insufficient delay");
_timestamps[id] = block.timestamp + delay;
}
/**
* @dev Cancel an operation.
*
* Requirements:
*
* - the caller must have the 'canceller' role.
*/
function cancel(bytes32 id) public virtual onlyRole(CANCELLER_ROLE) {
require(isOperationPending(id), "TimelockController: operation cannot be cancelled");
delete _timestamps[id];
emit Cancelled(id);
}
/**
* @dev Execute an (ready) operation containing a single transaction.
*
* Emits a {CallExecuted} event.
*
* Requirements:
*
* - the caller must have the 'executor' role.
*/
// This function can reenter, but it doesn't pose a risk because _afterCall checks that the proposal is pending,
// thus any modifications to the operation during reentrancy should be caught.
// slither-disable-next-line reentrancy-eth
function execute(
address target,
uint256 value,
bytes calldata payload,
bytes32 predecessor,
bytes32 salt
) public payable virtual onlyRoleOrOpenRole(EXECUTOR_ROLE) {
bytes32 id = hashOperation(target, value, payload, predecessor, salt);
_beforeCall(id, predecessor);
_execute(target, value, payload);
emit CallExecuted(id, 0, target, value, payload);
_afterCall(id);
}
/**
* @dev Execute an (ready) operation containing a batch of transactions.
*
* Emits one {CallExecuted} event per transaction in the batch.
*
* Requirements:
*
* - the caller must have the 'executor' role.
*/
// This function can reenter, but it doesn't pose a risk because _afterCall checks that the proposal is pending,
// thus any modifications to the operation during reentrancy should be caught.
// slither-disable-next-line reentrancy-eth
function executeBatch(
address[] calldata targets,
uint256[] calldata values,
bytes[] calldata payloads,
bytes32 predecessor,
bytes32 salt
) public payable virtual onlyRoleOrOpenRole(EXECUTOR_ROLE) {
require(targets.length == values.length, "TimelockController: length mismatch");
require(targets.length == payloads.length, "TimelockController: length mismatch");
bytes32 id = hashOperationBatch(targets, values, payloads, predecessor, salt);
_beforeCall(id, predecessor);
for (uint256 i = 0; i < targets.length; ++i) {
address target = targets[i];
uint256 value = values[i];
bytes calldata payload = payloads[i];
_execute(target, value, payload);
emit CallExecuted(id, i, target, value, payload);
}
_afterCall(id);
}
/**
* @dev Execute an operation's call.
*/
function _execute(address target, uint256 value, bytes calldata data) internal virtual {
(bool success, ) = target.call{value: value}(data);
require(success, "TimelockController: underlying transaction reverted");
}
/**
* @dev Checks before execution of an operation's calls.
*/
function _beforeCall(bytes32 id, bytes32 predecessor) private view {
require(isOperationReady(id), "TimelockController: operation is not ready");
require(predecessor == bytes32(0) || isOperationDone(predecessor), "TimelockController: missing dependency");
}
/**
* @dev Checks after execution of an operation's calls.
*/
function _afterCall(bytes32 id) private {
require(isOperationReady(id), "TimelockController: operation is not ready");
_timestamps[id] = _DONE_TIMESTAMP;
}
/**
* @dev Changes the minimum timelock duration for future operations.
*
* Emits a {MinDelayChange} event.
*
* Requirements:
*
* - the caller must be the timelock itself. This can only be achieved by scheduling and later executing
* an operation where the timelock is the target and the data is the ABI-encoded call to this function.
*/
function updateDelay(uint256 newDelay) external virtual {
require(msg.sender == address(this), "TimelockController: caller must be timelock");
emit MinDelayChange(_minDelay, newDelay);
_minDelay = newDelay;
}
/**
* @dev See {IERC721Receiver-onERC721Received}.
*/
function onERC721Received(address, address, uint256, bytes memory) public virtual override returns (bytes4) {
return this.onERC721Received.selector;
}
/**
* @dev See {IERC1155Receiver-onERC1155Received}.
*/
function onERC1155Received(
address,
address,
uint256,
uint256,
bytes memory
) public virtual override returns (bytes4) {
return this.onERC1155Received.selector;
}
/**
* @dev See {IERC1155Receiver-onERC1155BatchReceived}.
*/
function onERC1155BatchReceived(
address,
address,
uint256[] memory,
uint256[] memory,
bytes memory
) public virtual override returns (bytes4) {
return this.onERC1155BatchReceived.selector;
}
}// SPDX-License-Identifier: MIT
// OpenZeppelin Contracts (last updated v4.5.0) (interfaces/draft-IERC1822.sol)
pragma solidity ^0.8.0;
/**
* @dev ERC1822: Universal Upgradeable Proxy Standard (UUPS) documents a method for upgradeability through a simplified
* proxy whose upgrades are fully controlled by the current implementation.
*/
interface IERC1822Proxiable {
/**
* @dev Returns the storage slot that the proxiable contract assumes is being used to store the implementation
* address.
*
* IMPORTANT: A proxy pointing at a proxiable contract should not be considered proxiable itself, because this risks
* bricking a proxy that upgrades to it, by delegating to itself until out of gas. Thus it is critical that this
* function revert if invoked through a proxy.
*/
function proxiableUUID() external view returns (bytes32);
}// SPDX-License-Identifier: MIT
// OpenZeppelin Contracts (last updated v4.9.0) (interfaces/IERC1967.sol)
pragma solidity ^0.8.0;
/**
* @dev ERC-1967: Proxy Storage Slots. This interface contains the events defined in the ERC.
*
* _Available since v4.8.3._
*/
interface IERC1967 {
/**
* @dev Emitted when the implementation is upgraded.
*/
event Upgraded(address indexed implementation);
/**
* @dev Emitted when the admin account has changed.
*/
event AdminChanged(address previousAdmin, address newAdmin);
/**
* @dev Emitted when the beacon is changed.
*/
event BeaconUpgraded(address indexed beacon);
}// SPDX-License-Identifier: MIT
// OpenZeppelin Contracts v4.4.1 (proxy/beacon/IBeacon.sol)
pragma solidity ^0.8.0;
/**
* @dev This is the interface that {BeaconProxy} expects of its beacon.
*/
interface IBeacon {
/**
* @dev Must return an address that can be used as a delegate call target.
*
* {BeaconProxy} will check that this address is a contract.
*/
function implementation() external view returns (address);
}// SPDX-License-Identifier: MIT
// OpenZeppelin Contracts (last updated v4.7.0) (proxy/ERC1967/ERC1967Proxy.sol)
pragma solidity ^0.8.0;
import "../Proxy.sol";
import "./ERC1967Upgrade.sol";
/**
* @dev This contract implements an upgradeable proxy. It is upgradeable because calls are delegated to an
* implementation address that can be changed. This address is stored in storage in the location specified by
* https://eips.ethereum.org/EIPS/eip-1967[EIP1967], so that it doesn't conflict with the storage layout of the
* implementation behind the proxy.
*/
contract ERC1967Proxy is Proxy, ERC1967Upgrade {
/**
* @dev Initializes the upgradeable proxy with an initial implementation specified by `_logic`.
*
* If `_data` is nonempty, it's used as data in a delegate call to `_logic`. This will typically be an encoded
* function call, and allows initializing the storage of the proxy like a Solidity constructor.
*/
constructor(address _logic, bytes memory _data) payable {
_upgradeToAndCall(_logic, _data, false);
}
/**
* @dev Returns the current implementation address.
*/
function _implementation() internal view virtual override returns (address impl) {
return ERC1967Upgrade._getImplementation();
}
}// SPDX-License-Identifier: MIT
// OpenZeppelin Contracts (last updated v4.9.0) (proxy/ERC1967/ERC1967Upgrade.sol)
pragma solidity ^0.8.2;
import "../beacon/IBeacon.sol";
import "../../interfaces/IERC1967.sol";
import "../../interfaces/draft-IERC1822.sol";
import "../../utils/Address.sol";
import "../../utils/StorageSlot.sol";
/**
* @dev This abstract contract provides getters and event emitting update functions for
* https://eips.ethereum.org/EIPS/eip-1967[EIP1967] slots.
*
* _Available since v4.1._
*/
abstract contract ERC1967Upgrade is IERC1967 {
// This is the keccak-256 hash of "eip1967.proxy.rollback" subtracted by 1
bytes32 private constant _ROLLBACK_SLOT = 0x4910fdfa16fed3260ed0e7147f7cc6da11a60208b5b9406d12a635614ffd9143;
/**
* @dev Storage slot with the address of the current implementation.
* This is the keccak-256 hash of "eip1967.proxy.implementation" subtracted by 1, and is
* validated in the constructor.
*/
bytes32 internal constant _IMPLEMENTATION_SLOT = 0x360894a13ba1a3210667c828492db98dca3e2076cc3735a920a3ca505d382bbc;
/**
* @dev Returns the current implementation address.
*/
function _getImplementation() internal view returns (address) {
return StorageSlot.getAddressSlot(_IMPLEMENTATION_SLOT).value;
}
/**
* @dev Stores a new address in the EIP1967 implementation slot.
*/
function _setImplementation(address newImplementation) private {
require(Address.isContract(newImplementation), "ERC1967: new implementation is not a contract");
StorageSlot.getAddressSlot(_IMPLEMENTATION_SLOT).value = newImplementation;
}
/**
* @dev Perform implementation upgrade
*
* Emits an {Upgraded} event.
*/
function _upgradeTo(address newImplementation) internal {
_setImplementation(newImplementation);
emit Upgraded(newImplementation);
}
/**
* @dev Perform implementation upgrade with additional setup call.
*
* Emits an {Upgraded} event.
*/
function _upgradeToAndCall(address newImplementation, bytes memory data, bool forceCall) internal {
_upgradeTo(newImplementation);
if (data.length > 0 || forceCall) {
Address.functionDelegateCall(newImplementation, data);
}
}
/**
* @dev Perform implementation upgrade with security checks for UUPS proxies, and additional setup call.
*
* Emits an {Upgraded} event.
*/
function _upgradeToAndCallUUPS(address newImplementation, bytes memory data, bool forceCall) internal {
// Upgrades from old implementations will perform a rollback test. This test requires the new
// implementation to upgrade back to the old, non-ERC1822 compliant, implementation. Removing
// this special case will break upgrade paths from old UUPS implementation to new ones.
if (StorageSlot.getBooleanSlot(_ROLLBACK_SLOT).value) {
_setImplementation(newImplementation);
} else {
try IERC1822Proxiable(newImplementation).proxiableUUID() returns (bytes32 slot) {
require(slot == _IMPLEMENTATION_SLOT, "ERC1967Upgrade: unsupported proxiableUUID");
} catch {
revert("ERC1967Upgrade: new implementation is not UUPS");
}
_upgradeToAndCall(newImplementation, data, forceCall);
}
}
/**
* @dev Storage slot with the admin of the contract.
* This is the keccak-256 hash of "eip1967.proxy.admin" subtracted by 1, and is
* validated in the constructor.
*/
bytes32 internal constant _ADMIN_SLOT = 0xb53127684a568b3173ae13b9f8a6016e243e63b6e8ee1178d6a717850b5d6103;
/**
* @dev Returns the current admin.
*/
function _getAdmin() internal view returns (address) {
return StorageSlot.getAddressSlot(_ADMIN_SLOT).value;
}
/**
* @dev Stores a new address in the EIP1967 admin slot.
*/
function _setAdmin(address newAdmin) private {
require(newAdmin != address(0), "ERC1967: new admin is the zero address");
StorageSlot.getAddressSlot(_ADMIN_SLOT).value = newAdmin;
}
/**
* @dev Changes the admin of the proxy.
*
* Emits an {AdminChanged} event.
*/
function _changeAdmin(address newAdmin) internal {
emit AdminChanged(_getAdmin(), newAdmin);
_setAdmin(newAdmin);
}
/**
* @dev The storage slot of the UpgradeableBeacon contract which defines the implementation for this proxy.
* This is bytes32(uint256(keccak256('eip1967.proxy.beacon')) - 1)) and is validated in the constructor.
*/
bytes32 internal constant _BEACON_SLOT = 0xa3f0ad74e5423aebfd80d3ef4346578335a9a72aeaee59ff6cb3582b35133d50;
/**
* @dev Returns the current beacon.
*/
function _getBeacon() internal view returns (address) {
return StorageSlot.getAddressSlot(_BEACON_SLOT).value;
}
/**
* @dev Stores a new beacon in the EIP1967 beacon slot.
*/
function _setBeacon(address newBeacon) private {
require(Address.isContract(newBeacon), "ERC1967: new beacon is not a contract");
require(
Address.isContract(IBeacon(newBeacon).implementation()),
"ERC1967: beacon implementation is not a contract"
);
StorageSlot.getAddressSlot(_BEACON_SLOT).value = newBeacon;
}
/**
* @dev Perform beacon upgrade with additional setup call. Note: This upgrades the address of the beacon, it does
* not upgrade the implementation contained in the beacon (see {UpgradeableBeacon-_setImplementation} for that).
*
* Emits a {BeaconUpgraded} event.
*/
function _upgradeBeaconToAndCall(address newBeacon, bytes memory data, bool forceCall) internal {
_setBeacon(newBeacon);
emit BeaconUpgraded(newBeacon);
if (data.length > 0 || forceCall) {
Address.functionDelegateCall(IBeacon(newBeacon).implementation(), data);
}
}
}// SPDX-License-Identifier: MIT
// OpenZeppelin Contracts (last updated v4.6.0) (proxy/Proxy.sol)
pragma solidity ^0.8.0;
/**
* @dev This abstract contract provides a fallback function that delegates all calls to another contract using the EVM
* instruction `delegatecall`. We refer to the second contract as the _implementation_ behind the proxy, and it has to
* be specified by overriding the virtual {_implementation} function.
*
* Additionally, delegation to the implementation can be triggered manually through the {_fallback} function, or to a
* different contract through the {_delegate} function.
*
* The success and return data of the delegated call will be returned back to the caller of the proxy.
*/
abstract contract Proxy {
/**
* @dev Delegates the current call to `implementation`.
*
* This function does not return to its internal call site, it will return directly to the external caller.
*/
function _delegate(address implementation) internal virtual {
assembly {
// Copy msg.data. We take full control of memory in this inline assembly
// block because it will not return to Solidity code. We overwrite the
// Solidity scratch pad at memory position 0.
calldatacopy(0, 0, calldatasize())
// Call the implementation.
// out and outsize are 0 because we don't know the size yet.
let result := delegatecall(gas(), implementation, 0, calldatasize(), 0, 0)
// Copy the returned data.
returndatacopy(0, 0, returndatasize())
switch result
// delegatecall returns 0 on error.
case 0 {
revert(0, returndatasize())
}
default {
return(0, returndatasize())
}
}
}
/**
* @dev This is a virtual function that should be overridden so it returns the address to which the fallback function
* and {_fallback} should delegate.
*/
function _implementation() internal view virtual returns (address);
/**
* @dev Delegates the current call to the address returned by `_implementation()`.
*
* This function does not return to its internal call site, it will return directly to the external caller.
*/
function _fallback() internal virtual {
_beforeFallback();
_delegate(_implementation());
}
/**
* @dev Fallback function that delegates calls to the address returned by `_implementation()`. Will run if no other
* function in the contract matches the call data.
*/
fallback() external payable virtual {
_fallback();
}
/**
* @dev Fallback function that delegates calls to the address returned by `_implementation()`. Will run if call data
* is empty.
*/
receive() external payable virtual {
_fallback();
}
/**
* @dev Hook that is called before falling back to the implementation. Can happen as part of a manual `_fallback`
* call, or as part of the Solidity `fallback` or `receive` functions.
*
* If overridden should call `super._beforeFallback()`.
*/
function _beforeFallback() internal virtual {}
}// SPDX-License-Identifier: MIT
// OpenZeppelin Contracts (last updated v4.9.0) (proxy/transparent/TransparentUpgradeableProxy.sol)
pragma solidity ^0.8.0;
import "../ERC1967/ERC1967Proxy.sol";
/**
* @dev Interface for {TransparentUpgradeableProxy}. In order to implement transparency, {TransparentUpgradeableProxy}
* does not implement this interface directly, and some of its functions are implemented by an internal dispatch
* mechanism. The compiler is unaware that these functions are implemented by {TransparentUpgradeableProxy} and will not
* include them in the ABI so this interface must be used to interact with it.
*/
interface ITransparentUpgradeableProxy is IERC1967 {
function admin() external view returns (address);
function implementation() external view returns (address);
function changeAdmin(address) external;
function upgradeTo(address) external;
function upgradeToAndCall(address, bytes memory) external payable;
}
/**
* @dev This contract implements a proxy that is upgradeable by an admin.
*
* To avoid https://medium.com/nomic-labs-blog/malicious-backdoors-in-ethereum-proxies-62629adf3357[proxy selector
* clashing], which can potentially be used in an attack, this contract uses the
* https://blog.openzeppelin.com/the-transparent-proxy-pattern/[transparent proxy pattern]. This pattern implies two
* things that go hand in hand:
*
* 1. If any account other than the admin calls the proxy, the call will be forwarded to the implementation, even if
* that call matches one of the admin functions exposed by the proxy itself.
* 2. If the admin calls the proxy, it can access the admin functions, but its calls will never be forwarded to the
* implementation. If the admin tries to call a function on the implementation it will fail with an error that says
* "admin cannot fallback to proxy target".
*
* These properties mean that the admin account can only be used for admin actions like upgrading the proxy or changing
* the admin, so it's best if it's a dedicated account that is not used for anything else. This will avoid headaches due
* to sudden errors when trying to call a function from the proxy implementation.
*
* Our recommendation is for the dedicated account to be an instance of the {ProxyAdmin} contract. If set up this way,
* you should think of the `ProxyAdmin` instance as the real administrative interface of your proxy.
*
* NOTE: The real interface of this proxy is that defined in `ITransparentUpgradeableProxy`. This contract does not
* inherit from that interface, and instead the admin functions are implicitly implemented using a custom dispatch
* mechanism in `_fallback`. Consequently, the compiler will not produce an ABI for this contract. This is necessary to
* fully implement transparency without decoding reverts caused by selector clashes between the proxy and the
* implementation.
*
* WARNING: It is not recommended to extend this contract to add additional external functions. If you do so, the compiler
* will not check that there are no selector conflicts, due to the note above. A selector clash between any new function
* and the functions declared in {ITransparentUpgradeableProxy} will be resolved in favor of the new one. This could
* render the admin operations inaccessible, which could prevent upgradeability. Transparency may also be compromised.
*/
contract TransparentUpgradeableProxy is ERC1967Proxy {
/**
* @dev Initializes an upgradeable proxy managed by `_admin`, backed by the implementation at `_logic`, and
* optionally initialized with `_data` as explained in {ERC1967Proxy-constructor}.
*/
constructor(address _logic, address admin_, bytes memory _data) payable ERC1967Proxy(_logic, _data) {
_changeAdmin(admin_);
}
/**
* @dev Modifier used internally that will delegate the call to the implementation unless the sender is the admin.
*
* CAUTION: This modifier is deprecated, as it could cause issues if the modified function has arguments, and the
* implementation provides a function with the same selector.
*/
modifier ifAdmin() {
if (msg.sender == _getAdmin()) {
_;
} else {
_fallback();
}
}
/**
* @dev If caller is the admin process the call internally, otherwise transparently fallback to the proxy behavior
*/
function _fallback() internal virtual override {
if (msg.sender == _getAdmin()) {
bytes memory ret;
bytes4 selector = msg.sig;
if (selector == ITransparentUpgradeableProxy.upgradeTo.selector) {
ret = _dispatchUpgradeTo();
} else if (selector == ITransparentUpgradeableProxy.upgradeToAndCall.selector) {
ret = _dispatchUpgradeToAndCall();
} else if (selector == ITransparentUpgradeableProxy.changeAdmin.selector) {
ret = _dispatchChangeAdmin();
} else if (selector == ITransparentUpgradeableProxy.admin.selector) {
ret = _dispatchAdmin();
} else if (selector == ITransparentUpgradeableProxy.implementation.selector) {
ret = _dispatchImplementation();
} else {
revert("TransparentUpgradeableProxy: admin cannot fallback to proxy target");
}
assembly {
return(add(ret, 0x20), mload(ret))
}
} else {
super._fallback();
}
}
/**
* @dev Returns the current admin.
*
* TIP: To get this value clients can read directly from the storage slot shown below (specified by EIP1967) using the
* https://eth.wiki/json-rpc/API#eth_getstorageat[`eth_getStorageAt`] RPC call.
* `0xb53127684a568b3173ae13b9f8a6016e243e63b6e8ee1178d6a717850b5d6103`
*/
function _dispatchAdmin() private returns (bytes memory) {
_requireZeroValue();
address admin = _getAdmin();
return abi.encode(admin);
}
/**
* @dev Returns the current implementation.
*
* TIP: To get this value clients can read directly from the storage slot shown below (specified by EIP1967) using the
* https://eth.wiki/json-rpc/API#eth_getstorageat[`eth_getStorageAt`] RPC call.
* `0x360894a13ba1a3210667c828492db98dca3e2076cc3735a920a3ca505d382bbc`
*/
function _dispatchImplementation() private returns (bytes memory) {
_requireZeroValue();
address implementation = _implementation();
return abi.encode(implementation);
}
/**
* @dev Changes the admin of the proxy.
*
* Emits an {AdminChanged} event.
*/
function _dispatchChangeAdmin() private returns (bytes memory) {
_requireZeroValue();
address newAdmin = abi.decode(msg.data[4:], (address));
_changeAdmin(newAdmin);
return "";
}
/**
* @dev Upgrade the implementation of the proxy.
*/
function _dispatchUpgradeTo() private returns (bytes memory) {
_requireZeroValue();
address newImplementation = abi.decode(msg.data[4:], (address));
_upgradeToAndCall(newImplementation, bytes(""), false);
return "";
}
/**
* @dev Upgrade the implementation of the proxy, and then call a function from the new implementation as specified
* by `data`, which should be an encoded function call. This is useful to initialize new storage variables in the
* proxied contract.
*/
function _dispatchUpgradeToAndCall() private returns (bytes memory) {
(address newImplementation, bytes memory data) = abi.decode(msg.data[4:], (address, bytes));
_upgradeToAndCall(newImplementation, data, true);
return "";
}
/**
* @dev Returns the current admin.
*
* CAUTION: This function is deprecated. Use {ERC1967Upgrade-_getAdmin} instead.
*/
function _admin() internal view virtual returns (address) {
return _getAdmin();
}
/**
* @dev To keep this contract fully transparent, all `ifAdmin` functions must be payable. This helper is here to
* emulate some proxy functions being non-payable while still allowing value to pass through.
*/
function _requireZeroValue() private {
require(msg.value == 0);
}
}// SPDX-License-Identifier: MIT
// OpenZeppelin Contracts (last updated v4.9.0) (proxy/utils/Initializable.sol)
pragma solidity ^0.8.2;
import "../../utils/Address.sol";
/**
* @dev This is a base contract to aid in writing upgradeable contracts, or any kind of contract that will be deployed
* behind a proxy. Since proxied contracts do not make use of a constructor, it's common to move constructor logic to an
* external initializer function, usually called `initialize`. It then becomes necessary to protect this initializer
* function so it can only be called once. The {initializer} modifier provided by this contract will have this effect.
*
* The initialization functions use a version number. Once a version number is used, it is consumed and cannot be
* reused. This mechanism prevents re-execution of each "step" but allows the creation of new initialization steps in
* case an upgrade adds a module that needs to be initialized.
*
* For example:
*
* [.hljs-theme-light.nopadding]
* ```solidity
* contract MyToken is ERC20Upgradeable {
* function initialize() initializer public {
* __ERC20_init("MyToken", "MTK");
* }
* }
*
* contract MyTokenV2 is MyToken, ERC20PermitUpgradeable {
* function initializeV2() reinitializer(2) public {
* __ERC20Permit_init("MyToken");
* }
* }
* ```
*
* TIP: To avoid leaving the proxy in an uninitialized state, the initializer function should be called as early as
* possible by providing the encoded function call as the `_data` argument to {ERC1967Proxy-constructor}.
*
* CAUTION: When used with inheritance, manual care must be taken to not invoke a parent initializer twice, or to ensure
* that all initializers are idempotent. This is not verified automatically as constructors are by Solidity.
*
* [CAUTION]
* ====
* Avoid leaving a contract uninitialized.
*
* An uninitialized contract can be taken over by an attacker. This applies to both a proxy and its implementation
* contract, which may impact the proxy. To prevent the implementation contract from being used, you should invoke
* the {_disableInitializers} function in the constructor to automatically lock it when it is deployed:
*
* [.hljs-theme-light.nopadding]
* ```
* /// @custom:oz-upgrades-unsafe-allow constructor
* constructor() {
* _disableInitializers();
* }
* ```
* ====
*/
abstract contract Initializable {
/**
* @dev Indicates that the contract has been initialized.
* @custom:oz-retyped-from bool
*/
uint8 private _initialized;
/**
* @dev Indicates that the contract is in the process of being initialized.
*/
bool private _initializing;
/**
* @dev Triggered when the contract has been initialized or reinitialized.
*/
event Initialized(uint8 version);
/**
* @dev A modifier that defines a protected initializer function that can be invoked at most once. In its scope,
* `onlyInitializing` functions can be used to initialize parent contracts.
*
* Similar to `reinitializer(1)`, except that functions marked with `initializer` can be nested in the context of a
* constructor.
*
* Emits an {Initialized} event.
*/
modifier initializer() {
bool isTopLevelCall = !_initializing;
require(
(isTopLevelCall && _initialized < 1) || (!Address.isContract(address(this)) && _initialized == 1),
"Initializable: contract is already initialized"
);
_initialized = 1;
if (isTopLevelCall) {
_initializing = true;
}
_;
if (isTopLevelCall) {
_initializing = false;
emit Initialized(1);
}
}
/**
* @dev A modifier that defines a protected reinitializer function that can be invoked at most once, and only if the
* contract hasn't been initialized to a greater version before. In its scope, `onlyInitializing` functions can be
* used to initialize parent contracts.
*
* A reinitializer may be used after the original initialization step. This is essential to configure modules that
* are added through upgrades and that require initialization.
*
* When `version` is 1, this modifier is similar to `initializer`, except that functions marked with `reinitializer`
* cannot be nested. If one is invoked in the context of another, execution will revert.
*
* Note that versions can jump in increments greater than 1; this implies that if multiple reinitializers coexist in
* a contract, executing them in the right order is up to the developer or operator.
*
* WARNING: setting the version to 255 will prevent any future reinitialization.
*
* Emits an {Initialized} event.
*/
modifier reinitializer(uint8 version) {
require(!_initializing && _initialized < version, "Initializable: contract is already initialized");
_initialized = version;
_initializing = true;
_;
_initializing = false;
emit Initialized(version);
}
/**
* @dev Modifier to protect an initialization function so that it can only be invoked by functions with the
* {initializer} and {reinitializer} modifiers, directly or indirectly.
*/
modifier onlyInitializing() {
require(_initializing, "Initializable: contract is not initializing");
_;
}
/**
* @dev Locks the contract, preventing any future reinitialization. This cannot be part of an initializer call.
* Calling this in the constructor of a contract will prevent that contract from being initialized or reinitialized
* to any version. It is recommended to use this to lock implementation contracts that are designed to be called
* through proxies.
*
* Emits an {Initialized} event the first time it is successfully executed.
*/
function _disableInitializers() internal virtual {
require(!_initializing, "Initializable: contract is initializing");
if (_initialized != type(uint8).max) {
_initialized = type(uint8).max;
emit Initialized(type(uint8).max);
}
}
/**
* @dev Returns the highest version that has been initialized. See {reinitializer}.
*/
function _getInitializedVersion() internal view returns (uint8) {
return _initialized;
}
/**
* @dev Returns `true` if the contract is currently initializing. See {onlyInitializing}.
*/
function _isInitializing() internal view returns (bool) {
return _initializing;
}
}// SPDX-License-Identifier: MIT
// OpenZeppelin Contracts (last updated v4.9.0) (security/ReentrancyGuard.sol)
pragma solidity ^0.8.0;
/**
* @dev Contract module that helps prevent reentrant calls to a function.
*
* Inheriting from `ReentrancyGuard` will make the {nonReentrant} modifier
* available, which can be applied to functions to make sure there are no nested
* (reentrant) calls to them.
*
* Note that because there is a single `nonReentrant` guard, functions marked as
* `nonReentrant` may not call one another. This can be worked around by making
* those functions `private`, and then adding `external` `nonReentrant` entry
* points to them.
*
* TIP: If you would like to learn more about reentrancy and alternative ways
* to protect against it, check out our blog post
* https://blog.openzeppelin.com/reentrancy-after-istanbul/[Reentrancy After Istanbul].
*/
abstract contract ReentrancyGuard {
// Booleans are more expensive than uint256 or any type that takes up a full
// word because each write operation emits an extra SLOAD to first read the
// slot's contents, replace the bits taken up by the boolean, and then write
// back. This is the compiler's defense against contract upgrades and
// pointer aliasing, and it cannot be disabled.
// The values being non-zero value makes deployment a bit more expensive,
// but in exchange the refund on every call to nonReentrant will be lower in
// amount. Since refunds are capped to a percentage of the total
// transaction's gas, it is best to keep them low in cases like this one, to
// increase the likelihood of the full refund coming into effect.
uint256 private constant _NOT_ENTERED = 1;
uint256 private constant _ENTERED = 2;
uint256 private _status;
constructor() {
_status = _NOT_ENTERED;
}
/**
* @dev Prevents a contract from calling itself, directly or indirectly.
* Calling a `nonReentrant` function from another `nonReentrant`
* function is not supported. It is possible to prevent this from happening
* by making the `nonReentrant` function external, and making it call a
* `private` function that does the actual work.
*/
modifier nonReentrant() {
_nonReentrantBefore();
_;
_nonReentrantAfter();
}
function _nonReentrantBefore() private {
// On the first call to nonReentrant, _status will be _NOT_ENTERED
require(_status != _ENTERED, "ReentrancyGuard: reentrant call");
// Any calls to nonReentrant after this point will fail
_status = _ENTERED;
}
function _nonReentrantAfter() private {
// By storing the original value once again, a refund is triggered (see
// https://eips.ethereum.org/EIPS/eip-2200)
_status = _NOT_ENTERED;
}
/**
* @dev Returns true if the reentrancy guard is currently set to "entered", which indicates there is a
* `nonReentrant` function in the call stack.
*/
function _reentrancyGuardEntered() internal view returns (bool) {
return _status == _ENTERED;
}
}// SPDX-License-Identifier: MIT
// OpenZeppelin Contracts (last updated v4.9.0) (token/ERC1155/ERC1155.sol)
pragma solidity ^0.8.0;
import "./IERC1155.sol";
import "./IERC1155Receiver.sol";
import "./extensions/IERC1155MetadataURI.sol";
import "../../utils/Address.sol";
import "../../utils/Context.sol";
import "../../utils/introspection/ERC165.sol";
/**
* @dev Implementation of the basic standard multi-token.
* See https://eips.ethereum.org/EIPS/eip-1155
* Originally based on code by Enjin: https://github.com/enjin/erc-1155
*
* _Available since v3.1._
*/
contract ERC1155 is Context, ERC165, IERC1155, IERC1155MetadataURI {
using Address for address;
// Mapping from token ID to account balances
mapping(uint256 => mapping(address => uint256)) private _balances;
// Mapping from account to operator approvals
mapping(address => mapping(address => bool)) private _operatorApprovals;
// Used as the URI for all token types by relying on ID substitution, e.g. https://token-cdn-domain/{id}.json
string private _uri;
/**
* @dev See {_setURI}.
*/
constructor(string memory uri_) {
_setURI(uri_);
}
/**
* @dev See {IERC165-supportsInterface}.
*/
function supportsInterface(bytes4 interfaceId) public view virtual override(ERC165, IERC165) returns (bool) {
return
interfaceId == type(IERC1155).interfaceId ||
interfaceId == type(IERC1155MetadataURI).interfaceId ||
super.supportsInterface(interfaceId);
}
/**
* @dev See {IERC1155MetadataURI-uri}.
*
* This implementation returns the same URI for *all* token types. It relies
* on the token type ID substitution mechanism
* https://eips.ethereum.org/EIPS/eip-1155#metadata[defined in the EIP].
*
* Clients calling this function must replace the `\{id\}` substring with the
* actual token type ID.
*/
function uri(uint256) public view virtual override returns (string memory) {
return _uri;
}
/**
* @dev See {IERC1155-balanceOf}.
*
* Requirements:
*
* - `account` cannot be the zero address.
*/
function balanceOf(address account, uint256 id) public view virtual override returns (uint256) {
require(account != address(0), "ERC1155: address zero is not a valid owner");
return _balances[id][account];
}
/**
* @dev See {IERC1155-balanceOfBatch}.
*
* Requirements:
*
* - `accounts` and `ids` must have the same length.
*/
function balanceOfBatch(
address[] memory accounts,
uint256[] memory ids
) public view virtual override returns (uint256[] memory) {
require(accounts.length == ids.length, "ERC1155: accounts and ids length mismatch");
uint256[] memory batchBalances = new uint256[](accounts.length);
for (uint256 i = 0; i < accounts.length; ++i) {
batchBalances[i] = balanceOf(accounts[i], ids[i]);
}
return batchBalances;
}
/**
* @dev See {IERC1155-setApprovalForAll}.
*/
function setApprovalForAll(address operator, bool approved) public virtual override {
_setApprovalForAll(_msgSender(), operator, approved);
}
/**
* @dev See {IERC1155-isApprovedForAll}.
*/
function isApprovedForAll(address account, address operator) public view virtual override returns (bool) {
return _operatorApprovals[account][operator];
}
/**
* @dev See {IERC1155-safeTransferFrom}.
*/
function safeTransferFrom(
address from,
address to,
uint256 id,
uint256 amount,
bytes memory data
) public virtual override {
require(
from == _msgSender() || isApprovedForAll(from, _msgSender()),
"ERC1155: caller is not token owner or approved"
);
_safeTransferFrom(from, to, id, amount, data);
}
/**
* @dev See {IERC1155-safeBatchTransferFrom}.
*/
function safeBatchTransferFrom(
address from,
address to,
uint256[] memory ids,
uint256[] memory amounts,
bytes memory data
) public virtual override {
require(
from == _msgSender() || isApprovedForAll(from, _msgSender()),
"ERC1155: caller is not token owner or approved"
);
_safeBatchTransferFrom(from, to, ids, amounts, data);
}
/**
* @dev Transfers `amount` tokens of token type `id` from `from` to `to`.
*
* Emits a {TransferSingle} event.
*
* Requirements:
*
* - `to` cannot be the zero address.
* - `from` must have a balance of tokens of type `id` of at least `amount`.
* - If `to` refers to a smart contract, it must implement {IERC1155Receiver-onERC1155Received} and return the
* acceptance magic value.
*/
function _safeTransferFrom(
address from,
address to,
uint256 id,
uint256 amount,
bytes memory data
) internal virtual {
require(to != address(0), "ERC1155: transfer to the zero address");
address operator = _msgSender();
uint256[] memory ids = _asSingletonArray(id);
uint256[] memory amounts = _asSingletonArray(amount);
_beforeTokenTransfer(operator, from, to, ids, amounts, data);
uint256 fromBalance = _balances[id][from];
require(fromBalance >= amount, "ERC1155: insufficient balance for transfer");
unchecked {
_balances[id][from] = fromBalance - amount;
}
_balances[id][to] += amount;
emit TransferSingle(operator, from, to, id, amount);
_afterTokenTransfer(operator, from, to, ids, amounts, data);
_doSafeTransferAcceptanceCheck(operator, from, to, id, amount, data);
}
/**
* @dev xref:ROOT:erc1155.adoc#batch-operations[Batched] version of {_safeTransferFrom}.
*
* Emits a {TransferBatch} event.
*
* Requirements:
*
* - If `to` refers to a smart contract, it must implement {IERC1155Receiver-onERC1155BatchReceived} and return the
* acceptance magic value.
*/
function _safeBatchTransferFrom(
address from,
address to,
uint256[] memory ids,
uint256[] memory amounts,
bytes memory data
) internal virtual {
require(ids.length == amounts.length, "ERC1155: ids and amounts length mismatch");
require(to != address(0), "ERC1155: transfer to the zero address");
address operator = _msgSender();
_beforeTokenTransfer(operator, from, to, ids, amounts, data);
for (uint256 i = 0; i < ids.length; ++i) {
uint256 id = ids[i];
uint256 amount = amounts[i];
uint256 fromBalance = _balances[id][from];
require(fromBalance >= amount, "ERC1155: insufficient balance for transfer");
unchecked {
_balances[id][from] = fromBalance - amount;
}
_balances[id][to] += amount;
}
emit TransferBatch(operator, from, to, ids, amounts);
_afterTokenTransfer(operator, from, to, ids, amounts, data);
_doSafeBatchTransferAcceptanceCheck(operator, from, to, ids, amounts, data);
}
/**
* @dev Sets a new URI for all token types, by relying on the token type ID
* substitution mechanism
* https://eips.ethereum.org/EIPS/eip-1155#metadata[defined in the EIP].
*
* By this mechanism, any occurrence of the `\{id\}` substring in either the
* URI or any of the amounts in the JSON file at said URI will be replaced by
* clients with the token type ID.
*
* For example, the `https://token-cdn-domain/\{id\}.json` URI would be
* interpreted by clients as
* `https://token-cdn-domain/000000000000000000000000000000000000000000000000000000000004cce0.json`
* for token type ID 0x4cce0.
*
* See {uri}.
*
* Because these URIs cannot be meaningfully represented by the {URI} event,
* this function emits no events.
*/
function _setURI(string memory newuri) internal virtual {
_uri = newuri;
}
/**
* @dev Creates `amount` tokens of token type `id`, and assigns them to `to`.
*
* Emits a {TransferSingle} event.
*
* Requirements:
*
* - `to` cannot be the zero address.
* - If `to` refers to a smart contract, it must implement {IERC1155Receiver-onERC1155Received} and return the
* acceptance magic value.
*/
function _mint(address to, uint256 id, uint256 amount, bytes memory data) internal virtual {
require(to != address(0), "ERC1155: mint to the zero address");
address operator = _msgSender();
uint256[] memory ids = _asSingletonArray(id);
uint256[] memory amounts = _asSingletonArray(amount);
_beforeTokenTransfer(operator, address(0), to, ids, amounts, data);
_balances[id][to] += amount;
emit TransferSingle(operator, address(0), to, id, amount);
_afterTokenTransfer(operator, address(0), to, ids, amounts, data);
_doSafeTransferAcceptanceCheck(operator, address(0), to, id, amount, data);
}
/**
* @dev xref:ROOT:erc1155.adoc#batch-operations[Batched] version of {_mint}.
*
* Emits a {TransferBatch} event.
*
* Requirements:
*
* - `ids` and `amounts` must have the same length.
* - If `to` refers to a smart contract, it must implement {IERC1155Receiver-onERC1155BatchReceived} and return the
* acceptance magic value.
*/
function _mintBatch(
address to,
uint256[] memory ids,
uint256[] memory amounts,
bytes memory data
) internal virtual {
require(to != address(0), "ERC1155: mint to the zero address");
require(ids.length == amounts.length, "ERC1155: ids and amounts length mismatch");
address operator = _msgSender();
_beforeTokenTransfer(operator, address(0), to, ids, amounts, data);
for (uint256 i = 0; i < ids.length; i++) {
_balances[ids[i]][to] += amounts[i];
}
emit TransferBatch(operator, address(0), to, ids, amounts);
_afterTokenTransfer(operator, address(0), to, ids, amounts, data);
_doSafeBatchTransferAcceptanceCheck(operator, address(0), to, ids, amounts, data);
}
/**
* @dev Destroys `amount` tokens of token type `id` from `from`
*
* Emits a {TransferSingle} event.
*
* Requirements:
*
* - `from` cannot be the zero address.
* - `from` must have at least `amount` tokens of token type `id`.
*/
function _burn(address from, uint256 id, uint256 amount) internal virtual {
require(from != address(0), "ERC1155: burn from the zero address");
address operator = _msgSender();
uint256[] memory ids = _asSingletonArray(id);
uint256[] memory amounts = _asSingletonArray(amount);
_beforeTokenTransfer(operator, from, address(0), ids, amounts, "");
uint256 fromBalance = _balances[id][from];
require(fromBalance >= amount, "ERC1155: burn amount exceeds balance");
unchecked {
_balances[id][from] = fromBalance - amount;
}
emit TransferSingle(operator, from, address(0), id, amount);
_afterTokenTransfer(operator, from, address(0), ids, amounts, "");
}
/**
* @dev xref:ROOT:erc1155.adoc#batch-operations[Batched] version of {_burn}.
*
* Emits a {TransferBatch} event.
*
* Requirements:
*
* - `ids` and `amounts` must have the same length.
*/
function _burnBatch(address from, uint256[] memory ids, uint256[] memory amounts) internal virtual {
require(from != address(0), "ERC1155: burn from the zero address");
require(ids.length == amounts.length, "ERC1155: ids and amounts length mismatch");
address operator = _msgSender();
_beforeTokenTransfer(operator, from, address(0), ids, amounts, "");
for (uint256 i = 0; i < ids.length; i++) {
uint256 id = ids[i];
uint256 amount = amounts[i];
uint256 fromBalance = _balances[id][from];
require(fromBalance >= amount, "ERC1155: burn amount exceeds balance");
unchecked {
_balances[id][from] = fromBalance - amount;
}
}
emit TransferBatch(operator, from, address(0), ids, amounts);
_afterTokenTransfer(operator, from, address(0), ids, amounts, "");
}
/**
* @dev Approve `operator` to operate on all of `owner` tokens
*
* Emits an {ApprovalForAll} event.
*/
function _setApprovalForAll(address owner, address operator, bool approved) internal virtual {
require(owner != operator, "ERC1155: setting approval status for self");
_operatorApprovals[owner][operator] = approved;
emit ApprovalForAll(owner, operator, approved);
}
/**
* @dev Hook that is called before any token transfer. This includes minting
* and burning, as well as batched variants.
*
* The same hook is called on both single and batched variants. For single
* transfers, the length of the `ids` and `amounts` arrays will be 1.
*
* Calling conditions (for each `id` and `amount` pair):
*
* - When `from` and `to` are both non-zero, `amount` of ``from``'s tokens
* of token type `id` will be transferred to `to`.
* - When `from` is zero, `amount` tokens of token type `id` will be minted
* for `to`.
* - when `to` is zero, `amount` of ``from``'s tokens of token type `id`
* will be burned.
* - `from` and `to` are never both zero.
* - `ids` and `amounts` have the same, non-zero length.
*
* To learn more about hooks, head to xref:ROOT:extending-contracts.adoc#using-hooks[Using Hooks].
*/
function _beforeTokenTransfer(
address operator,
address from,
address to,
uint256[] memory ids,
uint256[] memory amounts,
bytes memory data
) internal virtual {}
/**
* @dev Hook that is called after any token transfer. This includes minting
* and burning, as well as batched variants.
*
* The same hook is called on both single and batched variants. For single
* transfers, the length of the `id` and `amount` arrays will be 1.
*
* Calling conditions (for each `id` and `amount` pair):
*
* - When `from` and `to` are both non-zero, `amount` of ``from``'s tokens
* of token type `id` will be transferred to `to`.
* - When `from` is zero, `amount` tokens of token type `id` will be minted
* for `to`.
* - when `to` is zero, `amount` of ``from``'s tokens of token type `id`
* will be burned.
* - `from` and `to` are never both zero.
* - `ids` and `amounts` have the same, non-zero length.
*
* To learn more about hooks, head to xref:ROOT:extending-contracts.adoc#using-hooks[Using Hooks].
*/
function _afterTokenTransfer(
address operator,
address from,
address to,
uint256[] memory ids,
uint256[] memory amounts,
bytes memory data
) internal virtual {}
function _doSafeTransferAcceptanceCheck(
address operator,
address from,
address to,
uint256 id,
uint256 amount,
bytes memory data
) private {
if (to.isContract()) {
try IERC1155Receiver(to).onERC1155Received(operator, from, id, amount, data) returns (bytes4 response) {
if (response != IERC1155Receiver.onERC1155Received.selector) {
revert("ERC1155: ERC1155Receiver rejected tokens");
}
} catch Error(string memory reason) {
revert(reason);
} catch {
revert("ERC1155: transfer to non-ERC1155Receiver implementer");
}
}
}
function _doSafeBatchTransferAcceptanceCheck(
address operator,
address from,
address to,
uint256[] memory ids,
uint256[] memory amounts,
bytes memory data
) private {
if (to.isContract()) {
try IERC1155Receiver(to).onERC1155BatchReceived(operator, from, ids, amounts, data) returns (
bytes4 response
) {
if (response != IERC1155Receiver.onERC1155BatchReceived.selector) {
revert("ERC1155: ERC1155Receiver rejected tokens");
}
} catch Error(string memory reason) {
revert(reason);
} catch {
revert("ERC1155: transfer to non-ERC1155Receiver implementer");
}
}
}
function _asSingletonArray(uint256 element) private pure returns (uint256[] memory) {
uint256[] memory array = new uint256[](1);
array[0] = element;
return array;
}
}// SPDX-License-Identifier: MIT
// OpenZeppelin Contracts v4.4.1 (token/ERC1155/extensions/IERC1155MetadataURI.sol)
pragma solidity ^0.8.0;
import "../IERC1155.sol";
/**
* @dev Interface of the optional ERC1155MetadataExtension interface, as defined
* in the https://eips.ethereum.org/EIPS/eip-1155#metadata-extensions[EIP].
*
* _Available since v3.1._
*/
interface IERC1155MetadataURI is IERC1155 {
/**
* @dev Returns the URI for token type `id`.
*
* If the `\{id\}` substring is present in the URI, it must be replaced by
* clients with the actual token type ID.
*/
function uri(uint256 id) external view returns (string memory);
}// SPDX-License-Identifier: MIT
// OpenZeppelin Contracts (last updated v4.9.0) (token/ERC1155/IERC1155.sol)
pragma solidity ^0.8.0;
import "../../utils/introspection/IERC165.sol";
/**
* @dev Required interface of an ERC1155 compliant contract, as defined in the
* https://eips.ethereum.org/EIPS/eip-1155[EIP].
*
* _Available since v3.1._
*/
interface IERC1155 is IERC165 {
/**
* @dev Emitted when `value` tokens of token type `id` are transferred from `from` to `to` by `operator`.
*/
event TransferSingle(address indexed operator, address indexed from, address indexed to, uint256 id, uint256 value);
/**
* @dev Equivalent to multiple {TransferSingle} events, where `operator`, `from` and `to` are the same for all
* transfers.
*/
event TransferBatch(
address indexed operator,
address indexed from,
address indexed to,
uint256[] ids,
uint256[] values
);
/**
* @dev Emitted when `account` grants or revokes permission to `operator` to transfer their tokens, according to
* `approved`.
*/
event ApprovalForAll(address indexed account, address indexed operator, bool approved);
/**
* @dev Emitted when the URI for token type `id` changes to `value`, if it is a non-programmatic URI.
*
* If an {URI} event was emitted for `id`, the standard
* https://eips.ethereum.org/EIPS/eip-1155#metadata-extensions[guarantees] that `value` will equal the value
* returned by {IERC1155MetadataURI-uri}.
*/
event URI(string value, uint256 indexed id);
/**
* @dev Returns the amount of tokens of token type `id` owned by `account`.
*
* Requirements:
*
* - `account` cannot be the zero address.
*/
function balanceOf(address account, uint256 id) external view returns (uint256);
/**
* @dev xref:ROOT:erc1155.adoc#batch-operations[Batched] version of {balanceOf}.
*
* Requirements:
*
* - `accounts` and `ids` must have the same length.
*/
function balanceOfBatch(
address[] calldata accounts,
uint256[] calldata ids
) external view returns (uint256[] memory);
/**
* @dev Grants or revokes permission to `operator` to transfer the caller's tokens, according to `approved`,
*
* Emits an {ApprovalForAll} event.
*
* Requirements:
*
* - `operator` cannot be the caller.
*/
function setApprovalForAll(address operator, bool approved) external;
/**
* @dev Returns true if `operator` is approved to transfer ``account``'s tokens.
*
* See {setApprovalForAll}.
*/
function isApprovedForAll(address account, address operator) external view returns (bool);
/**
* @dev Transfers `amount` tokens of token type `id` from `from` to `to`.
*
* Emits a {TransferSingle} event.
*
* Requirements:
*
* - `to` cannot be the zero address.
* - If the caller is not `from`, it must have been approved to spend ``from``'s tokens via {setApprovalForAll}.
* - `from` must have a balance of tokens of type `id` of at least `amount`.
* - If `to` refers to a smart contract, it must implement {IERC1155Receiver-onERC1155Received} and return the
* acceptance magic value.
*/
function safeTransferFrom(address from, address to, uint256 id, uint256 amount, bytes calldata data) external;
/**
* @dev xref:ROOT:erc1155.adoc#batch-operations[Batched] version of {safeTransferFrom}.
*
* Emits a {TransferBatch} event.
*
* Requirements:
*
* - `ids` and `amounts` must have the same length.
* - If `to` refers to a smart contract, it must implement {IERC1155Receiver-onERC1155BatchReceived} and return the
* acceptance magic value.
*/
function safeBatchTransferFrom(
address from,
address to,
uint256[] calldata ids,
uint256[] calldata amounts,
bytes calldata data
) external;
}// 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.5.0) (token/ERC1155/utils/ERC1155Holder.sol)
pragma solidity ^0.8.0;
import "./ERC1155Receiver.sol";
/**
* Simple implementation of `ERC1155Receiver` that will allow a contract to hold ERC1155 tokens.
*
* IMPORTANT: When inheriting this contract, you must include a way to use the received tokens, otherwise they will be
* stuck.
*
* @dev _Available since v3.1._
*/
contract ERC1155Holder is ERC1155Receiver {
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;
}
}// SPDX-License-Identifier: MIT
// OpenZeppelin Contracts v4.4.1 (token/ERC1155/utils/ERC1155Receiver.sol)
pragma solidity ^0.8.0;
import "../IERC1155Receiver.sol";
import "../../../utils/introspection/ERC165.sol";
/**
* @dev _Available since v3.1._
*/
abstract contract ERC1155Receiver is ERC165, IERC1155Receiver {
/**
* @dev See {IERC165-supportsInterface}.
*/
function supportsInterface(bytes4 interfaceId) public view virtual override(ERC165, IERC165) returns (bool) {
return interfaceId == type(IERC1155Receiver).interfaceId || super.supportsInterface(interfaceId);
}
}// SPDX-License-Identifier: MIT
// OpenZeppelin Contracts (last updated v4.9.0) (token/ERC20/ERC20.sol)
pragma solidity ^0.8.0;
import "./IERC20.sol";
import "./extensions/IERC20Metadata.sol";
import "../../utils/Context.sol";
/**
* @dev Implementation of the {IERC20} interface.
*
* This implementation is agnostic to the way tokens are created. This means
* that a supply mechanism has to be added in a derived contract using {_mint}.
* For a generic mechanism see {ERC20PresetMinterPauser}.
*
* TIP: For a detailed writeup see our guide
* https://forum.openzeppelin.com/t/how-to-implement-erc20-supply-mechanisms/226[How
* to implement supply mechanisms].
*
* The default value of {decimals} is 18. To change this, you should override
* this function so it returns a different value.
*
* We have followed general OpenZeppelin Contracts guidelines: functions revert
* instead returning `false` on failure. This behavior is nonetheless
* conventional and does not conflict with the expectations of ERC20
* applications.
*
* Additionally, an {Approval} event is emitted on calls to {transferFrom}.
* This allows applications to reconstruct the allowance for all accounts just
* by listening to said events. Other implementations of the EIP may not emit
* these events, as it isn't required by the specification.
*
* Finally, the non-standard {decreaseAllowance} and {increaseAllowance}
* functions have been added to mitigate the well-known issues around setting
* allowances. See {IERC20-approve}.
*/
contract ERC20 is Context, IERC20, IERC20Metadata {
mapping(address => uint256) private _balances;
mapping(address => mapping(address => uint256)) private _allowances;
uint256 private _totalSupply;
string private _name;
string private _symbol;
/**
* @dev Sets the values for {name} and {symbol}.
*
* All two of these values are immutable: they can only be set once during
* construction.
*/
constructor(string memory name_, string memory symbol_) {
_name = name_;
_symbol = symbol_;
}
/**
* @dev Returns the name of the token.
*/
function name() public view virtual override returns (string memory) {
return _name;
}
/**
* @dev Returns the symbol of the token, usually a shorter version of the
* name.
*/
function symbol() public view virtual override returns (string memory) {
return _symbol;
}
/**
* @dev Returns the number of decimals used to get its user representation.
* For example, if `decimals` equals `2`, a balance of `505` tokens should
* be displayed to a user as `5.05` (`505 / 10 ** 2`).
*
* Tokens usually opt for a value of 18, imitating the relationship between
* Ether and Wei. This is the default value returned by this function, unless
* it's overridden.
*
* NOTE: This information is only used for _display_ purposes: it in
* no way affects any of the arithmetic of the contract, including
* {IERC20-balanceOf} and {IERC20-transfer}.
*/
function decimals() public view virtual override returns (uint8) {
return 18;
}
/**
* @dev See {IERC20-totalSupply}.
*/
function totalSupply() public view virtual override returns (uint256) {
return _totalSupply;
}
/**
* @dev See {IERC20-balanceOf}.
*/
function balanceOf(address account) public view virtual override returns (uint256) {
return _balances[account];
}
/**
* @dev See {IERC20-transfer}.
*
* Requirements:
*
* - `to` cannot be the zero address.
* - the caller must have a balance of at least `amount`.
*/
function transfer(address to, uint256 amount) public virtual override returns (bool) {
address owner = _msgSender();
_transfer(owner, to, amount);
return true;
}
/**
* @dev See {IERC20-allowance}.
*/
function allowance(address owner, address spender) public view virtual override returns (uint256) {
return _allowances[owner][spender];
}
/**
* @dev See {IERC20-approve}.
*
* NOTE: If `amount` is the maximum `uint256`, the allowance is not updated on
* `transferFrom`. This is semantically equivalent to an infinite approval.
*
* Requirements:
*
* - `spender` cannot be the zero address.
*/
function approve(address spender, uint256 amount) public virtual override returns (bool) {
address owner = _msgSender();
_approve(owner, spender, amount);
return true;
}
/**
* @dev See {IERC20-transferFrom}.
*
* Emits an {Approval} event indicating the updated allowance. This is not
* required by the EIP. See the note at the beginning of {ERC20}.
*
* NOTE: Does not update the allowance if the current allowance
* is the maximum `uint256`.
*
* Requirements:
*
* - `from` and `to` cannot be the zero address.
* - `from` must have a balance of at least `amount`.
* - the caller must have allowance for ``from``'s tokens of at least
* `amount`.
*/
function transferFrom(address from, address to, uint256 amount) public virtual override returns (bool) {
address spender = _msgSender();
_spendAllowance(from, spender, amount);
_transfer(from, to, amount);
return true;
}
/**
* @dev Atomically increases the allowance granted to `spender` by the caller.
*
* This is an alternative to {approve} that can be used as a mitigation for
* problems described in {IERC20-approve}.
*
* Emits an {Approval} event indicating the updated allowance.
*
* Requirements:
*
* - `spender` cannot be the zero address.
*/
function increaseAllowance(address spender, uint256 addedValue) public virtual returns (bool) {
address owner = _msgSender();
_approve(owner, spender, allowance(owner, spender) + addedValue);
return true;
}
/**
* @dev Atomically decreases the allowance granted to `spender` by the caller.
*
* This is an alternative to {approve} that can be used as a mitigation for
* problems described in {IERC20-approve}.
*
* Emits an {Approval} event indicating the updated allowance.
*
* Requirements:
*
* - `spender` cannot be the zero address.
* - `spender` must have allowance for the caller of at least
* `subtractedValue`.
*/
function decreaseAllowance(address spender, uint256 subtractedValue) public virtual returns (bool) {
address owner = _msgSender();
uint256 currentAllowance = allowance(owner, spender);
require(currentAllowance >= subtractedValue, "ERC20: decreased allowance below zero");
unchecked {
_approve(owner, spender, currentAllowance - subtractedValue);
}
return true;
}
/**
* @dev Moves `amount` of tokens from `from` to `to`.
*
* This internal function is equivalent to {transfer}, and can be used to
* e.g. implement automatic token fees, slashing mechanisms, etc.
*
* Emits a {Transfer} event.
*
* Requirements:
*
* - `from` cannot be the zero address.
* - `to` cannot be the zero address.
* - `from` must have a balance of at least `amount`.
*/
function _transfer(address from, address to, uint256 amount) internal virtual {
require(from != address(0), "ERC20: transfer from the zero address");
require(to != address(0), "ERC20: transfer to the zero address");
_beforeTokenTransfer(from, to, amount);
uint256 fromBalance = _balances[from];
require(fromBalance >= amount, "ERC20: transfer amount exceeds balance");
unchecked {
_balances[from] = fromBalance - amount;
// Overflow not possible: the sum of all balances is capped by totalSupply, and the sum is preserved by
// decrementing then incrementing.
_balances[to] += amount;
}
emit Transfer(from, to, amount);
_afterTokenTransfer(from, to, amount);
}
/** @dev Creates `amount` tokens and assigns them to `account`, increasing
* the total supply.
*
* Emits a {Transfer} event with `from` set to the zero address.
*
* Requirements:
*
* - `account` cannot be the zero address.
*/
function _mint(address account, uint256 amount) internal virtual {
require(account != address(0), "ERC20: mint to the zero address");
_beforeTokenTransfer(address(0), account, amount);
_totalSupply += amount;
unchecked {
// Overflow not possible: balance + amount is at most totalSupply + amount, which is checked above.
_balances[account] += amount;
}
emit Transfer(address(0), account, amount);
_afterTokenTransfer(address(0), account, amount);
}
/**
* @dev Destroys `amount` tokens from `account`, reducing the
* total supply.
*
* Emits a {Transfer} event with `to` set to the zero address.
*
* Requirements:
*
* - `account` cannot be the zero address.
* - `account` must have at least `amount` tokens.
*/
function _burn(address account, uint256 amount) internal virtual {
require(account != address(0), "ERC20: burn from the zero address");
_beforeTokenTransfer(account, address(0), amount);
uint256 accountBalance = _balances[account];
require(accountBalance >= amount, "ERC20: burn amount exceeds balance");
unchecked {
_balances[account] = accountBalance - amount;
// Overflow not possible: amount <= accountBalance <= totalSupply.
_totalSupply -= amount;
}
emit Transfer(account, address(0), amount);
_afterTokenTransfer(account, address(0), amount);
}
/**
* @dev Sets `amount` as the allowance of `spender` over the `owner` s tokens.
*
* This internal function is equivalent to `approve`, and can be used to
* e.g. set automatic allowances for certain subsystems, etc.
*
* Emits an {Approval} event.
*
* Requirements:
*
* - `owner` cannot be the zero address.
* - `spender` cannot be the zero address.
*/
function _approve(address owner, address spender, uint256 amount) internal virtual {
require(owner != address(0), "ERC20: approve from the zero address");
require(spender != address(0), "ERC20: approve to the zero address");
_allowances[owner][spender] = amount;
emit Approval(owner, spender, amount);
}
/**
* @dev Updates `owner` s allowance for `spender` based on spent `amount`.
*
* Does not update the allowance amount in case of infinite allowance.
* Revert if not enough allowance is available.
*
* Might emit an {Approval} event.
*/
function _spendAllowance(address owner, address spender, uint256 amount) internal virtual {
uint256 currentAllowance = allowance(owner, spender);
if (currentAllowance != type(uint256).max) {
require(currentAllowance >= amount, "ERC20: insufficient allowance");
unchecked {
_approve(owner, spender, currentAllowance - amount);
}
}
}
/**
* @dev Hook that is called before any transfer of tokens. This includes
* minting and burning.
*
* Calling conditions:
*
* - when `from` and `to` are both non-zero, `amount` of ``from``'s tokens
* will be transferred to `to`.
* - when `from` is zero, `amount` tokens will be minted for `to`.
* - when `to` is zero, `amount` of ``from``'s tokens will be burned.
* - `from` and `to` are never both zero.
*
* To learn more about hooks, head to xref:ROOT:extending-contracts.adoc#using-hooks[Using Hooks].
*/
function _beforeTokenTransfer(address from, address to, uint256 amount) internal virtual {}
/**
* @dev Hook that is called after any transfer of tokens. This includes
* minting and burning.
*
* Calling conditions:
*
* - when `from` and `to` are both non-zero, `amount` of ``from``'s tokens
* has been transferred to `to`.
* - when `from` is zero, `amount` tokens have been minted for `to`.
* - when `to` is zero, `amount` of ``from``'s tokens have been burned.
* - `from` and `to` are never both zero.
*
* To learn more about hooks, head to xref:ROOT:extending-contracts.adoc#using-hooks[Using Hooks].
*/
function _afterTokenTransfer(address from, address to, uint256 amount) internal virtual {}
}// SPDX-License-Identifier: MIT
// OpenZeppelin Contracts (last updated v4.5.0) (token/ERC20/extensions/ERC20Burnable.sol)
pragma solidity ^0.8.0;
import "../ERC20.sol";
import "../../../utils/Context.sol";
/**
* @dev Extension of {ERC20} that allows token holders to destroy both their own
* tokens and those that they have an allowance for, in a way that can be
* recognized off-chain (via event analysis).
*/
abstract contract ERC20Burnable is Context, ERC20 {
/**
* @dev Destroys `amount` tokens from the caller.
*
* See {ERC20-_burn}.
*/
function burn(uint256 amount) public virtual {
_burn(_msgSender(), amount);
}
/**
* @dev Destroys `amount` tokens from `account`, deducting from the caller's
* allowance.
*
* See {ERC20-_burn} and {ERC20-allowance}.
*
* Requirements:
*
* - the caller must have allowance for ``accounts``'s tokens of at least
* `amount`.
*/
function burnFrom(address account, uint256 amount) public virtual {
_spendAllowance(account, _msgSender(), amount);
_burn(account, amount);
}
}// SPDX-License-Identifier: MIT
// OpenZeppelin Contracts v4.4.1 (token/ERC20/extensions/IERC20Metadata.sol)
pragma solidity ^0.8.0;
import "../IERC20.sol";
/**
* @dev Interface for the optional metadata functions from the ERC20 standard.
*
* _Available since v4.1._
*/
interface IERC20Metadata is IERC20 {
/**
* @dev Returns the name of the token.
*/
function name() external view returns (string memory);
/**
* @dev Returns the symbol of the token.
*/
function symbol() external view returns (string memory);
/**
* @dev Returns the decimals places of the token.
*/
function decimals() external view returns (uint8);
}// SPDX-License-Identifier: MIT
// OpenZeppelin Contracts (last updated v4.9.0) (token/ERC20/extensions/IERC20Permit.sol)
pragma solidity ^0.8.0;
/**
* @dev Interface of the ERC20 Permit extension allowing approvals to be made via signatures, as defined in
* https://eips.ethereum.org/EIPS/eip-2612[EIP-2612].
*
* Adds the {permit} method, which can be used to change an account's ERC20 allowance (see {IERC20-allowance}) by
* presenting a message signed by the account. By not relying on {IERC20-approve}, the token holder account doesn't
* need to send a transaction, and thus is not required to hold Ether at all.
*/
interface IERC20Permit {
/**
* @dev Sets `value` as the allowance of `spender` over ``owner``'s tokens,
* given ``owner``'s signed approval.
*
* IMPORTANT: The same issues {IERC20-approve} has related to transaction
* ordering also apply here.
*
* Emits an {Approval} event.
*
* Requirements:
*
* - `spender` cannot be the zero address.
* - `deadline` must be a timestamp in the future.
* - `v`, `r` and `s` must be a valid `secp256k1` signature from `owner`
* over the EIP712-formatted function arguments.
* - the signature must use ``owner``'s current nonce (see {nonces}).
*
* For more information on the signature format, see the
* https://eips.ethereum.org/EIPS/eip-2612#specification[relevant EIP
* section].
*/
function permit(
address owner,
address spender,
uint256 value,
uint256 deadline,
uint8 v,
bytes32 r,
bytes32 s
) external;
/**
* @dev Returns the current nonce for `owner`. This value must be
* included whenever a signature is generated for {permit}.
*
* Every successful call to {permit} increases ``owner``'s nonce by one. This
* prevents a signature from being used multiple times.
*/
function nonces(address owner) external view returns (uint256);
/**
* @dev Returns the domain separator used in the encoding of the signature for {permit}, as defined by {EIP712}.
*/
// solhint-disable-next-line func-name-mixedcase
function DOMAIN_SEPARATOR() external view returns (bytes32);
}// SPDX-License-Identifier: MIT
// OpenZeppelin Contracts (last updated v4.9.0) (token/ERC20/IERC20.sol)
pragma solidity ^0.8.0;
/**
* @dev Interface of the ERC20 standard as defined in the EIP.
*/
interface IERC20 {
/**
* @dev Emitted when `value` tokens are moved from one account (`from`) to
* another (`to`).
*
* Note that `value` may be zero.
*/
event Transfer(address indexed from, address indexed to, uint256 value);
/**
* @dev Emitted when the allowance of a `spender` for an `owner` is set by
* a call to {approve}. `value` is the new allowance.
*/
event Approval(address indexed owner, address indexed spender, uint256 value);
/**
* @dev Returns the amount of tokens in existence.
*/
function totalSupply() external view returns (uint256);
/**
* @dev Returns the amount of tokens owned by `account`.
*/
function balanceOf(address account) external view returns (uint256);
/**
* @dev Moves `amount` tokens from the caller's account to `to`.
*
* Returns a boolean value indicating whether the operation succeeded.
*
* Emits a {Transfer} event.
*/
function transfer(address to, uint256 amount) external returns (bool);
/**
* @dev Returns the remaining number of tokens that `spender` will be
* allowed to spend on behalf of `owner` through {transferFrom}. This is
* zero by default.
*
* This value changes when {approve} or {transferFrom} are called.
*/
function allowance(address owner, address spender) external view returns (uint256);
/**
* @dev Sets `amount` as the allowance of `spender` over the caller's tokens.
*
* Returns a boolean value indicating whether the operation succeeded.
*
* IMPORTANT: Beware that changing an allowance with this method brings the risk
* that someone may use both the old and the new allowance by unfortunate
* transaction ordering. One possible solution to mitigate this race
* condition is to first reduce the spender's allowance to 0 and set the
* desired value afterwards:
* https://github.com/ethereum/EIPs/issues/20#issuecomment-263524729
*
* Emits an {Approval} event.
*/
function approve(address spender, uint256 amount) external returns (bool);
/**
* @dev Moves `amount` tokens from `from` to `to` using the
* allowance mechanism. `amount` is then deducted from the caller's
* allowance.
*
* Returns a boolean value indicating whether the operation succeeded.
*
* Emits a {Transfer} event.
*/
function transferFrom(address from, address to, uint256 amount) external returns (bool);
}// SPDX-License-Identifier: MIT
// OpenZeppelin Contracts (last updated v4.9.3) (token/ERC20/utils/SafeERC20.sol)
pragma solidity ^0.8.0;
import "../IERC20.sol";
import "../extensions/IERC20Permit.sol";
import "../../../utils/Address.sol";
/**
* @title SafeERC20
* @dev Wrappers around ERC20 operations that throw on failure (when the token
* contract returns false). Tokens that return no value (and instead revert or
* throw on failure) are also supported, non-reverting calls are assumed to be
* successful.
* To use this library you can add a `using SafeERC20 for IERC20;` statement to your contract,
* which allows you to call the safe operations as `token.safeTransfer(...)`, etc.
*/
library SafeERC20 {
using Address for address;
/**
* @dev Transfer `value` amount of `token` from the calling contract to `to`. If `token` returns no value,
* non-reverting calls are assumed to be successful.
*/
function safeTransfer(IERC20 token, address to, uint256 value) internal {
_callOptionalReturn(token, abi.encodeWithSelector(token.transfer.selector, to, value));
}
/**
* @dev Transfer `value` amount of `token` from `from` to `to`, spending the approval given by `from` to the
* calling contract. If `token` returns no value, non-reverting calls are assumed to be successful.
*/
function safeTransferFrom(IERC20 token, address from, address to, uint256 value) internal {
_callOptionalReturn(token, abi.encodeWithSelector(token.transferFrom.selector, from, to, value));
}
/**
* @dev Deprecated. This function has issues similar to the ones found in
* {IERC20-approve}, and its usage is discouraged.
*
* Whenever possible, use {safeIncreaseAllowance} and
* {safeDecreaseAllowance} instead.
*/
function safeApprove(IERC20 token, address spender, uint256 value) internal {
// safeApprove should only be called when setting an initial allowance,
// or when resetting it to zero. To increase and decrease it, use
// 'safeIncreaseAllowance' and 'safeDecreaseAllowance'
require(
(value == 0) || (token.allowance(address(this), spender) == 0),
"SafeERC20: approve from non-zero to non-zero allowance"
);
_callOptionalReturn(token, abi.encodeWithSelector(token.approve.selector, spender, value));
}
/**
* @dev Increase the calling contract's allowance toward `spender` by `value`. If `token` returns no value,
* non-reverting calls are assumed to be successful.
*/
function safeIncreaseAllowance(IERC20 token, address spender, uint256 value) internal {
uint256 oldAllowance = token.allowance(address(this), spender);
_callOptionalReturn(token, abi.encodeWithSelector(token.approve.selector, spender, oldAllowance + value));
}
/**
* @dev Decrease the calling contract's allowance toward `spender` by `value`. If `token` returns no value,
* non-reverting calls are assumed to be successful.
*/
function safeDecreaseAllowance(IERC20 token, address spender, uint256 value) internal {
unchecked {
uint256 oldAllowance = token.allowance(address(this), spender);
require(oldAllowance >= value, "SafeERC20: decreased allowance below zero");
_callOptionalReturn(token, abi.encodeWithSelector(token.approve.selector, spender, oldAllowance - value));
}
}
/**
* @dev Set the calling contract's allowance toward `spender` to `value`. If `token` returns no value,
* non-reverting calls are assumed to be successful. Meant to be used with tokens that require the approval
* to be set to zero before setting it to a non-zero value, such as USDT.
*/
function forceApprove(IERC20 token, address spender, uint256 value) internal {
bytes memory approvalCall = abi.encodeWithSelector(token.approve.selector, spender, value);
if (!_callOptionalReturnBool(token, approvalCall)) {
_callOptionalReturn(token, abi.encodeWithSelector(token.approve.selector, spender, 0));
_callOptionalReturn(token, approvalCall);
}
}
/**
* @dev Use a ERC-2612 signature to set the `owner` approval toward `spender` on `token`.
* Revert on invalid signature.
*/
function safePermit(
IERC20Permit token,
address owner,
address spender,
uint256 value,
uint256 deadline,
uint8 v,
bytes32 r,
bytes32 s
) internal {
uint256 nonceBefore = token.nonces(owner);
token.permit(owner, spender, value, deadline, v, r, s);
uint256 nonceAfter = token.nonces(owner);
require(nonceAfter == nonceBefore + 1, "SafeERC20: permit did not succeed");
}
/**
* @dev Imitates a Solidity high-level call (i.e. a regular function call to a contract), relaxing the requirement
* on the return value: the return value is optional (but if data is returned, it must not be false).
* @param token The token targeted by the call.
* @param data The call data (encoded using abi.encode or one of its variants).
*/
function _callOptionalReturn(IERC20 token, bytes memory data) private {
// We need to perform a low level call here, to bypass Solidity's return data size checking mechanism, since
// we're implementing it ourselves. We use {Address-functionCall} to perform this call, which verifies that
// the target address contains contract code and also asserts for success in the low-level call.
bytes memory returndata = address(token).functionCall(data, "SafeERC20: low-level call failed");
require(returndata.length == 0 || abi.decode(returndata, (bool)), "SafeERC20: ERC20 operation did not succeed");
}
/**
* @dev Imitates a Solidity high-level call (i.e. a regular function call to a contract), relaxing the requirement
* on the return value: the return value is optional (but if data is returned, it must not be false).
* @param token The token targeted by the call.
* @param data The call data (encoded using abi.encode or one of its variants).
*
* This is a variant of {_callOptionalReturn} that silents catches all reverts and returns a bool instead.
*/
function _callOptionalReturnBool(IERC20 token, bytes memory data) private returns (bool) {
// We need to perform a low level call here, to bypass Solidity's return data size checking mechanism, since
// we're implementing it ourselves. We cannot use {Address-functionCall} here since this should return false
// and not revert is the subcall reverts.
(bool success, bytes memory returndata) = address(token).call(data);
return
success && (returndata.length == 0 || abi.decode(returndata, (bool))) && Address.isContract(address(token));
}
}// SPDX-License-Identifier: MIT
// OpenZeppelin Contracts (last updated v4.9.0) (token/ERC721/ERC721.sol)
pragma solidity ^0.8.0;
import "./IERC721.sol";
import "./IERC721Receiver.sol";
import "./extensions/IERC721Metadata.sol";
import "../../utils/Address.sol";
import "../../utils/Context.sol";
import "../../utils/Strings.sol";
import "../../utils/introspection/ERC165.sol";
/**
* @dev Implementation of https://eips.ethereum.org/EIPS/eip-721[ERC721] Non-Fungible Token Standard, including
* the Metadata extension, but not including the Enumerable extension, which is available separately as
* {ERC721Enumerable}.
*/
contract ERC721 is Context, ERC165, IERC721, IERC721Metadata {
using Address for address;
using Strings for uint256;
// Token name
string private _name;
// Token symbol
string private _symbol;
// Mapping from token ID to owner address
mapping(uint256 => address) private _owners;
// Mapping owner address to token count
mapping(address => uint256) private _balances;
// Mapping from token ID to approved address
mapping(uint256 => address) private _tokenApprovals;
// Mapping from owner to operator approvals
mapping(address => mapping(address => bool)) private _operatorApprovals;
/**
* @dev Initializes the contract by setting a `name` and a `symbol` to the token collection.
*/
constructor(string memory name_, string memory symbol_) {
_name = name_;
_symbol = symbol_;
}
/**
* @dev See {IERC165-supportsInterface}.
*/
function supportsInterface(bytes4 interfaceId) public view virtual override(ERC165, IERC165) returns (bool) {
return
interfaceId == type(IERC721).interfaceId ||
interfaceId == type(IERC721Metadata).interfaceId ||
super.supportsInterface(interfaceId);
}
/**
* @dev See {IERC721-balanceOf}.
*/
function balanceOf(address owner) public view virtual override returns (uint256) {
require(owner != address(0), "ERC721: address zero is not a valid owner");
return _balances[owner];
}
/**
* @dev See {IERC721-ownerOf}.
*/
function ownerOf(uint256 tokenId) public view virtual override returns (address) {
address owner = _ownerOf(tokenId);
require(owner != address(0), "ERC721: invalid token ID");
return owner;
}
/**
* @dev See {IERC721Metadata-name}.
*/
function name() public view virtual override returns (string memory) {
return _name;
}
/**
* @dev See {IERC721Metadata-symbol}.
*/
function symbol() public view virtual override returns (string memory) {
return _symbol;
}
/**
* @dev See {IERC721Metadata-tokenURI}.
*/
function tokenURI(uint256 tokenId) public view virtual override returns (string memory) {
_requireMinted(tokenId);
string memory baseURI = _baseURI();
return bytes(baseURI).length > 0 ? string(abi.encodePacked(baseURI, tokenId.toString())) : "";
}
/**
* @dev Base URI for computing {tokenURI}. If set, the resulting URI for each
* token will be the concatenation of the `baseURI` and the `tokenId`. Empty
* by default, can be overridden in child contracts.
*/
function _baseURI() internal view virtual returns (string memory) {
return "";
}
/**
* @dev See {IERC721-approve}.
*/
function approve(address to, uint256 tokenId) public virtual override {
address owner = ERC721.ownerOf(tokenId);
require(to != owner, "ERC721: approval to current owner");
require(
_msgSender() == owner || isApprovedForAll(owner, _msgSender()),
"ERC721: approve caller is not token owner or approved for all"
);
_approve(to, tokenId);
}
/**
* @dev See {IERC721-getApproved}.
*/
function getApproved(uint256 tokenId) public view virtual override returns (address) {
_requireMinted(tokenId);
return _tokenApprovals[tokenId];
}
/**
* @dev See {IERC721-setApprovalForAll}.
*/
function setApprovalForAll(address operator, bool approved) public virtual override {
_setApprovalForAll(_msgSender(), operator, approved);
}
/**
* @dev See {IERC721-isApprovedForAll}.
*/
function isApprovedForAll(address owner, address operator) public view virtual override returns (bool) {
return _operatorApprovals[owner][operator];
}
/**
* @dev See {IERC721-transferFrom}.
*/
function transferFrom(address from, address to, uint256 tokenId) public virtual override {
//solhint-disable-next-line max-line-length
require(_isApprovedOrOwner(_msgSender(), tokenId), "ERC721: caller is not token owner or approved");
_transfer(from, to, tokenId);
}
/**
* @dev See {IERC721-safeTransferFrom}.
*/
function safeTransferFrom(address from, address to, uint256 tokenId) public virtual override {
safeTransferFrom(from, to, tokenId, "");
}
/**
* @dev See {IERC721-safeTransferFrom}.
*/
function safeTransferFrom(address from, address to, uint256 tokenId, bytes memory data) public virtual override {
require(_isApprovedOrOwner(_msgSender(), tokenId), "ERC721: caller is not token owner or approved");
_safeTransfer(from, to, tokenId, data);
}
/**
* @dev Safely transfers `tokenId` token from `from` to `to`, checking first that contract recipients
* are aware of the ERC721 protocol to prevent tokens from being forever locked.
*
* `data` is additional data, it has no specified format and it is sent in call to `to`.
*
* This internal function is equivalent to {safeTransferFrom}, and can be used to e.g.
* implement alternative mechanisms to perform token transfer, such as signature-based.
*
* Requirements:
*
* - `from` cannot be the zero address.
* - `to` cannot be the zero address.
* - `tokenId` token must exist and be owned by `from`.
* - If `to` refers to a smart contract, it must implement {IERC721Receiver-onERC721Received}, which is called upon a safe transfer.
*
* Emits a {Transfer} event.
*/
function _safeTransfer(address from, address to, uint256 tokenId, bytes memory data) internal virtual {
_transfer(from, to, tokenId);
require(_checkOnERC721Received(from, to, tokenId, data), "ERC721: transfer to non ERC721Receiver implementer");
}
/**
* @dev Returns the owner of the `tokenId`. Does NOT revert if token doesn't exist
*/
function _ownerOf(uint256 tokenId) internal view virtual returns (address) {
return _owners[tokenId];
}
/**
* @dev Returns whether `tokenId` exists.
*
* Tokens can be managed by their owner or approved accounts via {approve} or {setApprovalForAll}.
*
* Tokens start existing when they are minted (`_mint`),
* and stop existing when they are burned (`_burn`).
*/
function _exists(uint256 tokenId) internal view virtual returns (bool) {
return _ownerOf(tokenId) != address(0);
}
/**
* @dev Returns whether `spender` is allowed to manage `tokenId`.
*
* Requirements:
*
* - `tokenId` must exist.
*/
function _isApprovedOrOwner(address spender, uint256 tokenId) internal view virtual returns (bool) {
address owner = ERC721.ownerOf(tokenId);
return (spender == owner || isApprovedForAll(owner, spender) || getApproved(tokenId) == spender);
}
/**
* @dev Safely mints `tokenId` and transfers it to `to`.
*
* Requirements:
*
* - `tokenId` must not exist.
* - If `to` refers to a smart contract, it must implement {IERC721Receiver-onERC721Received}, which is called upon a safe transfer.
*
* Emits a {Transfer} event.
*/
function _safeMint(address to, uint256 tokenId) internal virtual {
_safeMint(to, tokenId, "");
}
/**
* @dev Same as {xref-ERC721-_safeMint-address-uint256-}[`_safeMint`], with an additional `data` parameter which is
* forwarded in {IERC721Receiver-onERC721Received} to contract recipients.
*/
function _safeMint(address to, uint256 tokenId, bytes memory data) internal virtual {
_mint(to, tokenId);
require(
_checkOnERC721Received(address(0), to, tokenId, data),
"ERC721: transfer to non ERC721Receiver implementer"
);
}
/**
* @dev Mints `tokenId` and transfers it to `to`.
*
* WARNING: Usage of this method is discouraged, use {_safeMint} whenever possible
*
* Requirements:
*
* - `tokenId` must not exist.
* - `to` cannot be the zero address.
*
* Emits a {Transfer} event.
*/
function _mint(address to, uint256 tokenId) internal virtual {
require(to != address(0), "ERC721: mint to the zero address");
require(!_exists(tokenId), "ERC721: token already minted");
_beforeTokenTransfer(address(0), to, tokenId, 1);
// Check that tokenId was not minted by `_beforeTokenTransfer` hook
require(!_exists(tokenId), "ERC721: token already minted");
unchecked {
// Will not overflow unless all 2**256 token ids are minted to the same owner.
// Given that tokens are minted one by one, it is impossible in practice that
// this ever happens. Might change if we allow batch minting.
// The ERC fails to describe this case.
_balances[to] += 1;
}
_owners[tokenId] = to;
emit Transfer(address(0), to, tokenId);
_afterTokenTransfer(address(0), to, tokenId, 1);
}
/**
* @dev Destroys `tokenId`.
* The approval is cleared when the token is burned.
* This is an internal function that does not check if the sender is authorized to operate on the token.
*
* Requirements:
*
* - `tokenId` must exist.
*
* Emits a {Transfer} event.
*/
function _burn(uint256 tokenId) internal virtual {
address owner = ERC721.ownerOf(tokenId);
_beforeTokenTransfer(owner, address(0), tokenId, 1);
// Update ownership in case tokenId was transferred by `_beforeTokenTransfer` hook
owner = ERC721.ownerOf(tokenId);
// Clear approvals
delete _tokenApprovals[tokenId];
unchecked {
// Cannot overflow, as that would require more tokens to be burned/transferred
// out than the owner initially received through minting and transferring in.
_balances[owner] -= 1;
}
delete _owners[tokenId];
emit Transfer(owner, address(0), tokenId);
_afterTokenTransfer(owner, address(0), tokenId, 1);
}
/**
* @dev Transfers `tokenId` from `from` to `to`.
* As opposed to {transferFrom}, this imposes no restrictions on msg.sender.
*
* Requirements:
*
* - `to` cannot be the zero address.
* - `tokenId` token must be owned by `from`.
*
* Emits a {Transfer} event.
*/
function _transfer(address from, address to, uint256 tokenId) internal virtual {
require(ERC721.ownerOf(tokenId) == from, "ERC721: transfer from incorrect owner");
require(to != address(0), "ERC721: transfer to the zero address");
_beforeTokenTransfer(from, to, tokenId, 1);
// Check that tokenId was not transferred by `_beforeTokenTransfer` hook
require(ERC721.ownerOf(tokenId) == from, "ERC721: transfer from incorrect owner");
// Clear approvals from the previous owner
delete _tokenApprovals[tokenId];
unchecked {
// `_balances[from]` cannot overflow for the same reason as described in `_burn`:
// `from`'s balance is the number of token held, which is at least one before the current
// transfer.
// `_balances[to]` could overflow in the conditions described in `_mint`. That would require
// all 2**256 token ids to be minted, which in practice is impossible.
_balances[from] -= 1;
_balances[to] += 1;
}
_owners[tokenId] = to;
emit Transfer(from, to, tokenId);
_afterTokenTransfer(from, to, tokenId, 1);
}
/**
* @dev Approve `to` to operate on `tokenId`
*
* Emits an {Approval} event.
*/
function _approve(address to, uint256 tokenId) internal virtual {
_tokenApprovals[tokenId] = to;
emit Approval(ERC721.ownerOf(tokenId), to, tokenId);
}
/**
* @dev Approve `operator` to operate on all of `owner` tokens
*
* Emits an {ApprovalForAll} event.
*/
function _setApprovalForAll(address owner, address operator, bool approved) internal virtual {
require(owner != operator, "ERC721: approve to caller");
_operatorApprovals[owner][operator] = approved;
emit ApprovalForAll(owner, operator, approved);
}
/**
* @dev Reverts if the `tokenId` has not been minted yet.
*/
function _requireMinted(uint256 tokenId) internal view virtual {
require(_exists(tokenId), "ERC721: invalid token ID");
}
/**
* @dev Internal function to invoke {IERC721Receiver-onERC721Received} on a target address.
* The call is not executed if the target address is not a contract.
*
* @param from address representing the previous owner of the given token ID
* @param to target address that will receive the tokens
* @param tokenId uint256 ID of the token to be transferred
* @param data bytes optional data to send along with the call
* @return bool whether the call correctly returned the expected magic value
*/
function _checkOnERC721Received(
address from,
address to,
uint256 tokenId,
bytes memory data
) private returns (bool) {
if (to.isContract()) {
try IERC721Receiver(to).onERC721Received(_msgSender(), from, tokenId, data) returns (bytes4 retval) {
return retval == IERC721Receiver.onERC721Received.selector;
} catch (bytes memory reason) {
if (reason.length == 0) {
revert("ERC721: transfer to non ERC721Receiver implementer");
} else {
/// @solidity memory-safe-assembly
assembly {
revert(add(32, reason), mload(reason))
}
}
}
} else {
return true;
}
}
/**
* @dev Hook that is called before any token transfer. This includes minting and burning. If {ERC721Consecutive} is
* used, the hook may be called as part of a consecutive (batch) mint, as indicated by `batchSize` greater than 1.
*
* Calling conditions:
*
* - When `from` and `to` are both non-zero, ``from``'s tokens will be transferred to `to`.
* - When `from` is zero, the tokens will be minted for `to`.
* - When `to` is zero, ``from``'s tokens will be burned.
* - `from` and `to` are never both zero.
* - `batchSize` is non-zero.
*
* To learn more about hooks, head to xref:ROOT:extending-contracts.adoc#using-hooks[Using Hooks].
*/
function _beforeTokenTransfer(address from, address to, uint256 firstTokenId, uint256 batchSize) internal virtual {}
/**
* @dev Hook that is called after any token transfer. This includes minting and burning. If {ERC721Consecutive} is
* used, the hook may be called as part of a consecutive (batch) mint, as indicated by `batchSize` greater than 1.
*
* Calling conditions:
*
* - When `from` and `to` are both non-zero, ``from``'s tokens were transferred to `to`.
* - When `from` is zero, the tokens were minted for `to`.
* - When `to` is zero, ``from``'s tokens were burned.
* - `from` and `to` are never both zero.
* - `batchSize` is non-zero.
*
* To learn more about hooks, head to xref:ROOT:extending-contracts.adoc#using-hooks[Using Hooks].
*/
function _afterTokenTransfer(address from, address to, uint256 firstTokenId, uint256 batchSize) internal virtual {}
/**
* @dev Unsafe write access to the balances, used by extensions that "mint" tokens using an {ownerOf} override.
*
* WARNING: Anyone calling this MUST ensure that the balances remain consistent with the ownership. The invariant
* being that for any address `a` the value returned by `balanceOf(a)` must be equal to the number of tokens such
* that `ownerOf(tokenId)` is `a`.
*/
// solhint-disable-next-line func-name-mixedcase
function __unsafe_increaseBalance(address account, uint256 amount) internal {
_balances[account] += amount;
}
}// SPDX-License-Identifier: MIT
// OpenZeppelin Contracts (last updated v4.8.0) (token/ERC721/extensions/ERC721Enumerable.sol)
pragma solidity ^0.8.0;
import "../ERC721.sol";
import "./IERC721Enumerable.sol";
/**
* @dev This implements an optional extension of {ERC721} defined in the EIP that adds
* enumerability of all the token ids in the contract as well as all token ids owned by each
* account.
*/
abstract contract ERC721Enumerable is ERC721, IERC721Enumerable {
// Mapping from owner to list of owned token IDs
mapping(address => mapping(uint256 => uint256)) private _ownedTokens;
// Mapping from token ID to index of the owner tokens list
mapping(uint256 => uint256) private _ownedTokensIndex;
// Array with all token ids, used for enumeration
uint256[] private _allTokens;
// Mapping from token id to position in the allTokens array
mapping(uint256 => uint256) private _allTokensIndex;
/**
* @dev See {IERC165-supportsInterface}.
*/
function supportsInterface(bytes4 interfaceId) public view virtual override(IERC165, ERC721) returns (bool) {
return interfaceId == type(IERC721Enumerable).interfaceId || super.supportsInterface(interfaceId);
}
/**
* @dev See {IERC721Enumerable-tokenOfOwnerByIndex}.
*/
function tokenOfOwnerByIndex(address owner, uint256 index) public view virtual override returns (uint256) {
require(index < ERC721.balanceOf(owner), "ERC721Enumerable: owner index out of bounds");
return _ownedTokens[owner][index];
}
/**
* @dev See {IERC721Enumerable-totalSupply}.
*/
function totalSupply() public view virtual override returns (uint256) {
return _allTokens.length;
}
/**
* @dev See {IERC721Enumerable-tokenByIndex}.
*/
function tokenByIndex(uint256 index) public view virtual override returns (uint256) {
require(index < ERC721Enumerable.totalSupply(), "ERC721Enumerable: global index out of bounds");
return _allTokens[index];
}
/**
* @dev See {ERC721-_beforeTokenTransfer}.
*/
function _beforeTokenTransfer(
address from,
address to,
uint256 firstTokenId,
uint256 batchSize
) internal virtual override {
super._beforeTokenTransfer(from, to, firstTokenId, batchSize);
if (batchSize > 1) {
// Will only trigger during construction. Batch transferring (minting) is not available afterwards.
revert("ERC721Enumerable: consecutive transfers not supported");
}
uint256 tokenId = firstTokenId;
if (from == address(0)) {
_addTokenToAllTokensEnumeration(tokenId);
} else if (from != to) {
_removeTokenFromOwnerEnumeration(from, tokenId);
}
if (to == address(0)) {
_removeTokenFromAllTokensEnumeration(tokenId);
} else if (to != from) {
_addTokenToOwnerEnumeration(to, tokenId);
}
}
/**
* @dev Private function to add a token to this extension's ownership-tracking data structures.
* @param to address representing the new owner of the given token ID
* @param tokenId uint256 ID of the token to be added to the tokens list of the given address
*/
function _addTokenToOwnerEnumeration(address to, uint256 tokenId) private {
uint256 length = ERC721.balanceOf(to);
_ownedTokens[to][length] = tokenId;
_ownedTokensIndex[tokenId] = length;
}
/**
* @dev Private function to add a token to this extension's token tracking data structures.
* @param tokenId uint256 ID of the token to be added to the tokens list
*/
function _addTokenToAllTokensEnumeration(uint256 tokenId) private {
_allTokensIndex[tokenId] = _allTokens.length;
_allTokens.push(tokenId);
}
/**
* @dev Private function to remove a token from this extension's ownership-tracking data structures. Note that
* while the token is not assigned a new owner, the `_ownedTokensIndex` mapping is _not_ updated: this allows for
* gas optimizations e.g. when performing a transfer operation (avoiding double writes).
* This has O(1) time complexity, but alters the order of the _ownedTokens array.
* @param from address representing the previous owner of the given token ID
* @param tokenId uint256 ID of the token to be removed from the tokens list of the given address
*/
function _removeTokenFromOwnerEnumeration(address from, uint256 tokenId) private {
// To prevent a gap in from's tokens array, we store the last token in the index of the token to delete, and
// then delete the last slot (swap and pop).
uint256 lastTokenIndex = ERC721.balanceOf(from) - 1;
uint256 tokenIndex = _ownedTokensIndex[tokenId];
// When the token to delete is the last token, the swap operation is unnecessary
if (tokenIndex != lastTokenIndex) {
uint256 lastTokenId = _ownedTokens[from][lastTokenIndex];
_ownedTokens[from][tokenIndex] = lastTokenId; // Move the last token to the slot of the to-delete token
_ownedTokensIndex[lastTokenId] = tokenIndex; // Update the moved token's index
}
// This also deletes the contents at the last position of the array
delete _ownedTokensIndex[tokenId];
delete _ownedTokens[from][lastTokenIndex];
}
/**
* @dev Private function to remove a token from this extension's token tracking data structures.
* This has O(1) time complexity, but alters the order of the _allTokens array.
* @param tokenId uint256 ID of the token to be removed from the tokens list
*/
function _removeTokenFromAllTokensEnumeration(uint256 tokenId) private {
// To prevent a gap in the tokens array, we store the last token in the index of the token to delete, and
// then delete the last slot (swap and pop).
uint256 lastTokenIndex = _allTokens.length - 1;
uint256 tokenIndex = _allTokensIndex[tokenId];
// When the token to delete is the last token, the swap operation is unnecessary. However, since this occurs so
// rarely (when the last minted token is burnt) that we still do the swap here to avoid the gas cost of adding
// an 'if' statement (like in _removeTokenFromOwnerEnumeration)
uint256 lastTokenId = _allTokens[lastTokenIndex];
_allTokens[tokenIndex] = lastTokenId; // Move the last token to the slot of the to-delete token
_allTokensIndex[lastTokenId] = tokenIndex; // Update the moved token's index
// This also deletes the contents at the last position of the array
delete _allTokensIndex[tokenId];
_allTokens.pop();
}
}// SPDX-License-Identifier: MIT
// OpenZeppelin Contracts (last updated v4.5.0) (token/ERC721/extensions/IERC721Enumerable.sol)
pragma solidity ^0.8.0;
import "../IERC721.sol";
/**
* @title ERC-721 Non-Fungible Token Standard, optional enumeration extension
* @dev See https://eips.ethereum.org/EIPS/eip-721
*/
interface IERC721Enumerable is IERC721 {
/**
* @dev Returns the total amount of tokens stored by the contract.
*/
function totalSupply() external view returns (uint256);
/**
* @dev Returns a token ID owned by `owner` at a given `index` of its token list.
* Use along with {balanceOf} to enumerate all of ``owner``'s tokens.
*/
function tokenOfOwnerByIndex(address owner, uint256 index) external view returns (uint256);
/**
* @dev Returns a token ID at a given `index` of all the tokens stored by the contract.
* Use along with {totalSupply} to enumerate all tokens.
*/
function tokenByIndex(uint256 index) external view returns (uint256);
}// SPDX-License-Identifier: MIT
// OpenZeppelin Contracts v4.4.1 (token/ERC721/extensions/IERC721Metadata.sol)
pragma solidity ^0.8.0;
import "../IERC721.sol";
/**
* @title ERC-721 Non-Fungible Token Standard, optional metadata extension
* @dev See https://eips.ethereum.org/EIPS/eip-721
*/
interface IERC721Metadata is IERC721 {
/**
* @dev Returns the token collection name.
*/
function name() external view returns (string memory);
/**
* @dev Returns the token collection symbol.
*/
function symbol() external view returns (string memory);
/**
* @dev Returns the Uniform Resource Identifier (URI) for `tokenId` token.
*/
function tokenURI(uint256 tokenId) external view returns (string memory);
}// SPDX-License-Identifier: MIT
// OpenZeppelin Contracts (last updated v4.9.0) (token/ERC721/IERC721.sol)
pragma solidity ^0.8.0;
import "../../utils/introspection/IERC165.sol";
/**
* @dev Required interface of an ERC721 compliant contract.
*/
interface IERC721 is IERC165 {
/**
* @dev Emitted when `tokenId` token is transferred from `from` to `to`.
*/
event Transfer(address indexed from, address indexed to, uint256 indexed tokenId);
/**
* @dev Emitted when `owner` enables `approved` to manage the `tokenId` token.
*/
event Approval(address indexed owner, address indexed approved, uint256 indexed tokenId);
/**
* @dev Emitted when `owner` enables or disables (`approved`) `operator` to manage all of its assets.
*/
event ApprovalForAll(address indexed owner, address indexed operator, bool approved);
/**
* @dev Returns the number of tokens in ``owner``'s account.
*/
function balanceOf(address owner) external view returns (uint256 balance);
/**
* @dev Returns the owner of the `tokenId` token.
*
* Requirements:
*
* - `tokenId` must exist.
*/
function ownerOf(uint256 tokenId) external view returns (address owner);
/**
* @dev Safely transfers `tokenId` token from `from` to `to`.
*
* Requirements:
*
* - `from` cannot be the zero address.
* - `to` cannot be the zero address.
* - `tokenId` token must exist and be owned by `from`.
* - If the caller is not `from`, it must be approved to move this token by either {approve} or {setApprovalForAll}.
* - If `to` refers to a smart contract, it must implement {IERC721Receiver-onERC721Received}, which is called upon a safe transfer.
*
* Emits a {Transfer} event.
*/
function safeTransferFrom(address from, address to, uint256 tokenId, bytes calldata data) external;
/**
* @dev Safely transfers `tokenId` token from `from` to `to`, checking first that contract recipients
* are aware of the ERC721 protocol to prevent tokens from being forever locked.
*
* Requirements:
*
* - `from` cannot be the zero address.
* - `to` cannot be the zero address.
* - `tokenId` token must exist and be owned by `from`.
* - If the caller is not `from`, it must have been allowed to move this token by either {approve} or {setApprovalForAll}.
* - If `to` refers to a smart contract, it must implement {IERC721Receiver-onERC721Received}, which is called upon a safe transfer.
*
* Emits a {Transfer} event.
*/
function safeTransferFrom(address from, address to, uint256 tokenId) external;
/**
* @dev Transfers `tokenId` token from `from` to `to`.
*
* WARNING: Note that the caller is responsible to confirm that the recipient is capable of receiving ERC721
* or else they may be permanently lost. Usage of {safeTransferFrom} prevents loss, though the caller must
* understand this adds an external call which potentially creates a reentrancy vulnerability.
*
* Requirements:
*
* - `from` cannot be the zero address.
* - `to` cannot be the zero address.
* - `tokenId` token must be owned by `from`.
* - If the caller is not `from`, it must be approved to move this token by either {approve} or {setApprovalForAll}.
*
* Emits a {Transfer} event.
*/
function transferFrom(address from, address to, uint256 tokenId) external;
/**
* @dev Gives permission to `to` to transfer `tokenId` token to another account.
* The approval is cleared when the token is transferred.
*
* Only a single account can be approved at a time, so approving the zero address clears previous approvals.
*
* Requirements:
*
* - The caller must own the token or be an approved operator.
* - `tokenId` must exist.
*
* Emits an {Approval} event.
*/
function approve(address to, uint256 tokenId) external;
/**
* @dev Approve or remove `operator` as an operator for the caller.
* Operators can call {transferFrom} or {safeTransferFrom} for any token owned by the caller.
*
* Requirements:
*
* - The `operator` cannot be the caller.
*
* Emits an {ApprovalForAll} event.
*/
function setApprovalForAll(address operator, bool approved) external;
/**
* @dev Returns the account approved for `tokenId` token.
*
* Requirements:
*
* - `tokenId` must exist.
*/
function getApproved(uint256 tokenId) external view returns (address operator);
/**
* @dev Returns if the `operator` is allowed to manage all of the assets of `owner`.
*
* See {setApprovalForAll}
*/
function isApprovedForAll(address owner, address operator) external view returns (bool);
}// 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 (last updated v4.9.0) (token/ERC721/utils/ERC721Holder.sol)
pragma solidity ^0.8.0;
import "../IERC721Receiver.sol";
/**
* @dev Implementation of the {IERC721Receiver} interface.
*
* Accepts all token transfers.
* Make sure the contract is able to use its token with {IERC721-safeTransferFrom}, {IERC721-approve} or {IERC721-setApprovalForAll}.
*/
contract ERC721Holder is IERC721Receiver {
/**
* @dev See {IERC721Receiver-onERC721Received}.
*
* Always returns `IERC721Receiver.onERC721Received.selector`.
*/
function onERC721Received(address, address, uint256, bytes memory) public virtual override returns (bytes4) {
return this.onERC721Received.selector;
}
}// SPDX-License-Identifier: MIT
// OpenZeppelin Contracts (last updated v4.9.0) (utils/Address.sol)
pragma solidity ^0.8.1;
/**
* @dev Collection of functions related to the address type
*/
library Address {
/**
* @dev Returns true if `account` is a contract.
*
* [IMPORTANT]
* ====
* It is unsafe to assume that an address for which this function returns
* false is an externally-owned account (EOA) and not a contract.
*
* Among others, `isContract` will return false for the following
* types of addresses:
*
* - an externally-owned account
* - a contract in construction
* - an address where a contract will be created
* - an address where a contract lived, but was destroyed
*
* Furthermore, `isContract` will also return true if the target contract within
* the same transaction is already scheduled for destruction by `SELFDESTRUCT`,
* which only has an effect at the end of a transaction.
* ====
*
* [IMPORTANT]
* ====
* You shouldn't rely on `isContract` to protect against flash loan attacks!
*
* Preventing calls from contracts is highly discouraged. It breaks composability, breaks support for smart wallets
* like Gnosis Safe, and does not provide security since it can be circumvented by calling from a contract
* constructor.
* ====
*/
function isContract(address account) internal view returns (bool) {
// This method relies on extcodesize/address.code.length, which returns 0
// for contracts in construction, since the code is only stored at the end
// of the constructor execution.
return account.code.length > 0;
}
/**
* @dev Replacement for Solidity's `transfer`: sends `amount` wei to
* `recipient`, forwarding all available gas and reverting on errors.
*
* https://eips.ethereum.org/EIPS/eip-1884[EIP1884] increases the gas cost
* of certain opcodes, possibly making contracts go over the 2300 gas limit
* imposed by `transfer`, making them unable to receive funds via
* `transfer`. {sendValue} removes this limitation.
*
* https://consensys.net/diligence/blog/2019/09/stop-using-soliditys-transfer-now/[Learn more].
*
* IMPORTANT: because control is transferred to `recipient`, care must be
* taken to not create reentrancy vulnerabilities. Consider using
* {ReentrancyGuard} or the
* https://solidity.readthedocs.io/en/v0.8.0/security-considerations.html#use-the-checks-effects-interactions-pattern[checks-effects-interactions pattern].
*/
function sendValue(address payable recipient, uint256 amount) internal {
require(address(this).balance >= amount, "Address: insufficient balance");
(bool success, ) = recipient.call{value: amount}("");
require(success, "Address: unable to send value, recipient may have reverted");
}
/**
* @dev Performs a Solidity function call using a low level `call`. A
* plain `call` is an unsafe replacement for a function call: use this
* function instead.
*
* If `target` reverts with a revert reason, it is bubbled up by this
* function (like regular Solidity function calls).
*
* Returns the raw returned data. To convert to the expected return value,
* use https://solidity.readthedocs.io/en/latest/units-and-global-variables.html?highlight=abi.decode#abi-encoding-and-decoding-functions[`abi.decode`].
*
* Requirements:
*
* - `target` must be a contract.
* - calling `target` with `data` must not revert.
*
* _Available since v3.1._
*/
function functionCall(address target, bytes memory data) internal returns (bytes memory) {
return functionCallWithValue(target, data, 0, "Address: low-level call failed");
}
/**
* @dev Same as {xref-Address-functionCall-address-bytes-}[`functionCall`], but with
* `errorMessage` as a fallback revert reason when `target` reverts.
*
* _Available since v3.1._
*/
function functionCall(
address target,
bytes memory data,
string memory errorMessage
) internal returns (bytes memory) {
return functionCallWithValue(target, data, 0, errorMessage);
}
/**
* @dev Same as {xref-Address-functionCall-address-bytes-}[`functionCall`],
* but also transferring `value` wei to `target`.
*
* Requirements:
*
* - the calling contract must have an ETH balance of at least `value`.
* - the called Solidity function must be `payable`.
*
* _Available since v3.1._
*/
function functionCallWithValue(address target, bytes memory data, uint256 value) internal returns (bytes memory) {
return functionCallWithValue(target, data, value, "Address: low-level call with value failed");
}
/**
* @dev Same as {xref-Address-functionCallWithValue-address-bytes-uint256-}[`functionCallWithValue`], but
* with `errorMessage` as a fallback revert reason when `target` reverts.
*
* _Available since v3.1._
*/
function functionCallWithValue(
address target,
bytes memory data,
uint256 value,
string memory errorMessage
) internal returns (bytes memory) {
require(address(this).balance >= value, "Address: insufficient balance for call");
(bool success, bytes memory returndata) = target.call{value: value}(data);
return verifyCallResultFromTarget(target, success, returndata, errorMessage);
}
/**
* @dev Same as {xref-Address-functionCall-address-bytes-}[`functionCall`],
* but performing a static call.
*
* _Available since v3.3._
*/
function functionStaticCall(address target, bytes memory data) internal view returns (bytes memory) {
return functionStaticCall(target, data, "Address: low-level static call failed");
}
/**
* @dev Same as {xref-Address-functionCall-address-bytes-string-}[`functionCall`],
* but performing a static call.
*
* _Available since v3.3._
*/
function functionStaticCall(
address target,
bytes memory data,
string memory errorMessage
) internal view returns (bytes memory) {
(bool success, bytes memory returndata) = target.staticcall(data);
return verifyCallResultFromTarget(target, success, returndata, errorMessage);
}
/**
* @dev Same as {xref-Address-functionCall-address-bytes-}[`functionCall`],
* but performing a delegate call.
*
* _Available since v3.4._
*/
function functionDelegateCall(address target, bytes memory data) internal returns (bytes memory) {
return functionDelegateCall(target, data, "Address: low-level delegate call failed");
}
/**
* @dev Same as {xref-Address-functionCall-address-bytes-string-}[`functionCall`],
* but performing a delegate call.
*
* _Available since v3.4._
*/
function functionDelegateCall(
address target,
bytes memory data,
string memory errorMessage
) internal returns (bytes memory) {
(bool success, bytes memory returndata) = target.delegatecall(data);
return verifyCallResultFromTarget(target, success, returndata, errorMessage);
}
/**
* @dev Tool to verify that a low level call to smart-contract was successful, and revert (either by bubbling
* the revert reason or using the provided one) in case of unsuccessful call or if target was not a contract.
*
* _Available since v4.8._
*/
function verifyCallResultFromTarget(
address target,
bool success,
bytes memory returndata,
string memory errorMessage
) internal view returns (bytes memory) {
if (success) {
if (returndata.length == 0) {
// only check isContract if the call was successful and the return data is empty
// otherwise we already know that it was a contract
require(isContract(target), "Address: call to non-contract");
}
return returndata;
} else {
_revert(returndata, errorMessage);
}
}
/**
* @dev Tool to verify that a low level call was successful, and revert if it wasn't, either by bubbling the
* revert reason or using the provided one.
*
* _Available since v4.3._
*/
function verifyCallResult(
bool success,
bytes memory returndata,
string memory errorMessage
) internal pure returns (bytes memory) {
if (success) {
return returndata;
} else {
_revert(returndata, errorMessage);
}
}
function _revert(bytes memory returndata, string memory errorMessage) private pure {
// Look for revert reason and bubble it up if present
if (returndata.length > 0) {
// The easiest way to bubble the revert reason is using memory via assembly
/// @solidity memory-safe-assembly
assembly {
let returndata_size := mload(returndata)
revert(add(32, returndata), returndata_size)
}
} else {
revert(errorMessage);
}
}
}// SPDX-License-Identifier: MIT
// OpenZeppelin Contracts v4.4.1 (utils/Context.sol)
pragma solidity ^0.8.0;
/**
* @dev Provides information about the current execution context, including the
* sender of the transaction and its data. While these are generally available
* via msg.sender and msg.data, they should not be accessed in such a direct
* manner, since when dealing with meta-transactions the account sending and
* paying for execution may not be the actual sender (as far as an application
* is concerned).
*
* This contract is only required for intermediate, library-like contracts.
*/
abstract contract Context {
function _msgSender() internal view virtual returns (address) {
return msg.sender;
}
function _msgData() internal view virtual returns (bytes calldata) {
return msg.data;
}
}// SPDX-License-Identifier: MIT
// OpenZeppelin Contracts v4.4.1 (utils/introspection/ERC165.sol)
pragma solidity ^0.8.0;
import "./IERC165.sol";
/**
* @dev Implementation of the {IERC165} interface.
*
* Contracts that want to implement ERC165 should inherit from this contract and override {supportsInterface} to check
* for the additional interface id that will be supported. For example:
*
* ```solidity
* function supportsInterface(bytes4 interfaceId) public view virtual override returns (bool) {
* return interfaceId == type(MyInterface).interfaceId || super.supportsInterface(interfaceId);
* }
* ```
*
* Alternatively, {ERC165Storage} provides an easier to use but more expensive implementation.
*/
abstract contract ERC165 is IERC165 {
/**
* @dev See {IERC165-supportsInterface}.
*/
function supportsInterface(bytes4 interfaceId) public view virtual override returns (bool) {
return interfaceId == type(IERC165).interfaceId;
}
}// 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
// OpenZeppelin Contracts (last updated v4.9.0) (utils/math/Math.sol)
pragma solidity ^0.8.0;
/**
* @dev Standard math utilities missing in the Solidity language.
*/
library Math {
enum Rounding {
Down, // Toward negative infinity
Up, // Toward infinity
Zero // Toward zero
}
/**
* @dev Returns the largest of two numbers.
*/
function max(uint256 a, uint256 b) internal pure returns (uint256) {
return a > b ? a : b;
}
/**
* @dev Returns the smallest of two numbers.
*/
function min(uint256 a, uint256 b) internal pure returns (uint256) {
return a < b ? a : b;
}
/**
* @dev Returns the average of two numbers. The result is rounded towards
* zero.
*/
function average(uint256 a, uint256 b) internal pure returns (uint256) {
// (a + b) / 2 can overflow.
return (a & b) + (a ^ b) / 2;
}
/**
* @dev Returns the ceiling of the division of two numbers.
*
* This differs from standard division with `/` in that it rounds up instead
* of rounding down.
*/
function ceilDiv(uint256 a, uint256 b) internal pure returns (uint256) {
// (a + b - 1) / b can overflow on addition, so we distribute.
return a == 0 ? 0 : (a - 1) / b + 1;
}
/**
* @notice Calculates floor(x * y / denominator) with full precision. Throws if result overflows a uint256 or denominator == 0
* @dev Original credit to Remco Bloemen under MIT license (https://xn--2-umb.com/21/muldiv)
* with further edits by Uniswap Labs also under MIT license.
*/
function mulDiv(uint256 x, uint256 y, uint256 denominator) internal pure returns (uint256 result) {
unchecked {
// 512-bit multiply [prod1 prod0] = x * y. Compute the product mod 2^256 and mod 2^256 - 1, then use
// use the Chinese Remainder Theorem to reconstruct the 512 bit result. The result is stored in two 256
// variables such that product = prod1 * 2^256 + prod0.
uint256 prod0; // Least significant 256 bits of the product
uint256 prod1; // Most significant 256 bits of the product
assembly {
let mm := mulmod(x, y, not(0))
prod0 := mul(x, y)
prod1 := sub(sub(mm, prod0), lt(mm, prod0))
}
// Handle non-overflow cases, 256 by 256 division.
if (prod1 == 0) {
// Solidity will revert if denominator == 0, unlike the div opcode on its own.
// The surrounding unchecked block does not change this fact.
// See https://docs.soliditylang.org/en/latest/control-structures.html#checked-or-unchecked-arithmetic.
return prod0 / denominator;
}
// Make sure the result is less than 2^256. Also prevents denominator == 0.
require(denominator > prod1, "Math: mulDiv overflow");
///////////////////////////////////////////////
// 512 by 256 division.
///////////////////////////////////////////////
// Make division exact by subtracting the remainder from [prod1 prod0].
uint256 remainder;
assembly {
// Compute remainder using mulmod.
remainder := mulmod(x, y, denominator)
// Subtract 256 bit number from 512 bit number.
prod1 := sub(prod1, gt(remainder, prod0))
prod0 := sub(prod0, remainder)
}
// Factor powers of two out of denominator and compute largest power of two divisor of denominator. Always >= 1.
// See https://cs.stackexchange.com/q/138556/92363.
// Does not overflow because the denominator cannot be zero at this stage in the function.
uint256 twos = denominator & (~denominator + 1);
assembly {
// Divide denominator by twos.
denominator := div(denominator, twos)
// Divide [prod1 prod0] by twos.
prod0 := div(prod0, twos)
// Flip twos such that it is 2^256 / twos. If twos is zero, then it becomes one.
twos := add(div(sub(0, twos), twos), 1)
}
// Shift in bits from prod1 into prod0.
prod0 |= prod1 * twos;
// Invert denominator mod 2^256. Now that denominator is an odd number, it has an inverse modulo 2^256 such
// that denominator * inv = 1 mod 2^256. Compute the inverse by starting with a seed that is correct for
// four bits. That is, denominator * inv = 1 mod 2^4.
uint256 inverse = (3 * denominator) ^ 2;
// Use the Newton-Raphson iteration to improve the precision. Thanks to Hensel's lifting lemma, this also works
// in modular arithmetic, doubling the correct bits in each step.
inverse *= 2 - denominator * inverse; // inverse mod 2^8
inverse *= 2 - denominator * inverse; // inverse mod 2^16
inverse *= 2 - denominator * inverse; // inverse mod 2^32
inverse *= 2 - denominator * inverse; // inverse mod 2^64
inverse *= 2 - denominator * inverse; // inverse mod 2^128
inverse *= 2 - denominator * inverse; // inverse mod 2^256
// Because the division is now exact we can divide by multiplying with the modular inverse of denominator.
// This will give us the correct result modulo 2^256. Since the preconditions guarantee that the outcome is
// less than 2^256, this is the final result. We don't need to compute the high bits of the result and prod1
// is no longer required.
result = prod0 * inverse;
return result;
}
}
/**
* @notice Calculates x * y / denominator with full precision, following the selected rounding direction.
*/
function mulDiv(uint256 x, uint256 y, uint256 denominator, Rounding rounding) internal pure returns (uint256) {
uint256 result = mulDiv(x, y, denominator);
if (rounding == Rounding.Up && mulmod(x, y, denominator) > 0) {
result += 1;
}
return result;
}
/**
* @dev Returns the square root of a number. If the number is not a perfect square, the value is rounded down.
*
* Inspired by Henry S. Warren, Jr.'s "Hacker's Delight" (Chapter 11).
*/
function sqrt(uint256 a) internal pure returns (uint256) {
if (a == 0) {
return 0;
}
// For our first guess, we get the biggest power of 2 which is smaller than the square root of the target.
//
// We know that the "msb" (most significant bit) of our target number `a` is a power of 2 such that we have
// `msb(a) <= a < 2*msb(a)`. This value can be written `msb(a)=2**k` with `k=log2(a)`.
//
// This can be rewritten `2**log2(a) <= a < 2**(log2(a) + 1)`
// → `sqrt(2**k) <= sqrt(a) < sqrt(2**(k+1))`
// → `2**(k/2) <= sqrt(a) < 2**((k+1)/2) <= 2**(k/2 + 1)`
//
// Consequently, `2**(log2(a) / 2)` is a good first approximation of `sqrt(a)` with at least 1 correct bit.
uint256 result = 1 << (log2(a) >> 1);
// At this point `result` is an estimation with one bit of precision. We know the true value is a uint128,
// since it is the square root of a uint256. Newton's method converges quadratically (precision doubles at
// every iteration). We thus need at most 7 iteration to turn our partial result with one bit of precision
// into the expected uint128 result.
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);
}
}
/**
* @notice Calculates sqrt(a), following the selected rounding direction.
*/
function sqrt(uint256 a, Rounding rounding) internal pure returns (uint256) {
unchecked {
uint256 result = sqrt(a);
return result + (rounding == Rounding.Up && result * result < a ? 1 : 0);
}
}
/**
* @dev Return the log in base 2, rounded down, of a positive value.
* Returns 0 if given 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;
}
/**
* @dev Return the log in base 2, following the selected rounding direction, of a positive value.
* Returns 0 if given 0.
*/
function log2(uint256 value, Rounding rounding) internal pure returns (uint256) {
unchecked {
uint256 result = log2(value);
return result + (rounding == Rounding.Up && 1 << result < value ? 1 : 0);
}
}
/**
* @dev Return the log in base 10, rounded down, of a positive value.
* Returns 0 if given 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;
}
/**
* @dev Return the log in base 10, following the selected rounding direction, of a positive value.
* Returns 0 if given 0.
*/
function log10(uint256 value, Rounding rounding) internal pure returns (uint256) {
unchecked {
uint256 result = log10(value);
return result + (rounding == Rounding.Up && 10 ** result < value ? 1 : 0);
}
}
/**
* @dev Return the log in base 256, rounded down, of a positive value.
* Returns 0 if given 0.
*
* Adding one to the result gives the number of pairs of hex symbols needed to represent `value` as a hex string.
*/
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;
}
/**
* @dev Return the log in base 256, following the selected rounding direction, of a positive value.
* Returns 0 if given 0.
*/
function log256(uint256 value, Rounding rounding) internal pure returns (uint256) {
unchecked {
uint256 result = log256(value);
return result + (rounding == Rounding.Up && 1 << (result << 3) < value ? 1 : 0);
}
}
}// SPDX-License-Identifier: MIT
// OpenZeppelin Contracts (last updated v4.9.0) (utils/math/SafeMath.sol)
pragma solidity ^0.8.0;
// CAUTION
// This version of SafeMath should only be used with Solidity 0.8 or later,
// because it relies on the compiler's built in overflow checks.
/**
* @dev Wrappers over Solidity's arithmetic operations.
*
* NOTE: `SafeMath` is generally not needed starting with Solidity 0.8, since the compiler
* now has built in overflow checking.
*/
library SafeMath {
/**
* @dev Returns the addition of two unsigned integers, with an overflow flag.
*
* _Available since v3.4._
*/
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);
}
}
/**
* @dev Returns the subtraction of two unsigned integers, with an overflow flag.
*
* _Available since v3.4._
*/
function trySub(uint256 a, uint256 b) internal pure returns (bool, uint256) {
unchecked {
if (b > a) return (false, 0);
return (true, a - b);
}
}
/**
* @dev Returns the multiplication of two unsigned integers, with an overflow flag.
*
* _Available since v3.4._
*/
function tryMul(uint256 a, uint256 b) internal pure returns (bool, uint256) {
unchecked {
// Gas optimization: this is cheaper than requiring 'a' not being zero, but the
// benefit is lost if 'b' is also tested.
// See: https://github.com/OpenZeppelin/openzeppelin-contracts/pull/522
if (a == 0) return (true, 0);
uint256 c = a * b;
if (c / a != b) return (false, 0);
return (true, c);
}
}
/**
* @dev Returns the division of two unsigned integers, with a division by zero flag.
*
* _Available since v3.4._
*/
function tryDiv(uint256 a, uint256 b) internal pure returns (bool, uint256) {
unchecked {
if (b == 0) return (false, 0);
return (true, a / b);
}
}
/**
* @dev Returns the remainder of dividing two unsigned integers, with a division by zero flag.
*
* _Available since v3.4._
*/
function tryMod(uint256 a, uint256 b) internal pure returns (bool, uint256) {
unchecked {
if (b == 0) return (false, 0);
return (true, a % b);
}
}
/**
* @dev Returns the addition of two unsigned integers, reverting on
* overflow.
*
* Counterpart to Solidity's `+` operator.
*
* Requirements:
*
* - Addition cannot overflow.
*/
function add(uint256 a, uint256 b) internal pure returns (uint256) {
return a + b;
}
/**
* @dev Returns the subtraction of two unsigned integers, reverting on
* overflow (when the result is negative).
*
* Counterpart to Solidity's `-` operator.
*
* Requirements:
*
* - Subtraction cannot overflow.
*/
function sub(uint256 a, uint256 b) internal pure returns (uint256) {
return a - b;
}
/**
* @dev Returns the multiplication of two unsigned integers, reverting on
* overflow.
*
* Counterpart to Solidity's `*` operator.
*
* Requirements:
*
* - Multiplication cannot overflow.
*/
function mul(uint256 a, uint256 b) internal pure returns (uint256) {
return a * b;
}
/**
* @dev Returns the integer division of two unsigned integers, reverting on
* division by zero. The result is rounded towards zero.
*
* Counterpart to Solidity's `/` operator.
*
* Requirements:
*
* - The divisor cannot be zero.
*/
function div(uint256 a, uint256 b) internal pure returns (uint256) {
return a / b;
}
/**
* @dev Returns the remainder of dividing two unsigned integers. (unsigned integer modulo),
* reverting when dividing by zero.
*
* Counterpart to Solidity's `%` operator. This function uses a `revert`
* opcode (which leaves remaining gas untouched) while Solidity uses an
* invalid opcode to revert (consuming all remaining gas).
*
* Requirements:
*
* - The divisor cannot be zero.
*/
function mod(uint256 a, uint256 b) internal pure returns (uint256) {
return a % b;
}
/**
* @dev Returns the subtraction of two unsigned integers, reverting with custom message on
* overflow (when the result is negative).
*
* CAUTION: This function is deprecated because it requires allocating memory for the error
* message unnecessarily. For custom revert reasons use {trySub}.
*
* Counterpart to Solidity's `-` operator.
*
* Requirements:
*
* - Subtraction cannot overflow.
*/
function sub(uint256 a, uint256 b, string memory errorMessage) internal pure returns (uint256) {
unchecked {
require(b <= a, errorMessage);
return a - b;
}
}
/**
* @dev Returns the integer division of two unsigned integers, reverting with custom message on
* division by zero. The result is rounded towards zero.
*
* Counterpart to Solidity's `/` operator. Note: this function uses a
* `revert` opcode (which leaves remaining gas untouched) while Solidity
* uses an invalid opcode to revert (consuming all remaining gas).
*
* Requirements:
*
* - The divisor cannot be zero.
*/
function div(uint256 a, uint256 b, string memory errorMessage) internal pure returns (uint256) {
unchecked {
require(b > 0, errorMessage);
return a / b;
}
}
/**
* @dev Returns the remainder of dividing two unsigned integers. (unsigned integer modulo),
* reverting with custom message when dividing by zero.
*
* CAUTION: This function is deprecated because it requires allocating memory for the error
* message unnecessarily. For custom revert reasons use {tryMod}.
*
* Counterpart to Solidity's `%` operator. This function uses a `revert`
* opcode (which leaves remaining gas untouched) while Solidity uses an
* invalid opcode to revert (consuming all remaining gas).
*
* Requirements:
*
* - The divisor cannot be zero.
*/
function mod(uint256 a, uint256 b, string memory errorMessage) internal pure returns (uint256) {
unchecked {
require(b > 0, errorMessage);
return a % b;
}
}
}// SPDX-License-Identifier: MIT
// OpenZeppelin Contracts (last updated v4.8.0) (utils/math/SignedMath.sol)
pragma solidity ^0.8.0;
/**
* @dev Standard signed math utilities missing in the Solidity language.
*/
library SignedMath {
/**
* @dev Returns the largest of two signed numbers.
*/
function max(int256 a, int256 b) internal pure returns (int256) {
return a > b ? a : b;
}
/**
* @dev Returns the smallest of two signed numbers.
*/
function min(int256 a, int256 b) internal pure returns (int256) {
return a < b ? a : b;
}
/**
* @dev Returns the average of two signed numbers without overflow.
* The result is rounded towards zero.
*/
function average(int256 a, int256 b) internal pure returns (int256) {
// Formula from the book "Hacker's Delight"
int256 x = (a & b) + ((a ^ b) >> 1);
return x + (int256(uint256(x) >> 255) & (a ^ b));
}
/**
* @dev Returns the absolute unsigned value of a signed value.
*/
function abs(int256 n) internal pure returns (uint256) {
unchecked {
// must be unchecked in order to support `n = type(int256).min`
return uint256(n >= 0 ? n : -n);
}
}
}// SPDX-License-Identifier: MIT
// OpenZeppelin Contracts (last updated v4.9.0) (utils/StorageSlot.sol)
// This file was procedurally generated from scripts/generate/templates/StorageSlot.js.
pragma solidity ^0.8.0;
/**
* @dev Library for reading and writing primitive types to specific storage slots.
*
* Storage slots are often used to avoid storage conflict when dealing with upgradeable contracts.
* This library helps with reading and writing to such slots without the need for inline assembly.
*
* The functions in this library return Slot structs that contain a `value` member that can be used to read or write.
*
* Example usage to set ERC1967 implementation slot:
* ```solidity
* contract ERC1967 {
* bytes32 internal constant _IMPLEMENTATION_SLOT = 0x360894a13ba1a3210667c828492db98dca3e2076cc3735a920a3ca505d382bbc;
*
* function _getImplementation() internal view returns (address) {
* return StorageSlot.getAddressSlot(_IMPLEMENTATION_SLOT).value;
* }
*
* function _setImplementation(address newImplementation) internal {
* require(Address.isContract(newImplementation), "ERC1967: new implementation is not a contract");
* StorageSlot.getAddressSlot(_IMPLEMENTATION_SLOT).value = newImplementation;
* }
* }
* ```
*
* _Available since v4.1 for `address`, `bool`, `bytes32`, `uint256`._
* _Available since v4.9 for `string`, `bytes`._
*/
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;
}
/**
* @dev Returns an `AddressSlot` with member `value` located at `slot`.
*/
function getAddressSlot(bytes32 slot) internal pure returns (AddressSlot storage r) {
/// @solidity memory-safe-assembly
assembly {
r.slot := slot
}
}
/**
* @dev Returns an `BooleanSlot` with member `value` located at `slot`.
*/
function getBooleanSlot(bytes32 slot) internal pure returns (BooleanSlot storage r) {
/// @solidity memory-safe-assembly
assembly {
r.slot := slot
}
}
/**
* @dev Returns an `Bytes32Slot` with member `value` located at `slot`.
*/
function getBytes32Slot(bytes32 slot) internal pure returns (Bytes32Slot storage r) {
/// @solidity memory-safe-assembly
assembly {
r.slot := slot
}
}
/**
* @dev Returns an `Uint256Slot` with member `value` located at `slot`.
*/
function getUint256Slot(bytes32 slot) internal pure returns (Uint256Slot storage r) {
/// @solidity memory-safe-assembly
assembly {
r.slot := slot
}
}
/**
* @dev Returns an `StringSlot` with member `value` located at `slot`.
*/
function getStringSlot(bytes32 slot) internal pure returns (StringSlot storage r) {
/// @solidity memory-safe-assembly
assembly {
r.slot := slot
}
}
/**
* @dev Returns an `StringSlot` representation of the string storage pointer `store`.
*/
function getStringSlot(string storage store) internal pure returns (StringSlot storage r) {
/// @solidity memory-safe-assembly
assembly {
r.slot := store.slot
}
}
/**
* @dev Returns an `BytesSlot` with member `value` located at `slot`.
*/
function getBytesSlot(bytes32 slot) internal pure returns (BytesSlot storage r) {
/// @solidity memory-safe-assembly
assembly {
r.slot := slot
}
}
/**
* @dev Returns an `BytesSlot` representation of the bytes storage pointer `store`.
*/
function getBytesSlot(bytes storage store) internal pure returns (BytesSlot storage r) {
/// @solidity memory-safe-assembly
assembly {
r.slot := store.slot
}
}
}// SPDX-License-Identifier: MIT
// OpenZeppelin Contracts (last updated v4.9.0) (utils/Strings.sol)
pragma solidity ^0.8.0;
import "./math/Math.sol";
import "./math/SignedMath.sol";
/**
* @dev String operations.
*/
library Strings {
bytes16 private constant _SYMBOLS = "0123456789abcdef";
uint8 private constant _ADDRESS_LENGTH = 20;
/**
* @dev Converts a `uint256` to its ASCII `string` decimal representation.
*/
function toString(uint256 value) internal pure returns (string memory) {
unchecked {
uint256 length = Math.log10(value) + 1;
string memory buffer = new string(length);
uint256 ptr;
/// @solidity memory-safe-assembly
assembly {
ptr := add(buffer, add(32, length))
}
while (true) {
ptr--;
/// @solidity memory-safe-assembly
assembly {
mstore8(ptr, byte(mod(value, 10), _SYMBOLS))
}
value /= 10;
if (value == 0) break;
}
return buffer;
}
}
/**
* @dev Converts a `int256` to its ASCII `string` decimal representation.
*/
function toString(int256 value) internal pure returns (string memory) {
return string(abi.encodePacked(value < 0 ? "-" : "", toString(SignedMath.abs(value))));
}
/**
* @dev Converts a `uint256` to its ASCII `string` hexadecimal representation.
*/
function toHexString(uint256 value) internal pure returns (string memory) {
unchecked {
return toHexString(value, Math.log256(value) + 1);
}
}
/**
* @dev Converts a `uint256` to its ASCII `string` hexadecimal representation with fixed length.
*/
function toHexString(uint256 value, uint256 length) internal pure returns (string memory) {
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] = _SYMBOLS[value & 0xf];
value >>= 4;
}
require(value == 0, "Strings: hex length insufficient");
return string(buffer);
}
/**
* @dev Converts an `address` with fixed length of 20 bytes to its not checksummed ASCII `string` hexadecimal representation.
*/
function toHexString(address addr) internal pure returns (string memory) {
return toHexString(uint256(uint160(addr)), _ADDRESS_LENGTH);
}
/**
* @dev Returns true if the two strings are equal.
*/
function equal(string memory a, string memory b) internal pure returns (bool) {
return keccak256(bytes(a)) == keccak256(bytes(b));
}
}// SPDX-License-Identifier: MIT
pragma solidity 0.8.18;
import "@openzeppelin/contracts/token/ERC20/utils/SafeERC20.sol";
import "@openzeppelin/contracts/access/Ownable.sol";
import "@openzeppelin/contracts/security/ReentrancyGuard.sol";
import "@openzeppelin/contracts/utils/Address.sol";
struct GetFeesParam {
uint256 srcChainID;
uint256 destChainID;
}
struct GetFeesReturn {
uint256 contractFee;
uint256 agentFee;
}
interface IFeeReadSC {
function getFee(GetFeesParam memory param) external view returns(GetFeesReturn memory fee);
}
interface ICircleTokenMessenger {
function depositForBurn(
uint256 amount,
uint32 destinationDomain,
bytes32 mintRecipient,
address burnToken) external;
}
interface ICircleMessageTransmitter {
function receiveMessage(
bytes calldata message,
bytes calldata attestation) external;
}
contract Fee is Ownable, ReentrancyGuard {
using SafeERC20 for IERC20;
address public feeToAddress;
address public feeReadSC;
address public circleTokenMessengerSC;
address public circleMessageTransmitterSC;
uint256 public localChainId;
mapping(uint256 => uint256) public circlePathToBip44;
event DepositForBurnWithFee(
uint256 amount,
uint32 destinationDomain,
bytes32 mintRecipient,
address burnToken,
uint256 fee
);
event UpdateFeeTo(address to);
event MintToken(bytes32 mintRecipient, address token);
constructor(address _feeToAddress, address _feeReadSC, uint256 _localChainId, address _circleTokenMessengerSC, address _circleMessageTransmitterSC) {
feeToAddress = _feeToAddress;
feeReadSC = _feeReadSC;
localChainId = _localChainId;
circleTokenMessengerSC = _circleTokenMessengerSC;
circleMessageTransmitterSC = _circleMessageTransmitterSC;
}
function setFeeToAddress(address _feeToAddress) external onlyOwner {
feeToAddress = _feeToAddress;
emit UpdateFeeTo(_feeToAddress);
}
function setCirclePathToBip44(uint256 _circlePath, uint256 _bip44) external onlyOwner {
circlePathToBip44[_circlePath] = _bip44;
}
function setFeeReadSC(address _feeReadSC) external onlyOwner {
feeReadSC = _feeReadSC;
}
function estimateFee(uint32 destinationDomain) public view returns(uint256) {
require(circlePathToBip44[destinationDomain] != 0, "Fee: Invalid destination domain");
GetFeesReturn memory fee = IFeeReadSC(feeReadSC).getFee(GetFeesParam({
srcChainID: localChainId,
destChainID: circlePathToBip44[destinationDomain]
}));
return fee.contractFee;
}
function depositForBurn(
uint256 amount,
uint32 destinationDomain,
bytes32 mintRecipient,
address burnToken) external payable nonReentrant {
uint256 fee = estimateFee(destinationDomain);
require(msg.value >= fee, "Fee: Insufficient fee");
if (msg.value > fee) {
Address.sendValue(payable(msg.sender), msg.value - fee);
}
Address.sendValue(payable(feeToAddress), fee);
IERC20(burnToken).safeTransferFrom(msg.sender, address(this), amount);
IERC20(burnToken).safeApprove(circleTokenMessengerSC, amount);
ICircleTokenMessenger(circleTokenMessengerSC).depositForBurn(amount, destinationDomain, mintRecipient, burnToken);
emit DepositForBurnWithFee(amount, destinationDomain, mintRecipient, burnToken, fee);
}
function receiveMessage(bytes calldata message, bytes calldata attestation, bytes32 mintRecipient, address token) external nonReentrant {
ICircleMessageTransmitter(circleMessageTransmitterSC).receiveMessage(message, attestation);
emit MintToken(mintRecipient, token);
}
}// SPDX-License-Identifier: MIT
pragma solidity 0.8.18;
import "@openzeppelin/contracts/proxy/transparent/TransparentUpgradeableProxy.sol";
contract CommonProxy is ITransparentUpgradeableProxy, TransparentUpgradeableProxy {
constructor(address _logic, address admin_, bytes memory _data)
payable
TransparentUpgradeableProxy(_logic, admin_, _data) {}
/**
* @dev Changes the admin of the proxy.
*
* Emits an {AdminChanged} event.
* @dev Only the admin can call this function; other callers are delegated
*/
function changeAdmin(address newAdmin) external virtual ifAdmin {
_changeAdmin(newAdmin);
}
/**
* @dev Upgrade the implementation of the proxy.
* @dev Only the admin can call this function; other callers are delegated
*/
function upgradeTo(address newImplementation) external virtual ifAdmin {
_upgradeTo(newImplementation);
}
/**
* @dev Upgrade the implementation of the proxy, and then call a function from the new implementation as specified
* by `data`, which should be an encoded function call. This is useful to initialize new storage variables in the
* proxied contract.
* @dev Only the admin can call this function; other callers are delegated
*/
function upgradeToAndCall(
address newImplementation,
bytes calldata data
) external payable virtual ifAdmin {
_upgradeToAndCall(newImplementation, data, false);
}
/**
* @dev Returns the current admin.
*/
function admin() external view returns (address admin_) {
admin_ = _admin();
}
/**
* @dev Returns the current implementation.
*/
function implementation() external view returns (address implementation_) {
implementation_ = _implementation();
}
}// SPDX-License-Identifier: MIT
pragma solidity 0.8.18;
import "@openzeppelin/contracts/token/ERC20/utils/SafeERC20.sol";
import "@openzeppelin/contracts/security/ReentrancyGuard.sol";
import "@openzeppelin/contracts/utils/Address.sol";
import "@openzeppelin/contracts/proxy/utils/Initializable.sol";
import "@openzeppelin/contracts/access/AccessControl.sol";
struct SetCirclePathToBip44Param {
uint256 bip44ChainID;
uint256 domain;
}
struct SetFeeParam {
uint256 bip44ChainID;
uint256 fee;
}
struct GetFeesParam {
uint256 srcChainID;
uint256 destChainID;
}
struct GetFeesReturn {
uint256 contractFee;
uint256 agentFee;
}
interface IFeeReadSC {
function getFee(GetFeesParam memory param) external view returns(GetFeesReturn memory fee);
}
interface ICircleTokenMessengerV2 {
function depositForBurn(
uint256 amount,
uint32 destinationDomain,
bytes32 mintRecipient,
address burnToken,
bytes32 destinationCaller,
uint256 maxFee,
uint32 minFinalityThreshold
) external;
}
interface ICircleMessageTransmitterV2 {
function receiveMessage(
bytes calldata message,
bytes calldata attestation
) external returns (bool success);
}
contract WanCctpV2 is ReentrancyGuard, Initializable, AccessControl {
using SafeERC20 for IERC20;
address public feeToAddress;
address public feeReadSC;
address public circleTokenMessengerSC;
address public circleMessageTransmitterSC;
uint256 public localChainId;
// domain => bip44ChainID
mapping(uint256 => uint256) public circlePathToBip44;
// destination chain bip44ChainID => fee
mapping(uint256 => uint256) public feeMap;
/**
* @notice Emitted when a DepositForBurn message is sent
* @param burnToken address of token burnt on source domain
* @param amount deposit amount
* @param mintRecipient address receiving minted tokens on destination domain as bytes32
* @param destinationDomain destination domain
* @param destinationCaller authorized caller as bytes32 of receiveMessage() on destination domain.
* If equal to bytes32(0), any address can broadcast the message.
* @param maxFee maximum fee to pay on destination domain, in units of burnToken
* @param fee fee for execution on destination domain
* @param minFinalityThreshold the minimum finality at which the message should be attested to.
* @param hookData optional hook for execution on destination domain
*/
event DepositForBurnWithFee(
address indexed burnToken,
uint256 amount,
bytes32 mintRecipient,
uint32 indexed destinationDomain,
bytes32 destinationCaller,
uint256 maxFee,
uint256 fee,
uint32 indexed minFinalityThreshold,
bytes hookData
);
event ReceiveMessage();
event UpdateFeeTo(address to);
event UpdateFeeReadSC(address feeReadSC);
event SetCirclePathToBip44(uint256 indexed bip44ChainID, uint256 indexed domain);
event SetFee(uint256 indexed bip44ChainID, uint256 indexed fee);
/** Modifier */
modifier onlyAdmin() {
require(hasRole(DEFAULT_ADMIN_ROLE, msg.sender), "WanCctpV2: not admin");
_;
}
function initialize(address _admin, address _feeToAddress, address _feeReadSC, uint256 _localChainId, address _circleTokenMessengerSC, address _circleMessageTransmitterSC)
external
initializer
{
_setupRole(DEFAULT_ADMIN_ROLE, _admin);
feeToAddress = _feeToAddress;
feeReadSC = _feeReadSC;
localChainId = _localChainId;
circleTokenMessengerSC = _circleTokenMessengerSC;
circleMessageTransmitterSC = _circleMessageTransmitterSC;
emit UpdateFeeTo(_feeToAddress);
emit UpdateFeeReadSC(_feeReadSC);
}
function setCirclePathToBip44(SetCirclePathToBip44Param[] calldata params) external onlyAdmin {
for (uint256 i = 0; i < params.length; i++) {
circlePathToBip44[params[i].domain] = params[i].bip44ChainID;
emit SetCirclePathToBip44(params[i].bip44ChainID, params[i].domain);
}
}
function setFees(SetFeeParam[] calldata params) external onlyAdmin {
for (uint256 i = 0; i < params.length; i++) {
feeMap[params[i].bip44ChainID] = params[i].fee;
emit SetFee(params[i].bip44ChainID, params[i].fee);
}
}
function getFees(uint256[] calldata bip44ChainIDs) public view returns(uint256[] memory fees) {
fees = new uint256[](bip44ChainIDs.length);
for (uint256 i = 0; i < bip44ChainIDs.length; i++) {
uint256 fee = feeMap[bip44ChainIDs[i]];
if (fee == 0 && feeReadSC != address(0)) {
GetFeesReturn memory crossChainFee = IFeeReadSC(feeReadSC).getFee(GetFeesParam({
srcChainID: localChainId,
destChainID: bip44ChainIDs[i]
}));
fee = crossChainFee.contractFee;
}
fees[i] = fee;
}
}
function estimateFee(uint32 destinationDomain) public view returns(uint256 fee) {
require(circlePathToBip44[destinationDomain] != 0, "WanCctpV2: Invalid destination domain");
uint256 bip44ChainID = circlePathToBip44[destinationDomain];
fee = feeMap[bip44ChainID];
if (fee == 0 && feeReadSC != address(0)) {
GetFeesReturn memory crossChainFee = IFeeReadSC(feeReadSC).getFee(GetFeesParam({
srcChainID: localChainId,
destChainID: circlePathToBip44[destinationDomain]
}));
fee = crossChainFee.contractFee;
}
return fee;
}
/**
* @notice Deposits and burns tokens from sender to be minted on destination domain.
* Emits a `DepositForBurn` event.
* @dev reverts if:
* - insufficient fee
* - given burnToken is not supported
* - given destinationDomain has no Bip44 chainID registered
* - given destinationDomain has no TokenMessenger registered
* - transferFrom() reverts. For example, if sender's burnToken balance or approved allowance
* to this contract is less than `amount`.
* - burn() reverts. For example, if `amount` is 0.
* - maxFee is greater than or equal to `amount`.
* - MessageTransmitterV2#sendMessage reverts.
* @param amount amount of tokens to burn
* @param destinationDomain destination domain to receive message on
* @param mintRecipient address of mint recipient on destination domain
* @param burnToken token to burn `amount` of, on local domain
* @param destinationCaller authorized caller on the destination domain, as bytes32. If equal to bytes32(0),
* any address can broadcast the message.
* @param maxFee maximum fee to pay on the destination domain, specified in units of burnToken
* @param minFinalityThreshold the minimum finality at which a burn message will be attested to.
*/
function depositForBurn(
uint256 amount,
uint32 destinationDomain,
bytes32 mintRecipient,
address burnToken,
bytes32 destinationCaller,
uint256 maxFee,
uint32 minFinalityThreshold
) external payable nonReentrant {
uint256 fee = estimateFee(destinationDomain);
require(msg.value == fee, "WanCctpV2: Mismatch fee");
if (fee > 0) {
Address.sendValue(payable(feeToAddress), fee);
}
IERC20(burnToken).safeTransferFrom(msg.sender, address(this), amount);
IERC20(burnToken).forceApprove(circleTokenMessengerSC, amount);
ICircleTokenMessengerV2(circleTokenMessengerSC).depositForBurn(amount, destinationDomain, mintRecipient, burnToken, destinationCaller, maxFee, minFinalityThreshold);
bytes memory _emptyHookData = msg.data[0:0];
emit DepositForBurnWithFee(burnToken, amount, mintRecipient, destinationDomain, destinationCaller, maxFee, fee, minFinalityThreshold, _emptyHookData);
}
/**
* @notice Receive a message. Messages can only be broadcast once for a given nonce.
* The message body of a valid message is passed to the specified recipient for further processing.
*
* @dev Attestation format:
* A valid attestation is the concatenated 65-byte signature(s) of exactly
* `thresholdSignature` signatures, in increasing order of attester address.
* ***If the attester addresses recovered from signatures are not in
* increasing order, signature verification will fail.***
* If incorrect number of signatures or duplicate signatures are supplied,
* signature verification will fail.
*
* Message Format:
*
* Field Bytes Type Index
* version 4 uint32 0
* sourceDomain 4 uint32 4
* destinationDomain 4 uint32 8
* nonce 32 bytes32 12
* sender 32 bytes32 44
* recipient 32 bytes32 76
* destinationCaller 32 bytes32 108
* minFinalityThreshold 4 uint32 140
* finalityThresholdExecuted 4 uint32 144
* messageBody dynamic bytes 148
* @param message Message bytes
* @param attestation Concatenated 65-byte signature(s) of `message`, in increasing order
* of the attester address recovered from signatures.
* @return success True, if successful; false, if not
*/
function receiveMessage(bytes calldata message, bytes calldata attestation) external nonReentrant returns (bool success) {
success = ICircleMessageTransmitterV2(circleMessageTransmitterSC).receiveMessage(message, attestation);
emit ReceiveMessage();
}
function setFeeToAddress(address _feeToAddress) external onlyAdmin {
feeToAddress = _feeToAddress;
emit UpdateFeeTo(_feeToAddress);
}
function setFeeReadSC(address _feeReadSC) external onlyAdmin {
feeReadSC = _feeReadSC;
emit UpdateFeeReadSC(_feeReadSC);
}
}// SPDX-License-Identifier: MIT
pragma solidity 0.8.18;
import "@openzeppelin/contracts/token/ERC20/utils/SafeERC20.sol";
import "@openzeppelin/contracts/utils/Address.sol";
interface IWanCctpV2 {
function depositForBurn(
uint256 amount,
uint32 destinationDomain,
bytes32 mintRecipient,
address burnToken,
bytes32 destinationCaller,
uint256 maxFee,
uint32 minFinalityThreshold
) external payable ;
function receiveMessage(
bytes calldata message,
bytes calldata attestation
) external returns (bool success);
function estimateFee(uint32 destinationDomain) external view returns(uint256 fee);
}
contract WanCctpV2Test {
using SafeERC20 for IERC20;
function doubleDepositForBurn(
address feeV2,
uint256 amount,
uint32 destinationDomain,
bytes32 mintRecipient,
address burnToken,
bytes32 destinationCaller,
uint256 maxFee,
uint32 minFinalityThreshold
) external payable {
uint256 fee = IWanCctpV2(feeV2).estimateFee(destinationDomain);
uint256 feeDouble = fee * 2;
require(msg.value >= feeDouble, "Fee: Insufficient fee");
if (msg.value > feeDouble) {
Address.sendValue(payable(msg.sender), msg.value - feeDouble);
}
IERC20(burnToken).safeTransferFrom(msg.sender, address(this), amount);
IERC20(burnToken).forceApprove(feeV2, amount);
uint256 amount1 = amount / 2;
IWanCctpV2(feeV2).depositForBurn{value: fee}(amount1, destinationDomain, mintRecipient, burnToken, destinationCaller, maxFee, minFinalityThreshold);
uint256 amount2 = amount - amount1;
IWanCctpV2(feeV2).depositForBurn{value: fee}(amount2, destinationDomain, mintRecipient, burnToken, destinationCaller, maxFee, minFinalityThreshold);
}
}// SPDX-License-Identifier: MIT
pragma solidity ^0.8.18;
import "./Owned.sol";
/**
* @title Admin
* @dev Contract for managing administrative access control
* This contract provides functionality for managing admin addresses
* and controlling access to administrative functions
*
* Key features:
* - Admin address management
* - Access control through modifiers
* - Admin addition and removal
*
* @custom:security
* - Inherits Owned contract for ownership management
* - Only owner can add/remove admins
* - Only admins can access protected functions
*/
contract Admin is Owned {
/**
* @dev Mapping of addresses to their admin status
*
* @custom:usage
* - Used to track admin addresses
* - Provides quick lookup for admin status
* - Supports admin access control
*/
mapping(address => bool) public mapAdmin;
/**
* @dev Emitted when a new admin is added
*
* @param admin The address of the newly added admin
*/
event AddAdmin(address admin);
/**
* @dev Emitted when an admin is removed
*
* @param admin The address of the removed admin
*/
event RemoveAdmin(address admin);
/**
* @dev Modifier to restrict function access to admin addresses only
*
* @custom:requirements
* - Caller must be an admin
*
* @custom:reverts
* - If caller is not an admin
*/
modifier onlyAdmin() {
require(mapAdmin[msg.sender], "not admin");
_;
}
/**
* @dev Adds a new admin address
*
* @param admin The address to be added as admin
*
* @custom:requirements
* - Caller must be the contract owner
*
* @custom:effects
* - Sets admin status for the address
* - Emits AddAdmin event
*/
function addAdmin(
address admin
)
external
onlyOwner
{
mapAdmin[admin] = true;
emit AddAdmin(admin);
}
/**
* @dev Removes an admin address
*
* @param admin The address to be removed from admin status
*
* @custom:requirements
* - Caller must be the contract owner
*
* @custom:effects
* - Removes admin status for the address
* - Emits RemoveAdmin event
*/
function removeAdmin(
address admin
)
external
onlyOwner
{
delete mapAdmin[admin];
emit RemoveAdmin(admin);
}
}// SPDX-License-Identifier: MIT
pragma solidity ^0.8.18;
import "../lib/BasicStorageLib.sol";
/**
* @title BasicStorage
* @dev Base contract for managing different types of storage data
* This contract provides basic storage functionality for various data types
* using the BasicStorageLib library
*
* Key features:
* - Multiple data type storage support
* - Library-based storage management
* - Internal storage access
*
* @custom:usage
* - Used as base contract for storage functionality
* - Provides structured storage for different data types
* - Supports inheritance for storage management
*/
contract BasicStorage {
/************************************************************
**
** VARIABLES
**
************************************************************/
//// basic variables
/**
* @dev Library usage declarations for different data types
*
* @custom:usage
* - UintData: For unsigned integer storage
* - BoolData: For boolean storage
* - AddressData: For address storage
* - BytesData: For bytes storage
* - StringData: For string storage
*/
using BasicStorageLib for BasicStorageLib.UintData;
using BasicStorageLib for BasicStorageLib.BoolData;
using BasicStorageLib for BasicStorageLib.AddressData;
using BasicStorageLib for BasicStorageLib.BytesData;
using BasicStorageLib for BasicStorageLib.StringData;
/**
* @dev Internal storage variables for different data types
*
* @custom:usage
* - uintData: Stores unsigned integers
* - boolData: Stores boolean values
* - addressData: Stores addresses
* - bytesData: Stores bytes data
* - stringData: Stores strings
*
* @custom:security
* - Internal visibility for controlled access
* - Library-based storage management
*/
BasicStorageLib.UintData internal uintData;
BasicStorageLib.BoolData internal boolData;
BasicStorageLib.AddressData internal addressData;
BasicStorageLib.BytesData internal bytesData;
BasicStorageLib.StringData internal stringData;
}// SPDX-License-Identifier: MIT
pragma solidity ^0.8.18;
import "@openzeppelin/contracts/access/AccessControl.sol";
/**
* @dev Structure for setting cross-chain fees
*
* @param srcChainID Source chain identifier
* @param destChainID Destination chain identifier
* @param contractFee Fee for contract operations
* @param agentFee Fee for agent operations
*
* @custom:usage
* - Used in cross-chain fee management
* - Supports multiple chain pairs
* - Handles different fee types
*/
struct SetFeesParam {
uint256 srcChainID;
uint256 destChainID;
uint256 contractFee;
uint256 agentFee;
}
/**
* @dev Interface for cross-chain proxy operations
*
* @custom:usage
* - Defines cross-chain fee setting functionality
* - Used for proxy contract interactions
*/
interface ICrossProxy {
function setFees(SetFeesParam [] calldata params) external;
}
/**
* @title CrossAdminManager
* @dev Contract for managing cross-chain administrative operations
* This contract provides functionality for managing cross-chain fees
* and administrative operations with role-based access control
*
* Key features:
* - Cross-chain fee management
* - Role-based access control
* - Administrative operations
* - Fallback functionality
*
* @custom:security
* - Inherits OpenZeppelin's AccessControl
* - Role-based permissions
* - Controlled access to cross-chain operations
*/
contract CrossAdminManager is AccessControl {
// Address of the cross-chain contract
address crossSC;
/**
* @dev Role identifier for operator permissions
*
* @custom:usage
* - Used for operator role assignment
* - Controls operator access rights
*/
bytes32 public constant OPERATOR_ROLE = keccak256("OPERATOR_ROLE");
/**
* @dev Modifier to restrict function access to admin and operator roles
*
* @custom:requirements
* - Caller must have admin or operator role
*
* @custom:reverts
* - If caller has neither admin nor operator role
*/
modifier onlyAdminAndOperator() {
require(hasRole(DEFAULT_ADMIN_ROLE, msg.sender) || hasRole(OPERATOR_ROLE, msg.sender), "CrossAdminManager: caller is not admin or operator");
_;
}
/**
* @dev Constructor initializes the contract with admin, operator, and cross-chain addresses
*
* @param admin Address of the admin role holder
* @param operator Address of the operator role holder
* @param _cross Address of the cross-chain contract
*
* @custom:effects
* - Sets up admin role
* - Sets up operator role
* - Initializes cross-chain contract address
*/
constructor(address admin, address operator, address _cross) {
_setupRole(DEFAULT_ADMIN_ROLE, admin);
_setupRole(OPERATOR_ROLE, operator);
crossSC = _cross;
}
/**
* @dev Sets fees for cross-chain operations
*
* @param params Array of fee parameters for different chain pairs
*
* @custom:requirements
* - Caller must have admin or operator role
*
* @custom:effects
* - Updates fees in cross-chain contract
* - Supports multiple chain pairs
*/
function setFees(SetFeesParam [] calldata params) public onlyAdminAndOperator {
ICrossProxy(crossSC).setFees(params);
}
/**
* @dev Fallback function for handling unknown function calls
*
* @custom:requirements
* - Caller must have admin role
*
* @custom:effects
* - Forwards calls to cross-chain contract
* - Requires successful execution
*
* @custom:reverts
* - If call to cross-chain contract fails
*/
fallback() external onlyRole(DEFAULT_ADMIN_ROLE) {
(bool success, ) = crossSC.call(msg.data);
require(success, "CrossAdminManager: fallback call failed");
}
}// SPDX-License-Identifier: MIT
/*
Copyright 2023 Wanchain Foundation.
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
*/
// _ _ _
// __ ____ _ _ __ ___| |__ __ _(_)_ __ __| | _____ __
// \ \ /\ / / _` | '_ \ / __| '_ \ / _` | | '_ \@/ _` |/ _ \ \ / /
// \ V V / (_| | | | | (__| | | | (_| | | | | | (_| | __/\ V /
// \_/\_/ \__,_|_| |_|\___|_| |_|\__,_|_|_| |_|\__,_|\___| \_/
//
//
pragma solidity 0.8.18;
import "@openzeppelin/contracts/token/ERC20/IERC20.sol";
import "@openzeppelin/contracts/token/ERC20/utils/SafeERC20.sol";
import "@openzeppelin/contracts/token/ERC721/IERC721.sol";
import "@openzeppelin/contracts/token/ERC1155/IERC1155.sol";
import "@openzeppelin/contracts/token/ERC721/utils/ERC721Holder.sol";
import "@openzeppelin/contracts/token/ERC1155/utils/ERC1155Holder.sol";
import "@openzeppelin/contracts/utils/introspection/ERC165.sol";
/**
* @dev Interface for cross-chain operations
*
* @custom:usage
* - Defines core cross-chain functionality
* - Handles token transfers and burns
* - Manages NFT operations
*/
interface ICross {
function currentChainID() external view returns (uint);
function getPartners() external view returns(address tokenManager, address smgAdminProxy, address smgFeeProxy, address quota, address sigVerifier);
function userLock(bytes32 smgID, uint tokenPairID, uint value, bytes calldata userAccount) external payable;
function userBurn(bytes32 smgID, uint tokenPairID, uint value, uint fee, address tokenAccount, bytes calldata userAccount) external payable;
function userLockNFT(bytes32 smgID, uint tokenPairID, uint[] memory tokenIDs, uint[] memory tokenValues, bytes memory userAccount) external payable;
function userBurnNFT(bytes32 smgID, uint tokenPairID, uint[] memory tokenIDs, uint[] memory tokenValues, address tokenAccount, bytes memory userAccount) external payable;
}
/**
* @dev Interface for token pair management
*
* @custom:usage
* - Manages token pair information
* - Handles token pair type mapping
*/
interface ITokenManager {
function getTokenPairInfo(uint id) external view returns (uint fromChainID, bytes memory fromAccount, uint toChainID, bytes memory toAccount);
function mapTokenPairType(uint id) external view returns (uint8);
}
/**
* @dev Interface for XDC token receiver operations
*
* @custom:usage
* - Handles XDC token reception
* - Supports ERC721 and ERC1155 tokens
*/
interface IXDCReceiver {
function onXRC721Received(address, address, uint256, bytes calldata) external returns (bytes4);
function onXRC1155Received(address, address, uint256, uint256, bytes calldata) external returns (bytes4);
function onXRC1155BatchReceived(address, address, uint256[] calldata, uint256[] calldata, bytes calldata) external returns (bytes4);
}
/**
* @title CrossWrapper
* @dev Contract for wrapping cross-chain operations with token support
* This contract provides functionality for handling cross-chain token transfers
* and NFT operations with proper token approvals and safety checks
*
* Key features:
* - Cross-chain token transfers
* - NFT cross-chain operations
* - Token approval management
* - Safe token transfers
*
* @custom:security
* - SafeERC20 for token transfers
* - ERC721Holder for NFT support
* - ERC1155Holder for batch NFT support
*/
contract CrossWrapper is IXDCReceiver, ERC721Holder, ERC1155Holder {
using SafeERC20 for IERC20;
// Cross-chain contract instance
ICross public cross;
// Token manager contract address
address public tokenManager;
// Current chain identifier
uint public currentChainID;
/**
* @dev Enum for different token types supported in cross-chain operations
*
* @custom:usage
* - ERC20: Standard token type
* - ERC721: Non-fungible token type
* - ERC1155: Multi-token type
*/
enum TokenCrossType {ERC20, ERC721, ERC1155}
/**
* @dev Event emitted when partner cross-chain operation is performed
*
* @param partner Partner identifier
* @param _partner Partner identifier (duplicate for compatibility)
*/
event PartnerCross(string indexed partner, string _partner);
/**
* @dev Constructor initializes the contract with cross-chain address
*
* @param _cross Address of the cross-chain contract
*
* @custom:effects
* - Sets up cross-chain contract
* - Initializes token manager
* - Sets current chain ID
*/
constructor(address _cross) {
cross = ICross(_cross);
(tokenManager, , , , ) = cross.getPartners();
currentChainID = cross.currentChainID();
}
/**
* @dev Locks tokens for cross-chain transfer
*
* @param smgID Storeman group identifier
* @param tokenPairID Token pair identifier
* @param value Amount of tokens to lock
* @param userAccount User account information
* @param partner Partner identifier
*
* @custom:effects
* - Transfers tokens from user
* - Approves cross-chain contract
* - Initiates cross-chain lock
*/
function userLock(bytes32 smgID, uint tokenPairID, uint value, bytes calldata userAccount, string memory partner) external payable {
address tokenAddress = _getTokenAddressFromPairID(tokenPairID);
if (tokenAddress != address(0)) {
IERC20(tokenAddress).safeTransferFrom(msg.sender, address(this), value);
IERC20(tokenAddress).forceApprove(address(cross), value);
}
cross.userLock{value: msg.value}(smgID, tokenPairID, value, userAccount);
emit PartnerCross(partner, partner);
}
/**
* @dev Burns tokens for cross-chain transfer
*
* @param smgID Storeman group identifier
* @param tokenPairID Token pair identifier
* @param value Amount of tokens to burn
* @param fee Cross-chain fee
* @param tokenAccount Token account address
* @param userAccount User account information
* @param partner Partner identifier
*
* @custom:effects
* - Transfers tokens from user
* - Initiates cross-chain burn
*/
function userBurn(bytes32 smgID, uint tokenPairID, uint value, uint fee, address tokenAccount, bytes calldata userAccount, string memory partner) external payable {
address tokenAddress = _getTokenAddressFromPairID(tokenPairID);
IERC20(tokenAddress).safeTransferFrom(msg.sender, address(this), value);
cross.userBurn{value: msg.value}(smgID, tokenPairID, value, fee, tokenAccount, userAccount);
emit PartnerCross(partner, partner);
}
/**
* @dev Locks NFTs for cross-chain transfer
*
* @param smgID Storeman group identifier
* @param tokenPairID Token pair identifier
* @param tokenIDs Array of NFT token IDs
* @param tokenValues Array of NFT token values
* @param userAccount User account information
* @param partner Partner identifier
*
* @custom:effects
* - Transfers NFTs from user
* - Approves cross-chain contract
* - Initiates cross-chain NFT lock
*/
function userLockNFT(bytes32 smgID, uint tokenPairID, uint[] memory tokenIDs, uint[] memory tokenValues, bytes memory userAccount, string memory partner) external payable {
uint8 tokenCrossType = ITokenManager(tokenManager).mapTokenPairType(tokenPairID);
address tokenScAddr = _getTokenAddressFromPairID(tokenPairID);
if (tokenCrossType == uint8(TokenCrossType.ERC721)) {
for(uint idx = 0; idx < tokenIDs.length; ++idx) {
IERC721(tokenScAddr).safeTransferFrom(msg.sender, address(this), tokenIDs[idx], "");
}
IERC721(tokenScAddr).setApprovalForAll(address(cross), true);
} else if(tokenCrossType == uint8(TokenCrossType.ERC1155)) {
IERC1155(tokenScAddr).safeBatchTransferFrom(msg.sender, address(this), tokenIDs, tokenValues, "");
IERC1155(tokenScAddr).setApprovalForAll(address(cross), true);
} else {
require(false, "Invalid NFT type");
}
cross.userLockNFT{value: msg.value}(smgID, tokenPairID, tokenIDs, tokenValues, userAccount);
emit PartnerCross(partner, partner);
}
/**
* @dev Burns NFTs for cross-chain transfer
*
* @param smgID Storeman group identifier
* @param tokenPairID Token pair identifier
* @param tokenIDs Array of NFT token IDs
* @param tokenValues Array of NFT token values
* @param tokenAccount Token account address
* @param userAccount User account information
* @param partner Partner identifier
*
* @custom:effects
* - Transfers NFTs from user
* - Initiates cross-chain NFT burn
*/
function userBurnNFT(bytes32 smgID, uint tokenPairID, uint[] memory tokenIDs, uint[] memory tokenValues, address tokenAccount, bytes memory userAccount, string memory partner) external payable {
uint8 tokenCrossType = ITokenManager(tokenManager).mapTokenPairType(tokenPairID);
address tokenScAddr = _getTokenAddressFromPairID(tokenPairID);
if (tokenCrossType == uint8(TokenCrossType.ERC721)) {
for(uint idx = 0; idx < tokenIDs.length; ++idx) {
IERC721(tokenScAddr).safeTransferFrom(msg.sender, address(this), tokenIDs[idx], "");
}
} else if(tokenCrossType == uint8(TokenCrossType.ERC1155)) {
IERC1155(tokenScAddr).safeBatchTransferFrom(msg.sender, address(this), tokenIDs, tokenValues, "");
} else {
require(false, "Invalid NFT type");
}
cross.userBurnNFT{value: msg.value}(smgID, tokenPairID, tokenIDs, tokenValues, tokenAccount, userAccount);
emit PartnerCross(partner, partner);
}
/**
* @dev Checks if contract supports specific interface
*
* @param interfaceId Interface identifier to check
* @return True if interface is supported
*/
function supportsInterface(bytes4 interfaceId) public view virtual override(ERC1155Receiver) returns (bool) {
return
interfaceId == type(IERC721Receiver).interfaceId ||
interfaceId == type(IERC1155Receiver).interfaceId ||
interfaceId == type(IERC165).interfaceId ||
interfaceId == type(IXDCReceiver).interfaceId ||
super.supportsInterface(interfaceId);
}
/**
* @dev Handles ERC721 token reception
*
* @return Function selector
*/
function onXRC721Received(address, address, uint256, bytes calldata)
external
pure
returns (bytes4)
{
return this.onXRC721Received.selector;
}
/**
* @dev Handles ERC1155 token reception
*
* @return Function selector
*/
function onXRC1155Received(address, address, uint256, uint256, bytes calldata)
external
pure
returns(bytes4)
{
return this.onXRC1155Received.selector;
}
/**
* @dev Handles batch ERC1155 token reception
*
* @return Function selector
*/
function onXRC1155BatchReceived(address, address, uint256[] calldata, uint256[] calldata, bytes calldata)
external
pure
returns(bytes4)
{
return this.onXRC1155BatchReceived.selector;
}
/**
* @dev Gets token address from token pair ID
*
* @param tokenPairID Token pair identifier
* @return Token contract address
*
* @custom:reverts
* - If token pair ID is invalid
*/
function _getTokenAddressFromPairID(uint tokenPairID) internal view returns (address) {
(uint fromChainID, bytes memory fromAccount, uint toChainID, bytes memory toAccount) = ITokenManager(tokenManager).getTokenPairInfo(tokenPairID);
if (currentChainID == fromChainID) {
return _bytesToAddress(fromAccount);
} else if (currentChainID == toChainID) {
return _bytesToAddress(toAccount);
} else {
revert("Invalid token pair ID");
}
}
/**
* @dev Converts bytes to address
*
* @param b Bytes to convert
* @return addr Converted address
*/
function _bytesToAddress(bytes memory b) internal pure returns (address addr) {
assembly {
addr := mload(add(b,20))
}
}
}// SPDX-License-Identifier: MIT
/*
Copyright 2023 Wanchain Foundation.
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
*/
// _ _ _
// __ ____ _ _ __ ___| |__ __ _(_)_ __ __| | _____ __
// \ \ /\ / / _` | '_ \ / __| '_ \ / _` | | '_ \@/ _` |/ _ \ \ / /
// \ V V / (_| | | | | (__| | | | (_| | | | | | (_| | __/\ V /
// \_/\_/ \__,_|_| |_|\___|_| |_|\__,_|_|_| |_|\__,_|\___| \_/
//
//
pragma solidity ^0.8.18;
import './Owned.sol';
/**
* @title Halt
* @dev Contract for emergency stop functionality
* This contract provides functionality to halt and resume contract operations
* in emergency situations
*
* Key features:
* - Emergency stop mechanism
* - Access control through ownership
* - Modifiers for halted state checks
*
* @custom:security
* - Inherits Owned contract for ownership management
* - Only owner can halt/resume operations
* - State checks through modifiers
*/
contract Halt is Owned {
/**
* @dev Public state variable indicating if contract is halted
*
* @custom:usage
* - Controls contract operation state
* - Accessible for external queries
* - Modified through setHalt function
*/
bool public halted = false;
/**
* @dev Modifier to ensure contract is not halted
*
* @custom:requirements
* - Contract must not be in halted state
*
* @custom:reverts
* - If contract is halted
*/
modifier notHalted() {
require(!halted, "Smart contract is halted");
_;
}
/**
* @dev Modifier to ensure contract is halted
*
* @custom:requirements
* - Contract must be in halted state
*
* @custom:reverts
* - If contract is not halted
*/
modifier isHalted() {
require(halted, "Smart contract is not halted");
_;
}
/**
* @dev Sets the halted state of the contract
*
* @param halt Boolean indicating desired halted state
*
* @custom:requirements
* - Caller must be the contract owner
*
* @custom:effects
* - Updates halted state
* - Controls contract operation availability
*/
function setHalt(bool halt)
public
onlyOwner
{
halted = halt;
}
}// SPDX-License-Identifier: MIT
/*
Copyright 2023 Wanchain Foundation.
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
*/
// _ _ _
// __ ____ _ _ __ ___| |__ __ _(_)_ __ __| | _____ __
// \ \ /\ / / _` | '_ \ / __| '_ \ / _` | | '_ \@/ _` |/ _ \ \ / /
// \ V V / (_| | | | | (__| | | | (_| | | | | | (_| | __/\ V /
// \_/\_/ \__,_|_| |_|\___|_| |_|\__,_|_|_| |_|\__,_|\___| \_/
//
//
pragma solidity ^0.8.18;
/**
* @title Owned
* @dev Base contract for ownership management
* This contract provides functionality for managing contract ownership
* with support for ownership transfer and renunciation
*
* Key features:
* - Ownership assignment
* - Ownership transfer
* - Ownership renunciation
* - Two-step ownership transfer
*
* @custom:security
* - Owner-only access control
* - Safe ownership transfer
* - Ownership renunciation capability
*/
contract Owned {
/**
* @dev Emitted when ownership is transferred
*
* @param previousOwner Address of the previous owner
* @param newOwner Address of the new owner
*/
event OwnershipTransferred(address indexed previousOwner, address indexed newOwner);
/**
* @dev Modifier to restrict function access to owner only
*
* @custom:requirements
* - Caller must be the contract owner
*
* @custom:reverts
* - If caller is not the owner
*/
modifier onlyOwner() {
require(msg.sender == owner, "Not owner");
_;
}
/**
* @dev Public state variable for contract owner
*
* @custom:usage
* - Stores current owner address
* - Accessible for external queries
* - Modified through ownership functions
*/
address public owner;
/**
* @dev Constructor assigns initial owner
*
* @custom:effects
* - Sets initial owner to contract deployer
*/
constructor() {
owner = msg.sender;
}
/**
* @dev Public state variable for pending owner
*
* @custom:usage
* - Stores address of pending owner
* - Used in two-step ownership transfer
*/
address public newOwner;
/**
* @dev Transfers ownership to a new address
*
* @param _newOwner Address of the new owner
*
* @custom:requirements
* - Caller must be the current owner
* - New owner address must not be zero
*
* @custom:effects
* - Updates owner address
* - Emits OwnershipTransferred event
*/
function transferOwner(address _newOwner) public onlyOwner {
require(_newOwner != address(0), "New owner is the zero address");
emit OwnershipTransferred(owner, _newOwner);
owner = _newOwner;
}
/**
* @dev Initiates two-step ownership transfer
*
* @param _newOwner Address of the new owner
*
* @custom:requirements
* - Caller must be the current owner
*
* @custom:effects
* - Sets pending owner address
*/
function changeOwner(address _newOwner) public onlyOwner {
newOwner = _newOwner;
}
/**
* @dev Accepts pending ownership transfer
*
* @custom:requirements
* - Caller must be the pending owner
*
* @custom:effects
* - Updates owner address to pending owner
*/
function acceptOwnership() public {
if (msg.sender == newOwner) {
owner = newOwner;
}
}
/**
* @dev Renounces ownership of the contract
*
* @custom:requirements
* - Caller must be the current owner
*
* @custom:effects
* - Sets owner to zero address
* - Makes contract unowned
*/
function renounceOwnership() public onlyOwner {
owner = address(0);
}
}// SPDX-License-Identifier: MIT
/*
Copyright 2023 Wanchain Foundation.
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
*/
// _ _ _
// __ ____ _ _ __ ___| |__ __ _(_)_ __ __| | _____ __
// \ \ /\ / / _` | '_ \ / __| '_ \ / _` | | '_ \@/ _` |/ _ \ \ / /
// \ V V / (_| | | | | (__| | | | (_| | | | | | (_| | __/\ V /
// \_/\_/ \__,_|_| |_|\___|_| |_|\__,_|_|_| |_|\__,_|\___| \_/
//
//
pragma solidity ^0.8.18;
/**
* @title Proxy
* @dev Base contract for proxy pattern implementation
* This contract provides functionality for delegating calls to implementation contracts
* and supports contract upgradeability
*
* Key features:
* - Implementation contract delegation
* - Contract upgrade support
* - Fallback handling
* - Receive function support
*
* @custom:security
* - Implementation address validation
* - Safe delegatecall execution
* - Proper return data handling
*/
contract Proxy {
/**
* @dev Emitted when the implementation contract is upgraded
*
* @param implementation Address of the new implementation contract
*/
event Upgraded(address indexed implementation);
/**
* @dev Internal storage for implementation contract address
*
* @custom:usage
* - Stores current implementation address
* - Used for delegatecall operations
* - Modified through upgrade operations
*/
address internal _implementation;
/**
* @dev Returns the current implementation contract address
*
* @return Address of the current implementation contract
*/
function implementation() public view returns (address) {
return _implementation;
}
/**
* @dev Internal function to handle fallback calls
* Delegates all calls to the implementation contract
*
* @custom:requirements
* - Implementation contract must be set
*
* @custom:effects
* - Executes delegatecall to implementation
* - Handles return data
*
* @custom:reverts
* - If implementation contract is not set
* - If delegatecall fails
*/
function _fallback() internal {
address _impl = _implementation;
require(_impl != address(0), "implementation contract not set");
assembly {
let ptr := mload(0x40)
calldatacopy(ptr, 0, calldatasize())
let result := delegatecall(gas(), _impl, ptr, calldatasize(), 0, 0)
let size := returndatasize()
returndatacopy(ptr, 0, size)
switch result
case 0 { revert(ptr, size) }
default { return(ptr, size) }
}
}
/**
* @dev Fallback function to handle unknown function calls
* Delegates all calls to the implementation contract
*
* @custom:effects
* - Forwards call to _fallback
*/
fallback() external payable {
return _fallback();
}
/**
* @dev Receive function to handle incoming ETH
* Delegates all calls to the implementation contract
*
* @custom:effects
* - Forwards call to _fallback
*/
receive() external payable {
return _fallback();
}
}// SPDX-License-Identifier: MIT
pragma solidity ^0.8.18;
import "../storemanGroupAdmin/StoremanType.sol";
/**
* @title ISmg
* @dev Interface for Storeman Group management operations
*
* Key features:
* - Query active group IDs
* - Get storeman group information
*/
interface ISmg {
/**
* @dev Get active group IDs for a specific day
*
* @param day The day to query (in days since epoch)
* @return Array of active group IDs
*/
function getActiveGroupIds(uint day) external view returns (bytes32[] memory);
/**
* @dev Get storeman group information by ID
*
* @param id The group ID to query
* @return info StoremanGroupInfo struct containing group details
*/
function getStoremanGroupInfo(bytes32 id) external view returns(StoremanType.StoremanGroupInfo memory info);
}
/**
* @title QueryCurrentSmgId
* @dev Contract for querying current active storeman group ID based on timestamp
*
* Key features:
* - Query active group ID by timestamp
* - Handle multiple active groups
* - Time-based group selection
*
* @custom:security
* - External contract validation
* - Safe array access
* - Time boundary checks
*/
contract QueryCurrentSmgId {
/**
* @dev Interface instance for storeman group operations
*
* @custom:usage
* - Used for querying group information
* - Set during contract construction
*/
ISmg public smg;
/**
* @dev Constructor initializes the storeman group interface
*
* @param smgAddr Address of the storeman group contract
*
* @custom:effects
* - Sets the ISmg interface instance
*/
constructor(address smgAddr) {
smg = ISmg(smgAddr);
}
/**
* @dev Get the active group ID for a specific timestamp
*
* @param time Unix timestamp to query
* @return bytes32 The active group ID
*
* @custom:logic
* - Converts timestamp to day
* - Gets active groups for the day
* - Returns appropriate group ID based on time window
*
* @custom:returns
* - bytes32(0) if no active groups
* - Single group ID if only one active group
* - Group ID within time window if multiple groups
* - First group ID as fallback
*/
function getActiveGroupIds(uint time) external view returns (bytes32) {
uint day = time / 86400;
bytes32[] memory ids = smg.getActiveGroupIds(day);
if (ids.length == 0) {
return bytes32(0);
}
if (ids.length == 1) {
return ids[0];
}
for (uint i=0; i<ids.length; i++) {
StoremanType.StoremanGroupInfo memory info = smg.getStoremanGroupInfo(ids[i]);
if (time >= info.startTime && time < info.endTime) {
return ids[i];
}
}
return ids[0];
}
}// SPDX-License-Identifier: MIT
pragma solidity ^0.8.18;
/**
* @title ReentrancyGuard
* @dev Abstract contract module that helps prevent reentrant calls to a function
*
* Key features:
* - Prevents reentrant function calls
* - Gas optimization for refunds
* - Support for nested function protection
*
* @custom:security
* - Prevents reentrancy attacks
* - Optimizes gas refunds
* - Supports private function calls
*
* @custom:usage
* - Inherit this contract to use nonReentrant modifier
* - Apply nonReentrant modifier to functions that need protection
* - Use private functions for nested calls
*/
abstract contract ReentrancyGuard {
/**
* @dev Private state variable to track reentrancy status
*
* @custom:usage
* - true: Function can be entered
* - false: Function is currently executing
*
* @custom:security
* - Prevents reentrant calls
* - Optimizes gas refunds
*/
bool private _notEntered;
/**
* @dev Constructor initializes the reentrancy guard
*
* @custom:effects
* - Sets initial state to true
* - Optimizes gas refunds
*
* @custom:security
* - Ensures proper initialization
* - Prevents initial reentrancy
*/
constructor () {
// Storing an initial non-zero value makes deployment a bit more
// expensive, but in exchange the refund on every call to nonReentrant
// will be lower in amount. Since refunds are capped to a percetange of
// the total transaction's gas, it is best to keep them low in cases
// like this one, to increase the likelihood of the full refund coming
// into effect.
_notEntered = true;
}
/**
* @dev Modifier to prevent reentrant calls to a function
*
* @custom:requirements
* - Function must not be currently executing
*
* @custom:effects
* - Sets _notEntered to false during execution
* - Restores _notEntered to true after execution
*
* @custom:reverts
* - If function is already executing
*
* @custom:usage
* - Apply to functions that need reentrancy protection
* - Use with private functions for nested calls
*/
modifier nonReentrant() {
// On the first call to nonReentrant, _notEntered will be true
require(_notEntered, "ReentrancyGuard: reentrant call");
// Any calls to nonReentrant after this point will fail
_notEntered = false;
_;
// By storing the original value once again, a refund is triggered (see
// https://eips.ethereum.org/EIPS/eip-2200)
_notEntered = true;
}
}// SPDX-License-Identifier: MIT pragma solidity 0.8.18; import "@openzeppelin/contracts/governance/TimelockController.sol";
// SPDX-License-Identifier: MIT
/*
Copyright 2023 Wanchain Foundation.
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
*/
// _ _ _
// __ ____ _ _ __ ___| |__ __ _(_)_ __ __| | _____ __
// \ \ /\ / / _` | '_ \ / __| '_ \ / _` | | '_ \@/ _` |/ _ \ \ / /
// \ V V / (_| | | | | (__| | | | (_| | | | | | (_| | __/\ V /
// \_/\_/ \__,_|_| |_|\___|_| |_|\__,_|_|_| |_|\__,_|\___| \_/
//
//
pragma solidity 0.8.18;
import "../components/Halt.sol";
import "../components/Admin.sol";
import "./ConfigStorage.sol";
/**
* @title ConfigDelegate
* @dev Implementation contract for configuration management
* This contract provides functionality for managing curve implementations
* and their corresponding contract addresses
*/
contract ConfigDelegate is ConfigStorage, Halt , Admin{
/**
* @notice Sets the contract addresses for different curve implementations
* @dev Only callable by admin
* @param curveId Array of curve type identifiers
* @param curveAddress Array of corresponding curve contract addresses
* @dev Throws if arrays are empty or have mismatched lengths
*/
function setCurve(uint8[] calldata curveId, address[] calldata curveAddress)
external
onlyAdmin
{
uint8 length = uint8(curveId.length);
require((length > 0) && (length == curveAddress.length), "Mismatched length");
for (uint8 i = 0; i < length; i++) {
curves[curveId[i]] = curveAddress[i];
}
}
/**
* @notice Retrieves the contract address for a specific curve type
* @param curveId The identifier of the curve type
* @return address The contract address implementing the curve
* @dev Throws if no curve implementation exists for the given ID
*/
function getCurve(uint8 curveId)
external
view
returns(address){
require(curves[curveId] != address(0), "No curve");
return curves[curveId];
}
/**
* @dev Prevents the contract from receiving ETH
* @notice This contract does not support receiving ETH
*/
receive() external payable {
revert("Not support");
}
}// SPDX-License-Identifier: MIT
/*
Copyright 2023 Wanchain Foundation.
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
*/
// _ _ _
// __ ____ _ _ __ ___| |__ __ _(_)_ __ __| | _____ __
// \ \ /\ / / _` | '_ \ / __| '_ \ / _` | | '_ \@/ _` |/ _ \ \ / /
// \ V V / (_| | | | | (__| | | | (_| | | | | | (_| | __/\ V /
// \_/\_/ \__,_|_| |_|\___|_| |_|\__,_|_|_| |_|\__,_|\___| \_/
//
//
pragma solidity 0.8.18;
/**
* Math operations with safety checks
*/
import "../components/Halt.sol";
import "../components/Admin.sol";
import "./ConfigStorage.sol";
import "../components/Proxy.sol";
/**
* @title ConfigProxy
* @dev Proxy contract for configuration management
* This contract implements the upgradeable pattern for the configuration system
* and provides administrative controls for contract upgrades
*/
contract ConfigProxy is ConfigStorage, Halt, Admin, Proxy {
/**
* @notice Updates the implementation address of the ConfigDelegate contract
* @dev Only callable by the contract owner
* @param impl The address of the new ConfigDelegate contract
* @dev Throws if the new implementation address is invalid or the same as current
* @dev Emits Upgraded event on successful upgrade
*/
function upgradeTo(address impl) public onlyOwner {
require(impl != address(0), "Cannot upgrade to invalid address");
require(impl != _implementation, "Cannot upgrade to the same implementation");
_implementation = impl;
emit Upgraded(impl);
}
}// SPDX-License-Identifier: MIT
/*
Copyright 2023 Wanchain Foundation.
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
*/
// _ _ _
// __ ____ _ _ __ ___| |__ __ _(_)_ __ __| | _____ __
// \ \ /\ / / _` | '_ \ / __| '_ \ / _` | | '_ \@/ _` |/ _ \ \ / /
// \ V V / (_| | | | | (__| | | | (_| | | | | | (_| | __/\ V /
// \_/\_/ \__,_|_| |_|\___|_| |_|\__,_|_|_| |_|\__,_|\___| \_/
//
//
pragma solidity ^0.8.18;
import "../components/BasicStorage.sol";
/**
* @title ConfigStorage
* @dev Storage contract for configuration management
* This contract stores the mapping between curve types and their corresponding contract addresses
*/
contract ConfigStorage is BasicStorage {
/************************************************************
**
** VARIABLES
**
************************************************************/
/**
* @dev Mapping from curve type to its contract address
* @notice Stores the addresses of different curve implementations
* uint8 The curve type identifier
* address The contract address implementing the curve
*/
mapping(uint8 => address) curves;
}// SPDX-License-Identifier: MIT
/*
Copyright 2023 Wanchain Foundation.
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
*/
// _ _ _
// __ ____ _ _ __ ___| |__ __ _(_)_ __ __| | _____ __
// \ \ /\ / / _` | '_ \ / __| '_ \ / _` | | '_ \@/ _` |/ _ \ \ / /
// \ V V / (_| | | | | (__| | | | (_| | | | | | (_| | __/\ V /
// \_/\_/ \__,_|_| |_|\___|_| |_|\__,_|_|_| |_|\__,_|\___| \_/
//
//
pragma solidity 0.8.18;
import "./CrossStorageV4.sol";
import "./lib/RapidityLibV4.sol";
import "./lib/NFTLibV1.sol";
/**
* @title CrossDelegateV4
* @dev Main implementation contract for cross-chain functionality
* This contract handles:
* - Cross-chain token transfers
* - NFT transfers
* - Fee management
* - Admin and operator role management
* - Transaction verification and execution
*/
contract CrossDelegateV4 is CrossStorageV4 {
using SafeMath for uint;
/**
* @notice Events emitted by the contract
*/
/**
* @notice Emitted when a new admin is set
* @param adminAccount The address of the new admin
*/
event SetAdmin(address adminAccount);
/**
* @notice Emitted when fees are updated for cross-chain operations
* @param srcChainID Source chain identifier
* @param destChainID Destination chain identifier
* @param contractFee Fee charged by the contract
* @param agentFee Fee charged by the agent
*/
event SetFee(uint indexed srcChainID, uint indexed destChainID, uint contractFee, uint agentFee);
/**
* @notice Emitted when token pair fees are updated
* @param tokenPairID ID of the token pair
* @param contractFee Fee charged by the contract for this token pair
*/
event SetTokenPairFee(uint indexed tokenPairID, uint contractFee);
/**
* @notice Emitted when a storeman group withdraws original coins to a receiver
* @param smgID ID of the storeman group
* @param timeStamp Timestamp of the withdrawal
* @param receiver Address of the receiver
* @param fee Shadow coin fee received by the storeman group
*/
event WithdrawHistoryFeeLogger(bytes32 indexed smgID, uint indexed timeStamp, address indexed receiver, uint fee);
/**
* @notice Emitted when operator status is configured
* @param operator Address of the operator
* @param enabled Whether the operator is enabled or disabled
*/
event ConfigOperator(address indexed operator, bool indexed enabled);
/**
* @notice Emitted when admin status is configured
* @param admin Address of the admin
* @param enabled Whether the admin is enabled or disabled
*/
event ConfigAdmin(address indexed admin, bool indexed enabled);
/**
*
* MODIFIERS
*
*/
/**
* @notice Ensures the caller has admin privileges
* @dev Checks if the caller is an admin, the main admin, or the owner
*/
modifier onlyAdmin() {
require(isAdmin[msg.sender] || msg.sender == admin || msg.sender == owner, "not admin");
_;
}
/**
* @notice Ensures the caller has operator privileges
* @dev Checks if the caller is an operator, an admin, the main admin, or the owner
*/
modifier onlyOperator() {
require(isOperator[msg.sender] || isAdmin[msg.sender] || msg.sender == admin || msg.sender == owner, "not operator");
_;
}
/**
* @notice Ensures the storeman group is in ready state
* @dev Checks if the specified storeman group is ready for operations
*/
modifier onlyReadySmg(bytes32 smgID) {
uint8 status;
uint startTime;
uint endTime;
(status,startTime,endTime) = storageData.smgAdminProxy.getStoremanGroupStatus(smgID);
require(status == uint8(GroupStatus.ready) && block.timestamp >= startTime && block.timestamp <= endTime, "PK is not ready");
_;
}
/**
*
* MANIPULATIONS
*
*/
/**
* @notice Initiates a cross-chain token transfer by locking original tokens
* @dev This function handles the initial step of cross-chain transfer where original tokens are locked
* @param smgID ID of the storeman group handling the transfer
* @param tokenPairID ID of the token pair for cross-chain transfer
* @param value Amount of tokens to transfer
* @param userAccount Account information for receiving tokens on the destination chain
* Requirements:
* - Contract must not be halted
* - Storeman group must be ready
* - Value must be greater than 0
* - Token pair must exist
*/
function userLock(bytes32 smgID, uint tokenPairID, uint value, bytes calldata userAccount)
external
payable
notHalted
nonReentrant
onlyReadySmg(smgID)
{
address smgFeeProxy = getSmgFeeProxy();
RapidityLibV4.RapidityUserLockParams memory params = RapidityLibV4.RapidityUserLockParams({
smgID: smgID,
tokenPairID: tokenPairID,
value: value,
currentChainID: currentChainID,
tokenPairContractFee: mapTokenPairContractFee[tokenPairID],
etherTransferGasLimit: getEtherTransferGasLimit(),
destUserAccount: userAccount,
smgFeeProxy: smgFeeProxy
});
RapidityLibV4.userLock(storageData, params);
}
/**
* @notice Initiates a cross-chain token transfer by burning WRC20 tokens
* @dev This function handles the initial step of cross-chain transfer where WRC20 tokens are burned
* @param smgID ID of the storeman group handling the transfer
* @param tokenPairID ID of the token pair for cross-chain transfer
* @param value Amount of tokens to transfer
* @param fee Fee for the transfer operation
* @param tokenAccount Address of the token contract
* @param userAccount Account information for receiving tokens on the destination chain
* Requirements:
* - Contract must not be halted
* - Storeman group must be ready
* - Value must be greater than fee
* - Token pair must exist
*/
function userBurn(bytes32 smgID, uint tokenPairID, uint value, uint fee, address tokenAccount, bytes calldata userAccount)
external
payable
notHalted
nonReentrant
onlyReadySmg(smgID)
{
address smgFeeProxy = getSmgFeeProxy();
RapidityLibV4.RapidityUserBurnParams memory params = RapidityLibV4.RapidityUserBurnParams({
smgID: smgID,
tokenPairID: tokenPairID,
value: value,
fee: fee,
currentChainID: currentChainID,
tokenPairContractFee: mapTokenPairContractFee[tokenPairID],
etherTransferGasLimit: getEtherTransferGasLimit(),
srcTokenAccount: tokenAccount,
destUserAccount: userAccount,
smgFeeProxy: smgFeeProxy
});
RapidityLibV4.userBurn(storageData, params);
}
/**
* @notice Mints WRC20 tokens for cross-chain transfer
* @dev This function is called by the storeman group to mint WRC20 tokens after receiving original tokens
* @param uniqueID Unique identifier for the cross-chain transaction
* @param smgID ID of the storeman group handling the transfer
* @param tokenPairID ID of the token pair for cross-chain transfer
* @param value Amount of tokens to mint
* @param fee Fee for the transfer operation
* @param tokenAccount Address of the token contract
* @param userAccount Address of the user to receive the minted tokens
* @param r First part of the signature
* @param s Second part of the signature
* Requirements:
* - Contract must not be halted
* - Signature must be valid
* - Transaction must not be already processed
* - Value must be greater than fee
*/
function smgMint(bytes32 uniqueID, bytes32 smgID, uint tokenPairID, uint value, uint fee, address tokenAccount, address userAccount, bytes calldata r, bytes32 s)
external
notHalted
{
uint curveID;
bytes memory PK;
(curveID, PK) = acquireReadySmgInfo(smgID);
RapidityLibV4.RapiditySmgMintParams memory params = RapidityLibV4.RapiditySmgMintParams({
uniqueID: uniqueID,
smgID: smgID,
tokenPairID: tokenPairID,
value: value,
fee: fee,
destTokenAccount: tokenAccount,
destUserAccount: userAccount,
smgFeeProxy: (storageData.smgFeeProxy == address(0)) ? owner : storageData.smgFeeProxy // fix: Stack too deep
});
RapidityLibV4.smgMint(storageData, params);
bytes32 mHash = hashFunc(abi.encode(currentChainID, uniqueID, tokenPairID, value, fee, tokenAccount, userAccount));
verifySignature(curveID, mHash, PK, r, s);
}
/**
* @notice Releases original tokens in exchange for WRC20 tokens on Wanchain
* @dev This function is called by the storeman group to release original tokens after receiving WRC20 tokens
* @param uniqueID Unique identifier for the cross-chain transaction
* @param smgID ID of the storeman group handling the transfer
* @param tokenPairID ID of the token pair for cross-chain transfer
* @param value Amount of tokens to release
* @param fee Fee for the transfer operation
* @param tokenAccount Address of the token contract
* @param userAccount Address of the user to receive the original tokens
* @param r First part of the signature
* @param s Second part of the signature
* Requirements:
* - Contract must not be halted
* - Storeman group must be ready and valid
* - Signature must be valid
* - Transaction must not be already processed
*/
function smgRelease(bytes32 uniqueID, bytes32 smgID, uint tokenPairID, uint value, uint fee, address tokenAccount, address userAccount, bytes calldata r, bytes32 s)
external
notHalted
{
uint curveID;
bytes memory PK;
(curveID, PK) = acquireReadySmgInfo(smgID);
RapidityLibV4.RapiditySmgReleaseParams memory params = RapidityLibV4.RapiditySmgReleaseParams({
uniqueID: uniqueID,
smgID: smgID,
tokenPairID: tokenPairID,
value: value,
fee: fee,
etherTransferGasLimit: getEtherTransferGasLimit(),
destTokenAccount: tokenAccount,
destUserAccount: userAccount,
smgFeeProxy: (storageData.smgFeeProxy == address(0)) ? owner : storageData.smgFeeProxy // fix: Stack too deep
});
RapidityLibV4.smgRelease(storageData, params);
bytes32 mHash = hashFunc(abi.encode(currentChainID, uniqueID, tokenPairID, value, fee, tokenAccount, userAccount));
verifySignature(curveID, mHash, PK, r, s);
}
/**
* @notice Sets the fees for cross-chain transfers between specific chains
* @dev This function allows operators to set both contract and agent fees for cross-chain operations
* @param param Struct containing the fee parameters:
* - srcChainID: Source chain ID
* - destChainID: Destination chain ID
* - contractFee: Fee charged by the contract
* - agentFee: Fee charged by the agent
* Requirements:
* - Caller must be an operator
*/
function setFee(SetFeesParam calldata param) public virtual onlyOperator {
storageData.mapContractFee[param.srcChainID][param.destChainID] = param.contractFee;
storageData.mapAgentFee[param.srcChainID][param.destChainID] = param.agentFee;
emit SetFee(param.srcChainID, param.destChainID, param.contractFee, param.agentFee);
}
/**
* @notice Sets fees for multiple cross-chain transfer pairs at once
* @dev This function allows operators to set fees for multiple chain pairs in a single transaction
* @param params Array of fee parameters for different chain pairs
* Requirements:
* - Caller must be an operator
*/
function setFees(SetFeesParam [] calldata params) public virtual onlyOperator {
for (uint i = 0; i < params.length; ++i) {
storageData.mapContractFee[params[i].srcChainID][params[i].destChainID] = params[i].contractFee;
storageData.mapAgentFee[params[i].srcChainID][params[i].destChainID] = params[i].agentFee;
emit SetFee(params[i].srcChainID, params[i].destChainID, params[i].contractFee, params[i].agentFee);
}
}
/**
* @notice Sets the contract fee for a specific token pair
* @dev This function allows operators to set the contract fee for cross-chain transfers of a specific token pair
* @param tokenPairID ID of the token pair
* @param contractFee Fee charged by the contract for this token pair
* Requirements:
* - Caller must be an operator
*/
function setTokenPairFee(uint256 tokenPairID, uint256 contractFee) external virtual onlyOperator {
mapTokenPairContractFee[tokenPairID] = contractFee;
emit SetTokenPairFee(tokenPairID, contractFee);
}
/**
* @notice Sets contract fees for multiple token pairs at once
* @dev This function allows operators to set contract fees for multiple token pairs in a single transaction
* @param params Array of token pair fee parameters
* Requirements:
* - Caller must be an operator
*/
function setTokenPairFees(SetTokenPairFeesParam [] calldata params) public virtual onlyOperator {
for (uint i = 0; i < params.length; ++i) {
mapTokenPairContractFee[params[i].tokenPairID] = params[i].contractFee;
emit SetTokenPairFee(params[i].tokenPairID, params[i].contractFee);
}
}
/**
* @notice Sets the current chain ID for the contract
* @dev This function allows admin to set the chain ID only if it hasn't been set before
* @param chainID The chain ID to set
* Requirements:
* - Caller must be admin
* - Chain ID must not be already set
*/
function setChainID(uint256 chainID) external virtual onlyAdmin {
if (currentChainID == 0) {
currentChainID = chainID;
}
}
/**
* @notice Computes the hash of input data using either keccak256 or sha256
* @dev This function is used for signature verification in cross-chain transactions
* @param data The input data to hash
* @return The computed hash value
*/
function hashFunc(bytes memory data) public view returns (bytes32){
if(hashType == 1) {
return keccak256(data);
} else {
return sha256(data);
}
}
/**
* @notice Sets the hash function type to be used for signature verification
* @dev This function allows the owner to switch between keccak256 (1) and sha256 (0)
* @param _hashType The hash function type to set (1 for keccak256, 0 for sha256)
* Requirements:
* - Caller must be the owner
*/
function setHashType(uint _hashType) external onlyOwner {
hashType = _hashType;
}
/**
* @notice Sets the admin address for the contract
* @dev This function allows the owner to change the admin address
* @param adminAccount The new admin address
* Requirements:
* - Caller must be the owner
*/
function setAdmin(address adminAccount) external onlyOwner {
admin = adminAccount;
emit SetAdmin(adminAccount);
}
/**
* @notice Sets a uint value in the contract's storage
* @dev This function allows admin to set values in the contract's storage using key-value pairs
* @param key The primary key for the storage location
* @param innerKey The secondary key for the storage location
* @param value The uint value to store
* Requirements:
* - Caller must be admin
*/
function setUintValue(bytes calldata key, bytes calldata innerKey, uint value) external virtual onlyAdmin {
return BasicStorageLib.setStorage(uintData, key, innerKey, value);
}
/**
* @notice Deletes a uint value from the contract's storage
* @dev This function allows admin to remove values from the contract's storage
* @param key The primary key for the storage location
* @param innerKey The secondary key for the storage location
* Requirements:
* - Caller must be admin
*/
function delUintValue(bytes calldata key, bytes calldata innerKey) external virtual onlyAdmin {
return BasicStorageLib.delStorage(uintData, key, innerKey);
}
/// @notice update the initialized state value of this contract
/// @param tokenManager address of the token manager
/// @param smgAdminProxy address of the storeman group admin
/// @param smgFeeProxy address of the proxy to store fee for storeman group
/// @param sigVerifier address of the signature verifier
function setPartners(address tokenManager, address smgAdminProxy, address smgFeeProxy, address, address sigVerifier)
external
onlyOwner
{
require(tokenManager != address(0) && smgAdminProxy != address(0) && sigVerifier != address(0),
"Parameter is invalid");
storageData.smgAdminProxy = IStoremanGroup(smgAdminProxy);
storageData.tokenManager = ITokenManager(tokenManager);
// storageData.quota = IQuota(quota);
storageData.smgFeeProxy = smgFeeProxy;
storageData.sigVerifier = ISignatureVerifier(sigVerifier);
}
/**
* @notice Withdraws accumulated historical fees to the foundation account
* @dev This function allows withdrawing fees accumulated by storeman groups
* @param smgIDs Array of storeman group IDs whose fees are to be withdrawn
* Requirements:
* - smgFeeProxy must be a valid address
*/
function smgWithdrawHistoryFee(bytes32 [] calldata smgIDs) external {
uint fee;
uint currentFee;
address smgFeeProxy = storageData.smgFeeProxy;
if (smgFeeProxy == address(0)) {
smgFeeProxy = owner;
}
require(smgFeeProxy != address(0), "invalid smgFeeProxy");
for (uint i = 0; i < smgIDs.length; ++i) {
currentFee = storageData.mapStoremanFee[smgIDs[i]];
delete storageData.mapStoremanFee[smgIDs[i]];
fee = fee.add(currentFee);
emit WithdrawHistoryFeeLogger(smgIDs[i], block.timestamp, smgFeeProxy, currentFee);
}
if (fee > 0) {
EtherTransfer.sendValue(payable(smgFeeProxy), fee, getEtherTransferGasLimit());
}
}
/**
* @notice Retrieves a uint value from the contract's storage
* @dev This function allows reading values from the contract's storage using key-value pairs
* @param key The primary key for the storage location
* @param innerKey The secondary key for the storage location
* @return The stored uint value
*/
function getUintValue(bytes calldata key, bytes calldata innerKey) public view returns (uint) {
return BasicStorageLib.getStorage(uintData, key, innerKey);
}
/**
* @notice Retrieves the accumulated fee for a specific storeman group
* @dev This function allows checking the fee amount that a storeman group has accumulated
* @param key The storeman group ID
* @return fee The accumulated fee amount for the storeman group
*/
function getStoremanFee(bytes32 key) external view returns(uint fee) {
fee = storageData.mapStoremanFee[key];
}
/**
* @notice Retrieves the fees for cross-chain transfers between specific chains
* @dev This function allows checking both contract and agent fees for a specific chain pair
* @param param Struct containing the chain IDs to check fees for
* @return fee Struct containing the contract and agent fees
*/
function getFee(GetFeesParam calldata param) public view returns(GetFeesReturn memory fee) {
fee.contractFee = storageData.mapContractFee[param.srcChainID][param.destChainID];
fee.agentFee = storageData.mapAgentFee[param.srcChainID][param.destChainID];
}
/**
* @notice Retrieves fees for multiple cross-chain transfer pairs at once
* @dev This function allows checking fees for multiple chain pairs in a single call
* @param params Array of chain pair parameters to check fees for
* @return fees Array of fee structs containing contract and agent fees for each chain pair
*/
function getFees(GetFeesParam [] calldata params) public view returns(GetFeesReturn [] memory fees) {
fees = new GetFeesReturn[](params.length);
for (uint i = 0; i < params.length; ++i) {
fees[i].contractFee = storageData.mapContractFee[params[i].srcChainID][params[i].destChainID];
fees[i].agentFee = storageData.mapAgentFee[params[i].srcChainID][params[i].destChainID];
}
}
/**
* @notice Retrieves the contract fee for a specific token pair
* @dev This function allows checking the contract fee for cross-chain transfers of a specific token pair
* @param tokenPairID ID of the token pair
* @return contractFee The contract fee for the specified token pair
*/
function getTokenPairFee(uint256 tokenPairID) external view returns(uint256 contractFee) {
contractFee = mapTokenPairContractFee[tokenPairID];
}
/**
* @notice Retrieves contract fees for multiple token pairs at once
* @dev This function allows checking contract fees for multiple token pairs in a single call
* @param tokenPairIDs Array of token pair IDs
* @return contractFees Array of contract fees for each token pair
*/
function getTokenPairFees(uint256[] calldata tokenPairIDs) external view returns(uint256 [] memory contractFees) {
contractFees = new uint256[](tokenPairIDs.length);
for (uint i = 0; i < tokenPairIDs.length; ++i) {
contractFees[i] = mapTokenPairContractFee[tokenPairIDs[i]];
}
}
/**
* @notice Retrieves the initialized state and partner addresses of the contract
* @dev This function returns the addresses of all core components and partner contracts
* @return tokenManager Address of the token manager contract
* @return smgAdminProxy Address of the storeman group admin proxy
* @return smgFeeProxy Address of the proxy to store fees for storeman group
* @return quota Address of the quota contract
* @return sigVerifier Address of the signature verifier contract
*/
function getPartners()
external
view
returns(address tokenManager, address smgAdminProxy, address smgFeeProxy, address quota, address sigVerifier)
{
tokenManager = address(storageData.tokenManager);
smgAdminProxy = address(storageData.smgAdminProxy);
smgFeeProxy = storageData.smgFeeProxy;
quota = address(storageData.quota);
sigVerifier = address(storageData.sigVerifier);
}
/** Private and Internal Functions */
/**
* @notice Retrieves information about a ready storeman group
* @dev This function returns the curve ID and public key of a storeman group that is ready for operations
* @param smgID ID of the storeman group to check
* @return curveID ID of the elliptic curve used by the storeman group
* @return PK Public key of the storeman group
* Requirements:
* - Storeman group must be in ready status
*/
function acquireReadySmgInfo(bytes32 smgID)
internal
view
returns (uint curveID, bytes memory PK)
{
uint8 status;
uint startTime;
uint endTime;
(,status,,,,curveID,,PK,,startTime,endTime) = storageData.smgAdminProxy.getStoremanGroupConfig(smgID);
require(status == uint8(GroupStatus.ready) && block.timestamp >= startTime && block.timestamp <= endTime, "PK is not ready");
return (curveID, PK);
}
/**
* @notice Retrieves information about an unregistered storeman group
* @dev This function returns the curve ID and public key of a storeman group that is not yet registered
* @param smgID ID of the storeman group to check
* @return curveID ID of the elliptic curve used by the storeman group
* @return PK Public key of the storeman group
* Requirements:
* - Storeman group must be in unregistered status
*/
function acquireUnregisteredSmgInfo(bytes32 smgID)
internal
view
returns (uint curveID, bytes memory PK)
{
uint8 status;
(,status,,,,curveID,,PK,,,) = storageData.smgAdminProxy.getStoremanGroupConfig(smgID);
require(status == uint8(GroupStatus.unregistered), "PK is not unregistered");
}
/**
* @notice Converts a bytes array to bytes32 starting from a specified offset
* @dev This function is used for extracting bytes32 values from a bytes array
* @param b The bytes array to convert
* @param offset The starting offset in the array
* @return result The converted bytes32 value
*/
function bytesToBytes32(bytes memory b, uint offset) internal pure returns (bytes32 result) {
assembly {
result := mload(add(add(b, offset), 32))
}
}
/**
* @notice Verifies a signature using the provided parameters
* @dev This function verifies a signature using the storeman group's public key and the signature components
* @param curveID ID of the elliptic curve used for verification
* @param message The message that was signed
* @param PK The public key of the signer
* @param r First component of the signature
* @param s Second component of the signature
* Requirements:
* - Signature must be valid according to the signature verifier contract
*/
function verifySignature(uint curveID, bytes32 message, bytes memory PK, bytes memory r, bytes32 s) internal {
bytes32 PKx = bytesToBytes32(PK, 0);
bytes32 PKy = bytesToBytes32(PK, 32);
bytes32 Rx = bytesToBytes32(r, 0);
bytes32 Ry = bytesToBytes32(r, 32);
require(storageData.sigVerifier.verify(curveID, s, PKx, PKy, Rx, Ry, message), "Signature verification failed");
}
/**
* @notice Gets the address of the storeman group fee proxy
* @dev This function returns the fee proxy address, falling back to the owner if not set
* @return The address of the fee proxy or owner
*/
function getSmgFeeProxy() internal view returns (address) {
address smgFeeProxy = storageData.smgFeeProxy;
return (smgFeeProxy == address(0)) ? owner : smgFeeProxy;
}
//*********************************************************************************************
//*********************************************************************************************
// NFT
/**
* @notice Implements the ERC721 token receiver interface
* @dev This function allows the contract to receive ERC721 tokens
* address - operator The address which called safeTransferFrom function
* address - from The address which previously owned the token
* uint256 - tokenId The token identifier
* bytes - data Additional data with no specified format
* @return The function selector of onERC721Received
*/
function onERC721Received(address, address, uint256, bytes memory)
public
pure
returns(bytes4)
{
return this.onERC721Received.selector;
}
/**
* @notice Implements the ERC1155 token receiver interface for single token transfers
* @dev This function allows the contract to receive ERC1155 tokens
* address - operator The address which called safeTransferFrom function
* address - from The address which previously owned the token
* uint256 - id The token identifier
* uint256 - value The amount of tokens being transferred
* bytes - data Additional data with no specified format
* @return The function selector of onERC1155Received
*/
function onERC1155Received(address, address, uint256, uint256, bytes memory)
public
pure
returns (bytes4)
{
return this.onERC1155Received.selector;
}
/**
* @notice Implements the ERC1155 token receiver interface for batch token transfers
* @dev This function allows the contract to receive multiple ERC1155 tokens in a single transaction
* address - operator The address which called safeBatchTransferFrom function
* address - from The address which previously owned the tokens
* uint256[] - ids Array of token identifiers
* uint256[] - values Array of token amounts
* bytes - data Additional data with no specified format
* @return The function selector of onERC1155BatchReceived
*/
function onERC1155BatchReceived(address, address, uint256[] calldata, uint256[] calldata, bytes calldata)
public
pure
returns (bytes4)
{
return this.onERC1155BatchReceived.selector;
}
/**
* @notice Initiates a cross-chain NFT transfer by locking original NFTs
* @dev This function handles the initial step of cross-chain NFT transfer where original NFTs are locked
* @param smgID ID of the storeman group handling the transfer
* @param tokenPairID ID of the token pair for cross-chain transfer
* @param tokenIDs Array of NFT token IDs to transfer
* @param tokenValues Array of token values (amounts) for each NFT
* @param userAccount Account information for receiving NFTs on the destination chain
* Requirements:
* - Contract must not be halted
* - Storeman group must be ready
* - Number of tokens must be between 1 and maxBatchSize
* - Length of tokenIDs and tokenValues must match
*/
function userLockNFT(bytes32 smgID, uint tokenPairID, uint[] memory tokenIDs, uint[] memory tokenValues, bytes memory userAccount)
public
payable
virtual
notHalted
nonReentrant
onlyReadySmg(smgID)
{
require(tokenIDs.length > 0 && tokenIDs.length <= getMaxBatchSize(), "Invalid length");
require(tokenIDs.length == tokenValues.length, "Length mismatch");
NFTLibV1.RapidityUserLockNFTParams memory params = NFTLibV1.RapidityUserLockNFTParams({
smgID: smgID,
tokenPairID: tokenPairID,
tokenIDs: tokenIDs,
tokenValues: tokenValues,
currentChainID: currentChainID,
tokenPairContractFee: mapTokenPairContractFee[tokenPairID],
etherTransferGasLimit: getEtherTransferGasLimit(),
destUserAccount: userAccount,
smgFeeProxy: getSmgFeeProxy()
});
NFTLibV1.userLockNFT(storageData, params);
}
/**
* @notice Initiates a cross-chain NFT transfer by burning WRC721 tokens
* @dev This function handles the initial step of cross-chain NFT transfer where WRC721 tokens are burned
* @param smgID ID of the storeman group handling the transfer
* @param tokenPairID ID of the token pair for cross-chain transfer
* @param tokenIDs Array of NFT token IDs to transfer
* @param tokenValues Array of token values (amounts) for each NFT
* @param tokenAccount Address of the token contract
* @param userAccount Account information for receiving NFTs on the destination chain
* Requirements:
* - Contract must not be halted
* - Storeman group must be ready
* - Number of tokens must be between 1 and maxBatchSize
* - Length of tokenIDs and tokenValues must match
*/
function userBurnNFT(bytes32 smgID, uint tokenPairID, uint[] memory tokenIDs, uint[] memory tokenValues, address tokenAccount, bytes memory userAccount)
public
payable
notHalted
nonReentrant
onlyReadySmg(smgID)
{
require(tokenIDs.length > 0 && tokenIDs.length <= getMaxBatchSize(), "Invalid length");
require(tokenIDs.length == tokenValues.length, "Length mismatch");
NFTLibV1.RapidityUserBurnNFTParams memory params = NFTLibV1.RapidityUserBurnNFTParams({
smgID: smgID,
tokenPairID: tokenPairID,
tokenIDs: tokenIDs,
tokenValues: tokenValues,
currentChainID: currentChainID,
tokenPairContractFee: mapTokenPairContractFee[tokenPairID],
etherTransferGasLimit: getEtherTransferGasLimit(),
srcTokenAccount: tokenAccount,
destUserAccount: userAccount,
smgFeeProxy: getSmgFeeProxy()
});
NFTLibV1.userBurnNFT(storageData, params);
}
/**
* @notice Mints WRC721 tokens for cross-chain NFT transfer
* @dev This function is called by the storeman group to mint WRC721 tokens after receiving original NFTs
* @param uniqueID Unique identifier for the cross-chain transaction
* @param smgID ID of the storeman group handling the transfer
* @param tokenPairID ID of the token pair for cross-chain transfer
* @param tokenIDs Array of NFT token IDs to mint
* @param tokenValues Array of token values (amounts) for each NFT
* @param extData Additional data for the transfer
* @param tokenAccount Address of the token contract
* @param userAccount Address of the user to receive the minted NFTs
* @param r First part of the signature
* @param s Second part of the signature
* Requirements:
* - Contract must not be halted
* - Storeman group must be ready and valid
* - Signature must be valid
* - Transaction must not be already processed
*/
function smgMintNFT(bytes32 uniqueID, bytes32 smgID, uint tokenPairID, uint[] memory tokenIDs, uint[] memory tokenValues, bytes memory extData, address tokenAccount, address userAccount, bytes memory r, bytes32 s)
public
notHalted
{
uint curveID;
bytes memory PK;
(curveID, PK) = acquireReadySmgInfo(smgID);
NFTLibV1.RapiditySmgMintNFTParams memory params = NFTLibV1.RapiditySmgMintNFTParams({
uniqueID: uniqueID,
smgID: smgID,
tokenPairID: tokenPairID,
tokenIDs: tokenIDs,
tokenValues: tokenValues,
extData: extData,
destTokenAccount: tokenAccount,
destUserAccount: userAccount
});
NFTLibV1.smgMintNFT(storageData, params);
bytes32 mHash = hashFunc(abi.encode(currentChainID, uniqueID, tokenPairID, tokenIDs, tokenValues, extData, tokenAccount, userAccount));
verifySignature(curveID, mHash, PK, r, s);
}
/**
* @notice Releases original NFTs in exchange for WRC721 tokens on Wanchain
* @dev This function is called by the storeman group to release original NFTs after receiving WRC721 tokens
* @param uniqueID Unique identifier for the cross-chain transaction
* @param smgID ID of the storeman group handling the transfer
* @param tokenPairID ID of the token pair for cross-chain transfer
* @param tokenIDs Array of NFT token IDs to release
* @param tokenValues Array of token values (amounts) for each NFT
* @param tokenAccount Address of the token contract
* @param userAccount Address of the user to receive the original NFTs
* @param r First part of the signature
* @param s Second part of the signature
* Requirements:
* - Contract must not be halted
* - Storeman group must be ready and valid
* - Signature must be valid
* - Transaction must not be already processed
*/
function smgReleaseNFT(bytes32 uniqueID, bytes32 smgID, uint tokenPairID, uint[] memory tokenIDs, uint[] memory tokenValues, address tokenAccount, address userAccount, bytes memory r, bytes32 s)
public
notHalted
{
uint curveID;
bytes memory PK;
(curveID, PK) = acquireReadySmgInfo(smgID);
NFTLibV1.RapiditySmgReleaseNFTParams memory params = NFTLibV1.RapiditySmgReleaseNFTParams({
uniqueID: uniqueID,
smgID: smgID,
tokenPairID: tokenPairID,
tokenIDs: tokenIDs,
tokenValues: tokenValues,
destTokenAccount: tokenAccount,
destUserAccount: userAccount
});
NFTLibV1.smgReleaseNFT(storageData, params);
bytes32 mHash = hashFunc(abi.encode(currentChainID, uniqueID, tokenPairID, tokenIDs, tokenValues, tokenAccount, userAccount));
verifySignature(curveID, mHash, PK, r, s);
}
/**
* @notice Sets the maximum batch size for NFT transfers
* @dev This function allows admin to set the maximum number of NFTs that can be transferred in a single transaction
* @param _maxBatchSize The new maximum batch size
* Requirements:
* - Caller must be admin
*/
function setMaxBatchSize(uint _maxBatchSize)
external
virtual
onlyAdmin
{
maxBatchSize = _maxBatchSize;
}
/**
* @notice Gets the maximum batch size for NFT transfers
* @dev This function returns the maximum number of NFTs that can be transferred in a single transaction
* @return The maximum batch size (defaults to 20 if not set)
*/
function getMaxBatchSize()
public
view
returns (uint)
{
if(maxBatchSize == 0) {
return 20;
}
return maxBatchSize;
}
/**
* @notice Gets the batch fee for NFT transfers
* @dev This function calculates the fee for transferring a batch of NFTs
* @param tokenPairID ID of the token pair
* @param batchLength Number of NFTs in the batch
* @return The calculated batch fee
*/
function getBatchFee(uint tokenPairID, uint batchLength)
external
view
returns (uint)
{
uint contractFee;
(, contractFee) = NFTLibV1.getTokenScAddrAndContractFee(storageData, tokenPairID, mapTokenPairContractFee[tokenPairID], currentChainID, batchLength);
return contractFee;
}
/**
* @notice Sets the gas limit for ether transfers
* @dev This function allows admin to set the gas limit used for ether transfers
* @param _etherTransferGasLimit The new gas limit
* Requirements:
* - Caller must be admin
*/
function setEtherTransferGasLimit(uint _etherTransferGasLimit)
external
virtual
onlyAdmin
{
etherTransferGasLimit = _etherTransferGasLimit;
}
/**
* @notice Gets the gas limit for ether transfers
* @dev This function returns the gas limit used for ether transfers
* @return The gas limit (defaults to 2300 if not set)
*/
function getEtherTransferGasLimit()
public
view
returns (uint)
{
if(etherTransferGasLimit == 0) {
return 2300;
}
return etherTransferGasLimit;
}
/**
* @notice Configures operator status for an address
* @dev This function allows admin to enable or disable operator privileges for an address
* @param _operator The address to configure
* @param enabled Whether to enable or disable operator privileges
* Requirements:
* - Caller must be admin
*/
function configOperator(address _operator, bool enabled) external onlyAdmin {
isOperator[_operator] = enabled;
emit ConfigOperator(_operator, enabled);
}
/**
* @notice Configures admin status for an address
* @dev This function allows owner to enable or disable admin privileges for an address
* @param _admin The address to configure
* @param enabled Whether to enable or disable admin privileges
* Requirements:
* - Caller must be owner
*/
function configAdmin(address _admin, bool enabled) external onlyOwner {
isAdmin[_admin] = enabled;
emit ConfigAdmin(_admin, enabled);
}
}// SPDX-License-Identifier: MIT
/*
Copyright 2023 Wanchain Foundation.
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
*/
// _ _ _
// __ ____ _ _ __ ___| |__ __ _(_)_ __ __| | _____ __
// \ \ /\ / / _` | '_ \ / __| '_ \ / _` | | '_ \@/ _` |/ _ \ \ / /
// \ V V / (_| | | | | (__| | | | (_| | | | | | (_| | __/\ V /
// \_/\_/ \__,_|_| |_|\___|_| |_|\__,_|_|_| |_|\__,_|\___| \_/
//
//
pragma solidity 0.8.18;
import "./CrossDelegateV4.sol";
/**
* @title CrossDelegateV5
* @dev Enhanced version of CrossDelegateV4 that adds NFT cross-chain ID management functionality
* This contract extends CrossDelegateV4 to provide:
* - NFT collection registration tracking
* - Cross-chain ID mapping for NFTs
* - Batch registration capabilities
*/
contract CrossDelegateV5 is CrossDelegateV4 {
/**
* @notice Mapping from NFT collection address to registration count
* @dev Tracks the number of NFTs registered for cross-chain operations from each collection
*/
mapping(address => uint256) public nftRegisterCount;
/**
* @notice Mapping from NFT collection and token ID to cross-chain ID
* @dev Stores the cross-chain ID assigned to each NFT
* uint256 - collection The address of the NFT collection
* uint256 - tokenId The ID of the NFT within the collection
* @return The assigned cross-chain ID
*/
mapping(address => mapping(uint256 => uint256)) public crossId;
/**
* @notice Mapping from NFT collection and cross-chain ID to NFT token ID
* @dev Stores the original NFT token ID for each cross-chain ID
* uint256 - collection The address of the NFT collection
* uint256 - crossId The cross-chain ID
* @return The original NFT token ID
*/
mapping(address => mapping(uint256 => uint256)) public crossIdToNftBaseInfo;
/**
* @notice Emitted when a new NFT is registered for cross-chain operations
* @param collection The address of the NFT collection
* @param tokenId The ID of the NFT within the collection
* @param crossId The assigned cross-chain ID
*/
event RegisterNftCrossId(address indexed collection, uint256 indexed tokenId, uint256 indexed crossId);
/**
* @notice Initiates a cross-chain NFT transfer by locking original NFTs
* @dev Overrides the parent function to add cross-chain ID registration
* @param smgID ID of the storeman group handling the transfer
* @param tokenPairID ID of the token pair for cross-chain transfer
* @param tokenIDs Array of NFT token IDs to transfer
* @param tokenValues Array of token values (amounts) for each NFT
* @param userAccount Account information for receiving NFTs on the destination chain
*/
function userLockNFT(bytes32 smgID, uint tokenPairID, uint[] memory tokenIDs, uint[] memory tokenValues, bytes memory userAccount)
public
payable
override
{
super.userLockNFT(smgID, tokenPairID, tokenIDs, tokenValues, userAccount);
address tokenScAddr = localTokenAddress(tokenPairID);
uint count = tokenIDs.length;
for (uint i = 0; i < count; i++) {
registerNftCrossId(tokenScAddr, tokenIDs[i]);
}
}
/**
* @notice Registers a new NFT for cross-chain operations
* @dev Assigns a unique cross-chain ID to an NFT if not already registered
* @param collection The address of the NFT collection
* @param tokenId The ID of the NFT within the collection
* @return The assigned cross-chain ID
*/
function registerNftCrossId(address collection, uint tokenId) internal returns (uint256) {
if (crossId[collection][tokenId] > 0) {
return crossId[collection][tokenId];
} else {
nftRegisterCount[collection] += 1;
crossId[collection][tokenId] = nftRegisterCount[collection];
crossIdToNftBaseInfo[collection][nftRegisterCount[collection]] = tokenId;
emit RegisterNftCrossId(collection, tokenId, nftRegisterCount[collection]);
return nftRegisterCount[collection];
}
}
/**
* @notice Registers multiple NFTs for cross-chain operations in a single transaction
* @dev Allows admin to register multiple NFTs from different collections at once
* @param collection Array of NFT collection addresses
* @param tokenIds Array of NFT token IDs
* Requirements:
* - Caller must be admin
* - Length of collection array must match length of tokenIds array
*/
function batchRegisterNftCrossId(address[] memory collection, uint256[] memory tokenIds) external onlyAdmin {
require(collection.length == tokenIds.length, "CrossDelegateV5: collection length not equal to tokenIds length");
for (uint256 i = 0; i < tokenIds.length; i++) {
registerNftCrossId(collection[i], tokenIds[i]);
}
}
/**
* @notice Retrieves the local token address for a given token pair
* @dev Determines the correct token address based on the current chain ID
* @param tokenPairID ID of the token pair
* @return The address of the token contract on the current chain
* Requirements:
* - Token pair must exist
* - Current chain must be either source or destination chain
*/
function localTokenAddress(uint tokenPairID)
public
view
returns (address)
{
ITokenManager tokenManager = storageData.tokenManager;
uint fromChainID;
uint toChainID;
bytes memory fromTokenAccount;
bytes memory toTokenAccount;
(fromChainID,fromTokenAccount,toChainID,toTokenAccount) = tokenManager.getTokenPairInfo(tokenPairID);
require(fromChainID != 0, "Token does not exist");
address tokenScAddr;
if (currentChainID == fromChainID) {
tokenScAddr = CrossTypes.bytesToAddress(fromTokenAccount);
} else if (currentChainID == toChainID) {
tokenScAddr = CrossTypes.bytesToAddress(toTokenAccount);
} else {
require(false, "Invalid token pair");
}
return tokenScAddr;
}
}// SPDX-License-Identifier: MIT
/*
Copyright 2023 Wanchain Foundation.
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
*/
// _ _ _
// __ ____ _ _ __ ___| |__ __ _(_)_ __ __| | _____ __
// \ \ /\ / / _` | '_ \ / __| '_ \ / _` | | '_ \@/ _` |/ _ \ \ / /
// \ V V / (_| | | | | (__| | | | (_| | | | | | (_| | __/\ V /
// \_/\_/ \__,_|_| |_|\___|_| |_|\__,_|_|_| |_|\__,_|\___| \_/
//
//
pragma solidity 0.8.18;
import "./CrossDelegateV4.sol";
/**
* @title CrossDelegateXinFin
* @dev Cross-chain delegate contract specifically designed for XinFin network
* This contract extends CrossDelegateV4 and implements XRC721 and XRC1155 token receiver interfaces
* to handle cross-chain token transfers on the XinFin network
*/
contract CrossDelegateXinFin is CrossDelegateV4 {
/**
* @dev Implementation of the XRC721 token receiver interface
* address - operator The address which called `safeTransferFrom` function
* address - from The address which previously owned the token
* uint256 - tokenId The identifier of the token being transferred
* bytes - data Additional data with no specified format
* @return bytes4 The function selector of this function
*/
function onXRC721Received(address, address, uint256, bytes calldata)
external
pure
returns (bytes4)
{
return this.onXRC721Received.selector;
}
/**
* @dev Implementation of the XRC1155 token receiver interface for single token transfers
* address - operator The address which initiated the transfer
* address - from The address which previously owned the token
* uint256 - id The identifier of the token being transferred
* uint256 - value The amount of tokens being transferred
* bytes - data Additional data with no specified format
* @return bytes4 The function selector of this function
*/
function onXRC1155Received(address, address, uint256, uint256, bytes calldata)
external
pure
returns(bytes4)
{
return this.onXRC1155Received.selector;
}
/**
* @dev Implementation of the XRC1155 token receiver interface for batch token transfers
* address - operator The address which initiated the transfer
* address - from The address which previously owned the tokens
* uint256[] - ids Array of token identifiers being transferred
* uint256[] - values Array of amounts of tokens being transferred
* bytes - data Additional data with no specified format
* @return bytes4 The function selector of this function
*/
function onXRC1155BatchReceived(address, address, uint256[] calldata, uint256[] calldata, bytes calldata)
external
pure
returns(bytes4)
{
return this.onXRC1155BatchReceived.selector;
}
}// SPDX-License-Identifier: MIT
/*
Copyright 2023 Wanchain Foundation.
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
*/
// _ _ _
// __ ____ _ _ __ ___| |__ __ _(_)_ __ __| | _____ __
// \ \ /\ / / _` | '_ \ / __| '_ \ / _` | | '_ \@/ _` |/ _ \ \ / /
// \ V V / (_| | | | | (__| | | | (_| | | | | | (_| | __/\ V /
// \_/\_/ \__,_|_| |_|\___|_| |_|\__,_|_|_| |_|\__,_|\___| \_/
//
//
pragma solidity 0.8.18;
/**
* Math operations with safety checks
*/
import "../components/Proxy.sol";
import "../components/Halt.sol";
import "../components/ReentrancyGuard.sol";
import "./CrossStorage.sol";
/**
* @title CrossProxy
* @dev Proxy contract for cross-chain functionality that allows for implementation upgrades
* This contract inherits from:
* - CrossStorage: For storing cross-chain related data
* - ReentrancyGuard: To prevent reentrancy attacks
* - Halt: To provide emergency stop functionality
* - Proxy: To enable implementation upgrades
*/
contract CrossProxy is CrossStorage, ReentrancyGuard, Halt, Proxy {
/**
* @dev Updates the implementation address of the CrossDelegate contract
* @param impl The address of the new CrossDelegate contract implementation
* Requirements:
* - Caller must be the owner
* - New implementation address cannot be zero
* - New implementation address must be different from current implementation
* Emits:
* - Upgraded event with the new implementation address
*/
function upgradeTo(address impl) public onlyOwner {
require(impl != address(0), "Cannot upgrade to invalid address");
require(impl != _implementation, "Cannot upgrade to the same implementation");
_implementation = impl;
emit Upgraded(impl);
}
}// SPDX-License-Identifier: MIT
/*
Copyright 2023 Wanchain Foundation.
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
*/
// _ _ _
// __ ____ _ _ __ ___| |__ __ _(_)_ __ __| | _____ __
// \ \ /\ / / _` | '_ \ / __| '_ \ / _` | | '_ \@/ _` |/ _ \ \ / /
// \ V V / (_| | | | | (__| | | | (_| | | | | | (_| | __/\ V /
// \_/\_/ \__,_|_| |_|\___|_| |_|\__,_|_|_| |_|\__,_|\___| \_/
//
//
pragma solidity ^0.8.18;
import "../components/BasicStorage.sol";
import "./lib/CrossTypes.sol";
import "./lib/HTLCTxLib.sol";
import "./lib/RapidityTxLib.sol";
/**
* @title CrossStorage
* @dev Storage contract for cross-chain functionality that manages cross-chain related data
* This contract inherits from BasicStorage and provides storage for:
* - HTLC (Hash Time-Locked Contract) transactions
* - Rapidity transactions
* - Cross-chain types and data
*/
contract CrossStorage is BasicStorage {
using HTLCTxLib for HTLCTxLib.Data;
using RapidityTxLib for RapidityTxLib.Data;
/************************************************************
**
** VARIABLES
**
************************************************************/
/**
* @dev Internal storage for cross-chain related data
*/
CrossTypes.Data internal storageData;
/**
* @notice Time period for which assets are locked in HTLC transactions
* @dev Default value is 36 hours (3600*36 seconds)
*/
uint public lockedTime = uint(3600*36);
/**
* @notice Timeout period for storeman group fee receiver address changes
* @dev Since storeman group admin receiver address may be changed, system ensures:
* - New address becomes valid after this timeout
* - Old address becomes invalid after this timeout
* Default value is 10 minutes (10*60 seconds)
*/
uint public smgFeeReceiverTimeout = uint(10*60);
/**
* @notice Enumeration of possible states for a storeman group
* @dev States:
* - none: Initial state
* - initial: Group has been initialized
* - curveSeted: Curve parameters have been set
* - failed: Group setup has failed
* - selected: Group has been selected
* - ready: Group is ready for operations
* - unregistered: Group has been unregistered
* - dismissed: Group has been dismissed
*/
enum GroupStatus { none, initial, curveSeted, failed, selected, ready, unregistered, dismissed }
}// SPDX-License-Identifier: MIT
/*
Copyright 2023 Wanchain Foundation.
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
*/
// _ _ _
// __ ____ _ _ __ ___| |__ __ _(_)_ __ __| | _____ __
// \ \ /\ / / _` | '_ \ / __| '_ \ / _` | | '_ \@/ _` |/ _ \ \ / /
// \ V V / (_| | | | | (__| | | | (_| | | | | | (_| | __/\ V /
// \_/\_/ \__,_|_| |_|\___|_| |_|\__,_|_|_| |_|\__,_|\___| \_/
//
//
pragma solidity ^0.8.18;
import "../components/Proxy.sol";
import "../components/Halt.sol";
import "../components/ReentrancyGuard.sol";
import "./CrossStorage.sol";
/**
* @title CrossStorageV2
* @dev Enhanced version of CrossStorage that adds chain ID and fee management functionality
* This contract inherits from:
* - CrossStorage: Base storage functionality
* - ReentrancyGuard: To prevent reentrancy attacks
* - Halt: To provide emergency stop functionality
* - Proxy: To enable implementation upgrades
*/
contract CrossStorageV2 is CrossStorage, ReentrancyGuard, Halt, Proxy {
/************************************************************
**
** VARIABLES
**
************************************************************/
/** STATE VARIABLES **/
/**
* @notice The chain ID of the current network
* @dev Used to identify the source chain in cross-chain operations
*/
uint256 public currentChainID;
/**
* @notice The address of the contract administrator
* @dev Has special privileges for managing the contract
*/
address public admin;
/** STRUCTURES **/
/**
* @notice Parameters for setting cross-chain fees
* @dev Used when configuring fees for specific chain pairs
* @param srcChainID Source chain identifier
* @param destChainID Destination chain identifier
* @param contractFee Fee charged by the contract
* @param agentFee Fee charged by the agent
*/
struct SetFeesParam {
uint256 srcChainID;
uint256 destChainID;
uint256 contractFee;
uint256 agentFee;
}
/**
* @notice Parameters for retrieving cross-chain fees
* @dev Used when querying fees for specific chain pairs
* @param srcChainID Source chain identifier
* @param destChainID Destination chain identifier
*/
struct GetFeesParam {
uint256 srcChainID;
uint256 destChainID;
}
/**
* @notice Return structure for fee queries
* @dev Contains the fee information for a specific chain pair
* @param contractFee Fee charged by the contract
* @param agentFee Fee charged by the agent
*/
struct GetFeesReturn {
uint256 contractFee;
uint256 agentFee;
}
}// SPDX-License-Identifier: MIT
/*
Copyright 2023 Wanchain Foundation.
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
*/
// _ _ _
// __ ____ _ _ __ ___| |__ __ _(_)_ __ __| | _____ __
// \ \ /\ / / _` | '_ \ / __| '_ \ / _` | | '_ \@/ _` |/ _ \ \ / /
// \ V V / (_| | | | | (__| | | | (_| | | | | | (_| | __/\ V /
// \_/\_/ \__,_|_| |_|\___|_| |_|\__,_|_|_| |_|\__,_|\___| \_/
//
//
pragma solidity ^0.8.18;
import "./CrossStorageV2.sol";
/**
* @title CrossStorageV3
* @dev Enhanced version of CrossStorageV2 that adds token pair fee management functionality
* This contract inherits from CrossStorageV2 and provides:
* - Mapping for token pair contract fees
* - Structure for setting token pair fees
*/
contract CrossStorageV3 is CrossStorageV2 {
/************************************************************
**
** VARIABLES
**
************************************************************/
/** STATE VARIABLES **/
/**
* @notice Mapping from token pair ID to contract fee
* @dev Used to store and retrieve fees for specific token pairs
* uint256 - tokenPairID Unique identifier for a token pair
* uint256 - contractFee The fee charged by the contract for this token pair
*/
mapping(uint256 => uint256) mapTokenPairContractFee;
/**
* @notice Parameters for setting token pair fees
* @dev Used when configuring fees for specific token pairs
* @param tokenPairID Unique identifier for a token pair
* @param contractFee The fee to be charged by the contract for this token pair
*/
struct SetTokenPairFeesParam {
uint256 tokenPairID;
uint256 contractFee;
}
}// SPDX-License-Identifier: MIT
/*
Copyright 2023 Wanchain Foundation.
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
*/
// _ _ _
// __ ____ _ _ __ ___| |__ __ _(_)_ __ __| | _____ __
// \ \ /\ / / _` | '_ \ / __| '_ \ / _` | | '_ \@/ _` |/ _ \ \ / /
// \ V V / (_| | | | | (__| | | | (_| | | | | | (_| | __/\ V /
// \_/\_/ \__,_|_| |_|\___|_| |_|\__,_|_|_| |_|\__,_|\___| \_/
//
//
pragma solidity ^0.8.18;
import "./CrossStorageV3.sol";
/**
* @title CrossStorageV4
* @dev Enhanced version of CrossStorageV3 that adds batch processing, gas limits, and role-based access control
* This contract inherits from CrossStorageV3 and provides:
* - Maximum batch size configuration
* - Gas limit settings for ether transfers
* - Hash function type selection
* - Role-based access control for admin and operator roles
*/
contract CrossStorageV4 is CrossStorageV3 {
/************************************************************
**
** VARIABLES
**
************************************************************/
/**
* @notice Maximum number of transactions that can be processed in a single batch
* @dev Used to limit the size of batch operations for gas optimization
*/
uint internal maxBatchSize;
/**
* @notice Gas limit for ether transfer operations
* @dev Used to estimate gas costs for cross-chain ether transfers
*/
uint internal etherTransferGasLimit;
/**
* @notice Type of hash function to be used
* @dev 0: sha256, 1: keccak256
* Used for generating transaction hashes in cross-chain operations
*/
uint public hashType; // 0: sha256, 1: keccak256
/**
* @notice Mapping of addresses to admin role status
* @dev Used for role-based access control of administrative functions
*/
mapping(address => bool) public isAdmin;
/**
* @notice Mapping of addresses to operator role status
* @dev Used for role-based access control of operational functions
*/
mapping(address => bool) public isOperator;
}// SPDX-License-Identifier: MIT
/*
Copyright 2023 Wanchain Foundation.
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
*/
// _ _ _
// __ ____ _ _ __ ___| |__ __ _(_)_ __ __| | _____ __
// \ \ /\ / / _` | '_ \ / __| '_ \ / _` | | '_ \@/ _` |/ _ \ \ / /
// \ V V / (_| | | | | (__| | | | (_| | | | | | (_| | __/\ V /
// \_/\_/ \__,_|_| |_|\___|_| |_|\__,_|_|_| |_|\__,_|\___| \_/
//
//
pragma solidity ^0.8.18;
import "../../interfaces/IRC20Protocol.sol";
import "../../interfaces/IQuota.sol";
import "../../interfaces/IStoremanGroup.sol";
import "../../interfaces/ITokenManager.sol";
import "../../interfaces/ISignatureVerifier.sol";
import "./HTLCTxLib.sol";
import "./RapidityTxLib.sol";
/**
* @title CrossTypes
* @dev Library containing common types and utilities for cross-chain operations
* This library provides:
* - Data structures for cross-chain transactions
* - Utility functions for address and token operations
*/
library CrossTypes {
using SafeMath for uint;
/**
* @notice Main data structure for cross-chain operations
* @dev Contains all necessary data and mappings for cross-chain functionality
*/
struct Data {
/**
* @notice HTLC transaction data storage
* @dev Stores information about Hash Time-Locked Contract transactions
*/
HTLCTxLib.Data htlcTxData;
/**
* @notice Rapidity transaction data storage
* @dev Stores information about rapid cross-chain transactions
*/
RapidityTxLib.Data rapidityTxData;
/**
* @notice Quota management interface for storeman group
* @dev Handles quota allocation and management for storeman groups
*/
IQuota quota;
/**
* @notice Token management interface
* @dev Handles token pair management and cross-chain token operations
*/
ITokenManager tokenManager;
/**
* @notice Storeman group admin interface
* @dev Manages storeman group administration and configuration
*/
IStoremanGroup smgAdminProxy;
/**
* @notice Storeman group fee admin address
* @dev Address responsible for managing storeman group fees
*/
address smgFeeProxy;
/**
* @notice Signature verification interface
* @dev Handles signature verification for cross-chain transactions
*/
ISignatureVerifier sigVerifier;
/**
* @notice Mapping of storeman group fees
* @dev Maps storeman group IDs to their respective fees
*/
mapping(bytes32 => uint) mapStoremanFee;
/**
* @notice Mapping of contract fees between chains
* @dev Maps source chain ID and destination chain ID to contract fees
*/
mapping(uint => mapping(uint =>uint)) mapContractFee;
/**
* @notice Mapping of agent fees between chains
* @dev Maps source chain ID and destination chain ID to agent fees
*/
mapping(uint => mapping(uint =>uint)) mapAgentFee;
}
/**
* @notice Converts bytes to address
* @dev Uses assembly to efficiently convert bytes to address
* @param b Bytes to convert
* @return addr The converted address
*/
function bytesToAddress(bytes memory b) internal pure returns (address addr) {
assembly {
addr := mload(add(b,20))
}
}
/**
* @notice Transfers tokens from the contract to a specified address
* @dev Verifies the transfer was successful by checking balance changes
* @param tokenScAddr Address of the token contract
* @param to Address to receive the tokens
* @param value Amount of tokens to transfer
* @return bool True if transfer was successful
* Requirements:
* - Transfer must succeed
* - Balance change must match the transfer amount
*/
function transfer(address tokenScAddr, address to, uint value)
internal
returns(bool)
{
uint beforeBalance;
uint afterBalance;
IRC20Protocol token = IRC20Protocol(tokenScAddr);
beforeBalance = token.balanceOf(to);
(bool success,) = tokenScAddr.call(abi.encodeWithSelector(token.transfer.selector, to, value));
require(success, "transfer failed");
afterBalance = token.balanceOf(to);
return afterBalance == beforeBalance.add(value);
}
/**
* @notice Transfers tokens from one address to another
* @dev Verifies the transfer was successful by checking balance changes
* @param tokenScAddr Address of the token contract
* @param from Address to transfer tokens from
* @param to Address to receive the tokens
* @param value Amount of tokens to transfer
* @return bool True if transfer was successful
* Requirements:
* - Transfer must succeed
* - Balance change must match the transfer amount
*/
function transferFrom(address tokenScAddr, address from, address to, uint value)
internal
returns(bool)
{
uint beforeBalance;
uint afterBalance;
IRC20Protocol token = IRC20Protocol(tokenScAddr);
beforeBalance = token.balanceOf(to);
(bool success,) = tokenScAddr.call(abi.encodeWithSelector(token.transferFrom.selector, from, to, value));
require(success, "transferFrom failed");
afterBalance = token.balanceOf(to);
return afterBalance == beforeBalance.add(value);
}
}// SPDX-License-Identifier: MIT
pragma solidity ^0.8.1;
/**
* @title EtherTransfer
* @dev Library for safe ether transfer operations
* This library provides a safer alternative to Solidity's native transfer function
* by allowing custom gas limits and better error handling
*/
library EtherTransfer {
/**
* @dev Replacement for Solidity's `transfer`: sends `amount` wei to
* `recipient`, forwarding all available gas and reverting on errors.
*
* https://eips.ethereum.org/EIPS/eip-1884[EIP1884] increases the gas cost
* of certain opcodes, possibly making contracts go over the 2300 gas limit
* imposed by `transfer`, making them unable to receive funds via
* `transfer`. {sendValue} removes this limitation.
*
* https://consensys.net/diligence/blog/2023/09/stop-using-soliditys-transfer-now/[Learn more].
*
* IMPORTANT: because control is transferred to `recipient`, care must be
* taken to not create reentrancy vulnerabilities. Consider using
* {ReentrancyGuard} or the
* https://solidity.readthedocs.io/en/v0.8.0/security-considerations.html#use-the-checks-effects-interactions-pattern[checks-effects-interactions pattern].
*/
function sendValue(address payable recipient, uint256 amount, uint256 gasLimit) internal {
require(address(this).balance >= amount, "EtherTransfer: insufficient balance");
(bool success, ) = recipient.call{value: amount, gas: gasLimit}("");
require(success, "EtherTransfer: unable to send value, recipient may have reverted");
}
}// SPDX-License-Identifier: MIT
/*
Copyright 2023 Wanchain Foundation.
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
*/
// _ _ _
// __ ____ _ _ __ ___| |__ __ _(_)_ __ __| | _____ __
// \ \ /\ / / _` | '_ \ / __| '_ \ / _` | | '_ \@/ _` |/ _ \ \ / /
// \ V V / (_| | | | | (__| | | | (_| | | | | | (_| | __/\ V /
// \_/\_/ \__,_|_| |_|\___|_| |_|\__,_|_|_| |_|\__,_|\___| \_/
//
//
pragma solidity ^0.8.18;
import "@openzeppelin/contracts/utils/math/SafeMath.sol";
/**
* @title HTLCTxLib
* @dev Library for managing Hash Time-Locked Contract (HTLC) transactions
* This library provides functionality for:
* - User and storeman transaction management
* - Debt management between storeman groups
* - Transaction status tracking and verification
*/
library HTLCTxLib {
using SafeMath for uint;
/**
* @notice Enumeration of possible transaction statuses
* @dev Status flow:
* - None: Initial state
* - Locked: Transaction is locked and pending
* - Redeemed: Transaction has been completed
* - Revoked: Transaction has been cancelled
* - AssetLocked: Asset is locked in debt management
* - DebtLocked: Debt is locked in debt management
*/
enum TxStatus {None, Locked, Redeemed, Revoked, AssetLocked, DebtLocked}
/**
* @notice Parameters for user-initiated HTLC transactions
* @dev Used when creating new user transactions
* @param xHash Hash of the HTLC random number
* @param smgID ID of the selected storeman group
* @param tokenPairID ID of the token pair for cross-chain transfer
* @param value Amount of tokens to transfer
* @param lockFee Fee for the lock operation
* @param lockedTime Duration for which the transaction is locked
*/
struct HTLCUserParams {
bytes32 xHash;
bytes32 smgID;
uint tokenPairID;
uint value;
uint lockFee;
uint lockedTime;
}
/**
* @notice Base structure for all HTLC transactions
* @dev Contains common fields for all transaction types
* @param smgID ID of the storeman group
* @param lockedTime Duration for which the transaction is locked
* @param beginLockedTime Timestamp when the transaction was locked
* @param status Current status of the transaction
*/
struct BaseTx {
bytes32 smgID;
uint lockedTime;
uint beginLockedTime;
TxStatus status;
}
/**
* @notice Structure for user-initiated transactions
* @dev Extends BaseTx with user-specific information
* @param baseTx Base transaction information
* @param tokenPairID ID of the token pair
* @param value Amount of tokens
* @param fee Transaction fee
* @param userAccount Address of the user initiating the transaction
*/
struct UserTx {
BaseTx baseTx;
uint tokenPairID;
uint value;
uint fee;
address userAccount;
}
/**
* @notice Structure for storeman-initiated transactions
* @dev Extends BaseTx with storeman-specific information
* @param baseTx Base transaction information
* @param tokenPairID ID of the token pair
* @param value Amount of tokens
* @param userAccount Address of the user to receive tokens
*/
struct SmgTx {
BaseTx baseTx;
uint tokenPairID;
uint value;
address userAccount;
}
/**
* @notice Structure for storeman debt transactions
* @dev Extends BaseTx with debt-specific information
* @param baseTx Base transaction information
* @param srcSmgID ID of the source storeman group
*/
struct DebtTx {
BaseTx baseTx;
bytes32 srcSmgID;
}
/**
* @notice Main data structure for HTLC transactions
* @dev Contains mappings for all transaction types
* @param mapHashXUserTxs Mapping of transaction hashes to user transactions
* @param mapHashXSmgTxs Mapping of transaction hashes to storeman transactions
* @param mapHashXDebtTxs Mapping of transaction hashes to debt transactions
*/
struct Data {
mapping(bytes32 => UserTx) mapHashXUserTxs;
mapping(bytes32 => SmgTx) mapHashXSmgTxs;
mapping(bytes32 => DebtTx) mapHashXDebtTxs;
}
/**
* @notice Adds a new user transaction
* @dev Creates a new user transaction with the provided parameters
* @param self The storage data structure
* @param params Parameters for the new transaction
* Requirements:
* - Transaction must not already exist
*/
function addUserTx(Data storage self, HTLCUserParams memory params)
public
{
UserTx memory userTx = self.mapHashXUserTxs[params.xHash];
// UserTx storage userTx = self.mapHashXUserTxs[params.xHash];
// require(params.value != 0, "Value is invalid");
require(userTx.baseTx.status == TxStatus.None, "User tx exists");
userTx.baseTx.smgID = params.smgID;
userTx.baseTx.lockedTime = params.lockedTime;
userTx.baseTx.beginLockedTime = block.timestamp;
userTx.baseTx.status = TxStatus.Locked;
userTx.tokenPairID = params.tokenPairID;
userTx.value = params.value;
userTx.fee = params.lockFee;
userTx.userAccount = msg.sender;
self.mapHashXUserTxs[params.xHash] = userTx;
}
/**
* @notice Redeems a user transaction
* @dev Used for storeman redeem (outbound) operations
* @param self The storage data structure
* @param x The HTLC random number
* @return xHash The hash of the random number
* Requirements:
* - Transaction must be in Locked status
* - Transaction must not be expired
*/
function redeemUserTx(Data storage self, bytes32 x)
external
returns(bytes32 xHash)
{
xHash = sha256(abi.encodePacked(x));
UserTx storage userTx = self.mapHashXUserTxs[xHash];
require(userTx.baseTx.status == TxStatus.Locked, "Status is not locked");
require(block.timestamp < userTx.baseTx.beginLockedTime.add(userTx.baseTx.lockedTime), "Redeem timeout");
userTx.baseTx.status = TxStatus.Redeemed;
return xHash;
}
/**
* @notice Revokes a user transaction
* @dev Allows cancellation of expired transactions
* @param self The storage data structure
* @param xHash Hash of the HTLC random number
* Requirements:
* - Transaction must be in Locked status
* - Transaction must be expired
*/
function revokeUserTx(Data storage self, bytes32 xHash)
external
{
UserTx storage userTx = self.mapHashXUserTxs[xHash];
require(userTx.baseTx.status == TxStatus.Locked, "Status is not locked");
require(block.timestamp >= userTx.baseTx.beginLockedTime.add(userTx.baseTx.lockedTime), "Revoke is not permitted");
userTx.baseTx.status = TxStatus.Revoked;
}
/**
* @notice Retrieves user transaction information
* @dev Returns all relevant information about a user transaction
* @param self The storage data structure
* @param xHash Hash of the HTLC random number
* @return smgID ID of the storeman group
* @return tokenPairID ID of the token pair
* @return value Amount of tokens
* @return fee Transaction fee
* @return userAccount Address of the user
*/
function getUserTx(Data storage self, bytes32 xHash)
external
view
returns (bytes32, uint, uint, uint, address)
{
UserTx storage userTx = self.mapHashXUserTxs[xHash];
return (userTx.baseTx.smgID, userTx.tokenPairID, userTx.value, userTx.fee, userTx.userAccount);
}
/**
* @notice Adds a new storeman transaction
* @dev Creates a new storeman transaction with the provided parameters
* @param self The storage data structure
* @param xHash Hash of the HTLC random number
* @param smgID ID of the storeman group
* @param tokenPairID ID of the token pair
* @param value Amount of tokens
* @param userAccount Address of the user to receive tokens
* @param lockedTime Duration for which the transaction is locked
* Requirements:
* - Value must be non-zero
* - Transaction must not already exist
*/
function addSmgTx(Data storage self, bytes32 xHash, bytes32 smgID, uint tokenPairID, uint value, address userAccount, uint lockedTime)
external
{
SmgTx memory smgTx = self.mapHashXSmgTxs[xHash];
// SmgTx storage smgTx = self.mapHashXSmgTxs[xHash];
require(value != 0, "Value is invalid");
require(smgTx.baseTx.status == TxStatus.None, "Smg tx exists");
smgTx.baseTx.smgID = smgID;
smgTx.baseTx.status = TxStatus.Locked;
smgTx.baseTx.lockedTime = lockedTime;
smgTx.baseTx.beginLockedTime = block.timestamp;
smgTx.tokenPairID = tokenPairID;
smgTx.value = value;
smgTx.userAccount = userAccount;
self.mapHashXSmgTxs[xHash] = smgTx;
}
/**
* @notice Redeems a storeman transaction
* @dev Used for user redeem (inbound) operations
* @param self The storage data structure
* @param x The HTLC random number
* @return xHash The hash of the random number
* Requirements:
* - Transaction must be in Locked status
* - Transaction must not be expired
*/
function redeemSmgTx(Data storage self, bytes32 x)
external
returns(bytes32 xHash)
{
xHash = sha256(abi.encodePacked(x));
SmgTx storage smgTx = self.mapHashXSmgTxs[xHash];
require(smgTx.baseTx.status == TxStatus.Locked, "Status is not locked");
require(block.timestamp < smgTx.baseTx.beginLockedTime.add(smgTx.baseTx.lockedTime), "Redeem timeout");
smgTx.baseTx.status = TxStatus.Redeemed;
return xHash;
}
/**
* @notice Revokes a storeman transaction
* @dev Allows cancellation of expired transactions
* @param self The storage data structure
* @param xHash Hash of the HTLC random number
* Requirements:
* - Transaction must be in Locked status
* - Transaction must be expired
*/
function revokeSmgTx(Data storage self, bytes32 xHash)
external
{
SmgTx storage smgTx = self.mapHashXSmgTxs[xHash];
require(smgTx.baseTx.status == TxStatus.Locked, "Status is not locked");
require(block.timestamp >= smgTx.baseTx.beginLockedTime.add(smgTx.baseTx.lockedTime), "Revoke is not permitted");
smgTx.baseTx.status = TxStatus.Revoked;
}
/**
* @notice Retrieves storeman transaction information
* @dev Returns all relevant information about a storeman transaction
* @param self The storage data structure
* @param xHash Hash of the HTLC random number
* @return smgID ID of the storeman group
* @return tokenPairID ID of the token pair
* @return value Amount of tokens
* @return userAccount Address of the user to receive tokens
*/
function getSmgTx(Data storage self, bytes32 xHash)
external
view
returns (bytes32, uint, uint, address)
{
SmgTx storage smgTx = self.mapHashXSmgTxs[xHash];
return (smgTx.baseTx.smgID, smgTx.tokenPairID, smgTx.value, smgTx.userAccount);
}
/**
* @notice Adds a new debt transaction
* @dev Creates a new debt transaction between storeman groups
* @param self The storage data structure
* @param xHash Hash of the HTLC random number
* @param srcSmgID ID of the source storeman group
* @param destSmgID ID of the destination storeman group
* @param lockedTime Duration for which the transaction is locked
* @param status Initial status of the transaction
* Requirements:
* - Transaction must not already exist
* - Status must be either Locked or DebtLocked
*/
function addDebtTx(Data storage self, bytes32 xHash, bytes32 srcSmgID, bytes32 destSmgID, uint lockedTime, TxStatus status)
external
{
DebtTx memory debtTx = self.mapHashXDebtTxs[xHash];
// DebtTx storage debtTx = self.mapHashXDebtTxs[xHash];
require(debtTx.baseTx.status == TxStatus.None, "Debt tx exists");
debtTx.baseTx.smgID = destSmgID;
debtTx.baseTx.status = status;//TxStatus.Locked;
debtTx.baseTx.lockedTime = lockedTime;
debtTx.baseTx.beginLockedTime = block.timestamp;
debtTx.srcSmgID = srcSmgID;
self.mapHashXDebtTxs[xHash] = debtTx;
}
/**
* @notice Redeems a debt transaction
* @dev Used to complete debt transfer between storeman groups
* @param self The storage data structure
* @param x The HTLC random number
* @return xHash The hash of the random number
* Requirements:
* - Transaction must be in Locked or DebtLocked status
* - Transaction must not be expired
*/
function redeemDebtTx(Data storage self, bytes32 x, TxStatus status)
external
returns(bytes32 xHash)
{
xHash = sha256(abi.encodePacked(x));
DebtTx storage debtTx = self.mapHashXDebtTxs[xHash];
// require(debtTx.baseTx.status == TxStatus.Locked, "Status is not locked");
require(debtTx.baseTx.status == status, "Status is not locked");
require(block.timestamp < debtTx.baseTx.beginLockedTime.add(debtTx.baseTx.lockedTime), "Redeem timeout");
debtTx.baseTx.status = TxStatus.Redeemed;
return xHash;
}
/**
* @notice Revokes a debt transaction
* @dev Allows cancellation of expired debt transactions
* @param self The storage data structure
* @param xHash Hash of the HTLC random number
* Requirements:
* - Transaction must be in Locked or DebtLocked status
* - Transaction must be expired
*/
function revokeDebtTx(Data storage self, bytes32 xHash, TxStatus status)
external
{
DebtTx storage debtTx = self.mapHashXDebtTxs[xHash];
// require(debtTx.baseTx.status == TxStatus.Locked, "Status is not locked");
require(debtTx.baseTx.status == status, "Status is not locked");
require(block.timestamp >= debtTx.baseTx.beginLockedTime.add(debtTx.baseTx.lockedTime), "Revoke is not permitted");
debtTx.baseTx.status = TxStatus.Revoked;
}
/**
* @notice Retrieves debt transaction information
* @dev Returns all relevant information about a debt transaction
* @param self The storage data structure
* @param xHash Hash of the HTLC random number
* @return smgID ID of the storeman group
* @return srcSmgID ID of the source storeman group
*/
function getDebtTx(Data storage self, bytes32 xHash)
external
view
returns (bytes32, bytes32)
{
DebtTx storage debtTx = self.mapHashXDebtTxs[xHash];
return (debtTx.srcSmgID, debtTx.baseTx.smgID);
}
function getLeftTime(uint endTime) private view returns (uint) {
if (block.timestamp < endTime) {
return endTime.sub(block.timestamp);
}
return 0;
}
/// @notice function for get debt info
/// @param xHash hash of HTLC random number
/// @return leftTime the left lock time
function getLeftLockedTime(Data storage self, bytes32 xHash)
external
view
returns (uint leftTime)
{
UserTx storage userTx = self.mapHashXUserTxs[xHash];
if (userTx.baseTx.status != TxStatus.None) {
return getLeftTime(userTx.baseTx.beginLockedTime.add(userTx.baseTx.lockedTime));
}
SmgTx storage smgTx = self.mapHashXSmgTxs[xHash];
if (smgTx.baseTx.status != TxStatus.None) {
return getLeftTime(smgTx.baseTx.beginLockedTime.add(smgTx.baseTx.lockedTime));
}
DebtTx storage debtTx = self.mapHashXDebtTxs[xHash];
if (debtTx.baseTx.status != TxStatus.None) {
return getLeftTime(debtTx.baseTx.beginLockedTime.add(debtTx.baseTx.lockedTime));
}
require(false, 'invalid xHash');
}
}// SPDX-License-Identifier: MIT
/*
Copyright 2023 Wanchain Foundation.
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
*/
// _ _ _
// __ ____ _ _ __ ___| |__ __ _(_)_ __ __| | _____ __
// \ \ /\ / / _` | '_ \ / __| '_ \ / _` | | '_ \@/ _` |/ _ \ \ / /
// \ V V / (_| | | | | (__| | | | (_| | | | | | (_| | __/\ V /
// \_/\_/ \__,_|_| |_|\___|_| |_|\__,_|_|_| |_|\__,_|\___| \_/
//
//
pragma solidity ^0.8.18;
import "@openzeppelin/contracts/token/ERC1155/IERC1155.sol";
import "@openzeppelin/contracts/token/ERC721/IERC721.sol";
import "./RapidityTxLib.sol";
import "./CrossTypes.sol";
import "../../interfaces/ITokenManager.sol";
import "./EtherTransfer.sol";
/**
* @title NFTLibV1
* @dev Library for managing NFT cross-chain operations
* This library provides functionality for:
* - NFT locking and unlocking
* - NFT minting and burning
* - Cross-chain NFT transfer management
*/
library NFTLibV1 {
using SafeMath for uint;
using RapidityTxLib for RapidityTxLib.Data;
/**
* @notice Enumeration of supported token types for cross-chain operations
* @dev Defines the types of tokens that can be transferred across chains
*/
enum TokenCrossType {ERC20, ERC721, ERC1155}
/**
* @notice Parameters for user-initiated NFT locking operations
* @dev Used when users want to lock their NFTs for cross-chain transfer
* @param smgID ID of the selected storeman group
* @param tokenPairID ID of the token pair for cross-chain transfer
* @param tokenIDs Array of NFT token IDs to be locked
* @param tokenValues Array of NFT token values (for ERC1155)
* @param currentChainID ID of the current blockchain
* @param tokenPairContractFee Fee for the token pair contract
* @param etherTransferGasLimit Gas limit for ether transfers
* @param destUserAccount Destination user account address
* @param smgFeeProxy Address of the proxy for storing storeman group fees
*/
struct RapidityUserLockNFTParams {
bytes32 smgID; /// ID of storeman group which user has selected
uint tokenPairID; /// token pair id on cross chain
uint[] tokenIDs; /// NFT token Ids
uint[] tokenValues; /// NFT token values
uint currentChainID; /// current chain ID
uint tokenPairContractFee; /// fee of token pair
uint etherTransferGasLimit; /// exchange token fee
bytes destUserAccount; /// account of shadow chain, used to receive token
address smgFeeProxy; /// address of the proxy to store fee for storeman group
}
/**
* @notice Parameters for storeman-initiated NFT minting operations
* @dev Used when storeman group mints NFTs on the destination chain
* @param uniqueID Unique identifier for the rapidity transaction
* @param smgID ID of the storeman group
* @param tokenPairID ID of the token pair
* @param tokenIDs Array of NFT token IDs to be minted
* @param tokenValues Array of NFT token values (for ERC1155)
* @param extData Additional data for storeman operations
* @param destTokenAccount Destination token contract address
* @param destUserAccount Destination user account address
*/
struct RapiditySmgMintNFTParams {
bytes32 uniqueID; /// Rapidity random number
bytes32 smgID; /// ID of storeman group which user has selected
uint tokenPairID; /// token pair id on cross chain
uint[] tokenIDs; /// NFT token Ids
uint[] tokenValues; /// NFT token values
bytes extData; /// storeman data
address destTokenAccount; /// shadow token account
address destUserAccount; /// account of shadow chain, used to receive token
}
/**
* @notice Parameters for user-initiated NFT burning operations
* @dev Used when users want to burn their NFTs for cross-chain transfer
* @param smgID ID of the selected storeman group
* @param tokenPairID ID of the token pair
* @param tokenIDs Array of NFT token IDs to be burned
* @param tokenValues Array of NFT token values (for ERC1155)
* @param currentChainID ID of the current blockchain
* @param tokenPairContractFee Fee for the token pair contract
* @param etherTransferGasLimit Gas limit for ether transfers
* @param srcTokenAccount Source token contract address
* @param destUserAccount Destination user account address
* @param smgFeeProxy Address of the proxy for storing storeman group fees
*/
struct RapidityUserBurnNFTParams {
bytes32 smgID; /// ID of storeman group which user has selected
uint tokenPairID; /// token pair id on cross chain
uint[] tokenIDs; /// NFT token Ids
uint[] tokenValues; /// NFT token values
uint currentChainID; /// current chain ID
uint tokenPairContractFee; /// fee of token pair
uint etherTransferGasLimit; /// exchange token fee
address srcTokenAccount; /// shadow token account
bytes destUserAccount; /// account of token destination chain, used to receive token
address smgFeeProxy; /// address of the proxy to store fee for storeman group
}
/**
* @notice Parameters for storeman-initiated NFT release operations
* @dev Used when storeman group releases NFTs on the original chain
* @param uniqueID Unique identifier for the rapidity transaction
* @param smgID ID of the storeman group
* @param tokenPairID ID of the token pair
* @param tokenIDs Array of NFT token IDs to be released
* @param tokenValues Array of NFT token values (for ERC1155)
* @param destTokenAccount Destination token contract address
* @param destUserAccount Destination user account address
*/
struct RapiditySmgReleaseNFTParams {
bytes32 uniqueID; /// Rapidity random number
bytes32 smgID; /// ID of storeman group which user has selected
uint tokenPairID; /// token pair id on cross chain
uint[] tokenIDs; /// NFT token Ids
uint[] tokenValues; /// NFT token values
address destTokenAccount; /// original token/coin account
address destUserAccount; /// account of token original chain, used to receive token
}
/**
* @notice Event emitted when NFTs are locked by a user
* @param smgID ID of the storeman group
* @param tokenPairID ID of the token pair
* @param tokenAccount Address of the token contract
* @param keys Array of event keys
* @param values Array of event values
*/
event UserLockNFT(bytes32 indexed smgID, uint indexed tokenPairID, address indexed tokenAccount, string[] keys, bytes[] values);
/**
* @notice Event emitted when NFTs are burned by a user
* @param smgID ID of the storeman group
* @param tokenPairID ID of the token pair
* @param tokenAccount Address of the token contract
* @param keys Array of event keys
* @param values Array of event values
*/
event UserBurnNFT(bytes32 indexed smgID, uint indexed tokenPairID, address indexed tokenAccount, string[] keys, bytes[] values);
/**
* @notice Event emitted when NFTs are minted by storeman group
* @param uniqueID Unique identifier for the rapidity transaction
* @param smgID ID of the storeman group
* @param tokenPairID ID of the token pair
* @param keys Array of event keys
* @param values Array of event values
*/
event SmgMintNFT(bytes32 indexed uniqueID, bytes32 indexed smgID, uint indexed tokenPairID, string[] keys, bytes[] values);
/**
* @notice Event emitted when NFTs are released by storeman group
* @param uniqueID Unique identifier for the rapidity transaction
* @param smgID ID of the storeman group
* @param tokenPairID ID of the token pair
* @param keys Array of event keys
* @param values Array of event values
*/
event SmgReleaseNFT(bytes32 indexed uniqueID, bytes32 indexed smgID, uint indexed tokenPairID, string[] keys, bytes[] values);
/**
* @notice Gets the token contract address and contract fee for a token pair
* @dev Calculates the appropriate contract fee based on chain IDs and batch size
* @param storageData Cross storage data
* @param tokenPairID ID of the token pair
* @param tokenPairContractFee Fee for the token pair contract
* @param currentChainID ID of the current blockchain
* @param batchLength Length of the batch operation
* @return tokenScAddr Address of the token contract
* @return contractFee Calculated contract fee
* Requirements:
* - Token pair must exist
* - Current chain ID must be valid
*/
function getTokenScAddrAndContractFee(CrossTypes.Data storage storageData, uint tokenPairID, uint tokenPairContractFee, uint currentChainID, uint batchLength)
public
view
returns (address, uint)
{
ITokenManager tokenManager = storageData.tokenManager;
uint fromChainID;
uint toChainID;
bytes memory fromTokenAccount;
bytes memory toTokenAccount;
(fromChainID,fromTokenAccount,toChainID,toTokenAccount) = tokenManager.getTokenPairInfo(tokenPairID);
require(fromChainID != 0, "Token does not exist");
uint contractFee = tokenPairContractFee;
address tokenScAddr;
if (currentChainID == fromChainID) {
if (contractFee == 0) {
contractFee = storageData.mapContractFee[fromChainID][toChainID];
}
if (contractFee == 0) {
contractFee = storageData.mapContractFee[fromChainID][0];
}
tokenScAddr = CrossTypes.bytesToAddress(fromTokenAccount);
} else if (currentChainID == toChainID) {
if (contractFee == 0) {
contractFee = storageData.mapContractFee[toChainID][fromChainID];
}
if (contractFee == 0) {
contractFee = storageData.mapContractFee[toChainID][0];
}
tokenScAddr = CrossTypes.bytesToAddress(toTokenAccount);
} else {
require(false, "Invalid token pair");
}
if (contractFee > 0) {
contractFee = contractFee.mul(9 + batchLength).div(10);
}
return (tokenScAddr, contractFee);
}
/**
* @notice Locks NFTs for cross-chain transfer
* @dev Handles the locking of ERC721 and ERC1155 tokens
* @param storageData Cross storage data
* @param params Parameters for the NFT locking operation
* Requirements:
* - NFT type must be valid (ERC721 or ERC1155)
* - User must have sufficient balance
* - Contract must have approval to transfer tokens
*/
function userLockNFT(CrossTypes.Data storage storageData, RapidityUserLockNFTParams memory params)
public
{
address tokenScAddr;
uint contractFee;
(tokenScAddr, contractFee) = getTokenScAddrAndContractFee(storageData, params.tokenPairID, params.tokenPairContractFee, params.currentChainID, params.tokenIDs.length);
if (contractFee > 0) {
EtherTransfer.sendValue(payable(params.smgFeeProxy), contractFee, params.etherTransferGasLimit);
}
uint left = (msg.value).sub(contractFee);
uint8 tokenCrossType = storageData.tokenManager.mapTokenPairType(params.tokenPairID);
if (tokenCrossType == uint8(TokenCrossType.ERC721)) {
for(uint idx = 0; idx < params.tokenIDs.length; ++idx) {
IERC721(tokenScAddr).safeTransferFrom(msg.sender, address(this), params.tokenIDs[idx], "");
}
} else if(tokenCrossType == uint8(TokenCrossType.ERC1155)) {
IERC1155(tokenScAddr).safeBatchTransferFrom(msg.sender, address(this), params.tokenIDs, params.tokenValues, "");
}
else{
require(false, "Invalid NFT type");
}
if (left != 0) {
EtherTransfer.sendValue(payable(msg.sender), left, params.etherTransferGasLimit);
}
string[] memory keys = new string[](4);
bytes[] memory values = new bytes[](4);
keys[0] = "tokenIDs:uint256[]";
values[0] = abi.encode(params.tokenIDs);
keys[1] = "tokenValues:uint256[]";
values[1] = abi.encode(params.tokenValues);
keys[2] = "userAccount:bytes";
values[2] = params.destUserAccount;
keys[3] = "contractFee:uint256";
values[3] = abi.encodePacked(contractFee);
emit UserLockNFT(params.smgID, params.tokenPairID, tokenScAddr, keys, values);
}
/**
* @notice Burns NFTs for cross-chain transfer
* @dev Handles the burning of ERC721 and ERC1155 tokens
* @param storageData Cross storage data
* @param params Parameters for the NFT burning operation
* Requirements:
* - NFT type must be valid (ERC721 or ERC1155)
* - User must have sufficient balance
* - Contract must have approval to burn tokens
*/
function userBurnNFT(CrossTypes.Data storage storageData, RapidityUserBurnNFTParams memory params)
public
{
address tokenScAddr;
uint contractFee;
(tokenScAddr, contractFee) = getTokenScAddrAndContractFee(storageData, params.tokenPairID, params.tokenPairContractFee, params.currentChainID, params.tokenIDs.length);
ITokenManager tokenManager = storageData.tokenManager;
uint8 tokenCrossType = tokenManager.mapTokenPairType(params.tokenPairID);
require((tokenCrossType == uint8(TokenCrossType.ERC721) || tokenCrossType == uint8(TokenCrossType.ERC1155)), "Invalid NFT type");
ITokenManager(tokenManager).burnNFT(uint(tokenCrossType), tokenScAddr, msg.sender, params.tokenIDs, params.tokenValues);
if (contractFee > 0) {
EtherTransfer.sendValue(payable(params.smgFeeProxy), contractFee, params.etherTransferGasLimit);
}
uint left = (msg.value).sub(contractFee);
if (left != 0) {
EtherTransfer.sendValue(payable(msg.sender), left, params.etherTransferGasLimit);
}
string[] memory keys = new string[](4);
bytes[] memory values = new bytes[](4);
keys[0] = "tokenIDs:uint256[]";
values[0] = abi.encode(params.tokenIDs);
keys[1] = "tokenValues:uint256[]";
values[1] = abi.encode(params.tokenValues);
keys[2] = "userAccount:bytes";
values[2] = params.destUserAccount;
keys[3] = "contractFee:uint256";
values[3] = abi.encodePacked(contractFee);
emit UserBurnNFT(params.smgID, params.tokenPairID, tokenScAddr, keys, values);
}
/**
* @notice Mints NFTs on the destination chain
* @dev Handles the minting of ERC721 and ERC1155 tokens
* @param storageData Cross storage data
* @param params Parameters for the NFT minting operation
* Requirements:
* - NFT type must be valid (ERC721 or ERC1155)
* - Storeman group must have permission to mint
*/
function smgMintNFT(CrossTypes.Data storage storageData, RapiditySmgMintNFTParams memory params)
public
{
storageData.rapidityTxData.addRapidityTx(params.uniqueID);
ITokenManager tokenManager = storageData.tokenManager;
uint8 tokenCrossType = tokenManager.mapTokenPairType(params.tokenPairID);
require((tokenCrossType == uint8(TokenCrossType.ERC721) || tokenCrossType == uint8(TokenCrossType.ERC1155)), "Invalid NFT type");
ITokenManager(tokenManager).mintNFT(uint(tokenCrossType), params.destTokenAccount, params.destUserAccount, params.tokenIDs, params.tokenValues, params.extData);
string[] memory keys = new string[](5);
bytes[] memory values = new bytes[](5);
keys[0] = "tokenIDs:uint256[]";
values[0] = abi.encode(params.tokenIDs);
keys[1] = "tokenValues:uint256[]";
values[1] = abi.encode(params.tokenValues);
keys[2] = "tokenAccount:address";
values[2] = abi.encodePacked(params.destTokenAccount);
keys[3] = "userAccount:address";
values[3] = abi.encodePacked(params.destUserAccount);
keys[4] = "extData:bytes";
values[4] = params.extData;
emit SmgMintNFT(params.uniqueID, params.smgID, params.tokenPairID, keys, values);
}
/**
* @notice Releases NFTs on the original chain
* @dev Handles the release of ERC721 and ERC1155 tokens
* @param storageData Cross storage data
* @param params Parameters for the NFT release operation
* Requirements:
* - NFT type must be valid (ERC721 or ERC1155)
* - Storeman group must have permission to release
*/
function smgReleaseNFT(CrossTypes.Data storage storageData, RapiditySmgReleaseNFTParams memory params)
public
{
storageData.rapidityTxData.addRapidityTx(params.uniqueID);
uint8 tokenCrossType = storageData.tokenManager.mapTokenPairType(params.tokenPairID);
if (tokenCrossType == uint8(TokenCrossType.ERC721)) {
for(uint idx = 0; idx < params.tokenIDs.length; ++idx) {
IERC721(params.destTokenAccount).safeTransferFrom(address(this), params.destUserAccount, params.tokenIDs[idx], "");
}
}
else if(tokenCrossType == uint8(TokenCrossType.ERC1155)) {
IERC1155(params.destTokenAccount).safeBatchTransferFrom(address(this), params.destUserAccount, params.tokenIDs, params.tokenValues, "");
}
else {
require(false, "Invalid NFT type");
}
string[] memory keys = new string[](4);
bytes[] memory values = new bytes[](4);
keys[0] = "tokenIDs:uint256[]";
values[0] = abi.encode(params.tokenIDs);
keys[1] = "tokenValues:uint256[]";
values[1] = abi.encode(params.tokenValues);
keys[2] = "tokenAccount:address";
values[2] = abi.encodePacked(params.destTokenAccount);
keys[3] = "userAccount:address";
values[3] = abi.encodePacked(params.destUserAccount);
emit SmgReleaseNFT(params.uniqueID, params.smgID, params.tokenPairID, keys, values);
}
}// SPDX-License-Identifier: MIT
/*
Copyright 2023 Wanchain Foundation.
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
*/
// _ _ _
// __ ____ _ _ __ ___| |__ __ _(_)_ __ __| | _____ __
// \ \ /\ / / _` | '_ \ / __| '_ \ / _` | | '_ \@/ _` |/ _ \ \ / /
// \ V V / (_| | | | | (__| | | | (_| | | | | | (_| | __/\ V /
// \_/\_/ \__,_|_| |_|\___|_| |_|\__,_|_|_| |_|\__,_|\___| \_/
//
//
pragma solidity ^0.8.18;
import "./RapidityTxLib.sol";
import "./CrossTypes.sol";
import "../../interfaces/ITokenManager.sol";
import "../../interfaces/IRC20Protocol.sol";
import "./EtherTransfer.sol";
/**
* @title RapidityLibV4
* @dev Library for managing rapid cross-chain token transfers
* This library provides functionality for:
* - Token locking and unlocking
* - Token minting and burning
* - Cross-chain token transfer management
*/
library RapidityLibV4 {
using SafeMath for uint;
using RapidityTxLib for RapidityTxLib.Data;
/**
* @notice Enumeration of supported token types for cross-chain operations
* @dev Defines the types of tokens that can be transferred across chains
*/
enum TokenCrossType {ERC20, ERC721, ERC1155}
/**
* @notice Parameters for user-initiated token locking operations
* @dev Used when users want to lock their tokens for cross-chain transfer
* @param smgID ID of the selected storeman group
* @param tokenPairID ID of the token pair for cross-chain transfer
* @param value Amount of tokens to transfer
* @param currentChainID ID of the current blockchain
* @param tokenPairContractFee Fee for the token pair contract
* @param etherTransferGasLimit Gas limit for ether transfers
* @param destUserAccount Destination user account address
* @param smgFeeProxy Address of the proxy for storing storeman group fees
*/
struct RapidityUserLockParams {
bytes32 smgID;
uint tokenPairID;
uint value;
uint currentChainID;
uint tokenPairContractFee;
uint etherTransferGasLimit;
bytes destUserAccount;
address smgFeeProxy;
}
/**
* @notice Parameters for storeman-initiated token minting operations
* @dev Used when storeman group mints tokens on the destination chain
* @param uniqueID Unique identifier for the rapidity transaction
* @param smgID ID of the storeman group
* @param tokenPairID ID of the token pair
* @param value Amount of tokens to mint
* @param fee Transaction fee
* @param destTokenAccount Destination token contract address
* @param destUserAccount Destination user account address
* @param smgFeeProxy Address of the proxy for storing storeman group fees
*/
struct RapiditySmgMintParams {
bytes32 uniqueID;
bytes32 smgID;
uint tokenPairID;
uint value;
uint fee;
address destTokenAccount;
address destUserAccount;
address smgFeeProxy;
}
/**
* @notice Parameters for user-initiated token burning operations
* @dev Used when users want to burn their tokens for cross-chain transfer
* @param smgID ID of the selected storeman group
* @param tokenPairID ID of the token pair
* @param value Amount of tokens to burn
* @param currentChainID ID of the current blockchain
* @param fee Transaction fee
* @param tokenPairContractFee Fee for the token pair contract
* @param etherTransferGasLimit Gas limit for ether transfers
* @param srcTokenAccount Source token contract address
* @param destUserAccount Destination user account address
* @param smgFeeProxy Address of the proxy for storing storeman group fees
*/
struct RapidityUserBurnParams {
bytes32 smgID;
uint tokenPairID;
uint value;
uint currentChainID;
uint fee;
uint tokenPairContractFee;
uint etherTransferGasLimit;
address srcTokenAccount;
bytes destUserAccount;
address smgFeeProxy;
}
/**
* @notice Parameters for storeman-initiated token release operations
* @dev Used when storeman group releases tokens on the original chain
* @param uniqueID Unique identifier for the rapidity transaction
* @param smgID ID of the storeman group
* @param tokenPairID ID of the token pair
* @param value Amount of tokens to release
* @param fee Transaction fee
* @param etherTransferGasLimit Gas limit for ether transfers
* @param destTokenAccount Destination token contract address
* @param destUserAccount Destination user account address
* @param smgFeeProxy Address of the proxy for storing storeman group fees
*/
struct RapiditySmgReleaseParams {
bytes32 uniqueID;
bytes32 smgID;
uint tokenPairID;
uint value;
uint fee;
uint etherTransferGasLimit;
address destTokenAccount;
address destUserAccount;
address smgFeeProxy;
}
/**
* @notice Event emitted when tokens are locked by a user
* @param smgID ID of the storeman group
* @param tokenPairID ID of the token pair
* @param tokenAccount Address of the token contract
* @param value Amount of tokens locked
* @param contractFee Contract fee charged
* @param userAccount Destination user account address
*/
event UserLockLogger(bytes32 indexed smgID, uint indexed tokenPairID, address indexed tokenAccount, uint value, uint contractFee, bytes userAccount);
/**
* @notice Event emitted when tokens are burned by a user
* @param smgID ID of the storeman group
* @param tokenPairID ID of the token pair
* @param tokenAccount Address of the token contract
* @param value Amount of tokens burned
* @param contractFee Contract fee charged
* @param fee Transaction fee charged
* @param userAccount Destination user account address
*/
event UserBurnLogger(bytes32 indexed smgID, uint indexed tokenPairID, address indexed tokenAccount, uint value, uint contractFee, uint fee, bytes userAccount);
/**
* @notice Event emitted when tokens are minted by storeman group
* @param uniqueID Unique identifier for the rapidity transaction
* @param smgID ID of the storeman group
* @param tokenPairID ID of the token pair
* @param value Amount of tokens minted
* @param tokenAccount Address of the token contract
* @param userAccount Destination user account address
*/
event SmgMintLogger(bytes32 indexed uniqueID, bytes32 indexed smgID, uint indexed tokenPairID, uint value, address tokenAccount, address userAccount);
/**
* @notice Event emitted when tokens are minted by storeman group (with additional data)
* @param uniqueID Unique identifier for the rapidity transaction
* @param smgID ID of the storeman group
* @param tokenPairID ID of the token pair
* @param keys Array of event keys
* @param values Array of event values
*/
event SmgMint(bytes32 indexed uniqueID, bytes32 indexed smgID, uint indexed tokenPairID, string[] keys, bytes[] values);
/**
* @notice Event emitted when tokens are released by storeman group
* @param uniqueID Unique identifier for the rapidity transaction
* @param smgID ID of the storeman group
* @param tokenPairID ID of the token pair
* @param value Amount of tokens released
* @param tokenAccount Address of the token contract
* @param userAccount Destination user account address
*/
event SmgReleaseLogger(bytes32 indexed uniqueID, bytes32 indexed smgID, uint indexed tokenPairID, uint value, address tokenAccount, address userAccount);
/**
* @notice Event emitted when tokens are released by storeman group (with additional data)
* @param uniqueID Unique identifier for the rapidity transaction
* @param smgID ID of the storeman group
* @param tokenPairID ID of the token pair
* @param keys Array of event keys
* @param values Array of event values
*/
event SmgRelease(bytes32 indexed uniqueID, bytes32 indexed smgID, uint indexed tokenPairID, string[] keys, bytes[] values);
/**
* @notice Locks tokens for cross-chain transfer
* @dev Handles the locking of ERC20 tokens
* @param storageData Cross storage data
* @param params Parameters for the token locking operation
* Requirements:
* - Token type must be ERC20
* - User must have sufficient balance
* - Contract must have approval to transfer tokens
*/
function userLock(CrossTypes.Data storage storageData, RapidityUserLockParams memory params)
public
{
ITokenManager tokenManager = storageData.tokenManager;
uint fromChainID;
uint toChainID;
bytes memory fromTokenAccount;
bytes memory toTokenAccount;
(fromChainID,fromTokenAccount,toChainID,toTokenAccount) = tokenManager.getTokenPairInfo(params.tokenPairID);
require(fromChainID != 0, "Token does not exist");
uint contractFee = params.tokenPairContractFee;
address tokenScAddr;
if (params.currentChainID == fromChainID) {
if (contractFee == 0) {
contractFee = storageData.mapContractFee[fromChainID][toChainID];
}
if (contractFee == 0) {
contractFee = storageData.mapContractFee[fromChainID][0];
}
tokenScAddr = CrossTypes.bytesToAddress(fromTokenAccount);
} else if (params.currentChainID == toChainID) {
if (contractFee == 0) {
contractFee = storageData.mapContractFee[toChainID][fromChainID];
}
if (contractFee == 0) {
contractFee = storageData.mapContractFee[toChainID][0];
}
tokenScAddr = CrossTypes.bytesToAddress(toTokenAccount);
} else {
require(false, "Invalid token pair");
}
if (contractFee > 0) {
EtherTransfer.sendValue(payable(params.smgFeeProxy), contractFee, params.etherTransferGasLimit);
}
uint left;
if (tokenScAddr == address(0)) {
left = (msg.value).sub(params.value).sub(contractFee);
} else {
left = (msg.value).sub(contractFee);
uint8 tokenCrossType = tokenManager.mapTokenPairType(params.tokenPairID);
require(tokenCrossType == uint8(TokenCrossType.ERC20), "Not support");
require(CrossTypes.transferFrom(tokenScAddr, msg.sender, address(this), params.value), "Lock token failed");
}
if (left != 0) {
EtherTransfer.sendValue(payable(msg.sender), left, params.etherTransferGasLimit);
}
emit UserLockLogger(params.smgID, params.tokenPairID, tokenScAddr, params.value, contractFee, params.destUserAccount);
}
/**
* @notice Burns tokens for cross-chain transfer
* @dev Handles the burning of ERC20 tokens
* @param storageData Cross storage data
* @param params Parameters for the token burning operation
* Requirements:
* - Token type must be ERC20
* - User must have sufficient balance
* - Contract must have approval to burn tokens
*/
function userBurn(CrossTypes.Data storage storageData, RapidityUserBurnParams memory params)
public
{
ITokenManager tokenManager = storageData.tokenManager;
uint fromChainID;
uint toChainID;
bytes memory fromTokenAccount;
bytes memory toTokenAccount;
(fromChainID,fromTokenAccount,toChainID,toTokenAccount) = tokenManager.getTokenPairInfo(params.tokenPairID);
require(fromChainID != 0, "Token does not exist");
uint256 contractFee = params.tokenPairContractFee;
address tokenScAddr;
if (params.currentChainID == toChainID) {
if (contractFee == 0) {
contractFee = storageData.mapContractFee[toChainID][fromChainID];
}
if (contractFee == 0) {
contractFee = storageData.mapContractFee[toChainID][0];
}
tokenScAddr = CrossTypes.bytesToAddress(toTokenAccount);
} else if (params.currentChainID == fromChainID) {
if (contractFee == 0) {
contractFee = storageData.mapContractFee[fromChainID][toChainID];
}
if (contractFee == 0) {
contractFee = storageData.mapContractFee[fromChainID][0];
}
tokenScAddr = CrossTypes.bytesToAddress(fromTokenAccount);
} else {
require(false, "Invalid token pair");
}
require(params.srcTokenAccount == tokenScAddr, "Invalid token account");
// Reuse variable fromChainID as tokenCrossType; burn token by tokenCrossType
fromChainID = tokenManager.mapTokenPairType(params.tokenPairID);
require(fromChainID == uint8(TokenCrossType.ERC20), "Not support");
require(burnShadowToken(tokenManager, tokenScAddr, msg.sender, params.value), "Burn failed");
if (contractFee > 0) {
EtherTransfer.sendValue(payable(params.smgFeeProxy), contractFee, params.etherTransferGasLimit);
}
uint left = (msg.value).sub(contractFee);
if (left != 0) {
EtherTransfer.sendValue(payable(msg.sender), left, params.etherTransferGasLimit);
}
emit UserBurnLogger(params.smgID, params.tokenPairID, tokenScAddr, params.value, contractFee, params.fee, params.destUserAccount);
}
/**
* @notice Mints tokens on the destination chain
* @dev Handles the minting of ERC20 tokens
* @param storageData Cross storage data
* @param params Parameters for the token minting operation
* Requirements:
* - Token type must be valid (ERC20)
* - Storeman group must have permission to mint
*/
function smgMint(CrossTypes.Data storage storageData, RapiditySmgMintParams memory params)
public
{
storageData.rapidityTxData.addRapidityTx(params.uniqueID);
ITokenManager tokenManager = storageData.tokenManager;
uint8 tokenCrossType = tokenManager.mapTokenPairType(params.tokenPairID);
require(tokenCrossType == uint8(TokenCrossType.ERC20), "Not support");
if (params.fee > 0) {
require(mintShadowToken(tokenManager, params.destTokenAccount, params.smgFeeProxy, params.fee), "Mint fee failed");
}
require(mintShadowToken(tokenManager, params.destTokenAccount, params.destUserAccount, params.value), "Mint failed");
string[] memory keys = new string[](4);
bytes[] memory values = new bytes[](4);
keys[0] = "value:uint256";
values[0] = abi.encodePacked(params.value);
keys[1] = "tokenAccount:address";
values[1] = abi.encodePacked(params.destTokenAccount);
keys[2] = "userAccount:address";
values[2] = abi.encodePacked(params.destUserAccount);
keys[3] = "fee:uint256";
values[3] = abi.encodePacked(params.fee);
emit SmgMint(params.uniqueID, params.smgID, params.tokenPairID, keys, values);
emit SmgMintLogger(params.uniqueID, params.smgID, params.tokenPairID, params.value, params.destTokenAccount, params.destUserAccount);
}
/**
* @notice Releases tokens on the original chain
* @dev Handles the release of ERC20 tokens
* @param storageData Cross storage data
* @param params Parameters for the token release operation
* Requirements:
* - Token type must be ERC20
* - Storeman group must have permission to release
*/
function smgRelease(CrossTypes.Data storage storageData, RapiditySmgReleaseParams memory params)
public
{
storageData.rapidityTxData.addRapidityTx(params.uniqueID);
if (params.destTokenAccount == address(0)) {
EtherTransfer.sendValue(payable(params.destUserAccount), params.value, params.etherTransferGasLimit);
if (params.fee > 0) {
EtherTransfer.sendValue(payable(params.smgFeeProxy), params.fee, params.etherTransferGasLimit);
}
} else {
uint8 tokenCrossType = storageData.tokenManager.mapTokenPairType(params.tokenPairID);
require(tokenCrossType == uint8(TokenCrossType.ERC20), "Not support");
if (params.fee > 0) {
require(CrossTypes.transfer(params.destTokenAccount, params.smgFeeProxy, params.fee), "Transfer token fee failed");
}
require(CrossTypes.transfer(params.destTokenAccount, params.destUserAccount, params.value), "Transfer token failed");
}
string[] memory keys = new string[](4);
bytes[] memory values = new bytes[](4);
keys[0] = "value:uint256";
values[0] = abi.encodePacked(params.value);
keys[1] = "tokenAccount:address";
values[1] = abi.encodePacked(params.destTokenAccount);
keys[2] = "userAccount:address";
values[2] = abi.encodePacked(params.destUserAccount);
keys[3] = "fee:uint256";
values[3] = abi.encodePacked(params.fee);
emit SmgRelease(params.uniqueID, params.smgID, params.tokenPairID, keys, values);
emit SmgReleaseLogger(params.uniqueID, params.smgID, params.tokenPairID, params.value, params.destTokenAccount, params.destUserAccount);
}
/**
* @notice Burns shadow tokens
* @dev Handles the burning of ERC20 tokens
* @param tokenManager Token manager contract
* @param tokenAddress Address of the token contract
* @param userAccount Address of the user account
* @param value Amount of tokens to burn
* @return bool indicating whether the burn was successful
*/
function burnShadowToken(ITokenManager tokenManager, address tokenAddress, address userAccount, uint value) private returns (bool) {
uint beforeBalance;
uint afterBalance;
beforeBalance = IRC20Protocol(tokenAddress).balanceOf(userAccount);
tokenManager.burnToken(tokenAddress, userAccount, value);
afterBalance = IRC20Protocol(tokenAddress).balanceOf(userAccount);
return afterBalance == beforeBalance.sub(value);
}
/**
* @notice Mints shadow tokens
* @dev Handles the minting of ERC20 tokens
* @param tokenManager Token manager contract
* @param tokenAddress Address of the token contract
* @param userAccount Address of the user account
* @param value Amount of tokens to mint
* @return bool indicating whether the mint was successful
*/
function mintShadowToken(ITokenManager tokenManager, address tokenAddress, address userAccount, uint value) private returns (bool) {
uint beforeBalance;
uint afterBalance;
beforeBalance = IRC20Protocol(tokenAddress).balanceOf(userAccount);
tokenManager.mintToken(tokenAddress, userAccount, value);
afterBalance = IRC20Protocol(tokenAddress).balanceOf(userAccount);
return afterBalance == beforeBalance.add(value);
}
}// SPDX-License-Identifier: MIT
/*
Copyright 2023 Wanchain Foundation.
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
*/
// _ _ _
// __ ____ _ _ __ ___| |__ __ _(_)_ __ __| | _____ __
// \ \ /\ / / _` | '_ \ / __| '_ \ / _` | | '_ \@/ _` |/ _ \ \ / /
// \ V V / (_| | | | | (__| | | | (_| | | | | | (_| | __/\ V /
// \_/\_/ \__,_|_| |_|\___|_| |_|\__,_|_|_| |_|\__,_|\___| \_/
//
//
pragma solidity ^0.8.18;
/**
* @title RapidityTxLib
* @dev Library for managing rapid cross-chain transaction status
* This library provides functionality for:
* - Tracking transaction status
* - Managing rapid cross-chain transactions
*/
library RapidityTxLib {
/**
* @notice Enumeration of possible transaction statuses
* @dev Defines the states a rapidity transaction can be in
* - None: Initial state, transaction not yet processed
* - Redeemed: Transaction has been completed
*/
enum TxStatus {None, Redeemed}
/**
* @notice Main data structure for rapidity transactions
* @dev Contains mappings for tracking transaction statuses
* @param mapTxStatus Mapping of transaction unique IDs to their status
*/
struct Data {
mapping(bytes32 => TxStatus) mapTxStatus;
}
/**
* @notice Adds a new rapidity transaction
* @dev Marks a transaction as redeemed when it is added
* @param self The storage data structure
* @param uniqueID Unique identifier for the rapidity transaction
* Requirements:
* - Transaction must not already exist
*/
function addRapidityTx(Data storage self, bytes32 uniqueID)
internal
{
TxStatus status = self.mapTxStatus[uniqueID];
require(status == TxStatus.None, "Rapidity tx exists");
self.mapTxStatus[uniqueID] = TxStatus.Redeemed;
}
}// SPDX-License-Identifier: MIT
/*
Copyright 2023 Wanchain Foundation.
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
*/
// _ _ _
// __ ____ _ _ __ ___| |__ __ _(_)_ __ __| | _____ __
// \ \ /\ / / _` | '_ \ / __| '_ \ / _` | | '_ \@/ _` |/ _ \ \ / /
// \ V V / (_| | | | | (__| | | | (_| | | | | | (_| | __/\ V /
// \_/\_/ \__,_|_| |_|\___|_| |_|\__,_|_|_| |_|\__,_|\___| \_/
//
// Code style according to: https://github.com/wanchain/wanchain-token/blob/master/style-guide.rst
pragma solidity 0.8.18;
import "@openzeppelin/contracts/utils/math/SafeMath.sol";
import "../components/Admin.sol";
import "./GpkStorageV2.sol";
import "../interfaces/IStoremanGroup.sol";
import "./lib/GpkLib.sol" ;
/**
* @title GpkDelegate
* @dev Implementation contract for Group Public Key (GPK) functionality
* This contract handles the core logic for GPK operations including:
* - Group initialization and management
* - Polynomial commitment submission and verification
* - Encrypted share distribution and verification
* - Timeout handling and slashing
*/
contract GpkDelegate is GpkStorageV2 {
using SafeMath for uint;
/**
* @dev Events for tracking GPK operations
*/
/**
* @notice Emitted when a storeman submits their polynomial commitment
* @param groupId The ID of the storeman group
* @param round The current negotiation round
* @param curveIndex The index of the signature curve
* @param storeman The address of the storeman
*/
event SetPolyCommitLogger(bytes32 indexed groupId, uint16 indexed round, uint8 indexed curveIndex, address storeman);
/**
* @notice Emitted when a storeman submits an encrypted share
* @param groupId The ID of the storeman group
* @param round The current negotiation round
* @param curveIndex The index of the signature curve
* @param src The source storeman address
* @param dest The destination storeman address
*/
event SetEncSijLogger(bytes32 indexed groupId, uint16 indexed round, uint8 indexed curveIndex, address src, address dest);
/**
* @notice Emitted when a storeman verifies an encrypted share
* @param groupId The ID of the storeman group
* @param round The current negotiation round
* @param curveIndex The index of the signature curve
* @param src The source storeman address
* @param dest The destination storeman address
* @param isValid Whether the encrypted share is valid
*/
event SetCheckStatusLogger(bytes32 indexed groupId, uint16 indexed round, uint8 indexed curveIndex, address src, address dest, bool isValid);
/**
* @notice Emitted when a storeman reveals their share
* @param groupId The ID of the storeman group
* @param round The current negotiation round
* @param curveIndex The index of the signature curve
* @param src The source storeman address
* @param dest The destination storeman address
*/
event RevealSijLogger(bytes32 indexed groupId, uint16 indexed round, uint8 indexed curveIndex, address src, address dest);
/**
* @notice Emitted when a new GPK is created
* @param groupId The ID of the storeman group
* @param round The current negotiation round
* @param gpk The array of GPK values
*/
event GpkCreatedLogger(bytes32 indexed groupId, uint16 indexed round, bytes[] gpk);
/**
* @notice Emitted when GPK configuration is set
* @param groupId The ID of the storeman group
* @param count The number of GPK configurations
*/
event setGpkCfgEvent(bytes32 indexed groupId, uint indexed count);
/**
* @dev Contract manipulation functions
*/
/**
* @notice Sets the dependencies for the contract
* @dev Only callable by the contract owner
* @param cfgAddr The address of the configuration contract
* @param smgAddr The address of the storeman group contract
* @dev Throws if either address is invalid
*/
function setDependence(address cfgAddr, address smgAddr)
external
onlyOwner
{
require(cfgAddr != address(0), "Invalid cfg");
cfg = cfgAddr;
require(smgAddr != address(0), "Invalid smg");
smg = smgAddr;
}
/**
* @notice Sets the time periods for different phases of GPK generation
* @dev Only callable by admin
* @param groupId The ID of the storeman group
* @param ployCommitPeriod The period for polynomial commitment submission
* @param defaultPeriod The default timeout period
* @param negotiatePeriod The period for negotiation phase
*/
function setPeriod(bytes32 groupId, uint32 ployCommitPeriod, uint32 defaultPeriod, uint32 negotiatePeriod)
external
onlyAdmin
{
GpkTypes.Group storage group = groupMap[groupId];
group.ployCommitPeriod = ployCommitPeriod;
group.defaultPeriod = defaultPeriod;
group.negotiatePeriod = negotiatePeriod;
}
/**
* @notice Allows a storeman to submit their polynomial commitment
* @dev Handles the polynomial commitment phase of GPK generation
* @param groupId The ID of the storeman group
* @param roundIndex The current negotiation round
* @param curveIndex The index of the signature curve
* @param polyCommit The polynomial commitment data
* @dev Throws if the commitment is invalid or duplicate
* @dev Emits SetPolyCommitLogger event on success
*/
function setPolyCommit(bytes32 groupId, uint16 roundIndex, uint8 curveIndex, bytes memory polyCommit)
external
{
require(polyCommit.length > 0, "Invalid polyCommit");
require(gpkCount[groupId] != 0, "Invalid gpk count");
GpkTypes.Group storage group = groupMap[groupId];
GpkTypes.Round storage round = group.roundMap[roundIndex][curveIndex];
uint[] memory curves = new uint[](gpkCount[groupId]);
for(uint8 i=0; i<gpkCount[groupId]; i++) {
curves[i] = curve[groupId][i];
}
if (group.smNumber == 0) {
// init group when the first node submit to start every round
GpkLib.initGroup(groupId, group, cfg, smg, curves);
}
if (round.statusTime == 0) {
round.statusTime = block.timestamp;
}
checkValid(group, roundIndex, curveIndex, GpkTypes.GpkStatus.PolyCommit, true, false, address(0));
require(round.srcMap[msg.sender].polyCommit.length == 0, "Duplicate");
round.srcMap[msg.sender].polyCommit = polyCommit;
round.polyCommitCount++;
GpkLib.updateGpk(round, polyCommit);
GpkLib.updateGpkShare(group, round, polyCommit);
if (round.polyCommitCount >= group.smNumber) {
round.status = GpkTypes.GpkStatus.Negotiate;
round.statusTime = block.timestamp;
}
emit SetPolyCommitLogger(groupId, roundIndex, curveIndex, msg.sender);
}
/**
* @notice Reports timeout for polynomial commitment submission
* @dev Handles slashing for storemen who failed to submit commitments
* @param groupId The ID of the storeman group
* @param curveIndex The index of the signature curve
* @dev Throws if the timeout period has not elapsed
*/
function polyCommitTimeout(bytes32 groupId, uint8 curveIndex)
external
{
GpkTypes.Group storage group = groupMap[groupId];
checkValid(group, group.round, curveIndex, GpkTypes.GpkStatus.PolyCommit, false, false, address(0));
GpkTypes.Round storage round = group.roundMap[group.round][curveIndex];
uint32 timeout = (group.round == 0) ? group.ployCommitPeriod : group.defaultPeriod;
require(block.timestamp.sub(round.statusTime) > timeout, "Time not arrive");
uint slashCount = 0;
for (uint i = 0; (i < group.smNumber) && (slashCount + round.polyCommitCount < group.smNumber); i++) {
address src = group.addrMap[i];
if (round.srcMap[src].polyCommit.length == 0) {
GpkLib.slash(group, curveIndex, GpkTypes.SlashType.PolyCommitTimeout, src, address(0), false, smg);
slashCount++;
}
}
GpkLib.slashMulti(group, curveIndex, smg);
}
/**
* @notice Allows a storeman to submit an encrypted share
* @dev Handles the encrypted share distribution phase
* @param groupId The ID of the storeman group
* @param roundIndex The current negotiation round
* @param curveIndex The index of the signature curve
* @param dest The destination storeman address
* @param encSij The encrypted share data
* @dev Throws if the encrypted share is invalid or duplicate
* @dev Emits SetEncSijLogger event on success
*/
function setEncSij(bytes32 groupId, uint16 roundIndex, uint8 curveIndex, address dest, bytes memory encSij)
external
{
require(encSij.length > 0, "Invalid encSij"); // ephemPublicKey(65) + iv(16) + mac(32) + ciphertext(48)
GpkTypes.Group storage group = groupMap[groupId];
checkValid(group, roundIndex, curveIndex, GpkTypes.GpkStatus.Negotiate, true, true, dest);
GpkTypes.Round storage round = group.roundMap[roundIndex][curveIndex];
GpkTypes.Dest storage d = round.srcMap[msg.sender].destMap[dest];
require(d.encSij.length == 0, "Duplicate");
d.encSij = encSij;
d.setTime = block.timestamp;
emit SetEncSijLogger(groupId, roundIndex, curveIndex, msg.sender, dest);
}
/**
* @notice Allows a storeman to verify an encrypted share
* @dev Handles the verification phase of encrypted shares
* @param groupId The ID of the storeman group
* @param roundIndex The current negotiation round
* @param curveIndex The index of the signature curve
* @param src The source storeman address
* @param isValid Whether the encrypted share is valid
* @dev Throws if the share is not ready or already verified
* @dev Emits SetCheckStatusLogger event on success
*/
function setCheckStatus(bytes32 groupId, uint16 roundIndex, uint8 curveIndex, address src, bool isValid)
external
{
GpkTypes.Group storage group = groupMap[groupId];
checkValid(group, roundIndex, curveIndex, GpkTypes.GpkStatus.Negotiate, true, true, src);
GpkTypes.Round storage round = group.roundMap[roundIndex][curveIndex];
GpkTypes.Src storage s = round.srcMap[src];
GpkTypes.Dest storage d = s.destMap[msg.sender];
require(d.encSij.length != 0, "Not ready");
require(d.checkStatus == GpkTypes.CheckStatus.Init, "Duplicate");
d.checkTime = block.timestamp;
emit SetCheckStatusLogger(groupId, roundIndex, curveIndex, src, msg.sender, isValid);
if (isValid) {
d.checkStatus = GpkTypes.CheckStatus.Valid;
s.checkValidCount++;
round.checkValidCount++;
if (round.checkValidCount >= group.smNumber ** 2) {
round.status = GpkTypes.GpkStatus.Complete;
round.statusTime = block.timestamp;
tryComplete(groupId, smg);
}
} else {
d.checkStatus = GpkTypes.CheckStatus.Invalid;
}
}
/**
* @notice Reports timeout for encrypted share submission
* @dev Handles slashing for storemen who failed to submit encrypted shares
* @param groupId The ID of the storeman group
* @param curveIndex The index of the signature curve
* @param src The source storeman address
* @dev Throws if the share is already submitted or timeout period has not elapsed
*/
function encSijTimeout(bytes32 groupId, uint8 curveIndex, address src)
external
{
GpkTypes.Group storage group = groupMap[groupId];
checkValid(group, group.round, curveIndex, GpkTypes.GpkStatus.Negotiate, true, true, src);
GpkTypes.Round storage round = group.roundMap[group.round][curveIndex];
GpkTypes.Dest storage d = round.srcMap[src].destMap[msg.sender];
require(d.encSij.length == 0, "Outdated");
require(block.timestamp.sub(round.statusTime) > group.defaultPeriod, "Not late");
GpkLib.slash(group, curveIndex, GpkTypes.SlashType.EncSijTimout, src, msg.sender, true, smg);
}
/**
* @notice Allows a storeman to reveal their share
* @dev Handles the share revelation phase
* @param groupId The ID of the storeman group
* @param roundIndex The current negotiation round
* @param curveIndex The index of the signature curve
* @param dest The destination storeman address
* @param sij The share value
* @param ephemPrivateKey The ephemeral private key
* @dev Throws if the share is not needed or invalid
* @dev Emits RevealSijLogger event on success
*/
function revealSij(bytes32 groupId, uint16 roundIndex, uint8 curveIndex, address dest, uint sij, uint ephemPrivateKey)
external
{
GpkTypes.Group storage group = groupMap[groupId];
checkValid(group, roundIndex, curveIndex, GpkTypes.GpkStatus.Negotiate, true, true, dest);
GpkTypes.Round storage round = group.roundMap[roundIndex][curveIndex];
GpkTypes.Src storage src = round.srcMap[msg.sender];
GpkTypes.Dest storage d = src.destMap[dest];
require(d.checkStatus == GpkTypes.CheckStatus.Invalid, "Not need");
d.sij = sij;
d.ephemPrivateKey = ephemPrivateKey;
emit RevealSijLogger(groupId, roundIndex, curveIndex, msg.sender, dest);
if (GpkLib.verifySij(d, group.pkMap[dest], src.polyCommit, round.curve)) {
GpkLib.slash(group, curveIndex, GpkTypes.SlashType.CheckInvalid, dest, msg.sender, true, smg);
} else {
GpkLib.slash(group, curveIndex, GpkTypes.SlashType.SijInvalid, msg.sender, dest, true, smg);
}
}
/**
* @notice Reports timeout for share verification
* @dev Handles slashing for storemen who failed to verify shares
* @param groupId The ID of the storeman group
* @param curveIndex The index of the signature curve
* @param dest The destination storeman address
* @dev Throws if the share is already verified or timeout period has not elapsed
*/
function checkSijTimeout(bytes32 groupId, uint8 curveIndex, address dest)
external
{
GpkTypes.Group storage group = groupMap[groupId];
checkValid(group, group.round, curveIndex, GpkTypes.GpkStatus.Negotiate, true, true, dest);
GpkTypes.Round storage round = group.roundMap[group.round][curveIndex];
GpkTypes.Dest storage d = round.srcMap[msg.sender].destMap[dest];
require(d.checkStatus == GpkTypes.CheckStatus.Init, "Checked");
require(d.encSij.length != 0, "Not ready");
require(block.timestamp.sub(d.setTime) > group.defaultPeriod, "Not late");
GpkLib.slash(group, curveIndex, GpkTypes.SlashType.CheckTimeout, dest, msg.sender, true, smg);
}
/**
* @notice Reports timeout for share submission
* @dev Handles slashing for storemen who failed to submit shares
* @param groupId The ID of the storeman group
* @param curveIndex The index of the signature curve
* @param src The source storeman address
* @dev Throws if the share is not needed or timeout period has not elapsed
*/
function SijTimeout(bytes32 groupId, uint8 curveIndex, address src)
external
{
GpkTypes.Group storage group = groupMap[groupId];
checkValid(group, group.round, curveIndex, GpkTypes.GpkStatus.Negotiate, true, true, src);
GpkTypes.Round storage round = group.roundMap[group.round][curveIndex];
GpkTypes.Dest storage d = round.srcMap[src].destMap[msg.sender];
require(d.checkStatus == GpkTypes.CheckStatus.Invalid, "Not need");
require(block.timestamp.sub(d.checkTime) > group.defaultPeriod, "Not late");
GpkLib.slash(group, curveIndex, GpkTypes.SlashType.SijTimeout, src, msg.sender, true, smg);
}
/**
* @notice Terminates the protocol for a group
* @dev Handles the termination of the GPK generation protocol
* @param groupId The ID of the storeman group
* @param curveIndex The index of the signature curve
* @dev Throws if the negotiation period has not elapsed
*/
function terminate(bytes32 groupId, uint8 curveIndex)
external
{
GpkTypes.Group storage group = groupMap[groupId];
checkValid(group, group.round, curveIndex, GpkTypes.GpkStatus.Negotiate, false, false, address(0));
GpkTypes.Round storage round = group.roundMap[group.round][curveIndex];
require(block.timestamp.sub(round.statusTime) > group.negotiatePeriod, "Not late");
for (uint i = 0; i < group.smNumber; i++) {
address src = group.addrMap[i];
uint slashPair = uint(group.smNumber).sub(uint(round.srcMap[src].checkValidCount));
for (uint j = 0; (j < group.smNumber) && (slashPair > 0); j++) {
address dest = group.addrMap[j];
GpkTypes.Dest storage d = round.srcMap[src].destMap[dest];
if (d.checkStatus != GpkTypes.CheckStatus.Valid) {
if (d.encSij.length == 0) {
GpkLib.slash(group, curveIndex, GpkTypes.SlashType.EncSijTimout, src, dest, false, smg);
GpkLib.slash(group, curveIndex, GpkTypes.SlashType.Connive, dest, src, false, smg);
} else if (d.checkStatus == GpkTypes.CheckStatus.Init) {
GpkLib.slash(group, curveIndex, GpkTypes.SlashType.Connive, src, dest, false, smg);
GpkLib.slash(group, curveIndex, GpkTypes.SlashType.CheckTimeout, dest, src, false, smg);
} else { // GpkTypes.CheckStatus.Invalid
GpkLib.slash(group, curveIndex, GpkTypes.SlashType.SijTimeout, src, dest, false, smg);
GpkLib.slash(group, curveIndex, GpkTypes.SlashType.Connive, dest, src, false, smg);
}
slashPair--;
}
}
}
GpkLib.slashMulti(group, curveIndex, smg);
}
/**
* @notice Validates parameters for group operations
* @dev Internal function to check validity of group parameters
* @param group The group structure
* @param roundIndex The current negotiation round
* @param curveIndex The index of the signature curve
* @param status The expected group status
* @param checkSender Whether to check the sender
* @param checkStoreman Whether to check the storeman
* @param storeman The storeman address
* @dev Throws if any parameter is invalid
*/
function checkValid(GpkTypes.Group storage group, uint16 roundIndex, uint8 curveIndex, GpkTypes.GpkStatus status, bool checkSender, bool checkStoreman, address storeman)
private
view
{
require(roundIndex == group.round, "Invalid round"); // must be current round
require(curveIndex < gpkCount[group.groupId], "Invalid curve");
GpkTypes.Round storage round = group.roundMap[roundIndex][curveIndex];
require((round.status == status) && (round.statusTime > 0), "Invalid status");
if (checkSender) {
require(group.pkMap[msg.sender].length > 0, "Invalid sender");
}
if (checkStoreman) {
require(group.pkMap[storeman].length > 0, "Invalid storeman");
}
}
/**
* @notice Gets information about a group
* @param groupId The ID of the storeman group
* @param roundIndex The round index to query (-1 for current round)
* @return queriedRound The queried round number
* @return curve1 The address of curve 1
* @return curve1Status The status of curve 1
* @return curve1StatusTime The status time of curve 1
* @return curve2 The address of curve 2
* @return curve2Status The status of curve 2
* @return curve2StatusTime The status time of curve 2
*/
function getGroupInfo(bytes32 groupId, int32 roundIndex)
external
view
returns(uint16 queriedRound, address curve1, uint8 curve1Status, uint curve1StatusTime, address curve2, uint8 curve2Status, uint curve2StatusTime)
{
GpkTypes.Group storage group = groupMap[groupId];
queriedRound = (roundIndex >= 0)? uint16(uint32(roundIndex)) : group.round;
GpkTypes.Round storage round1 = group.roundMap[queriedRound][0];
GpkTypes.Round storage round2 = group.roundMap[queriedRound][1];
return (queriedRound, round1.curve, uint8(round1.status), round1.statusTime, round2.curve, uint8(round2.status), round2.statusTime);
}
/**
* @notice Gets the polynomial commitment for a storeman
* @param groupId The ID of the storeman group
* @param roundIndex The round index
* @param curveIndex The index of the signature curve
* @param src The source storeman address
* @return polyCommit The polynomial commitment data
*/
function getPolyCommit(bytes32 groupId, uint16 roundIndex, uint8 curveIndex, address src)
external
view
returns(bytes memory polyCommit)
{
GpkTypes.Group storage group = groupMap[groupId];
GpkTypes.Round storage round = group.roundMap[roundIndex][curveIndex];
return round.srcMap[src].polyCommit;
}
/**
* @notice Gets information about a share
* @param groupId The ID of the storeman group
* @param roundIndex The round index
* @param curveIndex The index of the signature curve
* @param src The source storeman address
* @param dest The destination storeman address
* @return encSij The encrypted share data
* @return checkStatus The verification status
* @return setTime The time when the share was set
* @return checkTime The time when the share was verified
* @return sij The share value
* @return ephemPrivateKey The ephemeral private key
*/
function getSijInfo(bytes32 groupId, uint16 roundIndex, uint8 curveIndex, address src, address dest)
external
view
returns(bytes memory encSij, uint8 checkStatus, uint setTime, uint checkTime, uint sij, uint ephemPrivateKey)
{
GpkTypes.Group storage group = groupMap[groupId];
GpkTypes.Round storage round = group.roundMap[roundIndex][curveIndex];
GpkTypes.Dest storage d = round.srcMap[src].destMap[dest];
return (d.encSij, uint8(d.checkStatus), d.setTime, d.checkTime, d.sij, d.ephemPrivateKey);
}
/**
* @notice Gets the GPK share for a storeman
* @param groupId The ID of the storeman group
* @param index The index of the storeman
* @return gpkShare1 The GPK share for curve 1
* @return gpkShare2 The GPK share for curve 2
*/
function getGpkShare(bytes32 groupId, uint16 index)
external
view
returns(bytes memory gpkShare1, bytes memory gpkShare2)
{
GpkTypes.Group storage group = groupMap[groupId];
address src = group.addrMap[index];
mapping(uint8 => GpkTypes.Round) storage roundMap = groupMap[groupId].roundMap[group.round];
return (roundMap[0].srcMap[src].gpkShare, roundMap[1].srcMap[src].gpkShare);
}
/**
* @notice Gets the GPK for a group
* @param groupId The ID of the storeman group
* @return gpk1 The GPK for curve 1
* @return gpk2 The GPK for curve 2
*/
function getGpk(bytes32 groupId)
external
view
returns(bytes memory gpk1, bytes memory gpk2)
{
GpkTypes.Group storage group = groupMap[groupId];
mapping(uint8 => GpkTypes.Round) storage roundMap = groupMap[groupId].roundMap[group.round];
return (roundMap[0].gpk, roundMap[1].gpk);
}
/**
* @notice Gets the GPK for a specific index
* @param groupId The ID of the storeman group
* @param gpkIndex The index of the GPK
* @return gpk The GPK data
*/
function getGpkbyIndex(bytes32 groupId, uint8 gpkIndex)
external
view
returns(bytes memory gpk)
{
GpkTypes.Group storage group = groupMap[groupId];
mapping(uint8 => GpkTypes.Round) storage roundMap = groupMap[groupId].roundMap[group.round];
return roundMap[gpkIndex].gpk;
}
/**
* @notice Gets the GPK share for a specific storeman and index
* @param groupId The ID of the storeman group
* @param smIndex The index of the storeman
* @param gpkIndex The index of the GPK
* @return gpkShare The GPK share data
*/
function getGpkSharebyIndex(bytes32 groupId, uint16 smIndex, uint8 gpkIndex)
external
view
returns(bytes memory gpkShare)
{
GpkTypes.Group storage group = groupMap[groupId];
address src = group.addrMap[smIndex];
mapping(uint8 => GpkTypes.Round) storage roundMap = groupMap[groupId].roundMap[group.round];
return roundMap[gpkIndex].srcMap[src].gpkShare;
}
/**
* @notice Gets the number of GPKs for a group
* @param groupId The ID of the storeman group
* @return count The number of GPKs
*/
function getGpkCount(bytes32 groupId) public view returns(uint count) {
return gpkCount[groupId];
}
/**
* @notice Gets the GPK configuration for a group
* @param groupId The ID of the storeman group
* @param index The index of the configuration
* @return curveIndex The index of the curve
* @return algoIndex The index of the algorithm
*/
function getGpkCfgbyGroup(bytes32 groupId, uint index) external view returns(uint curveIndex, uint algoIndex) {
return (curve[groupId][index], algo[groupId][index]);
}
/**
* @notice Sets the GPK configuration for a group
* @dev Only callable by admin
* @param groupId The ID of the storeman group
* @param curIndex The array of curve indices
* @param algoIndex The array of algorithm indices
* @dev Throws if the arrays are empty or have different lengths
* @dev Emits setGpkCfgEvent on success
*/
function setGpkCfg(bytes32 groupId, uint[] memory curIndex, uint[] memory algoIndex) external onlyAdmin {
require(curIndex.length != 0, "empty curve");
require(curIndex.length == algoIndex.length, "invalid length");
gpkCount[groupId] = curIndex.length;
for(uint i=0; i<gpkCount[groupId]; i++) {
algo[groupId][i] = algoIndex[i];
curve[groupId][i] = curIndex[i];
}
emit setGpkCfgEvent(groupId, gpkCount[groupId]);
}
/**
* @notice Attempts to complete the GPK generation process
* @dev Internal function to handle GPK completion
* @param groupId The ID of the storeman group
* @param smg The address of the storeman group contract
*/
function tryComplete(bytes32 groupId, address smg)
internal
{
GpkTypes.Group storage group = groupMap[groupId];
uint8 i;
for(i=0; i<gpkCount[groupId]; i++) {
if(group.roundMap[group.round][i].status != GpkTypes.GpkStatus.Complete) {
return;
}
}
bytes[] memory gpks = new bytes[](gpkCount[groupId]);
for(i=0; i<gpkCount[groupId]; i++) {
gpks[i] = group.roundMap[group.round][i].gpk;
}
emit GpkCreatedLogger(groupId, group.round, gpks );
GpkTypes.Round storage round1 = group.roundMap[group.round][0];
GpkTypes.Round storage round2 = group.roundMap[group.round][1];
IStoremanGroup(smg).setGpk(groupId, round1.gpk, round2.gpk); // only set 2 gpk for compatible
}
/**
* @notice Gets group information for a specific GPK index
* @param groupId The ID of the storeman group
* @param roundIndex The round index to query (-1 for current round)
* @param gpkIndex The index of the GPK
* @return queriedRound The queried round number
* @return curve The address of the curve
* @return curveStatus The status of the curve
* @return curveStatusTime The status time of the curve
*/
function getGroupInfobyIndex(bytes32 groupId, int32 roundIndex, uint8 gpkIndex)
external
view
returns(uint16 queriedRound, address curve, uint8 curveStatus, uint curveStatusTime)
{
GpkTypes.Group storage group = groupMap[groupId];
queriedRound = (roundIndex >= 0)? uint16(uint32(roundIndex)) : group.round;
GpkTypes.Round storage round = group.roundMap[queriedRound][gpkIndex];
return (queriedRound, round.curve, uint8(round.status), round.statusTime);
}
}// SPDX-License-Identifier: MIT
/*
Copyright 2023 Wanchain Foundation.
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
*/
// _ _ _
// __ ____ _ _ __ ___| |__ __ _(_)_ __ __| | _____ __
// \ \ /\ / / _` | '_ \ / __| '_ \ / _` | | '_ \@/ _` |/ _ \ \ / /
// \ V V / (_| | | | | (__| | | | (_| | | | | | (_| | __/\ V /
// \_/\_/ \__,_|_| |_|\___|_| |_|\__,_|_|_| |_|\__,_|\___| \_/
//
// Code style according to: https://github.com/wanchain/wanchain-token/blob/master/style-guide.rst
pragma solidity 0.8.18;
import "../components/Admin.sol";
import "./GpkStorage.sol";
import "../components/Proxy.sol";
/**
* @title GpkProxy
* @dev Proxy contract for Group Public Key (GPK) functionality
* This contract implements the proxy pattern for upgradeable GPK contracts,
* allowing the implementation to be upgraded while maintaining the same storage
*/
contract GpkProxy is GpkStorage, Admin, Proxy {
/**
* @dev Contract upgrade functionality
*/
/**
* @notice Upgrades the GPK implementation to a new version
* @dev Only callable by the contract owner
* @param impl The address of the new GpkDelegate contract implementation
* @dev Throws if the new implementation address is invalid or the same as current
* @dev Emits an Upgraded event on successful upgrade
*/
function upgradeTo(address impl) external onlyOwner {
require(impl != address(0), "Cannot upgrade to invalid address");
require(impl != _implementation, "Cannot upgrade to the same implementation");
_implementation = impl;
emit Upgraded(impl);
}
}// SPDX-License-Identifier: MIT
/*
Copyright 2023 Wanchain Foundation.
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
*/
// _ _ _
// __ ____ _ _ __ ___| |__ __ _(_)_ __ __| | _____ __
// \ \ /\ / / _` | '_ \ / __| '_ \ / _` | | '_ \@/ _` |/ _ \ \ / /
// \ V V / (_| | | | | (__| | | | (_| | | | | | (_| | __/\ V /
// \_/\_/ \__,_|_| |_|\___|_| |_|\__,_|_|_| |_|\__,_|\___| \_/
//
// Code style according to: https://github.com/wanchain/wanchain-token/blob/master/style-guide.rst
pragma solidity ^0.8.18;
import "../components/BasicStorage.sol";
import "./lib/GpkTypes.sol";
/**
* @title GpkStorage
* @dev Storage contract for Group Public Key (GPK) functionality
* This contract serves as the storage layer for the GPK system, inheriting from BasicStorage
* and providing storage for group-related data and configuration
*/
contract GpkStorage is BasicStorage {
/// @notice Address of the Storeman Group (SMG) instance
address public smg;
/// @notice Address of the GPK configuration contract
address public cfg;
/// @notice Mapping from group ID to Group structure
/// @dev Stores all group-related information indexed by their unique identifiers
mapping(bytes32 => GpkTypes.Group) public groupMap;
}// SPDX-License-Identifier: MIT
/*
Copyright 2023 Wanchain Foundation.
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
*/
// _ _ _
// __ ____ _ _ __ ___| |__ __ _(_)_ __ __| | _____ __
// \ \ /\ / / _` | '_ \ / __| '_ \ / _` | | '_ \@/ _` |/ _ \ \ / /
// \ V V / (_| | | | | (__| | | | (_| | | | | | (_| | __/\ V /
// \_/\_/ \__,_|_| |_|\___|_| |_|\__,_|_|_| |_|\__,_|\___| \_/
//
// Code style according to: https://github.com/wanchain/wanchain-token/blob/master/style-guide.rst
pragma solidity ^0.8.18;
import "../components/BasicStorage.sol";
import "./lib/GpkTypes.sol";
import "./GpkStorage.sol";
import "../components/Admin.sol";
import "../components/Proxy.sol";
/**
* @title GpkStorageV2
* @dev Extended storage contract for Group Public Key (GPK) functionality
* This contract extends GpkStorage with additional storage for multiple GPK configurations
* per group, including curve and algorithm information
*/
contract GpkStorageV2 is GpkStorage, Admin, Proxy {
/// @notice Mapping from group ID to the number of GPK configurations
/// @dev Tracks how many different GPK configurations exist for each group
mapping(bytes32=>uint) public gpkCount;
/// @notice Mapping from group ID and GPK index to curve identifier
/// @dev Stores the curve type for each GPK configuration
mapping(bytes32=>mapping(uint=>uint)) public curve;
/// @notice Mapping from group ID and GPK index to algorithm identifier
/// @dev Stores the algorithm type for each GPK configuration
mapping(bytes32=>mapping(uint=>uint)) public algo;
}// SPDX-License-Identifier: MIT
/*
Copyright 2023 Wanchain Foundation.
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
*/
// _ _ _
// __ ____ _ _ __ ___| |__ __ _(_)_ __ __| | _____ __
// \ \ /\ / / _` | '_ \ / __| '_ \ / _` | | '_ \@/ _` |/ _ \ \ / /
// \ V V / (_| | | | | (__| | | | (_| | | | | | (_| | __/\ V /
// \_/\_/ \__,_|_| |_|\___|_| |_|\__,_|_|_| |_|\__,_|\___| \_/
//
// Code style according to: https://github.com/wanchain/wanchain-token/blob/master/style-guide.rst
pragma solidity ^0.8.18;
import "../../lib/CommonTool.sol";
import "../../interfaces/IStoremanGroup.sol";
import "../../interfaces/ICurve.sol";
import "../../interfaces/IConfig.sol";
import "./GpkTypes.sol";
import "../../storemanGroupAdmin/StoremanType.sol";
/**
* @title GpkLib
* @dev Library containing core functionality for Group Public Key (GPK) operations
* This library implements the cryptographic operations and protocol logic for GPK generation
*/
library GpkLib {
/// @dev Default timeout period (2 hours)
uint32 constant DEFAULT_PERIOD = 2 * 60 * 60; // 2 hours
/// @dev Period for polynomial commitment submission (48 hours)
uint32 constant PLOYCOMMIT_PERIOD = 48 * 60 * 60; // 48 hours
/// @dev Period for negotiation phase (6 hours)
uint32 constant NEGOTIATE_PERIOD = 6 * 60 * 60; // 6 hours
/**
* @dev Events for tracking GPK operations
*/
/**
* @notice Emitted when a new GPK is created
* @param groupId The ID of the storeman group
* @param round The current negotiation round
* @param gpk1 The group public key for chain1
* @param gpk2 The group public key for chain2
*/
event GpkCreatedLogger(bytes32 indexed groupId, uint16 indexed round, bytes gpk1, bytes gpk2);
/**
* @notice Emitted when a storeman is slashed
* @param groupId The ID of the storeman group
* @param slashType The reason for slashing
* @param slashed The address of the slashed storeman
* @param partner The negotiation partner
* @param round The current negotiation round
* @param curveIndex The index of the signature curve
*/
event SlashLogger(bytes32 indexed groupId, uint8 indexed slashType, address indexed slashed, address partner, uint16 round, uint8 curveIndex);
/**
* @notice Emitted when the group protocol is reset
* @param groupId The ID of the storeman group
* @param round The current negotiation round
*/
event ResetLogger(bytes32 indexed groupId, uint16 indexed round);
/**
* @notice Emitted when the group protocol is closed
* @param groupId The ID of the storeman group
* @param round The maximum negotiation round
*/
event CloseLogger(bytes32 indexed groupId, uint16 indexed round);
/**
* @dev Core GPK operations
*/
/**
* @notice Initializes a storeman group with its configuration
* @dev Sets up periods, curves, and retrieves storeman information
* @param groupId The ID of the storeman group
* @param group The group structure to initialize
* @param cfg The configuration contract address
* @param smg The storeman group contract address
* @param curves Array of curve identifiers (sec256:0, bn256:1)
* @dev Throws if the group status is invalid
*/
function initGroup(bytes32 groupId, GpkTypes.Group storage group, address cfg, address smg, uint[] memory curves)
public
{
// init period
if (group.defaultPeriod == 0) {
group.defaultPeriod = DEFAULT_PERIOD;
group.ployCommitPeriod = PLOYCOMMIT_PERIOD;
group.negotiatePeriod = NEGOTIATE_PERIOD;
}
// init signature curve
uint8 status;
(,status,,,,,,,,,) = IStoremanGroup(smg).getStoremanGroupConfig(groupId);
require(status == uint8(StoremanType.GroupStatus.selected), "Invalid stage");
uint8 i;
for(i=0; i<curves.length; i++){
group.roundMap[group.round][i].curve = IConfig(cfg).getCurve(uint8(curves[i]));
}
group.groupId = groupId;
// retrieve selected sm list
group.smNumber = uint16(IStoremanGroup(smg).getSelectedSmNumber(groupId));
address txAddress;
bytes memory pk;
for (i = 0; i < group.smNumber; i++) {
(txAddress, pk,) = IStoremanGroup(smg).getSelectedSmInfo(groupId, i);
group.addrMap[i] = txAddress;
group.indexMap[txAddress] = i;
group.pkMap[txAddress] = pk;
}
}
/**
* @notice Updates the group public key with a new polynomial commitment
* @dev Performs elliptic curve addition to combine commitments
* @param round The current round structure
* @param polyCommit The polynomial commitment to add
* @dev Throws if the curve addition fails
*/
function updateGpk(GpkTypes.Round storage round, bytes memory polyCommit)
public
{
bytes memory gpk = round.gpk;
uint x = CommonTool.bytes2uint(polyCommit, 0, 32);
uint y = CommonTool.bytes2uint(polyCommit, 32, 32);
if (gpk.length != 0) {
uint gpkX = CommonTool.bytes2uint(gpk, 0, 32);
uint gpkY = CommonTool.bytes2uint(gpk, 32, 32);
bool success;
(x, y, success) = ICurve(round.curve).add(x, y, gpkX, gpkY);
require(success == true, "Gpk failed");
} else {
gpk = new bytes(64);
}
assembly { mstore(add(gpk, 32), x) }
assembly { mstore(add(gpk, 64), y) }
round.gpk = gpk;
}
/**
* @notice Updates the GPK shares for all storemen
* @dev Calculates and stores individual shares of the group public key
* @param group The storeman group structure
* @param round The current round structure
* @param polyCommit The polynomial commitment to process
* @dev Throws if polynomial commitment calculation or curve addition fails
*/
function updateGpkShare(GpkTypes.Group storage group, GpkTypes.Round storage round, bytes memory polyCommit)
public
{
uint x;
uint y;
bool success;
for (uint i = 0; i < group.smNumber; i++) {
address txAddress = group.addrMap[i];
bytes memory pk = group.pkMap[txAddress];
(x, y, success) = ICurve(round.curve).calPolyCommit(polyCommit, pk);
require(success == true, "PolyCommit failed");
bytes memory gpkShare = round.srcMap[txAddress].gpkShare;
if (gpkShare.length != 0) {
uint pkX = CommonTool.bytes2uint(gpkShare, 0, 32);
uint pkY = CommonTool.bytes2uint(gpkShare, 32, 32);
(x, y, success) = ICurve(round.curve).add(x, y, pkX, pkY);
require(success == true, "Add failed");
} else {
gpkShare = new bytes(64);
}
assembly { mstore(add(gpkShare, 32), x) }
assembly { mstore(add(gpkShare, 64), y) }
round.srcMap[txAddress].gpkShare = gpkShare;
}
}
/**
* @notice Verifies a secret share against a challenge
* @dev Checks both the share value and its encryption
* @param d The destination structure containing the share
* @param destPk The public key of the destination storeman
* @param polyCommit The polynomial commitment of the source storeman
* @param curve The curve contract address
* @return bool Whether the verification was successful
*/
function verifySij(GpkTypes.Dest storage d, bytes memory destPk, bytes memory polyCommit, address curve)
public
view
returns(bool)
{
// check sij
uint x;
uint y;
uint pcX;
uint pcY;
bool success;
(x, y, success) = ICurve(curve).mulG(d.sij);
if (success) {
(pcX, pcY, success) = ICurve(curve).calPolyCommit(polyCommit, destPk);
if (success && (x == pcX) && (y == pcY)) {
// check enc
uint iv = CommonTool.bytes2uint(d.encSij, 65, 16);
bytes memory cipher;
(cipher, success) = CommonTool.enc(bytes32(d.ephemPrivateKey), bytes32(iv), d.sij, destPk);
if (success) {
return CommonTool.cmpBytes(d.encSij, cipher);
}
}
}
return false;
}
/**
* @notice Handles slashing of a storeman
* @dev Updates slash status and emits events
* @param group The storeman group structure
* @param curveIndex The index of the signature curve
* @param slashType The reason for slashing
* @param slashed The address of the slashed storeman
* @param parter The negotiation partner
* @param toReset Whether to reset the protocol immediately
* @param smg The storeman group contract address
*/
function slash(GpkTypes.Group storage group, uint8 curveIndex, GpkTypes.SlashType slashType,
address slashed, address parter, bool toReset, address smg)
public
{
GpkTypes.Src storage src = group.roundMap[group.round][curveIndex].srcMap[slashed];
if (src.slashType == GpkTypes.SlashType.None) {
group.roundMap[group.round][curveIndex].slashCount++;
}
if ((slashType == GpkTypes.SlashType.SijInvalid)
|| (slashType == GpkTypes.SlashType.CheckInvalid)
|| (src.slashType == GpkTypes.SlashType.None)
|| (src.slashType == GpkTypes.SlashType.Connive)) {
src.slashType = slashType;
}
emit SlashLogger(group.groupId, uint8(slashType), slashed, parter, group.round, curveIndex);
if (toReset) {
uint[] memory ids = new uint[](1);
ids[0] = group.indexMap[slashed];
uint8[] memory types = new uint8[](1);
types[0] = uint8(slashType);
bool isContinue = IStoremanGroup(smg).setInvalidSm(group.groupId, ids, types);
reset(group, isContinue);
}
}
/**
* @notice Handles slashing of multiple storemen
* @dev Processes slashing for all invalid storemen in a round
* @param group The storeman group structure
* @param curveIndex The index of the signature curve
* @param smg The storeman group contract address
*/
function slashMulti(GpkTypes.Group storage group, uint8 curveIndex, address smg)
public
{
GpkTypes.Round storage round = group.roundMap[group.round][curveIndex];
uint[] memory ids = new uint[](round.slashCount);
uint8[] memory types = new uint8[](round.slashCount);
uint slashCount = 0;
for (uint i = 0; (i < group.smNumber) && (slashCount < round.slashCount); i++) {
GpkTypes.Src storage src = round.srcMap[group.addrMap[i]];
if (src.slashType != GpkTypes.SlashType.None) {
ids[slashCount] = i;
types[slashCount] = uint8(src.slashType);
slashCount++;
}
}
bool isContinue = IStoremanGroup(smg).setInvalidSm(group.groupId, ids, types);
reset(group, isContinue);
}
/**
* @notice Resets the GPK protocol for a group
* @dev Handles protocol reset and group closure
* @param group The storeman group structure
* @param isContinue Whether to continue with the protocol
*/
function reset(GpkTypes.Group storage group, bool isContinue)
public
{
uint8 i;
while(true) {
GpkTypes.Round storage round = group.roundMap[group.round][i++];
if(round.curve == address(0)) {
break;
}
round.status = GpkTypes.GpkStatus.Close;
round.statusTime = block.timestamp;
}
// clear data
for (i = 0; i < group.smNumber; i++) {
delete group.pkMap[group.addrMap[i]];
delete group.indexMap[group.addrMap[i]];
delete group.addrMap[i];
}
group.smNumber = 0;
if (isContinue) {
emit ResetLogger(group.groupId, group.round);
group.round++;
} else {
emit CloseLogger(group.groupId, group.round);
}
}
}// SPDX-License-Identifier: MIT
/*
Copyright 2023 Wanchain Foundation.
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
*/
// _ _ _
// __ ____ _ _ __ ___| |__ __ _(_)_ __ __| | _____ __
// \ \ /\ / / _` | '_ \ / __| '_ \ / _` | | '_ \@/ _` |/ _ \ \ / /
// \ V V / (_| | | | | (__| | | | (_| | | | | | (_| | __/\ V /
// \_/\_/ \__,_|_| |_|\___|_| |_|\__,_|_|_| |_|\__,_|\___| \_/
//
// Code style according to: https://github.com/wanchain/wanchain-token/blob/master/style-guide.rst
pragma solidity ^0.8.18;
/**
* @title GpkTypes
* @dev Library containing type definitions for Group Public Key (GPK) functionality
* This library defines the core data structures and enums used throughout the GPK system
*/
library GpkTypes {
/**
* @dev Structure representing a storeman group
* @param groupId Unique identifier for the group
* @param round Current negotiation round number
* @param ployCommitPeriod Time period for polynomial commitment submission
* @param defaultPeriod Default timeout period
* @param negotiatePeriod Time period for negotiation phase
* @param roundMap Mapping of rounds and curve indices to Round structures
* @param smNumber Number of storemen in the group
* @param addrMap Mapping of indices to storeman addresses
* @param indexMap Mapping of storeman addresses to their indices
* @param pkMap Mapping of storeman addresses to their public keys
*/
struct Group {
bytes32 groupId;
uint16 round;
uint32 ployCommitPeriod;
uint32 defaultPeriod;
uint32 negotiatePeriod;
/// round -> curveIndex -> Round
mapping(uint16 => mapping(uint8 => Round)) roundMap;
uint16 smNumber;
/// index -> txAddress
mapping(uint => address) addrMap;
/// txAddress -> slectedIndex
mapping(address => uint) indexMap;
/// txAddress -> pk
mapping(address => bytes) pkMap;
}
/**
* @dev Structure representing a round of GPK generation
* @param curve Address of the curve contract used for cryptographic operations
* @param status Current status of the round
* @param polyCommitCount Number of polynomial commitments received
* @param checkValidCount Number of valid checks performed
* @param slashCount Number of slashing events
* @param statusTime Timestamp of the last status change
* @param gpk Generated group public key
* @param srcMap Mapping of storeman addresses to their source data
*/
struct Round {
address curve;
GpkStatus status;
uint16 polyCommitCount;
uint32 checkValidCount;
uint16 slashCount;
uint statusTime;
bytes gpk;
/// txAddress -> Src
mapping(address => Src) srcMap;
}
/**
* @dev Enumeration of possible GPK round statuses
* @param PolyCommit Phase where storemen submit polynomial commitments
* @param Negotiate Phase where storemen negotiate and verify shares
* @param Complete Phase where GPK generation is completed
* @param Close Phase where the round is closed
*/
enum GpkStatus {PolyCommit, Negotiate, Complete, Close}
/**
* @dev Structure representing source data for a storeman
* @param polyCommit Polynomial commitment submitted by the storeman
* @param gpkShare Share of the group public key
* @param destMap Mapping of destination addresses to their data
* @param checkValidCount Number of valid checks received
* @param slashType Type of slashing event if any
*/
struct Src {
bytes polyCommit;
bytes gpkShare;
/// txAddress -> Dest
mapping(address => Dest) destMap;
uint16 checkValidCount;
SlashType slashType;
}
/**
* @dev Structure representing destination data for a storeman
* @param checkStatus Status of the share verification
* @param setTime Timestamp when the encrypted share was set
* @param checkTime Timestamp when the share was checked
* @param sij Secret share value
* @param ephemPrivateKey Ephemeral private key for encryption
* @param encSij Encrypted secret share
*/
struct Dest {
CheckStatus checkStatus;
uint setTime;
uint checkTime;
uint sij;
uint ephemPrivateKey;
bytes encSij;
}
/**
* @dev Enumeration of possible check statuses
* @param Init Initial state
* @param Valid Share is valid
* @param Invalid Share is invalid
*/
enum CheckStatus {Init, Valid, Invalid}
/**
* @dev Enumeration of possible slashing types
* @param None No slashing
* @param PolyCommitTimeout Slashing for timeout in polynomial commitment phase
* @param EncSijTimout Slashing for timeout in encrypted share submission
* @param CheckTimeout Slashing for timeout in share verification
* @param SijTimeout Slashing for timeout in share revelation
* @param SijInvalid Slashing for invalid share
* @param CheckInvalid Slashing for invalid verification
* @param Connive Slashing for collusion
*/
enum SlashType {None, PolyCommitTimeout, EncSijTimout, CheckTimeout, SijTimeout, SijInvalid, CheckInvalid, Connive}
}// SPDX-License-Identifier: MIT
pragma solidity 0.8.18;
import "./IMPC.sol";
import "./ICross.sol";
/**
* @title GroupApprove
* @dev Contract that implements a multi-signature governance mechanism for cross-chain operations
* where proposals require approval from a storeman group before execution
*
* @custom:architecture This contract acts as a governance layer for critical cross-chain operations,
* requiring both foundation proposal and storeman group approval for actions
*/
contract GroupApprove {
/**
* @dev Structure representing an executable task
* @param to Target contract address to call
* @param data Call data to be executed
* @param executed Flag indicating if the task has been executed
*/
struct Task {
address to;
bytes data;
bool executed;
}
/**
* @dev Structure for signature verification data
* @param sigHash Hash of the message being signed
* @param smgID Storeman group ID that signed the message
* @param r R component of the signature (contains X and Y coordinates)
* @param s S component of the signature
*/
struct SigData {
bytes32 sigHash;
bytes32 smgID;
bytes r;
bytes32 s;
}
// slip-0044 chainId
uint256 public chainId;
uint256 public taskCount;
address public foundation;
address public signatureVerifier;
address public oracle;
// proposalId => task
mapping(uint256 => Task) public tasks;
/**
* @dev Enum representing various states a storeman group can be in
*/
enum GroupStatus { none, initial, curveSet, failed, selected, ready, unregistered, dismissed }
/**
* @dev Restricts function access to the foundation address
*/
modifier onlyFoundation() {
require(msg.sender == foundation, "not foundation");
_;
}
/**
* @dev Restricts function access to the contract itself
* Used for functions that should only be called via executing a task
*/
modifier onlySelf() {
require(msg.sender == address(this), "not self");
_;
}
/**
* @dev Validates signature from a storeman group before executing a function
* @param proposalId ID of the proposal being approved
* @param smgID ID of the storeman group signing the approval
* @param r R component of the signature
* @param s S component of the signature
*/
modifier onlySmg(uint proposalId, bytes32 smgID, bytes calldata r, bytes32 s) {
bytes32 sigHash = keccak256(abi.encode(proposalId, chainId));
_verifyMpcSignature(
SigData(
sigHash, smgID, r, s
)
);
_;
}
/**
* @dev Emitted when a new proposal is created
* @param proposalId Unique identifier for the proposal
* @param to Target contract address to call
* @param data Call data to be executed
*/
event Proposal(
uint256 indexed proposalId,
address indexed to,
bytes data
);
/**
* @dev Emitted when a proposal is approved and executed
* @param proposalId Unique identifier for the proposal
* @param to Target contract address that was called
* @param data Call data that was executed
* @param smgID ID of the storeman group that approved the proposal
*/
event ApprovedAndExecuted(
uint256 indexed proposalId,
address indexed to,
bytes data,
bytes32 smgID
);
/**
* @dev Emitted when the foundation address is transferred
* @param oldFoundation Previous foundation address
* @param newFoundation New foundation address
*/
event TransferFoundation(
address indexed oldFoundation,
address indexed newFoundation
);
/**
* @dev Error thrown when signature verification fails
* @param smgID ID of the storeman group
* @param sigHash Hash of the signed message
* @param r R component of the signature
* @param s S component of the signature
*/
error SignatureVerifyFailed(
bytes32 smgID,
bytes32 sigHash,
bytes r,
bytes32 s
);
/**
* @dev Error thrown when a storeman group is not in ready state
* @param smgID ID of the storeman group
* @param status Current status of the storeman group
* @param timestamp Current block timestamp
* @param startTime Start time of the storeman group
* @param endTime End time of the storeman group
*/
error StoremanGroupNotReady(
bytes32 smgID,
uint256 status,
uint256 timestamp,
uint256 startTime,
uint256 endTime
);
/**
* @dev Constructor initializes the contract with key addresses and validates cross-chain configuration
* @param _foundation Address of the foundation that can create proposals
* @param _signatureVerifier Address of the contract used to verify signatures
* @param _oracle Address of the oracle contract that provides storeman group information
* @param _cross Address of the cross-chain bridge contract
*
* @notice Validates that the provided oracle and signatureVerifier match the ones in the cross contract
*/
constructor(address _foundation, address _signatureVerifier, address _oracle, address _cross) {
require(_foundation != address(0), "foundation is empty");
address _oracleCross;
address _signatureVerifierCross;
// cross check oracle and signatureVerifier address with cross contract
(, _oracleCross, , , _signatureVerifierCross) = ICross(_cross).getPartners();
oracle = _oracle;
signatureVerifier = _signatureVerifier;
require(_oracle == _oracleCross, "oracle not match");
require(_signatureVerifier == _signatureVerifierCross, "signatureVerifier not match");
chainId = ICross(_cross).currentChainID(); // read from cross
require(chainId != 0, "chainId is empty");
foundation = _foundation;
}
/**
* @dev Creates a new proposal to be executed after storeman group approval
* @param _chainId Chain ID for which the proposal is intended
* @param _to Target contract address to call
* @param _data Call data to be executed
*
* @notice Only the foundation can create proposals
* @notice The chain ID must match the current chain ID
*/
function proposal(
uint256 _chainId,
address _to,
bytes memory _data
) external onlyFoundation {
require(_data.length > 0, "data is empty");
require(_to != address(0), "to is empty");
require(_chainId == chainId, "chainId not match");
// save task
tasks[taskCount] = Task(_to, _data, false);
emit Proposal(taskCount, _to, _data);
taskCount++;
}
/**
* @dev Approves and executes a proposal with storeman group signature
* @param proposalId ID of the proposal to execute
* @param smgID ID of the storeman group providing approval
* @param r R component of the signature
* @param s S component of the signature
*
* @notice Requires valid signature from an active storeman group
* @notice The task must not have been executed before
*/
function approveAndExecute(
uint256 proposalId,
bytes32 smgID,
bytes calldata r,
bytes32 s
) external onlySmg(proposalId, smgID, r, s) {
Task storage task = tasks[proposalId];
require(task.to != address(0), "task not exists");
require(!task.executed, "task already executed");
(bool success, ) = task.to.call(task.data);
require(success, "call failed");
task.executed = true;
emit ApprovedAndExecuted(proposalId, task.to, task.data, smgID);
}
/**
* @dev Sets the halt status of a cross-chain contract
* @param _to Address of the cross-chain contract to halt or resume
* @param _halt True to halt, false to resume
*
* @notice Only the foundation can call this function
*/
function halt(address _to, bool _halt) external onlyFoundation {
ICross(_to).setHalt(_halt);
}
/**
* @dev Transfers the foundation role to a new address
* @param _newFoundation Address of the new foundation
*
* @notice This function can only be called through executing a task
* @notice Ensures security by requiring both foundation proposal and storeman approval
*/
function transferFoundation(address _newFoundation) external onlySelf {
require(_newFoundation != address(0), "new foundation is empty");
require(_newFoundation != foundation, "new foundation is same as old");
foundation = _newFoundation;
emit TransferFoundation(foundation, _newFoundation);
}
// -------- internal functions --------
/**
* @notice Check if a storeman group is ready and retrieve its cryptographic information
* @param smgID ID of the storeman group to check
* @return curveID ID of the elliptic curve used by the storeman group
* @return PK Public key of the storeman group
*
* @dev Reverts if the storeman group is not in ready state or outside its active time window
*/
function _acquireReadySmgInfo(bytes32 smgID)
internal
view
returns (uint curveID, bytes memory PK)
{
uint8 status;
uint startTime;
uint endTime;
(,status,,,,curveID,,PK,,startTime,endTime) = IMPC(oracle).getStoremanGroupConfig(smgID);
if (!(status == uint8(GroupStatus.ready) && block.timestamp >= startTime && block.timestamp <= endTime)) {
revert StoremanGroupNotReady({
smgID: smgID,
status: uint256(status),
timestamp: block.timestamp,
startTime: startTime,
endTime: endTime
});
}
return (curveID, PK);
}
/**
* @notice Convert bytes to bytes32 at a specific offset
* @param b Bytes array to convert
* @param offset Offset in the bytes array
* @return result The bytes32 value at the specified offset
*
* @dev Uses assembly for gas-efficient conversion
*/
function _bytesToBytes32(bytes memory b, uint offset) internal pure returns (bytes32 result) {
assembly {
result := mload(add(add(b, offset), 32))
}
}
/**
* @dev Verifies an MPC signature for a given message and Storeman Group ID
* @param sig The signature data structure containing all verification components
*
* @dev Reverts with SignatureVerifyFailed if verification fails
* @dev Uses the external signature verifier contract for the actual verification
*/
function _verifyMpcSignature(SigData memory sig) internal {
uint curveID;
bytes memory PK;
// Acquire the curve ID and group public key for the given Storeman Group ID
(curveID, PK) = _acquireReadySmgInfo(sig.smgID);
// Extract the X and Y components of the group public key
bytes32 PKx = _bytesToBytes32(PK, 0);
bytes32 PKy = _bytesToBytes32(PK, 32);
// Extract the X and Y components of the signature
bytes32 Rx = _bytesToBytes32(sig.r, 0);
bytes32 Ry = _bytesToBytes32(sig.r, 32);
// Verify the signature using the Wanchain MPC contract
if (!IMPC(signatureVerifier).verify(curveID, sig.s, PKx, PKy, Rx, Ry, sig.sigHash)) {
revert SignatureVerifyFailed({
smgID: sig.smgID,
sigHash: sig.sigHash,
r: sig.r,
s: sig.s
});
}
}
}// SPDX-License-Identifier: MIT
pragma solidity 0.8.18;
/**
* @title ICross
* @dev Interface for the cross-chain functionality contract that handles asset transfers and communication between chains
*
* @custom:key-features
* - Enable/disable cross-chain functionality
* - Get related contract addresses
* - Get current chain ID information
*/
interface ICross {
/**
* @dev Sets the halt status of the cross-chain contract
*
* @param halt true to halt cross-chain functionality, false to resume
*/
function setHalt(bool halt) external;
/**
* @dev Gets the addresses of related contracts in the cross-chain system
*
* @return tokenManager Token manager contract address
* @return smgAdminProxy Storeman group admin proxy contract address
* @return smgFeeProxy Storeman group fee proxy contract address
* @return quota Quota management contract address
* @return sigVerifier Signature verification contract address
*
* @custom:usage
* - Used to verify key contract addresses
* - Ensures system component consistency
*/
function getPartners() external view returns(address tokenManager, address smgAdminProxy, address smgFeeProxy, address quota, address sigVerifier);
/**
* @dev Gets the ID of the current blockchain
*
* @return uint256 The unique identifier of the current blockchain
*
* @custom:usage
* - Used in cross-chain transactions to verify target chains
* - Ensures messages are sent to the correct target chain
*/
function currentChainID() external view returns (uint256);
}// SPDX-License-Identifier: MIT
pragma solidity 0.8.18;
/**
* @title IMPC
* @dev Interface for Multi-Party Computation (MPC) operations related to storeman groups
*
* @custom:key-features
* - Storeman group configuration retrieval
* - Threshold signature verification
* - Cross-chain security validation
*
* @custom:security
* - Cryptographic signature verification
* - Elliptic curve operations
* - Group public key management
*/
interface IMPC {
/**
* @dev Retrieves the configuration of a Storeman Group by ID
* @param id The ID of the Storeman Group to retrieve
* @return groupId The group ID of the Storeman Group
* @return status The status of the Storeman Group
* @return deposit The deposit amount of the Storeman Group
* @return chain1 The ID of the first chain supported by the Storeman Group
* @return chain2 The ID of the second chain supported by the Storeman Group
* @return curve1 The ID of the first elliptic curve supported by the Storeman Group
* @return curve2 The ID of the second elliptic curve supported by the Storeman Group
* @return gpk1 The Group Public Key for the first elliptic curve
* @return gpk2 The Group Public Key for the second elliptic curve
* @return startTime The start time of the Storeman Group
* @return endTime The end time of the Storeman Group
*
* @custom:usage
* - Used to validate storeman group status
* - Provides cryptographic parameters for signature verification
* - Ensures group is active within its time window
*/
function getStoremanGroupConfig(
bytes32 id
) external view returns (
bytes32 groupId,
uint8 status,
uint deposit,
uint chain1,
uint chain2,
uint curve1,
uint curve2,
bytes memory gpk1,
bytes memory gpk2,
uint startTime,
uint endTime
);
/**
* @dev Verifies a signature using the provided parameters
* @param curveId The ID of the elliptic curve used for the signature
* @param signature The signature to be verified
* @param groupKeyX The X component of the group public key
* @param groupKeyY The Y component of the group public key
* @param randomPointX The X component of the random point
* @param randomPointY The Y component of the random point
* @param message The message that was signed
* @return true if the signature is valid, false otherwise
*
* @custom:security
* - Performs threshold signature verification
* - Uses secure elliptic curve cryptography
* - Critical for cross-chain message authentication
* - Protects against signature forgery attacks
*/
function verify(
uint curveId,
bytes32 signature,
bytes32 groupKeyX,
bytes32 groupKeyY,
bytes32 randomPointX,
bytes32 randomPointY,
bytes32 message
) external returns (bool);
}// SPDX-License-Identifier: MIT
/*
Copyright 2023 Wanchain Foundation.
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
*/
// _ _ _
// __ ____ _ _ __ ___| |__ __ _(_)_ __ __| | _____ __
// \ \ /\ / / _` | '_ \ / __| '_ \ / _` | | '_ \@/ _` |/ _ \ \ / /
// \ V V / (_| | | | | (__| | | | (_| | | | | | (_| | __/\ V /
// \_/\_/ \__,_|_| |_|\___|_| |_|\__,_|_|_| |_|\__,_|\___| \_/
//
//
pragma solidity ^0.8.18;
interface IConfig {
function getCurve(uint8 curveId) external returns(address);
}// SPDX-License-Identifier: MIT
/*
Copyright 2023 Wanchain Foundation.
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
*/
// _ _ _
// __ ____ _ _ __ ___| |__ __ _(_)_ __ __| | _____ __
// \ \ /\ / / _` | '_ \ / __| '_ \ / _` | | '_ \@/ _` |/ _ \ \ / /
// \ V V / (_| | | | | (__| | | | (_| | | | | | (_| | __/\ V /
// \_/\_/ \__,_|_| |_|\___|_| |_|\__,_|_|_| |_|\__,_|\___| \_/
//
//
pragma solidity ^0.8.18;
interface ICurve {
function add(uint256 x1, uint256 y1, uint256 x2, uint256 y2) external view returns(uint256 retx, uint256 rety, bool success);
function mulG(uint256 scalar) external view returns(uint256 x, uint256 y, bool success);
function calPolyCommit(bytes memory polyCommit, bytes memory pk) external view returns(uint256 sx, uint256 sy, bool success);
function mulPk(uint256 scalar, uint256 xPk, uint256 yPk)
external
view
returns (uint256 x, uint256 y, bool success);
function equalPt (uint256 xLeft, uint256 yLeft,uint256 xRight, uint256 yRight) external view returns(bool);
function checkSig (bytes32 hash, bytes32 r, bytes32 s, bytes memory pk) external view returns(bool);
}// SPDX-License-Identifier: MIT
/*
Copyright 2023 Wanchain Foundation.
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
*/
// _ _ _
// __ ____ _ _ __ ___| |__ __ _(_)_ __ __| | _____ __
// \ \ /\ / / _` | '_ \ / __| '_ \ / _` | | '_ \@/ _` |/ _ \ \ / /
// \ V V / (_| | | | | (__| | | | (_| | | | | | (_| | __/\ V /
// \_/\_/ \__,_|_| |_|\___|_| |_|\__,_|_|_| |_|\__,_|\___| \_/
//
//
pragma solidity ^0.8.18;
interface IGPK {
function getGpkShare(bytes32 groupId, uint index) external view returns(bytes memory gpkShare1, bytes memory gpkShare2);
function getGpk(bytes32 groupId) external view returns(bytes memory gpk1, bytes memory gpk2);
function getGpkCount(bytes32 groupId) external view returns(uint count);
function getGpkbyIndex(bytes32 groupId, uint8 gpkIndex) external view returns(bytes memory gpk);
function getGpkSharebyIndex(bytes32 groupId, uint16 smIndex, uint8 gpkIndex) external view returns(bytes memory gpkShare);
function getGpkCfgbyGroup(bytes32 groupId, uint index) external view returns(uint curveIndex, uint algoIndex);
function setGpkCfg(bytes32 groupId, uint[] memory curIndex, uint[] memory algoIndex) external;
}// SPDX-License-Identifier: MIT
pragma solidity ^0.8.18;
interface IListGroup {
function addActiveGroup(bytes32 groupId, uint startTime, uint endTime) external;
function getActiveGroupIds(uint day) external view returns (bytes32[] memory);
function getTotalDeposit(uint day) external view returns(uint);
function setTotalDeposit(uint day, uint value) external;
function cleanExpiredGroup() external;
function getDelegateQuitGroupId(address wkAddr, address deAddr) external view returns (bytes32 groupId, bytes32 nextGroupId);
function getPartQuitGroupId(address wkAddr, address pnAddr) external view returns (bytes32 groupId, bytes32 nextGroupId);
function setDelegateQuitGroupId(address wkAddr, address deAddr, bytes32 groupId, bytes32 nextGroupId)external;
function setPartQuitGroupId(address wkAddr, address pnAddr, bytes32 groupId, bytes32 nextGroupId) external;
}// SPDX-License-Identifier: MIT
/*
Copyright 2023 Wanchain Foundation.
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
*/
// _ _ _
// __ ____ _ _ __ ___| |__ __ _(_)_ __ __| | _____ __
// \ \ /\ / / _` | '_ \ / __| '_ \ / _` | | '_ \@/ _` |/ _ \ \ / /
// \ V V / (_| | | | | (__| | | | (_| | | | | | (_| | __/\ V /
// \_/\_/ \__,_|_| |_|\___|_| |_|\__,_|_|_| |_|\__,_|\___| \_/
//
//
pragma solidity ^0.8.18;
interface IMetric {
// index of array: smIndex
function getPrdInctMetric(bytes32 grpId, uint startEpId, uint endEpId) external returns(uint[] memory);
function getPrdSlshMetric(bytes32 grpId, uint startEpId, uint endEpId) external returns(uint[] memory);
function getSmSuccCntByEpId(bytes32 grpId, uint epId, uint8 smIndex) external returns(uint);
function getSlshCntByEpId(bytes32 grpId, uint epId, uint8 smIndex) external returns(uint);
}// SPDX-License-Identifier: MIT
pragma solidity ^0.8.18;
interface IOracle {
function getDeposit(bytes32 smgID) external view returns (uint);
function getValue(bytes32 key) external view returns (uint);
function getValues(bytes32[] calldata keys) external view returns (uint[] memory values);
function getStoremanGroupConfig(
bytes32 id
) external view returns(bytes32 groupId, uint8 status, uint deposit, uint chain1, uint chain2, uint curve1, uint curve2, bytes memory gpk1, bytes memory gpk2, uint startTime, uint endTime);
}// SPDX-License-Identifier: MIT
pragma solidity ^0.8.18;
interface IPosLib {
function getEpochId(uint256 blockTime) external view returns (uint256);
function getMinIncentive(uint256 smgDeposit, uint256 day, uint256 totalDeposit) external view returns (uint256);
}// SPDX-License-Identifier: MIT
/*
Copyright 2023 Wanchain Foundation.
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
*/
// _ _ _
// __ ____ _ _ __ ___| |__ __ _(_)_ __ __| | _____ __
// \ \ /\ / / _` | '_ \ / __| '_ \ / _` | | '_ \@/ _` |/ _ \ \ / /
// \ V V / (_| | | | | (__| | | | (_| | | | | | (_| | __/\ V /
// \_/\_/ \__,_|_| |_|\___|_| |_|\__,_|_|_| |_|\__,_|\___| \_/
//
//
pragma solidity ^0.8.18;
interface IQuota {
function userLock(uint tokenId, bytes32 storemanGroupId, uint value) external;
function userBurn(uint tokenId, bytes32 storemanGroupId, uint value) external;
function smgRelease(uint tokenId, bytes32 storemanGroupId, uint value) external;
function smgMint(uint tokenId, bytes32 storemanGroupId, uint value) external;
function upgrade(bytes32 storemanGroupId) external;
function transferAsset(bytes32 srcStoremanGroupId, bytes32 dstStoremanGroupId) external;
function receiveDebt(bytes32 srcStoremanGroupId, bytes32 dstStoremanGroupId) external;
function getUserMintQuota(uint tokenId, bytes32 storemanGroupId) external view returns (uint);
function getSmgMintQuota(uint tokenId, bytes32 storemanGroupId) external view returns (uint);
function getUserBurnQuota(uint tokenId, bytes32 storemanGroupId) external view returns (uint);
function getSmgBurnQuota(uint tokenId, bytes32 storemanGroupId) external view returns (uint);
function getAsset(uint tokenId, bytes32 storemanGroupId) external view returns (uint asset, uint asset_receivable, uint asset_payable);
function getDebt(uint tokenId, bytes32 storemanGroupId) external view returns (uint debt, uint debt_receivable, uint debt_payable);
function isDebtClean(bytes32 storemanGroupId) external view returns (bool);
}// SPDX-License-Identifier: MIT
/*
Copyright 2023 Wanchain Foundation.
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
*/
// _ _ _
// __ ____ _ _ __ ___| |__ __ _(_)_ __ __| | _____ __
// \ \ /\ / / _` | '_ \ / __| '_ \ / _` | | '_ \@/ _` |/ _ \ \ / /
// \ V V / (_| | | | | (__| | | | (_| | | | | | (_| | __/\ V /
// \_/\_/ \__,_|_| |_|\___|_| |_|\__,_|_|_| |_|\__,_|\___| \_/
//
//
pragma solidity ^0.8.18;
interface IRC20Protocol {
function transfer(address, uint) external returns (bool);
function transferFrom(address, address, uint) external returns (bool);
function balanceOf(address _owner) external view returns (uint);
}// SPDX-License-Identifier: MIT
/*
Copyright 2023 Wanchain Foundation.
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
*/
// _ _ _
// __ ____ _ _ __ ___| |__ __ _(_)_ __ __| | _____ __
// \ \ /\ / / _` | '_ \ / __| '_ \ / _` | | '_ \@/ _` |/ _ \ \ / /
// \ V V / (_| | | | | (__| | | | (_| | | | | | (_| | __/\ V /
// \_/\_/ \__,_|_| |_|\___|_| |_|\__,_|_|_| |_|\__,_|\___| \_/
//
//
pragma solidity ^0.8.18;
interface ISignatureVerifier {
function verify(
uint curveId,
bytes32 signature,
bytes32 groupKeyX,
bytes32 groupKeyY,
bytes32 randomPointX,
bytes32 randomPointY,
bytes32 message
) external returns (bool);
}// SPDX-License-Identifier: MIT
/*
Copyright 2023 Wanchain Foundation.
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
*/
// _ _ _
// __ ____ _ _ __ ___| |__ __ _(_)_ __ __| | _____ __
// \ \ /\ / / _` | '_ \ / __| '_ \ / _` | | '_ \@/ _` |/ _ \ \ / /
// \ V V / (_| | | | | (__| | | | (_| | | | | | (_| | __/\ V /
// \_/\_/ \__,_|_| |_|\___|_| |_|\__,_|_|_| |_|\__,_|\___| \_/
//
//
pragma solidity ^0.8.18;
interface ISmgFeeProxy {
function smgTransfer(bytes32 smgID) external payable;
}// SPDX-License-Identifier: MIT
/*
Copyright 2023 Wanchain Foundation.
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
*/
// _ _ _
// __ ____ _ _ __ ___| |__ __ _(_)_ __ __| | _____ __
// \ \ /\ / / _` | '_ \ / __| '_ \ / _` | | '_ \@/ _` |/ _ \ \ / /
// \ V V / (_| | | | | (__| | | | (_| | | | | | (_| | __/\ V /
// \_/\_/ \__,_|_| |_|\___|_| |_|\__,_|_|_| |_|\__,_|\___| \_/
//
//
pragma solidity ^0.8.18;
interface IStoremanGroup {
function getSelectedSmNumber(bytes32 groupId) external view returns(uint number);
function getStoremanGroupConfig(bytes32 id) external view returns(bytes32 groupId, uint8 status, uint deposit, uint chain1, uint chain2, uint curve1, uint curve2, bytes memory gpk1, bytes memory gpk2, uint startTime, uint endTime);
function getDeposit(bytes32 id) external view returns(uint);
function getStoremanGroupStatus(bytes32 id) external view returns(uint8 status, uint startTime, uint endTime);
function setGpk(bytes32 groupId, bytes memory gpk1, bytes memory gpk2) external;
function setInvalidSm(bytes32 groupId, uint[] memory indexs, uint8[] memory slashTypes) external returns(bool isContinue);
function getThresholdByGrpId(bytes32 groupId) external view returns (uint);
function getSelectedSmInfo(bytes32 groupId, uint index) external view returns(address wkAddr, bytes memory PK, bytes memory enodeId);
function recordSmSlash(address wk) external;
}// SPDX-License-Identifier: MIT
/*
Copyright 2023 Wanchain Foundation.
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
*/
// _ _ _
// __ ____ _ _ __ ___| |__ __ _(_)_ __ __| | _____ __
// \ \ /\ / / _` | '_ \ / __| '_ \ / _` | | '_ \@/ _` |/ _ \ \ / /
// \ V V / (_| | | | | (__| | | | (_| | | | | | (_| | __/\ V /
// \_/\_/ \__,_|_| |_|\___|_| |_|\__,_|_|_| |_|\__,_|\___| \_/
//
//
pragma solidity ^0.8.18;
interface ITokenManager {
function getTokenPairInfo(uint id) external view
returns (uint origChainID, bytes memory tokenOrigAccount, uint shadowChainID, bytes memory tokenShadowAccount);
function getTokenPairInfoSlim(uint id) external view
returns (uint origChainID, bytes memory tokenOrigAccount, uint shadowChainID);
function getAncestorInfo(uint id) external view
returns (bytes memory account, string memory name, string memory symbol, uint8 decimals, uint chainId);
function mintToken(address tokenAddress, address to, uint value) external;
function burnToken(address tokenAddress, address from, uint value) external;
function mapTokenPairType(uint tokenPairID) external view
returns (uint8 tokenPairType);
// erc1155
function mintNFT(uint tokenCrossType, address tokenAddress, address to, uint[] calldata ids, uint[] calldata values, bytes calldata data) external;
function burnNFT(uint tokenCrossType, address tokenAddress, address from, uint[] calldata ids, uint[] calldata values) external;
}// SPDX-License-Identifier: MIT
/*
Copyright 2023 Wanchain Foundation.
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
*/
// _ _ _
// __ ____ _ _ __ ___| |__ __ _(_)_ __ __| | _____ __
// \ \ /\ / / _` | '_ \ / __| '_ \ / _` | | '_ \@/ _` |/ _ \ \ / /
// \ V V / (_| | | | | (__| | | | (_| | | | | | (_| | __/\ V /
// \_/\_/ \__,_|_| |_|\___|_| |_|\__,_|_|_| |_|\__,_|\___| \_/
//
//
pragma solidity ^0.8.18;
interface IWrappedNFT1155 {
function changeOwner(address _newOwner) external;
function acceptOwnership() external;
function transferOwner(address) external;
function name() external view returns (string memory);
function symbol() external view returns (string memory);
function update(string calldata, string calldata) external;
function burnBatch(address , uint256[] calldata , uint256[] calldata ) external;
function mintBatch(address , uint256[] calldata , uint256[] calldata , bytes calldata) external;
}// SPDX-License-Identifier: MIT
/*
Copyright 2023 Wanchain Foundation.
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
*/
// _ _ _
// __ ____ _ _ __ ___| |__ __ _(_)_ __ __| | _____ __
// \ \ /\ / / _` | '_ \ / __| '_ \ / _` | | '_ \@/ _` |/ _ \ \ / /
// \ V V / (_| | | | | (__| | | | (_| | | | | | (_| | __/\ V /
// \_/\_/ \__,_|_| |_|\___|_| |_|\__,_|_|_| |_|\__,_|\___| \_/
//
//
pragma solidity ^0.8.18;
interface IWrappedNFT721 {
function changeOwner(address _newOwner) external;
function acceptOwnership() external;
function transferOwner(address) external;
function name() external view returns (string memory);
function symbol() external view returns (string memory);
function decimals() external view returns (uint8);
function burn(address, uint) external;
function update(string calldata, string calldata) external;
function mint(address, uint, bytes calldata) external;
function burnBatch(address , uint256[] calldata) external;
function mintBatch(address , uint256[] calldata , bytes calldata) external;
}// SPDX-License-Identifier: MIT
/*
Copyright 2023 Wanchain Foundation.
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
*/
// _ _ _
// __ ____ _ _ __ ___| |__ __ _(_)_ __ __| | _____ __
// \ \ /\ / / _` | '_ \ / __| '_ \ / _` | | '_ \@/ _` |/ _ \ \ / /
// \ V V / (_| | | | | (__| | | | (_| | | | | | (_| | __/\ V /
// \_/\_/ \__,_|_| |_|\___|_| |_|\__,_|_|_| |_|\__,_|\___| \_/
//
//
pragma solidity ^0.8.18;
interface IWrappedToken {
function changeOwner(address _newOwner) external;
function acceptOwnership() external;
function transferOwner(address) external;
function name() external view returns (string memory);
function symbol() external view returns (string memory);
function decimals() external view returns (uint8);
function mint(address, uint) external;
function burn(address, uint) external;
function update(string memory, string memory) external;
}// SPDX-License-Identifier: MIT
pragma solidity ^0.8.18;
/**
* @title BasicStorageLib
* @dev Library for basic storage operations with support for multiple data types
* This library provides a structured approach to contract storage with a two-level key system
*
* Key features:
* - Support for multiple data types (uint, bool, address, bytes, string)
* - Two-level key storage system for flexible data organization
* - CRUD operations for each data type with consistent interface
* - Gas-efficient storage patterns
*
* @custom:security
* - Internal access only to prevent unauthorized storage manipulation
* - Safe storage operations with proper type enforcement
* - Type-specific operations to prevent type confusion
* - Consistent interface reduces likelihood of implementation errors
*
* @custom:usage
* - Used as a foundation for complex contract storage patterns
* - Enables modular and organized data storage in contracts
* - Simplifies storage access with standardized methods
* - Perfect for contracts with diverse data storage needs
*/
library BasicStorageLib {
/**
* @dev Structure for storing uint values
* Provides a two-level nested mapping for uint storage
*
* @custom:usage
* - Stores uint values with two-level key system for hierarchical data
* - Used for numeric data storage such as balances, timestamps, amounts
* - Primary key often represents a category, while innerKey represents specific item
* - Essential for tracking numeric values in complex systems
*/
struct UintData {
mapping(bytes => mapping(bytes => uint)) _storage;
}
/**
* @dev Structure for storing boolean values
* Provides a two-level nested mapping for boolean storage
*
* @custom:usage
* - Stores boolean values with two-level key system for hierarchical data
* - Used for flag and state storage such as activation status or permissions
* - Efficient for representing binary states (true/false)
* - Perfect for access control and feature toggles
*/
struct BoolData {
mapping(bytes => mapping(bytes => bool)) _storage;
}
/**
* @dev Structure for storing address values
* Provides a two-level nested mapping for address storage
*
* @custom:usage
* - Stores address values with two-level key system for hierarchical data
* - Used for contract and account address storage
* - Essential for tracking ownership, relationships between entities
* - Enables role-based systems and contract registries
*/
struct AddressData {
mapping(bytes => mapping(bytes => address)) _storage;
}
/**
* @dev Structure for storing bytes values
* Provides a two-level nested mapping for bytes storage
*
* @custom:usage
* - Stores bytes values with two-level key system for hierarchical data
* - Used for raw data storage such as cryptographic proofs, signatures
* - Perfect for storing variable-length binary data
* - Enables storage of complex serialized structures
*/
struct BytesData {
mapping(bytes => mapping(bytes => bytes)) _storage;
}
/**
* @dev Structure for storing string values
* Provides a two-level nested mapping for string storage
*
* @custom:usage
* - Stores string values with two-level key system for hierarchical data
* - Used for text data storage such as names, descriptions, metadata
* - Human-readable information storage
* - Suitable for configuration parameters and user-facing content
*/
struct StringData {
mapping(bytes => mapping(bytes => string)) _storage;
}
/**
* @dev Set uint value in storage
* Assigns a uint value to a specific key pair in storage
*
* @param self Storage structure reference
* @param key Primary key for categorization (e.g., user ID, token type)
* @param innerKey Secondary key for specific attribute (e.g., balance, timestamp)
* @param value Unsigned integer value to store
*
* @custom:effects
* - Updates storage with new value, overwriting any existing value
* - Gas usage scales with key sizes, not with value size
* - Optimized for single-slot storage operations
*/
function setStorage(UintData storage self, bytes memory key, bytes memory innerKey, uint value) internal {
self._storage[key][innerKey] = value;
}
/**
* @dev Get uint value from storage
* Retrieves a uint value from a specific key pair in storage
*
* @param self Storage structure reference
* @param key Primary key for categorization
* @param innerKey Secondary key for specific attribute
* @return Stored uint value, or 0 if no value has been set
*
* @custom:effects
* - Read-only operation that doesn't modify state
* - Returns default value (0) if entry doesn't exist
* - Gas cost is constant regardless of value stored
*/
function getStorage(UintData storage self, bytes memory key, bytes memory innerKey) internal view returns (uint) {
return self._storage[key][innerKey];
}
/**
* @dev Delete uint value from storage
* Removes a uint value from a specific key pair in storage
*
* @param self Storage structure reference
* @param key Primary key for categorization
* @param innerKey Secondary key for specific attribute
*
* @custom:effects
* - Removes value from storage, setting it to default value (0)
* - Gas refund is provided when clearing storage to zero
* - Frees up storage space, potentially reducing contract storage costs
*/
function delStorage(UintData storage self, bytes memory key, bytes memory innerKey) internal {
delete self._storage[key][innerKey];
}
/**
* @dev Set boolean value in storage
* Assigns a boolean value to a specific key pair in storage
*
* @param self Storage structure reference
* @param key Primary key for categorization (e.g., feature name, user ID)
* @param innerKey Secondary key for specific flag (e.g., active, approved)
* @param value Boolean value to store
*
* @custom:effects
* - Updates storage with new value, overwriting any existing value
* - Gas efficient for storing binary state information
* - Packs values efficiently in storage
*/
function setStorage(BoolData storage self, bytes memory key, bytes memory innerKey, bool value) internal {
self._storage[key][innerKey] = value;
}
/**
* @dev Get boolean value from storage
* Retrieves a boolean value from a specific key pair in storage
*
* @param self Storage structure reference
* @param key Primary key for categorization
* @param innerKey Secondary key for specific flag
* @return Stored boolean value, or false if no value has been set
*
* @custom:effects
* - Read-only operation that doesn't modify state
* - Returns default value (false) if entry doesn't exist
* - Gas efficient for checking state conditions
*/
function getStorage(BoolData storage self, bytes memory key, bytes memory innerKey) internal view returns (bool) {
return self._storage[key][innerKey];
}
/**
* @dev Delete boolean value from storage
* Removes a boolean value from a specific key pair in storage
*
* @param self Storage structure reference
* @param key Primary key for categorization
* @param innerKey Secondary key for specific flag
*
* @custom:effects
* - Removes value from storage, setting it to default value (false)
* - Gas refund is provided when clearing storage to zero
* - Particularly efficient for boolean values
*/
function delStorage(BoolData storage self, bytes memory key, bytes memory innerKey) internal {
delete self._storage[key][innerKey];
}
/**
* @dev Set address value in storage
* Assigns an address value to a specific key pair in storage
*
* @param self Storage structure reference
* @param key Primary key for categorization (e.g., role name, contract type)
* @param innerKey Secondary key for specific relationship (e.g., owner, delegate)
* @param value Ethereum address to store
*
* @custom:effects
* - Updates storage with new address, overwriting any existing value
* - Stores full 20-byte Ethereum addresses
* - Critical for tracking contract relationships and ownership
*/
function setStorage(AddressData storage self, bytes memory key, bytes memory innerKey, address value) internal {
self._storage[key][innerKey] = value;
}
/**
* @dev Get address value from storage
* Retrieves an address value from a specific key pair in storage
*
* @param self Storage structure reference
* @param key Primary key for categorization
* @param innerKey Secondary key for specific relationship
* @return Stored address value, or address(0) if no value has been set
*
* @custom:effects
* - Read-only operation that doesn't modify state
* - Returns default value (address(0)) if entry doesn't exist
* - Used for permission checks and relationship verification
*/
function getStorage(AddressData storage self, bytes memory key, bytes memory innerKey) internal view returns (address) {
return self._storage[key][innerKey];
}
/**
* @dev Delete address value from storage
* Removes an address value from a specific key pair in storage
*
* @param self Storage structure reference
* @param key Primary key for categorization
* @param innerKey Secondary key for specific relationship
*
* @custom:effects
* - Removes value from storage, setting it to default value (address(0))
* - Gas refund is provided when clearing storage to zero
* - Important for revoking permissions or updating relationships
*/
function delStorage(AddressData storage self, bytes memory key, bytes memory innerKey) internal {
delete self._storage[key][innerKey];
}
/**
* @dev Set bytes value in storage
* Assigns a bytes value to a specific key pair in storage
*
* @param self Storage structure reference
* @param key Primary key for categorization (e.g., data type, record ID)
* @param innerKey Secondary key for specific data (e.g., signature, hash)
* @param value Bytes data to store
*
* @custom:effects
* - Updates storage with new bytes data, overwriting any existing value
* - Dynamically sized data is stored with length prefix
* - Gas cost scales with the size of the bytes array
* - Suitable for arbitrary binary data storage
*/
function setStorage(BytesData storage self, bytes memory key, bytes memory innerKey, bytes memory value) internal {
self._storage[key][innerKey] = value;
}
/**
* @dev Get bytes value from storage
* Retrieves a bytes value from a specific key pair in storage
*
* @param self Storage structure reference
* @param key Primary key for categorization
* @param innerKey Secondary key for specific data
* @return Stored bytes value, or empty bytes if no value has been set
*
* @custom:effects
* - Read-only operation that doesn't modify state
* - Returns default value (empty bytes) if entry doesn't exist
* - Gas cost scales with the size of the retrieved data
* - Used for retrieving serialized data, proofs, or signatures
*/
function getStorage(BytesData storage self, bytes memory key, bytes memory innerKey) internal view returns (bytes memory) {
return self._storage[key][innerKey];
}
/**
* @dev Delete bytes value from storage
* Removes a bytes value from a specific key pair in storage
*
* @param self Storage structure reference
* @param key Primary key for categorization
* @param innerKey Secondary key for specific data
*
* @custom:effects
* - Removes value from storage, setting it to default value (empty bytes)
* - Gas refund is provided when clearing storage
* - More gas efficient for larger data due to storage refunds
* - Complete removal of variable-length data
*/
function delStorage(BytesData storage self, bytes memory key, bytes memory innerKey) internal {
delete self._storage[key][innerKey];
}
/**
* @dev Set string value in storage
* Assigns a string value to a specific key pair in storage
*
* @param self Storage structure reference
* @param key Primary key for categorization (e.g., metadata type, record ID)
* @param innerKey Secondary key for specific text (e.g., name, description)
* @param value String data to store
*
* @custom:effects
* - Updates storage with new string, overwriting any existing value
* - Strings are stored as UTF-8 encoded bytes with length prefix
* - Gas cost scales with the length of the string
* - Ideal for human-readable text storage
*/
function setStorage(StringData storage self, bytes memory key, bytes memory innerKey, string memory value) internal {
self._storage[key][innerKey] = value;
}
/**
* @dev Get string value from storage
* Retrieves a string value from a specific key pair in storage
*
* @param self Storage structure reference
* @param key Primary key for categorization
* @param innerKey Secondary key for specific text
* @return Stored string value, or empty string if no value has been set
*
* @custom:effects
* - Read-only operation that doesn't modify state
* - Returns default value (empty string) if entry doesn't exist
* - Gas cost scales with the length of the retrieved string
* - Used for retrieving human-readable configuration and metadata
*/
function getStorage(StringData storage self, bytes memory key, bytes memory innerKey) internal view returns (string memory) {
return self._storage[key][innerKey];
}
/**
* @dev Delete string value from storage
* Removes a string value from a specific key pair in storage
*
* @param self Storage structure reference
* @param key Primary key for categorization
* @param innerKey Secondary key for specific text
*
* @custom:effects
* - Removes value from storage, setting it to default value (empty string)
* - Gas refund is provided when clearing storage
* - More gas efficient for longer strings due to storage refunds
* - Complete removal of variable-length text data
*/
function delStorage(StringData storage self, bytes memory key, bytes memory innerKey) internal {
delete self._storage[key][innerKey];
}
}// SPDX-License-Identifier: MIT
pragma solidity ^0.8.18;
/**
* @title Bn256Curve
* @dev Library for Bn256 elliptic curve operations
* The Bn256 (Barreto-Naehrig) curve is optimized for pairing operations and used in zkSNARKs
*
* Key features:
* - Point addition on Bn256 curve for combining public keys
* - Scalar multiplication with generator point for key generation
* - Polynomial commitment calculation for threshold signatures
* - Public key multiplication for shared secret derivation
* - Point equality check for verification operations
*
* @custom:security
* - Precompile contract calls for gas-efficient and secure implementations
* - Safe memory operations through assembly
* - Input validation for cryptographic safety
* - Leverages Ethereum's precompiled contracts at addresses 0x6, 0x7, and 0x268
*/
library Bn256Curve {
/**
* @dev Address of the Bn256 precompile contract
* Used for specialized curve operations beyond the standard precompiles
* This contract provides extended cryptographic functionality
*/
address constant PRECOMPILE_CONTRACT_ADDR = address(0x268);
/**
* @dev Add two points on the Bn256 curve: (x1,y1) + (x2,y2)
* Point addition is a fundamental operation for elliptic curve cryptography
*
* @param x1 X coordinate of first point
* @param y1 Y coordinate of first point
* @param x2 X coordinate of second point
* @param y2 Y coordinate of second point
*
* @return retx X coordinate of resulting point
* @return rety Y coordinate of resulting point
* @return success Whether the operation was successful
*
* @custom:effects
* - Performs point addition on Bn256 curve using precompile at address 0x6
* - Used in key aggregation for multi-signature schemes
* - Fails if either input point is not on the curve
* - Returns coordinates that represent the geometric sum of the points
*/
function add(uint256 x1, uint256 y1, uint256 x2, uint256 y2)
public
view
returns(uint256 retx, uint256 rety, bool success)
{
address to = address(0x6);
assembly {
let freePtr := mload(0x40)
mstore(add(freePtr, 0), x1)
mstore(add(freePtr, 32), y1)
mstore(add(freePtr, 64), x2)
mstore(add(freePtr, 96), y2)
// call ERC20 Token contract transfer function
success := staticcall(gas(),to, freePtr,128, freePtr, 64)
retx := mload(freePtr)
rety := mload(add(freePtr,32))
}
}
/**
* @dev Multiply generator point G by a scalar
* The generator point is the standard base point for the Bn256 curve
*
* @param scalar The scalar to multiply by
*
* @return x X coordinate of resulting point
* @return y Y coordinate of resulting point
* @return success Whether the operation was successful
*
* @custom:effects
* - Computes scalar * G where G is the generator point of Bn256
* - Used for deterministic public key generation from private keys
* - Essential for creating public keys in distributed key generation
* - More efficient than general scalar multiplication for the generator point
*/
function mulG(uint256 scalar)
public
view
returns(uint256 x, uint256 y, bool success)
{
bytes32 functionSelector = 0x0e5725cd00000000000000000000000000000000000000000000000000000000;
address to = PRECOMPILE_CONTRACT_ADDR;
assembly {
let freePtr := mload(0x40)
mstore(freePtr, functionSelector)
mstore(add(freePtr, 4), scalar)
// call ERC20 Token contract transfer function
success := staticcall(gas(), to, freePtr,36, freePtr, 64)
x := mload(freePtr)
y := mload(add(freePtr,32))
}
}
/**
* @dev Calculate polynomial commitment for threshold signature schemes
* Used in distributed key generation protocols and threshold cryptography
*
* @param polyCommit Polynomial commitment data (sequence of curve points)
* @param pk Public key data (additional curve point)
*
* @return sx X coordinate of resulting commitment point
* @return sy Y coordinate of resulting commitment point
* @return success Whether the operation was successful
*
* @custom:requirements
* - Combined length of polyCommit and pk must be multiple of 64 bytes
* - Each 64-byte chunk represents a curve point (x,y coordinates)
*
* @custom:effects
* - Combines multiple commitment points with the public key
* - Critical for secure distributed key generation
* - Enables threshold signature schemes across the bridge
* - Verifies participant contributions in distributed protocols
*/
function calPolyCommit(bytes memory polyCommit, bytes memory pk)
public
view
returns(uint256 sx, uint256 sy, bool success)
{
address to = PRECOMPILE_CONTRACT_ADDR;
bytes32 functionSelector = 0x77f683ba00000000000000000000000000000000000000000000000000000000;
require((polyCommit.length + pk.length)%64 == 0, "bn error len polyCommint or pk");
uint polyCommitCnt = polyCommit.length/64;
uint total = (polyCommitCnt + 1)*2;
assembly {
let freePtr := mload(0x40)
mstore(freePtr, functionSelector)
mstore(add(freePtr,4), mload(add(polyCommit,32)))
mstore(add(freePtr,36), mload(add(polyCommit,64)))
let loopCnt := 1
for { } lt(loopCnt, polyCommitCnt) { loopCnt := add(loopCnt, 1) } {
mstore(add(freePtr,add(4,mul(loopCnt,64))), mload(add(add(add(polyCommit,32),mul(loopCnt,64)),0)))
mstore(add(freePtr,add(4,add(mul(loopCnt,64),32))), mload(add(add(add(add(polyCommit,32),mul(loopCnt,64)),0),32)))
}
mstore(add(freePtr, add(4,mul(loopCnt,64))), mload(add(pk,32)))
mstore(add(freePtr,add(add(4,mul(loopCnt,64)),32)), mload(add(pk,64)))
success := staticcall(gas(),to, freePtr,add(mul(total,32),4), freePtr, 64)
sx := mload(freePtr)
sy := mload(add(freePtr, 32))
}
}
/**
* @dev Multiply an arbitrary point by a scalar: scalar * (xPk, yPk)
* General form of scalar multiplication for any point on the curve
*
* @param scalar The scalar to multiply by
* @param xPk X coordinate of the point (typically a public key)
* @param yPk Y coordinate of the point (typically a public key)
*
* @return x X coordinate of resulting point
* @return y Y coordinate of resulting point
* @return success Whether the operation was successful
*
* @custom:effects
* - Computes scalar * P where P is the point (xPk, yPk)
* - Uses the Bn256 scalar multiplication precompile at address 0x7 (EIP-196)
* - Used for ECDH (Elliptic Curve Diffie-Hellman) shared secret derivation
* - Important for secure communication between storeman nodes
* - More computationally intensive than generator point multiplication
*/
function mulPk(uint256 scalar, uint256 xPk, uint256 yPk)
public
view
returns (uint256 x, uint256 y, bool success){
address to = address(0x7);
assembly {
let freePtr := mload(0x40)
mstore(add(freePtr,0), xPk)
mstore(add(freePtr,32), yPk)
mstore(add(freePtr, 64), scalar)
success := staticcall(gas(), to, freePtr,96, freePtr, 64)
x := mload(freePtr)
y := mload(add(freePtr,32))
}
}
/**
* @dev Check if two elliptic curve points are equal
* Points are equal if their coordinates are identical
*
* @param xLeft X coordinate of first point
* @param yLeft Y coordinate of first point
* @param xRight X coordinate of second point
* @param yRight Y coordinate of second point
*
* @return bool True if points are equal, false otherwise
*
* @custom:effects
* - Simply compares coordinates for equality
* - Used in various verification processes
* - Helps determine if cryptographic operations produced expected results
* - Simplifies comparison logic in higher-level protocols
*/
function equalPt (uint256 xLeft, uint256 yLeft,uint256 xRight, uint256 yRight) public pure returns(bool){
return xLeft == xRight && yLeft == yRight;
}
}// SPDX-License-Identifier: MIT
/*
Copyright 2023 Wanchain Foundation.
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
*/
// _ _ _
// __ ____ _ _ __ ___| |__ __ _(_)_ __ __| | _____ __
// \ \ /\ / / _` | '_ \ / __| '_ \ / _` | | '_ \@/ _` |/ _ \ \ / /
// \ V V / (_| | | | | (__| | | | (_| | | | | | (_| | __/\ V /
// \_/\_/ \__,_|_| |_|\___|_| |_|\__,_|_|_| |_|\__,_|\___| \_/
//
//
pragma solidity ^0.8.18;
/**
* @title CommonTool
* @dev Library for common cryptographic and data manipulation operations
* This library provides utility functions used throughout the cross-chain bridge system
*
* Key features:
* - Bytes to uint conversion for binary data handling
* - Bytes to bytes32 conversion for hash processing
* - Bytes comparison for message verification
* - Encryption operations for secure cross-chain communication
* - Support for different elliptic curve types
*
* @custom:security
* - Safe memory operations with boundary checks
* - Input validation for cryptographic operations
* - Precompile contract calls for gas-efficient cryptography
* - Null byte handling in comparisons
*/
library CommonTool {
/**
* @dev Enum for supported elliptic curve types
* Used to specify which curve to use for cryptographic operations
*
* @custom:values
* - SK: Secp256k1 curve (used by Ethereum, Bitcoin)
* - BN: Bn256 curve (used for pairing-based cryptography)
*
* @custom:usage
* - Different chains may require different curve types
* - Curve selection affects signature verification and key operations
*/
enum CurveType {SK, BN}
/**
* @dev Address of the precompile contract for extended cryptographic operations
* This contract provides functions beyond standard precompiles
*/
address constant PRECOMPILE_CONTRACT_ADDR = address(0x268);
/**
* @dev Convert a subsection of a bytes array to uint256
* Performs big-endian conversion (most significant byte first)
*
* @param source Source bytes array containing the data
* @param offset Starting position in bytes array (0-indexed)
* @param length Number of bytes to convert (max 32)
*
* @return uint The converted unsigned integer value
*
* @custom:effects
* - Converts specified bytes to uint256 using big-endian byte order
* - Left-pads with zeros if length < 32
* - Useful for extracting numeric fields from serialized messages
* - Used in cross-chain message parsing and verification
*/
function bytes2uint(bytes memory source, uint16 offset, uint16 length)
public
pure
returns(uint)
{
uint number = 0;
for (uint i = 0; i < length; i++) {
number = number + uint8(source[i + offset]) * (2 ** (8 * (length - (i + 1))));
}
return number;
}
/**
* @dev Convert first 32 bytes of a bytes array to bytes32
* Efficient conversion using assembly
*
* @param source Source bytes array
*
* @return result The converted bytes32 value
*
* @custom:effects
* - Directly loads first 32 bytes into a bytes32 value
* - More gas efficient than byte-by-byte copying
* - Used for extracting fixed-size fields like hashes, IDs, and signatures
* - No effect if source is less than 32 bytes (will be right-padded with zeros)
*
* @custom:assembly Uses assembly for direct memory access and loading
*/
function bytesToBytes32(bytes memory source) pure public returns (bytes32 result) {
assembly {
result := mload(add(source, 32))
}
}
/**
* @dev Compare two bytes arrays for equality, with padding support
*
* @param b1 First bytes array (reference array)
* @param b2 Second bytes array (may contain right padding with zeros)
*
* @return bool True if arrays are equal (accounting for padding), false otherwise
*
* @custom:effects
* - Compares bytes arrays byte by byte for equality
* - Handles zero-padding in the second array (ignores trailing zeros in b2)
* - Returns false if b2 is shorter than b1
* - Returns false if b2 has non-zero bytes beyond b1's length
* - Used for comparing contract addresses, public keys, and other identifiers
* - Critical for verifying cross-chain message authenticity
*/
function cmpBytes(bytes memory b1, bytes memory b2)
public
pure
returns(bool)
{
uint len1 = b1.length;
uint len2 = b2.length; // maybe has padding
if (len2 >= len1) {
for (uint i = 0; i < len2; i++) {
if (i < len1) {
if (b1[i] != b2[i]) {
return false;
}
} else if (b2[i] != 0x0) {
return false;
}
}
return true;
} else {
return false;
}
}
/**
* @dev Encrypt data using ECIES (Elliptic Curve Integrated Encryption Scheme)
* Provides hybrid encryption combining ECC with symmetric encryption
*
* @param rbpri Random ephemeral private key for this encryption operation
* @param iv Initialization vector for symmetric encryption (AES)
* @param mes The message value to encrypt
* @param pub The recipient's public key
*
* @return bytes Encrypted data (contains ciphertext and other ECIES components)
* @return success Whether encryption was successful
*
* @custom:effects
* - Performs ECIES encryption using a precompile contract
* - Generates a shared secret using ECC
* - Uses the shared secret with AES to encrypt the message
* - Returns the complete encrypted package and success status
* - Used for secure communication between storeman nodes
* - Ensures only the intended recipient can decrypt the message
*
* @custom:security
* - Fresh ephemeral key should be used for each encryption
* - IV should be unique for each encryption operation
* - Relies on secure precompile implementation
*/
function enc(bytes32 rbpri, bytes32 iv, uint256 mes, bytes memory pub)
public
view
returns(bytes memory, bool success)
{
bytes32 functionSelector = 0xa1ecea4b00000000000000000000000000000000000000000000000000000000;
address to = PRECOMPILE_CONTRACT_ADDR;
bytes memory cc = new bytes(6*32);
assembly {
let freePtr := mload(0x40)
mstore(freePtr, functionSelector)
mstore(add(freePtr, 4), rbpri)
mstore(add(freePtr, 36), iv)
mstore(add(freePtr, 68), mes)
mstore(add(freePtr, 100), mload(add(pub, 32)))
mstore(add(freePtr, 132), mload(add(pub, 64)))
// call ERC20 Token contract transfer function
success := staticcall(gas(),to, freePtr, 164, freePtr, 1024)
for { let loopCnt := 0 } lt(loopCnt, 6) { loopCnt := add(loopCnt, 1) } {
mstore(add(cc, mul(add(loopCnt, 1), 32)), mload(add(freePtr, mul(loopCnt, 32))))
}
}
return (cc,success);
}
}// SPDX-License-Identifier: MIT
pragma solidity ^0.8.18;
pragma experimental ABIEncoderV2;
/**
* @title Multicall2
* @dev Contract for aggregating multiple read-only function calls in a single external call
* Based on the popular Multicall pattern but with enhanced functionality
*
* Key features:
* - Batch multiple read-only calls to reduce RPC requests and gas costs
* - Block information retrieval for consistent state views
* - Transaction state queries with atomic execution
* - Flexible success handling with try variants
* - Support for cross-contract data aggregation
*
* @custom:security
* - Read-only operations prevent state modifications
* - Safe call handling with success status checking
* - Block state validation for consistent snapshots
* - Error propagation with detailed status reporting
*
* @custom:usage
* - Useful for frontends needing to fetch multiple contract states
* - Reduces network overhead by batching calls
* - Ensures all data is from the same block (atomic reads)
* - Can be used for complex data aggregation across contracts
*/
contract Multicall2 {
/**
* @dev Structure for call parameters
* Each Call instance represents a contract call to execute
*
* @param target Address of the contract to call
* @param callData Encoded function call data (function selector + arguments)
*/
struct Call {
address target;
bytes callData;
}
/**
* @dev Structure for call results
* Each Result contains both success flag and returned data
*
* @param success Whether the call was successful (true) or reverted (false)
* @param returnData Returned data from the call (ABI encoded response)
*/
struct Result {
bool success;
bytes returnData;
}
/**
* @dev Aggregate multiple calls and require all to succeed
* This is the simplest form of aggregation with no tolerance for failures
*
* @param calls Array of calls to execute in sequence
*
* @return blockNumber Current block number when the calls were executed
* @return returnData Array of call results in the same order as input calls
*
* @custom:effects
* - Executes all calls in sequence in a single transaction
* - Requires all calls to succeed, reverting the entire transaction otherwise
* - Returns current block number to identify the blockchain state
* - Useful when all calls must succeed or the entire operation should fail
*/
function aggregate(Call[] memory calls) public returns (uint256 blockNumber, bytes[] memory returnData) {
blockNumber = block.number;
returnData = new bytes[](calls.length);
for(uint256 i = 0; i < calls.length; i++) {
(bool success, bytes memory ret) = calls[i].target.call(calls[i].callData);
require(success, "Multicall aggregate: call failed");
returnData[i] = ret;
}
}
/**
* @dev Aggregate calls with block information
* Enhanced version that includes block hash for state verification
*
* @param calls Array of calls to execute in sequence
*
* @return blockNumber Current block number when the calls were executed
* @return blockHash Current block hash for state verification
* @return returnData Array of call results with success flags
*
* @custom:effects
* - Executes calls with block context for state consistency
* - Requires all calls to succeed, reverting the entire transaction otherwise
* - Returns block hash which can be used to verify the blockchain state
* - Combines functionality of aggregate and blockHash in one call
*/
function blockAndAggregate(Call[] memory calls) public returns (uint256 blockNumber, bytes32 blockHash, Result[] memory returnData) {
(blockNumber, blockHash, returnData) = tryBlockAndAggregate(true, calls);
}
/**
* @dev Get block hash for a specific block number
* Wraps the blockhash opcode for convenient access
*
* @param blockNumber Block number to query (must be one of the 256 most recent blocks)
*
* @return blockHash Hash of the specified block, or zero bytes if block is too old
*
* @custom:effects
* - Returns zero bytes for blocks older than the 256 most recent blocks
* - Useful for verifying that a state is from a specific block
*/
function getBlockHash(uint256 blockNumber) public view returns (bytes32 blockHash) {
blockHash = blockhash(blockNumber);
}
/**
* @dev Get current block number
* Wraps the block.number property
*
* @return blockNumber Current block number at execution time
*/
function getBlockNumber() public view returns (uint256 blockNumber) {
blockNumber = block.number;
}
/**
* @dev Get current block coinbase address
* Wraps the block.coinbase property
*
* @return coinbase Address of the current block's coinbase (miner/validator)
*/
function getCurrentBlockCoinbase() public view returns (address coinbase) {
coinbase = block.coinbase;
}
/**
* @dev Get current block difficulty or prevrandao value
* Wraps the block.difficulty/prevrandao property
*
* @return difficulty Current block's difficulty or randomness value post-merge
*
* @custom:effects
* - Returns prevrandao value after The Merge
* - Returns difficulty before The Merge
*/
function getCurrentBlockDifficulty() public view returns (uint256 difficulty) {
difficulty = block.prevrandao;
}
/**
* @dev Get current block gas limit
* Wraps the block.gaslimit property
*
* @return gaslimit Current block's gas limit
*/
function getCurrentBlockGasLimit() public view returns (uint256 gaslimit) {
gaslimit = block.gaslimit;
}
/**
* @dev Get current block timestamp
* Wraps the block.timestamp property
*
* @return timestamp Current block's timestamp (seconds since Unix epoch)
*/
function getCurrentBlockTimestamp() public view returns (uint256 timestamp) {
timestamp = block.timestamp;
}
/**
* @dev Get ETH balance of an address
* Wraps the address.balance property
*
* @param addr Address to query balance for
*
* @return balance ETH balance of the address in wei
*/
function getEthBalance(address addr) public view returns (uint256 balance) {
balance = addr.balance;
}
/**
* @dev Get hash of the last block
* Convenience method for getting the previous block's hash
*
* @return blockHash Hash of the previous block
*/
function getLastBlockHash() public view returns (bytes32 blockHash) {
blockHash = blockhash(block.number - 1);
}
/**
* @dev Try to aggregate calls with optional success requirement
* More flexible version that can continue despite individual call failures
*
* @param requireSuccess Whether to require all calls to succeed (true) or continue on failure (false)
* @param calls Array of calls to execute in sequence
*
* @return returnData Array of call results with individual success status
*
* @custom:effects
* - Executes all calls in sequence in a single transaction
* - Can optionally continue despite individual call failures
* - Returns success status and data for each call
* - Useful for UIs that need to display partial results even if some calls fail
* - When requireSuccess is false, never reverts due to target call failures
*/
function tryAggregate(bool requireSuccess, Call[] memory calls) public returns (Result[] memory returnData) {
returnData = new Result[](calls.length);
for(uint256 i = 0; i < calls.length; i++) {
(bool success, bytes memory ret) = calls[i].target.call(calls[i].callData);
if (requireSuccess) {
require(success, "Multicall2 aggregate: call failed");
}
returnData[i] = Result(success, ret);
}
}
/**
* @dev Try to aggregate calls with block information
* Most comprehensive version with both flexible success handling and block context
*
* @param requireSuccess Whether to require all calls to succeed (true) or continue on failure (false)
* @param calls Array of calls to execute in sequence
*
* @return blockNumber Current block number when the calls were executed
* @return blockHash Current block hash for state verification
* @return returnData Array of call results with individual success status
*
* @custom:effects
* - Executes calls with block context for state consistency
* - Can optionally continue despite individual call failures
* - Returns block information and detailed results for each call
* - Most flexible variant that provides complete information
* - Useful for applications that need full context and error handling
*/
function tryBlockAndAggregate(bool requireSuccess, Call[] memory calls) public returns (uint256 blockNumber, bytes32 blockHash, Result[] memory returnData) {
blockNumber = block.number;
blockHash = blockhash(block.number);
returnData = tryAggregate(requireSuccess, calls);
}
}// SPDX-License-Identifier: MIT
pragma solidity ^0.8.18;
import "@openzeppelin/contracts/utils/math/SafeMath.sol";
/**
* @title PosLib
* @dev Library for Proof of Stake (PoS) related calculations and operations
*
* Key features:
* - Epoch ID retrieval
* - Average PoS return calculation
* - Hard cap determination
* - Minimum incentive calculation
*
* @custom:security
* - Precompile contract calls
* - Safe math operations
* - Input validation
*/
library PosLib {
using SafeMath for uint;
/**
* @dev Constant divisor for percentage calculations
*/
uint public constant DIVISOR = 10000;
/**
* @dev Address of the precompile contract
* Used for PoS related operations
*/
address constant PRECOMPILE_CONTRACT_ADDR = address(0x268);
/**
* @dev Get the epoch ID for a given block time
*
* @param blockTime The block time to query
*
* @return uint256 The epoch ID
*
* @custom:effects
* - Calls precompile contract to retrieve epoch ID
* - Requires successful call execution
*/
function getEpochId(uint256 blockTime) public view returns (uint256) {
bytes32 functionSelector = keccak256("getEpochId(uint256)");
(uint256 result, bool success) = callWith32BytesReturnsUint256(
address(0x262),
functionSelector,
bytes32(blockTime)
);
require(success, "ASSEMBLY_CALL getEpochId failed");
return result;
}
/**
* @dev Internal function to call a precompile contract with a 32-byte parameter
*
* @param to Address of the precompile contract
* @param functionSelector Function selector for the call
* @param param1 First parameter for the call
*
* @return result Result of the call
* @return success Whether the call was successful
*/
function callWith32BytesReturnsUint256(
address to,
bytes32 functionSelector,
bytes32 param1
) private view returns (uint256 result, bool success) {
assembly {
let freePtr := mload(0x40)
mstore(freePtr, functionSelector)
mstore(add(freePtr, 4), param1)
// call ERC20 Token contract transfer function
success := staticcall(gas(), to, freePtr, 36, freePtr, 32)
result := mload(freePtr)
}
}
/**
* @dev Get the average PoS return for a target duration
*
* @param targetSecond The target duration in seconds
*
* @return result Average PoS return
* @return success Whether the call was successful
*
* @custom:effects
* - Calls precompile contract to retrieve average PoS return
*/
function getPosAvgReturn(uint256 targetSecond) public view returns(uint256 result,bool success) {
// bytes32 functionSelector = keccak256("getPosAvgReturn(uint256)");
bytes32 functionSelector = 0x94fee72400000000000000000000000000000000000000000000000000000000;
address to = PRECOMPILE_CONTRACT_ADDR;
assembly {
let freePtr := mload(0x40)
mstore(freePtr, functionSelector)
mstore(add(freePtr, 4), targetSecond)
// call ERC20 Token contract transfer function
success := staticcall(gas(), to, freePtr,36, freePtr, 32)
result := mload(freePtr)
}
}
/**
* @dev Test function to get hard cap
*
* @return uint256 Hard cap value
* @return bool Whether the call was successful
*/
function testGetHardCap () public view returns(uint256,bool) {
return getHardCap(block.timestamp - 3600 * 24);
}
/**
* @dev Get the hard cap for a given time
*
* @param time The time to query
*
* @return uint256 Hard cap value
* @return bool Whether the call was successful
*
* @custom:effects
* - Calls precompile contract to retrieve hard cap
*/
function getHardCap (uint256 time) public view returns(uint256,bool) {
bytes32 functionSelector = 0x8b19e7b700000000000000000000000000000000000000000000000000000000;
address to = PRECOMPILE_CONTRACT_ADDR;
uint256 posReturn=0;
bool success;
assembly {
let freePtr := mload(0x40)
mstore(freePtr, functionSelector)
mstore(add(freePtr, 4), time)
success := staticcall(gas(), to, freePtr,36, freePtr, 32)
posReturn := mload(freePtr)
}
return (posReturn,success);
}
/**
* @dev Calculate the minimum incentive for a given deposit and duration
*
* @param smgDeposit Storeman group deposit
* @param day Duration in days
* @param totalDeposit Total deposit amount
*
* @return uint256 Minimum incentive value
*
* @custom:effects
* - Calculates minimum incentive based on PoS return and hard cap
*/
function getMinIncentive (uint256 smgDeposit,uint256 day, uint256 totalDeposit) public view returns(uint256) {
uint256 p1;
bool success;
uint targetSecond = day.mul(3600*24);
(p1,success) = getPosAvgReturn(targetSecond);
if(!success) {
return 0;
}
uint256 p1Return = smgDeposit.mul(p1).div(DIVISOR).div(365);
uint256 hardcap;
(hardcap,success) = getHardCap(targetSecond);
if(!success) {
return 0;
}
uint256 hardcapReturn = hardcap.mul(1 ether).div(DIVISOR).mul(smgDeposit).div(totalDeposit);
return hardcapReturn<=p1Return?hardcapReturn:p1Return;
}
}// SPDX-License-Identifier: MIT
pragma solidity ^0.8.18;
/**
* @title Secp256k1Curve
* @dev Library for operations on the secp256k1 elliptic curve
* The secp256k1 curve is used in Bitcoin and many other cryptocurrencies for cryptographic operations
*
* Key features:
* - Point addition on the curve
* - Scalar multiplication with generator point and arbitrary points
* - Polynomial commitment calculations
* - Signature verification
*
* @custom:security
* - Uses precompiled contracts for efficient and secure curve operations
* - Input validation for critical operations
* - Protection against curve-related vulnerabilities
*/
library Secp256k1Curve {
/**
* @dev Address of the precompiled contract for secp256k1 curve operations
* This precompile provides efficient implementations of cryptographic operations
*/
address constant PRECOMPILE_CONTRACT_ADDR = address(0x268);
/**
* @dev Add two points on the secp256k1 curve: (x1,y1) + (x2,y2)
* Point addition is a fundamental operation for elliptic curve cryptography
*
* @param x1 X coordinate of the first point
* @param y1 Y coordinate of the first point
* @param x2 X coordinate of the second point
* @param y2 Y coordinate of the second point
*
* @return retx X coordinate of the resulting point
* @return rety Y coordinate of the resulting point
* @return success Whether the operation was successful
*
* @custom:assembly Uses assembly to call the precompiled contract at address 0x42
*/
function add(uint256 x1, uint256 y1, uint256 x2, uint256 y2)
public
view
returns(uint256 retx, uint256 rety, bool success)
{
address to = address(0x42);
assembly {
let freePtr := mload(0x40)
mstore(add(freePtr, 0), x1)
mstore(add(freePtr, 32), y1)
mstore(add(freePtr, 64), x2)
mstore(add(freePtr, 96), y2)
// call ERC20 Token contract transfer function
success := staticcall(gas(),to, freePtr,128, freePtr, 64)
retx := mload(freePtr)
rety := mload(add(freePtr,32))
}
}
/**
* @dev Multiply the generator point G by a scalar
* The generator point is the standard base point for the secp256k1 curve
*
* @param scalar The scalar value to multiply with
*
* @return x X coordinate of the resulting point
* @return y Y coordinate of the resulting point
* @return success Whether the operation was successful
*
* @custom:effects
* - Calculates scalar * G where G is the generator point
* - Used for public key generation from private keys
*/
function mulG(uint256 scalar)
public
view
returns(uint256 x, uint256 y, bool success)
{
bytes32 functionSelector = 0xbb734c4e00000000000000000000000000000000000000000000000000000000;//keccak256("mulG(uint256)");
address to = PRECOMPILE_CONTRACT_ADDR;
assembly {
let freePtr := mload(0x40)
mstore(freePtr, functionSelector)
mstore(add(freePtr, 4), scalar)
// call ERC20 Token contract transfer function
success := staticcall(gas(), to, freePtr,36, freePtr, 64)
x := mload(freePtr)
y := mload(add(freePtr,32))
}
}
/**
* @dev Calculate polynomial commitment for threshold signatures
* Used in distributed key generation and threshold signature schemes
*
* @param polyCommit The polynomial commitment data
* @param pk The public key data
*
* @return sx X coordinate of the resulting point
* @return sy Y coordinate of the resulting point
* @return success Whether the operation was successful
*
* @custom:security
* - Validates that input lengths are proper multiples of 64 bytes
* - Essential for secure threshold signature schemes
*/
function calPolyCommit(bytes memory polyCommit, bytes memory pk)
public
view
returns(uint256 sx, uint256 sy, bool success)
{
address to = PRECOMPILE_CONTRACT_ADDR;
bytes32 functionSelector = 0x66c85fc200000000000000000000000000000000000000000000000000000000;
require((polyCommit.length + pk.length)%64 == 0, "error len polyCommint or pk");
uint polyCommitCnt = polyCommit.length/64;
uint total = (polyCommitCnt + 1)*2;
assembly {
let freePtr := mload(0x40)
mstore(freePtr, functionSelector)
mstore(add(freePtr,4), mload(add(polyCommit,32)))
mstore(add(freePtr,36), mload(add(polyCommit,64)))
let loopCnt := 1
for { } lt(loopCnt, polyCommitCnt) { loopCnt := add(loopCnt, 1) } {
mstore(add(freePtr, add(4, mul(loopCnt, 64))), mload(add(add(add(polyCommit, 32), mul(loopCnt, 64)), 0)))
mstore(add(freePtr, add(4, add(mul(loopCnt, 64), 32))), mload(add(add(add(add(polyCommit, 32), mul(loopCnt, 64)), 0), 32)))
}
mstore(add(freePtr, add(4,mul(loopCnt,64))), mload(add(pk,32)))
mstore(add(freePtr,add(add(4,mul(loopCnt,64)),32)), mload(add(pk,64)))
success := staticcall(gas(),to, freePtr,add(mul(total,32),4), freePtr, 64)
sx := mload(freePtr)
sy := mload(add(freePtr, 32))
}
}
/**
* @dev Multiply an arbitrary point by a scalar: scalar * (xPk, yPk)
* General form of scalar multiplication for any point on the curve
*
* @param scalar The scalar value to multiply with
* @param xPk X coordinate of the point
* @param yPk Y coordinate of the point
*
* @return x X coordinate of the resulting point
* @return y Y coordinate of the resulting point
* @return success Whether the operation was successful
*
* @custom:effects
* - Calculates scalar * P where P is the point (xPk, yPk)
* - Used for various cryptographic operations including shared secret computation
*/
function mulPk(uint256 scalar, uint256 xPk, uint256 yPk)
public
view
returns (uint256 x, uint256 y, bool success){
address to = address(0x43);
assembly {
let freePtr := mload(0x40)
mstore(add(freePtr, 0), scalar)
mstore(add(freePtr,32), xPk)
mstore(add(freePtr,64), yPk)
success := staticcall(gas(), to, freePtr,96, freePtr, 64)
x := mload(freePtr)
y := mload(add(freePtr,32))
}
}
/**
* @dev Check if two points on the curve are equal
* Points are equal if their coordinates are identical
*
* @param xLeft X coordinate of the first point
* @param yLeft Y coordinate of the first point
* @param xRight X coordinate of the second point
* @param yRight Y coordinate of the second point
*
* @return bool True if the points are equal, false otherwise
*/
function equalPt (uint256 xLeft, uint256 yLeft,uint256 xRight, uint256 yRight) public pure returns(bool){
return xLeft == xRight && yLeft == yRight;
}
/**
* @dev Verify a secp256k1 signature
* Implements ECDSA signature verification for the secp256k1 curve
*
* @param hash The message hash that was signed
* @param r The r value of the signature
* @param s The s value of the signature
* @param pk The public key to verify against (as bytes containing X and Y coordinates)
*
* @return bool Whether the signature is valid for the given message and public key
*
* @custom:effects
* - Verifies that the signature is valid for the message hash and public key
* - Returns false on any verification failure or error
* - Critical for cross-chain message validation
*/
function checkSig (bytes32 hash, bytes32 r, bytes32 s, bytes memory pk) public view returns(bool) {
bytes32 functionSelector = 0x861731d500000000000000000000000000000000000000000000000000000000;
address to = PRECOMPILE_CONTRACT_ADDR;
uint256 result;
bool success;
assembly {
let freePtr := mload(0x40)
mstore(freePtr, functionSelector)
mstore(add(freePtr, 4), hash)
mstore(add(freePtr, 36), r)
mstore(add(freePtr, 68), s)
mstore(add(freePtr, 100), mload(add(pk,32)))
mstore(add(freePtr, 132), mload(add(pk,64)))
success := staticcall(gas(), to, freePtr,164, freePtr, 32)
result := mload(freePtr)
}
if (success) {
return result == 1;
} else {
return false;
}
}
}// SPDX-License-Identifier: MIT
/*
Copyright 2023 Wanchain Foundation.
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
*/
// _ _ _
// __ ____ _ _ __ ___| |__ __ _(_)_ __ __| | _____ __
// \ \ /\ / / _` | '_ \ / __| '_ \ / _` | | '_ \@/ _` |/ _ \ \ / /
// \ V V / (_| | | | | (__| | | | (_| | | | | | (_| | __/\ V /
// \_/\_/ \__,_|_| |_|\___|_| |_|\__,_|_|_| |_|\__,_|\___| \_/
//
//
pragma solidity ^0.8.18;
import "@openzeppelin/contracts/utils/math/SafeMath.sol";
import "./MetricTypes.sol";
import "../../lib/CommonTool.sol";
import "../../interfaces/IPosLib.sol";
import "../../interfaces/IStoremanGroup.sol";
import "../../interfaces/ICurve.sol";
/**
* @title MetricLib
* @dev Library for handling metric-related operations in the Storeman system
* This library provides functionality for managing and validating slash operations
* in both R and S stages of the Storeman protocol
*/
library MetricLib {
using SafeMath for uint;
/**
* @notice Writes and validates a slash operation for the R stage
* @dev Processes R stage slash data and updates the metric storage
* @param metricData Storage data for metrics
* @param grpId Group identifier
* @param hashX Hash of the signed data
* @param rslshData Slash data for R stage
* @param smCount Total number of storeman nodes
* @return bool Success status of the operation
* @return uint8 Index of the storeman node
*/
function writeRSlsh(MetricTypes.MetricStorageData storage metricData, bytes32 grpId, bytes32 hashX, MetricTypes.RSlshData memory rslshData, uint8 smCount)
internal
returns (bool, uint8)
{
require(metricData.mapRSlsh[grpId][hashX][rslshData.sndrIndex].polyDataPln.polyData.length == 0,"Duplicate RSlsh");
require(rslshData.sndrIndex <= smCount, "invalid send index");
require(rslshData.rcvrIndex <= smCount, "invalid receiver index");
require(rslshData.polyCMData.polyCM.length != 0, "polyCM is empty");
require(rslshData.polyDataPln.polyData.length != 0, "polyData is empty");
require(rslshData.becauseSndr, "R because sender is not true");
uint8 smIndex;
//smIndex = rslshData.becauseSndr ? rslshData.sndrIndex : rslshData.rcvrIndex;
smIndex = rslshData.sndrIndex;
metricData.mapRSlsh[grpId][hashX][smIndex] = rslshData;
if (checkRProof(metricData, grpId, hashX, smIndex)) {
metricData.mapSlshCount[grpId][getEpochId(metricData)][smIndex] = metricData.mapSlshCount[grpId][getEpochId(metricData)][smIndex].add(uint(1));
return (true, smIndex);
} else {
delete metricData.mapRSlsh[grpId][hashX][smIndex];
return (false, smIndex);
}
}
/**
* @notice Validates the proof for R stage slash operation
* @dev Checks both signature and content of the R stage proof
* @param metricData Storage data for metrics
* @param grpId Group identifier
* @param hashX Hash of the signed data
* @param smIndex Index of the storeman node
* @return bool Validation result
*/
function checkRProof(MetricTypes.MetricStorageData storage metricData, bytes32 grpId, bytes32 hashX, uint8 smIndex)
internal
returns (bool)
{
bool bSig = checkRSig(metricData, grpId, hashX, smIndex);
bool bContent = checkRContent(metricData, grpId, hashX, smIndex);
return getChkResult(bSig, bContent);
}
/**
* @notice Validates the signature of R stage proof
* @dev Verifies the cryptographic signature of the R stage proof
* @param metricData Storage data for metrics
* @param grpId Group identifier
* @param hashX Hash of the signed data
* @param smIndex Index of the storeman node
* @return bool Signature validation result
*/
function checkRSig(MetricTypes.MetricStorageData storage metricData, bytes32 grpId, bytes32 hashX, uint8 smIndex)
internal
returns (bool)
{
bytes32 h;
bytes memory senderPk;
MetricTypes.RSlshData storage rslshData = metricData.mapRSlsh[grpId][hashX][smIndex];
// build h
h = sha256(rslshData.polyDataPln.polyData);
// build senderpk
senderPk = getPkBytesByInx(metricData, grpId, rslshData.sndrIndex);
return ckSig(metricData,h, rslshData.polyDataPln.polyDataR, rslshData.polyDataPln.polyDataS, senderPk);
}
/**
* @notice Validates the content of R stage proof
* @dev Verifies the mathematical correctness of the R stage proof content
* @param metricData Storage data for metrics
* @param grpId Group identifier
* @param hashX Hash of the signed data
* @param smIndex Index of the storeman node
* @return bool Content validation result
*/
function checkRContent(MetricTypes.MetricStorageData storage metricData, bytes32 grpId, bytes32 hashX, uint8 smIndex)
internal
returns (bool)
{
uint256 xLeft;
uint256 yLeft;
uint256 xRight;
uint256 yRight;
bool success;
bytes memory sij;
bytes memory rcvrPk;
MetricTypes.RSlshData memory rslshData = metricData.mapRSlsh[grpId][hashX][smIndex];
sij = rslshData.polyDataPln.polyData;
rcvrPk = getPkBytesByInx(metricData, grpId, rslshData.rcvrIndex);
// left point compute by CMG polyCM:= 64*n
address curveAddr;
curveAddr = IConfig(metricData.config).getCurve(uint8(rslshData.curveType));
(xLeft, yLeft, success) = ICurve(curveAddr).calPolyCommit(rslshData.polyCMData.polyCM, rcvrPk);
require(success, 'calPolyCommit fail');
// right point s[i][i]*G
uint256 uintSij = CommonTool.bytes2uint(sij, 0, uint16(sij.length));
(xRight, yRight, success) = ICurve(curveAddr).mulG(uintSij);
require(success, 'mulG fail');
return ICurve(curveAddr).equalPt(xLeft,yLeft,xRight,yRight);
}
/**
* @notice Determines the final check result based on signature and content validation
* @dev Combines signature and content validation results
* @param bSig Signature validation result
* @param bContent Content validation result
* @return bool Final validation result
*/
function getChkResult(bool bSig, bool bContent)
internal
pure
returns (bool)
{
return !bSig || !bContent;
}
/**
* @notice Writes and validates a slash operation for the S stage
* @dev Processes S stage slash data and updates the metric storage
* @param metricData Storage data for metrics
* @param grpId Group identifier
* @param hashX Hash of the signed data
* @param sslshData Slash data for S stage
* @param smCount Total number of storeman nodes
* @return bool Success status of the operation
* @return uint8 Index of the storeman node
*/
function writeSSlsh(MetricTypes.MetricStorageData storage metricData, bytes32 grpId, bytes32 hashX, MetricTypes.SSlshData memory sslshData, uint8 smCount)
public
returns (bool, uint8)
{
require(metricData.mapSSlsh[grpId][hashX][sslshData.sndrIndex].polyDataPln.polyData.length == 0,"Duplicate SSlsh");
require(sslshData.sndrIndex <= smCount, "invalid send index");
require(sslshData.rcvrIndex <= smCount, "invalid receiver index");
require(sslshData.m.length != 0, "m is empty");
require(sslshData.rpkShare.length != 0, "rpkShare is empty");
require(sslshData.gpkShare.length != 0, "gpkShare is empty");
require(sslshData.polyDataPln.polyData.length != 0, "polyData is empty");
require(sslshData.becauseSndr, "S because sender is not true");
uint8 smIndex;
//smIndex = sslshData.becauseSndr ? sslshData.sndrIndex : sslshData.rcvrIndex;
smIndex = sslshData.sndrIndex;
metricData.mapSSlsh[grpId][hashX][smIndex] = sslshData;
if (checkSProof(metricData, grpId, hashX, smIndex)) {
metricData.mapSlshCount[grpId][getEpochId(metricData)][smIndex] = metricData.mapSlshCount[grpId][getEpochId(metricData)][smIndex].add(uint(1));
return (true, smIndex);
} else {
delete metricData.mapSSlsh[grpId][hashX][smIndex];
return (false, smIndex);
}
}
/**
* @notice Validates the proof for S stage slash operation
* @dev Checks both signature and content of the S stage proof
* @param metricData Storage data for metrics
* @param grpId Group identifier
* @param hashX Hash of the signed data
* @param smIndex Index of the storeman node
* @return bool Validation result
*/
function checkSProof(MetricTypes.MetricStorageData storage metricData, bytes32 grpId, bytes32 hashX, uint8 smIndex)
internal
returns (bool)
{
bool bSig = checkSSig(metricData, grpId, hashX, smIndex);
bool bContent = checkSContent(metricData, grpId, hashX, smIndex);
return getChkResult(bSig, bContent);
}
/**
* @notice Validates the signature of S stage proof
* @dev Verifies the cryptographic signature of the S stage proof
* @param metricData Storage data for metrics
* @param grpId Group identifier
* @param hashX Hash of the signed data
* @param smIndex Index of the storeman node
* @return bool Signature validation result
*/
function checkSSig(MetricTypes.MetricStorageData storage metricData, bytes32 grpId, bytes32 hashX, uint8 smIndex)
internal
returns (bool)
{
bytes32 h;
bytes memory senderPk;
MetricTypes.SSlshData storage sslshData = metricData.mapSSlsh[grpId][hashX][smIndex];
// build h
h = sha256(sslshData.polyDataPln.polyData);
// build senderpk
senderPk = getPkBytesByInx(metricData, grpId, sslshData.sndrIndex);
return ckSig(metricData, h, sslshData.polyDataPln.polyDataR, sslshData.polyDataPln.polyDataS, senderPk);
}
/**
* @notice Validates a cryptographic signature
* @dev Checks if a signature is valid using the specified curve
* @param metricData Storage data for metrics
* @param hash Hash of the signed data
* @param r First part of the signature
* @param s Second part of the signature
* @param pk Public key of the signer
* @return bool Signature validation result
*/
function ckSig(MetricTypes.MetricStorageData storage metricData, bytes32 hash, bytes32 r, bytes32 s, bytes memory pk)
internal
returns (bool){
address curveAddr;
curveAddr = IConfig(metricData.config).getCurve(uint8(CommonTool.CurveType.SK));
return ICurve(curveAddr).checkSig(hash, r, s, pk);
}
/**
* @notice Validates the content of S stage proof
* @dev Verifies the mathematical correctness of the S stage proof content
* @param metricData Storage data for metrics
* @param grpId Group identifier
* @param hashX Hash of the signed data
* @param smIndex Index of the storeman node
* @return bool Content validation result
*/
function checkSContent(MetricTypes.MetricStorageData storage metricData, bytes32 grpId, bytes32 hashX, uint8 smIndex)
internal
returns (bool)
{
bool success;
uint xLeft;
uint yLeft;
uint xRight;
uint yRight;
uint mgpkX;
uint mgpkY;
MetricTypes.SSlshData memory sslshData = metricData.mapSSlsh[grpId][hashX][smIndex];
// s*G
address curveAddr;
curveAddr = IConfig(metricData.config).getCurve(uint8(sslshData.curveType));
uint16 ployDataLen = uint16(sslshData.polyDataPln.polyData.length);
(xRight, yRight, success) = ICurve(curveAddr).mulG(CommonTool.bytes2uint(sslshData.polyDataPln.polyData, 0,ployDataLen));
require(success, 'mulG fail');
// rpkShare + m * gpkShare
(mgpkX, mgpkY, success) = ICurve(curveAddr).mulPk(CommonTool.bytes2uint(sslshData.m, 0, uint16(sslshData.m.length)),
CommonTool.bytes2uint(sslshData.gpkShare, 0, 32),
CommonTool.bytes2uint(sslshData.gpkShare, 32, 32));
require(success, 'mulPk fail');
(xLeft, yLeft, success) = ICurve(curveAddr).add(CommonTool.bytes2uint(sslshData.rpkShare, 0, 32),
CommonTool.bytes2uint(sslshData.rpkShare, 32, 32),
mgpkX,
mgpkY);
require(success, 'add fail');
return ICurve(curveAddr).equalPt(xLeft,yLeft,yLeft,yRight);
}
/**
* @notice Retrieves public key bytes by storeman index
* @dev Gets the public key of a storeman node by its index
* @param metricData Storage data for metrics
* @param grpId Group identifier
* @param smIndex Index of the storeman node
* @return bytes Public key bytes
*/
function getPkBytesByInx(MetricTypes.MetricStorageData storage metricData, bytes32 grpId, uint8 smIndex)
internal
view
returns (bytes memory)
{
bytes memory smPk;
(, smPk,) = (IStoremanGroup)(metricData.smg).getSelectedSmInfo(grpId, uint(smIndex));
return smPk;
}
/**
* @notice Gets the current epoch ID based on timestamp
* @dev Retrieves the epoch ID for the current block timestamp
* @param metricData Storage data for metrics
* @return uint Current epoch ID
*/
function getEpochId(MetricTypes.MetricStorageData storage metricData)
internal
view
returns (uint)
{
return IPosLib(metricData.posLib).getEpochId(block.timestamp);
}
/**
* @notice Gets the total number of storeman nodes in a specific group
* @dev Retrieves the count of selected storeman nodes in a group
* @param metricData Storage data for metrics
* @param grpId Group identifier
* @return uint8 Number of storeman nodes
*/
function getSMCount(MetricTypes.MetricStorageData storage metricData, bytes32 grpId)
public
view
returns (uint8)
{
IStoremanGroup smgTemp = IStoremanGroup(metricData.smg);
return uint8(smgTemp.getSelectedSmNumber(grpId));
}
/**
* @notice Gets the leader address of a group
* @dev Retrieves the address of the leader node in a group
* @param metricData Storage data for metrics
* @param grpId Group identifier
* @return address Leader node address
*/
function getLeader(MetricTypes.MetricStorageData storage metricData, bytes32 grpId)
public
view
returns (address)
{
address leader;
IStoremanGroup smgTemp = IStoremanGroup(metricData.smg);
(leader,,) = smgTemp.getSelectedSmInfo(grpId, uint(0));
return leader;
}
/**
* @notice Gets the work address of a storeman node
* @dev Retrieves the work address of a storeman node by its index
* @param metricData Storage data for metrics
* @param grpId Group identifier
* @param smIndex Index of the storeman node
* @return address Work address of the node
*/
function getWkAddr(MetricTypes.MetricStorageData storage metricData, bytes32 grpId, uint smIndex)
public
view
returns (address)
{
address wkAddr;
IStoremanGroup smgTemp = IStoremanGroup(metricData.smg);
(wkAddr,,) = smgTemp.getSelectedSmInfo(grpId, smIndex);
return wkAddr;
}
/**
* @notice Records a storeman slash event
* @dev Records a slash event for a specific storeman node
* @param metricData Storage data for metrics
* @param grpId Group identifier
* @param smIndex Index of the storeman node
*/
function recordSmSlash(MetricTypes.MetricStorageData storage metricData, bytes32 grpId, uint smIndex)
public
{
address wkAddr;
IStoremanGroup smgTemp = IStoremanGroup(metricData.smg);
wkAddr = getWkAddr(metricData, grpId, smIndex);
smgTemp.recordSmSlash(wkAddr);
}
}// SPDX-License-Identifier: MIT
/*
Copyright 2023 Wanchain Foundation.
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
*/
// _ _ _
// __ ____ _ _ __ ___| |__ __ _(_)_ __ __| | _____ __
// \ \ /\ / / _` | '_ \ / __| '_ \ / _` | | '_ \@/ _` |/ _ \ \ / /
// \ V V / (_| | | | | (__| | | | (_| | | | | | (_| | __/\ V /
// \_/\_/ \__,_|_| |_|\___|_| |_|\__,_|_|_| |_|\__,_|\___| \_/
//
//
pragma solidity ^0.8.18;
import "../../interfaces/IConfig.sol";
import "../../interfaces/IStoremanGroup.sol";
import "../../lib/CommonTool.sol";
/**
* @title MetricTypes
* @dev Library containing type definitions and data structures for metric operations
*/
library MetricTypes {
/**
* @notice Enumeration of possible slash reasons
* @dev Defines different types of slashing conditions
* - CM: Commitment mismatch
* - R: R stage violation
* - RNK: R stage no key violation
* - S: S stage violation
* - SNK: S stage no key violation
*/
enum SlshReason {CM, R, RNK, S, SNK}
/**
* @notice Structure containing polynomial commitment data
* @dev Contains data related to polynomial commitments
* @param polyCM Polynomial commitment
* @param polyCMR R component of polynomial commitment signature
* @param polyCMS S component of polynomial commitment signature
*/
struct PolyCMData {
bytes polyCM;
bytes32 polyCMR;
bytes32 polyCMS;
}
/**
* @notice Structure containing polynomial data and its signature
* @dev Contains polynomial data and its signature components
* @param polyData Polynomial data
* @param polyDataR R component of polynomial data signature
* @param polyDataS S component of polynomial data signature
*/
struct PolyDataPln {
bytes polyData;
bytes32 polyDataR;
bytes32 polyDataS;
}
/**
* @notice Structure containing R stage slash data
* @dev Contains data related to R stage slashing
* @param polyCMData Polynomial commitment data
* @param polyDataPln Polynomial data and signature
* @param sndrIndex Index of the sender
* @param rcvrIndex Index of the receiver
* @param becauseSndr Whether the slash is due to sender
* @param curveType Type of curve used
*/
struct RSlshData {
PolyCMData polyCMData;
PolyDataPln polyDataPln;
uint8 sndrIndex;
uint8 rcvrIndex;
bool becauseSndr;
CommonTool.CurveType curveType;
}
/**
* @notice Structure containing S stage slash data
* @dev Contains data related to S stage slashing
* @param polyDataPln Polynomial data and signature
* @param m Hash of R and hash of M
* @param rpkShare Receiver public key share
* @param gpkShare Group public key share
* @param sndrIndex Index of the sender
* @param rcvrIndex Index of the receiver
* @param becauseSndr Whether the slash is due to sender
* @param curveType Type of curve used
*/
struct SSlshData {
PolyDataPln polyDataPln;
bytes m; // hash(R|| hash(M))
bytes rpkShare;
bytes gpkShare;
uint8 sndrIndex;
uint8 rcvrIndex;
bool becauseSndr;
CommonTool.CurveType curveType;
}
/**
* @notice Structure containing incentive data
* @dev Contains data related to incentives
* @param smIndexes Bitmap of Storeman indexes
*/
struct InctData {
uint256 smIndexes;
}
/**
* @notice Structure containing R stage no working data
* @dev Contains data related to R stage no working status
* @param smIndexes Bitmap of Storeman indexes
*/
struct RNWData {
uint256 smIndexes;
}
/**
* @notice Structure containing S stage no working data
* @dev Contains data related to S stage no working status
* @param smIndexes Bitmap of Storeman indexes
*/
struct SNWData {
uint256 smIndexes;
}
/**
* @notice Structure containing metric storage data
* @dev Contains all data related to metric operations
* @param mapInct Mapping of incentive data by group ID and hash
* @param mapRSlsh Mapping of R stage slash data by group ID, hash, and Storeman index
* @param mapRNW Mapping of R stage no working data by group ID and hash
* @param mapSSlsh Mapping of S stage slash data by group ID, hash, and Storeman index
* @param mapSNW Mapping of S stage no working data by group ID and hash
* @param mapSlshCount Mapping of slash counts by group ID, epoch ID, and Storeman index
* @param mapInctCount Mapping of incentive counts by group ID, epoch ID, and Storeman index
* @param config Address of the configuration contract
* @param smg Address of the Storeman group contract
* @param posLib Address of the POS library contract
*/
struct MetricStorageData {
/** Incentive data **/
/// groupId -> hashx -> InctData
mapping(bytes32 => mapping(bytes32 => InctData)) mapInct;
/** R slsh data **/
// groupId -> hashx -> smIndex -> RSlshData
mapping(bytes32 => mapping(bytes32 => mapping(uint8 => RSlshData))) mapRSlsh;
/** R No Working data **/
// groupId -> hashx -> RNWData
mapping(bytes32 => mapping(bytes32 => RNWData)) mapRNW;
/** S slsh data **/
// groupId -> hashx -> smIndex -> SSlshData
mapping(bytes32 => mapping(bytes32 => mapping(uint8 => SSlshData))) mapSSlsh;
/** S No Working data **/
// groupId -> hashx -> SNWData
mapping(bytes32 => mapping(bytes32 => SNWData)) mapSNW;
/** slsh count statistics **/
/// grpId -> epochId -> smIndex -> slsh count
mapping(bytes32 => mapping(uint256 => mapping(uint8 => uint256))) mapSlshCount;
/** incentive count statistics **/
/// grpId -> epochId -> smIndex -> incentive count
mapping(bytes32 => mapping(uint256 => mapping(uint8 => uint256))) mapInctCount;
/// config instance address
address config;
/// smg instance address
address smg;
/// posLib address
address posLib;
}
}// SPDX-License-Identifier: MIT
/*
Copyright 2023 Wanchain Foundation.
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
*/
// _ _ _
// __ ____ _ _ __ ___| |__ __ _(_)_ __ __| | _____ __
// \ \ /\ / / _` | '_ \ / __| '_ \ / _` | | '_ \@/ _` |/ _ \ \ / /
// \ V V / (_| | | | | (__| | | | (_| | | | | | (_| | __/\ V /
// \_/\_/ \__,_|_| |_|\___|_| |_|\__,_|_|_| |_|\__,_|\___| \_/
//
//
pragma solidity 0.8.18;
import "@openzeppelin/contracts/utils/math/SafeMath.sol";
import "../components/Halt.sol";
import "./MetricStorage.sol";
import "./lib/MetricTypes.sol";
import "../interfaces/IStoremanGroup.sol";
import "../lib/CommonTool.sol";
import "./lib/MetricLib.sol";
import "../interfaces/IPosLib.sol";
/**
* @title MetricDelegate
* @dev Implementation contract for metric system
* This contract handles the recording and retrieval of metrics, incentives, and slashing data
* for the storeman group system
*/
contract MetricDelegate is MetricStorage, Halt{
using SafeMath for uint;
using MetricLib for MetricTypes.MetricStorageData;
/**
* @dev Modifiers for access control
*/
/**
* @dev Ensures that only the group leader can call the function
* @param grpId The ID of the storeman group
* @dev Throws if the caller is not the group leader
*/
modifier onlyLeader(bytes32 grpId) {
address leader;
leader = metricData.getLeader(grpId);
require(msg.sender == leader, "Not leader");
_;
}
/**
* @dev Core metric operations
*/
/**
* @notice Retrieves the addresses of dependent contracts
* @return address The configuration contract address
* @return address The storeman group contract address
* @return address The position library contract address
*/
function getDependence()
external
view
returns (address, address,address)
{
return (metricData.config, metricData.smg, metricData.posLib);
}
/**
* @dev Statistics functions
*/
/**
* @notice Retrieves incentive counts for all storemen during specified epochs
* @param grpId The ID of the storeman group
* @param startEpId The starting epoch ID
* @param endEpId The ending epoch ID
* @return uint[] Array of incentive counts for each storeman
* @dev Throws if endEpId is less than startEpId
*/
function getPrdInctMetric(bytes32 grpId, uint startEpId, uint endEpId)
external
view
returns (uint[] memory) {
require(endEpId >= startEpId, "endEpId<startEpId");
uint[] memory ret;
uint8 n = getSMCount(grpId);
ret = new uint[](n);
for (uint8 i = 0; i < n; i++) {
for (uint j = startEpId; j <= endEpId; j++) {
ret[i] = ret[i].add(metricData.mapInctCount[grpId][j][i]);
}
}
return ret;
}
/**
* @notice Retrieves slash counts for all storemen during specified epochs
* @param grpId The ID of the storeman group
* @param startEpId The starting epoch ID
* @param endEpId The ending epoch ID
* @return uint[] Array of slash counts for each storeman
* @dev Throws if endEpId is less than startEpId
*/
function getPrdSlshMetric(bytes32 grpId, uint startEpId, uint endEpId)
external
view
returns (uint[] memory)
{
require(endEpId >= startEpId, "endEpId<startEpId");
uint[] memory ret;
uint8 n = getSMCount(grpId);
ret = new uint[](n);
for (uint8 i = 0; i < n; i++) {
for (uint j = startEpId; j <= endEpId; j++) {
ret[i] = ret[i].add(metricData.mapSlshCount[grpId][j][i]);
}
}
return ret;
}
/**
* @notice Retrieves the success count for a specific storeman in an epoch
* @param grpId The ID of the storeman group
* @param epId The epoch ID
* @param smIndex The index of the storeman
* @return uint The success count
*/
function getSmSuccCntByEpId(bytes32 grpId, uint epId, uint8 smIndex)
external
view
returns (uint)
{
return metricData.mapInctCount[grpId][epId][smIndex];
}
/**
* @notice Retrieves the slash count for a specific storeman in an epoch
* @param grpId The ID of the storeman group
* @param epId The epoch ID
* @param smIndex The index of the storeman
* @return uint The slash count
*/
function getSlshCntByEpId(bytes32 grpId, uint epId, uint8 smIndex)
external
view
returns (uint)
{
return metricData.mapSlshCount[grpId][epId][smIndex];
}
/**
* @notice Retrieves the R stage slash proof for a specific storeman
* @param grpId The ID of the storeman group
* @param hashX The hash of the signed data
* @param smIndex The index of the storeman
* @return MetricTypes.RSlshData The R stage slash proof data
*/
function getRSlshProof(bytes32 grpId, bytes32 hashX, uint8 smIndex)
external
view
returns (MetricTypes.RSlshData memory)
{
return metricData.mapRSlsh[grpId][hashX][smIndex];
}
/**
* @notice Retrieves the S stage slash proof for a specific storeman
* @param grpId The ID of the storeman group
* @param hashX The hash of the signed data
* @param smIndex The index of the storeman
* @return MetricTypes.SSlshData The S stage slash proof data
*/
function getSSlshProof(bytes32 grpId, bytes32 hashX, uint8 smIndex)
external
view
returns (MetricTypes.SSlshData memory)
{
return metricData.mapSSlsh[grpId][hashX][smIndex];
}
/**
* @dev Write operations for incentives and slashing
*/
/**
* @notice Records incentive data for storemen
* @param grpId The ID of the storeman group
* @param hashX The hash of the signed data
* @param inctData The bitmap of incentivized storemen
* @dev Only callable by the group leader when not halted
* @dev Throws if incentive data already exists
*/
function wrInct(bytes32 grpId, bytes32 hashX, uint inctData)
external
notHalted
onlyLeader(grpId)
{
require(metricData.mapInct[grpId][hashX].smIndexes == uint(0), 'Duplicate Incentive');
metricData.mapInct[grpId][hashX].smIndexes = inctData;
uint8 smCount = getSMCount(grpId);
uint epochId = getEpochId();
for (uint8 i = 0; i < smCount; i++) {
if (checkHamming(inctData, i)) {
metricData.mapInctCount[grpId][epochId][i] = metricData.mapInctCount[grpId][epochId][i].add(uint(1));
}
}
}
/**
* @notice Records R stage slash data
* @param grpId The ID of the storeman group
* @param hashX The hash of the signed data
* @param rslshData The R stage slash data
* @dev Only callable by the group leader when not halted
* @dev Throws if slash data writing fails
* @dev Emits SMSlshLogger event on success
*/
function wrRSlsh(bytes32 grpId, bytes32 hashX, MetricTypes.RSlshData memory rslshData)
public
notHalted
onlyLeader(grpId)
{
bool success;
uint8 smIndex;
(success, smIndex) = metricData.writeRSlsh(grpId, hashX, rslshData, getSMCount(grpId));
require(success, 'Fail to write R slsh');
metricData.recordSmSlash(grpId, smIndex);
emit SMSlshLogger(grpId, hashX, smIndex, MetricTypes.SlshReason.R);
}
/**
* @notice Records S stage slash data
* @param grpId The ID of the storeman group
* @param hashX The hash of the signed data
* @param sslshData The S stage slash data
* @dev Only callable by the group leader when not halted
* @dev Throws if slash data writing fails
* @dev Emits SMSlshLogger event on success
*/
function wrSSlsh(bytes32 grpId, bytes32 hashX, MetricTypes.SSlshData memory sslshData)
public
notHalted
onlyLeader(grpId)
{
bool success;
uint8 smIndex;
(success, smIndex) = metricData.writeSSlsh(grpId, hashX, sslshData, getSMCount(grpId));
require(success, 'Fail to write S Slsh');
metricData.recordSmSlash(grpId, smIndex);
emit SMSlshLogger(grpId, hashX, smIndex, MetricTypes.SlshReason.S);
}
/**
* @notice Sets the addresses of dependent contracts
* @param configAddr The configuration contract address
* @param smgAddr The storeman group contract address
* @param posAddr The position library contract address
* @dev Only callable by the contract owner
* @dev Throws if any address is invalid
*/
function setDependence(address configAddr, address smgAddr, address posAddr)
external
onlyOwner
{
require(configAddr != address(0), "Invalid config address");
require(smgAddr != address(0), "Invalid smg address");
require(posAddr != address(0), "Invalid posLib address");
metricData.config = configAddr;
metricData.smg = smgAddr;
metricData.posLib = posAddr;
}
/**
* @dev Internal helper functions
*/
/**
* @notice Gets the number of storemen in a group
* @param grpId The ID of the storeman group
* @return uint8 The number of storemen
*/
function getSMCount(bytes32 grpId)
private
view
returns (uint8)
{
return uint8(metricData.getSMCount(grpId));
}
/**
* @notice Gets the current epoch ID based on block timestamp
* @return uint The current epoch ID
*/
function getEpochId()
private
view
returns (uint)
{
return IPosLib(metricData.posLib).getEpochId(block.timestamp);
}
function checkHamming(uint indexes, uint8 smIndex)
private
pure
returns (bool)
{
return indexes & (uint(1) << smIndex) != uint(0);
}
receive() external payable {
revert("Not support");
}
}// SPDX-License-Identifier: MIT
/*
Copyright 2023 Wanchain Foundation.
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
*/
// _ _ _
// __ ____ _ _ __ ___| |__ __ _(_)_ __ __| | _____ __
// \ \ /\ / / _` | '_ \ / __| '_ \ / _` | | '_ \@/ _` |/ _ \ \ / /
// \ V V / (_| | | | | (__| | | | (_| | | | | | (_| | __/\ V /
// \_/\_/ \__,_|_| |_|\___|_| |_|\__,_|_|_| |_|\__,_|\___| \_/
//
//
pragma solidity 0.8.18;
pragma experimental ABIEncoderV2;
/**
* Math operations with safety checks
*/
import "../components/Halt.sol";
import "../components/Admin.sol";
import "./MetricStorage.sol";
import "../components/Proxy.sol";
/**
* @title MetricProxy
* @dev Proxy contract for metric system
* This contract implements the upgradeable pattern for the metric system
* and provides administrative controls for contract upgrades
*/
contract MetricProxy is MetricStorage, Halt, Proxy {
/**
* @notice Updates the implementation address of the MetricDelegate contract
* @dev Only callable by the contract owner
* @param impl The address of the new MetricDelegate contract
* @dev Throws if the new implementation address is invalid or the same as current
* @dev Emits Upgraded event on successful upgrade
*/
///@dev update the address of MetricDelegate contract
///@param impl the address of the new MetricDelegate contract
function upgradeTo(address impl) public onlyOwner {
require(impl != address(0), "Cannot upgrade to invalid address");
require(impl != _implementation, "Cannot upgrade to the same implementation");
_implementation = impl;
emit Upgraded(impl);
}
}// SPDX-License-Identifier: MIT
/*
Copyright 2023 Wanchain Foundation.
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
*/
// _ _ _
// __ ____ _ _ __ ___| |__ __ _(_)_ __ __| | _____ __
// \ \ /\ / / _` | '_ \ / __| '_ \ / _` | | '_ \@/ _` |/ _ \ \ / /
// \ V V / (_| | | | | (__| | | | (_| | | | | | (_| | __/\ V /
// \_/\_/ \__,_|_| |_|\___|_| |_|\__,_|_|_| |_|\__,_|\___| \_/
//
//
pragma solidity ^0.8.18;
import "../components/BasicStorage.sol";
import "./lib/MetricTypes.sol";
import "../interfaces/IConfig.sol";
import "../interfaces/IStoremanGroup.sol";
/**
* @title MetricStorage
* @dev Storage contract for metric system
* This contract stores metrics and slashing data for the storeman group system
*/
contract MetricStorage is BasicStorage {
/**
* @dev Events for tracking metric operations
*/
/**
* @notice Emitted when a storeman is slashed
* @param groupId The ID of the storeman group
* @param hashX The hash of the transaction
* @param smIndex The index of the slashed storeman
* @param slshReason The reason for slashing
*/
event SMSlshLogger(bytes32 indexed groupId, bytes32 indexed hashX, uint8 indexed smIndex, MetricTypes.SlshReason slshReason);
/************************************************************
**
** VARIABLES
**
************************************************************/
/**
* @dev Public storage for metric data
* @notice Contains all metric-related data structures and mappings
*/
MetricTypes.MetricStorageData public metricData;
}// SPDX-License-Identifier: MIT
pragma solidity 0.8.18;
/**
* Math operations with safety checks
*/
import "../components/Owned.sol";
import "./OracleStorage.sol";
/**
* @title OracleDelegate
* @dev Implementation contract for Oracle functionality
* This contract provides:
* - Price data management
* - Storeman group configuration management
* - Admin role management
* - Debt status tracking
*/
contract OracleDelegate is OracleStorage, Owned {
/**
* @notice Emitted when admin address is changed
* @param addr New admin address
*/
event SetAdmin(address addr);
/**
* @notice Emitted when prices are updated
* @param keys Array of price keys
* @param prices Array of new prices
*/
event UpdatePrice(bytes32[] keys, uint[] prices);
/**
* @notice Emitted when debt clean status is updated
* @param id Storeman group ID
* @param isDebtClean New debt clean status
*/
event SetDebtClean(bytes32 indexed id, bool isDebtClean);
/**
* @notice Emitted when storeman group configuration is set
* @param id Storeman group ID
* @param status New status
* @param deposit New deposit amount
* @param chain Array of chain IDs
* @param curve Array of curve parameters
* @param gpk1 First group public key
* @param gpk2 Second group public key
* @param startTime New start time
* @param endTime New end time
*/
event SetStoremanGroupConfig(bytes32 indexed id, uint8 status, uint deposit, uint[2] chain, uint[2] curve, bytes gpk1, bytes gpk2, uint startTime, uint endTime);
/**
* @notice Emitted when storeman group status is updated
* @param id Storeman group ID
* @param status New status
*/
event SetStoremanGroupStatus(bytes32 indexed id, uint8 status);
/**
* @notice Emitted when deposit amount is updated
* @param id Storeman group ID
* @param deposit New deposit amount
*/
event UpdateDeposit(bytes32 indexed id, uint deposit);
/**
* @notice Modifier to restrict function access to admin only
* @dev Throws if called by any account other than admin or owner
*/
modifier onlyAdmin() {
require((msg.sender == admin) || (msg.sender == owner), "not admin");
_;
}
/**
* @notice Updates multiple token prices
* @dev Can only be called by admin
* @param keys Array of price keys
* @param prices Array of new prices
* Requirements:
* - Arrays must have the same length
* - Caller must be admin
* Emits:
* - UpdatePrice event with keys and prices
*/
function updatePrice(
bytes32[] calldata keys,
uint[] calldata prices
)
external
onlyAdmin
{
require(keys.length == prices.length, "length not same");
for (uint256 i = 0; i < keys.length; i++) {
mapPrices[keys[i]] = prices[i];
}
emit UpdatePrice(keys, prices);
}
/**
* @notice Updates deposit amount for a storeman group
* @dev Can only be called by admin
* @param smgID Storeman group ID
* @param amount New deposit amount
* Requirements:
* - Caller must be admin
* Emits:
* - UpdateDeposit event with group ID and new amount
*/
function updateDeposit(
bytes32 smgID,
uint amount
)
external
onlyAdmin
{
mapStoremanGroupConfig[smgID].deposit = amount;
emit UpdateDeposit(smgID, amount);
}
/**
* @notice Sets status for a storeman group
* @dev Can only be called by admin
* @param id Storeman group ID
* @param status New status
* Requirements:
* - Caller must be admin
* Emits:
* - SetStoremanGroupStatus event with group ID and new status
*/
function setStoremanGroupStatus(
bytes32 id,
uint8 status
)
external
onlyAdmin
{
mapStoremanGroupConfig[id].status = status;
emit SetStoremanGroupStatus(id, status);
}
/**
* @notice Sets complete configuration for a storeman group
* @dev Can only be called by admin
* @param id Storeman group ID
* @param status New status
* @param deposit New deposit amount
* @param chain Array of chain IDs
* @param curve Array of curve parameters
* @param gpk1 First group public key
* @param gpk2 Second group public key
* @param startTime New start time
* @param endTime New end time
* Requirements:
* - Caller must be admin
* Emits:
* - SetStoremanGroupConfig event with all parameters
*/
function setStoremanGroupConfig(
bytes32 id,
uint8 status,
uint deposit,
uint[2] calldata chain,
uint[2] calldata curve,
bytes calldata gpk1,
bytes calldata gpk2,
uint startTime,
uint endTime
)
external
onlyAdmin
{
mapStoremanGroupConfig[id].deposit = deposit;
mapStoremanGroupConfig[id].status = status;
mapStoremanGroupConfig[id].chain[0] = chain[0];
mapStoremanGroupConfig[id].chain[1] = chain[1];
mapStoremanGroupConfig[id].curve[0] = curve[0];
mapStoremanGroupConfig[id].curve[1] = curve[1];
mapStoremanGroupConfig[id].gpk1 = gpk1;
mapStoremanGroupConfig[id].gpk2 = gpk2;
mapStoremanGroupConfig[id].startTime = startTime;
mapStoremanGroupConfig[id].endTime = endTime;
emit SetStoremanGroupConfig(id, status, deposit, chain, curve, gpk1, gpk2, startTime, endTime);
}
/**
* @notice Sets debt clean status for a storeman group
* @dev Can only be called by admin
* @param storemanGroupId Storeman group ID
* @param isClean New debt clean status
* Requirements:
* - Caller must be admin
* Emits:
* - SetDebtClean event with group ID and new status
*/
function setDebtClean(
bytes32 storemanGroupId,
bool isClean
)
external
onlyAdmin
{
mapStoremanGroupConfig[storemanGroupId].isDebtClean = isClean;
emit SetDebtClean(storemanGroupId, isClean);
}
/**
* @notice Sets the admin address
* @dev Can only be called by owner
* @param addr New admin address
* Requirements:
* - Caller must be owner
* Emits:
* - SetAdmin event with new admin address
*/
function setAdmin(
address addr
) external onlyOwner
{
admin = addr;
emit SetAdmin(addr);
}
/**
* @notice Gets price for a specific key
* @param key Price key
* @return Current price value
*/
function getValue(bytes32 key) external view returns (uint) {
return mapPrices[key];
}
/**
* @notice Gets prices for multiple keys
* @param keys Array of price keys
* @return values Array of current prices
*/
function getValues(bytes32[] calldata keys) external view returns (uint[] memory values) {
values = new uint[](keys.length);
for(uint256 i = 0; i < keys.length; i++) {
values[i] = mapPrices[keys[i]];
}
}
/**
* @notice Gets deposit amount for a storeman group
* @param smgID Storeman group ID
* @return Current deposit amount
*/
function getDeposit(bytes32 smgID) external view returns (uint) {
return mapStoremanGroupConfig[smgID].deposit;
}
/**
* @notice Gets complete configuration for a storeman group
* @param id Storeman group ID
* @return groupId Group ID
* @return status Current status
* @return deposit Current deposit amount
* @return chain1 First chain ID
* @return chain2 Second chain ID
* @return curve1 First curve parameter
* @return curve2 Second curve parameter
* @return gpk1 First group public key
* @return gpk2 Second group public key
* @return startTime Start time
* @return endTime End time
*/
function getStoremanGroupConfig(
bytes32 id
)
external
view
returns(bytes32 groupId, uint8 status, uint deposit, uint chain1, uint chain2, uint curve1, uint curve2, bytes memory gpk1, bytes memory gpk2, uint startTime, uint endTime)
{
groupId = id;
status = mapStoremanGroupConfig[id].status;
deposit = mapStoremanGroupConfig[id].deposit;
chain1 = mapStoremanGroupConfig[id].chain[0];
chain2 = mapStoremanGroupConfig[id].chain[1];
curve1 = mapStoremanGroupConfig[id].curve[0];
curve2 = mapStoremanGroupConfig[id].curve[1];
gpk1 = mapStoremanGroupConfig[id].gpk1;
gpk2 = mapStoremanGroupConfig[id].gpk2;
startTime = mapStoremanGroupConfig[id].startTime;
endTime = mapStoremanGroupConfig[id].endTime;
}
/**
* @notice Gets status information for a storeman group
* @param id Storeman group ID
* @return status Current status
* @return startTime Start time
* @return endTime End time
*/
function getStoremanGroupStatus(bytes32 id)
public
view
returns(uint8 status, uint startTime, uint endTime)
{
status = mapStoremanGroupConfig[id].status;
startTime = mapStoremanGroupConfig[id].startTime;
endTime = mapStoremanGroupConfig[id].endTime;
}
/**
* @notice Checks if a storeman group's debts are clean
* @param storemanGroupId Storeman group ID
* @return Whether debts are clean
*/
function isDebtClean(
bytes32 storemanGroupId
)
external
view
returns (bool)
{
return mapStoremanGroupConfig[storemanGroupId].isDebtClean;
}
}// SPDX-License-Identifier: MIT
pragma solidity ^0.8.18;
import "../components/BasicStorage.sol";
/**
* @title OracleStorage
* @dev Storage contract for Oracle functionality
* This contract provides:
* - Price data storage
* - Storeman group configuration storage
* - Admin role management
*/
contract OracleStorage is BasicStorage {
/************************************************************
**
** STRUCTURE DEFINATIONS
**
************************************************************/
/**
* @notice Configuration structure for Storeman Group
* @dev Contains all necessary parameters for storeman group operation
* @param deposit Required deposit amount
* @param chain Array of chain IDs [source, destination]
* @param curve Array of curve parameters
* @param gpk1 First group public key
* @param gpk2 Second group public key
* @param startTime Start time of the group
* @param endTime End time of the group
* @param status Current status of the group
* @param isDebtClean Whether all debts are cleared
*/
struct StoremanGroupConfig {
uint deposit;
uint[2] chain;
uint[2] curve;
bytes gpk1;
bytes gpk2;
uint startTime;
uint endTime;
uint8 status;
bool isDebtClean;
}
/************************************************************
**
** VARIABLES
**
************************************************************/
/// @notice Mapping from token symbol to its current price
/// @dev Used to store and retrieve token prices
mapping(bytes32 => uint) public mapPrices;
/// @notice Mapping from storeman group ID to its configuration
/// @dev Stores all configuration parameters for each storeman group
mapping(bytes32 => StoremanGroupConfig) public mapStoremanGroupConfig;
/// @notice Address of the admin account
/// @dev Admin has special privileges for contract management
address public admin;
}// SPDX-License-Identifier: MIT
pragma solidity ^0.8.18;
import "@openzeppelin/contracts/utils/math/SafeMath.sol";
/**
* @title Bn128
* @dev Implementation of the Bn128 elliptic curve operations
* This contract provides:
* - Base point (G) coordinates
* - Curve order
* - Point addition (ECADD)
* - Scalar multiplication (ECMUL)
*/
contract Bn128 {
using SafeMath for uint256;
/// @notice x-coordinate of the base point G
uint256 constant gx = 0x1;
/// @notice y-coordinate of the base point G
uint256 constant gy = 0x2;
/// @notice Order of the curve
/// @dev Order is the number of elements in both G₁ and G₂: 36u⁴+36u³+18u²+6u+1
uint256 constant order = 21888242871839275222246405745257275088548364400416034343698204186575808495617;
/**
* @notice Returns the x-coordinate of the base point G
* @return x-coordinate of G
*/
function getGx() public pure returns (uint256) {
return gx;
}
/**
* @notice Returns the y-coordinate of the base point G
* @return y-coordinate of G
*/
function getGy() public pure returns (uint256) {
return gy;
}
/**
* @notice Returns the order of the curve
* @return Order of the curve
*/
function getOrder() public pure returns (uint256) {
return order;
}
/**
* @notice Performs point addition on the curve
* @dev Adds two points (x1,y1) and (x2,y2) to get (x3,y3)
* @param x1 x-coordinate of first point
* @param y1 y-coordinate of first point
* @param x2 x-coordinate of second point
* @param y2 y-coordinate of second point
* @return x3 x-coordinate of resulting point
* @return y3 y-coordinate of resulting point
*/
function ecadd(
uint256 x1,
uint256 y1,
uint256 x2,
uint256 y2
) public view returns (uint256 x3, uint256 y3) {
uint256[2] memory outValue;
uint256[4] memory input;
input[0] = x1;
input[1] = y1;
input[2] = x2;
input[3] = y2;
assembly {
if iszero(staticcall(gas(), 0x06, input, 0x80, outValue, 0x40)) {
revert(0, 0)
}
}
x3 = outValue[0];
y3 = outValue[1];
}
/**
* @notice Performs scalar multiplication on the curve
* @dev Multiplies point (x1,y1) by scalar to get (x2,y2)
* @param x1 x-coordinate of input point
* @param y1 y-coordinate of input point
* @param scalar Scalar value to multiply by
* @return x2 x-coordinate of resulting point
* @return y2 y-coordinate of resulting point
*/
function ecmul(
uint256 x1,
uint256 y1,
uint256 scalar
) public view returns (uint256 x2, uint256 y2) {
uint256[2] memory outValue;
uint256[3] memory input;
input[0] = x1;
input[1] = y1;
input[2] = scalar;
assembly {
if iszero(staticcall(gas(), 0x07, input, 0x60, outValue, 0x40)) {
revert(0, 0)
}
}
x2 = outValue[0];
y2 = outValue[1];
}
}// SPDX-License-Identifier: MIT
pragma solidity ^0.8.18;
import "./Bn128.sol";
/**
* @title Bn128SchnorrVerifier
* @dev Implementation of Schnorr signature verification for the Bn128 curve
* This contract provides functionality to verify Schnorr signatures using the Bn128 elliptic curve
*/
contract Bn128SchnorrVerifier is Bn128 {
using SafeMath for uint256;
/**
* @dev Structure representing a point on the elliptic curve
* @param x X-coordinate of the point
* @param y Y-coordinate of the point
*/
struct Point {
uint256 x; uint256 y;
}
/**
* @dev Structure containing all the data needed for signature verification
* @param groupKey The public key point
* @param randomPoint The random point used in signature generation
* @param signature The signature value
* @param message The message that was signed
* @param _hash The hash value computed during verification
* @param _left The left side of the verification equation
* @param _right The right side of the verification equation
*/
struct Verification {
Point groupKey;
Point randomPoint;
uint256 signature;
bytes32 message;
uint256 _hash;
Point _left;
Point _right;
}
/**
* @notice Computes the hash value used in Schnorr signature verification
* @dev Implements the hash function h(m, R) where m is the message and R is the random point
* @param m The message to be hashed
* @param a X-coordinate of the random point
* @param b Y-coordinate of the random point
* @return The computed hash value
*/
function h(bytes32 m, uint256 a, uint256 b) public pure returns (uint256) {
return uint256(sha256(abi.encodePacked(m, a, b)));
}
/**
* @notice Performs scalar multiplication on a point
* @dev Wrapper for the ecmul function from Bn128
* @param x X-coordinate of the input point
* @param y Y-coordinate of the input point
* @param scalar The scalar value to multiply by
* @return The resulting point coordinates
*/
function cmul(uint256 x, uint256 y, uint256 scalar) public view returns (uint256, uint256) {
return ecmul(x, y, scalar);
}
/**
* @notice Computes the signature point sG
* @dev Multiplies the base point G by the signature value
* @param sig_s The signature value
* @return The resulting point coordinates
*/
function sg(uint256 sig_s) public view returns (uint256, uint256) {
return ecmul(getGx(), getGy(), sig_s);
}
/**
* @notice Performs point addition
* @dev Wrapper for the ecadd function from Bn128
* @param ax X-coordinate of the first point
* @param ay Y-coordinate of the first point
* @param bx X-coordinate of the second point
* @param by Y-coordinate of the second point
* @return The resulting point coordinates
*/
function cadd(uint256 ax, uint256 ay, uint256 bx, uint256 by) public view returns (uint256, uint256) {
return ecadd(ax, ay, bx, by);
}
/**
* @notice Verifies a Schnorr signature
* @dev Implements the Schnorr signature verification algorithm for Bn128 curve
* The verification checks if sG = R + h(m,R)Y where:
* - s is the signature
* - G is the base point
* - R is the random point
* - h(m,R) is the hash of the message and random point
* - Y is the public key
* The hash value is reduced modulo the curve order to ensure it's in the correct range
* @param signature The signature to verify
* @param groupKeyX X-coordinate of the public key
* @param groupKeyY Y-coordinate of the public key
* @param randomPointX X-coordinate of the random point
* @param randomPointY Y-coordinate of the random point
* @param message The message that was signed
* @return bool indicating whether the signature is valid
*/
function verify(bytes32 signature, bytes32 groupKeyX, bytes32 groupKeyY, bytes32 randomPointX, bytes32 randomPointY, bytes32 message)
public
view
returns(bool)
{
bool flag = false;
Verification memory state;
state.signature = uint256(signature);
state.groupKey.x = uint256(groupKeyX);
state.groupKey.y = uint256(groupKeyY);
state.randomPoint.x = uint256(randomPointX);
state.randomPoint.y = uint256(randomPointY);
state.message = message;
state._hash = h(state.message, state.randomPoint.x, state.randomPoint.y);
/// @dev Reduce hash value to bn256 range
state._hash = (state._hash).mod(getOrder());
(state._left.x, state._left.y) = sg(state.signature);
Point memory rightPart;
(rightPart.x, rightPart.y) = cmul(state.groupKey.x, state.groupKey.y, state._hash);
(state._right.x, state._right.y) = cadd(state.randomPoint.x, state.randomPoint.y, rightPart.x, rightPart.y);
flag = state._left.x == state._right.x && state._left.y == state._right.y;
return flag;
}
}// SPDX-License-Identifier: MIT
pragma solidity >=0.8.0;
/**
* @title EcSchnorrVerifier
* @dev Implementation of Schnorr signature verification using Ethereum's ecrecover precompile
* This contract provides a gas-efficient way to verify Schnorr signatures by leveraging
* the existing ecrecover functionality
*/
contract EcSchnorrVerifier {
/// @notice The order of the secp256k1 curve group
/// @dev This is the prime order of the curve, used for modular arithmetic
uint256 constant public Q =
0xFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFEBAAEDCE6AF48A03BBFD25E8CD0364141;
/**
* @notice Internal function to verify a Schnorr signature
* @dev Uses ecrecover precompile to verify the signature
* The verification process:
* 1. Computes sp = Q - (s * px) mod Q
* 2. Computes ep = Q - (e * px) mod Q
* 3. Uses ecrecover to recover the signer's address
* 4. Verifies the challenge e matches the hash of the recovered data
* @param parity The parity of the public key y-coordinate (27 or 28)
* @param px The x-coordinate of the public key
* @param message The 32-byte message that was signed
* @param e The Schnorr signature challenge
* @param s The Schnorr signature
* @return bool indicating whether the signature is valid
* @dev Throws if sp is zero or if ecrecover fails
*/
function _verify(
uint8 parity,
bytes32 px,
bytes32 message,
bytes32 e,
bytes32 s
) public pure returns (bool) {
// Compute sp = Q - (s * px) mod Q
bytes32 sp = bytes32(Q - mulmod(uint256(s), uint256(px), Q));
// Compute ep = Q - (e * px) mod Q
bytes32 ep = bytes32(Q - mulmod(uint256(e), uint256(px), Q));
require(sp != 0);
// The ecrecover precompile implementation checks that the `r` and `s`
// inputs are non-zero (in this case, `px` and `ep`), thus we don't need to
// check if they're zero.
address R = ecrecover(sp, parity, px, ep);
require(R != address(0), "ecrecover failed");
// Verify that e matches the hash of the recovered data
return e == keccak256(
abi.encodePacked(R, uint8(parity), px, message)
);
}
/**
* @notice Public function to verify a Schnorr signature
* @dev Wrapper for the internal _verify function
* @param signature The signature to verify
* @param px The x-coordinate of the public key
* @param e The Schnorr signature challenge
* @param parity The parity of the public key y-coordinate
* @param message The message that was signed
* @return bool indicating whether the signature is valid
*/
function verify(bytes32 signature, bytes32 px, bytes32 /*groupKeyY*/, bytes32 e, bytes32 parity, bytes32 message)
public
pure
returns(bool)
{
return _verify(uint8(uint256(parity)), px, message, e, signature);
}
}// SPDX-License-Identifier: MIT
pragma solidity ^0.8.18;
import "../components/Halt.sol";
import "../interfaces/ISignatureVerifier.sol";
/**
* @title IBaseSignVerifier
* @dev Interface for multi-curve signature verification
* This interface defines the standard verification method that all curve-specific verifiers must implement
*/
interface IBaseSignVerifier {
/**
* @notice Verifies a signature using the specified curve parameters
* @param signature The signature to verify
* @param groupKeyX X-coordinate of the group public key
* @param groupKeyY Y-coordinate of the group public key
* @param randomPointX X-coordinate of the random point
* @param randomPointY Y-coordinate of the random point
* @param message The message that was signed
* @return bool indicating whether the signature is valid
*/
function verify(
bytes32 signature,
bytes32 groupKeyX,
bytes32 groupKeyY,
bytes32 randomPointX,
bytes32 randomPointY,
bytes32 message
) external returns (bool);
}
/**
* @title SignatureVerifier
* @dev A contract that manages and routes signature verification requests to appropriate curve-specific verifiers
* This contract acts as a registry and router for different curve implementations
*/
contract SignatureVerifier is Halt {
/// @notice Mapping from curve ID to its corresponding verifier contract address
/// @dev Used to route verification requests to the appropriate curve implementation
mapping(uint256 => address) public verifierMap;
/**
* @notice Verifies a signature using the specified curve
* @dev Routes the verification request to the appropriate curve-specific verifier
* @param curveId The ID of the curve to use for verification
* @param signature The signature to verify
* @param groupKeyX X-coordinate of the group public key
* @param groupKeyY Y-coordinate of the group public key
* @param randomPointX X-coordinate of the random point
* @param randomPointY Y-coordinate of the random point
* @param message The message that was signed
* @return bool indicating whether the signature is valid
* @dev Throws if the curveId is not registered
*/
function verify(
uint256 curveId,
bytes32 signature,
bytes32 groupKeyX,
bytes32 groupKeyY,
bytes32 randomPointX,
bytes32 randomPointY,
bytes32 message
) external returns (bool) {
require(verifierMap[curveId] != address(0), "curveId not correct");
IBaseSignVerifier verifier = IBaseSignVerifier(verifierMap[curveId]);
return verifier.verify(signature, groupKeyX, groupKeyY, randomPointX, randomPointY, message);
}
/**
* @notice Registers a new curve verifier
* @dev Only callable by the contract owner
* @param curveId The ID of the curve to register
* @param verifierAddress The address of the curve-specific verifier contract
*/
function register(uint256 curveId, address verifierAddress) external onlyOwner {
verifierMap[curveId] = verifierAddress;
}
}// SPDX-License-Identifier: MIT
pragma solidity ^0.8.18;
import "@openzeppelin/contracts/utils/math/SafeMath.sol";
/**
* @title Deposit
* @dev Library for managing deposit records, used to track storeman group stake history
*
* @custom:key-features
* - Cumulative deposit record management
* - ID-based historical queries
* - Support for incremental deposit records
* - Cleanup functionality
*
* @custom:security
* - Uses SafeMath to prevent overflows
* - Internal access control
* - Ordered record storage
*/
library Deposit {
using SafeMath for uint;
/**
* @dev Structure for a single deposit record
*
* @param id ID of the deposit record, typically a timestamp or sequence number
* @param value Current total deposit value, including previous deposits
*
* @custom:usage
* - Used to store cumulative deposit amount at a specific point in time
* - ID typically used to correlate with external events or blocks
*/
struct Record {
uint id;
uint value; // the value is current total value, include the old deposit
}
/**
* @dev Structure for a collection of deposit records
*
* @param total Total number of records
* @param records Mapping from index to deposit record
*
* @custom:usage
* - Used to manage a set of related deposit records
* - Supports chronological storage and retrieval
* - Maintains cumulative values to simplify queries
*/
struct Records {
uint total;
mapping(uint=>Record) records;
}
/**
* @dev Get the deposit value of the last record
*
* @param self Storage reference to the Records collection
* @return uint Deposit value of the last record, or 0 if no records exist
*
* @custom:effects
* - Read-only operation, does not modify state
* - Returns 0 if no records exist
*/
function getLastValue(Records storage self) internal view returns (uint) {
if(self.total == 0) {
return 0;
} else {
return self.records[self.total-1].value;
}
}
/**
* @dev Get deposit value by ID, returns the value of the last record with ID less than or equal to the specified ID
*
* @param self Storage reference to the Records collection
* @param id ID to query for
* @return uint Deposit value associated with the specified ID
*
* @custom:effects
* - Read-only operation, does not modify state
* - Searches backwards from the most recent record for efficiency
* - Returns the value of the first record with ID less than or equal to the specified ID
* - Returns 0 if all record IDs are greater than the specified ID
*/
function getValueById(Records storage self, uint id) internal view returns (uint) {
for (uint i = self.total; i > 0; i--) {
if (id >= self.records[i-1].id){
return self.records[i-1].value;
}
}
return 0;
}
/**
* @dev Clean all deposit records
*
* @param self Storage reference to the Records collection
*
* @custom:effects
* - Resets the total number of records to 0
* - Does not actually delete previous records, but they will no longer be accessible
* - Used for restarting record-keeping or cleaning up unneeded historical data
*/
function clean(Records storage self) internal {
self.total = 0;
}
/**
* @dev Add a new deposit record
*
* @param self Storage reference to the Records collection
* @param r New record to add
*
* @custom:effects
* - If it's the first record, adds it directly
* - If the new record's ID is the same as the last record's ID, combines the values of the two records
* - Otherwise, adds a new record with value being the new record's value plus the last record's value
* - Maintains cumulative deposit values to simplify historical queries
*/
function addRecord(Records storage self, Record memory r) internal {
if(self.total == 0) {
self.records[0] = r;
self.total = 1;
} else {
Record storage e = self.records[self.total-1];
if(e.id == r.id) {
e.value = e.value.add(r.value);
}else{
Record memory n = Record(r.id, r.value);
n.value = n.value.add(self.records[self.total-1].value);
self.records[self.total] = n;
self.total++;
}
}
}
}// SPDX-License-Identifier: MIT
/*
Copyright 2023 Wanchain Foundation.
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
*/
// _ _ _
// __ ____ _ _ __ ___| |__ __ _(_)_ __ __| | _____ __
// \ \ /\ / / _` | '_ \ / __| '_ \ / _` | | '_ \@/ _` |/ _ \ \ / /
// \ V V / (_| | | | | (__| | | | (_| | | | | | (_| | __/\ V /
// \_/\_/ \__,_|_| |_|\___|_| |_|\__,_|_|_| |_|\__,_|\___| \_/
//
//
pragma solidity ^0.8.18;
import "./StoremanType.sol";
import "../interfaces/IPosLib.sol";
import "../interfaces/IMetric.sol";
import "../interfaces/IListGroup.sol";
import "./StoremanUtil.sol";
/**
* @title IncentiveLib
* @dev Library for managing incentive records and calculations
* This library provides functionality for tracking and calculating incentives
* for storeman groups and their members
*
* Key features:
* - Incentive record management
* - Incentive calculation
* - Record querying and validation
*
* @custom:usage
* - Used by StoremanGroupDelegate contract
* - Manages incentive distribution
* - Tracks incentive history
*
* @custom:security
* - Input validation
* - Safe math operations
* - State consistency checks
*/
library IncentiveLib {
using Deposit for Deposit.Records;
using SafeMath for uint;
uint public constant DIVISOR = 10000;
/**
* @notice Event emitted when incentives are processed
* @dev Indicates the progress of incentive processing
* @param groupId ID of the group
* @param wkAddr Work address
* @param finished Whether processing is complete
* @param from Starting day
* @param end Ending day
*/
event incentiveEvent(bytes32 indexed groupId, address indexed wkAddr, bool indexed finished, uint from, uint end);
/**
* @notice Event emitted when members are selected
* @dev Indicates the selection of group members
* @param groupId ID of the group
* @param count Number of selected members
* @param members Array of selected member addresses
*/
event selectedEvent(bytes32 indexed groupId, uint indexed count, address[] members);
/**
* @notice Gets the chain type coefficient for a given chain pair
* @dev Calculates the coefficient based on chain IDs
* @param data Storeman data storage
* @param chain1 First chain ID
* @param chain2 Second chain ID
* @return co Coefficient value
*/
function getChainTypeCo(StoremanType.StoremanData storage data, uint chain1, uint chain2) public view returns(uint co){
if(chain1 < chain2) {
co = data.chainTypeCo[chain1][chain2];
} else {
co = data.chainTypeCo[chain2][chain1];
}
if(co == 0){
return data.conf.chainTypeCoDefault;
}
return co;
}
/**
* @notice Gets the group incentive for a specific day
* @dev Calculates the incentive based on deposit and chain type coefficient
* @param groupListAddr Address of the group list contract
* @param group Storeman group data
* @param day Target day
* @param data Storeman data storage
* @return Calculated incentive amount
*/
function getGroupIncentive(address groupListAddr, StoremanType.StoremanGroup storage group, uint day,StoremanType.StoremanData storage data) private returns (uint) {
uint chainTypeCo = getChainTypeCo(data,group.chain1, group.chain2);
uint totalDeposit = IListGroup(groupListAddr).getTotalDeposit(day);
if(totalDeposit == 0){
bytes32[] memory groupIds = IListGroup(groupListAddr).getActiveGroupIds(day);
for(uint i=0; i<groupIds.length; i++){
totalDeposit = totalDeposit.add(data.groups[groupIds[i]].deposit.getValueById(day));
}
require(totalDeposit != 0, "internal error");
IListGroup(groupListAddr).setTotalDeposit(day, totalDeposit);
}
return IPosLib(data.posLib).getMinIncentive(group.deposit.getValueById(day),day, totalDeposit).mul(chainTypeCo).div(DIVISOR);
}
/**
* @notice Calculates incentive for a specific weight
* @dev Computes incentive based on group incentive and weights
* @param groupIncentive Total group incentive
* @param groupWeight Total group weight
* @param weight Individual weight
* @return Calculated incentive amount
*/
function calIncentive(uint groupIncentive, uint groupWeight, uint weight) private pure returns (uint) {
return groupIncentive.mul(weight).div(groupWeight);
}
/**
* @notice Checks metric data for a specific day
* @dev Validates metric data against leader performance
* @param metric Metric contract address
* @param groupId ID of the group
* @param day Target day
* @param index Node index
* @return bool indicating if metric check passed
*/
function checkMetric(IMetric metric, bytes32 groupId, uint day, uint index) private returns (bool) {
if(index == 0) {
return true; // leader is always OK.
}
uint[] memory counts = metric.getPrdInctMetric(groupId, day, day);
uint leadCount = counts[0];
if(leadCount < 6) {
return true;
}
uint nodeCount = counts[index];
if(nodeCount >= leadCount/2){
return true;
}
return false;
}
/**
* @notice Rotates Storeman group for a node
* @dev Updates group assignment for a node based on time
* @param posLib POS library address
* @param sk Candidate data
* @param group Group data
*/
function rotateSkGroup(address posLib, StoremanType.Candidate storage sk, StoremanType.StoremanGroup storage group) public {
if(sk.incentivedDay+1 == StoremanUtil.getDaybyTime(posLib, group.workTime+group.totalTime) && group.status == StoremanType.GroupStatus.dismissed) {
if(sk.nextGroupId != bytes32(0x00)) {
sk.groupId = sk.nextGroupId;
sk.nextGroupId = bytes32(0x00);
} else {
// if whitelist, set groupId = 0
if(sk.isWhite){
sk.groupId = bytes32(0x00);
}
}
}
}
/**
* @notice Calculates start and end days for incentive processing
* @dev Determines the time range for incentive calculation
* @param posLib POS library address
* @param sk Candidate data
* @param group Group data
* @return Tuple containing (start day, end day)
*/
function calFromEndDay(address posLib, StoremanType.Candidate storage sk, StoremanType.StoremanGroup storage group) private view returns(uint,uint) {
uint fromDay = StoremanUtil.getDaybyTime(posLib, group.workTime);
if (fromDay <= sk.incentivedDay){
fromDay = sk.incentivedDay + 1;
}
uint endDay = block.timestamp;
if (endDay > group.workTime + group.totalTime) {
endDay = group.workTime + group.totalTime;
}
endDay = StoremanUtil.getDaybyTime(posLib, endDay);
return (fromDay, endDay);
}
/**
* @notice Checks if a partner has quit
* @dev Validates partner's quit status
* @param listGroupAddr Group list contract address
* @param sk Candidate data
* @param pnAddr Partner address
* @return bool indicating if partner has quit
*/
function checkPartQuited(address listGroupAddr, StoremanType.Candidate storage sk, address pnAddr) private view returns(bool){
bytes32 QuitGroupId;
bytes32 QuitNextGroupId;
(QuitGroupId,QuitNextGroupId) = IListGroup(listGroupAddr).getPartQuitGroupId(sk.wkAddr, pnAddr);
StoremanType.Delegator storage pn = sk.partners[pnAddr];
if(pn.quited && QuitGroupId != sk.groupId && QuitNextGroupId != sk.groupId){
return true;
}
return false;
}
/**
* @notice Checks if a delegator has quit
* @dev Validates delegator's quit status
* @param listGroupAddr Group list contract address
* @param sk Candidate data
* @param deAddr Delegator address
* @return bool indicating if delegator has quit
*/
function checkDelegateQuited(address listGroupAddr, StoremanType.Candidate storage sk, address deAddr) private view returns(bool){
bytes32 QuitGroupId;
bytes32 QuitNextGroupId;
(QuitGroupId,QuitNextGroupId) = IListGroup(listGroupAddr).getDelegateQuitGroupId(sk.wkAddr, deAddr);
StoremanType.Delegator storage de = sk.delegators[deAddr];
if(de.quited && QuitGroupId != sk.groupId && QuitNextGroupId != sk.groupId){
return true;
}
return false;
}
/**
* @notice Processes incentives for a node
* @dev Calculates and distributes incentives to node and partners
* @param day Target day
* @param sk Candidate data
* @param group Group data
* @param data Storeman data storage
* @param listGroupAddr Group list contract address
*/
function incentiveNode(uint day, StoremanType.Candidate storage sk, StoremanType.StoremanGroup storage group,StoremanType.StoremanData storage data, address listGroupAddr) public {
sk.incentive[day] = calIncentive(group.groupIncentive[day], group.depositWeight.getValueById(day), StoremanUtil.calSkWeight(data.conf.standaloneWeight,sk.deposit.getValueById(day)));
sk.incentive[0] = sk.incentive[0].add(sk.incentive[day]);
data.totalReward = data.totalReward.add(sk.incentive[day]);
for(uint m=0; m<sk.partnerCount; m++){
if(checkPartQuited(listGroupAddr, sk, sk.partMap[m])){
continue;
}
uint partnerWeight = StoremanUtil.calSkWeight(data.conf.standaloneWeight, sk.partners[sk.partMap[m]].deposit.getValueById(day));
uint partnerReward = calIncentive(group.groupIncentive[day], group.depositWeight.getValueById(day), partnerWeight);
sk.incentive[day] = sk.incentive[day].add(partnerReward);
sk.incentive[0] = sk.incentive[0].add(partnerReward);
data.totalReward = data.totalReward.add(partnerReward);
}
}
/**
* @notice Processes incentives for a delegator
* @dev Calculates and distributes incentives to delegator and node
* @param day Target day
* @param sk Candidate data
* @param group Group data
* @param data Storeman data storage
* @param listGroupAddr Group list contract address
*/
function incentiveDelegator(uint day, StoremanType.Candidate storage sk, StoremanType.StoremanGroup storage group,StoremanType.StoremanData storage data, address listGroupAddr) public {
address deAddr = sk.delegatorMap[sk.incentivedDelegator];
if(checkDelegateQuited(listGroupAddr, sk, deAddr)){
sk.incentivedDelegator++;
return;
}
uint incs = calIncentive(group.groupIncentive[day], group.depositWeight.getValueById(day), sk.delegators[deAddr].deposit.getValueById(day));
uint incSk = incs.mul(group.delegateFee).div(DIVISOR);
uint incDe = incs.sub(incSk);
sk.delegators[deAddr].incentive[day] = sk.delegators[deAddr].incentive[day].add(incDe);
sk.delegators[deAddr].incentive[0] = sk.delegators[deAddr].incentive[0].add(incDe);
sk.incentive[day] = sk.incentive[day].add(incSk);
sk.incentive[0] = sk.incentive[0].add(incSk);
data.totalReward = data.totalReward.add(incs);
sk.incentivedDelegator++;
}
/**
* @notice Processes incentives for a candidate
* @dev Main function for processing all incentives
1) get the incentive by day and groupID.
If the incentive array by day haven't got from low level, the tx will try to get it.
so the one who first incentive will spend more gas.
2) calculate the sk incentive every days.
3) calculate the delegator every days one by one.
* @param data Storeman data storage
* @param wkAddr Work address
* @param metricAddr Metric contract address
* @param listGroupAddr Group list contract address
*/
function incentiveCandidator(StoremanType.StoremanData storage data, address wkAddr, address metricAddr, address listGroupAddr) public {
StoremanType.Candidate storage sk = data.candidates[0][wkAddr];
StoremanType.StoremanGroup storage group = data.groups[sk.groupId];
require(group.status >= StoremanType.GroupStatus.ready, "not ready");
uint fromDay; uint endDay;
uint reservedGas = 2500000;
(fromDay, endDay) = calFromEndDay(data.posLib, sk, group);
uint day;
uint idx = 0;
for (; idx < group.selectedCount; idx++) {
address addr = group.selectedNode[idx];
if (addr == sk.wkAddr) {
break;
}
}
require(idx < group.selectedCount, "not selected");
for (day = fromDay; day < endDay; day++) {
if (gasleft() < reservedGas ) { // check the gas. because calculate delegator incentive need more gas left.
emit incentiveEvent(sk.groupId, wkAddr, false, fromDay, day);
return;
}
if (group.groupIncentive[day] == 0) {
group.groupIncentive[day] = getGroupIncentive(listGroupAddr, group, day, data);
}
if(checkMetric(IMetric(metricAddr), sk.groupId, day, idx)){
if(0 == sk.incentive[day]) {
incentiveNode(day, sk,group,data, listGroupAddr);
}
while (sk.incentivedDelegator < sk.delegatorCount) {
if (gasleft() < reservedGas ) {
emit incentiveEvent(sk.groupId, wkAddr, false, fromDay, 0);
return;
}
incentiveDelegator(day, sk,group,data,listGroupAddr);
}
}
sk.incentivedDay = day;
rotateSkGroup(data.posLib, sk, group);
sk.incentivedDelegator = 0;
}
emit incentiveEvent(sk.groupId, wkAddr, true, fromDay, endDay-1);
}
/**
* @notice Sets group deposit information
* @dev Calculates and updates group deposit data
* @param data Storeman data storage
* @param group Group data
*/
function setGroupDeposit(StoremanType.StoremanData storage data,StoremanType.StoremanGroup storage group) public {
uint day = StoremanUtil.getDaybyTime(data.posLib, group.workTime);
uint groupDeposit = 0;
uint groupDepositWeight = 0;
for(uint i = 0; i<group.memberCountDesign; i++){
StoremanType.Candidate storage sk = data.candidates[0][group.selectedNode[i]];
groupDeposit = groupDeposit.add(sk.deposit.getLastValue().add(sk.partnerDeposit).add(sk.delegateDeposit));
groupDepositWeight = groupDepositWeight.add(StoremanUtil.calSkWeight(data.conf.standaloneWeight,sk.deposit.getLastValue().add(sk.partnerDeposit)).add(sk.delegateDeposit));
}
Deposit.Record memory deposit = Deposit.Record(day, groupDeposit);
Deposit.Record memory depositWeight = Deposit.Record(day, groupDepositWeight);
group.deposit.clean();
group.depositWeight.clean();
group.deposit.addRecord(deposit);
group.depositWeight.addRecord(depositWeight);
return;
}
/**
* @notice Cleans up Storeman node data
* @dev Removes or updates node data based on whitelist status
* @param skt Candidate data
* @param groupId Group ID
*/
function cleanSmNode(StoremanType.Candidate storage skt, bytes32 groupId) public {
if(skt.isWhite){
if(skt.groupId == groupId){
skt.groupId = bytes32(0x00);
}else if(skt.nextGroupId == groupId){
skt.nextGroupId = bytes32(0x00);
}
}
}
/**
* @notice Selects members for a group
* @dev Processes member selection and updates group status
* @param data Storeman data storage
* @param groupId Group ID
*/
function toSelect(StoremanType.StoremanData storage data,bytes32 groupId) public {
StoremanType.StoremanGroup storage group = data.groups[groupId];
require(group.status == StoremanType.GroupStatus.curveSeted,"Wrong status");
require(block.timestamp > group.registerTime + group.registerDuration, "Wrong time");
if(group.memberCount < group.memberCountDesign){
group.status = StoremanType.GroupStatus.failed;
for(uint k=0; k<group.whiteCountAll; k++){
StoremanType.Candidate storage skt = data.candidates[0][group.whiteMap[k]];
cleanSmNode(skt, groupId);
}
return;
}
address[] memory members = new address[](group.memberCountDesign);
for(uint i = 0; i<group.memberCountDesign; i++){
members[i] = group.selectedNode[i];
}
emit selectedEvent(groupId, group.memberCountDesign, members);
group.status = StoremanType.GroupStatus.selected;
setGroupDeposit(data,group);
return;
}
}// SPDX-License-Identifier: MIT
pragma solidity ^0.8.18;
import "../components/Admin.sol";
import "./StoremanUtil.sol";
/**
* @title ListGroup
* @dev Contract for managing storeman group lists and their lifecycle
* This contract handles the creation, tracking, and expiration of storeman groups
*
* Key features:
* - Active group management
* - Group lifecycle tracking
* - Delegate and participant quit group tracking
* - Epoch deposit tracking
*
* @custom:security
* - Access control through Admin contract
* - Restricted function access to StoremanGroup contract
* - Safe group management operations
*/
contract ListGroup is Admin {
/**
* @dev Structure representing a storeman group
*
* @param groupId Unique identifier for the group
* @param startTime Timestamp when the group becomes active
* @param endTime Timestamp when the group expires
*
* @custom:usage
* - Used to track group lifecycle
* - Manages group validity periods
* - Supports group expiration checks
*/
struct Group {
bytes32 groupId;
uint startTime;
uint endTime;
}
// Array of all created groups
Group[] groups;
// Address of the StoremanGroup contract
address public smg;
// Address of the POS (Proof of Stake) contract
address public posAddr;
// Mapping of epoch days to total deposits
mapping(uint=>uint) epochDeposit;
// Mapping of work address and delegate address to quit group ID
mapping(address=>mapping(address=>bytes32)) delegateQuitGroupId;
// Mapping of work address and delegate address to next group ID after quit
mapping(address=>mapping(address=>bytes32)) delegateQuitNextGroupId;
// Mapping of work address and participant address to quit group ID
mapping(address=>mapping(address=>bytes32)) partQuitGroupId;
// Mapping of work address and participant address to next group ID after quit
mapping(address=>mapping(address=>bytes32)) partQuitNextGroupId;
/**
* @dev Constructor initializes the contract with StoremanGroup and POS addresses
*
* @param _smg Address of the StoremanGroup contract
* @param _pos Address of the POS contract
*/
constructor(address _smg, address _pos) {
smg = _smg;
posAddr = _pos;
}
/**
* @dev Sets the quit group IDs for a delegate
*
* @param wkAddr Work address of the storeman
* @param deAddr Address of the delegate
* @param groupId Current group ID
* @param nextGroupId Next group ID after quit
*
* @custom:requirements
* - Caller must be the StoremanGroup contract
*
* @custom:effects
* - Updates delegate quit group mappings
*/
function setDelegateQuitGroupId(address wkAddr, address deAddr, bytes32 groupId, bytes32 nextGroupId)external {
require(smg == msg.sender, "not allow");
delegateQuitGroupId[wkAddr][deAddr] = groupId;
delegateQuitNextGroupId[wkAddr][deAddr] = nextGroupId;
}
/**
* @dev Sets the quit group IDs for a participant
*
* @param wkAddr Work address of the storeman
* @param pnAddr Address of the participant
* @param groupId Current group ID
* @param nextGroupId Next group ID after quit
*
* @custom:requirements
* - Caller must be the StoremanGroup contract
*
* @custom:effects
* - Updates participant quit group mappings
*/
function setPartQuitGroupId(address wkAddr, address pnAddr, bytes32 groupId, bytes32 nextGroupId) external {
require(smg == msg.sender, "not allow");
partQuitGroupId[wkAddr][pnAddr] = groupId;
partQuitNextGroupId[wkAddr][pnAddr] = nextGroupId;
}
/**
* @dev Retrieves quit group IDs for a delegate
*
* @param wkAddr Work address of the storeman
* @param deAddr Address of the delegate
* @return groupId Current group ID
* @return nextGroupId Next group ID after quit
*/
function getDelegateQuitGroupId(address wkAddr, address deAddr) external view returns (bytes32 groupId, bytes32 nextGroupId){
return (delegateQuitGroupId[wkAddr][deAddr], delegateQuitNextGroupId[wkAddr][deAddr]);
}
/**
* @dev Retrieves quit group IDs for a participant
*
* @param wkAddr Work address of the storeman
* @param pnAddr Address of the participant
* @return groupId Current group ID
* @return nextGroupId Next group ID after quit
*/
function getPartQuitGroupId(address wkAddr, address pnAddr) external view returns (bytes32 groupId, bytes32 nextGroupId){
return (partQuitGroupId[wkAddr][pnAddr], partQuitNextGroupId[wkAddr][pnAddr]);
}
/**
* @dev Adds a new active group
*
* @param groupId Unique identifier for the new group
* @param startTime Timestamp when the group becomes active
* @param endTime Timestamp when the group expires
*
* @custom:requirements
* - Caller must be the StoremanGroup contract
* - Group ID must not already exist
*
* @custom:effects
* - Adds new group to the groups array
*/
function addActiveGroup(bytes32 groupId, uint startTime, uint endTime) external {
require(smg == msg.sender, "not allow");
for(uint i=0; i<groups.length; i++){
require(groups[i].groupId != groupId,"existed");
}
Group memory one = Group(groupId, startTime, endTime);
groups.push(one);
}
/**
* @dev Sets the total deposit for a specific day
*
* @param day The day to set the deposit for
* @param value The deposit amount
*
* @custom:requirements
* - Caller must be the StoremanGroup contract
*/
function setTotalDeposit(uint day, uint value) external {
require(smg == msg.sender, "not allow");
epochDeposit[day] = value;
}
/**
* @dev Retrieves the total deposit for a specific day
*
* @param day The day to query
* @return The total deposit amount for the specified day
*/
function getTotalDeposit(uint day) external view returns(uint) {
return epochDeposit[day];
}
/**
* @dev Returns all groups
*
* @return Array of all Group structs
*/
function getGroups() external view returns (Group[] memory) {
return groups;
}
/**
* @dev Removes expired groups from the list
*
* @custom:effects
* - Removes groups where endTime is less than current block timestamp
* - Maintains array order by moving last element to removed position
*/
function cleanExpiredGroup() external {
for(uint i=groups.length; i>0; i--) {
if(groups[i-1].endTime < block.timestamp){ // expired.
if(i < groups.length){
groups[i-1]= groups[groups.length-1];
}
groups.pop();
}
}
}
/**
* @dev Returns active group IDs for a specific epoch
*
* @param epochId The epoch ID to query
* @return Array of active group IDs
*
* @custom:effects
* - Filters groups based on epoch ID
* - Returns only groups that are currently active
*/
function getActiveGroupIds(uint epochId) external view returns (bytes32[] memory) {
bytes32[] memory activeGroups = new bytes32[](groups.length);
uint activeCount;
for(uint i=groups.length; i>0; i--) {
if(StoremanUtil.getDaybyTime(posAddr, groups[i-1].startTime) <= epochId){
if(StoremanUtil.getDaybyTime(posAddr, groups[i-1].endTime) > epochId){ // not expired.
activeGroups[activeCount] = groups[i-1].groupId;
activeCount++;
}
}
}
bytes32[] memory ret = new bytes32[](activeCount);
for(uint k; k<activeCount; k++) {
ret[k] = activeGroups[k];
}
return ret;
}
}// SPDX-License-Identifier: MIT
/*
Copyright 2023 Wanchain Foundation.
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
*/
// _ _ _
// __ ____ _ _ __ ___| |__ __ _(_)_ __ __| | _____ __
// \ \ /\ / / _` | '_ \ / __| '_ \ / _` | | '_ \@/ _` |/ _ \ \ / /
// \ V V / (_| | | | | (__| | | | (_| | | | | | (_| | __/\ V /
// \_/\_/ \__,_|_| |_|\___|_| |_|\__,_|_|_| |_|\__,_|\___| \_/
//
// Code style according to: https://github.com/wanchain/wanchain-token/blob/master/style-guide.rst
pragma solidity 0.8.18;
import "@openzeppelin/contracts/utils/math/SafeMath.sol";
import "../components/Halt.sol";
import "../components/Admin.sol";
import "./StoremanGroupStorage.sol";
import "../interfaces/IListGroup.sol";
import "./StoremanLib.sol";
import "./StoremanType.sol";
import "./IncentiveLib.sol";
import "../interfaces/IQuota.sol";
import "../gpk/lib/GpkTypes.sol";
import "../components/ReentrancyGuard.sol";
/**
* @title StoremanGroupDelegate
* @dev Implementation contract for storeman group administration
* This contract implements the core functionality for managing storeman groups,
* including creation, updates, and member management
*
* Key features:
* - Group creation and management
* - Member addition and removal
* - Status updates for groups and members
* - Group deletion
* - Member information updates
*
* @custom:inheritance
* - StoremanGroupAdminStorage: Provides storage layout
* - Halt: Provides emergency stop functionality
* - Admin: Provides administrative access control
* - Proxy: Implements upgradeable proxy pattern
*
* @custom:events
* - Group lifecycle events (create, update, delete)
* - Member management events (add, remove, status update)
* - Information update events (work address, public key, enode ID)
*
* @custom:security
* - Access control through Admin contract
* - Emergency stop capability through Halt contract
* - Upgradeable through Proxy pattern
*/
contract StoremanGroupDelegate is StoremanGroupStorage, Halt, Admin,ReentrancyGuard {
using SafeMath for uint;
using Deposit for Deposit.Records;
bytes key = "openStoreman";
bytes innerKey = "totalDeposit";
/**
* @notice Event emitted when a Storeman group registration starts
* @dev Indicates the beginning of group registration process
* @param groupId ID of the group
* @param preGroupId ID of the previous group
* @param workStart Start time of work period
* @param workDuration Duration of work period
* @param registerDuration Duration of registration period
*/
event StoremanGroupRegisterStartEvent(bytes32 indexed groupId, bytes32 indexed preGroupId, uint workStart, uint workDuration, uint registerDuration);
/**
* @notice Event emitted when a Storeman group is dismissed
* @dev Indicates group dismissal
* @param groupId ID of the group
* @param dismissTime Time of dismissal
*/
event StoremanGroupDismissedEvent(bytes32 indexed groupId, uint dismissTime);
/**
* @notice Event emitted when GPK is set for a group
* @dev Indicates GPK configuration
* @param groupId ID of the group
*/
event StoremanGroupSetGpkEvent(bytes32 indexed groupId);
/**
* @notice Event emitted when group chain configuration is updated
* @dev Indicates chain configuration update
* @param groupId ID of the group
* @param chain1 First chain ID
* @param chain2 Second chain ID
* @param curve1 First curve type
* @param curve2 Second curve type
*/
event updateGroupChainEvent(bytes32 indexed groupId, uint256 indexed chain1, uint256 indexed chain2, uint256 curve1, uint256 curve2);
/**
* @notice Event emitted when contribution is made to a group
* @dev Indicates contribution transaction
* @param sender Address of the contributor
* @param value Amount contributed
*/
event storemanGroupContributeEvent(address indexed sender, uint indexed value);
/**
* @dev Modifier to restrict access to group leader only
* @param groupId ID of the group
*/
modifier onlyGroupLeader(bytes32 groupId) {
StoremanType.StoremanGroup storage group = data.groups[groupId];
require(msg.sender == group.selectedNode[0], "Sender is not allowed");
_;
}
/**
* @notice Sets dependencies for the contract
* @dev Configures contract addresses for metric, GPK, quota, and POS
* @param metricAddr Address of the metric contract
* @param gpkAddr Address of the GPK contract
* @param quotaAddr Address of the quota contract
* @param posAddr Address of the POS contract
*/
function setDependence(address metricAddr, address gpkAddr,address quotaAddr, address posAddr)
external
onlyOwner
{
require(metricAddr != address(0), "Invalid metricAddr address");
require(gpkAddr != address(0), "Invalid gpkAddr address");
require(quotaAddr != address(0), "Invalid quotaAddr address");
metric = metricAddr;
data.posLib = posAddr;
createGpkAddr = gpkAddr;
quotaInst = IQuota(quotaAddr);
}
/**
* @notice Initiates registration of a new Storeman group
* @dev Creates a new group with specified parameters
* @param smg Group input parameters
* @param wkAddrs Array of whitelist work addresses
* @param senders Array of sender addresses for whitelist nodes
*/
function storemanGroupRegisterStart(StoremanType.StoremanGroupInput calldata smg,
address[] calldata wkAddrs, address[] calldata senders)
public
onlyAdmin
{
bytes32 groupId = smg.groupId;
bytes32 preGroupId = smg.preGroupId;
require(wkAddrs.length == senders.length, "Invalid white list length");
require(wkAddrs.length >= data.conf.backupCount, "Insufficient white list");
require(wkAddrs.length <= smg.memberCountDesign+data.conf.backupCount, "Too many whitelist node");
// check preGroupId exist.
if(preGroupId != bytes32(0x00)){
StoremanType.StoremanGroup storage preGroup = data.groups[preGroupId];
require(preGroup.status == StoremanType.GroupStatus.ready || preGroup.status == StoremanType.GroupStatus.failed,"invalid preGroup");
}
initGroup(groupId, smg);
emit StoremanGroupRegisterStartEvent(groupId, preGroupId, smg.workTime, smg.totalTime, smg.registerDuration);
emit updateGroupChainEvent(groupId, smg.chain1, smg.chain2, smg.curve1, smg.curve2);
return StoremanLib.inheritNode(data, groupId, preGroupId, wkAddrs, senders);
}
/**
* @notice Initializes a new Storeman group
* @dev Sets up group parameters and status
* @param groupId ID of the group
* @param smg Group input parameters
*/
function initGroup(bytes32 groupId, StoremanType.StoremanGroupInput calldata smg)
private
{
StoremanType.StoremanGroup storage group = data.groups[groupId];
require(group.status == StoremanType.GroupStatus.none, "group has existed already");
group.registerTime = block.timestamp;
group.status = StoremanType.GroupStatus.curveSeted;
group.memberCountDesign = smg.memberCountDesign;
group.workTime = smg.workTime;
group.totalTime = smg.totalTime;
group.registerDuration = smg.registerDuration;
group.threshold = smg.threshold;
group.minStakeIn = smg.minStakeIn;
group.minDelegateIn = smg.minDelegateIn;
group.minPartIn = smg.minPartIn;
group.delegateFee = smg.delegateFee;
group.chain1 = smg.chain1;
group.chain2 = smg.chain2;
group.curve1 = smg.curve1;
group.curve2 = smg.curve2;
}
/**
* @notice Processes incentives for a candidate
* @dev Calculates and distributes incentives
* @param wkAddr Work address of the candidate
*/
function incentiveCandidator(address wkAddr) external {
IncentiveLib.incentiveCandidator(data, wkAddr,metric, getGlobalGroupScAddr());
}
/**
* @notice Allows staking into a Storeman group
* @dev Processes staking operation
* @param groupId ID of the target group
* @param PK Public key of the staker
* @param enodeID Enode ID for P2P network
*/
function stakeIn(bytes32 groupId, bytes calldata PK, bytes calldata enodeID)
external
notHalted
payable
{
return StoremanLib.stakeIn(data, groupId, PK, enodeID);
}
/**
* @notice Allows appending stake to existing position
* @dev Processes additional staking
* @param wkAddr Work address of the staker
*/
function stakeAppend(address wkAddr)
external
notHalted
payable
{
return StoremanLib.stakeAppend(data, wkAddr);
}
/**
* @notice Allows announcing intention to quit next group
* @dev Processes stake withdrawal request
* the next group will open in advance of the current group end. so if a node want to quit, it should call stakeOut before the new group open.
* If the new group has opened, the node in old group can't stake out.
* @param wkAddr Work address of the staker
*/
function stakeOut(address wkAddr) external notHalted {
return StoremanLib.stakeOut(data, wkAddr);
}
/**
* @notice Checks if stake can be withdrawn
* @dev Validates stake withdrawal conditions
* @param wkAddr Work address to check
* @return bool indicating if withdrawal is possible
*/
function checkCanStakeOut(address wkAddr) external view returns(bool) {
return StoremanLib.checkCanStakeOut(data, wkAddr);
}
/**
* @notice Checks if stake can be claimed
* @dev Validates stake claim conditions
* @param wkAddr Work address to check
* @return bool indicating if claim is possible
*/
function checkCanStakeClaim(address wkAddr) external view returns(bool){
return StoremanLib.checkCanStakeClaim(data, wkAddr);
}
/**
* @notice Checks if partner can claim
* @dev Validates partner claim conditions
* @param wkAddr Work address of the node
* @param pnAddr Address of the partner
* @return bool indicating if claim is possible
*/
function checkCanPartnerClaim(address wkAddr, address pnAddr) external view returns(bool) {
return StoremanLib.checkCanPartnerClaim(data, wkAddr, pnAddr, getGlobalGroupScAddr());
}
/**
* @notice Checks if delegator can claim
* @dev Validates delegator claim conditions
* @param wkAddr Work address of the node
* @param deAddr Address of the delegator
* @return bool indicating if claim is possible
*/
function checkCanDelegatorClaim(address wkAddr, address deAddr) external view returns(bool) {
return StoremanLib.checkCanDelegatorClaim(data, wkAddr, deAddr, getGlobalGroupScAddr());
}
/**
* @notice Processes stake claim
* @dev Handles stake and incentive claims
* @param wkAddr Work address of the claimant
*/
function stakeClaim(address wkAddr) external notHalted nonReentrant {
return StoremanLib.stakeClaim(data,wkAddr);
}
/**
* @notice Processes incentive claim
* @dev Handles incentive distribution
* @param wkAddr Work address of the claimant
*/
function stakeIncentiveClaim(address wkAddr) external notHalted nonReentrant{
return StoremanLib.stakeIncentiveClaim(data,wkAddr);
}
/**
* @notice Processes delegation
* @dev Handles delegation operation
* @param wkAddr Work address of the delegator
*/
function delegateIn(address wkAddr)
external
notHalted
payable
{
return StoremanLib.delegateIn(data,wkAddr);
}
/**
* @notice Processes delegation withdrawal
* @dev Handles delegation withdrawal
* @param wkAddr Work address of the delegator
*/
function delegateOut(address wkAddr) external {
return StoremanLib.delegateOut(data,wkAddr, getGlobalGroupScAddr());
}
/**
* @notice Processes delegation claim
* @dev Handles delegation claims
* @param wkAddr Work address of the delegator
*/
function delegateClaim(address wkAddr) external notHalted nonReentrant{
return StoremanLib.delegateClaim(data, wkAddr, getGlobalGroupScAddr());
}
/**
* @notice Processes delegation incentive claim
* @dev Handles delegation incentive distribution
* @param wkAddr Work address of the delegator
*/
function delegateIncentiveClaim(address wkAddr) external notHalted nonReentrant{
return StoremanLib.delegateIncentiveClaim(data, wkAddr);
}
/**
* @notice Processes partnership
* @dev Handles partnership operation
* @param wkAddr Work address of the partner
*/
function partIn(address wkAddr)
external
notHalted
payable
{
return StoremanLib.partIn(data,wkAddr);
}
/**
* @notice Processes partnership withdrawal
* @dev Handles partnership withdrawal
* @param wkAddr Work address of the partner
*/
function partOut(address wkAddr) external notHalted{
return StoremanLib.partOut(data, wkAddr, getGlobalGroupScAddr());
}
/**
* @notice Processes partnership claim
* @dev Handles partnership claims
* @param wkAddr Work address of the partner
*/
function partClaim(address wkAddr) external notHalted nonReentrant{
return StoremanLib.partClaim(data,wkAddr, getGlobalGroupScAddr());
}
/**
* @notice Gets number of selected Storeman nodes
* @dev Returns count of selected nodes
* @param groupId ID of the group
* @return uint Number of selected nodes
*/
function getSelectedSmNumber(bytes32 groupId) external view returns(uint) {
return StoremanUtil.getSelectedSmNumber(data, groupId);
}
/**
* @notice Gets addresses of selected Storeman nodes
* @dev Returns array of selected node addresses
* @param groupId ID of the group
* @return address[] Array of selected node addresses
*/
function getSelectedStoreman(bytes32 groupId) external view returns(address[] memory) {
return StoremanUtil.getSelectedStoreman(data, groupId);
}
/**
* @notice Selects members for a group
* @dev Processes member selection
* @param groupId ID of the group
*/
function select(bytes32 groupId)
external
notHalted
{
return IncentiveLib.toSelect(data, groupId);
}
/**
* @notice Gets information about a selected Storeman node
* @dev Returns node details
* @param groupId ID of the group
* @param index Index of the node
* @return wkAddr Work address
* @return PK Public key
* @return enodeId Enode ID
*/
function getSelectedSmInfo(bytes32 groupId, uint index) external view returns(address wkAddr, bytes memory PK, bytes memory enodeId) {
StoremanType.StoremanGroup storage group = data.groups[groupId];
address addr = group.selectedNode[index];
StoremanType.Candidate storage sk = data.candidates[0][addr];
return (sk.wkAddr, sk.PK, sk.enodeID);
}
/**
* @notice Updates group status
* @dev Changes group status for unexpected reasons
* @param groupId ID of the group
* @param status New status
*/
function updateGroupStatus(bytes32 groupId, StoremanType.GroupStatus status) external onlyAdmin {
StoremanType.StoremanGroup storage group = data.groups[groupId];
group.status = status;
}
/**
* @notice Gets Storeman incentive for a specific day
* @dev Returns incentive amount
* @param wkAddr Work address
* @param day Target day
* @return incentive Amount of incentive
*/
function getStoremanIncentive(address wkAddr, uint day) external view returns(uint incentive) {
StoremanType.Candidate storage sk = data.candidates[0][wkAddr];
return sk.incentive[day];
}
/**
* @notice Gets delegator incentive information
* @dev Returns delegator incentive details
* @param wkAddr Work address
* @param deAddr Delegator address
* @param day Target day
* @return uint Incentive amount
*/
function getSmDelegatorInfoIncentive(address wkAddr, address deAddr, uint day) external view returns ( uint) {
StoremanType.Candidate storage sk = data.candidates[0][wkAddr];
StoremanType.Delegator storage de = sk.delegators[deAddr];
return (de.incentive[day]);
}
/**
* @notice Gets delegator information
* @dev Returns delegator details
* @param wkAddr Work address
* @param deAddr Delegator address
* @return sender Address of sender
* @return deposit Amount of deposit
* @return incentive Amount of incentive
* @return quited Whether delegator has quit
*/
function getSmDelegatorInfo(address wkAddr, address deAddr) external view returns (address sender, uint deposit, uint incentive, bool quited) {
StoremanType.Candidate storage sk = data.candidates[0][wkAddr];
StoremanType.Delegator storage de = sk.delegators[deAddr];
return (deAddr, de.deposit.getLastValue(), de.incentive[0], de.quited);
}
/**
* @notice Gets partner information
* @dev Returns partner details
* @param wkAddr Work address
* @param pnAddr Partner address
* @return sender Address of sender
* @return deposit Amount of deposit
* @return quited Whether partner has quit
*/
function getSmPartnerInfo(address wkAddr, address pnAddr) external view returns (address sender, uint deposit, bool quited) {
StoremanType.Candidate storage sk = data.candidates[0][wkAddr];
StoremanType.Delegator storage pn = sk.partners[pnAddr];
return (pnAddr, pn.deposit.getLastValue(), pn.quited);
}
/**
* @notice Gets partner address by index
* @dev Returns partner address
* @param wkAddr Work address
* @param index Partner index
* @return pkAddr Partner address
*/
function getSmPartnerAddr(address wkAddr, uint index) external view returns(address pkAddr) {
StoremanType.Candidate storage sk = data.candidates[0][wkAddr];
return sk.partMap[index];
}
/**
* @notice Gets delegator address by index
* @dev Returns delegator address
* @param wkAddr Work address
* @param index Delegator index
* @return deAddr Delegator address
*/
function getSmDelegatorAddr(address wkAddr, uint index) external view returns(address deAddr) {
StoremanType.Candidate storage sk = data.candidates[0][wkAddr];
return sk.delegatorMap[index];
}
/**
* @notice Sets GPK for a group
* @dev Configures group GPK
* @param groupId ID of the group
* @param gpk1 First GPK
* @param gpk2 Second GPK
*/
function setGpk(bytes32 groupId, bytes calldata gpk1, bytes calldata gpk2)
external
{
require(msg.sender == createGpkAddr, "Sender is not allowed");
StoremanType.StoremanGroup storage group = data.groups[groupId];
require(group.status == StoremanType.GroupStatus.selected,"invalid status");
group.gpk1 = gpk1;
group.gpk2 = gpk2;
group.status = StoremanType.GroupStatus.ready;
addActiveGroup(groupId, group.workTime, group.workTime+group.totalTime);
emit StoremanGroupSetGpkEvent(groupId);
}
/**
* @notice Adds active group ID
* @dev Registers active group
* @param groupId ID of the group
*/
function addActiveGroupId(bytes32 groupId) external onlyAdmin{
address addr = getGlobalGroupScAddr();
StoremanType.StoremanGroup storage group = data.groups[groupId];
IListGroup(addr).addActiveGroup(groupId, group.workTime, group.workTime+group.totalTime);
}
/**
* @notice Gets contract dependencies
* @dev Returns addresses of dependent contracts
* @return metricAddr Metric contract address
* @return gpkAddr GPK contract address
* @return quotaAddr Quota contract address
* @return posAddr POS contract address
* @return listGroupAddr List group contract address
*/
function getDependence() public view returns(address metricAddr, address gpkAddr,address quotaAddr, address posAddr, address listGroupAddr) {
return (metric, createGpkAddr,address(quotaInst), data.posLib, getGlobalGroupScAddr());
}
/**
* @notice Sets global group SC address
* @dev Updates global group address
* @param _addr New address
*/
function setGlobalGroupScAddr(address _addr) external onlyOwner {
BasicStorageLib.setStorage(addressData, key, innerKey, _addr);
}
/**
* @notice Gets global group SC address
* @dev Returns global group address
* @return address Global group address
*/
function getGlobalGroupScAddr() public view returns(address) {
return BasicStorageLib.getStorage(addressData, key, innerKey);
}
/**
* @notice Adds active group
* @dev Registers active group with time range
* @param groupId ID of the group
* @param startTime Start time
* @param endTime End time
*/
function addActiveGroup(bytes32 groupId, uint startTime, uint endTime) private {
address addr = getGlobalGroupScAddr();
IListGroup(addr).addActiveGroup(groupId, startTime, endTime);
}
/**
* @notice Cleans expired groups
* @dev Removes expired groups from active list
*/
function cleanExpiredGroup() private {
address addr = getGlobalGroupScAddr();
IListGroup(addr).cleanExpiredGroup();
}
/**
* @notice Gets active group IDs
* @dev Returns array of active group IDs
* @param epochId Epoch ID
* @return bytes32[] Array of active group IDs
*/
function getActiveGroupIds(uint epochId) external view returns(bytes32[] memory){
address addr = getGlobalGroupScAddr();
return IListGroup(addr).getActiveGroupIds(epochId);
}
/**
* @notice Sets invalid Storeman nodes
* @dev Marks nodes as invalid based on slash types
* @param groupId ID of the group
* @param indexs Array of node indices
* @param slashTypes Array of slash types
* @return isContinue Whether operation should continue
*/
function setInvalidSm(bytes32 groupId, uint[] calldata indexs, GpkTypes.SlashType[] calldata slashTypes)
external
returns(bool isContinue)
{
require(msg.sender == createGpkAddr, "Sender is not allowed");
StoremanType.StoremanGroup storage group = data.groups[groupId];
if (group.status != StoremanType.GroupStatus.selected) {
return false;
}
for (uint i = 0; i < indexs.length; i++) {
StoremanType.Candidate storage skt = data.candidates[0][group.selectedNode[indexs[i]]];
if (slashTypes[i] == GpkTypes.SlashType.SijInvalid || slashTypes[i] == GpkTypes.SlashType.CheckInvalid || slashTypes[i] == GpkTypes.SlashType.SijTimeout) {
recordSmSlash(group.selectedNode[indexs[i]]);
}
IncentiveLib.cleanSmNode(skt, groupId);
if (group.tickedCount + group.whiteCount >= group.whiteCountAll) {
group.status = StoremanType.GroupStatus.failed;
return false;
}
group.tickedNode[group.tickedCount] = group.selectedNode[indexs[i]];
group.selectedNode[indexs[i]] = group.whiteMap[group.whiteCount + group.tickedCount];
group.tickedCount++;
StoremanType.Candidate storage skn = data.candidates[0][group.selectedNode[indexs[i]]];
if(skn.groupId == 0) {
skn.groupId = groupId;
}else {
skn.nextGroupId = groupId;
}
}
IncentiveLib.setGroupDeposit(data, group);
return true;
}
/**
* @notice Records Storeman slash
* @dev Updates slash count for a node
* @param wk Work address
*/
function recordSmSlash(address wk)
public
{
require((msg.sender == metric) || (msg.sender == createGpkAddr), "Sender is not allowed");
StoremanType.Candidate storage sk = data.candidates[0][wk];
sk.slashedCount++;
}
/**
* @notice Gets threshold by group ID
* @dev Returns group threshold
* @param groupId ID of the group
* @return uint Threshold value
*/
function getThresholdByGrpId(bytes32 groupId) external view returns (uint){
StoremanType.StoremanGroup storage group = data.groups[groupId];
return group.threshold;
}
/**
* @notice Unregisters a Storeman group
* @dev Processes group unregistration
* @param groupId ID of the group
*/
function storemanGroupUnregister(bytes32 groupId)
external
notHalted
onlyGroupLeader(groupId)
{
StoremanLib.storemanGroupUnregister(data, groupId);
return cleanExpiredGroup();
}
/**
* @notice Dismisses a Storeman group
* @dev Processes group dismissal
* @param groupId ID of the group
*/
function storemanGroupDismiss(bytes32 groupId)
external
notHalted
onlyGroupLeader(groupId)
{
StoremanType.StoremanGroup storage group = data.groups[groupId];
bool quitable = quotaInst.isDebtClean(groupId);
require(quitable, "can not dismiss");
group.status = StoremanType.GroupStatus.dismissed;
emit StoremanGroupDismissedEvent(groupId, block.timestamp);
StoremanType.Candidate storage sk;
for(uint i=0; i<group.selectedCount; i++){
sk = data.candidates[0][group.selectedNode[i]];
IncentiveLib.rotateSkGroup(data.posLib, sk, group);
}
}
/**
* @notice Checks if group can be dismissed
* @dev Validates dismissal conditions
* @param groupId ID of the group
* @return bool Whether group can be dismissed
*/
function checkGroupDismissable(bytes32 groupId) external view returns(bool) {
bool dismissable = quotaInst.isDebtClean(groupId);
return dismissable;
}
/**
* @notice Gets Storeman information
* @dev Returns detailed node information
* @param wkAddr Work address
* @return si Storeman information
*/
function getStoremanInfo(address wkAddr) external view returns(StoremanType.StoremanInfo memory si){
StoremanType.Candidate storage sk = data.candidates[0][wkAddr];
si.sender = sk.sender;
si.enodeID = sk.enodeID;
si.PK = sk.PK;
si.wkAddr = sk.wkAddr;
si.isWhite = sk.isWhite;
si.quited = sk.quited;
si.delegatorCount = sk.delegatorCount;
si.delegateDeposit = sk.delegateDeposit;
si.partnerCount = sk.partnerCount;
si.partnerDeposit = sk.partnerDeposit;
si.crossIncoming = sk.crossIncoming;
si.slashedCount = sk.slashedCount;
si.incentivedDelegator = sk.incentivedDelegator;
si.incentivedDay = sk.incentivedDay;
si.groupId = sk.groupId;
si.incentive = sk.incentive[0];
si.nextGroupId = sk.nextGroupId;
si.deposit = sk.deposit.getLastValue();
}
/**
* @notice Gets Storeman group information
* @dev Returns detailed group information
* @param id Group ID
* @return info Group information
*/
function getStoremanGroupInfo(bytes32 id) external view returns(StoremanType.StoremanGroupInfo memory info){
StoremanType.StoremanGroup storage smg = data.groups[id];
info.groupId = id;
info.status = smg.status;
info.deposit = smg.deposit.getLastValue();
info.depositWeight = smg.depositWeight.getLastValue();
info.selectedCount = smg.selectedCount;
info.memberCount = smg.memberCount;
info.whiteCount = smg.whiteCount;
info.whiteCountAll = smg.whiteCountAll;
info.startTime = smg.workTime;
info.endTime = smg.workTime+smg.totalTime;
info.registerTime = smg.registerTime;
info.registerDuration = smg.registerDuration;
info.memberCountDesign = smg.memberCountDesign;
info.threshold = smg.threshold;
info.chain1 = smg.chain1;
info.chain2 = smg.chain2;
info.curve1 = smg.curve1;
info.curve2 = smg.curve2;
info.tickedCount = smg.tickedCount;
info.minStakeIn = smg.minStakeIn;
info.minDelegateIn = smg.minDelegateIn;
info.minPartIn = smg.minPartIn;
info.crossIncoming = smg.crossIncoming;
info.gpk1 = smg.gpk1;
info.gpk2 = smg.gpk2;
info.delegateFee = smg.delegateFee;
}
/**
* @notice Gets Storeman group configuration
* @dev Returns group configuration details
* @param id Group ID
* @return groupId ID of the group
* @return status Group status
* @return deposit Total deposit
* @return chain1 First chain ID
* @return chain2 Second chain ID
* @return curve1 First curve type
* @return curve2 Second curve type
* @return gpk1 First GPK
* @return gpk2 Second GPK
* @return startTime Start time
* @return endTime End time
*/
function getStoremanGroupConfig(bytes32 id)
external
view
returns(bytes32 groupId, StoremanType.GroupStatus status, uint deposit, uint chain1, uint chain2, uint curve1, uint curve2, bytes memory gpk1, bytes memory gpk2, uint startTime, uint endTime)
{
StoremanType.StoremanGroup storage smg = data.groups[id];
return (id, smg.status,smg.deposit.getLastValue(), smg.chain1, smg.chain2,smg.curve1, smg.curve2,
smg.gpk1, smg.gpk2, smg.workTime, smg.workTime+smg.totalTime);
}
/**
* @notice Gets Storeman group status
* @dev Returns group status and time information
* @param id Group ID
* @return status Group status
* @return startTime Start time
* @return endTime End time
*/
function getStoremanGroupStatus(bytes32 id)
public
view
returns(StoremanType.GroupStatus status, uint startTime, uint endTime)
{
StoremanType.StoremanGroup storage smg = data.groups[id];
return (smg.status, smg.workTime, smg.workTime+smg.totalTime);
}
/**
* @notice Gets group deposit
* @dev Returns total deposit amount
* @param id Group ID
* @return uint Deposit amount
*/
function getDeposit(bytes32 id)
external
view
returns (uint)
{
return data.groups[id].deposit.getLastValue();
}
/**
* @notice Checks group incentive
* @dev Returns incentive amount for a day
* @param id Group ID
* @param day Target day
* @return uint Incentive amount
*/
function checkGroupIncentive(bytes32 id, uint day) external view returns ( uint) {
StoremanType.StoremanGroup storage group = data.groups[id];
return group.groupIncentive[day];
}
/**
* @notice Processes contribution
* @dev Handles contribution transaction
*/
function contribute() external payable {
emit storemanGroupContributeEvent(msg.sender, msg.value);
data.contribution = data.contribution.add(msg.value);
return;
}
/**
* @notice Processes SMG transfer
* @dev Handles transfer operation
* @param smgID Group ID
*/
function smgTransfer(bytes32 smgID) external payable{
StoremanType.StoremanGroup storage group = data.groups[smgID];
group.crossIncoming = group.crossIncoming.add(msg.value);
uint i;
StoremanType.Candidate storage sk;
for(i=0; i<group.selectedCount; i++) {
sk = data.candidates[0][group.selectedNode[i]];
sk.crossIncoming = sk.crossIncoming.add(msg.value.div(group.selectedCount));
}
}
/**
* @notice Sets chain type coefficient
* @dev Updates coefficient for chain pair
* @param chain1 First chain ID
* @param chain2 Second chain ID
* @param co Coefficient value
*/
function setChainTypeCo(uint chain1, uint chain2, uint co) external onlyAdmin {
if(chain1 < chain2) {
data.chainTypeCo[chain1][chain2] = co;
} else {
data.chainTypeCo[chain2][chain1] = co;
}
}
/**
* @notice Gets chain type coefficient
* @dev Returns coefficient for chain pair
* @param chain1 First chain ID
* @param chain2 Second chain ID
* @return co Coefficient value
*/
function getChainTypeCo(uint chain1, uint chain2) external view returns (uint co) {
return IncentiveLib.getChainTypeCo(data, chain1, chain2);
}
/**
* @notice Gets Storeman configuration
* @dev Returns configuration parameters
* @return backupCount Number of backup nodes
* @return standaloneWeight Standalone weight
* @return delegationMulti Delegation multiplier
*/
function getStoremanConf() external view returns(uint backupCount, uint standaloneWeight, uint delegationMulti) {
return (data.conf.backupCount, data.conf.standaloneWeight, data.conf.DelegationMulti);
}
/**
* @notice Updates Storeman configuration
* @dev Modifies configuration parameters
* @param backupCount Number of backup nodes
* @param standaloneWeight Standalone weight
* @param DelegationMulti Delegation multiplier
*/
function updateStoremanConf(uint backupCount, uint standaloneWeight, uint DelegationMulti) external onlyAdmin {
data.conf.backupCount = backupCount;
data.conf.standaloneWeight = standaloneWeight;
data.conf.DelegationMulti = DelegationMulti;
}
/**
* @notice Gets global incentive information
* @dev Returns contribution and reward totals
* @return contribution Total contribution
* @return totalReward Total reward
*/
function getGlobalIncentive() external view returns(uint contribution, uint totalReward) {
return (data.contribution, data.totalReward);
}
}// SPDX-License-Identifier: MIT
/*
Copyright 2023 Wanchain Foundation.
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
*/
// _ _ _
// __ ____ _ _ __ ___| |__ __ _(_)_ __ __| | _____ __
// \ \ /\ / / _` | '_ \ / __| '_ \ / _` | | '_ \@/ _` |/ _ \ \ / /
// \ V V / (_| | | | | (__| | | | (_| | | | | | (_| | __/\ V /
// \_/\_/ \__,_|_| |_|\___|_| |_|\__,_|_|_| |_|\__,_|\___| \_/
//
// Code style according to: https://github.com/wanchain/wanchain-token/blob/master/style-guide.rst
pragma solidity 0.8.18;
import "../components/Halt.sol";
import "../components/Admin.sol";
import "./StoremanGroupStorage.sol";
import "../components/Proxy.sol";
import "../components/ReentrancyGuard.sol";
/**
* @title StoremanGroupProxy
* @dev Proxy contract for storeman group administration
* This contract implements the proxy pattern for the storeman group administration system,
* allowing for upgradeable functionality while maintaining storage and address
*
* Key features:
* - Upgradeable implementation through proxy pattern
* - Storage layout preservation
* - Access control through Admin contract
* - Emergency stop capability through Halt contract
*
* @custom:inheritance
* - StoremanGroupAdminStorage: Provides storage layout
* - Halt: Provides emergency stop functionality
* - Admin: Provides administrative access control
* - Proxy: Implements upgradeable proxy pattern
*
* @custom:security
* - Access control through Admin contract
* - Emergency stop capability through Halt contract
* - Upgradeable through Proxy pattern
* - Storage layout preservation
*
* @custom:upgradeability
* - Implementation can be upgraded without changing storage
* - Storage layout is preserved across upgrades
* - Upgrade process is controlled by admin
*/
contract StoremanGroupProxy is StoremanGroupStorage, Halt, Admin, ReentrancyGuard,Proxy {
/**
*
* MANIPULATIONS
*
*/
/**
* @dev Updates the implementation address of the StoremanGroupAdminDelegate contract
* This function allows the admin to upgrade the implementation contract while
* preserving the storage layout and contract address
*
* @param impl The new implementation address
*
* @custom:requirements
* - Caller must be the contract owner
* - Implementation address must not be zero
* - Implementation address must not be the current implementation
*
* @custom:effects
* - Updates the implementation address
* - Emits Upgraded event
*
* @custom:modifiers
* - onlyOwner: Only contract owner can upgrade
*
* @custom:reverts
* - If implementation address is zero
* - If implementation address is already set
*
* @custom:examples
* ```solidity
* // Upgrade to new implementation
* upgradeTo(newImplementation);
* ```
*/
function upgradeTo(address impl) public onlyOwner {
require(impl != address(0), "Cannot upgrade to invalid address");
require(impl != _implementation, "Cannot upgrade to the same implementation");
_implementation = impl;
emit Upgraded(impl);
}
}// SPDX-License-Identifier: MIT
/*
Copyright 2023 Wanchain Foundation.
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
*/
// _ _ _
// __ ____ _ _ __ ___| |__ __ _(_)_ __ __| | _____ __
// \ \ /\ / / _` | '_ \ / __| '_ \ / _` | | '_ \@/ _` |/ _ \ \ / /
// \ V V / (_| | | | | (__| | | | (_| | | | | | (_| | __/\ V /
// \_/\_/ \__,_|_| |_|\___|_| |_|\__,_|_|_| |_|\__,_|\___| \_/
//
// Code style according to: https://github.com/wanchain/wanchain-token/blob/master/style-guide.rst
pragma solidity ^0.8.18;
import "../components/BasicStorage.sol";
import "../interfaces/IMetric.sol";
import "./Deposit.sol";
import "./StoremanType.sol";
import "../interfaces/IQuota.sol";
/**
* @title StoremanGroupStorage
* @dev Contract for managing storage of storeman group data and configurations
* This contract inherits from BasicStorage and provides storage functionality
* for storeman group management
*
* Key features:
* - Storeman data storage
* - Configuration management
* - Metric and quota integration
* - Default parameter initialization
*
* @custom:security
* - Inherits BasicStorage security features
* - Access control through BasicStorage
* - Safe storage operations
*/
contract StoremanGroupStorage is BasicStorage {
// Address of the metric contract for performance tracking
address public metric;
// Instance of the quota contract for managing quotas
IQuota public quotaInst;
// Address for creating group public key
address public createGpkAddr;
// Main storage structure for storeman data
StoremanType.StoremanData data;
/**
* @dev Constructor initializes default configuration values
*
* @custom:effects
* - Sets default backup count to 3
* - Sets default max slashed count to 2
* - Sets default standalone weight to 15000
* - Sets default chain type coefficient to 10000
* - Sets default delegation multiplier to 5
*
* @custom:initialization
* - Initializes all configuration parameters
* - Sets up default values for storeman group management
*/
constructor() {
uint backupCountDefault = 3;
uint maxSlashedCount = 2;
uint standaloneWeightDefault = 15000;
uint chainTypeCoDefault = 10000;
uint DelegationMultiDefault = 5;
data.conf.standaloneWeight = standaloneWeightDefault;
data.conf.backupCount = backupCountDefault;
data.conf.chainTypeCoDefault = chainTypeCoDefault;
data.conf.maxSlashedCount = maxSlashedCount;
data.conf.DelegationMulti = DelegationMultiDefault;
}
}// SPDX-License-Identifier: MIT
/*
Copyright 2023 Wanchain Foundation.
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
*/
// _ _ _
// __ ____ _ _ __ ___| |__ __ _(_)_ __ __| | _____ __
// \ \ /\ / / _` | '_ \ / __| '_ \ / _` | | '_ \@/ _` |/ _ \ \ / /
// \ V V / (_| | | | | (__| | | | (_| | | | | | (_| | __/\ V /
// \_/\_/ \__,_|_| |_|\___|_| |_|\__,_|_|_| |_|\__,_|\___| \_/
//
//
pragma solidity ^0.8.18;
import "./StoremanType.sol";
import "./StoremanUtil.sol";
import "../interfaces/IListGroup.sol";
/**
* @title StoremanLib
* @dev Library for storeman management
* This library provides utility functions for managing individual storemen,
* including creation, updates, and information retrieval
*
* Key features:
* - Storeman creation and validation
* - Information updates
* - Status management
* - Information retrieval
*
* @custom:usage
* - Used by StoremanGroupLib contract
* - Provides reusable storeman management functions
* - Ensures consistent storeman operations
*
* @custom:security
* - Input validation
* - State consistency checks
* - Safe data operations
*/
library StoremanLib {
using Deposit for Deposit.Records;
using SafeMath for uint;
uint constant MaxPartnerCount = 5;
/**
* @notice Event emitted when a node stakes into a group
* @dev Indicates successful staking operation
* @param groupId ID of the group
* @param wkAddr Work address of the node
* @param from Address of the sender
* @param value Amount staked
*/
event stakeInEvent(bytes32 indexed groupId,address indexed wkAddr, address indexed from, uint value);
event stakeAppendEvent(address indexed wkAddr, address indexed from, uint indexed value);
/**
* @notice Event emitted when a node stakes out of a group
* @dev Indicates successful stake withdrawal
* @param wkAddr Work address of the node
* @param from Address of the sender
*/
event stakeOutEvent(address indexed wkAddr, address indexed from);
/**
* @notice Event emitted when a node claims their stake
* @dev Indicates successful stake claim
* @param groupId ID of the group
* @param wkAddr Work address of the node
* @param from Address of the sender
* @param value Amount claimed
*/
event stakeClaimEvent(address indexed wkAddr, address indexed from,bytes32 indexed groupId, uint value);
event stakeIncentiveClaimEvent(address indexed wkAddr,address indexed sender,uint indexed amount);
/**
* @notice Event emitted when a node claims their incentive
* @dev Indicates successful incentive claim
* @param wkAddr Work address of the node
* @param sender Address of the sender
* @param amount Amount claimed
*/
event stakeIncentiveCrossFeeEvent(address indexed wkAddr,address indexed sender,uint indexed amount);
/**
* @notice Event emitted when a node transfers between groups
* @dev Indicates successful group transfer
* @param groupId ID of the group
* @param preGroupId ID of the previous group
* @param wkAddrs Addresses of the nodes
*/
event storemanTransferEvent(bytes32 indexed groupId, bytes32 indexed preGroupId, address[] wkAddrs);
/**
* @notice Event emitted when a group is unregistered
* @dev Indicates successful group unregistration
* @param groupId ID of the group
*/
event StoremanGroupUnregisterEvent(bytes32 indexed groupId);
/**
* @notice Event emitted when a node delegates to another node
* @dev Indicates successful delegation
* @param wkAddr Work address of the delegator
* @param from Address of the sender
* @param value Amount delegated
*/
event delegateInEvent(address indexed wkAddr, address indexed from, uint indexed value);
/**
* @notice Event emitted when a node quits a group
* @dev Indicates successful group exit
* @param wkAddr Work address of the node
* @param from Address of the sender
*/
event delegateOutEvent(address indexed wkAddr, address indexed from);
/**
* @notice Event emitted when a node claims their incentive
* @dev Indicates successful incentive claim
* @param wkAddr Work address of the node
* @param from Address of the sender
* @param amount Amount claimed
*/
event delegateClaimEvent(address indexed wkAddr, address indexed from, uint256 indexed amount);
/**
* @notice Event emitted when a node claims their incentive
* @dev Indicates successful incentive claim
* @param wkAddr Work address of the node
* @param sender Address of the sender
* @param amount Amount claimed
*/
event delegateIncentiveClaimEvent(address indexed wkAddr,address indexed sender,uint indexed amount);
/**
* @notice Event emitted when a node claims their incentive
* @dev Indicates successful incentive claim
* @param wkAddr Work address of the node
* @param from Address of the sender
* @param value Amount claimed
*/
event partInEvent(address indexed wkAddr, address indexed from, uint indexed value);
/**
* @notice Event emitted when a node quits a group
* @dev Indicates successful group exit
* @param wkAddr Work address of the node
* @param from Address of the sender
*/
event partOutEvent(address indexed wkAddr, address indexed from);
/**
* @notice Event emitted when a node claims their incentive
* @dev Indicates successful incentive claim
* @param wkAddr Work address of the node
* @param from Address of the sender
* @param amount Amount claimed
*/
event partClaimEvent(address indexed wkAddr, address indexed from, uint256 indexed amount);
/**
* @notice Unregisters a Storeman group
* @dev Marks a group as unregistered after its work period has expired
* @param data Storage data for Storeman operations
* @param groupId Identifier of the group to unregister
*/
function storemanGroupUnregister(StoremanType.StoremanData storage data,bytes32 groupId)
external
{
StoremanType.StoremanGroup storage group = data.groups[groupId];
require(block.timestamp > group.workTime + group.totalTime, "not expired");
require(group.status == StoremanType.GroupStatus.ready,"Invalid status");
group.status = StoremanType.GroupStatus.unregistered;
emit StoremanGroupUnregisterEvent(groupId);
}
/**
* @notice Deletes a Storeman node if it has no deposits or delegations
* @dev Removes a node from storage when it has no active participation
* @param data Storage data for Storeman operations
* @param wkAddr Work address of the node to delete
*/
function deleteStoremanNode(StoremanType.StoremanData storage data, address wkAddr) private {
StoremanType.Candidate storage sk = data.candidates[0][wkAddr];
if(sk.deposit.getLastValue() == 0 && sk.delegatorCount == 0 && sk.partnerCount == 0) {
delete data.candidates[0][wkAddr];
}
}
/**
* @notice Processes a new stake in a Storeman group
* @dev Handles the initial staking process for a new node
* @param data Storage data for Storeman operations
* @param groupId Identifier of the target group
* @param PK Public key of the staker
* @param enodeID Enode ID for P2P network
*/
function stakeIn(StoremanType.StoremanData storage data, bytes32 groupId, bytes memory PK, bytes memory enodeID) external
{
StoremanType.StoremanGroup storage group = data.groups[groupId];
require(group.status == StoremanType.GroupStatus.curveSeted,"invalid group");
require(block.timestamp <= group.registerTime+group.registerDuration,"Registration closed");
require(msg.value >= group.minStakeIn, "Too small value in stake");
require(StoremanUtil.onCurve(PK), "invalid PK");
require(StoremanUtil.onCurve(enodeID), "invalid enodeID");
address wkAddr = address(uint160(uint256(keccak256(PK))));
StoremanType.Candidate storage sk = data.candidates[0][wkAddr];
if(sk.sender != address(0x00)){
deleteStoremanNode(data, wkAddr);
sk = data.candidates[0][wkAddr];
}
require(sk.sender == address(0x00), "Candidate has existed");
sk.sender = msg.sender;
sk.enodeID = enodeID;
sk.PK = PK;
sk.wkAddr = wkAddr;
sk.groupId = groupId;
sk.deposit.addRecord(Deposit.Record(StoremanUtil.getDaybyTime(data.posLib, block.timestamp), msg.value));
// only not whitelist address need add memberCount;
if(!isWorkingNodeInGroup(group, wkAddr)) {
group.skMap[group.memberCount] = sk.wkAddr;
group.memberCount++;
}
// check if it is white
if(group.whiteWk[wkAddr] != address(0x00)){
if(group.whiteWk[wkAddr] != msg.sender){
revert("invalid sender");
}
sk.isWhite = true;
// check if it is a backup whitelist, if yes, change it's groupId to 0.
for(uint i=group.whiteCount; i<group.whiteCountAll; i++) {
if(group.whiteMap[i] == wkAddr){
sk.groupId = bytes32(0x00);
break;
}
}
} else {
realInsert(data,group, wkAddr, StoremanUtil.calSkWeight(data.conf.standaloneWeight, msg.value));
}
emit stakeInEvent(groupId, wkAddr, msg.sender, msg.value);
}
/**
* @notice Appends additional stake to an existing position
* @dev Handles additional staking for an existing node
* @param data Storage data for Storeman operations
* @param wkAddr Work address of the staker
*/
function stakeAppend(StoremanType.StoremanData storage data, address wkAddr) external {
StoremanType.Candidate storage sk = data.candidates[0][wkAddr];
require(sk.wkAddr == wkAddr, "Candidate doesn't exist");
require(sk.sender == msg.sender, "Only the sender can stakeAppend");
uint amount = sk.deposit.getLastValue();
require(amount != 0, "Claimed");
uint day = StoremanUtil.getDaybyTime(data.posLib, block.timestamp);
Deposit.Record memory r = Deposit.Record(day, msg.value);
Deposit.Record memory rw = Deposit.Record(day, StoremanUtil.calSkWeight(data.conf.standaloneWeight, msg.value));
sk.deposit.addRecord(r);
StoremanType.StoremanGroup storage group = data.groups[sk.groupId];
StoremanType.StoremanGroup storage nextGroup = data.groups[sk.nextGroupId];
updateGroup(data, sk, group, r, rw);
updateGroup(data, sk, nextGroup, r, rw);
emit stakeAppendEvent(wkAddr, msg.sender,msg.value);
}
/**
* @notice Checks if a stake can be withdrawn
* @dev Validates conditions for stake withdrawal
* @param data Storage data for Storeman operations
* @param wkAddr Work address to check
* @return bool Whether the stake can be withdrawn
*/
function checkCanStakeOut(StoremanType.StoremanData storage data, address wkAddr) public view returns(bool){
StoremanType.Candidate storage sk = data.candidates[0][wkAddr];
require(sk.wkAddr == wkAddr, "Candidate doesn't exist");
StoremanType.StoremanGroup storage group = data.groups[sk.groupId];
StoremanType.StoremanGroup storage nextGroup = data.groups[sk.nextGroupId];
// if a group haven't selected, can't quit.
// if the sk joined in the next group, and the next group haven't selected, cannot quit.
//else change the flag quited==true
if(group.status < StoremanType.GroupStatus.selected) {
return false;
}
if(nextGroup.status != StoremanType.GroupStatus.none) {
if(nextGroup.status < StoremanType.GroupStatus.selected){
return false;
}
}
return true;
}
/**
* @notice Processes stake withdrawal request
* @dev Handles the stake withdrawal process
* @param data Storage data for Storeman operations
* @param wkAddr Work address of the staker
*/
function stakeOut(StoremanType.StoremanData storage data, address wkAddr) external {
StoremanType.Candidate storage sk = data.candidates[0][wkAddr];
require(sk.sender == msg.sender, "Only the sender can stakeOut");
require(checkCanStakeOut(data, wkAddr),"selecting");
sk.quited = true;
emit stakeOutEvent(wkAddr, msg.sender);
}
/**
* @notice Checks if a node is a working node in a group
* @dev Verifies if a node is actively participating in a group
* @param group Group data to check against
* @param wkAddr Work address to check
* @return bool Whether the node is a working node
*/
function isWorkingNodeInGroup(StoremanType.StoremanGroup storage group, address wkAddr) public view returns (bool) {
uint count = group.selectedCount;
for(uint8 i = 0; i < count; i++) {
if(wkAddr == group.selectedNode[i]) {
return true;
}
}
return false;
}
/**
* @notice Checks if a stake can be claimed from a specific group
* @dev Validates conditions for claiming stake from a group
* @param posLib Address of the POS library
* @param sk Candidate data
* @param group Group data
* @return bool Whether the stake can be claimed
*/
function checkCanStakeClaimFromGroup(address posLib, StoremanType.Candidate storage sk, StoremanType.StoremanGroup storage group) private view returns (bool) {
// if group haven't selected, can't claim
// if group failed, can claim.
// if group selected and the sk haven't been selected, can claim.
// if group selected and the sk was selected, then, must 1, group is dismissed. 2. incentived.
if(group.status == StoremanType.GroupStatus.none) {
return true; // group does not exist.
}
if(group.status == StoremanType.GroupStatus.failed) {
return true; // group failed
}
if(group.status < StoremanType.GroupStatus.selected) {
return false;
}
if(!isWorkingNodeInGroup(group, sk.wkAddr)){
return true;
} else {
if(group.status == StoremanType.GroupStatus.dismissed
&& sk.incentivedDay+1 >= StoremanUtil.getDaybyTime(posLib, group.workTime+group.totalTime) ) {
return true;
}
}
return false;
}
/**
* @notice Checks if a stake can be claimed
* @dev Validates overall conditions for stake claiming
* @param data Storage data for Storeman operations
* @param wkAddr Work address to check
* @return bool Whether the stake can be claimed
*/
function checkCanStakeClaim(StoremanType.StoremanData storage data, address wkAddr) public view returns(bool) {
StoremanType.Candidate storage sk = data.candidates[0][wkAddr];
if(sk.wkAddr != wkAddr){ // sk doesn't exist.
return false;
}
StoremanType.StoremanGroup storage group = data.groups[sk.groupId];
StoremanType.StoremanGroup storage nextGroup = data.groups[sk.nextGroupId];
if(checkCanStakeClaimFromGroup(data.posLib, sk, group) && checkCanStakeClaimFromGroup(data.posLib, sk, nextGroup)){
return true;
} else {
return false;
}
}
/**
* @notice Processes stake claim
* @dev Handles the stake claiming process including slashing and incentives
* @param data Storage data for Storeman operations
* @param wkAddr Work address of the claimant
*/
function stakeClaim(StoremanType.StoremanData storage data, address wkAddr) external {
require(checkCanStakeClaim(data,wkAddr),"Cannot claim");
StoremanType.Candidate storage sk = data.candidates[0][wkAddr];
uint amount = sk.deposit.getLastValue();
require(amount != 0, "Claimed");
sk.deposit.clean();
// slash the node
if(sk.slashedCount >= data.conf.maxSlashedCount) {
amount = 0;
} else {
amount = amount.mul(data.conf.maxSlashedCount.sub(sk.slashedCount)).div(data.conf.maxSlashedCount);
}
emit stakeClaimEvent(wkAddr, sk.sender, sk.groupId, amount);
// the cross chain fee
emit stakeIncentiveCrossFeeEvent(wkAddr, sk.sender, sk.crossIncoming);
amount = amount.add(sk.crossIncoming);
sk.crossIncoming = 0;
// the incentive
emit stakeIncentiveClaimEvent(wkAddr,sk.sender,sk.incentive[0]);
amount = amount.add(sk.incentive[0]);
sk.incentive[0] = 0;
sk.quited = true;
if(amount != 0){
payable(sk.sender).transfer(amount);
}
}
/**
* @notice Processes incentive claim for staking
* @dev Handles the claiming of staking incentives
* @param data Storage data for Storeman operations
* @param wkAddr Work address of the claimant
*/
function stakeIncentiveClaim(StoremanType.StoremanData storage data, address wkAddr) external {
StoremanType.Candidate storage sk = data.candidates[0][wkAddr];
require(sk.wkAddr == wkAddr, "Candidate doesn't exist");
uint amount = sk.incentive[0];
sk.incentive[0] = 0;
if(amount != 0){
payable(sk.sender).transfer(amount);
}
emit stakeIncentiveClaimEvent(wkAddr,sk.sender,amount);
}
/**
* @notice Inserts a node into the group based on weight
* @dev Handles the insertion of a node into the group's sorted structure
* @param data Storage data for Storeman operations
* @param group Group data
* @param skAddr Address of the node to insert
* @param weight Weight value for sorting
*/
function realInsert(StoremanType.StoremanData storage data, StoremanType.StoremanGroup storage group, address skAddr, uint weight) internal{
uint i = group.whiteCount;
for (; i < group.selectedCount; i++) {
StoremanType.Candidate storage cmpNode = data.candidates[0][group.selectedNode[i]];
if (cmpNode.wkAddr == skAddr) { // keep self position, do not sort
return;
}
uint cmpWeight = StoremanUtil.calSkWeight(data.conf.standaloneWeight, cmpNode.deposit.getLastValue().add(cmpNode.partnerDeposit)).add(cmpNode.delegateDeposit);
if (weight > cmpWeight) {
break;
}
}
if (i < group.memberCountDesign) {
address curAddr = group.selectedNode[i]; // must not be skAddr
uint j = i;
for (; j < group.selectedCount; j++) {
if (j + 1 < group.memberCountDesign) {
address nextAddr = group.selectedNode[j + 1];
group.selectedNode[j + 1] = curAddr;
if (nextAddr != skAddr) {
curAddr = nextAddr;
} else {
break;
}
}
}
// insert or move to place i
group.selectedNode[i] = skAddr;
if ((group.selectedCount < group.memberCountDesign) && (j == group.selectedCount)) {
group.selectedCount++;
}
}
}
/**
* @notice Updates group information based on new deposits
* @dev Handles group updates when new deposits are made
* @param data Storage data for Storeman operations
* @param sk Candidate data
* @param group Group data
* @param r Deposit record
* @param rw Weighted deposit record
*/
function updateGroup(StoremanType.StoremanData storage data,StoremanType.Candidate storage sk, StoremanType.StoremanGroup storage group, Deposit.Record memory r, Deposit.Record memory rw) internal {
//if haven't selected, need not update group.
// if selected, need to update group.
if(group.status == StoremanType.GroupStatus.none){ // not exist group.
return;
}
if(group.status == StoremanType.GroupStatus.curveSeted) {
if(group.whiteWk[sk.wkAddr] == address(0x00)){
realInsert(data, group, sk.wkAddr, StoremanUtil.calSkWeight(data.conf.standaloneWeight, sk.deposit.getLastValue().add(sk.partnerDeposit)).add(sk.delegateDeposit));
}
} else {
if(isWorkingNodeInGroup(group, sk.wkAddr)){
group.deposit.addRecord(r);
group.depositWeight.addRecord(rw);
}
}
}
/**
* @notice Processes a new delegation
* @dev Handles the delegation process for a storeman node
* @param data Storage data for Storeman operations
* @param wkAddr Work address of the node
*/
function delegateIn(StoremanType.StoremanData storage data, address wkAddr)
external
{
StoremanType.Candidate storage sk = data.candidates[0][wkAddr];
require(sk.wkAddr == wkAddr, "Candidate doesn't exist");
StoremanType.StoremanGroup storage group = data.groups[sk.groupId];
StoremanType.StoremanGroup storage nextGroup = data.groups[sk.nextGroupId];
require(msg.value >= group.minDelegateIn, "Too small value");
require(sk.delegateDeposit.add(msg.value) <= (sk.deposit.getLastValue().add(sk.partnerDeposit)).mul(data.conf.DelegationMulti), "Too many delegation");
StoremanType.Delegator storage dk = sk.delegators[msg.sender];
require(dk.quited == false, "Quited");
if(dk.deposit.getLastValue() == 0) {
sk.delegatorMap[sk.delegatorCount] = msg.sender;
dk.index = sk.delegatorCount;
sk.delegatorCount = sk.delegatorCount.add(1);
}
sk.delegateDeposit = sk.delegateDeposit.add(msg.value);
uint day = StoremanUtil.getDaybyTime(data.posLib, block.timestamp);
Deposit.Record memory r = Deposit.Record(day, msg.value);
dk.deposit.addRecord(r);
updateGroup(data, sk, group, r, r);
updateGroup(data, sk, nextGroup, r, r);
emit delegateInEvent(wkAddr, msg.sender,msg.value);
}
/**
* @notice Inherits nodes from a previous group
* @dev Handles the inheritance of nodes from a previous group to a new group
* @param data Storage data for Storeman operations
* @param groupId New group identifier
* @param preGroupId Previous group identifier
* @param wkAddrs Array of work addresses
* @param senders Array of sender addresses
*/
function inheritNode(StoremanType.StoremanData storage data, bytes32 groupId, bytes32 preGroupId, address[] memory wkAddrs, address[] memory senders) public
{
StoremanType.StoremanGroup storage group = data.groups[groupId];
uint len = 0;
if(preGroupId != bytes32(0x00)) {
StoremanType.StoremanGroup storage oldGroup = data.groups[preGroupId];
len = oldGroup.memberCountDesign * 2;
}else {
len = wkAddrs.length;
}
address[] memory oldAddr = new address[](len);
uint oldCount = 0;
uint k = 0;
group.whiteCount = wkAddrs.length.sub(data.conf.backupCount);
group.whiteCountAll = wkAddrs.length;
for(k = 0; k < wkAddrs.length; k++){
group.whiteMap[k] = wkAddrs[k];
group.whiteWk[wkAddrs[k]] = senders[k];
StoremanType.Candidate storage skw = data.candidates[0][wkAddrs[k]];
if(skw.wkAddr != address(0x00)){ // this node has exist
if(preGroupId != bytes32(0x00)) {
require(skw.groupId == bytes32(0x00) || skw.groupId == preGroupId, "Invalid whitelist");
}
require(!skw.quited, "Invalid node");
oldAddr[oldCount] = wkAddrs[k];
oldCount++;
}
if(k < group.whiteCount) {
group.selectedNode[k] = wkAddrs[k];
if(skw.groupId==bytes32(0x00)){
skw.groupId = groupId;
} else {
skw.nextGroupId = groupId;
}
}
}
group.selectedCount = group.whiteCount;
group.memberCount = group.selectedCount;
if (preGroupId != bytes32(0x00)) {
oldCount = inheritStaker(data, groupId, preGroupId, oldAddr, oldCount);
}
address[] memory oldArray = new address[](oldCount);
for (k = 0; k < oldCount; k++) {
oldArray[k] = oldAddr[k];
}
emit storemanTransferEvent(groupId, preGroupId, oldArray);
}
/**
* @notice Inherits stakers from a previous group
* @dev Handles the inheritance of stakers from a previous group
* @param data Storage data for Storeman operations
* @param groupId New group identifier
* @param preGroupId Previous group identifier
* @param oldAddr Array of old addresses
* @param oldCount Count of old addresses
* @return uint Updated count of old addresses
*/
function inheritStaker(StoremanType.StoremanData storage data, bytes32 groupId, bytes32 preGroupId, address[] memory oldAddr, uint oldCount) public returns(uint) {
StoremanType.StoremanGroup storage group = data.groups[groupId];
StoremanType.StoremanGroup storage oldGroup = data.groups[preGroupId];
uint[] memory stakes = new uint[](oldGroup.memberCountDesign * 2);
uint k;
for (k = oldGroup.whiteCount; k < oldGroup.memberCountDesign; k++) {
address wkAddr = oldGroup.selectedNode[k];
StoremanType.Candidate storage sk = data.candidates[0][wkAddr];
if (sk.groupId == preGroupId && !sk.quited && sk.slashedCount == 0 && !sk.isWhite) {
if (oldGroup.status == StoremanType.GroupStatus.failed){
sk.groupId = groupId;
} else {
sk.nextGroupId = groupId;
}
group.memberCount++;
oldAddr[oldCount] = sk.wkAddr;
stakes[oldCount] = StoremanUtil.calSkWeight(data.conf.standaloneWeight, sk.deposit.getLastValue().add(sk.partnerDeposit)).add(sk.delegateDeposit);
oldCount++;
}
}
inheritSortedStaker(group, oldAddr, stakes, oldCount.sub(group.memberCount.sub(group.whiteCount)), oldCount);
return oldCount;
}
/**
* @notice Inherits sorted stakers into a group
* @dev Handles the inheritance of stakers in sorted order
* @param group Group data
* @param addresses Array of addresses
* @param stakes Array of stake amounts
* @param start Starting index
* @param end Ending index
*/
function inheritSortedStaker(StoremanType.StoremanGroup storage group, address[] memory addresses, uint[] memory stakes, uint start, uint end) public {
while ((group.selectedCount < group.memberCount) && (group.selectedCount < group.memberCountDesign)) {
uint maxIndex = start;
for (uint i = (start + 1); i < end; i++) {
if (stakes[i] > stakes[maxIndex]) {
maxIndex = i;
}
}
group.selectedNode[group.selectedCount] = addresses[maxIndex];
group.selectedCount++;
if (maxIndex == start) {
start += 1;
} else {
stakes[maxIndex] = 0;
}
}
}
/**
* @notice Processes delegation withdrawal
* @dev Handles the withdrawal of a delegation
* @param data Storage data for Storeman operations
* @param wkAddr Work address of the node
* @param listGroupAddr Address of the list group contract
*/
function delegateOut(StoremanType.StoremanData storage data, address wkAddr, address listGroupAddr) external {
StoremanType.Candidate storage sk = data.candidates[0][wkAddr];
require(sk.wkAddr == wkAddr, "Candidate doesn't exist");
require(checkCanStakeOut(data, wkAddr),"selecting");
StoremanType.Delegator storage dk = sk.delegators[msg.sender];
require(dk.quited == false,"Quited");
require(dk.deposit.getLastValue() != 0, "no deposit");
dk.quited = true;
uint amount = dk.deposit.getLastValue();
sk.delegateDeposit = sk.delegateDeposit.sub(amount);
IListGroup(listGroupAddr).setDelegateQuitGroupId(wkAddr, msg.sender, sk.groupId, sk.nextGroupId);
emit delegateOutEvent(wkAddr, msg.sender);
}
/**
* @notice Processes delegation claim
* @dev Handles the claiming of delegated funds
* @param data Storage data for Storeman operations
* @param wkAddr Work address of the node
* @param listGroupAddr Address of the list group contract
*/
function delegateClaim(StoremanType.StoremanData storage data, address wkAddr, address listGroupAddr) external {
require(checkCanDelegatorClaim(data, wkAddr, msg.sender, listGroupAddr),"Cannot claim");
StoremanType.Candidate storage sk = data.candidates[0][wkAddr];
StoremanType.Delegator storage dk = sk.delegators[msg.sender];
uint amount = dk.deposit.getLastValue();
require(amount != 0,"not exist");
dk.deposit.clean();
emit delegateClaimEvent(wkAddr, msg.sender, amount);
if(!dk.quited) {
sk.delegateDeposit = sk.delegateDeposit.sub(amount);
}
address lastDkAddr = sk.delegatorMap[sk.delegatorCount.sub(1)];
StoremanType.Delegator storage lastDk = sk.delegators[lastDkAddr];
sk.delegatorMap[dk.index] = lastDkAddr;
lastDk.index = dk.index;
emit delegateIncentiveClaimEvent(wkAddr,msg.sender,dk.incentive[0]);
amount = amount.add(dk.incentive[0]);
dk.incentive[0] = 0;
sk.delegatorCount = sk.delegatorCount.sub(1);
delete sk.delegatorMap[sk.delegatorCount];
delete sk.delegators[msg.sender];
IListGroup(listGroupAddr).setDelegateQuitGroupId(wkAddr, msg.sender, bytes32(0x00), bytes32(0x00));
payable(msg.sender).transfer(amount);
}
/**
* @notice Processes delegation incentive claim
* @dev Handles the claiming of delegation incentives
* @param data Storage data for Storeman operations
* @param wkAddr Work address of the node
*/
function delegateIncentiveClaim(StoremanType.StoremanData storage data, address wkAddr) external {
StoremanType.Candidate storage sk = data.candidates[0][wkAddr];
require(sk.wkAddr == wkAddr, "Candidate doesn't exist");
StoremanType.Delegator storage dk = sk.delegators[msg.sender];
require(dk.deposit.getLastValue() != 0, "not exist");
uint amount = dk.incentive[0];
dk.incentive[0] = 0;
if(amount!=0){
payable(msg.sender).transfer(amount);
}
emit delegateIncentiveClaimEvent(wkAddr,msg.sender,amount);
}
/**
* @notice Processes partnership stake
* @dev Handles the staking process for partners
* @param data Storage data for Storeman operations
* @param wkAddr Work address of the node
*/
function partIn(StoremanType.StoremanData storage data, address wkAddr)
external
{
uint maxPartnerCount = 5;
StoremanType.Candidate storage sk = data.candidates[0][wkAddr];
require(sk.wkAddr == wkAddr, "Candidate doesn't exist");
StoremanType.StoremanGroup storage group = data.groups[sk.groupId];
StoremanType.StoremanGroup storage nextGroup = data.groups[sk.nextGroupId];
//require(msg.value >= group.minPartIn, "Too small value");
StoremanType.Delegator storage pn = sk.partners[msg.sender];
require(pn.quited == false, "Quited");
if(pn.deposit.getLastValue() == 0) {
require(msg.value >= group.minPartIn, "Too small value");
require(sk.partnerCount<maxPartnerCount,"Too many partners");
sk.partMap[sk.partnerCount] = msg.sender;
pn.index = sk.partnerCount;
sk.partnerCount++;
}
sk.partnerDeposit = sk.partnerDeposit.add(msg.value);
uint day = StoremanUtil.getDaybyTime(data.posLib, block.timestamp);
Deposit.Record memory r = Deposit.Record(day, msg.value);
Deposit.Record memory rw = Deposit.Record(day, StoremanUtil.calSkWeight(data.conf.standaloneWeight, msg.value));
pn.deposit.addRecord(r);
updateGroup(data, sk, group, r, rw);
updateGroup(data, sk, nextGroup, r, rw);
emit partInEvent(wkAddr, msg.sender, msg.value);
}
/**
* @notice Processes partnership withdrawal
* @dev Handles the withdrawal of partnership stake
* @param data Storage data for Storeman operations
* @param wkAddr Work address of the node
* @param listGroupAddr Address of the list group contract
*/
function partOut(StoremanType.StoremanData storage data, address wkAddr, address listGroupAddr) external {
require(checkCanStakeOut(data, wkAddr),"selecting");
StoremanType.Candidate storage sk = data.candidates[0][wkAddr];
StoremanType.Delegator storage pn = sk.partners[msg.sender];
require(pn.quited == false,"Quited");
require(pn.deposit.getLastValue() != 0, "Partner doesn't exist");
pn.quited = true;
uint amount = pn.deposit.getLastValue();
sk.partnerDeposit = sk.partnerDeposit.sub(amount);
IListGroup(listGroupAddr).setPartQuitGroupId(wkAddr, msg.sender, sk.groupId, sk.nextGroupId);
emit partOutEvent(wkAddr, msg.sender);
}
/**
* @notice Checks if a group is terminated
* @dev Verifies if a group has reached a terminated state
* @param data Storage data for Storeman operations
* @param groupId Group identifier
* @return bool Whether the group is terminated
*/
function checkGroupTerminated(StoremanType.StoremanData storage data, bytes32 groupId) public view returns(bool){
if(groupId == bytes32(0x00)) {
return true;
}
StoremanType.StoremanGroup storage group = data.groups[groupId];
if(group.status == StoremanType.GroupStatus.none || group.status == StoremanType.GroupStatus.failed || group.status == StoremanType.GroupStatus.dismissed){
return true;
}
return false;
}
function checkCanPartnerClaim(StoremanType.StoremanData storage data, address wkAddr, address pnAddr, address listGroupAddr) public view returns(bool) {
if(checkCanStakeClaim(data,wkAddr)){
return true;
}
StoremanType.Candidate storage sk = data.candidates[0][wkAddr];
StoremanType.Delegator storage pn = sk.partners[pnAddr];
bytes32 quitGroupId;
bytes32 quitNextGroupId;
(quitGroupId,quitNextGroupId) = IListGroup(listGroupAddr).getPartQuitGroupId(wkAddr, pnAddr);
if(pn.quited && checkGroupTerminated(data, quitGroupId) && checkGroupTerminated(data, quitNextGroupId)){
return true;
}
return false;
}
function checkCanDelegatorClaim(StoremanType.StoremanData storage data, address wkAddr, address deAddr, address listGroupAddr) public view returns(bool) {
if(checkCanStakeClaim(data,wkAddr)){
return true;
}
StoremanType.Candidate storage sk = data.candidates[0][wkAddr];
StoremanType.Delegator storage de = sk.delegators[deAddr];
bytes32 quitGroupId;
bytes32 quitNextGroupId;
(quitGroupId,quitNextGroupId) = IListGroup(listGroupAddr).getDelegateQuitGroupId(wkAddr, deAddr);
if(de.quited && checkGroupTerminated(data, quitGroupId) && checkGroupTerminated(data, quitNextGroupId)){
return true;
}
return false;
}
function partClaim(StoremanType.StoremanData storage data, address wkAddr, address listGroupAddr) external {
require(checkCanPartnerClaim(data,wkAddr, msg.sender,listGroupAddr),"Cannot claim");
StoremanType.Candidate storage sk = data.candidates[0][wkAddr];
StoremanType.Delegator storage pn = sk.partners[msg.sender];
uint amount = pn.deposit.getLastValue();
require(amount != 0, "not exist");
pn.deposit.clean();
if(!pn.quited) {
sk.partnerDeposit = sk.partnerDeposit.sub(amount);
}
address lastPnAddr = sk.partMap[sk.partnerCount.sub(1)];
StoremanType.Delegator storage lastPn = sk.partners[lastPnAddr];
sk.partMap[pn.index] = lastPnAddr;
lastPn.index = pn.index;
sk.partnerCount = sk.partnerCount.sub(1);
delete sk.partMap[sk.partnerCount];
delete sk.partners[msg.sender];
IListGroup(listGroupAddr).setPartQuitGroupId(wkAddr, msg.sender, bytes32(0x00), bytes32(0x00));
// slash the node
if(sk.slashedCount >= data.conf.maxSlashedCount) {
amount = 0;
} else {
amount = amount.mul(data.conf.maxSlashedCount.sub(sk.slashedCount)).div(data.conf.maxSlashedCount);
}
emit partClaimEvent(wkAddr, msg.sender, amount);
payable(msg.sender).transfer(amount);
}
}// SPDX-License-Identifier: MIT
pragma solidity ^0.8.18;
import "./Deposit.sol";
/**
* @title StoremanType
* @dev Library containing type definitions and data structures for Storeman Group management
*/
library StoremanType {
using Deposit for Deposit.Records;
/**
* @notice Enumeration of possible group statuses
* @dev Defines the various states a Storeman group can be in
* - none: Initial state
* - initial: Group initialization
* - curveSeted: Curve parameters set
* - failed: Group operation failed
* - selected: Members selected
* - ready: Group ready for operation (GPK finished)
* - unregistered: Group unregistered
* - dismissed: Group dismissed
*/
enum GroupStatus {none, initial,curveSeted, failed,selected,ready,unregistered, dismissed}
/**
* @notice Structure representing a delegator in the system
* @dev Contains information about a delegator's status and deposits
*/
struct Delegator {
bool quited;
uint index; // for delete from candidate;
Deposit.Records deposit;
mapping(uint=>uint) incentive;
}
/**
* @notice Structure representing a candidate node in the system
* @dev Contains comprehensive information about a Storeman node
*/
struct Candidate {
address sender;
bytes enodeID;
bytes PK;
address wkAddr;
bool isWhite;
bool quited;
uint delegatorCount;
uint delegateDeposit; // only used when selecting. need not records.
uint partnerCount;
uint partnerDeposit;
uint crossIncoming;
uint slashedCount;
uint incentivedDelegator; // how may delegator have beend incentived, == delegatorCount means incentive finished.
uint incentivedDay;
bytes32 groupId;
bytes32 nextGroupId;
Deposit.Records deposit; // the sk hiself's deposit.
mapping(uint=>uint) incentive; // without delegation.. set to 0 after claim.
// delegator index => delegator addr
mapping(uint=>address) delegatorMap;
mapping(address=>Delegator) delegators;
// partner index => partner address
mapping(uint=>address) partMap;
mapping(address=>Delegator) partners;
}
/**
* @notice Structure representing a Storeman group
* @dev Contains all information about a group's configuration and state
*/
struct StoremanGroup {
GroupStatus status;
Deposit.Records deposit; //group's deposit, used for calculate group incentive
Deposit.Records depositWeight; // use for incentive distribution in a group
uint selectedCount;
uint memberCount;
uint whiteCount; // only used node, don't include backup.
uint whiteCountAll; // all
uint workTime;
uint totalTime;
uint registerTime;
uint registerDuration; // how long allow to staking. check when stakeIn tx.
uint memberCountDesign;
uint threshold;
uint chain1;
uint chain2;
uint curve1;
uint curve2;
uint tickedCount;
uint minStakeIn;
uint minDelegateIn;
uint minPartIn;
uint crossIncoming;
bytes gpk1;
bytes gpk2;
uint delegateFee; // div(10000)
mapping(uint=>uint) tickedType;
mapping(uint=>address) tickedNode;
mapping(uint=>address) skMap;
mapping(uint=>address) selectedNode;
mapping(uint=>address) whiteMap;
mapping(address=>address) whiteWk; // the white list specified when start group. the from sender of whitelist.
mapping(uint=>uint) groupIncentive; // by day.
}
/**
* @notice Structure for global Storeman configuration
* @dev Contains system-wide configuration parameters
*/
struct StoremanGlobalConf {
uint standaloneWeight; // defult 15000; need mul 10000
uint DelegationMulti; // 10
uint backupCount; // 3
uint chainTypeCoDefault; //10000
uint maxSlashedCount;
}
/**
* @notice Structure for global Storeman data
* @dev Contains system-wide data and mappings
*/
struct StoremanData {
uint contribution;
uint totalReward;
address posLib;
StoremanGlobalConf conf;
mapping(bytes32 => StoremanType.StoremanGroup) groups;
mapping(uint=>mapping(address=>StoremanType.Candidate)) candidates;
mapping(uint=> mapping(uint => uint)) chainTypeCo;
}
/**
* @notice Structure for Storeman node information
* @dev Contains public information about a Storeman node
*/
struct StoremanInfo {
address sender;
bytes enodeID;
bytes PK;
address wkAddr;
bool isWhite;
bool quited;
uint delegatorCount;
uint delegateDeposit;
uint partnerCount;
uint partnerDeposit;
uint crossIncoming;
uint slashedCount;
uint incentivedDelegator;
uint incentivedDay;
bytes32 groupId;
bytes32 nextGroupId;
uint deposit;
uint incentive;
}
/**
* @notice Structure for Storeman group information
* @dev Contains public information about a Storeman group
*/
struct StoremanGroupInfo {
bytes32 groupId;
GroupStatus status;
uint deposit;
uint depositWeight;
uint selectedCount;
uint memberCount;
uint whiteCount; // only used node, don't include backup.
uint whiteCountAll; // all
uint startTime;
uint endTime;
uint registerTime;
uint registerDuration; // how long allow to staking. check when stakeIn tx.
uint memberCountDesign;
uint threshold;
uint chain1;
uint chain2;
uint curve1;
uint curve2;
uint tickedCount;
uint minStakeIn;
uint minDelegateIn;
uint minPartIn;
uint crossIncoming;
bytes gpk1;
bytes gpk2;
uint delegateFee;
}
/**
* @notice Structure for Storeman group input parameters
* @dev Contains parameters required for group creation
*/
struct StoremanGroupInput {
bytes32 groupId;
bytes32 preGroupId;
uint workTime; // cross chain start time
uint totalTime; // cross chain duration.
uint registerDuration;
uint memberCountDesign;
uint threshold;
uint chain1;
uint chain2;
uint curve1;
uint curve2;
uint minStakeIn;
uint minDelegateIn;
uint minPartIn;
uint delegateFee;
}
}// SPDX-License-Identifier: MIT
pragma solidity ^0.8.18;
import "@openzeppelin/contracts/utils/math/SafeMath.sol";
import "./StoremanType.sol";
import "../interfaces/IPosLib.sol";
import "../lib/CommonTool.sol";
/**
* @title StoremanUtil
* @dev Utility library for Storeman Group operations
*/
library StoremanUtil {
using SafeMath for uint;
/**
* @notice Calculates the weight of a Storeman node
* @dev Computes the weight based on standalone weight and deposit amount
* @param standaloneWeight Base weight of the node
* @param deposit Deposit amount
* @return Calculated weight value
*/
function calSkWeight(uint standaloneWeight,uint deposit) public pure returns(uint) {
return deposit*standaloneWeight/10000;
}
/**
* @notice Gets the epoch ID for a given timestamp
* @dev Converts timestamp to epoch ID using POS library
* @param posLib Address of the POS library contract
* @param time Timestamp to convert
* @return ID number
*/
function getDaybyTime(address posLib, uint time) public view returns(uint) {
return IPosLib(posLib).getEpochId(time);
}
/**
* @notice Gets the number of selected Storeman nodes in a group
* @dev Returns the count of selected nodes for a given group
* @param data Storeman data storage
* @param groupId ID of the group
* @return Number of selected nodes
*/
function getSelectedSmNumber(StoremanType.StoremanData storage data, bytes32 groupId) public view returns(uint) {
StoremanType.StoremanGroup storage group = data.groups[groupId];
return group.selectedCount;
}
/**
* @notice Gets the list of selected Storeman nodes in a group
* @dev Returns an array of selected node addresses for a given group
* @param data Storeman data storage
* @param groupId ID of the group
* @return Array of selected node addresses
*/
function getSelectedStoreman(StoremanType.StoremanData storage data, bytes32 groupId) public view returns(address[] memory) {
StoremanType.StoremanGroup storage group = data.groups[groupId];
address[] memory storemans = new address[](group.selectedCount);
for(uint8 i=0; i<group.selectedCount; i++){
storemans[i] = group.selectedNode[i];
}
return storemans;
}
/**
* @notice Checks if a public key is valid on the curve
* @dev Validates if the given public key coordinates lie on the secp256k1 curve
* @param pubkey Public key bytes to validate
* @return bool indicating if the key is valid
*/
function onCurve(bytes calldata pubkey) public pure returns (bool) {
if(pubkey.length != 64) return false;
uint[2] memory P;
P[0] = CommonTool.bytes2uint(pubkey, 0, 32);
P[1] = CommonTool.bytes2uint(pubkey, 32, 32);
uint p = 0xFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFEFFFFFC2F;
if (0 == P[0] || P[0] == p || 0 == P[1] || P[1] == p)
return false;
uint LHS = mulmod(P[1], P[1], p);
uint RHS = addmod(mulmod(mulmod(P[0], P[0], p), P[0], p), 7, p);
return LHS == RHS;
}
}// SPDX-License-Identifier: MIT
pragma solidity ^0.8.18;
contract FakeBnCurve {
address constant PRECOMPILE_CONTRACT_ADDR = address(0x268);
bool public checkSigResult = true;
bool public addResult = true;
bool public mulGResult = true;
bool public calPolyCommitResult = true;
bool public mulPkResult = true;
bool public equalPtRes = true;
bool public addZeroFail = false;
function add(uint256 x1, uint256 y1, uint256 /* x2 */, uint256 /* y2 */)
public
view
returns(uint256 retx, uint256 rety, bool success)
{
bool result = addResult;
if (result == true) {
if ((addZeroFail == true) && (x1 == 0) && (y1 == 0)) {
result = false;
}
}
return (0,0,result);
}
function mulG(uint256 /* scalar */)
public
view
returns(uint256 x, uint256 y, bool success)
{
return (0,0,mulGResult);
}
function calPolyCommit(bytes calldata /* polyCommit */, bytes calldata /* pk */)
public
view
returns(uint256 sx, uint256 sy, bool success)
{
return (0,0,calPolyCommitResult);
}
function mulPk(uint256 /* scalar */, uint256 /* xPk */, uint256 /* yPk */)
public
view
returns (uint256 x, uint256 y, bool success){
return (0,0,mulPkResult);
}
function equalPt (uint256 /* xLeft */, uint256 /* yLeft */,uint256 /* xRight */, uint256 /* yRight */) public view returns(bool){
return equalPtRes;
}
// set
function setCheckSig (bool checkSigRes) public {
checkSigResult = checkSigRes;
}
function setAddResult (bool res) public {
addResult = res;
}
function setMulGResult (bool res) public {
mulGResult = res;
}
function setCalPolyCommitResult (bool res) public {
calPolyCommitResult = res;
}
function setMulPkResult (bool res) public {
mulPkResult = res;
}
function setEqualPtRes (bool res) public {
equalPtRes = res;
}
function setAddZeroFail (bool fail) public {
addZeroFail = fail;
}
function checkSig (bytes32 /* hash */, bytes32 /* r */, bytes32 /* s */, bytes calldata /* pk */) public view returns(bool) {
return checkSigResult;
}
}// SPDX-License-Identifier: MIT
/*
Copyright 2020 Wanchain Foundation.
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
*/
// _ _ _
// __ ____ _ _ __ ___| |__ __ _(_)_ __ __| | _____ __
// \ \ /\ / / _` | '_ \ / __| '_ \ / _` | | '_ \@/ _` |/ _ \ \ / /
// \ V V / (_| | | | | (__| | | | (_| | | | | | (_| | __/\ V /
// \_/\_/ \__,_|_| |_|\___|_| |_|\__,_|_|_| |_|\__,_|\___| \_/
//
//
pragma solidity ^0.8.18;
library FakeCommonTool {
enum CurveType {SK, BN}
address constant PRECOMPILE_CONTRACT_ADDR = address(0x268);
bytes constant encValue = "0123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef";
function bytes2uint(bytes memory source, uint16 offset, uint16 length)
public
pure
returns(uint)
{
uint number = 0;
for (uint i = 0; i < length; i++) {
number = number + uint8(source[i + offset]) * (2 ** (8 * (length - (i + 1))));
}
return number;
}
function bytesToBytes32(bytes memory source) pure public returns (bytes32 result) {
assembly {
result := mload(add(source, 32))
}
}
function cmpBytes(bytes memory b1, bytes memory b2)
public
pure
returns(bool)
{
uint len1 = b1.length;
uint len2 = b2.length; // maybe has padding
if (len2 >= len1) {
for (uint i = 0; i < len2; i++) {
if (i < len1) {
if (b1[i] != b2[i]) {
return false;
}
} else if (b2[i] != 0x0) {
return false;
}
}
return true;
} else {
return false;
}
}
function enc(bytes32 /* rbpri */, bytes32 /* iv */, uint256 mes, bytes memory /* pub */)
public
pure
returns(bytes memory, bool success)
{
return (encValue, mes != 0);
}
}// SPDX-License-Identifier: MIT
pragma solidity ^0.8.18;
import "../crossApproach/CrossDelegateV4.sol";
contract FakeCrossDelegate is CrossDelegateV4 {
function renounceOwner() external {
owner = address(0);
}
function setStoremanFee(bytes32 groupID) payable external {
storageData.mapStoremanFee[groupID] = msg.value;
}
}// SPDX-License-Identifier: MIT
/*
Copyright 2023 Wanchain Foundation.
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
*/
// _ _ _
// __ ____ _ _ __ ___| |__ __ _(_)_ __ __| | _____ __
// \ \ /\ / / _` | '_ \ / __| '_ \ / _` | | '_ \@/ _` |/ _ \ \ / /
// \ V V / (_| | | | | (__| | | | (_| | | | | | (_| | __/\ V /
// \_/\_/ \__,_|_| |_|\___|_| |_|\__,_|_|_| |_|\__,_|\___| \_/
//
// Code style according to: https://github.com/wanchain/wanchain-token/blob/master/style-guide.rst
pragma solidity ^0.8.18;
contract FakeMetric {
uint[4] c;
function getPrdInctMetric(bytes32 /* grpId */, uint /* startEpId */, uint /* endEpId */) external view returns(uint[] memory){
uint[] memory c2 = new uint[](4);
for(uint i=0; i<c.length; i++){
c2[i] = c[i];
}
return c2;
}
function setC0(uint _c) public {
c[0] = _c;
}
function setC1(uint _c) public {
c[1] = _c;
c[2] = _c;
c[3] = _c;
}
}// SPDX-License-Identifier: MIT
pragma solidity ^0.8.18;
import "@openzeppelin/contracts/utils/math/SafeMath.sol";
library FakePosLib {
using SafeMath for uint;
uint public constant DIVISOR = 10000;
address constant PRECOMPILE_CONTRACT_ADDR = address(0x268);
function getEpochId(uint256 blockTime) public pure returns (uint256) {
return blockTime/1;
}
function getMinIncentive (uint256 smgDeposit,uint256 /* targetSecond */, uint256 totalDeposit) public pure returns(uint256) {
uint posCap = 60000000000;
if(totalDeposit < posCap) return 30000000;
uint cap = posCap.mul(smgDeposit).div(totalDeposit);
return cap > 30000000 ? 30000000 : cap;
}
}// SPDX-License-Identifier: MIT
/*
Copyright 2023 Wanchain Foundation.
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
*/
// _ _ _
// __ ____ _ _ __ ___| |__ __ _(_)_ __ __| | _____ __
// \ \ /\ / / _` | '_ \ / __| '_ \ / _` | | '_ \@/ _` |/ _ \ \ / /
// \ V V / (_| | | | | (__| | | | (_| | | | | | (_| | __/\ V /
// \_/\_/ \__,_|_| |_|\___|_| |_|\__,_|_|_| |_|\__,_|\___| \_/
//
// Code style according to: https://github.com/wanchain/wanchain-token/blob/master/style-guide.rst
pragma solidity ^0.8.18;
contract fakeQuota {
bool flag=true;
function isDebtClean(bytes32 /* storemanGroupId */) external view returns (bool) {
return flag;
}
function setDebtClean(bool f) public {
flag = f;
}
}// SPDX-License-Identifier: MIT
pragma solidity ^0.8.18;
import "../components/ReentrancyGuard.sol";
contract FakeReentrancy is ReentrancyGuard {
uint256 public counter;
constructor() {
counter = 0;
}
function countLocalRecursive(uint256 times) public nonReentrant {
if (times > 0) {
increase();
countLocalRecursive(times - 1);
}
}
function countThisRecursive(uint256 times) external nonReentrant {
if (times > 0) {
increase();
(bool success, ) = address(this).call(abi.encodeWithSignature("countThisRecursive(uint256)", times - 1));
require(success, "FakeReentrancy: failed call");
}
}
function increase() private {
counter += 1;
}
}// SPDX-License-Identifier: MIT
pragma solidity ^0.8.18;
contract FakeSkCurve {
address constant PRECOMPILE_CONTRACT_ADDR = address(0x268);
bool public checkSigResult = true;
bool public addResult = true;
bool public mulGResult = true;
bool public calPolyCommitResult = true;
bool public mulPkResult = true;
bool public equalPtRes = true;
bool public addZeroFail = false;
function add(uint256 x1, uint256 y1, uint256 /* x2 */, uint256 /* y2 */)
public
view
returns(uint256 retx, uint256 rety, bool success)
{
bool result = addResult;
if (result == true) {
if ((addZeroFail == true) && (x1 == 0) && (y1 == 0)) {
result = false;
}
}
return (0,0,result);
}
function mulG(uint256 /* scalar */)
public
view
returns(uint256 x, uint256 y, bool success)
{
return (0,0,mulGResult);
}
function calPolyCommit(bytes memory /* polyCommit */, bytes memory /* pk */)
public
view
returns(uint256 sx, uint256 sy, bool success)
{
return (0,0,calPolyCommitResult);
}
function mulPk(uint256 /* scalar */, uint256 /* xPk */, uint256 /* yPk */)
public
view
returns (uint256 x, uint256 y, bool success){
return (0,0,mulPkResult);
}
function equalPt (uint256 /* xLeft */, uint256 /* yLeft */,uint256 /* xRight */, uint256 /* yRight */) public view returns(bool){
return equalPtRes;
}
// set
function setCheckSig (bool checkSigRes) public {
checkSigResult = checkSigRes;
}
function setAddResult (bool res) public {
addResult = res;
}
function setMulGResult (bool res) public {
mulGResult = res;
}
function setCalPolyCommitResult (bool res) public {
calPolyCommitResult = res;
}
function setMulPkResult (bool res) public {
mulPkResult = res;
}
function setEqualPtRes (bool res) public {
equalPtRes = res;
}
function setAddZeroFail (bool fail) public {
addZeroFail = fail;
}
function checkSig (bytes32 /* hash */, bytes32 /* r */, bytes32 /* s */, bytes memory /* pk */) public view returns(bool) {
return checkSigResult;
}
}// SPDX-License-Identifier: MIT
pragma solidity ^0.8.18;
import '../storemanGroupAdmin/StoremanType.sol';
contract FakeSmg {
/*
*
* VARIABLES
*
*/
uint constant SelectedSMNumber = 4;
uint constant ThresholdNumber = 3;
string constant GroupIdStr = "0000000000000000000000000000000000000031353839393533323738313235";
string constant GroupIdStr1 = "0000000000000000000000000000000000000031353839393533323738313236";
string constant EnodeIdStr = "0000000000000000000000000000000000000000000000000000000000000001";
string constant bytestr = "bytes";
string constant gpk1Str = "82e5d4ad633e9e028b283e52338e4fe4c5467091fd4f5d9aec74cb78c25738be1154a9b1cff44b7fe935e774da7a9fad873b76323573138bc361a9cfdb6a20d2";
string constant gpk2Str = "1761e90a6287d8e771373074626befaf4a46e6e3a2d45f8b7a2ec5361f1de7a102d43cd0d14e5a438d754c01d0d94cf2a8ff8fd9df49c9f7291975c831bcb983";
string constant gpkShare1Str = "f716e789cd79d106343b8e9c3ac494865d02241337cf6ce8df4df6548ec1eccc900963c639664b1667df09d322a8e5c8a9185a09742f96b204b4fcc59dae7fab";
string constant gpkShare2Str = "17a4f5c4add16108a4ab16fc1635635af0df9798176459ca3cd58a15ceb64d4808651a691e5f89ed012ee076bc39bff193064b852ce11741f0110a81c6d876d7";
bytes32 grpId;
// string[4] Pks = ["0425fa6a4190ddc87d9f9dd986726cafb901e15c21aafd2ed729efed1200c73de89f1657726631d29733f4565a97dc00200b772b4bc2f123a01e582e7e56b80cf8",
// "04be3b7fd88613dc272a36f4de570297f5f33b87c26de3060ad04e2ea697e13125a2454acd296e1879a7ddd0084d9e4e724fca9ef610b21420978476e2632a1782",
// "0495e8fd461c37f1db5da62bfbee2ad305d77e57fbef917ec8109e6425e942fb60ddc28b1edfdbcda1aa5ace3160b458b9d3d5b1fe306b4d09a030302a08e2db93",
// "04ccd16e96a70a5b496ff1cec869902b6a8ffa00715897937518f1c9299726f7090bc36cc23c1d028087eb0988c779663e996391f290631317fc22f84fa9bf2467"];
string[4] Pks = ["25fa6a4190ddc87d9f9dd986726cafb901e15c21aafd2ed729efed1200c73de89f1657726631d29733f4565a97dc00200b772b4bc2f123a01e582e7e56b80cf8",
"be3b7fd88613dc272a36f4de570297f5f33b87c26de3060ad04e2ea697e13125a2454acd296e1879a7ddd0084d9e4e724fca9ef610b21420978476e2632a1782",
"95e8fd461c37f1db5da62bfbee2ad305d77e57fbef917ec8109e6425e942fb60ddc28b1edfdbcda1aa5ace3160b458b9d3d5b1fe306b4d09a030302a08e2db93",
"ccd16e96a70a5b496ff1cec869902b6a8ffa00715897937518f1c9299726f7090bc36cc23c1d028087eb0988c779663e996391f290631317fc22f84fa9bf2467"];
address constant ADD_0 = address(0x0000000000000000000000000000000000000000);
address constant ADD_LEADER= address(0x2d0E7c0813A51d3bd1d08246Af2A8a7A57d8922E);
address public leaderAdd;
// groupId=>index=>pk
mapping(bytes32 => mapping(uint8 => bytes)) mapSmgInfo;
constructor() {
grpId = bytesToBytes32(fromHex(GroupIdStr), 0);
for (uint i = 0; i < Pks.length; i++) {
mapSmgInfo[grpId][uint8(i)] = fromHex(Pks[i]);
}
grpId = bytesToBytes32(fromHex(GroupIdStr1), 0);
for (uint j = 0; j < Pks.length; j++) {
mapSmgInfo[grpId][uint8(j)] = fromHex(Pks[j]);
}
}
/*
*
* FUNCTIONS
*
*/
function getSelectedSmNumber(bytes32 /* groupId */) external pure returns (uint number){
return SelectedSMNumber;
}
function getThresholdByGrpId(bytes32 /* groupId */) external pure returns (uint){
return ThresholdNumber;
}
function getSelectedSmInfo(bytes32 groupId, uint index) external view returns (address txAddress, bytes memory pk, bytes memory enodeId){
(txAddress,pk,enodeId) = (leaderAdd, mapSmgInfo[groupId][uint8(index)], fromHex(EnodeIdStr));
}
function getStoremanInfo(address /* wkAddress */) external pure returns(
bytes32 groupId,
bytes32 nextGroupId)
{
return (bytesToBytes32(fromHex(GroupIdStr),0),bytesToBytes32(fromHex(GroupIdStr1),0));
}
function getStoremanGroupInfo(bytes32 /* id */)
external
pure
returns(bytes32 groupId, StoremanType.GroupStatus status, uint deposit, uint whiteCount, uint memberCount, uint startTime, uint endTime){
return (bytesToBytes32(fromHex(GroupIdStr),0),StoremanType.GroupStatus.ready,uint(0),uint(0),uint(0),uint(0),uint(0));
}
function getStoremanGroupConfig(bytes32 /* id */) external pure returns(bytes32 groupId, uint8 status, uint deposit, uint chain1, uint chain2,
uint curve1, uint curve2, bytes memory gpk1, bytes memory gpk2, uint startTime, uint endTime){
return (bytesToBytes32(fromHex(GroupIdStr),0),0,0,0,0,0x00,0x01,fromHex(gpk1Str),fromHex(gpk2Str),0,0);
}
function getGpkShare(bytes32 /* groupId */, uint /* index */) external pure returns(bytes memory gpkShare1, bytes memory gpkShare2){
return (fromHex(gpkShare1Str),fromHex(gpkShare2Str));
}
function setLeader(address leader) external{
leaderAdd = leader;
}
function bytesToBytes32(bytes memory b, uint offset) internal pure returns (bytes32) {
bytes32 out;
for (uint i = 0; i < 32; i++) {
out |= bytes32(b[offset + i] & 0xFF) >> (i * 8);
}
return out;
}
// Convert an hexadecimal character to their value
function fromHexChar(uint c) public pure returns (uint) {
if (uint8(c) >= uint8(bytes1('0')) && uint8(c) <= uint8(bytes1('9'))) {
return c - uint(uint8(bytes1('0')));
}
if (uint8(c) >= uint8(bytes1('a')) && uint8(c) <= uint8(bytes1('f'))) {
return 10 + c - uint(uint8(bytes1('a')));
}
if (uint8(c) >= uint8(bytes1('A')) && uint8(c) <= uint8(bytes1('F'))) {
return 10 + c - uint(uint8(bytes1('A')));
}
return uint(0);
}
// Convert an hexadecimal string to raw bytes
function fromHex(string memory s) public pure returns (bytes memory) {
bytes memory ss = bytes(s);
require(ss.length % 2 == 0);
// length must be even
bytes memory r = new bytes(ss.length / 2);
for (uint i = 0; i < ss.length / 2; ++i) {
r[i] = bytes1(uint8(fromHexChar(uint(uint8(ss[2 * i]))) * 16 +
fromHexChar(uint(uint8(ss[2 * i + 1])))));
}
return r;
}
function recordSmSlash(address wk) external{}
}// SPDX-License-Identifier: MIT
pragma solidity ^0.8.18;
import "@openzeppelin/contracts/access/Ownable.sol";
import "@openzeppelin/contracts/token/ERC20/extensions/ERC20Burnable.sol";
contract FakeToken is ERC20Burnable, Ownable {
address public pendingOwner;
bool public invalidMode;
constructor(
string memory name_,
string memory symbol_
) ERC20(name_, symbol_) {}
function changeOwner(address _newOwner) external {
pendingOwner = _newOwner;
}
function acceptOwnership() external {
Ownable.transferOwnership(pendingOwner);
}
function burn(uint256 amount) public override {
_burn(_msgSender(), amount);
}
function setTestInvalidMode(bool invalidMode_) external virtual {
invalidMode = invalidMode_;
}
function balanceOf(address account) public view override returns (uint256) {
if (invalidMode) {
return 210000000 ether;
} else {
return super.balanceOf(account);
}
}
function transferFrom(address from, address to, uint256 amount) public override returns (bool) {
if (invalidMode) {
return true;
} else {
return super.transferFrom(from, to, amount);
}
}
function transfer(address to, uint256 amount) public override returns (bool) {
if (invalidMode) {
return true;
} else {
return super.transfer(to, amount);
}
}
function burn(address account_, uint256 value_)
external
virtual
{
if (!invalidMode) {
_burn(account_, value_);
}
}
function mint(address account_, uint256 value_)
external
virtual
{
if (!invalidMode) {
_mint(account_, value_);
}
}
}// SPDX-License-Identifier: MIT
pragma solidity ^0.8.18;
import '../tokenManager/WrappedToken.sol';
contract FakeWrappedToken is WrappedToken {
address public pendingOwner;
constructor(
string memory name_,
string memory symbol_,
uint8 decimal_
) WrappedToken(name_, symbol_, decimal_) {}
function changeOwner(address _newOwner) external {
pendingOwner = _newOwner;
}
function acceptOwnership() external {
Ownable.transferOwnership(pendingOwner);
}
function burn(uint256 amount) public override {
_burn(_msgSender(), amount);
}
}// SPDX-License-Identifier: MIT
pragma solidity ^0.8.18;
import "@openzeppelin/contracts/access/Ownable.sol";
import "@openzeppelin/contracts/token/ERC721/extensions/ERC721Enumerable.sol";
import "@openzeppelin/contracts/utils/math/SafeMath.sol";
/**
* @notice This is the template for all NFT contract.
*/
contract MappingNftToken is ERC721Enumerable, Ownable {
using SafeMath for uint;
string private _name;
string private _symbol;
/**
* @dev Sets the values for {name} and {symbol}.
*
* The defaut value of {decimals} is 18. To select a different value for
* {decimals} you should overload it.
*
* All three of these values are immutable: they can only be set once during
* construction.
*/
constructor(
string memory name_,
string memory symbol_
) ERC721(name_, symbol_) {
_name = name_;
_symbol = symbol_;
}
/****************************************************************************
**
** MANIPULATIONS of mapping token
**
****************************************************************************/
/// @notice Create token
/// @dev Create token
/// @param account_ Address will receive token
/// @param nftID ID of token to be minted
function mint(address account_, uint256 nftID)
external
onlyOwner
{
_mint(account_, nftID);
}
/// @notice Burn token
/// @dev Burn token
/// @param account_ Address of whose token will be burnt
/// @param nftID ID of token to be burnt
function burn(address account_, uint256 nftID)
external
onlyOwner
{
address tokenOwner = ERC721.ownerOf(nftID);
require(account_ == tokenOwner, "invalid nft token owner");
_burn(nftID);
}
/// @notice update token name, symbol
/// @dev update token name, symbol
/// @param name_ token new name
/// @param symbol_ token new symbol
function update(string memory name_, string memory symbol_)
external
onlyOwner
{
_name = name_;
_symbol = symbol_;
}
function transferOwner(address newOwner_) public onlyOwner {
Ownable.transferOwnership(newOwner_);
}
// mint supprt data
function mint(address account_, uint256 nftID, bytes memory /* data */)
external
onlyOwner
{
_mint(account_, nftID);
}
function burnBatch(address /* account_*/, uint256[] memory nftIDs)
external
onlyOwner
{
for(uint i = 0; i < nftIDs.length; ++i) {
_burn(nftIDs[i]);
}
}
function mintBatch(address account_, uint256[] memory nftIDs, bytes memory /* data*/)
external
onlyOwner
{
for(uint i = 0; i < nftIDs.length; ++i) {
_mint(account_, nftIDs[i]);
}
}
}// SPDX-License-Identifier: MIT
/*
Copyright 2023 Wanchain Foundation.
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
*/
// _ _ _
// __ ____ _ _ __ ___| |__ __ _(_)_ __ __| | _____ __
// \ \ /\ / / _` | '_ \ / __| '_ \ / _` | | '_ \@/ _` |/ _ \ \ / /
// \ V V / (_| | | | | (__| | | | (_| | | | | | (_| | __/\ V /
// \_/\_/ \__,_|_| |_|\___|_| |_|\__,_|_|_| |_|\__,_|\___| \_/
//
//
pragma solidity ^0.8.18;
import './StandardToken.sol';
import '../components/Owned.sol';
contract MappingToken is StandardToken, Owned {
using SafeMath for uint;
/****************************************************************************
**
** MODIFIERS
**
****************************************************************************/
modifier onlyMeaningfulValue(uint value) {
require(value > 0, "Value is null");
_;
}
/****************************************************************************
**
** EVENTS
**
****************************************************************************/
///@notice Initialize the TokenManager address
///@dev Initialize the TokenManager address
///@param tokenName The token name to be used
///@param tokenSymbol The token symbol to be used
///@param tokenDecimal The token decimals to be used
constructor(string memory tokenName, string memory tokenSymbol, uint8 tokenDecimal)
{
name = tokenName;
symbol = tokenSymbol;
decimals = tokenDecimal;
}
/****************************************************************************
**
** MANIPULATIONS
**
****************************************************************************/
/// @notice Create token
/// @dev Create token
/// @param account Address will receive token
/// @param value Amount of token to be minted
function mint(address account, uint value)
external
onlyOwner
onlyMeaningfulValue(value)
{
balances[account] = balances[account].add(value);
totalSupply = totalSupply.add(value);
emit Transfer(address(0), account, value);
}
/// @notice Burn token
/// @dev Burn token
/// @param account Address of whose token will be burnt
/// @param value Amount of token to be burnt
function burn(address account, uint value)
external
onlyOwner
onlyMeaningfulValue(value)
{
balances[account] = balances[account].sub(value);
totalSupply = totalSupply.sub(value);
emit Transfer(account, address(0), value);
}
/// @notice update token name, symbol
/// @dev update token name, symbol
/// @param _name token new name
/// @param _symbol token new symbol
function update(string calldata _name, string calldata _symbol)
external
onlyOwner
{
name = _name;
symbol = _symbol;
}
}// SPDX-License-Identifier: MIT
pragma solidity ^0.8.18;
contract MockCross {
bool public halted;
address public oracle;
address public sigVerifier;
uint public chainId;
address public owner;
constructor(address _oracle, address _sigVerifier, uint256 _chainId) {
oracle = _oracle;
sigVerifier = _sigVerifier;
chainId = _chainId;
owner = msg.sender;
}
function setHalt(bool _halt) external {
halted = _halt;
}
function getPartners() external view returns(address tokenManager, address smgAdminProxy, address smgFeeProxy, address quota, address _sigVerifier) {
return (address(0), oracle, address(0), address(0), sigVerifier);
}
function currentChainID() external view returns (uint256) {
return chainId;
}
function transferOwnership(address newOwner) external {
require(msg.sender == owner, "only owner can transfer ownership");
owner = newOwner;
}
}// SPDX-License-Identifier: MIT
pragma solidity 0.8.18;
contract MockCrossV4 {
address public tokenManagerAddr;
constructor(address _tokenManager) {
tokenManagerAddr = _tokenManager;
}
function currentChainID() external pure returns (uint) {
return 1;
}
function getPartners() external view returns(
address tokenManager,
address smgAdminProxy,
address smgFeeProxy,
address quota,
address sigVerifier
) {
return (
tokenManagerAddr,
address(0),
address(0),
address(0),
address(0)
);
}
function userLock(
bytes32 /*smgID*/,
uint /*tokenPairID*/,
uint /*value*/,
bytes calldata /*userAccount*/
) external payable {
}
function userBurn(
bytes32 /*smgID*/,
uint /*tokenPairID*/,
uint /*value*/,
uint /*fee*/,
address /*tokenAccount*/,
bytes calldata /*userAccount*/
) external payable {
}
function userLockNFT(
bytes32 /*smgID*/,
uint /*tokenPairID*/,
uint[] memory /*tokenIDs*/,
uint[] memory /*tokenValues*/,
bytes memory /*userAccount*/
) external payable {
}
function userBurnNFT(
bytes32 /*smgID*/,
uint /*tokenPairID*/,
uint[] memory /*tokenIDs*/,
uint[] memory /*tokenValues*/,
address /*tokenAccount*/,
bytes memory /*userAccount*/
) external payable {
}
}// SPDX-License-Identifier: MIT
pragma solidity 0.8.18;
contract MockTokenManager {
address public mockERC20;
address public mockERC721;
address public mockERC1155;
constructor(address _mockERC20, address _mockERC721, address _mockERC1155) {
mockERC20 = _mockERC20;
mockERC721 = _mockERC721;
mockERC1155 = _mockERC1155;
}
function getTokenPairInfo(uint id) external view returns (uint fromChainID, bytes memory fromAccount, uint toChainID, bytes memory toAccount) {
address tokenAddr;
if (id == 1) tokenAddr = mockERC20;
else if (id == 2) tokenAddr = mockERC721;
else if (id == 3) tokenAddr = mockERC1155;
else revert("Invalid token pair ID");
fromChainID = 1;
fromAccount = abi.encodePacked(tokenAddr);
toChainID = 2;
toAccount = abi.encodePacked(tokenAddr);
}
function mapTokenPairType(uint id) external pure returns (uint8) {
if (id == 1) return 0; // ERC20
if (id == 2) return 1; // ERC721
if (id == 3) return 2; // ERC1155
return 0;
}
}// SPDX-License-Identifier: MIT
pragma solidity 0.8.18;
import "@openzeppelin/contracts/token/ERC20/ERC20.sol";
import "@openzeppelin/contracts/token/ERC721/ERC721.sol";
import "@openzeppelin/contracts/token/ERC1155/ERC1155.sol";
contract MockERC20 is ERC20 {
constructor() ERC20("MockToken", "MTK") {}
function mint(address to, uint256 amount) public {
_mint(to, amount);
}
}
contract MockERC721 is ERC721 {
constructor() ERC721("MockNFT", "MNFT") {}
function mint(address to, uint256 tokenId) public {
_mint(to, tokenId);
}
}
contract MockERC1155 is ERC1155 {
constructor() ERC1155("") {}
function mint(address to, uint256 id, uint256 amount) public {
_mint(to, id, amount, "");
}
}// SPDX-License-Identifier: MIT
pragma solidity >=0.8.0;
contract MokeSigVerifier {
bool public isVerified = false;
function setResult(bool isVerified_) external {
isVerified = isVerified_;
}
// Function to verify signature
function verify(bytes32, bytes32, bytes32, bytes32, bytes32, bytes32)
public
view
returns(bool)
{
return isVerified;
}
}// SPDX-License-Identifier: MIT
/*
Copyright 2023 Wanchain Foundation.
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
*/
// _ _ _
// __ ____ _ _ __ ___| |__ __ _(_)_ __ __| | _____ __
// \ \ /\ / / _` | '_ \ / __| '_ \ / _` | | '_ \@/ _` |/ _ \ \ / /
// \ V V / (_| | | | | (__| | | | (_| | | | | | (_| | __/\ V /
// \_/\_/ \__,_|_| |_|\___|_| |_|\__,_|_|_| |_|\__,_|\___| \_/
//
//
pragma solidity ^0.8.18;
import "./WRC20Protocol.sol";
import "@openzeppelin/contracts/utils/math/SafeMath.sol";
contract StandardToken is WRC20Protocol {
using SafeMath for uint;
/**
* @dev Fix for the ERC20 short address attack.
*/
modifier onlyPayloadSize(uint size) {
require(msg.data.length >= size + 4, "Payload size is incorrect");
_;
}
function transfer(address _to, uint _value) public override onlyPayloadSize(2 * 32) returns (bool success) {
balances[msg.sender] = balances[msg.sender].sub(_value);
balances[_to] = balances[_to].add(_value);
emit Transfer(msg.sender, _to, _value);
return true;
}
function transferFrom(address _from, address _to, uint _value) public override onlyPayloadSize(3 * 32) returns (bool success) {
balances[_to] = balances[_to].add(_value);
balances[_from] = balances[_from].sub(_value);
allowed[_from][msg.sender] = allowed[_from][msg.sender].sub(_value);
emit Transfer(_from, _to, _value);
return true;
}
function balanceOf(address _owner) public view override returns (uint balance) {
return balances[_owner];
}
function approve(address _spender, uint _value) public override onlyPayloadSize(2 * 32) returns (bool success) {
// To change the approve amount you first have to reduce the addresses`
// allowance to zero by calling `approve(_spender, 0)` if it is not
// already 0 to mitigate the race condition described here:
// https://github.com/ethereum/EIPs/issues/20#issuecomment-263524729
require((_value == 0) || (allowed[msg.sender][_spender] == 0), "Not permitted");
allowed[msg.sender][_spender] = _value;
emit Approval(msg.sender, _spender, _value);
return true;
}
function allowance(address _owner, address _spender) public view override returns (uint remaining) {
return allowed[_owner][_spender];
}
}// SPDX-License-Identifier: MIT
pragma solidity ^0.8.18;
import "../components/BasicStorage.sol";
contract TestBasicStorage is BasicStorage {
/* uintData */
function setUintData(bytes memory key, bytes memory innerKey, uint value)
external
{
return BasicStorageLib.setStorage(uintData, key, innerKey, value);
}
function getUintData(bytes memory key, bytes memory innerKey)
external
view
returns (uint)
{
return BasicStorageLib.getStorage(uintData, key, innerKey);
}
function delUintData(bytes memory key, bytes memory innerKey)
external
{
return BasicStorageLib.delStorage(uintData, key, innerKey);
}
/* boolData */
function setBoolData(bytes memory key, bytes memory innerKey, bool value)
external
{
BasicStorageLib.setStorage(boolData, key, innerKey, value);
}
function getBoolData(bytes memory key, bytes memory innerKey)
external
view
returns (bool)
{
return BasicStorageLib.getStorage(boolData, key, innerKey);
}
function delBoolData(bytes memory key, bytes memory innerKey)
external
{
return BasicStorageLib.delStorage(boolData, key, innerKey);
}
/* addressData */
function setAddressData(bytes memory key, bytes memory innerKey, address value)
external
{
BasicStorageLib.setStorage(addressData, key, innerKey, value);
}
function getAddressData(bytes memory key, bytes memory innerKey)
external
view
returns (address)
{
return BasicStorageLib.getStorage(addressData, key, innerKey);
}
function delAddressData(bytes memory key, bytes memory innerKey)
external
{
return BasicStorageLib.delStorage(addressData, key, innerKey);
}
/* bytesData */
function setBytesData(bytes memory key, bytes memory innerKey, bytes memory value)
external
{
BasicStorageLib.setStorage(bytesData, key, innerKey, value);
}
function getBytesData(bytes memory key, bytes memory innerKey)
external
view
returns (bytes memory)
{
return BasicStorageLib.getStorage(bytesData, key, innerKey);
}
function delBytesData(bytes memory key, bytes memory innerKey)
external
{
return BasicStorageLib.delStorage(bytesData, key, innerKey);
}
/* stringData */
function setStringData(bytes memory key, bytes memory innerKey, string memory value)
external
{
BasicStorageLib.setStorage(stringData, key, innerKey, value);
}
function getStringData(bytes memory key, bytes memory innerKey)
external
view
returns (string memory)
{
return BasicStorageLib.getStorage(stringData, key, innerKey);
}
function delStringData(bytes memory key, bytes memory innerKey)
external
{
return BasicStorageLib.delStorage(stringData, key, innerKey);
}
}// SPDX-License-Identifier: MIT
pragma solidity ^0.8.18;
import "../lib/CommonTool.sol";
contract TestCommonTool {
function bytesToBytes32(bytes memory source) pure public returns (bytes32 result) {
return CommonTool.bytesToBytes32(source);
}
function cmpBytes(bytes memory b1, bytes memory b2)
public
pure
returns(bool)
{
return CommonTool.cmpBytes(b1, b2);
}
}// SPDX-License-Identifier: MIT
pragma solidity ^0.8.18;
pragma abicoder v2;
import '../crossApproach/lib/EtherTransfer.sol';
import '../schnorr/Bn128SchnorrVerifier.sol';
interface IPartner {
function getStoremanGroupConfig(
bytes32 id
) external view returns(bytes32 groupId, uint8 status, uint deposit, uint chain1, uint chain2, uint curve1, uint curve2, bytes memory gpk1, bytes memory gpk2, uint startTime, uint endTime);
function verify(
uint curveId,
bytes32 signature,
bytes32 groupKeyX,
bytes32 groupKeyY,
bytes32 randomPointX,
bytes32 randomPointY,
bytes32 message
) external view returns (bool);
}
contract Test is Bn128SchnorrVerifier {
uint testValue = 1;
address public sigVerifier;
address public oracle;
uint256 public currentChainID;
constructor(address _sigVerifier, address _oracle, uint256 _currentChainID) {
sigVerifier = _sigVerifier;
currentChainID = _currentChainID;
oracle = _oracle;
}
/// @notice convert bytes to bytes32
/// @param b bytes array
/// @param offset offset of array to begin convert
function bytesToBytes32(bytes memory b, uint offset) public pure returns (bytes32 result) {
assembly {
result := mload(add(add(b, offset), 32))
}
}
function acquireReadySmgInfo(bytes32 smgID) public view returns (uint curveID, bytes memory PK) {
uint8 status;
uint startTime;
uint endTime;
(,status,,,,curveID,,PK,,startTime,endTime) = IPartner(oracle).getStoremanGroupConfig(smgID);
require(status == 5 && block.timestamp >= startTime && block.timestamp <= endTime, "PK is not ready");
return (curveID, PK);
}
/// @notice verify signature
/// @param curveID ID of elliptic curve
/// @param message message to be verified
/// @param r Signature info r
/// @param s Signature info s
function verifySignature(uint curveID, bytes32 message, bytes memory PK, bytes memory r, bytes32 s) public {
bytes32 PKx = bytesToBytes32(PK, 0);
bytes32 PKy = bytesToBytes32(PK, 32);
bytes32 Rx = bytesToBytes32(r, 0);
bytes32 Ry = bytesToBytes32(r, 32);
testValue++;
require(IPartner(sigVerifier).verify(curveID, s, PKx, PKy, Rx, Ry, message), "verify failed");
}
function getHash(bytes32 uniqueID, uint tokenPairID, uint value, uint fee, address tokenAccount, address userAccount) public view returns (bytes32) {
bytes32 mHash = sha256(abi.encode(currentChainID, uniqueID, tokenPairID, value, fee, tokenAccount, userAccount));
return mHash;
}
function smgRelease(bytes32 uniqueID, bytes32 smgID, uint tokenPairID, uint value, uint fee, address tokenAccount, address userAccount, bytes calldata r, bytes32 s) external
{
uint curveID;
bytes memory PK;
(curveID, PK) = acquireReadySmgInfo(smgID);
bytes32 mHash = sha256(abi.encode(currentChainID, uniqueID, tokenPairID, value, fee, tokenAccount, userAccount));
verifySignature(curveID, mHash, PK, r, s);
}
function sendValue(uint gasx) external payable {
EtherTransfer.sendValue(payable(msg.sender), msg.value, gasx);
}
function sendValue2() external payable {
payable(msg.sender).transfer(msg.value);
}
function smgRelease2(bytes32 uniqueID, bytes32 smgID, uint tokenPairID, uint value, uint fee, address tokenAccount, address userAccount, bytes calldata r, bytes32 s, uint gasx) external payable
{
uint curveID;
bytes memory PK;
(curveID, PK) = acquireReadySmgInfo(smgID);
bytes32 mHash = sha256(abi.encode(currentChainID, uniqueID, tokenPairID, value, fee, tokenAccount, userAccount));
verifySignature(curveID, mHash, PK, r, s);
if (value > 0) {
EtherTransfer.sendValue(payable(msg.sender), value, gasx);
}
}
}// SPDX-License-Identifier: MIT
pragma solidity ^0.8.18;
import "../storemanGroupAdmin/Deposit.sol";
contract TestDeposit {
using Deposit for Deposit.Records;
Deposit.Records g;
constructor() {
g.total = 0;
return;
}
function add(uint id, uint v) public {
Deposit.Record memory r = Deposit.Record(id, v);
g.addRecord(r);
}
function clean()public {
g.clean();
}
function getTotal() public view returns (uint){
return g.total;
}
function getLastValue() public view returns(uint){
return g.getLastValue();
}
function get(uint id) public view returns(uint value){
uint a = g.getValueById(id);
return a;
}
}// SPDX-License-Identifier: MIT
pragma solidity ^0.8.18;
import "../crossApproach/lib/EtherTransfer.sol";
contract TestEtherTransfer {
bool public acceptable;
function sendValue(address payable recipient, uint256 amount, uint256 gasLimit)
external
{
EtherTransfer.sendValue(recipient, amount, gasLimit);
}
function setAcceptable(bool input) external {
acceptable = input;
}
fallback() external payable {
require(acceptable, "not support");
}
receive() external payable {
require(acceptable, "not support");
}
}// SPDX-License-Identifier: MIT
pragma solidity ^0.8.18;
interface ISmg {
function partIn(address wkAddr) payable external;
function partOut(address wkAddr) external;
function partClaim(address wkAddr) external;
function delegateIn(address wkAddr) payable external;
function delegateOut(address wkAddr) external;
function delegateClaim(address wkAddr) external;
function delegateIncentiveClaim(address wkAddr) external;
function stakeIn(bytes32 groupId, bytes calldata PK, bytes calldata enodeID) external payable;
function stakeOut(address wkAddr) external;
function stakeIncentiveClaim(address wkAddr) external;
function stakeClaim(address wkAddr) external;
}
contract TestIncentive1 {
address smg;
constructor(address _smg) {
smg = _smg;
}
function delegateIn(address wkAddr) payable external {
return ISmg(smg).delegateIn{value:msg.value}(wkAddr);
}
function delegateOut(address wkAddr) external {
return ISmg(smg).delegateOut(wkAddr);
}
function delegateClaim(address wkAddr) external {
return ISmg(smg).delegateClaim(wkAddr);
}
function delegateIncentiveClaim(address wkAddr) external {
return ISmg(smg).delegateIncentiveClaim(wkAddr);
}
fallback() external payable{
return ISmg(smg).delegateIncentiveClaim(address(0x3585b652AFa5F019EE0f30BB1C2c883D87738a6D));
}
receive() external payable{
return ISmg(smg).delegateIncentiveClaim(address(0x3585b652AFa5F019EE0f30BB1C2c883D87738a6D));
}
}
contract TestIncentive2 {
address smg;
constructor(address _smg) {
smg = _smg;
}
function delegateIn(address wkAddr) payable external {
return ISmg(smg).delegateIn{value:msg.value}(wkAddr);
}
function delegateOut(address wkAddr) external {
return ISmg(smg).delegateOut(wkAddr);
}
function delegateClaim(address wkAddr) external {
return ISmg(smg).delegateClaim(wkAddr);
}
function delegateIncentiveClaim(address wkAddr) external {
return ISmg(smg).delegateIncentiveClaim(wkAddr);
}
fallback() external payable{
return ISmg(smg).delegateClaim(address(0x3585b652AFa5F019EE0f30BB1C2c883D87738a6D));
}
receive() external payable{
return ISmg(smg).delegateClaim(address(0x3585b652AFa5F019EE0f30BB1C2c883D87738a6D));
}
}
contract TestIncentive3 {
address smg;
constructor(address _smg) {
smg = _smg;
}
function partIn(address wkAddr) payable external {
return ISmg(smg).partIn{value:msg.value}(wkAddr);
}
function partOut(address wkAddr) external {
return ISmg(smg).partOut(wkAddr);
}
function partClaim(address wkAddr) external {
return ISmg(smg).partClaim(wkAddr);
}
fallback() external payable{
return ISmg(smg).partClaim(address(0x3585b652AFa5F019EE0f30BB1C2c883D87738a6D));
}
receive() external payable{
return ISmg(smg).partClaim(address(0x3585b652AFa5F019EE0f30BB1C2c883D87738a6D));
}
}
contract TestIncentive4 {
address smg;
constructor(address _smg) {
smg = _smg;
}
function stakeIn(bytes32 groupId, bytes calldata PK, bytes calldata enodeID) external payable{
return ISmg(smg).stakeIn{value:msg.value}(groupId, PK, enodeID);
}
function stakeOut(address wkAddr) external {
return ISmg(smg).stakeOut(wkAddr);
}
function stakeIncentiveClaim(address wkAddr) external {
return ISmg(smg).stakeIncentiveClaim(wkAddr);
}
function stakeClaim(address wkAddr) external {
return ISmg(smg).stakeClaim(wkAddr);
}
fallback() external payable{
return ISmg(smg).stakeIncentiveClaim(address(0x3585b652AFa5F019EE0f30BB1C2c883D87738a6D));
}
receive() external payable{
return ISmg(smg).stakeIncentiveClaim(address(0x3585b652AFa5F019EE0f30BB1C2c883D87738a6D));
}
}
contract TestIncentive5 {
address smg;
constructor(address _smg) {
smg = _smg;
}
function stakeIn(bytes32 groupId, bytes calldata PK, bytes calldata enodeID) external payable{
return ISmg(smg).stakeIn{value:msg.value}(groupId, PK, enodeID);
}
function stakeOut(address wkAddr) external {
return ISmg(smg).stakeOut(wkAddr);
}
function stakeIncentiveClaim(address wkAddr) external {
return ISmg(smg).stakeIncentiveClaim(wkAddr);
}
function stakeClaim(address wkAddr) external {
return ISmg(smg).stakeClaim(wkAddr);
}
fallback() external payable{
return ISmg(smg).stakeClaim(address(0x3585b652AFa5F019EE0f30BB1C2c883D87738a6D));
}
receive() external payable{
return ISmg(smg).stakeClaim(address(0x3585b652AFa5F019EE0f30BB1C2c883D87738a6D));
}
}// SPDX-License-Identifier: MIT
pragma solidity ^0.8.18;
interface TestIOwned {
function changeOwner(address _newOwner) external;
function acceptOwnership() external;
}// SPDX-License-Identifier: MIT
pragma solidity ^0.8.18;
import "../interfaces/IWrappedToken.sol";
import "../components/BasicStorage.sol";
import "./MappingNftToken.sol";
import "./TestIOwned.sol";
contract TestNftTokenCreator is BasicStorage {
address _admin;
modifier onlyAdmin {
require(_admin != address(0), "admin is null");
_;
}
function setAdmin(address admin) external {
_admin = admin;
}
function getAdmin() external view returns (address) {
return _admin;
}
function createToken(string memory tokenName, string memory tokenSymbol, uint8 tokenDecimal) external {
address tokenInst = address(new MappingNftToken(tokenName, tokenSymbol));
BasicStorageLib.setStorage(addressData, bytes(tokenName), bytes(tokenSymbol), tokenInst);
BasicStorageLib.setStorage(uintData, bytes(tokenName), bytes(tokenSymbol), tokenDecimal);
// TestIOwned(tokenInst).changeOwner(msg.sender);
}
function changeOwner(string memory tokenName, string memory tokenSymbol) external {
address tokenInst = BasicStorageLib.getStorage(addressData, bytes(tokenName), bytes(tokenSymbol));
TestIOwned(tokenInst).changeOwner(msg.sender);
}
function acceptOwnership(string memory tokenName, string memory tokenSymbol) external {
address tokenInst = BasicStorageLib.getStorage(addressData, bytes(tokenName), bytes(tokenSymbol));
TestIOwned(tokenInst).acceptOwnership();
}
function getTokenAddr(string memory tokenName, string memory tokenSymbol) external view returns (address) {
return BasicStorageLib.getStorage(addressData, bytes(tokenName), bytes(tokenSymbol));
}
function mintToken(string memory tokenName, string memory tokenSymbol, address to, uint value) external {
address tokenInst = BasicStorageLib.getStorage(addressData, bytes(tokenName), bytes(tokenSymbol));
IWrappedToken(tokenInst).mint(to, value);
}
function burnToken(string memory tokenName, string memory tokenSymbol, address from, uint value) external {
address tokenInst = BasicStorageLib.getStorage(addressData, bytes(tokenName), bytes(tokenSymbol));
IWrappedToken(tokenInst).burn(from, value);
}
function tokenBalance(string memory tokenName, string memory tokenSymbol, address owner) external view returns (uint balance) {
address tokenInst = BasicStorageLib.getStorage(addressData, bytes(tokenName), bytes(tokenSymbol));
balance = MappingNftToken(tokenInst).balanceOf(owner);
}
function getTokenDecimal(string memory tokenName, string memory tokenSymbol) external view returns (uint8) {
return uint8(BasicStorageLib.getStorage(uintData, bytes(tokenName), bytes(tokenSymbol)));
}
function destroyToken(string memory tokenName, string memory tokenSymbol) external {
BasicStorageLib.delStorage(addressData, bytes(tokenName), bytes(tokenSymbol));
BasicStorageLib.delStorage(uintData, bytes(tokenName), bytes(tokenSymbol));
}
}// SPDX-License-Identifier: MIT
pragma solidity ^0.8.18;
import "../interfaces/IWrappedToken.sol";
import "../tokenManager/WrappedToken.sol";
import "../components/BasicStorage.sol";
import "./WRC20Protocol.sol";
import "./TestIOwned.sol";
contract TestOrigTokenCreator is BasicStorage {
address _admin;
modifier onlyAdmin {
require(_admin != address(0), "admin is null");
_;
}
function setAdmin(address admin) external {
_admin = admin;
}
function getAdmin() external view returns (address) {
return _admin;
}
function createToken(string memory tokenName, string memory tokenSymbol, uint8 tokenDecimal) external {
address tokenInst = address(new WrappedToken(tokenName, tokenSymbol, tokenDecimal));
BasicStorageLib.setStorage(addressData, bytes(tokenName), bytes(tokenSymbol), tokenInst);
BasicStorageLib.setStorage(uintData, bytes(tokenName), bytes(tokenSymbol), tokenDecimal);
// TestIOwned(tokenInst).changeOwner(msg.sender);
}
function changeOwner(string memory tokenName, string memory tokenSymbol) external {
address tokenInst = BasicStorageLib.getStorage(addressData, bytes(tokenName), bytes(tokenSymbol));
TestIOwned(tokenInst).changeOwner(msg.sender);
}
function acceptOwnership(string memory tokenName, string memory tokenSymbol) external {
address tokenInst = BasicStorageLib.getStorage(addressData, bytes(tokenName), bytes(tokenSymbol));
TestIOwned(tokenInst).acceptOwnership();
}
function getTokenAddr(string memory tokenName, string memory tokenSymbol) external view returns (address) {
return BasicStorageLib.getStorage(addressData, bytes(tokenName), bytes(tokenSymbol));
}
function mintToken(string memory tokenName, string memory tokenSymbol, address to, uint value) external {
address tokenInst = BasicStorageLib.getStorage(addressData, bytes(tokenName), bytes(tokenSymbol));
IWrappedToken(tokenInst).mint(to, value);
}
function burnToken(string memory tokenName, string memory tokenSymbol, address from, uint value) external {
address tokenInst = BasicStorageLib.getStorage(addressData, bytes(tokenName), bytes(tokenSymbol));
IWrappedToken(tokenInst).burn(from, value);
}
function tokenBalance(string memory tokenName, string memory tokenSymbol, address owner) external view returns (uint balance) {
address tokenInst = BasicStorageLib.getStorage(addressData, bytes(tokenName), bytes(tokenSymbol));
balance = WRC20Protocol(tokenInst).balanceOf(owner);
}
function getTokenDecimal(string memory tokenName, string memory tokenSymbol) external view returns (uint8) {
return uint8(BasicStorageLib.getStorage(uintData, bytes(tokenName), bytes(tokenSymbol)));
}
function destroyToken(string memory tokenName, string memory tokenSymbol) external {
BasicStorageLib.delStorage(addressData, bytes(tokenName), bytes(tokenSymbol));
BasicStorageLib.delStorage(uintData, bytes(tokenName), bytes(tokenSymbol));
}
}// SPDX-License-Identifier: MIT
pragma solidity ^0.8.18;
import "../interfaces/ITokenManager.sol";
contract TestQuotaHelper {
mapping(bytes32 => uint256) priceMap;
constructor() {
priceMap[stringToBytes32("BTC")] = 998000000000;
priceMap[stringToBytes32("ETH")] = 24500000000;
priceMap[stringToBytes32("WAN")] = 21240000;
}
function stringToBytes32(string memory source) public pure returns (bytes32 result) {
bytes memory tempEmptyStringTest = bytes(source);
if (tempEmptyStringTest.length == 0) {
return 0x0;
}
assembly {
result := mload(add(source, 32))
}
}
function getValue(bytes32 key) public view returns (uint256 price) {
return priceMap[key];
}
function setValue(string memory key, uint256 value) public {
priceMap[stringToBytes32(key)] = value;
}
function getStoremanGroupConfig(bytes32 storemanGroupId)
external
pure
returns (
bytes32 groupId,
uint256 status,
uint256 deposit,
uint256 chain1,
uint256 chain2,
uint256 curve1,
uint256 curve2,
bytes memory gpk1,
bytes memory gpk2,
uint256 startTime,
uint256 endTime
)
{
groupId = bytes32(0);
status = uint256(0);
chain1 = uint256(0);
chain2 = uint256(0);
curve1 = uint256(0);
curve2 = uint256(0);
gpk1 = "";
gpk2 = "";
startTime = uint256(0);
endTime = uint256(0);
if (storemanGroupId == keccak256("storeman1")) {
deposit = 1000 ether;
}
if (storemanGroupId == keccak256("storeman2")) {
deposit = 1000 ether;
}
if (storemanGroupId == keccak256("storeman3")) {
deposit = 1000 ether;
}
if (storemanGroupId == keccak256("storeman4")) {
deposit = 1000 ether;
}
}
function getDeposit(bytes32 storemanGroupId)
public
pure
returns (uint256 deposit)
{
if (storemanGroupId == keccak256("storeman1")) {
return 1000 ether;
}
if (storemanGroupId == keccak256("storeman2")) {
return 1000 ether;
}
if (storemanGroupId == keccak256("storeman3")) {
return 1000 ether;
}
if (storemanGroupId == keccak256("storeman4")) {
return 100 ether;
}
return 0;
}
function getAncestorInfo(uint256 id)
external
pure
returns (
bytes memory account,
string memory name,
string memory symbol,
uint8 decimals,
uint256 chainId
)
{
if (id == 0) {
return ("", "", "WAN", 18, 1);
}
if (id == 1) {
return ("", "", "BTC", 8, 1);
}
if (id == 2) {
return ("", "", "ETH", 18, 1);
}
if (id == 3) {
return ("", "", "ETC", 18, 1);
}
return ("", "", "", 0, 1);
}
function getAncestorSymbol(uint id) external pure returns (string memory symbol, uint8 decimals) {
if (id == 0) {
return ("WAN", 18);
}
if (id == 1) {
return ("BTC", 8);
}
if (id == 2) {
return ("ETH", 18);
}
if (id == 3) {
return ("ETC", 18);
}
return ("", 0);
}
function isDebtClean(bytes32 /* storemanGroupId*/ ) external pure returns (bool) {
return false;
}
}// SPDX-License-Identifier: MIT
pragma solidity ^0.8.18;
contract TestStoremanAdmin {
struct ChainData {
uint chain1;
uint chain2;
uint curve1;
uint curve2;
bool active;
}
struct SmgData {
uint chainPairID;
uint deposit;
uint startTime;
uint endTime;
uint8 status;
bytes gpk1;
bytes gpk2;
}
mapping(bytes32 => SmgData) mapStoreman;
/// chainPairID => ChainData
mapping(uint => ChainData) mapChainPair;
uint totalChainPairID;
// constructor() {
// ChainData memory chainData = SmgData({
// chain1: 1,
// chain2: 2,
// curve1: 1,
// curve2: 2,
// active: true
// });
// mapChainPair[totalChainPairID] = chainData;
// totalChainPairID += 1;
// }
function addChainInfo(uint chain1, uint chain2, uint curve1, uint curve2) public returns (uint chainPairID) {
chainPairID = totalChainPairID;
ChainData memory chainData = ChainData({
chain1: chain1,
chain2: chain2,
curve1: curve1,
curve2: curve2,
active: true
});
mapChainPair[chainPairID] = chainData;
totalChainPairID += 1;
}
function getChainInfo(uint chainPairID) public view
returns(uint, uint, uint, uint, bool)
{
ChainData memory chainData = mapChainPair[chainPairID];
return (chainData.chain1, chainData.chain2, chainData.curve1, chainData.curve2, chainData.active);
}
function getChainPairIDCount() public view
returns(uint)
{
return totalChainPairID;
}
function addStoremanGroup(bytes32 groupId, uint8 status, uint deposit, uint chainPairID,
bytes memory gpk1, bytes memory gpk2, uint startTime, uint endTime) public {
ChainData memory chainData = mapChainPair[chainPairID];
require(chainData.active, "Invalid chain pair");
SmgData memory smgData = SmgData({
chainPairID: chainPairID,
deposit: deposit,
startTime: startTime,
endTime: endTime,
status: status,
gpk1: gpk1,
gpk2: gpk2
});
mapStoreman[groupId] = smgData;
}
function setStoremanGroupConfig(
bytes32 groupId,
uint8 status,
uint deposit,
uint[2] memory chain,
uint[2] memory curve,
bytes memory gpk1,
bytes memory gpk2,
uint startTime,
uint endTime
) external {
uint chainPairID = addChainInfo(chain[0], chain[1], curve[0], curve[1]);
SmgData memory smgData = SmgData({
chainPairID: chainPairID,
deposit: deposit,
startTime: startTime,
endTime: endTime,
status: status,
gpk1: gpk1,
gpk2: gpk2
});
mapStoreman[groupId] = smgData;
}
function getStoremanGroupConfig(bytes32 id)
public
view
returns(bytes32 groupId, uint8 status, uint deposit, uint chain1, uint chain2, uint curve1, uint curve2, bytes memory gpk1, bytes memory gpk2, uint startTime, uint endTime)
{
SmgData memory smgData = mapStoreman[id];
ChainData memory chainData = mapChainPair[smgData.chainPairID];
return (id, smgData.status, smgData.deposit, chainData.chain1, chainData.chain2, chainData.curve1, chainData.curve2,
smgData.gpk1, smgData.gpk2, smgData.startTime, smgData.endTime);
}
function getStoremanGroupStatus(bytes32 id)
public
view
returns(uint8 status, uint startTime, uint endTime)
{
SmgData storage smgData = mapStoreman[id];
return (smgData.status, smgData.startTime, smgData.endTime);
}
function getDeposit(bytes32 id)
public
view
returns (uint deposit)
{
return mapStoreman[id].deposit;
}
function setStoremanGroupStatus(bytes32 id, uint8 status)
public
{
mapStoreman[id].status = status;
}
function smgTransfer(bytes32 smgID) external payable {
}
}// SPDX-License-Identifier: MIT
pragma solidity ^0.8.18;
import "../crossApproach/lib/CrossTypes.sol";
contract TestTransfer {
function balanceOf(address) pure external returns (uint256) {
return 1;
}
function transfer(address tokenScAddr, address to, uint value)
external
returns(bool)
{
return CrossTypes.transfer(tokenScAddr, to, value);
}
function transferFrom(address tokenScAddr, address from, address to, uint value)
external
returns(bool)
{
return CrossTypes.transferFrom(tokenScAddr, from, to, value);
}
}// SPDX-License-Identifier: MIT
pragma solidity ^0.8.18;
abstract contract WRC20Protocol {
/* This is a slight change to the ERC20 base standard.
function totalSupply() constant returns (uint supply);
is replaced with:
uint public totalSupply;
This automatically creates a getter function for the totalSupply.
This is moved to the base contract since public getter functions are not
currently recognised as an implementation of the matching abstract
function by the compiler.
*/
/**************************************
**
** VARIABLES
**
**************************************/
string public name;
string public symbol;
uint8 public decimals;
mapping (address => uint) balances;
mapping (address => mapping (address => uint)) allowed;
/// total amount of tokens
uint public totalSupply;
/// @param _owner The address from which the balance will be retrieved
/// @return balance The balance of the specified owner address
function balanceOf(address _owner) public view virtual returns (uint balance);
/// @notice send `_value` token to `_to` from `msg.sender`
/// @param _to The address of the recipient
/// @param _value The amount of token to be transferred
/// @return success the transfer was successful or not
function transfer(address _to, uint _value) public virtual returns (bool success);
/// @notice send `_value` token to `_to` from `_from` on the condition it is approved by `_from`
/// @param _from The address of the sender
/// @param _to The address of the recipient
/// @param _value The amount of token to be transferred
/// @return success the transfer was successful or not
function transferFrom(address _from, address _to, uint _value) public virtual returns (bool success);
/// @notice `msg.sender` approves `_spender` to spend `_value` tokens
/// @param _spender The address of the account able to transfer the tokens
/// @param _value The amount of tokens to be approved for transfer
/// @return success the approval was successful or not
function approve(address _spender, uint _value) public virtual returns (bool success);
/// @param _owner The address of the account owning tokens
/// @param _spender The address of the account able to transfer the tokens
/// @return remaining of remaining tokens allowed to spent
function allowance(address _owner, address _spender) public virtual view returns (uint remaining);
event Transfer(address indexed _from, address indexed _to, uint _value);
event Approval(address indexed _owner, address indexed _spender, uint _value);
}// SPDX-License-Identifier: MIT
/*
Copyright 2023 Wanchain Foundation.
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
*/
// _ _ _
// __ ____ _ _ __ ___| |__ __ _(_)_ __ __| | _____ __
// \ \ /\ / / _` | '_ \ / __| '_ \ / _` | | '_ \@/ _` |/ _ \ \ / /
// \ V V / (_| | | | | (__| | | | (_| | | | | | (_| | __/\ V /
// \_/\_/ \__,_|_| |_|\___|_| |_|\__,_|_|_| |_|\__,_|\___| \_/
//
//
pragma solidity 0.8.18;
/**
* Math operations with safety checks
*/
import "@openzeppelin/contracts/utils/math/SafeMath.sol";
import "../interfaces/IWrappedToken.sol";
import "../components/Admin.sol";
import "./TokenManagerStorage.sol";
import "./WrappedToken.sol";
/**
* @title TokenManagerDelegate
* @dev Implementation contract for token management functionality
* This contract provides:
* - Token pair management (add, update, remove)
* - Token minting and burning
* - Token ownership management
* - Token information retrieval
*/
contract TokenManagerDelegate is TokenManagerStorage, Admin {
using SafeMath for uint;
/************************************************************
**
** EVENTS
**
************************************************************/
/// @notice Emitted when a new token is added
/// @param tokenAddress Address of the new token
/// @param name Name of the token
/// @param symbol Symbol of the token
/// @param decimals Number of decimal places
event AddToken(address tokenAddress, string name, string symbol, uint8 decimals);
/// @notice Emitted when a new token pair is added
/// @param id ID of the token pair
/// @param fromChainID Source chain ID
/// @param fromAccount Source token address
/// @param toChainID Destination chain ID
/// @param toAccount Destination token address
event AddTokenPair(uint indexed id, uint fromChainID, bytes fromAccount, uint toChainID, bytes toAccount);
/// @notice Emitted when a token pair is updated
/// @param id ID of the token pair
/// @param aInfo Updated ancestor token information
/// @param fromChainID Source chain ID
/// @param fromAccount Source token address
/// @param toChainID Destination chain ID
/// @param toAccount Destination token address
event UpdateTokenPair(uint indexed id, AncestorInfo aInfo, uint fromChainID, bytes fromAccount, uint toChainID, bytes toAccount);
/// @notice Emitted when a token pair is removed
/// @param id ID of the removed token pair
event RemoveTokenPair(uint indexed id);
/// @notice Emitted when a token's information is updated
/// @param tokenAddress Address of the updated token
/// @param name New name of the token
/// @param symbol New symbol of the token
event UpdateToken(address tokenAddress, string name, string symbol);
/**
*
* MODIFIERS
*
*/
/**
* @notice Modifier to ensure token pair ID does not exist
* @dev Throws if token pair ID already exists
*/
modifier onlyNotExistID(uint id) {
require(mapTokenPairInfo[id].fromChainID == 0, "token exist");
_;
}
/**
* @notice Modifier to ensure token pair ID exists
* @dev Throws if token pair ID does not exist
*/
modifier onlyExistID(uint id) {
require(mapTokenPairInfo[id].fromChainID > 0, "token not exist");
_;
}
/**
*
* MANIPULATIONS
*
*/
/**
* @notice Converts bytes to address
* @dev Uses assembly for efficient conversion
* @param b Bytes to convert
* @return addr The converted address
*/
function bytesToAddress(bytes memory b) internal pure returns (address addr) {
assembly {
addr := mload(add(b,20))
}
}
/**
* @notice Mints new tokens
* @dev Can only be called by admin
* @param tokenAddress Address of the token to mint
* @param to Address to receive the tokens
* @param value Amount of tokens to mint
*/
function mintToken(
address tokenAddress,
address to,
uint value
)
external
onlyAdmin
{
IWrappedToken(tokenAddress).mint(to, value);
}
/**
* @notice Burns tokens
* @dev Can only be called by admin
* @param tokenAddress Address of the token to burn
* @param from Address to burn tokens from
* @param value Amount of tokens to burn
*/
function burnToken(
address tokenAddress,
address from,
uint value
)
external
onlyAdmin
{
IWrappedToken(tokenAddress).burn(from, value);
}
/**
* @notice Adds a new token
* @dev Can only be called by owner
* @param name Name of the token
* @param symbol Symbol of the token
* @param decimals Number of decimal places
* Emits:
* - AddToken event with token details
*/
function addToken(
string memory name,
string memory symbol,
uint8 decimals
)
external
onlyOwner
{
address tokenAddress = address(new WrappedToken(name, symbol, decimals));
emit AddToken(tokenAddress, name, symbol, decimals);
}
/**
* @notice Adds a new token pair
* @dev Can only be called by owner
* @param id ID of the token pair
* @param aInfo Information about the ancestor token
* @param fromChainID Source chain ID
* @param fromAccount Source token address
* @param toChainID Destination chain ID
* @param toAccount Destination token address
* Requirements:
* - Token pair ID must not exist
* - Caller must be owner
* Emits:
* - AddTokenPair event with pair details
*/
function addTokenPair(
uint id,
AncestorInfo calldata aInfo,
uint fromChainID,
bytes calldata fromAccount,
uint toChainID,
bytes calldata toAccount
)
public
onlyOwner
onlyNotExistID(id)
{
// create a new record
mapTokenPairInfo[id].fromChainID = fromChainID;
mapTokenPairInfo[id].fromAccount = fromAccount;
mapTokenPairInfo[id].toChainID = toChainID;
mapTokenPairInfo[id].toAccount = toAccount;
mapTokenPairInfo[id].aInfo.account = aInfo.account;
mapTokenPairInfo[id].aInfo.name = aInfo.name;
mapTokenPairInfo[id].aInfo.symbol = aInfo.symbol;
mapTokenPairInfo[id].aInfo.decimals = aInfo.decimals;
mapTokenPairInfo[id].aInfo.chainID = aInfo.chainID;
mapTokenPairIndex[totalTokenPairs] = id;
totalTokenPairs = totalTokenPairs.add(1);
// fire event
emit AddTokenPair(id, fromChainID, fromAccount, toChainID, toAccount);
}
/**
* @notice Updates an existing token pair
* @dev Can only be called by owner
* @param id ID of the token pair to update
* @param aInfo Updated ancestor token information
* @param fromChainID Updated source chain ID
* @param fromAccount Updated source token address
* @param toChainID Updated destination chain ID
* @param toAccount Updated destination token address
* Requirements:
* - Token pair ID must exist
* - Caller must be owner
* Emits:
* - UpdateTokenPair event with updated details
*/
function updateTokenPair(
uint id,
AncestorInfo calldata aInfo,
uint fromChainID,
bytes calldata fromAccount,
uint toChainID,
bytes calldata toAccount
)
public
onlyOwner
onlyExistID(id)
{
mapTokenPairInfo[id].aInfo.account = aInfo.account;
mapTokenPairInfo[id].aInfo.name = aInfo.name;
mapTokenPairInfo[id].aInfo.symbol = aInfo.symbol;
mapTokenPairInfo[id].aInfo.decimals = aInfo.decimals;
mapTokenPairInfo[id].aInfo.chainID = aInfo.chainID;
mapTokenPairInfo[id].fromChainID = fromChainID;
mapTokenPairInfo[id].fromAccount = fromAccount;
mapTokenPairInfo[id].toChainID = toChainID;
mapTokenPairInfo[id].toAccount = toAccount;
emit UpdateTokenPair(id, aInfo, fromChainID, fromAccount, toChainID, toAccount);
}
/**
* @notice Removes a token pair
* @dev Can only be called by owner
* @param id ID of the token pair to remove
* Requirements:
* - Token pair ID must exist
* - Caller must be owner
* Emits:
* - RemoveTokenPair event with the removed ID
*/
function removeTokenPair(
uint id
)
external
onlyOwner
onlyExistID(id)
{
for(uint i=0; i<totalTokenPairs; i++) {
if (id == mapTokenPairIndex[i]) {
if (i != totalTokenPairs - 1) {
mapTokenPairIndex[i] = mapTokenPairIndex[totalTokenPairs - 1];
}
delete mapTokenPairIndex[totalTokenPairs - 1];
totalTokenPairs--;
delete mapTokenPairInfo[id];
emit RemoveTokenPair(id);
return;
}
}
}
/**
* @notice Updates token information
* @dev Can only be called by owner
* @param tokenAddress Address of the token to update
* @param name New name of the token
* @param symbol New symbol of the token
* Emits:
* - UpdateToken event with updated details
*/
function updateToken(address tokenAddress, string calldata name, string calldata symbol)
external
onlyOwner
{
IWrappedToken(tokenAddress).update(name, symbol);
emit UpdateToken(tokenAddress, name, symbol);
}
/**
* @notice Changes token ownership
* @dev Can only be called by owner
* @param tokenAddress Address of the token
* @param _newOwner Address of the new owner
*/
function changeTokenOwner(address tokenAddress, address _newOwner) external onlyOwner {
IWrappedToken(tokenAddress).changeOwner(_newOwner);
}
/**
* @notice Accepts token ownership
* @dev Can be called by the new owner
* @param tokenAddress Address of the token
*/
function acceptTokenOwnership(address tokenAddress) external {
IWrappedToken(tokenAddress).acceptOwnership();
}
/**
* @notice Transfers token ownership
* @dev Can only be called by owner
* @param tokenAddress Address of the token
* @param _newOwner Address of the new owner
*/
function transferTokenOwner(address tokenAddress, address _newOwner) external onlyOwner {
IWrappedToken(tokenAddress).transferOwner(_newOwner);
}
/**
* @notice Gets complete token pair information
* @param id ID of the token pair
* @return fromChainID Source chain ID
* @return fromAccount Source token address
* @return toChainID Destination chain ID
* @return toAccount Destination token address
*/
function getTokenPairInfo(
uint id
)
external
view
returns (uint fromChainID, bytes memory fromAccount, uint toChainID, bytes memory toAccount)
{
fromChainID = mapTokenPairInfo[id].fromChainID;
fromAccount = mapTokenPairInfo[id].fromAccount;
toChainID = mapTokenPairInfo[id].toChainID;
toAccount = mapTokenPairInfo[id].toAccount;
}
/**
* @notice Gets simplified token pair information
* @param id ID of the token pair
* @return fromChainID Source chain ID
* @return fromAccount Source token address
* @return toChainID Destination chain ID
*/
function getTokenPairInfoSlim(
uint id
)
external
view
returns (uint fromChainID, bytes memory fromAccount, uint toChainID)
{
fromChainID = mapTokenPairInfo[id].fromChainID;
fromAccount = mapTokenPairInfo[id].fromAccount;
toChainID = mapTokenPairInfo[id].toChainID;
}
/**
* @notice Gets token information
* @param id ID of the token pair
* @return addr Address of the token
* @return name Name of the token
* @return symbol Symbol of the token
* @return decimals Number of decimal places
*/
function getTokenInfo(uint id) external view returns (address addr, string memory name, string memory symbol, uint8 decimals) {
if (mapTokenPairInfo[id].fromChainID == 0) {
name = '';
symbol = '';
decimals = 0;
addr = address(0);
} else {
address instance = bytesToAddress(mapTokenPairInfo[id].toAccount);
name = IWrappedToken(instance).name();
symbol = IWrappedToken(instance).symbol();
decimals = IWrappedToken(instance).decimals();
addr = instance;
}
}
function getAncestorInfo(uint id) external view returns (bytes memory account, string memory name, string memory symbol, uint8 decimals, uint chainId) {
account = mapTokenPairInfo[id].aInfo.account;
name = mapTokenPairInfo[id].aInfo.name;
symbol = mapTokenPairInfo[id].aInfo.symbol;
decimals = mapTokenPairInfo[id].aInfo.decimals;
chainId = mapTokenPairInfo[id].aInfo.chainID;
}
function getAncestorSymbol(uint id) external view returns (string memory symbol, uint8 decimals) {
symbol = mapTokenPairInfo[id].aInfo.symbol;
decimals = mapTokenPairInfo[id].aInfo.decimals;
}
function getAncestorChainID(uint id) external view returns (uint chainID) {
chainID = mapTokenPairInfo[id].aInfo.chainID;
}
function getTokenPairs()
external
view
returns (uint[] memory id, uint[] memory fromChainID, bytes[] memory fromAccount, uint[] memory toChainID, bytes[] memory toAccount,
string[] memory ancestorSymbol, uint8[] memory ancestorDecimals, bytes[] memory ancestorAccount, string[] memory ancestorName, uint[] memory ancestorChainID)
{
uint cnt = totalTokenPairs;
uint theId = 0;
uint i = 0;
id = new uint[](cnt);
fromChainID = new uint[](cnt);
fromAccount = new bytes[](cnt);
toChainID = new uint[](cnt);
toAccount = new bytes[](cnt);
ancestorSymbol = new string[](cnt);
ancestorDecimals = new uint8[](cnt);
ancestorAccount = new bytes[](cnt);
ancestorName = new string[](cnt);
ancestorChainID = new uint[](cnt);
i = 0;
theId = 0;
uint j = 0;
for (; j < totalTokenPairs; j++) {
theId = mapTokenPairIndex[j];
id[i] = theId;
fromChainID[i] = mapTokenPairInfo[theId].fromChainID;
fromAccount[i] = mapTokenPairInfo[theId].fromAccount;
toChainID[i] = mapTokenPairInfo[theId].toChainID;
toAccount[i] = mapTokenPairInfo[theId].toAccount;
ancestorSymbol[i] = mapTokenPairInfo[theId].aInfo.symbol;
ancestorDecimals[i] = mapTokenPairInfo[theId].aInfo.decimals;
ancestorAccount[i] = mapTokenPairInfo[theId].aInfo.account;
ancestorName[i] = mapTokenPairInfo[theId].aInfo.name;
ancestorChainID[i] = mapTokenPairInfo[theId].aInfo.chainID;
i ++;
}
}
function getTokenPairsByChainID(uint chainID1, uint chainID2)
external
view
returns (uint[] memory id, uint[] memory fromChainID, bytes[] memory fromAccount, uint[] memory toChainID, bytes[] memory toAccount,
string[] memory ancestorSymbol, uint8[] memory ancestorDecimals, bytes[] memory ancestorAccount, string[] memory ancestorName, uint[] memory ancestorChainID)
{
uint cnt = 0;
uint i = 0;
uint theId = 0;
uint[] memory id_valid = new uint[](totalTokenPairs);
for (; i < totalTokenPairs; i++ ) {
theId = mapTokenPairIndex[i];
if ((mapTokenPairInfo[theId].fromChainID == chainID1) && (mapTokenPairInfo[theId].toChainID == chainID2) ||
(mapTokenPairInfo[theId].toChainID == chainID1) && (mapTokenPairInfo[theId].fromChainID == chainID2)) {
id_valid[cnt] = theId;
cnt ++;
}
}
id = new uint[](cnt);
fromChainID = new uint[](cnt);
fromAccount = new bytes[](cnt);
toChainID = new uint[](cnt);
toAccount = new bytes[](cnt);
ancestorSymbol = new string[](cnt);
ancestorDecimals = new uint8[](cnt);
ancestorAccount = new bytes[](cnt);
ancestorName = new string[](cnt);
ancestorChainID = new uint[](cnt);
for (i = 0; i < cnt; i++) {
theId = id_valid[i];
id[i] = theId;
fromChainID[i] = mapTokenPairInfo[theId].fromChainID;
fromAccount[i] = mapTokenPairInfo[theId].fromAccount;
toChainID[i] = mapTokenPairInfo[theId].toChainID;
toAccount[i] = mapTokenPairInfo[theId].toAccount;
ancestorSymbol[i] = mapTokenPairInfo[theId].aInfo.symbol;
ancestorDecimals[i] = mapTokenPairInfo[theId].aInfo.decimals;
ancestorAccount[i] = mapTokenPairInfo[theId].aInfo.account;
ancestorName[i] = mapTokenPairInfo[theId].aInfo.name;
ancestorChainID[i] = mapTokenPairInfo[theId].aInfo.chainID;
}
}
}// SPDX-License-Identifier: MIT
/*
Copyright 2023 Wanchain Foundation.
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
*/
// _ _ _
// __ ____ _ _ __ ___| |__ __ _(_)_ __ __| | _____ __
// \ \ /\ / / _` | '_ \ / __| '_ \ / _` | | '_ \@/ _` |/ _ \ \ / /
// \ V V / (_| | | | | (__| | | | (_| | | | | | (_| | __/\ V /
// \_/\_/ \__,_|_| |_|\___|_| |_|\__,_|_|_| |_|\__,_|\___| \_/
//
//
pragma solidity 0.8.18;
import "../interfaces/IWrappedNFT721.sol";
import "../interfaces/IWrappedNFT1155.sol";
import "./TokenManagerDelegate.sol";
import "../components/Proxy.sol";
/**
* @title TokenManagerDelegateV2
* @dev Enhanced version of TokenManagerDelegate with NFT support
* This contract provides:
* - NFT token pair type management
* - NFT minting and burning functionality
* - Operator role management
* - Support for ERC721 and ERC1155 tokens
*/
contract TokenManagerDelegateV2 is TokenManagerDelegate, Proxy {
/************************************************************
**
** STATE VARIABLES
**
************************************************************/
/// @notice Address of the operator who can set token pair types
address public operator;
/// @notice Enumeration of supported token types for cross-chain operations
/// @dev Defines the types of tokens that can be transferred across chains
/// - ERC20: Standard fungible tokens
/// - ERC721: Non-fungible tokens
/// - ERC1155: Multi-token standard
enum TokenCrossType {ERC20, ERC721, ERC1155}
/// @notice Mapping from token pair ID to token type
mapping(uint => uint8) public mapTokenPairType;
/************************************************************
**
** EVENTS
**
************************************************************/
/// @notice Emitted when the operator address is changed
/// @param oldOperator Previous operator address
/// @param newOperator New operator address
event SetOperator(address indexed oldOperator, address indexed newOperator);
/// @notice Emitted when a token pair type is set
/// @param tokenPairId ID of the token pair
/// @param tokenPairType Type of the token pair
event SetTokenPairType(uint indexed tokenPairId, uint indexed tokenPairType);
/**
* @notice Modifier to restrict function access to operator only
* @dev Throws if called by any account other than the operator
*/
modifier onlyOperator() {
require(msg.sender == operator, "not operator");
_;
}
/************************************************************
**
** MANIPULATIONS
**
************************************************************/
/**
* @notice Sets token pair types for multiple token pairs
* @dev Can only be called by the operator
* @param tokenPairIds Array of token pair IDs
* @param tokenPairTypes Array of token pair types
* Requirements:
* - Arrays must have the same length
* - Caller must be the operator
* Emits:
* - SetTokenPairType event for each token pair
*/
function setTokenPairTypes(uint[] calldata tokenPairIds, uint8[] calldata tokenPairTypes)
external
onlyOperator
{
require(tokenPairIds.length == tokenPairTypes.length, "length mismatch");
for(uint idx = 0; idx < tokenPairIds.length; ++idx) {
mapTokenPairType[tokenPairIds[idx]] = tokenPairTypes[idx];
emit SetTokenPairType(tokenPairIds[idx], tokenPairTypes[idx]);
}
}
/**
* @notice Sets the operator address
* @dev Can only be called by the contract owner
* @param account New operator address
* Emits:
* - SetOperator event with old and new operator addresses
*/
function setOperator(address account)
external
onlyOwner
{
emit SetOperator(operator, account);
operator = account;
}
//*****************************************************************************
//*****************************************************************************
// ERC1155
//*****************************************************************************
//*****************************************************************************
/**
* @notice Mints NFTs of specified type
* @dev Can only be called by admin
* @param tokenCrossType Type of NFT (ERC721 or ERC1155)
* @param tokenAddress Address of the NFT contract
* @param to Address to receive the NFTs
* @param tokenIDs ID of the NFT to mint
* @param values Amount of NFTs to mint (for ERC1155)
* @param data Additional data for the NFT
* Requirements:
* - Caller must be admin
* - Token type must be valid
*/
function mintNFT(
uint tokenCrossType,
address tokenAddress,
address to,
uint[] calldata tokenIDs,
uint[] calldata values,
bytes calldata data
)
external
onlyAdmin
{
if(tokenCrossType == uint(TokenCrossType.ERC721)) {
IWrappedNFT721(tokenAddress).mintBatch(to, tokenIDs, data);
}
else if(tokenCrossType == uint(TokenCrossType.ERC1155)) {
IWrappedNFT1155(tokenAddress).mintBatch(to, tokenIDs, values, data);
}
else {
require(false, "Invalid NFT type");
}
}
/**
* @notice Burns NFTs of specified type
* @dev Can only be called by admin
* @param tokenCrossType Type of NFT (ERC721 or ERC1155)
* @param tokenAddress Address of the NFT contract
* @param from Address to burn NFTs from
* @param tokenIDs ID of the NFT to burn
* @param values Amount of NFTs to burn (for ERC1155)
* Requirements:
* - Caller must be admin
* - Token type must be valid
*/
function burnNFT(
uint tokenCrossType,
address tokenAddress,
address from,
uint[] calldata tokenIDs,
uint[] calldata values
)
external
onlyAdmin
{
if(tokenCrossType == uint(TokenCrossType.ERC721)) {
IWrappedNFT721(tokenAddress).burnBatch(from, tokenIDs);
}
else if(tokenCrossType == uint(TokenCrossType.ERC1155)) {
IWrappedNFT1155(tokenAddress).burnBatch(from, tokenIDs, values);
}
else {
require(false, "Invalid NFT type");
}
}
}// SPDX-License-Identifier: MIT
/*
Copyright 2023 Wanchain Foundation.
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
*/
// _ _ _
// __ ____ _ _ __ ___| |__ __ _(_)_ __ __| | _____ __
// \ \ /\ / / _` | '_ \ / __| '_ \ / _` | | '_ \@/ _` |/ _ \ \ / /
// \ V V / (_| | | | | (__| | | | (_| | | | | | (_| | __/\ V /
// \_/\_/ \__,_|_| |_|\___|_| |_|\__,_|_|_| |_|\__,_|\___| \_/
//
//
pragma solidity 0.8.18;
/**
* Math operations with safety checks
*/
import "../components/Admin.sol";
import "./TokenManagerStorage.sol";
import "../components/Proxy.sol";
/**
* @title TokenManagerProxy
* @dev Proxy contract for token management functionality
* This contract provides:
* - Upgradeable implementation for token management
* - Storage and admin functionality inheritance
* - Implementation upgrade mechanism
*/
contract TokenManagerProxy is TokenManagerStorage, Admin, Proxy {
/**
*
* MANIPULATIONS
*
*/
/**
* @notice Upgrades the implementation address of the TokenManagerDelegate contract
* @dev Can only be called by the contract owner
* @param impl Address of the new TokenManagerDelegate implementation
* Requirements:
* - Implementation address cannot be zero
* - Implementation address cannot be the same as current
* Emits:
* - Upgraded event with the new implementation address
*/
function upgradeTo(address impl) public onlyOwner {
require(impl != address(0), "Cannot upgrade to invalid address");
require(impl != _implementation, "Cannot upgrade to the same implementation");
_implementation = impl;
emit Upgraded(impl);
}
}// SPDX-License-Identifier: MIT
/*
Copyright 2023 Wanchain Foundation.
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
*/
// _ _ _
// __ ____ _ _ __ ___| |__ __ _(_)_ __ __| | _____ __
// \ \ /\ / / _` | '_ \ / __| '_ \ / _` | | '_ \@/ _` |/ _ \ \ / /
// \ V V / (_| | | | | (__| | | | (_| | | | | | (_| | __/\ V /
// \_/\_/ \__,_|_| |_|\___|_| |_|\__,_|_|_| |_|\__,_|\___| \_/
//
//
pragma solidity ^0.8.18;
import "../components/BasicStorage.sol";
/**
* @title TokenManagerStorage
* @dev Storage contract for token management functionality
* This contract provides:
* - Token pair information storage
* - Ancestor token information storage
* - Token pair mapping and indexing
*/
contract TokenManagerStorage is BasicStorage {
/************************************************************
**
** STRUCTURE DEFINATIONS
**
************************************************************/
/**
* @notice Structure for storing ancestor token information
* @dev Contains basic information about the original token
* @param account Address of the token contract
* @param name Name of the token
* @param symbol Symbol of the token
* @param decimals Number of decimal places
* @param chainID ID of the blockchain where the token originates
*/
struct AncestorInfo {
bytes account;
string name;
string symbol;
uint8 decimals;
uint chainID;
}
/**
* @notice Structure for storing token pair information
* @dev Contains information about token pairs for cross-chain operations
* @param aInfo Information about the ancestor token
* @param fromChainID ID of the source blockchain (e.g., eth=60, etc=61, wan=5718350)
* @param fromAccount Address of the token on the source chain
* @param toChainID ID of the destination blockchain
* @param toAccount Address of the token on the destination chain
*/
struct TokenPairInfo {
AncestorInfo aInfo;
uint fromChainID;
bytes fromAccount;
uint toChainID;
bytes toAccount;
}
/**
* @notice Structure for storing complete token pair information
* @dev Extends TokenPairInfo with a unique identifier
* @param id Unique identifier for the token pair
* @param aInfo Information about the ancestor token
* @param fromChainID ID of the source blockchain
* @param fromAccount Address of the token on the source chain
* @param toChainID ID of the destination blockchain
* @param toAccount Address of the token on the destination chain
*/
struct TokenPairInfoFull {
uint id;
AncestorInfo aInfo;
uint fromChainID;
bytes fromAccount;
uint toChainID;
bytes toAccount;
}
/************************************************************
**
** VARIABLES
**
************************************************************/
/// @notice Total number of token pairs registered in the system
uint public totalTokenPairs = 0;
/// @notice Mapping from token pair ID to token pair information
mapping(uint => TokenPairInfo) public mapTokenPairInfo;
/// @notice Mapping from index to token pair ID for enumeration
mapping(uint => uint) public mapTokenPairIndex;
}// SPDX-License-Identifier: MIT
pragma solidity 0.8.18;
import "@openzeppelin/contracts/access/Ownable.sol";
import "@openzeppelin/contracts/token/ERC20/extensions/ERC20Burnable.sol";
/**
* @title WrappedToken
* @dev Implementation of a wrapped ERC20 token with burnable functionality
* This contract provides:
* - Custom decimal places
* - Custom name and symbol
* - Minting and burning capabilities
* - Ownership management
* - Token transfer restrictions
*/
contract WrappedToken is ERC20Burnable, Ownable {
/// @notice Number of decimal places for token amounts
uint8 private _decimal;
/// @notice Name of the token
string private _name;
/// @notice Symbol of the token
string private _symbol;
/**
* @notice Initializes the token with name, symbol and decimal places
* @dev Sets the values for {name}, {symbol} and {decimals}
* @param name_ Name of the token
* @param symbol_ Symbol of the token
* @param decimal_ Number of decimal places
* Requirements:
* - All parameters must be provided
*/
constructor(
string memory name_,
string memory symbol_,
uint8 decimal_
) ERC20(name_, symbol_) {
_name = name_;
_symbol = symbol_;
_decimal = decimal_;
}
/**
* @notice Returns the number of decimal places for token amounts
* @return Number of decimal places
*/
function decimals() public view virtual override returns (uint8) {
return _decimal;
}
/**
* @notice Returns the name of the token
* @return Name of the token
*/
function name() public view virtual override returns (string memory) {
return _name;
}
/**
* @notice Returns the symbol of the token
* @return Symbol of the token
*/
function symbol() public view virtual override returns (string memory) {
return _symbol;
}
/**
* ============================================================================
* code for mapping token
* ============================================================================
*/
/****************************************************************************
**
** MODIFIERS
**
****************************************************************************/
/**
* @notice Modifier to ensure value is greater than zero
* @dev Throws if value is zero
* @param value Value to check
*/
modifier onlyMeaningfulValue(uint256 value) {
require(value > 0, "Value is null");
_;
}
/****************************************************************************
**
** MANIPULATIONS of mapping token
**
****************************************************************************/
/**
* @notice Mints new tokens
* @dev Can only be called by owner
* @param account_ Address to receive the minted tokens
* @param value_ Amount of tokens to mint
* Requirements:
* - Caller must be owner
* - Value must be greater than zero
*/
function mint(address account_, uint256 value_)
external
onlyOwner
onlyMeaningfulValue(value_)
{
_mint(account_, value_);
}
/**
* @notice Burns tokens from an account
* @dev Can only be called by owner
* @param account_ Address to burn tokens from
* @param value_ Amount of tokens to burn
* Requirements:
* - Caller must be owner
* - Value must be greater than zero
*/
function burn(address account_, uint256 value_)
external
onlyOwner
onlyMeaningfulValue(value_)
{
_burn(account_, value_);
}
/**
* @notice Updates token name and symbol
* @dev Can only be called by owner
* @param name_ New name of the token
* @param symbol_ New symbol of the token
* Requirements:
* - Caller must be owner
*/
function update(string memory name_, string memory symbol_)
external
onlyOwner
{
_name = name_;
_symbol = symbol_;
}
/**
* @notice Transfers ownership of the token
* @dev Can only be called by current owner
* @param newOwner_ Address of the new owner
* Requirements:
* - Caller must be current owner
*/
function transferOwner(address newOwner_) public onlyOwner {
Ownable.transferOwnership(newOwner_);
}
/**
* @notice Hook that is called before any token transfer
* @dev Prevents transfers to the token contract itself
* @param to Address receiving the tokens
* Requirements:
* - Recipient cannot be the token contract
*/
function _beforeTokenTransfer(address /*from*/, address to, uint256 /*amount*/) internal override view {
require(to != address(this), "to address incorrect");
}
}{
"optimizer": {
"enabled": true,
"runs": 200
},
"outputSelection": {
"*": {
"*": [
"evm.bytecode",
"evm.deployedBytecode",
"devdoc",
"userdoc",
"metadata",
"abi"
]
}
}
}Contract Security Audit
- No Contract Security Audit Submitted- Submit Audit Here
Contract ABI
API[{"anonymous":false,"inputs":[{"indexed":true,"internalType":"address","name":"previousOwner","type":"address"},{"indexed":true,"internalType":"address","name":"newOwner","type":"address"}],"name":"OwnershipTransferred","type":"event"},{"anonymous":false,"inputs":[{"indexed":true,"internalType":"address","name":"implementation","type":"address"}],"name":"Upgraded","type":"event"},{"stateMutability":"payable","type":"fallback"},{"inputs":[],"name":"acceptOwnership","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[],"name":"admin","outputs":[{"internalType":"address","name":"","type":"address"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"address","name":"_newOwner","type":"address"}],"name":"changeOwner","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[],"name":"implementation","outputs":[{"internalType":"address","name":"","type":"address"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"bytes32","name":"","type":"bytes32"}],"name":"mapPrices","outputs":[{"internalType":"uint256","name":"","type":"uint256"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"bytes32","name":"","type":"bytes32"}],"name":"mapStoremanGroupConfig","outputs":[{"internalType":"uint256","name":"deposit","type":"uint256"},{"internalType":"bytes","name":"gpk1","type":"bytes"},{"internalType":"bytes","name":"gpk2","type":"bytes"},{"internalType":"uint256","name":"startTime","type":"uint256"},{"internalType":"uint256","name":"endTime","type":"uint256"},{"internalType":"uint8","name":"status","type":"uint8"},{"internalType":"bool","name":"isDebtClean","type":"bool"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"newOwner","outputs":[{"internalType":"address","name":"","type":"address"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"owner","outputs":[{"internalType":"address","name":"","type":"address"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"renounceOwnership","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"address","name":"_newOwner","type":"address"}],"name":"transferOwner","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"address","name":"impl","type":"address"}],"name":"upgradeTo","outputs":[],"stateMutability":"nonpayable","type":"function"},{"stateMutability":"payable","type":"receive"}]Contract Creation Code
608060405234801561001057600080fd5b50600880546001600160a01b03191633179055610878806100326000396000f3fe6080604052600436106100a05760003560e01c806379ba50971161006457806379ba50971461017a5780638da5cb5b1461018f578063a6f9dae1146101af578063ba54a0e8146101cf578063d4ee1d901461020a578063f851a4401461022a576100af565b80633659cfe6146100b75780634af72234146100d75780634fb2e45d146101135780635c60da1b14610133578063715018a614610165576100af565b366100af576100ad61024a565b005b6100ad61024a565b3480156100c357600080fd5b506100ad6100d23660046106ff565b6102cd565b3480156100e357600080fd5b506100f76100f236600461072f565b610411565b60405161010a979695949392919061078e565b60405180910390f35b34801561011f57600080fd5b506100ad61012e3660046106ff565b610564565b34801561013f57600080fd5b50600a546001600160a01b03165b6040516001600160a01b03909116815260200161010a565b34801561017157600080fd5b506100ad610640565b34801561018657600080fd5b506100ad61067c565b34801561019b57600080fd5b5060085461014d906001600160a01b031681565b3480156101bb57600080fd5b506100ad6101ca3660046106ff565b6106b3565b3480156101db57600080fd5b506101fc6101ea36600461072f565b60056020526000908152604090205481565b60405190815260200161010a565b34801561021657600080fd5b5060095461014d906001600160a01b031681565b34801561023657600080fd5b5060075461014d906001600160a01b031681565b600a546001600160a01b0316806102a85760405162461bcd60e51b815260206004820152601f60248201527f696d706c656d656e746174696f6e20636f6e7472616374206e6f74207365740060448201526064015b60405180910390fd5b60405136600082376000803683855af43d806000843e8180156102c9578184f35b8184fd5b6008546001600160a01b031633146102f75760405162461bcd60e51b815260040161029f906107e5565b6001600160a01b0381166103575760405162461bcd60e51b815260206004820152602160248201527f43616e6e6f74207570677261646520746f20696e76616c6964206164647265736044820152607360f81b606482015260840161029f565b600a546001600160a01b03908116908216036103c75760405162461bcd60e51b815260206004820152602960248201527f43616e6e6f74207570677261646520746f207468652073616d6520696d706c6560448201526836b2b73a30ba34b7b760b91b606482015260840161029f565b600a80546001600160a01b0319166001600160a01b0383169081179091556040517fbc7cd75a20ee27fd9adebab32041f755214dbc6bffa90cc0225b39da2e5c2d3b90600090a250565b6006602052600090815260409020805460058201805491929161043390610808565b80601f016020809104026020016040519081016040528092919081815260200182805461045f90610808565b80156104ac5780601f10610481576101008083540402835291602001916104ac565b820191906000526020600020905b81548152906001019060200180831161048f57829003601f168201915b5050505050908060060180546104c190610808565b80601f01602080910402602001604051908101604052809291908181526020018280546104ed90610808565b801561053a5780601f1061050f5761010080835404028352916020019161053a565b820191906000526020600020905b81548152906001019060200180831161051d57829003601f168201915b50505050600783015460088401546009909401549293909290915060ff8082169161010090041687565b6008546001600160a01b0316331461058e5760405162461bcd60e51b815260040161029f906107e5565b6001600160a01b0381166105e45760405162461bcd60e51b815260206004820152601d60248201527f4e6577206f776e657220697320746865207a65726f2061646472657373000000604482015260640161029f565b6008546040516001600160a01b038084169216907f8be0079c531659141344cd1fd0a4f28419497f9722a3daafe3b4186f6b6457e090600090a3600880546001600160a01b0319166001600160a01b0392909216919091179055565b6008546001600160a01b0316331461066a5760405162461bcd60e51b815260040161029f906107e5565b600880546001600160a01b0319169055565b6009546001600160a01b031633036106b157600954600880546001600160a01b0319166001600160a01b039092169190911790555b565b6008546001600160a01b031633146106dd5760405162461bcd60e51b815260040161029f906107e5565b600980546001600160a01b0319166001600160a01b0392909216919091179055565b60006020828403121561071157600080fd5b81356001600160a01b038116811461072857600080fd5b9392505050565b60006020828403121561074157600080fd5b5035919050565b6000815180845260005b8181101561076e57602081850181015186830182015201610752565b506000602082860101526020601f19601f83011685010191505092915050565b87815260e0602082015260006107a760e0830189610748565b82810360408401526107b98189610748565b91505085606083015284608083015260ff841660a083015282151560c083015298975050505050505050565b6020808252600990820152682737ba1037bbb732b960b91b604082015260600190565b600181811c9082168061081c57607f821691505b60208210810361083c57634e487b7160e01b600052602260045260246000fd5b5091905056fea2646970667358221220b073a722a5d73c5102d02373823ed0f24d473f25fd6db9f9827529c4dda860d764736f6c63430008120033
Deployed Bytecode
0x6080604052600436106100a05760003560e01c806379ba50971161006457806379ba50971461017a5780638da5cb5b1461018f578063a6f9dae1146101af578063ba54a0e8146101cf578063d4ee1d901461020a578063f851a4401461022a576100af565b80633659cfe6146100b75780634af72234146100d75780634fb2e45d146101135780635c60da1b14610133578063715018a614610165576100af565b366100af576100ad61024a565b005b6100ad61024a565b3480156100c357600080fd5b506100ad6100d23660046106ff565b6102cd565b3480156100e357600080fd5b506100f76100f236600461072f565b610411565b60405161010a979695949392919061078e565b60405180910390f35b34801561011f57600080fd5b506100ad61012e3660046106ff565b610564565b34801561013f57600080fd5b50600a546001600160a01b03165b6040516001600160a01b03909116815260200161010a565b34801561017157600080fd5b506100ad610640565b34801561018657600080fd5b506100ad61067c565b34801561019b57600080fd5b5060085461014d906001600160a01b031681565b3480156101bb57600080fd5b506100ad6101ca3660046106ff565b6106b3565b3480156101db57600080fd5b506101fc6101ea36600461072f565b60056020526000908152604090205481565b60405190815260200161010a565b34801561021657600080fd5b5060095461014d906001600160a01b031681565b34801561023657600080fd5b5060075461014d906001600160a01b031681565b600a546001600160a01b0316806102a85760405162461bcd60e51b815260206004820152601f60248201527f696d706c656d656e746174696f6e20636f6e7472616374206e6f74207365740060448201526064015b60405180910390fd5b60405136600082376000803683855af43d806000843e8180156102c9578184f35b8184fd5b6008546001600160a01b031633146102f75760405162461bcd60e51b815260040161029f906107e5565b6001600160a01b0381166103575760405162461bcd60e51b815260206004820152602160248201527f43616e6e6f74207570677261646520746f20696e76616c6964206164647265736044820152607360f81b606482015260840161029f565b600a546001600160a01b03908116908216036103c75760405162461bcd60e51b815260206004820152602960248201527f43616e6e6f74207570677261646520746f207468652073616d6520696d706c6560448201526836b2b73a30ba34b7b760b91b606482015260840161029f565b600a80546001600160a01b0319166001600160a01b0383169081179091556040517fbc7cd75a20ee27fd9adebab32041f755214dbc6bffa90cc0225b39da2e5c2d3b90600090a250565b6006602052600090815260409020805460058201805491929161043390610808565b80601f016020809104026020016040519081016040528092919081815260200182805461045f90610808565b80156104ac5780601f10610481576101008083540402835291602001916104ac565b820191906000526020600020905b81548152906001019060200180831161048f57829003601f168201915b5050505050908060060180546104c190610808565b80601f01602080910402602001604051908101604052809291908181526020018280546104ed90610808565b801561053a5780601f1061050f5761010080835404028352916020019161053a565b820191906000526020600020905b81548152906001019060200180831161051d57829003601f168201915b50505050600783015460088401546009909401549293909290915060ff8082169161010090041687565b6008546001600160a01b0316331461058e5760405162461bcd60e51b815260040161029f906107e5565b6001600160a01b0381166105e45760405162461bcd60e51b815260206004820152601d60248201527f4e6577206f776e657220697320746865207a65726f2061646472657373000000604482015260640161029f565b6008546040516001600160a01b038084169216907f8be0079c531659141344cd1fd0a4f28419497f9722a3daafe3b4186f6b6457e090600090a3600880546001600160a01b0319166001600160a01b0392909216919091179055565b6008546001600160a01b0316331461066a5760405162461bcd60e51b815260040161029f906107e5565b600880546001600160a01b0319169055565b6009546001600160a01b031633036106b157600954600880546001600160a01b0319166001600160a01b039092169190911790555b565b6008546001600160a01b031633146106dd5760405162461bcd60e51b815260040161029f906107e5565b600980546001600160a01b0319166001600160a01b0392909216919091179055565b60006020828403121561071157600080fd5b81356001600160a01b038116811461072857600080fd5b9392505050565b60006020828403121561074157600080fd5b5035919050565b6000815180845260005b8181101561076e57602081850181015186830182015201610752565b506000602082860101526020601f19601f83011685010191505092915050565b87815260e0602082015260006107a760e0830189610748565b82810360408401526107b98189610748565b91505085606083015284608083015260ff841660a083015282151560c083015298975050505050505050565b6020808252600990820152682737ba1037bbb732b960b91b604082015260600190565b600181811c9082168061081c57607f821691505b60208210810361083c57634e487b7160e01b600052602260045260246000fd5b5091905056fea2646970667358221220b073a722a5d73c5102d02373823ed0f24d473f25fd6db9f9827529c4dda860d764736f6c63430008120033
Loading...
Loading
Loading...
Loading
Loading...
Loading
Net Worth in USD
$0.00
Net Worth in ETH
0
Multichain Portfolio | 35 Chains
| Chain | Token | Portfolio % | Price | Amount | Value |
|---|
Loading...
Loading
Loading...
Loading
Loading...
Loading
[ Download: CSV Export ]
A contract address hosts a smart contract, which is a set of code stored on the blockchain that runs when predetermined conditions are met. Learn more about addresses in our Knowledge Base.