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

Nft bridge #291

Open
wants to merge 76 commits into
base: master
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from 45 commits
Commits
Show all changes
76 commits
Select commit Hold shift + click to select a range
5bdf8bf
address registry and nft vault
critesjosh Nov 30, 2022
f0296ef
updates
critesjosh Dec 1, 2022
596fe4a
e2e test
critesjosh Dec 1, 2022
4dc874c
update max int
critesjosh Dec 1, 2022
19f3ada
remove deposit param
critesjosh Dec 1, 2022
a370585
increase gas
critesjosh Dec 1, 2022
847cc31
e2e working
critesjosh Dec 1, 2022
52e1a43
add input value
critesjosh Dec 1, 2022
63ef608
add registry to constructor
critesjosh Dec 2, 2022
1aa9eb0
del
critesjosh Dec 2, 2022
869e81a
rename
critesjosh Dec 2, 2022
eafca9e
deposit + withdraw
critesjosh Dec 2, 2022
86e68f7
add 0 output check
critesjosh Dec 2, 2022
202c05e
update comments
critesjosh Dec 2, 2022
d0668a6
add tests
critesjosh Dec 2, 2022
4936461
del
critesjosh Dec 2, 2022
69463ee
cleanup comments
critesjosh Dec 2, 2022
271abda
Merge branch 'AztecProtocol:master' into nft-bridge
critesjosh Dec 2, 2022
c3b5ce0
fmt
critesjosh Dec 3, 2022
27c6167
Merge branch 'nft-bridge' of https://github.com/critesjosh/aztec-conn…
critesjosh Dec 3, 2022
0ab5406
add L1 option
critesjosh Dec 3, 2022
1288f97
update inc
critesjosh Dec 3, 2022
b845e70
remove comments, fix matchDeposit
critesjosh Dec 6, 2022
217a526
rename id, cleanup
critesjosh Dec 6, 2022
4355353
update tests
critesjosh Dec 6, 2022
6559576
check event
critesjosh Dec 6, 2022
cfe88ae
fmt
critesjosh Dec 6, 2022
a5d6705
clean up comments
critesjosh Dec 6, 2022
a10c6ba
clean up comments
critesjosh Dec 6, 2022
469c372
clean comments
critesjosh Dec 6, 2022
281be8f
clean comments
critesjosh Dec 6, 2022
5c2c0b3
add deposit/withdraw events
critesjosh Dec 8, 2022
ad48137
revert reason for invalid input/outputs
critesjosh Dec 8, 2022
3473dd6
deployments
critesjosh Dec 8, 2022
1ae3752
gas benchmarks
critesjosh Dec 8, 2022
aa7049e
test deposit/withdraw events
critesjosh Dec 8, 2022
746f595
fmt
critesjosh Dec 8, 2022
0e9ead3
Merge branch 'AztecProtocol:master' into nft-bridge
critesjosh Dec 8, 2022
055c399
natspec comments
critesjosh Dec 8, 2022
cdfacf9
Merge branch 'nft-bridge' of https://github.com/critesjosh/aztec-conn…
critesjosh Dec 8, 2022
41004f2
fmt
critesjosh Dec 8, 2022
623725e
add test
critesjosh Dec 8, 2022
55031c9
fix linting
critesjosh Dec 8, 2022
32214eb
fmt
critesjosh Dec 8, 2022
ae33651
uint256 addressCount
critesjosh Dec 8, 2022
a4dbf00
incorporate lasse's feedback
critesjosh Dec 8, 2022
39984bd
Merge remote-tracking branch 'upstream/master' into nft-bridge
critesjosh Dec 9, 2022
d0b3cef
fix test
critesjosh Dec 9, 2022
8367e39
fmt
critesjosh Dec 9, 2022
b6e18ea
add transfer functionality
critesjosh Dec 24, 2022
249a917
move asset defs to storage
critesjosh Dec 31, 2022
55f3064
add .env
critesjosh Dec 31, 2022
82f7617
edits
critesjosh Dec 31, 2022
7002556
add .env
critesjosh Dec 31, 2022
22588ab
add .env
critesjosh Dec 31, 2022
4e937c8
add readme
critesjosh Dec 31, 2022
7741364
update address id
critesjosh Dec 31, 2022
73124ef
add readme
critesjosh Dec 31, 2022
f6d8757
add content
critesjosh Dec 31, 2022
4f91cf8
update
critesjosh Dec 31, 2022
62f2f20
fmt, lint
critesjosh Dec 31, 2022
d65c41c
Merge branch 'nft-bridge' into nft-transfer-bridge
critesjosh Dec 31, 2022
625f166
Merge pull request #2 from critesjosh/nft-transfer-bridge
critesjosh Dec 31, 2022
e00d25a
Merge branch 'AztecProtocol:master' into nft-bridge
critesjosh Dec 31, 2022
e00d03d
Update .gitignore
critesjosh Jan 3, 2023
5420aed
fix hardcoded nonce
critesjosh Jan 3, 2023
586ef5d
Merge branch 'nft-bridge' of https://github.com/critesjosh/aztec-conn…
critesjosh Jan 3, 2023
9f7be07
remove unreachable code
critesjosh Jan 3, 2023
c50d702
test invalid input amount
critesjosh Jan 3, 2023
f9fd54e
incorporat e review suggestions
critesjosh Jan 4, 2023
e21dd63
Delete .env
critesjosh Jan 4, 2023
7feeec7
Merge branch 'AztecProtocol:master' into nft-bridge
critesjosh Jan 4, 2023
7cef40c
fmt
critesjosh Jan 4, 2023
54833bd
Merge branch 'AztecProtocol:master' into nft-bridge
critesjosh Jan 10, 2023
a4e70ca
fmt
critesjosh Jan 10, 2023
c5fd393
retry fmt
critesjosh Jan 10, 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
2 changes: 2 additions & 0 deletions src/bridges/base/ErrorLib.sol
Original file line number Diff line number Diff line change
Expand Up @@ -18,4 +18,6 @@ library ErrorLib {

error InvalidNonce();
error AsyncDisabled();

error InvalidVirtualAsset();
critesjosh marked this conversation as resolved.
Show resolved Hide resolved
}
130 changes: 130 additions & 0 deletions src/bridges/nft-basic/NftVault.sol
Original file line number Diff line number Diff line change
@@ -0,0 +1,130 @@
// 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 {
critesjosh marked this conversation as resolved.
Show resolved Hide resolved
struct NftAsset {
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Do you need full uint256? If you can handle all expect token ids with a uint96 the NftAsset structure can be packed into one storage slot saving you a sstore.

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

tokenId is typically of type uint256 in erc721 contracts and since this contract is generic and should work with all erc721 contract, and we don't know what the possible range of tokenIds will be for a given erc721 contract I figured I should use uint256

address collection;
uint256 id;
critesjosh marked this conversation as resolved.
Show resolved Hide resolved
}

