Skip to content

Commit

Permalink
add NFT support
Browse files Browse the repository at this point in the history
  • Loading branch information
zouxyan committed Dec 15, 2020
1 parent c9212e4 commit cf1dc4a
Show file tree
Hide file tree
Showing 25 changed files with 1,749 additions and 15 deletions.
26 changes: 26 additions & 0 deletions contracts/core/assets/erc721_template/CrossChainNFTMapping.sol
Original file line number Diff line number Diff line change
@@ -0,0 +1,26 @@
pragma solidity ^0.6.0;

import "./../../../libs/token/ERC721/ERC721.sol";
import "./../../../libs/utils/Address.sol";

contract CrossChainNFTMapping is ERC721 {
using Address for address;

address private lpAddr;

constructor (address _lpAddr, string memory name, string memory symbol) public ERC721(name, symbol) {
require(_lpAddr.isContract(), "lockproxy address must be contract.");
lpAddr = _lpAddr;
}

modifier onlyProxy() {
require(msg.sender == lpAddr, "");
_;
}

function mintWithURI(address to, uint256 tokenId, string memory uri) public onlyProxy {
require(!_exists(tokenId), "token id already exist");
_safeMint(to, tokenId);
_setTokenURI(tokenId, uri);
}
}
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
pragma solidity ^0.5.0;
pragma solidity >=0.5.0;

