Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 2 additions & 0 deletions Cargo.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

2 changes: 2 additions & 0 deletions crates/cli/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@ name = "enclave"
path = "src/main.rs"

[dependencies]
alloy = { workspace = true }
actix = { workspace = true }
anyhow = { workspace = true }
clap = { workspace = true }
Expand All @@ -20,6 +21,7 @@ dialoguer = { workspace = true }
e3-config = { workspace = true }
e3-crypto = { workspace = true }
e3-entrypoint = { workspace = true }
e3-evm = { workspace = true }
e3-events = { workspace = true }
e3-init = { workspace = true }
e3-support-scripts = { workspace = true }
Expand Down
154 changes: 154 additions & 0 deletions crates/cli/src/ciphernode/context.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,154 @@
// SPDX-License-Identifier: LGPL-3.0-only
//
// This file is provided WITHOUT ANY WARRANTY;
// without even the implied warranty of MERCHANTABILITY
// or FITNESS FOR A PARTICULAR PURPOSE.

use std::str::FromStr;

use alloy::{primitives::Address, providers::WalletProvider, sol};
use anyhow::{anyhow, Context, Result};
use e3_config::{chain_config::ChainConfig, AppConfig};
use e3_crypto::Cipher;
use e3_entrypoint::helpers::datastore::get_repositories;
use e3_evm::{
helpers::{load_signer_from_repository, ConcreteWriteProvider, EthProvider, ProviderConfig},
EthPrivateKeyRepositoryFactory,
};

mod bonding_registry_contract {
use super::sol;

sol!(
#[sol(rpc)]
BondingRegistryContract,
"../../packages/enclave-contracts/artifacts/contracts/interfaces/IBondingRegistry.sol/IBondingRegistry.json"
Comment thread
hmzakhalid marked this conversation as resolved.
);
}

mod enclave_ticket_token_contract {
use super::sol;

sol!(
#[sol(rpc)]
EnclaveTicketTokenContract,
"../../packages/enclave-contracts/artifacts/contracts/token/EnclaveTicketToken.sol/EnclaveTicketToken.json"
);
}

mod erc20_metadata_interface {
use super::sol;

sol!(
#[sol(rpc)]
interface IERC20Metadata {
function balanceOf(address account) external view returns (uint256);
function allowance(address owner, address spender) external view returns (uint256);
function approve(address spender, uint256 amount) external returns (bool);
function decimals() external view returns (uint8);
function symbol() external view returns (string memory);
function name() external view returns (string memory);
}
);
}
Comment thread
coderabbitai[bot] marked this conversation as resolved.

use bonding_registry_contract::BondingRegistryContract;
use enclave_ticket_token_contract::EnclaveTicketTokenContract;
use erc20_metadata_interface::IERC20Metadata;

pub(crate) struct ChainContext {
chain_label: String,
bonding_registry: Address,
provider: EthProvider<ConcreteWriteProvider>,
signer_address: Address,
}

impl ChainContext {
pub(crate) async fn new(config: &AppConfig, selection: Option<&str>) -> Result<Self> {
let chain = select_chain(config, selection)?;
let bonding_registry = parse_address(chain.contracts.bonding_registry.address())?;

let rpc = chain.rpc_url()?;
let cipher = Cipher::from_file(config.key_file()).await?;
let repositories = get_repositories(config)?;
let signer = load_signer_from_repository(repositories.eth_private_key(), &cipher).await?;
let provider = ProviderConfig::new(rpc, chain.rpc_auth.clone())
.create_signer_provider(&signer)
.await?;
let signer_address = provider.provider().default_signer_address();

let label = selection.unwrap_or(chain.name.as_str()).to_string();

Ok(Self {
chain_label: label,
bonding_registry,
provider,
signer_address,
})
}

fn provider_client(&self) -> ConcreteWriteProvider {
self.provider.provider().clone()
}

pub(crate) fn bonding(
&self,
) -> BondingRegistryContract::BondingRegistryContractInstance<ConcreteWriteProvider> {
BondingRegistryContract::new(self.bonding_registry, self.provider_client())
}

pub(crate) fn operator(&self) -> Address {
self.signer_address
}

pub(crate) fn chain_label(&self) -> &str {
&self.chain_label
}

pub(crate) fn bonding_registry(&self) -> Address {
self.bonding_registry
}

pub(crate) async fn license_token_address(&self) -> Result<Address> {
Ok(self.bonding().getLicenseToken().call().await?)
}

pub(crate) async fn ticket_token_address(&self) -> Result<Address> {
Ok(self.bonding().getTicketToken().call().await?)
}

pub(crate) async fn ticket_underlying_address(&self) -> Result<Address> {
let ticket = self.ticket_token_address().await?;
Ok(
EnclaveTicketTokenContract::new(ticket, self.provider_client())
.underlying()
.call()
.await?,
)
}

pub(crate) fn erc20(
&self,
address: Address,
) -> IERC20Metadata::IERC20MetadataInstance<ConcreteWriteProvider> {
IERC20Metadata::new(address, self.provider_client())
}
}

