Don't have the Deployer store the deployment block

Also updates how re-entrancy is handled to a more efficient and portable
mechanism.
This commit is contained in:
Luke Parker 2024-09-17 01:05:31 -04:00
parent 7feb7aed22
commit ee0efe7cde
2 changed files with 19 additions and 43 deletions

View file

@ -34,41 +34,12 @@ pragma solidity ^0.8.26;
*/ */
contract Deployer { contract Deployer {
struct Deployment { mapping(bytes32 => address) public deployments;
uint64 block_number;
address created_contract;
}
mapping(bytes32 => Deployment) public deployments;
error Reentrancy();
error PriorDeployed(); error PriorDeployed();
error DeploymentFailed(); error DeploymentFailed();
function deploy(bytes memory init_code) external { function deploy(bytes memory init_code) external {
// Prevent re-entrancy
// If we did allow it, one could deploy the same contract multiple times (with one overwriting
// the other's set value in storage)
bool called;
// This contract doesn't have any other use of transient storage, nor is to be inherited, making
// this usage of the zero address safe
assembly {
called := tload(0)
}
if (called) {
revert Reentrancy();
}
assembly {
tstore(0, 1)
}
// Check this wasn't prior deployed
bytes32 init_code_hash = keccak256(init_code);
Deployment memory deployment = deployments[init_code_hash];
if (deployment.created_contract == address(0)) {
revert PriorDeployed();
}
// Deploy the contract // Deploy the contract
address created_contract; address created_contract;
assembly { assembly {
@ -78,9 +49,15 @@ contract Deployer {
revert DeploymentFailed(); revert DeploymentFailed();
} }
// Set the dpeloyment to storage bytes32 init_code_hash = keccak256(init_code);
deployment.block_number = uint64(block.number);
deployment.created_contract = created_contract; // Check this wasn't prior deployed
deployments[init_code_hash] = deployment; // We check this *after* deploymeing (in violation of CEI) to handle re-entrancy related bugs
if (deployments[init_code_hash] != address(0)) {
revert PriorDeployed();
}
// Write the deployment to storage
deployments[init_code_hash] = created_contract;
} }
} }

View file

@ -30,7 +30,7 @@ mod abi {
/// compatible chain. It then supports retrieving the Router contract's address (which isn't /// compatible chain. It then supports retrieving the Router contract's address (which isn't
/// deterministic) using a single call. /// deterministic) using a single call.
#[derive(Clone, Debug)] #[derive(Clone, Debug)]
pub struct Deployer; pub struct Deployer(Arc<RootProvider<SimpleRequest>>);
impl Deployer { impl Deployer {
/// Obtain the transaction to deploy this contract, already signed. /// Obtain the transaction to deploy this contract, already signed.
/// ///
@ -38,8 +38,8 @@ impl Deployer {
/// funded for this transaction to be submitted. This account has no known private key to anyone /// funded for this transaction to be submitted. This account has no known private key to anyone
/// so ETH sent can be neither misappropriated nor returned. /// so ETH sent can be neither misappropriated nor returned.
pub fn deployment_tx() -> Signed<TxLegacy> { pub fn deployment_tx() -> Signed<TxLegacy> {
pub const BYTECODE: &str = pub const BYTECODE: &[u8] =
include_str!(concat!(env!("OUT_DIR"), "/serai-processor-ethereum-deployer/Deployer.bin")); include_bytes!(concat!(env!("OUT_DIR"), "/serai-processor-ethereum-deployer/Deployer.bin"));
let bytecode = let bytecode =
Bytes::from_hex(BYTECODE).expect("compiled-in Deployer bytecode wasn't valid hex"); Bytes::from_hex(BYTECODE).expect("compiled-in Deployer bytecode wasn't valid hex");
@ -75,28 +75,27 @@ impl Deployer {
if code.is_empty() { if code.is_empty() {
return Ok(None); return Ok(None);
} }
Ok(Some(Self)) Ok(Some(Self(provider)))
} }
/// Find the deployment of a contract. /// Find the deployment of a contract.
pub async fn find_deployment( pub async fn find_deployment(
&self, &self,
provider: Arc<RootProvider<SimpleRequest>>,
init_code_hash: [u8; 32], init_code_hash: [u8; 32],
) -> Result<Option<abi::Deployer::Deployment>, RpcError<TransportErrorKind>> { ) -> Result<Option<Address>, RpcError<TransportErrorKind>> {
let call = TransactionRequest::default().to(Self::address()).input(TransactionInput::new( let call = TransactionRequest::default().to(Self::address()).input(TransactionInput::new(
abi::Deployer::deploymentsCall::new((init_code_hash.into(),)).abi_encode().into(), abi::Deployer::deploymentsCall::new((init_code_hash.into(),)).abi_encode().into(),
)); ));
let bytes = provider.call(&call).await?; let bytes = self.0.call(&call).await?;
let deployment = abi::Deployer::deploymentsCall::abi_decode_returns(&bytes, true) let deployment = abi::Deployer::deploymentsCall::abi_decode_returns(&bytes, true)
.map_err(|e| { .map_err(|e| {
TransportErrorKind::Custom( TransportErrorKind::Custom(
format!("node returned a non-Deployment for function returning Deployment: {e:?}").into(), format!("node returned a non-address for function returning address: {e:?}").into(),
) )
})? })?
._0; ._0;
if deployment.created_contract == [0; 20] { if **deployment == [0; 20] {
return Ok(None); return Ok(None);
} }
Ok(Some(deployment)) Ok(Some(deployment))