draft-ERC7579Utils.test.js 13 KB

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