Skip to content

Commit 61c308a

Browse files
authored
Merge pull request #6 from QEDK/main
Add support for ERC721, ERC1155, modify ERC20
2 parents a29bc91 + de653c9 commit 61c308a

28 files changed

+1927
-10
lines changed
Lines changed: 130 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,130 @@
1+
// SPDX-License-Identifier: MIT
2+
pragma solidity 0.7.3;
3+
4+
import {IFxERC1155} from "../../tokens/IFxERC1155.sol";
5+
import {ERC1155Holder} from "../../lib/ERC1155Holder.sol" ;
6+
import { Create2 } from "../..//lib/Create2.sol";
7+
import { FxBaseChildTunnel } from "../../tunnel/FxBaseChildTunnel.sol";
8+
9+
contract FxERC1155ChildTunnel is FxBaseChildTunnel, Create2, ERC1155Holder {
10+
bytes32 public constant DEPOSIT = keccak256("DEPOSIT");
11+
bytes32 public constant DEPOSIT_BATCH = keccak256("DEPOSIT_BATCH");
12+
bytes32 public constant WITHDRAW = keccak256("WITHDRAW");
13+
bytes32 public constant WITHDRAW_BATCH = keccak256("WITHDRAW_BATCH");
14+
bytes32 public constant MAP_TOKEN = keccak256("MAP_TOKEN");
15+
//string public constant URI = "FXERC1155URI" ;
16+
17+
event TokenMapped(address indexed rootToken, address indexed childToken);
18+
// root to child token
19+
mapping(address => address) public rootToChildToken;
20+
address public tokenTemplate;
21+
22+
constructor(address _fxChild, address _tokenTemplate) FxBaseChildTunnel(_fxChild) {
23+
tokenTemplate = _tokenTemplate;
24+
require(_isContract(_tokenTemplate), "Token template is not contract");
25+
}
26+
27+
function withdraw(address childToken, uint256 id, uint256 amount, bytes memory data) public {
28+
IFxERC1155 childTokenContract = IFxERC1155(childToken);
29+
address rootToken = childTokenContract.connectedToken();
30+
31+
require(
32+
childToken != address(0x0) &&
33+
rootToken != address(0x0) &&
34+
childToken == rootToChildToken[rootToken],
35+
"FxERC1155ChildTunnel: NO_MAPPED_TOKEN"
36+
);
37+
38+
childTokenContract.burn(msg.sender, id, amount);
39+
40+
bytes memory message = abi.encode(WITHDRAW, abi.encode(rootToken, childToken, msg.sender, id, amount, data));
41+
_sendMessageToRoot(message);
42+
}
43+
44+
function withdrawBatch(address childToken, uint256[] memory ids, uint256[] memory amounts, bytes memory data) public {
45+
IFxERC1155 childTokenContract = IFxERC1155(childToken);
46+
address rootToken = childTokenContract.connectedToken();
47+
48+
require(
49+
childToken != address(0x0) &&
50+
rootToken != address(0x0) &&
51+
childToken == rootToChildToken[rootToken],
52+
"FxERC1155ChildTunnel: NO_MAPPED_TOKEN"
53+
);
54+
55+
childTokenContract.burnBatch(msg.sender, ids, amounts);
56+
57+
bytes memory message = abi.encode(WITHDRAW_BATCH, abi.encode(rootToken, childToken, msg.sender, ids, amounts, data));
58+
_sendMessageToRoot(message);
59+
}
60+
61+
function _processMessageFromRoot(uint256 /* stateId */, address sender, bytes memory data)
62+
internal
63+
override
64+
validateSender(sender) {
65+
66+
(bytes32 syncType, bytes memory syncData) = abi.decode(data, (bytes32, bytes));
67+
68+
if(syncType == MAP_TOKEN) {
69+
_mapToken(syncData);
70+
}
71+
72+
else if(syncType == DEPOSIT) {
73+
_syncDeposit(syncData);
74+
}
75+
76+
else if(syncType == DEPOSIT_BATCH) {
77+
_syncDepositBatch(syncData);
78+
}
79+
80+
else {
81+
revert("FxERC1155ChildTunnel: INVALID_SYNC_TYPE");
82+
}
83+
}
84+
85+
function _mapToken(bytes memory syncData) internal returns (address) {
86+
(address rootToken, string memory uri) = abi.decode(syncData , (address, string));
87+
88+
address childToken = rootToChildToken[rootToken];
89+
require(childToken == address(0x0), "FxERC1155ChildTunnel: ALREADY_MAPPED");
90+
91+
bytes32 salt = keccak256(abi.encodePacked(rootToken));
92+
childToken = createClone(salt, tokenTemplate);
93+
IFxERC1155(childToken).initialize(address(this),rootToken, string(abi.encodePacked(uri)));
94+
95+
rootToChildToken[rootToken] = childToken;
96+
emit TokenMapped(rootToken, childToken);
97+
98+
return childToken;
99+
}
100+
101+
function _syncDeposit(bytes memory syncData) internal {
102+
(address rootToken, address depositor, address user, uint256 id, uint256 amount, bytes memory data) = abi.decode(syncData, (address, address, address, uint256, uint256, bytes));
103+
104+
address childToken = rootToChildToken[rootToken];
105+
IFxERC1155 childTokenContract = IFxERC1155(childToken);
106+
107+
childTokenContract.mint(user, id, amount, data);
108+
109+
}
110+
111+
function _syncDepositBatch(bytes memory syncData) internal {
112+
(address rootToken, address depositor, address user, uint256[] memory ids, uint256[] memory amounts, bytes memory data) = abi.decode(syncData, (address, address, address, uint256[], uint256[], bytes));
113+
114+
address childToken = rootToChildToken[rootToken];
115+
IFxERC1155 childTokenContract = IFxERC1155(childToken);
116+
117+
childTokenContract.mintBatch(user, ids, amounts, data);
118+
119+
}
120+
121+
122+
function _isContract(address _addr) private view returns (bool){
123+
uint32 size;
124+
assembly {
125+
size := extcodesize(_addr)
126+
}
127+
return (size > 0);
128+
}
129+
130+
}
Lines changed: 112 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,112 @@
1+
// SPDX-License-Identifier: MIT
2+
pragma solidity 0.7.3;
3+
4+
import {ERC1155} from "../../lib/ERC1155.sol";
5+
import {ERC1155Holder} from "../../lib/ERC1155Holder.sol" ;
6+
import { Create2 } from "../../lib/Create2.sol";
7+
import { FxBaseRootTunnel } from "../../tunnel/FxBaseRootTunnel.sol";
8+
9+
contract FxERC1155RootTunnel is FxBaseRootTunnel, Create2, ERC1155Holder {
10+
bytes32 public constant DEPOSIT = keccak256("DEPOSIT");
11+
bytes32 public constant DEPOSIT_BATCH = keccak256("DEPOSIT_BATCH");
12+
bytes32 public constant WITHDRAW = keccak256("WITHDRAW");
13+
bytes32 public constant WITHDRAW_BATCH = keccak256("WITHDRAW_BATCH");
14+
bytes32 public constant MAP_TOKEN = keccak256("MAP_TOKEN");
15+
16+
event TokenMapped(address indexed rootToken, address indexed childToken);
17+
18+
mapping(address => address) public rootToChildTokens;
19+
bytes32 public childTokenTemplateCodeHash;
20+
21+
constructor(address _checkpointManager, address _fxRoot, address _fxERC1155Token) FxBaseRootTunnel(_checkpointManager, _fxRoot) {
22+
childTokenTemplateCodeHash = keccak256(minimalProxyCreationCode(_fxERC1155Token));
23+
}
24+
25+
function mapToken(address rootToken) public {
26+
require(rootToChildTokens[rootToken] == address(0x0), "FxERC1155RootTunnel: ALREADY_MAPPED");
27+
28+
ERC1155 rootTokenContract = ERC1155(rootToken);
29+
string memory uri = rootTokenContract.uri(0); //token Id?
30+
31+
// MAP_TOKEN, encode(rootToken,uri)
32+
bytes memory message = abi.encode(MAP_TOKEN, abi.encode(rootToken,uri));
33+
_sendMessageToChild(message);
34+
35+
// compute child token address before deployment using create2
36+
bytes32 salt = keccak256(abi.encodePacked(rootToken));
37+
address childToken = computedCreate2Address(salt, childTokenTemplateCodeHash, fxChildTunnel);
38+
39+
// add into mapped tokens
40+
rootToChildTokens[rootToken] = childToken;
41+
emit TokenMapped(rootToken, childToken);
42+
}
43+
44+
function deposit(address rootToken, address user, uint256 id, uint256 amount, bytes memory data) public {
45+
// map token if not mapped
46+
if (rootToChildTokens[rootToken] == address(0x0)) {
47+
mapToken(rootToken);
48+
}
49+
50+
// transfer from depositor to this contract
51+
ERC1155(rootToken).safeTransferFrom(
52+
msg.sender, // depositor
53+
address(this), // manager contract
54+
id,
55+
amount,
56+
data
57+
);
58+
59+
// DEPOSIT, encode(rootToken, depositor, user, id, amount, extra data)
60+
bytes memory message = abi.encode(DEPOSIT, abi.encode(rootToken, msg.sender, user, id, amount, data));
61+
_sendMessageToChild(message);
62+
}
63+
64+
function depositBatch(address rootToken, address user, uint256[] memory ids, uint256[] memory amounts, bytes memory data) public {
65+
// map token if not mapped
66+
if (rootToChildTokens[rootToken] == address(0x0)) {
67+
mapToken(rootToken);
68+
}
69+
70+
// transfer from depositor to this contract
71+
ERC1155(rootToken).safeBatchTransferFrom(
72+
msg.sender, // depositor
73+
address(this), // manager contract
74+
ids,
75+
amounts,
76+
data
77+
);
78+
79+
// DEPOSIT_BATCH, encode(rootToken, depositor, user, id, amount, extra data)
80+
bytes memory message = abi.encode(DEPOSIT_BATCH, abi.encode(rootToken, msg.sender, user, ids, amounts, data));
81+
_sendMessageToChild(message);
82+
}
83+
84+
function _processMessageFromChild(bytes memory data) internal override {
85+
(bytes32 syncType, bytes memory syncData) = abi.decode(data, (bytes32, bytes));
86+
87+
if(syncType == WITHDRAW) {
88+
_syncWithdraw(syncData);
89+
}
90+
91+
else if(syncType == WITHDRAW_BATCH) {
92+
_syncBatchWithdraw(syncData);
93+
}
94+
95+
else {
96+
revert("FxERC1155RootTunnel: INVALID_SYNC_TYPE");
97+
}
98+
99+
}
100+
101+
function _syncWithdraw(bytes memory syncData) internal {
102+
(address rootToken, address childToken, address user, uint256 id, uint256 amount, bytes memory data) = abi.decode(syncData, (address, address, address, uint256, uint256, bytes));
103+
require(rootToChildTokens[rootToken] == childToken, "FxERC1155RootTunnel: INVALID_MAPPING_ON_EXIT");
104+
ERC1155(rootToken).safeTransferFrom(address(this), user, id, amount, data);
105+
}
106+
107+
function _syncBatchWithdraw(bytes memory syncData) internal {
108+
(address rootToken, address childToken, address user, uint256[] memory ids, uint256[] memory amounts, bytes memory data) = abi.decode(syncData, (address, address, address, uint256[], uint256[], bytes));
109+
require(rootToChildTokens[rootToken] == childToken, "FxERC1155RootTunnel: INVALID_MAPPING_ON_EXIT");
110+
ERC1155(rootToken).safeBatchTransferFrom(address(this), user, ids, amounts, data);
111+
}
112+
}

