Skip to content

Allow for "Nick's Method" Style Transactions & Contract Deployments in Revive#10476

Merged
athei merged 21 commits intomasterfrom
0xOmarA/allow-no-chain-id-txs
Jan 7, 2026
Merged

Allow for "Nick's Method" Style Transactions & Contract Deployments in Revive#10476
athei merged 21 commits intomasterfrom
0xOmarA/allow-no-chain-id-txs

Conversation

@0xOmarA
Copy link
Copy Markdown
Contributor

@0xOmarA 0xOmarA commented Nov 29, 2025

Description

This pull request allows for “Nick’s Method” style of deploying contracts which was requested in paritytech/contract-issues#225 and was attempted to be solved in paritytech/contract-issues#99.

The “Nick Method” style of contract deployment is a very useful concept to use when we wish to deploy a contract on multiple chains with the same address. It allows us to do that by constructing a deployment transaction that can be executed on any chain, thus allowing us to get the same address for the contract’s deployment on any chain.

Additionally, this method allows for the contracts to be deployed trustlessly, meaning that if any actor (regardless of whether they’re honest or not) follow the same method for deploying the contract then they’d get the same address across all chains. This allows anyone in the Polkadot ecosystem to take existing contracts that use this method of deployment (e.g., Multicall3 and the ERC-1820 registry) and deploy them on Polkadot and there’s already trust that the code is the same.

In order to be able to use the same transaction across multiple chains transaction authors need to:

  • Not include a chain id.
  • Include a high gas limit so that the transaction works on (almost) all chains.

As mentioned in paritytech/contract-issues#225, this pattern is already being used in a number of contracts. Most notably, Multicall3 and the ERC-1820 Registry.

Before this PR this method didn’t work in revive since the chain id was an absolute must and any transaction that didn’t include a chain id would fail with an Invalid Transaction error as seen below:

let chain_id = tx.chain_id.unwrap_or_default();
if chain_id != <T as Config>::ChainId::get().into() {
log::debug!(target: LOG_TARGET, "Invalid chain_id {chain_id:?}");
return Err(InvalidTransaction::Call);
}

The above implementation misses an important detail: legacy transactions are permitted to not have the chain id set while all other non-legacy transactions do not permit that. The models we had for legacy transactions respected that, but we still did the chain id check for all transaction types, which is incorrect. Therefore, this part of the code was changed to the following:

match (tx.chain_id, tx.r#type.as_ref()) {
	(None, Some(super::Byte(TYPE_LEGACY))) => {},
	(Some(chain_id), ..) =>
		if chain_id != <T as Config>::ChainId::get().into() {
			log::debug!(target: LOG_TARGET, "Invalid chain_id {chain_id:?}");
			return Err(InvalidTransaction::Call);
		},
	(None, ..) => {
		log::debug!(target: LOG_TARGET, "Invalid chain_id None");
		return Err(InvalidTransaction::Call);
	},
}

The above code skips the chain id check if the transaction is of the legacy type. Otherwise, the chain id is checked. If no chain id is provided and the transaction is not of the legacy type then we error out.

Integration

Be aware that we now allow for legacy transactions to not have the chain ID set in order to allow for “Nick’s Method” style of contract deployment. Non-legacy transaction continue to require the chain id to be provided.

Review Notes

  • The main change that this PR makes can be found in the substrate/frame/revive/src/evm/call.rs file allowing for legacy contracts to not have the chain id set.
  • Two new tests were added with this PR:
    • dry_run_contract_deployment_with_nick_method_works
    • contract_deployment_with_nick_method_works
  • Both of the above tests test that “Nick’s Method” can be used to deploy the singleton factory contract provided in ERC-2470 in a dry run and in an eth_transact call.
  • Note that the above tests needed to modify the transaction provided in the ERC to update its gas limit (since the provided gas limit was too low for our platform), which breaks the whole idea of Nick’s Method where the same transaction can be submitted to the same chain. I suspect that [Revive] Add configuration to set Ethereum gas scale #10393 should fix this issue with the new gas scaling.

Behavior On Other Chains

Since some of the comments on this PR talked about whether this is a good idea, I wanted to add this section as justification for adding this and also to review what other chains do with regards to unprotected transactions.

I think that the security of this feature can be justified by:

  • The fact that this feature is allowed on most other EVM chains, including Ethereum mainnet itself.
  • That metamask will fill in the chain-id automatically for users (as seen here), thus preventing users from broadcasting a re-playable transaction.
  • That the default behavior of Alloy and other developer tools is to fill in the chain ID when the developer is constructing transactions.
  • That people who want to use this feature need to start their own RPC with the --allow-unprotected-txs flag set

The behavior of Geth when it comes to unprotected transactions is:

  • They're allowed at the protocol level for legacy transactions.
  • They're disallowed at the protocol level for other transaction types.
  • They're not allowed to be submitted through the Geth RPC unless the --rpc.allow_unprotected_txs flag is set when Geth is started.

The above behavior matches what EIP-155 set in place for legacy transactions.

The following is an overview of the various chains and their stance on unprotected transactions:

  • Ethereum/Geth: Allowed at the protocol level, the node must be started with a special flag to permit it to be submitted through the RPC (source)
  • Avalanche: Allowed at the protocol level, the node must be started with a special flag to permit it to be submitted through the RPC (source)
  • Polygon: Allowed at the protocol level, the node must be started with a special flag to permit it to be submitted through the RPC (source)
  • Arbitrum: Allowed at the protocol level, the node must be started with a special flag to permit it to be submitted through the RPC (source)

@0xOmarA 0xOmarA added the T7-smart_contracts This PR/Issue is related to smart contracts. label Nov 29, 2025
@0xOmarA
Copy link
Copy Markdown
Contributor Author

0xOmarA commented Nov 29, 2025

/cmd prdoc --audience runtime_dev --bump patch

@github-actions
Copy link
Copy Markdown
Contributor

github-actions bot commented Nov 29, 2025

Differential Tests Results (PolkaVM)

Specified Tests

  • simple
  • complex
  • translated_semantic_tests

Counts

  • Total Number of Test Cases: 26447
  • Total Number of Successes: 5801
  • Total Number of Failures: 60
  • Total Number of Ignores: 20586

Failures

The test specifiers seen in this section have the format 'path::case_idx::compilation_mode' and they're compatible with the revive differential tests framework and can be specified to it directly in the same way that they're provided through the --test argument of the framework.

The failures are provided in an expandable section to ensure that the PR does not get polluted with information. Please click on the section below for more information

