Skip to content

Commit

Permalink
functions are not storeable, use struct interface instead
Browse files Browse the repository at this point in the history
  • Loading branch information
loic1 committed Oct 16, 2024
1 parent 59f369b commit 10aaed6
Show file tree
Hide file tree
Showing 10 changed files with 285 additions and 108 deletions.
17 changes: 17 additions & 0 deletions escrow/contracts/Escrow.cdc
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@
*/

import NonFungibleToken from "NonFungibleToken"
import NFTLocker from "NFTLocker"

access(all) contract Escrow {
// Event emitted when a new leaderboard is created.
Expand Down Expand Up @@ -231,6 +232,22 @@ access(all) contract Escrow {
}
}

// Handler for depositing NFTs to the Escrow Collection, used by the NFTLocker contract.
access(all) struct DepositHandler: NFTLocker.IAuthorizedDepositHandler {
access(all) fun deposit(nft: @{NonFungibleToken.NFT}, ownerAddress: Address, passThruParams: {String: AnyStruct}) {
// Get leaderboard name from pass-thru parameters
let leaderboardName = passThruParams["leaderboardName"] as! String?
?? panic("Missing or invalid 'leaderboardName' entry in pass-thru parameters map")

// Get the Escrow Collection public reference
let escrowCollectionPublic = Escrow.account.capabilities.borrow<&Escrow.Collection>(Escrow.CollectionPublicPath)
?? panic("Could not borrow a reference to the public leaderboard collection")

// Add the NFT to the escrow leaderboard
escrowCollectionPublic.addEntryToLeaderboard(nft: <-nft, leaderboardName: leaderboardName, ownerAddress: ownerAddress)
}
}

