Skip to content

Refactor and fix async operations in ERC2771Context.test.js #5532

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Open
wants to merge 1 commit into
base: master
Choose a base branch
from
Open
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
92 changes: 34 additions & 58 deletions test/metatx/ERC2771Context.test.js
Original file line number Diff line number Diff line change
Expand Up @@ -15,9 +15,20 @@ async function fixture() {
const forwarderAsSigner = await impersonate(forwarder.target);
const context = await ethers.deployContract('ERC2771ContextMock', [forwarder]);
const domain = await getDomain(forwarder);
const types = { ForwardRequest };

return { sender, other, forwarder, forwarderAsSigner, context, domain, types };
const prepareAndSignRequest = async (signer, request) => {
// request.to is mandatory
request.from ??= signer.address;
request.value ??= 0n;
request.data ??= '0x';
request.gas ??= 100000n;
request.nonce ??= await forwarder.nonces(signer);
request.deadline ??= MAX_UINT48;
request.signature = await signer.signTypedData(domain, { ForwardRequest }, request);
return request;
};

return { sender, other, forwarder, forwarderAsSigner, context, prepareAndSignRequest };
}

describe('ERC2771Context', function () {
Expand All @@ -26,11 +37,11 @@ describe('ERC2771Context', function () {
});

it('recognize trusted forwarder', async function () {
expect(await this.context.isTrustedForwarder(this.forwarder)).to.be.true;
await expect(this.context.isTrustedForwarder(this.forwarder)).to.eventually.be.true;
});

it('returns the trusted forwarder', async function () {
expect(await this.context.trustedForwarder()).to.equal(this.forwarder);
await expect(this.context.trustedForwarder()).to.eventually.equal(this.forwarder);
});

describe('when called directly', function () {
Expand All @@ -40,23 +51,12 @@ describe('ERC2771Context', function () {
describe('when receiving a relayed call', function () {
describe('msgSender', function () {
it('returns the relayed transaction original sender', async function () {
const nonce = await this.forwarder.nonces(this.sender);
const data = this.context.interface.encodeFunctionData('msgSender');

const req = {
from: await this.sender.getAddress(),
to: await this.context.getAddress(),
value: 0n,
data,
gas: 100000n,
nonce,
deadline: MAX_UINT48,
};

req.signature = await this.sender.signTypedData(this.domain, this.types, req);

expect(await this.forwarder.verify(req)).to.be.true;
const req = await this.prepareAndSignRequest(this.sender, {
to: this.context.target,
data: this.context.interface.encodeFunctionData('msgSender'),
});

await expect(this.forwarder.verify(req)).to.eventually.be.true;
await expect(this.forwarder.execute(req)).to.emit(this.context, 'Sender').withArgs(this.sender);
});

Expand All @@ -72,62 +72,38 @@ describe('ERC2771Context', function () {
it('returns the relayed transaction original data', async function () {
const args = [42n, 'OpenZeppelin'];

const nonce = await this.forwarder.nonces(this.sender);
const data = this.context.interface.encodeFunctionData('msgData', args);

const req = {
from: await this.sender.getAddress(),
to: await this.context.getAddress(),
value: 0n,
data,
gas: 100000n,
nonce,
deadline: MAX_UINT48,
};

req.signature = this.sender.signTypedData(this.domain, this.types, req);

expect(await this.forwarder.verify(req)).to.be.true;
const req = await this.prepareAndSignRequest(this.sender, {
to: this.context.target,
data: this.context.interface.encodeFunctionData('msgData', args),
});

await expect(this.forwarder.verify(req)).to.eventually.be.true;
await expect(this.forwarder.execute(req))
.to.emit(this.context, 'Data')
.withArgs(data, ...args);
.withArgs(req.data, ...args);
});
});

it('returns the full original data when calldata length is less than 20 bytes (address length)', async function () {
const data = this.context.interface.encodeFunctionData('msgDataShort');

// The forwarder doesn't produce calls with calldata length less than 20 bytes so `this.forwarderAsSigner` is used instead.
await expect(await this.context.connect(this.forwarderAsSigner).msgDataShort())
await expect(this.context.connect(this.forwarderAsSigner).msgDataShort())
.to.emit(this.context, 'DataShort')
.withArgs(data);
});
});

it('multicall poison attack', async function () {
const nonce = await this.forwarder.nonces(this.sender);
const data = this.context.interface.encodeFunctionData('multicall', [
[
const req = await this.prepareAndSignRequest(this.sender, {
to: this.context.target,
data: this.context.interface.encodeFunctionData('multicall', [
// poisonned call to 'msgSender()'
ethers.concat([this.context.interface.encodeFunctionData('msgSender'), this.other.address]),
],
]);

const req = {
from: await this.sender.getAddress(),
to: await this.context.getAddress(),
value: 0n,
data,
gas: 100000n,
nonce,
deadline: MAX_UINT48,
};

req.signature = await this.sender.signTypedData(this.domain, this.types, req);

expect(await this.forwarder.verify(req)).to.be.true;
[ethers.concat([this.context.interface.encodeFunctionData('msgSender'), this.other.address])],
]),
});

await expect(this.forwarder.verify(req)).to.eventually.be.true;
await expect(this.forwarder.execute(req)).to.emit(this.context, 'Sender').withArgs(this.sender);
});
});
Loading