Detailed Differential Tests Failure Information
Test Specifier Failure Reason Note
complex/create/create2_many/test.json::1::Y+ Failed to execute all of the steps on the driver: Failure on step 0: Function call step Failed: Failed to handle function call assertions: Transaction status assertion failed - Expected true but got false. Revert reason: Some("ContractTrapped")
complex/create/create_in_library/test.json::0::Y+ Failed to execute all of the steps on the driver: Failure on step 0: Function call step Failed: Failed to handle function call assertions: Transaction status assertion failed - Expected true but got false. Revert reason: Some("")
complex/create/create_in_library/test.json::0::Y- Failed to execute all of the steps on the driver: Failure on step 0: Function call step Failed: Failed to handle function call assertions: Transaction status assertion failed - Expected true but got false. Revert reason: Some("")
complex/create/create_many/test.json::1::Y+ Failed to execute all of the steps on the driver: Failure on step 0: Function call step Failed: Failed to handle function call assertions: Transaction status assertion failed - Expected true but got false. Revert reason: Some("ContractTrapped")
complex/library_call_tuple/test.json::0::Y+ Failed to execute all of the steps on the driver: Failure on step 0: Function call step Failed: Failed to handle function call assertions: Transaction status assertion failed - Expected true but got false. Revert reason: Some("")
complex/library_call_tuple/test.json::0::Y- Failed to execute all of the steps on the driver: Failure on step 0: Function call step Failed: Failed to handle function call assertions: Transaction status assertion failed - Expected true but got false. Revert reason: Some("")
complex/solidity_by_example/applications/iterable_mapping/test.json::0::Y- >=0.8.1 Failed to execute all of the steps on the driver: Failure on step 0: Function call step Failed: Failed to handle function call assertions: Transaction status assertion failed - Expected true but got false. Revert reason: Some("")
complex/solidity_by_example/applications/iterable_mapping/test.json::0::Y+ >=0.8.1 Failed to execute all of the steps on the driver: Failure on step 0: Function call step Failed: Failed to handle function call assertions: Transaction status assertion failed - Expected true but got false. Revert reason: Some("")
simple/function/many_arguments_2.sol::0::Y- Failed to create the drivers for the various platforms: Failed to create driver for revive-dev-node-polkavm-resolc: Failed to initialize the execution state of the platform: Failed to produce the pre-linking compiled contracts: Compilation callback failed (cache miss path): Encountered an error in the compilation: CompilerError: Stack too deep. Try compiling with `--via-ir` (cli) or the equivalent `viaIR: true` (standard JSON) while enabling the optimizer. Otherwise, try removing local variables. --> /__w/polkadot-sdk/polkadot-sdk/revive-differential-tests/resolc-compiler-tests/fixtures/solidity/simple/function/many_arguments_2.sol:52:40: 52 result = result && main(i, p1, p2, p3, p4, p5, p6, p7, p8, p9, p10, p11, p12, p13, p14, p15, p16, p17, p18, p19, p20) == uint256(i) * i; ^^
simple/function/many_arguments_2.sol::0::Y+ Failed to create the drivers for the various platforms: Failed to create driver for revive-dev-node-polkavm-resolc: Failed to initialize the execution state of the platform: Failed to produce the pre-linking compiled contracts: Compilation callback failed (cache miss path): Encountered an error in the compilation: CompilerError: Stack too deep. Try compiling with `--via-ir` (cli) or the equivalent `viaIR: true` (standard JSON) while enabling the optimizer. Otherwise, try removing local variables. --> /__w/polkadot-sdk/polkadot-sdk/revive-differential-tests/resolc-compiler-tests/fixtures/solidity/simple/function/many_arguments_2.sol:52:40: 52 result = result && main(i, p1, p2, p3, p4, p5, p6, p7, p8, p9, p10, p11, p12, p13, p14, p15, p16, p17, p18, p19, p20) == uint256(i) * i; ^^
simple/function/many_arguments_2_complex.sol::0::Y+ Failed to create the drivers for the various platforms: Failed to create driver for revive-dev-node-polkavm-resolc: Failed to initialize the execution state of the platform: Failed to produce the pre-linking compiled contracts: Compilation callback failed (cache miss path): Encountered an error in the compilation: CompilerError: Stack too deep. Try compiling with `--via-ir` (cli) or the equivalent `viaIR: true` (standard JSON) while enabling the optimizer. Otherwise, try removing local variables. --> /__w/polkadot-sdk/polkadot-sdk/revive-differential-tests/resolc-compiler-tests/fixtures/solidity/simple/function/many_arguments_2_complex.sol:58:21: 58 p1, ^^
simple/internal_function_pointers/sum_oddness.sol::0::Y+ Failed to execute all of the steps on the driver: Failure on step 0: Function call step Failed: Failed to handle function call assertions: Transaction status assertion failed - Expected true but got false. Revert reason: Some("ContractTrapped") This test case succeeded with other compilation modes: {'Y-'}
simple/internal_function_pointers/sum_oddness.sol::1::Y+ Failed to execute all of the steps on the driver: Failure on step 0: Function call step Failed: Failed to handle function call assertions: Transaction status assertion failed - Expected true but got false. Revert reason: Some("ContractTrapped") This test case succeeded with other compilation modes: {'Y-'}
simple/pointer/large_offset.sol::0::Y+ Failed to execute all of the steps on the driver: Failure on step 0: Function call step Failed: Failed to handle function call assertions: Transaction status assertion failed - Expected true but got false. Revert reason: Some("ContractTrapped")
simple/pointer/large_offset.sol::0::Y- Failed to execute all of the steps on the driver: Failure on step 0: Function call step Failed: Failed to handle function call assertions: Transaction status assertion failed - Expected true but got false. Revert reason: Some("ContractTrapped")
simple/recursion/recursion_keccak.sol::0::Y+ Failed to create the drivers for the various platforms: Failed to create driver for revive-dev-node-polkavm-resolc: Failed to initialize the execution state of the platform: Failed to produce the pre-linking compiled contracts: Compilation callback failed (cache miss path): Failed to parse resolc standard JSON output: failed to parse resolc JSON output: recursion limit exceeded at line 1 column 184228 stderr:
simple/recursion/recursion_keccak.sol::0::Y- Failed to create the drivers for the various platforms: Failed to create driver for revive-dev-node-polkavm-resolc: Failed to initialize the execution state of the platform: Failed to produce the pre-linking compiled contracts: Compilation callback failed (cache miss path): Failed to parse resolc standard JSON output: failed to parse resolc JSON output: recursion limit exceeded at line 1 column 267625 stderr:
simple/recursion_keccak.sol::0::Y- Failed to create the drivers for the various platforms: Failed to create driver for revive-dev-node-polkavm-resolc: Failed to initialize the execution state of the platform: Failed to produce the pre-linking compiled contracts: Compilation callback failed (cache miss path): Failed to parse resolc standard JSON output: failed to parse resolc JSON output: recursion limit exceeded at line 1 column 267555 stderr:
simple/recursion_keccak.sol::0::Y+ Failed to create the drivers for the various platforms: Failed to create driver for revive-dev-node-polkavm-resolc: Failed to initialize the execution state of the platform: Failed to produce the pre-linking compiled contracts: Compilation callback failed (cache miss path): Failed to parse resolc standard JSON output: failed to parse resolc JSON output: recursion limit exceeded at line 1 column 184158 stderr:
simple/try_catch/unbalanced_gas_limit.sol::0::Y+ Failed to execute all of the steps on the driver: Failure on step 0: Function call step Failed: Failed to handle function call assertions: Output assertion failed - Expected Compound([CalldataItem("0"), CalldataItem("1"), CalldataItem("0"), CalldataItem("0")]) but got 0x0000000000000000000000000000000000000000000000000000000000000001000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000001
simple/try_catch/unbalanced_gas_limit.sol::0::Y- Failed to execute all of the steps on the driver: Failure on step 0: Function call step Failed: Failed to handle function call assertions: Output assertion failed - Expected Compound([CalldataItem("0"), CalldataItem("1"), CalldataItem("0"), CalldataItem("0")]) but got 0x0000000000000000000000000000000000000000000000000000000000000001000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000001
simple/yul_instructions/returndatacopy.sol::41::Y- Failed to execute all of the steps on the driver: Failure on step 0: Function call step Failed: Failed to handle function call assertions: Transaction status assertion failed - Expected true but got false. Revert reason: Some("OutOfGas")
simple/yul_instructions/returndatacopy.sol::41::Y+ Failed to execute all of the steps on the driver: Failure on step 0: Function call step Failed: Failed to handle function call assertions: Transaction status assertion failed - Expected true but got false. Revert reason: Some("OutOfGas")
simple/yul_instructions/revert.sol::59::Y- Failed to execute all of the steps on the driver: Failure on step 0: Function call step Failed: Failed to handle function call assertions: Output assertion failed - Expected Compound([CalldataItem("0xdeadbeef")]) but got 0x0000000000000000000000000000000000000000000000000000000000000000
simple/yul_instructions/revert.sol::59::Y+ Failed to execute all of the steps on the driver: Failure on step 0: Function call step Failed: Failed to handle function call assertions: Output assertion failed - Expected Compound([CalldataItem("0xdeadbeef")]) but got 0x0000000000000000000000000000000000000000000000000000000000000000
simple/yul_instructions/revert.sol::60::Y- Failed to execute all of the steps on the driver: Failure on step 0: Function call step Failed: Failed to handle function call assertions: Output assertion failed - Expected Compound([CalldataItem("0xdeadbeef")]) but got 0x0000000000000000000000000000000000000000000000000000000000000000
simple/yul_instructions/revert.sol::60::Y+ Failed to execute all of the steps on the driver: Failure on step 0: Function call step Failed: Failed to handle function call assertions: Output assertion failed - Expected Compound([CalldataItem("0xdeadbeef")]) but got 0x0000000000000000000000000000000000000000000000000000000000000000
simple/yul_instructions/revert.sol::61::Y+ Failed to execute all of the steps on the driver: Failure on step 0: Function call step Failed: Failed to handle function call assertions: Output assertion failed - Expected Compound([CalldataItem("0xdeadbeef")]) but got 0x0000000000000000000000000000000000000000000000000000000000000000
simple/yul_instructions/revert.sol::61::Y- Failed to execute all of the steps on the driver: Failure on step 0: Function call step Failed: Failed to handle function call assertions: Output assertion failed - Expected Compound([CalldataItem("0xdeadbeef")]) but got 0x0000000000000000000000000000000000000000000000000000000000000000
simple/yul_instructions/revert.sol::75::Y- Failed to execute all of the steps on the driver: Failure on step 0: Function call step Failed: Failed to handle function call assertions: Output assertion failed - Expected Compound([CalldataItem("0xdeadbeef")]) but got 0x0000000000000000000000000000000000000000000000000000000000000000
simple/yul_instructions/revert.sol::75::Y+ Failed to execute all of the steps on the driver: Failure on step 0: Function call step Failed: Failed to handle function call assertions: Output assertion failed - Expected Compound([CalldataItem("0xdeadbeef")]) but got 0x0000000000000000000000000000000000000000000000000000000000000000
simple/yul_instructions/revert.sol::197::Y- Failed to execute all of the steps on the driver: Failure on step 0: Function call step Failed: Failed to handle function call assertions: Output assertion failed - Expected Compound([CalldataItem("0xdeadbeef")]) but got 0x0000000000000000000000000000000000000000000000000000000000000000
simple/yul_instructions/revert.sol::197::Y+ Failed to execute all of the steps on the driver: Failure on step 0: Function call step Failed: Failed to handle function call assertions: Output assertion failed - Expected Compound([CalldataItem("0xdeadbeef")]) but got 0x0000000000000000000000000000000000000000000000000000000000000000
simple/yul_instructions/revert.sol::200::Y+ Failed to execute all of the steps on the driver: Failure on step 0: Function call step Failed: Failed to handle function call assertions: Transaction status assertion failed - Expected true but got false. Revert reason: Some("OutOfGas")
simple/yul_instructions/revert.sol::200::Y- Failed to execute all of the steps on the driver: Failure on step 0: Function call step Failed: Failed to handle function call assertions: Transaction status assertion failed - Expected true but got false. Revert reason: Some("OutOfGas")
simple/yul_instructions/revert.sol::202::Y- Failed to execute all of the steps on the driver: Failure on step 0: Function call step Failed: Failed to handle function call assertions: Output assertion failed - Expected Compound([CalldataItem("0xdeadbeef")]) but got 0x0000000000000000000000000000000000000000000000000000000000000000
simple/yul_instructions/revert.sol::202::Y+ Failed to execute all of the steps on the driver: Failure on step 0: Function call step Failed: Failed to handle function call assertions: Output assertion failed - Expected Compound([CalldataItem("0xdeadbeef")]) but got 0x0000000000000000000000000000000000000000000000000000000000000000
simple/yul_instructions/revert.sol::205::Y+ Failed to execute all of the steps on the driver: Failure on step 0: Function call step Failed: Failed to handle function call assertions: Transaction status assertion failed - Expected true but got false. Revert reason: Some("OutOfGas")
simple/yul_instructions/revert.sol::205::Y- Failed to execute all of the steps on the driver: Failure on step 0: Function call step Failed: Failed to handle function call assertions: Transaction status assertion failed - Expected true but got false. Revert reason: Some("OutOfGas")
simple/yul_instructions/revert.sol::207::Y+ Failed to execute all of the steps on the driver: Failure on step 0: Function call step Failed: Failed to handle function call assertions: Output assertion failed - Expected Compound([CalldataItem("0xdeadbeef")]) but got 0x0000000000000000000000000000000000000000000000000000000000000000
simple/yul_instructions/revert.sol::207::Y- Failed to execute all of the steps on the driver: Failure on step 0: Function call step Failed: Failed to handle function call assertions: Output assertion failed - Expected Compound([CalldataItem("0xdeadbeef")]) but got 0x0000000000000000000000000000000000000000000000000000000000000000
simple/yul_instructions/revert.sol::210::Y- Failed to execute all of the steps on the driver: Failure on step 0: Function call step Failed: Failed to handle function call assertions: Transaction status assertion failed - Expected true but got false. Revert reason: Some("OutOfGas")
simple/yul_instructions/revert.sol::210::Y+ Failed to execute all of the steps on the driver: Failure on step 0: Function call step Failed: Failed to handle function call assertions: Transaction status assertion failed - Expected true but got false. Revert reason: Some("OutOfGas")
translated_semantic_tests/array/copying/array_nested_calldata_to_storage/test.json::0::Y- Failed to create the drivers for the various platforms: Failed to create driver for revive-dev-node-polkavm-resolc: Failed to initialize the execution state of the platform: Failed to produce the pre-linking compiled contracts: Compilation callback failed (cache miss path): Encountered an error in the compilation: UnimplementedFeatureError: Copying nested calldata dynamic arrays to storage is not implemented in the old code generator. --> /__w/polkadot-sdk/polkadot-sdk/revive-differential-tests/resolc-compiler-tests/fixtures/solidity/translated_semantic_tests/array/copying/array_nested_calldata_to_storage/array_nested_calldata_to_storage.sol:2:1: 2 contract c { ^ (Relevant source part starts here and spans across multiple lines).
translated_semantic_tests/array/copying/array_of_struct_calldata_to_storage/test.json::0::Y- Failed to create the drivers for the various platforms: Failed to create driver for revive-dev-node-polkavm-resolc: Failed to initialize the execution state of the platform: Failed to produce the pre-linking compiled contracts: Compilation callback failed (cache miss path): Encountered an error in the compilation: UnimplementedFeatureError: Copying of type struct C.S calldata[] calldata to storage is not supported in legacy (only supported by the IR pipeline). Hint: try compiling with `--via-ir` (CLI) or the equivalent `viaIR: true` (Standard JSON) --> /__w/polkadot-sdk/polkadot-sdk/revive-differential-tests/resolc-compiler-tests/fixtures/solidity/translated_semantic_tests/array/copying/array_of_struct_calldata_to_storage/array_of_struct_calldata_to_storage.sol:2:1: 2 contract C { ^ (Relevant source part starts here and spans across multiple lines).
translated_semantic_tests/array/copying/array_of_struct_memory_to_storage/test.json::0::Y- Failed to create the drivers for the various platforms: Failed to create driver for revive-dev-node-polkavm-resolc: Failed to initialize the execution state of the platform: Failed to produce the pre-linking compiled contracts: Compilation callback failed (cache miss path): Encountered an error in the compilation: UnimplementedFeatureError: Copying of type struct C.S memory[] memory to storage is not supported in legacy (only supported by the IR pipeline). Hint: try compiling with `--via-ir` (CLI) or the equivalent `viaIR: true` (Standard JSON) --> /__w/polkadot-sdk/polkadot-sdk/revive-differential-tests/resolc-compiler-tests/fixtures/solidity/translated_semantic_tests/array/copying/array_of_struct_memory_to_storage/array_of_struct_memory_to_storage.sol:1:1: 1 contract C { ^ (Relevant source part starts here and spans across multiple lines).
translated_semantic_tests/array/copying/array_of_structs_containing_arrays_calldata_to_storage/test.json::0::Y- Failed to create the drivers for the various platforms: Failed to create driver for revive-dev-node-polkavm-resolc: Failed to initialize the execution state of the platform: Failed to produce the pre-linking compiled contracts: Compilation callback failed (cache miss path): Encountered an error in the compilation: UnimplementedFeatureError: Copying of type struct C.S calldata[] calldata to storage is not supported in legacy (only supported by the IR pipeline). Hint: try compiling with `--via-ir` (CLI) or the equivalent `viaIR: true` (Standard JSON) --> /__w/polkadot-sdk/polkadot-sdk/revive-differential-tests/resolc-compiler-tests/fixtures/solidity/translated_semantic_tests/array/copying/array_of_structs_containing_arrays_calldata_to_storage/array_of_structs_containing_arrays_calldata_to_storage.sol:2:1: 2 contract C { ^ (Relevant source part starts here and spans across multiple lines).
translated_semantic_tests/array/copying/array_of_structs_containing_arrays_memory_to_storage/test.json::0::Y- Failed to create the drivers for the various platforms: Failed to create driver for revive-dev-node-polkavm-resolc: Failed to initialize the execution state of the platform: Failed to produce the pre-linking compiled contracts: Compilation callback failed (cache miss path): Encountered an error in the compilation: UnimplementedFeatureError: Copying of type struct C.S memory[] memory to storage is not supported in legacy (only supported by the IR pipeline). Hint: try compiling with `--via-ir` (CLI) or the equivalent `viaIR: true` (Standard JSON) --> /__w/polkadot-sdk/polkadot-sdk/revive-differential-tests/resolc-compiler-tests/fixtures/solidity/translated_semantic_tests/array/copying/array_of_structs_containing_arrays_memory_to_storage/array_of_structs_containing_arrays_memory_to_storage.sol:2:1: 2 contract C { ^ (Relevant source part starts here and spans across multiple lines).
translated_semantic_tests/array/copying/calldata_array_to_mapping/test.json::0::Y- Failed to create the drivers for the various platforms: Failed to create driver for revive-dev-node-polkavm-resolc: Failed to initialize the execution state of the platform: Failed to produce the pre-linking compiled contracts: Compilation callback failed (cache miss path): Encountered an error in the compilation: UnimplementedFeatureError: Copying nested calldata dynamic arrays to storage is not implemented in the old code generator. --> /__w/polkadot-sdk/polkadot-sdk/revive-differential-tests/resolc-compiler-tests/fixtures/solidity/translated_semantic_tests/array/copying/calldata_array_to_mapping/calldata_array_to_mapping.sol:2:1: 2 contract C { ^ (Relevant source part starts here and spans across multiple lines).
translated_semantic_tests/array/copying/elements_of_nested_array_of_structs_calldata_to_storage/test.json::0::Y- Failed to create the drivers for the various platforms: Failed to create driver for revive-dev-node-polkavm-resolc: Failed to initialize the execution state of the platform: Failed to produce the pre-linking compiled contracts: Compilation callback failed (cache miss path): Encountered an error in the compilation: UnimplementedFeatureError: Copying nested calldata dynamic arrays to storage is not implemented in the old code generator. --> /__w/polkadot-sdk/polkadot-sdk/revive-differential-tests/resolc-compiler-tests/fixtures/solidity/translated_semantic_tests/array/copying/elements_of_nested_array_of_structs_calldata_to_storage/elements_of_nested_array_of_structs_calldata_to_storage.sol:2:1: 2 contract C { ^ (Relevant source part starts here and spans across multiple lines).
translated_semantic_tests/array/copying/elements_of_nested_array_of_structs_memory_to_storage/test.json::0::Y- Failed to create the drivers for the various platforms: Failed to create driver for revive-dev-node-polkavm-resolc: Failed to initialize the execution state of the platform: Failed to produce the pre-linking compiled contracts: Compilation callback failed (cache miss path): Encountered an error in the compilation: UnimplementedFeatureError: Copying of type struct C.S memory[1] memory to storage is not supported in legacy (only supported by the IR pipeline). Hint: try compiling with `--via-ir` (CLI) or the equivalent `viaIR: true` (Standard JSON) --> /__w/polkadot-sdk/polkadot-sdk/revive-differential-tests/resolc-compiler-tests/fixtures/solidity/translated_semantic_tests/array/copying/elements_of_nested_array_of_structs_memory_to_storage/elements_of_nested_array_of_structs_memory_to_storage.sol:2:1: 2 contract C { ^ (Relevant source part starts here and spans across multiple lines).
translated_semantic_tests/array/copying/nested_array_of_structs_calldata_to_storage/test.json::0::Y- Failed to create the drivers for the various platforms: Failed to create driver for revive-dev-node-polkavm-resolc: Failed to initialize the execution state of the platform: Failed to produce the pre-linking compiled contracts: Compilation callback failed (cache miss path): Encountered an error in the compilation: UnimplementedFeatureError: Copying nested calldata dynamic arrays to storage is not implemented in the old code generator. --> /__w/polkadot-sdk/polkadot-sdk/revive-differential-tests/resolc-compiler-tests/fixtures/solidity/translated_semantic_tests/array/copying/nested_array_of_structs_calldata_to_storage/nested_array_of_structs_calldata_to_storage.sol:2:1: 2 contract C { ^ (Relevant source part starts here and spans across multiple lines).
translated_semantic_tests/array/copying/nested_array_of_structs_memory_to_storage/test.json::0::Y- Failed to create the drivers for the various platforms: Failed to create driver for revive-dev-node-polkavm-resolc: Failed to initialize the execution state of the platform: Failed to produce the pre-linking compiled contracts: Compilation callback failed (cache miss path): Encountered an error in the compilation: UnimplementedFeatureError: Copying of type struct C.S memory[] memory to storage is not supported in legacy (only supported by the IR pipeline). Hint: try compiling with `--via-ir` (CLI) or the equivalent `viaIR: true` (Standard JSON) --> /__w/polkadot-sdk/polkadot-sdk/revive-differential-tests/resolc-compiler-tests/fixtures/solidity/translated_semantic_tests/array/copying/nested_array_of_structs_memory_to_storage/nested_array_of_structs_memory_to_storage.sol:2:1: 2 contract C { ^ (Relevant source part starts here and spans across multiple lines).
translated_semantic_tests/array/copying/nested_array_of_structs_storage_to_storage/test.json::0::Y- Failed to create the drivers for the various platforms: Failed to create driver for revive-dev-node-polkavm-resolc: Failed to initialize the execution state of the platform: Failed to produce the pre-linking compiled contracts: Compilation callback failed (cache miss path): Encountered an error in the compilation: UnimplementedFeatureError: Copying of type struct C.S memory[1] memory to storage is not supported in legacy (only supported by the IR pipeline). Hint: try compiling with `--via-ir` (CLI) or the equivalent `viaIR: true` (Standard JSON) --> /__w/polkadot-sdk/polkadot-sdk/revive-differential-tests/resolc-compiler-tests/fixtures/solidity/translated_semantic_tests/array/copying/nested_array_of_structs_storage_to_storage/nested_array_of_structs_storage_to_storage.sol:2:1: 2 contract C { ^ (Relevant source part starts here and spans across multiple lines).
translated_semantic_tests/array/copying/nested_array_of_structs_with_nested_array_from_storage_to_memory/test.json::0::Y- Failed to create the drivers for the various platforms: Failed to create driver for revive-dev-node-polkavm-resolc: Failed to initialize the execution state of the platform: Failed to produce the pre-linking compiled contracts: Compilation callback failed (cache miss path): Encountered an error in the compilation: UnimplementedFeatureError: Copying of type struct C.S memory[] memory to storage is not supported in legacy (only supported by the IR pipeline). Hint: try compiling with `--via-ir` (CLI) or the equivalent `viaIR: true` (Standard JSON) --> /__w/polkadot-sdk/polkadot-sdk/revive-differential-tests/resolc-compiler-tests/fixtures/solidity/translated_semantic_tests/array/copying/nested_array_of_structs_with_nested_array_from_storage_to_memory/nested_array_of_structs_with_nested_array_from_storage_to_memory.sol:2:1: 2 contract C { ^ (Relevant source part starts here and spans across multiple lines).
translated_semantic_tests/array/copying/nested_dynamic_array_element_calldata_to_storage/test.json::0::Y- Failed to create the drivers for the various platforms: Failed to create driver for revive-dev-node-polkavm-resolc: Failed to initialize the execution state of the platform: Failed to produce the pre-linking compiled contracts: Compilation callback failed (cache miss path): Encountered an error in the compilation: UnimplementedFeatureError: Copying nested calldata dynamic arrays to storage is not implemented in the old code generator. --> /__w/polkadot-sdk/polkadot-sdk/revive-differential-tests/resolc-compiler-tests/fixtures/solidity/translated_semantic_tests/array/copying/nested_dynamic_array_element_calldata_to_storage/nested_dynamic_array_element_calldata_to_storage.sol:2:1: 2 contract C { ^ (Relevant source part starts here and spans across multiple lines).
translated_semantic_tests/array/nested_calldata_storage/test.json::0::Y- Failed to create the drivers for the various platforms: Failed to create driver for revive-dev-node-polkavm-resolc: Failed to initialize the execution state of the platform: Failed to produce the pre-linking compiled contracts: Compilation callback failed (cache miss path): Encountered an error in the compilation: UnimplementedFeatureError: Copying nested calldata dynamic arrays to storage is not implemented in the old code generator. --> /__w/polkadot-sdk/polkadot-sdk/revive-differential-tests/resolc-compiler-tests/fixtures/solidity/translated_semantic_tests/array/nested_calldata_storage/nested_calldata_storage.sol:2:1: 2 contract C { ^ (Relevant source part starts here and spans across multiple lines).
translated_semantic_tests/array/nested_calldata_storage2/test.json::0::Y- Failed to create the drivers for the various platforms: Failed to create driver for revive-dev-node-polkavm-resolc: Failed to initialize the execution state of the platform: Failed to produce the pre-linking compiled contracts: Compilation callback failed (cache miss path): Encountered an error in the compilation: UnimplementedFeatureError: Copying nested calldata dynamic arrays to storage is not implemented in the old code generator. --> /__w/polkadot-sdk/polkadot-sdk/revive-differential-tests/resolc-compiler-tests/fixtures/solidity/translated_semantic_tests/array/nested_calldata_storage2/nested_calldata_storage2.sol:2:1: 2 contract C { ^ (Relevant source part starts here and spans across multiple lines).
translated_semantic_tests/saltedCreate/prediction_example/test.json::0::Y- Failed to execute all of the steps on the driver: Failure on step 0: Function call step Failed: Failed to handle function call assertions: Transaction status assertion failed - Expected true but got false. Revert reason: Some("revert: Address mismatch.")
translated_semantic_tests/shanghai/evmone_support/test.json::0::Y- Failed to create the drivers for the various platforms: Failed to create driver for revive-dev-node-polkavm-resolc: Failed to initialize the execution state of the platform: Failed to produce the pre-linking compiled contracts: Compilation callback failed (cache miss path): Encountered an error in the compilation: Error: LLVM IR generator: 306:17 The `EXTCODECOPY` instruction is not supported --> /__w/polkadot-sdk/polkadot-sdk/revive-differential-tests/resolc-compiler-tests/fixtures/solidity/translated_semantic_tests/shanghai/evmone_support/evmone_support.sol

@github-actions
Copy link
Copy Markdown
Contributor

github-actions bot commented Nov 29, 2025

Differential Tests Results (REVM)

Specified Tests

  • simple
  • complex
  • translated_semantic_tests

Counts

  • Total Number of Test Cases: 26447
  • Total Number of Successes: 19176
  • Total Number of Failures: 0
  • Total Number of Ignores: 7271

Failures

The test specifiers seen in this section have the format 'path::case_idx::compilation_mode' and they're compatible with the revive differential tests framework and can be specified to it directly in the same way that they're provided through the --test argument of the framework.

The failures are provided in an expandable section to ensure that the PR does not get polluted with information. Please click on the section below for more information

Detailed Differential Tests Failure Information
Test Specifier Failure Reason Note

@0xOmarA 0xOmarA changed the title Allow for "Nick's Method" style deployments Allow for "Nick's Method" style deployments in revive Nov 29, 2025
@0xOmarA 0xOmarA changed the title Allow for "Nick's Method" style deployments in revive Allow for "Nick's Method" Style Transactions & Contract Deployments in Revive Dec 1, 2025
Copy link
Copy Markdown
Contributor

@pgherveou pgherveou left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I am not sure we should allow that though I think the competitor disallow it now
can you double check it?

for example this is what I found in Geth
https://github.com/ethereum/go-ethereum/blob/6f2cbb7a27ba7e62b0bdb2090755ef0d271714be/internal/ethapi/api.go?plain=1#L1557-L1560

removing chain-id protection (for legacy tx) seems like a step backward 🤔

@pgherveou
Copy link
Copy Markdown
Contributor

I am not sure we should allow that though I think the competitor disallow it now can you double check it?

for example this is what I found in Geth https://github.com/ethereum/go-ethereum/blob/6f2cbb7a27ba7e62b0bdb2090755ef0d271714be/internal/ethapi/api.go?plain=1#L1557-L1560

removing chain-id protection (for legacy tx) seems like a step backward 🤔

as you mentioned seems to only be disabled at the rpc layer

quickly checked other implementation, these changes seems to match what other implementation do
e.g:
https://github.com/bluealloy/revm/blob/77abd37dbd1280a131626cea00b40056a9d39cc1/crates/handler/src/validation.rs?plain=1#L128-L131

@albertov19
Copy link
Copy Markdown

albertov19 commented Dec 1, 2025

@pgherveou pre EIP155 transactions are still supported AFAIK -> https://github.com/papermoonio/pre-eip155-hardhat (Moonbase and Sepolia).

Typically, applications are recommended to switch to EIP155 transaction signing, including the actual chain ID.

One option could be to include a client flag to force EIP155 check, etc.

Copy link
Copy Markdown
Member

@xermicus xermicus left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I'm not convinced that this is a good idea and we need it. The referenced EIP is marked as stagnant (so, not live) anyways.

Comment on lines +71 to +78
// We would like to allow for transactions without a chain id to be executed through pallet
// revive. These are called unprotected transactions and they are transactions that predate
// EIP-155 which do not include a Chain ID. These transactions are still useful today in certain
// patterns in Ethereum such as "Nick's Method" for contract deployment which allows a contract
// to be deployed on all chains with the same address. This is only allowed for legacy
// transactions and isn't allowed for any other transaction type.
// * Here's a relevant EIP: https://eips.ethereum.org/EIPS/eip-2470
// * Here's Nick's article: https://weka.medium.com/how-to-send-ether-to-11-440-people-187e332566b7
Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This should be a doc comment

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Where would you recommend the doc comment to go? AFAIK there is no place to add a doc comment other than functions, types, etc, so there's no place that would accept a doc comment here

Comment on lines +71 to +78
// We would like to allow for transactions without a chain id to be executed through pallet
// revive. These are called unprotected transactions and they are transactions that predate
// EIP-155 which do not include a Chain ID. These transactions are still useful today in certain
// patterns in Ethereum such as "Nick's Method" for contract deployment which allows a contract
// to be deployed on all chains with the same address. This is only allowed for legacy
// transactions and isn't allowed for any other transaction type.
// * Here's a relevant EIP: https://eips.ethereum.org/EIPS/eip-2470
// * Here's Nick's article: https://weka.medium.com/how-to-send-ether-to-11-440-people-187e332566b7
Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The linked article doesn't talk about chain IDs at all. What am I missing?

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

No, Nick's method is not really talking about chain IDs at all, but for the contracts mentioned in the issue they are not using EIP2718 transaction envelope or EIP155, meaning that the contracts don't expect the chain-id as part of the RLP encode being signed.

This means that if we don't support pre-eip155 transactions (of legacy type) then we won't be able to deploy some of the contracts that use Nick's method to ensure the same address across different chains.

With type 2 transactions, the chain ID is included in the RLP encode but the v value is no longer relevant.

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This means that if we don't support pre-eip155 transactions (of legacy type) then we won't be able to deploy some of the contracts that use Nick's method to ensure the same address across different chains.

FWIW we don't have to do this hack since we have governance on Polkadot.

@0xOmarA
Copy link
Copy Markdown
Contributor Author

0xOmarA commented Dec 2, 2025

@pgherveou

I am not sure we should allow that though I think the competitor disallow it now
can you double check it?

I added a section in the PR doc that shows the various competitors and proof that this is allowed. Additionally, this adheres to EIP-155.

for example this is what I found in Geth
https://github.com/ethereum/go-ethereum/blob/6f2cbb7a27ba7e62b0bdb2090755ef0d271714be/internal/ethapi/api.go?plain=1#L1557-L1560

This is the RPC check I mentioned in the PR doc, you can see that it can be skipped if b.UnprotectedAllowed() returns true.

removing chain-id protection (for legacy tx) seems like a step backward 🤔

It's not removed, it's only removed for legacy transactions that do not include it.

@0xOmarA
Copy link
Copy Markdown
Contributor Author

0xOmarA commented Dec 3, 2025

@albertov19

One option could be to include a client flag to force EIP155 check, etc.

Yup, this is a good idea and it matches what other chains do too where there is an additional check in the RPC layer that can be disabled through a flag. I added that to this PR.

@athei
Copy link
Copy Markdown
Member

athei commented Dec 4, 2025

Can you add the protection to the RPC in this PR? If we allow it in the runtime we also need to add the same protection Geth has at the same time.

0xOmarA and others added 2 commits December 4, 2025 18:27
# Description

This is a PR that updates the commit hash of the
revive-differential-tests framework and the compilation caches to a
version that includes fixes to certain tests that used hard-coded gas
values. The compilation caches required an update since this was a
change to the contract's code.

---------

Co-authored-by: cmd[bot] <41898282+github-actions[bot]@users.noreply.github.com>
Co-authored-by: Torsten Stüber <[email protected]>
@0xOmarA 0xOmarA requested review from a team as code owners December 4, 2025 15:27
@0xOmarA
Copy link
Copy Markdown
Contributor Author

0xOmarA commented Dec 4, 2025

@athei Yup, I just added it. I believe that with this we match Geth's behavior at the protocol level and the RPC level.

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

There should only be one prdoc file per PR.

@Wizdave97
Copy link
Copy Markdown
Contributor

@pgherveou @0xOmarA, any updates on when this will be merged and available on Passet Hub? We need it to deploy hyperbridge contracts.

@athei
Copy link
Copy Markdown
Member

athei commented Dec 17, 2025

This is almost done. But author is in holidays already. I'll take this over and backport to the release branch.

@athei athei added the A4-backport-unstable2507 Pull request must be backported to the unstable2507 release branch label Dec 17, 2025
@Wizdave97
Copy link
Copy Markdown
Contributor

This is almost done. But author is in holidays already. I'll take this over and backport to the release branch.

Thank you

@athei athei added A4-backport-unstable2507 Pull request must be backported to the unstable2507 release branch and removed A4-backport-unstable2507 Pull request must be backported to the unstable2507 release branch labels Jan 6, 2026
@athei athei enabled auto-merge January 7, 2026 04:24
@athei athei added this pull request to the merge queue Jan 7, 2026
Merged via the queue into master with commit 0241d9b Jan 7, 2026
234 of 239 checks passed
@athei athei deleted the 0xOmarA/allow-no-chain-id-txs branch January 7, 2026 05:13
paritytech-release-backport-bot bot pushed a commit that referenced this pull request Jan 7, 2026
…n Revive (#10476)

# Description

This pull request allows for “[Nick’s
Method](https://weka.medium.com/how-to-send-ether-to-11-440-people-187e332566b7)”
style of deploying contracts which was requested in
paritytech/contract-issues#225 and was
attempted to be solved in
paritytech/contract-issues#99.

The “Nick Method” style of contract deployment is a very useful concept
to use when we wish to deploy a contract on multiple chains **with the
same address**. It allows us to do that by constructing a deployment
transaction that can be executed on any chain, thus allowing us to get
the same address for the contract’s deployment on any chain.

Additionally, this method allows for the contracts to be deployed
trustlessly, meaning that if any actor (regardless of whether they’re
honest or not) follow the same method for deploying the contract then
they’d get the same address across all chains. This allows anyone in the
Polkadot ecosystem to take existing contracts that use this method of
deployment (e.g., `Multicall3` and the ERC-1820 registry) and deploy
them on Polkadot and there’s already trust that the code is the same.

In order to be able to use the same transaction across multiple chains
transaction authors need to:

- Not include a chain id.
- Include a high gas limit so that the transaction works on (almost) all
chains.

As mentioned in
paritytech/contract-issues#225, this pattern
is already being used in a number of contracts. Most notably,
`Multicall3` and the ERC-1820 Registry.

Before this PR this method didn’t work in revive since the chain id was
an absolute must and any transaction that didn’t include a chain id
would fail with an `Invalid Transaction` error as seen below:

https://github.com/paritytech/polkadot-sdk/blob/c688963f51c55b3c2a16a00a33c4a086792a1544/substrate/frame/revive/src/evm/call.rs#L71-L76

The above implementation misses an important detail: legacy transactions
are permitted to not have the chain id set while all other non-legacy
transactions do not permit that. The models we had for legacy
transactions respected that, but we still did the chain id check for all
transaction types, which is incorrect. Therefore, this part of the code
was changed to the following:

```rust
match (tx.chain_id, tx.r#type.as_ref()) {
	(None, Some(super::Byte(TYPE_LEGACY))) => {},
	(Some(chain_id), ..) =>
		if chain_id != <T as Config>::ChainId::get().into() {
			log::debug!(target: LOG_TARGET, "Invalid chain_id {chain_id:?}");
			return Err(InvalidTransaction::Call);
		},
	(None, ..) => {
		log::debug!(target: LOG_TARGET, "Invalid chain_id None");
		return Err(InvalidTransaction::Call);
	},
}
```

The above code skips the chain id check if the transaction is of the
legacy type. Otherwise, the chain id is checked. If no chain id is
provided and the transaction is not of the legacy type then we error
out.

## Integration

Be aware that we now allow for legacy transactions to not have the chain
ID set in order to allow for “[Nick’s
Method](https://weka.medium.com/how-to-send-ether-to-11-440-people-187e332566b7)”
style of contract deployment. Non-legacy transaction continue to require
the chain id to be provided.

## Review Notes

- The main change that this PR makes can be found in the
`substrate/frame/revive/src/evm/call.rs` file allowing for legacy
contracts to not have the chain id set.
- Two new tests were added with this PR:
  - `dry_run_contract_deployment_with_nick_method_works`
  - `contract_deployment_with_nick_method_works`
- Both of the above tests test that “[Nick’s
Method](https://weka.medium.com/how-to-send-ether-to-11-440-people-187e332566b7)”
can be used to deploy the singleton factory contract provided in
[ERC-2470](https://eips.ethereum.org/EIPS/eip-2470) in a dry run and in
an `eth_transact` call.
- Note that the above tests needed to modify the transaction provided in
the ERC to update its gas limit (since the provided gas limit was too
low for our platform), **which breaks the whole idea of Nick’s Method
where the same transaction can be submitted to the same chain**. I
suspect that #10393
should fix this issue with the new gas scaling.

## Behavior On Other Chains

Since some of the comments on this PR talked about whether this is a
good idea, I wanted to add this section as justification for adding this
and also to review what other chains do with regards to unprotected
transactions.

I think that the security of this feature can be justified by:
* The fact that this feature is allowed on most other EVM chains,
including Ethereum mainnet itself.
* That metamask will fill in the chain-id automatically for users (as
seen
[here](https://docs.metamask.io/wallet/how-to/send-transactions?utm_source=chatgpt.com#chain-id)),
thus preventing users from broadcasting a re-playable transaction.
* That the default behavior of Alloy and other developer tools is to
fill in the chain ID when the developer is constructing transactions.
* That people who want to use this feature need to start their own RPC
with the `--allow-unprotected-txs` flag set

The behavior of Geth when it comes to unprotected transactions is:
* They're allowed at the protocol level for legacy transactions.
* They're disallowed at the protocol level for other transaction types.
* They're not allowed to be submitted through the Geth RPC unless the
`--rpc.allow_unprotected_txs` flag is set when Geth is started.

The above behavior matches what EIP-155 set in place for legacy
transactions.

The following is an overview of the various chains and their stance on
unprotected transactions:

* Ethereum/Geth: Allowed at the protocol level, the node must be started
with a special flag to permit it to be submitted through the RPC
([source](https://geth.ethereum.org/docs/fundamentals/command-line-options))
* Avalanche: Allowed at the protocol level, the node must be started
with a special flag to permit it to be submitted through the RPC
([source](https://build.avax.network/docs/nodes/chain-configs/subnet-evm#transaction-processing))
* Polygon: Allowed at the protocol level, the node must be started with
a special flag to permit it to be submitted through the RPC
([source](https://forum.polygon.technology/t/bor-v0-4-0-mainnet-release-indore-fork/12280))
* Arbitrum: Allowed at the protocol level, the node must be started with
a special flag to permit it to be submitted through the RPC
([source](https://pkg.go.dev/github.com/nim4/go-arbitrum/cmd/utils#:~:text=AllowUnprotectedTxs%20%3D%20%26cli.BoolFlag%7B%0A%09%09Name%3A%20%20%20%20%20%22rpc.allow%2Dunprotected%2Dtxs%22%2C%0A%09%09Usage%3A%20%20%20%20%22Allow%20for%20unprotected%20(non%20EIP155%20signed)%20transactions%20to%20be%20submitted%20via%20RPC%22%2C%0A%09%09Category%3A%20flags.APICategory%2C%0A%09%7D))

---------

Co-authored-by: cmd[bot] <41898282+github-actions[bot]@users.noreply.github.com>
Co-authored-by: Torsten Stüber <[email protected]>
Co-authored-by: Alexander Theißen <[email protected]>
(cherry picked from commit 0241d9b)
@paritytech-release-backport-bot
Copy link
Copy Markdown

Successfully created backport PR for unstable2507:

paritytech-release-backport-bot bot pushed a commit that referenced this pull request Jan 7, 2026
…n Revive (#10476)

# Description

This pull request allows for “[Nick’s
Method](https://weka.medium.com/how-to-send-ether-to-11-440-people-187e332566b7)”
style of deploying contracts which was requested in
paritytech/contract-issues#225 and was
attempted to be solved in
paritytech/contract-issues#99.

The “Nick Method” style of contract deployment is a very useful concept
to use when we wish to deploy a contract on multiple chains **with the
same address**. It allows us to do that by constructing a deployment
transaction that can be executed on any chain, thus allowing us to get
the same address for the contract’s deployment on any chain.

Additionally, this method allows for the contracts to be deployed
trustlessly, meaning that if any actor (regardless of whether they’re
honest or not) follow the same method for deploying the contract then
they’d get the same address across all chains. This allows anyone in the
Polkadot ecosystem to take existing contracts that use this method of
deployment (e.g., `Multicall3` and the ERC-1820 registry) and deploy
them on Polkadot and there’s already trust that the code is the same.

In order to be able to use the same transaction across multiple chains
transaction authors need to:

- Not include a chain id.
- Include a high gas limit so that the transaction works on (almost) all
chains.

As mentioned in
paritytech/contract-issues#225, this pattern
is already being used in a number of contracts. Most notably,
`Multicall3` and the ERC-1820 Registry.

Before this PR this method didn’t work in revive since the chain id was
an absolute must and any transaction that didn’t include a chain id
would fail with an `Invalid Transaction` error as seen below:

https://github.com/paritytech/polkadot-sdk/blob/c688963f51c55b3c2a16a00a33c4a086792a1544/substrate/frame/revive/src/evm/call.rs#L71-L76

The above implementation misses an important detail: legacy transactions
are permitted to not have the chain id set while all other non-legacy
transactions do not permit that. The models we had for legacy
transactions respected that, but we still did the chain id check for all
transaction types, which is incorrect. Therefore, this part of the code
was changed to the following:

```rust
match (tx.chain_id, tx.r#type.as_ref()) {
	(None, Some(super::Byte(TYPE_LEGACY))) => {},
	(Some(chain_id), ..) =>
		if chain_id != <T as Config>::ChainId::get().into() {
			log::debug!(target: LOG_TARGET, "Invalid chain_id {chain_id:?}");
			return Err(InvalidTransaction::Call);
		},
	(None, ..) => {
		log::debug!(target: LOG_TARGET, "Invalid chain_id None");
		return Err(InvalidTransaction::Call);
	},
}
```

The above code skips the chain id check if the transaction is of the
legacy type. Otherwise, the chain id is checked. If no chain id is
provided and the transaction is not of the legacy type then we error
out.

## Integration

Be aware that we now allow for legacy transactions to not have the chain
ID set in order to allow for “[Nick’s
Method](https://weka.medium.com/how-to-send-ether-to-11-440-people-187e332566b7)”
style of contract deployment. Non-legacy transaction continue to require
the chain id to be provided.

## Review Notes

- The main change that this PR makes can be found in the
`substrate/frame/revive/src/evm/call.rs` file allowing for legacy
contracts to not have the chain id set.
- Two new tests were added with this PR:
  - `dry_run_contract_deployment_with_nick_method_works`
  - `contract_deployment_with_nick_method_works`
- Both of the above tests test that “[Nick’s
Method](https://weka.medium.com/how-to-send-ether-to-11-440-people-187e332566b7)”
can be used to deploy the singleton factory contract provided in
[ERC-2470](https://eips.ethereum.org/EIPS/eip-2470) in a dry run and in
an `eth_transact` call.
- Note that the above tests needed to modify the transaction provided in
the ERC to update its gas limit (since the provided gas limit was too
low for our platform), **which breaks the whole idea of Nick’s Method
where the same transaction can be submitted to the same chain**. I
suspect that #10393
should fix this issue with the new gas scaling.

## Behavior On Other Chains

Since some of the comments on this PR talked about whether this is a
good idea, I wanted to add this section as justification for adding this
and also to review what other chains do with regards to unprotected
transactions.

I think that the security of this feature can be justified by:
* The fact that this feature is allowed on most other EVM chains,
including Ethereum mainnet itself.
* That metamask will fill in the chain-id automatically for users (as
seen
[here](https://docs.metamask.io/wallet/how-to/send-transactions?utm_source=chatgpt.com#chain-id)),
thus preventing users from broadcasting a re-playable transaction.
* That the default behavior of Alloy and other developer tools is to
fill in the chain ID when the developer is constructing transactions.
* That people who want to use this feature need to start their own RPC
with the `--allow-unprotected-txs` flag set

The behavior of Geth when it comes to unprotected transactions is:
* They're allowed at the protocol level for legacy transactions.
* They're disallowed at the protocol level for other transaction types.
* They're not allowed to be submitted through the Geth RPC unless the
`--rpc.allow_unprotected_txs` flag is set when Geth is started.

The above behavior matches what EIP-155 set in place for legacy
transactions.

The following is an overview of the various chains and their stance on
unprotected transactions:

* Ethereum/Geth: Allowed at the protocol level, the node must be started
with a special flag to permit it to be submitted through the RPC
([source](https://geth.ethereum.org/docs/fundamentals/command-line-options))
* Avalanche: Allowed at the protocol level, the node must be started
with a special flag to permit it to be submitted through the RPC
([source](https://build.avax.network/docs/nodes/chain-configs/subnet-evm#transaction-processing))
* Polygon: Allowed at the protocol level, the node must be started with
a special flag to permit it to be submitted through the RPC
([source](https://forum.polygon.technology/t/bor-v0-4-0-mainnet-release-indore-fork/12280))
* Arbitrum: Allowed at the protocol level, the node must be started with
a special flag to permit it to be submitted through the RPC
([source](https://pkg.go.dev/github.com/nim4/go-arbitrum/cmd/utils#:~:text=AllowUnprotectedTxs%20%3D%20%26cli.BoolFlag%7B%0A%09%09Name%3A%20%20%20%20%20%22rpc.allow%2Dunprotected%2Dtxs%22%2C%0A%09%09Usage%3A%20%20%20%20%22Allow%20for%20unprotected%20(non%20EIP155%20signed)%20transactions%20to%20be%20submitted%20via%20RPC%22%2C%0A%09%09Category%3A%20flags.APICategory%2C%0A%09%7D))

---------

Co-authored-by: cmd[bot] <41898282+github-actions[bot]@users.noreply.github.com>
Co-authored-by: Torsten Stüber <[email protected]>
Co-authored-by: Alexander Theißen <[email protected]>
(cherry picked from commit 0241d9b)
@paritytech-release-backport-bot
Copy link
Copy Markdown

Successfully created backport PR for stable2512:

athei added a commit that referenced this pull request Jan 7, 2026
Backport #10476 into `unstable2507` from 0xOmarA.

See the
[documentation](https://github.com/paritytech/polkadot-sdk/blob/master/docs/BACKPORT.md)
on how to use this bot.

<!--
  # To be used by other automation, do not modify:
  original-pr-number: #${pull_number}
-->

---------

Co-authored-by: Omar <[email protected]>
Co-authored-by: cmd[bot] <41898282+github-actions[bot]@users.noreply.github.com>
Co-authored-by: Torsten Stüber <[email protected]>
Co-authored-by: Alexander Theißen <[email protected]>
athei added a commit that referenced this pull request Jan 7, 2026
Backport #10476 into `stable2512` from 0xOmarA.

See the
[documentation](https://github.com/paritytech/polkadot-sdk/blob/master/docs/BACKPORT.md)
on how to use this bot.

<!--
  # To be used by other automation, do not modify:
  original-pr-number: #${pull_number}
-->

---------

Co-authored-by: Omar <[email protected]>
Co-authored-by: cmd[bot] <41898282+github-actions[bot]@users.noreply.github.com>
Co-authored-by: Torsten Stüber <[email protected]>
Co-authored-by: Alexander Theißen <[email protected]>
@Polkadot-Forum
Copy link
Copy Markdown

This pull request has been mentioned on Polkadot Forum. There might be relevant details there:

https://forum.polkadot.network/t/release-of-smart-contracts-on-polkadot/16848/3

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

A4-backport-stable2512 Pull request must be backported to the stable2512 release branch A4-backport-unstable2507 Pull request must be backported to the unstable2507 release branch T7-smart_contracts This PR/Issue is related to smart contracts.

Projects

Status: Done

Development

Successfully merging this pull request may close these issues.

8 participants