fn select_chain<'a>(config: &'a AppConfig, name: Option<&str>) -> Result<&'a ChainConfig> {
match name {
Some(desired) => config
.chains()
.iter()
.find(|c| c.name == desired)
.ok_or_else(|| anyhow!("Chain '{}' not found in configuration", desired)),
None => config
.chains()
.first()
.ok_or_else(|| anyhow!("No chains configured. Run `enclave config-set` first.")),
}
}

fn parse_address(value: &str) -> Result<Address> {
Address::from_str(value).context("Invalid address in configuration")
}
92 changes: 92 additions & 0 deletions crates/cli/src/ciphernode/license.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,92 @@
// SPDX-License-Identifier: LGPL-3.0-only
//
// This file is provided WITHOUT ANY WARRANTY;
// without even the implied warranty of MERCHANTABILITY
// or FITNESS FOR A PARTICULAR PURPOSE.

use alloy::primitives::U256;
use anyhow::Result;

use super::context::ChainContext;
use super::utils::{ensure_allowance, parse_amount};
use super::LicenseCommands;

pub(crate) async fn execute(ctx: &ChainContext, command: LicenseCommands) -> Result<()> {
match command {
LicenseCommands::Bond { amount } => {
bond_license(ctx, &amount).await?;
}
LicenseCommands::Unbond { amount } => {
let license = ctx.license_token_address().await?;
let decimals = ctx.erc20(license).decimals().call().await?;
let parsed = parse_amount(&amount, decimals)?;
let receipt = ctx
.bonding()
.unbondLicense(parsed)
.send()
.await?
.get_receipt()
.await?;
println!(
"Queued {} ENCL for exit (tx: {:#x})",
amount, receipt.transaction_hash
);
}
LicenseCommands::Claim {
max_ticket,
max_license,
} => {
let ticket_decimals = ctx
.erc20(ctx.ticket_token_address().await?)
.decimals()
.call()
.await?;
let license_decimals = ctx
.erc20(ctx.license_token_address().await?)
.decimals()
.call()
.await?;

let ticket = if let Some(value) = max_ticket {
parse_amount(&value, ticket_decimals)?
} else {
U256::MAX
};
let license = if let Some(value) = max_license {
parse_amount(&value, license_decimals)?
} else {
U256::MAX
};
let receipt = ctx
.bonding()
.claimExits(ticket, license)
.send()
.await?
.get_receipt()
.await?;
println!("Claimed exits (tx: {:#x})", receipt.transaction_hash);
}
}

Ok(())
}

async fn bond_license(ctx: &ChainContext, amount: &str) -> Result<()> {
let license = ctx.license_token_address().await?;
let erc20 = ctx.erc20(license);
let decimals = erc20.decimals().call().await?;
let parsed = parse_amount(amount, decimals)?;
ensure_allowance(ctx, license, ctx.bonding_registry(), parsed).await?;
let receipt = ctx
.bonding()
.bondLicense(parsed)
.send()
.await?
.get_receipt()
.await?;
println!(
"Bonded {} ENCL (tx: {:#x})",
amount, receipt.transaction_hash
);
Ok(())
}
Loading
Loading