├── .gitattributes ├── .gitignore ├── LICENSE.md ├── README.md ├── erc1155 ├── .gitignore ├── README.md ├── contracts │ ├── Migrations.sol │ ├── erc1155.vy │ └── erc1155_Metadata.vy ├── migrations │ ├── 1_initial_migration.js │ └── 2_deploy_contracts.js ├── test │ ├── README.md │ └── erc1155.js └── truffle.js ├── erc20 ├── .gitignore ├── README.md ├── contracts │ ├── Migrations.sol │ └── erc20.vy ├── migrations │ ├── 1_initial_migration.js │ └── 2_deploy_contracts.js ├── test │ ├── README.md │ ├── erc20-consensys.js │ └── helpers │ │ └── assertRevert.js └── truffle.js ├── erc721 ├── .gitignore ├── README.md ├── contracts │ ├── Migrations.sol │ └── erc721.vy ├── migrations │ ├── 1_initial_migration.js │ └── 2_deploy_contracts.js ├── test │ └── erc721.js └── truffle.js ├── erc777 ├── .gitignore ├── README.md ├── contracts │ ├── Migrations.sol │ ├── erc777.vy │ ├── erc777TokenReceiver.vy │ ├── erc777TokenSender.vy │ └── helpers │ │ └── ERC1820Registry.sol ├── migrations │ ├── 1_initial_migration.js │ ├── 2_deploy_contracts.js │ └── helpers │ │ └── checkForERC182Registry.js ├── test │ ├── README.md │ └── erc777.js └── truffle.js ├── linear_optimization_problem_bounty ├── .gitignore ├── README.md ├── contracts │ ├── Migrations.sol │ └── linear_optimization_problem_bounty.vy ├── migrations │ ├── 1_initial_migration.js │ └── 2_deploy_contracts.js ├── test │ └── linear_optimization_problem_bounty.js └── truffle.js ├── vyperStorage ├── .gitignore ├── README.md ├── contracts │ ├── Migrations.sol │ └── vyperStorage.vy ├── migrations │ ├── 1_initial_migration.js │ └── 2_deploy_contracts.js ├── test │ └── vyperStorage.js └── truffle.js └── wallet ├── .gitignore ├── README.md ├── contracts ├── Migrations.sol ├── helpers │ ├── README.md │ ├── erc20.vy │ ├── erc721.vy │ ├── erc777.vy │ └── erc777TokenReceiver.vy └── wallet.vy ├── migrations ├── 1_initial_migration.js ├── 2_deploy_contracts.js └── helpers │ └── checkForERC182Registry.js ├── test └── wallet.js └── truffle.js /.gitattributes: -------------------------------------------------------------------------------- 1 | *.vy linguist-language=Python 2 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # OSX 2 | .DS_Store 3 | -------------------------------------------------------------------------------- /LICENSE.md: -------------------------------------------------------------------------------- 1 | CC0 1.0 Universal 2 | 3 | Statement of Purpose 4 | 5 | The laws of most jurisdictions throughout the world automatically confer 6 | exclusive Copyright and Related Rights (defined below) upon the creator and 7 | subsequent owner(s) (each and all, an "owner") of an original work of 8 | authorship and/or a database (each, a "Work"). 9 | 10 | Certain owners wish to permanently relinquish those rights to a Work for the 11 | purpose of contributing to a commons of creative, cultural and scientific 12 | works ("Commons") that the public can reliably and without fear of later 13 | claims of infringement build upon, modify, incorporate in other works, reuse 14 | and redistribute as freely as possible in any form whatsoever and for any 15 | purposes, including without limitation commercial purposes. These owners may 16 | contribute to the Commons to promote the ideal of a free culture and the 17 | further production of creative, cultural and scientific works, or to gain 18 | reputation or greater distribution for their Work in part through the use and 19 | efforts of others. 20 | 21 | For these and/or other purposes and motivations, and without any expectation 22 | of additional consideration or compensation, the person associating CC0 with a 23 | Work (the "Affirmer"), to the extent that he or she is an owner of Copyright 24 | and Related Rights in the Work, voluntarily elects to apply CC0 to the Work 25 | and publicly distribute the Work under its terms, with knowledge of his or her 26 | Copyright and Related Rights in the Work and the meaning and intended legal 27 | effect of CC0 on those rights. 28 | 29 | 1. Copyright and Related Rights. A Work made available under CC0 may be 30 | protected by copyright and related or neighboring rights ("Copyright and 31 | Related Rights"). Copyright and Related Rights include, but are not limited 32 | to, the following: 33 | 34 | i. the right to reproduce, adapt, distribute, perform, display, communicate, 35 | and translate a Work; 36 | 37 | ii. moral rights retained by the original author(s) and/or performer(s); 38 | 39 | iii. publicity and privacy rights pertaining to a person's image or likeness 40 | depicted in a Work; 41 | 42 | iv. rights protecting against unfair competition in regards to a Work, 43 | subject to the limitations in paragraph 4(a), below; 44 | 45 | v. rights protecting the extraction, dissemination, use and reuse of data in 46 | a Work; 47 | 48 | vi. database rights (such as those arising under Directive 96/9/EC of the 49 | European Parliament and of the Council of 11 March 1996 on the legal 50 | protection of databases, and under any national implementation thereof, 51 | including any amended or successor version of such directive); and 52 | 53 | vii. other similar, equivalent or corresponding rights throughout the world 54 | based on applicable law or treaty, and any national implementations thereof. 55 | 56 | 2. Waiver. To the greatest extent permitted by, but not in contravention of, 57 | applicable law, Affirmer hereby overtly, fully, permanently, irrevocably and 58 | unconditionally waives, abandons, and surrenders all of Affirmer's Copyright 59 | and Related Rights and associated claims and causes of action, whether now 60 | known or unknown (including existing as well as future claims and causes of 61 | action), in the Work (i) in all territories worldwide, (ii) for the maximum 62 | duration provided by applicable law or treaty (including future time 63 | extensions), (iii) in any current or future medium and for any number of 64 | copies, and (iv) for any purpose whatsoever, including without limitation 65 | commercial, advertising or promotional purposes (the "Waiver"). Affirmer makes 66 | the Waiver for the benefit of each member of the public at large and to the 67 | detriment of Affirmer's heirs and successors, fully intending that such Waiver 68 | shall not be subject to revocation, rescission, cancellation, termination, or 69 | any other legal or equitable action to disrupt the quiet enjoyment of the Work 70 | by the public as contemplated by Affirmer's express Statement of Purpose. 71 | 72 | 3. Public License Fallback. Should any part of the Waiver for any reason be 73 | judged legally invalid or ineffective under applicable law, then the Waiver 74 | shall be preserved to the maximum extent permitted taking into account 75 | Affirmer's express Statement of Purpose. In addition, to the extent the Waiver 76 | is so judged Affirmer hereby grants to each affected person a royalty-free, 77 | non transferable, non sublicensable, non exclusive, irrevocable and 78 | unconditional license to exercise Affirmer's Copyright and Related Rights in 79 | the Work (i) in all territories worldwide, (ii) for the maximum duration 80 | provided by applicable law or treaty (including future time extensions), (iii) 81 | in any current or future medium and for any number of copies, and (iv) for any 82 | purpose whatsoever, including without limitation commercial, advertising or 83 | promotional purposes (the "License"). The License shall be deemed effective as 84 | of the date CC0 was applied by Affirmer to the Work. Should any part of the 85 | License for any reason be judged legally invalid or ineffective under 86 | applicable law, such partial invalidity or ineffectiveness shall not 87 | invalidate the remainder of the License, and in such case Affirmer hereby 88 | affirms that he or she will not (i) exercise any of his or her remaining 89 | Copyright and Related Rights in the Work or (ii) assert any associated claims 90 | and causes of action with respect to the Work, in either case contrary to 91 | Affirmer's express Statement of Purpose. 92 | 93 | 4. Limitations and Disclaimers. 94 | 95 | a. No trademark or patent rights held by Affirmer are waived, abandoned, 96 | surrendered, licensed or otherwise affected by this document. 97 | 98 | b. Affirmer offers the Work as-is and makes no representations or warranties 99 | of any kind concerning the Work, express, implied, statutory or otherwise, 100 | including without limitation warranties of title, merchantability, fitness 101 | for a particular purpose, non infringement, or the absence of latent or 102 | other defects, accuracy, or the present or absence of errors, whether or not 103 | discoverable, all to the greatest extent permissible under applicable law. 104 | 105 | c. Affirmer disclaims responsibility for clearing rights of other persons 106 | that may apply to the Work or any use thereof, including without limitation 107 | any person's Copyright and Related Rights in the Work. Further, Affirmer 108 | disclaims responsibility for obtaining any necessary consents, permissions 109 | or other rights required for any use of the Work. 110 | 111 | d. Affirmer understands and acknowledges that Creative Commons is not a 112 | party to this document and has no duty or obligation with respect to this 113 | CC0 or use of the Work. 114 | 115 | For more information, please see 116 | 117 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Vyper Contracts 2 | All smart contracts listed at [vyperhub.io](https://contracts.vyperhub.io/) 3 | 4 | Each contract in this repository is wrapped inside its own [truffle](https://truffleframework.com/) project. 5 | This makes it easy to quickly compile and test them locally. 6 | 7 | ## Overview 8 | 9 | | Project | | | 10 | | - | - | - | 11 | | [ERC20](erc20/) | [Contract](erc20/contracts/erc20.vy) | [Tests](erc20/test/) | 12 | | [ERC721](erc721/) | [Contract](erc721/contracts/erc721.vy) | [Tests](erc721/test/erc721.js) | 13 | | [ERC777](erc777/) | [Contract](erc777/contracts/erc777.vy) | [Tests](erc777/test) | 14 | | [Linear optimization problem bounty](linear_optimization_problem_bounty/) | [Contract](linear_optimization_problem_bounty/contracts/linear_optimization_problem_bounty.vy) | [Tests](linear_optimization_problem_bounty/test/linear_optimization_problem_bounty.js) | 15 | | [VyperStorage](vyperStorage/) | [Contract](vyperStorage/contracts/vyperStorage.vy) | [Tests](vyperStorage/test/vyperStorage.js) | 16 | | [Wallet](wallet/) | [Contract](wallet/contracts/wallet.vy) | [Tests](wallet/test/wallet.js) | 17 | 18 | **Disclaimer:** All contracts in this repository as well as contracts hosted at [vyperhub.io](https://contracts.vyperhub.io/) are in beta and should not be used in production! We are not responsible for any losses that occure when using these or related contracts. 19 | -------------------------------------------------------------------------------- /erc1155/.gitignore: -------------------------------------------------------------------------------- 1 | ./build 2 | -------------------------------------------------------------------------------- /erc1155/README.md: -------------------------------------------------------------------------------- 1 | # ERC1155 2 | https://eips.ethereum.org/EIPS/eip-1155 3 | 4 | ## Run tests 5 | ```bash 6 | $ truffle test --network ganache 7 | ``` 8 | -------------------------------------------------------------------------------- /erc1155/contracts/Migrations.sol: -------------------------------------------------------------------------------- 1 | pragma solidity >=0.4.21 <0.6.0; 2 | 3 | contract Migrations { 4 | address public owner; 5 | uint public last_completed_migration; 6 | 7 | constructor() public { 8 | owner = msg.sender; 9 | } 10 | 11 | modifier restricted() { 12 | if (msg.sender == owner) _; 13 | } 14 | 15 | function setCompleted(uint completed) public restricted { 16 | last_completed_migration = completed; 17 | } 18 | 19 | function upgrade(address new_address) public restricted { 20 | Migrations upgraded = Migrations(new_address); 21 | upgraded.setCompleted(last_completed_migration); 22 | } 23 | } 24 | -------------------------------------------------------------------------------- /erc1155/contracts/erc1155.vy: -------------------------------------------------------------------------------- 1 | # Author: Sören Steiger, github.com/ssteiger 2 | # License: MIT 3 | 4 | # ERC1155 Token Standard 5 | # https://eips.ethereum.org/EIPS/eip-1155 6 | 7 | 8 | contract ERC1155TokenReceiver: 9 | def onERC1155Received( 10 | _operator: address, 11 | _from: address, 12 | _id: uint256, 13 | _value: uint256, 14 | _data: bytes[256] 15 | ) -> bytes32: modifying # TODO: should return bytes4 16 | def onERC1155BatchReceived( 17 | _operator: address, 18 | _from: address, 19 | _ids: uint256[BATCH_SIZE], 20 | _values: uint256[BATCH_SIZE], 21 | _data: bytes[256] 22 | ) -> bytes32: modifying # TODO: should return bytes4 23 | 24 | 25 | # @dev Either `TransferSingle` or `TransferBatch` MUST emit when tokens are transferred, including zero value transfers as well as minting or burning (see "Safe Transfer Rules" section of the standard). 26 | # The `_operator` argument MUST be the address of an account/contract that is approved to make the transfer (SHOULD be msg.sender). 27 | # The `_from` argument MUST be the address of the holder whose balance is decreased. 28 | # The `_to` argument MUST be the address of the recipient whose balance is increased. 29 | # The `_id` argument MUST be the token type being transferred. 30 | # The `_value` argument MUST be the number of tokens the holder balance is decreased by and match what the recipient balance is increased by. 31 | # When minting/creating tokens, the `_from` argument MUST be set to `0x0` (i.e. zero address). 32 | # When burning/destroying tokens, the `_to` argument MUST be set to `0x0` (i.e. zero address). 33 | # event TransferSingle(address indexed _operator, address indexed _from, address indexed _to, uint256 _id, uint256 _value); 34 | TransferSingle: event({ 35 | _operator: indexed(address), 36 | _from: indexed(address), 37 | _to: indexed(address), 38 | _id: uint256, 39 | _value: uint256 40 | }) 41 | 42 | # @dev Either `TransferSingle` or `TransferBatch` MUST emit when tokens are transferred, including zero value transfers as well as minting or burning (see "Safe Transfer Rules" section of the standard). 43 | # The `_operator` argument MUST be the address of an account/contract that is approved to make the transfer (SHOULD be msg.sender). 44 | # The `_from` argument MUST be the address of the holder whose balance is decreased. 45 | # The `_to` argument MUST be the address of the recipient whose balance is increased. 46 | # The `_ids` argument MUST be the list of tokens being transferred. 47 | # The `_values` argument MUST be the list of number of tokens (matching the list and order of tokens specified in _ids) the holder balance is decreased by and match what the recipient balance is increased by. 48 | # When minting/creating tokens, the `_from` argument MUST be set to `0x0` (i.e. zero address). 49 | # When burning/destroying tokens, the `_to` argument MUST be set to `0x0` (i.e. zero address). 50 | # event TransferBatch(address indexed _operator, address indexed _from, address indexed _to, uint256[] _ids, uint256[] _values); 51 | TransferBatch: event({ 52 | _operator: indexed(address), 53 | _from: indexed(address), 54 | _to: indexed(address), 55 | _ids: uint256[BATCH_SIZE], 56 | _value: uint256[BATCH_SIZE] 57 | }) 58 | 59 | # @dev MUST emit when approval for a second party/operator address to manage all tokens for an owner address is enabled or disabled (absence of an event assumes disabled). 60 | # event ApprovalForAll(address indexed _owner, address indexed _operator, bool _approved); 61 | ApprovalForAll: event({ 62 | _owner: indexed(address), 63 | _operator: indexed(address), 64 | _approved: bool 65 | }) 66 | 67 | 68 | 69 | supportedInterfaces: map(bytes32, bool) 70 | 71 | # https://eips.ethereum.org/EIPS/eip-165 72 | ERC165_INTERFACE_ID: constant(bytes32) = 0x0000000000000000000000000000000000000000000000000000000001ffc9a7 73 | ERC1155_INTERFACE_ID: constant(bytes32) = 0x00000000000000000000000000000000000000000000000000000000d9b67a26 74 | 75 | tokensIdCount: uint256 76 | 77 | _balanceOf: map(address, map(uint256, uint256)) 78 | 79 | operators: map(address, map(address, bool)) 80 | 81 | # TODO: decide which batch size to use 82 | BATCH_SIZE: constant(uint256) = 5 83 | 84 | 85 | 86 | @public 87 | def __init__(): 88 | self.tokensIdCount = 0 89 | self.supportedInterfaces[ERC165_INTERFACE_ID] = True 90 | self.supportedInterfaces[ERC1155_INTERFACE_ID] = True 91 | 92 | 93 | @public 94 | @constant 95 | def supportsInterface(_interfaceID: bytes32) -> bool: 96 | return self.supportedInterfaces[_interfaceID] 97 | 98 | 99 | # @notice Transfers `_value` amount of an `_id` from the `_from` address to the `_to` address specified (with safety call). 100 | # @dev Caller must be approved to manage the tokens being transferred out of the `_from` account (see "Approval" section of the standard). 101 | # MUST revert if `_to` is the zero address. 102 | # MUST revert if balance of holder for token `_id` is lower than the `_value` sent. 103 | # MUST revert on any other error. 104 | # MUST emit the `TransferSingle` event to reflect the balance change (see "Safe Transfer Rules" section of the standard). 105 | # After the above conditions are met, this function MUST check if `_to` is a smart contract (e.g. code size > 0). If so, it MUST call `onERC1155Received` on `_to` and act appropriately (see "Safe Transfer Rules" section of the standard). 106 | # @param _from Source address 107 | # @param _to Target address 108 | # @param _id ID of the token type 109 | # @param _value Transfer amount 110 | # @param _data Additional data with no specified format, MUST be sent unaltered in call to `onERC1155Received` on `_to` 111 | # function safeTransferFrom(address _from, address _to, uint256 _id, uint256 _value, bytes calldata _data) external; 112 | @public 113 | def safeTransferFrom( 114 | _from: address, 115 | _to: address, 116 | _id: uint256, 117 | _value: uint256, 118 | _data: bytes[256] 119 | ): 120 | assert _from == msg.sender or (self.operators[_from])[msg.sender] 121 | assert _to != ZERO_ADDRESS 122 | assert self._balanceOf[_from][_id] >= _value 123 | 124 | if _to.is_contract: 125 | returnValue: bytes32 = ERC1155TokenReceiver(_to).onERC1155Received(msg.sender, _from, _id, _value, _data) 126 | assert returnValue == method_id("onERC1155Received(address,address,uint256,uint256,bytes)", bytes32) 127 | 128 | self._balanceOf[_from][_id] -= _value 129 | self._balanceOf[_to][_id] += _value 130 | log.TransferSingle(msg.sender, _from, _to, _id, _value) 131 | 132 | 133 | # @notice Transfers `_values` amount(s) of `_ids` from the `_from` address to the `_to` address specified (with safety call). 134 | # @dev Caller must be approved to manage the tokens being transferred out of the `_from` account (see "Approval" section of the standard). 135 | # MUST revert if `_to` is the zero address. 136 | # MUST revert if length of `_ids` is not the same as length of `_values`. 137 | # MUST revert if any of the balance(s) of the holder(s) for token(s) in `_ids` is lower than the respective amount(s) in `_values` sent to the recipient. 138 | # MUST revert on any other error. 139 | # MUST emit `TransferSingle` or `TransferBatch` event(s) such that all the balance changes are reflected (see "Safe Transfer Rules" section of the standard). 140 | # Balance changes and events MUST follow the ordering of the arrays (_ids[0]/_values[0] before _ids[1]/_values[1], etc). 141 | # After the above conditions for the transfer(s) in the batch are met, this function MUST check if `_to` is a smart contract (e.g. code size > 0). If so, it MUST call the relevant `ERC1155TokenReceiver` hook(s) on `_to` and act appropriately (see "Safe Transfer Rules" section of the standard). 142 | # @param _from Source address 143 | # @param _to Target address 144 | # @param _ids IDs of each token type (order and length must match _values array) 145 | # @param _values Transfer amounts per token type (order and length must match _ids array) 146 | # @param _data Additional data with no specified format, MUST be sent unaltered in call to the `ERC1155TokenReceiver` hook(s) on `_to` 147 | # function safeBatchTransferFrom(address _from, address _to, uint256[] calldata _ids, uint256[] calldata _values, bytes calldata _data) external; 148 | @public 149 | def safeBatchTransferFrom( 150 | _from: address, 151 | _to: address, 152 | _ids: uint256[BATCH_SIZE], 153 | _values: uint256[BATCH_SIZE], 154 | _data: bytes[256] 155 | ): 156 | assert _from == msg.sender or (self.operators[_from])[msg.sender] 157 | assert _to != ZERO_ADDRESS 158 | #assert len(_ids) == len(_values) 159 | 160 | for i in range(BATCH_SIZE): 161 | assert self._balanceOf[_from][_ids[i]] >= _values[i] 162 | self._balanceOf[_from][_ids[i]] -= _values[i] 163 | self._balanceOf[_to][_ids[i]] += _values[i] 164 | if _to.is_contract: 165 | returnValue: bytes32 = ERC1155TokenReceiver(_to).onERC1155Received(msg.sender, _from, _ids[i], _values[i], _data) 166 | assert returnValue == method_id("onERC1155Received(address,address,uint256,uint256,bytes)", bytes32) 167 | 168 | log.TransferBatch(msg.sender, _from, _to, _ids, _values) 169 | 170 | 171 | # @notice Get the balance of an account's tokens. 172 | # @param _owner The address of the token holder 173 | # @param _id ID of the token 174 | # @return The _owner's balance of the token type requested 175 | # function balanceOf(address _owner, uint256 _id) external view returns (uint256); 176 | @public 177 | @constant 178 | def balanceOf( 179 | _owner: address, 180 | _id: uint256 181 | ) -> uint256: 182 | assert _owner != ZERO_ADDRESS 183 | return self._balanceOf[_owner][_id] 184 | 185 | 186 | # @notice Get the balance of multiple account/token pairs 187 | # @param _owners The addresses of the token holders 188 | # @param _ids ID of the tokens 189 | # @return The _owner's balance of the token types requested (i.e. balance for each (owner, id) pair) 190 | # function balanceOfBatch(address[] calldata _owners, uint256[] calldata _ids) external view returns (uint256[] memory); 191 | @public 192 | @constant 193 | def balanceOfBatch( 194 | _owner: address[BATCH_SIZE], 195 | _ids: uint256[BATCH_SIZE] 196 | ) -> uint256[BATCH_SIZE]: 197 | returnValues: uint256[BATCH_SIZE] 198 | for i in range(BATCH_SIZE): 199 | returnValues[i] = self._balanceOf[_owner[i]][_ids[i]] 200 | return returnValues 201 | 202 | 203 | # @notice Enable or disable approval for a third party ("operator") to manage all of the caller's tokens. 204 | # @dev MUST emit the ApprovalForAll event on success. 205 | # @param _operator Address to add to the set of authorized operators 206 | # @param _approved True if the operator is approved, false to revoke approval 207 | # function setApprovalForAll(address _operator, bool _approved) external; 208 | @public 209 | def setApprovalForAll( 210 | _operator: address, 211 | _approved: bool 212 | ): 213 | (self.operators[msg.sender])[_operator] = _approved 214 | log.ApprovalForAll(msg.sender, _operator, _approved) 215 | 216 | 217 | # @notice Queries the approval status of an operator for a given owner. 218 | # @param _owner The owner of the tokens 219 | # @param _operator Address of authorized operator 220 | # @return True if the operator is approved, false if not 221 | # function isApprovedForAll(address _owner, address _operator) external view returns (bool); 222 | @public 223 | @constant 224 | def isApprovedForAll( 225 | _owner: address, 226 | _operator: address 227 | ) -> bool: 228 | return (self.operators[_owner])[_operator] 229 | 230 | 231 | # NOTE: This is not part of the standard 232 | # TODO: Right now anyone can mint 233 | @public 234 | def mint( 235 | _to: address, 236 | _supply: uint256, 237 | _data: bytes[256]="" 238 | ) -> uint256: 239 | assert _to != ZERO_ADDRESS 240 | self._balanceOf[msg.sender][self.tokensIdCount] = _supply 241 | self.tokensIdCount += 1 242 | log.TransferSingle(msg.sender, ZERO_ADDRESS, _to, self.tokensIdCount, _supply) 243 | return self.tokensIdCount 244 | 245 | 246 | # NOTE: This is not part of the standard 247 | # TODO: Right now anyone can mint 248 | @public 249 | def mintBatch( 250 | _to: address, 251 | _supplys: uint256[BATCH_SIZE], 252 | _data: bytes[256]="" 253 | ) -> uint256[BATCH_SIZE]: 254 | assert _to != ZERO_ADDRESS 255 | ids: uint256[BATCH_SIZE] 256 | for i in range(BATCH_SIZE): 257 | self._balanceOf[msg.sender][self.tokensIdCount] = _supplys[i] 258 | self.tokensIdCount += 1 259 | id: uint256 = self.tokensIdCount 260 | ids[i] = id 261 | 262 | log.TransferBatch(msg.sender, ZERO_ADDRESS, _to, ids, _supplys) 263 | return ids 264 | 265 | 266 | # TODO: specify a burn()/burnBatch() function 267 | -------------------------------------------------------------------------------- /erc1155/contracts/erc1155_Metadata.vy: -------------------------------------------------------------------------------- 1 | # Author: Sören Steiger, github.com/ssteiger 2 | # License: MIT 3 | 4 | # ERC1155 Token Standard 5 | # https://eips.ethereum.org/EIPS/eip-1155 6 | 7 | 8 | contract ERC1155TokenReceiver: 9 | def onERC1155Received( 10 | _operator: address, 11 | _from: address, 12 | _id: uint256, 13 | _value: uint256, 14 | _data: bytes[256] 15 | ) -> bytes32: modifying # TODO: should return bytes4 16 | def onERC1155BatchReceived( 17 | _operator: address, 18 | _from: address, 19 | _ids: uint256[BATCH_SIZE], 20 | _values: uint256[BATCH_SIZE], 21 | _data: bytes[256] 22 | ) -> bytes32: modifying # TODO: should return bytes4 23 | 24 | 25 | # @dev Either `TransferSingle` or `TransferBatch` MUST emit when tokens are transferred, including zero value transfers as well as minting or burning (see "Safe Transfer Rules" section of the standard). 26 | # The `_operator` argument MUST be the address of an account/contract that is approved to make the transfer (SHOULD be msg.sender). 27 | # The `_from` argument MUST be the address of the holder whose balance is decreased. 28 | # The `_to` argument MUST be the address of the recipient whose balance is increased. 29 | # The `_id` argument MUST be the token type being transferred. 30 | # The `_value` argument MUST be the number of tokens the holder balance is decreased by and match what the recipient balance is increased by. 31 | # When minting/creating tokens, the `_from` argument MUST be set to `0x0` (i.e. zero address). 32 | # When burning/destroying tokens, the `_to` argument MUST be set to `0x0` (i.e. zero address). 33 | # event TransferSingle(address indexed _operator, address indexed _from, address indexed _to, uint256 _id, uint256 _value); 34 | TransferSingle: event({ 35 | _operator: indexed(address), 36 | _from: indexed(address), 37 | _to: indexed(address), 38 | _id: uint256, 39 | _value: uint256 40 | }) 41 | 42 | # @dev Either `TransferSingle` or `TransferBatch` MUST emit when tokens are transferred, including zero value transfers as well as minting or burning (see "Safe Transfer Rules" section of the standard). 43 | # The `_operator` argument MUST be the address of an account/contract that is approved to make the transfer (SHOULD be msg.sender). 44 | # The `_from` argument MUST be the address of the holder whose balance is decreased. 45 | # The `_to` argument MUST be the address of the recipient whose balance is increased. 46 | # The `_ids` argument MUST be the list of tokens being transferred. 47 | # The `_values` argument MUST be the list of number of tokens (matching the list and order of tokens specified in _ids) the holder balance is decreased by and match what the recipient balance is increased by. 48 | # When minting/creating tokens, the `_from` argument MUST be set to `0x0` (i.e. zero address). 49 | # When burning/destroying tokens, the `_to` argument MUST be set to `0x0` (i.e. zero address). 50 | # event TransferBatch(address indexed _operator, address indexed _from, address indexed _to, uint256[] _ids, uint256[] _values); 51 | TransferBatch: event({ 52 | _operator: indexed(address), 53 | _from: indexed(address), 54 | _to: indexed(address), 55 | _ids: uint256[BATCH_SIZE], 56 | _value: uint256[BATCH_SIZE] 57 | }) 58 | 59 | # @dev MUST emit when approval for a second party/operator address to manage all tokens for an owner address is enabled or disabled (absence of an event assumes disabled). 60 | # event ApprovalForAll(address indexed _owner, address indexed _operator, bool _approved); 61 | ApprovalForAll: event({ 62 | _owner: indexed(address), 63 | _operator: indexed(address), 64 | _approved: bool 65 | }) 66 | 67 | # @dev MUST emit when the URI is updated for a token ID. 68 | # URIs are defined in RFC 3986. 69 | # The URI MUST point to a JSON file that conforms to the "ERC-1155 Metadata URI JSON Schema". 70 | # event URI(string _value, uint256 indexed _id); 71 | URI: event({ 72 | # https://stackoverflow.com/questions/417142/what-is-the-maximum-length-of-a-url-in-different-browsers 73 | _value: string[MAX_URI_SIZE], 74 | _id: indexed(uint256) 75 | }) 76 | 77 | 78 | # TODO: decide which batch size to use 79 | BATCH_SIZE: constant(uint256) = 5 80 | MAX_URI_SIZE: constant(uint256) = 1024 81 | 82 | # https://eips.ethereum.org/EIPS/eip-165 83 | INTERFACE_ID_ERC165: constant(bytes32) = 0x0000000000000000000000000000000000000000000000000000000001ffc9a7 84 | INTERFACE_ID_ERC1155: constant(bytes32) = 0x00000000000000000000000000000000000000000000000000000000d9b67a26 85 | INTERFACE_ID_ERC1155_METADATA: constant(bytes32) = 0x000000000000000000000000000000000000000000000000000000000e89341c 86 | 87 | supportedInterfaces: map(bytes32, bool) 88 | 89 | tokensIdCount: uint256 90 | 91 | _balanceOf: map(address, map(uint256, uint256)) 92 | 93 | _uri: map(uint256, string[MAX_URI_SIZE]) 94 | 95 | operators: map(address, map(address, bool)) 96 | 97 | 98 | 99 | @public 100 | def __init__(): 101 | self.tokensIdCount = 0 102 | self.supportedInterfaces[INTERFACE_ID_ERC165] = True 103 | self.supportedInterfaces[INTERFACE_ID_ERC1155] = True 104 | self.supportedInterfaces[INTERFACE_ID_ERC1155_METADATA] = True 105 | 106 | 107 | @public 108 | @constant 109 | def supportsInterface(_interfaceID: bytes32) -> bool: 110 | return self.supportedInterfaces[_interfaceID] 111 | 112 | 113 | # @notice Transfers `_value` amount of an `_id` from the `_from` address to the `_to` address specified (with safety call). 114 | # @dev Caller must be approved to manage the tokens being transferred out of the `_from` account (see "Approval" section of the standard). 115 | # MUST revert if `_to` is the zero address. 116 | # MUST revert if balance of holder for token `_id` is lower than the `_value` sent. 117 | # MUST revert on any other error. 118 | # MUST emit the `TransferSingle` event to reflect the balance change (see "Safe Transfer Rules" section of the standard). 119 | # After the above conditions are met, this function MUST check if `_to` is a smart contract (e.g. code size > 0). If so, it MUST call `onERC1155Received` on `_to` and act appropriately (see "Safe Transfer Rules" section of the standard). 120 | # @param _from Source address 121 | # @param _to Target address 122 | # @param _id ID of the token type 123 | # @param _value Transfer amount 124 | # @param _data Additional data with no specified format, MUST be sent unaltered in call to `onERC1155Received` on `_to` 125 | # function safeTransferFrom(address _from, address _to, uint256 _id, uint256 _value, bytes calldata _data) external; 126 | @public 127 | def safeTransferFrom( 128 | _from: address, 129 | _to: address, 130 | _id: uint256, 131 | _value: uint256, 132 | _data: bytes[256] 133 | ): 134 | assert _to != ZERO_ADDRESS 135 | assert self._balanceOf[_from][_id] >= _value 136 | 137 | if _to.is_contract: 138 | returnValue: bytes32 = ERC1155TokenReceiver(_to).onERC1155Received(msg.sender, _from, _id, _value, _data) 139 | assert returnValue == method_id("onERC1155Received(address,address,uint256,uint256,bytes)", bytes32) 140 | 141 | self._balanceOf[_from][_id] -= _value 142 | self._balanceOf[_to][_id] += _value 143 | log.TransferSingle(msg.sender, _from, _to, _id, _value) 144 | 145 | 146 | # @notice Transfers `_values` amount(s) of `_ids` from the `_from` address to the `_to` address specified (with safety call). 147 | # @dev Caller must be approved to manage the tokens being transferred out of the `_from` account (see "Approval" section of the standard). 148 | # MUST revert if `_to` is the zero address. 149 | # MUST revert if length of `_ids` is not the same as length of `_values`. 150 | # MUST revert if any of the balance(s) of the holder(s) for token(s) in `_ids` is lower than the respective amount(s) in `_values` sent to the recipient. 151 | # MUST revert on any other error. 152 | # MUST emit `TransferSingle` or `TransferBatch` event(s) such that all the balance changes are reflected (see "Safe Transfer Rules" section of the standard). 153 | # Balance changes and events MUST follow the ordering of the arrays (_ids[0]/_values[0] before _ids[1]/_values[1], etc). 154 | # After the above conditions for the transfer(s) in the batch are met, this function MUST check if `_to` is a smart contract (e.g. code size > 0). If so, it MUST call the relevant `ERC1155TokenReceiver` hook(s) on `_to` and act appropriately (see "Safe Transfer Rules" section of the standard). 155 | # @param _from Source address 156 | # @param _to Target address 157 | # @param _ids IDs of each token type (order and length must match _values array) 158 | # @param _values Transfer amounts per token type (order and length must match _ids array) 159 | # @param _data Additional data with no specified format, MUST be sent unaltered in call to the `ERC1155TokenReceiver` hook(s) on `_to` 160 | # function safeBatchTransferFrom(address _from, address _to, uint256[] calldata _ids, uint256[] calldata _values, bytes calldata _data) external; 161 | @public 162 | def safeBatchTransferFrom( 163 | _from: address, 164 | _to: address, 165 | _ids: uint256[BATCH_SIZE], 166 | _values: uint256[BATCH_SIZE], 167 | _data: bytes[256] 168 | ): 169 | assert _to != ZERO_ADDRESS 170 | #assert len(_ids) == len(_values) 171 | 172 | for i in range(BATCH_SIZE): 173 | assert self._balanceOf[_from][_ids[i]] >= _values[i] 174 | self._balanceOf[_from][_ids[i]] -= _values[i] 175 | self._balanceOf[_to][_ids[i]] += _values[i] 176 | if _to.is_contract: 177 | returnValue: bytes32 = ERC1155TokenReceiver(_to).onERC1155Received(msg.sender, _from, _ids[i], _values[i], _data) 178 | assert returnValue == method_id("onERC1155Received(address,address,uint256,uint256,bytes)", bytes32) 179 | 180 | log.TransferBatch(msg.sender, _from, _to, _ids, _values) 181 | 182 | 183 | # @notice Get the balance of an account's tokens. 184 | # @param _owner The address of the token holder 185 | # @param _id ID of the token 186 | # @return The _owner's balance of the token type requested 187 | # function balanceOf(address _owner, uint256 _id) external view returns (uint256); 188 | @public 189 | @constant 190 | def balanceOf( 191 | _owner: address, 192 | _id: uint256 193 | ) -> uint256: 194 | assert _owner != ZERO_ADDRESS 195 | return self._balanceOf[_owner][_id] 196 | 197 | 198 | # NOTE: This is not part of the standard 199 | # TODO: Right now everyone can set/change an uri 200 | # https://www.ietf.org/rfc/rfc3986.txt 201 | @public 202 | def setUri(_id: uint256, _newUri: string[MAX_URI_SIZE]): 203 | self._uri[_id] = _newUri 204 | log.URI(_newUri, _id) 205 | 206 | 207 | # @notice A distinct Uniform Resource Identifier (URI) for a given token. 208 | # @dev URIs are defined in RFC 3986. (https://www.ietf.org/rfc/rfc3986.txt) 209 | # The URI MUST point to a JSON file that conforms to the "ERC-1155 Metadata URI JSON Schema". 210 | # @return URI string 211 | # function uri(uint256 _id) external view returns (string memory); 212 | @public 213 | @constant 214 | def uri(_id: uint256) -> string[MAX_URI_SIZE]: 215 | return self._uri[_id] 216 | 217 | 218 | # @notice Get the balance of multiple account/token pairs 219 | # @param _owners The addresses of the token holders 220 | # @param _ids ID of the tokens 221 | # @return The _owner's balance of the token types requested (i.e. balance for each (owner, id) pair) 222 | # function balanceOfBatch(address[] calldata _owners, uint256[] calldata _ids) external view returns (uint256[] memory); 223 | @public 224 | @constant 225 | def balanceOfBatch( 226 | _owner: address[BATCH_SIZE], 227 | _ids: uint256[BATCH_SIZE] 228 | ) -> uint256[BATCH_SIZE]: 229 | returnValues: uint256[BATCH_SIZE] 230 | for i in range(BATCH_SIZE): 231 | returnValues[i] = self._balanceOf[_owner[i]][_ids[i]] 232 | return returnValues 233 | 234 | 235 | # @notice Enable or disable approval for a third party ("operator") to manage all of the caller's tokens. 236 | # @dev MUST emit the ApprovalForAll event on success. 237 | # @param _operator Address to add to the set of authorized operators 238 | # @param _approved True if the operator is approved, false to revoke approval 239 | # function setApprovalForAll(address _operator, bool _approved) external; 240 | @public 241 | def setApprovalForAll( 242 | _operator: address, 243 | _approved: bool 244 | ): 245 | (self.operators[msg.sender])[_operator] = _approved 246 | log.ApprovalForAll(msg.sender, _operator, _approved) 247 | 248 | 249 | # @notice Queries the approval status of an operator for a given owner. 250 | # @param _owner The owner of the tokens 251 | # @param _operator Address of authorized operator 252 | # @return True if the operator is approved, false if not 253 | # function isApprovedForAll(address _owner, address _operator) external view returns (bool); 254 | @public 255 | @constant 256 | def isApprovedForAll( 257 | _owner: address, 258 | _operator: address 259 | ) -> bool: 260 | return (self.operators[_owner])[_operator] 261 | 262 | 263 | # NOTE: This is not part of the standard 264 | # TODO: Right now everyone can mint 265 | @public 266 | def mint( 267 | _to: address, 268 | _supply: uint256, 269 | _data: bytes[256]="" 270 | ) -> uint256: 271 | self._balanceOf[msg.sender][self.tokensIdCount] = _supply 272 | self.tokensIdCount += 1 273 | log.TransferSingle(msg.sender, ZERO_ADDRESS, _to, self.tokensIdCount, _supply) 274 | return self.tokensIdCount 275 | 276 | 277 | # NOTE: This is not part of the standard 278 | # TODO: Right now everyone can mint 279 | @public 280 | def mintBatch( 281 | _to: address, 282 | _supplys: uint256[BATCH_SIZE], 283 | _data: bytes[256]="" 284 | ) -> uint256[BATCH_SIZE]: 285 | assert _to != ZERO_ADDRESS 286 | ids: uint256[BATCH_SIZE] 287 | for i in range(BATCH_SIZE): 288 | self._balanceOf[msg.sender][self.tokensIdCount] = _supplys[i] 289 | self.tokensIdCount += 1 290 | id: uint256 = self.tokensIdCount 291 | ids[i] = id 292 | 293 | log.TransferBatch(msg.sender, ZERO_ADDRESS, _to, ids, _supplys) 294 | return ids 295 | 296 | 297 | # TODO: specify a burn()/burnBatch() function 298 | -------------------------------------------------------------------------------- /erc1155/migrations/1_initial_migration.js: -------------------------------------------------------------------------------- 1 | var Migrations = artifacts.require("./Migrations.sol"); 2 | 3 | module.exports = function(deployer) { 4 | deployer.deploy(Migrations); 5 | }; 6 | -------------------------------------------------------------------------------- /erc1155/migrations/2_deploy_contracts.js: -------------------------------------------------------------------------------- 1 | var ERC1155 = artifacts.require("erc1155"); 2 | 3 | module.exports = function(deployer) { 4 | //deployer.deploy(ERC1155, name, symbol, decimals, totalSupply); 5 | }; 6 | -------------------------------------------------------------------------------- /erc1155/test/README.md: -------------------------------------------------------------------------------- 1 | ## Run tests 2 | ```bash 3 | $ truffle test --network ganache 4 | ``` 5 | -------------------------------------------------------------------------------- /erc1155/test/erc1155.js: -------------------------------------------------------------------------------- 1 | const ERC1155Abstraction = artifacts.require('erc1155'); 2 | 3 | contract('ERC1155', (accounts) => { 4 | /* 5 | beforeEach(async () => { 6 | ERC1155 = await ERC1155Abstraction.new( 7 | 8 | { from: accounts[0] } 9 | ); 10 | }); 11 | */ 12 | }); 13 | -------------------------------------------------------------------------------- /erc1155/truffle.js: -------------------------------------------------------------------------------- 1 | /** 2 | * Use this file to configure your truffle project. It's seeded with some 3 | * common settings for different networks and features like migrations, 4 | * compilation and testing. Uncomment the ones you need or modify 5 | * them to suit your project as necessary. 6 | * 7 | * More information about configuration can be found at: 8 | * 9 | * truffleframework.com/docs/advanced/configuration 10 | * 11 | * To deploy via Infura you'll need a wallet provider (like truffle-hdwallet-provider) 12 | * to sign your transactions before they're sent to a remote public node. Infura API 13 | * keys are available for free at: infura.io/register 14 | * 15 | * You'll also need a mnemonic - the twelve word phrase the wallet uses to generate 16 | * public/private key pairs. If you're publishing your code to GitHub make sure you load this 17 | * phrase from a file you've .gitignored so it doesn't accidentally become public. 18 | * 19 | */ 20 | 21 | module.exports = { 22 | networks: { 23 | ganache: { 24 | host: '127.0.0.1', 25 | port: 7545, 26 | network_id: '*', // match any network id 27 | from: '0x954e72fdc51Cf919203067406fB337Ed4bDC8CdA', // account address from which to deploy 28 | gas: 4000000, 29 | } 30 | } 31 | } 32 | -------------------------------------------------------------------------------- /erc20/.gitignore: -------------------------------------------------------------------------------- 1 | ./build 2 | -------------------------------------------------------------------------------- /erc20/README.md: -------------------------------------------------------------------------------- 1 | # ERC20 2 | https://eips.ethereum.org/EIPS/eip-20 3 | 4 | ## Run tests 5 | ```bash 6 | $ truffle test --network ganache 7 | ``` 8 | -------------------------------------------------------------------------------- /erc20/contracts/Migrations.sol: -------------------------------------------------------------------------------- 1 | pragma solidity >=0.4.21 <0.6.0; 2 | 3 | contract Migrations { 4 | address public owner; 5 | uint public last_completed_migration; 6 | 7 | constructor() public { 8 | owner = msg.sender; 9 | } 10 | 11 | modifier restricted() { 12 | if (msg.sender == owner) _; 13 | } 14 | 15 | function setCompleted(uint completed) public restricted { 16 | last_completed_migration = completed; 17 | } 18 | 19 | function upgrade(address new_address) public restricted { 20 | Migrations upgraded = Migrations(new_address); 21 | upgraded.setCompleted(last_completed_migration); 22 | } 23 | } 24 | -------------------------------------------------------------------------------- /erc20/contracts/erc20.vy: -------------------------------------------------------------------------------- 1 | # Author: Sören Steiger, github.com/ssteiger 2 | # License: MIT 3 | 4 | # ERC20 Token Standard 5 | # https://github.com/ethereum/EIPs/blob/master/EIPS/eip-20.md 6 | 7 | 8 | from vyper.interfaces import ERC20 9 | 10 | implements: ERC20 11 | 12 | # EVENTS: 13 | 14 | # ----- Transfer ----- 15 | # MUST trigger when tokens are transferred, including zero value transfers. 16 | # A token contract which creates new tokens SHOULD trigger a Transfer event 17 | # with the _from address set to 0x0 when tokens are created. 18 | Transfer: event({_from: indexed(address), _to: indexed(address), _value: uint256}) 19 | 20 | # ----- Approval ----- 21 | # MUST trigger on any successful call to approve(address _spender, uint256 _value). 22 | Approval: event({_owner: indexed(address), _spender: indexed(address), _value: uint256}) 23 | 24 | 25 | # STATE VARIABLES: 26 | # values which are permanently stored in contract storage 27 | 28 | # ----- name ----- 29 | # Returns the name of the token - e.g. "MyToken". 30 | # OPTIONAL - This method can be used to improve usability, but interfaces and 31 | # other contracts MUST NOT expect these values to be present. 32 | name: public(string[64]) # TODO: is this an acceptable size? 33 | 34 | # ----- symbol ----- 35 | # Returns the symbol of the token. E.g. "HIX". 36 | # OPTIONAL - This method can be used to improve usability, but interfaces and 37 | # other contracts MUST NOT expect these values to be present. 38 | symbol: public(string[32]) # TODO: is this an acceptable size? 39 | 40 | # ----- decimals ----- 41 | # Returns the number of decimals the token uses - e.g. 8, means to divide 42 | # the token amount by 100000000 to get its user representation. 43 | # OPTIONAL - This method can be used to improve usability, but interfaces and 44 | # other contracts MUST NOT expect these values to be present. 45 | decimals: public(uint256) 46 | 47 | # ----- totalSupply ----- 48 | # Returns the total token supply. 49 | totalSupply: public(uint256) 50 | 51 | # mappings 52 | balanceOf: public(map(address, uint256)) 53 | approvedFunds: map(address, map(address, uint256)) 54 | 55 | 56 | @public 57 | def __init__(_name: string[64], _symbol: string[32], _decimals: uint256, _totalSupply: uint256): 58 | self.name = _name 59 | self.symbol = _symbol 60 | self.decimals = _decimals 61 | #self.totalSupply = _totalSupply * 10 ** _decimals 62 | self.totalSupply = _totalSupply 63 | # mint all tokens to the contract creator 64 | self.balanceOf[msg.sender] = self.totalSupply 65 | # fire transfer event 66 | log.Transfer(ZERO_ADDRESS, msg.sender, self.totalSupply) 67 | 68 | 69 | # METHODS: 70 | 71 | # NOTES: 72 | # Callers MUST handle false from returns (bool success). 73 | # Callers MUST NOT assume that false is never returned! 74 | 75 | 76 | # ----- balanceOf ----- 77 | # Returns the account balance of another account with address _owner. 78 | # See: https://github.com/ethereum/vyper/issues/1241 79 | # And: https://vyper.readthedocs.io/en/v0.1.0-beta.8/types.html?highlight=getter#mappings 80 | 81 | 82 | # ----- transfer ----- 83 | # Transfers _value amount of tokens to address _to, and MUST fire the Transfer 84 | # event. The function SHOULD throw if the _from account balance does not have 85 | # enough tokens to spend. 86 | 87 | # NOTE: Transfers of 0 values MUST be treated as normal transfers and fire the 88 | # Transfer event. 89 | @public 90 | def transfer(_to: address, _value: uint256) -> bool: 91 | # NOTE: vyper does not allow unterflows 92 | # so checks for sufficient funds are done implicitly 93 | # see https://github.com/ethereum/vyper/issues/1237#issuecomment-461957413 94 | # substract balance from sender 95 | self.balanceOf[msg.sender] -= _value 96 | # add balance to recipient 97 | self.balanceOf[_to] += _value 98 | # fire transfer event 99 | log.Transfer(msg.sender, _to, _value) 100 | return True 101 | 102 | 103 | # ----- transferFrom ----- 104 | # Transfers _value amount of tokens from address _from to address _to, 105 | # and MUST fire the Transfer event. 106 | 107 | # The transferFrom method is used for a withdraw workflow, allowing contracts 108 | # to transfer tokens on your behalf. This can be used for example to allow a 109 | # contract to transfer tokens on your behalf and/or to charge fees in 110 | # sub-currencies. The function SHOULD throw unless the _from account has 111 | # deliberately authorized the sender of the message via some mechanism. 112 | 113 | # NOTE: Transfers of 0 values MUST be treated as normal transfers and fire the 114 | # Transfer event. 115 | @public 116 | def transferFrom(_from: address, _to: address, _value: uint256) -> bool: 117 | # NOTE: vyper does not allow unterflows 118 | # so checks for sufficient funds are done implicitly 119 | # see https://github.com/ethereum/vyper/issues/1237#issuecomment-461957413 120 | # update approved funds 121 | self.approvedFunds[_from][msg.sender] -= _value 122 | # update sender balance 123 | self.balanceOf[_from] -= _value 124 | # update recipient balance 125 | self.balanceOf[_to] += _value 126 | # fire transfer event 127 | log.Transfer(_from, _to, _value) 128 | return True 129 | 130 | 131 | # ----- approve ----- 132 | # Allows _spender to withdraw from your account multiple times, up to the _value 133 | # amount. If this function is called again it overwrites the current allowance 134 | # with _value. 135 | 136 | # NOTE: To prevent attack vectors like the one described here and discussed here, 137 | # clients SHOULD make sure to create user interfaces in such a way that they set 138 | # the allowance first to 0 before setting it to another value for the same 139 | # spender. THOUGH The contract itself shouldn't enforce it, to allow backwards 140 | # compatibility with contracts deployed before. 141 | @public 142 | def approve(_spender: address, _value: uint256) -> bool: 143 | # overwrites the current allowance 144 | self.approvedFunds[msg.sender][_spender] = _value 145 | # fire approval event 146 | log.Approval(msg.sender, _spender, _value) 147 | return True 148 | 149 | 150 | # ----- allowance ----- 151 | # Returns the amount which _spender is still allowed to withdraw from _owner. 152 | @public 153 | @constant 154 | def allowance(_owner: address, _spender: address) -> uint256: 155 | return self.approvedFunds[_owner][_spender] 156 | -------------------------------------------------------------------------------- /erc20/migrations/1_initial_migration.js: -------------------------------------------------------------------------------- 1 | var Migrations = artifacts.require("./Migrations.sol"); 2 | 3 | module.exports = function(deployer) { 4 | deployer.deploy(Migrations); 5 | }; 6 | -------------------------------------------------------------------------------- /erc20/migrations/2_deploy_contracts.js: -------------------------------------------------------------------------------- 1 | var ERC20 = artifacts.require("erc20"); 2 | 3 | module.exports = function(deployer) { 4 | const name = "MyToken"; 5 | const symbol = "MT"; 6 | const decimals = 18; 7 | const totalSupply = 100000000; 8 | deployer.deploy(ERC20, name, symbol, decimals, totalSupply); 9 | }; 10 | -------------------------------------------------------------------------------- /erc20/test/README.md: -------------------------------------------------------------------------------- 1 | Test in this folder are taken from [ConsenSys/Tokens](https://github.com/ConsenSys/Tokens/blob/master/test/eip20/eip20.js). 2 | -------------------------------------------------------------------------------- /erc20/test/erc20-consensys.js: -------------------------------------------------------------------------------- 1 | // NOTE: The following tests are taken from the consensys token repository 2 | // https://github.com/ConsenSys/Tokens/blob/master/test/eip20/eip20.js 3 | 4 | const { assertRevert } = require('./helpers/assertRevert'); 5 | 6 | const EIP20Abstraction = artifacts.require('erc20'); 7 | 8 | const args = { 9 | name: "Simon Bucks", 10 | symbol: "SBX", 11 | decimals: 1, 12 | totalSupply: 10000, 13 | } 14 | 15 | let HST; 16 | 17 | contract('ERC20', (accounts) => { 18 | beforeEach(async () => { 19 | HST = await EIP20Abstraction.new( 20 | args.name, 21 | args.symbol, 22 | args.decimals, 23 | args.totalSupply, 24 | { from: accounts[0] } 25 | ); 26 | }); 27 | 28 | it('creation: should create an initial balance of 10000 for the creator', async () => { 29 | const balance = await HST.balanceOf.call(accounts[0]); 30 | assert.strictEqual(balance.toNumber(), 10000); 31 | }); 32 | 33 | it('creation: test correct setting of vanity information', async () => { 34 | const name = await HST.name.call(); 35 | assert.strictEqual(name, 'Simon Bucks'); 36 | 37 | const decimals = await HST.decimals.call(); 38 | assert.strictEqual(decimals.toNumber(), 1); 39 | 40 | const symbol = await HST.symbol.call(); 41 | assert.strictEqual(symbol, 'SBX'); 42 | }); 43 | 44 | it('creation: should succeed in creating over 2^256 - 1 (max) tokens', async () => { 45 | // 2^256 - 1 46 | const maxTotalSupply = '115792089237316195423570985008687907853269984665640564039457584007913129639935' 47 | const HST2 = await EIP20Abstraction.new( 48 | args.name, 49 | args.symbol, 50 | args.decimals, 51 | maxTotalSupply, 52 | { from: accounts[0] } 53 | ); 54 | let totalSupply = await HST2.totalSupply(); 55 | totalSupply = totalSupply.toString(); 56 | assert.strictEqual(totalSupply, maxTotalSupply); 57 | //const match = totalSupply.equals('1.15792089237316195423570985008687907853269984665640564039457584007913129639935e+77'); 58 | //assert(match, 'result is not correct'); 59 | }); 60 | 61 | // TRANSFERS 62 | // normal transfers without approvals 63 | it('transfers: ether transfer should be reversed.', async () => { 64 | const balanceBefore = await HST.balanceOf.call(accounts[0]); 65 | assert.strictEqual(balanceBefore.toNumber(), 10000); 66 | 67 | await assertRevert(new Promise((resolve, reject) => { 68 | web3.eth.sendTransaction({ from: accounts[0], to: HST.address, value: web3.utils.toWei('10', 'Ether') }, (err, res) => { 69 | if (err) { reject(err); } 70 | resolve(res); 71 | }); 72 | })); 73 | 74 | const balanceAfter = await HST.balanceOf.call(accounts[0]); 75 | assert.strictEqual(balanceAfter.toNumber(), 10000); 76 | }); 77 | 78 | it('transfers: should transfer 10000 to accounts[1] with accounts[0] having 10000', async () => { 79 | await HST.transfer(accounts[1], 10000, { from: accounts[0] }); 80 | const balance = await HST.balanceOf.call(accounts[1]); 81 | assert.strictEqual(balance.toNumber(), 10000); 82 | }); 83 | 84 | it('transfers: should fail when trying to transfer 10001 to accounts[1] with accounts[0] having 10000', async () => { 85 | await assertRevert(HST.transfer.call(accounts[1], 10001, { from: accounts[0] })); 86 | }); 87 | 88 | it('transfers: should handle zero-transfers normally', async () => { 89 | assert(await HST.transfer.call(accounts[1], 0, { from: accounts[0] }), 'zero-transfer has failed'); 90 | }); 91 | 92 | // NOTE: testing uint256 wrapping is impossible since you can't supply > 2^256 -1 93 | // todo: transfer max amounts 94 | 95 | // APPROVALS 96 | it('approvals: msg.sender should approve 100 to accounts[1]', async () => { 97 | await HST.approve(accounts[1], 100, { from: accounts[0] }); 98 | const allowance = await HST.allowance.call(accounts[0], accounts[1]); 99 | assert.strictEqual(allowance.toNumber(), 100); 100 | }); 101 | 102 | // bit overkill. But is for testing a bug 103 | it('approvals: msg.sender approves accounts[1] of 100 & withdraws 20 once.', async () => { 104 | const balance0 = await HST.balanceOf.call(accounts[0]); 105 | assert.strictEqual(balance0.toNumber(), 10000); 106 | 107 | await HST.approve(accounts[1], 100, { from: accounts[0] }); // 100 108 | const balance2 = await HST.balanceOf.call(accounts[2]); 109 | assert.strictEqual(balance2.toNumber(), 0, 'balance2 not correct'); 110 | 111 | await HST.transferFrom.call(accounts[0], accounts[2], 20, { from: accounts[1] }); 112 | await HST.allowance.call(accounts[0], accounts[1]); 113 | await HST.transferFrom(accounts[0], accounts[2], 20, { from: accounts[1] }); // -20 114 | const allowance01 = await HST.allowance.call(accounts[0], accounts[1]); 115 | assert.strictEqual(allowance01.toNumber(), 80); // =80 116 | 117 | const balance22 = await HST.balanceOf.call(accounts[2]); 118 | assert.strictEqual(balance22.toNumber(), 20); 119 | 120 | const balance02 = await HST.balanceOf.call(accounts[0]); 121 | assert.strictEqual(balance02.toNumber(), 9980); 122 | }); 123 | 124 | // should approve 100 of msg.sender & withdraw 50, twice. (should succeed) 125 | it('approvals: msg.sender approves accounts[1] of 100 & withdraws 20 twice.', async () => { 126 | await HST.approve(accounts[1], 100, { from: accounts[0] }); 127 | const allowance01 = await HST.allowance.call(accounts[0], accounts[1]); 128 | assert.strictEqual(allowance01.toNumber(), 100); 129 | 130 | await HST.transferFrom(accounts[0], accounts[2], 20, { from: accounts[1] }); 131 | const allowance012 = await HST.allowance.call(accounts[0], accounts[1]); 132 | assert.strictEqual(allowance012.toNumber(), 80); 133 | 134 | const balance2 = await HST.balanceOf.call(accounts[2]); 135 | assert.strictEqual(balance2.toNumber(), 20); 136 | 137 | const balance0 = await HST.balanceOf.call(accounts[0]); 138 | assert.strictEqual(balance0.toNumber(), 9980); 139 | 140 | // FIRST tx done. 141 | // onto next. 142 | await HST.transferFrom(accounts[0], accounts[2], 20, { from: accounts[1] }); 143 | const allowance013 = await HST.allowance.call(accounts[0], accounts[1]); 144 | assert.strictEqual(allowance013.toNumber(), 60); 145 | 146 | const balance22 = await HST.balanceOf.call(accounts[2]); 147 | assert.strictEqual(balance22.toNumber(), 40); 148 | 149 | const balance02 = await HST.balanceOf.call(accounts[0]); 150 | assert.strictEqual(balance02.toNumber(), 9960); 151 | }); 152 | 153 | // should approve 100 of msg.sender & withdraw 50 & 60 (should fail). 154 | it('approvals: msg.sender approves accounts[1] of 100 & withdraws 50 & 60 (2nd tx should fail)', async () => { 155 | await HST.approve(accounts[1], 100, { from: accounts[0] }); 156 | const allowance01 = await HST.allowance.call(accounts[0], accounts[1]); 157 | assert.strictEqual(allowance01.toNumber(), 100); 158 | 159 | await HST.transferFrom(accounts[0], accounts[2], 50, { from: accounts[1] }); 160 | const allowance012 = await HST.allowance.call(accounts[0], accounts[1]); 161 | assert.strictEqual(allowance012.toNumber(), 50); 162 | 163 | const balance2 = await HST.balanceOf.call(accounts[2]); 164 | assert.strictEqual(balance2.toNumber(), 50); 165 | 166 | const balance0 = await HST.balanceOf.call(accounts[0]); 167 | assert.strictEqual(balance0.toNumber(), 9950); 168 | 169 | // FIRST tx done. 170 | // onto next. 171 | await assertRevert(HST.transferFrom.call(accounts[0], accounts[2], 60, { from: accounts[1] })); 172 | }); 173 | 174 | it('approvals: attempt withdrawal from account with no allowance (should fail)', async () => { 175 | await assertRevert(HST.transferFrom.call(accounts[0], accounts[2], 60, { from: accounts[1] })); 176 | }); 177 | 178 | it('approvals: allow accounts[1] 100 to withdraw from accounts[0]. Withdraw 60 and then approve 0 & attempt transfer.', async () => { 179 | await HST.approve(accounts[1], 100, { from: accounts[0] }); 180 | await HST.transferFrom(accounts[0], accounts[2], 60, { from: accounts[1] }); 181 | await HST.approve(accounts[1], 0, { from: accounts[0] }); 182 | await assertRevert(HST.transferFrom.call(accounts[0], accounts[2], 10, { from: accounts[1] })); 183 | }); 184 | 185 | it('approvals: approve max (2^256 - 1)', async () => { 186 | // 2^256 - 1 187 | const maxAllowance = '115792089237316195423570985008687907853269984665640564039457584007913129639935' 188 | await HST.approve(accounts[1], maxAllowance, { from: accounts[0] }); 189 | let allowance = await HST.allowance(accounts[0], accounts[1]); 190 | allowance = allowance.toString(); 191 | assert.strictEqual(allowance, maxAllowance); 192 | //assert(allowance.equals('1.15792089237316195423570985008687907853269984665640564039457584007913129639935e+77')); 193 | }); 194 | 195 | // should approve max of msg.sender & withdraw 20 without changing allowance (should succeed). 196 | it('approvals: msg.sender approves accounts[1] of max (2^256 - 1) & withdraws 20', async () => { 197 | const balance0 = await HST.balanceOf.call(accounts[0]); 198 | assert.strictEqual(balance0.toNumber(), 10000); 199 | 200 | const maxAllowance = '115792089237316195423570985008687907853269984665640564039457584007913129639935'; 201 | await HST.approve(accounts[1], maxAllowance, { from: accounts[0] }); 202 | const balance2 = await HST.balanceOf.call(accounts[2]); 203 | assert.strictEqual(balance2.toNumber(), 0, 'balance2 not correct'); 204 | 205 | await HST.transferFrom(accounts[0], accounts[2], 20, { from: accounts[1] }); 206 | let allowance01 = await HST.allowance.call(accounts[0], accounts[1]); 207 | //allowance01 = allowance01.toNumber(); 208 | assert.strictEqual(allowance01.toString(), '115792089237316195423570985008687907853269984665640564039457584007913129639915'); 209 | 210 | const balance22 = await HST.balanceOf.call(accounts[2]); 211 | assert.strictEqual(balance22.toNumber(), 20); 212 | 213 | const balance02 = await HST.balanceOf.call(accounts[0]); 214 | assert.strictEqual(balance02.toNumber(), 9980); 215 | }); 216 | 217 | /* eslint-disable no-underscore-dangle */ 218 | it('events: should fire Transfer event properly', async () => { 219 | const res = await HST.transfer(accounts[1], '2666', { from: accounts[0] }); 220 | const transferLog = res.logs.find(element => element.event.match('Transfer')); 221 | assert.strictEqual(transferLog.args._from, accounts[0]); 222 | assert.strictEqual(transferLog.args._to, accounts[1]); 223 | assert.strictEqual(transferLog.args._value.toString(), '2666'); 224 | }); 225 | 226 | it('events: should fire Transfer event normally on a zero transfer', async () => { 227 | const res = await HST.transfer(accounts[1], '0', { from: accounts[0] }); 228 | const transferLog = res.logs.find(element => element.event.match('Transfer')); 229 | assert.strictEqual(transferLog.args._from, accounts[0]); 230 | assert.strictEqual(transferLog.args._to, accounts[1]); 231 | assert.strictEqual(transferLog.args._value.toString(), '0'); 232 | }); 233 | 234 | it('events: should fire Approval event properly', async () => { 235 | const res = await HST.approve(accounts[1], '2666', { from: accounts[0] }); 236 | const approvalLog = res.logs.find(element => element.event.match('Approval')); 237 | assert.strictEqual(approvalLog.args._owner, accounts[0]); 238 | assert.strictEqual(approvalLog.args._spender, accounts[1]); 239 | assert.strictEqual(approvalLog.args._value.toString(), '2666'); 240 | }); 241 | }); 242 | -------------------------------------------------------------------------------- /erc20/test/helpers/assertRevert.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | assertRevert: async (promise) => { 3 | try { 4 | await promise; 5 | } catch (error) { 6 | const revertFound = error.message.search('revert') >= 0; 7 | assert(revertFound, `Expected "revert", got ${error} instead`); 8 | return; 9 | } 10 | assert.fail('Expected revert not received'); 11 | }, 12 | }; 13 | -------------------------------------------------------------------------------- /erc20/truffle.js: -------------------------------------------------------------------------------- 1 | /** 2 | * Use this file to configure your truffle project. It's seeded with some 3 | * common settings for different networks and features like migrations, 4 | * compilation and testing. Uncomment the ones you need or modify 5 | * them to suit your project as necessary. 6 | * 7 | * More information about configuration can be found at: 8 | * 9 | * truffleframework.com/docs/advanced/configuration 10 | * 11 | * To deploy via Infura you'll need a wallet provider (like truffle-hdwallet-provider) 12 | * to sign your transactions before they're sent to a remote public node. Infura API 13 | * keys are available for free at: infura.io/register 14 | * 15 | * You'll also need a mnemonic - the twelve word phrase the wallet uses to generate 16 | * public/private key pairs. If you're publishing your code to GitHub make sure you load this 17 | * phrase from a file you've .gitignored so it doesn't accidentally become public. 18 | * 19 | */ 20 | 21 | module.exports = { 22 | networks: { 23 | ganache: { 24 | host: '127.0.0.1', 25 | port: 7545, 26 | network_id: '*', // match any network id 27 | from: '0x954e72fdc51Cf919203067406fB337Ed4bDC8CdA', // account address from which to deploy 28 | gas: 4000000, 29 | } 30 | } 31 | } 32 | -------------------------------------------------------------------------------- /erc721/.gitignore: -------------------------------------------------------------------------------- 1 | ./build 2 | -------------------------------------------------------------------------------- /erc721/README.md: -------------------------------------------------------------------------------- 1 | # ERC721 2 | 3 | This contract implements the ERC721 standard. 4 | It allowes for the creation of so called non fungible tokens, or NFT's. 5 | The difference of NFT's to other token standards like ERC20 or ERC777 is that each token is unique and not interchangable. 6 | 7 | 8 | ## Getting started 9 | 10 | ### Run tests 11 | 12 | ```bash 13 | $ truffle test --network ganache 14 | ``` 15 | 16 | | Further resources | 17 | | - | 18 | | [EIP](https://eips.ethereum.org/EIPS/eip-721) | 19 | | [What are ERC721 standard tokens?](https://hackernoon.com/what-are-erc721-standard-tokens-3624adcc3e54) | 20 | | [The Anatomy of ERC721](https://medium.com/crypto-currently/the-anatomy-of-erc721-e9db77abfc24) | 21 | | [Noobs Guide to Understanding ERC-20 vs ERC-721 Tokens](https://medium.com/@brenn.a.hill/noobs-guide-to-understanding-erc-20-vs-erc-721-tokens-d7f5657a4ee7) | 22 | | [Marketplace for trading ERC721 tokens](https://opensea.io/) | 23 | 24 | | Implementations | 25 | | - | 26 | | [CryptoKitties](https://www.cryptokitties.co/) | 27 | | [Decentraland](https://market.decentraland.org/) | 28 | | [Ethermon](https://www.etheremon.com/) | 29 | -------------------------------------------------------------------------------- /erc721/contracts/Migrations.sol: -------------------------------------------------------------------------------- 1 | pragma solidity >=0.4.21 <0.6.0; 2 | 3 | contract Migrations { 4 | address public owner; 5 | uint public last_completed_migration; 6 | 7 | constructor() public { 8 | owner = msg.sender; 9 | } 10 | 11 | modifier restricted() { 12 | if (msg.sender == owner) _; 13 | } 14 | 15 | function setCompleted(uint completed) public restricted { 16 | last_completed_migration = completed; 17 | } 18 | 19 | function upgrade(address new_address) public restricted { 20 | Migrations upgraded = Migrations(new_address); 21 | upgraded.setCompleted(last_completed_migration); 22 | } 23 | } 24 | -------------------------------------------------------------------------------- /erc721/contracts/erc721.vy: -------------------------------------------------------------------------------- 1 | # Author: Sören Steiger, github.com/ssteiger 2 | # License: MIT 3 | 4 | # ERC721 Token Standard 5 | # https://github.com/ethereum/EIPs/blob/master/EIPS/eip-721.md 6 | 7 | # @dev Note: the ERC-165 identifier for this interface is 0x150b7a02. 8 | # @notice Handle the receipt of an NFT 9 | # @dev The ERC721 smart contract calls this function on the recipient 10 | # after a `transfer`. 11 | # This function MAY throw to revert and reject the transfer. 12 | # Return of other than the magic value MUST result in the transaction 13 | # being reverted. 14 | # Note: the contract address is always the message sender. 15 | # @param _operator The address which called `safeTransferFrom` function 16 | # @param _from The address which previously owned the token 17 | # @param _tokenId The NFT identifier which is being transferred 18 | # @param _data Additional data with no specified format 19 | # @return `bytes4(keccak256("onERC721Received(address,address,uint256,bytes)"))` 20 | # unless throwing 21 | # function onERC721Received(address _operator, address _from, uint256 _tokenId, bytes _data) external returns(bytes4); 22 | contract ERC721TokenReceiver: 23 | def onERC721Received( 24 | _operator: address, 25 | _from: address, 26 | _tokenId: uint256, 27 | _data: bytes[256] 28 | ) -> bytes32: constant 29 | 30 | 31 | # EVENTS: 32 | 33 | # @dev This emits when ownership of any NFT changes by any mechanism. 34 | # This event emits when NFTs are created (`from` == 0) and destroyed 35 | # (`to` == 0). 36 | # Exception: during contract creation, any number of NFTs may be created 37 | # and assigned without emitting Transfer. 38 | # At the time of any transfer, the approved address for that NFT (if any) 39 | # is reset to none. 40 | Transfer: event({ 41 | _from: indexed(address), 42 | _to: indexed(address), 43 | _tokenId: indexed(uint256) 44 | }) 45 | 46 | 47 | # NOTE: This is not part of the standard 48 | Mint: event({ 49 | _to: indexed(address), 50 | _tokenId: indexed(uint256) 51 | }) 52 | 53 | 54 | # @dev This emits when the approved address for an NFT is changed or reaffirmed. 55 | # The zero address indicates there is no approved address. 56 | # When a Transfer event emits, this also indicates that the approved 57 | # address for that NFT (if any) is reset to none. 58 | Approval: event({ 59 | _owner: indexed(address), 60 | _approved: indexed(address), 61 | _tokenId: indexed(uint256) 62 | }) 63 | 64 | 65 | # @dev This emits when an operator is enabled or disabled for an owner. 66 | # The operator can manage all NFTs of the owner. 67 | ApprovalForAll: event({ 68 | _owner: indexed(address), 69 | _operator: indexed(address), 70 | _approved: bool 71 | }) 72 | 73 | 74 | # STATE VARIABLES: 75 | 76 | # NOTE: This is not part of the standard 77 | contractOwner: public(address) 78 | # Used for token id's 79 | nftSupply: uint256 80 | 81 | # Used to keep track of the number of tokens an address holds 82 | nftCount: public(map(address, uint256)) 83 | ownerOfNFT: public(map(uint256, address)) 84 | 85 | operatorFor: public(map(uint256, address)) 86 | approvedForAll: public(map(address, map(address, bool))) 87 | 88 | # Interface detection as specified in ERC165 89 | # https://github.com/ethereum/EIPs/blob/master/EIPS/eip-165.md 90 | supportedInterfaces: public(map(bytes32, bool)) 91 | # ERC165 interface ID's 92 | ERC165_INTERFACE_ID: constant(bytes32) = 0x0000000000000000000000000000000000000000000000000000000001ffc9a7 93 | ERC721_INTERFACE_ID: constant(bytes32) = 0x0000000000000000000000000000000000000000000000000000000080ac58cd 94 | 95 | 96 | # METHODS: 97 | @public 98 | def __init__(): 99 | # set initial supply (used for token id's) 100 | self.nftSupply = 0 101 | # set supported interfaces 102 | self.supportedInterfaces[ERC165_INTERFACE_ID] = True 103 | self.supportedInterfaces[ERC721_INTERFACE_ID] = True 104 | # set contract owner 105 | # NOTE: This is not part of the standard 106 | # only contractOwner can call mint() 107 | self.contractOwner = msg.sender 108 | 109 | 110 | @private 111 | def _checkIfIsOwnerOrOperatorOrApprovedForAll(_msgSender: address, _from: address, _tokenId: uint256): 112 | # Throws unless `msg.sender` is 113 | # the current owner 114 | isOwner: bool = self.ownerOfNFT[_tokenId] == _msgSender 115 | # an authorized operator 116 | isOperator: bool = self.operatorFor[_tokenId] == _msgSender 117 | # or the approved address for this NFT 118 | isApprovedForAll: bool = (self.approvedForAll[_from])[_msgSender] 119 | assert (isOwner or isOperator or isApprovedForAll) 120 | 121 | 122 | @private 123 | def _setNewOwner(_currentOwner: address, _newOwner: address, _tokenId: uint256): 124 | # set new owner 125 | self.ownerOfNFT[_tokenId] = _newOwner 126 | # updated balances 127 | self.nftCount[_currentOwner] -= 1 128 | self.nftCount[_newOwner] += 1 129 | # reset operator 130 | self.operatorFor[_tokenId] = ZERO_ADDRESS 131 | 132 | 133 | @private 134 | def _transfer(_from: address, _to: address, _tokenId: uint256): 135 | # Throws if `_from` is not the current owner. 136 | assert self.ownerOfNFT[_tokenId] == _from 137 | # Throws if `_to` is the zero address. 138 | assert _to != ZERO_ADDRESS 139 | # Throws if `_tokenId` is not a valid NFT. 140 | assert self.ownerOfNFT[_tokenId] != ZERO_ADDRESS 141 | # transfer to new owner 142 | self._setNewOwner(_from, _to, _tokenId) 143 | # log transfer 144 | log.Transfer(_from, _to, _tokenId) 145 | 146 | 147 | @public 148 | @constant 149 | def supportsInterface(_interfaceID: bytes32) -> bool: 150 | # Interface detection as specified in ERC165 151 | # https://github.com/ethereum/EIPs/blob/master/EIPS/eip-165.md 152 | return self.supportedInterfaces[_interfaceID] 153 | 154 | 155 | # @notice Count all NFTs assigned to an owner 156 | # @dev NFTs assigned to the zero address are considered invalid, and this 157 | # function throws for queries about the zero address. 158 | # @param _owner An address for whom to query the balance 159 | # @return The number of NFTs owned by `_owner`, possibly zero 160 | # function balanceOf(address _owner) external view returns (uint256); 161 | @public 162 | @constant 163 | def balanceOf(_owner: address) -> uint256: 164 | # NFTs assigned to the zero address are considered invalid, and this 165 | # function throws for queries about the zero address. 166 | assert _owner != ZERO_ADDRESS 167 | return self.nftCount[_owner] 168 | 169 | 170 | # @notice Find the owner of an NFT 171 | # @dev NFTs assigned to zero address are considered invalid, and queries 172 | # about them do throw. 173 | # @param _tokenId The identifier for an NFT 174 | # @return The address of the owner of the NFT 175 | # function ownerOf(uint256 _tokenId) external view returns (address); 176 | @public 177 | @constant 178 | def ownerOf(_tokenId: uint256) -> address: 179 | # NFTs assigned to the zero address are considered invalid, and this 180 | # function throws for queries about the zero address. 181 | owner: address = self.ownerOfNFT[_tokenId] 182 | assert owner != ZERO_ADDRESS 183 | return owner 184 | 185 | 186 | # @notice Transfers the ownership of an NFT from one address to another address 187 | # @dev Throws unless `msg.sender` is 188 | # the current owner, 189 | # an authorized operator, 190 | # or the approved address for this NFT. 191 | # Throws if `_from` is not the current owner. 192 | # Throws if `_to` is the zero address. 193 | # Throws if `_tokenId` is not a valid NFT. 194 | # When transfer is complete, this function checks if `_to` is 195 | # a smart contract (code size > 0). If so, it calls 196 | # `onERC721Received` on `_to` and throws if the return value is not 197 | # `bytes4(keccak256("onERC721Received(address,address,uint256,bytes)"))`. 198 | # @param _from The current owner of the NFT 199 | # @param _to The new owner 200 | # @param _tokenId The NFT to transfer 201 | # @param data Additional data with no specified format, sent in call to `_to` 202 | # function safeTransferFrom(address _from, address _to, uint256 _tokenId, bytes data) external payable; 203 | @public 204 | @payable 205 | def safeTransferFrom(_from: address, _to: address, _tokenId: uint256, _data: bytes[256]=""): 206 | # Throws unless `msg.sender` is 207 | # the current owner, 208 | # an authorized operator, 209 | # or the approved address for this NFT. 210 | self._checkIfIsOwnerOrOperatorOrApprovedForAll(msg.sender, _from, _tokenId) 211 | # transfer 212 | self._transfer(_from, _to, _tokenId) 213 | # When transfer is complete, 214 | # this function checks if `_to` is a smart contract (code size > 0) 215 | if _to.is_contract: 216 | # If so, it calls `onERC721Received` on `_to` and throws if the return value is not 217 | # `bytes4(keccak256("onERC721Received(address,address,uint256,bytes)"))`. 218 | returnValue: bytes32 = ERC721TokenReceiver(_to).onERC721Received(msg.sender, _from, _tokenId, _data) 219 | assert returnValue == method_id("onERC721Received(address,address,uint256,bytes)", bytes32) 220 | 221 | 222 | # @notice Transfers the ownership of an NFT from one address to another address 223 | # @dev This works identically to the other function with an extra data 224 | # parameter, except this function just sets data to "". 225 | # @param _from The current owner of the NFT 226 | # @param _to The new owner 227 | # @param _tokenId The NFT to transfer 228 | # function safeTransferFrom(address _from, address _to, uint256 _tokenId) external payable; 229 | 230 | 231 | # @notice Transfer ownership of an NFT -- THE CALLER IS RESPONSIBLE 232 | # TO CONFIRM THAT `_to` IS CAPABLE OF RECEIVING NFTS OR ELSE 233 | # THEY MAY BE PERMANENTLY LOST 234 | # @dev Throws unless 235 | # `msg.sender` is the current owner, 236 | # an authorized operator, 237 | # or the approved address for this NFT. 238 | # Throws if `_from` is not the current owner. 239 | # Throws if `_to` is the zero address. 240 | # Throws if `_tokenId` is not a valid NFT. 241 | # @param _from The current owner of the NFT 242 | # @param _to The new owner 243 | # @param _tokenId The NFT to transfer 244 | # function transferFrom(address _from, address _to, uint256 _tokenId) external payable; 245 | @public 246 | @payable 247 | def transferFrom(_from: address, _to: address, _tokenId: uint256): 248 | # Throws unless `msg.sender` is 249 | # the current owner, 250 | # an authorized operator, 251 | # or the approved address for this NFT. 252 | self._checkIfIsOwnerOrOperatorOrApprovedForAll(msg.sender, _from, _tokenId) 253 | # do transfer 254 | self._transfer(_from, _to, _tokenId) 255 | 256 | 257 | # @notice Change or reaffirm the approved address for an NFT 258 | # @dev The zero address indicates there is no approved address. 259 | # Throws unless `msg.sender` is 260 | # the current NFT owner, 261 | # or an authorized operator of the current owner. 262 | # @param _approved The new approved NFT controller 263 | # @param _tokenId The NFT to approve 264 | # function approve(address _approved, uint256 _tokenId) external payable; 265 | @public 266 | @payable 267 | def approve(_approved: address, _tokenId: uint256): 268 | # Throws if _tokenId is not owned / a valid NFT 269 | assert self.ownerOfNFT[_tokenId] != ZERO_ADDRESS 270 | # Throws unless `msg.sender` is the current NFT owner 271 | isOwner: bool = self.ownerOfNFT[_tokenId] == msg.sender 272 | # or an authorized operator of the current owner. 273 | isOperator: bool = self.operatorFor[_tokenId] == msg.sender 274 | # TODO: does the this include approvedForAll? 275 | assert (isOwner or isOperator) 276 | # set new approved address 277 | self.operatorFor[_tokenId] = _approved 278 | # log change 279 | log.Approval(msg.sender, _approved, _tokenId) 280 | 281 | 282 | # @notice Enable or disable approval for a third party ("operator") to manage 283 | # all of `msg.sender`'s assets 284 | # @dev Emits the ApprovalForAll event. 285 | # The contract MUST allow multiple operators per owner. 286 | # @param _operator Address to add to the set of authorized operators 287 | # @param _approved True if the operator is approved, false to revoke approval 288 | # function setApprovalForAll(address _operator, bool _approved) external; 289 | @public 290 | def setApprovalForAll(_operator: address, _approved: bool): 291 | # The contract MUST allow multiple operators per owner. 292 | self.approvedForAll[msg.sender][_operator] = _approved 293 | # log change 294 | log.ApprovalForAll(msg.sender, _operator, _approved) 295 | 296 | 297 | # @notice Get the approved address for a single NFT 298 | # @dev Throws if `_tokenId` is not a valid NFT. 299 | # @param _tokenId The NFT to find the approved address for 300 | # @return The approved address for this NFT, or the zero address if 301 | # there is none 302 | # function getApproved(uint256 _tokenId) external view returns (address); 303 | @public 304 | @constant 305 | def getApproved(_tokenId: uint256) -> address: 306 | # Throws if `_tokenId` is not a valid NFT. 307 | assert self.ownerOfNFT[_tokenId] != ZERO_ADDRESS 308 | return self.operatorFor[_tokenId] 309 | 310 | 311 | # @notice Query if an address is an authorized operator for another address 312 | # @param _owner The address that owns the NFTs 313 | # @param _operator The address that acts on behalf of the owner 314 | # @return True if `_operator` is an approved operator for `_owner`, 315 | # false otherwise 316 | # function isApprovedForAll(address _owner, address _operator) external view returns (bool); 317 | @public 318 | @constant 319 | def isApprovedForAll(_owner: address, _operator: address) -> bool: 320 | return (self.approvedForAll[_owner])[_operator] 321 | 322 | 323 | # NOTE: This is not part of the standard 324 | @public 325 | def mint() -> uint256: 326 | # only contractOwner is allowed to mint 327 | assert msg.sender == self.contractOwner 328 | # update supply 329 | tokenId: uint256 = self.nftSupply 330 | self.nftSupply += 1 331 | # update ownership 332 | self.ownerOfNFT[tokenId] = msg.sender 333 | self.nftCount[msg.sender] += 1 334 | self.operatorFor[tokenId] = ZERO_ADDRESS 335 | # log mint 336 | log.Mint(msg.sender, tokenId) 337 | return tokenId 338 | -------------------------------------------------------------------------------- /erc721/migrations/1_initial_migration.js: -------------------------------------------------------------------------------- 1 | var Migrations = artifacts.require("./Migrations.sol"); 2 | 3 | module.exports = function(deployer) { 4 | deployer.deploy(Migrations); 5 | }; 6 | -------------------------------------------------------------------------------- /erc721/migrations/2_deploy_contracts.js: -------------------------------------------------------------------------------- 1 | var ERC721 = artifacts.require("erc721"); 2 | 3 | module.exports = function(deployer) { 4 | deployer.deploy(ERC721); 5 | }; 6 | -------------------------------------------------------------------------------- /erc721/test/erc721.js: -------------------------------------------------------------------------------- 1 | const erc721 = artifacts.require("erc721"); 2 | 3 | let owner, 4 | receiver, 5 | operator, 6 | erc721Token; 7 | 8 | const ZERO_ADDRESS = '0x0000000000000000000000000000000000000000'; 9 | const ERC165_INTERFACE_ID = '0x0000000000000000000000000000000000000000000000000000000001ffc9a7'; 10 | const ERC721_INTERFACE_ID = '0x0000000000000000000000000000000000000000000000000000000080ac58cd'; 11 | 12 | contract("ERC721", async accounts => { 13 | beforeEach(async () => { 14 | owner = accounts[0]; 15 | receiver = accounts[1]; 16 | operator = accounts[2]; 17 | erc721Token = await erc721.new({ from: owner }); 18 | }); 19 | 20 | it("...should register ERC165 interface.", async () => { 21 | const has_erc165_interface = await erc721Token.supportsInterface.call(ERC165_INTERFACE_ID); 22 | assert.ok(has_erc165_interface, "The ERC165 interface was not correctly registered."); 23 | }); 24 | 25 | it("...should register ERC721 interface.", async () => { 26 | const has_erc721_interface = await erc721Token.supportsInterface.call(ERC721_INTERFACE_ID); 27 | assert.ok(has_erc721_interface, "The ERC721 interface was not correctly registered."); 28 | }); 29 | 30 | it("...should mint token to owner.", async () => { 31 | let balance = await erc721Token.balanceOf.call(owner); 32 | const minter_starting_balance = balance.toString(); 33 | 34 | // TODO: weird -> we are getting the token id from the mint event parameters 35 | // how to get the return value of mint()?? 36 | let returnData = await erc721Token.mint({ from: owner }); 37 | const tokenId_1 = returnData.logs[0].args['1'].toString(); 38 | returnData = await erc721Token.mint({ from: owner }); 39 | const tokenId_2 = returnData.logs[0].args['1'].toString(); 40 | 41 | const ownerOfToken_1 = await erc721Token.ownerOf.call(tokenId_1); 42 | const ownerOfToken_2 = await erc721Token.ownerOf.call(tokenId_2); 43 | 44 | balance = await erc721Token.balanceOf.call(owner); 45 | const minter_ending_balance = balance.toString(); 46 | 47 | assert.equal( 48 | tokenId_1, 49 | 0, 50 | "Token ID wasn't correctly set." 51 | ); 52 | assert.equal( 53 | tokenId_2, 54 | 1, 55 | "Token ID wasn't correctly set." 56 | ); 57 | assert.equal( 58 | minter_starting_balance, 59 | minter_ending_balance - 2, 60 | "Token wasn't correctly minted to owner." 61 | ); 62 | assert.equal( 63 | ownerOfToken_1, 64 | owner, 65 | "Owner of first minted token wasn't correctly set." 66 | ); 67 | assert.equal( 68 | ownerOfToken_2, 69 | owner, 70 | "Owner of second minted token wasn't correctly set." 71 | ); 72 | }); 73 | 74 | // TODO: 75 | it("...mint should revert if msg.sender is not owner.", async () => { 76 | try { 77 | await erc721Token.mint({ from: receiver }); 78 | } catch (e) { 79 | revert = e; 80 | } 81 | 82 | assert.instanceOf( 83 | revert, 84 | Error, 85 | 'Mint by non owner did not revert.' 86 | ) 87 | }); 88 | 89 | it("...should safeTransferFrom.", async () => { 90 | // TODO: weird -> we are getting the token id from the mint event parameters 91 | // how to get the return value of mint()?? 92 | const returnData = await erc721Token.mint({ from: owner }); 93 | const tokenId = returnData.logs[0].args['1'].toString(); 94 | 95 | const ownerBalanceBefore = await erc721Token.balanceOf.call(owner); 96 | const receiverBalanceBefore = await erc721Token.balanceOf.call(receiver); 97 | const ownerOfTokenBefore = await erc721Token.ownerOf.call(tokenId); 98 | 99 | await erc721Token.safeTransferFrom(owner, receiver, tokenId); 100 | 101 | const ownerBalanceAfter = await erc721Token.balanceOf.call(owner); 102 | const receiverBalanceAfter = await erc721Token.balanceOf.call(receiver); 103 | const ownerOfTokenAfter = await erc721Token.ownerOf.call(tokenId); 104 | 105 | assert.equal( 106 | ownerBalanceBefore - 1, 107 | ownerBalanceAfter.toString(), 108 | "Balance of owner wasn't correctly updated." 109 | ); 110 | 111 | assert.equal( 112 | receiverBalanceBefore.toString(), 113 | receiverBalanceAfter - 1, 114 | "Balance of reveiver wasn't correctly updated." 115 | ); 116 | 117 | assert.equal( 118 | ownerOfTokenAfter, 119 | receiver, 120 | "Owner of token wasn't correctly updated." 121 | ); 122 | }); 123 | 124 | it("...should transferFrom.", async () => { 125 | // TODO: weird -> we are getting the token id from the mint event parameters 126 | // how to get the return value of mint()?? 127 | const returnData = await erc721Token.mint({ from: owner }); 128 | const tokenId = returnData.logs[0].args['1'].toString(); 129 | 130 | const ownerBalanceBefore = await erc721Token.balanceOf.call(owner); 131 | const receiverBalanceBefore = await erc721Token.balanceOf.call(receiver); 132 | const ownerOfTokenBefore = await erc721Token.ownerOf.call(tokenId); 133 | 134 | await erc721Token.transferFrom(owner, receiver, tokenId); 135 | 136 | const ownerBalanceAfter = await erc721Token.balanceOf.call(owner); 137 | const receiverBalanceAfter = await erc721Token.balanceOf.call(receiver); 138 | const ownerOfTokenAfter = await erc721Token.ownerOf.call(tokenId); 139 | 140 | assert.equal( 141 | ownerBalanceBefore - 1, 142 | ownerBalanceAfter.toString(), 143 | "Balance of owner wasn't correctly updated." 144 | ); 145 | 146 | assert.equal( 147 | receiverBalanceBefore.toString(), 148 | receiverBalanceAfter - 1, 149 | "Balance of reveiver wasn't correctly updated." 150 | ); 151 | 152 | assert.equal( 153 | ownerOfTokenAfter, 154 | receiver, 155 | "Owner of token wasn't correctly updated." 156 | ); 157 | }); 158 | 159 | // TODO: 160 | it("...safeTransferFrom() to contract without onERC721Received should revert.", async () => { 161 | 162 | }); 163 | 164 | it("...safeTransferFrom() to ZERO_ADDRESS should revert.", async () => { 165 | const returnData = await erc721Token.mint({ from: owner }); 166 | const tokenId = returnData.logs[0].args['1'].toString(); 167 | 168 | let revert = false; 169 | try { 170 | const result = await erc721Token.safeTransferFrom(owner, ZERO_ADDRESS, tokenId); 171 | } catch (e) { 172 | revert = e; 173 | } 174 | assert.instanceOf( 175 | revert, 176 | Error, 177 | "SafeTransferFrom to ZERO_ADDRESS did not revert." 178 | ) 179 | }); 180 | 181 | it("...transferFrom() to ZERO_ADDRESS should revert.", async () => { 182 | const returnData = await erc721Token.mint({ from: owner }); 183 | const tokenId = returnData.logs[0].args['1'].toString(); 184 | 185 | let revert = false; 186 | try { 187 | const result = await erc721Token.transferFrom(owner, ZERO_ADDRESS, tokenId); 188 | } catch (e) { 189 | revert = e; 190 | } 191 | assert.instanceOf( 192 | revert, 193 | Error, 194 | "TransferFrom to ZERO_ADDRESS did not revert." 195 | ) 196 | }); 197 | 198 | it("...safeTransferFrom() should revert if 'tokenId' is not a valid NFT.", async () => { 199 | const tokenId = -1 200 | 201 | let revert = false; 202 | try { 203 | await erc721Token.safeTransferFrom(owner, receiver, tokenId); 204 | } catch (e) { 205 | revert = e; 206 | } 207 | 208 | assert.instanceOf( 209 | revert, 210 | Error, 211 | "SafeTransferFrom of non valid NFT did not revert." 212 | ) 213 | }); 214 | 215 | it("...transferFrom() should revert if 'tokenId' is not a valid NFT.", async () => { 216 | const tokenId = -1 217 | 218 | let revert = false; 219 | try { 220 | await erc721Token.transferFrom(owner, receiver, tokenId); 221 | } catch (e) { 222 | revert = e; 223 | } 224 | 225 | assert.instanceOf( 226 | revert, 227 | Error, 228 | "TransferFrom of non valid NFT did not revert." 229 | ) 230 | }); 231 | 232 | it("...safeTransferFrom() should revert if 'from' is not the owner.", async () => { 233 | const returnData = await erc721Token.mint({ from: owner }); 234 | const tokenId = returnData.logs[0].args['1'].toString(); 235 | 236 | let revert = false; 237 | try { 238 | await erc721Token.safeTransferFrom(receiver, owner, tokenId); 239 | } catch (e) { 240 | revert = e; 241 | } 242 | 243 | assert.instanceOf( 244 | revert, 245 | Error, 246 | "SafeTransferFrom by non owner did not revert." 247 | ) 248 | }); 249 | 250 | it("...transferFrom() should revert if 'from' is not the owner.", async () => { 251 | const returnData = await erc721Token.mint({ from: owner }); 252 | const tokenId = returnData.logs[0].args['1'].toString(); 253 | 254 | let revert = false; 255 | try { 256 | await erc721Token.transferFrom(receiver, owner, tokenId); 257 | } catch (e) { 258 | revert = e; 259 | } 260 | 261 | assert.instanceOf( 262 | revert, 263 | Error, 264 | "SafeTransferFrom by non owner did not revert." 265 | ) 266 | }); 267 | 268 | it("...balanceOf() should revert for queries about the ZERO_ADDRESS.", async () => { 269 | let revert = false; 270 | try { 271 | await erc721Token.balanceOf(ZERO_ADDRESS); 272 | } catch (e) { 273 | revert = e; 274 | } 275 | 276 | assert.instanceOf( 277 | revert, 278 | Error, 279 | "BalanceOf query about ZERO_ADDRESS did not revert." 280 | ) 281 | }); 282 | 283 | it("...should approve() if is owner.", async () => { 284 | const returnData = await erc721Token.mint({ from: owner }); 285 | const tokenId = returnData.logs[0].args['1'].toString(); 286 | 287 | let revert = false; 288 | try { 289 | await erc721Token.approve(operator, tokenId, { from: owner }); 290 | } catch (e) { 291 | revert = e; 292 | } 293 | 294 | const isApproved = await erc721Token.getApproved.call(tokenId); 295 | 296 | assert.isOk( 297 | isApproved, 298 | "Operator was not correctly approved." 299 | ); 300 | assert.isNotOk( 301 | revert, 302 | "Operator was not correctly approved." 303 | ); 304 | }); 305 | 306 | it("...approve() should revert if 'tokenId' is not owned by 'msg.sender'.", async () => { 307 | const returnData = await erc721Token.mint({ from: owner }); 308 | const tokenId = returnData.logs[0].args['1'].toString(); 309 | 310 | let revert = false; 311 | try { 312 | await erc721Token.approve(owner, tokenId, { from: operator }); 313 | } catch (e) { 314 | revert = e; 315 | } 316 | 317 | assert.instanceOf( 318 | revert, 319 | Error, 320 | "Approve from non owner did not revert." 321 | ) 322 | }); 323 | 324 | it("...approve() should revert if 'tokenId' is not a valid NFT.", async () => { 325 | let revert = false; 326 | try { 327 | await erc721Token.approve(owner, -1, { from: operator }); 328 | } catch (e) { 329 | revert = e; 330 | } 331 | 332 | assert.instanceOf( 333 | revert, 334 | Error, 335 | "Approve of non valid NFT did not revert." 336 | ) 337 | }); 338 | 339 | it("...should approve if is an authorized operator.", async () => { 340 | const returnData = await erc721Token.mint({ from: owner }); 341 | const tokenId = returnData.logs[0].args['1'].toString(); 342 | 343 | await erc721Token.approve(operator, tokenId, { from: owner }); 344 | 345 | await erc721Token.approve(accounts[3], tokenId, { from: operator }); 346 | 347 | const isApproved = await erc721Token.getApproved.call(tokenId); 348 | 349 | assert.equal( 350 | isApproved, 351 | accounts[3], 352 | "Authorized operator did not correctly approve." 353 | ); 354 | }); 355 | 356 | it("...approve should revert if 'msg.sender' is not an authorized operator.", async () => { 357 | const returnData = await erc721Token.mint({ from: owner }); 358 | const tokenId = returnData.logs[0].args['1'].toString(); 359 | 360 | let revert = false; 361 | try { 362 | await erc721Token.approve(owner, tokenId, { from: account[3] }); 363 | } catch (e) { 364 | revert = e; 365 | } 366 | 367 | assert.instanceOf( 368 | revert, 369 | Error, 370 | "Approve call from non authorized operator did not revert." 371 | ) 372 | }); 373 | 374 | it("...should getApproved.", async () => { 375 | const returnData = await erc721Token.mint({ from: owner }); 376 | const tokenId = returnData.logs[0].args['1'].toString(); 377 | 378 | await erc721Token.approve(operator, tokenId, { from: owner }); 379 | 380 | const isApproved = await erc721Token.getApproved.call(tokenId); 381 | 382 | assert.equal( 383 | isApproved, 384 | operator, 385 | "Did not correctly getApproved." 386 | ); 387 | }); 388 | 389 | it("...should setApprovalForAll.", async () => { 390 | await erc721Token.setApprovalForAll(operator, true, { from: owner }); 391 | 392 | const isApprovedForAll = await erc721Token.isApprovedForAll.call(owner, operator); 393 | 394 | assert.isOk( 395 | isApprovedForAll, 396 | "Did not setApprovalForAll." 397 | ); 398 | }); 399 | 400 | it("...isApprovedForAll should return 'False' if address is not approvedForAll.", async () => { 401 | const isApprovedForAll = await erc721Token.isApprovedForAll.call(owner, accounts[3]); 402 | 403 | assert.isNotOk( 404 | isApprovedForAll, 405 | "Did not return 'False' for address that is not approvedForAll." 406 | ); 407 | }); 408 | 409 | it("...getApproved should revert if 'tokenId' is not a valid NFT.", async () => { 410 | let revert = false; 411 | try { 412 | await erc721Token.getApproved.call(-1); 413 | } catch (e) { 414 | revert = e; 415 | } 416 | 417 | assert.instanceOf( 418 | revert, 419 | Error, 420 | "GetApproved of non valid NFT did not revert." 421 | ) 422 | }); 423 | 424 | // Test event logging 425 | it("...should log 'Minted' on mint().", async () => { 426 | const res = await erc721Token.mint({ from: owner }); 427 | const log = res.logs.find(element => element.event.match('Mint')); 428 | 429 | assert.strictEqual(log.args._to, owner); 430 | assert.strictEqual(log.args._tokenId.toString(), "0"); 431 | }) 432 | 433 | it("...should log 'Transfer' on safeTransferFrom().", async () => { 434 | const returnData = await erc721Token.mint({ from: owner }); 435 | const tokenId = returnData.logs[0].args['1'].toString(); 436 | 437 | const res = await erc721Token.safeTransferFrom(owner, receiver, tokenId); 438 | const log = res.logs.find(element => element.event.match('Transfer')); 439 | 440 | assert.strictEqual(log.args._from, owner); 441 | assert.strictEqual(log.args._to, receiver); 442 | assert.strictEqual(log.args._tokenId.toString(), tokenId); 443 | }) 444 | 445 | it("...should log 'Transfer' on transferFrom().", async () => { 446 | const returnData = await erc721Token.mint({ from: owner }); 447 | const tokenId = returnData.logs[0].args['1'].toString(); 448 | 449 | const res = await erc721Token.transferFrom(owner, receiver, tokenId); 450 | const log = res.logs.find(element => element.event.match('Transfer')); 451 | 452 | assert.strictEqual(log.args._from, owner); 453 | assert.strictEqual(log.args._to, receiver); 454 | assert.strictEqual(log.args._tokenId.toString(), tokenId); 455 | }) 456 | 457 | it("...should log 'Approval' on approve().", async () => { 458 | const returnData = await erc721Token.mint({ from: owner }); 459 | const tokenId = returnData.logs[0].args['1'].toString(); 460 | 461 | const res = await erc721Token.approve(operator, tokenId, { from: owner }); 462 | const log = res.logs.find(element => element.event.match('Approval')); 463 | 464 | assert.strictEqual(log.args._owner, owner); 465 | assert.strictEqual(log.args._approved, operator); 466 | assert.strictEqual(log.args._tokenId.toString(), tokenId); 467 | }) 468 | 469 | it("...should log 'ApprovalForAll' on setApprovalForAll().", async () => { 470 | const res = await erc721Token.setApprovalForAll(operator, true, { from: owner }); 471 | const log = res.logs.find(element => element.event.match('ApprovalForAll')); 472 | 473 | assert.strictEqual(log.args._owner, owner); 474 | assert.strictEqual(log.args._operator, operator); 475 | assert.strictEqual(log.args._approved, true); 476 | }) 477 | }); 478 | -------------------------------------------------------------------------------- /erc721/truffle.js: -------------------------------------------------------------------------------- 1 | /** 2 | * Use this file to configure your truffle project. It's seeded with some 3 | * common settings for different networks and features like migrations, 4 | * compilation and testing. Uncomment the ones you need or modify 5 | * them to suit your project as necessary. 6 | * 7 | * More information about configuration can be found at: 8 | * 9 | * truffleframework.com/docs/advanced/configuration 10 | * 11 | * To deploy via Infura you'll need a wallet provider (like truffle-hdwallet-provider) 12 | * to sign your transactions before they're sent to a remote public node. Infura API 13 | * keys are available for free at: infura.io/register 14 | * 15 | * You'll also need a mnemonic - the twelve word phrase the wallet uses to generate 16 | * public/private key pairs. If you're publishing your code to GitHub make sure you load this 17 | * phrase from a file you've .gitignored so it doesn't accidentally become public. 18 | * 19 | */ 20 | 21 | module.exports = { 22 | networks: { 23 | ganache: { 24 | host: '127.0.0.1', 25 | port: 7545, 26 | network_id: '*', // match any network id 27 | from: '0x954e72fdc51Cf919203067406fB337Ed4bDC8CdA', // account address from which to deploy 28 | gas: 4000000, 29 | } 30 | } 31 | } 32 | -------------------------------------------------------------------------------- /erc777/.gitignore: -------------------------------------------------------------------------------- 1 | ./build 2 | -------------------------------------------------------------------------------- /erc777/README.md: -------------------------------------------------------------------------------- 1 | # ERC777 2 | 3 | The [ERC777 Token Standard](https://eips.ethereum.org/EIPS/eip-777) improves upon the popular [ERC20](https://contracts.vyperhub.io/contracts/erc20) standard. 4 | 5 | Its most defining feature is the use of the new [ERC1820](http://eips.ethereum.org/EIPS/eip-1820) interface standard which it uses in such a way, that each time tokens are sent the ERC777 contract does two things: 6 | 1. It checks whether the sender of the transaction is a contract and whether that contract implements a `tokensToSend(_operator, _from, _to, _amount, _data, _operatorData)` function. 7 | 2. It checks whether the receiver of the transaction is a contract and whether that contract implements a `tokensToSend(_operator, _from, _to, _amount, _data, _operatorData)` function. 8 | 9 | If the functions exist, then the code inside of both functions is executed. 10 | The exciting part is, that there are no restrictions on what the code inside of the two functions looks like or what it does. 11 | 12 | ## Tests 13 | 14 | ### Run local tests 15 | 16 | ```bash 17 | $ truffle test --network ganache 18 | ``` 19 | ### More tests 20 | 21 | Further tests for this implementation may be found [here](https://github.com/0xjac/ERC777/tree/master/test). 22 | -------------------------------------------------------------------------------- /erc777/contracts/Migrations.sol: -------------------------------------------------------------------------------- 1 | pragma solidity >=0.4.21 <0.6.0; 2 | 3 | contract Migrations { 4 | address public owner; 5 | uint public last_completed_migration; 6 | 7 | constructor() public { 8 | owner = msg.sender; 9 | } 10 | 11 | modifier restricted() { 12 | if (msg.sender == owner) _; 13 | } 14 | 15 | function setCompleted(uint completed) public restricted { 16 | last_completed_migration = completed; 17 | } 18 | 19 | function upgrade(address new_address) public restricted { 20 | Migrations upgraded = Migrations(new_address); 21 | upgraded.setCompleted(last_completed_migration); 22 | } 23 | } 24 | -------------------------------------------------------------------------------- /erc777/contracts/erc777.vy: -------------------------------------------------------------------------------- 1 | # Author: Sören Steiger, github.com/ssteiger 2 | # License: MIT 3 | 4 | # ERC777 Token Standard 5 | # https://eips.ethereum.org/EIPS/eip-777 6 | 7 | 8 | # Interface for ERC1820 registry contract 9 | # https://eips.ethereum.org/EIPS/eip-1820 10 | contract ERC1820Registry: 11 | def setInterfaceImplementer( 12 | _addr: address, 13 | _interfaceHash: bytes32, 14 | _implementer: address, 15 | ): modifying 16 | def getInterfaceImplementer( 17 | _addr: address, 18 | _interfaceHash: bytes32, 19 | ) -> address: modifying 20 | 21 | # Interface for ERC777Tokens sender contracts 22 | contract ERC777TokensSender: 23 | def tokensToSend( 24 | _operator: address, 25 | _from: address, 26 | _to: address, 27 | _amount: uint256, 28 | _data: bytes[256], 29 | _operatorData: bytes[256] 30 | ): modifying 31 | 32 | # Interface for ERC777Tokens recipient contracts 33 | contract ERC777TokensRecipient: 34 | def tokensReceived( 35 | _operator: address, 36 | _from: address, 37 | _to: address, 38 | _amount: uint256, 39 | _data: bytes[256], 40 | _operatorData: bytes[256] 41 | ): modifying 42 | 43 | 44 | Sent: event({ 45 | _operator: indexed(address), # Address which triggered the send. 46 | _from: indexed(address), # Token holder. 47 | _to: indexed(address), # Token recipient. 48 | _amount: uint256, # Number of tokens to send. 49 | _data: bytes[256], # Information provided by the token holder. 50 | _operatorData: bytes[256] # Information provided by the operator. 51 | }) 52 | 53 | Minted: event({ 54 | _operator: indexed(address), # Address which triggered the mint. 55 | _to: indexed(address), # Recipient of the tokens. 56 | _amount: uint256, # Number of tokens minted. 57 | _data: bytes[256], # Information provided for the recipient. 58 | _operatorData: bytes[256] # Information provided by the operator. 59 | }) 60 | 61 | Burned: event({ 62 | _operator: indexed(address), # Address which triggered the burn. 63 | _from: indexed(address), # Token holder whose tokens are burned. 64 | _amount: uint256, # Token holder whose tokens are burned. 65 | _data: bytes[256], # Information provided by the token holder. 66 | _operatorData: bytes[256] # Information provided by the operator. 67 | }) 68 | 69 | AuthorizedOperator: event({ 70 | _operator: indexed(address), # Address which became an operator of tokenHolder. 71 | _holder: indexed(address) # Address of a token holder which authorized the operator address as an operator. 72 | }) 73 | 74 | RevokedOperator: event({ 75 | _operator: indexed(address), # Address which was revoked as an operator of tokenHolder. 76 | _holder: indexed(address) # Address of a token holder which revoked the operator address as an operator. 77 | }) 78 | 79 | 80 | erc1820Registry: ERC1820Registry 81 | erc1820RegistryAddress: constant(address) = 0x1820a4B7618BdE71Dce8cdc73aAB6C95905faD24 82 | 83 | # TODO: decide which size to use 84 | NO_OF_DEFAULT_OPERATORS: constant(uint256) = 5 85 | MAX_LENGTH_NAME: constant(uint256) = 64 86 | MAX_LENGTH_SYMBOL: constant(uint256) = 32 87 | 88 | name: public(string[MAX_LENGTH_NAME]) 89 | symbol: public(string[MAX_LENGTH_SYMBOL]) 90 | 91 | totalSupply: public(uint256) 92 | granularity: public(uint256) 93 | 94 | balanceOf: public(map(address, uint256)) 95 | 96 | defaultOperatorsList: address[NO_OF_DEFAULT_OPERATORS] 97 | defaultOperatorsMap: map(address, bool) 98 | 99 | operators: map(address, map(address, bool)) 100 | 101 | 102 | @public 103 | def __init__( 104 | _name: string[MAX_LENGTH_NAME], 105 | _symbol: string[MAX_LENGTH_SYMBOL], 106 | _totalSupply: uint256, 107 | _granularity: uint256, 108 | _defaultOperators: address[NO_OF_DEFAULT_OPERATORS] 109 | ): 110 | self.name = _name 111 | self.symbol = _symbol 112 | self.totalSupply = _totalSupply 113 | # The granularity value MUST be greater than or equal to 1 114 | assert _granularity >= 1 115 | self.granularity = _granularity 116 | self.defaultOperatorsList = _defaultOperators 117 | for i in range(NO_OF_DEFAULT_OPERATORS): 118 | assert _defaultOperators[i] != ZERO_ADDRESS 119 | self.defaultOperatorsMap[_defaultOperators[i]] = True 120 | self.erc1820Registry = ERC1820Registry(erc1820RegistryAddress) 121 | self.erc1820Registry.setInterfaceImplementer(self, keccak256("ERC777Token"), self) 122 | 123 | 124 | @private 125 | def _checkForERC777TokensInterface_Sender( 126 | _operator: address, 127 | _from: address, 128 | _to: address, 129 | _amount: uint256, 130 | _data: bytes[256]="", 131 | _operatorData: bytes[256]="" 132 | ): 133 | implementer: address = self.erc1820Registry.getInterfaceImplementer(_from, keccak256("ERC777TokensSender")) 134 | if implementer != ZERO_ADDRESS: 135 | ERC777TokensSender(_from).tokensToSend(_operator, _from, _to, _amount, _data, _operatorData) 136 | 137 | 138 | @private 139 | def _checkForERC777TokensInterface_Recipient( 140 | _operator: address, 141 | _from: address, 142 | _to: address, 143 | _amount: uint256, 144 | _data: bytes[256]="", 145 | _operatorData: bytes[256]="" 146 | ): 147 | implementer: address = self.erc1820Registry.getInterfaceImplementer(_to, keccak256("ERC777TokensRecipient")) 148 | if implementer != ZERO_ADDRESS: 149 | ERC777TokensRecipient(_to).tokensReceived(_operator, _from, _to, _amount, _data, _operatorData) 150 | 151 | 152 | @private 153 | def _transferFunds( 154 | _operator: address, 155 | _from: address, 156 | _to: address, 157 | _amount: uint256, 158 | _data: bytes[256]="", 159 | _operatorData: bytes[256]="" 160 | ): 161 | # any minting, sending or burning of tokens MUST be a multiple of the granularity value. 162 | assert _amount % self.granularity == 0 163 | 164 | # check for 'tokensToSend' hook 165 | if _from.is_contract: 166 | self._checkForERC777TokensInterface_Sender(_operator, _from, _to, _amount, _data, _operatorData) 167 | 168 | self.balanceOf[_from] -= _amount 169 | self.balanceOf[_to] += _amount 170 | 171 | # check for 'tokensReceived' hook 172 | # but only if transfer is not a burn 173 | if _to != ZERO_ADDRESS: 174 | if _to.is_contract: 175 | self._checkForERC777TokensInterface_Recipient(_operator, _from, _to, _amount, _data, _operatorData) 176 | 177 | 178 | @public 179 | @constant 180 | def defaultOperators() -> address[NO_OF_DEFAULT_OPERATORS]: 181 | return self.defaultOperatorsList 182 | 183 | 184 | @public 185 | @constant 186 | def isOperatorFor(_operator: address, _holder: address) -> bool: 187 | return (self.operators[_holder])[_operator] or self.defaultOperatorsMap[_operator] or _operator == _holder 188 | 189 | 190 | @public 191 | def authorizeOperator(_operator: address): 192 | (self.operators[msg.sender])[_operator] = True 193 | log.AuthorizedOperator(_operator, msg.sender) 194 | 195 | 196 | @public 197 | def revokeOperator(_operator: address): 198 | # MUST revert if it is called to revoke the holder as an operator for itself 199 | assert _operator != msg.sender 200 | (self.operators[msg.sender])[_operator] = False 201 | log.RevokedOperator(_operator, msg.sender) 202 | 203 | 204 | @public 205 | def send(_to: address, _amount: uint256, _data: bytes[256]=""): 206 | assert _to != ZERO_ADDRESS 207 | operatorData: bytes[256]="" 208 | self._transferFunds(msg.sender, msg.sender, _to, _amount, _data, operatorData) 209 | log.Sent(msg.sender, msg.sender, _to, _amount, _data, operatorData) 210 | 211 | 212 | @public 213 | def operatorSend( 214 | _from: address, 215 | _to: address, 216 | _amount: uint256, 217 | _data: bytes[256]="", 218 | _operatorData: bytes[256]="" 219 | ): 220 | assert _to != ZERO_ADDRESS 221 | assert self.isOperatorFor(msg.sender, _from) 222 | self._transferFunds(msg.sender, _from, _to, _amount, _data, _operatorData) 223 | log.Sent(msg.sender, _from, _to, _amount, _data, _operatorData) 224 | 225 | 226 | @public 227 | def burn(_amount: uint256, _data: bytes[256]=""): 228 | operatorData: bytes[256]="" 229 | self._transferFunds(msg.sender, msg.sender, ZERO_ADDRESS, _amount, _data, operatorData) 230 | self.totalSupply -= _amount 231 | log.Burned(msg.sender, msg.sender, _amount, _data, operatorData) 232 | 233 | 234 | @public 235 | def operatorBurn( 236 | _from: address, 237 | _amount: uint256, 238 | _data: bytes[256]="", 239 | _operatorData: bytes[256]="" 240 | ): 241 | # _from: Token holder whose tokens will be burned (or 0x0 to set from to msg.sender). 242 | fromAddress: address 243 | if _from == ZERO_ADDRESS: 244 | fromAddress = msg.sender 245 | else: 246 | fromAddress = _from 247 | assert self.isOperatorFor(msg.sender, fromAddress) 248 | self._transferFunds(msg.sender, fromAddress, ZERO_ADDRESS, _amount, _data, _operatorData) 249 | self.totalSupply -= _amount 250 | log.Burned(msg.sender, fromAddress, _amount, _data, _operatorData) 251 | 252 | 253 | # NOTE: ERC777 intentionally does not define specific functions to mint tokens. 254 | @public 255 | def mint( 256 | _to: address, 257 | _amount: uint256, 258 | _operatorData: bytes[256]="" 259 | ): 260 | assert _to != ZERO_ADDRESS 261 | # any minting, sending or burning of tokens MUST be a multiple of the granularity value. 262 | assert _amount % self.granularity == 0 263 | # only operators are allowed to mint 264 | assert self.defaultOperatorsMap[msg.sender] 265 | self.balanceOf[_to] += _amount 266 | self.totalSupply += _amount 267 | data: bytes[256]="" 268 | if _to.is_contract: 269 | self._checkForERC777TokensInterface_Recipient(msg.sender, ZERO_ADDRESS, _to, _amount, data, _operatorData) 270 | log.Minted(msg.sender, _to, _amount, data, _operatorData) 271 | -------------------------------------------------------------------------------- /erc777/contracts/erc777TokenReceiver.vy: -------------------------------------------------------------------------------- 1 | # Author: Sören Steiger, github.com/ssteiger 2 | # License: MIT 3 | 4 | # ERC777 Token Receiver 5 | # https://eips.ethereum.org/EIPS/eip-777 6 | 7 | # Interface for ERC1820 registry contract 8 | # https://eips.ethereum.org/EIPS/eip-1820 9 | contract ERC1820Registry: 10 | def setInterfaceImplementer( 11 | _addr: address, 12 | _interfaceHash: bytes32, 13 | _implementer: address, 14 | ): modifying 15 | 16 | 17 | TokensReceived: event({ 18 | _operator: indexed(address), 19 | _from: indexed(address), 20 | _to: indexed(address), 21 | _amount: uint256, 22 | _data: bytes[256], 23 | _operatorData: bytes[256] 24 | }) 25 | 26 | 27 | erc1820Registry: ERC1820Registry 28 | erc1820RegistryAddress: constant(address) = 0x1820a4B7618BdE71Dce8cdc73aAB6C95905faD24 29 | 30 | 31 | @public 32 | def __init__(): 33 | self.erc1820Registry = ERC1820Registry(erc1820RegistryAddress) 34 | self.erc1820Registry.setInterfaceImplementer(self, keccak256("ERC777TokensRecipient"), self) 35 | 36 | 37 | @public 38 | def tokensReceived( 39 | _operator: address, 40 | _from: address, 41 | _to: address, 42 | _amount: uint256, 43 | _data: bytes[256], 44 | _operatorData: bytes[256] 45 | ): 46 | log.TokensReceived(_operator, _from, _to, _amount, _data, _operatorData) 47 | -------------------------------------------------------------------------------- /erc777/contracts/erc777TokenSender.vy: -------------------------------------------------------------------------------- 1 | # Author: Sören Steiger, github.com/ssteiger 2 | # License: MIT 3 | 4 | # ERC777 Token Sender 5 | # https://eips.ethereum.org/EIPS/eip-777 6 | 7 | # Interface for ERC1820 registry contract 8 | # https://eips.ethereum.org/EIPS/eip-1820 9 | contract ERC1820Registry: 10 | def setInterfaceImplementer( 11 | _addr: address, 12 | _interfaceHash: bytes32, 13 | _implementer: address, 14 | ): modifying 15 | 16 | 17 | TokensSent: event({ 18 | _operator: indexed(address), 19 | _from: indexed(address), 20 | _to: indexed(address), 21 | _amount: uint256, 22 | _data: bytes[256], 23 | _operatorData: bytes[256] 24 | }) 25 | 26 | 27 | erc1820Registry: ERC1820Registry 28 | erc1820RegistryAddress: constant(address) = 0x1820a4B7618BdE71Dce8cdc73aAB6C95905faD24 29 | 30 | 31 | @public 32 | def __init__(): 33 | self.erc1820Registry = ERC1820Registry(erc1820RegistryAddress) 34 | self.erc1820Registry.setInterfaceImplementer(self, keccak256("ERC777TokensSender"), self) 35 | 36 | 37 | @public 38 | def tokensToSend( 39 | _operator: address, 40 | _from: address, 41 | _to: address, 42 | _amount: uint256, 43 | _data: bytes[256], 44 | _operatorData: bytes[256] 45 | ): 46 | log.TokensSent(_operator, _from, _to, _amount, _data, _operatorData) 47 | -------------------------------------------------------------------------------- /erc777/contracts/helpers/ERC1820Registry.sol: -------------------------------------------------------------------------------- 1 | /* ERC1820 Pseudo-introspection Registry Contract 2 | * This standard defines a universal registry smart contract where any address (contract or regular account) can 3 | * register which interface it supports and which smart contract is responsible for its implementation. 4 | * 5 | * Written in 2019 by Jordi Baylina and Jacques Dafflon 6 | * 7 | * To the extent possible under law, the author(s) have dedicated all copyright and related and neighboring rights to 8 | * this software to the public domain worldwide. This software is distributed without any warranty. 9 | * 10 | * You should have received a copy of the CC0 Public Domain Dedication along with this software. If not, see 11 | * . 12 | * 13 | * ███████╗██████╗ ██████╗ ██╗ █████╗ ██████╗ ██████╗ 14 | * ██╔════╝██╔══██╗██╔════╝███║██╔══██╗╚════██╗██╔═████╗ 15 | * █████╗ ██████╔╝██║ ╚██║╚█████╔╝ █████╔╝██║██╔██║ 16 | * ██╔══╝ ██╔══██╗██║ ██║██╔══██╗██╔═══╝ ████╔╝██║ 17 | * ███████╗██║ ██║╚██████╗ ██║╚█████╔╝███████╗╚██████╔╝ 18 | * ╚══════╝╚═╝ ╚═╝ ╚═════╝ ╚═╝ ╚════╝ ╚══════╝ ╚═════╝ 19 | * 20 | * ██████╗ ███████╗ ██████╗ ██╗███████╗████████╗██████╗ ██╗ ██╗ 21 | * ██╔══██╗██╔════╝██╔════╝ ██║██╔════╝╚══██╔══╝██╔══██╗╚██╗ ██╔╝ 22 | * ██████╔╝█████╗ ██║ ███╗██║███████╗ ██║ ██████╔╝ ╚████╔╝ 23 | * ██╔══██╗██╔══╝ ██║ ██║██║╚════██║ ██║ ██╔══██╗ ╚██╔╝ 24 | * ██║ ██║███████╗╚██████╔╝██║███████║ ██║ ██║ ██║ ██║ 25 | * ╚═╝ ╚═╝╚══════╝ ╚═════╝ ╚═╝╚══════╝ ╚═╝ ╚═╝ ╚═╝ ╚═╝ 26 | * 27 | */ 28 | pragma solidity 0.5.8; 29 | // IV is value needed to have a vanity address starting with '0x1820'. 30 | // IV: 53759 31 | 32 | /// @dev The interface a contract MUST implement if it is the implementer of 33 | /// some (other) interface for any address other than itself. 34 | interface ERC1820ImplementerInterface { 35 | /// @notice Indicates whether the contract implements the interface 'interfaceHash' for the address 'addr' or not. 36 | /// @param interfaceHash keccak256 hash of the name of the interface 37 | /// @param addr Address for which the contract will implement the interface 38 | /// @return ERC1820_ACCEPT_MAGIC only if the contract implements 'interfaceHash' for the address 'addr'. 39 | function canImplementInterfaceForAddress(bytes32 interfaceHash, address addr) external view returns(bytes32); 40 | } 41 | 42 | 43 | /// @title ERC1820 Pseudo-introspection Registry Contract 44 | /// @author Jordi Baylina and Jacques Dafflon 45 | /// @notice This contract is the official implementation of the ERC1820 Registry. 46 | /// @notice For more details, see https://eips.ethereum.org/EIPS/eip-1820 47 | contract ERC1820Registry { 48 | /// @notice ERC165 Invalid ID. 49 | bytes4 constant internal INVALID_ID = 0xffffffff; 50 | /// @notice Method ID for the ERC165 supportsInterface method (= `bytes4(keccak256('supportsInterface(bytes4)'))`). 51 | bytes4 constant internal ERC165ID = 0x01ffc9a7; 52 | /// @notice Magic value which is returned if a contract implements an interface on behalf of some other address. 53 | bytes32 constant internal ERC1820_ACCEPT_MAGIC = keccak256(abi.encodePacked("ERC1820_ACCEPT_MAGIC")); 54 | 55 | /// @notice mapping from addresses and interface hashes to their implementers. 56 | mapping(address => mapping(bytes32 => address)) internal interfaces; 57 | /// @notice mapping from addresses to their manager. 58 | mapping(address => address) internal managers; 59 | /// @notice flag for each address and erc165 interface to indicate if it is cached. 60 | mapping(address => mapping(bytes4 => bool)) internal erc165Cached; 61 | 62 | /// @notice Indicates a contract is the 'implementer' of 'interfaceHash' for 'addr'. 63 | event InterfaceImplementerSet(address indexed addr, bytes32 indexed interfaceHash, address indexed implementer); 64 | /// @notice Indicates 'newManager' is the address of the new manager for 'addr'. 65 | event ManagerChanged(address indexed addr, address indexed newManager); 66 | 67 | /// @notice Query if an address implements an interface and through which contract. 68 | /// @param _addr Address being queried for the implementer of an interface. 69 | /// (If '_addr' is the zero address then 'msg.sender' is assumed.) 70 | /// @param _interfaceHash Keccak256 hash of the name of the interface as a string. 71 | /// E.g., 'web3.utils.keccak256("ERC777TokensRecipient")' for the 'ERC777TokensRecipient' interface. 72 | /// @return The address of the contract which implements the interface '_interfaceHash' for '_addr' 73 | /// or '0' if '_addr' did not register an implementer for this interface. 74 | function getInterfaceImplementer(address _addr, bytes32 _interfaceHash) external view returns (address) { 75 | address addr = _addr == address(0) ? msg.sender : _addr; 76 | if (isERC165Interface(_interfaceHash)) { 77 | bytes4 erc165InterfaceHash = bytes4(_interfaceHash); 78 | return implementsERC165Interface(addr, erc165InterfaceHash) ? addr : address(0); 79 | } 80 | return interfaces[addr][_interfaceHash]; 81 | } 82 | 83 | /// @notice Sets the contract which implements a specific interface for an address. 84 | /// Only the manager defined for that address can set it. 85 | /// (Each address is the manager for itself until it sets a new manager.) 86 | /// @param _addr Address for which to set the interface. 87 | /// (If '_addr' is the zero address then 'msg.sender' is assumed.) 88 | /// @param _interfaceHash Keccak256 hash of the name of the interface as a string. 89 | /// E.g., 'web3.utils.keccak256("ERC777TokensRecipient")' for the 'ERC777TokensRecipient' interface. 90 | /// @param _implementer Contract address implementing '_interfaceHash' for '_addr'. 91 | function setInterfaceImplementer(address _addr, bytes32 _interfaceHash, address _implementer) external { 92 | address addr = _addr == address(0) ? msg.sender : _addr; 93 | require(getManager(addr) == msg.sender, "Not the manager"); 94 | 95 | require(!isERC165Interface(_interfaceHash), "Must not be an ERC165 hash"); 96 | if (_implementer != address(0) && _implementer != msg.sender) { 97 | require( 98 | ERC1820ImplementerInterface(_implementer) 99 | .canImplementInterfaceForAddress(_interfaceHash, addr) == ERC1820_ACCEPT_MAGIC, 100 | "Does not implement the interface" 101 | ); 102 | } 103 | interfaces[addr][_interfaceHash] = _implementer; 104 | emit InterfaceImplementerSet(addr, _interfaceHash, _implementer); 105 | } 106 | 107 | /// @notice Sets '_newManager' as manager for '_addr'. 108 | /// The new manager will be able to call 'setInterfaceImplementer' for '_addr'. 109 | /// @param _addr Address for which to set the new manager. 110 | /// @param _newManager Address of the new manager for 'addr'. (Pass '0x0' to reset the manager to '_addr'.) 111 | function setManager(address _addr, address _newManager) external { 112 | require(getManager(_addr) == msg.sender, "Not the manager"); 113 | managers[_addr] = _newManager == _addr ? address(0) : _newManager; 114 | emit ManagerChanged(_addr, _newManager); 115 | } 116 | 117 | /// @notice Get the manager of an address. 118 | /// @param _addr Address for which to return the manager. 119 | /// @return Address of the manager for a given address. 120 | function getManager(address _addr) public view returns(address) { 121 | // By default the manager of an address is the same address 122 | if (managers[_addr] == address(0)) { 123 | return _addr; 124 | } else { 125 | return managers[_addr]; 126 | } 127 | } 128 | 129 | /// @notice Compute the keccak256 hash of an interface given its name. 130 | /// @param _interfaceName Name of the interface. 131 | /// @return The keccak256 hash of an interface name. 132 | function interfaceHash(string calldata _interfaceName) external pure returns(bytes32) { 133 | return keccak256(abi.encodePacked(_interfaceName)); 134 | } 135 | 136 | /* --- ERC165 Related Functions --- */ 137 | /* --- Developed in collaboration with William Entriken. --- */ 138 | 139 | /// @notice Updates the cache with whether the contract implements an ERC165 interface or not. 140 | /// @param _contract Address of the contract for which to update the cache. 141 | /// @param _interfaceId ERC165 interface for which to update the cache. 142 | function updateERC165Cache(address _contract, bytes4 _interfaceId) external { 143 | interfaces[_contract][_interfaceId] = implementsERC165InterfaceNoCache( 144 | _contract, _interfaceId) ? _contract : address(0); 145 | erc165Cached[_contract][_interfaceId] = true; 146 | } 147 | 148 | /// @notice Checks whether a contract implements an ERC165 interface or not. 149 | // If the result is not cached a direct lookup on the contract address is performed. 150 | // If the result is not cached or the cached value is out-of-date, the cache MUST be updated manually by calling 151 | // 'updateERC165Cache' with the contract address. 152 | /// @param _contract Address of the contract to check. 153 | /// @param _interfaceId ERC165 interface to check. 154 | /// @return True if '_contract' implements '_interfaceId', false otherwise. 155 | function implementsERC165Interface(address _contract, bytes4 _interfaceId) public view returns (bool) { 156 | if (!erc165Cached[_contract][_interfaceId]) { 157 | return implementsERC165InterfaceNoCache(_contract, _interfaceId); 158 | } 159 | return interfaces[_contract][_interfaceId] == _contract; 160 | } 161 | 162 | /// @notice Checks whether a contract implements an ERC165 interface or not without using nor updating the cache. 163 | /// @param _contract Address of the contract to check. 164 | /// @param _interfaceId ERC165 interface to check. 165 | /// @return True if '_contract' implements '_interfaceId', false otherwise. 166 | function implementsERC165InterfaceNoCache(address _contract, bytes4 _interfaceId) public view returns (bool) { 167 | uint256 success; 168 | uint256 result; 169 | 170 | (success, result) = noThrowCall(_contract, ERC165ID); 171 | if (success == 0 || result == 0) { 172 | return false; 173 | } 174 | 175 | (success, result) = noThrowCall(_contract, INVALID_ID); 176 | if (success == 0 || result != 0) { 177 | return false; 178 | } 179 | 180 | (success, result) = noThrowCall(_contract, _interfaceId); 181 | if (success == 1 && result == 1) { 182 | return true; 183 | } 184 | return false; 185 | } 186 | 187 | /// @notice Checks whether the hash is a ERC165 interface (ending with 28 zeroes) or not. 188 | /// @param _interfaceHash The hash to check. 189 | /// @return True if '_interfaceHash' is an ERC165 interface (ending with 28 zeroes), false otherwise. 190 | function isERC165Interface(bytes32 _interfaceHash) internal pure returns (bool) { 191 | return _interfaceHash & 0x00000000FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF == 0; 192 | } 193 | 194 | /// @dev Make a call on a contract without throwing if the function does not exist. 195 | function noThrowCall(address _contract, bytes4 _interfaceId) 196 | internal view returns (uint256 success, uint256 result) 197 | { 198 | bytes4 erc165ID = ERC165ID; 199 | 200 | assembly { 201 | let x := mload(0x40) // Find empty storage location using "free memory pointer" 202 | mstore(x, erc165ID) // Place signature at beginning of empty storage 203 | mstore(add(x, 0x04), _interfaceId) // Place first argument directly next to signature 204 | 205 | success := staticcall( 206 | 30000, // 30k gas 207 | _contract, // To addr 208 | x, // Inputs are stored at location x 209 | 0x24, // Inputs are 36 (4 + 32) bytes long 210 | x, // Store output over input (saves space) 211 | 0x20 // Outputs are 32 bytes long 212 | ) 213 | 214 | result := mload(x) // Load the result 215 | } 216 | } 217 | } 218 | -------------------------------------------------------------------------------- /erc777/migrations/1_initial_migration.js: -------------------------------------------------------------------------------- 1 | const checkForERC182Registry = require('./helpers/checkForERC182Registry.js'); 2 | 3 | const Migrations = artifacts.require("./Migrations.sol"); 4 | 5 | module.exports = async function(deployer) { 6 | await checkForERC182Registry(web3, deployer); 7 | 8 | deployer.deploy(Migrations); 9 | }; 10 | -------------------------------------------------------------------------------- /erc777/migrations/2_deploy_contracts.js: -------------------------------------------------------------------------------- 1 | const erc777 = artifacts.require('erc777'); 2 | 3 | // NOTE: this address has to match the 'from' address used in 'truffle.js' 4 | const truffleFromAddress = '0x954e72fdc51Cf919203067406fB337Ed4bDC8CdA'; 5 | 6 | const args = { 7 | name: 'My777Token', 8 | symbol: 'MT777', 9 | totalSupply: 100000000, 10 | granularity: 1, 11 | defaultOperators: [ 12 | truffleFromAddress, 13 | '0x0000000000000000000000000000000000000001', 14 | '0x0000000000000000000000000000000000000002', 15 | '0x0000000000000000000000000000000000000003', 16 | ], 17 | } 18 | 19 | module.exports = function(deployer) { 20 | deployer.deploy( 21 | erc777, 22 | args.name, 23 | args.symbol, 24 | args.totalSupply, 25 | args.granularity, 26 | args.defaultOperators 27 | ); 28 | }; 29 | -------------------------------------------------------------------------------- /erc777/migrations/helpers/checkForERC182Registry.js: -------------------------------------------------------------------------------- 1 | // NOTE: this address has to match the 'from' address used in 'truffle.js' 2 | const truffleFromAddress = '0x954e72fdc51Cf919203067406fB337Ed4bDC8CdA'; 3 | 4 | module.exports = async function(web3, deployer) { 5 | // check if erc1820 registry contract exists on network 6 | // http://eips.ethereum.org/EIPS/eip-1820 7 | 8 | console.log('checking if erc1820Registry contract exists on network'); 9 | 10 | // check for code at the erc1820Registry address 11 | const code = await web3.eth.getCode('0x1820a4B7618BdE71Dce8cdc73aAB6C95905faD24'); 12 | 13 | // NOTE: If no contract exists at address, '0x' is returned 14 | if (code == '0x') { 15 | // erc1820Registry does not exist on network 16 | // -> deploy it 17 | console.log('erc1820Registry contract not found -> deloying it now'); 18 | 19 | // fund address from which the erc1820Registry contract will be deployed 20 | const fundSize = web3.utils.toWei('0.08', 'ether'); 21 | await web3.eth.sendTransaction({ 22 | from: truffleFromAddress, 23 | to: '0xa990077c3205cbDf861e17Fa532eeB069cE9fF96', 24 | value: fundSize 25 | }); 26 | 27 | // the raw transaction that deployes the erc1820Registry 28 | const rawTx = '0xf90a388085174876e800830c35008080b909e5608060405234801561001057600080fd5b506109c5806100206000396000f3fe608060405234801561001057600080fd5b50600436106100a5576000357c010000000000000000000000000000000000000000000000000000000090048063a41e7d5111610078578063a41e7d51146101d4578063aabbb8ca1461020a578063b705676514610236578063f712f3e814610280576100a5565b806329965a1d146100aa5780633d584063146100e25780635df8122f1461012457806365ba36c114610152575b600080fd5b6100e0600480360360608110156100c057600080fd5b50600160a060020a038135811691602081013591604090910135166102b6565b005b610108600480360360208110156100f857600080fd5b5035600160a060020a0316610570565b60408051600160a060020a039092168252519081900360200190f35b6100e06004803603604081101561013a57600080fd5b50600160a060020a03813581169160200135166105bc565b6101c26004803603602081101561016857600080fd5b81019060208101813564010000000081111561018357600080fd5b82018360208201111561019557600080fd5b803590602001918460018302840111640100000000831117156101b757600080fd5b5090925090506106b3565b60408051918252519081900360200190f35b6100e0600480360360408110156101ea57600080fd5b508035600160a060020a03169060200135600160e060020a0319166106ee565b6101086004803603604081101561022057600080fd5b50600160a060020a038135169060200135610778565b61026c6004803603604081101561024c57600080fd5b508035600160a060020a03169060200135600160e060020a0319166107ef565b604080519115158252519081900360200190f35b61026c6004803603604081101561029657600080fd5b508035600160a060020a03169060200135600160e060020a0319166108aa565b6000600160a060020a038416156102cd57836102cf565b335b9050336102db82610570565b600160a060020a031614610339576040805160e560020a62461bcd02815260206004820152600f60248201527f4e6f7420746865206d616e616765720000000000000000000000000000000000604482015290519081900360640190fd5b6103428361092a565b15610397576040805160e560020a62461bcd02815260206004820152601a60248201527f4d757374206e6f7420626520616e204552433136352068617368000000000000604482015290519081900360640190fd5b600160a060020a038216158015906103b85750600160a060020a0382163314155b156104ff5760405160200180807f455243313832305f4143434550545f4d4147494300000000000000000000000081525060140190506040516020818303038152906040528051906020012082600160a060020a031663249cb3fa85846040518363ffffffff167c01000000000000000000000000000000000000000000000000000000000281526004018083815260200182600160a060020a0316600160a060020a031681526020019250505060206040518083038186803b15801561047e57600080fd5b505afa158015610492573d6000803e3d6000fd5b505050506040513d60208110156104a857600080fd5b5051146104ff576040805160e560020a62461bcd02815260206004820181905260248201527f446f6573206e6f7420696d706c656d656e742074686520696e74657266616365604482015290519081900360640190fd5b600160a060020a03818116600081815260208181526040808320888452909152808220805473ffffffffffffffffffffffffffffffffffffffff19169487169485179055518692917f93baa6efbd2244243bfee6ce4cfdd1d04fc4c0e9a786abd3a41313bd352db15391a450505050565b600160a060020a03818116600090815260016020526040812054909116151561059a5750806105b7565b50600160a060020a03808216600090815260016020526040902054165b919050565b336105c683610570565b600160a060020a031614610624576040805160e560020a62461bcd02815260206004820152600f60248201527f4e6f7420746865206d616e616765720000000000000000000000000000000000604482015290519081900360640190fd5b81600160a060020a031681600160a060020a0316146106435780610646565b60005b600160a060020a03838116600081815260016020526040808220805473ffffffffffffffffffffffffffffffffffffffff19169585169590951790945592519184169290917f605c2dbf762e5f7d60a546d42e7205dcb1b011ebc62a61736a57c9089d3a43509190a35050565b600082826040516020018083838082843780830192505050925050506040516020818303038152906040528051906020012090505b92915050565b6106f882826107ef565b610703576000610705565b815b600160a060020a03928316600081815260208181526040808320600160e060020a031996909616808452958252808320805473ffffffffffffffffffffffffffffffffffffffff19169590971694909417909555908152600284528181209281529190925220805460ff19166001179055565b600080600160a060020a038416156107905783610792565b335b905061079d8361092a565b156107c357826107ad82826108aa565b6107b85760006107ba565b815b925050506106e8565b600160a060020a0390811660009081526020818152604080832086845290915290205416905092915050565b6000808061081d857f01ffc9a70000000000000000000000000000000000000000000000000000000061094c565b909250905081158061082d575080155b1561083d576000925050506106e8565b61084f85600160e060020a031961094c565b909250905081158061086057508015155b15610870576000925050506106e8565b61087a858561094c565b909250905060018214801561088f5750806001145b1561089f576001925050506106e8565b506000949350505050565b600160a060020a0382166000908152600260209081526040808320600160e060020a03198516845290915281205460ff1615156108f2576108eb83836107ef565b90506106e8565b50600160a060020a03808316600081815260208181526040808320600160e060020a0319871684529091529020549091161492915050565b7bffffffffffffffffffffffffffffffffffffffffffffffffffffffff161590565b6040517f01ffc9a7000000000000000000000000000000000000000000000000000000008082526004820183905260009182919060208160248189617530fa90519096909550935050505056fea165627a7a72305820377f4a2d4301ede9949f163f319021a6e9c687c292a5e2b2c4734c126b524e6c00291ba01820182018201820182018201820182018201820182018201820182018201820a01820182018201820182018201820182018201820182018201820182018201820'; 29 | 30 | // deploy erc1820 registry 31 | const erc1820Registry = await web3.eth.sendSignedTransaction(rawTx); 32 | 33 | } else { 34 | console.log('erc1820Registry contract found'); 35 | } 36 | } 37 | -------------------------------------------------------------------------------- /erc777/test/README.md: -------------------------------------------------------------------------------- 1 | ### Run local tests 2 | 3 | ```bash 4 | $ truffle test --network ganache 5 | ``` 6 | 7 | More tests for this implementation can be found [here](https://github.com/0xjac/ERC777/tree/master/test). 8 | -------------------------------------------------------------------------------- /erc777/truffle.js: -------------------------------------------------------------------------------- 1 | /** 2 | * Use this file to configure your truffle project. It's seeded with some 3 | * common settings for different networks and features like migrations, 4 | * compilation and testing. Uncomment the ones you need or modify 5 | * them to suit your project as necessary. 6 | * 7 | * More information about configuration can be found at: 8 | * 9 | * truffleframework.com/docs/advanced/configuration 10 | * 11 | * To deploy via Infura you'll need a wallet provider (like truffle-hdwallet-provider) 12 | * to sign your transactions before they're sent to a remote public node. Infura API 13 | * keys are available for free at: infura.io/register 14 | * 15 | * You'll also need a mnemonic - the twelve word phrase the wallet uses to generate 16 | * public/private key pairs. If you're publishing your code to GitHub make sure you load this 17 | * phrase from a file you've .gitignored so it doesn't accidentally become public. 18 | * 19 | */ 20 | 21 | module.exports = { 22 | networks: { 23 | ganache: { 24 | host: '127.0.0.1', 25 | port: 7545, 26 | network_id: '*', // match any network id 27 | from: '0x954e72fdc51Cf919203067406fB337Ed4bDC8CdA', // account address from which to deploy 28 | gas: 4000000, 29 | } 30 | } 31 | } 32 | -------------------------------------------------------------------------------- /linear_optimization_problem_bounty/.gitignore: -------------------------------------------------------------------------------- 1 | ./build 2 | -------------------------------------------------------------------------------- /linear_optimization_problem_bounty/README.md: -------------------------------------------------------------------------------- 1 | # Linear optimization problem bounty 2 | 3 | This contract implements a bounty for solving linear optimization problems. 4 | An example problem is defined in the contracts `_calculateNewSolution(_x1: uint256, _x2: uint256)` method: 5 | 6 | ```python 7 | assert x1 <= 40 8 | assert x2 <= 35 9 | assert (3 * x1) + (2 * x2) <= 200 10 | assert x1 + x2 <= 120 11 | assert x1 > 0 and x2 > 0 12 | # calculate and return new solution 13 | return (4 * x1) + (6 * x2) 14 | ``` 15 | 16 | ### How it works 17 | 18 | Users can submit solutions using the `submitSolution(_x1: uint256, _x2: uint256)` method. 19 | The contract checks the submitted values against the problems constraints and saves/rejects them depending on whether the solution respects all of them. 20 | When the end of the competition is reached, the address that submitted the best solution can call `claimBounty()` to claim the Ether that is locked in the contract. 21 | 22 | ## Run tests 23 | ```bash 24 | $ truffle test --network ganache 25 | ``` 26 | -------------------------------------------------------------------------------- /linear_optimization_problem_bounty/contracts/Migrations.sol: -------------------------------------------------------------------------------- 1 | pragma solidity >=0.4.21 <0.6.0; 2 | 3 | contract Migrations { 4 | address public owner; 5 | uint public last_completed_migration; 6 | 7 | constructor() public { 8 | owner = msg.sender; 9 | } 10 | 11 | modifier restricted() { 12 | if (msg.sender == owner) _; 13 | } 14 | 15 | function setCompleted(uint completed) public restricted { 16 | last_completed_migration = completed; 17 | } 18 | 19 | function upgrade(address new_address) public restricted { 20 | Migrations upgraded = Migrations(new_address); 21 | upgraded.setCompleted(last_completed_migration); 22 | } 23 | } 24 | -------------------------------------------------------------------------------- /linear_optimization_problem_bounty/contracts/linear_optimization_problem_bounty.vy: -------------------------------------------------------------------------------- 1 | # Author: Sören Steiger, github.com/ssteiger 2 | # License: MIT 3 | 4 | # EVENTS: 5 | NewSolutionFound: event({_addressOfWinner: indexed(address), _solution: uint256}) 6 | BountyTransferred: event({_to: indexed(address), _amount: wei_value}) 7 | BountyIncreased: event({_amount: wei_value}) 8 | CompetitionTimeExtended: event({_to: uint256}) 9 | 10 | 11 | # STATE VARIABLES: 12 | owner: public(address) 13 | 14 | x1: public(uint256) 15 | x2: public(uint256) 16 | 17 | bestSolution: public(uint256) 18 | addressOfWinner: public(address) 19 | 20 | durationInBlocks: public(uint256) 21 | competitionEnd: public(uint256) 22 | claimPeriodeLength: public(uint256) 23 | 24 | 25 | # METHODS: 26 | @public 27 | def __init__(_durationInBlocks: uint256): 28 | self.owner = msg.sender 29 | self.bestSolution = 0 30 | self.durationInBlocks = _durationInBlocks 31 | self.competitionEnd = block.number + _durationInBlocks 32 | self.addressOfWinner = ZERO_ADDRESS 33 | # set claim periode to three days 34 | # assuming an average blocktime of 14 seconds -> 86400/14 35 | self.claimPeriodeLength = 6172 36 | 37 | 38 | @public 39 | @payable 40 | def __default__(): 41 | # return any funds sent to the contract address directly 42 | send(msg.sender, msg.value) 43 | 44 | 45 | @private 46 | def _calculateNewSolution(_x1: uint256, _x2: uint256) -> uint256: 47 | # check new parameters against constraints 48 | assert _x1 <= 40 49 | assert _x2 <= 35 50 | assert (3 * _x1) + (2 * _x2) <= 200 51 | assert _x1 + _x2 <= 120 52 | assert _x1 > 0 and _x2 > 0 53 | # calculate and return new solution 54 | return (4 * _x1) + (6 * _x2) 55 | 56 | 57 | @public 58 | def submitSolution(_x1: uint256, _x2: uint256) -> uint256: 59 | newSolution: uint256 60 | newSolution = self._calculateNewSolution(_x1, _x2) 61 | assert newSolution > self.bestSolution 62 | # save the solution and it's values 63 | self.x1 = _x1 64 | self.x2 = _x2 65 | self.bestSolution = newSolution 66 | self.addressOfWinner = msg.sender 67 | log.NewSolutionFound(msg.sender, newSolution) 68 | return newSolution 69 | 70 | 71 | @public 72 | def claimBounty(): 73 | assert block.number > self.competitionEnd 74 | if (self.addressOfWinner == ZERO_ADDRESS): 75 | # no solution was found -> extend duration of competition 76 | self.competitionEnd = block.number + self.durationInBlocks 77 | else: 78 | assert block.number < (self.competitionEnd + self.claimPeriodeLength) 79 | assert msg.sender == self.addressOfWinner 80 | send(self.addressOfWinner, self.balance) 81 | # extend duration of competition 82 | self.competitionEnd = block.number + self.durationInBlocks 83 | log.BountyTransferred(self.addressOfWinner, self.balance) 84 | 85 | 86 | @public 87 | @payable 88 | def topUpBounty(): 89 | log.BountyIncreased(msg.value) 90 | 91 | 92 | @public 93 | def extendCompetition(): 94 | # only if no valid solution has been submitted 95 | assert self.addressOfWinner == ZERO_ADDRESS 96 | assert block.number > (self.competitionEnd + self.claimPeriodeLength) 97 | # extend duration of competition 98 | self.competitionEnd = block.number + self.durationInBlocks 99 | # reset winner address 100 | self.addressOfWinner = ZERO_ADDRESS 101 | log.CompetitionTimeExtended(self.competitionEnd) 102 | -------------------------------------------------------------------------------- /linear_optimization_problem_bounty/migrations/1_initial_migration.js: -------------------------------------------------------------------------------- 1 | var Migrations = artifacts.require("./Migrations.sol"); 2 | 3 | module.exports = function(deployer) { 4 | deployer.deploy(Migrations); 5 | }; 6 | -------------------------------------------------------------------------------- /linear_optimization_problem_bounty/migrations/2_deploy_contracts.js: -------------------------------------------------------------------------------- 1 | var LOPB = artifacts.require("linear_optimization_problem_bounty"); 2 | 3 | module.exports = function(deployer) { 4 | const durationInBlocks = 100; 5 | deployer.deploy(durationInBlocks); 6 | }; 7 | -------------------------------------------------------------------------------- /linear_optimization_problem_bounty/test/linear_optimization_problem_bounty.js: -------------------------------------------------------------------------------- 1 | const LOPB = artifacts.require("linear_optimization_problem_bounty"); 2 | 3 | const args = { 4 | durationInBlocks: 10, 5 | } 6 | 7 | contract("linear_optimization_problem_bounty", async accounts => { 8 | it("...should set correct initial paramters.", async () => { 9 | const instance = await LOPB.deployed({ ...args }); 10 | const owner = await instance.owner(); 11 | const bestSolution = await instance.bestSolution(); 12 | const durationInBlocks = await instance.durationInBlocks(); 13 | const competitionEnd = await instance.competitionEnd(); 14 | const addressOfWinner = await instance.addressOfWinner(); 15 | const claimPeriodeLength = await instance.claimPeriodeLength(); 16 | 17 | const minedBlockNumber = await web3.eth.getBlockNumber(); 18 | 19 | assert.equal( 20 | owner, 21 | accounts[0], 22 | "Owner wasn't correctly set." 23 | ); 24 | assert.equal( 25 | bestSolution, 26 | 0, 27 | "BestSolution wasn't initialized with 0." 28 | ); 29 | assert.equal( 30 | durationInBlocks, 31 | args.durationInBlocks, 32 | "DurationInBlocks wasn't correctly set." 33 | ); 34 | assert.equal( 35 | competitionEnd, 36 | minedBlockNumber + durationInBlocks, 37 | "DurationInBlocks wasn't correctly set." 38 | ); 39 | assert.equal( 40 | addressOfWinner, 41 | '0x0000000000000000000000000000000000000000', 42 | "AddressOfWinner wasn't correctly initialized with ZERO_ADDRESS." 43 | ); 44 | assert.equal( 45 | claimPeriodeLength, 46 | args.durationInBlocks, 47 | "ClaimPeriodeLength wasn't correctly set." 48 | ); 49 | }); 50 | 51 | it("...should set correct values when submitting new valid solution.", async () => { 52 | const instance = await LOPB.deployed({ ...args }); 53 | const x1 = 1; 54 | const x2 = 1; 55 | await instance.submitSolution.call(x1, x2); 56 | 57 | const instance_x1 = await instance.x1(); 58 | const instance_x2 = await instance.x2(); 59 | const instance_bestSolution = await instance.bestSolution(); 60 | const instance_addressOfWinner = await instance.addressOfWinner(); 61 | 62 | assert.equal( 63 | instance_x1, 64 | x1, 65 | "x1 value wasn't correctly set." 66 | ); 67 | assert.equal( 68 | instance_x2, 69 | x2, 70 | "x2 value wasn't correctly set." 71 | ); 72 | // NOTE: update this if the problem in the contract is changed 73 | assert.equal( 74 | instance_bestSolution, 75 | (4 * x1) + (6 * x2), 76 | "BestSolution value wasn't correctly set." 77 | ); 78 | assert.equal( 79 | instance_addressOfWinner, 80 | accounts[0], 81 | "AddressOfWinner wasn't correctly set." 82 | ); 83 | }); 84 | 85 | it("...should increase bounty.", async () => { 86 | const instance = await LOPB.deployed({ ...args }); 87 | const contractBalance_BeforeTopUp = web3.eth.getBalance(instance); 88 | const transactionValue_inEth = 1; 89 | const transactionValue_inWei = web3.utils.toWei(transactionValue_inEth); 90 | // top up bounty 91 | await instance.topUpBounty.call({}, { from: account[0], value: transactionValue_inWei }); 92 | 93 | const contractBalance_AfterTopUp = web3.eth.getBalance(instance); 94 | assert.equal( 95 | contractBalance_BeforeTopUp, 96 | contractBalance_AfterTopUp - transactionValue_inWei, 97 | "Bounty was not correctly increased." 98 | ); 99 | }); 100 | 101 | // TODO: this is not complete 102 | // TODO: Hmmm, need to create 'claimPeriodeLength = 6172' blocks 103 | // so that block.number > (self.competitionEnd + self.claimPeriodeLength) 104 | // not sure how to test this... 105 | it("...should claim bounty.", async () => { 106 | const instance = await LOPB.deployed({ ...args }); 107 | 108 | const contractBalance_Before = web3.eth.getBalance(instance); 109 | const competitionEnd_Before = await instance.competitionEnd(); 110 | const x1 = 1; 111 | const x2 = 1; 112 | const transactionValue_inEth = 1; 113 | const transactionValue_inWei = web3.utils.toWei(transactionValue_inEth); 114 | // top up bounty 115 | await instance.topUpBounty.call({}, { from: account[0], value: transactionValue_inWei }); 116 | const contractBalance_After = web3.eth.getBalance(instance); 117 | await instance.claimBounty(); 118 | 119 | const competitionEnd_After = await instance.competitionEnd(); 120 | 121 | }); 122 | 123 | // TODO: 124 | it("...should extend competition.", async () => { 125 | const instance = await LOPB.deployed({ durationInBlocks: 1 }); 126 | const durationInBlocks = await instance.durationInBlocks(); 127 | const competitionEnd_Before = await instance.competitionEnd(); 128 | 129 | // TODO: Hmmm, need to create 'claimPeriodeLength = 6172' blocks 130 | // so that block.number > (self.competitionEnd + self.claimPeriodeLength) 131 | // not sure how to test this... 132 | 133 | /* 134 | // create dummy block 135 | 136 | const result = await web3.eth.sendTransaction({ 137 | from: account[0], 138 | to: '0x0000000000000000000000000000000000000000', 139 | value: 1, // in wei 140 | }); 141 | */ 142 | }); 143 | }); 144 | -------------------------------------------------------------------------------- /linear_optimization_problem_bounty/truffle.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | networks: { 3 | ganache: { 4 | host: '127.0.0.1', 5 | port: 7545, 6 | network_id: '*', // match any network id 7 | from: '0x954e72fdc51Cf919203067406fB337Ed4bDC8CdA', // account address from which to deploy 8 | gas: 4000000, 9 | } 10 | } 11 | } 12 | -------------------------------------------------------------------------------- /vyperStorage/.gitignore: -------------------------------------------------------------------------------- 1 | ./build 2 | -------------------------------------------------------------------------------- /vyperStorage/README.md: -------------------------------------------------------------------------------- 1 | # Vyper Storage 2 | 3 | ## Run tests 4 | ```bash 5 | $ truffle test --network ganache 6 | ``` 7 | -------------------------------------------------------------------------------- /vyperStorage/contracts/Migrations.sol: -------------------------------------------------------------------------------- 1 | pragma solidity >=0.4.21 <0.6.0; 2 | 3 | contract Migrations { 4 | address public owner; 5 | uint public last_completed_migration; 6 | 7 | constructor() public { 8 | owner = msg.sender; 9 | } 10 | 11 | modifier restricted() { 12 | if (msg.sender == owner) _; 13 | } 14 | 15 | function setCompleted(uint completed) public restricted { 16 | last_completed_migration = completed; 17 | } 18 | 19 | function upgrade(address new_address) public restricted { 20 | Migrations upgraded = Migrations(new_address); 21 | upgraded.setCompleted(last_completed_migration); 22 | } 23 | } 24 | -------------------------------------------------------------------------------- /vyperStorage/contracts/vyperStorage.vy: -------------------------------------------------------------------------------- 1 | stored_data: public(uint256) 2 | 3 | @public 4 | def __init__(): 5 | self.stored_data = 0 6 | 7 | @public 8 | def set(new_value: uint256): 9 | self.stored_data = new_value 10 | -------------------------------------------------------------------------------- /vyperStorage/migrations/1_initial_migration.js: -------------------------------------------------------------------------------- 1 | var Migrations = artifacts.require("./Migrations.sol"); 2 | 3 | module.exports = function(deployer) { 4 | deployer.deploy(Migrations); 5 | }; 6 | -------------------------------------------------------------------------------- /vyperStorage/migrations/2_deploy_contracts.js: -------------------------------------------------------------------------------- 1 | var VyperStorage = artifacts.require("VyperStorage"); 2 | 3 | module.exports = function(deployer) { 4 | deployer.deploy(VyperStorage); 5 | }; 6 | -------------------------------------------------------------------------------- /vyperStorage/test/vyperStorage.js: -------------------------------------------------------------------------------- 1 | const vyperStorage = artifacts.require('vyperStorage'); 2 | 3 | contract('vyperStorage', () => { 4 | it('...should initialize storage with 0.', async () => { 5 | const storage = await vyperStorage.deployed(); 6 | 7 | const storedData = await storage.stored_data(); 8 | 9 | assert.equal( 10 | storedData, 11 | 0, 12 | 'The value 89 was not stored.' 13 | ); 14 | }); 15 | 16 | it('...should store the value 89.', async () => { 17 | const storage = await vyperStorage.deployed(); 18 | 19 | await storage.set(89); 20 | 21 | const storedData = await storage.stored_data(); 22 | 23 | assert.equal( 24 | storedData, 25 | 89, 26 | 'The value 89 was not stored.' 27 | ); 28 | }); 29 | }); 30 | -------------------------------------------------------------------------------- /vyperStorage/truffle.js: -------------------------------------------------------------------------------- 1 | /** 2 | * Use this file to configure your truffle project. It's seeded with some 3 | * common settings for different networks and features like migrations, 4 | * compilation and testing. Uncomment the ones you need or modify 5 | * them to suit your project as necessary. 6 | * 7 | * More information about configuration can be found at: 8 | * 9 | * truffleframework.com/docs/advanced/configuration 10 | * 11 | * To deploy via Infura you'll need a wallet provider (like truffle-hdwallet-provider) 12 | * to sign your transactions before they're sent to a remote public node. Infura API 13 | * keys are available for free at: infura.io/register 14 | * 15 | * You'll also need a mnemonic - the twelve word phrase the wallet uses to generate 16 | * public/private key pairs. If you're publishing your code to GitHub make sure you load this 17 | * phrase from a file you've .gitignored so it doesn't accidentally become public. 18 | * 19 | */ 20 | 21 | module.exports = { 22 | networks: { 23 | ganache: { 24 | host: '127.0.0.1', 25 | port: 7545, 26 | network_id: '*', // match any network id 27 | from: '0x954e72fdc51Cf919203067406fB337Ed4bDC8CdA', // account address from which to deploy 28 | gas: 4000000, 29 | } 30 | } 31 | } 32 | -------------------------------------------------------------------------------- /wallet/.gitignore: -------------------------------------------------------------------------------- 1 | ./build 2 | -------------------------------------------------------------------------------- /wallet/README.md: -------------------------------------------------------------------------------- 1 | # Wallet 2 | 3 | This wallet contract is capable of receiving, holding and sending Ether as well as tokens that comply with one 4 | of the following standards: 5 | 6 | * [ERC20](https://eips.ethereum.org/EIPS/eip-20) 7 | * [ERC721](https://eips.ethereum.org/EIPS/eip-721) 8 | * [ERC777](https://eips.ethereum.org/EIPS/eip-777) 9 | 10 | ## Run tests 11 | 12 | ```bash 13 | $ truffle test --network ganache 14 | ``` 15 | 16 | The `wallet` contract accesses the `ERC1820Registry` contract in its constructor. 17 | It is therefore necessary that the `ERC1820Registry` contract exists on the (test) network to where the `wallet` contract gets deployed. 18 | In `migrations/1_initial_migration.js` a check is performed to determine if the `ERC1820Registry` contract exists - if it doesn't it is deployed. 19 | -------------------------------------------------------------------------------- /wallet/contracts/Migrations.sol: -------------------------------------------------------------------------------- 1 | pragma solidity >=0.4.21 <0.6.0; 2 | 3 | contract Migrations { 4 | address public owner; 5 | uint public last_completed_migration; 6 | 7 | constructor() public { 8 | owner = msg.sender; 9 | } 10 | 11 | modifier restricted() { 12 | if (msg.sender == owner) _; 13 | } 14 | 15 | function setCompleted(uint completed) public restricted { 16 | last_completed_migration = completed; 17 | } 18 | 19 | function upgrade(address new_address) public restricted { 20 | Migrations upgraded = Migrations(new_address); 21 | upgraded.setCompleted(last_completed_migration); 22 | } 23 | } 24 | -------------------------------------------------------------------------------- /wallet/contracts/helpers/README.md: -------------------------------------------------------------------------------- 1 | NOTE: The contracts in this folder are used for testing purposes. 2 | -------------------------------------------------------------------------------- /wallet/contracts/helpers/erc20.vy: -------------------------------------------------------------------------------- 1 | # Author: Sören Steiger, github.com/ssteiger 2 | # License: MIT 3 | 4 | # ERC20 Token Standard 5 | # https://github.com/ethereum/EIPs/blob/master/EIPS/eip-20.md 6 | 7 | 8 | from vyper.interfaces import ERC20 9 | 10 | implements: ERC20 11 | 12 | # EVENTS: 13 | 14 | # ----- Transfer ----- 15 | # MUST trigger when tokens are transferred, including zero value transfers. 16 | # A token contract which creates new tokens SHOULD trigger a Transfer event 17 | # with the _from address set to 0x0 when tokens are created. 18 | Transfer: event({_from: indexed(address), _to: indexed(address), _value: uint256}) 19 | 20 | # ----- Approval ----- 21 | # MUST trigger on any successful call to approve(address _spender, uint256 _value). 22 | Approval: event({_owner: indexed(address), _spender: indexed(address), _value: uint256}) 23 | 24 | 25 | # STATE VARIABLES: 26 | # values which are permanently stored in contract storage 27 | 28 | # ----- name ----- 29 | # Returns the name of the token - e.g. "MyToken". 30 | # OPTIONAL - This method can be used to improve usability, but interfaces and 31 | # other contracts MUST NOT expect these values to be present. 32 | name: public(string[64]) # TODO: is this an acceptable size? 33 | 34 | # ----- symbol ----- 35 | # Returns the symbol of the token. E.g. "HIX". 36 | # OPTIONAL - This method can be used to improve usability, but interfaces and 37 | # other contracts MUST NOT expect these values to be present. 38 | symbol: public(string[32]) # TODO: is this an acceptable size? 39 | 40 | # ----- decimals ----- 41 | # Returns the number of decimals the token uses - e.g. 8, means to divide 42 | # the token amount by 100000000 to get its user representation. 43 | # OPTIONAL - This method can be used to improve usability, but interfaces and 44 | # other contracts MUST NOT expect these values to be present. 45 | decimals: public(uint256) 46 | 47 | # ----- totalSupply ----- 48 | # Returns the total token supply. 49 | totalSupply: public(uint256) 50 | 51 | # mappings 52 | balanceOf: public(map(address, uint256)) 53 | approvedFunds: map(address, map(address, uint256)) 54 | 55 | 56 | @public 57 | def __init__(_name: string[64], _symbol: string[32], _decimals: uint256, _totalSupply: uint256): 58 | self.name = _name 59 | self.symbol = _symbol 60 | self.decimals = _decimals 61 | #self.totalSupply = _totalSupply * 10 ** _decimals 62 | self.totalSupply = _totalSupply 63 | # mint all tokens to the contract creator 64 | self.balanceOf[msg.sender] = self.totalSupply 65 | # fire transfer event 66 | log.Transfer(ZERO_ADDRESS, msg.sender, self.totalSupply) 67 | 68 | 69 | # METHODS: 70 | 71 | # NOTES: 72 | # Callers MUST handle false from returns (bool success). 73 | # Callers MUST NOT assume that false is never returned! 74 | 75 | 76 | # ----- balanceOf ----- 77 | # Returns the account balance of another account with address _owner. 78 | # See: https://github.com/ethereum/vyper/issues/1241 79 | # And: https://vyper.readthedocs.io/en/v0.1.0-beta.8/types.html?highlight=getter#mappings 80 | 81 | 82 | # ----- transfer ----- 83 | # Transfers _value amount of tokens to address _to, and MUST fire the Transfer 84 | # event. The function SHOULD throw if the _from account balance does not have 85 | # enough tokens to spend. 86 | 87 | # NOTE: Transfers of 0 values MUST be treated as normal transfers and fire the 88 | # Transfer event. 89 | @public 90 | def transfer(_to: address, _value: uint256) -> bool: 91 | # NOTE: vyper does not allow unterflows 92 | # so checks for sufficient funds are done implicitly 93 | # see https://github.com/ethereum/vyper/issues/1237#issuecomment-461957413 94 | # substract balance from sender 95 | self.balanceOf[msg.sender] -= _value 96 | # add balance to recipient 97 | self.balanceOf[_to] += _value 98 | # fire transfer event 99 | log.Transfer(msg.sender, _to, _value) 100 | return True 101 | 102 | 103 | # ----- transferFrom ----- 104 | # Transfers _value amount of tokens from address _from to address _to, 105 | # and MUST fire the Transfer event. 106 | 107 | # The transferFrom method is used for a withdraw workflow, allowing contracts 108 | # to transfer tokens on your behalf. This can be used for example to allow a 109 | # contract to transfer tokens on your behalf and/or to charge fees in 110 | # sub-currencies. The function SHOULD throw unless the _from account has 111 | # deliberately authorized the sender of the message via some mechanism. 112 | 113 | # NOTE: Transfers of 0 values MUST be treated as normal transfers and fire the 114 | # Transfer event. 115 | @public 116 | def transferFrom(_from: address, _to: address, _value: uint256) -> bool: 117 | # NOTE: vyper does not allow unterflows 118 | # so checks for sufficient funds are done implicitly 119 | # see https://github.com/ethereum/vyper/issues/1237#issuecomment-461957413 120 | # update approved funds 121 | self.approvedFunds[_from][msg.sender] -= _value 122 | # update sender balance 123 | self.balanceOf[_from] -= _value 124 | # update recipient balance 125 | self.balanceOf[_to] += _value 126 | # fire transfer event 127 | log.Transfer(_from, _to, _value) 128 | return True 129 | 130 | 131 | # ----- approve ----- 132 | # Allows _spender to withdraw from your account multiple times, up to the _value 133 | # amount. If this function is called again it overwrites the current allowance 134 | # with _value. 135 | 136 | # NOTE: To prevent attack vectors like the one described here and discussed here, 137 | # clients SHOULD make sure to create user interfaces in such a way that they set 138 | # the allowance first to 0 before setting it to another value for the same 139 | # spender. THOUGH The contract itself shouldn't enforce it, to allow backwards 140 | # compatibility with contracts deployed before. 141 | @public 142 | def approve(_spender: address, _value: uint256) -> bool: 143 | # overwrites the current allowance 144 | self.approvedFunds[msg.sender][_spender] = _value 145 | # fire approval event 146 | log.Approval(msg.sender, _spender, _value) 147 | return True 148 | 149 | 150 | # ----- allowance ----- 151 | # Returns the amount which _spender is still allowed to withdraw from _owner. 152 | @public 153 | @constant 154 | def allowance(_owner: address, _spender: address) -> uint256: 155 | return self.approvedFunds[_owner][_spender] 156 | -------------------------------------------------------------------------------- /wallet/contracts/helpers/erc721.vy: -------------------------------------------------------------------------------- 1 | # Author: Sören Steiger, github.com/ssteiger 2 | # License: MIT 3 | 4 | # ERC721 Token Standard 5 | # https://github.com/ethereum/EIPs/blob/master/EIPS/eip-721.md 6 | 7 | # @dev Note: the ERC-165 identifier for this interface is 0x150b7a02. 8 | # @notice Handle the receipt of an NFT 9 | # @dev The ERC721 smart contract calls this function on the recipient 10 | # after a `transfer`. 11 | # This function MAY throw to revert and reject the transfer. 12 | # Return of other than the magic value MUST result in the transaction 13 | # being reverted. 14 | # Note: the contract address is always the message sender. 15 | # @param _operator The address which called `safeTransferFrom` function 16 | # @param _from The address which previously owned the token 17 | # @param _tokenId The NFT identifier which is being transferred 18 | # @param _data Additional data with no specified format 19 | # @return `bytes4(keccak256("onERC721Received(address,address,uint256,bytes)"))` 20 | # unless throwing 21 | # function onERC721Received(address _operator, address _from, uint256 _tokenId, bytes _data) external returns(bytes4); 22 | contract ERC721TokenReceiver: 23 | def onERC721Received( 24 | _operator: address, 25 | _from: address, 26 | _tokenId: uint256, 27 | _data: bytes[256] 28 | ) -> bytes32: constant 29 | 30 | 31 | # EVENTS: 32 | 33 | # @dev This emits when ownership of any NFT changes by any mechanism. 34 | # This event emits when NFTs are created (`from` == 0) and destroyed 35 | # (`to` == 0). 36 | # Exception: during contract creation, any number of NFTs may be created 37 | # and assigned without emitting Transfer. 38 | # At the time of any transfer, the approved address for that NFT (if any) 39 | # is reset to none. 40 | Transfer: event({ 41 | _from: indexed(address), 42 | _to: indexed(address), 43 | _tokenId: indexed(uint256) 44 | }) 45 | 46 | 47 | # NOTE: This is not part of the standard 48 | Mint: event({ 49 | _to: indexed(address), 50 | _tokenId: indexed(uint256) 51 | }) 52 | 53 | 54 | # @dev This emits when the approved address for an NFT is changed or reaffirmed. 55 | # The zero address indicates there is no approved address. 56 | # When a Transfer event emits, this also indicates that the approved 57 | # address for that NFT (if any) is reset to none. 58 | Approval: event({ 59 | _owner: indexed(address), 60 | _approved: indexed(address), 61 | _tokenId: indexed(uint256) 62 | }) 63 | 64 | 65 | # @dev This emits when an operator is enabled or disabled for an owner. 66 | # The operator can manage all NFTs of the owner. 67 | ApprovalForAll: event({ 68 | _owner: indexed(address), 69 | _operator: indexed(address), 70 | _approved: bool 71 | }) 72 | 73 | 74 | # STATE VARIABLES: 75 | 76 | # NOTE: This is not part of the standard 77 | contractOwner: public(address) 78 | # Used for token id's 79 | nftSupply: uint256 80 | 81 | # Used to keep track of the number of tokens an address holds 82 | nftCount: public(map(address, uint256)) 83 | ownerOfNFT: public(map(uint256, address)) 84 | 85 | operatorFor: public(map(uint256, address)) 86 | approvedForAll: public(map(address, map(address, bool))) 87 | 88 | # Interface detection as specified in ERC165 89 | # https://github.com/ethereum/EIPs/blob/master/EIPS/eip-165.md 90 | supportedInterfaces: public(map(bytes32, bool)) 91 | # ERC165 interface ID's 92 | ERC165_INTERFACE_ID: constant(bytes32) = 0x0000000000000000000000000000000000000000000000000000000001ffc9a7 93 | ERC721_INTERFACE_ID: constant(bytes32) = 0x0000000000000000000000000000000000000000000000000000000080ac58cd 94 | 95 | 96 | # METHODS: 97 | @public 98 | def __init__(): 99 | # set initial supply (used for token id's) 100 | self.nftSupply = 0 101 | # set supported interfaces 102 | self.supportedInterfaces[ERC165_INTERFACE_ID] = True 103 | self.supportedInterfaces[ERC721_INTERFACE_ID] = True 104 | # set contract owner 105 | # NOTE: This is not part of the standard 106 | # only contractOwner can call mint() 107 | self.contractOwner = msg.sender 108 | 109 | 110 | @private 111 | def _checkIfIsOwnerOrOperatorOrApprovedForAll(_msgSender: address, _from: address, _tokenId: uint256): 112 | # Throws unless `msg.sender` is 113 | # the current owner 114 | isOwner: bool = self.ownerOfNFT[_tokenId] == _msgSender 115 | # an authorized operator 116 | isOperator: bool = self.operatorFor[_tokenId] == _msgSender 117 | # or the approved address for this NFT 118 | isApprovedForAll: bool = (self.approvedForAll[_from])[_msgSender] 119 | assert (isOwner or isOperator or isApprovedForAll) 120 | 121 | 122 | @private 123 | def _setNewOwner(_currentOwner: address, _newOwner: address, _tokenId: uint256): 124 | # set new owner 125 | self.ownerOfNFT[_tokenId] = _newOwner 126 | # updated balances 127 | self.nftCount[_currentOwner] -= 1 128 | self.nftCount[_newOwner] += 1 129 | # reset operator 130 | self.operatorFor[_tokenId] = ZERO_ADDRESS 131 | 132 | 133 | @private 134 | def _transfer(_from: address, _to: address, _tokenId: uint256): 135 | # Throws if `_from` is not the current owner. 136 | assert self.ownerOfNFT[_tokenId] == _from 137 | # Throws if `_to` is the zero address. 138 | assert _to != ZERO_ADDRESS 139 | # Throws if `_tokenId` is not a valid NFT. 140 | assert self.ownerOfNFT[_tokenId] != ZERO_ADDRESS 141 | # transfer to new owner 142 | self._setNewOwner(_from, _to, _tokenId) 143 | # log transfer 144 | log.Transfer(_from, _to, _tokenId) 145 | 146 | 147 | @public 148 | @constant 149 | def supportsInterface(_interfaceID: bytes32) -> bool: 150 | # Interface detection as specified in ERC165 151 | # https://github.com/ethereum/EIPs/blob/master/EIPS/eip-165.md 152 | return self.supportedInterfaces[_interfaceID] 153 | 154 | 155 | # @notice Count all NFTs assigned to an owner 156 | # @dev NFTs assigned to the zero address are considered invalid, and this 157 | # function throws for queries about the zero address. 158 | # @param _owner An address for whom to query the balance 159 | # @return The number of NFTs owned by `_owner`, possibly zero 160 | # function balanceOf(address _owner) external view returns (uint256); 161 | @public 162 | @constant 163 | def balanceOf(_owner: address) -> uint256: 164 | # NFTs assigned to the zero address are considered invalid, and this 165 | # function throws for queries about the zero address. 166 | assert _owner != ZERO_ADDRESS 167 | return self.nftCount[_owner] 168 | 169 | 170 | # @notice Find the owner of an NFT 171 | # @dev NFTs assigned to zero address are considered invalid, and queries 172 | # about them do throw. 173 | # @param _tokenId The identifier for an NFT 174 | # @return The address of the owner of the NFT 175 | # function ownerOf(uint256 _tokenId) external view returns (address); 176 | @public 177 | @constant 178 | def ownerOf(_tokenId: uint256) -> address: 179 | # NFTs assigned to the zero address are considered invalid, and this 180 | # function throws for queries about the zero address. 181 | owner: address = self.ownerOfNFT[_tokenId] 182 | assert owner != ZERO_ADDRESS 183 | return owner 184 | 185 | 186 | # @notice Transfers the ownership of an NFT from one address to another address 187 | # @dev Throws unless `msg.sender` is 188 | # the current owner, 189 | # an authorized operator, 190 | # or the approved address for this NFT. 191 | # Throws if `_from` is not the current owner. 192 | # Throws if `_to` is the zero address. 193 | # Throws if `_tokenId` is not a valid NFT. 194 | # When transfer is complete, this function checks if `_to` is 195 | # a smart contract (code size > 0). If so, it calls 196 | # `onERC721Received` on `_to` and throws if the return value is not 197 | # `bytes4(keccak256("onERC721Received(address,address,uint256,bytes)"))`. 198 | # @param _from The current owner of the NFT 199 | # @param _to The new owner 200 | # @param _tokenId The NFT to transfer 201 | # @param data Additional data with no specified format, sent in call to `_to` 202 | # function safeTransferFrom(address _from, address _to, uint256 _tokenId, bytes data) external payable; 203 | @public 204 | @payable 205 | def safeTransferFrom(_from: address, _to: address, _tokenId: uint256, _data: bytes[256]=""): 206 | # Throws unless `msg.sender` is 207 | # the current owner, 208 | # an authorized operator, 209 | # or the approved address for this NFT. 210 | self._checkIfIsOwnerOrOperatorOrApprovedForAll(msg.sender, _from, _tokenId) 211 | # transfer 212 | self._transfer(_from, _to, _tokenId) 213 | # When transfer is complete, 214 | # this function checks if `_to` is a smart contract (code size > 0) 215 | if _to.is_contract: 216 | # If so, it calls `onERC721Received` on `_to` and throws if the return value is not 217 | # `bytes4(keccak256("onERC721Received(address,address,uint256,bytes)"))`. 218 | returnValue: bytes32 = ERC721TokenReceiver(_to).onERC721Received(msg.sender, _from, _tokenId, _data) 219 | assert returnValue == method_id("onERC721Received(address,address,uint256,bytes)", bytes32) 220 | 221 | 222 | # @notice Transfers the ownership of an NFT from one address to another address 223 | # @dev This works identically to the other function with an extra data 224 | # parameter, except this function just sets data to "". 225 | # @param _from The current owner of the NFT 226 | # @param _to The new owner 227 | # @param _tokenId The NFT to transfer 228 | # function safeTransferFrom(address _from, address _to, uint256 _tokenId) external payable; 229 | 230 | 231 | # @notice Transfer ownership of an NFT -- THE CALLER IS RESPONSIBLE 232 | # TO CONFIRM THAT `_to` IS CAPABLE OF RECEIVING NFTS OR ELSE 233 | # THEY MAY BE PERMANENTLY LOST 234 | # @dev Throws unless 235 | # `msg.sender` is the current owner, 236 | # an authorized operator, 237 | # or the approved address for this NFT. 238 | # Throws if `_from` is not the current owner. 239 | # Throws if `_to` is the zero address. 240 | # Throws if `_tokenId` is not a valid NFT. 241 | # @param _from The current owner of the NFT 242 | # @param _to The new owner 243 | # @param _tokenId The NFT to transfer 244 | # function transferFrom(address _from, address _to, uint256 _tokenId) external payable; 245 | @public 246 | @payable 247 | def transferFrom(_from: address, _to: address, _tokenId: uint256): 248 | # Throws unless `msg.sender` is 249 | # the current owner, 250 | # an authorized operator, 251 | # or the approved address for this NFT. 252 | self._checkIfIsOwnerOrOperatorOrApprovedForAll(msg.sender, _from, _tokenId) 253 | # do transfer 254 | self._transfer(_from, _to, _tokenId) 255 | 256 | 257 | # @notice Change or reaffirm the approved address for an NFT 258 | # @dev The zero address indicates there is no approved address. 259 | # Throws unless `msg.sender` is 260 | # the current NFT owner, 261 | # or an authorized operator of the current owner. 262 | # @param _approved The new approved NFT controller 263 | # @param _tokenId The NFT to approve 264 | # function approve(address _approved, uint256 _tokenId) external payable; 265 | @public 266 | @payable 267 | def approve(_approved: address, _tokenId: uint256): 268 | # Throws if _tokenId is not owned / a valid NFT 269 | assert self.ownerOfNFT[_tokenId] != ZERO_ADDRESS 270 | # Throws unless `msg.sender` is the current NFT owner 271 | isOwner: bool = self.ownerOfNFT[_tokenId] == msg.sender 272 | # or an authorized operator of the current owner. 273 | isOperator: bool = self.operatorFor[_tokenId] == msg.sender 274 | # TODO: does the this include approvedForAll? 275 | assert (isOwner or isOperator) 276 | # set new approved address 277 | self.operatorFor[_tokenId] = _approved 278 | # log change 279 | log.Approval(msg.sender, _approved, _tokenId) 280 | 281 | 282 | # @notice Enable or disable approval for a third party ("operator") to manage 283 | # all of `msg.sender`'s assets 284 | # @dev Emits the ApprovalForAll event. 285 | # The contract MUST allow multiple operators per owner. 286 | # @param _operator Address to add to the set of authorized operators 287 | # @param _approved True if the operator is approved, false to revoke approval 288 | # function setApprovalForAll(address _operator, bool _approved) external; 289 | @public 290 | def setApprovalForAll(_operator: address, _approved: bool): 291 | # The contract MUST allow multiple operators per owner. 292 | self.approvedForAll[msg.sender][_operator] = _approved 293 | # log change 294 | log.ApprovalForAll(msg.sender, _operator, _approved) 295 | 296 | 297 | # @notice Get the approved address for a single NFT 298 | # @dev Throws if `_tokenId` is not a valid NFT. 299 | # @param _tokenId The NFT to find the approved address for 300 | # @return The approved address for this NFT, or the zero address if 301 | # there is none 302 | # function getApproved(uint256 _tokenId) external view returns (address); 303 | @public 304 | @constant 305 | def getApproved(_tokenId: uint256) -> address: 306 | # Throws if `_tokenId` is not a valid NFT. 307 | assert self.ownerOfNFT[_tokenId] != ZERO_ADDRESS 308 | return self.operatorFor[_tokenId] 309 | 310 | 311 | # @notice Query if an address is an authorized operator for another address 312 | # @param _owner The address that owns the NFTs 313 | # @param _operator The address that acts on behalf of the owner 314 | # @return True if `_operator` is an approved operator for `_owner`, 315 | # false otherwise 316 | # function isApprovedForAll(address _owner, address _operator) external view returns (bool); 317 | @public 318 | @constant 319 | def isApprovedForAll(_owner: address, _operator: address) -> bool: 320 | return (self.approvedForAll[_owner])[_operator] 321 | 322 | 323 | # NOTE: This is not part of the standard 324 | @public 325 | def mint() -> uint256: 326 | # only contractOwner is allowed to mint 327 | assert msg.sender == self.contractOwner 328 | # update supply 329 | tokenId: uint256 = self.nftSupply 330 | self.nftSupply += 1 331 | # update ownership 332 | self.ownerOfNFT[tokenId] = msg.sender 333 | self.nftCount[msg.sender] += 1 334 | self.operatorFor[tokenId] = ZERO_ADDRESS 335 | # log mint 336 | log.Mint(msg.sender, tokenId) 337 | return tokenId 338 | -------------------------------------------------------------------------------- /wallet/contracts/helpers/erc777.vy: -------------------------------------------------------------------------------- 1 | # Author: Sören Steiger, github.com/ssteiger 2 | # License: MIT 3 | 4 | # ERC777 Token Standard 5 | # https://eips.ethereum.org/EIPS/eip-777 6 | 7 | 8 | # Interface for ERC1820 registry contract 9 | # https://eips.ethereum.org/EIPS/eip-1820 10 | contract ERC1820Registry: 11 | def setInterfaceImplementer( 12 | _addr: address, 13 | _interfaceHash: bytes32, 14 | _implementer: address, 15 | ): modifying 16 | def getInterfaceImplementer( 17 | _addr: address, 18 | _interfaceHash: bytes32, 19 | ) -> address: modifying 20 | 21 | # Interface for ERC777Tokens sender contracts 22 | contract ERC777TokensSender: 23 | def tokensToSend( 24 | _operator: address, 25 | _from: address, 26 | _to: address, 27 | _amount: uint256, 28 | _data: bytes[256], 29 | _operatorData: bytes[256] 30 | ): modifying 31 | 32 | # Interface for ERC777Tokens recipient contracts 33 | contract ERC777TokensRecipient: 34 | def tokensReceived( 35 | _operator: address, 36 | _from: address, 37 | _to: address, 38 | _amount: uint256, 39 | _data: bytes[256], 40 | _operatorData: bytes[256] 41 | ): modifying 42 | 43 | 44 | Sent: event({ 45 | _operator: indexed(address), # Address which triggered the send. 46 | _from: indexed(address), # Token holder. 47 | _to: indexed(address), # Token recipient. 48 | _amount: uint256, # Number of tokens to send. 49 | _data: bytes[256], # Information provided by the token holder. 50 | _operatorData: bytes[256] # Information provided by the operator. 51 | }) 52 | 53 | Minted: event({ 54 | _operator: indexed(address), # Address which triggered the mint. 55 | _to: indexed(address), # Recipient of the tokens. 56 | _amount: uint256, # Number of tokens minted. 57 | _data: bytes[256], # Information provided for the recipient. 58 | _operatorData: bytes[256] # Information provided by the operator. 59 | }) 60 | 61 | Burned: event({ 62 | _operator: indexed(address), # Address which triggered the burn. 63 | _from: indexed(address), # Token holder whose tokens are burned. 64 | _amount: uint256, # Token holder whose tokens are burned. 65 | _data: bytes[256], # Information provided by the token holder. 66 | _operatorData: bytes[256] # Information provided by the operator. 67 | }) 68 | 69 | AuthorizedOperator: event({ 70 | _operator: indexed(address), # Address which became an operator of tokenHolder. 71 | _holder: indexed(address) # Address of a token holder which authorized the operator address as an operator. 72 | }) 73 | 74 | RevokedOperator: event({ 75 | _operator: indexed(address), # Address which was revoked as an operator of tokenHolder. 76 | _holder: indexed(address) # Address of a token holder which revoked the operator address as an operator. 77 | }) 78 | 79 | 80 | erc1820Registry: ERC1820Registry 81 | erc1820RegistryAddress: constant(address) = 0x1820a4B7618BdE71Dce8cdc73aAB6C95905faD24 82 | 83 | name: public(string[64]) 84 | symbol: public(string[32]) 85 | totalSupply: public(uint256) 86 | granularity: public(uint256) 87 | 88 | balanceOf: public(map(address, uint256)) 89 | 90 | defaultOperatorsList: address[4] 91 | defaultOperatorsMap: map(address, bool) 92 | 93 | operators: map(address, map(address, bool)) 94 | 95 | 96 | @public 97 | def __init__( 98 | _name: string[64], 99 | _symbol: string[32], 100 | _totalSupply: uint256, 101 | _granularity: uint256, 102 | _defaultOperators: address[4] 103 | ): 104 | self.name = _name 105 | self.symbol = _symbol 106 | self.totalSupply = _totalSupply 107 | # The granularity value MUST be greater than or equal to 1 108 | assert _granularity >= 1 109 | self.granularity = _granularity 110 | self.defaultOperatorsList = _defaultOperators 111 | for i in range(4): 112 | assert _defaultOperators[i] != ZERO_ADDRESS 113 | self.defaultOperatorsMap[_defaultOperators[i]] = True 114 | self.erc1820Registry = ERC1820Registry(erc1820RegistryAddress) 115 | self.erc1820Registry.setInterfaceImplementer(self, keccak256("ERC777Token"), self) 116 | 117 | 118 | @private 119 | def _checkForERC777TokensInterface_Sender( 120 | _operator: address, 121 | _from: address, 122 | _to: address, 123 | _amount: uint256, 124 | _data: bytes[256]="", 125 | _operatorData: bytes[256]="" 126 | ): 127 | implementer: address = self.erc1820Registry.getInterfaceImplementer(_from, keccak256("ERC777TokensSender")) 128 | if implementer != ZERO_ADDRESS: 129 | ERC777TokensSender(_from).tokensToSend(_operator, _from, _to, _amount, _data, _operatorData) 130 | 131 | 132 | @private 133 | def _checkForERC777TokensInterface_Recipient( 134 | _operator: address, 135 | _from: address, 136 | _to: address, 137 | _amount: uint256, 138 | _data: bytes[256]="", 139 | _operatorData: bytes[256]="" 140 | ): 141 | implementer: address = self.erc1820Registry.getInterfaceImplementer(_to, keccak256("ERC777TokensRecipient")) 142 | if implementer != ZERO_ADDRESS: 143 | ERC777TokensRecipient(_to).tokensReceived(_operator, _from, _to, _amount, _data, _operatorData) 144 | 145 | 146 | @private 147 | def _transferFunds( 148 | _operator: address, 149 | _from: address, 150 | _to: address, 151 | _amount: uint256, 152 | _data: bytes[256]="", 153 | _operatorData: bytes[256]="" 154 | ): 155 | # any minting, sending or burning of tokens MUST be a multiple of the granularity value. 156 | assert _amount % self.granularity == 0 157 | 158 | # check for 'tokensToSend' hook 159 | if _from.is_contract: 160 | self._checkForERC777TokensInterface_Sender(_operator, _from, _to, _amount, _data, _operatorData) 161 | 162 | self.balanceOf[_from] -= _amount 163 | self.balanceOf[_to] += _amount 164 | 165 | # check for 'tokensReceived' hook 166 | # but only if transfer is not a burn 167 | if _to != ZERO_ADDRESS: 168 | if _to.is_contract: 169 | self._checkForERC777TokensInterface_Recipient(_operator, _from, _to, _amount, _data, _operatorData) 170 | 171 | 172 | @public 173 | @constant 174 | def defaultOperators() -> address[4]: 175 | return self.defaultOperatorsList 176 | 177 | 178 | @public 179 | @constant 180 | def isOperatorFor(_operator: address, _holder: address) -> bool: 181 | return (self.operators[_holder])[_operator] or self.defaultOperatorsMap[_operator] or _operator == _holder 182 | 183 | 184 | @public 185 | def authorizeOperator(_operator: address): 186 | (self.operators[msg.sender])[_operator] = True 187 | log.AuthorizedOperator(_operator, msg.sender) 188 | 189 | 190 | @public 191 | def revokeOperator(_operator: address): 192 | # MUST revert if it is called to revoke the holder as an operator for itself 193 | assert _operator != msg.sender 194 | (self.operators[msg.sender])[_operator] = False 195 | log.RevokedOperator(_operator, msg.sender) 196 | 197 | 198 | @public 199 | def send(_to: address, _amount: uint256, _data: bytes[256]=""): 200 | assert _to != ZERO_ADDRESS 201 | operatorData: bytes[256]="" 202 | self._transferFunds(msg.sender, msg.sender, _to, _amount, _data, operatorData) 203 | log.Sent(msg.sender, msg.sender, _to, _amount, _data, operatorData) 204 | 205 | 206 | @public 207 | def operatorSend( 208 | _from: address, 209 | _to: address, 210 | _amount: uint256, 211 | _data: bytes[256]="", 212 | _operatorData: bytes[256]="" 213 | ): 214 | assert _to != ZERO_ADDRESS 215 | assert self.isOperatorFor(msg.sender, _from) 216 | self._transferFunds(msg.sender, _from, _to, _amount, _data, _operatorData) 217 | log.Sent(msg.sender, _from, _to, _amount, _data, _operatorData) 218 | 219 | 220 | @public 221 | def burn(_amount: uint256, _data: bytes[256]=""): 222 | operatorData: bytes[256]="" 223 | self._transferFunds(msg.sender, msg.sender, ZERO_ADDRESS, _amount, _data, operatorData) 224 | self.totalSupply -= _amount 225 | log.Burned(msg.sender, msg.sender, _amount, _data, operatorData) 226 | 227 | 228 | @public 229 | def operatorBurn( 230 | _from: address, 231 | _amount: uint256, 232 | _data: bytes[256]="", 233 | _operatorData: bytes[256]="" 234 | ): 235 | # _from: Token holder whose tokens will be burned (or 0x0 to set from to msg.sender). 236 | fromAddress: address 237 | if _from == ZERO_ADDRESS: 238 | fromAddress = msg.sender 239 | else: 240 | fromAddress = _from 241 | assert self.isOperatorFor(msg.sender, fromAddress) 242 | self._transferFunds(msg.sender, fromAddress, ZERO_ADDRESS, _amount, _data, _operatorData) 243 | self.totalSupply -= _amount 244 | log.Burned(msg.sender, fromAddress, _amount, _data, _operatorData) 245 | 246 | 247 | # NOTE: ERC777 intentionally does not define specific functions to mint tokens. 248 | @public 249 | def mint( 250 | _to: address, 251 | _amount: uint256, 252 | _operatorData: bytes[256]="" 253 | ): 254 | assert _to != ZERO_ADDRESS 255 | # any minting, sending or burning of tokens MUST be a multiple of the granularity value. 256 | assert _amount % self.granularity == 0 257 | # only operators are allowed to mint 258 | assert self.defaultOperatorsMap[msg.sender] 259 | self.balanceOf[_to] += _amount 260 | self.totalSupply += _amount 261 | data: bytes[256]="" 262 | if _to.is_contract: 263 | self._checkForERC777TokensInterface_Recipient(msg.sender, ZERO_ADDRESS, _to, _amount, data, _operatorData) 264 | log.Minted(msg.sender, _to, _amount, data, _operatorData) 265 | -------------------------------------------------------------------------------- /wallet/contracts/helpers/erc777TokenReceiver.vy: -------------------------------------------------------------------------------- 1 | # Author: Sören Steiger, github.com/ssteiger 2 | # License: MIT 3 | 4 | # ERC777 Token Receiver (https://eips.ethereum.org/EIPS/eip-777) 5 | 6 | # Interface for ERC1820 registry contract (http://eips.ethereum.org/EIPS/eip-1820) 7 | contract ERC1820Registry: 8 | def setInterfaceImplementer( 9 | _addr: address, 10 | _interfaceHash: bytes32, 11 | _implementer: address, 12 | ): modifying 13 | 14 | 15 | TokensReceived: event({ 16 | _operator: indexed(address), 17 | _from: indexed(address), 18 | _to: indexed(address), 19 | _amount: uint256, 20 | _data: bytes[256], 21 | _operatorData: bytes[256] 22 | }) 23 | 24 | 25 | erc1820Registry: ERC1820Registry 26 | erc1820RegistryAddress: constant(address) = 0x1820a4B7618BdE71Dce8cdc73aAB6C95905faD24 27 | 28 | 29 | @public 30 | def __init__(): 31 | self.erc1820Registry = ERC1820Registry(erc1820RegistryAddress) 32 | self.erc1820Registry.setInterfaceImplementer(self, keccak256("ERC777TokensRecipient"), self) 33 | 34 | 35 | @public 36 | def tokensReceived( 37 | _operator: address, 38 | _from: address, 39 | _to: address, 40 | _amount: uint256, 41 | _data: bytes[256], 42 | _operatorData: bytes[256] 43 | ): 44 | log.TokensReceived(_operator, _from, _to, _amount, _data, _operatorData) 45 | -------------------------------------------------------------------------------- /wallet/contracts/wallet.vy: -------------------------------------------------------------------------------- 1 | # Author: Sören Steiger, github.com/ssteiger 2 | # License: MIT 3 | 4 | contract ERC1820Registry: 5 | def setInterfaceImplementer( 6 | _addr: address, 7 | _interfaceHash: bytes32, 8 | _implementer: address 9 | ): modifying 10 | 11 | contract ERC20Token: 12 | def transfer( 13 | _to: address, 14 | _value: uint256 15 | ) -> bool: modifying 16 | 17 | contract ERC721Token: 18 | def safeTransferFrom( 19 | _from: address, 20 | _to: address, 21 | _tokenId: uint256, 22 | _data: bytes[256] 23 | ): modifying 24 | 25 | contract ERC777Token: 26 | def send( 27 | _to: address, 28 | _amount: uint256, 29 | _data: bytes[256] 30 | ): modifying 31 | 32 | 33 | ETHReceived: event({ 34 | _from: address, 35 | _amount: wei_value 36 | }) 37 | 38 | ETHSent: event({ 39 | _to: address, 40 | _amount: uint256 41 | }) 42 | 43 | # TODO: 44 | #ERC20Received: event({ 45 | #}) 46 | 47 | ERC20Sent: event({ 48 | _token: address, 49 | _to: address, 50 | _amount: uint256 51 | }) 52 | 53 | ERC721Received: event({ 54 | _token: address, 55 | _from: address, 56 | _tokenId: uint256, 57 | _data: bytes32 58 | }) 59 | 60 | ERC721Sent: event({ 61 | _token: address, 62 | _from: address, 63 | _to: address, 64 | _tokenId: uint256, 65 | _data: bytes[256] 66 | }) 67 | 68 | ERC777Received: event({ 69 | _operator: indexed(address), 70 | _from: indexed(address), 71 | _to: indexed(address), 72 | _amount: uint256, 73 | _data: bytes[256], 74 | _operatorData: bytes[256] 75 | }) 76 | 77 | ERC777Sent: event({ 78 | _operator: indexed(address), 79 | _from: indexed(address), 80 | _to: indexed(address), 81 | _amount: uint256, 82 | _data: bytes[256], 83 | _operatorData: bytes[256] 84 | }) 85 | 86 | 87 | erc1820Registry: ERC1820Registry 88 | erc1820RegistryAddress: constant(address) = 0x1820a4B7618BdE71Dce8cdc73aAB6C95905faD24 89 | 90 | owner: public(address) 91 | 92 | 93 | @public 94 | def __init__(): 95 | self.owner = msg.sender 96 | self.erc1820Registry = ERC1820Registry(erc1820RegistryAddress) 97 | self.erc1820Registry.setInterfaceImplementer(self, keccak256("ERC777TokensRecipient"), self) 98 | self.erc1820Registry.setInterfaceImplementer(self, keccak256("ERC777TokensSender"), self) 99 | 100 | 101 | # ----- ETH ----- 102 | @public 103 | @payable 104 | def __default__(): 105 | log.ETHReceived(msg.sender, msg.value) 106 | 107 | @public 108 | def sendETH( 109 | _to: address, 110 | _amount: uint256 111 | ): 112 | assert msg.sender == self.owner 113 | send(_to, _amount) 114 | log.ETHSent(_to, _amount) 115 | 116 | 117 | # ----- ERC20 ----- 118 | @public 119 | def sendERC20( 120 | _token: address, 121 | _to: address, 122 | _amount: uint256 123 | ): 124 | assert msg.sender == self.owner 125 | ERC20Token(_token).transfer(_to, _amount) 126 | log.ERC20Sent(_token, _to, _amount) 127 | 128 | #@public 129 | #def onERC20Received(): 130 | 131 | 132 | # ----- ERC721 ----- 133 | @public 134 | def sendERC721( 135 | _token: address, 136 | _to: address, 137 | _tokenId: uint256, 138 | _data: bytes[256]="" 139 | ): 140 | assert msg.sender == self.owner 141 | ERC721Token(_token).safeTransferFrom(self, _to, _tokenId, _data) 142 | log.ERC721Sent(_token, self, _to, _tokenId, _data) 143 | 144 | @public 145 | def onERC721Received( 146 | _token: address, 147 | _from: address, 148 | _tokenId: uint256, 149 | _data: bytes32 150 | ) -> bytes32: 151 | log.ERC721Received(_token, _from, _tokenId, _data) 152 | # TODO: need to return bytes4 153 | return keccak256("onERC721Received(address,address,uint256,bytes)") 154 | 155 | 156 | # ----- ERC777 ----- 157 | @public 158 | def sendERC777( 159 | _token: address, 160 | _to: address, 161 | _amount: uint256, 162 | _data: bytes[256]="" 163 | ): 164 | assert msg.sender == self.owner 165 | ERC777Token(_token).send(_to, _amount, _data) 166 | 167 | @public 168 | def tokensToSend( 169 | _operator: address, 170 | _from: address, 171 | _to: address, 172 | _amount: uint256, 173 | _data: bytes[256], 174 | _operatorData: bytes[256] 175 | ): 176 | log.ERC777Sent(_operator, _from, _to, _amount, _data, _operatorData) 177 | 178 | @public 179 | def tokensReceived( 180 | _operator: address, 181 | _from: address, 182 | _to: address, 183 | _amount: uint256, 184 | _data: bytes[256], 185 | _operatorData: bytes[256] 186 | ): 187 | log.ERC777Received(_operator, _from, _to, _amount, _data, _operatorData) 188 | -------------------------------------------------------------------------------- /wallet/migrations/1_initial_migration.js: -------------------------------------------------------------------------------- 1 | const checkForERC182Registry = require('./helpers/checkForERC182Registry.js'); 2 | 3 | const Migrations = artifacts.require('./Migrations.sol'); 4 | 5 | module.exports = async function(deployer) { 6 | await checkForERC182Registry(web3, deployer); 7 | 8 | deployer.deploy(Migrations); 9 | }; 10 | -------------------------------------------------------------------------------- /wallet/migrations/2_deploy_contracts.js: -------------------------------------------------------------------------------- 1 | const erc20 = artifacts.require('erc20'); 2 | const erc721 = artifacts.require('erc721'); 3 | const erc777 = artifacts.require('erc777'); 4 | const wallet = artifacts.require('wallet'); 5 | 6 | const truffleFromAddress = '0x954e72fdc51Cf919203067406fB337Ed4bDC8CdA'; 7 | 8 | const args = { 9 | erc20: { 10 | name: 'My20Token', 11 | symbol: 'MT20', 12 | decimals: 18, 13 | totalSupply: 100000000, 14 | }, 15 | erc777: { 16 | name: 'My777Token', 17 | symbol: 'MT777', 18 | totalSupply: 100000000, 19 | granularity: 1, 20 | defaultOperators: [ 21 | truffleFromAddress, 22 | '0x0000000000000000000000000000000000000001', 23 | '0x0000000000000000000000000000000000000002', 24 | '0x0000000000000000000000000000000000000003', 25 | ], 26 | } 27 | } 28 | 29 | module.exports = function(deployer) { 30 | if (deployer.network == 'ganache') { 31 | // deploy contracts necessary for testing 32 | deployer.deploy( 33 | erc20, 34 | args.erc20.name, 35 | args.erc20.symbol, 36 | args.erc20.decimals, 37 | args.erc20.totalSupply, 38 | ); 39 | 40 | deployer.deploy(erc721); 41 | 42 | deployer.deploy( 43 | erc777, 44 | args.erc777.name, 45 | args.erc777.symbol, 46 | args.erc777.totalSupply, 47 | args.erc777.granularity, 48 | args.erc777.defaultOperators, 49 | ); 50 | } // if deployer.network == 'ganache' 51 | 52 | // deploy wallet contract 53 | deployer.deploy(wallet); 54 | }; 55 | -------------------------------------------------------------------------------- /wallet/migrations/helpers/checkForERC182Registry.js: -------------------------------------------------------------------------------- 1 | // NOTE: this address has to match the 'from' address used in 'truffle.js' 2 | const truffleFromAddress = '0x954e72fdc51Cf919203067406fB337Ed4bDC8CdA'; 3 | 4 | module.exports = async function(web3, deployer) { 5 | // check if erc1820 registry contract exists on network 6 | // http://eips.ethereum.org/EIPS/eip-1820 7 | 8 | console.log('checking if erc1820Registry contract exists on network'); 9 | 10 | // check for code at the erc1820Registry address 11 | const code = await web3.eth.getCode('0x1820a4B7618BdE71Dce8cdc73aAB6C95905faD24'); 12 | 13 | // NOTE: If no contract exists at address, '0x' is returned 14 | if (code == '0x') { 15 | // erc1820Registry does not exist on network 16 | // -> deploy it 17 | console.log('erc1820Registry contract not found -> deloying it now'); 18 | 19 | // fund address from which the erc1820Registry contract will be deployed 20 | const fundSize = web3.utils.toWei('0.08', 'ether'); 21 | await web3.eth.sendTransaction({ 22 | from: truffleFromAddress, 23 | to: '0xa990077c3205cbDf861e17Fa532eeB069cE9fF96', 24 | value: fundSize 25 | }); 26 | 27 | // the raw transaction that deployes the erc1820Registry 28 | const rawTx = '0xf90a388085174876e800830c35008080b909e5608060405234801561001057600080fd5b506109c5806100206000396000f3fe608060405234801561001057600080fd5b50600436106100a5576000357c010000000000000000000000000000000000000000000000000000000090048063a41e7d5111610078578063a41e7d51146101d4578063aabbb8ca1461020a578063b705676514610236578063f712f3e814610280576100a5565b806329965a1d146100aa5780633d584063146100e25780635df8122f1461012457806365ba36c114610152575b600080fd5b6100e0600480360360608110156100c057600080fd5b50600160a060020a038135811691602081013591604090910135166102b6565b005b610108600480360360208110156100f857600080fd5b5035600160a060020a0316610570565b60408051600160a060020a039092168252519081900360200190f35b6100e06004803603604081101561013a57600080fd5b50600160a060020a03813581169160200135166105bc565b6101c26004803603602081101561016857600080fd5b81019060208101813564010000000081111561018357600080fd5b82018360208201111561019557600080fd5b803590602001918460018302840111640100000000831117156101b757600080fd5b5090925090506106b3565b60408051918252519081900360200190f35b6100e0600480360360408110156101ea57600080fd5b508035600160a060020a03169060200135600160e060020a0319166106ee565b6101086004803603604081101561022057600080fd5b50600160a060020a038135169060200135610778565b61026c6004803603604081101561024c57600080fd5b508035600160a060020a03169060200135600160e060020a0319166107ef565b604080519115158252519081900360200190f35b61026c6004803603604081101561029657600080fd5b508035600160a060020a03169060200135600160e060020a0319166108aa565b6000600160a060020a038416156102cd57836102cf565b335b9050336102db82610570565b600160a060020a031614610339576040805160e560020a62461bcd02815260206004820152600f60248201527f4e6f7420746865206d616e616765720000000000000000000000000000000000604482015290519081900360640190fd5b6103428361092a565b15610397576040805160e560020a62461bcd02815260206004820152601a60248201527f4d757374206e6f7420626520616e204552433136352068617368000000000000604482015290519081900360640190fd5b600160a060020a038216158015906103b85750600160a060020a0382163314155b156104ff5760405160200180807f455243313832305f4143434550545f4d4147494300000000000000000000000081525060140190506040516020818303038152906040528051906020012082600160a060020a031663249cb3fa85846040518363ffffffff167c01000000000000000000000000000000000000000000000000000000000281526004018083815260200182600160a060020a0316600160a060020a031681526020019250505060206040518083038186803b15801561047e57600080fd5b505afa158015610492573d6000803e3d6000fd5b505050506040513d60208110156104a857600080fd5b5051146104ff576040805160e560020a62461bcd02815260206004820181905260248201527f446f6573206e6f7420696d706c656d656e742074686520696e74657266616365604482015290519081900360640190fd5b600160a060020a03818116600081815260208181526040808320888452909152808220805473ffffffffffffffffffffffffffffffffffffffff19169487169485179055518692917f93baa6efbd2244243bfee6ce4cfdd1d04fc4c0e9a786abd3a41313bd352db15391a450505050565b600160a060020a03818116600090815260016020526040812054909116151561059a5750806105b7565b50600160a060020a03808216600090815260016020526040902054165b919050565b336105c683610570565b600160a060020a031614610624576040805160e560020a62461bcd02815260206004820152600f60248201527f4e6f7420746865206d616e616765720000000000000000000000000000000000604482015290519081900360640190fd5b81600160a060020a031681600160a060020a0316146106435780610646565b60005b600160a060020a03838116600081815260016020526040808220805473ffffffffffffffffffffffffffffffffffffffff19169585169590951790945592519184169290917f605c2dbf762e5f7d60a546d42e7205dcb1b011ebc62a61736a57c9089d3a43509190a35050565b600082826040516020018083838082843780830192505050925050506040516020818303038152906040528051906020012090505b92915050565b6106f882826107ef565b610703576000610705565b815b600160a060020a03928316600081815260208181526040808320600160e060020a031996909616808452958252808320805473ffffffffffffffffffffffffffffffffffffffff19169590971694909417909555908152600284528181209281529190925220805460ff19166001179055565b600080600160a060020a038416156107905783610792565b335b905061079d8361092a565b156107c357826107ad82826108aa565b6107b85760006107ba565b815b925050506106e8565b600160a060020a0390811660009081526020818152604080832086845290915290205416905092915050565b6000808061081d857f01ffc9a70000000000000000000000000000000000000000000000000000000061094c565b909250905081158061082d575080155b1561083d576000925050506106e8565b61084f85600160e060020a031961094c565b909250905081158061086057508015155b15610870576000925050506106e8565b61087a858561094c565b909250905060018214801561088f5750806001145b1561089f576001925050506106e8565b506000949350505050565b600160a060020a0382166000908152600260209081526040808320600160e060020a03198516845290915281205460ff1615156108f2576108eb83836107ef565b90506106e8565b50600160a060020a03808316600081815260208181526040808320600160e060020a0319871684529091529020549091161492915050565b7bffffffffffffffffffffffffffffffffffffffffffffffffffffffff161590565b6040517f01ffc9a7000000000000000000000000000000000000000000000000000000008082526004820183905260009182919060208160248189617530fa90519096909550935050505056fea165627a7a72305820377f4a2d4301ede9949f163f319021a6e9c687c292a5e2b2c4734c126b524e6c00291ba01820182018201820182018201820182018201820182018201820182018201820a01820182018201820182018201820182018201820182018201820182018201820'; 29 | 30 | // deploy erc1820 registry 31 | const erc1820Registry = await web3.eth.sendSignedTransaction(rawTx); 32 | 33 | } else { 34 | console.log('erc1820Registry contract found'); 35 | } 36 | } 37 | -------------------------------------------------------------------------------- /wallet/test/wallet.js: -------------------------------------------------------------------------------- 1 | const erc20 = artifacts.require('erc20'); 2 | const erc721 = artifacts.require('erc721'); 3 | const erc777 = artifacts.require('erc777'); 4 | 5 | const walletAbstraction = artifacts.require('wallet'); 6 | 7 | let wallet, walletOwner; 8 | 9 | contract('wallet', accounts => { 10 | beforeEach(async () => { 11 | wallet = await walletAbstraction.new({ from: accounts[0] }); 12 | walletOwner = accounts[0]; 13 | }); 14 | 15 | it('...should accept eth deposit.', async () => { 16 | const sender = accounts[0]; 17 | 18 | const wallet_starting_balance = await web3.eth.getBalance(wallet.address); 19 | 20 | const amount = web3.utils.toWei('1', 'ether'); 21 | 22 | await web3.eth.sendTransaction({ 23 | from: sender, 24 | to: wallet.address, 25 | value: amount 26 | }); 27 | 28 | const wallet_ending_balance = await web3.eth.getBalance(wallet.address); 29 | 30 | assert.equal( 31 | wallet_starting_balance, 32 | wallet_ending_balance - amount, 33 | 'ETH was not correctly deposited.' 34 | ); 35 | }); 36 | 37 | it('...should send eth.', async () => { 38 | const sender = accounts[0]; 39 | const receiver = accounts[1]; 40 | 41 | const amount = web3.utils.toWei('1', 'ether'); 42 | 43 | await web3.eth.sendTransaction({ 44 | from: sender, 45 | to: wallet.address, 46 | value: amount 47 | }); 48 | 49 | const wallet_starting_balance = await web3.eth.getBalance(wallet.address); 50 | const receiver_starting_balance = await web3.eth.getBalance(receiver); 51 | 52 | await wallet.sendETH(receiver, amount, { from: walletOwner, gas: 40000 }); 53 | 54 | const wallet_ending_balance = await web3.eth.getBalance(wallet.address); 55 | const receiver_ending_balance = await web3.eth.getBalance(receiver); 56 | 57 | assert.equal( 58 | wallet_starting_balance - amount, 59 | wallet_ending_balance, 60 | 'ETH was not correctly sent.' 61 | ); 62 | assert.equal( 63 | receiver_starting_balance, 64 | receiver_ending_balance - amount, 65 | 'ETH was not correctly sent.' 66 | ); 67 | }); 68 | 69 | it('...should accept erc20 tokens deposit.', async () => { 70 | const sender = accounts[0]; 71 | 72 | const erc20Token = await erc20.deployed(); 73 | 74 | const wallet_starting_balance = await erc20Token.balanceOf.call(wallet.address); 75 | 76 | const amount = 100; 77 | 78 | await erc20Token.transfer(wallet.address, amount, { from: sender }); 79 | 80 | const wallet_ending_balance = await erc20Token.balanceOf.call(wallet.address); 81 | 82 | assert.equal( 83 | wallet_starting_balance, 84 | wallet_ending_balance - amount, 85 | 'ERC20 tokens were not correctly deposited.' 86 | ); 87 | }); 88 | 89 | it('...should send erc20 tokens.', async () => { 90 | const receiver = accounts[1]; 91 | 92 | const amount = 100; 93 | 94 | const erc20Token = await erc20.deployed(); 95 | await erc20Token.transfer(wallet.address, amount, { from: accounts[0] }); 96 | 97 | let receiver_starting_balance = await erc20Token.balanceOf.call(receiver); 98 | 99 | await wallet.sendERC20(erc20Token.address, receiver, amount, { from: walletOwner, gas: 4000000 }); 100 | 101 | balance = await erc20Token.balanceOf.call(receiver); 102 | const receiver_ending_balance = balance.toString(); 103 | 104 | assert.equal( 105 | receiver_starting_balance, 106 | receiver_ending_balance - amount, 107 | 'ERC20 tokens were not correctly credited.' 108 | ); 109 | }); 110 | 111 | it('...should accept erc721 token deposit.', async () => { 112 | const sender = accounts[0]; 113 | 114 | const erc721Token = await erc721.deployed(); 115 | 116 | const mintReturnData = await erc721Token.mint(); 117 | const tokenId = mintReturnData.logs[0].args['1'].toString(); 118 | 119 | let balance = await erc721Token.balanceOf.call(wallet.address); 120 | const wallet_starting_balance = balance.toString(); 121 | 122 | await erc721Token.transferFrom(sender, wallet.address, tokenId, { from: sender }); 123 | 124 | balance = await erc721Token.balanceOf.call(wallet.address); 125 | const wallet_ending_balance = balance.toString(); 126 | 127 | const ownerOfNFT = await erc721Token.ownerOf.call(tokenId); 128 | 129 | assert.equal( 130 | wallet_starting_balance, 131 | wallet_ending_balance - 1, 132 | 'ERC721 token was not correctly deposited' 133 | ); 134 | assert.equal( 135 | wallet.address, 136 | ownerOfNFT, 137 | 'ERC721 token was not correctly deposited.' 138 | ); 139 | }); 140 | 141 | it('...should accept erc777 tokens deposit.', async () => { 142 | const amount = 80; 143 | 144 | const erc777Token = await erc777.deployed(); 145 | 146 | const wallet_starting_balance = await erc777Token.balanceOf.call(wallet.address); 147 | 148 | await erc777Token.mint(wallet.address, amount); 149 | 150 | const wallet_ending_balance = await erc777Token.balanceOf.call(wallet.address); 151 | 152 | assert.equal( 153 | wallet_starting_balance, 154 | wallet_ending_balance - amount, 155 | 'ERC777 token was not correctly deposited.' 156 | ); 157 | }); 158 | 159 | it('...should send erc777 tokens.', async () => { 160 | const receiver = accounts[1]; 161 | 162 | const amount = 120; 163 | 164 | const erc777Token = await erc777.deployed(); 165 | await erc777Token.mint(wallet.address, amount); 166 | 167 | const wallet_starting_balance = await erc777Token.balanceOf.call(wallet.address); 168 | 169 | await wallet.sendERC777(erc777Token.address, receiver, amount, { from: walletOwner, gas: 3000000 }); 170 | 171 | const wallet_ending_balance = await erc777Token.balanceOf.call(wallet.address); 172 | 173 | assert.equal( 174 | wallet_starting_balance - amount, 175 | wallet_ending_balance, 176 | 'ERC777 token was not correctly sent.' 177 | ); 178 | }); 179 | 180 | }); 181 | -------------------------------------------------------------------------------- /wallet/truffle.js: -------------------------------------------------------------------------------- 1 | /** 2 | * Use this file to configure your truffle project. It's seeded with some 3 | * common settings for different networks and features like migrations, 4 | * compilation and testing. Uncomment the ones you need or modify 5 | * them to suit your project as necessary. 6 | * 7 | * More information about configuration can be found at: 8 | * 9 | * truffleframework.com/docs/advanced/configuration 10 | * 11 | * To deploy via Infura you'll need a wallet provider (like truffle-hdwallet-provider) 12 | * to sign your transactions before they're sent to a remote public node. Infura API 13 | * keys are available for free at: infura.io/register 14 | * 15 | * You'll also need a mnemonic - the twelve word phrase the wallet uses to generate 16 | * public/private key pairs. If you're publishing your code to GitHub make sure you load this 17 | * phrase from a file you've .gitignored so it doesn't accidentally become public. 18 | * 19 | */ 20 | 21 | module.exports = { 22 | networks: { 23 | ganache: { 24 | host: '127.0.0.1', 25 | port: 7545, 26 | network_id: '*', // match any network id 27 | from: '0x954e72fdc51Cf919203067406fB337Ed4bDC8CdA', // account address from which to deploy 28 | gas: 4000000, 29 | } 30 | } 31 | } 32 | --------------------------------------------------------------------------------