feat: ConnectGelatoProviderPayment auto-top to GelatoCore

This commit is contained in:
gitpusha 2020-11-03 19:18:06 +01:00 committed by Twin Fish
parent 63cd6daa42
commit 10f62bbc9a
11 changed files with 364 additions and 69 deletions

View File

@ -48,7 +48,7 @@ contract ConnectGelatoDataForFullRefinance is ConnectorInterface {
uint256 internal immutable _id;
address internal immutable _connectGelatoProviderPayment;
uint256 public constant GAS_COST = 1490779 + (14908 * 12); // 1490779 + ~12% (Estimated Value)
uint256 public constant GAS_COST = 1800000 + (18000 * 12); // 1800000 + ~12% (Estimated Value)
constructor(uint256 id, address connectGelatoProviderPayment) {
_id = id;

View File

@ -4,12 +4,16 @@ pragma solidity 0.7.4;
import {
IConnectGelatoProviderPayment
} from "../../interfaces/InstaDapp/connectors/IConnectGelatoProviderPayment.sol";
import {Ownable} from "../../vendor/Ownable.sol";
import {Address} from "../../vendor/Address.sol";
import {IERC20} from "../../interfaces/tokens/IERC20.sol";
import {GelatoString} from "../../lib/GelatoString.sol";
import {IERC20} from "../../vendor/IERC20.sol";
import {SafeERC20} from "../../vendor/SafeERC20.sol";
import {_getUint, _setUint} from "../../functions/InstaDapp/FInstaDapp.sol";
import {ETH} from "../../constants/CInstaDapp.sol";
import {Ownable} from "../../vendor/Ownable.sol";
import {
IGelatoProviders
} from "@gelatonetwork/core/contracts/gelato_core/interfaces/IGelatoProviders.sol";
/// @title ConnectGelatoProviderPayment
/// @notice InstaDapp Connector to compensate Gelato automation-gas Providers.
@ -19,11 +23,16 @@ contract ConnectGelatoProviderPayment is
Ownable
{
using Address for address payable;
using GelatoString for string;
using SafeERC20 for IERC20;
// solhint-disable-next-line const-name-snakecase
string public constant override name = "ConnectGelatoProviderPayment-v1.0";
address
public constant
override GELATO_CORE = 0x1d681d76ce96E4d70a88A00EBbcfc1E47808d0b8;
address public override gelatoProvider;
uint256 internal immutable _id;
@ -55,6 +64,7 @@ contract ConnectGelatoProviderPayment is
/// - _getId does not match actual InstaMemory gelatoProvider payment slot
/// - _token balance not in DSA
/// - worthless _token risk
/// payable to be compatible in conjunction with DSA.cast payable target
/// @param _token The token used to pay the Provider.
/// @param _amt The amount of _token to pay the Gelato Provider.
/// @param _getId The InstaMemory slot at which the payment amount was stored.
@ -71,10 +81,25 @@ contract ConnectGelatoProviderPayment is
provider != address(0x0),
"ConnectGelatoProviderPayment.payProvider:!provider"
);
uint256 amt = _getUint(_getId, _amt);
_setUint(_setId, amt);
_token == ETH
? payable(provider).sendValue(amt)
: IERC20(_token).safeTransfer(provider, amt);
if (_token == ETH) {
// solhint-disable no-empty-blocks
try
IGelatoProviders(GELATO_CORE).provideFunds{value: amt}(provider)
{} catch Error(string memory error) {
error.revertWithInfo(
"ConnectGelatoProviderPayment.payProvider.provideFunds:"
);
} catch {
revert(
"ConnectGelatoProviderPayment.payProvider.provideFunds:undefined"
);
}
} else {
IERC20(_token).safeTransfer(provider, amt);
}
}
}

View File

