Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Token symbol uniqueness #57

Merged
merged 12 commits into from
Apr 10, 2023
2 changes: 2 additions & 0 deletions src/diamonds/nayms/AppStorage.sol
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@ pragma solidity 0.8.17;

/// @notice storage for nayms v3 decentralized insurance platform

// solhint-disable no-global-import
import "./interfaces/FreeStructs.sol";

struct AppStorage {
Expand Down Expand Up @@ -74,6 +75,7 @@ struct AppStorage {
mapping(bytes32 => uint256) upgradeScheduled; // id of the upgrade => the time that the upgrade is valid until.
uint256 upgradeExpiration; // the period of time that an upgrade is valid until.
uint256 sysAdmins; // counter for the number of sys admin accounts currently assigned
mapping(string => bytes32) tokenSymbolObjectId; // reverse mapping token symbol => object ID, to ensure symbol uniqueness
}

struct FunctionLockedStorage {
Expand Down
19 changes: 12 additions & 7 deletions src/diamonds/nayms/libs/LibAdmin.sol
Original file line number Diff line number Diff line change
Expand Up @@ -51,13 +51,18 @@ library LibAdmin {
}
AppStorage storage s = LibAppStorage.diamondStorage();

bool alreadyAdded = s.externalTokenSupported[_tokenAddress];
if (!alreadyAdded) {
s.externalTokenSupported[_tokenAddress] = true;
LibObject._createObject(LibHelpers._getIdForAddress(_tokenAddress));
s.supportedExternalTokens.push(_tokenAddress);
emit SupportedTokenAdded(_tokenAddress);
}
require(!s.externalTokenSupported[_tokenAddress], "external token already added");

string memory symbol = LibERC20.symbol(_tokenAddress);
require(LibObject._tokenSymbolNotUsed(symbol), "token symbol already in use");

s.externalTokenSupported[_tokenAddress] = true;
bytes32 tokenId = LibHelpers._getIdForAddress(_tokenAddress);
LibObject._createObject(tokenId);
s.supportedExternalTokens.push(_tokenAddress);
s.tokenSymbolObjectId[symbol] = tokenId;

emit SupportedTokenAdded(_tokenAddress);
}

function _getSupportedExternalTokens() internal view returns (address[] memory) {
Expand Down
8 changes: 4 additions & 4 deletions src/diamonds/nayms/libs/LibFeeRouter.sol
Original file line number Diff line number Diff line change
Expand Up @@ -57,7 +57,7 @@ library LibFeeRouter {
// Pay Nayms, LTD commission
LibTokenizedVault._internalTransfer(_takerId, LibHelpers._stringToBytes32(LibConstants.NAYMS_LTD_IDENTIFIER), _tokenId, tc.commissionNaymsLtd);

// Pay Nayms Discretionsry Fund commission
// Pay Nayms Discretionary Fund commission
LibTokenizedVault._internalTransfer(_takerId, LibHelpers._stringToBytes32(LibConstants.NDF_IDENTIFIER), _tokenId, tc.commissionNDF);

// Pay Staking Mechanism commission
Expand All @@ -66,7 +66,7 @@ library LibFeeRouter {
// Pay market maker commission
LibTokenizedVault._internalTransfer(_takerId, _makerId, _tokenId, tc.commissionMaker);

// Work it out again so the math is precise, ignoring remainers
// Work it out again so the math is precise, ignoring remainders
commissionPaid_ = tc.totalCommissions;

emit TradingCommissionsPaid(_takerId, _tokenId, commissionPaid_);
Expand Down Expand Up @@ -107,7 +107,7 @@ library LibFeeRouter {
// Pay Nayms, LTD commission
tc.commissionNaymsLtd = (s.tradingCommissionNaymsLtdBP * tc.roughCommissionPaid) / LibConstants.BP_FACTOR;

// Pay Nayms Discretionsry Fund commission
// Pay Nayms Discretionary Fund commission
tc.commissionNDF = (s.tradingCommissionNDFBP * tc.roughCommissionPaid) / LibConstants.BP_FACTOR;

// Pay Staking Mechanism commission
Expand All @@ -116,7 +116,7 @@ library LibFeeRouter {
// Pay market maker commission
tc.commissionMaker = (s.tradingCommissionMakerBP * tc.roughCommissionPaid) / LibConstants.BP_FACTOR;

// Work it out again so the math is precise, ignoring remainers
// Work it out again so the math is precise, ignoring remainders
tc.totalCommissions = tc.commissionNaymsLtd + tc.commissionNDF + tc.commissionSTM + tc.commissionMaker;
}

Expand Down
9 changes: 8 additions & 1 deletion src/diamonds/nayms/libs/LibObject.sol
Original file line number Diff line number Diff line change
Expand Up @@ -77,6 +77,11 @@ library LibObject {
return (bytes(s.objectTokenSymbol[_objectId]).length != 0);
}

function _tokenSymbolNotUsed(string memory _symbol) internal view returns (bool) {
AppStorage storage s = LibAppStorage.diamondStorage();
return s.tokenSymbolObjectId[_symbol] == bytes32(0);
}

function _enableObjectTokenization(
bytes32 _objectId,
string memory _symbol,
Expand All @@ -86,17 +91,19 @@ library LibObject {
if (bytes(_symbol).length == 0) {
revert MissingSymbolWhenEnablingTokenization(_objectId);
}
require(bytes(_symbol).length < 16, "symbol must be less than 16 characters");

// Ensure the entity exists before tokenizing the entity, otherwise revert.
if (!s.existingEntities[_objectId]) {
revert EntityDoesNotExist(_objectId);
}

require(!_isObjectTokenizable(_objectId), "object already tokenized");
require(bytes(_symbol).length < 16, "symbol must be less than 16 characters");
require(_tokenSymbolNotUsed(_symbol), "token symbol already in use");

s.objectTokenSymbol[_objectId] = _symbol;
s.objectTokenName[_objectId] = _name;
s.tokenSymbolObjectId[_symbol] = _objectId;
}

function _isObjectTokenWrapped(bytes32 _objectId) internal view returns (bool) {
Expand Down
22 changes: 11 additions & 11 deletions src/diamonds/nayms/libs/LibTokenizedVault.sol
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,7 @@ library LibTokenizedVault {
* @param tokenId ID of token
* @param newAmountOwned new amount owned
* @param functionName Function name
* @param msgSender msg.sende
* @param msgSender msg.sender
*/
event InternalTokenBalanceUpdate(bytes32 indexed ownerId, bytes32 tokenId, uint256 newAmountOwned, string functionName, address msgSender);

Expand All @@ -23,14 +23,14 @@ library LibTokenizedVault {
* @param tokenId ID of token
* @param newTokenSupply New token supply
* @param functionName Function name
* @param msgSender msg.sende
* @param msgSender msg.sender
*/
event InternalTokenSupplyUpdate(bytes32 indexed tokenId, uint256 newTokenSupply, string functionName, address msgSender);

/**
* @dev Emitted when a dividend gets payed out.
* @param guid divident distribution ID
* @param from distribution intiator
* @param guid dividend distribution ID
* @param from distribution initiator
* @param to distribution receiver
* @param amount distributed amount
*/
Expand Down Expand Up @@ -99,7 +99,7 @@ library LibTokenizedVault {
uint256 supply = _internalTokenSupply(_tokenId);

// This must be done BEFORE the supply increases!!!
// This will calcualte the hypothetical dividends that would correspond to this number of shares.
// This will calculate the hypothetical dividends that would correspond to this number of shares.
// It must be added to the withdrawn dividend for every denomination for the user who receives the minted tokens
bytes32[] memory dividendDenominations = s.dividendDenominations[_tokenId];

Expand Down Expand Up @@ -141,22 +141,22 @@ library LibTokenizedVault {
//
// When a dividend is payed, you divide by the total supply and add it to the totalDividendPerToken
// Dividends are held by the diamond contract at: LibHelpers._stringToBytes32(LibConstants.DIVIDEND_BANK_IDENTIFIER)
// When dividends are paid, they are transfered OUT of that same diamond contract ID.
// When dividends are paid, they are transferred OUT of that same diamond contract ID.
//
// To calculate withdrawableDividiend = ownedTokens * totalDividendPerToken - totalWithdrawnDividendPerOwner
// To calculate withdrawableDividend = ownedTokens * totalDividendPerToken - totalWithdrawnDividendPerOwner
//
// When a dividend is collected you set the totalWithdrawnDividendPerOwner to the total amount the owner withdrew
//
// When you trasnsfer, you pay out all dividends to previous owner first, then transfer ownership
// When you transfer, you pay out all dividends to previous owner first, then transfer ownership
// !!!YOU ALSO TRANSFER totalWithdrawnDividendPerOwner for those shares!!!
// totalWithdrawnDividendPerOwner(for new owner) += numberOfSharesTransfered * totalDividendPerToken
// totalWithdrawnDividendPerOwner(for previous owner) -= numberOfSharesTransfered * totalDividendPerToken (can be optimized)
// totalWithdrawnDividendPerOwner(for new owner) += numberOfSharesTransferred * totalDividendPerToken
// totalWithdrawnDividendPerOwner(for previous owner) -= numberOfSharesTransferred * totalDividendPerToken (can be optimized)
//
// When minting
// Add the token balance to the new owner
// totalWithdrawnDividendPerOwner(for new owner) += numberOfSharesMinted * totalDividendPerToken
//
// When doing the division theser will be dust. Leave the dust in the diamond!!!
// When doing the division these will be dust. Leave the dust in the diamond!!!
function _withdrawDividend(
bytes32 _ownerId,
bytes32 _tokenId,
Expand Down
42 changes: 22 additions & 20 deletions src/erc20/LibERC20.sol
Original file line number Diff line number Diff line change
Expand Up @@ -9,62 +9,56 @@
import { IERC20 } from "./IERC20.sol";

library LibERC20 {
function decimals(address _token) internal returns (uint8) {
uint256 size;
assembly {
size := extcodesize(_token)
}
require(size > 0, "LibERC20: ERC20 token address has no code");
_assertNotEmptyContract(_token);
(bool success, bytes memory result) = _token.call(abi.encodeWithSelector(IERC20.decimals.selector));
if (success) {
return abi.decode(result, (uint8));
} else {
revert("LibERC20: call to decimals() failed");
}
}

Check warning

Code scanning / Slither

Dead-code Warning

LibERC20.decimals(address) is never used and should be removed

Check warning

Code scanning / Slither

Dead-code Warning

LibERC20.decimals(address) is never used and should be removed

Check warning

Code scanning / Slither

Dead-code Warning

LibERC20.decimals(address) is never used and should be removed

function balanceOf(address _token, address _who) internal returns (uint256) {
uint256 size;
assembly {
size := extcodesize(_token)
function symbol(address _token) internal returns (string memory) {
_assertNotEmptyContract(_token);
(bool success, bytes memory result) = _token.call(abi.encodeWithSelector(IERC20.symbol.selector));
if (success) {
return abi.decode(result, (string));
} else {
revert("LibERC20: call to symbol() failed");
}
require(size > 0, "LibERC20: ERC20 token address has no code");
}
Dismissed Show dismissed Hide dismissed
Dismissed Show dismissed Hide dismissed
Dismissed Show dismissed Hide dismissed

function balanceOf(address _token, address _who) internal returns (uint256) {
_assertNotEmptyContract(_token);
(bool success, bytes memory result) = _token.call(abi.encodeWithSelector(IERC20.balanceOf.selector, _who));
if (success) {
return abi.decode(result, (uint256));
} else {
revert("LibERC20: call to balanceOf() failed");
}
}

Check warning

Code scanning / Slither

Dead-code Warning

LibERC20.balanceOf(address,address) is never used and should be removed

Check warning

Code scanning / Slither

Dead-code Warning

LibERC20.balanceOf(address,address) is never used and should be removed

Check warning

Code scanning / Slither

Dead-code Warning

LibERC20.balanceOf(address,address) is never used and should be removed

function transferFrom(
address _token,
address _from,
address _to,
uint256 _value
) internal {
uint256 size;
assembly {
size := extcodesize(_token)
}
require(size > 0, "LibERC20: ERC20 token address has no code");
_assertNotEmptyContract(_token);
(bool success, bytes memory result) = _token.call(abi.encodeWithSelector(IERC20.transferFrom.selector, _from, _to, _value));
handleReturn(success, result);
}

Check warning

Code scanning / Slither

Dead-code Warning

LibERC20.transferFrom(address,address,address,uint256) is never used and should be removed

Check warning

Code scanning / Slither

Dead-code Warning

LibERC20.transferFrom(address,address,address,uint256) is never used and should be removed

Check warning

Code scanning / Slither

Dead-code Warning

LibERC20.transferFrom(address,address,address,uint256) is never used and should be removed

function transfer(
address _token,
address _to,
uint256 _value
) internal {
uint256 size;
assembly {
size := extcodesize(_token)
}
require(size > 0, "LibERC20: ERC20 token address has no code");
_assertNotEmptyContract(_token);
(bool success, bytes memory result) = _token.call(abi.encodeWithSelector(IERC20.transfer.selector, _to, _value));
handleReturn(success, result);
}

Check warning

Code scanning / Slither

Dead-code Warning

LibERC20.transfer(address,address,uint256) is never used and should be removed

Check warning

Code scanning / Slither

Dead-code Warning

LibERC20.transfer(address,address,uint256) is never used and should be removed

Check warning

Code scanning / Slither

Dead-code Warning

LibERC20.transfer(address,address,uint256) is never used and should be removed

function handleReturn(bool _success, bytes memory _result) internal pure {
if (_success) {
Expand All @@ -83,4 +77,12 @@
}
}
}

function _assertNotEmptyContract(address _token) internal view {
uint256 size;
assembly {
size := extcodesize(_token)
}
require(size > 0, "LibERC20: ERC20 token address has no code");
}
Dismissed Show dismissed Hide dismissed
Dismissed Show dismissed Hide dismissed
Dismissed Show dismissed Hide dismissed
}
10 changes: 9 additions & 1 deletion test/T02Admin.t.sol
Original file line number Diff line number Diff line change
Expand Up @@ -109,12 +109,20 @@ contract T02AdminTest is D03ProtocolDefaults, MockAccounts {
assertTrue(nayms.isSupportedExternalToken(id));
}

function testSupportedTokenSymbolUnique() public {
bytes32 entityId = createTestEntity(account0Id);
nayms.enableEntityTokenization(entityId, "WBTC", "Entity1 Token");

bytes32 wbtcId = LibHelpers._getIdForAddress(wbtcAddress);
vm.expectRevert("token symbol already in use");
nayms.addSupportedExternalToken(wbtcAddress);
}

function testAddSupportedExternalTokenIfAlreadyAdded() public {
address[] memory orig = nayms.getSupportedExternalTokens();

vm.recordLogs();

nayms.addSupportedExternalToken(wbtcAddress);
nayms.addSupportedExternalToken(wbtcAddress);

address[] memory v = nayms.getSupportedExternalTokens();
Expand Down
2 changes: 1 addition & 1 deletion test/T03NaymsOwnership.t.sol
Original file line number Diff line number Diff line change
Expand Up @@ -53,7 +53,7 @@ contract T03NaymsOwnershipTest is D03ProtocolDefaults, MockAccounts {
address notSysAdmin,
address anotherSysAdmin
) public {
vm.assume(newOwner != anotherSysAdmin);
vm.assume(newOwner != anotherSysAdmin && newOwner != account0);
vm.assume(anotherSysAdmin != address(0));

bytes32 notSysAdminId = LibHelpers._getIdForAddress(address(notSysAdmin));
Expand Down
15 changes: 15 additions & 0 deletions test/T03TokenizedVault.t.sol
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@ import { TokenizedVaultFixture } from "test/fixtures/TokenizedVaultFixture.sol";
import { FixedPointMathLib } from "solmate/utils/FixedPointMathLib.sol";

// solhint-disable max-states-count
// solhint-disable no-console
contract T03TokenizedVaultTest is D03ProtocolDefaults, MockAccounts {
using FixedPointMathLib for uint256;

Expand Down Expand Up @@ -113,6 +114,20 @@ contract T03TokenizedVaultTest is D03ProtocolDefaults, MockAccounts {
require(success, "Should get commissions from app storage");
}

function testEntityTokenSymbolUniqueness() public {
bytes32 entityId = createTestEntity(account0Id);
bytes32 entityId2 = createTestEntityWithId(account0Id, "0xe2");
bytes32 entityId3 = createTestEntityWithId(account0Id, "0xe3");

nayms.enableEntityTokenization(entityId, "Entity1", "Entity1 Token");

vm.expectRevert("token symbol already in use");
nayms.enableEntityTokenization(entityId2, "Entity1", "Entity2 Token");

vm.expectRevert("token symbol already in use");
nayms.enableEntityTokenization(entityId3, "WBTC", "Entity3 Token");
}

function testGetLockedBalance() public {
bytes32 entityId = createTestEntity(account0Id);

Expand Down
4 changes: 1 addition & 3 deletions test/T04Entity.t.sol
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,7 @@ import { LibFeeRouterFixture } from "test/fixtures/LibFeeRouterFixture.sol";
import { SimplePolicyFixture } from "test/fixtures/SimplePolicyFixture.sol";
import "src/diamonds/nayms/interfaces/CustomErrors.sol";

// solhint-disable no-console
contract T04EntityTest is D03ProtocolDefaults {
bytes32 internal entityId1 = "0xe1";
bytes32 internal policyId1 = "0xC0FFEE";
Expand Down Expand Up @@ -791,9 +792,6 @@ contract T04EntityTest is D03ProtocolDefaults {
}

function testTokenSale() public {
// whitelist underlying token
nayms.addSupportedExternalToken(wethAddress);

uint256 sellAmount = 1000;
uint256 sellAtPrice = 1000;

Expand Down
14 changes: 1 addition & 13 deletions test/T04Market.t.sol
Original file line number Diff line number Diff line change
Expand Up @@ -78,8 +78,7 @@ contract T04MarketTest is D03ProtocolDefaults, MockAccounts {
function setUp() public virtual override {
super.setUp();

// whitelist tokens
nayms.addSupportedExternalToken(wethAddress);
// whitelist WBTC as well
nayms.addSupportedExternalToken(wbtcAddress);

dividendBankId = LibHelpers._stringToBytes32(LibConstants.DIVIDEND_BANK_IDENTIFIER);
Expand Down Expand Up @@ -324,9 +323,6 @@ contract T04MarketTest is D03ProtocolDefaults, MockAccounts {
vm.assume(1_000 < saleAmount && saleAmount < type(uint128).max);
vm.assume(1_000 < salePrice && salePrice < type(uint128).max);

// whitelist underlying token
nayms.addSupportedExternalToken(wethAddress);

nayms.createEntity(entity1, signer1Id, initEntity(wethId, collateralRatio_500, salePrice, true), "test");
nayms.createEntity(entity2, signer2Id, initEntity(wethId, collateralRatio_500, salePrice, true), "test");

Expand Down Expand Up @@ -373,9 +369,6 @@ contract T04MarketTest is D03ProtocolDefaults, MockAccounts {
vm.assume(1_000 < saleAmount && saleAmount < type(uint128).max);
vm.assume(1_000 < salePrice && salePrice < type(uint128).max);

// whitelist underlying token
nayms.addSupportedExternalToken(wethAddress);

nayms.createEntity(entity1, signer1Id, initEntity(wethId, collateralRatio_500, salePrice, true), "test");
nayms.createEntity(entity2, signer2Id, initEntity(wethId, collateralRatio_500, salePrice, true), "test");

Expand Down Expand Up @@ -549,7 +542,6 @@ contract T04MarketTest is D03ProtocolDefaults, MockAccounts {
}

function testMatchingExternalTokenOnSellSide() public {
nayms.addSupportedExternalToken(wethAddress);
writeTokenBalance(account0, naymsAddress, wethAddress, dt.entity1StartingBal);

nayms.createEntity(entity1, signer1Id, initEntity(wethId, collateralRatio_500, maxCapital_2000eth, true), "test");
Expand Down Expand Up @@ -725,8 +717,6 @@ contract T04MarketTest is D03ProtocolDefaults, MockAccounts {
uint256 offer3sell = 2000;
uint256 offer3buy = 1000;

nayms.addSupportedExternalToken(wethAddress);

// OFFER 1: 2000 pTokens -> 2000 WETH
writeTokenBalance(account0, naymsAddress, wethAddress, e1balance);
nayms.createEntity(entity1, signer1Id, initEntity(wethId, collateralRatio_500, maxCapital_2000eth, true), "test");
Expand Down Expand Up @@ -759,8 +749,6 @@ contract T04MarketTest is D03ProtocolDefaults, MockAccounts {
uint256 salePrice = 100 ether;
uint256 saleAmount = 100 ether;

nayms.addSupportedExternalToken(wethAddress);

bytes32 e1Id = DEFAULT_UNDERWRITER_ENTITY_ID;

// init test funds to maxint
Expand Down