mapping(uint256 => NftAsset) public tokens;
critesjosh marked this conversation as resolved.
Show resolved Hide resolved
AddressRegistry public immutable REGISTRY;
critesjosh marked this conversation as resolved.
Show resolved Hide resolved

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.sol and AddressRegistry.sol
critesjosh marked this conversation as resolved.
Show resolved Hide resolved
* @param _rollupProcessor Address of the RollupProcessor.sol
critesjosh marked this conversation as resolved.
Show resolved Hide resolved
* @param _registry Address of the AddressRegistry.sol
critesjosh marked this conversation as resolved.
Show resolved Hide resolved
*/
constructor(address _rollupProcessor, address _registry) BridgeBase(_rollupProcessor) {
REGISTRY = AddressRegistry(_registry);
}

/**
* @notice Function for the first step of a NFT deposit, or a NFT withdrawal.
* @dev This method can only be called from the RollupProcessor.sol. The first step of the
critesjosh marked this conversation as resolved.
Show resolved Hide resolved
* 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 matchDeposit 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 user (withdraw).
critesjosh marked this conversation as resolved.
Show resolved Hide resolved
*
* @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 _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,
uint64 _auxData,
address
)
external
payable
override (
// uint64 _auxData,
critesjosh marked this conversation as resolved.
Show resolved Hide resolved
// address _rollupBeneficiary
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 (
critesjosh marked this conversation as resolved.
Show resolved Hide resolved
_inputAssetA.assetType == AztecTypes.AztecAssetType.ETH
&& _outputAssetA.assetType == AztecTypes.AztecAssetType.VIRTUAL
) {
if (_totalInputValue != 1 wei) {
critesjosh marked this conversation as resolved.
Show resolved Hide resolved
revert ErrorLib.InvalidInputAmount();
}
return (1, 0, false);
} else if (
_inputAssetA.assetType == AztecTypes.AztecAssetType.VIRTUAL
&& _outputAssetA.assetType == AztecTypes.AztecAssetType.ETH
) {
NftAsset memory token = tokens[_inputAssetA.id];
if (token.collection == address(0x0)) {
revert ErrorLib.InvalidInputA();
}

address _to = REGISTRY.addresses(_auxData);
critesjosh marked this conversation as resolved.
Show resolved Hide resolved
if (_to == address(0x0)) {
revert ErrorLib.InvalidAuxData();
critesjosh marked this conversation as resolved.
Show resolved Hide resolved
}

IERC721(token.collection).transferFrom(address(this), _to, token.id);
emit NftWithdraw(_inputAssetA.id, token.collection, token.id);
delete tokens[_inputAssetA.id];
critesjosh marked this conversation as resolved.
Show resolved Hide resolved
return (0, 0, false);
}
}

/**
* @notice Function for the second step of a NFT deposit.
* @dev 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.
*
* @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 matchDeposit(uint256 _virtualAssetId, address _collection, uint256 _tokenId) external {
if (tokens[_virtualAssetId].collection != address(0x0)) {
revert ErrorLib.InvalidVirtualAsset();
}
tokens[_virtualAssetId] = NftAsset({collection: _collection, id: _tokenId});
IERC721(_collection).transferFrom(msg.sender, address(this), _tokenId);
emit NftDeposit(_virtualAssetId, _collection, _tokenId);
}
}
96 changes: 96 additions & 0 deletions src/bridges/registry/AddressRegistry.sol
Original file line number Diff line number Diff line change
@@ -0,0 +1,96 @@
// 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 addressCount, address indexed registeredAddress);
critesjosh marked this conversation as resolved.
Show resolved Hide resolved

/**
* @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.sol. The first step to register an address is for a user to
critesjosh marked this conversation as resolved.
Show resolved Hide resolved
* 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 number of the
critesjosh marked this conversation as resolved.
Show resolved Hide resolved
* 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
&& _outputAssetA.assetType == AztecTypes.AztecAssetType.VIRTUAL
critesjosh marked this conversation as resolved.
Show resolved Hide resolved
) {
if (_totalInputValue != 1 wei) {
revert ErrorLib.InvalidInputAmount();
critesjosh marked this conversation as resolved.
Show resolved Hide resolved
}
return (type(uint160).max, 0, false);
} else if (
_inputAssetA.assetType == AztecTypes.AztecAssetType.VIRTUAL
&& _outputAssetA.assetType == AztecTypes.AztecAssetType.VIRTUAL
critesjosh marked this conversation as resolved.
Show resolved Hide resolved
) {
addressCount++;
critesjosh marked this conversation as resolved.
Show resolved Hide resolved
address toRegister = address(uint160(_totalInputValue));
addresses[addressCount] = toRegister;
emit AddressRegistered(addressCount, toRegister);
return (0, 0, false);
} else {
revert ErrorLib.InvalidInput();
critesjosh marked this conversation as resolved.
Show resolved Hide resolved
}
}

/**
* @notice Function for registering an address from Ethereum.
* @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 - ETH (step 1) or VIRTUAL (step 2)
critesjosh marked this conversation as resolved.
Show resolved Hide resolved
*/

