Add Wormhole721: ERC721 impl using Wormhole

This commit is contained in:
Emanuele Cesena 2022-07-15 12:17:34 -04:00 committed by Evan Gray
parent b669228722
commit c664796d5d
23 changed files with 17090 additions and 0 deletions

64
wormhole721/.gitignore vendored Normal file
View File

@ -0,0 +1,64 @@
*.swp
*.swo
# Logs
logs
*.log
# Runtime data
pids
*.pid
*.seed
allFiredEvents
scTopics
# Coverage directory used by tools like istanbul
coverage
coverage.json
coverageEnv
# node-waf configuration
.lock-wscript
# Dependency directory
node_modules
# Debug log from npm
npm-debug.log
# local env variables
.env
# truffle build directory
build/
# macOS
.DS_Store
# truffle
.node-xmlhttprequest-*
# IntelliJ IDE
.idea
# docs artifacts
docs/modules/api
# only used to package @openzeppelin/contracts
contracts/build/
contracts/README.md
# temporary artifact from solidity-coverage
.coverage_artifacts
.coverage_cache
.coverage_contracts
# hardhat
cache
artifacts
#local data
db
env.json
/etherscan
.openzeppelin

View File

@ -0,0 +1,3 @@
*.js
*.lock
pnpm-lock.yaml

View File

@ -0,0 +1,14 @@
module.exports = {
useTabs: false,
printWidth: 128,
tabWidth: 2,
plugins: [require('prettier-plugin-solidity')],
// overrides: [
// {
// "files": "*.sol",
// "options": {
// "explicitTypes": "always"
// }
// }
// ]
}

14
wormhole721/.solhint.json Normal file
View File

@ -0,0 +1,14 @@
{
"rules": {
"no-unused-vars": "error",
"private-vars-leading-underscore": "error",
"const-name-snakecase": "error",
"contract-name-camelcase": "error",
"event-name-camelcase": "error",
"func-name-mixedcase": "error",
"func-param-name-mixedcase": "error",
"modifier-name-mixedcase": "error",
"var-name-mixedcase": "error",
"imports-on-top": "error"
}
}

202
wormhole721/LICENSE Normal file
View File

