Skip to content

Commit

Permalink
Add listed super tokens and super token logic contracts to verificati…
Browse files Browse the repository at this point in the history
…on check
  • Loading branch information
ElvijsDzirkals committed Sep 22, 2023
1 parent 048af58 commit 0054cbf
Show file tree
Hide file tree
Showing 19 changed files with 690 additions and 88 deletions.
6 changes: 5 additions & 1 deletion .github/workflows/daily-slack-message.yml
Original file line number Diff line number Diff line change
Expand Up @@ -15,9 +15,13 @@ jobs:
uses: actions/setup-node@v3
with:
node-version: 18.x


- name: Send slack message
run: node tasks/daily-slack-bot.js
working-directory: tasks
run: |
npm install ethers --force
node daily-slack-bot.js
env:
CI_SLACK_WEBHOOK: ${{ secrets.CI_SLACK_WEBHOOK }}
ETHERSCAN_API_KEY: ${{ secrets.ETHERSCAN_API_KEY }}
Expand Down
7 changes: 7 additions & 0 deletions packages/ethereum-contracts/CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,13 @@ This project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.htm
- Expose `SuperToken._underlyingDecimals` with `SuperToken.getUnderlyingDecimals()`
- Expose `_toUnderlyingAmount(uint256 amount)` with `toUnderlyingAmount(uint256 amount)`
- `batchCall` supports payable `OPERATION_TYPE_SUPERFLUID_CALL_APP_ACTION`: only the first `OPERATION_TYPE_SUPERFLUID_CALL_APP_ACTION` will be payable
- Added two new functions to `SuperfluidGovernanceBase.sol`: `changeSuperTokenAdmin` and `batchChangeSuperTokenAdmin`
- `Superfluid.changeSuperTokenAdmin()` function added to be called via governance for tokens with no admin override
- Added an overloaded `initialize` to `SuperToken.sol`, which additionally takes `address adminOverride` if you want to initialize the token with an admin override
- `SuperToken.changeAdmin(address newAdmin)` added which is only callable by the current admin, the "admin" of a SuperToken can change the admin and update the proxy contract's pointer to a logic contract
> Note that the default admin (when address(0)) is the host contract as is currently the case
- `SuperToken.getAdminOverride()` added to retrieve the AdminOverride struct (currently only holds one property: `address admin`), but is future-proofed with 12 additional bits that can be packed in the struct
- `SuperTokenFactory.createERC20Wrapper()` overloads added to create a SuperToken AND explicitly initialize a SuperToken with an admin

### Changed

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -130,6 +130,23 @@ abstract contract SuperfluidGovernanceBase is ISuperfluidGovernance
}
}

function changeSuperTokenAdmin(ISuperfluid host, ISuperToken token, address newAdmin)
external
onlyAuthorized(host)
{
host.changeSuperTokenAdmin(token, newAdmin);
}

function batchChangeSuperTokenAdmin(ISuperfluid host, ISuperToken[] calldata token, address[] calldata newAdmins)
external
onlyAuthorized(host)
{
assert(token.length == newAdmins.length);
for (uint i = 0; i < token.length; ++i) {
host.changeSuperTokenAdmin(token[i], newAdmins[i]);
}
}