function registerWithdrawAddress(address _to) external returns (uint256) {
critesjosh marked this conversation as resolved.
Show resolved Hide resolved
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It is possible to list the same address multiple times. Is this intended behaviour?

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yes it is possible as its written. Doing so is redundant so there is no reason for someone to do it, but the contract doesn't explicitly prevent it. Do you recommend making it impossible? If so, should I create a mapping of adddress => uint256 to store whether an address has been registered, and its index?

critesjosh marked this conversation as resolved.
Show resolved Hide resolved
addressCount++;
critesjosh marked this conversation as resolved.
Show resolved Hide resolved
critesjosh marked this conversation as resolved.
Show resolved Hide resolved
addresses[addressCount] = _to;
emit AddressRegistered(addressCount, _to);
return addressCount;
}
}
42 changes: 42 additions & 0 deletions src/deployment/nft-basic/NftVaultDeployment.s.sol
Original file line number Diff line number Diff line change
@@ -0,0 +1,42 @@
// SPDX-License-Identifier: Apache-2.0
// Copyright 2022 Aztec.
pragma solidity >=0.8.4;

import {BaseDeployment} from "../base/BaseDeployment.s.sol";
import {NftVault} from "../../bridges/nft-basic/NftVault.sol";
import {AddressRegistry} from "../../bridges/registry/AddressRegistry.sol";

contract NftVaultDeployment is BaseDeployment {
function deploy(address _addressRegistry) public returns (address) {
emit log("Deploying NftVault bridge");

vm.broadcast();
NftVault bridge = new NftVault(ROLLUP_PROCESSOR, _addressRegistry);

emit log_named_address("NftVault bridge deployed to", address(bridge));

return address(bridge);
}

function deployAndList(address _addressRegistry) public returns (address) {
address bridge = deploy(_addressRegistry);

uint256 addressId = listBridge(bridge, 400000);
emit log_named_uint("NftVault bridge address id", addressId);

return bridge;
}

function deployAndListAddressRegistry() public returns (address) {
emit log("Deploying AddressRegistry bridge");

vm.broadcast();
AddressRegistry bridge = new AddressRegistry(ROLLUP_PROCESSOR);

emit log_named_address("AddressRegistry bridge deployed to", address(bridge));
uint256 addressId = listBridge(address(bridge), 400000);
emit log_named_uint("AddressRegistry bridge address id", addressId);

return address(bridge);
}
}
28 changes: 28 additions & 0 deletions src/deployment/registry/AddressRegistryDeployment.s.sol
Original file line number Diff line number Diff line change
@@ -0,0 +1,28 @@
// SPDX-License-Identifier: Apache-2.0
// Copyright 2022 Aztec.
pragma solidity >=0.8.4;

