-
Notifications
You must be signed in to change notification settings - Fork 29
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
add payload and contract receiver, support payload in relayer/witness
- Loading branch information
Showing
87 changed files
with
10,996 additions
and
4,745 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,53 @@ | ||
// SPDX-License-Identifier: MIT | ||
pragma solidity >= 0.8.0; | ||
|
||
import "@openzeppelin/contracts/access/Ownable.sol"; | ||
|
||
interface ICrosschainToken { | ||
function withdrawTo(address, uint256) external; | ||
function coToken() external view returns (address); | ||
} | ||
|
||
interface IWEth { | ||
function withdraw(uint256) external; | ||
} | ||
|
||
contract Unwrapper is Ownable { | ||
event Whitelisted(address indexed); | ||
event Blacklisted(address indexed); | ||
mapping(address => bool) public whitelist; | ||
IWEth public weth; | ||
|
||
constructor(address _weth) Ownable() { | ||
weth = IWEth(_weth); | ||
} | ||
|
||
receive() external payable { | ||
} | ||
|
||
function onReceive(address _sender, ICrosschainToken _token, uint256 _amount, bytes calldata _payload) external { | ||
require(whitelist[msg.sender], "invalid caller"); | ||
address recipient = _sender; | ||
if (_payload.length == 32) { | ||
(recipient) = abi.decode(_payload, (address)); | ||
} | ||
address coToken = _token.coToken(); | ||
if (coToken == address(weth)) { | ||
_token.withdrawTo(address(this), _amount); | ||
IWEth(coToken).withdraw(_amount); | ||
payable(recipient).transfer(_amount); | ||
return; | ||
} | ||
_token.withdrawTo(recipient, _amount); | ||
} | ||
|
||
function addWhitelist(address _addr) external onlyOwner { | ||
whitelist[_addr] = true; | ||
emit Whitelisted(_addr); | ||
} | ||
|
||
function removeWhitelist(address _addr) external onlyOwner { | ||
whitelist[_addr] = false; | ||
emit Blacklisted(_addr); | ||
} | ||
} |
78 changes: 78 additions & 0 deletions
78
contracts/iotube/CrosschainTokenCashierWithPayloadRouter.sol
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,78 @@ | ||
// SPDX-License-Identifier: MIT | ||
pragma solidity >=0.8.0; | ||
|
||
interface ICashier { | ||
function depositTo(address _token, address _to, uint256 _amount, bytes memory _payload) external payable; | ||
} | ||
|
||
interface IERC20 { | ||
function balanceOf(address _owner) external view returns(uint256); | ||
} | ||
|
||
interface ICrosschainToken { | ||
function balanceOf(address _account) external returns (uint256); | ||
function deposit(uint256 _amount) external; | ||
function coToken() external view returns (address); | ||
} | ||
|
||
interface WToken { | ||
function deposit() external payable; | ||
} | ||
|
||
contract CrosschainTokenCashierWithPayloadRouter { | ||
|
||
address public ciotx; | ||
WToken public wiotx; | ||
|
||
constructor(ICrosschainToken _ciotx) { | ||
wiotx = WToken(_ciotx.coToken()); | ||
ciotx = address(_ciotx); | ||
_approve(ciotx, address(wiotx)); | ||
} | ||
|
||
function _approve(address _ctoken, address _coToken) private { | ||
require(safeApprove(_coToken, _ctoken, type(uint256).max), "failed to approve allowance to crosschain token"); | ||
} | ||
|
||
function approveCrosschainToken(address _crosschainToken) public { | ||
_approve(_crosschainToken, ICrosschainToken(_crosschainToken).coToken()); | ||
} | ||
|
||
function _depositToCashier(address _token, address _cashier, address _to, uint256 _amount, uint256 _value, bytes memory _payload) private { | ||
ICrosschainToken ctoken = ICrosschainToken(_token); | ||
uint256 originBalance = ctoken.balanceOf(address(this)); | ||
ctoken.deposit(_amount); | ||
uint256 newBalance = ctoken.balanceOf(address(this)); | ||
require(newBalance > originBalance, "invalid balance"); | ||
require(safeApprove(_token, _cashier, newBalance - originBalance), "failed to approve allowance to cashier"); | ||
ICashier(_cashier).depositTo{value: _value}(_token, _to, newBalance - originBalance, _payload); | ||
} | ||
|
||
function depositCoinTo(address _cashier, address _to, uint256 _amount, bytes memory _payload) public payable { | ||
require(_amount <= msg.value, "invalid amount"); | ||
wiotx.deposit{value: _amount}(); | ||
_depositToCashier(ciotx, _cashier, _to, _amount, msg.value - _amount, _payload); | ||
} | ||
|
||
function depositTo(address _cashier, address _crosschainToken, address _to, uint256 _amount, bytes memory _payload) public payable { | ||
require(_crosschainToken != address(0), "invalid token"); | ||
address coToken = ICrosschainToken(_crosschainToken).coToken(); | ||
uint256 originBalance = IERC20(coToken).balanceOf(address(this)); | ||
require(safeTransferFrom(coToken, msg.sender, address(this), _amount), "failed to transfer token"); | ||
uint256 newBalance = IERC20(coToken).balanceOf(address(this)); | ||
require(newBalance > originBalance, "invalid balance"); | ||
_depositToCashier(_crosschainToken, _cashier, _to, newBalance - originBalance, msg.value, _payload); | ||
} | ||
|
||
function safeTransferFrom(address _token, address _from, address _to, uint256 _amount) internal returns (bool) { | ||
// selector = bytes4(keccak256(bytes('transferFrom(address,address,uint256)'))) | ||
(bool success, bytes memory data) = _token.call(abi.encodeWithSelector(0x23b872dd, _from, _to, _amount)); | ||
return success && (data.length == 0 || abi.decode(data, (bool))); | ||
} | ||
|
||
function safeApprove(address _token, address _spender, uint256 _amount) internal returns (bool) { | ||
// selector = bytes4(keccak256(bytes('approve(address,uint256)'))) | ||
(bool success, bytes memory data) = _token.call(abi.encodeWithSelector(0x095ea7b3, _spender, _amount)); | ||
return success && (data.length == 0 || abi.decode(data, (bool))); | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,133 @@ | ||
// SPDX-License-Identifier: MIT | ||
pragma solidity >= 0.8.0; | ||
|
||
import "@openzeppelin/contracts/access/Ownable.sol"; | ||
|
||
interface ITokenList { | ||
function isAllowed(address) external view returns (bool); | ||
} | ||
|
||
interface IWrappedCoin { | ||
function deposit() external payable; | ||
} | ||
|
||
contract TokenCashierWithPayload is Ownable { | ||
event Receipt(address indexed token, uint256 indexed id, address sender, address recipient, uint256 amount, uint256 fee, bytes payload); | ||
event Pause(); | ||
event Unpause(); | ||
event TokenListAdded(ITokenList tokenList, address tokenSafe); | ||
modifier whenNotPaused() { | ||
require(!paused); | ||
_; | ||
} | ||
|
||
bool public paused; | ||
|
||
ITokenList[] public tokenLists; | ||
address[] public tokenSafes; | ||
mapping(address => uint256) public counts; | ||
uint256 public depositFee; | ||
IWrappedCoin public wrappedCoin; | ||
|
||
constructor(IWrappedCoin _wrappedCoin) { | ||
wrappedCoin = _wrappedCoin; | ||
} | ||
|
||
fallback() external { | ||
revert(); | ||
} | ||
|
||
function pause() public onlyOwner { | ||
require(!paused, "already paused"); | ||
paused = true; | ||
emit Pause(); | ||
} | ||
|
||
function unpause() public onlyOwner { | ||
require(paused, "already unpaused"); | ||
paused = false; | ||
emit Unpause(); | ||
} | ||
|
||
function addTokenList(ITokenList _tokenList, address _tokenSafe) public onlyOwner { | ||
tokenLists.push(_tokenList); | ||
tokenSafes.push(_tokenSafe); | ||
emit TokenListAdded(_tokenList, _tokenSafe); | ||
} | ||
|
||
function count(address _token) public view returns (uint256) { | ||
return counts[_token]; | ||
} | ||
|
||
function setDepositFee(uint256 _fee) public onlyOwner { | ||
depositFee = _fee; | ||
} | ||
|
||
function getSafeAddress(address _token) public view returns (address) { | ||
for (uint256 i = 0; i < tokenLists.length; i++) { | ||
if (tokenLists[i].isAllowed(_token)) { | ||
return tokenSafes[i]; | ||
} | ||
} | ||
return address(0); | ||
} | ||
|
||
function depositTo(address _token, address _to, uint256 _amount, bytes memory _payload) public whenNotPaused payable { | ||
require(_to != address(0), "invalid destination"); | ||
bool isCoin = false; | ||
uint256 fee = msg.value; | ||
if (_token == address(0)) { | ||
require(msg.value >= _amount, "insufficient msg.value"); | ||
fee = msg.value - _amount; | ||
wrappedCoin.deposit{value: _amount}(); | ||
_token = address(wrappedCoin); | ||
isCoin = true; | ||
} | ||
require(fee >= depositFee, "insufficient fee"); | ||
address safe = getSafeAddress(_token); | ||
if (safe == address(0)) { | ||
require(!isCoin && safeTransferFrom(_token, msg.sender, address(this), _amount), "fail to transfer token to cashier"); | ||
// selector = bytes4(keccak256(bytes('burn(uint256)'))) | ||
(bool success, bytes memory data) = _token.call(abi.encodeWithSelector(0x42966c68, _amount)); | ||
require(success && (data.length == 0 || abi.decode(data, (bool))), "fail to burn token"); | ||
} else { | ||
if (isCoin) { | ||
require(safeTransfer(_token, safe, _amount), "failed to put into safe"); | ||
} else { | ||
require(safeTransferFrom(_token, msg.sender, safe, _amount), "failed to put into safe"); | ||
} | ||
} | ||
counts[_token] += 1; | ||
emit Receipt(_token, counts[_token], msg.sender, _to, _amount, fee, _payload); | ||
} | ||
|
||
function deposit(address _token, uint256 _amount, bytes memory _payload) public payable { | ||
depositTo(_token, msg.sender, _amount, _payload); | ||
} | ||
|
||
function withdraw() external onlyOwner { | ||
payable(msg.sender).transfer(address(this).balance); | ||
} | ||
|
||
function withdrawToken(address _token) public onlyOwner { | ||
// selector = bytes4(keccak256(bytes('balanceOf(address)'))) | ||
(bool success, bytes memory balance) = _token.call(abi.encodeWithSelector(0x70a08231, address(this))); | ||
require(success, "failed to call balanceOf"); | ||
uint256 bal = abi.decode(balance, (uint256)); | ||
if (bal > 0) { | ||
require(safeTransfer(_token, msg.sender, bal), "failed to withdraw token"); | ||
} | ||
} | ||
|
||
function safeTransferFrom(address _token, address _from, address _to, uint256 _amount) internal returns (bool) { | ||
// selector = bytes4(keccak256(bytes('transferFrom(address,address,uint256)'))) | ||
(bool success, bytes memory data) = _token.call(abi.encodeWithSelector(0x23b872dd, _from, _to, _amount)); | ||
return success && (data.length == 0 || abi.decode(data, (bool))); | ||
} | ||
|
||
function safeTransfer(address _token, address _to, uint256 _amount) internal returns (bool) { | ||
// selector = bytes4(keccak256(bytes('transfer(address,uint256)'))) | ||
(bool success, bytes memory data) = _token.call(abi.encodeWithSelector(0xa9059cbb, _to, _amount)); | ||
return success && (data.length == 0 || abi.decode(data, (bool))); | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Oops, something went wrong.