Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

crosschain calls #241

Open
wants to merge 3 commits into
base: master
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from 1 commit
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
124 changes: 124 additions & 0 deletions contracts/iotube/TokenCashierV3.sol
Original file line number Diff line number Diff line change
@@ -0,0 +1,124 @@
pragma solidity <6.0 >=0.4.24;

import "../lifecycle/Pausable.sol";

interface ITokenList {
function isAllowed(address) external returns (bool);
function maxAmount(address) external returns (uint256);
function minAmount(address) external returns (uint256);
}

interface IWrappedCoin {
function deposit() external payable;
}

contract TokenCashierV3 is Pausable {
event Receipt(address indexed token, uint256 indexed id, address sender, address recipient, uint256 amount, uint256 fee, bytes payload);
// event ContractDestinationAdded(address indexed destination);
// event ContractDestinationRemoved(address indexed destination);

ITokenList[] public tokenLists;
address[] public tokenSafes;
mapping(address => uint256) public counts;
uint256 public depositFee;
IWrappedCoin public wrappedCoin;
// mapping(address => bool) public contractDestinations;

constructor(IWrappedCoin _wrappedCoin, ITokenList[] memory _tokenLists, address[] memory _tokenSafes) public {
require(_tokenLists.length == _tokenSafes.length, "# of token lists is not equal to # of safes");
wrappedCoin = _wrappedCoin;
tokenLists = _tokenLists;
tokenSafes = _tokenSafes;
}

function() external {
revert();
}

function count(address _token) public view returns (uint256) {
return counts[_token];
}
/*
function addContractDestination(address _dest) public onlyOwner {
require(!contractDestinations[_dest], "already added");
contractDestinations[_dest] = true;
emit ContractDestinationAdded(_dest);
}

function removeContractDestination(address _dest) public onlyOwner {
require(contractDestinations[_dest], "invalid destination");
contractDestinations[_dest] = false;
emit ContractDestinationRemoved(_dest);
}
*/
function setDepositFee(uint256 _fee) public onlyOwner {
depositFee = _fee;
}

function depositTo(address _token, address _to, uint256 _amount, bytes calldata _payload) public whenNotPaused payable {
require(_to != address(0), "invalid destination");
// require(_payload.length == 0 || contractDestinations[_to], "invalid destination with payload");
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

how is contractDestinations used?

bool isCoin = false;
uint256 fee = msg.value;
if (_token == address(0)) {
require(msg.value >= _amount, "insufficient msg.value");
fee = msg.value - _amount;
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

should check decimal overflow or use solidity 0.8 above

wrappedCoin.deposit.value(_amount)();
_token = address(wrappedCoin);
isCoin = true;
}
require(fee >= depositFee, "insufficient fee");
for (uint256 i = 0; i < tokenLists.length; i++) {
if (tokenLists[i].isAllowed(_token)) {
require(_amount >= tokenLists[i].minAmount(_token), "amount too low");
require(_amount <= tokenLists[i].maxAmount(_token), "amount too high");
if (tokenSafes[i] == 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, tokenSafes[i], _amount), "failed to put into safe");
} else {
require(safeTransferFrom(_token, msg.sender, tokenSafes[i], _amount), "failed to put into safe");
}
}
counts[_token] += 1;
emit Receipt(_token, counts[_token], msg.sender, _to, _amount, fee, _payload);
return;
}
}
revert("not a whitelisted token");
}

function deposit(address _token, uint256 _amount, bytes calldata _payload) public payable {
depositTo(_token, msg.sender, _amount, _payload);
}

function withdraw() external onlyOwner {
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)));
}
}
135 changes: 135 additions & 0 deletions contracts/iotube/TransferValidatorV3.sol
Original file line number Diff line number Diff line change
@@ -0,0 +1,135 @@
pragma solidity <6.0 >=0.4.24;
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

is this based on contracts/iotube/TransferValidator.sol? Why not contracts/iotube/TransferValidatorV2.sol instead?


import "../lifecycle/Pausable.sol";

interface IAllowlist {
function isAllowed(address) external view returns (bool);
function numOfActive() external view returns (uint256);
}

interface IMinter {
function mint(address, address, uint256) external returns(bool);
function transferOwnership(address) external;
function owner() external view returns(address);
}

