Skip to content
Merged
Show file tree
Hide file tree
Changes from 7 commits
Commits
Show all changes
24 commits
Select commit Hold shift + click to select a range
acde6b4
update delegate_call to take address and weight
ermalkaleci Oct 17, 2024
02dace6
fix fixtures
ermalkaleci Oct 17, 2024
23b9319
Merge branch 'master' of github.com:ermalkaleci/polkadot-sdk into fea…
ermalkaleci Oct 23, 2024
697422b
fix test
ermalkaleci Oct 23, 2024
7f374c0
add prdoc
ermalkaleci Oct 23, 2024
4a29f0a
fmt
ermalkaleci Oct 23, 2024
78d39c2
fmt
ermalkaleci Oct 23, 2024
171167a
update prdoc
ermalkaleci Oct 28, 2024
67174f0
Merge branch 'master' of github.com:ermalkaleci/polkadot-sdk into fea…
ermalkaleci Oct 28, 2024
f899e9e
add option to limit storage deposit
ermalkaleci Oct 28, 2024
a5f2f1c
clippy
ermalkaleci Oct 28, 2024
c05bd51
fix immutable read on delegate call
ermalkaleci Oct 31, 2024
ea118a0
Merge branch 'master' of github.com:ermalkaleci/polkadot-sdk into fea…
ermalkaleci Oct 31, 2024
46af335
refactor
ermalkaleci Oct 31, 2024
ad9008e
Merge branch 'master' of github.com:ermalkaleci/polkadot-sdk into fea…
ermalkaleci Oct 31, 2024
63844ee
fmt
ermalkaleci Oct 31, 2024
bb79cda
Revert "refactor"
ermalkaleci Oct 31, 2024
fc6a762
merge caller and callee into delegate info
ermalkaleci Nov 1, 2024
7073ee3
apply suggestion
ermalkaleci Nov 6, 2024
9f06a03
add deposit_limit test
ermalkaleci Nov 6, 2024
95d1ffd
fmt
ermalkaleci Nov 6, 2024
aee61a6
Merge branch 'master' into feat/update-delegate-call
athei Nov 18, 2024
20767b6
Merge branch 'master' into feat/update-delegate-call
athei Nov 18, 2024
5c1f1ed
fix test
ermalkaleci Nov 19, 2024
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
17 changes: 17 additions & 0 deletions prdoc/pr_6111.prdoc
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
title: "[pallet-revive] Update delegate_call to accept address and weight"

doc:
- audience: Runtime Dev
description: |
Enhance the `delegate_call` function to accept an `address` target parameter instead of a `code_hash`.
This allows direct identification of the target contract using the provided address.
Additionally, introduce parameters for specifying a customizable `ref_time` limit and `proof_size` limit,
thereby improving flexibility and control during contract interactions.

