draft-ERC7579Utils.test.js 13 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356
  1. const { ethers } = require('hardhat');
  2. const { expect } = require('chai');
  3. const { loadFixture } = require('@nomicfoundation/hardhat-network-helpers');
  4. const {
  5. EXEC_TYPE_DEFAULT,
  6. EXEC_TYPE_TRY,
  7. encodeSingle,
  8. encodeBatch,
  9. encodeDelegate,
  10. CALL_TYPE_CALL,
  11. CALL_TYPE_BATCH,
  12. encodeMode,
  13. } = require('../../helpers/erc7579');
  14. const { selector } = require('../../helpers/methods');
  15. const coder = ethers.AbiCoder.defaultAbiCoder();
  16. const fixture = async () => {
  17. const [sender] = await ethers.getSigners();
  18. const utils = await ethers.deployContract('$ERC7579Utils', { value: ethers.parseEther('1') });
  19. const utilsGlobal = await ethers.deployContract('$ERC7579UtilsGlobalMock');
  20. const target = await ethers.deployContract('CallReceiverMock');
  21. const anotherTarget = await ethers.deployContract('CallReceiverMock');
  22. return { utils, utilsGlobal, target, anotherTarget, sender };
  23. };
  24. describe('ERC7579Utils', function () {
  25. beforeEach(async function () {
  26. Object.assign(this, await loadFixture(fixture));
  27. });
  28. describe('execSingle', function () {
  29. it('calls the target with value', async function () {
  30. const value = 0x012;
  31. const data = encodeSingle(this.target, value, this.target.interface.encodeFunctionData('mockFunction'));
  32. await expect(this.utils.$execSingle(data, EXEC_TYPE_DEFAULT)).to.emit(this.target, 'MockFunctionCalled');
  33. await expect(ethers.provider.getBalance(this.target)).to.eventually.equal(value);
  34. });
  35. it('calls the target with value and args', async function () {
  36. const value = 0x432;
  37. const data = encodeSingle(
  38. this.target,
  39. value,
  40. this.target.interface.encodeFunctionData('mockFunctionWithArgs', [42, '0x1234']),
  41. );
  42. await expect(this.utils.$execSingle(data, EXEC_TYPE_DEFAULT))
  43. .to.emit(this.target, 'MockFunctionCalledWithArgs')
  44. .withArgs(42, '0x1234');
  45. await expect(ethers.provider.getBalance(this.target)).to.eventually.equal(value);
  46. });
  47. it('reverts when target reverts in default ExecType', async function () {
  48. const value = 0x012;
  49. const data = encodeSingle(
  50. this.target,
  51. value,
  52. this.target.interface.encodeFunctionData('mockFunctionRevertsReason'),
  53. );
  54. await expect(this.utils.$execSingle(data, EXEC_TYPE_DEFAULT)).to.be.revertedWith('CallReceiverMock: reverting');
  55. });
  56. it('emits ERC7579TryExecuteFail event when target reverts in try ExecType', async function () {
  57. const value = 0x012;
  58. const data = encodeSingle(
  59. this.target,
  60. value,
  61. this.target.interface.encodeFunctionData('mockFunctionRevertsReason'),
  62. );
  63. await expect(this.utils.$execSingle(data, EXEC_TYPE_TRY))
  64. .to.emit(this.utils, 'ERC7579TryExecuteFail')
  65. .withArgs(
  66. CALL_TYPE_CALL,
  67. ethers.solidityPacked(
  68. ['bytes4', 'bytes'],
  69. [selector('Error(string)'), coder.encode(['string'], ['CallReceiverMock: reverting'])],
  70. ),
  71. );
  72. });
  73. it('reverts with an invalid exec type', async function () {
  74. const value = 0x012;
  75. const data = encodeSingle(this.target, value, this.target.interface.encodeFunctionData('mockFunction'));
  76. await expect(this.utils.$execSingle(data, '0x03'))
  77. .to.be.revertedWithCustomError(this.utils, 'ERC7579UnsupportedExecType')
  78. .withArgs('0x03');
  79. });
  80. });
  81. describe('execBatch', function () {
  82. it('calls the targets with value', async function () {
  83. const value1 = 0x012;
  84. const value2 = 0x234;
  85. const data = encodeBatch(
  86. [this.target, value1, this.target.interface.encodeFunctionData('mockFunction')],
  87. [this.anotherTarget, value2, this.anotherTarget.interface.encodeFunctionData('mockFunction')],
  88. );
  89. await expect(this.utils.$execBatch(data, EXEC_TYPE_DEFAULT))
  90. .to.emit(this.target, 'MockFunctionCalled')
  91. .to.emit(this.anotherTarget, 'MockFunctionCalled');
  92. await expect(ethers.provider.getBalance(this.target)).to.eventually.equal(value1);
  93. await expect(ethers.provider.getBalance(this.anotherTarget)).to.eventually.equal(value2);
  94. });
  95. it('calls the targets with value and args', async function () {
  96. const value1 = 0x012;
  97. const value2 = 0x234;
  98. const data = encodeBatch(
  99. [this.target, value1, this.target.interface.encodeFunctionData('mockFunctionWithArgs', [42, '0x1234'])],
  100. [
  101. this.anotherTarget,
  102. value2,
  103. this.anotherTarget.interface.encodeFunctionData('mockFunctionWithArgs', [42, '0x1234']),
  104. ],
  105. );
  106. await expect(this.utils.$execBatch(data, EXEC_TYPE_DEFAULT))
  107. .to.emit(this.target, 'MockFunctionCalledWithArgs')
  108. .to.emit(this.anotherTarget, 'MockFunctionCalledWithArgs');
  109. await expect(ethers.provider.getBalance(this.target)).to.eventually.equal(value1);
  110. await expect(ethers.provider.getBalance(this.anotherTarget)).to.eventually.equal(value2);
  111. });
  112. it('reverts when any target reverts in default ExecType', async function () {
  113. const value1 = 0x012;
  114. const value2 = 0x234;
  115. const data = encodeBatch(
  116. [this.target, value1, this.target.interface.encodeFunctionData('mockFunction')],
  117. [this.anotherTarget, value2, this.anotherTarget.interface.encodeFunctionData('mockFunctionRevertsReason')],
  118. );
  119. await expect(this.utils.$execBatch(data, EXEC_TYPE_DEFAULT)).to.be.revertedWith('CallReceiverMock: reverting');
  120. });
  121. it('emits ERC7579TryExecuteFail event when any target reverts in try ExecType', async function () {
  122. const value1 = 0x012;
  123. const value2 = 0x234;
  124. const data = encodeBatch(
  125. [this.target, value1, this.target.interface.encodeFunctionData('mockFunction')],
  126. [this.anotherTarget, value2, this.anotherTarget.interface.encodeFunctionData('mockFunctionRevertsReason')],
  127. );
  128. await expect(this.utils.$execBatch(data, EXEC_TYPE_TRY))
  129. .to.emit(this.utils, 'ERC7579TryExecuteFail')
  130. .withArgs(
  131. CALL_TYPE_BATCH,
  132. ethers.solidityPacked(
  133. ['bytes4', 'bytes'],
  134. [selector('Error(string)'), coder.encode(['string'], ['CallReceiverMock: reverting'])],
  135. ),
  136. );
  137. // Check balances
  138. await expect(ethers.provider.getBalance(this.target)).to.eventually.equal(value1);
  139. await expect(ethers.provider.getBalance(this.anotherTarget)).to.eventually.equal(0);
  140. });
  141. it('reverts with an invalid exec type', async function () {
  142. const value1 = 0x012;
  143. const value2 = 0x234;
  144. const data = encodeBatch(
  145. [this.target, value1, this.target.interface.encodeFunctionData('mockFunction')],
  146. [this.anotherTarget, value2, this.anotherTarget.interface.encodeFunctionData('mockFunction')],
  147. );
  148. await expect(this.utils.$execBatch(data, '0x03'))
  149. .to.be.revertedWithCustomError(this.utils, 'ERC7579UnsupportedExecType')
  150. .withArgs('0x03');
  151. });
  152. });
  153. describe('execDelegateCall', function () {
  154. it('delegate calls the target', async function () {
  155. const slot = ethers.hexlify(ethers.randomBytes(32));
  156. const value = ethers.hexlify(ethers.randomBytes(32));
  157. const data = encodeDelegate(
  158. this.target,
  159. this.target.interface.encodeFunctionData('mockFunctionWritesStorage', [slot, value]),
  160. );
  161. await expect(ethers.provider.getStorage(this.utils.target, slot)).to.eventually.equal(ethers.ZeroHash);
  162. await this.utils.$execDelegateCall(data, EXEC_TYPE_DEFAULT);
  163. await expect(ethers.provider.getStorage(this.utils.target, slot)).to.eventually.equal(value);
  164. });
  165. it('reverts when target reverts in default ExecType', async function () {
  166. const data = encodeDelegate(this.target, this.target.interface.encodeFunctionData('mockFunctionRevertsReason'));
  167. await expect(this.utils.$execDelegateCall(data, EXEC_TYPE_DEFAULT)).to.be.revertedWith(
  168. 'CallReceiverMock: reverting',
  169. );
  170. });
  171. it('emits ERC7579TryExecuteFail event when target reverts in try ExecType', async function () {
  172. const data = encodeDelegate(this.target, this.target.interface.encodeFunctionData('mockFunctionRevertsReason'));
  173. await expect(this.utils.$execDelegateCall(data, EXEC_TYPE_TRY))
  174. .to.emit(this.utils, 'ERC7579TryExecuteFail')
  175. .withArgs(
  176. CALL_TYPE_CALL,
  177. ethers.solidityPacked(
  178. ['bytes4', 'bytes'],
  179. [selector('Error(string)'), coder.encode(['string'], ['CallReceiverMock: reverting'])],
  180. ),
  181. );
  182. });
  183. it('reverts with an invalid exec type', async function () {
  184. const data = encodeDelegate(this.target, this.target.interface.encodeFunctionData('mockFunction'));
  185. await expect(this.utils.$execDelegateCall(data, '0x03'))
  186. .to.be.revertedWithCustomError(this.utils, 'ERC7579UnsupportedExecType')
  187. .withArgs('0x03');
  188. });
  189. });
  190. it('encodes Mode', async function () {
  191. const callType = CALL_TYPE_BATCH;
  192. const execType = EXEC_TYPE_TRY;
  193. const selector = '0x12345678';
  194. const payload = ethers.toBeHex(0, 22);
  195. await expect(this.utils.$encodeMode(callType, execType, selector, payload)).to.eventually.equal(
  196. encodeMode({
  197. callType,
  198. execType,
  199. selector,
  200. payload,
  201. }),
  202. );
  203. });
  204. it('decodes Mode', async function () {
  205. const callType = CALL_TYPE_BATCH;
  206. const execType = EXEC_TYPE_TRY;
  207. const selector = '0x12345678';
  208. const payload = ethers.toBeHex(0, 22);
  209. await expect(
  210. this.utils.$decodeMode(
  211. encodeMode({
  212. callType,
  213. execType,
  214. selector,
  215. payload,
  216. }),
  217. ),
  218. ).to.eventually.deep.equal([callType, execType, selector, payload]);
  219. });
  220. it('encodes single', async function () {
  221. const target = this.target;
  222. const value = 0x123;
  223. const data = '0x12345678';
  224. await expect(this.utils.$encodeSingle(target, value, data)).to.eventually.equal(encodeSingle(target, value, data));
  225. });
  226. it('decodes single', async function () {
  227. const target = this.target;
  228. const value = 0x123;
  229. const data = '0x12345678';
  230. await expect(this.utils.$decodeSingle(encodeSingle(target, value, data))).to.eventually.deep.equal([
  231. target.target,
  232. value,
  233. data,
  234. ]);
  235. });
  236. it('encodes batch', async function () {
  237. const entries = [
  238. [this.target, 0x123, '0x12345678'],
  239. [this.anotherTarget, 0x456, '0x12345678'],
  240. ];
  241. await expect(this.utils.$encodeBatch(entries)).to.eventually.equal(encodeBatch(...entries));
  242. });
  243. it('decodes batch', async function () {
  244. const entries = [
  245. [this.target.target, 0x123, '0x12345678'],
  246. [this.anotherTarget.target, 0x456, '0x12345678'],
  247. ];
  248. await expect(this.utils.$decodeBatch(encodeBatch(...entries))).to.eventually.deep.equal(entries);
  249. });
  250. it('encodes delegate', async function () {
  251. const target = this.target;
  252. const data = '0x12345678';
  253. await expect(this.utils.$encodeDelegate(target, data)).to.eventually.equal(encodeDelegate(target, data));
  254. });
  255. it('decodes delegate', async function () {
  256. const target = this.target;
  257. const data = '0x12345678';
  258. await expect(this.utils.$decodeDelegate(encodeDelegate(target, data))).to.eventually.deep.equal([
  259. target.target,
  260. data,
  261. ]);
  262. });
  263. describe('global', function () {
  264. describe('eqCallTypeGlobal', function () {
  265. it('returns true if both call types are equal', async function () {
  266. await expect(this.utilsGlobal.$eqCallTypeGlobal(CALL_TYPE_BATCH, CALL_TYPE_BATCH)).to.eventually.be.true;
  267. });
  268. it('returns false if both call types are different', async function () {
  269. await expect(this.utilsGlobal.$eqCallTypeGlobal(CALL_TYPE_CALL, CALL_TYPE_BATCH)).to.eventually.be.false;
  270. });
  271. });
  272. describe('eqExecTypeGlobal', function () {
  273. it('returns true if both exec types are equal', async function () {
  274. await expect(this.utilsGlobal.$eqExecTypeGlobal(EXEC_TYPE_TRY, EXEC_TYPE_TRY)).to.eventually.be.true;
  275. });
  276. it('returns false if both exec types are different', async function () {
  277. await expect(this.utilsGlobal.$eqExecTypeGlobal(EXEC_TYPE_DEFAULT, EXEC_TYPE_TRY)).to.eventually.be.false;
  278. });
  279. });
  280. describe('eqModeSelectorGlobal', function () {
  281. it('returns true if both selectors are equal', async function () {
  282. await expect(this.utilsGlobal.$eqModeSelectorGlobal('0x12345678', '0x12345678')).to.eventually.be.true;
  283. });
  284. it('returns false if both selectors are different', async function () {
  285. await expect(this.utilsGlobal.$eqModeSelectorGlobal('0x12345678', '0x87654321')).to.eventually.be.false;
  286. });
  287. });
  288. describe('eqModePayloadGlobal', function () {
  289. it('returns true if both payloads are equal', async function () {
  290. await expect(this.utilsGlobal.$eqModePayloadGlobal(ethers.toBeHex(0, 22), ethers.toBeHex(0, 22))).to.eventually
  291. .be.true;
  292. });
  293. it('returns false if both payloads are different', async function () {
  294. await expect(this.utilsGlobal.$eqModePayloadGlobal(ethers.toBeHex(0, 22), ethers.toBeHex(1, 22))).to.eventually
  295. .be.false;
  296. });
  297. });
  298. });
  299. });