Skip to content

Commit

Permalink
ivotes checks
Browse files Browse the repository at this point in the history
  • Loading branch information
novaknole committed Sep 17, 2024
1 parent 207d988 commit 5dfccfc
Show file tree
Hide file tree
Showing 2 changed files with 83 additions and 22 deletions.
42 changes: 21 additions & 21 deletions packages/contracts/src/TokenVotingSetup.sol
Original file line number Diff line number Diff line change
Expand Up @@ -131,18 +131,7 @@ contract TokenVotingSetup is PluginUpgradeableSetup {
revert TokenNotERC20(token);
}

// [0] = IERC20Upgradeable, [1] = IVotesUpgradeable, [2] = IGovernanceWrappedERC20
bool[] memory supportedIds = _getTokenInterfaceIds(token);

if (
// If token supports none of them
// it's simply ERC20 which gets checked by _isERC20
// Currently, not a satisfiable check.
(!supportedIds[0] && !supportedIds[1] && !supportedIds[2]) ||
// If token supports IERC20, but neither
// IVotes nor IGovernanceWrappedERC20, it needs wrapping.
(supportedIds[0] && !supportedIds[1] && !supportedIds[2])
) {
if (!supportsIVotesInterface(token)) {
token = governanceWrappedERC20Base.clone();
// User already has a token. We need to wrap it in
// GovernanceWrappedERC20 in order to make the token
Expand All @@ -164,6 +153,7 @@ contract TokenVotingSetup is PluginUpgradeableSetup {
);
}


// Prepare and deploy plugin proxy.
plugin = address(tokenVotingBase).deployUUPSProxy(
abi.encodeWithSignature(
Expand Down Expand Up @@ -340,15 +330,25 @@ contract TokenVotingSetup is PluginUpgradeableSetup {
);
}

/// @notice Retrieves the interface identifiers supported by the token contract.
/// @dev It is crucial to verify if the provided token address represents a valid contract before using the below.
/// @param token The token address
function _getTokenInterfaceIds(address token) private view returns (bool[] memory) {
bytes4[] memory interfaceIds = new bytes4[](3);
interfaceIds[0] = type(IERC20Upgradeable).interfaceId;
interfaceIds[1] = type(IVotesUpgradeable).interfaceId;
interfaceIds[2] = type(IGovernanceWrappedERC20).interfaceId;
return token.getSupportedInterfaces(interfaceIds);
/// @notice Unsatisfiably determines if the token is an IVotes interface.
/// @dev Many tokens don't use ERC165 even though they still support IVotes.
function supportsIVotesInterface(address token) public view returns (bool) {
(bool success1, bytes memory data1) = token.staticcall(
abi.encodeWithSelector(IVotesUpgradeable.getPastTotalSupply.selector, 0)
);
(bool success2, bytes memory data2) = token.staticcall(
abi.encodeWithSelector(IVotesUpgradeable.getVotes.selector, address(this))
);
(bool success3, bytes memory data3) = token.staticcall(
abi.encodeWithSelector(IVotesUpgradeable.getPastVotes.selector, address(this), 0)
);

return (success1 &&
data1.length == 0x20 &&
success2 &&
data2.length == 0x20 &&
success3 &&
data3.length == 0x20);
}

/// @notice Unsatisfiably determines if the contract is an ERC20 token.
Expand Down
63 changes: 62 additions & 1 deletion packages/contracts/test/10_unit-testing/12_plugin-setup.ts
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,8 @@ import {
GovernanceERC20__factory,
GovernanceWrappedERC20,
GovernanceWrappedERC20__factory,
IERC20Upgradeable__factory,
IVotesUpgradeable__factory,
} from '../../typechain';
import {MajorityVotingBase} from '../../typechain/src/MajorityVotingBase';
import {
Expand All @@ -27,6 +29,7 @@ import {
import {VotingMode} from '../test-utils/voting-helpers';
import {
DAO_PERMISSIONS,
getInterfaceId,
Operation,
PLUGIN_UUPS_UPGRADEABLE_PERMISSIONS,
} from '@aragon/osx-commons-sdk';
Expand All @@ -39,6 +42,8 @@ import {loadFixture} from '@nomicfoundation/hardhat-network-helpers';
import {SignerWithAddress} from '@nomiclabs/hardhat-ethers/signers';
import {expect} from 'chai';
import {ethers} from 'hardhat';
import { plugins } from '../../typechain/@aragon/osx-v1.0.0';
import { IGovernanceWrappedERC20__factory } from '../../typechain/factories/src/ERC20/governance';

const abiCoder = ethers.utils.defaultAbiCoder;
const AddressZero = ethers.constants.AddressZero;
Expand Down Expand Up @@ -371,6 +376,9 @@ describe('TokenVotingSetup', function () {
preparedSetupData: {helpers, permissions},
} = await pluginSetup.callStatic.prepareInstallation(dao.address, data);

expect(await pluginSetup.supportsIVotesInterface(erc20.address))
.to.be.false;

expect(plugin).to.be.equal(anticipatedPluginAddress);
expect(helpers.length).to.be.equal(2);
expect(helpers).to.be.deep.equal([anticipatedWrappedTokenAddress, anticipatedCondition]);
Expand Down Expand Up @@ -455,6 +463,36 @@ describe('TokenVotingSetup', function () {
expect(await governanceWrappedERC20Contract.underlying()).to.be.equal(
erc20.address
);

expect(await pluginSetup.supportsIVotesInterface(erc20.address))
.to.be.false;

// If a token address is not passed, it must have deployed GovernanceERC20.
const ivotesInterfaceId = getInterfaceId(
IVotesUpgradeable__factory.createInterface()
);
const iERC20InterfaceId = getInterfaceId(
IERC20Upgradeable__factory.createInterface()
);
const iGovernanceWrappedERC20 = getInterfaceId(
IGovernanceWrappedERC20__factory.createInterface()
);

expect(
await governanceWrappedERC20Contract.supportsInterface(
ivotesInterfaceId
)
).to.be.true;
expect(
await governanceWrappedERC20Contract.supportsInterface(
iERC20InterfaceId
)
).to.be.true;
expect(
await governanceWrappedERC20Contract.supportsInterface(
iGovernanceWrappedERC20
)
).to.be.true;
});

it('correctly returns plugin, helpers and permissions, when a governance token address is supplied', async () => {
Expand Down Expand Up @@ -504,6 +542,10 @@ describe('TokenVotingSetup', function () {
preparedSetupData: {helpers, permissions},
} = await pluginSetup.callStatic.prepareInstallation(dao.address, data);

expect(
await pluginSetup.supportsIVotesInterface(governanceERC20.address)
).to.be.true;

expect(plugin).to.be.equal(anticipatedPluginAddress);
expect(helpers.length).to.be.equal(2);
expect(helpers).to.be.deep.equal([governanceERC20.address, anticipatedCondition]);
Expand Down Expand Up @@ -541,7 +583,7 @@ describe('TokenVotingSetup', function () {
});

it('correctly returns plugin, helpers and permissions, when a token address is not supplied', async () => {
const {pluginSetup, dao, prepareInstallationInputs} = await loadFixture(
const {pluginSetup, dao, defaultTokenSettings, prepareInstallationInputs} = await loadFixture(
fixture
);

Expand Down Expand Up @@ -571,6 +613,12 @@ describe('TokenVotingSetup', function () {
prepareInstallationInputs
);

expect(
await pluginSetup.supportsIVotesInterface(
defaultTokenSettings.addr
)
).to.be.false;

expect(plugin).to.be.equal(anticipatedPluginAddress);
expect(helpers.length).to.be.equal(2);
expect(helpers).to.be.deep.equal([anticipatedTokenAddress, anticipatedCondition]);
Expand Down Expand Up @@ -691,6 +739,19 @@ describe('TokenVotingSetup', function () {
expect(await token.dao()).to.be.equal(daoAddress);
expect(await token.name()).to.be.equal(defaultTokenSettings.name);
expect(await token.symbol()).to.be.equal(defaultTokenSettings.symbol);

// If a token address is not passed, it must have deployed GovernanceERC20.
const ivotesInterfaceId = getInterfaceId(
IVotesUpgradeable__factory.createInterface()
);
const iERC20InterfaceId = getInterfaceId(
IERC20Upgradeable__factory.createInterface()
);

expect(await token.supportsInterface(ivotesInterfaceId))
.to.be.true;
expect(await token.supportsInterface(iERC20InterfaceId))
.to.be.true;
});
});

Expand Down

0 comments on commit 5dfccfc

Please sign in to comment.