From 2342eda2d292189be649fceca31226ea3dc29971 Mon Sep 17 00:00:00 2001 From: lazaralex98 Date: Wed, 9 Mar 2022 07:32:01 +0200 Subject: [PATCH] better tests --- contracts/OffsetHelper.sol | 11 +- package-lock.json | 139 +++++++++++++++++++++-- package.json | 3 +- test/index.ts | 225 ++++++++++++++++++++++++++++++++++++- tsconfig.json | 2 +- 5 files changed, 357 insertions(+), 23 deletions(-) diff --git a/contracts/OffsetHelper.sol b/contracts/OffsetHelper.sol index 27f81c2..66b8e60 100644 --- a/contracts/OffsetHelper.sol +++ b/contracts/OffsetHelper.sol @@ -154,8 +154,6 @@ contract OffsetHelper is OffsetHelperStorage { // Here is a link to the specific error from inside the safeTransferETH() method // https://github.com/sushiswap/sushiswap/blob/canary/contracts/uniswapv2/libraries/TransferHelper.sol#L27 - // TODO also, the swap method will send unused MATIC to OffsetHelper instead of to the user, need to adapt for that - // check eligibility of token to swap for require(isRedeemable(_toToken), "Can't swap for this token"); @@ -169,12 +167,9 @@ contract OffsetHelper is OffsetHelperStorage { path[2] = _toToken; // swap MATIC for tokens - routerSushi.swapETHForExactTokens{value: msg.value}( - _amount, - path, - address(this), - block.timestamp - ); + uint256[] memory amounts = routerSushi.swapETHForExactTokens{ + value: msg.value + }(_amount, path, address(this), block.timestamp); // update balances balances[msg.sender][path[2]] += _amount; diff --git a/package-lock.json b/package-lock.json index f489935..66aa168 100644 --- a/package-lock.json +++ b/package-lock.json @@ -9,7 +9,8 @@ "@openzeppelin/contracts": "^4.5.0", "@openzeppelin/contracts-upgradeable": "^4.5.1", "i": "^0.3.7", - "solc": "^0.8.12" + "solc": "^0.8.12", + "urql": "^2.2.0" }, "devDependencies": { "@nomiclabs/hardhat-ethers": "^2.0.5", @@ -1403,6 +1404,14 @@ "@ethersproject/strings": "^5.5.0" } }, + "node_modules/@graphql-typed-document-node/core": { + "version": "3.1.1", + "resolved": "https://registry.npmjs.org/@graphql-typed-document-node/core/-/core-3.1.1.tgz", + "integrity": "sha512-NQ17ii0rK1b34VZonlmT2QMJFI70m0TRwbknO/ihlbatXyaktDhN/98vBiUU6kNBPljqGqyIrl2T4nY2RpFANg==", + "peerDependencies": { + "graphql": "^0.8.0 || ^0.9.0 || ^0.10.0 || ^0.11.0 || ^0.12.0 || ^0.13.0 || ^14.0.0 || ^15.0.0 || ^16.0.0" + } + }, "node_modules/@humanwhocodes/config-array": { "version": "0.5.0", "resolved": "https://registry.npmjs.org/@humanwhocodes/config-array/-/config-array-0.5.0.tgz", @@ -2533,6 +2542,18 @@ "integrity": "sha512-tAG9LWg8+M2CMu7hIsqHPaTyG4uDzjr6mhvH96LvOpLZZj6tgzTluBt+LsCf1/QaYrlis6pITvpIaIhE+iZB+Q==", "dev": true }, + "node_modules/@urql/core": { + "version": "2.4.3", + "resolved": "https://registry.npmjs.org/@urql/core/-/core-2.4.3.tgz", + "integrity": "sha512-FpapxUKF0nLdzRLoB1QsudDjeLXJhBwzkzl8bSOJ6Cnj7LRRKJ+dYdqHfqGykswB/ILrkZks2Isp4a4BhqyUow==", + "dependencies": { + "@graphql-typed-document-node/core": "^3.1.1", + "wonka": "^4.0.14" + }, + "peerDependencies": { + "graphql": "^0.11.0 || ^0.12.0 || ^0.13.0 || ^14.0.0 || ^15.0.0 || ^16.0.0" + } + }, "node_modules/@yarnpkg/lockfile": { "version": "1.1.0", "resolved": "https://registry.npmjs.org/@yarnpkg/lockfile/-/lockfile-1.1.0.tgz", @@ -15543,6 +15564,15 @@ "integrity": "sha512-NtNxqUcXgpW2iMrfqSfR73Glt39K+BLwWsPs94yR63v45T0Wbej7eRmL5cWfwEgqXnmjQp3zaJTshdRW/qC2ZQ==", "dev": true }, + "node_modules/graphql": { + "version": "16.3.0", + "resolved": "https://registry.npmjs.org/graphql/-/graphql-16.3.0.tgz", + "integrity": "sha512-xm+ANmA16BzCT5pLjuXySbQVFwH3oJctUVdy81w1sV0vBU0KgDdBGtxQOUd5zqOBk/JayAFeG8Dlmeq74rjm/A==", + "peer": true, + "engines": { + "node": "^12.22.0 || ^14.16.0 || >=16.0.0" + } + }, "node_modules/growl": { "version": "1.10.5", "resolved": "https://registry.npmjs.org/growl/-/growl-1.10.5.tgz", @@ -16798,8 +16828,7 @@ "node_modules/js-tokens": { "version": "4.0.0", "resolved": "https://registry.npmjs.org/js-tokens/-/js-tokens-4.0.0.tgz", - "integrity": "sha512-RdJUflcE3cUzKiMqQgsCu06FPu9UdIJO0beYbPhHN4k6apgJtifcoCtT9bcxOpYBtpD2kCM6Sbzg4CausW/PKQ==", - "dev": true + "integrity": "sha512-RdJUflcE3cUzKiMqQgsCu06FPu9UdIJO0beYbPhHN4k6apgJtifcoCtT9bcxOpYBtpD2kCM6Sbzg4CausW/PKQ==" }, "node_modules/js-yaml": { "version": "3.14.1", @@ -17267,6 +17296,18 @@ "node": ">=4" } }, + "node_modules/loose-envify": { + "version": "1.4.0", + "resolved": "https://registry.npmjs.org/loose-envify/-/loose-envify-1.4.0.tgz", + "integrity": "sha512-lyuxPGr/Wfhrlem2CL/UcnUc1zcqKAImBDzukY7Y5F/yQiNdko6+fRLevlw1HgMySw7f611UIY408EtxRSoK3Q==", + "peer": true, + "dependencies": { + "js-tokens": "^3.0.0 || ^4.0.0" + }, + "bin": { + "loose-envify": "cli.js" + } + }, "node_modules/loupe": { "version": "2.3.4", "resolved": "https://registry.npmjs.org/loupe/-/loupe-2.3.4.tgz", @@ -18159,7 +18200,6 @@ "version": "4.1.1", "resolved": "https://registry.npmjs.org/object-assign/-/object-assign-4.1.1.tgz", "integrity": "sha1-IQmtx5ZYh8/AXLvUQsrIv7s2CGM=", - "dev": true, "engines": { "node": ">=0.10.0" } @@ -19112,6 +19152,19 @@ "node": ">= 0.8" } }, + "node_modules/react": { + "version": "17.0.2", + "resolved": "https://registry.npmjs.org/react/-/react-17.0.2.tgz", + "integrity": "sha512-gnhPt75i/dq/z3/6q/0asP78D0u592D5L1pd7M8P+dck6Fu/jJeL6iVVK23fptSUZj8Vjf++7wXA8UNclGQcbA==", + "peer": true, + "dependencies": { + "loose-envify": "^1.1.0", + "object-assign": "^4.1.1" + }, + "engines": { + "node": ">=0.10.0" + } + }, "node_modules/read-pkg": { "version": "1.1.0", "resolved": "https://registry.npmjs.org/read-pkg/-/read-pkg-1.1.0.tgz", @@ -21900,6 +21953,19 @@ "integrity": "sha1-llOgNvt8HuQjQvIyXM7v6jkmxI0=", "dev": true }, + "node_modules/urql": { + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/urql/-/urql-2.2.0.tgz", + "integrity": "sha512-36wnWqDrpXqhwT5r2/qRSZXhb7Y4sXA0nLlYEd3uLgvfIdOA8kUaPdfTujzfrvfCcfiVVFxhzqVAhc8r17NMwQ==", + "dependencies": { + "@urql/core": "^2.4.3", + "wonka": "^4.0.14" + }, + "peerDependencies": { + "graphql": "^0.11.0 || ^0.12.0 || ^0.13.0 || ^14.0.0 || ^15.0.0 || ^16.0.0", + "react": ">= 16.8.0" + } + }, "node_modules/utf-8-validate": { "version": "5.0.8", "resolved": "https://registry.npmjs.org/utf-8-validate/-/utf-8-validate-5.0.8.tgz", @@ -22864,6 +22930,11 @@ "node": ">= 0.10.0" } }, + "node_modules/wonka": { + "version": "4.0.15", + "resolved": "https://registry.npmjs.org/wonka/-/wonka-4.0.15.tgz", + "integrity": "sha512-U0IUQHKXXn6PFo9nqsHphVCE5m3IntqZNB9Jjn7EB1lrR7YTDY3YWgFvEvwniTzXSvOH/XMzAZaIfJF/LvHYXg==" + }, "node_modules/word-wrap": { "version": "1.2.3", "resolved": "https://registry.npmjs.org/word-wrap/-/word-wrap-1.2.3.tgz", @@ -24206,6 +24277,12 @@ "@ethersproject/strings": "^5.5.0" } }, + "@graphql-typed-document-node/core": { + "version": "3.1.1", + "resolved": "https://registry.npmjs.org/@graphql-typed-document-node/core/-/core-3.1.1.tgz", + "integrity": "sha512-NQ17ii0rK1b34VZonlmT2QMJFI70m0TRwbknO/ihlbatXyaktDhN/98vBiUU6kNBPljqGqyIrl2T4nY2RpFANg==", + "requires": {} + }, "@humanwhocodes/config-array": { "version": "0.5.0", "resolved": "https://registry.npmjs.org/@humanwhocodes/config-array/-/config-array-0.5.0.tgz", @@ -25175,6 +25252,15 @@ } } }, + "@urql/core": { + "version": "2.4.3", + "resolved": "https://registry.npmjs.org/@urql/core/-/core-2.4.3.tgz", + "integrity": "sha512-FpapxUKF0nLdzRLoB1QsudDjeLXJhBwzkzl8bSOJ6Cnj7LRRKJ+dYdqHfqGykswB/ILrkZks2Isp4a4BhqyUow==", + "requires": { + "@graphql-typed-document-node/core": "^3.1.1", + "wonka": "^4.0.14" + } + }, "@yarnpkg/lockfile": { "version": "1.1.0", "resolved": "https://registry.npmjs.org/@yarnpkg/lockfile/-/lockfile-1.1.0.tgz", @@ -35341,6 +35427,12 @@ "integrity": "sha512-NtNxqUcXgpW2iMrfqSfR73Glt39K+BLwWsPs94yR63v45T0Wbej7eRmL5cWfwEgqXnmjQp3zaJTshdRW/qC2ZQ==", "dev": true }, + "graphql": { + "version": "16.3.0", + "resolved": "https://registry.npmjs.org/graphql/-/graphql-16.3.0.tgz", + "integrity": "sha512-xm+ANmA16BzCT5pLjuXySbQVFwH3oJctUVdy81w1sV0vBU0KgDdBGtxQOUd5zqOBk/JayAFeG8Dlmeq74rjm/A==", + "peer": true + }, "growl": { "version": "1.10.5", "resolved": "https://registry.npmjs.org/growl/-/growl-1.10.5.tgz", @@ -36276,8 +36368,7 @@ "js-tokens": { "version": "4.0.0", "resolved": "https://registry.npmjs.org/js-tokens/-/js-tokens-4.0.0.tgz", - "integrity": "sha512-RdJUflcE3cUzKiMqQgsCu06FPu9UdIJO0beYbPhHN4k6apgJtifcoCtT9bcxOpYBtpD2kCM6Sbzg4CausW/PKQ==", - "dev": true + "integrity": "sha512-RdJUflcE3cUzKiMqQgsCu06FPu9UdIJO0beYbPhHN4k6apgJtifcoCtT9bcxOpYBtpD2kCM6Sbzg4CausW/PKQ==" }, "js-yaml": { "version": "3.14.1", @@ -36664,6 +36755,15 @@ } } }, + "loose-envify": { + "version": "1.4.0", + "resolved": "https://registry.npmjs.org/loose-envify/-/loose-envify-1.4.0.tgz", + "integrity": "sha512-lyuxPGr/Wfhrlem2CL/UcnUc1zcqKAImBDzukY7Y5F/yQiNdko6+fRLevlw1HgMySw7f611UIY408EtxRSoK3Q==", + "peer": true, + "requires": { + "js-tokens": "^3.0.0 || ^4.0.0" + } + }, "loupe": { "version": "2.3.4", "resolved": "https://registry.npmjs.org/loupe/-/loupe-2.3.4.tgz", @@ -37383,8 +37483,7 @@ "object-assign": { "version": "4.1.1", "resolved": "https://registry.npmjs.org/object-assign/-/object-assign-4.1.1.tgz", - "integrity": "sha1-IQmtx5ZYh8/AXLvUQsrIv7s2CGM=", - "dev": true + "integrity": "sha1-IQmtx5ZYh8/AXLvUQsrIv7s2CGM=" }, "object-inspect": { "version": "1.12.0", @@ -38102,6 +38201,16 @@ "unpipe": "1.0.0" } }, + "react": { + "version": "17.0.2", + "resolved": "https://registry.npmjs.org/react/-/react-17.0.2.tgz", + "integrity": "sha512-gnhPt75i/dq/z3/6q/0asP78D0u592D5L1pd7M8P+dck6Fu/jJeL6iVVK23fptSUZj8Vjf++7wXA8UNclGQcbA==", + "peer": true, + "requires": { + "loose-envify": "^1.1.0", + "object-assign": "^4.1.1" + } + }, "read-pkg": { "version": "1.1.0", "resolved": "https://registry.npmjs.org/read-pkg/-/read-pkg-1.1.0.tgz", @@ -40291,6 +40400,15 @@ "integrity": "sha1-FQWgOiiaSMvXpDTvuu7FBV9WM6k=", "dev": true }, + "urql": { + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/urql/-/urql-2.2.0.tgz", + "integrity": "sha512-36wnWqDrpXqhwT5r2/qRSZXhb7Y4sXA0nLlYEd3uLgvfIdOA8kUaPdfTujzfrvfCcfiVVFxhzqVAhc8r17NMwQ==", + "requires": { + "@urql/core": "^2.4.3", + "wonka": "^4.0.14" + } + }, "utf-8-validate": { "version": "5.0.8", "resolved": "https://registry.npmjs.org/utf-8-validate/-/utf-8-validate-5.0.8.tgz", @@ -41135,6 +41253,11 @@ "integrity": "sha1-tDFbtCFKPXBY6+7okuE/ok2YsHU=", "dev": true }, + "wonka": { + "version": "4.0.15", + "resolved": "https://registry.npmjs.org/wonka/-/wonka-4.0.15.tgz", + "integrity": "sha512-U0IUQHKXXn6PFo9nqsHphVCE5m3IntqZNB9Jjn7EB1lrR7YTDY3YWgFvEvwniTzXSvOH/XMzAZaIfJF/LvHYXg==" + }, "word-wrap": { "version": "1.2.3", "resolved": "https://registry.npmjs.org/word-wrap/-/word-wrap-1.2.3.tgz", diff --git a/package.json b/package.json index 3389fae..0aa90a8 100644 --- a/package.json +++ b/package.json @@ -39,6 +39,7 @@ "@openzeppelin/contracts": "^4.5.0", "@openzeppelin/contracts-upgradeable": "^4.5.1", "i": "^0.3.7", - "solc": "^0.8.12" + "solc": "^0.8.12", + "urql": "^2.2.0" } } diff --git a/test/index.ts b/test/index.ts index 2812de0..bdb9ad5 100644 --- a/test/index.ts +++ b/test/index.ts @@ -11,10 +11,8 @@ import { OffsetHelper__factory, ToucanCarbonOffsets, } from "../typechain"; -import { Pool } from "@uniswap/v3-sdk"; -import { abi as QuoterABI } from "@uniswap/v3-periphery/artifacts/contracts/lens/Quoter.sol/Quoter.json"; -import { BigNumber, BigNumberish } from "ethers"; import { FormatTypes, Interface } from "ethers/lib/utils"; +import { BigNumber } from "ethers"; const addresses: any = { myAddress: "0x721F6f7A29b99CbdE1F18C4AA7D7AEb31eb2923B", @@ -61,7 +59,7 @@ describe("Offset Helper", function () { }); describe("swap()", function () { - it("Should swap WETH for 1.0 NCT", async function () { + it("Contract should swap WETH for 1.0 NCT", async function () { // since I have no WETH, I need to impersonate an account that has it // I'll also give it some wei just to be safe await network.provider.request({ @@ -108,13 +106,63 @@ describe("Offset Helper", function () { ) ).wait(); + // I expect the offsetHelper will have 1 extra NCT in its balance const balance = await nct.balanceOf(offsetHelper.address); expect(ethers.utils.formatEther(balance)).to.be.eql( ethers.utils.formatEther( initialBalance.add(ethers.utils.parseEther("1.0")) ) ); + }); + + it("User's in-contract balance should have 1.0 NCT", async function () { + // since I have no WETH, I need to impersonate an account that has it + // I'll also give it some wei just to be safe + await network.provider.request({ + method: "hardhat_stopImpersonatingAccount", + params: [addresses.myAddress], + }); + await network.provider.request({ + method: "hardhat_impersonateAccount", + params: ["0xdc9232e2df177d7a12fdff6ecbab114e2231198d"], + }); + await network.provider.send("hardhat_setBalance", [ + "0xdc9232e2df177d7a12fdff6ecbab114e2231198d", + ethers.utils.parseEther("2.0").toHexString(), + ]); + const signer = await ethers.getSigner( + "0xdc9232e2df177d7a12fdff6ecbab114e2231198d" + ); + + // @ts-ignore + nct = new ethers.Contract(addresses.nctAddress, nctAbi.abi, owner); + + const initialBalance = await nct.balanceOf(offsetHelper.address); + + const iface = new Interface( + '[{"inputs":[{"internalType":"address","name":"childChainManager","type":"address"}],"stateMutability":"nonpayable","type":"constructor"},{"anonymous":false,"inputs":[{"indexed":true,"internalType":"address","name":"owner","type":"address"},{"indexed":true,"internalType":"address","name":"spender","type":"address"},{"indexed":false,"internalType":"uint256","name":"value","type":"uint256"}],"name":"Approval","type":"event"},{"anonymous":false,"inputs":[{"indexed":false,"internalType":"address","name":"userAddress","type":"address"},{"indexed":false,"internalType":"address payable","name":"relayerAddress","type":"address"},{"indexed":false,"internalType":"bytes","name":"functionSignature","type":"bytes"}],"name":"MetaTransactionExecuted","type":"event"},{"anonymous":false,"inputs":[{"indexed":true,"internalType":"bytes32","name":"role","type":"bytes32"},{"indexed":true,"internalType":"bytes32","name":"previousAdminRole","type":"bytes32"},{"indexed":true,"internalType":"bytes32","name":"newAdminRole","type":"bytes32"}],"name":"RoleAdminChanged","type":"event"},{"anonymous":false,"inputs":[{"indexed":true,"internalType":"bytes32","name":"role","type":"bytes32"},{"indexed":true,"internalType":"address","name":"account","type":"address"},{"indexed":true,"internalType":"address","name":"sender","type":"address"}],"name":"RoleGranted","type":"event"},{"anonymous":false,"inputs":[{"indexed":true,"internalType":"bytes32","name":"role","type":"bytes32"},{"indexed":true,"internalType":"address","name":"account","type":"address"},{"indexed":true,"internalType":"address","name":"sender","type":"address"}],"name":"RoleRevoked","type":"event"},{"anonymous":false,"inputs":[{"indexed":true,"internalType":"address","name":"from","type":"address"},{"indexed":true,"internalType":"address","name":"to","type":"address"},{"indexed":false,"internalType":"uint256","name":"value","type":"uint256"}],"name":"Transfer","type":"event"},{"inputs":[],"name":"CHILD_CHAIN_ID","outputs":[{"internalType":"uint256","name":"","type":"uint256"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"CHILD_CHAIN_ID_BYTES","outputs":[{"internalType":"bytes","name":"","type":"bytes"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"DEFAULT_ADMIN_ROLE","outputs":[{"internalType":"bytes32","name":"","type":"bytes32"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"DEPOSITOR_ROLE","outputs":[{"internalType":"bytes32","name":"","type":"bytes32"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"ERC712_VERSION","outputs":[{"internalType":"string","name":"","type":"string"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"ROOT_CHAIN_ID","outputs":[{"internalType":"uint256","name":"","type":"uint256"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"ROOT_CHAIN_ID_BYTES","outputs":[{"internalType":"bytes","name":"","type":"bytes"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"address","name":"owner","type":"address"},{"internalType":"address","name":"spender","type":"address"}],"name":"allowance","outputs":[{"internalType":"uint256","name":"","type":"uint256"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"address","name":"spender","type":"address"},{"internalType":"uint256","name":"amount","type":"uint256"}],"name":"approve","outputs":[{"internalType":"bool","name":"","type":"bool"}],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"address","name":"account","type":"address"}],"name":"balanceOf","outputs":[{"internalType":"uint256","name":"","type":"uint256"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"decimals","outputs":[{"internalType":"uint8","name":"","type":"uint8"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"address","name":"spender","type":"address"},{"internalType":"uint256","name":"subtractedValue","type":"uint256"}],"name":"decreaseAllowance","outputs":[{"internalType":"bool","name":"","type":"bool"}],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"address","name":"user","type":"address"},{"internalType":"bytes","name":"depositData","type":"bytes"}],"name":"deposit","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"address","name":"userAddress","type":"address"},{"internalType":"bytes","name":"functionSignature","type":"bytes"},{"internalType":"bytes32","name":"sigR","type":"bytes32"},{"internalType":"bytes32","name":"sigS","type":"bytes32"},{"internalType":"uint8","name":"sigV","type":"uint8"}],"name":"executeMetaTransaction","outputs":[{"internalType":"bytes","name":"","type":"bytes"}],"stateMutability":"payable","type":"function"},{"inputs":[],"name":"getChainId","outputs":[{"internalType":"uint256","name":"","type":"uint256"}],"stateMutability":"pure","type":"function"},{"inputs":[],"name":"getDomainSeperator","outputs":[{"internalType":"bytes32","name":"","type":"bytes32"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"address","name":"user","type":"address"}],"name":"getNonce","outputs":[{"internalType":"uint256","name":"nonce","type":"uint256"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"bytes32","name":"role","type":"bytes32"}],"name":"getRoleAdmin","outputs":[{"internalType":"bytes32","name":"","type":"bytes32"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"bytes32","name":"role","type":"bytes32"},{"internalType":"uint256","name":"index","type":"uint256"}],"name":"getRoleMember","outputs":[{"internalType":"address","name":"","type":"address"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"bytes32","name":"role","type":"bytes32"}],"name":"getRoleMemberCount","outputs":[{"internalType":"uint256","name":"","type":"uint256"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"bytes32","name":"role","type":"bytes32"},{"internalType":"address","name":"account","type":"address"}],"name":"grantRole","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"bytes32","name":"role","type":"bytes32"},{"internalType":"address","name":"account","type":"address"}],"name":"hasRole","outputs":[{"internalType":"bool","name":"","type":"bool"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"address","name":"spender","type":"address"},{"internalType":"uint256","name":"addedValue","type":"uint256"}],"name":"increaseAllowance","outputs":[{"internalType":"bool","name":"","type":"bool"}],"stateMutability":"nonpayable","type":"function"},{"inputs":[],"name":"name","outputs":[{"internalType":"string","name":"","type":"string"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"bytes32","name":"role","type":"bytes32"},{"internalType":"address","name":"account","type":"address"}],"name":"renounceRole","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"bytes32","name":"role","type":"bytes32"},{"internalType":"address","name":"account","type":"address"}],"name":"revokeRole","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[],"name":"symbol","outputs":[{"internalType":"string","name":"","type":"string"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"totalSupply","outputs":[{"internalType":"uint256","name":"","type":"uint256"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"address","name":"recipient","type":"address"},{"internalType":"uint256","name":"amount","type":"uint256"}],"name":"transfer","outputs":[{"internalType":"bool","name":"","type":"bool"}],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"address","name":"sender","type":"address"},{"internalType":"address","name":"recipient","type":"address"},{"internalType":"uint256","name":"amount","type":"uint256"}],"name":"transferFrom","outputs":[{"internalType":"bool","name":"","type":"bool"}],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"uint256","name":"amount","type":"uint256"}],"name":"withdraw","outputs":[],"stateMutability":"nonpayable","type":"function"}]' + ); + iface.format(FormatTypes.full); + + const weth = new ethers.Contract(addresses.wethAddress, iface, owner); + + await ( + await weth + .connect(signer) + .approve(offsetHelper.address, ethers.utils.parseEther("1.0")) + ).wait(); + + await ( + await offsetHelper + .connect(signer) + ["swap(address,address,uint256)"]( + addresses.wethAddress, + addresses.nctAddress, + ethers.utils.parseEther("1.0") + ) + ).wait(); + // I expect that the user should have his in-contract balance for NCT to be 1.0 expect( ethers.utils.formatEther( await offsetHelper.balances( @@ -142,6 +190,28 @@ describe("Offset Helper", function () { const balance = await nct.balanceOf(offsetHelper.address); expect(ethers.utils.formatEther(balance)).to.be.eql("1.0"); }); + + it("Surplus MATIC should be sent to user", async function () { + const preSwapETHBalance = await owner.getBalance(); + + await ( + await offsetHelper["swap(address,uint256)"]( + addresses.nctAddress, + ethers.utils.parseEther("1.0"), + { + value: ethers.utils.parseEther("5.0"), + } + ) + ).wait(); + + const postSwapETHBalance = await owner.getBalance(); + + // I'm expecting that the OffsetHelper doesn't have extra MATIC + // this check is done to ensure any surplus MATIC has been sent to the user, and not to OffsetHelper + expect(ethers.utils.formatEther(preSwapETHBalance)).to.be.eql( + ethers.utils.formatEther(postSwapETHBalance) + ); + }); }); describe("deposit()", function () { @@ -191,7 +261,7 @@ describe("Offset Helper", function () { }); describe("autoRedeem()", function () { - it("Should redeem 1.0 NCT for 1.0 TCO2", async function () { + it("OffsetHelper should have 0.0 NCT", async function () { // since I have no NCT, I need to impersonate an account that has it // I'll also give it some wei, just to be safe await network.provider.request({ @@ -231,10 +301,53 @@ describe("Offset Helper", function () { .autoRedeem(addresses.nctAddress, ethers.utils.parseEther("1.0")) ).wait(); + // expecting offsetHelper to have 0.0 NCT expect( ethers.utils.formatEther(await nct.balanceOf(offsetHelper.address)) ).to.be.eql("0.0"); + }); + + it("User's in-contract balance for NCT should be 0.0", async function () { + // since I have no NCT, I need to impersonate an account that has it + // I'll also give it some wei, just to be safe + await network.provider.request({ + method: "hardhat_stopImpersonatingAccount", + params: [addresses.myAddress], + }); + await network.provider.request({ + method: "hardhat_impersonateAccount", + params: ["0xdab7f2bc9aa986d9759718203c9a76534894e900"], + }); + await network.provider.send("hardhat_setBalance", [ + "0xdab7f2bc9aa986d9759718203c9a76534894e900", + ethers.utils.parseEther("2.0").toHexString(), + ]); + const signer = await ethers.getSigner( + "0xdab7f2bc9aa986d9759718203c9a76534894e900" + ); + + // @ts-ignore + nct = new ethers.Contract(addresses.nctAddress, nctAbi.abi, owner); + + await ( + await nct + .connect(signer) + .approve(offsetHelper.address, ethers.utils.parseEther("1.0")) + ).wait(); + + await ( + await offsetHelper + .connect(signer) + .deposit(addresses.nctAddress, ethers.utils.parseEther("1.0")) + ).wait(); + + await ( + await offsetHelper + .connect(signer) + .autoRedeem(addresses.nctAddress, ethers.utils.parseEther("1.0")) + ).wait(); + // expecting user's in-contract balance for NCT to be 0.0 expect( ethers.utils.formatEther( await offsetHelper.balances( @@ -243,7 +356,49 @@ describe("Offset Helper", function () { ) ) ).to.be.eql("0.0"); + }); + + it("User's in-contract balance for TCO2s should be 1.0", async function () { + // since I have no NCT, I need to impersonate an account that has it + // I'll also give it some wei, just to be safe + await network.provider.request({ + method: "hardhat_stopImpersonatingAccount", + params: [addresses.myAddress], + }); + await network.provider.request({ + method: "hardhat_impersonateAccount", + params: ["0xdab7f2bc9aa986d9759718203c9a76534894e900"], + }); + await network.provider.send("hardhat_setBalance", [ + "0xdab7f2bc9aa986d9759718203c9a76534894e900", + ethers.utils.parseEther("2.0").toHexString(), + ]); + const signer = await ethers.getSigner( + "0xdab7f2bc9aa986d9759718203c9a76534894e900" + ); + + // @ts-ignore + nct = new ethers.Contract(addresses.nctAddress, nctAbi.abi, owner); + + await ( + await nct + .connect(signer) + .approve(offsetHelper.address, ethers.utils.parseEther("1.0")) + ).wait(); + + await ( + await offsetHelper + .connect(signer) + .deposit(addresses.nctAddress, ethers.utils.parseEther("1.0")) + ).wait(); + + await ( + await offsetHelper + .connect(signer) + .autoRedeem(addresses.nctAddress, ethers.utils.parseEther("1.0")) + ).wait(); + // expecting user's in-contract balance for TCO2s to be 1.0 expect( ethers.utils.formatEther( await offsetHelper.tco2Balance( @@ -252,6 +407,65 @@ describe("Offset Helper", function () { ) ).to.be.eql("1.0"); }); + + it("OffsetHelper contract should hold 1.0 TCO2s", async function () { + this.timeout(120000); + + // since I have no NCT, I need to impersonate an account that has it + // I'll also give it some wei, just to be safe + await network.provider.request({ + method: "hardhat_stopImpersonatingAccount", + params: [addresses.myAddress], + }); + await network.provider.request({ + method: "hardhat_impersonateAccount", + params: ["0xdab7f2bc9aa986d9759718203c9a76534894e900"], + }); + await network.provider.send("hardhat_setBalance", [ + "0xdab7f2bc9aa986d9759718203c9a76534894e900", + ethers.utils.parseEther("2.0").toHexString(), + ]); + const signer = await ethers.getSigner( + "0xdab7f2bc9aa986d9759718203c9a76534894e900" + ); + + // @ts-ignore + nct = new ethers.Contract(addresses.nctAddress, nctAbi.abi, owner); + + await ( + await nct + .connect(signer) + .approve(offsetHelper.address, ethers.utils.parseEther("1.0")) + ).wait(); + + await ( + await offsetHelper + .connect(signer) + .deposit(addresses.nctAddress, ethers.utils.parseEther("1.0")) + ).wait(); + + await ( + await offsetHelper + .connect(signer) + .autoRedeem(addresses.nctAddress, ethers.utils.parseEther("1.0")) + ).wait(); + + const scoredTCO2s = await nct.getScoredTCO2s(); + + let tokenContract: ToucanCarbonOffsets; + let totalTCO2sHeld = ethers.utils.parseEther("0.0"); + + await Promise.all( + scoredTCO2s.map(async (token) => { + // @ts-ignore + tokenContract = new ethers.Contract(token, tcoAbi.abi, owner); + const balance = await tokenContract.balanceOf(offsetHelper.address); + totalTCO2sHeld = totalTCO2sHeld.add(balance); + }) + ); + + expect(ethers.utils.formatEther(totalTCO2sHeld)).to.be.eql("1.0"); + }); }); describe("autoRetire()", function () { @@ -301,6 +515,7 @@ describe("Offset Helper", function () { .autoRetire(ethers.utils.parseEther("1.0"), addresses.nctAddress) ).wait(); + // I expect the user's in-contract TCO2 balance to be 0.0 expect( ethers.utils.formatEther( await offsetHelper.tco2Balance( diff --git a/tsconfig.json b/tsconfig.json index 6ca4a9b..728f1e8 100644 --- a/tsconfig.json +++ b/tsconfig.json @@ -8,6 +8,6 @@ "declaration": true, "resolveJsonModule": true }, - "include": ["./scripts", "./test", "./typechain"], + "include": ["./scripts", "./test", "./typechain", "./utils"], "files": ["./hardhat.config.ts"] }