Skip to content

Commit

Permalink
add payload and contract receiver, support payload in relayer/witness
Browse files Browse the repository at this point in the history
  • Loading branch information
CoderZhi committed Oct 9, 2024
1 parent 3a3ecbe commit dc0b086
Show file tree
Hide file tree
Showing 87 changed files with 10,996 additions and 4,745 deletions.
5 changes: 2 additions & 3 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -15,12 +15,11 @@
# vendor/

# Truffle build directories
build/
*/build/

witness-service/bin/

node_modules/

.vscode
node_modules
.env
coverage
Expand Down
53 changes: 53 additions & 0 deletions contracts/Unwrapper.sol
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 contracts/iotube/CrosschainTokenCashierWithPayloadRouter.sol
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)));
}
}
133 changes: 133 additions & 0 deletions contracts/iotube/TokenCashierWithPayload.sol
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)));
}
}
2 changes: 1 addition & 1 deletion contracts/iotube/TransferValidatorV2.sol
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,7 @@ interface IMinter {
function owner() external view returns(address);
}

contract TransferValidator is Pausable {
contract TransferValidatorV2 is Pausable {
event Settled(bytes32 indexed key, address[] witnesses);

mapping(bytes32 => uint256) public settles;
Expand Down
Loading

0 comments on commit dc0b086

Please sign in to comment.