@ -4,11 +4,11 @@ pragma solidity 0.7.4;
import {sub, wmul, wdiv} from "../../vendor/DSMath.sol";
function _wCalcCollateralToWithdraw(
uint256 _wMinColRatioMaker,
uint256 _wMinColRatioA,
uint256 _wMinColRatioB,
uint256 _wColPrice,
uint256 _wPricedCol,
uint256 _wDaiDebtOnMaker
uint256 _wDebtOnA
) pure returns (uint256) {
return
wdiv(
@ -16,13 +16,10 @@ function _wCalcCollateralToWithdraw(
_wPricedCol,
wdiv(
sub(
wmul(_wMinColRatioMaker, _wPricedCol),
wmul(
_wMinColRatioMaker,
wmul(_wMinColRatioB, _wDaiDebtOnMaker)
)
wmul(_wMinColRatioA, _wPricedCol),
wmul(_wMinColRatioA, wmul(_wMinColRatioB, _wDebtOnA))
),
sub(_wMinColRatioMaker, _wMinColRatioB)
sub(_wMinColRatioA, _wMinColRatioB)
)
),
_wColPrice
@ -30,25 +27,22 @@ function _wCalcCollateralToWithdraw(
}
function _wCalcDebtToRepay(
uint256 _wMinColRatioMaker,
uint256 _wMinColRatioA,
uint256 _wMinColRatioB,
uint256 _wPricedCol,
uint256 _wDaiDebtOnMaker
uint256 _wDebtOnA
) pure returns (uint256) {
return
sub(
_wDaiDebtOnMaker,
_wDebtOnA,
wmul(
wdiv(1e18, _wMinColRatioMaker),
wdiv(1e18, _wMinColRatioA),
wdiv(
sub(
wmul(_wMinColRatioMaker, _wPricedCol),
wmul(
_wMinColRatioMaker,
wmul(_wMinColRatioB, _wDaiDebtOnMaker)
)
wmul(_wMinColRatioA, _wPricedCol),
wmul(_wMinColRatioA, wmul(_wMinColRatioB, _wDebtOnA))
),
sub(_wMinColRatioMaker, _wMinColRatioB)
sub(_wMinColRatioA, _wMinColRatioB)
)
)
);

View File

@ -14,4 +14,7 @@ interface IConnectGelatoProviderPayment is ConnectorInterface {
) external payable;
function gelatoProvider() external view returns (address);
// solhint-disable-next-line func-name-mixedcase
function GELATO_CORE() external pure returns (address);
}

View File

@ -1,8 +0,0 @@
// SPDX-License-Identifier: MIT
pragma solidity 0.7.4;
interface IERC20 {
function transfer(address recipient, uint256 amount)
external
returns (bool);
}

76
contracts/vendor/IERC20.sol vendored Normal file
View File

@ -0,0 +1,76 @@
// SPDX-License-Identifier: MIT
pragma solidity 0.7.4;
/**
* @dev Interface of the ERC20 standard as defined in the EIP.
*/
interface IERC20 {
/**
* @dev Returns the amount of tokens in existence.
*/
function totalSupply() external view returns (uint256);
/**
* @dev Returns the amount of tokens owned by `account`.
*/
function balanceOf(address account) external view returns (uint256);
/**
* @dev Moves `amount` tokens from the caller's account to `recipient`.
*
* Returns a boolean value indicating whether the operation succeeded.
*
* Emits a {Transfer} event.
*/
function transfer(address recipient, uint256 amount) external returns (bool);
/**
* @dev Returns the remaining number of tokens that `spender` will be
* allowed to spend on behalf of `owner` through {transferFrom}. This is
* zero by default.
*
* This value changes when {approve} or {transferFrom} are called.
*/
function allowance(address owner, address spender) external view returns (uint256);
/**
* @dev Sets `amount` as the allowance of `spender` over the caller's tokens.
*
* Returns a boolean value indicating whether the operation succeeded.
*
* IMPORTANT: Beware that changing an allowance with this method brings the risk
* that someone may use both the old and the new allowance by unfortunate
* transaction ordering. One possible solution to mitigate this race
* condition is to first reduce the spender's allowance to 0 and set the
* desired value afterwards:
* https://github.com/ethereum/EIPs/issues/20#issuecomment-263524729
*
* Emits an {Approval} event.
*/
function approve(address spender, uint256 amount) external returns (bool);
/**
* @dev Moves `amount` tokens from `sender` to `recipient` using the
* allowance mechanism. `amount` is then deducted from the caller's
* allowance.
*
* Returns a boolean value indicating whether the operation succeeded.
*
* Emits a {Transfer} event.
*/
function transferFrom(address sender, address recipient, uint256 amount) external returns (bool);
/**
* @dev Emitted when `value` tokens are moved from one account (`from`) to
* another (`to`).
*
* Note that `value` may be zero.
*/
event Transfer(address indexed from, address indexed to, uint256 value);
/**
* @dev Emitted when the allowance of a `spender` for an `owner` is set by
* a call to {approve}. `value` is the new allowance.
*/
event Approval(address indexed owner, address indexed spender, uint256 value);
}

View File

@ -1,21 +1,57 @@
// SPDX-License-Identifier: MIT
pragma solidity 0.7.4;
import {IERC20} from "./IERC20.sol";
import {SafeMath} from "./SafeMath.sol";
import {Address} from "./Address.sol";
import {IERC20} from "../interfaces/tokens/IERC20.sol";
/**
* @title SafeERC20
* @dev Wrappers around ERC20 operations that throw on failure (when the token
* contract returns false). Tokens that return no value (and instead revert or
* throw on failure) are also supported, non-reverting calls are assumed to be
* successful.
* To use this library you can add a `using SafeERC20 for IERC20;` statement to your contract,
* which allows you to call the safe operations as `token.safeTransfer(...)`, etc.
*/
library SafeERC20 {
using SafeMath for uint256;
using Address for address;
function safeTransfer(
IERC20 token,
address to,
uint256 value
) internal {
_callOptionalReturn(
token,
abi.encodeWithSelector(token.transfer.selector, to, value)
function safeTransfer(IERC20 token, address to, uint256 value) internal {
_callOptionalReturn(token, abi.encodeWithSelector(token.transfer.selector, to, value));
}
function safeTransferFrom(IERC20 token, address from, address to, uint256 value) internal {
_callOptionalReturn(token, abi.encodeWithSelector(token.transferFrom.selector, from, to, value));
}
/**
* @dev Deprecated. This function has issues similar to the ones found in
* {IERC20-approve}, and its usage is discouraged.
*
* Whenever possible, use {safeIncreaseAllowance} and
* {safeDecreaseAllowance} instead.
*/
function safeApprove(IERC20 token, address spender, uint256 value) internal {
// safeApprove should only be called when setting an initial allowance,
// or when resetting it to zero. To increase and decrease it, use
// 'safeIncreaseAllowance' and 'safeDecreaseAllowance'
// solhint-disable-next-line max-line-length
require((value == 0) || (token.allowance(address(this), spender) == 0),
"SafeERC20: approve from non-zero to non-zero allowance"
);
_callOptionalReturn(token, abi.encodeWithSelector(token.approve.selector, spender, value));
}
function safeIncreaseAllowance(IERC20 token, address spender, uint256 value) internal {
uint256 newAllowance = token.allowance(address(this), spender).add(value);
_callOptionalReturn(token, abi.encodeWithSelector(token.approve.selector, spender, newAllowance));
}
function safeDecreaseAllowance(IERC20 token, address spender, uint256 value) internal {
uint256 newAllowance = token.allowance(address(this), spender).sub(value, "SafeERC20: decreased allowance below zero");
_callOptionalReturn(token, abi.encodeWithSelector(token.approve.selector, spender, newAllowance));
}
/**
@ -29,17 +65,10 @@ library SafeERC20 {
// we're implementing it ourselves. We use {Address.functionCall} to perform this call, which verifies that
// the target address contains contract code and also asserts for success in the low-level call.
bytes memory returndata = address(token).functionCall(
data,
"SafeERC20: low-level call failed"
);
if (returndata.length > 0) {
// Return data is optional
bytes memory returndata = address(token).functionCall(data, "SafeERC20: low-level call failed");
if (returndata.length > 0) { // Return data is optional
// solhint-disable-next-line max-line-length
require(
abi.decode(returndata, (bool)),
"SafeERC20: ERC20 operation did not succeed"
);
require(abi.decode(returndata, (bool)), "SafeERC20: ERC20 operation did not succeed");
}
}
}

158
contracts/vendor/SafeMath.sol vendored Normal file
View File

@ -0,0 +1,158 @@
// SPDX-License-Identifier: MIT
pragma solidity 0.7.4;
/**
* @dev Wrappers over Solidity's arithmetic operations with added overflow
* checks.
*
* Arithmetic operations in Solidity wrap on overflow. This can easily result
* in bugs, because programmers usually assume that an overflow raises an
* error, which is the standard behavior in high level programming languages.
* `SafeMath` restores this intuition by reverting the transaction when an
* operation overflows.
*
* Using this library instead of the unchecked operations eliminates an entire
* class of bugs, so it's recommended to use it always.
*/
library SafeMath {
/**
* @dev Returns the addition of two unsigned integers, reverting on
* overflow.
*
* Counterpart to Solidity's `+` operator.
*
* Requirements:
*
* - Addition cannot overflow.
*/
function add(uint256 a, uint256 b) internal pure returns (uint256) {
uint256 c = a + b;
require(c >= a, "SafeMath: addition overflow");
return c;
}
/**
* @dev Returns the subtraction of two unsigned integers, reverting on
* overflow (when the result is negative).
*
* Counterpart to Solidity's `-` operator.
*
* Requirements:
*
* - Subtraction cannot overflow.
*/
function sub(uint256 a, uint256 b) internal pure returns (uint256) {
return sub(a, b, "SafeMath: subtraction overflow");
}
/**
* @dev Returns the subtraction of two unsigned integers, reverting with custom message on
* overflow (when the result is negative).
*
* Counterpart to Solidity's `-` operator.
*
* Requirements:
*
* - Subtraction cannot overflow.
*/
function sub(uint256 a, uint256 b, string memory errorMessage) internal pure returns (uint256) {
require(b <= a, errorMessage);
uint256 c = a - b;
return c;
}
/**
* @dev Returns the multiplication of two unsigned integers, reverting on
* overflow.
*
* Counterpart to Solidity's `*` operator.
*
* Requirements:
*
* - Multiplication cannot overflow.
*/
function mul(uint256 a, uint256 b) internal pure returns (uint256) {
// Gas optimization: this is cheaper than requiring 'a' not being zero, but the
// benefit is lost if 'b' is also tested.
// See: https://github.com/OpenZeppelin/openzeppelin-contracts/pull/522
if (a == 0) {
return 0;
}
uint256 c = a * b;
require(c / a == b, "SafeMath: multiplication overflow");
return c;
}
/**
* @dev Returns the integer division of two unsigned integers. Reverts on
* division by zero. The result is rounded towards zero.
*
* Counterpart to Solidity's `/` operator. Note: this function uses a
* `revert` opcode (which leaves remaining gas untouched) while Solidity
* uses an invalid opcode to revert (consuming all remaining gas).
*
* Requirements:
*
* - The divisor cannot be zero.
*/
function div(uint256 a, uint256 b) internal pure returns (uint256) {
return div(a, b, "SafeMath: division by zero");
}
/**
* @dev Returns the integer division of two unsigned integers. Reverts with custom message on
* division by zero. The result is rounded towards zero.
*
* Counterpart to Solidity's `/` operator. Note: this function uses a
* `revert` opcode (which leaves remaining gas untouched) while Solidity
* uses an invalid opcode to revert (consuming all remaining gas).
*
* Requirements:
*
* - The divisor cannot be zero.
*/
function div(uint256 a, uint256 b, string memory errorMessage) internal pure returns (uint256) {
require(b > 0, errorMessage);
uint256 c = a / b;
// assert(a == b * c + a % b); // There is no case in which this doesn't hold
return c;
}
/**
* @dev Returns the remainder of dividing two unsigned integers. (unsigned integer modulo),
* Reverts when dividing by zero.
*
* Counterpart to Solidity's `%` operator. This function uses a `revert`
* opcode (which leaves remaining gas untouched) while Solidity uses an
* invalid opcode to revert (consuming all remaining gas).
*
* Requirements:
*
* - The divisor cannot be zero.
*/
function mod(uint256 a, uint256 b) internal pure returns (uint256) {
return mod(a, b, "SafeMath: modulo by zero");
}
/**
* @dev Returns the remainder of dividing two unsigned integers. (unsigned integer modulo),
* Reverts with custom message when dividing by zero.
*
* Counterpart to Solidity's `%` operator. This function uses a `revert`
* opcode (which leaves remaining gas untouched) while Solidity uses an
* invalid opcode to revert (consuming all remaining gas).
*
* Requirements:
*
* - The divisor cannot be zero.
*/
function mod(uint256 a, uint256 b, string memory errorMessage) internal pure returns (uint256) {
require(b != 0, errorMessage);
return a % b;
}
}

View File

@ -179,18 +179,22 @@ describe("Full Debt Bridge refinancing loan from Maker to Compound", function ()
//#region EXPECTED OUTCOME
const gasFeesPaidFromCol = ethers.utils
.parseUnits(String(1490779 + 14908 * 12), 0)
.mul(gelatoGasPrice);
const debtOnMakerBefore = await contracts.makerResolver.getMakerVaultDebt(
vaultId
);
const gasFeesPaidFromCol = ethers.utils
.parseUnits(String(1800000 + 18000 * 12), 0)
.mul(gelatoGasPrice);
const pricedCollateral = (
await contracts.makerResolver.getMakerVaultCollateralBalance(vaultId)
).sub(gasFeesPaidFromCol);
//#endregion
const providerBalanceBeforeExecution = await wallets.providerWallet.getBalance();
const providerBalanceBeforeExecution = await contracts.gelatoCore.providerFunds(
wallets.providerAddress
);
await expect(
contracts.gelatoCore.connect(wallets.executorWallet).exec(taskReceipt, {
@ -214,9 +218,9 @@ describe("Full Debt Bridge refinancing loan from Maker to Compound", function ()
// }
// await GelatoCoreLib.sleep(10000);
expect(await wallets.providerWallet.getBalance()).to.be.gt(
providerBalanceBeforeExecution
);
expect(
await contracts.gelatoCore.providerFunds(wallets.providerAddress)
).to.be.gt(providerBalanceBeforeExecution);
// compound position of DSA on cDai and cEth
const compoundPosition = await contracts.compoundResolver.getCompoundData(

View File

@ -183,19 +183,22 @@ describe("Full Debt Bridge refinancing loan from ETH-A to ETH-B", function () {
// by a debt refinancing in compound.
//#region EXPECTED OUTCOME
const gasFeesPaidFromCol = ethers.utils
.parseUnits(String(1490779 + 14908 * 12), 0)
.mul(gelatoGasPrice);
const debtOnMakerBefore = await contracts.makerResolver.getMakerVaultDebt(
vaultAId
);
const gasFeesPaidFromCol = ethers.utils
.parseUnits(String(1800000 + 18000 * 12), 0)
.mul(gelatoGasPrice);
const pricedCollateral = (
await contracts.makerResolver.getMakerVaultCollateralBalance(vaultAId)
).sub(gasFeesPaidFromCol);
//#endregion
const providerBalanceBeforeExecution = await wallets.providerWallet.getBalance();
const providerBalanceBeforeExecution = await contracts.gelatoCore.providerFunds(
wallets.providerAddress
);
await expect(
contracts.gelatoCore.connect(wallets.executorWallet).exec(taskReceipt, {
@ -233,9 +236,9 @@ describe("Full Debt Bridge refinancing loan from ETH-A to ETH-B", function () {
vaultBId
);
expect(await wallets.providerWallet.getBalance()).to.be.gt(
providerBalanceBeforeExecution
);
expect(
await contracts.gelatoCore.providerFunds(wallets.providerAddress)
).to.be.gt(providerBalanceBeforeExecution);
// Estimated amount to borrowed token should be equal to the actual one read on compound contracts
expect(debtOnMakerBefore).to.be.equal(debtOnMakerVaultB);

View File

@ -2,6 +2,8 @@ const {expect} = require("chai");
const hre = require("hardhat");
const {ethers} = hre;
const GelatoCoreLib = require("@gelatonetwork/core");
// #region Contracts ABI
const ConnectMaker = require("../../pre-compiles/ConnectMaker.json");
@ -28,6 +30,8 @@ describe("ConnectGelatoProviderPayment Unit Test", function () {
let providerWallet;
let providerAddress;
let gelatoCore;
let instaList;
let instaIndex;
let DAI;
@ -54,6 +58,11 @@ describe("ConnectGelatoProviderPayment Unit Test", function () {
hre.network.config.InstaMaster
);
gelatoCore = await ethers.getContractAt(
GelatoCoreLib.GelatoCore.abi,
hre.network.config.GelatoCore
);
// Hardhat default accounts prefilled with 100 ETH
expect(await userWallet.getBalance()).to.be.gt(
ethers.utils.parseEther("10")
@ -253,7 +262,9 @@ describe("ConnectGelatoProviderPayment Unit Test", function () {
});
it("#4: payProvider should pay to Provider 1 ether", async function () {
const providerBalanceBefore = await providerWallet.getBalance();
const providerBalanceOnGelatoCoreBefore = await gelatoCore.providerFunds(
providerAddress
);
await dsa.cast(
[connectBasic.address, connectGelatoProviderPayment.address],
@ -282,8 +293,8 @@ describe("ConnectGelatoProviderPayment Unit Test", function () {
}
);
expect(await providerWallet.getBalance()).to.be.equal(
providerBalanceBefore.add(ethers.utils.parseEther("1"))
expect(await gelatoCore.providerFunds(providerAddress)).to.be.equal(
providerBalanceOnGelatoCoreBefore.add(ethers.utils.parseEther("1"))
);
});
});