-
Notifications
You must be signed in to change notification settings - Fork 27
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
base: master
Are you sure you want to change the base?
crosschain calls #241
Changes from 1 commit
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
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"); | ||
bool isCoin = false; | ||
uint256 fee = msg.value; | ||
if (_token == address(0)) { | ||
require(msg.value >= _amount, "insufficient msg.value"); | ||
fee = msg.value - _amount; | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe 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))); | ||
} | ||
} |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,135 @@ | ||
pragma solidity <6.0 >=0.4.24; | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe 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); | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe 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"); | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe 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); | ||
} | ||
} |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
how is contractDestinations used?