Skip to content

Commit

Permalink
FOTToken: canonical way of implementation, following SETH example
Browse files Browse the repository at this point in the history
  • Loading branch information
d10r committed Sep 20, 2023
1 parent c6f3e1e commit e9b8402
Show file tree
Hide file tree
Showing 2 changed files with 53 additions and 30 deletions.
62 changes: 45 additions & 17 deletions contracts/experimental/FOTToken.sol
Original file line number Diff line number Diff line change
Expand Up @@ -10,19 +10,30 @@ import {
} from "@superfluid-finance/ethereum-contracts/contracts/interfaces/superfluid/ISuperfluid.sol";
import { Ownable } from "@openzeppelin/contracts/access/Ownable.sol";

// Custom methods of this token, to be inherited from by both the proxy and interface.
interface IFOTTokenCustom {
// admin interface
function setFeeConfig(uint256 _feePerTx, address _feeRecipient) external /*onlyOwner*/;
}

// Methods included in ISuperToken, but intercepted by the proxy in order to change its behaviour.
// This dedicated interface can be inherited only by the proxy.
interface IFOTTokenIntercepted {
// subset of IERC20 intercepted in the proxy in order to add a fee
function transferFrom(address holder, address recipient, uint256 amount) external returns (bool);
function transfer(address recipient, uint256 amount) external returns (bool);
}

/// @title FOT (Fee on Transfer) Token
/// trivial implementation using a fixed fee amount per tx, added to the actual transfer amount.
/// @notice CustomSuperTokenBase MUST be the first inherited contract, otherwise the storage layout may get corrupted
contract FOTTokenProxy is CustomSuperTokenBase, UUPSProxy, Ownable {
uint256 public feePerTx;
address public feeRecipient;
contract FOTTokenProxy is CustomSuperTokenBase, UUPSProxy, Ownable, IFOTTokenCustom, IFOTTokenIntercepted {
uint256 public feePerTx; // amount detracted as fee per tx
address public feeRecipient; // receiver of the fee

constructor(uint256 _feePerTx, address _feeRecipient) {
feePerTx = _feePerTx;
feeRecipient = _feeRecipient;
}
event FeeConfigSet(uint256 feePerTx, address feeRecipient);

function setFeeConfig(uint256 _feePerTx, address _feeRecipient) external onlyOwner {
constructor(uint256 _feePerTx, address _feeRecipient) {
feePerTx = _feePerTx;
feeRecipient = _feeRecipient;
}
Expand All @@ -36,25 +47,42 @@ contract FOTTokenProxy is CustomSuperTokenBase, UUPSProxy, Ownable {
ISuperToken(address(this)).initialize(IERC20(address(0)), 18, name, symbol);
}

// ERC20 functions we want to intercept and add custom behavior to
// ======= IFOTTokenCustom =======

function setFeeConfig(uint256 _feePerTx, address _feeRecipient) external override onlyOwner {
feePerTx = _feePerTx;
feeRecipient = _feeRecipient;
emit FeeConfigSet(_feePerTx, _feeRecipient);
}

// ======= IFOTTokenIntercepted =======

function transferFrom(address holder, address recipient, uint256 amount)
public returns (bool)
external override returns (bool)
{
_transferFrom(holder, recipient, amount);
return true; // returns true if it didn't revert
}

function transfer(address recipient, uint256 amount)
external override returns (bool)
{
_transferFrom(msg.sender, recipient, amount);
return true;
}


// ======= Internal =======

function _transferFrom(address holder, address recipient, uint256 amount) internal {
// get the fee
ISuperToken(address(this)).selfTransferFrom(holder, msg.sender, feeRecipient, feePerTx);
// (external) call into the implementation contract.

// do the actual tx
ISuperToken(address(this)).selfTransferFrom(holder, msg.sender, recipient, amount);
return true; // returns true if it didn't revert
}

function transfer(address recipient, uint256 amount)
public returns (bool)
{
return transferFrom(msg.sender, recipient, amount);
}
}

// TODO: create interfaces IFOTTokenCustom, IFOTToken - see https://github.com/superfluid-finance/protocol-monorepo/blob/dev/packages/ethereum-contracts/contracts/interfaces/tokens/ISETH.sol
interface IFOTToken is ISuperToken, IFOTTokenCustom {}
21 changes: 8 additions & 13 deletions test/foundry/FOTToken.t.sol
Original file line number Diff line number Diff line change
Expand Up @@ -2,18 +2,13 @@
pragma solidity 0.8.19;

import "forge-std/Test.sol";
import {
FOTTokenProxy,
ISuperToken
} from "../../contracts/experimental/FOTToken.sol";
import {ISuperfluid} from "@superfluid-finance/ethereum-contracts/contracts/interfaces/superfluid/ISuperfluid.sol";
import {
SuperfluidFrameworkDeployer
} from "@superfluid-finance/ethereum-contracts/contracts/utils/SuperfluidFrameworkDeployer.sol";
import {ERC1820RegistryCompiled} from "@superfluid-finance/ethereum-contracts/contracts/libs/ERC1820RegistryCompiled.sol";
import { FOTTokenProxy, IFOTToken } from "../../contracts/experimental/FOTToken.sol";
import { ISuperfluid } from "@superfluid-finance/ethereum-contracts/contracts/interfaces/superfluid/ISuperfluid.sol";
import { SuperfluidFrameworkDeployer } from "@superfluid-finance/ethereum-contracts/contracts/utils/SuperfluidFrameworkDeployer.sol";
import { ERC1820RegistryCompiled } from "@superfluid-finance/ethereum-contracts/contracts/libs/ERC1820RegistryCompiled.sol";

contract FOTTokenTest is Test {
ISuperToken public fotToken;
IFOTToken public fotToken;
SuperfluidFrameworkDeployer.Framework sf;
uint256 public txFee = 1e16;

Expand All @@ -34,7 +29,7 @@ contract FOTTokenTest is Test {
vm.startPrank(admin);
FOTTokenProxy fotTokenProxy = new FOTTokenProxy(txFee, txFeeRecipient);
fotTokenProxy.initialize(sf.superTokenFactory, "FOTToken", "FOT");
fotToken = ISuperToken(address(fotTokenProxy));
fotToken = IFOTToken(address(fotTokenProxy));
}

function testTransfer() public {
Expand Down Expand Up @@ -84,10 +79,10 @@ contract FOTTokenTest is Test {
vm.startPrank(dan);
vm.expectRevert("Ownable: caller is not the owner"); // dan isn't the owner
// casting to payable needed because the proxy contains a payable fallback function
FOTTokenProxy(payable(address(fotToken))).setFeeConfig(newTxFee, dan);
fotToken.setFeeConfig(newTxFee, dan);

vm.startPrank(admin);
FOTTokenProxy(payable(address(fotToken))).setFeeConfig(newTxFee, dan);
fotToken.setFeeConfig(newTxFee, dan);

deal(address(fotToken), alice, 100 ether);

Expand Down

0 comments on commit e9b8402

Please sign in to comment.