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
101 changes: 90 additions & 11 deletions l1-contracts/test/portals/TokenPortal.sol
Original file line number Diff line number Diff line change
Expand Up @@ -23,29 +23,28 @@ contract TokenPortal {
}

/**
* @notice Deposit funds into the portal and adds an L2 message.
* @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 _deadline - The timestamp after which the entry can be cancelled
* @param _secretHash - The hash of the secret consumable message
* @param _canceller - The address that can cancel the L1 to L2 message.
* @param _secretHash - The hash of the secret consumable message. The hash should be 254 bits (so it can fit in a Field element)
* @param _canceller - The address that can cancel the L1 to L2 message
* @return The key of the entry in the Inbox
*/
function depositToAztec(
function depositToAztecPublic(
bytes32 _to,
uint256 _amount,
uint32 _deadline,
bytes32 _secretHash,
address _canceller
) external payable returns (bytes32) {
// Preamble
// @todo: (issue #624) handle different versions
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(uint256,bytes32,address)", _amount, _to, _canceller)
abi.encodeWithSignature("mint_public(uint256,bytes32,address)", _amount, _to, _canceller)
);

// Hold the tokens in the portal
Expand All @@ -55,9 +54,48 @@ contract TokenPortal {
return inbox.sendL2Message{value: msg.value}(actor, _deadline, contentHash, _secretHash);
}

/**
* @notice Deposit funds into the portal and adds an L2 message which can only be consumed privately on Aztec
* @param _amount - The amount to deposit
* @param _deadline - The timestamp after which the entry can be cancelled
* @param _secretHashForL2MessageConsumption - The hash of the secret consumable L1 to L2 message. The hash should be 254 bits (so it can fit in a Field element)
* @param _secretHashForL2MessageConsumption - The hash of the secret to redeem minted notes privately on Aztec. The hash should be 254 bits (so it can fit in a Field element)
* @param _canceller - The address that can cancel the L1 to L2 message
* @return The key of the entry in the Inbox
*/
function depositToAztecPrivate(
uint256 _amount,
uint32 _deadline,
bytes32 _secretHashForL2MessageConsumption,
bytes32 _secretHashForRedeemingMintedNotes,
address _canceller
) 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_private(uint256,bytes32,address)",
_amount,
_secretHashForRedeemingMintedNotes,
_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, _secretHashForL2MessageConsumption
);
}

// docs:start:token_portal_cancel
/**
* @notice Cancel the L1 to L2 message
* @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
Expand All @@ -66,22 +104,21 @@ contract TokenPortal {
* @param _fee - The fee paid to the sequencer
* @return The key of the entry in the Inbox
*/
function cancelL1ToAztecMessage(
function cancelL1ToAztecMessagePublic(
bytes32 _to,
uint256 _amount,
uint32 _deadline,
bytes32 _secretHash,
uint64 _fee
) external returns (bytes32) {
// @todo: (issue #624) handle different versions
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(uint256,bytes32,address)", _amount, _to, msg.sender)
abi.encodeWithSignature("mint_public(uint256,bytes32,address)", _amount, _to, msg.sender)
),
secretHash: _secretHash,
deadline: _deadline,
Expand All @@ -93,6 +130,49 @@ contract TokenPortal {
underlying.transfer(msg.sender, _amount);
return entryKey;
}

/**
* @notice Cancel a private depositToAztec L1 to L2 message
* @dev only callable by the `canceller` of the message
* @param _amount - The amount to deposit per the original message
* @param _deadline - The timestamp after which the entry can be cancelled
* @param _secretHashForL2MessageConsumption - The hash of the secret consumable L1 to L2 message
* @param _secretHashForL2MessageConsumption - The hash of the secret to redeem minted notes privately on Aztec
* @param _fee - The fee paid to the sequencer
* @return The key of the entry in the Inbox
*/
function cancelL1ToAztecMessagePrivate(
uint256 _amount,
uint32 _deadline,
bytes32 _secretHashForL2MessageConsumption,
bytes32 _secretHashForRedeemingMintedNotes,
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_private(uint256,bytes32,address)",
_amount,
_secretHashForRedeemingMintedNotes,
msg.sender
)
),
secretHash: _secretHashForL2MessageConsumption,
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;
}

// docs:end:token_portal_cancel

// docs:start:token_portal_withdraw
Expand Down Expand Up @@ -122,7 +202,6 @@ contract TokenPortal {
)
});

// @todo: (issue #624) handle different versions
bytes32 entryKey = registry.getOutbox().consume(message);

