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

Add Creator royalties #178

Merged
merged 8 commits into from
Jul 31, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
5 changes: 1 addition & 4 deletions contract/Forest/ForestContractState.cs
Original file line number Diff line number Diff line change
Expand Up @@ -42,10 +42,7 @@ public partial class ForestContractState : ContractState
/// <summary>
/// Symbol -> Token Id -> Royalty
/// </summary>
public MappedState<string, int> RoyaltyMap { get; set; }

public MappedState<string, Address> RoyaltyFeeReceiverMap { get; set; }
public MappedState<string, CertainNFTRoyaltyInfo> CertainNFTRoyaltyMap { get; set; }
public MappedState<string, RoyaltyInfo> RoyaltyInfoMap { get; set; }
public MappedState<string, StringList> TokenWhiteListMap { get; set; }

/// <summary>
Expand Down
30 changes: 6 additions & 24 deletions contract/Forest/ForestContract_Creators.cs
Original file line number Diff line number Diff line change
Expand Up @@ -9,37 +9,19 @@ public partial class ForestContract
public override Empty SetRoyalty(SetRoyaltyInput input)
{
AssertContractInitialized();

// 0% - 10%
Assert(0 <= input.Royalty && input.Royalty <= 1000, "Royalty should be between 0% to 10%.");
var nftCollectionInfos = State.TokenContract.GetTokenInfo.Call(new GetTokenInfoInput
{
Symbol = input.Symbol
});
State.TokenContract.GetTokenInfo.Call(new GetTokenInfoInput
{
Symbol = input.Symbol
});

var nftCollectionInfo = State.TokenContract.GetTokenInfo.Call(new GetTokenInfoInput
AssertSenderIsAdmin();
Assert(0 <= input.Royalty && input.Royalty < FeeDenominator, "Royalty should be between 0% to 100%.");
var tokenInfo = State.TokenContract.GetTokenInfo.Call(new GetTokenInfoInput
{
Symbol = input.Symbol,
});
Assert(!string.IsNullOrEmpty(nftCollectionInfo.Symbol), "NFT Collection not found.");
Assert(!string.IsNullOrEmpty(tokenInfo.Symbol), "TokenInfo not found.");

var nftInfo = State.TokenContract.GetTokenInfo.Call(new GetTokenInfoInput
State.RoyaltyInfoMap[input.Symbol] = new RoyaltyInfo()
{
Symbol = input.Symbol,
});

Assert(nftCollectionInfo.Issuer == Context.Sender || nftInfo.Issuer == Context.Sender, "No permission.");
State.CertainNFTRoyaltyMap[input.Symbol] = new CertainNFTRoyaltyInfo
{
IsManuallySet = true,
RoyaltyFeeReceiver = input.RoyaltyFeeReceiver,
Royalty = input.Royalty
};

State.RoyaltyFeeReceiverMap[input.Symbol] = input.RoyaltyFeeReceiver;
return new Empty();
}

Expand Down
31 changes: 31 additions & 0 deletions contract/Forest/ForestContract_Helpers.cs
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,15 @@ private void PerformDeal(PerformDealInput performDealInput)
Assert(performDealInput.NFTFrom != performDealInput.NFTTo, "NFT From address cannot be NFT To address.");
var serviceFee = performDealInput.PurchaseAmount.Mul(State.ServiceFeeRate.Value).Div(FeeDenominator);
var actualAmount = performDealInput.PurchaseAmount.Sub(serviceFee);

var royaltyFee = 0l;
var royaltyInfo = GetRoyaltyInfo(performDealInput.NFTSymbol);
if (royaltyInfo != null && royaltyInfo.Royalty > 0 && royaltyInfo.RoyaltyFeeReceiver != null)
{
royaltyFee = performDealInput.PurchaseAmount.Mul(royaltyInfo.Royalty).Div(FeeDenominator);
actualAmount = actualAmount.Sub(royaltyFee);
}

if (actualAmount != 0)
{
State.TokenContract.TransferFrom.Send(new TransferFromInput
Expand All @@ -36,6 +45,17 @@ private void PerformDeal(PerformDealInput performDealInput)
Amount = serviceFee
});
}
//RoyaltyFee
if (royaltyFee > 0 && performDealInput.NFTTo != royaltyInfo.RoyaltyFeeReceiver)
{
State.TokenContract.TransferFrom.Send(new TransferFromInput
{
From = performDealInput.NFTTo,
To = royaltyInfo.RoyaltyFeeReceiver,
Symbol = performDealInput.PurchaseSymbol,
Amount = royaltyFee
});
}
}

