Transaction Hash:
Block:
20118154 at Jun-18-2024 10:50:11 AM +UTC
Transaction Fee:
0.000906409790764312 ETH
$1.93
Gas Used:
163,574 Gas / 5.541282788 Gwei
Emitted Events:
173 |
VOWToken.Sent( operator=[Receiver] DBridge, from=[Sender] 0x5396e00615214cb50c911164adb931ae505e46f4, to=[Receiver] DBridge, amount=2900000000000000000000, data=0x, operatorData=0x )
|
174 |
VOWToken.Transfer( from=[Sender] 0x5396e00615214cb50c911164adb931ae505e46f4, to=[Receiver] DBridge, value=2900000000000000000000 )
|
175 |
DBridge.Deposit( bridgeId=F01F98BBC5C28FABD153A3915970FD313CC991E5A71FF16A0A29BB9F1BBBDEB6, bridgeIndex=5385, sender=[Sender] 0x5396e00615214cb50c911164adb931ae505e46f4, chainId=56, tokenAddress=0xF585B5b4...62630397b, amount=2900000000000000000000 )
|
Account State Difference:
Address | Before | After | State Difference | ||
---|---|---|---|---|---|
0x1BBf25e7...F4bE946Fb | |||||
0x1f9090aa...8e676c326
Miner
| 2.241340172419162793 Eth | 2.241503746419162793 Eth | 0.000163574 | ||
0x5396E006...E505e46F4 |
0.035231585548349398 Eth
Nonce: 1266
|
0.034325175757585086 Eth
Nonce: 1267
| 0.000906409790764312 | ||
0xa7C14010...57dde644d |
Execution Trace
DBridge.deposit( chainId=56, tokenAddress=0x1BBf25e71EC48B84d773809B4bA55B6F4bE946Fb, amount=2900000000000000000000 ) => ( bridgeId=F01F98BBC5C28FABD153A3915970FD313CC991E5A71FF16A0A29BB9F1BBBDEB6 )
VOWToken.transferFrom( _from=0x5396E00615214Cb50C911164AdB931AE505e46F4, _to=0xa7C14010afA616fa23A2Bb0A94d76Dd57dde644d, _value=2900000000000000000000 ) => ( success_=True )
LToken.2e6a5609( )
-
ERC1820Registry.getInterfaceImplementer( _addr=0x5396E00615214Cb50C911164AdB931AE505e46F4, _interfaceHash=29DDB589B1FB5FC7CF394961C1ADF5F8C6454761ADF795E67FE149F658ABE895 ) => ( 0x0000000000000000000000000000000000000000 )
-
ERC1820Registry.getInterfaceImplementer( _addr=0xa7C14010afA616fa23A2Bb0A94d76Dd57dde644d, _interfaceHash=B281FC8C12954D22544DB45DE3159A39272895B169A852B314F9CC762E44C53B ) => ( 0x0000000000000000000000000000000000000000 )
-
File 1 of 4: DBridge
File 2 of 4: VOWToken
File 3 of 4: LToken
File 4 of 4: ERC1820Registry
// SPDX-License-Identifier: MIT // OpenZeppelin Contracts (last updated v5.0.0) (access/AccessControl.sol) pragma solidity ^0.8.20; import {IAccessControl} from "./IAccessControl.sol"; import {Context} from "../utils/Context.sol"; import {ERC165} from "../utils/introspection/ERC165.sol"; /** * @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 account => bool) hasRole; bytes32 adminRole; } mapping(bytes32 role => RoleData) private _roles; bytes32 public constant DEFAULT_ADMIN_ROLE = 0x00; /** * @dev Modifier that checks that an account has a specific role. Reverts * with an {AccessControlUnauthorizedAccount} error including the required role. */ 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 returns (bool) { return _roles[role].hasRole[account]; } /** * @dev Reverts with an {AccessControlUnauthorizedAccount} error if `_msgSender()` * is missing `role`. Overriding this function changes the behavior of the {onlyRole} modifier. */ function _checkRole(bytes32 role) internal view virtual { _checkRole(role, _msgSender()); } /** * @dev Reverts with an {AccessControlUnauthorizedAccount} error if `account` * is missing `role`. */ function _checkRole(bytes32 role, address account) internal view virtual { if (!hasRole(role, account)) { revert AccessControlUnauthorizedAccount(account, role); } } /** * @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 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 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 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 `callerConfirmation`. * * May emit a {RoleRevoked} event. */ function renounceRole(bytes32 role, address callerConfirmation) public virtual { if (callerConfirmation != _msgSender()) { revert AccessControlBadConfirmation(); } _revokeRole(role, callerConfirmation); } /** * @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 Attempts to grant `role` to `account` and returns a boolean indicating if `role` was granted. * * Internal function without access restriction. * * May emit a {RoleGranted} event. */ function _grantRole(bytes32 role, address account) internal virtual returns (bool) { if (!hasRole(role, account)) { _roles[role].hasRole[account] = true; emit RoleGranted(role, account, _msgSender()); return true; } else { return false; } } /** * @dev Attempts to revoke `role` to `account` and returns a boolean indicating if `role` was revoked. * * Internal function without access restriction. * * May emit a {RoleRevoked} event. */ function _revokeRole(bytes32 role, address account) internal virtual returns (bool) { if (hasRole(role, account)) { _roles[role].hasRole[account] = false; emit RoleRevoked(role, account, _msgSender()); return true; } else { return false; } } } // SPDX-License-Identifier: MIT // OpenZeppelin Contracts (last updated v5.0.0) (access/IAccessControl.sol) pragma solidity ^0.8.20; /** * @dev External interface of AccessControl declared to support ERC165 detection. */ interface IAccessControl { /** * @dev The `account` is missing a role. */ error AccessControlUnauthorizedAccount(address account, bytes32 neededRole); /** * @dev The caller of a function is not the expected one. * * NOTE: Don't confuse with {AccessControlUnauthorizedAccount}. */ error AccessControlBadConfirmation(); /** * @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. */ 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 `callerConfirmation`. */ function renounceRole(bytes32 role, address callerConfirmation) external; } // SPDX-License-Identifier: MIT // OpenZeppelin Contracts (last updated v5.0.0) (token/ERC20/extensions/IERC20Permit.sol) pragma solidity ^0.8.20; /** * @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. * * ==== Security Considerations * * There are two important considerations concerning the use of `permit`. The first is that a valid permit signature * expresses an allowance, and it should not be assumed to convey additional meaning. In particular, it should not be * considered as an intention to spend the allowance in any specific way. The second is that because permits have * built-in replay protection and can be submitted by anyone, they can be frontrun. A protocol that uses permits should * take this into consideration and allow a `permit` call to fail. Combining these two aspects, a pattern that may be * generally recommended is: * * ```solidity * function doThingWithPermit(..., uint256 value, uint256 deadline, uint8 v, bytes32 r, bytes32 s) public { * try token.permit(msg.sender, address(this), value, deadline, v, r, s) {} catch {} * doThing(..., value); * } * * function doThing(..., uint256 value) public { * token.safeTransferFrom(msg.sender, address(this), value); * ... * } * ``` * * Observe that: 1) `msg.sender` is used as the owner, leaving no ambiguity as to the signer intent, and 2) the use of * `try/catch` allows the permit to fail and makes the code tolerant to frontrunning. (See also * {SafeERC20-safeTransferFrom}). * * Additionally, note that smart contract wallets (such as Argent or Safe) are not able to produce permit signatures, so * contracts should have entry points that don't rely on permit. */ 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]. * * CAUTION: See Security Considerations above. */ 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 v5.0.0) (token/ERC20/IERC20.sol) pragma solidity ^0.8.20; /** * @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 value of tokens in existence. */ function totalSupply() external view returns (uint256); /** * @dev Returns the value of tokens owned by `account`. */ function balanceOf(address account) external view returns (uint256); /** * @dev Moves a `value` amount of 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 value) 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 a `value` amount of tokens 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 value) external returns (bool); /** * @dev Moves a `value` amount of tokens from `from` to `to` using the * allowance mechanism. `value` 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 value) external returns (bool); } // SPDX-License-Identifier: MIT // OpenZeppelin Contracts (last updated v5.0.0) (token/ERC20/utils/SafeERC20.sol) pragma solidity ^0.8.20; import {IERC20} from "../IERC20.sol"; import {IERC20Permit} from "../extensions/IERC20Permit.sol"; import {Address} from "../../../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 An operation with an ERC20 token failed. */ error SafeERC20FailedOperation(address token); /** * @dev Indicates a failed `decreaseAllowance` request. */ error SafeERC20FailedDecreaseAllowance(address spender, uint256 currentAllowance, uint256 requestedDecrease); /** * @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.encodeCall(token.transfer, (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.encodeCall(token.transferFrom, (from, to, 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); forceApprove(token, spender, oldAllowance + value); } /** * @dev Decrease the calling contract's allowance toward `spender` by `requestedDecrease`. If `token` returns no * value, non-reverting calls are assumed to be successful. */ function safeDecreaseAllowance(IERC20 token, address spender, uint256 requestedDecrease) internal { unchecked { uint256 currentAllowance = token.allowance(address(this), spender); if (currentAllowance < requestedDecrease) { revert SafeERC20FailedDecreaseAllowance(spender, currentAllowance, requestedDecrease); } forceApprove(token, spender, currentAllowance - requestedDecrease); } } /** * @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.encodeCall(token.approve, (spender, value)); if (!_callOptionalReturnBool(token, approvalCall)) { _callOptionalReturn(token, abi.encodeCall(token.approve, (spender, 0))); _callOptionalReturn(token, approvalCall); } } /** * @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); if (returndata.length != 0 && !abi.decode(returndata, (bool))) { revert SafeERC20FailedOperation(address(token)); } } /** * @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(token).code.length > 0; } } // SPDX-License-Identifier: MIT // OpenZeppelin Contracts (last updated v5.0.0) (utils/Address.sol) pragma solidity ^0.8.20; /** * @dev Collection of functions related to the address type */ library Address { /** * @dev The ETH balance of the account is not enough to perform the operation. */ error AddressInsufficientBalance(address account); /** * @dev There's no code at `target` (it is not a contract). */ error AddressEmptyCode(address target); /** * @dev A call to an address target failed. The target may have reverted. */ error FailedInnerCall(); /** * @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.20/security-considerations.html#use-the-checks-effects-interactions-pattern[checks-effects-interactions pattern]. */ function sendValue(address payable recipient, uint256 amount) internal { if (address(this).balance < amount) { revert AddressInsufficientBalance(address(this)); } (bool success, ) = recipient.call{value: amount}(""); if (!success) { revert FailedInnerCall(); } } /** * @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 or custom error, it is bubbled * up by this function (like regular Solidity function calls). However, if * the call reverted with no returned reason, this function reverts with a * {FailedInnerCall} error. * * 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. */ function functionCall(address target, bytes memory data) internal returns (bytes memory) { return functionCallWithValue(target, data, 0); } /** * @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`. */ function functionCallWithValue(address target, bytes memory data, uint256 value) internal returns (bytes memory) { if (address(this).balance < value) { revert AddressInsufficientBalance(address(this)); } (bool success, bytes memory returndata) = target.call{value: value}(data); return verifyCallResultFromTarget(target, success, returndata); } /** * @dev Same as {xref-Address-functionCall-address-bytes-}[`functionCall`], * but performing a static call. */ function functionStaticCall(address target, bytes memory data) internal view returns (bytes memory) { (bool success, bytes memory returndata) = target.staticcall(data); return verifyCallResultFromTarget(target, success, returndata); } /** * @dev Same as {xref-Address-functionCall-address-bytes-}[`functionCall`], * but performing a delegate call. */ function functionDelegateCall(address target, bytes memory data) internal returns (bytes memory) { (bool success, bytes memory returndata) = target.delegatecall(data); return verifyCallResultFromTarget(target, success, returndata); } /** * @dev Tool to verify that a low level call to smart-contract was successful, and reverts if the target * was not a contract or bubbling up the revert reason (falling back to {FailedInnerCall}) in case of an * unsuccessful call. */ function verifyCallResultFromTarget( address target, bool success, bytes memory returndata ) internal view returns (bytes memory) { if (!success) { _revert(returndata); } else { // only check if target is a contract if the call was successful and the return data is empty // otherwise we already know that it was a contract if (returndata.length == 0 && target.code.length == 0) { revert AddressEmptyCode(target); } return returndata; } } /** * @dev Tool to verify that a low level call was successful, and reverts if it wasn't, either by bubbling the * revert reason or with a default {FailedInnerCall} error. */ function verifyCallResult(bool success, bytes memory returndata) internal pure returns (bytes memory) { if (!success) { _revert(returndata); } else { return returndata; } } /** * @dev Reverts with returndata if present. Otherwise reverts with {FailedInnerCall}. */ function _revert(bytes memory returndata) 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 FailedInnerCall(); } } } // SPDX-License-Identifier: MIT // OpenZeppelin Contracts (last updated v5.0.0) (utils/Context.sol) pragma solidity ^0.8.20; /** * @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 (last updated v5.0.0) (utils/cryptography/ECDSA.sol) pragma solidity ^0.8.20; /** * @dev Elliptic Curve Digital Signature Algorithm (ECDSA) operations. * * These functions can be used to verify that a message was signed by the holder * of the private keys of a given address. */ library ECDSA { enum RecoverError { NoError, InvalidSignature, InvalidSignatureLength, InvalidSignatureS } /** * @dev The signature derives the `address(0)`. */ error ECDSAInvalidSignature(); /** * @dev The signature has an invalid length. */ error ECDSAInvalidSignatureLength(uint256 length); /** * @dev The signature has an S value that is in the upper half order. */ error ECDSAInvalidSignatureS(bytes32 s); /** * @dev Returns the address that signed a hashed message (`hash`) with `signature` or an error. This will not * return address(0) without also returning an error description. Errors are documented using an enum (error type) * and a bytes32 providing additional information about the error. * * If no error is returned, then the address can be used for verification purposes. * * The `ecrecover` EVM precompile allows for malleable (non-unique) signatures: * this function rejects them by requiring the `s` value to be in the lower * half order, and the `v` value to be either 27 or 28. * * IMPORTANT: `hash` _must_ be the result of a hash operation for the * verification to be secure: it is possible to craft signatures that * recover to arbitrary addresses for non-hashed data. A safe way to ensure * this is by receiving a hash of the original message (which may otherwise * be too long), and then calling {MessageHashUtils-toEthSignedMessageHash} on it. * * Documentation for signature generation: * - with https://web3js.readthedocs.io/en/v1.3.4/web3-eth-accounts.html#sign[Web3.js] * - with https://docs.ethers.io/v5/api/signer/#Signer-signMessage[ethers] */ function tryRecover(bytes32 hash, bytes memory signature) internal pure returns (address, RecoverError, bytes32) { if (signature.length == 65) { bytes32 r; bytes32 s; uint8 v; // ecrecover takes the signature parameters, and the only way to get them // currently is to use assembly. /// @solidity memory-safe-assembly assembly { r := mload(add(signature, 0x20)) s := mload(add(signature, 0x40)) v := byte(0, mload(add(signature, 0x60))) } return tryRecover(hash, v, r, s); } else { return (address(0), RecoverError.InvalidSignatureLength, bytes32(signature.length)); } } /** * @dev Returns the address that signed a hashed message (`hash`) with * `signature`. This address can then be used for verification purposes. * * The `ecrecover` EVM precompile allows for malleable (non-unique) signatures: * this function rejects them by requiring the `s` value to be in the lower * half order, and the `v` value to be either 27 or 28. * * IMPORTANT: `hash` _must_ be the result of a hash operation for the * verification to be secure: it is possible to craft signatures that * recover to arbitrary addresses for non-hashed data. A safe way to ensure * this is by receiving a hash of the original message (which may otherwise * be too long), and then calling {MessageHashUtils-toEthSignedMessageHash} on it. */ function recover(bytes32 hash, bytes memory signature) internal pure returns (address) { (address recovered, RecoverError error, bytes32 errorArg) = tryRecover(hash, signature); _throwError(error, errorArg); return recovered; } /** * @dev Overload of {ECDSA-tryRecover} that receives the `r` and `vs` short-signature fields separately. * * See https://eips.ethereum.org/EIPS/eip-2098[EIP-2098 short signatures] */ function tryRecover(bytes32 hash, bytes32 r, bytes32 vs) internal pure returns (address, RecoverError, bytes32) { unchecked { bytes32 s = vs & bytes32(0x7fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff); // We do not check for an overflow here since the shift operation results in 0 or 1. uint8 v = uint8((uint256(vs) >> 255) + 27); return tryRecover(hash, v, r, s); } } /** * @dev Overload of {ECDSA-recover} that receives the `r and `vs` short-signature fields separately. */ function recover(bytes32 hash, bytes32 r, bytes32 vs) internal pure returns (address) { (address recovered, RecoverError error, bytes32 errorArg) = tryRecover(hash, r, vs); _throwError(error, errorArg); return recovered; } /** * @dev Overload of {ECDSA-tryRecover} that receives the `v`, * `r` and `s` signature fields separately. */ function tryRecover( bytes32 hash, uint8 v, bytes32 r, bytes32 s ) internal pure returns (address, RecoverError, bytes32) { // EIP-2 still allows signature malleability for ecrecover(). Remove this possibility and make the signature // unique. Appendix F in the Ethereum Yellow paper (https://ethereum.github.io/yellowpaper/paper.pdf), defines // the valid range for s in (301): 0 < s < secp256k1n ÷ 2 + 1, and for v in (302): v ∈ {27, 28}. Most // signatures from current libraries generate a unique signature with an s-value in the lower half order. // // If your library generates malleable signatures, such as s-values in the upper range, calculate a new s-value // with 0xFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFEBAAEDCE6AF48A03BBFD25E8CD0364141 - s1 and flip v from 27 to 28 or // vice versa. If your library also generates signatures with 0/1 for v instead 27/28, add 27 to v to accept // these malleable signatures as well. if (uint256(s) > 0x7FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF5D576E7357A4501DDFE92F46681B20A0) { return (address(0), RecoverError.InvalidSignatureS, s); } // If the signature is valid (and not malleable), return the signer address address signer = ecrecover(hash, v, r, s); if (signer == address(0)) { return (address(0), RecoverError.InvalidSignature, bytes32(0)); } return (signer, RecoverError.NoError, bytes32(0)); } /** * @dev Overload of {ECDSA-recover} that receives the `v`, * `r` and `s` signature fields separately. */ function recover(bytes32 hash, uint8 v, bytes32 r, bytes32 s) internal pure returns (address) { (address recovered, RecoverError error, bytes32 errorArg) = tryRecover(hash, v, r, s); _throwError(error, errorArg); return recovered; } /** * @dev Optionally reverts with the corresponding custom error according to the `error` argument provided. */ function _throwError(RecoverError error, bytes32 errorArg) private pure { if (error == RecoverError.NoError) { return; // no error: do nothing } else if (error == RecoverError.InvalidSignature) { revert ECDSAInvalidSignature(); } else if (error == RecoverError.InvalidSignatureLength) { revert ECDSAInvalidSignatureLength(uint256(errorArg)); } else if (error == RecoverError.InvalidSignatureS) { revert ECDSAInvalidSignatureS(errorArg); } } } // SPDX-License-Identifier: MIT // OpenZeppelin Contracts (last updated v5.0.0) (utils/introspection/ERC165.sol) pragma solidity ^0.8.20; import {IERC165} from "./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); * } * ``` */ abstract contract ERC165 is IERC165 { /** * @dev See {IERC165-supportsInterface}. */ function supportsInterface(bytes4 interfaceId) public view virtual returns (bool) { return interfaceId == type(IERC165).interfaceId; } } // SPDX-License-Identifier: MIT // OpenZeppelin Contracts (last updated v5.0.0) (utils/introspection/IERC165.sol) pragma solidity ^0.8.20; /** * @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 v5.0.0) (utils/Pausable.sol) pragma solidity ^0.8.20; import {Context} from "../utils/Context.sol"; /** * @dev Contract module which allows children to implement an emergency stop * mechanism that can be triggered by an authorized account. * * This module is used through inheritance. It will make available the * modifiers `whenNotPaused` and `whenPaused`, which can be applied to * the functions of your contract. Note that they will not be pausable by * simply including this module, only once the modifiers are put in place. */ abstract contract Pausable is Context { bool private _paused; /** * @dev Emitted when the pause is triggered by `account`. */ event Paused(address account); /** * @dev Emitted when the pause is lifted by `account`. */ event Unpaused(address account); /** * @dev The operation failed because the contract is paused. */ error EnforcedPause(); /** * @dev The operation failed because the contract is not paused. */ error ExpectedPause(); /** * @dev Initializes the contract in unpaused state. */ constructor() { _paused = false; } /** * @dev Modifier to make a function callable only when the contract is not paused. * * Requirements: * * - The contract must not be paused. */ modifier whenNotPaused() { _requireNotPaused(); _; } /** * @dev Modifier to make a function callable only when the contract is paused. * * Requirements: * * - The contract must be paused. */ modifier whenPaused() { _requirePaused(); _; } /** * @dev Returns true if the contract is paused, and false otherwise. */ function paused() public view virtual returns (bool) { return _paused; } /** * @dev Throws if the contract is paused. */ function _requireNotPaused() internal view virtual { if (paused()) { revert EnforcedPause(); } } /** * @dev Throws if the contract is not paused. */ function _requirePaused() internal view virtual { if (!paused()) { revert ExpectedPause(); } } /** * @dev Triggers stopped state. * * Requirements: * * - The contract must not be paused. */ function _pause() internal virtual whenNotPaused { _paused = true; emit Paused(_msgSender()); } /** * @dev Returns to normal state. * * Requirements: * * - The contract must be paused. */ function _unpause() internal virtual whenPaused { _paused = false; emit Unpaused(_msgSender()); } } // SPDX-License-Identifier: MIT pragma solidity ^0.8.20; import "@openzeppelin/contracts/access/AccessControl.sol"; import "@openzeppelin/contracts/utils/Pausable.sol"; import "@openzeppelin/contracts/token/ERC20/utils/SafeERC20.sol"; import "@openzeppelin/contracts/utils/cryptography/ECDSA.sol"; import "./interface/IDToken.sol"; /// @title DBridge: A decentralized bridge contract for token transfers /// @notice This contract facilitates token transfers between different chains using a decentralized approach. contract DBridge is Pausable, AccessControl { using SafeERC20 for IERC20; bytes32 public constant GOVERNOR_ROLE = keccak256("GOVERNOR_ROLE"); bytes32 public constant RELAYER_ROLE = keccak256("RELAYER_ROLE"); bytes32 public immutable domainHash; address payable immutable exchangePool; uint256 private _bridgeIdCounter = 0; IDToken private rewardToken; uint256 private baseVoteFee; uint256 private rewardFee; /// @dev Enum representing the types of transactions (Deposit or Withdraw). enum TxnType { DEPOSIT, WITHDRAW } struct TokenDetails { mapping(uint256 => address) bridgeTokenAddress; uint256 platformFee; bool isAvailable; bool isMintableBurnable; } struct BridgeTxn { uint256 bridgeIndex; TxnType txnType; address sender; uint256 chainId; address tokenAddress; uint256 amount; bool isWithdrawed; bool isVerifiedByRelayer; address[] confirmations; mapping(address => uint256) reward; } struct VoteData { bytes32 bridgeId; uint256 bridgeIndex; address sender; uint256 chainId; uint256 sourceChainId; address sourceBridgeAddress; address tokenAddress; uint256 amount; } struct WithdrawData { bytes32 bridgeId; uint256 bridgeIndex; address sender; uint256 chainId; uint256 sourceChainId; address sourceBridgeAddress; address tokenAddress; uint256 amount; bytes[] signatures; } mapping(address => TokenDetails) public supportedTokens; mapping(bytes32 => BridgeTxn) public bridgeTxn; event Deposit( bytes32 indexed bridgeId, uint256 indexed bridgeIndex, address sender, uint256 chainId, address tokenAddress, uint256 amount ); event Bridge( bytes32 indexed bridgeId, uint256 sourceChainId, address receiver, address tokenAddress, uint256 amount, address[] validators ); event Confirmation( bytes32 indexed bridgeId, uint256 sourceChainId, address receiver, address tokenAddress, uint256 amount, address confirmer ); event Withdraw( bytes32 indexed bridgeId, address indexed receiver, address bridgeTokenAddress, uint256 amount ); /// @notice Deploy the DBridge contract with the provided parameters. /// @param name The name of the contract. /// @param version The version of the contract. /// @param exchangePoolAddress The address of the exchange pool. /// @param rewarTokenAddress The address of the reward token. /// @param _baseVoteFee The base vote fee. /// @param _rewardFee The reward fee. constructor( string memory name, string memory version, address payable exchangePoolAddress, IDToken rewarTokenAddress, uint256 _baseVoteFee, uint256 _rewardFee ) { _grantRole(DEFAULT_ADMIN_ROLE, msg.sender); _grantRole(GOVERNOR_ROLE, msg.sender); exchangePool = exchangePoolAddress; rewardToken = rewarTokenAddress; baseVoteFee = _baseVoteFee; rewardFee = _rewardFee; domainHash = keccak256( abi.encode( keccak256( abi.encodePacked( "EIP712Domain(string name,string version,uint256 chainId,address verifyingContract)" ) ), keccak256(abi.encodePacked(name)), keccak256(abi.encodePacked(version)), block.chainid, address(this) ) ); } /// @notice Pause the contract, preventing new transactions. function pause() public onlyRole(DEFAULT_ADMIN_ROLE) { _pause(); } /// @notice Unpause the contract, allowing transactions to continue. function unpause() public onlyRole(DEFAULT_ADMIN_ROLE) { _unpause(); } /// @notice Add a new token to the supported tokens list. /// @param tokenAddress The address of the token to be added. /// @param tokenPlatformFee The platform fee associated with the token. /// @param isMintableBurnableToken A flag indicating if the token is mintable/burnable. function addToken( address tokenAddress, uint256 tokenPlatformFee, bool isMintableBurnableToken ) external onlyRole(GOVERNOR_ROLE) whenNotPaused { try IDToken(tokenAddress).totalSupply() returns (uint256) { try IDToken(tokenAddress).name() returns (string memory) { TokenDetails storage token = supportedTokens[tokenAddress]; token.isAvailable = true; token.platformFee = tokenPlatformFee; token.isMintableBurnable = isMintableBurnableToken; } catch { revert("Address not token"); } } catch { revert("Address not token"); } } /// @notice Add support for a new chain for a specific token. /// @param tokenAddress The address of the token. /// @param chainId The ID of the supported chain. /// @param bridgeTokenAddress The address of the corresponding bridge token. function addSupportedChain( address tokenAddress, uint256 chainId, address bridgeTokenAddress ) external onlyRole(GOVERNOR_ROLE) whenNotPaused { supportedTokens[tokenAddress].bridgeTokenAddress[ chainId ] = bridgeTokenAddress; } /// @notice Enable or disable a token for transfers. /// @param tokenAddress The address of the token. /// @param isEnable Set to true to enable the token, or false to disable it. function enableDisableToken( address tokenAddress, bool isEnable ) external onlyRole(GOVERNOR_ROLE) whenNotPaused { supportedTokens[tokenAddress].isAvailable = isEnable; } /// @notice Edit the platform fee of an existing token. /// @param tokenAddress The address of the token. /// @param newPlatformFee The updated platform fee for the token. function updateTokenPlatformFee( address tokenAddress, uint256 newPlatformFee ) external onlyRole(GOVERNOR_ROLE) { require( supportedTokens[tokenAddress].isAvailable, "Token is not supported" ); supportedTokens[tokenAddress].platformFee = newPlatformFee; } /// @notice Get the bridge token address for a specific token on a given chain. /// @param tokenAddress The address of the token. /// @param chainId The ID of the chain. /// @return bridgeTokenAddress The bridge token address for the token on the specified chain. function getBridgeTokenAddress( address tokenAddress, uint256 chainId ) external view whenNotPaused returns (address bridgeTokenAddress) { return supportedTokens[tokenAddress].bridgeTokenAddress[chainId]; } /// @notice Deposit tokens into the bridge for cross-chain transfer. /// @param chainId The ID of the target chain. /// @param tokenAddress The address of the token being deposited. /// @param amount The amount of tokens to deposit. /// @return bridgeId The unique ID for the bridge transaction. function deposit( uint256 chainId, address tokenAddress, uint256 amount ) external whenNotPaused returns (bytes32 bridgeId) { require( supportedTokens[tokenAddress].isAvailable, "Token is not supported" ); require( supportedTokens[tokenAddress].bridgeTokenAddress[chainId] != address(0), "Chain ID is not supported" ); _bridgeIdCounter += 1; bridgeId = keccak256( abi.encodePacked( _msgSender(), address(this), chainId, supportedTokens[tokenAddress].bridgeTokenAddress[chainId], amount, _bridgeIdCounter ) ); require( bridgeTxn[bridgeId].sender == address(0), "Bridge already exists" ); if (supportedTokens[tokenAddress].isMintableBurnable) { IDToken(tokenAddress).burnFrom(_msgSender(), amount); } else { IERC20(tokenAddress).safeTransferFrom( _msgSender(), address(this), amount ); } BridgeTxn storage txn = bridgeTxn[bridgeId]; txn.txnType = TxnType.DEPOSIT; txn.sender = _msgSender(); txn.chainId = chainId; txn.tokenAddress = tokenAddress; txn.amount = amount; txn.isWithdrawed = false; txn.isVerifiedByRelayer = false; emit Deposit( bridgeId, _bridgeIdCounter, _msgSender(), chainId, supportedTokens[tokenAddress].bridgeTokenAddress[chainId], amount ); } /// @notice Bridge a withdrawal request initiated on another chain. /// @param withdrawData The data for the withdrawal request. function bridge(WithdrawData calldata withdrawData) external whenNotPaused { require(hasRole(RELAYER_ROLE, _msgSender()), "Not a relayer"); BridgeTxn storage txn = bridgeTxn[withdrawData.bridgeId]; require(!txn.isVerifiedByRelayer, "Bridge already verified"); bytes32 bridgeId_ = keccak256( abi.encodePacked( withdrawData.sender, withdrawData.sourceBridgeAddress, block.chainid, withdrawData.tokenAddress, withdrawData.amount, withdrawData.bridgeIndex ) ); require(withdrawData.bridgeId == bridgeId_, "Invalid bridge id"); require( withdrawData.signatures.length >= 3, "should have 3 or more signatures" ); address[] memory signers = _verifyWithdraw(withdrawData); for (uint256 i = 0; i < signers.length; i++) { require( hasRole(RELAYER_ROLE, signers[i]), "Unauthorized signature" ); } if (txn.sender == address(0)) { txn.bridgeIndex = withdrawData.bridgeIndex; txn.txnType = TxnType.WITHDRAW; txn.sender = withdrawData.sender; txn.chainId = withdrawData.sourceChainId; txn.tokenAddress = withdrawData.tokenAddress; txn.amount = withdrawData.amount; txn.isWithdrawed = false; } else { require( txn.txnType == TxnType.WITHDRAW, "Not a withdrawal transaction" ); } txn.isVerifiedByRelayer = true; emit Bridge( withdrawData.bridgeId, withdrawData.sourceChainId, withdrawData.sender, withdrawData.tokenAddress, withdrawData.amount, signers ); } /// @notice Add a new bridge transaction initiated on another chain. /// @param voteData The data for the bridge transaction. function addBridge(VoteData calldata voteData) external payable whenNotPaused { require( supportedTokens[voteData.tokenAddress].isAvailable, "Token is not supported" ); bytes32 bridgeId_ = keccak256( abi.encodePacked( voteData.sender, voteData.sourceBridgeAddress, block.chainid, voteData.tokenAddress, voteData.amount, voteData.bridgeIndex ) ); require(voteData.bridgeId == bridgeId_, "Invalid bridge id"); require(voteData.sender != _msgSender(), "Cannot vote for self"); BridgeTxn storage txn = bridgeTxn[voteData.bridgeId]; require(txn.sender == address(0), "Bridge already exists"); uint256 rewardMultiple = msg.value / baseVoteFee; require(rewardMultiple > 0, "Insufficient fee"); exchangePool.transfer(msg.value); txn.bridgeIndex = voteData.bridgeIndex; txn.txnType = TxnType.WITHDRAW; txn.sender = voteData.sender; txn.chainId = voteData.sourceChainId; txn.tokenAddress = voteData.tokenAddress; txn.amount = voteData.amount; txn.isWithdrawed = false; txn.isVerifiedByRelayer = false; txn.confirmations.push(_msgSender()); txn.reward[_msgSender()] = rewardMultiple * rewardFee; emit Confirmation( voteData.bridgeId, voteData.sourceChainId, voteData.sender, voteData.tokenAddress, voteData.amount, _msgSender() ); } /// @notice Confirm a previously initiated bridge transaction. /// @param voteData The data for confirming the bridge transaction. function confirmBridge(VoteData calldata voteData) external payable whenNotPaused { bytes32 bridgeId_ = keccak256( abi.encodePacked( voteData.sender, voteData.sourceBridgeAddress, voteData.chainId, voteData.tokenAddress, voteData.amount, voteData.bridgeIndex ) ); require(voteData.bridgeId == bridgeId_, "Invalid bridge id"); require(voteData.sender != _msgSender(), "Cannot vote for self"); BridgeTxn storage txn = bridgeTxn[voteData.bridgeId]; require(txn.sender != address(0), "Bridge doesn't exist"); require(!txn.isWithdrawed, "Bridge already withdrawn"); require(txn.txnType == TxnType.WITHDRAW, "Not a withdrawal type"); require( txn.bridgeIndex == voteData.bridgeIndex && txn.sender == voteData.sender && txn.chainId == voteData.sourceChainId && txn.tokenAddress == voteData.tokenAddress && txn.amount == voteData.amount, "Data verification failed" ); uint256 rewardMultiple = msg.value / baseVoteFee; require(rewardMultiple > 0, "Insufficient fee"); exchangePool.transfer(msg.value); txn.confirmations.push(_msgSender()); txn.reward[_msgSender()] = rewardMultiple * rewardFee; } /// @notice Withdraw tokens from the bridge after all confirmations are received. /// @param bridgeId The ID of the bridge transaction to withdraw from. function withdraw(bytes32 bridgeId) external payable whenNotPaused { BridgeTxn storage txn = bridgeTxn[bridgeId]; require(txn.sender != address(0), "Bridge doesn't exist"); require(!txn.isWithdrawed, "Bridge already withdrawn"); require(txn.txnType == TxnType.WITHDRAW, "Not a withdrawal type"); require(txn.isVerifiedByRelayer, "Bridge not verified"); require( supportedTokens[txn.tokenAddress].platformFee == msg.value, "Not Platform fee" ); if (supportedTokens[txn.tokenAddress].platformFee > 0) { exchangePool.transfer(msg.value); } txn.isWithdrawed = true; if (supportedTokens[txn.tokenAddress].isMintableBurnable) { IDToken(txn.tokenAddress).mint(txn.sender, txn.amount); } else { IERC20(txn.tokenAddress).safeTransfer(txn.sender, txn.amount); } for (uint256 i = 0; i < txn.confirmations.length; i++) { IDToken(rewardToken).mint( txn.confirmations[i], txn.reward[txn.confirmations[i]] ); } emit Withdraw(bridgeId, txn.sender, txn.tokenAddress, txn.amount); } /** * @dev Verifies the withdrawal request by recovering the signers from provided ECDSA signatures. * * @param withdrawData_ The withdrawal data including the bridge ID, sender, chain ID, token address, amount, and signatures. * @return signers An array of addresses representing the verified signers of the withdrawal request. */ function _verifyWithdraw( WithdrawData calldata withdrawData_ ) internal view returns (address[] memory) { bytes32 hash = keccak256( abi.encodePacked( "\\x19\\x01", domainHash, keccak256( abi.encode( keccak256( abi.encodePacked( "BridgeToken(bytes32 bridgeId,address sender,uint256 chainId,address tokenAddress,uint256 amount)" ) ), withdrawData_.bridgeId, withdrawData_.sender, block.chainid, withdrawData_.tokenAddress, withdrawData_.amount ) ) ) ); // Create an array of addresses to store the recovered signers. address[] memory signers = new address[]( withdrawData_.signatures.length ); // Recover signers from the provided ECDSA signatures. for (uint256 i = 0; i < withdrawData_.signatures.length; i++) { bytes memory _signature = withdrawData_.signatures[i]; signers[i] = ECDSA.recover(hash, _signature); } return signers; } } // SPDX-License-Identifier: MIT pragma solidity ^0.8.20; interface IDToken { function name() external view returns (string memory); function totalSupply() external view returns (uint256); function mint(address to, uint256 amount) external; function burnFrom(address account, uint256 amount) external; }
File 2 of 4: VOWToken
// File: contracts/thirdParty/ECDSA.sol // Source: https://github.com/OpenZeppelin/openzeppelin-contracts/blob/master/contracts/cryptography/ECDSA.sol // Line 60 added to original source in accordance with recommendation on accepting signatures with 0/1 for v pragma solidity ^0.6.0; /** * @dev Elliptic Curve Digital Signature Algorithm (ECDSA) operations. * * These functions can be used to verify that a message was signed by the holder * of the private keys of a given address. */ library ECDSA { /** * @dev Returns the address that signed a hashed message (`hash`) with * `signature`. This address can then be used for verification purposes. * * The `ecrecover` EVM opcode allows for malleable (non-unique) signatures: * this function rejects them by requiring the `s` value to be in the lower * half order, and the `v` value to be either 27 or 28. * * IMPORTANT: `hash` _must_ be the result of a hash operation for the * verification to be secure: it is possible to craft signatures that * recover to arbitrary addresses for non-hashed data. A safe way to ensure * this is by receiving a hash of the original message (which may otherwise * be too long), and then calling {toEthSignedMessageHash} on it. */ function recover(bytes32 hash, bytes memory signature) internal pure returns (address) { // Check the signature length if (signature.length != 65) { revert("ECDSA: invalid signature length"); } // Divide the signature in r, s and v variables bytes32 r; bytes32 s; uint8 v; // ecrecover takes the signature parameters, and the only way to get them // currently is to use assembly. // solhint-disable-next-line no-inline-assembly assembly { r := mload(add(signature, 0x20)) s := mload(add(signature, 0x40)) v := byte(0, mload(add(signature, 0x60))) } // EIP-2 still allows signature malleability for ecrecover(). Remove this possibility and make the signature // unique. Appendix F in the Ethereum Yellow paper (https://ethereum.github.io/yellowpaper/paper.pdf), defines // the valid range for s in (281): 0 < s < secp256k1n ÷ 2 + 1, and for v in (282): v ∈ {27, 28}. Most // signatures from current libraries generate a unique signature with an s-value in the lower half order. // // If your library generates malleable signatures, such as s-values in the upper range, calculate a new s-value // with 0xFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFEBAAEDCE6AF48A03BBFD25E8CD0364141 - s1 and flip v from 27 to 28 or // vice versa. If your library also generates signatures with 0/1 for v instead 27/28, add 27 to v to accept // these malleable signatures as well. if (uint256(s) > 0x7FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF5D576E7357A4501DDFE92F46681B20A0) { revert("ECDSA: invalid signature 's' value"); } if (v < 27) v += 27; if (v != 27 && v != 28) { revert("ECDSA: invalid signature 'v' value"); } // If the signature is valid (and not malleable), return the signer address address signer = ecrecover(hash, v, r, s); require(signer != address(0), "ECDSA: invalid signature"); return signer; } /** * @dev Returns an Ethereum Signed Message, created from a `hash`. This * replicates the behavior of the * https://github.com/ethereum/wiki/wiki/JSON-RPC#eth_sign[`eth_sign`] * JSON-RPC method. * * See {recover}. */ function toEthSignedMessageHash(bytes32 hash) internal pure returns (bytes32) { // 32 is the length in bytes of hash, // enforced by the type signature above return keccak256(abi.encodePacked("\x19Ethereum Signed Message:\n32", hash)); } } // File: contracts/interfaces/IERC777.sol pragma solidity 0.6.7; // As defined in https://eips.ethereum.org/EIPS/eip-777 interface IERC777 { event Sent(address indexed operator, address indexed from, address indexed to, uint256 amount, bytes data, bytes operatorData); event Minted(address indexed operator, address indexed to, uint256 amount, bytes data, bytes operatorData); event Burned(address indexed operator, address indexed from, uint256 amount, bytes data, bytes operatorData); event AuthorizedOperator(address indexed operator,address indexed holder); event RevokedOperator(address indexed operator, address indexed holder); function name() external view returns (string memory); function symbol() external view returns (string memory); function totalSupply() external view returns (uint256); function balanceOf(address holder) external view returns (uint256); function granularity() external view returns (uint256); function defaultOperators() external view returns (address[] memory); function isOperatorFor(address operator, address holder) external view returns (bool); function authorizeOperator(address operator) external; function revokeOperator(address operator) external; function send(address to, uint256 amount, bytes calldata data) external; function operatorSend(address from, address to, uint256 amount, bytes calldata data, bytes calldata operatorData) external; function burn(uint256 amount, bytes calldata data) external; function operatorBurn( address from, uint256 amount, bytes calldata data, bytes calldata operatorData) external; } // File: contracts/interfaces/IERC20.sol pragma solidity 0.6.7; // As described in https://eips.ethereum.org/EIPS/eip-20 interface IERC20 { event Transfer(address indexed from, address indexed to, uint256 value); event Approval(address indexed owner, address indexed spender, uint256 value); function name() external view returns (string memory); // optional method - see eip spec function symbol() external view returns (string memory); // optional method - see eip spec function decimals() external view returns (uint8); // optional method - see eip spec function totalSupply() external view returns (uint256); function balanceOf(address owner) external view returns (uint256); function transfer(address to, uint256 value) external returns (bool); function transferFrom(address from, address to, uint256 value) external returns (bool); function approve(address spender, uint256 value) external returns (bool); function allowance(address owner, address spender) external view returns (uint256); } // File: contracts/thirdParty/interfaces/IERC1820Registry.sol // From open https://github.com/OpenZeppelin/openzeppelin-contracts/blob/master/contracts/introspection/IERC1820Registry.sol pragma solidity ^0.6.0; /** * @dev Interface of the global ERC1820 Registry, as defined in the * https://eips.ethereum.org/EIPS/eip-1820[EIP]. Accounts may register * implementers for interfaces in this registry, as well as query support. * * Implementers may be shared by multiple accounts, and can also implement more * than a single interface for each account. Contracts can implement interfaces * for themselves, but externally-owned accounts (EOA) must delegate this to a * contract. * * {IERC165} interfaces can also be queried via the registry. * * For an in-depth explanation and source code analysis, see the EIP text. */ interface IERC1820Registry { /** * @dev Sets `newManager` as the manager for `account`. A manager of an * account is able to set interface implementers for it. * * By default, each account is its own manager. Passing a value of `0x0` in * `newManager` will reset the manager to this initial state. * * Emits a {ManagerChanged} event. * * Requirements: * * - the caller must be the current manager for `account`. */ function setManager(address account, address newManager) external; /** * @dev Returns the manager for `account`. * * See {setManager}. */ function getManager(address account) external view returns (address); /** * @dev Sets the `implementer` contract as ``account``'s implementer for * `interfaceHash`. * * `account` being the zero address is an alias for the caller's address. * The zero address can also be used in `implementer` to remove an old one. * * See {interfaceHash} to learn how these are created. * * Emits an {InterfaceImplementerSet} event. * * Requirements: * * - the caller must be the current manager for `account`. * - `interfaceHash` must not be an {IERC165} interface id (i.e. it must not * end in 28 zeroes). * - `implementer` must implement {IERC1820Implementer} and return true when * queried for support, unless `implementer` is the caller. See * {IERC1820Implementer-canImplementInterfaceForAddress}. */ function setInterfaceImplementer(address account, bytes32 interfaceHash, address implementer) external; /** * @dev Returns the implementer of `interfaceHash` for `account`. If no such * implementer is registered, returns the zero address. * * If `interfaceHash` is an {IERC165} interface id (i.e. it ends with 28 * zeroes), `account` will be queried for support of it. * * `account` being the zero address is an alias for the caller's address. */ function getInterfaceImplementer(address account, bytes32 interfaceHash) external view returns (address); /** * @dev Returns the interface hash for an `interfaceName`, as defined in the * corresponding * https://eips.ethereum.org/EIPS/eip-1820#interface-name[section of the EIP]. */ function interfaceHash(string calldata interfaceName) external pure returns (bytes32); /** * @notice Updates the cache with whether the contract implements an ERC165 interface or not. * @param account Address of the contract for which to update the cache. * @param interfaceId ERC165 interface for which to update the cache. */ function updateERC165Cache(address account, bytes4 interfaceId) external; /** * @notice Checks whether a contract implements an ERC165 interface or not. * If the result is not cached a direct lookup on the contract address is performed. * If the result is not cached or the cached value is out-of-date, the cache MUST be updated manually by calling * {updateERC165Cache} with the contract address. * @param account Address of the contract to check. * @param interfaceId ERC165 interface to check. * @return True if `account` implements `interfaceId`, false otherwise. */ function implementsERC165Interface(address account, bytes4 interfaceId) external view returns (bool); /** * @notice Checks whether a contract implements an ERC165 interface or not without using nor updating the cache. * @param account Address of the contract to check. * @param interfaceId ERC165 interface to check. * @return True if `account` implements `interfaceId`, false otherwise. */ function implementsERC165InterfaceNoCache(address account, bytes4 interfaceId) external view returns (bool); event InterfaceImplementerSet(address indexed account, bytes32 indexed interfaceHash, address indexed implementer); event ManagerChanged(address indexed account, address indexed newManager); } // File: contracts/interfaces/IERC777Sender.sol pragma solidity 0.6.7; // As defined in the 'ERC777TokensSender And The tokensToSend Hook' section of https://eips.ethereum.org/EIPS/eip-777 interface IERC777Sender { function tokensToSend(address operator, address from, address to, uint256 amount, bytes calldata data, bytes calldata operatorData) external; } // File: contracts/interfaces/IERC777Recipient.sol pragma solidity 0.6.7; // As defined in the 'ERC777TokensRecipient And The tokensReceived Hook' section of https://eips.ethereum.org/EIPS/eip-777 interface IERC777Recipient { function tokensReceived(address operator, address from, address to, uint256 amount, bytes calldata data, bytes calldata operatorData) external; } // File: contracts/thirdParty/SafeMath.sol // Source: https://github.com/OpenZeppelin/openzeppelin-contracts/blob/master/contracts/math/SafeMath.sol pragma solidity ^0.6.0; /** * @dev Wrappers over Solidity's arithmetic operations with added overflow * checks. * * Arithmetic operations in Solidity wrap on overflow. This can easily result * in bugs, because programmers usually assume that an overflow raises an * error, which is the standard behavior in high level programming languages. * `SafeMath` restores this intuition by reverting the transaction when an * operation overflows. * * Using this library instead of the unchecked operations eliminates an entire * class of bugs, so it's recommended to use it always. */ library SafeMath { /** * @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) { uint256 c = a + b; require(c >= a, "SafeMath: addition overflow"); return c; } /** * @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 sub(a, b, "SafeMath: subtraction overflow"); } /** * @dev Returns the subtraction of two unsigned integers, reverting with custom message on * overflow (when the result is negative). * * Counterpart to Solidity's `-` operator. * * Requirements: * - Subtraction cannot overflow. * * _Available since v2.4.0._ */ function sub(uint256 a, uint256 b, string memory errorMessage) internal pure returns (uint256) { require(b <= a, errorMessage); uint256 c = a - b; return c; } /** * @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) { // 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 0; } uint256 c = a * b; require(c / a == b, "SafeMath: multiplication overflow"); return c; } /** * @dev Returns the integer division of two unsigned integers. Reverts 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) internal pure returns (uint256) { return div(a, b, "SafeMath: division by zero"); } /** * @dev Returns the integer division of two unsigned integers. Reverts 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. * * _Available since v2.4.0._ */ function div(uint256 a, uint256 b, string memory errorMessage) internal pure returns (uint256) { // Solidity only automatically asserts when dividing by 0 require(b > 0, errorMessage); uint256 c = a / b; // assert(a == b * c + a % b); // There is no case in which this doesn't hold return c; } /** * @dev Returns the remainder of dividing two unsigned integers. (unsigned integer modulo), * Reverts 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 mod(a, b, "SafeMath: modulo by zero"); } /** * @dev Returns the remainder of dividing two unsigned integers. (unsigned integer modulo), * Reverts with custom message 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. * * _Available since v2.4.0._ */ function mod(uint256 a, uint256 b, string memory errorMessage) internal pure returns (uint256) { require(b != 0, errorMessage); return a % b; } } // File: contracts/libraries/LToken.sol pragma solidity 0.6.7; struct TokenState { uint256 totalSupply; mapping(address => uint256) balances; mapping(address => mapping(address => uint256)) approvals; mapping(address => mapping(address => bool)) authorizedOperators; address[] defaultOperators; mapping(address => bool) defaultOperatorIsRevoked; mapping(address => bool) minters; } library LToken { using SafeMath for uint256; event Transfer(address indexed from, address indexed to, uint256 value); event Approval(address indexed owner, address indexed spender, uint256 value); event Sent(address indexed operator, address indexed from, address indexed to, uint256 amount, bytes data, bytes operatorData); event Minted(address indexed operator, address indexed to, uint256 amount, bytes data, bytes operatorData); event Burned(address indexed operator, address indexed from, uint256 amount, bytes data, bytes operatorData); event AuthorizedOperator(address indexed operator, address indexed holder); event RevokedOperator(address indexed operator, address indexed holder); // Universal address as defined in Registry Contract Address section of https://eips.ethereum.org/EIPS/eip-1820 IERC1820Registry constant internal ERC1820_REGISTRY = IERC1820Registry(0x1820a4B7618BdE71Dce8cdc73aAB6C95905faD24); // precalculated hashes - see https://github.com/ethereum/solidity/issues/4024 // keccak256("ERC777TokensSender") bytes32 constant internal ERC777_TOKENS_SENDER_HASH = 0x29ddb589b1fb5fc7cf394961c1adf5f8c6454761adf795e67fe149f658abe895; // keccak256("ERC777TokensRecipient") bytes32 constant internal ERC777_TOKENS_RECIPIENT_HASH = 0xb281fc8c12954d22544db45de3159a39272895b169a852b314f9cc762e44c53b; modifier checkSenderNotOperator(address _operator) { require(_operator != msg.sender, "Cannot be operator for self"); _; } function initState(TokenState storage _tokenState, uint8 _decimals, uint256 _initialSupply) external { _tokenState.defaultOperators.push(address(this)); _tokenState.totalSupply = _initialSupply.mul(10**uint256(_decimals)); _tokenState.balances[msg.sender] = _tokenState.totalSupply; } function transferFrom(TokenState storage _tokenState, address _from, address _to, uint256 _value) external { _tokenState.approvals[_from][msg.sender] = _tokenState.approvals[_from][msg.sender].sub(_value, "Amount not approved"); doSend(_tokenState, msg.sender, _from, _to, _value, "", "", false); } function approve(TokenState storage _tokenState, address _spender, uint256 _value) external { require(_spender != address(0), "Cannot approve to zero address"); _tokenState.approvals[msg.sender][_spender] = _value; emit Approval(msg.sender, _spender, _value); } function authorizeOperator(TokenState storage _tokenState, address _operator) checkSenderNotOperator(_operator) external { if (_operator == address(this)) _tokenState.defaultOperatorIsRevoked[msg.sender] = false; else _tokenState.authorizedOperators[_operator][msg.sender] = true; emit AuthorizedOperator(_operator, msg.sender); } function revokeOperator(TokenState storage _tokenState, address _operator) checkSenderNotOperator(_operator) external { if (_operator == address(this)) _tokenState.defaultOperatorIsRevoked[msg.sender] = true; else _tokenState.authorizedOperators[_operator][msg.sender] = false; emit RevokedOperator(_operator, msg.sender); } function authorizeMinter(TokenState storage _tokenState, address _minter) external { _tokenState.minters[_minter] = true; } function revokeMinter(TokenState storage _tokenState, address _minter) external { _tokenState.minters[_minter] = false; } function doMint(TokenState storage _tokenState, address _to, uint256 _amount) external { assert(_to != address(0)); _tokenState.totalSupply = _tokenState.totalSupply.add(_amount); _tokenState.balances[_to] = _tokenState.balances[_to].add(_amount); // From ERC777: The token contract MUST call the tokensReceived hook after updating the state. receiveHook(address(this), address(0), _to, _amount, "", "", true); emit Minted(address(this), _to, _amount, "", ""); emit Transfer(address(0), _to, _amount); } function doBurn(TokenState storage _tokenState, address _operator, address _from, uint256 _amount, bytes calldata _data, bytes calldata _operatorData) external { assert(_from != address(0)); // From ERC777: The token contract MUST call the tokensToSend hook before updating the state. sendHook(_operator, _from, address(0), _amount, _data, _operatorData); _tokenState.balances[_from] = _tokenState.balances[_from].sub(_amount, "Cannot burn more than balance"); _tokenState.totalSupply = _tokenState.totalSupply.sub(_amount); emit Burned(_operator, _from, _amount, _data, _operatorData); emit Transfer(_from, address(0), _amount); } function doSend(TokenState storage _tokenState, address _operator, address _from, address _to, uint256 _amount, bytes memory _data, bytes memory _operatorData, bool _enforceERC777) public { assert(_from != address(0)); require(_to != address(0), "Cannot send funds to 0 address"); // From ERC777: The token contract MUST call the tokensToSend hook before updating the state. sendHook(_operator, _from, _to, _amount, _data, _operatorData); _tokenState.balances[_from] = _tokenState.balances[_from].sub(_amount, "Amount exceeds available funds"); _tokenState.balances[_to] = _tokenState.balances[_to].add(_amount); emit Sent(_operator, _from, _to, _amount, _data, _operatorData); emit Transfer(_from, _to, _amount); // From ERC777: The token contract MUST call the tokensReceived hook after updating the state. receiveHook(_operator, _from, _to, _amount, _data, _operatorData, _enforceERC777); } function receiveHook(address _operator, address _from, address _to, uint256 _amount, bytes memory _data, bytes memory _operatorData, bool _enforceERC777) public { address implementer = ERC1820_REGISTRY.getInterfaceImplementer(_to, ERC777_TOKENS_RECIPIENT_HASH); if (implementer != address(0)) IERC777Recipient(implementer).tokensReceived(_operator, _from, _to, _amount, _data, _operatorData); else if (_enforceERC777) require(!isContract(_to), "Must be registered with ERC1820"); } function sendHook(address _operator, address _from, address _to, uint256 _amount, bytes memory _data, bytes memory _operatorData) public { address implementer = ERC1820_REGISTRY.getInterfaceImplementer(_from, ERC777_TOKENS_SENDER_HASH); if (implementer != address(0)) IERC777Sender(implementer).tokensToSend(_operator, _from, _to, _amount, _data, _operatorData); } function isContract(address _account) private view returns (bool isContract_) { uint256 size; assembly { size := extcodesize(_account) } isContract_ = size != 0; } } // File: contracts/Token.sol pragma solidity 0.6.7; /** * Implements ERC777 with ERC20 as defined in https://eips.ethereum.org/EIPS/eip-777, with minting support. * NOTE: Minting is internal only: derive from this contract according to usage. */ contract Token is IERC777, IERC20 { string private tokenName; string private tokenSymbol; uint8 constant private tokenDecimals = 18; uint256 constant private tokenGranularity = 1; TokenState public tokenState; // Universal address as defined in Registry Contract Address section of https://eips.ethereum.org/EIPS/eip-1820 IERC1820Registry constant internal ERC1820_REGISTRY = IERC1820Registry(0x1820a4B7618BdE71Dce8cdc73aAB6C95905faD24); // keccak256("ERC777Token") bytes32 constant internal ERC777_TOKEN_HASH = 0xac7fbab5f54a3ca8194167523c6753bfeb96a445279294b6125b68cce2177054; // keccak256("ERC20Token") bytes32 constant internal ERC20_TOKEN_HASH = 0xaea199e31a596269b42cdafd93407f14436db6e4cad65417994c2eb37381e05a; event AuthorizedMinter(address minter); event RevokedMinter(address minter); constructor(string memory _name, string memory _symbol, uint256 _initialSupply) internal { require(bytes(_name).length != 0, "Needs a name"); require(bytes(_symbol).length != 0, "Needs a symbol"); tokenName = _name; tokenSymbol = _symbol; LToken.initState(tokenState, tokenDecimals, _initialSupply); ERC1820_REGISTRY.setInterfaceImplementer(address(this), ERC777_TOKEN_HASH, address(this)); ERC1820_REGISTRY.setInterfaceImplementer(address(this), ERC20_TOKEN_HASH, address(this)); } modifier onlyOperator(address _holder) { require(isOperatorFor(msg.sender, _holder), "Not an operator"); _; } modifier onlyMinter { require(tokenState.minters[msg.sender], "onlyMinter"); _; } function name() external view override(IERC777, IERC20) returns (string memory name_) { name_ = tokenName; } function symbol() external view override(IERC777, IERC20) returns (string memory symbol_) { symbol_ = tokenSymbol; } function decimals() external view override returns (uint8 decimals_) { decimals_ = tokenDecimals; } function granularity() external view override returns (uint256 granularity_) { granularity_ = tokenGranularity; } function balanceOf(address _holder) external override(IERC777, IERC20) view returns (uint256 balance_) { balance_ = tokenState.balances[_holder]; } function transfer(address _to, uint256 _value) external override returns (bool success_) { doSend(msg.sender, msg.sender, _to, _value, "", "", false); success_ = true; } function transferFrom(address _from, address _to, uint256 _value) external override returns (bool success_) { LToken.transferFrom(tokenState, _from, _to, _value); success_ = true; } function approve(address _spender, uint256 _value) external override returns (bool success_) { LToken.approve(tokenState, _spender, _value); success_ = true; } function allowance(address _holder, address _spender) external view override returns (uint256 remaining_) { remaining_ = tokenState.approvals[_holder][_spender]; } function defaultOperators() external view override returns (address[] memory) { return tokenState.defaultOperators; } function authorizeOperator(address _operator) external override { LToken.authorizeOperator(tokenState, _operator); } function revokeOperator(address _operator) external override { LToken.revokeOperator(tokenState, _operator); } function send(address _to, uint256 _amount, bytes calldata _data) external override { doSend(msg.sender, msg.sender, _to, _amount, _data, "", true); } function operatorSend(address _from, address _to, uint256 _amount, bytes calldata _data, bytes calldata _operatorData) external override onlyOperator(_from) { doSend(msg.sender, _from, _to, _amount, _data, _operatorData, true); } function burn(uint256 _amount, bytes calldata _data) external override { doBurn(msg.sender, msg.sender, _amount, _data, ""); } function operatorBurn(address _from, uint256 _amount, bytes calldata _data, bytes calldata _operatorData) external override onlyOperator(_from) { doBurn(msg.sender, _from, _amount, _data, _operatorData); } function mint(address _to, uint256 _amount) external onlyMinter { LToken.doMint(tokenState, _to, _amount); } function totalSupply() external view override(IERC777, IERC20) returns (uint256 totalSupply_) { totalSupply_ = tokenState.totalSupply; } function isOperatorFor(address _operator, address _holder) public view override returns (bool isOperatorFor_) { isOperatorFor_ = (_operator == _holder || tokenState.authorizedOperators[_operator][_holder] || _operator == address(this) && !tokenState.defaultOperatorIsRevoked[_holder]); } function doSend(address _operator, address _from, address _to, uint256 _amount, bytes memory _data, bytes memory _operatorData, bool _enforceERC777) internal virtual { LToken.doSend(tokenState, _operator, _from, _to, _amount, _data, _operatorData, _enforceERC777); } function doBurn(address _operator, address _from, uint256 _amount, bytes memory _data, bytes memory _operatorData) internal { LToken.doBurn(tokenState, _operator, _from, _amount, _data, _operatorData); } function authorizeMinter(address _minter) internal { LToken.authorizeMinter(tokenState, _minter); emit AuthorizedMinter(_minter); } function revokeMinter(address _minter) internal { LToken.revokeMinter(tokenState, _minter); emit RevokedMinter(_minter); } } // File: contracts/Owned.sol pragma solidity 0.6.7; contract Owned { address public owner = msg.sender; event LogOwnershipTransferred(address indexed owner, address indexed newOwner); modifier onlyOwner { require(msg.sender == owner, "Sender must be owner"); _; } function setOwner(address _owner) external onlyOwner { require(_owner != address(0), "Owner cannot be zero address"); emit LogOwnershipTransferred(owner, _owner); owner = _owner; } } // File: contracts/VOWToken.sol pragma solidity 0.6.7; /** * ERC777/20 contract which also: * - is owned * - supports proxying of own tokens (only if signed correctly) * - supports partner contracts, keyed by hash * - supports minting (only by owner approved contracts) * - has a USD price */ contract VOWToken is Token, IERC777Recipient, Owned { mapping (bytes32 => bool) public proxyProofs; uint256[2] public usdRate; address public usdRateSetter; mapping(bytes32 => address payable) public partnerContracts; // precalculated hash - see https://github.com/ethereum/solidity/issues/4024 // keccak256("ERC777TokensRecipient") bytes32 constant internal ERC777_TOKENS_RECIPIENT_HASH = 0xb281fc8c12954d22544db45de3159a39272895b169a852b314f9cc762e44c53b; event LogUSDRateSetterSet(address indexed usdRateSetter); event LogUSDRateSet(uint256 numTokens, uint256 numUSD); event LogProxiedTokens(address indexed from, address indexed to, uint256 amount, bytes data, uint256 nonce, bytes proof); event LogPartnerContractSet(bytes32 indexed keyHash, address indexed partnerContract); event LogMintPermissionSet(address indexed contractAddress, bool canMint); constructor(string memory _name, string memory _symbol, uint256 _initialSupply, uint256[2] memory _initialUSDRate) public Token(_name, _symbol, _initialSupply) { doSetUSDRate(_initialUSDRate[0], _initialUSDRate[1]); ERC1820_REGISTRY.setInterfaceImplementer(address(this), ERC777_TOKENS_RECIPIENT_HASH, address(this)); } modifier onlyUSDRateSetter() { require(msg.sender == usdRateSetter, "onlyUSDRateSetter"); _; } modifier onlyOwnTokens { require(msg.sender == address(this), "onlyOwnTokens"); _; } modifier addressNotNull(address _address) { require(_address != address(0), "Address cannot be null"); _; } function tokensReceived(address /* _operator */, address /* _from */, address /* _to */, uint256 _amount, bytes calldata _data, bytes calldata /* _operatorData */) external override onlyOwnTokens { (address from, address to, uint256 amount, bytes memory data, uint256 nonce, bytes memory proof) = abi.decode(_data, (address, address, uint256, bytes, uint256, bytes)); checkProxying(from, to, amount, data, nonce, proof); if (_amount != 0) this.send(from, _amount, ""); this.operatorSend(from, to, amount, data, _data); emit LogProxiedTokens(from, to, amount, data, nonce, proof); } function setPartnerContract(bytes32 _keyHash, address payable _partnerContract) external onlyOwner addressNotNull(_partnerContract) { require(_keyHash != bytes32(0), "Missing key hash"); partnerContracts[_keyHash] = _partnerContract; emit LogPartnerContractSet(_keyHash, _partnerContract); } function setUSDRateSetter(address _usdRateSetter) external onlyOwner addressNotNull(_usdRateSetter) { usdRateSetter = _usdRateSetter; emit LogUSDRateSetterSet(_usdRateSetter); } function setUSDRate(uint256 _numTokens, uint256 _numUSD) external onlyUSDRateSetter { doSetUSDRate(_numTokens, _numUSD); emit LogUSDRateSet(_numTokens, _numUSD); } function setMintPermission(address _contract, bool _canMint) external onlyOwner addressNotNull(_contract) { if (_canMint) authorizeMinter(_contract); else revokeMinter(_contract); emit LogMintPermissionSet(_contract, _canMint); } function doSetUSDRate(uint256 _numTokens, uint256 _numUSD) private { require(_numTokens != 0, "numTokens cannot be zero"); require(_numUSD != 0, "numUSD cannot be zero"); usdRate = [_numTokens, _numUSD]; } function checkProxying(address _from, address _to, uint256 _amount, bytes memory _data, uint256 _nonce, bytes memory _proof) private { require(!proxyProofs[keccak256(_proof)], "Proxy proof not unique"); proxyProofs[keccak256(_proof)] = true; bytes32 hash = keccak256(abi.encodePacked(address(this), _from, _to, _amount, _data, _nonce)); address signer = ECDSA.recover(ECDSA.toEthSignedMessageHash(hash), _proof); require(signer == _from, "Bad signer"); } }
File 3 of 4: LToken
// File: contracts\thirdParty\interfaces\IERC1820Registry.sol // From open https://github.com/OpenZeppelin/openzeppelin-contracts/blob/master/contracts/introspection/IERC1820Registry.sol pragma solidity ^0.6.0; /** * @dev Interface of the global ERC1820 Registry, as defined in the * https://eips.ethereum.org/EIPS/eip-1820[EIP]. Accounts may register * implementers for interfaces in this registry, as well as query support. * * Implementers may be shared by multiple accounts, and can also implement more * than a single interface for each account. Contracts can implement interfaces * for themselves, but externally-owned accounts (EOA) must delegate this to a * contract. * * {IERC165} interfaces can also be queried via the registry. * * For an in-depth explanation and source code analysis, see the EIP text. */ interface IERC1820Registry { /** * @dev Sets `newManager` as the manager for `account`. A manager of an * account is able to set interface implementers for it. * * By default, each account is its own manager. Passing a value of `0x0` in * `newManager` will reset the manager to this initial state. * * Emits a {ManagerChanged} event. * * Requirements: * * - the caller must be the current manager for `account`. */ function setManager(address account, address newManager) external; /** * @dev Returns the manager for `account`. * * See {setManager}. */ function getManager(address account) external view returns (address); /** * @dev Sets the `implementer` contract as ``account``'s implementer for * `interfaceHash`. * * `account` being the zero address is an alias for the caller's address. * The zero address can also be used in `implementer` to remove an old one. * * See {interfaceHash} to learn how these are created. * * Emits an {InterfaceImplementerSet} event. * * Requirements: * * - the caller must be the current manager for `account`. * - `interfaceHash` must not be an {IERC165} interface id (i.e. it must not * end in 28 zeroes). * - `implementer` must implement {IERC1820Implementer} and return true when * queried for support, unless `implementer` is the caller. See * {IERC1820Implementer-canImplementInterfaceForAddress}. */ function setInterfaceImplementer(address account, bytes32 interfaceHash, address implementer) external; /** * @dev Returns the implementer of `interfaceHash` for `account`. If no such * implementer is registered, returns the zero address. * * If `interfaceHash` is an {IERC165} interface id (i.e. it ends with 28 * zeroes), `account` will be queried for support of it. * * `account` being the zero address is an alias for the caller's address. */ function getInterfaceImplementer(address account, bytes32 interfaceHash) external view returns (address); /** * @dev Returns the interface hash for an `interfaceName`, as defined in the * corresponding * https://eips.ethereum.org/EIPS/eip-1820#interface-name[section of the EIP]. */ function interfaceHash(string calldata interfaceName) external pure returns (bytes32); /** * @notice Updates the cache with whether the contract implements an ERC165 interface or not. * @param account Address of the contract for which to update the cache. * @param interfaceId ERC165 interface for which to update the cache. */ function updateERC165Cache(address account, bytes4 interfaceId) external; /** * @notice Checks whether a contract implements an ERC165 interface or not. * If the result is not cached a direct lookup on the contract address is performed. * If the result is not cached or the cached value is out-of-date, the cache MUST be updated manually by calling * {updateERC165Cache} with the contract address. * @param account Address of the contract to check. * @param interfaceId ERC165 interface to check. * @return True if `account` implements `interfaceId`, false otherwise. */ function implementsERC165Interface(address account, bytes4 interfaceId) external view returns (bool); /** * @notice Checks whether a contract implements an ERC165 interface or not without using nor updating the cache. * @param account Address of the contract to check. * @param interfaceId ERC165 interface to check. * @return True if `account` implements `interfaceId`, false otherwise. */ function implementsERC165InterfaceNoCache(address account, bytes4 interfaceId) external view returns (bool); event InterfaceImplementerSet(address indexed account, bytes32 indexed interfaceHash, address indexed implementer); event ManagerChanged(address indexed account, address indexed newManager); } // File: contracts\interfaces\IERC777Sender.sol pragma solidity 0.6.7; // As defined in the 'ERC777TokensSender And The tokensToSend Hook' section of https://eips.ethereum.org/EIPS/eip-777 interface IERC777Sender { function tokensToSend(address operator, address from, address to, uint256 amount, bytes calldata data, bytes calldata operatorData) external; } // File: contracts\interfaces\IERC777Recipient.sol pragma solidity 0.6.7; // As defined in the 'ERC777TokensRecipient And The tokensReceived Hook' section of https://eips.ethereum.org/EIPS/eip-777 interface IERC777Recipient { function tokensReceived(address operator, address from, address to, uint256 amount, bytes calldata data, bytes calldata operatorData) external; } // File: contracts\thirdParty\SafeMath.sol // Source: https://github.com/OpenZeppelin/openzeppelin-contracts/blob/master/contracts/math/SafeMath.sol pragma solidity ^0.6.0; /** * @dev Wrappers over Solidity's arithmetic operations with added overflow * checks. * * Arithmetic operations in Solidity wrap on overflow. This can easily result * in bugs, because programmers usually assume that an overflow raises an * error, which is the standard behavior in high level programming languages. * `SafeMath` restores this intuition by reverting the transaction when an * operation overflows. * * Using this library instead of the unchecked operations eliminates an entire * class of bugs, so it's recommended to use it always. */ library SafeMath { /** * @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) { uint256 c = a + b; require(c >= a, "SafeMath: addition overflow"); return c; } /** * @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 sub(a, b, "SafeMath: subtraction overflow"); } /** * @dev Returns the subtraction of two unsigned integers, reverting with custom message on * overflow (when the result is negative). * * Counterpart to Solidity's `-` operator. * * Requirements: * - Subtraction cannot overflow. * * _Available since v2.4.0._ */ function sub(uint256 a, uint256 b, string memory errorMessage) internal pure returns (uint256) { require(b <= a, errorMessage); uint256 c = a - b; return c; } /** * @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) { // 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 0; } uint256 c = a * b; require(c / a == b, "SafeMath: multiplication overflow"); return c; } /** * @dev Returns the integer division of two unsigned integers. Reverts 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) internal pure returns (uint256) { return div(a, b, "SafeMath: division by zero"); } /** * @dev Returns the integer division of two unsigned integers. Reverts 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. * * _Available since v2.4.0._ */ function div(uint256 a, uint256 b, string memory errorMessage) internal pure returns (uint256) { // Solidity only automatically asserts when dividing by 0 require(b > 0, errorMessage); uint256 c = a / b; // assert(a == b * c + a % b); // There is no case in which this doesn't hold return c; } /** * @dev Returns the remainder of dividing two unsigned integers. (unsigned integer modulo), * Reverts 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 mod(a, b, "SafeMath: modulo by zero"); } /** * @dev Returns the remainder of dividing two unsigned integers. (unsigned integer modulo), * Reverts with custom message 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. * * _Available since v2.4.0._ */ function mod(uint256 a, uint256 b, string memory errorMessage) internal pure returns (uint256) { require(b != 0, errorMessage); return a % b; } } // File: contracts\libraries\LToken.sol pragma solidity 0.6.7; struct TokenState { uint256 totalSupply; mapping(address => uint256) balances; mapping(address => mapping(address => uint256)) approvals; mapping(address => mapping(address => bool)) authorizedOperators; address[] defaultOperators; mapping(address => bool) defaultOperatorIsRevoked; mapping(address => bool) minters; } library LToken { using SafeMath for uint256; event Transfer(address indexed from, address indexed to, uint256 value); event Approval(address indexed owner, address indexed spender, uint256 value); event Sent(address indexed operator, address indexed from, address indexed to, uint256 amount, bytes data, bytes operatorData); event Minted(address indexed operator, address indexed to, uint256 amount, bytes data, bytes operatorData); event Burned(address indexed operator, address indexed from, uint256 amount, bytes data, bytes operatorData); event AuthorizedOperator(address indexed operator, address indexed holder); event RevokedOperator(address indexed operator, address indexed holder); // Universal address as defined in Registry Contract Address section of https://eips.ethereum.org/EIPS/eip-1820 IERC1820Registry constant internal ERC1820_REGISTRY = IERC1820Registry(0x1820a4B7618BdE71Dce8cdc73aAB6C95905faD24); // precalculated hashes - see https://github.com/ethereum/solidity/issues/4024 // keccak256("ERC777TokensSender") bytes32 constant internal ERC777_TOKENS_SENDER_HASH = 0x29ddb589b1fb5fc7cf394961c1adf5f8c6454761adf795e67fe149f658abe895; // keccak256("ERC777TokensRecipient") bytes32 constant internal ERC777_TOKENS_RECIPIENT_HASH = 0xb281fc8c12954d22544db45de3159a39272895b169a852b314f9cc762e44c53b; modifier checkSenderNotOperator(address _operator) { require(_operator != msg.sender, "Cannot be operator for self"); _; } function initState(TokenState storage _tokenState, uint8 _decimals, uint256 _initialSupply) external { _tokenState.defaultOperators.push(address(this)); _tokenState.totalSupply = _initialSupply.mul(10**uint256(_decimals)); _tokenState.balances[msg.sender] = _tokenState.totalSupply; } function transferFrom(TokenState storage _tokenState, address _from, address _to, uint256 _value) external { _tokenState.approvals[_from][msg.sender] = _tokenState.approvals[_from][msg.sender].sub(_value, "Amount not approved"); doSend(_tokenState, msg.sender, _from, _to, _value, "", "", false); } function approve(TokenState storage _tokenState, address _spender, uint256 _value) external { require(_spender != address(0), "Cannot approve to zero address"); _tokenState.approvals[msg.sender][_spender] = _value; emit Approval(msg.sender, _spender, _value); } function authorizeOperator(TokenState storage _tokenState, address _operator) checkSenderNotOperator(_operator) external { if (_operator == address(this)) _tokenState.defaultOperatorIsRevoked[msg.sender] = false; else _tokenState.authorizedOperators[_operator][msg.sender] = true; emit AuthorizedOperator(_operator, msg.sender); } function revokeOperator(TokenState storage _tokenState, address _operator) checkSenderNotOperator(_operator) external { if (_operator == address(this)) _tokenState.defaultOperatorIsRevoked[msg.sender] = true; else _tokenState.authorizedOperators[_operator][msg.sender] = false; emit RevokedOperator(_operator, msg.sender); } function authorizeMinter(TokenState storage _tokenState, address _minter) external { _tokenState.minters[_minter] = true; } function revokeMinter(TokenState storage _tokenState, address _minter) external { _tokenState.minters[_minter] = false; } function doMint(TokenState storage _tokenState, address _to, uint256 _amount) external { assert(_to != address(0)); _tokenState.totalSupply = _tokenState.totalSupply.add(_amount); _tokenState.balances[_to] = _tokenState.balances[_to].add(_amount); // From ERC777: The token contract MUST call the tokensReceived hook after updating the state. receiveHook(address(this), address(0), _to, _amount, "", "", true); emit Minted(address(this), _to, _amount, "", ""); emit Transfer(address(0), _to, _amount); } function doBurn(TokenState storage _tokenState, address _operator, address _from, uint256 _amount, bytes calldata _data, bytes calldata _operatorData) external { assert(_from != address(0)); // From ERC777: The token contract MUST call the tokensToSend hook before updating the state. sendHook(_operator, _from, address(0), _amount, _data, _operatorData); _tokenState.balances[_from] = _tokenState.balances[_from].sub(_amount, "Cannot burn more than balance"); _tokenState.totalSupply = _tokenState.totalSupply.sub(_amount); emit Burned(_operator, _from, _amount, _data, _operatorData); emit Transfer(_from, address(0), _amount); } function doSend(TokenState storage _tokenState, address _operator, address _from, address _to, uint256 _amount, bytes memory _data, bytes memory _operatorData, bool _enforceERC777) public { assert(_from != address(0)); require(_to != address(0), "Cannot send funds to 0 address"); // From ERC777: The token contract MUST call the tokensToSend hook before updating the state. sendHook(_operator, _from, _to, _amount, _data, _operatorData); _tokenState.balances[_from] = _tokenState.balances[_from].sub(_amount, "Amount exceeds available funds"); _tokenState.balances[_to] = _tokenState.balances[_to].add(_amount); emit Sent(_operator, _from, _to, _amount, _data, _operatorData); emit Transfer(_from, _to, _amount); // From ERC777: The token contract MUST call the tokensReceived hook after updating the state. receiveHook(_operator, _from, _to, _amount, _data, _operatorData, _enforceERC777); } function receiveHook(address _operator, address _from, address _to, uint256 _amount, bytes memory _data, bytes memory _operatorData, bool _enforceERC777) public { address implementer = ERC1820_REGISTRY.getInterfaceImplementer(_to, ERC777_TOKENS_RECIPIENT_HASH); if (implementer != address(0)) IERC777Recipient(implementer).tokensReceived(_operator, _from, _to, _amount, _data, _operatorData); else if (_enforceERC777) require(!isContract(_to), "Must be registered with ERC1820"); } function sendHook(address _operator, address _from, address _to, uint256 _amount, bytes memory _data, bytes memory _operatorData) public { address implementer = ERC1820_REGISTRY.getInterfaceImplementer(_from, ERC777_TOKENS_SENDER_HASH); if (implementer != address(0)) IERC777Sender(implementer).tokensToSend(_operator, _from, _to, _amount, _data, _operatorData); } function isContract(address _account) private view returns (bool isContract_) { uint256 size; assembly { size := extcodesize(_account) } isContract_ = size != 0; } }
File 4 of 4: ERC1820Registry
/* ERC1820 Pseudo-introspection Registry Contract * This standard defines a universal registry smart contract where any address (contract or regular account) can * register which interface it supports and which smart contract is responsible for its implementation. * * Written in 2019 by Jordi Baylina and Jacques Dafflon * * To the extent possible under law, the author(s) have dedicated all copyright and related and neighboring rights to * this software to the public domain worldwide. This software is distributed without any warranty. * * You should have received a copy of the CC0 Public Domain Dedication along with this software. If not, see * <http://creativecommons.org/publicdomain/zero/1.0/>. * * ███████╗██████╗ ██████╗ ██╗ █████╗ ██████╗ ██████╗ * ██╔════╝██╔══██╗██╔════╝███║██╔══██╗╚════██╗██╔═████╗ * █████╗ ██████╔╝██║ ╚██║╚█████╔╝ █████╔╝██║██╔██║ * ██╔══╝ ██╔══██╗██║ ██║██╔══██╗██╔═══╝ ████╔╝██║ * ███████╗██║ ██║╚██████╗ ██║╚█████╔╝███████╗╚██████╔╝ * ╚══════╝╚═╝ ╚═╝ ╚═════╝ ╚═╝ ╚════╝ ╚══════╝ ╚═════╝ * * ██████╗ ███████╗ ██████╗ ██╗███████╗████████╗██████╗ ██╗ ██╗ * ██╔══██╗██╔════╝██╔════╝ ██║██╔════╝╚══██╔══╝██╔══██╗╚██╗ ██╔╝ * ██████╔╝█████╗ ██║ ███╗██║███████╗ ██║ ██████╔╝ ╚████╔╝ * ██╔══██╗██╔══╝ ██║ ██║██║╚════██║ ██║ ██╔══██╗ ╚██╔╝ * ██║ ██║███████╗╚██████╔╝██║███████║ ██║ ██║ ██║ ██║ * ╚═╝ ╚═╝╚══════╝ ╚═════╝ ╚═╝╚══════╝ ╚═╝ ╚═╝ ╚═╝ ╚═╝ * */ pragma solidity 0.5.3; // IV is value needed to have a vanity address starting with '0x1820'. // IV: 53759 /// @dev The interface a contract MUST implement if it is the implementer of /// some (other) interface for any address other than itself. interface ERC1820ImplementerInterface { /// @notice Indicates whether the contract implements the interface 'interfaceHash' for the address 'addr' or not. /// @param interfaceHash keccak256 hash of the name of the interface /// @param addr Address for which the contract will implement the interface /// @return ERC1820_ACCEPT_MAGIC only if the contract implements 'interfaceHash' for the address 'addr'. function canImplementInterfaceForAddress(bytes32 interfaceHash, address addr) external view returns(bytes32); } /// @title ERC1820 Pseudo-introspection Registry Contract /// @author Jordi Baylina and Jacques Dafflon /// @notice This contract is the official implementation of the ERC1820 Registry. /// @notice For more details, see https://eips.ethereum.org/EIPS/eip-1820 contract ERC1820Registry { /// @notice ERC165 Invalid ID. bytes4 constant internal INVALID_ID = 0xffffffff; /// @notice Method ID for the ERC165 supportsInterface method (= `bytes4(keccak256('supportsInterface(bytes4)'))`). bytes4 constant internal ERC165ID = 0x01ffc9a7; /// @notice Magic value which is returned if a contract implements an interface on behalf of some other address. bytes32 constant internal ERC1820_ACCEPT_MAGIC = keccak256(abi.encodePacked("ERC1820_ACCEPT_MAGIC")); /// @notice mapping from addresses and interface hashes to their implementers. mapping(address => mapping(bytes32 => address)) internal interfaces; /// @notice mapping from addresses to their manager. mapping(address => address) internal managers; /// @notice flag for each address and erc165 interface to indicate if it is cached. mapping(address => mapping(bytes4 => bool)) internal erc165Cached; /// @notice Indicates a contract is the 'implementer' of 'interfaceHash' for 'addr'. event InterfaceImplementerSet(address indexed addr, bytes32 indexed interfaceHash, address indexed implementer); /// @notice Indicates 'newManager' is the address of the new manager for 'addr'. event ManagerChanged(address indexed addr, address indexed newManager); /// @notice Query if an address implements an interface and through which contract. /// @param _addr Address being queried for the implementer of an interface. /// (If '_addr' is the zero address then 'msg.sender' is assumed.) /// @param _interfaceHash Keccak256 hash of the name of the interface as a string. /// E.g., 'web3.utils.keccak256("ERC777TokensRecipient")' for the 'ERC777TokensRecipient' interface. /// @return The address of the contract which implements the interface '_interfaceHash' for '_addr' /// or '0' if '_addr' did not register an implementer for this interface. function getInterfaceImplementer(address _addr, bytes32 _interfaceHash) external view returns (address) { address addr = _addr == address(0) ? msg.sender : _addr; if (isERC165Interface(_interfaceHash)) { bytes4 erc165InterfaceHash = bytes4(_interfaceHash); return implementsERC165Interface(addr, erc165InterfaceHash) ? addr : address(0); } return interfaces[addr][_interfaceHash]; } /// @notice Sets the contract which implements a specific interface for an address. /// Only the manager defined for that address can set it. /// (Each address is the manager for itself until it sets a new manager.) /// @param _addr Address for which to set the interface. /// (If '_addr' is the zero address then 'msg.sender' is assumed.) /// @param _interfaceHash Keccak256 hash of the name of the interface as a string. /// E.g., 'web3.utils.keccak256("ERC777TokensRecipient")' for the 'ERC777TokensRecipient' interface. /// @param _implementer Contract address implementing '_interfaceHash' for '_addr'. function setInterfaceImplementer(address _addr, bytes32 _interfaceHash, address _implementer) external { address addr = _addr == address(0) ? msg.sender : _addr; require(getManager(addr) == msg.sender, "Not the manager"); require(!isERC165Interface(_interfaceHash), "Must not be an ERC165 hash"); if (_implementer != address(0) && _implementer != msg.sender) { require( ERC1820ImplementerInterface(_implementer) .canImplementInterfaceForAddress(_interfaceHash, addr) == ERC1820_ACCEPT_MAGIC, "Does not implement the interface" ); } interfaces[addr][_interfaceHash] = _implementer; emit InterfaceImplementerSet(addr, _interfaceHash, _implementer); } /// @notice Sets '_newManager' as manager for '_addr'. /// The new manager will be able to call 'setInterfaceImplementer' for '_addr'. /// @param _addr Address for which to set the new manager. /// @param _newManager Address of the new manager for 'addr'. (Pass '0x0' to reset the manager to '_addr'.) function setManager(address _addr, address _newManager) external { require(getManager(_addr) == msg.sender, "Not the manager"); managers[_addr] = _newManager == _addr ? address(0) : _newManager; emit ManagerChanged(_addr, _newManager); } /// @notice Get the manager of an address. /// @param _addr Address for which to return the manager. /// @return Address of the manager for a given address. function getManager(address _addr) public view returns(address) { // By default the manager of an address is the same address if (managers[_addr] == address(0)) { return _addr; } else { return managers[_addr]; } } /// @notice Compute the keccak256 hash of an interface given its name. /// @param _interfaceName Name of the interface. /// @return The keccak256 hash of an interface name. function interfaceHash(string calldata _interfaceName) external pure returns(bytes32) { return keccak256(abi.encodePacked(_interfaceName)); } /* --- ERC165 Related Functions --- */ /* --- Developed in collaboration with William Entriken. --- */ /// @notice Updates the cache with whether the contract implements an ERC165 interface or not. /// @param _contract Address of the contract for which to update the cache. /// @param _interfaceId ERC165 interface for which to update the cache. function updateERC165Cache(address _contract, bytes4 _interfaceId) external { interfaces[_contract][_interfaceId] = implementsERC165InterfaceNoCache( _contract, _interfaceId) ? _contract : address(0); erc165Cached[_contract][_interfaceId] = true; } /// @notice Checks whether a contract implements an ERC165 interface or not. // If the result is not cached a direct lookup on the contract address is performed. // If the result is not cached or the cached value is out-of-date, the cache MUST be updated manually by calling // 'updateERC165Cache' with the contract address. /// @param _contract Address of the contract to check. /// @param _interfaceId ERC165 interface to check. /// @return True if '_contract' implements '_interfaceId', false otherwise. function implementsERC165Interface(address _contract, bytes4 _interfaceId) public view returns (bool) { if (!erc165Cached[_contract][_interfaceId]) { return implementsERC165InterfaceNoCache(_contract, _interfaceId); } return interfaces[_contract][_interfaceId] == _contract; } /// @notice Checks whether a contract implements an ERC165 interface or not without using nor updating the cache. /// @param _contract Address of the contract to check. /// @param _interfaceId ERC165 interface to check. /// @return True if '_contract' implements '_interfaceId', false otherwise. function implementsERC165InterfaceNoCache(address _contract, bytes4 _interfaceId) public view returns (bool) { uint256 success; uint256 result; (success, result) = noThrowCall(_contract, ERC165ID); if (success == 0 || result == 0) { return false; } (success, result) = noThrowCall(_contract, INVALID_ID); if (success == 0 || result != 0) { return false; } (success, result) = noThrowCall(_contract, _interfaceId); if (success == 1 && result == 1) { return true; } return false; } /// @notice Checks whether the hash is a ERC165 interface (ending with 28 zeroes) or not. /// @param _interfaceHash The hash to check. /// @return True if '_interfaceHash' is an ERC165 interface (ending with 28 zeroes), false otherwise. function isERC165Interface(bytes32 _interfaceHash) internal pure returns (bool) { return _interfaceHash & 0x00000000FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF == 0; } /// @dev Make a call on a contract without throwing if the function does not exist. function noThrowCall(address _contract, bytes4 _interfaceId) internal view returns (uint256 success, uint256 result) { bytes4 erc165ID = ERC165ID; assembly { let x := mload(0x40) // Find empty storage location using "free memory pointer" mstore(x, erc165ID) // Place signature at beginning of empty storage mstore(add(x, 0x04), _interfaceId) // Place first argument directly next to signature success := staticcall( 30000, // 30k gas _contract, // To addr x, // Inputs are stored at location x 0x24, // Inputs are 36 (4 + 32) bytes long x, // Store output over input (saves space) 0x20 // Outputs are 32 bytes long ) result := mload(x) // Load the result } } }