diff --git a/.github/workflows/daily-slack-message.yml b/.github/workflows/daily-slack-message.yml index 736701c867..3dff286914 100644 --- a/.github/workflows/daily-slack-message.yml +++ b/.github/workflows/daily-slack-message.yml @@ -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 }} diff --git a/packages/ethereum-contracts/CHANGELOG.md b/packages/ethereum-contracts/CHANGELOG.md index aea245ab03..7998332b7c 100644 --- a/packages/ethereum-contracts/CHANGELOG.md +++ b/packages/ethereum-contracts/CHANGELOG.md @@ -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 diff --git a/packages/ethereum-contracts/contracts/gov/SuperfluidGovernanceBase.sol b/packages/ethereum-contracts/contracts/gov/SuperfluidGovernanceBase.sol index 6f589c07cc..3ed7e16750 100644 --- a/packages/ethereum-contracts/contracts/gov/SuperfluidGovernanceBase.sol +++ b/packages/ethereum-contracts/contracts/gov/SuperfluidGovernanceBase.sol @@ -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, diff --git a/packages/ethereum-contracts/contracts/interfaces/superfluid/ISuperToken.sol b/packages/ethereum-contracts/contracts/interfaces/superfluid/ISuperToken.sol index 83bdc6c9b7..cf75d42d43 100644 --- a/packages/ethereum-contracts/contracts/interfaces/superfluid/ISuperToken.sol +++ b/packages/ethereum-contracts/contracts/interfaces/superfluid/ISuperToken.sol @@ -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 *************************************************************************/ @@ -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 @@ -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 *************************************************************************/ diff --git a/packages/ethereum-contracts/contracts/interfaces/superfluid/ISuperTokenFactory.sol b/packages/ethereum-contracts/contracts/interfaces/superfluid/ISuperTokenFactory.sol index 7d8b1491e1..134438082a 100644 --- a/packages/ethereum-contracts/contracts/interfaces/superfluid/ISuperTokenFactory.sol +++ b/packages/ethereum-contracts/contracts/interfaces/superfluid/ISuperTokenFactory.sol @@ -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 @@ -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 diff --git a/packages/ethereum-contracts/contracts/interfaces/superfluid/ISuperfluid.sol b/packages/ethereum-contracts/contracts/interfaces/superfluid/ISuperfluid.sol index 06bddb2c22..39f508fd12 100644 --- a/packages/ethereum-contracts/contracts/interfaces/superfluid/ISuperfluid.sol +++ b/packages/ethereum-contracts/contracts/interfaces/superfluid/ISuperfluid.sol @@ -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 *************************************************************************/ diff --git a/packages/ethereum-contracts/contracts/mocks/SuperTokenMock.sol b/packages/ethereum-contracts/contracts/mocks/SuperTokenMock.sol index f70d5c4b95..092c698862 100644 --- a/packages/ethereum-contracts/contracts/mocks/SuperTokenMock.sol +++ b/packages/ethereum-contracts/contracts/mocks/SuperTokenMock.sol @@ -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"); diff --git a/packages/ethereum-contracts/contracts/superfluid/SuperToken.sol b/packages/ethereum-contracts/contracts/superfluid/SuperToken.sol index 71194600c2..a67183252d 100644 --- a/packages/ethereum-contracts/contracts/superfluid/SuperToken.sol +++ b/packages/ethereum-contracts/contracts/superfluid/SuperToken.sol @@ -69,6 +69,9 @@ 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 @@ -76,8 +79,7 @@ contract SuperToken is // 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; @@ -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 @@ -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 *************************************************************************/ @@ -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. @@ -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(); + _; + } + } diff --git a/packages/ethereum-contracts/contracts/superfluid/SuperTokenFactory.sol b/packages/ethereum-contracts/contracts/superfluid/SuperTokenFactory.sol index 7ea1283f4b..67e33a1c15 100644 --- a/packages/ethereum-contracts/contracts/superfluid/SuperTokenFactory.sol +++ b/packages/ethereum-contracts/contracts/superfluid/SuperTokenFactory.sol @@ -206,17 +206,14 @@ abstract contract SuperTokenFactoryBase is return superToken; } - /// @inheritdoc ISuperTokenFactory function createERC20Wrapper( IERC20Metadata underlyingToken, uint8 underlyingDecimals, Upgradability upgradability, string calldata name, - string calldata symbol - ) - public override - returns (ISuperToken superToken) - { + string calldata symbol, + address adminOverride + ) public override returns (ISuperToken superToken) { if (address(underlyingToken) == address(0)) { revert SUPER_TOKEN_FACTORY_ZERO_ADDRESS(); } @@ -235,16 +232,59 @@ abstract contract SuperTokenFactoryBase is } // initialize the token - superToken.initialize( + superToken.initializeWithAdminOverride( underlyingToken, underlyingDecimals, name, - symbol + symbol, + adminOverride ); emit SuperTokenCreated(superToken); } + /// @inheritdoc ISuperTokenFactory + function createERC20Wrapper( + IERC20Metadata underlyingToken, + uint8 underlyingDecimals, + Upgradability upgradability, + string calldata name, + string calldata symbol + ) + external override + returns (ISuperToken superToken) + { + return createERC20Wrapper( + underlyingToken, + underlyingDecimals, + upgradability, + name, + symbol, + address(0) + ); + } + + /// @inheritdoc ISuperTokenFactory + function createERC20Wrapper( + IERC20Metadata underlyingToken, + Upgradability upgradability, + string calldata name, + string calldata symbol, + address adminOverride + ) + external override + returns (ISuperToken superToken) + { + return createERC20Wrapper( + underlyingToken, + underlyingToken.decimals(), + upgradability, + name, + symbol, + adminOverride + ); + } + /// @inheritdoc ISuperTokenFactory function createERC20Wrapper( IERC20Metadata underlyingToken, @@ -260,7 +300,8 @@ abstract contract SuperTokenFactoryBase is underlyingToken.decimals(), upgradability, name, - symbol + symbol, + address(0) ); } diff --git a/packages/ethereum-contracts/contracts/superfluid/Superfluid.sol b/packages/ethereum-contracts/contracts/superfluid/Superfluid.sol index 5e901621e7..a642d272f9 100644 --- a/packages/ethereum-contracts/contracts/superfluid/Superfluid.sol +++ b/packages/ethereum-contracts/contracts/superfluid/Superfluid.sol @@ -304,6 +304,10 @@ contract Superfluid is emit SuperTokenLogicUpdated(token, newLogic); } + function changeSuperTokenAdmin(ISuperToken token, address newAdmin) external onlyGovernance { + token.changeAdmin(newAdmin); + } + //////////////////////////////////////////////////////////////////////////////////////////////////////////////////// // App Registry //////////////////////////////////////////////////////////////////////////////////////////////////////////////////// diff --git a/packages/ethereum-contracts/contracts/utils/SuperfluidFrameworkDeployer.sol b/packages/ethereum-contracts/contracts/utils/SuperfluidFrameworkDeployer.sol index 6d0f07bd9b..7a29ce6b68 100644 --- a/packages/ethereum-contracts/contracts/utils/SuperfluidFrameworkDeployer.sol +++ b/packages/ethereum-contracts/contracts/utils/SuperfluidFrameworkDeployer.sol @@ -159,41 +159,44 @@ contract SuperfluidFrameworkDeployer is SuperfluidFrameworkDeploymentSteps { /// @dev SuperToken name and symbol format: `Super ${_underlyingSymbol}` and `${_underlyingSymbol}x`, respectively /// @param _underlyingName The underlying token name /// @param _underlyingSymbol The token symbol + /// @param _decimals The token decimals + /// @param _mintLimit The mint limit of the underlying token + /// @param adminOverride The admin override address for the Super Token /// @return underlyingToken and superToken function deployWrapperSuperToken( string calldata _underlyingName, string calldata _underlyingSymbol, uint8 _decimals, - uint256 _mintLimit + uint256 _mintLimit, + address adminOverride ) external requiresSuperTokenFactory deploySuperTokenRequires1820 returns (TestToken underlyingToken, SuperToken superToken) { - underlyingToken = - TokenDeployerLibrary.deployTestToken(_underlyingName, _underlyingSymbol, _decimals, _mintLimit); - - string memory superTokenSymbol = string.concat(_underlyingSymbol, "x"); - - superToken = SuperToken( - address( - superTokenFactory.createERC20Wrapper( - IERC20Metadata(address(underlyingToken)), - ISuperTokenFactory.Upgradability.SEMI_UPGRADABLE, - string.concat("Super ", _underlyingSymbol), - superTokenSymbol - ) - ) - ); - - // list underlying token in resolver - string memory underlyingTokenKey = string.concat(RESOLVER_BASE_TOKEN_KEY, underlyingToken.symbol()); - _handleResolverList(true, underlyingTokenKey, address(underlyingToken)); + return _deployWrapperSuperToken(_underlyingName, _underlyingSymbol, _decimals, _mintLimit, adminOverride); + } - // list super token in resolver - string memory superTokenKey = string.concat(RESOLVER_BASE_SUPER_TOKEN_KEY, superToken.symbol()); - _handleResolverList(true, superTokenKey, address(superToken)); + /// @notice Deploys an ERC20 and a Wrapper Super Token for the ERC20 and lists both in the resolver + /// @dev SuperToken name and symbol format: `Super ${_underlyingSymbol}` and `${_underlyingSymbol}x`, respectively + /// @param _underlyingName The underlying token name + /// @param _underlyingSymbol The token symbol + /// @param _decimals The token decimals + /// @param _mintLimit The mint limit of the underlying token + /// @return underlyingToken and superToken + function deployWrapperSuperToken( + string calldata _underlyingName, + string calldata _underlyingSymbol, + uint8 _decimals, + uint256 _mintLimit + ) + external + requiresSuperTokenFactory + deploySuperTokenRequires1820 + returns (TestToken underlyingToken, SuperToken superToken) + { + return _deployWrapperSuperToken(_underlyingName, _underlyingSymbol, _decimals, _mintLimit, address(0)); } /// @notice Deploys a Native Asset Super Token and lists it in the resolver @@ -249,6 +252,40 @@ contract SuperfluidFrameworkDeployer is SuperfluidFrameworkDeploymentSteps { } } + function _deployWrapperSuperToken( + string calldata _underlyingName, + string calldata _underlyingSymbol, + uint8 _decimals, + uint256 _mintLimit, + address _adminOverride + ) internal returns (TestToken underlyingToken, SuperToken superToken) { + underlyingToken = + TokenDeployerLibrary.deployTestToken(_underlyingName, _underlyingSymbol, _decimals, _mintLimit); + + string memory superTokenSymbol = string.concat(_underlyingSymbol, "x"); + + superToken = SuperToken( + address( + superTokenFactory.createERC20Wrapper( + IERC20Metadata(address(underlyingToken)), + underlyingToken.decimals(), + ISuperTokenFactory.Upgradability.SEMI_UPGRADABLE, + string.concat("Super ", _underlyingSymbol), + superTokenSymbol, + _adminOverride + ) + ) + ); + + // list underlying token in resolver + string memory underlyingTokenKey = string.concat(RESOLVER_BASE_TOKEN_KEY, underlyingToken.symbol()); + _handleResolverList(true, underlyingTokenKey, address(underlyingToken)); + + // list super token in resolver + string memory superTokenKey = string.concat(RESOLVER_BASE_SUPER_TOKEN_KEY, superToken.symbol()); + _handleResolverList(true, superTokenKey, address(superToken)); + } + /// @notice Deploys all peripheral Superfluid contracts /// @dev Deploys Resolver, SuperfluidLoaderV1, CFAv1Forwarder, TOGA, BatchLiquidator contracts function deployPeripheralContracts() public { diff --git a/packages/ethereum-contracts/dev-scripts/deploy-contracts-and-token.js b/packages/ethereum-contracts/dev-scripts/deploy-contracts-and-token.js index f8ee84a781..6302928387 100644 --- a/packages/ethereum-contracts/dev-scripts/deploy-contracts-and-token.js +++ b/packages/ethereum-contracts/dev-scripts/deploy-contracts-and-token.js @@ -8,17 +8,17 @@ const { async function deployContractsAndToken() { const [Deployer] = await ethers.getSigners(); - const {frameworkDeployer: deployer } = await deployTestFramework(); + const {frameworkDeployer: deployer} = await deployTestFramework(); const framework = await deployer.getFramework(); const resolver = await ethers.getContractAt( testResolverArtifact.abi, framework.resolver ); - + await deployer .connect(Deployer) - .deployWrapperSuperToken( + ["deployWrapperSuperToken(string,string,uint8,uint256)"]( "Fake DAI", "fDAI", 18, diff --git a/packages/ethereum-contracts/test/contracts/superfluid/SuperToken.NonStandard.test.ts b/packages/ethereum-contracts/test/contracts/superfluid/SuperToken.NonStandard.test.ts index 94681a65d0..7f03b825aa 100644 --- a/packages/ethereum-contracts/test/contracts/superfluid/SuperToken.NonStandard.test.ts +++ b/packages/ethereum-contracts/test/contracts/superfluid/SuperToken.NonStandard.test.ts @@ -92,7 +92,7 @@ describe("SuperToken's Non Standard Functions", function () { await expectCustomError( superToken.updateCode(ZERO_ADDRESS), superToken, - "SUPER_TOKEN_ONLY_HOST" + "SUPER_TOKEN_ONLY_ADMIN" ); }); diff --git a/packages/ethereum-contracts/test/foundry/FoundrySuperfluidTester.sol b/packages/ethereum-contracts/test/foundry/FoundrySuperfluidTester.sol index 5bc5438013..22b3042193 100644 --- a/packages/ethereum-contracts/test/foundry/FoundrySuperfluidTester.sol +++ b/packages/ethereum-contracts/test/foundry/FoundrySuperfluidTester.sol @@ -12,7 +12,7 @@ import { ISETH } from "../../contracts/interfaces/tokens/ISETH.sol"; import { UUPSProxy } from "../../contracts/upgradability/UUPSProxy.sol"; import { ConstantFlowAgreementV1 } from "../../contracts/agreements/ConstantFlowAgreementV1.sol"; import { SuperTokenV1Library } from "../../contracts/apps/SuperTokenV1Library.sol"; -import { ISuperToken, SuperToken } from "../../contracts/superfluid/SuperToken.sol"; +import { IERC20, ISuperToken, SuperToken } from "../../contracts/superfluid/SuperToken.sol"; import { SuperfluidLoader } from "../../contracts/utils/SuperfluidLoader.sol"; import { TestResolver } from "../../contracts/utils/TestResolver.sol"; import { TestToken } from "../../contracts/utils/TestToken.sol"; @@ -606,6 +606,24 @@ contract FoundrySuperfluidTester is Test { } } + // Write Helpers - SuperToken + + function _helperDeploySuperTokenAndInitialize( + ISuperToken previousSuperToken, + IERC20 underlyingToken, + uint8 underlyingDecimals, + string memory name, + string memory symbol, + address adminOverride + ) internal returns (SuperToken localSuperToken) { + localSuperToken = new SuperToken( + sf.host, + previousSuperToken.CONSTANT_OUTFLOW_NFT(), + previousSuperToken.CONSTANT_INFLOW_NFT() + ); + localSuperToken.initializeWithAdminOverride(underlyingToken, underlyingDecimals, name, symbol, adminOverride); + } + // Write Helpers - ConstantFlowAgreementV1 /// @notice Creates a flow between a sender and receiver at a given flow rate /// @dev This helper assumes a valid flow rate with vm.assume and asserts that state has updated as expected. @@ -1110,8 +1128,8 @@ contract FoundrySuperfluidTester is Test { _assertSubscriptionData(params.superToken, subId, approved, units, pending); - // Assert Global Invariants - _assertGlobalInvariants(); + // Assert Global Invariants + _assertGlobalInvariants(); } } diff --git a/packages/ethereum-contracts/test/foundry/gov/SuperfluidGovernanceII.t.sol b/packages/ethereum-contracts/test/foundry/gov/SuperfluidGovernanceII.t.sol new file mode 100644 index 0000000000..4c4da1d1e7 --- /dev/null +++ b/packages/ethereum-contracts/test/foundry/gov/SuperfluidGovernanceII.t.sol @@ -0,0 +1,108 @@ +// SPDX-License-Identifier: AGPLv3 +pragma solidity 0.8.19; + +import "../FoundrySuperfluidTester.sol"; +import { UUPSProxiable } from "../../../contracts/upgradability/UUPSProxiable.sol"; +import { ISuperToken, SuperToken } from "../../../contracts/superfluid/SuperToken.sol"; +import { SuperTokenV1Library } from "../../../contracts/apps/SuperTokenV1Library.sol"; +import { ISuperAgreement } from "../../../contracts/interfaces/superfluid/ISuperAgreement.sol"; +import { ISuperfluid } from "../../../contracts/interfaces/superfluid/ISuperfluid.sol"; +import { AgreementMock } from "../../../contracts/mocks/AgreementMock.sol"; + +contract SuperfluidGovernanceIntegrationTest is FoundrySuperfluidTester { + using SuperTokenV1Library for SuperToken; + + constructor() FoundrySuperfluidTester(3) { } + + function testChangeSuperTokenAdmin(address newAdmin) public { + vm.assume(newAdmin != address(0)); + + vm.startPrank(sf.governance.owner()); + sf.governance.changeSuperTokenAdmin(sf.host, superToken, newAdmin); + vm.stopPrank(); + + assertEq(superToken.getAdminOverride().admin, newAdmin, "Superfluid.t: super token admin not changed"); + } + + function testRevertChangeSuperTokenAdminWhenCallerIsNotNotGovOwner(address newAdmin) public { + vm.assume(newAdmin != address(0)); + + vm.startPrank(newAdmin); + vm.expectRevert(); + sf.governance.changeSuperTokenAdmin(sf.host, superToken, newAdmin); + vm.stopPrank(); + } + + function testRevertWhenHostIsNotAdmin(address initialAdmin) public { + vm.assume(initialAdmin != address(0)); + vm.assume(initialAdmin != address(sf.host)); + + vm.startPrank(address(sf.host)); + superToken.changeAdmin(initialAdmin); + vm.stopPrank(); + + vm.startPrank(sf.governance.owner()); + vm.expectRevert(ISuperToken.SUPER_TOKEN_ONLY_ADMIN.selector); + sf.governance.changeSuperTokenAdmin(sf.host, superToken, initialAdmin); + vm.stopPrank(); + } + + function testBatchChangeSuperTokenAdmin(address newAdmin) public { + vm.assume(newAdmin != address(0)); + + (, ISuperToken localSuperToken) = sfDeployer.deployWrapperSuperToken("FTT", "FTT", 18, type(uint256).max); + + ISuperToken[] memory superTokens = new ISuperToken[](2); + superTokens[0] = superToken; // host admin + superTokens[1] = localSuperToken; // host admin + + address[] memory newAdmins = new address[](2); + newAdmins[0] = newAdmin; + newAdmins[1] = newAdmin; + + vm.startPrank(sf.governance.owner()); + sf.governance.batchChangeSuperTokenAdmin(sf.host, superTokens, newAdmins); + vm.stopPrank(); + + assertEq(superToken.getAdminOverride().admin, newAdmin, "Superfluid.t: super token admin not changed"); + } + + function testRevertBatchChangeSuperTokenAdminWhenHostNotAdmin(address newAdmin) public { + vm.assume(newAdmin != address(0)); + + (, ISuperToken localSuperToken) = sfDeployer.deployWrapperSuperToken("FTT", "FTT", 18, type(uint256).max, alice); + + ISuperToken[] memory superTokens = new ISuperToken[](2); + superTokens[0] = superToken; // host admin + superTokens[1] = localSuperToken; // non-host admin + + address[] memory newAdmins = new address[](2); + newAdmins[0] = newAdmin; + newAdmins[1] = newAdmin; + + vm.startPrank(sf.governance.owner()); + vm.expectRevert(); + sf.governance.batchChangeSuperTokenAdmin(sf.host, superTokens, newAdmins); + vm.stopPrank(); + } + + function testRevertBatchChangeWhenCallerIsNotGovOwner(address newAdmin) public { + vm.assume(newAdmin != address(0)); + vm.assume(newAdmin != address(sf.governance.owner())); + + (, ISuperToken localSuperToken) = sfDeployer.deployWrapperSuperToken("FTT", "FTT", 18, type(uint256).max); + + ISuperToken[] memory superTokens = new ISuperToken[](2); + superTokens[0] = superToken; // host admin + superTokens[1] = localSuperToken; // host admin + + address[] memory newAdmins = new address[](2); + newAdmins[0] = newAdmin; + newAdmins[1] = newAdmin; + + vm.startPrank(newAdmin); + vm.expectRevert(); + sf.governance.batchChangeSuperTokenAdmin(sf.host, superTokens, newAdmins); + vm.stopPrank(); + } +} diff --git a/packages/ethereum-contracts/test/foundry/superfluid/SuperToken.t.sol b/packages/ethereum-contracts/test/foundry/superfluid/SuperToken.t.sol index 704db8c530..3f80f3d779 100644 --- a/packages/ethereum-contracts/test/foundry/superfluid/SuperToken.t.sol +++ b/packages/ethereum-contracts/test/foundry/superfluid/SuperToken.t.sol @@ -4,13 +4,14 @@ pragma solidity 0.8.19; import { Test } from "forge-std/Test.sol"; import { UUPSProxy } from "../../../contracts/upgradability/UUPSProxy.sol"; import { UUPSProxiable } from "../../../contracts/upgradability/UUPSProxiable.sol"; -import { ISuperToken, SuperToken } from "../../../contracts/superfluid/SuperToken.sol"; +import { IERC20, ISuperToken, SuperToken } from "../../../contracts/superfluid/SuperToken.sol"; import { ConstantOutflowNFT, IConstantOutflowNFT } from "../../../contracts/superfluid/ConstantOutflowNFT.sol"; import { ConstantInflowNFT, IConstantInflowNFT } from "../../../contracts/superfluid/ConstantInflowNFT.sol"; import { FoundrySuperfluidTester } from "../FoundrySuperfluidTester.sol"; import { TestToken } from "../../../contracts/utils/TestToken.sol"; +import { TokenDeployerLibrary } from "../../../contracts/utils/SuperfluidFrameworkDeploymentSteps.sol"; -contract SuperTokenTest is FoundrySuperfluidTester { +contract SuperTokenIntegrationTest is FoundrySuperfluidTester { constructor() FoundrySuperfluidTester(0) { } function setUp() public override { @@ -118,4 +119,119 @@ contract SuperTokenTest is FoundrySuperfluidTester { vm.expectRevert(ISuperToken.SUPER_TOKEN_NFT_PROXY_ADDRESS_CHANGED.selector); UUPSProxiable(address(superToken)).updateCode(address(superTokenLogic)); } + + function testInitializeSuperTokenWithAndWithoutAdminOverride(address adminOverride) public { + (, ISuperToken localSuperToken) = + sfDeployer.deployWrapperSuperToken("FTT", "FTT", 18, type(uint256).max, adminOverride); + + assertEq( + localSuperToken.getAdminOverride().admin, + adminOverride, + "testInitializeSuperTokenWithAndWithoutAdminOverride: admin override not set correctly" + ); + } + + function testOnlyHostCanChangeAdminWhenNoAdminOverride(address adminOverride) public { + (, ISuperToken localSuperToken) = sfDeployer.deployWrapperSuperToken("FTT", "FTT", 18, type(uint256).max); + + vm.startPrank(address(sf.host)); + localSuperToken.changeAdmin(adminOverride); + vm.stopPrank(); + + assertEq( + localSuperToken.getAdminOverride().admin, + adminOverride, + "testOnlyHostCanChangeAdminWhenNoAdminOverride: admin override not set correctly" + ); + } + + function testOnlyAdminOverrideCanChangeAdmin(address adminOverride, address newAdminOverride) public { + if (adminOverride == address(0)) { + adminOverride = address(sf.host); + } + + (, ISuperToken localSuperToken) = + sfDeployer.deployWrapperSuperToken("FTT", "FTT", 18, type(uint256).max, adminOverride); + + vm.startPrank(adminOverride); + localSuperToken.changeAdmin(newAdminOverride); + vm.stopPrank(); + + assertEq( + localSuperToken.getAdminOverride().admin, + newAdminOverride, + "testOnlyAdminOverrideCanChangeAdmin: admin override not set correctly" + ); + } + + function testRevertWhenNonAdminTriesToChangeAdmin(address adminOverride, address nonAdminOverride) public { + vm.assume(adminOverride != nonAdminOverride); + vm.assume(nonAdminOverride != address(0)); + if (adminOverride == address(0)) { + adminOverride = address(sf.host); + } + + (, ISuperToken localSuperToken) = + sfDeployer.deployWrapperSuperToken("FTT", "FTT", 18, type(uint256).max, adminOverride); + + vm.startPrank(nonAdminOverride); + vm.expectRevert(ISuperToken.SUPER_TOKEN_ONLY_ADMIN.selector); + localSuperToken.changeAdmin(nonAdminOverride); + vm.stopPrank(); + } + + function testOnlyHostCanUpdateCodeWhenNoAdminOverride() public { + (TestToken localTestToken, ISuperToken localSuperToken) = + sfDeployer.deployWrapperSuperToken("FTT", "FTT", 18, type(uint256).max); + + SuperToken newSuperTokenLogic = + _helperDeploySuperTokenAndInitialize(localSuperToken, localTestToken, 18, "FTT", "FTT", address(0)); + + vm.startPrank(address(sf.host)); + UUPSProxiable(address(localSuperToken)).updateCode(address(newSuperTokenLogic)); + vm.stopPrank(); + + assertEq( + UUPSProxiable(address(localSuperToken)).getCodeAddress(), + address(newSuperTokenLogic), + "testOnlyHostCanUpdateCodeWhenNoAdminOverride: super token logic not updated correctly" + ); + } + + function testOnlyAdminOverrideCanUpdateCode(address adminOverride) public { + if (adminOverride == address(0)) { + adminOverride = address(sf.host); + } + + (TestToken localTestToken, ISuperToken localSuperToken) = + sfDeployer.deployWrapperSuperToken("FTT", "FTT", 18, type(uint256).max, adminOverride); + + SuperToken newSuperTokenLogic = + _helperDeploySuperTokenAndInitialize(localSuperToken, localTestToken, 18, "FTT", "FTT", adminOverride); + + vm.startPrank(adminOverride); + UUPSProxiable(address(localSuperToken)).updateCode(address(newSuperTokenLogic)); + vm.stopPrank(); + + assertEq( + UUPSProxiable(address(localSuperToken)).getCodeAddress(), + address(newSuperTokenLogic), + "testOnlyHostCanUpdateCodeWhenNoAdminOverride: super token logic not updated correctly" + ); + } + + function testRevertWhenNonAdminTriesToUpdateCode(address adminOverride, address nonAdminOverride) public { + vm.assume(adminOverride != address(sf.host)); + + (TestToken localTestToken, ISuperToken localSuperToken) = + sfDeployer.deployWrapperSuperToken("FTT", "FTT", 18, type(uint256).max); + + SuperToken newSuperTokenLogic = + _helperDeploySuperTokenAndInitialize(localSuperToken, localTestToken, 18, "FTT", "FTT", adminOverride); + + vm.startPrank(nonAdminOverride); + vm.expectRevert(ISuperToken.SUPER_TOKEN_ONLY_ADMIN.selector); + UUPSProxiable(address(localSuperToken)).updateCode(address(newSuperTokenLogic)); + vm.stopPrank(); + } } diff --git a/packages/ethereum-contracts/test/foundry/superfluid/Superfluid.t.sol b/packages/ethereum-contracts/test/foundry/superfluid/Superfluid.t.sol index f62918bbf0..1fbbb090f7 100644 --- a/packages/ethereum-contracts/test/foundry/superfluid/Superfluid.t.sol +++ b/packages/ethereum-contracts/test/foundry/superfluid/Superfluid.t.sol @@ -2,6 +2,7 @@ pragma solidity 0.8.19; import "../FoundrySuperfluidTester.sol"; +import { UUPSProxiable } from "../../../contracts/upgradability/UUPSProxiable.sol"; import { SuperToken } from "../../../contracts/superfluid/SuperToken.sol"; import { SuperTokenV1Library } from "../../../contracts/apps/SuperTokenV1Library.sol"; import { ISuperAgreement } from "../../../contracts/interfaces/superfluid/ISuperAgreement.sol"; @@ -50,4 +51,35 @@ contract SuperfluidIntegrationTest is FoundrySuperfluidTester { sf.governance.registerAgreementClass(sf.host, address(badmock)); vm.stopPrank(); } + + function testChangeSuperTokenAdmin(address newAdmin) public { + vm.startPrank(address(sf.governance)); + sf.host.changeSuperTokenAdmin(superToken, newAdmin); + vm.stopPrank(); + + assertEq(superToken.getAdminOverride().admin, newAdmin, "Superfluid.t: super token admin not changed"); + } + + function testRevertChangeSuperTokenAdminWhenHostIsNotAdminOverride(address initialAdmin, address newAdmin) public { + vm.assume(initialAdmin != address(0)); + vm.assume(newAdmin != address(0)); + vm.assume(initialAdmin != address(sf.host)); + + vm.startPrank(address(sf.host)); + superToken.changeAdmin(initialAdmin); + vm.stopPrank(); + + vm.startPrank(address(sf.governance)); + vm.expectRevert(ISuperToken.SUPER_TOKEN_ONLY_ADMIN.selector); + sf.host.changeSuperTokenAdmin(superToken, newAdmin); + vm.stopPrank(); + } + + function testRevertChangeSuperTokenAdminWhenNotGovernanceCalling(address newAdmin) public { + vm.assume(newAdmin != address(sf.governance)); + vm.startPrank(newAdmin); + vm.expectRevert(ISuperfluid.HOST_ONLY_GOVERNANCE.selector); + sf.host.changeSuperTokenAdmin(superToken, newAdmin); + vm.stopPrank(); + } } diff --git a/tasks/daily-slack-bot.js b/tasks/daily-slack-bot.js index e79e6ff3a5..f539d42bf6 100644 --- a/tasks/daily-slack-bot.js +++ b/tasks/daily-slack-bot.js @@ -1,4 +1,5 @@ const https = require("https"); +const ethers = require("ethers") const workflowPath = "https://api.github.com/repos/superfluid-finance/protocol-monorepo/actions/runs?per_page=100"; @@ -18,8 +19,7 @@ const topSectionMessage = "Looks like there are some lonely pull requests open in your area"; const workflowFileName = ".github/workflows/ci.canary.yml"; const metadataLink = - "https://raw.githubusercontent.com/superfluid-finance/metadata/master/networks.json"; - + "https://raw.githubusercontent.com/superfluid-finance/protocol-monorepo/dev/packages/metadata/networks.json"; const redImage = "https://upload.wikimedia.org/wikipedia/commons/thumb/6/62/Solid_red.svg/512px-Solid_red.svg.png?20150316143248"; const orangeImage = @@ -100,6 +100,8 @@ const networkSpecificData = { }, }; +const superTokenFactoryABI = [{ "inputs": [{ "internalType": "contract ISuperfluid", "name": "host", "type": "address" }, { "internalType": "contract ISuperToken", "name": "superTokenLogic", "type": "address" }, { "internalType": "contract IConstantOutflowNFT", "name": "constantOutflowNFTLogic", "type": "address" }, { "internalType": "contract IConstantInflowNFT", "name": "constantInflowNFTLogic", "type": "address" }, { "internalType": "contract IPoolAdminNFT", "name": "poolAdminNFT", "type": "address" }, { "internalType": "contract IPoolMemberNFT", "name": "poolMemberNFT", "type": "address" }], "stateMutability": "nonpayable", "type": "constructor" }, { "inputs": [], "name": "SUPER_TOKEN_FACTORY_ALREADY_EXISTS", "type": "error" }, { "inputs": [], "name": "SUPER_TOKEN_FACTORY_DOES_NOT_EXIST", "type": "error" }, { "inputs": [], "name": "SUPER_TOKEN_FACTORY_NON_UPGRADEABLE_IS_DEPRECATED", "type": "error" }, { "inputs": [], "name": "SUPER_TOKEN_FACTORY_ONLY_GOVERNANCE_OWNER", "type": "error" }, { "inputs": [], "name": "SUPER_TOKEN_FACTORY_ONLY_HOST", "type": "error" }, { "inputs": [], "name": "SUPER_TOKEN_FACTORY_UNINITIALIZED", "type": "error" }, { "inputs": [], "name": "SUPER_TOKEN_FACTORY_ZERO_ADDRESS", "type": "error" }, { "anonymous": false, "inputs": [{ "indexed": false, "internalType": "bytes32", "name": "uuid", "type": "bytes32" }, { "indexed": false, "internalType": "address", "name": "codeAddress", "type": "address" }], "name": "CodeUpdated", "type": "event" }, { "anonymous": false, "inputs": [{ "indexed": true, "internalType": "contract ISuperToken", "name": "token", "type": "address" }], "name": "CustomSuperTokenCreated", "type": "event" }, { "anonymous": false, "inputs": [{ "indexed": false, "internalType": "uint8", "name": "version", "type": "uint8" }], "name": "Initialized", "type": "event" }, { "anonymous": false, "inputs": [{ "indexed": true, "internalType": "contract ISuperToken", "name": "token", "type": "address" }], "name": "SuperTokenCreated", "type": "event" }, { "anonymous": false, "inputs": [{ "indexed": true, "internalType": "contract ISuperToken", "name": "tokenLogic", "type": "address" }], "name": "SuperTokenLogicCreated", "type": "event" }, { "inputs": [], "name": "CONSTANT_INFLOW_NFT_LOGIC", "outputs": [{ "internalType": "contract IConstantInflowNFT", "name": "", "type": "address" }], "stateMutability": "view", "type": "function" }, { "inputs": [], "name": "CONSTANT_OUTFLOW_NFT_LOGIC", "outputs": [{ "internalType": "contract IConstantOutflowNFT", "name": "", "type": "address" }], "stateMutability": "view", "type": "function" }, { "inputs": [], "name": "POOL_ADMIN_NFT_LOGIC", "outputs": [{ "internalType": "contract IPoolAdminNFT", "name": "", "type": "address" }], "stateMutability": "view", "type": "function" }, { "inputs": [], "name": "POOL_MEMBER_NFT_LOGIC", "outputs": [{ "internalType": "contract IPoolMemberNFT", "name": "", "type": "address" }], "stateMutability": "view", "type": "function" }, { "inputs": [], "name": "_SUPER_TOKEN_LOGIC", "outputs": [{ "internalType": "contract ISuperToken", "name": "", "type": "address" }], "stateMutability": "view", "type": "function" }, { "inputs": [], "name": "castrate", "outputs": [], "stateMutability": "nonpayable", "type": "function" }, { "inputs": [{ "internalType": "address", "name": "_underlyingToken", "type": "address" }], "name": "computeCanonicalERC20WrapperAddress", "outputs": [{ "internalType": "address", "name": "superTokenAddress", "type": "address" }, { "internalType": "bool", "name": "isDeployed", "type": "bool" }], "stateMutability": "view", "type": "function" }, { "inputs": [{ "internalType": "contract ERC20WithTokenInfo", "name": "_underlyingToken", "type": "address" }], "name": "createCanonicalERC20Wrapper", "outputs": [{ "internalType": "contract ISuperToken", "name": "", "type": "address" }], "stateMutability": "nonpayable", "type": "function" }, { "inputs": [{ "internalType": "contract ERC20WithTokenInfo", "name": "underlyingToken", "type": "address" }, { "internalType": "enum ISuperTokenFactory.Upgradability", "name": "upgradability", "type": "uint8" }, { "internalType": "string", "name": "name", "type": "string" }, { "internalType": "string", "name": "symbol", "type": "string" }], "name": "createERC20Wrapper", "outputs": [{ "internalType": "contract ISuperToken", "name": "superToken", "type": "address" }], "stateMutability": "nonpayable", "type": "function" }, { "inputs": [{ "internalType": "contract IERC20", "name": "underlyingToken", "type": "address" }, { "internalType": "uint8", "name": "underlyingDecimals", "type": "uint8" }, { "internalType": "enum ISuperTokenFactory.Upgradability", "name": "upgradability", "type": "uint8" }, { "internalType": "string", "name": "name", "type": "string" }, { "internalType": "string", "name": "symbol", "type": "string" }], "name": "createERC20Wrapper", "outputs": [{ "internalType": "contract ISuperToken", "name": "superToken", "type": "address" }], "stateMutability": "nonpayable", "type": "function" }, { "inputs": [{ "internalType": "address", "name": "_underlyingTokenAddress", "type": "address" }], "name": "getCanonicalERC20Wrapper", "outputs": [{ "internalType": "address", "name": "superTokenAddress", "type": "address" }], "stateMutability": "view", "type": "function" }, { "inputs": [], "name": "getCodeAddress", "outputs": [{ "internalType": "address", "name": "codeAddress", "type": "address" }], "stateMutability": "view", "type": "function" }, { "inputs": [], "name": "getHost", "outputs": [{ "internalType": "address", "name": "host", "type": "address" }], "stateMutability": "view", "type": "function" }, { "inputs": [], "name": "getSuperTokenLogic", "outputs": [{ "internalType": "contract ISuperToken", "name": "", "type": "address" }], "stateMutability": "view", "type": "function" }, { "inputs": [], "name": "initialize", "outputs": [], "stateMutability": "nonpayable", "type": "function" }, { "inputs": [{ "components": [{ "internalType": "address", "name": "underlyingToken", "type": "address" }, { "internalType": "address", "name": "superToken", "type": "address" }], "internalType": "struct SuperTokenFactoryBase.InitializeData[]", "name": "_data", "type": "tuple[]" }], "name": "initializeCanonicalWrapperSuperTokens", "outputs": [], "stateMutability": "nonpayable", "type": "function" }, { "inputs": [{ "internalType": "address", "name": "customSuperTokenProxy", "type": "address" }], "name": "initializeCustomSuperToken", "outputs": [], "stateMutability": "nonpayable", "type": "function" }, { "inputs": [], "name": "proxiableUUID", "outputs": [{ "internalType": "bytes32", "name": "", "type": "bytes32" }], "stateMutability": "pure", "type": "function" }, { "inputs": [{ "internalType": "address", "name": "newAddress", "type": "address" }], "name": "updateCode", "outputs": [], "stateMutability": "nonpayable", "type": "function" }] + async function getDataAsJson(url) { let options = { headers: { @@ -159,6 +161,18 @@ async function sendMessageToSlack(data) { req.end(); } +async function getSuperTokenLogicAddress(network) { + const rpcUrl = "https://rpc-endpoints.superfluid.dev/" + network.name + const provider = new ethers.JsonRpcProvider(rpcUrl) + const contract = new ethers.Contract(network.contractsV1.superTokenFactory, superTokenFactoryABI, provider) + try { + return await contract.getSuperTokenLogic() + } catch (e) { + console.log(e) + } +} + + async function checkNetworkContractVerification(network) { if (networkSpecificData[network.name] === undefined) { return ""; @@ -167,6 +181,17 @@ async function checkNetworkContractVerification(network) { contractsToCheck.nativeTokenWrapper = network.nativeTokenWrapper; contractsToCheck.wrapperToken = networkSpecificData[network.name].wrapperTokenAddress; + contractsToCheck.superTokenLogic = await getSuperTokenLogicAddress(network) + if (network.contractsV1.autowrap) { + contractsToCheck.autoWrapManager = network.contractsV1.autowrap.manager + contractsToCheck.autoWrapStrategy = network.contractsV1.autowrap.wrapStrategy + delete network.contractsV1.autowrap + } + const networkTokenAddressList = await getNetworkTokenAddressList(network); + contractsToCheck = { + ...contractsToCheck, + ...networkTokenAddressList + }; let networkMessage = ""; for (const [contractName, address] of Object.entries(contractsToCheck)) { networkMessage += await checkIndividualContractVerification( @@ -183,6 +208,56 @@ async function checkNetworkContractVerification(network) { } } +async function getNetworkTokenAddressList(network) { + return new Promise((resolve, reject) => { + let response = ''; + const hostName = network.subgraphV1.hostedEndpoint ? "api.thegraph.com" : "subgraph.satsuma-prod.com"; + const path = network.subgraphV1.hostedEndpoint ? network.subgraphV1.hostedEndpoint.split(hostName)[1] : network.subgraphV1.satsumaEndpoint.split(hostName)[1]; + let options = { + headers: { + "Content-Type": "application/json", + "User-Agent": "Elvi.js slack bot", + }, + hostname: hostName, + path: path, + method: "POST", + }; + + const req = https + .request(options, (res) => { + console.log("Status Code:", res.statusCode); + + res.on("data", (chunk) => { + response += chunk.toString(); + }); + + res.on("end", () => { + try { + const parsedResponse = JSON.parse(response); + const newObject = {}; + parsedResponse.data.tokens.forEach((token) => { + newObject[token.symbol] = token.id; + }); + resolve(newObject); + } catch (error) { + reject(error); + } + }); + }) + .on("error", (err) => { + console.log("Error: ", err.message); + reject(err); + }); + + req.write( + JSON.stringify({ + query: "query { tokens(where: {isListed: true}) { symbol id }}", + }) + ); + req.end(); + }); +} + async function checkIndividualContractVerification( network, contractName, @@ -197,7 +272,7 @@ async function checkIndividualContractVerification( if (result.status === undefined) { throw new Error(`Failed checking ${contractName}: ${contractAddress}`); } - if(result.result === "Invalid API Key") { + if (result.result === "Invalid API Key") { throw new Error(`Invalid API key for ${network.name}}`); } if ( @@ -235,8 +310,8 @@ async function checkIndividualContractVerification( const lastWorkflowId = lastWorkflow.id; const lastWorkflowUsage = await getDataAsJson( "https://api.github.com/repos/superfluid-finance/protocol-monorepo/actions/runs/" + - lastWorkflowId + - "/timing" + lastWorkflowId + + "/timing" ); const workflowStatus = lastWorkflow.status; @@ -257,8 +332,8 @@ async function checkIndividualContractVerification( async function getPrOldestCommit(prJson) { let allCommits = await getDataAsJson( "https://api.github.com/repos/superfluid-finance/protocol-monorepo/pulls/" + - prJson.number + - "/commits" + prJson.number + + "/commits" ); return allCommits[allCommits.length - 1]; } @@ -450,14 +525,14 @@ async function checkIndividualContractVerification( addSectionWithImage( webhookPayload, "Please have a look at: *<" + - oldestDraftPRUrl + - "|" + - oldestDraftPRTitle + - ">*\nColumbus would have went to America " + - americaTrips + - " times already by this time ,do something with this as this has been open for *" + - lastDraftPrUpdateBeforeDays + - "* days", + oldestDraftPRUrl + + "|" + + oldestDraftPRTitle + + ">*\nColumbus would have went to America " + + americaTrips + + " times already by this time ,do something with this as this has been open for *" + + lastDraftPrUpdateBeforeDays + + "* days", redWarningIcon, "It took them 36 days" ); @@ -482,10 +557,10 @@ async function checkIndividualContractVerification( } else { let draftMessage = oldestDraftPR ? "There are no open PRs????? *<" + - allPullRequests + - "|" + - amountOfDraftPRs + - " pull requests are in draft , you might want to look into those>*" + allPullRequests + + "|" + + amountOfDraftPRs + + " pull requests are in draft , you might want to look into those>*" : "There are no open and draft PRs? What is this, why u no work, you might want to read this:\n**"; addSectionWithImage( webhookPayload, @@ -516,9 +591,9 @@ async function checkIndividualContractVerification( addContextWithImage( webhookPayload, "*The PR has been last updated before " + - lastUpdatedBeforeDays + - " days*\nLast commit: " + - oldestPRMessage, + lastUpdatedBeforeDays + + " days*\nLast commit: " + + oldestPRMessage, imageToAddToContext, imageText ); diff --git a/yarn.lock b/yarn.lock index 539178bfd9..3473713fab 100644 --- a/yarn.lock +++ b/yarn.lock @@ -10618,15 +10618,10 @@ graphql@^15.3.0: resolved "https://registry.yarnpkg.com/graphql/-/graphql-15.8.0.tgz#33410e96b012fa3bdb1091cc99a94769db212b38" integrity sha512-5gghUc24tP9HRznNpV2+FIoq3xKkj5dTQqf4v0CpdPbFVwFkWoxOM+o+2OC9ZSvjEMTjfmG9QT+gcvggTwW1zw== -graphql@^16.6.0: - version "16.6.0" - resolved "https://registry.yarnpkg.com/graphql/-/graphql-16.6.0.tgz#c2dcffa4649db149f6282af726c8c83f1c7c5fdb" - integrity sha512-KPIBPDlW7NxrbT/eh4qPXz5FiFdL5UbaA0XUNz2Rp3Z3hqBSkbj0GVjwFDztsWVauZUWsbKHgMg++sk8UX0bkw== - -graphql@^16.8.0: - version "16.8.0" - resolved "https://registry.yarnpkg.com/graphql/-/graphql-16.8.0.tgz#374478b7f27b2dc6153c8f42c1b80157f79d79d4" - integrity sha512-0oKGaR+y3qcS5mCu1vb7KG+a89vjn06C7Ihq/dDl3jA+A8B3TKomvi3CiEcVLJQGalbu8F52LxkOym7U5sSfbg== +graphql@^16.6.0, graphql@^16.8.0: + version "16.8.1" + resolved "https://registry.yarnpkg.com/graphql/-/graphql-16.8.1.tgz#1930a965bef1170603702acdb68aedd3f3cf6f07" + integrity sha512-59LZHPdGZVh695Ud9lRzPBVTtlX9ZCV150Er2W43ro37wVof0ctenSaskPPjN7lVTIN8mSZt8PHUNKZuNQUuxw== gzip-size@^6.0.0: version "6.0.0"