@ -0,0 +1,202 @@
Apache License
Version 2.0, January 2004
http://www.apache.org/licenses/
TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION
1. Definitions.
"License" shall mean the terms and conditions for use, reproduction,
and distribution as defined by Sections 1 through 9 of this document.
"Licensor" shall mean the copyright owner or entity authorized by
the copyright owner that is granting the License.
"Legal Entity" shall mean the union of the acting entity and all
other entities that control, are controlled by, or are under common
control with that entity. For the purposes of this definition,
"control" means (i) the power, direct or indirect, to cause the
direction or management of such entity, whether by contract or
otherwise, or (ii) ownership of fifty percent (50%) or more of the
outstanding shares, or (iii) beneficial ownership of such entity.
"You" (or "Your") shall mean an individual or Legal Entity
exercising permissions granted by this License.
"Source" form shall mean the preferred form for making modifications,
including but not limited to software source code, documentation
source, and configuration files.
"Object" form shall mean any form resulting from mechanical
transformation or translation of a Source form, including but
not limited to compiled object code, generated documentation,
and conversions to other media types.
"Work" shall mean the work of authorship, whether in Source or
Object form, made available under the License, as indicated by a
copyright notice that is included in or attached to the work
(an example is provided in the Appendix below).
"Derivative Works" shall mean any work, whether in Source or Object
form, that is based on (or derived from) the Work and for which the
editorial revisions, annotations, elaborations, or other modifications
represent, as a whole, an original work of authorship. For the purposes
of this License, Derivative Works shall not include works that remain
separable from, or merely link (or bind by name) to the interfaces of,
the Work and Derivative Works thereof.
"Contribution" shall mean any work of authorship, including
the original version of the Work and any modifications or additions
to that Work or Derivative Works thereof, that is intentionally
submitted to Licensor for inclusion in the Work by the copyright owner
or by an individual or Legal Entity authorized to submit on behalf of
the copyright owner. For the purposes of this definition, "submitted"
means any form of electronic, verbal, or written communication sent
to the Licensor or its representatives, including but not limited to
communication on electronic mailing lists, source code control systems,
and issue tracking systems that are managed by, or on behalf of, the
Licensor for the purpose of discussing and improving the Work, but
excluding communication that is conspicuously marked or otherwise
designated in writing by the copyright owner as "Not a Contribution."
"Contributor" shall mean Licensor and any individual or Legal Entity
on behalf of whom a Contribution has been received by Licensor and
subsequently incorporated within the Work.
2. Grant of Copyright License. Subject to the terms and conditions of
this License, each Contributor hereby grants to You a perpetual,
worldwide, non-exclusive, no-charge, royalty-free, irrevocable
copyright license to reproduce, prepare Derivative Works of,
publicly display, publicly perform, sublicense, and distribute the
Work and such Derivative Works in Source or Object form.
3. Grant of Patent License. Subject to the terms and conditions of
this License, each Contributor hereby grants to You a perpetual,
worldwide, non-exclusive, no-charge, royalty-free, irrevocable
(except as stated in this section) patent license to make, have made,
use, offer to sell, sell, import, and otherwise transfer the Work,
where such license applies only to those patent claims licensable
by such Contributor that are necessarily infringed by their
Contribution(s) alone or by combination of their Contribution(s)
with the Work to which such Contribution(s) was submitted. If You
institute patent litigation against any entity (including a
cross-claim or counterclaim in a lawsuit) alleging that the Work
or a Contribution incorporated within the Work constitutes direct
or contributory patent infringement, then any patent licenses
granted to You under this License for that Work shall terminate
as of the date such litigation is filed.
4. Redistribution. You may reproduce and distribute copies of the
Work or Derivative Works thereof in any medium, with or without
modifications, and in Source or Object form, provided that You
meet the following conditions:
(a) You must give any other recipients of the Work or
Derivative Works a copy of this License; and
(b) You must cause any modified files to carry prominent notices
stating that You changed the files; and
(c) You must retain, in the Source form of any Derivative Works
that You distribute, all copyright, patent, trademark, and
attribution notices from the Source form of the Work,
excluding those notices that do not pertain to any part of
the Derivative Works; and
(d) If the Work includes a "NOTICE" text file as part of its
distribution, then any Derivative Works that You distribute must
include a readable copy of the attribution notices contained
within such NOTICE file, excluding those notices that do not
pertain to any part of the Derivative Works, in at least one
of the following places: within a NOTICE text file distributed
as part of the Derivative Works; within the Source form or
documentation, if provided along with the Derivative Works; or,
within a display generated by the Derivative Works, if and
wherever such third-party notices normally appear. The contents
of the NOTICE file are for informational purposes only and
do not modify the License. You may add Your own attribution
notices within Derivative Works that You distribute, alongside
or as an addendum to the NOTICE text from the Work, provided
that such additional attribution notices cannot be construed
as modifying the License.
You may add Your own copyright statement to Your modifications and
may provide additional or different license terms and conditions
for use, reproduction, or distribution of Your modifications, or
for any such Derivative Works as a whole, provided Your use,
reproduction, and distribution of the Work otherwise complies with
the conditions stated in this License.
5. Submission of Contributions. Unless You explicitly state otherwise,
any Contribution intentionally submitted for inclusion in the Work
by You to the Licensor shall be under the terms and conditions of
this License, without any additional terms or conditions.
Notwithstanding the above, nothing herein shall supersede or modify
the terms of any separate license agreement you may have executed
with Licensor regarding such Contributions.
6. Trademarks. This License does not grant permission to use the trade
names, trademarks, service marks, or product names of the Licensor,
except as required for reasonable and customary use in describing the
origin of the Work and reproducing the content of the NOTICE file.
7. Disclaimer of Warranty. Unless required by applicable law or
agreed to in writing, Licensor provides the Work (and each
Contributor provides its Contributions) on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
implied, including, without limitation, any warranties or conditions
of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A
PARTICULAR PURPOSE. You are solely responsible for determining the
appropriateness of using or redistributing the Work and assume any
risks associated with Your exercise of permissions under this License.
8. Limitation of Liability. In no event and under no legal theory,
whether in tort (including negligence), contract, or otherwise,
unless required by applicable law (such as deliberate and grossly
negligent acts) or agreed to in writing, shall any Contributor be
liable to You for damages, including any direct, indirect, special,
incidental, or consequential damages of any character arising as a
result of this License or out of the use or inability to use the
Work (including but not limited to damages for loss of goodwill,
work stoppage, computer failure or malfunction, or any and all
other commercial damages or losses), even if such Contributor
has been advised of the possibility of such damages.
9. Accepting Warranty or Additional Liability. While redistributing
the Work or Derivative Works thereof, You may choose to offer,
and charge a fee for, acceptance of support, warranty, indemnity,
or other liability obligations and/or rights consistent with this
License. However, in accepting such obligations, You may act only
on Your own behalf and on Your sole responsibility, not on behalf
of any other Contributor, and only if You agree to indemnify,
defend, and hold each Contributor harmless for any liability
incurred by, or claims asserted against, such Contributor by reason
of your accepting any such warranty or additional liability.
END OF TERMS AND CONDITIONS
APPENDIX: How to apply the Apache License to your work.
To apply the Apache License to your work, attach the following
boilerplate notice, with the fields enclosed by brackets "[]"
replaced with your own identifying information. (Don't include
the brackets!) The text should be enclosed in the appropriate
comment syntax for the file format. We also recommend that a
file or class name and description of purpose be included on the
same "printed page" as the copyright notice for easier
identification within third-party archives.
Copyright 2022 Nduja Labs, https://ndujalabs.com
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.

38
wormhole721/README.md Normal file
View File

