-
Notifications
You must be signed in to change notification settings - Fork 309
feat(satp): add support for NFTs #3965
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
base: satp-stg
Are you sure you want to change the base?
feat(satp): add support for NFTs #3965
Conversation
2ba814c
to
52ac5ad
Compare
TokenType tt = tokens[tokenId].tokenType; | ||
if(tt == TokenType.NONSTANDARD_FUNGIBLE || tt == TokenType.ERC20) { | ||
if(tokens[tokenId].amount > 0) { | ||
revert TokenLocked(tokenId); | ||
} | ||
} | ||
else if(tt == TokenType.NONSTANDARD_NONFUNGIBLE || tt == TokenType.ERC721) { | ||
if(tokens[tokenId].amount != 0) { | ||
revert TokenLocked(tokenId); | ||
} | ||
} | ||
else { | ||
revert TokenTypeNotSupported(tokenId); |
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.
Shouldn't this be fungible and not fungible? Will the other standards have different APIs?
And for the contracts that are both fungible and not fungible?
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.
TokensType were reduced, to keep track with the TokenTypes used by SATP. Also, the ERC standards were separated as another independent element that composes an asset
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.
Please take a look at the comments.
if(lockSuccess) { | ||
// The locked amount is added to the amount of the token struct | ||
tokens[tokenId].amount += amount; | ||
emit Lock(tokenId, amount); | ||
TokenType tt = tokens[tokenId].tokenType; | ||
if (tt == TokenType.ERC20 || tt == TokenType.NONSTANDARD_FUNGIBLE) { | ||
// The locked amount is added to the amount of the token struct | ||
tokens[tokenId].amount += assetAttribute; | ||
} | ||
else if (tt == TokenType.ERC721 || tt == TokenType.NONSTANDARD_NONFUNGIBLE) { | ||
// When dealing with non-fungible tokens, the "amount" is interpreted as the token unique descriptor. | ||
tokens[tokenId].amount = assetAttribute; | ||
} | ||
emit Lock(tokenId, assetAttribute); |
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.
If it is not of those types, what happens?
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.
^ do error handling
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.
Done
|
||
if (token.amount == undefined) { | ||
throw new AmountMissingError(fnTag); | ||
let token: FungibleAsset | NonFungibleAsset; |
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.
why don't you abstract this with a class name asset, and the fungible and non fungible are extensions of the same?
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.
That would be probably the right thing to do, because in the future it will be easier to add new features. @Tomas-Silva2187, @LordKubaya, I propose a middle term, which is leaving this solution (provided it works), and @Tomas-Silva2187 creates a detailed issue with a proposal to implement what @LordKubaya proposed.
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.
Done as @LordKubaya suggested
switch (asset.type) { | ||
case TokenType.ERC20: | ||
case TokenType.NONSTANDARD_FUNGIBLE: | ||
protoAsset.amount = BigInt((asset as EvmFungibleAsset).amount); | ||
protoAsset.contractAddress = ( | ||
asset as EvmFungibleAsset | ||
).contractAddress; | ||
break; | ||
case TokenType.ERC721: | ||
case TokenType.NONSTANDARD_NONFUNGIBLE: | ||
protoAsset.amount = BigInt( | ||
(asset as EvmNonFungibleAsset).uniqueDescriptor, | ||
); | ||
protoAsset.contractAddress = ( | ||
asset as EvmNonFungibleAsset | ||
).contractAddress; | ||
break; | ||
default: | ||
throw new Error(`Unsupported asset type ${asset.type}`); | ||
} |
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 see these lines repeated multiple times. Maybe create a method to reduce redundancy?
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.
Done
return this.wrapperContractName; | ||
case "NONFUNGIBLE": | ||
//TODO implement | ||
//TODO implement: can be the same wrapper of fungible assets |
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.
Why leave this comment? Isn't this PR the implementation of that?
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 PR only implements NFT support for Ethereum and Besu. A new issue was created for Fabric support to be later implemented.
//Uncomment following snippet to allow non fungible tokens to be returned | ||
/*return { | ||
type: Number(token.tokenType), | ||
id: token.tokenId, | ||
referenceId: token.referenceId, | ||
owner: token.owner, | ||
mspId: token.mspId, | ||
channelName: token.channelName, | ||
contractName: token.contractName, | ||
uniqueDescriptor: token.amount.toString(), | ||
network: this.networkIdentification, | ||
} as FabricNonFungibleAsset;*/ |
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.
Why are these lines commented?
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.
Cleared
/*function testApprove() public { | ||
uint256 tokenId = 1001; | ||
vm.prank(bridge); | ||
satpContract.mint(user, tokenId); | ||
vm.startPrank(user); | ||
satpContract.approveAsset(bridge, tokenId); | ||
vm.stopPrank(); | ||
vm.prank(bridge); | ||
bool allowance = satpContract.checkAssignment(bridge, tokenId); | ||
assertEq(allowance, true, "Approval allowance mismatch"); | ||
}*/ |
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.
why is this test commented
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.
The test was corrected, and is now working
//function testMint() public { | ||
//wrapperContract.wrap(contract1.name(), address(contract1), TokenType.NONSTANDARD_FUNGIBLE, contract1.name(), "refID", address(this), signatures); | ||
//wrapperContract.mint(contract1.name(), 10); | ||
|
||
//assertEq(contract1.balanceOf(address(this)), 10, "Token not minted"); | ||
//} |
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.
why is this test commented?
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.
The test was corrected and is now working
bytecode: contract.bytecode.object, | ||
}, | ||
keychainId: this.keychainPlugin1.getKeychainId(), | ||
keychainId: this.keychainPluginFungible.getKeychainId(), // TODO: Should we use a different keyChainId for the oracle? |
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.
Why do you think we should?
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 comment was due to a misunderstanding. Comment was cleared, and this keychain can still be used for both fungible contracts and oracle contracts
...gin-satp-hermes/src/test/typescript/integration/gateway/satp-e2e-transfer-2-gateways.test.ts
Outdated
Show resolved
Hide resolved
}, | ||
[ | ||
{ | ||
assetType: SupportedBesuContractTypes.FUNGIBLE, |
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.
Why is this marked as an assetType fungible? Is this even an asset?
Can you fix this?
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. ORACLE was defined as a SupportedContractType, to allow for a more logical support of this change to the test environments.
error InsuficientAmountLocked(string tokenId, uint256 amount); | ||
|
||
error TokenTypeNotSupported(string tokenId); | ||
|
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.
Possibly a better name is TokenNotSupported, because we only know what types we support.
error TokenNotSupported(string tokenId); |
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.
Done
if(tokens[tokenId].amount > 0) { | ||
revert TokenLocked(tokenId); | ||
TokenType tt = tokens[tokenId].tokenType; | ||
if(tt == TokenType.NONSTANDARD_FUNGIBLE || tt == TokenType.ERC20) { |
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 do you mean by non standard fungible?
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.
We support erc721 (non fungible) tokens and erc20 (fungible). we should probably refer to the specific standards that we support
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.
Maybe in this situation, tokens that do not follow the ERC APIs
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.
TokenTypes were reduced to Nonstandard Fungible and Nonstandard NonFungible. They kept this nomenclature due to an error in CBDC that occurs when these names are changed. As for the token standards, they were defined as new attribute of assets, independent of the TokenType.
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.
Thank you very much for your contribution. Overall, this is an impressive contribution. Code is clean, tests are detailed and address the implemented functionality. Please do add documentation on your added feature - namely the "assetAttribute". It is understandable that this parameter is a generalization of the old "amount", but the different supported values should be well-documented.
Prior to merge, please address my and Carlo's comments - we can use the PR threads to discuss.
Great work!
if(tokens[tokenId].amount > 0) { | ||
revert TokenLocked(tokenId); | ||
TokenType tt = tokens[tokenId].tokenType; | ||
if(tt == TokenType.NONSTANDARD_FUNGIBLE || tt == TokenType.ERC20) { |
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.
We support erc721 (non fungible) tokens and erc20 (fungible). we should probably refer to the specific standards that we support
if(lockSuccess) { | ||
// The locked amount is added to the amount of the token struct | ||
tokens[tokenId].amount += amount; | ||
emit Lock(tokenId, amount); | ||
TokenType tt = tokens[tokenId].tokenType; | ||
if (tt == TokenType.ERC20 || tt == TokenType.NONSTANDARD_FUNGIBLE) { | ||
// The locked amount is added to the amount of the token struct | ||
tokens[tokenId].amount += assetAttribute; | ||
} | ||
else if (tt == TokenType.ERC721 || tt == TokenType.NONSTANDARD_NONFUNGIBLE) { | ||
// When dealing with non-fungible tokens, the "amount" is interpreted as the token unique descriptor. | ||
tokens[tokenId].amount = assetAttribute; | ||
} | ||
emit Lock(tokenId, assetAttribute); |
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.
^ do error handling
// upon minting, the minted attribute is set as the value that was minted | ||
// (may it be amount for fungibles, or uniqueDescriptor for non-fungibles) | ||
tokens[tokenId].amount = assetAttribute; | ||
emit Mint(tokenId, assetAttribute); |
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 does this asset attribute work, in particular? Please add documentation on what is the goal of this attribute and how we use it for different standards
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.
Done
* @param assetAttribute The asset attribute of tokens to be encoded. | ||
*/ | ||
function encodeParams(VarType[] memory variables, string memory tokenId, address receiver, uint256 amount) internal view returns (bytes[] memory){ | ||
function encodeParams(VarType[] memory variables, string memory tokenId, address receiver, uint256 assetAttribute) internal view returns (bytes[] memory){ |
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 function should have a more specific name since it is not general-purpose encoding
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.
Done. It was renamed as AssetParameterIdentifierEncoder, as it encodes custom parameters defined for the assets, to use them in contract calls.
|
||
if (token.amount == undefined) { | ||
throw new AmountMissingError(fnTag); | ||
let token: FungibleAsset | NonFungibleAsset; |
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.
That would be probably the right thing to do, because in the future it will be easier to add new features. @Tomas-Silva2187, @LordKubaya, I propose a middle term, which is leaving this solution (provided it works), and @Tomas-Silva2187 creates a detailed issue with a proposal to implement what @LordKubaya proposed.
throw new WrapperContractError( | ||
`${fnTag}, Wrapper Contract not deployed`, | ||
); | ||
switch (asset.type) { |
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.
same comments as in besu-leaf
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 has been addressed
network: NetworkId; | ||
} | ||
|
||
export type Brand<K, T> = K & { __brand: T }; |
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.
nice solution
export interface EvmFungibleAsset extends EvmAsset, FungibleAsset {} | ||
export interface EvmNonFungibleAsset extends EvmAsset, NonFungibleAsset {} | ||
|
||
export enum VarType { |
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.
perhaps assetParameterIdentifier would be a more accurate name?
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.
Done
|
||
const proof = await this.bridgeEndPoint.getProof( | ||
asset, | ||
this.claimType, |
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 looks great. However there is quite a lot of code duplication. Could you extract the functionality into a function that can be reused in these different related functionalities?
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.
Done
/** | ||
* @title SATPTokenContract | ||
* The SATPTokenContract is an example of a custom ERC721 token contract. | ||
* This is a simple contract, meaning it uses functions that assume everything is correct. |
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 do you mean that functions "assume everything is correct"?
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.
Initially, the NFT contract was an unsafe OpenZeppelin version, which used functions that did not pre-check if the function caller had authorization over the asset it was interacting with. The currently implemented version uses safe functions, and the approve action, which is required to deal with these aspects of authorization, is implemented.
4cacbaf
to
44f3bb4
Compare
c548d20
to
a03c7b0
Compare
6617318
to
83f9482
Compare
1cb5caf
to
392aa6e
Compare
f14db8d
to
45e1e33
Compare
88864ef
to
e7f952b
Compare
59d6d7d
to
b830efc
Compare
724038e
to
bd7f382
Compare
b830efc
to
78bc4c6
Compare
b3bb285
to
a14f896
Compare
Signed-off-by: Tomás Silva <[email protected]>
78bc4c6
to
26c13ed
Compare
@Tomas-Silva2187 most items seem to have been addressed. Could you please confirm that you addressed all comments (I see some items without a response). Thank you! |
9bee4bd
to
1ae6a34
Compare
feat(satp): add support for NFTs
Implements support for cross-chain transfers of NFTs with Ethereum and Besu Leafs.
CLOSES #3869
Pull Request Requirements
upstream/main
branch and squashed into single commit to help maintainers review it more efficient and to avoid spaghetti git commit graphs that obfuscate which commit did exactly what change, when and, why.-s
flag when usinggit commit
command. You may refer to this link for more information.Character Limit
A Must Read for Beginners
For rebasing and squashing, here's a must read guide for beginners.