Skip to content

Commit

Permalink
feat: bytes32 name and symbol (#34)
Browse files Browse the repository at this point in the history
  • Loading branch information
deluca-mike authored Jul 23, 2024
1 parent e809402 commit 45aa01d
Show file tree
Hide file tree
Showing 12 changed files with 183 additions and 28 deletions.
17 changes: 12 additions & 5 deletions src/ERC20Extended.sol
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,8 @@ pragma solidity 0.8.23;
import { IERC20 } from "./interfaces/IERC20.sol";
import { IERC20Extended } from "./interfaces/IERC20Extended.sol";

import { Bytes32String } from "./libs/Bytes32String.sol";

import { ERC3009 } from "./ERC3009.sol";

/**
Expand All @@ -25,8 +27,8 @@ abstract contract ERC20Extended is IERC20Extended, ERC3009 {
/// @inheritdoc IERC20
uint8 public immutable decimals;

/// @inheritdoc IERC20
string public symbol;
/// @dev The symbol of the token (stored as a bytes32 instead of a string in order to be immutable).
bytes32 internal immutable _symbol;

/// @inheritdoc IERC20
mapping(address account => mapping(address spender => uint256 allowance)) public allowance;
Expand All @@ -40,7 +42,7 @@ abstract contract ERC20Extended is IERC20Extended, ERC3009 {
* @param decimals_ The number of decimals the token uses.
*/
constructor(string memory name_, string memory symbol_, uint8 decimals_) ERC3009(name_) {
symbol = symbol_;
_symbol = Bytes32String.toBytes32(symbol_);
decimals = decimals_;
}

Expand Down Expand Up @@ -102,8 +104,13 @@ abstract contract ERC20Extended is IERC20Extended, ERC3009 {
/* ============ View/Pure Functions ============ */

/// @inheritdoc IERC20
function name() external view returns (string memory name_) {
return _name;
function name() external view returns (string memory) {
return Bytes32String.toString(_name);
}

/// @inheritdoc IERC20
function symbol() external view returns (string memory) {
return Bytes32String.toString(_symbol);
}

/* ============ Internal Interactive Functions ============ */
Expand Down
11 changes: 6 additions & 5 deletions src/ERC712Extended.sol
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@ import { IERC712 } from "./interfaces/IERC712.sol";
import { IERC712Extended } from "./interfaces/IERC712Extended.sol";

import { SignatureChecker } from "./libs/SignatureChecker.sol";
import { Bytes32String } from "./libs/Bytes32String.sol";

/**
* @title Typed structured data hashing and signing via EIP-712, extended by EIP-5267.
Expand All @@ -27,8 +28,8 @@ abstract contract ERC712Extended is IERC712Extended {
/// @dev Initial EIP-712 domain separator set at deployment.
bytes32 internal immutable _INITIAL_DOMAIN_SEPARATOR;

/// @dev The name of the contract.
string internal _name;
/// @dev The name of the contract (stored as a bytes32 instead of a string in order to be immutable).
bytes32 internal immutable _name;

/* ============ Constructor ============ */

Expand All @@ -37,7 +38,7 @@ abstract contract ERC712Extended is IERC712Extended {
* @param name_ The name of the contract.
*/
constructor(string memory name_) {
_name = name_;
_name = Bytes32String.toBytes32(name_);

_INITIAL_CHAIN_ID = block.chainid;
_INITIAL_DOMAIN_SEPARATOR = _getDomainSeparator();
Expand All @@ -62,7 +63,7 @@ abstract contract ERC712Extended is IERC712Extended {
{
return (
hex"0f", // 01111
_name,
Bytes32String.toString(_name),
"1",
block.chainid,
address(this),
Expand All @@ -87,7 +88,7 @@ abstract contract ERC712Extended is IERC712Extended {
keccak256(
abi.encode(
_EIP712_DOMAIN_HASH,
keccak256(bytes(_name)),
keccak256(bytes(Bytes32String.toString(_name))),
_EIP712_VERSION_HASH,
block.chainid,
address(this)
Expand Down
29 changes: 29 additions & 0 deletions src/libs/Bytes32String.sol
Original file line number Diff line number Diff line change
@@ -0,0 +1,29 @@
// SPDX-License-Identifier: GPL-3.0

pragma solidity 0.8.23;

/**
* @title A library to convert between string and bytes32 (assuming 32 characters or less).
* @author M^0 Labs
*/
library Bytes32String {
function toBytes32(string memory input_) internal pure returns (bytes32) {
return bytes32(abi.encodePacked(input_));
}

function toString(bytes32 input_) internal pure returns (string memory) {
uint256 length_;

while (length_ < 32 && uint8(input_[length_]) != 0) {
++length_;
}

bytes memory name_ = new bytes(length_);

for (uint256 index_; index_ < length_; ++index_) {
name_[index_] = input_[index_];
}

return string(name_);
}
}
File renamed without changes.
113 changes: 113 additions & 0 deletions test/Bytes32String.sol
Original file line number Diff line number Diff line change
@@ -0,0 +1,113 @@
// SPDX-License-Identifier: UNLICENSED

pragma solidity 0.8.23;

import { Test } from "../lib/forge-std/src/Test.sol";

import { Bytes32StringHarness } from "./utils/Bytes32StringHarness.sol";

contract Bytes32StringTests is Test {
Bytes32StringHarness internal _bytes32String;

function setUp() external {
_bytes32String = new Bytes32StringHarness();
}

function test_full() external {
assertEq(_bytes32String.toString(_bytes32String.toBytes32("")), "");
assertEq(_bytes32String.toString(_bytes32String.toBytes32("T")), "T");
assertEq(_bytes32String.toString(_bytes32String.toBytes32("Te")), "Te");
assertEq(_bytes32String.toString(_bytes32String.toBytes32("Tes")), "Tes");
assertEq(_bytes32String.toString(_bytes32String.toBytes32("Test")), "Test");
assertEq(_bytes32String.toString(_bytes32String.toBytes32("Test1")), "Test1");
assertEq(_bytes32String.toString(_bytes32String.toBytes32("Test1-")), "Test1-");
assertEq(_bytes32String.toString(_bytes32String.toBytes32("Test1-T")), "Test1-T");
assertEq(_bytes32String.toString(_bytes32String.toBytes32("Test1-Te")), "Test1-Te");
assertEq(_bytes32String.toString(_bytes32String.toBytes32("Test1-Tes")), "Test1-Tes");
assertEq(_bytes32String.toString(_bytes32String.toBytes32("Test1-Test")), "Test1-Test");
assertEq(_bytes32String.toString(_bytes32String.toBytes32("Test1-Test2")), "Test1-Test2");
assertEq(_bytes32String.toString(_bytes32String.toBytes32("Test1-Test2-")), "Test1-Test2-");
assertEq(_bytes32String.toString(_bytes32String.toBytes32("Test1-Test2-T")), "Test1-Test2-T");
assertEq(_bytes32String.toString(_bytes32String.toBytes32("Test1-Test2-Te")), "Test1-Test2-Te");
assertEq(_bytes32String.toString(_bytes32String.toBytes32("Test1-Test2-Tes")), "Test1-Test2-Tes");
assertEq(_bytes32String.toString(_bytes32String.toBytes32("Test1-Test2-Test")), "Test1-Test2-Test");
assertEq(_bytes32String.toString(_bytes32String.toBytes32("Test1-Test2-Test3")), "Test1-Test2-Test3");
assertEq(_bytes32String.toString(_bytes32String.toBytes32("Test1-Test2-Test3-")), "Test1-Test2-Test3-");
assertEq(_bytes32String.toString(_bytes32String.toBytes32("Test1-Test2-Test3-T")), "Test1-Test2-Test3-T");
assertEq(_bytes32String.toString(_bytes32String.toBytes32("Test1-Test2-Test3-Te")), "Test1-Test2-Test3-Te");
assertEq(_bytes32String.toString(_bytes32String.toBytes32("Test1-Test2-Test3-Tes")), "Test1-Test2-Test3-Tes");
assertEq(_bytes32String.toString(_bytes32String.toBytes32("Test1-Test2-Test3-Test")), "Test1-Test2-Test3-Test");
assertEq(
_bytes32String.toString(_bytes32String.toBytes32("Test1-Test2-Test3-Test4")),
"Test1-Test2-Test3-Test4"
);
assertEq(
_bytes32String.toString(_bytes32String.toBytes32("Test1-Test2-Test3-Test4-")),
"Test1-Test2-Test3-Test4-"
);
assertEq(
_bytes32String.toString(_bytes32String.toBytes32("Test1-Test2-Test3-Test4-T")),
"Test1-Test2-Test3-Test4-T"
);
assertEq(
_bytes32String.toString(_bytes32String.toBytes32("Test1-Test2-Test3-Test4-Te")),
"Test1-Test2-Test3-Test4-Te"
);
assertEq(
_bytes32String.toString(_bytes32String.toBytes32("Test1-Test2-Test3-Test4-Tes")),
"Test1-Test2-Test3-Test4-Tes"
);
assertEq(
_bytes32String.toString(_bytes32String.toBytes32("Test1-Test2-Test3-Test4-Test")),
"Test1-Test2-Test3-Test4-Test"
);
assertEq(
_bytes32String.toString(_bytes32String.toBytes32("Test1-Test2-Test3-Test4-Test5")),
"Test1-Test2-Test3-Test4-Test5"
);
assertEq(
_bytes32String.toString(_bytes32String.toBytes32("Test1-Test2-Test3-Test4-Test5-")),
"Test1-Test2-Test3-Test4-Test5-"
);
assertEq(
_bytes32String.toString(_bytes32String.toBytes32("Test1-Test2-Test3-Test4-Test5-T")),
"Test1-Test2-Test3-Test4-Test5-T"
);
assertEq(
_bytes32String.toString(_bytes32String.toBytes32("Test1-Test2-Test3-Test4-Test5-Te")),
"Test1-Test2-Test3-Test4-Test5-Te"
);

// Beyond this point, input is larger than 32 bytes, so it is truncated.
assertEq(
_bytes32String.toString(_bytes32String.toBytes32("Test1-Test2-Test3-Test4-Test5-Tes")),
"Test1-Test2-Test3-Test4-Test5-Te"
);
assertEq(
_bytes32String.toString(_bytes32String.toBytes32("Test1-Test2-Test3-Test4-Test5-Test")),
"Test1-Test2-Test3-Test4-Test5-Te"
);
assertEq(
_bytes32String.toString(_bytes32String.toBytes32("Test1-Test2-Test3-Test4-Test5-Test6")),
"Test1-Test2-Test3-Test4-Test5-Te"
);
}

function testFuzz_full(string memory input_) external {
assertEq(_bytes32String.toString(_bytes32String.toBytes32(input_)), _truncate32(input_));
}

function _truncate32(string memory input_) internal pure returns (string memory) {
bytes memory bytesIn_ = bytes(input_);

if (bytesIn_.length <= 32) return input_;

bytes memory bytesOut_ = new bytes(32);

for (uint256 index_; index_ < 32; ++index_) {
bytesOut_[index_] = bytesIn_[index_];
}

return string(bytesOut_);
}
}
2 changes: 1 addition & 1 deletion test/ERC3009.t.sol
Original file line number Diff line number Diff line change
Expand Up @@ -30,7 +30,7 @@ contract ERC3009Tests is TestUtils {
_token = new ERC20ExtendedHarness("ERC3009 Token", "ERC3009_TKN", 0);
}

/* ============ Typehashes ============ */
/* ============ TypeHashes ============ */
function test_transferWithAuthorizationTypehash() external {
assertEq(
_token.TRANSFER_WITH_AUTHORIZATION_TYPEHASH(),
Expand Down
1 change: 0 additions & 1 deletion test/ERC712Extended.t.sol
Original file line number Diff line number Diff line change
Expand Up @@ -40,7 +40,6 @@ contract ERC712ExtendedTests is TestUtils {

/* ============ constructor ============ */
function test_constructor() external {
assertEq(_erc712.name(), _name);
assertEq(_erc712.DOMAIN_SEPARATOR(), _computeDomainSeparator(_name, block.chainid, address(_erc712)));
}

Expand Down
4 changes: 2 additions & 2 deletions test/invariant/ERC20ExtendedInvariant.t.sol
Original file line number Diff line number Diff line change
Expand Up @@ -37,7 +37,7 @@ contract ERC20ExtendedHandler is CommonBase, StdCheats, StdUtils {
_token.approve(to_, amount_);
}

function transferfrom(address from_, address to_, uint256 amount_) public {
function transferFrom(address from_, address to_, uint256 amount_) public {
if (from_ == address(0) || to_ == address(0)) return;

amount_ = bound(amount_, 0, _token.balanceOf(from_));
Expand Down Expand Up @@ -87,7 +87,7 @@ contract ERC20ExtendedInvariantTests is TestUtils {
selectors[0] = ERC20ExtendedHandler.mint.selector;
selectors[1] = ERC20ExtendedHandler.burn.selector;
selectors[2] = ERC20ExtendedHandler.approve.selector;
selectors[3] = ERC20ExtendedHandler.transferfrom.selector;
selectors[3] = ERC20ExtendedHandler.transferFrom.selector;
selectors[4] = ERC20ExtendedHandler.transfer.selector;

targetSelector(FuzzSelector({ addr: address(_handler), selectors: selectors }));
Expand Down
16 changes: 16 additions & 0 deletions test/utils/Bytes32StringHarness.sol
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
// SPDX-License-Identifier: GPL-3.0

pragma solidity 0.8.23;

import { Bytes32String } from "../../src/libs/Bytes32String.sol";

/// @title Bytes32String harness used to correctly display test coverage.
contract Bytes32StringHarness {
function toBytes32(string memory input_) external pure returns (bytes32) {
return Bytes32String.toBytes32(input_);
}

function toString(bytes32 input_) external pure returns (string memory) {
return Bytes32String.toString(input_);
}
}
2 changes: 1 addition & 1 deletion test/utils/ContractHelperHarness.sol
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@

pragma solidity 0.8.23;

import { ContractHelper } from "../../src/ContractHelper.sol";
import { ContractHelper } from "../../src/libs/ContractHelper.sol";

/// @title ContractHelper harness used to correctly display test coverage.
contract ContractHelperHarness {
Expand Down
12 changes: 3 additions & 9 deletions test/utils/ERC20ExtendedHarness.sol
Original file line number Diff line number Diff line change
Expand Up @@ -11,9 +11,7 @@ contract ERC20ExtendedHarness is ERC20Extended {

constructor(string memory name_, string memory symbol_, uint8 decimals_) ERC20Extended(name_, symbol_, decimals_) {}

/******************************************************************************************************************\
| External/Public Interactive Functions |
\******************************************************************************************************************/
/* ============ Interactive Functions ============ */

function mint(address recipient_, uint256 amount_) external {
_transfer(address(0), recipient_, amount_);
Expand All @@ -27,9 +25,7 @@ contract ERC20ExtendedHarness is ERC20Extended {
authorizationState[authorizer_][nonce_] = isNonceUsed_;
}

/******************************************************************************************************************\
| External/Public View/Pure Functions |
\******************************************************************************************************************/
/* ============ View/Pure Functions ============ */

function getDigest(bytes32 internalDigest_) external view returns (bytes32) {
return _getDigest(internalDigest_);
Expand Down Expand Up @@ -71,9 +67,7 @@ contract ERC20ExtendedHarness is ERC20Extended {
return _getCancelAuthorizationDigest(authorizer_, nonce_);
}

/******************************************************************************************************************\
| Internal Interactive Functions |
\******************************************************************************************************************/
/* ============ Internal Interactive Functions ============ */

function _transfer(address sender_, address recipient_, uint256 amount_) internal override {
if (sender_ != address(0)) {
Expand Down
4 changes: 0 additions & 4 deletions test/utils/ERC712ExtendedHarness.sol
Original file line number Diff line number Diff line change
Expand Up @@ -7,10 +7,6 @@ import { ERC712Extended } from "../../src/ERC712Extended.sol";
contract ERC712ExtendedHarness is ERC712Extended {
constructor(string memory name_) ERC712Extended(name_) {}

function name() external view returns (string memory) {
return _name;
}

function getDomainSeparator() external view returns (bytes32) {
return _getDomainSeparator();
}
Expand Down

0 comments on commit 45aa01d

Please sign in to comment.