Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

[ETHEREUM-CONTRACTS] add IERC20Metadata to SuperfluidPool #2046

Open
wants to merge 4 commits into
base: dev
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
5 changes: 5 additions & 0 deletions packages/ethereum-contracts/CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,11 @@ All notable changes to the ethereum-contracts will be documented in this file.

This project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).

## UNRELEASED

### Changed
- Fixed deployment of SimpleForwarder (solved an issue which caused batch operation `OPERATION_TYPE_SIMPLE_FORWARD_CALL` to always revert)

## [v1.12.0]

### Added
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -18,7 +18,8 @@ import { poolIndexDataToPDPoolIndex, SuperfluidPool } from "./SuperfluidPool.sol
import { SuperfluidPoolDeployerLibrary } from "./SuperfluidPoolDeployerLibrary.sol";
import {
IGeneralDistributionAgreementV1,
PoolConfig
PoolConfig,
PoolERC20Metadata
} from "../../interfaces/agreements/gdav1/IGeneralDistributionAgreementV1.sol";
import { SuperfluidUpgradeableBeacon } from "../../upgradability/SuperfluidUpgradeableBeacon.sol";
import { ISuperfluidToken } from "../../interfaces/superfluid/ISuperfluidToken.sol";
Expand Down Expand Up @@ -265,16 +266,22 @@ contract GeneralDistributionAgreementV1 is AgreementBase, TokenMonad, IGeneralDi
actualAmount = uint256(Value.unwrap(actualDistributionAmount));
}

