rainbow-bridge-lib
Version:
Rainbow Bridge Lib
418 lines (390 loc) • 12.5 kB
JavaScript
const utils = require('ethereumjs-util')
const BN = require('bn.js')
const fs = require('fs')
const nearlib = require('near-api-js')
const {
EthProofExtractor,
receiptFromWeb3,
logFromWeb3,
} = require('../eth-proof-extractor')
const { tokenAddressParam, tokenAccountParam } = require('./deploy-token')
const { verifyAccount } = require('../rainbow/helpers')
const { NearMintableToken } = require('../near-mintable-token')
const { RainbowConfig } = require('../config')
const { EthOnNearClientContract } = require('../eth-on-near-client')
const { sleep, RobustWeb3, normalizeEthKey } = require('../rainbow/robust')
let initialCmd
class TransferETHERC20ToNear {
static showRetryAndExit() {
console.log('Retry with command:')
console.log(initialCmd)
process.exit(1)
}
static async approve({
robustWeb3,
ethERC20Contract,
amount,
ethSenderAccount,
}) {
// Approve tokens for transfer.
const lockerAddress = RainbowConfig.getParam('eth-locker-address')
try {
console.log(
`Approving token transfer to ${lockerAddress} ${Number(amount)}.`
)
await robustWeb3.callContract(
ethERC20Contract,
'approve',
[lockerAddress, Number(amount)],
{
from: ethSenderAccount,
gas: 5000000,
}
)
console.log('Approved token transfer.')
TransferETHERC20ToNear.recordTransferLog({ finished: 'approve' })
} catch (txRevertMessage) {
console.log('Failed to approve.')
console.log(txRevertMessage.toString())
TransferETHERC20ToNear.showRetryAndExit()
}
}
static async lock({
robustWeb3,
ethTokenLockerContract,
tokenAddress,
amount,
nearReceiverAccount,
ethSenderAccount,
}) {
try {
console.log(
`Transferring tokens from the ERC20 account to the token locker account ${Number(
amount
)}.`
)
const transaction = await robustWeb3.callContract(
ethTokenLockerContract,
'lockToken',
[tokenAddress, Number(amount), nearReceiverAccount],
{
from: ethSenderAccount,
gas: 5000000,
}
)
console.log(transaction)
const lockedEvent = transaction.events.Locked
console.log('Success tranfer to locker')
TransferETHERC20ToNear.recordTransferLog({
finished: 'lock',
lockedEvent,
})
} catch (txRevertMessage) {
console.log('Failed to lock account.')
console.log(txRevertMessage.toString())
TransferETHERC20ToNear.showRetryAndExit()
}
}
static async findProof({ extractor, lockedEvent, web3 }) {
const receipt = await extractor.extractReceipt(lockedEvent.transactionHash)
const block = await extractor.extractBlock(receipt.blockNumber)
const tree = await extractor.buildTrie(block)
const proof = await extractor.extractProof(
web3,
block,
tree,
receipt.transactionIndex
)
let txLogIndex = -1
let logFound = false
let log
for (let receiptLog of receipt.logs) {
txLogIndex++
const blockLogIndex = receiptLog.logIndex
if (blockLogIndex === lockedEvent.logIndex) {
logFound = true
log = receiptLog
break
}
}
if (logFound) {
TransferETHERC20ToNear.recordTransferLog({
finished: 'find-proof',
proof,
log,
txLogIndex,
receipt,
lockedEvent,
block,
})
} else {
console.log(`Failed to find log for event ${lockedEvent}`)
TransferETHERC20ToNear.showRetryAndExit()
}
}
static async waitBlockSafe({
log,
proof,
receipt,
txLogIndex,
lockedEvent,
block,
ethOnNearClientContract,
}) {
const log_entry_data = logFromWeb3(log).serialize()
const receipt_index = proof.txIndex
const receipt_data = receiptFromWeb3(receipt).serialize()
const header_data = proof.header_rlp
const _proof = []
for (const node of proof.receiptProof) {
_proof.push(utils.rlp.encode(node))
}
const proof_locker = {
log_index: txLogIndex,
log_entry_data: log_entry_data,
receipt_index: receipt_index,
receipt_data: receipt_data,
header_data: header_data,
proof: _proof,
}
const new_owner_id = lockedEvent.returnValues.accountId
const amount = lockedEvent.returnValues.amount
console.log(
`Transferring ${amount} tokens from ${lockedEvent.returnValues.token} ERC20. From ${lockedEvent.returnValues.sender} sender to ${new_owner_id} recipient`
)
const blockNumber = block.number
// Wait until client accepts this block number.
while (true) {
// @ts-ignore
const last_block_number = (
await ethOnNearClientContract.last_block_number()
).toNumber()
const is_safe = await ethOnNearClientContract.block_hash_safe(blockNumber)
if (!is_safe) {
const delay = 10
console.log(
`Near Client contract is currently at block ${last_block_number}. Waiting for block ${blockNumber} to be confirmed. Sleeping for ${delay} sec.`
)
await sleep(delay * 1000)
} else {
break
}
}
TransferETHERC20ToNear.recordTransferLog({
finished: 'block-safe',
proof_locker,
new_owner_id,
})
}
static async deposit({
proof_locker,
nearFactoryContract,
nearFactoryContractBorsh,
nearTokenContract,
new_owner_id,
}) {
// @ts-ignore
const old_balance = await nearTokenContract.get_balance({
owner_id: new_owner_id,
})
console.log(
`Balance of ${new_owner_id} before the transfer is ${old_balance}`
)
// @ts-ignore
try {
await nearFactoryContractBorsh.deposit(
proof_locker,
new BN('300000000000000'),
// We need to attach tokens because minting increases the contract state, by <600 bytes, which
// requires an additional 0.06 NEAR to be deposited to the account for state staking.
// Note technically 0.0537 NEAR should be enough, but we round it up to stay on the safe side.
new BN('100000000000000000000').mul(new BN('600'))
)
console.log('Transferred')
} catch (e) {
console.log('Deposit failed with error:')
console.log(e)
TransferETHERC20ToNear.showRetryAndExit()
}
// @ts-ignore
const new_balance = await nearTokenContract.get_balance({
owner_id: new_owner_id,
})
console.log(
`Balance of ${new_owner_id} after the transfer is ${new_balance}`
)
TransferETHERC20ToNear.deleteTransferLog()
}
static recordTransferLog(obj) {
fs.writeFileSync('transfer-eth-erc20-to-near.log.json', JSON.stringify(obj))
}
static parseBuffer(obj) {
for (let i in obj) {
if (obj[i] && obj[i].type === 'Buffer') {
obj[i] = Buffer.from(obj[i].data)
} else if (obj[i] && typeof obj[i] === 'object') {
obj[i] = TransferETHERC20ToNear.parseBuffer(obj[i])
}
}
return obj
}
static loadTransferLog() {
try {
let log =
JSON.parse(
fs.readFileSync('transfer-eth-erc20-to-near.log.json').toString()
) || {}
return TransferETHERC20ToNear.parseBuffer(log)
} catch (e) {
return {}
}
}
static deleteTransferLog() {
try {
fs.unlinkSync('transfer-eth-erc20-to-near.log.json')
} catch (e) {
console.log('Warning: failed to remove tranfer log')
}
}
static async execute(command) {
initialCmd = command.parent.rawArgs.join(' ')
let transferLog = TransferETHERC20ToNear.loadTransferLog()
const amount = command.amount
const ethSenderSk = command.ethSenderSk
const nearReceiverAccount = command.nearReceiverAccount
const tokenAddress = command.tokenName
? RainbowConfig.getParam(tokenAddressParam(command.tokenName))
: RainbowConfig.getParam('eth-erc20-address')
const tokenAccount = command.tokenName
? RainbowConfig.getParam(tokenAccountParam(command.tokenName))
: RainbowConfig.getParam('near-erc20-account')
console.log(`Using ETH address ${tokenAddress}`)
// @ts-ignore
let robustWeb3 = new RobustWeb3(RainbowConfig.getParam('eth-node-url'))
let web3 = robustWeb3.web3
let ethSenderAccount = web3.eth.accounts.privateKeyToAccount(
normalizeEthKey(ethSenderSk)
)
web3.eth.accounts.wallet.add(ethSenderAccount)
web3.eth.defaultAccount = ethSenderAccount.address
ethSenderAccount = ethSenderAccount.address
const ethERC20Contract = new web3.eth.Contract(
// @ts-ignore
JSON.parse(fs.readFileSync(RainbowConfig.getParam('eth-erc20-abi-path'))),
tokenAddress
)
const nearMasterAccountId = RainbowConfig.getParam('near-master-account')
console.log(nearMasterAccountId)
// @ts-ignore
const keyStore = new nearlib.keyStores.InMemoryKeyStore()
await keyStore.setKey(
RainbowConfig.getParam('near-network-id'),
nearMasterAccountId,
nearlib.KeyPair.fromString(RainbowConfig.getParam('near-master-sk'))
)
const near = await nearlib.connect({
nodeUrl: RainbowConfig.getParam('near-node-url'),
networkId: RainbowConfig.getParam('near-network-id'),
masterAccount: nearMasterAccountId,
deps: { keyStore: keyStore },
})
const nearMasterAccount = new nearlib.Account(
near.connection,
nearMasterAccountId
)
await verifyAccount(near, nearMasterAccountId)
const nearFactoryContract = new nearlib.Contract(
nearMasterAccount,
RainbowConfig.getParam('near-token-factory-account'),
{
changeMethods: ['deposit'],
viewMethods: [],
}
)
const nearTokenContract = new nearlib.Contract(
nearMasterAccount,
tokenAccount,
{
changeMethods: [],
viewMethods: ['get_balance'],
}
)
const nearFactoryContractBorsh = new NearMintableToken(
nearMasterAccount,
RainbowConfig.getParam('near-token-factory-account')
)
await nearFactoryContractBorsh.accessKeyInit()
const extractor = new EthProofExtractor()
extractor.initialize(RainbowConfig.getParam('eth-node-url'))
const ethTokenLockerContract = new web3.eth.Contract(
// @ts-ignore
JSON.parse(
fs.readFileSync(RainbowConfig.getParam('eth-locker-abi-path'))
),
RainbowConfig.getParam('eth-locker-address')
)
const clientAccount = RainbowConfig.getParam('near-client-account')
const ethOnNearClientContract = new EthOnNearClientContract(
nearMasterAccount,
clientAccount
)
if (transferLog.finished === undefined) {
// TODO fix before using
// Mint tokens first???
/*await ethERC20Contract.methods
.mint(ethSenderAccount, Number(amount))
.send({ from: ethSenderAccount, gas: 5000000 })*/
console.log(
'Balance: ',
await ethERC20Contract.methods.balanceOf(ethSenderAccount).call()
)
await TransferETHERC20ToNear.approve({
robustWeb3,
ethERC20Contract,
amount,
ethSenderAccount,
})
transferLog = TransferETHERC20ToNear.loadTransferLog()
}
if (transferLog.finished === 'approve') {
await TransferETHERC20ToNear.lock({
robustWeb3,
ethTokenLockerContract,
tokenAddress,
amount,
nearReceiverAccount,
ethSenderAccount,
})
transferLog = TransferETHERC20ToNear.loadTransferLog()
}
if (transferLog.finished === 'lock') {
await TransferETHERC20ToNear.findProof({
extractor,
lockedEvent: transferLog.lockedEvent,
web3,
})
transferLog = TransferETHERC20ToNear.loadTransferLog()
}
if (transferLog.finished === 'find-proof') {
await TransferETHERC20ToNear.waitBlockSafe({
ethOnNearClientContract,
...transferLog,
})
transferLog = TransferETHERC20ToNear.loadTransferLog()
}
if (transferLog.finished === 'block-safe') {
await TransferETHERC20ToNear.deposit({
nearFactoryContract,
nearFactoryContractBorsh,
nearTokenContract,
...transferLog,
})
}
try {
// Only WebSocket provider can close.
web3.currentProvider.connection.close()
} catch (e) {}
process.exit(0)
}
}
exports.TransferETHERC20ToNear = TransferETHERC20ToNear