diff --git a/contracts/DODOEthProxy.sol b/contracts/DODOEthProxy.sol index a619be1..aebaa52 100644 --- a/contracts/DODOEthProxy.sol +++ b/contracts/DODOEthProxy.sol @@ -15,12 +15,10 @@ import {IDODO} from "./intf/IDODO.sol"; import {IERC20} from "./intf/IERC20.sol"; import {IWETH} from "./intf/IWETH.sol"; - interface IDODOZoo { function getDODO(address baseToken, address quoteToken) external view returns (address); } - /** * @title DODO Eth Proxy * @author DODO Breeder @@ -112,7 +110,7 @@ contract DODOEthProxy is ReentrancyGuard { require(DODO != address(0), "DODO_NOT_EXIST"); payTokenAmount = IDODO(DODO).queryBuyBaseToken(ethAmount); _transferIn(quoteTokenAddress, msg.sender, payTokenAmount); - IERC20(quoteTokenAddress).approve(DODO, payTokenAmount); + IERC20(quoteTokenAddress).safeApprove(DODO, payTokenAmount); IDODO(DODO).buyBaseToken(ethAmount, maxPayTokenAmount, ""); IWETH(_WETH_).withdraw(ethAmount); msg.sender.transfer(ethAmount); @@ -127,7 +125,7 @@ contract DODOEthProxy is ReentrancyGuard { ) external preventReentrant returns (uint256 receiveEthAmount) { address DODO = IDODOZoo(_DODO_ZOO_).getDODO(baseTokenAddress, _WETH_); require(DODO != address(0), "DODO_NOT_EXIST"); - IERC20(baseTokenAddress).approve(DODO, tokenAmount); + IERC20(baseTokenAddress).safeApprove(DODO, tokenAmount); _transferIn(baseTokenAddress, msg.sender, tokenAmount); receiveEthAmount = IDODO(DODO).sellBaseToken(tokenAmount, minReceiveEthAmount, ""); IWETH(_WETH_).withdraw(receiveEthAmount); diff --git a/contracts/lib/SafeERC20.sol b/contracts/lib/SafeERC20.sol index 09440bf..67a0188 100644 --- a/contracts/lib/SafeERC20.sol +++ b/contracts/lib/SafeERC20.sol @@ -45,6 +45,22 @@ library SafeERC20 { ); } + 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)); + } + /** * @dev Imitates a Solidity high-level call (i.e. a regular function call to a contract), relaxing the requirement * on the return value: the return value is optional (but if data is returned, it must not be false). diff --git a/test/DODOEthProxyAsBase.test.ts b/test/DODOEthProxyAsBase.test.ts index 010ed7b..e72babc 100644 --- a/test/DODOEthProxyAsBase.test.ts +++ b/test/DODOEthProxyAsBase.test.ts @@ -101,14 +101,14 @@ describe("DODO ETH PROXY", () => { describe("buy&sell eth directly", () => { it("buy", async () => { const buyAmount = "1"; - logGas( - await DODOEthProxy.methods + await logGas( + DODOEthProxy.methods .buyEthWithToken( ctx.QUOTE.options.address, decimalStr(buyAmount), decimalStr("200") - ) - .send(ctx.sendParam(trader)), + ), + ctx.sendParam(trader), "buy ETH with token directly" ); assert.strictEqual( @@ -122,14 +122,14 @@ describe("DODO ETH PROXY", () => { }); it("sell", async () => { const sellAmount = "1"; - logGas( - await DODOEthProxy.methods + await logGas( + DODOEthProxy.methods .sellEthToToken( ctx.QUOTE.options.address, decimalStr(sellAmount), decimalStr("50") - ) - .send(ctx.sendParam(trader, sellAmount)), + ), + ctx.sendParam(trader, sellAmount), "sell ETH to token directly" ); assert.strictEqual( diff --git a/test/DODOEthProxyAsQuote.test.ts b/test/DODOEthProxyAsQuote.test.ts index 985a8b4..04b1932 100644 --- a/test/DODOEthProxyAsQuote.test.ts +++ b/test/DODOEthProxyAsQuote.test.ts @@ -103,14 +103,12 @@ describe("DODO ETH PROXY", () => { const maxPayEthAmount = "2.1"; const ethInPoolBefore = decimalStr("10"); const traderEthBalanceBefore = await ctx.Web3.eth.getBalance(trader); - const txReceipt: TransactionReceipt = await DODOEthProxy.methods + const txReceipt: TransactionReceipt = await logGas(DODOEthProxy.methods .buyTokenWithEth( ctx.BASE.options.address, decimalStr("200"), decimalStr(maxPayEthAmount) - ) - .send(ctx.sendParam(trader, maxPayEthAmount)); - logGas(txReceipt, "buy token with ETH directly"); + ), ctx.sendParam(trader, maxPayEthAmount), "buy token with ETH directly"); const ethInPoolAfter = "12056338203652739553"; assert.strictEqual( await ctx.DODO.methods._QUOTE_BALANCE_().call(), @@ -134,14 +132,14 @@ describe("DODO ETH PROXY", () => { }); it("sell", async () => { const minReceiveEthAmount = "0.45"; - logGas( - await DODOEthProxy.methods + await logGas( + DODOEthProxy.methods .sellTokenToEth( ctx.BASE.options.address, decimalStr("50"), decimalStr(minReceiveEthAmount) - ) - .send(ctx.sendParam(trader)), + ), + ctx.sendParam(trader), "sell token to ETH directly" ); assert.strictEqual( diff --git a/test/utils/Log.ts b/test/utils/Log.ts index c2bc65d..7e7c3b7 100644 --- a/test/utils/Log.ts +++ b/test/utils/Log.ts @@ -5,15 +5,15 @@ */ -import { TransactionReceipt } from "web3-core" - export const blueText = x => `\x1b[36m${x}\x1b[0m`; export const yellowText = x => `\x1b[33m${x}\x1b[0m`; export const greenText = x => `\x1b[32m${x}\x1b[0m`; export const redText = x => `\x1b[31m${x}\x1b[0m`; export const numberWithCommas = x => x.toString().replace(/\B(?=(\d{3})+(?!\d))/g, ','); -export function logGas(receipt: TransactionReceipt, desc: string) { +export async function logGas(funcCall: any, params: any, desc: string) { + const estimatedGas = await funcCall.estimateGas(params) + const receipt = await funcCall.send(params) const gasUsed = receipt.gasUsed; let colorFn; @@ -25,5 +25,6 @@ export function logGas(receipt: TransactionReceipt, desc: string) { colorFn = redText; } - console.log(("Gas used:").padEnd(60, '.'), blueText(desc) + " ", colorFn(numberWithCommas(gasUsed).padStart(5))); + console.log(("Gas estimated:" + numberWithCommas(estimatedGas)).padEnd(60, '.'), blueText(desc) + " ", colorFn(numberWithCommas(gasUsed).padStart(5))); + return receipt } \ No newline at end of file