Skip to content

Commit

Permalink
make token name and symbol configurable (#564)
Browse files Browse the repository at this point in the history
  • Loading branch information
adridadou authored Sep 6, 2022
1 parent d395b04 commit 43bd921
Show file tree
Hide file tree
Showing 5 changed files with 105 additions and 59 deletions.
9 changes: 7 additions & 2 deletions configs/contracts.config.ts
Original file line number Diff line number Diff line change
Expand Up @@ -330,9 +330,7 @@ export const contracts: Array<ContractConfig> = [
},
deploymentArgs: [
"daoAddress",
"erc20TokenName",
"erc20TokenAddress",
"erc20TokenSymbol",
"erc20TokenDecimals",
],
generatesExtensionId: extensionsIdsMap.ERC20_EXT,
Expand Down Expand Up @@ -539,6 +537,13 @@ export const contracts: Array<ContractConfig> = [
],
},
},
daoConfigs: [
[
"daoAddress",
"erc20TokenName",
"erc20TokenSymbol"
],
],
},
{
id: adaptersIdsMap.CONFIGURATION_ADAPTER,
Expand Down
32 changes: 32 additions & 0 deletions contracts/adapters/BankAdapter.sol
Original file line number Diff line number Diff line change
Expand Up @@ -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.
Expand Down Expand Up @@ -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.
Expand Down
49 changes: 23 additions & 26 deletions contracts/extensions/token/erc20/ERC20TokenExtension.sol
Original file line number Diff line number Diff line change
Expand Up @@ -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;

Expand All @@ -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.
Expand Down Expand Up @@ -105,31 +118,15 @@ 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)));
}

/**
* @dev Returns the symbol of the token, usually a shorter version of the
* 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)));
}

/**
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -52,27 +52,21 @@ 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);
require(daoAddress != address(0x0), "invalid dao addr");
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
Expand Down
68 changes: 43 additions & 25 deletions test/extensions/erc20.test.js
Original file line number Diff line number Diff line change
Expand Up @@ -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,
Expand Down Expand Up @@ -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];
Expand All @@ -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 =
Expand All @@ -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;
Expand All @@ -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;
});
Expand All @@ -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 () => {
Expand Down Expand Up @@ -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
Expand Down

0 comments on commit 43bd921

Please sign in to comment.