From 43bd92163fda5e35a88d22a911c4cc9b2265a768 Mon Sep 17 00:00:00 2001 From: David Roon Date: Tue, 6 Sep 2022 16:06:26 +0200 Subject: [PATCH] make token name and symbol configurable (#564) --- configs/contracts.config.ts | 9 ++- contracts/adapters/BankAdapter.sol | 32 +++++++++ .../token/erc20/ERC20TokenExtension.sol | 49 +++++++------ .../erc20/ERC20TokenExtensionFactory.sol | 6 -- test/extensions/erc20.test.js | 68 ++++++++++++------- 5 files changed, 105 insertions(+), 59 deletions(-) diff --git a/configs/contracts.config.ts b/configs/contracts.config.ts index 2f12aa152..82ec15a83 100644 --- a/configs/contracts.config.ts +++ b/configs/contracts.config.ts @@ -330,9 +330,7 @@ export const contracts: Array = [ }, deploymentArgs: [ "daoAddress", - "erc20TokenName", "erc20TokenAddress", - "erc20TokenSymbol", "erc20TokenDecimals", ], generatesExtensionId: extensionsIdsMap.ERC20_EXT, @@ -539,6 +537,13 @@ export const contracts: Array = [ ], }, }, + daoConfigs: [ + [ + "daoAddress", + "erc20TokenName", + "erc20TokenSymbol" + ], + ], }, { id: adaptersIdsMap.CONFIGURATION_ADAPTER, diff --git a/contracts/adapters/BankAdapter.sol b/contracts/adapters/BankAdapter.sol index b128c9228..db68be36e 100644 --- a/contracts/adapters/BankAdapter.sol +++ b/contracts/adapters/BankAdapter.sol @@ -34,6 +34,11 @@ SOFTWARE. */ contract BankAdapterContract is AdapterGuard, Reimbursable { + bytes32 internal constant TokenName = + keccak256("erc20.extension.tokenName"); + bytes32 internal constant TokenSymbol = + keccak256("erc20.extension.tokenSymbol"); + /** * @notice Allows the member/advisor of the DAO to withdraw the funds from their internal bank account. * @notice Only accounts that are not reserved can withdraw the funds. @@ -63,6 +68,33 @@ contract BankAdapterContract is AdapterGuard, Reimbursable { bank.withdraw(dao, payable(account), token, balance); } + function configureDao( + DaoRegistry dao, + string calldata tokenName, + string calldata tokenSymbol + ) external { + dao.setConfiguration(TokenName, uint256(stringToBytes32(tokenName))); + dao.setConfiguration( + TokenSymbol, + uint256(stringToBytes32(tokenSymbol)) + ); + } + + function stringToBytes32(string memory source) + public + pure + returns (bytes32 result) + { + bytes memory tempEmptyStringTest = bytes(source); + if (tempEmptyStringTest.length == 0) { + return 0x0; + } + + assembly { + result := mload(add(source, 32)) + } + } + /** * @notice Allows anyone to update the token balance in the bank extension * @notice If theres is no available balance in the user's account, the transaction is reverted. diff --git a/contracts/extensions/token/erc20/ERC20TokenExtension.sol b/contracts/extensions/token/erc20/ERC20TokenExtension.sol index ebce79d5e..04c847d73 100644 --- a/contracts/extensions/token/erc20/ERC20TokenExtension.sol +++ b/contracts/extensions/token/erc20/ERC20TokenExtension.sol @@ -43,18 +43,17 @@ contract ERC20Extension is AdapterGuard, IExtension, IERC20 { // The DAO address that this extension belongs to DaoRegistry public dao; + bytes32 internal constant TokenName = + keccak256("erc20.extension.tokenName"); + bytes32 internal constant TokenSymbol = + keccak256("erc20.extension.tokenSymbol"); + // Internally tracks deployment under eip-1167 proxy pattern bool public initialized = false; // The token address managed by the DAO that tracks the internal transfers address public tokenAddress; - // The name of the token managed by the DAO - string public tokenName; - - // The symbol of the token managed by the DAO - string public tokenSymbol; - // The number of decimals of the token managed by the DAO uint8 public tokenDecimals; @@ -72,12 +71,26 @@ contract ERC20Extension is AdapterGuard, IExtension, IERC20 { function initialize(DaoRegistry _dao, address) external override { require(!initialized, "already initialized"); require(tokenAddress != address(0x0), "missing token address"); - require(bytes(tokenName).length != 0, "missing token name"); - require(bytes(tokenSymbol).length != 0, "missing token symbol"); initialized = true; dao = _dao; } + function bytes32ToString(bytes32 _bytes32) + internal + pure + returns (string memory) + { + uint8 i = 0; + while (i < 32 && _bytes32[i] != 0) { + i++; + } + bytes memory bytesArray = new bytes(i); + for (i = 0; i < 32 && _bytes32[i] != 0; i++) { + bytesArray[i] = _bytes32[i]; + } + return string(bytesArray); + } + /** * @dev Returns the token address managed by the DAO that tracks the * internal transfers. @@ -105,15 +118,7 @@ contract ERC20Extension is AdapterGuard, IExtension, IERC20 { * @dev Returns the name of the token. */ function name() external view virtual returns (string memory) { - return tokenName; - } - - /** - * @dev Sets the name of the token if the extension is not initialized. - */ - function setName(string memory _name) external { - require(!initialized, "already initialized"); - tokenName = _name; + return bytes32ToString(bytes32(dao.getConfiguration(TokenName))); } /** @@ -121,15 +126,7 @@ contract ERC20Extension is AdapterGuard, IExtension, IERC20 { * name. */ function symbol() external view virtual returns (string memory) { - return tokenSymbol; - } - - /** - * @dev Sets the token symbol if the extension is not initialized. - */ - function setSymbol(string memory _symbol) external { - require(!initialized, "already initialized"); - tokenSymbol = _symbol; + return bytes32ToString(bytes32(dao.getConfiguration(TokenSymbol))); } /** diff --git a/contracts/extensions/token/erc20/ERC20TokenExtensionFactory.sol b/contracts/extensions/token/erc20/ERC20TokenExtensionFactory.sol index ac943b3f0..9d260cb38 100644 --- a/contracts/extensions/token/erc20/ERC20TokenExtensionFactory.sol +++ b/contracts/extensions/token/erc20/ERC20TokenExtensionFactory.sol @@ -52,17 +52,13 @@ contract ERC20TokenExtensionFactory is IFactory, CloneFactory, ReentrancyGuard { * @notice It initializes the extension and sets the DAO owner as the extension creator. * @notice The safest way to read the new extension address is to read it from the event. * @param dao The dao address that will be associated with the new extension. - * @param tokenName The name of the token. * @param tokenAddress The address of the ERC20 token. - * @param tokenSymbol The symbol of the ERC20 token. * @param decimals The number of decimal places of the ERC20 token. */ // slither-disable-next-line reentrancy-events function create( DaoRegistry dao, - string calldata tokenName, address tokenAddress, - string calldata tokenSymbol, uint8 decimals ) external nonReentrant { address daoAddress = address(dao); @@ -70,9 +66,7 @@ contract ERC20TokenExtensionFactory is IFactory, CloneFactory, ReentrancyGuard { address payable extensionAddr = _createClone(identityAddress); _extensions[daoAddress] = extensionAddr; ERC20Extension extension = ERC20Extension(extensionAddr); - extension.setName(tokenName); extension.setToken(tokenAddress); - extension.setSymbol(tokenSymbol); extension.setDecimals(decimals); extension.initialize(dao, address(0)); // slither-disable-next-line reentrancy-events diff --git a/test/extensions/erc20.test.js b/test/extensions/erc20.test.js index 18ee3d4e8..502106966 100644 --- a/test/extensions/erc20.test.js +++ b/test/extensions/erc20.test.js @@ -25,6 +25,7 @@ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */ const { expect } = require("chai"); +const { ethers } = require("hardhat"); const { toBN, sha3, @@ -89,9 +90,7 @@ describe("Extension - ERC20", () => { it("should be possible to create an extension using the factory", async () => { const { logs } = await this.factories.erc20ExtFactory.create( this.dao.address, - "Token A", this.testContracts.testToken1.address, - "Test", 0 ); const log = logs[0]; @@ -103,9 +102,7 @@ describe("Extension - ERC20", () => { it("should be possible to get an extension address by dao", async () => { await this.factories.erc20ExtFactory.create( this.dao.address, - "Token A", this.testContracts.testToken1.address, - "Test", 0 ); const extAddress = @@ -126,9 +123,7 @@ describe("Extension - ERC20", () => { await expect( this.factories.erc20ExtFactory.create( ZERO_ADDRESS, - "Token A", this.testContracts.testToken1.address, - "Test", 0 ) ).to.be.reverted; @@ -146,8 +141,6 @@ describe("Extension - ERC20", () => { it("should be possible to call initialize with a non member", async () => { const extension = await ERC20Extension.new(); await extension.setToken(DAI_TOKEN); - await extension.setName("DAI"); - await extension.setSymbol("DAI"); await extension.initialize(this.dao.address, creator); expect(await extension.initialized()).to.be.true; }); @@ -158,23 +151,6 @@ describe("Extension - ERC20", () => { extension.initialize(this.dao.address, daoOwner) ).to.be.revertedWith("missing token address"); }); - - it("should not be possible to call initialize with an invalid token name", async () => { - const extension = await ERC20Extension.new(); - await extension.setToken(DAI_TOKEN); - await expect( - extension.initialize(this.dao.address, daoOwner) - ).to.be.revertedWith("missing token name"); - }); - - it("should not be possible to call initialize with an invalid token name", async () => { - const extension = await ERC20Extension.new(); - await extension.setToken(DAI_TOKEN); - await extension.setName("DAI"); - await expect( - extension.initialize(this.dao.address, daoOwner) - ).to.be.revertedWith("missing token symbol"); - }); }); it("should be possible to create a dao with a erc20 extension pre-configured", async () => { @@ -263,6 +239,48 @@ describe("Extension - ERC20", () => { ); }); + it("should be possible to rename the token and its symbol with config changes", async () => { + const dao = this.dao; + const configuration = this.adapters.configuration; + const voting = this.adapters.voting; + const erc20Ext = this.extensions.erc20Ext; + + const initialErc20Name = await erc20Ext.name(); + const initialErc20Symbol = await erc20Ext.symbol(); + + expect(initialErc20Name).equal("Test Token"); + expect(initialErc20Symbol).equal("TTK"); + + //configure + await submitConfigProposal( + dao, + getProposalCounter(), + daoOwner, + configuration, + voting, + [ + { + key: sha3("erc20.extension.tokenName"), + numericValue: ethers.utils.formatBytes32String("token name"), + addressValue: ZERO_ADDRESS, + configType: 0, + }, + { + key: sha3("erc20.extension.tokenSymbol"), + numericValue: ethers.utils.formatBytes32String("token symbol"), + addressValue: ZERO_ADDRESS, + configType: 0, + }, + ] + ); + + const erc20Name = await erc20Ext.name(); + const erc20Symbol = await erc20Ext.symbol(); + + expect(erc20Name).equal("token name"); + expect(erc20Symbol).equal("token symbol"); + }); + it("should be possible to approve and transferFrom units from a member to another member when the transfer type is equals 0 (member transfer only)", async () => { const dao = this.dao; //onboarded member A & B