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 of MultiToken Contract will be case-insensitive #3548

Merged
merged 12 commits into from
May 21, 2024
1 change: 1 addition & 0 deletions contract/AElf.Contracts.MultiToken/TokenContractState.cs
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@ public partial class TokenContractState : ContractState

public StringState ChainPrimaryTokenSymbol { get; set; }
public MappedState<string, TokenInfo> TokenInfos { get; set; }
public MappedState<string, bool> InsensitiveTokenExisting { get; set; }
public MappedState<string, string> SymbolSeedMap { get; set; }
public MappedState<Address, string, long> Balances { get; set; }
public MappedState<Address, Address, string, long> Allowances { get; set; }
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -56,7 +56,7 @@
if (!IsAddressInCreateWhiteList(Context.Sender) &&
input.Symbol != TokenContractConstants.SeedCollectionSymbol)
{
var symbolSeed = State.SymbolSeedMap[input.Symbol];
var symbolSeed = State.SymbolSeedMap[input.Symbol.ToUpper()];

Check warning on line 59 in contract/AElf.Contracts.MultiToken/TokenContract_Actions.cs

View check run for this annotation

Codecov / codecov/patch

contract/AElf.Contracts.MultiToken/TokenContract_Actions.cs#L59

Added line #L59 was not covered by tests
CheckSeedNFT(symbolSeed, input.Symbol);
// seed nft for one-time use only
long balance = State.Balances[Context.Sender][symbolSeed];
Expand Down
24 changes: 14 additions & 10 deletions contract/AElf.Contracts.MultiToken/TokenContract_Helper.cs
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Text.RegularExpressions;
using AElf.Contracts.Parliament;
using AElf.CSharp.Core;
using AElf.Sdk.CSharp;
Expand All @@ -14,20 +15,19 @@

public partial class TokenContract
{
private static bool IsValidSymbolChar(char character)
private static bool IsValidSymbol(string symbol)
{
return (character >= 'A' && character <= 'Z') || (character >= '0' && character <= '9') ||
character == TokenContractConstants.NFTSymbolSeparator;
return Regex.IsMatch(symbol, "^[a-zA-Z0-9]+(-[0-9]+)?$");
}

private bool IsValidItemIdChar(char character)
private bool IsValidItemId(string symbolItemId)
{
return character >= '0' && character <= '9';
return Regex.IsMatch(symbolItemId, "^[0-9]+$");

Check warning on line 25 in contract/AElf.Contracts.MultiToken/TokenContract_Helper.cs

View check run for this annotation

Codecov / codecov/patch

contract/AElf.Contracts.MultiToken/TokenContract_Helper.cs#L25

Added line #L25 was not covered by tests
}

private bool IsValidCreateSymbolChar(char character)
private bool IsValidCreateSymbol(string symbol)
{
return character >= 'A' && character <= 'Z';
return Regex.IsMatch(symbol, "^[a-zA-Z0-9]+$");
}

private TokenInfo AssertValidToken(string symbol, long amount)
Expand All @@ -40,7 +40,7 @@

private void AssertValidSymbolAndAmount(string symbol, long amount)
{
Assert(!string.IsNullOrEmpty(symbol) && symbol.All(IsValidSymbolChar),
Assert(!string.IsNullOrEmpty(symbol) && IsValidSymbol(symbol),
"Invalid symbol.");
Assert(amount > 0, "Invalid amount.");
}
Expand Down Expand Up @@ -184,13 +184,14 @@
private void RegisterTokenInfo(TokenInfo tokenInfo)
{
CheckTokenExists(tokenInfo.Symbol);
Assert(!string.IsNullOrEmpty(tokenInfo.Symbol) && tokenInfo.Symbol.All(IsValidSymbolChar),
Assert(!string.IsNullOrEmpty(tokenInfo.Symbol) && IsValidSymbol(tokenInfo.Symbol),
"Invalid symbol.");
Assert(!string.IsNullOrEmpty(tokenInfo.TokenName), "Token name can neither be null nor empty.");
Assert(tokenInfo.TotalSupply > 0, "Invalid total supply.");
Assert(tokenInfo.Issuer != null, "Invalid issuer address.");
Assert(tokenInfo.Owner != null, "Invalid owner address.");
State.TokenInfos[tokenInfo.Symbol] = tokenInfo;
State.InsensitiveTokenExisting[tokenInfo.Symbol.ToUpper()] = true;
}

private void CrossChainVerify(Hash transactionId, long parentChainHeight, int chainId, MerklePath merklePath)
Expand Down Expand Up @@ -255,8 +256,11 @@
private void CheckTokenExists(string symbol)
{
var empty = new TokenInfo();
var existing = State.TokenInfos[symbol];
// check old token
var existing = State.TokenInfos[symbol.ToUpper()];
Assert(existing == null || existing.Equals(empty), "Token already exists.");
// check new token
Assert(!State.InsensitiveTokenExisting[symbol.ToUpper()], "Token already exists.");
}

private void CheckSymbolLength(string symbol, SymbolType symbolType)
Expand Down
4 changes: 2 additions & 2 deletions contract/AElf.Contracts.MultiToken/TokenContract_NFTHelper.cs
Original file line number Diff line number Diff line change
Expand Up @@ -7,9 +7,9 @@ public partial class TokenContract
private SymbolType GetCreateInputSymbolType(string symbol)
{
var words = symbol.Split(TokenContractConstants.NFTSymbolSeparator);
Assert(words[0].Length > 0 && words[0].All(IsValidCreateSymbolChar), "Invalid Symbol input");
Assert(words[0].Length > 0 && IsValidCreateSymbol(words[0]), "Invalid Symbol input");
if (words.Length == 1) return SymbolType.Token;
Assert(words.Length == 2 && words[1].Length > 0 && words[1].All(IsValidItemIdChar), "Invalid NFT Symbol input");
Assert(words.Length == 2 && words[1].Length > 0 && IsValidItemId(words[1]), "Invalid NFT Symbol input");
return words[1] == TokenContractConstants.CollectionSymbolSuffix ? SymbolType.NftCollection : SymbolType.Nft;
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -48,15 +48,15 @@
CheckSymbolLength(ownedSymbol, ownedSymbolType);
CheckTokenAndCollectionExists(ownedSymbol);
CheckSymbolSeed(ownedSymbol);
State.SymbolSeedMap[ownedSymbol] = input.Symbol;
State.SymbolSeedMap[ownedSymbol.ToUpper()] = input.Symbol;

Check warning on line 51 in contract/AElf.Contracts.MultiToken/TokenContract_NFT_Actions.cs

View check run for this annotation

Codecov / codecov/patch

contract/AElf.Contracts.MultiToken/TokenContract_NFT_Actions.cs#L51

Added line #L51 was not covered by tests
}

return CreateToken(input, SymbolType.Nft);
}

private void CheckSymbolSeed(string ownedSymbol)
{
var oldSymbolSeed = State.SymbolSeedMap[ownedSymbol];
var oldSymbolSeed = State.SymbolSeedMap[ownedSymbol.ToUpper()];

Assert(oldSymbolSeed == null || !State.TokenInfos[oldSymbolSeed].ExternalInfo.Value
.TryGetValue(TokenContractConstants.SeedExpireTimeExternalInfoKey,
Expand Down Expand Up @@ -102,7 +102,7 @@
var words = symbol.Split(TokenContractConstants.NFTSymbolSeparator);
const int tokenSymbolLength = 1;
if (words.Length == tokenSymbolLength) return null;
Assert(words.Length == 2 && words[1].All(IsValidItemIdChar), "Invalid NFT Symbol Input");
Assert(words.Length == 2 && IsValidItemId(words[1]), "Invalid NFT Symbol Input");
return symbol == $"{words[0]}-0" ? null : $"{words[0]}-0";
}

Expand Down
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
using System;
using System.Globalization;
using System.Linq;
using System.Text.RegularExpressions;
using AElf.Contracts.MultiToken;
using AElf.CSharp.Core;
using AElf.Sdk.CSharp;
Expand Down Expand Up @@ -321,8 +322,7 @@

private static bool IsValidSymbol(string symbol)
{
return symbol.Length > 0 &&
symbol.All(c => c >= 'A' && c <= 'Z');
return Regex.IsMatch(symbol, "^[a-zA-Z0-9]+$");

Check warning on line 325 in contract/AElf.Contracts.TokenConverter/TokenConverterContract.cs

View check run for this annotation

Codecov / codecov/patch

contract/AElf.Contracts.TokenConverter/TokenConverterContract.cs#L325

Added line #L325 was not covered by tests
}

private static bool IsValidBaseSymbol(string symbol)
Expand Down
13 changes: 13 additions & 0 deletions src/AElf.CSharp.CodeOps/Validators/Whitelist/IWhitelistProvider.cs
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@
using System.Reflection;
using System.Runtime.CompilerServices;
using System.Text;
using System.Text.RegularExpressions;
using AElf.Cryptography.SecretSharing;
using AElf.CSharp.Core;
using AElf.Kernel.SmartContract;
Expand Down Expand Up @@ -37,6 +38,7 @@
.Assembly(System.Reflection.Assembly.Load("System.Runtime.Extensions"), Trust.Partial)
.Assembly(System.Reflection.Assembly.Load("System.Private.CoreLib"), Trust.Partial)
.Assembly(System.Reflection.Assembly.Load("System.ObjectModel"), Trust.Partial)
.Assembly(System.Reflection.Assembly.Load("System.Text.RegularExpressions"), Trust.Partial)

Check warning on line 41 in src/AElf.CSharp.CodeOps/Validators/Whitelist/IWhitelistProvider.cs

View check run for this annotation

Codecov / codecov/patch

src/AElf.CSharp.CodeOps/Validators/Whitelist/IWhitelistProvider.cs#L41

Added line #L41 was not covered by tests
.Assembly(System.Reflection.Assembly.Load("System.Linq"), Trust.Full)
.Assembly(System.Reflection.Assembly.Load("System.Linq.Expressions"), Trust.Full)
.Assembly(System.Reflection.Assembly.Load("System.Collections"), Trust.Full)
Expand Down Expand Up @@ -142,6 +144,17 @@
.Member(nameof(Encoding.UTF8), Permission.Allowed)
.Member(nameof(Encoding.UTF8.GetByteCount), Permission.Allowed)))
.Namespace("System.Numerics", Permission.Allowed)
.Namespace("System.Text.RegularExpressions", Permission.Denied, type => type
.Type(nameof(Regex), Permission.Denied, member => member
.Member(nameof(Regex.IsMatch), Permission.Allowed)
.Member(nameof(Regex.Match), Permission.Allowed)
.Member(nameof(Regex.Matches), Permission.Allowed)
.Member(nameof(Regex.Replace), Permission.Allowed)
.Member(nameof(Regex.Split), Permission.Allowed)
)
.Type(nameof(MatchCollection), Permission.Allowed)
.Type(nameof(Match), Permission.Allowed)
)

Check warning on line 157 in src/AElf.CSharp.CodeOps/Validators/Whitelist/IWhitelistProvider.cs

View check run for this annotation

Codecov / codecov/patch

src/AElf.CSharp.CodeOps/Validators/Whitelist/IWhitelistProvider.cs#L147-L157

Added lines #L147 - L157 were not covered by tests
;
}

Expand Down
19 changes: 17 additions & 2 deletions test/AElf.Contracts.MultiToken.Tests/BVT/NftApplicationTests.cs
Original file line number Diff line number Diff line change
Expand Up @@ -275,8 +275,23 @@ public async Task MultiTokenContract_Create_NFTCollection_Input_Check_Test()
ExternalInfo = input.ExternalInfo,
Owner = input.Owner
});

