diff --git a/src/interfaces/IBurnableERC20.sol b/src/interfaces/IBurnableERC20.sol index e3e4241..39d8fda 100644 --- a/src/interfaces/IBurnableERC20.sol +++ b/src/interfaces/IBurnableERC20.sol @@ -4,4 +4,5 @@ pragma solidity 0.8.15; interface IBurnableERC20 { function burn(uint256 amount) external; + function burnFrom(address user, uint256 amount) external; } diff --git a/src/token/ERC20MintBurn.sol b/src/token/ERC20MintBurn.sol index c7e0f43..9731cc7 100644 --- a/src/token/ERC20MintBurn.sol +++ b/src/token/ERC20MintBurn.sol @@ -3,7 +3,6 @@ pragma solidity 0.8.15; import "../utils/Ownable.sol"; -import "../interfaces/IMintableERC20.sol"; import "./BaseERC20.sol"; import "../interfaces/IMintableERC20.sol"; import "../interfaces/IBurnableERC20.sol"; @@ -58,4 +57,18 @@ abstract contract ERC20MintBurn is IMintableERC20, IBurnableERC20, Ownable, Base _burn(msg.sender, _value); } + + /** + * @dev Burns pre-approved tokens from the other address. + * Callable only by one of the burner addresses. + * @param _from account to burn tokens from. + * @param _value amount of tokens to burn. Should be less than or equal to account balance. + */ + function burnFrom(address _from, uint256 _value) external virtual { + require(isBurner(msg.sender), "ERC20MintBurn: not a burner"); + + _spendAllowance(_from, msg.sender, _value); + + _burn(_from, _value); + } } diff --git a/test/BobToken.t.sol b/test/BobToken.t.sol index 77e272a..ebd3a9f 100644 --- a/test/BobToken.t.sol +++ b/test/BobToken.t.sol @@ -94,6 +94,30 @@ contract BobTokenTest is Test, EIP2470Test { assertEq(bob.balanceOf(user2), 0 ether); } + function testBurnFrom() public { + vm.prank(user1); + bob.mint(user1, 1 ether); + + vm.expectRevert("ERC20MintBurn: not a burner"); + bob.burnFrom(user1, 1 ether); + + vm.prank(user2); + vm.expectRevert("ERC20: insufficient allowance"); + bob.burnFrom(user1, 1 ether); + + vm.prank(user1); + bob.approve(user2, 10 ether); + + vm.prank(user2); + vm.expectEmit(true, true, false, true); + emit Transfer(user1, address(0), 1 ether); + bob.burnFrom(user1, 1 ether); + + assertEq(bob.totalSupply(), 0); + assertEq(bob.balanceOf(user1), 0); + assertEq(bob.allowance(user1, user2), 9 ether); + } + function testMinterChange() public { vm.expectRevert("Ownable: caller is not the owner"); bob.updateMinter(user3, true, true);