function _createPool(ISuperfluidToken token, address admin, PoolConfig memory config)
internal
returns (ISuperfluidPool pool)
{
function _createPool(
ISuperfluidToken token,
address admin,
PoolConfig memory config,
PoolERC20Metadata memory poolERC20Metadata
) internal returns (ISuperfluidPool pool) {
// @note ensure if token and admin are the same that nothing funky happens with echidna
if (admin == address(0)) revert GDA_NO_ZERO_ADDRESS_ADMIN();
if (_isPool(token, admin)) revert GDA_ADMIN_CANNOT_BE_POOL();

pool = ISuperfluidPool(
address(SuperfluidPoolDeployerLibrary.deploy(address(superfluidPoolBeacon), admin, token, config))
address(
SuperfluidPoolDeployerLibrary.deploy(
address(superfluidPoolBeacon), admin, token, config, poolERC20Metadata
)
)
);

// @note We utilize the storage slot for Universal Index State
Expand All @@ -298,7 +305,22 @@ contract GeneralDistributionAgreementV1 is AgreementBase, TokenMonad, IGeneralDi
override
returns (ISuperfluidPool pool)
{
return _createPool(token, admin, config);
return _createPool(
token,
admin,
config,
PoolERC20Metadata("", "", 0) // use defaults specified by the implementation contract
);
}

/// @inheritdoc IGeneralDistributionAgreementV1
function createPoolWithCustomERC20Metadata(
ISuperfluidToken token,
address admin,
PoolConfig memory config,
PoolERC20Metadata memory poolERC20Metadata
) external override returns (ISuperfluidPool pool) {
return _createPool(token, admin, config, poolERC20Metadata);
}

/// @inheritdoc IGeneralDistributionAgreementV1
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@ pragma solidity ^0.8.23;

// Notes: We use these interfaces in natspec documentation below, grep @inheritdoc
// solhint-disable-next-line no-unused-import
import { IERC20 } from "@openzeppelin/contracts/token/ERC20/IERC20.sol";
import { IERC20, IERC20Metadata } from "@openzeppelin/contracts/token/ERC20/extensions/IERC20Metadata.sol";
import { SafeCast } from "@openzeppelin/contracts/utils/math/SafeCast.sol";
import {
BasicParticle,
Expand Down Expand Up @@ -81,9 +81,16 @@ contract SuperfluidPool is ISuperfluidPool, BeaconProxiable {
int256 claimedValue;
}

// Constants & Immutables

string internal constant _DEFAULT_ERC20_NAME = "Superfluid Pool";
string internal constant _DEFAULT_ERC20_SYMBOL = "POOL";
// ERC20 decimals implicitly defaults to 0

GeneralDistributionAgreementV1 public immutable GDA;

// State variables - NEVER REORDER!

ISuperfluidToken public superToken;
address public admin;
PoolIndexData internal _index;
Expand All @@ -101,6 +108,11 @@ contract SuperfluidPool is ISuperfluidPool, BeaconProxiable {
/// @inheritdoc ISuperfluidPool
bool public distributionFromAnyAddress;

// ERC20 metadata
string internal _erc20Name;
string internal _erc20Symbol;
uint8 internal _erc20Decimals;

constructor(GeneralDistributionAgreementV1 gda) {
GDA = gda;
}
Expand All @@ -109,12 +121,18 @@ contract SuperfluidPool is ISuperfluidPool, BeaconProxiable {
address admin_,
ISuperfluidToken superToken_,
bool transferabilityForUnitsOwner_,
bool distributionFromAnyAddress_
bool distributionFromAnyAddress_,
string memory erc20Name_,
string memory erc20Symbol_,
uint8 erc20Decimals_
) external initializer {
admin = admin_;
superToken = superToken_;
transferabilityForUnitsOwner = transferabilityForUnitsOwner_;
distributionFromAnyAddress = distributionFromAnyAddress_;
_erc20Name = erc20Name_;
_erc20Symbol = erc20Symbol_;
_erc20Decimals = erc20Decimals_;
}

function proxiableUUID() public pure override returns (bytes32) {
Expand Down Expand Up @@ -284,6 +302,21 @@ contract SuperfluidPool is ISuperfluidPool, BeaconProxiable {
else return (_index.wrappedFlowRate * uint256(units).toInt256()).toInt96();
}

/// @inheritdoc IERC20Metadata
function name() external view override returns (string memory) {
return bytes(_erc20Name).length == 0 ? "Superfluid Pool" : _erc20Name;
}

/// @inheritdoc IERC20Metadata
function symbol() external view override returns (string memory) {
return bytes(_erc20Symbol).length == 0 ? "POOL" : _erc20Symbol;
}

/// @inheritdoc IERC20Metadata
function decimals() external view override returns (uint8) {
return _erc20Decimals;
}

function _pdPoolIndexToPoolIndexData(PDPoolIndex memory pdPoolIndex)
internal
pure
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -4,21 +4,25 @@ pragma solidity ^0.8.23;
import { BeaconProxy } from "@openzeppelin/contracts/proxy/beacon/BeaconProxy.sol";
import { ISuperfluidToken } from "../../interfaces/superfluid/ISuperfluidToken.sol";
import { SuperfluidPool } from "./SuperfluidPool.sol";
import { PoolConfig } from "../../interfaces/agreements/gdav1/IGeneralDistributionAgreementV1.sol";
import { PoolConfig, PoolERC20Metadata } from "../../interfaces/agreements/gdav1/IGeneralDistributionAgreementV1.sol";

library SuperfluidPoolDeployerLibrary {
function deploy(
address beacon,
address admin,
ISuperfluidToken token,
PoolConfig memory config
PoolConfig memory config,
PoolERC20Metadata memory poolERC20Metadata
) external returns (SuperfluidPool pool) {
bytes memory initializeCallData = abi.encodeWithSelector(
SuperfluidPool.initialize.selector,
admin,
token,
config.transferabilityForUnitsOwner,
config.distributionFromAnyAddress
config.distributionFromAnyAddress,
poolERC20Metadata.name,
poolERC20Metadata.symbol,
poolERC20Metadata.decimals
);
BeaconProxy superfluidPoolBeaconProxy = new BeaconProxy(
beacon,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,12 @@ struct PoolConfig {
bool distributionFromAnyAddress;
}

struct PoolERC20Metadata {
string name;
string symbol;
uint8 decimals;
}

/**
* @title General Distribution Agreement interface
* @author Superfluid
Expand Down Expand Up @@ -178,6 +184,19 @@ abstract contract IGeneralDistributionAgreementV1 is ISuperAgreement {
virtual
returns (ISuperfluidPool pool);

/// @notice Creates a new pool for `token` with custom ERC20 metadata.
/// @param token The token address
/// @param admin The admin of the pool
/// @param poolConfig The pool configuration (see PoolConfig struct)
/// @param poolERC20Metadata The pool ERC20 metadata (see PoolERC20Metadata struct)
/// @return pool The pool address
function createPoolWithCustomERC20Metadata(
ISuperfluidToken token,
address admin,
PoolConfig memory poolConfig,
PoolERC20Metadata memory poolERC20Metadata
) external virtual returns (ISuperfluidPool pool);

function updateMemberUnits(ISuperfluidPool pool, address memberAddress, uint128 newUnits, bytes calldata ctx)
external
virtual
Expand Down
Original file line number Diff line number Diff line change
@@ -1,13 +1,13 @@
// SPDX-License-Identifier: AGPLv3
pragma solidity >=0.8.4;

import { IERC20 } from "@openzeppelin/contracts/token/ERC20/IERC20.sol";
import { IERC20, IERC20Metadata } from "@openzeppelin/contracts/token/ERC20/extensions/IERC20Metadata.sol";
import { ISuperfluidToken } from "../../superfluid/ISuperfluidToken.sol";

/**
* @dev The interface for any super token pool regardless of the distribution schemes.
*/
interface ISuperfluidPool is IERC20 {
interface ISuperfluidPool is IERC20, IERC20Metadata {
// Custom Errors

error SUPERFLUID_POOL_INVALID_TIME(); // 0x83c35016
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -29,7 +29,11 @@ import { IPoolMemberNFT } from "../agreements/gdav1/IPoolMemberNFT.sol";
import { ISuperAgreement } from "./ISuperAgreement.sol";
import { IConstantFlowAgreementV1 } from "../agreements/IConstantFlowAgreementV1.sol";
import { IInstantDistributionAgreementV1 } from "../agreements/IInstantDistributionAgreementV1.sol";
import { IGeneralDistributionAgreementV1, PoolConfig } from "../agreements/gdav1/IGeneralDistributionAgreementV1.sol";
import {
IGeneralDistributionAgreementV1,
PoolConfig,
PoolERC20Metadata
} from "../agreements/gdav1/IGeneralDistributionAgreementV1.sol";
import { ISuperfluidPool } from "../agreements/gdav1/ISuperfluidPool.sol";
/// Superfluid App interfaces:
import { ISuperApp } from "./ISuperApp.sol";
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,8 @@ import { Superfluid } from "../../contracts/superfluid/Superfluid.sol";
import { ISuperfluidPool, SuperfluidPool } from "../../contracts/agreements/gdav1/SuperfluidPool.sol";
import {
IGeneralDistributionAgreementV1,
PoolConfig
PoolConfig,
PoolERC20Metadata
} from "../../contracts/interfaces/agreements/gdav1/IGeneralDistributionAgreementV1.sol";
import { IPoolNFTBase } from "../../contracts/interfaces/agreements/gdav1/IPoolNFTBase.sol";
import { IPoolAdminNFT } from "../../contracts/interfaces/agreements/gdav1/IPoolAdminNFT.sol";
Expand Down Expand Up @@ -1078,19 +1079,41 @@ contract FoundrySuperfluidTester is Test {
address _poolAdmin,
bool _useForwarder,
PoolConfig memory _poolConfig
) internal returns (ISuperfluidPool) {
ISuperfluidPool localPool;

) internal returns (ISuperfluidPool localPool) {
vm.startPrank(_caller);
if (!_useForwarder) {
localPool = SuperfluidPool(address(sf.gda.createPool(_superToken, _poolAdmin, _poolConfig)));
} else {
(, localPool) = sf.gdaV1Forwarder.createPool(_superToken, _poolAdmin, _poolConfig);
}
vm.stopPrank();
_addAccount(address(localPool));
_assertPoolCreation(localPool, _useForwarder, _superToken, _poolAdmin, false, PoolERC20Metadata("", "", 0));
}

function _helperCreatePoolWithCustomERC20Metadata(
ISuperToken _superToken,
address _caller,
address _poolAdmin,
PoolConfig memory _poolConfig,
PoolERC20Metadata memory _poolERC20Metadata
) internal returns (ISuperfluidPool localPool) {
vm.startPrank(_caller);
localPool = SuperfluidPool(address(sf.gda.createPoolWithCustomERC20Metadata(
_superToken, _poolAdmin, _poolConfig, _poolERC20Metadata)));
vm.stopPrank();
_assertPoolCreation(localPool, false, _superToken, _poolAdmin, true, _poolERC20Metadata);
}

// Assert Pool Creation was properly handled
// Assert Pool Creation was properly handled
function _assertPoolCreation(
ISuperfluidPool localPool,
bool _useForwarder,
ISuperToken _superToken,
address _poolAdmin,
bool _useCustomERC20Metadata,
PoolERC20Metadata memory _poolERC20Metadata
) private {
_addAccount(address(localPool));
address poolAdmin = localPool.admin();
{
bool isPool = _useForwarder
Expand Down Expand Up @@ -1124,7 +1147,19 @@ contract FoundrySuperfluidTester is Test {
assertEq(poolAdmin, adjustmentFlowRecipient, "_helperCreatePool: Incorrect pool adjustment flow receiver");
}

return localPool;
// Assert ERC20 Metadata as expected
{
if (_useCustomERC20Metadata) {
assertEq(localPool.name(), _poolERC20Metadata.name, "_helperCreatePool: Pool ERC20 Metadata name mismatch");
assertEq(localPool.symbol(), _poolERC20Metadata.symbol, "_helperCreatePool: Pool ERC20 Metadata symbol mismatch");
assertEq(localPool.decimals(), _poolERC20Metadata.decimals, "_helperCreatePool: Pool ERC20 Metadata decimals mismatch");
} else {
// expect the default/fallback values hardcoded in the pool contract
assertEq(localPool.name(), "Superfluid Pool", "_helperCreatePool: Pool ERC20 Metadata name mismatch");
assertEq(localPool.symbol(), "POOL", "_helperCreatePool: Pool ERC20 Metadata symbol mismatch");
assertEq(localPool.decimals(), 0, "_helperCreatePool: Pool ERC20 Metadata decimals mismatch");
}
}
}

function _helperCreatePool(ISuperToken _superToken, address _caller, address _poolAdmin)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -166,6 +166,13 @@ contract GeneralDistributionAgreementV1IntegrationTest is FoundrySuperfluidTeste
_helperCreatePool(superToken, alice, alice, useForwarder, config);
}

function testCreatePoolWithCustomERC20Metadata(PoolConfig memory config, uint8 decimals) public {
vm.assume(decimals < 32);
_helperCreatePoolWithCustomERC20Metadata(
superToken, alice, alice, config, PoolERC20Metadata("My SuperToken", "MYST", decimals)
);
}

function testRevertConnectPoolByNonHost(address notHost, PoolConfig memory config) public {
ISuperfluidPool pool = _helperCreatePool(superToken, alice, alice, false, config);
vm.assume(notHost != address(sf.host));
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -50,5 +50,14 @@ contract SuperfluidPoolStorageLayoutMock is SuperfluidPool, StorageLayoutTestBas

assembly { slot := distributionFromAnyAddress.slot offset := distributionFromAnyAddress.offset }
if (slot != 10 || offset != 1) revert STORAGE_LOCATION_CHANGED("distributionFromAnyAddress");

assembly { slot := _erc20Name.slot offset := _erc20Name.offset }
if (slot != 11 || offset != 0) revert STORAGE_LOCATION_CHANGED("_erc20Name");

assembly { slot := _erc20Symbol.slot offset := _erc20Symbol.offset }
if (slot != 12 || offset != 0) revert STORAGE_LOCATION_CHANGED("_erc20Symbol");

assembly { slot := _erc20Decimals.slot offset := _erc20Decimals.offset }
if (slot != 13 || offset != 0) revert STORAGE_LOCATION_CHANGED("_erc20Decimals");
}
}
Loading