/**
* @dev Interface of the EthCrossChainManager contract for business contract like LockProxy to request cross chain transaction
Expand Down
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
pragma solidity ^0.5.0;
pragma solidity >=0.5.0;

/**
* @dev Interface of the EthCrossChainManagerProxy for business contract like LockProxy to obtain the reliable EthCrossChainManager contract hash.
Expand Down
14 changes: 7 additions & 7 deletions contracts/core/lock_proxy/LockProxy.sol
Original file line number Diff line number Diff line change
Expand Up @@ -154,18 +154,18 @@ contract LockProxy is Ownable {
return true;
}


function _transferERC20ToContract(address fromAssetHash, address fromAddress, address toAddress, uint256 amount) internal returns (bool) {
IERC20 erc20Token = IERC20(fromAssetHash);
IERC20 erc20Token = IERC20(fromAssetHash);
// require(erc20Token.transferFrom(fromAddress, toAddress, amount), "trasnfer ERC20 Token failed!");
erc20Token.safeTransferFrom(fromAddress, toAddress, amount);
return true;
erc20Token.safeTransferFrom(fromAddress, toAddress, amount);
return true;
}

function _transferERC20FromContract(address toAssetHash, address toAddress, uint256 amount) internal returns (bool) {
IERC20 erc20Token = IERC20(toAssetHash);
IERC20 erc20Token = IERC20(toAssetHash);
// require(erc20Token.transfer(toAddress, amount), "trasnfer ERC20 Token failed!");
erc20Token.safeTransfer(toAddress, amount);
return true;
erc20Token.safeTransfer(toAddress, amount);
return true;
}

function _serializeTxArgs(TxArgs memory args) internal pure returns (bytes memory) {
Expand Down
170 changes: 170 additions & 0 deletions contracts/core/lock_proxy/NFTLockProxy.sol
Original file line number Diff line number Diff line change
@@ -0,0 +1,170 @@
pragma solidity ^0.6.0;

import "./../../libs/ownership/Ownable.sol";
import "./../../libs/common/ZeroCopySink.sol";
import "./../../libs/common/ZeroCopySource.sol";
import "./../../libs/utils/Utils.sol";
import "./../../libs/utils/Address.sol";
import "./../../libs/token/ERC721/IERC721Metadata.sol";
import "./../../libs/token/ERC721/IERC721Receiver.sol";
import "./../../libs/math/SafeMath.sol";
import "./../cross_chain_manager/interface/IEthCrossChainManager.sol";
import "./../cross_chain_manager/interface/IEthCrossChainManagerProxy.sol";


contract NFTLockProxy is IERC721Receiver, Ownable {
using SafeMath for uint;
using Address for address;

struct TxArgs {
bytes toAssetHash;
bytes toAddress;
uint256 tokenId;
bytes tokenURI;
}

address public managerProxyContract;
mapping(uint64 => bytes) public proxyHashMap;
mapping(address => mapping(uint64 => bytes)) public assetHashMap;
mapping(address => bool) safeTransfer;

event SetManagerProxyEvent(address manager);
event BindProxyEvent(uint64 toChainId, bytes targetProxyHash);
event BindAssetEvent(address fromAssetHash, uint64 toChainId, bytes targetProxyHash);
event UnlockEvent(address toAssetHash, address toAddress, uint256 tokenId);
event LockEvent(address fromAssetHash, address fromAddress, bytes toAssetHash, bytes toAddress, uint64 toChainId, uint256 tokenId);

modifier onlyManagerContract() {
IEthCrossChainManagerProxy ieccmp = IEthCrossChainManagerProxy(managerProxyContract);
require(_msgSender() == ieccmp.getEthCrossChainManager(), "msgSender is not EthCrossChainManagerContract");
_;
}

function setManagerProxy(address ethCCMProxyAddr) onlyOwner public {
managerProxyContract = ethCCMProxyAddr;
emit SetManagerProxyEvent(managerProxyContract);
}

function bindProxyHash(uint64 toChainId, bytes memory targetProxyHash) onlyOwner public returns (bool) {
proxyHashMap[toChainId] = targetProxyHash;
emit BindProxyEvent(toChainId, targetProxyHash);
return true;
}

function bindAssetHash(address fromAssetHash, uint64 toChainId, bytes memory toAssetHash) onlyOwner public returns (bool) {
assetHashMap[fromAssetHash][toChainId] = toAssetHash;
emit BindAssetEvent(fromAssetHash, toChainId, toAssetHash);
return true;
}

// /* @notice This function is meant to be invoked by the ETH crosschain management contract,
// * then mint a certin amount of tokens to the designated address since a certain amount
// * was burnt from the source chain invoker.
// * @param argsBs The argument bytes recevied by the ethereum lock proxy contract, need to be deserialized.
// * based on the way of serialization in the source chain proxy contract.
// * @param fromContractAddr The source chain contract address
// * @param fromChainId The source chain id
// */
function unlock(bytes memory argsBs, bytes memory fromContractAddr, uint64 fromChainId) public onlyManagerContract returns (bool) {
TxArgs memory args = _deserializeTxArgs(argsBs);

require(fromContractAddr.length != 0, "from proxy contract address cannot be empty");
require(Utils.equalStorage(proxyHashMap[fromChainId], fromContractAddr), "From Proxy contract address error!");

require(args.toAssetHash.length != 0, "toAssetHash cannot be empty");
address toAssetHash = Utils.bytesToAddress(args.toAssetHash);

require(args.toAddress.length != 0, "toAddress cannot be empty");
address toAddress = Utils.bytesToAddress(args.toAddress);

bool success;
bytes memory res;
address owner;
bytes memory raw = abi.encodeWithSignature("ownerOf(uint256)", args.tokenId);
(success, res) = toAssetHash.call(raw);
if (success) {
owner = abi.decode(res, (address));
require(owner == address(this) || owner == address(0), "your token ID is not hold by lockproxy.");
if (owner == address(this)) {
raw = abi.encodeWithSignature("safeTransferFrom(address,address,uint256)", address(this), toAddress, args.tokenId);
(success, ) = toAssetHash.call(raw);
require(success, "failed to call safeTransferFrom");
}
}
if (!success || owner == address(0)) {
raw = abi.encodeWithSignature("mintWithURI(address,uint256,string)", toAddress, args.tokenId, string(args.tokenURI));
(success, ) = toAssetHash.call(raw);
require(success, "failed to call mintWithURI to mint a new mapping NFT");
}

emit UnlockEvent(toAssetHash, toAddress, args.tokenId);
return true;
}

