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
46 changes: 46 additions & 0 deletions crates/anvil-polkadot/src/api_server/server.rs
Original file line number Diff line number Diff line change
Expand Up @@ -360,6 +360,9 @@ impl ApiServer {
EthRequest::SetStorageAt(address, key, value) => {
self.set_storage_at(address, key, value).to_rpc_result()
}
EthRequest::SetImmutableStorageAt(address, data) => {
self.set_immutable_storage_at(address, data).to_rpc_result()
}
EthRequest::SetChainId(chain_id) => self.set_chain_id(chain_id).to_rpc_result(),
// --- Revert ---
EthRequest::EvmSnapshot(()) => self.snapshot().await.to_rpc_result(),
Expand Down Expand Up @@ -1327,6 +1330,49 @@ impl ApiServer {
Ok(())
}

fn set_immutable_storage_at(
&self,
address: Address,
immutables: Vec<alloy_primitives::Bytes>,
) -> Result<()> {
node_info!("anvil_setImmutableStorageAt");

let latest_block = self.latest_block();

// Convert ABI-encoded immutable values (big-endian) to PVM format (little-endian).
//
// ## Data Format
//
// Each element in `immutables` is a single ABI-encoded immutable value:
// - Value in big-endian format (standard for Solidity ABI encoding)
// - Padded to 32 bytes
//
// Example: For a contract with immutables `uint256 value` and `address addr`:
// immutables[0] = <32-byte big-endian uint256>
// immutables[1] = <12 zeros + 20-byte address>
//
// ## Conversion
//
// This method converts each immutable value (32-byte chunk) from big-endian to
// little-endian, since PVM (Polkadot Virtual Machine) expects immutable data in
// little-endian format. The conversion is done by reversing each 32-byte word.
// Then all converted values are concatenated in order.
let pvm_data: Vec<u8> = immutables
.into_iter()
.flat_map(|immutable| {
let mut word = [0u8; 32];
let len = immutable.len().min(32);
word[..len].copy_from_slice(&immutable[..len]);
word.reverse();
word
})
.collect();

self.backend.inject_immutable_data(latest_block, address, pvm_data);

Ok(())
}

// ----- Wallet RPCs
async fn sign(&self, address: Address, content: impl AsRef<[u8]>) -> Result<String> {
Ok(alloy_primitives::hex::encode_prefixed(self.wallet.sign(address, content.as_ref())?))
Expand Down
20 changes: 20 additions & 0 deletions crates/anvil-polkadot/src/substrate_node/service/backend.rs
Original file line number Diff line number Diff line change
Expand Up @@ -207,6 +207,11 @@ impl BackendWithOverlay {
overrides.set_code_info(at, code_hash, code_info);
}

pub fn inject_immutable_data(&self, at: Hash, address: Address, data: Vec<u8>) {
let mut overrides = self.overrides.lock();
overrides.set_immutable_data(at, address, data);
}

pub fn inject_child_storage(
&self,
at: Hash,
Expand Down Expand Up @@ -353,6 +358,21 @@ impl StorageOverrides {
self.add(latest_block, changeset);
}

fn set_immutable_data(&mut self, latest_block: Hash, address: Address, data: Vec<u8>) {
let mut changeset = BlockOverrides::default();

// SCALE-encode as BoundedVec<u8>: compact_length prefix followed by raw bytes
let mut encoded = codec::Compact(data.len() as u32).encode();
encoded.extend_from_slice(&data);

changeset.top.insert(
well_known_keys::immutable_data_of(H160::from_slice(address.as_slice())),
Some(encoded),
);

self.add(latest_block, changeset);
}

fn set_child_storage(
&mut self,
latest_block: Hash,
Expand Down
9 changes: 9 additions & 0 deletions crates/anvil-polkadot/src/substrate_node/service/storage.rs
Original file line number Diff line number Diff line change
Expand Up @@ -90,4 +90,13 @@ pub mod well_known_keys {

key
}

pub fn immutable_data_of(address: H160) -> Vec<u8> {
let mut key = Vec::new();
key.extend_from_slice(&twox_128("Revive".as_bytes()));
key.extend_from_slice(&twox_128("ImmutableDataOf".as_bytes()));
key.extend_from_slice(&address.encode());

key
}
}
75 changes: 75 additions & 0 deletions crates/anvil-polkadot/test-data/ImmutableStorage.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,75 @@
{
"abi": [
{
"inputs": [
{
"internalType": "uint256",
"name": "_value",
"type": "uint256"
},
{
"internalType": "address",
"name": "_addr",
"type": "address"
}
],
"stateMutability": "nonpayable",
"type": "constructor"
},
{
"inputs": [],
"name": "getImmutableAddress",
"outputs": [
{
"internalType": "address",
"name": "",
"type": "address"
}
],
"stateMutability": "view",
"type": "function"
},
{
"inputs": [],
"name": "getImmutableValue",
"outputs": [
{
"internalType": "uint256",
"name": "",
"type": "uint256"
}
],
"stateMutability": "view",
"type": "function"
},
{
"inputs": [],
"name": "immutableAddress",
"outputs": [
{
"internalType": "address",
"name": "",
"type": "address"
}
],
"stateMutability": "view",
"type": "function"
},
{
"inputs": [],
"name": "immutableValue",
"outputs": [
{
"internalType": "uint256",
"name": "",
"type": "uint256"
}
],
"stateMutability": "view",
"type": "function"
}
],
"bin": "60c060405234801561000f575f5ffd5b5060405161038b38038061038b83398181016040528101906100319190610105565b81608081815250508073ffffffffffffffffffffffffffffffffffffffff1660a08173ffffffffffffffffffffffffffffffffffffffff16815250505050610143565b5f5ffd5b5f819050919050565b61008a81610078565b8114610094575f5ffd5b50565b5f815190506100a581610081565b92915050565b5f73ffffffffffffffffffffffffffffffffffffffff82169050919050565b5f6100d4826100ab565b9050919050565b6100e4816100ca565b81146100ee575f5ffd5b50565b5f815190506100ff816100db565b92915050565b5f5f6040838503121561011b5761011a610074565b5b5f61012885828601610097565b9250506020610139858286016100f1565b9150509250929050565b60805160a05161021b6101705f395f818160f0015261013a01525f818160c90152610116015261021b5ff3fe608060405234801561000f575f5ffd5b506004361061004a575f3560e01c8063304e1cb21461004e578063477198e21461006c57806382f155161461008a5780638d726ac7146100a8575b5f5ffd5b6100566100c6565b6040516100639190610174565b60405180910390f35b6100746100ed565b60405161008191906101cc565b60405180910390f35b610092610114565b60405161009f9190610174565b60405180910390f35b6100b0610138565b6040516100bd91906101cc565b60405180910390f35b5f7f0000000000000000000000000000000000000000000000000000000000000000905090565b5f7f0000000000000000000000000000000000000000000000000000000000000000905090565b7f000000000000000000000000000000000000000000000000000000000000000081565b7f000000000000000000000000000000000000000000000000000000000000000081565b5f819050919050565b61016e8161015c565b82525050565b5f6020820190506101875f830184610165565b92915050565b5f73ffffffffffffffffffffffffffffffffffffffff82169050919050565b5f6101b68261018d565b9050919050565b6101c6816101ac565b82525050565b5f6020820190506101df5f8301846101bd565b9291505056fea264697066735822122040ab77b65e5147c48aee1cbca580ba83cdadcec862bf5c18d744a06874682ae064736f6c634300081e0033",
"bin-runtime": "608060405234801561000f575f5ffd5b506004361061004a575f3560e01c8063304e1cb21461004e578063477198e21461006c57806382f155161461008a5780638d726ac7146100a8575b5f5ffd5b6100566100c6565b6040516100639190610174565b60405180910390f35b6100746100ed565b60405161008191906101cc565b60405180910390f35b610092610114565b60405161009f9190610174565b60405180910390f35b6100b0610138565b6040516100bd91906101cc565b60405180910390f35b5f7f0000000000000000000000000000000000000000000000000000000000000000905090565b5f7f0000000000000000000000000000000000000000000000000000000000000000905090565b7f000000000000000000000000000000000000000000000000000000000000000081565b7f000000000000000000000000000000000000000000000000000000000000000081565b5f819050919050565b61016e8161015c565b82525050565b5f6020820190506101875f830184610165565b92915050565b5f73ffffffffffffffffffffffffffffffffffffffff82169050919050565b5f6101b68261018d565b9050919050565b6101c6816101ac565b82525050565b5f6020820190506101df5f8301846101bd565b9291505056fea264697066735822122040ab77b65e5147c48aee1cbca580ba83cdadcec862bf5c18d744a06874682ae064736f6c634300081e0033",
"bin-pvm": "50564d00008809000000000000010700c25000c2000003044000000004809a08000000000e0000001c0000002a000000390000004b000000560000006800000063616c6c5f646174615f636f707963616c6c5f646174615f6c6f616463616c6c5f646174615f73697a65636f6e73756d655f616c6c5f6761736765745f696d6d757461626c655f646174617365616c5f72657475726e7365745f696d6d757461626c655f6461746176616c75655f7472616e73666572726564051102848b0463616c6c8497066465706c6f790688b8210287811a001f00b000bb00e4000d017b01a001ac01f1012202470258028502bf02cb02dd024903630368039703a103b70323043d0441046b0475048b049704a4042e0576069511f07b10087b158475010a02013d07040002510507501002095010043e02951180fe7b1078017b1570017b166801951580018411e049215801492150014921480149214001831740010a0701821750018218580182194801821a4001d49808d4a707d487075107143308100002838833070133090a0528ca00390604000297672098772095771f8477e09578c000d87807492138017b1820017b17280194777b17300133074095182001501006a7053307c00064685010088e068377646833090a01551640143308100002838833070133090a05286c951700013308c00050100a3904821718017b1738821710017b1730821708017b1728821700017b17209517e0003308e00050100c10048219f0008216f80014070000000001000000d39707d46707989820888801946893785208123308100002838833070133090a05018217e8007b17108217e0007b17188217207b17c0008217287b17c8008217307b17d000330780009518c0007b19308219387b19d80050100edc047b16b8008217307b17b0008217107b17a8003307a000c871088219187b19a000501010b704951780003308405010127103821798008218900082198800821a8000d49707d48a09d47909989920d48707977720d49707520781003307100002bea707330833097b1a380a951760330880005010142c038217783306100004821870821968821a603e072800043e082000043e091800043e0a1000049517403308a000501016fb02821758821850821948821a407b67387b68307b69287b6a2028c902821738330850101802058378330733090a0528f2fe33001a0a03019511a07b10587b15507b16489515608411e06416491638491630491620800033074095682049162850101cd2033907040002531704419517e08477e07b67186471837733080a010182671881771c51471655f182155147c76a728d195247b21c4e300b50101e225247e29871470c501020f1003308100002838833070133093300220a0501951160ff7b1098007b1590007b1688009515a0008411e04911784911704911684911608317600a0701821770821878821968821a60d49808d4a707d4870751070a33081000022811390704000256170314330810000201838833070133090a0501951740330840501024d4018217587b17188217507b17108217487b170882164050102666015012284101520749646b3a091000043a081800043a0a2000043a072800047b17387b1a307b18289518207b192064b750102ac002330820646750102ca803837833092033070a0533002e0a03280833002e0a0301951160ff7b1098007b1590007b1688009515a0008411e04911784911704911684911608317600a0701821770821878821968821a60d49808d4a707d4870751070a33081000022811390704000256170314330810000201838833070133090a0501951740330840501030fa008217587b17188217507b17108217487b17088216405010328c0050123467520744646a3a093000043a0838000438074000044911387b17307b18289518207b192064a7501036ec013308206467501038d402837833092033070a0533003a0a03280833003a0a03019511f87b10330750103c6efb9511f87b1033070150103e61fb821718821808d48707821910d49608d47808988820d49707977720d4870732029511e87b10107b15087b163306000002390500000251051c3307100004837783680a040139070000022003000002ac571082101082150882169511183200003908000002510835fd330710000483770a062828fd9511f07b10087b156489647533082064975010401b027c78017c797c7a027c7b03978808d4980897aa1097bb18d4ba0ad4a8087c79057c7a047c7b067c7c07979908d4a90997bb1097cc18d4cb0bd4b909979920d489027c79097c7a087c7b0a7c7c0b979908d4a90997bb1097cc18d4cb0bd4b9097c7a0d7c7b0c7c7c0e7c780f97aa08d4ba0a97cc10978818d4c808d4a808978820d498037c78117c7a107c7b127c7c13978808d4a80897bb1097cc18d4cb0bd4b8087c7a157c7b147c7c167c791797aa08d4ba0a97cc10979918d4c909d4a909979920d4890a7c78197c79187c7b1a7c7c1b978808d4980897bb1097cc18d4cb0bd4b8087c791d7c7b1c7c7c1e7c771f979908d4b90997cc10977718d4c707d49707977720d487076f776fa86f396f2a7b5a187b59107b58087b57821008821595111032009511d87b10207b15187b161082897b19088289087b19828510828618330820501042d3006f686f59821a6faa821b086fbb787b18787a10787908787898bc38787c1f98bc30787c1e98bc28787c1d98bc20787c1c98bc18787c1b98bc10787c1a98bb08787b1998ab38787b1798ab30787b1698ab28787b1598ab20787b1498ab18787b1398ab10787b1298aa08787a11989a38787a0f989a30787a0e989a28787a0d989a20787a0c989a18787a0b989a10787a0a9899087879099889387879079889307879069889287879059889207879049889187879039889107879029888087878018210208215188216109511283200838951092e8b7a11520a34330b010002aeb92cc8780883881f8488e0563800000220390a080002ae8a093d080800020183773308100002c887073200004969488488884844443422224942a98424894444910851a88e50aa88108944221121228024a9845223914422119144884422920891101191244922a432111292841042222449922485aaa44992948424214492aa492010884008a53422229224699224492844586a1292244908298410922485144aa5d288884892a4499224a11061a94948922421520821495248a1542a4d85949024499294a41012aa2124290d112a4995424a92244992244992244992244992244992244992244992244992244992244992a42a49292549494942554a529224499224499224499224499224499224499224499254929024414858480100"
}
19 changes: 19 additions & 0 deletions crates/anvil-polkadot/test-data/ImmutableStorage.sol
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
pragma solidity ^0.8.0;

contract ImmutableStorage {
uint256 public immutable immutableValue;
address public immutable immutableAddress;

constructor(uint256 _value, address _addr) {
immutableValue = _value;
immutableAddress = _addr;
}

function getImmutableValue() public view returns (uint256) {
return immutableValue;
}

function getImmutableAddress() public view returns (address) {
return immutableAddress;
}
}
6 changes: 6 additions & 0 deletions crates/anvil-polkadot/tests/it/abi.rs
Original file line number Diff line number Diff line change
Expand Up @@ -23,3 +23,9 @@ sol!(
SimpleStorageCaller,
"test-data/SimpleStorageCaller.json"
);

sol!(
#[derive(Debug)]
ImmutableStorage,
"test-data/ImmutableStorage.json"
);
117 changes: 115 additions & 2 deletions crates/anvil-polkadot/tests/it/state_injector.rs
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
use crate::{
abi::SimpleStorage,
utils::{ContractCode, TestNode, get_contract_code, unwrap_response},
abi::{ImmutableStorage, SimpleStorage},
utils::{ContractCode, TestNode, get_contract_code, get_contract_pvm_code, unwrap_response},
};
use alloy_eips::BlockId;
use alloy_primitives::{Address, B256, Bytes, U256};
Expand Down Expand Up @@ -776,3 +776,116 @@ async fn test_set_storage() {
assert_eq!(stored_value, 511);
}
}

#[tokio::test(flavor = "multi_thread")]
async fn test_set_immutable_storage() {
use alloy_sol_types::SolValue;

let anvil_node_config = AnvilNodeConfig::test_config();
let substrate_node_config = SubstrateNodeConfig::new(&anvil_node_config);
let mut node = TestNode::new(anvil_node_config, substrate_node_config).await.unwrap();

let alith = Account::from(subxt_signer::eth::dev::alith());
let alith_addr = Address::from(ReviveAddress::new(alith.address()));
let initial_value = U256::from(12345);
let initial_address = Address::from(ReviveAddress::new(
Account::from(subxt_signer::eth::dev::baltathar()).address(),
));

// Deploy PVM contract with ABI-encoded constructor args
let bytecode = get_contract_pvm_code("ImmutableStorage");
let constructor_args = (initial_value, initial_address).abi_encode();
let deployment_bytecode = [bytecode.as_slice(), constructor_args.as_slice()].concat();
let tx_hash = node.deploy_contract(&deployment_bytecode, alith.address()).await;
unwrap_response::<()>(node.eth_rpc(EthRequest::Mine(None, None)).await.unwrap()).unwrap();

let contract_address = Address::from(ReviveAddress::new(
node.get_transaction_receipt(tx_hash).await.contract_address.unwrap(),
));

// Verify initial immutable value
let call_tx = TransactionRequest::default()
.from(alith_addr)
.to(contract_address)
.input(TransactionInput::both(
ImmutableStorage::getImmutableValueCall {}.abi_encode().into(),
));
let result = unwrap_response::<Bytes>(
node.eth_rpc(EthRequest::EthCall(call_tx.into(), None, None, None)).await.unwrap(),
)
.unwrap();
assert_eq!(
ImmutableStorage::getImmutableValueCall::abi_decode_returns(&result.0).unwrap(),
initial_value
);

// Verify initial immutable address
let call_tx = TransactionRequest::default()
.from(alith_addr)
.to(contract_address)
.input(TransactionInput::both(
ImmutableStorage::getImmutableAddressCall {}.abi_encode().into(),
));
let result = unwrap_response::<Bytes>(
node.eth_rpc(EthRequest::EthCall(call_tx.into(), None, None, None)).await.unwrap(),
)
.unwrap();
assert_eq!(
ImmutableStorage::getImmutableAddressCall::abi_decode_returns(&result.0).unwrap(),
initial_address
);

// Set new immutable values via anvil_setImmutableStorageAt
// Each immutable is passed as a separate ABI-encoded bytes value
let new_value = U256::from(99999);
let new_address = Address::from(ReviveAddress::new(
Account::from(subxt_signer::eth::dev::charleth()).address(),
));

let immutables = vec![
Bytes::from(new_value.abi_encode()),
Bytes::from(new_address.abi_encode()),
];

unwrap_response::<()>(
node.eth_rpc(EthRequest::SetImmutableStorageAt(
contract_address,
immutables,
))
.await
.unwrap(),
)
.unwrap();
Comment thread
pgherveou marked this conversation as resolved.

// Verify new immutable value
let call_tx = TransactionRequest::default()
.from(alith_addr)
.to(contract_address)
.input(TransactionInput::both(
ImmutableStorage::getImmutableValueCall {}.abi_encode().into(),
));
let result = unwrap_response::<Bytes>(
node.eth_rpc(EthRequest::EthCall(call_tx.into(), None, None, None)).await.unwrap(),
)
.unwrap();
assert_eq!(
ImmutableStorage::getImmutableValueCall::abi_decode_returns(&result.0).unwrap(),
new_value
);

// Verify new immutable address
let call_tx = TransactionRequest::default()
.from(alith_addr)
.to(contract_address)
.input(TransactionInput::both(
ImmutableStorage::getImmutableAddressCall {}.abi_encode().into(),
));
let result = unwrap_response::<Bytes>(
node.eth_rpc(EthRequest::EthCall(call_tx.into(), None, None, None)).await.unwrap(),
)
.unwrap();
assert_eq!(
ImmutableStorage::getImmutableAddressCall::abi_decode_returns(&result.0).unwrap(),
new_address
);
}
Loading
Loading