diff --git a/contracts/Migrations.sol b/contracts/Migrations.sol deleted file mode 100644 index c378ffb..0000000 --- a/contracts/Migrations.sol +++ /dev/null @@ -1,23 +0,0 @@ -pragma solidity >=0.4.21 <0.6.0; - -contract Migrations { - address public owner; - uint public last_completed_migration; - - constructor() public { - owner = msg.sender; - } - - modifier restricted() { - if (msg.sender == owner) _; - } - - function setCompleted(uint completed) public restricted { - last_completed_migration = completed; - } - - function upgrade(address new_address) public restricted { - Migrations upgraded = Migrations(new_address); - upgraded.setCompleted(last_completed_migration); - } -} diff --git a/contracts/core/lock_proxy/LockProxyLimited.sol b/contracts/core/lock_proxy/LockProxyLimited.sol new file mode 100644 index 0000000..93f0d15 --- /dev/null +++ b/contracts/core/lock_proxy/LockProxyLimited.sol @@ -0,0 +1,59 @@ +pragma solidity ^0.5.0; + +import "./LockProxyPausable.sol"; + +contract LockProxyLimited is LockProxyPausable { + + mapping(address => uint) public quota; + mapping(address => uint) public usedQuota; + mapping(address => uint) public refreshPeriod; + mapping(address => uint) public refreshTimestamp; + + function unlock(bytes memory argsBs, bytes memory fromContractAddr, uint64 fromChainId) onlyManagerContract whenNotPaused public returns (bool) { + TxArgs memory args = _deserializeTxArgs(argsBs); + + require(fromContractAddr.length != 0, "from proxy contract address cannot be empty"); + require(Utils.equalStorage(proxyHashMap[fromChainId], fromContractAddr), "From Proxy contract address error!"); + + require(args.toAssetHash.length != 0, "toAssetHash cannot be empty"); + address toAsset = Utils.bytesToAddress(args.toAssetHash); + require(assetHashMap[toAsset][fromChainId].length != 0, "toAsset not bind"); + + require(args.toAddress.length != 0, "toAddress cannot be empty"); + address toAddress = Utils.bytesToAddress(args.toAddress); + + if (refreshPeriod[toAsset] != 0) { + _refreshQuota(toAsset); + require(usedQuota[toAsset] + args.amount <= quota[toAsset], "limit reached"); + usedQuota[toAsset] += args.amount; + } else { // refreshPeriod = 0 means quota becomes single-time quota + refreshTimestamp[toAsset] = block.timestamp; + require(args.amount <= quota[toAsset], "limit reached"); + } + + require(_transferFromContract(toAsset, toAddress, args.amount), "transfer asset from lock_proxy contract to toAddress failed!"); + + emit UnlockEvent(toAsset, toAddress, args.amount); + + return true; + } + + function _refreshQuota(address asset) internal { + if (refreshTimestamp[asset] + refreshPeriod[asset] <= block.timestamp) { + refreshTimestamp[asset] = block.timestamp; + usedQuota[asset] = 0; + } + } + + /* + admin functions + */ + function setQuota(address asset, uint _quota) public onlyOwner() { + quota[asset] = _quota; + } + + function setRefreshPeriod(address asset, uint _refreshPeriod) public onlyOwner() { + refreshPeriod[asset] = _refreshPeriod; + _refreshQuota(asset); + } +} diff --git a/contracts/core/lock_proxy/LockProxyMintBurn.sol b/contracts/core/lock_proxy/LockProxyMintBurn.sol new file mode 100644 index 0000000..75f3ff1 --- /dev/null +++ b/contracts/core/lock_proxy/LockProxyMintBurn.sol @@ -0,0 +1,229 @@ +pragma solidity ^0.5.0; + +import "./../../libs/ownership/Ownable.sol"; +import "./../../libs/common/ZeroCopySource.sol"; +import "./../../libs/common/ZeroCopySink.sol"; +import "./../../libs/utils/Utils.sol"; +import "./../../libs/token/ERC20/SafeERC20.sol"; +import "./../../libs/token/ERC20/ERC20.sol"; +import "./../../libs/token/ERC20/ERC20Detailed.sol"; +import "./../cross_chain_manager/interface/IEthCrossChainManager.sol"; +import "./../cross_chain_manager/interface/IEthCrossChainManagerProxy.sol"; + +contract bridgeAsset is Context, ERC20, ERC20Detailed { + + address public theBridge; + + constructor (string memory name, string memory symbol, uint8 decimals, address bridge_) + public ERC20Detailed(name, symbol, decimals) { + theBridge = bridge_; + } + + modifier onlyBridge() { + require(_msgSender() == theBridge, "msgSender is not THE Bridge!"); + _; + } + + function isTheBridge(address _bridge) public view returns(bool) { + return _bridge == theBridge; + } + + function mint(address to, uint256 amount) public onlyBridge { + _mint(to, amount); + } + + function burnFrom(address account, uint256 amount) public onlyBridge { + _burnFrom(account, amount); + } +} + +contract LockProxyMintBurn is Ownable { + using SafeMath for uint; + using SafeERC20 for IERC20; + + struct TxArgs { + bytes toAssetHash; + bytes toAddress; + uint256 amount; + } + address public managerProxyContract; + mapping(uint64 => bytes) public proxyHashMap; + mapping(address => mapping(uint64 => bytes)) public assetHashMap; + + event SetManagerProxyEvent(address manager); + event BindProxyEvent(uint64 toChainId, bytes targetProxyHash); + event BindAssetEvent(address fromAssetHash, uint64 toChainId, bytes targetProxyHash, uint initialAmount); + event UnlockEvent(address toAssetHash, address toAddress, uint256 amount); + event LockEvent(address fromAssetHash, address fromAddress, uint64 toChainId, bytes toAssetHash, bytes toAddress, uint256 amount); + + + modifier onlyManagerContract() { + IEthCrossChainManagerProxy ieccmp = IEthCrossChainManagerProxy(managerProxyContract); + require(_msgSender() == ieccmp.getEthCrossChainManager(), "msgSender is not EthCrossChainManagerContract"); + _; + } + + function setManagerProxy(address ethCCMProxyAddr) onlyOwner public { + managerProxyContract = ethCCMProxyAddr; + emit SetManagerProxyEvent(managerProxyContract); + } + + function bindProxyHash(uint64 toChainId, bytes memory targetProxyHash) onlyOwner public returns (bool) { + proxyHashMap[toChainId] = targetProxyHash; + emit BindProxyEvent(toChainId, targetProxyHash); + return true; + } + + function bindAssetHash(address fromAssetHash, uint64 toChainId, bytes memory toAssetHash) onlyOwner public returns (bool) { + assetHashMap[fromAssetHash][toChainId] = toAssetHash; + emit BindAssetEvent(fromAssetHash, toChainId, toAssetHash, getBalanceFor(fromAssetHash)); + return true; + } + + /* @notice This function is meant to be invoked by the user, + * a certin amount teokens will be locked in the proxy contract the invoker/msg.sender immediately. + * Then the same amount of tokens will be unloked from target chain proxy contract at the target chain with chainId later. + * @param fromAssetHash The asset address in current chain, uniformly named as `fromAssetHash` + * @param toChainId The target chain id + * + * @param toAddress The address in bytes format to receive same amount of tokens in target chain + * @param amount The amount of tokens to be crossed from ethereum to the chain with chainId + */ + function lock(address fromAssetHash, uint64 toChainId, bytes memory toAddress, uint256 amount) public payable returns (bool) { + require(amount != 0, "amount cannot be zero!"); + + if (isBridgeAsset(fromAssetHash)) { + bridgeAsset(fromAssetHash).burnFrom(_msgSender(), amount); + } else { + require(_transferToContract(fromAssetHash, amount), "transfer asset from fromAddress to lock_proxy contract failed!"); + } + + bytes memory toAssetHash = assetHashMap[fromAssetHash][toChainId]; + require(toAssetHash.length != 0, "empty illegal toAssetHash"); + + TxArgs memory txArgs = TxArgs({ + toAssetHash: toAssetHash, + toAddress: toAddress, + amount: amount + }); + bytes memory txData = _serializeTxArgs(txArgs); + + IEthCrossChainManagerProxy eccmp = IEthCrossChainManagerProxy(managerProxyContract); + address eccmAddr = eccmp.getEthCrossChainManager(); + IEthCrossChainManager eccm = IEthCrossChainManager(eccmAddr); + + bytes memory toProxyHash = proxyHashMap[toChainId]; + require(toProxyHash.length != 0, "empty illegal toProxyHash"); + require(eccm.crossChain(toChainId, toProxyHash, "unlock", txData), "EthCrossChainManager crossChain executed error!"); + + emit LockEvent(fromAssetHash, _msgSender(), toChainId, toAssetHash, toAddress, amount); + + return true; + } + + // /* @notice This function is meant to be invoked by the ETH crosschain management contract, + // * then mint a certin amount of tokens to the designated address since a certain amount + // * was burnt from the source chain invoker. + // * @param argsBs The argument bytes recevied by the ethereum lock proxy contract, need to be deserialized. + // * based on the way of serialization in the source chain proxy contract. + // * @param fromContractAddr The source chain contract address + // * @param fromChainId The source chain id + // */ + function unlock(bytes memory argsBs, bytes memory fromContractAddr, uint64 fromChainId) onlyManagerContract public returns (bool) { + TxArgs memory args = _deserializeTxArgs(argsBs); + + require(fromContractAddr.length != 0, "from proxy contract address cannot be empty"); + require(Utils.equalStorage(proxyHashMap[fromChainId], fromContractAddr), "From Proxy contract address error!"); + + require(args.toAddress.length != 0, "toAddress cannot be empty"); + address toAddress = Utils.bytesToAddress(args.toAddress); + + require(args.toAssetHash.length != 0, "toAssetHash cannot be empty"); + address toAssetHash = Utils.bytesToAddress(args.toAssetHash); + + if (isBridgeAsset(toAssetHash)) { + bridgeAsset(toAssetHash).mint(toAddress, args.amount); + } else { + require(_transferFromContract(toAssetHash, toAddress, args.amount), "transfer asset from lock_proxy contract to toAddress failed!"); + } + + emit UnlockEvent(toAssetHash, toAddress, args.amount); + return true; + } + + function isBridgeAsset(address assetAddress) public view returns (bool) { + (bool success, bytes memory returnData) = assetAddress.staticcall(abi.encodeWithSignature("isTheBridge(address)", address(this))); + if (!success || returnData.length == 0) return false; + (bool res,) = ZeroCopySource.NextBool(returnData, 31); + return res; + } + + function getBalanceFor(address fromAssetHash) public view returns (uint256) { + if (fromAssetHash == address(0)) { + // return address(this).balance; // this expression would result in error: Failed to decode output: Error: insufficient data for uint256 type + address selfAddr = address(this); + return selfAddr.balance; + } else { + IERC20 erc20Token = IERC20(fromAssetHash); + return erc20Token.balanceOf(address(this)); + } + } + function _transferToContract(address fromAssetHash, uint256 amount) internal returns (bool) { + if (fromAssetHash == address(0)) { + // fromAssetHash === address(0) denotes user choose to lock ether + // passively check if the received msg.value equals amount + require(msg.value != 0, "transferred ether cannot be zero!"); + require(msg.value == amount, "transferred ether is not equal to amount!"); + } else { + // make sure lockproxy contract will decline any received ether + require(msg.value == 0, "there should be no ether transfer!"); + // actively transfer amount of asset from msg.sender to lock_proxy contract + require(_transferERC20ToContract(fromAssetHash, _msgSender(), address(this), amount), "transfer erc20 asset to lock_proxy contract failed!"); + } + return true; + } + function _transferFromContract(address toAssetHash, address toAddress, uint256 amount) internal returns (bool) { + if (toAssetHash == address(0x0000000000000000000000000000000000000000)) { + // toAssetHash === address(0) denotes contract needs to unlock ether to toAddress + // convert toAddress from 'address' type to 'address payable' type, then actively transfer ether + address(uint160(toAddress)).transfer(amount); + } else { + // actively transfer amount of asset from lock_proxy contract to toAddress + require(_transferERC20FromContract(toAssetHash, toAddress, amount), "transfer erc20 asset from lock_proxy contract to toAddress failed!"); + } + return true; + } + + + function _transferERC20ToContract(address fromAssetHash, address fromAddress, address toAddress, uint256 amount) internal returns (bool) { + IERC20 erc20Token = IERC20(fromAssetHash); + // require(erc20Token.transferFrom(fromAddress, toAddress, amount), "trasnfer ERC20 Token failed!"); + erc20Token.safeTransferFrom(fromAddress, toAddress, amount); + return true; + } + function _transferERC20FromContract(address toAssetHash, address toAddress, uint256 amount) internal returns (bool) { + IERC20 erc20Token = IERC20(toAssetHash); + // require(erc20Token.transfer(toAddress, amount), "trasnfer ERC20 Token failed!"); + erc20Token.safeTransfer(toAddress, amount); + return true; + } + + function _serializeTxArgs(TxArgs memory args) internal pure returns (bytes memory) { + bytes memory buff; + buff = abi.encodePacked( + ZeroCopySink.WriteVarBytes(args.toAssetHash), + ZeroCopySink.WriteVarBytes(args.toAddress), + ZeroCopySink.WriteUint255(args.amount) + ); + return buff; + } + + function _deserializeTxArgs(bytes memory valueBs) internal pure returns (TxArgs memory) { + TxArgs memory args; + uint256 off = 0; + (args.toAssetHash, off) = ZeroCopySource.NextVarBytes(valueBs, off); + (args.toAddress, off) = ZeroCopySource.NextVarBytes(valueBs, off); + (args.amount, off) = ZeroCopySource.NextUint255(valueBs, off); + return args; + } +} \ No newline at end of file diff --git a/contracts/core/lock_proxy/LockProxyPausable.sol b/contracts/core/lock_proxy/LockProxyPausable.sol new file mode 100644 index 0000000..a319e07 --- /dev/null +++ b/contracts/core/lock_proxy/LockProxyPausable.sol @@ -0,0 +1,198 @@ +pragma solidity ^0.5.0; + +import "./../../libs/ownership/Ownable.sol"; +import "./../../libs/common/ZeroCopySource.sol"; +import "./../../libs/common/ZeroCopySink.sol"; +import "./../../libs/utils/Utils.sol"; +import "./../../libs/lifecycle/Pausable.sol"; +import "./../../libs/token/ERC20/SafeERC20.sol"; +import "./../cross_chain_manager/interface/IEthCrossChainManager.sol"; +import "./../cross_chain_manager/interface/IEthCrossChainManagerProxy.sol"; + + +contract LockProxyPausable is Ownable, Pausable { + using SafeMath for uint; + using SafeERC20 for IERC20; + + struct TxArgs { + bytes toAssetHash; + bytes toAddress; + uint256 amount; + } + address public managerProxyContract; + mapping(uint64 => bytes) public proxyHashMap; + mapping(address => mapping(uint64 => bytes)) public assetHashMap; + mapping(address => bool) safeTransfer; + + event SetManagerProxyEvent(address manager); + event BindProxyEvent(uint64 toChainId, bytes targetProxyHash); + event BindAssetEvent(address fromAssetHash, uint64 toChainId, bytes targetProxyHash, uint initialAmount); + event UnlockEvent(address toAssetHash, address toAddress, uint256 amount); + event LockEvent(address fromAssetHash, address fromAddress, uint64 toChainId, bytes toAssetHash, bytes toAddress, uint256 amount); + + modifier onlyManagerContract() { + IEthCrossChainManagerProxy ieccmp = IEthCrossChainManagerProxy(managerProxyContract); + require(_msgSender() == ieccmp.getEthCrossChainManager(), "msgSender is not EthCrossChainManagerContract"); + _; + } + + function setManagerProxy(address ethCCMProxyAddr) onlyOwner public { + managerProxyContract = ethCCMProxyAddr; + emit SetManagerProxyEvent(managerProxyContract); + } + + function bindProxyHash(uint64 toChainId, bytes memory targetProxyHash) onlyOwner public returns (bool) { + proxyHashMap[toChainId] = targetProxyHash; + emit BindProxyEvent(toChainId, targetProxyHash); + return true; + } + + function bindAssetHash(address fromAssetHash, uint64 toChainId, bytes memory toAssetHash) onlyOwner public returns (bool) { + assetHashMap[fromAssetHash][toChainId] = toAssetHash; + emit BindAssetEvent(fromAssetHash, toChainId, toAssetHash, getBalanceFor(fromAssetHash)); + return true; + } + + function pause() onlyOwner public { + _pause(); + } + + function unpause() onlyOwner public { + _unpause(); + } + + /* @notice This function is meant to be invoked by the user, + * a certin amount teokens will be locked in the proxy contract the invoker/msg.sender immediately. + * Then the same amount of tokens will be unloked from target chain proxy contract at the target chain with chainId later. + * @param fromAssetHash The asset address in current chain, uniformly named as `fromAssetHash` + * @param toChainId The target chain id + * + * @param toAddress The address in bytes format to receive same amount of tokens in target chain + * @param amount The amount of tokens to be crossed from ethereum to the chain with chainId + */ + function lock(address fromAssetHash, uint64 toChainId, bytes memory toAddress, uint256 amount) whenNotPaused public payable returns (bool) { + require(amount != 0, "amount cannot be zero!"); + + + require(_transferToContract(fromAssetHash, amount), "transfer asset from fromAddress to lock_proxy contract failed!"); + + bytes memory toAssetHash = assetHashMap[fromAssetHash][toChainId]; + require(toAssetHash.length != 0, "empty illegal toAssetHash"); + + TxArgs memory txArgs = TxArgs({ + toAssetHash: toAssetHash, + toAddress: toAddress, + amount: amount + }); + bytes memory txData = _serializeTxArgs(txArgs); + + IEthCrossChainManagerProxy eccmp = IEthCrossChainManagerProxy(managerProxyContract); + address eccmAddr = eccmp.getEthCrossChainManager(); + IEthCrossChainManager eccm = IEthCrossChainManager(eccmAddr); + + bytes memory toProxyHash = proxyHashMap[toChainId]; + require(toProxyHash.length != 0, "empty illegal toProxyHash"); + require(eccm.crossChain(toChainId, toProxyHash, "unlock", txData), "EthCrossChainManager crossChain executed error!"); + + emit LockEvent(fromAssetHash, _msgSender(), toChainId, toAssetHash, toAddress, amount); + + return true; + + } + + // /* @notice This function is meant to be invoked by the ETH crosschain management contract, + // * then mint a certin amount of tokens to the designated address since a certain amount + // * was burnt from the source chain invoker. + // * @param argsBs The argument bytes recevied by the ethereum lock proxy contract, need to be deserialized. + // * based on the way of serialization in the source chain proxy contract. + // * @param fromContractAddr The source chain contract address + // * @param fromChainId The source chain id + // */ + function unlock(bytes memory argsBs, bytes memory fromContractAddr, uint64 fromChainId) onlyManagerContract whenNotPaused public returns (bool) { + TxArgs memory args = _deserializeTxArgs(argsBs); + + require(fromContractAddr.length != 0, "from proxy contract address cannot be empty"); + require(Utils.equalStorage(proxyHashMap[fromChainId], fromContractAddr), "From Proxy contract address error!"); + + require(args.toAssetHash.length != 0, "toAssetHash cannot be empty"); + address toAssetHash = Utils.bytesToAddress(args.toAssetHash); + require(assetHashMap[toAssetHash][fromChainId].length != 0, "toAsset not bind"); + + require(args.toAddress.length != 0, "toAddress cannot be empty"); + address toAddress = Utils.bytesToAddress(args.toAddress); + + require(_transferFromContract(toAssetHash, toAddress, args.amount), "transfer asset from lock_proxy contract to toAddress failed!"); + + emit UnlockEvent(toAssetHash, toAddress, args.amount); + return true; + } + + function getBalanceFor(address fromAssetHash) public view returns (uint256) { + if (fromAssetHash == address(0)) { + // return address(this).balance; // this expression would result in error: Failed to decode output: Error: insufficient data for uint256 type + address selfAddr = address(this); + return selfAddr.balance; + } else { + IERC20 erc20Token = IERC20(fromAssetHash); + return erc20Token.balanceOf(address(this)); + } + } + function _transferToContract(address fromAssetHash, uint256 amount) internal returns (bool) { + if (fromAssetHash == address(0)) { + // fromAssetHash === address(0) denotes user choose to lock ether + // passively check if the received msg.value equals amount + require(msg.value != 0, "transferred ether cannot be zero!"); + require(msg.value == amount, "transferred ether is not equal to amount!"); + } else { + // make sure lockproxy contract will decline any received ether + require(msg.value == 0, "there should be no ether transfer!"); + // actively transfer amount of asset from msg.sender to lock_proxy contract + require(_transferERC20ToContract(fromAssetHash, _msgSender(), address(this), amount), "transfer erc20 asset to lock_proxy contract failed!"); + } + return true; + } + function _transferFromContract(address toAssetHash, address toAddress, uint256 amount) internal returns (bool) { + if (toAssetHash == address(0x0000000000000000000000000000000000000000)) { + // toAssetHash === address(0) denotes contract needs to unlock ether to toAddress + // convert toAddress from 'address' type to 'address payable' type, then actively transfer ether + address(uint160(toAddress)).transfer(amount); + } else { + // actively transfer amount of asset from lock_proxy contract to toAddress + require(_transferERC20FromContract(toAssetHash, toAddress, amount), "transfer erc20 asset from lock_proxy contract to toAddress failed!"); + } + return true; + } + + + function _transferERC20ToContract(address fromAssetHash, address fromAddress, address toAddress, uint256 amount) internal returns (bool) { + IERC20 erc20Token = IERC20(fromAssetHash); + // require(erc20Token.transferFrom(fromAddress, toAddress, amount), "trasnfer ERC20 Token failed!"); + erc20Token.safeTransferFrom(fromAddress, toAddress, amount); + return true; + } + function _transferERC20FromContract(address toAssetHash, address toAddress, uint256 amount) internal returns (bool) { + IERC20 erc20Token = IERC20(toAssetHash); + // require(erc20Token.transfer(toAddress, amount), "trasnfer ERC20 Token failed!"); + erc20Token.safeTransfer(toAddress, amount); + return true; + } + + function _serializeTxArgs(TxArgs memory args) internal pure returns (bytes memory) { + bytes memory buff; + buff = abi.encodePacked( + ZeroCopySink.WriteVarBytes(args.toAssetHash), + ZeroCopySink.WriteVarBytes(args.toAddress), + ZeroCopySink.WriteUint255(args.amount) + ); + return buff; + } + + function _deserializeTxArgs(bytes memory valueBs) internal pure returns (TxArgs memory) { + TxArgs memory args; + uint256 off = 0; + (args.toAssetHash, off) = ZeroCopySource.NextVarBytes(valueBs, off); + (args.toAddress, off) = ZeroCopySource.NextVarBytes(valueBs, off); + (args.amount, off) = ZeroCopySource.NextUint255(valueBs, off); + return args; + } +} \ No newline at end of file diff --git a/contracts/core/lock_proxy/LockProxyReviewable.sol b/contracts/core/lock_proxy/LockProxyReviewable.sol new file mode 100644 index 0000000..f4e04be --- /dev/null +++ b/contracts/core/lock_proxy/LockProxyReviewable.sol @@ -0,0 +1,133 @@ +pragma solidity ^0.5.0; + +import "./LockProxyPausable.sol"; + +contract LockProxyReviewable is LockProxyPausable { + + enum RequestState { Invalid, Pending, Executed, Banned, Approved } + + struct UnlockRequest { + bytes argsBs; + bytes fromContractAddr; + uint64 fromChainId; + RequestState state; + } + + uint public latestRequestId; + + mapping(uint => UnlockRequest) public requests; + + mapping(address => uint) public limits; + + mapping(address => bool) public censors; + + event NewRequestEvent(uint requestId, bytes argsBs, bytes fromContractAddr, uint64 fromChainId); + event ExecuteRequestEvent(uint requestId, address toAsset, address toAddress, uint amount); + event BanRequestEvent(uint requestId, string reason); + event UnbanRequestEvent(uint requestId, string reason); + + function unlock(bytes memory argsBs, bytes memory fromContractAddr, uint64 fromChainId) onlyManagerContract whenNotPaused public returns (bool) { + TxArgs memory args = _deserializeTxArgs(argsBs); + + require(fromContractAddr.length != 0, "from proxy contract address cannot be empty"); + require(Utils.equalStorage(proxyHashMap[fromChainId], fromContractAddr), "From Proxy contract address error!"); + + require(args.toAssetHash.length != 0, "toAssetHash cannot be empty"); + address toAsset = Utils.bytesToAddress(args.toAssetHash); + require(assetHashMap[toAsset][fromChainId].length != 0, "toAsset not bind"); + + require(args.toAddress.length != 0, "toAddress cannot be empty"); + address toAddress = Utils.bytesToAddress(args.toAddress); + + if (limits[toAsset] != 0 && args.amount >= limits[toAsset]) { + latestRequestId += 1; + requests[latestRequestId] = UnlockRequest({ + argsBs: argsBs, + fromContractAddr: fromContractAddr, + fromChainId: fromChainId, + state: RequestState.Pending + }); + emit NewRequestEvent(latestRequestId, argsBs, fromContractAddr, fromChainId); + } else { + _unlock(toAsset, toAddress, args.amount); + } + return true; + } + + function _unlock(address toAsset, address toAddress, uint256 amount) internal { + require(_transferFromContract(toAsset, toAddress, amount), "transfer asset from lock_proxy contract to toAddress failed!"); + + emit UnlockEvent(toAsset, toAddress, amount); + } + + /* + admin functions + */ + function setLimitForToken(address toAsset, uint limit) public onlyOwner() { + limits[toAsset] = limit; + } + + function removeLimitForToken(address toAsset) public onlyOwner() { + limits[toAsset] = 0; + } + + // !! remove can not be undo + function removeBannedRequest(uint requestId) public onlyOwner() { + require(requests[requestId].state == RequestState.Banned, "this is not a banned request"); + requests[requestId].state = RequestState.Invalid; + } + + function addCensor(address newCensor) public onlyOwner() { + require(!censors[newCensor], "already added"); + censors[newCensor] = true; + } + + function removeCensor(address censor) public onlyOwner() { + require(censors[censor], "already removed"); + censors[censor] = false; + } + + /* + censor functions + */ + modifier onlyCensor() { + require(censors[msg.sender], "Access denied: not censor"); + _; + } + + function approve(uint requestId, address toAsset, address toAddress, uint amount) public onlyCensor() { + UnlockRequest memory request = requests[requestId]; + require(request.state == RequestState.Pending, "this is not a pending request"); + TxArgs memory args = _deserializeTxArgs(request.argsBs); + address _toAsset = Utils.bytesToAddress(args.toAssetHash); + address _toAddress = Utils.bytesToAddress(args.toAddress); + require(_toAsset == toAsset && _toAddress == toAddress && args.amount == amount, "invalid TxArgs"); + + requests[requestId].state = RequestState.Executed; + _unlock(toAsset, toAddress, amount); + + emit ExecuteRequestEvent(requestId, toAsset, toAddress, amount); + } + + function ban(uint requestId, string memory reason) public onlyCensor() { + require(requests[requestId].state == RequestState.Pending, "this is not a pending request"); + requests[requestId].state = RequestState.Banned; + + emit BanRequestEvent(requestId, reason); + } + + function unban(uint requestId, string memory reason , address toAsset, address toAddress, uint amount) public onlyCensor() { + UnlockRequest memory request = requests[requestId]; + require(request.state == RequestState.Banned, "this is not a banned request"); + TxArgs memory args = _deserializeTxArgs(request.argsBs); + address _toAsset = Utils.bytesToAddress(args.toAssetHash); + address _toAddress = Utils.bytesToAddress(args.toAddress); + require(_toAsset == toAsset && _toAddress == toAddress && args.amount == amount, "invalid TxArgs"); + + requests[requestId].state = RequestState.Executed; + _unlock(toAsset, toAddress, amount); + + emit UnbanRequestEvent(requestId, reason); + emit ExecuteRequestEvent(requestId, toAsset, toAddress, amount); + } +} \ No newline at end of file