underlying.transfer(_recipient, _amount);
Expand Down
161 changes: 147 additions & 14 deletions l1-contracts/test/portals/TokenPortal.t.sol
Original file line number Diff line number Diff line change
Expand Up @@ -48,7 +48,12 @@ contract TokenPortalTest is Test {
bytes32 internal to = bytes32(0x2d749407d8c364537cdeb799c1574929cb22ff1ece2b96d2a1c6fa287a0e0171);
uint256 internal amount = 100;
uint256 internal mintAmount = 1 ether;
bytes32 internal secretHash = 0x147e4fec49805c924e28150fc4b36824679bc17ecb1d7d9f6a9effb7fde6b6a0;
// this hash is just a random 32 byte string
bytes32 internal secretHashForL2MessageConsumption =
0x147e4fec49805c924e28150fc4b36824679bc17ecb1d7d9f6a9effb7fde6b6a0;
// this hash is just a random 32 byte string
bytes32 internal secretHashForRedeemingMintedNotes =
0x157e4fec49805c924e28150fc4b36824679bc17ecb1d7d9f6a9effb7fde6b6a0;
uint64 internal bid = 1 ether;

// params for withdraw:
Expand All @@ -71,7 +76,7 @@ contract TokenPortalTest is Test {
vm.deal(address(this), 100 ether);
}

function _createExpectedL1ToL2Message(address _canceller)
function _createExpectedMintPrivateL1ToL2Message(address _canceller)
internal
view
returns (DataStructures.L1ToL2Msg memory)
Expand All @@ -80,24 +85,90 @@ contract TokenPortalTest is Test {
sender: DataStructures.L1Actor(address(tokenPortal), block.chainid),
recipient: DataStructures.L2Actor(l2TokenAddress, 1),
content: Hash.sha256ToField(
abi.encodeWithSignature("mint(uint256,bytes32,address)", amount, to, _canceller)
abi.encodeWithSignature(
"mint_private(uint256,bytes32,address)",
amount,
secretHashForRedeemingMintedNotes,
_canceller
)
),
secretHash: secretHash,
secretHash: secretHashForL2MessageConsumption,
deadline: deadline,
fee: bid
});
}

