-
Notifications
You must be signed in to change notification settings - Fork 3
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
- Loading branch information
Showing
4 changed files
with
208 additions
and
96 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,124 @@ | ||
// SPDX-License-Identifier: GPL-3.0 | ||
pragma solidity >=0.8.0 <0.9.0; | ||
|
||
import { ERC20 } from "@openzeppelin/contracts/token/ERC20/ERC20.sol"; | ||
import { ERC20Permit } from "@openzeppelin/contracts/token/ERC20/extensions/ERC20Permit.sol"; | ||
import { ERC20Votes } from "@openzeppelin/contracts/token/ERC20/extensions/ERC20Votes.sol"; | ||
import { Pausable } from "@openzeppelin/contracts/utils/Pausable.sol"; | ||
import { Ownable } from "@openzeppelin/contracts/access/Ownable.sol"; | ||
import { Time } from "@openzeppelin/contracts/utils/types/Time.sol"; | ||
import { Nonces } from "@openzeppelin/contracts/utils/Nonces.sol"; | ||
import { Votes } from "@openzeppelin/contracts/governance/utils/Votes.sol"; | ||
|
||
/** | ||
* @title PUFFER Token | ||
* @author Puffer Finance | ||
* @custom:security-contact security@puffer.fi | ||
*/ | ||
contract PUFFER is ERC20Votes, ERC20Permit, Pausable, Ownable { | ||
/** | ||
* @notice Thrown when a transfer is attempted while the token is paused | ||
*/ | ||
error TransferPaused(); | ||
|
||
/** | ||
* @notice Event emitted when the allowedFrom status of an address is set | ||
* @param from The address that is allowed to transfer tokens | ||
* @param isAllowedFrom Whether the address is allowed to transfer tokens | ||
*/ | ||
event SetAllowedFrom(address indexed from, bool isAllowedFrom); | ||
|
||
/** | ||
* @notice Event emitted when the allowedTo status of an address is set | ||
* @param to The address that is allowed to receive tokens | ||
* @param isAllowedTo Whether the address is allowed to receive tokens | ||
*/ | ||
event SetAllowedTo(address indexed to, bool isAllowedTo); | ||
|
||
/** | ||
* @notice Mapping of addresses that are allowed to transfer tokens | ||
* @dev This is used to allow certain addresses to transfer tokens without pausing the token | ||
*/ | ||
mapping(address sender => bool allowed) public isAllowedFrom; | ||
|
||
/** | ||
* @notice Mapping of addresses that are allowed to receive tokens | ||
* @dev This is used to allow certain addresses to receive tokens without pausing the token | ||
*/ | ||
mapping(address receiver => bool allowed) public isAllowedTo; | ||
|
||
/** | ||
* @notice Constructor for the PUFI token | ||
* totalSupply is 1 billion PUFI | ||
*/ | ||
constructor(address initialOwner) ERC20("PUFFER", "PUFFER") ERC20Permit("PUFFER") Ownable(initialOwner) { | ||
_mint(initialOwner, 1_000_000_000 ether); | ||
_setAllowedFrom(initialOwner, true); | ||
_pause(); | ||
} | ||
|
||
/** | ||
* @inheritdoc Votes | ||
*/ | ||
function clock() public view virtual override returns (uint48) { | ||
return Time.timestamp(); | ||
} | ||
|
||
/** | ||
* @inheritdoc Votes | ||
*/ | ||
function CLOCK_MODE() public view virtual override returns (string memory) { | ||
return "mode=timestamp"; | ||
} | ||
|
||
/** | ||
* @inheritdoc ERC20Permit | ||
*/ | ||
function nonces(address owner) public view virtual override(ERC20Permit, Nonces) returns (uint256) { | ||
return super.nonces(owner); | ||
} | ||
|
||
/** | ||
* @notice Unpauses the token | ||
* @dev Only the owner can unpause the token | ||
*/ | ||
function unpause() external virtual onlyOwner { | ||
_unpause(); | ||
} | ||
|
||
/** | ||
* @notice Sets the allowedTo status of an address | ||
* @param receiver The address to set the allowedTo status of | ||
* @param allowed Whether the address is allowed to receive tokens | ||
*/ | ||
function setAllowedTo(address receiver, bool allowed) external onlyOwner { | ||
isAllowedTo[receiver] = allowed; | ||
emit SetAllowedTo(receiver, allowed); | ||
} | ||
|
||
/** | ||
* @notice Sets the allowedFrom status of an address | ||
* @param transferrer The address to set the allowedFrom status of | ||
* @param allowed Whether the address is allowed to transfer tokens | ||
*/ | ||
function setAllowedFrom(address transferrer, bool allowed) external onlyOwner { | ||
_setAllowedFrom(transferrer, allowed); | ||
} | ||
|
||
function _setAllowedFrom(address transferrer, bool allowed) internal { | ||
isAllowedFrom[transferrer] = allowed; | ||
emit SetAllowedFrom(transferrer, allowed); | ||
} | ||
|
||
/** | ||
* @notice Overrides the _update function to prevent token transfers | ||
* @dev We override the _update function to act like `_beforeTokenTransfer` hook | ||
*/ | ||
function _update(address from, address to, uint256 value) internal override(ERC20, ERC20Votes) { | ||
if (paused()) { | ||
require(isAllowedFrom[from] || isAllowedTo[to], TransferPaused()); | ||
} | ||
|
||
super._update(from, to, value); | ||
} | ||
} |
This file was deleted.
Oops, something went wrong.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,84 @@ | ||
// SPDX-License-Identifier: GPL-3.0 | ||
pragma solidity >=0.8.0 <0.9.0; | ||
|
||
import { PUFFER } from "../../src/PUFFER.sol"; | ||
import { UnitTestHelper } from "../helpers/UnitTestHelper.sol"; | ||
|
||
contract MockLocker { | ||
// do nothing | ||
} | ||
|
||
contract PUFFERTest is UnitTestHelper { | ||
address owner = makeAddr("multisig"); | ||
PUFFER public puffer; | ||
MockLocker public locker; | ||
|
||
function setUp() public override { | ||
puffer = new PUFFER(owner); | ||
locker = new MockLocker(); | ||
} | ||
|
||
function test_constructor() public { | ||
puffer = new PUFFER(owner); | ||
assertEq(puffer.owner(), owner); | ||
assertEq(puffer.totalSupply(), 1_000_000_000 ether); | ||
assertEq(puffer.name(), "PUFFER"); | ||
assertEq(puffer.symbol(), "PUFFER"); | ||
assertEq(puffer.paused(), true, "PUFFER should be paused"); | ||
assertEq(puffer.CLOCK_MODE(), "mode=timestamp", "Clock mode must be timestamp"); | ||
assertEq(puffer.nonces(owner), 0, "Nonce should be 0"); | ||
} | ||
|
||
function test_allowedSenderCanTransferToAnybody(address recipient) public { | ||
vm.assume(recipient != address(0)); | ||
assertEq(puffer.paused(), true, "PUFFER should be paused"); | ||
|
||
vm.startPrank(owner); | ||
puffer.transfer(alice, 100 ether); | ||
puffer.setAllowedFrom(alice, true); | ||
|
||
vm.startPrank(alice); | ||
|
||
puffer.transfer(recipient, 100 ether); | ||
|
||
assertEq(puffer.balanceOf(recipient), 100 ether); | ||
} | ||
|
||
function test_allowedRecipientCanReceiveTokensFromAnybody() public { | ||
assertEq(puffer.paused(), true, "PUFFER should be paused"); | ||
|
||
vm.startPrank(owner); | ||
puffer.transfer(alice, 100 ether); | ||
puffer.setAllowedTo(address(locker), true); | ||
|
||
vm.startPrank(alice); | ||
puffer.transfer(address(locker), 100 ether); | ||
assertEq(puffer.balanceOf(address(locker)), 100 ether); | ||
} | ||
|
||
function test_onlyOwnerCanTransfer() public { | ||
uint256 amount = 100 ether; | ||
assertEq(puffer.balanceOf(alice), 0, "Alice should have 0 PUFFER"); | ||
vm.prank(owner); | ||
puffer.transfer(alice, amount); | ||
assertEq(puffer.balanceOf(alice), amount, "Alice should have 100 PUFFER"); | ||
|
||
vm.startPrank(alice); | ||
vm.expectRevert(PUFFER.TransferPaused.selector); | ||
puffer.transfer(bob, amount); | ||
} | ||
|
||
// Token transfer should work when the token is unpaused | ||
function test_unpausedTokenTransfer(uint80 aliceAmount) public { | ||
vm.assume(aliceAmount > 0); | ||
|
||
vm.startPrank(owner); | ||
puffer.transfer(alice, aliceAmount); | ||
|
||
puffer.unpause(); | ||
|
||
vm.startPrank(alice); | ||
puffer.transfer(bob, aliceAmount); | ||
assertEq(puffer.balanceOf(bob), aliceAmount, "Bob should have 100 PUFFER"); | ||
} | ||
} |
This file was deleted.
Oops, something went wrong.