contracts/examples/erc20-transfer/FxERC20RootTunnel.sol

100644100755
Lines changed: 6 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -5,14 +5,16 @@ import { ERC20 } from "../../lib/ERC20.sol";
55
import { Create2 } from "../../lib/Create2.sol";
66
import { FxBaseRootTunnel } from "../../tunnel/FxBaseRootTunnel.sol";
77

8-
/**
8+
/**
99
* @title FxERC20RootTunnel
1010
*/
1111
contract FxERC20RootTunnel is FxBaseRootTunnel, Create2 {
1212
// maybe DEPOSIT and MAP_TOKEN can be reduced to bytes4
1313
bytes32 public constant DEPOSIT = keccak256("DEPOSIT");
1414
bytes32 public constant MAP_TOKEN = keccak256("MAP_TOKEN");
1515

16+
event TokenMapped(address indexed rootToken, address indexed childToken);
17+
1618
mapping(address => address) public rootToChildTokens;
1719
bytes32 public childTokenTemplateCodeHash;
1820

@@ -45,6 +47,7 @@ contract FxERC20RootTunnel is FxBaseRootTunnel, Create2 {
4547

4648
// add into mapped tokens
4749
rootToChildTokens[rootToken] = childToken;
50+
emit TokenMapped(rootToken, childToken);
4851
}
4952

5053
function deposit(address rootToken, address user, uint256 amount, bytes memory data) public {
@@ -71,9 +74,8 @@ contract FxERC20RootTunnel is FxBaseRootTunnel, Create2 {
7174
// validate mapping for root to child
7275
require(rootToChildTokens[rootToken] == childToken, "FxERC20RootTunnel: INVALID_MAPPING_ON_EXIT");
7376

74-
// transfer from tokens to
75-
ERC20(rootToken).transferFrom(
76-
address(this),
77+
// transfer from tokens to
78+
ERC20(rootToken).transfer(
7779
to,
7880
amount
7981
);
Lines changed: 116 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,116 @@
1+
// SPDX-License-Identifier: MIT
2+
pragma solidity 0.7.3;
3+
4+
import { FxBaseChildTunnel } from '../../tunnel/FxBaseChildTunnel.sol';
5+
import { Create2 } from '../../lib/Create2.sol';
6+
import { IFxERC721 } from '../../tokens/IFxERC721.sol';
7+
import { IERC721Receiver } from "../../lib/IERC721Receiver.sol";
8+
9+
/**
10+
* @title FxERC721ChildTunnel
11+
*/
12+
contract FxERC721ChildTunnel is FxBaseChildTunnel, Create2, IERC721Receiver {
13+
bytes32 public constant DEPOSIT = keccak256("DEPOSIT");
14+
bytes32 public constant MAP_TOKEN = keccak256("MAP_TOKEN");
15+
string public constant SUFFIX_NAME = " (FXERC721)";
16+
string public constant PREFIX_SYMBOL = "fx";
17+
18+
// event for token maping
19+
event TokenMapped(address indexed rootToken, address indexed childToken);
20+
// root to child token
21+
mapping(address => address) public rootToChildToken;
22+
// token template
23+
address public tokenTemplate;
24+
25+
constructor(address _fxChild, address _tokenTemplate) FxBaseChildTunnel(_fxChild) {
26+
tokenTemplate = _tokenTemplate;
27+
require(_isContract(_tokenTemplate), "Token template is not contract");
28+
}
29+
30+
function onERC721Received(
31+
address /* operator */, address /* from */, uint256 /* tokenId */, bytes calldata /* data */
32+
) external pure override returns (bytes4) {
33+
return this.onERC721Received.selector;
34+
}
35+
36+
function withdraw(address childToken, uint256 tokenId, bytes memory data) public {
37+
IFxERC721 childTokenContract = IFxERC721(childToken);
38+
// child token contract will have root token
39+
address rootToken = childTokenContract.connectedToken();
40+
41+
// validate root and child token mapping
42+
require(
43+
childToken != address(0x0) &&
44+
rootToken != address(0x0) &&
45+
childToken == rootToChildToken[rootToken],
46+
"FxERC721ChildTunnel: NO_MAPPED_TOKEN"
47+
);
48+
49+
// withdraw tokens
50+
childTokenContract.burn(tokenId);
51+
52+
// send message to root regarding token burn
53+
_sendMessageToRoot(abi.encode(rootToken, childToken, msg.sender, tokenId, data));
54+
}
55+
56+
//
57+
// Internal methods
58+
//
59+
60+
function _processMessageFromRoot(uint256 /* stateId */, address sender, bytes memory data)
61+
internal
62+
override
63+
validateSender(sender) {
64+
65+
// decode incoming data
66+
(bytes32 syncType, bytes memory syncData) = abi.decode(data, (bytes32, bytes));
67+
68+
if (syncType == DEPOSIT) {
69+
_syncDeposit(syncData);
70+
} else if (syncType == MAP_TOKEN) {
71+
_mapToken(syncData);
72+
} else {
73+
revert("FxERC721ChildTunnel: INVALID_SYNC_TYPE");
74+
}
75+
}
76+
77+
function _mapToken(bytes memory syncData) internal returns (address) {
78+
(address rootToken, string memory name, string memory symbol) = abi.decode(syncData, (address, string, string));
79+
80+
// get root to child token
81+
address childToken = rootToChildToken[rootToken];
82+
83+
// check if it's already mapped
84+
require(childToken == address(0x0), "FxERC721ChildTunnel: ALREADY_MAPPED");
85+
86+
// deploy new child token
87+
bytes32 salt = keccak256(abi.encodePacked(rootToken));
88+
childToken = createClone(salt, tokenTemplate);
89+
IFxERC721(childToken).initialize(address(this), rootToken, string(abi.encodePacked(name, SUFFIX_NAME)), string(abi.encodePacked(PREFIX_SYMBOL, symbol)));
90+
91+
// map the token
92+
rootToChildToken[rootToken] = childToken;
93+
emit TokenMapped(rootToken, childToken);
94+
95+
// return new child token
96+
return childToken;
97+
}
98+
99+
function _syncDeposit(bytes memory syncData) internal {
100+
(address rootToken, address depositor, address to, uint256 tokenId, bytes memory depositData) = abi.decode(syncData, (address, address, address, uint256, bytes));
101+
address childToken = rootToChildToken[rootToken];
102+
103+
// deposit tokens
104+
IFxERC721 childTokenContract = IFxERC721(childToken);
105+
childTokenContract.mint(to, tokenId, depositData);
106+
}
107+
108+
// check if address is contract
109+
function _isContract(address _addr) private view returns (bool){
110+
uint32 size;
111+
assembly {
112+
size := extcodesize(_addr)
113+
}
114+
return (size > 0);
115+
}
116+
}

0 commit comments

Comments
 (0)