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

Reference implementation for proposed Multi-token spec (NEP-245) #950

Draft
wants to merge 13 commits into
base: master
Choose a base branch
from

Conversation

uncle-T0ny
Copy link

@uncle-T0ny uncle-T0ny commented Oct 27, 2022

This PR is based on #776 implementing Multi-token Standard (near/NEPs#245, near/NEPs#227, near/NEPs#246)
with the updated codebase and the improvements.

The improvements list (by commits):

  1. MT token: update to the latest codebase
  • apply all the bugfixes in other standards that affect multi-token
  • update the MT by using a new way for XCC and use the "abi" feature
  1. MT token: fix mt_is_approved, make method viewable
  • in mt_is_approved view method can't use predecessor_account_id, added owner_id as a param
  1. MT token: fix the approval invalidation, reduce the approved amount
  • fixed an approve invalidation bug, also decrease the approved amount after the transfer
  1. MT token: added storage management, fix method types, refactor e2e tests
  • implement Storage Management for MT token
  • in some methods changed u128 to the wrapped version U128 for compatibility with the JS
  • improved e2e tests and added more cases

Copy link
Collaborator

@frol frol left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This is a massive PR, so I will review it in chunks. Please, bear with me

examples/multi-token/test-approval-receiver/src/lib.rs Outdated Show resolved Hide resolved
examples/multi-token/test-contract-defi/src/lib.rs Outdated Show resolved Hide resolved
Comment on lines +51 to +52
token_ids: Vec<TokenId>,
amounts: Vec<U128>,
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

What are the Pros of having these two fields instead of a single field with a Vec<(TokenId, U128)>?

Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

From my understanding the interface borrowed from eip-1155 method: onERC1155BatchReceived
I didn't find any proposal to change from the discussions and the meetings.

token_ids: Vec<TokenId>,
amounts: Vec<U128>,
msg: String,
) -> PromiseOrValue<Vec<U128>> {
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I would argue that it could be error-prone to only return balances without the token ids, so I would like to propose returning Vec<(TokenId, U128)>.

Also, the standard should define what to do if the returned list does not contain some of the tokens, i.e. if the request was [["TKN1", "10"], ["TKN2", "1"]] while the returned value is [["TKN2", "0"]] or even a completely empty list [], and while we are speaking of corner cases, what to do if the returned value mentions the token that was not in the input ([["TKN_NOT_REQUESTED", "3"]])

Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

From the NEP-245

 Take some action after receiving a multi token

 Requirements:
 * Contract MUST restrict calls to this function to a set of whitelisted  
   contracts
 * Contract MUST panic if `token_ids` length does not equals `amounts`
   length
 * Contract MUST panic if `previous_owner_ids` length does not equals `token_ids`
  length

But, you provided a nice case, I think It would be nice to add more test cases.

Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

from other side returning Vec<(TokenId, U128)> not safe because a predecessor can change the token

Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

from other side returning Vec<(TokenId, U128)> not safe because a predecessor can change the token

What do you mean?

Well, thinking about it a bit more, I feel that the list of amounts could be cheaper/easier to implement, so maybe it is not worth complicating things

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I kept parameters as-is based on the comments above. Agree with @frol that prioritizing simplicity makes sense here

Copy link
Collaborator

@frol frol left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Thanks for addressing the previous review comments!


#[init]
pub fn new(owner_id: AccountId, metadata: MtContractMetadata) -> Self {
require!(!env::state_exists(), "Already initialized");
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Since near-sdk-rs v3.0.1:

BREAKING #[init] now checks that the state is not initialized. This is expected behavior. To ignore state check you can call #[init(ignore_state)]

Thus, you don't need this check:

Suggested change
require!(!env::state_exists(), "Already initialized");

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

removed this

token_ids: Vec<TokenId>,
amounts: Vec<U128>,
msg: String,
) -> PromiseOrValue<Vec<U128>> {
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

from other side returning Vec<(TokenId, U128)> not safe because a predecessor can change the token

What do you mean?

Well, thinking about it a bit more, I feel that the list of amounts could be cheaper/easier to implement, so maybe it is not worth complicating things

@uncle-T0ny
Copy link
Author

Update:
Added extension to have a possibility to use Royalties and Payouts interface, like we have in NFT contract
MT: added token holders extension

Comment on lines 156 to 168
let grantee_to_approval = match by_owner.get(&owner_id) {
Some(grantee_to_approval) => grantee_to_approval,
None => return false,
};

let approval = match grantee_to_approval.get(&approved_account_id) {
Some(approval) => approval,
None => return false,
};

if !approval.amount.eq(&amount.into()) {
return false;
}

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
let grantee_to_approval = match by_owner.get(&owner_id) {
Some(grantee_to_approval) => grantee_to_approval,
None => return false,
};
let approval = match grantee_to_approval.get(&approved_account_id) {
Some(approval) => approval,
None => return false,
};
if !approval.amount.eq(&amount.into()) {
return false;
}
let approval = match by_owner
.get(&owner_id)
.and_then(|grantee_to_approval| grantee_to_approval.get(&approved_account_id))
{
Some(approval) if approval.amount.eq(&amount.into()) => approval,
_ => return false,
};

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Made this change


// Represents a temporary record of an Approval
// that was removed from the ApprovalContainer but may be restored in case of rollback in XCC.
// Values are (owner_id, approval_id, amount)

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think this tuple description should be changed, what do you think?

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Updated the description

pub extra_storage_in_bytes_per_emission: StorageUsage,

/// Owner of each token
pub owner_by_id: TreeMap<TokenId, AccountId>,

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

What is the reason of using TreeMap here?

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@Kirill-K-1 What do you propose is best data structure? Would you be fine with UnorderedMap so it can be iterated through?

self.owner_by_id.len() as u128 >= start_index,
"Out of bounds, please use a smaller from_index."
);
let limit = limit.map(|v| v as usize).unwrap_or(usize::MAX);

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

How about to use

let limit = limit.unwrap_or(u64::MAX);

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Fixed

@nearbuild
Copy link

Can we schedule a call in the Protocol Community call this month reviewing everything outlined in the Multitoken standard? https://near.social/#/devgovgigs.near/widget/gigs-board.pages.Post?id=376

@uint
Copy link
Contributor

uint commented Jul 11, 2023

@uncle-T0ny I'm looking through old PRs. Is this one something you'd like to keep working on and finalize? I understand if not - it looks like it moved slowly before.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

6 participants