Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

[pallet-revive] do not trap the caller on instantiations with duplicate contracts #7414

Merged
merged 3 commits into from
Feb 3, 2025
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
20 changes: 20 additions & 0 deletions prdoc/pr_7414.prdoc
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
title: '[pallet-revive] do not trap the caller on instantiations with duplicate contracts'
doc:
- audience: Runtime Dev
description: |-
This PR changes the behavior of `instantiate` when the resulting contract address already exists (because the caller tried to instantiate the same contract with the same salt multiple times): Instead of trapping the caller, return an error code.

Solidity allows `catch`ing this, which doesn't work if we are trapping the caller. For example, the change makes the following snippet work:

```Solidity
try new Foo{salt: hex"00"}() returns (Foo) {
// Instantiation was successful (contract address was free and constructor did not revert)
} catch {
// This branch is expected to be taken if the instantiation failed because of a duplicate salt
}
```
crates:
- name: pallet-revive
bump: major
- name: pallet-revive-uapi
bump: major
12 changes: 12 additions & 0 deletions substrate/frame/revive/src/tests.rs
Original file line number Diff line number Diff line change
Expand Up @@ -1614,6 +1614,18 @@ fn instantiate_return_code() {
.data(callee_hash.iter().chain(&2u32.to_le_bytes()).cloned().collect())
.build_and_unwrap_result();
assert_return_code!(result, RuntimeReturnCode::CalleeTrapped);

// Contract instantiation succeeds
let result = builder::bare_call(contract.addr)
.data(callee_hash.iter().chain(&0u32.to_le_bytes()).cloned().collect())
.build_and_unwrap_result();
assert_return_code!(result, 0);

// Contract instantiation fails because the same salt is being used again.
let result = builder::bare_call(contract.addr)
.data(callee_hash.iter().chain(&0u32.to_le_bytes()).cloned().collect())
.build_and_unwrap_result();
assert_return_code!(result, RuntimeReturnCode::DuplicateContractAddress);
});
}

Expand Down
2 changes: 2 additions & 0 deletions substrate/frame/revive/src/wasm/runtime.rs
Original file line number Diff line number Diff line change
Expand Up @@ -792,10 +792,12 @@ impl<'a, E: Ext, M: ?Sized + Memory<E::T>> Runtime<'a, E, M> {
let transfer_failed = Error::<E::T>::TransferFailed.into();
let out_of_gas = Error::<E::T>::OutOfGas.into();
let out_of_deposit = Error::<E::T>::StorageDepositLimitExhausted.into();
let duplicate_contract = Error::<E::T>::DuplicateContract.into();

// errors in the callee do not trap the caller
match (from.error, from.origin) {
(err, _) if err == transfer_failed => Ok(TransferFailed),
(err, _) if err == duplicate_contract => Ok(DuplicateContractAddress),
(err, Callee) if err == out_of_gas || err == out_of_deposit => Ok(OutOfResources),
(_, Callee) => Ok(CalleeTrapped),
(err, _) => Err(err),
Expand Down
3 changes: 3 additions & 0 deletions substrate/frame/revive/uapi/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -98,6 +98,9 @@ define_error_codes! {
XcmExecutionFailed = 9,
/// The `xcm_send` call failed.
XcmSendFailed = 10,
/// Contract instantiation failed because the address already exists.
/// Occurs when instantiating the same contract with the same salt more than once.
DuplicateContractAddress = 11,
}

/// The raw return code returned by the host side.
Expand Down
Loading