Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

2022 add validation scripts after contract deployments #2081

Draft
wants to merge 12 commits into
base: main
Choose a base branch
from
Draft
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
67 changes: 64 additions & 3 deletions hotshot-state-prover/src/service.rs
Original file line number Diff line number Diff line change
Expand Up @@ -563,7 +563,7 @@ mod test {
use jf_signature::{schnorr::SchnorrSignatureScheme, SignatureScheme};
use jf_utils::test_rng;
use sequencer_utils::{
deployer::{self, test_helpers::deploy_light_client_contract_as_proxy_for_test},
deployer::{self, test_helpers::deploy_light_client_contract_as_proxy_for_test, is_valid_admin_light_client_proxy},
test_utils::setup_test,
};

Expand Down Expand Up @@ -746,13 +746,14 @@ mod test {
}

#[async_std::test]
async fn test_validate_light_contract_is_proxy() -> Result<()> {
async fn test_validate_light_contract_proxy() -> Result<()> {
// Checks that the implementation address was set and the admin is as expected
setup_test();

let anvil = Anvil::new().spawn();
let dummy_genesis = ParsedLightClientState::dummy_genesis();
let dummy_stake_genesis = ParsedStakeTableState::dummy_genesis();
let (_wallet, contract) = deploy_contract_as_proxy_for_test(
let (wallet, contract) = deploy_contract_as_proxy_for_test(
&anvil,
dummy_genesis.clone(),
dummy_stake_genesis.clone(),
Expand Down Expand Up @@ -781,6 +782,66 @@ mod test {
result.is_ok(),
"Expected Light Client contract to be a proxy, but it was not"
);
// validate that the admin is the deployer account
let admin = wallet.clone().address();
let admin_result =
is_valid_admin_light_client_proxy(wallet, contract.clone().address(), admin).await?;
assert!(
admin_result,
"Testing whether the admin on the contract is {}",
admin
);

Ok(())
}

#[async_std::test]

async fn test_fail_validate_light_contract_proxy() -> Result<()> {
// Checks that the implementation address was not set as we expect when we deploy as a regular contract without a proxy

setup_test();

let anvil = Anvil::new().spawn();
let dummy_genesis = ParsedLightClientState::dummy_genesis();
let dummy_stake_genesis = ParsedStakeTableState::dummy_genesis();
//deploy as a regular contract and not a proxy so that the validation fails as we expect
let (wallet, contract) =
deploy_contract_for_test(&anvil, dummy_genesis.clone(), dummy_stake_genesis.clone())
.await?;

// now test if we can read from the contract
let genesis: ParsedLightClientState = contract.genesis_state().await?.into();
assert_eq!(genesis, dummy_genesis);

let stake_genesis: ParsedStakeTableState =
contract.genesis_stake_table_state().await?.into();
assert_eq!(stake_genesis, dummy_stake_genesis);

let config = StateProverConfig {
provider: Url::parse(anvil.endpoint().as_str())
.expect("Cannot parse anvil endpoint to URL."),
light_client_address: contract.clone().address(),
..Default::default()
};

let result = config.validate_light_client_contract().await;

// we expect the result to be an error because the contract is not a proxy
assert!(
result.is_err(),
"Expected Light Client contract not to be a proxy, but it was"
);

// validate that there is no owner set
let admin = wallet.clone().address();
let admin_result =
is_valid_admin_light_client_proxy(wallet, contract.clone().address(), admin).await?;
assert!(
!admin_result,
"Testing whether the admin on the contract is the zero address"
);

Ok(())
}

Expand Down
183 changes: 180 additions & 3 deletions utils/src/deployer.rs
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@ use async_std::sync::Arc;
use clap::{builder::OsStr, Parser, ValueEnum};
use contract_bindings::{
erc1967_proxy::ERC1967Proxy,
fee_contract::FeeContract,
fee_contract::{FeeContract, FEECONTRACT_ABI, FEECONTRACT_BYTECODE},
hot_shot::HotShot,
light_client::{LightClient, LIGHTCLIENT_ABI},
light_client_mock::LIGHTCLIENTMOCK_ABI,
Expand Down Expand Up @@ -307,6 +307,143 @@ pub async fn deploy_mock_light_client_contract<M: Middleware + 'static>(
Ok(contract.address())
}

/// Deployment `LightClientMock.sol` as proxy for testing
pub async fn deploy_light_client_contract_as_proxy_for_test<M: Middleware + 'static>(
l1: Arc<M>,
contracts: &mut Contracts,
constructor_args: Option<LightClientConstructorArgs>,
) -> anyhow::Result<Address> {
// Deploy library contracts.
let plonk_verifier = contracts
.deploy_tx(
Contract::PlonkVerifier,
PlonkVerifier2::deploy(l1.clone(), ())?,
)
.await?;
let vk = contracts
.deploy_tx(
Contract::StateUpdateVK,
LightClientStateUpdateVK::deploy(l1.clone(), ())?,
)
.await?;

// Link with LightClient's bytecode artifacts. We include the unlinked bytecode for the contract
// in this binary so that the contract artifacts do not have to be distributed with the binary.
// This should be fine because if the bindings we are importing are up to date, so should be the
// contract artifacts: this is no different than foundry inlining bytecode objects in generated
// bindings, except that foundry doesn't provide the bytecode for contracts that link with
// libraries, so we have to do it ourselves.
let mut bytecode: BytecodeObject = serde_json::from_str(include_str!(
"../../contract-bindings/artifacts/LightClient_bytecode.json",
))?;
bytecode
.link_fully_qualified(
"contracts/src/libraries/PlonkVerifier.sol:PlonkVerifier",
plonk_verifier,
)
.resolve()
.context("error linking PlonkVerifier lib")?;
bytecode
.link_fully_qualified(
"contracts/src/libraries/LightClientStateUpdateVK.sol:LightClientStateUpdateVK",
vk,
)
.resolve()
.context("error linking LightClientStateUpdateVK lib")?;
ensure!(!bytecode.is_unlinked(), "failed to link LightClient.sol");

// Deploy light client.
let light_client_factory = ContractFactory::new(
LIGHTCLIENT_ABI.clone(),
bytecode
.as_bytes()
.context("error parsing bytecode for linked LightClient contract")?
.clone(),
l1.clone(),
);
let contract = light_client_factory.deploy(())?.send().await?;

let light_client_address = contract.address();

let light_client = LightClient::new(light_client_address, l1.clone());

let constructor_args: LightClientConstructorArgs = match constructor_args {
Some(args) => args,
None => LightClientConstructorArgs::dummy_genesis(),
};

let deployer = *(l1.clone().get_accounts().await?.first()).expect("Address not found");

let data = light_client
.initialize(
constructor_args.light_client_state.into(),
constructor_args.stake_table_state.into(),
constructor_args.max_history_seconds,
deployer,
)
.calldata()
.context("calldata for initialize transaction not available")?;
let light_client_proxy_address = contracts
.deploy_tx(
Contract::LightClientProxy,
ERC1967Proxy::deploy(l1, (light_client_address, data))?,
)
.await?;

Ok(light_client_proxy_address)
}

/// Default deployment function `FeeContract.sol` for testing
///
pub async fn deploy_fee_contract<M: Middleware + 'static>(l1: Arc<M>) -> anyhow::Result<Address> {
// Deploy fee contract
let fee_contract_factory = ContractFactory::new(
FEECONTRACT_ABI.clone(),
FEECONTRACT_BYTECODE.clone(),
l1.clone(),
);
let contract = fee_contract_factory.deploy(())?.send().await?;

let fee_contract_address = contract.address();

Ok(fee_contract_address)
}

/// Default deployment function `FeeContract.sol` (as proxy) for testing
///
pub async fn deploy_fee_contract_as_proxy<M: Middleware + 'static>(
l1: Arc<M>,
contracts: &mut Contracts,
) -> anyhow::Result<Address> {
// Deploy fee contract
let fee_contract_factory = ContractFactory::new(
FEECONTRACT_ABI.clone(),
FEECONTRACT_BYTECODE.clone(),
l1.clone(),
);
let contract = fee_contract_factory.deploy(())?.send().await?;

let fee_contract_address = contract.address();

let fee_contract = FeeContract::new(fee_contract_address, l1.clone());

let deployer = *(l1.clone().get_accounts().await?.first()).expect("Address not found");

let data = fee_contract
.initialize(deployer)
.calldata()
.context("calldata for initialize transaction not available")?;

let fee_contract_proxy_address = contracts
.deploy_tx(
Contract::FeeContractProxy,
ERC1967Proxy::deploy(l1.clone(), (fee_contract_address, data))?,
)
.await?;

Ok(fee_contract_proxy_address)
}

#[allow(clippy::too_many_arguments)]
pub async fn deploy(
l1url: Url,
Expand Down Expand Up @@ -387,7 +524,7 @@ pub async fn deploy(
// Instantiate a wrapper with the proxy address and light client ABI.
let proxy = LightClient::new(light_client_proxy_address, l1.clone());

// Perission the light client prover.
// Permission the light client prover.
if let Some(prover) = permissioned_prover {
tracing::info!(%light_client_proxy_address, %prover, "setting permissioned prover");
proxy.set_permissioned_prover(prover).send().await?.await?;
Expand All @@ -401,6 +538,14 @@ pub async fn deploy(
"transferring light client proxy ownership to multisig",
);
proxy.transfer_ownership(owner).send().await?.await?;

// Confirm that the multisig address has been set as the owner
if !is_valid_admin_light_client_proxy(l1.clone(), light_client_proxy_address, owner)
.await
.expect("Failed to find the expected admin on the proxy")
{
panic!("Light Client Contract's proxy admin not set to the multisig");
}
}
}

Expand All @@ -421,7 +566,7 @@ pub async fn deploy(
)
.await?;

// confirm that the implementation address is the address of the fee contract deployed above
// Confirm that the implementation address is the address of the fee contract deployed above
if !is_proxy_contract(provider.clone(), fee_contract_proxy_address)
.await
.expect("Failed to determine if fee contract is a proxy")
Expand All @@ -440,6 +585,14 @@ pub async fn deploy(
"transferring fee contract proxy ownership to multisig",
);
proxy.transfer_ownership(owner).send().await?.await?;

// Confirm that the multisig address has been set as the owner
if !is_valid_admin_fee_contract_proxy(l1.clone(), fee_contract_proxy_address, owner)
.await
.expect("Failed to find the expected admin on the proxy")
{
panic!("Fee Contract's proxy admin not set to the multisig");
}
}
}

Expand Down Expand Up @@ -472,6 +625,30 @@ pub async fn is_proxy_contract(
Ok(implementation_address != H160::zero())
}

pub async fn is_valid_admin_light_client_proxy<M: Middleware + 'static>(
client: Arc<M>,
proxy_address: H160,
admin: H160,
) -> anyhow::Result<bool> {
let proxy_contract = LightClient::new(proxy_address, client);
let proxy_contract_admin = proxy_contract.owner().await?;

// we expect the admin_address to be equal to the one passed in
Ok(proxy_contract_admin == admin)
}

pub async fn is_valid_admin_fee_contract_proxy<M: Middleware + 'static>(
client: Arc<M>,
proxy_address: H160,
admin: H160,
) -> anyhow::Result<bool> {
let proxy_contract = FeeContract::new(proxy_address, client);
let proxy_contract_admin = proxy_contract.owner().await?;

// we expect the admin_address to be equal to the one passed in
Ok(proxy_contract_admin == admin)
}

#[derive(Clone, Copy, Debug, PartialEq, Eq, ValueEnum)]
pub enum ContractGroup {
#[clap(name = "hotshot")]
Expand Down
Loading