Skip to content

Commit

Permalink
PUFI -> PUFFER
Browse files Browse the repository at this point in the history
  • Loading branch information
bxmmm1 committed Sep 25, 2024
1 parent 4570bb7 commit c706f32
Show file tree
Hide file tree
Showing 4 changed files with 208 additions and 96 deletions.
124 changes: 124 additions & 0 deletions mainnet-contracts/src/PUFFER.sol
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);
}
}
47 changes: 0 additions & 47 deletions mainnet-contracts/src/PUFI.sol

This file was deleted.

84 changes: 84 additions & 0 deletions mainnet-contracts/test/unit/PUFER.t.sol
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");
}
}
49 changes: 0 additions & 49 deletions mainnet-contracts/test/unit/PUFI.t.sol

This file was deleted.

0 comments on commit c706f32

Please sign in to comment.