// Escrow contract initializer.
init() {
// Initialize paths.
Expand Down
28 changes: 20 additions & 8 deletions locked-nft/contracts/NFTLocker.cdc
Original file line number Diff line number Diff line change
Expand Up @@ -111,14 +111,20 @@ access(all) contract NFTLocker {
return self.account.storage.borrow<&ReceiverCollector>(from: NFTLocker.getReceiverCollectorStoragePath())
}

/// Interface for depositing NFTs to authorized receivers
///
access(all) struct interface IAuthorizedDepositHandler {
access(all) fun deposit(nft: @{NonFungibleToken.NFT}, ownerAddress: Address, passThruParams: {String: AnyStruct})
}

/// Struct that defines a Receiver
///
/// Receivers are entities that can receive locked NFTs and deposit them using a specific deposit method
///
access(all) struct Receiver {
/// The deposit method for the receiver
/// Handler for depositing NFTs to the receiver
///
access(all) var depositMethod: fun(@{NonFungibleToken.NFT}, LockedData, {String: AnyStruct})
access(all) var authorizedDepositHandler: {IAuthorizedDepositHandler}

/// The eligible NFT types for the receiver
///
Expand All @@ -131,15 +137,21 @@ access(all) contract NFTLocker {
/// Initialize Receiver struct
///
view init(
depositMethod: fun(@{NonFungibleToken.NFT}, LockedData, {String: AnyStruct}),
authorizedDepositHandler: {IAuthorizedDepositHandler},
eligibleNFTTypes: {Type: Bool}
) {
self.depositMethod = depositMethod
self.authorizedDepositHandler = authorizedDepositHandler
self.eligibleNFTTypes = eligibleNFTTypes
self.metadata = {}
}
}

/// Get the receiver by name
///
access(all) fun getReceiver(name: String): Receiver? {
return NFTLocker.borrowAdminReceiverCollectorPublic()!.getReceiver(name: name)
}

/// ReceiverCollector resource
///
/// Note: This resource is used to store receivers and corresponding deposit methods; currently, only
Expand All @@ -163,7 +175,7 @@ access(all) contract NFTLocker {
///
access(Operate) fun addReceiver(
name: String,
depositMethod: fun(@{NonFungibleToken.NFT}, LockedData, {String: AnyStruct}),
authorizedDepositHandler: {IAuthorizedDepositHandler},
eligibleNFTTypes: {Type: Bool}
) {
pre {
Expand All @@ -172,7 +184,7 @@ access(all) contract NFTLocker {

// Add the receiver
self.receiversByName[name] = Receiver(
depositMethod: depositMethod,
authorizedDepositHandler: authorizedDepositHandler,
eligibleNFTTypes: eligibleNFTTypes
)

Expand Down Expand Up @@ -347,9 +359,9 @@ access(all) contract NFTLocker {
NFTLocker.expireLock(id: id, nftType: nftType)

// Unlock and deposit the NFT using the receiver's deposit method
receiverCollector.getReceiver(name: receiverName)!.depositMethod(
receiverCollector.getReceiver(name: receiverName)!.authorizedDepositHandler.deposit(
nft: <- self.unlock(id: id, nftType: nftType),
lockedTokenDetails: lockedTokenDetails,
ownerAddress: lockedTokenDetails.owner,
passThruParams: passThruParams,
)
}
Expand Down
81 changes: 80 additions & 1 deletion locked-nft/lib/go/test/lockednft_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -332,6 +332,52 @@ func testAdminAddReceiver(
setupNFTLockerAccount(t, b, userAddress, userSigner, contracts)
setupExampleNFT(t, b, userAddress, userSigner, contracts)

mintExampleNFT(
t,
b,
contracts,
false,
userAddress.String(),
)

adminAddReceiver(
t,
b,
contracts,
false,
)
}

func TestUnlockWithAuthorizedDeposit(t *testing.T) {
b := newEmulator()
contracts := NFTLockerDeployContracts(t, b)

t.Run("Should be able to unlock with authorized deposit", func(t *testing.T) {
testUnlockWithAuthorizedDeposit(
t,
b,
contracts,
)
})
}

func testUnlockWithAuthorizedDeposit(
t *testing.T,
b *emulator.Blockchain,
contracts Contracts,
) {
userAddress, userSigner := createAccount(t, b)
setupNFTLockerAccount(t, b, userAddress, userSigner, contracts)
setupExampleNFT(t, b, userAddress, userSigner, contracts)

mintExampleNFT(
t,
b,
contracts,
false,
userAddress.String(),
)

exampleNftID := mintExampleNFT(
t,
b,
Expand All @@ -347,6 +393,40 @@ func testAdminAddReceiver(
false,
)

leaderboardName := "test-leaderboard-name"

createLeaderboard(
t,
b,
contracts,
leaderboardName,
)

var duration uint64 = 10000000000

lockedAt, lockedUntil := lockNFT(
t,
b,
contracts,
false,
userAddress,
userSigner,
exampleNftID,
duration,
)
assert.Equal(t, lockedAt+duration, lockedUntil)

unlockNFTWithAuthorizedDeposit(
t,
b,
contracts,
false,
userAddress,
userSigner,
leaderboardName,
exampleNftID,
)

err := func() (err error) {
defer func() {
if r := recover(); r != nil {
Expand All @@ -362,7 +442,6 @@ func testAdminAddReceiver(
return err
}()
assert.Error(t, err)

}

func TestAdminUnLockNFT(t *testing.T) {
Expand Down
33 changes: 26 additions & 7 deletions locked-nft/lib/go/test/templates.go
Original file line number Diff line number Diff line change
Expand Up @@ -43,12 +43,16 @@ const (
MetadataNFTReplaceAddress = `"NonFungibleToken"`

// NFTLocker
GetLockedTokenByIDScriptPath = ScriptsRootPath + "/get_locked_token.cdc"
GetInventoryScriptPath = ScriptsRootPath + "/inventory.cdc"
LockNFTTxPath = TransactionsRootPath + "/lock_nft.cdc"
UnlockNFTTxPath = TransactionsRootPath + "/unlock_nft.cdc"
AdminAddReceiverTxPath = TransactionsRootPath + "/admin_add_escrow_receiver.cdc"
AdminUnlockNFTTxPath = TransactionsRootPath + "/admin_unlock_nft.cdc"
GetLockedTokenByIDScriptPath = ScriptsRootPath + "/get_locked_token.cdc"
GetInventoryScriptPath = ScriptsRootPath + "/inventory.cdc"
LockNFTTxPath = TransactionsRootPath + "/lock_nft.cdc"
UnlockNFTTxPath = TransactionsRootPath + "/unlock_nft.cdc"
AdminAddReceiverTxPath = TransactionsRootPath + "/admin_add_escrow_receiver.cdc"
UnlockNFTWithAuthorizedDepositTxPath = TransactionsRootPath + "/unlock_nft_with_authorized_deposit.cdc"
AdminUnlockNFTTxPath = TransactionsRootPath + "/admin_unlock_nft.cdc"

// Escrow
CreateLeaderboardTxPath = TransactionsRootPath + "/testutils/create_leaderboard.cdc"
)

// ------------------------------------------------------------
Expand Down Expand Up @@ -78,11 +82,12 @@ func LoadNFTLockerContract(nftAddress flow.Address, metadataViewsAddress flow.Ad
return code
}

func LoadEscrowContract(nftAddress flow.Address, metadataViewsAddress flow.Address) []byte {
func LoadEscrowContract(nftAddress, metadataViewsAddress, nftLocker flow.Address) []byte {
code := readFile(EscrowPath)

nftRe := regexp.MustCompile(nftAddressPlaceholder)
code = nftRe.ReplaceAll(code, []byte("0x"+nftAddress.String()))
code = []byte(strings.ReplaceAll(string(code), NFTLockerAddressPlaceholder, "0x"+nftLocker.String()))

return code
}
Expand Down Expand Up @@ -146,13 +151,27 @@ func adminAddReceiverTransaction(contracts Contracts) []byte {
)
}

func unlockNFTWithAuthorizedDepositTransaction(contracts Contracts) []byte {
return replaceAddresses(
readFile(UnlockNFTWithAuthorizedDepositTxPath),
contracts,
)
}

func adminUnlockNFTTransaction(contracts Contracts) []byte {
return replaceAddresses(
readFile(AdminUnlockNFTTxPath),
contracts,
)
}

func createLeaderboardTransaction(contracts Contracts) []byte {
return replaceAddresses(
readFile(CreateLeaderboardTxPath),
contracts,
)
}

func DownloadFile(url string) ([]byte, error) {
// Get the data
resp, err := http.Get(url)
Expand Down
4 changes: 2 additions & 2 deletions locked-nft/lib/go/test/test.go
Original file line number Diff line number Diff line change
Expand Up @@ -75,15 +75,15 @@ func NFTLockerDeployContracts(t *testing.T, b *emulator.Blockchain) Contracts {

ExampleNFTCode := nftcontracts.ExampleNFT(nftAddress, metadataViewsAddr, resolverAddress)

EscrowCode := LoadEscrowContract(nftAddress, metadataViewsAddr)

NFTLockerAddress, err := adapter.CreateAccount(
context.Background(),
[]*flow.AccountKey{NFTLockerAccountKey},
nil,
)
require.NoError(t, err)

EscrowCode := LoadEscrowContract(nftAddress, metadataViewsAddr, NFTLockerAddress)

signer, err := b.ServiceKey().Signer()
assert.NoError(t, err)

Expand Down
59 changes: 56 additions & 3 deletions locked-nft/lib/go/test/transactions.go
Original file line number Diff line number Diff line change
@@ -1,14 +1,14 @@
package test

import (
"fmt"
"strings"
"testing"

"github.com/onflow/cadence"
"github.com/onflow/flow-emulator/emulator"
"github.com/onflow/flow-go-sdk"
"github.com/onflow/flow-go-sdk/crypto"
"github.com/stretchr/testify/require"
)

// ------------------------------------------------------------
Expand Down Expand Up @@ -112,13 +112,12 @@ func unlockNFT(
tx.AddArgument(cadence.UInt64(nftId))

signer, _ := b.ServiceKey().Signer()
txResult := signAndSubmit(
signAndSubmit(
t, b, tx,
[]flow.Address{b.ServiceKey().Address, userAddress},
[]crypto.Signer{signer, userSigner},
shouldRevert,
)
fmt.Println(txResult)
}

func adminAddReceiver(
Expand All @@ -143,6 +142,36 @@ func adminAddReceiver(
)
}

func unlockNFTWithAuthorizedDeposit(
t *testing.T,
b *emulator.Blockchain,
contracts Contracts,
shouldRevert bool,
userAddress flow.Address,
userSigner crypto.Signer,
leaderboardName string,
nftId uint64,
) {
tx := flow.NewTransaction().
SetScript(unlockNFTWithAuthorizedDepositTransaction(contracts)).
SetGasLimit(100).
SetProposalKey(b.ServiceKey().Address, b.ServiceKey().Index, b.ServiceKey().SequenceNumber).
SetPayer(b.ServiceKey().Address).
AddAuthorizer(userAddress)

leaderboardNameCadence, _ := cadence.NewString(leaderboardName)
tx.AddArgument(leaderboardNameCadence)
tx.AddArgument(cadence.UInt64(nftId))

signer, _ := b.ServiceKey().Signer()
signAndSubmit(
t, b, tx,
[]flow.Address{b.ServiceKey().Address, userAddress},
[]crypto.Signer{signer, userSigner},
shouldRevert,
)
}

func adminUnlockNFT(
t *testing.T,
b *emulator.Blockchain,
Expand All @@ -166,3 +195,27 @@ func adminUnlockNFT(
shouldRevert,
)
}

func createLeaderboard(
t *testing.T,
b *emulator.Blockchain,
contracts Contracts,
leaderboardName string,
) {
tx := flow.NewTransaction().
SetScript(createLeaderboardTransaction(contracts)).
SetComputeLimit(100).
SetProposalKey(b.ServiceKey().Address, b.ServiceKey().Index, b.ServiceKey().SequenceNumber).
SetPayer(b.ServiceKey().Address).
AddAuthorizer(contracts.NFTLockerAddress)
tx.AddArgument(cadence.String(leaderboardName))

signer, err := b.ServiceKey().Signer()
require.NoError(t, err)
signAndSubmit(
t, b, tx,
[]flow.Address{b.ServiceKey().Address, contracts.NFTLockerAddress},
[]crypto.Signer{signer, contracts.NFTLockerSigner},
false,
)
}
Loading

0 comments on commit 10aaed6

Please sign in to comment.