Skip to content

Commit

Permalink
(creator_account_id, creator_address, serial_id, content_hash) on wit…
Browse files Browse the repository at this point in the history
…hdrawing NFT
  • Loading branch information
Barichek committed May 10, 2021
1 parent 5b09592 commit 3c82680
Show file tree
Hide file tree
Showing 16 changed files with 185 additions and 63 deletions.
4 changes: 2 additions & 2 deletions contracts/contracts/Config.sol
Original file line number Diff line number Diff line change
Expand Up @@ -46,10 +46,10 @@ contract Config {
uint256 internal constant PARTIAL_EXIT_BYTES = 6 * CHUNK_BYTES;
uint256 internal constant TRANSFER_BYTES = 2 * CHUNK_BYTES;
uint256 internal constant FORCED_EXIT_BYTES = 6 * CHUNK_BYTES;
uint256 internal constant WITHDRAW_NFT_BYTES = 9 * CHUNK_BYTES;
uint256 internal constant WITHDRAW_NFT_BYTES = 10 * CHUNK_BYTES;

/// @dev Full exit operation length
uint256 internal constant FULL_EXIT_BYTES = 10 * CHUNK_BYTES;
uint256 internal constant FULL_EXIT_BYTES = 11 * CHUNK_BYTES;

/// @dev ChangePubKey operation length
uint256 internal constant CHANGE_PUBKEY_BYTES = 6 * CHUNK_BYTES;
Expand Down
43 changes: 27 additions & 16 deletions contracts/contracts/Governance.sol
Original file line number Diff line number Diff line change
Expand Up @@ -12,8 +12,12 @@ contract Governance is Config {
/// @notice Token added to Franklin net
event NewToken(address indexed token, uint16 indexed tokenId);

/// @notice
event NFTFactoryRegistered(address indexed creatorAddress, address factoryAddress);
/// @notice NFT factory registered new creator account
event NFTFactoryRegisteredCreator(
uint32 indexed creatorAccountId,
address indexed creatorAddress,
address factoryAddress
);

/// @notice Governor changed
event NewGovernor(address newGovernor);
Expand Down Expand Up @@ -48,7 +52,7 @@ contract Governance is Config {
address public tokenGovernance;

/// @notice NFT Creator address to factory address mapping
mapping(address => NFTFactory) public nftFactories;
mapping(uint32 => mapping(address => NFTFactory)) public nftFactories;

/// @notice Address which will be used if NFT token has no factories
NFTFactory public defaultFactory;
Expand Down Expand Up @@ -154,38 +158,45 @@ contract Governance is Config {
return tokenId;
}

/// @notice Register factory corresponding to the creator
/// @notice Register creator corresponding to the factory
/// @param _creatorAccountId Creator's zkSync account ID
/// @param _creatorAddress NFT creator address
/// @param _signature creator's signature
function registerNFTFactory(address _creatorAddress, bytes memory _signature) external {
require(address(nftFactories[_creatorAddress]) == address(0), "Q");
/// @param _signature Creator's signature
function registerNFTFactoryCreator(
uint32 _creatorAccountId,
address _creatorAddress,
bytes memory _signature
) external {
require(address(nftFactories[_creatorAccountId][_creatorAddress]) == address(0), "Q");
bytes32 messageHash =
keccak256(
abi.encodePacked(
"\x19Ethereum Signed Message:\n60",
"\nCreator:",
"\x19Ethereum Signed Message:\n141",
"\nCreator's account ID in zkSync: ",
Bytes.bytesToHexASCIIBytes(abi.encodePacked((_creatorAccountId))),
"\nCreator: ",
Bytes.bytesToHexASCIIBytes(abi.encodePacked((_creatorAddress))),
"\nFactory:",
"\nFactory: ",
Bytes.bytesToHexASCIIBytes(abi.encodePacked((msg.sender)))
)
);
address recoveredAddress = Utils.recoverAddressFromEthSignature(_signature, messageHash);
require(recoveredAddress == _creatorAddress && recoveredAddress != address(0), "ws");
nftFactories[_creatorAddress] = NFTFactory(msg.sender);
emit NFTFactoryRegistered(_creatorAddress, msg.sender);
nftFactories[_creatorAccountId][_creatorAddress] = NFTFactory(msg.sender);
emit NFTFactoryRegisteredCreator(_creatorAccountId, _creatorAddress, msg.sender);
}

//@notice Set default factory for our contract. This factory will be used to mint an NFT token that has no factory
//@param _factory Address of NFT factory
function setDefaultNFTFactory(address _factory) external {
requireGovernor(msg.sender);
require(address(defaultFactory) == address(0x0), "mb");
require(address(defaultFactory) == address(0), "mb");
defaultFactory = NFTFactory(_factory);
}

function getNFTFactory(address _creator) external view returns (NFTFactory) {
NFTFactory _factory = nftFactories[_creator];
if (address(_factory) == address(0x0)) {
function getNFTFactory(uint32 _creatorAccountId, address _creatorAddress) external view returns (NFTFactory) {
NFTFactory _factory = nftFactories[_creatorAccountId][_creatorAddress];
if (address(_factory) == address(0)) {
return defaultFactory;
} else {
return _factory;
Expand Down
9 changes: 8 additions & 1 deletion contracts/contracts/NFTFactory.sol
Original file line number Diff line number Diff line change
Expand Up @@ -5,9 +5,16 @@ interface NFTFactory {
function mintNFT(
address creator,
address recipient,
uint32 serialId,
bytes32 contentHash,
uint32 tokenId
) external;

event MintNFT(address indexed creator, address indexed recipient, bytes contentHash, uint256 tokenId);
event MintNFT(
address indexed creator,
address indexed recipient,
uint32 serialId,
bytes32 contentHash,
uint256 tokenId
);
}
22 changes: 18 additions & 4 deletions contracts/contracts/Operations.sol
Original file line number Diff line number Diff line change
Expand Up @@ -40,6 +40,8 @@ library Operations {
uint8 internal constant FEE_BYTES = 2;
/// @dev zkSync account id bytes lengths
uint8 internal constant ACCOUNT_ID_BYTES = 4;
/// @dev zkSync nft serial id bytes lengths
uint8 internal constant NFT_SERIAL_ID_BYTES = 4;
uint8 internal constant AMOUNT_BYTES = 16;
/// @dev Signature (for example full exit signature) bytes length
uint8 internal constant SIGNATURE_BYTES = 64;
Expand Down Expand Up @@ -92,7 +94,9 @@ library Operations {
address owner;
uint32 tokenId;
uint128 amount;
uint32 nftCreatorAccountId;
address nftCreatorAddress;
uint32 nftSerialId;
bytes32 nftContentHash;
}

Expand All @@ -102,7 +106,9 @@ library Operations {
ADDRESS_BYTES +
TOKEN_BYTES +
AMOUNT_BYTES +
ACCOUNT_ID_BYTES +
ADDRESS_BYTES +
NFT_SERIAL_ID_BYTES +
CONTENT_HASH_BYTES;

function readFullExitPubdata(bytes memory _data) internal pure returns (FullExit memory parsed) {
Expand All @@ -112,7 +118,9 @@ library Operations {
(offset, parsed.owner) = Bytes.readAddress(_data, offset); // owner
(offset, parsed.tokenId) = Bytes.readUInt32(_data, offset); // tokenId
(offset, parsed.amount) = Bytes.readUInt128(_data, offset); // amount
(offset, parsed.nftCreatorAccountId) = Bytes.readUInt32(_data, offset); // nftCreatorAccountId
(offset, parsed.nftCreatorAddress) = Bytes.readAddress(_data, offset); // nftCreatorAddress
(offset, parsed.nftSerialId) = Bytes.readUInt32(_data, offset); // nftSerialId
(offset, parsed.nftContentHash) = Bytes.readBytes32(_data, offset); // nftContentHash

require(offset == PACKED_FULL_EXIT_PUBDATA_BYTES, "O"); // reading invalid full exit pubdata size
Expand All @@ -125,7 +133,9 @@ library Operations {
op.owner, // owner
op.tokenId, // tokenId
uint128(0), // amount -- ignored
uint32(0), // nftCreatorAccountId -- ignored
address(0), // nftCreatorAddress -- ignored
uint32(0), // nftSerialId -- ignored
bytes32(0) // nftContentHash -- ignored
);
}
Expand Down Expand Up @@ -200,19 +210,23 @@ library Operations {
struct WithdrawNFT {
//uint8 opType; -- present in pubdata, ignored at serialization
//uint32 accountId; -- present in pubdata, ignored at serialization
address creator;
uint32 creatorAccountId;
address creatorAddress;
uint32 serialId;
bytes32 contentHash;
address owner;
address receiver;
uint32 tokenId;
//uint32 feeTokenId;
//uint16 fee; -- present in pubdata, ignored at serialization
}

function readWithdrawNFTPubdata(bytes memory _data) internal pure returns (WithdrawNFT memory parsed) {
uint256 offset = OP_TYPE_BYTES + ACCOUNT_ID_BYTES; // opType + accountId (ignored)
(offset, parsed.creator) = Bytes.readAddress(_data, offset);
(offset, parsed.creatorAccountId) = Bytes.readUInt32(_data, offset);
(offset, parsed.creatorAddress) = Bytes.readAddress(_data, offset);
(offset, parsed.serialId) = Bytes.readUInt32(_data, offset);
(offset, parsed.contentHash) = Bytes.readBytes32(_data, offset);
(offset, parsed.owner) = Bytes.readAddress(_data, offset);
(offset, parsed.receiver) = Bytes.readAddress(_data, offset);
(offset, parsed.tokenId) = Bytes.readUInt32(_data, offset);
}
}
14 changes: 13 additions & 1 deletion contracts/contracts/Verifier.sol
Original file line number Diff line number Diff line change
Expand Up @@ -59,13 +59,25 @@ contract Verifier is KeysWithPlonkVerifier, KeysWithPlonkVerifierOld, Config {
address _owner,
uint32 _tokenId,
uint128 _amount,
uint32 _nftCreatorAccountId,
address _nftCreatorAddress,
uint32 _nftSerialId,
bytes32 _nftContentHash,
uint256[] calldata _proof
) external view returns (bool) {
bytes32 commitment =
sha256(
abi.encodePacked(_rootHash, _accountId, _owner, _tokenId, _amount, _nftCreatorAddress, _nftContentHash)
abi.encodePacked(
_rootHash,
_accountId,
_owner,
_tokenId,
_amount,
_nftCreatorAccountId,
_nftCreatorAddress,
_nftSerialId,
_nftContentHash
)
);

uint256[] memory inputs = new uint256[](1);
Expand Down
57 changes: 44 additions & 13 deletions contracts/contracts/ZkSync.sol
Original file line number Diff line number Diff line change
Expand Up @@ -251,12 +251,13 @@ contract ZkSync is UpgradeableMaster, Storage, Config, Events, ReentrancyGuard {
/// @notice Withdraws NFT from zkSync contract to the owner
/// @param _tokenId Id of NFT token
function withdrawPendingNFTBalance(uint32 _tokenId) external nonReentrant {
require(_tokenId > MAX_FUNGIBLE_TOKEN_ID, "oq"); // Withdraw only nft tokens
Operations.WithdrawNFT memory op = pendingWithdrawnNFTs[_tokenId];
require(_tokenId == op.tokenId, "op"); // Token does not exists
require(op.creator != address(0x0), "op"); // Token does not exists
NFTFactory _factory = governance.getNFTFactory(op.creator);
_factory.mintNFT(op.creator, op.owner, op.contentHash, op.tokenId);
require(op.creatorAddress != address(0), "op"); // No NFT to withdraw
NFTFactory _factory = governance.getNFTFactory(op.creatorAccountId, op.creatorAddress);
_factory.mintNFT(op.creatorAddress, op.receiver, op.serialId, op.contentHash, op.tokenId);
// Save withdrawn nfts for future deposits
withdrawnNFTs[op.tokenId] = address(_factory);
emit WithdrawalNFT(op.tokenId);
delete pendingWithdrawnNFTs[_tokenId];
}

Expand All @@ -282,7 +283,9 @@ contract ZkSync is UpgradeableMaster, Storage, Config, Events, ReentrancyGuard {
owner: msg.sender,
tokenId: uint32(tokenId),
amount: 0, // unknown at this point
nftCreatorAccountId: uint32(0), // unknown at this point
nftCreatorAddress: address(0), // unknown at this point
nftSerialId: uint32(0), // unknown at this point
nftContentHash: bytes32(0) // unknown at this point
});
bytes memory pubData = Operations.writeFullExitPubdataForPriorityQueue(op);
Expand Down Expand Up @@ -310,7 +313,9 @@ contract ZkSync is UpgradeableMaster, Storage, Config, Events, ReentrancyGuard {
owner: msg.sender,
tokenId: _tokenId,
amount: 0, // unknown at this point
nftCreatorAccountId: uint32(0), // unknown at this point
nftCreatorAddress: address(0), // unknown at this point
nftSerialId: uint32(0), // unknown at this point
nftContentHash: bytes32(0) // unknown at this point
});
bytes memory pubData = Operations.writeFullExitPubdataForPriorityQueue(op);
Expand Down Expand Up @@ -382,8 +387,16 @@ contract ZkSync is UpgradeableMaster, Storage, Config, Events, ReentrancyGuard {
/// @dev 1. Try to send token to _recipients
/// @dev 2. On failure: Increment _recipients balance to withdraw.
function withdrawOrStoreNFT(Operations.WithdrawNFT memory op) internal {
NFTFactory _factory = governance.getNFTFactory(op.creator);
try _factory.mintNFT{gas: WITHDRAWAL_GAS_LIMIT}(op.creator, op.owner, op.contentHash, op.tokenId) {
NFTFactory _factory = governance.getNFTFactory(op.creatorAccountId, op.creatorAddress);
try
_factory.mintNFT{gas: WITHDRAWAL_GAS_LIMIT}(
op.creatorAddress,
op.receiver,
op.serialId,
op.contentHash,
op.tokenId
)
{
// Save withdrawn nfts for future deposits
withdrawnNFTs[op.tokenId] = address(_factory);
emit WithdrawalNFT(op.tokenId);
Expand Down Expand Up @@ -454,9 +467,16 @@ contract ZkSync is UpgradeableMaster, Storage, Config, Events, ReentrancyGuard {
withdrawOrStore(uint16(op.tokenId), op.owner, op.amount);
} else {
if (op.amount == 1) {
Operations.WithdrawNFT memory nftOp =
Operations.WithdrawNFT(op.nftCreatorAddress, op.nftContentHash, op.owner, op.tokenId);
withdrawOrStoreNFT(nftOp);
Operations.WithdrawNFT memory withdrawNftOp =
Operations.WithdrawNFT(
op.nftCreatorAccountId,
op.nftCreatorAddress,
op.nftSerialId,
op.nftContentHash,
op.owner,
op.tokenId
);
withdrawOrStoreNFT(withdrawNftOp);
}
}
} else if (opType == Operations.OpType.WithdrawNFT) {
Expand Down Expand Up @@ -582,7 +602,9 @@ contract ZkSync is UpgradeableMaster, Storage, Config, Events, ReentrancyGuard {
uint32 _accountId,
uint32 _tokenId,
uint128 _amount,
uint32 _nftCreatorAccountId,
address _nftCreatorAddress,
uint32 _nftSerialId,
bytes32 _nftContentHash,
uint256[] memory _proof
) external nonReentrant {
Expand All @@ -601,7 +623,9 @@ contract ZkSync is UpgradeableMaster, Storage, Config, Events, ReentrancyGuard {
_owner,
_tokenId,
_amount,
_nftCreatorAccountId,
_nftCreatorAddress,
_nftSerialId,
_nftContentHash,
_proof
);
Expand All @@ -612,9 +636,16 @@ contract ZkSync is UpgradeableMaster, Storage, Config, Events, ReentrancyGuard {
increaseBalanceToWithdraw(packedBalanceKey, _amount);
} else {
require(_amount != 0, "Z"); // Unsupported nft amount
Operations.WithdrawNFT memory nftOp =
Operations.WithdrawNFT(_nftCreatorAddress, _nftContentHash, _owner, _tokenId);
pendingWithdrawnNFTs[_tokenId] = nftOp;
Operations.WithdrawNFT memory withdrawNftOp =
Operations.WithdrawNFT(
_nftCreatorAccountId,
_nftCreatorAddress,
_nftSerialId,
_nftContentHash,
_owner,
_tokenId
);
pendingWithdrawnNFTs[_tokenId] = withdrawNftOp;
}
performedExodus[_accountId][_tokenId] = true;
}
Expand Down
13 changes: 8 additions & 5 deletions contracts/contracts/ZkSyncNFTFactory.sol
Original file line number Diff line number Diff line change
Expand Up @@ -8,25 +8,28 @@ import "@openzeppelin/contracts/token/ERC721/ERC721.sol";
contract ZkSyncNFTFactory is ERC721, NFTFactory {
// Optional mapping for token content hashes
mapping(uint256 => bytes32) private _contentHashes;
address private _zksyncAddress;
address private _zkSyncAddress;

constructor(
string memory name,
string memory symbol,
address zksyncAddress
address zkSyncAddress
) ERC721(name, symbol) {
_zksyncAddress = zksyncAddress;
_zkSyncAddress = zkSyncAddress;
}

function mintNFT(
address,
address creator,
address recipient,
uint32 serialId,
bytes32 contentHash,
uint32 tokenId
) external override {
require(_msgSender() == _zksyncAddress, "z"); // Minting allowed only from zksync
require(_msgSender() == _zkSyncAddress, "z"); // Minting allowed only from zkSync
_safeMint(recipient, tokenId);
_contentHashes[tokenId] = contentHash;

emit MintNFT(creator, recipient, serialId, contentHash, tokenId);
}

function _beforeTokenTransfer(
Expand Down
2 changes: 1 addition & 1 deletion contracts/src.ts/deploy.ts
Original file line number Diff line number Diff line change
Expand Up @@ -108,7 +108,7 @@ export class Deployer {
}

const govContract = await deployContract(this.deployWallet, this.contracts.governance, [], {
gasLimit: 1000000,
gasLimit: 1500000,
...ethTxOptions
});
const govRec = await govContract.deployTransaction.wait();
Expand Down
2 changes: 1 addition & 1 deletion core/bin/zksync_core/src/eth_watch/client.rs
Original file line number Diff line number Diff line change
Expand Up @@ -34,7 +34,7 @@ impl ContractTopics {
.expect("main contract abi error")
.signature(),
factory_registered: governance_contract
.event("NFTFactoryRegistered")
.event("NFTFactoryRegisteredCreator")
.expect("main contract abi error")
.signature(),
}
Expand Down
Loading

0 comments on commit 3c82680

Please sign in to comment.