Skip to content

Commit

Permalink
add tests
Browse files Browse the repository at this point in the history
- add SuperfluidGovernance, Superfluid  and SuperToken changeSuperTokenAdmin tests around permissions
  • Loading branch information
0xdavinchee committed Sep 21, 2023
1 parent a00c222 commit 8ae8b2c
Show file tree
Hide file tree
Showing 6 changed files with 272 additions and 7 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -61,6 +61,7 @@ interface ISuperToken is ISuperfluidToken, IERC20Metadata, IERC777 {
/**
* @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;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -129,7 +129,7 @@ contract SuperToken is
// initialize the Super Token
_initialize(underlyingToken, underlyingDecimals, n, s, address(0));
}

/// @dev Initialize the Super Token proxy with an admin override
function initialize(
IERC20 underlyingToken,
Expand All @@ -156,7 +156,7 @@ contract SuperToken is

/**
* @notice Updates the logic contract the proxy is pointing at
* @dev
* @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 {
Expand All @@ -178,6 +178,7 @@ contract SuperToken is
function changeAdmin(address newAdmin) external override onlyAdmin {
address oldAdmin = _adminOverride.admin;
_adminOverride.admin = newAdmin;

emit AdminChanged(oldAdmin, newAdmin);
}

Expand Down Expand Up @@ -860,7 +861,8 @@ contract SuperToken is
}

/**
* @dev Admin is the host contract if unset else it is the set admin override address
* @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;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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";
Expand Down Expand Up @@ -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.initialize(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.
Expand Down Expand Up @@ -1110,8 +1128,8 @@ contract FoundrySuperfluidTester is Test {

_assertSubscriptionData(params.superToken, subId, approved, units, pending);

// Assert Global Invariants
_assertGlobalInvariants();
// Assert Global Invariants
_assertGlobalInvariants();
}
}

Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,107 @@
// 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));

(, 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();
}
}
106 changes: 105 additions & 1 deletion packages/ethereum-contracts/test/foundry/superfluid/SuperToken.t.sol
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@ 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";
Expand Down Expand Up @@ -130,4 +130,108 @@ contract SuperTokenIntegrationTest is FoundrySuperfluidTester {
"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();
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -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";
Expand Down Expand Up @@ -50,4 +51,36 @@ 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));
sf.host.changeSuperTokenAdmin(superToken, newAdmin);
vm.stopPrank();

assertEq(superToken.getAdminOverride().admin, newAdmin, "Superfluid.t: super token admin not changed");
}

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();
}
}

0 comments on commit 8ae8b2c

Please sign in to comment.