From 13d1119d0e86897a61344100d631b1f7f2ff31ba Mon Sep 17 00:00:00 2001 From: HouseOfHufflepuff Date: Thu, 11 May 2023 19:17:37 -0700 Subject: [PATCH 1/6] WIP --- locked-nft/contracts/NFTLocker.cdc | 30 ++++++++-- locked-nft/lib/go/test/lockednft_test.go | 76 ++++++++++++++++++++++++ locked-nft/lib/go/test/scripts.go | 12 ++++ locked-nft/lib/go/test/templates.go | 8 +++ locked-nft/lib/go/test/test.go | 13 ---- locked-nft/lib/go/test/transactions.go | 29 +++++++++ locked-nft/lib/go/test/types.go | 8 +-- locked-nft/scripts/get_locked_token.cdc | 5 +- locked-nft/transactions/extend_lock.cdc | 17 ++++++ 9 files changed, 173 insertions(+), 25 deletions(-) create mode 100644 locked-nft/transactions/extend_lock.cdc diff --git a/locked-nft/contracts/NFTLocker.cdc b/locked-nft/contracts/NFTLocker.cdc index 6dca39e..0e62f3c 100644 --- a/locked-nft/contracts/NFTLocker.cdc +++ b/locked-nft/contracts/NFTLocker.cdc @@ -24,6 +24,12 @@ pub contract NFTLocker { from: Address?, nftType: Type ) + pub event NFTLockExtended( + id: UInt64, + lockedUntil: UInt64, + extendedDuration: UInt64, + nftType: Type + ) /// Named Paths /// @@ -44,8 +50,7 @@ pub contract NFTLocker { pub let id: UInt64 pub let owner: Address pub let lockedAt: UInt64 - pub let lockedUntil: UInt64 - pub let duration: UInt64 + pub var lockedUntil: UInt64 pub let nftType: Type init (id: UInt64, owner: Address, duration: UInt64, nftType: Type) { @@ -54,17 +59,19 @@ pub contract NFTLocker { self.owner = lockedToken.owner self.lockedAt = lockedToken.lockedAt self.lockedUntil = lockedToken.lockedUntil - self.duration = lockedToken.duration self.nftType = lockedToken.nftType } else { self.id = id self.owner = owner self.lockedAt = UInt64(getCurrentBlock().timestamp) self.lockedUntil = self.lockedAt + duration - self.duration = duration self.nftType = nftType } } + + pub fun extendLock(extendedDuration: UInt64) { + self.lockedUntil = self.lockedUntil + extendedDuration + } } pub fun getNFTLockerDetails(id: UInt64, nftType: Type): NFTLocker.LockedData? { @@ -95,6 +102,7 @@ pub contract NFTLocker { pub resource interface LockProvider { pub fun lock(token: @NonFungibleToken.NFT, duration: UInt64) pub fun unlock(id: UInt64, nftType: Type): @NonFungibleToken.NFT + pub fun extendLock(id: UInt64, nftType: Type, extendedDuration: UInt64) } /// An NFT Collection @@ -163,7 +171,7 @@ pub contract NFTLocker { to: self.owner?.address, lockedAt: lockedData.lockedAt, lockedUntil: lockedData.lockedUntil, - duration: lockedData.duration, + duration: duration, nftType: nftType ) @@ -172,6 +180,18 @@ pub contract NFTLocker { destroy oldToken } + pub fun extendLock(id: UInt64, nftType: Type, extendedDuration: UInt64) { + let lockedToken = (NFTLocker.lockedTokens[nftType]!)[id]! + lockedToken.extendLock(extendedDuration: extendedDuration) + + emit NFTLockExtended( + id: id, + lockedUntil: lockedToken.lockedUntil, + extendedDuration: extendedDuration, + nftType: nftType + ) + } + pub fun getIDs(nftType: Type): [UInt64]? { return self.lockedNFTs[nftType]?.keys } diff --git a/locked-nft/lib/go/test/lockednft_test.go b/locked-nft/lib/go/test/lockednft_test.go index 4951c34..7b49f6b 100644 --- a/locked-nft/lib/go/test/lockednft_test.go +++ b/locked-nft/lib/go/test/lockednft_test.go @@ -1,6 +1,7 @@ package test import ( + "fmt" emulator "github.com/onflow/flow-emulator" "github.com/stretchr/testify/assert" "testing" @@ -247,3 +248,78 @@ func testUnlockNFT( exampleNftID, ) } + +func TestExtendLock(t *testing.T) { + b := newEmulator() + contracts := NFTLockerDeployContracts(t, b) + t.Run("Should be able to extend the lock of an NFT", func(t *testing.T) { + testExtendLock( + t, + b, + contracts, + false, + ) + }) +} + +func testExtendLock( + t *testing.T, + b *emulator.Blockchain, + contracts Contracts, + shouldRevert bool, +) { + var duration uint64 = 10 + var extendedDuration uint64 = 1000 + userAddress, userSigner := createAccount(t, b) + setupNFTLockerAccount(t, b, userAddress, userSigner, contracts) + setupExampleNFT(t, b, userAddress, userSigner, contracts) + + exampleNftID := mintExampleNFT( + t, + b, + contracts, + false, + userAddress.String(), + ) + + lockedAt, lockedUntil := lockNFT( + t, + b, + contracts, + false, + userAddress, + userSigner, + exampleNftID, + duration, + ) + + assert.Equal(t, lockedAt+duration, lockedUntil) + + lockedData := getLockedTokenData( + t, + b, + contracts, + exampleNftID, + ) + + extendLock( + t, + b, + contracts, + false, + userAddress, + userSigner, + exampleNftID, + extendedDuration, + ) + + lockedData = getLockedTokenData( + t, + b, + contracts, + exampleNftID, + ) + + fmt.Println("blah") + fmt.Println(lockedData) +} diff --git a/locked-nft/lib/go/test/scripts.go b/locked-nft/lib/go/test/scripts.go index 4cf1559..3f89f86 100644 --- a/locked-nft/lib/go/test/scripts.go +++ b/locked-nft/lib/go/test/scripts.go @@ -27,3 +27,15 @@ func readLockedTokenByIDScript(contracts Contracts) []byte { contracts, ) } + +func getLockedTokenData( + t *testing.T, + b *emulator.Blockchain, + contracts Contracts, + id uint64, +) LockedData { + script := readLockedTokenByIDScript(contracts) + result := executeScriptAndCheck(t, b, script, [][]byte{jsoncdc.MustEncode(cadence.UInt64(id))}) + + return parseLockedData(result) +} diff --git a/locked-nft/lib/go/test/templates.go b/locked-nft/lib/go/test/templates.go index dacfe82..b745a95 100644 --- a/locked-nft/lib/go/test/templates.go +++ b/locked-nft/lib/go/test/templates.go @@ -41,6 +41,7 @@ const ( // NFTLocker GetLockedTokenByIDScriptPath = ScriptsRootPath + "/get_locked_token.cdc" LockNFTTxPath = TransactionsRootPath + "/lock_nft.cdc" + ExtendLockTxPath = TransactionsRootPath + "/extend_lock.cdc" UnlockNFTTxPath = TransactionsRootPath + "/unlock_nft.cdc" ) @@ -127,6 +128,13 @@ func lockNFTTransaction(contracts Contracts) []byte { ) } +func extendLockTransaction(contracts Contracts) []byte { + return replaceAddresses( + readFile(ExtendLockTxPath), + contracts, + ) +} + func unlockNFTTransaction(contracts Contracts) []byte { return replaceAddresses( readFile(UnlockNFTTxPath), diff --git a/locked-nft/lib/go/test/test.go b/locked-nft/lib/go/test/test.go index 7aeb68a..51475ba 100644 --- a/locked-nft/lib/go/test/test.go +++ b/locked-nft/lib/go/test/test.go @@ -4,7 +4,6 @@ import ( "io/ioutil" "testing" - jsoncdc "github.com/onflow/cadence/encoding/json" "github.com/onflow/flow-emulator/types" "github.com/onflow/cadence" @@ -303,15 +302,3 @@ func setupExampleNFT( false, ) } - -func getLockedTokenData( - t *testing.T, - b *emulator.Blockchain, - contracts Contracts, - id uint64, -) LockedData { - script := readLockedTokenByIDScript(contracts) - result := executeScriptAndCheck(t, b, script, [][]byte{jsoncdc.MustEncode(cadence.UInt64(id))}) - - return parseLockedData(result) -} diff --git a/locked-nft/lib/go/test/transactions.go b/locked-nft/lib/go/test/transactions.go index ffeed2f..af74709 100644 --- a/locked-nft/lib/go/test/transactions.go +++ b/locked-nft/lib/go/test/transactions.go @@ -136,3 +136,32 @@ func unlockNFT( ) fmt.Println(txResult) } + +func extendLock( + t *testing.T, + b *emulator.Blockchain, + contracts Contracts, + shouldRevert bool, + userAddress flow.Address, + userSigner crypto.Signer, + nftId uint64, + extendedDuration uint64, +) { + tx := flow.NewTransaction(). + SetScript(extendLockTransaction(contracts)). + SetGasLimit(100). + SetProposalKey(b.ServiceKey().Address, b.ServiceKey().Index, b.ServiceKey().SequenceNumber). + SetPayer(b.ServiceKey().Address). + AddAuthorizer(userAddress) + tx.AddArgument(cadence.UInt64(nftId)) + tx.AddArgument(cadence.UInt64(extendedDuration)) + + signer, _ := b.ServiceKey().Signer() + txResult := signAndSubmit( + t, b, tx, + []flow.Address{b.ServiceKey().Address, userAddress}, + []crypto.Signer{signer, userSigner}, + shouldRevert, + ) + fmt.Println(txResult) +} diff --git a/locked-nft/lib/go/test/types.go b/locked-nft/lib/go/test/types.go index 8d5f63f..b1f0557 100644 --- a/locked-nft/lib/go/test/types.go +++ b/locked-nft/lib/go/test/types.go @@ -5,18 +5,16 @@ import ( ) type LockedData struct { - Owner string + Id uint64 LockedAt uint64 LockedUntil uint64 - nftType string } func parseLockedData(value cadence.Value) LockedData { fields := value.(cadence.Struct).Fields return LockedData{ - fields[0].ToGoValue().(string), - fields[1].ToGoValue().(uint64), + fields[0].ToGoValue().(uint64), fields[2].ToGoValue().(uint64), - fields[3].ToGoValue().(string), + fields[3].ToGoValue().(uint64), } } diff --git a/locked-nft/scripts/get_locked_token.cdc b/locked-nft/scripts/get_locked_token.cdc index d3a2a15..df2f6df 100644 --- a/locked-nft/scripts/get_locked_token.cdc +++ b/locked-nft/scripts/get_locked_token.cdc @@ -1,5 +1,6 @@ import NFTLocker from "../contracts/NFTLocker.cdc" +import ExampleNFT from 0xEXAMPLENFTADDRESS -pub fun main(id: UInt64, nftType: Type): NFTLocker.LockedData? { - return NFTLocker.getNFTLockerDetails(id: id, nftType: nftType) +pub fun main(id: UInt64): NFTLocker.LockedData { + return NFTLocker.getNFTLockerDetails(id: id, nftType: Type<@ExampleNFT.NFT>())! } \ No newline at end of file diff --git a/locked-nft/transactions/extend_lock.cdc b/locked-nft/transactions/extend_lock.cdc new file mode 100644 index 0000000..8692882 --- /dev/null +++ b/locked-nft/transactions/extend_lock.cdc @@ -0,0 +1,17 @@ +import NFTLocker from "../contracts/NFTLocker.cdc" +import ExampleNFT from 0xEXAMPLENFTADDRESS + + +transaction(id: UInt64, extendedDuration: UInt64) { + let lockRef: &NFTLocker.Collection + + prepare(signer: AuthAccount) { + self.lockRef = signer + .borrow<&NFTLocker.Collection>(from: NFTLocker.CollectionStoragePath) + ?? panic("Account does not store an object at the specified path") + } + + execute { + self.lockRef.extendLock(id: id, nftType: Type<@ExampleNFT.NFT>(), extendedDuration: extendedDuration) + } +} \ No newline at end of file From 1d77c08a1e07387c2d2ba99cfe2ca0229b19e480 Mon Sep 17 00:00:00 2001 From: HouseOfHufflepuff Date: Fri, 12 May 2023 15:54:00 -0700 Subject: [PATCH 2/6] extend lock --- locked-nft/contracts/NFTLocker.cdc | 6 ++++++ locked-nft/lib/go/test/lockednft_test.go | 9 ++++----- 2 files changed, 10 insertions(+), 5 deletions(-) diff --git a/locked-nft/contracts/NFTLocker.cdc b/locked-nft/contracts/NFTLocker.cdc index 0e62f3c..c3df099 100644 --- a/locked-nft/contracts/NFTLocker.cdc +++ b/locked-nft/contracts/NFTLocker.cdc @@ -26,6 +26,7 @@ pub contract NFTLocker { ) pub event NFTLockExtended( id: UInt64, + lockedAt: UInt64, lockedUntil: UInt64, extendedDuration: UInt64, nftType: Type @@ -184,8 +185,13 @@ pub contract NFTLocker { let lockedToken = (NFTLocker.lockedTokens[nftType]!)[id]! lockedToken.extendLock(extendedDuration: extendedDuration) + let nestedLock = NFTLocker.lockedTokens[nftType]! + nestedLock[id] = lockedToken + NFTLocker.lockedTokens[nftType] = nestedLock + emit NFTLockExtended( id: id, + lockedAt: lockedToken.lockedAt, lockedUntil: lockedToken.lockedUntil, extendedDuration: extendedDuration, nftType: nftType diff --git a/locked-nft/lib/go/test/lockednft_test.go b/locked-nft/lib/go/test/lockednft_test.go index 7b49f6b..eab40df 100644 --- a/locked-nft/lib/go/test/lockednft_test.go +++ b/locked-nft/lib/go/test/lockednft_test.go @@ -1,7 +1,6 @@ package test import ( - "fmt" emulator "github.com/onflow/flow-emulator" "github.com/stretchr/testify/assert" "testing" @@ -295,7 +294,7 @@ func testExtendLock( assert.Equal(t, lockedAt+duration, lockedUntil) - lockedData := getLockedTokenData( + lockedDataPre := getLockedTokenData( t, b, contracts, @@ -313,13 +312,13 @@ func testExtendLock( extendedDuration, ) - lockedData = getLockedTokenData( + lockedDataPost := getLockedTokenData( t, b, contracts, exampleNftID, ) - fmt.Println("blah") - fmt.Println(lockedData) + assert.Equal(t, lockedAt+duration+extendedDuration, lockedDataPost.LockedUntil) + assert.Less(t, lockedDataPre.LockedUntil, lockedDataPost.LockedUntil) } From f2a89193cda27b46e2f054fdbf1a8fc3d42308f0 Mon Sep 17 00:00:00 2001 From: HouseOfHufflepuff Date: Fri, 12 May 2023 16:44:21 -0700 Subject: [PATCH 3/6] swap locks --- locked-nft/lib/go/test/lockednft_test.go | 109 ++++++++++++++++++++ locked-nft/lib/go/test/scripts.go | 24 +++++ locked-nft/lib/go/test/templates.go | 11 +- locked-nft/lib/go/test/transactions.go | 31 ++++++ locked-nft/scripts/examplenft/inventory.cdc | 4 +- locked-nft/scripts/inventory.cdc | 1 + locked-nft/transactions/swap_lock.cdc | 23 +++++ 7 files changed, 199 insertions(+), 4 deletions(-) create mode 100644 locked-nft/transactions/swap_lock.cdc diff --git a/locked-nft/lib/go/test/lockednft_test.go b/locked-nft/lib/go/test/lockednft_test.go index eab40df..c8e5f6f 100644 --- a/locked-nft/lib/go/test/lockednft_test.go +++ b/locked-nft/lib/go/test/lockednft_test.go @@ -322,3 +322,112 @@ func testExtendLock( assert.Equal(t, lockedAt+duration+extendedDuration, lockedDataPost.LockedUntil) assert.Less(t, lockedDataPre.LockedUntil, lockedDataPost.LockedUntil) } + +func TestSwapLock(t *testing.T) { + b := newEmulator() + contracts := NFTLockerDeployContracts(t, b) + t.Run("Should be able to swap the lock of an unlockable NFT", func(t *testing.T) { + testSwapLock( + t, + b, + contracts, + false, + ) + }) +} + +func testSwapLock( + t *testing.T, + b *emulator.Blockchain, + contracts Contracts, + shouldRevert bool, +) { + var duration uint64 = 0 + userAddress, userSigner := createAccount(t, b) + setupNFTLockerAccount(t, b, userAddress, userSigner, contracts) + setupExampleNFT(t, b, userAddress, userSigner, contracts) + + exampleNft1ID := mintExampleNFT( + t, + b, + contracts, + false, + userAddress.String(), + ) + + exampleNft2ID := mintExampleNFT( + t, + b, + contracts, + false, + userAddress.String(), + ) + + nftInventoryPre := getNFTInventory( + t, + b, + contracts, + userAddress, + ) + + assert.Equal(t, 2, len(nftInventoryPre)) + + lockedAt, lockedUntil := lockNFT( + t, + b, + contracts, + false, + userAddress, + userSigner, + exampleNft1ID, + duration, + ) + + assert.Equal(t, lockedAt+duration, lockedUntil) + + // fast-forward block time past unlock duration + for i := 1; i < 5; i++ { + time.Sleep(1 * time.Second) + b.CommitBlock() + } + + swapLock( + t, + b, + contracts, + false, + userAddress, + userSigner, + exampleNft1ID, + exampleNft2ID, + duration, + ) + + lockedDataNFT2 := getLockedTokenData( + t, + b, + contracts, + exampleNft2ID, + ) + + assert.Equal(t, lockedDataNFT2.LockedAt+duration, lockedDataNFT2.LockedUntil) + + nftInventoryPost := getNFTInventory( + t, + b, + contracts, + userAddress, + ) + + assert.Equal(t, true, arrayContains(nftInventoryPost, exampleNft1ID)) + assert.Equal(t, false, arrayContains(nftInventoryPost, exampleNft2ID)) +} + +func arrayContains(s []uint64, e uint64) bool { + for _, a := range s { + if a == e { + return true + } + } + return false +} diff --git a/locked-nft/lib/go/test/scripts.go b/locked-nft/lib/go/test/scripts.go index 3f89f86..b18432d 100644 --- a/locked-nft/lib/go/test/scripts.go +++ b/locked-nft/lib/go/test/scripts.go @@ -28,6 +28,13 @@ func readLockedTokenByIDScript(contracts Contracts) []byte { ) } +func readInventoryScript(contracts Contracts) []byte { + return replaceAddresses( + readFile(GetInventoryScriptPath), + contracts, + ) +} + func getLockedTokenData( t *testing.T, b *emulator.Blockchain, @@ -39,3 +46,20 @@ func getLockedTokenData( return parseLockedData(result) } + +func getNFTInventory( + t *testing.T, + b *emulator.Blockchain, + contracts Contracts, + userAddress flow.Address, +) []uint64 { + script := readInventoryScript(contracts) + result := executeScriptAndCheck(t, b, script, [][]byte{jsoncdc.MustEncode(cadence.NewAddress(userAddress))}) + var nftIds []uint64 + for _, val := range result.(cadence.Array).Values { + nftId := val.ToGoValue().(uint64) + nftIds = append(nftIds, nftId) + } + + return nftIds +} diff --git a/locked-nft/lib/go/test/templates.go b/locked-nft/lib/go/test/templates.go index b745a95..6a31fec 100644 --- a/locked-nft/lib/go/test/templates.go +++ b/locked-nft/lib/go/test/templates.go @@ -9,8 +9,6 @@ import ( "github.com/onflow/flow-go-sdk" ) -// Handle relative paths by making these regular expressions - const ( nftAddressPlaceholder = "\"[^\"]*NonFungibleToken.cdc\"" NFTLockerAddressPlaceholder = "\"[^\"]*NFTLocker.cdc\"" @@ -40,8 +38,10 @@ const ( // NFTLocker GetLockedTokenByIDScriptPath = ScriptsRootPath + "/get_locked_token.cdc" + GetInventoryScriptPath = ScriptsRootPath + "/examplenft/inventory.cdc" LockNFTTxPath = TransactionsRootPath + "/lock_nft.cdc" ExtendLockTxPath = TransactionsRootPath + "/extend_lock.cdc" + SwapLockTxPath = TransactionsRootPath + "/swap_lock.cdc" UnlockNFTTxPath = TransactionsRootPath + "/unlock_nft.cdc" ) @@ -135,6 +135,13 @@ func extendLockTransaction(contracts Contracts) []byte { ) } +func swapLockTransaction(contracts Contracts) []byte { + return replaceAddresses( + readFile(SwapLockTxPath), + contracts, + ) +} + func unlockNFTTransaction(contracts Contracts) []byte { return replaceAddresses( readFile(UnlockNFTTxPath), diff --git a/locked-nft/lib/go/test/transactions.go b/locked-nft/lib/go/test/transactions.go index af74709..0bf079a 100644 --- a/locked-nft/lib/go/test/transactions.go +++ b/locked-nft/lib/go/test/transactions.go @@ -165,3 +165,34 @@ func extendLock( ) fmt.Println(txResult) } + +func swapLock( + t *testing.T, + b *emulator.Blockchain, + contracts Contracts, + shouldRevert bool, + userAddress flow.Address, + userSigner crypto.Signer, + nftLockedID uint64, + nftToLockID uint64, + duration uint64, +) { + tx := flow.NewTransaction(). + SetScript(swapLockTransaction(contracts)). + SetGasLimit(100). + SetProposalKey(b.ServiceKey().Address, b.ServiceKey().Index, b.ServiceKey().SequenceNumber). + SetPayer(b.ServiceKey().Address). + AddAuthorizer(userAddress) + tx.AddArgument(cadence.UInt64(nftLockedID)) + tx.AddArgument(cadence.UInt64(nftToLockID)) + tx.AddArgument(cadence.UInt64(duration)) + + signer, _ := b.ServiceKey().Signer() + txResult := signAndSubmit( + t, b, tx, + []flow.Address{b.ServiceKey().Address, userAddress}, + []crypto.Signer{signer, userSigner}, + shouldRevert, + ) + fmt.Println(txResult) +} diff --git a/locked-nft/scripts/examplenft/inventory.cdc b/locked-nft/scripts/examplenft/inventory.cdc index cb02262..e54c8a9 100644 --- a/locked-nft/scripts/examplenft/inventory.cdc +++ b/locked-nft/scripts/examplenft/inventory.cdc @@ -1,5 +1,5 @@ -import NonFungibleToken from "../..contracts/NonFungibleToken.cdc" -import ExampleNFT from "../../contracts/ExampleNFT.cdc" +import ExampleNFT from 0xEXAMPLENFTADDRESS +import NonFungibleToken from "../contracts/NonFungibleToken.cdc" pub fun main(acctAddress: Address): [UInt64] { let nftOwner = getAccount(acctAddress); diff --git a/locked-nft/scripts/inventory.cdc b/locked-nft/scripts/inventory.cdc index d585778..440444b 100644 --- a/locked-nft/scripts/inventory.cdc +++ b/locked-nft/scripts/inventory.cdc @@ -1,4 +1,5 @@ import NFTLocker from "../contracts/NFTLocker.cdc" +import NonFungibleToken from "../contracts/NonFungibleToken.cdc" pub fun main(acctAddress: Address): [UInt64] { let nftOwner = getAccount(acctAddress); diff --git a/locked-nft/transactions/swap_lock.cdc b/locked-nft/transactions/swap_lock.cdc new file mode 100644 index 0000000..a682bdc --- /dev/null +++ b/locked-nft/transactions/swap_lock.cdc @@ -0,0 +1,23 @@ +import NonFungibleToken from "../contracts/NonFungibleToken.cdc" +import NFTLocker from "../contracts/NFTLocker.cdc" +import ExampleNFT from 0xEXAMPLENFTADDRESS + + +transaction(nftLockedID: UInt64, nftToLockID: UInt64, duration: UInt64) { + let exampleCollectionRef: &ExampleNFT.Collection + let lockRef: &NFTLocker.Collection + + prepare(signer: AuthAccount) { + self.exampleCollectionRef = signer + .borrow<&ExampleNFT.Collection>(from: ExampleNFT.CollectionStoragePath) + ?? panic("Could not borrow a reference to the owner's collection") + self.lockRef = signer + .borrow<&NFTLocker.Collection>(from: NFTLocker.CollectionStoragePath) + ?? panic("Account does not store an object at the specified path") + } + + execute { + self.exampleCollectionRef.deposit(token: <- self.lockRef.unlock(id: nftLockedID, nftType: Type<@ExampleNFT.NFT>())) + self.lockRef.lock(token: <- self.exampleCollectionRef.withdraw(withdrawID: nftToLockID), duration: duration) + } +} \ No newline at end of file From e69eed53a846927880e3d793c04b2b719e604371 Mon Sep 17 00:00:00 2001 From: HouseOfHufflepuff Date: Mon, 15 May 2023 16:53:04 -0700 Subject: [PATCH 4/6] pr feedback --- locked-nft/contracts/NFTLocker.cdc | 12 ++++++++++-- 1 file changed, 10 insertions(+), 2 deletions(-) diff --git a/locked-nft/contracts/NFTLocker.cdc b/locked-nft/contracts/NFTLocker.cdc index c3df099..174436a 100644 --- a/locked-nft/contracts/NFTLocker.cdc +++ b/locked-nft/contracts/NFTLocker.cdc @@ -53,24 +53,31 @@ pub contract NFTLocker { pub let lockedAt: UInt64 pub var lockedUntil: UInt64 pub let nftType: Type + pub let extension: {String: AnyStruct} - init (id: UInt64, owner: Address, duration: UInt64, nftType: Type) { + init (id: UInt64, owner: Address, duration: UInt64, nftType: Type, extension: {String: AnyStruct}) { if let lockedToken = (NFTLocker.lockedTokens[nftType]!)[id] { self.id = id self.owner = lockedToken.owner self.lockedAt = lockedToken.lockedAt self.lockedUntil = lockedToken.lockedUntil self.nftType = lockedToken.nftType + self.extension = lockedToken.extension } else { self.id = id self.owner = owner self.lockedAt = UInt64(getCurrentBlock().timestamp) self.lockedUntil = self.lockedAt + duration self.nftType = nftType + self.extension = extension } } pub fun extendLock(extendedDuration: UInt64) { + pre { + self.lockedUntil != 0: "nft not locked" + } + self.lockedUntil = self.lockedUntil + extendedDuration } } @@ -160,7 +167,8 @@ pub contract NFTLocker { id: id, owner: self.owner!.address, duration: duration, - nftType: nftType + nftType: nftType, + extension: {} ) nestedLock[id] = lockedData NFTLocker.lockedTokens[nftType] = nestedLock From ad7d075e060d939505d2841176872d465dcbcb75 Mon Sep 17 00:00:00 2001 From: HouseOfHufflepuff Date: Tue, 16 May 2023 09:38:02 -0700 Subject: [PATCH 5/6] added extend lock fail test --- locked-nft/contracts/NFTLocker.cdc | 19 +++++++--- locked-nft/lib/go/test/lockednft_test.go | 44 ++++++++++++++++++++++++ 2 files changed, 59 insertions(+), 4 deletions(-) diff --git a/locked-nft/contracts/NFTLocker.cdc b/locked-nft/contracts/NFTLocker.cdc index 174436a..142eac3 100644 --- a/locked-nft/contracts/NFTLocker.cdc +++ b/locked-nft/contracts/NFTLocker.cdc @@ -74,10 +74,6 @@ pub contract NFTLocker { } pub fun extendLock(extendedDuration: UInt64) { - pre { - self.lockedUntil != 0: "nft not locked" - } - self.lockedUntil = self.lockedUntil + extendedDuration } } @@ -98,6 +94,14 @@ pub contract NFTLocker { return false } + pub fun nftIsLocked(id: UInt64, nftType: Type): Bool { + if let lockedToken = (NFTLocker.lockedTokens[nftType]!)[id] { + return true + } + + return false + } + /// A public collection interface that returns the ids /// of nft locked for a given type /// @@ -190,6 +194,13 @@ pub contract NFTLocker { } pub fun extendLock(id: UInt64, nftType: Type, extendedDuration: UInt64) { + pre { + NFTLocker.nftIsLocked( + id: id, + nftType: nftType + ) == true : "token is not locked" + } + let lockedToken = (NFTLocker.lockedTokens[nftType]!)[id]! lockedToken.extendLock(extendedDuration: extendedDuration) diff --git a/locked-nft/lib/go/test/lockednft_test.go b/locked-nft/lib/go/test/lockednft_test.go index c8e5f6f..b44207d 100644 --- a/locked-nft/lib/go/test/lockednft_test.go +++ b/locked-nft/lib/go/test/lockednft_test.go @@ -323,6 +323,50 @@ func testExtendLock( assert.Less(t, lockedDataPre.LockedUntil, lockedDataPost.LockedUntil) } +func TestExtendLockFail(t *testing.T) { + b := newEmulator() + contracts := NFTLockerDeployContracts(t, b) + t.Run("Should fail to extend the lock of an NFT that has not been locked", func(t *testing.T) { + testExtendLockFail( + t, + b, + contracts, + true, + ) + }) +} + +func testExtendLockFail( + t *testing.T, + b *emulator.Blockchain, + contracts Contracts, + shouldRevert bool, +) { + var extendedDuration uint64 = 1000 + userAddress, userSigner := createAccount(t, b) + setupNFTLockerAccount(t, b, userAddress, userSigner, contracts) + setupExampleNFT(t, b, userAddress, userSigner, contracts) + + exampleNftID := mintExampleNFT( + t, + b, + contracts, + false, + userAddress.String(), + ) + + extendLock( + t, + b, + contracts, + shouldRevert, + userAddress, + userSigner, + exampleNftID, + extendedDuration, + ) +} + func TestSwapLock(t *testing.T) { b := newEmulator() contracts := NFTLockerDeployContracts(t, b) From 8cf660f4ccb6ebccd40587ea2eb52f6c79b27529 Mon Sep 17 00:00:00 2001 From: HouseOfHufflepuff Date: Wed, 17 May 2023 10:16:37 -0700 Subject: [PATCH 6/6] fix for map & additional tests --- locked-nft/contracts/NFTLocker.cdc | 9 +- locked-nft/lib/go/test/lockednft_test.go | 150 ++++++++++++++++++++++- 2 files changed, 154 insertions(+), 5 deletions(-) diff --git a/locked-nft/contracts/NFTLocker.cdc b/locked-nft/contracts/NFTLocker.cdc index 142eac3..5466cf0 100644 --- a/locked-nft/contracts/NFTLocker.cdc +++ b/locked-nft/contracts/NFTLocker.cdc @@ -126,6 +126,10 @@ pub contract NFTLocker { /// pub fun unlock(id: UInt64, nftType: Type): @NonFungibleToken.NFT { pre { + NFTLocker.nftIsLocked( + id: id, + nftType: nftType + ) == true : "token is not locked" NFTLocker.canUnlockToken( id: id, nftType: nftType @@ -134,8 +138,9 @@ pub contract NFTLocker { let token <- self.lockedNFTs[nftType]?.remove(key: id)!! - if let lockedToken = NFTLocker.lockedTokens[nftType] { - lockedToken.remove(key: id) + if let lockedType = NFTLocker.lockedTokens[nftType] { + lockedType.remove(key: id) + NFTLocker.lockedTokens[nftType] = lockedType } NFTLocker.totalLockedTokens = NFTLocker.totalLockedTokens - 1 diff --git a/locked-nft/lib/go/test/lockednft_test.go b/locked-nft/lib/go/test/lockednft_test.go index b44207d..7408d7c 100644 --- a/locked-nft/lib/go/test/lockednft_test.go +++ b/locked-nft/lib/go/test/lockednft_test.go @@ -78,11 +78,11 @@ func testLockNFT( assert.Equal(t, lockedAt+duration, lockedUntil) } -func TestReLockNFT(t *testing.T) { +func TestReLockNFTFail(t *testing.T) { b := newEmulator() contracts := NFTLockerDeployContracts(t, b) t.Run("Should fail to relock a locked nft", func(t *testing.T) { - testReLockNFT( + testReLockNFTFail( t, b, contracts, @@ -90,7 +90,7 @@ func TestReLockNFT(t *testing.T) { }) } -func testReLockNFT( +func testReLockNFTFail( t *testing.T, b *emulator.Blockchain, contracts Contracts, @@ -248,6 +248,78 @@ func testUnlockNFT( ) } +func TestReLockNFT(t *testing.T) { + b := newEmulator() + contracts := NFTLockerDeployContracts(t, b) + t.Run("Should be able to mint, lock, unlock, and lock an nft", func(t *testing.T) { + testReLockNFT( + t, + b, + contracts, + ) + }) +} + +func testReLockNFT( + t *testing.T, + b *emulator.Blockchain, + contracts Contracts, +) { + var duration uint64 = 0 + var relockDuration uint64 = 10 + userAddress, userSigner := createAccount(t, b) + setupNFTLockerAccount(t, b, userAddress, userSigner, contracts) + setupExampleNFT(t, b, userAddress, userSigner, contracts) + + exampleNftID := mintExampleNFT( + t, + b, + contracts, + false, + userAddress.String(), + ) + + lockedAt, lockedUntil := lockNFT( + t, + b, + contracts, + false, + userAddress, + userSigner, + exampleNftID, + duration, + ) + assert.Equal(t, lockedAt+duration, lockedUntil) + + // fast-forward block time past unlock duration + for i := 1; i < 5; i++ { + time.Sleep(1 * time.Second) + b.CommitBlock() + } + + unlockNFT( + t, + b, + contracts, + false, + userAddress, + userSigner, + exampleNftID, + ) + + lockedAt, lockedUntil = lockNFT( + t, + b, + contracts, + false, + userAddress, + userSigner, + exampleNftID, + relockDuration, + ) + assert.Equal(t, lockedAt+relockDuration, lockedUntil) +} + func TestExtendLock(t *testing.T) { b := newEmulator() contracts := NFTLockerDeployContracts(t, b) @@ -367,6 +439,78 @@ func testExtendLockFail( ) } +func TestExtendLockUnlockFail(t *testing.T) { + b := newEmulator() + contracts := NFTLockerDeployContracts(t, b) + t.Run("Should lock, unlock, then fail to extend lock", func(t *testing.T) { + testExtendLockUnlockFail( + t, + b, + contracts, + ) + }) +} + +func testExtendLockUnlockFail( + t *testing.T, + b *emulator.Blockchain, + contracts Contracts, +) { + var duration uint64 = 0 + var extendedDuration uint64 = 1000 + userAddress, userSigner := createAccount(t, b) + setupNFTLockerAccount(t, b, userAddress, userSigner, contracts) + setupExampleNFT(t, b, userAddress, userSigner, contracts) + + exampleNftID := mintExampleNFT( + t, + b, + contracts, + false, + userAddress.String(), + ) + + lockedAt, lockedUntil := lockNFT( + t, + b, + contracts, + false, + userAddress, + userSigner, + exampleNftID, + duration, + ) + + assert.Equal(t, lockedAt+duration, lockedUntil) + + // fast-forward block time past unlock duration + for i := 1; i < 5; i++ { + time.Sleep(1 * time.Second) + b.CommitBlock() + } + + unlockNFT( + t, + b, + contracts, + false, + userAddress, + userSigner, + exampleNftID, + ) + + extendLock( + t, + b, + contracts, + true, + userAddress, + userSigner, + exampleNftID, + extendedDuration, + ) +} + func TestSwapLock(t *testing.T) { b := newEmulator() contracts := NFTLockerDeployContracts(t, b)