var result = await TokenContractStub.Create.SendWithExceptionAsync(seedInput);;

var result = await TokenContractStub.Create.SendAsync(seedInput);
result.TransactionResult.Status.ShouldBe(TransactionResultStatus.Mined);

seedInput = BuildSeedCreateInput(new CreateInput
{
Symbol = "ABC123()",
TokenName = input.TokenName,
TotalSupply = input.TotalSupply,
Decimals = input.Decimals,
Issuer = input.Issuer,
IssueChainId = input.IssueChainId,
ExternalInfo = input.ExternalInfo,
Owner = input.Owner
});

result = await TokenContractStub.Create.SendWithExceptionAsync(seedInput);
result.TransactionResult.Status.ShouldBe(TransactionResultStatus.Failed);
result.TransactionResult.Error.ShouldContain("Invalid Symbol input");
}
Expand Down
23 changes: 23 additions & 0 deletions test/AElf.Contracts.MultiToken.Tests/BVT/SymbolValidationTest.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
using System.Text.RegularExpressions;
using Shouldly;
using Xunit;

namespace AElf.Contracts.MultiToken;

public class SymbolValidationTest
{
private const string RegexPattern = "^[a-zA-Z0-9]+(-[0-9]+)?$";

[Theory]
[InlineData("ELF", true)]
[InlineData("ELF-", false)]
[InlineData("ABC-123", true)]
[InlineData("abc-1", true)]
[InlineData("ABC-ABC", false)]
[InlineData("ABC--", false)]
[InlineData("121-1", true)]
public void SymbolValidation(string symbol, bool isValid)
{
Regex.IsMatch(symbol, RegexPattern).ShouldBe(isValid);
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -87,7 +87,7 @@ await ExecuteProposalForParliamentTransaction(TokenConverterContractAddress,
[Theory]
[InlineData("WRITE", "0.5", "0.5", "resource token symbol has existed")]
[InlineData("", "0.5", "0.5", "resource token symbol should not be empty")]
[InlineData("N89", "0.2", "0.5", "Invalid symbol.")]
[InlineData("N89()", "0.2", "0.5", "Invalid symbol.")]
[InlineData("MKA", "0", "0.5", "Connector Shares has to be a decimal between 0 and 1.")]
[InlineData("JUN", "0.9", "1", "Connector Shares has to be a decimal between 0 and 1.")]
public async Task AddPairConnector_With_Invalid_Input_Test(string tokenSymbol, string resourceWeight,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -84,7 +84,7 @@ public async Task Initialize_Failed_Test()
//Base token symbol is invalid.
{
var input = GetLegalInitializeInput();
input.BaseTokenSymbol = "elf1";
input.BaseTokenSymbol = "elf1<>";
var result = (await DefaultStub.Initialize.SendWithExceptionAsync(input)).TransactionResult;
result.Status.ShouldBe(TransactionResultStatus.Failed);
result.Error.Contains("Base token symbol is invalid.").ShouldBeTrue();
Expand All @@ -111,7 +111,7 @@ public async Task Initialize_Failed_Test()
//Invalid connector symbol
{
var input = GetLegalInitializeInput();
input.Connectors[0].Symbol = "write";
input.Connectors[0].Symbol = "write-0";
var result = (await DefaultStub.Initialize.SendWithExceptionAsync(input)).TransactionResult;
result.Status.ShouldBe(TransactionResultStatus.Failed);
result.Error.Contains("Invalid symbol.").ShouldBeTrue();
Expand Down