Skip to content

Commit

Permalink
add NFT Transfer templete
Browse files Browse the repository at this point in the history
  • Loading branch information
liusanchuan committed Jan 31, 2023
1 parent a461f25 commit bd37725
Show file tree
Hide file tree
Showing 10 changed files with 1,046 additions and 1 deletion.
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
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);
}
}
87 changes: 87 additions & 0 deletions src/bridges/registry/AddressRegistry.sol
Original file line number Diff line number Diff line change
@@ -0,0 +1,87 @@
// SPDX-License-Identifier: Apache-2.0
// Copyright 2022 Aztec.
pragma solidity >=0.8.4;

import {AztecTypes} from "../../../lib/rollup-encoder/src/libraries/AztecTypes.sol";
import {ErrorLib} from "../base/ErrorLib.sol";
import {BridgeBase} from "../base/BridgeBase.sol";

/**
* @title Aztec Address Registry.
* @author Josh Crites (@critesjosh on Github), Aztec team
* @notice This contract can be used to anonymously register an ethereum address with an id.
* This is useful for reducing the amount of data required to pass an ethereum address through auxData.
* @dev Use this contract to lookup ethereum addresses by id.
*/
contract AddressRegistry is BridgeBase {
uint256 public addressCount;
mapping(uint256 => address) public addresses;

event AddressRegistered(uint256 indexed index, address indexed entity);

/**
* @notice Set address of rollup processor
* @param _rollupProcessor Address of rollup processor
*/
constructor(address _rollupProcessor) BridgeBase(_rollupProcessor) {}

/**
* @notice Function for getting VIRTUAL assets (step 1) to register an address and registering an address (step 2).
* @dev This method can only be called from the RollupProcessor. The first step to register an address is for a user to
* get the type(uint160).max value of VIRTUAL assets back from the bridge. The second step is for the user
* to send an amount of VIRTUAL assets back to the bridge. The amount that is sent back is equal to the number of the
* ethereum address that is being registered (e.g. uint160(0x2e782B05290A7fFfA137a81a2bad2446AD0DdFEB)).
*
* @param _inputAssetA - ETH (step 1) or VIRTUAL (step 2)
* @param _outputAssetA - VIRTUAL (steps 1 and 2)
* @param _totalInputValue - must be 1 wei (ETH) (step 1) or address value (step 2)
* @return outputValueA - type(uint160).max (step 1) or 0 VIRTUAL (step 2)
*
*/

function convert(
AztecTypes.AztecAsset calldata _inputAssetA,
AztecTypes.AztecAsset calldata,
AztecTypes.AztecAsset calldata _outputAssetA,
AztecTypes.AztecAsset calldata,
uint256 _totalInputValue,
uint256,
uint64,
address
) external payable override (BridgeBase) onlyRollup returns (uint256 outputValueA, uint256, bool) {
if (
_inputAssetA.assetType == AztecTypes.AztecAssetType.NOT_USED
|| _inputAssetA.assetType == AztecTypes.AztecAssetType.ERC20
) revert ErrorLib.InvalidInputA();
if (_outputAssetA.assetType != AztecTypes.AztecAssetType.VIRTUAL) {
revert ErrorLib.InvalidOutputA();
}
if (_inputAssetA.assetType == AztecTypes.AztecAssetType.ETH) {
if (_totalInputValue != 1) {
revert ErrorLib.InvalidInputAmount();
}
return (type(uint160).max, 0, false);
} else if (_inputAssetA.assetType == AztecTypes.AztecAssetType.VIRTUAL) {
address toRegister = address(uint160(_totalInputValue));
registerAddress(toRegister);
return (0, 0, false);
}
}

/**
* @notice Register an address at the registry
* @dev This function can be called directly from another Ethereum account. This can be done in
* one step, in one transaction. Coming from Ethereum directly, this method is not as privacy
* preserving as registering an address through the bridge.
*
* @param _to - The address to register
* @return addressCount - the index of address that has been registered
*/

function registerAddress(address _to) public returns (uint256) {
uint256 userIndex = addressCount++;
addresses[userIndex] = _to;
emit AddressRegistered(userIndex, _to);
return userIndex;
}
}
Loading

0 comments on commit bd37725

Please sign in to comment.