function onERC721Received(address operator, address from, uint256 tokenId, bytes calldata data) public override returns (bytes4) {
address fromAssetHash = _msgSender();
require(data.length > 0, "length of toAddress can't be zero. ");
require(fromAssetHash.isContract(), "caller must be a contract. ");

bytes memory toAddress;
uint64 toChainId;
bytes memory toAssetHash;
{
(toAddress, toChainId) = _deserializeCallData(data);
toAssetHash = assetHashMap[fromAssetHash][toChainId];
require(toAssetHash.length != 0, "empty illegal toAssetHash");

IERC721Metadata nft = IERC721Metadata(fromAssetHash);
require(nft.ownerOf(tokenId) == address(this), "wrong owner for this token ID");

string memory uri = nft.tokenURI(tokenId);
TxArgs memory txArgs = TxArgs({
toAssetHash: toAssetHash,
toAddress: toAddress,
tokenId: tokenId,
tokenURI: bytes(uri)
});
bytes memory txData = _serializeTxArgs(txArgs);
IEthCrossChainManager eccm = IEthCrossChainManager(IEthCrossChainManagerProxy(managerProxyContract).getEthCrossChainManager());

bytes memory toProxyHash = proxyHashMap[toChainId];
require(toProxyHash.length != 0, "empty illegal toProxyHash");
require(eccm.crossChain(toChainId, toProxyHash, "unlock", txData), "EthCrossChainManager crossChain executed error!");
}
{
emit LockEvent(fromAssetHash, from, toAssetHash, toAddress, toChainId, tokenId);
}

return this.onERC721Received.selector;
}

function _serializeTxArgs(TxArgs memory args) internal pure returns (bytes memory) {
bytes memory buff;
buff = abi.encodePacked(
ZeroCopySink.WriteVarBytes(args.toAssetHash),
ZeroCopySink.WriteVarBytes(args.toAddress),
ZeroCopySink.WriteUint256(args.tokenId),
ZeroCopySink.WriteVarBytes(args.tokenURI)
);
return buff;
}

function _deserializeTxArgs(bytes memory valueBs) internal pure returns (TxArgs memory) {
TxArgs memory args;
uint256 off = 0;
(args.toAssetHash, off) = ZeroCopySource.NextVarBytes(valueBs, off);
(args.toAddress, off) = ZeroCopySource.NextVarBytes(valueBs, off);
(args.tokenId, off) = ZeroCopySource.NextUint256(valueBs, off);
(args.tokenURI, off) = ZeroCopySource.NextVarBytes(valueBs, off);
return args;
}

function _deserializeCallData(bytes memory valueBs) internal pure returns (bytes memory, uint64) {
bytes memory toAddress;
uint64 chainId;
uint256 off = 0;
(toAddress, off) = ZeroCopySource.NextVarBytes(valueBs, off);
(chainId, off) = ZeroCopySource.NextUint64(valueBs, off);
return (toAddress, chainId);
}
}
2 changes: 1 addition & 1 deletion contracts/libs/GSN/Context.sol
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
pragma solidity ^0.5.0;
pragma solidity >=0.5.0;

/*
* @dev Provides information about the current execution context, including the
Expand Down
28 changes: 27 additions & 1 deletion contracts/libs/common/ZeroCopySink.sol
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
pragma solidity ^0.5.0;
pragma solidity >=0.5.0;

/**
* @dev Wrappers over encoding and serialization operation into bytes from bassic types in Solidity for PolyNetwork cross chain utility.
Expand Down Expand Up @@ -162,6 +162,32 @@ library ZeroCopySink {
return buff;
}

/* @notice Convert limited uint256 value into bytes
* @param v The uint256 value
* @return Converted bytes array
*/
function WriteUint256(uint256 v) internal pure returns (bytes memory) {
require(v <= uint256(-1), "Value exceeds uint256 range");
bytes memory buff;

assembly{
buff := mload(0x40)
let byteLen := 0x20
mstore(buff, byteLen)
for {
let mindex := 0x00
let vindex := 0x1f
} lt(mindex, byteLen) {
mindex := add(mindex, 0x01)
vindex := sub(vindex, 0x01)
}{
mstore8(add(add(buff, 0x20), mindex), byte(vindex, v))
}
mstore(0x40, add(buff, 0x40))
}
return buff;
}

