Skip to content

Commit

Permalink
feat: multitoken nft upgrade
Browse files Browse the repository at this point in the history
  • Loading branch information
Stephen02Zhang committed Feb 22, 2023
1 parent 2f39429 commit e139a96
Show file tree
Hide file tree
Showing 24 changed files with 1,124 additions and 122 deletions.
2 changes: 1 addition & 1 deletion .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -303,7 +303,7 @@ scripts/patcher/*

tools
.dotnet
!.idea/
.idea/.idea.AElf.All/*
.idea/.idea.AElf/.idea/contentModel.xml
.idea/.idea.AElf/.idea/encodings.xml
.idea/.idea.AElf/.idea/indexLayout.xml
Expand Down
7 changes: 0 additions & 7 deletions AElf.All.sln
Original file line number Diff line number Diff line change
Expand Up @@ -359,8 +359,6 @@ Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "AElf.ContractDeployer.Tests
EndProject
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "AElf.Contracts.NFT", "contract\AElf.Contracts.NFT\AElf.Contracts.NFT.csproj", "{0ECCF46E-3989-4A7E-BA31-4E73D7515578}"
EndProject
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "AElf.Contracts.NFT.Tests", "test\AElf.Contracts.NFT.Tests\AElf.Contracts.NFT.Tests.csproj", "{7E07BD13-74BF-4FD7-A294-07E664C47100}"
EndProject
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "AElf.Contracts.TestContract.BigIntValue", "test\AElf.Contracts.TestContract.BigIntValue\AElf.Contracts.TestContract.BigIntValue.csproj", "{F50AF512-69E2-46B5-87C6-E058CE2C2D8A}"
EndProject
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "AElf.Kernel.FeatureManager", "src\AElf.Kernel.FeatureManager\AElf.Kernel.FeatureManager.csproj", "{B24BC602-DAFD-4941-A913-8B0725691681}"
Expand Down Expand Up @@ -1037,10 +1035,6 @@ Global
{0ECCF46E-3989-4A7E-BA31-4E73D7515578}.Debug|Any CPU.Build.0 = Debug|Any CPU
{0ECCF46E-3989-4A7E-BA31-4E73D7515578}.Release|Any CPU.ActiveCfg = Release|Any CPU
{0ECCF46E-3989-4A7E-BA31-4E73D7515578}.Release|Any CPU.Build.0 = Release|Any CPU
{7E07BD13-74BF-4FD7-A294-07E664C47100}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{7E07BD13-74BF-4FD7-A294-07E664C47100}.Debug|Any CPU.Build.0 = Debug|Any CPU
{7E07BD13-74BF-4FD7-A294-07E664C47100}.Release|Any CPU.ActiveCfg = Release|Any CPU
{7E07BD13-74BF-4FD7-A294-07E664C47100}.Release|Any CPU.Build.0 = Release|Any CPU
{F50AF512-69E2-46B5-87C6-E058CE2C2D8A}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{F50AF512-69E2-46B5-87C6-E058CE2C2D8A}.Debug|Any CPU.Build.0 = Debug|Any CPU
{F50AF512-69E2-46B5-87C6-E058CE2C2D8A}.Release|Any CPU.ActiveCfg = Release|Any CPU
Expand Down Expand Up @@ -1234,7 +1228,6 @@ Global
{C3EEC9D9-F818-4058-B6F3-B71C1C0B8D47} = {4E54480A-D155-43ED-9736-1A5BE7957211}
{B64C5954-9CC0-4A0B-9453-92324B51C23D} = {4E54480A-D155-43ED-9736-1A5BE7957211}
{0ECCF46E-3989-4A7E-BA31-4E73D7515578} = {9AA521A5-80BF-4D20-9339-31D7E86D5868}
{7E07BD13-74BF-4FD7-A294-07E664C47100} = {D3950CC9-808F-4ED8-946A-79A992F3F8EF}
{F50AF512-69E2-46B5-87C6-E058CE2C2D8A} = {D3950CC9-808F-4ED8-946A-79A992F3F8EF}
{B24BC602-DAFD-4941-A913-8B0725691681} = {90B310B4-C2DB-419E-B5EE-97FA096B62CC}
{1B44277E-74EB-49B2-B8FD-05C29EE51985} = {4E54480A-D155-43ED-9736-1A5BE7957211}
Expand Down
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
<Project Sdk="Microsoft.NET.Sdk">
<Import Project="..\..\common.props"/>
<Import Project="..\..\common.props" />

<PropertyGroup>
<TargetFramework>net6.0</TargetFramework>
Expand Down
2 changes: 2 additions & 0 deletions contract/AElf.Contracts.MultiToken/TokenContractConstants.cs
Original file line number Diff line number Diff line change
Expand Up @@ -15,4 +15,6 @@ public static class TokenContractConstants
public const string UnlockCallbackExternalInfoKey = "aelf_unlock_callback";
public const string LogEventExternalInfoKey = "aelf_log_event";
public const int DELEGATEE_MAX_COUNT = 128;
public const char NFTSymbolSeparator = '-';
public const int NFTSymbolMaxLength = 30;
}
Original file line number Diff line number Diff line change
Expand Up @@ -46,8 +46,8 @@ public override MethodFees GetMethodFee(StringValue input)
MethodName = input.Value,
IsSizeFeeFree = true
};

if (input.Value == nameof(Create))
var fees = State.TransactionFees[input.Value];
if (input.Value == nameof(Create) && fees == null)
return new MethodFees
{
MethodName = input.Value,
Expand All @@ -61,7 +61,7 @@ public override MethodFees GetMethodFee(StringValue input)
}
};

return State.TransactionFees[input.Value];
return fees;
}

public override AuthorityInfo GetMethodFeeController(Empty input)
Expand Down
44 changes: 19 additions & 25 deletions contract/AElf.Contracts.MultiToken/TokenContract_Actions.cs
Original file line number Diff line number Diff line change
Expand Up @@ -32,11 +32,21 @@ public override Empty InitializeFromParentChain(InitializeFromParentChainInput i
/// <returns></returns>
public override Empty Create(CreateInput input)
{
if (Context.Origin != Context.Sender)
Assert(IsAddressInCreateTokenWhiteList(Context.Sender), "No permission to create token via inline tx.");

// can not call create on side chain
Assert(State.SideChainCreator.Value == null, "Failed to create token if side chain creator already set.");
AssertValidCreateInput(input);
var inputSymbolType = GetCreateInputSymbolType(input.Symbol);
ChargeCreateFees();
return inputSymbolType switch
{
SymbolType.NFTCollection => CreateNFTCollection(input),
SymbolType.NFT => CreateNFTInfo(input),
_ => CreateToken(input)
};
}

private Empty CreateToken(CreateInput input, SymbolType symbolType = SymbolType.TOKEN)
{
AssertValidCreateInput(input, symbolType);
var tokenInfo = new TokenInfo
{
Symbol = input.Symbol,
Expand All @@ -48,7 +58,6 @@ public override Empty Create(CreateInput input)
IssueChainId = input.IssueChainId == 0 ? Context.ChainId : input.IssueChainId,
ExternalInfo = input.ExternalInfo ?? new ExternalInfo()
};
Assert(input.Symbol.All(IsValidCreateSymbolChar), "Invalid symbol.");
RegisterTokenInfo(tokenInfo);
if (string.IsNullOrEmpty(State.NativeTokenSymbol.Value))
{
Expand Down Expand Up @@ -113,6 +122,7 @@ public override Empty Issue(IssueInput input)
Assert(tokenInfo.Issued <= tokenInfo.TotalSupply, "Total supply exceeded");
State.TokenInfos[input.Symbol] = tokenInfo;
ModifyBalance(input.To, input.Symbol, input.Amount);

Context.Fire(new Issued
{
Symbol = input.Symbol,
Expand Down Expand Up @@ -190,25 +200,7 @@ public override Empty Unlock(UnlockInput input)
public override Empty TransferFrom(TransferFromInput input)
{
AssertValidToken(input.Symbol, input.Amount);
// First check allowance.
var allowance = State.Allowances[input.From][Context.Sender][input.Symbol];
if (allowance < input.Amount)
{
if (IsInWhiteList(new IsInWhiteListInput { Symbol = input.Symbol, Address = Context.Sender }).Value)
{
DoTransfer(input.From, input.To, input.Symbol, input.Amount, input.Memo);
DealWithExternalInfoDuringTransfer(input);
return new Empty();
}

Assert(false,
$"[TransferFrom]Insufficient allowance. Token: {input.Symbol}; {allowance}/{input.Amount}.\n" +
$"From:{input.From}\tSpender:{Context.Sender}\tTo:{input.To}");
}

DoTransfer(input.From, input.To, input.Symbol, input.Amount, input.Memo);
DealWithExternalInfoDuringTransfer(input);
State.Allowances[input.From][Context.Sender][input.Symbol] = allowance.Sub(input.Amount);
DoTransferFrom(input.From, input.To, Context.Sender, input.Symbol, input.Amount, input.Memo);
return new Empty();
}

Expand Down Expand Up @@ -248,6 +240,7 @@ public override Empty Burn(BurnInput input)
Assert(tokenInfo.IsBurnable, "The token is not burnable.");
ModifyBalance(Context.Sender, input.Symbol, -input.Amount);
tokenInfo.Supply = tokenInfo.Supply.Sub(input.Amount);

Context.Fire(new Burned
{
Burner = Context.Sender,
Expand Down Expand Up @@ -424,12 +417,13 @@ public override Empty CrossChainCreateToken(CrossChainCreateTokenInput input)
$"Token contract address of chain {ChainHelper.ConvertChainIdToBase58(input.FromChainId)} not registered.");

var originalTransaction = Transaction.Parser.ParseFrom(input.TransactionBytes);

AssertCrossChainTransaction(originalTransaction, tokenContractAddress, nameof(ValidateTokenInfoExists));
var originalTransactionId = originalTransaction.GetHash();
CrossChainVerify(originalTransactionId, input.ParentChainHeight, input.FromChainId, input.MerklePath);
var validateTokenInfoExistsInput =
ValidateTokenInfoExistsInput.Parser.ParseFrom(originalTransaction.Params);

AssertNftCollectionExist(validateTokenInfoExistsInput.Symbol);
var tokenInfo = new TokenInfo
{
Symbol = validateTokenInfoExistsInput.Symbol,
Expand Down
30 changes: 16 additions & 14 deletions contract/AElf.Contracts.MultiToken/TokenContract_Helper.cs
Original file line number Diff line number Diff line change
Expand Up @@ -15,14 +15,16 @@ public partial class TokenContract
{
private static bool IsValidSymbolChar(char character)
{
return (character >= 'A' && character <= 'Z') || (character >= '0' && character <= '9');
return (character >= 'A' && character <= 'Z') || (character >= '0' && character <= '9') || character == TokenContractConstants.NFTSymbolSeparator;
}

private bool IsValidCreateSymbolChar(char character)
private bool IsValidItemIdChar(char character)
{
if (State.CreateTokenWhiteListMap[Context.Sender])
return (character >= 'A' && character <= 'Z') || (character >= '0' && character <= '9');
return character >= '0' && character <= '9';
}

private bool IsValidCreateSymbolChar(char character)
{
return character >= 'A' && character <= 'Z';
}

Expand Down Expand Up @@ -98,7 +100,7 @@ private MethodFeeFreeAllowance GetFreeFeeAllowance(MethodFeeFreeAllowances freeA
{
return freeAllowances?.Value.FirstOrDefault(a => a.Symbol == symbol);
}

private long GetFreeFeeAllowanceAmount(MethodFeeFreeAllowances freeAllowances, string symbol)
{
var existingAllowance = 0L;
Expand Down Expand Up @@ -187,16 +189,16 @@ private int GetIssueChainId(string symbol)
return tokenInfo.IssueChainId;
}

private void AssertValidCreateInput(CreateInput input)
private void AssertValidCreateInput(CreateInput input, SymbolType symbolType)
{
var isValid = input.TokenName.Length <= TokenContractConstants.TokenNameLength
&& input.Symbol.Length > 0
&& input.Decimals >= 0
&& input.Decimals <= TokenContractConstants.MaxDecimals;
if (!State.CreateTokenWhiteListMap[Context.Sender])
isValid = isValid && input.Symbol.Length <= TokenContractConstants.SymbolMaxLength;

Assert(isValid, "Invalid input.");
Assert(input.TokenName.Length <= TokenContractConstants.TokenNameLength
&& input.Symbol.Length > 0
&& input.Decimals >= 0
&& input.Decimals <= TokenContractConstants.MaxDecimals, "Invalid input.");
if (symbolType == SymbolType.TOKEN)
Assert(input.Symbol.Length <= TokenContractConstants.SymbolMaxLength, "Invalid token symbol length");
if (symbolType == SymbolType.NFT || symbolType == SymbolType.NFTCollection)
Assert(input.Symbol.Length <= TokenContractConstants.NFTSymbolMaxLength, "Invalid NFT symbol length");
}

private void CheckCrossChainTokenContractRegistrationControllerAuthority()
Expand Down
27 changes: 27 additions & 0 deletions contract/AElf.Contracts.MultiToken/TokenContract_NFTHelper.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,27 @@
using System.Linq;

namespace AElf.Contracts.MultiToken;

public partial class TokenContract
{
public enum SymbolType
{
NFTCollection,
NFT,
TOKEN
}

private SymbolType GetCreateInputSymbolType(string symbol)
{
var words = symbol.Split(TokenContractConstants.NFTSymbolSeparator);
Assert(words[0].Length > 0 && words[0].All(IsValidCreateSymbolChar), "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");
return words[1] == "0" ? SymbolType.NFTCollection : SymbolType.NFT;
}

private void AssertNFTCreateInput(CreateInput input)
{
Assert(input.Decimals == 0, "NFT's decimals must be 0");
}
}
94 changes: 94 additions & 0 deletions contract/AElf.Contracts.MultiToken/TokenContract_NFT_Actions.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,94 @@
using System.Linq;
using AElf.CSharp.Core;
using AElf.Sdk.CSharp;
using AElf.Standards.ACS1;
using AElf.Types;
using Google.Protobuf.WellKnownTypes;

namespace AElf.Contracts.MultiToken;

public partial class TokenContract
{
private Empty CreateNFTCollection(CreateInput input)
{
AssertNFTCreateInput(input);
return CreateToken(input, SymbolType.NFTCollection);
}

private Empty CreateNFTInfo(CreateInput input)
{
AssertNFTCreateInput(input);
var nftCollectionInfo = AssertNftCollectionExist(input.Symbol);
input.IssueChainId = input.IssueChainId == 0 ? nftCollectionInfo.IssueChainId : input.IssueChainId;
Assert(input.IssueChainId == nftCollectionInfo.IssueChainId, "NFT create ChainId must be collection's issue chainId");
Assert(Context.Sender == nftCollectionInfo.Issuer && nftCollectionInfo.Issuer == input.Issuer, "NFT issuer must be collection's issuer");
return CreateToken(input, SymbolType.NFT);
}

private void ChargeCreateFees()
{
if (Context.Sender == Context.Origin) return;
if (IsAddressInCreateWhiteList(Context.Sender)) return;

var fee = GetCreateMethodFee();
Assert(fee != null, "not enough balance for create");
DoTransferFrom(Context.Sender, Context.Self, Context.Self, fee.Symbol, fee.BasicFee, "");

ModifyBalance(Context.Self, fee.Symbol, -fee.BasicFee);
Context.Fire(new TransactionFeeCharged()
{
Symbol = fee.Symbol,
Amount = fee.BasicFee,
ChargingAddress = Context.Self
});
}

private void DoTransferFrom(Address from, Address to, Address spender, string symbol, long amount, string memo)
{
// First check allowance.
var allowance = State.Allowances[from][spender][symbol];
if (allowance < amount)
{
if (IsInWhiteList(new IsInWhiteListInput { Symbol = symbol, Address = spender }).Value)
{
DoTransfer(from, to, symbol, amount, memo);
DealWithExternalInfoDuringTransfer(new TransferFromInput() { From = from, To = to, Symbol = symbol, Amount = amount, Memo = memo });
return;
}

Assert(false,
$"[TransferFrom]Insufficient allowance. Token: {symbol}; {allowance}/{amount}.\n" +
$"From:{from}\tSpender:{spender}\tTo:{to}");
}

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);
}

private MethodFee GetCreateMethodFee()
{
var fee = State.TransactionFees[nameof(Create)];
if (fee == null || fee.Fees.Count <= 0) return new MethodFee { Symbol = Context.Variables.NativeSymbol, BasicFee = 10000_00000000 };
return fee.Fees.FirstOrDefault(f => GetBalance(Context.Sender, f.Symbol) >= f.BasicFee);
}

private string GetNftCollectionSymbol(string inputSymbol)
{
var symbol = inputSymbol;
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");
return symbol == $"{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];
Assert(collectionInfo != null, "NFT collection not exist");
return collectionInfo;
}
}
20 changes: 5 additions & 15 deletions contract/AElf.Contracts.MultiToken/TokenContract_Views.cs
Original file line number Diff line number Diff line change
Expand Up @@ -199,13 +199,6 @@ public override BoolValue IsTokenAvailableForMethodFee(StringValue input)
};
}

public override BoolValue IsInCreateTokenWhiteList(Address input)
{
return new BoolValue
{
Value = IsAddressInCreateTokenWhiteList(input)
};
}

public override StringList GetReservedExternalInfoKeyList(Empty input)
{
Expand All @@ -228,14 +221,11 @@ private bool IsTokenAvailableForMethodFee(string symbol)
return tokenInfo.IsBurnable;
}

private bool IsAddressInCreateTokenWhiteList(Address address)
private bool IsAddressInCreateWhiteList(Address address)
{
if (address == Context.GetZeroSmartContractAddress() ||
address == GetDefaultParliamentController().OwnerAddress || address ==
Context.GetContractAddressByName(SmartContractConstants.EconomicContractSystemName) ||
address == Context.GetContractAddressByName(SmartContractConstants.CrossChainContractSystemName))
return true;

return State.CreateTokenWhiteListMap[address];
return address == Context.GetZeroSmartContractAddress() ||
address == GetDefaultParliamentController().OwnerAddress ||
address == Context.GetContractAddressByName(SmartContractConstants.EconomicContractSystemName) ||
address == Context.GetContractAddressByName(SmartContractConstants.CrossChainContractSystemName);
}
}
Loading

0 comments on commit e139a96

Please sign in to comment.