-
Notifications
You must be signed in to change notification settings - Fork 250
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
base: master
Are you sure you want to change the base?
Conversation
There was a problem hiding this 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
token_ids: Vec<TokenId>, | ||
amounts: Vec<U128>, |
There was a problem hiding this comment.
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)>
?
There was a problem hiding this comment.
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>> { |
There was a problem hiding this comment.
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"]]
)
There was a problem hiding this comment.
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.
There was a problem hiding this comment.
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
There was a problem hiding this comment.
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
There was a problem hiding this comment.
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
near-contract-standards/src/multi_token/approval/approval_impl.rs
Outdated
Show resolved
Hide resolved
near-contract-standards/src/multi_token/approval/approval_impl.rs
Outdated
Show resolved
Hide resolved
There was a problem hiding this 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!
examples/multi-token/mt/src/lib.rs
Outdated
|
||
#[init] | ||
pub fn new(owner_id: AccountId, metadata: MtContractMetadata) -> Self { | ||
require!(!env::state_exists(), "Already initialized"); |
There was a problem hiding this comment.
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:
require!(!env::state_exists(), "Already initialized"); |
There was a problem hiding this comment.
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>> { |
There was a problem hiding this comment.
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
Update: |
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; | ||
} |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
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, | |
}; |
There was a problem hiding this comment.
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) |
There was a problem hiding this comment.
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?
There was a problem hiding this comment.
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>, |
There was a problem hiding this comment.
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?
There was a problem hiding this comment.
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); |
There was a problem hiding this comment.
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);
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Fixed
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 |
@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. |
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):