mirror of
https://github.com/serai-dex/serai.git
synced 2025-12-08 12:19:24 +00:00
Test the Deployer contract
This commit is contained in:
4
Cargo.lock
generated
4
Cargo.lock
generated
@@ -6926,14 +6926,18 @@ version = "0.1.0"
|
|||||||
dependencies = [
|
dependencies = [
|
||||||
"alloy-consensus",
|
"alloy-consensus",
|
||||||
"alloy-core",
|
"alloy-core",
|
||||||
|
"alloy-node-bindings",
|
||||||
"alloy-provider",
|
"alloy-provider",
|
||||||
|
"alloy-rpc-client",
|
||||||
"alloy-rpc-types-eth",
|
"alloy-rpc-types-eth",
|
||||||
"alloy-simple-request-transport",
|
"alloy-simple-request-transport",
|
||||||
"alloy-sol-macro",
|
"alloy-sol-macro",
|
||||||
"alloy-sol-types",
|
"alloy-sol-types",
|
||||||
"alloy-transport",
|
"alloy-transport",
|
||||||
"build-solidity-contracts",
|
"build-solidity-contracts",
|
||||||
|
"serai-ethereum-test-primitives",
|
||||||
"serai-processor-ethereum-primitives",
|
"serai-processor-ethereum-primitives",
|
||||||
|
"tokio",
|
||||||
]
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
|
|||||||
@@ -33,3 +33,11 @@ ethereum-primitives = { package = "serai-processor-ethereum-primitives", path =
|
|||||||
|
|
||||||
[build-dependencies]
|
[build-dependencies]
|
||||||
build-solidity-contracts = { path = "../../../networks/ethereum/build-contracts", default-features = false }
|
build-solidity-contracts = { path = "../../../networks/ethereum/build-contracts", default-features = false }
|
||||||
|
|
||||||
|
[dev-dependencies]
|
||||||
|
alloy-rpc-client = { version = "0.9", default-features = false }
|
||||||
|
alloy-node-bindings = { version = "0.9", default-features = false }
|
||||||
|
|
||||||
|
tokio = { version = "1.0", default-features = false, features = ["rt-multi-thread", "macros"] }
|
||||||
|
|
||||||
|
ethereum-test-primitives = { package = "serai-ethereum-test-primitives", path = "../test-primitives" }
|
||||||
|
|||||||
@@ -4,7 +4,7 @@
|
|||||||
|
|
||||||
use std::sync::Arc;
|
use std::sync::Arc;
|
||||||
|
|
||||||
use alloy_core::primitives::{hex::FromHex, Address, U256, Bytes, TxKind};
|
use alloy_core::primitives::{hex, Address, U256, Bytes, TxKind};
|
||||||
use alloy_consensus::{Signed, TxLegacy};
|
use alloy_consensus::{Signed, TxLegacy};
|
||||||
|
|
||||||
use alloy_sol_types::SolCall;
|
use alloy_sol_types::SolCall;
|
||||||
@@ -14,6 +14,9 @@ use alloy_transport::{TransportErrorKind, RpcError};
|
|||||||
use alloy_simple_request_transport::SimpleRequest;
|
use alloy_simple_request_transport::SimpleRequest;
|
||||||
use alloy_provider::{Provider, RootProvider};
|
use alloy_provider::{Provider, RootProvider};
|
||||||
|
|
||||||
|
#[cfg(test)]
|
||||||
|
mod tests;
|
||||||
|
|
||||||
#[rustfmt::skip]
|
#[rustfmt::skip]
|
||||||
#[expect(warnings)]
|
#[expect(warnings)]
|
||||||
#[expect(needless_pass_by_value)]
|
#[expect(needless_pass_by_value)]
|
||||||
@@ -24,6 +27,17 @@ mod abi {
|
|||||||
alloy_sol_macro::sol!("contracts/Deployer.sol");
|
alloy_sol_macro::sol!("contracts/Deployer.sol");
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const BYTECODE: &[u8] = {
|
||||||
|
const BYTECODE_HEX: &[u8] =
|
||||||
|
include_bytes!(concat!(env!("OUT_DIR"), "/serai-processor-ethereum-deployer/Deployer.bin"));
|
||||||
|
const BYTECODE: [u8; BYTECODE_HEX.len() / 2] =
|
||||||
|
match hex::const_decode_to_array::<{ BYTECODE_HEX.len() / 2 }>(BYTECODE_HEX) {
|
||||||
|
Ok(bytecode) => bytecode,
|
||||||
|
Err(_) => panic!("Deployer.bin did not contain valid hex"),
|
||||||
|
};
|
||||||
|
&BYTECODE
|
||||||
|
};
|
||||||
|
|
||||||
/// The Deployer contract for the Serai Router contract.
|
/// The Deployer contract for the Serai Router contract.
|
||||||
///
|
///
|
||||||
/// This Deployer has a deterministic address, letting it be immediately identified on any instance
|
/// This Deployer has a deterministic address, letting it be immediately identified on any instance
|
||||||
@@ -38,21 +52,39 @@ 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: &[u8] =
|
let bytecode = Bytes::from(BYTECODE);
|
||||||
include_bytes!(concat!(env!("OUT_DIR"), "/serai-processor-ethereum-deployer/Deployer.bin"));
|
|
||||||
let bytecode =
|
|
||||||
Bytes::from_hex(BYTECODE).expect("compiled-in Deployer bytecode wasn't valid hex");
|
|
||||||
|
|
||||||
// Legacy transactions are used to ensure the widest possible degree of support across EVMs
|
// Legacy transactions are used to ensure the widest possible degree of support across EVMs
|
||||||
let tx = TxLegacy {
|
let tx = TxLegacy {
|
||||||
chain_id: None,
|
chain_id: None,
|
||||||
nonce: 0,
|
nonce: 0,
|
||||||
// This uses a fixed gas price as necessary to achieve a deterministic address
|
/*
|
||||||
// The gas price is fixed to 100 gwei, which should be incredibly generous, in order to make
|
This needs to use a fixed gas price to achieve a deterministic address. The gas price is
|
||||||
// this getting stuck unlikely. While expensive, this only has to occur once
|
fixed to 100 gwei, which should be generous, in order to make this unlikely to get stuck.
|
||||||
|
While potentially expensive, this only has to occur per chain this is deployed on.
|
||||||
|
|
||||||
|
If this is too low of a gas price, private mempools can be used, with other transactions in
|
||||||
|
the bundle raising the gas price to acceptable levels. While this strategy could be
|
||||||
|
entirely relied upon, allowing the gas price paid to reflect the network's actual gas
|
||||||
|
price, that wouldn't work for EVM networks without private mempools.
|
||||||
|
|
||||||
|
That leaves this as failing only if it violates a protocol constant, or if the gas price is
|
||||||
|
too low on a network without private mempools to publish via. In that case, this code
|
||||||
|
should to be forked to accept an enum of which network the deployment is for (with the gas
|
||||||
|
price derivative of that, as common as possible across networks to minimize the amount of
|
||||||
|
addresses representing the Deployer).
|
||||||
|
*/
|
||||||
gas_price: 100_000_000_000u128,
|
gas_price: 100_000_000_000u128,
|
||||||
// TODO: Use a more accurate gas limit
|
/*
|
||||||
gas_limit: 1_000_000u64,
|
This is twice the cost of deployment as of Ethereum's Cancun upgrade. The wide margin is to
|
||||||
|
increase the likelihood of surviving changes to the cost of contract deployment (notably
|
||||||
|
the gas cost of calldata). While wasteful, this only has to be done once per chain and is
|
||||||
|
accepted accordingly.
|
||||||
|
|
||||||
|
If this is ever unacceptable, the parameterization suggested in case the `gas_price` is
|
||||||
|
unacceptable should be implemented.
|
||||||
|
*/
|
||||||
|
gas_limit: 300_698,
|
||||||
to: TxKind::Create,
|
to: TxKind::Create,
|
||||||
value: U256::ZERO,
|
value: U256::ZERO,
|
||||||
input: bytecode,
|
input: bytecode,
|
||||||
|
|||||||
107
processor/ethereum/deployer/src/tests.rs
Normal file
107
processor/ethereum/deployer/src/tests.rs
Normal file
@@ -0,0 +1,107 @@
|
|||||||
|
use std::sync::Arc;
|
||||||
|
|
||||||
|
use alloy_rpc_types_eth::{TransactionInput, TransactionRequest};
|
||||||
|
use alloy_simple_request_transport::SimpleRequest;
|
||||||
|
use alloy_rpc_client::ClientBuilder;
|
||||||
|
use alloy_provider::{Provider, RootProvider};
|
||||||
|
|
||||||
|
use alloy_node_bindings::Anvil;
|
||||||
|
|
||||||
|
use crate::{
|
||||||
|
abi::Deployer::{PriorDeployed, DeploymentFailed, DeployerErrors},
|
||||||
|
Deployer,
|
||||||
|
};
|
||||||
|
|
||||||
|
#[tokio::test]
|
||||||
|
async fn test_deployer() {
|
||||||
|
const CANCUN: &str = "cancun";
|
||||||
|
const LATEST: &str = "latest";
|
||||||
|
|
||||||
|
for network in [CANCUN, LATEST] {
|
||||||
|
let anvil = Anvil::new().arg("--hardfork").arg(network).spawn();
|
||||||
|
|
||||||
|
let provider = Arc::new(RootProvider::new(
|
||||||
|
ClientBuilder::default().transport(SimpleRequest::new(anvil.endpoint()), true),
|
||||||
|
));
|
||||||
|
|
||||||
|
// Deploy the Deployer
|
||||||
|
{
|
||||||
|
let deployment_tx = Deployer::deployment_tx();
|
||||||
|
let gas_programmed = deployment_tx.tx().gas_limit;
|
||||||
|
let receipt = ethereum_test_primitives::publish_tx(&provider, deployment_tx).await;
|
||||||
|
assert!(receipt.status());
|
||||||
|
assert_eq!(receipt.contract_address.unwrap(), Deployer::address());
|
||||||
|
|
||||||
|
if network == CANCUN {
|
||||||
|
// Check the gas programmed was twice the gas used
|
||||||
|
// We only check this for cancun as the constant was programmed per cancun's gas pricing
|
||||||
|
assert_eq!(2 * receipt.gas_used, gas_programmed);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Deploy the deployer with the deployer
|
||||||
|
let mut deploy_tx = Deployer::deploy_tx(crate::BYTECODE.to_vec());
|
||||||
|
deploy_tx.gas_price = 100_000_000_000u128;
|
||||||
|
deploy_tx.gas_limit = 1_000_000;
|
||||||
|
{
|
||||||
|
let deploy_tx = ethereum_primitives::deterministically_sign(deploy_tx.clone());
|
||||||
|
let receipt = ethereum_test_primitives::publish_tx(&provider, deploy_tx).await;
|
||||||
|
assert!(receipt.status());
|
||||||
|
}
|
||||||
|
|
||||||
|
// Verify we can now find the deployer
|
||||||
|
{
|
||||||
|
let deployer = Deployer::new(provider.clone()).await.unwrap().unwrap();
|
||||||
|
let deployed_deployer = deployer
|
||||||
|
.find_deployment(ethereum_primitives::keccak256(crate::BYTECODE))
|
||||||
|
.await
|
||||||
|
.unwrap()
|
||||||
|
.unwrap();
|
||||||
|
assert_eq!(
|
||||||
|
provider.get_code_at(deployed_deployer).await.unwrap(),
|
||||||
|
provider.get_code_at(Deployer::address()).await.unwrap(),
|
||||||
|
);
|
||||||
|
assert!(deployed_deployer != Deployer::address());
|
||||||
|
}
|
||||||
|
|
||||||
|
// Verify deploying the same init code multiple times fails
|
||||||
|
{
|
||||||
|
let mut deploy_tx = deploy_tx;
|
||||||
|
// Change the gas price to cause a distinct message, and with it, a distinct signer
|
||||||
|
deploy_tx.gas_price += 1;
|
||||||
|
let deploy_tx = ethereum_primitives::deterministically_sign(deploy_tx);
|
||||||
|
let receipt = ethereum_test_primitives::publish_tx(&provider, deploy_tx.clone()).await;
|
||||||
|
assert!(!receipt.status());
|
||||||
|
|
||||||
|
let call = TransactionRequest::default()
|
||||||
|
.to(Deployer::address())
|
||||||
|
.input(TransactionInput::new(deploy_tx.tx().input.clone()));
|
||||||
|
let call_err = provider.call(&call).await.unwrap_err();
|
||||||
|
assert!(matches!(
|
||||||
|
call_err.as_error_resp().unwrap().as_decoded_error::<DeployerErrors>(true).unwrap(),
|
||||||
|
DeployerErrors::PriorDeployed(PriorDeployed {}),
|
||||||
|
));
|
||||||
|
}
|
||||||
|
|
||||||
|
// Verify deployment failures yield errors properly
|
||||||
|
{
|
||||||
|
// 0xfe is an invalid opcode which is guaranteed to remain invalid
|
||||||
|
let mut deploy_tx = Deployer::deploy_tx(vec![0xfe]);
|
||||||
|
deploy_tx.gas_price = 100_000_000_000u128;
|
||||||
|
deploy_tx.gas_limit = 1_000_000;
|
||||||
|
|
||||||
|
let deploy_tx = ethereum_primitives::deterministically_sign(deploy_tx);
|
||||||
|
let receipt = ethereum_test_primitives::publish_tx(&provider, deploy_tx.clone()).await;
|
||||||
|
assert!(!receipt.status());
|
||||||
|
|
||||||
|
let call = TransactionRequest::default()
|
||||||
|
.to(Deployer::address())
|
||||||
|
.input(TransactionInput::new(deploy_tx.tx().input.clone()));
|
||||||
|
let call_err = provider.call(&call).await.unwrap_err();
|
||||||
|
assert!(matches!(
|
||||||
|
call_err.as_error_resp().unwrap().as_decoded_error::<DeployerErrors>(true).unwrap(),
|
||||||
|
DeployerErrors::DeploymentFailed(DeploymentFailed {}),
|
||||||
|
));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
Reference in New Issue
Block a user