-
Notifications
You must be signed in to change notification settings - Fork 270
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
- Loading branch information
1 parent
c5eeb4c
commit 5ae252b
Showing
54 changed files
with
104,055 additions
and
692 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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; | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
9 changes: 9 additions & 0 deletions
9
noir-projects/noir-contracts/contracts/app_subscription_contract/Nargo.toml
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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" } |
84 changes: 84 additions & 0 deletions
84
noir-projects/noir-contracts/contracts/app_subscription_contract/src/dapp_payload.nr
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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 | ||
} |
152 changes: 152 additions & 0 deletions
152
noir-projects/noir-contracts/contracts/app_subscription_contract/src/main.nr
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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() | ||
} | ||
} |
Oops, something went wrong.