Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@
## vNEXT

- Remove `smock` from unit tests:
- IexecEscrow.v8 (#154)
- IexecPocoDelegate (#149, #151)
- IexecPocoBoost (#148, #150, #153)
- Migrate unit test files to Typescript & Hardhat:
Expand Down
17 changes: 17 additions & 0 deletions contracts/tools/testing/IexecEscrowTestContract.sol
Original file line number Diff line number Diff line change
Expand Up @@ -25,4 +25,21 @@ contract IexecEscrowTestContract is IexecEscrow {
function seize_(address account, uint256 value, bytes32 ref) external {
seize(account, value, ref);
}

// Helper functions used in unit tests.

function setBalance(address account, uint256 value) external {
m_balances[account] = value;
}

// TODO remove the following function and inherit `IexecAccessorsDelegate`
// when it is migrated to solidity v8.

function balanceOf(address account) external view returns (uint256) {
return m_balances[account];
}

function frozenOf(address account) external view returns (uint256) {
return m_frozens[account];
}
}
30 changes: 2 additions & 28 deletions deploy/0_deploy.ts
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,6 @@ import { SignerWithAddress } from '@nomiclabs/hardhat-ethers/signers';
import fs from 'fs';
import hre, { ethers } from 'hardhat';
import path from 'path';
import { getFunctionSignatures } from '../migrations/utils/getFunctionSignatures';
import {
AppRegistry__factory,
DatasetRegistry__factory,
Expand Down Expand Up @@ -40,8 +39,8 @@ import {
} from '../typechain';
import { Ownable__factory } from '../typechain/factories/@openzeppelin/contracts/access';
import { FactoryDeployerHelper } from '../utils/FactoryDeployerHelper';
import { getBaseNameFromContractFactory } from '../utils/deploy-tools';
import { Category } from '../utils/poco-tools';
import { linkContractToProxy } from '../utils/proxy-tools';
const CONFIG = require('../config/config.json');

/**
Expand Down Expand Up @@ -231,34 +230,9 @@ async function getOrDeployRlc(token: string, owner: SignerWithAddress) {
});
}

/**
* Link a contract to an ERC1538 proxy.
* @param proxy contract to ERC1538 proxy.
* @param contractAddress The contract address to link to the proxy.
* @param contractFactory The contract factory to link to the proxy.
*/
async function linkContractToProxy(
proxy: ERC1538Update,
contractAddress: string,
contractFactory: any,
) {
const contractName = getBaseNameFromContractFactory(contractFactory);
await proxy
.updateContract(
contractAddress,
// TODO: Use contractFactory.interface.functions when moving to ethers@v6
// https://github.com/ethers-io/ethers.js/issues/1069
getFunctionSignatures(contractFactory.constructor.abi),
'Linking ' + contractName,
)
.then((tx) => tx.wait())
.catch(() => {
throw new Error(`Failed to link ${contractName}`);
});
}

// TODO [optional]: Use hardhat-deploy to save addresses automatically
// https://github.com/wighawag/hardhat-deploy/tree/master#hardhat-deploy-in-a-nutshell
// TODO remove this.
/**
* Save addresses of deployed contracts (since hardhat does not do it for us).
* @param contractName contract name to deploy
Expand Down
166 changes: 69 additions & 97 deletions test/byContract/IexecPocoBoost/IexecEscrow.v8.test.ts
Original file line number Diff line number Diff line change
@@ -1,60 +1,51 @@
import { MockContract, smock } from '@defi-wonderland/smock';
// SPDX-FileCopyrightText: 2023-2024 IEXEC BLOCKCHAIN TECH <contact@iex.ec>
// SPDX-License-Identifier: Apache-2.0

import { loadFixture } from '@nomicfoundation/hardhat-network-helpers';
import { SignerWithAddress } from '@nomiclabs/hardhat-ethers/signers';
import { constants } from 'ethers';
import { ethers, expect } from 'hardhat';
import { expect } from 'hardhat';
import { IexecEscrowTestContract, IexecEscrowTestContract__factory } from '../../../typechain';
import { getIexecAccounts } from '../../../utils/poco-tools';

const userBalance = 1000;
const amount = 3;
const ref = constants.HashZero; // TODO remove this and use HashZero

const BALANCES = 'm_balances';
const FROZENS = 'm_frozens';
let iexecEscrow: IexecEscrowTestContract;
let user: SignerWithAddress;

describe('IexecEscrow.v8', function () {
// Using 0 causes an error when checking initial balance.
const initialEscrowBalance = 10;
const initialUserBalance = 20;
const initialUserFrozen = 30;
const amount = 3;
const ref = constants.HashZero;

let deployer: SignerWithAddress;
let user: SignerWithAddress;
let iexecEscrow: MockContract<IexecEscrowTestContract>;

beforeEach(async function () {
// Get wallets.
[deployer, user] = await ethers.getSigners();
// Deploy the contract to be tested as a mock.
iexecEscrow = (await smock
.mock<IexecEscrowTestContract__factory>('IexecEscrowTestContract')
.then((instance) => instance.deploy())
.then((contract) => contract.deployed())) as MockContract<IexecEscrowTestContract>;
// Set initial state of contract.
await iexecEscrow.setVariables({
[BALANCES]: {
[iexecEscrow.address]: initialEscrowBalance,
[user.address]: initialUserBalance,
},
[FROZENS]: {
[user.address]: initialUserFrozen,
},
});
beforeEach('Deploy', async () => {
// Initialize test environment
await loadFixture(initFixture);
});

async function initFixture() {
user = (await getIexecAccounts()).anyone;
// Deploy test contract to make internal escrow functions accessible.
iexecEscrow = await new IexecEscrowTestContract__factory()
.connect(user) // Anyone works.
.deploy()
.then((contract) => contract.deployed());
// Deposit some funds in the user's account.
await iexecEscrow.setBalance(user.address, userBalance).then((tx) => tx.wait());
}

describe('Lock', function () {
it('Should lock funds', async function () {
// Check balances before the operation.
await checkInitialBalancesAndFrozens();
// Run operation.
const frozenBefore = (await iexecEscrow.frozenOf(user.address)).toNumber();
await expect(iexecEscrow.lock_(user.address, amount))
.to.changeTokenBalances(
iexecEscrow,
[iexecEscrow.address, user.address],
[amount, -amount],
)
.to.emit(iexecEscrow, 'Transfer')
.withArgs(user.address, iexecEscrow.address, amount)
.to.emit(iexecEscrow, 'Lock')
.withArgs(user.address, amount);
// Check balances after the operation.
await checkBalancesAndFrozens(
initialEscrowBalance + amount,
initialUserBalance - amount,
initialUserFrozen + amount,
);
expect(await iexecEscrow.frozenOf(user.address)).to.equal(frozenBefore + amount);
});

it('Should not lock funds for empty address', async function () {
Expand All @@ -64,28 +55,29 @@ describe('IexecEscrow.v8', function () {
});

it('Should not lock funds when insufficient balance', async function () {
await expect(
iexecEscrow.lock_(user.address, initialUserBalance + 1),
).to.be.revertedWith('IexecEscrow: Transfer amount exceeds balance');
await expect(iexecEscrow.lock_(user.address, userBalance + 1)).to.be.revertedWith(
'IexecEscrow: Transfer amount exceeds balance',
);
});
});

describe('Unlock', function () {
it('Should unlock funds', async function () {
// Check balances before the operation.
await checkInitialBalancesAndFrozens();
// Run operation.
// Lock some user funds to be able to unlock.
await iexecEscrow.lock_(user.address, userBalance).then((tx) => tx.wait());

const frozenBefore = (await iexecEscrow.frozenOf(user.address)).toNumber();
await expect(iexecEscrow.unlock_(user.address, amount))
.to.changeTokenBalances(
iexecEscrow,
[iexecEscrow.address, user.address],
[-amount, amount],
)
.to.emit(iexecEscrow, 'Transfer')
.withArgs(iexecEscrow.address, user.address, amount)
.to.emit(iexecEscrow, 'Unlock')
.withArgs(user.address, amount);
// Check balances after the operation.
await checkBalancesAndFrozens(
initialEscrowBalance - amount,
initialUserBalance + amount,
initialUserFrozen - amount,
);
expect(await iexecEscrow.frozenOf(user.address)).to.equal(frozenBefore - amount);
});

it('Should not unlock funds for empty address', async function () {
Expand All @@ -95,28 +87,27 @@ describe('IexecEscrow.v8', function () {
});

it('Should not unlock funds when insufficient balance', async function () {
await expect(
iexecEscrow.unlock_(user.address, initialUserFrozen + 1),
).to.be.revertedWith('IexecEscrow: Transfer amount exceeds balance');
await expect(iexecEscrow.unlock_(user.address, amount)).to.be.revertedWith(
'IexecEscrow: Transfer amount exceeds balance',
);
});
});

describe('Reward', function () {
it('Should reward', async function () {
// Check balances before the operation.
await checkInitialBalancesAndFrozens();
// Run operation.
// Fund iexecEscrow so it can reward the user.
await iexecEscrow.setBalance(iexecEscrow.address, amount).then((tx) => tx.wait());

await expect(iexecEscrow.reward_(user.address, amount, ref))
.to.changeTokenBalances(
iexecEscrow,
[iexecEscrow.address, user.address],
[-amount, amount],
)
.to.emit(iexecEscrow, 'Transfer')
.withArgs(iexecEscrow.address, user.address, amount)
.to.emit(iexecEscrow, 'Reward')
.withArgs(user.address, amount, ref);
// Check balances after the operation.
await checkBalancesAndFrozens(
initialEscrowBalance - amount,
initialUserBalance + amount,
initialUserFrozen,
);
});

it('Should not reward empty address', async function () {
Expand All @@ -126,26 +117,23 @@ describe('IexecEscrow.v8', function () {
});

it('Should not reward when insufficient balance', async function () {
await expect(
iexecEscrow.reward_(user.address, initialEscrowBalance + 1, ref),
).to.be.revertedWith('IexecEscrow: Transfer amount exceeds balance');
await expect(iexecEscrow.reward_(user.address, amount, ref)).to.be.revertedWith(
'IexecEscrow: Transfer amount exceeds balance',
);
});
});

describe('Seize', function () {
it('Should seize funds', async function () {
// Check balances before the operation.
await checkInitialBalancesAndFrozens();
// Run operation.
// Lock some user funds to be able to seize.
await iexecEscrow.lock_(user.address, userBalance).then((tx) => tx.wait());

const frozenBefore = (await iexecEscrow.frozenOf(user.address)).toNumber();
await expect(iexecEscrow.seize_(user.address, amount, ref))
.to.changeTokenBalances(iexecEscrow, [iexecEscrow.address, user.address], [0, 0])
.to.emit(iexecEscrow, 'Seize')
.withArgs(user.address, amount, ref);
// Check balances after the operation.
await checkBalancesAndFrozens(
initialEscrowBalance,
initialUserBalance,
initialUserFrozen - amount,
);
expect(await iexecEscrow.frozenOf(user.address)).to.equal(frozenBefore - amount);
});

it('Should not seize funds for empty address', async function () {
Expand All @@ -155,25 +143,9 @@ describe('IexecEscrow.v8', function () {
});

it('Should not seize funds when insufficient balance', async function () {
await expect(
iexecEscrow.seize_(user.address, initialUserFrozen + 1, ref),
).to.be.revertedWithPanic(0x11);
await expect(iexecEscrow.seize_(user.address, amount, ref)).to.be.revertedWithPanic(
0x11,
);
});
});

async function checkInitialBalancesAndFrozens() {
checkBalancesAndFrozens(initialEscrowBalance, initialUserBalance, initialUserFrozen);
}

async function checkBalancesAndFrozens(
escrowBalance: number,
userBalance: number,
userFrozen: number,
) {
expect(await iexecEscrow.getVariable(BALANCES, [iexecEscrow.address])).to.be.equal(
escrowBalance,
);
expect(await iexecEscrow.getVariable(BALANCES, [user.address])).to.be.equal(userBalance);
expect(await iexecEscrow.getVariable(FROZENS, [user.address])).to.be.equal(userFrozen);
}
});
2 changes: 1 addition & 1 deletion utils/deploy-tools.ts
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@ import { SignerWithAddress } from '@nomiclabs/hardhat-ethers/signers';
import { deployments } from 'hardhat';

/**
* Deploy a contract.
* Deploy a contract and save its deployment.
* @param contractFactory The contract to deploy
* @param deployer The signer to deploy the contract
* @param constructorArgs Arguments passed to the contract constructor at deployment
Expand Down
32 changes: 32 additions & 0 deletions utils/proxy-tools.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,32 @@
// SPDX-FileCopyrightText: 2024 IEXEC BLOCKCHAIN TECH <contact@iex.ec>
// SPDX-License-Identifier: Apache-2.0

import { getFunctionSignatures } from '../migrations/utils/getFunctionSignatures';
import { ERC1538Update } from '../typechain';
import { getBaseNameFromContractFactory } from '../utils/deploy-tools';

/**
* Link a contract to an ERC1538 proxy.
* @param proxy contract to ERC1538 proxy.
* @param contractAddress The contract address to link to the proxy.
* @param contractFactory The contract factory to link to the proxy.
*/
export async function linkContractToProxy(
proxy: ERC1538Update,
contractAddress: string,
contractFactory: any,
) {
const contractName = getBaseNameFromContractFactory(contractFactory);
await proxy
.updateContract(
contractAddress,
// TODO: Use contractFactory.interface.functions when moving to ethers@v6
// https://github.com/ethers-io/ethers.js/issues/1069
getFunctionSignatures(contractFactory.constructor.abi),
'Linking ' + contractName,
)
.then((tx) => tx.wait())
.catch(() => {
throw new Error(`Failed to link ${contractName}`);
});
}