function testDeposit() public returns (bytes32) {
function _createExpectedMintPublicL1ToL2Message(address _canceller)
internal
view
returns (DataStructures.L1ToL2Msg memory)
{
return DataStructures.L1ToL2Msg({
sender: DataStructures.L1Actor(address(tokenPortal), block.chainid),
recipient: DataStructures.L2Actor(l2TokenAddress, 1),
content: Hash.sha256ToField(
abi.encodeWithSignature("mint_public(uint256,bytes32,address)", amount, to, _canceller)
),
secretHash: secretHashForL2MessageConsumption,
deadline: deadline,
fee: bid
});
}

function testDepositPrivate() public returns (bytes32) {
// mint token and approve to the portal
portalERC20.mint(address(this), mintAmount);
portalERC20.approve(address(tokenPortal), mintAmount);

// Check for the expected message
DataStructures.L1ToL2Msg memory expectedMessage =
_createExpectedMintPrivateL1ToL2Message(address(this));
bytes32 expectedEntryKey = inbox.computeEntryKey(expectedMessage);

// Check the event was emitted
vm.expectEmit(true, true, true, true);
// event we expect
emit MessageAdded(
expectedEntryKey,
expectedMessage.sender.actor,
expectedMessage.recipient.actor,
expectedMessage.sender.chainId,
expectedMessage.recipient.version,
expectedMessage.deadline,
expectedMessage.fee,
expectedMessage.content,
expectedMessage.secretHash
);

// Perform op
bytes32 entryKey = tokenPortal.depositToAztecPrivate{value: bid}(
amount,
deadline,
secretHashForL2MessageConsumption,
secretHashForRedeemingMintedNotes,
address(this)
);

assertEq(entryKey, expectedEntryKey, "returned entry key and calculated entryKey should match");

// Check that the message is in the inbox
DataStructures.Entry memory entry = inbox.get(entryKey);
assertEq(entry.count, 1);

return entryKey;
}

function testDepositPublic() public returns (bytes32) {
// mint token and approve to the portal
portalERC20.mint(address(this), mintAmount);
portalERC20.approve(address(tokenPortal), mintAmount);

// Check for the expected message
DataStructures.L1ToL2Msg memory expectedMessage = _createExpectedL1ToL2Message(address(this));
DataStructures.L1ToL2Msg memory expectedMessage =
_createExpectedMintPublicL1ToL2Message(address(this));
bytes32 expectedEntryKey = inbox.computeEntryKey(expectedMessage);

// Check the even was emitted
// Check the event was emitted
vm.expectEmit(true, true, true, true);
// event we expect
emit MessageAdded(
Expand All @@ -113,8 +184,9 @@ contract TokenPortalTest is Test {
);

// Perform op
bytes32 entryKey =
tokenPortal.depositToAztec{value: bid}(to, amount, deadline, secretHash, address(this));
bytes32 entryKey = tokenPortal.depositToAztecPublic{value: bid}(
to, amount, deadline, secretHashForL2MessageConsumption, address(this)
);

assertEq(entryKey, expectedEntryKey, "returned entry key and calculated entryKey should match");

Expand All @@ -125,28 +197,89 @@ contract TokenPortalTest is Test {
return entryKey;
}

function testCancel() public {
bytes32 expectedEntryKey = testDeposit();
function testCancelPublic() public {
bytes32 expectedEntryKey = testDepositPublic();
// now cancel the message - move time forward (post deadline)
vm.warp(deadline + 1 days);

// ensure no one else can cancel the message:
vm.startPrank(address(0xdead));
bytes32 expectedWrongEntryKey =
inbox.computeEntryKey(_createExpectedL1ToL2Message(address(0xdead)));
inbox.computeEntryKey(_createExpectedMintPublicL1ToL2Message(address(0xdead)));
vm.expectRevert(
abi.encodeWithSelector(Errors.Inbox__NothingToConsume.selector, expectedWrongEntryKey)
);
tokenPortal.cancelL1ToAztecMessage(to, amount, deadline, secretHash, bid);
tokenPortal.cancelL1ToAztecMessagePublic(
to, amount, deadline, secretHashForL2MessageConsumption, bid
);
vm.stopPrank();

// ensure cant cancel with cancelPrivate (since deposit was public)
expectedWrongEntryKey =
inbox.computeEntryKey(_createExpectedMintPrivateL1ToL2Message(address(this)));
vm.expectRevert(
abi.encodeWithSelector(Errors.Inbox__NothingToConsume.selector, expectedWrongEntryKey)
);
tokenPortal.cancelL1ToAztecMessagePrivate(
amount, deadline, secretHashForL2MessageConsumption, secretHashForRedeemingMintedNotes, bid
);

// actually cancel the message
// check event was emitted
vm.expectEmit(true, false, false, false);
// expected event:
emit L1ToL2MessageCancelled(expectedEntryKey);
// perform op
bytes32 entryKey = tokenPortal.cancelL1ToAztecMessage(to, amount, deadline, secretHash, bid);
bytes32 entryKey = tokenPortal.cancelL1ToAztecMessagePublic(
to, amount, deadline, secretHashForL2MessageConsumption, bid
);

assertEq(entryKey, expectedEntryKey, "returned entry key and calculated entryKey should match");
assertFalse(inbox.contains(entryKey), "entry still in inbox");
assertEq(
portalERC20.balanceOf(address(this)),
mintAmount,
"assets should be transferred back to this contract"
);
assertEq(portalERC20.balanceOf(address(tokenPortal)), 0, "portal should have no assets");
}

function testCancelPrivate() public {
bytes32 expectedEntryKey = testDepositPrivate();
// now cancel the message - move time forward (post deadline)
vm.warp(deadline + 1 days);

// ensure no one else can cancel the message:
vm.startPrank(address(0xdead));
bytes32 expectedWrongEntryKey =
inbox.computeEntryKey(_createExpectedMintPrivateL1ToL2Message(address(0xdead)));
vm.expectRevert(
abi.encodeWithSelector(Errors.Inbox__NothingToConsume.selector, expectedWrongEntryKey)
);
tokenPortal.cancelL1ToAztecMessagePrivate(
amount, deadline, secretHashForL2MessageConsumption, secretHashForRedeemingMintedNotes, bid
);
vm.stopPrank();

// ensure cant cancel with cancelPublic (since deposit was private)
expectedWrongEntryKey =
inbox.computeEntryKey(_createExpectedMintPublicL1ToL2Message(address(this)));
vm.expectRevert(
abi.encodeWithSelector(Errors.Inbox__NothingToConsume.selector, expectedWrongEntryKey)
);
tokenPortal.cancelL1ToAztecMessagePublic(
to, amount, deadline, secretHashForL2MessageConsumption, bid
);

// actually cancel the message
// check event was emitted
vm.expectEmit(true, false, false, false);
// expected event:
emit L1ToL2MessageCancelled(expectedEntryKey);
// perform op
bytes32 entryKey = tokenPortal.cancelL1ToAztecMessagePrivate(
amount, deadline, secretHashForL2MessageConsumption, secretHashForRedeemingMintedNotes, bid
);

assertEq(entryKey, expectedEntryKey, "returned entry key and calculated entryKey should match");
assertFalse(inbox.contains(entryKey), "entry still in inbox");
Expand Down
Loading