Skip to content

Commit 4ad768f

Browse files
pgherveouclaude
andauthored
Add immutable support (#498)
* add immutable * Remove unnecessary genesis.rs changes Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com> * add test * refactor: improve anvil_setImmutableStorageAt API to accept Vec<Bytes> - Changed RPC interface to accept individual immutable values instead of concatenated bytes - Moved endianness conversion (big-endian to little-endian) from test to RPC handler - Updated test to pass immutable values as separate ABI-encoded Bytes elements - Simplified byte conversion logic: removed unnecessary intermediate vector, using direct indexing - Added comprehensive documentation explaining data format and conversion process - All immutable values now handled consistently with better API clarity This makes the RPC easier to invoke and aligns with how Sourcify and solc provide immutable data. Co-Authored-By: Claude Haiku 4.5 <noreply@anthropic.com> --------- Co-authored-by: Claude Opus 4.5 <noreply@anthropic.com>
1 parent 939b31c commit 4ad768f

File tree

10 files changed

+323
-8
lines changed

10 files changed

+323
-8
lines changed

crates/anvil-polkadot/src/api_server/server.rs

Lines changed: 46 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -360,6 +360,9 @@ impl ApiServer {
360360
EthRequest::SetStorageAt(address, key, value) => {
361361
self.set_storage_at(address, key, value).to_rpc_result()
362362
}
363+
EthRequest::SetImmutableStorageAt(address, data) => {
364+
self.set_immutable_storage_at(address, data).to_rpc_result()
365+
}
363366
EthRequest::SetChainId(chain_id) => self.set_chain_id(chain_id).to_rpc_result(),
364367
// --- Revert ---
365368
EthRequest::EvmSnapshot(()) => self.snapshot().await.to_rpc_result(),
@@ -1327,6 +1330,49 @@ impl ApiServer {
13271330
Ok(())
13281331
}
13291332

1333+
fn set_immutable_storage_at(
1334+
&self,
1335+
address: Address,
1336+
immutables: Vec<alloy_primitives::Bytes>,
1337+
) -> Result<()> {
1338+
node_info!("anvil_setImmutableStorageAt");
1339+
1340+
let latest_block = self.latest_block();
1341+
1342+
// Convert ABI-encoded immutable values (big-endian) to PVM format (little-endian).
1343+
//
1344+
// ## Data Format
1345+
//
1346+
// Each element in `immutables` is a single ABI-encoded immutable value:
1347+
// - Value in big-endian format (standard for Solidity ABI encoding)
1348+
// - Padded to 32 bytes
1349+
//
1350+
// Example: For a contract with immutables `uint256 value` and `address addr`:
1351+
// immutables[0] = <32-byte big-endian uint256>
1352+
// immutables[1] = <12 zeros + 20-byte address>
1353+
//
1354+
// ## Conversion
1355+
//
1356+
// This method converts each immutable value (32-byte chunk) from big-endian to
1357+
// little-endian, since PVM (Polkadot Virtual Machine) expects immutable data in
1358+
// little-endian format. The conversion is done by reversing each 32-byte word.
1359+
// Then all converted values are concatenated in order.
1360+
let pvm_data: Vec<u8> = immutables
1361+
.into_iter()
1362+
.flat_map(|immutable| {
1363+
let mut word = [0u8; 32];
1364+
let len = immutable.len().min(32);
1365+
word[..len].copy_from_slice(&immutable[..len]);
1366+
word.reverse();
1367+
word
1368+
})
1369+
.collect();
1370+
1371+
self.backend.inject_immutable_data(latest_block, address, pvm_data);
1372+
1373+
Ok(())
1374+
}
1375+
13301376
// ----- Wallet RPCs
13311377
async fn sign(&self, address: Address, content: impl AsRef<[u8]>) -> Result<String> {
13321378
Ok(alloy_primitives::hex::encode_prefixed(self.wallet.sign(address, content.as_ref())?))

crates/anvil-polkadot/src/substrate_node/service/backend.rs

Lines changed: 20 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -207,6 +207,11 @@ impl BackendWithOverlay {
207207
overrides.set_code_info(at, code_hash, code_info);
208208
}
209209

210+
pub fn inject_immutable_data(&self, at: Hash, address: Address, data: Vec<u8>) {
211+
let mut overrides = self.overrides.lock();
212+
overrides.set_immutable_data(at, address, data);
213+
}
214+
210215
pub fn inject_child_storage(
211216
&self,
212217
at: Hash,
@@ -353,6 +358,21 @@ impl StorageOverrides {
353358
self.add(latest_block, changeset);
354359
}
355360

361+
fn set_immutable_data(&mut self, latest_block: Hash, address: Address, data: Vec<u8>) {
362+
let mut changeset = BlockOverrides::default();
363+
364+
// SCALE-encode as BoundedVec<u8>: compact_length prefix followed by raw bytes
365+
let mut encoded = codec::Compact(data.len() as u32).encode();
366+
encoded.extend_from_slice(&data);
367+
368+
changeset.top.insert(
369+
well_known_keys::immutable_data_of(H160::from_slice(address.as_slice())),
370+
Some(encoded),
371+
);
372+
373+
self.add(latest_block, changeset);
374+
}
375+
356376
fn set_child_storage(
357377
&mut self,
358378
latest_block: Hash,

crates/anvil-polkadot/src/substrate_node/service/storage.rs

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -90,4 +90,13 @@ pub mod well_known_keys {
9090

9191
key
9292
}
93+
94+
pub fn immutable_data_of(address: H160) -> Vec<u8> {
95+
let mut key = Vec::new();
96+
key.extend_from_slice(&twox_128("Revive".as_bytes()));
97+
key.extend_from_slice(&twox_128("ImmutableDataOf".as_bytes()));
98+
key.extend_from_slice(&address.encode());
99+
100+
key
101+
}
93102
}
Lines changed: 75 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,75 @@
1+
{
2+
"abi": [
3+
{
4+
"inputs": [
5+
{
6+
"internalType": "uint256",
7+
"name": "_value",
8+
"type": "uint256"
9+
},
10+
{
11+
"internalType": "address",
12+
"name": "_addr",
13+
"type": "address"
14+
}
15+
],
16+
"stateMutability": "nonpayable",
17+
"type": "constructor"
18+
},
19+
{
20+
"inputs": [],
21+
"name": "getImmutableAddress",
22+
"outputs": [
23+
{
24+
"internalType": "address",
25+
"name": "",
26+
"type": "address"
27+
}
28+
],
29+
"stateMutability": "view",
30+
"type": "function"
31+
},
32+
{
33+
"inputs": [],
34+
"name": "getImmutableValue",
35+
"outputs": [
36+
{
37+
"internalType": "uint256",
38+
"name": "",
39+
"type": "uint256"
40+
}
41+
],
42+
"stateMutability": "view",
43+
"type": "function"
44+
},
45+
{
46+
"inputs": [],
47+
"name": "immutableAddress",
48+
"outputs": [
49+
{
50+
"internalType": "address",
51+
"name": "",
52+
"type": "address"
53+
}
54+
],
55+
"stateMutability": "view",
56+
"type": "function"
57+
},
58+
{
59+
"inputs": [],
60+
"name": "immutableValue",
61+
"outputs": [
62+
{
63+
"internalType": "uint256",
64+
"name": "",
65+
"type": "uint256"
66+
}
67+
],
68+
"stateMutability": "view",
69+
"type": "function"
70+
}
71+
],
72+
"bin": "60c060405234801561000f575f5ffd5b5060405161038b38038061038b83398181016040528101906100319190610105565b81608081815250508073ffffffffffffffffffffffffffffffffffffffff1660a08173ffffffffffffffffffffffffffffffffffffffff16815250505050610143565b5f5ffd5b5f819050919050565b61008a81610078565b8114610094575f5ffd5b50565b5f815190506100a581610081565b92915050565b5f73ffffffffffffffffffffffffffffffffffffffff82169050919050565b5f6100d4826100ab565b9050919050565b6100e4816100ca565b81146100ee575f5ffd5b50565b5f815190506100ff816100db565b92915050565b5f5f6040838503121561011b5761011a610074565b5b5f61012885828601610097565b9250506020610139858286016100f1565b9150509250929050565b60805160a05161021b6101705f395f818160f0015261013a01525f818160c90152610116015261021b5ff3fe608060405234801561000f575f5ffd5b506004361061004a575f3560e01c8063304e1cb21461004e578063477198e21461006c57806382f155161461008a5780638d726ac7146100a8575b5f5ffd5b6100566100c6565b6040516100639190610174565b60405180910390f35b6100746100ed565b60405161008191906101cc565b60405180910390f35b610092610114565b60405161009f9190610174565b60405180910390f35b6100b0610138565b6040516100bd91906101cc565b60405180910390f35b5f7f0000000000000000000000000000000000000000000000000000000000000000905090565b5f7f0000000000000000000000000000000000000000000000000000000000000000905090565b7f000000000000000000000000000000000000000000000000000000000000000081565b7f000000000000000000000000000000000000000000000000000000000000000081565b5f819050919050565b61016e8161015c565b82525050565b5f6020820190506101875f830184610165565b92915050565b5f73ffffffffffffffffffffffffffffffffffffffff82169050919050565b5f6101b68261018d565b9050919050565b6101c6816101ac565b82525050565b5f6020820190506101df5f8301846101bd565b9291505056fea264697066735822122040ab77b65e5147c48aee1cbca580ba83cdadcec862bf5c18d744a06874682ae064736f6c634300081e0033",
73+
"bin-runtime": "608060405234801561000f575f5ffd5b506004361061004a575f3560e01c8063304e1cb21461004e578063477198e21461006c57806382f155161461008a5780638d726ac7146100a8575b5f5ffd5b6100566100c6565b6040516100639190610174565b60405180910390f35b6100746100ed565b60405161008191906101cc565b60405180910390f35b610092610114565b60405161009f9190610174565b60405180910390f35b6100b0610138565b6040516100bd91906101cc565b60405180910390f35b5f7f0000000000000000000000000000000000000000000000000000000000000000905090565b5f7f0000000000000000000000000000000000000000000000000000000000000000905090565b7f000000000000000000000000000000000000000000000000000000000000000081565b7f000000000000000000000000000000000000000000000000000000000000000081565b5f819050919050565b61016e8161015c565b82525050565b5f6020820190506101875f830184610165565b92915050565b5f73ffffffffffffffffffffffffffffffffffffffff82169050919050565b5f6101b68261018d565b9050919050565b6101c6816101ac565b82525050565b5f6020820190506101df5f8301846101bd565b9291505056fea264697066735822122040ab77b65e5147c48aee1cbca580ba83cdadcec862bf5c18d744a06874682ae064736f6c634300081e0033",
74+
"bin-pvm": "50564d00008809000000000000010700c25000c2000003044000000004809a08000000000e0000001c0000002a000000390000004b000000560000006800000063616c6c5f646174615f636f707963616c6c5f646174615f6c6f616463616c6c5f646174615f73697a65636f6e73756d655f616c6c5f6761736765745f696d6d757461626c655f646174617365616c5f72657475726e7365745f696d6d757461626c655f6461746176616c75655f7472616e73666572726564051102848b0463616c6c8497066465706c6f790688b8210287811a001f00b000bb00e4000d017b01a001ac01f1012202470258028502bf02cb02dd024903630368039703a103b70323043d0441046b0475048b049704a4042e0576069511f07b10087b158475010a02013d07040002510507501002095010043e02951180fe7b1078017b1570017b166801951580018411e049215801492150014921480149214001831740010a0701821750018218580182194801821a4001d49808d4a707d487075107143308100002838833070133090a0528ca00390604000297672098772095771f8477e09578c000d87807492138017b1820017b17280194777b17300133074095182001501006a7053307c00064685010088e068377646833090a01551640143308100002838833070133090a05286c951700013308c00050100a3904821718017b1738821710017b1730821708017b1728821700017b17209517e0003308e00050100c10048219f0008216f80014070000000001000000d39707d46707989820888801946893785208123308100002838833070133090a05018217e8007b17108217e0007b17188217207b17c0008217287b17c8008217307b17d000330780009518c0007b19308219387b19d80050100edc047b16b8008217307b17b0008217107b17a8003307a000c871088219187b19a000501010b704951780003308405010127103821798008218900082198800821a8000d49707d48a09d47909989920d48707977720d49707520781003307100002bea707330833097b1a380a951760330880005010142c038217783306100004821870821968821a603e072800043e082000043e091800043e0a1000049517403308a000501016fb02821758821850821948821a407b67387b68307b69287b6a2028c902821738330850101802058378330733090a0528f2fe33001a0a03019511a07b10587b15507b16489515608411e06416491638491630491620800033074095682049162850101cd2033907040002531704419517e08477e07b67186471837733080a010182671881771c51471655f182155147c76a728d195247b21c4e300b50101e225247e29871470c501020f1003308100002838833070133093300220a0501951160ff7b1098007b1590007b1688009515a0008411e04911784911704911684911608317600a0701821770821878821968821a60d49808d4a707d4870751070a33081000022811390704000256170314330810000201838833070133090a0501951740330840501024d4018217587b17188217507b17108217487b170882164050102666015012284101520749646b3a091000043a081800043a0a2000043a072800047b17387b1a307b18289518207b192064b750102ac002330820646750102ca803837833092033070a0533002e0a03280833002e0a0301951160ff7b1098007b1590007b1688009515a0008411e04911784911704911684911608317600a0701821770821878821968821a60d49808d4a707d4870751070a33081000022811390704000256170314330810000201838833070133090a0501951740330840501030fa008217587b17188217507b17108217487b17088216405010328c0050123467520744646a3a093000043a0838000438074000044911387b17307b18289518207b192064a7501036ec013308206467501038d402837833092033070a0533003a0a03280833003a0a03019511f87b10330750103c6efb9511f87b1033070150103e61fb821718821808d48707821910d49608d47808988820d49707977720d4870732029511e87b10107b15087b163306000002390500000251051c3307100004837783680a040139070000022003000002ac571082101082150882169511183200003908000002510835fd330710000483770a062828fd9511f07b10087b156489647533082064975010401b027c78017c797c7a027c7b03978808d4980897aa1097bb18d4ba0ad4a8087c79057c7a047c7b067c7c07979908d4a90997bb1097cc18d4cb0bd4b909979920d489027c79097c7a087c7b0a7c7c0b979908d4a90997bb1097cc18d4cb0bd4b9097c7a0d7c7b0c7c7c0e7c780f97aa08d4ba0a97cc10978818d4c808d4a808978820d498037c78117c7a107c7b127c7c13978808d4a80897bb1097cc18d4cb0bd4b8087c7a157c7b147c7c167c791797aa08d4ba0a97cc10979918d4c909d4a909979920d4890a7c78197c79187c7b1a7c7c1b978808d4980897bb1097cc18d4cb0bd4b8087c791d7c7b1c7c7c1e7c771f979908d4b90997cc10977718d4c707d49707977720d487076f776fa86f396f2a7b5a187b59107b58087b57821008821595111032009511d87b10207b15187b161082897b19088289087b19828510828618330820501042d3006f686f59821a6faa821b086fbb787b18787a10787908787898bc38787c1f98bc30787c1e98bc28787c1d98bc20787c1c98bc18787c1b98bc10787c1a98bb08787b1998ab38787b1798ab30787b1698ab28787b1598ab20787b1498ab18787b1398ab10787b1298aa08787a11989a38787a0f989a30787a0e989a28787a0d989a20787a0c989a18787a0b989a10787a0a9899087879099889387879079889307879069889287879059889207879049889187879039889107879029888087878018210208215188216109511283200838951092e8b7a11520a34330b010002aeb92cc8780883881f8488e0563800000220390a080002ae8a093d080800020183773308100002c887073200004969488488884844443422224942a98424894444910851a88e50aa88108944221121228024a9845223914422119144884422920891101191244922a432111292841042222449922485aaa44992948424214492aa492010884008a53422229224699224492844586a1292244908298410922485144aa5d288884892a4499224a11061a94948922421520821495248a1542a4d85949024499294a41012aa2124290d112a4995424a92244992244992244992244992244992244992244992244992244992244992a42a49292549494942554a529224499224499224499224499224499224499224499254929024414858480100"
75+
}
Lines changed: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,19 @@
1+
pragma solidity ^0.8.0;
2+
3+
contract ImmutableStorage {
4+
uint256 public immutable immutableValue;
5+
address public immutable immutableAddress;
6+
7+
constructor(uint256 _value, address _addr) {
8+
immutableValue = _value;
9+
immutableAddress = _addr;
10+
}
11+
12+
function getImmutableValue() public view returns (uint256) {
13+
return immutableValue;
14+
}
15+
16+
function getImmutableAddress() public view returns (address) {
17+
return immutableAddress;
18+
}
19+
}

crates/anvil-polkadot/tests/it/abi.rs

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -23,3 +23,9 @@ sol!(
2323
SimpleStorageCaller,
2424
"test-data/SimpleStorageCaller.json"
2525
);
26+
27+
sol!(
28+
#[derive(Debug)]
29+
ImmutableStorage,
30+
"test-data/ImmutableStorage.json"
31+
);

crates/anvil-polkadot/tests/it/state_injector.rs

Lines changed: 115 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
use crate::{
2-
abi::SimpleStorage,
3-
utils::{ContractCode, TestNode, get_contract_code, unwrap_response},
2+
abi::{ImmutableStorage, SimpleStorage},
3+
utils::{ContractCode, TestNode, get_contract_code, get_contract_pvm_code, unwrap_response},
44
};
55
use alloy_eips::BlockId;
66
use alloy_primitives::{Address, B256, Bytes, U256};
@@ -776,3 +776,116 @@ async fn test_set_storage() {
776776
assert_eq!(stored_value, 511);
777777
}
778778
}
779+
780+
#[tokio::test(flavor = "multi_thread")]
781+
async fn test_set_immutable_storage() {
782+
use alloy_sol_types::SolValue;
783+
784+
let anvil_node_config = AnvilNodeConfig::test_config();
785+
let substrate_node_config = SubstrateNodeConfig::new(&anvil_node_config);
786+
let mut node = TestNode::new(anvil_node_config, substrate_node_config).await.unwrap();
787+
788+
let alith = Account::from(subxt_signer::eth::dev::alith());
789+
let alith_addr = Address::from(ReviveAddress::new(alith.address()));
790+
let initial_value = U256::from(12345);
791+
let initial_address = Address::from(ReviveAddress::new(
792+
Account::from(subxt_signer::eth::dev::baltathar()).address(),
793+
));
794+
795+
// Deploy PVM contract with ABI-encoded constructor args
796+
let bytecode = get_contract_pvm_code("ImmutableStorage");
797+
let constructor_args = (initial_value, initial_address).abi_encode();
798+
let deployment_bytecode = [bytecode.as_slice(), constructor_args.as_slice()].concat();
799+
let tx_hash = node.deploy_contract(&deployment_bytecode, alith.address()).await;
800+
unwrap_response::<()>(node.eth_rpc(EthRequest::Mine(None, None)).await.unwrap()).unwrap();
801+
802+
let contract_address = Address::from(ReviveAddress::new(
803+
node.get_transaction_receipt(tx_hash).await.contract_address.unwrap(),
804+
));
805+
806+
// Verify initial immutable value
807+
let call_tx = TransactionRequest::default()
808+
.from(alith_addr)
809+
.to(contract_address)
810+
.input(TransactionInput::both(
811+
ImmutableStorage::getImmutableValueCall {}.abi_encode().into(),
812+
));
813+
let result = unwrap_response::<Bytes>(
814+
node.eth_rpc(EthRequest::EthCall(call_tx.into(), None, None, None)).await.unwrap(),
815+
)
816+
.unwrap();
817+
assert_eq!(
818+
ImmutableStorage::getImmutableValueCall::abi_decode_returns(&result.0).unwrap(),
819+
initial_value
820+
);
821+
822+
// Verify initial immutable address
823+
let call_tx = TransactionRequest::default()
824+
.from(alith_addr)
825+
.to(contract_address)
826+
.input(TransactionInput::both(
827+
ImmutableStorage::getImmutableAddressCall {}.abi_encode().into(),
828+
));
829+
let result = unwrap_response::<Bytes>(
830+
node.eth_rpc(EthRequest::EthCall(call_tx.into(), None, None, None)).await.unwrap(),
831+
)
832+
.unwrap();
833+
assert_eq!(
834+
ImmutableStorage::getImmutableAddressCall::abi_decode_returns(&result.0).unwrap(),
835+
initial_address
836+
);
837+
838+
// Set new immutable values via anvil_setImmutableStorageAt
839+
// Each immutable is passed as a separate ABI-encoded bytes value
840+
let new_value = U256::from(99999);
841+
let new_address = Address::from(ReviveAddress::new(
842+
Account::from(subxt_signer::eth::dev::charleth()).address(),
843+
));
844+
845+
let immutables = vec![
846+
Bytes::from(new_value.abi_encode()),
847+
Bytes::from(new_address.abi_encode()),
848+
];
849+
850+
unwrap_response::<()>(
851+
node.eth_rpc(EthRequest::SetImmutableStorageAt(
852+
contract_address,
853+
immutables,
854+
))
855+
.await
856+
.unwrap(),
857+
)
858+
.unwrap();
859+
860+
// Verify new immutable value
861+
let call_tx = TransactionRequest::default()
862+
.from(alith_addr)
863+
.to(contract_address)
864+
.input(TransactionInput::both(
865+
ImmutableStorage::getImmutableValueCall {}.abi_encode().into(),
866+
));
867+
let result = unwrap_response::<Bytes>(
868+
node.eth_rpc(EthRequest::EthCall(call_tx.into(), None, None, None)).await.unwrap(),
869+
)
870+
.unwrap();
871+
assert_eq!(
872+
ImmutableStorage::getImmutableValueCall::abi_decode_returns(&result.0).unwrap(),
873+
new_value
874+
);
875+
876+
// Verify new immutable address
877+
let call_tx = TransactionRequest::default()
878+
.from(alith_addr)
879+
.to(contract_address)
880+
.input(TransactionInput::both(
881+
ImmutableStorage::getImmutableAddressCall {}.abi_encode().into(),
882+
));
883+
let result = unwrap_response::<Bytes>(
884+
node.eth_rpc(EthRequest::EthCall(call_tx.into(), None, None, None)).await.unwrap(),
885+
)
886+
.unwrap();
887+
assert_eq!(
888+
ImmutableStorage::getImmutableAddressCall::abi_decode_returns(&result.0).unwrap(),
889+
new_address
890+
);
891+
}

0 commit comments

Comments
 (0)