Merge the develop branch to the master branch, preparation to v6.0.0

This update for the `master` branch contains the changes made to address findings discovered during a security audit:
 * [Fix] Stricter preconditions for payInterest (#623)
 * [Fix] Fix offset in comments (#624)
 * [Fix] Use fixed lower call gas limit (#627)
 * [Fix] Separate XDaiForeignBridge contract with compound and GSN support (#626)
 * [Fix] Update GSN interface (#628)
 * [Fix] Block ERC20 selectors in AMB requests (#630)
 * [Other] Bump package and contracts interfaces version prior to 6.0.0 (#629)
This commit is contained in:
Alexander Kolotov 2021-09-06 13:19:03 +03:00 committed by GitHub
commit 908a481079
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
26 changed files with 1494 additions and 642 deletions

View File

@ -4,7 +4,6 @@ pragma experimental ABIEncoderV2;
import "../upgradeable_contracts/Ownable.sol";
import "./interfaces/GsnTypes.sol";
import "./interfaces/IPaymaster.sol";
import "./interfaces/IRelayHub.sol";
import "./utils/GsnEip712Library.sol";
@ -18,7 +17,7 @@ import "./forwarder/IForwarder.sol";
*/
contract BasePaymaster is IPaymaster, Ownable {
IRelayHub internal relayHub;
IForwarder public trustedForwarder;
address private _trustedForwarder;
function getHubAddr() public view returns (address) {
return address(relayHub);
@ -27,20 +26,26 @@ contract BasePaymaster is IPaymaster, Ownable {
//overhead of forwarder verify+signature, plus hub overhead.
uint256 public constant FORWARDER_HUB_OVERHEAD = 50000;
//These parameters are documented in IPaymaster.GasLimits
//These parameters are documented in IPaymaster.GasAndDataLimits
uint256 public constant PRE_RELAYED_CALL_GAS_LIMIT = 100000;
uint256 public constant POST_RELAYED_CALL_GAS_LIMIT = 110000;
uint256 public constant PAYMASTER_ACCEPTANCE_BUDGET = PRE_RELAYED_CALL_GAS_LIMIT + FORWARDER_HUB_OVERHEAD;
uint256 public constant CALLDATA_SIZE_LIMIT = 10500;
function getGasLimits() external view returns (IPaymaster.GasLimits) {
function getGasAndDataLimits() external view returns (IPaymaster.GasAndDataLimits limits) {
return
IPaymaster.GasLimits(PAYMASTER_ACCEPTANCE_BUDGET, PRE_RELAYED_CALL_GAS_LIMIT, POST_RELAYED_CALL_GAS_LIMIT);
IPaymaster.GasAndDataLimits(
PAYMASTER_ACCEPTANCE_BUDGET,
PRE_RELAYED_CALL_GAS_LIMIT,
POST_RELAYED_CALL_GAS_LIMIT,
CALLDATA_SIZE_LIMIT
);
}
// this method must be called from preRelayedCall to validate that the forwarder
// is approved by the paymaster as well as by the recipient contract.
function _verifyForwarder(GsnTypes.RelayRequest relayRequest) public view {
require(address(trustedForwarder) == relayRequest.relayData.forwarder, "Forwarder is not trusted");
require(address(_trustedForwarder) == relayRequest.relayData.forwarder, "Forwarder is not trusted");
GsnEip712Library.verifyForwarderTrusted(relayRequest);
}
@ -48,7 +53,7 @@ contract BasePaymaster is IPaymaster, Ownable {
* modifier to be used by recipients as access control protection for preRelayedCall & postRelayedCall
*/
modifier relayHubOnly() {
require(msg.sender == getHubAddr(), "Function can only be called by RelayHub");
require(msg.sender == getHubAddr(), "can only be called by RelayHub");
_;
}
@ -56,16 +61,28 @@ contract BasePaymaster is IPaymaster, Ownable {
relayHub = hub;
}
function setTrustedForwarder(IForwarder forwarder) public onlyOwner {
trustedForwarder = forwarder;
function setTrustedForwarder(address forwarder) public onlyOwner {
_trustedForwarder = forwarder;
}
// check current deposit on relay hub.
function getRelayHubDeposit() public view returns (uint256) {
function trustedForwarder() external view returns (address) {
return _trustedForwarder;
}
/// check current deposit on relay hub.
function getRelayHubDeposit() external view returns (uint256) {
return relayHub.balanceOf(address(this));
}
// withdraw deposit from relayHub
// any eth moved into the paymaster is transferred as a deposit.
// This way, we don't need to understand the RelayHub API in order to replenish
// the paymaster.
function() external payable {
require(address(relayHub) != address(0), "relay hub address not set");
relayHub.depositFor.value(msg.value)(address(this));
}
/// withdraw deposit from relayHub
function withdrawRelayHubDepositTo(uint256 amount, address target) public onlyOwner {
relayHub.withdraw(amount, target);
}

View File

@ -10,14 +10,20 @@ contract IForwarder {
uint256 gas;
uint256 nonce;
bytes data;
uint256 validUntil;
}
event DomainRegistered(bytes32 indexed domainSeparator, bytes domainValue);
event RequestTypeRegistered(bytes32 indexed typeHash, string typeStr);
function getNonce(address from) external view returns (uint256);
/**
* verify the transaction would execute.
* validate the signature and the nonce of the request.
* revert if either signature or nonce are incorrect.
* also revert if domainSeparator or requestTypeHash are not registered.
*/
function verify(
ForwardRequest forwardRequest,
@ -51,8 +57,8 @@ contract IForwarder {
/**
* Register a new Request typehash.
* @param typeName - the name of the request type.
* @param typeSuffix - anything after the generic params can be empty string (if no extra fields are needed)
* if it does contain a value, then a comma is added first.
* @param typeSuffix - any extra data after the generic params.
* (must add at least one param. The generic ForwardRequest type is always registered by the constructor)
*/
function registerRequestType(string typeName, string typeSuffix) external;

View File

@ -4,15 +4,16 @@ pragma solidity 0.4.24;
import "../forwarder/IForwarder.sol";
contract GsnTypes {
/// @notice gasPrice, pctRelayFee and baseRelayFee must be validated inside of the paymaster's preRelayedCall in order not to overpay
struct RelayData {
uint256 gasPrice;
uint256 pctRelayFee;
uint256 baseRelayFee;
address relayWorker;
address paymaster;
address forwarder;
bytes paymasterData;
uint256 clientId;
address forwarder;
}
//note: must start with the ForwardRequest to be an extension of the generic forwarder

View File

@ -29,16 +29,19 @@ contract IPaymaster {
* note that an OOG will revert the transaction, but the paymaster already committed to pay,
* so the relay will get compensated, at the expense of the paymaster
*/
struct GasLimits {
struct GasAndDataLimits {
uint256 acceptanceBudget;
uint256 preRelayedCallGasLimit;
uint256 postRelayedCallGasLimit;
uint256 calldataSizeLimit;
}
/**
* Return the GasLimits constants used by the Paymaster.
* Return the Gas Limits and msg.data max size constants used by the Paymaster.
*/
function getGasLimits() external view returns (GasLimits memory limits);
function getGasAndDataLimits() external view returns (GasAndDataLimits memory limits);
function trustedForwarder() external view returns (address);
/**
* return the relayHub of this contract.
@ -49,7 +52,7 @@ contract IPaymaster {
* Can be used to determine if the contract can pay for incoming calls before making any.
* @return the paymaster's deposit in the RelayHub.
*/
function getRelayHubDeposit() public view returns (uint256);
function getRelayHubDeposit() external view returns (uint256);
/**
* Called by Relay (and RelayHub), to validate if the paymaster agrees to pay for this call.
@ -74,7 +77,7 @@ contract IPaymaster {
* Note that in most cases the paymaster shouldn't try use it at all. It is always checked
* by the forwarder immediately after preRelayedCall returns.
* @param approvalData - extra dapp-specific data (e.g. signature from trusted party)
* @param maxPossibleGas - based on values returned from {@link getGasLimits},
* @param maxPossibleGas - based on values returned from {@link getGasAndDataLimits},
* the RelayHub will calculate the maximum possible amount of gas the user may be charged for.
* In order to convert this value to wei, the Paymaster has to call "relayHub.calculateCharge()"
* return:

View File

@ -6,21 +6,25 @@ import "openzeppelin-solidity/contracts/token/ERC20/ERC20.sol";
import "../BasePaymaster.sol";
import "./IUniswapV2Router02.sol";
import "../../upgradeable_contracts/GSNForeignERC20Bridge.sol";
import "../../upgradeable_contracts/Claimable.sol";
contract TokenPaymaster is BasePaymaster {
address private token;
contract TokenPaymaster is BasePaymaster, Claimable {
ERC20 private token;
IUniswapV2Router02 private router;
address private bridge;
address[] private tokenWethPair = new address[](2);
uint256 public postGasUsage = 300000;
// Default value from BasePaymaster, may be changed later
// if we need to increase number of signatures for bridge contract
uint256 public calldataSizeLimit = 10500;
constructor(address _relayHub, address _forwarder, address _token, address _router, address _bridge) public {
_setOwner(msg.sender);
relayHub = IRelayHub(_relayHub);
trustedForwarder = IForwarder(_forwarder);
setRelayHub(IRelayHub(_relayHub));
setTrustedForwarder(_forwarder);
token = _token;
token = ERC20(_token);
router = IUniswapV2Router02(_router);
bridge = _bridge;
@ -29,7 +33,7 @@ contract TokenPaymaster is BasePaymaster {
}
function setToken(address t) external onlyOwner {
token = t;
token = ERC20(t);
}
function setRouter(IUniswapV2Router02 r) external onlyOwner {
@ -40,13 +44,16 @@ contract TokenPaymaster is BasePaymaster {
bridge = b;
}
function versionPaymaster() external view returns (string memory) {
return "2.0.0+opengsn.tokengsn.ipaymaster";
function setPostGasUsage(uint256 gasUsage) external onlyOwner {
postGasUsage = gasUsage;
}
function() external payable {
require(address(relayHub) != address(0), "relay hub address not set");
relayHub.depositFor.value(msg.value)(address(this));
function setCalldataSizeLimit(uint256 sizeLimit) external onlyOwner {
calldataSizeLimit = sizeLimit;
}
function versionPaymaster() external view returns (string memory) {
return "2.2.0+opengsn.bridgetokengsn.ipaymaster";
}
function deposit() external payable {
@ -54,19 +61,16 @@ contract TokenPaymaster is BasePaymaster {
relayHub.depositFor.value(msg.value)(address(this));
}
function getGasLimits() external view returns (IPaymaster.GasLimits memory limits) {
function getGasAndDataLimits() external view returns (IPaymaster.GasAndDataLimits memory limits) {
return
IPaymaster.GasLimits(
IPaymaster.GasAndDataLimits(
PAYMASTER_ACCEPTANCE_BUDGET,
PRE_RELAYED_CALL_GAS_LIMIT,
postGasUsage // maximum postRelayedCall gasLimit
postGasUsage, // maximum postRelayedCall gasLimit
calldataSizeLimit
);
}
function erc20() internal view returns (ERC20) {
return ERC20(token);
}
function readBytes32(bytes memory b, uint256 index) internal pure returns (bytes32 res) {
require(b.length >= index + 32, "data too short");
assembly {
@ -79,7 +83,7 @@ contract TokenPaymaster is BasePaymaster {
bytes signature,
bytes approvalData,
uint256 maxPossibleGas
) public returns (bytes memory context, bool revertOnRecipientRevert) {
) public relayHubOnly returns (bytes memory context, bool revertOnRecipientRevert) {
(signature, approvalData);
_verifyForwarder(relayRequest);
bytes memory reqData = relayRequest.request.data;
@ -108,12 +112,9 @@ contract TokenPaymaster is BasePaymaster {
return a < b ? a : b;
}
function setPostGasUsage(uint256 gasUsage) external onlyOwner {
postGasUsage = gasUsage;
}
function postRelayedCall(bytes context, bool success, uint256 gasUseWithoutPost, GsnTypes.RelayData relayData)
public
relayHubOnly
{
(success);
// Extract data from context
@ -137,7 +138,7 @@ contract TokenPaymaster is BasePaymaster {
uint256 chargeWei = relayHub.calculateCharge(min(gasUseWithoutPost + postGasUsage, maxPossibleGas), relayData);
// Uniswap
require(erc20().approve(address(router), maxTokensFee), "approve failed");
require(token.approve(address(router), maxTokensFee), "approve failed");
// NOTE: Received eth automatically converts to relayhub deposit
uint256 spentTokens = router.swapTokensForExactETH(
chargeWei,
@ -149,7 +150,11 @@ contract TokenPaymaster is BasePaymaster {
// Send rest of tokens to user
if (spentTokens < maxTokensFee) {
require(erc20().transfer(to, maxTokensFee - spentTokens));
require(token.transfer(to, maxTokensFee - spentTokens));
}
}
function claimTokens(address _token, address _to) external onlyOwner {
claimValues(_token, _to);
}
}

View File

@ -15,7 +15,7 @@ library Message {
// offset 32: 20 bytes :: address - recipient address
// offset 52: 32 bytes :: uint256 - value
// offset 84: 32 bytes :: bytes32 - transaction hash
// offset 104: 20 bytes :: address - contract address to prevent double spending
// offset 116: 20 bytes :: address - contract address to prevent double spending
// mload always reads 32 bytes.
// so we can and have to start reading recipient at offset 20 instead of 32.

View File

@ -1,8 +1,8 @@
pragma solidity 0.4.24;
import "../upgradeable_contracts/erc20_to_native/ForeignBridgeErcToNative.sol";
import "../upgradeable_contracts/erc20_to_native/XDaiForeignBridge.sol";
contract ForeignBridgeErcToNativeMock is ForeignBridgeErcToNative {
contract XDaiForeignBridgeMock is XDaiForeignBridge {
/**
* @dev Tells the address of the DAI token in the Ganache Testchain.
*/

View File

@ -36,7 +36,8 @@ contract GSNForeignERC20Bridge is BasicForeignBridge, ERC20Bridge, BaseRelayReci
* as a commission
*/
function executeSignaturesGSN(bytes message, bytes signatures, uint256 maxTokensFee) external {
require(msg.sender == addressStorage[TRUSTED_FORWARDER], "invalid forwarder");
// Allow only forwarder calls
require(isTrustedForwarder(msg.sender), "invalid forwarder");
Message.hasEnoughValidSignatures(message, signatures, validatorContract(), false);
address recipient;
@ -59,12 +60,9 @@ contract GSNForeignERC20Bridge is BasicForeignBridge, ERC20Bridge, BaseRelayReci
function onExecuteMessageGSN(address recipient, uint256 amount, uint256 fee) internal returns (bool) {
addTotalExecutedPerDay(getCurrentDay(), amount);
// Send maxTokensFee to paymaster
uint256 unshiftMaxFee = _unshiftValue(fee);
bool first = erc20token().transfer(addressStorage[PAYMASTER], unshiftMaxFee);
// Send rest of tokens to user
uint256 unshiftLeft = _unshiftValue(amount - fee);
bool second = erc20token().transfer(recipient, unshiftLeft);
ERC20 token = erc20token();
bool first = token.transfer(addressStorage[PAYMASTER], fee);
bool second = token.transfer(recipient, amount - fee);
return first && second;
}

View File

@ -26,14 +26,6 @@ contract InterestReceiverStakeBuyback is InterestReceiverBase {
// (min received %) * (amount / 1 DAI) * (STAKE per 1 DAI)
uint256 minAmount = (minReceivedFraction * amount * uniswapRouterV2.getAmountsOut(1 ether, path)[2]) / 10**36;
bytes memory data = abi.encodeWithSelector(
uniswapRouterV2.swapExactTokensForTokens.selector,
amount,
minAmount,
path,
burnAddress,
now
);
address(uniswapRouterV2).call(data);
uniswapRouterV2.swapExactTokensForTokens(amount, minAmount, path, burnAddress, now);
}
}

View File

@ -22,15 +22,7 @@ contract InterestReceiverSwapToETH is InterestReceiverBase {
// (min received %) * (amount / 1 DAI) * (ETH per 1 DAI)
uint256 minAmount = (minReceivedFraction * amount * uniswapRouterV2.getAmountsOut(1 ether, path)[1]) / 10**36;
bytes memory data = abi.encodeWithSelector(
uniswapRouterV2.swapExactTokensForETH.selector,
amount,
minAmount,
path,
address(this),
now
);
address(uniswapRouterV2).call(data);
uniswapRouterV2.swapExactTokensForETH(amount, minAmount, path, address(this), now);
}
/**

View File

@ -2,7 +2,7 @@ pragma solidity 0.4.24;
contract VersionableBridge {
function getBridgeInterfacesVersion() external pure returns (uint64 major, uint64 minor, uint64 patch) {
return (6, 0, 0);
return (6, 1, 0);
}
/* solcov ignore next */

View File

@ -101,6 +101,15 @@ contract BasicAMB is BasicBridge, VersionableAMB {
return boolStorage[ALLOW_REENTRANT_REQUESTS];
}
/**
* @dev Withdraws the erc20 tokens or native coins from this contract.
* @param _token address of the claimed token or address(0) for native coins.
* @param _to address of the tokens/coins receiver.
*/
function claimTokens(address _token, address _to) external onlyIfUpgradeabilityOwner {
claimValues(_token, _to);
}
/**
* Internal function for retrieving current nonce value
* @return nonce value

View File

@ -45,7 +45,7 @@ contract BasicHomeAMB is BasicAMB, MessageDelivery {
* @param _data calldata passed to the executor on the other side.
* @param _gas gas limit used on the other network for executing a message.
*/
function requireToConfirmMessage(address _contract, bytes _data, uint256 _gas) external returns (bytes32) {
function requireToConfirmMessage(address _contract, bytes memory _data, uint256 _gas) public returns (bytes32) {
return _sendMessage(_contract, _data, _gas, SEND_TO_MANUAL_LANE);
}

View File

@ -10,6 +10,8 @@ contract MessageDelivery is BasicAMB, MessageProcessor {
using SafeMath for uint256;
uint256 internal constant SEND_TO_ORACLE_DRIVEN_LANE = 0x00;
// after EIP2929, call to warmed contract address costs 100 instead of 2600
uint256 internal constant MIN_GAS_PER_CALL = 100;
/**
* @dev Requests message relay to the opposite network
@ -17,7 +19,7 @@ contract MessageDelivery is BasicAMB, MessageProcessor {
* @param _data calldata passed to the executor on the other side
* @param _gas gas limit used on the other network for executing a message
*/
function requireToPassMessage(address _contract, bytes _data, uint256 _gas) public returns (bytes32) {
function requireToPassMessage(address _contract, bytes memory _data, uint256 _gas) public returns (bytes32) {
return _sendMessage(_contract, _data, _gas, SEND_TO_ORACLE_DRIVEN_LANE);
}
@ -28,11 +30,33 @@ contract MessageDelivery is BasicAMB, MessageProcessor {
* @param _gas gas limit used on the other network for executing a message
* @param _dataType AMB message dataType to be included as a part of the header
*/
function _sendMessage(address _contract, bytes _data, uint256 _gas, uint256 _dataType) internal returns (bytes32) {
function _sendMessage(address _contract, bytes memory _data, uint256 _gas, uint256 _dataType)
internal
returns (bytes32)
{
// it is not allowed to pass messages while other messages are processed
// if other is not explicitly configured
require(messageId() == bytes32(0) || allowReentrantRequests());
require(_gas >= getMinimumGasUsage(_data) && _gas <= maxGasPerTx());
require(_gas >= MIN_GAS_PER_CALL && _gas <= maxGasPerTx());
uint256 selector;
assembly {
selector := and(mload(add(_data, 4)), 0xffffffff)
}
// In order to prevent possible unauthorized ERC20 withdrawals, the following function signatures are prohibited:
// * transfer(address,uint256)
// * approve(address,uint256)
// * transferFrom(address,address,uint256)
// * approveAndCall(address,uint256,bytes)
// * transferAndCall(address,uint256,bytes)
// See https://medium.com/immunefi/xdai-stake-arbitrary-call-method-bug-postmortem-f80a90ac56e3 for more details
require(
selector != 0xa9059cbb &&
selector != 0x095ea7b3 &&
selector != 0x23b872dd &&
selector != 0x4000aea0 &&
selector != 0xcae9ca51
);
(bytes32 _messageId, bytes memory header) = _packHeader(_contract, _gas, _dataType);
@ -42,17 +66,6 @@ contract MessageDelivery is BasicAMB, MessageProcessor {
return _messageId;
}
/**
* @dev Returns a lower limit on gas limit for the particular message data
* @param _data calldata passed to the executor on the other side
*/
function getMinimumGasUsage(bytes _data) public pure returns (uint256 gas) {
// From Ethereum Yellow Paper
// 68 gas is paid for every non-zero byte of data or code for a transaction
// Starting from Istanbul hardfork, 16 gas is paid (EIP-2028)
return _data.length.mul(16);
}
/**
* @dev Packs message header into a single bytes blob
* @param _contract executor address on the other side

View File

@ -17,6 +17,6 @@ contract VersionableAMB is VersionableBridge {
* @return (major, minor, patch) version triple
*/
function getBridgeInterfacesVersion() external pure returns (uint64 major, uint64 minor, uint64 patch) {
return (6, 1, 0);
return (6, 2, 0);
}
}

View File

@ -86,7 +86,7 @@ contract CompoundConnector is InterestConnector {
/**
* @dev Claims Comp token and transfers it to the associated interest receiver.
*/
function claimCompAndPay() external {
function claimCompAndPay() external onlyEOA {
address[] memory holders = new address[](1);
holders[0] = address(this);
address[] memory markets = new address[](1);

View File

@ -1,18 +1,9 @@
pragma solidity 0.4.24;
import "../BasicForeignBridge.sol";
import "../ERC20Bridge.sol";
import "../OtherSideBridgeStorage.sol";
import "./CompoundConnector.sol";
import "../GSNForeignERC20Bridge.sol";
contract ForeignBridgeErcToNative is
BasicForeignBridge,
ERC20Bridge,
OtherSideBridgeStorage,
CompoundConnector,
GSNForeignERC20Bridge
{
contract ForeignBridgeErcToNative is ERC20Bridge, OtherSideBridgeStorage {
function initialize(
address _validatorContract,
address _erc20token,
@ -46,26 +37,6 @@ contract ForeignBridgeErcToNative is
return 0x18762d46; // bytes4(keccak256(abi.encodePacked("erc-to-native-core")))
}
function upgradeTo530(address _interestReceiver) external {
require(msg.sender == address(this));
address dai = address(daiToken());
address comp = address(compToken());
_setInterestEnabled(dai, true);
_setMinCashThreshold(dai, 1000000 ether);
_setMinInterestPaid(dai, 1000 ether);
_setInterestReceiver(dai, _interestReceiver);
_setMinInterestPaid(comp, 1 ether);
_setInterestReceiver(comp, _interestReceiver);
invest(dai);
}
function investDai() external {
invest(address(daiToken()));
}
/**
* @dev Withdraws the erc20 tokens or native coins from this contract.
* @param _token address of the claimed token or address(0) for native coins.
@ -73,51 +44,17 @@ contract ForeignBridgeErcToNative is
*/
function claimTokens(address _token, address _to) external onlyIfUpgradeabilityOwner {
// Since bridged tokens are locked at this contract, it is not allowed to claim them with the use of claimTokens function
address bridgedToken = address(erc20token());
require(_token != address(bridgedToken));
require(_token != address(cDaiToken()) || !isInterestEnabled(bridgedToken));
require(_token != address(compToken()) || !isInterestEnabled(bridgedToken));
require(_token != address(erc20token()));
claimValues(_token, _to);
}
function onExecuteMessageGSN(address recipient, uint256 amount, uint256 fee) internal returns (bool) {
addTotalExecutedPerDay(getCurrentDay(), amount);
uint256 unshiftMaxFee = _unshiftValue(fee);
uint256 unshiftLeft = _unshiftValue(amount - fee);
ERC20 token = erc20token();
ensureEnoughTokens(token, unshiftMaxFee + unshiftLeft);
// Send maxTokensFee to paymaster
bool first = token.transfer(addressStorage[PAYMASTER], unshiftMaxFee);
// Send rest of tokens to user
bool second = token.transfer(recipient, unshiftLeft);
return first && second;
}
function onExecuteMessage(
address _recipient,
uint256 _amount,
bytes32 /*_txHash*/
) internal returns (bool) {
addTotalExecutedPerDay(getCurrentDay(), _amount);
uint256 amount = _unshiftValue(_amount);
ERC20 token = erc20token();
ensureEnoughTokens(token, amount);
return token.transfer(_recipient, amount);
}
function ensureEnoughTokens(ERC20 token, uint256 amount) internal {
uint256 currentBalance = token.balanceOf(address(this));
if (currentBalance < amount) {
uint256 withdrawAmount = (amount - currentBalance).add(minCashThreshold(address(token)));
_withdraw(address(token), withdrawAmount);
}
return erc20token().transfer(_recipient, _unshiftValue(_amount));
}
function onFailedMessage(address, uint256, bytes32) internal {

View File

@ -21,6 +21,17 @@ contract InterestConnector is Ownable, ERC20Bridge {
_;
}
/**
* @dev Ensures that caller is an EOA.
* Functions with such modifier cannot be called from other contract (as well as from GSN-like approaches)
*/
modifier onlyEOA {
// solhint-disable-next-line avoid-tx-origin
require(msg.sender == tx.origin);
/* solcov ignore next */
_;
}
/**
* @dev Tells if interest earning was enabled for particular token.
* @return true, if interest bearing is enabled.
@ -123,7 +134,7 @@ contract InterestConnector is Ownable, ERC20Bridge {
* Requires interest for the given token to be enabled.
* @param _token address of the token contract.
*/
function payInterest(address _token) external interestEnabled(_token) {
function payInterest(address _token) external onlyEOA interestEnabled(_token) {
uint256 interest = interestAmount(_token);
require(interest >= minInterestPaid(_token));

View File

@ -0,0 +1,102 @@
pragma solidity 0.4.24;
import "./ForeignBridgeErcToNative.sol";
import "./CompoundConnector.sol";
import "../GSNForeignERC20Bridge.sol";
contract XDaiForeignBridge is ForeignBridgeErcToNative, CompoundConnector, GSNForeignERC20Bridge {
function initialize(
address _validatorContract,
address _erc20token,
uint256 _requiredBlockConfirmations,
uint256 _gasPrice,
uint256[3] _dailyLimitMaxPerTxMinPerTxArray, // [ 0 = _dailyLimit, 1 = _maxPerTx, 2 = _minPerTx ]
uint256[2] _homeDailyLimitHomeMaxPerTxArray, //[ 0 = _homeDailyLimit, 1 = _homeMaxPerTx ]
address _owner,
int256 _decimalShift,
address _bridgeOnOtherSide
) external onlyRelevantSender returns (bool) {
require(!isInitialized());
require(AddressUtils.isContract(_validatorContract));
require(_erc20token == address(daiToken()));
require(_decimalShift == 0);
addressStorage[VALIDATOR_CONTRACT] = _validatorContract;
uintStorage[DEPLOYED_AT_BLOCK] = block.number;
_setRequiredBlockConfirmations(_requiredBlockConfirmations);
_setGasPrice(_gasPrice);
_setLimits(_dailyLimitMaxPerTxMinPerTxArray);
_setExecutionLimits(_homeDailyLimitHomeMaxPerTxArray);
_setOwner(_owner);
_setBridgeContractOnOtherSide(_bridgeOnOtherSide);
setInitialize();
return isInitialized();
}
function erc20token() public view returns (ERC20) {
return daiToken();
}
function upgradeTo530(address _interestReceiver) external {
require(msg.sender == address(this));
address dai = address(daiToken());
address comp = address(compToken());
_setInterestEnabled(dai, true);
_setMinCashThreshold(dai, 1000000 ether);
_setMinInterestPaid(dai, 1000 ether);
_setInterestReceiver(dai, _interestReceiver);
_setMinInterestPaid(comp, 1 ether);
_setInterestReceiver(comp, _interestReceiver);
invest(dai);
}
function investDai() external {
invest(address(daiToken()));
}
/**
* @dev Withdraws the erc20 tokens or native coins from this contract.
* @param _token address of the claimed token or address(0) for native coins.
* @param _to address of the tokens/coins receiver.
*/
function claimTokens(address _token, address _to) external onlyIfUpgradeabilityOwner {
// Since bridged tokens are locked at this contract, it is not allowed to claim them with the use of claimTokens function
address bridgedToken = address(daiToken());
require(_token != address(bridgedToken));
require(_token != address(cDaiToken()) || !isInterestEnabled(bridgedToken));
require(_token != address(compToken()) || !isInterestEnabled(bridgedToken));
claimValues(_token, _to);
}
function onExecuteMessage(
address _recipient,
uint256 _amount,
bytes32 /*_txHash*/
) internal returns (bool) {
addTotalExecutedPerDay(getCurrentDay(), _amount);
ERC20 token = daiToken();
ensureEnoughTokens(token, _amount);
return token.transfer(_recipient, _amount);
}
function onExecuteMessageGSN(address recipient, uint256 amount, uint256 fee) internal returns (bool) {
ensureEnoughTokens(daiToken(), amount);
return super.onExecuteMessageGSN(recipient, amount, fee);
}
function ensureEnoughTokens(ERC20 token, uint256 amount) internal {
uint256 currentBalance = token.balanceOf(address(this));
if (currentBalance < amount) {
uint256 withdrawAmount = (amount - currentBalance).add(minCashThreshold(address(token)));
_withdraw(address(token), withdrawAmount);
}
}
}

View File

@ -13,7 +13,7 @@ const {
} = require('../deploymentUtils')
const { web3Foreign, deploymentPrivateKey, FOREIGN_RPC_URL } = require('../web3')
const {
foreignContracts: { EternalStorageProxy, BridgeValidators, ForeignBridgeErcToNative: ForeignBridge }
foreignContracts: { EternalStorageProxy, BridgeValidators, XDaiForeignBridge: ForeignBridge }
} = require('../loadContracts')
const VALIDATORS = env.VALIDATORS.split(' ')

View File

@ -11,7 +11,7 @@ function getContracts() {
ERC677BridgeToken: require(`../../build/${buildPath}/ERC677BridgeToken.json`),
ERC677BridgeTokenRewardable: require(`../../build/${buildPath}/ERC677BridgeTokenRewardable.json`),
ERC677BridgeTokenPermittable: require(`../../build/${buildPath}/PermittableToken.json`),
ForeignBridgeErcToNative: require(`../../build/${buildPath}/ForeignBridgeErcToNative.json`),
XDaiForeignBridge: require(`../../build/${buildPath}/XDaiForeignBridge.json`),
FeeManagerErcToNative: require(`../../build/${buildPath}/FeeManagerErcToNative.json`),
FeeManagerErcToNativePOSDAO: require(`../../build/${buildPath}/FeeManagerErcToNativePOSDAO.json`),
HomeBridgeErcToNative: require(`../../build/${buildPath}/HomeBridgeErcToNative.json`),

1604
package-lock.json generated

File diff suppressed because it is too large Load Diff

View File

@ -1,6 +1,6 @@
{
"name": "tokenbridge-contracts",
"version": "6.0.0-rc1",
"version": "6.0.0",
"description": "Bridge",
"main": "index.js",
"scripts": {
@ -20,12 +20,15 @@
"author": "POA network",
"license": "GPLv3",
"dependencies": {
"@opengsn/gsn": "^2.1.0",
"@opengsn/cli": "^2.2.2",
"@opengsn/contracts": "^2.2.2",
"@opengsn/provider": "^2.2.2",
"array-flat-polyfill": "^1.0.1",
"openzeppelin-solidity": "1.12.0",
"truffle": "^5.3.2",
"truffle-flattener": "^1.4.2",
"web3-provider-engine": "^14.0.6"
"web3-provider-engine": "^14.0.6",
"web3-utils": "^1.2.6"
},
"devDependencies": {
"@codechecks/client": "^0.1.9",

View File

@ -3,6 +3,7 @@ const HomeBridge = artifacts.require('HomeAMB.sol')
const GasToken = artifacts.require('GasTokenMock.sol')
const BridgeValidators = artifacts.require('BridgeValidators.sol')
const Box = artifacts.require('Box.sol')
const ERC20Mock = artifacts.require('ERC20Mock.sol')
const ERC677ReceiverTest = artifacts.require('ERC677ReceiverTest.sol')
const EternalStorageProxy = artifacts.require('EternalStorageProxy.sol')
@ -334,6 +335,49 @@ contract('ForeignAMB', async accounts => {
expect(messageId2).to.include(`${bridgeId}0000000000000001`)
expect(messageId3).to.include(`${bridgeId}0000000000000002`)
})
it('should fail to send message with blocked signatures', async () => {
const blockedFunctions = [
'transfer(address,uint256)',
'approve(address,uint256)',
'transferFrom(address,address,uint256)',
'approveAndCall(address,uint256,bytes)',
'transferAndCall(address,uint256,bytes)'
].map(web3.eth.abi.encodeFunctionSignature)
for (const signature of blockedFunctions) {
await foreignBridge.requireToPassMessage(accounts[7], signature, 10000).should.be.rejected
}
await foreignBridge.requireToPassMessage(accounts[7], '0x11223344', 10000).should.be.fulfilled
})
})
describe('claimTokens', () => {
let foreignBridge
let token
beforeEach(async () => {
const foreignBridgeV1 = await ForeignBridge.new()
token = await ERC20Mock.new('Test', 'TST', 18)
// create proxy
const proxy = await EternalStorageProxy.new()
await proxy.upgradeTo('1', foreignBridgeV1.address).should.be.fulfilled
foreignBridge = await ForeignBridge.at(proxy.address)
await foreignBridge.initialize(
FOREIGN_CHAIN_ID_HEX,
HOME_CHAIN_ID_HEX,
validatorContract.address,
oneEther,
gasPrice,
requiredBlockConfirmations,
owner
)
})
it('should claim mistakenly locked tokens', async () => {
await token.mint(foreignBridge.address, oneEther)
await foreignBridge.claimTokens(token.address, owner, { from: accounts[2] }).should.be.rejected
await foreignBridge.claimTokens(token.address, owner, { from: owner }).should.be.fulfilled
expect(await token.balanceOf(owner)).to.be.bignumber.equal(oneEther)
})
})
describe('executeSignatures', () => {
let foreignBridge

View File

@ -3,7 +3,7 @@ const BridgeValidators = artifacts.require('BridgeValidators.sol')
const EternalStorageProxy = artifacts.require('EternalStorageProxy.sol')
const ERC677BridgeToken = artifacts.require('ERC677BridgeToken.sol')
const ERC20Mock = artifacts.require('ERC20Mock.sol')
const ForeignBridgeErcToNativeMock = artifacts.require('ForeignBridgeErcToNativeMock.sol')
const XDaiForeignBridgeMock = artifacts.require('XDaiForeignBridgeMock.sol')
const { expect } = require('chai')
const { ERROR_MSG, ZERO_ADDRESS, toBN } = require('../setup')
@ -212,7 +212,7 @@ contract('ForeignBridge_ERC20_to_Native', async accounts => {
const value = ether('0.25')
let foreignBridge
beforeEach(async () => {
foreignBridge = await ForeignBridgeErcToNativeMock.new()
foreignBridge = await ForeignBridge.new()
token = await ERC677BridgeToken.new('Some ERC20', 'RSZT', 18)
await foreignBridge.initialize(
validatorContract.address,
@ -367,7 +367,7 @@ contract('ForeignBridge_ERC20_to_Native', async accounts => {
await multisigValidatorContract.initialize(2, twoAuthorities, ownerOfValidatorContract, {
from: ownerOfValidatorContract
})
foreignBridgeWithMultiSignatures = await ForeignBridgeErcToNativeMock.new()
foreignBridgeWithMultiSignatures = await ForeignBridge.new()
await foreignBridgeWithMultiSignatures.initialize(
multisigValidatorContract.address,
token.address,
@ -429,7 +429,7 @@ contract('ForeignBridge_ERC20_to_Native', async accounts => {
await validatorContractWith3Signatures.initialize(3, authoritiesFiveAccs, ownerOfValidators)
const erc20Token = await ERC677BridgeToken.new('Some ERC20', 'RSZT', 18)
const value = halfEther
const foreignBridgeWithThreeSigs = await ForeignBridgeErcToNativeMock.new()
const foreignBridgeWithThreeSigs = await ForeignBridge.new()
await foreignBridgeWithThreeSigs.initialize(
validatorContractWith3Signatures.address,
@ -477,7 +477,7 @@ contract('ForeignBridge_ERC20_to_Native', async accounts => {
await validatorContract.initialize(MAX_SIGNATURES, addresses, ownerOfValidators)
const erc20Token = await ERC677BridgeToken.new('Some ERC20', 'RSZT', 18)
const foreignBridgeWithMaxSigs = await ForeignBridgeErcToNativeMock.new()
const foreignBridgeWithMaxSigs = await ForeignBridge.new()
await foreignBridgeWithMaxSigs.initialize(
validatorContract.address,
@ -622,7 +622,7 @@ contract('ForeignBridge_ERC20_to_Native', async accounts => {
const valueOnHome = toBN(valueOnForeign * 10 ** decimalShift)
const owner = accounts[0]
const foreignBridgeImpl = await ForeignBridgeErcToNativeMock.new()
const foreignBridgeImpl = await ForeignBridge.new()
const storageProxy = await EternalStorageProxy.new().should.be.fulfilled
await storageProxy.upgradeTo('1', foreignBridgeImpl.address).should.be.fulfilled
const foreignBridge = await ForeignBridge.at(storageProxy.address)
@ -673,7 +673,7 @@ contract('ForeignBridge_ERC20_to_Native', async accounts => {
await multisigValidatorContract.initialize(2, twoAuthorities, ownerOfValidatorContract, {
from: ownerOfValidatorContract
})
const foreignBridgeWithMultiSignatures = await ForeignBridgeErcToNativeMock.new()
const foreignBridgeWithMultiSignatures = await ForeignBridge.new()
await foreignBridgeWithMultiSignatures.initialize(
multisigValidatorContract.address,
token.address,
@ -862,7 +862,7 @@ contract('ForeignBridge_ERC20_to_Native', async accounts => {
})
beforeEach(async () => {
foreignBridge = await ForeignBridgeErcToNativeMock.new()
foreignBridge = await XDaiForeignBridgeMock.new()
await foreignBridge.initialize(
validatorContract.address,
dai.address,

View File

@ -1,8 +1,7 @@
// Required for opengsn proper work
require('array-flat-polyfill')
const ForeignBridge = artifacts.require('ForeignBridgeErcToNative.sol')
const ForeignBridgeErcToNativeMock = artifacts.require('ForeignBridgeErcToNativeMock.sol')
const XDaiForeignBridgeMock = artifacts.require('XDaiForeignBridgeMock.sol')
const BridgeValidators = artifacts.require('BridgeValidators.sol')
const ERC677BridgeToken = artifacts.require('ERC677BridgeToken.sol')
@ -10,8 +9,8 @@ const UniswapRouterMock = artifacts.require('UniswapRouterMock.sol')
const TokenPaymaster = artifacts.require('TokenPaymaster.sol')
// GSN
const { RelayProvider } = require('@opengsn/gsn')
const { GsnTestEnvironment } = require('@opengsn/gsn/dist/GsnTestEnvironment')
const { RelayProvider } = require('@opengsn/provider')
const { GsnTestEnvironment } = require('@opengsn/cli/dist/GsnTestEnvironment')
const { toBN, ERROR_MSG, ZERO_ADDRESS } = require('../setup')
const {
@ -23,6 +22,7 @@ const {
evalMetrics,
paymasterError
} = require('../helpers/helpers')
const getCompoundContracts = require('../compound/contracts')
const requireBlockConfirmations = 8
const gasPrice = web3.utils.toWei('1', 'gwei')
@ -33,6 +33,7 @@ const minPerTx = ether('0.01')
const dailyLimit = homeDailyLimit
const ZERO = toBN(0)
const decimalShiftZero = 0
const HALF_ETHER = ether('0.5')
const FIVE_ETHER = ether('5')
const GSNGasLimit = 500000
@ -43,6 +44,8 @@ function createEmptyAccount(relayer) {
}
contract('ForeignBridge_ERC20_to_Native_GSN', async accounts => {
const faucet = accounts[6] // account where all Compound-related DAIs where minted
let validatorContract
let authorities
let owner
@ -65,16 +68,18 @@ contract('ForeignBridge_ERC20_to_Native_GSN', async accounts => {
authorities = [accounts[1], accounts[2]]
owner = accounts[0]
await validatorContract.initialize(1, authorities, owner)
otherSideBridge = await ForeignBridge.new()
otherSideBridge = await XDaiForeignBridgeMock.new()
const contracts = await getCompoundContracts()
token = contracts.dai
})
after(async () => {
await GsnTestEnvironment.stopGsn()
})
describe('#initialize', async () => {
it('should initialize', async () => {
token = await ERC677BridgeToken.new('Some ERC20', 'RSZT', 18)
it('should initialize paymaster', async () => {
router = await UniswapRouterMock.new()
foreignBridge = await ForeignBridgeErcToNativeMock.new()
foreignBridge = await XDaiForeignBridgeMock.new()
paymaster = await TokenPaymaster.new(
RelayHubAddress,
@ -100,9 +105,8 @@ contract('ForeignBridge_ERC20_to_Native_GSN', async accounts => {
let GSNRelayer
let GSNSigner
beforeEach(async () => {
token = await ERC677BridgeToken.new('Some ERC20', 'RSZT', 18)
ForeignBridgeErcToNativeMock.web3.setProvider(web3.currentProvider)
foreignBridge = await ForeignBridgeErcToNativeMock.new()
XDaiForeignBridgeMock.web3.setProvider(web3.currentProvider)
foreignBridge = await XDaiForeignBridgeMock.new()
await foreignBridge.initialize(
validatorContract.address,
token.address,
@ -128,7 +132,7 @@ contract('ForeignBridge_ERC20_to_Native_GSN', async accounts => {
await foreignBridge.setTrustedForwarder(ForwarderAddress)
await foreignBridge.setPayMaster(paymaster.address)
await token.mint(foreignBridge.address, BRIDGE_TOKENS)
await token.transfer(foreignBridge.address, BRIDGE_TOKENS, { from: faucet })
// Give Router 1 ether
await web3.eth.sendTransaction({
@ -158,7 +162,7 @@ contract('ForeignBridge_ERC20_to_Native_GSN', async accounts => {
// From now on all calls will be relayed through GSN.
// If you want to omit GSN specify
// { useGSN: false } in transaction details
ForeignBridgeErcToNativeMock.web3.setProvider(GSNRelayer)
XDaiForeignBridgeMock.web3.setProvider(GSNRelayer)
})
it('should allow to executeSignaturesGSN', async () => {
const recipientAccount = GSNSigner
@ -243,7 +247,7 @@ contract('ForeignBridge_ERC20_to_Native_GSN', async accounts => {
.fulfilled
// tx 2
await token.mint(foreignBridge.address, BRIDGE_TOKENS)
await token.transfer(foreignBridge.address, BRIDGE_TOKENS, { from: faucet })
const from2 = createEmptyAccount(GSNRelayer)
const message2 = createMessage(from2, REQUESTED_TOKENS, transactionHash, foreignBridge.address)
const signature2 = await sign(authorities[0], message2)
@ -282,4 +286,29 @@ contract('ForeignBridge_ERC20_to_Native_GSN', async accounts => {
.should.be.rejectedWith(`${ERROR_MSG} invalid forwarder`)
})
})
describe('#claimTokens', async () => {
it('can send erc20', async () => {
router = await UniswapRouterMock.new()
paymaster = await TokenPaymaster.new(
RelayHubAddress,
ForwarderAddress,
ZERO_ADDRESS,
router.address,
ZERO_ADDRESS,
{ from: owner }
)
const token = await ERC677BridgeToken.new('Some ERC20', 'RSZT', 18)
await token.mint(accounts[1], HALF_ETHER).should.be.fulfilled
await token.transfer(paymaster.address, HALF_ETHER, { from: accounts[1] })
await paymaster.claimTokens(token.address, accounts[3], { from: accounts[3] }).should.be.rejectedWith(ERROR_MSG)
await paymaster.claimTokens(token.address, accounts[3], { from: owner })
const pmBalance = await token.balanceOf(paymaster.address)
const accBalance = await token.balanceOf(accounts[3])
pmBalance.should.be.bignumber.equal(ZERO)
accBalance.should.be.bignumber.equal(HALF_ETHER)
})
})
})