Skip to content

Commit

Permalink
feat: delegate calls (#4586)
Browse files Browse the repository at this point in the history
Implemented delegatecall ETH-style with contract instances, pending
discussion regarding contract classes.

Fixed usage of `storageContractAddress` in many places and creation of
CallRequests with proper context information.

Better tests and docs are coming! Trying to get at least the
functionality merged before the offsite.
  • Loading branch information
Thunkar authored Feb 16, 2024
1 parent 731d7d0 commit e6d65a7
Show file tree
Hide file tree
Showing 32 changed files with 505 additions and 117 deletions.
13 changes: 13 additions & 0 deletions .circleci/config.yml
Original file line number Diff line number Diff line change
Expand Up @@ -698,6 +698,17 @@ jobs:
name: "Test"
command: cond_spot_run_compose end-to-end 4 ./scripts/docker-compose.yml TEST=e2e_static_calls.test.ts

e2e-delegate-calls:
docker:
- image: aztecprotocol/alpine-build-image
resource_class: small
steps:
- *checkout
- *setup_env
- run:
name: "Test"
command: cond_spot_run_compose end-to-end 4 ./scripts/docker-compose.yml TEST=e2e_delegate_calls.test.ts

e2e-non-contract-account:
docker:
- image: aztecprotocol/alpine-build-image
Expand Down Expand Up @@ -1330,6 +1341,7 @@ workflows:
- e2e-block-building: *e2e_test
- e2e-nested-contract: *e2e_test
- e2e-static-calls: *e2e_test
- e2e-delegate-calls: *e2e_test
- e2e-non-contract-account: *e2e_test
- e2e-multiple-accounts-1-enc-key: *e2e_test
- e2e-cli: *e2e_test
Expand Down Expand Up @@ -1373,6 +1385,7 @@ workflows:
- e2e-block-building
- e2e-nested-contract
- e2e-static-calls
- e2e-delegate-calls
- e2e-non-contract-account
- e2e-multiple-accounts-1-enc-key
- e2e-cli
Expand Down
2 changes: 2 additions & 0 deletions noir-projects/aztec-nr/authwit/src/entrypoint/app.nr
Original file line number Diff line number Diff line change
Expand Up @@ -62,13 +62,15 @@ impl AppPayload {
call.target_address,
call.function_selector,
call.args_hash,
false,
false
);
} else {
let _result = context.call_private_function_with_packed_args(
call.target_address,
call.function_selector,
call.args_hash,
false,
false
);
}
Expand Down
2 changes: 2 additions & 0 deletions noir-projects/aztec-nr/authwit/src/entrypoint/fee.nr
Original file line number Diff line number Diff line change
Expand Up @@ -58,13 +58,15 @@ impl FeePayload {
call.target_address,
call.function_selector,
call.args_hash,
false,
false
);
} else {
let _result = context.call_private_function_with_packed_args(
call.target_address,
call.function_selector,
call.args_hash,
false,
false
);
}
Expand Down
122 changes: 89 additions & 33 deletions noir-projects/aztec-nr/aztec/src/context/private_context.nr
Original file line number Diff line number Diff line change
Expand Up @@ -251,49 +251,70 @@ impl PrivateContext {
) -> [Field; RETURN_VALUES_LENGTH] {
let args_hash = hash_args(args);
assert(args_hash == arguments::pack_arguments(args));
self.call_private_function_with_packed_args(contract_address, function_selector, args_hash, false)
self.call_private_function_with_packed_args(contract_address, function_selector, args_hash, false, false)
}

