Address.test.js 14 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361
  1. const { balance, constants, ether, expectRevert, send, expectEvent } = require('@openzeppelin/test-helpers');
  2. const { expect } = require('chai');
  3. const Address = artifacts.require('$Address');
  4. const EtherReceiver = artifacts.require('EtherReceiverMock');
  5. const CallReceiverMock = artifacts.require('CallReceiverMock');
  6. contract('Address', function (accounts) {
  7. const [recipient, other] = accounts;
  8. beforeEach(async function () {
  9. this.mock = await Address.new();
  10. });
  11. describe('isContract', function () {
  12. it('returns false for account address', async function () {
  13. expect(await this.mock.$isContract(other)).to.equal(false);
  14. });
  15. it('returns true for contract address', async function () {
  16. expect(await this.mock.$isContract(this.mock.address)).to.equal(true);
  17. });
  18. });
  19. describe('sendValue', function () {
  20. beforeEach(async function () {
  21. this.recipientTracker = await balance.tracker(recipient);
  22. });
  23. context('when sender contract has no funds', function () {
  24. it('sends 0 wei', async function () {
  25. await this.mock.$sendValue(other, 0);
  26. expect(await this.recipientTracker.delta()).to.be.bignumber.equal('0');
  27. });
  28. it('reverts when sending non-zero amounts', async function () {
  29. await expectRevert(this.mock.$sendValue(other, 1), 'Address: insufficient balance');
  30. });
  31. });
  32. context('when sender contract has funds', function () {
  33. const funds = ether('1');
  34. beforeEach(async function () {
  35. await send.ether(other, this.mock.address, funds);
  36. });
  37. it('sends 0 wei', async function () {
  38. await this.mock.$sendValue(recipient, 0);
  39. expect(await this.recipientTracker.delta()).to.be.bignumber.equal('0');
  40. });
  41. it('sends non-zero amounts', async function () {
  42. await this.mock.$sendValue(recipient, funds.subn(1));
  43. expect(await this.recipientTracker.delta()).to.be.bignumber.equal(funds.subn(1));
  44. });
  45. it('sends the whole balance', async function () {
  46. await this.mock.$sendValue(recipient, funds);
  47. expect(await this.recipientTracker.delta()).to.be.bignumber.equal(funds);
  48. expect(await balance.current(this.mock.address)).to.be.bignumber.equal('0');
  49. });
  50. it('reverts when sending more than the balance', async function () {
  51. await expectRevert(this.mock.$sendValue(recipient, funds.addn(1)), 'Address: insufficient balance');
  52. });
  53. context('with contract recipient', function () {
  54. beforeEach(async function () {
  55. this.target = await EtherReceiver.new();
  56. });
  57. it('sends funds', async function () {
  58. const tracker = await balance.tracker(this.target.address);
  59. await this.target.setAcceptEther(true);
  60. await this.mock.$sendValue(this.target.address, funds);
  61. expect(await tracker.delta()).to.be.bignumber.equal(funds);
  62. });
  63. it('reverts on recipient revert', async function () {
  64. await this.target.setAcceptEther(false);
  65. await expectRevert(
  66. this.mock.$sendValue(this.target.address, funds),
  67. 'Address: unable to send value, recipient may have reverted',
  68. );
  69. });
  70. });
  71. });
  72. });
  73. describe('functionCall', function () {
  74. beforeEach(async function () {
  75. this.target = await CallReceiverMock.new();
  76. });
  77. context('with valid contract receiver', function () {
  78. it('calls the requested function', async function () {
  79. const abiEncodedCall = this.target.contract.methods.mockFunction().encodeABI();
  80. const receipt = await this.mock.$functionCall(this.target.address, abiEncodedCall);
  81. expectEvent(receipt, 'return$functionCall_address_bytes', {
  82. ret0: web3.eth.abi.encodeParameters(['string'], ['0x1234']),
  83. });
  84. await expectEvent.inTransaction(receipt.tx, CallReceiverMock, 'MockFunctionCalled');
  85. });
  86. it('calls the requested empty return function', async function () {
  87. const abiEncodedCall = this.target.contract.methods.mockFunctionEmptyReturn().encodeABI();
  88. const receipt = await this.mock.$functionCall(this.target.address, abiEncodedCall);
  89. await expectEvent.inTransaction(receipt.tx, CallReceiverMock, 'MockFunctionCalled');
  90. });
  91. it('reverts when the called function reverts with no reason', async function () {
  92. const abiEncodedCall = this.target.contract.methods.mockFunctionRevertsNoReason().encodeABI();
  93. await expectRevert(
  94. this.mock.$functionCall(this.target.address, abiEncodedCall),
  95. 'Address: low-level call failed',
  96. );
  97. });
  98. it('reverts when the called function reverts, bubbling up the revert reason', async function () {
  99. const abiEncodedCall = this.target.contract.methods.mockFunctionRevertsReason().encodeABI();
  100. await expectRevert(this.mock.$functionCall(this.target.address, abiEncodedCall), 'CallReceiverMock: reverting');
  101. });
  102. it('reverts when the called function runs out of gas', async function () {
  103. const abiEncodedCall = this.target.contract.methods.mockFunctionOutOfGas().encodeABI();
  104. await expectRevert(
  105. this.mock.$functionCall(this.target.address, abiEncodedCall, { gas: '120000' }),
  106. 'Address: low-level call failed',
  107. );
  108. });
  109. it('reverts when the called function throws', async function () {
  110. const abiEncodedCall = this.target.contract.methods.mockFunctionThrows().encodeABI();
  111. await expectRevert.unspecified(this.mock.$functionCall(this.target.address, abiEncodedCall));
  112. });
  113. it('bubbles up error message if specified', async function () {
  114. const errorMsg = 'Address: expected error';
  115. await expectRevert(this.mock.$functionCall(this.target.address, '0x12345678', errorMsg), errorMsg);
  116. });
  117. it('reverts when function does not exist', async function () {
  118. const abiEncodedCall = web3.eth.abi.encodeFunctionCall(
  119. {
  120. name: 'mockFunctionDoesNotExist',
  121. type: 'function',
  122. inputs: [],
  123. },
  124. [],
  125. );
  126. await expectRevert(
  127. this.mock.$functionCall(this.target.address, abiEncodedCall),
  128. 'Address: low-level call failed',
  129. );
  130. });
  131. });
  132. context('with non-contract receiver', function () {
  133. it('reverts when address is not a contract', async function () {
  134. const [recipient] = accounts;
  135. const abiEncodedCall = this.target.contract.methods.mockFunction().encodeABI();
  136. await expectRevert(this.mock.$functionCall(recipient, abiEncodedCall), 'Address: call to non-contract');
  137. });
  138. });
  139. });
  140. describe('functionCallWithValue', function () {
  141. beforeEach(async function () {
  142. this.target = await CallReceiverMock.new();
  143. });
  144. context('with zero value', function () {
  145. it('calls the requested function', async function () {
  146. const abiEncodedCall = this.target.contract.methods.mockFunction().encodeABI();
  147. const receipt = await this.mock.$functionCallWithValue(this.target.address, abiEncodedCall, 0);
  148. expectEvent(receipt, 'return$functionCallWithValue_address_bytes_uint256', {
  149. ret0: web3.eth.abi.encodeParameters(['string'], ['0x1234']),
  150. });
  151. await expectEvent.inTransaction(receipt.tx, CallReceiverMock, 'MockFunctionCalled');
  152. });
  153. });
  154. context('with non-zero value', function () {
  155. const amount = ether('1.2');
  156. it('reverts if insufficient sender balance', async function () {
  157. const abiEncodedCall = this.target.contract.methods.mockFunction().encodeABI();
  158. await expectRevert(
  159. this.mock.$functionCallWithValue(this.target.address, abiEncodedCall, amount),
  160. 'Address: insufficient balance for call',
  161. );
  162. });
  163. it('calls the requested function with existing value', async function () {
  164. const abiEncodedCall = this.target.contract.methods.mockFunction().encodeABI();
  165. const tracker = await balance.tracker(this.target.address);
  166. await send.ether(other, this.mock.address, amount);
  167. const receipt = await this.mock.$functionCallWithValue(this.target.address, abiEncodedCall, amount);
  168. expectEvent(receipt, 'return$functionCallWithValue_address_bytes_uint256', {
  169. ret0: web3.eth.abi.encodeParameters(['string'], ['0x1234']),
  170. });
  171. await expectEvent.inTransaction(receipt.tx, CallReceiverMock, 'MockFunctionCalled');
  172. expect(await tracker.delta()).to.be.bignumber.equal(amount);
  173. });
  174. it('calls the requested function with transaction funds', async function () {
  175. const abiEncodedCall = this.target.contract.methods.mockFunction().encodeABI();
  176. const tracker = await balance.tracker(this.target.address);
  177. expect(await balance.current(this.mock.address)).to.be.bignumber.equal('0');
  178. const receipt = await this.mock.$functionCallWithValue(this.target.address, abiEncodedCall, amount, {
  179. from: other,
  180. value: amount,
  181. });
  182. expectEvent(receipt, 'return$functionCallWithValue_address_bytes_uint256', {
  183. ret0: web3.eth.abi.encodeParameters(['string'], ['0x1234']),
  184. });
  185. await expectEvent.inTransaction(receipt.tx, CallReceiverMock, 'MockFunctionCalled');
  186. expect(await tracker.delta()).to.be.bignumber.equal(amount);
  187. });
  188. it('reverts when calling non-payable functions', async function () {
  189. const abiEncodedCall = this.target.contract.methods.mockFunctionNonPayable().encodeABI();
  190. await send.ether(other, this.mock.address, amount);
  191. await expectRevert(
  192. this.mock.$functionCallWithValue(this.target.address, abiEncodedCall, amount),
  193. 'Address: low-level call with value failed',
  194. );
  195. });
  196. it('bubbles up error message if specified', async function () {
  197. const errorMsg = 'Address: expected error';
  198. await expectRevert(this.mock.$functionCallWithValue(this.target.address, '0x12345678', 0, errorMsg), errorMsg);
  199. });
  200. });
  201. });
  202. describe('functionStaticCall', function () {
  203. beforeEach(async function () {
  204. this.target = await CallReceiverMock.new();
  205. });
  206. it('calls the requested function', async function () {
  207. const abiEncodedCall = this.target.contract.methods.mockStaticFunction().encodeABI();
  208. expect(await this.mock.$functionStaticCall(this.target.address, abiEncodedCall)).to.be.equal(
  209. web3.eth.abi.encodeParameters(['string'], ['0x1234']),
  210. );
  211. });
  212. it('reverts on a non-static function', async function () {
  213. const abiEncodedCall = this.target.contract.methods.mockFunction().encodeABI();
  214. await expectRevert(
  215. this.mock.$functionStaticCall(this.target.address, abiEncodedCall),
  216. 'Address: low-level static call failed',
  217. );
  218. });
  219. it('bubbles up revert reason', async function () {
  220. const abiEncodedCall = this.target.contract.methods.mockFunctionRevertsReason().encodeABI();
  221. await expectRevert(
  222. this.mock.$functionStaticCall(this.target.address, abiEncodedCall),
  223. 'CallReceiverMock: reverting',
  224. );
  225. });
  226. it('reverts when address is not a contract', async function () {
  227. const [recipient] = accounts;
  228. const abiEncodedCall = this.target.contract.methods.mockFunction().encodeABI();
  229. await expectRevert(this.mock.$functionStaticCall(recipient, abiEncodedCall), 'Address: call to non-contract');
  230. });
  231. it('bubbles up error message if specified', async function () {
  232. const errorMsg = 'Address: expected error';
  233. await expectRevert(this.mock.$functionCallWithValue(this.target.address, '0x12345678', 0, errorMsg), errorMsg);
  234. });
  235. });
  236. describe('functionDelegateCall', function () {
  237. beforeEach(async function () {
  238. this.target = await CallReceiverMock.new();
  239. });
  240. it('delegate calls the requested function', async function () {
  241. // pseudorandom values
  242. const slot = '0x93e4c53af435ddf777c3de84bb9a953a777788500e229a468ea1036496ab66a0';
  243. const value = '0x6a465d1c49869f71fb65562bcbd7e08c8044074927f0297127203f2a9924ff5b';
  244. const abiEncodedCall = this.target.contract.methods.mockFunctionWritesStorage(slot, value).encodeABI();
  245. expect(await web3.eth.getStorageAt(this.mock.address, slot)).to.be.equal(constants.ZERO_BYTES32);
  246. expectEvent(
  247. await this.mock.$functionDelegateCall(this.target.address, abiEncodedCall),
  248. 'return$functionDelegateCall_address_bytes',
  249. { ret0: web3.eth.abi.encodeParameters(['string'], ['0x1234']) },
  250. );
  251. expect(await web3.eth.getStorageAt(this.mock.address, slot)).to.be.equal(value);
  252. });
  253. it('bubbles up revert reason', async function () {
  254. const abiEncodedCall = this.target.contract.methods.mockFunctionRevertsReason().encodeABI();
  255. await expectRevert(
  256. this.mock.$functionDelegateCall(this.target.address, abiEncodedCall),
  257. 'CallReceiverMock: reverting',
  258. );
  259. });
  260. it('reverts when address is not a contract', async function () {
  261. const [recipient] = accounts;
  262. const abiEncodedCall = this.target.contract.methods.mockFunction().encodeABI();
  263. await expectRevert(this.mock.$functionDelegateCall(recipient, abiEncodedCall), 'Address: call to non-contract');
  264. });
  265. it('bubbles up error message if specified', async function () {
  266. const errorMsg = 'Address: expected error';
  267. await expectRevert(this.mock.$functionCallWithValue(this.target.address, '0x12345678', 0, errorMsg), errorMsg);
  268. });
  269. });
  270. describe('verifyCallResult', function () {
  271. it('returns returndata on success', async function () {
  272. const returndata = '0x123abc';
  273. expect(await this.mock.$verifyCallResult(true, returndata, '')).to.equal(returndata);
  274. });
  275. it('reverts with return data and error m', async function () {
  276. const errorMsg = 'Address: expected error';
  277. await expectRevert(this.mock.$verifyCallResult(false, '0x', errorMsg), errorMsg);
  278. });
  279. });
  280. });