Skip to content
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

allow escrow admin transfer moments to any account #81

Merged
Show file tree
Hide file tree
Changes from 4 commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
15 changes: 15 additions & 0 deletions .github/workflows/escrow-test.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
name: Test

on:
push:
paths:
- "escrow/**"
jobs:
build:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v2
- uses: actions/setup-go@v1
with:
go-version: '1.21'
- run: cd escrow && make test
44 changes: 30 additions & 14 deletions escrow/contracts/Escrow.cdc
Original file line number Diff line number Diff line change
Expand Up @@ -74,19 +74,11 @@ access(all) contract Escrow {
emit EntryDeposited(leaderboardName: self.name, nftID: nftID, owner: ownerAddress)
}

// Withdraws an NFT entry from the leaderboard.
// Transfers an NFT entry from the leaderboard to an account.
access(contract) fun transferNftToCollection(nftID: UInt64, depositCap: Capability<&{NonFungibleToken.Collection}>) {
pre {
depositCap.check() : "Deposit capability is not valid"
}
if(self.entriesData[nftID] == nil) {
return
}
if(depositCap.address != self.entriesData[nftID]!.ownerAddress){
panic("Only the owner of the entry can withdraw it")
}


pre {
depositCap.check() : "Deposit capability is not valid"
}
// Remove the NFT entry's data from the leaderboard.
self.entriesData.remove(key: nftID)!

Expand Down Expand Up @@ -195,14 +187,38 @@ access(all) contract Escrow {
leaderboard.addEntryToLeaderboard(nft: <-nft, ownerAddress: ownerAddress)
}

// Calls transferNftToCollection.
// transferNftToCollection transfers the moment back to the owner's collection once the leaderboard is completed.
// This is used for entries of users who didn't win the leaderboard
access(Operate) fun transferNftToCollection(leaderboardName: String, nftID: UInt64, depositCap: Capability<&{NonFungibleToken.Collection}>) {
let leaderboard= &self.leaderboards[leaderboardName] as &Leaderboard?
?? panic("Leaderboard does not exist with this name")

if(leaderboard.entriesData[nftID] == nil) {
return
}

if(depositCap.address != leaderboard.entriesData[nftID]!.ownerAddress){
panic("Only the owner of the entry can withdraw it")
}

leaderboard.transferNftToCollection(nftID: nftID, depositCap: depositCap)
}

// adminTransferNftToCollection transfers the moment back to an admin collection once the leaderboard is completed.
// This is used for entries of users who won the leaderboard
access(Operate) fun adminTransferNftToCollection(leaderboardName: String, nftID: UInt64, depositCap: Capability<&{NonFungibleToken.Collection}>) {
let leaderboard= &self.leaderboards[leaderboardName] as &Leaderboard?
?? panic("Leaderboard does not exist with this name")

if(leaderboard.entriesData[nftID] == nil) {
return
}

leaderboard.transferNftToCollection(nftID: nftID, depositCap: depositCap)
}

// Calls burn.
// burn destroys the NFT from the leaderboard.
// this is used for entries of users who won the leaderboard
access(Operate) fun burn(leaderboardName: String, nftID: UInt64) {
let leaderboard = &self.leaderboards[leaderboardName] as &Leaderboard?
?? panic("Leaderboard does not exist with this name")
Expand Down
2 changes: 1 addition & 1 deletion escrow/lib/go/test/Makefile
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
.PHONY: test
test:
go test ./...
CGO_ENABLED=0 go test -tags=no_cgo ./...

.PHONY: ci
ci: test
122 changes: 121 additions & 1 deletion escrow/lib/go/test/escrow_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -527,7 +527,7 @@ func TestEscrow(t *testing.T) {
t.Run("Should get the leaderboard by name to confirm it exists", func(t *testing.T) {
// Get leaderboard data from the contract.
leaderboard, _ := getLeaderboardData(t, b, contracts, "leaderboardBurn-1")
assert.Equal(t, "\"leaderboardBurn-1\"", leaderboard.Name)
assert.Equal(t, "leaderboardBurn-1", leaderboard.Name)
assert.Equal(t, "Type<A.120e725050340cab.AllDay.NFT>()", leaderboard.NftType)
assert.Equal(t, uint64(0), leaderboard.EntriesLength)
})
Expand Down Expand Up @@ -624,6 +624,108 @@ func TestEscrow(t *testing.T) {
})
}

func TestEscrowAdminTransfer(t *testing.T) {
b := newEmulator()
contracts := EscrowContracts(t, b)
userAddress, userSigner := createAccount(t, b)
setupAllDay(t, b, userAddress, userSigner, contracts)

createTestEditions(t, b, contracts)

t.Run("Should be able to mint a new MomentNFT from an edition that has a maxMintSize", func(t *testing.T) {
testMintMomentNFT(
t,
b,
contracts,
uint64(1),
nil,
userAddress,
uint64(1),
uint64(1),
false,
)
})

t.Run("Should confirm that 1 MomentNFT exists within users collection", func(t *testing.T) {
// Get the MomentNFT data from the users collection.
count := getMomentNFTLengthInAccount(t, b, contracts, userAddress)
assert.Equal(t, big.NewInt(1), count)
})

t.Run("Should be able to create a leaderboard", func(t *testing.T) {
testCreateLeaderboard(
t,
b,
contracts,
"leaderboardBurn-1",
)
})

t.Run("Should get the leaderboard by name to confirm it exists", func(t *testing.T) {
// Get leaderboard data from the contract.
leaderboard, _ := getLeaderboardData(t, b, contracts, "leaderboardBurn-1")
assert.Equal(t, "leaderboardBurn-1", leaderboard.Name)
assert.Equal(t, "Type<A.120e725050340cab.AllDay.NFT>()", leaderboard.NftType)
assert.Equal(t, uint64(0), leaderboard.EntriesLength)
})

t.Run("Should be able to escrow moment to leaderboard", func(t *testing.T) {
testEscrowMomentNFT(
t,
b,
contracts,
userSigner,
userAddress,
uint64(1),
)
})

t.Run("Should get the leaderboard by name to confirm entries", func(t *testing.T) {
// Get leaderboard data from the contract.
leaderboard, _ := getLeaderboardData(t, b, contracts, "leaderboardBurn-1")
assert.Equal(t, uint64(1), leaderboard.EntriesLength)
})

t.Run("Should confirm that 0 MomentNFTs exists within users collection due to escrow", func(t *testing.T) {
// Get the MomentNFT data from the users collection.
count := getMomentNFTLengthInAccount(t, b, contracts, userAddress)
assert.Equal(t, big.NewInt(0), count)
})

var (
newUserAddress flow.Address
newUserSigner crypto.Signer
)
t.Run("Should create another account and setup allDat collection", func(t *testing.T) {
newUserAddress, newUserSigner = createAccount(t, b)
setupAllDay(t, b, newUserAddress, newUserSigner, contracts)
})

t.Run("Should admin transfer moment to new account from Leaderboard by name", func(t *testing.T) {
testAdminTransferMomentNFT(
t,
b,
contracts,
"leaderboardBurn-1",
newUserAddress,
uint64(1),
)
})

t.Run("Should check that the MomentNFT is in the new user's collection", func(t *testing.T) {
// Get the MomentNFT data from the users collection.
count := getMomentNFTLengthInAccount(t, b, contracts, newUserAddress)
assert.Equal(t, big.NewInt(1), count)
})

t.Run("Should get the leaderboard by name to confirm entries", func(t *testing.T) {
// Get leaderboard data from the contract.
leaderboard, _ := getLeaderboardData(t, b, contracts, "leaderboardBurn-1")
assert.Equal(t, uint64(0), leaderboard.EntriesLength)
})

}

func testCreateLeaderboard(
t *testing.T,
b *emulator.Blockchain,
Expand Down Expand Up @@ -672,6 +774,24 @@ func testBurnMomentNFT(
)
}

func testAdminTransferMomentNFT(
t *testing.T,
b *emulator.Blockchain,
contracts Contracts,
leaderboardName string,
userAddress flow.Address,
momentNftFlowID uint64,
) {
adminTransferMomentNFT(
t,
b,
contracts,
leaderboardName,
userAddress,
momentNftFlowID,
)
}

func testEscrowMomentNFT(
t *testing.T,
b *emulator.Blockchain,
Expand Down
2 changes: 1 addition & 1 deletion escrow/lib/go/test/scripts.go
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,7 @@ func accountIsSetup(
contracts Contracts,
address flow.Address,
) bool {
script := loadEscrowAccountIsSetupScript(contracts)
script := loadAllDayAccountIsSetupScript(contracts)
result := executeScriptAndCheck(t, b, script, [][]byte{jsoncdc.MustEncode(cadence.BytesToAddress(address.Bytes()))})

return GetFieldValue(result).(bool)
Expand Down
28 changes: 18 additions & 10 deletions escrow/lib/go/test/templates.go
Original file line number Diff line number Diff line change
Expand Up @@ -26,8 +26,8 @@ const (
EscrowScriptsRootPath = "../../../scripts"

// Accounts
EscrowSetupAccountPath = EscrowTransactionsRootPath + "/user/setup_AllDay_account.cdc"
EscrowAccountIsSetupPath = EscrowScriptsRootPath + "/user/account_is_setup.cdc"
AllDaySetupAccountPath = EscrowTransactionsRootPath + "/user/setup_AllDay_account.cdc"
AllDayAccountIsSetupPath = EscrowScriptsRootPath + "/user/account_is_setup.cdc"

// Series
EscrowCreateSeriesPath = EscrowTransactionsRootPath + "/admin/series/create_series.cdc"
Expand Down Expand Up @@ -55,10 +55,11 @@ const (
EscrowReadCollectionLengthPath = EscrowScriptsRootPath + "/nfts/read_collection_nft_length.cdc"

// Escrow
EscrowMomentNFTPath = EscrowTransactionsRootPath + "/user/add_entry.cdc"
EscrowWithdrawMomentNFTPath = EscrowTransactionsRootPath + "/admin/leaderboards/withdraw_entry.cdc"
EscrowBurnNFTPath = EscrowTransactionsRootPath + "/admin/leaderboards/burn_nft.cdc"
EscrowReadLeaderboardInfoPath = EscrowScriptsRootPath + "/leaderboards/read_leaderboard_info.cdc"
EscrowMomentNFTPath = EscrowTransactionsRootPath + "/user/add_entry.cdc"
EscrowWithdrawMomentNFTPath = EscrowTransactionsRootPath + "/admin/leaderboards/withdraw_entry.cdc"
EscrowAdminTransferMomentNFTPath = EscrowTransactionsRootPath + "/admin/leaderboards/admin_transfer.cdc"
EscrowBurnNFTPath = EscrowTransactionsRootPath + "/admin/leaderboards/burn_nft.cdc"
EscrowReadLeaderboardInfoPath = EscrowScriptsRootPath + "/leaderboards/read_leaderboard_info.cdc"
)

// ------------------------------------------------------------
Expand Down Expand Up @@ -119,16 +120,16 @@ func LoadEscrow(nftAddress flow.Address) []byte {
return code
}

func loadEscrowSetupAccountTransaction(contracts Contracts) []byte {
func loadAllDaySetupAccountTransaction(contracts Contracts) []byte {
return replaceAddresses(
readFile(EscrowSetupAccountPath),
readFile(AllDaySetupAccountPath),
contracts,
)
}

func loadEscrowAccountIsSetupScript(contracts Contracts) []byte {
func loadAllDayAccountIsSetupScript(contracts Contracts) []byte {
return replaceAddresses(
readFile(EscrowAccountIsSetupPath),
readFile(AllDayAccountIsSetupPath),
contracts,
)
}
Expand Down Expand Up @@ -256,6 +257,13 @@ func loadEscrowWithdrawMomentNFT(contracts Contracts) []byte {
)
}

func loadEscrowAdminTransferMomentNFT(contracts Contracts) []byte {
return replaceAddresses(
readFile(EscrowAdminTransferMomentNFTPath),
contracts,
)
}

func loadEscrowBurnNFTTransaction(contracts Contracts) []byte {
return replaceAddresses(
readFile(EscrowBurnNFTPath),
Expand Down
2 changes: 1 addition & 1 deletion escrow/lib/go/test/test.go
Original file line number Diff line number Diff line change
Expand Up @@ -270,7 +270,7 @@ func setupAllDay(
contracts Contracts,
) {
tx := flow.NewTransaction().
SetScript(loadEscrowSetupAccountTransaction(contracts)).
SetScript(loadAllDaySetupAccountTransaction(contracts)).
SetComputeLimit(100).
SetProposalKey(b.ServiceKey().Address, b.ServiceKey().Index, b.ServiceKey().SequenceNumber).
SetPayer(b.ServiceKey().Address).
Expand Down
28 changes: 28 additions & 0 deletions escrow/lib/go/test/transactions.go
Original file line number Diff line number Diff line change
Expand Up @@ -289,6 +289,34 @@ func withdrawMomentNFT(
)
}

func adminTransferMomentNFT(
t *testing.T,
b *emulator.Blockchain,
contracts Contracts,
leaderboardName string,
userAddress flow.Address,
momentNftFlowID uint64,
) {
tx := flow.NewTransaction().
SetScript(loadEscrowAdminTransferMomentNFT(contracts)).
SetComputeLimit(100).
SetProposalKey(b.ServiceKey().Address, b.ServiceKey().Index, b.ServiceKey().SequenceNumber).
SetPayer(b.ServiceKey().Address).
AddAuthorizer(contracts.AllDayAddress)
tx.AddArgument(cadence.String(leaderboardName))
tx.AddArgument(cadence.NewUInt64(momentNftFlowID))
tx.AddArgument(cadence.BytesToAddress(userAddress.Bytes()))

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

func burnMomentNFT(
t *testing.T,
b *emulator.Blockchain,
Expand Down
2 changes: 1 addition & 1 deletion escrow/lib/go/test/types.go
Original file line number Diff line number Diff line change
Expand Up @@ -158,7 +158,7 @@ func parseLeaderboardInfo(value cadence.Value) (LeaderboardInfo, error) {
for k, v := range fields {
switch k {
case "name":
s.Name = v.(cadence.String).String()
s.Name = string(v.(cadence.String))
case "nftType":
s.NftType = v.String()
case "entriesLength":
Expand Down
22 changes: 22 additions & 0 deletions escrow/transactions/admin/leaderboards/admin_transfer.cdc
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
import Escrow from "Escrow"
import AllDay from "AllDay"
import NonFungibleToken from "NonFungibleToken"

// This transaction takes the leaderboardName and nftID and returns it to the correct owner.
transaction(leaderboardName: String, nftID: UInt64, ownerAddress: Address) {
prepare(signer: auth(BorrowValue) &Account) {
// Get a reference to the Collection resource in storage.
let collectionRef = signer.storage.borrow<auth(Escrow.Operate) &Escrow.Collection>(from: Escrow.CollectionStoragePath)
?? panic("Could not borrow reference to the Collection resource")

let depositCap = getAccount(ownerAddress)
.capabilities.get<&{NonFungibleToken.Collection}>(AllDay.CollectionPublicPath)

// Call transferNftToCollection function.
collectionRef.adminTransferNftToCollection(leaderboardName: leaderboardName, nftID: nftID, depositCap: depositCap)
}

execute {
log("Withdrawn NFT from leaderboard")
}
}
Loading