diff --git a/.gitignore b/.gitignore index 215b9bd..d75603d 100644 --- a/.gitignore +++ b/.gitignore @@ -3,3 +3,18 @@ /tools/data /tools/testnet1 /package-lock.json +clients/config.js +clients/config.js.map +clients/client.js +clients/client.js.map +clients/bridge/bridge.js +clients/bridge/bridge.js.map +clients/bridge/init.js +clients/bridge/init.js.map +clients/types/*.js +clients/types/*.js.map +clients/token/mint.js +clients/token/mint.js.map +clients/contracts/*.js +clients/contracts/*.js.map + diff --git a/.secrets.json b/.secrets.json deleted file mode 100644 index be1450c..0000000 --- a/.secrets.json +++ /dev/null @@ -1,18 +0,0 @@ -{ - "infura_id":"1c8e4178f8954e01a95c8eef7b8af2b7", - "getblock_key":"182a8e0d-c03a-44ac-b856-41d2e47801db", - "accounts": { - "deployer": { - "priv": "0xb8b8ceb316c761a97cdd6c3613fc5c6d71bb8c49d76f76a7a926e5f9414af0a3" - }, - "tester": { - "priv":"0xf6392ba9b8cb91490a3e06fe141d5140df89e73931b0e3570bad0de7ef1f25c3" - }, - "tester2": { - "priv":"0xba81dd44260d036d293fbe85ad1bb141276fcc331e724877f08b6b42cc3df8d9" - }, - "monitor": { - "priv":"0xf6392ba9b8cb91490a3e06fe141d5140df89e73931b0e3570bad0de7ef1f25c3" - } - } -} diff --git a/clients/bridge/abi.js b/clients/bridge/abi.js deleted file mode 100644 index 0d129c1..0000000 --- a/clients/bridge/abi.js +++ /dev/null @@ -1,12 +0,0 @@ -const Web3 = require("web3") -const BigNumber = Web3.utils.BN; -const BridgeInfo = require("../../build/contracts/Bridge.json"); -const Bridge = require("./bridge"); - -async function getBridge (config, client_mode) { - return Bridge.getBridgeClient(config, BridgeInfo, client_mode); -}; - -module.exports = { - getBridge: getBridge, -} diff --git a/clients/bridge/bridge.js b/clients/bridge/bridge.js deleted file mode 100644 index 0dc569a..0000000 --- a/clients/bridge/bridge.js +++ /dev/null @@ -1,239 +0,0 @@ -const Web3 = require("web3"); -const BigNumber = Web3.utils.BN; -const Client = require("web3subscriber/client"); -const PBinder = require("web3subscriber/pbinder"); -const ERC20 = require("../../build/contracts/IERC20.json"); -const VERIFIER = require("../../build/contracts/Verifier.json"); - -const L1ADDR_BITS = 160; -const Tokens = require("./tokenlist"); - -function hexcmp(x, y) { - const xx = new BigNumber(x, "hex"); - const yy = new BigNumber(y, "hex"); - return xx.eq(yy); -} - -function encode_l1address(address_hexstr, chex) { - let c = new BigNumber( - chex + "0000000000000000000000000000000000000000", - "hex" - ); - let a = new BigNumber(address_hexstr, 16); - return c.add(a); -} - -/* chain_id:dec * address:hex - */ -function decode_l1address(l1address) { - let uid = new BigNumber(l1address); - let chain_id = uid.shrn(L1ADDR_BITS); - let address_hex = uid.sub(chain_id.shln(L1ADDR_BITS)).toString(16); - //address is 160 thus we need to padding '0' at the begining - let prefix = Array(40 - address_hex.length + 1).join("0"); - address_hex = prefix + address_hex; - let chain_hex = chain_id.toString(10); - return [chain_hex, address_hex]; -} - -function extract_chain_info(all_tokens) { - let valid_tokens = all_tokens.filter((t) => t.token_uid != "0"); - valid_tokens = valid_tokens.map((token) => { - let [cid, address] = decode_l1address(token.token_uid); - return { - chainId: cid, - name: - Tokens.tokenInfo.find( - (x) => - hexcmp(x.address, address) && - x.chainId == x.chainId && - x.chainId == cid - )?.name || "unknown", - index: all_tokens.findIndex((x) => x.token_uid == token.token_uid), - address: address, - }; - }); - let chain_list = Array.from(new Set(valid_tokens.map((x) => x.chainId))); - let token_list = chain_list.map((chain_id) => ({ - chainId: chain_id, - chainName: Tokens.chainInfo[chain_id], - tokens: valid_tokens.filter((x) => x.chainId == chain_id), - })); - return token_list; -} - -class Bridge { - constructor(web3, config, account, bridge_info, chain_hex, client_mode) { - this.web3 = web3; - this.config = config; - this.chain_hex_id = chain_hex; - this.client_mode = client_mode; - this.account = account; - this.chain_name = config.chain_name; - } - - async init(web3, config, account, bridge_info) { - await this.switch_net(); - this.bridge = Client.getContract(web3, config, bridge_info, account); - console.log(`init_bridge on %s`, this.chain_name); - const bi = await this.getBridgeInfo(); - const tokens = await this.allTokens(); - this.metadata = { - bridgeInfo: bi, - tokens: tokens, - chainInfo: extract_chain_info(tokens), - }; - } - - getTokenInfo(idx) { - const token = this.metadata.tokens[idx]; - let [cid, addr] = decode_l1address(token.token_uid); - return { - chainId: cid, - chainName: Tokens.chainInfo[cid], - tokenAddress: addr, - tokenName: - Tokens.tokenInfo.find( - (x) => hexcmp(x.address, addr) && x.chainId == cid - )?.name || "unknown", - }; - } - - /* address must start with 0x */ - encode_l1address(address) { - console.assert(address.substring(0, 2) == "0x"); - let address_hex = address.substring(2); - let chex = this.chain_hex_id.substring(2); - return encode_l1address(address_hex, chex); - } - - async switch_net() { - let id = await this.web3.eth.net.getId(); - let id_hex = "0x" + new BigNumber(id).toString(16); - console.log("switch chain", id_hex, this.chain_hex_id); - if (id_hex != this.chain_hex_id && this.client_mode == true) { - try { - await this.web3.currentProvider.request({ - method: "wallet_switchEthereumChain", - params: [{ chainId: this.chain_hex_id }], - }); - } catch (e) { - if (e.code == 4902) { - try { - await this.web3.currentProvider.request({ - method: "wallet_addEthereumChain", - params: [ - { - chainId: this.chain_hex_id, - chainName: this.chain_name, - rpcUrls: [this.config.rpc_source], - }, - ], - }); - await this.web3.currentProvider.request({ - method: "wallet_switchEthereumChain", - params: [{ chainId: this.chain_hex_id }], - }); - } catch (e) { - throw new Error("Add Network Rejected by User."); - } - } else { - throw new Error("Can not switch to chain " + this.chain_hex_id); - } - } - } - id = await this.web3.eth.net.getId(); - console.log("switched", id_hex, this.chain_hex_id); - return true; - } - - async getBridgeInfo() { - await this.switch_net(); - let vinfo = await this.bridge.methods.getBridgeInfo().call(); - return vinfo; - } - - async allTokens() { - await this.switch_net(); - let vinfo = await this.bridge.methods.allTokens().call(); - return vinfo; - } - - async addToken(tokenid) { - await this.switch_net(); - let tx = await this.bridge.methods.addToken(tokenid).send(); - return tx; - } - - getMetaData() { - return this.metadata; - } - - verify(l2account, calldata, verifydata, vid, nonce, rid) { - let pbinder = new PBinder.PromiseBinder(); - let r = pbinder.return(async () => { - await this.switch_net(); - let rx = await pbinder.bind( - "Verify", - this.bridge.methods - .verify(l2account, calldata, verifydata, vid, nonce, rid) - .send() - ); - return rx; - }); - console.log(r); - return r; - } - - deposit(token_address, amount, l2account) { - let pbinder = new PBinder.PromiseBinder(); - let r = pbinder.return(async () => { - let c = await this.switch_net(); - let token = Client.getContractByAddress( - this.web3, - token_address, - ERC20, - this.account - ); - pbinder.snapshot("Approve"); - var rx = await pbinder.bind( - "Approve", - token.methods.approve(this.bridge.options.address, amount).send() - ); - pbinder.snapshot("Deposit"); - rx = await pbinder.bind( - "Deposit", - this.bridge.methods.deposit(token_address, amount, l2account).send() - ); - return rx; - }); - return r; - } - - async close() { - this.web3.currentProvider.engine.stop(); - } -} - -async function getBridgeClient(config, bridge_info, client_mode) { - let web3 = await Client.initWeb3(config, client_mode); - let chain_id = config.device_id; - let chain_hex = "0x" + new BigNumber(chain_id).toString(16); - let account = await Client.getDefaultAccount(web3, config); - bridge = new Bridge( - web3, - config, - account, - bridge_info, - chain_hex, - client_mode - ); - await bridge.init(web3, config, account, bridge_info); - return bridge; -} - -module.exports = { - getBridgeClient: getBridgeClient, - encodeL1Address: encode_l1address, - decodeL1Address: decode_l1address, -}; diff --git a/clients/bridge/init.js b/clients/bridge/init.js deleted file mode 100644 index 42b9ebd..0000000 --- a/clients/bridge/init.js +++ /dev/null @@ -1,66 +0,0 @@ -const Web3 = require("web3"); -const Client = require("web3subscriber/client"); -const Config = require('../config'); -const TokenInfo = require("../../build/contracts/Token.json"); -const BridgeABI = require("./abi"); -const BridgeHelper = require("./bridge"); -const Secrets = require('../../.secrets'); -const fs = require("fs"); -const path = require("path"); - -const Tokens = require("./tokenlist"); - -function crunch_tokens() { - return Tokens.tokenInfo - .filter((x) => x.address) - .map((x) => - BridgeHelper.encodeL1Address(x.address, parseInt(x.chainId).toString(16)) - ); -} - -const test_config = { - l2account: - "0x7a50c8fa50a39bd48dfd8053ebff44ba3da45dd8c3e90a5fec9fd73a4595251b", -}; - -async function test_main(config_name) { - console.log("start calling"); - config = Config[config_name](Secrets); - try { - let bridge = await BridgeABI.getBridge(config, false); - let token = Client.getContract( - bridge.web3, - bridge.config, - TokenInfo, - bridge.account - ); - - let output = {}; - let index = 4; - - console.log("Testing bridge [id=%s]", bridge.chain_hex_id); - for (token_uid of crunch_tokens()) { - console.log("Adding token uid: 0x", token_uid.toString(16)); - let tx = await bridge.addToken(token_uid); - console.log(tx); - - output[token_uid] = index++; - } - - fs.writeFileSync( - path.resolve(__dirname, "..", "token-index.json"), - JSON.stringify(output, undefined, 2) - ); - - let info = await bridge.getBridgeInfo(); - console.log("bridge info is", info); - let tokens = await bridge.allTokens(); - console.log("token list is", tokens); - } catch (err) { - console.log("%s", err); - } -} - -test_main(process.argv[2]).then((v) => { - process.exit(); -}); diff --git a/clients/client.ts b/clients/client.ts new file mode 100644 index 0000000..bad69de --- /dev/null +++ b/clients/client.ts @@ -0,0 +1,127 @@ +import BN from "bn.js"; +import { + DelphinusWeb3, + Web3BrowsersMode, + Web3ProviderMode, +} from "web3subscriber/src/client"; +import { encodeL1address } from "web3subscriber/src/addresses"; +import { ChainConfig, ProviderType } from "delphinus-deployment/src/types"; +import { BridgeContract } from "./contracts/bridge"; +import { TokenContract } from "./contracts/token"; +import { RioContract } from "./contracts/rio"; +import { + DelphinusHDWalletProvider, + DelphinusHttpProvider, + DelphinusWsProvider, +} from "web3subscriber/src/provider"; + +const L1ADDR_BITS = 160; + +function getDelphinusProviderFromConfig(config: ChainConfig) { + switch (config.providerType) { + case ProviderType.WebsocketProvider: + return new DelphinusWsProvider(config.wsSource); + case ProviderType.HDWalletProvider: + return new DelphinusHDWalletProvider(config.privateKey, config.rpcSource); + case ProviderType.HttpProvider: + return new DelphinusHttpProvider(config.wsSource); + } +} + +export class L1Client { + readonly web3: DelphinusWeb3; + private readonly config: ChainConfig; + + constructor(config: ChainConfig, clientMode: boolean) { + if (clientMode) { + this.web3 = new Web3BrowsersMode(); + } else { + this.web3 = new Web3ProviderMode({ + provider: getDelphinusProviderFromConfig(config), + monitorAccount: config.monitorAccount, + }); + } + + this.config = config; + } + + async init() { + console.log(`init_bridge on %s`, this.config.chainName); + + await this.web3.connect(); + await this.switchNet(); + } + + async close() { + await this.web3.close(); + } + + getChainIdHex() { + return "0x" + new BN(this.config.deviceId).toString(16); + } + + getDefaultAccount() { + return this.web3.getDefaultAccount(); + } + + getBridgeContract(account?: string) { + return new BridgeContract( + this.web3, + BridgeContract.getContractAddress(this.config.deviceId), + account + ); + } + + getRioContract(address?: string, account?: string) { + return new RioContract( + this.web3, + address || RioContract.getContractAddress(this.config.deviceId), + account + ); + } + + getTokenContract(address?: string, account?: string) { + return new TokenContract( + this.web3, + address || TokenContract.getContractAddress(this.config.deviceId), + account + ); + } + + private async switchNet() { + await this.web3.switchNet( + this.getChainIdHex(), + this.config.chainName, + this.config.rpcSource + ); + } + + /** + * + * @param address address must start with 0x + * @returns + */ + encodeL1Address(address: string) { + if (address.substring(0, 2) != "0x") { + throw "address must start with 0x"; + } + + const addressHex = address.substring(2); + const chex = this.getChainIdHex().substring(2); + return encodeL1address(addressHex, chex); + } +} + +export async function withL1Client( + config: ChainConfig, + clientMode: boolean, + cb: (_: L1Client) => Promise +) { + const l1Client = new L1Client(config, clientMode); + await l1Client.init(); + try { + return await cb(l1Client); + } finally { + await l1Client.close(); + } +} diff --git a/clients/config.js b/clients/config.js deleted file mode 100644 index c723660..0000000 --- a/clients/config.js +++ /dev/null @@ -1,90 +0,0 @@ -const HDWalletProvider = require('@truffle/hdwallet-provider'); - -const Web3WsProvider = require('web3-providers-ws'); -const Web3HttpProvider = require('web3-providers-http'); - -const ws_options = { - timeout: 30000, // ms - - clientConfig: { - // Useful if requests are large - maxReceivedFrameSize: 100000000, // bytes - default: 1MiB - maxReceivedMessageSize: 100000000, // bytes - default: 8MiB - - // Useful to keep a connection alive - keepalive: true, - keepaliveInterval: 60000 // ms - }, - - // Enable auto reconnection - reconnect: { - auto: true, - delay: 5000, // ms - maxAttempts: 5, - onTimeout: true - } -}; - -const http_options = { - keepAlive: false, - timeout: 20000, // milliseconds, - withCredentials: false -}; - - -const ws_provider = (url) => { - let p = new Web3WsProvider(url, ws_options); - return p; -} - -const http_provider = (url) => { - let p = new Web3HttpProvider(url, http_options); - return p; -} - -module.exports = { - localtestnet1: () => {return { - provider: () => "ws://127.0.0.1:8546", - mongodb_url: "mongodb://localhost:27017", - ws_source: "ws://127.0.0.1:8546", - rpc_source: "ws://127.0.0.1:8545", - device_id: "15", - monitor_account: "0x6f6ef6dfe681b6593ddf27da3bfde22083aef88b", - chain_name: "localtestnet1", - }}, - localtestnet2: () => {return { - provider: () => "ws://127.0.0.1:8746", - mongodb_url: "mongodb://localhost:27017", - rpc_source: "http://127.0.0.1:8745", - ws_source: "ws://127.0.0.1:8746", - monitor_account: "0x6f6ef6dfe681b6593ddf27da3bfde22083aef88b", - device_id: "16", - chain_name: "localtestnet2", - }}, - bsctestnet: (secrets) => {return { - provider: () => new HDWalletProvider({ - privateKeys: [secrets.accounts.deployer.priv], - providerOrUrl: http_provider("https://bsc.getblock.io/testnet/?api_key="+secrets.getblock_key), - shareNonce: false - }), - mongodb_url: "mongodb://localhost:27017", - rpc_source: "https://bsc.getblock.io/testnet/?api_key=" + secrets.getblock_key, - ws_source: "wss://bsc.getblock.io/testnet/?api_key=" + secrets.getblock_key, - monitor_account: "0x6f6ef6dfe681b6593ddf27da3bfde22083aef88b", - device_id: "97", - chain_name: "bsctestnet", - }}, - ropsten: (secrets) => {return { - provider: () => new HDWalletProvider({ - privateKeys: [secrets.accounts.deployer.priv], - providerOrUrl: http_provider("https://ropsten.infura.io/v3/" + secrets.infura_id), - shareNonce: false - }), - mongodb_url: "mongodb://localhost:27017", - rpc_source: "https://ropsten.infura.io/v3/" + secrets.infura_id, - ws_source: "wss://ropsten.infura.io/ws/v3/" + secrets.infura_id, - monitor_account: "0x6f6ef6dfe681b6593ddf27da3bfde22083aef88b", - device_id: "3", - chain_name: "ropsten", - }} -} diff --git a/clients/contracts/bridge.ts b/clients/contracts/bridge.ts new file mode 100644 index 0000000..648508d --- /dev/null +++ b/clients/contracts/bridge.ts @@ -0,0 +1,186 @@ +import BN = require('bn.js'); +import { DelphinusContract, DelphinusWeb3 } from "web3subscriber/src/client"; +import { decodeL1address } from "web3subscriber/src/addresses"; +import { PromiseBinder } from "web3subscriber/src/pbinder"; +import { TokenContract } from "./token"; +import { Tokens, Chains } from "./tokenlist"; +const BridgeContractABI = require("../../build/contracts/Bridge.json"); + +/* + * Types + */ +export interface BridgeInfo { + chain_id: string; + amount_token: string; + amount_pool: string; + owner: string; + merkle_root: string; + rid: string; +} + +export interface TokenInfo { + token_uid: string; +} + +/* + * Events + */ +export interface Deposit { + l1token: string; + l2account: string; + amount: string; + nonce: string; +} + +export interface SwapAck { + l2account: string; + rid: string; +} + +export interface WithDraw { + l1account: string; + l2account: string; + amount: string; + nonce: string; +} + +function hexcmp(x: string, y: string) { + const xx = new BN(x, "hex"); + const yy = new BN(y, "hex"); + return xx.eq(yy); +} + +export class BridgeContract extends DelphinusContract { + constructor(web3: DelphinusWeb3, address: string, account?: string) { + super(web3, BridgeContract.getJsonInterface(), address, account); + } + + static getJsonInterface(): any { + return BridgeContractABI; + } + + static getContractAddress(chainId: string) { + return BridgeContractABI.networks[chainId].address; + } + + getBridgeInfo() { + return this.getWeb3Contract().methods.getBridgeInfo().call(); + } + + allTokens(): Promise { + return this.getWeb3Contract().methods.allTokens().call(); + } + + addToken(tokenid: BN) { + return this.getWeb3Contract().methods.addToken(tokenid).send(); + } + + private _verify( + l2account: string, + calldata: BN[], + verifydata: BN[], + vid: number, + nonce: number, + rid: BN + ) { + return this.getWeb3Contract() + .methods.verify(l2account, calldata, verifydata, vid, nonce, rid) + .send(); + } + + private _deposit(tokenAddress: string, amount: number, l2account: string) { + return this.getWeb3Contract() + .methods.deposit(tokenAddress, amount, l2account) + .send(); + } + + verify( + l2account: string, + calldata: BN[], + verifydata: BN[], + vid: number, + nonce: number, + rid: BN + ) { + const pbinder = new PromiseBinder(); + + return pbinder.return(async () => { + return await pbinder.bind( + "Verify", + this._verify(l2account, calldata, verifydata, vid, nonce, rid) + ); + }); + } + + deposit( + tokenContract: TokenContract, + amount: number, + l2account: string + ) { + const pbinder = new PromiseBinder(); + + return pbinder.return(async () => { + pbinder.snapshot("Approve"); + await pbinder.bind( + "Approve", + tokenContract.approve(this.address(), amount) + ); + pbinder.snapshot("Deposit"); + return await pbinder.bind( + "Deposit", + this._deposit(tokenContract.address(), amount, l2account) + ); + }); + } + + private async extractChainInfo() { + let tokenInfos = await this.allTokens(); + let tokens = tokenInfos + .filter((t) => t.token_uid != "0") + .map((token) => { + let [cid, address] = decodeL1address(token.token_uid); + return { + address: address, + name: + Tokens.find( + (x: any) => hexcmp(x.address, address) && x.chainId == cid + )?.name || "unknown", + chainId: cid, + index: tokenInfos.findIndex( + (x: TokenInfo) => x.token_uid == token.token_uid + ), + }; + }); + let chain_list = Array.from(new Set(tokens.map((x) => x.chainId))); + let token_list = chain_list.map((chain_id) => ({ + chainId: chain_id, + chainName: Chains[chain_id], + tokens: tokens.filter((x) => x.chainId == chain_id), + enable: true, + })); + return token_list; + } + + async getTokenInfo(idx: number) { + const token = (await this.allTokens())[idx]; + let [cid, addr] = decodeL1address(token.token_uid); + return { + chainId: cid, + chainName: Chains[cid], + tokenAddress: addr, + tokenName: + Tokens.find( + (x: any) => hexcmp(x.address, addr) && x.chainId == cid + )?.name || "unknown", + index: idx, + }; + } + + async getMetaData() { + return { + bridgeInfo: await this.getBridgeInfo(), + tokens: await this.allTokens(), + chainInfo: await this.extractChainInfo(), + }; + } +} diff --git a/clients/contracts/rio.ts b/clients/contracts/rio.ts new file mode 100644 index 0000000..94ddc84 --- /dev/null +++ b/clients/contracts/rio.ts @@ -0,0 +1,36 @@ +import { DelphinusContract, DelphinusWeb3 } from "web3subscriber/src/client"; + +const RioContractABI = require("../../build/contracts/Rio.json"); +// const TokenABI = require("../../build/contracts/IERC20.json"); + +export class RioContract extends DelphinusContract { + constructor(web3: DelphinusWeb3, address: string, account?: string) { + super(web3, RioContract.getJsonInterface(), address, account); + } + + static getJsonInterface(): any { + return RioContractABI; + } + + static getContractAddress(chainId: string) { + return RioContractABI.networks[chainId].address; + } + + approve(address: string, amount: number) { + return this.getWeb3Contract().methods.approve(address, amount).send(); + } + + balanceOf(account: string) { + return this.getWeb3Contract().methods.balanceOf(account).call(); + } + + mint(amount: number) { + return this.getWeb3Contract().methods.mint(amount).send(); + } + + transfer(address: string, amount: number) { + return this.getWeb3Contract() + .methods.transfer(address, amount) + .send(); + } +} diff --git a/clients/contracts/token.ts b/clients/contracts/token.ts new file mode 100644 index 0000000..8b032e5 --- /dev/null +++ b/clients/contracts/token.ts @@ -0,0 +1,36 @@ +import { DelphinusContract, DelphinusWeb3 } from "web3subscriber/src/client"; + +const TokenContractABI = require("../../build/contracts/Token.json"); +// const TokenABI = require("../../build/contracts/IERC20.json"); + +export class TokenContract extends DelphinusContract { + constructor(web3: DelphinusWeb3, address: string, account?: string) { + super(web3, TokenContract.getJsonInterface(), address, account); + } + + static getJsonInterface(): any { + return TokenContractABI; + } + + static getContractAddress(chainId: string) { + return TokenContractABI.networks[chainId].address; + } + + approve(address: string, amount: number) { + return this.getWeb3Contract().methods.approve(address, amount).send(); + } + + balanceOf(account: string) { + return this.getWeb3Contract().methods.balanceOf(account).call(); + } + + mint(amount: number) { + return this.getWeb3Contract().methods.mint(amount).send(); + } + + transfer(address: string, amount: number) { + return this.getWeb3Contract() + .methods.transfer(address, amount) + .send(); + } +} diff --git a/clients/bridge/tokenlist.js b/clients/contracts/tokenlist.ts similarity index 89% rename from clients/bridge/tokenlist.js rename to clients/contracts/tokenlist.ts index 6e36c04..adcf61b 100644 --- a/clients/bridge/tokenlist.js +++ b/clients/contracts/tokenlist.ts @@ -3,14 +3,15 @@ const TokenInfo = require("../../build/contracts/Token.json"); const RioInfo = require("../../build/contracts/Rio.json"); -const chain_info = { + +export const Chains : Record = { "15": "local-test-net1", "16": "local-test-net2", "3": "ropsten", "97": "bsctestnet", } -const token_info = [ +export const Tokens = [ { chainId: "15", address:TokenInfo.networks["15"]?.address.replace("0x", ""), @@ -41,9 +42,4 @@ const token_info = [ address:TokenInfo.networks["97"]?.address.replace("0x", ""), name:"sToken" }, -]; - -module.exports = { - chainInfo: chain_info, - tokenInfo: token_info, -} +]; \ No newline at end of file diff --git a/clients/token-index.json b/clients/token-index.json deleted file mode 100644 index 3619688..0000000 --- a/clients/token-index.json +++ /dev/null @@ -1,5 +0,0 @@ -{ - "5195817369820449998442598550148912025672908262375": 4, - "4458373458645478945299645731909581084615981406885": 5, - "142918609290775582167001084962377293926986042917364": 6 -} \ No newline at end of file diff --git a/clients/token/mint.js b/clients/token/mint.js deleted file mode 100644 index ba72cbd..0000000 --- a/clients/token/mint.js +++ /dev/null @@ -1,37 +0,0 @@ -const Web3 = require("web3") -const FileSys = require("fs") -const Config = require("../config") -const Client = require("web3subscriber/client") -const PBinder= require("web3subscriber/pbinder") -const TokenInfo = require("../../build/contracts/Token.json") -const Secrets = require('../../.secrets'); - -function test_mint(config_name, target_account) { - let pbinder = new PBinder.PromiseBinder(); - let r = pbinder.return (async () => { - config = Config[config_name](Secrets); - let account = config.monitor_account; - let web3 = await Client.initWeb3(config, false); - let token = Client.getContract(web3, config, TokenInfo, account); - await web3.eth.net.getId(); - try { - console.log("mint token:", token.options.address); - var balance = await token.methods.balanceOf(account).call(); - console.log("sender: balance before mint:", balance); - await pbinder.bind("mint", token.methods.mint(0x10000000).send()); - balance = await Client.getBalance(token, account); - console.log("sender: balance after mint", balance); - if (target_account) { - await pbinder.bind("transfer", token.methods.transfer(target_account, 0x10000000).send()); - balance = await Client.getBalance(token, target_account); - console.log("balance of recipient after transfer", balance); - } - } catch (err) { - console.log("%s", err); - } - }); - return r; -} - -/* .once("transactionHash",hash => console.log(hash) */ -test_mint(process.argv[2], process.argv[3]).when("mint","transactionHash", hash=>console.log(hash)).then(v => {console.log("test done!"); process.exit();}); diff --git a/clients/bridge/deposit.js b/clients/tools/bridge/deposit.js similarity index 96% rename from clients/bridge/deposit.js rename to clients/tools/bridge/deposit.js index 666f8c1..b3b8cc3 100644 --- a/clients/bridge/deposit.js +++ b/clients/tools/bridge/deposit.js @@ -1,6 +1,6 @@ const Web3 = require("web3") const FileSys = require("fs") -const Client = require("web3subscriber/client"); +const Client = require("web3subscriber/src/client"); const Config = require('../config'); const BridgeABI = require('./abi'); const TokenInfo = require("../../build/contracts/Token.json"); diff --git a/clients/tools/bridge/init.ts b/clients/tools/bridge/init.ts new file mode 100644 index 0000000..8510ae8 --- /dev/null +++ b/clients/tools/bridge/init.ts @@ -0,0 +1,51 @@ +import { getConfigByChainName } from "delphinus-deployment/src/config"; +import { L1Client, withL1Client } from "../../client"; +import { encodeL1address } from "web3subscriber/src/addresses"; +import { Tokens } from "../../contracts/tokenlist"; +import { L1ClientRole } from "delphinus-deployment/src/types"; + +const fs = require("fs"); +const path = require("path"); + +function crunchTokens() { + return Tokens + .filter((x: any) => x.address) + .map((x: any) => + encodeL1address(x.address, parseInt(x.chainId).toString(16)) + ); +} + +async function main(config_name: string) { + console.log("start calling"); + let config = await getConfigByChainName(L1ClientRole.Monitor, config_name); + try { + await withL1Client(config, false, async (l1client: L1Client) => { + let bridge = l1client.getBridgeContract(); + let output: any = {}; + let index = 0; + + console.log("Testing bridge [id=%s]", l1client.getChainIdHex()); + for (let tokenUid of crunchTokens()) { + console.log("Adding token uid: 0x", tokenUid.toString(16)); + let tx = await bridge.addToken(tokenUid); + console.log(tx); + + output[tokenUid.toString()] = index++; + } + + fs.writeFileSync( + path.resolve(__dirname, "../../../../deployment/src/local", "token-index.json"), + JSON.stringify(output, undefined, 2) + ); + + let info = await bridge.getBridgeInfo(); + console.log("bridge info is", info); + let tokens = await bridge.allTokens(); + console.log("token list is", tokens); + }); + } catch (err) { + console.log("%s", err); + } +} + +main(process.argv[2]); diff --git a/clients/bridge/status.js b/clients/tools/bridge/status.js similarity index 95% rename from clients/bridge/status.js rename to clients/tools/bridge/status.js index 74dd034..5a63caa 100644 --- a/clients/bridge/status.js +++ b/clients/tools/bridge/status.js @@ -1,6 +1,6 @@ const Web3 = require("web3") const FileSys = require("fs") -const Client = require("web3subscriber/client"); +const Client = require("web3subscriber/src/client"); const Config = require('../config'); const BridgeABI = require('./abi'); const TokenInfo = require("../../build/contracts/Token.json"); diff --git a/clients/token/allowance.js b/clients/tools/token/allowance.js similarity index 93% rename from clients/token/allowance.js rename to clients/tools/token/allowance.js index 0c86a91..7a3157a 100644 --- a/clients/token/allowance.js +++ b/clients/tools/token/allowance.js @@ -1,6 +1,6 @@ const Web3 = require("web3") const Config = require("../config") -const Client = require("web3subscriber/client") +const Client = require("web3subscriber/src/client") const TokenInfo = require("../../build/contracts/Token.json") const Utils = require("../utils"); const Secrets = require('../../.secrets'); diff --git a/clients/token/balance.js b/clients/tools/token/balance.js similarity index 93% rename from clients/token/balance.js rename to clients/tools/token/balance.js index fba46e6..f431841 100644 --- a/clients/token/balance.js +++ b/clients/tools/token/balance.js @@ -1,6 +1,6 @@ const Web3 = require("web3") const Config = require("../config") -const Client = require("web3subscriber/client") +const Client = require("web3subscriber/src/client") const TokenInfo = require("../../build/contracts/Token.json") const Utils = require("../utils"); const Secrets = require('../../.secrets'); diff --git a/clients/token/mint-rio.js b/clients/tools/token/mint-rio-old.js similarity index 93% rename from clients/token/mint-rio.js rename to clients/tools/token/mint-rio-old.js index c7a60e1..9ed638d 100644 --- a/clients/token/mint-rio.js +++ b/clients/tools/token/mint-rio-old.js @@ -1,8 +1,8 @@ const Web3 = require("web3") const FileSys = require("fs") const Config = require("../config") -const Client = require("web3subscriber/client") -const PBinder= require("web3subscriber/pbinder") +const Client = require("web3subscriber/src/client") +const PBinder= require("web3subscriber/src/pbinder") const TokenInfo = require("../../build/contracts/Rio.json") const Secrets = require('../../.secrets'); diff --git a/clients/tools/token/mint-rio.ts b/clients/tools/token/mint-rio.ts new file mode 100644 index 0000000..c1dcf78 --- /dev/null +++ b/clients/tools/token/mint-rio.ts @@ -0,0 +1,42 @@ +import { withL1Client, L1Client } from "../../client"; +import { getConfigByChainName } from "delphinus-deployment/src/config"; +import { PromiseBinder } from "web3subscriber/src/pbinder"; +import { L1ClientRole } from "delphinus-deployment/src/types"; + +async function main(configName: string, targetAccount: string) { + let config = await getConfigByChainName(L1ClientRole.Monitor, configName); + let account = config.monitorAccount; + let pbinder = new PromiseBinder(); + let r = pbinder.return(async () => { + await withL1Client(config, false, async (l1client: L1Client) => { + let token = l1client.getRioContract(); + // await web3.eth.net.getId(); + try { + console.log("mint token:", token.address()); + let balance = await token.balanceOf(account); + console.log("sender: balance before mint:", balance); + await pbinder.bind("mint", token.mint(0x100000000000)); + balance = await token.balanceOf(account); + console.log("sender: balance after mint", balance); + if (targetAccount) { + await pbinder.bind( + "transfer", + token.transfer(targetAccount, 0x100000000000) + ); + balance = await token.balanceOf(targetAccount); + console.log("balance of recipient after transfer", balance); + } + } catch (err) { + console.log("%s", err); + } + }); + }); + await r.when( + "mint", + "transactionHash", + (hash: string) => console.log(hash) + ); +} + +/* .once("transactionHash",hash => console.log(hash) */ +main(process.argv[2], process.argv[3]); diff --git a/clients/tools/token/mint.ts b/clients/tools/token/mint.ts new file mode 100644 index 0000000..0b15e38 --- /dev/null +++ b/clients/tools/token/mint.ts @@ -0,0 +1,42 @@ +import { withL1Client, L1Client } from "../../client"; +import { getConfigByChainName } from "delphinus-deployment/src/config"; +import { PromiseBinder } from "web3subscriber/src/pbinder"; +import { L1ClientRole } from "delphinus-deployment/src/types"; + +async function main(configName: string, targetAccount: string) { + let config = await getConfigByChainName(L1ClientRole.Monitor, configName); + let account = config.monitorAccount; + let pbinder = new PromiseBinder(); + let r = pbinder.return(async () => { + await withL1Client(config, false, async (l1client: L1Client) => { + let token = l1client.getTokenContract(); + // await web3.eth.net.getId(); + try { + console.log("mint token:", token.address()); + let balance = await token.balanceOf(account); + console.log("sender: balance before mint:", balance); + await pbinder.bind("mint", token.mint(0x10000000)); + balance = await token.balanceOf(account); + console.log("sender: balance after mint", balance); + if (targetAccount) { + await pbinder.bind( + "transfer", + token.transfer(targetAccount, 0x10000000) + ); + balance = await token.balanceOf(targetAccount); + console.log("balance of recipient after transfer", balance); + } + } catch (err) { + console.log("%s", err); + } + }); + }); + await r.when( + "mint", + "transactionHash", + (hash: string) => console.log(hash) + ); +} + +/* .once("transactionHash",hash => console.log(hash) */ +main(process.argv[2], process.argv[3]); \ No newline at end of file diff --git a/clients/tx/nonce.js b/clients/tx/nonce.js index ec919ea..91bf7f8 100644 --- a/clients/tx/nonce.js +++ b/clients/tx/nonce.js @@ -1,8 +1,8 @@ const Web3 = require("web3") const FileSys = require("fs") const Config = require("../config") -const Client = require("web3subscriber/client") -const PBinder= require("web3subscriber/pbinder") +const Client = require("web3subscriber/src/client") +const PBinder= require("web3subscriber/src/pbinder") const TokenInfo = require("../../build/contracts/Token.json") const Secrets = require('../../.secrets'); const BridgeABI = require("../bridge/abi"); diff --git a/clients/tx/tx.js b/clients/tx/tx.js index bc5297a..f019374 100644 --- a/clients/tx/tx.js +++ b/clients/tx/tx.js @@ -1,8 +1,8 @@ const Web3 = require("web3") const FileSys = require("fs") const Config = require("../config") -const Client = require("web3subscriber/client") -const PBinder= require("web3subscriber/pbinder") +const Client = require("web3subscriber/src/client") +const PBinder= require("web3subscriber/src/pbinder") const TokenInfo = require("../../build/contracts/Token.json") const Secrets = require('../../.secrets'); diff --git a/contracts/Bridge.sol b/contracts/Bridge.sol index ee3b58e..4833a8b 100644 --- a/contracts/Bridge.sol +++ b/contracts/Bridge.sol @@ -1,6 +1,7 @@ // SPDX-License-Identifier: UNLICENSED pragma solidity ^0.8.0; import "@openzeppelin/contracts/token/ERC20/ERC20.sol"; +import "@openzeppelin/contracts/token/ERC721/ERC721.sol"; import "./Verifier.sol"; import "./Transaction.sol"; import "./MKT.sol"; @@ -9,6 +10,7 @@ contract Bridge { event Deposit(uint256 l1token, uint256 l2account, uint256 amount, uint256 nonce); event WithDraw(uint256 l1account, uint256 l2account, uint256 amount, uint256 nonce); event SwapAck(uint256 l2account, uint256 rid); + event DepositNFT(uint256 nftaddr, uint256 l2account, uint256 tokenId, uint256 nonce); BridgeInfo _bridge_info; @@ -16,21 +18,18 @@ contract Bridge { DelphinusVerifier[] private verifiers; TokenInfo[] private _tokens; + NFTAddressInfo[] private _nft_addresses; + mapping (uint256 => bool) private _tmap; address private _owner; mapping (uint256 => uint256) private _nonce; - constructor(uint32 chain_id) { _bridge_info.chain_id = chain_id; _bridge_info.owner = msg.sender; _bridge_info.merkle_root = 0x151399c724e17408a7a43cdadba2fc000da9339c56e4d49c6cdee6c4356fbc68; - addToken(0); - addToken(0); - addToken(0); - addToken(0); } /* Make sure token index is sain */ @@ -38,12 +37,23 @@ contract Bridge { require(tidx < _bridge_info.amount_token, "OutOfBound: Token Index"); } + /* Make sure nft index is sain */ + function nft_index_check(uint128 nidx) private view { + require(nidx < _bridge_info.amount_nft_contract, "OutOfBound: NFT Contract Index"); + } + /* Make sure token index is sain and return token uid */ function get_token_uid(uint128 tidx) private view returns (uint256){ token_index_check(tidx); return _tokens[tidx].token_uid; } + /* Make sure token index is sain and return nft address*/ + function get_nft_address(uint128 nidx) private view returns (uint256){ + nft_index_check(nidx); + return _nft_addresses[nidx].nft_address; + } + function ensure_admin() private view { require(_bridge_info.owner == msg.sender, "Authority: Require Admin"); } @@ -88,6 +98,21 @@ contract Bridge { } } + function _withdraw_nft(uint128 nidx, uint256 nftid, uint256 l1recipent) public { + uint256 nft_addr = get_nft_address(nidx); + if (_is_local(nft_addr) && _is_local(l1recipent)) { + address nft = address(uint160(nft_addr)); + address recipent = address(uint160(l1recipent)); + + // Sanitity checks + require(recipent!= address(0), "WithdrawNFT to the zero address"); + + // transfer amount back to recipent + IERC721 underlying_nft = IERC721(nft); + underlying_nft.transferFrom(address(this), recipent, nftid); + } + } + function addTransaction(address txaddr) public returns (uint) { ensure_admin(); uint cursor = transactions.length; @@ -141,6 +166,14 @@ contract Bridge { emit Deposit(_l1_address(token), l2account, amount , _nonce[l2account]); } + function deposit_nft(address nft, uint256 nftId, uint256 l2account) public { + IERC721 underlying_nft = IERC721(nft); + uint256 nft_address = _l1_address(nft); + underlying_nft.transferFrom(msg.sender, address(this), nftId); + _nonce[l2account] +=1; + emit DepositNFT(nft_address, l2account, nftId, _nonce[l2account]); + } + function nonceOf(uint256 l2account) public view returns(uint256) { return _nonce[l2account]; } diff --git a/contracts/MKT.sol b/contracts/MKT.sol index a0883ef..bf51132 100644 --- a/contracts/MKT.sol +++ b/contracts/MKT.sol @@ -12,9 +12,14 @@ struct TokenInfo { uint256 token_uid; } +struct NFTAddressInfo { + uint256 nft_address; +} + struct BridgeInfo { uint128 chain_id; uint32 amount_token; + uint32 amount_nft_contract; uint32 amount_pool; address owner; uint256 merkle_root; diff --git a/package.json b/package.json index 8373deb..e73ff3e 100644 --- a/package.json +++ b/package.json @@ -12,15 +12,18 @@ "react": "^17.0.2", "truffle": "latest", "web3": "1.5.3", - "web3subscriber": "*" + "web3subscriber": "*", + "delphinus-deployment": "*" }, "scripts": { "dev": "npx truffle dev", "migrate1": "cp ./truffle-config1.js ./truffle-config.js && npx truffle migrate", - "migrate2": "cp ./truffle-config2.js ./truffle-config.js && npx truffle migrate" + "migrate2": "cp ./truffle-config2.js ./truffle-config.js && npx truffle migrate", + "prepare": "npx tsc" }, "devDependencies": { "@webpack-cli/serve": "^1.3.1", + "typescript": "^4.4.4", "webpack-cli": "^4.6.0" } } diff --git a/truffle-config.js b/truffle-config.js index acb66c1..5c6606b 100644 --- a/truffle-config.js +++ b/truffle-config.js @@ -23,7 +23,7 @@ const Web3HttpProvider = require('web3-providers-http'); const HDWalletProvider = require('@truffle/hdwallet-provider'); const fs = require('fs'); -const secrets = require('./.secrets.json'); +const secrets = require('delphinus-deployment/server/config/monitor-secrets.json'); const http_options = { keepAlive: true, diff --git a/tsconfig.json b/tsconfig.json new file mode 100644 index 0000000..120a2a7 --- /dev/null +++ b/tsconfig.json @@ -0,0 +1,71 @@ +{ + "compilerOptions": { + /* Visit https://aka.ms/tsconfig.json to read more about this file */ + + /* Basic Options */ + // "incremental": true, /* Enable incremental compilation */ + "target": "ES2015", /* Specify ECMAScript target version: 'ES3' (default), 'ES5', 'ES2015', 'ES2016', 'ES2017', 'ES2018', 'ES2019', 'ES2020', 'ES2021', or 'ESNEXT'. */ + "module": "commonjs", /* Specify module code generation: 'none', 'commonjs', 'amd', 'system', 'umd', 'es2015', 'es2020', or 'ESNext'. */ + // "lib": [], /* Specify library files to be included in the compilation. */ + // "allowJs": true, /* Allow javascript files to be compiled. */ + // "checkJs": true, /* Report errors in .js files. */ + // "jsx": "preserve", /* Specify JSX code generation: 'preserve', 'react-native', 'react', 'react-jsx' or 'react-jsxdev'. */ + // "declaration": true, /* Generates corresponding '.d.ts' file. */ + // "declarationMap": true, /* Generates a sourcemap for each corresponding '.d.ts' file. */ + "sourceMap": true, /* Generates corresponding '.map' file. */ + // "outFile": "./", /* Concatenate and emit output to single file. */ + "rootDir": ".", /* Specify the root directory of input files. Use to control the output directory structure with --outDir. */ + // "composite": true, /* Enable project compilation */ + // "tsBuildInfoFile": "./", /* Specify file to store incremental compilation information */ + // "removeComments": true, /* Do not emit comments to output. */ + // "noEmit": true, /* Do not emit outputs. */ + // "importHelpers": true, /* Import emit helpers from 'tslib'. */ + // "downlevelIteration": true, /* Provide full support for iterables in 'for-of', spread, and destructuring when targeting 'ES5' or 'ES3'. */ + // "isolatedModules": true, /* Transpile each file as a separate module (similar to 'ts.transpileModule'). */ + + /* Strict Type-Checking Options */ + "strict": true, /* Enable all strict type-checking options. */ + // "noImplicitAny": true, /* Raise error on expressions and declarations with an implied 'any' type. */ + // "strictNullChecks": true, /* Enable strict null checks. */ + // "strictFunctionTypes": true, /* Enable strict checking of function types. */ + // "strictBindCallApply": true, /* Enable strict 'bind', 'call', and 'apply' methods on functions. */ + // "strictPropertyInitialization": true, /* Enable strict checking of property initialization in classes. */ + // "noImplicitThis": true, /* Raise error on 'this' expressions with an implied 'any' type. */ + // "alwaysStrict": true, /* Parse in strict mode and emit "use strict" for each source file. */ + + /* Additional Checks */ + // "noUnusedLocals": true, /* Report errors on unused locals. */ + // "noUnusedParameters": true, /* Report errors on unused parameters. */ + // "noImplicitReturns": true, /* Report error when not all code paths in function return a value. */ + // "noFallthroughCasesInSwitch": true, /* Report errors for fallthrough cases in switch statement. */ + // "noUncheckedIndexedAccess": true, /* Include 'undefined' in index signature results */ + // "noImplicitOverride": true, /* Ensure overriding members in derived classes are marked with an 'override' modifier. */ + // "noPropertyAccessFromIndexSignature": true, /* Require undeclared properties from index signatures to use element accesses. */ + + /* Module Resolution Options */ + // "moduleResolution": "node", /* Specify module resolution strategy: 'node' (Node.js) or 'classic' (TypeScript pre-1.6). */ + // "baseUrl": "./", /* Base directory to resolve non-absolute module names. */ + // "paths": {}, /* A series of entries which re-map imports to lookup locations relative to the 'baseUrl'. */ + // "rootDirs": [], /* List of root folders whose combined content represents the structure of the project at runtime. */ + // "typeRoots": [], /* List of folders to include type definitions from. */ + // "types": [], /* Type declaration files to be included in compilation. */ + // "allowSyntheticDefaultImports": true, /* Allow default imports from modules with no default export. This does not affect code emit, just typechecking. */ + "esModuleInterop": true, /* Enables emit interoperability between CommonJS and ES Modules via creation of namespace objects for all imports. Implies 'allowSyntheticDefaultImports'. */ + // "preserveSymlinks": true, /* Do not resolve the real path of symlinks. */ + // "allowUmdGlobalAccess": true, /* Allow accessing UMD globals from modules. */ + + /* Source Map Options */ + // "sourceRoot": "", /* Specify the location where debugger should locate TypeScript files instead of source locations. */ + // "mapRoot": "", /* Specify the location where debugger should locate map files instead of generated locations. */ + // "inlineSourceMap": true, /* Emit a single file with source maps instead of having a separate file. */ + // "inlineSources": true, /* Emit the source alongside the sourcemaps within a single file; requires '--inlineSourceMap' or '--sourceMap' to be set. */ + + /* Experimental Options */ + // "experimentalDecorators": true, /* Enables experimental support for ES7 decorators. */ + // "emitDecoratorMetadata": true, /* Enables experimental support for emitting type metadata for decorators. */ + + /* Advanced Options */ + "skipLibCheck": true, /* Skip type checking of declaration files. */ + "forceConsistentCasingInFileNames": true /* Disallow inconsistently-cased references to the same file. */ + } +}