Skip to content

Commit

Permalink
squash
Browse files Browse the repository at this point in the history
  • Loading branch information
just-mitch committed Feb 22, 2024
1 parent c5eeb4c commit 5ae252b
Show file tree
Hide file tree
Showing 54 changed files with 104,055 additions and 692 deletions.
98 changes: 98 additions & 0 deletions l1-contracts/test/portals/GasPortal.sol
Original file line number Diff line number Diff line change
@@ -0,0 +1,98 @@
pragma solidity >=0.8.18;

import {IERC20} from "@oz/token/ERC20/IERC20.sol";
import {SafeERC20} from "@oz/token/ERC20/utils/SafeERC20.sol";

// Messaging
import {IRegistry} from "../../src/core/interfaces/messagebridge/IRegistry.sol";
import {IInbox} from "../../src/core/interfaces/messagebridge/IInbox.sol";
import {DataStructures} from "../../src/core/libraries/DataStructures.sol";
// docs:start:content_hash_sol_import
import {Hash} from "../../src/core/libraries/Hash.sol";
// docs:end:content_hash_sol_import

// docs:start:init
contract GasPortal {
using SafeERC20 for IERC20;

IRegistry public registry;
IERC20 public underlying;
bytes32 public l2TokenAddress;

function initialize(address _registry, address _underlying, bytes32 _l2TokenAddress) external {
registry = IRegistry(_registry);
underlying = IERC20(_underlying);
l2TokenAddress = _l2TokenAddress;
}
// docs:end:init

// docs:start:deposit_public
/**
* @notice Deposit funds into the portal and adds an L2 message which can only be consumed publicly on Aztec
* @param _to - The aztec address of the recipient
* @param _amount - The amount to deposit
* @param _canceller - The address that can cancel the L1 to L2 message
* @param _deadline - The timestamp after which the entry can be cancelled
* @param _secretHash - The hash of the secret consumable message. The hash should be 254 bits (so it can fit in a Field element)
* @return - The key of the entry in the Inbox
*/
function depositToAztecPublic(
bytes32 _to,
uint256 _amount,
address _canceller,
uint32 _deadline,
bytes32 _secretHash
) external payable returns (bytes32) {
// Preamble
IInbox inbox = registry.getInbox();
DataStructures.L2Actor memory actor = DataStructures.L2Actor(l2TokenAddress, 1);

// Hash the message content to be reconstructed in the receiving contract
bytes32 contentHash = Hash.sha256ToField(
abi.encodeWithSignature("mint_public(bytes32,uint256,address)", _to, _amount, _canceller)
);

// Hold the tokens in the portal
underlying.safeTransferFrom(msg.sender, address(this), _amount);

// Send message to rollup
return inbox.sendL2Message{value: msg.value}(actor, _deadline, contentHash, _secretHash);
}

/**
* @notice Cancel a public depositToAztec L1 to L2 message
* @dev only callable by the `canceller` of the message
* @param _to - The aztec address of the recipient in the original message
* @param _amount - The amount to deposit per the original message
* @param _deadline - The timestamp after which the entry can be cancelled
* @param _secretHash - The hash of the secret consumable message in the original message
* @param _fee - The fee paid to the sequencer
* @return The key of the entry in the Inbox
*/
function cancelL1ToAztecMessagePublic(
bytes32 _to,
uint256 _amount,
uint32 _deadline,
bytes32 _secretHash,
uint64 _fee
) external returns (bytes32) {
IInbox inbox = registry.getInbox();
DataStructures.L1Actor memory l1Actor = DataStructures.L1Actor(address(this), block.chainid);
DataStructures.L2Actor memory l2Actor = DataStructures.L2Actor(l2TokenAddress, 1);
DataStructures.L1ToL2Msg memory message = DataStructures.L1ToL2Msg({
sender: l1Actor,
recipient: l2Actor,
content: Hash.sha256ToField(
abi.encodeWithSignature("mint_public(bytes32,uint256,address)", _to, _amount, msg.sender)
),
secretHash: _secretHash,
deadline: _deadline,
fee: _fee
});
bytes32 entryKey = inbox.cancelL2Message(message, address(this));
// release the funds to msg.sender (since the content hash (& message key) is derived by hashing the caller,
// we confirm that msg.sender is same as `_canceller` supplied when creating the message)
underlying.transfer(msg.sender, _amount);
return entryKey;
}
}
5 changes: 2 additions & 3 deletions noir-projects/aztec-nr/aztec/src/context/private_context.nr
Original file line number Diff line number Diff line change
Expand Up @@ -28,7 +28,7 @@ use dep::protocol_types::{
},
contrakt::{storage_read::StorageRead, storage_update_request::StorageUpdateRequest},
grumpkin_private_key::GrumpkinPrivateKey, hash::hash_args, header::Header,
messaging::l2_to_l1_message::L2ToL1Message, utils::reader::Reader
messaging::l2_to_l1_message::L2ToL1Message, utils::reader::Reader, traits::is_empty
};
use dep::std::option::Option;

