├── .gitignore ├── .env.example ├── contracts ├── TestToken.sol ├── TestWrappedJetton.sol ├── TonUtils.sol ├── SignatureChecker.sol ├── Bridge.sol └── TestTokenWithoutMetadata.sol ├── test ├── types │ └── TonTypes.ts ├── utils │ ├── constants.ts │ └── utils.ts ├── Token.test.ts └── Bridge.test.ts ├── tsconfig.json ├── scripts ├── deploy-test-token.ts ├── deploy-bridge.ts ├── deploy-mainnet-bridge.ts ├── call-lock.ts └── call-unlock.ts ├── hardhat.config.ts ├── package.json ├── README.md └── LICENSE /.gitignore: -------------------------------------------------------------------------------- 1 | node_modules 2 | .env 3 | coverage 4 | coverage.json 5 | typechain 6 | typechain-types 7 | 8 | #Hardhat files 9 | cache 10 | artifacts 11 | 12 | .idea -------------------------------------------------------------------------------- /.env.example: -------------------------------------------------------------------------------- 1 | ETHERSCAN_API_KEY=ABC123ABC123ABC123ABC123ABC123ABC1 2 | BSC_TESTNET_ENDPOINT=https://data-seed-prebsc-1-s1.binance.org:8545/ 3 | GOERLI_ENDPOINT=https://goerli.infura.io/v3/ 4 | PRIVATE_KEY=0xabc123abc123abc123abc123abc123abc123abc123abc123abc123abc123abc1 5 | -------------------------------------------------------------------------------- /contracts/TestToken.sol: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: MIT 2 | pragma solidity ^0.8.9; 3 | 4 | import "@openzeppelin/contracts/token/ERC20/ERC20.sol"; 5 | 6 | contract TestToken is ERC20 { 7 | constructor(uint256 totalSupply) ERC20("TestToken2", "TT2") { 8 | _mint(msg.sender, totalSupply); 9 | } 10 | } 11 | -------------------------------------------------------------------------------- /test/types/TonTypes.ts: -------------------------------------------------------------------------------- 1 | export type TonTxId = { 2 | address_hash: string; 3 | tx_hash: string; 4 | lt: number; 5 | }; 6 | 7 | export type SwapData = { 8 | receiver: string; 9 | token: string; 10 | amount: string; 11 | tx: TonTxId; 12 | }; 13 | 14 | export type Signature = { 15 | signer: string; 16 | signature: string; 17 | }; 18 | -------------------------------------------------------------------------------- /contracts/TestWrappedJetton.sol: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: MIT 2 | pragma solidity ^0.8.9; 3 | 4 | import "@openzeppelin/contracts/token/ERC20/ERC20.sol"; 5 | 6 | contract TestWrappedJetton is ERC20 { 7 | constructor() ERC20("TestWrappedJetton", "TWJ") { 8 | _mint(msg.sender, 2000000e18); 9 | } 10 | 11 | function isWrappedJetton() external pure returns (bool) { 12 | return true; 13 | } 14 | } 15 | -------------------------------------------------------------------------------- /test/utils/constants.ts: -------------------------------------------------------------------------------- 1 | 2 | export const TON_WORKCHAIN = 0; 3 | export const TON_ADDRESS_HASH = 4 | "0x2175818712088C0A5F087DF2594A41CB5CB29689EB60FC59F6848D752AF11498"; 5 | export const JETTON_ADDRESS_HASH = "0xc59b4cf486c0122ebef00f7c4888015fd749b2fac63e014d33bcc5f74131060a"; 6 | export const TON_TX_HASH = 7 | "0x6C79A5432D988FFAD699E60C4A6E9C7E191CBE5A1BD199294C1F3361D0893359"; 8 | export const TON_TX_LT = 19459352000003; 9 | 10 | export const CHAIN_ID = 31337; -------------------------------------------------------------------------------- /tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "compilerOptions": { 3 | "target": "es2018", 4 | "module": "commonjs", 5 | "declaration": true, 6 | "declarationMap": true, 7 | "sourceMap": true, 8 | "strict": true, 9 | "esModuleInterop": true, 10 | "outDir": "dist", 11 | "rootDirs": ["./src", "./scripts", "./test"], 12 | }, 13 | "include": ["./test", "./src", "./scripts"], 14 | "files": [ 15 | "./hardhat.config.ts" 16 | ] 17 | } -------------------------------------------------------------------------------- /scripts/deploy-test-token.ts: -------------------------------------------------------------------------------- 1 | import "@nomiclabs/hardhat-ethers"; 2 | import { parseEther } from "ethers/lib/utils"; 3 | import { ethers } from "hardhat"; 4 | 5 | async function main() { 6 | const [owner] = await ethers.getSigners(); 7 | 8 | const TestToken = await ethers.getContractFactory("TestToken"); 9 | const token = await TestToken.deploy("1000000000000000000000000000000"); 10 | await token.deployed(); 11 | 12 | console.log("token deployed to ", token.address); 13 | } 14 | 15 | main().catch((error) => { 16 | console.error(error); 17 | process.exitCode = 1; 18 | }); 19 | -------------------------------------------------------------------------------- /contracts/TonUtils.sol: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: GPL-3.0 2 | pragma solidity ^0.8.9; 3 | 4 | interface TonUtils { 5 | struct TonTxID { 6 | bytes32 address_hash; // sender user address 7 | bytes32 tx_hash; // transaction hash on bridge smart contract 8 | uint64 lt; // transaction LT (logical time) on bridge smart contract 9 | } 10 | 11 | struct SwapData { 12 | address receiver; // user's EVM-address to receive tokens 13 | address token; // ERC-20 token address 14 | uint256 amount; // token amount in units to receive in EVM-network 15 | TonTxID tx; 16 | } 17 | 18 | struct Signature { 19 | address signer; // oracle's EVM-address 20 | bytes signature; // oracle's signature 21 | } 22 | } 23 | -------------------------------------------------------------------------------- /test/Token.test.ts: -------------------------------------------------------------------------------- 1 | import { expect } from "chai"; 2 | import { formatUnits } from "ethers/lib/utils"; 3 | import { ethers } from "hardhat"; 4 | import {BigNumber} from "ethers"; 5 | 6 | describe("TestToken contract", function () { 7 | it("Deployment should assign the total supply of tokens to the owner", async function () { 8 | const [owner] = await ethers.getSigners(); 9 | 10 | const TestToken = await ethers.getContractFactory("TestToken"); 11 | const hardhatToken = await TestToken.deploy(BigNumber.from('2000000').mul(BigNumber.from(10).pow(BigNumber.from(18)))); 12 | 13 | const ownerBalance = await hardhatToken.balanceOf(owner.address); 14 | const totalSupply = await hardhatToken.totalSupply(); 15 | expect(formatUnits(totalSupply)).to.equal(formatUnits(ownerBalance)); 16 | }); 17 | }); 18 | -------------------------------------------------------------------------------- /scripts/deploy-bridge.ts: -------------------------------------------------------------------------------- 1 | import "@nomiclabs/hardhat-ethers"; 2 | import {ethers} from "hardhat"; 3 | 4 | async function main() { 5 | const [owner] = await ethers.getSigners(); 6 | 7 | const Bridge = await ethers.getContractFactory("Bridge"); 8 | const oracles = [ 9 | '0xeb05E1B6AC0d574eF2CF29FDf01cC0bA3D8F9Bf1', 10 | '0xe54CD631C97bE0767172AD16904688962d09d2FE', 11 | '0xF636f40Ebe17Fb2A1343e5EEee9D13AA90888b51' 12 | ]; 13 | const bridge = await Bridge.deploy(oracles); 14 | // const bridge = Bridge.attach('0xF113b76E11c738C1De215115B24f5aBe2aE104D9'); 15 | await bridge.deployed(); 16 | // console.log('oracles ', await bridge.getOracleSet()); 17 | 18 | console.log("bridge deployed to ", bridge.address); // 0xF113b76E11c738C1De215115B24f5aBe2aE104D9 19 | } 20 | 21 | main().catch((error) => { 22 | console.error(error); 23 | process.exitCode = 1; 24 | }); 25 | -------------------------------------------------------------------------------- /scripts/deploy-mainnet-bridge.ts: -------------------------------------------------------------------------------- 1 | import "@nomiclabs/hardhat-ethers"; 2 | import {ethers} from "hardhat"; 3 | 4 | async function main() { 5 | const [owner] = await ethers.getSigners(); 6 | 7 | const Bridge = await ethers.getContractFactory("Bridge"); 8 | const oracles = [ 9 | '0x3154E640c56D023a98890426A24D1A772f5A38B2', // 0 10 | '0x8B06A5D37625F41eE9D9F543482b6562C657EA6F', // 1 11 | '0x6D5E361F7E15ebA73e41904F4fB2A7d2ca045162', // 2 12 | '0x43931B8c29e34a8C16695408CD56327F511Cf086', // 3 13 | '0x7a0d3C42f795BA2dB707D421Add31deda9F1fEc1', // 4 14 | '0x88352632350690EF22F9a580e6B413c747c01FB2', // 5 15 | '0xeB8975966dAF0C86721C14b8Bb7DFb89FCBB99cA', // 6 16 | '0x48Bf4a783ECFb7f9AACab68d28B06fDafF37ac43', // 7 17 | '0x954AE64BB0268b06ffEFbb6f454867a5F2CB3177' // 8 18 | ]; 19 | const bridge = await Bridge.deploy(oracles); 20 | await bridge.deployed(); 21 | 22 | console.log("bridge deployed to ", bridge.address); 23 | } 24 | 25 | main().catch((error) => { 26 | console.error(error); 27 | process.exitCode = 1; 28 | }); 29 | -------------------------------------------------------------------------------- /scripts/call-lock.ts: -------------------------------------------------------------------------------- 1 | import "@nomiclabs/hardhat-ethers"; 2 | import { formatEther, parseEther, parseUnits } from "ethers/lib/utils"; 3 | import { ethers } from "hardhat"; 4 | 5 | const TON_ADDRESS_HASH = 6 | "0x68a32603bc376542ac01fc6266cd1bea8a6bab324c84ea337b7c00833a48dc7a"; 7 | 8 | async function main() { 9 | console.log("starting lock script"); 10 | const [owner] = await ethers.getSigners(); 11 | 12 | const bridge = await ethers.getContractAt( 13 | "Bridge", 14 | "0x896B65f1f1078104F0d0d4723bc371bCF173B60F" 15 | ); 16 | const token = await ethers.getContractAt( 17 | "TestToken", 18 | "0x6cB26573dacd1994BADfDa4CBcC7553AcafFf8d6" 19 | ); 20 | 21 | const bridgeAllowance = parseUnits("12"); 22 | 23 | // await token.transfer(); 24 | console.log(await token.balanceOf(owner.address)); 25 | 26 | console.log(`approve ${formatEther(bridgeAllowance)} tokens to bridge`); 27 | let tx = await token.approve(bridge.address, bridgeAllowance); 28 | await tx.wait(); 29 | console.log("approval successfull"); 30 | console.log(`lock ${formatEther(bridgeAllowance)} tokens in bridge by owner`); 31 | tx = await bridge.lock(token.address, bridgeAllowance, TON_ADDRESS_HASH); 32 | await tx.wait(); 33 | console.log("successfully locked"); 34 | } 35 | 36 | main().catch((error) => { 37 | console.error(error); 38 | process.exitCode = 1; 39 | }); 40 | -------------------------------------------------------------------------------- /hardhat.config.ts: -------------------------------------------------------------------------------- 1 | import * as dotenv from "dotenv"; 2 | 3 | import { HardhatUserConfig, task } from "hardhat/config"; 4 | import "@nomiclabs/hardhat-etherscan"; 5 | import "@nomiclabs/hardhat-waffle"; 6 | import "@typechain/hardhat"; 7 | import "hardhat-gas-reporter"; 8 | import "solidity-coverage"; 9 | 10 | dotenv.config(); 11 | 12 | // This is a sample Hardhat task. To learn how to create your own go to 13 | // https://hardhat.org/guides/create-task.html 14 | task("accounts", "Prints the list of accounts", async (taskArgs, hre) => { 15 | const accounts = await hre.ethers.getSigners(); 16 | 17 | for (const account of accounts) { 18 | console.log(account.address); 19 | } 20 | }); 21 | 22 | // You need to export an object to set up your config 23 | // Go to https://hardhat.org/config/ to learn more 24 | 25 | const config: HardhatUserConfig = { 26 | solidity: { 27 | version: "0.8.9", 28 | settings: { 29 | optimizer: { 30 | enabled: true, 31 | runs: 5000, 32 | }, 33 | }, 34 | }, 35 | networks: { 36 | hardhat: { 37 | accounts: { 38 | count: 100, 39 | }, 40 | }, 41 | ethereum: { 42 | url: process.env.ETH_ENDPOINT || "", 43 | accounts: 44 | [process.env.PRIVATE_KEY || ''], 45 | }, 46 | goerli: { 47 | url: process.env.GOERLI_ENDPOINT || "", 48 | accounts: 49 | [process.env.PRIVATE_KEY || ''], 50 | }, 51 | bsc_testnet: { 52 | url: process.env.BSC_TESTNET_ENDPOINT || "", 53 | accounts: [process.env.PRIVATE_KEY || ''] 54 | }, 55 | local: { 56 | url: "http://127.0.0.1:8545/", 57 | chainId: 31337, 58 | }, 59 | }, 60 | etherscan: { 61 | apiKey: process.env.ETHERSCAN_API_KEY, 62 | }, 63 | }; 64 | 65 | export default config; 66 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "token-bridge-solidity", 3 | "version": "1.0.0", 4 | "description": "", 5 | "main": "index.js", 6 | "scripts": { 7 | "test": "npx hardhat test", 8 | "deploy-test-token-goerli": "npx hardhat run ./scripts/deploy-test-token.ts --network goerli", 9 | "deploy-test-token-bsc-testnet": "npx hardhat run ./scripts/deploy-test-token.ts --network bsc_testnet", 10 | "deploy-bridge-goerli": "npx hardhat run ./scripts/deploy-bridge.ts --network goerli", 11 | "deploy-bridge-bsc-testnet": "npx hardhat run ./scripts/deploy-bridge.ts --network bsc_testnet", 12 | "deploy-bridge-ethereum": "npx hardhat run ./scripts/deploy-mainnet-bridge.ts --network ethereum", 13 | "triple-lock": "npx hardhat run ./scripts/call-lock.ts --network bsc_testnet && npx hardhat run ./scripts/call-lock.ts --network bsc_testnet && npx hardhat run ./scripts/call-lock.ts --network bsc_testnet", 14 | "lock-bnb": "npx hardhat run ./scripts/call-lock.ts --network bsc_testnet", 15 | "unlock": "npx hardhat run ./scripts/call-unlock.ts --network bsc_testnet" 16 | }, 17 | "author": "", 18 | "license": "GPL-3.0", 19 | "dependencies": { 20 | "@nomiclabs/hardhat-waffle": "^2.0.3", 21 | "@openzeppelin/contracts": "4.8.2", 22 | "dotenv": "^16.0.1", 23 | "tonweb": "^0.0.54", 24 | "hardhat": "^2.10.0", 25 | "web3": "^1.7.4" 26 | }, 27 | "devDependencies": { 28 | "@ethersproject/providers": "^5.6.8", 29 | "@nomicfoundation/hardhat-chai-matchers": "^1.0.1", 30 | "@nomicfoundation/hardhat-network-helpers": "^1.0.2", 31 | "@nomicfoundation/hardhat-toolbox": "^1.0.2", 32 | "@nomiclabs/hardhat-ethers": "^2.1.0", 33 | "@nomiclabs/hardhat-etherscan": "^3.1.0", 34 | "@typechain/ethers-v5": "^10.1.0", 35 | "@typechain/hardhat": "^6.1.2", 36 | "@types/mocha": "^9.1.1", 37 | "chai": "^4.3.6", 38 | "ethers": "^5.6.9", 39 | "hardhat": "^2.8.4", 40 | "hardhat-gas-reporter": "^1.0.8", 41 | "mocha": "^10.0.0", 42 | "solidity-coverage": "^0.7.21", 43 | "ts-node": ">=8.0.0", 44 | "typechain": "^8.1.0", 45 | "typescript": ">=4.5.0" 46 | } 47 | } -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # token-bridge-solidity 2 | 3 | TON-EVM token bridge - Solidity smart contracts 4 | 5 | Developed by [RSquad](https://rsquad.io/) by order of TON Foundation. 6 | 7 | ## Run tests 8 | 9 | ```bash 10 | npm run test 11 | ``` 12 | 13 | ## Deploy 14 | 15 | > ⚠️ NOTE: It is preferable that the number of oracles will be divisible by 3. 16 | > Note that in other cases minimum consensus is `floor((2 * oracleSet.length + 2) / 3)`. For example, 4 oracles required 3 signatures. It's different from the Toncoin bridge. 17 | 18 | change `PRIVATE_KEY`, `GOERLI_ENDPOINT`, `BSC_TESTNET_ENDPOINT` in `.env`. 19 | 20 | Ethereum Goerli Testnet: 21 | 22 | ```bash 23 | npm run deploy-test-token-goerli 24 | npm run deploy-bridge-goerli 25 | ``` 26 | 27 | BSC Testnet: 28 | 29 | ```bash 30 | npm run deploy-test-token-bsc-testnet 31 | npm run deploy-bridge-bsc-testnet 32 | ``` 33 | 34 | ## Code 35 | 36 | Based on [Toncoin Bridge](https://github.com/ton-blockchain/bridge-solidity/tree/58bf778e984f2a6c17bc2b24f4647fb66705e705). 37 | 38 | * `TonUtils` - `token` address added to `SwapData`. `TonAddress` replaced by `address_hash` because all tokens and token owners in workchain = 0. 39 | 40 | * `SignatureChecker` - `getSwapDataId`: `token` address and `chainId` added to signing data, `workchain` removed from signing data; 'uint64 amount' -> 'uint256 amount'; cosmetic changes; 41 | 42 | `checkSignature` 43 | 44 | `getNewSetId` - `chainId` added to signing data 45 | 46 | `getNewBurnStatusId` renamed to `getNewLockStatusId`, `chainId` added to signing data 47 | 48 | * `Bridge` 49 | 50 | `SwapEthToTon` renamed to `Lock`, some fields removed and added 51 | 52 | `SwapTonToEth` renamed to `Unlock`, some fields removed and added 53 | 54 | `burn` -> `lock`, new functionality 55 | 56 | `voteForMinting` -> `unlock`, new functionality 57 | 58 | `voteForNewOracleSet` same 59 | 60 | `allowBurn` renamed to `allowLock` 61 | 62 | `voteForSwitchBurn` renamed to `voteForSwitchLock` 63 | 64 | cosmetic changes and optimizations 65 | 66 | # Etherscan code verification 67 | 68 | Add to the end of `hardhat.config.ts` you api key of etherscan or bscscan 69 | 70 | ``` 71 | etherscan: { 72 | apiKey: "123ABC", 73 | }, 74 | ``` 75 | 76 | Use `etherscan` field for bscscan too. 77 | 78 | Make `arguments.js` file with init oracle addresses array: 79 | 80 | ```js 81 | module.exports = [ 82 | [ 83 | '0xeb05E1B6AC0d574eF2CF29FDf01cC0bA3D8F9Bf1', 84 | '0xe54CD631C97bE0767172AD16904688962d09d2FE', 85 | '0xF636f40Ebe17Fb2A1343e5EEee9D13AA90888b51' 86 | ] 87 | ]; 88 | ``` 89 | 90 | Run command 91 | 92 | ```basb 93 | npx hardhat verify --network bsc_testnet --constructor-args arguments.js 0xADDRESS_OF_BRIDGE_CONTRACT 94 | ``` -------------------------------------------------------------------------------- /contracts/SignatureChecker.sol: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: GPL-3.0 2 | pragma solidity ^0.8.9; 3 | 4 | import "./TonUtils.sol"; 5 | 6 | contract SignatureChecker is TonUtils { 7 | function checkSignature(bytes32 digest, Signature memory sig) public pure { 8 | require(sig.signer != address(0), "ECDSA: zero signer"); // The `ecrecover` function returns zero on failure, so if sig.signer == 0 then any signature will be accepted regardless of whether it is cryptographically valid. 9 | require(sig.signature.length == 65, "ECDSA: invalid signature length"); 10 | // Divide the signature in r, s and v variables 11 | bytes32 r; 12 | bytes32 s; 13 | uint8 v; 14 | 15 | bytes memory signature = sig.signature; 16 | 17 | // ecrecover takes the signature parameters, and the only way to get them 18 | // currently is to use assembly. 19 | // solhint-disable-next-line no-inline-assembly 20 | assembly { 21 | r := mload(add(signature, 0x20)) 22 | s := mload(add(signature, 0x40)) 23 | v := byte(0, mload(add(signature, 0x60))) 24 | } 25 | 26 | require( 27 | uint256(s) <= 0x7FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF5D576E7357A4501DDFE92F46681B20A0, 28 | "ECDSA: invalid signature 's' value" 29 | ); 30 | 31 | require(v == 27 || v == 28, "ECDSA: invalid signature 'v' value"); 32 | 33 | bytes memory prefix = "\x19Ethereum Signed Message:\n32"; 34 | bytes32 prefixedHash = keccak256(abi.encodePacked(prefix, digest)); 35 | require( 36 | ecrecover(prefixedHash, v, r, s) == sig.signer, 37 | "Wrong signature" 38 | ); 39 | } 40 | 41 | function getSwapDataId(SwapData memory data) 42 | public 43 | view 44 | returns (bytes32 result) 45 | { 46 | result = keccak256( 47 | abi.encode( 48 | 0xDA7A, 49 | address(this), 50 | block.chainid, 51 | data.receiver, 52 | data.token, 53 | data.amount, 54 | data.tx.address_hash, 55 | data.tx.tx_hash, 56 | data.tx.lt 57 | ) 58 | ); 59 | } 60 | 61 | function getNewSetId(uint256 oracleSetHash, address[] memory set) 62 | public 63 | view 64 | returns (bytes32 result) 65 | { 66 | result = keccak256( 67 | abi.encode(0x5e7, address(this), block.chainid, oracleSetHash, set) 68 | ); 69 | } 70 | 71 | function getNewLockStatusId(bool newLockStatus, uint256 nonce) 72 | public 73 | view 74 | returns (bytes32 result) 75 | { 76 | result = keccak256( 77 | abi.encode(0xB012, address(this), block.chainid, newLockStatus, nonce) 78 | ); 79 | } 80 | 81 | function getNewDisableToken(bool isDisable, address tokenAddress, uint256 nonce) 82 | public 83 | view 84 | returns (bytes32 result) 85 | { 86 | result = keccak256( 87 | abi.encode(0xD15A, address(this), block.chainid, isDisable, tokenAddress, nonce) 88 | ); 89 | } 90 | } 91 | -------------------------------------------------------------------------------- /scripts/call-unlock.ts: -------------------------------------------------------------------------------- 1 | import TonWeb from "tonweb"; 2 | import { ethers } from "hardhat"; 3 | import { Signature, SwapData } from "./types/TonTypes"; 4 | 5 | const decToHex = (dec: string): string => 6 | "0x" + new TonWeb.utils.BN(dec).toString(16); 7 | export interface EthSignature { 8 | publicKey: string; // secp_key in hex 9 | r: string; // 256bit in hex 10 | s: string; // 256bit in hex 11 | v: number; // 8bit 12 | } 13 | 14 | export const tonweb = new TonWeb( 15 | new TonWeb.HttpProvider(process.env.HTTP_PROVIDER_API_ROOT, { 16 | apiKey: process.env.HTTP_PROVIDER_API_KEY, 17 | }) 18 | ); 19 | 20 | export const parseEthSignature = (data: any): EthSignature => { 21 | const tuple: any[] = data.tuple.elements; 22 | const publicKey: string = decToHex(tuple[0].number.number); 23 | 24 | const rsv: any[] = tuple[1].tuple.elements; 25 | const r: string = decToHex(rsv[0].number.number); 26 | const s: string = decToHex(rsv[1].number.number); 27 | const v: number = Number(rsv[2].number.number); 28 | return { 29 | publicKey, 30 | r, 31 | s, 32 | v, 33 | }; 34 | }; 35 | 36 | export let prepareSwapData = ( 37 | receiver: string, 38 | token: string, 39 | amount: string, 40 | address_hash: string, 41 | tx_hash: any, 42 | lt: any 43 | ) => { 44 | // if (lt == TON_TX_LT) { 45 | // lt = lt + Math.ceil(Date.now() / 1000) + Math.ceil(10000 * Math.random()); 46 | // } 47 | let swapData: SwapData = { 48 | receiver, 49 | token, 50 | amount, 51 | tx: { 52 | address_hash, 53 | tx_hash, 54 | lt, 55 | }, 56 | }; 57 | return swapData; 58 | }; 59 | 60 | (async () => { 61 | const [owner] = await ethers.getSigners(); 62 | const addressBnb = "0x7644d43c5b479dAF2b471B83cF2DF29A1DFA2D37"; 63 | const addressLocal = "0x5FbDB2315678afecb367f032d93F642f64180aa3"; 64 | const bridgeEth = await ethers.getContractAt("Bridge", addressBnb); 65 | 66 | // const config = fs.readFileSync(`./artifacts/hr/system.json`, {encoding: 'utf8'}); 67 | const votesCollectorAddress = 68 | "UQCuzvIOXLjH2tv35gY4tzhIvXCqZWDuK9kUhFGXKLImg0k8"; 69 | const burnId = 70 | "1495c43d81ecba7fd7b49d3ec529d60d9080a1e9f8c74ded81a4e20068665160"; 71 | const intBurnId = new TonWeb.utils.BN(burnId, 16); 72 | 73 | const signatures: Signature[] = [ 74 | { 75 | signer: "0x89D12eBB0cDcb3Fe00045c9D97D8AbFC5F6c497e", 76 | signature: 77 | "0xdb917f026163f2f328a6aec7fa00c023f5c1397f71593956a95d9cf7f3e8b2ad29629aae531a62be620fb65e56ea0d454840f27860688b821e3849087c7afa231c", 78 | }, 79 | ]; 80 | 81 | const reciever = "0xa846bc19e8ab8bb0e0bf386853d8c5e199f0af9b"; 82 | const token = "0xc954f9239950ef7d278c91e9fb8416469d7c104f"; 83 | const amount = "1000000000000000000"; 84 | const ton_address_hash = "0x8ebd203c0030e59d5f1bca8006cbb0c73a7627d047b6d5fb3322124f96a36c8e"; 85 | const tx_hash = 86 | "0x6375001987b4813d0284a2d42580c98557bc6d3085977dbc0b5f278e957404ba"; 87 | const lt = "3444241000001"; 88 | 89 | const swapData = prepareSwapData( 90 | reciever, 91 | token, 92 | amount, 93 | ton_address_hash, 94 | tx_hash, 95 | lt 96 | ); 97 | 98 | // const address = ethers.utils.recoverAddress(burnId, '0x547d9b7f3c95bcd376e53a06a41d140e2792758aa835fc7e8c67162fb4a6d82548023e670d2bc07415be52f3ba9802b63704c33412ab6c34d1aaece0bbe151451c') 99 | // console.log({address}); 100 | 101 | const tx = await bridgeEth.unlock(swapData, signatures); 102 | await tx.wait(); 103 | 104 | console.log("burn done"); 105 | })(); 106 | -------------------------------------------------------------------------------- /test/utils/utils.ts: -------------------------------------------------------------------------------- 1 | import { ethers, utils } from "ethers"; 2 | import { Account } from "web3/eth/accounts"; 3 | import { SwapData, Signature } from "../types/TonTypes"; 4 | import {CHAIN_ID} from "./constants"; 5 | 6 | export let prepareSwapData = ( 7 | receiver: string, 8 | token: string, 9 | amount: any, 10 | address_hash: string, 11 | tx_hash: any, 12 | lt: any 13 | ) => { 14 | let swapData: SwapData = { 15 | receiver: receiver, 16 | token: token, 17 | amount: amount, 18 | tx: { 19 | address_hash: address_hash, 20 | tx_hash: tx_hash, 21 | lt: lt, 22 | }, 23 | }; 24 | return swapData; 25 | }; 26 | 27 | export let signHash = (hash: any, account: Account) => { 28 | let signature = account.sign(hash).signature; 29 | let result: Signature = { 30 | signer: account.address, 31 | signature: signature, 32 | }; 33 | return result; 34 | }; 35 | 36 | let compareSignatures = (a: Signature, b: Signature): number => { 37 | if (parseInt(a.signer) > parseInt(b.signer)) return 1; 38 | if (parseInt(a.signer) == parseInt(b.signer)) { 39 | return 0; 40 | } else return -1; 41 | } 42 | 43 | export let signSwapData = (swapData: SwapData, accounts: Account[], target: any) => { 44 | let signatures: Signature[] = new Array(); 45 | for (const account in accounts) { 46 | if (Object.prototype.hasOwnProperty.call(accounts, account)) { 47 | const element = accounts[account]; 48 | signatures.push(signHash(hashData(encodeSwapData(swapData, target)), element)); 49 | } 50 | } 51 | signatures.sort(compareSignatures); 52 | return signatures; 53 | }; 54 | 55 | export let signUpdateLockStatus = (newLockStatus: boolean, nonce: number, accounts: Account[], target: any) => { 56 | let signatures: Signature[] = new Array(); 57 | for (const account in accounts) { 58 | if (Object.prototype.hasOwnProperty.call(accounts, account)) { 59 | const element = accounts[account]; 60 | signatures.push(signHash(hashData(encodeNewLockStatus(newLockStatus, nonce, target)), element)); 61 | } 62 | } 63 | signatures.sort(compareSignatures); 64 | return signatures; 65 | }; 66 | 67 | export let signDisableToken = (isDisable: boolean, tokenAddress: string, nonce: number, accounts: Account[], target: any) => { 68 | let signatures: Signature[] = new Array(); 69 | for (const account in accounts) { 70 | if (Object.prototype.hasOwnProperty.call(accounts, account)) { 71 | const element = accounts[account]; 72 | signatures.push(signHash(hashData(encodeNewDisableToken(isDisable, tokenAddress, nonce, target)), element)); 73 | } 74 | } 75 | signatures.sort(compareSignatures); 76 | return signatures; 77 | }; 78 | 79 | export let signUpdateOracleData = ( 80 | oracleSetHash: string, 81 | newOracleSet: string[], 82 | accounts: Account[], 83 | target: any 84 | ) => { 85 | let signatures: Signature[] = new Array(); 86 | for (const account in accounts) { 87 | if (Object.prototype.hasOwnProperty.call(accounts, account)) { 88 | const signer = accounts[account]; 89 | signatures.push(signHash( 90 | hashData(encodeUpdateOracleData(oracleSetHash, newOracleSet, target)), 91 | signer 92 | )); 93 | } 94 | } 95 | signatures.sort(compareSignatures); 96 | return signatures; 97 | }; 98 | 99 | export let encodeUpdateOracleData = ( 100 | oracleSetHash: string, 101 | newOracleSet: string[], 102 | target: any 103 | ) => { 104 | return ethers.utils.defaultAbiCoder.encode( 105 | ["int", "address", "uint256", "uint256", "address[]"], 106 | [0x5e7, target, CHAIN_ID, oracleSetHash, newOracleSet] 107 | ); 108 | }; 109 | 110 | export let encodeNewLockStatus = ( 111 | newLockStatus: boolean, 112 | nonce: number, 113 | target: any 114 | ) => { 115 | return ethers.utils.defaultAbiCoder.encode( 116 | ["int", "address", "uint256", "bool", "uint256"], 117 | [0xB012, target, CHAIN_ID, newLockStatus, nonce] 118 | ); 119 | }; 120 | 121 | export let encodeNewDisableToken = ( 122 | isDisable: boolean, 123 | tokenAddress: string, 124 | nonce: number, 125 | target: any 126 | ) => { 127 | return ethers.utils.defaultAbiCoder.encode( 128 | ["int", "address", "uint256", "bool", "address", "uint256"], 129 | [0xD15A, target, CHAIN_ID, isDisable, tokenAddress, nonce] 130 | ); 131 | }; 132 | 133 | export let encodeSwapData = (d: SwapData, target: any) => { 134 | return ethers.utils.defaultAbiCoder.encode( 135 | [ 136 | "int", 137 | "address", 138 | "uint256", 139 | "address", 140 | "address", 141 | "uint256", 142 | "bytes32", 143 | "bytes32", 144 | "uint64", 145 | ], 146 | [ 147 | 0xda7a, 148 | target, 149 | CHAIN_ID, 150 | d.receiver, 151 | d.token, 152 | d.amount, 153 | d.tx.address_hash, 154 | d.tx.tx_hash, 155 | d.tx.lt, 156 | ] 157 | ); 158 | }; 159 | 160 | export let hashData = (encoded: any) => { 161 | return ethers.utils.keccak256(encoded); 162 | }; 163 | 164 | export let encodeOracleSet = (oracleSet: string[]) => { 165 | return ethers.utils.defaultAbiCoder.encode(["address[]"], [oracleSet]); 166 | }; -------------------------------------------------------------------------------- /contracts/Bridge.sol: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: GPL-3.0 2 | pragma solidity ^0.8.9; 3 | 4 | import "@openzeppelin/contracts/token/ERC20/IERC20.sol"; 5 | import "@openzeppelin/contracts/token/ERC20/utils/SafeERC20.sol"; 6 | import "./SignatureChecker.sol"; 7 | import "@openzeppelin/contracts/security/ReentrancyGuard.sol"; 8 | 9 | interface IWrappedJetton { 10 | function isWrappedJetton() external pure returns (bool); 11 | } 12 | 13 | interface IDecimals { 14 | function decimals() external view returns (uint8); 15 | } 16 | 17 | contract Bridge is SignatureChecker, ReentrancyGuard { 18 | using SafeERC20 for IERC20; 19 | address[] oracleSet; 20 | mapping(address => bool) public isOracle; 21 | mapping(address => bool) public disabledTokens; 22 | mapping(bytes32 => bool) public finishedVotings; 23 | bool public allowLock; 24 | 25 | event Lock( 26 | address indexed from, 27 | address indexed token, 28 | bytes32 indexed to_addr_hash, 29 | uint256 value, 30 | uint256 new_bridge_balance, 31 | uint8 decimals 32 | ); 33 | event Unlock( 34 | address indexed token, 35 | bytes32 ton_address_hash, 36 | bytes32 indexed ton_tx_hash, 37 | uint64 lt, 38 | address indexed to, 39 | uint256 value, 40 | uint256 new_bridge_balance 41 | ); 42 | event NewOracleSet(uint256 oracleSetHash, address[] newOracles); 43 | 44 | constructor(address[] memory initialSet) { 45 | _updateOracleSet(0, initialSet); 46 | disabledTokens[address(0)] = true; 47 | disabledTokens[address(0x582d872A1B094FC48F5DE31D3B73F2D9bE47def1)] = true; // wrapped toncoin 48 | disabledTokens[address(0x76A797A59Ba2C17726896976B7B3747BfD1d220f)] = true; // wrapped toncoin 49 | } 50 | 51 | function _generalVote(bytes32 digest, Signature[] memory signatures) 52 | internal 53 | view 54 | { 55 | require( 56 | signatures.length >= (2 * oracleSet.length + 2) / 3, 57 | "Not enough signatures" 58 | ); 59 | require(!finishedVotings[digest], "Vote is already finished"); 60 | uint256 signum = signatures.length; 61 | uint256 last_signer = 0; 62 | for (uint256 i = 0; i < signum; i++) { 63 | address signer = signatures[i].signer; 64 | require(isOracle[signer], "Unauthorized signer"); 65 | uint256 next_signer = uint256(uint160(signer)); 66 | require(next_signer > last_signer, "Signatures are not sorted"); 67 | last_signer = next_signer; 68 | checkSignature(digest, signatures[i]); 69 | } 70 | } 71 | 72 | function lock( 73 | address token, 74 | uint256 amount, 75 | bytes32 to_address_hash 76 | ) external nonReentrant { 77 | require(allowLock, "Lock is currently disabled"); 78 | require(!disabledTokens[token], "lock: disabled token"); 79 | require(!checkTokenIsWrappedJetton(token), "lock wrapped jetton"); 80 | 81 | uint256 oldBalance = IERC20(token).balanceOf(address(this)); 82 | 83 | IERC20(token).safeTransferFrom(msg.sender, address(this), amount); 84 | 85 | uint256 newBalance = IERC20(token).balanceOf(address(this)); 86 | 87 | require(newBalance > oldBalance, "newBalance must be greater than oldBalance"); 88 | 89 | require(newBalance <= 2 ** 120 - 1, "Max jetton totalSupply 2 ** 120 - 1"); 90 | 91 | emit Lock( 92 | msg.sender, 93 | token, 94 | to_address_hash, 95 | newBalance - oldBalance, 96 | newBalance, 97 | getDecimals(token) 98 | ); 99 | } 100 | 101 | function unlock(SwapData calldata data, Signature[] calldata signatures) 102 | external nonReentrant 103 | { 104 | bytes32 _id = getSwapDataId(data); 105 | _generalVote(_id, signatures); 106 | finishedVotings[_id] = true; 107 | IERC20(data.token).safeTransfer(data.receiver, data.amount); 108 | uint256 newBalance = IERC20(data.token).balanceOf(address(this)); 109 | emit Unlock(data.token, data.tx.address_hash, data.tx.tx_hash, data.tx.lt, data.receiver, data.amount, newBalance); 110 | } 111 | 112 | function voteForNewOracleSet( 113 | uint256 oracleSetHash, 114 | address[] calldata newOracles, 115 | Signature[] calldata signatures 116 | ) external { 117 | bytes32 _id = getNewSetId(oracleSetHash, newOracles); 118 | _generalVote(_id, signatures); 119 | finishedVotings[_id] = true; 120 | _updateOracleSet(oracleSetHash, newOracles); 121 | } 122 | 123 | function voteForSwitchLock( 124 | bool newLockStatus, 125 | uint256 nonce, 126 | Signature[] calldata signatures 127 | ) external { 128 | bytes32 _id = getNewLockStatusId(newLockStatus, nonce); 129 | _generalVote(_id, signatures); 130 | finishedVotings[_id] = true; 131 | allowLock = newLockStatus; 132 | } 133 | 134 | function voteForDisableToken( 135 | bool isDisable, 136 | address tokenAddress, 137 | uint256 nonce, 138 | Signature[] calldata signatures 139 | ) external { 140 | bytes32 _id = getNewDisableToken(isDisable, tokenAddress, nonce); 141 | _generalVote(_id, signatures); 142 | finishedVotings[_id] = true; 143 | if (isDisable) { 144 | disabledTokens[tokenAddress] = true; 145 | } else { 146 | delete disabledTokens[tokenAddress]; 147 | } 148 | } 149 | 150 | function _updateOracleSet(uint256 oracleSetHash, address[] memory newOracles) 151 | internal 152 | { 153 | require(newOracles.length > 2, "New set is too short"); 154 | uint256 oldSetLen = oracleSet.length; 155 | for (uint256 i = 0; i < oldSetLen; i++) { 156 | isOracle[oracleSet[i]] = false; 157 | } 158 | oracleSet = newOracles; 159 | uint256 newSetLen = oracleSet.length; 160 | for (uint256 i = 0; i < newSetLen; i++) { 161 | require(newOracles[i] != address(0), "zero signer"); 162 | require(!isOracle[newOracles[i]], "Duplicate oracle in Set"); 163 | isOracle[newOracles[i]] = true; 164 | } 165 | emit NewOracleSet(oracleSetHash, newOracles); 166 | } 167 | 168 | function getFullOracleSet() external view returns (address[] memory) { 169 | return oracleSet; 170 | } 171 | 172 | function checkTokenIsWrappedJetton(address token) public pure returns (bool) { 173 | try IWrappedJetton(token).isWrappedJetton() returns ( 174 | bool isWrappedJetton 175 | ) { 176 | return isWrappedJetton; 177 | } catch { 178 | return false; 179 | } 180 | } 181 | 182 | function getDecimals(address token) public view returns (uint8) { 183 | try IDecimals(token).decimals() returns ( 184 | uint8 decimals 185 | ) { 186 | return decimals; 187 | } catch { 188 | return 0; 189 | } 190 | } 191 | } 192 | -------------------------------------------------------------------------------- /contracts/TestTokenWithoutMetadata.sol: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: MIT 2 | // OpenZeppelin Contracts (last updated v4.8.0) (token/ERC20/ERC20.sol) 3 | 4 | pragma solidity ^0.8.0; 5 | 6 | import "@openzeppelin/contracts/token/ERC20/IERC20.sol"; 7 | import "@openzeppelin/contracts/utils/Context.sol"; 8 | 9 | /** 10 | * @dev Implementation of the {IERC20} interface. 11 | * 12 | * This implementation is agnostic to the way tokens are created. This means 13 | * that a supply mechanism has to be added in a derived contract using {_mint}. 14 | * For a generic mechanism see {ERC20PresetMinterPauser}. 15 | * 16 | * TIP: For a detailed writeup see our guide 17 | * https://forum.openzeppelin.com/t/how-to-implement-erc20-supply-mechanisms/226[How 18 | * to implement supply mechanisms]. 19 | * 20 | * The default value of {decimals} is 18. To change this, you should override 21 | * this function so it returns a different value. 22 | * 23 | * We have followed general OpenZeppelin Contracts guidelines: functions revert 24 | * instead returning `false` on failure. This behavior is nonetheless 25 | * conventional and does not conflict with the expectations of ERC20 26 | * applications. 27 | * 28 | * Additionally, an {Approval} event is emitted on calls to {transferFrom}. 29 | * This allows applications to reconstruct the allowance for all accounts just 30 | * by listening to said events. Other implementations of the EIP may not emit 31 | * these events, as it isn't required by the specification. 32 | * 33 | * Finally, the non-standard {decreaseAllowance} and {increaseAllowance} 34 | * functions have been added to mitigate the well-known issues around setting 35 | * allowances. See {IERC20-approve}. 36 | */ 37 | contract TestTokenWithoutMetadata is Context, IERC20 { 38 | mapping(address => uint256) private _balances; 39 | 40 | mapping(address => mapping(address => uint256)) private _allowances; 41 | 42 | uint256 private _totalSupply; 43 | 44 | string private _name; 45 | string private _symbol; 46 | 47 | /** 48 | * @dev Sets the values for {name} and {symbol}. 49 | * 50 | * All two of these values are immutable: they can only be set once during 51 | * construction. 52 | */ 53 | constructor(string memory name_, string memory symbol_, uint256 totalSupply_) { 54 | _name = name_; 55 | _symbol = symbol_; 56 | _mint(msg.sender, totalSupply_); 57 | } 58 | 59 | /** 60 | * @dev See {IERC20-totalSupply}. 61 | */ 62 | function totalSupply() public view virtual override returns (uint256) { 63 | return _totalSupply; 64 | } 65 | 66 | /** 67 | * @dev See {IERC20-balanceOf}. 68 | */ 69 | function balanceOf(address account) public view virtual override returns (uint256) { 70 | return _balances[account]; 71 | } 72 | 73 | /** 74 | * @dev See {IERC20-transfer}. 75 | * 76 | * Requirements: 77 | * 78 | * - `to` cannot be the zero address. 79 | * - the caller must have a balance of at least `amount`. 80 | */ 81 | function transfer(address to, uint256 amount) public virtual override returns (bool) { 82 | address owner = _msgSender(); 83 | _transfer(owner, to, amount); 84 | return true; 85 | } 86 | 87 | /** 88 | * @dev See {IERC20-allowance}. 89 | */ 90 | function allowance(address owner, address spender) public view virtual override returns (uint256) { 91 | return _allowances[owner][spender]; 92 | } 93 | 94 | /** 95 | * @dev See {IERC20-approve}. 96 | * 97 | * NOTE: If `amount` is the maximum `uint256`, the allowance is not updated on 98 | * `transferFrom`. This is semantically equivalent to an infinite approval. 99 | * 100 | * Requirements: 101 | * 102 | * - `spender` cannot be the zero address. 103 | */ 104 | function approve(address spender, uint256 amount) public virtual override returns (bool) { 105 | address owner = _msgSender(); 106 | _approve(owner, spender, amount); 107 | return true; 108 | } 109 | 110 | /** 111 | * @dev See {IERC20-transferFrom}. 112 | * 113 | * Emits an {Approval} event indicating the updated allowance. This is not 114 | * required by the EIP. See the note at the beginning of {ERC20}. 115 | * 116 | * NOTE: Does not update the allowance if the current allowance 117 | * is the maximum `uint256`. 118 | * 119 | * Requirements: 120 | * 121 | * - `from` and `to` cannot be the zero address. 122 | * - `from` must have a balance of at least `amount`. 123 | * - the caller must have allowance for ``from``'s tokens of at least 124 | * `amount`. 125 | */ 126 | function transferFrom(address from, address to, uint256 amount) public virtual override returns (bool) { 127 | address spender = _msgSender(); 128 | _spendAllowance(from, spender, amount); 129 | _transfer(from, to, amount); 130 | return true; 131 | } 132 | 133 | /** 134 | * @dev Atomically increases the allowance granted to `spender` by the caller. 135 | * 136 | * This is an alternative to {approve} that can be used as a mitigation for 137 | * problems described in {IERC20-approve}. 138 | * 139 | * Emits an {Approval} event indicating the updated allowance. 140 | * 141 | * Requirements: 142 | * 143 | * - `spender` cannot be the zero address. 144 | */ 145 | function increaseAllowance(address spender, uint256 addedValue) public virtual returns (bool) { 146 | address owner = _msgSender(); 147 | _approve(owner, spender, allowance(owner, spender) + addedValue); 148 | return true; 149 | } 150 | 151 | /** 152 | * @dev Atomically decreases the allowance granted to `spender` by the caller. 153 | * 154 | * This is an alternative to {approve} that can be used as a mitigation for 155 | * problems described in {IERC20-approve}. 156 | * 157 | * Emits an {Approval} event indicating the updated allowance. 158 | * 159 | * Requirements: 160 | * 161 | * - `spender` cannot be the zero address. 162 | * - `spender` must have allowance for the caller of at least 163 | * `subtractedValue`. 164 | */ 165 | function decreaseAllowance(address spender, uint256 subtractedValue) public virtual returns (bool) { 166 | address owner = _msgSender(); 167 | uint256 currentAllowance = allowance(owner, spender); 168 | require(currentAllowance >= subtractedValue, "ERC20: decreased allowance below zero"); 169 | unchecked { 170 | _approve(owner, spender, currentAllowance - subtractedValue); 171 | } 172 | 173 | return true; 174 | } 175 | 176 | /** 177 | * @dev Moves `amount` of tokens from `from` to `to`. 178 | * 179 | * This internal function is equivalent to {transfer}, and can be used to 180 | * e.g. implement automatic token fees, slashing mechanisms, etc. 181 | * 182 | * Emits a {Transfer} event. 183 | * 184 | * Requirements: 185 | * 186 | * - `from` cannot be the zero address. 187 | * - `to` cannot be the zero address. 188 | * - `from` must have a balance of at least `amount`. 189 | */ 190 | function _transfer(address from, address to, uint256 amount) internal virtual { 191 | require(from != address(0), "ERC20: transfer from the zero address"); 192 | require(to != address(0), "ERC20: transfer to the zero address"); 193 | 194 | _beforeTokenTransfer(from, to, amount); 195 | 196 | uint256 fromBalance = _balances[from]; 197 | require(fromBalance >= amount, "ERC20: transfer amount exceeds balance"); 198 | unchecked { 199 | _balances[from] = fromBalance - amount; 200 | // Overflow not possible: the sum of all balances is capped by totalSupply, and the sum is preserved by 201 | // decrementing then incrementing. 202 | _balances[to] += amount; 203 | } 204 | 205 | emit Transfer(from, to, amount); 206 | 207 | _afterTokenTransfer(from, to, amount); 208 | } 209 | 210 | /** @dev Creates `amount` tokens and assigns them to `account`, increasing 211 | * the total supply. 212 | * 213 | * Emits a {Transfer} event with `from` set to the zero address. 214 | * 215 | * Requirements: 216 | * 217 | * - `account` cannot be the zero address. 218 | */ 219 | function _mint(address account, uint256 amount) internal virtual { 220 | require(account != address(0), "ERC20: mint to the zero address"); 221 | 222 | _beforeTokenTransfer(address(0), account, amount); 223 | 224 | _totalSupply += amount; 225 | unchecked { 226 | // Overflow not possible: balance + amount is at most totalSupply + amount, which is checked above. 227 | _balances[account] += amount; 228 | } 229 | emit Transfer(address(0), account, amount); 230 | 231 | _afterTokenTransfer(address(0), account, amount); 232 | } 233 | 234 | /** 235 | * @dev Destroys `amount` tokens from `account`, reducing the 236 | * total supply. 237 | * 238 | * Emits a {Transfer} event with `to` set to the zero address. 239 | * 240 | * Requirements: 241 | * 242 | * - `account` cannot be the zero address. 243 | * - `account` must have at least `amount` tokens. 244 | */ 245 | function _burn(address account, uint256 amount) internal virtual { 246 | require(account != address(0), "ERC20: burn from the zero address"); 247 | 248 | _beforeTokenTransfer(account, address(0), amount); 249 | 250 | uint256 accountBalance = _balances[account]; 251 | require(accountBalance >= amount, "ERC20: burn amount exceeds balance"); 252 | unchecked { 253 | _balances[account] = accountBalance - amount; 254 | // Overflow not possible: amount <= accountBalance <= totalSupply. 255 | _totalSupply -= amount; 256 | } 257 | 258 | emit Transfer(account, address(0), amount); 259 | 260 | _afterTokenTransfer(account, address(0), amount); 261 | } 262 | 263 | /** 264 | * @dev Sets `amount` as the allowance of `spender` over the `owner` s tokens. 265 | * 266 | * This internal function is equivalent to `approve`, and can be used to 267 | * e.g. set automatic allowances for certain subsystems, etc. 268 | * 269 | * Emits an {Approval} event. 270 | * 271 | * Requirements: 272 | * 273 | * - `owner` cannot be the zero address. 274 | * - `spender` cannot be the zero address. 275 | */ 276 | function _approve(address owner, address spender, uint256 amount) internal virtual { 277 | require(owner != address(0), "ERC20: approve from the zero address"); 278 | require(spender != address(0), "ERC20: approve to the zero address"); 279 | 280 | _allowances[owner][spender] = amount; 281 | emit Approval(owner, spender, amount); 282 | } 283 | 284 | /** 285 | * @dev Updates `owner` s allowance for `spender` based on spent `amount`. 286 | * 287 | * Does not update the allowance amount in case of infinite allowance. 288 | * Revert if not enough allowance is available. 289 | * 290 | * Might emit an {Approval} event. 291 | */ 292 | function _spendAllowance(address owner, address spender, uint256 amount) internal virtual { 293 | uint256 currentAllowance = allowance(owner, spender); 294 | if (currentAllowance != type(uint256).max) { 295 | require(currentAllowance >= amount, "ERC20: insufficient allowance"); 296 | unchecked { 297 | _approve(owner, spender, currentAllowance - amount); 298 | } 299 | } 300 | } 301 | 302 | /** 303 | * @dev Hook that is called before any transfer of tokens. This includes 304 | * minting and burning. 305 | * 306 | * Calling conditions: 307 | * 308 | * - when `from` and `to` are both non-zero, `amount` of ``from``'s tokens 309 | * will be transferred to `to`. 310 | * - when `from` is zero, `amount` tokens will be minted for `to`. 311 | * - when `to` is zero, `amount` of ``from``'s tokens will be burned. 312 | * - `from` and `to` are never both zero. 313 | * 314 | * To learn more about hooks, head to xref:ROOT:extending-contracts.adoc#using-hooks[Using Hooks]. 315 | */ 316 | function _beforeTokenTransfer(address from, address to, uint256 amount) internal virtual {} 317 | 318 | /** 319 | * @dev Hook that is called after any transfer of tokens. This includes 320 | * minting and burning. 321 | * 322 | * Calling conditions: 323 | * 324 | * - when `from` and `to` are both non-zero, `amount` of ``from``'s tokens 325 | * has been transferred to `to`. 326 | * - when `from` is zero, `amount` tokens have been minted for `to`. 327 | * - when `to` is zero, `amount` of ``from``'s tokens have been burned. 328 | * - `from` and `to` are never both zero. 329 | * 330 | * To learn more about hooks, head to xref:ROOT:extending-contracts.adoc#using-hooks[Using Hooks]. 331 | */ 332 | function _afterTokenTransfer(address from, address to, uint256 amount) internal virtual {} 333 | } -------------------------------------------------------------------------------- /test/Bridge.test.ts: -------------------------------------------------------------------------------- 1 | import { expect } from "chai"; 2 | import { ethers } from "hardhat"; 3 | import { formatUnits, keccak256, parseUnits } from "ethers/lib/utils"; 4 | import { SignerWithAddress } from "@nomiclabs/hardhat-ethers/signers"; 5 | import Web3 from "web3"; 6 | import { Account } from "web3/eth/accounts"; 7 | import { 8 | TON_TX_LT, 9 | TON_WORKCHAIN, 10 | TON_ADDRESS_HASH, 11 | TON_TX_HASH 12 | } from "./utils/constants"; 13 | import { 14 | encodeOracleSet, 15 | prepareSwapData, signDisableToken, 16 | signSwapData, signUpdateLockStatus, 17 | signUpdateOracleData, 18 | } from "./utils/utils"; 19 | import type { Bridge, TestToken, TestWrappedJetton, TestTokenWithoutMetadata } from "../typechain-types"; 20 | import {BigNumber} from "ethers"; 21 | 22 | const web3 = new Web3(); 23 | 24 | describe("Bridge contract", () => { 25 | let owner: SignerWithAddress; 26 | let bridge: Bridge; 27 | let token: TestToken; 28 | let tokenWithoutMetadata: TestTokenWithoutMetadata; 29 | let wrappedJetton: TestWrappedJetton; 30 | 31 | const signer: Account = web3.eth.accounts.create() as unknown as Account; 32 | const user: Account = web3.eth.accounts.create() as unknown as Account; 33 | const signer1: Account = web3.eth.accounts.create() as unknown as Account; 34 | const signer2: Account = web3.eth.accounts.create() as unknown as Account; 35 | const unauthorized: Account = web3.eth.accounts.create() as unknown as Account; 36 | const tonAddressHash = TON_ADDRESS_HASH; 37 | 38 | const oracleSet = [ 39 | signer, 40 | user, 41 | signer1, 42 | signer2, 43 | ]; 44 | 45 | let oracleSetAddresses: string[] = new Array(); 46 | oracleSet.map((account) => { oracleSetAddresses.push(account.address) }); 47 | 48 | beforeEach(async () => { 49 | [owner] = await ethers.getSigners(); 50 | 51 | const Bridge = await ethers.getContractFactory("Bridge"); 52 | bridge = await Bridge.deploy(oracleSetAddresses) as Bridge; 53 | await bridge.deployed(); 54 | 55 | const TestToken = await ethers.getContractFactory("TestToken"); 56 | token = await TestToken.deploy(BigNumber.from('2000000').mul(BigNumber.from(10).pow(BigNumber.from(18)))) as TestToken; 57 | await token.deployed(); 58 | 59 | const TestWrappedJetton = await ethers.getContractFactory("TestWrappedJetton"); 60 | wrappedJetton = await TestWrappedJetton.deploy() as TestWrappedJetton; 61 | await wrappedJetton.deployed(); 62 | 63 | const TestTokenWithoutMetadata = await ethers.getContractFactory("TestTokenWithoutMetadata"); 64 | tokenWithoutMetadata = await TestTokenWithoutMetadata.deploy("name", "symbol", BigNumber.from('2000000').mul(BigNumber.from(10).pow(BigNumber.from(18)))) as TestTokenWithoutMetadata; 65 | await tokenWithoutMetadata.deployed(); 66 | 67 | const ownerBalance = await token.balanceOf(owner.address); 68 | expect(formatUnits(ownerBalance)).to.equal("2000000.0"); 69 | 70 | const signatures = signUpdateLockStatus( 71 | true, 72 | 1, 73 | oracleSet, 74 | bridge.address 75 | ); 76 | 77 | await bridge.voteForSwitchLock(true, 1, signatures); 78 | 79 | }); 80 | 81 | // LOCK 82 | 83 | it("Should lock token", async () => { 84 | const bridgeAllowance = parseUnits("5"); 85 | await token.approve(bridge.address, bridgeAllowance); 86 | 87 | await expect(bridge.lock(token.address, bridgeAllowance, tonAddressHash)) 88 | .to.emit(bridge, 'Lock') 89 | .withArgs(owner.address, token.address, tonAddressHash.toLowerCase(), bridgeAllowance, bridgeAllowance, 18); 90 | 91 | const ownerBalanceNew = await token.balanceOf(owner.address); 92 | const bridgeBalance = await token.balanceOf(bridge.address); 93 | 94 | expect(formatUnits(ownerBalanceNew)).to.equal("1999995.0"); 95 | expect(formatUnits(bridgeBalance)).to.equal("5.0"); 96 | }); 97 | 98 | it("Should lock token without metadata", async () => { 99 | const bridgeAllowance = parseUnits("5"); 100 | await tokenWithoutMetadata.approve(bridge.address, bridgeAllowance); 101 | 102 | await expect(bridge.lock(tokenWithoutMetadata.address, bridgeAllowance, tonAddressHash)) 103 | .to.emit(bridge, 'Lock') 104 | .withArgs(owner.address, tokenWithoutMetadata.address, tonAddressHash.toLowerCase(), bridgeAllowance, bridgeAllowance, 0); 105 | 106 | const ownerBalanceNew = await tokenWithoutMetadata.balanceOf(owner.address); 107 | const bridgeBalance = await tokenWithoutMetadata.balanceOf(bridge.address); 108 | 109 | expect(formatUnits(ownerBalanceNew)).to.equal("1999995.0"); 110 | expect(formatUnits(bridgeBalance)).to.equal("5.0"); 111 | }); 112 | 113 | it("Cant lock if no allowLock ", async () => { 114 | const signatures = signUpdateLockStatus( 115 | false, 116 | 2, 117 | oracleSet, 118 | bridge.address 119 | ); 120 | 121 | await bridge.voteForSwitchLock(false, 2, signatures); 122 | 123 | try { 124 | const bridgeAllowance = parseUnits("5"); 125 | await token.approve(bridge.address, bridgeAllowance); 126 | 127 | await bridge.lock(token.address, bridgeAllowance, tonAddressHash); 128 | 129 | expect.fail() 130 | } catch (err: any) { 131 | expect(err.toString()).to.have.string("Lock is currently disabled"); 132 | } 133 | }); 134 | 135 | it("Cant lock zero address", async () => { 136 | const bridgeAllowance = parseUnits("5"); 137 | await token.approve(bridge.address, bridgeAllowance); 138 | try { 139 | await bridge.lock( 140 | ethers.constants.AddressZero, 141 | bridgeAllowance, 142 | tonAddressHash 143 | ); 144 | expect.fail() 145 | } catch (err: any) { 146 | expect(err.toString()).to.have.string("lock: disabled token"); 147 | } 148 | }); 149 | 150 | it("Cant lock eth wrapped toncoin", async () => { 151 | const bridgeAllowance = parseUnits("5"); 152 | await token.approve(bridge.address, bridgeAllowance); 153 | try { 154 | await bridge.lock( 155 | '0x582d872a1b094fc48f5de31d3b73f2d9be47def1', 156 | bridgeAllowance, 157 | tonAddressHash 158 | ); 159 | expect.fail() 160 | } catch (err: any) { 161 | expect(err.toString()).to.have.string("lock: disabled token"); 162 | } 163 | }); 164 | 165 | it("Cant lock bsc wrapped toncoin", async () => { 166 | const bridgeAllowance = parseUnits("5"); 167 | await token.approve(bridge.address, bridgeAllowance); 168 | try { 169 | await bridge.lock( 170 | '0x76A797A59Ba2C17726896976B7B3747BfD1d220f', 171 | bridgeAllowance, 172 | tonAddressHash 173 | ); 174 | expect.fail() 175 | } catch (err: any) { 176 | expect(err.toString()).to.have.string("lock: disabled token"); 177 | } 178 | }); 179 | 180 | it("Cant lock wrapped jetton", async () => { 181 | const bridgeAllowance = parseUnits("5"); 182 | await wrappedJetton.approve(bridge.address, bridgeAllowance); 183 | 184 | try { 185 | await bridge.lock(wrappedJetton.address, bridgeAllowance, tonAddressHash); 186 | expect.fail() 187 | } catch (err: any) { 188 | expect(err.toString()).to.have.string("lock wrapped jetton"); 189 | } 190 | }); 191 | 192 | it("Cant lock big supply", async () => { 193 | const TestToken = await ethers.getContractFactory("TestToken"); 194 | const tokenBigSupply = await TestToken.deploy(BigNumber.from(2).pow(BigNumber.from(255))) as TestToken; 195 | await tokenBigSupply.deployed(); 196 | 197 | const bridgeAllowance = BigNumber.from(2).pow(BigNumber.from(255)); 198 | await tokenBigSupply.approve(bridge.address, bridgeAllowance); 199 | try { 200 | await bridge.lock( 201 | tokenBigSupply.address, 202 | bridgeAllowance, 203 | tonAddressHash 204 | ); 205 | expect.fail() 206 | } catch (err: any) { 207 | expect(err.toString()).to.have.string("Max jetton totalSupply 2 ** 120 - 1"); 208 | } 209 | }); 210 | 211 | it("Cant lock 2 ** 120", async () => { 212 | const TestToken = await ethers.getContractFactory("TestToken"); 213 | const tokenBigSupply = await TestToken.deploy(BigNumber.from(2).pow(BigNumber.from(255))) as TestToken; 214 | await tokenBigSupply.deployed(); 215 | 216 | const bridgeAllowance = BigNumber.from(2).pow(BigNumber.from(120)); 217 | await tokenBigSupply.approve(bridge.address, bridgeAllowance); 218 | try { 219 | await bridge.lock( 220 | tokenBigSupply.address, 221 | bridgeAllowance, 222 | tonAddressHash 223 | ); 224 | expect.fail() 225 | } catch (err: any) { 226 | expect(err.toString()).to.have.string("Max jetton totalSupply 2 ** 120 - 1"); 227 | } 228 | }); 229 | 230 | it("lock big supply 2**120 - 1", async () => { 231 | const TestToken = await ethers.getContractFactory("TestToken"); 232 | const tokenBigSupply = await TestToken.deploy(BigNumber.from(2).pow(BigNumber.from(120)).sub(1)) as TestToken; 233 | await tokenBigSupply.deployed(); 234 | 235 | const bridgeAllowance = BigNumber.from(2).pow(BigNumber.from(120)).sub(1); 236 | await tokenBigSupply.approve(bridge.address, bridgeAllowance); 237 | const ownerBalanceOld = await tokenBigSupply.balanceOf(owner.address); 238 | 239 | await bridge.lock(tokenBigSupply.address, bridgeAllowance, tonAddressHash); 240 | 241 | const ownerBalanceNew = ownerBalanceOld.sub(bridgeAllowance); 242 | const bridgeBalance = await tokenBigSupply.balanceOf(bridge.address); 243 | 244 | expect(await tokenBigSupply.balanceOf(owner.address)).to.equal(ownerBalanceNew); 245 | expect(bridgeBalance).to.equal(BigNumber.from(2).pow(BigNumber.from(120)).sub(1).toString()); 246 | }); 247 | 248 | it("lock big amount 2**100", async () => { 249 | const TestToken = await ethers.getContractFactory("TestToken"); 250 | const totalSupply = BigNumber.from(2).pow(BigNumber.from(100)); 251 | const tokenBigSupply = await TestToken.deploy(totalSupply) as TestToken; 252 | await tokenBigSupply.deployed(); 253 | 254 | const bridgeAllowance = totalSupply; 255 | await tokenBigSupply.approve(bridge.address, bridgeAllowance); 256 | 257 | await bridge.lock(tokenBigSupply.address, bridgeAllowance, tonAddressHash); 258 | 259 | const ownerBalanceNew = await tokenBigSupply.balanceOf(owner.address); 260 | const bridgeBalance = await tokenBigSupply.balanceOf(bridge.address); 261 | 262 | expect(ownerBalanceNew).to.equal("0"); 263 | expect(bridgeBalance).to.equal(totalSupply); 264 | }); 265 | 266 | it("Cant lock zero amount", async () => { 267 | try { 268 | await bridge.lock( 269 | token.address, 270 | parseUnits("0"), 271 | tonAddressHash 272 | ); 273 | expect.fail() 274 | } catch (err: any) { 275 | expect(err.toString()).to.have.string("newBalance must be greater than oldBalance"); 276 | } 277 | }); 278 | 279 | // UNLOCK 280 | 281 | it("Should throw 'Unauthorized signer' exception", async () => { 282 | const amount = parseUnits("2").toString(); 283 | const data = prepareSwapData( 284 | owner.address, 285 | token.address, 286 | amount, 287 | tonAddressHash, 288 | TON_TX_HASH, 289 | TON_TX_LT 290 | ); 291 | 292 | const oracleSetUnauthorized = [ 293 | unauthorized, 294 | user, 295 | signer1, 296 | signer2, 297 | ]; 298 | 299 | const signatures = signSwapData(data, oracleSetUnauthorized, bridge.address); 300 | 301 | try { 302 | await bridge.unlock(data, signatures); 303 | expect.fail() 304 | } catch (err: any) { 305 | expect(err.toString()).to.have.string("Unauthorized signer"); 306 | } 307 | }); 308 | 309 | it("Signatures are not sorted", async () => { 310 | const amount = parseUnits("2").toString(); 311 | const data = prepareSwapData( 312 | owner.address, 313 | token.address, 314 | amount, 315 | tonAddressHash, 316 | TON_TX_HASH, 317 | TON_TX_LT 318 | ); 319 | 320 | const signatures = signSwapData(data, oracleSet, bridge.address).reverse(); 321 | 322 | try { 323 | await bridge.unlock(data, signatures); 324 | expect.fail() 325 | } catch (err: any) { 326 | expect(err.toString()).to.have.string("Signatures are not sorted"); 327 | } 328 | }); 329 | 330 | it("Signatures duplicated", async () => { 331 | const amount = parseUnits("2").toString(); 332 | const data = prepareSwapData( 333 | owner.address, 334 | token.address, 335 | amount, 336 | tonAddressHash, 337 | TON_TX_HASH, 338 | TON_TX_LT 339 | ); 340 | 341 | const signatures = signSwapData(data, [ 342 | signer, 343 | signer, 344 | signer1, 345 | signer2, 346 | ], bridge.address).reverse(); 347 | 348 | try { 349 | await bridge.unlock(data, signatures); 350 | expect.fail() 351 | } catch (err: any) { 352 | expect(err.toString()).to.have.string("Signatures are not sorted"); 353 | } 354 | }); 355 | 356 | it("Should throw 'Not enough signatures' exception", async () => { 357 | const amount = parseUnits("2"); 358 | const data = prepareSwapData( 359 | owner.address, 360 | token.address, 361 | amount, 362 | tonAddressHash, 363 | TON_TX_HASH, 364 | TON_TX_LT 365 | ); 366 | 367 | const signatures = signSwapData(data, [signer], bridge.address); 368 | try { 369 | await bridge.unlock(data, signatures); 370 | expect.fail() 371 | } catch (err: any) { 372 | expect(err.toString()).to.have.string("Not enough signatures"); 373 | } 374 | }); 375 | 376 | it("Should throw 'Vote is already finished' exception", async () => { 377 | const bridgeAllowance = parseUnits("5"); 378 | await token.approve(bridge.address, bridgeAllowance); 379 | 380 | await bridge.lock(token.address, bridgeAllowance, tonAddressHash); 381 | const amount = parseUnits("2"); 382 | const data = prepareSwapData( 383 | owner.address, 384 | token.address, 385 | amount, 386 | tonAddressHash, 387 | TON_TX_HASH, 388 | TON_TX_LT 389 | ); 390 | 391 | const signatures = signSwapData(data, oracleSet, bridge.address); 392 | 393 | await bridge.unlock(data, signatures); 394 | try { 395 | await bridge.unlock(data, signatures); 396 | expect.fail() 397 | } catch (err: any) { 398 | expect(err.toString()).to.have.string("Vote is already finished"); 399 | } 400 | }); 401 | 402 | it("Should unlock token", async () => { 403 | const bridgeAllowance = parseUnits("5"); 404 | await token.approve(bridge.address, bridgeAllowance); 405 | 406 | await bridge.lock(token.address, bridgeAllowance, tonAddressHash); 407 | const amount = parseUnits("2"); 408 | const data = prepareSwapData( 409 | owner.address, 410 | token.address, 411 | amount, 412 | tonAddressHash, 413 | TON_TX_HASH, 414 | TON_TX_LT 415 | ); 416 | 417 | const signatures = signSwapData(data, oracleSet, bridge.address); 418 | 419 | await expect(bridge.unlock(data, signatures)) 420 | .to.emit(bridge, 'Unlock') 421 | .withArgs(token.address, tonAddressHash.toLowerCase(), TON_TX_HASH.toLowerCase(), TON_TX_LT, owner.address, amount, parseUnits("3")); 422 | 423 | const ownerBalanceUnlocked = await token.balanceOf(owner.address); 424 | const bridgeBalanceUnlocked = await token.balanceOf(bridge.address); 425 | expect(formatUnits(bridgeBalanceUnlocked)).to.equal("3.0"); 426 | expect(formatUnits(ownerBalanceUnlocked)).to.equal("1999997.0"); 427 | }); 428 | 429 | it("Unlock - wrong signature", async () => { 430 | const bridgeAllowance = parseUnits("5"); 431 | await token.approve(bridge.address, bridgeAllowance); 432 | 433 | await bridge.lock(token.address, bridgeAllowance, tonAddressHash); 434 | const amount = parseUnits("2"); 435 | const data = prepareSwapData( 436 | owner.address, 437 | token.address, 438 | amount, 439 | tonAddressHash, 440 | TON_TX_HASH, 441 | TON_TX_LT 442 | ); 443 | 444 | const signatures = signSwapData(data, oracleSet, bridge.address); 445 | 446 | try { 447 | await bridge.unlock(prepareSwapData( 448 | owner.address, 449 | token.address, 450 | parseUnits('5'), 451 | tonAddressHash, 452 | TON_TX_HASH, 453 | TON_TX_LT 454 | ), signatures); 455 | expect.fail() 456 | } catch (err: any) { 457 | expect(err.toString()).to.have.string("Wrong signature"); 458 | } 459 | }); 460 | 461 | it("Should unlock token 2 ** 100", async () => { 462 | const TestToken = await ethers.getContractFactory("TestToken"); 463 | const totalSupply = BigNumber.from(2).pow(BigNumber.from(100)); 464 | const tokenBigSupply = await TestToken.deploy(totalSupply) as TestToken; 465 | await tokenBigSupply.deployed(); 466 | 467 | const bridgeAllowance = totalSupply; 468 | await tokenBigSupply.approve(bridge.address, bridgeAllowance); 469 | 470 | await bridge.lock(tokenBigSupply.address, bridgeAllowance, tonAddressHash); 471 | const amount = totalSupply; 472 | const data = prepareSwapData( 473 | owner.address, 474 | tokenBigSupply.address, 475 | amount, 476 | tonAddressHash, 477 | TON_TX_HASH, 478 | TON_TX_LT 479 | ); 480 | 481 | const signatures = signSwapData(data, oracleSet, bridge.address); 482 | 483 | await bridge.unlock(data, signatures); 484 | 485 | const ownerBalanceUnlocked = await tokenBigSupply.balanceOf(owner.address); 486 | const bridgeBalanceUnlocked = await tokenBigSupply.balanceOf(bridge.address); 487 | expect(bridgeBalanceUnlocked).to.equal("0"); 488 | expect(ownerBalanceUnlocked).to.equal(totalSupply); 489 | }); 490 | 491 | // voteForSwitchLock 492 | 493 | it("voteForSwitchLock Vote is already finished", async () => { 494 | const signatures = signUpdateLockStatus( 495 | true, 496 | 666, 497 | oracleSet, 498 | bridge.address 499 | ); 500 | 501 | await bridge.voteForSwitchLock(true, 666, signatures); 502 | 503 | try { 504 | await bridge.voteForSwitchLock(true, 666, signatures); 505 | expect.fail() 506 | } catch (err: any) { 507 | expect(err.toString()).to.have.string("Vote is already finished"); 508 | } 509 | }) 510 | 511 | it("voteForSwitchLock Unauthorized signer", async () => { 512 | const signatures = signUpdateLockStatus( 513 | true, 514 | 666, 515 | oracleSet.slice(2).concat([unauthorized]), 516 | bridge.address 517 | ); 518 | 519 | try { 520 | await bridge.voteForSwitchLock(true, 666, signatures); 521 | expect.fail() 522 | } catch (err: any) { 523 | expect(err.toString()).to.have.string("Unauthorized signer"); 524 | } 525 | }) 526 | 527 | it("voteForSwitchLock Not enough signatures", async () => { 528 | const signatures = signUpdateLockStatus( 529 | true, 530 | 666, 531 | oracleSet.slice(2), 532 | bridge.address 533 | ); 534 | 535 | try { 536 | await bridge.voteForSwitchLock(true, 666, signatures); 537 | expect.fail() 538 | } catch (err: any) { 539 | expect(err.toString()).to.have.string("Not enough signatures"); 540 | } 541 | }) 542 | 543 | it("voteForSwitchLock invalid signature", async () => { 544 | const signatures = signUpdateLockStatus( 545 | true, 546 | 666, 547 | oracleSet, 548 | bridge.address 549 | ); 550 | 551 | try { 552 | await bridge.voteForSwitchLock(true, 555, signatures); 553 | expect.fail() 554 | } catch (err: any) { 555 | expect(err.toString()).to.have.string("Wrong signature"); 556 | } 557 | }) 558 | 559 | // voteForNewOracleSet 560 | 561 | it("Should update oracle set", async () => { 562 | const newSigner = web3.eth.accounts.create(); 563 | const newUser = web3.eth.accounts.create(); 564 | const newSigner1 = web3.eth.accounts.create(); 565 | 566 | const oldOracles = await bridge.getFullOracleSet(); 567 | expect(oldOracles).to.eql(oracleSetAddresses); 568 | 569 | const oracleSetHash = keccak256(encodeOracleSet(oracleSetAddresses)); 570 | 571 | const newOracleSet = [ 572 | newSigner.address, 573 | newUser.address, 574 | newSigner1.address, 575 | ]; 576 | 577 | const signatures = signUpdateOracleData( 578 | oracleSetHash, 579 | newOracleSet, 580 | oracleSet, 581 | bridge.address 582 | ); 583 | 584 | await bridge.voteForNewOracleSet(oracleSetHash, newOracleSet, signatures); 585 | 586 | const actualOracleSet = await bridge.getFullOracleSet(); 587 | 588 | expect(actualOracleSet).to.eql(newOracleSet); 589 | 590 | try { 591 | await bridge.voteForNewOracleSet(oracleSetHash, newOracleSet, signatures); 592 | expect.fail() 593 | } catch (err: any) { 594 | expect(err.toString()).to.have.string("Vote is already finished"); 595 | } 596 | }); 597 | 598 | it("update oracle set - invalid signature", async () => { 599 | const newSigner = web3.eth.accounts.create(); 600 | const newUser = web3.eth.accounts.create(); 601 | const newSigner1 = web3.eth.accounts.create(); 602 | 603 | const oldOracles = await bridge.getFullOracleSet(); 604 | expect(oldOracles).to.eql(oracleSetAddresses); 605 | 606 | const oracleSetHash = keccak256(encodeOracleSet(oracleSetAddresses)); 607 | 608 | const newOracleSet = [ 609 | newSigner.address, 610 | newUser.address, 611 | newSigner1.address, 612 | ]; 613 | 614 | const signatures = signUpdateOracleData( 615 | oracleSetHash, 616 | newOracleSet, 617 | oracleSet, 618 | bridge.address 619 | ); 620 | 621 | try { 622 | await bridge.voteForNewOracleSet(oracleSetHash, oldOracles, signatures); 623 | expect.fail() 624 | } catch (err: any) { 625 | expect(err.toString()).to.have.string("Wrong signature"); 626 | } 627 | }); 628 | 629 | it("update oracle set - zero oracle address", async () => { 630 | const newSigner = web3.eth.accounts.create(); 631 | const newUser = web3.eth.accounts.create(); 632 | const newSigner1 = web3.eth.accounts.create(); 633 | 634 | const oldOracles = await bridge.getFullOracleSet(); 635 | expect(oldOracles).to.eql(oracleSetAddresses); 636 | 637 | const oracleSetHash = keccak256(encodeOracleSet(oracleSetAddresses)); 638 | 639 | const newOracleSet = [ 640 | newSigner.address, 641 | "0x0000000000000000000000000000000000000000", 642 | newSigner1.address, 643 | ]; 644 | 645 | const signatures = signUpdateOracleData( 646 | oracleSetHash, 647 | newOracleSet, 648 | oracleSet, 649 | bridge.address 650 | ); 651 | 652 | try { 653 | await bridge.voteForNewOracleSet(oracleSetHash, newOracleSet, signatures); 654 | expect.fail() 655 | } catch (err: any) { 656 | expect(err.toString()).to.have.string("zero signer"); 657 | } 658 | }); 659 | 660 | it("cant create with zero oracle", async ()=>{ 661 | try { 662 | const Bridge = await ethers.getContractFactory("Bridge"); 663 | const b = await Bridge.deploy(oracleSetAddresses.concat("0x0000000000000000000000000000000000000000")) as Bridge; 664 | await bridge.deployed(); 665 | expect.fail() 666 | } catch (err: any) { 667 | expect(err.toString()).to.have.string("zero signer"); 668 | } 669 | }) 670 | 671 | it("Unauthorized update oracle set", async () => { 672 | const newSigner = web3.eth.accounts.create(); 673 | const newUser = web3.eth.accounts.create(); 674 | const newSigner1 = web3.eth.accounts.create(); 675 | 676 | const oldOracles = await bridge.getFullOracleSet(); 677 | expect(oldOracles).to.eql(oracleSetAddresses); 678 | 679 | const oracleSetHash = keccak256(encodeOracleSet(oracleSetAddresses)); 680 | 681 | const newOracleSet = [ 682 | newSigner.address, 683 | newUser.address, 684 | newSigner1.address, 685 | ]; 686 | 687 | const signatures = signUpdateOracleData( 688 | oracleSetHash, 689 | newOracleSet, 690 | oracleSet.slice(2).concat([unauthorized]), 691 | bridge.address 692 | ); 693 | 694 | try { 695 | await bridge.voteForNewOracleSet(oracleSetHash, newOracleSet, signatures); 696 | expect.fail() 697 | } catch (err: any) { 698 | expect(err.toString()).to.have.string("Unauthorized signer"); 699 | } 700 | }); 701 | 702 | it("Not enough signatures update oracle set", async () => { 703 | const newSigner = web3.eth.accounts.create(); 704 | const newUser = web3.eth.accounts.create(); 705 | const newSigner1 = web3.eth.accounts.create(); 706 | 707 | const oldOracles = await bridge.getFullOracleSet(); 708 | expect(oldOracles).to.eql(oracleSetAddresses); 709 | 710 | const oracleSetHash = keccak256(encodeOracleSet(oracleSetAddresses)); 711 | 712 | const newOracleSet = [ 713 | newSigner.address, 714 | newUser.address, 715 | newSigner1.address, 716 | ]; 717 | 718 | const signatures = signUpdateOracleData( 719 | oracleSetHash, 720 | newOracleSet, 721 | oracleSet.slice(2), 722 | bridge.address 723 | ); 724 | 725 | try { 726 | await bridge.voteForNewOracleSet(oracleSetHash, newOracleSet, signatures); 727 | expect.fail() 728 | } catch (err: any) { 729 | expect(err.toString()).to.have.string("Not enough signatures"); 730 | } 731 | }); 732 | 733 | it("Should throw 'New set is too short' exception", async () => { 734 | const newSigner = web3.eth.accounts.create(); 735 | 736 | const oldOracles = await bridge.getFullOracleSet(); 737 | expect(oldOracles).to.eql(oracleSetAddresses); 738 | 739 | const oracleSetHash = keccak256(encodeOracleSet(oracleSetAddresses)); 740 | 741 | const newOracleSet = [ 742 | newSigner.address, 743 | ]; 744 | 745 | const signatures = signUpdateOracleData( 746 | oracleSetHash, 747 | newOracleSet, 748 | oracleSet, 749 | bridge.address 750 | ); 751 | 752 | try { 753 | await bridge.voteForNewOracleSet(oracleSetHash, newOracleSet, signatures); 754 | expect.fail() 755 | } catch (error: any) { 756 | expect(error.toString()).to.have.string("New set is too short"); 757 | } 758 | 759 | }); 760 | 761 | it("Should throw 'Duplicate oracle in Set' exception", async () => { 762 | const newSigner = web3.eth.accounts.create(); 763 | 764 | const oldOracles = await bridge.getFullOracleSet(); 765 | expect(oldOracles).to.eql(oracleSetAddresses); 766 | 767 | const oracleSetHash = keccak256(encodeOracleSet(oracleSetAddresses)); 768 | 769 | const newOracleSet = [ 770 | newSigner.address, 771 | newSigner.address, 772 | newSigner.address, 773 | ]; 774 | 775 | const signatures = signUpdateOracleData( 776 | oracleSetHash, 777 | newOracleSet, 778 | oracleSet, 779 | bridge.address 780 | ); 781 | 782 | try { 783 | await bridge.voteForNewOracleSet(oracleSetHash, newOracleSet, signatures); 784 | expect.fail() 785 | } catch (error: any) { 786 | expect(error.toString()).to.have.string("Duplicate oracle in Set"); 787 | } 788 | 789 | }); 790 | 791 | // vote for disable token 792 | 793 | it("Should disable and enable token", async () => { 794 | const newSigner = web3.eth.accounts.create(); 795 | const newUser = web3.eth.accounts.create(); 796 | const newSigner1 = web3.eth.accounts.create(); 797 | 798 | const oldIsDisable = await bridge.disabledTokens(tokenWithoutMetadata.address); 799 | 800 | expect(oldIsDisable).to.eql(false); 801 | 802 | const signatures = signDisableToken( 803 | true, 804 | tokenWithoutMetadata.address, 805 | 1, 806 | oracleSet, 807 | bridge.address 808 | ); 809 | 810 | await bridge.voteForDisableToken(true, tokenWithoutMetadata.address, 1, signatures); 811 | 812 | const isDisable = await bridge.disabledTokens(tokenWithoutMetadata.address); 813 | 814 | expect(isDisable).to.eql(true); 815 | 816 | const bridgeAllowance = parseUnits("5"); 817 | await tokenWithoutMetadata.approve(bridge.address, bridgeAllowance); 818 | 819 | try { 820 | await bridge.lock(tokenWithoutMetadata.address, bridgeAllowance, tonAddressHash); 821 | expect.fail() 822 | } catch (err: any) { 823 | expect(err.toString()).to.have.string("disabled token"); 824 | } 825 | 826 | const signatures2 = signDisableToken( 827 | false, 828 | tokenWithoutMetadata.address, 829 | 2, 830 | oracleSet, 831 | bridge.address 832 | ); 833 | 834 | await bridge.voteForDisableToken(false, tokenWithoutMetadata.address, 2, signatures2); 835 | 836 | const isDisable2 = await bridge.disabledTokens(tokenWithoutMetadata.address); 837 | 838 | expect(isDisable2).to.eql(false); 839 | 840 | await bridge.lock(tokenWithoutMetadata.address, bridgeAllowance, tonAddressHash); 841 | 842 | }); 843 | 844 | it("voteForDisableToken Vote is already finished", async () => { 845 | const signatures = signDisableToken( 846 | true, 847 | tokenWithoutMetadata.address, 848 | 1, 849 | oracleSet, 850 | bridge.address 851 | ); 852 | 853 | await bridge.voteForDisableToken(true, tokenWithoutMetadata.address, 1, signatures); 854 | 855 | try { 856 | await bridge.voteForDisableToken(true, tokenWithoutMetadata.address, 1, signatures); 857 | expect.fail() 858 | } catch (err: any) { 859 | expect(err.toString()).to.have.string("Vote is already finished"); 860 | } 861 | }) 862 | 863 | it("voteForDisableToken Unauthorized signer", async () => { 864 | const signatures = signDisableToken( 865 | true, 866 | tokenWithoutMetadata.address, 867 | 1, 868 | oracleSet.slice(2).concat([unauthorized]), 869 | bridge.address 870 | ); 871 | 872 | try { 873 | await bridge.voteForDisableToken(true, tokenWithoutMetadata.address, 1, signatures); 874 | expect.fail() 875 | } catch (err: any) { 876 | expect(err.toString()).to.have.string("Unauthorized signer"); 877 | } 878 | }) 879 | 880 | it("voteForDisableToken Not enough signatures", async () => { 881 | const signatures = signDisableToken( 882 | true, 883 | tokenWithoutMetadata.address, 884 | 1, 885 | oracleSet.slice(2), 886 | bridge.address 887 | ); 888 | 889 | try { 890 | await bridge.voteForDisableToken(true, tokenWithoutMetadata.address, 1, signatures); 891 | expect.fail() 892 | } catch (err: any) { 893 | expect(err.toString()).to.have.string("Not enough signatures"); 894 | } 895 | }) 896 | 897 | it("voteForDisableToken invalid signature", async () => { 898 | const signatures = signDisableToken( 899 | true, 900 | tokenWithoutMetadata.address, 901 | 1, 902 | oracleSet, 903 | bridge.address 904 | ); 905 | 906 | try { 907 | await bridge.voteForDisableToken(true, tokenWithoutMetadata.address, 2, signatures); 908 | expect.fail() 909 | } catch (err: any) { 910 | expect(err.toString()).to.have.string("Wrong signature"); 911 | } 912 | }) 913 | 914 | }); 915 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | GNU GENERAL PUBLIC LICENSE 2 | Version 3, 29 June 2007 3 | 4 | Copyright (C) 2007 Free Software Foundation, Inc. 5 | Everyone is permitted to copy and distribute verbatim copies 6 | of this license document, but changing it is not allowed. 7 | 8 | Preamble 9 | 10 | The GNU General Public License is a free, copyleft license for 11 | software and other kinds of works. 12 | 13 | The licenses for most software and other practical works are designed 14 | to take away your freedom to share and change the works. By contrast, 15 | the GNU General Public License is intended to guarantee your freedom to 16 | share and change all versions of a program--to make sure it remains free 17 | software for all its users. We, the Free Software Foundation, use the 18 | GNU General Public License for most of our software; it applies also to 19 | any other work released this way by its authors. You can apply it to 20 | your programs, too. 21 | 22 | When we speak of free software, we are referring to freedom, not 23 | price. Our General Public Licenses are designed to make sure that you 24 | have the freedom to distribute copies of free software (and charge for 25 | them if you wish), that you receive source code or can get it if you 26 | want it, that you can change the software or use pieces of it in new 27 | free programs, and that you know you can do these things. 28 | 29 | To protect your rights, we need to prevent others from denying you 30 | these rights or asking you to surrender the rights. Therefore, you have 31 | certain responsibilities if you distribute copies of the software, or if 32 | you modify it: responsibilities to respect the freedom of others. 33 | 34 | For example, if you distribute copies of such a program, whether 35 | gratis or for a fee, you must pass on to the recipients the same 36 | freedoms that you received. You must make sure that they, too, receive 37 | or can get the source code. And you must show them these terms so they 38 | know their rights. 39 | 40 | Developers that use the GNU GPL protect your rights with two steps: 41 | (1) assert copyright on the software, and (2) offer you this License 42 | giving you legal permission to copy, distribute and/or modify it. 43 | 44 | For the developers' and authors' protection, the GPL clearly explains 45 | that there is no warranty for this free software. For both users' and 46 | authors' sake, the GPL requires that modified versions be marked as 47 | changed, so that their problems will not be attributed erroneously to 48 | authors of previous versions. 49 | 50 | Some devices are designed to deny users access to install or run 51 | modified versions of the software inside them, although the manufacturer 52 | can do so. This is fundamentally incompatible with the aim of 53 | protecting users' freedom to change the software. The systematic 54 | pattern of such abuse occurs in the area of products for individuals to 55 | use, which is precisely where it is most unacceptable. Therefore, we 56 | have designed this version of the GPL to prohibit the practice for those 57 | products. If such problems arise substantially in other domains, we 58 | stand ready to extend this provision to those domains in future versions 59 | of the GPL, as needed to protect the freedom of users. 60 | 61 | Finally, every program is threatened constantly by software patents. 62 | States should not allow patents to restrict development and use of 63 | software on general-purpose computers, but in those that do, we wish to 64 | avoid the special danger that patents applied to a free program could 65 | make it effectively proprietary. To prevent this, the GPL assures that 66 | patents cannot be used to render the program non-free. 67 | 68 | The precise terms and conditions for copying, distribution and 69 | modification follow. 70 | 71 | TERMS AND CONDITIONS 72 | 73 | 0. Definitions. 74 | 75 | "This License" refers to version 3 of the GNU General Public License. 76 | 77 | "Copyright" also means copyright-like laws that apply to other kinds of 78 | works, such as semiconductor masks. 79 | 80 | "The Program" refers to any copyrightable work licensed under this 81 | License. Each licensee is addressed as "you". "Licensees" and 82 | "recipients" may be individuals or organizations. 83 | 84 | To "modify" a work means to copy from or adapt all or part of the work 85 | in a fashion requiring copyright permission, other than the making of an 86 | exact copy. The resulting work is called a "modified version" of the 87 | earlier work or a work "based on" the earlier work. 88 | 89 | A "covered work" means either the unmodified Program or a work based 90 | on the Program. 91 | 92 | To "propagate" a work means to do anything with it that, without 93 | permission, would make you directly or secondarily liable for 94 | infringement under applicable copyright law, except executing it on a 95 | computer or modifying a private copy. Propagation includes copying, 96 | distribution (with or without modification), making available to the 97 | public, and in some countries other activities as well. 98 | 99 | To "convey" a work means any kind of propagation that enables other 100 | parties to make or receive copies. Mere interaction with a user through 101 | a computer network, with no transfer of a copy, is not conveying. 102 | 103 | An interactive user interface displays "Appropriate Legal Notices" 104 | to the extent that it includes a convenient and prominently visible 105 | feature that (1) displays an appropriate copyright notice, and (2) 106 | tells the user that there is no warranty for the work (except to the 107 | extent that warranties are provided), that licensees may convey the 108 | work under this License, and how to view a copy of this License. If 109 | the interface presents a list of user commands or options, such as a 110 | menu, a prominent item in the list meets this criterion. 111 | 112 | 1. Source Code. 113 | 114 | The "source code" for a work means the preferred form of the work 115 | for making modifications to it. "Object code" means any non-source 116 | form of a work. 117 | 118 | A "Standard Interface" means an interface that either is an official 119 | standard defined by a recognized standards body, or, in the case of 120 | interfaces specified for a particular programming language, one that 121 | is widely used among developers working in that language. 122 | 123 | The "System Libraries" of an executable work include anything, other 124 | than the work as a whole, that (a) is included in the normal form of 125 | packaging a Major Component, but which is not part of that Major 126 | Component, and (b) serves only to enable use of the work with that 127 | Major Component, or to implement a Standard Interface for which an 128 | implementation is available to the public in source code form. A 129 | "Major Component", in this context, means a major essential component 130 | (kernel, window system, and so on) of the specific operating system 131 | (if any) on which the executable work runs, or a compiler used to 132 | produce the work, or an object code interpreter used to run it. 133 | 134 | The "Corresponding Source" for a work in object code form means all 135 | the source code needed to generate, install, and (for an executable 136 | work) run the object code and to modify the work, including scripts to 137 | control those activities. However, it does not include the work's 138 | System Libraries, or general-purpose tools or generally available free 139 | programs which are used unmodified in performing those activities but 140 | which are not part of the work. For example, Corresponding Source 141 | includes interface definition files associated with source files for 142 | the work, and the source code for shared libraries and dynamically 143 | linked subprograms that the work is specifically designed to require, 144 | such as by intimate data communication or control flow between those 145 | subprograms and other parts of the work. 146 | 147 | The Corresponding Source need not include anything that users 148 | can regenerate automatically from other parts of the Corresponding 149 | Source. 150 | 151 | The Corresponding Source for a work in source code form is that 152 | same work. 153 | 154 | 2. Basic Permissions. 155 | 156 | All rights granted under this License are granted for the term of 157 | copyright on the Program, and are irrevocable provided the stated 158 | conditions are met. This License explicitly affirms your unlimited 159 | permission to run the unmodified Program. The output from running a 160 | covered work is covered by this License only if the output, given its 161 | content, constitutes a covered work. This License acknowledges your 162 | rights of fair use or other equivalent, as provided by copyright law. 163 | 164 | You may make, run and propagate covered works that you do not 165 | convey, without conditions so long as your license otherwise remains 166 | in force. You may convey covered works to others for the sole purpose 167 | of having them make modifications exclusively for you, or provide you 168 | with facilities for running those works, provided that you comply with 169 | the terms of this License in conveying all material for which you do 170 | not control copyright. Those thus making or running the covered works 171 | for you must do so exclusively on your behalf, under your direction 172 | and control, on terms that prohibit them from making any copies of 173 | your copyrighted material outside their relationship with you. 174 | 175 | Conveying under any other circumstances is permitted solely under 176 | the conditions stated below. Sublicensing is not allowed; section 10 177 | makes it unnecessary. 178 | 179 | 3. Protecting Users' Legal Rights From Anti-Circumvention Law. 180 | 181 | No covered work shall be deemed part of an effective technological 182 | measure under any applicable law fulfilling obligations under article 183 | 11 of the WIPO copyright treaty adopted on 20 December 1996, or 184 | similar laws prohibiting or restricting circumvention of such 185 | measures. 186 | 187 | When you convey a covered work, you waive any legal power to forbid 188 | circumvention of technological measures to the extent such circumvention 189 | is effected by exercising rights under this License with respect to 190 | the covered work, and you disclaim any intention to limit operation or 191 | modification of the work as a means of enforcing, against the work's 192 | users, your or third parties' legal rights to forbid circumvention of 193 | technological measures. 194 | 195 | 4. Conveying Verbatim Copies. 196 | 197 | You may convey verbatim copies of the Program's source code as you 198 | receive it, in any medium, provided that you conspicuously and 199 | appropriately publish on each copy an appropriate copyright notice; 200 | keep intact all notices stating that this License and any 201 | non-permissive terms added in accord with section 7 apply to the code; 202 | keep intact all notices of the absence of any warranty; and give all 203 | recipients a copy of this License along with the Program. 204 | 205 | You may charge any price or no price for each copy that you convey, 206 | and you may offer support or warranty protection for a fee. 207 | 208 | 5. Conveying Modified Source Versions. 209 | 210 | You may convey a work based on the Program, or the modifications to 211 | produce it from the Program, in the form of source code under the 212 | terms of section 4, provided that you also meet all of these conditions: 213 | 214 | a) The work must carry prominent notices stating that you modified 215 | it, and giving a relevant date. 216 | 217 | b) The work must carry prominent notices stating that it is 218 | released under this License and any conditions added under section 219 | 7. This requirement modifies the requirement in section 4 to 220 | "keep intact all notices". 221 | 222 | c) You must license the entire work, as a whole, under this 223 | License to anyone who comes into possession of a copy. This 224 | License will therefore apply, along with any applicable section 7 225 | additional terms, to the whole of the work, and all its parts, 226 | regardless of how they are packaged. This License gives no 227 | permission to license the work in any other way, but it does not 228 | invalidate such permission if you have separately received it. 229 | 230 | d) If the work has interactive user interfaces, each must display 231 | Appropriate Legal Notices; however, if the Program has interactive 232 | interfaces that do not display Appropriate Legal Notices, your 233 | work need not make them do so. 234 | 235 | A compilation of a covered work with other separate and independent 236 | works, which are not by their nature extensions of the covered work, 237 | and which are not combined with it such as to form a larger program, 238 | in or on a volume of a storage or distribution medium, is called an 239 | "aggregate" if the compilation and its resulting copyright are not 240 | used to limit the access or legal rights of the compilation's users 241 | beyond what the individual works permit. Inclusion of a covered work 242 | in an aggregate does not cause this License to apply to the other 243 | parts of the aggregate. 244 | 245 | 6. Conveying Non-Source Forms. 246 | 247 | You may convey a covered work in object code form under the terms 248 | of sections 4 and 5, provided that you also convey the 249 | machine-readable Corresponding Source under the terms of this License, 250 | in one of these ways: 251 | 252 | a) Convey the object code in, or embodied in, a physical product 253 | (including a physical distribution medium), accompanied by the 254 | Corresponding Source fixed on a durable physical medium 255 | customarily used for software interchange. 256 | 257 | b) Convey the object code in, or embodied in, a physical product 258 | (including a physical distribution medium), accompanied by a 259 | written offer, valid for at least three years and valid for as 260 | long as you offer spare parts or customer support for that product 261 | model, to give anyone who possesses the object code either (1) a 262 | copy of the Corresponding Source for all the software in the 263 | product that is covered by this License, on a durable physical 264 | medium customarily used for software interchange, for a price no 265 | more than your reasonable cost of physically performing this 266 | conveying of source, or (2) access to copy the 267 | Corresponding Source from a network server at no charge. 268 | 269 | c) Convey individual copies of the object code with a copy of the 270 | written offer to provide the Corresponding Source. This 271 | alternative is allowed only occasionally and noncommercially, and 272 | only if you received the object code with such an offer, in accord 273 | with subsection 6b. 274 | 275 | d) Convey the object code by offering access from a designated 276 | place (gratis or for a charge), and offer equivalent access to the 277 | Corresponding Source in the same way through the same place at no 278 | further charge. You need not require recipients to copy the 279 | Corresponding Source along with the object code. If the place to 280 | copy the object code is a network server, the Corresponding Source 281 | may be on a different server (operated by you or a third party) 282 | that supports equivalent copying facilities, provided you maintain 283 | clear directions next to the object code saying where to find the 284 | Corresponding Source. Regardless of what server hosts the 285 | Corresponding Source, you remain obligated to ensure that it is 286 | available for as long as needed to satisfy these requirements. 287 | 288 | e) Convey the object code using peer-to-peer transmission, provided 289 | you inform other peers where the object code and Corresponding 290 | Source of the work are being offered to the general public at no 291 | charge under subsection 6d. 292 | 293 | A separable portion of the object code, whose source code is excluded 294 | from the Corresponding Source as a System Library, need not be 295 | included in conveying the object code work. 296 | 297 | A "User Product" is either (1) a "consumer product", which means any 298 | tangible personal property which is normally used for personal, family, 299 | or household purposes, or (2) anything designed or sold for incorporation 300 | into a dwelling. In determining whether a product is a consumer product, 301 | doubtful cases shall be resolved in favor of coverage. For a particular 302 | product received by a particular user, "normally used" refers to a 303 | typical or common use of that class of product, regardless of the status 304 | of the particular user or of the way in which the particular user 305 | actually uses, or expects or is expected to use, the product. A product 306 | is a consumer product regardless of whether the product has substantial 307 | commercial, industrial or non-consumer uses, unless such uses represent 308 | the only significant mode of use of the product. 309 | 310 | "Installation Information" for a User Product means any methods, 311 | procedures, authorization keys, or other information required to install 312 | and execute modified versions of a covered work in that User Product from 313 | a modified version of its Corresponding Source. The information must 314 | suffice to ensure that the continued functioning of the modified object 315 | code is in no case prevented or interfered with solely because 316 | modification has been made. 317 | 318 | If you convey an object code work under this section in, or with, or 319 | specifically for use in, a User Product, and the conveying occurs as 320 | part of a transaction in which the right of possession and use of the 321 | User Product is transferred to the recipient in perpetuity or for a 322 | fixed term (regardless of how the transaction is characterized), the 323 | Corresponding Source conveyed under this section must be accompanied 324 | by the Installation Information. But this requirement does not apply 325 | if neither you nor any third party retains the ability to install 326 | modified object code on the User Product (for example, the work has 327 | been installed in ROM). 328 | 329 | The requirement to provide Installation Information does not include a 330 | requirement to continue to provide support service, warranty, or updates 331 | for a work that has been modified or installed by the recipient, or for 332 | the User Product in which it has been modified or installed. Access to a 333 | network may be denied when the modification itself materially and 334 | adversely affects the operation of the network or violates the rules and 335 | protocols for communication across the network. 336 | 337 | Corresponding Source conveyed, and Installation Information provided, 338 | in accord with this section must be in a format that is publicly 339 | documented (and with an implementation available to the public in 340 | source code form), and must require no special password or key for 341 | unpacking, reading or copying. 342 | 343 | 7. Additional Terms. 344 | 345 | "Additional permissions" are terms that supplement the terms of this 346 | License by making exceptions from one or more of its conditions. 347 | Additional permissions that are applicable to the entire Program shall 348 | be treated as though they were included in this License, to the extent 349 | that they are valid under applicable law. If additional permissions 350 | apply only to part of the Program, that part may be used separately 351 | under those permissions, but the entire Program remains governed by 352 | this License without regard to the additional permissions. 353 | 354 | When you convey a copy of a covered work, you may at your option 355 | remove any additional permissions from that copy, or from any part of 356 | it. (Additional permissions may be written to require their own 357 | removal in certain cases when you modify the work.) You may place 358 | additional permissions on material, added by you to a covered work, 359 | for which you have or can give appropriate copyright permission. 360 | 361 | Notwithstanding any other provision of this License, for material you 362 | add to a covered work, you may (if authorized by the copyright holders of 363 | that material) supplement the terms of this License with terms: 364 | 365 | a) Disclaiming warranty or limiting liability differently from the 366 | terms of sections 15 and 16 of this License; or 367 | 368 | b) Requiring preservation of specified reasonable legal notices or 369 | author attributions in that material or in the Appropriate Legal 370 | Notices displayed by works containing it; or 371 | 372 | c) Prohibiting misrepresentation of the origin of that material, or 373 | requiring that modified versions of such material be marked in 374 | reasonable ways as different from the original version; or 375 | 376 | d) Limiting the use for publicity purposes of names of licensors or 377 | authors of the material; or 378 | 379 | e) Declining to grant rights under trademark law for use of some 380 | trade names, trademarks, or service marks; or 381 | 382 | f) Requiring indemnification of licensors and authors of that 383 | material by anyone who conveys the material (or modified versions of 384 | it) with contractual assumptions of liability to the recipient, for 385 | any liability that these contractual assumptions directly impose on 386 | those licensors and authors. 387 | 388 | All other non-permissive additional terms are considered "further 389 | restrictions" within the meaning of section 10. If the Program as you 390 | received it, or any part of it, contains a notice stating that it is 391 | governed by this License along with a term that is a further 392 | restriction, you may remove that term. If a license document contains 393 | a further restriction but permits relicensing or conveying under this 394 | License, you may add to a covered work material governed by the terms 395 | of that license document, provided that the further restriction does 396 | not survive such relicensing or conveying. 397 | 398 | If you add terms to a covered work in accord with this section, you 399 | must place, in the relevant source files, a statement of the 400 | additional terms that apply to those files, or a notice indicating 401 | where to find the applicable terms. 402 | 403 | Additional terms, permissive or non-permissive, may be stated in the 404 | form of a separately written license, or stated as exceptions; 405 | the above requirements apply either way. 406 | 407 | 8. Termination. 408 | 409 | You may not propagate or modify a covered work except as expressly 410 | provided under this License. Any attempt otherwise to propagate or 411 | modify it is void, and will automatically terminate your rights under 412 | this License (including any patent licenses granted under the third 413 | paragraph of section 11). 414 | 415 | However, if you cease all violation of this License, then your 416 | license from a particular copyright holder is reinstated (a) 417 | provisionally, unless and until the copyright holder explicitly and 418 | finally terminates your license, and (b) permanently, if the copyright 419 | holder fails to notify you of the violation by some reasonable means 420 | prior to 60 days after the cessation. 421 | 422 | Moreover, your license from a particular copyright holder is 423 | reinstated permanently if the copyright holder notifies you of the 424 | violation by some reasonable means, this is the first time you have 425 | received notice of violation of this License (for any work) from that 426 | copyright holder, and you cure the violation prior to 30 days after 427 | your receipt of the notice. 428 | 429 | Termination of your rights under this section does not terminate the 430 | licenses of parties who have received copies or rights from you under 431 | this License. If your rights have been terminated and not permanently 432 | reinstated, you do not qualify to receive new licenses for the same 433 | material under section 10. 434 | 435 | 9. Acceptance Not Required for Having Copies. 436 | 437 | You are not required to accept this License in order to receive or 438 | run a copy of the Program. Ancillary propagation of a covered work 439 | occurring solely as a consequence of using peer-to-peer transmission 440 | to receive a copy likewise does not require acceptance. However, 441 | nothing other than this License grants you permission to propagate or 442 | modify any covered work. These actions infringe copyright if you do 443 | not accept this License. Therefore, by modifying or propagating a 444 | covered work, you indicate your acceptance of this License to do so. 445 | 446 | 10. Automatic Licensing of Downstream Recipients. 447 | 448 | Each time you convey a covered work, the recipient automatically 449 | receives a license from the original licensors, to run, modify and 450 | propagate that work, subject to this License. You are not responsible 451 | for enforcing compliance by third parties with this License. 452 | 453 | An "entity transaction" is a transaction transferring control of an 454 | organization, or substantially all assets of one, or subdividing an 455 | organization, or merging organizations. If propagation of a covered 456 | work results from an entity transaction, each party to that 457 | transaction who receives a copy of the work also receives whatever 458 | licenses to the work the party's predecessor in interest had or could 459 | give under the previous paragraph, plus a right to possession of the 460 | Corresponding Source of the work from the predecessor in interest, if 461 | the predecessor has it or can get it with reasonable efforts. 462 | 463 | You may not impose any further restrictions on the exercise of the 464 | rights granted or affirmed under this License. For example, you may 465 | not impose a license fee, royalty, or other charge for exercise of 466 | rights granted under this License, and you may not initiate litigation 467 | (including a cross-claim or counterclaim in a lawsuit) alleging that 468 | any patent claim is infringed by making, using, selling, offering for 469 | sale, or importing the Program or any portion of it. 470 | 471 | 11. Patents. 472 | 473 | A "contributor" is a copyright holder who authorizes use under this 474 | License of the Program or a work on which the Program is based. The 475 | work thus licensed is called the contributor's "contributor version". 476 | 477 | A contributor's "essential patent claims" are all patent claims 478 | owned or controlled by the contributor, whether already acquired or 479 | hereafter acquired, that would be infringed by some manner, permitted 480 | by this License, of making, using, or selling its contributor version, 481 | but do not include claims that would be infringed only as a 482 | consequence of further modification of the contributor version. For 483 | purposes of this definition, "control" includes the right to grant 484 | patent sublicenses in a manner consistent with the requirements of 485 | this License. 486 | 487 | Each contributor grants you a non-exclusive, worldwide, royalty-free 488 | patent license under the contributor's essential patent claims, to 489 | make, use, sell, offer for sale, import and otherwise run, modify and 490 | propagate the contents of its contributor version. 491 | 492 | In the following three paragraphs, a "patent license" is any express 493 | agreement or commitment, however denominated, not to enforce a patent 494 | (such as an express permission to practice a patent or covenant not to 495 | sue for patent infringement). To "grant" such a patent license to a 496 | party means to make such an agreement or commitment not to enforce a 497 | patent against the party. 498 | 499 | If you convey a covered work, knowingly relying on a patent license, 500 | and the Corresponding Source of the work is not available for anyone 501 | to copy, free of charge and under the terms of this License, through a 502 | publicly available network server or other readily accessible means, 503 | then you must either (1) cause the Corresponding Source to be so 504 | available, or (2) arrange to deprive yourself of the benefit of the 505 | patent license for this particular work, or (3) arrange, in a manner 506 | consistent with the requirements of this License, to extend the patent 507 | license to downstream recipients. "Knowingly relying" means you have 508 | actual knowledge that, but for the patent license, your conveying the 509 | covered work in a country, or your recipient's use of the covered work 510 | in a country, would infringe one or more identifiable patents in that 511 | country that you have reason to believe are valid. 512 | 513 | If, pursuant to or in connection with a single transaction or 514 | arrangement, you convey, or propagate by procuring conveyance of, a 515 | covered work, and grant a patent license to some of the parties 516 | receiving the covered work authorizing them to use, propagate, modify 517 | or convey a specific copy of the covered work, then the patent license 518 | you grant is automatically extended to all recipients of the covered 519 | work and works based on it. 520 | 521 | A patent license is "discriminatory" if it does not include within 522 | the scope of its coverage, prohibits the exercise of, or is 523 | conditioned on the non-exercise of one or more of the rights that are 524 | specifically granted under this License. You may not convey a covered 525 | work if you are a party to an arrangement with a third party that is 526 | in the business of distributing software, under which you make payment 527 | to the third party based on the extent of your activity of conveying 528 | the work, and under which the third party grants, to any of the 529 | parties who would receive the covered work from you, a discriminatory 530 | patent license (a) in connection with copies of the covered work 531 | conveyed by you (or copies made from those copies), or (b) primarily 532 | for and in connection with specific products or compilations that 533 | contain the covered work, unless you entered into that arrangement, 534 | or that patent license was granted, prior to 28 March 2007. 535 | 536 | Nothing in this License shall be construed as excluding or limiting 537 | any implied license or other defenses to infringement that may 538 | otherwise be available to you under applicable patent law. 539 | 540 | 12. No Surrender of Others' Freedom. 541 | 542 | If conditions are imposed on you (whether by court order, agreement or 543 | otherwise) that contradict the conditions of this License, they do not 544 | excuse you from the conditions of this License. If you cannot convey a 545 | covered work so as to satisfy simultaneously your obligations under this 546 | License and any other pertinent obligations, then as a consequence you may 547 | not convey it at all. For example, if you agree to terms that obligate you 548 | to collect a royalty for further conveying from those to whom you convey 549 | the Program, the only way you could satisfy both those terms and this 550 | License would be to refrain entirely from conveying the Program. 551 | 552 | 13. Use with the GNU Affero General Public License. 553 | 554 | Notwithstanding any other provision of this License, you have 555 | permission to link or combine any covered work with a work licensed 556 | under version 3 of the GNU Affero General Public License into a single 557 | combined work, and to convey the resulting work. The terms of this 558 | License will continue to apply to the part which is the covered work, 559 | but the special requirements of the GNU Affero General Public License, 560 | section 13, concerning interaction through a network will apply to the 561 | combination as such. 562 | 563 | 14. Revised Versions of this License. 564 | 565 | The Free Software Foundation may publish revised and/or new versions of 566 | the GNU General Public License from time to time. Such new versions will 567 | be similar in spirit to the present version, but may differ in detail to 568 | address new problems or concerns. 569 | 570 | Each version is given a distinguishing version number. If the 571 | Program specifies that a certain numbered version of the GNU General 572 | Public License "or any later version" applies to it, you have the 573 | option of following the terms and conditions either of that numbered 574 | version or of any later version published by the Free Software 575 | Foundation. If the Program does not specify a version number of the 576 | GNU General Public License, you may choose any version ever published 577 | by the Free Software Foundation. 578 | 579 | If the Program specifies that a proxy can decide which future 580 | versions of the GNU General Public License can be used, that proxy's 581 | public statement of acceptance of a version permanently authorizes you 582 | to choose that version for the Program. 583 | 584 | Later license versions may give you additional or different 585 | permissions. However, no additional obligations are imposed on any 586 | author or copyright holder as a result of your choosing to follow a 587 | later version. 588 | 589 | 15. Disclaimer of Warranty. 590 | 591 | THERE IS NO WARRANTY FOR THE PROGRAM, TO THE EXTENT PERMITTED BY 592 | APPLICABLE LAW. EXCEPT WHEN OTHERWISE STATED IN WRITING THE COPYRIGHT 593 | HOLDERS AND/OR OTHER PARTIES PROVIDE THE PROGRAM "AS IS" WITHOUT WARRANTY 594 | OF ANY KIND, EITHER EXPRESSED OR IMPLIED, INCLUDING, BUT NOT LIMITED TO, 595 | THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR 596 | PURPOSE. THE ENTIRE RISK AS TO THE QUALITY AND PERFORMANCE OF THE PROGRAM 597 | IS WITH YOU. SHOULD THE PROGRAM PROVE DEFECTIVE, YOU ASSUME THE COST OF 598 | ALL NECESSARY SERVICING, REPAIR OR CORRECTION. 599 | 600 | 16. Limitation of Liability. 601 | 602 | IN NO EVENT UNLESS REQUIRED BY APPLICABLE LAW OR AGREED TO IN WRITING 603 | WILL ANY COPYRIGHT HOLDER, OR ANY OTHER PARTY WHO MODIFIES AND/OR CONVEYS 604 | THE PROGRAM AS PERMITTED ABOVE, BE LIABLE TO YOU FOR DAMAGES, INCLUDING ANY 605 | GENERAL, SPECIAL, INCIDENTAL OR CONSEQUENTIAL DAMAGES ARISING OUT OF THE 606 | USE OR INABILITY TO USE THE PROGRAM (INCLUDING BUT NOT LIMITED TO LOSS OF 607 | DATA OR DATA BEING RENDERED INACCURATE OR LOSSES SUSTAINED BY YOU OR THIRD 608 | PARTIES OR A FAILURE OF THE PROGRAM TO OPERATE WITH ANY OTHER PROGRAMS), 609 | EVEN IF SUCH HOLDER OR OTHER PARTY HAS BEEN ADVISED OF THE POSSIBILITY OF 610 | SUCH DAMAGES. 611 | 612 | 17. Interpretation of Sections 15 and 16. 613 | 614 | If the disclaimer of warranty and limitation of liability provided 615 | above cannot be given local legal effect according to their terms, 616 | reviewing courts shall apply local law that most closely approximates 617 | an absolute waiver of all civil liability in connection with the 618 | Program, unless a warranty or assumption of liability accompanies a 619 | copy of the Program in return for a fee. 620 | 621 | END OF TERMS AND CONDITIONS 622 | 623 | How to Apply These Terms to Your New Programs 624 | 625 | If you develop a new program, and you want it to be of the greatest 626 | possible use to the public, the best way to achieve this is to make it 627 | free software which everyone can redistribute and change under these terms. 628 | 629 | To do so, attach the following notices to the program. It is safest 630 | to attach them to the start of each source file to most effectively 631 | state the exclusion of warranty; and each file should have at least 632 | the "copyright" line and a pointer to where the full notice is found. 633 | 634 | 635 | Copyright (C) 636 | 637 | This program is free software: you can redistribute it and/or modify 638 | it under the terms of the GNU General Public License as published by 639 | the Free Software Foundation, either version 3 of the License, or 640 | (at your option) any later version. 641 | 642 | This program is distributed in the hope that it will be useful, 643 | but WITHOUT ANY WARRANTY; without even the implied warranty of 644 | MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 645 | GNU General Public License for more details. 646 | 647 | You should have received a copy of the GNU General Public License 648 | along with this program. If not, see . 649 | 650 | Also add information on how to contact you by electronic and paper mail. 651 | 652 | If the program does terminal interaction, make it output a short 653 | notice like this when it starts in an interactive mode: 654 | 655 | Copyright (C) 656 | This program comes with ABSOLUTELY NO WARRANTY; for details type `show w'. 657 | This is free software, and you are welcome to redistribute it 658 | under certain conditions; type `show c' for details. 659 | 660 | The hypothetical commands `show w' and `show c' should show the appropriate 661 | parts of the General Public License. Of course, your program's commands 662 | might be different; for a GUI interface, you would use an "about box". 663 | 664 | You should also get your employer (if you work as a programmer) or school, 665 | if any, to sign a "copyright disclaimer" for the program, if necessary. 666 | For more information on this, and how to apply and follow the GNU GPL, see 667 | . 668 | 669 | The GNU General Public License does not permit incorporating your program 670 | into proprietary programs. If your program is a subroutine library, you 671 | may consider it more useful to permit linking proprietary applications with 672 | the library. If this is what you want to do, use the GNU Lesser General 673 | Public License instead of this License. But first, please read 674 | . 675 | --------------------------------------------------------------------------------