From 2a28efe4131e08a2de5dc84c0a9b7d001446fc0b Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Sami=20M=C3=A4kel=C3=A4?= Date: Wed, 26 Jan 2022 15:56:57 +0200 Subject: [PATCH 01/32] starting writing tests --- .../templates/NFT721AccessProofTemplate.sol | 16 + .../templates/NFTAccessProofTemplate.sol | 90 +++++ test/int/nft/NFTAccessProofAgreement.Test.js | 360 ++++++++++++++++++ 3 files changed, 466 insertions(+) create mode 100644 contracts/templates/NFT721AccessProofTemplate.sol create mode 100644 contracts/templates/NFTAccessProofTemplate.sol create mode 100644 test/int/nft/NFTAccessProofAgreement.Test.js diff --git a/contracts/templates/NFT721AccessProofTemplate.sol b/contracts/templates/NFT721AccessProofTemplate.sol new file mode 100644 index 00000000..debbae90 --- /dev/null +++ b/contracts/templates/NFT721AccessProofTemplate.sol @@ -0,0 +1,16 @@ +pragma solidity ^0.8.0; +// Copyright 2020 Keyko GmbH. +// SPDX-License-Identifier: (Apache-2.0 AND CC-BY-4.0) +// Code is Apache-2.0 and docs are CC-BY-4.0 + + +import './NFTAccessProofTemplate.sol'; + +/** + * @title Agreement Template + * @author Keyko + * + * @dev Implementation of NFT721 Access Proof Template + */ +contract NFT721AccessProofTemplate is NFTAccessProofTemplate { +} diff --git a/contracts/templates/NFTAccessProofTemplate.sol b/contracts/templates/NFTAccessProofTemplate.sol new file mode 100644 index 00000000..2141448d --- /dev/null +++ b/contracts/templates/NFTAccessProofTemplate.sol @@ -0,0 +1,90 @@ +pragma solidity ^0.8.0; +// Copyright 2020 Keyko GmbH. +// SPDX-License-Identifier: (Apache-2.0 AND CC-BY-4.0) +// Code is Apache-2.0 and docs are CC-BY-4.0 + + +import './BaseEscrowTemplate.sol'; +import '../conditions/AccessProofCondition.sol'; +import '../conditions/NFTs/INFTHolder.sol'; +import '../registry/DIDRegistry.sol'; + +/** + * @title Agreement Template + * @author Keyko + * + * @dev Implementation of NFT Access Template + * + * The NFT Access template is use case specific template. + * Anyone (consumer/provider/publisher) can use this template in order + * to setup an agreement allowing NFT holders to get access to Nevermined services. + * The template is a composite of 2 basic conditions: + * - NFT Holding Condition + * - Access Condition + * + * Once the agreement is created, the consumer can demonstrate is holding a NFT + * for a specific DID. If that's the case the Access condition can be fulfilled + * by the asset owner or provider and all the agreement is fulfilled. + * This can be used in scenarios where a data or services owner, can allow + * users to get access to exclusive services only when they demonstrate the + * are holding a specific number of NFTs of a DID. + * This is very useful in use cases like arts. + */ +contract NFTAccessProofTemplate is BaseEscrowTemplate { + + DIDRegistry internal didRegistry; + INFTHolder internal nftHolderCondition; + AccessProofCondition internal accessCondition; + + /** + * @notice initialize init the + * contract with the following parameters. + * @dev this function is called only once during the contract + * initialization. It initializes the ownable feature, and + * set push the required condition types including + * access secret store, lock reward and escrow reward conditions. + * @param _owner contract's owner account address + * @param _agreementStoreManagerAddress agreement store manager contract address + * @param _nftHolderConditionAddress lock reward condition contract address + * @param _accessConditionAddress access condition contract address + */ + function initialize( + address _owner, + address _agreementStoreManagerAddress, + address _nftHolderConditionAddress, + address _accessConditionAddress + ) + external + initializer() + { + require( + _owner != address(0) && + _agreementStoreManagerAddress != address(0) && + _nftHolderConditionAddress != address(0) && + _accessConditionAddress != address(0), + 'Invalid address' + ); + + OwnableUpgradeable.__Ownable_init(); + transferOwnership(_owner); + + agreementStoreManager = AgreementStoreManager( + _agreementStoreManagerAddress + ); + + didRegistry = DIDRegistry( + agreementStoreManager.getDIDRegistryAddress() + ); + + nftHolderCondition = INFTHolder( + _nftHolderConditionAddress + ); + + accessCondition = AccessProofCondition( + _accessConditionAddress + ); + + conditionTypes.push(address(nftHolderCondition)); + conditionTypes.push(address(accessCondition)); + } +} diff --git a/test/int/nft/NFTAccessProofAgreement.Test.js b/test/int/nft/NFTAccessProofAgreement.Test.js new file mode 100644 index 00000000..6ed70614 --- /dev/null +++ b/test/int/nft/NFTAccessProofAgreement.Test.js @@ -0,0 +1,360 @@ +/* eslint-env mocha */ +/* eslint-disable no-console */ +/* global artifacts, contract, describe, it, expect */ + +const chai = require('chai') +const { assert } = chai +const chaiAsPromised = require('chai-as-promised') +chai.use(chaiAsPromised) + +const NFTAccessProofTemplate = artifacts.require('NFTAccessProofTemplate') +const NFTHolderCondition = artifacts.require('NFTHolderCondition') + +const constants = require('../../helpers/constants.js') +const deployConditions = require('../../helpers/deployConditions.js') +const deployManagers = require('../../helpers/deployManagers.js') +const { getBalance } = require('../../helpers/getBalance.js') +const increaseTime = require('../../helpers/increaseTime.js') +const testUtils = require('../../helpers/utils') +const mimcdecrypt = require('../../helpers/mimcdecrypt').decrypt + +const poseidon = require('circomlib').poseidon +const babyJub = require('circomlib').babyJub +const mimcjs = require('circomlib').mimcsponge +const ZqField = require('ffjavascript').ZqField +const Scalar = require('ffjavascript').Scalar +const F = new ZqField(Scalar.fromString('21888242871839275222246405745257275088548364400416034343698204186575808495617')) +const snarkjs = require('snarkjs') +const { unstringifyBigInts } = require('ffjavascript').utils + +contract('NFT Access Proof Template integration test', (accounts) => { + const web3 = global.web3 + const didSeed = testUtils.generateId() + const checksum = testUtils.generateId() + const url = 'https://raw.githubusercontent.com/nevermined-io/assets/main/images/logo/banner_logo.png' + const royalties = 10 // 10% of royalties in the secondary market + const cappedAmount = 5 + let token, + didRegistry, + nft, + agreementStoreManager, + conditionStoreManager, + templateStoreManager, + nftAccessTemplate, + accessProofCondition, + lockPaymentCondition, + escrowPaymentCondition + const [ + owner, + deployer, + artist, + collector1, + collector2, + gallery, + market, + someone + ] = accounts + async function setupTest() { + ({ + token, + didRegistry, + nft, + agreementStoreManager, + conditionStoreManager, + templateStoreManager + } = await deployManagers( + deployer, + owner + )); + + ({ + accessProofCondition, + lockPaymentCondition, + escrowPaymentCondition + } = await deployConditions( + deployer, + owner, + agreementStoreManager, + conditionStoreManager, + didRegistry, + token + )) + nftHolderCondition = await NFTHolderCondition.new({ from: deployer }) + await nftHolderCondition.initialize( + owner, + conditionStoreManager.address, + nft.address, + { from: deployer } + ) + + nftAccessTemplate = await NFTAccessProofTemplate.new() + await nftAccessTemplate.methods['initialize(address,address,address,address)']( + owner, + agreementStoreManager.address, + nftHolderCondition.address, + accessProofCondition.address, + { from: deployer } + ) + + // propose and approve template + const templateId = nftAccessTemplate.address + await templateStoreManager.proposeTemplate(templateId) + await templateStoreManager.approveTemplate(templateId, { from: owner }) + + return { + templateId, + owner + } + } + + async function prepareAgreement({ + agreementId = testUtils.generateId(), + sender = accounts[0], + receivers = [accounts[2], accounts[3]], + escrowAmounts = [11, 4], + timeLockAccess = 0, + timeOutAccess = 0, + didSeed = testUtils.generateId(), + url = constants.registry.url, + checksum = constants.bytes32.one + } = {}) { + const orig1 = 222n + const orig2 = 333n + const origHash = poseidon([orig1, orig2]) + + const did = await didRegistry.hashDID(didSeed, receivers[0]) + + const buyerK = 123 + const providerK = 234 + const buyerPub = babyJub.mulPointEscalar(babyJub.Base8, F.e(buyerK)) + const providerPub = babyJub.mulPointEscalar(babyJub.Base8, F.e(providerK)) + + const k = babyJub.mulPointEscalar(buyerPub, F.e(providerK)) + + const cipher = mimcjs.hash(orig1, orig2, k[0]) + + const snarkParams = { + buyer_x: buyerPub[0], + buyer_y: buyerPub[1], + provider_x: providerPub[0], + provider_y: providerPub[1], + xL_in: orig1, + xR_in: orig2, + cipher_xL_in: cipher.xL, + cipher_xR_in: cipher.xR, + provider_k: providerK, + hash_plain: origHash + } + + // console.log(snark_params) + + const { proof } = await snarkjs.plonk.fullProve( + snarkParams, + 'circuits/keytransfer.wasm', + 'circuits/keytransfer.zkey' + ) + + const signals = [ + buyerPub[0], + buyerPub[1], + providerPub[0], + providerPub[1], + cipher.xL, + cipher.xR, + origHash + ] + + const proofSolidity = (await snarkjs.plonk.exportSolidityCallData(unstringifyBigInts(proof), signals)) + + const proofData = proofSolidity.split(',')[0] + + // construct agreement + const conditionIdNFTHolder = await nftHolderCondition.generateId(agreementAccessId, + await nftHolderCondition.hashValues(did, receiver, 1)) + const conditionIdAccess = await accessProofCondition.generateId(agreementId, + await accessProofCondition.hashValues(origHash, buyerPub, providerPub)) + + const agreement = { + did: did, + conditionIds: [ + conditionIdNFTHolder, + conditionIdAccess + ], + timeLocks: [0, 0], + timeOuts: [0, 0], + accessConsumer: receiver + } + const data = { + origHash, + buyerPub, + providerPub, + cipher: [cipher.xL, cipher.xR], + proof: proofData + } + return { + agreementId, + did, + data, + didSeed, + agreement, + sender, + receivers, + escrowAmounts, + timeLockAccess, + timeOutAccess, + checksum, + url, + buyerK, + providerPub, + origHash + } + } + + describe.only('As an artist I want to register a new artwork', () => { + it('I want to register a new artwork and tokenize (via NFT). I want to get 10% of royalties', async () => { + await setupTest() + + did = await didRegistry.hashDID(didSeed, artist) + + await didRegistry.registerMintableDID( + didSeed, checksum, [], url, cappedAmount, royalties, constants.activities.GENERATED, '', { from: artist }) + await didRegistry.mint(did, 5, { from: artist }) + + const balance = await nft.balanceOf(artist, did) + assert.strictEqual(5, balance.toNumber()) + }) + }) + + describe('create and fulfill access agreement', function() { + this.timeout(100000) + it('should create access agreement', async () => { + // prepare: escrow agreement + const { agreementId, data, did, didSeed, agreement, sender, receivers, escrowAmounts, checksum, url, buyerK, providerPub, origHash } = await prepareAgreement() + const totalAmount = escrowAmounts[0] + escrowAmounts[1] + const receiver = receivers[0] + // register DID + await didRegistry.registerAttribute(didSeed, checksum, [], url, { from: receiver }) + + // create agreement + await accessTemplate.createAgreement(agreementId, ...Object.values(agreement)) + + // check state of agreement and conditions + expect((await agreementStoreManager.getAgreement(agreementId)).did) + .to.equal(did) + + const conditionTypes = await accessTemplate.getConditionTypes() + let storedCondition + agreement.conditionIds.forEach(async (conditionId, i) => { + storedCondition = await conditionStoreManager.getCondition(conditionId) + expect(storedCondition.typeRef).to.equal(conditionTypes[i]) + expect(storedCondition.state.toNumber()).to.equal(constants.condition.state.unfulfilled) + }) + + // fill up wallet + await token.mint(sender, totalAmount, { from: owner }) + + assert.strictEqual(await getBalance(token, sender), totalAmount) + assert.strictEqual(await getBalance(token, lockPaymentCondition.address), 0) + assert.strictEqual(await getBalance(token, escrowPaymentCondition.address), 0) + assert.strictEqual(await getBalance(token, receiver), 0) + + // fulfill lock reward + await token.approve(lockPaymentCondition.address, totalAmount, { from: sender }) + await lockPaymentCondition.fulfill(agreementId, did, escrowPaymentCondition.address, token.address, escrowAmounts, receivers, { from: sender }) + + assert.strictEqual(await getBalance(token, sender), 0) + assert.strictEqual(await getBalance(token, lockPaymentCondition.address), 0) + assert.strictEqual(await getBalance(token, escrowPaymentCondition.address), totalAmount) + assert.strictEqual(await getBalance(token, receiver), 0) + + assert.strictEqual( + (await conditionStoreManager.getConditionState(agreement.conditionIds[1])).toNumber(), + constants.condition.state.fulfilled) + + // fulfill access + // await disputeManager.setAccepted(...Object.values(data)) + await accessProofCondition.fulfill(agreementId, ...Object.values(data), { from: receiver }) + + assert.strictEqual( + (await conditionStoreManager.getConditionState(agreement.conditionIds[0])).toNumber(), + constants.condition.state.fulfilled) + + // get reward + await escrowPaymentCondition.fulfill(agreementId, did, escrowAmounts, receivers, escrowPaymentCondition.address, token.address, agreement.conditionIds[1], agreement.conditionIds[0], { from: receiver }) + + assert.strictEqual( + (await conditionStoreManager.getConditionState(agreement.conditionIds[2])).toNumber(), + constants.condition.state.fulfilled + ) + + assert.strictEqual(await getBalance(token, sender), 0) + assert.strictEqual(await getBalance(token, lockPaymentCondition.address), 0) + assert.strictEqual(await getBalance(token, escrowPaymentCondition.address), 0) + assert.strictEqual(await getBalance(token, receivers[0]), escrowAmounts[0]) + assert.strictEqual(await getBalance(token, receivers[1]), escrowAmounts[1]) + + // make sure decryption works + const ev = await accessProofCondition.getPastEvents('Fulfilled', { fromBlock: 0, toBlock: 'latest', filter: { _agreementId: agreementId } }) + const [cipherL, cipherR] = ev[0].returnValues._cipher + const k2 = babyJub.mulPointEscalar(providerPub, F.e(buyerK)) + + const plain = mimcdecrypt(cipherL, cipherR, k2[0]) + assert.strictEqual(origHash, poseidon([plain.xL, plain.xR])) + }) + + it('should create escrow agreement and abort after timeout', async () => { + const { owner } = await setupTest() + + // prepare: escrow agreement + const { agreementId, data, did, didSeed, agreement, sender, receivers, escrowAmounts, checksum, url, timeOutAccess } = await prepareEscrowAgreementMultipleEscrow({ timeOutAccess: 10 }) + const totalAmount = escrowAmounts[0] + escrowAmounts[1] + const receiver = receivers[0] + + // register DID + await didRegistry.registerAttribute(didSeed, checksum, [], url, { from: receiver }) + + // create agreement + await accessTemplate.createAgreement(agreementId, ...Object.values(agreement)) + + // fill up wallet + await token.mint(sender, totalAmount, { from: owner }) + + // fulfill lock reward + await token.approve(lockPaymentCondition.address, totalAmount, { from: sender }) + await lockPaymentCondition.fulfill(agreementId, did, escrowPaymentCondition.address, token.address, escrowAmounts, receivers, { from: sender }) + assert.strictEqual( + (await conditionStoreManager.getConditionState(agreement.conditionIds[1])).toNumber(), + constants.condition.state.fulfilled) + + // No update since access is not fulfilled yet + // refund + const result = await escrowPaymentCondition.fulfill(agreementId, did, escrowAmounts, receivers, escrowPaymentCondition.address, token.address, agreement.conditionIds[1], agreement.conditionIds[0], { from: receiver }) + assert.strictEqual( + (await conditionStoreManager.getConditionState(agreement.conditionIds[2])).toNumber(), + constants.condition.state.unfulfilled + ) + assert.strictEqual(result.logs.length, 0) + + // wait: for time out + await increaseTime.mineBlocks(web3, timeOutAccess) + + // abort: fulfill access after timeout + await accessProofCondition.fulfill(agreementId, ...Object.values(data), { from: receiver }) + assert.strictEqual( + (await conditionStoreManager.getConditionState(agreement.conditionIds[0])).toNumber(), + constants.condition.state.aborted) + + // refund + await escrowPaymentCondition.fulfill(agreementId, did, escrowAmounts, receivers, escrowPaymentCondition.address, token.address, agreement.conditionIds[1], agreement.conditionIds[0], { from: sender }) + assert.strictEqual( + (await conditionStoreManager.getConditionState(agreement.conditionIds[2])).toNumber(), + constants.condition.state.fulfilled + ) + assert.strictEqual(await getBalance(token, receivers[0]), 0) + assert.strictEqual(await getBalance(token, receivers[1]), 0) + assert.strictEqual(await getBalance(token, escrowPaymentCondition.address), 0) + assert.strictEqual(await getBalance(token, sender), totalAmount) + }) + }) + +}) From 5d087433dae49d8787ff836d6b7c25c371927487 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Sami=20M=C3=A4kel=C3=A4?= Date: Wed, 26 Jan 2022 16:24:21 +0200 Subject: [PATCH 02/32] simple test working --- test/int/nft/NFTAccessProofAgreement.Test.js | 136 +++---------------- 1 file changed, 16 insertions(+), 120 deletions(-) diff --git a/test/int/nft/NFTAccessProofAgreement.Test.js b/test/int/nft/NFTAccessProofAgreement.Test.js index 6ed70614..f31dac1d 100644 --- a/test/int/nft/NFTAccessProofAgreement.Test.js +++ b/test/int/nft/NFTAccessProofAgreement.Test.js @@ -13,10 +13,7 @@ const NFTHolderCondition = artifacts.require('NFTHolderCondition') const constants = require('../../helpers/constants.js') const deployConditions = require('../../helpers/deployConditions.js') const deployManagers = require('../../helpers/deployManagers.js') -const { getBalance } = require('../../helpers/getBalance.js') -const increaseTime = require('../../helpers/increaseTime.js') const testUtils = require('../../helpers/utils') -const mimcdecrypt = require('../../helpers/mimcdecrypt').decrypt const poseidon = require('circomlib').poseidon const babyJub = require('circomlib').babyJub @@ -28,7 +25,6 @@ const snarkjs = require('snarkjs') const { unstringifyBigInts } = require('ffjavascript').utils contract('NFT Access Proof Template integration test', (accounts) => { - const web3 = global.web3 const didSeed = testUtils.generateId() const checksum = testUtils.generateId() const url = 'https://raw.githubusercontent.com/nevermined-io/assets/main/images/logo/banner_logo.png' @@ -41,18 +37,13 @@ contract('NFT Access Proof Template integration test', (accounts) => { conditionStoreManager, templateStoreManager, nftAccessTemplate, - accessProofCondition, - lockPaymentCondition, - escrowPaymentCondition + accessProofCondition const [ owner, deployer, artist, - collector1, - collector2, - gallery, - market, - someone + receiver, + someone, ] = accounts async function setupTest() { ({ @@ -114,7 +105,6 @@ contract('NFT Access Proof Template integration test', (accounts) => { escrowAmounts = [11, 4], timeLockAccess = 0, timeOutAccess = 0, - didSeed = testUtils.generateId(), url = constants.registry.url, checksum = constants.bytes32.one } = {}) { @@ -122,8 +112,6 @@ contract('NFT Access Proof Template integration test', (accounts) => { const orig2 = 333n const origHash = poseidon([orig1, orig2]) - const did = await didRegistry.hashDID(didSeed, receivers[0]) - const buyerK = 123 const providerK = 234 const buyerPub = babyJub.mulPointEscalar(babyJub.Base8, F.e(buyerK)) @@ -169,7 +157,7 @@ contract('NFT Access Proof Template integration test', (accounts) => { const proofData = proofSolidity.split(',')[0] // construct agreement - const conditionIdNFTHolder = await nftHolderCondition.generateId(agreementAccessId, + const conditionIdNFTHolder = await nftHolderCondition.generateId(agreementId, await nftHolderCondition.hashValues(did, receiver, 1)) const conditionIdAccess = await accessProofCondition.generateId(agreementId, await accessProofCondition.hashValues(origHash, buyerPub, providerPub)) @@ -210,7 +198,7 @@ contract('NFT Access Proof Template integration test', (accounts) => { } } - describe.only('As an artist I want to register a new artwork', () => { + describe('As an artist I want to register a new artwork', () => { it('I want to register a new artwork and tokenize (via NFT). I want to get 10% of royalties', async () => { await setupTest() @@ -222,27 +210,24 @@ contract('NFT Access Proof Template integration test', (accounts) => { const balance = await nft.balanceOf(artist, did) assert.strictEqual(5, balance.toNumber()) + + await nft.safeTransferFrom(artist, receiver, did, 2, '0x', {from: artist}) }) }) describe('create and fulfill access agreement', function() { this.timeout(100000) it('should create access agreement', async () => { - // prepare: escrow agreement - const { agreementId, data, did, didSeed, agreement, sender, receivers, escrowAmounts, checksum, url, buyerK, providerPub, origHash } = await prepareAgreement() - const totalAmount = escrowAmounts[0] + escrowAmounts[1] - const receiver = receivers[0] - // register DID - await didRegistry.registerAttribute(didSeed, checksum, [], url, { from: receiver }) + const { agreementId, data, agreement } = await prepareAgreement() // create agreement - await accessTemplate.createAgreement(agreementId, ...Object.values(agreement)) + await nftAccessTemplate.createAgreement(agreementId, ...Object.values(agreement)) // check state of agreement and conditions expect((await agreementStoreManager.getAgreement(agreementId)).did) .to.equal(did) - const conditionTypes = await accessTemplate.getConditionTypes() + const conditionTypes = await nftAccessTemplate.getConditionTypes() let storedCondition agreement.conditionIds.forEach(async (conditionId, i) => { storedCondition = await conditionStoreManager.getCondition(conditionId) @@ -250,111 +235,22 @@ contract('NFT Access Proof Template integration test', (accounts) => { expect(storedCondition.state.toNumber()).to.equal(constants.condition.state.unfulfilled) }) - // fill up wallet - await token.mint(sender, totalAmount, { from: owner }) - - assert.strictEqual(await getBalance(token, sender), totalAmount) - assert.strictEqual(await getBalance(token, lockPaymentCondition.address), 0) - assert.strictEqual(await getBalance(token, escrowPaymentCondition.address), 0) - assert.strictEqual(await getBalance(token, receiver), 0) - - // fulfill lock reward - await token.approve(lockPaymentCondition.address, totalAmount, { from: sender }) - await lockPaymentCondition.fulfill(agreementId, did, escrowPaymentCondition.address, token.address, escrowAmounts, receivers, { from: sender }) - - assert.strictEqual(await getBalance(token, sender), 0) - assert.strictEqual(await getBalance(token, lockPaymentCondition.address), 0) - assert.strictEqual(await getBalance(token, escrowPaymentCondition.address), totalAmount) - assert.strictEqual(await getBalance(token, receiver), 0) - - assert.strictEqual( - (await conditionStoreManager.getConditionState(agreement.conditionIds[1])).toNumber(), - constants.condition.state.fulfilled) - - // fulfill access - // await disputeManager.setAccepted(...Object.values(data)) - await accessProofCondition.fulfill(agreementId, ...Object.values(data), { from: receiver }) - + // fulfill holder + await nftHolderCondition.fulfill( + agreementId, did, receiver, 1, { from: someone }) assert.strictEqual( (await conditionStoreManager.getConditionState(agreement.conditionIds[0])).toNumber(), constants.condition.state.fulfilled) - // get reward - await escrowPaymentCondition.fulfill(agreementId, did, escrowAmounts, receivers, escrowPaymentCondition.address, token.address, agreement.conditionIds[1], agreement.conditionIds[0], { from: receiver }) - - assert.strictEqual( - (await conditionStoreManager.getConditionState(agreement.conditionIds[2])).toNumber(), - constants.condition.state.fulfilled - ) - - assert.strictEqual(await getBalance(token, sender), 0) - assert.strictEqual(await getBalance(token, lockPaymentCondition.address), 0) - assert.strictEqual(await getBalance(token, escrowPaymentCondition.address), 0) - assert.strictEqual(await getBalance(token, receivers[0]), escrowAmounts[0]) - assert.strictEqual(await getBalance(token, receivers[1]), escrowAmounts[1]) - - // make sure decryption works - const ev = await accessProofCondition.getPastEvents('Fulfilled', { fromBlock: 0, toBlock: 'latest', filter: { _agreementId: agreementId } }) - const [cipherL, cipherR] = ev[0].returnValues._cipher - const k2 = babyJub.mulPointEscalar(providerPub, F.e(buyerK)) - - const plain = mimcdecrypt(cipherL, cipherR, k2[0]) - assert.strictEqual(origHash, poseidon([plain.xL, plain.xR])) - }) - - it('should create escrow agreement and abort after timeout', async () => { - const { owner } = await setupTest() - - // prepare: escrow agreement - const { agreementId, data, did, didSeed, agreement, sender, receivers, escrowAmounts, checksum, url, timeOutAccess } = await prepareEscrowAgreementMultipleEscrow({ timeOutAccess: 10 }) - const totalAmount = escrowAmounts[0] + escrowAmounts[1] - const receiver = receivers[0] - - // register DID - await didRegistry.registerAttribute(didSeed, checksum, [], url, { from: receiver }) - - // create agreement - await accessTemplate.createAgreement(agreementId, ...Object.values(agreement)) - - // fill up wallet - await token.mint(sender, totalAmount, { from: owner }) + // fulfill access + await accessProofCondition.fulfill(agreementId, ...Object.values(data), { from: artist }) - // fulfill lock reward - await token.approve(lockPaymentCondition.address, totalAmount, { from: sender }) - await lockPaymentCondition.fulfill(agreementId, did, escrowPaymentCondition.address, token.address, escrowAmounts, receivers, { from: sender }) assert.strictEqual( (await conditionStoreManager.getConditionState(agreement.conditionIds[1])).toNumber(), constants.condition.state.fulfilled) - // No update since access is not fulfilled yet - // refund - const result = await escrowPaymentCondition.fulfill(agreementId, did, escrowAmounts, receivers, escrowPaymentCondition.address, token.address, agreement.conditionIds[1], agreement.conditionIds[0], { from: receiver }) - assert.strictEqual( - (await conditionStoreManager.getConditionState(agreement.conditionIds[2])).toNumber(), - constants.condition.state.unfulfilled - ) - assert.strictEqual(result.logs.length, 0) - - // wait: for time out - await increaseTime.mineBlocks(web3, timeOutAccess) - - // abort: fulfill access after timeout - await accessProofCondition.fulfill(agreementId, ...Object.values(data), { from: receiver }) - assert.strictEqual( - (await conditionStoreManager.getConditionState(agreement.conditionIds[0])).toNumber(), - constants.condition.state.aborted) - - // refund - await escrowPaymentCondition.fulfill(agreementId, did, escrowAmounts, receivers, escrowPaymentCondition.address, token.address, agreement.conditionIds[1], agreement.conditionIds[0], { from: sender }) - assert.strictEqual( - (await conditionStoreManager.getConditionState(agreement.conditionIds[2])).toNumber(), - constants.condition.state.fulfilled - ) - assert.strictEqual(await getBalance(token, receivers[0]), 0) - assert.strictEqual(await getBalance(token, receivers[1]), 0) - assert.strictEqual(await getBalance(token, escrowPaymentCondition.address), 0) - assert.strictEqual(await getBalance(token, sender), totalAmount) }) + }) }) From afa16d21f1e9681626caf4b6f0ea0948ca13e20f Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Sami=20M=C3=A4kel=C3=A4?= Date: Wed, 26 Jan 2022 16:39:49 +0200 Subject: [PATCH 03/32] escrow with multiple release conditions --- .../rewards/MultiEscrowPaymentCondition.sol | 317 ++++++++++++++++++ 1 file changed, 317 insertions(+) create mode 100644 contracts/conditions/rewards/MultiEscrowPaymentCondition.sol diff --git a/contracts/conditions/rewards/MultiEscrowPaymentCondition.sol b/contracts/conditions/rewards/MultiEscrowPaymentCondition.sol new file mode 100644 index 00000000..687e22f4 --- /dev/null +++ b/contracts/conditions/rewards/MultiEscrowPaymentCondition.sol @@ -0,0 +1,317 @@ +pragma solidity ^0.8.0; +// Copyright 2020 Keyko GmbH. +// SPDX-License-Identifier: (Apache-2.0 AND CC-BY-4.0) +// Code is Apache-2.0 and docs are CC-BY-4.0 + +import './Reward.sol'; +import '../../Common.sol'; +import '../ConditionStoreLibrary.sol'; +import '@openzeppelin/contracts-upgradeable/token/ERC20/utils/SafeERC20Upgradeable.sol'; +import '@openzeppelin/contracts-upgradeable/security/ReentrancyGuardUpgradeable.sol'; + +/** + * @title Escrow Payment Condition + * @author Keyko + * + * @dev Implementation of the Escrow Payment Condition + * + * The Escrow payment is reward condition in which only + * can release reward if lock and release conditions + * are fulfilled. + */ +contract MultiEscrowPaymentCondition is Reward, Common, ReentrancyGuardUpgradeable { + + using SafeERC20Upgradeable for IERC20Upgradeable; + + bytes32 constant public CONDITION_TYPE = keccak256('EscrowPayment'); + + event Fulfilled( + bytes32 indexed _agreementId, + address indexed _tokenAddress, + address[] _receivers, + bytes32 _conditionId, + uint256[] _amounts + ); + + event Received( + address indexed _from, + uint _value + ); + + receive() external payable { + emit Received(msg.sender, msg.value); + } + + /** + * @notice initialize init the + * contract with the following parameters + * @param _owner contract's owner account address + * @param _conditionStoreManagerAddress condition store manager address + */ + function initialize( + address _owner, + address _conditionStoreManagerAddress + ) + external + initializer() + { + require( + _conditionStoreManagerAddress != address(0), + 'Invalid address' + ); + OwnableUpgradeable.__Ownable_init(); + transferOwnership(_owner); + conditionStoreManager = ConditionStoreManager( + _conditionStoreManagerAddress + ); + } + + + /** + * @notice hashValues generates the hash of condition inputs + * with the following parameters + * @param _did asset decentralized identifier + * @param _amounts token amounts to be locked/released + * @param _receivers receiver's addresses + * @param _lockPaymentAddress lock payment contract address + * @param _tokenAddress the ERC20 contract address to use during the payment + * @param _lockCondition lock condition identifier + * @param _releaseConditions release condition identifier + * @return bytes32 hash of all these values + */ + function hashValues( + bytes32 _did, + uint256[] memory _amounts, + address[] memory _receivers, + address _lockPaymentAddress, + address _tokenAddress, + bytes32 _lockCondition, + bytes32[] memory _releaseConditions + ) + public pure + returns (bytes32) + { + require( + _amounts.length == _receivers.length, + 'Amounts and Receivers arguments have wrong length' + ); + return keccak256( + abi.encode( + _did, + _amounts, + _receivers, + _lockPaymentAddress, + _tokenAddress, + _lockCondition, + _releaseConditions + ) + ); + } + + /** + * @notice hashValuesLockPayment generates the hash of condition inputs + * with the following parameters + * @param _did the asset decentralized identifier + * @param _rewardAddress the contract address where the reward is locked + * @param _tokenAddress the ERC20 contract address to use during the lock payment. + * If the address is 0x0 means we won't use a ERC20 but ETH for payment + * @param _amounts token amounts to be locked/released + * @param _receivers receiver's addresses + * @return bytes32 hash of all these values + */ + function hashValuesLockPayment( + bytes32 _did, + address _rewardAddress, + address _tokenAddress, + uint256[] memory _amounts, + address[] memory _receivers + ) + public + pure + returns (bytes32) + { + return keccak256(abi.encode( + _did, + _rewardAddress, + _tokenAddress, + _amounts, + _receivers + )); + } + + /** + * @notice fulfill escrow reward condition + * @dev fulfill method checks whether the lock and + * release conditions are fulfilled in order to + * release/refund the reward to receiver/sender + * respectively. + * @param _agreementId agreement identifier + * @param _did asset decentralized identifier + * @param _amounts token amounts to be locked/released + * @param _receivers receiver's address + * @param _lockPaymentAddress lock payment contract address + * @param _tokenAddress the ERC20 contract address to use during the payment + * @param _lockCondition lock condition identifier + * @param _releaseConditions release condition identifier + * @return condition state (Fulfilled/Aborted) + */ + function fulfill( + bytes32 _agreementId, + bytes32 _did, + uint256[] memory _amounts, + address[] memory _receivers, + address _lockPaymentAddress, + address _tokenAddress, + bytes32 _lockCondition, + bytes32[] memory _releaseConditions + ) + external + nonReentrant + returns (ConditionStoreLibrary.ConditionState) + { + + require(keccak256( + abi.encode( + _agreementId, + conditionStoreManager.getConditionTypeRef(_lockCondition), + hashValuesLockPayment(_did, _lockPaymentAddress, _tokenAddress, _amounts, _receivers) + ) + ) == _lockCondition, + 'LockCondition ID does not match' + ); + + require( + conditionStoreManager.getConditionState(_lockCondition) == + ConditionStoreLibrary.ConditionState.Fulfilled, + 'LockCondition needs to be Fulfilled' + ); + + bool allFulfilled = true; + bool allAborted = true; + for (uint i = 0; i < _releaseConditions.length; i++) { + ConditionStoreLibrary.ConditionState cur = conditionStoreManager.getConditionState(_releaseConditions[i]); + if (cur != ConditionStoreLibrary.ConditionState.Fulfilled) { + allFulfilled = false; + } + if (cur != ConditionStoreLibrary.ConditionState.Aborted) { + allAborted = false; + } + } + + bytes32 id = generateId( + _agreementId, + hashValues( + _did, + _amounts, + _receivers, + _lockPaymentAddress, + _tokenAddress, + _lockCondition, + _releaseConditions + ) + ); + + ConditionStoreLibrary.ConditionState state; + if (allFulfilled) { + if (_tokenAddress != address(0)) + state = _transferAndFulfillERC20(id, _tokenAddress, _receivers, _amounts); + else + state = _transferAndFulfillETH(id, _receivers, _amounts); + + emit Fulfilled(_agreementId, _tokenAddress, _receivers, id, _amounts); + + } else if (allAborted) { + + uint256[] memory _totalAmounts = new uint256[](1); + _totalAmounts[0] = calculateTotalAmount(_amounts); + address[] memory _originalSender = new address[](1); + _originalSender[0] = conditionStoreManager.getConditionCreatedBy(_lockCondition); + + if (_tokenAddress != address(0)) + state = _transferAndFulfillERC20(id, _tokenAddress, _originalSender, _totalAmounts); + else + state = _transferAndFulfillETH(id, _originalSender, _totalAmounts); + + emit Fulfilled(_agreementId, _tokenAddress, _originalSender, id, _totalAmounts); + + } else { + return conditionStoreManager.getConditionState(id); + } + + return state; + } + + /** + * @notice _transferAndFulfill transfer ERC20 tokens and + * fulfill the condition + * @param _id condition identifier + * @param _tokenAddress the ERC20 contract address to use during the payment + * @param _receivers receiver's address + * @param _amounts token amount to be locked/released + * @return condition state (Fulfilled/Aborted) + */ + function _transferAndFulfillERC20( + bytes32 _id, + address _tokenAddress, + address[] memory _receivers, + uint256[] memory _amounts + ) + private + returns (ConditionStoreLibrary.ConditionState) + { + + IERC20Upgradeable token = ERC20Upgradeable(_tokenAddress); + + for(uint i = 0; i < _receivers.length; i++) { + require( + _receivers[i] != address(this), + 'Escrow contract can not be a receiver' + ); + token.safeTransfer(_receivers[i], _amounts[i]); + } + + return super.fulfill( + _id, + ConditionStoreLibrary.ConditionState.Fulfilled + ); + } + + /** + * @notice _transferAndFulfill transfer ETH and + * fulfill the condition + * @param _id condition identifier + * @param _receivers receiver's address + * @param _amounts token amount to be locked/released + * @return condition state (Fulfilled/Aborted) + */ + function _transferAndFulfillETH( + bytes32 _id, + address[] memory _receivers, + uint256[] memory _amounts + ) + private + returns (ConditionStoreLibrary.ConditionState) + { + for(uint i = 0; i < _receivers.length; i++) { + require( + _receivers[i] != address(this), + 'Escrow contract can not be a receiver' + ); + + require( + address(this).balance >= _amounts[i], + 'Contract balance too low' + ); + + // solhint-disable-next-line + (bool sent,) = _receivers[i].call{value: _amounts[i]}(''); + require(sent, 'Failed to send Ether'); + } + + return super.fulfill( + _id, + ConditionStoreLibrary.ConditionState.Fulfilled + ); + } + +} From a3b9680d6cf84cb1ff8f93ebcfa3c774439105a3 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Sami=20M=C3=A4kel=C3=A4?= Date: Wed, 26 Jan 2022 16:52:21 +0200 Subject: [PATCH 04/32] lint --- test/int/nft/NFTAccessProofAgreement.Test.js | 13 +++++-------- 1 file changed, 5 insertions(+), 8 deletions(-) diff --git a/test/int/nft/NFTAccessProofAgreement.Test.js b/test/int/nft/NFTAccessProofAgreement.Test.js index f31dac1d..d098934d 100644 --- a/test/int/nft/NFTAccessProofAgreement.Test.js +++ b/test/int/nft/NFTAccessProofAgreement.Test.js @@ -37,13 +37,15 @@ contract('NFT Access Proof Template integration test', (accounts) => { conditionStoreManager, templateStoreManager, nftAccessTemplate, + did, + nftHolderCondition, accessProofCondition const [ owner, deployer, artist, receiver, - someone, + someone ] = accounts async function setupTest() { ({ @@ -59,9 +61,7 @@ contract('NFT Access Proof Template integration test', (accounts) => { )); ({ - accessProofCondition, - lockPaymentCondition, - escrowPaymentCondition + accessProofCondition } = await deployConditions( deployer, owner, @@ -211,7 +211,7 @@ contract('NFT Access Proof Template integration test', (accounts) => { const balance = await nft.balanceOf(artist, did) assert.strictEqual(5, balance.toNumber()) - await nft.safeTransferFrom(artist, receiver, did, 2, '0x', {from: artist}) + await nft.safeTransferFrom(artist, receiver, did, 2, '0x', { from: artist }) }) }) @@ -248,9 +248,6 @@ contract('NFT Access Proof Template integration test', (accounts) => { assert.strictEqual( (await conditionStoreManager.getConditionState(agreement.conditionIds[1])).toNumber(), constants.condition.state.fulfilled) - }) - }) - }) From 07f045d4c06fce5c57246c8e60d40bfcc258d95a Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Sami=20M=C3=A4kel=C3=A4?= Date: Wed, 26 Jan 2022 19:05:27 +0200 Subject: [PATCH 05/32] adding sales with access --- .../templates/NFTSalesWithAccessTemplate.sol | 110 ++++++ test/helpers/deployConditions.js | 9 + .../NFTSalesWithAccessProofAgreement.Test.js | 329 ++++++++++++++++++ 3 files changed, 448 insertions(+) create mode 100644 contracts/templates/NFTSalesWithAccessTemplate.sol create mode 100644 test/int/nft/NFTSalesWithAccessProofAgreement.Test.js diff --git a/contracts/templates/NFTSalesWithAccessTemplate.sol b/contracts/templates/NFTSalesWithAccessTemplate.sol new file mode 100644 index 00000000..30c18827 --- /dev/null +++ b/contracts/templates/NFTSalesWithAccessTemplate.sol @@ -0,0 +1,110 @@ +pragma solidity ^0.8.0; +// Copyright 2020 Keyko GmbH. +// SPDX-License-Identifier: (Apache-2.0 AND CC-BY-4.0) +// Code is Apache-2.0 and docs are CC-BY-4.0 + + +import './BaseEscrowTemplate.sol'; +import '../conditions/LockPaymentCondition.sol'; +import '../conditions/NFTs/TransferNFTCondition.sol'; +import '../conditions/rewards/MultiEscrowPaymentCondition.sol'; +import '../registry/DIDRegistry.sol'; +import '../conditions/AccessProofCondition.sol'; + + +/** + * @title Agreement Template + * @author Keyko + * + * @dev Implementation of NFT Sales Template + * + * The NFT Sales template supports an scenario where a NFT owner + * can sell that asset to a new Owner. + * Anyone (consumer/provider/publisher) can use this template in order + * to setup an agreement allowing a NFT owner to transfer the asset ownership + * after some payment. + * The template is a composite of 3 basic conditions: + * - Lock Payment Condition + * - Transfer NFT Condition + * - Escrow Reward Condition + * + * This scenario takes into account royalties for original creators in the secondary market. + * Once the agreement is created, the consumer after payment can request the transfer of the NFT + * from the current owner for a specific DID. + */ +contract NFTSalesWithAccessTemplate is BaseEscrowTemplate { + + DIDRegistry internal didRegistry; + LockPaymentCondition internal lockPaymentCondition; + ITransferNFT internal transferCondition; + MultiEscrowPaymentCondition internal rewardCondition; + AccessProofCondition internal accessCondition; + + + /** + * @notice initialize init the + * contract with the following parameters. + * @dev this function is called only once during the contract + * initialization. It initializes the ownable feature, and + * set push the required condition types including + * access secret store, lock reward and escrow reward conditions. + * @param _owner contract's owner account address + * @param _agreementStoreManagerAddress agreement store manager contract address + * @param _lockPaymentConditionAddress lock reward condition contract address + * @param _transferConditionAddress transfer NFT condition contract address + * @param _escrowPaymentAddress escrow reward condition contract address + */ + function initialize( + address _owner, + address _agreementStoreManagerAddress, + address _lockPaymentConditionAddress, + address _transferConditionAddress, + address payable _escrowPaymentAddress, + address _accessCondition + ) + external + initializer() + { + require( + _owner != address(0) && + _agreementStoreManagerAddress != address(0) && + _lockPaymentConditionAddress != address(0) && + _transferConditionAddress != address(0) && + _escrowPaymentAddress != address(0) && + _accessCondition != address(0), + 'Invalid address' + ); + + OwnableUpgradeable.__Ownable_init(); + transferOwnership(_owner); + + agreementStoreManager = AgreementStoreManager( + _agreementStoreManagerAddress + ); + + didRegistry = DIDRegistry( + agreementStoreManager.getDIDRegistryAddress() + ); + + lockPaymentCondition = LockPaymentCondition( + _lockPaymentConditionAddress + ); + + transferCondition = TransferNFTCondition( + _transferConditionAddress + ); + + rewardCondition = MultiEscrowPaymentCondition( + _escrowPaymentAddress + ); + + accessCondition = AccessProofCondition( + _accessCondition + ); + + conditionTypes.push(address(lockPaymentCondition)); + conditionTypes.push(address(transferCondition)); + conditionTypes.push(address(rewardCondition)); + conditionTypes.push(address(accessCondition)); + } +} diff --git a/test/helpers/deployConditions.js b/test/helpers/deployConditions.js index 895239da..967d8766 100644 --- a/test/helpers/deployConditions.js +++ b/test/helpers/deployConditions.js @@ -2,6 +2,7 @@ const AccessCondition = artifacts.require('AccessCondition') const AccessProofCondition = artifacts.require('AccessProofCondition') const EscrowPaymentCondition = artifacts.require('EscrowPaymentCondition') +const MultiEscrowPaymentCondition = artifacts.require('MultiEscrowPaymentCondition') const LockPaymentCondition = artifacts.require('LockPaymentCondition') const ComputeExecutionCondition = artifacts.require('ComputeExecutionCondition') const DisputeManager = artifacts.require('PlonkVerifier') @@ -48,6 +49,13 @@ const deployConditions = async function( { from: deployer } ) + const multiEscrowCondition = await MultiEscrowPaymentCondition.new({ from: deployer }) + await multiEscrowCondition.initialize( + owner, + conditionStoreManager.address, + { from: deployer } + ) + const computeExecutionCondition = await ComputeExecutionCondition.new({ from: deployer }) await computeExecutionCondition.methods['initialize(address,address,address)']( owner, @@ -60,6 +68,7 @@ const deployConditions = async function( accessCondition, accessProofCondition, escrowPaymentCondition, + multiEscrowCondition, lockPaymentCondition, computeExecutionCondition, disputeManager diff --git a/test/int/nft/NFTSalesWithAccessProofAgreement.Test.js b/test/int/nft/NFTSalesWithAccessProofAgreement.Test.js new file mode 100644 index 00000000..19a44d01 --- /dev/null +++ b/test/int/nft/NFTSalesWithAccessProofAgreement.Test.js @@ -0,0 +1,329 @@ +/* eslint-env mocha */ +/* eslint-disable no-console */ +/* global artifacts, contract, describe, it, expect */ + +const chai = require('chai') +const { assert } = chai +const chaiAsPromised = require('chai-as-promised') +chai.use(chaiAsPromised) + +const NFTSalesTemplate = artifacts.require('NFTSalesWithAccessTemplate') +const NFTHolderCondition = artifacts.require('NFTHolderCondition') +const TransferNFTCondition = artifacts.require('TransferNFTCondition') + +const constants = require('../../helpers/constants.js') +const deployConditions = require('../../helpers/deployConditions.js') +const deployManagers = require('../../helpers/deployManagers.js') +const testUtils = require('../../helpers/utils') + +const poseidon = require('circomlib').poseidon +const babyJub = require('circomlib').babyJub +const mimcjs = require('circomlib').mimcsponge +const ZqField = require('ffjavascript').ZqField +const Scalar = require('ffjavascript').Scalar +const F = new ZqField(Scalar.fromString('21888242871839275222246405745257275088548364400416034343698204186575808495617')) +const snarkjs = require('snarkjs') +const { unstringifyBigInts } = require('ffjavascript').utils + +const { getBalance } = require('../../helpers/getBalance.js') + +contract('NFT Sales with Access Proof Template integration test', (accounts) => { + const didSeed = testUtils.generateId() + const checksum = testUtils.generateId() + const url = 'https://raw.githubusercontent.com/nevermined-io/assets/main/images/logo/banner_logo.png' + const royalties = 10 // 10% of royalties in the secondary market + const cappedAmount = 5 + let token, + didRegistry, + nft, + agreementStoreManager, + conditionStoreManager, + templateStoreManager, + nftAccessTemplate, + did, + transferCondition, + nftSalesTemplate, + nftSalesAgreement, + multiEscrowCondition, + lockPaymentCondition, + nftHolderCondition, + accessProofCondition + const [ + owner, + deployer, + artist, + receiver, + someone, + gallery, + market + ] = accounts + const collector1 = receiver + + const numberNFTs = 1 + const nftPrice = 20 + const amounts = [15, 5] + const receivers = [artist, gallery] + + async function setupTest() { + ({ + token, + didRegistry, + nft, + agreementStoreManager, + conditionStoreManager, + templateStoreManager + } = await deployManagers( + deployer, + owner + )); + + ({ + accessProofCondition, + multiEscrowCondition, + lockPaymentCondition, + transferCondition + } = await deployConditions( + deployer, + owner, + agreementStoreManager, + conditionStoreManager, + didRegistry, + token + )) + nftHolderCondition = await NFTHolderCondition.new({ from: deployer }) + await nftHolderCondition.initialize( + owner, + conditionStoreManager.address, + nft.address, + { from: deployer } + ) + transferCondition = await TransferNFTCondition.new() + await transferCondition.initialize( + owner, + conditionStoreManager.address, + nft.address, + market, + { from: deployer } + ) + nftSalesTemplate = await NFTSalesTemplate.new() + await nftSalesTemplate.methods['initialize(address,address,address,address,address,address)']( + owner, + agreementStoreManager.address, + lockPaymentCondition.address, + transferCondition.address, + multiEscrowCondition.address, + accessProofCondition.address, + { from: deployer } + ) + await nft.setProxyApproval(transferCondition.address, true, { from: owner }) + + await templateStoreManager.proposeTemplate(nftSalesTemplate.address) + await templateStoreManager.approveTemplate(nftSalesTemplate.address, { from: owner }) + } + + async function prepareAgreement({ + agreementId = testUtils.generateId(), + receivers, + amounts, + timeLockAccess = 0, + timeOutAccess = 0 + } = {}) { + const orig1 = 222n + const orig2 = 333n + const origHash = poseidon([orig1, orig2]) + + const buyerK = 123 + const providerK = 234 + const buyerPub = babyJub.mulPointEscalar(babyJub.Base8, F.e(buyerK)) + const providerPub = babyJub.mulPointEscalar(babyJub.Base8, F.e(providerK)) + + const k = babyJub.mulPointEscalar(buyerPub, F.e(providerK)) + + const cipher = mimcjs.hash(orig1, orig2, k[0]) + + const snarkParams = { + buyer_x: buyerPub[0], + buyer_y: buyerPub[1], + provider_x: providerPub[0], + provider_y: providerPub[1], + xL_in: orig1, + xR_in: orig2, + cipher_xL_in: cipher.xL, + cipher_xR_in: cipher.xR, + provider_k: providerK, + hash_plain: origHash + } + + // console.log(snark_params) + + const { proof } = await snarkjs.plonk.fullProve( + snarkParams, + 'circuits/keytransfer.wasm', + 'circuits/keytransfer.zkey' + ) + + const signals = [ + buyerPub[0], + buyerPub[1], + providerPub[0], + providerPub[1], + cipher.xL, + cipher.xR, + origHash + ] + + const proofSolidity = (await snarkjs.plonk.exportSolidityCallData(unstringifyBigInts(proof), signals)) + + const proofData = proofSolidity.split(',')[0] + + const conditionIdLockPayment = await lockPaymentCondition.generateId(agreementId, + await lockPaymentCondition.hashValues(did, multiEscrowCondition.address, token.address, amounts, receivers)) + + const conditionIdTransferNFT = await transferCondition.generateId(agreementId, + await transferCondition.hashValues(did, artist, receiver, numberNFTs, conditionIdLockPayment)) + + const conditionIdAccess = await accessProofCondition.generateId(agreementId, + await accessProofCondition.hashValues(origHash, buyerPub, providerPub)) + + const conditionIdEscrow = await multiEscrowCondition.generateId(agreementId, + await multiEscrowCondition.hashValues(did, amounts, receivers, multiEscrowCondition.address, token.address, conditionIdLockPayment, + [conditionIdTransferNFT, conditionIdAccess])) + + nftSalesAgreement = { + did: did, + conditionIds: [ + conditionIdLockPayment, + conditionIdTransferNFT, + conditionIdEscrow, + conditionIdAccess + ], + timeLocks: [0, 0, 0], + timeOuts: [0, 0, 0] + } + + const data = { + origHash, + buyerPub, + providerPub, + cipher: [cipher.xL, cipher.xR], + proof: proofData + } + return { + agreementId, + did, + data, + didSeed, + agreement: nftSalesAgreement, + timeLockAccess, + timeOutAccess, + checksum, + url, + buyerK, + providerPub, + origHash + } + } + + describe('As an artist I want to register a new artwork', () => { + it('I want to register a new artwork and tokenize (via NFT). I want to get 10% of royalties', async () => { + await setupTest() + + did = await didRegistry.hashDID(didSeed, artist) + + await didRegistry.registerMintableDID( + didSeed, checksum, [], url, cappedAmount, royalties, constants.activities.GENERATED, '', { from: artist }) + await didRegistry.mint(did, 5, { from: artist }) + + const balance = await nft.balanceOf(artist, did) + assert.strictEqual(5, balance.toNumber()) + + await nft.safeTransferFrom(artist, receiver, did, 2, '0x', { from: artist }) + }) + }) + + describe('create and fulfill access agreement', function() { + this.timeout(100000) + it('should create access agreement', async () => { + const { agreementId, data, agreement } = await prepareAgreement({ receivers, amounts }) + + // create agreement + await nftSalesTemplate.createAgreement(agreementId, ...Object.values(agreement)) + + // check state of agreement and conditions + expect((await agreementStoreManager.getAgreement(agreementId)).did) + .to.equal(did) + + const conditionTypes = await nftAccessTemplate.getConditionTypes() + let storedCondition + agreement.conditionIds.forEach(async (conditionId, i) => { + storedCondition = await conditionStoreManager.getCondition(conditionId) + expect(storedCondition.typeRef).to.equal(conditionTypes[i]) + expect(storedCondition.state.toNumber()).to.equal(constants.condition.state.unfulfilled) + }) + + // lock payment + await token.mint(collector1, nftPrice, { from: owner }) + await token.approve(lockPaymentCondition.address, nftPrice, { from: collector1 }) + await token.approve(multiEscrowCondition.address, nftPrice, { from: collector1 }) + + await lockPaymentCondition.fulfill(agreementId, did, multiEscrowCondition.address, token.address, amounts, receivers, { from: collector1 }) + + const { state } = await conditionStoreManager.getCondition(nftSalesAgreement.conditionIds[0]) + assert.strictEqual(state.toNumber(), constants.condition.state.fulfilled) + const collector1Balance = await getBalance(token, collector1) + assert.strictEqual(collector1Balance, 0) + + // transfer + const nftBalanceArtistBefore = await nft.balanceOf(artist, did) + const nftBalanceCollectorBefore = await nft.balanceOf(collector1, did) + + await nft.setApprovalForAll(transferCondition.address, true, { from: artist }) + await transferCondition.methods['fulfill(bytes32,bytes32,address,uint256,bytes32)']( + agreementId, + did, + collector1, + numberNFTs, + nftSalesAgreement.conditionIds[0], + { from: artist }) + await nft.setApprovalForAll(transferCondition.address, false, { from: artist }) + + const { state: state2 } = await conditionStoreManager.getCondition( + nftSalesAgreement.conditionIds[1]) + assert.strictEqual(state2.toNumber(), constants.condition.state.fulfilled) + + const nftBalanceArtistAfter = await nft.balanceOf(artist, did) + const nftBalanceCollectorAfter = await nft.balanceOf(collector1, did) + + assert.strictEqual(nftBalanceArtistAfter.toNumber(), nftBalanceArtistBefore.toNumber() - numberNFTs) + assert.strictEqual(nftBalanceCollectorAfter.toNumber(), nftBalanceCollectorBefore.toNumber() + numberNFTs) + + // fulfill access + await accessProofCondition.fulfill(agreementId, ...Object.values(data), { from: artist }) + + assert.strictEqual( + (await conditionStoreManager.getConditionState(agreement.conditionIds[1])).toNumber(), + constants.condition.state.fulfilled) + + // escrow + await multiEscrowCondition.fulfill( + agreementId, + did, + amounts, + receivers, + multiEscrowCondition.address, + token.address, + nftSalesAgreement.conditionIds[0], + [nftSalesAgreement.conditionIds[1], nftSalesAgreement.conditionIds[3]], + { from: artist }) + + const { state: state3 } = await conditionStoreManager.getCondition(nftSalesAgreement.conditionIds[2]) + assert.strictEqual(state3.toNumber(), constants.condition.state.fulfilled) + + assert.strictEqual(await getBalance(token, collector1), 0) + assert.strictEqual(await getBalance(token, lockPaymentCondition.address), 0) + assert.strictEqual(await getBalance(token, multiEscrowCondition.address), 0) + assert.strictEqual(await getBalance(token, receivers[0]), amounts[0]) + assert.strictEqual(await getBalance(token, receivers[1]), amounts[1]) + }) + }) +}) From 98d2347ed7972178e67f847821681fd5bc2cd6cf Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Sami=20M=C3=A4kel=C3=A4?= Date: Wed, 26 Jan 2022 19:09:50 +0200 Subject: [PATCH 06/32] seems to work --- test/int/nft/NFTSalesWithAccessProofAgreement.Test.js | 8 +++----- 1 file changed, 3 insertions(+), 5 deletions(-) diff --git a/test/int/nft/NFTSalesWithAccessProofAgreement.Test.js b/test/int/nft/NFTSalesWithAccessProofAgreement.Test.js index 19a44d01..a7aa9755 100644 --- a/test/int/nft/NFTSalesWithAccessProofAgreement.Test.js +++ b/test/int/nft/NFTSalesWithAccessProofAgreement.Test.js @@ -39,7 +39,6 @@ contract('NFT Sales with Access Proof Template integration test', (accounts) => agreementStoreManager, conditionStoreManager, templateStoreManager, - nftAccessTemplate, did, transferCondition, nftSalesTemplate, @@ -53,7 +52,6 @@ contract('NFT Sales with Access Proof Template integration test', (accounts) => deployer, artist, receiver, - someone, gallery, market ] = accounts @@ -197,8 +195,8 @@ contract('NFT Sales with Access Proof Template integration test', (accounts) => conditionIdEscrow, conditionIdAccess ], - timeLocks: [0, 0, 0], - timeOuts: [0, 0, 0] + timeLocks: [0, 0, 0, 0], + timeOuts: [0, 0, 0, 0] } const data = { @@ -253,7 +251,7 @@ contract('NFT Sales with Access Proof Template integration test', (accounts) => expect((await agreementStoreManager.getAgreement(agreementId)).did) .to.equal(did) - const conditionTypes = await nftAccessTemplate.getConditionTypes() + const conditionTypes = await nftSalesTemplate.getConditionTypes() let storedCondition agreement.conditionIds.forEach(async (conditionId, i) => { storedCondition = await conditionStoreManager.getCondition(conditionId) From 11735851d0df972246fd7e54ce5efeb63703da24 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Sami=20M=C3=A4kel=C3=A4?= Date: Thu, 27 Jan 2022 15:18:01 +0200 Subject: [PATCH 07/32] adding complex escrow --- .../rewards/NFTEscrowPaymentCondition.sol | 394 ++++++++++++++++++ 1 file changed, 394 insertions(+) create mode 100644 contracts/conditions/rewards/NFTEscrowPaymentCondition.sol diff --git a/contracts/conditions/rewards/NFTEscrowPaymentCondition.sol b/contracts/conditions/rewards/NFTEscrowPaymentCondition.sol new file mode 100644 index 00000000..d5d57b2f --- /dev/null +++ b/contracts/conditions/rewards/NFTEscrowPaymentCondition.sol @@ -0,0 +1,394 @@ +pragma solidity ^0.8.0; +// Copyright 2020 Keyko GmbH. +// SPDX-License-Identifier: (Apache-2.0 AND CC-BY-4.0) +// Code is Apache-2.0 and docs are CC-BY-4.0 + +import './Reward.sol'; +import '../../Common.sol'; +import '../ConditionStoreLibrary.sol'; +import '@openzeppelin/contracts-upgradeable/token/ERC20/utils/SafeERC20Upgradeable.sol'; +import '@openzeppelin/contracts-upgradeable/security/ReentrancyGuardUpgradeable.sol'; + +/** + * @title Escrow Payment Condition + * @author Keyko + * + * @dev Implementation of the Escrow Payment Condition + * + * The Escrow payment is reward condition in which only + * can release reward if lock and release conditions + * are fulfilled. + */ +contract NFTEscrowPaymentCondition is Reward, Common, ReentrancyGuardUpgradeable { + + using SafeERC20Upgradeable for IERC20Upgradeable; + + bytes32 constant public CONDITION_TYPE = keccak256('EscrowPayment'); + + event Fulfilled( + bytes32 indexed _agreementId, + address indexed _tokenAddress, + address[] _receivers, + bytes32 _conditionId, + uint256[] _amounts + ); + + event Received( + address indexed _from, + uint _value + ); + + receive() external payable { + emit Received(msg.sender, msg.value); + } + + /** + * @notice initialize init the + * contract with the following parameters + * @param _owner contract's owner account address + * @param _conditionStoreManagerAddress condition store manager address + */ + function initialize( + address _owner, + address _conditionStoreManagerAddress + ) + external + initializer() + { + require( + _conditionStoreManagerAddress != address(0), + 'Invalid address' + ); + OwnableUpgradeable.__Ownable_init(); + transferOwnership(_owner); + conditionStoreManager = ConditionStoreManager( + _conditionStoreManagerAddress + ); + } + + + /** + * @notice hashValues generates the hash of condition inputs + * with the following parameters + * @param _did asset decentralized identifier + * @param _amounts token amounts to be locked/released + * @param _receivers receiver's addresses + * @param _tokenAddress the ERC20 contract address to use during the payment + * @param _lockCondition lock condition identifier + * @param _releaseConditions release condition identifier + * @return bytes32 hash of all these values + */ + function hashValues( + bytes32 _did, + uint[3] memory _types, + uint256[][] memory _amounts, + address[][] memory _receivers, + address[] memory _tokenAddress, + bytes32[] memory _lockCondition, + bytes32[] memory _releaseConditions + ) + public pure + returns (bytes32) + { + require( + _amounts.length == _receivers.length, + 'Amounts and Receivers arguments have wrong length' + ); + return keccak256( + abi.encode( + _did, + _types, + _amounts, + _receivers, + _tokenAddress, + _lockCondition, + _releaseConditions + ) + ); + } + + /** + * @notice hashValuesLockPayment generates the hash of condition inputs + * with the following parameters + * @param _did the asset decentralized identifier + * @param _rewardAddress the contract address where the reward is locked + * @param _tokenAddress the ERC20 contract address to use during the lock payment. + * If the address is 0x0 means we won't use a ERC20 but ETH for payment + * @param _amounts token amounts to be locked/released + * @param _receivers receiver's addresses + * @return bytes32 hash of all these values + */ + function hashValuesLockPayment( + bytes32 _did, + address _rewardAddress, + address _tokenAddress, + uint256[] memory _amounts, + address[] memory _receivers + ) + public + pure + returns (bytes32) + { + return keccak256(abi.encode( + _did, + _rewardAddress, + _tokenAddress, + _amounts, + _receivers + )); + } + + function matchLockPayment( + bytes32 _agreementId, + bytes32 _did, + uint256[][] memory _amounts, + address[][] memory _receivers, + address[] memory _tokenAddress, + bytes32[] memory _lockConditions, + uint256 idx + ) + internal + view + returns (bool) + { + return keccak256( + abi.encode( + _agreementId, + conditionStoreManager.getConditionTypeRef(_lockConditions[idx]), + hashValuesLockPayment(_did, address(this), _tokenAddress[idx], _amounts[idx], _receivers[idx]) + ) + ) == _lockConditions[idx]; + } + + function cancelPayments( + bytes32 _agreementId, + uint[3] memory _types, + uint256[][] memory _amounts, + address[] memory _tokenAddress, + bytes32[] memory _lockConditions, + bytes32 id + ) + internal + returns (ConditionStoreLibrary.ConditionState) + { + for (uint256 i = 0; i < _types[0]; i++) { + // TODO: check that it was fulfilled + cancelPayment(_agreementId, _amounts, _tokenAddress, _lockConditions, i, id); + } + + return super.fulfill( + id, + ConditionStoreLibrary.ConditionState.Fulfilled + ); + + } + + function cancelPayment( + bytes32 _agreementId, + uint256[][] memory _amounts, + address[] memory _tokenAddress, + bytes32[] memory _lockConditions, + uint256 idx, + bytes32 id + ) + internal { + uint256[] memory _totalAmounts = new uint256[](1); + _totalAmounts[0] = calculateTotalAmount(_amounts[idx]); + address[] memory _originalSender = new address[](1); + _originalSender[0] = conditionStoreManager.getConditionCreatedBy(_lockConditions[idx]); + + if (_tokenAddress[idx] != address(0)) { + _transferAndFulfillERC20(_tokenAddress[idx], _originalSender, _totalAmounts); + } else { + _transferAndFulfillETH(_originalSender, _totalAmounts); + } + emit Fulfilled(_agreementId, _tokenAddress[idx], _originalSender, id, _totalAmounts); + + } + + function makePayment( + bytes32 _agreementId, + uint256[][] memory _amounts, + address[][] memory _receivers, + address[] memory _tokenAddress, + uint256 idx, + bytes32 id + ) + internal { + if (_tokenAddress[idx] != address(0)) { + _transferAndFulfillERC20(_tokenAddress[idx], _receivers[idx], _amounts[idx]); + } else { + _transferAndFulfillETH(_receivers[idx], _amounts[idx]); + } + emit Fulfilled(_agreementId, _tokenAddress[idx], _receivers[idx], id, _amounts[idx]); + } + + /* + * @notice fulfill escrow reward condition + * @dev fulfill method checks whether the lock and + * release conditions are fulfilled in order to + * release/refund the reward to receiver/sender + * respectively. + * @param _agreementId agreement identifier + * @param _did asset decentralized identifier + * @param _amounts token amounts to be locked/released + * @param _receivers receiver's address + * @param _tokenAddress the ERC20 contract address to use during the payment + * @param _lockCondition lock condition identifier + * @param _releaseConditions release condition identifier + * @return condition state (Fulfilled/Aborted) + */ + function fulfill( + /* + uint256[] memory _nft721Amounts, + address[] memory _nft721Receivers, + address[] memory _nft721TokenAddress, + bytes32[] memory _nft721LockConditions, + uint256[] memory _nftAmounts, + address[] memory _nftReceivers, + address[] memory _nftTokenAddress, + bytes32[] memory _nftLockConditions, + */ + bytes32 _agreementId, + bytes32 _did, + uint[3] memory _types, + uint256[][] memory _amounts, + address[][] memory _receivers, + address[] memory _tokenAddress, + bytes32[] memory _lockConditions, + bytes32[] memory _releaseConditions + ) + external + nonReentrant + returns (ConditionStoreLibrary.ConditionState) + { + bytes32 id = generateId( + _agreementId, + hashValues( + _did, + _types, + _amounts, + _receivers, + _tokenAddress, + _lockConditions, + _releaseConditions + ) + ); + + // Check that all lock conditions are fulfilled or if one of them is aborted + bool lockFulfilled = true; + bool lockAborted = false; + bool lockFinished = true; + + for (uint i = 0; i < _lockConditions.length; i++) { + ConditionStoreLibrary.ConditionState cur = conditionStoreManager.getConditionState(_lockConditions[i]); + + if (cur != ConditionStoreLibrary.ConditionState.Fulfilled) { + lockFulfilled = false; + } + if (cur != ConditionStoreLibrary.ConditionState.Fulfilled && cur != ConditionStoreLibrary.ConditionState.Aborted) { + lockFinished = false; + } + if (cur == ConditionStoreLibrary.ConditionState.Aborted) { + lockAborted = true; + } + } + + if (lockAborted && lockFinished) { + return cancelPayments(_agreementId, _types, _amounts, _tokenAddress, _lockConditions, id); + } + + // Check that lock conditions match this escrow + for (uint256 i = 0; i < _types[0]; i++) { + require( + matchLockPayment(_agreementId, _did, _amounts, _receivers, _tokenAddress, _lockConditions, i), + 'Lock payment does not match' + ); + } + + bool allFulfilled = true; + bool allAborted = true; + for (uint i = 0; i < _releaseConditions.length; i++) { + ConditionStoreLibrary.ConditionState cur = conditionStoreManager.getConditionState(_releaseConditions[i]); + if (cur != ConditionStoreLibrary.ConditionState.Fulfilled) { + allFulfilled = false; + } + if (cur != ConditionStoreLibrary.ConditionState.Aborted) { + allAborted = false; + } + } + + if (allFulfilled) { + for (uint256 i = 0; i < _types[0]; i++) { + makePayment(_agreementId, _amounts, _receivers, _tokenAddress, i, id); + } + + return super.fulfill( + id, + ConditionStoreLibrary.ConditionState.Fulfilled + ); + } else if (allAborted) { + return cancelPayments(_agreementId, _types, _amounts, _tokenAddress, _lockConditions, id); + } else { + return conditionStoreManager.getConditionState(id); + } + } + + /** + * @notice _transferAndFulfill transfer ERC20 tokens and + * fulfill the condition + * @param _tokenAddress the ERC20 contract address to use during the payment + * @param _receivers receiver's address + * @param _amounts token amount to be locked/released + */ + function _transferAndFulfillERC20( + address _tokenAddress, + address[] memory _receivers, + uint256[] memory _amounts + ) + private + { + + IERC20Upgradeable token = ERC20Upgradeable(_tokenAddress); + + for(uint i = 0; i < _receivers.length; i++) { + require( + _receivers[i] != address(this), + 'Escrow contract can not be a receiver' + ); + token.safeTransfer(_receivers[i], _amounts[i]); + } + + } + + /** + * @notice _transferAndFulfill transfer ETH and + * fulfill the condition + * @param _receivers receiver's address + * @param _amounts token amount to be locked/released + */ + function _transferAndFulfillETH( + address[] memory _receivers, + uint256[] memory _amounts + ) + private + { + for(uint i = 0; i < _receivers.length; i++) { + require( + _receivers[i] != address(this), + 'Escrow contract can not be a receiver' + ); + + require( + address(this).balance >= _amounts[i], + 'Contract balance too low' + ); + + // solhint-disable-next-line + (bool sent,) = _receivers[i].call{value: _amounts[i]}(''); + require(sent, 'Failed to send Ether'); + } + + } + +} From cefa62566c91a36ad43bbac1d7a76a719dbabf99 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Sami=20M=C3=A4kel=C3=A4?= Date: Thu, 27 Jan 2022 15:49:29 +0200 Subject: [PATCH 08/32] adding escrow for nfts --- .../NFTs/NFT721MarkedLockCondition.sol | 142 ++++++++ .../NFTs/NFTMarkedLockCondition.sol | 183 ++++++++++ .../rewards/MultiEscrowPaymentCondition.sol | 2 +- .../rewards/NFTEscrowPaymentCondition.sol | 315 +++++------------- 4 files changed, 415 insertions(+), 227 deletions(-) create mode 100644 contracts/conditions/NFTs/NFT721MarkedLockCondition.sol create mode 100644 contracts/conditions/NFTs/NFTMarkedLockCondition.sol diff --git a/contracts/conditions/NFTs/NFT721MarkedLockCondition.sol b/contracts/conditions/NFTs/NFT721MarkedLockCondition.sol new file mode 100644 index 00000000..3e15a269 --- /dev/null +++ b/contracts/conditions/NFTs/NFT721MarkedLockCondition.sol @@ -0,0 +1,142 @@ +pragma solidity ^0.8.0; +// Copyright 2020 Keyko GmbH. +// SPDX-License-Identifier: (Apache-2.0 AND CC-BY-4.0) +// Code is Apache-2.0 and docs are CC-BY-4.0 + + +import '../Condition.sol'; +import './INFTLock.sol'; +import '@openzeppelin/contracts-upgradeable/security/ReentrancyGuardUpgradeable.sol'; +import '@openzeppelin/contracts-upgradeable/token/ERC721/IERC721Upgradeable.sol'; +import '@openzeppelin/contracts-upgradeable/token/ERC721/IERC721ReceiverUpgradeable.sol'; + +/** + * @title NFT (ERC-721) Lock Condition + * @author Keyko + * + * @dev Implementation of the NFT Lock Condition for ERC-721 based NFTs + */ +contract NFT721LockCondition is Condition, ReentrancyGuardUpgradeable, IERC721ReceiverUpgradeable { + + bytes32 constant public CONDITION_TYPE = keccak256('NFT721LockCondition'); + + event Fulfilled( + bytes32 indexed _agreementId, + bytes32 indexed _did, + address indexed _lockAddress, + bytes32 _conditionId, + uint256 _amount, + address _receiver, + address _nftContractAddress + ); + + /** + * @notice initialize init the contract with the following parameters + * @dev this function is called only once during the contract + * initialization. + * @param _owner contract's owner account address + * @param _conditionStoreManagerAddress condition store manager address + */ + function initialize( + address _owner, + address _conditionStoreManagerAddress + ) + external + initializer() + { + require( + _conditionStoreManagerAddress != address(0), + 'Invalid address' + ); + OwnableUpgradeable.__Ownable_init(); + transferOwnership(_owner); + conditionStoreManager = ConditionStoreManager( + _conditionStoreManagerAddress + ); + } + + /** + * @notice hashValues generates the hash of condition inputs + * with the following parameters + * @param _did the DID of the asset with NFTs attached to lock + * @param _lockAddress the contract address where the NFT will be locked + * @param _amount is the amount of the locked tokens + * @param _nftContractAddress Is the address of the NFT (ERC-721) contract to use + * @return bytes32 hash of all these values + */ + function hashValues( + bytes32 _did, + address _lockAddress, + uint256 _amount, + address _receiver, + address _nftContractAddress + ) + public + pure + returns (bytes32) + { + return keccak256(abi.encode(_did, _lockAddress, _amount, _receiver, _nftContractAddress)); + } + + /** + * @notice fulfill the transfer NFT condition + * @dev Fulfill method lock a NFT into the `_lockAddress`. + * @param _agreementId agreement identifier + * @param _did refers to the DID in which secret store will issue the decryption keys + * @param _lockAddress the contract address where the NFT will be locked + * @param _amount is the amount of the locked tokens (1) + * @param _nftContractAddress Is the address of the NFT (ERC-721) contract to use + * @return condition state (Fulfilled/Aborted) + */ + function fulfill( + bytes32 _agreementId, + bytes32 _did, + address _lockAddress, + uint256 _amount, + address _receiver, + address _nftContractAddress + ) + external + nonReentrant + returns (ConditionStoreLibrary.ConditionState) + { + IERC721Upgradeable erc721 = IERC721Upgradeable(_nftContractAddress); + + require( + _amount == 0 || (_amount == 1 && erc721.ownerOf(uint256(_did)) == msg.sender), + 'Not enough balance' + ); + + if (_amount == 1) { + erc721.safeTransferFrom(msg.sender, _lockAddress, uint256(_did)); + } + + bytes32 _id = generateId( + _agreementId, + hashValues(_did, _lockAddress, _amount, _receiver, _nftContractAddress) + ); + ConditionStoreLibrary.ConditionState state = super.fulfill( + _id, + ConditionStoreLibrary.ConditionState.Fulfilled + ); + + emit Fulfilled( + _agreementId, + _did, + _lockAddress, + _id, + _amount, + _receiver, + _nftContractAddress + ); + return state; + } + + /** + * Always returns `IERC721Receiver.onERC721Received.selector`. + */ + function onERC721Received(address, address, uint256, bytes memory) public virtual override returns (bytes4) { + return this.onERC721Received.selector; + } + +} diff --git a/contracts/conditions/NFTs/NFTMarkedLockCondition.sol b/contracts/conditions/NFTs/NFTMarkedLockCondition.sol new file mode 100644 index 00000000..cfee1a9a --- /dev/null +++ b/contracts/conditions/NFTs/NFTMarkedLockCondition.sol @@ -0,0 +1,183 @@ +pragma solidity ^0.8.0; +// Copyright 2020 Keyko GmbH. +// SPDX-License-Identifier: (Apache-2.0 AND CC-BY-4.0) +// Code is Apache-2.0 and docs are CC-BY-4.0 + + +import '../Condition.sol'; +import '../../registry/DIDRegistry.sol'; +import './INFTLock.sol'; +import '@openzeppelin/contracts-upgradeable/token/ERC1155/IERC1155ReceiverUpgradeable.sol'; +import '@openzeppelin/contracts-upgradeable/security/ReentrancyGuardUpgradeable.sol'; + +/** + * @title NFT Lock Condition + * @author Keyko + * + * @dev Implementation of the NFT Lock Condition + */ +contract NFTMarkedLockCondition is Condition, ReentrancyGuardUpgradeable, IERC1155ReceiverUpgradeable { + + bytes32 constant public CONDITION_TYPE = keccak256('NFTMarkedLockCondition'); + + bytes4 constant internal ERC1155_ACCEPTED = 0xf23a6e61; // bytes4(keccak256("onERC1155Received(address,address,uint256,uint256,bytes)")) + bytes4 constant internal ERC1155_BATCH_ACCEPTED = 0xbc197c81; // bytes4(keccak256("onERC1155BatchReceived(address,address,uint256[],uint256[],bytes)")) + + event Fulfilled( + bytes32 indexed _agreementId, + bytes32 indexed _did, + address indexed _lockAddress, + bytes32 _conditionId, + uint256 _amount, + address _receiver, + address _nftContractAddress + ); + + /** + * @notice initialize init the contract with the following parameters + * @dev this function is called only once during the contract + * initialization. + * @param _owner contract's owner account address + * @param _conditionStoreManagerAddress condition store manager address + */ + function initialize( + address _owner, + address _conditionStoreManagerAddress + ) + external + initializer() + { + require( + _conditionStoreManagerAddress != address(0), + 'Invalid address' + ); + OwnableUpgradeable.__Ownable_init(); + transferOwnership(_owner); + conditionStoreManager = ConditionStoreManager( + _conditionStoreManagerAddress + ); + + } + + /** + * @notice hashValues generates the hash of condition inputs + * with the following parameters + * @param _did the DID of the asset with NFTs attached to lock + * @param _lockAddress the contract address where the NFT will be locked + * @param _amount is the amount of the locked tokens + * @param _nftContractAddress Is the address of the NFT (ERC-1155) contract to use + * @return bytes32 hash of all these values + */ + function hashValues( + bytes32 _did, + address _lockAddress, + uint256 _amount, + address _receiver, + address _nftContractAddress + ) + public + pure + returns (bytes32) + { + return keccak256( + abi.encode( + _did, + _lockAddress, + _amount, + _receiver, + _nftContractAddress + ) + ); + } + + /** + * @notice fulfill the transfer NFT condition + * @dev Fulfill method transfer a certain amount of NFTs + * to the _nftReceiver address. + * When true then fulfill the condition + * @param _agreementId agreement identifier + * @param _did refers to the DID in which secret store will issue the decryption keys + * @param _lockAddress the contract address where the NFT will be locked + * @param _amount is the amount of the locked tokens + * @param _nftContractAddress Is the address of the NFT (ERC-1155) contract to use + * @return condition state (Fulfilled/Aborted) + */ + function fulfill( + bytes32 _agreementId, + bytes32 _did, + address _lockAddress, + uint256 _amount, + address _receiver, + address _nftContractAddress + ) + public + nonReentrant + returns (ConditionStoreLibrary.ConditionState) + { + IERC1155Upgradeable(_nftContractAddress).safeTransferFrom(msg.sender, _lockAddress, uint256(_did), _amount, ''); + + bytes32 _id = generateId( + _agreementId, + hashValues(_did, _lockAddress, _amount, _receiver, _nftContractAddress) + ); + ConditionStoreLibrary.ConditionState state = super.fulfill( + _id, + ConditionStoreLibrary.ConditionState.Fulfilled + ); + + emit Fulfilled( + _agreementId, + _did, + _lockAddress, + _id, + _amount, + _receiver, + _nftContractAddress + ); + return state; + } + + // solhint-disable-next-line + function onERC1155Received( + address, + address, + uint256, + uint256, + bytes calldata + ) + external + override + pure + returns(bytes4) + { + return ERC1155_ACCEPTED; + } + + function onERC1155BatchReceived( + address, + address, + uint256[] calldata, + uint256[] calldata, + bytes calldata + ) + external + override + pure + returns(bytes4) + { + return ERC1155_BATCH_ACCEPTED; + } + + function supportsInterface( + bytes4 interfaceId + ) + external + override + pure + returns (bool) + { + return interfaceId == 0x01ffc9a7 || // ERC165 + interfaceId == 0x4e2312e0; // ERC1155_ACCEPTED ^ ERC1155_BATCH_ACCEPTED; + } + +} diff --git a/contracts/conditions/rewards/MultiEscrowPaymentCondition.sol b/contracts/conditions/rewards/MultiEscrowPaymentCondition.sol index 687e22f4..3c6f568f 100644 --- a/contracts/conditions/rewards/MultiEscrowPaymentCondition.sol +++ b/contracts/conditions/rewards/MultiEscrowPaymentCondition.sol @@ -23,7 +23,7 @@ contract MultiEscrowPaymentCondition is Reward, Common, ReentrancyGuardUpgradeab using SafeERC20Upgradeable for IERC20Upgradeable; - bytes32 constant public CONDITION_TYPE = keccak256('EscrowPayment'); + bytes32 constant public CONDITION_TYPE = keccak256('MultiEscrowPayment'); event Fulfilled( bytes32 indexed _agreementId, diff --git a/contracts/conditions/rewards/NFTEscrowPaymentCondition.sol b/contracts/conditions/rewards/NFTEscrowPaymentCondition.sol index d5d57b2f..5e1ad7ef 100644 --- a/contracts/conditions/rewards/NFTEscrowPaymentCondition.sol +++ b/contracts/conditions/rewards/NFTEscrowPaymentCondition.sol @@ -6,7 +6,7 @@ pragma solidity ^0.8.0; import './Reward.sol'; import '../../Common.sol'; import '../ConditionStoreLibrary.sol'; -import '@openzeppelin/contracts-upgradeable/token/ERC20/utils/SafeERC20Upgradeable.sol'; +import '@openzeppelin/contracts-upgradeable/token/ERC1155/IERC1155Upgradeable.sol'; import '@openzeppelin/contracts-upgradeable/security/ReentrancyGuardUpgradeable.sol'; /** @@ -21,16 +21,15 @@ import '@openzeppelin/contracts-upgradeable/security/ReentrancyGuardUpgradeable. */ contract NFTEscrowPaymentCondition is Reward, Common, ReentrancyGuardUpgradeable { - using SafeERC20Upgradeable for IERC20Upgradeable; - - bytes32 constant public CONDITION_TYPE = keccak256('EscrowPayment'); + bytes32 constant public CONDITION_TYPE = keccak256('NFTEscrowPayment'); event Fulfilled( bytes32 indexed _agreementId, address indexed _tokenAddress, - address[] _receivers, + bytes32 _did, + address _receivers, bytes32 _conditionId, - uint256[] _amounts + uint256 _amounts ); event Received( @@ -73,6 +72,7 @@ contract NFTEscrowPaymentCondition is Reward, Common, ReentrancyGuardUpgradeable * @param _did asset decentralized identifier * @param _amounts token amounts to be locked/released * @param _receivers receiver's addresses + * @param _lockPaymentAddress lock payment contract address * @param _tokenAddress the ERC20 contract address to use during the payment * @param _lockCondition lock condition identifier * @param _releaseConditions release condition identifier @@ -80,26 +80,22 @@ contract NFTEscrowPaymentCondition is Reward, Common, ReentrancyGuardUpgradeable */ function hashValues( bytes32 _did, - uint[3] memory _types, - uint256[][] memory _amounts, - address[][] memory _receivers, - address[] memory _tokenAddress, - bytes32[] memory _lockCondition, + uint256 _amounts, + address _receivers, + address _lockPaymentAddress, + address _tokenAddress, + bytes32 _lockCondition, bytes32[] memory _releaseConditions ) public pure returns (bytes32) { - require( - _amounts.length == _receivers.length, - 'Amounts and Receivers arguments have wrong length' - ); return keccak256( abi.encode( _did, - _types, _amounts, _receivers, + _lockPaymentAddress, _tokenAddress, _lockCondition, _releaseConditions @@ -111,119 +107,34 @@ contract NFTEscrowPaymentCondition is Reward, Common, ReentrancyGuardUpgradeable * @notice hashValuesLockPayment generates the hash of condition inputs * with the following parameters * @param _did the asset decentralized identifier - * @param _rewardAddress the contract address where the reward is locked - * @param _tokenAddress the ERC20 contract address to use during the lock payment. + * @param _lockAddress the contract address where the reward is locked + * @param _nftContractAddress the ERC20 contract address to use during the lock payment. * If the address is 0x0 means we won't use a ERC20 but ETH for payment - * @param _amounts token amounts to be locked/released - * @param _receivers receiver's addresses + * @param _amount token amounts to be locked/released + * @param _receiver receiver's addresses * @return bytes32 hash of all these values */ function hashValuesLockPayment( bytes32 _did, - address _rewardAddress, - address _tokenAddress, - uint256[] memory _amounts, - address[] memory _receivers + address _lockAddress, + address _nftContractAddress, + uint256 _amount, + address _receiver ) public pure returns (bytes32) { return keccak256(abi.encode( - _did, - _rewardAddress, - _tokenAddress, - _amounts, - _receivers + _did, + _lockAddress, + _amount, + _receiver, + _nftContractAddress )); } - - function matchLockPayment( - bytes32 _agreementId, - bytes32 _did, - uint256[][] memory _amounts, - address[][] memory _receivers, - address[] memory _tokenAddress, - bytes32[] memory _lockConditions, - uint256 idx - ) - internal - view - returns (bool) - { - return keccak256( - abi.encode( - _agreementId, - conditionStoreManager.getConditionTypeRef(_lockConditions[idx]), - hashValuesLockPayment(_did, address(this), _tokenAddress[idx], _amounts[idx], _receivers[idx]) - ) - ) == _lockConditions[idx]; - } - - function cancelPayments( - bytes32 _agreementId, - uint[3] memory _types, - uint256[][] memory _amounts, - address[] memory _tokenAddress, - bytes32[] memory _lockConditions, - bytes32 id - ) - internal - returns (ConditionStoreLibrary.ConditionState) - { - for (uint256 i = 0; i < _types[0]; i++) { - // TODO: check that it was fulfilled - cancelPayment(_agreementId, _amounts, _tokenAddress, _lockConditions, i, id); - } - - return super.fulfill( - id, - ConditionStoreLibrary.ConditionState.Fulfilled - ); - - } - - function cancelPayment( - bytes32 _agreementId, - uint256[][] memory _amounts, - address[] memory _tokenAddress, - bytes32[] memory _lockConditions, - uint256 idx, - bytes32 id - ) - internal { - uint256[] memory _totalAmounts = new uint256[](1); - _totalAmounts[0] = calculateTotalAmount(_amounts[idx]); - address[] memory _originalSender = new address[](1); - _originalSender[0] = conditionStoreManager.getConditionCreatedBy(_lockConditions[idx]); - - if (_tokenAddress[idx] != address(0)) { - _transferAndFulfillERC20(_tokenAddress[idx], _originalSender, _totalAmounts); - } else { - _transferAndFulfillETH(_originalSender, _totalAmounts); - } - emit Fulfilled(_agreementId, _tokenAddress[idx], _originalSender, id, _totalAmounts); - - } - - function makePayment( - bytes32 _agreementId, - uint256[][] memory _amounts, - address[][] memory _receivers, - address[] memory _tokenAddress, - uint256 idx, - bytes32 id - ) - internal { - if (_tokenAddress[idx] != address(0)) { - _transferAndFulfillERC20(_tokenAddress[idx], _receivers[idx], _amounts[idx]); - } else { - _transferAndFulfillETH(_receivers[idx], _amounts[idx]); - } - emit Fulfilled(_agreementId, _tokenAddress[idx], _receivers[idx], id, _amounts[idx]); - } - - /* + + /** * @notice fulfill escrow reward condition * @dev fulfill method checks whether the lock and * release conditions are fulfilled in order to @@ -231,80 +142,44 @@ contract NFTEscrowPaymentCondition is Reward, Common, ReentrancyGuardUpgradeable * respectively. * @param _agreementId agreement identifier * @param _did asset decentralized identifier - * @param _amounts token amounts to be locked/released - * @param _receivers receiver's address + * @param _amount token amounts to be locked/released + * @param _receiver receiver's address + * @param _lockPaymentAddress lock payment contract address * @param _tokenAddress the ERC20 contract address to use during the payment * @param _lockCondition lock condition identifier * @param _releaseConditions release condition identifier * @return condition state (Fulfilled/Aborted) */ function fulfill( - /* - uint256[] memory _nft721Amounts, - address[] memory _nft721Receivers, - address[] memory _nft721TokenAddress, - bytes32[] memory _nft721LockConditions, - uint256[] memory _nftAmounts, - address[] memory _nftReceivers, - address[] memory _nftTokenAddress, - bytes32[] memory _nftLockConditions, - */ bytes32 _agreementId, bytes32 _did, - uint[3] memory _types, - uint256[][] memory _amounts, - address[][] memory _receivers, - address[] memory _tokenAddress, - bytes32[] memory _lockConditions, + uint256 _amount, + address _receiver, + address _lockPaymentAddress, + address _tokenAddress, + bytes32 _lockCondition, bytes32[] memory _releaseConditions ) external nonReentrant returns (ConditionStoreLibrary.ConditionState) { - bytes32 id = generateId( - _agreementId, - hashValues( - _did, - _types, - _amounts, - _receivers, - _tokenAddress, - _lockConditions, - _releaseConditions + + require(keccak256( + abi.encode( + _agreementId, + conditionStoreManager.getConditionTypeRef(_lockCondition), + hashValuesLockPayment(_did, _lockPaymentAddress, _tokenAddress, _amount, _receiver) ) - ); + ) == _lockCondition, + 'LockCondition ID does not match' + ); - // Check that all lock conditions are fulfilled or if one of them is aborted - bool lockFulfilled = true; - bool lockAborted = false; - bool lockFinished = true; - - for (uint i = 0; i < _lockConditions.length; i++) { - ConditionStoreLibrary.ConditionState cur = conditionStoreManager.getConditionState(_lockConditions[i]); - - if (cur != ConditionStoreLibrary.ConditionState.Fulfilled) { - lockFulfilled = false; - } - if (cur != ConditionStoreLibrary.ConditionState.Fulfilled && cur != ConditionStoreLibrary.ConditionState.Aborted) { - lockFinished = false; - } - if (cur == ConditionStoreLibrary.ConditionState.Aborted) { - lockAborted = true; - } - } - - if (lockAborted && lockFinished) { - return cancelPayments(_agreementId, _types, _amounts, _tokenAddress, _lockConditions, id); - } - - // Check that lock conditions match this escrow - for (uint256 i = 0; i < _types[0]; i++) { - require( - matchLockPayment(_agreementId, _did, _amounts, _receivers, _tokenAddress, _lockConditions, i), - 'Lock payment does not match' - ); - } + require( + conditionStoreManager.getConditionState(_lockCondition) == + ConditionStoreLibrary.ConditionState.Fulfilled, + 'LockCondition needs to be Fulfilled' + ); bool allFulfilled = true; bool allAborted = true; @@ -318,77 +193,65 @@ contract NFTEscrowPaymentCondition is Reward, Common, ReentrancyGuardUpgradeable } } + bytes32 id = generateId( + _agreementId, + hashValues( + _did, + _amount, + _receiver, + _lockPaymentAddress, + _tokenAddress, + _lockCondition, + _releaseConditions + ) + ); + + ConditionStoreLibrary.ConditionState state; if (allFulfilled) { - for (uint256 i = 0; i < _types[0]; i++) { - makePayment(_agreementId, _amounts, _receivers, _tokenAddress, i, id); - } + state = _transferAndFulfillNFT(_did, id, _tokenAddress, _receiver, _amount); + emit Fulfilled(_agreementId, _tokenAddress, _did, _receiver, id, _amount); - return super.fulfill( - id, - ConditionStoreLibrary.ConditionState.Fulfilled - ); } else if (allAborted) { - return cancelPayments(_agreementId, _types, _amounts, _tokenAddress, _lockConditions, id); + + address _originalSender = conditionStoreManager.getConditionCreatedBy(_lockCondition); + + state = _transferAndFulfillNFT(_did, id, _tokenAddress, _originalSender, _amount); + + emit Fulfilled(_agreementId, _tokenAddress, _did, _originalSender, id, _amount); + } else { return conditionStoreManager.getConditionState(id); } + + return state; } /** * @notice _transferAndFulfill transfer ERC20 tokens and * fulfill the condition + * @param _id condition identifier * @param _tokenAddress the ERC20 contract address to use during the payment - * @param _receivers receiver's address - * @param _amounts token amount to be locked/released + * @param _receiver receiver's address + * @param _amount token amount to be locked/released + * @return condition state (Fulfilled/Aborted) */ - function _transferAndFulfillERC20( + function _transferAndFulfillNFT( + bytes32 _id, + bytes32 _did, address _tokenAddress, - address[] memory _receivers, - uint256[] memory _amounts + address _receiver, + uint256 _amount ) private + returns (ConditionStoreLibrary.ConditionState) { - IERC20Upgradeable token = ERC20Upgradeable(_tokenAddress); - - for(uint i = 0; i < _receivers.length; i++) { - require( - _receivers[i] != address(this), - 'Escrow contract can not be a receiver' - ); - token.safeTransfer(_receivers[i], _amounts[i]); - } + IERC1155Upgradeable(_tokenAddress).safeTransferFrom(address(this), _receiver, uint256(_did), _amount, ''); + return super.fulfill( + _id, + ConditionStoreLibrary.ConditionState.Fulfilled + ); } - - /** - * @notice _transferAndFulfill transfer ETH and - * fulfill the condition - * @param _receivers receiver's address - * @param _amounts token amount to be locked/released - */ - function _transferAndFulfillETH( - address[] memory _receivers, - uint256[] memory _amounts - ) - private - { - for(uint i = 0; i < _receivers.length; i++) { - require( - _receivers[i] != address(this), - 'Escrow contract can not be a receiver' - ); - - require( - address(this).balance >= _amounts[i], - 'Contract balance too low' - ); - - // solhint-disable-next-line - (bool sent,) = _receivers[i].call{value: _amounts[i]}(''); - require(sent, 'Failed to send Ether'); - } - - } } From 634a21cf5b90ea089306d7abbb201dd66536307f Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Sami=20M=C3=A4kel=C3=A4?= Date: Thu, 27 Jan 2022 16:02:33 +0200 Subject: [PATCH 09/32] compiles --- .../rewards/NFT721EscrowPaymentCondition.sol | 254 ++++++++++++++++++ .../rewards/NFTEscrowPaymentCondition.sol | 13 +- 2 files changed, 258 insertions(+), 9 deletions(-) create mode 100644 contracts/conditions/rewards/NFT721EscrowPaymentCondition.sol diff --git a/contracts/conditions/rewards/NFT721EscrowPaymentCondition.sol b/contracts/conditions/rewards/NFT721EscrowPaymentCondition.sol new file mode 100644 index 00000000..0baade85 --- /dev/null +++ b/contracts/conditions/rewards/NFT721EscrowPaymentCondition.sol @@ -0,0 +1,254 @@ +pragma solidity ^0.8.0; +// Copyright 2020 Keyko GmbH. +// SPDX-License-Identifier: (Apache-2.0 AND CC-BY-4.0) +// Code is Apache-2.0 and docs are CC-BY-4.0 + +import './Reward.sol'; +import '../../Common.sol'; +import '../ConditionStoreLibrary.sol'; +import '@openzeppelin/contracts-upgradeable/token/ERC721/IERC721Upgradeable.sol'; +import '@openzeppelin/contracts-upgradeable/security/ReentrancyGuardUpgradeable.sol'; + +/** + * @title Escrow Payment Condition + * @author Keyko + * + * @dev Implementation of the Escrow Payment Condition + * + * The Escrow payment is reward condition in which only + * can release reward if lock and release conditions + * are fulfilled. + */ +contract NFT721EscrowPaymentCondition is Reward, Common, ReentrancyGuardUpgradeable { + + bytes32 constant public CONDITION_TYPE = keccak256('NFTEscrowPayment'); + + event Fulfilled( + bytes32 indexed _agreementId, + address indexed _tokenAddress, + bytes32 _did, + address _receivers, + bytes32 _conditionId, + uint256 _amounts + ); + + event Received( + address indexed _from, + uint _value + ); + + receive() external payable { + emit Received(msg.sender, msg.value); + } + + /** + * @notice initialize init the + * contract with the following parameters + * @param _owner contract's owner account address + * @param _conditionStoreManagerAddress condition store manager address + */ + function initialize( + address _owner, + address _conditionStoreManagerAddress + ) + external + initializer() + { + require( + _conditionStoreManagerAddress != address(0), + 'Invalid address' + ); + OwnableUpgradeable.__Ownable_init(); + transferOwnership(_owner); + conditionStoreManager = ConditionStoreManager( + _conditionStoreManagerAddress + ); + } + + + /** + * @notice hashValues generates the hash of condition inputs + * with the following parameters + * @param _did asset decentralized identifier + * @param _amounts token amounts to be locked/released + * @param _receivers receiver's addresses + * @param _lockPaymentAddress lock payment contract address + * @param _tokenAddress the ERC20 contract address to use during the payment + * @param _lockCondition lock condition identifier + * @param _releaseConditions release condition identifier + * @return bytes32 hash of all these values + */ + function hashValues( + bytes32 _did, + uint256 _amounts, + address _receivers, + address _lockPaymentAddress, + address _tokenAddress, + bytes32 _lockCondition, + bytes32[] memory _releaseConditions + ) + public pure + returns (bytes32) + { + return keccak256( + abi.encode( + _did, + _amounts, + _receivers, + _lockPaymentAddress, + _tokenAddress, + _lockCondition, + _releaseConditions + ) + ); + } + + /** + * @notice hashValuesLockPayment generates the hash of condition inputs + * with the following parameters + * @param _did the asset decentralized identifier + * @param _lockAddress the contract address where the reward is locked + * @param _nftContractAddress the ERC20 contract address to use during the lock payment. + * If the address is 0x0 means we won't use a ERC20 but ETH for payment + * @param _amount token amounts to be locked/released + * @param _receiver receiver's addresses + * @return bytes32 hash of all these values + */ + function hashValuesLockPayment( + bytes32 _did, + address _lockAddress, + address _nftContractAddress, + uint256 _amount, + address _receiver + ) + public + pure + returns (bytes32) + { + return keccak256(abi.encode( + _did, + _lockAddress, + _amount, + _receiver, + _nftContractAddress + )); + } + + /** + * @notice fulfill escrow reward condition + * @dev fulfill method checks whether the lock and + * release conditions are fulfilled in order to + * release/refund the reward to receiver/sender + * respectively. + * @param _agreementId agreement identifier + * @param _did asset decentralized identifier + * @param _amount token amounts to be locked/released + * @param _receiver receiver's address + * @param _lockPaymentAddress lock payment contract address + * @param _tokenAddress the ERC20 contract address to use during the payment + * @param _lockCondition lock condition identifier + * @param _releaseConditions release condition identifier + * @return condition state (Fulfilled/Aborted) + */ + function fulfill( + bytes32 _agreementId, + bytes32 _did, + uint256 _amount, + address _receiver, + address _lockPaymentAddress, + address _tokenAddress, + bytes32 _lockCondition, + bytes32[] memory _releaseConditions + ) + external + nonReentrant + returns (ConditionStoreLibrary.ConditionState) + { + + require(keccak256( + abi.encode( + _agreementId, + conditionStoreManager.getConditionTypeRef(_lockCondition), + hashValuesLockPayment(_did, _lockPaymentAddress, _tokenAddress, _amount, _receiver) + ) + ) == _lockCondition, + 'LockCondition ID does not match' + ); + + require( + conditionStoreManager.getConditionState(_lockCondition) == + ConditionStoreLibrary.ConditionState.Fulfilled, + 'LockCondition needs to be Fulfilled' + ); + + bool allFulfilled = true; + bool allAborted = true; + for (uint i = 0; i < _releaseConditions.length; i++) { + ConditionStoreLibrary.ConditionState cur = conditionStoreManager.getConditionState(_releaseConditions[i]); + if (cur != ConditionStoreLibrary.ConditionState.Fulfilled) { + allFulfilled = false; + } + if (cur != ConditionStoreLibrary.ConditionState.Aborted) { + allAborted = false; + } + } + + bytes32 id = generateId( + _agreementId, + hashValues( + _did, + _amount, + _receiver, + _lockPaymentAddress, + _tokenAddress, + _lockCondition, + _releaseConditions + ) + ); + + if (allFulfilled) { + return _transferAndFulfillNFT(_agreementId, _did, id, _tokenAddress, _receiver, _amount); + + } else if (allAborted) { + return _transferAndFulfillNFT(_agreementId, _did, id, _tokenAddress, conditionStoreManager.getConditionCreatedBy(_lockCondition), _amount); + + + } else { + return conditionStoreManager.getConditionState(id); + } + + } + + /** + * @notice _transferAndFulfill transfer ERC20 tokens and + * fulfill the condition + * @param _id condition identifier + * @param _tokenAddress the ERC20 contract address to use during the payment + * @param _receiver receiver's address + * @param _amount token amount to be locked/released + * @return condition state (Fulfilled/Aborted) + */ + function _transferAndFulfillNFT( + bytes32 _agreementId, + bytes32 _id, + bytes32 _did, + address _tokenAddress, + address _receiver, + uint256 _amount + ) + private + returns (ConditionStoreLibrary.ConditionState) + { + + if (_amount == 1) { + IERC721Upgradeable(_tokenAddress).safeTransferFrom(address(this), _receiver, uint256(_did)); + } + emit Fulfilled(_agreementId, _tokenAddress, _did, _receiver, _id, _amount); + + return super.fulfill( + _id, + ConditionStoreLibrary.ConditionState.Fulfilled + ); + } + +} diff --git a/contracts/conditions/rewards/NFTEscrowPaymentCondition.sol b/contracts/conditions/rewards/NFTEscrowPaymentCondition.sol index 5e1ad7ef..6a982e49 100644 --- a/contracts/conditions/rewards/NFTEscrowPaymentCondition.sol +++ b/contracts/conditions/rewards/NFTEscrowPaymentCondition.sol @@ -206,24 +206,17 @@ contract NFTEscrowPaymentCondition is Reward, Common, ReentrancyGuardUpgradeable ) ); - ConditionStoreLibrary.ConditionState state; if (allFulfilled) { - state = _transferAndFulfillNFT(_did, id, _tokenAddress, _receiver, _amount); - emit Fulfilled(_agreementId, _tokenAddress, _did, _receiver, id, _amount); + return _transferAndFulfillNFT(_agreementId, _did, id, _tokenAddress, _receiver, _amount); } else if (allAborted) { - address _originalSender = conditionStoreManager.getConditionCreatedBy(_lockCondition); + return _transferAndFulfillNFT(_agreementId, _did, id, _tokenAddress, conditionStoreManager.getConditionCreatedBy(_lockCondition), _amount); - state = _transferAndFulfillNFT(_did, id, _tokenAddress, _originalSender, _amount); - - emit Fulfilled(_agreementId, _tokenAddress, _did, _originalSender, id, _amount); } else { return conditionStoreManager.getConditionState(id); } - - return state; } /** @@ -236,6 +229,7 @@ contract NFTEscrowPaymentCondition is Reward, Common, ReentrancyGuardUpgradeable * @return condition state (Fulfilled/Aborted) */ function _transferAndFulfillNFT( + bytes32 _agreementId, bytes32 _id, bytes32 _did, address _tokenAddress, @@ -247,6 +241,7 @@ contract NFTEscrowPaymentCondition is Reward, Common, ReentrancyGuardUpgradeable { IERC1155Upgradeable(_tokenAddress).safeTransferFrom(address(this), _receiver, uint256(_did), _amount, ''); + emit Fulfilled(_agreementId, _tokenAddress, _did, _receiver, _id, _amount); return super.fulfill( _id, From 02663bc00d7fd4021d3daaff14fe19a9b8ec21a4 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Sami=20M=C3=A4kel=C3=A4?= Date: Thu, 27 Jan 2022 18:05:55 +0200 Subject: [PATCH 10/32] adding new template --- .../NFTs/NFT721MarkedLockCondition.sol | 2 +- contracts/templates/NFTAccessSwapTemplate.sol | 103 ++++++++++++++++++ 2 files changed, 104 insertions(+), 1 deletion(-) create mode 100644 contracts/templates/NFTAccessSwapTemplate.sol diff --git a/contracts/conditions/NFTs/NFT721MarkedLockCondition.sol b/contracts/conditions/NFTs/NFT721MarkedLockCondition.sol index 3e15a269..204c2175 100644 --- a/contracts/conditions/NFTs/NFT721MarkedLockCondition.sol +++ b/contracts/conditions/NFTs/NFT721MarkedLockCondition.sol @@ -16,7 +16,7 @@ import '@openzeppelin/contracts-upgradeable/token/ERC721/IERC721ReceiverUpgradea * * @dev Implementation of the NFT Lock Condition for ERC-721 based NFTs */ -contract NFT721LockCondition is Condition, ReentrancyGuardUpgradeable, IERC721ReceiverUpgradeable { +contract NFT721MarkedLockCondition is Condition, ReentrancyGuardUpgradeable, IERC721ReceiverUpgradeable { bytes32 constant public CONDITION_TYPE = keccak256('NFT721LockCondition'); diff --git a/contracts/templates/NFTAccessSwapTemplate.sol b/contracts/templates/NFTAccessSwapTemplate.sol new file mode 100644 index 00000000..9c8cf0c7 --- /dev/null +++ b/contracts/templates/NFTAccessSwapTemplate.sol @@ -0,0 +1,103 @@ +pragma solidity ^0.8.0; +// Copyright 2020 Keyko GmbH. +// SPDX-License-Identifier: (Apache-2.0 AND CC-BY-4.0) +// Code is Apache-2.0 and docs are CC-BY-4.0 + + +import './BaseEscrowTemplate.sol'; +import '../conditions/NFTs/NFTMarkedLockCondition.sol'; +import '../conditions/rewards/NFTEscrowPaymentCondition.sol'; +import '../registry/DIDRegistry.sol'; +import '../conditions/AccessProofCondition.sol'; + + +/** + * @title Agreement Template + * @author Keyko + * + * @dev Implementation of NFT Sales Template + * + * The NFT Sales template supports an scenario where a NFT owner + * can sell that asset to a new Owner. + * Anyone (consumer/provider/publisher) can use this template in order + * to setup an agreement allowing a NFT owner to transfer the asset ownership + * after some payment. + * The template is a composite of 3 basic conditions: + * - Lock Payment Condition + * - Transfer NFT Condition + * - Escrow Reward Condition + * + * This scenario takes into account royalties for original creators in the secondary market. + * Once the agreement is created, the consumer after payment can request the transfer of the NFT + * from the current owner for a specific DID. + */ +contract NFTAccessSwapTemplate is BaseEscrowTemplate { + + DIDRegistry internal didRegistry; + NFTMarkedLockCondition internal lockPaymentCondition; + NFTEscrowPaymentCondition internal rewardCondition; + AccessProofCondition internal accessCondition; + + + /** + * @notice initialize init the + * contract with the following parameters. + * @dev this function is called only once during the contract + * initialization. It initializes the ownable feature, and + * set push the required condition types including + * access secret store, lock reward and escrow reward conditions. + * @param _owner contract's owner account address + * @param _agreementStoreManagerAddress agreement store manager contract address + * @param _lockPaymentConditionAddress lock reward condition contract address + * @param _transferConditionAddress transfer NFT condition contract address + * @param _escrowPaymentAddress escrow reward condition contract address + */ + function initialize( + address _owner, + address _agreementStoreManagerAddress, + address _lockPaymentConditionAddress, + address _transferConditionAddress, + address payable _escrowPaymentAddress, + address _accessCondition + ) + external + initializer() + { + require( + _owner != address(0) && + _agreementStoreManagerAddress != address(0) && + _lockPaymentConditionAddress != address(0) && + _transferConditionAddress != address(0) && + _escrowPaymentAddress != address(0) && + _accessCondition != address(0), + 'Invalid address' + ); + + OwnableUpgradeable.__Ownable_init(); + transferOwnership(_owner); + + agreementStoreManager = AgreementStoreManager( + _agreementStoreManagerAddress + ); + + didRegistry = DIDRegistry( + agreementStoreManager.getDIDRegistryAddress() + ); + + lockPaymentCondition = NFTMarkedLockCondition( + _lockPaymentConditionAddress + ); + + rewardCondition = NFTEscrowPaymentCondition( + _escrowPaymentAddress + ); + + accessCondition = AccessProofCondition( + _accessCondition + ); + + conditionTypes.push(address(lockPaymentCondition)); + conditionTypes.push(address(rewardCondition)); + conditionTypes.push(address(accessCondition)); + } +} From a5c0aeb902022357d3ae4bf37825f8da07f53c4e Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Sami=20M=C3=A4kel=C3=A4?= Date: Thu, 27 Jan 2022 18:50:03 +0200 Subject: [PATCH 11/32] test working --- .../rewards/NFT721EscrowPaymentCondition.sol | 4 +- .../rewards/NFTEscrowPaymentCondition.sol | 60 +++- contracts/templates/NFTAccessSwapTemplate.sol | 3 - test/int/nft/NFTAccessSwapAgreement.Test.js | 282 ++++++++++++++++++ 4 files changed, 337 insertions(+), 12 deletions(-) create mode 100644 test/int/nft/NFTAccessSwapAgreement.Test.js diff --git a/contracts/conditions/rewards/NFT721EscrowPaymentCondition.sol b/contracts/conditions/rewards/NFT721EscrowPaymentCondition.sol index 0baade85..dc6e21b5 100644 --- a/contracts/conditions/rewards/NFT721EscrowPaymentCondition.sol +++ b/contracts/conditions/rewards/NFT721EscrowPaymentCondition.sol @@ -207,10 +207,10 @@ contract NFT721EscrowPaymentCondition is Reward, Common, ReentrancyGuardUpgradea ); if (allFulfilled) { - return _transferAndFulfillNFT(_agreementId, _did, id, _tokenAddress, _receiver, _amount); + return _transferAndFulfillNFT(_agreementId, id, _did, _tokenAddress, _receiver, _amount); } else if (allAborted) { - return _transferAndFulfillNFT(_agreementId, _did, id, _tokenAddress, conditionStoreManager.getConditionCreatedBy(_lockCondition), _amount); + return _transferAndFulfillNFT(_agreementId, id, _did, _tokenAddress, conditionStoreManager.getConditionCreatedBy(_lockCondition), _amount); } else { diff --git a/contracts/conditions/rewards/NFTEscrowPaymentCondition.sol b/contracts/conditions/rewards/NFTEscrowPaymentCondition.sol index 6a982e49..a2ac40ef 100644 --- a/contracts/conditions/rewards/NFTEscrowPaymentCondition.sol +++ b/contracts/conditions/rewards/NFTEscrowPaymentCondition.sol @@ -8,7 +8,8 @@ import '../../Common.sol'; import '../ConditionStoreLibrary.sol'; import '@openzeppelin/contracts-upgradeable/token/ERC1155/IERC1155Upgradeable.sol'; import '@openzeppelin/contracts-upgradeable/security/ReentrancyGuardUpgradeable.sol'; - +import '@openzeppelin/contracts-upgradeable/token/ERC1155/IERC1155ReceiverUpgradeable.sol'; +import 'hardhat/console.sol'; /** * @title Escrow Payment Condition * @author Keyko @@ -19,7 +20,7 @@ import '@openzeppelin/contracts-upgradeable/security/ReentrancyGuardUpgradeable. * can release reward if lock and release conditions * are fulfilled. */ -contract NFTEscrowPaymentCondition is Reward, Common, ReentrancyGuardUpgradeable { +contract NFTEscrowPaymentCondition is Reward, Common, ReentrancyGuardUpgradeable, IERC1155ReceiverUpgradeable { bytes32 constant public CONDITION_TYPE = keccak256('NFTEscrowPayment'); @@ -207,11 +208,11 @@ contract NFTEscrowPaymentCondition is Reward, Common, ReentrancyGuardUpgradeable ); if (allFulfilled) { - return _transferAndFulfillNFT(_agreementId, _did, id, _tokenAddress, _receiver, _amount); + return _transferAndFulfillNFT(_agreementId, id, _did, _tokenAddress, _receiver, _amount); } else if (allAborted) { - return _transferAndFulfillNFT(_agreementId, _did, id, _tokenAddress, conditionStoreManager.getConditionCreatedBy(_lockCondition), _amount); + return _transferAndFulfillNFT(_agreementId, id, _did, _tokenAddress, conditionStoreManager.getConditionCreatedBy(_lockCondition), _amount); } else { @@ -239,8 +240,8 @@ contract NFTEscrowPaymentCondition is Reward, Common, ReentrancyGuardUpgradeable private returns (ConditionStoreLibrary.ConditionState) { - - IERC1155Upgradeable(_tokenAddress).safeTransferFrom(address(this), _receiver, uint256(_did), _amount, ''); + IERC1155Upgradeable nft = IERC1155Upgradeable(_tokenAddress); + nft.safeTransferFrom(address(this), _receiver, uint256(_did), _amount, ''); emit Fulfilled(_agreementId, _tokenAddress, _did, _receiver, _id, _amount); return super.fulfill( @@ -248,5 +249,50 @@ contract NFTEscrowPaymentCondition is Reward, Common, ReentrancyGuardUpgradeable ConditionStoreLibrary.ConditionState.Fulfilled ); } - + + bytes4 constant internal ERC1155_ACCEPTED = 0xf23a6e61; // bytes4(keccak256("onERC1155Received(address,address,uint256,uint256,bytes)")) + bytes4 constant internal ERC1155_BATCH_ACCEPTED = 0xbc197c81; // bytes4(keccak256("onERC1155BatchReceived(address,address,uint256[],uint256[],bytes)")) + + // solhint-disable-next-line + function onERC1155Received( + address, + address, + uint256, + uint256, + bytes calldata + ) + external + override + pure + returns(bytes4) + { + return ERC1155_ACCEPTED; + } + + function onERC1155BatchReceived( + address, + address, + uint256[] calldata, + uint256[] calldata, + bytes calldata + ) + external + override + pure + returns(bytes4) + { + return ERC1155_BATCH_ACCEPTED; + } + + function supportsInterface( + bytes4 interfaceId + ) + external + override + pure + returns (bool) + { + return interfaceId == 0x01ffc9a7 || // ERC165 + interfaceId == 0x4e2312e0; // ERC1155_ACCEPTED ^ ERC1155_BATCH_ACCEPTED; + } } diff --git a/contracts/templates/NFTAccessSwapTemplate.sol b/contracts/templates/NFTAccessSwapTemplate.sol index 9c8cf0c7..3aae7f24 100644 --- a/contracts/templates/NFTAccessSwapTemplate.sol +++ b/contracts/templates/NFTAccessSwapTemplate.sol @@ -49,14 +49,12 @@ contract NFTAccessSwapTemplate is BaseEscrowTemplate { * @param _owner contract's owner account address * @param _agreementStoreManagerAddress agreement store manager contract address * @param _lockPaymentConditionAddress lock reward condition contract address - * @param _transferConditionAddress transfer NFT condition contract address * @param _escrowPaymentAddress escrow reward condition contract address */ function initialize( address _owner, address _agreementStoreManagerAddress, address _lockPaymentConditionAddress, - address _transferConditionAddress, address payable _escrowPaymentAddress, address _accessCondition ) @@ -67,7 +65,6 @@ contract NFTAccessSwapTemplate is BaseEscrowTemplate { _owner != address(0) && _agreementStoreManagerAddress != address(0) && _lockPaymentConditionAddress != address(0) && - _transferConditionAddress != address(0) && _escrowPaymentAddress != address(0) && _accessCondition != address(0), 'Invalid address' diff --git a/test/int/nft/NFTAccessSwapAgreement.Test.js b/test/int/nft/NFTAccessSwapAgreement.Test.js new file mode 100644 index 00000000..b9637059 --- /dev/null +++ b/test/int/nft/NFTAccessSwapAgreement.Test.js @@ -0,0 +1,282 @@ +/* eslint-env mocha */ +/* eslint-disable no-console */ +/* global artifacts, contract, describe, it, expect */ + +const chai = require('chai') +const { assert } = chai +const chaiAsPromised = require('chai-as-promised') +chai.use(chaiAsPromised) + +const NFTAccessSwapTemplate = artifacts.require('NFTAccessSwapTemplate') +const NFTMarkedLockCondition = artifacts.require('NFTMarkedLockCondition') +const NFTEscrowCondition = artifacts.require('NFTEscrowPaymentCondition') + +const constants = require('../../helpers/constants.js') +const deployConditions = require('../../helpers/deployConditions.js') +const deployManagers = require('../../helpers/deployManagers.js') +const testUtils = require('../../helpers/utils') + +const poseidon = require('circomlib').poseidon +const babyJub = require('circomlib').babyJub +const mimcjs = require('circomlib').mimcsponge +const ZqField = require('ffjavascript').ZqField +const Scalar = require('ffjavascript').Scalar +const F = new ZqField(Scalar.fromString('21888242871839275222246405745257275088548364400416034343698204186575808495617')) +const snarkjs = require('snarkjs') +const { unstringifyBigInts } = require('ffjavascript').utils + +const { getBalance } = require('../../helpers/getBalance.js') + +contract('NFT Sales with Access Proof Template integration test', (accounts) => { + const didSeed = testUtils.generateId() + const checksum = testUtils.generateId() + const url = 'https://raw.githubusercontent.com/nevermined-io/assets/main/images/logo/banner_logo.png' + const royalties = 10 // 10% of royalties in the secondary market + const cappedAmount = 5 + let token, + didRegistry, + nft, + agreementStoreManager, + conditionStoreManager, + templateStoreManager, + did, + nftTemplate, + nftAgreement, + escrowCondition, + lockPaymentCondition, + accessProofCondition + const [ + owner, + deployer, + artist, + receiver + ] = accounts + const collector1 = receiver + + const numberNFTs = 1 + const amount = 1 + + async function setupTest() { + ({ + didRegistry, + nft, + agreementStoreManager, + conditionStoreManager, + templateStoreManager + } = await deployManagers( + deployer, + owner + )); + + token = nft; + + ({ + accessProofCondition + } = await deployConditions( + deployer, + owner, + agreementStoreManager, + conditionStoreManager, + didRegistry, + token + )) + escrowCondition = await NFTEscrowCondition.new({ from: deployer }) + await escrowCondition.initialize( + owner, + conditionStoreManager.address, + { from: deployer } + ) + lockPaymentCondition = await NFTMarkedLockCondition.new() + await lockPaymentCondition.initialize( + owner, + conditionStoreManager.address, + { from: deployer } + ) + nftTemplate = await NFTAccessSwapTemplate.new() + await nftTemplate.methods['initialize(address,address,address,address,address)']( + owner, + agreementStoreManager.address, + lockPaymentCondition.address, + escrowCondition.address, + accessProofCondition.address, + { from: deployer } + ) + + await templateStoreManager.proposeTemplate(nftTemplate.address) + await templateStoreManager.approveTemplate(nftTemplate.address, { from: owner }) + } + + async function prepareAgreement({ + agreementId = testUtils.generateId(), + receiver, + amount, + timeLockAccess = 0, + timeOutAccess = 0 + } = {}) { + const orig1 = 222n + const orig2 = 333n + const origHash = poseidon([orig1, orig2]) + + const buyerK = 123 + const providerK = 234 + const buyerPub = babyJub.mulPointEscalar(babyJub.Base8, F.e(buyerK)) + const providerPub = babyJub.mulPointEscalar(babyJub.Base8, F.e(providerK)) + + const k = babyJub.mulPointEscalar(buyerPub, F.e(providerK)) + + const cipher = mimcjs.hash(orig1, orig2, k[0]) + + const snarkParams = { + buyer_x: buyerPub[0], + buyer_y: buyerPub[1], + provider_x: providerPub[0], + provider_y: providerPub[1], + xL_in: orig1, + xR_in: orig2, + cipher_xL_in: cipher.xL, + cipher_xR_in: cipher.xR, + provider_k: providerK, + hash_plain: origHash + } + + // console.log(snark_params) + + const { proof } = await snarkjs.plonk.fullProve( + snarkParams, + 'circuits/keytransfer.wasm', + 'circuits/keytransfer.zkey' + ) + + const signals = [ + buyerPub[0], + buyerPub[1], + providerPub[0], + providerPub[1], + cipher.xL, + cipher.xR, + origHash + ] + + const proofSolidity = (await snarkjs.plonk.exportSolidityCallData(unstringifyBigInts(proof), signals)) + + const proofData = proofSolidity.split(',')[0] + + const conditionIdLockPayment = await lockPaymentCondition.generateId(agreementId, + await lockPaymentCondition.hashValues(did, escrowCondition.address, amount, receiver, token.address)) + + const conditionIdAccess = await accessProofCondition.generateId(agreementId, + await accessProofCondition.hashValues(origHash, buyerPub, providerPub)) + + const conditionIdEscrow = await escrowCondition.generateId(agreementId, + await escrowCondition.hashValues(did, amount, receiver, escrowCondition.address, token.address, conditionIdLockPayment, + [conditionIdAccess])) + + nftAgreement = { + did: did, + conditionIds: [ + conditionIdLockPayment, + conditionIdEscrow, + conditionIdAccess + ], + timeLocks: [0, 0, 0], + timeOuts: [0, 0, 0] + } + + const data = { + origHash, + buyerPub, + providerPub, + cipher: [cipher.xL, cipher.xR], + proof: proofData + } + return { + agreementId, + did, + data, + didSeed, + agreement: nftAgreement, + timeLockAccess, + timeOutAccess, + checksum, + url, + buyerK, + providerPub, + origHash + } + } + + describe('As an artist I want to register a new artwork', () => { + it('I want to register a new artwork and tokenize (via NFT). I want to get 10% of royalties', async () => { + await setupTest() + + did = await didRegistry.hashDID(didSeed, artist) + + await didRegistry.registerMintableDID( + didSeed, checksum, [], url, cappedAmount, royalties, constants.activities.GENERATED, '', { from: artist }) + await didRegistry.mint(did, 5, { from: artist }) + + const balance = await nft.balanceOf(artist, did) + assert.strictEqual(5, balance.toNumber()) + }) + }) + + describe('create and fulfill access agreement', function() { + this.timeout(100000) + it('should create access agreement', async () => { + const { agreementId, data, agreement } = await prepareAgreement({ receiver, amount }) + + // create agreement + await nftTemplate.createAgreement(agreementId, ...Object.values(agreement)) + + // check state of agreement and conditions + expect((await agreementStoreManager.getAgreement(agreementId)).did) + .to.equal(did) + + const conditionTypes = await nftTemplate.getConditionTypes() + let storedCondition + agreement.conditionIds.forEach(async (conditionId, i) => { + storedCondition = await conditionStoreManager.getCondition(conditionId) + expect(storedCondition.typeRef).to.equal(conditionTypes[i]) + expect(storedCondition.state.toNumber()).to.equal(constants.condition.state.unfulfilled) + }) + + // lock payment + const nftBalanceArtistBefore = await nft.balanceOf(artist, did) + const nftBalanceCollectorBefore = await nft.balanceOf(collector1, did) + + await nft.setApprovalForAll(lockPaymentCondition.address, true, { from: artist }) + await lockPaymentCondition.fulfill(agreementId, did, escrowCondition.address, amount, receiver, token.address, { from: artist }) + + const { state } = await conditionStoreManager.getCondition(nftAgreement.conditionIds[0]) + assert.strictEqual(state.toNumber(), constants.condition.state.fulfilled) + + // fulfill access + await accessProofCondition.fulfill(agreementId, ...Object.values(data), { from: collector1 }) + + assert.strictEqual( + (await conditionStoreManager.getConditionState(agreement.conditionIds[2])).toNumber(), + constants.condition.state.fulfilled) + + // escrow + await escrowCondition.fulfill( + agreementId, + did, + amount, + receiver, + escrowCondition.address, + token.address, + nftAgreement.conditionIds[0], + [nftAgreement.conditionIds[2]], + { from: collector1 }) + + const { state: state3 } = await conditionStoreManager.getCondition(nftAgreement.conditionIds[1]) + assert.strictEqual(state3.toNumber(), constants.condition.state.fulfilled) + + const nftBalanceArtistAfter = await nft.balanceOf(artist, did) + const nftBalanceCollectorAfter = await nft.balanceOf(collector1, did) + + assert.strictEqual(nftBalanceArtistAfter.toNumber(), nftBalanceArtistBefore.toNumber() - numberNFTs) + assert.strictEqual(nftBalanceCollectorAfter.toNumber(), nftBalanceCollectorBefore.toNumber() + numberNFTs) + }) + }) +}) From 2bdd45bb1eaaae5913f1865fbd9328190c27ba3b Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Sami=20M=C3=A4kel=C3=A4?= Date: Wed, 2 Feb 2022 13:13:28 +0200 Subject: [PATCH 12/32] adding erc 721 templates --- contracts/conditions/NFTs/INFTMarkedLock.sol | 66 +++++++++++++++++++ .../NFTs/NFT721MarkedLockCondition.sol | 16 ++--- .../NFTs/NFTMarkedLockCondition.sol | 16 ++--- contracts/conditions/rewards/INFTEscrow.sol | 30 +++++++++ .../rewards/NFT721EscrowPaymentCondition.sol | 12 +--- .../rewards/NFTEscrowPaymentCondition.sol | 12 +--- .../templates/NFT721AccessSwapTemplate.sol | 10 +++ .../NFT721SalesWithAccessTemplate.sol | 10 +++ contracts/templates/NFTAccessSwapTemplate.sol | 12 ++-- 9 files changed, 134 insertions(+), 50 deletions(-) create mode 100644 contracts/conditions/NFTs/INFTMarkedLock.sol create mode 100644 contracts/conditions/rewards/INFTEscrow.sol create mode 100644 contracts/templates/NFT721AccessSwapTemplate.sol create mode 100644 contracts/templates/NFT721SalesWithAccessTemplate.sol diff --git a/contracts/conditions/NFTs/INFTMarkedLock.sol b/contracts/conditions/NFTs/INFTMarkedLock.sol new file mode 100644 index 00000000..a4e7b1ef --- /dev/null +++ b/contracts/conditions/NFTs/INFTMarkedLock.sol @@ -0,0 +1,66 @@ +pragma solidity ^0.8.0; +// Copyright 2020 Keyko GmbH. +// SPDX-License-Identifier: (Apache-2.0 AND CC-BY-4.0) +// Code is Apache-2.0 and docs are CC-BY-4.0 + +import '../Condition.sol'; + + +interface INFTMarkedLock { + + event Fulfilled( + bytes32 indexed _agreementId, + bytes32 indexed _did, + address indexed _lockAddress, + bytes32 _conditionId, + uint256 _amount, + address _receiver, + address _nftContractAddress + ); + + /** + * @notice hashValues generates the hash of condition inputs + * with the following parameters + * @param _did the DID of the asset with NFTs attached to lock + * @param _lockAddress the contract address where the NFT will be locked + * @param _amount is the amount of the NFTs locked + * @param _receiver is the receiver of the NFTs locked + * @param _nftContractAddress Is the address of the NFT (ERC-721, ERC-1155) contract to use + * @return bytes32 hash of all these values + */ + function hashValues( + bytes32 _did, + address _lockAddress, + uint256 _amount, + address _receiver, + address _nftContractAddress + ) + external + pure + returns (bytes32); + + /** + * @notice fulfill the transfer NFT condition + * @dev Fulfill method transfer a certain amount of NFTs + * to the _nftReceiver address. + * When true then fulfill the condition + * @param _agreementId agreement identifier + * @param _did refers to the DID in which secret store will issue the decryption keys + * @param _lockAddress the contract address where the NFT will be locked + * @param _amount is the amount of the locked tokens + * @param _receiver is the receiver of the NFTs locked + * @param _nftContractAddress Is the address of the NFT (ERC-721) contract to use + * @return condition state (Fulfilled/Aborted) + */ + function fulfill( + bytes32 _agreementId, + bytes32 _did, + address _lockAddress, + uint256 _amount, + address _receiver, + address _nftContractAddress + ) + external + returns (ConditionStoreLibrary.ConditionState); + +} diff --git a/contracts/conditions/NFTs/NFT721MarkedLockCondition.sol b/contracts/conditions/NFTs/NFT721MarkedLockCondition.sol index 204c2175..ac780b35 100644 --- a/contracts/conditions/NFTs/NFT721MarkedLockCondition.sol +++ b/contracts/conditions/NFTs/NFT721MarkedLockCondition.sol @@ -5,7 +5,7 @@ pragma solidity ^0.8.0; import '../Condition.sol'; -import './INFTLock.sol'; +import './INFTMarkedLock.sol'; import '@openzeppelin/contracts-upgradeable/security/ReentrancyGuardUpgradeable.sol'; import '@openzeppelin/contracts-upgradeable/token/ERC721/IERC721Upgradeable.sol'; import '@openzeppelin/contracts-upgradeable/token/ERC721/IERC721ReceiverUpgradeable.sol'; @@ -16,20 +16,10 @@ import '@openzeppelin/contracts-upgradeable/token/ERC721/IERC721ReceiverUpgradea * * @dev Implementation of the NFT Lock Condition for ERC-721 based NFTs */ -contract NFT721MarkedLockCondition is Condition, ReentrancyGuardUpgradeable, IERC721ReceiverUpgradeable { +contract NFT721MarkedLockCondition is Condition, INFTMarkedLock, ReentrancyGuardUpgradeable, IERC721ReceiverUpgradeable { bytes32 constant public CONDITION_TYPE = keccak256('NFT721LockCondition'); - event Fulfilled( - bytes32 indexed _agreementId, - bytes32 indexed _did, - address indexed _lockAddress, - bytes32 _conditionId, - uint256 _amount, - address _receiver, - address _nftContractAddress - ); - /** * @notice initialize init the contract with the following parameters * @dev this function is called only once during the contract @@ -72,6 +62,7 @@ contract NFT721MarkedLockCondition is Condition, ReentrancyGuardUpgradeable, IER address _nftContractAddress ) public + override pure returns (bytes32) { @@ -97,6 +88,7 @@ contract NFT721MarkedLockCondition is Condition, ReentrancyGuardUpgradeable, IER address _nftContractAddress ) external + override nonReentrant returns (ConditionStoreLibrary.ConditionState) { diff --git a/contracts/conditions/NFTs/NFTMarkedLockCondition.sol b/contracts/conditions/NFTs/NFTMarkedLockCondition.sol index cfee1a9a..13842fdd 100644 --- a/contracts/conditions/NFTs/NFTMarkedLockCondition.sol +++ b/contracts/conditions/NFTs/NFTMarkedLockCondition.sol @@ -6,7 +6,7 @@ pragma solidity ^0.8.0; import '../Condition.sol'; import '../../registry/DIDRegistry.sol'; -import './INFTLock.sol'; +import './INFTMarkedLock.sol'; import '@openzeppelin/contracts-upgradeable/token/ERC1155/IERC1155ReceiverUpgradeable.sol'; import '@openzeppelin/contracts-upgradeable/security/ReentrancyGuardUpgradeable.sol'; @@ -16,23 +16,13 @@ import '@openzeppelin/contracts-upgradeable/security/ReentrancyGuardUpgradeable. * * @dev Implementation of the NFT Lock Condition */ -contract NFTMarkedLockCondition is Condition, ReentrancyGuardUpgradeable, IERC1155ReceiverUpgradeable { +contract NFTMarkedLockCondition is Condition, INFTMarkedLock, ReentrancyGuardUpgradeable, IERC1155ReceiverUpgradeable { bytes32 constant public CONDITION_TYPE = keccak256('NFTMarkedLockCondition'); bytes4 constant internal ERC1155_ACCEPTED = 0xf23a6e61; // bytes4(keccak256("onERC1155Received(address,address,uint256,uint256,bytes)")) bytes4 constant internal ERC1155_BATCH_ACCEPTED = 0xbc197c81; // bytes4(keccak256("onERC1155BatchReceived(address,address,uint256[],uint256[],bytes)")) - event Fulfilled( - bytes32 indexed _agreementId, - bytes32 indexed _did, - address indexed _lockAddress, - bytes32 _conditionId, - uint256 _amount, - address _receiver, - address _nftContractAddress - ); - /** * @notice initialize init the contract with the following parameters * @dev this function is called only once during the contract @@ -76,6 +66,7 @@ contract NFTMarkedLockCondition is Condition, ReentrancyGuardUpgradeable, IERC11 address _nftContractAddress ) public + override pure returns (bytes32) { @@ -111,6 +102,7 @@ contract NFTMarkedLockCondition is Condition, ReentrancyGuardUpgradeable, IERC11 address _nftContractAddress ) public + override nonReentrant returns (ConditionStoreLibrary.ConditionState) { diff --git a/contracts/conditions/rewards/INFTEscrow.sol b/contracts/conditions/rewards/INFTEscrow.sol new file mode 100644 index 00000000..67f7216a --- /dev/null +++ b/contracts/conditions/rewards/INFTEscrow.sol @@ -0,0 +1,30 @@ +pragma solidity ^0.8.0; +// Copyright 2020 Keyko GmbH. +// SPDX-License-Identifier: (Apache-2.0 AND CC-BY-4.0) +// Code is Apache-2.0 and docs are CC-BY-4.0 + +import './Reward.sol'; +import '../../Common.sol'; +import '../ConditionStoreLibrary.sol'; +import '@openzeppelin/contracts-upgradeable/token/ERC721/IERC721Upgradeable.sol'; +import '@openzeppelin/contracts-upgradeable/security/ReentrancyGuardUpgradeable.sol'; + +/** + * @title NFT Escrow Payment Interface + * @author Keyko + * + * @dev Common interface for ERC-721 and ERC-1155 + * + */ +contract INFTEscrow { + + event Fulfilled( + bytes32 indexed _agreementId, + address indexed _tokenAddress, + bytes32 _did, + address _receivers, + bytes32 _conditionId, + uint256 _amounts + ); + +} diff --git a/contracts/conditions/rewards/NFT721EscrowPaymentCondition.sol b/contracts/conditions/rewards/NFT721EscrowPaymentCondition.sol index dc6e21b5..799b39ff 100644 --- a/contracts/conditions/rewards/NFT721EscrowPaymentCondition.sol +++ b/contracts/conditions/rewards/NFT721EscrowPaymentCondition.sol @@ -4,6 +4,7 @@ pragma solidity ^0.8.0; // Code is Apache-2.0 and docs are CC-BY-4.0 import './Reward.sol'; +import './INFTEscrow.sol'; import '../../Common.sol'; import '../ConditionStoreLibrary.sol'; import '@openzeppelin/contracts-upgradeable/token/ERC721/IERC721Upgradeable.sol'; @@ -19,19 +20,10 @@ import '@openzeppelin/contracts-upgradeable/security/ReentrancyGuardUpgradeable. * can release reward if lock and release conditions * are fulfilled. */ -contract NFT721EscrowPaymentCondition is Reward, Common, ReentrancyGuardUpgradeable { +contract NFT721EscrowPaymentCondition is Reward, INFTEscrow, Common, ReentrancyGuardUpgradeable { bytes32 constant public CONDITION_TYPE = keccak256('NFTEscrowPayment'); - event Fulfilled( - bytes32 indexed _agreementId, - address indexed _tokenAddress, - bytes32 _did, - address _receivers, - bytes32 _conditionId, - uint256 _amounts - ); - event Received( address indexed _from, uint _value diff --git a/contracts/conditions/rewards/NFTEscrowPaymentCondition.sol b/contracts/conditions/rewards/NFTEscrowPaymentCondition.sol index a2ac40ef..f2e1d01f 100644 --- a/contracts/conditions/rewards/NFTEscrowPaymentCondition.sol +++ b/contracts/conditions/rewards/NFTEscrowPaymentCondition.sol @@ -5,6 +5,7 @@ pragma solidity ^0.8.0; import './Reward.sol'; import '../../Common.sol'; +import './INFTEscrow.sol'; import '../ConditionStoreLibrary.sol'; import '@openzeppelin/contracts-upgradeable/token/ERC1155/IERC1155Upgradeable.sol'; import '@openzeppelin/contracts-upgradeable/security/ReentrancyGuardUpgradeable.sol'; @@ -20,19 +21,10 @@ import 'hardhat/console.sol'; * can release reward if lock and release conditions * are fulfilled. */ -contract NFTEscrowPaymentCondition is Reward, Common, ReentrancyGuardUpgradeable, IERC1155ReceiverUpgradeable { +contract NFTEscrowPaymentCondition is Reward, INFTEscrow, Common, ReentrancyGuardUpgradeable, IERC1155ReceiverUpgradeable { bytes32 constant public CONDITION_TYPE = keccak256('NFTEscrowPayment'); - event Fulfilled( - bytes32 indexed _agreementId, - address indexed _tokenAddress, - bytes32 _did, - address _receivers, - bytes32 _conditionId, - uint256 _amounts - ); - event Received( address indexed _from, uint _value diff --git a/contracts/templates/NFT721AccessSwapTemplate.sol b/contracts/templates/NFT721AccessSwapTemplate.sol new file mode 100644 index 00000000..2334f8b2 --- /dev/null +++ b/contracts/templates/NFT721AccessSwapTemplate.sol @@ -0,0 +1,10 @@ +pragma solidity ^0.8.0; +// Copyright 2020 Keyko GmbH. +// SPDX-License-Identifier: (Apache-2.0 AND CC-BY-4.0) +// Code is Apache-2.0 and docs are CC-BY-4.0 + + +import './NFTAccessSwapTemplate.sol'; + +contract NFT721AccessSwapTemplate is NFTAccessSwapTemplate { +} diff --git a/contracts/templates/NFT721SalesWithAccessTemplate.sol b/contracts/templates/NFT721SalesWithAccessTemplate.sol new file mode 100644 index 00000000..8e8e68ef --- /dev/null +++ b/contracts/templates/NFT721SalesWithAccessTemplate.sol @@ -0,0 +1,10 @@ +pragma solidity ^0.8.0; +// Copyright 2020 Keyko GmbH. +// SPDX-License-Identifier: (Apache-2.0 AND CC-BY-4.0) +// Code is Apache-2.0 and docs are CC-BY-4.0 + + +import './NFTSalesWithAccessTemplate.sol'; + +contract NFT721SalesWithAccessTemplate is NFTSalesWithAccessTemplate { +} diff --git a/contracts/templates/NFTAccessSwapTemplate.sol b/contracts/templates/NFTAccessSwapTemplate.sol index 3aae7f24..d12aa867 100644 --- a/contracts/templates/NFTAccessSwapTemplate.sol +++ b/contracts/templates/NFTAccessSwapTemplate.sol @@ -5,8 +5,8 @@ pragma solidity ^0.8.0; import './BaseEscrowTemplate.sol'; -import '../conditions/NFTs/NFTMarkedLockCondition.sol'; -import '../conditions/rewards/NFTEscrowPaymentCondition.sol'; +import '../conditions/NFTs/INFTMarkedLock.sol'; +import '../conditions/rewards/INFTEscrow.sol'; import '../registry/DIDRegistry.sol'; import '../conditions/AccessProofCondition.sol'; @@ -34,8 +34,8 @@ import '../conditions/AccessProofCondition.sol'; contract NFTAccessSwapTemplate is BaseEscrowTemplate { DIDRegistry internal didRegistry; - NFTMarkedLockCondition internal lockPaymentCondition; - NFTEscrowPaymentCondition internal rewardCondition; + INFTMarkedLock internal lockPaymentCondition; + INFTEscrow internal rewardCondition; AccessProofCondition internal accessCondition; @@ -81,11 +81,11 @@ contract NFTAccessSwapTemplate is BaseEscrowTemplate { agreementStoreManager.getDIDRegistryAddress() ); - lockPaymentCondition = NFTMarkedLockCondition( + lockPaymentCondition = INFTMarkedLock( _lockPaymentConditionAddress ); - rewardCondition = NFTEscrowPaymentCondition( + rewardCondition = INFTEscrow( _escrowPaymentAddress ); From b196c9879cc0d254df6f49b657b62dcef9076f7f Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Sami=20M=C3=A4kel=C3=A4?= Date: Wed, 2 Feb 2022 13:16:02 +0200 Subject: [PATCH 13/32] lint --- test/int/nft/NFTAccessSwapAgreement.Test.js | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/test/int/nft/NFTAccessSwapAgreement.Test.js b/test/int/nft/NFTAccessSwapAgreement.Test.js index b9637059..f3206aac 100644 --- a/test/int/nft/NFTAccessSwapAgreement.Test.js +++ b/test/int/nft/NFTAccessSwapAgreement.Test.js @@ -25,8 +25,6 @@ const F = new ZqField(Scalar.fromString('218882428718392752222464057452572750885 const snarkjs = require('snarkjs') const { unstringifyBigInts } = require('ffjavascript').utils -const { getBalance } = require('../../helpers/getBalance.js') - contract('NFT Sales with Access Proof Template integration test', (accounts) => { const didSeed = testUtils.generateId() const checksum = testUtils.generateId() @@ -66,7 +64,7 @@ contract('NFT Sales with Access Proof Template integration test', (accounts) => } = await deployManagers( deployer, owner - )); + )) token = nft; From 6d2c227e6a0720dc40ad6fbd654b459f9640287e Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Sami=20M=C3=A4kel=C3=A4?= Date: Wed, 2 Feb 2022 13:33:38 +0200 Subject: [PATCH 14/32] simple tests for multi escrow --- .../conditions/rewards/EscrowPayment.Test.js | 1741 +++++++++-------- 1 file changed, 875 insertions(+), 866 deletions(-) diff --git a/test/unit/conditions/rewards/EscrowPayment.Test.js b/test/unit/conditions/rewards/EscrowPayment.Test.js index 89a934d0..2d6cd782 100644 --- a/test/unit/conditions/rewards/EscrowPayment.Test.js +++ b/test/unit/conditions/rewards/EscrowPayment.Test.js @@ -13,729 +13,456 @@ const ConditionStoreManager = artifacts.require('ConditionStoreManager') const NeverminedToken = artifacts.require('NeverminedToken') const LockPaymentCondition = artifacts.require('LockPaymentCondition') const EscrowPaymentCondition = artifacts.require('EscrowPaymentCondition') +const MultiEscrowPaymentCondition = artifacts.require('MultiEscrowPaymentCondition') const constants = require('../../../helpers/constants.js') const { getBalance, getETHBalance } = require('../../../helpers/getBalance.js') const testUtils = require('../../../helpers/utils.js') -contract('EscrowPaymentCondition contract', (accounts) => { - let conditionStoreManager - let token - let lockPaymentCondition - let escrowPayment - let didRegistry - - const createRole = accounts[0] - const owner = accounts[9] - const deployer = accounts[8] - const checksum = testUtils.generateId() - const url = 'https://nevermined.io/did/test-attr-example.txt' - - before(async () => { - const epochLibrary = await EpochLibrary.new() - await ConditionStoreManager.link(epochLibrary) - const didRegistryLibrary = await DIDRegistryLibrary.new() - await DIDRegistry.link(didRegistryLibrary) - }) +function escrowTest(EscrowPaymentCondition, isMulti) { + let multi = isMulti ? (a => [a]) : (a => a) + contract(isMulti ? 'EscrowPaymentCondition contract (multi)' : 'EscrowPaymentCondition contract', (accounts) => { + let conditionStoreManager + let token + let lockPaymentCondition + let escrowPayment + let didRegistry + + const createRole = accounts[0] + const owner = accounts[9] + const deployer = accounts[8] + const checksum = testUtils.generateId() + const url = 'https://nevermined.io/did/test-attr-example.txt' + + before(async () => { + if (!isMulti) { + const epochLibrary = await EpochLibrary.new() + await ConditionStoreManager.link(epochLibrary) + const didRegistryLibrary = await DIDRegistryLibrary.new() + await DIDRegistry.link(didRegistryLibrary) + } + }) - beforeEach(async () => { - await setupTest() - }) + beforeEach(async () => { + await setupTest() + }) + + async function setupTest({ + conditionId = testUtils.generateId(), + conditionType = testUtils.generateId() + } = {}) { + if (!escrowPayment) { + conditionStoreManager = await ConditionStoreManager.new() + await conditionStoreManager.initialize( + owner, + { from: owner } + ) + + await conditionStoreManager.delegateCreateRole( + createRole, + { from: owner } + ) + + didRegistry = await DIDRegistry.new() + await didRegistry.initialize(owner, constants.address.zero, constants.address.zero) + + token = await NeverminedToken.new() + await token.initialize(owner, owner) - async function setupTest({ - conditionId = testUtils.generateId(), - conditionType = testUtils.generateId() - } = {}) { - if (!escrowPayment) { - conditionStoreManager = await ConditionStoreManager.new() - await conditionStoreManager.initialize( - owner, - { from: owner } - ) - - await conditionStoreManager.delegateCreateRole( + lockPaymentCondition = await LockPaymentCondition.new() + await lockPaymentCondition.initialize( + owner, + conditionStoreManager.address, + didRegistry.address, + { from: deployer } + ) + + escrowPayment = await EscrowPaymentCondition.new() + await escrowPayment.initialize( + owner, + conditionStoreManager.address, + { from: deployer } + ) + } + + return { + escrowPayment, + lockPaymentCondition, + token, + conditionStoreManager, + conditionId, + conditionType, createRole, - { from: owner } - ) - - didRegistry = await DIDRegistry.new() - await didRegistry.initialize(owner, constants.address.zero, constants.address.zero) - - token = await NeverminedToken.new() - await token.initialize(owner, owner) - - lockPaymentCondition = await LockPaymentCondition.new() - await lockPaymentCondition.initialize( - owner, - conditionStoreManager.address, - didRegistry.address, - { from: deployer } - ) - - escrowPayment = await EscrowPaymentCondition.new() - await escrowPayment.initialize( - owner, - conditionStoreManager.address, - { from: deployer } - ) + owner + } } - return { - escrowPayment, - lockPaymentCondition, - token, - conditionStoreManager, - conditionId, - conditionType, - createRole, - owner - } - } + describe('init failure', () => { + it('needed contract addresses cannot be 0', async () => { + const conditionStoreManager = await ConditionStoreManager.new() + await conditionStoreManager.initialize( + owner, + { from: owner } + ) + + await conditionStoreManager.delegateCreateRole( + createRole, + { from: owner } + ) - describe('init failure', () => { - it('needed contract addresses cannot be 0', async () => { - const conditionStoreManager = await ConditionStoreManager.new() - await conditionStoreManager.initialize( - owner, - { from: owner } - ) + const didRegistry = await DIDRegistry.new() + await didRegistry.initialize(owner, constants.address.zero, constants.address.zero) - await conditionStoreManager.delegateCreateRole( - createRole, - { from: owner } - ) - - const didRegistry = await DIDRegistry.new() - await didRegistry.initialize(owner, constants.address.zero, constants.address.zero) - - const token = await NeverminedToken.new() - await token.initialize(owner, owner) - - const lockPaymentCondition = await LockPaymentCondition.new() - await lockPaymentCondition.initialize( - owner, - conditionStoreManager.address, - didRegistry.address, - { from: deployer } - ) - - const escrowPayment = await EscrowPaymentCondition.new() - await assert.isRejected(escrowPayment.initialize( - owner, - constants.address.zero, - { from: deployer } - ), undefined) + const token = await NeverminedToken.new() + await token.initialize(owner, owner) + + const lockPaymentCondition = await LockPaymentCondition.new() + await lockPaymentCondition.initialize( + owner, + conditionStoreManager.address, + didRegistry.address, + { from: deployer } + ) + + const escrowPayment = await EscrowPaymentCondition.new() + await assert.isRejected(escrowPayment.initialize( + owner, + constants.address.zero, + { from: deployer } + ), undefined) + }) }) - }) - describe('fulfill non existing condition', () => { - it('should not fulfill if conditions do not exist', async () => { - const agreementId = testUtils.generateId() - const did = testUtils.generateId() - const lockConditionId = accounts[2] - const releaseConditionId = accounts[3] - const sender = accounts[0] - const receivers = [accounts[1]] - const amounts = [10] - - await assert.isRejected( - escrowPayment.fulfill( + describe('fulfill non existing condition', () => { + it('should not fulfill if conditions do not exist', async () => { + const agreementId = testUtils.generateId() + const did = testUtils.generateId() + const lockConditionId = accounts[2] + const releaseConditionId = accounts[3] + const sender = accounts[0] + const receivers = [accounts[1]] + const amounts = [10] + + await assert.isRejected( + escrowPayment.fulfill( + agreementId, + did, + amounts, + receivers, + sender, + token.address, + lockConditionId, + multi(releaseConditionId)), + constants.condition.reward.escrowReward.error.lockConditionIdDoesNotMatch + ) + }) + }) + + describe('fulfill existing condition', () => { + it('ERC20: should fulfill if conditions exist for account address', async () => { + const agreementId = testUtils.generateId() + const did = testUtils.generateId() + const sender = accounts[0] + const receivers = [accounts[1]] + const amounts = [10] + const totalAmount = amounts[0] + const balanceBefore = await getBalance(token, escrowPayment.address) + + const hashValuesLock = await lockPaymentCondition.hashValues(did, escrowPayment.address, token.address, amounts, receivers) + const conditionLockId = await lockPaymentCondition.generateId(agreementId, hashValuesLock) + + await conditionStoreManager.createCondition( + conditionLockId, + lockPaymentCondition.address) + + const lockConditionId = conditionLockId + const releaseConditionId = conditionLockId + + const hashValues = await escrowPayment.hashValues( + did, + amounts, + receivers, + escrowPayment.address, + token.address, + lockConditionId, + multi(releaseConditionId)) + + const escrowConditionId = await escrowPayment.generateId(agreementId, hashValues) + + await conditionStoreManager.createCondition( + escrowConditionId, + escrowPayment.address) + + await token.mint(sender, totalAmount, { from: owner }) + await token.approve( + lockPaymentCondition.address, + totalAmount, + { from: sender }) + + await lockPaymentCondition.fulfill(agreementId, did, escrowPayment.address, token.address, amounts, receivers) + + assert.strictEqual(await getBalance(token, lockPaymentCondition.address), 0) + assert.strictEqual(await getBalance(token, escrowPayment.address), balanceBefore + totalAmount) + + const result = await escrowPayment.fulfill( agreementId, did, amounts, receivers, - sender, + escrowPayment.address, token.address, lockConditionId, - releaseConditionId), - constants.condition.reward.escrowReward.error.lockConditionIdDoesNotMatch - ) - }) - }) + multi(releaseConditionId)) - describe('fulfill existing condition', () => { - it('ERC20: should fulfill if conditions exist for account address', async () => { - const agreementId = testUtils.generateId() - const did = testUtils.generateId() - const sender = accounts[0] - const receivers = [accounts[1]] - const amounts = [10] - const totalAmount = amounts[0] - const balanceBefore = await getBalance(token, escrowPayment.address) - - const hashValuesLock = await lockPaymentCondition.hashValues(did, escrowPayment.address, token.address, amounts, receivers) - const conditionLockId = await lockPaymentCondition.generateId(agreementId, hashValuesLock) - - await conditionStoreManager.createCondition( - conditionLockId, - lockPaymentCondition.address) - - const lockConditionId = conditionLockId - const releaseConditionId = conditionLockId - - const hashValues = await escrowPayment.hashValues( - did, - amounts, - receivers, - escrowPayment.address, - token.address, - lockConditionId, - releaseConditionId) - - const escrowConditionId = await escrowPayment.generateId(agreementId, hashValues) - - await conditionStoreManager.createCondition( - escrowConditionId, - escrowPayment.address) - - await token.mint(sender, totalAmount, { from: owner }) - await token.approve( - lockPaymentCondition.address, - totalAmount, - { from: sender }) - - await lockPaymentCondition.fulfill(agreementId, did, escrowPayment.address, token.address, amounts, receivers) - - assert.strictEqual(await getBalance(token, lockPaymentCondition.address), 0) - assert.strictEqual(await getBalance(token, escrowPayment.address), balanceBefore + totalAmount) - - const result = await escrowPayment.fulfill( - agreementId, - did, - amounts, - receivers, - escrowPayment.address, - token.address, - lockConditionId, - releaseConditionId) - - assert.strictEqual( - (await conditionStoreManager.getConditionState(escrowConditionId)).toNumber(), - constants.condition.state.fulfilled - ) - - testUtils.assertEmitted(result, 1, 'Fulfilled') - const eventArgs = testUtils.getEventArgsFromTx(result, 'Fulfilled') - expect(eventArgs._agreementId).to.equal(agreementId) - expect(eventArgs._conditionId).to.equal(escrowConditionId) - expect(eventArgs._receivers[0]).to.equal(receivers[0]) - expect(eventArgs._amounts[0].toNumber()).to.equal(amounts[0]) - - assert.strictEqual(await getBalance(token, escrowPayment.address), 0) - assert.strictEqual(await getBalance(token, receivers[0]), totalAmount) - - await token.mint(sender, totalAmount, { from: owner }) - await token.approve(escrowPayment.address, totalAmount, { from: sender }) - await token.transfer(escrowPayment.address, totalAmount, { from: sender }) - - assert.strictEqual(await getBalance(token, escrowPayment.address), totalAmount) - await assert.isRejected( - escrowPayment.fulfill(agreementId, did, amounts, receivers, escrowPayment.address, token.address, lockConditionId, releaseConditionId), - constants.condition.state.error.invalidStateTransition - ) - }) + assert.strictEqual( + (await conditionStoreManager.getConditionState(escrowConditionId)).toNumber(), + constants.condition.state.fulfilled + ) - it('ETH: should fulfill if conditions exist for account address', async () => { - const sender = accounts[0] - const agreementId = testUtils.generateId() - const didSeed = testUtils.generateId() - const did = await didRegistry.hashDID(didSeed, accounts[0]) - const totalAmount = 500000000000n - const amounts = [totalAmount] - const receivers = [accounts[1]] - - // register DID - await didRegistry.registerMintableDID( - didSeed, checksum, [], url, amounts[0], 0, false, constants.activities.GENERATED, '') - - const hashValuesLock = await lockPaymentCondition.hashValues( - did, escrowPayment.address, constants.address.zero, amounts, receivers) - const conditionLockId = await lockPaymentCondition.generateId(agreementId, hashValuesLock) - - await conditionStoreManager.createCondition( - conditionLockId, - lockPaymentCondition.address) - - const lockConditionId = conditionLockId - const releaseConditionId = conditionLockId - - const hashValues = await escrowPayment.hashValues( - did, - amounts, - receivers, - escrowPayment.address, - constants.address.zero, - lockConditionId, - releaseConditionId) - - const escrowConditionId = await escrowPayment.generateId(agreementId, hashValues) - - await conditionStoreManager.createCondition( - escrowConditionId, - escrowPayment.address) - - const balanceSenderBefore = await getETHBalance(sender) - const balanceContractBefore = await getETHBalance(escrowPayment.address) - const balanceReceiverBefore = await getETHBalance(receivers[0]) - - // console.log('Balance Sender Before: ' + balanceSenderBefore) - // console.log('Balance Contract Before: ' + balanceContractBefore) - // console.log('Balance Receiver Before: ' + balanceReceiverBefore) - - assert(balanceSenderBefore >= totalAmount) - - await lockPaymentCondition.fulfill( - agreementId, did, escrowPayment.address, constants.address.zero, amounts, receivers, - { from: sender, value: Number(totalAmount), gasPrice: 0 }) - - const balanceSenderAfterLock = await getETHBalance(sender) - const balanceContractAfterLock = await getETHBalance(escrowPayment.address) - // const balanceReceiverAfterLock = await getETHBalance(receivers[0]) - - // console.log('Balance Sender Lock: ' + balanceSenderAfterLock) - // console.log('Balance Contract Lock: ' + balanceContractAfterLock) - // console.log('Balance Receiver Lock: ' + balanceReceiverAfterLock) - - assert(balanceSenderAfterLock >= balanceSenderBefore - totalAmount) - assert(balanceContractAfterLock <= balanceContractBefore + totalAmount) - - const result = await escrowPayment.fulfill( - agreementId, - did, - amounts, - receivers, - escrowPayment.address, - constants.address.zero, - lockConditionId, - releaseConditionId) - - assert.strictEqual( - (await conditionStoreManager.getConditionState(escrowConditionId)).toNumber(), - constants.condition.state.fulfilled - ) - - testUtils.assertEmitted(result, 1, 'Fulfilled') - const eventArgs = testUtils.getEventArgsFromTx(result, 'Fulfilled') - expect(eventArgs._agreementId).to.equal(agreementId) - expect(eventArgs._conditionId).to.equal(escrowConditionId) - expect(eventArgs._receivers[0]).to.equal(receivers[0]) - expect(eventArgs._amounts[0].toNumber()).to.equal(Number(amounts[0])) - - const balanceSenderAfterEscrow = await getETHBalance(sender) - const balanceContractAfterEscrow = await getETHBalance(escrowPayment.address) - const balanceReceiverAfterEscrow = await getETHBalance(receivers[0]) - - // console.log('Balance Sender Escrow: ' + balanceSenderAfterEscrow) - // console.log('Balance Contract Escrow: ' + balanceContractAfterEscrow) - // console.log('Balance Receiver Escrow: ' + balanceReceiverAfterEscrow) - - assert(balanceSenderAfterEscrow <= balanceSenderBefore - totalAmount) - assert(balanceContractAfterEscrow <= balanceContractBefore) - assert(balanceReceiverAfterEscrow >= balanceReceiverBefore + totalAmount) - await assert.isRejected( - escrowPayment.fulfill(agreementId, did, amounts, receivers, escrowPayment.address, constants.address.zero, lockConditionId, releaseConditionId), - undefined - ) - }) + testUtils.assertEmitted(result, 1, 'Fulfilled') + const eventArgs = testUtils.getEventArgsFromTx(result, 'Fulfilled') + expect(eventArgs._agreementId).to.equal(agreementId) + expect(eventArgs._conditionId).to.equal(escrowConditionId) + expect(eventArgs._receivers[0]).to.equal(receivers[0]) + expect(eventArgs._amounts[0].toNumber()).to.equal(amounts[0]) - it('ETH: fail if escrow is receiver', async () => { - const agreementId = testUtils.generateId() - const didSeed = testUtils.generateId() - const did = await didRegistry.hashDID(didSeed, accounts[0]) - const totalAmount = 500000000000n - const sender = accounts[0] - const amounts = [totalAmount] - const receivers = [escrowPayment.address] + assert.strictEqual(await getBalance(token, escrowPayment.address), 0) + assert.strictEqual(await getBalance(token, receivers[0]), totalAmount) - // register DID - await didRegistry.registerMintableDID( - didSeed, checksum, [], url, amounts[0], 0, false, constants.activities.GENERATED, '') + await token.mint(sender, totalAmount, { from: owner }) + await token.approve(escrowPayment.address, totalAmount, { from: sender }) + await token.transfer(escrowPayment.address, totalAmount, { from: sender }) - const hashValuesLock = await lockPaymentCondition.hashValues( - did, escrowPayment.address, constants.address.zero, amounts, receivers) - const conditionLockId = await lockPaymentCondition.generateId(agreementId, hashValuesLock) + assert.strictEqual(await getBalance(token, escrowPayment.address), totalAmount) + await assert.isRejected( + escrowPayment.fulfill(agreementId, did, amounts, receivers, escrowPayment.address, token.address, lockConditionId, multi(releaseConditionId)), + constants.condition.state.error.invalidStateTransition + ) + }) - await conditionStoreManager.createCondition( - conditionLockId, - lockPaymentCondition.address) + it('ETH: should fulfill if conditions exist for account address', async () => { + const sender = accounts[0] + const agreementId = testUtils.generateId() + const didSeed = testUtils.generateId() + const did = await didRegistry.hashDID(didSeed, accounts[0]) + const totalAmount = 500000000000n + const amounts = [totalAmount] + const receivers = [accounts[1]] - const lockConditionId = conditionLockId - const releaseConditionId = conditionLockId + // register DID + await didRegistry.registerMintableDID( + didSeed, checksum, [], url, amounts[0], 0, false, constants.activities.GENERATED, '') - const hashValues = await escrowPayment.hashValues( - did, - amounts, - receivers, - escrowPayment.address, - constants.address.zero, - lockConditionId, - releaseConditionId) + const hashValuesLock = await lockPaymentCondition.hashValues( + did, escrowPayment.address, constants.address.zero, amounts, receivers) + const conditionLockId = await lockPaymentCondition.generateId(agreementId, hashValuesLock) - const escrowConditionId = await escrowPayment.generateId(agreementId, hashValues) + await conditionStoreManager.createCondition( + conditionLockId, + lockPaymentCondition.address) - await conditionStoreManager.createCondition( - escrowConditionId, - escrowPayment.address) + const lockConditionId = conditionLockId + const releaseConditionId = conditionLockId - const balanceSenderBefore = await getETHBalance(sender) - const balanceContractBefore = await getETHBalance(escrowPayment.address) + const hashValues = await escrowPayment.hashValues( + did, + amounts, + receivers, + escrowPayment.address, + constants.address.zero, + lockConditionId, + multi(releaseConditionId)) - assert(balanceSenderBefore >= totalAmount) + const escrowConditionId = await escrowPayment.generateId(agreementId, hashValues) - await lockPaymentCondition.fulfill( - agreementId, did, escrowPayment.address, constants.address.zero, amounts, receivers, - { from: sender, value: String(totalAmount) }) + await conditionStoreManager.createCondition( + escrowConditionId, + escrowPayment.address) - const balanceSenderAfterLock = await getETHBalance(sender) - const balanceContractAfterLock = await getETHBalance(escrowPayment.address) + const balanceSenderBefore = await getETHBalance(sender) + const balanceContractBefore = await getETHBalance(escrowPayment.address) + const balanceReceiverBefore = await getETHBalance(receivers[0]) - assert(balanceSenderAfterLock <= balanceSenderBefore - totalAmount) - assert(balanceContractAfterLock >= balanceContractBefore + totalAmount) + // console.log('Balance Sender Before: ' + balanceSenderBefore) + // console.log('Balance Contract Before: ' + balanceContractBefore) + // console.log('Balance Receiver Before: ' + balanceReceiverBefore) - await assert.isRejected( - escrowPayment.fulfill(agreementId, did, amounts, receivers, escrowPayment.address, constants.address.zero, lockConditionId, releaseConditionId), - undefined - ) - }) + assert(balanceSenderBefore >= totalAmount) - it('receiver and amount lists need to have same length', async () => { - const agreementId = testUtils.generateId() - const did = testUtils.generateId() - const receivers = [accounts[1]] - const amounts = [10] - const amounts2 = [10, 20] - - const hashValuesLock = await lockPaymentCondition.hashValues(did, escrowPayment.address, token.address, amounts, receivers) - const conditionLockId = await lockPaymentCondition.generateId(agreementId, hashValuesLock) - - await conditionStoreManager.createCondition( - conditionLockId, - lockPaymentCondition.address) - - const lockConditionId = conditionLockId - const releaseConditionId = conditionLockId - - await assert.isRejected(escrowPayment.hashValues( - did, - amounts2, - receivers, - escrowPayment.address, - token.address, - lockConditionId, - releaseConditionId), - undefined) - }) + await lockPaymentCondition.fulfill( + agreementId, did, escrowPayment.address, constants.address.zero, amounts, receivers, + { from: sender, value: Number(totalAmount), gasPrice: 0 }) - it('lock condition not fulfilled', async () => { - const agreementId = testUtils.generateId() - const did = testUtils.generateId() - const receivers = [accounts[1]] - const amounts = [10] - - const hashValuesLock = await lockPaymentCondition.hashValues(did, escrowPayment.address, token.address, amounts, receivers) - const conditionLockId = await lockPaymentCondition.generateId(agreementId, hashValuesLock) - - await conditionStoreManager.createCondition( - conditionLockId, - lockPaymentCondition.address) - - const lockConditionId = conditionLockId - const releaseConditionId = conditionLockId - - const hashValues = await escrowPayment.hashValues( - did, - amounts, - receivers, - escrowPayment.address, - token.address, - lockConditionId, - releaseConditionId) - - const escrowConditionId = await escrowPayment.generateId(agreementId, hashValues) - - await conditionStoreManager.createCondition( - escrowConditionId, - escrowPayment.address) - - await assert.isRejected(escrowPayment.fulfill( - agreementId, - did, - amounts, - receivers, - escrowPayment.address, - token.address, - lockConditionId, - releaseConditionId), - 'LockCondition needs to be Fulfilled') - }) + const balanceSenderAfterLock = await getETHBalance(sender) + const balanceContractAfterLock = await getETHBalance(escrowPayment.address) + // const balanceReceiverAfterLock = await getETHBalance(receivers[0]) - it('release condition not fulfilled', async () => { - const agreementId = testUtils.generateId() - const did = testUtils.generateId() - const sender = accounts[0] - const receivers = [accounts[1]] - const amounts = [10] - const receivers2 = [accounts[5]] - const amounts2 = [20] - const totalAmount = amounts[0] - const balanceBefore = await getBalance(token, escrowPayment.address) - - const hashValuesLock = await lockPaymentCondition.hashValues(did, escrowPayment.address, token.address, amounts, receivers) - const lockConditionId = await lockPaymentCondition.generateId(agreementId, hashValuesLock) - - await conditionStoreManager.createCondition( - lockConditionId, - lockPaymentCondition.address) - - const hashValuesLock2 = await lockPaymentCondition.hashValues(did, escrowPayment.address, token.address, amounts2, receivers2) - const releaseConditionId = await lockPaymentCondition.generateId(agreementId, hashValuesLock2) - - await conditionStoreManager.createCondition( - releaseConditionId, - lockPaymentCondition.address) - - const hashValues = await escrowPayment.hashValues( - did, - amounts, - receivers, - escrowPayment.address, - token.address, - lockConditionId, - releaseConditionId) - - const escrowConditionId = await escrowPayment.generateId(agreementId, hashValues) - - await conditionStoreManager.createCondition( - escrowConditionId, - escrowPayment.address) - - await token.mint(sender, totalAmount, { from: owner }) - await token.approve( - lockPaymentCondition.address, - totalAmount, - { from: sender }) - - await lockPaymentCondition.fulfill(agreementId, did, escrowPayment.address, token.address, amounts, receivers) - - assert.strictEqual(await getBalance(token, lockPaymentCondition.address), 0) - assert.strictEqual(await getBalance(token, escrowPayment.address), balanceBefore + totalAmount) - - await escrowPayment.fulfill( - agreementId, - did, - amounts, - receivers, - escrowPayment.address, - token.address, - lockConditionId, - releaseConditionId) - - assert.strictEqual( - (await conditionStoreManager.getConditionState(escrowConditionId)).toNumber(), - constants.condition.state.unfulfilled - ) - - assert.strictEqual(await getBalance(token, escrowPayment.address), balanceBefore + totalAmount) - }) + // console.log('Balance Sender Lock: ' + balanceSenderAfterLock) + // console.log('Balance Contract Lock: ' + balanceContractAfterLock) + // console.log('Balance Receiver Lock: ' + balanceReceiverAfterLock) - it('ERC20: release condition aborted', async () => { - const agreementId = testUtils.generateId() - const did = testUtils.generateId() - const sender = accounts[0] - const receivers = [accounts[1]] - const amounts = [10] - const receivers2 = [accounts[5]] - const amounts2 = [20] - const totalAmount = amounts[0] - const balanceBefore = await getBalance(token, escrowPayment.address) - - const hashValuesLock = await lockPaymentCondition.hashValues(did, escrowPayment.address, token.address, amounts, receivers) - const lockConditionId = await lockPaymentCondition.generateId(agreementId, hashValuesLock) - - await conditionStoreManager.createCondition( - lockConditionId, - lockPaymentCondition.address) - - const hashValuesLock2 = await lockPaymentCondition.hashValues(did, escrowPayment.address, token.address, amounts2, receivers2) - const releaseConditionId = await lockPaymentCondition.generateId(agreementId, hashValuesLock2) - - await conditionStoreManager.createCondition( - releaseConditionId, - lockPaymentCondition.address, - 1, - 2, - sender - ) - - const hashValues = await escrowPayment.hashValues( - did, - amounts, - receivers, - escrowPayment.address, - token.address, - lockConditionId, - releaseConditionId) - - const escrowConditionId = await escrowPayment.generateId(agreementId, hashValues) - - await conditionStoreManager.createCondition( - escrowConditionId, - escrowPayment.address) - - await token.mint(sender, totalAmount, { from: owner }) - await token.approve( - lockPaymentCondition.address, - totalAmount, - { from: sender }) - - await lockPaymentCondition.fulfill(agreementId, did, escrowPayment.address, token.address, amounts, receivers) - - assert.strictEqual(await getBalance(token, lockPaymentCondition.address), 0) - assert.strictEqual(await getBalance(token, escrowPayment.address), balanceBefore + totalAmount) - - // abort release - await lockPaymentCondition.abortByTimeOut(releaseConditionId) - - await escrowPayment.fulfill( - agreementId, - did, - amounts, - receivers, - escrowPayment.address, - token.address, - lockConditionId, - releaseConditionId) - - assert.strictEqual( - (await conditionStoreManager.getConditionState(escrowConditionId)).toNumber(), - constants.condition.state.fulfilled - ) - - assert.strictEqual(await getBalance(token, sender), totalAmount) - }) + assert(balanceSenderAfterLock >= balanceSenderBefore - totalAmount) + assert(balanceContractAfterLock <= balanceContractBefore + totalAmount) - it('ETH: release condition aborted', async () => { - const agreementId = testUtils.generateId() - const did = testUtils.generateId() - const sender = accounts[0] - const receivers = [accounts[1]] - const amounts = [1000000000000] - const receivers2 = [accounts[5]] - const amounts2 = [20] - const totalAmount = amounts[0] - - const hashValuesLock = await lockPaymentCondition.hashValues(did, escrowPayment.address, constants.address.zero, amounts, receivers) - const lockConditionId = await lockPaymentCondition.generateId(agreementId, hashValuesLock) - - await conditionStoreManager.createCondition( - lockConditionId, - lockPaymentCondition.address - ) - - const hashValuesLock2 = await lockPaymentCondition.hashValues(did, escrowPayment.address, constants.address.zero, amounts2, receivers2) - const releaseConditionId = await lockPaymentCondition.generateId(agreementId, hashValuesLock2) - - await conditionStoreManager.createCondition( - releaseConditionId, - lockPaymentCondition.address, - 1, - 2, - sender - ) - - const hashValues = await escrowPayment.hashValues( - did, - amounts, - receivers, - escrowPayment.address, - constants.address.zero, - lockConditionId, - releaseConditionId) - - const escrowConditionId = await escrowPayment.generateId(agreementId, hashValues) - - await conditionStoreManager.createCondition( - escrowConditionId, - escrowPayment.address - ) - - const balanceBefore = await getETHBalance(sender) - await lockPaymentCondition.fulfill(agreementId, did, escrowPayment.address, constants.address.zero, amounts, receivers, - { from: sender, value: totalAmount, gasPrice: 0 }) - - // abort release - await lockPaymentCondition.abortByTimeOut(releaseConditionId, { from: sender, gasPrice: 0 }) - - await escrowPayment.fulfill( - agreementId, - did, - amounts, - receivers, - escrowPayment.address, - constants.address.zero, - lockConditionId, - releaseConditionId, { from: sender, gasPrice: 0 }) - - assert.strictEqual( - (await conditionStoreManager.getConditionState(escrowConditionId)).toNumber(), - constants.condition.state.fulfilled - ) - assert.strictEqual( - await getETHBalance(sender), - balanceBefore - ) - }) + const result = await escrowPayment.fulfill( + agreementId, + did, + amounts, + receivers, + escrowPayment.address, + constants.address.zero, + lockConditionId, + multi(releaseConditionId)) - it('should not fulfill in case of null addresses', async () => { - const agreementId = testUtils.generateId() - const did = testUtils.generateId() - const sender = accounts[0] - const receivers = [constants.address.zero] - const amounts = [10] - const totalAmount = amounts[0] - const balanceBefore = await getBalance(token, escrowPayment.address) - - const hashValuesLock = await lockPaymentCondition.hashValues(did, escrowPayment.address, token.address, amounts, receivers) - const conditionLockId = await lockPaymentCondition.generateId(agreementId, hashValuesLock) - - await conditionStoreManager.createCondition( - conditionLockId, - lockPaymentCondition.address) - - const lockConditionId = conditionLockId - const releaseConditionId = conditionLockId - - const hashValues = await escrowPayment.hashValues( - did, - amounts, - receivers, - sender, - token.address, - lockConditionId, - releaseConditionId) - const conditionId = await escrowPayment.generateId(agreementId, hashValues) - - await conditionStoreManager.createCondition( - testUtils.generateId(), - escrowPayment.address) - - await conditionStoreManager.createCondition( - conditionId, - escrowPayment.address) + assert.strictEqual( + (await conditionStoreManager.getConditionState(escrowConditionId)).toNumber(), + constants.condition.state.fulfilled + ) + + testUtils.assertEmitted(result, 1, 'Fulfilled') + const eventArgs = testUtils.getEventArgsFromTx(result, 'Fulfilled') + expect(eventArgs._agreementId).to.equal(agreementId) + expect(eventArgs._conditionId).to.equal(escrowConditionId) + expect(eventArgs._receivers[0]).to.equal(receivers[0]) + expect(eventArgs._amounts[0].toNumber()).to.equal(Number(amounts[0])) + + const balanceSenderAfterEscrow = await getETHBalance(sender) + const balanceContractAfterEscrow = await getETHBalance(escrowPayment.address) + const balanceReceiverAfterEscrow = await getETHBalance(receivers[0]) + + // console.log('Balance Sender Escrow: ' + balanceSenderAfterEscrow) + // console.log('Balance Contract Escrow: ' + balanceContractAfterEscrow) + // console.log('Balance Receiver Escrow: ' + balanceReceiverAfterEscrow) + + assert(balanceSenderAfterEscrow <= balanceSenderBefore - totalAmount) + assert(balanceContractAfterEscrow <= balanceContractBefore) + assert(balanceReceiverAfterEscrow >= balanceReceiverBefore + totalAmount) + await assert.isRejected( + escrowPayment.fulfill(agreementId, did, amounts, receivers, escrowPayment.address, constants.address.zero, lockConditionId, multi(releaseConditionId)), + undefined + ) + }) + + it('ETH: fail if escrow is receiver', async () => { + const agreementId = testUtils.generateId() + const didSeed = testUtils.generateId() + const did = await didRegistry.hashDID(didSeed, accounts[0]) + const totalAmount = 500000000000n + const sender = accounts[0] + const amounts = [totalAmount] + const receivers = [escrowPayment.address] + + // register DID + await didRegistry.registerMintableDID( + didSeed, checksum, [], url, amounts[0], 0, false, constants.activities.GENERATED, '') + + const hashValuesLock = await lockPaymentCondition.hashValues( + did, escrowPayment.address, constants.address.zero, amounts, receivers) + const conditionLockId = await lockPaymentCondition.generateId(agreementId, hashValuesLock) + + await conditionStoreManager.createCondition( + conditionLockId, + lockPaymentCondition.address) + + const lockConditionId = conditionLockId + const releaseConditionId = conditionLockId + + const hashValues = await escrowPayment.hashValues( + did, + amounts, + receivers, + escrowPayment.address, + constants.address.zero, + lockConditionId, + multi(releaseConditionId)) + + const escrowConditionId = await escrowPayment.generateId(agreementId, hashValues) + + await conditionStoreManager.createCondition( + escrowConditionId, + escrowPayment.address) + + const balanceSenderBefore = await getETHBalance(sender) + const balanceContractBefore = await getETHBalance(escrowPayment.address) + + assert(balanceSenderBefore >= totalAmount) + + await lockPaymentCondition.fulfill( + agreementId, did, escrowPayment.address, constants.address.zero, amounts, receivers, + { from: sender, value: String(totalAmount) }) + + const balanceSenderAfterLock = await getETHBalance(sender) + const balanceContractAfterLock = await getETHBalance(escrowPayment.address) + + assert(balanceSenderAfterLock <= balanceSenderBefore - totalAmount) + assert(balanceContractAfterLock >= balanceContractBefore + totalAmount) + + await assert.isRejected( + escrowPayment.fulfill(agreementId, did, amounts, receivers, escrowPayment.address, constants.address.zero, lockConditionId, multi(releaseConditionId)), + undefined + ) + }) + + it('receiver and amount lists need to have same length', async () => { + const agreementId = testUtils.generateId() + const did = testUtils.generateId() + const receivers = [accounts[1]] + const amounts = [10] + const amounts2 = [10, 20] + + const hashValuesLock = await lockPaymentCondition.hashValues(did, escrowPayment.address, token.address, amounts, receivers) + const conditionLockId = await lockPaymentCondition.generateId(agreementId, hashValuesLock) + + await conditionStoreManager.createCondition( + conditionLockId, + lockPaymentCondition.address) + + const lockConditionId = conditionLockId + const releaseConditionId = conditionLockId + + await assert.isRejected(escrowPayment.hashValues( + did, + amounts2, + receivers, + escrowPayment.address, + token.address, + lockConditionId, + multi(releaseConditionId)), + undefined) + }) - await token.mint(sender, totalAmount, { from: owner }) - await token.approve( - lockPaymentCondition.address, - totalAmount, - { from: sender }) + it('lock condition not fulfilled', async () => { + const agreementId = testUtils.generateId() + const did = testUtils.generateId() + const receivers = [accounts[1]] + const amounts = [10] - await lockPaymentCondition.fulfill(agreementId, did, escrowPayment.address, token.address, amounts, receivers) + const hashValuesLock = await lockPaymentCondition.hashValues(did, escrowPayment.address, token.address, amounts, receivers) + const conditionLockId = await lockPaymentCondition.generateId(agreementId, hashValuesLock) - assert.strictEqual(await getBalance(token, lockPaymentCondition.address), 0) - assert.strictEqual(await getBalance(token, escrowPayment.address), balanceBefore + totalAmount) + await conditionStoreManager.createCondition( + conditionLockId, + lockPaymentCondition.address) - await assert.isRejected( - escrowPayment.fulfill( + const lockConditionId = conditionLockId + const releaseConditionId = conditionLockId + + const hashValues = await escrowPayment.hashValues( + did, + amounts, + receivers, + escrowPayment.address, + token.address, + lockConditionId, + multi(releaseConditionId)) + + const escrowConditionId = await escrowPayment.generateId(agreementId, hashValues) + + await conditionStoreManager.createCondition( + escrowConditionId, + escrowPayment.address) + + await assert.isRejected(escrowPayment.fulfill( agreementId, did, amounts, @@ -743,61 +470,62 @@ contract('EscrowPaymentCondition contract', (accounts) => { escrowPayment.address, token.address, lockConditionId, - releaseConditionId - ), - 'ERC20: transfer to the zero address' - ) - }) - it('should not fulfill if the receiver address is Escrow contract address', async () => { - const agreementId = testUtils.generateId() - const did = testUtils.generateId() - const sender = accounts[0] - const receivers = [escrowPayment.address] - const amounts = [10] - const totalAmount = amounts[0] - const balanceBefore = await getBalance(token, escrowPayment.address) - - const hashValuesLock = await lockPaymentCondition.hashValues(did, escrowPayment.address, token.address, amounts, receivers) - const conditionLockId = await lockPaymentCondition.generateId(agreementId, hashValuesLock) - - await conditionStoreManager.createCondition( - conditionLockId, - lockPaymentCondition.address) - - const lockConditionId = conditionLockId - const releaseConditionId = conditionLockId - - const hashValues = await escrowPayment.hashValues( - did, - amounts, - receivers, - sender, - token.address, - lockConditionId, - releaseConditionId) - const conditionId = await escrowPayment.generateId(agreementId, hashValues) - - await conditionStoreManager.createCondition( - testUtils.generateId(), - escrowPayment.address) - - await conditionStoreManager.createCondition( - conditionId, - escrowPayment.address) + multi(releaseConditionId)), + 'LockCondition needs to be Fulfilled') + }) + + it('release condition not fulfilled', async () => { + const agreementId = testUtils.generateId() + const did = testUtils.generateId() + const sender = accounts[0] + const receivers = [accounts[1]] + const amounts = [10] + const receivers2 = [accounts[5]] + const amounts2 = [20] + const totalAmount = amounts[0] + const balanceBefore = await getBalance(token, escrowPayment.address) + + const hashValuesLock = await lockPaymentCondition.hashValues(did, escrowPayment.address, token.address, amounts, receivers) + const lockConditionId = await lockPaymentCondition.generateId(agreementId, hashValuesLock) - await token.mint(sender, totalAmount, { from: owner }) - await token.approve( - lockPaymentCondition.address, - totalAmount, - { from: sender }) + await conditionStoreManager.createCondition( + lockConditionId, + lockPaymentCondition.address) + + const hashValuesLock2 = await lockPaymentCondition.hashValues(did, escrowPayment.address, token.address, amounts2, receivers2) + const releaseConditionId = await lockPaymentCondition.generateId(agreementId, hashValuesLock2) + + await conditionStoreManager.createCondition( + releaseConditionId, + lockPaymentCondition.address) + + const hashValues = await escrowPayment.hashValues( + did, + amounts, + receivers, + escrowPayment.address, + token.address, + lockConditionId, + multi(releaseConditionId)) + + const escrowConditionId = await escrowPayment.generateId(agreementId, hashValues) + + await conditionStoreManager.createCondition( + escrowConditionId, + escrowPayment.address) - await lockPaymentCondition.fulfill(agreementId, did, escrowPayment.address, token.address, amounts, receivers) + await token.mint(sender, totalAmount, { from: owner }) + await token.approve( + lockPaymentCondition.address, + totalAmount, + { from: sender }) - assert.strictEqual(await getBalance(token, lockPaymentCondition.address), 0) - assert.strictEqual(await getBalance(token, escrowPayment.address), balanceBefore + totalAmount) + await lockPaymentCondition.fulfill(agreementId, did, escrowPayment.address, token.address, amounts, receivers) - await assert.isRejected( - escrowPayment.fulfill( + assert.strictEqual(await getBalance(token, lockPaymentCondition.address), 0) + assert.strictEqual(await getBalance(token, escrowPayment.address), balanceBefore + totalAmount) + + await escrowPayment.fulfill( agreementId, did, amounts, @@ -805,174 +533,455 @@ contract('EscrowPaymentCondition contract', (accounts) => { escrowPayment.address, token.address, lockConditionId, - releaseConditionId - ), - 'Escrow contract can not be a receiver' - ) - }) - }) + multi(releaseConditionId)) - describe('only fulfill conditions once', () => { - it('do not allow rewards to be fulfilled twice', async () => { - const agreementId = testUtils.generateId() - const did = testUtils.generateId() - const sender = accounts[0] - const attacker = [accounts[2]] - const receivers = [escrowPayment.address] - const amounts = [10] - const totalAmount = amounts[0] + assert.strictEqual( + (await conditionStoreManager.getConditionState(escrowConditionId)).toNumber(), + constants.condition.state.unfulfilled + ) - const hashValuesLock = await lockPaymentCondition.hashValues(did, escrowPayment.address, token.address, amounts, receivers) - const conditionLockId = await lockPaymentCondition.generateId(agreementId, hashValuesLock) + assert.strictEqual(await getBalance(token, escrowPayment.address), balanceBefore + totalAmount) + }) - await conditionStoreManager.createCondition( - conditionLockId, - lockPaymentCondition.address) + it('ERC20: release condition aborted', async () => { + const agreementId = testUtils.generateId() + const did = testUtils.generateId() + const sender = accounts[0] + const receivers = [accounts[1]] + const amounts = [10] + const receivers2 = [accounts[5]] + const amounts2 = [20] + const totalAmount = amounts[0] + const balanceBefore = await getBalance(token, escrowPayment.address) - await conditionStoreManager.createCondition( - testUtils.generateId(), - escrowPayment.address) + const hashValuesLock = await lockPaymentCondition.hashValues(did, escrowPayment.address, token.address, amounts, receivers) + const lockConditionId = await lockPaymentCondition.generateId(agreementId, hashValuesLock) - /* simulate a real environment by giving the EscrowPayment contract a bunch of tokens: */ - await token.mint(escrowPayment.address, 100, { from: owner }) + await conditionStoreManager.createCondition( + lockConditionId, + lockPaymentCondition.address) - const lockConditionId = conditionLockId - const releaseConditionId = conditionLockId + const hashValuesLock2 = await lockPaymentCondition.hashValues(did, escrowPayment.address, token.address, amounts2, receivers2) + const releaseConditionId = await lockPaymentCondition.generateId(agreementId, hashValuesLock2) - /* fulfill the lock condition */ + await conditionStoreManager.createCondition( + releaseConditionId, + lockPaymentCondition.address, + 1, + 2, + sender + ) - await token.mint(sender, totalAmount, { from: owner }) - await token.approve( - lockPaymentCondition.address, - totalAmount, - { from: sender }) + const hashValues = await escrowPayment.hashValues( + did, + amounts, + receivers, + escrowPayment.address, + token.address, + lockConditionId, + multi(releaseConditionId)) - await lockPaymentCondition.fulfill(agreementId, did, escrowPayment.address, token.address, amounts, receivers) + const escrowConditionId = await escrowPayment.generateId(agreementId, hashValues) - const escrowPaymentBalance = 110 + await conditionStoreManager.createCondition( + escrowConditionId, + escrowPayment.address) - /* attacker creates escrowPaymentBalance/amount bogus conditions to claim the locked reward: */ + await token.mint(sender, totalAmount, { from: owner }) + await token.approve( + lockPaymentCondition.address, + totalAmount, + { from: sender }) - for (let i = 0; i < escrowPaymentBalance / amounts; ++i) { - let agreementId = (3 + i).toString(16) - while (agreementId.length < 32 * 2) { - agreementId = '0' + agreementId - } - const attackerAgreementId = '0x' + agreementId - const attackerHashValues = await escrowPayment.hashValues( + await lockPaymentCondition.fulfill(agreementId, did, escrowPayment.address, token.address, amounts, receivers) + + assert.strictEqual(await getBalance(token, lockPaymentCondition.address), 0) + assert.strictEqual(await getBalance(token, escrowPayment.address), balanceBefore + totalAmount) + + // abort release + await lockPaymentCondition.abortByTimeOut(releaseConditionId) + + await escrowPayment.fulfill( + agreementId, + did, + amounts, + receivers, + escrowPayment.address, + token.address, + lockConditionId, + multi(releaseConditionId)) + + assert.strictEqual( + (await conditionStoreManager.getConditionState(escrowConditionId)).toNumber(), + constants.condition.state.fulfilled + ) + + assert.strictEqual(await getBalance(token, sender), totalAmount) + }) + + it('ETH: release condition aborted', async () => { + const agreementId = testUtils.generateId() + const did = testUtils.generateId() + const sender = accounts[0] + const receivers = [accounts[1]] + const amounts = [1000000000000] + const receivers2 = [accounts[5]] + const amounts2 = [20] + const totalAmount = amounts[0] + + const hashValuesLock = await lockPaymentCondition.hashValues(did, escrowPayment.address, constants.address.zero, amounts, receivers) + const lockConditionId = await lockPaymentCondition.generateId(agreementId, hashValuesLock) + + await conditionStoreManager.createCondition( + lockConditionId, + lockPaymentCondition.address + ) + + const hashValuesLock2 = await lockPaymentCondition.hashValues(did, escrowPayment.address, constants.address.zero, amounts2, receivers2) + const releaseConditionId = await lockPaymentCondition.generateId(agreementId, hashValuesLock2) + + await conditionStoreManager.createCondition( + releaseConditionId, + lockPaymentCondition.address, + 1, + 2, + sender + ) + + const hashValues = await escrowPayment.hashValues( + did, + amounts, + receivers, + escrowPayment.address, + constants.address.zero, + lockConditionId, + multi(releaseConditionId)) + + const escrowConditionId = await escrowPayment.generateId(agreementId, hashValues) + + await conditionStoreManager.createCondition( + escrowConditionId, + escrowPayment.address + ) + + const balanceBefore = await getETHBalance(sender) + await lockPaymentCondition.fulfill(agreementId, did, escrowPayment.address, constants.address.zero, amounts, receivers, + { from: sender, value: totalAmount, gasPrice: 0 }) + + // abort release + await lockPaymentCondition.abortByTimeOut(releaseConditionId, { from: sender, gasPrice: 0 }) + + await escrowPayment.fulfill( + agreementId, + did, + amounts, + receivers, + escrowPayment.address, + constants.address.zero, + lockConditionId, + multi(releaseConditionId), { from: sender, gasPrice: 0 }) + + assert.strictEqual( + (await conditionStoreManager.getConditionState(escrowConditionId)).toNumber(), + constants.condition.state.fulfilled + ) + assert.strictEqual( + await getETHBalance(sender), + balanceBefore + ) + }) + + it('should not fulfill in case of null addresses', async () => { + const agreementId = testUtils.generateId() + const did = testUtils.generateId() + const sender = accounts[0] + const receivers = [constants.address.zero] + const amounts = [10] + const totalAmount = amounts[0] + const balanceBefore = await getBalance(token, escrowPayment.address) + + const hashValuesLock = await lockPaymentCondition.hashValues(did, escrowPayment.address, token.address, amounts, receivers) + const conditionLockId = await lockPaymentCondition.generateId(agreementId, hashValuesLock) + + await conditionStoreManager.createCondition( + conditionLockId, + lockPaymentCondition.address) + + const lockConditionId = conditionLockId + const releaseConditionId = conditionLockId + + const hashValues = await escrowPayment.hashValues( + did, + amounts, + receivers, + sender, + token.address, + lockConditionId, + multi(releaseConditionId)) + const conditionId = await escrowPayment.generateId(agreementId, hashValues) + + await conditionStoreManager.createCondition( + testUtils.generateId(), + escrowPayment.address) + + await conditionStoreManager.createCondition( + conditionId, + escrowPayment.address) + + await token.mint(sender, totalAmount, { from: owner }) + await token.approve( + lockPaymentCondition.address, + totalAmount, + { from: sender }) + + await lockPaymentCondition.fulfill(agreementId, did, escrowPayment.address, token.address, amounts, receivers) + + assert.strictEqual(await getBalance(token, lockPaymentCondition.address), 0) + assert.strictEqual(await getBalance(token, escrowPayment.address), balanceBefore + totalAmount) + + await assert.isRejected( + escrowPayment.fulfill( + agreementId, + did, + amounts, + receivers, + escrowPayment.address, + token.address, + lockConditionId, + multi(releaseConditionId) + ), + 'ERC20: transfer to the zero address' + ) + }) + it('should not fulfill if the receiver address is Escrow contract address', async () => { + const agreementId = testUtils.generateId() + const did = testUtils.generateId() + const sender = accounts[0] + const receivers = [escrowPayment.address] + const amounts = [10] + const totalAmount = amounts[0] + const balanceBefore = await getBalance(token, escrowPayment.address) + + const hashValuesLock = await lockPaymentCondition.hashValues(did, escrowPayment.address, token.address, amounts, receivers) + const conditionLockId = await lockPaymentCondition.generateId(agreementId, hashValuesLock) + + await conditionStoreManager.createCondition( + conditionLockId, + lockPaymentCondition.address) + + const lockConditionId = conditionLockId + const releaseConditionId = conditionLockId + + const hashValues = await escrowPayment.hashValues( did, amounts, - attacker, - attacker[0], + receivers, + sender, token.address, lockConditionId, - releaseConditionId) - const attackerConditionId = await escrowPayment.generateId(attackerAgreementId, attackerHashValues) + multi(releaseConditionId)) + const conditionId = await escrowPayment.generateId(agreementId, hashValues) await conditionStoreManager.createCondition( - attackerConditionId, + testUtils.generateId(), escrowPayment.address) - /* attacker tries to claim the escrow before the legitimate users: */ + await conditionStoreManager.createCondition( + conditionId, + escrowPayment.address) + + await token.mint(sender, totalAmount, { from: owner }) + await token.approve( + lockPaymentCondition.address, + totalAmount, + { from: sender }) + + await lockPaymentCondition.fulfill(agreementId, did, escrowPayment.address, token.address, amounts, receivers) + + assert.strictEqual(await getBalance(token, lockPaymentCondition.address), 0) + assert.strictEqual(await getBalance(token, escrowPayment.address), balanceBefore + totalAmount) + await assert.isRejected( escrowPayment.fulfill( - attackerAgreementId, + agreementId, + did, + amounts, + receivers, + escrowPayment.address, + token.address, + lockConditionId, + multi(releaseConditionId) + ), + 'Escrow contract can not be a receiver' + ) + }) + }) + + describe('only fulfill conditions once', () => { + it('do not allow rewards to be fulfilled twice', async () => { + const agreementId = testUtils.generateId() + const did = testUtils.generateId() + const sender = accounts[0] + const attacker = [accounts[2]] + const receivers = [escrowPayment.address] + const amounts = [10] + const totalAmount = amounts[0] + + const hashValuesLock = await lockPaymentCondition.hashValues(did, escrowPayment.address, token.address, amounts, receivers) + const conditionLockId = await lockPaymentCondition.generateId(agreementId, hashValuesLock) + + await conditionStoreManager.createCondition( + conditionLockId, + lockPaymentCondition.address) + + await conditionStoreManager.createCondition( + testUtils.generateId(), + escrowPayment.address) + + /* simulate a real environment by giving the EscrowPayment contract a bunch of tokens: */ + await token.mint(escrowPayment.address, 100, { from: owner }) + + const lockConditionId = conditionLockId + const releaseConditionId = conditionLockId + + /* fulfill the lock condition */ + + await token.mint(sender, totalAmount, { from: owner }) + await token.approve( + lockPaymentCondition.address, + totalAmount, + { from: sender }) + + await lockPaymentCondition.fulfill(agreementId, did, escrowPayment.address, token.address, amounts, receivers) + + const escrowPaymentBalance = 110 + + /* attacker creates escrowPaymentBalance/amount bogus conditions to claim the locked reward: */ + + for (let i = 0; i < escrowPaymentBalance / amounts; ++i) { + let agreementId = (3 + i).toString(16) + while (agreementId.length < 32 * 2) { + agreementId = '0' + agreementId + } + const attackerAgreementId = '0x' + agreementId + const attackerHashValues = await escrowPayment.hashValues( did, amounts, attacker, attacker[0], token.address, lockConditionId, - releaseConditionId), - constants.condition.reward.escrowReward.error.lockConditionIdDoesNotMatch + multi(releaseConditionId)) + const attackerConditionId = await escrowPayment.generateId(attackerAgreementId, attackerHashValues) + + await conditionStoreManager.createCondition( + attackerConditionId, + escrowPayment.address) + + /* attacker tries to claim the escrow before the legitimate users: */ + await assert.isRejected( + escrowPayment.fulfill( + attackerAgreementId, + did, + amounts, + attacker, + attacker[0], + token.address, + lockConditionId, + multi(releaseConditionId)), + constants.condition.reward.escrowReward.error.lockConditionIdDoesNotMatch + ) + } + + /* make sure the EscrowPayment contract didn't get drained */ + assert.notStrictEqual( + (await token.balanceOf(escrowPayment.address)).toNumber(), + 0 ) - } + }) - /* make sure the EscrowPayment contract didn't get drained */ - assert.notStrictEqual( - (await token.balanceOf(escrowPayment.address)).toNumber(), - 0 - ) - }) + it('ERC20: should bit fulfill if was already fulfilled', async () => { + const agreementId = testUtils.generateId() + const did = testUtils.generateId() + const sender = accounts[0] + const receivers = [accounts[1]] + const amounts = [10] + const totalAmount = amounts[0] + + const balanceContractBefore = await getBalance(token, escrowPayment.address) + const balanceReceiverBefore = await getBalance(token, receivers[0]) + + const hashValuesLock = await lockPaymentCondition.hashValues(did, escrowPayment.address, token.address, amounts, receivers) + const conditionLockId = await lockPaymentCondition.generateId(agreementId, hashValuesLock) + + await conditionStoreManager.createCondition( + conditionLockId, + lockPaymentCondition.address) + + const lockConditionId = conditionLockId + const releaseConditionId = conditionLockId - it('ERC20: should bit fulfill if was already fulfilled', async () => { - const agreementId = testUtils.generateId() - const did = testUtils.generateId() - const sender = accounts[0] - const receivers = [accounts[1]] - const amounts = [10] - const totalAmount = amounts[0] - - const balanceContractBefore = await getBalance(token, escrowPayment.address) - const balanceReceiverBefore = await getBalance(token, receivers[0]) - - const hashValuesLock = await lockPaymentCondition.hashValues(did, escrowPayment.address, token.address, amounts, receivers) - const conditionLockId = await lockPaymentCondition.generateId(agreementId, hashValuesLock) - - await conditionStoreManager.createCondition( - conditionLockId, - lockPaymentCondition.address) - - const lockConditionId = conditionLockId - const releaseConditionId = conditionLockId - - const hashValues = await escrowPayment.hashValues( - did, - amounts, - receivers, - escrowPayment.address, - token.address, - lockConditionId, - releaseConditionId) - - const escrowConditionId = await escrowPayment.generateId(agreementId, hashValues) - - await conditionStoreManager.createCondition( - escrowConditionId, - escrowPayment.address) - - await token.mint(sender, totalAmount, { from: owner }) - await token.approve( - lockPaymentCondition.address, - totalAmount, - { from: sender }) - - await lockPaymentCondition.fulfill(agreementId, did, escrowPayment.address, token.address, amounts, receivers) - - assert.strictEqual(await getBalance(token, lockPaymentCondition.address), 0) - assert.strictEqual(await getBalance(token, escrowPayment.address), balanceContractBefore + totalAmount) - - await escrowPayment.fulfill( - agreementId, - did, - amounts, - receivers, - escrowPayment.address, - token.address, - lockConditionId, - releaseConditionId) - - assert.strictEqual( - (await conditionStoreManager.getConditionState(escrowConditionId)).toNumber(), - constants.condition.state.fulfilled - ) - - assert.strictEqual(await getBalance(token, escrowPayment.address), balanceContractBefore) - assert.strictEqual(await getBalance(token, receivers[0]), balanceReceiverBefore + totalAmount) - - await assert.isRejected(escrowPayment.fulfill( - agreementId, - did, - amounts, - receivers, - escrowPayment.address, - token.address, - lockConditionId, - releaseConditionId) - ) - - assert.strictEqual(await getBalance(token, escrowPayment.address), balanceContractBefore) - assert.strictEqual(await getBalance(token, receivers[0]), balanceReceiverBefore + totalAmount) + const hashValues = await escrowPayment.hashValues( + did, + amounts, + receivers, + escrowPayment.address, + token.address, + lockConditionId, + multi(releaseConditionId)) + + const escrowConditionId = await escrowPayment.generateId(agreementId, hashValues) + + await conditionStoreManager.createCondition( + escrowConditionId, + escrowPayment.address) + + await token.mint(sender, totalAmount, { from: owner }) + await token.approve( + lockPaymentCondition.address, + totalAmount, + { from: sender }) + + await lockPaymentCondition.fulfill(agreementId, did, escrowPayment.address, token.address, amounts, receivers) + + assert.strictEqual(await getBalance(token, lockPaymentCondition.address), 0) + assert.strictEqual(await getBalance(token, escrowPayment.address), balanceContractBefore + totalAmount) + + await escrowPayment.fulfill( + agreementId, + did, + amounts, + receivers, + escrowPayment.address, + token.address, + lockConditionId, + multi(releaseConditionId)) + + assert.strictEqual( + (await conditionStoreManager.getConditionState(escrowConditionId)).toNumber(), + constants.condition.state.fulfilled + ) + + assert.strictEqual(await getBalance(token, escrowPayment.address), balanceContractBefore) + assert.strictEqual(await getBalance(token, receivers[0]), balanceReceiverBefore + totalAmount) + + await assert.isRejected(escrowPayment.fulfill( + agreementId, + did, + amounts, + receivers, + escrowPayment.address, + token.address, + lockConditionId, + multi(releaseConditionId)) + ) + + assert.strictEqual(await getBalance(token, escrowPayment.address), balanceContractBefore) + assert.strictEqual(await getBalance(token, receivers[0]), balanceReceiverBefore + totalAmount) + }) }) }) -}) +} + +escrowTest(EscrowPaymentCondition, false) +escrowTest(MultiEscrowPaymentCondition, true) From 904fb00cc7bfecc5ff94a160c70053a68d976a96 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Sami=20M=C3=A4kel=C3=A4?= Date: Wed, 2 Feb 2022 13:56:15 +0200 Subject: [PATCH 15/32] fixing handling of rejected conditions --- .../rewards/MultiEscrowPaymentCondition.sol | 14 +++++++------- .../rewards/NFT721EscrowPaymentCondition.sol | 17 ++++++++--------- .../rewards/NFTEscrowPaymentCondition.sol | 19 ++++++++----------- 3 files changed, 23 insertions(+), 27 deletions(-) diff --git a/contracts/conditions/rewards/MultiEscrowPaymentCondition.sol b/contracts/conditions/rewards/MultiEscrowPaymentCondition.sol index 3c6f568f..e5d1338b 100644 --- a/contracts/conditions/rewards/MultiEscrowPaymentCondition.sol +++ b/contracts/conditions/rewards/MultiEscrowPaymentCondition.sol @@ -187,17 +187,19 @@ contract MultiEscrowPaymentCondition is Reward, Common, ReentrancyGuardUpgradeab ); bool allFulfilled = true; - bool allAborted = true; + bool someAborted = false; for (uint i = 0; i < _releaseConditions.length; i++) { ConditionStoreLibrary.ConditionState cur = conditionStoreManager.getConditionState(_releaseConditions[i]); if (cur != ConditionStoreLibrary.ConditionState.Fulfilled) { allFulfilled = false; } - if (cur != ConditionStoreLibrary.ConditionState.Aborted) { - allAborted = false; + if (cur == ConditionStoreLibrary.ConditionState.Aborted) { + someAborted = true; } } - + + require(someAborted || allFulfilled, 'Release conditions unresolved'); + bytes32 id = generateId( _agreementId, hashValues( @@ -220,7 +222,7 @@ contract MultiEscrowPaymentCondition is Reward, Common, ReentrancyGuardUpgradeab emit Fulfilled(_agreementId, _tokenAddress, _receivers, id, _amounts); - } else if (allAborted) { + } else if (someAborted) { uint256[] memory _totalAmounts = new uint256[](1); _totalAmounts[0] = calculateTotalAmount(_amounts); @@ -234,8 +236,6 @@ contract MultiEscrowPaymentCondition is Reward, Common, ReentrancyGuardUpgradeab emit Fulfilled(_agreementId, _tokenAddress, _originalSender, id, _totalAmounts); - } else { - return conditionStoreManager.getConditionState(id); } return state; diff --git a/contracts/conditions/rewards/NFT721EscrowPaymentCondition.sol b/contracts/conditions/rewards/NFT721EscrowPaymentCondition.sol index 799b39ff..1f81bf1d 100644 --- a/contracts/conditions/rewards/NFT721EscrowPaymentCondition.sol +++ b/contracts/conditions/rewards/NFT721EscrowPaymentCondition.sol @@ -174,17 +174,19 @@ contract NFT721EscrowPaymentCondition is Reward, INFTEscrow, Common, ReentrancyG ); bool allFulfilled = true; - bool allAborted = true; + bool someAborted = false; for (uint i = 0; i < _releaseConditions.length; i++) { ConditionStoreLibrary.ConditionState cur = conditionStoreManager.getConditionState(_releaseConditions[i]); if (cur != ConditionStoreLibrary.ConditionState.Fulfilled) { allFulfilled = false; } - if (cur != ConditionStoreLibrary.ConditionState.Aborted) { - allAborted = false; + if (cur == ConditionStoreLibrary.ConditionState.Aborted) { + someAborted = true; } } - + + require(someAborted || allFulfilled, 'Release conditions unresolved'); + bytes32 id = generateId( _agreementId, hashValues( @@ -201,12 +203,9 @@ contract NFT721EscrowPaymentCondition is Reward, INFTEscrow, Common, ReentrancyG if (allFulfilled) { return _transferAndFulfillNFT(_agreementId, id, _did, _tokenAddress, _receiver, _amount); - } else if (allAborted) { - return _transferAndFulfillNFT(_agreementId, id, _did, _tokenAddress, conditionStoreManager.getConditionCreatedBy(_lockCondition), _amount); - - } else { - return conditionStoreManager.getConditionState(id); + assert(someAborted == true); + return _transferAndFulfillNFT(_agreementId, id, _did, _tokenAddress, conditionStoreManager.getConditionCreatedBy(_lockCondition), _amount); } } diff --git a/contracts/conditions/rewards/NFTEscrowPaymentCondition.sol b/contracts/conditions/rewards/NFTEscrowPaymentCondition.sol index f2e1d01f..8a1da7e5 100644 --- a/contracts/conditions/rewards/NFTEscrowPaymentCondition.sol +++ b/contracts/conditions/rewards/NFTEscrowPaymentCondition.sol @@ -175,17 +175,19 @@ contract NFTEscrowPaymentCondition is Reward, INFTEscrow, Common, ReentrancyGuar ); bool allFulfilled = true; - bool allAborted = true; + bool someAborted = false; for (uint i = 0; i < _releaseConditions.length; i++) { ConditionStoreLibrary.ConditionState cur = conditionStoreManager.getConditionState(_releaseConditions[i]); if (cur != ConditionStoreLibrary.ConditionState.Fulfilled) { allFulfilled = false; } - if (cur != ConditionStoreLibrary.ConditionState.Aborted) { - allAborted = false; + if (cur == ConditionStoreLibrary.ConditionState.Aborted) { + someAborted = false; } } - + + require(someAborted || allFulfilled, 'Release conditions unresolved'); + bytes32 id = generateId( _agreementId, hashValues( @@ -201,14 +203,9 @@ contract NFTEscrowPaymentCondition is Reward, INFTEscrow, Common, ReentrancyGuar if (allFulfilled) { return _transferAndFulfillNFT(_agreementId, id, _did, _tokenAddress, _receiver, _amount); - - } else if (allAborted) { - - return _transferAndFulfillNFT(_agreementId, id, _did, _tokenAddress, conditionStoreManager.getConditionCreatedBy(_lockCondition), _amount); - - } else { - return conditionStoreManager.getConditionState(id); + assert(someAborted == true); + return _transferAndFulfillNFT(_agreementId, id, _did, _tokenAddress, conditionStoreManager.getConditionCreatedBy(_lockCondition), _amount); } } From b5a50e3e8f9c3dabc8ea60c4293f81f658315e9f Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Sami=20M=C3=A4kel=C3=A4?= Date: Wed, 2 Feb 2022 13:58:25 +0200 Subject: [PATCH 16/32] fixing handling of rejected conditions --- .../rewards/MultiEscrowPayment.Test.js | 303 ++++++++++++++++++ 1 file changed, 303 insertions(+) create mode 100644 test/unit/conditions/rewards/MultiEscrowPayment.Test.js diff --git a/test/unit/conditions/rewards/MultiEscrowPayment.Test.js b/test/unit/conditions/rewards/MultiEscrowPayment.Test.js new file mode 100644 index 00000000..a122ecf1 --- /dev/null +++ b/test/unit/conditions/rewards/MultiEscrowPayment.Test.js @@ -0,0 +1,303 @@ +/* eslint-env mocha */ +/* eslint-disable no-console */ +/* global artifacts, contract, describe, it, expect */ +const chai = require('chai') +const { assert } = chai +const chaiAsPromised = require('chai-as-promised') +chai.use(chaiAsPromised) + +const EpochLibrary = artifacts.require('EpochLibrary') +const DIDRegistryLibrary = artifacts.require('DIDRegistryLibrary') +const DIDRegistry = artifacts.require('DIDRegistry') +const ConditionStoreManager = artifacts.require('ConditionStoreManager') +const NeverminedToken = artifacts.require('NeverminedToken') +const LockPaymentCondition = artifacts.require('LockPaymentCondition') +const EscrowPaymentCondition = artifacts.require('MultiEscrowPaymentCondition') + +const constants = require('../../../helpers/constants.js') +const { getBalance, getETHBalance } = require('../../../helpers/getBalance.js') +const testUtils = require('../../../helpers/utils.js') + +contract('MultiEscrowPaymentCondition contract', (accounts) => { + let conditionStoreManager + let token + let lockPaymentCondition + let escrowPayment + let didRegistry + + const createRole = accounts[0] + const owner = accounts[9] + const deployer = accounts[8] + const checksum = testUtils.generateId() + const url = 'https://nevermined.io/did/test-attr-example.txt' + + before(async () => { + const epochLibrary = await EpochLibrary.new() + await ConditionStoreManager.link(epochLibrary) + const didRegistryLibrary = await DIDRegistryLibrary.new() + await DIDRegistry.link(didRegistryLibrary) + }) + + beforeEach(async () => { + await setupTest() + }) + + async function setupTest({ + conditionId = testUtils.generateId(), + conditionType = testUtils.generateId() + } = {}) { + if (!escrowPayment) { + conditionStoreManager = await ConditionStoreManager.new() + await conditionStoreManager.initialize( + owner, + { from: owner } + ) + + await conditionStoreManager.delegateCreateRole( + createRole, + { from: owner } + ) + + didRegistry = await DIDRegistry.new() + await didRegistry.initialize(owner, constants.address.zero, constants.address.zero) + + token = await NeverminedToken.new() + await token.initialize(owner, owner) + + lockPaymentCondition = await LockPaymentCondition.new() + await lockPaymentCondition.initialize( + owner, + conditionStoreManager.address, + didRegistry.address, + { from: deployer } + ) + + escrowPayment = await EscrowPaymentCondition.new() + await escrowPayment.initialize( + owner, + conditionStoreManager.address, + { from: deployer } + ) + } + + return { + escrowPayment, + lockPaymentCondition, + token, + conditionStoreManager, + conditionId, + conditionType, + createRole, + owner + } + } + + describe('fulfill with two release conditions', () => { + it('should fulfill if both are fulfilled', async () => { + const agreementId = testUtils.generateId() + const did = testUtils.generateId() + const sender = accounts[0] + const receivers = [accounts[1]] + const amounts = [10] + const receivers2 = [accounts[2]] + const amounts2 = [12] + const totalAmount = amounts[0] + amounts2[0] + const balanceBefore = await getBalance(token, escrowPayment.address) + + const hashValuesLock = await lockPaymentCondition.hashValues(did, escrowPayment.address, token.address, amounts, receivers) + const conditionLockId = await lockPaymentCondition.generateId(agreementId, hashValuesLock) + const hashValuesLock2 = await lockPaymentCondition.hashValues(did, escrowPayment.address, token.address, amounts2, receivers2) + const conditionLockId2 = await lockPaymentCondition.generateId(agreementId, hashValuesLock2) + + await conditionStoreManager.createCondition( + conditionLockId, + lockPaymentCondition.address) + + await conditionStoreManager.createCondition( + conditionLockId2, + lockPaymentCondition.address) + + const lockConditionId = conditionLockId + + const hashValues = await escrowPayment.hashValues( + did, + amounts, + receivers, + escrowPayment.address, + token.address, + lockConditionId, + [conditionLockId, conditionLockId2]) + + const escrowConditionId = await escrowPayment.generateId(agreementId, hashValues) + + await conditionStoreManager.createCondition( + escrowConditionId, + escrowPayment.address) + + await token.mint(sender, totalAmount, { from: owner }) + await token.approve( + lockPaymentCondition.address, + totalAmount, + { from: sender }) + + await assert.isRejected(escrowPayment.fulfill( + agreementId, + did, + amounts, + receivers, + escrowPayment.address, + token.address, + lockConditionId, + [conditionLockId, conditionLockId2]) + ) + + await lockPaymentCondition.fulfill(agreementId, did, escrowPayment.address, token.address, amounts, receivers) + + await assert.isRejected(escrowPayment.fulfill( + agreementId, + did, + amounts, + receivers, + escrowPayment.address, + token.address, + lockConditionId, + [conditionLockId, conditionLockId2]) + ) + + await lockPaymentCondition.fulfill(agreementId, did, escrowPayment.address, token.address, amounts2, receivers2) + + assert.strictEqual(await getBalance(token, lockPaymentCondition.address), 0) + assert.strictEqual(await getBalance(token, escrowPayment.address), balanceBefore + totalAmount) + + const result = await escrowPayment.fulfill( + agreementId, + did, + amounts, + receivers, + escrowPayment.address, + token.address, + lockConditionId, + [conditionLockId, conditionLockId2]) + + assert.strictEqual( + (await conditionStoreManager.getConditionState(escrowConditionId)).toNumber(), + constants.condition.state.fulfilled + ) + + testUtils.assertEmitted(result, 1, 'Fulfilled') + const eventArgs = testUtils.getEventArgsFromTx(result, 'Fulfilled') + expect(eventArgs._agreementId).to.equal(agreementId) + expect(eventArgs._conditionId).to.equal(escrowConditionId) + expect(eventArgs._receivers[0]).to.equal(receivers[0]) + expect(eventArgs._amounts[0].toNumber()).to.equal(amounts[0]) + + assert.strictEqual(await getBalance(token, escrowPayment.address), amounts2[0]) + assert.strictEqual(await getBalance(token, receivers[0]), amounts[0]) + + }) + + it('should cancel if conditions were aborted', async () => { + const agreementId = testUtils.generateId() + const did = testUtils.generateId() + const sender = accounts[0] + const receivers = [accounts[1]] + const amounts = [10] + const receivers2 = [accounts[2]] + const amounts2 = [12] + const totalAmount = amounts[0] + amounts2[0] + const balanceBefore = await getBalance(token, escrowPayment.address) + + const hashValuesLock = await lockPaymentCondition.hashValues(did, escrowPayment.address, token.address, amounts, receivers) + const conditionLockId = await lockPaymentCondition.generateId(agreementId, hashValuesLock) + const hashValuesLock2 = await lockPaymentCondition.hashValues(did, escrowPayment.address, token.address, amounts2, receivers2) + const conditionLockId2 = await lockPaymentCondition.generateId(agreementId, hashValuesLock2) + + await conditionStoreManager.createCondition( + conditionLockId, + lockPaymentCondition.address) + + await conditionStoreManager.createCondition( + conditionLockId2, + lockPaymentCondition.address) + + const lockConditionId = conditionLockId + + const hashValues = await escrowPayment.hashValues( + did, + amounts, + receivers, + escrowPayment.address, + token.address, + lockConditionId, + [conditionLockId, conditionLockId2]) + + const escrowConditionId = await escrowPayment.generateId(agreementId, hashValues) + + await conditionStoreManager.createCondition( + escrowConditionId, + escrowPayment.address) + + await token.mint(sender, totalAmount, { from: owner }) + await token.approve( + lockPaymentCondition.address, + totalAmount, + { from: sender }) + + await assert.isRejected(escrowPayment.fulfill( + agreementId, + did, + amounts, + receivers, + escrowPayment.address, + token.address, + lockConditionId, + [conditionLockId, conditionLockId2]) + ) + + await lockPaymentCondition.fulfill(agreementId, did, escrowPayment.address, token.address, amounts, receivers) + + await assert.isRejected(escrowPayment.fulfill( + agreementId, + did, + amounts, + receivers, + escrowPayment.address, + token.address, + lockConditionId, + [conditionLockId, conditionLockId2]) + ) + + await lockPaymentCondition.fulfill(agreementId, did, escrowPayment.address, token.address, amounts2, receivers2) + + assert.strictEqual(await getBalance(token, lockPaymentCondition.address), 0) + assert.strictEqual(await getBalance(token, escrowPayment.address), balanceBefore + totalAmount) + + const result = await escrowPayment.fulfill( + agreementId, + did, + amounts, + receivers, + escrowPayment.address, + token.address, + lockConditionId, + [conditionLockId, conditionLockId2]) + + assert.strictEqual( + (await conditionStoreManager.getConditionState(escrowConditionId)).toNumber(), + constants.condition.state.fulfilled + ) + + testUtils.assertEmitted(result, 1, 'Fulfilled') + const eventArgs = testUtils.getEventArgsFromTx(result, 'Fulfilled') + expect(eventArgs._agreementId).to.equal(agreementId) + expect(eventArgs._conditionId).to.equal(escrowConditionId) + expect(eventArgs._receivers[0]).to.equal(receivers[0]) + expect(eventArgs._amounts[0].toNumber()).to.equal(amounts[0]) + + assert.strictEqual(await getBalance(token, escrowPayment.address), amounts2[0]) + assert.strictEqual(await getBalance(token, receivers[0]), amounts[0]) + + }) + + }) +}) From 4d1ac9d0f10d2e4f1f433cfcab7b5c737091fe57 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Sami=20M=C3=A4kel=C3=A4?= Date: Wed, 2 Feb 2022 14:11:44 +0200 Subject: [PATCH 17/32] multi escrow tests --- .../rewards/MultiEscrowPayment.Test.js | 19 +++++++++++++------ 1 file changed, 13 insertions(+), 6 deletions(-) diff --git a/test/unit/conditions/rewards/MultiEscrowPayment.Test.js b/test/unit/conditions/rewards/MultiEscrowPayment.Test.js index a122ecf1..9ebb24dc 100644 --- a/test/unit/conditions/rewards/MultiEscrowPayment.Test.js +++ b/test/unit/conditions/rewards/MultiEscrowPayment.Test.js @@ -206,6 +206,8 @@ contract('MultiEscrowPaymentCondition contract', (accounts) => { const amounts2 = [12] const totalAmount = amounts[0] + amounts2[0] const balanceBefore = await getBalance(token, escrowPayment.address) + const senderBefore = await getBalance(token, sender) + const receiverBefore = await getBalance(token, receivers[0]) const hashValuesLock = await lockPaymentCondition.hashValues(did, escrowPayment.address, token.address, amounts, receivers) const conditionLockId = await lockPaymentCondition.generateId(agreementId, hashValuesLock) @@ -218,7 +220,11 @@ contract('MultiEscrowPaymentCondition contract', (accounts) => { await conditionStoreManager.createCondition( conditionLockId2, - lockPaymentCondition.address) + lockPaymentCondition.address, + 1, + 2, + sender + ) const lockConditionId = conditionLockId @@ -267,10 +273,10 @@ contract('MultiEscrowPaymentCondition contract', (accounts) => { [conditionLockId, conditionLockId2]) ) - await lockPaymentCondition.fulfill(agreementId, did, escrowPayment.address, token.address, amounts2, receivers2) + await lockPaymentCondition.abortByTimeOut(conditionLockId2) assert.strictEqual(await getBalance(token, lockPaymentCondition.address), 0) - assert.strictEqual(await getBalance(token, escrowPayment.address), balanceBefore + totalAmount) + assert.strictEqual(await getBalance(token, escrowPayment.address), balanceBefore + amounts[0]) const result = await escrowPayment.fulfill( agreementId, @@ -291,11 +297,12 @@ contract('MultiEscrowPaymentCondition contract', (accounts) => { const eventArgs = testUtils.getEventArgsFromTx(result, 'Fulfilled') expect(eventArgs._agreementId).to.equal(agreementId) expect(eventArgs._conditionId).to.equal(escrowConditionId) - expect(eventArgs._receivers[0]).to.equal(receivers[0]) + expect(eventArgs._receivers[0]).to.equal(sender) expect(eventArgs._amounts[0].toNumber()).to.equal(amounts[0]) - assert.strictEqual(await getBalance(token, escrowPayment.address), amounts2[0]) - assert.strictEqual(await getBalance(token, receivers[0]), amounts[0]) + assert.strictEqual(await getBalance(token, escrowPayment.address), balanceBefore) + assert.strictEqual(await getBalance(token, receivers[0]), receiverBefore) + assert.strictEqual(await getBalance(token, sender), senderBefore + totalAmount) }) From 7b38624bfc18d6131c428bfc4bd5ad885e03a4e0 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Sami=20M=C3=A4kel=C3=A4?= Date: Wed, 2 Feb 2022 14:13:04 +0200 Subject: [PATCH 18/32] lint --- test/unit/conditions/rewards/EscrowPayment.Test.js | 2 +- test/unit/conditions/rewards/MultiEscrowPayment.Test.js | 7 +------ 2 files changed, 2 insertions(+), 7 deletions(-) diff --git a/test/unit/conditions/rewards/EscrowPayment.Test.js b/test/unit/conditions/rewards/EscrowPayment.Test.js index 2d6cd782..8e31878b 100644 --- a/test/unit/conditions/rewards/EscrowPayment.Test.js +++ b/test/unit/conditions/rewards/EscrowPayment.Test.js @@ -20,7 +20,7 @@ const { getBalance, getETHBalance } = require('../../../helpers/getBalance.js') const testUtils = require('../../../helpers/utils.js') function escrowTest(EscrowPaymentCondition, isMulti) { - let multi = isMulti ? (a => [a]) : (a => a) + const multi = isMulti ? a => [a] : a => a contract(isMulti ? 'EscrowPaymentCondition contract (multi)' : 'EscrowPaymentCondition contract', (accounts) => { let conditionStoreManager let token diff --git a/test/unit/conditions/rewards/MultiEscrowPayment.Test.js b/test/unit/conditions/rewards/MultiEscrowPayment.Test.js index 9ebb24dc..206b6728 100644 --- a/test/unit/conditions/rewards/MultiEscrowPayment.Test.js +++ b/test/unit/conditions/rewards/MultiEscrowPayment.Test.js @@ -15,7 +15,7 @@ const LockPaymentCondition = artifacts.require('LockPaymentCondition') const EscrowPaymentCondition = artifacts.require('MultiEscrowPaymentCondition') const constants = require('../../../helpers/constants.js') -const { getBalance, getETHBalance } = require('../../../helpers/getBalance.js') +const { getBalance } = require('../../../helpers/getBalance.js') const testUtils = require('../../../helpers/utils.js') contract('MultiEscrowPaymentCondition contract', (accounts) => { @@ -28,8 +28,6 @@ contract('MultiEscrowPaymentCondition contract', (accounts) => { const createRole = accounts[0] const owner = accounts[9] const deployer = accounts[8] - const checksum = testUtils.generateId() - const url = 'https://nevermined.io/did/test-attr-example.txt' before(async () => { const epochLibrary = await EpochLibrary.new() @@ -193,7 +191,6 @@ contract('MultiEscrowPaymentCondition contract', (accounts) => { assert.strictEqual(await getBalance(token, escrowPayment.address), amounts2[0]) assert.strictEqual(await getBalance(token, receivers[0]), amounts[0]) - }) it('should cancel if conditions were aborted', async () => { @@ -303,8 +300,6 @@ contract('MultiEscrowPaymentCondition contract', (accounts) => { assert.strictEqual(await getBalance(token, escrowPayment.address), balanceBefore) assert.strictEqual(await getBalance(token, receivers[0]), receiverBefore) assert.strictEqual(await getBalance(token, sender), senderBefore + totalAmount) - }) - }) }) From d3de2e6691e17fa7bf445b4e25d878d1ea64497c Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Sami=20M=C3=A4kel=C3=A4?= Date: Wed, 2 Feb 2022 14:35:02 +0200 Subject: [PATCH 19/32] multi escrow rejects fulfill if conditions are unresolved --- .../conditions/rewards/EscrowPayment.Test.js | 38 ++++++++++++------- 1 file changed, 25 insertions(+), 13 deletions(-) diff --git a/test/unit/conditions/rewards/EscrowPayment.Test.js b/test/unit/conditions/rewards/EscrowPayment.Test.js index 8e31878b..c604d79d 100644 --- a/test/unit/conditions/rewards/EscrowPayment.Test.js +++ b/test/unit/conditions/rewards/EscrowPayment.Test.js @@ -525,20 +525,32 @@ function escrowTest(EscrowPaymentCondition, isMulti) { assert.strictEqual(await getBalance(token, lockPaymentCondition.address), 0) assert.strictEqual(await getBalance(token, escrowPayment.address), balanceBefore + totalAmount) - await escrowPayment.fulfill( - agreementId, - did, - amounts, - receivers, - escrowPayment.address, - token.address, - lockConditionId, - multi(releaseConditionId)) + if (isMulti) { + await assert.isRejected(escrowPayment.fulfill( + agreementId, + did, + amounts, + receivers, + escrowPayment.address, + token.address, + lockConditionId, + multi(releaseConditionId))) + } else { + await escrowPayment.fulfill( + agreementId, + did, + amounts, + receivers, + escrowPayment.address, + token.address, + lockConditionId, + multi(releaseConditionId)) - assert.strictEqual( - (await conditionStoreManager.getConditionState(escrowConditionId)).toNumber(), - constants.condition.state.unfulfilled - ) + assert.strictEqual( + (await conditionStoreManager.getConditionState(escrowConditionId)).toNumber(), + constants.condition.state.unfulfilled + ) + } assert.strictEqual(await getBalance(token, escrowPayment.address), balanceBefore + totalAmount) }) From 564cdde6e6422313b2264fd0c0ad4715ad8b0ee6 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Sami=20M=C3=A4kel=C3=A4?= Date: Wed, 2 Feb 2022 15:53:52 +0200 Subject: [PATCH 20/32] make multi escrow test generic --- .../rewards/MultiEscrowPayment.Test.js | 561 +++++++++--------- 1 file changed, 283 insertions(+), 278 deletions(-) diff --git a/test/unit/conditions/rewards/MultiEscrowPayment.Test.js b/test/unit/conditions/rewards/MultiEscrowPayment.Test.js index 206b6728..2e1259b1 100644 --- a/test/unit/conditions/rewards/MultiEscrowPayment.Test.js +++ b/test/unit/conditions/rewards/MultiEscrowPayment.Test.js @@ -18,288 +18,293 @@ const constants = require('../../../helpers/constants.js') const { getBalance } = require('../../../helpers/getBalance.js') const testUtils = require('../../../helpers/utils.js') -contract('MultiEscrowPaymentCondition contract', (accounts) => { - let conditionStoreManager - let token - let lockPaymentCondition - let escrowPayment - let didRegistry - - const createRole = accounts[0] - const owner = accounts[9] - const deployer = accounts[8] - - before(async () => { - const epochLibrary = await EpochLibrary.new() - await ConditionStoreManager.link(epochLibrary) - const didRegistryLibrary = await DIDRegistryLibrary.new() - await DIDRegistry.link(didRegistryLibrary) - }) +function testMultiEscrow(EscrowPaymentCondition) { + contract('MultiEscrowPaymentCondition contract', (accounts) => { + let conditionStoreManager + let token + let lockPaymentCondition + let escrowPayment + let didRegistry + + const createRole = accounts[0] + const owner = accounts[9] + const deployer = accounts[8] + + before(async () => { + const epochLibrary = await EpochLibrary.new() + await ConditionStoreManager.link(epochLibrary) + const didRegistryLibrary = await DIDRegistryLibrary.new() + await DIDRegistry.link(didRegistryLibrary) + }) - beforeEach(async () => { - await setupTest() - }) + beforeEach(async () => { + await setupTest() + }) - async function setupTest({ - conditionId = testUtils.generateId(), - conditionType = testUtils.generateId() - } = {}) { - if (!escrowPayment) { - conditionStoreManager = await ConditionStoreManager.new() - await conditionStoreManager.initialize( - owner, - { from: owner } - ) - - await conditionStoreManager.delegateCreateRole( + async function setupTest({ + conditionId = testUtils.generateId(), + conditionType = testUtils.generateId() + } = {}) { + if (!escrowPayment) { + conditionStoreManager = await ConditionStoreManager.new() + await conditionStoreManager.initialize( + owner, + { from: owner } + ) + + await conditionStoreManager.delegateCreateRole( + createRole, + { from: owner } + ) + + didRegistry = await DIDRegistry.new() + await didRegistry.initialize(owner, constants.address.zero, constants.address.zero) + + token = await NeverminedToken.new() + await token.initialize(owner, owner) + + lockPaymentCondition = await LockPaymentCondition.new() + await lockPaymentCondition.initialize( + owner, + conditionStoreManager.address, + didRegistry.address, + { from: deployer } + ) + + escrowPayment = await EscrowPaymentCondition.new() + await escrowPayment.initialize( + owner, + conditionStoreManager.address, + { from: deployer } + ) + } + + return { + escrowPayment, + lockPaymentCondition, + token, + conditionStoreManager, + conditionId, + conditionType, createRole, - { from: owner } - ) - - didRegistry = await DIDRegistry.new() - await didRegistry.initialize(owner, constants.address.zero, constants.address.zero) - - token = await NeverminedToken.new() - await token.initialize(owner, owner) - - lockPaymentCondition = await LockPaymentCondition.new() - await lockPaymentCondition.initialize( - owner, - conditionStoreManager.address, - didRegistry.address, - { from: deployer } - ) - - escrowPayment = await EscrowPaymentCondition.new() - await escrowPayment.initialize( - owner, - conditionStoreManager.address, - { from: deployer } - ) + owner + } } - return { - escrowPayment, - lockPaymentCondition, - token, - conditionStoreManager, - conditionId, - conditionType, - createRole, - owner - } - } - - describe('fulfill with two release conditions', () => { - it('should fulfill if both are fulfilled', async () => { - const agreementId = testUtils.generateId() - const did = testUtils.generateId() - const sender = accounts[0] - const receivers = [accounts[1]] - const amounts = [10] - const receivers2 = [accounts[2]] - const amounts2 = [12] - const totalAmount = amounts[0] + amounts2[0] - const balanceBefore = await getBalance(token, escrowPayment.address) - - const hashValuesLock = await lockPaymentCondition.hashValues(did, escrowPayment.address, token.address, amounts, receivers) - const conditionLockId = await lockPaymentCondition.generateId(agreementId, hashValuesLock) - const hashValuesLock2 = await lockPaymentCondition.hashValues(did, escrowPayment.address, token.address, amounts2, receivers2) - const conditionLockId2 = await lockPaymentCondition.generateId(agreementId, hashValuesLock2) - - await conditionStoreManager.createCondition( - conditionLockId, - lockPaymentCondition.address) - - await conditionStoreManager.createCondition( - conditionLockId2, - lockPaymentCondition.address) - - const lockConditionId = conditionLockId - - const hashValues = await escrowPayment.hashValues( - did, - amounts, - receivers, - escrowPayment.address, - token.address, - lockConditionId, - [conditionLockId, conditionLockId2]) - - const escrowConditionId = await escrowPayment.generateId(agreementId, hashValues) - - await conditionStoreManager.createCondition( - escrowConditionId, - escrowPayment.address) - - await token.mint(sender, totalAmount, { from: owner }) - await token.approve( - lockPaymentCondition.address, - totalAmount, - { from: sender }) - - await assert.isRejected(escrowPayment.fulfill( - agreementId, - did, - amounts, - receivers, - escrowPayment.address, - token.address, - lockConditionId, - [conditionLockId, conditionLockId2]) - ) - - await lockPaymentCondition.fulfill(agreementId, did, escrowPayment.address, token.address, amounts, receivers) - - await assert.isRejected(escrowPayment.fulfill( - agreementId, - did, - amounts, - receivers, - escrowPayment.address, - token.address, - lockConditionId, - [conditionLockId, conditionLockId2]) - ) - - await lockPaymentCondition.fulfill(agreementId, did, escrowPayment.address, token.address, amounts2, receivers2) - - assert.strictEqual(await getBalance(token, lockPaymentCondition.address), 0) - assert.strictEqual(await getBalance(token, escrowPayment.address), balanceBefore + totalAmount) - - const result = await escrowPayment.fulfill( - agreementId, - did, - amounts, - receivers, - escrowPayment.address, - token.address, - lockConditionId, - [conditionLockId, conditionLockId2]) - - assert.strictEqual( - (await conditionStoreManager.getConditionState(escrowConditionId)).toNumber(), - constants.condition.state.fulfilled - ) - - testUtils.assertEmitted(result, 1, 'Fulfilled') - const eventArgs = testUtils.getEventArgsFromTx(result, 'Fulfilled') - expect(eventArgs._agreementId).to.equal(agreementId) - expect(eventArgs._conditionId).to.equal(escrowConditionId) - expect(eventArgs._receivers[0]).to.equal(receivers[0]) - expect(eventArgs._amounts[0].toNumber()).to.equal(amounts[0]) - - assert.strictEqual(await getBalance(token, escrowPayment.address), amounts2[0]) - assert.strictEqual(await getBalance(token, receivers[0]), amounts[0]) - }) - - it('should cancel if conditions were aborted', async () => { - const agreementId = testUtils.generateId() - const did = testUtils.generateId() - const sender = accounts[0] - const receivers = [accounts[1]] - const amounts = [10] - const receivers2 = [accounts[2]] - const amounts2 = [12] - const totalAmount = amounts[0] + amounts2[0] - const balanceBefore = await getBalance(token, escrowPayment.address) - const senderBefore = await getBalance(token, sender) - const receiverBefore = await getBalance(token, receivers[0]) - - const hashValuesLock = await lockPaymentCondition.hashValues(did, escrowPayment.address, token.address, amounts, receivers) - const conditionLockId = await lockPaymentCondition.generateId(agreementId, hashValuesLock) - const hashValuesLock2 = await lockPaymentCondition.hashValues(did, escrowPayment.address, token.address, amounts2, receivers2) - const conditionLockId2 = await lockPaymentCondition.generateId(agreementId, hashValuesLock2) - - await conditionStoreManager.createCondition( - conditionLockId, - lockPaymentCondition.address) - - await conditionStoreManager.createCondition( - conditionLockId2, - lockPaymentCondition.address, - 1, - 2, - sender - ) - - const lockConditionId = conditionLockId - - const hashValues = await escrowPayment.hashValues( - did, - amounts, - receivers, - escrowPayment.address, - token.address, - lockConditionId, - [conditionLockId, conditionLockId2]) - - const escrowConditionId = await escrowPayment.generateId(agreementId, hashValues) - - await conditionStoreManager.createCondition( - escrowConditionId, - escrowPayment.address) - - await token.mint(sender, totalAmount, { from: owner }) - await token.approve( - lockPaymentCondition.address, - totalAmount, - { from: sender }) - - await assert.isRejected(escrowPayment.fulfill( - agreementId, - did, - amounts, - receivers, - escrowPayment.address, - token.address, - lockConditionId, - [conditionLockId, conditionLockId2]) - ) - - await lockPaymentCondition.fulfill(agreementId, did, escrowPayment.address, token.address, amounts, receivers) - - await assert.isRejected(escrowPayment.fulfill( - agreementId, - did, - amounts, - receivers, - escrowPayment.address, - token.address, - lockConditionId, - [conditionLockId, conditionLockId2]) - ) - - await lockPaymentCondition.abortByTimeOut(conditionLockId2) - - assert.strictEqual(await getBalance(token, lockPaymentCondition.address), 0) - assert.strictEqual(await getBalance(token, escrowPayment.address), balanceBefore + amounts[0]) - - const result = await escrowPayment.fulfill( - agreementId, - did, - amounts, - receivers, - escrowPayment.address, - token.address, - lockConditionId, - [conditionLockId, conditionLockId2]) - - assert.strictEqual( - (await conditionStoreManager.getConditionState(escrowConditionId)).toNumber(), - constants.condition.state.fulfilled - ) - - testUtils.assertEmitted(result, 1, 'Fulfilled') - const eventArgs = testUtils.getEventArgsFromTx(result, 'Fulfilled') - expect(eventArgs._agreementId).to.equal(agreementId) - expect(eventArgs._conditionId).to.equal(escrowConditionId) - expect(eventArgs._receivers[0]).to.equal(sender) - expect(eventArgs._amounts[0].toNumber()).to.equal(amounts[0]) - - assert.strictEqual(await getBalance(token, escrowPayment.address), balanceBefore) - assert.strictEqual(await getBalance(token, receivers[0]), receiverBefore) - assert.strictEqual(await getBalance(token, sender), senderBefore + totalAmount) + describe('fulfill with two release conditions', () => { + it('should fulfill if both are fulfilled', async () => { + const agreementId = testUtils.generateId() + const did = testUtils.generateId() + const sender = accounts[0] + const receivers = [accounts[1]] + const amounts = [10] + const receivers2 = [accounts[2]] + const amounts2 = [12] + const totalAmount = amounts[0] + amounts2[0] + const balanceBefore = await getBalance(token, escrowPayment.address) + + const hashValuesLock = await lockPaymentCondition.hashValues(did, escrowPayment.address, token.address, amounts, receivers) + const conditionLockId = await lockPaymentCondition.generateId(agreementId, hashValuesLock) + const hashValuesLock2 = await lockPaymentCondition.hashValues(did, escrowPayment.address, token.address, amounts2, receivers2) + const conditionLockId2 = await lockPaymentCondition.generateId(agreementId, hashValuesLock2) + + await conditionStoreManager.createCondition( + conditionLockId, + lockPaymentCondition.address) + + await conditionStoreManager.createCondition( + conditionLockId2, + lockPaymentCondition.address) + + const lockConditionId = conditionLockId + + const hashValues = await escrowPayment.hashValues( + did, + amounts, + receivers, + escrowPayment.address, + token.address, + lockConditionId, + [conditionLockId, conditionLockId2]) + + const escrowConditionId = await escrowPayment.generateId(agreementId, hashValues) + + await conditionStoreManager.createCondition( + escrowConditionId, + escrowPayment.address) + + await token.mint(sender, totalAmount, { from: owner }) + await token.approve( + lockPaymentCondition.address, + totalAmount, + { from: sender }) + + await assert.isRejected(escrowPayment.fulfill( + agreementId, + did, + amounts, + receivers, + escrowPayment.address, + token.address, + lockConditionId, + [conditionLockId, conditionLockId2]) + ) + + await lockPaymentCondition.fulfill(agreementId, did, escrowPayment.address, token.address, amounts, receivers) + + await assert.isRejected(escrowPayment.fulfill( + agreementId, + did, + amounts, + receivers, + escrowPayment.address, + token.address, + lockConditionId, + [conditionLockId, conditionLockId2]) + ) + + await lockPaymentCondition.fulfill(agreementId, did, escrowPayment.address, token.address, amounts2, receivers2) + + assert.strictEqual(await getBalance(token, lockPaymentCondition.address), 0) + assert.strictEqual(await getBalance(token, escrowPayment.address), balanceBefore + totalAmount) + + const result = await escrowPayment.fulfill( + agreementId, + did, + amounts, + receivers, + escrowPayment.address, + token.address, + lockConditionId, + [conditionLockId, conditionLockId2]) + + assert.strictEqual( + (await conditionStoreManager.getConditionState(escrowConditionId)).toNumber(), + constants.condition.state.fulfilled + ) + + testUtils.assertEmitted(result, 1, 'Fulfilled') + const eventArgs = testUtils.getEventArgsFromTx(result, 'Fulfilled') + expect(eventArgs._agreementId).to.equal(agreementId) + expect(eventArgs._conditionId).to.equal(escrowConditionId) + expect(eventArgs._receivers[0]).to.equal(receivers[0]) + expect(eventArgs._amounts[0].toNumber()).to.equal(amounts[0]) + + assert.strictEqual(await getBalance(token, escrowPayment.address), amounts2[0]) + assert.strictEqual(await getBalance(token, receivers[0]), amounts[0]) + }) + + it('should cancel if conditions were aborted', async () => { + const agreementId = testUtils.generateId() + const did = testUtils.generateId() + const sender = accounts[0] + const receivers = [accounts[1]] + const amounts = [10] + const receivers2 = [accounts[2]] + const amounts2 = [12] + const totalAmount = amounts[0] + amounts2[0] + const balanceBefore = await getBalance(token, escrowPayment.address) + const senderBefore = await getBalance(token, sender) + const receiverBefore = await getBalance(token, receivers[0]) + + const hashValuesLock = await lockPaymentCondition.hashValues(did, escrowPayment.address, token.address, amounts, receivers) + const conditionLockId = await lockPaymentCondition.generateId(agreementId, hashValuesLock) + const hashValuesLock2 = await lockPaymentCondition.hashValues(did, escrowPayment.address, token.address, amounts2, receivers2) + const conditionLockId2 = await lockPaymentCondition.generateId(agreementId, hashValuesLock2) + + await conditionStoreManager.createCondition( + conditionLockId, + lockPaymentCondition.address) + + await conditionStoreManager.createCondition( + conditionLockId2, + lockPaymentCondition.address, + 1, + 2, + sender + ) + + const lockConditionId = conditionLockId + + const hashValues = await escrowPayment.hashValues( + did, + amounts, + receivers, + escrowPayment.address, + token.address, + lockConditionId, + [conditionLockId, conditionLockId2]) + + const escrowConditionId = await escrowPayment.generateId(agreementId, hashValues) + + await conditionStoreManager.createCondition( + escrowConditionId, + escrowPayment.address) + + await token.mint(sender, totalAmount, { from: owner }) + await token.approve( + lockPaymentCondition.address, + totalAmount, + { from: sender }) + + await assert.isRejected(escrowPayment.fulfill( + agreementId, + did, + amounts, + receivers, + escrowPayment.address, + token.address, + lockConditionId, + [conditionLockId, conditionLockId2]) + ) + + await lockPaymentCondition.fulfill(agreementId, did, escrowPayment.address, token.address, amounts, receivers) + + await assert.isRejected(escrowPayment.fulfill( + agreementId, + did, + amounts, + receivers, + escrowPayment.address, + token.address, + lockConditionId, + [conditionLockId, conditionLockId2]) + ) + + await lockPaymentCondition.abortByTimeOut(conditionLockId2) + + assert.strictEqual(await getBalance(token, lockPaymentCondition.address), 0) + assert.strictEqual(await getBalance(token, escrowPayment.address), balanceBefore + amounts[0]) + + const result = await escrowPayment.fulfill( + agreementId, + did, + amounts, + receivers, + escrowPayment.address, + token.address, + lockConditionId, + [conditionLockId, conditionLockId2]) + + assert.strictEqual( + (await conditionStoreManager.getConditionState(escrowConditionId)).toNumber(), + constants.condition.state.fulfilled + ) + + testUtils.assertEmitted(result, 1, 'Fulfilled') + const eventArgs = testUtils.getEventArgsFromTx(result, 'Fulfilled') + expect(eventArgs._agreementId).to.equal(agreementId) + expect(eventArgs._conditionId).to.equal(escrowConditionId) + expect(eventArgs._receivers[0]).to.equal(sender) + expect(eventArgs._amounts[0].toNumber()).to.equal(amounts[0]) + + assert.strictEqual(await getBalance(token, escrowPayment.address), balanceBefore) + assert.strictEqual(await getBalance(token, receivers[0]), receiverBefore) + assert.strictEqual(await getBalance(token, sender), senderBefore + totalAmount) + }) }) }) -}) +} + +testMultiEscrow(EscrowPaymentCondition) + From fa2761b17a9929428ecc68c1004c576e70913e82 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Sami=20M=C3=A4kel=C3=A4?= Date: Wed, 2 Feb 2022 16:25:13 +0200 Subject: [PATCH 21/32] refactoring --- .../rewards/MultiEscrowPayment.Test.js | 101 ++++++++++++++---- 1 file changed, 78 insertions(+), 23 deletions(-) diff --git a/test/unit/conditions/rewards/MultiEscrowPayment.Test.js b/test/unit/conditions/rewards/MultiEscrowPayment.Test.js index 2e1259b1..06fbbe76 100644 --- a/test/unit/conditions/rewards/MultiEscrowPayment.Test.js +++ b/test/unit/conditions/rewards/MultiEscrowPayment.Test.js @@ -12,14 +12,57 @@ const DIDRegistry = artifacts.require('DIDRegistry') const ConditionStoreManager = artifacts.require('ConditionStoreManager') const NeverminedToken = artifacts.require('NeverminedToken') const LockPaymentCondition = artifacts.require('LockPaymentCondition') +const NFTMarkedLockCondition = artifacts.require('NFTMarkedLockCondition') +const NFT721MarkedLockCondition = artifacts.require('NFT721MarkedLockCondition') const EscrowPaymentCondition = artifacts.require('MultiEscrowPaymentCondition') +const NFTEscrowPaymentCondition = artifacts.require('NFTEscrowPaymentCondition') +const NFT721EscrowPaymentCondition = artifacts.require('NFT721EscrowPaymentCondition') const constants = require('../../../helpers/constants.js') const { getBalance } = require('../../../helpers/getBalance.js') const testUtils = require('../../../helpers/utils.js') -function testMultiEscrow(EscrowPaymentCondition) { +function tokenWrapper(contract) { + contract.hashWrap = (did, escrowPaymentAddress, tokenAddress, amounts, receivers) => { + return contract.hashValues(did, escrowPaymentAddress, tokenAddress, amounts, receivers) + } + contract.fulfillWrap = (agreementId, did, escrowPaymentAddress, tokenAddress, amounts, receivers) => { + return contract.fulfill(agreementId, did, escrowPaymentAddress, tokenAddress, amounts, receivers) + } + return contract +} + +function nftWrapper(contract) { + contract.hashWrap = (did, escrowPaymentAddress, tokenAddress, amounts, receivers) => { + return contract.hashValues(did, escrowPaymentAddress, amounts[0], receivers[0], tokenAddress) + } + contract.fulfillWrap = (agreementId, did, escrowPaymentAddress, tokenAddress, amounts, receivers) => { + return lockPaymentCondition.fulfill(agreementId, did, escrowPaymentAddress, amounts[0], receivers[0], tokenAddress) + } + return contract +} + +function testMultiEscrow(EscrowPaymentCondition, LockPaymentCondition, nft) { contract('MultiEscrowPaymentCondition contract', (accounts) => { + + const single = nft ? (a => a[0]) : (a => a) + + function tokenLockHash(lockPaymentCondition, did, escrowPaymentAddress, tokenAddress, amounts, receivers) { + return lockPaymentCondition.hashValues(did, escrowPaymentAddress, tokenAddress, amounts, receivers) + } + function nftLockHash(lockPaymentCondition, did, escrowPaymentAddress, tokenAddress, amounts, receivers) { + return lockPaymentCondition.hashValues(did, escrowPaymentAddress, amounts[0], receivers[0], tokenAddress) + } + const lockHash = nft ? nftLockHash : tokenLockHash + + function tokenLockFulfill(lockPaymentCondition, agreementId, did, escrowPaymentAddress, tokenAddress, amounts, receivers) { + return lockPaymentCondition.fulfill(agreementId, did, escrowPaymentAddress, tokenAddress, amounts, receivers) + } + function nftLockFulfill(lockPaymentCondition, agreementId, did, escrowPaymentAddress, tokenAddress, amounts, receivers) { + return lockPaymentCondition.fulfill(agreementId, did, escrowPaymentAddress, amounts[0], receivers[0], tokenAddress) + } + const lockFulfill = nft ? nftLockFulfill : tokenLockFulfill + let conditionStoreManager let token let lockPaymentCondition @@ -31,10 +74,12 @@ function testMultiEscrow(EscrowPaymentCondition) { const deployer = accounts[8] before(async () => { - const epochLibrary = await EpochLibrary.new() - await ConditionStoreManager.link(epochLibrary) - const didRegistryLibrary = await DIDRegistryLibrary.new() - await DIDRegistry.link(didRegistryLibrary) + if (!nft) { + const epochLibrary = await EpochLibrary.new() + await ConditionStoreManager.link(epochLibrary) + const didRegistryLibrary = await DIDRegistryLibrary.new() + await DIDRegistry.link(didRegistryLibrary) + } }) beforeEach(async () => { @@ -64,12 +109,20 @@ function testMultiEscrow(EscrowPaymentCondition) { await token.initialize(owner, owner) lockPaymentCondition = await LockPaymentCondition.new() - await lockPaymentCondition.initialize( - owner, - conditionStoreManager.address, - didRegistry.address, - { from: deployer } - ) + if (nft) { + await lockPaymentCondition.initialize( + owner, + conditionStoreManager.address, + { from: deployer } + ) + } else { + await lockPaymentCondition.initialize( + owner, + conditionStoreManager.address, + didRegistry.address, + { from: deployer } + ) + } escrowPayment = await EscrowPaymentCondition.new() await escrowPayment.initialize( @@ -103,9 +156,9 @@ function testMultiEscrow(EscrowPaymentCondition) { const totalAmount = amounts[0] + amounts2[0] const balanceBefore = await getBalance(token, escrowPayment.address) - const hashValuesLock = await lockPaymentCondition.hashValues(did, escrowPayment.address, token.address, amounts, receivers) + const hashValuesLock = await lockHash(lockPaymentCondition, did, escrowPayment.address, token.address, amounts, receivers) const conditionLockId = await lockPaymentCondition.generateId(agreementId, hashValuesLock) - const hashValuesLock2 = await lockPaymentCondition.hashValues(did, escrowPayment.address, token.address, amounts2, receivers2) + const hashValuesLock2 = await lockHash(lockPaymentCondition, did, escrowPayment.address, token.address, amounts2, receivers2) const conditionLockId2 = await lockPaymentCondition.generateId(agreementId, hashValuesLock2) await conditionStoreManager.createCondition( @@ -120,8 +173,8 @@ function testMultiEscrow(EscrowPaymentCondition) { const hashValues = await escrowPayment.hashValues( did, - amounts, - receivers, + single(amounts), + single(receivers), escrowPayment.address, token.address, lockConditionId, @@ -142,21 +195,21 @@ function testMultiEscrow(EscrowPaymentCondition) { await assert.isRejected(escrowPayment.fulfill( agreementId, did, - amounts, - receivers, + single(amounts), + single(receivers), escrowPayment.address, token.address, lockConditionId, [conditionLockId, conditionLockId2]) ) - await lockPaymentCondition.fulfill(agreementId, did, escrowPayment.address, token.address, amounts, receivers) + await lockFulfill(lockPaymentCondition, agreementId, did, escrowPayment.address, token.address, amounts, receivers) await assert.isRejected(escrowPayment.fulfill( agreementId, did, - amounts, - receivers, + single(amounts), + single(receivers), escrowPayment.address, token.address, lockConditionId, @@ -171,8 +224,8 @@ function testMultiEscrow(EscrowPaymentCondition) { const result = await escrowPayment.fulfill( agreementId, did, - amounts, - receivers, + single(amounts), + single(receivers), escrowPayment.address, token.address, lockConditionId, @@ -306,5 +359,7 @@ function testMultiEscrow(EscrowPaymentCondition) { }) } -testMultiEscrow(EscrowPaymentCondition) +testMultiEscrow(EscrowPaymentCondition, LockPaymentCondition, false) +testMultiEscrow(NFTEscrowPaymentCondition, NFTMarkedLockCondition, true) +testMultiEscrow(NFT721EscrowPaymentCondition, NFT721MarkedLockCondition, true) From dc102dccb0efd82fe3fcb491a0f7b52b9abd0f5b Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Sami=20M=C3=A4kel=C3=A4?= Date: Wed, 2 Feb 2022 16:29:32 +0200 Subject: [PATCH 22/32] refactoring --- .../rewards/MultiEscrowPayment.Test.js | 39 ++++++++++--------- 1 file changed, 20 insertions(+), 19 deletions(-) diff --git a/test/unit/conditions/rewards/MultiEscrowPayment.Test.js b/test/unit/conditions/rewards/MultiEscrowPayment.Test.js index 06fbbe76..7009a058 100644 --- a/test/unit/conditions/rewards/MultiEscrowPayment.Test.js +++ b/test/unit/conditions/rewards/MultiEscrowPayment.Test.js @@ -29,6 +29,15 @@ function tokenWrapper(contract) { contract.fulfillWrap = (agreementId, did, escrowPaymentAddress, tokenAddress, amounts, receivers) => { return contract.fulfill(agreementId, did, escrowPaymentAddress, tokenAddress, amounts, receivers) } + contract.initWrap = (owner, conditionStoreManagerAddress, _didRegistryAddress, deployer) => { + return contract.initialize( + owner, + conditionStoreManagerAddress, + didRegistryAddress, + { from: deployer } + ) + } + return contract } @@ -39,6 +48,13 @@ function nftWrapper(contract) { contract.fulfillWrap = (agreementId, did, escrowPaymentAddress, tokenAddress, amounts, receivers) => { return lockPaymentCondition.fulfill(agreementId, did, escrowPaymentAddress, amounts[0], receivers[0], tokenAddress) } + contract.initWrap = (owner, conditionStoreManagerAddress, _didRegistryAddress, deployer) => { + return contract.initialize( + owner, + conditionStoreManagerAddress, + { from: deployer } + ) + } return contract } @@ -46,22 +62,7 @@ function testMultiEscrow(EscrowPaymentCondition, LockPaymentCondition, nft) { contract('MultiEscrowPaymentCondition contract', (accounts) => { const single = nft ? (a => a[0]) : (a => a) - - function tokenLockHash(lockPaymentCondition, did, escrowPaymentAddress, tokenAddress, amounts, receivers) { - return lockPaymentCondition.hashValues(did, escrowPaymentAddress, tokenAddress, amounts, receivers) - } - function nftLockHash(lockPaymentCondition, did, escrowPaymentAddress, tokenAddress, amounts, receivers) { - return lockPaymentCondition.hashValues(did, escrowPaymentAddress, amounts[0], receivers[0], tokenAddress) - } - const lockHash = nft ? nftLockHash : tokenLockHash - - function tokenLockFulfill(lockPaymentCondition, agreementId, did, escrowPaymentAddress, tokenAddress, amounts, receivers) { - return lockPaymentCondition.fulfill(agreementId, did, escrowPaymentAddress, tokenAddress, amounts, receivers) - } - function nftLockFulfill(lockPaymentCondition, agreementId, did, escrowPaymentAddress, tokenAddress, amounts, receivers) { - return lockPaymentCondition.fulfill(agreementId, did, escrowPaymentAddress, amounts[0], receivers[0], tokenAddress) - } - const lockFulfill = nft ? nftLockFulfill : tokenLockFulfill + const lockWrapper = nft ? nftWrapper : tokenWrapper let conditionStoreManager let token @@ -108,7 +109,7 @@ function testMultiEscrow(EscrowPaymentCondition, LockPaymentCondition, nft) { token = await NeverminedToken.new() await token.initialize(owner, owner) - lockPaymentCondition = await LockPaymentCondition.new() + lockPaymentCondition = lockWrapper(await LockPaymentCondition.new()) if (nft) { await lockPaymentCondition.initialize( owner, @@ -156,9 +157,9 @@ function testMultiEscrow(EscrowPaymentCondition, LockPaymentCondition, nft) { const totalAmount = amounts[0] + amounts2[0] const balanceBefore = await getBalance(token, escrowPayment.address) - const hashValuesLock = await lockHash(lockPaymentCondition, did, escrowPayment.address, token.address, amounts, receivers) + const hashValuesLock = await lockPaymentCondition.hashWrap(did, escrowPayment.address, token.address, amounts, receivers) const conditionLockId = await lockPaymentCondition.generateId(agreementId, hashValuesLock) - const hashValuesLock2 = await lockHash(lockPaymentCondition, did, escrowPayment.address, token.address, amounts2, receivers2) + const hashValuesLock2 = await lockPaymentCondition.hashWrap(did, escrowPayment.address, token.address, amounts2, receivers2) const conditionLockId2 = await lockPaymentCondition.generateId(agreementId, hashValuesLock2) await conditionStoreManager.createCondition( From 389afb7ba96594321a0d2d4d139b22b0531b5ae1 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Sami=20M=C3=A4kel=C3=A4?= Date: Wed, 2 Feb 2022 16:43:44 +0200 Subject: [PATCH 23/32] need wrapper for token --- .../rewards/MultiEscrowPayment.Test.js | 101 ++++++++++++------ 1 file changed, 71 insertions(+), 30 deletions(-) diff --git a/test/unit/conditions/rewards/MultiEscrowPayment.Test.js b/test/unit/conditions/rewards/MultiEscrowPayment.Test.js index 7009a058..0afe7344 100644 --- a/test/unit/conditions/rewards/MultiEscrowPayment.Test.js +++ b/test/unit/conditions/rewards/MultiEscrowPayment.Test.js @@ -22,7 +22,7 @@ const constants = require('../../../helpers/constants.js') const { getBalance } = require('../../../helpers/getBalance.js') const testUtils = require('../../../helpers/utils.js') -function tokenWrapper(contract) { +function tokenLockWrapper(contract) { contract.hashWrap = (did, escrowPaymentAddress, tokenAddress, amounts, receivers) => { return contract.hashValues(did, escrowPaymentAddress, tokenAddress, amounts, receivers) } @@ -41,12 +41,12 @@ function tokenWrapper(contract) { return contract } -function nftWrapper(contract) { +function nftLockWrapper(contract) { contract.hashWrap = (did, escrowPaymentAddress, tokenAddress, amounts, receivers) => { return contract.hashValues(did, escrowPaymentAddress, amounts[0], receivers[0], tokenAddress) } contract.fulfillWrap = (agreementId, did, escrowPaymentAddress, tokenAddress, amounts, receivers) => { - return lockPaymentCondition.fulfill(agreementId, did, escrowPaymentAddress, amounts[0], receivers[0], tokenAddress) + return contract.fulfill(agreementId, did, escrowPaymentAddress, amounts[0], receivers[0], tokenAddress) } contract.initWrap = (owner, conditionStoreManagerAddress, _didRegistryAddress, deployer) => { return contract.initialize( @@ -58,11 +58,52 @@ function nftWrapper(contract) { return contract } -function testMultiEscrow(EscrowPaymentCondition, LockPaymentCondition, nft) { +function tokenEscrowWrapper(contract) { + contract.hashWrap = (did, amounts, receivers, escrowPaymentAddress, tokenAddress, lockConditionId, releaseConditionId) => { + return contract.hashValues(did, amounts, receivers, escrowPaymentAddress, tokenAddress, lockConditionId, releaseConditionId) + } + contract.fulfillWrap = (agreementId, did, amounts, receivers, escrowPaymentAddress, tokenAddress, lockConditionId, releaseConditionId) => { + return contract.fulfill( + agreementId, + did, + amounts, + receivers, + escrowPaymentAddress, + tokenAddress, + lockConditionId, + releaseConditionId + ) + } + + return contract +} + +function nftEscrowWrapper(contract) { + contract.hashWrap = (did, amounts, receivers, escrowPaymentAddress, tokenAddress, lockConditionId, releaseConditionId) => { + return contract.hashValues(did, amounts[0], receivers[0], escrowPaymentAddress, tokenAddress, lockConditionId, releaseConditionId) + } + contract.fulfillWrap = (agreementId, did, amounts, receivers, escrowPaymentAddress, tokenAddress, lockConditionId, releaseConditionId) => { + return contract.fulfill( + agreementId, + did, + amounts[0], + receivers[0], + escrowPaymentAddress, + tokenAddress, + lockConditionId, + releaseConditionId + ) + } + + return contract +} + +function testMultiEscrow(EscrowPaymentCondition, LockPaymentCondition, nft, amount1, amount2) { contract('MultiEscrowPaymentCondition contract', (accounts) => { const single = nft ? (a => a[0]) : (a => a) - const lockWrapper = nft ? nftWrapper : tokenWrapper + const lockWrapper = nft ? nftLockWrapper : tokenLockWrapper + const escrowWrapper = nft ? nftEscrowWrapper : tokenEscrowWrapper let conditionStoreManager let token @@ -125,7 +166,7 @@ function testMultiEscrow(EscrowPaymentCondition, LockPaymentCondition, nft) { ) } - escrowPayment = await EscrowPaymentCondition.new() + escrowPayment = escrowWrapper(await EscrowPaymentCondition.new()) await escrowPayment.initialize( owner, conditionStoreManager.address, @@ -151,9 +192,9 @@ function testMultiEscrow(EscrowPaymentCondition, LockPaymentCondition, nft) { const did = testUtils.generateId() const sender = accounts[0] const receivers = [accounts[1]] - const amounts = [10] + const amounts = [amount1] const receivers2 = [accounts[2]] - const amounts2 = [12] + const amounts2 = [amount2] const totalAmount = amounts[0] + amounts2[0] const balanceBefore = await getBalance(token, escrowPayment.address) @@ -172,10 +213,10 @@ function testMultiEscrow(EscrowPaymentCondition, LockPaymentCondition, nft) { const lockConditionId = conditionLockId - const hashValues = await escrowPayment.hashValues( + const hashValues = await escrowPayment.hashWrap( did, - single(amounts), - single(receivers), + amounts, + receivers, escrowPayment.address, token.address, lockConditionId, @@ -193,31 +234,31 @@ function testMultiEscrow(EscrowPaymentCondition, LockPaymentCondition, nft) { totalAmount, { from: sender }) - await assert.isRejected(escrowPayment.fulfill( + await assert.isRejected(escrowPayment.fulfillWrap( agreementId, did, - single(amounts), - single(receivers), + amounts, + receivers, escrowPayment.address, token.address, lockConditionId, [conditionLockId, conditionLockId2]) ) - await lockFulfill(lockPaymentCondition, agreementId, did, escrowPayment.address, token.address, amounts, receivers) + await lockPaymentCondition.fulfillWrap(agreementId, did, escrowPayment.address, token.address, amounts, receivers) - await assert.isRejected(escrowPayment.fulfill( + await assert.isRejected(escrowPayment.fulfillWrap( agreementId, did, - single(amounts), - single(receivers), + amounts, + receivers, escrowPayment.address, token.address, lockConditionId, [conditionLockId, conditionLockId2]) ) - await lockPaymentCondition.fulfill(agreementId, did, escrowPayment.address, token.address, amounts2, receivers2) + await lockPaymentCondition.fulfillWrap(agreementId, did, escrowPayment.address, token.address, amounts2, receivers2) assert.strictEqual(await getBalance(token, lockPaymentCondition.address), 0) assert.strictEqual(await getBalance(token, escrowPayment.address), balanceBefore + totalAmount) @@ -253,17 +294,17 @@ function testMultiEscrow(EscrowPaymentCondition, LockPaymentCondition, nft) { const did = testUtils.generateId() const sender = accounts[0] const receivers = [accounts[1]] - const amounts = [10] + const amounts = [amount1] const receivers2 = [accounts[2]] - const amounts2 = [12] + const amounts2 = [amount2] const totalAmount = amounts[0] + amounts2[0] const balanceBefore = await getBalance(token, escrowPayment.address) const senderBefore = await getBalance(token, sender) const receiverBefore = await getBalance(token, receivers[0]) - const hashValuesLock = await lockPaymentCondition.hashValues(did, escrowPayment.address, token.address, amounts, receivers) + const hashValuesLock = await lockPaymentCondition.hashWrap(did, escrowPayment.address, token.address, amounts, receivers) const conditionLockId = await lockPaymentCondition.generateId(agreementId, hashValuesLock) - const hashValuesLock2 = await lockPaymentCondition.hashValues(did, escrowPayment.address, token.address, amounts2, receivers2) + const hashValuesLock2 = await lockPaymentCondition.hashWrap(did, escrowPayment.address, token.address, amounts2, receivers2) const conditionLockId2 = await lockPaymentCondition.generateId(agreementId, hashValuesLock2) await conditionStoreManager.createCondition( @@ -280,7 +321,7 @@ function testMultiEscrow(EscrowPaymentCondition, LockPaymentCondition, nft) { const lockConditionId = conditionLockId - const hashValues = await escrowPayment.hashValues( + const hashValues = await escrowPayment.hashWrap( did, amounts, receivers, @@ -312,9 +353,9 @@ function testMultiEscrow(EscrowPaymentCondition, LockPaymentCondition, nft) { [conditionLockId, conditionLockId2]) ) - await lockPaymentCondition.fulfill(agreementId, did, escrowPayment.address, token.address, amounts, receivers) + await lockPaymentCondition.fulfillWrap(agreementId, did, escrowPayment.address, token.address, amounts, receivers) - await assert.isRejected(escrowPayment.fulfill( + await assert.isRejected(escrowPayment.fulfillWrap( agreementId, did, amounts, @@ -330,7 +371,7 @@ function testMultiEscrow(EscrowPaymentCondition, LockPaymentCondition, nft) { assert.strictEqual(await getBalance(token, lockPaymentCondition.address), 0) assert.strictEqual(await getBalance(token, escrowPayment.address), balanceBefore + amounts[0]) - const result = await escrowPayment.fulfill( + const result = await escrowPayment.fulfillWrap( agreementId, did, amounts, @@ -360,7 +401,7 @@ function testMultiEscrow(EscrowPaymentCondition, LockPaymentCondition, nft) { }) } -testMultiEscrow(EscrowPaymentCondition, LockPaymentCondition, false) -testMultiEscrow(NFTEscrowPaymentCondition, NFTMarkedLockCondition, true) -testMultiEscrow(NFT721EscrowPaymentCondition, NFT721MarkedLockCondition, true) +testMultiEscrow(EscrowPaymentCondition, LockPaymentCondition, false, 10, 12) +testMultiEscrow(NFTEscrowPaymentCondition, NFTMarkedLockCondition, true, 10, 12) +testMultiEscrow(NFT721EscrowPaymentCondition, NFT721MarkedLockCondition, true, 1, 0) From 714e50aebe4d80829b9985446eddfbe07de28f20 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Sami=20M=C3=A4kel=C3=A4?= Date: Wed, 2 Feb 2022 17:21:57 +0200 Subject: [PATCH 24/32] minting ... --- .../rewards/MultiEscrowPayment.Test.js | 98 ++++++++++++++----- 1 file changed, 71 insertions(+), 27 deletions(-) diff --git a/test/unit/conditions/rewards/MultiEscrowPayment.Test.js b/test/unit/conditions/rewards/MultiEscrowPayment.Test.js index 0afe7344..ec3f11d4 100644 --- a/test/unit/conditions/rewards/MultiEscrowPayment.Test.js +++ b/test/unit/conditions/rewards/MultiEscrowPayment.Test.js @@ -18,6 +18,9 @@ const EscrowPaymentCondition = artifacts.require('MultiEscrowPaymentCondition') const NFTEscrowPaymentCondition = artifacts.require('NFTEscrowPaymentCondition') const NFT721EscrowPaymentCondition = artifacts.require('NFT721EscrowPaymentCondition') +const NFT = artifacts.require('NFTUpgradeable') +const NFT721 = artifacts.require('NFT721Upgradeable') + const constants = require('../../../helpers/constants.js') const { getBalance } = require('../../../helpers/getBalance.js') const testUtils = require('../../../helpers/utils.js') @@ -29,12 +32,12 @@ function tokenLockWrapper(contract) { contract.fulfillWrap = (agreementId, did, escrowPaymentAddress, tokenAddress, amounts, receivers) => { return contract.fulfill(agreementId, did, escrowPaymentAddress, tokenAddress, amounts, receivers) } - contract.initWrap = (owner, conditionStoreManagerAddress, _didRegistryAddress, deployer) => { + contract.initWrap = (owner, conditionStoreManagerAddress, didRegistryAddress, args) => { return contract.initialize( owner, conditionStoreManagerAddress, didRegistryAddress, - { from: deployer } + args ) } @@ -48,11 +51,11 @@ function nftLockWrapper(contract) { contract.fulfillWrap = (agreementId, did, escrowPaymentAddress, tokenAddress, amounts, receivers) => { return contract.fulfill(agreementId, did, escrowPaymentAddress, amounts[0], receivers[0], tokenAddress) } - contract.initWrap = (owner, conditionStoreManagerAddress, _didRegistryAddress, deployer) => { + contract.initWrap = (owner, conditionStoreManagerAddress, _didRegistryAddress, args) => { return contract.initialize( owner, conditionStoreManagerAddress, - { from: deployer } + args ) } return contract @@ -98,12 +101,60 @@ function nftEscrowWrapper(contract) { return contract } -function testMultiEscrow(EscrowPaymentCondition, LockPaymentCondition, nft, amount1, amount2) { +function tokenTokenWrapper(contract) { + contract.initWrap = async (a, b, _registry) => { + return contract.initialize(a, b) + } + contract.getBalance = (addr) => { + return getBalance(contract, addr) + } + contract.mintWrap = async (_registry, target, amount, from) => { + return contract.mint(target, amount, { from }) + } + return contract +} + +function nftTokenWrapper(contract) { + contract.initWrap = async (_a, _b, registry, owner) => { + await contract.initialize('') + await contract.addMinter(registry.address) + await contract.setProxyApproval(registry.address, true) + const didSeed = testUtils.generateId() + const checksum = testUtils.generateId() + contract.did = await didRegistry.hashDID(didSeed, artist) + await registry.registerMintableDID( + didSeed, checksum, [], url, 1000, 0, constants.activities.GENERATED, '', { from: owner } + ) + } + contract.getBalance = async (addr) => { + return web3.utils.toDecimal(await contract.balanceOf(addr, contract.did)) + } + contract.mintWrap = async (registry, amount, from) => { + await registry.mint(contract.did, amount, { from }) + } + return contract +} + +function nft721TokenWrapper(contract) { + contract.initWrap = async (_a, _b, registry, _owner) => { + await contract.initialize() + await contract.addMinter(registry) + await contract.setProxyApproval(registry, true) + } + contract.getBalance = async (addr) => { + let res = await contract.ownerOf(contract.did) + return res === addr ? 1 : 0 + } + return contract +} + +function testMultiEscrow(EscrowPaymentCondition, LockPaymentCondition, Token, nft, amount1, amount2) { contract('MultiEscrowPaymentCondition contract', (accounts) => { const single = nft ? (a => a[0]) : (a => a) const lockWrapper = nft ? nftLockWrapper : tokenLockWrapper const escrowWrapper = nft ? nftEscrowWrapper : tokenEscrowWrapper + const tokenWrapper = nft ? (amount2 == 0 ? nft721TokenWrapper : nftTokenWrapper) : tokenTokenWrapper let conditionStoreManager let token @@ -144,27 +195,20 @@ function testMultiEscrow(EscrowPaymentCondition, LockPaymentCondition, nft, amou { from: owner } ) + token = tokenWrapper(await Token.new()) + didRegistry = await DIDRegistry.new() - await didRegistry.initialize(owner, constants.address.zero, constants.address.zero) + await didRegistry.initialize(owner, token.address, token.address) - token = await NeverminedToken.new() - await token.initialize(owner, owner) + await token.initWrap(owner, owner, didRegistry) lockPaymentCondition = lockWrapper(await LockPaymentCondition.new()) - if (nft) { - await lockPaymentCondition.initialize( - owner, - conditionStoreManager.address, - { from: deployer } - ) - } else { - await lockPaymentCondition.initialize( - owner, - conditionStoreManager.address, - didRegistry.address, - { from: deployer } - ) - } + await lockPaymentCondition.initWrap( + owner, + conditionStoreManager.address, + didRegistry.address, + { from: deployer } + ) escrowPayment = escrowWrapper(await EscrowPaymentCondition.new()) await escrowPayment.initialize( @@ -196,7 +240,7 @@ function testMultiEscrow(EscrowPaymentCondition, LockPaymentCondition, nft, amou const receivers2 = [accounts[2]] const amounts2 = [amount2] const totalAmount = amounts[0] + amounts2[0] - const balanceBefore = await getBalance(token, escrowPayment.address) + const balanceBefore = await token.getBalance(escrowPayment.address) const hashValuesLock = await lockPaymentCondition.hashWrap(did, escrowPayment.address, token.address, amounts, receivers) const conditionLockId = await lockPaymentCondition.generateId(agreementId, hashValuesLock) @@ -228,7 +272,7 @@ function testMultiEscrow(EscrowPaymentCondition, LockPaymentCondition, nft, amou escrowConditionId, escrowPayment.address) - await token.mint(sender, totalAmount, { from: owner }) + await token.mintWrap(didRegistry, sender, totalAmount, owner) await token.approve( lockPaymentCondition.address, totalAmount, @@ -401,7 +445,7 @@ function testMultiEscrow(EscrowPaymentCondition, LockPaymentCondition, nft, amou }) } -testMultiEscrow(EscrowPaymentCondition, LockPaymentCondition, false, 10, 12) -testMultiEscrow(NFTEscrowPaymentCondition, NFTMarkedLockCondition, true, 10, 12) -testMultiEscrow(NFT721EscrowPaymentCondition, NFT721MarkedLockCondition, true, 1, 0) +testMultiEscrow(EscrowPaymentCondition, LockPaymentCondition, NeverminedToken, false, 10, 12) +testMultiEscrow(NFTEscrowPaymentCondition, NFTMarkedLockCondition, NFT, true, 10, 12) +testMultiEscrow(NFT721EscrowPaymentCondition, NFT721MarkedLockCondition, NFT721, true, 1, 0) From 074bbd21f8c86879d09a17b7d09feab282c1b60f Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Sami=20M=C3=A4kel=C3=A4?= Date: Wed, 2 Feb 2022 17:22:13 +0200 Subject: [PATCH 25/32] minting ... --- test/unit/conditions/rewards/MultiEscrowPayment.Test.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/test/unit/conditions/rewards/MultiEscrowPayment.Test.js b/test/unit/conditions/rewards/MultiEscrowPayment.Test.js index ec3f11d4..dedf2653 100644 --- a/test/unit/conditions/rewards/MultiEscrowPayment.Test.js +++ b/test/unit/conditions/rewards/MultiEscrowPayment.Test.js @@ -121,7 +121,7 @@ function nftTokenWrapper(contract) { await contract.setProxyApproval(registry.address, true) const didSeed = testUtils.generateId() const checksum = testUtils.generateId() - contract.did = await didRegistry.hashDID(didSeed, artist) + contract.did = await registry.hashDID(didSeed, artist) await registry.registerMintableDID( didSeed, checksum, [], url, 1000, 0, constants.activities.GENERATED, '', { from: owner } ) From 358032e18c659f592c6caeeac609e3ea3994ff41 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Sami=20M=C3=A4kel=C3=A4?= Date: Wed, 2 Feb 2022 17:48:28 +0200 Subject: [PATCH 26/32] test works for erc 1155 --- .../rewards/NFTEscrowPaymentCondition.sol | 2 +- .../rewards/MultiEscrowPayment.Test.js | 90 ++++++++++++------- 2 files changed, 59 insertions(+), 33 deletions(-) diff --git a/contracts/conditions/rewards/NFTEscrowPaymentCondition.sol b/contracts/conditions/rewards/NFTEscrowPaymentCondition.sol index 8a1da7e5..22efd6d1 100644 --- a/contracts/conditions/rewards/NFTEscrowPaymentCondition.sol +++ b/contracts/conditions/rewards/NFTEscrowPaymentCondition.sol @@ -182,7 +182,7 @@ contract NFTEscrowPaymentCondition is Reward, INFTEscrow, Common, ReentrancyGuar allFulfilled = false; } if (cur == ConditionStoreLibrary.ConditionState.Aborted) { - someAborted = false; + someAborted = true; } } diff --git a/test/unit/conditions/rewards/MultiEscrowPayment.Test.js b/test/unit/conditions/rewards/MultiEscrowPayment.Test.js index dedf2653..460936cc 100644 --- a/test/unit/conditions/rewards/MultiEscrowPayment.Test.js +++ b/test/unit/conditions/rewards/MultiEscrowPayment.Test.js @@ -111,26 +111,41 @@ function tokenTokenWrapper(contract) { contract.mintWrap = async (_registry, target, amount, from) => { return contract.mint(target, amount, { from }) } + contract.makeDID = (sender, registry) => { + return testUtils.generateId() + } + contract.approveWrap = (addr, amount, args) => { + return contract.approve(addr, amount, args) + } return contract } function nftTokenWrapper(contract) { - contract.initWrap = async (_a, _b, registry, owner) => { + contract.initWrap = async (owner, _b, registry) => { await contract.initialize('') await contract.addMinter(registry.address) await contract.setProxyApproval(registry.address, true) + } + contract.getBalance = async (addr) => { + if (!contract.did) { + return 0 + } + return web3.utils.toDecimal(await contract.balanceOf(addr, contract.did)) + } + contract.makeDID = async (sender, registry) => { const didSeed = testUtils.generateId() const checksum = testUtils.generateId() - contract.did = await registry.hashDID(didSeed, artist) + contract.did = await registry.hashDID(didSeed, sender) await registry.registerMintableDID( - didSeed, checksum, [], url, 1000, 0, constants.activities.GENERATED, '', { from: owner } + didSeed, checksum, [], '', 1000, 0, constants.activities.GENERATED, '', { from: sender } ) + return contract.did } - contract.getBalance = async (addr) => { - return web3.utils.toDecimal(await contract.balanceOf(addr, contract.did)) + contract.mintWrap = async (registry, target, amount, from) => { + await registry.mint(contract.did, amount, { from: target }) } - contract.mintWrap = async (registry, amount, from) => { - await registry.mint(contract.did, amount, { from }) + contract.approveWrap = (addr, amount, args) => { + return contract.setApprovalForAll(addr, true, args) } return contract } @@ -233,7 +248,6 @@ function testMultiEscrow(EscrowPaymentCondition, LockPaymentCondition, Token, nf describe('fulfill with two release conditions', () => { it('should fulfill if both are fulfilled', async () => { const agreementId = testUtils.generateId() - const did = testUtils.generateId() const sender = accounts[0] const receivers = [accounts[1]] const amounts = [amount1] @@ -242,6 +256,7 @@ function testMultiEscrow(EscrowPaymentCondition, LockPaymentCondition, Token, nf const totalAmount = amounts[0] + amounts2[0] const balanceBefore = await token.getBalance(escrowPayment.address) + const did = await token.makeDID(sender, didRegistry) const hashValuesLock = await lockPaymentCondition.hashWrap(did, escrowPayment.address, token.address, amounts, receivers) const conditionLockId = await lockPaymentCondition.generateId(agreementId, hashValuesLock) const hashValuesLock2 = await lockPaymentCondition.hashWrap(did, escrowPayment.address, token.address, amounts2, receivers2) @@ -273,7 +288,7 @@ function testMultiEscrow(EscrowPaymentCondition, LockPaymentCondition, Token, nf escrowPayment.address) await token.mintWrap(didRegistry, sender, totalAmount, owner) - await token.approve( + await token.approveWrap( lockPaymentCondition.address, totalAmount, { from: sender }) @@ -304,8 +319,8 @@ function testMultiEscrow(EscrowPaymentCondition, LockPaymentCondition, Token, nf await lockPaymentCondition.fulfillWrap(agreementId, did, escrowPayment.address, token.address, amounts2, receivers2) - assert.strictEqual(await getBalance(token, lockPaymentCondition.address), 0) - assert.strictEqual(await getBalance(token, escrowPayment.address), balanceBefore + totalAmount) + assert.strictEqual(await token.getBalance(lockPaymentCondition.address), 0) + assert.strictEqual(await token.getBalance(escrowPayment.address), balanceBefore + totalAmount) const result = await escrowPayment.fulfill( agreementId, @@ -326,25 +341,31 @@ function testMultiEscrow(EscrowPaymentCondition, LockPaymentCondition, Token, nf const eventArgs = testUtils.getEventArgsFromTx(result, 'Fulfilled') expect(eventArgs._agreementId).to.equal(agreementId) expect(eventArgs._conditionId).to.equal(escrowConditionId) - expect(eventArgs._receivers[0]).to.equal(receivers[0]) - expect(eventArgs._amounts[0].toNumber()).to.equal(amounts[0]) - - assert.strictEqual(await getBalance(token, escrowPayment.address), amounts2[0]) - assert.strictEqual(await getBalance(token, receivers[0]), amounts[0]) + if (nft) { + expect(eventArgs._receivers).to.equal(receivers[0]) + expect(eventArgs._amounts.toNumber()).to.equal(amounts[0]) + } else { + expect(eventArgs._receivers[0]).to.equal(receivers[0]) + expect(eventArgs._amounts[0].toNumber()).to.equal(amounts[0]) + } + + assert.strictEqual(await token.getBalance(escrowPayment.address), amounts2[0]) + assert.strictEqual(await token.getBalance(receivers[0]), amounts[0]) }) it('should cancel if conditions were aborted', async () => { const agreementId = testUtils.generateId() - const did = testUtils.generateId() const sender = accounts[0] const receivers = [accounts[1]] const amounts = [amount1] const receivers2 = [accounts[2]] const amounts2 = [amount2] const totalAmount = amounts[0] + amounts2[0] - const balanceBefore = await getBalance(token, escrowPayment.address) - const senderBefore = await getBalance(token, sender) - const receiverBefore = await getBalance(token, receivers[0]) + const did = await token.makeDID(sender, didRegistry) + + const balanceBefore = await token.getBalance(escrowPayment.address) + const senderBefore = await token.getBalance(sender) + const receiverBefore = await token.getBalance(receivers[0]) const hashValuesLock = await lockPaymentCondition.hashWrap(did, escrowPayment.address, token.address, amounts, receivers) const conditionLockId = await lockPaymentCondition.generateId(agreementId, hashValuesLock) @@ -380,13 +401,13 @@ function testMultiEscrow(EscrowPaymentCondition, LockPaymentCondition, Token, nf escrowConditionId, escrowPayment.address) - await token.mint(sender, totalAmount, { from: owner }) - await token.approve( + await token.mintWrap(didRegistry, sender, totalAmount, owner) + await token.approveWrap( lockPaymentCondition.address, totalAmount, { from: sender }) - await assert.isRejected(escrowPayment.fulfill( + await assert.isRejected(escrowPayment.fulfillWrap( agreementId, did, amounts, @@ -412,8 +433,8 @@ function testMultiEscrow(EscrowPaymentCondition, LockPaymentCondition, Token, nf await lockPaymentCondition.abortByTimeOut(conditionLockId2) - assert.strictEqual(await getBalance(token, lockPaymentCondition.address), 0) - assert.strictEqual(await getBalance(token, escrowPayment.address), balanceBefore + amounts[0]) + assert.strictEqual(await token.getBalance(lockPaymentCondition.address), 0) + assert.strictEqual(await token.getBalance(escrowPayment.address), balanceBefore + amounts[0]) const result = await escrowPayment.fulfillWrap( agreementId, @@ -434,12 +455,17 @@ function testMultiEscrow(EscrowPaymentCondition, LockPaymentCondition, Token, nf const eventArgs = testUtils.getEventArgsFromTx(result, 'Fulfilled') expect(eventArgs._agreementId).to.equal(agreementId) expect(eventArgs._conditionId).to.equal(escrowConditionId) - expect(eventArgs._receivers[0]).to.equal(sender) - expect(eventArgs._amounts[0].toNumber()).to.equal(amounts[0]) - - assert.strictEqual(await getBalance(token, escrowPayment.address), balanceBefore) - assert.strictEqual(await getBalance(token, receivers[0]), receiverBefore) - assert.strictEqual(await getBalance(token, sender), senderBefore + totalAmount) + if (nft) { + expect(eventArgs._receivers).to.equal(sender) + expect(eventArgs._amounts.toNumber()).to.equal(amounts[0]) + } else { + expect(eventArgs._receivers[0]).to.equal(sender) + expect(eventArgs._amounts[0].toNumber()).to.equal(amounts[0]) + } + + assert.strictEqual(await token.getBalance(escrowPayment.address), balanceBefore) + assert.strictEqual(await token.getBalance(receivers[0]), receiverBefore) + assert.strictEqual(await token.getBalance(sender), senderBefore + totalAmount) }) }) }) @@ -447,5 +473,5 @@ function testMultiEscrow(EscrowPaymentCondition, LockPaymentCondition, Token, nf testMultiEscrow(EscrowPaymentCondition, LockPaymentCondition, NeverminedToken, false, 10, 12) testMultiEscrow(NFTEscrowPaymentCondition, NFTMarkedLockCondition, NFT, true, 10, 12) -testMultiEscrow(NFT721EscrowPaymentCondition, NFT721MarkedLockCondition, NFT721, true, 1, 0) +// testMultiEscrow(NFT721EscrowPaymentCondition, NFT721MarkedLockCondition, NFT721, true, 1, 0) From 73e71406baea19e4b8dcd87273a1288c1efe6c9e Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Sami=20M=C3=A4kel=C3=A4?= Date: Wed, 2 Feb 2022 18:01:40 +0200 Subject: [PATCH 27/32] multi escrow tests working --- .../rewards/NFT721EscrowPaymentCondition.sol | 17 +++++++- .../rewards/MultiEscrowPayment.Test.js | 40 ++++++++++++++----- 2 files changed, 45 insertions(+), 12 deletions(-) diff --git a/contracts/conditions/rewards/NFT721EscrowPaymentCondition.sol b/contracts/conditions/rewards/NFT721EscrowPaymentCondition.sol index 1f81bf1d..004df5f9 100644 --- a/contracts/conditions/rewards/NFT721EscrowPaymentCondition.sol +++ b/contracts/conditions/rewards/NFT721EscrowPaymentCondition.sol @@ -9,7 +9,7 @@ import '../../Common.sol'; import '../ConditionStoreLibrary.sol'; import '@openzeppelin/contracts-upgradeable/token/ERC721/IERC721Upgradeable.sol'; import '@openzeppelin/contracts-upgradeable/security/ReentrancyGuardUpgradeable.sol'; - +import '@openzeppelin/contracts-upgradeable/token/ERC721/IERC721ReceiverUpgradeable.sol'; /** * @title Escrow Payment Condition * @author Keyko @@ -20,7 +20,7 @@ import '@openzeppelin/contracts-upgradeable/security/ReentrancyGuardUpgradeable. * can release reward if lock and release conditions * are fulfilled. */ -contract NFT721EscrowPaymentCondition is Reward, INFTEscrow, Common, ReentrancyGuardUpgradeable { +contract NFT721EscrowPaymentCondition is Reward, INFTEscrow, Common, IERC721ReceiverUpgradeable, ReentrancyGuardUpgradeable { bytes32 constant public CONDITION_TYPE = keccak256('NFTEscrowPayment'); @@ -242,4 +242,17 @@ contract NFT721EscrowPaymentCondition is Reward, INFTEscrow, Common, ReentrancyG ); } + function onERC721Received( + address, + address, + uint256, + bytes memory + ) + public + virtual + override + returns (bytes4) + { + return this.onERC721Received.selector; + } } diff --git a/test/unit/conditions/rewards/MultiEscrowPayment.Test.js b/test/unit/conditions/rewards/MultiEscrowPayment.Test.js index 460936cc..eb8524c5 100644 --- a/test/unit/conditions/rewards/MultiEscrowPayment.Test.js +++ b/test/unit/conditions/rewards/MultiEscrowPayment.Test.js @@ -1,6 +1,6 @@ /* eslint-env mocha */ /* eslint-disable no-console */ -/* global artifacts, contract, describe, it, expect */ +/* global artifacts, contract, describe, it, expect, web3 */ const chai = require('chai') const { assert } = chai const chaiAsPromised = require('chai-as-promised') @@ -153,23 +153,44 @@ function nftTokenWrapper(contract) { function nft721TokenWrapper(contract) { contract.initWrap = async (_a, _b, registry, _owner) => { await contract.initialize() - await contract.addMinter(registry) - await contract.setProxyApproval(registry, true) + await contract.addMinter(registry.address) + await contract.setProxyApproval(registry.address, true) } contract.getBalance = async (addr) => { - let res = await contract.ownerOf(contract.did) - return res === addr ? 1 : 0 + if (!contract.did) { + return 0 + } + try { + const res = await contract.ownerOf(contract.did) + return res === addr ? 1 : 0 + } catch (e) { + return 0 + } + } + contract.makeDID = async (sender, registry) => { + const didSeed = testUtils.generateId() + const checksum = testUtils.generateId() + contract.did = await registry.hashDID(didSeed, sender) + await registry.registerMintableDID721( + didSeed, checksum, [], '', 0, false, constants.activities.GENERATED, '', { from: sender } + ) + return contract.did + } + contract.mintWrap = async (registry, target, amount, from) => { + await registry.mint721(contract.did, { from: target }) + } + contract.approveWrap = (addr, amount, args) => { + return contract.setApprovalForAll(addr, true, args) } return contract } function testMultiEscrow(EscrowPaymentCondition, LockPaymentCondition, Token, nft, amount1, amount2) { contract('MultiEscrowPaymentCondition contract', (accounts) => { - - const single = nft ? (a => a[0]) : (a => a) + const single = nft ? a => a[0] : a => a const lockWrapper = nft ? nftLockWrapper : tokenLockWrapper const escrowWrapper = nft ? nftEscrowWrapper : tokenEscrowWrapper - const tokenWrapper = nft ? (amount2 == 0 ? nft721TokenWrapper : nftTokenWrapper) : tokenTokenWrapper + const tokenWrapper = nft ? (amount2 === 0 ? nft721TokenWrapper : nftTokenWrapper) : tokenTokenWrapper let conditionStoreManager let token @@ -473,5 +494,4 @@ function testMultiEscrow(EscrowPaymentCondition, LockPaymentCondition, Token, nf testMultiEscrow(EscrowPaymentCondition, LockPaymentCondition, NeverminedToken, false, 10, 12) testMultiEscrow(NFTEscrowPaymentCondition, NFTMarkedLockCondition, NFT, true, 10, 12) -// testMultiEscrow(NFT721EscrowPaymentCondition, NFT721MarkedLockCondition, NFT721, true, 1, 0) - +testMultiEscrow(NFT721EscrowPaymentCondition, NFT721MarkedLockCondition, NFT721, true, 1, 0) From 0d471ed261f1e37acb7812892e3261a6657e9ea6 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Sami=20M=C3=A4kel=C3=A4?= Date: Wed, 2 Feb 2022 18:09:56 +0200 Subject: [PATCH 28/32] added test for marked lock condition --- .../conditions/NFTMarkedLockCondition.Test.js | 228 ++++++++++++++++++ 1 file changed, 228 insertions(+) create mode 100644 test/unit/conditions/NFTMarkedLockCondition.Test.js diff --git a/test/unit/conditions/NFTMarkedLockCondition.Test.js b/test/unit/conditions/NFTMarkedLockCondition.Test.js new file mode 100644 index 00000000..9ef51c50 --- /dev/null +++ b/test/unit/conditions/NFTMarkedLockCondition.Test.js @@ -0,0 +1,228 @@ +/* eslint-env mocha */ +/* eslint-disable no-console */ +/* global artifacts, contract, describe, it, expect */ +const chai = require('chai') +const { assert } = chai +const chaiAsPromised = require('chai-as-promised') +chai.use(chaiAsPromised) + +const EpochLibrary = artifacts.require('EpochLibrary') +const ConditionStoreManager = artifacts.require('ConditionStoreManager') +const DIDRegistryLibrary = artifacts.require('DIDRegistryLibrary') +const DIDRegistry = artifacts.require('DIDRegistry') +const NFTLockCondition = artifacts.require('NFTMarkedLockCondition') +const NFT = artifacts.require('NFTUpgradeable') + +const constants = require('../../helpers/constants.js') +const testUtils = require('../../helpers/utils.js') + +contract('NFTMarkedLockCondition', (accounts) => { + let conditionStoreManager + let didRegistry + let lockCondition + let nft + + const receiver = accounts[2] + const owner = accounts[1] + const createRole = accounts[0] + const url = constants.registry.url + const checksum = constants.bytes32.one + const amount = 10 + + before(async () => { + const epochLibrary = await EpochLibrary.new() + await ConditionStoreManager.link(epochLibrary) + const didRegistryLibrary = await DIDRegistryLibrary.new() + await DIDRegistry.link(didRegistryLibrary) + }) + + beforeEach(async () => { + await setupTest() + }) + + async function setupTest() { + if (!didRegistry) { + nft = await NFT.new() + await nft.initialize('') + + didRegistry = await DIDRegistry.new() + await didRegistry.initialize(owner, nft.address, constants.address.zero) + await nft.addMinter(didRegistry.address) + } + if (!conditionStoreManager) { + conditionStoreManager = await ConditionStoreManager.new() + await conditionStoreManager.initialize( + owner, + { from: owner } + ) + + await conditionStoreManager.delegateCreateRole( + createRole, + { from: owner } + ) + + lockCondition = await NFTLockCondition.new() + + await lockCondition.initialize( + owner, + conditionStoreManager.address, + { from: createRole } + ) + } + } + + describe('fulfill correctly', () => { + it('should fulfill if conditions exist for account address', async () => { + const didSeed = testUtils.generateId() + const did = await didRegistry.hashDID(didSeed, accounts[0]) + + const agreementId = testUtils.generateId() + const lockAddress = lockCondition.address + + // register DID + await didRegistry.registerMintableDID( + didSeed, checksum, [], url, amount, 0, constants.activities.GENERATED, '') + await didRegistry.mint(did, amount) + await nft.setApprovalForAll(lockCondition.address, true) + + const hashValues = await lockCondition.hashValues(did, lockAddress, amount, receiver, nft.address) + const conditionId = await lockCondition.generateId(agreementId, hashValues) + + await conditionStoreManager.createCondition( + conditionId, + lockCondition.address) + + const result = await lockCondition.fulfill(agreementId, did, lockAddress, amount, receiver, nft.address) + const { state } = await conditionStoreManager.getCondition(conditionId) + assert.strictEqual(state.toNumber(), constants.condition.state.fulfilled) + const nftBalance = await nft.balanceOf(lockCondition.address, did) + assert.strictEqual(nftBalance.toNumber(), amount) + + testUtils.assertEmitted(result, 1, 'Fulfilled') + const eventArgs = testUtils.getEventArgsFromTx(result, 'Fulfilled') + expect(eventArgs._agreementId).to.equal(agreementId) + expect(eventArgs._did).to.equal(did) + expect(eventArgs._conditionId).to.equal(conditionId) + expect(eventArgs._lockAddress).to.equal(lockAddress) + expect(eventArgs._amount.toNumber()).to.equal(amount) + }) + }) + + describe('trying to fulfill but is invalid', () => { + it('should not fulfill if conditions do not exist', async () => { + const agreementId = testUtils.generateId() + const didSeed = testUtils.generateId() + const did = await didRegistry.hashDID(didSeed, accounts[0]) + + const lockAddress = accounts[2] + + // register DID + await didRegistry.registerMintableDID( + didSeed, checksum, [], url, amount, 0, true, constants.activities.GENERATED, '') + + await nft.setApprovalForAll(lockCondition.address, true) + + await assert.isRejected( + lockCondition.fulfill(agreementId, did, lockAddress, amount, receiver, nft.address), + constants.acl.error.invalidUpdateRole + ) + }) + + it('out of balance should fail to fulfill', async () => { + const agreementId = testUtils.generateId() + const didSeed = testUtils.generateId() + const did = await didRegistry.hashDID(didSeed, accounts[0]) + + const lockAddress = accounts[2] + + // register DID + await didRegistry.registerMintableDID( + didSeed, checksum, [], url, amount, 0, constants.activities.GENERATED, '') + await didRegistry.mint(did, amount) + await nft.setApprovalForAll(lockCondition.address, true) + + const hashValues = await lockCondition.hashValues(did, lockAddress, amount, receiver, nft.address) + const conditionId = await lockCondition.generateId(agreementId, hashValues) + + await conditionStoreManager.createCondition( + conditionId, + lockCondition.address) + + await assert.isRejected( + lockCondition.fulfill(agreementId, did, lockAddress, amount + 1, receiver, nft.address), + undefined + ) + }) + + it('right transfer should fail to fulfill if conditions already fulfilled', async () => { + const agreementId = testUtils.generateId() + const didSeed = testUtils.generateId() + const did = await didRegistry.hashDID(didSeed, accounts[0]) + + const lockAddress = accounts[2] + + // register DID + await didRegistry.registerMintableDID( + didSeed, checksum, [], url, amount, 0, constants.activities.GENERATED, '') + await didRegistry.mint(did, amount) + await nft.setApprovalForAll(lockCondition.address, true) + + const hashValues = await lockCondition.hashValues(did, lockAddress, amount, receiver, nft.address) + const conditionId = await lockCondition.generateId(agreementId, hashValues) + + await conditionStoreManager.createCondition( + conditionId, + lockCondition.address + ) + + await lockCondition.fulfill(agreementId, did, lockAddress, amount, receiver, nft.address) + assert.strictEqual( + (await conditionStoreManager.getConditionState(conditionId)).toNumber(), + constants.condition.state.fulfilled + ) + + await assert.isRejected( + lockCondition.fulfill(agreementId, did, lockAddress, amount, receiver, nft.address), + undefined + ) + + assert.strictEqual( + (await conditionStoreManager.getConditionState(conditionId)).toNumber(), + constants.condition.state.fulfilled + ) + }) + + it('should fail to fulfill if conditions has different type ref', async () => { + const agreementId = testUtils.generateId() + const didSeed = testUtils.generateId() + const did = await didRegistry.hashDID(didSeed, accounts[0]) + + const lockAddress = accounts[2] + + // register DID + await didRegistry.registerMintableDID( + didSeed, checksum, [], url, amount, 0, constants.activities.GENERATED, '') + await didRegistry.mint(did, amount) + await nft.setApprovalForAll(lockCondition.address, true) + + const hashValues = await lockCondition.hashValues(did, lockAddress, amount, receiver, nft.address) + const conditionId = await lockCondition.generateId(agreementId, hashValues) + + await conditionStoreManager.createCondition( + conditionId, + lockCondition.address + ) + + await conditionStoreManager.delegateUpdateRole( + conditionId, + createRole, + { from: owner } + ) + + await assert.isRejected( + lockCondition.fulfill(agreementId, did, lockAddress, amount, receiver, nft.address), + constants.acl.error.invalidUpdateRole + ) + }) + }) +}) From 76d34af518416acd99dbfae55fa60b03563f9e48 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Sami=20M=C3=A4kel=C3=A4?= Date: Wed, 2 Feb 2022 18:54:07 +0200 Subject: [PATCH 29/32] dedup snark generation in tests --- .../agreement/AccessProofAgreement.Test.js | 70 ++----------------- test/int/nft/NFTAccessProofAgreement.Test.js | 59 +--------------- test/int/nft/NFTAccessSwapAgreement.Test.js | 60 +--------------- .../NFTSalesWithAccessProofAgreement.Test.js | 60 +--------------- 4 files changed, 14 insertions(+), 235 deletions(-) diff --git a/test/int/agreement/AccessProofAgreement.Test.js b/test/int/agreement/AccessProofAgreement.Test.js index 7b198fe4..a21a077f 100644 --- a/test/int/agreement/AccessProofAgreement.Test.js +++ b/test/int/agreement/AccessProofAgreement.Test.js @@ -16,15 +16,12 @@ const { getBalance } = require('../../helpers/getBalance.js') const increaseTime = require('../../helpers/increaseTime.js') const testUtils = require('../../helpers/utils') const mimcdecrypt = require('../../helpers/mimcdecrypt').decrypt - -const poseidon = require('circomlib').poseidon const babyJub = require('circomlib').babyJub -const mimcjs = require('circomlib').mimcsponge +const poseidon = require('circomlib').poseidon const ZqField = require('ffjavascript').ZqField const Scalar = require('ffjavascript').Scalar const F = new ZqField(Scalar.fromString('21888242871839275222246405745257275088548364400416034343698204186575808495617')) -const snarkjs = require('snarkjs') -const { unstringifyBigInts } = require('ffjavascript').utils +const { makeProof } = require('../../helpers/proofHelper') contract('Access Proof Template integration test', (accounts) => { const web3 = global.web3 @@ -101,64 +98,14 @@ contract('Access Proof Template integration test', (accounts) => { } = {}) { const orig1 = 222n const orig2 = 333n - const origHash = poseidon([orig1, orig2]) - - const did = await didRegistry.hashDID(didSeed, receivers[0]) const buyerK = 123 const providerK = 234 - const buyerPub = babyJub.mulPointEscalar(babyJub.Base8, F.e(buyerK)) - const providerPub = babyJub.mulPointEscalar(babyJub.Base8, F.e(providerK)) - - // console.log("public keys", buyer_pub, provider_pub) - - const k = babyJub.mulPointEscalar(buyerPub, F.e(providerK)) - // const k2 = babyJub.mulPointEscalar(provider_pub, F.e(buyer_k)) - - // console.log("encryption key", k) - // console.log("encryption key check", k2) - - const cipher = mimcjs.hash(orig1, orig2, k[0]) - // const plain = mimcdecrypt(cipher.xL, cipher.xR, k[0]) - // console.log('cipher', cipher, 'plain', plain) - - const snarkParams = { - buyer_x: buyerPub[0], - buyer_y: buyerPub[1], - provider_x: providerPub[0], - provider_y: providerPub[1], - xL_in: orig1, - xR_in: orig2, - cipher_xL_in: cipher.xL, - cipher_xR_in: cipher.xR, - provider_k: providerK, - hash_plain: origHash - } - - // console.log(snark_params) - const { proof } = await snarkjs.plonk.fullProve( - snarkParams, - 'circuits/keytransfer.wasm', - 'circuits/keytransfer.zkey' - ) - - const signals = [ - buyerPub[0], - buyerPub[1], - providerPub[0], - providerPub[1], - cipher.xL, - cipher.xR, - origHash - ] - - const proofSolidity = (await snarkjs.plonk.exportSolidityCallData(unstringifyBigInts(proof), signals)) + const data = await makeProof(orig1, orig2, buyerK, providerK) + const { origHash, buyerPub, providerPub } = data - const proofData = proofSolidity.split(',')[0] - // console.log("Proof: "); - // console.log(proof_solidity, proof_data); - // console.log(proof) + const did = await didRegistry.hashDID(didSeed, receivers[0]) // generate IDs from attributes const conditionIdLock = await lockPaymentCondition.generateId(agreementId, @@ -180,13 +127,6 @@ contract('Access Proof Template integration test', (accounts) => { timeOuts: [timeOutAccess, 0, 0], consumer: sender } - const data = { - origHash, - buyerPub, - providerPub, - cipher: [cipher.xL, cipher.xR], - proof: proofData - } return { agreementId, did, diff --git a/test/int/nft/NFTAccessProofAgreement.Test.js b/test/int/nft/NFTAccessProofAgreement.Test.js index d098934d..0a1e37f5 100644 --- a/test/int/nft/NFTAccessProofAgreement.Test.js +++ b/test/int/nft/NFTAccessProofAgreement.Test.js @@ -15,14 +15,7 @@ const deployConditions = require('../../helpers/deployConditions.js') const deployManagers = require('../../helpers/deployManagers.js') const testUtils = require('../../helpers/utils') -const poseidon = require('circomlib').poseidon -const babyJub = require('circomlib').babyJub -const mimcjs = require('circomlib').mimcsponge -const ZqField = require('ffjavascript').ZqField -const Scalar = require('ffjavascript').Scalar -const F = new ZqField(Scalar.fromString('21888242871839275222246405745257275088548364400416034343698204186575808495617')) -const snarkjs = require('snarkjs') -const { unstringifyBigInts } = require('ffjavascript').utils +const { makeProof } = require('../../helpers/proofHelper') contract('NFT Access Proof Template integration test', (accounts) => { const didSeed = testUtils.generateId() @@ -110,51 +103,12 @@ contract('NFT Access Proof Template integration test', (accounts) => { } = {}) { const orig1 = 222n const orig2 = 333n - const origHash = poseidon([orig1, orig2]) const buyerK = 123 const providerK = 234 - const buyerPub = babyJub.mulPointEscalar(babyJub.Base8, F.e(buyerK)) - const providerPub = babyJub.mulPointEscalar(babyJub.Base8, F.e(providerK)) - - const k = babyJub.mulPointEscalar(buyerPub, F.e(providerK)) - - const cipher = mimcjs.hash(orig1, orig2, k[0]) - - const snarkParams = { - buyer_x: buyerPub[0], - buyer_y: buyerPub[1], - provider_x: providerPub[0], - provider_y: providerPub[1], - xL_in: orig1, - xR_in: orig2, - cipher_xL_in: cipher.xL, - cipher_xR_in: cipher.xR, - provider_k: providerK, - hash_plain: origHash - } - - // console.log(snark_params) - const { proof } = await snarkjs.plonk.fullProve( - snarkParams, - 'circuits/keytransfer.wasm', - 'circuits/keytransfer.zkey' - ) - - const signals = [ - buyerPub[0], - buyerPub[1], - providerPub[0], - providerPub[1], - cipher.xL, - cipher.xR, - origHash - ] - - const proofSolidity = (await snarkjs.plonk.exportSolidityCallData(unstringifyBigInts(proof), signals)) - - const proofData = proofSolidity.split(',')[0] + const data = await makeProof(orig1, orig2, buyerK, providerK) + const { origHash, buyerPub, providerPub } = data // construct agreement const conditionIdNFTHolder = await nftHolderCondition.generateId(agreementId, @@ -172,13 +126,6 @@ contract('NFT Access Proof Template integration test', (accounts) => { timeOuts: [0, 0], accessConsumer: receiver } - const data = { - origHash, - buyerPub, - providerPub, - cipher: [cipher.xL, cipher.xR], - proof: proofData - } return { agreementId, did, diff --git a/test/int/nft/NFTAccessSwapAgreement.Test.js b/test/int/nft/NFTAccessSwapAgreement.Test.js index f3206aac..660c1c6e 100644 --- a/test/int/nft/NFTAccessSwapAgreement.Test.js +++ b/test/int/nft/NFTAccessSwapAgreement.Test.js @@ -16,14 +16,7 @@ const deployConditions = require('../../helpers/deployConditions.js') const deployManagers = require('../../helpers/deployManagers.js') const testUtils = require('../../helpers/utils') -const poseidon = require('circomlib').poseidon -const babyJub = require('circomlib').babyJub -const mimcjs = require('circomlib').mimcsponge -const ZqField = require('ffjavascript').ZqField -const Scalar = require('ffjavascript').Scalar -const F = new ZqField(Scalar.fromString('21888242871839275222246405745257275088548364400416034343698204186575808495617')) -const snarkjs = require('snarkjs') -const { unstringifyBigInts } = require('ffjavascript').utils +const { makeProof } = require('../../helpers/proofHelper') contract('NFT Sales with Access Proof Template integration test', (accounts) => { const didSeed = testUtils.generateId() @@ -113,51 +106,11 @@ contract('NFT Sales with Access Proof Template integration test', (accounts) => } = {}) { const orig1 = 222n const orig2 = 333n - const origHash = poseidon([orig1, orig2]) const buyerK = 123 const providerK = 234 - const buyerPub = babyJub.mulPointEscalar(babyJub.Base8, F.e(buyerK)) - const providerPub = babyJub.mulPointEscalar(babyJub.Base8, F.e(providerK)) - - const k = babyJub.mulPointEscalar(buyerPub, F.e(providerK)) - - const cipher = mimcjs.hash(orig1, orig2, k[0]) - - const snarkParams = { - buyer_x: buyerPub[0], - buyer_y: buyerPub[1], - provider_x: providerPub[0], - provider_y: providerPub[1], - xL_in: orig1, - xR_in: orig2, - cipher_xL_in: cipher.xL, - cipher_xR_in: cipher.xR, - provider_k: providerK, - hash_plain: origHash - } - - // console.log(snark_params) - - const { proof } = await snarkjs.plonk.fullProve( - snarkParams, - 'circuits/keytransfer.wasm', - 'circuits/keytransfer.zkey' - ) - - const signals = [ - buyerPub[0], - buyerPub[1], - providerPub[0], - providerPub[1], - cipher.xL, - cipher.xR, - origHash - ] - - const proofSolidity = (await snarkjs.plonk.exportSolidityCallData(unstringifyBigInts(proof), signals)) - - const proofData = proofSolidity.split(',')[0] + const data = await makeProof(orig1, orig2, buyerK, providerK) + const { origHash, buyerPub, providerPub } = data const conditionIdLockPayment = await lockPaymentCondition.generateId(agreementId, await lockPaymentCondition.hashValues(did, escrowCondition.address, amount, receiver, token.address)) @@ -180,13 +133,6 @@ contract('NFT Sales with Access Proof Template integration test', (accounts) => timeOuts: [0, 0, 0] } - const data = { - origHash, - buyerPub, - providerPub, - cipher: [cipher.xL, cipher.xR], - proof: proofData - } return { agreementId, did, diff --git a/test/int/nft/NFTSalesWithAccessProofAgreement.Test.js b/test/int/nft/NFTSalesWithAccessProofAgreement.Test.js index a7aa9755..c8d39feb 100644 --- a/test/int/nft/NFTSalesWithAccessProofAgreement.Test.js +++ b/test/int/nft/NFTSalesWithAccessProofAgreement.Test.js @@ -16,14 +16,7 @@ const deployConditions = require('../../helpers/deployConditions.js') const deployManagers = require('../../helpers/deployManagers.js') const testUtils = require('../../helpers/utils') -const poseidon = require('circomlib').poseidon -const babyJub = require('circomlib').babyJub -const mimcjs = require('circomlib').mimcsponge -const ZqField = require('ffjavascript').ZqField -const Scalar = require('ffjavascript').Scalar -const F = new ZqField(Scalar.fromString('21888242871839275222246405745257275088548364400416034343698204186575808495617')) -const snarkjs = require('snarkjs') -const { unstringifyBigInts } = require('ffjavascript').utils +const { makeProof } = require('../../helpers/proofHelper') const { getBalance } = require('../../helpers/getBalance.js') @@ -128,52 +121,12 @@ contract('NFT Sales with Access Proof Template integration test', (accounts) => } = {}) { const orig1 = 222n const orig2 = 333n - const origHash = poseidon([orig1, orig2]) const buyerK = 123 const providerK = 234 - const buyerPub = babyJub.mulPointEscalar(babyJub.Base8, F.e(buyerK)) - const providerPub = babyJub.mulPointEscalar(babyJub.Base8, F.e(providerK)) - - const k = babyJub.mulPointEscalar(buyerPub, F.e(providerK)) - - const cipher = mimcjs.hash(orig1, orig2, k[0]) - - const snarkParams = { - buyer_x: buyerPub[0], - buyer_y: buyerPub[1], - provider_x: providerPub[0], - provider_y: providerPub[1], - xL_in: orig1, - xR_in: orig2, - cipher_xL_in: cipher.xL, - cipher_xR_in: cipher.xR, - provider_k: providerK, - hash_plain: origHash - } - - // console.log(snark_params) - - const { proof } = await snarkjs.plonk.fullProve( - snarkParams, - 'circuits/keytransfer.wasm', - 'circuits/keytransfer.zkey' - ) - - const signals = [ - buyerPub[0], - buyerPub[1], - providerPub[0], - providerPub[1], - cipher.xL, - cipher.xR, - origHash - ] - - const proofSolidity = (await snarkjs.plonk.exportSolidityCallData(unstringifyBigInts(proof), signals)) - - const proofData = proofSolidity.split(',')[0] + const data = await makeProof(orig1, orig2, buyerK, providerK) + const { origHash, buyerPub, providerPub } = data const conditionIdLockPayment = await lockPaymentCondition.generateId(agreementId, await lockPaymentCondition.hashValues(did, multiEscrowCondition.address, token.address, amounts, receivers)) @@ -199,13 +152,6 @@ contract('NFT Sales with Access Proof Template integration test', (accounts) => timeOuts: [0, 0, 0, 0] } - const data = { - origHash, - buyerPub, - providerPub, - cipher: [cipher.xL, cipher.xR], - proof: proofData - } return { agreementId, did, From 529dc45fc87be3688636cb66693bda81de927c5e Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Sami=20M=C3=A4kel=C3=A4?= Date: Wed, 2 Feb 2022 18:54:54 +0200 Subject: [PATCH 30/32] dedup snark generation in tests --- test/helpers/proofHelper.js | 60 +++++++++++++++++++++++++++++++++++++ 1 file changed, 60 insertions(+) create mode 100644 test/helpers/proofHelper.js diff --git a/test/helpers/proofHelper.js b/test/helpers/proofHelper.js new file mode 100644 index 00000000..6a2d55df --- /dev/null +++ b/test/helpers/proofHelper.js @@ -0,0 +1,60 @@ +const poseidon = require('circomlib').poseidon +const babyJub = require('circomlib').babyJub +const mimcjs = require('circomlib').mimcsponge +const ZqField = require('ffjavascript').ZqField +const Scalar = require('ffjavascript').Scalar +const F = new ZqField(Scalar.fromString('21888242871839275222246405745257275088548364400416034343698204186575808495617')) +const snarkjs = require('snarkjs') +const { unstringifyBigInts } = require('ffjavascript').utils + +exports.makeProof = async function(orig1, orig2, buyerK, providerK) { + const origHash = poseidon([orig1, orig2]) + const buyerPub = babyJub.mulPointEscalar(babyJub.Base8, F.e(buyerK)) + const providerPub = babyJub.mulPointEscalar(babyJub.Base8, F.e(providerK)) + + const k = babyJub.mulPointEscalar(buyerPub, F.e(providerK)) + + const cipher = mimcjs.hash(orig1, orig2, k[0]) + + const snarkParams = { + buyer_x: buyerPub[0], + buyer_y: buyerPub[1], + provider_x: providerPub[0], + provider_y: providerPub[1], + xL_in: orig1, + xR_in: orig2, + cipher_xL_in: cipher.xL, + cipher_xR_in: cipher.xR, + provider_k: providerK, + hash_plain: origHash + } + + // console.log(snark_params) + + const { proof } = await snarkjs.plonk.fullProve( + snarkParams, + 'circuits/keytransfer.wasm', + 'circuits/keytransfer.zkey' + ) + + const signals = [ + buyerPub[0], + buyerPub[1], + providerPub[0], + providerPub[1], + cipher.xL, + cipher.xR, + origHash + ] + + const proofSolidity = (await snarkjs.plonk.exportSolidityCallData(unstringifyBigInts(proof), signals)) + const proofData = proofSolidity.split(',')[0] + + return { + origHash, + buyerPub, + providerPub, + cipher: [cipher.xL, cipher.xR], + proof: proofData + } +} From 39874371bcdaccc85daa68923008da46443aa2bb Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Sami=20M=C3=A4kel=C3=A4?= Date: Tue, 8 Feb 2022 15:09:50 +0200 Subject: [PATCH 31/32] fixing tests --- .../templates/NFTSalesWithAccessTemplate.sol | 6 +-- test/helpers/deployConditions.js | 9 ----- test/int/agreement/AccessAgreement.Test.js | 7 +--- .../agreement/AccessProofAgreement.Test.js | 7 +--- .../EscrowComputeExecutionAgreement.Test.js | 7 +--- .../NFTSalesWithAccessProofAgreement.Test.js | 22 +++++------ .../conditions/NFTMarkedLockCondition.Test.js | 6 +-- .../conditions/rewards/EscrowPayment.Test.js | 38 +++++-------------- .../rewards/MultiEscrowPayment.Test.js | 23 ++++++----- 9 files changed, 40 insertions(+), 85 deletions(-) diff --git a/contracts/templates/NFTSalesWithAccessTemplate.sol b/contracts/templates/NFTSalesWithAccessTemplate.sol index 30c18827..88dbbc1f 100644 --- a/contracts/templates/NFTSalesWithAccessTemplate.sol +++ b/contracts/templates/NFTSalesWithAccessTemplate.sol @@ -7,7 +7,7 @@ pragma solidity ^0.8.0; import './BaseEscrowTemplate.sol'; import '../conditions/LockPaymentCondition.sol'; import '../conditions/NFTs/TransferNFTCondition.sol'; -import '../conditions/rewards/MultiEscrowPaymentCondition.sol'; +import '../conditions/rewards/EscrowPaymentCondition.sol'; import '../registry/DIDRegistry.sol'; import '../conditions/AccessProofCondition.sol'; @@ -37,7 +37,7 @@ contract NFTSalesWithAccessTemplate is BaseEscrowTemplate { DIDRegistry internal didRegistry; LockPaymentCondition internal lockPaymentCondition; ITransferNFT internal transferCondition; - MultiEscrowPaymentCondition internal rewardCondition; + EscrowPaymentCondition internal rewardCondition; AccessProofCondition internal accessCondition; @@ -94,7 +94,7 @@ contract NFTSalesWithAccessTemplate is BaseEscrowTemplate { _transferConditionAddress ); - rewardCondition = MultiEscrowPaymentCondition( + rewardCondition = EscrowPaymentCondition( _escrowPaymentAddress ); diff --git a/test/helpers/deployConditions.js b/test/helpers/deployConditions.js index 734f9d2b..8038b3fb 100644 --- a/test/helpers/deployConditions.js +++ b/test/helpers/deployConditions.js @@ -2,7 +2,6 @@ const AccessCondition = artifacts.require('AccessCondition') const AccessProofCondition = artifacts.require('AccessProofCondition') const EscrowPaymentCondition = artifacts.require('EscrowPaymentCondition') -const MultiEscrowPaymentCondition = artifacts.require('MultiEscrowPaymentCondition') const LockPaymentCondition = artifacts.require('LockPaymentCondition') const ComputeExecutionCondition = artifacts.require('ComputeExecutionCondition') const DisputeManager = artifacts.require('PlonkVerifier') @@ -49,13 +48,6 @@ const deployConditions = async function( { from: deployer } ) - const multiEscrowCondition = await MultiEscrowPaymentCondition.new({ from: deployer }) - await multiEscrowCondition.initialize( - owner, - conditionStoreManager.address, - { from: deployer } - ) - const computeExecutionCondition = await ComputeExecutionCondition.new({ from: deployer }) await computeExecutionCondition.methods['initialize(address,address,address)']( owner, @@ -73,7 +65,6 @@ const deployConditions = async function( accessCondition, accessProofCondition, escrowPaymentCondition, - multiEscrowCondition, lockPaymentCondition, computeExecutionCondition, disputeManager diff --git a/test/int/agreement/AccessAgreement.Test.js b/test/int/agreement/AccessAgreement.Test.js index cc8fba48..b7489823 100644 --- a/test/int/agreement/AccessAgreement.Test.js +++ b/test/int/agreement/AccessAgreement.Test.js @@ -221,12 +221,9 @@ contract('Access Template integration test', (accounts) => { // No update since access is not fulfilled yet // refund - const result = await escrowPaymentCondition.fulfill(agreementId, did, escrowAmounts, receivers, escrowPaymentCondition.address, token.address, agreement.conditionIds[1], agreement.conditionIds[0], { from: receiver }) - assert.strictEqual( - (await conditionStoreManager.getConditionState(agreement.conditionIds[2])).toNumber(), - constants.condition.state.unfulfilled + await assert.isRejected( + escrowPaymentCondition.fulfill(agreementId, did, escrowAmounts, receivers, escrowPaymentCondition.address, token.address, agreement.conditionIds[1], agreement.conditionIds[0], { from: receiver }) ) - assert.strictEqual(result.logs.length, 0) // wait: for time out await increaseTime.mineBlocks(web3, timeOutAccess) diff --git a/test/int/agreement/AccessProofAgreement.Test.js b/test/int/agreement/AccessProofAgreement.Test.js index a21a077f..e05fe23a 100644 --- a/test/int/agreement/AccessProofAgreement.Test.js +++ b/test/int/agreement/AccessProofAgreement.Test.js @@ -251,12 +251,7 @@ contract('Access Proof Template integration test', (accounts) => { // No update since access is not fulfilled yet // refund - const result = await escrowPaymentCondition.fulfill(agreementId, did, escrowAmounts, receivers, escrowPaymentCondition.address, token.address, agreement.conditionIds[1], agreement.conditionIds[0], { from: receiver }) - assert.strictEqual( - (await conditionStoreManager.getConditionState(agreement.conditionIds[2])).toNumber(), - constants.condition.state.unfulfilled - ) - assert.strictEqual(result.logs.length, 0) + await assert.isRejected(escrowPaymentCondition.fulfill(agreementId, did, escrowAmounts, receivers, escrowPaymentCondition.address, token.address, agreement.conditionIds[1], agreement.conditionIds[0], { from: receiver })) // wait: for time out await increaseTime.mineBlocks(web3, timeOutAccess) diff --git a/test/int/agreement/EscrowComputeExecutionAgreement.Test.js b/test/int/agreement/EscrowComputeExecutionAgreement.Test.js index dfd4257f..f2957650 100644 --- a/test/int/agreement/EscrowComputeExecutionAgreement.Test.js +++ b/test/int/agreement/EscrowComputeExecutionAgreement.Test.js @@ -220,12 +220,7 @@ contract('Escrow Compute Execution Template integration test', (accounts) => { // No update since access is not fulfilled yet // refund - const result = await escrowPaymentCondition.fulfill(agreementId, did, escrowAmounts, receivers, escrowPaymentCondition.address, token.address, agreement.conditionIds[1], agreement.conditionIds[0], { from: receiver }) - assert.strictEqual( - (await conditionStoreManager.getConditionState(agreement.conditionIds[2])).toNumber(), - constants.condition.state.unfulfilled - ) - assert.strictEqual(result.logs.length, 0) + await assert.isRejected(escrowPaymentCondition.fulfill(agreementId, did, escrowAmounts, receivers, escrowPaymentCondition.address, token.address, agreement.conditionIds[1], agreement.conditionIds[0], { from: receiver })) // wait: for time out await increaseTime.mineBlocks(web3, timeOutAccess) diff --git a/test/int/nft/NFTSalesWithAccessProofAgreement.Test.js b/test/int/nft/NFTSalesWithAccessProofAgreement.Test.js index c8d39feb..9545120e 100644 --- a/test/int/nft/NFTSalesWithAccessProofAgreement.Test.js +++ b/test/int/nft/NFTSalesWithAccessProofAgreement.Test.js @@ -36,7 +36,7 @@ contract('NFT Sales with Access Proof Template integration test', (accounts) => transferCondition, nftSalesTemplate, nftSalesAgreement, - multiEscrowCondition, + escrowCondition, lockPaymentCondition, nftHolderCondition, accessProofCondition @@ -70,7 +70,7 @@ contract('NFT Sales with Access Proof Template integration test', (accounts) => ({ accessProofCondition, - multiEscrowCondition, + escrowPaymentCondition: escrowCondition, lockPaymentCondition, transferCondition } = await deployConditions( @@ -102,7 +102,7 @@ contract('NFT Sales with Access Proof Template integration test', (accounts) => agreementStoreManager.address, lockPaymentCondition.address, transferCondition.address, - multiEscrowCondition.address, + escrowCondition.address, accessProofCondition.address, { from: deployer } ) @@ -128,7 +128,7 @@ contract('NFT Sales with Access Proof Template integration test', (accounts) => const data = await makeProof(orig1, orig2, buyerK, providerK) const { origHash, buyerPub, providerPub } = data const conditionIdLockPayment = await lockPaymentCondition.generateId(agreementId, - await lockPaymentCondition.hashValues(did, multiEscrowCondition.address, token.address, amounts, receivers)) + await lockPaymentCondition.hashValues(did, escrowCondition.address, token.address, amounts, receivers)) const conditionIdTransferNFT = await transferCondition.generateId(agreementId, await transferCondition.hashValues(did, artist, receiver, numberNFTs, conditionIdLockPayment)) @@ -136,8 +136,8 @@ contract('NFT Sales with Access Proof Template integration test', (accounts) => const conditionIdAccess = await accessProofCondition.generateId(agreementId, await accessProofCondition.hashValues(origHash, buyerPub, providerPub)) - const conditionIdEscrow = await multiEscrowCondition.generateId(agreementId, - await multiEscrowCondition.hashValues(did, amounts, receivers, multiEscrowCondition.address, token.address, conditionIdLockPayment, + const conditionIdEscrow = await escrowCondition.generateId(agreementId, + await escrowCondition.hashValuesMulti(did, amounts, receivers, escrowCondition.address, token.address, conditionIdLockPayment, [conditionIdTransferNFT, conditionIdAccess])) nftSalesAgreement = { @@ -208,9 +208,9 @@ contract('NFT Sales with Access Proof Template integration test', (accounts) => // lock payment await token.mint(collector1, nftPrice, { from: owner }) await token.approve(lockPaymentCondition.address, nftPrice, { from: collector1 }) - await token.approve(multiEscrowCondition.address, nftPrice, { from: collector1 }) + await token.approve(escrowCondition.address, nftPrice, { from: collector1 }) - await lockPaymentCondition.fulfill(agreementId, did, multiEscrowCondition.address, token.address, amounts, receivers, { from: collector1 }) + await lockPaymentCondition.fulfill(agreementId, did, escrowCondition.address, token.address, amounts, receivers, { from: collector1 }) const { state } = await conditionStoreManager.getCondition(nftSalesAgreement.conditionIds[0]) assert.strictEqual(state.toNumber(), constants.condition.state.fulfilled) @@ -249,12 +249,12 @@ contract('NFT Sales with Access Proof Template integration test', (accounts) => constants.condition.state.fulfilled) // escrow - await multiEscrowCondition.fulfill( + await escrowCondition.fulfillMulti( agreementId, did, amounts, receivers, - multiEscrowCondition.address, + escrowCondition.address, token.address, nftSalesAgreement.conditionIds[0], [nftSalesAgreement.conditionIds[1], nftSalesAgreement.conditionIds[3]], @@ -265,7 +265,7 @@ contract('NFT Sales with Access Proof Template integration test', (accounts) => assert.strictEqual(await getBalance(token, collector1), 0) assert.strictEqual(await getBalance(token, lockPaymentCondition.address), 0) - assert.strictEqual(await getBalance(token, multiEscrowCondition.address), 0) + assert.strictEqual(await getBalance(token, escrowCondition.address), 0) assert.strictEqual(await getBalance(token, receivers[0]), amounts[0]) assert.strictEqual(await getBalance(token, receivers[1]), amounts[1]) }) diff --git a/test/unit/conditions/NFTMarkedLockCondition.Test.js b/test/unit/conditions/NFTMarkedLockCondition.Test.js index 9ef51c50..af6fb0da 100644 --- a/test/unit/conditions/NFTMarkedLockCondition.Test.js +++ b/test/unit/conditions/NFTMarkedLockCondition.Test.js @@ -52,12 +52,8 @@ contract('NFTMarkedLockCondition', (accounts) => { if (!conditionStoreManager) { conditionStoreManager = await ConditionStoreManager.new() await conditionStoreManager.initialize( - owner, - { from: owner } - ) - - await conditionStoreManager.delegateCreateRole( createRole, + owner, { from: owner } ) diff --git a/test/unit/conditions/rewards/EscrowPayment.Test.js b/test/unit/conditions/rewards/EscrowPayment.Test.js index 7493f724..7e7ccad8 100644 --- a/test/unit/conditions/rewards/EscrowPayment.Test.js +++ b/test/unit/conditions/rewards/EscrowPayment.Test.js @@ -13,7 +13,6 @@ const ConditionStoreManager = artifacts.require('ConditionStoreManager') const NeverminedToken = artifacts.require('NeverminedToken') const LockPaymentCondition = artifacts.require('LockPaymentCondition') const EscrowPaymentCondition = artifacts.require('EscrowPaymentCondition') -const MultiEscrowPaymentCondition = artifacts.require('MultiEscrowPaymentCondition') const constants = require('../../../helpers/constants.js') const { getBalance, getETHBalance } = require('../../../helpers/getBalance.js') @@ -517,32 +516,15 @@ function escrowTest(EscrowPaymentCondition, isMulti) { assert.strictEqual(await getBalance(token, lockPaymentCondition.address), 0) assert.strictEqual(await getBalance(token, escrowPayment.address), balanceBefore + totalAmount) - if (isMulti) { - await assert.isRejected(escrowPayment.fulfill( - agreementId, - did, - amounts, - receivers, - escrowPayment.address, - token.address, - lockConditionId, - multi(releaseConditionId))) - } else { - await escrowPayment.fulfill( - agreementId, - did, - amounts, - receivers, - escrowPayment.address, - token.address, - lockConditionId, - multi(releaseConditionId)) - - assert.strictEqual( - (await conditionStoreManager.getConditionState(escrowConditionId)).toNumber(), - constants.condition.state.unfulfilled - ) - } + await assert.isRejected(escrowPayment.fulfill( + agreementId, + did, + amounts, + receivers, + escrowPayment.address, + token.address, + lockConditionId, + multi(releaseConditionId))) assert.strictEqual(await getBalance(token, escrowPayment.address), balanceBefore + totalAmount) }) @@ -1099,4 +1081,4 @@ function escrowTest(EscrowPaymentCondition, isMulti) { } escrowTest(EscrowPaymentCondition, false) -escrowTest(MultiEscrowPaymentCondition, true) +// escrowTest(MultiEscrowPaymentCondition, true) diff --git a/test/unit/conditions/rewards/MultiEscrowPayment.Test.js b/test/unit/conditions/rewards/MultiEscrowPayment.Test.js index ed996b56..cc4acb63 100644 --- a/test/unit/conditions/rewards/MultiEscrowPayment.Test.js +++ b/test/unit/conditions/rewards/MultiEscrowPayment.Test.js @@ -14,7 +14,7 @@ const NeverminedToken = artifacts.require('NeverminedToken') const LockPaymentCondition = artifacts.require('LockPaymentCondition') const NFTMarkedLockCondition = artifacts.require('NFTMarkedLockCondition') const NFT721MarkedLockCondition = artifacts.require('NFT721MarkedLockCondition') -const EscrowPaymentCondition = artifacts.require('MultiEscrowPaymentCondition') +const EscrowPaymentCondition = artifacts.require('EscrowPaymentCondition') const NFTEscrowPaymentCondition = artifacts.require('NFTEscrowPaymentCondition') const NFT721EscrowPaymentCondition = artifacts.require('NFT721EscrowPaymentCondition') @@ -63,10 +63,10 @@ function nftLockWrapper(contract) { function tokenEscrowWrapper(contract) { contract.hashWrap = (did, amounts, receivers, escrowPaymentAddress, tokenAddress, lockConditionId, releaseConditionId) => { - return contract.hashValues(did, amounts, receivers, escrowPaymentAddress, tokenAddress, lockConditionId, releaseConditionId) + return contract.hashValuesMulti(did, amounts, receivers, escrowPaymentAddress, tokenAddress, lockConditionId, releaseConditionId) } contract.fulfillWrap = (agreementId, did, amounts, receivers, escrowPaymentAddress, tokenAddress, lockConditionId, releaseConditionId) => { - return contract.fulfill( + return contract.fulfillMulti( agreementId, did, amounts, @@ -185,9 +185,8 @@ function nft721TokenWrapper(contract) { return contract } -function testMultiEscrow(EscrowPaymentCondition, LockPaymentCondition, Token, nft, amount1, amount2) { - contract('MultiEscrowPaymentCondition contract', (accounts) => { - const single = nft ? a => a[0] : a => a +function testMultiEscrow(EscrowPaymentCondition, LockPaymentCondition, Token, nft, amount1, amount2, label) { + contract(`EscrowPaymentCondition contract (multi) for ${label}`, (accounts) => { const lockWrapper = nft ? nftLockWrapper : tokenLockWrapper const escrowWrapper = nft ? nftEscrowWrapper : tokenEscrowWrapper const tokenWrapper = nft ? (amount2 === 0 ? nft721TokenWrapper : nftTokenWrapper) : tokenTokenWrapper @@ -339,11 +338,11 @@ function testMultiEscrow(EscrowPaymentCondition, LockPaymentCondition, Token, nf assert.strictEqual(await token.getBalance(lockPaymentCondition.address), 0) assert.strictEqual(await token.getBalance(escrowPayment.address), balanceBefore + totalAmount) - const result = await escrowPayment.fulfill( + const result = await escrowPayment.fulfillWrap( agreementId, did, - single(amounts), - single(receivers), + amounts, + receivers, escrowPayment.address, token.address, lockConditionId, @@ -488,6 +487,6 @@ function testMultiEscrow(EscrowPaymentCondition, LockPaymentCondition, Token, nf }) } -testMultiEscrow(EscrowPaymentCondition, LockPaymentCondition, NeverminedToken, false, 10, 12) -testMultiEscrow(NFTEscrowPaymentCondition, NFTMarkedLockCondition, NFT, true, 10, 12) -testMultiEscrow(NFT721EscrowPaymentCondition, NFT721MarkedLockCondition, NFT721, true, 1, 0) +testMultiEscrow(EscrowPaymentCondition, LockPaymentCondition, NeverminedToken, false, 10, 12, 'ERC-20') +testMultiEscrow(NFTEscrowPaymentCondition, NFTMarkedLockCondition, NFT, true, 10, 12, 'ERC-1155') +testMultiEscrow(NFT721EscrowPaymentCondition, NFT721MarkedLockCondition, NFT721, true, 1, 0, 'ERC-721') From f763da76272dd3da95254fa9b109c39596e79fef Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Sami=20M=C3=A4kel=C3=A4?= Date: Tue, 8 Feb 2022 15:46:16 +0200 Subject: [PATCH 32/32] tests seem to work --- contracts/conditions/NFTs/INFTLock.sol | 23 +++ contracts/conditions/NFTs/INFTMarkedLock.sol | 66 ------- .../conditions/NFTs/NFT721LockCondition.sol | 43 ++++- .../NFTs/NFT721MarkedLockCondition.sol | 134 -------------- .../conditions/NFTs/NFTLockCondition.sol | 74 +++++--- .../NFTs/NFTMarkedLockCondition.sol | 175 ------------------ .../rewards/NFTEscrowPaymentCondition.sol | 2 + contracts/templates/NFTAccessSwapTemplate.sol | 6 +- test/int/nft/NFTAccessSwapAgreement.Test.js | 9 +- .../conditions/NFTMarkedLockCondition.Test.js | 23 +-- .../rewards/MultiEscrowPayment.Test.js | 32 +++- 11 files changed, 152 insertions(+), 435 deletions(-) delete mode 100644 contracts/conditions/NFTs/INFTMarkedLock.sol delete mode 100644 contracts/conditions/NFTs/NFT721MarkedLockCondition.sol delete mode 100644 contracts/conditions/NFTs/NFTMarkedLockCondition.sol diff --git a/contracts/conditions/NFTs/INFTLock.sol b/contracts/conditions/NFTs/INFTLock.sol index 53c10067..9259b2fa 100644 --- a/contracts/conditions/NFTs/INFTLock.sol +++ b/contracts/conditions/NFTs/INFTLock.sol @@ -14,6 +14,7 @@ interface INFTLock { address indexed _lockAddress, bytes32 _conditionId, uint256 _amount, + address _receiver, address _nftContractAddress ); @@ -36,6 +37,17 @@ interface INFTLock { pure returns (bytes32); + function hashValuesMarked( + bytes32 _did, + address _lockAddress, + uint256 _amount, + address _receiver, + address _nftContractAddress + ) + external + pure + returns (bytes32); + /** * @notice fulfill the transfer NFT condition * @dev Fulfill method transfer a certain amount of NFTs @@ -58,4 +70,15 @@ interface INFTLock { external returns (ConditionStoreLibrary.ConditionState); + function fulfillMarked( + bytes32 _agreementId, + bytes32 _did, + address _lockAddress, + uint256 _amount, + address _receiver, + address _nftContractAddress + ) + external + returns (ConditionStoreLibrary.ConditionState); + } diff --git a/contracts/conditions/NFTs/INFTMarkedLock.sol b/contracts/conditions/NFTs/INFTMarkedLock.sol deleted file mode 100644 index a4e7b1ef..00000000 --- a/contracts/conditions/NFTs/INFTMarkedLock.sol +++ /dev/null @@ -1,66 +0,0 @@ -pragma solidity ^0.8.0; -// Copyright 2020 Keyko GmbH. -// SPDX-License-Identifier: (Apache-2.0 AND CC-BY-4.0) -// Code is Apache-2.0 and docs are CC-BY-4.0 - -import '../Condition.sol'; - - -interface INFTMarkedLock { - - event Fulfilled( - bytes32 indexed _agreementId, - bytes32 indexed _did, - address indexed _lockAddress, - bytes32 _conditionId, - uint256 _amount, - address _receiver, - address _nftContractAddress - ); - - /** - * @notice hashValues generates the hash of condition inputs - * with the following parameters - * @param _did the DID of the asset with NFTs attached to lock - * @param _lockAddress the contract address where the NFT will be locked - * @param _amount is the amount of the NFTs locked - * @param _receiver is the receiver of the NFTs locked - * @param _nftContractAddress Is the address of the NFT (ERC-721, ERC-1155) contract to use - * @return bytes32 hash of all these values - */ - function hashValues( - bytes32 _did, - address _lockAddress, - uint256 _amount, - address _receiver, - address _nftContractAddress - ) - external - pure - returns (bytes32); - - /** - * @notice fulfill the transfer NFT condition - * @dev Fulfill method transfer a certain amount of NFTs - * to the _nftReceiver address. - * When true then fulfill the condition - * @param _agreementId agreement identifier - * @param _did refers to the DID in which secret store will issue the decryption keys - * @param _lockAddress the contract address where the NFT will be locked - * @param _amount is the amount of the locked tokens - * @param _receiver is the receiver of the NFTs locked - * @param _nftContractAddress Is the address of the NFT (ERC-721) contract to use - * @return condition state (Fulfilled/Aborted) - */ - function fulfill( - bytes32 _agreementId, - bytes32 _did, - address _lockAddress, - uint256 _amount, - address _receiver, - address _nftContractAddress - ) - external - returns (ConditionStoreLibrary.ConditionState); - -} diff --git a/contracts/conditions/NFTs/NFT721LockCondition.sol b/contracts/conditions/NFTs/NFT721LockCondition.sol index 00ab049b..9b289764 100644 --- a/contracts/conditions/NFTs/NFT721LockCondition.sol +++ b/contracts/conditions/NFTs/NFT721LockCondition.sol @@ -52,7 +52,7 @@ contract NFT721LockCondition is Condition, INFTLock, ReentrancyGuardUpgradeable, * @param _lockAddress the contract address where the NFT will be locked * @param _amount is the amount of the locked tokens * @param _nftContractAddress Is the address of the NFT (ERC-721) contract to use - * @return bytes32 hash of all these values + * @return bytes32 hash of all these values */ function hashValues( bytes32 _did, @@ -61,11 +61,26 @@ contract NFT721LockCondition is Condition, INFTLock, ReentrancyGuardUpgradeable, address _nftContractAddress ) public + override pure + returns (bytes32) + { + return hashValuesMarked(_did, _lockAddress, _amount, address(0), _nftContractAddress); + } + + function hashValuesMarked( + bytes32 _did, + address _lockAddress, + uint256 _amount, + address _receiver, + address _nftContractAddress + ) + public override + pure returns (bytes32) { - return keccak256(abi.encode(_did, _lockAddress, _amount, _nftContractAddress)); + return keccak256(abi.encode(_did, _lockAddress, _amount, _receiver, _nftContractAddress)); } /** @@ -78,14 +93,15 @@ contract NFT721LockCondition is Condition, INFTLock, ReentrancyGuardUpgradeable, * @param _nftContractAddress Is the address of the NFT (ERC-721) contract to use * @return condition state (Fulfilled/Aborted) */ - function fulfill( + function fulfillMarked( bytes32 _agreementId, bytes32 _did, address _lockAddress, uint256 _amount, + address _receiver, address _nftContractAddress ) - external + public override nonReentrant returns (ConditionStoreLibrary.ConditionState) @@ -100,10 +116,10 @@ contract NFT721LockCondition is Condition, INFTLock, ReentrancyGuardUpgradeable, if (_amount == 1) { erc721.safeTransferFrom(msg.sender, _lockAddress, uint256(_did)); } - + bytes32 _id = generateId( _agreementId, - hashValues(_did, _lockAddress, _amount, _nftContractAddress) + hashValuesMarked(_did, _lockAddress, _amount, _receiver, _nftContractAddress) ); ConditionStoreLibrary.ConditionState state = super.fulfill( _id, @@ -116,11 +132,26 @@ contract NFT721LockCondition is Condition, INFTLock, ReentrancyGuardUpgradeable, _lockAddress, _id, _amount, + _receiver, _nftContractAddress ); return state; } + function fulfill( + bytes32 _agreementId, + bytes32 _did, + address _lockAddress, + uint256 _amount, + address _nftContractAddress + ) + external + override + returns (ConditionStoreLibrary.ConditionState) + { + return fulfillMarked(_agreementId, _did, _lockAddress, _amount, address(0), _nftContractAddress); + } + /** * Always returns `IERC721Receiver.onERC721Received.selector`. */ diff --git a/contracts/conditions/NFTs/NFT721MarkedLockCondition.sol b/contracts/conditions/NFTs/NFT721MarkedLockCondition.sol deleted file mode 100644 index ac780b35..00000000 --- a/contracts/conditions/NFTs/NFT721MarkedLockCondition.sol +++ /dev/null @@ -1,134 +0,0 @@ -pragma solidity ^0.8.0; -// Copyright 2020 Keyko GmbH. -// SPDX-License-Identifier: (Apache-2.0 AND CC-BY-4.0) -// Code is Apache-2.0 and docs are CC-BY-4.0 - - -import '../Condition.sol'; -import './INFTMarkedLock.sol'; -import '@openzeppelin/contracts-upgradeable/security/ReentrancyGuardUpgradeable.sol'; -import '@openzeppelin/contracts-upgradeable/token/ERC721/IERC721Upgradeable.sol'; -import '@openzeppelin/contracts-upgradeable/token/ERC721/IERC721ReceiverUpgradeable.sol'; - -/** - * @title NFT (ERC-721) Lock Condition - * @author Keyko - * - * @dev Implementation of the NFT Lock Condition for ERC-721 based NFTs - */ -contract NFT721MarkedLockCondition is Condition, INFTMarkedLock, ReentrancyGuardUpgradeable, IERC721ReceiverUpgradeable { - - bytes32 constant public CONDITION_TYPE = keccak256('NFT721LockCondition'); - - /** - * @notice initialize init the contract with the following parameters - * @dev this function is called only once during the contract - * initialization. - * @param _owner contract's owner account address - * @param _conditionStoreManagerAddress condition store manager address - */ - function initialize( - address _owner, - address _conditionStoreManagerAddress - ) - external - initializer() - { - require( - _conditionStoreManagerAddress != address(0), - 'Invalid address' - ); - OwnableUpgradeable.__Ownable_init(); - transferOwnership(_owner); - conditionStoreManager = ConditionStoreManager( - _conditionStoreManagerAddress - ); - } - - /** - * @notice hashValues generates the hash of condition inputs - * with the following parameters - * @param _did the DID of the asset with NFTs attached to lock - * @param _lockAddress the contract address where the NFT will be locked - * @param _amount is the amount of the locked tokens - * @param _nftContractAddress Is the address of the NFT (ERC-721) contract to use - * @return bytes32 hash of all these values - */ - function hashValues( - bytes32 _did, - address _lockAddress, - uint256 _amount, - address _receiver, - address _nftContractAddress - ) - public - override - pure - returns (bytes32) - { - return keccak256(abi.encode(_did, _lockAddress, _amount, _receiver, _nftContractAddress)); - } - - /** - * @notice fulfill the transfer NFT condition - * @dev Fulfill method lock a NFT into the `_lockAddress`. - * @param _agreementId agreement identifier - * @param _did refers to the DID in which secret store will issue the decryption keys - * @param _lockAddress the contract address where the NFT will be locked - * @param _amount is the amount of the locked tokens (1) - * @param _nftContractAddress Is the address of the NFT (ERC-721) contract to use - * @return condition state (Fulfilled/Aborted) - */ - function fulfill( - bytes32 _agreementId, - bytes32 _did, - address _lockAddress, - uint256 _amount, - address _receiver, - address _nftContractAddress - ) - external - override - nonReentrant - returns (ConditionStoreLibrary.ConditionState) - { - IERC721Upgradeable erc721 = IERC721Upgradeable(_nftContractAddress); - - require( - _amount == 0 || (_amount == 1 && erc721.ownerOf(uint256(_did)) == msg.sender), - 'Not enough balance' - ); - - if (_amount == 1) { - erc721.safeTransferFrom(msg.sender, _lockAddress, uint256(_did)); - } - - bytes32 _id = generateId( - _agreementId, - hashValues(_did, _lockAddress, _amount, _receiver, _nftContractAddress) - ); - ConditionStoreLibrary.ConditionState state = super.fulfill( - _id, - ConditionStoreLibrary.ConditionState.Fulfilled - ); - - emit Fulfilled( - _agreementId, - _did, - _lockAddress, - _id, - _amount, - _receiver, - _nftContractAddress - ); - return state; - } - - /** - * Always returns `IERC721Receiver.onERC721Received.selector`. - */ - function onERC721Received(address, address, uint256, bytes memory) public virtual override returns (bytes4) { - return this.onERC721Received.selector; - } - -} diff --git a/contracts/conditions/NFTs/NFTLockCondition.sol b/contracts/conditions/NFTs/NFTLockCondition.sol index d2ef8572..1559178a 100644 --- a/contracts/conditions/NFTs/NFTLockCondition.sol +++ b/contracts/conditions/NFTs/NFTLockCondition.sol @@ -25,12 +25,6 @@ contract NFTLockCondition is Condition, INFTLock, ReentrancyGuardUpgradeable, IE bytes4 constant internal ERC1155_ACCEPTED = 0xf23a6e61; // bytes4(keccak256("onERC1155Received(address,address,uint256,uint256,bytes)")) bytes4 constant internal ERC1155_BATCH_ACCEPTED = 0xbc197c81; // bytes4(keccak256("onERC1155BatchReceived(address,address,uint256[],uint256[],bytes)")) - bytes public lastData; - address public lastOperator; - address public lastFrom; - uint256 public lastId; - uint256 public lastValue; - /** * @notice initialize init the contract with the following parameters * @dev this function is called only once during the contract @@ -100,6 +94,21 @@ contract NFTLockCondition is Condition, INFTLock, ReentrancyGuardUpgradeable, IE pure override returns (bytes32) + { + return hashValuesMarked(_did, _lockAddress, _amount, address(0), _nftContractAddress); + } + + function hashValuesMarked( + bytes32 _did, + address _lockAddress, + uint256 _amount, + address _receiver, + address _nftContractAddress + ) + public + pure + override + returns (bytes32) { return keccak256( abi.encode( @@ -107,6 +116,7 @@ contract NFTLockCondition is Condition, INFTLock, ReentrancyGuardUpgradeable, IE _did, _lockAddress, _amount, + _receiver, _nftContractAddress ) ); @@ -135,6 +145,20 @@ contract NFTLockCondition is Condition, INFTLock, ReentrancyGuardUpgradeable, IE return fulfill(_agreementId, _did, _lockAddress, _amount, address(erc1155)); } + function fulfill( + bytes32 _agreementId, + bytes32 _did, + address _lockAddress, + uint256 _amount, + address _nft + ) + public + override + returns (ConditionStoreLibrary.ConditionState) + { + return fulfillMarked(_agreementId, _did, _lockAddress, _amount, address(0), _nft); + } + /** * @notice fulfill the transfer NFT condition * @dev Fulfill method transfer a certain amount of NFTs @@ -147,11 +171,12 @@ contract NFTLockCondition is Condition, INFTLock, ReentrancyGuardUpgradeable, IE * @param _nftContractAddress Is the address of the NFT (ERC-1155) contract to use * @return condition state (Fulfilled/Aborted) */ - function fulfill( + function fulfillMarked( bytes32 _agreementId, bytes32 _did, address _lockAddress, uint256 _amount, + address _receiver, address _nftContractAddress ) public @@ -159,11 +184,11 @@ contract NFTLockCondition is Condition, INFTLock, ReentrancyGuardUpgradeable, IE nonReentrant returns (ConditionStoreLibrary.ConditionState) { - erc1155.safeTransferFrom(msg.sender, _lockAddress, uint256(_did), _amount, ''); + IERC1155Upgradeable(_nftContractAddress).safeTransferFrom(msg.sender, _lockAddress, uint256(_did), _amount, ''); bytes32 _id = generateId( _agreementId, - hashValues(_did, _lockAddress, _amount, _nftContractAddress) + hashValuesMarked(_did, _lockAddress, _amount, _receiver, _nftContractAddress) ); ConditionStoreLibrary.ConditionState state = super.fulfill( _id, @@ -176,6 +201,7 @@ contract NFTLockCondition is Condition, INFTLock, ReentrancyGuardUpgradeable, IE _lockAddress, _id, _amount, + _receiver, _nftContractAddress ); return state; @@ -183,40 +209,30 @@ contract NFTLockCondition is Condition, INFTLock, ReentrancyGuardUpgradeable, IE // solhint-disable-next-line function onERC1155Received( - address _operator, - address _from, - uint256 _id, - uint256 _value, - bytes calldata _data + address, + address, + uint256, + uint256, + bytes calldata ) external override returns(bytes4) { - lastOperator = _operator; - lastFrom = _from; - lastId = _id; - lastValue = _value; - lastData = _data; return ERC1155_ACCEPTED; } function onERC1155BatchReceived( - address _operator, - address _from, - uint256[] calldata _ids, - uint256[] calldata _values, - bytes calldata _data + address, + address, + uint256[] calldata, + uint256[] calldata, + bytes calldata ) external override returns(bytes4) { - lastOperator = _operator; - lastFrom = _from; - lastId = _ids[0]; - lastValue = _values[0]; - lastData = _data; return ERC1155_BATCH_ACCEPTED; } diff --git a/contracts/conditions/NFTs/NFTMarkedLockCondition.sol b/contracts/conditions/NFTs/NFTMarkedLockCondition.sol deleted file mode 100644 index 13842fdd..00000000 --- a/contracts/conditions/NFTs/NFTMarkedLockCondition.sol +++ /dev/null @@ -1,175 +0,0 @@ -pragma solidity ^0.8.0; -// Copyright 2020 Keyko GmbH. -// SPDX-License-Identifier: (Apache-2.0 AND CC-BY-4.0) -// Code is Apache-2.0 and docs are CC-BY-4.0 - - -import '../Condition.sol'; -import '../../registry/DIDRegistry.sol'; -import './INFTMarkedLock.sol'; -import '@openzeppelin/contracts-upgradeable/token/ERC1155/IERC1155ReceiverUpgradeable.sol'; -import '@openzeppelin/contracts-upgradeable/security/ReentrancyGuardUpgradeable.sol'; - -/** - * @title NFT Lock Condition - * @author Keyko - * - * @dev Implementation of the NFT Lock Condition - */ -contract NFTMarkedLockCondition is Condition, INFTMarkedLock, ReentrancyGuardUpgradeable, IERC1155ReceiverUpgradeable { - - bytes32 constant public CONDITION_TYPE = keccak256('NFTMarkedLockCondition'); - - bytes4 constant internal ERC1155_ACCEPTED = 0xf23a6e61; // bytes4(keccak256("onERC1155Received(address,address,uint256,uint256,bytes)")) - bytes4 constant internal ERC1155_BATCH_ACCEPTED = 0xbc197c81; // bytes4(keccak256("onERC1155BatchReceived(address,address,uint256[],uint256[],bytes)")) - - /** - * @notice initialize init the contract with the following parameters - * @dev this function is called only once during the contract - * initialization. - * @param _owner contract's owner account address - * @param _conditionStoreManagerAddress condition store manager address - */ - function initialize( - address _owner, - address _conditionStoreManagerAddress - ) - external - initializer() - { - require( - _conditionStoreManagerAddress != address(0), - 'Invalid address' - ); - OwnableUpgradeable.__Ownable_init(); - transferOwnership(_owner); - conditionStoreManager = ConditionStoreManager( - _conditionStoreManagerAddress - ); - - } - - /** - * @notice hashValues generates the hash of condition inputs - * with the following parameters - * @param _did the DID of the asset with NFTs attached to lock - * @param _lockAddress the contract address where the NFT will be locked - * @param _amount is the amount of the locked tokens - * @param _nftContractAddress Is the address of the NFT (ERC-1155) contract to use - * @return bytes32 hash of all these values - */ - function hashValues( - bytes32 _did, - address _lockAddress, - uint256 _amount, - address _receiver, - address _nftContractAddress - ) - public - override - pure - returns (bytes32) - { - return keccak256( - abi.encode( - _did, - _lockAddress, - _amount, - _receiver, - _nftContractAddress - ) - ); - } - - /** - * @notice fulfill the transfer NFT condition - * @dev Fulfill method transfer a certain amount of NFTs - * to the _nftReceiver address. - * When true then fulfill the condition - * @param _agreementId agreement identifier - * @param _did refers to the DID in which secret store will issue the decryption keys - * @param _lockAddress the contract address where the NFT will be locked - * @param _amount is the amount of the locked tokens - * @param _nftContractAddress Is the address of the NFT (ERC-1155) contract to use - * @return condition state (Fulfilled/Aborted) - */ - function fulfill( - bytes32 _agreementId, - bytes32 _did, - address _lockAddress, - uint256 _amount, - address _receiver, - address _nftContractAddress - ) - public - override - nonReentrant - returns (ConditionStoreLibrary.ConditionState) - { - IERC1155Upgradeable(_nftContractAddress).safeTransferFrom(msg.sender, _lockAddress, uint256(_did), _amount, ''); - - bytes32 _id = generateId( - _agreementId, - hashValues(_did, _lockAddress, _amount, _receiver, _nftContractAddress) - ); - ConditionStoreLibrary.ConditionState state = super.fulfill( - _id, - ConditionStoreLibrary.ConditionState.Fulfilled - ); - - emit Fulfilled( - _agreementId, - _did, - _lockAddress, - _id, - _amount, - _receiver, - _nftContractAddress - ); - return state; - } - - // solhint-disable-next-line - function onERC1155Received( - address, - address, - uint256, - uint256, - bytes calldata - ) - external - override - pure - returns(bytes4) - { - return ERC1155_ACCEPTED; - } - - function onERC1155BatchReceived( - address, - address, - uint256[] calldata, - uint256[] calldata, - bytes calldata - ) - external - override - pure - returns(bytes4) - { - return ERC1155_BATCH_ACCEPTED; - } - - function supportsInterface( - bytes4 interfaceId - ) - external - override - pure - returns (bool) - { - return interfaceId == 0x01ffc9a7 || // ERC165 - interfaceId == 0x4e2312e0; // ERC1155_ACCEPTED ^ ERC1155_BATCH_ACCEPTED; - } - -} diff --git a/contracts/conditions/rewards/NFTEscrowPaymentCondition.sol b/contracts/conditions/rewards/NFTEscrowPaymentCondition.sol index 22efd6d1..5cb21841 100644 --- a/contracts/conditions/rewards/NFTEscrowPaymentCondition.sol +++ b/contracts/conditions/rewards/NFTEscrowPaymentCondition.sol @@ -24,6 +24,7 @@ import 'hardhat/console.sol'; contract NFTEscrowPaymentCondition is Reward, INFTEscrow, Common, ReentrancyGuardUpgradeable, IERC1155ReceiverUpgradeable { bytes32 constant public CONDITION_TYPE = keccak256('NFTEscrowPayment'); + bytes32 constant public LOCK_CONDITION_TYPE = keccak256('NFTLockCondition'); event Received( address indexed _from, @@ -119,6 +120,7 @@ contract NFTEscrowPaymentCondition is Reward, INFTEscrow, Common, ReentrancyGuar returns (bytes32) { return keccak256(abi.encode( + LOCK_CONDITION_TYPE, _did, _lockAddress, _amount, diff --git a/contracts/templates/NFTAccessSwapTemplate.sol b/contracts/templates/NFTAccessSwapTemplate.sol index d12aa867..7a447d5e 100644 --- a/contracts/templates/NFTAccessSwapTemplate.sol +++ b/contracts/templates/NFTAccessSwapTemplate.sol @@ -5,7 +5,7 @@ pragma solidity ^0.8.0; import './BaseEscrowTemplate.sol'; -import '../conditions/NFTs/INFTMarkedLock.sol'; +import '../conditions/NFTs/INFTLock.sol'; import '../conditions/rewards/INFTEscrow.sol'; import '../registry/DIDRegistry.sol'; import '../conditions/AccessProofCondition.sol'; @@ -34,7 +34,7 @@ import '../conditions/AccessProofCondition.sol'; contract NFTAccessSwapTemplate is BaseEscrowTemplate { DIDRegistry internal didRegistry; - INFTMarkedLock internal lockPaymentCondition; + INFTLock internal lockPaymentCondition; INFTEscrow internal rewardCondition; AccessProofCondition internal accessCondition; @@ -81,7 +81,7 @@ contract NFTAccessSwapTemplate is BaseEscrowTemplate { agreementStoreManager.getDIDRegistryAddress() ); - lockPaymentCondition = INFTMarkedLock( + lockPaymentCondition = INFTLock( _lockPaymentConditionAddress ); diff --git a/test/int/nft/NFTAccessSwapAgreement.Test.js b/test/int/nft/NFTAccessSwapAgreement.Test.js index 660c1c6e..37120da8 100644 --- a/test/int/nft/NFTAccessSwapAgreement.Test.js +++ b/test/int/nft/NFTAccessSwapAgreement.Test.js @@ -8,7 +8,7 @@ const chaiAsPromised = require('chai-as-promised') chai.use(chaiAsPromised) const NFTAccessSwapTemplate = artifacts.require('NFTAccessSwapTemplate') -const NFTMarkedLockCondition = artifacts.require('NFTMarkedLockCondition') +const NFTLockCondition = artifacts.require('NFTLockCondition') const NFTEscrowCondition = artifacts.require('NFTEscrowPaymentCondition') const constants = require('../../helpers/constants.js') @@ -77,10 +77,11 @@ contract('NFT Sales with Access Proof Template integration test', (accounts) => conditionStoreManager.address, { from: deployer } ) - lockPaymentCondition = await NFTMarkedLockCondition.new() + lockPaymentCondition = await NFTLockCondition.new() await lockPaymentCondition.initialize( owner, conditionStoreManager.address, + nft.address, { from: deployer } ) nftTemplate = await NFTAccessSwapTemplate.new() @@ -113,7 +114,7 @@ contract('NFT Sales with Access Proof Template integration test', (accounts) => const { origHash, buyerPub, providerPub } = data const conditionIdLockPayment = await lockPaymentCondition.generateId(agreementId, - await lockPaymentCondition.hashValues(did, escrowCondition.address, amount, receiver, token.address)) + await lockPaymentCondition.hashValuesMarked(did, escrowCondition.address, amount, receiver, token.address)) const conditionIdAccess = await accessProofCondition.generateId(agreementId, await accessProofCondition.hashValues(origHash, buyerPub, providerPub)) @@ -189,7 +190,7 @@ contract('NFT Sales with Access Proof Template integration test', (accounts) => const nftBalanceCollectorBefore = await nft.balanceOf(collector1, did) await nft.setApprovalForAll(lockPaymentCondition.address, true, { from: artist }) - await lockPaymentCondition.fulfill(agreementId, did, escrowCondition.address, amount, receiver, token.address, { from: artist }) + await lockPaymentCondition.fulfillMarked(agreementId, did, escrowCondition.address, amount, receiver, token.address, { from: artist }) const { state } = await conditionStoreManager.getCondition(nftAgreement.conditionIds[0]) assert.strictEqual(state.toNumber(), constants.condition.state.fulfilled) diff --git a/test/unit/conditions/NFTMarkedLockCondition.Test.js b/test/unit/conditions/NFTMarkedLockCondition.Test.js index af6fb0da..16714b88 100644 --- a/test/unit/conditions/NFTMarkedLockCondition.Test.js +++ b/test/unit/conditions/NFTMarkedLockCondition.Test.js @@ -10,7 +10,7 @@ const EpochLibrary = artifacts.require('EpochLibrary') const ConditionStoreManager = artifacts.require('ConditionStoreManager') const DIDRegistryLibrary = artifacts.require('DIDRegistryLibrary') const DIDRegistry = artifacts.require('DIDRegistry') -const NFTLockCondition = artifacts.require('NFTMarkedLockCondition') +const NFTLockCondition = artifacts.require('NFTLockCondition') const NFT = artifacts.require('NFTUpgradeable') const constants = require('../../helpers/constants.js') @@ -62,6 +62,7 @@ contract('NFTMarkedLockCondition', (accounts) => { await lockCondition.initialize( owner, conditionStoreManager.address, + nft.address, { from: createRole } ) } @@ -81,14 +82,14 @@ contract('NFTMarkedLockCondition', (accounts) => { await didRegistry.mint(did, amount) await nft.setApprovalForAll(lockCondition.address, true) - const hashValues = await lockCondition.hashValues(did, lockAddress, amount, receiver, nft.address) + const hashValues = await lockCondition.hashValuesMarked(did, lockAddress, amount, receiver, nft.address) const conditionId = await lockCondition.generateId(agreementId, hashValues) await conditionStoreManager.createCondition( conditionId, lockCondition.address) - const result = await lockCondition.fulfill(agreementId, did, lockAddress, amount, receiver, nft.address) + const result = await lockCondition.fulfillMarked(agreementId, did, lockAddress, amount, receiver, nft.address) const { state } = await conditionStoreManager.getCondition(conditionId) assert.strictEqual(state.toNumber(), constants.condition.state.fulfilled) const nftBalance = await nft.balanceOf(lockCondition.address, did) @@ -119,7 +120,7 @@ contract('NFTMarkedLockCondition', (accounts) => { await nft.setApprovalForAll(lockCondition.address, true) await assert.isRejected( - lockCondition.fulfill(agreementId, did, lockAddress, amount, receiver, nft.address), + lockCondition.fulfillMarked(agreementId, did, lockAddress, amount, receiver, nft.address), constants.acl.error.invalidUpdateRole ) }) @@ -137,7 +138,7 @@ contract('NFTMarkedLockCondition', (accounts) => { await didRegistry.mint(did, amount) await nft.setApprovalForAll(lockCondition.address, true) - const hashValues = await lockCondition.hashValues(did, lockAddress, amount, receiver, nft.address) + const hashValues = await lockCondition.hashValuesMarked(did, lockAddress, amount, receiver, nft.address) const conditionId = await lockCondition.generateId(agreementId, hashValues) await conditionStoreManager.createCondition( @@ -145,7 +146,7 @@ contract('NFTMarkedLockCondition', (accounts) => { lockCondition.address) await assert.isRejected( - lockCondition.fulfill(agreementId, did, lockAddress, amount + 1, receiver, nft.address), + lockCondition.fulfillMarked(agreementId, did, lockAddress, amount + 1, receiver, nft.address), undefined ) }) @@ -163,7 +164,7 @@ contract('NFTMarkedLockCondition', (accounts) => { await didRegistry.mint(did, amount) await nft.setApprovalForAll(lockCondition.address, true) - const hashValues = await lockCondition.hashValues(did, lockAddress, amount, receiver, nft.address) + const hashValues = await lockCondition.hashValuesMarked(did, lockAddress, amount, receiver, nft.address) const conditionId = await lockCondition.generateId(agreementId, hashValues) await conditionStoreManager.createCondition( @@ -171,14 +172,14 @@ contract('NFTMarkedLockCondition', (accounts) => { lockCondition.address ) - await lockCondition.fulfill(agreementId, did, lockAddress, amount, receiver, nft.address) + await lockCondition.fulfillMarked(agreementId, did, lockAddress, amount, receiver, nft.address) assert.strictEqual( (await conditionStoreManager.getConditionState(conditionId)).toNumber(), constants.condition.state.fulfilled ) await assert.isRejected( - lockCondition.fulfill(agreementId, did, lockAddress, amount, receiver, nft.address), + lockCondition.fulfillMarked(agreementId, did, lockAddress, amount, receiver, nft.address), undefined ) @@ -201,7 +202,7 @@ contract('NFTMarkedLockCondition', (accounts) => { await didRegistry.mint(did, amount) await nft.setApprovalForAll(lockCondition.address, true) - const hashValues = await lockCondition.hashValues(did, lockAddress, amount, receiver, nft.address) + const hashValues = await lockCondition.hashValuesMarked(did, lockAddress, amount, receiver, nft.address) const conditionId = await lockCondition.generateId(agreementId, hashValues) await conditionStoreManager.createCondition( @@ -216,7 +217,7 @@ contract('NFTMarkedLockCondition', (accounts) => { ) await assert.isRejected( - lockCondition.fulfill(agreementId, did, lockAddress, amount, receiver, nft.address), + lockCondition.fulfillMarked(agreementId, did, lockAddress, amount, receiver, nft.address), constants.acl.error.invalidUpdateRole ) }) diff --git a/test/unit/conditions/rewards/MultiEscrowPayment.Test.js b/test/unit/conditions/rewards/MultiEscrowPayment.Test.js index cc4acb63..21e72084 100644 --- a/test/unit/conditions/rewards/MultiEscrowPayment.Test.js +++ b/test/unit/conditions/rewards/MultiEscrowPayment.Test.js @@ -12,8 +12,8 @@ const DIDRegistry = artifacts.require('DIDRegistry') const ConditionStoreManager = artifacts.require('ConditionStoreManager') const NeverminedToken = artifacts.require('NeverminedToken') const LockPaymentCondition = artifacts.require('LockPaymentCondition') -const NFTMarkedLockCondition = artifacts.require('NFTMarkedLockCondition') -const NFT721MarkedLockCondition = artifacts.require('NFT721MarkedLockCondition') +const NFTLockCondition = artifacts.require('NFTLockCondition') +const NFT721LockCondition = artifacts.require('NFT721LockCondition') const EscrowPaymentCondition = artifacts.require('EscrowPaymentCondition') const NFTEscrowPaymentCondition = artifacts.require('NFTEscrowPaymentCondition') const NFT721EscrowPaymentCondition = artifacts.require('NFT721EscrowPaymentCondition') @@ -46,10 +46,28 @@ function tokenLockWrapper(contract) { function nftLockWrapper(contract) { contract.hashWrap = (did, escrowPaymentAddress, tokenAddress, amounts, receivers) => { - return contract.hashValues(did, escrowPaymentAddress, amounts[0], receivers[0], tokenAddress) + return contract.hashValuesMarked(did, escrowPaymentAddress, amounts[0], receivers[0], tokenAddress) } contract.fulfillWrap = (agreementId, did, escrowPaymentAddress, tokenAddress, amounts, receivers) => { - return contract.fulfill(agreementId, did, escrowPaymentAddress, amounts[0], receivers[0], tokenAddress) + return contract.fulfillMarked(agreementId, did, escrowPaymentAddress, amounts[0], receivers[0], tokenAddress) + } + contract.initWrap = (owner, conditionStoreManagerAddress, _didRegistryAddress, args) => { + return contract.initialize( + owner, + conditionStoreManagerAddress, + owner, + args + ) + } + return contract +} + +function nft721LockWrapper(contract) { + contract.hashWrap = (did, escrowPaymentAddress, tokenAddress, amounts, receivers) => { + return contract.hashValuesMarked(did, escrowPaymentAddress, amounts[0], receivers[0], tokenAddress) + } + contract.fulfillWrap = (agreementId, did, escrowPaymentAddress, tokenAddress, amounts, receivers) => { + return contract.fulfillMarked(agreementId, did, escrowPaymentAddress, amounts[0], receivers[0], tokenAddress) } contract.initWrap = (owner, conditionStoreManagerAddress, _didRegistryAddress, args) => { return contract.initialize( @@ -187,7 +205,7 @@ function nft721TokenWrapper(contract) { function testMultiEscrow(EscrowPaymentCondition, LockPaymentCondition, Token, nft, amount1, amount2, label) { contract(`EscrowPaymentCondition contract (multi) for ${label}`, (accounts) => { - const lockWrapper = nft ? nftLockWrapper : tokenLockWrapper + const lockWrapper = nft ? (amount2 === 0 ? nft721LockWrapper : nftLockWrapper) : tokenLockWrapper const escrowWrapper = nft ? nftEscrowWrapper : tokenEscrowWrapper const tokenWrapper = nft ? (amount2 === 0 ? nft721TokenWrapper : nftTokenWrapper) : tokenTokenWrapper @@ -488,5 +506,5 @@ function testMultiEscrow(EscrowPaymentCondition, LockPaymentCondition, Token, nf } testMultiEscrow(EscrowPaymentCondition, LockPaymentCondition, NeverminedToken, false, 10, 12, 'ERC-20') -testMultiEscrow(NFTEscrowPaymentCondition, NFTMarkedLockCondition, NFT, true, 10, 12, 'ERC-1155') -testMultiEscrow(NFT721EscrowPaymentCondition, NFT721MarkedLockCondition, NFT721, true, 1, 0, 'ERC-721') +testMultiEscrow(NFTEscrowPaymentCondition, NFTLockCondition, NFT, true, 10, 12, 'ERC-1155') +testMultiEscrow(NFT721EscrowPaymentCondition, NFT721LockCondition, NFT721, true, 1, 0, 'ERC-721')