interface IReceiver {
function onReceive(bytes32 id, address sender, address token, uint256 amount, bytes calldata payload) external;
}

contract TransferValidatorV3 is Pausable {
event Settled(bytes32 indexed key, address[] witnesses);
event ReceiverAdded(address receiver);
event ReceiverRemoved(address receiver);

mapping(bytes32 => uint256) public settles;
mapping(address => bool) public receivers;

IMinter[] public minters;
IAllowlist[] public tokenLists;
IAllowlist public witnessList;

constructor(IAllowlist _witnessList) public {
witnessList = _witnessList;
}

function generateKey(address cashier, address tokenAddr, uint256 index, address from, address to, uint256 amount) public view returns(bytes32) {
return keccak256(abi.encodePacked(address(this), cashier, tokenAddr, index, from, to, amount));
}

function submit(address cashier, address tokenAddr, uint256 index, address from, address to, uint256 amount, bytes memory signatures, bytes calldata payload) public whenNotPaused {
require(amount != 0, "amount cannot be zero");
require(to != address(0), "recipient cannot be zero");
require(signatures.length % 65 == 0, "invalid signature length");
bytes32 key = generateKey(cashier, tokenAddr, index, from, to, amount);
require(settles[key] == 0, "transfer has been settled");
for (uint256 it = 0; it < tokenLists.length; it++) {
if (tokenLists[it].isAllowed(tokenAddr)) {
uint256 numOfSignatures = signatures.length / 65;
address[] memory witnesses = new address[](numOfSignatures);
Copy link
Collaborator

@Liuhaai Liuhaai Mar 15, 2024

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

more readable to keep inside extractWitnesses()

for (uint256 i = 0; i < numOfSignatures; i++) {
address witness = recover(key, signatures, i * 65);
require(witnessList.isAllowed(witness), "invalid signature");
for (uint256 j = 0; j < i; j++) {
require(witness != witnesses[j], "duplicate witness");
}
witnesses[i] = witness;
}
require(numOfSignatures * 3 > witnessList.numOfActive() * 2, "insufficient witnesses");
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Move L58 to above of L49 can be more efficiently when insufficient witnesses occur

settles[key] = block.number;
require(minters[it].mint(tokenAddr, to, amount), "failed to mint token");
if (receivers[to]) {
IReceiver(to).onReceive(key, from, tokenAddr, amount, payload);
}
emit Settled(key, witnesses);
return;
}
}
revert("not a whitelisted token");
}

function numOfPairs() external view returns (uint256) {
return tokenLists.length;
}

function addPair(IAllowlist _tokenList, IMinter _minter) external onlyOwner {
tokenLists.push(_tokenList);
minters.push(_minter);
}

function addReceiver(address _receiver) external onlyOwner {
require(!receivers[_receiver], "already a receiver");
receivers[_receiver] = true;
emit ReceiverAdded(_receiver);
}

function removeReceiver(address _receiver) external onlyOwner {
require(receivers[_receiver], "invalid receiver");
receivers[_receiver] = false;
emit ReceiverRemoved(_receiver);
}

function upgrade(address _newValidator) external onlyOwner {
address contractAddr = address(this);
for (uint256 i = 0; i < minters.length; i++) {
IMinter minter = minters[i];
if (minter.owner() == contractAddr) {
minter.transferOwnership(_newValidator);
}
}
}

/**
* @dev Recover signer address from a message by using their signature
* @param hash bytes32 message, the hash is the signed message. What is recovered is the signer address.
* @param signature bytes signature, the signature is generated using web3.eth.sign()
*/
function recover(bytes32 hash, bytes memory signature, uint256 offset)
internal
pure
returns (address)
{
bytes32 r;
bytes32 s;
uint8 v;

// Divide the signature in r, s and v variables with inline assembly.
assembly {
r := mload(add(signature, add(offset, 0x20)))
s := mload(add(signature, add(offset, 0x40)))
v := byte(0, mload(add(signature, add(offset, 0x60))))
}

// Version of signature should be 27 or 28, but 0 and 1 are also possible versions
if (v < 27) {
v += 27;
}

// If the version is correct return the signer address
if (v != 27 && v != 28) {
return (address(0));
}
// solium-disable-next-line arg-overflow
return ecrecover(hash, v, r, s);
}
}
Loading