Skip to content
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
14 changes: 12 additions & 2 deletions l1-contracts/test/portals/TokenPortal.sol
Original file line number Diff line number Diff line change
Expand Up @@ -98,14 +98,24 @@ contract TokenPortal {
* @dev Second part of withdraw, must be initiated from L2 first as it will consume a message from outbox
* @param _amount - The amount to withdraw
* @param _recipient - The address to send the funds to
* @param _withCaller - Flag to use `msg.sender` as caller, otherwise address(0)
* Must match the caller of the message (specified from L2) to consume it.
* @return The key of the entry in the Outbox
*/
function withdraw(uint256 _amount, address _recipient) external returns (bytes32) {
function withdraw(uint256 _amount, address _recipient, bool _withCaller)
external
returns (bytes32)
{
DataStructures.L2ToL1Msg memory message = DataStructures.L2ToL1Msg({
sender: DataStructures.L2Actor(l2TokenAddress, 1),
recipient: DataStructures.L1Actor(address(this), block.chainid),
content: Hash.sha256ToField(
abi.encodeWithSignature("withdraw(uint256,address)", _amount, _recipient)
abi.encodeWithSignature(
"withdraw(uint256,address,address)",
_amount,
_recipient,
_withCaller ? msg.sender : address(0)
)
)
});

Expand Down
99 changes: 78 additions & 21 deletions l1-contracts/test/portals/TokenPortal.t.sol
Original file line number Diff line number Diff line change
Expand Up @@ -40,16 +40,20 @@ contract TokenPortalTest is Test {
Rollup internal rollup;
bytes32 internal l2TokenAddress = bytes32(uint256(0x42));

TokenPortal tokenPortal;
PortalERC20 portalERC20;
TokenPortal internal tokenPortal;
PortalERC20 internal portalERC20;

// input params
uint32 deadline = uint32(block.timestamp + 1 days);
bytes32 to = bytes32(0x2d749407d8c364537cdeb799c1574929cb22ff1ece2b96d2a1c6fa287a0e0171);
uint256 amount = 100;
uint256 mintAmount = 1 ether;
bytes32 secretHash = 0x147e4fec49805c924e28150fc4b36824679bc17ecb1d7d9f6a9effb7fde6b6a0;
uint64 bid = 1 ether;
uint32 internal deadline = uint32(block.timestamp + 1 days);
bytes32 internal to = bytes32(0x2d749407d8c364537cdeb799c1574929cb22ff1ece2b96d2a1c6fa287a0e0171);
uint256 internal amount = 100;
uint256 internal mintAmount = 1 ether;
bytes32 internal secretHash = 0x147e4fec49805c924e28150fc4b36824679bc17ecb1d7d9f6a9effb7fde6b6a0;
uint64 internal bid = 1 ether;

// params for withdraw:
address internal recipient = address(0xdead);
uint256 internal withdrawAmount = 654;

function setUp() public {
registry = new Registry();
Expand Down Expand Up @@ -154,35 +158,88 @@ contract TokenPortalTest is Test {
assertEq(portalERC20.balanceOf(address(tokenPortal)), 0, "portal should have no assets");
}

function testWithdraw() public {
uint256 withdrawAmount = 654;
portalERC20.mint(address(tokenPortal), withdrawAmount);
address _recipient = address(0xdead);
bytes32[] memory entryKeys = new bytes32[](1);
entryKeys[0] = outbox.computeEntryKey(
function _createWithdrawMessageForOutbox(address _designatedCaller)
internal
view
returns (bytes32)
{
bytes32 entryKey = outbox.computeEntryKey(
DataStructures.L2ToL1Msg({
sender: DataStructures.L2Actor({actor: l2TokenAddress, version: 1}),
recipient: DataStructures.L1Actor({actor: address(tokenPortal), chainId: block.chainid}),
content: Hash.sha256ToField(
abi.encodeWithSignature("withdraw(uint256,address)", withdrawAmount, _recipient)
abi.encodeWithSignature(
"withdraw(uint256,address,address)", withdrawAmount, recipient, _designatedCaller
)
)
})
);
return entryKey;
}

function _addWithdrawMessageInOutbox(address _designatedCaller) internal returns (bytes32) {
// send assets to the portal
portalERC20.mint(address(tokenPortal), withdrawAmount);

// Create the message
bytes32[] memory entryKeys = new bytes32[](1);
entryKeys[0] = _createWithdrawMessageForOutbox(_designatedCaller);
// Insert messages into the outbox (impersonating the rollup contract)
vm.prank(address(rollup));
outbox.sendL1Messages(entryKeys);
return entryKeys[0];
}

assertEq(portalERC20.balanceOf(_recipient), 0);
function testAnyoneCanCallWithdrawIfNoDesignatedCaller(address _caller) public {
Comment thread
LHerskind marked this conversation as resolved.
vm.assume(_caller != address(0));
bytes32 expectedEntryKey = _addWithdrawMessageInOutbox(address(0));
assertEq(portalERC20.balanceOf(recipient), 0);

vm.startPrank(_caller);
vm.expectEmit(true, true, true, true);
emit MessageConsumed(entryKeys[0], address(tokenPortal));
bytes32 entryKey = tokenPortal.withdraw(withdrawAmount, _recipient);
emit MessageConsumed(expectedEntryKey, address(tokenPortal));
bytes32 actualEntryKey = tokenPortal.withdraw(withdrawAmount, recipient, false);
assertEq(expectedEntryKey, actualEntryKey);
// Should have received 654 RNA tokens
assertEq(portalERC20.balanceOf(_recipient), withdrawAmount);
assertEq(portalERC20.balanceOf(recipient), withdrawAmount);

// Should not be able to withdraw again
vm.expectRevert(abi.encodeWithSelector(Errors.Outbox__NothingToConsume.selector, entryKey));
tokenPortal.withdraw(withdrawAmount, _recipient);
vm.expectRevert(
abi.encodeWithSelector(Errors.Outbox__NothingToConsume.selector, actualEntryKey)
);
tokenPortal.withdraw(withdrawAmount, recipient, false);
vm.stopPrank();
}

function testWithdrawWithDesignatedCallerFailsForOtherCallers(address _caller) public {
vm.assume(_caller != address(this));
// add message with caller as this address
_addWithdrawMessageInOutbox(address(this));

vm.startPrank(_caller);
Comment thread
LHerskind marked this conversation as resolved.
bytes32 entryKeyPortalChecksAgainst = _createWithdrawMessageForOutbox(_caller);
vm.expectRevert(
abi.encodeWithSelector(Errors.Outbox__NothingToConsume.selector, entryKeyPortalChecksAgainst)
);
tokenPortal.withdraw(withdrawAmount, recipient, true);

entryKeyPortalChecksAgainst = _createWithdrawMessageForOutbox(address(0));
vm.expectRevert(
abi.encodeWithSelector(Errors.Outbox__NothingToConsume.selector, entryKeyPortalChecksAgainst)
);
tokenPortal.withdraw(withdrawAmount, recipient, false);
vm.stopPrank();
}

function testWithdrawWithDesignatedCallerSucceedsForDesignatedCaller() public {
// add message with caller as this address
bytes32 expectedEntryKey = _addWithdrawMessageInOutbox(address(this));

vm.expectEmit(true, true, true, true);
emit MessageConsumed(expectedEntryKey, address(tokenPortal));
bytes32 actualEntryKey = tokenPortal.withdraw(withdrawAmount, recipient, true);
assertEq(expectedEntryKey, actualEntryKey);
// Should have received 654 RNA tokens
assertEq(portalERC20.balanceOf(recipient), withdrawAmount);
}
}
3 changes: 1 addition & 2 deletions l1-contracts/test/portals/UniswapPortal.sol
Original file line number Diff line number Diff line change
Expand Up @@ -61,8 +61,7 @@ contract UniswapPortal {
IERC20 outputAsset = TokenPortal(_outputTokenPortal).underlying();

// Withdraw the input asset from the portal
// todo: add `true` when using designated caller
TokenPortal(_inputTokenPortal).withdraw(_inAmount, address(this));
TokenPortal(_inputTokenPortal).withdraw(_inAmount, address(this), true);

{
// prevent stack too deep errors
Expand Down
4 changes: 3 additions & 1 deletion l1-contracts/test/portals/UniswapPortal.t.sol
Original file line number Diff line number Diff line change
Expand Up @@ -72,7 +72,9 @@ contract UniswapPortalTest is Test {
sender: DataStructures.L2Actor(l2TokenAddress, 1),
recipient: DataStructures.L1Actor(address(daiTokenPortal), block.chainid),
content: Hash.sha256ToField(
abi.encodeWithSignature("withdraw(uint256,address)", amount, _recipient)
abi.encodeWithSignature(
"withdraw(uint256,address,address)", amount, _recipient, address(uniswapPortal)
)
)
});
entryKey = outbox.computeEntryKey(message);
Expand Down
8 changes: 5 additions & 3 deletions yarn-project/end-to-end/src/cross_chain/test_harness.ts
Original file line number Diff line number Diff line change
Expand Up @@ -158,15 +158,16 @@ export class CrossChainTestHarness {
expect(transferReceipt.status).toBe(TxStatus.MINED);
}

async checkEntryIsNotInOutbox(withdrawAmount: bigint): Promise<Fr> {
async checkEntryIsNotInOutbox(withdrawAmount: bigint, callerOnL1: EthAddress = EthAddress.ZERO): Promise<Fr> {
this.logger('Ensure that the entry is not in outbox yet');
const contractInfo = await this.aztecNode.getContractInfo(this.l2Contract.address);
// 0x00f714ce, selector for "withdraw(uint256,address)"
// 0xb460af94, selector for "withdraw(uint256,address,address)"
const content = sha256ToField(
Buffer.concat([
Buffer.from([0x00, 0xf7, 0x14, 0xce]),
Buffer.from([0xb4, 0x60, 0xaf, 0x94]),
toBufferBE(withdrawAmount, 32),
this.ethAccount.toBuffer32(),
callerOnL1.toBuffer32(),
]),
);
const entryKey = sha256ToField(
Expand All @@ -189,6 +190,7 @@ export class CrossChainTestHarness {
const { request: withdrawRequest, result: withdrawEntryKey } = await this.tokenPortal.simulate.withdraw([
withdrawAmount,
this.ethAccount.toString(),
false,
]);

expect(withdrawEntryKey).toBe(entryKey.toString(true));
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -80,7 +80,9 @@ describe('e2e_cross_chain_messaging', () => {

const withdrawFundsFromAztec = async (withdrawAmount: bigint) => {
logger('Send L2 tx to withdraw funds');
const withdrawTx = l2Contract.methods.withdraw(withdrawAmount, ownerPub, ethAccount).send({ from: ownerAddress });
const withdrawTx = l2Contract.methods
.withdraw(withdrawAmount, ownerPub, ethAccount, EthAddress.ZERO.toField())
.send({ from: ownerAddress });

await withdrawTx.isMined(0, 0.1);
const withdrawReceipt = await withdrawTx.getReceipt();
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -74,7 +74,7 @@ describe('e2e_public_cross_chain_messaging', () => {
const withdrawFundsFromAztec = async (withdrawAmount: bigint) => {
logger('Send L2 tx to withdraw funds');
const withdrawTx = l2Contract.methods
.withdrawPublic(withdrawAmount, ethAccount.toField())
.withdrawPublic(withdrawAmount, ethAccount.toField(), EthAddress.ZERO.toField())
.send({ from: ownerAddress });

await withdrawTx.isMined(0, 0.1);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -96,6 +96,7 @@ contract NonNativeToken {
amount: pub Field,
sender: pub Point,
recipient: pub Field, // ethereum address in the field
callerOnL1: pub Field, // ethereum address that can call this function on the L1 portal (0x0 if anyone can call)
) -> pub [Field; dep::aztec3::abi::PUBLIC_INPUTS_LENGTH] {
let mut initialContext = PrivateFunctionContext::new();
initialContext.args = initialContext.args.push_array([amount, sender.x, sender.y, recipient]);
Expand Down Expand Up @@ -123,7 +124,7 @@ contract NonNativeToken {
context = sender_balance.insert(context, change_note);
constrain emit_encrypted_log(inputs.call_context.storage_contract_address, sender_balance.storage_slot, change_note.owner, change_note) == 0;

let content = _get_withdraw_content_hash(amount, recipient);
let content = _get_withdraw_content_hash(amount, recipient, callerOnL1);

context = context.message_portal(content);

Expand Down Expand Up @@ -165,6 +166,7 @@ contract NonNativeToken {
//*********************************/
amount: Field,
recipient: Field,
callerOnL1: Field, // ethereum address that can call this function on the L1 portal (0x0 if anyone can call)
_padding: [Field; dep::aztec3::abi::MAX_ARGS - 2]
) {

Expand All @@ -178,7 +180,7 @@ contract NonNativeToken {
}
// TODO: Revert if there is not enough balance

let content = _get_withdraw_content_hash(amount, recipient);
let content = _get_withdraw_content_hash(amount, recipient, callerOnL1);

// Emit the l2 to l1 message
create_l2_to_l1_message(content);
Expand Down Expand Up @@ -425,20 +427,21 @@ contract NonNativeToken {
content_hash
}

fn _get_withdraw_content_hash(amount: Field, recipient: Field) -> pub Field {
fn _get_withdraw_content_hash(amount: Field, recipient: Field, callerOnL1: Field) -> pub Field {
// Compute the content hash
// Compute sha256(selector || amount || recipient)
// then convert to a single field element
// add that to the l2 to l1 messages
let mut hash_bytes: [u8; 68] = [0; 68];
let mut hash_bytes: [u8; 100] = [0; 100];
let amount_bytes = amount.to_be_bytes(32);
let recipient_bytes = recipient.to_be_bytes(32);
let callerOnL1_bytes = callerOnL1.to_be_bytes(32);

// 0x00f714ce, selector for "withdraw(uint256,address)"
hash_bytes[0] = 0x00;
hash_bytes[1] = 0xf7;
hash_bytes[2] = 0x14;
hash_bytes[3] = 0xce;
// 0xb460af94, selector for "withdraw(uint256,address,address)"
hash_bytes[0] = 0xb4;
hash_bytes[1] = 0x60;
hash_bytes[2] = 0xaf;
hash_bytes[3] = 0x94;

// Unroll loops because otherwise takes forever to compile
// for i in range(32):
Expand Down Expand Up @@ -511,6 +514,39 @@ contract NonNativeToken {
hash_bytes[34 + 32] = recipient_bytes[30];
hash_bytes[35 + 32] = recipient_bytes[31];

hash_bytes[4 + 64] = callerOnL1_bytes[0];
hash_bytes[5 + 64] = callerOnL1_bytes[1];
hash_bytes[6 + 64] = callerOnL1_bytes[2];
hash_bytes[7 + 64] = callerOnL1_bytes[3];
hash_bytes[8 + 64] = callerOnL1_bytes[4];
hash_bytes[9 + 64] = callerOnL1_bytes[5];
hash_bytes[10 + 64] = callerOnL1_bytes[6];
hash_bytes[11 + 64] = callerOnL1_bytes[7];
hash_bytes[12 + 64] = callerOnL1_bytes[8];
hash_bytes[13 + 64] = callerOnL1_bytes[9];
hash_bytes[14 + 64] = callerOnL1_bytes[10];
hash_bytes[15 + 64] = callerOnL1_bytes[11];
hash_bytes[16 + 64] = callerOnL1_bytes[12];
hash_bytes[17 + 64] = callerOnL1_bytes[13];
hash_bytes[18 + 64] = callerOnL1_bytes[14];
hash_bytes[19 + 64] = callerOnL1_bytes[15];
hash_bytes[20 + 64] = callerOnL1_bytes[16];
hash_bytes[21 + 64] = callerOnL1_bytes[17];
hash_bytes[22 + 64] = callerOnL1_bytes[18];
hash_bytes[23 + 64] = callerOnL1_bytes[19];
hash_bytes[24 + 64] = callerOnL1_bytes[20];
hash_bytes[25 + 64] = callerOnL1_bytes[21];
hash_bytes[26 + 64] = callerOnL1_bytes[22];
hash_bytes[27 + 64] = callerOnL1_bytes[23];
hash_bytes[28 + 64] = callerOnL1_bytes[24];
hash_bytes[29 + 64] = callerOnL1_bytes[25];
hash_bytes[30 + 64] = callerOnL1_bytes[26];
hash_bytes[31 + 64] = callerOnL1_bytes[27];
hash_bytes[32 + 64] = callerOnL1_bytes[28];
hash_bytes[33 + 64] = callerOnL1_bytes[29];
hash_bytes[34 + 64] = callerOnL1_bytes[30];
hash_bytes[35 + 64] = callerOnL1_bytes[31];

let content_sha256 = dep::std::hash::sha256(hash_bytes);

// Convert the content_sha256 to a field element
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -31,7 +31,7 @@ contract Uniswap {
uniswapFeeTier: pub Field, // which uniswap tier to use (eg 3000 for 0.3% fee)
outputAsset: pub Field,
outputAssetPortalAddress: pub Field, // l1 portal of output asset
mminimumOutputAmount: pub Field, // minimum output amount to receive (slippage protection for the swap)
minimumOutputAmount: pub Field, // minimum output amount to receive (slippage protection for the swap)
sender: pub Point,
recipient: pub Field, // recevier address of output asset after the swap
secretHash: pub Field, // for when l1 uniswap portal inserts the message to consume output assets on L2
Expand All @@ -49,7 +49,7 @@ contract Uniswap {
uniswapFeeTier,
outputAsset,
outputAssetPortalAddress,
mminimumOutputAmount,
minimumOutputAmount,
sender.x,
sender.y,
recipient,
Expand All @@ -59,12 +59,15 @@ contract Uniswap {
l1UniswapPortal,
]);

// inputAsset.withdraw(inputAmount, l1UniswapPortal)
// inputAsset.withdraw(inputAmount, sender, recipient=l1UniswapPortal, callerOnL1=l1UniswapPortal)
// only uniswap portal can call this (done to safegaurd ordering of message consumption)
// ref: https://docs.aztec.network/aztec/how-it-works/l1-l2-messaging#designated-caller
let mut args = [0; dep::aztec3::abi::MAX_ARGS];
args[0] = inputAmount;
args[1] = sender.x;
args[2] = sender.y;
args[3] = l1UniswapPortal;
args[4] = l1UniswapPortal;
let (callStackItem, mut context) = PrivateCallStackItem::call(inputAsset, withdrawFnSelector, args, initialContext);

let result = callStackItem.public_inputs.return_values[0];
Expand All @@ -76,7 +79,7 @@ contract Uniswap {
inputAmount,
uniswapFeeTier,
outputAssetPortalAddress,
mminimumOutputAmount,
minimumOutputAmount,
recipient,
secretHash,
deadlineForL1ToL2Message,
Expand Down

Large diffs are not rendered by default.

Large diffs are not rendered by default.