Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

add NFT Trading bridge #305

Open
wants to merge 29 commits into
base: master
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
29 commits
Select commit Hold shift + click to select a range
896792f
add specs
liusanchuan Jan 5, 2023
169e03a
copy templete
liusanchuan Jan 5, 2023
caab3a2
learn test
liusanchuan Jan 5, 2023
a26f1bc
update specs flow
liusanchuan Jan 6, 2023
1e7e3a5
add NFT Transfer templete
liusanchuan Jan 6, 2023
9ecc60c
Merge branch 'AztecProtocol:master' into nft-trading-bridge
liusanchuan Jan 31, 2023
8244828
add specs
liusanchuan Jan 5, 2023
34a70be
copy templete
liusanchuan Jan 5, 2023
e36dc2b
learn test
liusanchuan Jan 5, 2023
28d8124
update specs flow
liusanchuan Jan 6, 2023
8582acd
add NFT Transfer templete
liusanchuan Jan 6, 2023
2b3e198
style: updated formatting (#306)
benesjan Jan 9, 2023
f90f80a
Lh/deployments update (#294)
LHerskind Jan 9, 2023
5900677
nits: capitalize env vars (#300)
Maddiaa0 Jan 9, 2023
c7df8f8
Subsidy log script (#293)
benesjan Jan 9, 2023
a25f80c
fix: handle case on devnet where basefee is 0
LHerskind Jan 10, 2023
56ab87e
fix: cleanup + add a test
LHerskind Jan 10, 2023
24d672a
fix: pin BiDCA tests to block 14950000 (#310)
LHerskind Jan 18, 2023
e7f4b85
chore: removed `read()` and updates readme. (#312)
LHerskind Jan 19, 2023
8a143dd
Fixed small typos in the ElementBridge contract (#314)
cashd Jan 24, 2023
693e65d
Merge branch 'nft-trading-bridge' of https://github.com/liusanchuan/a…
liusanchuan Jan 31, 2023
bff4780
add specs
liusanchuan Jan 5, 2023
269a6e4
copy templete
liusanchuan Jan 5, 2023
dc9c463
learn test
liusanchuan Jan 5, 2023
a461f25
update specs flow
liusanchuan Jan 6, 2023
bd37725
add NFT Transfer templete
liusanchuan Jan 6, 2023
7fa9129
Merge branch 'nft-trading-bridge' of https://github.com/liusanchuan/a…
liusanchuan Jan 31, 2023
ec1e206
Merge branch 'nft-trading-bridge' of https://github.com/liusanchuan/a…
liusanchuan Jan 31, 2023
9d0539f
Merge branch 'nft-trading-bridge' of https://github.com/liusanchuan/a…
liusanchuan Jan 31, 2023
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
3 changes: 2 additions & 1 deletion .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -3,4 +3,5 @@ out/
node_modules/
yarn-error.log
typechain-types/
broadcast/
broadcast/
compose-dev.yaml
54 changes: 54 additions & 0 deletions specs/bridges/nft_trading/readme.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,54 @@
# Spec for NFT Trading Bridge

## What does the bridge do? Why build it?

This bridge enables user to trading their NFT on the established Layer 1 NFT marketplaces, aka [OpenSea](https://opensea.io/), where users can list and purchase NFTs from Aztec L2 without revealing their L1 identities.
In this way, the owner of one NFT owner is in private with the power of Aztec's zero knowledge rollup. So this bridge can enhance the secret characterist of Layer 1 user.


## What protocol(s) does the bridge interact with ?
The bridge interacts with [OpenSea](https://opensea.io/).

## What is the flow of the bridge?

The NFT bridge will design as a Wrap Erc721 Contract, that means everytime when the contract received a NFT, it will mint a relevant Wrapped NFT to this user, This Wrapped NFT can be transfer to Aztec L2, also can be unwrap and redeem the original NFT anytimes.

The `BUY` flow as below:
1. User A on Aztec chain bridge interface pay and buy an NFT on Opensea in Ethereum.
2. The "Buy" order and relevant fee is send to L1 by Aztec's rollup.
3. The bridge contract in L1 is triggered to interact with OpenSea protocol.
4. Then the bridge contract own this NFT and Mint a Wrapped NFT for Aztec L2 to bridge.
5. The User A On Aztec Owned this Wrapped NFT.

The `REDEEM` flow as below:
1. User A hold an wrapped NFT in Aztec L2.
2. He call the bridge to unwrapped and send the real NFT to a L1 address.
3. The Bridge contract is trigged, and send the resl NFT to the user specifed address, and burn the wrapped NFT.

### General Properties of convert(...) function
The `AztecTypes.AztecAsset calldata _inputAssetA` should be specificed as the Bridge's wrapped NFT.
And the relevant function calling is encoded into the `_auxData` Info.
In the Bridge contract, the function will be routed by the decoded
`_auxData`.

- The bridge is synchronous, and will always return `isAsync = false`.

- The bridge uses `_auxData` to encode the target NFT, id, price.

- The Bridge perform token pre-approvals to allow the `ROLLUP_PROCESSOR` to pull tokens from it.


## Is the contract upgradeable?

No, the bridge is immutable without any admin role.

## Does the bridge maintain state?

No, the bridge doesn't maintain a state.
However, it keeps an insignificant amount of tokens (dust) in the bridge to reduce gas-costs of future transactions (in case the DUST was sent to the bridge).
By having a dust, we don't need to do a `sstore` from `0` to `non-zero`.

## Does this bridge maintain state? If so, what is stored and why?

Yes, this bridge maintain the NFT bought from NFT to relevant user's private identity.
This state is a record for user to redeem their NFT asset.
135 changes: 135 additions & 0 deletions src/bridges/nft-basic/NFTVault.sol
Original file line number Diff line number Diff line change
@@ -0,0 +1,135 @@
// SPDX-License-Identifier: Apache-2.0
// Copyright 2022 Aztec.
pragma solidity >=0.8.4;

import {IERC721} from "../../../lib/openzeppelin-contracts/contracts/interfaces/IERC721.sol";
import {AztecTypes} from "../../../lib/rollup-encoder/src/libraries/AztecTypes.sol";
import {ErrorLib} from "../base/ErrorLib.sol";
import {BridgeBase} from "../base/BridgeBase.sol";
import {AddressRegistry} from "../registry/AddressRegistry.sol";

/**
* @title Basic NFT Vault for Aztec.
* @author Josh Crites, (@critesjosh on Github), Aztec Team
* @notice You can use this contract to hold your NFTs on Aztec. Whoever holds the corresponding virutal asset note can withdraw the NFT.
* @dev This bridge demonstrates basic functionality for an NFT bridge. This may be extended to support more features.
*/
contract NFTVault is BridgeBase {
struct NFTAsset {
address collection;
uint256 tokenId;
}

AddressRegistry public immutable REGISTRY;

mapping(uint256 => NFTAsset) public nftAssets;

error InvalidVirtualAssetId();

event NFTDeposit(uint256 indexed virtualAssetId, address indexed collection, uint256 indexed tokenId);
event NFTWithdraw(uint256 indexed virtualAssetId, address indexed collection, uint256 indexed tokenId);

/**
* @notice Set the addresses of RollupProcessor and AddressRegistry
* @param _rollupProcessor Address of the RollupProcessor
* @param _registry Address of the AddressRegistry
*/
constructor(address _rollupProcessor, address _registry) BridgeBase(_rollupProcessor) {
REGISTRY = AddressRegistry(_registry);
}

/**
* @notice Function for the first step of a NFT deposit, a NFT withdrawal, or transfer to another NFTVault.
* @dev This method can only be called from the RollupProcessor. The first step of the
* deposit flow returns a virutal asset note that will represent the NFT on Aztec. After the
* virutal asset note is received on Aztec, the user calls matchAndPull which deposits the NFT
* into Aztec and matches it with the virtual asset. When the virutal asset is sent to this function
* it is burned and the NFT is sent to the recipient passed in _auxData.
*
* @param _inputAssetA - ETH (Deposit) or VIRTUAL (Withdrawal)
* @param _outputAssetA - VIRTUAL (Deposit) or 0 ETH (Withdrawal)
* @param _totalInputValue - must be 1 wei (Deposit) or 1 VIRTUAL (Withdrawal)
* @param _interactionNonce - A globally unique identifier of this interaction/`convert(...)` call
* corresponding to the returned virtual asset id
* @param _auxData - corresponds to the Ethereum address id in the AddressRegistry.sol for withdrawals
* @return outputValueA - 1 VIRTUAL asset (Deposit) or 0 ETH (Withdrawal)
*
*/

function convert(
AztecTypes.AztecAsset calldata _inputAssetA,
AztecTypes.AztecAsset calldata,
AztecTypes.AztecAsset calldata _outputAssetA,
AztecTypes.AztecAsset calldata,
uint256 _totalInputValue,
uint256 _interactionNonce,
uint64 _auxData,
address
)
external
payable
override (BridgeBase)
onlyRollup
returns (uint256 outputValueA, uint256 outputValueB, bool isAsync)
{
if (
_inputAssetA.assetType == AztecTypes.AztecAssetType.NOT_USED
|| _inputAssetA.assetType == AztecTypes.AztecAssetType.ERC20
) revert ErrorLib.InvalidInputA();
if (
_outputAssetA.assetType == AztecTypes.AztecAssetType.NOT_USED
|| _outputAssetA.assetType == AztecTypes.AztecAssetType.ERC20
) revert ErrorLib.InvalidOutputA();
if (_totalInputValue != 1) {
revert ErrorLib.InvalidInputAmount();
}
if (
_inputAssetA.assetType == AztecTypes.AztecAssetType.ETH
&& _outputAssetA.assetType == AztecTypes.AztecAssetType.VIRTUAL
) {
return (1, 0, false);
} else if (_inputAssetA.assetType == AztecTypes.AztecAssetType.VIRTUAL) {
NFTAsset memory token = nftAssets[_inputAssetA.id];
if (token.collection == address(0x0)) {
revert ErrorLib.InvalidInputA();
}

address to = REGISTRY.addresses(_auxData);
if (to == address(0x0)) {
revert ErrorLib.InvalidAuxData();
}
delete nftAssets[_inputAssetA.id];
emit NFTWithdraw(_inputAssetA.id, token.collection, token.tokenId);

if (_outputAssetA.assetType == AztecTypes.AztecAssetType.ETH) {
IERC721(token.collection).transferFrom(address(this), to, token.tokenId);
return (0, 0, false);
} else {
IERC721(token.collection).approve(to, token.tokenId);
NFTVault(to).matchAndPull(_interactionNonce, token.collection, token.tokenId);
return (1, 0, false);
}
}
}

/**
* @notice Function for the second step of a NFT deposit or for transfers from other NFTVaults.
* @dev For a deposit, this method is called by an Ethereum L1 account that owns the NFT to deposit.
* The user must approve this bridge contract to transfer the users NFT before this function
* is called. This function assumes the NFT contract complies with the ERC721 standard.
* For a transfer from another NFTVault, this method is called by the NFTVault that is sending the NFT.
*
* @param _virtualAssetId - the virutal asset id of the note returned in the deposit step of the convert function
* @param _collection - collection address of the NFT
* @param _tokenId - the token id of the NFT
*/

function matchAndPull(uint256 _virtualAssetId, address _collection, uint256 _tokenId) external {
if (nftAssets[_virtualAssetId].collection != address(0x0)) {
revert InvalidVirtualAssetId();
}
nftAssets[_virtualAssetId] = NFTAsset({collection: _collection, tokenId: _tokenId});
IERC721(_collection).transferFrom(msg.sender, address(this), _tokenId);
emit NFTDeposit(_virtualAssetId, _collection, _tokenId);
}
}
135 changes: 135 additions & 0 deletions src/bridges/nft_trading/NFTVault.sol
Original file line number Diff line number Diff line change
@@ -0,0 +1,135 @@
// SPDX-License-Identifier: Apache-2.0
// Copyright 2022 Aztec.
pragma solidity >=0.8.4;

import {IERC721} from "../../../lib/openzeppelin-contracts/contracts/interfaces/IERC721.sol";
import {AztecTypes} from "../../../lib/rollup-encoder/src/libraries/AztecTypes.sol";
import {ErrorLib} from "../base/ErrorLib.sol";
import {BridgeBase} from "../base/BridgeBase.sol";
import {AddressRegistry} from "../registry/AddressRegistry.sol";

/**
* @title Basic NFT Vault for Aztec.
* @author Josh Crites, (@critesjosh on Github), Aztec Team
* @notice You can use this contract to hold your NFTs on Aztec. Whoever holds the corresponding virutal asset note can withdraw the NFT.
* @dev This bridge demonstrates basic functionality for an NFT bridge. This may be extended to support more features.
*/
contract NFTVault is BridgeBase {
struct NFTAsset {
address collection;
uint256 tokenId;
}

AddressRegistry public immutable REGISTRY;

mapping(uint256 => NFTAsset) public nftAssets;

error InvalidVirtualAssetId();

event NFTDeposit(uint256 indexed virtualAssetId, address indexed collection, uint256 indexed tokenId);
event NFTWithdraw(uint256 indexed virtualAssetId, address indexed collection, uint256 indexed tokenId);

/**
* @notice Set the addresses of RollupProcessor and AddressRegistry
* @param _rollupProcessor Address of the RollupProcessor
* @param _registry Address of the AddressRegistry
*/
constructor(address _rollupProcessor, address _registry) BridgeBase(_rollupProcessor) {
REGISTRY = AddressRegistry(_registry);
}

/**
* @notice Function for the first step of a NFT deposit, a NFT withdrawal, or transfer to another NFTVault.
* @dev This method can only be called from the RollupProcessor. The first step of the
* deposit flow returns a virutal asset note that will represent the NFT on Aztec. After the
* virutal asset note is received on Aztec, the user calls matchAndPull which deposits the NFT
* into Aztec and matches it with the virtual asset. When the virutal asset is sent to this function
* it is burned and the NFT is sent to the recipient passed in _auxData.
*
* @param _inputAssetA - ETH (Deposit) or VIRTUAL (Withdrawal)
* @param _outputAssetA - VIRTUAL (Deposit) or 0 ETH (Withdrawal)
* @param _totalInputValue - must be 1 wei (Deposit) or 1 VIRTUAL (Withdrawal)
* @param _interactionNonce - A globally unique identifier of this interaction/`convert(...)` call
* corresponding to the returned virtual asset id
* @param _auxData - corresponds to the Ethereum address id in the AddressRegistry.sol for withdrawals
* @return outputValueA - 1 VIRTUAL asset (Deposit) or 0 ETH (Withdrawal)
*
*/

function convert(
AztecTypes.AztecAsset calldata _inputAssetA,
AztecTypes.AztecAsset calldata,
AztecTypes.AztecAsset calldata _outputAssetA,
AztecTypes.AztecAsset calldata,
uint256 _totalInputValue,
uint256 _interactionNonce,
uint64 _auxData,
address
)
external
payable
override (BridgeBase)
onlyRollup
returns (uint256 outputValueA, uint256 outputValueB, bool isAsync)
{
if (
_inputAssetA.assetType == AztecTypes.AztecAssetType.NOT_USED
|| _inputAssetA.assetType == AztecTypes.AztecAssetType.ERC20
) revert ErrorLib.InvalidInputA();
if (
_outputAssetA.assetType == AztecTypes.AztecAssetType.NOT_USED
|| _outputAssetA.assetType == AztecTypes.AztecAssetType.ERC20
) revert ErrorLib.InvalidOutputA();
if (_totalInputValue != 1) {
revert ErrorLib.InvalidInputAmount();
}
if (
_inputAssetA.assetType == AztecTypes.AztecAssetType.ETH
&& _outputAssetA.assetType == AztecTypes.AztecAssetType.VIRTUAL
) {
return (1, 0, false);
} else if (_inputAssetA.assetType == AztecTypes.AztecAssetType.VIRTUAL) {
NFTAsset memory token = nftAssets[_inputAssetA.id];
if (token.collection == address(0x0)) {
revert ErrorLib.InvalidInputA();
}

address to = REGISTRY.addresses(_auxData);
if (to == address(0x0)) {
revert ErrorLib.InvalidAuxData();
}
delete nftAssets[_inputAssetA.id];
emit NFTWithdraw(_inputAssetA.id, token.collection, token.tokenId);

if (_outputAssetA.assetType == AztecTypes.AztecAssetType.ETH) {
IERC721(token.collection).transferFrom(address(this), to, token.tokenId);
return (0, 0, false);
} else {
IERC721(token.collection).approve(to, token.tokenId);
NFTVault(to).matchAndPull(_interactionNonce, token.collection, token.tokenId);
return (1, 0, false);
}
}
}

/**
* @notice Function for the second step of a NFT deposit or for transfers from other NFTVaults.
* @dev For a deposit, this method is called by an Ethereum L1 account that owns the NFT to deposit.
* The user must approve this bridge contract to transfer the users NFT before this function
* is called. This function assumes the NFT contract complies with the ERC721 standard.
* For a transfer from another NFTVault, this method is called by the NFTVault that is sending the NFT.
*
* @param _virtualAssetId - the virutal asset id of the note returned in the deposit step of the convert function
* @param _collection - collection address of the NFT
* @param _tokenId - the token id of the NFT
*/

function matchAndPull(uint256 _virtualAssetId, address _collection, uint256 _tokenId) external {
if (nftAssets[_virtualAssetId].collection != address(0x0)) {
revert InvalidVirtualAssetId();
}
nftAssets[_virtualAssetId] = NFTAsset({collection: _collection, tokenId: _tokenId});
IERC721(_collection).transferFrom(msg.sender, address(this), _tokenId);
emit NFTDeposit(_virtualAssetId, _collection, _tokenId);
}
}
Loading