pub fn call_private_function_static<ARGS_COUNT>(
pub fn static_call_private_function<ARGS_COUNT>(
&mut self,
contract_address: AztecAddress,
function_selector: FunctionSelector,
args: [Field; ARGS_COUNT]
) -> [Field; RETURN_VALUES_LENGTH] {
let args_hash = hash_args(args);
assert(args_hash == arguments::pack_arguments(args));
self.call_private_function_with_packed_args(contract_address, function_selector, args_hash, true)
self.call_private_function_with_packed_args(contract_address, function_selector, args_hash, true, false)
}

pub fn delegate_call_private_function<ARGS_COUNT>(
&mut self,
contract_address: AztecAddress,
function_selector: FunctionSelector,
args: [Field; ARGS_COUNT]
) -> [Field; RETURN_VALUES_LENGTH] {
let args_hash = hash_args(args);
assert(args_hash == arguments::pack_arguments(args));
self.call_private_function_with_packed_args(contract_address, function_selector, args_hash, false, true)
}

pub fn call_private_function_no_args(
&mut self,
contract_address: AztecAddress,
function_selector: FunctionSelector
) -> [Field; RETURN_VALUES_LENGTH] {
self.call_private_function_with_packed_args(contract_address, function_selector, 0, false)
self.call_private_function_with_packed_args(contract_address, function_selector, 0, false, false)
}

pub fn static_call_private_function_no_args(
&mut self,
contract_address: AztecAddress,
function_selector: FunctionSelector
) -> [Field; RETURN_VALUES_LENGTH] {
self.call_private_function_with_packed_args(contract_address, function_selector, 0, true, false)
}

pub fn call_private_function_no_args_static(
pub fn delegate_call_private_function_no_args<ARGS_COUNT>(
&mut self,
contract_address: AztecAddress,
function_selector: FunctionSelector
) -> [Field; RETURN_VALUES_LENGTH] {
self.call_private_function_with_packed_args(contract_address, function_selector, 0, true)
self.call_private_function_with_packed_args(contract_address, function_selector, 0, false, true)
}

fn call_private_function_with_packed_args(
pub fn call_private_function_with_packed_args(
&mut self,
contract_address: AztecAddress,
function_selector: FunctionSelector,
args_hash: Field,
is_static_call: bool
is_static_call: bool,
is_delegate_call: bool
) -> [Field; RETURN_VALUES_LENGTH] {
let item = call_private_function_internal(
contract_address,
function_selector,
args_hash,
self.side_effect_counter,
is_static_call
is_static_call,
is_delegate_call
);

assert_eq(item.public_inputs.call_context.start_side_effect_counter, self.side_effect_counter);
Expand All @@ -305,16 +326,23 @@ impl PrivateContext {
assert(args_hash == item.public_inputs.args_hash);

// Assert that the call context of the enqueued call generated by the oracle matches our request.
// We are issuing a regular call which is not delegate, static, or deployment. We also constrain
// the msg_sender in the nested call to be equal to our address, and the execution context address
// for the nested call to be equal to the address we actually called.
assert(item.public_inputs.call_context.is_delegate_call == false);
assert(item.public_inputs.call_context.is_delegate_call == is_delegate_call);
assert(item.public_inputs.call_context.is_static_call == is_static_call);
assert(item.public_inputs.call_context.is_contract_deployment == false);
assert(
item.public_inputs.call_context.msg_sender.eq(self.inputs.call_context.storage_contract_address)
);
assert(item.public_inputs.call_context.storage_contract_address.eq(contract_address));

if (is_delegate_call) {
// For delegate calls, we also constrain the execution context address for the nested call to be equal to our address.
assert(
item.public_inputs.call_context.storage_contract_address.eq(self.inputs.call_context.storage_contract_address)
);
assert(item.public_inputs.call_context.msg_sender.eq(self.inputs.call_context.msg_sender));
} else {
// For non-delegate calls, we also constrain the execution context address for the nested call to be equal to the address we called.
assert(item.public_inputs.call_context.storage_contract_address.eq(contract_address));
assert(
item.public_inputs.call_context.msg_sender.eq(self.inputs.call_context.storage_contract_address)
);
}

self.private_call_stack_hashes.push(item.hash());

Expand All @@ -329,49 +357,70 @@ impl PrivateContext {
) {
let args_hash = hash_args(args);
assert(args_hash == arguments::pack_arguments(args));
self.call_public_function_with_packed_args(contract_address, function_selector, args_hash, false)
self.call_public_function_with_packed_args(contract_address, function_selector, args_hash, false, false)
}

pub fn call_public_function_static<ARGS_COUNT>(
pub fn static_call_public_function<ARGS_COUNT>(
&mut self,
contract_address: AztecAddress,
function_selector: FunctionSelector,
args: [Field; ARGS_COUNT]
) {
let args_hash = hash_args(args);
assert(args_hash == arguments::pack_arguments(args));
self.call_public_function_with_packed_args(contract_address, function_selector, args_hash, true)
self.call_public_function_with_packed_args(contract_address, function_selector, args_hash, true, false)
}

pub fn delegate_call_public_function<ARGS_COUNT>(
&mut self,
contract_address: AztecAddress,
function_selector: FunctionSelector,
args: [Field; ARGS_COUNT]
) {
let args_hash = hash_args(args);
assert(args_hash == arguments::pack_arguments(args));
self.call_public_function_with_packed_args(contract_address, function_selector, args_hash, false, true)
}

pub fn call_public_function_no_args(
&mut self,
contract_address: AztecAddress,
function_selector: FunctionSelector
) {
self.call_public_function_with_packed_args(contract_address, function_selector, 0, false)
self.call_public_function_with_packed_args(contract_address, function_selector, 0, false, false)
}

pub fn static_call_public_function_no_args(
&mut self,
contract_address: AztecAddress,
function_selector: FunctionSelector
) {
self.call_public_function_with_packed_args(contract_address, function_selector, 0, true, false)
}

pub fn call_public_function_no_args_static(
pub fn delegate_call_public_function_no_args(
&mut self,
contract_address: AztecAddress,
function_selector: FunctionSelector
) {
self.call_public_function_with_packed_args(contract_address, function_selector, 0, true)
self.call_public_function_with_packed_args(contract_address, function_selector, 0, false, true)
}

pub fn call_public_function_with_packed_args(
&mut self,
contract_address: AztecAddress,
function_selector: FunctionSelector,
args_hash: Field,
is_static_call: bool
is_static_call: bool,
is_delegate_call: bool
) {
let fields = enqueue_public_function_call_internal(
contract_address,
function_selector,
args_hash,
self.side_effect_counter,
is_static_call
is_static_call,
is_delegate_call
);

let mut reader = Reader::new(fields);
Expand Down Expand Up @@ -410,16 +459,23 @@ impl PrivateContext {
assert(args_hash == item.public_inputs.args_hash);

// Assert that the call context of the enqueued call generated by the oracle matches our request.
// We are issuing a regular call which is not delegate, static, or deployment. We also constrain
// the msg_sender in the nested call to be equal to our address, and the execution context address
// for the nested call to be equal to the address we actually called.
assert(item.public_inputs.call_context.is_delegate_call == false);
assert(item.public_inputs.call_context.is_delegate_call == is_delegate_call);
assert(item.public_inputs.call_context.is_static_call == is_static_call);
assert(item.public_inputs.call_context.is_contract_deployment == false);
assert(
item.public_inputs.call_context.msg_sender.eq(self.inputs.call_context.storage_contract_address)
);
assert(item.public_inputs.call_context.storage_contract_address.eq(contract_address));

if (is_delegate_call) {
// For delegate calls, we also constrain the execution context address for the nested call to be equal to our address.
assert(
item.public_inputs.call_context.storage_contract_address.eq(self.inputs.call_context.storage_contract_address)
);
assert(item.public_inputs.call_context.msg_sender.eq(self.inputs.call_context.msg_sender));
} else {
// For non-delegate calls, we also constrain the execution context address for the nested call to be equal to the address we called.
assert(item.public_inputs.call_context.storage_contract_address.eq(contract_address));
assert(
item.public_inputs.call_context.msg_sender.eq(self.inputs.call_context.storage_contract_address)
);
}

self.public_call_stack_hashes.push(item.hash());
}
Expand Down
31 changes: 25 additions & 6 deletions noir-projects/aztec-nr/aztec/src/context/public_context.nr
Original file line number Diff line number Diff line change
Expand Up @@ -194,33 +194,52 @@ impl PublicContext {
) -> [Field; RETURN_VALUES_LENGTH] {
let args_hash = hash_args(args);
assert(args_hash == arguments::pack_arguments(args));
call_public_function_internal(contract_address, function_selector, args_hash, false)
call_public_function_internal(contract_address, function_selector, args_hash, false, false)
}

pub fn call_public_function_static<ARGS_COUNT>(
pub fn static_call_public_function<ARGS_COUNT>(
_self: Self,
contract_address: AztecAddress,
function_selector: FunctionSelector,
args: [Field; ARGS_COUNT]
) -> [Field; RETURN_VALUES_LENGTH] {
let args_hash = hash_args(args);
assert(args_hash == arguments::pack_arguments(args));
call_public_function_internal(contract_address, function_selector, args_hash, true)
call_public_function_internal(contract_address, function_selector, args_hash, true, false)
}

pub fn delegate_call_public_function<ARGS_COUNT>(
_self: Self,
contract_address: AztecAddress,
function_selector: FunctionSelector,
args: [Field; ARGS_COUNT]
) -> [Field; RETURN_VALUES_LENGTH] {
let args_hash = hash_args(args);
assert(args_hash == arguments::pack_arguments(args));
call_public_function_internal(contract_address, function_selector, args_hash, false, true)
}

pub fn call_public_function_no_args(
_self: Self,
contract_address: AztecAddress,
function_selector: FunctionSelector
) -> [Field; RETURN_VALUES_LENGTH] {
call_public_function_internal(contract_address, function_selector, 0, false)
call_public_function_internal(contract_address, function_selector, 0, false, false)
}

pub fn static_call_public_function_no_args(
_self: Self,
contract_address: AztecAddress,
function_selector: FunctionSelector
) -> [Field; RETURN_VALUES_LENGTH] {
call_public_function_internal(contract_address, function_selector, 0, true, false)
}

pub fn call_public_function_no_args_static(
pub fn delegate_call_public_function_no_args(
_self: Self,
contract_address: AztecAddress,
function_selector: FunctionSelector
) -> [Field; RETURN_VALUES_LENGTH] {
call_public_function_internal(contract_address, function_selector, 0, true)
call_public_function_internal(contract_address, function_selector, 0, false, true)
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -9,22 +9,25 @@ fn call_private_function_oracle(
_function_selector: FunctionSelector,
_args_hash: Field,
_start_side_effect_counter: u32,
_is_static_call: bool
_is_static_call: bool,
_is_delegate_call: bool
) -> [Field; PRIVATE_CALL_STACK_ITEM_LENGTH] {}

unconstrained pub fn call_private_function_internal(
contract_address: AztecAddress,
function_selector: FunctionSelector,
args_hash: Field,
start_side_effect_counter: u32,
is_static_call: bool
is_static_call: bool,
is_delegate_call: bool
) -> PrivateCallStackItem {
let fields = call_private_function_oracle(
contract_address,
function_selector,
args_hash,
start_side_effect_counter,
is_static_call
is_static_call,
is_delegate_call
);

PrivateCallStackItem::deserialize(fields)
Expand Down
Loading

0 comments on commit e6d65a7

Please sign in to comment.