Skip to content

Commit

Permalink
fix: Allow TP approval flow without cheque (#461)
Browse files Browse the repository at this point in the history
* fix: Prevent curations from being created if there are no older curations for an item

* fix: Get cheque by hash

* fix: Fix EIP712 hash

* fix: Add tests

* fix: Fix tests
  • Loading branch information
LautaroPetaccio committed Mar 30, 2022
1 parent 45eac4c commit 44821fc
Show file tree
Hide file tree
Showing 13 changed files with 288 additions and 59 deletions.
6 changes: 3 additions & 3 deletions package-lock.json

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

2 changes: 1 addition & 1 deletion package.json
Original file line number Diff line number Diff line change
Expand Up @@ -53,7 +53,7 @@
"dcl-ops-lib": "^1.0.6",
"decentraland-commons": "^5.1.0",
"decentraland-server": "^3.0.0",
"decentraland-transactions": "^1.30.0",
"decentraland-transactions": "^1.31.0",
"escape-html": "^1.0.3",
"ethers": "^5.6.1",
"express": "^4.16.4",
Expand Down
6 changes: 6 additions & 0 deletions spec/mocks/cheque.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
export const mockedCheque = {
signature:
'0xda1398329aff61ce133d4204c0ebc656b07e0629c280d6f60121874285ac3bbc56e48b9e7211296be9f209d646e723f44a96c82f0e6bd52ee6edf74cc44ee9d71c', // signatured generated using the wallet from fakePrivateKey
qty: 3,
salt: '0xb899ffa17ccea3e2ae5fd36236df56c3bf908a435ad101ed7fa6ec8a9631e253',
}
2 changes: 1 addition & 1 deletion spec/mocks/wallet.ts
Original file line number Diff line number Diff line change
Expand Up @@ -38,6 +38,7 @@ export const wallet: Wallet = {
}

// Taken from https://github.com/decentraland/builder-client/blob/00ab26cdbd350ce5f5a46da09358c3662c4c6741/src/test-utils/crypto.ts#L1
// Wallet address: 0x63FaC9201494f0bd17B9892B9fae4d52fe3BD377
export const fakePrivateKey =
'8da4ef21b864d2cc526dbdb2a120bd2874c36c9d0a1fb7f8c63d7f7a8b41de8f'

Expand All @@ -55,7 +56,6 @@ export async function createIdentity(
): Promise<AuthIdentity> {
const address = await signer.getAddress()

// const wallet = EthersWallet.createRandom()
const payload = {
address: wallet.address,
privateKey: wallet.privateKey,
Expand Down
104 changes: 74 additions & 30 deletions src/Collection/Collection.router.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -29,7 +29,12 @@ import {
itemFragmentMock,
} from '../../spec/mocks/items'
import { itemCurationMock } from '../../spec/mocks/itemCuration'
import { ItemFragment, CollectionFragment } from '../ethereum/api/fragments'
import { mockedCheque } from '../../spec/mocks/cheque'
import {
ItemFragment,
CollectionFragment,
ReceiptFragment,
} from '../ethereum/api/fragments'
import { collectionAPI } from '../ethereum/api/collection'
import { thirdPartyAPI } from '../ethereum/api/thirdParty'
import { Bridge } from '../ethereum/api/Bridge'
Expand Down Expand Up @@ -85,6 +90,8 @@ jest.mock('../Item/Item.model')
jest.mock('./Collection.model')
jest.mock('./access')

const thirdPartyAPIMock = thirdPartyAPI as jest.Mocked<typeof thirdPartyAPI>

describe('Collection router', () => {
let dbCollection: CollectionAttributes
let dbTPCollection: ThirdPartyCollectionAttributes
Expand Down Expand Up @@ -1548,13 +1555,7 @@ describe('Collection router', () => {

beforeEach(async () => {
mockedWallet = new ethers.Wallet(fakePrivateKey)
cheque = {
signature:
'0x393140034cb84d3a71d7dff062cbe0b4b8add8a6a1a66c0157508648ed9e290369c1ab613fd52836dfc08838b094ccd58db0700b24018d0f7bf36ed238811a8e1b', // signatured generated using the wallet from fakePrivateKey
qty: 3,
salt:
'0x866023072516cda998ccd2b696fbbed3912fa5ecea8b474af6e40dadc5352ce4',
}
cheque = { ...mockedCheque }
authHeaders = createAuthHeaders(
'post',
url,
Expand Down Expand Up @@ -2136,6 +2137,7 @@ describe('Collection router', () => {
;(SlotUsageCheque.findLastByCollectionId as jest.Mock).mockResolvedValueOnce(
{}
)
thirdPartyAPIMock.fetchReceiptById.mockResolvedValueOnce(undefined)
})

it('should respond with a 500 saying that the item is missing some properties', () => {
Expand Down Expand Up @@ -2176,9 +2178,8 @@ describe('Collection router', () => {
]

slotUsageCheque = {
qty: 2,
salt: 'salt',
signature: 'signature',
...mockedCheque,
third_party_id: dbTPCollection.third_party_id,
} as SlotUsageChequeAttributes

mockCollectionAuthorizationMiddleware(
Expand All @@ -2198,28 +2199,71 @@ describe('Collection router', () => {
)
})

it('should return an array with the data for pending curations', () => {
return server
.get(buildURL(url))
.set(createAuthHeaders('get', url))
.expect(200)
.then((response: any) => {
expect(response.body).toEqual({
ok: true,
data: {
cheque: {
qty: slotUsageCheque.qty,
salt: slotUsageCheque.salt,
signature: slotUsageCheque.signature,
describe("and the cheque doesn't exist in the blockcahin", () => {
beforeEach(() => {
thirdPartyAPIMock.fetchReceiptById.mockResolvedValueOnce(
undefined
)
})

it('should return an array with the data for pending curations, indicating that the cheque was not used', () => {
return server
.get(buildURL(url))
.set(createAuthHeaders('get', url))
.expect(200)
.then((response: any) => {
expect(response.body).toEqual({
ok: true,
data: {
cheque: {
qty: slotUsageCheque.qty,
salt: slotUsageCheque.salt,
signature: slotUsageCheque.signature,
},
content_hashes: {
[itemApprovalData[0].id]: 'Qm1abababa',
[itemApprovalData[1].id]: 'Qm2bdbdbdb',
[itemApprovalData[2].id]: 'Qm3rererer',
},
chequeWasConsumed: false,
},
content_hashes: {
[itemApprovalData[0].id]: 'Qm1abababa',
[itemApprovalData[1].id]: 'Qm2bdbdbdb',
[itemApprovalData[2].id]: 'Qm3rererer',
})
})
})
})

describe('and the cheque exists in the blockchain', () => {
beforeEach(() => {
thirdPartyAPIMock.fetchReceiptById.mockResolvedValueOnce({
id:
'0x7954b5d263d7d1298c98fa330de6a0d94952bb5f6694cab0dde144239d56dce1',
} as ReceiptFragment)
})

it('should return an array with the data for pending curations, indicating that the cheque was used', () => {
return server
.get(buildURL(url))
.set(createAuthHeaders('get', url))
.expect(200)
.then((response: any) => {
expect(response.body).toEqual({
ok: true,
data: {
cheque: {
qty: slotUsageCheque.qty,
salt: slotUsageCheque.salt,
signature: slotUsageCheque.signature,
},
content_hashes: {
[itemApprovalData[0].id]: 'Qm1abababa',
[itemApprovalData[1].id]: 'Qm2bdbdbdb',
[itemApprovalData[2].id]: 'Qm3rererer',
},
chequeWasConsumed: true,
},
},
})
})
})
})
})
})
})
Expand Down
16 changes: 15 additions & 1 deletion src/Collection/Collection.service.ts
Original file line number Diff line number Diff line change
Expand Up @@ -29,7 +29,11 @@ import {
} from '../Curation/CollectionCuration'
import { CurationStatus } from '../Curation'
import { decodeTPCollectionURN, isTPCollection } from '../utils/urn'
import { getAddressFromSignature, toDBCollection } from './utils'
import {
getAddressFromSignature,
getChequeMessageHash,
toDBCollection,
} from './utils'
import {
CollectionAttributes,
FullCollection,
Expand Down Expand Up @@ -446,13 +450,23 @@ export class CollectionService {
return acc
}, {} as Record<string, string>)

const slotUsageCheckHash = await getChequeMessageHash(
slotUsageCheque,
slotUsageCheque.third_party_id
)

const remoteCheque = await thirdPartyAPI.fetchReceiptById(
slotUsageCheckHash
)

return {
cheque: {
qty,
salt,
signature,
},
content_hashes,
chequeWasConsumed: remoteCheque?.id === slotUsageCheckHash,
}
}

Expand Down
25 changes: 24 additions & 1 deletion src/Collection/utils.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -7,10 +7,11 @@ import { itemCurationMock } from '../../spec/mocks/itemCuration'
import { collectionAPI } from '../ethereum/api/collection'
import { ItemCuration } from '../Curation/ItemCuration'
import { decodeTPCollectionURN } from '../utils/urn'
import { Cheque } from '../SlotUsageCheque'
import { UnpublishedCollectionError } from './Collection.errors'
import { CollectionAttributes } from './Collection.types'
import { Collection } from './Collection.model'
import { getMergedCollection } from './utils'
import { getChequeMessageHash, getMergedCollection } from './utils'

describe('when decoding the TP collection URN', () => {
const collectionNetwork = 'ropsten'
Expand Down Expand Up @@ -137,3 +138,25 @@ describe('getMergedCollection', () => {
})
})
})

describe('when getting the cheque hash', () => {
let cheque: Cheque
let thirdPartyId: string

beforeEach(() => {
thirdPartyId = 'urn:decentraland:mumbai:collections-thirdparty:jean-pier'
cheque = {
signature:
'0x1dd053b34b48bc1e08be16c1d4f51908b4551040cf0fb390b90d18583dab2c7716ba3c73f00b5143e8ecdcd6227433226195e545a897df2e28849e91d291d9201c',
qty: 1,
salt:
'0x79ab6dbeeebdd32191ad0b9774e07349b7883359f07237a6cb2179d7bf462a2f',
}
})

it('should return the correct hash', () => {
return expect(getChequeMessageHash(cheque, thirdPartyId)).resolves.toEqual(
'0x808b380dc4bd97f8a0cf17c3548ad5c085964b31a99d5c52311c571b398783bc'
)
})
})
Loading

0 comments on commit 44821fc

Please sign in to comment.