Skip to content

Commit

Permalink
Merge pull request onflow#6394 from onflow/bastian/recover-nft-collec…
Browse files Browse the repository at this point in the history
…tion

[FVM] Recover NFT Collections
  • Loading branch information
turbolent authored Aug 26, 2024
2 parents d040300 + 2d1a41d commit e211841
Show file tree
Hide file tree
Showing 2 changed files with 84 additions and 49 deletions.
54 changes: 6 additions & 48 deletions cmd/util/ledger/migrations/contract_checking_migration_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -31,40 +31,8 @@ func oldExampleFungibleTokenCode(fungibleTokenAddress flow.Address) string {
pub contract ExampleFungibleToken: FungibleToken {
pub var totalSupply: UFix64
pub resource Vault: FungibleToken.Provider, FungibleToken.Receiver, FungibleToken.Balance {
pub resource Vault {
pub var balance: UFix64
init(balance: UFix64) {
self.balance = balance
}
pub fun withdraw(amount: UFix64): @FungibleToken.Vault {
self.balance = self.balance - amount
emit TokensWithdrawn(amount: amount, from: self.owner?.address)
return <-create Vault(balance: amount)
}
pub fun deposit(from: @FungibleToken.Vault) {
let vault <- from as! @ExampleToken.Vault
self.balance = self.balance + vault.balance
emit TokensDeposited(amount: vault.balance, to: self.owner?.address)
vault.balance = 0.0
destroy vault
}
destroy() {
if self.balance > 0.0 {
ExampleToken.totalSupply = ExampleToken.totalSupply - self.balance
}
}
}
pub fun createEmptyVault(): @Vault {
return <-create Vault(balance: 0.0)
}
init() {
self.totalSupply = 0.0
}
}
`,
Expand All @@ -79,26 +47,16 @@ func oldExampleNonFungibleTokenCode(fungibleTokenAddress flow.Address) string {
pub contract ExampleNFT: NonFungibleToken {
/// Total supply of ExampleNFTs in existence
pub var totalSupply: UInt64
/// The core resource that represents a Non Fungible Token.
/// New instances will be created using the NFTMinter resource
/// and stored in the Collection resource
///
pub resource NFT: NonFungibleToken.INFT {
/// The unique ID that each NFT has
pub let id: UInt64
pub resource NFT {
init(id: UInt64) {
self.id = id
}
pub let id: UInt64
}
init() {
// Initialize the total supply
self.totalSupply = 0
pub resource Collection {
pub var ownedNFTs: @{UInt64: NonFungibleToken.NFT}
}
}
`,
Expand Down
79 changes: 78 additions & 1 deletion fvm/environment/program_recovery.go
Original file line number Diff line number Diff line change
Expand Up @@ -185,6 +185,47 @@ func RecoveredNonFungibleTokenCode(nonFungibleTokenAddress common.Address, contr
%[2]s.recoveryPanic("NFT.createEmptyCollection")
}
}
access(all)
resource Collection: NonFungibleToken.Collection {
access(all)
var ownedNFTs: @{UInt64: {NonFungibleToken.NFT}}
init() {
self.ownedNFTs <- {}
}
access(all)
fun deposit(token: @{NonFungibleToken.NFT}) {
%[2]s.recoveryPanic("Collection.deposit")
}
access(all)
view fun getSupportedNFTTypes(): {Type: Bool} {
%[2]s.recoveryPanic("Collection.getSupportedNFTTypes")
}
access(all)
view fun isSupportedNFTType(type: Type): Bool {
%[2]s.recoveryPanic("Collection.isSupportedNFTType")
}
access(NonFungibleToken.Withdraw)
fun withdraw(withdrawID: UInt64): @{NonFungibleToken.NFT} {
%[2]s.recoveryPanic("Collection.withdraw")
}
access(all)
view fun borrowNFT(_ id: UInt64): &{NonFungibleToken.NFT}? {
%[2]s.recoveryPanic("Collection.borrowNFT")
}
access(all)
fun createEmptyCollection(): @{NonFungibleToken.Collection} {
%[2]s.recoveryPanic("Collection.createEmptyCollection")
}
}
}
`,
nonFungibleTokenAddress.HexWithPrefix(),
Expand Down Expand Up @@ -250,6 +291,8 @@ const nonFungibleTokenTypeIdentifier = "NonFungibleToken"
const nonFungibleTokenTypeTotalSupplyFieldName = "totalSupply"
const nonFungibleTokenNFTTypeIdentifier = "NFT"
const nonFungibleTokenNFTTypeIDFieldName = "id"
const nonFungibleTokenCollectionTypeIdentifier = "Collection"
const nonFungibleTokenCollectionTypeOwnedNFTsFieldName = "ownedNFTs"

func isFungibleTokenContract(program *ast.Program, fungibleTokenAddress common.Address) bool {

Expand Down Expand Up @@ -329,7 +372,10 @@ func isNonFungibleTokenContract(program *ast.Program, nonFungibleTokenAddress co
}

// Check if the contract has an NFT resource
nftDeclaration := contractDeclaration.Members.CompositesByIdentifier()[nonFungibleTokenNFTTypeIdentifier]

nestedComposites := contractDeclaration.Members.CompositesByIdentifier()

nftDeclaration := nestedComposites[nonFungibleTokenNFTTypeIdentifier]
if nftDeclaration == nil {
return false
}
Expand All @@ -345,9 +391,40 @@ func isNonFungibleTokenContract(program *ast.Program, nonFungibleTokenAddress co
return false
}

// Check if the contract has a Collection resource
collectionDeclaration := nestedComposites[nonFungibleTokenCollectionTypeIdentifier]
if collectionDeclaration == nil {
return false
}

// Check if the Collection resource has an ownedNFTs field
ownedNFTsFieldDeclaration := getField(collectionDeclaration, nonFungibleTokenCollectionTypeOwnedNFTsFieldName)
if ownedNFTsFieldDeclaration == nil {
return false
}

// Check if the ownedNFTs field is of type {UInt64: NonFungibleToken.NFT} (NOTE: old syntax)
ownedNFTsFieldType := ownedNFTsFieldDeclaration.TypeAnnotation.Type
ownedNFTsFieldDictionaryType, ok := ownedNFTsFieldType.(*ast.DictionaryType)
if !ok ||
!isNominalType(ownedNFTsFieldDictionaryType.KeyType, sema.UInt64TypeName) ||
!isNonFungibleTokenNFTNominalType(ownedNFTsFieldDictionaryType.ValueType) {

return false
}

return true
}

// isNonFungibleTokenNFTNominalType checks if the given type is a nominal type representing `NonFungibleToken.NFT`
func isNonFungibleTokenNFTNominalType(ty ast.Type) bool {
nominalType, ok := ty.(*ast.NominalType)
return ok &&
nominalType.Identifier.Identifier == nonFungibleTokenTypeIdentifier &&
len(nominalType.NestedIdentifiers) == 1 &&
nominalType.NestedIdentifiers[0].Identifier == nonFungibleTokenNFTTypeIdentifier
}

func getField(declaration *ast.CompositeDeclaration, name string) *ast.FieldDeclaration {
for _, fieldDeclaration := range declaration.Members.Fields() {
if fieldDeclaration.Identifier.Identifier == name {
Expand Down

0 comments on commit e211841

Please sign in to comment.