Skip to content

Commit

Permalink
factorize match orders / boost functions with modifiers
Browse files Browse the repository at this point in the history
  • Loading branch information
gfournieriExec committed Apr 29, 2024
1 parent 9caa1f5 commit 91ecfa8
Show file tree
Hide file tree
Showing 2 changed files with 153 additions and 56 deletions.
124 changes: 77 additions & 47 deletions contracts/beacon/Voucher.sol
Original file line number Diff line number Diff line change
Expand Up @@ -30,6 +30,21 @@ contract Voucher is OwnableUpgradeable, IVoucher {
mapping(bytes32 dealId => uint256) _sponsoredAmounts;
}

modifier onlyAuthorized() {
VoucherStorage storage $ = _getVoucherStorage();
require(
msg.sender == owner() || $._authorizedAccounts[msg.sender],
"Voucher: sender is not authorized"
);
_;
}

modifier onlyNotExpired() {
VoucherStorage storage $ = _getVoucherStorage();
require(block.timestamp < $._expiration, "Voucher: voucher is expired");
_;
}

/// @custom:oz-upgrades-unsafe-allow constructor
constructor() {
_disableInitializers();
Expand Down Expand Up @@ -92,32 +107,17 @@ contract Voucher is OwnableUpgradeable, IVoucher {
) external returns (bytes32 dealId) {
// TODO add onlyAuthorized
// TODO check expiration
uint256 appPrice = appOrder.appprice;
uint256 datasetPrice = datasetOrder.datasetprice;
uint256 workerpoolPrice = workerpoolOrder.workerpoolprice;
VoucherStorage storage $ = _getVoucherStorage();
IVoucherHub voucherHub = IVoucherHub($._voucherHub);
uint256 sponsoredAmount = voucherHub.debitVoucher(
uint256 sponsoredAmount = _debitVoucherAndTransfer(
$._type,
appOrder.app,
appPrice,
datasetOrder.dataset,
datasetPrice,
workerpoolOrder.workerpool,
workerpoolPrice
$._voucherHub,
appOrder,
datasetOrder,
workerpoolOrder,
requestOrder
);
uint256 dealPrice = appPrice + datasetPrice + workerpoolPrice;
address iexecPoco = voucherHub.getIexecPoco();
if (sponsoredAmount != dealPrice) {
// Transfer non-sponsored amount from the iExec account of the
// requester to the iExec account of the voucher
IERC20(iexecPoco).transferFrom(
requestOrder.requester,
address(this),
dealPrice - sponsoredAmount
);
}
dealId = IexecPoco1(iexecPoco).sponsorMatchOrders(
dealId = IexecPoco1(voucherHub.getIexecPoco()).sponsorMatchOrders(
appOrder,
datasetOrder,
workerpoolOrder,
Expand All @@ -143,35 +143,18 @@ contract Voucher is OwnableUpgradeable, IVoucher {
IexecLibOrders_v5.DatasetOrder calldata datasetOrder,
IexecLibOrders_v5.WorkerpoolOrder calldata workerpoolOrder,
IexecLibOrders_v5.RequestOrder calldata requestOrder
) external returns (bytes32 dealId) {
// TODO add onlyAuthorized
// TODO check expiration
uint256 appPrice = appOrder.appprice;
uint256 datasetPrice = datasetOrder.datasetprice;
uint256 workerpoolPrice = workerpoolOrder.workerpoolprice;
) external onlyAuthorized onlyNotExpired returns (bytes32 dealId) {
VoucherStorage storage $ = _getVoucherStorage();
IVoucherHub voucherHub = IVoucherHub($._voucherHub);
uint256 sponsoredAmount = voucherHub.debitVoucher(
uint256 sponsoredAmount = _debitVoucherAndTransfer(
$._type,
appOrder.app,
appPrice,
datasetOrder.dataset,
datasetPrice,
workerpoolOrder.workerpool,
workerpoolPrice
$._voucherHub,
appOrder,
datasetOrder,
workerpoolOrder,
requestOrder
);
uint256 dealPrice = appPrice + datasetPrice + workerpoolPrice;
address iexecPoco = voucherHub.getIexecPoco();
if (sponsoredAmount != dealPrice) {
// Transfer non-sponsored amount from the iExec account of the
// requester to the iExec account of the voucher
IERC20(iexecPoco).transferFrom(
requestOrder.requester,
address(this),
dealPrice - sponsoredAmount
);
}
dealId = IexecPocoBoost(iexecPoco).sponsorMatchOrdersBoost(
dealId = IexecPocoBoost(voucherHub.getIexecPoco()).sponsorMatchOrdersBoost(
appOrder,
datasetOrder,
workerpoolOrder,
Expand Down Expand Up @@ -251,4 +234,51 @@ contract Voucher is OwnableUpgradeable, IVoucher {
$.slot := VOUCHER_STORAGE_LOCATION
}
}

/**
* @dev Debit voucher and transfer non-sponsored amount from requester's account.
*
* @param appOrder The app order.
* @param datasetOrder The dataset order.
* @param workerpoolOrder The workerpool order.
* @param requestOrder The request order.
*
* @return sponsoredAmount The amount sponsored by the voucher.
*/
function _debitVoucherAndTransfer(
uint256 _type,
address _voucherHub,
IexecLibOrders_v5.AppOrder calldata appOrder,
IexecLibOrders_v5.DatasetOrder calldata datasetOrder,
IexecLibOrders_v5.WorkerpoolOrder calldata workerpoolOrder,
IexecLibOrders_v5.RequestOrder calldata requestOrder
) private returns (uint256 sponsoredAmount) {
uint256 appPrice = appOrder.appprice;
uint256 datasetPrice = datasetOrder.datasetprice;
uint256 workerpoolPrice = workerpoolOrder.workerpoolprice;
IVoucherHub voucherHub = IVoucherHub(_voucherHub);

sponsoredAmount = voucherHub.debitVoucher(
_type,
appOrder.app,
appPrice,
datasetOrder.dataset,
datasetPrice,
workerpoolOrder.workerpool,
workerpoolPrice
);

uint256 dealPrice = appPrice + datasetPrice + workerpoolPrice;
address iexecPoco = voucherHub.getIexecPoco();

if (sponsoredAmount != dealPrice) {
// Transfer non-sponsored amount from the iExec account of the
// requester to the iExec account of the voucher
IERC20(iexecPoco).transferFrom(
requestOrder.requester,
address(this),
dealPrice - sponsoredAmount
);
}
}
}
85 changes: 76 additions & 9 deletions test/beacon/Voucher.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@
// SPDX-License-Identifier: Apache-2.0

import { SignerWithAddress } from '@nomicfoundation/hardhat-ethers/signers';
import { loadFixture } from '@nomicfoundation/hardhat-toolbox/network-helpers';
import { loadFixture, mine, time } from '@nomicfoundation/hardhat-toolbox/network-helpers';
import { expect } from 'chai';
import { ethers } from 'hardhat';
import * as commonUtils from '../../scripts/common';
Expand Down Expand Up @@ -61,6 +61,7 @@ describe('Voucher', function () {
voucherHubWithAssetEligibilityManagerSigner = voucherHub.connect(assetEligibilityManager);
voucherHubWithAnyoneSigner = voucherHub.connect(anyone);
await voucherHubWithAssetEligibilityManagerSigner.createVoucherType(description, duration);

await iexecPocoInstance
.transfer(await voucherHub.getAddress(), initVoucherHubBalance)
.then((tx) => tx.wait());
Expand Down Expand Up @@ -315,18 +316,20 @@ describe('Voucher', function () {
workerpoolprice: workerpoolPrice,
};
let requestOrder = { ...mockOrder };
let [voucherOwner1, requester]: SignerWithAddress[] = [];
let [voucherOwner1, requester, anyone]: SignerWithAddress[] = [];
let voucherHub: VoucherHub;
let voucher: Voucher;
let voucherWithOwnerSigner: Voucher;

beforeEach(async () => {
({ voucherHub, voucherOwner1, requester } = await loadFixture(deployFixture));
({ voucherHub, voucherOwner1, requester, anyone } = await loadFixture(deployFixture));
requestOrder.requester = requester.address;
voucher = await voucherHubWithVoucherManagerSigner
.createVoucher(voucherOwner1, voucherType, voucherValue)
.then((tx) => tx.wait())
.then(() => voucherHub.getVoucher(voucherOwner1))
.then((voucherAddress) => commonUtils.getVoucher(voucherAddress));
voucherWithOwnerSigner = voucher.connect(voucherOwner1);
});

it('Should match orders with full sponsored amount', async () => {
Expand Down Expand Up @@ -417,15 +420,20 @@ describe('Voucher', function () {
const requesterInitialSrlcBalanceBefore = await getRequesterBalanceOnIexecPoco();

expect(
await voucher.matchOrdersBoost.staticCall(
await voucherWithOwnerSigner.matchOrdersBoost.staticCall(
appOrder,
datasetOrder,
workerpoolOrder,
requestOrder,
),
).to.be.equal(dealId);
await expect(
voucher.matchOrdersBoost(appOrder, datasetOrder, workerpoolOrder, requestOrder),
voucherWithOwnerSigner.matchOrdersBoost(
appOrder,
datasetOrder,
workerpoolOrder,
requestOrder,
),
)
.to.emit(voucher, 'OrdersBoostMatchedWithVoucher')
.withArgs(dealId);
Expand Down Expand Up @@ -453,7 +461,12 @@ describe('Voucher', function () {
const requesterInitialSrlcBalance = await getRequesterBalanceOnIexecPoco();

await expect(
voucher.matchOrdersBoost(appOrder, datasetOrder, workerpoolOrder, requestOrder),
voucherWithOwnerSigner.matchOrdersBoost(
appOrder,
datasetOrder,
workerpoolOrder,
requestOrder,
),
)
.to.emit(iexecPocoInstance, 'Transfer')
.withArgs(requester.address, await voucher.getAddress(), dealPrice)
Expand All @@ -468,9 +481,38 @@ describe('Voucher', function () {
expect(await voucher.getSponsoredAmount(dealId)).to.equal(0);
});

it('Should match orders boost with an authorized account', async () => {
for (const asset of [app, dataset, workerpool]) {
await voucherHubWithAssetEligibilityManagerSigner
.addEligibleAsset(voucherType, asset)
.then((x) => x.wait());
}
const authorizationTx = await voucherWithOwnerSigner.authorizeAccount(
anyone.address,
);
await authorizationTx.wait();

const voucherWithAnyoneSigner = voucher.connect(anyone);
await expect(
voucherWithAnyoneSigner.matchOrdersBoost(
appOrder,
datasetOrder,
workerpoolOrder,
requestOrder,
),
)
.to.emit(voucher, 'OrdersBoostMatchedWithVoucher')
.withArgs(dealId);
});

it('Should not match orders boost when non-sponsored amount not transferable', async () => {
await expect(
voucher.matchOrdersBoost(appOrder, datasetOrder, workerpoolOrder, requestOrder),
voucherWithOwnerSigner.matchOrdersBoost(
appOrder,
datasetOrder,
workerpoolOrder,
requestOrder,
),
)
.to.be.revertedWithCustomError(iexecPocoInstance, 'ERC20InsufficientAllowance')
.withArgs(await voucher.getAddress(), 0, dealPrice);
Expand All @@ -480,16 +522,41 @@ describe('Voucher', function () {
await iexecPocoInstance
.willRevertOnSponsorMatchOrdersBoost()
.then((tx) => tx.wait());

await expect(
voucher.matchOrdersBoost(
voucherWithOwnerSigner.matchOrdersBoost(
{ ...appOrder, appprice: 0 },
{ ...datasetOrder, datasetprice: 0 },
{ ...workerpoolOrder, workerpoolprice: 0 },
requestOrder,
),
).to.be.revertedWith('IexecPocoMock: Failed to sponsorMatchOrdersBoost');
});
it('Should not match orders boost when sender is not allowed', async () => {
const voucherWithAnyoneSigner = voucher.connect(anyone);
await expect(
voucherWithAnyoneSigner.matchOrdersBoost(
appOrder,
datasetOrder,
workerpoolOrder,
requestOrder,
),
).to.be.revertedWith('Voucher: sender is not authorized');
});

it('Should not match orders boost when voucher is expired', async () => {
const expirationDate = (await voucher.getExpiration()) + BigInt(10);
await time.setNextBlockTimestamp(expirationDate);
// Mine empty block to get to voucher expiration time.
await mine();
await expect(
voucherWithOwnerSigner.matchOrdersBoost(
appOrder,
datasetOrder,
workerpoolOrder,
requestOrder,
),
).to.be.revertedWith('Voucher: voucher is expired');
});
});
});
});

0 comments on commit 91ecfa8

Please sign in to comment.