Skip to content

Commit

Permalink
Merge pull request 1inch#73 from 1inch/feature/safestErc20-weth-tests
Browse files Browse the repository at this point in the history
[SC-752] Tests for IWETH methods in SafeERC20 library
  • Loading branch information
ZumZoom authored Mar 2, 2023
2 parents d89fa6b + 90d204e commit 24a03de
Show file tree
Hide file tree
Showing 6 changed files with 175 additions and 3 deletions.
26 changes: 26 additions & 0 deletions contracts/tests/mocks/SafeERC20Helper.sol
Original file line number Diff line number Diff line change
Expand Up @@ -222,3 +222,29 @@ contract SafeERC20Wrapper {
return _token.allowance(address(0), address(0));
}
}

contract SafeWETHWrapper {
using SafeERC20 for IWETH;

IWETH private _token;

constructor(IWETH token) {
_token = token;
}

// solhint-disable-next-line no-empty-blocks
receive() external payable {
}

function deposit() external payable {
_token.safeDeposit(msg.value);
}

function withdraw(uint256 amount) external {
_token.safeWithdraw(amount);
}

function withdrawTo(uint256 amount, address to) external {
_token.safeWithdrawTo(amount, to);
}
}
64 changes: 64 additions & 0 deletions contracts/tests/mocks/WETH.sol
Original file line number Diff line number Diff line change
@@ -0,0 +1,64 @@
// SPDX-License-Identifier: MIT

pragma solidity ^0.8.15;

