Transaction Hash:
Block:
18743301 at Dec-08-2023 06:23:11 PM +UTC
Transaction Fee:
0.002666269746146992 ETH
$6.72
Gas Used:
63,218 Gas / 42.175800344 Gwei
Emitted Events:
380 |
CoinwebToken.Sent( operator=[Receiver] TokenReleaser, from=[Receiver] TokenReleaser, to=[Sender] 0xfd85b902bdaa0e43979b0afa02ebc66767df8588, amount=0, data=0x, operatorData=0x )
|
381 |
CoinwebToken.Transfer( from=[Receiver] TokenReleaser, to=[Sender] 0xfd85b902bdaa0e43979b0afa02ebc66767df8588, value=0 )
|
Account State Difference:
Address | Before | After | State Difference | ||
---|---|---|---|---|---|
0x95222290...5CC4BAfe5
Miner
| (beaverbuild) | 13.361727639957944353 Eth | 13.361733961757944353 Eth | 0.0000063218 | |
0xfD85b902...767Df8588 |
0.039901226338273543 Eth
Nonce: 68
|
0.037234956592126551 Eth
Nonce: 69
| 0.002666269746146992 |
Execution Trace
TokenReleaser.CALL( )
CoinwebToken.transfer( recipient=0xfD85b902BDAa0e43979B0aFA02eBC66767Df8588, amount=0 ) => ( True )
-
ERC1820Registry.getInterfaceImplementer( _addr=0x13Fe7160858F2A16b8e4429DFf26c8a3A4b12b1B, _interfaceHash=29DDB589B1FB5FC7CF394961C1ADF5F8C6454761ADF795E67FE149F658ABE895 ) => ( 0x0000000000000000000000000000000000000000 )
-
ERC1820Registry.getInterfaceImplementer( _addr=0xfD85b902BDAa0e43979B0aFA02eBC66767Df8588, _interfaceHash=B281FC8C12954D22544DB45DE3159A39272895B169A852B314F9CC762E44C53B ) => ( 0x0000000000000000000000000000000000000000 )
-
File 1 of 3: TokenReleaser
File 2 of 3: CoinwebToken
File 3 of 3: ERC1820Registry
// SPDX-License-Identifier: MIT pragma solidity ^0.8.0; // We'll actually use ERC777, but any IERC20 instance (including ERC777) // is supported. import 'openzeppelin-solidity/contracts/token/ERC20/IERC20.sol'; contract TokenReleaser { ///////////////////////////////////////////////////////////////////////////////////////////////////// // This smart contract is divided into 3 parts: // // - The first part defines the different types of releases (i.e token release for seed round investor, // token release for team and advisor ...etc). Here we define the `ReleaseType` and `ReleaseSchedule` // types, and the function `setTokenomics` which associate to each ReleaseType its associated // `ReleaseSchedule`. The association between the type of release and its schedule is what we call // the `tokenomic`. Notice `setTokenomics` is called only once, therefore `tokenomic` behaves like // a constant (unfortunately, due to solidity limitations it couldn't get declared as such). // // - Second part defines the `Beneficiary`, which represent an user (or more generally an address), // that got some tokens booked; and the external function `releaseToken`. A beneficiary will call // `releaseToken` to receive tokens, which will be sent according to the Beneficiary's release // schedule type. Notice `releaseToken` is the only public function a non-admin user can call. // // - The third part defines the admins, and the action they control. Each of these actions // is represented by a function and an Event. For security and simplicity reason, we've decide // to keep a fixed max number of admins, 3 of them specifically (though by default we only enable 2 // of them). At any time, any admin can modify the list of admins. // // Types, variables and events are defined right before they are mentioned on code. IERC20 public tokenContract; constructor(address _adminA, address _adminB, IERC20 _contract, uint256 avaliableTokens){ tokenContract = _contract; adminA = _adminA; adminB = _adminB; setTokenomics(avaliableTokens); } ////////////////////////////////////////////////////////////////////////////////////////////////////// // First part: enum ReleaseType { SEED_ROUND , PRIVATE_ROUND , STRATEGIC_ROUND , PUBLIC_SALE , COMPANY_RESERVE , TEAM_AND_ADVISORS , STRATEGIC_PARTNERS , FOUNDERS_AND_EARLY_CONTRIBUTORS , MINING_RESERVE } // Information about how an specific ReleaseType will be scheduled. struct ReleaseSchedule { uint256 tokenLockTime ; // lockup time after the release time starts. uint256 tokenReleaseDuration ; // vesting time during which the token will be gradually released. // if 0, it means everything available at once. uint256 immediateAccessTimeWindow ; // When the release time starts, the beneficiary will have immediate access to } // all the tokens to be released to him during the his `immediateAccessTimeWindow`. mapping(ReleaseType => ReleaseSchedule) public tokenomic; uint256 public avaliableTokensToRelease; // Defines the total amount of tokens available to get release, and how the schedule // for each type of release will be. function setTokenomics(uint256 avaliableTokens) private{ // NOTICE: we take a month to be equivalent to 30 days, regardless of actual calendar months. // In particular, 12 month would not sum up a whole year, but 360 days. tokenomic[ ReleaseType.SEED_ROUND ] = ReleaseSchedule ({ tokenLockTime : 0 * 30 days , tokenReleaseDuration : 24 * 30 days , immediateAccessTimeWindow : 1 * 30 days } ); tokenomic[ ReleaseType.PRIVATE_ROUND ] = ReleaseSchedule ({ tokenLockTime : 0 * 30 days , tokenReleaseDuration : 12 * 30 days , immediateAccessTimeWindow : 1 * 30 days } ); tokenomic[ ReleaseType.STRATEGIC_ROUND ] = ReleaseSchedule ({ tokenLockTime : 0 * 30 days , tokenReleaseDuration : 12 * 30 days , immediateAccessTimeWindow : 1 * 30 days } ); tokenomic[ ReleaseType.PUBLIC_SALE ] = ReleaseSchedule ({ tokenLockTime : 0 * 30 days , tokenReleaseDuration : 0 * 30 days , immediateAccessTimeWindow : 0 * 30 days } // as `tokenReleaseDuration == 0` everything is available as soon as the release time starts. ); tokenomic[ ReleaseType.COMPANY_RESERVE ] = ReleaseSchedule ({ tokenLockTime : 0 * 30 days , tokenReleaseDuration : 36 * 30 days , immediateAccessTimeWindow : 1 * 30 days } ); tokenomic[ ReleaseType.TEAM_AND_ADVISORS ] = ReleaseSchedule ({ tokenLockTime : 12 * 30 days , tokenReleaseDuration : 36 * 30 days , immediateAccessTimeWindow : 0 * 30 days } ); tokenomic[ ReleaseType.STRATEGIC_PARTNERS ] = ReleaseSchedule ({ tokenLockTime : 12 * 30 days , tokenReleaseDuration : 36 * 30 days , immediateAccessTimeWindow : 0 * 30 days } ); tokenomic[ ReleaseType.FOUNDERS_AND_EARLY_CONTRIBUTORS] = ReleaseSchedule ({ tokenLockTime : 12 * 30 days , tokenReleaseDuration : 48 * 30 days , immediateAccessTimeWindow : 0 * 30 days } ); tokenomic[ ReleaseType.MINING_RESERVE ] = ReleaseSchedule ({ tokenLockTime : 6 * 30 days , tokenReleaseDuration : 60 * 30 days , immediateAccessTimeWindow : 0 * 30 days } ); avaliableTokensToRelease = avaliableTokens; } ////////////////////////////////////////////////////////////////////////////////////////// // Second Part: struct Beneficiary { ReleaseType releaseSchedule ; // public sale, seed round ... uint256 tokensAlreadyReleased ; uint256 tokenBookedAmount ; } uint256 public releaseStartTime; mapping(address => Beneficiary) public beneficiaries; function releaseToken() external{ require( releaseStartTime > 0 , 'Release time has not started yet' ) ; Beneficiary memory beneficiary = beneficiaries[msg.sender]; require( beneficiary.tokenBookedAmount != 0 , 'Address doesnt belong to a beneficiary set by an admin.' ) ; ReleaseSchedule memory releaseSchedule = tokenomic[beneficiary.releaseSchedule]; // Date the tokens locktime finish for the beneficiary. uint256 startTime = releaseSchedule.tokenLockTime + releaseStartTime; // After the locktime has finished, there's a time window defined as `tokenReleaseDuration`. // `timeCompleted` is how much of that time window has been completed. // A beneficiary can only release a fraction of his token proportional to how much of this time window // has been completed (or the totality of them if it is completed). uint256 timeCompleted = 0; uint256 unlockedTokens = 0; if (block.timestamp >= startTime){ timeCompleted = block.timestamp - startTime; // Time completed is the maximum between the time it has passed since start, and // the immediateAccessTimeWindow, capped to a max of tokenReleaseDuration. if (timeCompleted < releaseSchedule.immediateAccessTimeWindow){ timeCompleted = releaseSchedule.immediateAccessTimeWindow; } if (timeCompleted >= releaseSchedule.tokenReleaseDuration){ timeCompleted = releaseSchedule.tokenReleaseDuration; } // `tokenReleaseDuration == 0` means everything is available right after the release lock time if (releaseSchedule.tokenReleaseDuration == 0){ unlockedTokens = beneficiary.tokenBookedAmount; } else { unlockedTokens = (timeCompleted * beneficiary.tokenBookedAmount) / releaseSchedule.tokenReleaseDuration; } } uint256 toRelease = unlockedTokens - beneficiary.tokensAlreadyReleased; beneficiaries[msg.sender].tokensAlreadyReleased += toRelease; tokenContract.transfer( msg.sender, toRelease ); } ////////////////////////////////////////////////////////////////////////////////////////// address public adminA; // active admin address public adminB; // backup admin address public adminT; // temporary admin, only used during key rotation for one of the other admins. modifier adminOnly(){ require( (msg.sender == adminA) || (msg.sender == adminB) || (msg.sender == adminT) , 'Only admins allowed' ) ; _; } event NewAdminA(address adminA ); function changeAdminA(address newAdminA) external adminOnly{ if (adminA != newAdminA){ emit NewAdminA(newAdminA); adminA = newAdminA; } // Notice we don't fail when there's not a NewAdminX defined, // this is on purpose, so the function becomes idempotent and // it is easier to programatically interact with while // deployed on testnet. } event NewAdminB(address adminB ); function changeAdminB(address newAdminB) external adminOnly{ if (adminB != newAdminB){ emit NewAdminB(newAdminB); adminB = newAdminB; } } event NewAdminT(address adminT ); function changeAdminT(address newAdminT) external adminOnly{ if (adminT != newAdminT){ emit NewAdminT(newAdminT); adminT = newAdminT; } } event TokensBook(address beneficiary , uint256 tokenAmount, ReleaseType releaseSchedule); function bookTokensFor( address beneficiary , uint256 tokenAmount, ReleaseType releaseSchedule) external adminOnly{ require( avaliableTokensToRelease >= tokenAmount , 'Not enough token to book' ) ; require( beneficiaries[beneficiary].tokenBookedAmount == 0 , 'Beneficiaries can only be set once' ) ; require( tokenAmount > 0 , 'More than 0 token needs to be booked to set a beneficiary' ) ; avaliableTokensToRelease -= tokenAmount; emit TokensBook(beneficiary, tokenAmount, releaseSchedule); beneficiaries[beneficiary] = Beneficiary(releaseSchedule,0,tokenAmount); } event ReleaseTimeStarted(); function startReleaseTime() external adminOnly{ require( releaseStartTime == 0 , 'Release time has already started' ) ; releaseStartTime = block.timestamp; emit ReleaseTimeStarted(); } event ImmediateTokensSent(address beneficiary, uint256 amount); // Release tokens immediately without a release schedule. function immediateSendTokens(address beneficiary, uint256 amount) external adminOnly{ require( avaliableTokensToRelease >= amount , 'Not enough token avaliable' ) ; avaliableTokensToRelease -= amount; emit ImmediateTokensSent(beneficiary, amount); tokenContract.transfer( beneficiary, amount ); } ////////////////////////////////////////////////////////////////////////////////////////// } // SPDX-License-Identifier: MIT pragma solidity ^0.8.0; /** * @dev Interface of the ERC20 standard as defined in the EIP. */ interface IERC20 { /** * @dev Returns the amount of tokens in existence. */ function totalSupply() external view returns (uint256); /** * @dev Returns the amount of tokens owned by `account`. */ function balanceOf(address account) external view returns (uint256); /** * @dev Moves `amount` tokens from the caller's account to `recipient`. * * Returns a boolean value indicating whether the operation succeeded. * * Emits a {Transfer} event. */ function transfer(address recipient, uint256 amount) external returns (bool); /** * @dev Returns the remaining number of tokens that `spender` will be * allowed to spend on behalf of `owner` through {transferFrom}. This is * zero by default. * * This value changes when {approve} or {transferFrom} are called. */ function allowance(address owner, address spender) external view returns (uint256); /** * @dev Sets `amount` as the allowance of `spender` over the caller's tokens. * * Returns a boolean value indicating whether the operation succeeded. * * IMPORTANT: Beware that changing an allowance with this method brings the risk * that someone may use both the old and the new allowance by unfortunate * transaction ordering. One possible solution to mitigate this race * condition is to first reduce the spender's allowance to 0 and set the * desired value afterwards: * https://github.com/ethereum/EIPs/issues/20#issuecomment-263524729 * * Emits an {Approval} event. */ function approve(address spender, uint256 amount) external returns (bool); /** * @dev Moves `amount` tokens from `sender` to `recipient` using the * allowance mechanism. `amount` is then deducted from the caller's * allowance. * * Returns a boolean value indicating whether the operation succeeded. * * Emits a {Transfer} event. */ function transferFrom( address sender, address recipient, uint256 amount ) external returns (bool); /** * @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); }
File 2 of 3: CoinwebToken
// SPDX-License-Identifier: MIT pragma solidity ^0.8.0; import 'openzeppelin-solidity/contracts/token/ERC777/ERC777.sol'; import './TokenReleaser.sol'; contract CoinwebToken is ERC777 { event Deployed(address releaser ); TokenReleaser rel; constructor() ERC777("Coinweb", "CWEB", new address[](0) ) { // This is equivalent to the expresion `7_680_000_000 ether`, but // we prefer being more explicit here to avoid confusion. uint256 totalSupply = 7_680_000_000 * 10 ** decimals(); _mint(msg.sender, totalSupply, "", ""); ////////////////////////////////////////////////////////////////// // TokenReleaser Admins: rel = new TokenReleaser( 0x3C159347b33cABabdb6980081f9408759833129b // Admin A , 0xE147f1Ae58466A64Ca13Af6534FC1651ecd0af43 // Admin B , this , totalSupply ); emit Deployed(address(rel)); transfer(address(rel), totalSupply ); // Pass token control the token releaser. ////////////////////////////////////////////////////////////////// } } // SPDX-License-Identifier: MIT pragma solidity ^0.8.0; // We'll actually use ERC777, but any IERC20 instance (including ERC777) // is supported. import 'openzeppelin-solidity/contracts/token/ERC20/IERC20.sol'; contract TokenReleaser { ///////////////////////////////////////////////////////////////////////////////////////////////////// // This smart contract is divided into 3 parts: // // - The first part defines the different types of releases (i.e token release for seed round investor, // token release for team and advisor ...etc). Here we define the `ReleaseType` and `ReleaseSchedule` // types, and the function `setTokenomics` which associate to each ReleaseType its associated // `ReleaseSchedule`. The association between the type of release and its schedule is what we call // the `tokenomic`. Notice `setTokenomics` is called only once, therefore `tokenomic` behaves like // a constant (unfortunately, due to solidity limitations it couldn't get declared as such). // // - Second part defines the `Beneficiary`, which represent an user (or more generally an address), // that got some tokens booked; and the external function `releaseToken`. A beneficiary will call // `releaseToken` to receive tokens, which will be sent according to the Beneficiary's release // schedule type. Notice `releaseToken` is the only public function a non-admin user can call. // // - The third part defines the admins, and the action they control. Each of these actions // is represented by a function and an Event. For security and simplicity reason, we've decide // to keep a fixed max number of admins, 3 of them specifically (though by default we only enable 2 // of them). At any time, any admin can modify the list of admins. // // Types, variables and events are defined right before they are mentioned on code. IERC20 public tokenContract; constructor(address _adminA, address _adminB, IERC20 _contract, uint256 avaliableTokens){ tokenContract = _contract; adminA = _adminA; adminB = _adminB; setTokenomics(avaliableTokens); } ////////////////////////////////////////////////////////////////////////////////////////////////////// // First part: enum ReleaseType { SEED_ROUND , PRIVATE_ROUND , STRATEGIC_ROUND , PUBLIC_SALE , COMPANY_RESERVE , TEAM_AND_ADVISORS , STRATEGIC_PARTNERS , FOUNDERS_AND_EARLY_CONTRIBUTORS , MINING_RESERVE } // Information about how an specific ReleaseType will be scheduled. struct ReleaseSchedule { uint256 tokenLockTime ; // lockup time after the release time starts. uint256 tokenReleaseDuration ; // vesting time during which the token will be gradually released. // if 0, it means everything available at once. uint256 immediateAccessTimeWindow ; // When the release time starts, the beneficiary will have immediate access to } // all the tokens to be released to him during the his `immediateAccessTimeWindow`. mapping(ReleaseType => ReleaseSchedule) public tokenomic; uint256 public avaliableTokensToRelease; // Defines the total amount of tokens available to get release, and how the schedule // for each type of release will be. function setTokenomics(uint256 avaliableTokens) private{ // NOTICE: we take a month to be equivalent to 30 days, regardless of actual calendar months. // In particular, 12 month would not sum up a whole year, but 360 days. tokenomic[ ReleaseType.SEED_ROUND ] = ReleaseSchedule ({ tokenLockTime : 0 * 30 days , tokenReleaseDuration : 24 * 30 days , immediateAccessTimeWindow : 1 * 30 days } ); tokenomic[ ReleaseType.PRIVATE_ROUND ] = ReleaseSchedule ({ tokenLockTime : 0 * 30 days , tokenReleaseDuration : 12 * 30 days , immediateAccessTimeWindow : 1 * 30 days } ); tokenomic[ ReleaseType.STRATEGIC_ROUND ] = ReleaseSchedule ({ tokenLockTime : 0 * 30 days , tokenReleaseDuration : 12 * 30 days , immediateAccessTimeWindow : 1 * 30 days } ); tokenomic[ ReleaseType.PUBLIC_SALE ] = ReleaseSchedule ({ tokenLockTime : 0 * 30 days , tokenReleaseDuration : 0 * 30 days , immediateAccessTimeWindow : 0 * 30 days } // as `tokenReleaseDuration == 0` everything is available as soon as the release time starts. ); tokenomic[ ReleaseType.COMPANY_RESERVE ] = ReleaseSchedule ({ tokenLockTime : 0 * 30 days , tokenReleaseDuration : 36 * 30 days , immediateAccessTimeWindow : 1 * 30 days } ); tokenomic[ ReleaseType.TEAM_AND_ADVISORS ] = ReleaseSchedule ({ tokenLockTime : 12 * 30 days , tokenReleaseDuration : 36 * 30 days , immediateAccessTimeWindow : 0 * 30 days } ); tokenomic[ ReleaseType.STRATEGIC_PARTNERS ] = ReleaseSchedule ({ tokenLockTime : 12 * 30 days , tokenReleaseDuration : 36 * 30 days , immediateAccessTimeWindow : 0 * 30 days } ); tokenomic[ ReleaseType.FOUNDERS_AND_EARLY_CONTRIBUTORS] = ReleaseSchedule ({ tokenLockTime : 12 * 30 days , tokenReleaseDuration : 48 * 30 days , immediateAccessTimeWindow : 0 * 30 days } ); tokenomic[ ReleaseType.MINING_RESERVE ] = ReleaseSchedule ({ tokenLockTime : 6 * 30 days , tokenReleaseDuration : 60 * 30 days , immediateAccessTimeWindow : 0 * 30 days } ); avaliableTokensToRelease = avaliableTokens; } ////////////////////////////////////////////////////////////////////////////////////////// // Second Part: struct Beneficiary { ReleaseType releaseSchedule ; // public sale, seed round ... uint256 tokensAlreadyReleased ; uint256 tokenBookedAmount ; } uint256 public releaseStartTime; mapping(address => Beneficiary) public beneficiaries; function releaseToken() external{ require( releaseStartTime > 0 , 'Release time has not started yet' ) ; Beneficiary memory beneficiary = beneficiaries[msg.sender]; require( beneficiary.tokenBookedAmount != 0 , 'Address doesnt belong to a beneficiary set by an admin.' ) ; ReleaseSchedule memory releaseSchedule = tokenomic[beneficiary.releaseSchedule]; // Date the tokens locktime finish for the beneficiary. uint256 startTime = releaseSchedule.tokenLockTime + releaseStartTime; // After the locktime has finished, there's a time window defined as `tokenReleaseDuration`. // `timeCompleted` is how much of that time window has been completed. // A beneficiary can only release a fraction of his token proportional to how much of this time window // has been completed (or the totality of them if it is completed). uint256 timeCompleted = 0; uint256 unlockedTokens = 0; if (block.timestamp >= startTime){ timeCompleted = block.timestamp - startTime; // Time completed is the maximum between the time it has passed since start, and // the immediateAccessTimeWindow, capped to a max of tokenReleaseDuration. if (timeCompleted < releaseSchedule.immediateAccessTimeWindow){ timeCompleted = releaseSchedule.immediateAccessTimeWindow; } if (timeCompleted >= releaseSchedule.tokenReleaseDuration){ timeCompleted = releaseSchedule.tokenReleaseDuration; } // `tokenReleaseDuration == 0` means everything is available right after the release lock time if (releaseSchedule.tokenReleaseDuration == 0){ unlockedTokens = beneficiary.tokenBookedAmount; } else { unlockedTokens = (timeCompleted * beneficiary.tokenBookedAmount) / releaseSchedule.tokenReleaseDuration; } } uint256 toRelease = unlockedTokens - beneficiary.tokensAlreadyReleased; beneficiaries[msg.sender].tokensAlreadyReleased += toRelease; tokenContract.transfer( msg.sender, toRelease ); } ////////////////////////////////////////////////////////////////////////////////////////// address public adminA; // active admin address public adminB; // backup admin address public adminT; // temporary admin, only used during key rotation for one of the other admins. modifier adminOnly(){ require( (msg.sender == adminA) || (msg.sender == adminB) || (msg.sender == adminT) , 'Only admins allowed' ) ; _; } event NewAdminA(address adminA ); function changeAdminA(address newAdminA) external adminOnly{ if (adminA != newAdminA){ emit NewAdminA(newAdminA); adminA = newAdminA; } // Notice we don't fail when there's not a NewAdminX defined, // this is on purpose, so the function becomes idempotent and // it is easier to programatically interact with while // deployed on testnet. } event NewAdminB(address adminB ); function changeAdminB(address newAdminB) external adminOnly{ if (adminB != newAdminB){ emit NewAdminB(newAdminB); adminB = newAdminB; } } event NewAdminT(address adminT ); function changeAdminT(address newAdminT) external adminOnly{ if (adminT != newAdminT){ emit NewAdminT(newAdminT); adminT = newAdminT; } } event TokensBook(address beneficiary , uint256 tokenAmount, ReleaseType releaseSchedule); function bookTokensFor( address beneficiary , uint256 tokenAmount, ReleaseType releaseSchedule) external adminOnly{ require( avaliableTokensToRelease >= tokenAmount , 'Not enough token to book' ) ; require( beneficiaries[beneficiary].tokenBookedAmount == 0 , 'Beneficiaries can only be set once' ) ; require( tokenAmount > 0 , 'More than 0 token needs to be booked to set a beneficiary' ) ; avaliableTokensToRelease -= tokenAmount; emit TokensBook(beneficiary, tokenAmount, releaseSchedule); beneficiaries[beneficiary] = Beneficiary(releaseSchedule,0,tokenAmount); } event ReleaseTimeStarted(); function startReleaseTime() external adminOnly{ require( releaseStartTime == 0 , 'Release time has already started' ) ; releaseStartTime = block.timestamp; emit ReleaseTimeStarted(); } event ImmediateTokensSent(address beneficiary, uint256 amount); // Release tokens immediately without a release schedule. function immediateSendTokens(address beneficiary, uint256 amount) external adminOnly{ require( avaliableTokensToRelease >= amount , 'Not enough token avaliable' ) ; avaliableTokensToRelease -= amount; emit ImmediateTokensSent(beneficiary, amount); tokenContract.transfer( beneficiary, amount ); } ////////////////////////////////////////////////////////////////////////////////////////// } // SPDX-License-Identifier: MIT pragma solidity ^0.8.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); } // SPDX-License-Identifier: MIT pragma solidity ^0.8.0; /** * @dev Provides information about the current execution context, including the * sender of the transaction and its data. While these are generally available * via msg.sender and msg.data, they should not be accessed in such a direct * manner, since when dealing with meta-transactions the account sending and * paying for execution may not be the actual sender (as far as an application * is concerned). * * This contract is only required for intermediate, library-like contracts. */ abstract contract Context { function _msgSender() internal view virtual returns (address) { return msg.sender; } function _msgData() internal view virtual returns (bytes calldata) { return msg.data; } } // SPDX-License-Identifier: MIT pragma solidity ^0.8.0; /** * @dev Collection of functions related to the address type */ library Address { /** * @dev Returns true if `account` is a contract. * * [IMPORTANT] * ==== * It is unsafe to assume that an address for which this function returns * false is an externally-owned account (EOA) and not a contract. * * Among others, `isContract` will return false for the following * types of addresses: * * - an externally-owned account * - a contract in construction * - an address where a contract will be created * - an address where a contract lived, but was destroyed * ==== */ function isContract(address account) internal view returns (bool) { // This method relies on extcodesize, which returns 0 for contracts in // construction, since the code is only stored at the end of the // constructor execution. uint256 size; assembly { size := extcodesize(account) } return size > 0; } /** * @dev Replacement for Solidity's `transfer`: sends `amount` wei to * `recipient`, forwarding all available gas and reverting on errors. * * https://eips.ethereum.org/EIPS/eip-1884[EIP1884] increases the gas cost * of certain opcodes, possibly making contracts go over the 2300 gas limit * imposed by `transfer`, making them unable to receive funds via * `transfer`. {sendValue} removes this limitation. * * https://diligence.consensys.net/posts/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.5.11/security-considerations.html#use-the-checks-effects-interactions-pattern[checks-effects-interactions pattern]. */ function sendValue(address payable recipient, uint256 amount) internal { require(address(this).balance >= amount, "Address: insufficient balance"); (bool success, ) = recipient.call{value: amount}(""); require(success, "Address: unable to send value, recipient may have reverted"); } /** * @dev Performs a Solidity function call using a low level `call`. A * plain `call` is an unsafe replacement for a function call: use this * function instead. * * If `target` reverts with a revert reason, it is bubbled up by this * function (like regular Solidity function calls). * * Returns the raw returned data. To convert to the expected return value, * use https://solidity.readthedocs.io/en/latest/units-and-global-variables.html?highlight=abi.decode#abi-encoding-and-decoding-functions[`abi.decode`]. * * Requirements: * * - `target` must be a contract. * - calling `target` with `data` must not revert. * * _Available since v3.1._ */ function functionCall(address target, bytes memory data) internal returns (bytes memory) { return functionCall(target, data, "Address: low-level call failed"); } /** * @dev Same as {xref-Address-functionCall-address-bytes-}[`functionCall`], but with * `errorMessage` as a fallback revert reason when `target` reverts. * * _Available since v3.1._ */ function functionCall( address target, bytes memory data, string memory errorMessage ) internal returns (bytes memory) { return functionCallWithValue(target, data, 0, errorMessage); } /** * @dev Same as {xref-Address-functionCall-address-bytes-}[`functionCall`], * but also transferring `value` wei to `target`. * * Requirements: * * - the calling contract must have an ETH balance of at least `value`. * - the called Solidity function must be `payable`. * * _Available since v3.1._ */ function functionCallWithValue( address target, bytes memory data, uint256 value ) internal returns (bytes memory) { return functionCallWithValue(target, data, value, "Address: low-level call with value failed"); } /** * @dev Same as {xref-Address-functionCallWithValue-address-bytes-uint256-}[`functionCallWithValue`], but * with `errorMessage` as a fallback revert reason when `target` reverts. * * _Available since v3.1._ */ function functionCallWithValue( address target, bytes memory data, uint256 value, string memory errorMessage ) internal returns (bytes memory) { require(address(this).balance >= value, "Address: insufficient balance for call"); require(isContract(target), "Address: call to non-contract"); (bool success, bytes memory returndata) = target.call{value: value}(data); return verifyCallResult(success, returndata, errorMessage); } /** * @dev Same as {xref-Address-functionCall-address-bytes-}[`functionCall`], * but performing a static call. * * _Available since v3.3._ */ function functionStaticCall(address target, bytes memory data) internal view returns (bytes memory) { return functionStaticCall(target, data, "Address: low-level static call failed"); } /** * @dev Same as {xref-Address-functionCall-address-bytes-string-}[`functionCall`], * but performing a static call. * * _Available since v3.3._ */ function functionStaticCall( address target, bytes memory data, string memory errorMessage ) internal view returns (bytes memory) { require(isContract(target), "Address: static call to non-contract"); (bool success, bytes memory returndata) = target.staticcall(data); return verifyCallResult(success, returndata, errorMessage); } /** * @dev Same as {xref-Address-functionCall-address-bytes-}[`functionCall`], * but performing a delegate call. * * _Available since v3.4._ */ function functionDelegateCall(address target, bytes memory data) internal returns (bytes memory) { return functionDelegateCall(target, data, "Address: low-level delegate call failed"); } /** * @dev Same as {xref-Address-functionCall-address-bytes-string-}[`functionCall`], * but performing a delegate call. * * _Available since v3.4._ */ function functionDelegateCall( address target, bytes memory data, string memory errorMessage ) internal returns (bytes memory) { require(isContract(target), "Address: delegate call to non-contract"); (bool success, bytes memory returndata) = target.delegatecall(data); return verifyCallResult(success, returndata, errorMessage); } /** * @dev Tool to verifies that a low level call was successful, and revert if it wasn't, either by bubbling the * revert reason using the provided one. * * _Available since v4.3._ */ function verifyCallResult( bool success, bytes memory returndata, string memory errorMessage ) internal pure returns (bytes memory) { if (success) { return returndata; } else { // 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 assembly { let returndata_size := mload(returndata) revert(add(32, returndata), returndata_size) } } else { revert(errorMessage); } } } } // SPDX-License-Identifier: MIT pragma solidity ^0.8.0; /** * @dev Interface of the ERC777TokensSender standard as defined in the EIP. * * {IERC777} Token holders can be notified of operations performed on their * tokens by having a contract implement this interface (contract holders can be * their own implementer) and registering it on the * https://eips.ethereum.org/EIPS/eip-1820[ERC1820 global registry]. * * See {IERC1820Registry} and {ERC1820Implementer}. */ interface IERC777Sender { /** * @dev Called by an {IERC777} token contract whenever a registered holder's * (`from`) tokens are about to be moved or destroyed. The type of operation * is conveyed by `to` being the zero address or not. * * This call occurs _before_ the token contract's state is updated, so * {IERC777-balanceOf}, etc., can be used to query the pre-operation state. * * This function may revert to prevent the operation from being executed. */ function tokensToSend( address operator, address from, address to, uint256 amount, bytes calldata userData, bytes calldata operatorData ) external; } // SPDX-License-Identifier: MIT pragma solidity ^0.8.0; /** * @dev Interface of the ERC777TokensRecipient standard as defined in the EIP. * * Accounts can be notified of {IERC777} tokens being sent to them by having a * contract implement this interface (contract holders can be their own * implementer) and registering it on the * https://eips.ethereum.org/EIPS/eip-1820[ERC1820 global registry]. * * See {IERC1820Registry} and {ERC1820Implementer}. */ interface IERC777Recipient { /** * @dev Called by an {IERC777} token contract whenever tokens are being * moved or created into a registered account (`to`). The type of operation * is conveyed by `from` being the zero address or not. * * This call occurs _after_ the token contract's state is updated, so * {IERC777-balanceOf}, etc., can be used to query the post-operation state. * * This function may revert to prevent the operation from being executed. */ function tokensReceived( address operator, address from, address to, uint256 amount, bytes calldata userData, bytes calldata operatorData ) external; } // SPDX-License-Identifier: MIT pragma solidity ^0.8.0; /** * @dev Interface of the ERC777Token standard as defined in the EIP. * * This contract uses the * https://eips.ethereum.org/EIPS/eip-1820[ERC1820 registry standard] to let * token holders and recipients react to token movements by using setting implementers * for the associated interfaces in said registry. See {IERC1820Registry} and * {ERC1820Implementer}. */ interface IERC777 { /** * @dev Returns the name of the token. */ function name() external view returns (string memory); /** * @dev Returns the symbol of the token, usually a shorter version of the * name. */ function symbol() external view returns (string memory); /** * @dev Returns the smallest part of the token that is not divisible. This * means all token operations (creation, movement and destruction) must have * amounts that are a multiple of this number. * * For most token contracts, this value will equal 1. */ function granularity() external view returns (uint256); /** * @dev Returns the amount of tokens in existence. */ function totalSupply() external view returns (uint256); /** * @dev Returns the amount of tokens owned by an account (`owner`). */ function balanceOf(address owner) external view returns (uint256); /** * @dev Moves `amount` tokens from the caller's account to `recipient`. * * If send or receive hooks are registered for the caller and `recipient`, * the corresponding functions will be called with `data` and empty * `operatorData`. See {IERC777Sender} and {IERC777Recipient}. * * Emits a {Sent} event. * * Requirements * * - the caller must have at least `amount` tokens. * - `recipient` cannot be the zero address. * - if `recipient` is a contract, it must implement the {IERC777Recipient} * interface. */ function send( address recipient, uint256 amount, bytes calldata data ) external; /** * @dev Destroys `amount` tokens from the caller's account, reducing the * total supply. * * If a send hook is registered for the caller, the corresponding function * will be called with `data` and empty `operatorData`. See {IERC777Sender}. * * Emits a {Burned} event. * * Requirements * * - the caller must have at least `amount` tokens. */ function burn(uint256 amount, bytes calldata data) external; /** * @dev Returns true if an account is an operator of `tokenHolder`. * Operators can send and burn tokens on behalf of their owners. All * accounts are their own operator. * * See {operatorSend} and {operatorBurn}. */ function isOperatorFor(address operator, address tokenHolder) external view returns (bool); /** * @dev Make an account an operator of the caller. * * See {isOperatorFor}. * * Emits an {AuthorizedOperator} event. * * Requirements * * - `operator` cannot be calling address. */ function authorizeOperator(address operator) external; /** * @dev Revoke an account's operator status for the caller. * * See {isOperatorFor} and {defaultOperators}. * * Emits a {RevokedOperator} event. * * Requirements * * - `operator` cannot be calling address. */ function revokeOperator(address operator) external; /** * @dev Returns the list of default operators. These accounts are operators * for all token holders, even if {authorizeOperator} was never called on * them. * * This list is immutable, but individual holders may revoke these via * {revokeOperator}, in which case {isOperatorFor} will return false. */ function defaultOperators() external view returns (address[] memory); /** * @dev Moves `amount` tokens from `sender` to `recipient`. The caller must * be an operator of `sender`. * * If send or receive hooks are registered for `sender` and `recipient`, * the corresponding functions will be called with `data` and * `operatorData`. See {IERC777Sender} and {IERC777Recipient}. * * Emits a {Sent} event. * * Requirements * * - `sender` cannot be the zero address. * - `sender` must have at least `amount` tokens. * - the caller must be an operator for `sender`. * - `recipient` cannot be the zero address. * - if `recipient` is a contract, it must implement the {IERC777Recipient} * interface. */ function operatorSend( address sender, address recipient, uint256 amount, bytes calldata data, bytes calldata operatorData ) external; /** * @dev Destroys `amount` tokens from `account`, reducing the total supply. * The caller must be an operator of `account`. * * If a send hook is registered for `account`, the corresponding function * will be called with `data` and `operatorData`. See {IERC777Sender}. * * Emits a {Burned} event. * * Requirements * * - `account` cannot be the zero address. * - `account` must have at least `amount` tokens. * - the caller must be an operator for `account`. */ function operatorBurn( address account, uint256 amount, bytes calldata data, bytes calldata operatorData ) external; 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 tokenHolder); event RevokedOperator(address indexed operator, address indexed tokenHolder); } // SPDX-License-Identifier: MIT pragma solidity ^0.8.0; import "./IERC777.sol"; import "./IERC777Recipient.sol"; import "./IERC777Sender.sol"; import "../ERC20/IERC20.sol"; import "../../utils/Address.sol"; import "../../utils/Context.sol"; import "../../utils/introspection/IERC1820Registry.sol"; /** * @dev Implementation of the {IERC777} interface. * * This implementation is agnostic to the way tokens are created. This means * that a supply mechanism has to be added in a derived contract using {_mint}. * * Support for ERC20 is included in this contract, as specified by the EIP: both * the ERC777 and ERC20 interfaces can be safely used when interacting with it. * Both {IERC777-Sent} and {IERC20-Transfer} events are emitted on token * movements. * * Additionally, the {IERC777-granularity} value is hard-coded to `1`, meaning that there * are no special restrictions in the amount of tokens that created, moved, or * destroyed. This makes integration with ERC20 applications seamless. */ contract ERC777 is Context, IERC777, IERC20 { using Address for address; IERC1820Registry internal constant _ERC1820_REGISTRY = IERC1820Registry(0x1820a4B7618BdE71Dce8cdc73aAB6C95905faD24); mapping(address => uint256) private _balances; uint256 private _totalSupply; string private _name; string private _symbol; bytes32 private constant _TOKENS_SENDER_INTERFACE_HASH = keccak256("ERC777TokensSender"); bytes32 private constant _TOKENS_RECIPIENT_INTERFACE_HASH = keccak256("ERC777TokensRecipient"); // This isn't ever read from - it's only used to respond to the defaultOperators query. address[] private _defaultOperatorsArray; // Immutable, but accounts may revoke them (tracked in __revokedDefaultOperators). mapping(address => bool) private _defaultOperators; // For each account, a mapping of its operators and revoked default operators. mapping(address => mapping(address => bool)) private _operators; mapping(address => mapping(address => bool)) private _revokedDefaultOperators; // ERC20-allowances mapping(address => mapping(address => uint256)) private _allowances; /** * @dev `defaultOperators` may be an empty array. */ constructor( string memory name_, string memory symbol_, address[] memory defaultOperators_ ) { _name = name_; _symbol = symbol_; _defaultOperatorsArray = defaultOperators_; for (uint256 i = 0; i < defaultOperators_.length; i++) { _defaultOperators[defaultOperators_[i]] = true; } // register interfaces _ERC1820_REGISTRY.setInterfaceImplementer(address(this), keccak256("ERC777Token"), address(this)); _ERC1820_REGISTRY.setInterfaceImplementer(address(this), keccak256("ERC20Token"), address(this)); } /** * @dev See {IERC777-name}. */ function name() public view virtual override returns (string memory) { return _name; } /** * @dev See {IERC777-symbol}. */ function symbol() public view virtual override returns (string memory) { return _symbol; } /** * @dev See {ERC20-decimals}. * * Always returns 18, as per the * [ERC777 EIP](https://eips.ethereum.org/EIPS/eip-777#backward-compatibility). */ function decimals() public pure virtual returns (uint8) { return 18; } /** * @dev See {IERC777-granularity}. * * This implementation always returns `1`. */ function granularity() public view virtual override returns (uint256) { return 1; } /** * @dev See {IERC777-totalSupply}. */ function totalSupply() public view virtual override(IERC20, IERC777) returns (uint256) { return _totalSupply; } /** * @dev Returns the amount of tokens owned by an account (`tokenHolder`). */ function balanceOf(address tokenHolder) public view virtual override(IERC20, IERC777) returns (uint256) { return _balances[tokenHolder]; } /** * @dev See {IERC777-send}. * * Also emits a {IERC20-Transfer} event for ERC20 compatibility. */ function send( address recipient, uint256 amount, bytes memory data ) public virtual override { _send(_msgSender(), recipient, amount, data, "", true); } /** * @dev See {IERC20-transfer}. * * Unlike `send`, `recipient` is _not_ required to implement the {IERC777Recipient} * interface if it is a contract. * * Also emits a {Sent} event. */ function transfer(address recipient, uint256 amount) public virtual override returns (bool) { require(recipient != address(0), "ERC777: transfer to the zero address"); address from = _msgSender(); _callTokensToSend(from, from, recipient, amount, "", ""); _move(from, from, recipient, amount, "", ""); _callTokensReceived(from, from, recipient, amount, "", "", false); return true; } /** * @dev See {IERC777-burn}. * * Also emits a {IERC20-Transfer} event for ERC20 compatibility. */ function burn(uint256 amount, bytes memory data) public virtual override { _burn(_msgSender(), amount, data, ""); } /** * @dev See {IERC777-isOperatorFor}. */ function isOperatorFor(address operator, address tokenHolder) public view virtual override returns (bool) { return operator == tokenHolder || (_defaultOperators[operator] && !_revokedDefaultOperators[tokenHolder][operator]) || _operators[tokenHolder][operator]; } /** * @dev See {IERC777-authorizeOperator}. */ function authorizeOperator(address operator) public virtual override { require(_msgSender() != operator, "ERC777: authorizing self as operator"); if (_defaultOperators[operator]) { delete _revokedDefaultOperators[_msgSender()][operator]; } else { _operators[_msgSender()][operator] = true; } emit AuthorizedOperator(operator, _msgSender()); } /** * @dev See {IERC777-revokeOperator}. */ function revokeOperator(address operator) public virtual override { require(operator != _msgSender(), "ERC777: revoking self as operator"); if (_defaultOperators[operator]) { _revokedDefaultOperators[_msgSender()][operator] = true; } else { delete _operators[_msgSender()][operator]; } emit RevokedOperator(operator, _msgSender()); } /** * @dev See {IERC777-defaultOperators}. */ function defaultOperators() public view virtual override returns (address[] memory) { return _defaultOperatorsArray; } /** * @dev See {IERC777-operatorSend}. * * Emits {Sent} and {IERC20-Transfer} events. */ function operatorSend( address sender, address recipient, uint256 amount, bytes memory data, bytes memory operatorData ) public virtual override { require(isOperatorFor(_msgSender(), sender), "ERC777: caller is not an operator for holder"); _send(sender, recipient, amount, data, operatorData, true); } /** * @dev See {IERC777-operatorBurn}. * * Emits {Burned} and {IERC20-Transfer} events. */ function operatorBurn( address account, uint256 amount, bytes memory data, bytes memory operatorData ) public virtual override { require(isOperatorFor(_msgSender(), account), "ERC777: caller is not an operator for holder"); _burn(account, amount, data, operatorData); } /** * @dev See {IERC20-allowance}. * * Note that operator and allowance concepts are orthogonal: operators may * not have allowance, and accounts with allowance may not be operators * themselves. */ function allowance(address holder, address spender) public view virtual override returns (uint256) { return _allowances[holder][spender]; } /** * @dev See {IERC20-approve}. * * Note that accounts cannot have allowance issued by their operators. */ function approve(address spender, uint256 value) public virtual override returns (bool) { address holder = _msgSender(); _approve(holder, spender, value); return true; } /** * @dev See {IERC20-transferFrom}. * * Note that operator and allowance concepts are orthogonal: operators cannot * call `transferFrom` (unless they have allowance), and accounts with * allowance cannot call `operatorSend` (unless they are operators). * * Emits {Sent}, {IERC20-Transfer} and {IERC20-Approval} events. */ function transferFrom( address holder, address recipient, uint256 amount ) public virtual override returns (bool) { require(recipient != address(0), "ERC777: transfer to the zero address"); require(holder != address(0), "ERC777: transfer from the zero address"); address spender = _msgSender(); _callTokensToSend(spender, holder, recipient, amount, "", ""); _move(spender, holder, recipient, amount, "", ""); uint256 currentAllowance = _allowances[holder][spender]; require(currentAllowance >= amount, "ERC777: transfer amount exceeds allowance"); _approve(holder, spender, currentAllowance - amount); _callTokensReceived(spender, holder, recipient, amount, "", "", false); return true; } /** * @dev Creates `amount` tokens and assigns them to `account`, increasing * the total supply. * * If a send hook is registered for `account`, the corresponding function * will be called with `operator`, `data` and `operatorData`. * * See {IERC777Sender} and {IERC777Recipient}. * * Emits {Minted} and {IERC20-Transfer} events. * * Requirements * * - `account` cannot be the zero address. * - if `account` is a contract, it must implement the {IERC777Recipient} * interface. */ function _mint( address account, uint256 amount, bytes memory userData, bytes memory operatorData ) internal virtual { _mint(account, amount, userData, operatorData, true); } /** * @dev Creates `amount` tokens and assigns them to `account`, increasing * the total supply. * * If `requireReceptionAck` is set to true, and if a send hook is * registered for `account`, the corresponding function will be called with * `operator`, `data` and `operatorData`. * * See {IERC777Sender} and {IERC777Recipient}. * * Emits {Minted} and {IERC20-Transfer} events. * * Requirements * * - `account` cannot be the zero address. * - if `account` is a contract, it must implement the {IERC777Recipient} * interface. */ function _mint( address account, uint256 amount, bytes memory userData, bytes memory operatorData, bool requireReceptionAck ) internal virtual { require(account != address(0), "ERC777: mint to the zero address"); address operator = _msgSender(); _beforeTokenTransfer(operator, address(0), account, amount); // Update state variables _totalSupply += amount; _balances[account] += amount; _callTokensReceived(operator, address(0), account, amount, userData, operatorData, requireReceptionAck); emit Minted(operator, account, amount, userData, operatorData); emit Transfer(address(0), account, amount); } /** * @dev Send tokens * @param from address token holder address * @param to address recipient address * @param amount uint256 amount of tokens to transfer * @param userData bytes extra information provided by the token holder (if any) * @param operatorData bytes extra information provided by the operator (if any) * @param requireReceptionAck if true, contract recipients are required to implement ERC777TokensRecipient */ function _send( address from, address to, uint256 amount, bytes memory userData, bytes memory operatorData, bool requireReceptionAck ) internal virtual { require(from != address(0), "ERC777: send from the zero address"); require(to != address(0), "ERC777: send to the zero address"); address operator = _msgSender(); _callTokensToSend(operator, from, to, amount, userData, operatorData); _move(operator, from, to, amount, userData, operatorData); _callTokensReceived(operator, from, to, amount, userData, operatorData, requireReceptionAck); } /** * @dev Burn tokens * @param from address token holder address * @param amount uint256 amount of tokens to burn * @param data bytes extra information provided by the token holder * @param operatorData bytes extra information provided by the operator (if any) */ function _burn( address from, uint256 amount, bytes memory data, bytes memory operatorData ) internal virtual { require(from != address(0), "ERC777: burn from the zero address"); address operator = _msgSender(); _callTokensToSend(operator, from, address(0), amount, data, operatorData); _beforeTokenTransfer(operator, from, address(0), amount); // Update state variables uint256 fromBalance = _balances[from]; require(fromBalance >= amount, "ERC777: burn amount exceeds balance"); unchecked { _balances[from] = fromBalance - amount; } _totalSupply -= amount; emit Burned(operator, from, amount, data, operatorData); emit Transfer(from, address(0), amount); } function _move( address operator, address from, address to, uint256 amount, bytes memory userData, bytes memory operatorData ) private { _beforeTokenTransfer(operator, from, to, amount); uint256 fromBalance = _balances[from]; require(fromBalance >= amount, "ERC777: transfer amount exceeds balance"); unchecked { _balances[from] = fromBalance - amount; } _balances[to] += amount; emit Sent(operator, from, to, amount, userData, operatorData); emit Transfer(from, to, amount); } /** * @dev See {ERC20-_approve}. * * Note that accounts cannot have allowance issued by their operators. */ function _approve( address holder, address spender, uint256 value ) internal { require(holder != address(0), "ERC777: approve from the zero address"); require(spender != address(0), "ERC777: approve to the zero address"); _allowances[holder][spender] = value; emit Approval(holder, spender, value); } /** * @dev Call from.tokensToSend() if the interface is registered * @param operator address operator requesting the transfer * @param from address token holder address * @param to address recipient address * @param amount uint256 amount of tokens to transfer * @param userData bytes extra information provided by the token holder (if any) * @param operatorData bytes extra information provided by the operator (if any) */ function _callTokensToSend( address operator, address from, address to, uint256 amount, bytes memory userData, bytes memory operatorData ) private { address implementer = _ERC1820_REGISTRY.getInterfaceImplementer(from, _TOKENS_SENDER_INTERFACE_HASH); if (implementer != address(0)) { IERC777Sender(implementer).tokensToSend(operator, from, to, amount, userData, operatorData); } } /** * @dev Call to.tokensReceived() if the interface is registered. Reverts if the recipient is a contract but * tokensReceived() was not registered for the recipient * @param operator address operator requesting the transfer * @param from address token holder address * @param to address recipient address * @param amount uint256 amount of tokens to transfer * @param userData bytes extra information provided by the token holder (if any) * @param operatorData bytes extra information provided by the operator (if any) * @param requireReceptionAck if true, contract recipients are required to implement ERC777TokensRecipient */ function _callTokensReceived( address operator, address from, address to, uint256 amount, bytes memory userData, bytes memory operatorData, bool requireReceptionAck ) private { address implementer = _ERC1820_REGISTRY.getInterfaceImplementer(to, _TOKENS_RECIPIENT_INTERFACE_HASH); if (implementer != address(0)) { IERC777Recipient(implementer).tokensReceived(operator, from, to, amount, userData, operatorData); } else if (requireReceptionAck) { require(!to.isContract(), "ERC777: token recipient contract has no implementer for ERC777TokensRecipient"); } } /** * @dev Hook that is called before any token transfer. This includes * calls to {send}, {transfer}, {operatorSend}, minting and burning. * * Calling conditions: * * - when `from` and `to` are both non-zero, `amount` of ``from``'s tokens * will be to transferred to `to`. * - when `from` is zero, `amount` tokens will be minted for `to`. * - when `to` is zero, `amount` of ``from``'s tokens will be burned. * - `from` and `to` are never both zero. * * To learn more about hooks, head to xref:ROOT:extending-contracts.adoc#using-hooks[Using Hooks]. */ function _beforeTokenTransfer( address operator, address from, address to, uint256 amount ) internal virtual {} } // SPDX-License-Identifier: MIT pragma solidity ^0.8.0; /** * @dev Interface of the ERC20 standard as defined in the EIP. */ interface IERC20 { /** * @dev Returns the amount of tokens in existence. */ function totalSupply() external view returns (uint256); /** * @dev Returns the amount of tokens owned by `account`. */ function balanceOf(address account) external view returns (uint256); /** * @dev Moves `amount` tokens from the caller's account to `recipient`. * * Returns a boolean value indicating whether the operation succeeded. * * Emits a {Transfer} event. */ function transfer(address recipient, uint256 amount) external returns (bool); /** * @dev Returns the remaining number of tokens that `spender` will be * allowed to spend on behalf of `owner` through {transferFrom}. This is * zero by default. * * This value changes when {approve} or {transferFrom} are called. */ function allowance(address owner, address spender) external view returns (uint256); /** * @dev Sets `amount` as the allowance of `spender` over the caller's tokens. * * Returns a boolean value indicating whether the operation succeeded. * * IMPORTANT: Beware that changing an allowance with this method brings the risk * that someone may use both the old and the new allowance by unfortunate * transaction ordering. One possible solution to mitigate this race * condition is to first reduce the spender's allowance to 0 and set the * desired value afterwards: * https://github.com/ethereum/EIPs/issues/20#issuecomment-263524729 * * Emits an {Approval} event. */ function approve(address spender, uint256 amount) external returns (bool); /** * @dev Moves `amount` tokens from `sender` to `recipient` using the * allowance mechanism. `amount` is then deducted from the caller's * allowance. * * Returns a boolean value indicating whether the operation succeeded. * * Emits a {Transfer} event. */ function transferFrom( address sender, address recipient, uint256 amount ) external returns (bool); /** * @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); }
File 3 of 3: 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 } } }