diff --git a/contract/AElf.Contracts.MultiToken/TokenContractConstants.cs b/contract/AElf.Contracts.MultiToken/TokenContractConstants.cs
index 8a41b78140..50e0c8c092 100644
--- a/contract/AElf.Contracts.MultiToken/TokenContractConstants.cs
+++ b/contract/AElf.Contracts.MultiToken/TokenContractConstants.cs
@@ -14,6 +14,7 @@ public static class TokenContractConstants
public const string LockCallbackExternalInfoKey = "aelf_lock_callback";
public const string UnlockCallbackExternalInfoKey = "aelf_unlock_callback";
public const string LogEventExternalInfoKey = "aelf_log_event";
+ public const string TokenAliasExternalInfoKey = "aelf_token_alias";
public const int DELEGATEE_MAX_COUNT = 24;
public const char NFTSymbolSeparator = '-';
public const int NFTSymbolMaxLength = 30;
@@ -24,4 +25,6 @@ public static class TokenContractConstants
public const string SeedExpireTimeExternalInfoKey = "__seed_exp_time";
public const string NftCreateChainIdExternalInfoKey = "__nft_create_chain_id";
public const int DefaultMaxBatchApproveCount = 100;
+ public const char AllSymbolIdentifier = '*';
+
}
\ No newline at end of file
diff --git a/contract/AElf.Contracts.MultiToken/TokenContractState.cs b/contract/AElf.Contracts.MultiToken/TokenContractState.cs
index aa6953a530..6627c3625b 100644
--- a/contract/AElf.Contracts.MultiToken/TokenContractState.cs
+++ b/contract/AElf.Contracts.MultiToken/TokenContractState.cs
@@ -8,6 +8,11 @@ public partial class TokenContractState : ContractState
public StringState NativeTokenSymbol { get; set; }
public StringState ChainPrimaryTokenSymbol { get; set; }
+
+ ///
+ /// WARNING: Use GetTokenInfo & SetTokenInfo to operate TokenInfos
+ /// due to token symbol alias feature.
+ ///
public MappedState TokenInfos { get; set; }
public MappedState InsensitiveTokenExisting { get; set; }
public MappedState SymbolSeedMap { get; set; }
@@ -68,4 +73,7 @@ public partial class TokenContractState : ContractState
public SingletonState TokenIssuerAndOwnerModificationDisabled { get; set; }
public SingletonState MaxBatchApproveCount { get; set; }
+
+ // Alias -> Actual Symbol
+ public MappedState SymbolAliasMap { get; set; }
}
\ No newline at end of file
diff --git a/contract/AElf.Contracts.MultiToken/TokenContract_ACS1_MethodFeeProvider.cs b/contract/AElf.Contracts.MultiToken/TokenContract_ACS1_MethodFeeProvider.cs
index 9ef198b39c..e24c2d8acc 100644
--- a/contract/AElf.Contracts.MultiToken/TokenContract_ACS1_MethodFeeProvider.cs
+++ b/contract/AElf.Contracts.MultiToken/TokenContract_ACS1_MethodFeeProvider.cs
@@ -123,9 +123,12 @@ private bool CheckOrganizationExist(AuthorityInfo authorityInfo)
private void AssertValidFeeToken(string symbol, long amount)
{
AssertValidSymbolAndAmount(symbol, amount);
- if (State.TokenInfos[symbol] == null)
+ var tokenInfo = GetTokenInfo(symbol);
+ if (tokenInfo == null)
+ {
throw new AssertionException("Token is not found");
- Assert(State.TokenInfos[symbol].IsBurnable, $"Token {symbol} cannot set as method fee.");
+ }
+ Assert(tokenInfo.IsBurnable, $"Token {symbol} cannot set as method fee.");
}
#endregion
diff --git a/contract/AElf.Contracts.MultiToken/TokenContract_ACS2_StatePathsProvider.cs b/contract/AElf.Contracts.MultiToken/TokenContract_ACS2_StatePathsProvider.cs
index 8d12f604e1..8d013d3180 100644
--- a/contract/AElf.Contracts.MultiToken/TokenContract_ACS2_StatePathsProvider.cs
+++ b/contract/AElf.Contracts.MultiToken/TokenContract_ACS2_StatePathsProvider.cs
@@ -44,8 +44,6 @@ public override ResourceInfo GetResourceInfo(Transaction txn)
{
WritePaths =
{
- GetPath(nameof(TokenContractState.Allowances), args.From.ToString(), txn.From.ToString(),
- args.Symbol),
GetPath(nameof(TokenContractState.Balances), args.From.ToString(), args.Symbol),
GetPath(nameof(TokenContractState.Balances), args.To.ToString(), args.Symbol),
GetPath(nameof(TokenContractState.LockWhiteLists), args.Symbol, txn.From.ToString())
@@ -57,7 +55,7 @@ public override ResourceInfo GetResourceInfo(Transaction txn)
GetPath(nameof(TokenContractState.TransactionFeeFreeAllowancesSymbolList))
}
};
-
+ AddPathForAllowance(resourceInfo, args.From.ToString(), txn.From.ToString(), args.Symbol);
AddPathForTransactionFee(resourceInfo, txn.From.ToString(), txn.MethodName);
AddPathForDelegatees(resourceInfo, txn.From, txn.To, txn.MethodName);
AddPathForTransactionFeeFreeAllowance(resourceInfo, txn.From);
@@ -70,6 +68,18 @@ public override ResourceInfo GetResourceInfo(Transaction txn)
}
}
+ private void AddPathForAllowance(ResourceInfo resourceInfo, string from, string spender, string symbol)
+ {
+ resourceInfo.WritePaths.Add(GetPath(nameof(TokenContractState.Allowances), from, spender, symbol));
+ resourceInfo.WritePaths.Add(GetPath(nameof(TokenContractState.Allowances), from, spender,
+ GetAllSymbolIdentifier()));
+ var symbolType = GetSymbolType(symbol);
+ if (symbolType == SymbolType.Nft || symbolType == SymbolType.NftCollection)
+ {
+ resourceInfo.WritePaths.Add(GetPath(nameof(TokenContractState.Allowances), from, spender,
+ GetNftCollectionAllSymbolIdentifier(symbol)));
+ }
+ }
private void AddPathForTransactionFee(ResourceInfo resourceInfo, string from, string methodName)
{
diff --git a/contract/AElf.Contracts.MultiToken/TokenContract_Actions.cs b/contract/AElf.Contracts.MultiToken/TokenContract_Actions.cs
index 65981eee66..6adcf01a2a 100644
--- a/contract/AElf.Contracts.MultiToken/TokenContract_Actions.cs
+++ b/contract/AElf.Contracts.MultiToken/TokenContract_Actions.cs
@@ -32,7 +32,7 @@ public override Empty InitializeFromParentChain(InitializeFromParentChainInput i
///
public override Empty Create(CreateInput input)
{
- var inputSymbolType = GetCreateInputSymbolType(input.Symbol);
+ var inputSymbolType = GetSymbolType(input.Symbol);
if (input.Owner == null)
{
input.Owner = input.Issuer;
@@ -77,6 +77,14 @@ private Empty CreateToken(CreateInput input, SymbolType symbolType = SymbolType.
ExternalInfo = input.ExternalInfo ?? new ExternalInfo(),
Owner = input.Owner
};
+
+ if (IsAliasSettingExists(tokenInfo))
+ {
+ Assert(symbolType == SymbolType.NftCollection, "Token alias can only be set for NFT Item.");
+ SetTokenAlias(tokenInfo);
+ }
+
+ CheckTokenExists(tokenInfo.Symbol);
RegisterTokenInfo(tokenInfo);
if (string.IsNullOrEmpty(State.NativeTokenSymbol.Value))
{
@@ -110,7 +118,7 @@ private Empty CreateToken(CreateInput input, SymbolType symbolType = SymbolType.
private void CheckSeedNFT(string symbolSeed, String symbol)
{
Assert(!string.IsNullOrEmpty(symbolSeed), "Seed NFT does not exist.");
- var tokenInfo = State.TokenInfos[symbolSeed];
+ var tokenInfo = GetTokenInfo(symbolSeed);
Assert(tokenInfo != null, "Seed NFT does not exist.");
Assert(State.Balances[Context.Sender][symbolSeed] > 0, "Seed NFT balance is not enough.");
Assert(tokenInfo.ExternalInfo != null && tokenInfo.ExternalInfo.Value.TryGetValue(
@@ -131,7 +139,7 @@ private void CheckSeedNFT(string symbolSeed, String symbol)
public override Empty SetPrimaryTokenSymbol(SetPrimaryTokenSymbolInput input)
{
Assert(State.ChainPrimaryTokenSymbol.Value == null, "Failed to set primary token symbol.");
- Assert(!string.IsNullOrWhiteSpace(input.Symbol) && State.TokenInfos[input.Symbol] != null, "Invalid input symbol.");
+ Assert(!string.IsNullOrWhiteSpace(input.Symbol) && GetTokenInfo(input.Symbol) != null, "Invalid input symbol.");
State.ChainPrimaryTokenSymbol.Value = input.Symbol;
Context.Fire(new ChainPrimaryTokenSymbolSet { TokenSymbol = input.Symbol });
@@ -156,7 +164,7 @@ public override Empty Issue(IssueInput input)
tokenInfo.Supply = tokenInfo.Supply.Add(input.Amount);
Assert(tokenInfo.Issued <= tokenInfo.TotalSupply, "Total supply exceeded");
- State.TokenInfos[input.Symbol] = tokenInfo;
+ SetTokenInfo(tokenInfo);
ModifyBalance(input.To, input.Symbol, input.Amount);
Context.Fire(new Issued
@@ -171,14 +179,14 @@ public override Empty Issue(IssueInput input)
public override Empty Transfer(TransferInput input)
{
- AssertValidToken(input.Symbol, input.Amount);
- DoTransfer(Context.Sender, input.To, input.Symbol, input.Amount, input.Memo);
+ var tokenInfo = AssertValidToken(input.Symbol, input.Amount);
+ DoTransfer(Context.Sender, input.To, tokenInfo.Symbol, input.Amount, input.Memo);
DealWithExternalInfoDuringTransfer(new TransferFromInput
{
From = Context.Sender,
To = input.To,
Amount = input.Amount,
- Symbol = input.Symbol,
+ Symbol = tokenInfo.Symbol,
Memo = input.Memo
});
return new Empty();
@@ -245,27 +253,29 @@ public override Empty Unlock(UnlockInput input)
public override Empty TransferFrom(TransferFromInput input)
{
- AssertValidToken(input.Symbol, input.Amount);
- DoTransferFrom(input.From, input.To, Context.Sender, input.Symbol, input.Amount, input.Memo);
+ var tokenInfo = AssertValidToken(input.Symbol, input.Amount);
+ DoTransferFrom(input.From, input.To, Context.Sender, tokenInfo.Symbol, input.Amount, input.Memo);
return new Empty();
}
public override Empty Approve(ApproveInput input)
{
AssertValidInputAddress(input.Spender);
- AssertValidToken(input.Symbol, input.Amount);
- Approve(input.Spender, input.Symbol, input.Amount);
+ var actualSymbol = GetActualTokenSymbol(input.Symbol);
+ AssertValidApproveTokenAndAmount(actualSymbol, input.Amount);
+ Approve(input.Spender, actualSymbol, input.Amount);
return new Empty();
}
private void Approve(Address spender, string symbol, long amount)
{
- State.Allowances[Context.Sender][spender][symbol] = amount;
+ var actualSymbol = GetActualTokenSymbol(symbol);
+ State.Allowances[Context.Sender][spender][actualSymbol] = amount;
Context.Fire(new Approved
{
Owner = Context.Sender,
Spender = spender,
- Symbol = symbol,
+ Symbol = actualSymbol,
Amount = amount
});
}
@@ -277,7 +287,8 @@ public override Empty BatchApprove(BatchApproveInput input)
foreach (var approve in input.Value)
{
AssertValidInputAddress(approve.Spender);
- AssertValidToken(approve.Symbol, approve.Amount);
+ var actualSymbol = GetActualTokenSymbol(approve.Symbol);
+ AssertValidApproveTokenAndAmount(actualSymbol, approve.Amount);
}
var approveInputList = input.Value.GroupBy(approve => approve.Symbol + approve.Spender, approve => approve)
.Select(approve => approve.Last()).ToList();
@@ -289,15 +300,16 @@ public override Empty BatchApprove(BatchApproveInput input)
public override Empty UnApprove(UnApproveInput input)
{
AssertValidInputAddress(input.Spender);
- AssertValidToken(input.Symbol, input.Amount);
- var oldAllowance = State.Allowances[Context.Sender][input.Spender][input.Symbol];
+ var symbol = GetActualTokenSymbol(input.Symbol);
+ AssertValidApproveTokenAndAmount(symbol, input.Amount);
+ var oldAllowance = State.Allowances[Context.Sender][input.Spender][symbol];
var amountOrAll = Math.Min(input.Amount, oldAllowance);
- State.Allowances[Context.Sender][input.Spender][input.Symbol] = oldAllowance.Sub(amountOrAll);
+ State.Allowances[Context.Sender][input.Spender][symbol] = oldAllowance.Sub(amountOrAll);
Context.Fire(new UnApproved
{
Owner = Context.Sender,
Spender = input.Spender,
- Symbol = input.Symbol,
+ Symbol = symbol,
Amount = amountOrAll
});
return new Empty();
@@ -426,7 +438,7 @@ public override Empty TakeResourceTokenBack(TakeResourceTokenBackInput input)
public override Empty ValidateTokenInfoExists(ValidateTokenInfoExistsInput input)
{
Assert(!string.IsNullOrWhiteSpace(input.Symbol), "Invalid input symbol.");
- var tokenInfo = State.TokenInfos[input.Symbol];
+ var tokenInfo = GetTokenInfo(input.Symbol);
if (tokenInfo == null) throw new AssertionException("Token validation failed.");
var validationResult = tokenInfo.TokenName == input.TokenName &&
@@ -489,24 +501,38 @@ public override Empty CrossChainCreateToken(CrossChainCreateTokenInput input)
ExternalInfo = new ExternalInfo { Value = { validateTokenInfoExistsInput.ExternalInfo } },
Owner = validateTokenInfoExistsInput.Owner ?? validateTokenInfoExistsInput.Issuer
};
- RegisterTokenInfo(tokenInfo);
- Context.Fire(new TokenCreated
+
+ var isSymbolAliasSet = SyncSymbolAliasFromTokenInfo(tokenInfo);
+ if (State.TokenInfos[tokenInfo.Symbol] == null)
{
- Symbol = validateTokenInfoExistsInput.Symbol,
- TokenName = validateTokenInfoExistsInput.TokenName,
- TotalSupply = validateTokenInfoExistsInput.TotalSupply,
- Decimals = validateTokenInfoExistsInput.Decimals,
- Issuer = validateTokenInfoExistsInput.Issuer,
- IsBurnable = validateTokenInfoExistsInput.IsBurnable,
- IssueChainId = validateTokenInfoExistsInput.IssueChainId,
- ExternalInfo = new ExternalInfo { Value = { validateTokenInfoExistsInput.ExternalInfo } },
- Owner = tokenInfo.Owner
- });
+ RegisterTokenInfo(tokenInfo);
+ Context.Fire(new TokenCreated
+ {
+ Symbol = validateTokenInfoExistsInput.Symbol,
+ TokenName = validateTokenInfoExistsInput.TokenName,
+ TotalSupply = validateTokenInfoExistsInput.TotalSupply,
+ Decimals = validateTokenInfoExistsInput.Decimals,
+ Issuer = validateTokenInfoExistsInput.Issuer,
+ IsBurnable = validateTokenInfoExistsInput.IsBurnable,
+ IssueChainId = validateTokenInfoExistsInput.IssueChainId,
+ ExternalInfo = new ExternalInfo { Value = { validateTokenInfoExistsInput.ExternalInfo } },
+ Owner = tokenInfo.Owner,
+ });
+ }
+ else
+ {
+ if (isSymbolAliasSet &&
+ validateTokenInfoExistsInput.ExternalInfo.TryGetValue(TokenContractConstants.TokenAliasExternalInfoKey,
+ out var tokenAliasSetting))
+ {
+ State.TokenInfos[tokenInfo.Symbol].ExternalInfo.Value
+ .Add(TokenContractConstants.TokenAliasExternalInfoKey, tokenAliasSetting);
+ }
+ }
return new Empty();
}
-
public override Empty RegisterCrossChainTokenContractAddress(RegisterCrossChainTokenContractAddressInput input)
{
CheckCrossChainTokenContractRegistrationControllerAuthority();
@@ -533,21 +559,21 @@ public override Empty RegisterCrossChainTokenContractAddress(RegisterCrossChainT
///
public override Empty CrossChainTransfer(CrossChainTransferInput input)
{
- AssertValidToken(input.Symbol, input.Amount);
+ var tokenInfo = AssertValidToken(input.Symbol, input.Amount);
AssertValidMemo(input.Memo);
- var issueChainId = GetIssueChainId(input.Symbol);
+ var issueChainId = GetIssueChainId(tokenInfo.Symbol);
Assert(issueChainId == input.IssueChainId, "Incorrect issue chain id.");
var burnInput = new BurnInput
{
Amount = input.Amount,
- Symbol = input.Symbol
+ Symbol = tokenInfo.Symbol
};
Burn(burnInput);
Context.Fire(new CrossChainTransferred
{
From = Context.Sender,
To = input.To,
- Symbol = input.Symbol,
+ Symbol = tokenInfo.Symbol,
Amount = input.Amount,
IssueChainId = input.IssueChainId,
Memo = input.Memo,
@@ -578,28 +604,28 @@ public override Empty CrossChainReceiveToken(CrossChainReceiveTokenInput input)
var transferSender = transferTransaction.From;
var tokenInfo = AssertValidToken(symbol, amount);
- var issueChainId = GetIssueChainId(symbol);
+ var issueChainId = GetIssueChainId(tokenInfo.Symbol);
Assert(issueChainId == crossChainTransferInput.IssueChainId, "Incorrect issue chain id.");
Assert(targetChainId == Context.ChainId, "Unable to claim cross chain token.");
var registeredTokenContractAddress = State.CrossChainTransferWhiteList[input.FromChainId];
AssertCrossChainTransaction(transferTransaction, registeredTokenContractAddress,
nameof(CrossChainTransfer));
Context.LogDebug(() =>
- $"symbol == {symbol}, amount == {amount}, receivingAddress == {receivingAddress}, targetChainId == {targetChainId}");
+ $"symbol == {tokenInfo.Symbol}, amount == {amount}, receivingAddress == {receivingAddress}, targetChainId == {targetChainId}");
CrossChainVerify(transferTransactionId, input.ParentChainHeight, input.FromChainId, input.MerklePath);
State.VerifiedCrossChainTransferTransaction[transferTransactionId] = true;
tokenInfo.Supply = tokenInfo.Supply.Add(amount);
Assert(tokenInfo.Supply <= tokenInfo.TotalSupply, "Total supply exceeded");
- State.TokenInfos[symbol] = tokenInfo;
- ModifyBalance(receivingAddress, symbol, amount);
+ SetTokenInfo(tokenInfo);
+ ModifyBalance(receivingAddress, tokenInfo.Symbol, amount);
Context.Fire(new CrossChainReceived
{
From = transferSender,
To = receivingAddress,
- Symbol = symbol,
+ Symbol = tokenInfo.Symbol,
Amount = amount,
Memo = crossChainTransferInput.Memo,
FromChainId = input.FromChainId,
@@ -619,7 +645,7 @@ public override Empty ModifyTokenIssuerAndOwner(ModifyTokenIssuerAndOwnerInput i
Assert(input.Issuer != null && !input.Issuer.Value.IsNullOrEmpty(), "Invalid input issuer.");
Assert(input.Owner != null && !input.Owner.Value.IsNullOrEmpty(), "Invalid input owner.");
- var tokenInfo = State.TokenInfos[input.Symbol];
+ var tokenInfo = GetTokenInfo(input.Symbol);
Assert(tokenInfo != null, "Token is not found.");
Assert(tokenInfo.Issuer == Context.Sender, "Only token issuer can set token issuer and owner.");
@@ -648,7 +674,7 @@ public override BoolValue GetTokenIssuerAndOwnerModificationEnabled(Empty input)
Value = !State.TokenIssuerAndOwnerModificationDisabled.Value
};
}
-
+
public override Empty SetMaxBatchApproveCount(Int32Value input)
{
Assert(input.Value > 0, "Invalid input.");
@@ -671,4 +697,127 @@ private int GetMaxBatchApproveCount()
? TokenContractConstants.DefaultMaxBatchApproveCount
: State.MaxBatchApproveCount.Value;
}
+
+ ///
+ /// For example:
+ /// Symbol: SGR-1, Alias: SGR
+ /// Symbol: ABC-233, Alias: ABC
+ ///
+ ///
+ ///
+ public override Empty SetSymbolAlias(SetSymbolAliasInput input)
+ {
+ // Alias setting can only work for NFT Item for now.
+ // And the setting exists on the TokenInfo of the NFT Collection.
+
+ // Can only happen on Main Chain.
+ Assert(Context.ChainId == ChainHelper.ConvertBase58ToChainId("AELF"),
+ "Symbol alias setting only works on MainChain.");
+
+ var collectionSymbol = GetNftCollectionSymbol(input.Symbol, true);
+
+ // For now, token alias can only be set once.
+ Assert(State.SymbolAliasMap[input.Alias] == null, $"Token alias {input.Alias} already exists.");
+
+ CheckTokenAlias(input.Alias, collectionSymbol);
+
+ var collectionTokenInfo = GetTokenInfo(collectionSymbol);
+ if (collectionTokenInfo == null)
+ {
+ throw new AssertionException($"NFT Collection {collectionSymbol} not found.");
+ }
+
+ Assert(collectionTokenInfo.Owner == Context.Sender || collectionTokenInfo.Issuer == Context.Sender,
+ "No permission.");
+
+ collectionTokenInfo.ExternalInfo.Value[TokenContractConstants.TokenAliasExternalInfoKey]
+ = $"{{\"{input.Symbol}\":\"{input.Alias}\"}}";
+
+ SetTokenInfo(collectionTokenInfo);
+
+ State.SymbolAliasMap[input.Alias] = input.Symbol;
+
+ Context.LogDebug(() => $"Token alias added: {input.Symbol} -> {input.Alias}");
+
+ Context.Fire(new SymbolAliasAdded
+ {
+ Symbol = input.Symbol,
+ Alias = input.Alias
+ });
+
+ return new Empty();
+ }
+
+ private bool SyncSymbolAliasFromTokenInfo(TokenInfo newTokenInfo)
+ {
+ var maybePreviousTokenInfo = State.TokenInfos[newTokenInfo.Symbol]?.Clone();
+
+ if (maybePreviousTokenInfo != null && IsAliasSettingExists(maybePreviousTokenInfo))
+ {
+ return false;
+ }
+
+ if (IsAliasSettingExists(newTokenInfo))
+ {
+ SetTokenAlias(newTokenInfo);
+ return true;
+ }
+
+ return false;
+ }
+
+ private bool IsAliasSettingExists(TokenInfo tokenInfo)
+ {
+ return tokenInfo.ExternalInfo != null &&
+ tokenInfo.ExternalInfo.Value.Count > 0 &&
+ tokenInfo.ExternalInfo.Value.ContainsKey(TokenContractConstants.TokenAliasExternalInfoKey);
+ }
+
+ ///
+ /// Extract alias setting from ExternalInfo.
+ ///
+ ///
+ /// (Symbol, Alias)
+ private KeyValuePair ExtractAliasSetting(TokenInfo tokenInfo)
+ {
+ if (!tokenInfo.ExternalInfo.Value.ContainsKey(TokenContractConstants.TokenAliasExternalInfoKey))
+ {
+ return new KeyValuePair(string.Empty, string.Empty);
+ }
+
+ var tokenAliasSetting = tokenInfo.ExternalInfo.Value[TokenContractConstants.TokenAliasExternalInfoKey];
+ tokenAliasSetting = tokenAliasSetting.Trim('{', '}');
+ var parts = tokenAliasSetting.Split(':');
+ var key = parts[0].Trim().Trim('\"');
+ var value = parts[1].Trim().Trim('\"');
+ return new KeyValuePair(key, value);
+ }
+
+ private void SetTokenAlias(TokenInfo tokenInfo)
+ {
+ var (symbol, alias) = ExtractAliasSetting(tokenInfo);
+ State.SymbolAliasMap[alias] = symbol;
+
+ CheckTokenAlias(alias, tokenInfo.Symbol);
+
+ Context.Fire(new SymbolAliasAdded
+ {
+ Symbol = symbol,
+ Alias = alias
+ });
+ }
+
+ private void CheckTokenAlias(string alias, string collectionSymbol)
+ {
+ if (collectionSymbol == null)
+ {
+ throw new AssertionException("Token alias can only be set for NFT Item.");
+ }
+
+ // Current Rule: Alias must be the seed name.
+ var parts = collectionSymbol.Split(TokenContractConstants.NFTSymbolSeparator);
+ Assert(parts.Length == 2, $"Incorrect collection symbol: {collectionSymbol}.");
+ Assert(parts.Last() == TokenContractConstants.CollectionSymbolSuffix, "Incorrect collection symbol suffix.");
+ Assert(alias == parts.First(), $"Alias for an item of {collectionSymbol} cannot be {alias}.");
+ }
}
\ No newline at end of file
diff --git a/contract/AElf.Contracts.MultiToken/TokenContract_Fees.cs b/contract/AElf.Contracts.MultiToken/TokenContract_Fees.cs
index 554ca1c480..12743edd91 100644
--- a/contract/AElf.Contracts.MultiToken/TokenContract_Fees.cs
+++ b/contract/AElf.Contracts.MultiToken/TokenContract_Fees.cs
@@ -620,7 +620,6 @@ public override Empty SetSymbolsToPayTxSizeFee(SymbolListToPayTxSizeFee input)
var isPrimaryTokenExist = false;
var symbolList = new List();
var primaryTokenSymbol = GetPrimaryTokenSymbol(new Empty());
- var primaryTokenInfo = State.TokenInfos[primaryTokenSymbol.Value];
Assert(!string.IsNullOrEmpty(primaryTokenSymbol.Value), "primary token does not exist");
foreach (var tokenWeightInfo in input.SymbolsToPayTxSizeFee)
{
@@ -1153,7 +1152,7 @@ private void TransferTransactionFeesToFeeReceiver(string symbol, long totalAmoun
if (totalAmount <= 0) return;
- var tokenInfo = State.TokenInfos[symbol];
+ var tokenInfo = GetTokenInfo(symbol);
if (!tokenInfo.IsBurnable)
{
return;
@@ -1268,7 +1267,7 @@ public override Empty ConfigTransactionFeeFreeAllowances(ConfigTransactionFeeFre
private void ValidateToken(string symbol)
{
Assert(!string.IsNullOrWhiteSpace(symbol), "Invalid input symbol");
- Assert(State.TokenInfos[symbol] != null, $"Symbol {symbol} not exist");
+ Assert(GetTokenInfo(symbol) != null, $"Symbol {symbol} not exist");
}
public override Empty RemoveTransactionFeeFreeAllowancesConfig(RemoveTransactionFeeFreeAllowancesConfigInput input)
@@ -1411,7 +1410,7 @@ private bool IsDelegationEnough(string txSymbol, string baseSymbol, long cost,
private void AssertSymbolToPayTxFeeIsValid(string tokenSymbol, out long totalSupply)
{
- var tokenInfo = State.TokenInfos[tokenSymbol];
+ var tokenInfo = GetTokenInfo(tokenSymbol);
if (tokenInfo == null)
{
throw new AssertionException($"Token is not found. {tokenSymbol}");
diff --git a/contract/AElf.Contracts.MultiToken/TokenContract_Helper.cs b/contract/AElf.Contracts.MultiToken/TokenContract_Helper.cs
index 40616c8973..3a78c60cbe 100644
--- a/contract/AElf.Contracts.MultiToken/TokenContract_Helper.cs
+++ b/contract/AElf.Contracts.MultiToken/TokenContract_Helper.cs
@@ -33,11 +33,51 @@ private bool IsValidCreateSymbol(string symbol)
private TokenInfo AssertValidToken(string symbol, long amount)
{
AssertValidSymbolAndAmount(symbol, amount);
- var tokenInfo = State.TokenInfos[symbol];
+ var tokenInfo = GetTokenInfo(symbol);
Assert(tokenInfo != null && !string.IsNullOrEmpty(tokenInfo.Symbol), $"Token is not found. {symbol}");
return tokenInfo;
}
+ private void AssertValidApproveTokenAndAmount(string symbol, long amount)
+ {
+ Assert(amount > 0, "Invalid amount.");
+ AssertApproveToken(symbol);
+ }
+
+ private void ValidTokenExists(string symbol)
+ {
+ var tokenInfo = State.TokenInfos[symbol];
+ Assert(tokenInfo != null && !string.IsNullOrEmpty(tokenInfo.Symbol),
+ $"Token is not found. {symbol}");
+ }
+
+ private void AssertApproveToken(string symbol)
+ {
+ Assert(!string.IsNullOrEmpty(symbol), "Symbol can not be null.");
+ var words = symbol.Split(TokenContractConstants.NFTSymbolSeparator);
+ var symbolPrefix = words[0];
+ var allSymbolIdentifier = GetAllSymbolIdentifier();
+ Assert(symbolPrefix.Length > 0 && (IsValidCreateSymbol(symbolPrefix) || symbolPrefix.Equals(allSymbolIdentifier)), "Invalid symbol.");
+ if (words.Length == 1)
+ {
+ if (!symbolPrefix.Equals(allSymbolIdentifier))
+ {
+ ValidTokenExists(symbolPrefix);
+ }
+ return;
+ }
+ Assert(words.Length == 2, "Invalid symbol length.");
+ var itemId = words[1];
+ Assert(itemId.Length > 0 && (IsValidItemId(itemId) || itemId.Equals(allSymbolIdentifier)), "Invalid NFT Symbol.");
+ var nftSymbol = itemId.Equals(allSymbolIdentifier) ? GetCollectionSymbol(symbolPrefix) : symbol;
+ ValidTokenExists(nftSymbol);
+ }
+
+ private string GetCollectionSymbol(string symbolPrefix)
+ {
+ return $"{symbolPrefix}-{TokenContractConstants.CollectionSymbolSuffix}";
+ }
+
private void AssertValidSymbolAndAmount(string symbol, long amount)
{
Assert(!string.IsNullOrEmpty(symbol) && IsValidSymbol(symbol),
@@ -122,13 +162,12 @@ private List GetSymbolListSortedByExpirationTime(TransactionFeeFreeAllow
fromAddress][t]).Seconds).ToList();
}
-
private long GetBalance(Address address, string symbol)
{
AssertValidInputAddress(address);
- Assert(!string.IsNullOrWhiteSpace(symbol), "Invalid symbol.");
-
- return State.Balances[address][symbol];
+ var actualSymbol = GetActualTokenSymbol(symbol);
+ Assert(!string.IsNullOrWhiteSpace(actualSymbol), "Invalid symbol.");
+ return State.Balances[address][actualSymbol];
}
// private MethodFeeFreeAllowance GetFreeFeeAllowance(MethodFeeFreeAllowances freeAllowances, string symbol)
@@ -183,7 +222,6 @@ private void AssertCrossChainTransaction(Transaction originalTransaction, Addres
private void RegisterTokenInfo(TokenInfo tokenInfo)
{
- CheckTokenExists(tokenInfo.Symbol);
Assert(!string.IsNullOrEmpty(tokenInfo.Symbol) && IsValidSymbol(tokenInfo.Symbol),
"Invalid symbol.");
Assert(!string.IsNullOrEmpty(tokenInfo.TokenName), "Token name can neither be null nor empty.");
@@ -226,7 +264,7 @@ private AuthorityInfo GetCrossChainTokenContractRegistrationController()
private int GetIssueChainId(string symbol)
{
- var tokenInfo = State.TokenInfos[symbol];
+ var tokenInfo = GetTokenInfo(symbol);
return tokenInfo.IssueChainId;
}
@@ -257,7 +295,7 @@ private void CheckTokenExists(string symbol)
{
var empty = new TokenInfo();
// check old token
- var existing = State.TokenInfos[symbol.ToUpper()];
+ var existing = GetTokenInfo(symbol);
Assert(existing == null || existing.Equals(empty), "Token already exists.");
// check new token
Assert(!State.InsensitiveTokenExisting[symbol.ToUpper()], "Token already exists.");
@@ -282,7 +320,7 @@ private void CheckCrossChainTokenContractRegistrationControllerAuthority()
private void DealWithExternalInfoDuringLocking(TransferFromInput input)
{
- var tokenInfo = State.TokenInfos[input.Symbol];
+ var tokenInfo = GetTokenInfo(input.Symbol);
if (tokenInfo.ExternalInfo == null) return;
if (tokenInfo.ExternalInfo.Value.ContainsKey(TokenContractConstants.LockCallbackExternalInfoKey))
{
@@ -297,7 +335,7 @@ private void DealWithExternalInfoDuringLocking(TransferFromInput input)
private void DealWithExternalInfoDuringTransfer(TransferFromInput input)
{
- var tokenInfo = State.TokenInfos[input.Symbol];
+ var tokenInfo = GetTokenInfo(input.Symbol);
if (tokenInfo.ExternalInfo == null) return;
if (tokenInfo.ExternalInfo.Value.ContainsKey(TokenContractConstants.TransferCallbackExternalInfoKey))
{
@@ -312,7 +350,7 @@ private void DealWithExternalInfoDuringTransfer(TransferFromInput input)
private void DealWithExternalInfoDuringUnlock(TransferFromInput input)
{
- var tokenInfo = State.TokenInfos[input.Symbol];
+ var tokenInfo = GetTokenInfo(input.Symbol);
if (tokenInfo.ExternalInfo == null) return;
if (tokenInfo.ExternalInfo.Value.ContainsKey(TokenContractConstants.UnlockCallbackExternalInfoKey))
{
@@ -362,4 +400,23 @@ private Address GetVoteContractAddress()
return State.VoteContractAddress.Value;
}
+
+ private TokenInfo GetTokenInfo(string symbolOrAlias)
+ {
+ var tokenInfo = State.TokenInfos[symbolOrAlias];
+ if (tokenInfo != null) return tokenInfo;
+ var actualTokenSymbol = State.SymbolAliasMap[symbolOrAlias];
+ if (!string.IsNullOrEmpty(actualTokenSymbol))
+ {
+ tokenInfo = State.TokenInfos[actualTokenSymbol];
+ }
+
+ return tokenInfo;
+ }
+
+ private void SetTokenInfo(TokenInfo tokenInfo)
+ {
+ var symbol = tokenInfo.Symbol;
+ State.TokenInfos[symbol] = tokenInfo;
+ }
}
\ No newline at end of file
diff --git a/contract/AElf.Contracts.MultiToken/TokenContract_NFTHelper.cs b/contract/AElf.Contracts.MultiToken/TokenContract_NFTHelper.cs
index 63747f2784..ae01062131 100644
--- a/contract/AElf.Contracts.MultiToken/TokenContract_NFTHelper.cs
+++ b/contract/AElf.Contracts.MultiToken/TokenContract_NFTHelper.cs
@@ -4,7 +4,7 @@ namespace AElf.Contracts.MultiToken;
public partial class TokenContract
{
- private SymbolType GetCreateInputSymbolType(string symbol)
+ private SymbolType GetSymbolType(string symbol)
{
var words = symbol.Split(TokenContractConstants.NFTSymbolSeparator);
Assert(words[0].Length > 0 && IsValidCreateSymbol(words[0]), "Invalid Symbol input");
diff --git a/contract/AElf.Contracts.MultiToken/TokenContract_NFT_Actions.cs b/contract/AElf.Contracts.MultiToken/TokenContract_NFT_Actions.cs
index 24fc1fc3ef..93d54ec7b3 100644
--- a/contract/AElf.Contracts.MultiToken/TokenContract_NFT_Actions.cs
+++ b/contract/AElf.Contracts.MultiToken/TokenContract_NFT_Actions.cs
@@ -43,7 +43,7 @@ private Empty CreateNFTInfo(CreateInput input)
out var expirationTime)
&& long.TryParse(expirationTime, out var expirationTimeLong) &&
Context.CurrentBlockTime.Seconds <= expirationTimeLong, "Invalid ownedSymbol.");
- var ownedSymbolType = GetCreateInputSymbolType(ownedSymbol);
+ var ownedSymbolType = GetSymbolType(ownedSymbol);
Assert(ownedSymbolType != SymbolType.Nft, "Invalid OwnedSymbol.");
CheckSymbolLength(ownedSymbol, ownedSymbolType);
CheckTokenAndCollectionExists(ownedSymbol);
@@ -58,7 +58,7 @@ private void CheckSymbolSeed(string ownedSymbol)
{
var oldSymbolSeed = State.SymbolSeedMap[ownedSymbol.ToUpper()];
- Assert(oldSymbolSeed == null || !State.TokenInfos[oldSymbolSeed].ExternalInfo.Value
+ Assert(oldSymbolSeed == null || !GetTokenInfo(oldSymbolSeed).ExternalInfo.Value
.TryGetValue(TokenContractConstants.SeedExpireTimeExternalInfoKey,
out var oldSymbolSeedExpireTime) ||
!long.TryParse(oldSymbolSeedExpireTime, out var symbolSeedExpireTime)
@@ -66,14 +66,13 @@ private void CheckSymbolSeed(string ownedSymbol)
"OwnedSymbol has been created");
}
-
private void DoTransferFrom(Address from, Address to, Address spender, string symbol, long amount, string memo)
{
AssertValidInputAddress(from);
AssertValidInputAddress(to);
// First check allowance.
- var allowance = State.Allowances[from][spender][symbol];
+ var allowance = GetAllowance(from, spender, symbol, amount, out var allowanceSymbol);
if (allowance < amount)
{
if (IsInWhiteList(new IsInWhiteListInput { Symbol = symbol, Address = spender }).Value)
@@ -92,25 +91,80 @@ private void DoTransferFrom(Address from, Address to, Address spender, string sy
DoTransfer(from, to, symbol, amount, memo);
DealWithExternalInfoDuringTransfer(new TransferFromInput()
{ From = from, To = to, Symbol = symbol, Amount = amount, Memo = memo });
- State.Allowances[from][spender][symbol] = allowance.Sub(amount);
+ State.Allowances[from][spender][allowanceSymbol] = allowance.Sub(amount);
}
+ private long GetAllowance(Address from, Address spender, string sourceSymbol, long amount,
+ out string allowanceSymbol)
+ {
+ allowanceSymbol = sourceSymbol;
+ var allowance = State.Allowances[from][spender][sourceSymbol];
+ if (allowance >= amount) return allowance;
+ var tokenType = GetSymbolType(sourceSymbol);
+ if (tokenType == SymbolType.Token)
+ {
+ allowance = GetAllSymbolAllowance(from, spender, out allowanceSymbol);
+ }
+ else
+ {
+ allowance = GetNftCollectionAllSymbolAllowance(from, spender, sourceSymbol, out allowanceSymbol);
+ if (allowance >= amount) return allowance;
+ allowance = GetAllSymbolAllowance(from, spender, out allowanceSymbol);
+ }
+
+ return allowance;
+ }
+
+
+ private long GetAllSymbolAllowance(Address from, Address spender, out string allowanceSymbol)
+ {
+ allowanceSymbol = GetAllSymbolIdentifier();
+ return State.Allowances[from][spender][allowanceSymbol];
+ }
+
+ private long GetNftCollectionAllSymbolAllowance(Address from, Address spender, string sourceSymbol,
+ out string allowanceSymbol)
+ {
+ allowanceSymbol = GetNftCollectionAllSymbolIdentifier(sourceSymbol);
+ return State.Allowances[from][spender][allowanceSymbol];
+ }
+
+ private string GetNftCollectionAllSymbolIdentifier(string sourceSymbol)
+ {
+ // "AAA-*"
+ return $"{sourceSymbol.Split(TokenContractConstants.NFTSymbolSeparator)[0]}-{TokenContractConstants.AllSymbolIdentifier}";
+ }
+
+ private string GetAllSymbolIdentifier()
+ {
+ // "*"
+ return TokenContractConstants.AllSymbolIdentifier.ToString();
+ }
- private string GetNftCollectionSymbol(string inputSymbol)
+ ///
+ /// ELF -> null
+ /// NFT-1 -> NFT-0
+ /// If isAllowCollection == true: NFT-0 -> NFT-0
+ /// If isAllowCollection == false: NFT-0 -> null
+ ///
+ ///
+ ///
+ /// Return null if inputSymbol is not NFT.
+ private string GetNftCollectionSymbol(string inputSymbol, bool isAllowCollection = false)
{
var symbol = inputSymbol;
var words = symbol.Split(TokenContractConstants.NFTSymbolSeparator);
const int tokenSymbolLength = 1;
if (words.Length == tokenSymbolLength) return null;
Assert(words.Length == 2 && IsValidItemId(words[1]), "Invalid NFT Symbol Input");
- return symbol == $"{words[0]}-0" ? null : $"{words[0]}-0";
+ return symbol == $"{words[0]}-0" ? (isAllowCollection ? $"{words[0]}-0" : null) : $"{words[0]}-0";
}
private TokenInfo AssertNftCollectionExist(string symbol)
{
var collectionSymbol = GetNftCollectionSymbol(symbol);
if (collectionSymbol == null) return null;
- var collectionInfo = State.TokenInfos[collectionSymbol];
+ var collectionInfo = GetTokenInfo(collectionSymbol);
Assert(collectionInfo != null, "NFT collection not exist");
return collectionInfo;
}
diff --git a/contract/AElf.Contracts.MultiToken/TokenContract_Views.cs b/contract/AElf.Contracts.MultiToken/TokenContract_Views.cs
index 8366261ebf..01eb2bd7be 100644
--- a/contract/AElf.Contracts.MultiToken/TokenContract_Views.cs
+++ b/contract/AElf.Contracts.MultiToken/TokenContract_Views.cs
@@ -1,4 +1,5 @@
-using System.Linq;
+using System;
+using System.Linq;
using AElf.Sdk.CSharp;
using AElf.Types;
using Google.Protobuf.WellKnownTypes;
@@ -10,12 +11,12 @@ public partial class TokenContract
[View]
public override TokenInfo GetTokenInfo(GetTokenInfoInput input)
{
- return State.TokenInfos[input.Symbol];
+ return GetTokenInfo(input.Symbol);
}
public override TokenInfo GetNativeTokenInfo(Empty input)
{
- return State.TokenInfos[State.NativeTokenSymbol.Value];
+ return GetTokenInfo(State.NativeTokenSymbol.Value);
}
public override TokenInfoList GetResourceTokenInfo(Empty input)
@@ -23,13 +24,13 @@ public override TokenInfoList GetResourceTokenInfo(Empty input)
var tokenInfoList = new TokenInfoList();
foreach (var symbol in Context.Variables.GetStringArray(TokenContractConstants.PayTxFeeSymbolListName)
.Where(symbol =>
- State.TokenInfos[symbol] != null))
- tokenInfoList.Value.Add(State.TokenInfos[symbol]);
+ GetTokenInfo(symbol) != null))
+ tokenInfoList.Value.Add(GetTokenInfo(symbol));
foreach (var symbol in Context.Variables.GetStringArray(TokenContractConstants.PayRentalSymbolListName)
.Where(symbol =>
- State.TokenInfos[symbol] != null))
- tokenInfoList.Value.Add(State.TokenInfos[symbol]);
+ GetTokenInfo(symbol) != null))
+ tokenInfoList.Value.Add(GetTokenInfo(symbol));
return tokenInfoList;
}
@@ -37,26 +38,61 @@ public override TokenInfoList GetResourceTokenInfo(Empty input)
[View]
public override GetBalanceOutput GetBalance(GetBalanceInput input)
{
+ var symbol = GetActualTokenSymbol(input.Symbol);
return new GetBalanceOutput
{
Symbol = input.Symbol,
Owner = input.Owner,
- Balance = GetBalance(input.Owner, input.Symbol)
+ Balance = GetBalance(input.Owner, symbol)
};
}
-
+
[View]
public override GetAllowanceOutput GetAllowance(GetAllowanceInput input)
{
+ var symbol = GetActualTokenSymbol(input.Symbol);
return new GetAllowanceOutput
+ {
+ Symbol = symbol,
+ Owner = input.Owner,
+ Spender = input.Spender,
+ Allowance = State.Allowances[input.Owner][input.Spender][symbol]
+ };
+ }
+
+ [View]
+ public override GetAllowanceOutput GetAvailableAllowance(GetAllowanceInput input)
+ {
+ var result = new GetAllowanceOutput
{
Symbol = input.Symbol,
Owner = input.Owner,
Spender = input.Spender,
- Allowance = State.Allowances[input.Owner][input.Spender][input.Symbol]
};
+ var symbol = input.Symbol;
+ var allowance = State.Allowances[input.Owner][input.Spender][symbol];
+ if (CheckSymbolIdentifier(symbol))
+ {
+ result.Allowance = allowance;
+ return result;
+ }
+ var symbolType = GetSymbolType(symbol);
+ allowance = Math.Max(allowance, GetAllSymbolAllowance(input.Owner,input.Spender,out _));
+ if (symbolType == SymbolType.Nft || symbolType == SymbolType.NftCollection)
+ {
+ allowance = Math.Max(allowance, GetNftCollectionAllSymbolAllowance(input.Owner, input.Spender, symbol, out _));
+ }
+ result.Allowance = allowance;
+ return result;
}
+ private bool CheckSymbolIdentifier(string symbol)
+ {
+ var words = symbol.Split(TokenContractConstants.NFTSymbolSeparator);
+ var allSymbolIdentifier = GetAllSymbolIdentifier();
+ return words[0].Equals(allSymbolIdentifier) || (words.Length > 1 && words[1].Equals(allSymbolIdentifier));
+ }
+
public override BoolValue IsInWhiteList(IsInWhiteListInput input)
{
return new BoolValue { Value = State.LockWhiteLists[input.Symbol][input.Address] };
@@ -199,7 +235,6 @@ public override BoolValue IsTokenAvailableForMethodFee(StringValue input)
};
}
-
public override StringList GetReservedExternalInfoKeyList(Empty input)
{
return new StringList
@@ -216,7 +251,7 @@ public override StringList GetReservedExternalInfoKeyList(Empty input)
private bool IsTokenAvailableForMethodFee(string symbol)
{
- var tokenInfo = State.TokenInfos[symbol];
+ var tokenInfo = GetTokenInfo(symbol);
if (tokenInfo == null) throw new AssertionException("Token is not found.");
return tokenInfo.IsBurnable;
}
@@ -228,4 +263,33 @@ private bool IsAddressInCreateWhiteList(Address address)
address == Context.GetContractAddressByName(SmartContractConstants.EconomicContractSystemName) ||
address == Context.GetContractAddressByName(SmartContractConstants.CrossChainContractSystemName);
}
+
+ public override StringValue GetTokenAlias(StringValue input)
+ {
+ var collectionSymbol = GetNftCollectionSymbol(input.Value, true);
+ var tokenInfo = GetTokenInfo(collectionSymbol);
+ var (_, alias) = ExtractAliasSetting(tokenInfo);
+ return new StringValue
+ {
+ Value = alias
+ };
+ }
+
+ public override StringValue GetSymbolByAlias(StringValue input)
+ {
+ return new StringValue
+ {
+ Value = GetActualTokenSymbol(input.Value)
+ };
+ }
+
+ private string GetActualTokenSymbol(string aliasOrSymbol)
+ {
+ if (State.TokenInfos[aliasOrSymbol] == null)
+ {
+ return State.SymbolAliasMap[aliasOrSymbol] ?? aliasOrSymbol;
+ }
+
+ return aliasOrSymbol;
+ }
}
\ No newline at end of file
diff --git a/protobuf/token_contract.proto b/protobuf/token_contract.proto
index e1174b8b45..9931b680a3 100644
--- a/protobuf/token_contract.proto
+++ b/protobuf/token_contract.proto
@@ -144,6 +144,9 @@ service TokenContract {
rpc RemoveTransactionFeeDelegatee (RemoveTransactionFeeDelegateeInput) returns (google.protobuf.Empty){
}
+ rpc SetSymbolAlias (SetSymbolAliasInput) returns (google.protobuf.Empty){
+ }
+
// Get all delegatees' address of delegator from input
rpc GetTransactionFeeDelegatees (GetTransactionFeeDelegateesInput) returns (GetTransactionFeeDelegateesOutput) {
option (aelf.is_view) = true;
@@ -174,6 +177,11 @@ service TokenContract {
option (aelf.is_view) = true;
}
+ // Query the account's available allowance for other addresses
+ rpc GetAvailableAllowance (GetAllowanceInput) returns (GetAllowanceOutput) {
+ option (aelf.is_view) = true;
+ }
+
// Check whether the token is in the whitelist of an address,
// which can be called TransferFrom to transfer the token under the condition of not being credited.
rpc IsInWhiteList (IsInWhiteListInput) returns (google.protobuf.BoolValue) {
@@ -229,6 +237,14 @@ service TokenContract {
rpc GetTransactionFeeDelegationsOfADelegatee(GetTransactionFeeDelegationsOfADelegateeInput) returns(TransactionFeeDelegations){
option (aelf.is_view) = true;
}
+
+ rpc GetTokenAlias (google.protobuf.StringValue) returns (google.protobuf.StringValue) {
+ option (aelf.is_view) = true;
+ }
+
+ rpc GetSymbolByAlias (google.protobuf.StringValue) returns (google.protobuf.StringValue) {
+ option (aelf.is_view) = true;
+ }
}
message TokenInfo {
@@ -689,6 +705,11 @@ message GetTransactionFeeDelegateesOutput {
repeated aelf.Address delegatee_addresses = 1;
}
+message SetSymbolAliasInput {
+ string symbol = 1;
+ string alias = 2;
+}
+
// Events
message Transferred {
@@ -858,4 +879,16 @@ message TransactionFeeDelegationCancelled {
aelf.Address delegator = 1 [(aelf.is_indexed) = true];
aelf.Address delegatee = 2 [(aelf.is_indexed) = true];
aelf.Address caller = 3 [(aelf.is_indexed) = true];
+}
+
+message SymbolAliasAdded {
+ option (aelf.is_event) = true;
+ string symbol = 1 [(aelf.is_indexed) = true];
+ string alias = 2 [(aelf.is_indexed) = true];
+}
+
+message SymbolAliasDeleted {
+ option (aelf.is_event) = true;
+ string symbol = 1 [(aelf.is_indexed) = true];
+ string alias = 2 [(aelf.is_indexed) = true];
}
\ No newline at end of file
diff --git a/test/AElf.Contracts.MultiToken.Tests/BVT/ACS2_TokenResourceTests.cs b/test/AElf.Contracts.MultiToken.Tests/BVT/ACS2_TokenResourceTests.cs
index 35f84f7154..8729acf7c1 100644
--- a/test/AElf.Contracts.MultiToken.Tests/BVT/ACS2_TokenResourceTests.cs
+++ b/test/AElf.Contracts.MultiToken.Tests/BVT/ACS2_TokenResourceTests.cs
@@ -48,6 +48,24 @@ public async Task ACS2_GetResourceInfo_TransferFrom_Test()
result.NonParallelizable.ShouldBeFalse();
result.WritePaths.Count.ShouldBeGreaterThan(0);
}
+
+ [Fact]
+ public async Task ACS2_GetResourceInfo_TransferFrom_NFT_Test()
+ {
+ var transaction = GenerateTokenTransaction(Accounts[0].Address, nameof(TokenContractStub.TransferFrom),
+ new TransferFromInput
+ {
+ Amount = 100,
+ Symbol = "ABC-1",
+ From = Accounts[1].Address,
+ To = Accounts[2].Address,
+ Memo = "Test get resource"
+ });
+
+ var result = await Acs2BaseStub.GetResourceInfo.CallAsync(transaction);
+ result.NonParallelizable.ShouldBeFalse();
+ result.WritePaths.Count.ShouldBeGreaterThan(0);
+ }
private async Task GetDefaultParliamentAddressAsync()
{
@@ -179,7 +197,7 @@ await TokenContractStubDelegate3.SetTransactionFeeDelegateInfos.SendAsync(new Se
var result = await Acs2BaseStub.GetResourceInfo.CallAsync(transaction);
result.NonParallelizable.ShouldBeFalse();
- result.WritePaths.Count.ShouldBe(9);
+ result.WritePaths.Count.ShouldBe(10);
}
[Fact]
diff --git a/test/AElf.Contracts.MultiToken.Tests/BVT/TokenAliasTests.cs b/test/AElf.Contracts.MultiToken.Tests/BVT/TokenAliasTests.cs
new file mode 100644
index 0000000000..d95e92816a
--- /dev/null
+++ b/test/AElf.Contracts.MultiToken.Tests/BVT/TokenAliasTests.cs
@@ -0,0 +1,379 @@
+using System.Threading.Tasks;
+using AElf.Types;
+using Google.Protobuf.WellKnownTypes;
+using Shouldly;
+using Xunit;
+
+namespace AElf.Contracts.MultiToken;
+
+public partial class MultiTokenContractTests
+{
+ public const string TokenAliasExternalInfoKey = "aelf_token_alias";
+
+ [Fact]
+ public async Task SetTokenAlias_NFTCollection_Test()
+ {
+ var symbols = await CreateNftCollectionAndNft();
+ await TokenContractStub.SetSymbolAlias.SendAsync(new SetSymbolAliasInput
+ {
+ Symbol = symbols[1],
+ Alias = "TP"
+ });
+
+ {
+ // Check TokenInfo of NFT Collection.
+ var tokenInfo = await TokenContractStub.GetTokenInfo.CallAsync(new GetTokenInfoInput
+ {
+ Symbol = symbols[0]
+ });
+ tokenInfo.ExternalInfo.Value.ContainsKey(TokenAliasExternalInfoKey);
+ tokenInfo.ExternalInfo.Value[TokenAliasExternalInfoKey].ShouldBe("{\"TP-31175\":\"TP\"}");
+ }
+
+ {
+ // Check TokenInfo of NFT Item.
+ var tokenInfo = await TokenContractStub.GetTokenInfo.CallAsync(new GetTokenInfoInput
+ {
+ Symbol = "TP"
+ });
+ tokenInfo.Symbol.ShouldBe(symbols[1]);
+ }
+
+ {
+ // Check alias.
+ var alias = await TokenContractStub.GetTokenAlias.CallAsync(new StringValue { Value = "TP-31175" });
+ alias.Value.ShouldBe("TP");
+ }
+
+ {
+ var alias = await TokenContractStub.GetSymbolByAlias.CallAsync(new StringValue { Value = "TP" });
+ alias.Value.ShouldBe("TP-31175");
+ }
+ }
+
+ [Fact]
+ public async Task SetTokenAlias_NFTCollection_CollectionSymbol_Test()
+ {
+ await CreateNftCollectionAndNft();
+ await TokenContractStub.SetSymbolAlias.SendAsync(new SetSymbolAliasInput
+ {
+ Symbol = "TP-0",
+ Alias = "TP"
+ });
+
+ {
+ // Check TokenInfo of NFT Collection.
+ var tokenInfo = await TokenContractStub.GetTokenInfo.CallAsync(new GetTokenInfoInput
+ {
+ Symbol = "TP-0"
+ });
+ tokenInfo.ExternalInfo.Value.ContainsKey(TokenAliasExternalInfoKey);
+ tokenInfo.ExternalInfo.Value[TokenAliasExternalInfoKey].ShouldBe("{\"TP-0\":\"TP\"}");
+ }
+
+ {
+ // Check TokenInfo of NFT Item.
+ var tokenInfo = await TokenContractStub.GetTokenInfo.CallAsync(new GetTokenInfoInput
+ {
+ Symbol = "TP"
+ });
+ tokenInfo.Symbol.ShouldBe("TP-0");
+ }
+
+ {
+ // Check alias.
+ var alias = await TokenContractStub.GetTokenAlias.CallAsync(new StringValue { Value = "TP-0" });
+ alias.Value.ShouldBe("TP");
+ }
+
+ {
+ var alias = await TokenContractStub.GetSymbolByAlias.CallAsync(new StringValue { Value = "TP" });
+ alias.Value.ShouldBe("TP-0");
+ }
+ }
+
+ [Fact]
+ public async Task SetTokenAlias_FT_Test()
+ {
+ await CreateNormalTokenAsync();
+
+ // Set token alias for FT.
+ var result = await TokenContractStub.SetSymbolAlias.SendWithExceptionAsync(new SetSymbolAliasInput
+ {
+ Symbol = AliceCoinTokenInfo.Symbol,
+ });
+ result.TransactionResult.Error.ShouldContain("Token alias can only be set for NFT Item.");
+ }
+
+ [Fact]
+ public async Task CreateTokenWithAlias_Test()
+ {
+ var createCollectionResult = await CreateNftCollectionAsync(NftCollection1155WithAliasInfo);
+ createCollectionResult.TransactionResult.Status.ShouldBe(TransactionResultStatus.Mined);
+
+ await CreateNftAsync(NftCollection1155WithAliasInfo.Symbol, Nft721Info);
+
+ {
+ // Check alias.
+ var alias = await TokenContractStub.GetTokenAlias.CallAsync(new StringValue { Value = "TP-31175" });
+ alias.Value.ShouldBe("TP");
+ }
+
+ {
+ // Check TokenInfo of NFT Item.
+ var tokenInfo = await TokenContractStub.GetTokenInfo.CallAsync(new GetTokenInfoInput
+ {
+ Symbol = "TP"
+ });
+ tokenInfo.Symbol.ShouldBe("TP-31175");
+ }
+ }
+
+ [Fact]
+ public async Task CreateTokenWithAlias_FT_Test()
+ {
+ var createInput = new CreateInput
+ {
+ Symbol = AliceCoinTokenInfo.Symbol,
+ TokenName = AliceCoinTokenInfo.TokenName,
+ TotalSupply = AliceCoinTokenInfo.TotalSupply,
+ Decimals = AliceCoinTokenInfo.Decimals,
+ Issuer = AliceCoinTokenInfo.Issuer,
+ Owner = AliceCoinTokenInfo.Issuer,
+ IsBurnable = AliceCoinTokenInfo.IsBurnable,
+ LockWhiteList =
+ {
+ BasicFunctionContractAddress,
+ OtherBasicFunctionContractAddress,
+ TokenConverterContractAddress,
+ TreasuryContractAddress
+ },
+ ExternalInfo = new ExternalInfo
+ {
+ Value =
+ {
+ { TokenAliasExternalInfoKey, "{\"ALICE-111\":\"ALICE\"}" }
+ }
+ }
+ };
+ await CreateSeedNftAsync(TokenContractStub, createInput);
+ var result = await TokenContractStub.Create.SendWithExceptionAsync(createInput);
+ result.TransactionResult.Error.ShouldContain("Token alias can only be set for NFT Item.");
+ }
+
+ [Fact]
+ public async Task TransferViaAlias_Test()
+ {
+ await CreateTokenWithAlias_Test();
+
+ await TokenContractStub.Issue.SendAsync(new IssueInput
+ {
+ Symbol = "TP-31175",
+ Amount = 1,
+ To = DefaultAddress
+ });
+
+ {
+ var balance = await TokenContractStub.GetBalance.CallAsync(new GetBalanceInput
+ {
+ Owner = DefaultAddress,
+ Symbol = "TP"
+ });
+ balance.Balance.ShouldBe(1);
+ }
+
+ await TokenContractStub.Transfer.SendAsync(new TransferInput
+ {
+ // Transfer via alias.
+ Symbol = "TP",
+ Amount = 1,
+ To = User1Address
+ });
+
+ {
+ var balance = await TokenContractStub.GetBalance.CallAsync(new GetBalanceInput
+ {
+ Owner = User1Address,
+ Symbol = "TP"
+ });
+ balance.Balance.ShouldBe(1);
+ }
+ }
+
+ [Fact]
+ public async Task ApproveAndTransferFromViaAlias_Test()
+ {
+ await CreateTokenWithAlias_Test();
+
+ await TokenContractStub.Issue.SendAsync(new IssueInput
+ {
+ Symbol = "TP-31175",
+ Amount = 1,
+ To = DefaultAddress
+ });
+
+ await TokenContractStub.Approve.SendAsync(new ApproveInput
+ {
+ Symbol = "TP",
+ Amount = 1,
+ Spender = User1Address
+ });
+
+ await TokenContractStubUser.TransferFrom.SendAsync(new TransferFromInput
+ {
+ Symbol = "TP",
+ Amount = 1,
+ From = DefaultAddress,
+ To = User2Address,
+ });
+
+ {
+ var balance = await TokenContractStub.GetBalance.CallAsync(new GetBalanceInput
+ {
+ Owner = User2Address,
+ Symbol = "TP"
+ });
+ balance.Balance.ShouldBe(1);
+ }
+ }
+
+ [Fact]
+ public async Task GetBalanceOfNotExistToken_Test()
+ {
+ var balance = await TokenContractStub.GetBalance.CallAsync(new GetBalanceInput
+ {
+ Owner = User2Address,
+ Symbol = "TP"
+ });
+ balance.Balance.ShouldBe(0);
+ }
+
+ [Fact]
+ public async Task GetAllowanceOfNotExistToken_Test()
+ {
+ var allowance = await TokenContractStub.GetAllowance.CallAsync(new GetAllowanceInput
+ {
+ Owner = User2Address,
+ Symbol = "TP",
+ Spender = DefaultAddress
+ });
+ allowance.Allowance.ShouldBe(0);
+ }
+
+ [Fact]
+ public async Task BatchApproveWithAlias_Test()
+ {
+ await SetTokenAlias_NFTCollection_Test();
+ await CreateTokenAndIssue();
+ var approveBasisResult = (await TokenContractStub.BatchApprove.SendAsync(new BatchApproveInput
+ {
+ Value =
+ {
+ new ApproveInput
+ {
+ Symbol = SymbolForTest,
+ Amount = 2000L,
+ Spender = BasicFunctionContractAddress
+ },
+ new ApproveInput
+ {
+ Symbol = "TP",
+ Amount = 1000L,
+ Spender = OtherBasicFunctionContractAddress
+ },
+ new ApproveInput
+ {
+ Symbol = SymbolForTest,
+ Amount = 5000L,
+ Spender = TreasuryContractAddress
+ }
+ }
+ })).TransactionResult;
+ approveBasisResult.Status.ShouldBe(TransactionResultStatus.Mined);
+
+ var basicAllowanceOutput = await TokenContractStub.GetAllowance.CallAsync(new GetAllowanceInput
+ {
+ Owner = DefaultAddress,
+ Spender = BasicFunctionContractAddress,
+ Symbol = SymbolForTest
+ });
+ basicAllowanceOutput.Allowance.ShouldBe(2000L);
+ var otherBasicAllowanceOutput = await TokenContractStub.GetAllowance.CallAsync(new GetAllowanceInput
+ {
+ Owner = DefaultAddress,
+ Spender = OtherBasicFunctionContractAddress,
+ Symbol = "TP"
+ });
+ otherBasicAllowanceOutput.Allowance.ShouldBe(1000L);
+ var treasuryAllowanceOutput = await TokenContractStub.GetAllowance.CallAsync(new GetAllowanceInput
+ {
+ Owner = DefaultAddress,
+ Spender = TreasuryContractAddress,
+ Symbol = SymbolForTest
+ });
+ treasuryAllowanceOutput.Allowance.ShouldBe(5000L);
+
+ approveBasisResult = (await TokenContractStub.BatchApprove.SendAsync(new BatchApproveInput
+ {
+ Value =
+ {
+ new ApproveInput
+ {
+ Symbol = "TP",
+ Amount = 1000L,
+ Spender = BasicFunctionContractAddress
+ },
+ new ApproveInput
+ {
+ Symbol = SymbolForTest,
+ Amount = 3000L,
+ Spender = BasicFunctionContractAddress
+ },
+ new ApproveInput
+ {
+ Symbol = SymbolForTest,
+ Amount = 3000L,
+ Spender = TreasuryContractAddress
+ }
+ }
+ })).TransactionResult;
+ approveBasisResult.Status.ShouldBe(TransactionResultStatus.Mined);
+ basicAllowanceOutput = await TokenContractStub.GetAllowance.CallAsync(new GetAllowanceInput
+ {
+ Owner = DefaultAddress,
+ Spender = BasicFunctionContractAddress,
+ Symbol = SymbolForTest
+ });
+ basicAllowanceOutput.Allowance.ShouldBe(3000L);
+
+ treasuryAllowanceOutput = await TokenContractStub.GetAllowance.CallAsync(new GetAllowanceInput
+ {
+ Owner = DefaultAddress,
+ Spender = TreasuryContractAddress,
+ Symbol = SymbolForTest
+ });
+ treasuryAllowanceOutput.Allowance.ShouldBe(3000L);
+ }
+
+ private TokenInfo NftCollection1155WithAliasInfo => new()
+ {
+ Symbol = "TP-",
+ TokenName = "Trump Digital Trading Cards #1155",
+ TotalSupply = TotalSupply,
+ Decimals = 0,
+ Issuer = DefaultAddress,
+ IssueChainId = _chainId,
+ ExternalInfo = new ExternalInfo
+ {
+ Value =
+ {
+ {
+ NftCollectionMetaFields.ImageUrlKey,
+ "https://i.seadn.io/gcs/files/0f5cdfaaf687de2ebb5834b129a5bef3.png?auto=format&w=3840"
+ },
+ { NftCollectionMetaFields.NftType, NftType },
+ { TokenAliasExternalInfoKey, "{\"TP-31175\":\"TP\"}" }
+ }
+ }
+ };
+}
\ No newline at end of file
diff --git a/test/AElf.Contracts.MultiToken.Tests/BVT/TokenApplicationTests.cs b/test/AElf.Contracts.MultiToken.Tests/BVT/TokenApplicationTests.cs
index 49642bae3a..201e56d6e6 100644
--- a/test/AElf.Contracts.MultiToken.Tests/BVT/TokenApplicationTests.cs
+++ b/test/AElf.Contracts.MultiToken.Tests/BVT/TokenApplicationTests.cs
@@ -504,6 +504,452 @@ await TreasuryContractStub.Donate.SendAsync(new DonateInput
afterTransferFromBalance.Balance.ShouldBe(beforeTransferFromBalance.Balance.Sub(transferAmount));
}
+ private async Task CreateNft()
+ {
+ await CreateMutiTokenAsync(TokenContractStub, new CreateInput
+ {
+ TokenName = "Test",
+ TotalSupply = TotalSupply,
+ Decimals = 0,
+ Issuer = DefaultAddress,
+ Owner = DefaultAddress,
+ IssueChainId = _chainId,
+ Symbol = "ABC-0"
+ });
+ await TokenContractStub.Create.SendAsync(new CreateInput
+ {
+ TokenName = "Test",
+ TotalSupply = TotalSupply,
+ Decimals = 0,
+ Issuer = DefaultAddress,
+ Owner = DefaultAddress,
+ IssueChainId = _chainId,
+ Symbol = "ABC-1"
+ });
+ }
+ [Fact]
+ public async Task MultiTokenContract_TransferFrom_Nft_Global_Test()
+ {
+ await CreateNft();
+ await TokenContractStub.Issue.SendAsync(new IssueInput
+ {
+ Symbol = "ABC-1",
+ Amount = 100,
+ To = DefaultAddress,
+ Memo = "test"
+ });
+ await TokenContractStub.Issue.SendAsync(new IssueInput
+ {
+ Symbol = "ABC-1",
+ Amount = 200,
+ To = User1Address,
+ Memo = "test"
+ });
+ var balance = await TokenContractStub.GetBalance.CallAsync(new GetBalanceInput
+ {
+ Owner = DefaultAddress,
+ Symbol = "ABC-1"
+ });
+ balance.Balance.ShouldBe(100);
+ balance = await TokenContractStub.GetBalance.CallAsync(new GetBalanceInput
+ {
+ Owner = User1Address,
+ Symbol = "ABC-1"
+ });
+ balance.Balance.ShouldBe(200);
+ await TokenContractStub.Approve.SendAsync(new ApproveInput
+ {
+ Amount = 1000,
+ Symbol = "*",
+ Spender = User1Address
+ });
+
+ await TokenContractStub.Approve.SendAsync(new ApproveInput
+ {
+ Amount = 1,
+ Symbol = "ABC-*",
+ Spender = User1Address
+ });
+ var allowance = await TokenContractStub.GetAllowance.CallAsync(new GetAllowanceInput
+ {
+ Owner = DefaultAddress,
+ Spender = User1Address,
+ Symbol = "ABC-1"
+ });
+ allowance.Allowance.ShouldBe(0);
+ allowance = await TokenContractStub.GetAllowance.CallAsync(new GetAllowanceInput
+ {
+ Owner = DefaultAddress,
+ Spender = User1Address,
+ Symbol = "ELF"
+ });
+ allowance.Allowance.ShouldBe(0);
+ {
+ var realAllowance = await TokenContractStub.GetAvailableAllowance.CallAsync(new GetAllowanceInput
+ {
+ Owner = DefaultAddress,
+ Spender = User1Address,
+ Symbol = "ABC-1"
+ });
+ realAllowance.Allowance.ShouldBe(1000);
+ }
+ {
+ var realAllowance = await TokenContractStub.GetAvailableAllowance.CallAsync(new GetAllowanceInput
+ {
+ Owner = DefaultAddress,
+ Spender = User1Address,
+ Symbol = "ELF"
+ });
+ realAllowance.Allowance.ShouldBe(1000);
+ }
+ var user1Stub =
+ GetTester(TokenContractAddress, User1KeyPair);
+ var result2 = await user1Stub.TransferFrom.SendAsync(new TransferFromInput
+ {
+ Amount = 50,
+ From = DefaultAddress,
+ Memo = "test",
+ Symbol = "ABC-1",
+ To = User1Address
+ });
+ result2.TransactionResult.Status.ShouldBe(TransactionResultStatus.Mined);
+ {
+ var realAllowance = await TokenContractStub.GetAllowance.CallAsync(new GetAllowanceInput
+ {
+ Owner = DefaultAddress,
+ Spender = User1Address,
+ Symbol = "ABC-1"
+ });
+ realAllowance.Allowance.ShouldBe(0);
+ }
+ allowance = await TokenContractStub.GetAvailableAllowance.CallAsync(new GetAllowanceInput
+ {
+ Owner = DefaultAddress,
+ Spender = User1Address,
+ Symbol = "ABC-1"
+ });
+ allowance.Allowance.ShouldBe(1000-50);
+ balance = await TokenContractStub.GetBalance.CallAsync(new GetBalanceInput
+ {
+ Owner = DefaultAddress,
+ Symbol = "ABC-1"
+ });
+ balance.Balance.ShouldBe(50);
+ balance = await TokenContractStub.GetBalance.CallAsync(new GetBalanceInput
+ {
+ Owner = User1Address,
+ Symbol = "ABC-1"
+ });
+ balance.Balance.ShouldBe(250);
+ }
+
+ [Fact]
+ public async Task MultiTokenContract_TransferFrom_Nft_Collection_Test()
+ {
+ await CreateNft();
+ await TokenContractStub.Issue.SendAsync(new IssueInput
+ {
+ Symbol = "ABC-1",
+ Amount = 100,
+ To = DefaultAddress,
+ Memo = "test"
+ });
+ await TokenContractStub.Issue.SendAsync(new IssueInput
+ {
+ Symbol = "ABC-1",
+ Amount = 200,
+ To = User1Address,
+ Memo = "test"
+ });
+ await TokenContractStub.Approve.SendAsync(new ApproveInput
+ {
+ Amount = 20,
+ Symbol = "*",
+ Spender = User1Address
+ });
+
+ await TokenContractStub.Approve.SendAsync(new ApproveInput
+ {
+ Amount = 1000,
+ Symbol = "ABC-*",
+ Spender = User1Address
+ });
+ {
+ var realAllowance = await TokenContractStub.GetAllowance.CallAsync(new GetAllowanceInput
+ {
+ Owner = DefaultAddress,
+ Spender = User1Address,
+ Symbol = "ABC-1"
+ });
+ realAllowance.Allowance.ShouldBe(0);
+ }
+ var allowance = await TokenContractStub.GetAvailableAllowance.CallAsync(new GetAllowanceInput
+ {
+ Owner = DefaultAddress,
+ Spender = User1Address,
+ Symbol = "ABC-1"
+ });
+ allowance.Allowance.ShouldBe(1000);
+ allowance = await TokenContractStub.GetAvailableAllowance.CallAsync(new GetAllowanceInput
+ {
+ Owner = DefaultAddress,
+ Spender = User1Address,
+ Symbol = "ELF"
+ });
+ allowance.Allowance.ShouldBe(20);
+ var user1Stub =
+ GetTester(TokenContractAddress, User1KeyPair);
+ var result2 = await user1Stub.TransferFrom.SendAsync(new TransferFromInput
+ {
+ Amount = 50,
+ From = DefaultAddress,
+ Memo = "test",
+ Symbol = "ABC-1",
+ To = User1Address
+ });
+ result2.TransactionResult.Status.ShouldBe(TransactionResultStatus.Mined);
+ allowance = await TokenContractStub.GetAvailableAllowance.CallAsync(new GetAllowanceInput
+ {
+ Owner = DefaultAddress,
+ Spender = User1Address,
+ Symbol = "ABC-1"
+ });
+ allowance.Allowance.ShouldBe(1000-50);
+ allowance = await TokenContractStub.GetAllowance.CallAsync(new GetAllowanceInput
+ {
+ Owner = DefaultAddress,
+ Spender = User1Address,
+ Symbol = "*"
+ });
+ allowance.Allowance.ShouldBe(20);
+
+ }
+
+ [Fact]
+ public async Task MultiTokenContract_TransferFrom_Token_Test()
+ {
+ await CreateAndIssueToken();
+ await TokenContractStub.Approve.SendAsync(new ApproveInput
+ {
+ Amount = 100_00000000,
+ Symbol = "*",
+ Spender = User1Address
+ });
+ var allowance = await TokenContractStub.GetAllowance.CallAsync(new GetAllowanceInput
+ {
+ Owner = DefaultAddress,
+ Spender = User1Address,
+ Symbol = "SSS"
+ });
+ allowance.Allowance.ShouldBe(0);
+ {
+ var realAllowance = await TokenContractStub.GetAvailableAllowance.CallAsync(new GetAllowanceInput
+ {
+ Owner = DefaultAddress,
+ Spender = User1Address,
+ Symbol = "SSS"
+ });
+ realAllowance.Allowance.ShouldBe(100_00000000);
+ }
+ allowance = await TokenContractStub.GetAllowance.CallAsync(new GetAllowanceInput
+ {
+ Owner = DefaultAddress,
+ Spender = User1Address,
+ Symbol = "ELF"
+ });
+ allowance.Allowance.ShouldBe(0);
+ {
+ var realAllowance = await TokenContractStub.GetAvailableAllowance.CallAsync(new GetAllowanceInput
+ {
+ Owner = DefaultAddress,
+ Spender = User1Address,
+ Symbol = "SSS"
+ });
+ realAllowance.Allowance.ShouldBe(100_00000000);
+ }
+ var user1Stub =
+ GetTester(TokenContractAddress, User1KeyPair);
+ var result2 = await user1Stub.TransferFrom.SendAsync(new TransferFromInput
+ {
+ Amount = 50_00000000,
+ From = DefaultAddress,
+ Memo = "test",
+ Symbol = "SSS",
+ To = User1Address
+ });
+ result2.TransactionResult.Status.ShouldBe(TransactionResultStatus.Mined);
+ allowance = await TokenContractStub.GetAvailableAllowance.CallAsync(new GetAllowanceInput
+ {
+ Owner = DefaultAddress,
+ Spender = User1Address,
+ Symbol = "SSS"
+ });
+ allowance.Allowance.ShouldBe(100_00000000-50_00000000);
+ var balance = await TokenContractStub.GetBalance.CallAsync(new GetBalanceInput
+ {
+ Owner = DefaultAddress,
+ Symbol = "SSS"
+ });
+ balance.Balance.ShouldBe(TotalSupply - 50_00000000);
+ balance = await TokenContractStub.GetBalance.CallAsync(new GetBalanceInput
+ {
+ Owner = User1Address,
+ Symbol = "SSS"
+ });
+ balance.Balance.ShouldBe(50_00000000);
+ }
+
+ private async Task CreateAndIssueToken()
+ {
+ await CreateMutiTokenAsync(TokenContractStub, new CreateInput
+ {
+ TokenName = "Test",
+ TotalSupply = TotalSupply,
+ Decimals = 8,
+ Issuer = DefaultAddress,
+ Owner = DefaultAddress,
+ IssueChainId = _chainId,
+ Symbol = "SSS"
+ });
+ await TokenContractStub.Issue.SendAsync(new IssueInput
+ {
+ Symbol = "SSS",
+ Amount = TotalSupply,
+ To = DefaultAddress,
+ Memo = "Issue"
+ });
+ var balance = await TokenContractStub.GetBalance.CallAsync(new GetBalanceInput
+ {
+ Owner = DefaultAddress,
+ Symbol = "SSS"
+ });
+ balance.Balance.ShouldBe(TotalSupply);
+ }
+ [Fact]
+ public async Task MultiTokenContract_Approve_Test_New()
+ {
+ await CreateAndIssueToken();
+ await TokenContractStub.Approve.SendAsync(new ApproveInput
+ {
+ Spender = User1Address,
+ Symbol = "SSS",
+ Amount = 100_000000000
+ });
+ var allowance = await TokenContractStub.GetAllowance.CallAsync(new GetAllowanceInput
+ {
+ Owner = DefaultAddress,
+ Spender = User1Address,
+ Symbol = "SSS"
+ });
+ allowance.Allowance.ShouldBe(100_000000000);
+ await TokenContractStub.Approve.SendAsync(new ApproveInput
+ {
+ Spender = User1Address,
+ Symbol = "*",
+ Amount = 200_000000000
+ });
+ {
+ var realAllowance = await TokenContractStub.GetAllowance.CallAsync(new GetAllowanceInput
+ {
+ Owner = DefaultAddress,
+ Spender = User1Address,
+ Symbol = "SSS"
+ });
+ realAllowance.Allowance.ShouldBe(100_000000000);
+ }
+ allowance = await TokenContractStub.GetAvailableAllowance.CallAsync(new GetAllowanceInput
+ {
+ Owner = DefaultAddress,
+ Spender = User1Address,
+ Symbol = "SSS"
+ });
+ allowance.Allowance.ShouldBe(200_000000000);
+ allowance = await TokenContractStub.GetAvailableAllowance.CallAsync(new GetAllowanceInput
+ {
+ Owner = DefaultAddress,
+ Spender = User1Address,
+ Symbol = "*"
+ });
+ allowance.Allowance.ShouldBe(200_000000000);
+ await TokenContractStub.UnApprove.SendAsync(new UnApproveInput
+ {
+ Spender = User1Address,
+ Symbol = "*",
+ Amount = 20_000000000
+ });
+ allowance = await TokenContractStub.GetAvailableAllowance.CallAsync(new GetAllowanceInput
+ {
+ Owner = DefaultAddress,
+ Spender = User1Address,
+ Symbol = "*"
+ });
+ allowance.Allowance.ShouldBe(200_000000000-20_000000000);
+ }
+
+ [Fact]
+ public async Task MultiTokenContract_Approve_Test_New_Fail()
+ {
+ await CreateAndIssueToken();
+ {
+ var executionResult = await TokenContractStub.Approve.SendWithExceptionAsync(new ApproveInput
+ {
+ Spender = User1Address,
+ Symbol = "SSS*",
+ Amount = 100_000000000
+ });
+ executionResult.TransactionResult.Error.ShouldContain("Invalid symbol.");
+ }
+ {
+ var executionResult = await TokenContractStub.Approve.SendWithExceptionAsync(new ApproveInput
+ {
+ Spender = User1Address,
+ Symbol = "SSS**",
+ Amount = 100_000000000
+ });
+ executionResult.TransactionResult.Error.ShouldContain("Invalid symbol.");
+ }
+ {
+ var executionResult = await TokenContractStub.Approve.SendWithExceptionAsync(new ApproveInput
+ {
+ Spender = User1Address,
+ Symbol = "*-*",
+ Amount = 100_000000000
+ });
+ executionResult.TransactionResult.Error.ShouldContain("Token is not found");
+ }
+ }
+
+ [Fact]
+ public async Task MultiTokenContract_Approve_Test_New_Nft_Fail()
+ {
+ await CreateNft();
+ await TokenContractStub.Issue.SendAsync(new IssueInput
+ {
+ Symbol = "ABC-1",
+ Amount = 100,
+ To = DefaultAddress,
+ Memo = "test"
+ });
+ {
+ var executionResult = await TokenContractStub.Approve.SendWithExceptionAsync(new ApproveInput
+ {
+ Spender = User1Address,
+ Symbol = "AB*-*",
+ Amount = 100_000000000
+ });
+ executionResult.TransactionResult.Error.ShouldContain("Invalid Symbol");
+ }
+ {
+ var executionResult = await TokenContractStub.Approve.SendWithExceptionAsync(new ApproveInput
+ {
+ Spender = User1Address,
+ Symbol = "ABC-*9",
+ Amount = 100_000000000
+ });
+ executionResult.TransactionResult.Error.ShouldContain("Invalid NFT Symbol.");
+ }
+ }
+
private async Task CreateTokenAndIssue(List whitelist = null, Address issueTo = null)
{
if (whitelist == null)
diff --git a/test/AElf.Contracts.MultiTokenCrossChainTransfer.Tests/MultiTokenContractCrossChainTest.cs b/test/AElf.Contracts.MultiTokenCrossChainTransfer.Tests/MultiTokenContractCrossChainTest.cs
index 4c76a04b09..30a6d59c74 100644
--- a/test/AElf.Contracts.MultiTokenCrossChainTransfer.Tests/MultiTokenContractCrossChainTest.cs
+++ b/test/AElf.Contracts.MultiTokenCrossChainTransfer.Tests/MultiTokenContractCrossChainTest.cs
@@ -1,5 +1,6 @@
using System.Collections.Generic;
using System.Linq;
+using System.Text.Json;
using System.Threading.Tasks;
using AElf.Contracts.Parliament;
using AElf.ContractTestBase.ContractTestKit;
@@ -11,6 +12,7 @@
using AElf.Standards.ACS7;
using AElf.Types;
using Google.Protobuf;
+using Google.Protobuf.WellKnownTypes;
using Shouldly;
using Xunit;
@@ -19,11 +21,13 @@ namespace AElf.Contracts.MultiToken;
public class MultiTokenContractCrossChainTest : MultiTokenContractCrossChainTestBase
{
private const string SymbolForTesting = "ELFTEST";
+ private const string NFTSymbolForTesting = "ELFNFT";
private const string NativeToken = "ELF";
private static readonly long _totalSupply = 1000L;
private readonly Hash _fakeBlockHeader = HashHelper.ComputeFrom("fakeBlockHeader");
private readonly int _parentChainHeightOfCreation = 5;
private readonly string sideChainSymbol = "STA";
+ public const string TokenAliasExternalInfoKey = "aelf_token_alias";
#region register test
@@ -321,7 +325,7 @@ public async Task SideChain_CrossChainSideChainCreateToken_Test()
}
- [Fact]
+ [Fact(Skip = "Now we allow this.")]
public async Task SideChain_CrossChainCreateToken_WithAlreadyCreated_Test()
{
await GenerateSideChainAsync();
@@ -387,6 +391,146 @@ public async Task CrossChainCreateToken_With_Invalid_Verification_Test()
Assert.True(result.Status == TransactionResultStatus.Failed);
Assert.Contains("Invalid transaction", result.Error);
}
+
+ [Fact]
+ public async Task SideChain_CrossChainSideChainCreateToken_WithAlias_Test()
+ {
+ await GenerateSideChainAsync();
+ await RegisterSideChainContractAddressOnMainChainAsync();
+
+ // Main chain create token
+ await BootMinerChangeRoundAsync(AEDPoSContractStub, true);
+ var createTransaction = await CreateTransactionForNFTCreation(TokenContractStub,
+ DefaultAccount.Address, $"{NFTSymbolForTesting}-0", TokenContractAddress);
+ var blockExecutedSet = await MineAsync(new List { createTransaction });
+ var createResult = blockExecutedSet.TransactionResultMap[createTransaction.GetHash()];
+ Assert.True(createResult.Status == TransactionResultStatus.Mined, createResult.Error);
+
+ var createdTokenInfo = await TokenContractStub.GetTokenInfo.CallAsync(new GetTokenInfoInput
+ {
+ Symbol = $"{NFTSymbolForTesting}-0"
+ });
+ var tokenValidationTransaction = CreateTokenInfoValidationTransaction(createdTokenInfo,
+ TokenContractStub);
+
+ blockExecutedSet = await MineAsync(new List { tokenValidationTransaction });
+ var merklePath = GetTransactionMerklePathAndRoot(tokenValidationTransaction, out var blockRoot);
+ await IndexMainChainTransactionAsync(blockExecutedSet.Height, blockRoot, blockRoot);
+ var crossChainCreateTokenInput = new CrossChainCreateTokenInput
+ {
+ FromChainId = MainChainId,
+ ParentChainHeight = blockExecutedSet.Height,
+ TransactionBytes = tokenValidationTransaction.ToByteString(),
+ MerklePath = merklePath
+ };
+ // Side chain cross chain create
+ var executionResult =
+ await SideChainTokenContractStub.CrossChainCreateToken.SendAsync(crossChainCreateTokenInput);
+ executionResult.TransactionResult.Status.ShouldBe(TransactionResultStatus.Mined,
+ executionResult.TransactionResult.Error);
+
+ var newTokenInfo = await SideChainTokenContractStub.GetTokenInfo.CallAsync(new GetTokenInfoInput
+ {
+ Symbol = $"{NFTSymbolForTesting}-0"
+ });
+ newTokenInfo.TotalSupply.ShouldBe(_totalSupply);
+
+ var alias = await SideChainTokenContractStub.GetTokenAlias.CallAsync(new StringValue
+ {
+ Value = $"{NFTSymbolForTesting}-{1}"
+ });
+ alias.Value.ShouldBe(NFTSymbolForTesting);
+ }
+
+ [Fact]
+ public async Task SideChain_CrossChainSideChainCreateToken_SetAliasAndSyncAgain_Test()
+ {
+ await GenerateSideChainAsync();
+ await RegisterSideChainContractAddressOnMainChainAsync();
+
+ // Main chain create token
+ await BootMinerChangeRoundAsync(AEDPoSContractStub, true);
+ var createTransaction = await CreateTransactionForNFTCreation(TokenContractStub,
+ DefaultAccount.Address, $"{NFTSymbolForTesting}-0", TokenContractAddress, false);
+ var blockExecutedSet = await MineAsync(new List { createTransaction });
+ var createResult = blockExecutedSet.TransactionResultMap[createTransaction.GetHash()];
+ Assert.True(createResult.Status == TransactionResultStatus.Mined, createResult.Error);
+
+ // Sync for the first time
+ {
+ var createdTokenInfo = await TokenContractStub.GetTokenInfo.CallAsync(new GetTokenInfoInput
+ {
+ Symbol = $"{NFTSymbolForTesting}-0"
+ });
+ var tokenValidationTransaction = CreateTokenInfoValidationTransaction(createdTokenInfo,
+ TokenContractStub);
+
+ blockExecutedSet = await MineAsync(new List { tokenValidationTransaction });
+ var merklePath = GetTransactionMerklePathAndRoot(tokenValidationTransaction, out var blockRoot);
+ await IndexMainChainTransactionAsync(blockExecutedSet.Height, blockRoot, blockRoot);
+ var crossChainCreateTokenInput = new CrossChainCreateTokenInput
+ {
+ FromChainId = MainChainId,
+ ParentChainHeight = blockExecutedSet.Height,
+ TransactionBytes = tokenValidationTransaction.ToByteString(),
+ MerklePath = merklePath
+ };
+ // Side chain cross chain create
+ var executionResult =
+ await SideChainTokenContractStub.CrossChainCreateToken.SendAsync(crossChainCreateTokenInput);
+ executionResult.TransactionResult.Status.ShouldBe(TransactionResultStatus.Mined,
+ executionResult.TransactionResult.Error);
+ }
+
+ // Set Alias
+ {
+ var setAliasTransaction = await TokenContractStub.SetSymbolAlias.SendAsync(new SetSymbolAliasInput
+ {
+ Symbol = $"{NFTSymbolForTesting}-1",
+ Alias = NFTSymbolForTesting
+ });
+ var setAliasResult = setAliasTransaction.TransactionResult;
+ setAliasResult.Status.ShouldBe(TransactionResultStatus.Mined);
+ }
+
+ // Sync for the second time
+ {
+ var createdTokenInfo = await TokenContractStub.GetTokenInfo.CallAsync(new GetTokenInfoInput
+ {
+ Symbol = $"{NFTSymbolForTesting}-0"
+ });
+ var tokenValidationTransaction = CreateTokenInfoValidationTransaction(createdTokenInfo,
+ TokenContractStub);
+
+ blockExecutedSet = await MineAsync(new List { tokenValidationTransaction });
+ var merklePath = GetTransactionMerklePathAndRoot(tokenValidationTransaction, out var blockRoot);
+ await IndexMainChainTransactionAsync(blockExecutedSet.Height, blockRoot, blockRoot);
+ var crossChainCreateTokenInput = new CrossChainCreateTokenInput
+ {
+ FromChainId = MainChainId,
+ ParentChainHeight = blockExecutedSet.Height,
+ TransactionBytes = tokenValidationTransaction.ToByteString(),
+ MerklePath = merklePath
+ };
+ // Side chain cross chain create
+ var executionResult =
+ await SideChainTokenContractStub.CrossChainCreateToken.SendAsync(crossChainCreateTokenInput);
+ executionResult.TransactionResult.Status.ShouldBe(TransactionResultStatus.Mined,
+ executionResult.TransactionResult.Error);
+ }
+
+ var newTokenInfo = await SideChainTokenContractStub.GetTokenInfo.CallAsync(new GetTokenInfoInput
+ {
+ Symbol = $"{NFTSymbolForTesting}-0"
+ });
+ newTokenInfo.TotalSupply.ShouldBe(_totalSupply);
+
+ var alias = await SideChainTokenContractStub.GetTokenAlias.CallAsync(new StringValue
+ {
+ Value = $"{NFTSymbolForTesting}-1"
+ });
+ alias.Value.ShouldBe(NFTSymbolForTesting);
+ }
#endregion
@@ -891,12 +1035,48 @@ private async Task CreateTransactionForTokenCreation(
await CreateSeedNftAsync(tokenContractImplStub, input, lockWhiteAddress);
return tokenContractImplStub.Create.GetTransaction(input);
}
+
+ private async Task CreateTransactionForNFTCreation(
+ TokenContractImplContainer.TokenContractImplStub tokenContractImplStub,
+ Address issuer, string symbol, Address lockWhiteAddress, bool withAlias = true)
+ {
+ await CreateSeedNftCollection(tokenContractImplStub, issuer);
+ var tokenInfo = GetTokenInfo(symbol, issuer);
+ var input = new CreateInput
+ {
+ Symbol = tokenInfo.Symbol,
+ Decimals = 0,
+ Issuer = tokenInfo.Issuer,
+ Owner = tokenInfo.Issuer,
+ IsBurnable = tokenInfo.IsBurnable,
+ TokenName = tokenInfo.TokenName,
+ TotalSupply = tokenInfo.TotalSupply,
+ };
+ if (withAlias)
+ {
+ input.ExternalInfo = new ExternalInfo
+ {
+ Value =
+ {
+ {
+ TokenAliasExternalInfoKey, JsonSerializer.Serialize(new Dictionary
+ {
+ { $"{NFTSymbolForTesting}-{1}", NFTSymbolForTesting }
+ })
+ }
+ }
+ };
+ }
+
+ await CreateSeedNftAsync(tokenContractImplStub, input, lockWhiteAddress);
+ return tokenContractImplStub.Create.GetTransaction(input);
+ }
private Transaction CreateTokenInfoValidationTransaction(TokenInfo createdTokenInfo,
TokenContractImplContainer.TokenContractImplStub tokenContractImplStub)
{
- return tokenContractImplStub.ValidateTokenInfoExists.GetTransaction(new ValidateTokenInfoExistsInput
+ var input = new ValidateTokenInfoExistsInput
{
TokenName = createdTokenInfo.TokenName,
Symbol = createdTokenInfo.Symbol,
@@ -906,7 +1086,12 @@ private Transaction CreateTokenInfoValidationTransaction(TokenInfo createdTokenI
IsBurnable = createdTokenInfo.IsBurnable,
TotalSupply = createdTokenInfo.TotalSupply,
IssueChainId = createdTokenInfo.IssueChainId
- });
+ };
+ if (createdTokenInfo.ExternalInfo != null)
+ {
+ input.ExternalInfo.Add(createdTokenInfo.ExternalInfo.Value);
+ }
+ return tokenContractImplStub.ValidateTokenInfoExists.GetTransaction(input);
}
private TokenInfo GetTokenInfo(string symbol, Address issuer, bool isBurnable = true)