contract WETH {
string public name = "Wrapped Ether";
string public symbol = "WETH";
uint8 public decimals = 18;

event Approval(address indexed src, address indexed guy, uint wad);
event Transfer(address indexed src, address indexed dst, uint wad);
event Deposit(address indexed dst, uint wad);
event Withdrawal(address indexed src, uint wad);

mapping (address => uint) public balanceOf;
mapping (address => mapping (address => uint)) public allowance;

receive() external payable {
deposit();
}
function deposit() public payable {
balanceOf[msg.sender] += msg.value;
emit Deposit(msg.sender, msg.value);
}
function withdraw(uint wad) public {
require(balanceOf[msg.sender] >= wad, "Not enough balance");
balanceOf[msg.sender] -= wad;
payable(msg.sender).transfer(wad);
emit Withdrawal(msg.sender, wad);
}

function totalSupply() public view returns (uint) {
return address(this).balance;
}

function approve(address guy, uint wad) public returns (bool) {
allowance[msg.sender][guy] = wad;
emit Approval(msg.sender, guy, wad);
return true;
}

function transfer(address dst, uint wad) public returns (bool) {
return transferFrom(msg.sender, dst, wad);
}

function transferFrom(address src, address dst, uint wad)
public
returns (bool)
{
require(balanceOf[src] >= wad, "Not enough balance");

if (src != msg.sender && allowance[src][msg.sender] != type(uint).max) {
require(allowance[src][msg.sender] >= wad, "Not enough allowance");
allowance[src][msg.sender] -= wad;
}

balanceOf[src] -= wad;
balanceOf[dst] += wad;

emit Transfer(src, dst, wad);

return true;
}
}
1 change: 1 addition & 0 deletions hardhat.config.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@ import '@typechain/hardhat';
import '@nomiclabs/hardhat-ethers';
import '@nomicfoundation/hardhat-chai-matchers';
import 'hardhat-gas-reporter';
import 'hardhat-tracer';
require('solidity-coverage'); // require because no TS typings available
import dotenv from 'dotenv';
import { HardhatUserConfig } from 'hardhat/config';
Expand Down
3 changes: 2 additions & 1 deletion package.json
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
{
"name": "@1inch/solidity-utils",
"version": "2.2.19",
"version": "2.2.20",
"main": "dist/src/index.js",
"types": "dist/src/index.d.ts",
"repository": {
Expand Down Expand Up @@ -62,6 +62,7 @@
"eslint-plugin-standard": "5.0.0",
"ethereumjs-wallet": "1.0.2",
"hardhat-gas-reporter": "1.0.9",
"hardhat-tracer": "2.1.2",
"prettier": "2.7.1",
"prettier-plugin-solidity": "1.0.0-beta.24",
"rimraf": "3.0.2",
Expand Down
75 changes: 74 additions & 1 deletion test/contracts/SafestERC20.test.ts
Original file line number Diff line number Diff line change
@@ -1,10 +1,11 @@
import { constants, expect } from '../../src/prelude';
import { constants, ether, expect } from '../../src/prelude';
import { SignerWithAddress } from '@nomiclabs/hardhat-ethers/signers';
import { loadFixture } from '@nomicfoundation/hardhat-network-helpers';
import { ethers } from 'hardhat';
import { Contract, ContractFactory } from 'ethers';
import { PERMIT2_ADDRESS } from '@uniswap/permit2-sdk';
import { splitSignature } from 'ethers/lib/utils';
import { countInstructions, trackReceivedTokenAndTx } from '../../src/utils';

const Permit = [
{ name: 'owner', type: 'address' },
Expand All @@ -18,10 +19,12 @@ describe('SafeERC20', function () {
let owner: SignerWithAddress;
let spender: SignerWithAddress;
let SafeERC20Wrapper: ContractFactory;
let SafeWETHWrapper: ContractFactory;

before(async function () {
[owner, spender] = await ethers.getSigners();
SafeERC20Wrapper = await ethers.getContractFactory('SafeERC20Wrapper');
SafeWETHWrapper = await ethers.getContractFactory('SafeWETHWrapper');
});

async function deployWrapperSimple() {
Expand Down Expand Up @@ -100,6 +103,22 @@ describe('SafeERC20', function () {
return { token, wrapper, data, signature };
}

async function deployWrapperWETH() {
const WETH = await ethers.getContractFactory('WETH');
const weth = await WETH.deploy();
await weth.deployed();

const wrapper = await SafeWETHWrapper.deploy(weth.address);
await wrapper.deployed();
return { weth, wrapper };
}

async function deployWrapperWETHAndDeposit() {
const { weth, wrapper } = await deployWrapperWETH();
await wrapper.deposit({ value: ether('1') });
return { weth, wrapper };
}

describe('with address that has no contract code', function () {
shouldRevertOnAllCalls(
['SafeTransferFailed', 'SafeTransferFromFailed', 'ForceApproveFailed', ''],
Expand Down Expand Up @@ -233,6 +252,60 @@ describe('SafeERC20', function () {
});
});

describe('IWETH methods', function () {
it('should deposit tokens', async function () {
const { weth, wrapper } = await loadFixture(deployWrapperWETH);
const [received, tx] = await trackReceivedTokenAndTx(ethers.provider, weth, wrapper.address, () =>
wrapper.deposit({ value: ether('1') }),
);
expect(received).to.be.equal(ether('1'));
expect(await countInstructions(ethers.provider, tx.events[0].transactionHash, ['STATICCALL', 'CALL', 'MSTORE', 'MLOAD', 'SSTORE', 'SLOAD'])).to.be.deep.equal([
0, 1, 6, 2, 1, 2,
]);
});

it('should be cheap on deposit 0 tokens', async function () {
const { weth, wrapper } = await loadFixture(deployWrapperWETH);
const [, tx] = await trackReceivedTokenAndTx(ethers.provider, weth, wrapper.address, () =>
wrapper.deposit(),
);
expect(await countInstructions(ethers.provider, tx.transactionHash, ['STATICCALL', 'CALL', 'MSTORE', 'MLOAD', 'SSTORE', 'SLOAD'])).to.be.deep.equal([
0, 0, 1, 0, 0, 1,
]);
});

it('should withdrawal tokens on withdraw', async function () {
const { weth, wrapper } = await loadFixture(deployWrapperWETHAndDeposit);
const [received] = await trackReceivedTokenAndTx(ethers.provider, weth, wrapper.address, () =>
wrapper.withdraw(ether('0.5')),
);
expect(received).to.be.equal(-ether('0.5'));
});

it('should withdrawal tokens on withdrawTo', async function () {
const { weth, wrapper } = await loadFixture(deployWrapperWETHAndDeposit);
const spenderBalanceBefore = await ethers.provider.getBalance(spender.address);
const [received, tx] = await trackReceivedTokenAndTx(ethers.provider, weth, wrapper.address, () =>
wrapper.withdrawTo(ether('0.5'), spender.address),
);
expect(received).to.be.equal(-ether('0.5'));
expect(await ethers.provider.getBalance(spender.address)).to.be.equal(spenderBalanceBefore.toBigInt() + ether('0.5'));
expect(await countInstructions(ethers.provider, tx.transactionHash, ['STATICCALL', 'CALL'])).to.be.deep.equal([
0, 3,
]);
});

it('should be cheap on withdrawTo to self', async function () {
const { weth, wrapper } = await loadFixture(deployWrapperWETHAndDeposit);
const [, tx] = await trackReceivedTokenAndTx(ethers.provider, weth, wrapper.address, () =>
wrapper.withdrawTo(ether('0.5'), wrapper.address),
);
expect(await countInstructions(ethers.provider, tx.transactionHash, ['STATICCALL', 'CALL'])).to.be.deep.equal([
0, 2,
]);
});
});

function shouldRevertOnAllCalls(reasons: string[], fixture: () => Promise<{ wrapper: Contract }>) {
it('reverts on transfer', async function () {
const { wrapper } = await loadFixture(fixture);
Expand Down
9 changes: 8 additions & 1 deletion yarn.lock
Original file line number Diff line number Diff line change
Expand Up @@ -2743,7 +2743,7 @@ ethers@^4.0.40:
uuid "2.0.1"
xmlhttprequest "1.8.0"

ethers@^5.3.1:
ethers@^5.3.1, ethers@^5.6.1:
version "5.7.2"
resolved "https://registry.yarnpkg.com/ethers/-/ethers-5.7.2.tgz#3a7deeabbb8c030d4126b24f84e525466145872e"
integrity sha512-wswUsmWo1aOK8rR7DIKiWSw9DbLWe6x98Jrn8wcTflTVvaXhAMaB5zGAXy0GYQEQp9iO1iSHWVyARQm11zUtyg==
Expand Down Expand Up @@ -3337,6 +3337,13 @@ [email protected]:
eth-gas-reporter "^0.2.25"
sha1 "^1.1.1"

[email protected]:
version "2.1.2"
resolved "https://registry.yarnpkg.com/hardhat-tracer/-/hardhat-tracer-2.1.2.tgz#9a1a3896856a59461594e2ee4a54871eafb2ab63"
integrity sha512-Uee7EgMGZPmGA1NFu6at8zk3u184irfPQ+AK50a7s2Ep8jbsAc55mSFs844+UhSH/uNNuk7fqAsx/Qa5gHzPpQ==
dependencies:
ethers "^5.6.1"

[email protected]:
version "2.11.2"
resolved "https://registry.yarnpkg.com/hardhat/-/hardhat-2.11.2.tgz#c81388630255823bb1717ec07c4ee651b1fbe97f"
Expand Down

0 comments on commit 24a03de

Please sign in to comment.