├── .gitignore ├── LICENSE ├── README.md └── ethereum ├── README.md ├── contracts ├── ERC20AtomicSwapper.sol ├── ETHAtomicSwapper.sol ├── Migrations.sol └── bnb.sol ├── migrations ├── 1_initial_migration.js └── 2_deploy_contracts.js ├── package.json ├── spec ├── ERC20AtomicSwap.md └── ETHAtomicSwap.md ├── test ├── ERC20AtomicSwapTest.js └── ETHAtomicSwapTest.js └── truffle.js /.gitignore: -------------------------------------------------------------------------------- 1 | .idea/ 2 | ethereum/node_modules 3 | ethereum/package-lock.json 4 | ethereum/build/ -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | Apache License 2 | Version 2.0, January 2004 3 | http://www.apache.org/licenses/ 4 | 5 | TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION 6 | 7 | 1. Definitions. 8 | 9 | "License" shall mean the terms and conditions for use, reproduction, 10 | and distribution as defined by Sections 1 through 9 of this document. 11 | 12 | "Licensor" shall mean the copyright owner or entity authorized by 13 | the copyright owner that is granting the License. 14 | 15 | "Legal Entity" shall mean the union of the acting entity and all 16 | other entities that control, are controlled by, or are under common 17 | control with that entity. For the purposes of this definition, 18 | "control" means (i) the power, direct or indirect, to cause the 19 | direction or management of such entity, whether by contract or 20 | otherwise, or (ii) ownership of fifty percent (50%) or more of the 21 | outstanding shares, or (iii) beneficial ownership of such entity. 22 | 23 | "You" (or "Your") shall mean an individual or Legal Entity 24 | exercising permissions granted by this License. 25 | 26 | "Source" form shall mean the preferred form for making modifications, 27 | including but not limited to software source code, documentation 28 | source, and configuration files. 29 | 30 | "Object" form shall mean any form resulting from mechanical 31 | transformation or translation of a Source form, including but 32 | not limited to compiled object code, generated documentation, 33 | and conversions to other media types. 34 | 35 | "Work" shall mean the work of authorship, whether in Source or 36 | Object form, made available under the License, as indicated by a 37 | copyright notice that is included in or attached to the work 38 | (an example is provided in the Appendix below). 39 | 40 | "Derivative Works" shall mean any work, whether in Source or Object 41 | form, that is based on (or derived from) the Work and for which the 42 | editorial revisions, annotations, elaborations, or other modifications 43 | represent, as a whole, an original work of authorship. For the purposes 44 | of this License, Derivative Works shall not include works that remain 45 | separable from, or merely link (or bind by name) to the interfaces of, 46 | the Work and Derivative Works thereof. 47 | 48 | "Contribution" shall mean any work of authorship, including 49 | the original version of the Work and any modifications or additions 50 | to that Work or Derivative Works thereof, that is intentionally 51 | submitted to Licensor for inclusion in the Work by the copyright owner 52 | or by an individual or Legal Entity authorized to submit on behalf of 53 | the copyright owner. For the purposes of this definition, "submitted" 54 | means any form of electronic, verbal, or written communication sent 55 | to the Licensor or its representatives, including but not limited to 56 | communication on electronic mailing lists, source code control systems, 57 | and issue tracking systems that are managed by, or on behalf of, the 58 | Licensor for the purpose of discussing and improving the Work, but 59 | excluding communication that is conspicuously marked or otherwise 60 | designated in writing by the copyright owner as "Not a Contribution." 61 | 62 | "Contributor" shall mean Licensor and any individual or Legal Entity 63 | on behalf of whom a Contribution has been received by Licensor and 64 | subsequently incorporated within the Work. 65 | 66 | 2. Grant of Copyright License. Subject to the terms and conditions of 67 | this License, each Contributor hereby grants to You a perpetual, 68 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable 69 | copyright license to reproduce, prepare Derivative Works of, 70 | publicly display, publicly perform, sublicense, and distribute the 71 | Work and such Derivative Works in Source or Object form. 72 | 73 | 3. Grant of Patent License. Subject to the terms and conditions of 74 | this License, each Contributor hereby grants to You a perpetual, 75 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable 76 | (except as stated in this section) patent license to make, have made, 77 | use, offer to sell, sell, import, and otherwise transfer the Work, 78 | where such license applies only to those patent claims licensable 79 | by such Contributor that are necessarily infringed by their 80 | Contribution(s) alone or by combination of their Contribution(s) 81 | with the Work to which such Contribution(s) was submitted. If You 82 | institute patent litigation against any entity (including a 83 | cross-claim or counterclaim in a lawsuit) alleging that the Work 84 | or a Contribution incorporated within the Work constitutes direct 85 | or contributory patent infringement, then any patent licenses 86 | granted to You under this License for that Work shall terminate 87 | as of the date such litigation is filed. 88 | 89 | 4. Redistribution. You may reproduce and distribute copies of the 90 | Work or Derivative Works thereof in any medium, with or without 91 | modifications, and in Source or Object form, provided that You 92 | meet the following conditions: 93 | 94 | (a) You must give any other recipients of the Work or 95 | Derivative Works a copy of this License; and 96 | 97 | (b) You must cause any modified files to carry prominent notices 98 | stating that You changed the files; and 99 | 100 | (c) You must retain, in the Source form of any Derivative Works 101 | that You distribute, all copyright, patent, trademark, and 102 | attribution notices from the Source form of the Work, 103 | excluding those notices that do not pertain to any part of 104 | the Derivative Works; and 105 | 106 | (d) If the Work includes a "NOTICE" text file as part of its 107 | distribution, then any Derivative Works that You distribute must 108 | include a readable copy of the attribution notices contained 109 | within such NOTICE file, excluding those notices that do not 110 | pertain to any part of the Derivative Works, in at least one 111 | of the following places: within a NOTICE text file distributed 112 | as part of the Derivative Works; within the Source form or 113 | documentation, if provided along with the Derivative Works; or, 114 | within a display generated by the Derivative Works, if and 115 | wherever such third-party notices normally appear. The contents 116 | of the NOTICE file are for informational purposes only and 117 | do not modify the License. You may add Your own attribution 118 | notices within Derivative Works that You distribute, alongside 119 | or as an addendum to the NOTICE text from the Work, provided 120 | that such additional attribution notices cannot be construed 121 | as modifying the License. 122 | 123 | You may add Your own copyright statement to Your modifications and 124 | may provide additional or different license terms and conditions 125 | for use, reproduction, or distribution of Your modifications, or 126 | for any such Derivative Works as a whole, provided Your use, 127 | reproduction, and distribution of the Work otherwise complies with 128 | the conditions stated in this License. 129 | 130 | 5. Submission of Contributions. Unless You explicitly state otherwise, 131 | any Contribution intentionally submitted for inclusion in the Work 132 | by You to the Licensor shall be under the terms and conditions of 133 | this License, without any additional terms or conditions. 134 | Notwithstanding the above, nothing herein shall supersede or modify 135 | the terms of any separate license agreement you may have executed 136 | with Licensor regarding such Contributions. 137 | 138 | 6. Trademarks. This License does not grant permission to use the trade 139 | names, trademarks, service marks, or product names of the Licensor, 140 | except as required for reasonable and customary use in describing the 141 | origin of the Work and reproducing the content of the NOTICE file. 142 | 143 | 7. Disclaimer of Warranty. Unless required by applicable law or 144 | agreed to in writing, Licensor provides the Work (and each 145 | Contributor provides its Contributions) on an "AS IS" BASIS, 146 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or 147 | implied, including, without limitation, any warranties or conditions 148 | of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A 149 | PARTICULAR PURPOSE. You are solely responsible for determining the 150 | appropriateness of using or redistributing the Work and assume any 151 | risks associated with Your exercise of permissions under this License. 152 | 153 | 8. Limitation of Liability. In no event and under no legal theory, 154 | whether in tort (including negligence), contract, or otherwise, 155 | unless required by applicable law (such as deliberate and grossly 156 | negligent acts) or agreed to in writing, shall any Contributor be 157 | liable to You for damages, including any direct, indirect, special, 158 | incidental, or consequential damages of any character arising as a 159 | result of this License or out of the use or inability to use the 160 | Work (including but not limited to damages for loss of goodwill, 161 | work stoppage, computer failure or malfunction, or any and all 162 | other commercial damages or losses), even if such Contributor 163 | has been advised of the possibility of such damages. 164 | 165 | 9. Accepting Warranty or Additional Liability. While redistributing 166 | the Work or Derivative Works thereof, You may choose to offer, 167 | and charge a fee for, acceptance of support, warranty, indemnity, 168 | or other liability obligations and/or rights consistent with this 169 | License. However, in accepting such obligations, You may act only 170 | on Your own behalf and on Your sole responsibility, not on behalf 171 | of any other Contributor, and only if You agree to indemnify, 172 | defend, and hold each Contributor harmless for any liability 173 | incurred by, or claims asserted against, such Contributor by reason 174 | of your accepting any such warranty or additional liability. 175 | 176 | END OF TERMS AND CONDITIONS 177 | 178 | APPENDIX: How to apply the Apache License to your work. 179 | 180 | To apply the Apache License to your work, attach the following 181 | boilerplate notice, with the fields enclosed by brackets "[]" 182 | replaced with your own identifying information. (Don't include 183 | the brackets!) The text should be enclosed in the appropriate 184 | comment syntax for the file format. We also recommend that a 185 | file or class name and description of purpose be included on the 186 | same "printed page" as the copyright notice for easier 187 | identification within third-party archives. 188 | 189 | Copyright [yyyy] [name of copyright owner] 190 | 191 | Licensed under the Apache License, Version 2.0 (the "License"); 192 | you may not use this file except in compliance with the License. 193 | You may obtain a copy of the License at 194 | 195 | http://www.apache.org/licenses/LICENSE-2.0 196 | 197 | Unless required by applicable law or agreed to in writing, software 198 | distributed under the License is distributed on an "AS IS" BASIS, 199 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 200 | See the License for the specific language governing permissions and 201 | limitations under the License. 202 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | **This repo is out of maintenance and decommissioned.** 2 | ## BEP3-smartcontracts 3 | 4 | This repository includes some atomic swap contracts which are described in [BEP3](https://github.com/binance-chain/BEPs/blob/master/BEP3.md). Currently only Ethereum smart contract is implemented. Later, more Blockchain platforms will be implemented. 5 | -------------------------------------------------------------------------------- /ethereum/README.md: -------------------------------------------------------------------------------- 1 | # Atomic Swap Contract 2 | 3 | ## Construct test environment 4 | 5 | 1. Install truffle and ganache-cli 6 | ``` 7 | npm install 8 | ``` 9 | 2. Start Ethereum test environment 10 | ``` 11 | npm run ganache 12 | ``` 13 | 14 | ## Run deploy tests and functionality tests. 15 | 1. Run deploy tests: 16 | ``` 17 | npm run migration 18 | ``` 19 | 2. Run functionality tests: 20 | ``` 21 | npm run test 22 | ``` 23 | 24 | ## Specification 25 | 26 | Please refer to [ERC20 swap](spec/ERC20AtomicSwap.md) and [ETH swap](spec/ETHAtomicSwap.md) 27 | -------------------------------------------------------------------------------- /ethereum/contracts/ERC20AtomicSwapper.sol: -------------------------------------------------------------------------------- 1 | pragma solidity 0.5.8; 2 | 3 | interface ERC20 { 4 | function totalSupply() external view returns (uint); 5 | function balanceOf(address who) external view returns (uint); 6 | function transfer(address to, uint value) external returns (bool); 7 | function allowance(address owner, address spender) external view returns (uint); 8 | function transferFrom(address from, address to, uint value) external returns (bool); 9 | function approve(address spender, uint value) external returns (bool); 10 | } 11 | 12 | contract ERC20AtomicSwapper { 13 | 14 | struct Swap { 15 | uint256 outAmount; 16 | uint256 expireHeight; 17 | bytes32 randomNumberHash; 18 | uint64 timestamp; 19 | address sender; 20 | address recipientAddr; 21 | } 22 | 23 | enum States { 24 | INVALID, 25 | OPEN, 26 | COMPLETED, 27 | EXPIRED 28 | } 29 | 30 | // Events 31 | event HTLT(address indexed _msgSender, address indexed _recipientAddr, bytes32 indexed _swapID, bytes32 _randomNumberHash, uint64 _timestamp, bytes20 _bep2Addr, uint256 _expireHeight, uint256 _outAmount, uint256 _bep2Amount); 32 | event Refunded(address indexed _msgSender, address indexed _recipientAddr, bytes32 indexed _swapID, bytes32 _randomNumberHash); 33 | event Claimed(address indexed _msgSender, address indexed _recipientAddr, bytes32 indexed _swapID, bytes32 _randomNumberHash, bytes32 _randomNumber); 34 | 35 | // Storage 36 | mapping (bytes32 => Swap) private swaps; 37 | mapping (bytes32 => States) private swapStates; 38 | 39 | address public ERC20ContractAddr; 40 | 41 | /// @notice Throws if the swap is not open. 42 | modifier onlyOpenSwaps(bytes32 _swapID) { 43 | require(swapStates[_swapID] == States.OPEN, "swap is not opened"); 44 | _; 45 | } 46 | 47 | /// @notice Throws if the swap is already expired. 48 | modifier onlyAfterExpireHeight(bytes32 _swapID) { 49 | require(block.number >= swaps[_swapID].expireHeight, "swap is not expired"); 50 | _; 51 | } 52 | 53 | /// @notice Throws if the expireHeight is reached 54 | modifier onlyBeforeExpireHeight(bytes32 _swapID) { 55 | require(block.number < swaps[_swapID].expireHeight, "swap is already expired"); 56 | _; 57 | } 58 | 59 | /// @notice Throws if the random number is not valid. 60 | modifier onlyWithRandomNumber(bytes32 _swapID, bytes32 _randomNumber) { 61 | require(swaps[_swapID].randomNumberHash == sha256(abi.encodePacked(_randomNumber, swaps[_swapID].timestamp)), "invalid randomNumber"); 62 | _; 63 | } 64 | 65 | /// @param _erc20Contract The ERC20 contract address 66 | constructor(address _erc20Contract) public { 67 | ERC20ContractAddr = _erc20Contract; 68 | } 69 | 70 | /// @notice htlt locks asset to contract address and create an atomic swap. 71 | /// 72 | /// @param _randomNumberHash The hash of the random number and timestamp 73 | /// @param _timestamp Counted by second 74 | /// @param _heightSpan The number of blocks to wait before the asset can be returned to sender 75 | /// @param _recipientAddr The ethereum address of the swap counterpart. 76 | /// @param _bep2SenderAddr the swap sender address on BNB Beacon Chain 77 | /// @param _bep2RecipientAddr The recipient address on BNB Beacon Chain 78 | /// @param _outAmount ERC20 asset to swap out. 79 | /// @param _bep2Amount BEP2 asset to swap in. 80 | function htlt( 81 | bytes32 _randomNumberHash, 82 | uint64 _timestamp, 83 | uint256 _heightSpan, 84 | address _recipientAddr, 85 | bytes20 _bep2SenderAddr, 86 | bytes20 _bep2RecipientAddr, 87 | uint256 _outAmount, 88 | uint256 _bep2Amount 89 | ) external returns (bool) { 90 | bytes32 swapID = calSwapID(_randomNumberHash, msg.sender, _bep2SenderAddr); 91 | require(swapStates[swapID] == States.INVALID, "swap is opened previously"); 92 | // Assume average block time interval is 10 second 93 | // The heightSpan period should be more than 10 minutes and less than one week 94 | require(_heightSpan >= 60 && _heightSpan <= 60480, "_heightSpan should be in [60, 60480]"); 95 | require(_recipientAddr != address(0), "_recipientAddr should not be zero"); 96 | require(_outAmount > 0, "_outAmount must be more than 0"); 97 | require(_timestamp > now - 1800 && _timestamp < now + 900, "Timestamp can neither be 15 minutes ahead of the current time, nor 30 minutes later"); 98 | // Store the details of the swap. 99 | Swap memory swap = Swap({ 100 | outAmount: _outAmount, 101 | expireHeight: _heightSpan + block.number, 102 | randomNumberHash: _randomNumberHash, 103 | timestamp: _timestamp, 104 | sender: msg.sender, 105 | recipientAddr: _recipientAddr 106 | }); 107 | 108 | swaps[swapID] = swap; 109 | swapStates[swapID] = States.OPEN; 110 | 111 | // Transfer ERC20 token to the swap contract 112 | require(ERC20(ERC20ContractAddr).transferFrom(msg.sender, address(this), _outAmount), "failed to transfer client asset to swap contract address"); 113 | 114 | // Emit initialization event 115 | emit HTLT(msg.sender, _recipientAddr, swapID, _randomNumberHash, _timestamp, _bep2RecipientAddr, swap.expireHeight, _outAmount, _bep2Amount); 116 | return true; 117 | } 118 | 119 | /// @notice claim claims the previously locked asset. 120 | /// 121 | /// @param _swapID The hash of randomNumberHash, swap creator and swap recipient 122 | /// @param _randomNumber The random number 123 | function claim(bytes32 _swapID, bytes32 _randomNumber) external onlyOpenSwaps(_swapID) onlyBeforeExpireHeight(_swapID) onlyWithRandomNumber(_swapID, _randomNumber) returns (bool) { 124 | // Complete the swap. 125 | swapStates[_swapID] = States.COMPLETED; 126 | 127 | address recipientAddr = swaps[_swapID].recipientAddr; 128 | uint256 outAmount = swaps[_swapID].outAmount; 129 | bytes32 randomNumberHash = swaps[_swapID].randomNumberHash; 130 | // delete closed swap 131 | delete swaps[_swapID]; 132 | 133 | // Pay erc20 token to recipient 134 | require(ERC20(ERC20ContractAddr).transfer(recipientAddr, outAmount), "Failed to transfer locked asset to recipient"); 135 | 136 | // Emit completion event 137 | emit Claimed(msg.sender, recipientAddr, _swapID, randomNumberHash, _randomNumber); 138 | 139 | return true; 140 | } 141 | 142 | /// @notice refund refunds the previously locked asset. 143 | /// 144 | /// @param _swapID The hash of randomNumberHash, swap creator and swap recipient 145 | function refund(bytes32 _swapID) external onlyOpenSwaps(_swapID) onlyAfterExpireHeight(_swapID) returns (bool) { 146 | // Expire the swap. 147 | swapStates[_swapID] = States.EXPIRED; 148 | 149 | address swapSender = swaps[_swapID].sender; 150 | uint256 outAmount = swaps[_swapID].outAmount; 151 | bytes32 randomNumberHash = swaps[_swapID].randomNumberHash; 152 | // delete closed swap 153 | delete swaps[_swapID]; 154 | 155 | // refund erc20 token to swap creator 156 | require(ERC20(ERC20ContractAddr).transfer(swapSender, outAmount), "Failed to transfer locked asset back to swap creator"); 157 | 158 | // Emit expire event 159 | emit Refunded(msg.sender, swapSender, _swapID, randomNumberHash); 160 | 161 | return true; 162 | } 163 | 164 | /// @notice query an atomic swap by randomNumberHash 165 | /// 166 | /// @param _swapID The hash of randomNumberHash, swap creator and swap recipient 167 | function queryOpenSwap(bytes32 _swapID) external view returns(bytes32 _randomNumberHash, uint64 _timestamp, uint256 _expireHeight, uint256 _outAmount, address _sender, address _recipient) { 168 | Swap memory swap = swaps[_swapID]; 169 | return ( 170 | swap.randomNumberHash, 171 | swap.timestamp, 172 | swap.expireHeight, 173 | swap.outAmount, 174 | swap.sender, 175 | swap.recipientAddr 176 | ); 177 | } 178 | 179 | /// @notice Checks whether a swap with specified swapID exist 180 | /// 181 | /// @param _swapID The hash of randomNumberHash, swap creator and swap recipient 182 | function isSwapExist(bytes32 _swapID) external view returns (bool) { 183 | return (swapStates[_swapID] != States.INVALID); 184 | } 185 | 186 | /// @notice Checks whether a swap is refundable or not. 187 | /// 188 | /// @param _swapID The hash of randomNumberHash, swap creator and swap recipient 189 | function refundable(bytes32 _swapID) external view returns (bool) { 190 | return (block.number >= swaps[_swapID].expireHeight && swapStates[_swapID] == States.OPEN); 191 | } 192 | 193 | /// @notice Checks whether a swap is claimable or not. 194 | /// 195 | /// @param _swapID The hash of randomNumberHash, swap creator and swap recipient 196 | function claimable(bytes32 _swapID) external view returns (bool) { 197 | return (block.number < swaps[_swapID].expireHeight && swapStates[_swapID] == States.OPEN); 198 | } 199 | 200 | /// @notice Calculate the swapID from randomNumberHash and swapCreator 201 | /// 202 | /// @param _randomNumberHash The hash of random number and timestamp. 203 | /// @param _swapSender The creator of swap. 204 | /// @param _bep2SenderAddr The sender of swap on BNB Beacon Chain. 205 | function calSwapID(bytes32 _randomNumberHash, address _swapSender, bytes20 _bep2SenderAddr) public pure returns (bytes32) { 206 | if (_bep2SenderAddr == bytes20(0)) { 207 | return sha256(abi.encodePacked(_randomNumberHash, _swapSender)); 208 | } 209 | return sha256(abi.encodePacked(_randomNumberHash, _swapSender, _bep2SenderAddr)); 210 | } 211 | } 212 | -------------------------------------------------------------------------------- /ethereum/contracts/ETHAtomicSwapper.sol: -------------------------------------------------------------------------------- 1 | pragma solidity 0.5.8; 2 | 3 | contract ETHAtomicSwapper { 4 | 5 | struct Swap { 6 | uint256 outAmount; 7 | uint256 expireHeight; 8 | bytes32 randomNumberHash; 9 | uint64 timestamp; 10 | address payable sender; 11 | address payable recipientAddr; 12 | } 13 | 14 | enum States { 15 | INVALID, 16 | OPEN, 17 | COMPLETED, 18 | EXPIRED 19 | } 20 | 21 | // Events 22 | event HTLT(address indexed _msgSender, address indexed _recipientAddr, bytes32 indexed _swapID, bytes32 _randomNumberHash, uint64 _timestamp, bytes20 _bep2Addr, uint256 _expireHeight, uint256 _outAmount, uint256 _bep2Amount); 23 | event Refunded(address indexed _msgSender, address indexed _recipientAddr, bytes32 indexed _swapID, bytes32 _randomNumberHash); 24 | event Claimed(address indexed _msgSender, address indexed _recipientAddr, bytes32 indexed _swapID, bytes32 _randomNumberHash, bytes32 _randomNumber); 25 | 26 | // Storage 27 | mapping (bytes32 => Swap) private swaps; 28 | mapping (bytes32 => States) private swapStates; 29 | 30 | /// @notice Throws if the swap is not invalid (i.e. has already been used) 31 | modifier onlyInvalidSwaps(bytes32 _randomNumberHash) { 32 | require(swapStates[_randomNumberHash] == States.INVALID, "swap is opened previously"); 33 | _; 34 | } 35 | 36 | /// @notice Throws if the swap is not open. 37 | modifier onlyOpenSwaps(bytes32 _swapID) { 38 | require(swapStates[_swapID] == States.OPEN, "swap is not opened"); 39 | _; 40 | } 41 | 42 | /// @notice Throws if the swap is already expired. 43 | modifier onlyAfterExpireHeight(bytes32 _swapID) { 44 | require(block.number >= swaps[_swapID].expireHeight, "swap is not expired"); 45 | _; 46 | } 47 | 48 | /// @notice Throws if the expireHeight is reached 49 | modifier onlyBeforeExpireHeight(bytes32 _swapID) { 50 | require(block.number < swaps[_swapID].expireHeight, "swap is already expired"); 51 | _; 52 | } 53 | 54 | /// @notice Throws if the random number is not valid. 55 | modifier onlyWithRandomNumber(bytes32 _swapID, bytes32 _randomNumber) { 56 | require(swaps[_swapID].randomNumberHash == sha256(abi.encodePacked(_randomNumber, swaps[_swapID].timestamp)), "invalid randomNumber"); 57 | _; 58 | } 59 | 60 | /// @notice htlt locks asset to contract address and create an atomic swap. 61 | /// 62 | /// @param _randomNumberHash The hash of the random number and timestamp 63 | /// @param _timestamp Counted by second 64 | /// @param _heightSpan The number of blocks to wait before the asset can be returned to sender 65 | /// @param _recipientAddr The ethereum address of the swap counterpart. 66 | /// @param _bep2SenderAddr the swap sender address on BNB Beacon Chain 67 | /// @param _bep2RecipientAddr The recipient address on BNB Beacon Chain 68 | /// @param _bep2Amount BEP2 asset to swap in. 69 | function htlt( 70 | bytes32 _randomNumberHash, 71 | uint64 _timestamp, 72 | uint256 _heightSpan, 73 | address payable _recipientAddr, 74 | bytes20 _bep2SenderAddr, 75 | bytes20 _bep2RecipientAddr, 76 | uint256 _bep2Amount 77 | ) external payable returns (bool) { 78 | bytes32 swapID = calSwapID(_randomNumberHash, msg.sender, _bep2SenderAddr); 79 | require(swapStates[swapID] == States.INVALID, "swap is opened previously"); 80 | // Assume average block time interval is 10 second 81 | // The heightSpan period should be more than 10 minutes and less than one week 82 | require(_heightSpan >= 60 && _heightSpan <= 60480, "_heightSpan should be in [60, 60480]"); 83 | require(_recipientAddr != address(0), "_recipientAddr should not be zero"); 84 | require(msg.value > 0, "msg.value must be more than 0"); 85 | require(_timestamp > now - 1800 && _timestamp < now + 900, "Timestamp can neither be 15 minutes ahead of the current time, nor 30 minutes later"); 86 | // Store the details of the swap. 87 | Swap memory swap = Swap({ 88 | outAmount: msg.value, 89 | expireHeight: _heightSpan + block.number, 90 | randomNumberHash: _randomNumberHash, 91 | timestamp: _timestamp, 92 | sender: msg.sender, 93 | recipientAddr: _recipientAddr 94 | }); 95 | 96 | swaps[swapID] = swap; 97 | swapStates[swapID] = States.OPEN; 98 | 99 | // Emit initialization event 100 | emit HTLT(msg.sender, _recipientAddr, swapID, _randomNumberHash, _timestamp, _bep2RecipientAddr, swap.expireHeight, msg.value, _bep2Amount); 101 | return true; 102 | } 103 | 104 | /// @notice claim claims the previously locked asset. 105 | /// 106 | /// @param _swapID The hash of randomNumberHash, swap creator and swap recipient 107 | /// @param _randomNumber The random number 108 | function claim(bytes32 _swapID, bytes32 _randomNumber) external onlyOpenSwaps(_swapID) onlyBeforeExpireHeight(_swapID) onlyWithRandomNumber(_swapID, _randomNumber) returns (bool) { 109 | // Complete the swap. 110 | swapStates[_swapID] = States.COMPLETED; 111 | 112 | address payable recipientAddr = swaps[_swapID].recipientAddr; 113 | uint256 outAmount = swaps[_swapID].outAmount; 114 | bytes32 randomNumberHash = swaps[_swapID].randomNumberHash; 115 | // delete closed swap 116 | delete swaps[_swapID]; 117 | 118 | // Pay eth coin to recipient 119 | recipientAddr.transfer(outAmount); 120 | 121 | // Emit completion event 122 | emit Claimed(msg.sender, recipientAddr, _swapID, randomNumberHash, _randomNumber); 123 | 124 | return true; 125 | } 126 | 127 | /// @notice refund refunds the previously locked asset. 128 | /// 129 | /// @param _swapID The hash of randomNumberHash, swap creator and swap recipient 130 | function refund(bytes32 _swapID) external onlyOpenSwaps(_swapID) onlyAfterExpireHeight(_swapID) returns (bool) { 131 | // Expire the swap. 132 | swapStates[_swapID] = States.EXPIRED; 133 | 134 | address payable swapSender = swaps[_swapID].sender; 135 | uint256 outAmount = swaps[_swapID].outAmount; 136 | bytes32 randomNumberHash = swaps[_swapID].randomNumberHash; 137 | // delete closed swap 138 | delete swaps[_swapID]; 139 | 140 | // refund eth coin to swap creator 141 | swapSender.transfer(outAmount); 142 | 143 | // Emit expire event 144 | emit Refunded(msg.sender, swapSender, _swapID, randomNumberHash); 145 | 146 | return true; 147 | } 148 | 149 | /// @notice query an atomic swap by randomNumberHash 150 | /// 151 | /// @param _swapID The hash of randomNumberHash, swap creator and swap recipient 152 | function queryOpenSwap(bytes32 _swapID) external view returns(bytes32 _randomNumberHash, uint64 _timestamp, uint256 _expireHeight, uint256 _outAmount, address _sender, address _recipient) { 153 | Swap memory swap = swaps[_swapID]; 154 | return ( 155 | swap.randomNumberHash, 156 | swap.timestamp, 157 | swap.expireHeight, 158 | swap.outAmount, 159 | swap.sender, 160 | swap.recipientAddr 161 | ); 162 | } 163 | 164 | /// @notice Checks whether a swap with specified swapID exist 165 | /// 166 | /// @param _swapID The hash of randomNumberHash, swap creator and swap recipient 167 | function isSwapExist(bytes32 _swapID) external view returns (bool) { 168 | return (swapStates[_swapID] != States.INVALID); 169 | } 170 | 171 | /// @notice Checks whether a swap is refundable or not. 172 | /// 173 | /// @param _swapID The hash of randomNumberHash, swap creator and swap recipient 174 | function refundable(bytes32 _swapID) external view returns (bool) { 175 | return (block.number >= swaps[_swapID].expireHeight && swapStates[_swapID] == States.OPEN); 176 | } 177 | 178 | /// @notice Checks whether a swap is claimable or not. 179 | /// 180 | /// @param _swapID The hash of randomNumberHash, swap creator and swap recipient 181 | function claimable(bytes32 _swapID) external view returns (bool) { 182 | return (block.number < swaps[_swapID].expireHeight && swapStates[_swapID] == States.OPEN); 183 | } 184 | 185 | /// @notice Calculate the swapID from randomNumberHash and swapCreator 186 | /// 187 | /// @param _randomNumberHash The hash of random number and timestamp. 188 | /// @param _swapSender The creator of swap. 189 | /// @param _bep2SenderAddr The sender of swap on BNB Beacon Chain. 190 | function calSwapID(bytes32 _randomNumberHash, address _swapSender, bytes20 _bep2SenderAddr) public pure returns (bytes32) { 191 | if (_bep2SenderAddr == bytes20(0)) { 192 | return sha256(abi.encodePacked(_randomNumberHash, _swapSender)); 193 | } 194 | return sha256(abi.encodePacked(_randomNumberHash, _swapSender, _bep2SenderAddr)); 195 | } 196 | } -------------------------------------------------------------------------------- /ethereum/contracts/Migrations.sol: -------------------------------------------------------------------------------- 1 | pragma solidity 0.5.8; 2 | 3 | contract Migrations { 4 | address public owner; 5 | uint public last_completed_migration; 6 | 7 | modifier restricted() { 8 | if (msg.sender == owner) _; 9 | } 10 | 11 | constructor() public { 12 | owner = msg.sender; 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 | -------------------------------------------------------------------------------- /ethereum/contracts/bnb.sol: -------------------------------------------------------------------------------- 1 | /** 2 | *Submitted for verification at Etherscan.io on 2019-07-12 3 | */ 4 | 5 | pragma solidity 0.5.8; 6 | 7 | /** 8 | * @dev Wrappers over Solidity's arithmetic operations with added overflow 9 | * checks. 10 | * 11 | * Arithmetic operations in Solidity wrap on overflow. This can easily result 12 | * in bugs, because programmers usually assume that an overflow raises an 13 | * error, which is the standard behavior in high level programming languages. 14 | * `SafeMath` restores this intuition by reverting the transaction when an 15 | * operation overflows. 16 | * 17 | * Using this library instead of the unchecked operations eliminates an entire 18 | * class of bugs, so it's recommended to use it always. 19 | */ 20 | library SafeMath { 21 | /** 22 | * @dev Returns the addition of two unsigned integers, reverting on 23 | * overflow. 24 | * 25 | * Counterpart to Solidity's `+` operator. 26 | * 27 | * Requirements: 28 | * - Addition cannot overflow. 29 | */ 30 | function add(uint256 a, uint256 b) internal pure returns (uint256) { 31 | uint256 c = a + b; 32 | require(c >= a, "SafeMath: addition overflow"); 33 | 34 | return c; 35 | } 36 | 37 | /** 38 | * @dev Returns the subtraction of two unsigned integers, reverting on 39 | * overflow (when the result is negative). 40 | * 41 | * Counterpart to Solidity's `-` operator. 42 | * 43 | * Requirements: 44 | * - Subtraction cannot overflow. 45 | */ 46 | function sub(uint256 a, uint256 b) internal pure returns (uint256) { 47 | require(b <= a, "SafeMath: subtraction overflow"); 48 | uint256 c = a - b; 49 | 50 | return c; 51 | } 52 | 53 | /** 54 | * @dev Returns the multiplication of two unsigned integers, reverting on 55 | * overflow. 56 | * 57 | * Counterpart to Solidity's `*` operator. 58 | * 59 | * Requirements: 60 | * - Multiplication cannot overflow. 61 | */ 62 | function mul(uint256 a, uint256 b) internal pure returns (uint256) { 63 | // Gas optimization: this is cheaper than requiring 'a' not being zero, but the 64 | // benefit is lost if 'b' is also tested. 65 | // See: https://github.com/OpenZeppelin/openzeppelin-solidity/pull/522 66 | if (a == 0) { 67 | return 0; 68 | } 69 | 70 | uint256 c = a * b; 71 | require(c / a == b, "SafeMath: multiplication overflow"); 72 | 73 | return c; 74 | } 75 | 76 | /** 77 | * @dev Returns the integer division of two unsigned integers. Reverts on 78 | * division by zero. The result is rounded towards zero. 79 | * 80 | * Counterpart to Solidity's `/` operator. Note: this function uses a 81 | * `revert` opcode (which leaves remaining gas untouched) while Solidity 82 | * uses an invalid opcode to revert (consuming all remaining gas). 83 | * 84 | * Requirements: 85 | * - The divisor cannot be zero. 86 | */ 87 | function div(uint256 a, uint256 b) internal pure returns (uint256) { 88 | // Solidity only automatically asserts when dividing by 0 89 | require(b > 0, "SafeMath: division by zero"); 90 | uint256 c = a / b; 91 | // assert(a == b * c + a % b); // There is no case in which this doesn't hold 92 | 93 | return c; 94 | } 95 | 96 | /** 97 | * @dev Returns the remainder of dividing two unsigned integers. (unsigned integer modulo), 98 | * Reverts when dividing by zero. 99 | * 100 | * Counterpart to Solidity's `%` operator. This function uses a `revert` 101 | * opcode (which leaves remaining gas untouched) while Solidity uses an 102 | * invalid opcode to revert (consuming all remaining gas). 103 | * 104 | * Requirements: 105 | * - The divisor cannot be zero. 106 | */ 107 | function mod(uint256 a, uint256 b) internal pure returns (uint256) { 108 | require(b != 0, "SafeMath: modulo by zero"); 109 | return a % b; 110 | } 111 | } 112 | 113 | /** 114 | * @title Roles 115 | * @dev Library for managing addresses assigned to a Role. 116 | */ 117 | library Roles { 118 | struct Role { 119 | mapping (address => bool) bearer; 120 | } 121 | 122 | /** 123 | * @dev Give an account access to this role. 124 | */ 125 | function add(Role storage role, address account) internal { 126 | require(!has(role, account), "Roles: account already has role"); 127 | role.bearer[account] = true; 128 | } 129 | 130 | /** 131 | * @dev Remove an account's access to this role. 132 | */ 133 | function remove(Role storage role, address account) internal { 134 | require(has(role, account), "Roles: account does not have role"); 135 | role.bearer[account] = false; 136 | } 137 | 138 | /** 139 | * @dev Check if an account has this role. 140 | * @return bool 141 | */ 142 | function has(Role storage role, address account) internal view returns (bool) { 143 | require(account != address(0), "Roles: account is the zero address"); 144 | return role.bearer[account]; 145 | } 146 | } 147 | 148 | contract PauserRole { 149 | using Roles for Roles.Role; 150 | 151 | event PauserAdded(address indexed account); 152 | event PauserRemoved(address indexed account); 153 | 154 | Roles.Role private _pausers; 155 | 156 | constructor () internal { 157 | _addPauser(msg.sender); 158 | } 159 | 160 | modifier onlyPauser() { 161 | require(isPauser(msg.sender), "PauserRole: caller does not have the Pauser role"); 162 | _; 163 | } 164 | 165 | function isPauser(address account) public view returns (bool) { 166 | return _pausers.has(account); 167 | } 168 | 169 | function addPauser(address account) public onlyPauser { 170 | _addPauser(account); 171 | } 172 | 173 | function renouncePauser() public { 174 | _removePauser(msg.sender); 175 | } 176 | 177 | function _addPauser(address account) internal { 178 | _pausers.add(account); 179 | emit PauserAdded(account); 180 | } 181 | 182 | function _removePauser(address account) internal { 183 | _pausers.remove(account); 184 | emit PauserRemoved(account); 185 | } 186 | } 187 | 188 | /** 189 | * @dev Contract module which allows children to implement an emergency stop 190 | * mechanism that can be triggered by an authorized account. 191 | * 192 | * This module is used through inheritance. It will make available the 193 | * modifiers `whenNotPaused` and `whenPaused`, which can be applied to 194 | * the functions of your contract. Note that they will not be pausable by 195 | * simply including this module, only once the modifiers are put in place. 196 | */ 197 | contract Pausable is PauserRole { 198 | /** 199 | * @dev Emitted when the pause is triggered by a pauser (`account`). 200 | */ 201 | event Paused(address account); 202 | 203 | /** 204 | * @dev Emitted when the pause is lifted by a pauser (`account`). 205 | */ 206 | event Unpaused(address account); 207 | 208 | bool private _paused; 209 | 210 | /** 211 | * @dev Initializes the contract in unpaused state. Assigns the Pauser role 212 | * to the deployer. 213 | */ 214 | constructor () internal { 215 | _paused = false; 216 | } 217 | 218 | /** 219 | * @dev Returns true if the contract is paused, and false otherwise. 220 | */ 221 | function paused() public view returns (bool) { 222 | return _paused; 223 | } 224 | 225 | /** 226 | * @dev Modifier to make a function callable only when the contract is not paused. 227 | */ 228 | modifier whenNotPaused() { 229 | require(!_paused, "Pausable: paused"); 230 | _; 231 | } 232 | 233 | /** 234 | * @dev Modifier to make a function callable only when the contract is paused. 235 | */ 236 | modifier whenPaused() { 237 | require(_paused, "Pausable: not paused"); 238 | _; 239 | } 240 | 241 | /** 242 | * @dev Called by a pauser to pause, triggers stopped state. 243 | */ 244 | function pause() public onlyPauser whenNotPaused { 245 | _paused = true; 246 | emit Paused(msg.sender); 247 | } 248 | 249 | /** 250 | * @dev Called by a pauser to unpause, returns to normal state. 251 | */ 252 | function unpause() public onlyPauser whenPaused { 253 | _paused = false; 254 | emit Unpaused(msg.sender); 255 | } 256 | } 257 | 258 | /** 259 | * @dev Contract module which provides a basic access control mechanism, where 260 | * there is an account (an owner) that can be granted exclusive access to 261 | * specific functions. 262 | * 263 | * This module is used through inheritance. It will make available the modifier 264 | * `onlyOwner`, which can be aplied to your functions to restrict their use to 265 | * the owner. 266 | */ 267 | contract Ownable { 268 | address private _owner; 269 | 270 | event OwnershipTransferred(address indexed previousOwner, address indexed newOwner); 271 | 272 | /** 273 | * @dev Initializes the contract setting the deployer as the initial owner. 274 | */ 275 | constructor () internal { 276 | _owner = msg.sender; 277 | emit OwnershipTransferred(address(0), _owner); 278 | } 279 | 280 | /** 281 | * @dev Returns the address of the current owner. 282 | */ 283 | function owner() public view returns (address) { 284 | return _owner; 285 | } 286 | 287 | /** 288 | * @dev Throws if called by any account other than the owner. 289 | */ 290 | modifier onlyOwner() { 291 | require(isOwner(), "Ownable: caller is not the owner"); 292 | _; 293 | } 294 | 295 | /** 296 | * @dev Returns true if the caller is the current owner. 297 | */ 298 | function isOwner() public view returns (bool) { 299 | return msg.sender == _owner; 300 | } 301 | 302 | /** 303 | * @dev Leaves the contract without owner. It will not be possible to call 304 | * `onlyOwner` functions anymore. Can only be called by the current owner. 305 | * 306 | * > Note: Renouncing ownership will leave the contract without an owner, 307 | * thereby removing any functionality that is only available to the owner. 308 | */ 309 | function renounceOwnership() public onlyOwner { 310 | emit OwnershipTransferred(_owner, address(0)); 311 | _owner = address(0); 312 | } 313 | 314 | /** 315 | * @dev Transfers ownership of the contract to a new account (`newOwner`). 316 | * Can only be called by the current owner. 317 | */ 318 | function transferOwnership(address newOwner) public onlyOwner { 319 | _transferOwnership(newOwner); 320 | } 321 | 322 | /** 323 | * @dev Transfers ownership of the contract to a new account (`newOwner`). 324 | */ 325 | function _transferOwnership(address newOwner) internal { 326 | require(newOwner != address(0), "Ownable: new owner is the zero address"); 327 | emit OwnershipTransferred(_owner, newOwner); 328 | _owner = newOwner; 329 | } 330 | } 331 | 332 | /** 333 | * @dev Interface of the ERC20 standard as defined in the EIP. Does not include 334 | * the optional functions; to access them see `ERC20Detailed`. 335 | */ 336 | interface IERC20 { 337 | /** 338 | * @dev Returns the amount of tokens in existence. 339 | */ 340 | function totalSupply() external view returns (uint256); 341 | 342 | /** 343 | * @dev Returns the amount of tokens owned by `account`. 344 | */ 345 | function balanceOf(address account) external view returns (uint256); 346 | 347 | /** 348 | * @dev Moves `amount` tokens from the caller's account to `recipient`. 349 | * 350 | * Returns a boolean value indicating whether the operation succeeded. 351 | * 352 | * Emits a `Transfer` event. 353 | */ 354 | function transfer(address recipient, uint256 amount) external returns (bool); 355 | 356 | /** 357 | * @dev Returns the remaining number of tokens that `spender` will be 358 | * allowed to spend on behalf of `owner` through `transferFrom`. This is 359 | * zero by default. 360 | * 361 | * This value changes when `approve` or `transferFrom` are called. 362 | */ 363 | function allowance(address owner, address spender) external view returns (uint256); 364 | 365 | /** 366 | * @dev Sets `amount` as the allowance of `spender` over the caller's tokens. 367 | * 368 | * Returns a boolean value indicating whether the operation succeeded. 369 | * 370 | * > Beware that changing an allowance with this method brings the risk 371 | * that someone may use both the old and the new allowance by unfortunate 372 | * transaction ordering. One possible solution to mitigate this race 373 | * condition is to first reduce the spender's allowance to 0 and set the 374 | * desired value afterwards: 375 | * https://github.com/ethereum/EIPs/issues/20#issuecomment-263524729 376 | * 377 | * Emits an `Approval` event. 378 | */ 379 | function approve(address spender, uint256 amount) external returns (bool); 380 | 381 | /** 382 | * @dev Moves `amount` tokens from `sender` to `recipient` using the 383 | * allowance mechanism. `amount` is then deducted from the caller's 384 | * allowance. 385 | * 386 | * Returns a boolean value indicating whether the operation succeeded. 387 | * 388 | * Emits a `Transfer` event. 389 | */ 390 | function transferFrom(address sender, address recipient, uint256 amount) external returns (bool); 391 | 392 | /** 393 | * @dev Emitted when `value` tokens are moved from one account (`from`) to 394 | * another (`to`). 395 | * 396 | * Note that `value` may be zero. 397 | */ 398 | event Transfer(address indexed from, address indexed to, uint256 value); 399 | 400 | /** 401 | * @dev Emitted when the allowance of a `spender` for an `owner` is set by 402 | * a call to `approve`. `value` is the new allowance. 403 | */ 404 | event Approval(address indexed owner, address indexed spender, uint256 value); 405 | } 406 | 407 | /** 408 | * @dev Implementation of the `IERC20` interface. 409 | * 410 | * This implementation is agnostic to the way tokens are created. This means 411 | * that a supply mechanism has to be added in a derived contract using `_mint`. 412 | * For a generic mechanism see `ERC20Mintable`. 413 | * 414 | * *For a detailed writeup see our guide [How to implement supply 415 | * mechanisms](https://forum.zeppelin.solutions/t/how-to-implement-erc20-supply-mechanisms/226).* 416 | * 417 | * We have followed general OpenZeppelin guidelines: functions revert instead 418 | * of returning `false` on failure. This behavior is nonetheless conventional 419 | * and does not conflict with the expectations of ERC20 applications. 420 | * 421 | * Additionally, an `Approval` event is emitted on calls to `transferFrom`. 422 | * This allows applications to reconstruct the allowance for all accounts just 423 | * by listening to said events. Other implementations of the EIP may not emit 424 | * these events, as it isn't required by the specification. 425 | * 426 | * Finally, the non-standard `decreaseAllowance` and `increaseAllowance` 427 | * functions have been added to mitigate the well-known issues around setting 428 | * allowances. See `IERC20.approve`. 429 | */ 430 | contract ERC20 is IERC20 { 431 | using SafeMath for uint256; 432 | 433 | mapping (address => uint256) private _balances; 434 | 435 | mapping (address => mapping (address => uint256)) private _allowances; 436 | 437 | uint256 private _totalSupply; 438 | 439 | /** 440 | * @dev See `IERC20.totalSupply`. 441 | */ 442 | function totalSupply() public view returns (uint256) { 443 | return _totalSupply; 444 | } 445 | 446 | /** 447 | * @dev See `IERC20.balanceOf`. 448 | */ 449 | function balanceOf(address account) public view returns (uint256) { 450 | return _balances[account]; 451 | } 452 | 453 | /** 454 | * @dev See `IERC20.transfer`. 455 | * 456 | * Requirements: 457 | * 458 | * - `recipient` cannot be the zero address. 459 | * - the caller must have a balance of at least `amount`. 460 | */ 461 | function transfer(address recipient, uint256 amount) public returns (bool) { 462 | _transfer(msg.sender, recipient, amount); 463 | return true; 464 | } 465 | 466 | /** 467 | * @dev See `IERC20.allowance`. 468 | */ 469 | function allowance(address owner, address spender) public view returns (uint256) { 470 | return _allowances[owner][spender]; 471 | } 472 | 473 | /** 474 | * @dev See `IERC20.approve`. 475 | * 476 | * Requirements: 477 | * 478 | * - `spender` cannot be the zero address. 479 | */ 480 | function approve(address spender, uint256 value) public returns (bool) { 481 | _approve(msg.sender, spender, value); 482 | return true; 483 | } 484 | 485 | /** 486 | * @dev See `IERC20.transferFrom`. 487 | * 488 | * Emits an `Approval` event indicating the updated allowance. This is not 489 | * required by the EIP. See the note at the beginning of `ERC20`; 490 | * 491 | * Requirements: 492 | * - `sender` and `recipient` cannot be the zero address. 493 | * - `sender` must have a balance of at least `value`. 494 | * - the caller must have allowance for `sender`'s tokens of at least 495 | * `amount`. 496 | */ 497 | function transferFrom(address sender, address recipient, uint256 amount) public returns (bool) { 498 | _transfer(sender, recipient, amount); 499 | _approve(sender, msg.sender, _allowances[sender][msg.sender].sub(amount)); 500 | return true; 501 | } 502 | 503 | /** 504 | * @dev Atomically increases the allowance granted to `spender` by the caller. 505 | * 506 | * This is an alternative to `approve` that can be used as a mitigation for 507 | * problems described in `IERC20.approve`. 508 | * 509 | * Emits an `Approval` event indicating the updated allowance. 510 | * 511 | * Requirements: 512 | * 513 | * - `spender` cannot be the zero address. 514 | */ 515 | function increaseAllowance(address spender, uint256 addedValue) public returns (bool) { 516 | _approve(msg.sender, spender, _allowances[msg.sender][spender].add(addedValue)); 517 | return true; 518 | } 519 | 520 | /** 521 | * @dev Atomically decreases the allowance granted to `spender` by the caller. 522 | * 523 | * This is an alternative to `approve` that can be used as a mitigation for 524 | * problems described in `IERC20.approve`. 525 | * 526 | * Emits an `Approval` event indicating the updated allowance. 527 | * 528 | * Requirements: 529 | * 530 | * - `spender` cannot be the zero address. 531 | * - `spender` must have allowance for the caller of at least 532 | * `subtractedValue`. 533 | */ 534 | function decreaseAllowance(address spender, uint256 subtractedValue) public returns (bool) { 535 | _approve(msg.sender, spender, _allowances[msg.sender][spender].sub(subtractedValue)); 536 | return true; 537 | } 538 | 539 | /** 540 | * @dev Moves tokens `amount` from `sender` to `recipient`. 541 | * 542 | * This is internal function is equivalent to `transfer`, and can be used to 543 | * e.g. implement automatic token fees, slashing mechanisms, etc. 544 | * 545 | * Emits a `Transfer` event. 546 | * 547 | * Requirements: 548 | * 549 | * - `sender` cannot be the zero address. 550 | * - `recipient` cannot be the zero address. 551 | * - `sender` must have a balance of at least `amount`. 552 | */ 553 | function _transfer(address sender, address recipient, uint256 amount) internal { 554 | require(sender != address(0), "ERC20: transfer from the zero address"); 555 | require(recipient != address(0), "ERC20: transfer to the zero address"); 556 | 557 | _balances[sender] = _balances[sender].sub(amount); 558 | _balances[recipient] = _balances[recipient].add(amount); 559 | emit Transfer(sender, recipient, amount); 560 | } 561 | 562 | /** @dev Creates `amount` tokens and assigns them to `account`, increasing 563 | * the total supply. 564 | * 565 | * Emits a `Transfer` event with `from` set to the zero address. 566 | * 567 | * Requirements 568 | * 569 | * - `to` cannot be the zero address. 570 | */ 571 | function _mint(address account, uint256 amount) internal { 572 | require(account != address(0), "ERC20: mint to the zero address"); 573 | 574 | _totalSupply = _totalSupply.add(amount); 575 | _balances[account] = _balances[account].add(amount); 576 | emit Transfer(address(0), account, amount); 577 | } 578 | 579 | /** 580 | * @dev Destoys `amount` tokens from `account`, reducing the 581 | * total supply. 582 | * 583 | * Emits a `Transfer` event with `to` set to the zero address. 584 | * 585 | * Requirements 586 | * 587 | * - `account` cannot be the zero address. 588 | * - `account` must have at least `amount` tokens. 589 | */ 590 | function _burn(address account, uint256 value) internal { 591 | require(account != address(0), "ERC20: burn from the zero address"); 592 | 593 | _totalSupply = _totalSupply.sub(value); 594 | _balances[account] = _balances[account].sub(value); 595 | emit Transfer(account, address(0), value); 596 | } 597 | 598 | /** 599 | * @dev Sets `amount` as the allowance of `spender` over the `owner`s tokens. 600 | * 601 | * This is internal function is equivalent to `approve`, and can be used to 602 | * e.g. set automatic allowances for certain subsystems, etc. 603 | * 604 | * Emits an `Approval` event. 605 | * 606 | * Requirements: 607 | * 608 | * - `owner` cannot be the zero address. 609 | * - `spender` cannot be the zero address. 610 | */ 611 | function _approve(address owner, address spender, uint256 value) internal { 612 | require(owner != address(0), "ERC20: approve from the zero address"); 613 | require(spender != address(0), "ERC20: approve to the zero address"); 614 | 615 | _allowances[owner][spender] = value; 616 | emit Approval(owner, spender, value); 617 | } 618 | 619 | /** 620 | * @dev Destoys `amount` tokens from `account`.`amount` is then deducted 621 | * from the caller's allowance. 622 | * 623 | * See `_burn` and `_approve`. 624 | */ 625 | function _burnFrom(address account, uint256 amount) internal { 626 | _burn(account, amount); 627 | _approve(account, msg.sender, _allowances[account][msg.sender].sub(amount)); 628 | } 629 | } 630 | 631 | contract BlackListableToken is Ownable, ERC20 { 632 | 633 | /////// Getters to allow the same blacklist to be used also by other contracts (including upgraded Tether) /////// 634 | function getBlackListStatus(address _maker) external view returns (bool) { 635 | return isBlackListed[_maker]; 636 | } 637 | 638 | mapping (address => bool) public isBlackListed; 639 | 640 | function addBlackList(address _evilUser) public onlyOwner { 641 | require(!isBlackListed[_evilUser], "_evilUser is already in black list"); 642 | 643 | isBlackListed[_evilUser] = true; 644 | emit AddedBlackList(_evilUser); 645 | } 646 | 647 | function removeBlackList(address _clearedUser) public onlyOwner { 648 | require(isBlackListed[_clearedUser], "_clearedUser isn't in black list"); 649 | 650 | isBlackListed[_clearedUser] = false; 651 | emit RemovedBlackList(_clearedUser); 652 | } 653 | 654 | function destroyBlackFunds(address _blackListedUser) public onlyOwner { 655 | require(_blackListedUser != address(0x0), "_blackListedUser is the zero address"); 656 | require(isBlackListed[_blackListedUser], "_blackListedUser isn't in black list"); 657 | 658 | uint256 dirtyFunds = balanceOf(_blackListedUser); 659 | super._burn(_blackListedUser, dirtyFunds); 660 | emit DestroyedBlackFunds(_blackListedUser, dirtyFunds); 661 | } 662 | 663 | event DestroyedBlackFunds(address indexed _blackListedUser, uint256 _balance); 664 | 665 | event AddedBlackList(address indexed _user); 666 | 667 | event RemovedBlackList(address indexed _user); 668 | 669 | } 670 | 671 | contract BNBToken is ERC20, Pausable, BlackListableToken { 672 | 673 | string public name; 674 | string public symbol; 675 | uint8 public decimals; 676 | 677 | // The contract can be initialized with a number of tokens 678 | // All the tokens are deposited to the owner address 679 | // 680 | // @param _balance Initial supply of the contract 681 | // @param _name Token Name 682 | // @param _symbol Token symbol 683 | // @param _decimals Token decimals 684 | constructor(uint256 _initialSupply, string memory _name, string memory _symbol, uint8 _decimals) public { 685 | name = _name; 686 | symbol = _symbol; 687 | decimals = _decimals; 688 | super._mint(msg.sender, _initialSupply); 689 | } 690 | 691 | function transfer(address _to, uint256 _value) public whenNotPaused returns (bool success) { 692 | require(!isBlackListed[msg.sender], "can't transfer token from address in black list"); 693 | require(!isBlackListed[_to], "can't transfer token to address in black list"); 694 | return super.transfer(_to, _value); 695 | } 696 | 697 | function transferFrom(address _from, address _to, uint256 _value) public whenNotPaused returns (bool success) { 698 | require(!isBlackListed[_from], "can't transfer token from address in black list"); 699 | require(!isBlackListed[_to], "can't transfer token to address in black list"); 700 | return super.transferFrom(_from, _to, _value); 701 | } 702 | 703 | function balanceOf(address who) public view returns (uint256) { 704 | return super.balanceOf(who); 705 | } 706 | 707 | function approve(address _spender, uint256 _value) public whenNotPaused returns (bool success) { 708 | return super.approve(_spender, _value); 709 | } 710 | 711 | function increaseAllowance(address _spender, uint256 _addedValue) public whenNotPaused returns (bool success) { 712 | return super.increaseAllowance(_spender, _addedValue); 713 | } 714 | 715 | function decreaseAllowance(address _spender, uint256 _subtractedValue) public whenNotPaused returns (bool success) { 716 | return super.decreaseAllowance(_spender, _subtractedValue); 717 | } 718 | 719 | function allowance(address _owner, address _spender) public view returns (uint256 remaining) { 720 | return super.allowance(_owner, _spender); 721 | } 722 | 723 | function totalSupply() public view returns (uint256) { 724 | return super.totalSupply(); 725 | } 726 | 727 | // Issue a new amount of tokens 728 | // these tokens are deposited into the owner address 729 | // 730 | // @param _amount Number of tokens to be issued 731 | function issue(uint256 amount) public onlyOwner whenNotPaused { 732 | 733 | super._mint(msg.sender, amount); 734 | emit Issue(amount); 735 | } 736 | 737 | // Redeem tokens. 738 | // These tokens are withdrawn from the owner address 739 | // if the balance must be enough to cover the redeem 740 | // or the call will fail. 741 | // @param _amount Number of tokens to be issued 742 | function redeem(uint256 amount) public onlyOwner whenNotPaused { 743 | super._burn(msg.sender, amount); 744 | emit Redeem(amount); 745 | } 746 | 747 | // Called when new token are issued 748 | event Issue(uint256 amount); 749 | 750 | // Called when tokens are redeemed 751 | event Redeem(uint256 amount); 752 | } -------------------------------------------------------------------------------- /ethereum/migrations/1_initial_migration.js: -------------------------------------------------------------------------------- 1 | const Migrations = artifacts.require("Migrations"); 2 | 3 | module.exports = function(deployer) { 4 | deployer.deploy(Migrations); 5 | }; 6 | -------------------------------------------------------------------------------- /ethereum/migrations/2_deploy_contracts.js: -------------------------------------------------------------------------------- 1 | const BNBToken = artifacts.require("BNBToken"); 2 | const ERC20AtomicSwapper = artifacts.require("ERC20AtomicSwapper"); 3 | const ETHAtomicSwapper = artifacts.require("ETHAtomicSwapper"); 4 | 5 | module.exports = function(deployer) { 6 | deployer.deploy(BNBToken, "10000000000000000", "BNB Token", "BNB", "8").then(function(){ 7 | return deployer.deploy(ERC20AtomicSwapper, BNBToken.address); 8 | }); 9 | deployer.deploy(ETHAtomicSwapper) 10 | }; 11 | -------------------------------------------------------------------------------- /ethereum/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "AtomicSwapper", 3 | "version": "1.0.0", 4 | "description": "", 5 | "main": "truffle.js", 6 | "directories": { 7 | "test": "test", 8 | "migration": "migration" 9 | }, 10 | "author": "", 11 | "license": "", 12 | "scripts": { 13 | "ganache": "ganache-cli -p 8545 --networkId 5555", 14 | "migration": "truffle migration --network test", 15 | "test": "truffle test --network test" 16 | }, 17 | "devDependencies": { 18 | "ganache-cli": "^v6.4.4", 19 | "truffle-assertions": "^0.9.1", 20 | "big.js": "^3.1.3 " 21 | }, 22 | "dependencies": { 23 | "truffle": "^v5.0.26", 24 | "openzeppelin-solidity": "^2.3.0" 25 | } 26 | } 27 | -------------------------------------------------------------------------------- /ethereum/spec/ERC20AtomicSwap.md: -------------------------------------------------------------------------------- 1 | # Atomic Swap Contract 2 | 3 | ## Summary 4 | 5 | This contract implement secret hash lock mechanism which enables atomic swap between ERC20 token and BEP2 tokens on BNB Beacon Chain. 6 | 7 | ## Smart Contract Interface 8 | 9 | ### Transaction interfaces 10 | 11 | 1. function **htlt**(bytes32 _randomNumberHash, uint64 _timestamp, uint256 _heightSpan, address _recipientAddr, bytes20 _bep2SenderAddr, bytes20 _bep2RecipientAddr, uint256 _outAmount, uint256 _bep2Amount) 12 | 1. `_timestamp` is supposed to be the time of sending transaction, counted by second. If this htlt is response to another htlt on other chain, then their timestamp should be identical. 13 | 2. `_randomNumberHash` sha256(_randomNumber, _timestamp) 14 | 3. `_heightSpan` is the number of blocks to wait before the asset can be refunded 15 | 4. `_recipientAddr` is the Ethereum address of swap counter party 16 | 5. `_bep2SenderAddr` is the swap sender address on BNB Beacon Chain 17 | 5. `_bep2RecipientAddr` is the receiver address on BNB Beacon Chain. 18 | 6. `_outAmount` is the recipient address on BNB Beacon Chain. 19 | 7. `_bep2Amount` is the expected received BEP2 token on BNB Beacon Chain. 20 | 21 | 2. function **refund**(bytes32 _swapID) 22 | 23 | `_swapID` sha256(swap.randomNumberHash, swap.From, swap.SenderOtherChain) 24 | 25 | 3. function **claim**(bytes32 _swapID, bytes32 _randomNumber) 26 | 1. `_swapID` sha256(swap.randomNumberHash, swap.From, swap.SenderOtherChain) 27 | 2. `_randomNumber` is a random 32-length byte array. Client should keep it private strictly. 28 | 29 | ### Query interfaces 30 | 31 | 1. function **isSwapExist**(bytes32 _swapID) returns (bool) 32 | 33 | Judge if the `_swapID` has been used already. 34 | 35 | 2. function **refundable**(bytes32 _swapID) returns (bool) 36 | 37 | Judge if the asset locked by the specified swap can be refunded or not. If true, anyone can call refund function to refund locked asset to the swap creator. 38 | 39 | 3. function **claimable**(bytes32 _swapID) returns (bool) 40 | 41 | Judge if the asset locked by the specified swap can be claimed or not. If true, anyone can call claim function to transfer locked asset to the `_receiverAddr` address. 42 | 43 | 5. function **queryOpenSwap**(uin256 _swapID) returns (bytes32 _randomNumberHash, uint64 _timestamp, uint256 _expireHeight, uint256 _outAmount, address _sender, address _recipient) 44 | 45 | Query an opened swap record by swapID. 46 | 47 | ### Event 48 | 49 | 1. event **HTLT**(address indexed _msgSender, address indexed _recipientAddr, bytes32 indexed _swapID, bytes32 _randomNumberHash, uint64 _timestamp, bytes20 _bep2Addr, uint256 _expireHeight, uint256 _outAmount, uint256 _bep2Amount); 50 | 51 | Once a swap is created, then this event will be emitted. Client can monitor this event to get all new created swaps. 52 | 53 | 2. event **Refunded**(address indexed _msgSender, address indexed _recipientAddr, bytes32 indexed _swapID, bytes32 _randomNumberHash); 54 | 55 | One a swap expire height is passed and someone call **refund** function, then this event will be emitted. 56 | 57 | 3. event **Claimed**(address indexed _msgSender, address indexed _recipientAddr, bytes32 indexed _swapID, bytes32 _randomNumberHash, bytes32 _randomNumber); 58 | 59 | If someone call **claim** to a swap with correct secretKey and the swap expire height is not passed, then this event will be emitted. Client can monitor this event to get the secretKey. 60 | 61 | -------------------------------------------------------------------------------- /ethereum/spec/ETHAtomicSwap.md: -------------------------------------------------------------------------------- 1 | # Atomic Swap Contract 2 | 3 | ## Summary 4 | 5 | This contract implement secret hash lock mechanism which enables atomic swap between ETH coin and BEP2 tokens on BNB Beacon Chain. 6 | 7 | ## Smart Contract Interface 8 | 9 | ### Transaction interfaces 10 | 11 | 1. function **htlt**(bytes32 _randomNumberHash, uint64 _timestamp, uint256 _heightSpan, address _recipientAddr, bytes20 _bep2SenderAddr, bytes20 _bep2RecipientAddr, uint256 _outAmount, uint256 _bep2Amount) 12 | 1. `_timestamp` is supposed to be the time of sending transaction, counted by second. If this htlt is response to another htlt on other chain, then their timestamp should be identical. 13 | 2. `_randomNumberHash` sha256(_randomNumber, _timestamp) 14 | 3. `_heightSpan` is the number of blocks to wait before the asset can be refunded 15 | 4. `_recipientAddr` is the Ethereum address of swap counter party 16 | 5. `_bep2SenderAddr` is the swap sender address on BNB Beacon Chain 17 | 5. `_bep2RecipientAddr` is the receiver address on BNB Beacon Chain. 18 | 6. `_outAmount` is the recipient address on BNB Beacon Chain. 19 | 7. `_bep2Amount` is the expected received BEP2 token on BNB Beacon Chain. 20 | 21 | 2. function **refund**(bytes32 _swapID) 22 | 23 | `_swapID` sha256(swap.randomNumberHash, swap.From, swap.SenderOtherChain) 24 | 25 | 3. function **claim**(bytes32 _swapID, bytes32 _randomNumber) 26 | 1. `_swapID` sha256(swap.randomNumberHash, swap.From, swap.SenderOtherChain) 27 | 2. `_randomNumber` is a random 32-length byte array. Client should keep it private strictly. 28 | 29 | ### Query interfaces 30 | 31 | 1. function **isSwapExist**(bytes32 _swapID) returns (bool) 32 | 33 | Judge if the `_swapID` has been used already. 34 | 35 | 2. function **refundable**(bytes32 _swapID) returns (bool) 36 | 37 | Judge if the asset locked by the specified swap can be refunded or not. If true, anyone can call refund function to refund locked asset to the swap creator. 38 | 39 | 3. function **claimable**(bytes32 _swapID) returns (bool) 40 | 41 | Judge if the asset locked by the specified swap can be claimed or not. If true, anyone can call claim function to transfer locked asset to the `_receiverAddr` address. 42 | 43 | 5. function **queryOpenSwap**(uin256 _swapID) returns (bytes32 _randomNumberHash, uint64 _timestamp, uint256 _expireHeight, uint256 _outAmount, address _sender, address _recipient) 44 | 45 | Query an opened swap record by swapID. 46 | 47 | ### Event 48 | 49 | 1. event **HTLT**(address indexed _msgSender, address indexed _recipientAddr, bytes32 indexed _swapID, bytes32 _randomNumberHash, uint64 _timestamp, bytes20 _bep2Addr, uint256 _expireHeight, uint256 _outAmount, uint256 _bep2Amount); 50 | 51 | Once a swap is created, then this event will be emitted. Client can monitor this event to get all new created swaps. 52 | 53 | 2. event **Refunded**(address indexed _msgSender, address indexed _recipientAddr, bytes32 indexed _swapID, bytes32 _randomNumberHash); 54 | 55 | One a swap expire height is passed and someone call **refund** function, then this event will be emitted. 56 | 57 | 3. event **Claimed**(address indexed _msgSender, address indexed _recipientAddr, bytes32 indexed _swapID, bytes32 _randomNumberHash, bytes32 _randomNumber); 58 | 59 | If someone call **claim** to a swap with correct secretKey and the swap expire height is not passed, then this event will be emitted. Client can monitor this event to get the secretKey. 60 | 61 | -------------------------------------------------------------------------------- /ethereum/test/ERC20AtomicSwapTest.js: -------------------------------------------------------------------------------- 1 | const BNBToken = artifacts.require("BNBToken"); 2 | const ERC20AtomicSwapper = artifacts.require("ERC20AtomicSwapper"); 3 | const crypto = require('crypto'); 4 | const truffleAssert = require('truffle-assertions'); 5 | 6 | function calculateRandomNumberHash (randomNumber, timestamp) { 7 | const timestampHexStr = timestamp.toString(16); 8 | var timestampHexStrFormat = timestampHexStr; 9 | // timestampHexStrFormat should be the hex string of a 32-length byte array. Fill 0 if the timestampHexStr length is less than 64 10 | for (var i = 0; i < 16 - timestampHexStr.length; i++) { 11 | timestampHexStrFormat = '0' + timestampHexStrFormat; 12 | } 13 | const timestampBytes = Buffer.from(timestampHexStrFormat, "hex"); 14 | const newBuffer = Buffer.concat([Buffer.from(randomNumber.substring(2, 66), "hex"), timestampBytes]); 15 | const hash = crypto.createHash('sha256'); 16 | hash.update(newBuffer); 17 | return "0x" + hash.digest('hex'); 18 | } 19 | 20 | function calculateSwapID(randomNumberHash, sender, recipient) { 21 | const newBuffer = Buffer.concat([Buffer.from(randomNumberHash.substring(2, 66), "hex"), Buffer.from(sender.substring(2, 42), "hex"), Buffer.from(recipient.substring(2, 42), "hex")]); 22 | const hash = crypto.createHash('sha256'); 23 | hash.update(newBuffer); 24 | return "0x" + hash.digest('hex'); 25 | } 26 | 27 | contract('Verify BNBToken and ERC20AtomicSwapper', (accounts) => { 28 | it('Check init state for BNBToken and ERC20AtomicSwapper', async () => { 29 | const initSupply = 10000000000000000; 30 | 31 | const bnbInstance = await BNBToken.deployed(); 32 | const balance = await bnbInstance.balanceOf.call(accounts[0]); 33 | assert.equal(Number(balance.toString()), initSupply, "10000000000000000 wasn't in the first account"); 34 | 35 | const name = await bnbInstance.name.call(); 36 | assert.equal(name, "BNB Token", "Contract name should be BNB Token"); 37 | 38 | const symbol = await bnbInstance.symbol.call(); 39 | assert.equal(symbol, "BNB", "Token symbol should be BNB"); 40 | 41 | const decimals = await bnbInstance.decimals.call(); 42 | assert.equal(decimals, 8, "Token decimals should be 8"); 43 | 44 | const totalSupply = await bnbInstance.totalSupply.call(); 45 | assert.equal(Number(totalSupply.toString()), initSupply, "Token total supply should be 10000000000000000"); 46 | 47 | const owner = await bnbInstance.owner.call(); 48 | assert.equal(owner, accounts[0], "Contract owner should be accounts[0]"); 49 | 50 | const paused = await bnbInstance.paused.call(); 51 | assert.equal(paused, false, "Contract paused status should be false"); 52 | 53 | const swapInstance = await ERC20AtomicSwapper.deployed(); 54 | const erc20Address = await swapInstance.ERC20ContractAddr.call(); 55 | assert.equal(erc20Address, BNBToken.address, "swap contract should have erc20 contract address"); 56 | }); 57 | it('Test transfer, approve and transferFrom for BNB token', async () => { 58 | const bnbInstance = await BNBToken.deployed(); 59 | const acc0 = accounts[0]; 60 | const acc1 = accounts[1]; 61 | const acc2 = accounts[2]; 62 | const acc3 = accounts[3]; 63 | const amount = 1000000000000; 64 | 65 | await bnbInstance.transfer(acc1, amount, { from: acc0 }); 66 | const acc1Balance = (await bnbInstance.balanceOf.call(acc1)).valueOf(); 67 | assert.equal(Number(acc1Balance.toString()), amount, "acc1 balance should be " + amount); 68 | 69 | await bnbInstance.approve(acc2, amount, { from: acc1 }); 70 | await bnbInstance.transferFrom(acc1, acc3, amount, { from: acc2 }); 71 | 72 | const balanceAcc1 = (await bnbInstance.balanceOf.call(acc1)).valueOf(); 73 | const balanceAcc2 = (await bnbInstance.balanceOf.call(acc2)).valueOf(); 74 | const balanceAcc3 = (await bnbInstance.balanceOf.call(acc3)).valueOf(); 75 | 76 | assert.equal(Number(balanceAcc1.toString()), 0, "acc1 balance should be 0"); 77 | assert.equal(Number(balanceAcc2.toString()), 0, "acc2 balance should be 0"); 78 | assert.equal(Number(balanceAcc3.toString()), amount, "acc3 balance should be " + amount); 79 | 80 | await bnbInstance.approve(acc2, amount, { from: acc0 }); 81 | await bnbInstance.transferFrom(acc0, acc2, amount, { from: acc2 }); 82 | const balanceAcc2_1 = (await bnbInstance.balanceOf.call(acc2)).valueOf(); 83 | assert.equal(Number(balanceAcc2_1.toString()), amount, "acc2 balance should be " + amount); 84 | }); 85 | it('Test swap initiate, claim', async () => { 86 | const swapInstance = await ERC20AtomicSwapper.deployed(); 87 | const bnbInstance = await BNBToken.deployed(); 88 | 89 | const swapA = accounts[0]; 90 | const swapB = accounts[4]; 91 | 92 | const timestamp = Math.floor(Date.now()/1000); // counted by second 93 | const randomNumber = "0xaabbccddaabbccddaabbccddaabbccddaabbccddaabbccddaabbccddaabbccdd"; 94 | const randomNumberHash = calculateRandomNumberHash(randomNumber, timestamp); 95 | const timelock = 1000; 96 | const recipientAddr = swapB; 97 | const bep2Addr = "0xc9a2c4868f0f96faaa739b59934dc9cb304112ec"; 98 | const erc20Amount = 100000000; 99 | const bep2Amount = 100000000; 100 | const swapID = calculateSwapID(randomNumberHash, swapA, "0xdd365624c0fcc851ab935e3d39b4e1f6599f4d09"); 101 | 102 | var isSwapExist = (await swapInstance.isSwapExist.call(randomNumberHash)).valueOf(); 103 | assert.equal(isSwapExist, false); 104 | 105 | await bnbInstance.approve(ERC20AtomicSwapper.address, erc20Amount, { from: swapA }); 106 | let initiateTx = await swapInstance.htlt(randomNumberHash, timestamp, timelock, recipientAddr, "0xdd365624c0fcc851ab935e3d39b4e1f6599f4d09", bep2Addr, erc20Amount, bep2Amount, { from: swapA }); 107 | //SwapInit event should be emitted 108 | truffleAssert.eventEmitted(initiateTx, 'HTLT', (ev) => { 109 | return ev._msgSender === swapA && 110 | ev._recipientAddr === swapB && 111 | ev._bep2Addr === bep2Addr && 112 | ev._swapID === swapID && 113 | ev._randomNumberHash === randomNumberHash && 114 | Number(ev._timestamp.toString()) === timestamp && 115 | Number(ev._outAmount.toString()) === erc20Amount && 116 | Number(ev._bep2Amount.toString()) === bep2Amount; 117 | }); 118 | console.log("initiateTx gasUsed: ", initiateTx.receipt.gasUsed); 119 | 120 | // Verify if the swapped ERC20 token has been transferred to contract address 121 | var balanceOfSwapContract = await bnbInstance.balanceOf.call(ERC20AtomicSwapper.address); 122 | assert.equal(Number(balanceOfSwapContract.toString()), erc20Amount); 123 | 124 | // querySwapByHashLock 125 | var swap = (await swapInstance.queryOpenSwap.call(swapID)).valueOf(); 126 | assert.equal(timestamp, swap._timestamp); 127 | assert.equal(swapA, swap._sender); 128 | 129 | isSwapExist = (await swapInstance.isSwapExist.call(swapID)).valueOf(); 130 | assert.equal(isSwapExist, true); 131 | var claimable = (await swapInstance.claimable.call(swapID)).valueOf(); 132 | assert.equal(claimable, true); 133 | var refundable = (await swapInstance.refundable.call(swapID)).valueOf(); 134 | assert.equal(refundable, false); 135 | 136 | var balanceOfSwapB = await bnbInstance.balanceOf.call(swapB); 137 | assert.equal(Number(balanceOfSwapB.toString()), 0); 138 | 139 | // Anyone can call claim and the token will be paid to swapB address 140 | let claimTx = await swapInstance.claim(swapID, randomNumber, { from: accounts[6] }); 141 | //SwapComplete n event should be emitted 142 | truffleAssert.eventEmitted(claimTx, 'Claimed', (ev) => { 143 | return ev._msgSender === accounts[6] && ev._recipientAddr === swapB && ev._swapID === swapID && ev._randomNumberHash === randomNumberHash && ev._randomNumber === randomNumber; 144 | }); 145 | console.log("claimTx gasUsed: ", claimTx.receipt.gasUsed); 146 | 147 | balanceOfSwapB = await bnbInstance.balanceOf.call(swapB); 148 | assert.equal(Number(balanceOfSwapB.toString()), erc20Amount); 149 | 150 | balanceOfSwapContract = await bnbInstance.balanceOf.call(ERC20AtomicSwapper.address); 151 | assert.equal(Number(balanceOfSwapContract.toString()), 0); 152 | 153 | claimable = (await swapInstance.claimable.call(swapID)).valueOf(); 154 | assert.equal(claimable, false); 155 | refundable = (await swapInstance.refundable.call(swapID)).valueOf(); 156 | assert.equal(refundable, false); 157 | }); 158 | it('Test swap initiate, refund', async () => { 159 | const swapInstance = await ERC20AtomicSwapper.deployed(); 160 | const bnbInstance = await BNBToken.deployed(); 161 | 162 | const swapA = accounts[0]; 163 | const swapB = accounts[5]; 164 | 165 | const timestamp = Math.floor(Date.now()/1000); // counted by second 166 | const randomNumber = "0x5566778855667788556677885566778855667788556677885566778855667788"; 167 | const randomNumberHash = calculateRandomNumberHash(randomNumber, timestamp); 168 | const timelock = 100; 169 | const recipientAddr = swapB; 170 | const bep2Addr = "0xc9a2c4868f0f96faaa739b59934dc9cb304112ec"; 171 | const erc20Amount = 100000000; 172 | const bep2Amount = 100000000; 173 | const swapID = calculateSwapID(randomNumberHash, swapA, "0xdd365624c0fcc851ab935e3d39b4e1f6599f4d09"); 174 | 175 | var isSwapExist = (await swapInstance.isSwapExist.call(swapID)).valueOf(); 176 | assert.equal(isSwapExist, false); 177 | 178 | await bnbInstance.approve(ERC20AtomicSwapper.address, erc20Amount, { from: swapA }); 179 | let initiateTx = await swapInstance.htlt(randomNumberHash, timestamp, timelock, recipientAddr, "0xdd365624c0fcc851ab935e3d39b4e1f6599f4d09", bep2Addr, erc20Amount, bep2Amount, { from: swapA }); 180 | //SwapInit event should be emitted 181 | truffleAssert.eventEmitted(initiateTx, 'HTLT', (ev) => { 182 | return ev._msgSender === swapA && 183 | ev._recipientAddr === swapB && 184 | ev._bep2Addr === bep2Addr && 185 | ev._swapID === swapID && 186 | ev._randomNumberHash === randomNumberHash && 187 | Number(ev._timestamp.toString()) === timestamp && 188 | Number(ev._outAmount.toString()) === erc20Amount && 189 | Number(ev._bep2Amount.toString()) === bep2Amount; 190 | }); 191 | console.log("initiateTx gasUsed: ", initiateTx.receipt.gasUsed); 192 | 193 | isSwapExist = (await swapInstance.isSwapExist.call(swapID)).valueOf(); 194 | assert.equal(isSwapExist, true); 195 | var claimable = (await swapInstance.claimable.call(swapID)).valueOf(); 196 | assert.equal(claimable, true); 197 | var refundable = (await swapInstance.refundable.call(swapID)).valueOf(); 198 | assert.equal(refundable, false); 199 | 200 | 201 | // Just for producing new blocks 202 | for (var i = 0; i { 220 | return ev._msgSender === accounts[6] && ev._recipientAddr === swapA && ev._swapID === swapID && ev._randomNumberHash === randomNumberHash; 221 | }); 222 | console.log("refundTx gasUsed: ", refundTx.receipt.gasUsed); 223 | 224 | balanceOfSwapB = await bnbInstance.balanceOf.call(swapB); 225 | assert.equal(Number(balanceOfSwapB.toString()), 0); 226 | 227 | var balanceOfSwapANew = await bnbInstance.balanceOf.call(swapA); 228 | assert.equal(Number(balanceOfSwapANew.toString()), Number(balanceOfSwapA.toString()) + erc20Amount); 229 | 230 | var balanceOfSwapContract = await bnbInstance.balanceOf.call(ERC20AtomicSwapper.address); 231 | assert.equal(Number(balanceOfSwapContract.toString()), 0); 232 | 233 | claimable = (await swapInstance.claimable.call(swapID)).valueOf(); 234 | assert.equal(claimable, false); 235 | refundable = (await swapInstance.refundable.call(swapID)).valueOf(); 236 | assert.equal(refundable, false); 237 | }); 238 | }); -------------------------------------------------------------------------------- /ethereum/test/ETHAtomicSwapTest.js: -------------------------------------------------------------------------------- 1 | const ETHAtomicSwapper = artifacts.require("ETHAtomicSwapper"); 2 | const crypto = require('crypto'); 3 | const truffleAssert = require('truffle-assertions'); 4 | const Big = require('big.js'); 5 | 6 | function calculateRandomNumberHash (randomNumber, timestamp) { 7 | const timestampHexStr = timestamp.toString(16); 8 | var timestampHexStrFormat = timestampHexStr; 9 | // timestampHexStrFormat should be the hex string of a 32-length byte array. Fill 0 if the timestampHexStr length is less than 64 10 | for (var i = 0; i < 16 - timestampHexStr.length; i++) { 11 | timestampHexStrFormat = '0' + timestampHexStrFormat; 12 | } 13 | const timestampBytes = Buffer.from(timestampHexStrFormat, "hex"); 14 | const newBuffer = Buffer.concat([Buffer.from(randomNumber.substring(2, 66), "hex"), timestampBytes]); 15 | const hash = crypto.createHash('sha256'); 16 | hash.update(newBuffer); 17 | return "0x" + hash.digest('hex'); 18 | } 19 | 20 | function calculateSwapID(randomNumberHash, sender, recipient) { 21 | const newBuffer = Buffer.concat([Buffer.from(randomNumberHash.substring(2, 66), "hex"), Buffer.from(sender.substring(2, 42), "hex"), Buffer.from(recipient.substring(2, 42), "hex")]); 22 | const hash = crypto.createHash('sha256'); 23 | hash.update(newBuffer); 24 | return "0x" + hash.digest('hex'); 25 | } 26 | 27 | contract('Verify ETHAtomicSwapper', (accounts) => { 28 | it('Test swap initiate, claim', async () => { 29 | const swapInstance = await ETHAtomicSwapper.deployed(); 30 | 31 | const swapA = accounts[1]; 32 | const swapB = accounts[2]; 33 | 34 | const timestamp = Math.floor(Date.now()/1000); // counted by second 35 | const randomNumber = "0xaabbccddaabbccddaabbccddaabbccddaabbccddaabbccddaabbccddaabbccdd"; 36 | const randomNumberHash = calculateRandomNumberHash(randomNumber, timestamp); 37 | const timelock = 1000; 38 | const recipientAddr = swapB; 39 | const bep2Addr = "0xc9a2c4868f0f96faaa739b59934dc9cb304112ec"; 40 | const ETHCoin = 100000000; 41 | const bep2Amount = 100000000; 42 | const swapID = calculateSwapID(randomNumberHash, swapA, "0xdd365624c0fcc851ab935e3d39b4e1f6599f4d09"); 43 | 44 | var isSwapExist = (await swapInstance.isSwapExist.call(randomNumberHash)).valueOf(); 45 | assert.equal(isSwapExist, false); 46 | 47 | const initialbalanceOfSwapA = await web3.eth.getBalance(swapA); 48 | const initialbalanceOfSwapB = await web3.eth.getBalance(swapB); 49 | 50 | let initiateTx = await swapInstance.htlt(randomNumberHash, timestamp, timelock, recipientAddr, "0xdd365624c0fcc851ab935e3d39b4e1f6599f4d09", bep2Addr, bep2Amount, { from: swapA , value: ETHCoin}); 51 | //SwapInit event should be emitted 52 | truffleAssert.eventEmitted(initiateTx, 'HTLT', (ev) => { 53 | return ev._msgSender === swapA && 54 | ev._recipientAddr === swapB && 55 | ev._swapID === swapID && 56 | ev._bep2Addr === bep2Addr && 57 | ev._randomNumberHash === randomNumberHash && 58 | Number(ev._timestamp.toString()) === timestamp && 59 | Number(ev._outAmount.toString()) === ETHCoin && 60 | Number(ev._bep2Amount.toString()) === bep2Amount; 61 | }); 62 | 63 | // Verify if the swapped ERC20 token has been transferred to contract address 64 | var balanceOfSwapContract = await web3.eth.getBalance(ETHAtomicSwapper.address); 65 | assert.equal(Number(balanceOfSwapContract.toString()), ETHCoin); 66 | 67 | // querySwapByHashLock 68 | var swap = (await swapInstance.queryOpenSwap.call(swapID)).valueOf(); 69 | assert.equal(timestamp, swap._timestamp); 70 | assert.equal(ETHCoin, swap._outAmount); 71 | 72 | isSwapExist = (await swapInstance.isSwapExist.call(swapID)).valueOf(); 73 | assert.equal(isSwapExist, true); 74 | var claimable = (await swapInstance.claimable.call(swapID)).valueOf(); 75 | assert.equal(claimable, true); 76 | var refundable = (await swapInstance.refundable.call(swapID)).valueOf(); 77 | assert.equal(refundable, false); 78 | 79 | const gasUsed = initiateTx.receipt.gasUsed; 80 | const tx = await web3.eth.getTransaction(initiateTx.tx); 81 | const txFee = gasUsed * tx.gasPrice; 82 | console.log("initiateTx gasUsed: ", initiateTx.receipt.gasUsed); 83 | 84 | var balanceOfSwapA = await web3.eth.getBalance(swapA); 85 | assert.equal(balanceOfSwapA.toString(), new Big(initialbalanceOfSwapA).minus(ETHCoin).minus(txFee).toString()); 86 | var balanceOfSwapB = await web3.eth.getBalance(swapB); 87 | assert.equal(balanceOfSwapB, initialbalanceOfSwapB); 88 | 89 | // Anyone can call claim and the token will be paid to swapB address 90 | let claimTx = await swapInstance.claim(swapID, randomNumber, { from: accounts[6] }); 91 | //SwapComplete n event should be emitted 92 | truffleAssert.eventEmitted(claimTx, 'Claimed', (ev) => { 93 | return ev._msgSender === accounts[6] && ev._recipientAddr === swapB && ev._randomNumberHash === randomNumberHash && ev._swapID === swapID && ev._randomNumber === randomNumber; 94 | }); 95 | console.log("claimTx gasUsed: ", claimTx.receipt.gasUsed); 96 | 97 | balanceOfSwapB = await web3.eth.getBalance(swapB); 98 | assert.equal(balanceOfSwapB.toString(), new Big(initialbalanceOfSwapB).plus(ETHCoin).toString()); 99 | 100 | balanceOfSwapContract = await web3.eth.getBalance(ETHAtomicSwapper.address); 101 | assert.equal(Number(balanceOfSwapContract), 0); 102 | 103 | claimable = (await swapInstance.claimable.call(swapID)).valueOf(); 104 | assert.equal(claimable, false); 105 | refundable = (await swapInstance.refundable.call(swapID)).valueOf(); 106 | assert.equal(refundable, false); 107 | }); 108 | it('Test swap initiate, refund', async () => { 109 | const swapInstance = await ETHAtomicSwapper.deployed(); 110 | 111 | const swapA = accounts[3]; 112 | const swapB = accounts[4]; 113 | 114 | const timestamp = Math.floor(Date.now()/1000); // counted by second 115 | const randomNumber = "0x1122334411223344112233441122334411223344112233441122334411223344"; 116 | const randomNumberHash = calculateRandomNumberHash(randomNumber, timestamp); 117 | const timelock = 100; 118 | const recipientAddr = swapB; 119 | const bep2Addr = "0xc9a2c4868f0f96faaa739b59934dc9cb304112ec"; 120 | const ETHCoin = 100000000; 121 | const bep2Amount = 100000000; 122 | const swapID = calculateSwapID(randomNumberHash, swapA, "0xdd365624c0fcc851ab935e3d39b4e1f6599f4d09"); 123 | 124 | var isSwapExist = (await swapInstance.isSwapExist.call(swapID)).valueOf(); 125 | assert.equal(isSwapExist, false); 126 | 127 | const initialbalanceOfSwapA = await web3.eth.getBalance(swapA); 128 | const initialbalanceOfSwapB = await web3.eth.getBalance(swapB); 129 | 130 | let initiateTx = await swapInstance.htlt(randomNumberHash, timestamp, timelock, recipientAddr, "0xdd365624c0fcc851ab935e3d39b4e1f6599f4d09", bep2Addr, bep2Amount, { from: swapA , value: ETHCoin}); 131 | //SwapInit event should be emitted 132 | truffleAssert.eventEmitted(initiateTx, 'HTLT', (ev) => { 133 | return ev._msgSender === swapA && 134 | ev._recipientAddr === swapB && 135 | ev._swapID === swapID && 136 | ev._bep2Addr === bep2Addr && 137 | ev._randomNumberHash === randomNumberHash && 138 | Number(ev._timestamp.toString()) === timestamp && 139 | Number(ev._outAmount.toString()) === ETHCoin && 140 | Number(ev._bep2Amount.toString()) === bep2Amount; 141 | }); 142 | 143 | const gasUsed = initiateTx.receipt.gasUsed; 144 | const tx = await web3.eth.getTransaction(initiateTx.tx); 145 | const txFee = gasUsed * tx.gasPrice; 146 | console.log("initiateTx gasUsed: ", initiateTx.receipt.gasUsed); 147 | 148 | isSwapExist = (await swapInstance.isSwapExist.call(swapID)).valueOf(); 149 | assert.equal(isSwapExist, true); 150 | var claimable = (await swapInstance.claimable.call(swapID)).valueOf(); 151 | assert.equal(claimable, true); 152 | var refundable = (await swapInstance.refundable.call(swapID)).valueOf(); 153 | assert.equal(refundable, false); 154 | 155 | 156 | // Just for producing new blocks 157 | for (var i = 0; i { 174 | return ev._msgSender === accounts[6] && ev._recipientAddr === swapA && ev._swapID === swapID && ev._randomNumberHash === randomNumberHash; 175 | }); 176 | console.log("refundTx gasUsed: ", refundTx.receipt.gasUsed); 177 | 178 | var balanceOfSwapB = await web3.eth.getBalance(swapB); 179 | assert.equal(initialbalanceOfSwapB, balanceOfSwapB); 180 | 181 | var balanceOfSwapANew = await web3.eth.getBalance(swapA); 182 | assert.equal(balanceOfSwapANew, new Big(initialbalanceOfSwapA).minus(txFee)); 183 | 184 | var balanceOfSwapContract = await web3.eth.getBalance(ETHAtomicSwapper.address); 185 | assert.equal(Number(balanceOfSwapContract.toString()), 0); 186 | 187 | claimable = (await swapInstance.claimable.call(swapID)).valueOf(); 188 | assert.equal(claimable, false); 189 | refundable = (await swapInstance.refundable.call(swapID)).valueOf(); 190 | assert.equal(refundable, false); 191 | }); 192 | }); -------------------------------------------------------------------------------- /ethereum/truffle.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | networks: { 3 | test: { 4 | host: "127.0.0.1", 5 | port: 8545, 6 | network_id: "5555" 7 | } 8 | } 9 | }; --------------------------------------------------------------------------------