event ConfigChanged(
ISuperfluid indexed host,
ISuperfluidToken indexed superToken,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,12 @@ import { IConstantInflowNFT } from "./IConstantInflowNFT.sol";
*/
interface ISuperToken is ISuperfluidToken, IERC20Metadata, IERC777 {

struct AdminOverride {
address admin;
/// @note we use a struct so the 12 remaining
/// packed bytes may be used later on
}

/**************************************************************************
* Errors
*************************************************************************/
Expand All @@ -21,7 +27,7 @@ interface ISuperToken is ISuperfluidToken, IERC20Metadata, IERC777 {
error SUPER_TOKEN_INFLATIONARY_DEFLATIONARY_NOT_SUPPORTED(); // 0xe3e13698
error SUPER_TOKEN_NO_UNDERLYING_TOKEN(); // 0xf79cf656
error SUPER_TOKEN_ONLY_SELF(); // 0x7ffa6648
error SUPER_TOKEN_ONLY_HOST(); // 0x98f73704
error SUPER_TOKEN_ONLY_ADMIN(); // 0x0484acab
error SUPER_TOKEN_ONLY_GOV_OWNER(); // 0xd9c7ed08
error SUPER_TOKEN_APPROVE_FROM_ZERO_ADDRESS(); // 0x81638627
error SUPER_TOKEN_APPROVE_TO_ZERO_ADDRESS(); // 0xdf070274
Expand All @@ -41,6 +47,32 @@ interface ISuperToken is ISuperfluidToken, IERC20Metadata, IERC777 {
string calldata s
) external;

/**
* @dev Initialize the contract with admin override
*/
function initializeWithAdminOverride(
IERC20 underlyingToken,
uint8 underlyingDecimals,
string calldata n,
string calldata s,
address adminOverride
) external;

/**
* @notice Changes the admin override for the SuperToken
* @dev Only the current admin can call this function
* if adminOverride is address(0), it is implicitly the host address
* @param newAdmin New admin override address
*/
function changeAdmin(address newAdmin) external;

event AdminChanged(address indexed oldAdmin, address indexed newAdmin);

/**
* @dev Returns the admin override struct for the SuperToken
*/
function getAdminOverride() external view returns (AdminOverride memory);

/**************************************************************************
* Immutable variables
*************************************************************************/
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -48,6 +48,27 @@ interface ISuperTokenFactory {
FULL_UPGRADABLE
}

/**
* @notice Create new super token wrapper for the underlying ERC20 token
* @param underlyingToken Underlying ERC20 token
* @param underlyingDecimals Underlying token decimals
* @param upgradability Upgradability mode
* @param name Super token name
* @param symbol Super token symbol
* @param adminOverride Admin address override
* @return superToken The deployed and initialized wrapper super token
*/
function createERC20Wrapper(
IERC20Metadata underlyingToken,
uint8 underlyingDecimals,
Upgradability upgradability,
string calldata name,
string calldata symbol,
address adminOverride
)
external
returns (ISuperToken superToken);

/**
* @notice Create new super token wrapper for the underlying ERC20 token
* @param underlyingToken Underlying ERC20 token
Expand All @@ -67,6 +88,25 @@ interface ISuperTokenFactory {
external
returns (ISuperToken superToken);

/**
* @notice Create new super token wrapper for the underlying ERC20 token
* @param underlyingToken Underlying ERC20 token
* @param upgradability Upgradability mode
* @param name Super token name
* @param symbol Super token symbol
* @param adminOverride Admin address override
* @return superToken The deployed and initialized wrapper super token
*/
function createERC20Wrapper(
IERC20Metadata underlyingToken,
Upgradability upgradability,
string calldata name,
string calldata symbol,
address adminOverride
)
external
returns (ISuperToken superToken);

/**
* @notice Create new super token wrapper for the underlying ERC20 token with extra token info
* @param underlyingToken Underlying ERC20 token
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -233,6 +233,13 @@ interface ISuperfluid {
*/
event SuperTokenLogicUpdated(ISuperToken indexed token, address code);

/**
* @notice Change the SuperToken admin override
* @dev The admin override is the only account allowed to update the token logic
* For backward compatibility, the "host" is the default "admin" if unset (address(0)).
*/
function changeSuperTokenAdmin(ISuperToken token, address newAdmin) external;

/**************************************************************************
* App Registry
*************************************************************************/
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -59,8 +59,11 @@ contract SuperTokenStorageLayoutTester is SuperToken {
require (slot == 18 && offset == 0, "_operators changed location");
// uses 4 slots

assembly { slot:= _reserve22.slot offset := _reserve22.offset }
require (slot == 22 && offset == 0, "_reserve22 changed location");
assembly { slot:= _adminOverride.slot offset := _adminOverride.offset }
require (slot == 22 && offset == 0, "_adminOverride changed location");

assembly { slot:= _reserve23.slot offset := _reserve23.offset }
require (slot == 23 && offset == 0, "_reserve23 changed location");

assembly { slot:= _reserve31.slot offset := _reserve31.offset }
require (slot == 31 && offset == 0, "_reserve31 changed location");
Expand Down
90 changes: 78 additions & 12 deletions packages/ethereum-contracts/contracts/superfluid/SuperToken.sol
Original file line number Diff line number Diff line change
Expand Up @@ -69,15 +69,17 @@ contract SuperToken is
/// @dev ERC777 operators support data
ERC777Helper.Operators internal _operators;

/// @dev A struct which contains the address of the admin override
AdminOverride internal _adminOverride;

// NOTE: for future compatibility, these are reserved solidity slots
// The sub-class of SuperToken solidity slot will start after _reserve22

// NOTE: Whenever modifying the storage layout here it is important to update the validateStorageLayout
// function in its respective mock contract to ensure that it doesn't break anything or lead to unexpected
// behaviors/layout when upgrading

uint256 internal _reserve22;
uint256 private _reserve23;
uint256 internal _reserve23;
uint256 private _reserve24;
uint256 private _reserve25;
uint256 private _reserve26;
Expand Down Expand Up @@ -121,25 +123,43 @@ contract SuperToken is
override
initializer // OpenZeppelin Initializable
{
_underlyingToken = underlyingToken;
_underlyingDecimals = underlyingDecimals;
// @note This function is only run once during the initial
// deployment of the proxy contract.

_name = n;
_symbol = s;
// initialize the Super Token
_initialize(underlyingToken, underlyingDecimals, n, s, address(0));
}

// register interfaces
ERC777Helper.register(address(this));
/// @dev Initialize the Super Token proxy with an admin override
function initializeWithAdminOverride(
IERC20 underlyingToken,
uint8 underlyingDecimals,
string calldata n,
string calldata s,
address adminOverride
)
external
virtual
override
initializer // OpenZeppelin Initializable
{
// @note This function is only run once during the initial
// deployment of the proxy contract.

// help tools like explorers detect the token contract
emit Transfer(address(0), address(0), 0);
// initialize the Super Token
_initialize(underlyingToken, underlyingDecimals, n, s, adminOverride);
}

function proxiableUUID() public pure virtual override returns (bytes32) {
return keccak256("org.superfluid-finance.contracts.SuperToken.implementation");
}

function updateCode(address newAddress) external virtual override {
if (msg.sender != address(_host)) revert SUPER_TOKEN_ONLY_HOST();
/**
* @notice Updates the logic contract the proxy is pointing at
* @dev Only the admin can call this function (host if adminOverride.admin == address(0))
* @param newAddress Address of the new logic contract
*/
function updateCode(address newAddress) external virtual override onlyAdmin {
UUPSProxiable._updateCodeAddress(newAddress);

// @note This is another check to ensure that when updating to a new SuperToken logic contract
Expand All @@ -155,6 +175,17 @@ contract SuperToken is
}
}

function changeAdmin(address newAdmin) external override onlyAdmin {
address oldAdmin = _adminOverride.admin;
_adminOverride.admin = newAdmin;

emit AdminChanged(oldAdmin, newAdmin);
}

function getAdminOverride() external view override returns (AdminOverride memory) {
return _adminOverride;
}

/**************************************************************************
* ERC20 Token Info
*************************************************************************/
Expand All @@ -175,6 +206,31 @@ contract SuperToken is
* (private) Token Logics
*************************************************************************/

function _initialize(
IERC20 underlyingToken,
uint8 underlyingDecimals,
string calldata n,
string calldata s,
address adminOverride
) internal {
_underlyingToken = underlyingToken;
_underlyingDecimals = underlyingDecimals;

_name = n;
_symbol = s;

_adminOverride.admin = adminOverride;

// register interfaces
ERC777Helper.register(address(this));

// help tools like explorers detect the token contract
emit Transfer(address(0), address(0), 0);

// previous admin will always be the zero address in an uninitialized contract
emit AdminChanged(address(0), adminOverride);
}

/**
* @notice in the original openzeppelin implementation, transfer() and transferFrom()
* did invoke the send and receive hooks, as required by ERC777.
Expand Down Expand Up @@ -804,4 +860,14 @@ contract SuperToken is
_;
}

/**
* @dev The host contract is implicitly the admin if admin is address(0) else it is the explicitly set admin
* override address
*/
modifier onlyAdmin() {
address admin = _adminOverride.admin == address(0) ? address(_host) : _adminOverride.admin;
if (msg.sender != admin) revert SUPER_TOKEN_ONLY_ADMIN();
_;
}

}
Loading

0 comments on commit 0054cbf

Please sign in to comment.