-
-
Save irzhywau/b1257353a2dabbf3504043d2cb52038f to your computer and use it in GitHub Desktop.
// SPDX-License-Identifier: MIT | |
pragma solidity >=0.6.12; | |
import "@openzeppelin/contracts/token/ERC20/IERC20.sol"; | |
import "@openzeppelin/contracts/access/Ownable.sol"; | |
import "./IERC20Wrappable.sol"; | |
contract ERC20WrappableSupport { | |
modifier withWrap(address payable wToken) { | |
require(_bundleWrap(wToken), "failed to wrap"); | |
_; | |
} | |
modifier withUnwrap(address payable wToken, uint256 amount) { | |
_; | |
_unwrap(wToken, amount); | |
} | |
modifier _underlyingTokenRequired(address payable wToken) { | |
require(wToken != address(0), "underlying token not set"); | |
_; | |
} | |
/// @dev execute wrap transaction along the process and transfer wETH into original msg.sender account | |
function _bundleWrap(address payable wToken) | |
internal | |
_underlyingTokenRequired(wToken) | |
returns (bool) | |
{ | |
// Note: | |
// There exists a special variant of a message `call`, | |
// named `delegatecall` which is identical to a message call | |
// apart from the fact that the code at the target address | |
// is executed in the context of the calling contract | |
// and msg.sender and msg.value do not change their values. | |
(bool deposited, ) = wToken.call{value: msg.value}( | |
abi.encodeWithSignature("deposit()") | |
); | |
require(deposited, "failed to deposit"); | |
IERC20(wToken).transferFrom(address(this), msg.sender, msg.value); | |
// @todo: figure out how to bundle this in this method | |
// (bool approved, ) = wToken.delegatecall( | |
// abi.encodeWithSignature("approve(address,uint256)", this, amount) | |
// ); | |
// require(approved, "failed to approve"); | |
return true; | |
} | |
function _unwrap(address payable wToken, uint256 wad) | |
internal | |
_underlyingTokenRequired(wToken) | |
returns (bool) | |
{ | |
// this statement needs an approval in prior | |
// transfer fund from user account to the current contract | |
require( | |
IERC20(wToken).transferFrom(msg.sender, address(this), wad), | |
"wERC20: failed to transfer to calling contract" | |
); | |
// here we will withdraw transfered wETH in the contract to ETH | |
// then transfer it again to the recipient (msg.sender) | |
// execution operated by this contract | |
IERC20Wrappable(wToken).withdraw(wad); | |
(bool withdrawn, ) = msg.sender.call{value: wad}(""); | |
return withdrawn; | |
} | |
} |
// SPDX-License-Identifier: MIT | |
pragma solidity >=0.6.12; | |
import "@openzeppelin/contracts/token/ERC20/IERC20.sol"; | |
interface IERC20Wrappable is IERC20 { | |
function deposit() external payable; | |
function withdraw(uint256 wad) external; | |
} |
// SPDX-License-Identifier: MIT | |
pragma solidity >=0.6.12; | |
import "@openzeppelin/contracts/access/Ownable.sol"; | |
import "../library/ERC20WrappableSupport.sol"; | |
contract TestWrap is Ownable, ERC20WrappableSupport { | |
address payable public wToken; | |
function pay() external payable { | |
require(_bundleWrap(wToken), "failed to bundle actions"); | |
} | |
function refundMe(uint256 amount) external { | |
require(_unwrap(wToken, amount), "failed to withdraw"); | |
} | |
function setWToken(address _wToken) public onlyOwner { | |
wToken = payable(_wToken); | |
} | |
} |
here is the scenario I'm trying to do, I use truffle console for it (to be able to make better debug when error occurs)
the WET9
contract code is taken from ropsten 0x0a180a76e4466bf68a7f86fb029bed3cccfaaac5
weth = await WETH9.new();
w = await TestWrap.new();
await w.setWToken(weth.address);
await w.pay({value: 20});
await weth.approve(w.address, 20);
await weth.withdraw(10); // OK
await w.refundMe(10); // revert without any explicit reason
function _unwrap(address payable wToken, uint256 wad)
internal
_underlyingTokenRequired(wToken)
returns (bool)
{
// this statement needs an approval in prior
// transfer fund from user account to the current contract
require(
IERC20(wToken).transferFrom(msg.sender, address(this), wad),
"wERC20: failed to transfer to calling contract"
);
// here we will withdraw transfered wETH in the contract to ETH
// then transfer it again to the recipient (msg.sender)
// execution operated by this contract
IERC20Wrappable(wToken).withdraw(wad);
(bool withdrawn, ) = msg.sender.call{value: wad}("");
return withdrawn;
}
Erros is in these line
require(
IERC20(wToken).transferFrom(msg.sender, address(this), wad),
"wERC20: failed to transfer to calling contract"
);
You are running transferFrom function without approve.
You need to check the allowance amount before the transferFrom function and add the approve function in test before refund function.
require(IERC20(wToken).allowance(msg.sender, address(this)) >= wad, "insufficient allowance");
I am using truffle and I will post test code.
const { expect, use, util } = require("chai");
const { ethers } = require("hardhat");
const { solidity } = require("ethereum-waffle");
use(solidity);
describe("Eth9 Test", function () {
let erc20Wrappable, addr1, addr2, addr3, addr4;
beforeEach(async () => {
[addr1, addr2, addr3, addr4] = await ethers.getSigners();
const ERC20Wrappable = await ethers.getContractFactory("ERC20WrappableSupport");
erc20Wrappable = await ERC20Wrappable.deploy();
await erc20Wrappable.deployed();
const TestWrap = await ethers.getContractFactory("TestWrap");
testWrap = await TestWrap.deploy();
await testWrap.deployed();
const Weth9 = await ethers.getContractFactory("WETH9");
weth9 = await Weth9.deploy();
await weth9.deployed();
});
it("Initialize", async function () {
expect(erc20Wrappable).to.be.ok;
expect(testWrap).to.be.ok;
expect(weth9).to.be.ok;
});
it("Token address", async function () {
await testWrap.setWToken(weth9.address);
expect(await testWrap.wToken()).to.equal(weth9.address);
await testWrap.connect(addr1).pay({value: ethers.utils.parseEther("20")});
//approve first
await weth9.connect(addr1).approve(testWrap.address,ethers.utils.parseEther("20"));
// next refondMe
await testWrap.refondMe(10);
});
});
Thank you so much for your help,
finally the issue was rather about the contract that implement ERC20WrappableSupport
it needed a fallback
and receive
functions
by adding both, everything seemed ok
but still, I'm not really sure to understand what will be the effect of that
I just added below in TestWrap
function _fallback() internal {}
fallback() external payable {
_fallback();
}
receive() external payable {
_fallback();
}
receive() external payable {
_fallback();
}
This function means that the contract can recieve ether from ourside contract or account.
If there is no such function, we can't recieve ether from outside.
got it.
thank you @alexlu0917 your help just unlocked me from a week of struggle
Ive checked that part and seems ok, the error seems occurring on msg.sender.transfer instead then I dont know why it fails at that stage