Skip to content

Commit

Permalink
ROLLUP-449/prepare-forced-exit-account (#106)
Browse files Browse the repository at this point in the history
* feat: refactor forcedExit script

* refactor: server initialization

* refactor: PR comments

* fix: fix coinmarketcap client tests (#108)

* fix: fix coinmarketcap client tests

* style: apply fmt

* test: fix parallel tests

---------

Co-authored-by: Francisco Tobar <[email protected]>

* Update docker-compose.yml

Co-authored-by: franciscotobar <[email protected]>

---------

Co-authored-by: Antonio Morrone <[email protected]>
  • Loading branch information
franciscotobar and antomor authored Oct 19, 2023
1 parent 989b205 commit 03e7619
Show file tree
Hide file tree
Showing 12 changed files with 280 additions and 123 deletions.
64 changes: 57 additions & 7 deletions core/bin/zksync_api/src/bin/providers/dev_price_provider.rs
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@
use actix_web::{web, HttpRequest, HttpResponse, Result};
use bigdecimal::BigDecimal;
use chrono::Utc;
use chrono::{SecondsFormat, Utc};
use serde::{Deserialize, Serialize};
use serde_json::json;
use std::{collections::HashMap, fs::read_to_string, path::Path};
Expand Down Expand Up @@ -50,6 +50,45 @@ macro_rules! make_sloppy {
}};
}

async fn handle_coinmarketcap_token_price_query(
query: web::Query<CoinMarketCapTokenQuery>,
_data: web::Data<Vec<TokenData>>,
) -> Result<HttpResponse> {
let symbol = query.symbol.clone();
let base_price = match symbol.as_str() {
"RBTC" => BigDecimal::from(1800),
"wBTC" => BigDecimal::from(9000),
// Even though these tokens have their base price equal to
// the default one, we still keep them here so that in the future it would
// be easier to change the default price without affecting the important tokens
"DAI" => BigDecimal::from(1),
"tGLM" => BigDecimal::from(1),
"GLM" => BigDecimal::from(1),

"RIF" => BigDecimal::try_from(0.053533).unwrap(),
_ => BigDecimal::from(1),
};
let random_multiplier = thread_rng().gen_range(0.9, 1.1);

let price = base_price * BigDecimal::try_from(random_multiplier).unwrap();

let last_updated = Utc::now().to_rfc3339_opts(SecondsFormat::Millis, true);
let resp = json!({
"data": {
symbol: {
"quote": {
"USD": {
"price": price.to_string(),
"last_updated": last_updated
}
}
}
}
});
vlog::info!("1.0 {} = {} USD", query.symbol, price);
Ok(HttpResponse::Ok().json(resp))
}

