dexorder
This commit is contained in:
133
lib_openzeppelin_contracts/test/metatx/ERC2771Context.test.js
Normal file
133
lib_openzeppelin_contracts/test/metatx/ERC2771Context.test.js
Normal file
@@ -0,0 +1,133 @@
|
||||
const { ethers } = require('hardhat');
|
||||
const { expect } = require('chai');
|
||||
const { loadFixture } = require('@nomicfoundation/hardhat-network-helpers');
|
||||
|
||||
const { impersonate } = require('../helpers/account');
|
||||
const { getDomain, ForwardRequest } = require('../helpers/eip712');
|
||||
const { MAX_UINT48 } = require('../helpers/constants');
|
||||
|
||||
const { shouldBehaveLikeRegularContext } = require('../utils/Context.behavior');
|
||||
|
||||
async function fixture() {
|
||||
const [sender, other] = await ethers.getSigners();
|
||||
|
||||
const forwarder = await ethers.deployContract('ERC2771Forwarder', []);
|
||||
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 };
|
||||
}
|
||||
|
||||
describe('ERC2771Context', function () {
|
||||
beforeEach(async function () {
|
||||
Object.assign(this, await loadFixture(fixture));
|
||||
});
|
||||
|
||||
it('recognize trusted forwarder', async function () {
|
||||
expect(await this.context.isTrustedForwarder(this.forwarder)).to.be.true;
|
||||
});
|
||||
|
||||
it('returns the trusted forwarder', async function () {
|
||||
expect(await this.context.trustedForwarder()).to.equal(this.forwarder);
|
||||
});
|
||||
|
||||
describe('when called directly', function () {
|
||||
shouldBehaveLikeRegularContext();
|
||||
});
|
||||
|
||||
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;
|
||||
|
||||
await expect(this.forwarder.execute(req)).to.emit(this.context, 'Sender').withArgs(this.sender);
|
||||
});
|
||||
|
||||
it('returns the original sender when calldata length is less than 20 bytes (address length)', async function () {
|
||||
// The forwarder doesn't produce calls with calldata length less than 20 bytes so `this.forwarderAsSigner` is used instead.
|
||||
await expect(this.context.connect(this.forwarderAsSigner).msgSender())
|
||||
.to.emit(this.context, 'Sender')
|
||||
.withArgs(this.forwarder);
|
||||
});
|
||||
});
|
||||
|
||||
describe('msgData', 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;
|
||||
|
||||
await expect(this.forwarder.execute(req))
|
||||
.to.emit(this.context, 'Data')
|
||||
.withArgs(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())
|
||||
.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', [
|
||||
[
|
||||
// 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;
|
||||
|
||||
await expect(this.forwarder.execute(req)).to.emit(this.context, 'Sender').withArgs(this.sender);
|
||||
});
|
||||
});
|
||||
Reference in New Issue
Block a user