diff --git a/integration-tests/package.json b/integration-tests/package.json index 57762759e0..8d90e7ca82 100644 --- a/integration-tests/package.json +++ b/integration-tests/package.json @@ -16,6 +16,7 @@ "clean": "rimraf cache artifacts" }, "devDependencies": { + "@boba/api": "0.0.1", "@boba/contracts": "0.0.1", "@boba/turing-hybrid-compute": "0.2.0", "@eth-optimism/contracts": "^0.5.11", diff --git a/integration-tests/test/boba-api.spec.ts b/integration-tests/test/boba-api.spec.ts new file mode 100644 index 0000000000..c47261d1f5 --- /dev/null +++ b/integration-tests/test/boba-api.spec.ts @@ -0,0 +1,545 @@ +import chai, { expect } from 'chai' +import chaiAsPromised from 'chai-as-promised' +chai.use(chaiAsPromised) + +/* Imports: External */ +import { ethers, BigNumber, Contract, Wallet } from 'ethers' +import { predeploys, getContractFactory } from '@eth-optimism/contracts' +import ethSigUtil from 'eth-sig-util' +import util from 'util' + +/* Imports: Internal */ +import { OptimismEnv } from './shared/env' +import { gasPriceOracleWallet } from './shared/utils' +import { rinkebySwapBOBAForETH, mainnetSwapBOBAForETH } from '@boba/api' + +describe('Boba API Tests', async () => { + let env: OptimismEnv + let L2Boba: Contract + let Boba_GasPriceOracle: Contract + + before(async () => { + env = await OptimismEnv.new() + + L2Boba = getContractFactory('L2GovernanceERC20') + .attach(predeploys.L2GovernanceERC20) + .connect(env.l2Wallet) + Boba_GasPriceOracle = getContractFactory('Boba_GasPriceOracle') + .attach(predeploys.Proxy__Boba_GasPriceOracle) + .connect(env.l2Wallet) + }) + + describe('Meta Transaction API Tests', async () => { + let EIP712Domain: any + let Permit: any + let name: string + let version: string + let chainId: number + + before(async () => { + EIP712Domain = [ + { name: 'name', type: 'string' }, + { name: 'version', type: 'string' }, + { name: 'chainId', type: 'uint256' }, + { name: 'verifyingContract', type: 'address' }, + ] + Permit = [ + { name: 'owner', type: 'address' }, + { name: 'spender', type: 'address' }, + { name: 'value', type: 'uint256' }, + { name: 'nonce', type: 'uint256' }, + { name: 'deadline', type: 'uint256' }, + ] + + name = await L2Boba.name() + version = '1' + chainId = (await env.l2Provider.getNetwork()).chainId + + // Add ETH first + await env.l2Wallet.sendTransaction({ + to: Boba_GasPriceOracle.address, + value: ethers.utils.parseEther('10'), + }) + + // Load env + process.env.L2_NODE_WEB3_URL = env.l2Provider.connection.url + process.env.PRIVATE_KEY = env.l2Wallet.privateKey + process.env.BOBA_GASPRICEORACLE_ADDRESS = Boba_GasPriceOracle.address + process.env.L2_BOBA_ADDRESS = L2Boba.address + }) + + describe('Mainnet', async () => { + it('{tag:boba} should swap BOBA for ETH', async () => { + const owner = env.l2Wallet_2.address + const spender = Boba_GasPriceOracle.address + const receivedETHAmount = await Boba_GasPriceOracle.receivedETHAmount() + const value = (await Boba_GasPriceOracle.getBOBAForSwap()).toString() + const nonce = (await L2Boba.nonces(env.l2Wallet_2.address)).toNumber() + const deadline = Math.floor(Date.now() / 1000) + 90 + const verifyingContract = L2Boba.address + + const data: any = { + primaryType: 'Permit', + types: { EIP712Domain, Permit }, + domain: { name, version, chainId, verifyingContract }, + message: { owner, spender, value, nonce, deadline }, + } + + const signature = ethSigUtil.signTypedData( + Buffer.from(env.l2Wallet_2.privateKey.slice(2), 'hex'), + { data } + ) + + const payload = { owner, spender, value, deadline, signature, data } + const asyncMainnetSwapBOBAForETH: any = util.promisify( + mainnetSwapBOBAForETH + ) + + const BobaBalanceBefore = await L2Boba.balanceOf(env.l2Wallet_2.address) + const ETHBalanceBefore = await env.l2Wallet_2.getBalance() + const GPO_ETHBalanceBefore = await env.l2Provider.getBalance( + Boba_GasPriceOracle.address + ) + + const response = await asyncMainnetSwapBOBAForETH( + { body: JSON.stringify(payload) }, + null + ) + + const BobaBalanceAfter = await L2Boba.balanceOf(env.l2Wallet_2.address) + const ETHBalanceAfter = await env.l2Wallet_2.getBalance() + const GPO_ETHBalanceAfter = await env.l2Provider.getBalance( + Boba_GasPriceOracle.address + ) + + expect(response.statusCode).to.equal(201) + + expect(BobaBalanceAfter).to.be.deep.eq( + BobaBalanceBefore.sub(BigNumber.from(value)) + ) + expect(ETHBalanceAfter).to.be.deep.eq( + ETHBalanceBefore.add(receivedETHAmount) + ) + expect(GPO_ETHBalanceAfter).to.be.deep.eq( + GPO_ETHBalanceBefore.sub(receivedETHAmount) + ) + }) + + it('{tag:boba} should return error messages using the wrong payload', async () => { + // Get balance + const BobaBalanceBefore = await L2Boba.balanceOf(env.l2Wallet_2.address) + const ETHBalanceBefore = await env.l2Wallet_2.getBalance() + const GPO_ETHBalanceBefore = await env.l2Provider.getBalance( + Boba_GasPriceOracle.address + ) + + // Missing parameters + const owner = env.l2Wallet_2.address + const spender = Boba_GasPriceOracle.address + const value = (await Boba_GasPriceOracle.getBOBAForSwap()).toString() + const nonce = (await L2Boba.nonces(env.l2Wallet_2.address)).toNumber() + const deadline = Math.floor(Date.now() / 1000) + 90 + const verifyingContract = L2Boba.address + + const data: any = { + primaryType: 'Permit', + types: { EIP712Domain, Permit }, + domain: { name, version, chainId, verifyingContract }, + message: { owner, spender, value, nonce, deadline }, + } + + const signature = ethSigUtil.signTypedData( + Buffer.from(env.l2Wallet_2.privateKey.slice(2), 'hex'), + { data } + ) + + const payload_1 = { owner, spender, value, deadline, signature } + const asyncMainnetSwapBOBAForETH: any = util.promisify( + mainnetSwapBOBAForETH + ) + const response_1 = await asyncMainnetSwapBOBAForETH( + { body: JSON.stringify(payload_1) }, + null + ) + const errorMessage_1 = JSON.parse(response_1.body).error + expect(response_1.statusCode).to.equal(400) + expect(errorMessage_1).to.equal('Missing parameters') + + // Wrong signature + const invalidSignature = ethSigUtil.signTypedData( + Buffer.from(env.l2Wallet.privateKey.slice(2), 'hex'), + { data } + ) + const payload_2 = { + owner, + spender, + value, + deadline, + signature: invalidSignature, + data, + } + + const response_2 = await asyncMainnetSwapBOBAForETH( + { body: JSON.stringify(payload_2) }, + null + ) + const errorMessage_2 = JSON.parse(response_2.body).error + expect(response_2.statusCode).to.equal(400) + expect(errorMessage_2).to.equal('Invalid signature') + + // Insufficient BOBA balance + const randomWallet = Wallet.createRandom().connect( + env.l2Wallet.provider + ) + const invalidData = { + primaryType: 'Permit', + types: { EIP712Domain, Permit }, + domain: { name, version, chainId, verifyingContract }, + message: { + owner: randomWallet.address, + spender, + value, + nonce, + deadline, + }, + } + + const signature_3 = ethSigUtil.signTypedData( + Buffer.from(randomWallet.privateKey.slice(2), 'hex'), + { data: invalidData } + ) + + const payload_3 = { + owner: randomWallet.address, + spender, + value, + deadline, + signature: signature_3, + data: invalidData, + } + const response_3 = await asyncMainnetSwapBOBAForETH( + { body: JSON.stringify(payload_3) }, + null + ) + const errorMessage_3 = JSON.parse(response_3.body).error + expect(response_3.statusCode).to.equal(400) + expect(errorMessage_3).to.equal('Insufficient balance') + + // Get balance + const BobaBalanceAfter = await L2Boba.balanceOf(env.l2Wallet_2.address) + const ETHBalanceAfter = await env.l2Wallet_2.getBalance() + const GPO_ETHBalanceAfter = await env.l2Provider.getBalance( + Boba_GasPriceOracle.address + ) + + expect(BobaBalanceAfter).to.be.deep.eq(BobaBalanceBefore) + expect(ETHBalanceAfter).to.be.deep.eq(ETHBalanceBefore) + expect(GPO_ETHBalanceAfter).to.be.deep.eq(GPO_ETHBalanceBefore) + }) + + it('{tag:boba} should return reverted reason from API if Boba_GasPriceOracle has insufficient ETH', async () => { + // withdraw ETH first + await Boba_GasPriceOracle.connect(gasPriceOracleWallet).withdrawETH() + const Boba_GasPriceOracleBalance = await env.l2Provider.getBalance( + Boba_GasPriceOracle.address + ) + expect(Boba_GasPriceOracleBalance).to.be.equal(BigNumber.from('0')) + + // should revert + const owner = env.l2Wallet_2.address + const spender = Boba_GasPriceOracle.address + const value = (await Boba_GasPriceOracle.getBOBAForSwap()).toString() + const nonce = (await L2Boba.nonces(env.l2Wallet_2.address)).toNumber() + const deadline = Math.floor(Date.now() / 1000) + 90 + const verifyingContract = L2Boba.address + + const data: any = { + primaryType: 'Permit', + types: { EIP712Domain, Permit }, + domain: { name, version, chainId, verifyingContract }, + message: { owner, spender, value, nonce, deadline }, + } + + const signature = ethSigUtil.signTypedData( + Buffer.from(env.l2Wallet_2.privateKey.slice(2), 'hex'), + { data } + ) + + const payload = { owner, spender, value, deadline, signature, data } + const asyncMainnetSwapBOBAForETH: any = util.promisify( + mainnetSwapBOBAForETH + ) + + const BobaBalanceBefore = await L2Boba.balanceOf(env.l2Wallet_2.address) + const ETHBalanceBefore = await env.l2Wallet_2.getBalance() + const GPO_ETHBalanceBefore = await env.l2Provider.getBalance( + Boba_GasPriceOracle.address + ) + + const response = await asyncMainnetSwapBOBAForETH( + { body: JSON.stringify(payload) }, + null + ) + + const BobaBalanceAfter = await L2Boba.balanceOf(env.l2Wallet_2.address) + const ETHBalanceAfter = await env.l2Wallet_2.getBalance() + const GPO_ETHBalanceAfter = await env.l2Provider.getBalance( + Boba_GasPriceOracle.address + ) + + expect(response.statusCode).to.equal(400) + expect( + JSON.parse(JSON.parse(response.body).error.error.error.body).error + .message + ).to.equal('execution reverted: Failed to send ETH') + + expect(BobaBalanceAfter).to.be.deep.eq(BobaBalanceBefore) + expect(ETHBalanceAfter).to.be.deep.eq(ETHBalanceBefore) + expect(GPO_ETHBalanceAfter).to.be.deep.eq(GPO_ETHBalanceBefore) + + // Add funds + await env.l2Wallet.sendTransaction({ + to: Boba_GasPriceOracle.address, + value: ethers.utils.parseEther('10'), + }) + }) + }) + + describe('Rinkeby', async () => { + it('{tag:boba} should swap BOBA for ETH', async () => { + const owner = env.l2Wallet_2.address + const spender = Boba_GasPriceOracle.address + const receivedETHAmount = await Boba_GasPriceOracle.receivedETHAmount() + const value = (await Boba_GasPriceOracle.getBOBAForSwap()).toString() + const nonce = (await L2Boba.nonces(env.l2Wallet_2.address)).toNumber() + const deadline = Math.floor(Date.now() / 1000) + 90 + const verifyingContract = L2Boba.address + + const data: any = { + primaryType: 'Permit', + types: { EIP712Domain, Permit }, + domain: { name, version, chainId, verifyingContract }, + message: { owner, spender, value, nonce, deadline }, + } + + const signature = ethSigUtil.signTypedData( + Buffer.from(env.l2Wallet_2.privateKey.slice(2), 'hex'), + { data } + ) + + const payload = { owner, spender, value, deadline, signature, data } + const asyncRinkebySwapBOBAForETH: any = util.promisify( + rinkebySwapBOBAForETH + ) + + const BobaBalanceBefore = await L2Boba.balanceOf(env.l2Wallet_2.address) + const ETHBalanceBefore = await env.l2Wallet_2.getBalance() + const GPO_ETHBalanceBefore = await env.l2Provider.getBalance( + Boba_GasPriceOracle.address + ) + + const response = await asyncRinkebySwapBOBAForETH( + { body: JSON.stringify(payload) }, + null + ) + + const BobaBalanceAfter = await L2Boba.balanceOf(env.l2Wallet_2.address) + const ETHBalanceAfter = await env.l2Wallet_2.getBalance() + const GPO_ETHBalanceAfter = await env.l2Provider.getBalance( + Boba_GasPriceOracle.address + ) + + expect(response.statusCode).to.equal(201) + + expect(BobaBalanceAfter).to.be.deep.eq( + BobaBalanceBefore.sub(BigNumber.from(value)) + ) + expect(ETHBalanceAfter).to.be.deep.eq( + ETHBalanceBefore.add(receivedETHAmount) + ) + expect(GPO_ETHBalanceAfter).to.be.deep.eq( + GPO_ETHBalanceBefore.sub(receivedETHAmount) + ) + }) + + it('{tag:boba} should return error messages using the wrong payload', async () => { + // Get balance + const BobaBalanceBefore = await L2Boba.balanceOf(env.l2Wallet_2.address) + const ETHBalanceBefore = await env.l2Wallet_2.getBalance() + const GPO_ETHBalanceBefore = await env.l2Provider.getBalance( + Boba_GasPriceOracle.address + ) + + // Missing parameters + const owner = env.l2Wallet_2.address + const spender = Boba_GasPriceOracle.address + const value = (await Boba_GasPriceOracle.getBOBAForSwap()).toString() + const nonce = (await L2Boba.nonces(env.l2Wallet_2.address)).toNumber() + const deadline = Math.floor(Date.now() / 1000) + 90 + const verifyingContract = L2Boba.address + + const data: any = { + primaryType: 'Permit', + types: { EIP712Domain, Permit }, + domain: { name, version, chainId, verifyingContract }, + message: { owner, spender, value, nonce, deadline }, + } + + const signature = ethSigUtil.signTypedData( + Buffer.from(env.l2Wallet_2.privateKey.slice(2), 'hex'), + { data } + ) + + const payload_1 = { owner, spender, value, deadline, signature } + const asyncRinkebySwapBOBAForETH: any = util.promisify( + rinkebySwapBOBAForETH + ) + const response_1 = await asyncRinkebySwapBOBAForETH( + { body: JSON.stringify(payload_1) }, + null + ) + const errorMessage_1 = JSON.parse(response_1.body).error + expect(response_1.statusCode).to.equal(400) + expect(errorMessage_1).to.equal('Missing parameters') + + // Wrong signature + const invalidSignature = ethSigUtil.signTypedData( + Buffer.from(env.l2Wallet.privateKey.slice(2), 'hex'), + { data } + ) + const payload_2 = { + owner, + spender, + value, + deadline, + signature: invalidSignature, + data, + } + + const response_2 = await asyncRinkebySwapBOBAForETH( + { body: JSON.stringify(payload_2) }, + null + ) + const errorMessage_2 = JSON.parse(response_2.body).error + expect(response_2.statusCode).to.equal(400) + expect(errorMessage_2).to.equal('Invalid signature') + + // Insufficient BOBA balance + const randomWallet = Wallet.createRandom().connect( + env.l2Wallet.provider + ) + const invalidData = { + primaryType: 'Permit', + types: { EIP712Domain, Permit }, + domain: { name, version, chainId, verifyingContract }, + message: { + owner: randomWallet.address, + spender, + value, + nonce, + deadline, + }, + } + + const signature_3 = ethSigUtil.signTypedData( + Buffer.from(randomWallet.privateKey.slice(2), 'hex'), + { data: invalidData } + ) + + const payload_3 = { + owner: randomWallet.address, + spender, + value, + deadline, + signature: signature_3, + data: invalidData, + } + const response_3 = await asyncRinkebySwapBOBAForETH( + { body: JSON.stringify(payload_3) }, + null + ) + const errorMessage_3 = JSON.parse(response_3.body).error + expect(response_3.statusCode).to.equal(400) + expect(errorMessage_3).to.equal('Insufficient balance') + + // Get balance + const BobaBalanceAfter = await L2Boba.balanceOf(env.l2Wallet_2.address) + const ETHBalanceAfter = await env.l2Wallet_2.getBalance() + const GPO_ETHBalanceAfter = await env.l2Provider.getBalance( + Boba_GasPriceOracle.address + ) + + expect(BobaBalanceAfter).to.be.deep.eq(BobaBalanceBefore) + expect(ETHBalanceAfter).to.be.deep.eq(ETHBalanceBefore) + expect(GPO_ETHBalanceAfter).to.be.deep.eq(GPO_ETHBalanceBefore) + }) + + it('{tag:boba} should return reverted reason from API if Boba_GasPriceOracle has insufficient ETH', async () => { + // withdraw ETH first + await Boba_GasPriceOracle.connect(gasPriceOracleWallet).withdrawETH() + const Boba_GasPriceOracleBalance = await env.l2Provider.getBalance( + Boba_GasPriceOracle.address + ) + expect(Boba_GasPriceOracleBalance).to.be.equal(BigNumber.from('0')) + + // should revert + const owner = env.l2Wallet_2.address + const spender = Boba_GasPriceOracle.address + const value = (await Boba_GasPriceOracle.getBOBAForSwap()).toString() + const nonce = (await L2Boba.nonces(env.l2Wallet_2.address)).toNumber() + const deadline = Math.floor(Date.now() / 1000) + 90 + const verifyingContract = L2Boba.address + + const data: any = { + primaryType: 'Permit', + types: { EIP712Domain, Permit }, + domain: { name, version, chainId, verifyingContract }, + message: { owner, spender, value, nonce, deadline }, + } + + const signature = ethSigUtil.signTypedData( + Buffer.from(env.l2Wallet_2.privateKey.slice(2), 'hex'), + { data } + ) + + const payload = { owner, spender, value, deadline, signature, data } + const asyncRinkebySwapBOBAForETH: any = util.promisify( + rinkebySwapBOBAForETH + ) + + const BobaBalanceBefore = await L2Boba.balanceOf(env.l2Wallet_2.address) + const ETHBalanceBefore = await env.l2Wallet_2.getBalance() + const GPO_ETHBalanceBefore = await env.l2Provider.getBalance( + Boba_GasPriceOracle.address + ) + + const response = await asyncRinkebySwapBOBAForETH( + { body: JSON.stringify(payload) }, + null + ) + + const BobaBalanceAfter = await L2Boba.balanceOf(env.l2Wallet_2.address) + const ETHBalanceAfter = await env.l2Wallet_2.getBalance() + const GPO_ETHBalanceAfter = await env.l2Provider.getBalance( + Boba_GasPriceOracle.address + ) + + expect(response.statusCode).to.equal(400) + expect( + JSON.parse(JSON.parse(response.body).error.error.error.body).error + .message + ).to.equal('execution reverted: Failed to send ETH') + + expect(BobaBalanceAfter).to.be.deep.eq(BobaBalanceBefore) + expect(ETHBalanceAfter).to.be.deep.eq(ETHBalanceBefore) + expect(GPO_ETHBalanceAfter).to.be.deep.eq(GPO_ETHBalanceBefore) + + // Add funds + await env.l2Wallet.sendTransaction({ + to: Boba_GasPriceOracle.address, + value: ethers.utils.parseEther('10'), + }) + }) + }) + }) +}) diff --git a/integration-tests/tsconfig.json b/integration-tests/tsconfig.json index 8ef06dc0f7..776e79d886 100644 --- a/integration-tests/tsconfig.json +++ b/integration-tests/tsconfig.json @@ -1,7 +1,8 @@ { "extends": "../tsconfig.json", "compilerOptions": { - "resolveJsonModule": true + "resolveJsonModule": true, + "allowJs": true }, "include": [ "./test", diff --git a/ops/docker/Dockerfile.integration-tests b/ops/docker/Dockerfile.integration-tests index d4d90a4566..45b3a4e9f8 100644 --- a/ops/docker/Dockerfile.integration-tests +++ b/ops/docker/Dockerfile.integration-tests @@ -24,6 +24,8 @@ COPY --from=builder /optimism/packages/contracts ./packages/contracts COPY --from=builder /optimism/packages/boba/contracts ./packages/boba/contracts COPY --from=builder /optimism/packages/boba/turing ./packages/boba/turing +COPY --from=builder /optimism/ops_boba/api ./ops_boba/api + # get the needed built artifacts WORKDIR /opt/optimism/integration-tests COPY --from=builder /optimism/integration-tests ./ diff --git a/ops/docker/Dockerfile.monorepo b/ops/docker/Dockerfile.monorepo index d7836264d5..460aa7558e 100644 --- a/ops/docker/Dockerfile.monorepo +++ b/ops/docker/Dockerfile.monorepo @@ -48,6 +48,7 @@ COPY packages/data-transport-layer/package.json ./packages/data-transport-layer/ COPY packages/message-relayer/package.json ./packages/message-relayer/package.json COPY packages/replica-healthcheck/package.json ./packages/replica-healthcheck/package.json COPY integration-tests/package.json ./integration-tests/package.json +COPY ops_boba/api/package.json ./ops_boba/api/package.json COPY packages/boba/contracts/package.json ./packages/boba/contracts/package.json COPY packages/boba/gas-price-oracle/package.json ./packages/boba/gas-price-oracle/package.json @@ -70,12 +71,14 @@ WORKDIR /optimism COPY --from=builder /optimism/node_modules ./node_modules COPY --from=builder /optimism/packages ./packages COPY --from=builder /optimism/integration-tests ./integration-tests +COPY --from=builder /optimism/ops_boba/api ./ops_boba/api # the following steps are cheap COPY *.json yarn.lock ./ # copy over the source COPY ./packages ./packages COPY ./integration-tests ./integration-tests +COPY ./ops_boba/api ./ops_boba/api # copy over solc to save time building (35+ seconds vs not doing this step) COPY --from=downloader solc-v0.4.11+commit.68ef5810 /root/.cache/hardhat-nodejs/compilers/linux-amd64/solc-linux-amd64-v0.4.11+commit.68ef5810 COPY --from=downloader solc-v0.5.17+commit.d19bba13 /root/.cache/hardhat-nodejs/compilers/linux-amd64/solc-linux-amd64-v0.5.17+commit.d19bba13 diff --git a/ops_boba/api/index.js b/ops_boba/api/index.js new file mode 100644 index 0000000000..92efa1c267 --- /dev/null +++ b/ops_boba/api/index.js @@ -0,0 +1,6 @@ +import { + rinkebySwapBOBAForETH, + mainnetSwapBOBAForETH, +} from './metatransaction-api' + +export { rinkebySwapBOBAForETH, mainnetSwapBOBAForETH } diff --git a/ops_boba/api/metatransaction-api/index.js b/ops_boba/api/metatransaction-api/index.js new file mode 100644 index 0000000000..239573496d --- /dev/null +++ b/ops_boba/api/metatransaction-api/index.js @@ -0,0 +1,9 @@ +import { + rinkebyHandler, + mainnetHandler, +} from './metaTransaction_swapBOBAForETH' + +export { + rinkebyHandler as rinkebySwapBOBAForETH, + mainnetHandler as mainnetSwapBOBAForETH, +} diff --git a/ops_boba/api/metatransaction-api/metaTransaction_swapBOBAForETH.js b/ops_boba/api/metatransaction-api/metaTransaction_swapBOBAForETH.js index 05b4bf4b57..f18d88def6 100644 --- a/ops_boba/api/metatransaction-api/metaTransaction_swapBOBAForETH.js +++ b/ops_boba/api/metatransaction-api/metaTransaction_swapBOBAForETH.js @@ -3,40 +3,8 @@ const ethSigUtil = require('eth-sig-util') const YAML = require('yaml') const fs = require('fs') -// Load env -const file = fs.readFileSync('./env.yml', 'utf8') -const env = YAML.parse(file) -const L2_NODE_WEB3_URL = env.L2_NODE_WEB3_URL -const PRIVATE_KEY = env.PRIVATE_KEY -const BOBA_GASPRICEORACLE_ADDRESS = env.BOBA_GASPRICEORACLE_ADDRESS -const L2_BOBA_ADDRESS = env.L2_BOBA_ADDRESS - -// Get provider and wallet -const l2Provider = new ethers.providers.JsonRpcProvider(L2_NODE_WEB3_URL) -const l2Wallet = new ethers.Wallet(PRIVATE_KEY).connect(l2Provider) - -// ABI -const BobaGasPriceOracleInterface = new ethers.utils.Interface([ - 'function useBobaAsFeeToken()', - 'function useETHAsFeeToken()', - 'function bobaFeeTokenUsers(address) view returns (bool)', - 'function swapBOBAForETHMetaTransaction(address,address,uint256,uint256,uint8,bytes32,bytes32)', - 'function metaTransactionFee() view returns (uint256)', - 'function marketPriceRatio() view returns (uint256)', - 'function receivedETHAmount() view returns (uint256)', -]) - -const L2BobaInterface = new ethers.utils.Interface([ - 'function balanceOf(address) view returns (uint256)', -]) - -// Load contracts -const Boba_GasPriceOracle = new ethers.Contract( - BOBA_GASPRICEORACLE_ADDRESS, - BobaGasPriceOracleInterface, - l2Wallet -) -const L2Boba = new ethers.Contract(L2_BOBA_ADDRESS, L2BobaInterface, l2Wallet) +// Support local tests +require('dotenv').config() const headers = { 'Access-Control-Allow-Origin': '*', @@ -49,9 +17,53 @@ const headers = { 'Permissions-Policy': '*', } +// Load contracts +const loadContracts = () => { + // Load env + let env = process.env + if (fs.existsSync('./env.yml')) { + const file = fs.readFileSync('./env.yml', 'utf8') + env = YAML.parse(file) + } + const L2_NODE_WEB3_URL = env.L2_NODE_WEB3_URL || process.env.L2_NODE_WEB3_URL + const PRIVATE_KEY = env.PRIVATE_KEY || process.env.PRIVATE_KEY + const BOBA_GASPRICEORACLE_ADDRESS = + env.BOBA_GASPRICEORACLE_ADDRESS || process.env.BOBA_GASPRICEORACLE_ADDRESS + const L2_BOBA_ADDRESS = env.L2_BOBA_ADDRESS || process.env.L2_BOBA_ADDRESS + + // Get provider and wallet + const l2Provider = new ethers.providers.JsonRpcProvider(L2_NODE_WEB3_URL) + const l2Wallet = new ethers.Wallet(PRIVATE_KEY).connect(l2Provider) + + // ABI + const BobaGasPriceOracleInterface = new ethers.utils.Interface([ + 'function useBobaAsFeeToken()', + 'function useETHAsFeeToken()', + 'function bobaFeeTokenUsers(address) view returns (bool)', + 'function swapBOBAForETHMetaTransaction(address,address,uint256,uint256,uint8,bytes32,bytes32)', + 'function metaTransactionFee() view returns (uint256)', + 'function marketPriceRatio() view returns (uint256)', + 'function receivedETHAmount() view returns (uint256)', + ]) + + const L2BobaInterface = new ethers.utils.Interface([ + 'function balanceOf(address) view returns (uint256)', + ]) + + // Load contracts + const Boba_GasPriceOracle = new ethers.Contract( + BOBA_GASPRICEORACLE_ADDRESS, + BobaGasPriceOracleInterface, + l2Wallet + ) + const L2Boba = new ethers.Contract(L2_BOBA_ADDRESS, L2BobaInterface, l2Wallet) + + return [Boba_GasPriceOracle, L2Boba] +} + // Decrypt the signature and verify the message // Verify the user balance and the value -const verifyBobay = async (body) => { +const verifyBobay = async (body, Boba_GasPriceOracle, L2Boba) => { const { owner, spender, value, deadline, signature, data } = body if ( @@ -112,7 +124,8 @@ const verifyBobay = async (body) => { module.exports.mainnetHandler = async (event, context, callback) => { const body = JSON.parse(event.body) - const isVerified = await verifyBobay(body) + const [Boba_GasPriceOracle, L2Boba] = loadContracts() + const isVerified = await verifyBobay(body, Boba_GasPriceOracle, L2Boba) if (isVerified.isVerified === false) { return callback(null, { headers, @@ -158,7 +171,8 @@ module.exports.mainnetHandler = async (event, context, callback) => { module.exports.rinkebyHandler = async (event, context, callback) => { const body = JSON.parse(event.body) - const isVerified = await verifyBobay(body) + const [Boba_GasPriceOracle, L2Boba] = loadContracts() + const isVerified = await verifyBobay(body, Boba_GasPriceOracle, L2Boba) if (isVerified.isVerified === false) { return callback(null, { headers, diff --git a/ops_boba/api/metatransaction-api/package.json b/ops_boba/api/metatransaction-api/package.json deleted file mode 100644 index fe5b509539..0000000000 --- a/ops_boba/api/metatransaction-api/package.json +++ /dev/null @@ -1,11 +0,0 @@ -{ - "name": "@boba/metatransaction-api", - "version": "1.0.0", - "description": "Boba Meta Transaction API", - "dependencies": { - "eth-sig-util": "^3.0.1", - "ethers": "^5.6.2", - "fs": "^0.0.1-security", - "yaml": "^1.10.2" - } -} diff --git a/ops_boba/api/package.json b/ops_boba/api/package.json new file mode 100644 index 0000000000..94e40823ad --- /dev/null +++ b/ops_boba/api/package.json @@ -0,0 +1,15 @@ +{ + "name": "@boba/api", + "version": "0.0.1", + "description": "BOBA API", + "private": true, + "author": "Enya.ai", + "license": "MIT", + "dependencies": { + "dotenv": "^8.2.0", + "eth-sig-util": "^3.0.1", + "ethers": "^5.4.5", + "fs": "^0.0.1-security", + "yaml": "^1.10.0" + } +} diff --git a/package.json b/package.json index d784a07cec..10ddd81dd7 100644 --- a/package.json +++ b/package.json @@ -11,6 +11,7 @@ "integration-tests", "specs", "ops_boba/monitor", + "ops_boba/api", "go/gas-oracle", "go/proxyd", "go/op-exporter", diff --git a/yarn.lock b/yarn.lock index ae0e94c09d..c3507aef8f 100644 --- a/yarn.lock +++ b/yarn.lock @@ -10585,6 +10585,16 @@ eth-sig-util@^2.5.2: tweetnacl "^1.0.3" tweetnacl-util "^0.15.0" +eth-sig-util@^3.0.1: + version "3.0.1" + resolved "https://registry.yarnpkg.com/eth-sig-util/-/eth-sig-util-3.0.1.tgz#8753297c83a3f58346bd13547b59c4b2cd110c96" + integrity sha512-0Us50HiGGvZgjtWTyAI/+qTzYPMLy5Q451D0Xy68bxq1QMWdoOddDwGvsqcFT27uohKgalM9z/yxplyt+mY2iQ== + dependencies: + ethereumjs-abi "^0.6.8" + ethereumjs-util "^5.1.1" + tweetnacl "^1.0.3" + tweetnacl-util "^0.15.0" + eth-tx-summary@^3.1.2: version "3.2.4" resolved "https://registry.yarnpkg.com/eth-tx-summary/-/eth-tx-summary-3.2.4.tgz#e10eb95eb57cdfe549bf29f97f1e4f1db679035c" @@ -11876,6 +11886,11 @@ fs.realpath@^1.0.0: resolved "https://registry.yarnpkg.com/fs.realpath/-/fs.realpath-1.0.0.tgz#1504ad2523158caa40db4a2787cb01411994ea4f" integrity sha1-FQStJSMVjKpA20onh8sBQRmU6k8= +fs@^0.0.1-security: + version "0.0.1-security" + resolved "https://registry.yarnpkg.com/fs/-/fs-0.0.1-security.tgz#8a7bd37186b6dddf3813f23858b57ecaaf5e41d4" + integrity sha1-invTcYa23d84E/I4WLV+yq9eQdQ= + fsevents@2.1.2: version "2.1.2" resolved "https://registry.yarnpkg.com/fsevents/-/fsevents-2.1.2.tgz#4c0a1fb34bc68e543b4b82a9ec392bfbda840805"