ERC1363.test.js 13 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370
  1. const { ethers } = require('hardhat');
  2. const { expect } = require('chai');
  3. const { loadFixture } = require('@nomicfoundation/hardhat-network-helpers');
  4. const {
  5. shouldBehaveLikeERC20,
  6. shouldBehaveLikeERC20Transfer,
  7. shouldBehaveLikeERC20Approve,
  8. } = require('../ERC20.behavior.js');
  9. const { shouldSupportInterfaces } = require('../../../utils/introspection/SupportsInterface.behavior');
  10. const { RevertType } = require('../../../helpers/enums.js');
  11. const name = 'My Token';
  12. const symbol = 'MTKN';
  13. const value = 1000n;
  14. const data = '0x123456';
  15. async function fixture() {
  16. // this.accounts is used by shouldBehaveLikeERC20
  17. const accounts = await ethers.getSigners();
  18. const [holder, other] = accounts;
  19. const receiver = await ethers.deployContract('ERC1363ReceiverMock');
  20. const spender = await ethers.deployContract('ERC1363SpenderMock');
  21. const token = await ethers.deployContract('$ERC1363', [name, symbol]);
  22. await token.$_mint(holder, value);
  23. return {
  24. accounts,
  25. holder,
  26. other,
  27. token,
  28. receiver,
  29. spender,
  30. selectors: {
  31. onTransferReceived: receiver.interface.getFunction('onTransferReceived(address,address,uint256,bytes)').selector,
  32. onApprovalReceived: spender.interface.getFunction('onApprovalReceived(address,uint256,bytes)').selector,
  33. },
  34. };
  35. }
  36. describe('ERC1363', function () {
  37. beforeEach(async function () {
  38. Object.assign(this, await loadFixture(fixture));
  39. });
  40. shouldSupportInterfaces(['ERC165', 'ERC1363']);
  41. shouldBehaveLikeERC20(value);
  42. describe('transferAndCall', function () {
  43. describe('as a transfer', function () {
  44. beforeEach(async function () {
  45. this.recipient = this.receiver;
  46. this.transfer = (holder, ...rest) =>
  47. this.token.connect(holder).getFunction('transferAndCall(address,uint256)')(...rest);
  48. });
  49. shouldBehaveLikeERC20Transfer(value);
  50. });
  51. it('reverts transferring to an EOA', async function () {
  52. await expect(this.token.connect(this.holder).getFunction('transferAndCall(address,uint256)')(this.other, value))
  53. .to.be.revertedWithCustomError(this.token, 'ERC1363InvalidReceiver')
  54. .withArgs(this.other.address);
  55. });
  56. it('succeeds without data', async function () {
  57. await expect(
  58. this.token.connect(this.holder).getFunction('transferAndCall(address,uint256)')(this.receiver, value),
  59. )
  60. .to.emit(this.token, 'Transfer')
  61. .withArgs(this.holder.address, this.receiver.target, value)
  62. .to.emit(this.receiver, 'Received')
  63. .withArgs(this.holder.address, this.holder.address, value, '0x');
  64. });
  65. it('succeeds with data', async function () {
  66. await expect(
  67. this.token.connect(this.holder).getFunction('transferAndCall(address,uint256,bytes)')(
  68. this.receiver,
  69. value,
  70. data,
  71. ),
  72. )
  73. .to.emit(this.token, 'Transfer')
  74. .withArgs(this.holder.address, this.receiver.target, value)
  75. .to.emit(this.receiver, 'Received')
  76. .withArgs(this.holder.address, this.holder.address, value, data);
  77. });
  78. it('reverts with reverting hook (without reason)', async function () {
  79. await this.receiver.setUp(this.selectors.onTransferReceived, RevertType.RevertWithoutMessage);
  80. await expect(
  81. this.token.connect(this.holder).getFunction('transferAndCall(address,uint256,bytes)')(
  82. this.receiver,
  83. value,
  84. data,
  85. ),
  86. )
  87. .to.be.revertedWithCustomError(this.token, 'ERC1363InvalidReceiver')
  88. .withArgs(this.receiver.target);
  89. });
  90. it('reverts with reverting hook (with reason)', async function () {
  91. await this.receiver.setUp(this.selectors.onTransferReceived, RevertType.RevertWithMessage);
  92. await expect(
  93. this.token.connect(this.holder).getFunction('transferAndCall(address,uint256,bytes)')(
  94. this.receiver,
  95. value,
  96. data,
  97. ),
  98. ).to.be.revertedWith('ERC1363ReceiverMock: reverting');
  99. });
  100. it('reverts with reverting hook (with custom error)', async function () {
  101. const reason = '0x12345678';
  102. await this.receiver.setUp(reason, RevertType.RevertWithCustomError);
  103. await expect(
  104. this.token.connect(this.holder).getFunction('transferAndCall(address,uint256,bytes)')(
  105. this.receiver,
  106. value,
  107. data,
  108. ),
  109. )
  110. .to.be.revertedWithCustomError(this.receiver, 'CustomError')
  111. .withArgs(reason);
  112. });
  113. it('panics with reverting hook (with panic)', async function () {
  114. await this.receiver.setUp(this.selectors.onTransferReceived, RevertType.Panic);
  115. await expect(
  116. this.token.connect(this.holder).getFunction('transferAndCall(address,uint256,bytes)')(
  117. this.receiver,
  118. value,
  119. data,
  120. ),
  121. ).to.be.revertedWithPanic();
  122. });
  123. it('reverts with bad return value', async function () {
  124. await this.receiver.setUp('0x12345678', RevertType.None);
  125. await expect(
  126. this.token.connect(this.holder).getFunction('transferAndCall(address,uint256,bytes)')(
  127. this.receiver,
  128. value,
  129. data,
  130. ),
  131. )
  132. .to.be.revertedWithCustomError(this.token, 'ERC1363InvalidReceiver')
  133. .withArgs(this.receiver.target);
  134. });
  135. });
  136. describe('transferFromAndCall', function () {
  137. beforeEach(async function () {
  138. await this.token.connect(this.holder).approve(this.other, ethers.MaxUint256);
  139. });
  140. describe('as a transfer', function () {
  141. beforeEach(async function () {
  142. this.recipient = this.receiver;
  143. this.transfer = this.token.connect(this.other).getFunction('transferFromAndCall(address,address,uint256)');
  144. });
  145. shouldBehaveLikeERC20Transfer(value);
  146. });
  147. it('reverts transferring to an EOA', async function () {
  148. await expect(
  149. this.token.connect(this.other).getFunction('transferFromAndCall(address,address,uint256)')(
  150. this.holder,
  151. this.other,
  152. value,
  153. ),
  154. )
  155. .to.be.revertedWithCustomError(this.token, 'ERC1363InvalidReceiver')
  156. .withArgs(this.other.address);
  157. });
  158. it('succeeds without data', async function () {
  159. await expect(
  160. this.token.connect(this.other).getFunction('transferFromAndCall(address,address,uint256)')(
  161. this.holder,
  162. this.receiver,
  163. value,
  164. ),
  165. )
  166. .to.emit(this.token, 'Transfer')
  167. .withArgs(this.holder.address, this.receiver.target, value)
  168. .to.emit(this.receiver, 'Received')
  169. .withArgs(this.other.address, this.holder.address, value, '0x');
  170. });
  171. it('succeeds with data', async function () {
  172. await expect(
  173. this.token.connect(this.other).getFunction('transferFromAndCall(address,address,uint256,bytes)')(
  174. this.holder,
  175. this.receiver,
  176. value,
  177. data,
  178. ),
  179. )
  180. .to.emit(this.token, 'Transfer')
  181. .withArgs(this.holder.address, this.receiver.target, value)
  182. .to.emit(this.receiver, 'Received')
  183. .withArgs(this.other.address, this.holder.address, value, data);
  184. });
  185. it('reverts with reverting hook (without reason)', async function () {
  186. await this.receiver.setUp(this.selectors.onTransferReceived, RevertType.RevertWithoutMessage);
  187. await expect(
  188. this.token.connect(this.other).getFunction('transferFromAndCall(address,address,uint256,bytes)')(
  189. this.holder,
  190. this.receiver,
  191. value,
  192. data,
  193. ),
  194. )
  195. .to.be.revertedWithCustomError(this.token, 'ERC1363InvalidReceiver')
  196. .withArgs(this.receiver.target);
  197. });
  198. it('reverts with reverting hook (with reason)', async function () {
  199. await this.receiver.setUp(this.selectors.onTransferReceived, RevertType.RevertWithMessage);
  200. await expect(
  201. this.token.connect(this.other).getFunction('transferFromAndCall(address,address,uint256,bytes)')(
  202. this.holder,
  203. this.receiver,
  204. value,
  205. data,
  206. ),
  207. ).to.be.revertedWith('ERC1363ReceiverMock: reverting');
  208. });
  209. it('reverts with reverting hook (with custom error)', async function () {
  210. const reason = '0x12345678';
  211. await this.receiver.setUp(reason, RevertType.RevertWithCustomError);
  212. await expect(
  213. this.token.connect(this.other).getFunction('transferFromAndCall(address,address,uint256,bytes)')(
  214. this.holder,
  215. this.receiver,
  216. value,
  217. data,
  218. ),
  219. )
  220. .to.be.revertedWithCustomError(this.receiver, 'CustomError')
  221. .withArgs(reason);
  222. });
  223. it('panics with reverting hook (with panic)', async function () {
  224. await this.receiver.setUp(this.selectors.onTransferReceived, RevertType.Panic);
  225. await expect(
  226. this.token.connect(this.other).getFunction('transferFromAndCall(address,address,uint256,bytes)')(
  227. this.holder,
  228. this.receiver,
  229. value,
  230. data,
  231. ),
  232. ).to.be.revertedWithPanic();
  233. });
  234. it('reverts with bad return value', async function () {
  235. await this.receiver.setUp('0x12345678', RevertType.None);
  236. await expect(
  237. this.token.connect(this.other).getFunction('transferFromAndCall(address,address,uint256,bytes)')(
  238. this.holder,
  239. this.receiver,
  240. value,
  241. data,
  242. ),
  243. )
  244. .to.be.revertedWithCustomError(this.token, 'ERC1363InvalidReceiver')
  245. .withArgs(this.receiver.target);
  246. });
  247. });
  248. describe('approveAndCall', function () {
  249. describe('as an approval', function () {
  250. beforeEach(async function () {
  251. this.recipient = this.spender;
  252. this.approve = (holder, ...rest) =>
  253. this.token.connect(holder).getFunction('approveAndCall(address,uint256)')(...rest);
  254. });
  255. shouldBehaveLikeERC20Approve(value);
  256. });
  257. it('reverts approving an EOA', async function () {
  258. await expect(this.token.connect(this.holder).getFunction('approveAndCall(address,uint256)')(this.other, value))
  259. .to.be.revertedWithCustomError(this.token, 'ERC1363InvalidSpender')
  260. .withArgs(this.other.address);
  261. });
  262. it('succeeds without data', async function () {
  263. await expect(this.token.connect(this.holder).getFunction('approveAndCall(address,uint256)')(this.spender, value))
  264. .to.emit(this.token, 'Approval')
  265. .withArgs(this.holder.address, this.spender.target, value)
  266. .to.emit(this.spender, 'Approved')
  267. .withArgs(this.holder.address, value, '0x');
  268. });
  269. it('succeeds with data', async function () {
  270. await expect(
  271. this.token.connect(this.holder).getFunction('approveAndCall(address,uint256,bytes)')(this.spender, value, data),
  272. )
  273. .to.emit(this.token, 'Approval')
  274. .withArgs(this.holder.address, this.spender.target, value)
  275. .to.emit(this.spender, 'Approved')
  276. .withArgs(this.holder.address, value, data);
  277. });
  278. it('with reverting hook (without reason)', async function () {
  279. await this.spender.setUp(this.selectors.onApprovalReceived, RevertType.RevertWithoutMessage);
  280. await expect(
  281. this.token.connect(this.holder).getFunction('approveAndCall(address,uint256,bytes)')(this.spender, value, data),
  282. )
  283. .to.be.revertedWithCustomError(this.token, 'ERC1363InvalidSpender')
  284. .withArgs(this.spender.target);
  285. });
  286. it('reverts with reverting hook (with reason)', async function () {
  287. await this.spender.setUp(this.selectors.onApprovalReceived, RevertType.RevertWithMessage);
  288. await expect(
  289. this.token.connect(this.holder).getFunction('approveAndCall(address,uint256,bytes)')(this.spender, value, data),
  290. ).to.be.revertedWith('ERC1363SpenderMock: reverting');
  291. });
  292. it('reverts with reverting hook (with custom error)', async function () {
  293. const reason = '0x12345678';
  294. await this.spender.setUp(reason, RevertType.RevertWithCustomError);
  295. await expect(
  296. this.token.connect(this.holder).getFunction('approveAndCall(address,uint256,bytes)')(this.spender, value, data),
  297. )
  298. .to.be.revertedWithCustomError(this.spender, 'CustomError')
  299. .withArgs(reason);
  300. });
  301. it('panics with reverting hook (with panic)', async function () {
  302. await this.spender.setUp(this.selectors.onApprovalReceived, RevertType.Panic);
  303. await expect(
  304. this.token.connect(this.holder).getFunction('approveAndCall(address,uint256,bytes)')(this.spender, value, data),
  305. ).to.be.revertedWithPanic();
  306. });
  307. it('reverts with bad return value', async function () {
  308. await this.spender.setUp('0x12345678', RevertType.None);
  309. await expect(
  310. this.token.connect(this.holder).getFunction('approveAndCall(address,uint256,bytes)')(this.spender, value, data),
  311. )
  312. .to.be.revertedWithCustomError(this.token, 'ERC1363InvalidSpender')
  313. .withArgs(this.spender.target);
  314. });
  315. });
  316. });