import {BaseDeployment} from "../base/BaseDeployment.s.sol";
import {AddressRegistry} from "../../bridges/registry/AddressRegistry.sol";

contract AddressRegistryDeployment is BaseDeployment {
function deploy() public returns (address) {
emit log("Deploying AddressRegistry bridge");

vm.broadcast();
AddressRegistry bridge = new AddressRegistry(ROLLUP_PROCESSOR);

emit log_named_address("AddressRegistry bridge deployed to", address(bridge));

return address(bridge);
}

function deployAndList() public returns (address) {
address bridge = deploy();

uint256 addressId = listBridge(bridge, 400000);
emit log_named_uint("AddressRegistry bridge address id", addressId);

return bridge;
}
}
89 changes: 89 additions & 0 deletions src/gas/nft-basic/NftVaultGas.s.sol
Original file line number Diff line number Diff line change
@@ -0,0 +1,89 @@
// SPDX-License-Identifier: Apache-2.0
// Copyright 2022 Aztec.
pragma solidity >=0.8.4;

import {AddressRegistry} from "../../bridges/registry/AddressRegistry.sol";
import {AddressRegistryDeployment} from "../../deployment/registry/AddressRegistryDeployment.s.sol";
import {NftVault} from "../../bridges/nft-basic/NftVault.sol";
import {AztecTypes} from "rollup-encoder/libraries/AztecTypes.sol";

import {NftVaultDeployment} from "../../deployment/nft-basic/NftVaultDeployment.s.sol";
import {GasBase} from "../base/GasBase.sol";

import {ERC721PresetMinterPauserAutoId} from
"@openzeppelin/contracts/token/ERC721/presets/ERC721PresetMinterPauserAutoId.sol";

interface IRead {
function defiBridgeProxy() external view returns (address);
}

contract NftVaultGas is NftVaultDeployment, AddressRegistryDeployment {
GasBase internal gasBase;
NftVault internal bridge;
ERC721PresetMinterPauserAutoId internal nftContract;
address internal registry;
uint256 internal registryAddressId;

function measure() public {
uint256 privKey1 = vm.envUint("PRIVATE_KEY");
address addr1 = vm.addr(privKey1);

address defiProxy = IRead(ROLLUP_PROCESSOR).defiBridgeProxy();
vm.label(defiProxy, "DefiProxy");

vm.startBroadcast();
gasBase = new GasBase(defiProxy);
nftContract = new ERC721PresetMinterPauserAutoId("test", "NFT", "");
nftContract.mint(addr1);
vm.stopBroadcast();

address temp = ROLLUP_PROCESSOR;
ROLLUP_PROCESSOR = address(gasBase);
registry = deployAndListAddressRegistry();
address bridge = deployAndList(registry);
ROLLUP_PROCESSOR = temp;

AztecTypes.AztecAsset memory empty;
AztecTypes.AztecAsset memory eth =
AztecTypes.AztecAsset({id: 0, erc20Address: address(0), assetType: AztecTypes.AztecAssetType.ETH});
AztecTypes.AztecAsset memory virtualAsset =
AztecTypes.AztecAsset({id: 100, erc20Address: address(0), assetType: AztecTypes.AztecAssetType.VIRTUAL});

vm.startBroadcast();
address(gasBase).call{value: 2 ether}("");

// get registry virtual asset
gasBase.convert(address(registry), eth, empty, virtualAsset, empty, 1, 0, 0, address(0), 400000);
// register address
gasBase.convert(
address(registry),
virtualAsset,
empty,
virtualAsset,
empty,
uint256(uint160(addr1)),
0,
0,
address(0),
400000
);
nftContract.approve(address(bridge), 0);
vm.stopBroadcast();

// Get virtual assets
{
vm.broadcast();
gasBase.convert(bridge, eth, empty, virtualAsset, empty, 1, 0, 0, address(0), 400000);
}
// deposit nft
{
vm.broadcast();
NftVault(bridge).matchDeposit(virtualAsset.id, address(nftContract), 0);
}
// withdraw nft
{
vm.broadcast();
gasBase.convert(bridge, virtualAsset, empty, eth, empty, 1, 0, 1, address(0), 400000);
}
}
}
Loading