Skip to content

Commit

Permalink
allow escrow admin transfer moments to any account (#81)
Browse files Browse the repository at this point in the history
* allow escrow admin transfer moments to any account

* Update Escrow.cdc

* escrow test

* trigger test

* Create Makefile

* Update Escrow.cdc

* Update templates.go
  • Loading branch information
Deewai authored Oct 7, 2024
1 parent e3dfd9f commit 2f70167
Show file tree
Hide file tree
Showing 11 changed files with 245 additions and 29 deletions.
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
7 changes: 7 additions & 0 deletions escrow/Makefile
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
.PHONY: test
test:
$(MAKE) test -C ./lib/go

.PHONY: ci
ci:
$(MAKE) ci -C ./lib/go
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")
}
}

0 comments on commit 2f70167

Please sign in to comment.