Expand Down Expand Up @@ -71,8 +71,7 @@ impl PrivateContext {
pub fn new(inputs: PrivateContextInputs, args_hash: Field) -> PrivateContext {
let side_effect_counter = inputs.call_context.start_side_effect_counter;
let mut min_revertible_side_effect_counter = 0;
// Note. The side effect counter is 2 when this is the initial call
if (side_effect_counter == 2) {
if is_empty(inputs.call_context.msg_sender) {
min_revertible_side_effect_counter = side_effect_counter;
}
PrivateContext {
Expand Down
4 changes: 2 additions & 2 deletions noir-projects/aztec-nr/aztec/src/state_vars/singleton.nr
Original file line number Diff line number Diff line change
Expand Up @@ -31,9 +31,9 @@ impl<Note> Singleton<Note> {
// When this initialization nullifier is emitted, an observer could do a dictionary or rainbow attack to learn the preimage of this nullifier to deduce the storage slot and contract address.
// For some applications, leaking the details that a particular state variable of a particular contract has been initialized will be unacceptable.
// Under such circumstances, such application developers might wish to _not_ use this state variable type.
// This is especially dangerous for initial assignment to elements of a `Map<AztecAddress, Singleton>` type (for example), because the storage slot often also identifies an actor. e.g.
// This is especially dangerous for initial assignment to elements of a `Map<AztecAddress, Singleton>` type (for example), because the storage slot often also identifies an actor. e.g.
// the initial assignment to `my_map.at(msg.sender)` will leak: `msg.sender`, the fact that an element of `my_map` was assigned-to for the first time, and the contract_address.
// Note: subsequent nullification of this state variable, via the `replace` method will not be leaky, if the `compute_nullifier()` method of the underlying note is designed to ensure privacy.
// Note: subsequent nullification of this state variable, via the `replace` method will not be leaky, if the `compute_nullifier()` method of the underlying note is designed to ensure privacy.
// For example, if the `compute_nullifier()` method injects the secret key of a note owner into the computed nullifier's preimage.
pub fn compute_initialization_nullifier(self) -> Field {
pedersen_hash(
Expand Down
2 changes: 2 additions & 0 deletions noir-projects/noir-contracts/Nargo.toml
Original file line number Diff line number Diff line change
@@ -1,6 +1,8 @@
[workspace]
members = [
"contracts/app_subscription_contract",
"contracts/avm_test_contract",
"contracts/banana_fpc_contract",
"contracts/benchmarking_contract",
"contracts/card_game_contract",
"contracts/child_contract",
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
[package]
name = "app_subscription_contract"
authors = [""]
compiler_version = ">=0.18.0"
type = "contract"

[dependencies]
aztec = { path = "../../../aztec-nr/aztec" }
authwit = { path = "../../../aztec-nr/authwit" }
Original file line number Diff line number Diff line change
@@ -0,0 +1,84 @@
use dep::aztec::context::PrivateContext;
use dep::aztec::protocol_types::{
constants::GENERATOR_INDEX__SIGNATURE_PAYLOAD, hash::pedersen_hash, traits::{Hash, Serialize},
address::AztecAddress
};

use dep::authwit::entrypoint::function_call::{FunctionCall, FUNCTION_CALL_SIZE_IN_BYTES};

// FUNCTION_CALL_SIZE * DAPP_MAX_CALLS + 1
global DAPP_PAYLOAD_SIZE: Field = 5;
// FUNCTION_CALL_SIZE_IN_BYTES * DAPP_MAX_CALLS + 32
global DAPP_PAYLOAD_SIZE_IN_BYTES: Field = 129;

global DAPP_MAX_CALLS: Field = 1;

// Note: If you change the following struct you have to update default_entrypoint.ts
// docs:start:app-payload-struct
struct DAppPayload {
function_calls: [FunctionCall; DAPP_MAX_CALLS],
nonce: Field,
}
// docs:end:app-payload-struct

impl Serialize<DAPP_PAYLOAD_SIZE> for DAppPayload {
// Serializes the entrypoint struct
fn serialize(self) -> [Field; DAPP_PAYLOAD_SIZE] {
let mut fields: BoundedVec<Field, DAPP_PAYLOAD_SIZE> = BoundedVec::new(0);
for call in self.function_calls {
fields.extend_from_array(call.serialize());
}
fields.push(self.nonce);
fields.storage
}
}

impl Hash for DAppPayload {
fn hash(self) -> Field {
pedersen_hash(
self.serialize(),
GENERATOR_INDEX__SIGNATURE_PAYLOAD
)
}
}

impl DAppPayload {
// Serializes the payload as an array of bytes. Useful for hashing with sha256.
fn to_be_bytes(self) -> [u8; DAPP_PAYLOAD_SIZE_IN_BYTES] {
let mut bytes: BoundedVec<u8, DAPP_PAYLOAD_SIZE_IN_BYTES> = BoundedVec::new(0);

for i in 0..DAPP_MAX_CALLS {
bytes.extend_from_array(self.function_calls[i].to_be_bytes());
}
bytes.extend_from_array(self.nonce.to_be_bytes(32));

bytes.storage
}

// Executes all private and public calls
// docs:start:entrypoint-execute-calls
fn execute_calls(self, context: &mut PrivateContext, target_address: AztecAddress) {
for call in self.function_calls {
// whitelist the calls that the user can do only go to the expected Dapp contract
assert(call.target_address == target_address);
if call.is_public {
context.call_public_function_with_packed_args(
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
);
}
}
}
// docs:end:entrypoint-execute-calls
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,152 @@
mod subscription_note;
mod dapp_payload;

contract AppSubscriptionContract {
use dep::std;
use dep::std::option::Option;
use crate::dapp_payload::DAppPayload;

use dep::aztec::protocol_types::{address::AztecAddress, abis::function_selector::FunctionSelector};

use dep::aztec::{
context::{PrivateContext, Context}, note::{note_header::NoteHeader, utils as note_utils},
oracle::get_public_key::get_public_key,
state_vars::{map::Map, singleton::Singleton, public_state::PublicState, stable_public_state::StablePublicState}
};
use dep::authwit::{account::AccountActions, auth_witness::get_auth_witness, auth::assert_current_call_valid_authwit};

use crate::subscription_note::{SubscriptionNote, SUBSCRIPTION_NOTE_LEN};

struct Storage {
// todo change this to immutable singletons because it's only needed in private
target_address: StablePublicState<AztecAddress>,
subscription_token_address: StablePublicState<AztecAddress>,
subscription_recipient_address: StablePublicState<AztecAddress>,
subscription_price: StablePublicState<Field>,
subscriptions: Map<AztecAddress, Singleton<SubscriptionNote>>,
gas_token_address: StablePublicState<AztecAddress>,
}

global SUBSCRIPTION_DURATION_IN_BLOCKS = 5;
global SUBSCRIPTION_TXS = 5;
// global GAS_TOKEN_ADDRESS = AztecAddress::from_field(0x08c0e8041f92758ca49ccb62a77318b46090019d380552ddaec5cd0b54804636);

// Constructs the contract
#[aztec(private)]
fn constructor(
target_address: AztecAddress,
subscription_recipient_address: AztecAddress,
subscription_token_address: AztecAddress,
subscription_price: Field,
gas_token_address: AztecAddress
) {
context.call_public_function(
context.this_address(),
FunctionSelector::from_signature("init((Field),(Field),(Field),Field,(Field))"),
[
target_address.to_field(), subscription_token_address.to_field(), subscription_recipient_address.to_field(), subscription_price, gas_token_address.to_field()
]
);
}

#[aztec(private)]
fn entrypoint(payload: pub DAppPayload, user_address: pub AztecAddress) {
assert(context.msg_sender().to_field() == 0);
assert_current_call_valid_authwit(&mut context, user_address);

let mut note = storage.subscriptions.at(user_address).get_note(false);
assert(note.remaining_txs as u64 > 0, "you're out of txs");

note.remaining_txs -= 1;
storage.subscriptions.at(user_address).replace(&mut note, true);

context.call_public_function(
storage.gas_token_address.read_private(),
FunctionSelector::from_signature("check_balance(Field)"),
// max gas limit in Gas tokens
[42]
);

context.call_public_function(
storage.gas_token_address.read_private(),
FunctionSelector::from_signature("pay_fee(Field)"),
[42]
);

context.capture_min_revertible_side_effect_counter();

context.call_public_function(
context.this_address(),
FunctionSelector::from_signature("assert_not_expired(Field)"),
[note.expiry_block_number]
);

payload.execute_calls(&mut context, storage.target_address.read_private());
}

#[aztec(public)]
internal fn init(
target_address: AztecAddress,
subscription_token_address: AztecAddress,
subscription_recipient_address: AztecAddress,
subscription_price: Field,
gas_token_address: AztecAddress
) {
storage.target_address.initialize(target_address);
storage.subscription_token_address.initialize(subscription_token_address);
storage.subscription_recipient_address.initialize(subscription_recipient_address);
storage.subscription_price.initialize(subscription_price);
storage.gas_token_address.initialize(gas_token_address);
}

#[aztec(public)]
internal fn assert_not_expired(expiry_block_number: Field) {
assert((context.block_number()) as u64 < expiry_block_number as u64);
}

#[aztec(public)]
internal fn assert_block_number(expiry_block_number: Field) {
assert(
(context.block_number() + SUBSCRIPTION_DURATION_IN_BLOCKS) as u64
>= expiry_block_number as u64
);
}

#[aztec(private)]
fn subscribe(
subscriber_address: AztecAddress,
nonce: Field,
expiry_block_number: Field,
tx_count: Field
) {
assert(tx_count as u64 <= SUBSCRIPTION_TXS as u64);

let _ = context.call_private_function(
storage.subscription_token_address.read_private(),
FunctionSelector::from_signature("transfer((Field),(Field),Field,Field)"),
[
context.msg_sender().to_field(), storage.subscription_recipient_address.read_private().to_field(), storage.subscription_price.read_private(), nonce
]
);

// Assert that the given expiry_block_number < current_block_number + SUBSCRIPTION_DURATION_IN_BLOCKS.
context.call_public_function(
context.this_address(),
FunctionSelector::from_signature("assert_block_number(Field)"),
[expiry_block_number]
);

let mut subscription_note = SubscriptionNote::new(subscriber_address, expiry_block_number, tx_count);
if (!is_initialized(subscriber_address)) {
storage.subscriptions.at(subscriber_address).initialize(&mut subscription_note, true);
} else {
storage.subscriptions.at(subscriber_address).replace(&mut subscription_note, true)
}
}

// Compiler bug workaround. You can't call an unconstrained function in another module, unless its from an
// unconstained function in your module.
unconstrained fn is_initialized(subscriber_address: AztecAddress) -> pub bool {
storage.subscriptions.at(subscriber_address).is_initialized()
}
}
Loading

0 comments on commit 5ae252b

Please sign in to comment.