crates:
- name: pallet-revive
bump: major
- name: pallet-revive-fixtures
bump: major
Comment thread
ermalkaleci marked this conversation as resolved.
Outdated
- name: pallet-revive-uapi
bump: major
8 changes: 6 additions & 2 deletions substrate/frame/revive/fixtures/contracts/delegate_call.rs
Original file line number Diff line number Diff line change
Expand Up @@ -28,7 +28,11 @@ pub extern "C" fn deploy() {}
#[no_mangle]
#[polkavm_derive::polkavm_export]
pub extern "C" fn call() {
input!(code_hash: &[u8; 32],);
input!(
address: &[u8; 20],
ref_time: u64,
proof_size: u64,
);

let mut key = [0u8; 32];
key[0] = 1u8;
Expand All @@ -42,7 +46,7 @@ pub extern "C" fn call() {
assert!(value[0] == 2u8);

let input = [0u8; 0];
api::delegate_call(uapi::CallFlags::empty(), code_hash, &input, None).unwrap();
api::delegate_call(uapi::CallFlags::empty(), address, ref_time, proof_size, &input, None).unwrap();

api::get_storage(StorageFlags::empty(), &key, value).unwrap();
assert!(value[0] == 1u8);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -28,9 +28,9 @@ pub extern "C" fn deploy() {}
#[no_mangle]
#[polkavm_derive::polkavm_export]
pub extern "C" fn call() {
input!(code_hash: &[u8; 32],);
input!(address: &[u8; 20],);

// Delegate call into passed code hash.
// Delegate call into passed address.
let input = [0u8; 0];
api::delegate_call(uapi::CallFlags::empty(), code_hash, &input, None).unwrap();
api::delegate_call(uapi::CallFlags::empty(), address, 0, 0, &input, None).unwrap();
}
Original file line number Diff line number Diff line change
Expand Up @@ -30,6 +30,7 @@ const ETH_ALICE: [u8; 20] = [1u8; 20];
fn load_input(delegate_call: bool) {
input!(
action: u32,
address: &[u8; 20],
code_hash: &[u8; 32],
);

Expand All @@ -51,7 +52,7 @@ fn load_input(delegate_call: bool) {
}

if delegate_call {
api::delegate_call(uapi::CallFlags::empty(), code_hash, &[], None).unwrap();
api::delegate_call(uapi::CallFlags::empty(), address, 0, 0, &[], None).unwrap();
}
}

Expand Down
4 changes: 3 additions & 1 deletion substrate/frame/revive/src/benchmarking/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -1501,7 +1501,9 @@ mod benchmarks {
result = runtime.bench_delegate_call(
memory.as_mut_slice(),
0, // flags
0, // code_hash_ptr
0, // address_ptr
0, // ref_time_limit
0, // proof_size_limit
0, // input_data_ptr
0, // input_data_len
SENTINEL, // output_ptr
Expand Down
82 changes: 71 additions & 11 deletions substrate/frame/revive/src/exec.rs
Original file line number Diff line number Diff line change
Expand Up @@ -198,7 +198,12 @@ pub trait Ext: sealing::Sealed {
/// Execute code in the current frame.
///
/// Returns the code size of the called contract.
fn delegate_call(&mut self, code: H256, input_data: Vec<u8>) -> Result<(), ExecError>;
fn delegate_call(
&mut self,
gas_limit: Weight,
Comment thread
ermalkaleci marked this conversation as resolved.
address: H160,
input_data: Vec<u8>,
) -> Result<(), ExecError>;

/// Instantiate a contract from the given code.
///
Expand Down Expand Up @@ -1397,12 +1402,18 @@ where
result
}

fn delegate_call(&mut self, code_hash: H256, input_data: Vec<u8>) -> Result<(), ExecError> {
fn delegate_call(
&mut self,
gas_limit: Weight,
address: H160,
input_data: Vec<u8>,
) -> Result<(), ExecError> {
// We reset the return data now, so it is cleared out even if no new frame was executed.
// This is for example the case for unknown code hashes or creating the frame fails.
*self.last_frame_output_mut() = Default::default();

let executable = E::from_storage(code_hash, self.gas_meter_mut())?;
let contract_info = ContractInfoOf::<T>::get(&address).ok_or(Error::<T>::CodeNotFound)?;
let executable = E::from_storage(contract_info.code_hash, self.gas_meter_mut())?;
let top_frame = self.top_frame_mut();
let contract_info = top_frame.contract_info().clone();
let account_id = top_frame.account_id.clone();
Expand All @@ -1414,7 +1425,7 @@ where
delegated_call: Some(DelegatedCall { executable, caller: self.caller().clone() }),
},
value,
Weight::zero(),
gas_limit,
BalanceOf::<T>::zero(),
self.is_read_only(),
)?;
Expand Down Expand Up @@ -1839,7 +1850,7 @@ mod tests {
AddressMapper, Error,
};
use assert_matches::assert_matches;
use frame_support::{assert_err, assert_ok, parameter_types};
use frame_support::{assert_err, assert_noop, assert_ok, parameter_types};
use frame_system::{AccountInfo, EventRecord, Phase};
use pallet_revive_uapi::ReturnFlags;
use pretty_assertions::assert_eq;
Expand Down Expand Up @@ -2062,33 +2073,82 @@ mod tests {

let delegate_ch = MockLoader::insert(Call, move |ctx, _| {
assert_eq!(ctx.ext.value_transferred(), U256::from(value));
let _ = ctx.ext.delegate_call(success_ch, Vec::new())?;
let _ = ctx.ext.delegate_call(Weight::zero(), CHARLIE_ADDR, Vec::new())?;
Ok(ExecReturnValue { flags: ReturnFlags::empty(), data: Vec::new() })
});

ExtBuilder::default().build().execute_with(|| {
place_contract(&BOB, delegate_ch);
place_contract(&CHARLIE, success_ch);
set_balance(&ALICE, 100);
let balance = get_balance(&BOB_CONTRACT_ID);
let origin = Origin::from_account_id(ALICE);
let mut storage_meter = storage::meter::Meter::new(&origin, 0, 55).unwrap();

let _ = MockStack::run_call(
assert_ok!(MockStack::run_call(
origin,
BOB_ADDR,
&mut GasMeter::<Test>::new(GAS_LIMIT),
&mut storage_meter,
value,
vec![],
None,
)
.unwrap();
));

assert_eq!(get_balance(&ALICE), 100 - value);
assert_eq!(get_balance(&BOB_CONTRACT_ID), balance + value);
});
}

#[test]
fn delegate_call_missing_contract() {
let missing_ch = MockLoader::insert(Call, move |_ctx, _| {
Ok(ExecReturnValue { flags: ReturnFlags::empty(), data: Vec::new() })
});

let delegate_ch = MockLoader::insert(Call, move |ctx, _| {
let _ = ctx.ext.delegate_call(Weight::zero(), CHARLIE_ADDR, Vec::new())?;
Ok(ExecReturnValue { flags: ReturnFlags::empty(), data: Vec::new() })
});

ExtBuilder::default().build().execute_with(|| {
place_contract(&BOB, delegate_ch);
set_balance(&ALICE, 100);

let origin = Origin::from_account_id(ALICE);
let mut storage_meter = storage::meter::Meter::new(&origin, 0, 55).unwrap();

// contract code missing
assert_noop!(
MockStack::run_call(
origin.clone(),
BOB_ADDR,
&mut GasMeter::<Test>::new(GAS_LIMIT),
&mut storage_meter,
0,
vec![],
None,
),
ExecError {
error: Error::<Test>::CodeNotFound.into(),
origin: ErrorOrigin::Callee,
}
);

// add missing contract code
place_contract(&CHARLIE, missing_ch);
assert_ok!(MockStack::run_call(
origin,
BOB_ADDR,
&mut GasMeter::<Test>::new(GAS_LIMIT),
&mut storage_meter,
0,
vec![],
None,
));
});
}

#[test]
fn changes_are_reverted_on_failing_call() {
// This test verifies that changes are reverted on a call which fails (or equally, returns
Expand Down Expand Up @@ -4410,7 +4470,7 @@ mod tests {
// An unknown code hash to fail the delegate_call on the first condition.
*ctx.ext.last_frame_output_mut() = output_revert();
assert_eq!(
ctx.ext.delegate_call(invalid_code_hash, Default::default()),
ctx.ext.delegate_call(Weight::zero(), H160([0xff; 20]), Default::default()),
Err(Error::<Test>::CodeNotFound.into())
);
assert_eq!(ctx.ext.last_frame_output(), &Default::default());
Expand Down Expand Up @@ -4529,7 +4589,7 @@ mod tests {

// In a delegate call, we should witness the caller immutable data
assert_eq!(
ctx.ext.delegate_call(charlie_ch, Vec::new()).map(|_| ctx
ctx.ext.delegate_call(Weight::zero(), CHARLIE_ADDR, Vec::new()).map(|_| ctx
.ext
.last_frame_output()
.data
Expand Down
74 changes: 65 additions & 9 deletions substrate/frame/revive/src/tests.rs
Original file line number Diff line number Diff line change
Expand Up @@ -1142,7 +1142,7 @@ mod run_tests {
#[test]
fn delegate_call() {
let (caller_wasm, _caller_code_hash) = compile_module("delegate_call").unwrap();
let (callee_wasm, callee_code_hash) = compile_module("delegate_call_lib").unwrap();
let (callee_wasm, _callee_code_hash) = compile_module("delegate_call_lib").unwrap();

ExtBuilder::default().existential_deposit(500).build().execute_with(|| {
let _ = <Test as Config>::Currency::set_balance(&ALICE, 1_000_000);
Expand All @@ -1152,12 +1152,53 @@ mod run_tests {
builder::bare_instantiate(Code::Upload(caller_wasm))
.value(300_000)
.build_and_unwrap_contract();
// Only upload 'callee' code
assert_ok!(Contracts::upload_code(RuntimeOrigin::signed(ALICE), callee_wasm, 100_000,));

// Instantiate the 'callee'
let Contract { addr: callee_addr, .. } =
builder::bare_instantiate(Code::Upload(callee_wasm))
.value(100_000)
.build_and_unwrap_contract();

assert_ok!(builder::call(caller_addr)
.value(1337)
.data(callee_code_hash.as_ref().to_vec())
.data((callee_addr, 0u64, 0u64).encode())
.build());
});
}

#[test]
fn delegate_call_with_limits() {
let (caller_wasm, _caller_code_hash) = compile_module("delegate_call").unwrap();
let (callee_wasm, _callee_code_hash) = compile_module("delegate_call_lib").unwrap();

ExtBuilder::default().existential_deposit(500).build().execute_with(|| {
let _ = <Test as Config>::Currency::set_balance(&ALICE, 1_000_000);

// Instantiate the 'caller'
let Contract { addr: caller_addr, .. } =
builder::bare_instantiate(Code::Upload(caller_wasm))
.value(300_000)
.build_and_unwrap_contract();

// Instantiate the 'callee'
let Contract { addr: callee_addr, .. } =
builder::bare_instantiate(Code::Upload(callee_wasm))
.value(100_000)
.build_and_unwrap_contract();

// fails, not enough weight
assert_err!(
builder::bare_call(caller_addr)
.value(1337)
.data((callee_addr, 100u64, 100u64).encode())
.build()
.result,
Error::<Test>::ContractTrapped,
);

assert_ok!(builder::call(caller_addr)
.value(1337)
.data((callee_addr, 500_000_000u64, 100_000u64).encode())
.build());
});
}
Expand Down Expand Up @@ -3722,6 +3763,12 @@ mod run_tests {
.map(|c| sp_core::H256(sp_io::hashing::keccak_256(c)))
.collect();

let hash2addr = |code_hash: &H256| {
let mut addr = H160::zero();
addr.as_bytes_mut().copy_from_slice(&code_hash.as_ref()[..20]);
addr
};

// Define inputs with various actions to test locking / unlocking delegate_dependencies.
// See the contract for more details.
let noop_input = (0u32, callee_hashes[0]);
Expand All @@ -3731,17 +3778,19 @@ mod run_tests {

// Instantiate the caller contract with the given input.
let instantiate = |input: &(u32, H256)| {
let (action, code_hash) = input;
builder::bare_instantiate(Code::Upload(wasm_caller.clone()))
.origin(RuntimeOrigin::signed(ETH_ALICE))
.data(input.encode())
.data((action, hash2addr(code_hash), code_hash).encode())
.build()
};

// Call contract with the given input.
let call = |addr_caller: &H160, input: &(u32, H256)| {
let (action, code_hash) = input;
builder::bare_call(*addr_caller)
.origin(RuntimeOrigin::signed(ETH_ALICE))
.data(input.encode())
.data((action, hash2addr(code_hash), code_hash).encode())
.build()
};
const ED: u64 = 2000;
Expand All @@ -3758,14 +3807,17 @@ mod run_tests {
// Upload all the delegated codes (they all have the same size)
let mut deposit = Default::default();
for code in callee_codes.iter() {
let CodeUploadReturnValue { deposit: deposit_per_code, .. } =
let CodeUploadReturnValue { deposit: deposit_per_code, code_hash } =
Contracts::bare_upload_code(
RuntimeOrigin::signed(ETH_ALICE),
code.clone(),
deposit_limit::<Test>(),
)
.unwrap();
deposit = deposit_per_code;
// Mock contract info by using first 20 bytes of code_hash as address.
let addr = hash2addr(&code_hash);
ContractInfoOf::<Test>::set(&addr, ContractInfo::new(&addr, 0, code_hash).ok());
}

// Instantiate should now work.
Expand Down Expand Up @@ -3802,7 +3854,11 @@ mod run_tests {

// Locking self should fail.
assert_err!(
call(&addr_caller, &(1u32, self_code_hash)).result,
builder::bare_call(addr_caller)
.origin(RuntimeOrigin::signed(ETH_ALICE))
.data((1u32, &addr_caller, self_code_hash).encode())
.build()
.result,
Error::<Test>::CannotAddSelfAsDelegateDependency
);

Expand Down Expand Up @@ -3841,7 +3897,7 @@ mod run_tests {
assert_err!(
builder::bare_call(addr_caller)
.storage_deposit_limit(dependency_deposit - 1)
.data(lock_delegate_dependency_input.encode())
.data((1u32, hash2addr(&callee_hashes[0]), callee_hashes[0]).encode())
.build()
.result,
Error::<Test>::StorageDepositLimitExhausted
Expand Down
Loading