#[derive(Debug, Deserialize)]
struct Token {
pub address: Address,
Expand Down Expand Up @@ -140,22 +179,33 @@ pub fn create_price_service(sloppy_mode: bool) -> actix_web::Scope {
.chain(testnet_tokens.into_iter())
.collect();
if sloppy_mode {
web::scope(API_PATH)
web::scope("")
.app_data(web::Data::new(data))
.route(
"/coins/list",
"/cryptocurrency/quotes/latest",
web::get().to(make_sloppy!(handle_coinmarketcap_token_price_query)),
)
.route(
format!("{}/coins/list", API_PATH).as_str(),
web::get().to(make_sloppy!(handle_coingecko_token_list)),
)
.route(
"/coins/{coin_id}/market_chart",
format!("{}/coins/{{coin_id}}/market_chart", API_PATH).as_str(),
web::get().to(make_sloppy!(handle_coingecko_token_price_query)),
)
} else {
web::scope(API_PATH)
web::scope("")
.app_data(web::Data::new(data))
.route("/coins/list", web::get().to(handle_coingecko_token_list))
.route(
"/coins/{coin_id}/market_chart",
"/cryptocurrency/quotes/latest",
web::get().to(handle_coinmarketcap_token_price_query),
)
.route(
format!("{}/coins/list", API_PATH).as_str(),
web::get().to(handle_coingecko_token_list),
)
.route(
format!("{}/coins/{{coin_id}}/market_chart", API_PATH).as_str(),
web::get().to(handle_coingecko_token_price_query),
)
}
Expand Down
2 changes: 1 addition & 1 deletion core/lib/wallet_creator/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -44,7 +44,7 @@ zk test i wallet-generator

Or to do it manually:

Navigate to `ri-aggregation/core/lib/wallet_creator` and run:
Navigate to `./core/lib/wallet_creator` and run:

Unit tests

Expand Down
3 changes: 2 additions & 1 deletion docker-compose.yml
Original file line number Diff line number Diff line change
Expand Up @@ -35,8 +35,9 @@ services:
start_period: 15s

dev-ticker:
image: "rsksmart/rollup-dev-ticker:1.0.0-beta"
image: "rsksmart/rollup-dev-ticker:1.1.0-beta"
env_file:
- ./etc/env/${ZKSYNC_ENV-dev}.env
- ./etc/env/${ENV_OVERRIDE-deploy}.env
ports:
- "9876:9876"
Expand Down
1 change: 1 addition & 0 deletions docker/environment/Dockerfile
Original file line number Diff line number Diff line change
Expand Up @@ -26,6 +26,7 @@ ENV RUSTUP_HOME=/usr/local/rustup \
PATH=/usr/local/cargo/bin:$PATH
RUN curl https://sh.rustup.rs -sSf | bash -s -- -y
RUN rustup install 1.69.0
RUN rustup override set 1.69.0
RUN cargo install diesel_cli --version=1.4.0 --no-default-features --features postgres
RUN cargo install --version=0.5.6 sqlx-cli
RUN cargo install wasm-pack --git https://github.com/d3lm/wasm-pack --rev 713868b204f151acd1989c3f29ff9d3bc944c306
Expand Down
1 change: 0 additions & 1 deletion docs/architecture.md
Original file line number Diff line number Diff line change
Expand Up @@ -83,7 +83,6 @@ This section provides an overview on folders / sub-projects that exist in this r
- `/docker`: Dockerfiles used for development of zkSync and for packaging zkSync for a production environment.
- `/etc`: Configration files.
- `/env`: `.env` files that contain environment variables for different configuration of zkSync Server / Prover.
- `/js`: Configuration files for JavaScript applications (such as Explorer).
- `/tokens`: Configuration of supported Rootstock ERC-20 tokens.
- `/infrastructure`: Application that aren't naturally a part of zkSync core, but are related to it.
- `/keys`: Verification keys for `circuit` module.
Expand Down
26 changes: 0 additions & 26 deletions etc/js/env-config.js

This file was deleted.

3 changes: 0 additions & 3 deletions infrastructure/zk/src/docker.ts
Original file line number Diff line number Diff line change
Expand Up @@ -36,9 +36,6 @@ async function dockerCommand(command: 'push' | 'build', image: string, tag: stri
}

async function _build(image: string, tag: string) {
if (image == 'nginx') {
await utils.spawn('yarn explorer build');
}
if (image == 'server' || image == 'prover') {
await contract.build();
}
Expand Down
2 changes: 1 addition & 1 deletion infrastructure/zk/src/init.ts
Original file line number Diff line number Diff line change
Expand Up @@ -53,7 +53,7 @@ async function createVolumes() {

export const initCommand = new Command('init')
.description('perform zksync network initialization for development')
.option('--no-crypto', 'not include sdk packages')
.option('--no-crypto', 'not include crypto packages')
.option('--with-docker', 'use docker container instead of local environment')
.action(async (cmd: Command) => {
const { crypto, withDocker } = cmd;
Expand Down
207 changes: 207 additions & 0 deletions infrastructure/zk/src/run/forced-exit.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,207 @@
import { Command } from 'commander';
import fetch from 'node-fetch';
import { BigNumber, BigNumberish, ethers, providers } from 'ethers';
import * as utils from '../utils';

type State = {
balances: {
[token: string]: BigNumberish;
};
nonce: number;
pubKeyHash: string;
};

type RestAccountState = {
finalized: State | undefined;
};

type RpcAccountState = {
verified: State;
};

const SYNC = 'sync:0000000000000000000000000000000000000000';

async function isForcedExitSenderAccountReady(nodeUrl: string, nodeType: string): Promise<boolean> {
if (nodeType !== 'REST' && nodeType !== 'JSONRPC') {
console.log('Node type must be either REST or JSONRPC');
return false;
}

const forcedExitAccount = process.env.FORCED_EXIT_REQUESTS_SENDER_ACCOUNT_ADDRESS as string;
const state =
nodeType === 'REST'
? (await processRestRequest(nodeUrl, forcedExitAccount)).finalized
: (await processJsonRpcRequest(nodeUrl, forcedExitAccount)).verified;

if (state?.pubKeyHash !== SYNC) {
console.log('Forced exit sender account is ready');
return true;
}

if (state?.balances['RBTC']) {
const balance = BigNumber.from(state.balances['RBTC']);
if (!balance.isZero()) {
console.log(`Forced exit sender account balance is ${balance.toString()} RBTC`);
console.log('Wait until the preparation of the forced exit sender account is completed');
return true;
}
}

console.log('Forced exit sender account is not ready');
return false;
}

async function processRestRequest(nodeUrl: string, forcedExitAccount: string): Promise<RestAccountState> {
const response = await fetch(`${nodeUrl}/accounts/${forcedExitAccount}`);
const { result } = await response.json();

return result;
}

async function processJsonRpcRequest(nodeUrl: string, forcedExitAccount: string): Promise<RpcAccountState> {
const body = {
jsonrpc: '2.0',
method: 'account_info',
params: [forcedExitAccount],
id: 1
};
const response = await fetch(nodeUrl, {
method: 'POST',
body: JSON.stringify(body),
headers: {
Accept: 'application/json',
'Content-type': 'application/json'
}
});
const { result } = await response.json();

return result;
}

async function prepareForcedExitSenderAccount(
l1Address: string,
privateKey: string,
amount: string | undefined,
nodeUrl: string,
nodeType: string
): Promise<void> {
if (await isForcedExitSenderAccountReady(nodeUrl, nodeType)) {
return;
}

await depositToForcedExitSenderAccount(l1Address, privateKey, amount);
}

export async function depositToForcedExitSenderAccount(l1Address?: string, privateKey?: string, amount = '0.0001') {
console.log('Depositing to the forced exit sender account sender');

const parsedAmount = ethers.utils.parseEther(amount);

const signer = await retrieveSigner(parsedAmount, l1Address, privateKey);

if (!signer) {
console.log('Must provide an L1 address and L1 private key that matches');
return;
}

const mainZkSyncContract = new ethers.Contract(
process.env.CONTRACTS_CONTRACT_ADDR as string,
await utils.readZkSyncAbi(),
signer
);

const forcedExitAccount = process.env.FORCED_EXIT_REQUESTS_SENDER_ACCOUNT_ADDRESS as string;
const gasPrice = await signer.getGasPrice();
const depositTransaction = (await mainZkSyncContract.depositRBTC(forcedExitAccount, {
value: parsedAmount,
gasPrice
})) as ethers.ContractTransaction;

console.log(`Deposit transaction hash: ${depositTransaction.hash}`);

await depositTransaction.wait();

console.log('Deposit to the forced exit sender account has been successfully completed');
}

async function retrieveSigner(
amount: BigNumberish,
l1Address?: string,
privateKey?: string
): Promise<ethers.Signer | undefined> {
const provider = new providers.JsonRpcProvider(
process.env.FORCED_EXIT_REQUESTS_WEB3_URL ?? process.env.ETH_CLIENT_WEB3_URL
);

let signer: ethers.Signer | undefined;
if (l1Address && privateKey) {
signer = new ethers.Wallet(privateKey, provider);

const address = await signer.getAddress();

if (l1Address.toLowerCase() !== address.toLowerCase()) {
console.log('L1 address does not match the provided private key');
return undefined;
}
}

if (!signer && process.env.ZKSYNC_ENV === 'dev') {
signer = await findWealthyAccount(amount, provider);
}

return signer;
}

async function findWealthyAccount(
requiredBalance: BigNumberish,
provider: providers.JsonRpcProvider
): Promise<ethers.Signer | undefined> {
let accounts: string[] = [];
try {
accounts = await provider.listAccounts();

for (let i = accounts.length - 1; i >= 0; i--) {
const signer = provider.getSigner(i);
const balance = await signer.getBalance();
if (balance.gte(requiredBalance)) {
console.log(`Found funded account ${await signer.getAddress()}`);

return signer;
}
}
} catch (error) {
console.log('Failed to retrieve accounts and balances:', error);
}
console.log(`could not find unlocked account with sufficient balance; all accounts:\n - ${accounts.join('\n - ')}`);
}

export const command = new Command('forced-exit')
.description('prepare forced exit sender account')
.requiredOption('-n, --nodeUrl <nodeUrl>', 'Node url')
.requiredOption('-t, --nodeType <nodeType>', 'Node type (REST or JSONRPC)');

command
.command('check')
.description('check forced exit sender account balance')
.action(async (cmd: Command) => {
const {
parent: { nodeUrl, nodeType }
} = cmd;
await isForcedExitSenderAccountReady(nodeUrl, nodeType);
});

command
.command('prepare')
.description('deposit to forced exit sender account if necessary')
.requiredOption('--address <l1Address>', 'L1 address')
.requiredOption('-p, --privateKey <privateKey>', 'Private key of the L1 address')
.option('--amount <amount>', 'Amount of RBTC to deposit (default: 0.0001')
.action(async (cmd: Command) => {
const {
address,
privateKey,
amount,
parent: { nodeUrl, nodeType }
} = cmd;
await prepareForcedExitSenderAccount(address, privateKey, amount, nodeUrl, nodeType);
});
Loading

0 comments on commit 03e7619

Please sign in to comment.