State.TokenContract.TransferFrom.Send(new TransferFromInput
Expand Down Expand Up @@ -385,4 +405,15 @@ private void RequireMaxBatchCancelListCountSet()
State.MaxBatchCancelListCount.Value = DefaultMaxBatchCancelListCount;
}
}

private RoyaltyInfo GetRoyaltyInfo(string nftSymbol)
{
var symbol = TransferCollectionSymbol(nftSymbol);
if (string.IsNullOrEmpty(symbol))
{
return null;
}

return State.RoyaltyInfoMap[symbol];
}
}
18 changes: 1 addition & 17 deletions contract/Forest/ForestContract_Views.cs
Original file line number Diff line number Diff line change
Expand Up @@ -64,23 +64,7 @@ public override StringList GetGlobalTokenWhiteList(Empty input)

public override RoyaltyInfo GetRoyalty(GetRoyaltyInput input)
{
var royaltyInfo = new RoyaltyInfo
{
Royalty = State.RoyaltyMap[input.Symbol]
};

var certainNftRoyalty = State.CertainNFTRoyaltyMap[input.Symbol] ?? new CertainNFTRoyaltyInfo();
if (certainNftRoyalty.IsManuallySet)
{
royaltyInfo.Royalty = certainNftRoyalty.Royalty;
}

if (State.RoyaltyFeeReceiverMap[input.Symbol] != null)
{
royaltyInfo.RoyaltyFeeReceiver = State.RoyaltyFeeReceiverMap[input.Symbol];
}

return royaltyInfo;
return State.RoyaltyInfoMap[input.Symbol];
}

public override ServiceFeeInfo GetServiceFeeInfo(Empty input)
Expand Down
5 changes: 0 additions & 5 deletions protobuf/forest_contract.proto
Original file line number Diff line number Diff line change
Expand Up @@ -212,11 +212,6 @@ message DealInfo {
google.protobuf.Timestamp deal_time = 7;
}

message CertainNFTRoyaltyInfo {
bool is_manually_set = 1;
int32 royalty = 2;
}

message ServiceFeeInfo {
int32 service_fee_rate = 1;
aelf.Address service_fee_receiver = 2;
Expand Down
56 changes: 56 additions & 0 deletions test/Forest.Tests/ForestContractTests_List.cs
Original file line number Diff line number Diff line change
Expand Up @@ -3260,4 +3260,60 @@ public async void BatchCancelListTest()

}
}

[Fact]
public async void RoyaltyTest()
{
await InitializeForestContract();
await PrepareNftData();

await AdminForestContractStub.SetRoyalty.SendAsync(new SetRoyaltyInput()
{
Symbol = "TESTNFT-0",
RoyaltyFeeReceiver = User1Address,
Royalty = 250
});

var royaltyInfo = await ForestContractStub.GetRoyalty.CallAsync(new GetRoyaltyInput()
{
Symbol = "TESTNFT-0"
});
royaltyInfo.ShouldNotBeNull();
royaltyInfo.Royalty.ShouldBe(250);
royaltyInfo.RoyaltyFeeReceiver.ShouldBe(User1Address);

//exception
// var exception = await Assert.ThrowsAsync<Exception>(act);
var result = await AdminForestContractStub.SetRoyalty.SendWithExceptionAsync(new SetRoyaltyInput()
{
Symbol = "TEST-0",
RoyaltyFeeReceiver = User1Address,
Royalty = 250
});
result.TransactionResult.Error.ShouldContain("TokenInfo not found");

result = await AdminForestContractStub.SetRoyalty.SendWithExceptionAsync(new SetRoyaltyInput()
{
Symbol = "TESTNFT-0",
RoyaltyFeeReceiver = User1Address,
Royalty = -250
});
result.TransactionResult.Error.ShouldContain("Royalty should be between");

result = await AdminForestContractStub.SetRoyalty.SendWithExceptionAsync(new SetRoyaltyInput()
{
Symbol = "TESTNFT-0",
RoyaltyFeeReceiver = User1Address,
Royalty = 10000
});
result.TransactionResult.Error.ShouldContain("Royalty should be between");

result = await Seller1ForestContractStub.SetRoyalty.SendWithExceptionAsync(new SetRoyaltyInput()
{
Symbol = "TESTNFT-0",
RoyaltyFeeReceiver = User1Address,
Royalty = 10000
});
result.TransactionResult.Error.ShouldContain("No permission");
}
}
Loading