/* @notice Encode bytes format data into bytes
* @param data The bytes array data
* @return Encoded bytes array
Expand Down
26 changes: 25 additions & 1 deletion contracts/libs/common/ZeroCopySource.sol
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
pragma solidity ^0.5.0;
pragma solidity >=0.5.0;

/**
* @dev Wrappers over decoding and deserialization operation from bytes into bassic types in Solidity for PolyNetwork cross chain utility.
Expand Down Expand Up @@ -173,6 +173,30 @@ library ZeroCopySource {
require(v <= 0x7fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff, "Value exceeds the range");
return (v, offset + 32);
}

function NextUint256(bytes memory buff, uint256 offset) internal pure returns (uint256, uint256) {
require(offset + 32 <= buff.length && offset < offset + 32, "NextUint256, offset exceeds maximum");
uint256 v;
assembly {
let tmpbytes := mload(0x40)
let byteLen := 0x20
for {
let tindex := 0x00
let bindex := sub(byteLen, 0x01)
let bvalue := mload(add(add(buff, 0x20), offset))
} lt(tindex, byteLen) {
tindex := add(tindex, 0x01)
bindex := sub(bindex, 0x01)
}{
mstore8(add(tmpbytes, tindex), byte(bindex, bvalue))
}
mstore(0x40, add(tmpbytes, byteLen))
v := mload(tmpbytes)
}
require(v <= uint256(-1), "Value exceeds the range");
return (v, offset + 32);
}

/* @notice Read next variable bytes starting from offset,
the decoding rule coming from multi-chain
* @param buff Source bytes array
Expand Down
54 changes: 54 additions & 0 deletions contracts/libs/introspection/ERC165.sol
Original file line number Diff line number Diff line change
@@ -0,0 +1,54 @@
// SPDX-License-Identifier: MIT

pragma solidity ^0.6.0;

import "./IERC165.sol";

/**
* @dev Implementation of the {IERC165} interface.
*
* Contracts may inherit from this and call {_registerInterface} to declare
* their support of an interface.
*/
contract ERC165 is IERC165 {
/*
* bytes4(keccak256('supportsInterface(bytes4)')) == 0x01ffc9a7
*/
bytes4 private constant _INTERFACE_ID_ERC165 = 0x01ffc9a7;

/**
* @dev Mapping of interface ids to whether or not it's supported.
*/
mapping(bytes4 => bool) private _supportedInterfaces;

constructor () internal {
// Derived contracts need only register support for their own interfaces,
// we register support for ERC165 itself here
_registerInterface(_INTERFACE_ID_ERC165);
}

/**
* @dev See {IERC165-supportsInterface}.
*
* Time complexity O(1), guaranteed to always use less than 30 000 gas.
*/
function supportsInterface(bytes4 interfaceId) public view override returns (bool) {
return _supportedInterfaces[interfaceId];
}

/**
* @dev Registers the contract as an implementer of the interface defined by
* `interfaceId`. Support of the actual ERC165 interface is automatic and
* registering its interface id is not required.
*
* See {IERC165-supportsInterface}.
*
* Requirements:
*
* - `interfaceId` cannot be the ERC165 invalid interface (`0xffffffff`).
*/
function _registerInterface(bytes4 interfaceId) internal virtual {
require(interfaceId != 0xffffffff, "ERC165: invalid interface id");
_supportedInterfaces[interfaceId] = true;
}
}
24 changes: 24 additions & 0 deletions contracts/libs/introspection/IERC165.sol
Original file line number Diff line number Diff line change
@@ -0,0 +1,24 @@
// SPDX-License-Identifier: MIT

pragma solidity ^0.6.0;

/**
* @dev Interface of the ERC165 standard, as defined in the
* https://eips.ethereum.org/EIPS/eip-165[EIP].
*
* Implementers can declare support of contract interfaces, which can then be
* queried by others ({ERC165Checker}).
*
* For an implementation, see {ERC165}.
*/
interface IERC165 {
/**
* @dev Returns true if this contract implements the interface defined by
* `interfaceId`. See the corresponding
* https://eips.ethereum.org/EIPS/eip-165#how-interfaces-are-identified[EIP section]
* to learn more about how these ids are created.
*
* This function call must use less than 30 000 gas.
*/
function supportsInterface(bytes4 interfaceId) external view returns (bool);
}
Loading

0 comments on commit cf1dc4a

Please sign in to comment.