@ -0,0 +1,38 @@
# Wormhole721 & Wormhole721Upgradable
Implementation of ERC721 and ERC721Upgradable NFTs using Wormhole to be natively cross-chain.
## Usage
Install:
```
npm install @ndujalabs/wormhole721
```
NFT contact:
```js
import "@openzeppelin/contracts-upgradeable/token/ERC721/ERC721Upgradeable.sol";
import "@openzeppelin/contracts-upgradeable/access/OwnableUpgradeable.sol";
import "@openzeppelin/contracts-upgradeable/token/ERC721/extensions/ERC721EnumerableUpgradeable.sol";
import "@ndujalabs/wormhole721/contracts/Wormhole721Upgradeable.sol";
...
contract MyBeautifulNFT is
...
ERC721Upgradeable,
ERC721EnumerableUpgradeable,
Wormhole721Upgradeable
{
...
function initialize(uint256 lastTokenId_, bool secondaryChain) public initializer {
__Wormhole721_init("My Beautiful NFT", "MBNFT");
__ERC721Enumerable_init();
...
}
...
}
```
## Examples
For a real-world example, see the [Everdragons2 Genesis contact](https://github.com/ndujaLabs/everdragons2-core).

3
wormhole721/arguments.js Normal file
View File

@ -0,0 +1,3 @@
module.exports = [
"0x4B97426740f536a4a51677A50A5f6d836ec466D6"
]

View File

@ -0,0 +1,135 @@
// SPDX-License-Identifier: Apache2
pragma solidity ^0.8.3;
import "@openzeppelin/contracts/token/ERC721/ERC721.sol";
import "@openzeppelin/contracts/security/Pausable.sol";
import "@openzeppelin/contracts/access/Ownable.sol";
import "./interfaces/IWormhole.sol";
import "./libraries/BytesLib.sol";
import "./nftHelpers/NFTStructs.sol";
import "./nftHelpers/NFTGetters.sol";
import "./nftHelpers/NFTSetters.sol";
import "./interfaces/IWormhole721.sol";
contract Wormhole721 is ERC721, IWormhole721, NFTGetters, NFTSetters, Pausable, Ownable {
using BytesLib for bytes;
constructor(string memory name, string memory symbol) ERC721(name, symbol) {}
function supportsInterface(bytes4 interfaceId) public view virtual override(ERC721) returns (bool) {
return interfaceId == type(IWormhole721).interfaceId || super.supportsInterface(interfaceId);
}
function wormholeInit(uint16 chainId, address wormhole) public override onlyOwner {
_setChainId(chainId);
_setWormhole(wormhole);
}
function wormholeRegisterContract(uint16 chainId_, bytes32 nftContract_) public override onlyOwner {
_setNftContract(chainId_, nftContract_);
}
function wormholeGetContract(uint16 chainId) public view override returns (bytes32) {
return nftContract(chainId);
}
function _wormholeCompleteTransfer(bytes memory encodedVm) internal returns (address to, uint256 tokenId) {
(IWormhole.VM memory vm, bool valid, string memory reason) = wormhole().parseAndVerifyVM(encodedVm);
require(valid, reason);
require(_verifyNftContractVM(vm), "invalid emitter");
NFTStructs.Transfer memory transfer = _parseTransfer(vm.payload);
require(!isTransferCompleted(vm.hash), "transfer already completed");
_setTransferCompleted(vm.hash);
require(transfer.toChain == chainId(), "invalid target chain");
// transfer bridged NFT to recipient
address transferRecipient = address(uint160(uint256(transfer.to)));
return (transferRecipient, transfer.tokenId);
}
// function _wormholeTransfer(
// uint256 tokenId,
// uint16 recipientChain,
// bytes32 recipient,
// uint32 nonce
// ) internal returns (uint64 sequence) {
// // TODO msg.value - Wormhole fees
// return _wormholeTransferWithValue(tokenId, recipientChain, recipient, nonce, msg.value);
// }
function _wormholeTransferWithValue(
uint256 tokenId,
uint16 recipientChain,
bytes32 recipient,
uint32 nonce,
uint256 value
) internal returns (uint64 sequence) {
require(nftContract(recipientChain) != 0, "ERC721: recipientChain not allowed");
sequence = _logTransfer(NFTStructs.Transfer({tokenId: tokenId, to: recipient, toChain: recipientChain}), value, nonce);
return sequence;
}
function _logTransfer(
NFTStructs.Transfer memory transfer,
uint256 callValue,
uint32 nonce
) internal returns (uint64 sequence) {
bytes memory encoded = _encodeTransfer(transfer);
sequence = wormhole().publishMessage{value: callValue}(nonce, encoded, 15);
}
function _verifyNftContractVM(IWormhole.VM memory vm) internal view returns (bool) {
if (nftContract(vm.emitterChainId) == vm.emitterAddress) {
return true;
}
return false;
}
function _encodeTransfer(NFTStructs.Transfer memory transfer) internal pure returns (bytes memory encoded) {
encoded = abi.encodePacked(uint8(1), transfer.tokenId, transfer.to, transfer.toChain);
}
function _parseTransfer(bytes memory encoded) internal pure returns (NFTStructs.Transfer memory transfer) {
uint256 index = 0;
uint8 payloadId = encoded.toUint8(index);
index += 1;
require(payloadId == 1, "invalid Transfer");
transfer.tokenId = encoded.toUint256(index);
index += 32;
transfer.to = encoded.toBytes32(index);
index += 32;
transfer.toChain = encoded.toUint16(index);
index += 2;
require(encoded.length == index, "invalid Transfer");
return transfer;
}
function wormholeTransfer(
uint256 tokenID,
uint16 recipientChain,
bytes32 recipient,
uint32 nonce
) public payable override returns (uint64 sequence) {
require(_isApprovedOrOwner(_msgSender(), tokenID), "ERC721: transfer caller is not owner nor approved");
_burn(tokenID);
return _wormholeTransferWithValue(tokenID, recipientChain, recipient, nonce, msg.value);
}
// Complete a transfer from Wormhole
function wormholeCompleteTransfer(bytes memory encodedVm) public override {
(address to, uint256 tokenId) = _wormholeCompleteTransfer(encodedVm);
_safeMint(to, tokenId);
}
}

View File

@ -0,0 +1,159 @@
// SPDX-License-Identifier: Apache2
pragma solidity ^0.8.3;
import "@openzeppelin/contracts-upgradeable/token/ERC721/ERC721Upgradeable.sol";
import "@openzeppelin/contracts-upgradeable/security/PausableUpgradeable.sol";
import "@openzeppelin/contracts-upgradeable/access/OwnableUpgradeable.sol";
import "@openzeppelin/contracts-upgradeable/proxy/utils/UUPSUpgradeable.sol";
import "@openzeppelin/contracts-upgradeable/utils/introspection/ERC165Upgradeable.sol";
import "./interfaces/IWormhole.sol";
import "./libraries/BytesLib.sol";
import "./nftHelpers/NFTStructs.sol";
import "./nftHelpers/NFTGetters.sol";
import "./nftHelpers/NFTSetters.sol";
import "./interfaces/IWormhole721.sol";
contract Wormhole721Upgradeable is
ERC721Upgradeable,
IWormhole721,
NFTGetters,
NFTSetters,
PausableUpgradeable,
OwnableUpgradeable,
UUPSUpgradeable
{
using BytesLib for bytes;
// solhint-disable-next-line func-name-mixedcase
function __Wormhole721_init(string memory name, string memory symbol) internal virtual initializer {
__Ownable_init();
__Pausable_init();
__UUPSUpgradeable_init();
__ERC721_init(name, symbol);
}
function _authorizeUpgrade(address newImplementation) internal virtual override onlyOwner {}
function supportsInterface(bytes4 interfaceId) public view virtual override(ERC721Upgradeable) returns (bool) {
return interfaceId == type(IWormhole721).interfaceId || super.supportsInterface(interfaceId);
}
function wormholeInit(uint16 chainId, address wormhole) public override onlyOwner {
_setChainId(chainId);
_setWormhole(wormhole);
}
function wormholeRegisterContract(uint16 chainId_, bytes32 nftContract_) public override onlyOwner {
_setNftContract(chainId_, nftContract_);
}
function wormholeGetContract(uint16 chainId) public view override returns (bytes32) {
return nftContract(chainId);
}
function _wormholeCompleteTransfer(bytes memory encodedVm) internal returns (address to, uint256 tokenId) {
(IWormhole.VM memory vm, bool valid, string memory reason) = wormhole().parseAndVerifyVM(encodedVm);
require(valid, reason);
require(_verifyNftContractVM(vm), "invalid emitter");
NFTStructs.Transfer memory transfer = _parseTransfer(vm.payload);
require(!isTransferCompleted(vm.hash), "transfer already completed");
_setTransferCompleted(vm.hash);
require(transfer.toChain == chainId(), "invalid target chain");
// transfer bridged NFT to recipient
address transferRecipient = address(uint160(uint256(transfer.to)));
return (transferRecipient, transfer.tokenId);
}
// function _wormholeTransfer(
// uint256 tokenId,
// uint16 recipientChain,
// bytes32 recipient,
// uint32 nonce
// ) internal returns (uint64 sequence) {
// // TODO msg.value - Wormhole fees
// return _wormholeTransferWithValue(tokenId, recipientChain, recipient, nonce, msg.value);
// }
function _wormholeTransferWithValue(
uint256 tokenId,
uint16 recipientChain,
bytes32 recipient,
uint32 nonce,
uint256 value
) internal returns (uint64 sequence) {
require(nftContract(recipientChain) != 0, "ERC721: recipientChain not allowed");
sequence = _logTransfer(NFTStructs.Transfer({tokenId: tokenId, to: recipient, toChain: recipientChain}), value, nonce);
return sequence;
}
function _logTransfer(
NFTStructs.Transfer memory transfer,
uint256 callValue,
uint32 nonce
) internal returns (uint64 sequence) {
bytes memory encoded = _encodeTransfer(transfer);
sequence = wormhole().publishMessage{value: callValue}(nonce, encoded, 15);
}
function _verifyNftContractVM(IWormhole.VM memory vm) internal view returns (bool) {
if (nftContract(vm.emitterChainId) == vm.emitterAddress) {
return true;
}
return false;
}
function _encodeTransfer(NFTStructs.Transfer memory transfer) internal pure returns (bytes memory encoded) {
encoded = abi.encodePacked(uint8(1), transfer.tokenId, transfer.to, transfer.toChain);
}
function _parseTransfer(bytes memory encoded) internal pure returns (NFTStructs.Transfer memory transfer) {
uint256 index = 0;
uint8 payloadId = encoded.toUint8(index);
index += 1;
require(payloadId == 1, "invalid Transfer");
transfer.tokenId = encoded.toUint256(index);
index += 32;
transfer.to = encoded.toBytes32(index);
index += 32;
transfer.toChain = encoded.toUint16(index);
index += 2;
require(encoded.length == index, "invalid Transfer");
return transfer;
}
function wormholeTransfer(
uint256 tokenID,
uint16 recipientChain,
bytes32 recipient,
uint32 nonce
) public payable override returns (uint64 sequence) {
require(_isApprovedOrOwner(_msgSender(), tokenID), "ERC721: transfer caller is not owner nor approved");
_burn(tokenID);
return _wormholeTransferWithValue(tokenID, recipientChain, recipient, nonce, msg.value);
}
// Complete a transfer from Wormhole
function wormholeCompleteTransfer(bytes memory encodedVm) public override {
(address to, uint256 tokenId) = _wormholeCompleteTransfer(encodedVm);
_safeMint(to, tokenId);
}
// convenience helper
// function getIWormhole721InterfaceId() external pure returns(bytes4) {
// return type(IWormhole721).interfaceId;
// }
}

View File

@ -0,0 +1,51 @@
// SPDX-License-Identifier: Apache2
pragma solidity ^0.8.3;
import "./IWormholeStructs.sol";
interface IWormhole is IWormholeStructs {
event LogMessagePublished(address indexed sender, uint64 sequence, uint32 nonce, bytes payload, uint8 consistencyLevel);
function publishMessage(
uint32 nonce,
bytes memory payload,
uint8 consistencyLevel
) external payable returns (uint64 sequence);
function parseAndVerifyVM(bytes calldata encodedVM)
external
view
returns (
IWormholeStructs.VM memory vm,
bool valid,
string memory reason
);
function verifyVM(IWormholeStructs.VM memory vm) external view returns (bool valid, string memory reason);
function verifySignatures(
bytes32 hash,
IWormholeStructs.Signature[] memory signatures,
IWormholeStructs.GuardianSet memory guardianSet
) external pure returns (bool valid, string memory reason);
function parseVM(bytes memory encodedVM) external pure returns (IWormholeStructs.VM memory vm);
function getGuardianSet(uint32 index) external view returns (IWormholeStructs.GuardianSet memory);
function getCurrentGuardianSetIndex() external view returns (uint32);
function getGuardianSetExpiry() external view returns (uint32);
function governanceActionIsConsumed(bytes32 hash) external view returns (bool);
function isInitialized(address impl) external view returns (bool);
function chainId() external view returns (uint16);
function governanceChainId() external view returns (uint16);
function governanceContract() external view returns (bytes32);
function messageFee() external view returns (uint256);
}

View File

@ -0,0 +1,21 @@
// SPDX-License-Identifier: Apache2
pragma solidity ^0.8.3;
/// ERC165 interfaceId is 0x647bffff
/* is IERC165 */
interface IWormhole721 {
function wormholeInit(uint16 chainId, address wormhole) external;
function wormholeRegisterContract(uint16 chainId, bytes32 nftContract) external;
function wormholeGetContract(uint16 chainId) external view returns (bytes32);
function wormholeTransfer(
uint256 tokenID,
uint16 recipientChain,
bytes32 recipient,
uint32 nonce
) external payable returns (uint64 sequence);
function wormholeCompleteTransfer(bytes memory encodedVm) external;
}

View File

@ -0,0 +1,36 @@
// SPDX-License-Identifier: Apache2
pragma solidity ^0.8.3;
interface IWormholeStructs {
struct Provider {
uint16 chainId;
uint16 governanceChainId;
bytes32 governanceContract;
}
struct GuardianSet {
address[] keys;
uint32 expirationTime;
}
struct Signature {
bytes32 r;
bytes32 s;
uint8 v;
uint8 guardianIndex;
}
struct VM {
uint8 version;
uint32 timestamp;
uint32 nonce;
uint16 emitterChainId;
bytes32 emitterAddress;
uint64 sequence;
uint8 consistencyLevel;
bytes payload;
uint32 guardianSetIndex;
Signature[] signatures;
bytes32 hash;
}
}

View File

@ -0,0 +1,487 @@
// SPDX-License-Identifier: Unlicense
/*
* @title Solidity Bytes Arrays Utils
* @author Gonçalo <goncalo.sa@consensys.net>
*
* @dev Bytes tightly packed arrays utility library for ethereum contracts written in Solidity.
* The library lets you concatenate, slice and type cast bytes arrays both in memory and storage.
*/
pragma solidity >=0.8.0 <0.9.0;
library BytesLib {
function concat(bytes memory _preBytes, bytes memory _postBytes) internal pure returns (bytes memory) {
bytes memory tempBytes;
assembly {
// Get a location of some free memory and store it in tempBytes as
// Solidity does for memory variables.
tempBytes := mload(0x40)
// Store the length of the first bytes array at the beginning of
// the memory for tempBytes.
let length := mload(_preBytes)
mstore(tempBytes, length)
// Maintain a memory counter for the current write location in the
// temp bytes array by adding the 32 bytes for the array length to
// the starting location.
let mc := add(tempBytes, 0x20)
// Stop copying when the memory counter reaches the length of the
// first bytes array.
let end := add(mc, length)
for {
// Initialize a copy counter to the start of the _preBytes data,
// 32 bytes into its memory.
let cc := add(_preBytes, 0x20)
} lt(mc, end) {
// Increase both counters by 32 bytes each iteration.
mc := add(mc, 0x20)
cc := add(cc, 0x20)
} {
// Write the _preBytes data into the tempBytes memory 32 bytes
// at a time.
mstore(mc, mload(cc))
}
// Add the length of _postBytes to the current length of tempBytes
// and store it as the new length in the first 32 bytes of the
// tempBytes memory.
length := mload(_postBytes)
mstore(tempBytes, add(length, mload(tempBytes)))
// Move the memory counter back from a multiple of 0x20 to the
// actual end of the _preBytes data.
mc := end
// Stop copying when the memory counter reaches the new combined
// length of the arrays.
end := add(mc, length)
for {
let cc := add(_postBytes, 0x20)
} lt(mc, end) {
mc := add(mc, 0x20)
cc := add(cc, 0x20)
} {
mstore(mc, mload(cc))
}
// Update the free-memory pointer by padding our last write location
// to 32 bytes: add 31 bytes to the end of tempBytes to move to the
// next 32 byte block, then round down to the nearest multiple of
// 32. If the sum of the length of the two arrays is zero then add
// one before rounding down to leave a blank 32 bytes (the length block with 0).
mstore(
0x40,
and(
add(add(end, iszero(add(length, mload(_preBytes)))), 31),
not(31) // Round down to the nearest 32 bytes.
)
)
}
return tempBytes;
}
function concatStorage(bytes storage _preBytes, bytes memory _postBytes) internal {
assembly {
// Read the first 32 bytes of _preBytes storage, which is the length
// of the array. (We don't need to use the offset into the slot
// because arrays use the entire slot.)
let fslot := sload(_preBytes.slot)
// Arrays of 31 bytes or less have an even value in their slot,
// while longer arrays have an odd value. The actual length is
// the slot divided by two for odd values, and the lowest order
// byte divided by two for even values.
// If the slot is even, bitwise and the slot with 255 and divide by
// two to get the length. If the slot is odd, bitwise and the slot
// with -1 and divide by two.
let slength := div(and(fslot, sub(mul(0x100, iszero(and(fslot, 1))), 1)), 2)
let mlength := mload(_postBytes)
let newlength := add(slength, mlength)
// slength can contain both the length and contents of the array
// if length < 32 bytes so let's prepare for that
// v. http://solidity.readthedocs.io/en/latest/miscellaneous.html#layout-of-state-variables-in-storage
switch add(lt(slength, 32), lt(newlength, 32))
case 2 {
// Since the new array still fits in the slot, we just need to
// update the contents of the slot.
// uint256(bytes_storage) = uint256(bytes_storage) + uint256(bytes_memory) + new_length
sstore(
_preBytes.slot,
// all the modifications to the slot are inside this
// next block
add(
// we can just add to the slot contents because the
// bytes we want to change are the LSBs
fslot,
add(
mul(
div(
// load the bytes from memory
mload(add(_postBytes, 0x20)),
// zero all bytes to the right
exp(0x100, sub(32, mlength))
),
// and now shift left the number of bytes to
// leave space for the length in the slot
exp(0x100, sub(32, newlength))
),
// increase length by the double of the memory
// bytes length
mul(mlength, 2)
)
)
)
}
case 1 {
// The stored value fits in the slot, but the combined value
// will exceed it.
// get the keccak hash to get the contents of the array
mstore(0x0, _preBytes.slot)
let sc := add(keccak256(0x0, 0x20), div(slength, 32))
// save new length
sstore(_preBytes.slot, add(mul(newlength, 2), 1))
// The contents of the _postBytes array start 32 bytes into
// the structure. Our first read should obtain the `submod`
// bytes that can fit into the unused space in the last word
// of the stored array. To get this, we read 32 bytes starting
// from `submod`, so the data we read overlaps with the array
// contents by `submod` bytes. Masking the lowest-order
// `submod` bytes allows us to add that value directly to the
// stored value.
let submod := sub(32, slength)
let mc := add(_postBytes, submod)
let end := add(_postBytes, mlength)
let mask := sub(exp(0x100, submod), 1)
sstore(sc, add(and(fslot, 0xffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff00), and(mload(mc), mask)))
for {
mc := add(mc, 0x20)
sc := add(sc, 1)
} lt(mc, end) {
sc := add(sc, 1)
mc := add(mc, 0x20)
} {
sstore(sc, mload(mc))
}
mask := exp(0x100, sub(mc, end))
sstore(sc, mul(div(mload(mc), mask), mask))
}
default {
// get the keccak hash to get the contents of the array
mstore(0x0, _preBytes.slot)
// Start copying to the last used word of the stored array.
let sc := add(keccak256(0x0, 0x20), div(slength, 32))
// save new length
sstore(_preBytes.slot, add(mul(newlength, 2), 1))
// Copy over the first `submod` bytes of the new data as in
// case 1 above.
let slengthmod := mod(slength, 32)
// let mlengthmod := mod(mlength, 32)
let submod := sub(32, slengthmod)
let mc := add(_postBytes, submod)
let end := add(_postBytes, mlength)
let mask := sub(exp(0x100, submod), 1)
sstore(sc, add(sload(sc), and(mload(mc), mask)))
for {
sc := add(sc, 1)
mc := add(mc, 0x20)
} lt(mc, end) {
sc := add(sc, 1)
mc := add(mc, 0x20)
} {
sstore(sc, mload(mc))
}
mask := exp(0x100, sub(mc, end))
sstore(sc, mul(div(mload(mc), mask), mask))
}
}
}
function slice(
bytes memory _bytes,
uint256 _start,
uint256 _length
) internal pure returns (bytes memory) {
require(_length + 31 >= _length, "slice_overflow");
require(_bytes.length >= _start + _length, "slice_outOfBounds");
bytes memory tempBytes;
assembly {
switch iszero(_length)
case 0 {
// Get a location of some free memory and store it in tempBytes as
// Solidity does for memory variables.
tempBytes := mload(0x40)
// The first word of the slice result is potentially a partial
// word read from the original array. To read it, we calculate
// the length of that partial word and start copying that many
// bytes into the array. The first word we copy will start with
// data we don't care about, but the last `lengthmod` bytes will
// land at the beginning of the contents of the new array. When
// we're done copying, we overwrite the full first word with
// the actual length of the slice.
let lengthmod := and(_length, 31)
// The multiplication in the next line is necessary
// because when slicing multiples of 32 bytes (lengthmod == 0)
// the following copy loop was copying the origin's length
// and then ending prematurely not copying everything it should.
let mc := add(add(tempBytes, lengthmod), mul(0x20, iszero(lengthmod)))
let end := add(mc, _length)
for {
// The multiplication in the next line has the same exact purpose
// as the one above.
let cc := add(add(add(_bytes, lengthmod), mul(0x20, iszero(lengthmod))), _start)
} lt(mc, end) {
mc := add(mc, 0x20)
cc := add(cc, 0x20)
} {
mstore(mc, mload(cc))
}
mstore(tempBytes, _length)
//update free-memory pointer
//allocating the array padded to 32 bytes like the compiler does now
mstore(0x40, and(add(mc, 31), not(31)))
}
//if we want a zero-length slice let's just return a zero-length array
default {
tempBytes := mload(0x40)
//zero out the 32 bytes slice we are about to return
//we need to do it because Solidity does not garbage collect
mstore(tempBytes, 0)
mstore(0x40, add(tempBytes, 0x20))
}
}
return tempBytes;
}
function toAddress(bytes memory _bytes, uint256 _start) internal pure returns (address) {
require(_bytes.length >= _start + 20, "toAddress_outOfBounds");
address tempAddress;
assembly {
tempAddress := div(mload(add(add(_bytes, 0x20), _start)), 0x1000000000000000000000000)
}
return tempAddress;
}
function toUint8(bytes memory _bytes, uint256 _start) internal pure returns (uint8) {
require(_bytes.length >= _start + 1, "toUint8_outOfBounds");
uint8 tempUint;
assembly {
tempUint := mload(add(add(_bytes, 0x1), _start))
}
return tempUint;
}
function toUint16(bytes memory _bytes, uint256 _start) internal pure returns (uint16) {
require(_bytes.length >= _start + 2, "toUint16_outOfBounds");
uint16 tempUint;
assembly {
tempUint := mload(add(add(_bytes, 0x2), _start))
}
return tempUint;
}
function toUint32(bytes memory _bytes, uint256 _start) internal pure returns (uint32) {
require(_bytes.length >= _start + 4, "toUint32_outOfBounds");
uint32 tempUint;
assembly {
tempUint := mload(add(add(_bytes, 0x4), _start))
}
return tempUint;
}
function toUint64(bytes memory _bytes, uint256 _start) internal pure returns (uint64) {
require(_bytes.length >= _start + 8, "toUint64_outOfBounds");
uint64 tempUint;
assembly {
tempUint := mload(add(add(_bytes, 0x8), _start))
}
return tempUint;
}
function toUint96(bytes memory _bytes, uint256 _start) internal pure returns (uint96) {
require(_bytes.length >= _start + 12, "toUint96_outOfBounds");
uint96 tempUint;
assembly {
tempUint := mload(add(add(_bytes, 0xc), _start))
}
return tempUint;
}
function toUint128(bytes memory _bytes, uint256 _start) internal pure returns (uint128) {
require(_bytes.length >= _start + 16, "toUint128_outOfBounds");
uint128 tempUint;
assembly {
tempUint := mload(add(add(_bytes, 0x10), _start))
}
return tempUint;
}
function toUint256(bytes memory _bytes, uint256 _start) internal pure returns (uint256) {
require(_bytes.length >= _start + 32, "toUint256_outOfBounds");
uint256 tempUint;
assembly {
tempUint := mload(add(add(_bytes, 0x20), _start))
}
return tempUint;
}
function toBytes32(bytes memory _bytes, uint256 _start) internal pure returns (bytes32) {
require(_bytes.length >= _start + 32, "toBytes32_outOfBounds");
bytes32 tempBytes32;
assembly {
tempBytes32 := mload(add(add(_bytes, 0x20), _start))
}
return tempBytes32;
}
function equal(bytes memory _preBytes, bytes memory _postBytes) internal pure returns (bool) {
bool success = true;
assembly {
let length := mload(_preBytes)
// if lengths don't match the arrays are not equal
switch eq(length, mload(_postBytes))
case 1 {
// cb is a circuit breaker in the for loop since there's
// no said feature for inline assembly loops
// cb = 1 - don't breaker
// cb = 0 - break
let cb := 1
let mc := add(_preBytes, 0x20)
let end := add(mc, length)
for {
let cc := add(_postBytes, 0x20)
// the next line is the loop condition:
// while(uint256(mc < end) + cb == 2)
} eq(add(lt(mc, end), cb), 2) {
mc := add(mc, 0x20)
cc := add(cc, 0x20)
} {
// if any of these checks fails then arrays are not equal
if iszero(eq(mload(mc), mload(cc))) {
// unsuccess:
success := 0
cb := 0
}
}
}
default {
// unsuccess:
success := 0
}
}
return success;
}
function equalStorage(bytes storage _preBytes, bytes memory _postBytes) internal view returns (bool) {
bool success = true;
assembly {
// we know _preBytes_offset is 0
let fslot := sload(_preBytes.slot)
// Decode the length of the stored array like in concatStorage().
let slength := div(and(fslot, sub(mul(0x100, iszero(and(fslot, 1))), 1)), 2)
let mlength := mload(_postBytes)
// if lengths don't match the arrays are not equal
switch eq(slength, mlength)
case 1 {
// slength can contain both the length and contents of the array
// if length < 32 bytes so let's prepare for that
// v. http://solidity.readthedocs.io/en/latest/miscellaneous.html#layout-of-state-variables-in-storage
if iszero(iszero(slength)) {
switch lt(slength, 32)
case 1 {
// blank the last byte which is the length
fslot := mul(div(fslot, 0x100), 0x100)
if iszero(eq(fslot, mload(add(_postBytes, 0x20)))) {
// unsuccess:
success := 0
}
}
default {
// cb is a circuit breaker in the for loop since there's
// no said feature for inline assembly loops
// cb = 1 - don't breaker
// cb = 0 - break
let cb := 1
// get the keccak hash to get the contents of the array
mstore(0x0, _preBytes.slot)
let sc := keccak256(0x0, 0x20)
let mc := add(_postBytes, 0x20)
let end := add(mc, mlength)
// the next line is the loop condition:
// while(uint256(mc < end) + cb == 2)
for {
} eq(add(lt(mc, end), cb), 2) {
sc := add(sc, 1)
mc := add(mc, 0x20)
} {
if iszero(eq(sload(sc), mload(mc))) {
// unsuccess:
success := 0
cb := 0
}
}
}
}
}
default {
// unsuccess:
success := 0
}
}
return success;
}
}

View File

@ -0,0 +1,23 @@
// SPDX-License-Identifier: Apache2
pragma solidity ^0.8.3;
import "../interfaces/IWormhole.sol";
import "./NFTState.sol";
contract NFTGetters is NFTState {
function isTransferCompleted(bytes32 hash) public view returns (bool) {
return _wormholeState.completedTransfers[hash];
}
function nftContract(uint16 chainId_) public view returns (bytes32) {
return _wormholeState.nftContracts[chainId_];
}
function wormhole() public view returns (IWormhole) {
return IWormhole(_wormholeState.wormhole);
}
function chainId() public view returns (uint16) {
return _wormholeState.chainId;
}
}

View File

@ -0,0 +1,22 @@
// SPDX-License-Identifier: Apache2
pragma solidity ^0.8.3;
import "./NFTState.sol";
contract NFTSetters is NFTState {
function _setWormhole(address wh) internal {
_wormholeState.wormhole = payable(wh);
}
function _setChainId(uint16 chainId_) internal {
_wormholeState.chainId = chainId_;
}
function _setTransferCompleted(bytes32 hash) internal {
_wormholeState.completedTransfers[hash] = true;
}
function _setNftContract(uint16 chainId, bytes32 nftContract) internal {
_wormholeState.nftContracts[chainId] = nftContract;
}
}

View File

@ -0,0 +1,8 @@
// SPDX-License-Identifier: Apache2
pragma solidity ^0.8.3;
import "./NFTStorage.sol";
contract NFTState {
NFTStorage.State _wormholeState;
}

View File

@ -0,0 +1,14 @@
// SPDX-License-Identifier: Apache2
pragma solidity ^0.8.3;
contract NFTStorage {
struct State {
// Wormhole bridge contract address and chainId
address payable wormhole;
uint16 chainId;
// Mapping of consumed token transfers
mapping(bytes32 => bool) completedTransfers;
// Mapping of NFT contracts on other chains
mapping(uint16 => bytes32) nftContracts;
}
}

View File

@ -0,0 +1,14 @@
// SPDX-License-Identifier: Apache2
pragma solidity ^0.8.3;
contract NFTStructs {
struct Transfer {
// PayloadID uint8 = 1
// TokenID of the token
uint256 tokenId;
// Address of the recipient. Left-zero-padded if shorter than 32 bytes
bytes32 to;
// Chain ID of the recipient
uint16 toChain;
}
}

View File

@ -0,0 +1,39 @@
require("@nomiclabs/hardhat-waffle");
require('@openzeppelin/hardhat-upgrades');
// This is a sample Hardhat task. To learn how to create your own go to
// https://hardhat.org/guides/create-task.html
task("accounts", "Prints the list of accounts", async () => {
const accounts = await ethers.getSigners();
for (const account of accounts) {
console.log(account.address);
}
});
// You need to export an object to set up your config
// Go to https://hardhat.org/config/ to learn more
/**
* @type import('hardhat/config').HardhatUserConfig
*/
module.exports = {
solidity: {
version: '0.8.3',
settings: {
optimizer: {
enabled: true,
runs: 200,
},
},
},
networks: {
hardhat: {
blockGasLimit: 10000000,
},
localhost: {
url: "http://localhost:8545"
}
}
};

15611
wormhole721/package-lock.json generated Normal file

File diff suppressed because it is too large Load Diff

37
wormhole721/package.json Normal file
View File

@ -0,0 +1,37 @@
{
"name": "@ndujalabs/wormhole721",
"version": "0.2.0",
"description": "ERC721 NFTs using Wormhole to be natively cross-chain",
"publishConfig": {
"access": "public"
},
"files": [
"/contracts/**/*.sol"
],
"scripts": {
"test": "npx hardhat test",
"compile": "npx hardhat compile",
"lint": "prettier --write 'contracts/**/*.sol' && solhint 'contracts/**/*.sol'"
},
"authors": [
"Francesco Sullo <fs@ndujalabs.com>",
"Emanuele Cesena <ec@ndujalabs.com>"
],
"license": "Apache2",
"devDependencies": {
"@nomiclabs/hardhat-ethers": "^2.0.2",
"@nomiclabs/hardhat-etherscan": "^2.1.1",
"@nomiclabs/hardhat-waffle": "^2.0.1",
"@openzeppelin/contracts": "^4.0.0",
"@openzeppelin/contracts-upgradeable": "^4.4.1",
"@openzeppelin/hardhat-upgrades": "^1.12.0",
"@poanet/solidity-flattener": "^3.0.6",
"chai": "^4.3.4",
"ethereum-waffle": "^3.3.0",
"ethers": "^5.0.32",
"hardhat": "^2.1.2",
"prettier": "^2.3.2",
"prettier-plugin-solidity": "^1.0.0-beta.17",
"solhint": "^3.3.6"
}
}

View File

@ -0,0 +1,25 @@
const {expect, assert} = require("chai")
const {deployContract} = require('./helpers')
describe("Wormhole721", function () {
let wormhole721
let erc721NotPlayableMock
let playerMock
let owner, holder
before(async function () {
[owner, holder] = await ethers.getSigners()
})
beforeEach(async function () {
// wormhole721 = ...
})
it("should be implemented", async function () {
return true
})
})

View File

@ -0,0 +1,70 @@
const {assert} = require("chai");
const Helpers = {
initEthers(ethers) {
this.ethers = ethers
},
async assertThrowsMessage(promise, message) {
try {
await promise
console.log('It did not throw :-(')
assert.isTrue(false)
} catch (e) {
const shouldBeTrue = e.message.indexOf(message) > -1
if (!shouldBeTrue) {
console.error('Expected:', message)
console.error('Returned:', e.message)
// console.log(e)
}
assert.isTrue(shouldBeTrue)
}
},
async deployContractBy(contractName, owner, ...args) {
const Contract = await this.ethers.getContractFactory(contractName)
const contract = await Contract.connect(owner).deploy(...args)
await contract.deployed()
return contract
},
async deployContract(contractName, ...args) {
const Contract = await this.ethers.getContractFactory(contractName)
const contract = await Contract.deploy(...args)
await contract.deployed()
return contract
},
async deployContractUpgradeable(contractName, args) {
const Contract = await this.ethers.getContractFactory(contractName)
const contract = await upgrades.deployProxy(Contract, args);
await contract.deployed()
return contract
},
async signPackedData(
hash,
// hardhat account #4, starting from #0
privateKey = '0x47e179ec197488593b187f80a00eb0da91f1b9d0b13f8733639f19c30a34926a'
) {
const signingKey = new this.ethers.utils.SigningKey(privateKey)
const signedDigest = signingKey.signDigest(hash)
return this.ethers.utils.joinSignature(signedDigest)
},
async getTimestamp() {
return (await this.ethers.provider.getBlock()).timestamp
},
addr0: '0x0000000000000000000000000000000000000000',
async increaseBlockTimestampBy(offset) {
await this.ethers.provider.send("evm_increaseTime", [offset])
await this.ethers.provider.send('evm_mine')
}
}
module.exports = Helpers